From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- gfx/skia/LICENSE | 27 + gfx/skia/README | 3 + gfx/skia/README_COMMITTING | 10 + gfx/skia/README_MOZILLA | 33 + gfx/skia/generate_mozbuild.py | 423 ++ gfx/skia/moz.build | 497 ++ gfx/skia/patches/README | 2 + ...g-687189-Implement-SkPaint-getPosTextPath.patch | 66 + ...ug-777614-Re-add-our-SkUserConfig.h-r-nrc.patch | 34 + ...Skia-cross-compilation-for-Windows-fails-.patch | 26 + ...Remove-unused-find_from_uniqueID-function.patch | 38 + ...8366-Dont-invalidate-all-radial-gradients.patch | 30 + ...Re-apply-Bug-795549-Move-TileProc-functio.patch | 50 + .../archive/0003-SkUserConfig-for-Mozilla.patch | 39 + ...4-Bug-722011-Fix-trailing-commas-in-enums.patch | 280 + ...Re-apply-bug-719872-Fix-crash-on-Android-.patch | 684 ++ .../0005-Bug-731384-Fix-clang-SK_OVERRIDE.patch | 36 + ...Add-a-new-SkFontHost-that-takes-a-cairo_s.patch | 449 ++ ...Re-apply-bug-687188-Expand-the-gradient-c.patch | 198 + ...0006-Bug-751814-ARM-EDSP-ARMv6-Skia-fixes.patch | 147 + ...Re-apply-Bug-777614-Add-our-SkUserConfig..patch | 27 + .../0007-Bug-719872-Old-Android-FontHost.patch | 702 ++ ...Re-apply-bug-687188-Expand-the-gradient-c.patch | 168 + .../0008-Bug-687188-Skia-radial-gradients.patch | 173 + ...Re-apply-759683-Handle-compilers-that-don.patch | 35 + .../archive/0009-Bug-755869-FreeBSD-Hurd.patch | 28 + ...Re-apply-759683-Handle-compilers-that-don.patch | 40 + ...Re-apply-bug-751418-Add-our-own-GrUserCon.patch | 23 + .../patches/archive/0010-Bug-689069-ARM-Opts.patch | 36 + ...6892-Add-new-blending-modes-to-SkXfermode.patch | 698 ++ ...Re-apply-bug-817356-Patch-Skia-to-recogni.patch | 22 + .../archive/0011-Bug-719575-Fix-clang-build.patch | 28 + ...no-anon-namespace-around-SkNO_RETURN_HINT.patch | 31 + ...18-Add-our-own-GrUserConfig-r-mattwoodrow.patch | 29 + .../0012-Bug-759683-make-ssse3-conditional.patch | 22 + ...Fix-compile-error-on-gcc-in-Skia-GL-r-mat.patch | 26 + .../patches/archive/0013-Bug-761890-fonts.patch | 162 + .../archive/0014-Bug-765038-Fix-clang-build.patch | 29 + .../patches/archive/0015-Bug-766017-warnings.patch | 865 +++ .../archive/0016-Bug-718849-Radial-gradients.patch | 400 ++ .../0017-Bug-740194-SkMemory-mozalloc.patch | 73 + .../archive/0018-Bug-817356-PPC-defines.patch | 14 + ...Re-apply-bug-795538-Ensure-we-use-the-cor.patch | 39 + ...90539-Fix-SK_COMPILE_ASSERT-build-warning.patch | 39 + .../0024-Bug-887318-fix-bgra-readback.patch | 217 + ...-Bug-896049-Add-default-Value-SK_OVERRIDE.patch | 26 + .../archive/0026-Bug-901208-Fix-ARM-v4t.patch | 83 + ...030-Bug-939629-Add-missing-include-guards.patch | 94 + .../0031-Bug-945588-Add-include-guard.patch | 39 + ...32-Bug-974900-More-missing-include-guards.patch | 148 + .../0033-Bug-974900-undef-interface-windows.patch | 27 + gfx/skia/patches/archive/SkPostConfig.patch | 32 + gfx/skia/patches/archive/arm-fixes.patch | 191 + gfx/skia/patches/archive/arm-opts.patch | 41 + .../patches/archive/fix-comma-end-enum-list.patch | 380 + gfx/skia/patches/archive/fix-gradient-clamp.patch | 211 + gfx/skia/patches/archive/getpostextpath.patch | 70 + gfx/skia/patches/archive/mingw-fix.patch | 57 + gfx/skia/patches/archive/new-aa.patch | 22 + .../patches/archive/old-android-fonthost.patch | 530 ++ gfx/skia/patches/archive/radial-gradients.patch | 25 + .../patches/archive/skia_restrict_problem.patch | 461 ++ .../patches/archive/uninitialized-margin.patch | 22 + gfx/skia/patches/archive/user-config.patch | 40 + .../skia/include/android/SkAndroidFrameworkUtils.h | 56 + gfx/skia/skia/include/android/SkAnimatedImage.h | 167 + gfx/skia/skia/include/android/SkBRDAllocator.h | 29 + .../skia/include/android/SkBitmapRegionDecoder.h | 92 + .../skia/include/atlastext/SkAtlasTextContext.h | 42 + gfx/skia/skia/include/atlastext/SkAtlasTextFont.h | 38 + .../skia/include/atlastext/SkAtlasTextRenderer.h | 72 + .../skia/include/atlastext/SkAtlasTextTarget.h | 100 + gfx/skia/skia/include/c/sk_canvas.h | 159 + gfx/skia/skia/include/c/sk_colorspace.h | 25 + gfx/skia/skia/include/c/sk_data.h | 70 + gfx/skia/skia/include/c/sk_image.h | 71 + gfx/skia/skia/include/c/sk_imageinfo.h | 62 + gfx/skia/skia/include/c/sk_maskfilter.h | 47 + gfx/skia/skia/include/c/sk_matrix.h | 49 + gfx/skia/skia/include/c/sk_paint.h | 145 + gfx/skia/skia/include/c/sk_path.h | 84 + gfx/skia/skia/include/c/sk_picture.h | 70 + gfx/skia/skia/include/c/sk_shader.h | 143 + gfx/skia/skia/include/c/sk_surface.h | 73 + gfx/skia/skia/include/c/sk_types.h | 256 + gfx/skia/skia/include/codec/SkAndroidCodec.h | 287 + gfx/skia/skia/include/codec/SkCodec.h | 954 +++ gfx/skia/skia/include/codec/SkCodecAnimation.h | 43 + gfx/skia/skia/include/codec/SkEncodedOrigin.h | 47 + gfx/skia/skia/include/config/SkUserConfig.h | 131 + gfx/skia/skia/include/core/SkAnnotation.h | 50 + gfx/skia/skia/include/core/SkBBHFactory.h | 31 + gfx/skia/skia/include/core/SkBitmap.h | 1166 +++ gfx/skia/skia/include/core/SkBlendMode.h | 65 + gfx/skia/skia/include/core/SkBlurTypes.h | 22 + gfx/skia/skia/include/core/SkCanvas.h | 2789 +++++++ .../skia/include/core/SkCanvasVirtualEnforcer.h | 92 + gfx/skia/skia/include/core/SkClipOp.h | 33 + gfx/skia/skia/include/core/SkColor.h | 414 ++ gfx/skia/skia/include/core/SkColorFilter.h | 167 + gfx/skia/skia/include/core/SkColorPriv.h | 151 + gfx/skia/skia/include/core/SkColorSpace.h | 249 + gfx/skia/skia/include/core/SkContourMeasure.h | 148 + gfx/skia/skia/include/core/SkCoverageMode.h | 30 + gfx/skia/skia/include/core/SkCubicMap.h | 45 + gfx/skia/skia/include/core/SkData.h | 182 + gfx/skia/skia/include/core/SkDataTable.h | 119 + .../include/core/SkDeferredDisplayListRecorder.h | 173 + gfx/skia/skia/include/core/SkDeque.h | 141 + gfx/skia/skia/include/core/SkDocument.h | 91 + gfx/skia/skia/include/core/SkDrawLooper.h | 136 + gfx/skia/skia/include/core/SkDrawable.h | 160 + gfx/skia/skia/include/core/SkEncodedImageFormat.h | 34 + gfx/skia/skia/include/core/SkExecutor.h | 34 + gfx/skia/skia/include/core/SkFilterQuality.h | 26 + gfx/skia/skia/include/core/SkFlattenable.h | 106 + gfx/skia/skia/include/core/SkFont.h | 538 ++ gfx/skia/skia/include/core/SkFontArguments.h | 79 + gfx/skia/skia/include/core/SkFontLCDConfig.h | 58 + gfx/skia/skia/include/core/SkFontMetrics.h | 110 + gfx/skia/skia/include/core/SkFontMgr.h | 163 + gfx/skia/skia/include/core/SkFontParameters.h | 38 + gfx/skia/skia/include/core/SkFontStyle.h | 80 + gfx/skia/skia/include/core/SkFontTypes.h | 25 + gfx/skia/skia/include/core/SkGraphics.h | 179 + gfx/skia/skia/include/core/SkICC.h | 19 + gfx/skia/skia/include/core/SkImage.h | 1143 +++ gfx/skia/skia/include/core/SkImageEncoder.h | 70 + gfx/skia/skia/include/core/SkImageFilter.h | 164 + gfx/skia/skia/include/core/SkImageGenerator.h | 207 + gfx/skia/skia/include/core/SkImageInfo.h | 743 ++ gfx/skia/skia/include/core/SkMallocPixelRef.h | 42 + gfx/skia/skia/include/core/SkMaskFilter.h | 77 + gfx/skia/skia/include/core/SkMath.h | 75 + gfx/skia/skia/include/core/SkMatrix.h | 1863 +++++ gfx/skia/skia/include/core/SkMatrix44.h | 480 ++ gfx/skia/skia/include/core/SkMilestone.h | 9 + gfx/skia/skia/include/core/SkMultiPictureDraw.h | 75 + gfx/skia/skia/include/core/SkOverdrawCanvas.h | 74 + gfx/skia/skia/include/core/SkPaint.h | 668 ++ gfx/skia/skia/include/core/SkPath.h | 1746 +++++ gfx/skia/skia/include/core/SkPathEffect.h | 187 + gfx/skia/skia/include/core/SkPathMeasure.h | 88 + gfx/skia/skia/include/core/SkPathTypes.h | 54 + gfx/skia/skia/include/core/SkPicture.h | 258 + gfx/skia/skia/include/core/SkPictureRecorder.h | 125 + gfx/skia/skia/include/core/SkPixelRef.h | 129 + gfx/skia/skia/include/core/SkPixmap.h | 713 ++ gfx/skia/skia/include/core/SkPngChunkReader.h | 45 + gfx/skia/skia/include/core/SkPoint.h | 552 ++ gfx/skia/skia/include/core/SkPoint3.h | 157 + gfx/skia/skia/include/core/SkPostConfig.h | 306 + gfx/skia/skia/include/core/SkPreConfig.h | 200 + gfx/skia/skia/include/core/SkPromiseImageTexture.h | 59 + gfx/skia/skia/include/core/SkRRect.h | 519 ++ gfx/skia/skia/include/core/SkRSXform.h | 69 + gfx/skia/skia/include/core/SkRWBuffer.h | 111 + .../skia/include/core/SkRasterHandleAllocator.h | 87 + gfx/skia/skia/include/core/SkRect.h | 1335 ++++ gfx/skia/skia/include/core/SkRefCnt.h | 370 + gfx/skia/skia/include/core/SkRegion.h | 621 ++ gfx/skia/skia/include/core/SkScalar.h | 208 + gfx/skia/skia/include/core/SkSerialProcs.h | 73 + gfx/skia/skia/include/core/SkShader.h | 154 + gfx/skia/skia/include/core/SkSize.h | 92 + gfx/skia/skia/include/core/SkStream.h | 515 ++ gfx/skia/skia/include/core/SkString.h | 299 + gfx/skia/skia/include/core/SkStrokeRec.h | 154 + gfx/skia/skia/include/core/SkSurface.h | 1046 +++ .../skia/include/core/SkSurfaceCharacterization.h | 228 + gfx/skia/skia/include/core/SkSurfaceProps.h | 89 + gfx/skia/skia/include/core/SkSwizzle.h | 19 + gfx/skia/skia/include/core/SkTextBlob.h | 418 ++ gfx/skia/skia/include/core/SkTileMode.h | 41 + gfx/skia/skia/include/core/SkTime.h | 62 + gfx/skia/skia/include/core/SkTraceMemoryDump.h | 90 + gfx/skia/skia/include/core/SkTypeface.h | 455 ++ gfx/skia/skia/include/core/SkTypes.h | 226 + gfx/skia/skia/include/core/SkUnPreMultiply.h | 56 + gfx/skia/skia/include/core/SkVertices.h | 281 + gfx/skia/skia/include/core/SkYUVAIndex.h | 81 + gfx/skia/skia/include/core/SkYUVASizeInfo.h | 58 + gfx/skia/skia/include/docs/SkPDFDocument.h | 202 + gfx/skia/skia/include/docs/SkXPSDocument.h | 27 + gfx/skia/skia/include/effects/Sk1DPathEffect.h | 77 + gfx/skia/skia/include/effects/Sk2DPathEffect.h | 105 + .../skia/include/effects/SkAlphaThresholdFilter.h | 36 + .../skia/include/effects/SkArithmeticImageFilter.h | 36 + gfx/skia/skia/include/effects/SkBlurDrawLooper.h | 24 + gfx/skia/skia/include/effects/SkBlurImageFilter.h | 46 + gfx/skia/skia/include/effects/SkBlurMaskFilter.h | 35 + .../include/effects/SkColorFilterImageFilter.h | 27 + gfx/skia/skia/include/effects/SkColorMatrix.h | 66 + .../skia/include/effects/SkColorMatrixFilter.h | 25 + .../skia/include/effects/SkComposeImageFilter.h | 24 + gfx/skia/skia/include/effects/SkCornerPathEffect.h | 43 + gfx/skia/skia/include/effects/SkDashPathEffect.h | 39 + .../skia/include/effects/SkDiscretePathEffect.h | 53 + .../skia/include/effects/SkDisplacementMapEffect.h | 49 + .../skia/include/effects/SkDropShadowImageFilter.h | 38 + gfx/skia/skia/include/effects/SkGradientShader.h | 259 + .../skia/include/effects/SkHighContrastFilter.h | 84 + gfx/skia/skia/include/effects/SkImageFilters.h | 434 ++ gfx/skia/skia/include/effects/SkImageSource.h | 29 + gfx/skia/skia/include/effects/SkLayerDrawLooper.h | 150 + .../skia/include/effects/SkLightingImageFilter.h | 46 + gfx/skia/skia/include/effects/SkLumaColorFilter.h | 57 + .../skia/include/effects/SkMagnifierImageFilter.h | 28 + .../effects/SkMatrixConvolutionImageFilter.h | 88 + gfx/skia/skia/include/effects/SkMergeImageFilter.h | 34 + .../skia/include/effects/SkMorphologyImageFilter.h | 40 + .../skia/include/effects/SkOffsetImageFilter.h | 26 + gfx/skia/skia/include/effects/SkOpPathEffect.h | 38 + .../skia/include/effects/SkOverdrawColorFilter.h | 54 + gfx/skia/skia/include/effects/SkPaintImageFilter.h | 36 + .../skia/include/effects/SkPerlinNoiseShader.h | 60 + .../skia/include/effects/SkPictureImageFilter.h | 35 + gfx/skia/skia/include/effects/SkShaderMaskFilter.h | 24 + gfx/skia/skia/include/effects/SkTableColorFilter.h | 42 + gfx/skia/skia/include/effects/SkTableMaskFilter.h | 37 + gfx/skia/skia/include/effects/SkTileImageFilter.h | 31 + gfx/skia/skia/include/effects/SkTrimPathEffect.h | 41 + .../skia/include/effects/SkXfermodeImageFilter.h | 34 + gfx/skia/skia/include/encode/SkEncoder.h | 42 + gfx/skia/skia/include/encode/SkJpegEncoder.h | 97 + gfx/skia/skia/include/encode/SkPngEncoder.h | 99 + gfx/skia/skia/include/encode/SkWebpEncoder.h | 48 + gfx/skia/skia/include/gpu/GrBackendDrawableInfo.h | 44 + gfx/skia/skia/include/gpu/GrBackendSemaphore.h | 96 + gfx/skia/skia/include/gpu/GrBackendSurface.h | 453 ++ gfx/skia/skia/include/gpu/GrConfig.h | 164 + gfx/skia/skia/include/gpu/GrContext.h | 545 ++ gfx/skia/skia/include/gpu/GrContextOptions.h | 270 + .../skia/include/gpu/GrContextThreadSafeProxy.h | 102 + gfx/skia/skia/include/gpu/GrDriverBugWorkarounds.h | 51 + .../include/gpu/GrDriverBugWorkaroundsAutogen.h | 43 + gfx/skia/skia/include/gpu/GrGpuResource.h | 321 + gfx/skia/skia/include/gpu/GrSurface.h | 157 + gfx/skia/skia/include/gpu/GrTexture.h | 102 + gfx/skia/skia/include/gpu/GrTypes.h | 349 + gfx/skia/skia/include/gpu/dawn/GrDawnTypes.h | 40 + gfx/skia/skia/include/gpu/gl/GrGLAssembleHelpers.h | 11 + .../skia/include/gpu/gl/GrGLAssembleInterface.h | 39 + gfx/skia/skia/include/gpu/gl/GrGLConfig.h | 99 + gfx/skia/skia/include/gpu/gl/GrGLConfig_chrome.h | 22 + gfx/skia/skia/include/gpu/gl/GrGLExtensions.h | 78 + gfx/skia/skia/include/gpu/gl/GrGLFunctions.h | 320 + gfx/skia/skia/include/gpu/gl/GrGLInterface.h | 340 + gfx/skia/skia/include/gpu/gl/GrGLTypes.h | 186 + gfx/skia/skia/include/gpu/mock/GrMockTypes.h | 118 + gfx/skia/skia/include/gpu/mtl/GrMtlTypes.h | 39 + gfx/skia/skia/include/gpu/vk/GrVkBackendContext.h | 76 + gfx/skia/skia/include/gpu/vk/GrVkExtensions.h | 63 + gfx/skia/skia/include/gpu/vk/GrVkMemoryAllocator.h | 90 + gfx/skia/skia/include/gpu/vk/GrVkTypes.h | 256 + gfx/skia/skia/include/gpu/vk/GrVkVulkan.h | 32 + gfx/skia/skia/include/pathops/SkPathOps.h | 113 + gfx/skia/skia/include/ports/SkCFObject.h | 138 + .../skia/include/ports/SkFontConfigInterface.h | 115 + .../include/ports/SkFontMgr_FontConfigInterface.h | 20 + gfx/skia/skia/include/ports/SkFontMgr_android.h | 45 + gfx/skia/skia/include/ports/SkFontMgr_directory.h | 21 + gfx/skia/skia/include/ports/SkFontMgr_empty.h | 21 + gfx/skia/skia/include/ports/SkFontMgr_fontconfig.h | 22 + gfx/skia/skia/include/ports/SkFontMgr_fuchsia.h | 19 + gfx/skia/skia/include/ports/SkFontMgr_indirect.h | 99 + gfx/skia/skia/include/ports/SkImageGeneratorCG.h | 20 + gfx/skia/skia/include/ports/SkImageGeneratorWIC.h | 35 + gfx/skia/skia/include/ports/SkRemotableFontMgr.h | 139 + gfx/skia/skia/include/ports/SkTypeface_cairo.h | 18 + gfx/skia/skia/include/ports/SkTypeface_mac.h | 49 + gfx/skia/skia/include/ports/SkTypeface_win.h | 93 + gfx/skia/skia/include/private/GrContext_Base.h | 89 + gfx/skia/skia/include/private/GrGLTypesPriv.h | 94 + gfx/skia/skia/include/private/GrImageContext.h | 57 + gfx/skia/skia/include/private/GrRecordingContext.h | 137 + gfx/skia/skia/include/private/GrResourceKey.h | 357 + gfx/skia/skia/include/private/GrSharedEnums.h | 37 + gfx/skia/skia/include/private/GrSingleOwner.h | 55 + gfx/skia/skia/include/private/GrTypesPriv.h | 1341 ++++ gfx/skia/skia/include/private/GrVkTypesPriv.h | 50 + gfx/skia/skia/include/private/SkBitmaskEnum.h | 48 + gfx/skia/skia/include/private/SkChecksum.h | 71 + gfx/skia/skia/include/private/SkColorData.h | 444 ++ .../skia/include/private/SkDeferredDisplayList.h | 78 + gfx/skia/skia/include/private/SkEncodedInfo.h | 247 + gfx/skia/skia/include/private/SkFixed.h | 140 + gfx/skia/skia/include/private/SkFloatBits.h | 91 + gfx/skia/skia/include/private/SkFloatingPoint.h | 265 + gfx/skia/skia/include/private/SkHalf.h | 85 + gfx/skia/skia/include/private/SkImageInfoPriv.h | 135 + gfx/skia/skia/include/private/SkMacros.h | 67 + gfx/skia/skia/include/private/SkMalloc.h | 136 + gfx/skia/skia/include/private/SkMutex.h | 52 + gfx/skia/skia/include/private/SkNoncopyable.h | 30 + gfx/skia/skia/include/private/SkNx.h | 441 ++ gfx/skia/skia/include/private/SkNx_neon.h | 740 ++ gfx/skia/skia/include/private/SkNx_sse.h | 894 +++ gfx/skia/skia/include/private/SkOnce.h | 50 + gfx/skia/skia/include/private/SkPathRef.h | 492 ++ gfx/skia/skia/include/private/SkSafe32.h | 34 + gfx/skia/skia/include/private/SkSafe_math.h | 52 + gfx/skia/skia/include/private/SkSemaphore.h | 79 + gfx/skia/skia/include/private/SkShadowFlags.h | 23 + gfx/skia/skia/include/private/SkSpinlock.h | 57 + gfx/skia/skia/include/private/SkTArray.h | 641 ++ gfx/skia/skia/include/private/SkTDArray.h | 372 + gfx/skia/skia/include/private/SkTFitsIn.h | 99 + gfx/skia/skia/include/private/SkTHash.h | 362 + gfx/skia/skia/include/private/SkTLogic.h | 106 + gfx/skia/skia/include/private/SkTemplates.h | 455 ++ .../skia/include/private/SkThreadAnnotations.h | 80 + gfx/skia/skia/include/private/SkThreadID.h | 19 + gfx/skia/skia/include/private/SkTo.h | 28 + gfx/skia/skia/include/private/SkVx.h | 527 ++ gfx/skia/skia/include/private/SkWeakRefCnt.h | 170 + gfx/skia/skia/include/svg/SkSVGCanvas.h | 36 + gfx/skia/skia/include/third_party/skcms/LICENSE | 29 + gfx/skia/skia/include/third_party/skcms/skcms.h | 332 + gfx/skia/skia/include/third_party/vulkan/LICENSE | 29 + .../third_party/vulkan/vulkan/vk_platform.h | 92 + .../include/third_party/vulkan/vulkan/vulkan.h | 79 + .../third_party/vulkan/vulkan/vulkan_android.h | 126 + .../third_party/vulkan/vulkan/vulkan_core.h | 7576 ++++++++++++++++++++ .../include/third_party/vulkan/vulkan/vulkan_ios.h | 58 + .../third_party/vulkan/vulkan/vulkan_macos.h | 58 + .../third_party/vulkan/vulkan/vulkan_win32.h | 276 + .../include/third_party/vulkan/vulkan/vulkan_xcb.h | 66 + gfx/skia/skia/include/utils/Sk3D.h | 19 + gfx/skia/skia/include/utils/SkAnimCodecPlayer.h | 60 + gfx/skia/skia/include/utils/SkBase64.h | 39 + gfx/skia/skia/include/utils/SkCamera.h | 154 + gfx/skia/skia/include/utils/SkCanvasStateUtils.h | 78 + gfx/skia/skia/include/utils/SkEventTracer.h | 74 + .../skia/include/utils/SkFrontBufferedStream.h | 39 + gfx/skia/skia/include/utils/SkInterpolator.h | 138 + gfx/skia/skia/include/utils/SkLua.h | 69 + gfx/skia/skia/include/utils/SkLuaCanvas.h | 72 + gfx/skia/skia/include/utils/SkNWayCanvas.h | 94 + gfx/skia/skia/include/utils/SkNoDrawCanvas.h | 86 + gfx/skia/skia/include/utils/SkNullCanvas.h | 18 + gfx/skia/skia/include/utils/SkPaintFilterCanvas.h | 116 + gfx/skia/skia/include/utils/SkParse.h | 32 + gfx/skia/skia/include/utils/SkParsePath.h | 23 + gfx/skia/skia/include/utils/SkRandom.h | 169 + gfx/skia/skia/include/utils/SkShadowUtils.h | 56 + gfx/skia/skia/include/utils/SkTextUtils.h | 38 + gfx/skia/skia/include/utils/SkTraceEventPhase.h | 19 + gfx/skia/skia/include/utils/mac/SkCGUtils.h | 86 + .../skia/src/android/SkAndroidFrameworkUtils.cpp | 82 + gfx/skia/skia/src/android/SkAnimatedImage.cpp | 359 + gfx/skia/skia/src/android/SkBitmapRegionCodec.cpp | 118 + gfx/skia/skia/src/android/SkBitmapRegionCodec.h | 49 + .../skia/src/android/SkBitmapRegionDecoder.cpp | 47 + .../skia/src/android/SkBitmapRegionDecoderPriv.h | 61 + gfx/skia/skia/src/atlastext/SkAtlasTextContext.cpp | 17 + gfx/skia/skia/src/atlastext/SkAtlasTextTarget.cpp | 255 + .../src/atlastext/SkInternalAtlasTextContext.cpp | 128 + .../src/atlastext/SkInternalAtlasTextContext.h | 83 + gfx/skia/skia/src/c/sk_c_from_to.h | 34 + gfx/skia/skia/src/c/sk_effects.cpp | 186 + gfx/skia/skia/src/c/sk_imageinfo.cpp | 143 + gfx/skia/skia/src/c/sk_paint.cpp | 173 + gfx/skia/skia/src/c/sk_surface.cpp | 443 ++ gfx/skia/skia/src/c/sk_types_priv.h | 41 + gfx/skia/skia/src/codec/SkAndroidCodec.cpp | 416 ++ gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp | 30 + gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h | 37 + gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp | 16 + gfx/skia/skia/src/codec/SkBmpBaseCodec.h | 38 + gfx/skia/skia/src/codec/SkBmpCodec.cpp | 650 ++ gfx/skia/skia/src/codec/SkBmpCodec.h | 151 + gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp | 105 + gfx/skia/skia/src/codec/SkBmpMaskCodec.h | 62 + gfx/skia/skia/src/codec/SkBmpRLECodec.cpp | 568 ++ gfx/skia/skia/src/codec/SkBmpRLECodec.h | 120 + gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp | 341 + gfx/skia/skia/src/codec/SkBmpStandardCodec.h | 92 + gfx/skia/skia/src/codec/SkCodec.cpp | 867 +++ gfx/skia/skia/src/codec/SkCodecAnimationPriv.h | 32 + gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp | 99 + gfx/skia/skia/src/codec/SkCodecImageGenerator.h | 47 + gfx/skia/skia/src/codec/SkCodecPriv.h | 251 + gfx/skia/skia/src/codec/SkColorTable.cpp | 23 + gfx/skia/skia/src/codec/SkColorTable.h | 50 + gfx/skia/skia/src/codec/SkEncodedInfo.cpp | 28 + gfx/skia/skia/src/codec/SkFrameHolder.h | 201 + gfx/skia/skia/src/codec/SkGifCodec.cpp | 533 ++ gfx/skia/skia/src/codec/SkGifCodec.h | 156 + gfx/skia/skia/src/codec/SkHeifCodec.cpp | 472 ++ gfx/skia/skia/src/codec/SkHeifCodec.h | 128 + gfx/skia/skia/src/codec/SkIcoCodec.cpp | 384 + gfx/skia/skia/src/codec/SkIcoCodec.h | 99 + gfx/skia/skia/src/codec/SkJpegCodec.cpp | 974 +++ gfx/skia/skia/src/codec/SkJpegCodec.h | 144 + gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp | 100 + gfx/skia/skia/src/codec/SkJpegDecoderMgr.h | 75 + gfx/skia/skia/src/codec/SkJpegPriv.h | 53 + gfx/skia/skia/src/codec/SkJpegUtility.cpp | 142 + gfx/skia/skia/src/codec/SkJpegUtility.h | 44 + gfx/skia/skia/src/codec/SkMaskSwizzler.cpp | 568 ++ gfx/skia/skia/src/codec/SkMaskSwizzler.h | 72 + gfx/skia/skia/src/codec/SkMasks.cpp | 162 + gfx/skia/skia/src/codec/SkMasks.h | 86 + gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp | 61 + gfx/skia/skia/src/codec/SkParseEncodedOrigin.h | 19 + gfx/skia/skia/src/codec/SkPngCodec.cpp | 1194 +++ gfx/skia/skia/src/codec/SkPngCodec.h | 121 + gfx/skia/skia/src/codec/SkPngPriv.h | 19 + gfx/skia/skia/src/codec/SkRawCodec.cpp | 797 ++ gfx/skia/skia/src/codec/SkRawCodec.h | 65 + gfx/skia/skia/src/codec/SkSampledCodec.cpp | 352 + gfx/skia/skia/src/codec/SkSampledCodec.h | 59 + gfx/skia/skia/src/codec/SkSampler.cpp | 64 + gfx/skia/skia/src/codec/SkSampler.h | 84 + gfx/skia/skia/src/codec/SkScalingCodec.h | 39 + gfx/skia/skia/src/codec/SkStreamBuffer.cpp | 88 + gfx/skia/skia/src/codec/SkStreamBuffer.h | 116 + gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h | 76 + gfx/skia/skia/src/codec/SkSwizzler.cpp | 1237 ++++ gfx/skia/skia/src/codec/SkSwizzler.h | 222 + gfx/skia/skia/src/codec/SkWbmpCodec.cpp | 193 + gfx/skia/skia/src/codec/SkWbmpCodec.h | 62 + gfx/skia/skia/src/codec/SkWebpCodec.cpp | 567 ++ gfx/skia/skia/src/codec/SkWebpCodec.h | 104 + gfx/skia/skia/src/codec/SkWuffsCodec.cpp | 870 +++ gfx/skia/skia/src/codec/SkWuffsCodec.h | 17 + gfx/skia/skia/src/core/Sk4px.h | 251 + gfx/skia/skia/src/core/SkAAClip.cpp | 2162 ++++++ gfx/skia/skia/src/core/SkAAClip.h | 137 + gfx/skia/skia/src/core/SkATrace.cpp | 83 + gfx/skia/skia/src/core/SkATrace.h | 53 + gfx/skia/skia/src/core/SkAdvancedTypefaceMetrics.h | 73 + gfx/skia/skia/src/core/SkAlphaRuns.cpp | 78 + gfx/skia/skia/src/core/SkAnalyticEdge.cpp | 512 ++ gfx/skia/skia/src/core/SkAnalyticEdge.h | 204 + gfx/skia/skia/src/core/SkAnnotation.cpp | 48 + gfx/skia/skia/src/core/SkAnnotationKeys.h | 33 + gfx/skia/skia/src/core/SkAntiRun.h | 196 + gfx/skia/skia/src/core/SkArenaAlloc.cpp | 166 + gfx/skia/skia/src/core/SkArenaAlloc.h | 240 + gfx/skia/skia/src/core/SkArenaAllocList.h | 80 + gfx/skia/skia/src/core/SkAutoBlitterChoose.h | 56 + gfx/skia/skia/src/core/SkAutoMalloc.h | 177 + gfx/skia/skia/src/core/SkAutoPixmapStorage.cpp | 66 + gfx/skia/skia/src/core/SkAutoPixmapStorage.h | 81 + gfx/skia/skia/src/core/SkBBHFactory.cpp | 15 + gfx/skia/skia/src/core/SkBBoxHierarchy.h | 43 + gfx/skia/skia/src/core/SkBigPicture.cpp | 71 + gfx/skia/skia/src/core/SkBigPicture.h | 71 + gfx/skia/skia/src/core/SkBitmap.cpp | 630 ++ gfx/skia/skia/src/core/SkBitmapCache.cpp | 300 + gfx/skia/skia/src/core/SkBitmapCache.h | 67 + gfx/skia/skia/src/core/SkBitmapController.cpp | 106 + gfx/skia/skia/src/core/SkBitmapController.h | 53 + gfx/skia/skia/src/core/SkBitmapDevice.cpp | 820 +++ gfx/skia/skia/src/core/SkBitmapDevice.h | 191 + gfx/skia/skia/src/core/SkBitmapFilter.h | 209 + gfx/skia/skia/src/core/SkBitmapProcState.cpp | 660 ++ gfx/skia/skia/src/core/SkBitmapProcState.h | 216 + .../src/core/SkBitmapProcState_matrixProcs.cpp | 830 +++ gfx/skia/skia/src/core/SkBitmapScaler.cpp | 254 + gfx/skia/skia/src/core/SkBitmapScaler.h | 46 + gfx/skia/skia/src/core/SkBlendMode.cpp | 168 + gfx/skia/skia/src/core/SkBlendModePriv.h | 50 + gfx/skia/skia/src/core/SkBlitBWMaskTemplate.h | 127 + gfx/skia/skia/src/core/SkBlitRow.h | 38 + gfx/skia/skia/src/core/SkBlitRow_D32.cpp | 314 + gfx/skia/skia/src/core/SkBlitter.cpp | 884 +++ gfx/skia/skia/src/core/SkBlitter.h | 321 + gfx/skia/skia/src/core/SkBlitter_A8.cpp | 94 + gfx/skia/skia/src/core/SkBlitter_ARGB32.cpp | 1420 ++++ gfx/skia/skia/src/core/SkBlitter_RGB565.cpp | 139 + gfx/skia/skia/src/core/SkBlitter_Sprite.cpp | 215 + gfx/skia/skia/src/core/SkBlurMF.cpp | 938 +++ gfx/skia/skia/src/core/SkBlurMask.cpp | 658 ++ gfx/skia/skia/src/core/SkBlurMask.h | 87 + gfx/skia/skia/src/core/SkBlurPriv.h | 38 + gfx/skia/skia/src/core/SkBuffer.cpp | 87 + gfx/skia/skia/src/core/SkBuffer.h | 132 + gfx/skia/skia/src/core/SkCachedData.cpp | 177 + gfx/skia/skia/src/core/SkCachedData.h | 113 + gfx/skia/skia/src/core/SkCanvas.cpp | 3155 ++++++++ gfx/skia/skia/src/core/SkCanvasPriv.cpp | 99 + gfx/skia/skia/src/core/SkCanvasPriv.h | 58 + gfx/skia/skia/src/core/SkClipOpPriv.h | 21 + gfx/skia/skia/src/core/SkClipStack.cpp | 1100 +++ gfx/skia/skia/src/core/SkClipStack.h | 522 ++ gfx/skia/skia/src/core/SkClipStackDevice.cpp | 108 + gfx/skia/skia/src/core/SkClipStackDevice.h | 48 + gfx/skia/skia/src/core/SkColor.cpp | 165 + gfx/skia/skia/src/core/SkColorFilter.cpp | 501 ++ gfx/skia/skia/src/core/SkColorFilterPriv.h | 53 + gfx/skia/skia/src/core/SkColorFilter_Matrix.cpp | 135 + gfx/skia/skia/src/core/SkColorFilter_Matrix.h | 45 + gfx/skia/skia/src/core/SkColorSpace.cpp | 419 ++ gfx/skia/skia/src/core/SkColorSpacePriv.h | 108 + gfx/skia/skia/src/core/SkColorSpaceXformSteps.cpp | 165 + gfx/skia/skia/src/core/SkColorSpaceXformSteps.h | 62 + gfx/skia/skia/src/core/SkContourMeasure.cpp | 651 ++ gfx/skia/skia/src/core/SkConvertPixels.cpp | 234 + gfx/skia/skia/src/core/SkConvertPixels.h | 35 + gfx/skia/skia/src/core/SkConvolver.cpp | 272 + gfx/skia/skia/src/core/SkConvolver.h | 173 + gfx/skia/skia/src/core/SkCoreBlitters.h | 179 + gfx/skia/skia/src/core/SkCoverageModePriv.h | 43 + gfx/skia/skia/src/core/SkCpu.cpp | 160 + gfx/skia/skia/src/core/SkCpu.h | 114 + gfx/skia/skia/src/core/SkCubicClipper.cpp | 156 + gfx/skia/skia/src/core/SkCubicClipper.h | 34 + gfx/skia/skia/src/core/SkCubicMap.cpp | 109 + gfx/skia/skia/src/core/SkCubicSolver.h | 71 + gfx/skia/skia/src/core/SkData.cpp | 198 + gfx/skia/skia/src/core/SkDataTable.cpp | 131 + gfx/skia/skia/src/core/SkDebug.cpp | 14 + gfx/skia/skia/src/core/SkDeferredDisplayList.cpp | 26 + gfx/skia/skia/src/core/SkDeferredDisplayListPriv.h | 56 + .../src/core/SkDeferredDisplayListRecorder.cpp | 302 + gfx/skia/skia/src/core/SkDeque.cpp | 307 + gfx/skia/skia/src/core/SkDescriptor.cpp | 138 + gfx/skia/skia/src/core/SkDescriptor.h | 105 + gfx/skia/skia/src/core/SkDevice.cpp | 475 ++ gfx/skia/skia/src/core/SkDevice.h | 452 ++ gfx/skia/skia/src/core/SkDiscardableMemory.h | 65 + gfx/skia/skia/src/core/SkDistanceFieldGen.cpp | 562 ++ gfx/skia/skia/src/core/SkDistanceFieldGen.h | 75 + gfx/skia/skia/src/core/SkDocument.cpp | 78 + gfx/skia/skia/src/core/SkDraw.cpp | 1307 ++++ gfx/skia/skia/src/core/SkDraw.h | 170 + gfx/skia/skia/src/core/SkDrawLooper.cpp | 108 + gfx/skia/skia/src/core/SkDrawProcs.h | 42 + gfx/skia/skia/src/core/SkDrawShadowInfo.cpp | 192 + gfx/skia/skia/src/core/SkDrawShadowInfo.h | 77 + gfx/skia/skia/src/core/SkDraw_atlas.cpp | 120 + gfx/skia/skia/src/core/SkDraw_text.cpp | 139 + gfx/skia/skia/src/core/SkDraw_vertices.cpp | 376 + gfx/skia/skia/src/core/SkDrawable.cpp | 83 + gfx/skia/skia/src/core/SkEdge.cpp | 503 ++ gfx/skia/skia/src/core/SkEdge.h | 138 + gfx/skia/skia/src/core/SkEdgeBuilder.cpp | 405 ++ gfx/skia/skia/src/core/SkEdgeBuilder.h | 89 + gfx/skia/skia/src/core/SkEdgeClipper.cpp | 557 ++ gfx/skia/skia/src/core/SkEdgeClipper.h | 57 + gfx/skia/skia/src/core/SkEffectPriv.h | 30 + gfx/skia/skia/src/core/SkEndian.h | 194 + gfx/skia/skia/src/core/SkEnumerate.h | 76 + gfx/skia/skia/src/core/SkExchange.h | 25 + gfx/skia/skia/src/core/SkExecutor.cpp | 151 + gfx/skia/skia/src/core/SkFDot6.h | 78 + gfx/skia/skia/src/core/SkFixed15.h | 77 + gfx/skia/skia/src/core/SkFlattenable.cpp | 145 + gfx/skia/skia/src/core/SkFont.cpp | 543 ++ gfx/skia/skia/src/core/SkFontDescriptor.cpp | 148 + gfx/skia/skia/src/core/SkFontDescriptor.h | 84 + gfx/skia/skia/src/core/SkFontLCDConfig.cpp | 27 + gfx/skia/skia/src/core/SkFontMgr.cpp | 308 + gfx/skia/skia/src/core/SkFontMgrPriv.h | 14 + gfx/skia/skia/src/core/SkFontPriv.h | 104 + gfx/skia/skia/src/core/SkFontStream.cpp | 211 + gfx/skia/skia/src/core/SkFontStream.h | 49 + gfx/skia/skia/src/core/SkForceCPlusPlusLinking.cpp | 20 + gfx/skia/skia/src/core/SkFuzzLogging.h | 23 + gfx/skia/skia/src/core/SkGaussFilter.cpp | 109 + gfx/skia/skia/src/core/SkGaussFilter.h | 34 + gfx/skia/skia/src/core/SkGeometry.cpp | 1491 ++++ gfx/skia/skia/src/core/SkGeometry.h | 453 ++ .../skia/src/core/SkGlobalInitialization_core.cpp | 18 + gfx/skia/skia/src/core/SkGlyph.cpp | 353 + gfx/skia/skia/src/core/SkGlyph.h | 320 + gfx/skia/skia/src/core/SkGlyphBuffer.cpp | 83 + gfx/skia/skia/src/core/SkGlyphBuffer.h | 142 + gfx/skia/skia/src/core/SkGlyphRun.cpp | 378 + gfx/skia/skia/src/core/SkGlyphRun.h | 175 + gfx/skia/skia/src/core/SkGlyphRunPainter.cpp | 932 +++ gfx/skia/skia/src/core/SkGlyphRunPainter.h | 202 + gfx/skia/skia/src/core/SkGpuBlurUtils.cpp | 556 ++ gfx/skia/skia/src/core/SkGpuBlurUtils.h | 62 + gfx/skia/skia/src/core/SkGraphics.cpp | 135 + gfx/skia/skia/src/core/SkHalf.cpp | 97 + gfx/skia/skia/src/core/SkICC.cpp | 350 + gfx/skia/skia/src/core/SkICCPriv.h | 54 + gfx/skia/skia/src/core/SkIPoint16.h | 57 + gfx/skia/skia/src/core/SkImageFilter.cpp | 713 ++ gfx/skia/skia/src/core/SkImageFilterCache.cpp | 170 + gfx/skia/skia/src/core/SkImageFilterCache.h | 75 + gfx/skia/skia/src/core/SkImageFilterTypes.cpp | 97 + gfx/skia/skia/src/core/SkImageFilterTypes.h | 702 ++ gfx/skia/skia/src/core/SkImageFilter_Base.h | 442 ++ gfx/skia/skia/src/core/SkImageGenerator.cpp | 110 + gfx/skia/skia/src/core/SkImageInfo.cpp | 194 + gfx/skia/skia/src/core/SkImagePriv.h | 94 + gfx/skia/skia/src/core/SkLRUCache.h | 115 + gfx/skia/skia/src/core/SkLatticeIter.cpp | 302 + gfx/skia/skia/src/core/SkLatticeIter.h | 77 + gfx/skia/skia/src/core/SkLeanWindows.h | 34 + gfx/skia/skia/src/core/SkLineClipper.cpp | 276 + gfx/skia/skia/src/core/SkLineClipper.h | 45 + .../skia/src/core/SkLocalMatrixImageFilter.cpp | 55 + gfx/skia/skia/src/core/SkLocalMatrixImageFilter.h | 40 + gfx/skia/skia/src/core/SkMD5.cpp | 260 + gfx/skia/skia/src/core/SkMD5.h | 42 + gfx/skia/skia/src/core/SkMSAN.h | 42 + gfx/skia/skia/src/core/SkMakeUnique.h | 28 + gfx/skia/skia/src/core/SkMallocPixelRef.cpp | 94 + gfx/skia/skia/src/core/SkMask.cpp | 114 + gfx/skia/skia/src/core/SkMask.h | 243 + gfx/skia/skia/src/core/SkMaskBlurFilter.cpp | 1051 +++ gfx/skia/skia/src/core/SkMaskBlurFilter.h | 37 + gfx/skia/skia/src/core/SkMaskCache.cpp | 185 + gfx/skia/skia/src/core/SkMaskCache.h | 44 + gfx/skia/skia/src/core/SkMaskFilter.cpp | 720 ++ gfx/skia/skia/src/core/SkMaskFilterBase.h | 245 + gfx/skia/skia/src/core/SkMaskGamma.cpp | 125 + gfx/skia/skia/src/core/SkMaskGamma.h | 232 + gfx/skia/skia/src/core/SkMath.cpp | 93 + gfx/skia/skia/src/core/SkMathPriv.h | 251 + gfx/skia/skia/src/core/SkMatrix.cpp | 1833 +++++ gfx/skia/skia/src/core/SkMatrix44.cpp | 1032 +++ gfx/skia/skia/src/core/SkMatrixImageFilter.cpp | 131 + gfx/skia/skia/src/core/SkMatrixImageFilter.h | 53 + gfx/skia/skia/src/core/SkMatrixPriv.h | 155 + gfx/skia/skia/src/core/SkMatrixUtils.h | 38 + gfx/skia/skia/src/core/SkMessageBus.h | 125 + gfx/skia/skia/src/core/SkMiniRecorder.cpp | 137 + gfx/skia/skia/src/core/SkMiniRecorder.h | 58 + gfx/skia/skia/src/core/SkMipMap.cpp | 778 ++ gfx/skia/skia/src/core/SkMipMap.h | 82 + gfx/skia/skia/src/core/SkModeColorFilter.cpp | 141 + gfx/skia/skia/src/core/SkModeColorFilter.h | 46 + gfx/skia/skia/src/core/SkMultiPictureDraw.cpp | 109 + gfx/skia/skia/src/core/SkNextID.h | 21 + gfx/skia/skia/src/core/SkNormalFlatSource.cpp | 100 + gfx/skia/skia/src/core/SkNormalFlatSource.h | 47 + gfx/skia/skia/src/core/SkNormalMapSource.cpp | 257 + gfx/skia/skia/src/core/SkNormalMapSource.h | 57 + gfx/skia/skia/src/core/SkNormalSource.cpp | 19 + gfx/skia/skia/src/core/SkNormalSource.h | 82 + gfx/skia/skia/src/core/SkOSFile.h | 99 + gfx/skia/skia/src/core/SkOpts.cpp | 150 + gfx/skia/skia/src/core/SkOpts.h | 90 + gfx/skia/skia/src/core/SkOrderedReadBuffer.h | 9 + gfx/skia/skia/src/core/SkOverdrawCanvas.cpp | 274 + gfx/skia/skia/src/core/SkPaint.cpp | 552 ++ gfx/skia/skia/src/core/SkPaintDefaults.h | 31 + gfx/skia/skia/src/core/SkPaintPriv.cpp | 86 + gfx/skia/skia/src/core/SkPaintPriv.h | 74 + gfx/skia/skia/src/core/SkPath.cpp | 3739 ++++++++++ gfx/skia/skia/src/core/SkPathEffect.cpp | 195 + gfx/skia/skia/src/core/SkPathMakers.h | 88 + gfx/skia/skia/src/core/SkPathMeasure.cpp | 53 + gfx/skia/skia/src/core/SkPathMeasurePriv.h | 29 + gfx/skia/skia/src/core/SkPathPriv.h | 400 ++ gfx/skia/skia/src/core/SkPathRef.cpp | 702 ++ gfx/skia/skia/src/core/SkPath_serial.cpp | 292 + gfx/skia/skia/src/core/SkPicture.cpp | 333 + gfx/skia/skia/src/core/SkPictureCommon.h | 92 + gfx/skia/skia/src/core/SkPictureData.cpp | 536 ++ gfx/skia/skia/src/core/SkPictureData.h | 170 + gfx/skia/skia/src/core/SkPictureFlat.cpp | 20 + gfx/skia/skia/src/core/SkPictureFlat.h | 208 + gfx/skia/skia/src/core/SkPictureImageGenerator.cpp | 126 + gfx/skia/skia/src/core/SkPicturePlayback.cpp | 662 ++ gfx/skia/skia/src/core/SkPicturePlayback.h | 65 + gfx/skia/skia/src/core/SkPicturePriv.h | 106 + gfx/skia/skia/src/core/SkPictureRecord.cpp | 932 +++ gfx/skia/skia/src/core/SkPictureRecord.h | 271 + gfx/skia/skia/src/core/SkPictureRecorder.cpp | 134 + gfx/skia/skia/src/core/SkPixelRef.cpp | 152 + gfx/skia/skia/src/core/SkPixelRefPriv.h | 20 + gfx/skia/skia/src/core/SkPixmap.cpp | 594 ++ gfx/skia/skia/src/core/SkPixmapPriv.h | 65 + gfx/skia/skia/src/core/SkPoint.cpp | 164 + gfx/skia/skia/src/core/SkPoint3.cpp | 76 + gfx/skia/skia/src/core/SkPointPriv.h | 127 + gfx/skia/skia/src/core/SkPromiseImageTexture.cpp | 48 + gfx/skia/skia/src/core/SkPtrRecorder.cpp | 73 + gfx/skia/skia/src/core/SkPtrRecorder.h | 171 + gfx/skia/skia/src/core/SkQuadClipper.cpp | 117 + gfx/skia/skia/src/core/SkQuadClipper.h | 69 + gfx/skia/skia/src/core/SkRRect.cpp | 688 ++ gfx/skia/skia/src/core/SkRRectPriv.h | 45 + gfx/skia/skia/src/core/SkRTree.cpp | 178 + gfx/skia/skia/src/core/SkRTree.h | 89 + gfx/skia/skia/src/core/SkRWBuffer.cpp | 364 + gfx/skia/skia/src/core/SkRasterClip.cpp | 507 ++ gfx/skia/skia/src/core/SkRasterClip.h | 249 + gfx/skia/skia/src/core/SkRasterClipStack.h | 174 + gfx/skia/skia/src/core/SkRasterPipeline.cpp | 358 + gfx/skia/skia/src/core/SkRasterPipeline.h | 298 + gfx/skia/skia/src/core/SkRasterPipelineBlitter.cpp | 472 ++ gfx/skia/skia/src/core/SkReadBuffer.cpp | 465 ++ gfx/skia/skia/src/core/SkReadBuffer.h | 321 + gfx/skia/skia/src/core/SkReader32.h | 169 + gfx/skia/skia/src/core/SkRecord.cpp | 37 + gfx/skia/skia/src/core/SkRecord.h | 194 + gfx/skia/skia/src/core/SkRecordDraw.cpp | 529 ++ gfx/skia/skia/src/core/SkRecordDraw.h | 82 + gfx/skia/skia/src/core/SkRecordOpts.cpp | 320 + gfx/skia/skia/src/core/SkRecordOpts.h | 32 + gfx/skia/skia/src/core/SkRecordPattern.h | 176 + gfx/skia/skia/src/core/SkRecordedDrawable.cpp | 90 + gfx/skia/skia/src/core/SkRecordedDrawable.h | 42 + gfx/skia/skia/src/core/SkRecorder.cpp | 410 ++ gfx/skia/skia/src/core/SkRecorder.h | 149 + gfx/skia/skia/src/core/SkRecords.cpp | 24 + gfx/skia/skia/src/core/SkRecords.h | 346 + gfx/skia/skia/src/core/SkRect.cpp | 168 + gfx/skia/skia/src/core/SkRectPriv.h | 64 + gfx/skia/skia/src/core/SkRegion.cpp | 1582 ++++ gfx/skia/skia/src/core/SkRegionPriv.h | 259 + gfx/skia/skia/src/core/SkRegion_path.cpp | 549 ++ gfx/skia/skia/src/core/SkRemoteGlyphCache.cpp | 891 +++ gfx/skia/skia/src/core/SkRemoteGlyphCache.h | 224 + gfx/skia/skia/src/core/SkResourceCache.cpp | 605 ++ gfx/skia/skia/src/core/SkResourceCache.h | 292 + gfx/skia/skia/src/core/SkSafeMath.h | 108 + gfx/skia/skia/src/core/SkSafeRange.h | 49 + gfx/skia/skia/src/core/SkScalar.cpp | 35 + gfx/skia/skia/src/core/SkScaleToSides.h | 71 + gfx/skia/skia/src/core/SkScalerContext.cpp | 1205 ++++ gfx/skia/skia/src/core/SkScalerContext.h | 447 ++ gfx/skia/skia/src/core/SkScan.cpp | 111 + gfx/skia/skia/src/core/SkScan.h | 136 + gfx/skia/skia/src/core/SkScanPriv.h | 100 + gfx/skia/skia/src/core/SkScan_AAAPath.cpp | 2011 ++++++ gfx/skia/skia/src/core/SkScan_AntiPath.cpp | 838 +++ gfx/skia/skia/src/core/SkScan_Antihair.cpp | 1009 +++ gfx/skia/skia/src/core/SkScan_Hairline.cpp | 732 ++ gfx/skia/skia/src/core/SkScan_Path.cpp | 780 ++ gfx/skia/skia/src/core/SkScopeExit.h | 59 + gfx/skia/skia/src/core/SkSemaphore.cpp | 80 + gfx/skia/skia/src/core/SkSharedMutex.cpp | 366 + gfx/skia/skia/src/core/SkSharedMutex.h | 106 + gfx/skia/skia/src/core/SkSpan.h | 56 + gfx/skia/skia/src/core/SkSpecialImage.cpp | 560 ++ gfx/skia/skia/src/core/SkSpecialImage.h | 171 + gfx/skia/skia/src/core/SkSpecialSurface.cpp | 186 + gfx/skia/skia/src/core/SkSpecialSurface.h | 99 + gfx/skia/skia/src/core/SkSpinlock.cpp | 47 + gfx/skia/skia/src/core/SkSpriteBlitter.h | 49 + gfx/skia/skia/src/core/SkSpriteBlitter_ARGB32.cpp | 118 + gfx/skia/skia/src/core/SkSpriteBlitter_RGB565.cpp | 180 + gfx/skia/skia/src/core/SkStream.cpp | 948 +++ gfx/skia/skia/src/core/SkStreamPriv.h | 34 + gfx/skia/skia/src/core/SkStrike.cpp | 291 + gfx/skia/skia/src/core/SkStrike.h | 205 + gfx/skia/skia/src/core/SkStrikeCache.cpp | 581 ++ gfx/skia/skia/src/core/SkStrikeCache.h | 168 + gfx/skia/skia/src/core/SkStrikeForGPU.cpp | 27 + gfx/skia/skia/src/core/SkStrikeForGPU.h | 83 + gfx/skia/skia/src/core/SkStrikeSpec.cpp | 259 + gfx/skia/skia/src/core/SkStrikeSpec.h | 131 + gfx/skia/skia/src/core/SkString.cpp | 604 ++ gfx/skia/skia/src/core/SkStringUtils.cpp | 81 + gfx/skia/skia/src/core/SkStringUtils.h | 35 + gfx/skia/skia/src/core/SkStroke.cpp | 1579 ++++ gfx/skia/skia/src/core/SkStroke.h | 79 + gfx/skia/skia/src/core/SkStrokeRec.cpp | 170 + gfx/skia/skia/src/core/SkStrokerPriv.cpp | 235 + gfx/skia/skia/src/core/SkStrokerPriv.h | 43 + .../skia/src/core/SkSurfaceCharacterization.cpp | 114 + gfx/skia/skia/src/core/SkSurfacePriv.h | 27 + gfx/skia/skia/src/core/SkSwizzle.cpp | 14 + gfx/skia/skia/src/core/SkTDPQueue.h | 219 + gfx/skia/skia/src/core/SkTDynamicHash.h | 298 + gfx/skia/skia/src/core/SkTInternalLList.h | 302 + gfx/skia/skia/src/core/SkTLList.h | 354 + gfx/skia/skia/src/core/SkTLS.cpp | 105 + gfx/skia/skia/src/core/SkTLS.h | 83 + gfx/skia/skia/src/core/SkTLazy.h | 202 + gfx/skia/skia/src/core/SkTMultiMap.h | 210 + gfx/skia/skia/src/core/SkTSearch.cpp | 116 + gfx/skia/skia/src/core/SkTSearch.h | 146 + gfx/skia/skia/src/core/SkTSort.h | 216 + gfx/skia/skia/src/core/SkTTopoSort.h | 113 + gfx/skia/skia/src/core/SkTaskGroup.cpp | 51 + gfx/skia/skia/src/core/SkTaskGroup.h | 48 + gfx/skia/skia/src/core/SkTextBlob.cpp | 947 +++ gfx/skia/skia/src/core/SkTextBlobPriv.h | 268 + gfx/skia/skia/src/core/SkTextFormatParams.h | 38 + gfx/skia/skia/src/core/SkThreadID.cpp | 16 + gfx/skia/skia/src/core/SkTime.cpp | 87 + gfx/skia/skia/src/core/SkTraceEvent.h | 366 + gfx/skia/skia/src/core/SkTraceEventCommon.h | 291 + gfx/skia/skia/src/core/SkTypeface.cpp | 420 ++ gfx/skia/skia/src/core/SkTypefaceCache.cpp | 111 + gfx/skia/skia/src/core/SkTypefaceCache.h | 74 + gfx/skia/skia/src/core/SkTypefacePriv.h | 38 + gfx/skia/skia/src/core/SkTypeface_remote.cpp | 112 + gfx/skia/skia/src/core/SkTypeface_remote.h | 141 + gfx/skia/skia/src/core/SkUnPreMultiply.cpp | 79 + gfx/skia/skia/src/core/SkUtils.cpp | 35 + gfx/skia/skia/src/core/SkUtils.h | 98 + gfx/skia/skia/src/core/SkUtilsArm.cpp | 8 + gfx/skia/skia/src/core/SkVM.cpp | 2221 ++++++ gfx/skia/skia/src/core/SkVM.h | 521 ++ gfx/skia/skia/src/core/SkVMBlitter.cpp | 446 ++ gfx/skia/skia/src/core/SkValidatingReadBuffer.h | 23 + gfx/skia/skia/src/core/SkValidationUtils.h | 36 + gfx/skia/skia/src/core/SkVertState.cpp | 106 + gfx/skia/skia/src/core/SkVertState.h | 58 + gfx/skia/skia/src/core/SkVertices.cpp | 431 ++ gfx/skia/skia/src/core/SkVptr.h | 24 + gfx/skia/skia/src/core/SkWriteBuffer.cpp | 260 + gfx/skia/skia/src/core/SkWriteBuffer.h | 139 + gfx/skia/skia/src/core/SkWritePixelsRec.h | 50 + gfx/skia/skia/src/core/SkWriter32.cpp | 90 + gfx/skia/skia/src/core/SkWriter32.h | 282 + gfx/skia/skia/src/core/SkXfermode.cpp | 153 + .../skia/src/core/SkXfermodeInterpretation.cpp | 44 + gfx/skia/skia/src/core/SkXfermodeInterpretation.h | 30 + gfx/skia/skia/src/core/SkXfermodePriv.h | 62 + gfx/skia/skia/src/core/SkYUVASizeInfo.cpp | 37 + gfx/skia/skia/src/core/SkYUVMath.cpp | 201 + gfx/skia/skia/src/core/SkYUVMath.h | 19 + gfx/skia/skia/src/core/SkYUVPlanesCache.cpp | 89 + gfx/skia/skia/src/core/SkYUVPlanesCache.h | 47 + gfx/skia/skia/src/core/SkZip.h | 187 + gfx/skia/skia/src/effects/Sk1DPathEffect.cpp | 214 + gfx/skia/skia/src/effects/Sk2DPathEffect.cpp | 135 + gfx/skia/skia/src/effects/SkColorMatrix.cpp | 171 + gfx/skia/skia/src/effects/SkColorMatrixFilter.cpp | 36 + gfx/skia/skia/src/effects/SkCornerPathEffect.cpp | 156 + gfx/skia/skia/src/effects/SkDashImpl.h | 42 + gfx/skia/skia/src/effects/SkDashPathEffect.cpp | 398 + gfx/skia/skia/src/effects/SkDiscretePathEffect.cpp | 145 + gfx/skia/skia/src/effects/SkEmbossMask.cpp | 114 + gfx/skia/skia/src/effects/SkEmbossMask.h | 19 + gfx/skia/skia/src/effects/SkEmbossMaskFilter.cpp | 142 + gfx/skia/skia/src/effects/SkEmbossMaskFilter.h | 48 + gfx/skia/skia/src/effects/SkHighContrastFilter.cpp | 361 + gfx/skia/skia/src/effects/SkLayerDrawLooper.cpp | 328 + gfx/skia/skia/src/effects/SkLumaColorFilter.cpp | 45 + gfx/skia/skia/src/effects/SkOpPE.h | 69 + gfx/skia/skia/src/effects/SkOpPathEffect.cpp | 124 + .../skia/src/effects/SkOverdrawColorFilter.cpp | 97 + gfx/skia/skia/src/effects/SkPackBits.cpp | 112 + gfx/skia/skia/src/effects/SkPackBits.h | 45 + gfx/skia/skia/src/effects/SkShaderMaskFilter.cpp | 131 + gfx/skia/skia/src/effects/SkTableColorFilter.cpp | 432 ++ gfx/skia/skia/src/effects/SkTableMaskFilter.cpp | 168 + gfx/skia/skia/src/effects/SkTrimPE.h | 33 + gfx/skia/skia/src/effects/SkTrimPathEffect.cpp | 119 + .../imagefilters/SkAlphaThresholdFilter.cpp | 266 + .../imagefilters/SkArithmeticImageFilter.cpp | 479 ++ .../src/effects/imagefilters/SkBlurImageFilter.cpp | 706 ++ .../imagefilters/SkColorFilterImageFilter.cpp | 157 + .../effects/imagefilters/SkComposeImageFilter.cpp | 122 + .../imagefilters/SkDisplacementMapEffect.cpp | 675 ++ .../imagefilters/SkDropShadowImageFilter.cpp | 183 + .../src/effects/imagefilters/SkImageFilters.cpp | 260 + .../src/effects/imagefilters/SkImageSource.cpp | 169 + .../effects/imagefilters/SkLightingImageFilter.cpp | 2120 ++++++ .../imagefilters/SkMagnifierImageFilter.cpp | 222 + .../SkMatrixConvolutionImageFilter.cpp | 574 ++ .../effects/imagefilters/SkMergeImageFilter.cpp | 126 + .../imagefilters/SkMorphologyImageFilter.cpp | 853 +++ .../effects/imagefilters/SkOffsetImageFilter.cpp | 145 + .../effects/imagefilters/SkPaintImageFilter.cpp | 106 + .../effects/imagefilters/SkPictureImageFilter.cpp | 146 + .../src/effects/imagefilters/SkTileImageFilter.cpp | 190 + .../effects/imagefilters/SkXfermodeImageFilter.cpp | 335 + gfx/skia/skia/src/fonts/SkFontMgr_indirect.cpp | 187 + gfx/skia/skia/src/fonts/SkRemotableFontMgr.cpp | 23 + .../src/gpu/GrAHardwareBufferImageGenerator.cpp | 230 + .../skia/src/gpu/GrAHardwareBufferImageGenerator.h | 65 + gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.cpp | 556 ++ gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.h | 69 + gfx/skia/skia/src/gpu/GrAllocator.h | 439 ++ gfx/skia/skia/src/gpu/GrAppliedClip.h | 141 + gfx/skia/skia/src/gpu/GrAuditTrail.cpp | 200 + gfx/skia/skia/src/gpu/GrAuditTrail.h | 181 + gfx/skia/skia/src/gpu/GrAutoLocaleSetter.h | 98 + gfx/skia/skia/src/gpu/GrBackendSurface.cpp | 924 +++ .../src/gpu/GrBackendTextureImageGenerator.cpp | 224 + .../skia/src/gpu/GrBackendTextureImageGenerator.h | 87 + gfx/skia/skia/src/gpu/GrBaseContextPriv.h | 54 + gfx/skia/skia/src/gpu/GrBitmapTextureMaker.cpp | 110 + gfx/skia/skia/src/gpu/GrBitmapTextureMaker.h | 36 + gfx/skia/skia/src/gpu/GrBlend.h | 151 + gfx/skia/skia/src/gpu/GrBlurUtils.cpp | 478 ++ gfx/skia/skia/src/gpu/GrBlurUtils.h | 56 + gfx/skia/skia/src/gpu/GrBuffer.h | 36 + gfx/skia/skia/src/gpu/GrBufferAllocPool.cpp | 534 ++ gfx/skia/skia/src/gpu/GrBufferAllocPool.h | 317 + gfx/skia/skia/src/gpu/GrCaps.cpp | 393 + gfx/skia/skia/src/gpu/GrCaps.h | 553 ++ .../skia/src/gpu/GrClientMappedBufferManager.cpp | 71 + .../skia/src/gpu/GrClientMappedBufferManager.h | 72 + gfx/skia/skia/src/gpu/GrClip.h | 174 + gfx/skia/skia/src/gpu/GrClipStackClip.cpp | 544 ++ gfx/skia/skia/src/gpu/GrClipStackClip.h | 68 + gfx/skia/skia/src/gpu/GrColor.h | 113 + gfx/skia/skia/src/gpu/GrColorInfo.cpp | 37 + gfx/skia/skia/src/gpu/GrColorInfo.h | 51 + gfx/skia/skia/src/gpu/GrColorSpaceXform.cpp | 186 + gfx/skia/skia/src/gpu/GrColorSpaceXform.h | 103 + gfx/skia/skia/src/gpu/GrContext.cpp | 582 ++ gfx/skia/skia/src/gpu/GrContextPriv.cpp | 340 + gfx/skia/skia/src/gpu/GrContextPriv.h | 288 + gfx/skia/skia/src/gpu/GrContextThreadSafeProxy.cpp | 124 + .../skia/src/gpu/GrContextThreadSafeProxyPriv.h | 63 + gfx/skia/skia/src/gpu/GrContext_Base.cpp | 71 + gfx/skia/skia/src/gpu/GrCoordTransform.h | 140 + gfx/skia/skia/src/gpu/GrCopyRenderTask.cpp | 84 + gfx/skia/skia/src/gpu/GrCopyRenderTask.h | 53 + gfx/skia/skia/src/gpu/GrCpuBuffer.h | 45 + gfx/skia/skia/src/gpu/GrDDLContext.cpp | 80 + gfx/skia/skia/src/gpu/GrDataUtils.cpp | 616 ++ gfx/skia/skia/src/gpu/GrDataUtils.h | 40 + gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.cpp | 363 + gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.h | 97 + gfx/skia/skia/src/gpu/GrDeferredProxyUploader.h | 121 + gfx/skia/skia/src/gpu/GrDeferredUpload.h | 147 + .../skia/src/gpu/GrDistanceFieldGenFromVector.cpp | 870 +++ .../skia/src/gpu/GrDistanceFieldGenFromVector.h | 39 + gfx/skia/skia/src/gpu/GrDrawOpAtlas.cpp | 664 ++ gfx/skia/skia/src/gpu/GrDrawOpAtlas.h | 450 ++ gfx/skia/skia/src/gpu/GrDrawOpTest.cpp | 59 + gfx/skia/skia/src/gpu/GrDrawOpTest.h | 39 + gfx/skia/skia/src/gpu/GrDrawingManager.cpp | 939 +++ gfx/skia/skia/src/gpu/GrDrawingManager.h | 246 + gfx/skia/skia/src/gpu/GrDriverBugWorkarounds.cpp | 41 + gfx/skia/skia/src/gpu/GrFPArgs.h | 97 + gfx/skia/skia/src/gpu/GrFixedClip.cpp | 72 + gfx/skia/skia/src/gpu/GrFixedClip.h | 57 + gfx/skia/skia/src/gpu/GrFragmentProcessor.cpp | 431 ++ gfx/skia/skia/src/gpu/GrFragmentProcessor.h | 475 ++ gfx/skia/skia/src/gpu/GrGeometryProcessor.h | 45 + gfx/skia/skia/src/gpu/GrGlyph.h | 103 + gfx/skia/skia/src/gpu/GrGpu.cpp | 775 ++ gfx/skia/skia/src/gpu/GrGpu.h | 685 ++ gfx/skia/skia/src/gpu/GrGpuBuffer.cpp | 68 + gfx/skia/skia/src/gpu/GrGpuBuffer.h | 103 + gfx/skia/skia/src/gpu/GrGpuResource.cpp | 214 + gfx/skia/skia/src/gpu/GrGpuResourceCacheAccess.h | 103 + gfx/skia/skia/src/gpu/GrGpuResourcePriv.h | 95 + gfx/skia/skia/src/gpu/GrImageContext.cpp | 47 + gfx/skia/skia/src/gpu/GrImageContextPriv.h | 63 + gfx/skia/skia/src/gpu/GrImageInfo.h | 92 + gfx/skia/skia/src/gpu/GrImageTextureMaker.cpp | 117 + gfx/skia/skia/src/gpu/GrImageTextureMaker.h | 75 + gfx/skia/skia/src/gpu/GrLegacyDirectContext.cpp | 243 + gfx/skia/skia/src/gpu/GrMemoryPool.cpp | 217 + gfx/skia/skia/src/gpu/GrMemoryPool.h | 155 + gfx/skia/skia/src/gpu/GrMesh.h | 270 + gfx/skia/skia/src/gpu/GrNativeRect.h | 98 + gfx/skia/skia/src/gpu/GrNonAtomicRef.h | 63 + .../skia/src/gpu/GrOnFlushResourceProvider.cpp | 134 + gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.h | 102 + gfx/skia/skia/src/gpu/GrOpFlushState.cpp | 230 + gfx/skia/skia/src/gpu/GrOpFlushState.h | 203 + gfx/skia/skia/src/gpu/GrOpsRenderPass.cpp | 68 + gfx/skia/skia/src/gpu/GrOpsRenderPass.h | 109 + gfx/skia/skia/src/gpu/GrOpsTask.cpp | 856 +++ gfx/skia/skia/src/gpu/GrOpsTask.h | 299 + gfx/skia/skia/src/gpu/GrPaint.cpp | 67 + gfx/skia/skia/src/gpu/GrPaint.h | 136 + gfx/skia/skia/src/gpu/GrPath.cpp | 55 + gfx/skia/skia/src/gpu/GrPath.h | 58 + gfx/skia/skia/src/gpu/GrPathProcessor.cpp | 141 + gfx/skia/skia/src/gpu/GrPathProcessor.h | 50 + gfx/skia/skia/src/gpu/GrPathRenderer.cpp | 120 + gfx/skia/skia/src/gpu/GrPathRenderer.h | 208 + gfx/skia/skia/src/gpu/GrPathRendererChain.cpp | 125 + gfx/skia/skia/src/gpu/GrPathRendererChain.h | 65 + gfx/skia/skia/src/gpu/GrPathRendering.cpp | 65 + gfx/skia/skia/src/gpu/GrPathRendering.h | 134 + gfx/skia/skia/src/gpu/GrPathRendering_none.cpp | 59 + gfx/skia/skia/src/gpu/GrPersistentCacheUtils.h | 105 + gfx/skia/skia/src/gpu/GrPipeline.cpp | 115 + gfx/skia/skia/src/gpu/GrPipeline.h | 241 + gfx/skia/skia/src/gpu/GrPrimitiveProcessor.cpp | 74 + gfx/skia/skia/src/gpu/GrPrimitiveProcessor.h | 369 + gfx/skia/skia/src/gpu/GrProcessor.cpp | 131 + gfx/skia/skia/src/gpu/GrProcessor.h | 226 + gfx/skia/skia/src/gpu/GrProcessorAnalysis.cpp | 48 + gfx/skia/skia/src/gpu/GrProcessorAnalysis.h | 153 + gfx/skia/skia/src/gpu/GrProcessorSet.cpp | 253 + gfx/skia/skia/src/gpu/GrProcessorSet.h | 205 + gfx/skia/skia/src/gpu/GrProcessorUnitTest.cpp | 25 + gfx/skia/skia/src/gpu/GrProcessorUnitTest.h | 220 + gfx/skia/skia/src/gpu/GrProgramDesc.cpp | 268 + gfx/skia/skia/src/gpu/GrProgramDesc.h | 144 + gfx/skia/skia/src/gpu/GrProgramInfo.cpp | 121 + gfx/skia/skia/src/gpu/GrProgramInfo.h | 115 + gfx/skia/skia/src/gpu/GrProxyProvider.cpp | 958 +++ gfx/skia/skia/src/gpu/GrProxyProvider.h | 296 + gfx/skia/skia/src/gpu/GrRecordingContext.cpp | 389 + gfx/skia/skia/src/gpu/GrRecordingContextPriv.h | 142 + gfx/skia/skia/src/gpu/GrRectanizer.h | 44 + gfx/skia/skia/src/gpu/GrRectanizer_pow2.cpp | 59 + gfx/skia/skia/src/gpu/GrRectanizer_pow2.h | 81 + gfx/skia/skia/src/gpu/GrRectanizer_skyline.cpp | 121 + gfx/skia/skia/src/gpu/GrRectanizer_skyline.h | 62 + gfx/skia/skia/src/gpu/GrReducedClip.cpp | 991 +++ gfx/skia/skia/src/gpu/GrReducedClip.h | 151 + gfx/skia/skia/src/gpu/GrRenderTarget.cpp | 100 + gfx/skia/skia/src/gpu/GrRenderTarget.h | 77 + gfx/skia/skia/src/gpu/GrRenderTargetContext.cpp | 2466 +++++++ gfx/skia/skia/src/gpu/GrRenderTargetContext.h | 657 ++ gfx/skia/skia/src/gpu/GrRenderTargetContextPriv.h | 128 + gfx/skia/skia/src/gpu/GrRenderTargetPriv.h | 73 + gfx/skia/skia/src/gpu/GrRenderTargetProxy.cpp | 157 + gfx/skia/skia/src/gpu/GrRenderTargetProxy.h | 179 + gfx/skia/skia/src/gpu/GrRenderTargetProxyPriv.h | 53 + gfx/skia/skia/src/gpu/GrRenderTask.cpp | 301 + gfx/skia/skia/src/gpu/GrRenderTask.h | 207 + gfx/skia/skia/src/gpu/GrResourceAllocator.cpp | 510 ++ gfx/skia/skia/src/gpu/GrResourceAllocator.h | 277 + gfx/skia/skia/src/gpu/GrResourceCache.cpp | 945 +++ gfx/skia/skia/src/gpu/GrResourceCache.h | 448 ++ gfx/skia/skia/src/gpu/GrResourceHandle.h | 36 + gfx/skia/skia/src/gpu/GrResourceProvider.cpp | 581 ++ gfx/skia/skia/src/gpu/GrResourceProvider.h | 360 + gfx/skia/skia/src/gpu/GrResourceProviderPriv.h | 39 + gfx/skia/skia/src/gpu/GrSWMaskHelper.cpp | 112 + gfx/skia/skia/src/gpu/GrSWMaskHelper.h | 71 + .../skia/src/gpu/GrSamplePatternDictionary.cpp | 42 + gfx/skia/skia/src/gpu/GrSamplePatternDictionary.h | 39 + gfx/skia/skia/src/gpu/GrSamplerState.h | 86 + gfx/skia/skia/src/gpu/GrScissorState.h | 40 + gfx/skia/skia/src/gpu/GrSemaphore.h | 39 + gfx/skia/skia/src/gpu/GrShaderCaps.cpp | 172 + gfx/skia/skia/src/gpu/GrShaderCaps.h | 314 + gfx/skia/skia/src/gpu/GrShaderUtils.cpp | 220 + gfx/skia/skia/src/gpu/GrShaderUtils.h | 23 + gfx/skia/skia/src/gpu/GrShaderVar.cpp | 61 + gfx/skia/skia/src/gpu/GrShaderVar.h | 295 + gfx/skia/skia/src/gpu/GrSkSLFPFactoryCache.h | 37 + gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.cpp | 395 + gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.h | 79 + gfx/skia/skia/src/gpu/GrStencilAttachment.cpp | 18 + gfx/skia/skia/src/gpu/GrStencilAttachment.h | 58 + gfx/skia/skia/src/gpu/GrStencilClip.h | 59 + gfx/skia/skia/src/gpu/GrStencilSettings.cpp | 506 ++ gfx/skia/skia/src/gpu/GrStencilSettings.h | 135 + gfx/skia/skia/src/gpu/GrStyle.cpp | 199 + gfx/skia/skia/src/gpu/GrStyle.h | 215 + gfx/skia/skia/src/gpu/GrSurface.cpp | 59 + gfx/skia/skia/src/gpu/GrSurfaceContext.cpp | 661 ++ gfx/skia/skia/src/gpu/GrSurfaceContext.h | 166 + gfx/skia/skia/src/gpu/GrSurfaceContextPriv.h | 45 + gfx/skia/skia/src/gpu/GrSurfacePriv.h | 42 + gfx/skia/skia/src/gpu/GrSurfaceProxy.cpp | 523 ++ gfx/skia/skia/src/gpu/GrSurfaceProxy.h | 445 ++ gfx/skia/skia/src/gpu/GrSurfaceProxyPriv.h | 65 + gfx/skia/skia/src/gpu/GrSwizzle.cpp | 36 + gfx/skia/skia/src/gpu/GrSwizzle.h | 151 + gfx/skia/skia/src/gpu/GrTRecorder.h | 176 + gfx/skia/skia/src/gpu/GrTessellator.cpp | 2415 +++++++ gfx/skia/skia/src/gpu/GrTessellator.h | 53 + gfx/skia/skia/src/gpu/GrTestUtils.cpp | 350 + gfx/skia/skia/src/gpu/GrTestUtils.h | 157 + gfx/skia/skia/src/gpu/GrTexture.cpp | 127 + gfx/skia/skia/src/gpu/GrTextureAdjuster.cpp | 166 + gfx/skia/skia/src/gpu/GrTextureAdjuster.h | 58 + gfx/skia/skia/src/gpu/GrTextureContext.cpp | 52 + gfx/skia/skia/src/gpu/GrTextureContext.h | 57 + gfx/skia/skia/src/gpu/GrTextureMaker.cpp | 142 + gfx/skia/skia/src/gpu/GrTextureMaker.h | 51 + gfx/skia/skia/src/gpu/GrTexturePriv.h | 85 + gfx/skia/skia/src/gpu/GrTextureProducer.cpp | 306 + gfx/skia/skia/src/gpu/GrTextureProducer.h | 203 + gfx/skia/skia/src/gpu/GrTextureProxy.cpp | 197 + gfx/skia/skia/src/gpu/GrTextureProxy.h | 196 + gfx/skia/skia/src/gpu/GrTextureProxyCacheAccess.h | 46 + gfx/skia/skia/src/gpu/GrTextureProxyPriv.h | 51 + .../skia/src/gpu/GrTextureRenderTargetProxy.cpp | 182 + gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.h | 84 + gfx/skia/skia/src/gpu/GrTextureResolveManager.h | 37 + .../skia/src/gpu/GrTextureResolveRenderTask.cpp | 100 + gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.h | 51 + gfx/skia/skia/src/gpu/GrTracing.h | 23 + gfx/skia/skia/src/gpu/GrTransferFromRenderTask.cpp | 30 + gfx/skia/skia/src/gpu/GrTransferFromRenderTask.h | 60 + gfx/skia/skia/src/gpu/GrUserStencilSettings.h | 257 + gfx/skia/skia/src/gpu/GrUtil.h | 90 + gfx/skia/skia/src/gpu/GrVertexWriter.h | 176 + gfx/skia/skia/src/gpu/GrWaitRenderTask.cpp | 28 + gfx/skia/skia/src/gpu/GrWaitRenderTask.h | 44 + gfx/skia/skia/src/gpu/GrWindowRectangles.h | 123 + gfx/skia/skia/src/gpu/GrWindowRectsState.h | 54 + gfx/skia/skia/src/gpu/GrXferProcessor.cpp | 191 + gfx/skia/skia/src/gpu/GrXferProcessor.h | 323 + gfx/skia/skia/src/gpu/GrYUVProvider.cpp | 190 + gfx/skia/skia/src/gpu/GrYUVProvider.h | 93 + gfx/skia/skia/src/gpu/SkGpuDevice.cpp | 1671 +++++ gfx/skia/skia/src/gpu/SkGpuDevice.h | 247 + gfx/skia/skia/src/gpu/SkGpuDevice_drawTexture.cpp | 608 ++ gfx/skia/skia/src/gpu/SkGr.cpp | 609 ++ gfx/skia/skia/src/gpu/SkGr.h | 232 + gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.cpp | 268 + gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.h | 177 + gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.cpp | 75 + gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.h | 81 + gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.cpp | 124 + gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.h | 47 + gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.cpp | 110 + gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.h | 49 + .../skia/src/gpu/ccpr/GrCCCoverageProcessor.cpp | 215 + gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.h | 298 + gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.cpp | 159 + gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.h | 48 + gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp | 461 ++ gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.h | 147 + gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.cpp | 802 +++ gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.h | 143 + gfx/skia/skia/src/gpu/ccpr/GrCCFiller.cpp | 583 ++ gfx/skia/skia/src/gpu/ccpr/GrCCFiller.h | 127 + gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.cpp | 442 ++ gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.h | 371 + gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.cpp | 250 + gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.h | 123 + .../skia/src/gpu/ccpr/GrCCPerFlushResources.cpp | 611 ++ gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.h | 205 + gfx/skia/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h | 32 + gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.cpp | 95 + gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.h | 47 + gfx/skia/skia/src/gpu/ccpr/GrCCSTLList.h | 66 + gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp | 583 ++ gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.h | 179 + gfx/skia/skia/src/gpu/ccpr/GrCCStroker.cpp | 836 +++ gfx/skia/skia/src/gpu/ccpr/GrCCStroker.h | 128 + .../gpu/ccpr/GrCoverageCountingPathRenderer.cpp | 374 + .../src/gpu/ccpr/GrCoverageCountingPathRenderer.h | 121 + .../ccpr/GrCoverageCountingPathRenderer_none.cpp | 23 + .../skia/src/gpu/ccpr/GrGSCoverageProcessor.cpp | 463 ++ gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.h | 54 + gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.cpp | 168 + gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.h | 115 + .../skia/src/gpu/ccpr/GrSampleMaskProcessor.cpp | 118 + gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.h | 33 + gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.cpp | 162 + gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.h | 84 + .../skia/src/gpu/ccpr/GrVSCoverageProcessor.cpp | 553 ++ gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.h | 38 + gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.cpp | 75 + gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.h | 35 + gfx/skia/skia/src/gpu/dawn/GrDawnCaps.cpp | 231 + gfx/skia/skia/src/gpu/dawn/GrDawnCaps.h | 88 + gfx/skia/skia/src/gpu/dawn/GrDawnGpu.cpp | 699 ++ gfx/skia/skia/src/gpu/dawn/GrDawnGpu.h | 191 + gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.cpp | 203 + gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.h | 96 + .../skia/src/gpu/dawn/GrDawnProgramBuilder.cpp | 555 ++ gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.h | 101 + .../skia/src/gpu/dawn/GrDawnProgramDataManager.cpp | 290 + .../skia/src/gpu/dawn/GrDawnProgramDataManager.h | 93 + gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.cpp | 65 + gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.h | 54 + gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.cpp | 36 + gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.h | 43 + .../skia/src/gpu/dawn/GrDawnStagingManager.cpp | 65 + gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.h | 49 + .../skia/src/gpu/dawn/GrDawnStencilAttachment.cpp | 71 + .../skia/src/gpu/dawn/GrDawnStencilAttachment.h | 43 + gfx/skia/skia/src/gpu/dawn/GrDawnTexture.cpp | 201 + gfx/skia/skia/src/gpu/dawn/GrDawnTexture.h | 63 + .../src/gpu/dawn/GrDawnTextureRenderTarget.cpp | 36 + .../skia/src/gpu/dawn/GrDawnTextureRenderTarget.h | 55 + .../skia/src/gpu/dawn/GrDawnUniformHandler.cpp | 326 + gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.h | 65 + gfx/skia/skia/src/gpu/dawn/GrDawnUtil.cpp | 72 + gfx/skia/skia/src/gpu/dawn/GrDawnUtil.h | 22 + .../skia/src/gpu/dawn/GrDawnVaryingHandler.cpp | 108 + gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.h | 27 + gfx/skia/skia/src/gpu/effects/GrAARectEffect.fp | 65 + .../effects/GrAlphaThresholdFragmentProcessor.fp | 77 + .../skia/src/gpu/effects/GrAtlasedShaderHelpers.h | 74 + gfx/skia/skia/src/gpu/effects/GrBezierEffect.cpp | 462 ++ gfx/skia/skia/src/gpu/effects/GrBezierEffect.h | 216 + gfx/skia/skia/src/gpu/effects/GrBicubicEffect.cpp | 255 + gfx/skia/skia/src/gpu/effects/GrBicubicEffect.h | 148 + .../skia/src/gpu/effects/GrBitmapTextGeoProc.cpp | 233 + .../skia/src/gpu/effects/GrBitmapTextGeoProc.h | 80 + .../gpu/effects/GrBlurredEdgeFragmentProcessor.fp | 26 + .../gpu/effects/GrCircleBlurFragmentProcessor.fp | 292 + gfx/skia/skia/src/gpu/effects/GrCircleEffect.fp | 79 + .../gpu/effects/GrColorMatrixFragmentProcessor.fp | 90 + .../skia/src/gpu/effects/GrComposeLerpEffect.fp | 16 + .../skia/src/gpu/effects/GrComposeLerpRedEffect.fp | 16 + .../src/gpu/effects/GrConfigConversionEffect.fp | 174 + .../skia/src/gpu/effects/GrConstColorProcessor.fp | 86 + .../skia/src/gpu/effects/GrConvexPolyEffect.cpp | 235 + gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.h | 92 + .../skia/src/gpu/effects/GrCoverageSetOpXP.cpp | 234 + gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.h | 65 + gfx/skia/skia/src/gpu/effects/GrCustomXfermode.cpp | 436 ++ gfx/skia/skia/src/gpu/effects/GrCustomXfermode.h | 26 + gfx/skia/skia/src/gpu/effects/GrDisableColorXP.cpp | 76 + gfx/skia/skia/src/gpu/effects/GrDisableColorXP.h | 67 + .../src/gpu/effects/GrDistanceFieldGeoProc.cpp | 928 +++ .../skia/src/gpu/effects/GrDistanceFieldGeoProc.h | 265 + gfx/skia/skia/src/gpu/effects/GrEllipseEffect.fp | 132 + .../GrGaussianConvolutionFragmentProcessor.cpp | 305 + .../GrGaussianConvolutionFragmentProcessor.h | 104 + .../src/gpu/effects/GrLumaColorFilterEffect.fp | 27 + gfx/skia/skia/src/gpu/effects/GrMagnifierEffect.fp | 94 + .../src/gpu/effects/GrMatrixConvolutionEffect.cpp | 352 + .../src/gpu/effects/GrMatrixConvolutionEffect.h | 96 + gfx/skia/skia/src/gpu/effects/GrMixerEffect.fp | 46 + gfx/skia/skia/src/gpu/effects/GrOvalEffect.cpp | 33 + gfx/skia/skia/src/gpu/effects/GrOvalEffect.h | 27 + .../effects/GrOverrideInputFragmentProcessor.fp | 55 + .../src/gpu/effects/GrPorterDuffXferProcessor.cpp | 945 +++ .../src/gpu/effects/GrPorterDuffXferProcessor.h | 76 + .../gpu/effects/GrPremulInputFragmentProcessor.fp | 21 + gfx/skia/skia/src/gpu/effects/GrRRectBlurEffect.fp | 207 + gfx/skia/skia/src/gpu/effects/GrRRectEffect.cpp | 769 ++ gfx/skia/skia/src/gpu/effects/GrRRectEffect.h | 29 + gfx/skia/skia/src/gpu/effects/GrRectBlurEffect.fp | 205 + gfx/skia/skia/src/gpu/effects/GrSRGBEffect.cpp | 136 + gfx/skia/skia/src/gpu/effects/GrSRGBEffect.h | 56 + .../skia/src/gpu/effects/GrSaturateProcessor.fp | 25 + gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.cpp | 84 + gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.h | 52 + .../skia/src/gpu/effects/GrSimpleTextureEffect.fp | 81 + gfx/skia/skia/src/gpu/effects/GrSkSLFP.cpp | 573 ++ gfx/skia/skia/src/gpu/effects/GrSkSLFP.h | 202 + gfx/skia/skia/src/gpu/effects/GrTextureDomain.cpp | 507 ++ gfx/skia/skia/src/gpu/effects/GrTextureDomain.h | 294 + .../gpu/effects/GrXfermodeFragmentProcessor.cpp | 524 ++ .../src/gpu/effects/GrXfermodeFragmentProcessor.h | 37 + gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp | 252 + gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.h | 96 + .../src/gpu/effects/generated/GrAARectEffect.cpp | 118 + .../src/gpu/effects/generated/GrAARectEffect.h | 40 + .../GrAlphaThresholdFragmentProcessor.cpp | 126 + .../generated/GrAlphaThresholdFragmentProcessor.h | 56 + .../generated/GrBlurredEdgeFragmentProcessor.cpp | 60 + .../generated/GrBlurredEdgeFragmentProcessor.h | 38 + .../generated/GrCircleBlurFragmentProcessor.cpp | 350 + .../generated/GrCircleBlurFragmentProcessor.h | 47 + .../src/gpu/effects/generated/GrCircleEffect.cpp | 121 + .../src/gpu/effects/generated/GrCircleEffect.h | 48 + .../generated/GrColorMatrixFragmentProcessor.cpp | 118 + .../generated/GrColorMatrixFragmentProcessor.h | 82 + .../gpu/effects/generated/GrComposeLerpEffect.cpp | 82 + .../gpu/effects/generated/GrComposeLerpEffect.h | 51 + .../effects/generated/GrComposeLerpRedEffect.cpp | 75 + .../gpu/effects/generated/GrComposeLerpRedEffect.h | 55 + .../effects/generated/GrConfigConversionEffect.cpp | 71 + .../effects/generated/GrConfigConversionEffect.h | 159 + .../effects/generated/GrConstColorProcessor.cpp | 103 + .../gpu/effects/generated/GrConstColorProcessor.h | 65 + .../src/gpu/effects/generated/GrEllipseEffect.cpp | 147 + .../src/gpu/effects/generated/GrEllipseEffect.h | 57 + .../effects/generated/GrLumaColorFilterEffect.cpp | 50 + .../effects/generated/GrLumaColorFilterEffect.h | 43 + .../gpu/effects/generated/GrMagnifierEffect.cpp | 200 + .../src/gpu/effects/generated/GrMagnifierEffect.h | 59 + .../src/gpu/effects/generated/GrMixerEffect.cpp | 78 + .../skia/src/gpu/effects/generated/GrMixerEffect.h | 71 + .../generated/GrOverrideInputFragmentProcessor.cpp | 98 + .../generated/GrOverrideInputFragmentProcessor.h | 70 + .../generated/GrPremulInputFragmentProcessor.cpp | 50 + .../generated/GrPremulInputFragmentProcessor.h | 40 + .../gpu/effects/generated/GrRRectBlurEffect.cpp | 169 + .../src/gpu/effects/generated/GrRRectBlurEffect.h | 136 + .../src/gpu/effects/generated/GrRectBlurEffect.cpp | 176 + .../src/gpu/effects/generated/GrRectBlurEffect.h | 136 + .../gpu/effects/generated/GrSaturateProcessor.cpp | 54 + .../gpu/effects/generated/GrSaturateProcessor.h | 41 + .../effects/generated/GrSimpleTextureEffect.cpp | 92 + .../gpu/effects/generated/GrSimpleTextureEffect.h | 75 + gfx/skia/skia/src/gpu/geometry/GrPathUtils.cpp | 859 +++ gfx/skia/skia/src/gpu/geometry/GrPathUtils.h | 217 + gfx/skia/skia/src/gpu/geometry/GrQuad.cpp | 145 + gfx/skia/skia/src/gpu/geometry/GrQuad.h | 159 + gfx/skia/skia/src/gpu/geometry/GrQuadBuffer.h | 378 + gfx/skia/skia/src/gpu/geometry/GrQuadUtils.cpp | 346 + gfx/skia/skia/src/gpu/geometry/GrQuadUtils.h | 43 + gfx/skia/skia/src/gpu/geometry/GrRect.h | 148 + gfx/skia/skia/src/gpu/geometry/GrShape.cpp | 765 ++ gfx/skia/skia/src/gpu/geometry/GrShape.h | 631 ++ .../gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp | 509 ++ .../src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp | 501 ++ gfx/skia/skia/src/gpu/gl/GrGLAssembleHelpers.cpp | 24 + gfx/skia/skia/src/gpu/gl/GrGLAssembleInterface.cpp | 42 + .../gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp | 240 + gfx/skia/skia/src/gpu/gl/GrGLBuffer.cpp | 300 + gfx/skia/skia/src/gpu/gl/GrGLBuffer.h | 68 + gfx/skia/skia/src/gpu/gl/GrGLCaps.cpp | 4246 +++++++++++ gfx/skia/skia/src/gpu/gl/GrGLCaps.h | 691 ++ gfx/skia/skia/src/gpu/gl/GrGLContext.cpp | 117 + gfx/skia/skia/src/gpu/gl/GrGLContext.h | 109 + gfx/skia/skia/src/gpu/gl/GrGLDefines.h | 1112 +++ gfx/skia/skia/src/gpu/gl/GrGLExtensions.cpp | 171 + gfx/skia/skia/src/gpu/gl/GrGLGLSL.cpp | 71 + gfx/skia/skia/src/gpu/gl/GrGLGLSL.h | 20 + gfx/skia/skia/src/gpu/gl/GrGLGpu.cpp | 4035 +++++++++++ gfx/skia/skia/src/gpu/gl/GrGLGpu.h | 664 ++ gfx/skia/skia/src/gpu/gl/GrGLGpuProgramCache.cpp | 106 + gfx/skia/skia/src/gpu/gl/GrGLInterfaceAutogen.cpp | 742 ++ .../src/gpu/gl/GrGLMakeNativeInterface_none.cpp | 12 + gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.cpp | 25 + gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.h | 78 + gfx/skia/skia/src/gpu/gl/GrGLPath.cpp | 347 + gfx/skia/skia/src/gpu/gl/GrGLPath.h | 56 + gfx/skia/skia/src/gpu/gl/GrGLPathRendering.cpp | 266 + gfx/skia/skia/src/gpu/gl/GrGLPathRendering.h | 121 + gfx/skia/skia/src/gpu/gl/GrGLProgram.cpp | 168 + gfx/skia/skia/src/gpu/gl/GrGLProgram.h | 174 + .../skia/src/gpu/gl/GrGLProgramDataManager.cpp | 317 + gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.h | 117 + gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.cpp | 263 + gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.h | 114 + gfx/skia/skia/src/gpu/gl/GrGLSemaphore.cpp | 29 + gfx/skia/skia/src/gpu/gl/GrGLSemaphore.h | 53 + gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.cpp | 44 + gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.h | 67 + gfx/skia/skia/src/gpu/gl/GrGLTexture.cpp | 174 + gfx/skia/skia/src/gpu/gl/GrGLTexture.h | 91 + .../skia/src/gpu/gl/GrGLTextureRenderTarget.cpp | 78 + gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.h | 79 + gfx/skia/skia/src/gpu/gl/GrGLTypesPriv.cpp | 67 + gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.cpp | 136 + gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.h | 78 + gfx/skia/skia/src/gpu/gl/GrGLUtil.cpp | 658 ++ gfx/skia/skia/src/gpu/gl/GrGLUtil.h | 377 + gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.cpp | 34 + gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.h | 35 + gfx/skia/skia/src/gpu/gl/GrGLVertexArray.cpp | 223 + gfx/skia/skia/src/gpu/gl/GrGLVertexArray.h | 127 + .../gl/android/GrGLMakeNativeInterface_android.cpp | 7 + .../src/gpu/gl/builders/GrGLProgramBuilder.cpp | 609 ++ .../skia/src/gpu/gl/builders/GrGLProgramBuilder.h | 108 + .../gpu/gl/builders/GrGLShaderStringBuilder.cpp | 115 + .../src/gpu/gl/builders/GrGLShaderStringBuilder.h | 32 + .../src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp | 137 + .../gpu/gl/glfw/GrGLMakeNativeInterface_glfw.cpp | 29 + .../src/gpu/gl/glx/GrGLMakeNativeInterface_glx.cpp | 37 + .../src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp | 57 + .../src/gpu/gl/mac/GrGLMakeNativeInterface_mac.cpp | 63 + .../src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp | 100 + gfx/skia/skia/src/gpu/glsl/GrGLSL.cpp | 101 + gfx/skia/skia/src/gpu/glsl/GrGLSL.h | 58 + gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.cpp | 510 ++ gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.h | 28 + .../src/gpu/glsl/GrGLSLColorSpaceXformHelper.h | 87 + .../skia/src/gpu/glsl/GrGLSLFragmentProcessor.cpp | 130 + .../skia/src/gpu/glsl/GrGLSLFragmentProcessor.h | 211 + .../src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp | 322 + .../src/gpu/glsl/GrGLSLFragmentShaderBuilder.h | 224 + .../skia/src/gpu/glsl/GrGLSLGeometryProcessor.cpp | 150 + .../skia/src/gpu/glsl/GrGLSLGeometryProcessor.h | 95 + .../skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp | 78 + .../skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.h | 151 + .../skia/src/gpu/glsl/GrGLSLProgramBuilder.cpp | 372 + gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.h | 181 + .../skia/src/gpu/glsl/GrGLSLProgramDataManager.cpp | 32 + .../skia/src/gpu/glsl/GrGLSLProgramDataManager.h | 73 + gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.cpp | 278 + gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.h | 257 + gfx/skia/skia/src/gpu/glsl/GrGLSLUniformHandler.h | 102 + gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.cpp | 52 + gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.h | 19 + gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.cpp | 154 + gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.h | 180 + .../skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp | 91 + .../skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.h | 86 + gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.cpp | 173 + gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.h | 124 + gfx/skia/skia/src/gpu/gpu_workaround_list.txt | 17 + .../src/gpu/gradients/GrClampedGradientEffect.fp | 58 + .../gradients/GrDualIntervalGradientColorizer.fp | 62 + .../src/gpu/gradients/GrGradientBitmapCache.cpp | 249 + .../skia/src/gpu/gradients/GrGradientBitmapCache.h | 62 + .../skia/src/gpu/gradients/GrGradientShader.cpp | 308 + gfx/skia/skia/src/gpu/gradients/GrGradientShader.h | 65 + .../src/gpu/gradients/GrLinearGradientLayout.fp | 74 + .../src/gpu/gradients/GrRadialGradientLayout.fp | 70 + .../gradients/GrSingleIntervalGradientColorizer.fp | 21 + .../src/gpu/gradients/GrSweepGradientLayout.fp | 83 + .../gpu/gradients/GrTextureGradientColorizer.fp | 18 + .../src/gpu/gradients/GrTiledGradientEffect.fp | 59 + .../gradients/GrTwoPointConicalGradientLayout.fp | 302 + .../gradients/GrUnrolledBinaryGradientColorizer.fp | 188 + gfx/skia/skia/src/gpu/gradients/README.md | 71 + .../generated/GrClampedGradientEffect.cpp | 109 + .../gradients/generated/GrClampedGradientEffect.h | 64 + .../generated/GrDualIntervalGradientColorizer.cpp | 149 + .../generated/GrDualIntervalGradientColorizer.h | 46 + .../gradients/generated/GrLinearGradientLayout.cpp | 91 + .../gradients/generated/GrLinearGradientLayout.h | 44 + .../gradients/generated/GrRadialGradientLayout.cpp | 95 + .../gradients/generated/GrRadialGradientLayout.h | 44 + .../GrSingleIntervalGradientColorizer.cpp | 82 + .../generated/GrSingleIntervalGradientColorizer.h | 40 + .../gradients/generated/GrSweepGradientLayout.cpp | 132 + .../gradients/generated/GrSweepGradientLayout.h | 48 + .../generated/GrTextureGradientColorizer.cpp | 59 + .../generated/GrTextureGradientColorizer.h | 40 + .../gradients/generated/GrTiledGradientEffect.cpp | 85 + .../gradients/generated/GrTiledGradientEffect.h | 60 + .../generated/GrTwoPointConicalGradientLayout.cpp | 305 + .../generated/GrTwoPointConicalGradientLayout.h | 62 + .../GrUnrolledBinaryGradientColorizer.cpp | 381 + .../generated/GrUnrolledBinaryGradientColorizer.h | 93 + gfx/skia/skia/src/gpu/mock/GrMockBuffer.h | 35 + gfx/skia/skia/src/gpu/mock/GrMockCaps.cpp | 41 + gfx/skia/skia/src/gpu/mock/GrMockCaps.h | 186 + gfx/skia/skia/src/gpu/mock/GrMockGpu.cpp | 310 + gfx/skia/skia/src/gpu/mock/GrMockGpu.h | 160 + gfx/skia/skia/src/gpu/mock/GrMockOpsRenderPass.h | 61 + .../skia/src/gpu/mock/GrMockStencilAttachment.h | 29 + gfx/skia/skia/src/gpu/mock/GrMockTexture.h | 199 + gfx/skia/skia/src/gpu/mock/GrMockTypes.cpp | 18 + gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.h | 58 + gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.mm | 188 + gfx/skia/skia/src/gpu/mtl/GrMtlCaps.h | 188 + gfx/skia/skia/src/gpu/mtl/GrMtlCaps.mm | 1107 +++ gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.h | 52 + gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.mm | 138 + gfx/skia/skia/src/gpu/mtl/GrMtlCppUtil.h | 22 + gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.h | 65 + gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.mm | 126 + gfx/skia/skia/src/gpu/mtl/GrMtlGpu.h | 238 + gfx/skia/skia/src/gpu/mtl/GrMtlGpu.mm | 1375 ++++ gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.h | 105 + gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm | 420 ++ gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.h | 133 + gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.mm | 248 + .../skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h | 89 + .../skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm | 475 ++ .../src/gpu/mtl/GrMtlPipelineStateDataManager.h | 84 + .../src/gpu/mtl/GrMtlPipelineStateDataManager.mm | 362 + gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.h | 94 + gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.mm | 146 + gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.h | 114 + gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.mm | 290 + gfx/skia/skia/src/gpu/mtl/GrMtlSampler.h | 46 + gfx/skia/skia/src/gpu/mtl/GrMtlSampler.mm | 78 + gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.h | 47 + gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.mm | 70 + gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.h | 54 + .../skia/src/gpu/mtl/GrMtlStencilAttachment.mm | 72 + gfx/skia/skia/src/gpu/mtl/GrMtlTexture.h | 71 + gfx/skia/skia/src/gpu/mtl/GrMtlTexture.mm | 112 + .../skia/src/gpu/mtl/GrMtlTextureRenderTarget.h | 86 + .../skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm | 144 + gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.h | 28 + gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.mm | 25 + gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.h | 99 + gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.mm | 310 + gfx/skia/skia/src/gpu/mtl/GrMtlUtil.h | 108 + gfx/skia/skia/src/gpu/mtl/GrMtlUtil.mm | 351 + gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.h | 27 + gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.mm | 42 + .../skia/src/gpu/ops/GrAAConvexPathRenderer.cpp | 880 +++ gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.h | 23 + .../skia/src/gpu/ops/GrAAConvexTessellator.cpp | 1108 +++ gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.h | 292 + .../skia/src/gpu/ops/GrAAHairLinePathRenderer.cpp | 1106 +++ .../skia/src/gpu/ops/GrAAHairLinePathRenderer.h | 30 + .../gpu/ops/GrAALinearizingConvexPathRenderer.cpp | 394 + .../gpu/ops/GrAALinearizingConvexPathRenderer.h | 23 + gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.cpp | 576 ++ gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.h | 179 + gfx/skia/skia/src/gpu/ops/GrClearOp.cpp | 65 + gfx/skia/skia/src/gpu/ops/GrClearOp.h | 103 + gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.cpp | 28 + gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.h | 68 + .../skia/src/gpu/ops/GrDashLinePathRenderer.cpp | 58 + gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.h | 30 + gfx/skia/skia/src/gpu/ops/GrDashOp.cpp | 1279 ++++ gfx/skia/skia/src/gpu/ops/GrDashOp.h | 38 + gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.cpp | 30 + gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.h | 54 + .../skia/src/gpu/ops/GrDefaultPathRenderer.cpp | 714 ++ gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.h | 44 + gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.cpp | 346 + gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.h | 30 + gfx/skia/skia/src/gpu/ops/GrDrawOp.h | 63 + gfx/skia/skia/src/gpu/ops/GrDrawPathOp.cpp | 154 + gfx/skia/skia/src/gpu/ops/GrDrawPathOp.h | 100 + gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.cpp | 708 ++ gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.h | 39 + gfx/skia/skia/src/gpu/ops/GrDrawableOp.cpp | 34 + gfx/skia/skia/src/gpu/ops/GrDrawableOp.h | 53 + gfx/skia/skia/src/gpu/ops/GrFillRRectOp.cpp | 826 +++ gfx/skia/skia/src/gpu/ops/GrFillRRectOp.h | 92 + gfx/skia/skia/src/gpu/ops/GrFillRectOp.cpp | 447 ++ gfx/skia/skia/src/gpu/ops/GrFillRectOp.h | 58 + gfx/skia/skia/src/gpu/ops/GrLatticeOp.cpp | 479 ++ gfx/skia/skia/src/gpu/ops/GrLatticeOp.h | 36 + gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.cpp | 106 + gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.h | 181 + gfx/skia/skia/src/gpu/ops/GrOp.cpp | 79 + gfx/skia/skia/src/gpu/ops/GrOp.h | 337 + gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.cpp | 3266 +++++++++ gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.h | 68 + gfx/skia/skia/src/gpu/ops/GrPathStencilSettings.h | 96 + gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp | 1042 +++ gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.h | 121 + gfx/skia/skia/src/gpu/ops/GrRegionOp.cpp | 222 + gfx/skia/skia/src/gpu/ops/GrRegionOp.h | 30 + gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.cpp | 692 ++ gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.h | 30 + .../skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp | 201 + .../skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.h | 228 + gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.cpp | 999 +++ gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.h | 83 + .../src/gpu/ops/GrStencilAndCoverPathRenderer.cpp | 171 + .../src/gpu/ops/GrStencilAndCoverPathRenderer.h | 44 + gfx/skia/skia/src/gpu/ops/GrStencilPathOp.cpp | 40 + gfx/skia/skia/src/gpu/ops/GrStencilPathOp.h | 72 + gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.cpp | 830 +++ gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.h | 45 + .../src/gpu/ops/GrTessellatingPathRenderer.cpp | 436 ++ .../skia/src/gpu/ops/GrTessellatingPathRenderer.h | 37 + gfx/skia/skia/src/gpu/ops/GrTextureOp.cpp | 817 +++ gfx/skia/skia/src/gpu/ops/GrTextureOp.h | 68 + gfx/skia/skia/src/gpu/text/GrAtlasManager.cpp | 183 + gfx/skia/skia/src/gpu/text/GrAtlasManager.h | 145 + .../src/gpu/text/GrDistanceFieldAdjustTable.cpp | 104 + .../skia/src/gpu/text/GrDistanceFieldAdjustTable.h | 35 + gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.cpp | 100 + gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.h | 24 + gfx/skia/skia/src/gpu/text/GrStrikeCache.cpp | 244 + gfx/skia/skia/src/gpu/text/GrStrikeCache.h | 138 + gfx/skia/skia/src/gpu/text/GrTextBlob.cpp | 453 ++ gfx/skia/skia/src/gpu/text/GrTextBlob.h | 642 ++ gfx/skia/skia/src/gpu/text/GrTextBlobCache.cpp | 95 + gfx/skia/skia/src/gpu/text/GrTextBlobCache.h | 191 + .../src/gpu/text/GrTextBlobVertexRegenerator.cpp | 287 + gfx/skia/skia/src/gpu/text/GrTextContext.cpp | 270 + gfx/skia/skia/src/gpu/text/GrTextContext.h | 98 + gfx/skia/skia/src/gpu/text/GrTextTarget.h | 56 + .../skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp | 275 + gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h | 57 + gfx/skia/skia/src/gpu/vk/GrVkBuffer.cpp | 268 + gfx/skia/skia/src/gpu/vk/GrVkBuffer.h | 122 + gfx/skia/skia/src/gpu/vk/GrVkBufferView.cpp | 38 + gfx/skia/skia/src/gpu/vk/GrVkBufferView.h | 38 + gfx/skia/skia/src/gpu/vk/GrVkCaps.cpp | 1747 +++++ gfx/skia/skia/src/gpu/vk/GrVkCaps.h | 321 + gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.cpp | 942 +++ gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.h | 347 + gfx/skia/skia/src/gpu/vk/GrVkCommandPool.cpp | 91 + gfx/skia/skia/src/gpu/vk/GrVkCommandPool.h | 69 + gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.cpp | 51 + gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.h | 50 + gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.cpp | 34 + gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.h | 44 + .../skia/src/gpu/vk/GrVkDescriptorSetManager.cpp | 336 + .../skia/src/gpu/vk/GrVkDescriptorSetManager.h | 97 + gfx/skia/skia/src/gpu/vk/GrVkExtensions.cpp | 131 + gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.cpp | 57 + gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.h | 48 + gfx/skia/skia/src/gpu/vk/GrVkGpu.cpp | 2558 +++++++ gfx/skia/skia/src/gpu/vk/GrVkGpu.h | 313 + gfx/skia/skia/src/gpu/vk/GrVkImage.cpp | 331 + gfx/skia/skia/src/gpu/vk/GrVkImage.h | 259 + gfx/skia/skia/src/gpu/vk/GrVkImageLayout.h | 32 + gfx/skia/skia/src/gpu/vk/GrVkImageView.cpp | 77 + gfx/skia/skia/src/gpu/vk/GrVkImageView.h | 53 + gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.cpp | 62 + gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.h | 37 + gfx/skia/skia/src/gpu/vk/GrVkInterface.cpp | 483 ++ gfx/skia/skia/src/gpu/vk/GrVkInterface.h | 235 + gfx/skia/skia/src/gpu/vk/GrVkMemory.cpp | 230 + gfx/skia/skia/src/gpu/vk/GrVkMemory.h | 56 + gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.cpp | 662 ++ gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.h | 131 + gfx/skia/skia/src/gpu/vk/GrVkPipeline.cpp | 642 ++ gfx/skia/skia/src/gpu/vk/GrVkPipeline.h | 66 + gfx/skia/skia/src/gpu/vk/GrVkPipelineState.cpp | 337 + gfx/skia/skia/src/gpu/vk/GrVkPipelineState.h | 150 + .../skia/src/gpu/vk/GrVkPipelineStateBuilder.cpp | 353 + .../skia/src/gpu/vk/GrVkPipelineStateBuilder.h | 121 + .../skia/src/gpu/vk/GrVkPipelineStateCache.cpp | 120 + .../src/gpu/vk/GrVkPipelineStateDataManager.cpp | 346 + .../skia/src/gpu/vk/GrVkPipelineStateDataManager.h | 85 + gfx/skia/skia/src/gpu/vk/GrVkRenderPass.cpp | 266 + gfx/skia/skia/src/gpu/vk/GrVkRenderPass.h | 158 + gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.cpp | 401 ++ gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.h | 182 + gfx/skia/skia/src/gpu/vk/GrVkResource.h | 243 + gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.cpp | 585 ++ gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.h | 284 + gfx/skia/skia/src/gpu/vk/GrVkSampler.cpp | 132 + gfx/skia/skia/src/gpu/vk/GrVkSampler.h | 88 + .../skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp | 112 + .../skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h | 75 + .../skia/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp | 168 + .../skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h | 113 + gfx/skia/skia/src/gpu/vk/GrVkSemaphore.cpp | 80 + gfx/skia/skia/src/gpu/vk/GrVkSemaphore.h | 90 + gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.cpp | 102 + gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.h | 58 + gfx/skia/skia/src/gpu/vk/GrVkTexture.cpp | 252 + gfx/skia/skia/src/gpu/vk/GrVkTexture.h | 81 + .../skia/src/gpu/vk/GrVkTextureRenderTarget.cpp | 251 + gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.h | 120 + gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.cpp | 61 + gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.h | 47 + gfx/skia/skia/src/gpu/vk/GrVkTypesPriv.cpp | 50 + gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.cpp | 103 + gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.h | 59 + gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.cpp | 341 + gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.h | 120 + gfx/skia/skia/src/gpu/vk/GrVkUtil.cpp | 197 + gfx/skia/skia/src/gpu/vk/GrVkUtil.h | 97 + gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.cpp | 109 + gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.h | 27 + gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.cpp | 73 + gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.h | 37 + gfx/skia/skia/src/image/SkImage.cpp | 535 ++ gfx/skia/skia/src/image/SkImage_Base.h | 128 + gfx/skia/skia/src/image/SkImage_Gpu.cpp | 681 ++ gfx/skia/skia/src/image/SkImage_Gpu.h | 75 + gfx/skia/skia/src/image/SkImage_GpuBase.cpp | 518 ++ gfx/skia/skia/src/image/SkImage_GpuBase.h | 103 + gfx/skia/skia/src/image/SkImage_GpuYUVA.cpp | 400 ++ gfx/skia/skia/src/image/SkImage_GpuYUVA.h | 107 + gfx/skia/skia/src/image/SkImage_Lazy.cpp | 568 ++ gfx/skia/skia/src/image/SkImage_Lazy.h | 100 + gfx/skia/skia/src/image/SkImage_Raster.cpp | 360 + gfx/skia/skia/src/image/SkReadPixelsRec.h | 50 + gfx/skia/skia/src/image/SkSurface.cpp | 643 ++ gfx/skia/skia/src/image/SkSurface_Base.h | 169 + gfx/skia/skia/src/image/SkSurface_Gpu.cpp | 741 ++ gfx/skia/skia/src/image/SkSurface_Gpu.h | 69 + gfx/skia/skia/src/image/SkSurface_GpuMtl.mm | 62 + gfx/skia/skia/src/image/SkSurface_Raster.cpp | 214 + gfx/skia/skia/src/images/SkImageEncoder.cpp | 105 + gfx/skia/skia/src/images/SkImageEncoderFns.h | 178 + gfx/skia/skia/src/images/SkImageEncoderPriv.h | 38 + gfx/skia/skia/src/images/SkJPEGWriteUtility.cpp | 67 + gfx/skia/skia/src/images/SkJPEGWriteUtility.h | 39 + gfx/skia/skia/src/images/SkJpegEncoder.cpp | 257 + gfx/skia/skia/src/images/SkPngEncoder.cpp | 450 ++ gfx/skia/skia/src/images/SkWebpEncoder.cpp | 219 + gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.cpp | 241 + gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.h | 67 + gfx/skia/skia/src/opts/Sk4px_NEON.h | 56 + gfx/skia/skia/src/opts/Sk4px_SSE2.h | 76 + gfx/skia/skia/src/opts/Sk4px_none.h | 59 + gfx/skia/skia/src/opts/SkBitmapFilter_opts.h | 927 +++ gfx/skia/skia/src/opts/SkBitmapProcState_opts.h | 502 ++ gfx/skia/skia/src/opts/SkBlitMask_opts.h | 231 + gfx/skia/skia/src/opts/SkBlitRow_opts.h | 202 + gfx/skia/skia/src/opts/SkChecksum_opts.h | 212 + gfx/skia/skia/src/opts/SkOpts_avx.cpp | 41 + gfx/skia/skia/src/opts/SkOpts_crc32.cpp | 17 + gfx/skia/skia/src/opts/SkOpts_hsw.cpp | 142 + gfx/skia/skia/src/opts/SkOpts_sse41.cpp | 31 + gfx/skia/skia/src/opts/SkOpts_sse42.cpp | 18 + gfx/skia/skia/src/opts/SkOpts_ssse3.cpp | 33 + gfx/skia/skia/src/opts/SkRasterPipeline_opts.h | 4557 ++++++++++++ gfx/skia/skia/src/opts/SkSwizzler_opts.h | 823 +++ gfx/skia/skia/src/opts/SkUtils_opts.h | 66 + gfx/skia/skia/src/opts/SkXfermode_opts.h | 138 + gfx/skia/skia/src/pathops/SkAddIntersections.cpp | 579 ++ gfx/skia/skia/src/pathops/SkAddIntersections.h | 17 + .../skia/src/pathops/SkDConicLineIntersection.cpp | 384 + .../skia/src/pathops/SkDCubicLineIntersection.cpp | 455 ++ gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp | 44 + gfx/skia/skia/src/pathops/SkDLineIntersection.cpp | 339 + .../skia/src/pathops/SkDQuadLineIntersection.cpp | 470 ++ gfx/skia/skia/src/pathops/SkIntersectionHelper.h | 113 + gfx/skia/skia/src/pathops/SkIntersections.cpp | 173 + gfx/skia/skia/src/pathops/SkIntersections.h | 333 + gfx/skia/skia/src/pathops/SkLineParameters.h | 181 + gfx/skia/skia/src/pathops/SkOpAngle.cpp | 1141 +++ gfx/skia/skia/src/pathops/SkOpAngle.h | 146 + gfx/skia/skia/src/pathops/SkOpBuilder.cpp | 195 + gfx/skia/skia/src/pathops/SkOpCoincidence.cpp | 1448 ++++ gfx/skia/skia/src/pathops/SkOpCoincidence.h | 303 + gfx/skia/skia/src/pathops/SkOpContour.cpp | 109 + gfx/skia/skia/src/pathops/SkOpContour.h | 454 ++ gfx/skia/skia/src/pathops/SkOpCubicHull.cpp | 150 + gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp | 357 + gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h | 75 + gfx/skia/skia/src/pathops/SkOpSegment.cpp | 1781 +++++ gfx/skia/skia/src/pathops/SkOpSegment.h | 455 ++ gfx/skia/skia/src/pathops/SkOpSpan.cpp | 484 ++ gfx/skia/skia/src/pathops/SkOpSpan.h | 578 ++ gfx/skia/skia/src/pathops/SkPathOpsAsWinding.cpp | 427 ++ gfx/skia/skia/src/pathops/SkPathOpsBounds.h | 65 + gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp | 333 + gfx/skia/skia/src/pathops/SkPathOpsCommon.h | 30 + gfx/skia/skia/src/pathops/SkPathOpsConic.cpp | 190 + gfx/skia/skia/src/pathops/SkPathOpsConic.h | 193 + gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp | 750 ++ gfx/skia/skia/src/pathops/SkPathOpsCubic.h | 240 + gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp | 145 + gfx/skia/skia/src/pathops/SkPathOpsCurve.h | 421 ++ gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp | 3147 ++++++++ gfx/skia/skia/src/pathops/SkPathOpsDebug.h | 495 ++ gfx/skia/skia/src/pathops/SkPathOpsLine.cpp | 149 + gfx/skia/skia/src/pathops/SkPathOpsLine.h | 39 + gfx/skia/skia/src/pathops/SkPathOpsOp.cpp | 382 + gfx/skia/skia/src/pathops/SkPathOpsPoint.h | 279 + gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp | 416 ++ gfx/skia/skia/src/pathops/SkPathOpsQuad.h | 184 + gfx/skia/skia/src/pathops/SkPathOpsRect.cpp | 66 + gfx/skia/skia/src/pathops/SkPathOpsRect.h | 77 + gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp | 226 + gfx/skia/skia/src/pathops/SkPathOpsTCurve.h | 48 + gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp | 2138 ++++++ gfx/skia/skia/src/pathops/SkPathOpsTSect.h | 372 + gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp | 74 + gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp | 255 + gfx/skia/skia/src/pathops/SkPathOpsTypes.h | 624 ++ gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp | 425 ++ gfx/skia/skia/src/pathops/SkPathWriter.cpp | 434 ++ gfx/skia/skia/src/pathops/SkPathWriter.h | 54 + gfx/skia/skia/src/pathops/SkReduceOrder.cpp | 283 + gfx/skia/skia/src/pathops/SkReduceOrder.h | 35 + gfx/skia/skia/src/pdf/SkBitmapKey.h | 22 + gfx/skia/skia/src/pdf/SkClusterator.cpp | 64 + gfx/skia/skia/src/pdf/SkClusterator.h | 47 + gfx/skia/skia/src/pdf/SkDeflate.cpp | 126 + gfx/skia/skia/src/pdf/SkDeflate.h | 53 + gfx/skia/skia/src/pdf/SkDocument_PDF_None.cpp | 15 + gfx/skia/skia/src/pdf/SkJpegInfo.cpp | 128 + gfx/skia/skia/src/pdf/SkJpegInfo.h | 25 + gfx/skia/skia/src/pdf/SkKeyedImage.cpp | 49 + gfx/skia/skia/src/pdf/SkKeyedImage.h | 46 + gfx/skia/skia/src/pdf/SkPDFBitmap.cpp | 290 + gfx/skia/skia/src/pdf/SkPDFBitmap.h | 22 + gfx/skia/skia/src/pdf/SkPDFDevice.cpp | 1765 +++++ gfx/skia/skia/src/pdf/SkPDFDevice.h | 200 + gfx/skia/skia/src/pdf/SkPDFDocument.cpp | 594 ++ gfx/skia/skia/src/pdf/SkPDFDocumentPriv.h | 159 + gfx/skia/skia/src/pdf/SkPDFFont.cpp | 690 ++ gfx/skia/skia/src/pdf/SkPDFFont.h | 141 + gfx/skia/skia/src/pdf/SkPDFFormXObject.cpp | 39 + gfx/skia/skia/src/pdf/SkPDFFormXObject.h | 28 + gfx/skia/skia/src/pdf/SkPDFGlyphUse.h | 49 + gfx/skia/skia/src/pdf/SkPDFGradientShader.cpp | 921 +++ gfx/skia/skia/src/pdf/SkPDFGradientShader.h | 66 + gfx/skia/skia/src/pdf/SkPDFGraphicStackState.cpp | 250 + gfx/skia/skia/src/pdf/SkPDFGraphicStackState.h | 40 + gfx/skia/skia/src/pdf/SkPDFGraphicState.cpp | 140 + gfx/skia/skia/src/pdf/SkPDFGraphicState.h | 71 + .../skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp | 206 + .../skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h | 23 + gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.cpp | 220 + gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.h | 28 + gfx/skia/skia/src/pdf/SkPDFMetadata.cpp | 412 ++ gfx/skia/skia/src/pdf/SkPDFMetadata.h | 29 + gfx/skia/skia/src/pdf/SkPDFResourceDict.cpp | 96 + gfx/skia/skia/src/pdf/SkPDFResourceDict.h | 50 + gfx/skia/skia/src/pdf/SkPDFShader.cpp | 375 + gfx/skia/skia/src/pdf/SkPDFShader.h | 67 + gfx/skia/skia/src/pdf/SkPDFSubsetFont.cpp | 181 + gfx/skia/skia/src/pdf/SkPDFSubsetFont.h | 16 + gfx/skia/skia/src/pdf/SkPDFTag.cpp | 226 + gfx/skia/skia/src/pdf/SkPDFTag.h | 39 + gfx/skia/skia/src/pdf/SkPDFType1Font.cpp | 335 + gfx/skia/skia/src/pdf/SkPDFType1Font.h | 11 + gfx/skia/skia/src/pdf/SkPDFTypes.cpp | 486 ++ gfx/skia/skia/src/pdf/SkPDFTypes.h | 204 + gfx/skia/skia/src/pdf/SkPDFUnion.h | 128 + gfx/skia/skia/src/pdf/SkPDFUtils.cpp | 395 + gfx/skia/skia/src/pdf/SkPDFUtils.h | 137 + gfx/skia/skia/src/pdf/SkUUID.h | 18 + gfx/skia/skia/src/ports/SkDebug_android.cpp | 35 + gfx/skia/skia/src/ports/SkDebug_stdio.cpp | 20 + gfx/skia/skia/src/ports/SkDebug_win.cpp | 34 + .../skia/src/ports/SkDiscardableMemory_none.cpp | 14 + gfx/skia/skia/src/ports/SkFontConfigInterface.cpp | 33 + .../src/ports/SkFontConfigInterface_direct.cpp | 710 ++ .../skia/src/ports/SkFontConfigInterface_direct.h | 39 + .../ports/SkFontConfigInterface_direct_factory.cpp | 16 + gfx/skia/skia/src/ports/SkFontConfigTypeface.h | 85 + gfx/skia/skia/src/ports/SkFontHost_FreeType.cpp | 2034 ++++++ .../skia/src/ports/SkFontHost_FreeType_common.cpp | 760 ++ .../skia/src/ports/SkFontHost_FreeType_common.h | 127 + gfx/skia/skia/src/ports/SkFontHost_cairo.cpp | 688 ++ gfx/skia/skia/src/ports/SkFontHost_mac.cpp | 3025 ++++++++ gfx/skia/skia/src/ports/SkFontHost_win.cpp | 2287 ++++++ .../src/ports/SkFontMgr_FontConfigInterface.cpp | 328 + .../SkFontMgr_FontConfigInterface_factory.cpp | 18 + gfx/skia/skia/src/ports/SkFontMgr_android.cpp | 598 ++ .../skia/src/ports/SkFontMgr_android_factory.cpp | 17 + .../skia/src/ports/SkFontMgr_android_parser.cpp | 836 +++ gfx/skia/skia/src/ports/SkFontMgr_android_parser.h | 216 + gfx/skia/skia/src/ports/SkFontMgr_custom.cpp | 289 + gfx/skia/skia/src/ports/SkFontMgr_custom.h | 161 + .../skia/src/ports/SkFontMgr_custom_directory.cpp | 104 + .../ports/SkFontMgr_custom_directory_factory.cpp | 17 + .../skia/src/ports/SkFontMgr_custom_embedded.cpp | 122 + .../ports/SkFontMgr_custom_embedded_factory.cpp | 17 + gfx/skia/skia/src/ports/SkFontMgr_custom_empty.cpp | 27 + .../src/ports/SkFontMgr_custom_empty_factory.cpp | 13 + .../skia/src/ports/SkFontMgr_empty_factory.cpp | 13 + gfx/skia/skia/src/ports/SkFontMgr_fontconfig.cpp | 1042 +++ .../src/ports/SkFontMgr_fontconfig_factory.cpp | 14 + gfx/skia/skia/src/ports/SkFontMgr_fuchsia.cpp | 505 ++ gfx/skia/skia/src/ports/SkFontMgr_win_dw.cpp | 1227 ++++ .../skia/src/ports/SkFontMgr_win_dw_factory.cpp | 18 + .../src/ports/SkGlobalInitialization_default.cpp | 58 + gfx/skia/skia/src/ports/SkImageEncoder_CG.cpp | 118 + gfx/skia/skia/src/ports/SkImageEncoder_WIC.cpp | 198 + gfx/skia/skia/src/ports/SkImageGeneratorCG.cpp | 157 + gfx/skia/skia/src/ports/SkImageGeneratorWIC.cpp | 206 + gfx/skia/skia/src/ports/SkImageGenerator_none.cpp | 12 + gfx/skia/skia/src/ports/SkImageGenerator_skia.cpp | 13 + gfx/skia/skia/src/ports/SkMemory_malloc.cpp | 80 + gfx/skia/skia/src/ports/SkMemory_mozalloc.cpp | 49 + gfx/skia/skia/src/ports/SkOSFile_ios.h | 47 + gfx/skia/skia/src/ports/SkOSFile_posix.cpp | 213 + gfx/skia/skia/src/ports/SkOSFile_stdio.cpp | 188 + gfx/skia/skia/src/ports/SkOSFile_win.cpp | 282 + gfx/skia/skia/src/ports/SkOSLibrary.h | 14 + gfx/skia/skia/src/ports/SkOSLibrary_posix.cpp | 21 + gfx/skia/skia/src/ports/SkOSLibrary_win.cpp | 21 + .../skia/src/ports/SkRemotableFontMgr_win_dw.cpp | 472 ++ gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp | 1241 ++++ gfx/skia/skia/src/ports/SkScalerContext_win_dw.h | 106 + gfx/skia/skia/src/ports/SkTLS_none.cpp | 18 + gfx/skia/skia/src/ports/SkTLS_pthread.cpp | 25 + gfx/skia/skia/src/ports/SkTLS_win.cpp | 80 + gfx/skia/skia/src/ports/SkTypeface_win_dw.cpp | 533 ++ gfx/skia/skia/src/ports/SkTypeface_win_dw.h | 171 + gfx/skia/skia/src/sfnt/SkIBMFamilyClass.h | 142 + gfx/skia/skia/src/sfnt/SkOTTableTypes.h | 62 + gfx/skia/skia/src/sfnt/SkOTTable_EBDT.h | 108 + gfx/skia/skia/src/sfnt/SkOTTable_EBLC.h | 150 + gfx/skia/skia/src/sfnt/SkOTTable_EBSC.h | 41 + gfx/skia/skia/src/sfnt/SkOTTable_OS_2.h | 52 + gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V0.h | 146 + gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V1.h | 515 ++ gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V2.h | 538 ++ gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V3.h | 547 ++ gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V4.h | 582 ++ gfx/skia/skia/src/sfnt/SkOTTable_OS_2_VA.h | 141 + gfx/skia/skia/src/sfnt/SkOTTable_fvar.h | 56 + gfx/skia/skia/src/sfnt/SkOTTable_gasp.h | 72 + gfx/skia/skia/src/sfnt/SkOTTable_glyf.h | 213 + gfx/skia/skia/src/sfnt/SkOTTable_head.h | 146 + gfx/skia/skia/src/sfnt/SkOTTable_hhea.h | 54 + gfx/skia/skia/src/sfnt/SkOTTable_loca.h | 31 + gfx/skia/skia/src/sfnt/SkOTTable_maxp.h | 34 + gfx/skia/skia/src/sfnt/SkOTTable_maxp_CFF.h | 30 + gfx/skia/skia/src/sfnt/SkOTTable_maxp_TT.h | 48 + gfx/skia/skia/src/sfnt/SkOTTable_name.cpp | 586 ++ gfx/skia/skia/src/sfnt/SkOTTable_name.h | 577 ++ gfx/skia/skia/src/sfnt/SkOTTable_post.h | 50 + gfx/skia/skia/src/sfnt/SkOTUtils.cpp | 239 + gfx/skia/skia/src/sfnt/SkOTUtils.h | 105 + gfx/skia/skia/src/sfnt/SkPanose.h | 527 ++ gfx/skia/skia/src/sfnt/SkSFNTHeader.h | 70 + gfx/skia/skia/src/sfnt/SkTTCFHeader.h | 57 + gfx/skia/skia/src/shaders/SkBitmapProcShader.cpp | 109 + gfx/skia/skia/src/shaders/SkBitmapProcShader.h | 25 + gfx/skia/skia/src/shaders/SkColorFilterShader.cpp | 79 + gfx/skia/skia/src/shaders/SkColorFilterShader.h | 37 + gfx/skia/skia/src/shaders/SkColorShader.cpp | 116 + gfx/skia/skia/src/shaders/SkColorShader.h | 71 + gfx/skia/skia/src/shaders/SkComposeShader.cpp | 253 + gfx/skia/skia/src/shaders/SkComposeShader.h | 101 + gfx/skia/skia/src/shaders/SkEmptyShader.h | 46 + gfx/skia/skia/src/shaders/SkImageShader.cpp | 614 ++ gfx/skia/skia/src/shaders/SkImageShader.h | 61 + gfx/skia/skia/src/shaders/SkLightingShader.cpp | 490 ++ gfx/skia/skia/src/shaders/SkLightingShader.h | 39 + gfx/skia/skia/src/shaders/SkLights.cpp | 68 + gfx/skia/skia/src/shaders/SkLights.h | 192 + gfx/skia/skia/src/shaders/SkLocalMatrixShader.cpp | 106 + gfx/skia/skia/src/shaders/SkLocalMatrixShader.h | 61 + gfx/skia/skia/src/shaders/SkPerlinNoiseShader.cpp | 1546 ++++ gfx/skia/skia/src/shaders/SkPictureShader.cpp | 365 + gfx/skia/skia/src/shaders/SkPictureShader.h | 81 + gfx/skia/skia/src/shaders/SkRTShader.cpp | 156 + gfx/skia/skia/src/shaders/SkRTShader.h | 64 + gfx/skia/skia/src/shaders/SkShader.cpp | 199 + gfx/skia/skia/src/shaders/SkShaderBase.h | 252 + .../src/shaders/gradients/Sk4fGradientBase.cpp | 313 + .../skia/src/shaders/gradients/Sk4fGradientBase.h | 79 + .../skia/src/shaders/gradients/Sk4fGradientPriv.h | 105 + .../src/shaders/gradients/Sk4fLinearGradient.cpp | 388 + .../src/shaders/gradients/Sk4fLinearGradient.h | 40 + .../src/shaders/gradients/SkGradientShader.cpp | 886 +++ .../src/shaders/gradients/SkGradientShaderPriv.h | 146 + .../src/shaders/gradients/SkLinearGradient.cpp | 98 + .../skia/src/shaders/gradients/SkLinearGradient.h | 44 + .../src/shaders/gradients/SkRadialGradient.cpp | 77 + .../skia/src/shaders/gradients/SkRadialGradient.h | 39 + .../skia/src/shaders/gradients/SkSweepGradient.cpp | 82 + .../skia/src/shaders/gradients/SkSweepGradient.h | 44 + .../gradients/SkTwoPointConicalGradient.cpp | 248 + .../shaders/gradients/SkTwoPointConicalGradient.h | 91 + gfx/skia/skia/src/sksl/GLSL.std.450.h | 131 + gfx/skia/skia/src/sksl/README | 173 + gfx/skia/skia/src/sksl/SkSLASTFile.h | 34 + gfx/skia/skia/src/sksl/SkSLASTNode.cpp | 238 + gfx/skia/skia/src/sksl/SkSLASTNode.h | 635 ++ gfx/skia/skia/src/sksl/SkSLByteCode.cpp | 1760 +++++ gfx/skia/skia/src/sksl/SkSLByteCode.h | 304 + gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.cpp | 1665 +++++ gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.h | 353 + gfx/skia/skia/src/sksl/SkSLCFGGenerator.cpp | 673 ++ gfx/skia/skia/src/sksl/SkSLCFGGenerator.h | 170 + gfx/skia/skia/src/sksl/SkSLCPP.h | 57 + gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.cpp | 1343 ++++ gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.h | 141 + gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.cpp | 234 + gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.h | 133 + gfx/skia/skia/src/sksl/SkSLCodeGenerator.h | 42 + gfx/skia/skia/src/sksl/SkSLCompiler.cpp | 1676 +++++ gfx/skia/skia/src/sksl/SkSLCompiler.h | 239 + gfx/skia/skia/src/sksl/SkSLContext.h | 429 ++ gfx/skia/skia/src/sksl/SkSLDefines.h | 58 + gfx/skia/skia/src/sksl/SkSLErrorReporter.h | 33 + gfx/skia/skia/src/sksl/SkSLExternalValue.h | 116 + gfx/skia/skia/src/sksl/SkSLFileOutputStream.h | 76 + gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.cpp | 1786 +++++ gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.h | 266 + gfx/skia/skia/src/sksl/SkSLHCodeGenerator.cpp | 389 + gfx/skia/skia/src/sksl/SkSLHCodeGenerator.h | 86 + gfx/skia/skia/src/sksl/SkSLIRGenerator.cpp | 2493 +++++++ gfx/skia/skia/src/sksl/SkSLIRGenerator.h | 180 + gfx/skia/skia/src/sksl/SkSLLexer.cpp | 1037 +++ gfx/skia/skia/src/sksl/SkSLLexer.h | 245 + gfx/skia/skia/src/sksl/SkSLMain.cpp | 152 + gfx/skia/skia/src/sksl/SkSLMemoryLayout.h | 140 + gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.cpp | 1702 +++++ gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.h | 288 + gfx/skia/skia/src/sksl/SkSLOutputStream.cpp | 30 + gfx/skia/skia/src/sksl/SkSLOutputStream.h | 42 + gfx/skia/skia/src/sksl/SkSLParser.cpp | 2150 ++++++ gfx/skia/skia/src/sksl/SkSLParser.h | 289 + .../src/sksl/SkSLPipelineStageCodeGenerator.cpp | 297 + .../skia/src/sksl/SkSLPipelineStageCodeGenerator.h | 73 + gfx/skia/skia/src/sksl/SkSLPosition.h | 39 + gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.cpp | 3257 +++++++++ gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.h | 411 ++ .../src/sksl/SkSLSectionAndParameterHelper.cpp | 239 + .../skia/src/sksl/SkSLSectionAndParameterHelper.h | 125 + gfx/skia/skia/src/sksl/SkSLString.cpp | 274 + gfx/skia/skia/src/sksl/SkSLString.h | 153 + gfx/skia/skia/src/sksl/SkSLStringStream.h | 87 + gfx/skia/skia/src/sksl/SkSLUtil.cpp | 76 + gfx/skia/skia/src/sksl/SkSLUtil.h | 396 + gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h | 61 + gfx/skia/skia/src/sksl/ir/SkSLBlock.h | 63 + gfx/skia/skia/src/sksl/ir/SkSLBoolLiteral.h | 57 + gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h | 36 + gfx/skia/skia/src/sksl/ir/SkSLConstructor.h | 223 + gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h | 36 + gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h | 36 + gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h | 43 + gfx/skia/skia/src/sksl/ir/SkSLEnum.h | 60 + gfx/skia/skia/src/sksl/ir/SkSLExpression.h | 149 + .../skia/src/sksl/ir/SkSLExpressionStatement.h | 39 + gfx/skia/skia/src/sksl/ir/SkSLExtension.h | 38 + .../skia/src/sksl/ir/SkSLExternalFunctionCall.h | 62 + .../skia/src/sksl/ir/SkSLExternalValueReference.h | 43 + gfx/skia/skia/src/sksl/ir/SkSLField.h | 42 + gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h | 56 + gfx/skia/skia/src/sksl/ir/SkSLFloatLiteral.h | 67 + gfx/skia/skia/src/sksl/ir/SkSLForStatement.h | 67 + gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h | 64 + .../skia/src/sksl/ir/SkSLFunctionDeclaration.h | 117 + gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h | 44 + gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h | 51 + gfx/skia/skia/src/sksl/ir/SkSLIRNode.h | 35 + gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h | 56 + gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h | 89 + gfx/skia/skia/src/sksl/ir/SkSLIntLiteral.h | 69 + gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h | 83 + gfx/skia/skia/src/sksl/ir/SkSLLayout.h | 432 ++ gfx/skia/skia/src/sksl/ir/SkSLModifiers.h | 116 + .../skia/src/sksl/ir/SkSLModifiersDeclaration.h | 41 + gfx/skia/skia/src/sksl/ir/SkSLNop.h | 40 + gfx/skia/skia/src/sksl/ir/SkSLNullLiteral.h | 51 + gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h | 46 + gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h | 80 + gfx/skia/skia/src/sksl/ir/SkSLProgram.h | 298 + gfx/skia/skia/src/sksl/ir/SkSLProgramElement.h | 44 + gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h | 49 + gfx/skia/skia/src/sksl/ir/SkSLSection.h | 47 + gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp | 22 + gfx/skia/skia/src/sksl/ir/SkSLSetting.h | 55 + gfx/skia/skia/src/sksl/ir/SkSLStatement.h | 55 + gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h | 58 + gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h | 65 + gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h | 166 + gfx/skia/skia/src/sksl/ir/SkSLSymbol.h | 43 + gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp | 139 + gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h | 66 + gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h | 53 + gfx/skia/skia/src/sksl/ir/SkSLType.cpp | 232 + gfx/skia/skia/src/sksl/ir/SkSLType.h | 442 ++ gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h | 49 + gfx/skia/skia/src/sksl/ir/SkSLUnresolvedFunction.h | 40 + gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h | 115 + .../src/sksl/ir/SkSLVarDeclarationsStatement.h | 49 + gfx/skia/skia/src/sksl/ir/SkSLVariable.h | 84 + .../skia/src/sksl/ir/SkSLVariableReference.cpp | 114 + gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h | 75 + gfx/skia/skia/src/sksl/ir/SkSLWhileStatement.h | 43 + gfx/skia/skia/src/sksl/lex/DFA.h | 37 + gfx/skia/skia/src/sksl/lex/DFAState.h | 73 + gfx/skia/skia/src/sksl/lex/LexUtil.h | 18 + gfx/skia/skia/src/sksl/lex/Main.cpp | 216 + gfx/skia/skia/src/sksl/lex/NFA.cpp | 41 + gfx/skia/skia/src/sksl/lex/NFA.h | 54 + gfx/skia/skia/src/sksl/lex/NFAState.h | 150 + gfx/skia/skia/src/sksl/lex/NFAtoDFA.h | 168 + gfx/skia/skia/src/sksl/lex/RegexNode.cpp | 117 + gfx/skia/skia/src/sksl/lex/RegexNode.h | 78 + gfx/skia/skia/src/sksl/lex/RegexParser.cpp | 176 + gfx/skia/skia/src/sksl/lex/RegexParser.h | 89 + gfx/skia/skia/src/sksl/lex/sksl.lex | 98 + gfx/skia/skia/src/sksl/sksl_enums.inc | 35 + gfx/skia/skia/src/sksl/sksl_fp.inc | 34 + gfx/skia/skia/src/sksl/sksl_frag.inc | 23 + gfx/skia/skia/src/sksl/sksl_geom.inc | 24 + gfx/skia/skia/src/sksl/sksl_gpu.inc | 299 + gfx/skia/skia/src/sksl/sksl_interp.inc | 53 + gfx/skia/skia/src/sksl/sksl_pipeline.inc | 3 + gfx/skia/skia/src/sksl/sksl_vert.inc | 14 + gfx/skia/skia/src/sksl/spirv.h | 870 +++ gfx/skia/skia/src/svg/SkSVGCanvas.cpp | 26 + gfx/skia/skia/src/svg/SkSVGDevice.cpp | 1048 +++ gfx/skia/skia/src/svg/SkSVGDevice.h | 76 + gfx/skia/skia/src/utils/Sk3D.cpp | 64 + gfx/skia/skia/src/utils/SkAnimCodecPlayer.cpp | 96 + gfx/skia/skia/src/utils/SkBase64.cpp | 163 + gfx/skia/skia/src/utils/SkBitSet.h | 67 + gfx/skia/skia/src/utils/SkCallableTraits.h | 86 + gfx/skia/skia/src/utils/SkCamera.cpp | 397 + gfx/skia/skia/src/utils/SkCanvasStack.cpp | 105 + gfx/skia/skia/src/utils/SkCanvasStack.h | 60 + gfx/skia/skia/src/utils/SkCanvasStateUtils.cpp | 318 + gfx/skia/skia/src/utils/SkCharToGlyphCache.cpp | 130 + gfx/skia/skia/src/utils/SkCharToGlyphCache.h | 62 + gfx/skia/skia/src/utils/SkDashPath.cpp | 461 ++ gfx/skia/skia/src/utils/SkDashPathPriv.h | 52 + gfx/skia/skia/src/utils/SkEventTracer.cpp | 63 + gfx/skia/skia/src/utils/SkFloatToDecimal.cpp | 184 + gfx/skia/skia/src/utils/SkFloatToDecimal.h | 34 + gfx/skia/skia/src/utils/SkFloatUtils.h | 173 + gfx/skia/skia/src/utils/SkFrontBufferedStream.cpp | 214 + gfx/skia/skia/src/utils/SkInterpolator.cpp | 272 + gfx/skia/skia/src/utils/SkJSON.cpp | 917 +++ gfx/skia/skia/src/utils/SkJSON.h | 361 + gfx/skia/skia/src/utils/SkJSONWriter.cpp | 46 + gfx/skia/skia/src/utils/SkJSONWriter.h | 358 + gfx/skia/skia/src/utils/SkLua.cpp | 1993 +++++ gfx/skia/skia/src/utils/SkLuaCanvas.cpp | 288 + gfx/skia/skia/src/utils/SkMatrix22.cpp | 40 + gfx/skia/skia/src/utils/SkMatrix22.h | 31 + gfx/skia/skia/src/utils/SkMultiPictureDocument.cpp | 203 + gfx/skia/skia/src/utils/SkMultiPictureDocument.h | 44 + .../skia/src/utils/SkMultiPictureDocumentPriv.h | 21 + gfx/skia/skia/src/utils/SkNWayCanvas.cpp | 365 + gfx/skia/skia/src/utils/SkNullCanvas.cpp | 18 + gfx/skia/skia/src/utils/SkOSPath.cpp | 45 + gfx/skia/skia/src/utils/SkOSPath.h | 55 + gfx/skia/skia/src/utils/SkPaintFilterCanvas.cpp | 295 + gfx/skia/skia/src/utils/SkParse.cpp | 288 + gfx/skia/skia/src/utils/SkParseColor.cpp | 280 + gfx/skia/skia/src/utils/SkParsePath.cpp | 269 + gfx/skia/skia/src/utils/SkPatchUtils.cpp | 369 + gfx/skia/skia/src/utils/SkPatchUtils.h | 57 + gfx/skia/skia/src/utils/SkPolyUtils.cpp | 1838 +++++ gfx/skia/skia/src/utils/SkPolyUtils.h | 112 + gfx/skia/skia/src/utils/SkShadowTessellator.cpp | 1169 +++ gfx/skia/skia/src/utils/SkShadowTessellator.h | 44 + gfx/skia/skia/src/utils/SkShadowUtils.cpp | 766 ++ gfx/skia/skia/src/utils/SkShaperJSONWriter.cpp | 236 + gfx/skia/skia/src/utils/SkShaperJSONWriter.h | 68 + gfx/skia/skia/src/utils/SkTextUtils.cpp | 49 + gfx/skia/skia/src/utils/SkThreadUtils_pthread.cpp | 11 + gfx/skia/skia/src/utils/SkThreadUtils_win.cpp | 11 + gfx/skia/skia/src/utils/SkUTF.cpp | 253 + gfx/skia/skia/src/utils/SkUTF.h | 68 + gfx/skia/skia/src/utils/SkWhitelistChecksums.inc | 50 + gfx/skia/skia/src/utils/SkWhitelistTypefaces.cpp | 273 + gfx/skia/skia/src/utils/mac/SkCreateCGImageRef.cpp | 282 + gfx/skia/skia/src/utils/mac/SkStream_mac.cpp | 83 + gfx/skia/skia/src/utils/mac/SkUniqueCFRef.h | 25 + gfx/skia/skia/src/utils/win/SkAutoCoInitialize.cpp | 32 + gfx/skia/skia/src/utils/win/SkAutoCoInitialize.h | 32 + gfx/skia/skia/src/utils/win/SkDWrite.cpp | 129 + gfx/skia/skia/src/utils/win/SkDWrite.h | 99 + .../skia/src/utils/win/SkDWriteFontFileStream.cpp | 235 + .../skia/src/utils/win/SkDWriteFontFileStream.h | 88 + .../skia/src/utils/win/SkDWriteGeometrySink.cpp | 150 + gfx/skia/skia/src/utils/win/SkDWriteGeometrySink.h | 44 + .../skia/src/utils/win/SkDWriteNTDDI_VERSION.h | 31 + gfx/skia/skia/src/utils/win/SkHRESULT.cpp | 40 + gfx/skia/skia/src/utils/win/SkHRESULT.h | 62 + gfx/skia/skia/src/utils/win/SkIStream.cpp | 236 + gfx/skia/skia/src/utils/win/SkIStream.h | 101 + gfx/skia/skia/src/utils/win/SkObjBase.h | 25 + gfx/skia/skia/src/utils/win/SkTScopedComPtr.h | 86 + gfx/skia/skia/src/utils/win/SkWGL.h | 165 + gfx/skia/skia/src/utils/win/SkWGL_win.cpp | 508 ++ gfx/skia/skia/src/xml/SkDOM.cpp | 429 ++ gfx/skia/skia/src/xml/SkDOM.h | 95 + gfx/skia/skia/src/xml/SkXMLParser.cpp | 216 + gfx/skia/skia/src/xml/SkXMLParser.h | 87 + gfx/skia/skia/src/xml/SkXMLWriter.cpp | 303 + gfx/skia/skia/src/xml/SkXMLWriter.h | 102 + gfx/skia/skia/src/xps/SkXPSDevice.cpp | 2018 ++++++ gfx/skia/skia/src/xps/SkXPSDevice.h | 282 + gfx/skia/skia/src/xps/SkXPSDocument.cpp | 86 + gfx/skia/skia/third_party/skcms/LICENSE | 29 + gfx/skia/skia/third_party/skcms/README.chromium | 5 + gfx/skia/skia/third_party/skcms/skcms.cc | 2570 +++++++ gfx/skia/skia/third_party/skcms/skcms.gni | 8 + gfx/skia/skia/third_party/skcms/skcms_internal.h | 47 + .../skia/third_party/skcms/src/Transform_inl.h | 1545 ++++ gfx/skia/skia/third_party/skcms/version.sha1 | 1 + 2113 files changed, 504908 insertions(+) create mode 100644 gfx/skia/LICENSE create mode 100644 gfx/skia/README create mode 100644 gfx/skia/README_COMMITTING create mode 100644 gfx/skia/README_MOZILLA create mode 100755 gfx/skia/generate_mozbuild.py create mode 100644 gfx/skia/moz.build create mode 100644 gfx/skia/patches/README create mode 100644 gfx/skia/patches/archive/0001-Bug-687189-Implement-SkPaint-getPosTextPath.patch create mode 100644 gfx/skia/patches/archive/0001-Bug-777614-Re-add-our-SkUserConfig.h-r-nrc.patch create mode 100644 gfx/skia/patches/archive/0001-Bug-803063-Skia-cross-compilation-for-Windows-fails-.patch create mode 100644 gfx/skia/patches/archive/0001-Bug-895086-Remove-unused-find_from_uniqueID-function.patch create mode 100644 gfx/skia/patches/archive/0002-Bug-688366-Dont-invalidate-all-radial-gradients.patch create mode 100644 gfx/skia/patches/archive/0002-Bug-848491-Re-apply-Bug-795549-Move-TileProc-functio.patch create mode 100644 gfx/skia/patches/archive/0003-SkUserConfig-for-Mozilla.patch create mode 100644 gfx/skia/patches/archive/0004-Bug-722011-Fix-trailing-commas-in-enums.patch create mode 100644 gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch create mode 100644 gfx/skia/patches/archive/0005-Bug-731384-Fix-clang-SK_OVERRIDE.patch create mode 100644 gfx/skia/patches/archive/0005-Bug-736276-Add-a-new-SkFontHost-that-takes-a-cairo_s.patch create mode 100644 gfx/skia/patches/archive/0005-Bug-777614-Re-apply-bug-687188-Expand-the-gradient-c.patch create mode 100644 gfx/skia/patches/archive/0006-Bug-751814-ARM-EDSP-ARMv6-Skia-fixes.patch create mode 100644 gfx/skia/patches/archive/0006-Bug-848491-Re-apply-Bug-777614-Add-our-SkUserConfig..patch create mode 100644 gfx/skia/patches/archive/0007-Bug-719872-Old-Android-FontHost.patch create mode 100644 gfx/skia/patches/archive/0007-Bug-848491-Re-apply-bug-687188-Expand-the-gradient-c.patch create mode 100644 gfx/skia/patches/archive/0008-Bug-687188-Skia-radial-gradients.patch create mode 100644 gfx/skia/patches/archive/0008-Bug-848491-Re-apply-759683-Handle-compilers-that-don.patch create mode 100644 gfx/skia/patches/archive/0009-Bug-755869-FreeBSD-Hurd.patch create mode 100644 gfx/skia/patches/archive/0009-Bug-777614-Re-apply-759683-Handle-compilers-that-don.patch create mode 100644 gfx/skia/patches/archive/0009-Bug-848491-Re-apply-bug-751418-Add-our-own-GrUserCon.patch create mode 100644 gfx/skia/patches/archive/0010-Bug-689069-ARM-Opts.patch create mode 100644 gfx/skia/patches/archive/0010-Bug-836892-Add-new-blending-modes-to-SkXfermode.patch create mode 100644 gfx/skia/patches/archive/0010-Bug-848491-Re-apply-bug-817356-Patch-Skia-to-recogni.patch create mode 100644 gfx/skia/patches/archive/0011-Bug-719575-Fix-clang-build.patch create mode 100644 gfx/skia/patches/archive/0011-Bug-839347-no-anon-namespace-around-SkNO_RETURN_HINT.patch create mode 100644 gfx/skia/patches/archive/0012-Bug-751418-Add-our-own-GrUserConfig-r-mattwoodrow.patch create mode 100644 gfx/skia/patches/archive/0012-Bug-759683-make-ssse3-conditional.patch create mode 100644 gfx/skia/patches/archive/0013-Bug-751418-Fix-compile-error-on-gcc-in-Skia-GL-r-mat.patch create mode 100644 gfx/skia/patches/archive/0013-Bug-761890-fonts.patch create mode 100644 gfx/skia/patches/archive/0014-Bug-765038-Fix-clang-build.patch create mode 100644 gfx/skia/patches/archive/0015-Bug-766017-warnings.patch create mode 100644 gfx/skia/patches/archive/0016-Bug-718849-Radial-gradients.patch create mode 100644 gfx/skia/patches/archive/0017-Bug-740194-SkMemory-mozalloc.patch create mode 100644 gfx/skia/patches/archive/0018-Bug-817356-PPC-defines.patch create mode 100644 gfx/skia/patches/archive/0022-Bug-848491-Re-apply-bug-795538-Ensure-we-use-the-cor.patch create mode 100644 gfx/skia/patches/archive/0023-Bug-890539-Fix-SK_COMPILE_ASSERT-build-warning.patch create mode 100644 gfx/skia/patches/archive/0024-Bug-887318-fix-bgra-readback.patch create mode 100644 gfx/skia/patches/archive/0025-Bug-896049-Add-default-Value-SK_OVERRIDE.patch create mode 100644 gfx/skia/patches/archive/0026-Bug-901208-Fix-ARM-v4t.patch create mode 100644 gfx/skia/patches/archive/0030-Bug-939629-Add-missing-include-guards.patch create mode 100644 gfx/skia/patches/archive/0031-Bug-945588-Add-include-guard.patch create mode 100644 gfx/skia/patches/archive/0032-Bug-974900-More-missing-include-guards.patch create mode 100644 gfx/skia/patches/archive/0033-Bug-974900-undef-interface-windows.patch create mode 100644 gfx/skia/patches/archive/SkPostConfig.patch create mode 100644 gfx/skia/patches/archive/arm-fixes.patch create mode 100644 gfx/skia/patches/archive/arm-opts.patch create mode 100644 gfx/skia/patches/archive/fix-comma-end-enum-list.patch create mode 100644 gfx/skia/patches/archive/fix-gradient-clamp.patch create mode 100644 gfx/skia/patches/archive/getpostextpath.patch create mode 100644 gfx/skia/patches/archive/mingw-fix.patch create mode 100644 gfx/skia/patches/archive/new-aa.patch create mode 100644 gfx/skia/patches/archive/old-android-fonthost.patch create mode 100644 gfx/skia/patches/archive/radial-gradients.patch create mode 100644 gfx/skia/patches/archive/skia_restrict_problem.patch create mode 100644 gfx/skia/patches/archive/uninitialized-margin.patch create mode 100644 gfx/skia/patches/archive/user-config.patch create mode 100644 gfx/skia/skia/include/android/SkAndroidFrameworkUtils.h create mode 100644 gfx/skia/skia/include/android/SkAnimatedImage.h create mode 100644 gfx/skia/skia/include/android/SkBRDAllocator.h create mode 100644 gfx/skia/skia/include/android/SkBitmapRegionDecoder.h create mode 100644 gfx/skia/skia/include/atlastext/SkAtlasTextContext.h create mode 100644 gfx/skia/skia/include/atlastext/SkAtlasTextFont.h create mode 100644 gfx/skia/skia/include/atlastext/SkAtlasTextRenderer.h create mode 100644 gfx/skia/skia/include/atlastext/SkAtlasTextTarget.h create mode 100644 gfx/skia/skia/include/c/sk_canvas.h create mode 100644 gfx/skia/skia/include/c/sk_colorspace.h create mode 100644 gfx/skia/skia/include/c/sk_data.h create mode 100644 gfx/skia/skia/include/c/sk_image.h create mode 100644 gfx/skia/skia/include/c/sk_imageinfo.h create mode 100644 gfx/skia/skia/include/c/sk_maskfilter.h create mode 100644 gfx/skia/skia/include/c/sk_matrix.h create mode 100644 gfx/skia/skia/include/c/sk_paint.h create mode 100644 gfx/skia/skia/include/c/sk_path.h create mode 100644 gfx/skia/skia/include/c/sk_picture.h create mode 100644 gfx/skia/skia/include/c/sk_shader.h create mode 100644 gfx/skia/skia/include/c/sk_surface.h create mode 100644 gfx/skia/skia/include/c/sk_types.h create mode 100644 gfx/skia/skia/include/codec/SkAndroidCodec.h create mode 100644 gfx/skia/skia/include/codec/SkCodec.h create mode 100644 gfx/skia/skia/include/codec/SkCodecAnimation.h create mode 100644 gfx/skia/skia/include/codec/SkEncodedOrigin.h create mode 100644 gfx/skia/skia/include/config/SkUserConfig.h create mode 100644 gfx/skia/skia/include/core/SkAnnotation.h create mode 100644 gfx/skia/skia/include/core/SkBBHFactory.h create mode 100644 gfx/skia/skia/include/core/SkBitmap.h create mode 100644 gfx/skia/skia/include/core/SkBlendMode.h create mode 100644 gfx/skia/skia/include/core/SkBlurTypes.h create mode 100644 gfx/skia/skia/include/core/SkCanvas.h create mode 100644 gfx/skia/skia/include/core/SkCanvasVirtualEnforcer.h create mode 100644 gfx/skia/skia/include/core/SkClipOp.h create mode 100644 gfx/skia/skia/include/core/SkColor.h create mode 100644 gfx/skia/skia/include/core/SkColorFilter.h create mode 100644 gfx/skia/skia/include/core/SkColorPriv.h create mode 100644 gfx/skia/skia/include/core/SkColorSpace.h create mode 100644 gfx/skia/skia/include/core/SkContourMeasure.h create mode 100644 gfx/skia/skia/include/core/SkCoverageMode.h create mode 100644 gfx/skia/skia/include/core/SkCubicMap.h create mode 100644 gfx/skia/skia/include/core/SkData.h create mode 100644 gfx/skia/skia/include/core/SkDataTable.h create mode 100644 gfx/skia/skia/include/core/SkDeferredDisplayListRecorder.h create mode 100644 gfx/skia/skia/include/core/SkDeque.h create mode 100644 gfx/skia/skia/include/core/SkDocument.h create mode 100644 gfx/skia/skia/include/core/SkDrawLooper.h create mode 100644 gfx/skia/skia/include/core/SkDrawable.h create mode 100644 gfx/skia/skia/include/core/SkEncodedImageFormat.h create mode 100644 gfx/skia/skia/include/core/SkExecutor.h create mode 100644 gfx/skia/skia/include/core/SkFilterQuality.h create mode 100644 gfx/skia/skia/include/core/SkFlattenable.h create mode 100644 gfx/skia/skia/include/core/SkFont.h create mode 100644 gfx/skia/skia/include/core/SkFontArguments.h create mode 100644 gfx/skia/skia/include/core/SkFontLCDConfig.h create mode 100644 gfx/skia/skia/include/core/SkFontMetrics.h create mode 100644 gfx/skia/skia/include/core/SkFontMgr.h create mode 100644 gfx/skia/skia/include/core/SkFontParameters.h create mode 100644 gfx/skia/skia/include/core/SkFontStyle.h create mode 100644 gfx/skia/skia/include/core/SkFontTypes.h create mode 100644 gfx/skia/skia/include/core/SkGraphics.h create mode 100644 gfx/skia/skia/include/core/SkICC.h create mode 100644 gfx/skia/skia/include/core/SkImage.h create mode 100644 gfx/skia/skia/include/core/SkImageEncoder.h create mode 100644 gfx/skia/skia/include/core/SkImageFilter.h create mode 100644 gfx/skia/skia/include/core/SkImageGenerator.h create mode 100644 gfx/skia/skia/include/core/SkImageInfo.h create mode 100644 gfx/skia/skia/include/core/SkMallocPixelRef.h create mode 100644 gfx/skia/skia/include/core/SkMaskFilter.h create mode 100644 gfx/skia/skia/include/core/SkMath.h create mode 100644 gfx/skia/skia/include/core/SkMatrix.h create mode 100644 gfx/skia/skia/include/core/SkMatrix44.h create mode 100644 gfx/skia/skia/include/core/SkMilestone.h create mode 100644 gfx/skia/skia/include/core/SkMultiPictureDraw.h create mode 100644 gfx/skia/skia/include/core/SkOverdrawCanvas.h create mode 100644 gfx/skia/skia/include/core/SkPaint.h create mode 100644 gfx/skia/skia/include/core/SkPath.h create mode 100644 gfx/skia/skia/include/core/SkPathEffect.h create mode 100644 gfx/skia/skia/include/core/SkPathMeasure.h create mode 100644 gfx/skia/skia/include/core/SkPathTypes.h create mode 100644 gfx/skia/skia/include/core/SkPicture.h create mode 100644 gfx/skia/skia/include/core/SkPictureRecorder.h create mode 100644 gfx/skia/skia/include/core/SkPixelRef.h create mode 100644 gfx/skia/skia/include/core/SkPixmap.h create mode 100644 gfx/skia/skia/include/core/SkPngChunkReader.h create mode 100644 gfx/skia/skia/include/core/SkPoint.h create mode 100644 gfx/skia/skia/include/core/SkPoint3.h create mode 100644 gfx/skia/skia/include/core/SkPostConfig.h create mode 100644 gfx/skia/skia/include/core/SkPreConfig.h create mode 100644 gfx/skia/skia/include/core/SkPromiseImageTexture.h create mode 100644 gfx/skia/skia/include/core/SkRRect.h create mode 100644 gfx/skia/skia/include/core/SkRSXform.h create mode 100644 gfx/skia/skia/include/core/SkRWBuffer.h create mode 100644 gfx/skia/skia/include/core/SkRasterHandleAllocator.h create mode 100644 gfx/skia/skia/include/core/SkRect.h create mode 100644 gfx/skia/skia/include/core/SkRefCnt.h create mode 100644 gfx/skia/skia/include/core/SkRegion.h create mode 100644 gfx/skia/skia/include/core/SkScalar.h create mode 100644 gfx/skia/skia/include/core/SkSerialProcs.h create mode 100644 gfx/skia/skia/include/core/SkShader.h create mode 100644 gfx/skia/skia/include/core/SkSize.h create mode 100644 gfx/skia/skia/include/core/SkStream.h create mode 100644 gfx/skia/skia/include/core/SkString.h create mode 100644 gfx/skia/skia/include/core/SkStrokeRec.h create mode 100644 gfx/skia/skia/include/core/SkSurface.h create mode 100644 gfx/skia/skia/include/core/SkSurfaceCharacterization.h create mode 100644 gfx/skia/skia/include/core/SkSurfaceProps.h create mode 100644 gfx/skia/skia/include/core/SkSwizzle.h create mode 100644 gfx/skia/skia/include/core/SkTextBlob.h create mode 100644 gfx/skia/skia/include/core/SkTileMode.h create mode 100644 gfx/skia/skia/include/core/SkTime.h create mode 100644 gfx/skia/skia/include/core/SkTraceMemoryDump.h create mode 100644 gfx/skia/skia/include/core/SkTypeface.h create mode 100644 gfx/skia/skia/include/core/SkTypes.h create mode 100644 gfx/skia/skia/include/core/SkUnPreMultiply.h create mode 100644 gfx/skia/skia/include/core/SkVertices.h create mode 100644 gfx/skia/skia/include/core/SkYUVAIndex.h create mode 100644 gfx/skia/skia/include/core/SkYUVASizeInfo.h create mode 100644 gfx/skia/skia/include/docs/SkPDFDocument.h create mode 100644 gfx/skia/skia/include/docs/SkXPSDocument.h create mode 100644 gfx/skia/skia/include/effects/Sk1DPathEffect.h create mode 100644 gfx/skia/skia/include/effects/Sk2DPathEffect.h create mode 100644 gfx/skia/skia/include/effects/SkAlphaThresholdFilter.h create mode 100644 gfx/skia/skia/include/effects/SkArithmeticImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkBlurDrawLooper.h create mode 100644 gfx/skia/skia/include/effects/SkBlurImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkBlurMaskFilter.h create mode 100644 gfx/skia/skia/include/effects/SkColorFilterImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkColorMatrix.h create mode 100644 gfx/skia/skia/include/effects/SkColorMatrixFilter.h create mode 100644 gfx/skia/skia/include/effects/SkComposeImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkCornerPathEffect.h create mode 100644 gfx/skia/skia/include/effects/SkDashPathEffect.h create mode 100644 gfx/skia/skia/include/effects/SkDiscretePathEffect.h create mode 100644 gfx/skia/skia/include/effects/SkDisplacementMapEffect.h create mode 100644 gfx/skia/skia/include/effects/SkDropShadowImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkGradientShader.h create mode 100644 gfx/skia/skia/include/effects/SkHighContrastFilter.h create mode 100644 gfx/skia/skia/include/effects/SkImageFilters.h create mode 100644 gfx/skia/skia/include/effects/SkImageSource.h create mode 100644 gfx/skia/skia/include/effects/SkLayerDrawLooper.h create mode 100644 gfx/skia/skia/include/effects/SkLightingImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkLumaColorFilter.h create mode 100644 gfx/skia/skia/include/effects/SkMagnifierImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkMatrixConvolutionImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkMergeImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkMorphologyImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkOffsetImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkOpPathEffect.h create mode 100644 gfx/skia/skia/include/effects/SkOverdrawColorFilter.h create mode 100644 gfx/skia/skia/include/effects/SkPaintImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkPerlinNoiseShader.h create mode 100644 gfx/skia/skia/include/effects/SkPictureImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkShaderMaskFilter.h create mode 100644 gfx/skia/skia/include/effects/SkTableColorFilter.h create mode 100644 gfx/skia/skia/include/effects/SkTableMaskFilter.h create mode 100644 gfx/skia/skia/include/effects/SkTileImageFilter.h create mode 100644 gfx/skia/skia/include/effects/SkTrimPathEffect.h create mode 100644 gfx/skia/skia/include/effects/SkXfermodeImageFilter.h create mode 100644 gfx/skia/skia/include/encode/SkEncoder.h create mode 100644 gfx/skia/skia/include/encode/SkJpegEncoder.h create mode 100644 gfx/skia/skia/include/encode/SkPngEncoder.h create mode 100644 gfx/skia/skia/include/encode/SkWebpEncoder.h create mode 100644 gfx/skia/skia/include/gpu/GrBackendDrawableInfo.h create mode 100644 gfx/skia/skia/include/gpu/GrBackendSemaphore.h create mode 100644 gfx/skia/skia/include/gpu/GrBackendSurface.h create mode 100644 gfx/skia/skia/include/gpu/GrConfig.h create mode 100644 gfx/skia/skia/include/gpu/GrContext.h create mode 100644 gfx/skia/skia/include/gpu/GrContextOptions.h create mode 100644 gfx/skia/skia/include/gpu/GrContextThreadSafeProxy.h create mode 100644 gfx/skia/skia/include/gpu/GrDriverBugWorkarounds.h create mode 100644 gfx/skia/skia/include/gpu/GrDriverBugWorkaroundsAutogen.h create mode 100644 gfx/skia/skia/include/gpu/GrGpuResource.h create mode 100644 gfx/skia/skia/include/gpu/GrSurface.h create mode 100644 gfx/skia/skia/include/gpu/GrTexture.h create mode 100644 gfx/skia/skia/include/gpu/GrTypes.h create mode 100644 gfx/skia/skia/include/gpu/dawn/GrDawnTypes.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLAssembleHelpers.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLAssembleInterface.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLConfig.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLConfig_chrome.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLExtensions.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLFunctions.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLInterface.h create mode 100644 gfx/skia/skia/include/gpu/gl/GrGLTypes.h create mode 100644 gfx/skia/skia/include/gpu/mock/GrMockTypes.h create mode 100644 gfx/skia/skia/include/gpu/mtl/GrMtlTypes.h create mode 100644 gfx/skia/skia/include/gpu/vk/GrVkBackendContext.h create mode 100644 gfx/skia/skia/include/gpu/vk/GrVkExtensions.h create mode 100644 gfx/skia/skia/include/gpu/vk/GrVkMemoryAllocator.h create mode 100644 gfx/skia/skia/include/gpu/vk/GrVkTypes.h create mode 100644 gfx/skia/skia/include/gpu/vk/GrVkVulkan.h create mode 100644 gfx/skia/skia/include/pathops/SkPathOps.h create mode 100644 gfx/skia/skia/include/ports/SkCFObject.h create mode 100644 gfx/skia/skia/include/ports/SkFontConfigInterface.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_FontConfigInterface.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_android.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_directory.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_empty.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_fontconfig.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_fuchsia.h create mode 100644 gfx/skia/skia/include/ports/SkFontMgr_indirect.h create mode 100644 gfx/skia/skia/include/ports/SkImageGeneratorCG.h create mode 100644 gfx/skia/skia/include/ports/SkImageGeneratorWIC.h create mode 100644 gfx/skia/skia/include/ports/SkRemotableFontMgr.h create mode 100644 gfx/skia/skia/include/ports/SkTypeface_cairo.h create mode 100644 gfx/skia/skia/include/ports/SkTypeface_mac.h create mode 100644 gfx/skia/skia/include/ports/SkTypeface_win.h create mode 100644 gfx/skia/skia/include/private/GrContext_Base.h create mode 100644 gfx/skia/skia/include/private/GrGLTypesPriv.h create mode 100644 gfx/skia/skia/include/private/GrImageContext.h create mode 100644 gfx/skia/skia/include/private/GrRecordingContext.h create mode 100644 gfx/skia/skia/include/private/GrResourceKey.h create mode 100644 gfx/skia/skia/include/private/GrSharedEnums.h create mode 100644 gfx/skia/skia/include/private/GrSingleOwner.h create mode 100644 gfx/skia/skia/include/private/GrTypesPriv.h create mode 100644 gfx/skia/skia/include/private/GrVkTypesPriv.h create mode 100644 gfx/skia/skia/include/private/SkBitmaskEnum.h create mode 100644 gfx/skia/skia/include/private/SkChecksum.h create mode 100644 gfx/skia/skia/include/private/SkColorData.h create mode 100644 gfx/skia/skia/include/private/SkDeferredDisplayList.h create mode 100644 gfx/skia/skia/include/private/SkEncodedInfo.h create mode 100644 gfx/skia/skia/include/private/SkFixed.h create mode 100644 gfx/skia/skia/include/private/SkFloatBits.h create mode 100644 gfx/skia/skia/include/private/SkFloatingPoint.h create mode 100644 gfx/skia/skia/include/private/SkHalf.h create mode 100644 gfx/skia/skia/include/private/SkImageInfoPriv.h create mode 100644 gfx/skia/skia/include/private/SkMacros.h create mode 100644 gfx/skia/skia/include/private/SkMalloc.h create mode 100644 gfx/skia/skia/include/private/SkMutex.h create mode 100644 gfx/skia/skia/include/private/SkNoncopyable.h create mode 100644 gfx/skia/skia/include/private/SkNx.h create mode 100644 gfx/skia/skia/include/private/SkNx_neon.h create mode 100644 gfx/skia/skia/include/private/SkNx_sse.h create mode 100644 gfx/skia/skia/include/private/SkOnce.h create mode 100644 gfx/skia/skia/include/private/SkPathRef.h create mode 100644 gfx/skia/skia/include/private/SkSafe32.h create mode 100644 gfx/skia/skia/include/private/SkSafe_math.h create mode 100644 gfx/skia/skia/include/private/SkSemaphore.h create mode 100644 gfx/skia/skia/include/private/SkShadowFlags.h create mode 100644 gfx/skia/skia/include/private/SkSpinlock.h create mode 100644 gfx/skia/skia/include/private/SkTArray.h create mode 100644 gfx/skia/skia/include/private/SkTDArray.h create mode 100644 gfx/skia/skia/include/private/SkTFitsIn.h create mode 100644 gfx/skia/skia/include/private/SkTHash.h create mode 100644 gfx/skia/skia/include/private/SkTLogic.h create mode 100644 gfx/skia/skia/include/private/SkTemplates.h create mode 100644 gfx/skia/skia/include/private/SkThreadAnnotations.h create mode 100644 gfx/skia/skia/include/private/SkThreadID.h create mode 100644 gfx/skia/skia/include/private/SkTo.h create mode 100644 gfx/skia/skia/include/private/SkVx.h create mode 100644 gfx/skia/skia/include/private/SkWeakRefCnt.h create mode 100644 gfx/skia/skia/include/svg/SkSVGCanvas.h create mode 100644 gfx/skia/skia/include/third_party/skcms/LICENSE create mode 100644 gfx/skia/skia/include/third_party/skcms/skcms.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/LICENSE create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vk_platform.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_android.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_core.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_ios.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_macos.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_win32.h create mode 100644 gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_xcb.h create mode 100644 gfx/skia/skia/include/utils/Sk3D.h create mode 100644 gfx/skia/skia/include/utils/SkAnimCodecPlayer.h create mode 100644 gfx/skia/skia/include/utils/SkBase64.h create mode 100644 gfx/skia/skia/include/utils/SkCamera.h create mode 100644 gfx/skia/skia/include/utils/SkCanvasStateUtils.h create mode 100644 gfx/skia/skia/include/utils/SkEventTracer.h create mode 100644 gfx/skia/skia/include/utils/SkFrontBufferedStream.h create mode 100644 gfx/skia/skia/include/utils/SkInterpolator.h create mode 100644 gfx/skia/skia/include/utils/SkLua.h create mode 100644 gfx/skia/skia/include/utils/SkLuaCanvas.h create mode 100644 gfx/skia/skia/include/utils/SkNWayCanvas.h create mode 100644 gfx/skia/skia/include/utils/SkNoDrawCanvas.h create mode 100644 gfx/skia/skia/include/utils/SkNullCanvas.h create mode 100644 gfx/skia/skia/include/utils/SkPaintFilterCanvas.h create mode 100644 gfx/skia/skia/include/utils/SkParse.h create mode 100644 gfx/skia/skia/include/utils/SkParsePath.h create mode 100644 gfx/skia/skia/include/utils/SkRandom.h create mode 100644 gfx/skia/skia/include/utils/SkShadowUtils.h create mode 100644 gfx/skia/skia/include/utils/SkTextUtils.h create mode 100644 gfx/skia/skia/include/utils/SkTraceEventPhase.h create mode 100644 gfx/skia/skia/include/utils/mac/SkCGUtils.h create mode 100644 gfx/skia/skia/src/android/SkAndroidFrameworkUtils.cpp create mode 100644 gfx/skia/skia/src/android/SkAnimatedImage.cpp create mode 100644 gfx/skia/skia/src/android/SkBitmapRegionCodec.cpp create mode 100644 gfx/skia/skia/src/android/SkBitmapRegionCodec.h create mode 100644 gfx/skia/skia/src/android/SkBitmapRegionDecoder.cpp create mode 100644 gfx/skia/skia/src/android/SkBitmapRegionDecoderPriv.h create mode 100644 gfx/skia/skia/src/atlastext/SkAtlasTextContext.cpp create mode 100644 gfx/skia/skia/src/atlastext/SkAtlasTextTarget.cpp create mode 100644 gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.cpp create mode 100644 gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.h create mode 100644 gfx/skia/skia/src/c/sk_c_from_to.h create mode 100644 gfx/skia/skia/src/c/sk_effects.cpp create mode 100644 gfx/skia/skia/src/c/sk_imageinfo.cpp create mode 100644 gfx/skia/skia/src/c/sk_paint.cpp create mode 100644 gfx/skia/skia/src/c/sk_surface.cpp create mode 100644 gfx/skia/skia/src/c/sk_types_priv.h create mode 100644 gfx/skia/skia/src/codec/SkAndroidCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp create mode 100644 gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h create mode 100644 gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkBmpBaseCodec.h create mode 100644 gfx/skia/skia/src/codec/SkBmpCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkBmpCodec.h create mode 100644 gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkBmpMaskCodec.h create mode 100644 gfx/skia/skia/src/codec/SkBmpRLECodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkBmpRLECodec.h create mode 100644 gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkBmpStandardCodec.h create mode 100644 gfx/skia/skia/src/codec/SkCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkCodecAnimationPriv.h create mode 100644 gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp create mode 100644 gfx/skia/skia/src/codec/SkCodecImageGenerator.h create mode 100644 gfx/skia/skia/src/codec/SkCodecPriv.h create mode 100644 gfx/skia/skia/src/codec/SkColorTable.cpp create mode 100644 gfx/skia/skia/src/codec/SkColorTable.h create mode 100644 gfx/skia/skia/src/codec/SkEncodedInfo.cpp create mode 100644 gfx/skia/skia/src/codec/SkFrameHolder.h create mode 100644 gfx/skia/skia/src/codec/SkGifCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkGifCodec.h create mode 100644 gfx/skia/skia/src/codec/SkHeifCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkHeifCodec.h create mode 100644 gfx/skia/skia/src/codec/SkIcoCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkIcoCodec.h create mode 100644 gfx/skia/skia/src/codec/SkJpegCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkJpegCodec.h create mode 100644 gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp create mode 100644 gfx/skia/skia/src/codec/SkJpegDecoderMgr.h create mode 100644 gfx/skia/skia/src/codec/SkJpegPriv.h create mode 100644 gfx/skia/skia/src/codec/SkJpegUtility.cpp create mode 100644 gfx/skia/skia/src/codec/SkJpegUtility.h create mode 100644 gfx/skia/skia/src/codec/SkMaskSwizzler.cpp create mode 100644 gfx/skia/skia/src/codec/SkMaskSwizzler.h create mode 100644 gfx/skia/skia/src/codec/SkMasks.cpp create mode 100644 gfx/skia/skia/src/codec/SkMasks.h create mode 100644 gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp create mode 100644 gfx/skia/skia/src/codec/SkParseEncodedOrigin.h create mode 100644 gfx/skia/skia/src/codec/SkPngCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkPngCodec.h create mode 100644 gfx/skia/skia/src/codec/SkPngPriv.h create mode 100644 gfx/skia/skia/src/codec/SkRawCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkRawCodec.h create mode 100644 gfx/skia/skia/src/codec/SkSampledCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkSampledCodec.h create mode 100644 gfx/skia/skia/src/codec/SkSampler.cpp create mode 100644 gfx/skia/skia/src/codec/SkSampler.h create mode 100644 gfx/skia/skia/src/codec/SkScalingCodec.h create mode 100644 gfx/skia/skia/src/codec/SkStreamBuffer.cpp create mode 100644 gfx/skia/skia/src/codec/SkStreamBuffer.h create mode 100644 gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h create mode 100644 gfx/skia/skia/src/codec/SkSwizzler.cpp create mode 100644 gfx/skia/skia/src/codec/SkSwizzler.h create mode 100644 gfx/skia/skia/src/codec/SkWbmpCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkWbmpCodec.h create mode 100644 gfx/skia/skia/src/codec/SkWebpCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkWebpCodec.h create mode 100644 gfx/skia/skia/src/codec/SkWuffsCodec.cpp create mode 100644 gfx/skia/skia/src/codec/SkWuffsCodec.h create mode 100644 gfx/skia/skia/src/core/Sk4px.h create mode 100644 gfx/skia/skia/src/core/SkAAClip.cpp create mode 100644 gfx/skia/skia/src/core/SkAAClip.h create mode 100644 gfx/skia/skia/src/core/SkATrace.cpp create mode 100644 gfx/skia/skia/src/core/SkATrace.h create mode 100644 gfx/skia/skia/src/core/SkAdvancedTypefaceMetrics.h create mode 100644 gfx/skia/skia/src/core/SkAlphaRuns.cpp create mode 100644 gfx/skia/skia/src/core/SkAnalyticEdge.cpp create mode 100644 gfx/skia/skia/src/core/SkAnalyticEdge.h create mode 100644 gfx/skia/skia/src/core/SkAnnotation.cpp create mode 100644 gfx/skia/skia/src/core/SkAnnotationKeys.h create mode 100644 gfx/skia/skia/src/core/SkAntiRun.h create mode 100644 gfx/skia/skia/src/core/SkArenaAlloc.cpp create mode 100644 gfx/skia/skia/src/core/SkArenaAlloc.h create mode 100644 gfx/skia/skia/src/core/SkArenaAllocList.h create mode 100644 gfx/skia/skia/src/core/SkAutoBlitterChoose.h create mode 100644 gfx/skia/skia/src/core/SkAutoMalloc.h create mode 100644 gfx/skia/skia/src/core/SkAutoPixmapStorage.cpp create mode 100644 gfx/skia/skia/src/core/SkAutoPixmapStorage.h create mode 100644 gfx/skia/skia/src/core/SkBBHFactory.cpp create mode 100644 gfx/skia/skia/src/core/SkBBoxHierarchy.h create mode 100644 gfx/skia/skia/src/core/SkBigPicture.cpp create mode 100644 gfx/skia/skia/src/core/SkBigPicture.h create mode 100644 gfx/skia/skia/src/core/SkBitmap.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapCache.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapCache.h create mode 100644 gfx/skia/skia/src/core/SkBitmapController.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapController.h create mode 100644 gfx/skia/skia/src/core/SkBitmapDevice.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapDevice.h create mode 100644 gfx/skia/skia/src/core/SkBitmapFilter.h create mode 100644 gfx/skia/skia/src/core/SkBitmapProcState.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapProcState.h create mode 100644 gfx/skia/skia/src/core/SkBitmapProcState_matrixProcs.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapScaler.cpp create mode 100644 gfx/skia/skia/src/core/SkBitmapScaler.h create mode 100644 gfx/skia/skia/src/core/SkBlendMode.cpp create mode 100644 gfx/skia/skia/src/core/SkBlendModePriv.h create mode 100644 gfx/skia/skia/src/core/SkBlitBWMaskTemplate.h create mode 100644 gfx/skia/skia/src/core/SkBlitRow.h create mode 100644 gfx/skia/skia/src/core/SkBlitRow_D32.cpp create mode 100644 gfx/skia/skia/src/core/SkBlitter.cpp create mode 100644 gfx/skia/skia/src/core/SkBlitter.h create mode 100644 gfx/skia/skia/src/core/SkBlitter_A8.cpp create mode 100644 gfx/skia/skia/src/core/SkBlitter_ARGB32.cpp create mode 100644 gfx/skia/skia/src/core/SkBlitter_RGB565.cpp create mode 100644 gfx/skia/skia/src/core/SkBlitter_Sprite.cpp create mode 100644 gfx/skia/skia/src/core/SkBlurMF.cpp create mode 100644 gfx/skia/skia/src/core/SkBlurMask.cpp create mode 100644 gfx/skia/skia/src/core/SkBlurMask.h create mode 100644 gfx/skia/skia/src/core/SkBlurPriv.h create mode 100644 gfx/skia/skia/src/core/SkBuffer.cpp create mode 100644 gfx/skia/skia/src/core/SkBuffer.h create mode 100644 gfx/skia/skia/src/core/SkCachedData.cpp create mode 100644 gfx/skia/skia/src/core/SkCachedData.h create mode 100644 gfx/skia/skia/src/core/SkCanvas.cpp create mode 100644 gfx/skia/skia/src/core/SkCanvasPriv.cpp create mode 100644 gfx/skia/skia/src/core/SkCanvasPriv.h create mode 100644 gfx/skia/skia/src/core/SkClipOpPriv.h create mode 100644 gfx/skia/skia/src/core/SkClipStack.cpp create mode 100644 gfx/skia/skia/src/core/SkClipStack.h create mode 100644 gfx/skia/skia/src/core/SkClipStackDevice.cpp create mode 100644 gfx/skia/skia/src/core/SkClipStackDevice.h create mode 100644 gfx/skia/skia/src/core/SkColor.cpp create mode 100644 gfx/skia/skia/src/core/SkColorFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkColorFilterPriv.h create mode 100644 gfx/skia/skia/src/core/SkColorFilter_Matrix.cpp create mode 100644 gfx/skia/skia/src/core/SkColorFilter_Matrix.h create mode 100644 gfx/skia/skia/src/core/SkColorSpace.cpp create mode 100644 gfx/skia/skia/src/core/SkColorSpacePriv.h create mode 100644 gfx/skia/skia/src/core/SkColorSpaceXformSteps.cpp create mode 100644 gfx/skia/skia/src/core/SkColorSpaceXformSteps.h create mode 100644 gfx/skia/skia/src/core/SkContourMeasure.cpp create mode 100644 gfx/skia/skia/src/core/SkConvertPixels.cpp create mode 100644 gfx/skia/skia/src/core/SkConvertPixels.h create mode 100644 gfx/skia/skia/src/core/SkConvolver.cpp create mode 100644 gfx/skia/skia/src/core/SkConvolver.h create mode 100644 gfx/skia/skia/src/core/SkCoreBlitters.h create mode 100644 gfx/skia/skia/src/core/SkCoverageModePriv.h create mode 100644 gfx/skia/skia/src/core/SkCpu.cpp create mode 100644 gfx/skia/skia/src/core/SkCpu.h create mode 100644 gfx/skia/skia/src/core/SkCubicClipper.cpp create mode 100644 gfx/skia/skia/src/core/SkCubicClipper.h create mode 100644 gfx/skia/skia/src/core/SkCubicMap.cpp create mode 100644 gfx/skia/skia/src/core/SkCubicSolver.h create mode 100644 gfx/skia/skia/src/core/SkData.cpp create mode 100644 gfx/skia/skia/src/core/SkDataTable.cpp create mode 100644 gfx/skia/skia/src/core/SkDebug.cpp create mode 100644 gfx/skia/skia/src/core/SkDeferredDisplayList.cpp create mode 100644 gfx/skia/skia/src/core/SkDeferredDisplayListPriv.h create mode 100644 gfx/skia/skia/src/core/SkDeferredDisplayListRecorder.cpp create mode 100644 gfx/skia/skia/src/core/SkDeque.cpp create mode 100644 gfx/skia/skia/src/core/SkDescriptor.cpp create mode 100644 gfx/skia/skia/src/core/SkDescriptor.h create mode 100644 gfx/skia/skia/src/core/SkDevice.cpp create mode 100644 gfx/skia/skia/src/core/SkDevice.h create mode 100644 gfx/skia/skia/src/core/SkDiscardableMemory.h create mode 100644 gfx/skia/skia/src/core/SkDistanceFieldGen.cpp create mode 100644 gfx/skia/skia/src/core/SkDistanceFieldGen.h create mode 100644 gfx/skia/skia/src/core/SkDocument.cpp create mode 100644 gfx/skia/skia/src/core/SkDraw.cpp create mode 100644 gfx/skia/skia/src/core/SkDraw.h create mode 100644 gfx/skia/skia/src/core/SkDrawLooper.cpp create mode 100644 gfx/skia/skia/src/core/SkDrawProcs.h create mode 100644 gfx/skia/skia/src/core/SkDrawShadowInfo.cpp create mode 100644 gfx/skia/skia/src/core/SkDrawShadowInfo.h create mode 100644 gfx/skia/skia/src/core/SkDraw_atlas.cpp create mode 100644 gfx/skia/skia/src/core/SkDraw_text.cpp create mode 100644 gfx/skia/skia/src/core/SkDraw_vertices.cpp create mode 100644 gfx/skia/skia/src/core/SkDrawable.cpp create mode 100644 gfx/skia/skia/src/core/SkEdge.cpp create mode 100644 gfx/skia/skia/src/core/SkEdge.h create mode 100644 gfx/skia/skia/src/core/SkEdgeBuilder.cpp create mode 100644 gfx/skia/skia/src/core/SkEdgeBuilder.h create mode 100644 gfx/skia/skia/src/core/SkEdgeClipper.cpp create mode 100644 gfx/skia/skia/src/core/SkEdgeClipper.h create mode 100644 gfx/skia/skia/src/core/SkEffectPriv.h create mode 100644 gfx/skia/skia/src/core/SkEndian.h create mode 100644 gfx/skia/skia/src/core/SkEnumerate.h create mode 100644 gfx/skia/skia/src/core/SkExchange.h create mode 100644 gfx/skia/skia/src/core/SkExecutor.cpp create mode 100644 gfx/skia/skia/src/core/SkFDot6.h create mode 100644 gfx/skia/skia/src/core/SkFixed15.h create mode 100644 gfx/skia/skia/src/core/SkFlattenable.cpp create mode 100644 gfx/skia/skia/src/core/SkFont.cpp create mode 100644 gfx/skia/skia/src/core/SkFontDescriptor.cpp create mode 100644 gfx/skia/skia/src/core/SkFontDescriptor.h create mode 100644 gfx/skia/skia/src/core/SkFontLCDConfig.cpp create mode 100644 gfx/skia/skia/src/core/SkFontMgr.cpp create mode 100644 gfx/skia/skia/src/core/SkFontMgrPriv.h create mode 100644 gfx/skia/skia/src/core/SkFontPriv.h create mode 100644 gfx/skia/skia/src/core/SkFontStream.cpp create mode 100644 gfx/skia/skia/src/core/SkFontStream.h create mode 100644 gfx/skia/skia/src/core/SkForceCPlusPlusLinking.cpp create mode 100644 gfx/skia/skia/src/core/SkFuzzLogging.h create mode 100644 gfx/skia/skia/src/core/SkGaussFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkGaussFilter.h create mode 100644 gfx/skia/skia/src/core/SkGeometry.cpp create mode 100644 gfx/skia/skia/src/core/SkGeometry.h create mode 100644 gfx/skia/skia/src/core/SkGlobalInitialization_core.cpp create mode 100644 gfx/skia/skia/src/core/SkGlyph.cpp create mode 100644 gfx/skia/skia/src/core/SkGlyph.h create mode 100644 gfx/skia/skia/src/core/SkGlyphBuffer.cpp create mode 100644 gfx/skia/skia/src/core/SkGlyphBuffer.h create mode 100644 gfx/skia/skia/src/core/SkGlyphRun.cpp create mode 100644 gfx/skia/skia/src/core/SkGlyphRun.h create mode 100644 gfx/skia/skia/src/core/SkGlyphRunPainter.cpp create mode 100644 gfx/skia/skia/src/core/SkGlyphRunPainter.h create mode 100644 gfx/skia/skia/src/core/SkGpuBlurUtils.cpp create mode 100644 gfx/skia/skia/src/core/SkGpuBlurUtils.h create mode 100644 gfx/skia/skia/src/core/SkGraphics.cpp create mode 100644 gfx/skia/skia/src/core/SkHalf.cpp create mode 100644 gfx/skia/skia/src/core/SkICC.cpp create mode 100644 gfx/skia/skia/src/core/SkICCPriv.h create mode 100644 gfx/skia/skia/src/core/SkIPoint16.h create mode 100644 gfx/skia/skia/src/core/SkImageFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkImageFilterCache.cpp create mode 100644 gfx/skia/skia/src/core/SkImageFilterCache.h create mode 100644 gfx/skia/skia/src/core/SkImageFilterTypes.cpp create mode 100644 gfx/skia/skia/src/core/SkImageFilterTypes.h create mode 100644 gfx/skia/skia/src/core/SkImageFilter_Base.h create mode 100644 gfx/skia/skia/src/core/SkImageGenerator.cpp create mode 100644 gfx/skia/skia/src/core/SkImageInfo.cpp create mode 100644 gfx/skia/skia/src/core/SkImagePriv.h create mode 100644 gfx/skia/skia/src/core/SkLRUCache.h create mode 100644 gfx/skia/skia/src/core/SkLatticeIter.cpp create mode 100644 gfx/skia/skia/src/core/SkLatticeIter.h create mode 100644 gfx/skia/skia/src/core/SkLeanWindows.h create mode 100644 gfx/skia/skia/src/core/SkLineClipper.cpp create mode 100644 gfx/skia/skia/src/core/SkLineClipper.h create mode 100644 gfx/skia/skia/src/core/SkLocalMatrixImageFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkLocalMatrixImageFilter.h create mode 100644 gfx/skia/skia/src/core/SkMD5.cpp create mode 100644 gfx/skia/skia/src/core/SkMD5.h create mode 100644 gfx/skia/skia/src/core/SkMSAN.h create mode 100644 gfx/skia/skia/src/core/SkMakeUnique.h create mode 100644 gfx/skia/skia/src/core/SkMallocPixelRef.cpp create mode 100644 gfx/skia/skia/src/core/SkMask.cpp create mode 100644 gfx/skia/skia/src/core/SkMask.h create mode 100644 gfx/skia/skia/src/core/SkMaskBlurFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkMaskBlurFilter.h create mode 100644 gfx/skia/skia/src/core/SkMaskCache.cpp create mode 100644 gfx/skia/skia/src/core/SkMaskCache.h create mode 100644 gfx/skia/skia/src/core/SkMaskFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkMaskFilterBase.h create mode 100644 gfx/skia/skia/src/core/SkMaskGamma.cpp create mode 100644 gfx/skia/skia/src/core/SkMaskGamma.h create mode 100644 gfx/skia/skia/src/core/SkMath.cpp create mode 100644 gfx/skia/skia/src/core/SkMathPriv.h create mode 100644 gfx/skia/skia/src/core/SkMatrix.cpp create mode 100644 gfx/skia/skia/src/core/SkMatrix44.cpp create mode 100644 gfx/skia/skia/src/core/SkMatrixImageFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkMatrixImageFilter.h create mode 100644 gfx/skia/skia/src/core/SkMatrixPriv.h create mode 100644 gfx/skia/skia/src/core/SkMatrixUtils.h create mode 100644 gfx/skia/skia/src/core/SkMessageBus.h create mode 100644 gfx/skia/skia/src/core/SkMiniRecorder.cpp create mode 100644 gfx/skia/skia/src/core/SkMiniRecorder.h create mode 100644 gfx/skia/skia/src/core/SkMipMap.cpp create mode 100644 gfx/skia/skia/src/core/SkMipMap.h create mode 100644 gfx/skia/skia/src/core/SkModeColorFilter.cpp create mode 100644 gfx/skia/skia/src/core/SkModeColorFilter.h create mode 100644 gfx/skia/skia/src/core/SkMultiPictureDraw.cpp create mode 100644 gfx/skia/skia/src/core/SkNextID.h create mode 100644 gfx/skia/skia/src/core/SkNormalFlatSource.cpp create mode 100644 gfx/skia/skia/src/core/SkNormalFlatSource.h create mode 100644 gfx/skia/skia/src/core/SkNormalMapSource.cpp create mode 100644 gfx/skia/skia/src/core/SkNormalMapSource.h create mode 100644 gfx/skia/skia/src/core/SkNormalSource.cpp create mode 100644 gfx/skia/skia/src/core/SkNormalSource.h create mode 100644 gfx/skia/skia/src/core/SkOSFile.h create mode 100644 gfx/skia/skia/src/core/SkOpts.cpp create mode 100644 gfx/skia/skia/src/core/SkOpts.h create mode 100644 gfx/skia/skia/src/core/SkOrderedReadBuffer.h create mode 100644 gfx/skia/skia/src/core/SkOverdrawCanvas.cpp create mode 100644 gfx/skia/skia/src/core/SkPaint.cpp create mode 100644 gfx/skia/skia/src/core/SkPaintDefaults.h create mode 100644 gfx/skia/skia/src/core/SkPaintPriv.cpp create mode 100644 gfx/skia/skia/src/core/SkPaintPriv.h create mode 100644 gfx/skia/skia/src/core/SkPath.cpp create mode 100644 gfx/skia/skia/src/core/SkPathEffect.cpp create mode 100644 gfx/skia/skia/src/core/SkPathMakers.h create mode 100644 gfx/skia/skia/src/core/SkPathMeasure.cpp create mode 100644 gfx/skia/skia/src/core/SkPathMeasurePriv.h create mode 100644 gfx/skia/skia/src/core/SkPathPriv.h create mode 100644 gfx/skia/skia/src/core/SkPathRef.cpp create mode 100644 gfx/skia/skia/src/core/SkPath_serial.cpp create mode 100644 gfx/skia/skia/src/core/SkPicture.cpp create mode 100644 gfx/skia/skia/src/core/SkPictureCommon.h create mode 100644 gfx/skia/skia/src/core/SkPictureData.cpp create mode 100644 gfx/skia/skia/src/core/SkPictureData.h create mode 100644 gfx/skia/skia/src/core/SkPictureFlat.cpp create mode 100644 gfx/skia/skia/src/core/SkPictureFlat.h create mode 100644 gfx/skia/skia/src/core/SkPictureImageGenerator.cpp create mode 100644 gfx/skia/skia/src/core/SkPicturePlayback.cpp create mode 100644 gfx/skia/skia/src/core/SkPicturePlayback.h create mode 100644 gfx/skia/skia/src/core/SkPicturePriv.h create mode 100644 gfx/skia/skia/src/core/SkPictureRecord.cpp create mode 100644 gfx/skia/skia/src/core/SkPictureRecord.h create mode 100644 gfx/skia/skia/src/core/SkPictureRecorder.cpp create mode 100644 gfx/skia/skia/src/core/SkPixelRef.cpp create mode 100644 gfx/skia/skia/src/core/SkPixelRefPriv.h create mode 100644 gfx/skia/skia/src/core/SkPixmap.cpp create mode 100644 gfx/skia/skia/src/core/SkPixmapPriv.h create mode 100644 gfx/skia/skia/src/core/SkPoint.cpp create mode 100644 gfx/skia/skia/src/core/SkPoint3.cpp create mode 100644 gfx/skia/skia/src/core/SkPointPriv.h create mode 100644 gfx/skia/skia/src/core/SkPromiseImageTexture.cpp create mode 100644 gfx/skia/skia/src/core/SkPtrRecorder.cpp create mode 100644 gfx/skia/skia/src/core/SkPtrRecorder.h create mode 100644 gfx/skia/skia/src/core/SkQuadClipper.cpp create mode 100644 gfx/skia/skia/src/core/SkQuadClipper.h create mode 100644 gfx/skia/skia/src/core/SkRRect.cpp create mode 100644 gfx/skia/skia/src/core/SkRRectPriv.h create mode 100644 gfx/skia/skia/src/core/SkRTree.cpp create mode 100644 gfx/skia/skia/src/core/SkRTree.h create mode 100644 gfx/skia/skia/src/core/SkRWBuffer.cpp create mode 100644 gfx/skia/skia/src/core/SkRasterClip.cpp create mode 100644 gfx/skia/skia/src/core/SkRasterClip.h create mode 100644 gfx/skia/skia/src/core/SkRasterClipStack.h create mode 100644 gfx/skia/skia/src/core/SkRasterPipeline.cpp create mode 100644 gfx/skia/skia/src/core/SkRasterPipeline.h create mode 100644 gfx/skia/skia/src/core/SkRasterPipelineBlitter.cpp create mode 100644 gfx/skia/skia/src/core/SkReadBuffer.cpp create mode 100644 gfx/skia/skia/src/core/SkReadBuffer.h create mode 100644 gfx/skia/skia/src/core/SkReader32.h create mode 100644 gfx/skia/skia/src/core/SkRecord.cpp create mode 100644 gfx/skia/skia/src/core/SkRecord.h create mode 100644 gfx/skia/skia/src/core/SkRecordDraw.cpp create mode 100644 gfx/skia/skia/src/core/SkRecordDraw.h create mode 100644 gfx/skia/skia/src/core/SkRecordOpts.cpp create mode 100644 gfx/skia/skia/src/core/SkRecordOpts.h create mode 100644 gfx/skia/skia/src/core/SkRecordPattern.h create mode 100644 gfx/skia/skia/src/core/SkRecordedDrawable.cpp create mode 100644 gfx/skia/skia/src/core/SkRecordedDrawable.h create mode 100644 gfx/skia/skia/src/core/SkRecorder.cpp create mode 100644 gfx/skia/skia/src/core/SkRecorder.h create mode 100644 gfx/skia/skia/src/core/SkRecords.cpp create mode 100644 gfx/skia/skia/src/core/SkRecords.h create mode 100644 gfx/skia/skia/src/core/SkRect.cpp create mode 100644 gfx/skia/skia/src/core/SkRectPriv.h create mode 100644 gfx/skia/skia/src/core/SkRegion.cpp create mode 100644 gfx/skia/skia/src/core/SkRegionPriv.h create mode 100644 gfx/skia/skia/src/core/SkRegion_path.cpp create mode 100644 gfx/skia/skia/src/core/SkRemoteGlyphCache.cpp create mode 100644 gfx/skia/skia/src/core/SkRemoteGlyphCache.h create mode 100644 gfx/skia/skia/src/core/SkResourceCache.cpp create mode 100644 gfx/skia/skia/src/core/SkResourceCache.h create mode 100644 gfx/skia/skia/src/core/SkSafeMath.h create mode 100644 gfx/skia/skia/src/core/SkSafeRange.h create mode 100644 gfx/skia/skia/src/core/SkScalar.cpp create mode 100644 gfx/skia/skia/src/core/SkScaleToSides.h create mode 100644 gfx/skia/skia/src/core/SkScalerContext.cpp create mode 100644 gfx/skia/skia/src/core/SkScalerContext.h create mode 100644 gfx/skia/skia/src/core/SkScan.cpp create mode 100644 gfx/skia/skia/src/core/SkScan.h create mode 100644 gfx/skia/skia/src/core/SkScanPriv.h create mode 100644 gfx/skia/skia/src/core/SkScan_AAAPath.cpp create mode 100644 gfx/skia/skia/src/core/SkScan_AntiPath.cpp create mode 100644 gfx/skia/skia/src/core/SkScan_Antihair.cpp create mode 100644 gfx/skia/skia/src/core/SkScan_Hairline.cpp create mode 100644 gfx/skia/skia/src/core/SkScan_Path.cpp create mode 100644 gfx/skia/skia/src/core/SkScopeExit.h create mode 100644 gfx/skia/skia/src/core/SkSemaphore.cpp create mode 100644 gfx/skia/skia/src/core/SkSharedMutex.cpp create mode 100644 gfx/skia/skia/src/core/SkSharedMutex.h create mode 100644 gfx/skia/skia/src/core/SkSpan.h create mode 100644 gfx/skia/skia/src/core/SkSpecialImage.cpp create mode 100644 gfx/skia/skia/src/core/SkSpecialImage.h create mode 100644 gfx/skia/skia/src/core/SkSpecialSurface.cpp create mode 100644 gfx/skia/skia/src/core/SkSpecialSurface.h create mode 100644 gfx/skia/skia/src/core/SkSpinlock.cpp create mode 100644 gfx/skia/skia/src/core/SkSpriteBlitter.h create mode 100644 gfx/skia/skia/src/core/SkSpriteBlitter_ARGB32.cpp create mode 100644 gfx/skia/skia/src/core/SkSpriteBlitter_RGB565.cpp create mode 100644 gfx/skia/skia/src/core/SkStream.cpp create mode 100644 gfx/skia/skia/src/core/SkStreamPriv.h create mode 100644 gfx/skia/skia/src/core/SkStrike.cpp create mode 100644 gfx/skia/skia/src/core/SkStrike.h create mode 100644 gfx/skia/skia/src/core/SkStrikeCache.cpp create mode 100644 gfx/skia/skia/src/core/SkStrikeCache.h create mode 100644 gfx/skia/skia/src/core/SkStrikeForGPU.cpp create mode 100644 gfx/skia/skia/src/core/SkStrikeForGPU.h create mode 100644 gfx/skia/skia/src/core/SkStrikeSpec.cpp create mode 100644 gfx/skia/skia/src/core/SkStrikeSpec.h create mode 100644 gfx/skia/skia/src/core/SkString.cpp create mode 100644 gfx/skia/skia/src/core/SkStringUtils.cpp create mode 100644 gfx/skia/skia/src/core/SkStringUtils.h create mode 100644 gfx/skia/skia/src/core/SkStroke.cpp create mode 100644 gfx/skia/skia/src/core/SkStroke.h create mode 100644 gfx/skia/skia/src/core/SkStrokeRec.cpp create mode 100644 gfx/skia/skia/src/core/SkStrokerPriv.cpp create mode 100644 gfx/skia/skia/src/core/SkStrokerPriv.h create mode 100644 gfx/skia/skia/src/core/SkSurfaceCharacterization.cpp create mode 100644 gfx/skia/skia/src/core/SkSurfacePriv.h create mode 100644 gfx/skia/skia/src/core/SkSwizzle.cpp create mode 100644 gfx/skia/skia/src/core/SkTDPQueue.h create mode 100644 gfx/skia/skia/src/core/SkTDynamicHash.h create mode 100644 gfx/skia/skia/src/core/SkTInternalLList.h create mode 100644 gfx/skia/skia/src/core/SkTLList.h create mode 100644 gfx/skia/skia/src/core/SkTLS.cpp create mode 100644 gfx/skia/skia/src/core/SkTLS.h create mode 100644 gfx/skia/skia/src/core/SkTLazy.h create mode 100644 gfx/skia/skia/src/core/SkTMultiMap.h create mode 100644 gfx/skia/skia/src/core/SkTSearch.cpp create mode 100644 gfx/skia/skia/src/core/SkTSearch.h create mode 100644 gfx/skia/skia/src/core/SkTSort.h create mode 100644 gfx/skia/skia/src/core/SkTTopoSort.h create mode 100644 gfx/skia/skia/src/core/SkTaskGroup.cpp create mode 100644 gfx/skia/skia/src/core/SkTaskGroup.h create mode 100644 gfx/skia/skia/src/core/SkTextBlob.cpp create mode 100644 gfx/skia/skia/src/core/SkTextBlobPriv.h create mode 100644 gfx/skia/skia/src/core/SkTextFormatParams.h create mode 100644 gfx/skia/skia/src/core/SkThreadID.cpp create mode 100644 gfx/skia/skia/src/core/SkTime.cpp create mode 100644 gfx/skia/skia/src/core/SkTraceEvent.h create mode 100644 gfx/skia/skia/src/core/SkTraceEventCommon.h create mode 100644 gfx/skia/skia/src/core/SkTypeface.cpp create mode 100644 gfx/skia/skia/src/core/SkTypefaceCache.cpp create mode 100644 gfx/skia/skia/src/core/SkTypefaceCache.h create mode 100644 gfx/skia/skia/src/core/SkTypefacePriv.h create mode 100644 gfx/skia/skia/src/core/SkTypeface_remote.cpp create mode 100644 gfx/skia/skia/src/core/SkTypeface_remote.h create mode 100644 gfx/skia/skia/src/core/SkUnPreMultiply.cpp create mode 100644 gfx/skia/skia/src/core/SkUtils.cpp create mode 100644 gfx/skia/skia/src/core/SkUtils.h create mode 100644 gfx/skia/skia/src/core/SkUtilsArm.cpp create mode 100644 gfx/skia/skia/src/core/SkVM.cpp create mode 100644 gfx/skia/skia/src/core/SkVM.h create mode 100644 gfx/skia/skia/src/core/SkVMBlitter.cpp create mode 100644 gfx/skia/skia/src/core/SkValidatingReadBuffer.h create mode 100644 gfx/skia/skia/src/core/SkValidationUtils.h create mode 100644 gfx/skia/skia/src/core/SkVertState.cpp create mode 100644 gfx/skia/skia/src/core/SkVertState.h create mode 100644 gfx/skia/skia/src/core/SkVertices.cpp create mode 100644 gfx/skia/skia/src/core/SkVptr.h create mode 100644 gfx/skia/skia/src/core/SkWriteBuffer.cpp create mode 100644 gfx/skia/skia/src/core/SkWriteBuffer.h create mode 100644 gfx/skia/skia/src/core/SkWritePixelsRec.h create mode 100644 gfx/skia/skia/src/core/SkWriter32.cpp create mode 100644 gfx/skia/skia/src/core/SkWriter32.h create mode 100644 gfx/skia/skia/src/core/SkXfermode.cpp create mode 100644 gfx/skia/skia/src/core/SkXfermodeInterpretation.cpp create mode 100644 gfx/skia/skia/src/core/SkXfermodeInterpretation.h create mode 100644 gfx/skia/skia/src/core/SkXfermodePriv.h create mode 100644 gfx/skia/skia/src/core/SkYUVASizeInfo.cpp create mode 100644 gfx/skia/skia/src/core/SkYUVMath.cpp create mode 100644 gfx/skia/skia/src/core/SkYUVMath.h create mode 100644 gfx/skia/skia/src/core/SkYUVPlanesCache.cpp create mode 100644 gfx/skia/skia/src/core/SkYUVPlanesCache.h create mode 100644 gfx/skia/skia/src/core/SkZip.h create mode 100644 gfx/skia/skia/src/effects/Sk1DPathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/Sk2DPathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/SkColorMatrix.cpp create mode 100644 gfx/skia/skia/src/effects/SkColorMatrixFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkCornerPathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/SkDashImpl.h create mode 100644 gfx/skia/skia/src/effects/SkDashPathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/SkDiscretePathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/SkEmbossMask.cpp create mode 100644 gfx/skia/skia/src/effects/SkEmbossMask.h create mode 100644 gfx/skia/skia/src/effects/SkEmbossMaskFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkEmbossMaskFilter.h create mode 100644 gfx/skia/skia/src/effects/SkHighContrastFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkLayerDrawLooper.cpp create mode 100644 gfx/skia/skia/src/effects/SkLumaColorFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkOpPE.h create mode 100644 gfx/skia/skia/src/effects/SkOpPathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/SkOverdrawColorFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkPackBits.cpp create mode 100644 gfx/skia/skia/src/effects/SkPackBits.h create mode 100644 gfx/skia/skia/src/effects/SkShaderMaskFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkTableColorFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkTableMaskFilter.cpp create mode 100644 gfx/skia/skia/src/effects/SkTrimPE.h create mode 100644 gfx/skia/skia/src/effects/SkTrimPathEffect.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkAlphaThresholdFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkArithmeticImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkBlurImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkColorFilterImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkComposeImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkDisplacementMapEffect.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkDropShadowImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkImageFilters.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkImageSource.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkLightingImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkMagnifierImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkMergeImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkMorphologyImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkOffsetImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkPaintImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkPictureImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkTileImageFilter.cpp create mode 100644 gfx/skia/skia/src/effects/imagefilters/SkXfermodeImageFilter.cpp create mode 100644 gfx/skia/skia/src/fonts/SkFontMgr_indirect.cpp create mode 100644 gfx/skia/skia/src/fonts/SkRemotableFontMgr.cpp create mode 100644 gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.cpp create mode 100644 gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.h create mode 100644 gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.h create mode 100644 gfx/skia/skia/src/gpu/GrAllocator.h create mode 100644 gfx/skia/skia/src/gpu/GrAppliedClip.h create mode 100644 gfx/skia/skia/src/gpu/GrAuditTrail.cpp create mode 100644 gfx/skia/skia/src/gpu/GrAuditTrail.h create mode 100644 gfx/skia/skia/src/gpu/GrAutoLocaleSetter.h create mode 100644 gfx/skia/skia/src/gpu/GrBackendSurface.cpp create mode 100644 gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.cpp create mode 100644 gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.h create mode 100644 gfx/skia/skia/src/gpu/GrBaseContextPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrBitmapTextureMaker.cpp create mode 100644 gfx/skia/skia/src/gpu/GrBitmapTextureMaker.h create mode 100644 gfx/skia/skia/src/gpu/GrBlend.h create mode 100644 gfx/skia/skia/src/gpu/GrBlurUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/GrBlurUtils.h create mode 100644 gfx/skia/skia/src/gpu/GrBuffer.h create mode 100644 gfx/skia/skia/src/gpu/GrBufferAllocPool.cpp create mode 100644 gfx/skia/skia/src/gpu/GrBufferAllocPool.h create mode 100644 gfx/skia/skia/src/gpu/GrCaps.cpp create mode 100644 gfx/skia/skia/src/gpu/GrCaps.h create mode 100644 gfx/skia/skia/src/gpu/GrClientMappedBufferManager.cpp create mode 100644 gfx/skia/skia/src/gpu/GrClientMappedBufferManager.h create mode 100644 gfx/skia/skia/src/gpu/GrClip.h create mode 100644 gfx/skia/skia/src/gpu/GrClipStackClip.cpp create mode 100644 gfx/skia/skia/src/gpu/GrClipStackClip.h create mode 100644 gfx/skia/skia/src/gpu/GrColor.h create mode 100644 gfx/skia/skia/src/gpu/GrColorInfo.cpp create mode 100644 gfx/skia/skia/src/gpu/GrColorInfo.h create mode 100644 gfx/skia/skia/src/gpu/GrColorSpaceXform.cpp create mode 100644 gfx/skia/skia/src/gpu/GrColorSpaceXform.h create mode 100644 gfx/skia/skia/src/gpu/GrContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrContextPriv.cpp create mode 100644 gfx/skia/skia/src/gpu/GrContextPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrContextThreadSafeProxy.cpp create mode 100644 gfx/skia/skia/src/gpu/GrContextThreadSafeProxyPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrContext_Base.cpp create mode 100644 gfx/skia/skia/src/gpu/GrCoordTransform.h create mode 100644 gfx/skia/skia/src/gpu/GrCopyRenderTask.cpp create mode 100644 gfx/skia/skia/src/gpu/GrCopyRenderTask.h create mode 100644 gfx/skia/skia/src/gpu/GrCpuBuffer.h create mode 100644 gfx/skia/skia/src/gpu/GrDDLContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDataUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDataUtils.h create mode 100644 gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.h create mode 100644 gfx/skia/skia/src/gpu/GrDeferredProxyUploader.h create mode 100644 gfx/skia/skia/src/gpu/GrDeferredUpload.h create mode 100644 gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.h create mode 100644 gfx/skia/skia/src/gpu/GrDrawOpAtlas.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDrawOpAtlas.h create mode 100644 gfx/skia/skia/src/gpu/GrDrawOpTest.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDrawOpTest.h create mode 100644 gfx/skia/skia/src/gpu/GrDrawingManager.cpp create mode 100644 gfx/skia/skia/src/gpu/GrDrawingManager.h create mode 100644 gfx/skia/skia/src/gpu/GrDriverBugWorkarounds.cpp create mode 100644 gfx/skia/skia/src/gpu/GrFPArgs.h create mode 100644 gfx/skia/skia/src/gpu/GrFixedClip.cpp create mode 100644 gfx/skia/skia/src/gpu/GrFixedClip.h create mode 100644 gfx/skia/skia/src/gpu/GrFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/GrFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/GrGeometryProcessor.h create mode 100644 gfx/skia/skia/src/gpu/GrGlyph.h create mode 100644 gfx/skia/skia/src/gpu/GrGpu.cpp create mode 100644 gfx/skia/skia/src/gpu/GrGpu.h create mode 100644 gfx/skia/skia/src/gpu/GrGpuBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/GrGpuBuffer.h create mode 100644 gfx/skia/skia/src/gpu/GrGpuResource.cpp create mode 100644 gfx/skia/skia/src/gpu/GrGpuResourceCacheAccess.h create mode 100644 gfx/skia/skia/src/gpu/GrGpuResourcePriv.h create mode 100644 gfx/skia/skia/src/gpu/GrImageContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrImageContextPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrImageInfo.h create mode 100644 gfx/skia/skia/src/gpu/GrImageTextureMaker.cpp create mode 100644 gfx/skia/skia/src/gpu/GrImageTextureMaker.h create mode 100644 gfx/skia/skia/src/gpu/GrLegacyDirectContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrMemoryPool.cpp create mode 100644 gfx/skia/skia/src/gpu/GrMemoryPool.h create mode 100644 gfx/skia/skia/src/gpu/GrMesh.h create mode 100644 gfx/skia/skia/src/gpu/GrNativeRect.h create mode 100644 gfx/skia/skia/src/gpu/GrNonAtomicRef.h create mode 100644 gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.cpp create mode 100644 gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.h create mode 100644 gfx/skia/skia/src/gpu/GrOpFlushState.cpp create mode 100644 gfx/skia/skia/src/gpu/GrOpFlushState.h create mode 100644 gfx/skia/skia/src/gpu/GrOpsRenderPass.cpp create mode 100644 gfx/skia/skia/src/gpu/GrOpsRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/GrOpsTask.cpp create mode 100644 gfx/skia/skia/src/gpu/GrOpsTask.h create mode 100644 gfx/skia/skia/src/gpu/GrPaint.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPaint.h create mode 100644 gfx/skia/skia/src/gpu/GrPath.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPath.h create mode 100644 gfx/skia/skia/src/gpu/GrPathProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPathProcessor.h create mode 100644 gfx/skia/skia/src/gpu/GrPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/GrPathRendererChain.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPathRendererChain.h create mode 100644 gfx/skia/skia/src/gpu/GrPathRendering.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPathRendering.h create mode 100644 gfx/skia/skia/src/gpu/GrPathRendering_none.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPersistentCacheUtils.h create mode 100644 gfx/skia/skia/src/gpu/GrPipeline.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPipeline.h create mode 100644 gfx/skia/skia/src/gpu/GrPrimitiveProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/GrPrimitiveProcessor.h create mode 100644 gfx/skia/skia/src/gpu/GrProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProcessor.h create mode 100644 gfx/skia/skia/src/gpu/GrProcessorAnalysis.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProcessorAnalysis.h create mode 100644 gfx/skia/skia/src/gpu/GrProcessorSet.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProcessorSet.h create mode 100644 gfx/skia/skia/src/gpu/GrProcessorUnitTest.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProcessorUnitTest.h create mode 100644 gfx/skia/skia/src/gpu/GrProgramDesc.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProgramDesc.h create mode 100644 gfx/skia/skia/src/gpu/GrProgramInfo.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProgramInfo.h create mode 100644 gfx/skia/skia/src/gpu/GrProxyProvider.cpp create mode 100644 gfx/skia/skia/src/gpu/GrProxyProvider.h create mode 100644 gfx/skia/skia/src/gpu/GrRecordingContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRecordingContextPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrRectanizer.h create mode 100644 gfx/skia/skia/src/gpu/GrRectanizer_pow2.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRectanizer_pow2.h create mode 100644 gfx/skia/skia/src/gpu/GrRectanizer_skyline.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRectanizer_skyline.h create mode 100644 gfx/skia/skia/src/gpu/GrReducedClip.cpp create mode 100644 gfx/skia/skia/src/gpu/GrReducedClip.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetContext.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetContextPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetProxy.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetProxy.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTargetProxyPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrRenderTask.cpp create mode 100644 gfx/skia/skia/src/gpu/GrRenderTask.h create mode 100644 gfx/skia/skia/src/gpu/GrResourceAllocator.cpp create mode 100644 gfx/skia/skia/src/gpu/GrResourceAllocator.h create mode 100644 gfx/skia/skia/src/gpu/GrResourceCache.cpp create mode 100644 gfx/skia/skia/src/gpu/GrResourceCache.h create mode 100644 gfx/skia/skia/src/gpu/GrResourceHandle.h create mode 100644 gfx/skia/skia/src/gpu/GrResourceProvider.cpp create mode 100644 gfx/skia/skia/src/gpu/GrResourceProvider.h create mode 100644 gfx/skia/skia/src/gpu/GrResourceProviderPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrSWMaskHelper.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSWMaskHelper.h create mode 100644 gfx/skia/skia/src/gpu/GrSamplePatternDictionary.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSamplePatternDictionary.h create mode 100644 gfx/skia/skia/src/gpu/GrSamplerState.h create mode 100644 gfx/skia/skia/src/gpu/GrScissorState.h create mode 100644 gfx/skia/skia/src/gpu/GrSemaphore.h create mode 100644 gfx/skia/skia/src/gpu/GrShaderCaps.cpp create mode 100644 gfx/skia/skia/src/gpu/GrShaderCaps.h create mode 100644 gfx/skia/skia/src/gpu/GrShaderUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/GrShaderUtils.h create mode 100644 gfx/skia/skia/src/gpu/GrShaderVar.cpp create mode 100644 gfx/skia/skia/src/gpu/GrShaderVar.h create mode 100644 gfx/skia/skia/src/gpu/GrSkSLFPFactoryCache.h create mode 100644 gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/GrStencilAttachment.cpp create mode 100644 gfx/skia/skia/src/gpu/GrStencilAttachment.h create mode 100644 gfx/skia/skia/src/gpu/GrStencilClip.h create mode 100644 gfx/skia/skia/src/gpu/GrStencilSettings.cpp create mode 100644 gfx/skia/skia/src/gpu/GrStencilSettings.h create mode 100644 gfx/skia/skia/src/gpu/GrStyle.cpp create mode 100644 gfx/skia/skia/src/gpu/GrStyle.h create mode 100644 gfx/skia/skia/src/gpu/GrSurface.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSurfaceContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSurfaceContext.h create mode 100644 gfx/skia/skia/src/gpu/GrSurfaceContextPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrSurfacePriv.h create mode 100644 gfx/skia/skia/src/gpu/GrSurfaceProxy.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSurfaceProxy.h create mode 100644 gfx/skia/skia/src/gpu/GrSurfaceProxyPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrSwizzle.cpp create mode 100644 gfx/skia/skia/src/gpu/GrSwizzle.h create mode 100644 gfx/skia/skia/src/gpu/GrTRecorder.h create mode 100644 gfx/skia/skia/src/gpu/GrTessellator.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTessellator.h create mode 100644 gfx/skia/skia/src/gpu/GrTestUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTestUtils.h create mode 100644 gfx/skia/skia/src/gpu/GrTexture.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureAdjuster.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureAdjuster.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureContext.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureContext.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureMaker.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureMaker.h create mode 100644 gfx/skia/skia/src/gpu/GrTexturePriv.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureProducer.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureProducer.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureProxy.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureProxy.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureProxyCacheAccess.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureProxyPriv.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureResolveManager.h create mode 100644 gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.h create mode 100644 gfx/skia/skia/src/gpu/GrTracing.h create mode 100644 gfx/skia/skia/src/gpu/GrTransferFromRenderTask.cpp create mode 100644 gfx/skia/skia/src/gpu/GrTransferFromRenderTask.h create mode 100644 gfx/skia/skia/src/gpu/GrUserStencilSettings.h create mode 100644 gfx/skia/skia/src/gpu/GrUtil.h create mode 100644 gfx/skia/skia/src/gpu/GrVertexWriter.h create mode 100644 gfx/skia/skia/src/gpu/GrWaitRenderTask.cpp create mode 100644 gfx/skia/skia/src/gpu/GrWaitRenderTask.h create mode 100644 gfx/skia/skia/src/gpu/GrWindowRectangles.h create mode 100644 gfx/skia/skia/src/gpu/GrWindowRectsState.h create mode 100644 gfx/skia/skia/src/gpu/GrXferProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/GrXferProcessor.h create mode 100644 gfx/skia/skia/src/gpu/GrYUVProvider.cpp create mode 100644 gfx/skia/skia/src/gpu/GrYUVProvider.h create mode 100644 gfx/skia/skia/src/gpu/SkGpuDevice.cpp create mode 100644 gfx/skia/skia/src/gpu/SkGpuDevice.h create mode 100644 gfx/skia/skia/src/gpu/SkGpuDevice_drawTexture.cpp create mode 100644 gfx/skia/skia/src/gpu/SkGr.cpp create mode 100644 gfx/skia/skia/src/gpu/SkGr.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCFiller.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCFiller.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCSTLList.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCStroker.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCCStroker.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.h create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnCaps.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnCaps.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnGpu.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnGpu.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnTexture.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnTexture.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnUtil.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnUtil.h create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.cpp create mode 100644 gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrAARectEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrAlphaThresholdFragmentProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrAtlasedShaderHelpers.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrBezierEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrBezierEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrBicubicEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrBicubicEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrCircleBlurFragmentProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrCircleEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrColorMatrixFragmentProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrComposeLerpEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrComposeLerpRedEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrConfigConversionEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrConstColorProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrCustomXfermode.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrCustomXfermode.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrDisableColorXP.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrDisableColorXP.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrEllipseEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrLumaColorFilterEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrMagnifierEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrMixerEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrOvalEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrOvalEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrOverrideInputFragmentProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrPremulInputFragmentProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrRRectBlurEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrRRectEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrRRectEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrRectBlurEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrSRGBEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrSRGBEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrSaturateProcessor.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrSimpleTextureEffect.fp create mode 100644 gfx/skia/skia/src/gpu/effects/GrSkSLFP.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrSkSLFP.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrTextureDomain.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrTextureDomain.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.h create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.h create mode 100644 gfx/skia/skia/src/gpu/geometry/GrPathUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/geometry/GrPathUtils.h create mode 100644 gfx/skia/skia/src/gpu/geometry/GrQuad.cpp create mode 100644 gfx/skia/skia/src/gpu/geometry/GrQuad.h create mode 100644 gfx/skia/skia/src/gpu/geometry/GrQuadBuffer.h create mode 100644 gfx/skia/skia/src/gpu/geometry/GrQuadUtils.cpp create mode 100644 gfx/skia/skia/src/gpu/geometry/GrQuadUtils.h create mode 100644 gfx/skia/skia/src/gpu/geometry/GrRect.h create mode 100644 gfx/skia/skia/src/gpu/geometry/GrShape.cpp create mode 100644 gfx/skia/skia/src/gpu/geometry/GrShape.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLAssembleHelpers.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLAssembleInterface.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLBuffer.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLCaps.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLCaps.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLContext.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLContext.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLDefines.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLExtensions.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLGLSL.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLGLSL.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLGpu.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLGpu.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLGpuProgramCache.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLInterfaceAutogen.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLMakeNativeInterface_none.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLPath.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLPath.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLPathRendering.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLPathRendering.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLProgram.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLProgram.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLSemaphore.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLSemaphore.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLTexture.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLTexture.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLTypesPriv.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLUtil.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLUtil.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.h create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLVertexArray.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/GrGLVertexArray.h create mode 100644 gfx/skia/skia/src/gpu/gl/android/GrGLMakeNativeInterface_android.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.h create mode 100644 gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.h create mode 100644 gfx/skia/skia/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/glfw/GrGLMakeNativeInterface_glfw.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/glx/GrGLMakeNativeInterface_glx.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/mac/GrGLMakeNativeInterface_mac.cpp create mode 100644 gfx/skia/skia/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSL.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSL.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLUniformHandler.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.h create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.cpp create mode 100644 gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.h create mode 100644 gfx/skia/skia/src/gpu/gpu_workaround_list.txt create mode 100644 gfx/skia/skia/src/gpu/gradients/GrClampedGradientEffect.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrDualIntervalGradientColorizer.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.h create mode 100644 gfx/skia/skia/src/gpu/gradients/GrGradientShader.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrGradientShader.h create mode 100644 gfx/skia/skia/src/gpu/gradients/GrLinearGradientLayout.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrRadialGradientLayout.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrSingleIntervalGradientColorizer.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrSweepGradientLayout.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrTextureGradientColorizer.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrTiledGradientEffect.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp create mode 100644 gfx/skia/skia/src/gpu/gradients/README.md create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp create mode 100644 gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockBuffer.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockCaps.cpp create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockCaps.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockGpu.cpp create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockGpu.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockOpsRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockStencilAttachment.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockTexture.h create mode 100644 gfx/skia/skia/src/gpu/mock/GrMockTypes.cpp create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlCaps.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlCaps.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlCppUtil.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlGpu.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlGpu.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlSampler.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlSampler.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlTexture.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlTexture.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlUtil.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlUtil.mm create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.h create mode 100644 gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.mm create mode 100644 gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrClearOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrClearOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDashOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDashOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawPathOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawPathOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawableOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrDrawableOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrFillRRectOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrFillRRectOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrFillRectOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrFillRectOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrLatticeOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrLatticeOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrPathStencilSettings.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrRegionOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrRegionOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrStencilPathOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrStencilPathOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.h create mode 100644 gfx/skia/skia/src/gpu/ops/GrTextureOp.cpp create mode 100644 gfx/skia/skia/src/gpu/ops/GrTextureOp.h create mode 100644 gfx/skia/skia/src/gpu/text/GrAtlasManager.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrAtlasManager.h create mode 100644 gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.h create mode 100644 gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.h create mode 100644 gfx/skia/skia/src/gpu/text/GrStrikeCache.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrStrikeCache.h create mode 100644 gfx/skia/skia/src/gpu/text/GrTextBlob.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrTextBlob.h create mode 100644 gfx/skia/skia/src/gpu/text/GrTextBlobCache.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrTextBlobCache.h create mode 100644 gfx/skia/skia/src/gpu/text/GrTextBlobVertexRegenerator.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrTextContext.cpp create mode 100644 gfx/skia/skia/src/gpu/text/GrTextContext.h create mode 100644 gfx/skia/skia/src/gpu/text/GrTextTarget.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkBuffer.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkBufferView.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkBufferView.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkCaps.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkCaps.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkCommandPool.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkCommandPool.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkExtensions.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkGpu.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkGpu.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkImage.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkImage.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkImageLayout.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkImageView.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkImageView.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkInterface.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkInterface.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkMemory.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkMemory.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipeline.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipeline.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineState.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineState.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineStateCache.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkRenderPass.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkRenderPass.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkResource.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSampler.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSampler.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSemaphore.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkSemaphore.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTexture.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTexture.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkTypesPriv.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkUtil.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkUtil.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.h create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.cpp create mode 100644 gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.h create mode 100644 gfx/skia/skia/src/image/SkImage.cpp create mode 100644 gfx/skia/skia/src/image/SkImage_Base.h create mode 100644 gfx/skia/skia/src/image/SkImage_Gpu.cpp create mode 100644 gfx/skia/skia/src/image/SkImage_Gpu.h create mode 100644 gfx/skia/skia/src/image/SkImage_GpuBase.cpp create mode 100644 gfx/skia/skia/src/image/SkImage_GpuBase.h create mode 100644 gfx/skia/skia/src/image/SkImage_GpuYUVA.cpp create mode 100644 gfx/skia/skia/src/image/SkImage_GpuYUVA.h create mode 100644 gfx/skia/skia/src/image/SkImage_Lazy.cpp create mode 100644 gfx/skia/skia/src/image/SkImage_Lazy.h create mode 100644 gfx/skia/skia/src/image/SkImage_Raster.cpp create mode 100644 gfx/skia/skia/src/image/SkReadPixelsRec.h create mode 100644 gfx/skia/skia/src/image/SkSurface.cpp create mode 100644 gfx/skia/skia/src/image/SkSurface_Base.h create mode 100644 gfx/skia/skia/src/image/SkSurface_Gpu.cpp create mode 100644 gfx/skia/skia/src/image/SkSurface_Gpu.h create mode 100644 gfx/skia/skia/src/image/SkSurface_GpuMtl.mm create mode 100644 gfx/skia/skia/src/image/SkSurface_Raster.cpp create mode 100644 gfx/skia/skia/src/images/SkImageEncoder.cpp create mode 100644 gfx/skia/skia/src/images/SkImageEncoderFns.h create mode 100644 gfx/skia/skia/src/images/SkImageEncoderPriv.h create mode 100644 gfx/skia/skia/src/images/SkJPEGWriteUtility.cpp create mode 100644 gfx/skia/skia/src/images/SkJPEGWriteUtility.h create mode 100644 gfx/skia/skia/src/images/SkJpegEncoder.cpp create mode 100644 gfx/skia/skia/src/images/SkPngEncoder.cpp create mode 100644 gfx/skia/skia/src/images/SkWebpEncoder.cpp create mode 100644 gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.cpp create mode 100644 gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.h create mode 100644 gfx/skia/skia/src/opts/Sk4px_NEON.h create mode 100644 gfx/skia/skia/src/opts/Sk4px_SSE2.h create mode 100644 gfx/skia/skia/src/opts/Sk4px_none.h create mode 100644 gfx/skia/skia/src/opts/SkBitmapFilter_opts.h create mode 100644 gfx/skia/skia/src/opts/SkBitmapProcState_opts.h create mode 100644 gfx/skia/skia/src/opts/SkBlitMask_opts.h create mode 100644 gfx/skia/skia/src/opts/SkBlitRow_opts.h create mode 100644 gfx/skia/skia/src/opts/SkChecksum_opts.h create mode 100644 gfx/skia/skia/src/opts/SkOpts_avx.cpp create mode 100644 gfx/skia/skia/src/opts/SkOpts_crc32.cpp create mode 100644 gfx/skia/skia/src/opts/SkOpts_hsw.cpp create mode 100644 gfx/skia/skia/src/opts/SkOpts_sse41.cpp create mode 100644 gfx/skia/skia/src/opts/SkOpts_sse42.cpp create mode 100644 gfx/skia/skia/src/opts/SkOpts_ssse3.cpp create mode 100644 gfx/skia/skia/src/opts/SkRasterPipeline_opts.h create mode 100644 gfx/skia/skia/src/opts/SkSwizzler_opts.h create mode 100644 gfx/skia/skia/src/opts/SkUtils_opts.h create mode 100644 gfx/skia/skia/src/opts/SkXfermode_opts.h create mode 100644 gfx/skia/skia/src/pathops/SkAddIntersections.cpp create mode 100644 gfx/skia/skia/src/pathops/SkAddIntersections.h create mode 100644 gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp create mode 100644 gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp create mode 100644 gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp create mode 100644 gfx/skia/skia/src/pathops/SkDLineIntersection.cpp create mode 100644 gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp create mode 100644 gfx/skia/skia/src/pathops/SkIntersectionHelper.h create mode 100644 gfx/skia/skia/src/pathops/SkIntersections.cpp create mode 100644 gfx/skia/skia/src/pathops/SkIntersections.h create mode 100644 gfx/skia/skia/src/pathops/SkLineParameters.h create mode 100644 gfx/skia/skia/src/pathops/SkOpAngle.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpAngle.h create mode 100644 gfx/skia/skia/src/pathops/SkOpBuilder.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpCoincidence.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpCoincidence.h create mode 100644 gfx/skia/skia/src/pathops/SkOpContour.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpContour.h create mode 100644 gfx/skia/skia/src/pathops/SkOpCubicHull.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h create mode 100644 gfx/skia/skia/src/pathops/SkOpSegment.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpSegment.h create mode 100644 gfx/skia/skia/src/pathops/SkOpSpan.cpp create mode 100644 gfx/skia/skia/src/pathops/SkOpSpan.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsAsWinding.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsBounds.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsCommon.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsConic.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsConic.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsCubic.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsCurve.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsDebug.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsLine.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsLine.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsOp.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsPoint.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsQuad.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsRect.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsRect.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsTCurve.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsTSect.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsTypes.h create mode 100644 gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathWriter.cpp create mode 100644 gfx/skia/skia/src/pathops/SkPathWriter.h create mode 100644 gfx/skia/skia/src/pathops/SkReduceOrder.cpp create mode 100644 gfx/skia/skia/src/pathops/SkReduceOrder.h create mode 100644 gfx/skia/skia/src/pdf/SkBitmapKey.h create mode 100644 gfx/skia/skia/src/pdf/SkClusterator.cpp create mode 100644 gfx/skia/skia/src/pdf/SkClusterator.h create mode 100644 gfx/skia/skia/src/pdf/SkDeflate.cpp create mode 100644 gfx/skia/skia/src/pdf/SkDeflate.h create mode 100644 gfx/skia/skia/src/pdf/SkDocument_PDF_None.cpp create mode 100644 gfx/skia/skia/src/pdf/SkJpegInfo.cpp create mode 100644 gfx/skia/skia/src/pdf/SkJpegInfo.h create mode 100644 gfx/skia/skia/src/pdf/SkKeyedImage.cpp create mode 100644 gfx/skia/skia/src/pdf/SkKeyedImage.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFBitmap.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFBitmap.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFDevice.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFDevice.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFDocument.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFDocumentPriv.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFFont.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFFont.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFFormXObject.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFFormXObject.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFGlyphUse.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFGradientShader.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFGradientShader.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFGraphicStackState.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFGraphicStackState.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFGraphicState.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFGraphicState.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFMetadata.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFMetadata.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFResourceDict.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFResourceDict.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFShader.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFShader.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFSubsetFont.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFSubsetFont.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFTag.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFTag.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFType1Font.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFType1Font.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFTypes.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFTypes.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFUnion.h create mode 100644 gfx/skia/skia/src/pdf/SkPDFUtils.cpp create mode 100644 gfx/skia/skia/src/pdf/SkPDFUtils.h create mode 100644 gfx/skia/skia/src/pdf/SkUUID.h create mode 100644 gfx/skia/skia/src/ports/SkDebug_android.cpp create mode 100644 gfx/skia/skia/src/ports/SkDebug_stdio.cpp create mode 100644 gfx/skia/skia/src/ports/SkDebug_win.cpp create mode 100644 gfx/skia/skia/src/ports/SkDiscardableMemory_none.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontConfigInterface.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontConfigInterface_direct.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontConfigInterface_direct.h create mode 100644 gfx/skia/skia/src/ports/SkFontConfigInterface_direct_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontConfigTypeface.h create mode 100644 gfx/skia/skia/src/ports/SkFontHost_FreeType.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontHost_FreeType_common.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontHost_FreeType_common.h create mode 100644 gfx/skia/skia/src/ports/SkFontHost_cairo.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontHost_mac.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontHost_win.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_android.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_android_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_android_parser.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_android_parser.h create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom.h create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom_directory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom_directory_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom_embedded.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom_embedded_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom_empty.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_custom_empty_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_empty_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_fontconfig.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_fontconfig_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_fuchsia.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_win_dw.cpp create mode 100644 gfx/skia/skia/src/ports/SkFontMgr_win_dw_factory.cpp create mode 100644 gfx/skia/skia/src/ports/SkGlobalInitialization_default.cpp create mode 100644 gfx/skia/skia/src/ports/SkImageEncoder_CG.cpp create mode 100644 gfx/skia/skia/src/ports/SkImageEncoder_WIC.cpp create mode 100644 gfx/skia/skia/src/ports/SkImageGeneratorCG.cpp create mode 100644 gfx/skia/skia/src/ports/SkImageGeneratorWIC.cpp create mode 100644 gfx/skia/skia/src/ports/SkImageGenerator_none.cpp create mode 100644 gfx/skia/skia/src/ports/SkImageGenerator_skia.cpp create mode 100644 gfx/skia/skia/src/ports/SkMemory_malloc.cpp create mode 100644 gfx/skia/skia/src/ports/SkMemory_mozalloc.cpp create mode 100644 gfx/skia/skia/src/ports/SkOSFile_ios.h create mode 100644 gfx/skia/skia/src/ports/SkOSFile_posix.cpp create mode 100644 gfx/skia/skia/src/ports/SkOSFile_stdio.cpp create mode 100644 gfx/skia/skia/src/ports/SkOSFile_win.cpp create mode 100644 gfx/skia/skia/src/ports/SkOSLibrary.h create mode 100644 gfx/skia/skia/src/ports/SkOSLibrary_posix.cpp create mode 100644 gfx/skia/skia/src/ports/SkOSLibrary_win.cpp create mode 100644 gfx/skia/skia/src/ports/SkRemotableFontMgr_win_dw.cpp create mode 100644 gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp create mode 100644 gfx/skia/skia/src/ports/SkScalerContext_win_dw.h create mode 100644 gfx/skia/skia/src/ports/SkTLS_none.cpp create mode 100644 gfx/skia/skia/src/ports/SkTLS_pthread.cpp create mode 100644 gfx/skia/skia/src/ports/SkTLS_win.cpp create mode 100644 gfx/skia/skia/src/ports/SkTypeface_win_dw.cpp create mode 100644 gfx/skia/skia/src/ports/SkTypeface_win_dw.h create mode 100644 gfx/skia/skia/src/sfnt/SkIBMFamilyClass.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTableTypes.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_EBDT.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_EBLC.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_EBSC.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V0.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V1.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V2.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V3.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V4.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_OS_2_VA.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_fvar.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_gasp.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_glyf.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_head.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_hhea.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_loca.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_maxp.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_maxp_CFF.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_maxp_TT.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_name.cpp create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_name.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTTable_post.h create mode 100644 gfx/skia/skia/src/sfnt/SkOTUtils.cpp create mode 100644 gfx/skia/skia/src/sfnt/SkOTUtils.h create mode 100644 gfx/skia/skia/src/sfnt/SkPanose.h create mode 100644 gfx/skia/skia/src/sfnt/SkSFNTHeader.h create mode 100644 gfx/skia/skia/src/sfnt/SkTTCFHeader.h create mode 100644 gfx/skia/skia/src/shaders/SkBitmapProcShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkBitmapProcShader.h create mode 100644 gfx/skia/skia/src/shaders/SkColorFilterShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkColorFilterShader.h create mode 100644 gfx/skia/skia/src/shaders/SkColorShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkColorShader.h create mode 100644 gfx/skia/skia/src/shaders/SkComposeShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkComposeShader.h create mode 100644 gfx/skia/skia/src/shaders/SkEmptyShader.h create mode 100644 gfx/skia/skia/src/shaders/SkImageShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkImageShader.h create mode 100644 gfx/skia/skia/src/shaders/SkLightingShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkLightingShader.h create mode 100644 gfx/skia/skia/src/shaders/SkLights.cpp create mode 100644 gfx/skia/skia/src/shaders/SkLights.h create mode 100644 gfx/skia/skia/src/shaders/SkLocalMatrixShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkLocalMatrixShader.h create mode 100644 gfx/skia/skia/src/shaders/SkPerlinNoiseShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkPictureShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkPictureShader.h create mode 100644 gfx/skia/skia/src/shaders/SkRTShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkRTShader.h create mode 100644 gfx/skia/skia/src/shaders/SkShader.cpp create mode 100644 gfx/skia/skia/src/shaders/SkShaderBase.h create mode 100644 gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.h create mode 100644 gfx/skia/skia/src/shaders/gradients/Sk4fGradientPriv.h create mode 100644 gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.h create mode 100644 gfx/skia/skia/src/shaders/gradients/SkGradientShader.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/SkGradientShaderPriv.h create mode 100644 gfx/skia/skia/src/shaders/gradients/SkLinearGradient.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/SkLinearGradient.h create mode 100644 gfx/skia/skia/src/shaders/gradients/SkRadialGradient.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/SkRadialGradient.h create mode 100644 gfx/skia/skia/src/shaders/gradients/SkSweepGradient.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/SkSweepGradient.h create mode 100644 gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.cpp create mode 100644 gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.h create mode 100644 gfx/skia/skia/src/sksl/GLSL.std.450.h create mode 100644 gfx/skia/skia/src/sksl/README create mode 100644 gfx/skia/skia/src/sksl/SkSLASTFile.h create mode 100644 gfx/skia/skia/src/sksl/SkSLASTNode.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLASTNode.h create mode 100644 gfx/skia/skia/src/sksl/SkSLByteCode.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLByteCode.h create mode 100644 gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLCFGGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLCFGGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLCPP.h create mode 100644 gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.h create mode 100644 gfx/skia/skia/src/sksl/SkSLCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLCompiler.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLCompiler.h create mode 100644 gfx/skia/skia/src/sksl/SkSLContext.h create mode 100644 gfx/skia/skia/src/sksl/SkSLDefines.h create mode 100644 gfx/skia/skia/src/sksl/SkSLErrorReporter.h create mode 100644 gfx/skia/skia/src/sksl/SkSLExternalValue.h create mode 100644 gfx/skia/skia/src/sksl/SkSLFileOutputStream.h create mode 100644 gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLHCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLHCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLIRGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLIRGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLLexer.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLLexer.h create mode 100644 gfx/skia/skia/src/sksl/SkSLMain.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLMemoryLayout.h create mode 100644 gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLOutputStream.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLOutputStream.h create mode 100644 gfx/skia/skia/src/sksl/SkSLParser.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLParser.h create mode 100644 gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLPosition.h create mode 100644 gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.h create mode 100644 gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.h create mode 100644 gfx/skia/skia/src/sksl/SkSLString.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLString.h create mode 100644 gfx/skia/skia/src/sksl/SkSLStringStream.h create mode 100644 gfx/skia/skia/src/sksl/SkSLUtil.cpp create mode 100644 gfx/skia/skia/src/sksl/SkSLUtil.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLBlock.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLBoolLiteral.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLConstructor.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLEnum.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLExpression.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLExtension.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLExternalFunctionCall.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLExternalValueReference.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLField.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLFloatLiteral.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLForStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLIRNode.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLIntLiteral.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLLayout.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLModifiers.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLNop.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLNullLiteral.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLProgram.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLProgramElement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSection.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSetting.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSymbol.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLType.cpp create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLType.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLUnresolvedFunction.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLVarDeclarationsStatement.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLVariable.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h create mode 100644 gfx/skia/skia/src/sksl/ir/SkSLWhileStatement.h create mode 100644 gfx/skia/skia/src/sksl/lex/DFA.h create mode 100644 gfx/skia/skia/src/sksl/lex/DFAState.h create mode 100644 gfx/skia/skia/src/sksl/lex/LexUtil.h create mode 100644 gfx/skia/skia/src/sksl/lex/Main.cpp create mode 100644 gfx/skia/skia/src/sksl/lex/NFA.cpp create mode 100644 gfx/skia/skia/src/sksl/lex/NFA.h create mode 100644 gfx/skia/skia/src/sksl/lex/NFAState.h create mode 100644 gfx/skia/skia/src/sksl/lex/NFAtoDFA.h create mode 100644 gfx/skia/skia/src/sksl/lex/RegexNode.cpp create mode 100644 gfx/skia/skia/src/sksl/lex/RegexNode.h create mode 100644 gfx/skia/skia/src/sksl/lex/RegexParser.cpp create mode 100644 gfx/skia/skia/src/sksl/lex/RegexParser.h create mode 100644 gfx/skia/skia/src/sksl/lex/sksl.lex create mode 100644 gfx/skia/skia/src/sksl/sksl_enums.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_fp.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_frag.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_geom.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_gpu.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_interp.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_pipeline.inc create mode 100644 gfx/skia/skia/src/sksl/sksl_vert.inc create mode 100644 gfx/skia/skia/src/sksl/spirv.h create mode 100644 gfx/skia/skia/src/svg/SkSVGCanvas.cpp create mode 100644 gfx/skia/skia/src/svg/SkSVGDevice.cpp create mode 100644 gfx/skia/skia/src/svg/SkSVGDevice.h create mode 100644 gfx/skia/skia/src/utils/Sk3D.cpp create mode 100644 gfx/skia/skia/src/utils/SkAnimCodecPlayer.cpp create mode 100644 gfx/skia/skia/src/utils/SkBase64.cpp create mode 100644 gfx/skia/skia/src/utils/SkBitSet.h create mode 100644 gfx/skia/skia/src/utils/SkCallableTraits.h create mode 100644 gfx/skia/skia/src/utils/SkCamera.cpp create mode 100644 gfx/skia/skia/src/utils/SkCanvasStack.cpp create mode 100644 gfx/skia/skia/src/utils/SkCanvasStack.h create mode 100644 gfx/skia/skia/src/utils/SkCanvasStateUtils.cpp create mode 100644 gfx/skia/skia/src/utils/SkCharToGlyphCache.cpp create mode 100644 gfx/skia/skia/src/utils/SkCharToGlyphCache.h create mode 100644 gfx/skia/skia/src/utils/SkDashPath.cpp create mode 100644 gfx/skia/skia/src/utils/SkDashPathPriv.h create mode 100644 gfx/skia/skia/src/utils/SkEventTracer.cpp create mode 100644 gfx/skia/skia/src/utils/SkFloatToDecimal.cpp create mode 100644 gfx/skia/skia/src/utils/SkFloatToDecimal.h create mode 100644 gfx/skia/skia/src/utils/SkFloatUtils.h create mode 100644 gfx/skia/skia/src/utils/SkFrontBufferedStream.cpp create mode 100644 gfx/skia/skia/src/utils/SkInterpolator.cpp create mode 100644 gfx/skia/skia/src/utils/SkJSON.cpp create mode 100644 gfx/skia/skia/src/utils/SkJSON.h create mode 100644 gfx/skia/skia/src/utils/SkJSONWriter.cpp create mode 100644 gfx/skia/skia/src/utils/SkJSONWriter.h create mode 100644 gfx/skia/skia/src/utils/SkLua.cpp create mode 100644 gfx/skia/skia/src/utils/SkLuaCanvas.cpp create mode 100644 gfx/skia/skia/src/utils/SkMatrix22.cpp create mode 100644 gfx/skia/skia/src/utils/SkMatrix22.h create mode 100644 gfx/skia/skia/src/utils/SkMultiPictureDocument.cpp create mode 100644 gfx/skia/skia/src/utils/SkMultiPictureDocument.h create mode 100644 gfx/skia/skia/src/utils/SkMultiPictureDocumentPriv.h create mode 100644 gfx/skia/skia/src/utils/SkNWayCanvas.cpp create mode 100644 gfx/skia/skia/src/utils/SkNullCanvas.cpp create mode 100644 gfx/skia/skia/src/utils/SkOSPath.cpp create mode 100644 gfx/skia/skia/src/utils/SkOSPath.h create mode 100644 gfx/skia/skia/src/utils/SkPaintFilterCanvas.cpp create mode 100644 gfx/skia/skia/src/utils/SkParse.cpp create mode 100644 gfx/skia/skia/src/utils/SkParseColor.cpp create mode 100644 gfx/skia/skia/src/utils/SkParsePath.cpp create mode 100644 gfx/skia/skia/src/utils/SkPatchUtils.cpp create mode 100644 gfx/skia/skia/src/utils/SkPatchUtils.h create mode 100644 gfx/skia/skia/src/utils/SkPolyUtils.cpp create mode 100644 gfx/skia/skia/src/utils/SkPolyUtils.h create mode 100644 gfx/skia/skia/src/utils/SkShadowTessellator.cpp create mode 100644 gfx/skia/skia/src/utils/SkShadowTessellator.h create mode 100644 gfx/skia/skia/src/utils/SkShadowUtils.cpp create mode 100644 gfx/skia/skia/src/utils/SkShaperJSONWriter.cpp create mode 100644 gfx/skia/skia/src/utils/SkShaperJSONWriter.h create mode 100644 gfx/skia/skia/src/utils/SkTextUtils.cpp create mode 100644 gfx/skia/skia/src/utils/SkThreadUtils_pthread.cpp create mode 100644 gfx/skia/skia/src/utils/SkThreadUtils_win.cpp create mode 100644 gfx/skia/skia/src/utils/SkUTF.cpp create mode 100644 gfx/skia/skia/src/utils/SkUTF.h create mode 100644 gfx/skia/skia/src/utils/SkWhitelistChecksums.inc create mode 100644 gfx/skia/skia/src/utils/SkWhitelistTypefaces.cpp create mode 100644 gfx/skia/skia/src/utils/mac/SkCreateCGImageRef.cpp create mode 100644 gfx/skia/skia/src/utils/mac/SkStream_mac.cpp create mode 100644 gfx/skia/skia/src/utils/mac/SkUniqueCFRef.h create mode 100644 gfx/skia/skia/src/utils/win/SkAutoCoInitialize.cpp create mode 100644 gfx/skia/skia/src/utils/win/SkAutoCoInitialize.h create mode 100644 gfx/skia/skia/src/utils/win/SkDWrite.cpp create mode 100644 gfx/skia/skia/src/utils/win/SkDWrite.h create mode 100644 gfx/skia/skia/src/utils/win/SkDWriteFontFileStream.cpp create mode 100644 gfx/skia/skia/src/utils/win/SkDWriteFontFileStream.h create mode 100644 gfx/skia/skia/src/utils/win/SkDWriteGeometrySink.cpp create mode 100644 gfx/skia/skia/src/utils/win/SkDWriteGeometrySink.h create mode 100644 gfx/skia/skia/src/utils/win/SkDWriteNTDDI_VERSION.h create mode 100644 gfx/skia/skia/src/utils/win/SkHRESULT.cpp create mode 100644 gfx/skia/skia/src/utils/win/SkHRESULT.h create mode 100644 gfx/skia/skia/src/utils/win/SkIStream.cpp create mode 100644 gfx/skia/skia/src/utils/win/SkIStream.h create mode 100644 gfx/skia/skia/src/utils/win/SkObjBase.h create mode 100644 gfx/skia/skia/src/utils/win/SkTScopedComPtr.h create mode 100644 gfx/skia/skia/src/utils/win/SkWGL.h create mode 100644 gfx/skia/skia/src/utils/win/SkWGL_win.cpp create mode 100644 gfx/skia/skia/src/xml/SkDOM.cpp create mode 100644 gfx/skia/skia/src/xml/SkDOM.h create mode 100644 gfx/skia/skia/src/xml/SkXMLParser.cpp create mode 100644 gfx/skia/skia/src/xml/SkXMLParser.h create mode 100644 gfx/skia/skia/src/xml/SkXMLWriter.cpp create mode 100644 gfx/skia/skia/src/xml/SkXMLWriter.h create mode 100644 gfx/skia/skia/src/xps/SkXPSDevice.cpp create mode 100644 gfx/skia/skia/src/xps/SkXPSDevice.h create mode 100644 gfx/skia/skia/src/xps/SkXPSDocument.cpp create mode 100644 gfx/skia/skia/third_party/skcms/LICENSE create mode 100644 gfx/skia/skia/third_party/skcms/README.chromium create mode 100644 gfx/skia/skia/third_party/skcms/skcms.cc create mode 100644 gfx/skia/skia/third_party/skcms/skcms.gni create mode 100644 gfx/skia/skia/third_party/skcms/skcms_internal.h create mode 100644 gfx/skia/skia/third_party/skcms/src/Transform_inl.h create mode 100755 gfx/skia/skia/third_party/skcms/version.sha1 (limited to 'gfx/skia') diff --git a/gfx/skia/LICENSE b/gfx/skia/LICENSE new file mode 100644 index 0000000000..e74c256cba --- /dev/null +++ b/gfx/skia/LICENSE @@ -0,0 +1,27 @@ +// Copyright (c) 2011 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/gfx/skia/README b/gfx/skia/README new file mode 100644 index 0000000000..84e4ecc907 --- /dev/null +++ b/gfx/skia/README @@ -0,0 +1,3 @@ +Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. + +See full details, and build instructions, at http://code.google.com/p/skia/wiki/DocRoot diff --git a/gfx/skia/README_COMMITTING b/gfx/skia/README_COMMITTING new file mode 100644 index 0000000000..4014ea3c7f --- /dev/null +++ b/gfx/skia/README_COMMITTING @@ -0,0 +1,10 @@ +Any changes to Skia should have at a minimum both a Mozilla bug tagged with the [skia-upstream] +whiteboard tag, and also an upstream bug and review request. Any patches that do ultimately land +in mozilla-central must be reviewed by a Skia submodule peer. + +See https://wiki.mozilla.org/Modules/Core#Graphics for current peers. + +In most cases the patch will need to have an r+ from upstream before it is eligible to land here. + +For information on submitting upstream, see: +https://sites.google.com/site/skiadocs/developer-documentation/contributing-code/how-to-submit-a-patch diff --git a/gfx/skia/README_MOZILLA b/gfx/skia/README_MOZILLA new file mode 100644 index 0000000000..af7a003743 --- /dev/null +++ b/gfx/skia/README_MOZILLA @@ -0,0 +1,33 @@ +This is an import of Skia. See skia/include/core/SkMilestone.h for the milestone number. + +Current upstream hash: 4655534302e6a3601c77eae70cc65b202609ab66 (milestone 79) + +How to update +============= + +To update to a new version of Skia: + +- Clone Skia from upstream using the instructions here: https://skia.org/user/download + Usually: `git clone https://skia.googlesource.com/skia` +- Checkout the wanted revision (`git checkout -b `). See below for the current + hash. +- Copy the entire source tree from a Skia clone to mozilla-central/gfx/skia/skia + (make sure that . files are also copied as .gn is mandatory). +- Download gn: cd gfx/skia/skia/bin && python fetch-gn && cd - + Note that these scripts might need Python 2.7. Please use a virtualenv if needed. +- cd gfx/skia && ./generate_mozbuild.py + +Once that's done, use git status to view the files that have changed. Keep an eye on GrUserConfig.h +and SkUserConfig.h as those probably don't want to be overwritten by upstream versions. + +This process will be made more automatic in the future. + +Debug +===== + +In case of issues when updating, run the command manually. +For example, if the following error occurs: +`subprocess.CalledProcessError: Command 'cd skia && bin/gn gen out/linux --args='target_os="linux" ' > /dev/null && bin/gn desc out/linux :skia sources' returned non-zero exit status 1` +Run: +`cd skia && bin/gn gen out/linux --args='target_os="linux"'` +and look at the errors. diff --git a/gfx/skia/generate_mozbuild.py b/gfx/skia/generate_mozbuild.py new file mode 100755 index 0000000000..20e2929733 --- /dev/null +++ b/gfx/skia/generate_mozbuild.py @@ -0,0 +1,423 @@ +#!/usr/bin/env python + +import locale +import subprocess +from collections import defaultdict +locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + +header = """ +# +# ##### ####### # # # # # # +# ## # # # # # # # # # # # # +# ## # # # # # # # # # # # +# ## #### # # # # # # # # # # +# ## # # # ####### # # # ####### # ### +# ## # # # # # # # # # # # ### +# # ##### ####### # # ## ## # # # ### +# +# Seriously. You shouldn't even be looking at this file unless you're +# debugging generate_mozbuild.py. +# +# DO NOT MODIFY THIS FILE IT IS AUTOGENERATED. +# + +skia_opt_flags = [] + +if CONFIG['MOZ_OPTIMIZE']: + if CONFIG['CC_TYPE'] == 'clang-cl': + skia_opt_flags += ['-O2'] + elif CONFIG['CC_TYPE'] in ('clang', 'gcc'): + skia_opt_flags += ['-O3'] + +""" + +footer = """ + +# We allow warnings for third-party code that can be updated from upstream. +AllowCompilerWarnings() + +FINAL_LIBRARY = 'gkmedias' +LOCAL_INCLUDES += [ + 'skia', + 'skia/include/third_party/skcms', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + UNIFIED_SOURCES += [ + 'skia/src/fonts/SkFontMgr_indirect.cpp', + 'skia/src/fonts/SkRemotableFontMgr.cpp', + ] + +# We should autogenerate these SSE related flags. + +if CONFIG['INTEL_ARCHITECTURE']: + SOURCES['skia/src/opts/SkOpts_ssse3.cpp'].flags += ['-mssse3'] + SOURCES['skia/src/opts/SkOpts_sse41.cpp'].flags += ['-msse4.1'] + SOURCES['skia/src/opts/SkOpts_sse42.cpp'].flags += ['-msse4.2'] + SOURCES['skia/src/opts/SkOpts_avx.cpp'].flags += ['-mavx'] + SOURCES['skia/src/opts/SkOpts_hsw.cpp'].flags += ['-mavx2', '-mf16c', '-mfma'] +elif CONFIG['CPU_ARCH'] == 'arm' and CONFIG['CC_TYPE'] in ('clang', 'gcc'): + CXXFLAGS += CONFIG['NEON_FLAGS'] +elif CONFIG['CPU_ARCH'] == 'aarch64' and CONFIG['CC_TYPE'] in ('clang', 'gcc'): + SOURCES['skia/src/opts/SkOpts_crc32.cpp'].flags += ['-march=armv8-a+crc'] + +DEFINES['SKIA_IMPLEMENTATION'] = 1 + +DEFINES['SK_PDF_USE_HARFBUZZ_SUBSETTING'] = 1 + +if CONFIG['MOZ_TREE_FREETYPE']: + DEFINES['SK_CAN_USE_DLOPEN'] = 0 + +# Suppress warnings in third-party code. +CXXFLAGS += [ + '-Wno-deprecated-declarations', + '-Wno-overloaded-virtual', + '-Wno-sign-compare', + '-Wno-unreachable-code', + '-Wno-unused-function', +] +if CONFIG['CC_TYPE'] == 'gcc': + CXXFLAGS += [ + '-Wno-logical-op', + '-Wno-maybe-uninitialized', + ] +if CONFIG['CC_TYPE'] in ('clang', 'clang-cl'): + CXXFLAGS += [ + '-Wno-implicit-fallthrough', + '-Wno-inconsistent-missing-override', + '-Wno-macro-redefined', + '-Wno-unused-private-field', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk', 'android'): + LOCAL_INCLUDES += [ + "/gfx/cairo/cairo/src", + ] + CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': + CXXFLAGS += CONFIG['MOZ_PANGO_CFLAGS'] +""" + +import json + +platforms = ['linux', 'mac', 'android', 'win'] + +def parse_sources(output): + return set(v.replace('//', 'skia/') for v in output.split() if v.endswith('.cpp') or v.endswith('.S')) + +def generate_opt_sources(): + cpus = [('intel', 'x86', [':sse2', ':ssse3', ':sse41', ':sse42', ':avx', ':hsw']), + ('arm', 'arm', [':armv7']), + ('arm64', 'arm64', [':arm64', ':crc32']), + ('none', 'none', [':none'])] + + opt_sources = {} + for key, cpu, deps in cpus: + subprocess.check_output('cd skia && bin/gn gen out/{0} --args=\'target_cpu="{1}"\''.format(key, cpu), shell=True) + opt_sources[key] = set() + for dep in deps: + try: + output = subprocess.check_output('cd skia && bin/gn desc out/{0} {1} sources'.format(key, dep), shell=True) + if output: + opt_sources[key].update(parse_sources(output)) + except subprocess.CalledProcessError as e: + if e.output.find('source_set') < 0: + raise + + return opt_sources + +def generate_platform_sources(): + sources = {} + platform_args = { + 'win' : 'win_vc="C:/" win_sdk_version="00.0.00000.0" win_toolchain_version="00.00.00000"' + } + for plat in platforms: + args = platform_args.get(plat, '') + output = subprocess.check_output('cd skia && bin/gn gen out/{0} --args=\'target_os="{0}" {1}\' > /dev/null && bin/gn desc out/{0} :skia sources'.format(plat, args), shell=True) + if output: + sources[plat] = parse_sources(output) + + plat_deps = {':fontmgr_win' : 'win', ':fontmgr_win_gdi' : 'win'} + for dep, key in plat_deps.items(): + output = subprocess.check_output('cd skia && bin/gn desc out/{1} {0} sources'.format(dep, key), shell=True) + if output: + sources[key].update(parse_sources(output)) + + deps = {':pdf' : 'pdf'} + for dep, key in deps.items(): + output = subprocess.check_output('cd skia && bin/gn desc out/linux {} sources'.format(dep), shell=True) + if output: + sources[key] = parse_sources(output) + + return dict(sources.items() + generate_opt_sources().items()) + + +def generate_separated_sources(platform_sources): + ignorelist = [ + 'skia/src/android/', + 'skia/src/atlastext/', + 'skia/src/c/', + 'skia/src/effects/', + 'skia/src/fonts/', + 'skia/src/ports/SkImageEncoder', + 'skia/src/ports/SkImageGenerator', + 'SkBitmapRegion', + 'SkLite', + 'SkLight', + 'SkNormal', + 'codec', + 'SkWGL', + 'SkMemory_malloc', + 'third_party', + 'Sk3D', + 'SkAnimCodecPlayer', + 'SkCamera', + 'SkCanvasStack', + 'SkCanvasStateUtils', + 'SkFrontBufferedStream', + 'SkInterpolator', + 'JSON', + 'SkMultiPictureDocument', + 'SkNullCanvas', + 'SkNWayCanvas', + 'SkOverdrawCanvas', + 'SkPaintFilterCanvas', + 'SkParseColor', + 'SkWhitelistTypefaces', + 'SkXPS', + 'SkCreateCGImageRef', + 'skia/src/ports/SkGlobalInitialization', + 'skia/src/sksl/SkSLJIT', + ] + + def isignorelisted(value): + for item in ignorelist: + if value.find(item) >= 0: + return True + + return False + + separated = defaultdict(set, { + 'common': { + 'skia/src/codec/SkMasks.cpp', + 'skia/src/effects/imagefilters/SkBlurImageFilter.cpp', + 'skia/src/effects/imagefilters/SkComposeImageFilter.cpp', + 'skia/src/effects/SkDashPathEffect.cpp', + 'skia/src/ports/SkDiscardableMemory_none.cpp', + 'skia/src/ports/SkGlobalInitialization_default.cpp', + 'skia/src/ports/SkMemory_mozalloc.cpp', + 'skia/src/ports/SkImageGenerator_none.cpp', + 'skia/third_party/skcms/skcms.cc', + 'skia/src/core/SkBitmapScaler.cpp', + 'skia/src/core/SkGlyphBuffer.cpp', + 'skia/src/core/SkConvolver.cpp', + 'skia/src/core/SkImageFilterTypes.cpp', + }, + 'android': { + # 'skia/src/ports/SkDebug_android.cpp', + 'skia/src/ports/SkFontHost_cairo.cpp', + # 'skia/src/ports/SkFontHost_FreeType.cpp', + 'skia/src/ports/SkFontHost_FreeType_common.cpp', + # 'skia/src/ports/SkTime_Unix.cpp', + # 'skia/src/utils/SkThreadUtils_pthread.cpp', + }, + 'linux': { + 'skia/src/ports/SkFontHost_cairo.cpp', + 'skia/src/ports/SkFontHost_FreeType_common.cpp', + }, + 'win': set (), + 'intel': set(), + 'arm': set(), + 'arm64': set(), + 'none': set(), + 'pdf': set() + }) + + for plat in platform_sources.keys(): + for value in platform_sources[plat]: + if isignorelisted(value): + continue + + if value in separated['common']: + continue + + key = plat + + if all(value in platform_sources.get(p, {}) + for p in platforms if p != plat): + key = 'common' + + separated[key].add(value) + + return separated + +def uniq(seq): + seen = set() + seen_add = seen.add + return [ x for x in seq if x not in seen and not seen_add(x)] + +def write_cflags(f, values, subsearch, cflag, indent): + def write_indent(indent): + for _ in range(indent): + f.write(' ') + + if isinstance(subsearch, str): + subsearch = [ subsearch ] + + def isallowlisted(value): + for item in subsearch: + if value.find(item) >= 0: + return True + + return False + + val_list = uniq(sorted(values, key=lambda x: x.lower())) + + if len(val_list) == 0: + return + + for val in val_list: + if isallowlisted(val): + write_indent(indent) + f.write("SOURCES[\'" + val + "\'].flags += " + cflag + "\n") + +opt_allowlist = [ + 'SkOpts', + 'SkBitmapProcState', + 'SkBitmapScaler', + 'SkBlitRow', + 'SkBlitter', + 'SkSpriteBlitter', + 'SkMatrix.cpp', + 'skcms', +] + +# Unfortunately for now the gpu and pathops directories are +# non-unifiable. Keep track of this and fix it. +unified_ignorelist = [ + 'FontHost', + 'SkBitmapProcState_matrixProcs.cpp', + 'SkBlitter_A8.cpp', + 'SkBlitter_ARGB32.cpp', + 'SkBlitter_RGB16.cpp', + 'SkBlitter_Sprite.cpp', + 'SkScan_Antihair.cpp', + 'SkScan_AntiPath.cpp', + 'SkScan_DAAPath.cpp', + 'SkParse.cpp', + 'SkPDFFont.cpp', + 'SkPDFDevice.cpp', + 'SkPDFType1Font.cpp', + 'SkPictureData.cpp', + 'SkColorSpace', + 'SkPathOpsDebug.cpp', + 'SkParsePath.cpp', + 'SkRecorder.cpp', + 'SkMiniRecorder.cpp', + 'SkXfermode', + 'SkMatrix44.cpp', + 'SkRTree.cpp', + 'SkVertices.cpp', + 'SkSLHCodeGenerator.cpp', + 'SkSLLexer.cpp', +] + opt_allowlist + +def write_sources(f, values, indent): + def isignorelisted(value): + for item in unified_ignorelist: + if value.find(item) >= 0: + return True + + return False + + sources = {} + sources['nonunified'] = set() + sources['unified'] = set() + + for item in values: + if isignorelisted(item): + sources['nonunified'].add(item) + else: + sources['unified'].add(item) + + write_list(f, "UNIFIED_SOURCES", sources['unified'], indent) + write_list(f, "SOURCES", sources['nonunified'], indent) + +def write_list(f, name, values, indent): + def write_indent(indent): + for _ in range(indent): + f.write(' ') + + val_list = uniq(sorted(values, key=lambda x: x.lower())) + + if len(val_list) == 0: + return + + write_indent(indent) + f.write(name + ' += [\n') + for val in val_list: + write_indent(indent + 4) + f.write('\'' + val + '\',\n') + + write_indent(indent) + f.write(']\n') + +def write_mozbuild(sources): + filename = 'moz.build' + f = open(filename, 'w') + + f.write(header) + + write_sources(f, sources['common'], 0) + write_cflags(f, sources['common'], opt_allowlist, 'skia_opt_flags', 0) + + f.write("if CONFIG['MOZ_ENABLE_SKIA_PDF']:\n") + write_sources(f, sources['pdf'], 4) + + f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':\n") + write_sources(f, sources['android'], 4) + + f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):\n") + write_sources(f, sources['mac'], 4) + + f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk':\n") + write_sources(f, sources['linux'], 4) + + f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':\n") + write_list(f, "SOURCES", sources['win'], 4) + + f.write("if CONFIG['INTEL_ARCHITECTURE']:\n") + write_sources(f, sources['intel'], 4) + write_cflags(f, sources['intel'], opt_allowlist, 'skia_opt_flags', 4) + + if sources['arm']: + f.write("elif CONFIG['CPU_ARCH'] == 'arm' and CONFIG['CC_TYPE'] in ('clang', 'gcc'):\n") + write_sources(f, sources['arm'], 4) + write_cflags(f, sources['arm'], opt_allowlist, 'skia_opt_flags', 4) + + if sources['arm64']: + f.write("elif CONFIG['CPU_ARCH'] == 'aarch64':\n") + write_sources(f, sources['arm64'], 4) + write_cflags(f, sources['arm64'], opt_allowlist, 'skia_opt_flags', 4) + + if sources['none']: + f.write("else:\n") + write_sources(f, sources['none'], 4) + + f.write(footer) + + f.close() + + print('Wrote ' + filename) + +def main(): + platform_sources = generate_platform_sources() + separated_sources = generate_separated_sources(platform_sources) + write_mozbuild(separated_sources) + + +if __name__ == '__main__': + main() diff --git a/gfx/skia/moz.build b/gfx/skia/moz.build new file mode 100644 index 0000000000..52966211bd --- /dev/null +++ b/gfx/skia/moz.build @@ -0,0 +1,497 @@ + +# +# ##### ####### # # # # # # +# ## # # # # # # # # # # # # +# ## # # # # # # # # # # # +# ## #### # # # # # # # # # # +# ## # # # ####### # # # ####### # ### +# ## # # # # # # # # # # # ### +# # ##### ####### # # ## ## # # # ### +# +# Seriously. You shouldn't even be looking at this file unless you're +# debugging generate_mozbuild.py. +# +# DO NOT MODIFY THIS FILE IT IS AUTOGENERATED. +# + +skia_opt_flags = [] + +if CONFIG['MOZ_OPTIMIZE']: + if CONFIG['CC_TYPE'] == 'clang-cl': + skia_opt_flags += ['-O2'] + elif CONFIG['CC_TYPE'] in ('clang', 'gcc'): + skia_opt_flags += ['-O3'] + +UNIFIED_SOURCES += [ + 'skia/src/codec/SkMasks.cpp', + 'skia/src/core/SkAAClip.cpp', + 'skia/src/core/SkAlphaRuns.cpp', + 'skia/src/core/SkAnalyticEdge.cpp', + 'skia/src/core/SkAnnotation.cpp', + 'skia/src/core/SkArenaAlloc.cpp', + 'skia/src/core/SkATrace.cpp', + 'skia/src/core/SkAutoPixmapStorage.cpp', + 'skia/src/core/SkBBHFactory.cpp', + 'skia/src/core/SkBigPicture.cpp', + 'skia/src/core/SkBitmap.cpp', + 'skia/src/core/SkBitmapCache.cpp', + 'skia/src/core/SkBitmapController.cpp', + 'skia/src/core/SkBitmapDevice.cpp', + 'skia/src/core/SkBlendMode.cpp', + 'skia/src/core/SkBlurMask.cpp', + 'skia/src/core/SkBlurMF.cpp', + 'skia/src/core/SkBuffer.cpp', + 'skia/src/core/SkCachedData.cpp', + 'skia/src/core/SkCanvas.cpp', + 'skia/src/core/SkCanvasPriv.cpp', + 'skia/src/core/SkClipStack.cpp', + 'skia/src/core/SkClipStackDevice.cpp', + 'skia/src/core/SkColor.cpp', + 'skia/src/core/SkColorFilter.cpp', + 'skia/src/core/SkColorFilter_Matrix.cpp', + 'skia/src/core/SkContourMeasure.cpp', + 'skia/src/core/SkConvertPixels.cpp', + 'skia/src/core/SkConvolver.cpp', + 'skia/src/core/SkCpu.cpp', + 'skia/src/core/SkCubicClipper.cpp', + 'skia/src/core/SkCubicMap.cpp', + 'skia/src/core/SkData.cpp', + 'skia/src/core/SkDataTable.cpp', + 'skia/src/core/SkDebug.cpp', + 'skia/src/core/SkDeferredDisplayList.cpp', + 'skia/src/core/SkDeferredDisplayListRecorder.cpp', + 'skia/src/core/SkDeque.cpp', + 'skia/src/core/SkDescriptor.cpp', + 'skia/src/core/SkDevice.cpp', + 'skia/src/core/SkDistanceFieldGen.cpp', + 'skia/src/core/SkDocument.cpp', + 'skia/src/core/SkDraw.cpp', + 'skia/src/core/SkDraw_atlas.cpp', + 'skia/src/core/SkDraw_text.cpp', + 'skia/src/core/SkDraw_vertices.cpp', + 'skia/src/core/SkDrawable.cpp', + 'skia/src/core/SkDrawLooper.cpp', + 'skia/src/core/SkDrawShadowInfo.cpp', + 'skia/src/core/SkEdge.cpp', + 'skia/src/core/SkEdgeBuilder.cpp', + 'skia/src/core/SkEdgeClipper.cpp', + 'skia/src/core/SkExecutor.cpp', + 'skia/src/core/SkFlattenable.cpp', + 'skia/src/core/SkFont.cpp', + 'skia/src/core/SkFontDescriptor.cpp', + 'skia/src/core/SkFontLCDConfig.cpp', + 'skia/src/core/SkFontMgr.cpp', + 'skia/src/core/SkFontStream.cpp', + 'skia/src/core/SkGaussFilter.cpp', + 'skia/src/core/SkGeometry.cpp', + 'skia/src/core/SkGlobalInitialization_core.cpp', + 'skia/src/core/SkGlyph.cpp', + 'skia/src/core/SkGlyphBuffer.cpp', + 'skia/src/core/SkGlyphRun.cpp', + 'skia/src/core/SkGlyphRunPainter.cpp', + 'skia/src/core/SkGpuBlurUtils.cpp', + 'skia/src/core/SkGraphics.cpp', + 'skia/src/core/SkHalf.cpp', + 'skia/src/core/SkICC.cpp', + 'skia/src/core/SkImageFilter.cpp', + 'skia/src/core/SkImageFilterCache.cpp', + 'skia/src/core/SkImageFilterTypes.cpp', + 'skia/src/core/SkImageGenerator.cpp', + 'skia/src/core/SkImageInfo.cpp', + 'skia/src/core/SkLatticeIter.cpp', + 'skia/src/core/SkLineClipper.cpp', + 'skia/src/core/SkLocalMatrixImageFilter.cpp', + 'skia/src/core/SkMallocPixelRef.cpp', + 'skia/src/core/SkMask.cpp', + 'skia/src/core/SkMaskBlurFilter.cpp', + 'skia/src/core/SkMaskCache.cpp', + 'skia/src/core/SkMaskFilter.cpp', + 'skia/src/core/SkMaskGamma.cpp', + 'skia/src/core/SkMath.cpp', + 'skia/src/core/SkMatrixImageFilter.cpp', + 'skia/src/core/SkMD5.cpp', + 'skia/src/core/SkMipMap.cpp', + 'skia/src/core/SkModeColorFilter.cpp', + 'skia/src/core/SkMultiPictureDraw.cpp', + 'skia/src/core/SkPaint.cpp', + 'skia/src/core/SkPaintPriv.cpp', + 'skia/src/core/SkPath.cpp', + 'skia/src/core/SkPath_serial.cpp', + 'skia/src/core/SkPathEffect.cpp', + 'skia/src/core/SkPathMeasure.cpp', + 'skia/src/core/SkPathRef.cpp', + 'skia/src/core/SkPicture.cpp', + 'skia/src/core/SkPictureFlat.cpp', + 'skia/src/core/SkPictureImageGenerator.cpp', + 'skia/src/core/SkPicturePlayback.cpp', + 'skia/src/core/SkPictureRecord.cpp', + 'skia/src/core/SkPictureRecorder.cpp', + 'skia/src/core/SkPixelRef.cpp', + 'skia/src/core/SkPixmap.cpp', + 'skia/src/core/SkPoint.cpp', + 'skia/src/core/SkPoint3.cpp', + 'skia/src/core/SkPromiseImageTexture.cpp', + 'skia/src/core/SkPtrRecorder.cpp', + 'skia/src/core/SkQuadClipper.cpp', + 'skia/src/core/SkRasterClip.cpp', + 'skia/src/core/SkRasterPipeline.cpp', + 'skia/src/core/SkRasterPipelineBlitter.cpp', + 'skia/src/core/SkReadBuffer.cpp', + 'skia/src/core/SkRecord.cpp', + 'skia/src/core/SkRecordDraw.cpp', + 'skia/src/core/SkRecordedDrawable.cpp', + 'skia/src/core/SkRecordOpts.cpp', + 'skia/src/core/SkRecords.cpp', + 'skia/src/core/SkRect.cpp', + 'skia/src/core/SkRegion.cpp', + 'skia/src/core/SkRegion_path.cpp', + 'skia/src/core/SkRemoteGlyphCache.cpp', + 'skia/src/core/SkResourceCache.cpp', + 'skia/src/core/SkRRect.cpp', + 'skia/src/core/SkRWBuffer.cpp', + 'skia/src/core/SkScalar.cpp', + 'skia/src/core/SkScalerContext.cpp', + 'skia/src/core/SkScan.cpp', + 'skia/src/core/SkScan_AAAPath.cpp', + 'skia/src/core/SkScan_Hairline.cpp', + 'skia/src/core/SkScan_Path.cpp', + 'skia/src/core/SkSemaphore.cpp', + 'skia/src/core/SkSharedMutex.cpp', + 'skia/src/core/SkSpecialImage.cpp', + 'skia/src/core/SkSpecialSurface.cpp', + 'skia/src/core/SkSpinlock.cpp', + 'skia/src/core/SkStream.cpp', + 'skia/src/core/SkStrike.cpp', + 'skia/src/core/SkStrikeCache.cpp', + 'skia/src/core/SkStrikeSpec.cpp', + 'skia/src/core/SkString.cpp', + 'skia/src/core/SkStringUtils.cpp', + 'skia/src/core/SkStroke.cpp', + 'skia/src/core/SkStrokeRec.cpp', + 'skia/src/core/SkStrokerPriv.cpp', + 'skia/src/core/SkSurfaceCharacterization.cpp', + 'skia/src/core/SkSwizzle.cpp', + 'skia/src/core/SkTaskGroup.cpp', + 'skia/src/core/SkTextBlob.cpp', + 'skia/src/core/SkThreadID.cpp', + 'skia/src/core/SkTime.cpp', + 'skia/src/core/SkTLS.cpp', + 'skia/src/core/SkTSearch.cpp', + 'skia/src/core/SkTypeface.cpp', + 'skia/src/core/SkTypeface_remote.cpp', + 'skia/src/core/SkTypefaceCache.cpp', + 'skia/src/core/SkUnPreMultiply.cpp', + 'skia/src/core/SkUtils.cpp', + 'skia/src/core/SkVertState.cpp', + 'skia/src/core/SkVM.cpp', + 'skia/src/core/SkVMBlitter.cpp', + 'skia/src/core/SkWriteBuffer.cpp', + 'skia/src/core/SkWriter32.cpp', + 'skia/src/core/SkYUVASizeInfo.cpp', + 'skia/src/core/SkYUVMath.cpp', + 'skia/src/core/SkYUVPlanesCache.cpp', + 'skia/src/effects/imagefilters/SkBlurImageFilter.cpp', + 'skia/src/effects/imagefilters/SkComposeImageFilter.cpp', + 'skia/src/effects/SkDashPathEffect.cpp', + 'skia/src/image/SkImage.cpp', + 'skia/src/image/SkImage_Lazy.cpp', + 'skia/src/image/SkImage_Raster.cpp', + 'skia/src/image/SkSurface.cpp', + 'skia/src/image/SkSurface_Raster.cpp', + 'skia/src/images/SkImageEncoder.cpp', + 'skia/src/lazy/SkDiscardableMemoryPool.cpp', + 'skia/src/pathops/SkAddIntersections.cpp', + 'skia/src/pathops/SkDConicLineIntersection.cpp', + 'skia/src/pathops/SkDCubicLineIntersection.cpp', + 'skia/src/pathops/SkDCubicToQuads.cpp', + 'skia/src/pathops/SkDLineIntersection.cpp', + 'skia/src/pathops/SkDQuadLineIntersection.cpp', + 'skia/src/pathops/SkIntersections.cpp', + 'skia/src/pathops/SkOpAngle.cpp', + 'skia/src/pathops/SkOpBuilder.cpp', + 'skia/src/pathops/SkOpCoincidence.cpp', + 'skia/src/pathops/SkOpContour.cpp', + 'skia/src/pathops/SkOpCubicHull.cpp', + 'skia/src/pathops/SkOpEdgeBuilder.cpp', + 'skia/src/pathops/SkOpSegment.cpp', + 'skia/src/pathops/SkOpSpan.cpp', + 'skia/src/pathops/SkPathOpsAsWinding.cpp', + 'skia/src/pathops/SkPathOpsCommon.cpp', + 'skia/src/pathops/SkPathOpsConic.cpp', + 'skia/src/pathops/SkPathOpsCubic.cpp', + 'skia/src/pathops/SkPathOpsCurve.cpp', + 'skia/src/pathops/SkPathOpsLine.cpp', + 'skia/src/pathops/SkPathOpsOp.cpp', + 'skia/src/pathops/SkPathOpsQuad.cpp', + 'skia/src/pathops/SkPathOpsRect.cpp', + 'skia/src/pathops/SkPathOpsSimplify.cpp', + 'skia/src/pathops/SkPathOpsTightBounds.cpp', + 'skia/src/pathops/SkPathOpsTSect.cpp', + 'skia/src/pathops/SkPathOpsTypes.cpp', + 'skia/src/pathops/SkPathOpsWinding.cpp', + 'skia/src/pathops/SkPathWriter.cpp', + 'skia/src/pathops/SkReduceOrder.cpp', + 'skia/src/ports/SkDiscardableMemory_none.cpp', + 'skia/src/ports/SkGlobalInitialization_default.cpp', + 'skia/src/ports/SkImageGenerator_none.cpp', + 'skia/src/ports/SkMemory_mozalloc.cpp', + 'skia/src/ports/SkOSFile_stdio.cpp', + 'skia/src/sfnt/SkOTTable_name.cpp', + 'skia/src/sfnt/SkOTUtils.cpp', + 'skia/src/shaders/gradients/Sk4fGradientBase.cpp', + 'skia/src/shaders/gradients/Sk4fLinearGradient.cpp', + 'skia/src/shaders/gradients/SkGradientShader.cpp', + 'skia/src/shaders/gradients/SkLinearGradient.cpp', + 'skia/src/shaders/gradients/SkRadialGradient.cpp', + 'skia/src/shaders/gradients/SkSweepGradient.cpp', + 'skia/src/shaders/gradients/SkTwoPointConicalGradient.cpp', + 'skia/src/shaders/SkBitmapProcShader.cpp', + 'skia/src/shaders/SkColorFilterShader.cpp', + 'skia/src/shaders/SkColorShader.cpp', + 'skia/src/shaders/SkComposeShader.cpp', + 'skia/src/shaders/SkImageShader.cpp', + 'skia/src/shaders/SkLocalMatrixShader.cpp', + 'skia/src/shaders/SkPerlinNoiseShader.cpp', + 'skia/src/shaders/SkPictureShader.cpp', + 'skia/src/shaders/SkRTShader.cpp', + 'skia/src/shaders/SkShader.cpp', + 'skia/src/sksl/ir/SkSLSetting.cpp', + 'skia/src/sksl/ir/SkSLSymbolTable.cpp', + 'skia/src/sksl/ir/SkSLType.cpp', + 'skia/src/sksl/ir/SkSLVariableReference.cpp', + 'skia/src/sksl/SkSLASTNode.cpp', + 'skia/src/sksl/SkSLByteCode.cpp', + 'skia/src/sksl/SkSLByteCodeGenerator.cpp', + 'skia/src/sksl/SkSLCFGGenerator.cpp', + 'skia/src/sksl/SkSLCompiler.cpp', + 'skia/src/sksl/SkSLIRGenerator.cpp', + 'skia/src/sksl/SkSLParser.cpp', + 'skia/src/sksl/SkSLSectionAndParameterHelper.cpp', + 'skia/src/sksl/SkSLString.cpp', + 'skia/src/sksl/SkSLUtil.cpp', + 'skia/src/utils/mac/SkStream_mac.cpp', + 'skia/src/utils/SkBase64.cpp', + 'skia/src/utils/SkCharToGlyphCache.cpp', + 'skia/src/utils/SkDashPath.cpp', + 'skia/src/utils/SkEventTracer.cpp', + 'skia/src/utils/SkFloatToDecimal.cpp', + 'skia/src/utils/SkMatrix22.cpp', + 'skia/src/utils/SkOSPath.cpp', + 'skia/src/utils/SkPatchUtils.cpp', + 'skia/src/utils/SkPolyUtils.cpp', + 'skia/src/utils/SkShadowTessellator.cpp', + 'skia/src/utils/SkShadowUtils.cpp', + 'skia/src/utils/SkTextUtils.cpp', + 'skia/src/utils/SkThreadUtils_pthread.cpp', + 'skia/src/utils/SkThreadUtils_win.cpp', + 'skia/src/utils/SkUTF.cpp', + 'skia/src/utils/win/SkAutoCoInitialize.cpp', + 'skia/src/utils/win/SkDWrite.cpp', + 'skia/src/utils/win/SkDWriteFontFileStream.cpp', + 'skia/src/utils/win/SkDWriteGeometrySink.cpp', + 'skia/src/utils/win/SkHRESULT.cpp', + 'skia/src/utils/win/SkIStream.cpp', +] +SOURCES += [ + 'skia/src/core/SkBitmapProcState.cpp', + 'skia/src/core/SkBitmapProcState_matrixProcs.cpp', + 'skia/src/core/SkBitmapScaler.cpp', + 'skia/src/core/SkBlitRow_D32.cpp', + 'skia/src/core/SkBlitter.cpp', + 'skia/src/core/SkBlitter_A8.cpp', + 'skia/src/core/SkBlitter_ARGB32.cpp', + 'skia/src/core/SkBlitter_RGB565.cpp', + 'skia/src/core/SkBlitter_Sprite.cpp', + 'skia/src/core/SkColorSpace.cpp', + 'skia/src/core/SkColorSpaceXformSteps.cpp', + 'skia/src/core/SkMatrix.cpp', + 'skia/src/core/SkMatrix44.cpp', + 'skia/src/core/SkMiniRecorder.cpp', + 'skia/src/core/SkOpts.cpp', + 'skia/src/core/SkPictureData.cpp', + 'skia/src/core/SkRecorder.cpp', + 'skia/src/core/SkRTree.cpp', + 'skia/src/core/SkScan_Antihair.cpp', + 'skia/src/core/SkScan_AntiPath.cpp', + 'skia/src/core/SkSpriteBlitter_ARGB32.cpp', + 'skia/src/core/SkSpriteBlitter_RGB565.cpp', + 'skia/src/core/SkVertices.cpp', + 'skia/src/core/SkXfermode.cpp', + 'skia/src/core/SkXfermodeInterpretation.cpp', + 'skia/src/pathops/SkPathOpsDebug.cpp', + 'skia/src/sksl/SkSLLexer.cpp', + 'skia/src/utils/SkParse.cpp', + 'skia/src/utils/SkParsePath.cpp', + 'skia/third_party/skcms/skcms.cc', +] +SOURCES['skia/src/core/SkBitmapProcState.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBitmapProcState_matrixProcs.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBitmapScaler.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBlitRow_D32.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBlitter.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBlitter_A8.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBlitter_ARGB32.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBlitter_RGB565.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkBlitter_Sprite.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkMatrix.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkOpts.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkSpriteBlitter_ARGB32.cpp'].flags += skia_opt_flags +SOURCES['skia/src/core/SkSpriteBlitter_RGB565.cpp'].flags += skia_opt_flags +SOURCES['skia/third_party/skcms/skcms.cc'].flags += skia_opt_flags +if CONFIG['MOZ_ENABLE_SKIA_PDF']: + UNIFIED_SOURCES += [ + 'skia/src/pdf/SkClusterator.cpp', + 'skia/src/pdf/SkDeflate.cpp', + 'skia/src/pdf/SkJpegInfo.cpp', + 'skia/src/pdf/SkKeyedImage.cpp', + 'skia/src/pdf/SkPDFBitmap.cpp', + 'skia/src/pdf/SkPDFDocument.cpp', + 'skia/src/pdf/SkPDFFormXObject.cpp', + 'skia/src/pdf/SkPDFGradientShader.cpp', + 'skia/src/pdf/SkPDFGraphicStackState.cpp', + 'skia/src/pdf/SkPDFGraphicState.cpp', + 'skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp', + 'skia/src/pdf/SkPDFMakeToUnicodeCmap.cpp', + 'skia/src/pdf/SkPDFMetadata.cpp', + 'skia/src/pdf/SkPDFResourceDict.cpp', + 'skia/src/pdf/SkPDFShader.cpp', + 'skia/src/pdf/SkPDFSubsetFont.cpp', + 'skia/src/pdf/SkPDFTag.cpp', + 'skia/src/pdf/SkPDFTypes.cpp', + 'skia/src/pdf/SkPDFUtils.cpp', + ] + SOURCES += [ + 'skia/src/pdf/SkPDFDevice.cpp', + 'skia/src/pdf/SkPDFFont.cpp', + 'skia/src/pdf/SkPDFType1Font.cpp', + ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + UNIFIED_SOURCES += [ + 'skia/src/ports/SkDebug_android.cpp', + 'skia/src/ports/SkOSFile_posix.cpp', + 'skia/src/ports/SkOSLibrary_posix.cpp', + 'skia/src/ports/SkTLS_pthread.cpp', + ] + SOURCES += [ + 'skia/src/ports/SkFontHost_cairo.cpp', + 'skia/src/ports/SkFontHost_FreeType_common.cpp', + ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'): + UNIFIED_SOURCES += [ + 'skia/src/ports/SkDebug_stdio.cpp', + 'skia/src/ports/SkOSFile_posix.cpp', + 'skia/src/ports/SkOSLibrary_posix.cpp', + 'skia/src/ports/SkTLS_pthread.cpp', + ] + SOURCES += [ + 'skia/src/ports/SkFontHost_mac.cpp', + ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': + UNIFIED_SOURCES += [ + 'skia/src/ports/SkDebug_stdio.cpp', + 'skia/src/ports/SkOSFile_posix.cpp', + 'skia/src/ports/SkOSLibrary_posix.cpp', + 'skia/src/ports/SkTLS_pthread.cpp', + ] + SOURCES += [ + 'skia/src/ports/SkFontHost_cairo.cpp', + 'skia/src/ports/SkFontHost_FreeType_common.cpp', + ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'skia/src/ports/SkDebug_win.cpp', + 'skia/src/ports/SkFontHost_win.cpp', + 'skia/src/ports/SkFontMgr_win_dw.cpp', + 'skia/src/ports/SkFontMgr_win_dw_factory.cpp', + 'skia/src/ports/SkOSFile_win.cpp', + 'skia/src/ports/SkOSLibrary_win.cpp', + 'skia/src/ports/SkScalerContext_win_dw.cpp', + 'skia/src/ports/SkTLS_win.cpp', + 'skia/src/ports/SkTypeface_win_dw.cpp', + ] +if CONFIG['INTEL_ARCHITECTURE']: + SOURCES += [ + 'skia/src/opts/SkOpts_avx.cpp', + 'skia/src/opts/SkOpts_hsw.cpp', + 'skia/src/opts/SkOpts_sse41.cpp', + 'skia/src/opts/SkOpts_sse42.cpp', + 'skia/src/opts/SkOpts_ssse3.cpp', + ] + SOURCES['skia/src/opts/SkOpts_avx.cpp'].flags += skia_opt_flags + SOURCES['skia/src/opts/SkOpts_hsw.cpp'].flags += skia_opt_flags + SOURCES['skia/src/opts/SkOpts_sse41.cpp'].flags += skia_opt_flags + SOURCES['skia/src/opts/SkOpts_sse42.cpp'].flags += skia_opt_flags + SOURCES['skia/src/opts/SkOpts_ssse3.cpp'].flags += skia_opt_flags +elif CONFIG['CPU_ARCH'] == 'aarch64': + SOURCES += [ + 'skia/src/opts/SkOpts_crc32.cpp', + ] + SOURCES['skia/src/opts/SkOpts_crc32.cpp'].flags += skia_opt_flags + + +# We allow warnings for third-party code that can be updated from upstream. +AllowCompilerWarnings() + +FINAL_LIBRARY = 'gkmedias' +LOCAL_INCLUDES += [ + 'skia', + 'skia/include/third_party/skcms', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + UNIFIED_SOURCES += [ + 'skia/src/fonts/SkFontMgr_indirect.cpp', + 'skia/src/fonts/SkRemotableFontMgr.cpp', + ] + +# We should autogenerate these SSE related flags. + +if CONFIG['INTEL_ARCHITECTURE']: + SOURCES['skia/src/opts/SkOpts_ssse3.cpp'].flags += ['-mssse3'] + SOURCES['skia/src/opts/SkOpts_sse41.cpp'].flags += ['-msse4.1'] + SOURCES['skia/src/opts/SkOpts_sse42.cpp'].flags += ['-msse4.2'] + SOURCES['skia/src/opts/SkOpts_avx.cpp'].flags += ['-mavx'] + SOURCES['skia/src/opts/SkOpts_hsw.cpp'].flags += ['-mavx2', '-mf16c', '-mfma'] +elif CONFIG['CPU_ARCH'] == 'arm' and CONFIG['CC_TYPE'] in ('clang', 'gcc'): + CXXFLAGS += CONFIG['NEON_FLAGS'] +elif CONFIG['CPU_ARCH'] == 'aarch64' and CONFIG['CC_TYPE'] in ('clang', 'gcc'): + SOURCES['skia/src/opts/SkOpts_crc32.cpp'].flags += ['-march=armv8-a+crc'] + +DEFINES['SKIA_IMPLEMENTATION'] = 1 + +DEFINES['SK_PDF_USE_HARFBUZZ_SUBSETTING'] = 1 + +if CONFIG['MOZ_TREE_FREETYPE']: + DEFINES['SK_CAN_USE_DLOPEN'] = 0 + +# Suppress warnings in third-party code. +CXXFLAGS += [ + '-Wno-deprecated-declarations', + '-Wno-overloaded-virtual', + '-Wno-sign-compare', + '-Wno-unreachable-code', + '-Wno-unused-function', +] +if CONFIG['CC_TYPE'] == 'gcc': + CXXFLAGS += [ + '-Wno-logical-op', + '-Wno-maybe-uninitialized', + ] +if CONFIG['CC_TYPE'] in ('clang', 'clang-cl'): + CXXFLAGS += [ + '-Wno-implicit-fallthrough', + '-Wno-inconsistent-missing-override', + '-Wno-macro-redefined', + '-Wno-unused-private-field', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk', 'android'): + LOCAL_INCLUDES += [ + "/gfx/cairo/cairo/src", + ] + CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': + CXXFLAGS += CONFIG['MOZ_PANGO_CFLAGS'] diff --git a/gfx/skia/patches/README b/gfx/skia/patches/README new file mode 100644 index 0000000000..8fd2c5396a --- /dev/null +++ b/gfx/skia/patches/README @@ -0,0 +1,2 @@ +We no longer keep a local patch queue of patches against upstream. The protocol now +is to upstream all patches before they are landed in mozilla-central. diff --git a/gfx/skia/patches/archive/0001-Bug-687189-Implement-SkPaint-getPosTextPath.patch b/gfx/skia/patches/archive/0001-Bug-687189-Implement-SkPaint-getPosTextPath.patch new file mode 100644 index 0000000000..f8e76dbb90 --- /dev/null +++ b/gfx/skia/patches/archive/0001-Bug-687189-Implement-SkPaint-getPosTextPath.patch @@ -0,0 +1,66 @@ +From 27a914815e757ed12523edf968c9da134dabeaf8 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:10:44 -0400 +Subject: [PATCH 01/10] Bug 755869 - [4] Re-apply bug 687189 - Implement + SkPaint::getPosTextPath r=mattwoodrow + +--- + gfx/skia/include/core/SkPaint.h | 3 +++ + gfx/skia/src/core/SkPaint.cpp | 27 +++++++++++++++++++++++++++ + 2 files changed, 30 insertions(+), 0 deletions(-) + +diff --git a/gfx/skia/include/core/SkPaint.h b/gfx/skia/include/core/SkPaint.h +index 1930db1..ff37d77 100644 +--- a/gfx/skia/include/core/SkPaint.h ++++ b/gfx/skia/include/core/SkPaint.h +@@ -813,6 +813,9 @@ public: + void getTextPath(const void* text, size_t length, SkScalar x, SkScalar y, + SkPath* path) const; + ++ void getPosTextPath(const void* text, size_t length, ++ const SkPoint pos[], SkPath* path) const; ++ + #ifdef SK_BUILD_FOR_ANDROID + const SkGlyph& getUnicharMetrics(SkUnichar); + const SkGlyph& getGlyphMetrics(uint16_t); +diff --git a/gfx/skia/src/core/SkPaint.cpp b/gfx/skia/src/core/SkPaint.cpp +index 1b74fa1..4c119aa 100644 +--- a/gfx/skia/src/core/SkPaint.cpp ++++ b/gfx/skia/src/core/SkPaint.cpp +@@ -1355,6 +1355,33 @@ void SkPaint::getTextPath(const void* textData, size_t length, + } + } + ++void SkPaint::getPosTextPath(const void* textData, size_t length, ++ const SkPoint pos[], SkPath* path) const { ++ SkASSERT(length == 0 || textData != NULL); ++ ++ const char* text = (const char*)textData; ++ if (text == NULL || length == 0 || path == NULL) { ++ return; ++ } ++ ++ SkTextToPathIter iter(text, length, *this, false); ++ SkMatrix matrix; ++ SkPoint prevPos; ++ prevPos.set(0, 0); ++ ++ matrix.setScale(iter.getPathScale(), iter.getPathScale()); ++ path->reset(); ++ ++ unsigned int i = 0; ++ const SkPath* iterPath; ++ while ((iterPath = iter.next(NULL)) != NULL) { ++ matrix.postTranslate(pos[i].fX - prevPos.fX, pos[i].fY - prevPos.fY); ++ path->addPath(*iterPath, matrix); ++ prevPos = pos[i]; ++ i++; ++ } ++} ++ + static void add_flattenable(SkDescriptor* desc, uint32_t tag, + SkFlattenableWriteBuffer* buffer) { + buffer->flatten(desc->addEntry(tag, buffer->size(), NULL)); +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0001-Bug-777614-Re-add-our-SkUserConfig.h-r-nrc.patch b/gfx/skia/patches/archive/0001-Bug-777614-Re-add-our-SkUserConfig.h-r-nrc.patch new file mode 100644 index 0000000000..8fe0135fbb --- /dev/null +++ b/gfx/skia/patches/archive/0001-Bug-777614-Re-add-our-SkUserConfig.h-r-nrc.patch @@ -0,0 +1,34 @@ +From 2dd8c789fc4ad3b5323c2c29f3e982d185f5b5d9 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Thu, 13 Sep 2012 22:33:38 -0400 +Subject: [PATCH 1/9] Bug 777614 - Re-add our SkUserConfig.h r=nrc + +--- + gfx/skia/include/config/SkUserConfig.h | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/gfx/skia/include/config/SkUserConfig.h b/gfx/skia/include/config/SkUserConfig.h +index 353272c..fbfbfe0 100644 +--- a/gfx/skia/include/config/SkUserConfig.h ++++ b/gfx/skia/include/config/SkUserConfig.h +@@ -184,5 +184,16 @@ + directories from your include search path when you're not building the GPU + backend. Defaults to 1 (build the GPU code). + */ +-//#define SK_SUPPORT_GPU 1 ++#define SK_SUPPORT_GPU 0 ++ ++/* Don't dither 32bit gradients, to match what the canvas test suite expects. ++ */ ++#define SK_DISABLE_DITHER_32BIT_GRADIENT ++ ++/* Don't include stdint.h on windows as it conflicts with our build system. ++ */ ++#ifdef SK_BUILD_FOR_WIN32 ++ #define SK_IGNORE_STDINT_DOT_H ++#endif ++ + #endif +-- +1.7.11.4 + diff --git a/gfx/skia/patches/archive/0001-Bug-803063-Skia-cross-compilation-for-Windows-fails-.patch b/gfx/skia/patches/archive/0001-Bug-803063-Skia-cross-compilation-for-Windows-fails-.patch new file mode 100644 index 0000000000..20155977e2 --- /dev/null +++ b/gfx/skia/patches/archive/0001-Bug-803063-Skia-cross-compilation-for-Windows-fails-.patch @@ -0,0 +1,26 @@ +From 81ff1a8f5c2a7cc9e8b853101b995433a0c0fa37 Mon Sep 17 00:00:00 2001 +From: Jacek Caban +Date: Thu, 18 Oct 2012 15:25:08 +0200 +Subject: [PATCH] Bug 803063 - Skia cross compilation for Windows fails on + case sensitive OS + +--- + gfx/skia/src/core/SkAdvancedTypefaceMetrics.cpp | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/gfx/skia/src/core/SkAdvancedTypefaceMetrics.cpp b/gfx/skia/src/core/SkAdvancedTypefaceMetrics.cpp +index 370616e..b647ada 100644 +--- a/gfx/skia/src/core/SkAdvancedTypefaceMetrics.cpp ++++ b/gfx/skia/src/core/SkAdvancedTypefaceMetrics.cpp +@@ -13,7 +13,7 @@ + SK_DEFINE_INST_COUNT(SkAdvancedTypefaceMetrics) + + #if defined(SK_BUILD_FOR_WIN) +-#include ++#include + #endif + + #if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_ANDROID) +-- +1.7.8.6 + diff --git a/gfx/skia/patches/archive/0001-Bug-895086-Remove-unused-find_from_uniqueID-function.patch b/gfx/skia/patches/archive/0001-Bug-895086-Remove-unused-find_from_uniqueID-function.patch new file mode 100644 index 0000000000..aa1fadb435 --- /dev/null +++ b/gfx/skia/patches/archive/0001-Bug-895086-Remove-unused-find_from_uniqueID-function.patch @@ -0,0 +1,38 @@ +From 58861c38751adf1f4ef3f67f8e85f5c36f1c43a5 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Wed, 17 Jul 2013 16:28:07 -0400 +Subject: [PATCH] Bug 895086 - Remove unused find_from_uniqueID() function from + SkFontHost_linux + +--- + gfx/skia/src/ports/SkFontHost_linux.cpp | 14 -------------- + 1 file changed, 14 deletions(-) + +diff --git a/gfx/skia/src/ports/SkFontHost_linux.cpp b/gfx/skia/src/ports/SkFontHost_linux.cpp +index df21014..05b73dc 100644 +--- a/gfx/skia/src/ports/SkFontHost_linux.cpp ++++ b/gfx/skia/src/ports/SkFontHost_linux.cpp +@@ -117,20 +117,6 @@ static FamilyRec* find_family(const SkTypeface* member) { + return NULL; + } + +-static SkTypeface* find_from_uniqueID(uint32_t uniqueID) { +- FamilyRec* curr = gFamilyHead; +- while (curr != NULL) { +- for (int i = 0; i < 4; i++) { +- SkTypeface* face = curr->fFaces[i]; +- if (face != NULL && face->uniqueID() == uniqueID) { +- return face; +- } +- } +- curr = curr->fNext; +- } +- return NULL; +-} +- + /* Remove reference to this face from its family. If the resulting family + is empty (has no faces), return that family, otherwise return NULL + */ +-- +1.8.3.1 + diff --git a/gfx/skia/patches/archive/0002-Bug-688366-Dont-invalidate-all-radial-gradients.patch b/gfx/skia/patches/archive/0002-Bug-688366-Dont-invalidate-all-radial-gradients.patch new file mode 100644 index 0000000000..d396b4ed12 --- /dev/null +++ b/gfx/skia/patches/archive/0002-Bug-688366-Dont-invalidate-all-radial-gradients.patch @@ -0,0 +1,30 @@ +From f310d7e8b8d9cf6870c739650324bb585b591c0c Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:11:32 -0400 +Subject: [PATCH 02/10] Bug 755869 - [5] Re-apply bug 688366 - Fix Skia + marking radial gradients with the same radius as + invalid. r=mattwoodrow + +--- + gfx/skia/src/effects/SkGradientShader.cpp | 5 ++++- + 1 files changed, 4 insertions(+), 1 deletions(-) + +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +index 6de820b..59ba48c 100644 +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -1911,7 +1911,10 @@ public: + SkPMColor* SK_RESTRICT dstC = dstCParam; + + // Zero difference between radii: fill with transparent black. +- if (fDiffRadius == 0) { ++ // TODO: Is removing this actually correct? Two circles with the ++ // same radius, but different centers doesn't sound like it ++ // should be cleared ++ if (fDiffRadius == 0 && fCenter1 == fCenter2) { + sk_bzero(dstC, count * sizeof(*dstC)); + return; + } +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0002-Bug-848491-Re-apply-Bug-795549-Move-TileProc-functio.patch b/gfx/skia/patches/archive/0002-Bug-848491-Re-apply-Bug-795549-Move-TileProc-functio.patch new file mode 100644 index 0000000000..6ac2c9179d --- /dev/null +++ b/gfx/skia/patches/archive/0002-Bug-848491-Re-apply-Bug-795549-Move-TileProc-functio.patch @@ -0,0 +1,50 @@ +From: George Wright +Date: Mon, 14 Jan 2013 17:59:09 -0500 +Subject: Bug 848491 - Re-apply Bug 795549 - Move TileProc functions into their own file to ensure they only exist once in a library + + +diff --git a/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h b/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h +index b9dbf1b..729ce4e 100644 +--- a/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h ++++ b/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h +@@ -37,34 +37,9 @@ static inline void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1, + } + } + +-// Clamp +- +-static inline SkFixed clamp_tileproc(SkFixed x) { +- return SkClampMax(x, 0xFFFF); +-} +- +-// Repeat +- +-static inline SkFixed repeat_tileproc(SkFixed x) { +- return x & 0xFFFF; +-} +- +-// Mirror +- +-// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly. +-// See http://code.google.com/p/skia/issues/detail?id=472 +-#if defined(_MSC_VER) && (_MSC_VER >= 1600) +-#pragma optimize("", off) +-#endif +- +-static inline SkFixed mirror_tileproc(SkFixed x) { +- int s = x << 15 >> 31; +- return (x ^ s) & 0xFFFF; +-} +- +-#if defined(_MSC_VER) && (_MSC_VER >= 1600) +-#pragma optimize("", on) +-#endif ++SkFixed clamp_tileproc(SkFixed x); ++SkFixed repeat_tileproc(SkFixed x); ++SkFixed mirror_tileproc(SkFixed x); + + /////////////////////////////////////////////////////////////////////////////// + +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0003-SkUserConfig-for-Mozilla.patch b/gfx/skia/patches/archive/0003-SkUserConfig-for-Mozilla.patch new file mode 100644 index 0000000000..dc52a8d3d0 --- /dev/null +++ b/gfx/skia/patches/archive/0003-SkUserConfig-for-Mozilla.patch @@ -0,0 +1,39 @@ +From ef53776c06cffc7607c3777702f93e04c0852981 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:13:49 -0400 +Subject: [PATCH 03/10] Bug 755869 - [6] Re-apply SkUserConfig (no + original bug) r=mattwoodrow + +--- + gfx/skia/include/config/SkUserConfig.h | 10 ++++++++++ + 1 files changed, 10 insertions(+), 0 deletions(-) + +diff --git a/gfx/skia/include/config/SkUserConfig.h b/gfx/skia/include/config/SkUserConfig.h +index 9fdbd0a..f98ba85 100644 +--- a/gfx/skia/include/config/SkUserConfig.h ++++ b/gfx/skia/include/config/SkUserConfig.h +@@ -156,6 +156,10 @@ + //#define SK_SUPPORT_UNITTEST + #endif + ++/* Don't dither 32bit gradients, to match what the canvas test suite expects. ++ */ ++#define SK_DISABLE_DITHER_32BIT_GRADIENT ++ + /* If your system embeds skia and has complex event logging, define this + symbol to name a file that maps the following macros to your system's + equivalents: +@@ -177,4 +181,10 @@ + #define SK_A32_SHIFT 24 + #endif + ++/* Don't include stdint.h on windows as it conflicts with our build system. ++ */ ++#ifdef SK_BUILD_FOR_WIN32 ++ #define SK_IGNORE_STDINT_DOT_H ++#endif ++ + #endif +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0004-Bug-722011-Fix-trailing-commas-in-enums.patch b/gfx/skia/patches/archive/0004-Bug-722011-Fix-trailing-commas-in-enums.patch new file mode 100644 index 0000000000..179aeded5d --- /dev/null +++ b/gfx/skia/patches/archive/0004-Bug-722011-Fix-trailing-commas-in-enums.patch @@ -0,0 +1,280 @@ +From 81d61682a94d47be5b47fb7882ea7e7c7e6c3351 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:15:28 -0400 +Subject: [PATCH 04/10] Bug 755869 - [7] Re-apply bug 722011 - Fix + trailing commas at end of enum lists r=mattwoodrow + +--- + gfx/skia/include/core/SkAdvancedTypefaceMetrics.h | 8 ++++---- + gfx/skia/include/core/SkBlitRow.h | 2 +- + gfx/skia/include/core/SkCanvas.h | 2 +- + gfx/skia/include/core/SkDevice.h | 2 +- + gfx/skia/include/core/SkDeviceProfile.h | 4 ++-- + gfx/skia/include/core/SkFlattenable.h | 2 +- + gfx/skia/include/core/SkFontHost.h | 4 ++-- + gfx/skia/include/core/SkMaskFilter.h | 2 +- + gfx/skia/include/core/SkPaint.h | 4 ++-- + gfx/skia/include/core/SkScalerContext.h | 9 +++++---- + gfx/skia/include/core/SkTypes.h | 2 +- + gfx/skia/include/effects/SkLayerDrawLooper.h | 2 +- + gfx/skia/src/core/SkBitmap.cpp | 2 +- + gfx/skia/src/core/SkGlyphCache.cpp | 2 +- + 14 files changed, 24 insertions(+), 23 deletions(-) + +diff --git a/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h b/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h +index 09fc9a9..5ffdb45 100644 +--- a/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h ++++ b/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h +@@ -34,7 +34,7 @@ public: + kCFF_Font, + kTrueType_Font, + kOther_Font, +- kNotEmbeddable_Font, ++ kNotEmbeddable_Font + }; + // The type of the underlying font program. This field determines which + // of the following fields are valid. If it is kOther_Font or +@@ -56,7 +56,7 @@ public: + kItalic_Style = 0x00040, + kAllCaps_Style = 0x10000, + kSmallCaps_Style = 0x20000, +- kForceBold_Style = 0x40000, ++ kForceBold_Style = 0x40000 + }; + uint16_t fStyle; // Font style characteristics. + int16_t fItalicAngle; // Counterclockwise degrees from vertical of the +@@ -75,7 +75,7 @@ public: + kHAdvance_PerGlyphInfo = 0x1, // Populate horizontal advance data. + kVAdvance_PerGlyphInfo = 0x2, // Populate vertical advance data. + kGlyphNames_PerGlyphInfo = 0x4, // Populate glyph names (Type 1 only). +- kToUnicode_PerGlyphInfo = 0x8, // Populate ToUnicode table, ignored ++ kToUnicode_PerGlyphInfo = 0x8 // Populate ToUnicode table, ignored + // for Type 1 fonts + }; + +@@ -84,7 +84,7 @@ public: + enum MetricType { + kDefault, // Default advance: fAdvance.count = 1 + kRange, // Advances for a range: fAdvance.count = fEndID-fStartID +- kRun, // fStartID-fEndID have same advance: fAdvance.count = 1 ++ kRun // fStartID-fEndID have same advance: fAdvance.count = 1 + }; + MetricType fType; + uint16_t fStartId; +diff --git a/gfx/skia/include/core/SkBlitRow.h b/gfx/skia/include/core/SkBlitRow.h +index 973ab4c..febc405 100644 +--- a/gfx/skia/include/core/SkBlitRow.h ++++ b/gfx/skia/include/core/SkBlitRow.h +@@ -42,7 +42,7 @@ public: + + enum Flags32 { + kGlobalAlpha_Flag32 = 1 << 0, +- kSrcPixelAlpha_Flag32 = 1 << 1, ++ kSrcPixelAlpha_Flag32 = 1 << 1 + }; + + /** Function pointer that blends 32bit colors onto a 32bit destination. +diff --git a/gfx/skia/include/core/SkCanvas.h b/gfx/skia/include/core/SkCanvas.h +index 25cc94a..d942783 100644 +--- a/gfx/skia/include/core/SkCanvas.h ++++ b/gfx/skia/include/core/SkCanvas.h +@@ -148,7 +148,7 @@ public: + * low byte to high byte: R, G, B, A. + */ + kRGBA_Premul_Config8888, +- kRGBA_Unpremul_Config8888, ++ kRGBA_Unpremul_Config8888 + }; + + /** +diff --git a/gfx/skia/include/core/SkDevice.h b/gfx/skia/include/core/SkDevice.h +index 1e4e0a3..b4d44bf 100644 +--- a/gfx/skia/include/core/SkDevice.h ++++ b/gfx/skia/include/core/SkDevice.h +@@ -139,7 +139,7 @@ public: + protected: + enum Usage { + kGeneral_Usage, +- kSaveLayer_Usage, // +Date: Thu, 23 Aug 2012 16:45:38 -0400 +Subject: [PATCH 4/9] Bug 777614 - Re-apply bug 719872 - Fix crash on Android + by reverting to older FontHost r=nrc + +--- + gfx/skia/src/ports/SkFontHost_android_old.cpp | 664 ++++++++++++++++++++++++++ + 1 file changed, 664 insertions(+) + create mode 100644 gfx/skia/src/ports/SkFontHost_android_old.cpp + +diff --git a/gfx/skia/src/ports/SkFontHost_android_old.cpp b/gfx/skia/src/ports/SkFontHost_android_old.cpp +new file mode 100644 +index 0000000..b5c4f3c +--- /dev/null ++++ b/gfx/skia/src/ports/SkFontHost_android_old.cpp +@@ -0,0 +1,664 @@ ++ ++/* ++ * Copyright 2006 The Android Open Source Project ++ * ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. ++ */ ++ ++ ++#include "SkFontHost.h" ++#include "SkDescriptor.h" ++#include "SkMMapStream.h" ++#include "SkPaint.h" ++#include "SkString.h" ++#include "SkStream.h" ++#include "SkThread.h" ++#include "SkTSearch.h" ++#include ++ ++#define FONT_CACHE_MEMORY_BUDGET (768 * 1024) ++ ++#ifndef SK_FONT_FILE_PREFIX ++ #define SK_FONT_FILE_PREFIX "/fonts/" ++#endif ++ ++bool find_name_and_attributes(SkStream* stream, SkString* name, SkTypeface::Style* style, ++ bool* isFixedWidth); ++ ++static void GetFullPathForSysFonts(SkString* full, const char name[]) { ++ full->set(getenv("ANDROID_ROOT")); ++ full->append(SK_FONT_FILE_PREFIX); ++ full->append(name); ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++struct FamilyRec; ++ ++/* This guy holds a mapping of a name -> family, used for looking up fonts. ++ Since it is stored in a stretchy array that doesn't preserve object ++ semantics, we don't use constructor/destructors, but just have explicit ++ helpers to manage our internal bookkeeping. ++*/ ++struct NameFamilyPair { ++ const char* fName; // we own this ++ FamilyRec* fFamily; // we don't own this, we just reference it ++ ++ void construct(const char name[], FamilyRec* family) { ++ fName = strdup(name); ++ fFamily = family; // we don't own this, so just record the referene ++ } ++ ++ void destruct() { ++ free((char*)fName); ++ // we don't own family, so just ignore our reference ++ } ++}; ++ ++// we use atomic_inc to grow this for each typeface we create ++static int32_t gUniqueFontID; ++ ++// this is the mutex that protects these globals ++static SkMutex gFamilyMutex; ++static FamilyRec* gFamilyHead; ++static SkTDArray gNameList; ++ ++struct FamilyRec { ++ FamilyRec* fNext; ++ SkTypeface* fFaces[4]; ++ ++ FamilyRec() ++ { ++ fNext = gFamilyHead; ++ memset(fFaces, 0, sizeof(fFaces)); ++ gFamilyHead = this; ++ } ++}; ++ ++static SkTypeface* find_best_face(const FamilyRec* family, ++ SkTypeface::Style style) { ++ SkTypeface* const* faces = family->fFaces; ++ ++ if (faces[style] != NULL) { // exact match ++ return faces[style]; ++ } ++ // look for a matching bold ++ style = (SkTypeface::Style)(style ^ SkTypeface::kItalic); ++ if (faces[style] != NULL) { ++ return faces[style]; ++ } ++ // look for the plain ++ if (faces[SkTypeface::kNormal] != NULL) { ++ return faces[SkTypeface::kNormal]; ++ } ++ // look for anything ++ for (int i = 0; i < 4; i++) { ++ if (faces[i] != NULL) { ++ return faces[i]; ++ } ++ } ++ // should never get here, since the faces list should not be empty ++ SkASSERT(!"faces list is empty"); ++ return NULL; ++} ++ ++static FamilyRec* find_family(const SkTypeface* member) { ++ FamilyRec* curr = gFamilyHead; ++ while (curr != NULL) { ++ for (int i = 0; i < 4; i++) { ++ if (curr->fFaces[i] == member) { ++ return curr; ++ } ++ } ++ curr = curr->fNext; ++ } ++ return NULL; ++} ++ ++/* Returns the matching typeface, or NULL. If a typeface is found, its refcnt ++ is not modified. ++ */ ++static SkTypeface* find_from_uniqueID(uint32_t uniqueID) { ++ FamilyRec* curr = gFamilyHead; ++ while (curr != NULL) { ++ for (int i = 0; i < 4; i++) { ++ SkTypeface* face = curr->fFaces[i]; ++ if (face != NULL && face->uniqueID() == uniqueID) { ++ return face; ++ } ++ } ++ curr = curr->fNext; ++ } ++ return NULL; ++} ++ ++/* Remove reference to this face from its family. If the resulting family ++ is empty (has no faces), return that family, otherwise return NULL ++*/ ++static FamilyRec* remove_from_family(const SkTypeface* face) { ++ FamilyRec* family = find_family(face); ++ SkASSERT(family->fFaces[face->style()] == face); ++ family->fFaces[face->style()] = NULL; ++ ++ for (int i = 0; i < 4; i++) { ++ if (family->fFaces[i] != NULL) { // family is non-empty ++ return NULL; ++ } ++ } ++ return family; // return the empty family ++} ++ ++// maybe we should make FamilyRec be doubly-linked ++static void detach_and_delete_family(FamilyRec* family) { ++ FamilyRec* curr = gFamilyHead; ++ FamilyRec* prev = NULL; ++ ++ while (curr != NULL) { ++ FamilyRec* next = curr->fNext; ++ if (curr == family) { ++ if (prev == NULL) { ++ gFamilyHead = next; ++ } else { ++ prev->fNext = next; ++ } ++ SkDELETE(family); ++ return; ++ } ++ prev = curr; ++ curr = next; ++ } ++ SkASSERT(!"Yikes, couldn't find family in our list to remove/delete"); ++} ++ ++static SkTypeface* find_typeface(const char name[], SkTypeface::Style style) { ++ NameFamilyPair* list = gNameList.begin(); ++ int count = gNameList.count(); ++ ++ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); ++ ++ if (index >= 0) { ++ return find_best_face(list[index].fFamily, style); ++ } ++ return NULL; ++} ++ ++static SkTypeface* find_typeface(const SkTypeface* familyMember, ++ SkTypeface::Style style) { ++ const FamilyRec* family = find_family(familyMember); ++ return family ? find_best_face(family, style) : NULL; ++} ++ ++static void add_name(const char name[], FamilyRec* family) { ++ SkAutoAsciiToLC tolc(name); ++ name = tolc.lc(); ++ ++ NameFamilyPair* list = gNameList.begin(); ++ int count = gNameList.count(); ++ ++ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); ++ ++ if (index < 0) { ++ list = gNameList.insert(~index); ++ list->construct(name, family); ++ } ++} ++ ++static void remove_from_names(FamilyRec* emptyFamily) ++{ ++#ifdef SK_DEBUG ++ for (int i = 0; i < 4; i++) { ++ SkASSERT(emptyFamily->fFaces[i] == NULL); ++ } ++#endif ++ ++ SkTDArray& list = gNameList; ++ ++ // must go backwards when removing ++ for (int i = list.count() - 1; i >= 0; --i) { ++ NameFamilyPair* pair = &list[i]; ++ if (pair->fFamily == emptyFamily) { ++ pair->destruct(); ++ list.remove(i); ++ } ++ } ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++class FamilyTypeface : public SkTypeface { ++public: ++ FamilyTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ bool isFixedWidth) ++ : SkTypeface(style, sk_atomic_inc(&gUniqueFontID) + 1, isFixedWidth) { ++ fIsSysFont = sysFont; ++ ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyRec* rec = NULL; ++ if (familyMember) { ++ rec = find_family(familyMember); ++ SkASSERT(rec); ++ } else { ++ rec = SkNEW(FamilyRec); ++ } ++ rec->fFaces[style] = this; ++ } ++ ++ virtual ~FamilyTypeface() { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ // remove us from our family. If the family is now empty, we return ++ // that and then remove that family from the name list ++ FamilyRec* family = remove_from_family(this); ++ if (NULL != family) { ++ remove_from_names(family); ++ detach_and_delete_family(family); ++ } ++ } ++ ++ bool isSysFont() const { return fIsSysFont; } ++ ++ virtual SkStream* openStream() = 0; ++ virtual const char* getUniqueString() const = 0; ++ virtual const char* getFilePath() const = 0; ++ ++private: ++ bool fIsSysFont; ++ ++ typedef SkTypeface INHERITED; ++}; ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++class StreamTypeface : public FamilyTypeface { ++public: ++ StreamTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ SkStream* stream, bool isFixedWidth) ++ : INHERITED(style, sysFont, familyMember, isFixedWidth) { ++ SkASSERT(stream); ++ stream->ref(); ++ fStream = stream; ++ } ++ virtual ~StreamTypeface() { ++ fStream->unref(); ++ } ++ ++ // overrides ++ virtual SkStream* openStream() { ++ // we just ref our existing stream, since the caller will call unref() ++ // when they are through ++ fStream->ref(); ++ // must rewind each time, since the caller assumes a "new" stream ++ fStream->rewind(); ++ return fStream; ++ } ++ virtual const char* getUniqueString() const { return NULL; } ++ virtual const char* getFilePath() const { return NULL; } ++ ++private: ++ SkStream* fStream; ++ ++ typedef FamilyTypeface INHERITED; ++}; ++ ++class FileTypeface : public FamilyTypeface { ++public: ++ FileTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ const char path[], bool isFixedWidth) ++ : INHERITED(style, sysFont, familyMember, isFixedWidth) { ++ SkString fullpath; ++ ++ if (sysFont) { ++ GetFullPathForSysFonts(&fullpath, path); ++ path = fullpath.c_str(); ++ } ++ fPath.set(path); ++ } ++ ++ // overrides ++ virtual SkStream* openStream() { ++ SkStream* stream = SkNEW_ARGS(SkMMAPStream, (fPath.c_str())); ++ ++ // check for failure ++ if (stream->getLength() <= 0) { ++ SkDELETE(stream); ++ // maybe MMAP isn't supported. try FILE ++ stream = SkNEW_ARGS(SkFILEStream, (fPath.c_str())); ++ if (stream->getLength() <= 0) { ++ SkDELETE(stream); ++ stream = NULL; ++ } ++ } ++ return stream; ++ } ++ virtual const char* getUniqueString() const { ++ const char* str = strrchr(fPath.c_str(), '/'); ++ if (str) { ++ str += 1; // skip the '/' ++ } ++ return str; ++ } ++ virtual const char* getFilePath() const { ++ return fPath.c_str(); ++ } ++ ++private: ++ SkString fPath; ++ ++ typedef FamilyTypeface INHERITED; ++}; ++ ++/////////////////////////////////////////////////////////////////////////////// ++/////////////////////////////////////////////////////////////////////////////// ++ ++static bool get_name_and_style(const char path[], SkString* name, ++ SkTypeface::Style* style, ++ bool* isFixedWidth, bool isExpected) { ++ SkString fullpath; ++ GetFullPathForSysFonts(&fullpath, path); ++ ++ SkMMAPStream stream(fullpath.c_str()); ++ if (stream.getLength() > 0) { ++ find_name_and_attributes(&stream, name, style, isFixedWidth); ++ return true; ++ } ++ else { ++ SkFILEStream stream(fullpath.c_str()); ++ if (stream.getLength() > 0) { ++ find_name_and_attributes(&stream, name, style, isFixedWidth); ++ return true; ++ } ++ } ++ ++ if (isExpected) { ++ SkDebugf("---- failed to open <%s> as a font\n", fullpath.c_str()); ++ } ++ return false; ++} ++ ++// used to record our notion of the pre-existing fonts ++struct FontInitRec { ++ const char* fFileName; ++ const char* const* fNames; // null-terminated list ++}; ++ ++static const char* gSansNames[] = { ++ "sans-serif", "arial", "helvetica", "tahoma", "verdana", NULL ++}; ++ ++static const char* gSerifNames[] = { ++ "serif", "times", "times new roman", "palatino", "georgia", "baskerville", ++ "goudy", "fantasy", "cursive", "ITC Stone Serif", NULL ++}; ++ ++static const char* gMonoNames[] = { ++ "monospace", "courier", "courier new", "monaco", NULL ++}; ++ ++// deliberately empty, but we use the address to identify fallback fonts ++static const char* gFBNames[] = { NULL }; ++ ++/* Fonts must be grouped by family, with the first font in a family having the ++ list of names (even if that list is empty), and the following members having ++ null for the list. The names list must be NULL-terminated ++*/ ++static const FontInitRec gSystemFonts[] = { ++ { "DroidSans.ttf", gSansNames }, ++ { "DroidSans-Bold.ttf", NULL }, ++ { "DroidSerif-Regular.ttf", gSerifNames }, ++ { "DroidSerif-Bold.ttf", NULL }, ++ { "DroidSerif-Italic.ttf", NULL }, ++ { "DroidSerif-BoldItalic.ttf", NULL }, ++ { "DroidSansMono.ttf", gMonoNames }, ++ /* These are optional, and can be ignored if not found in the file system. ++ These are appended to gFallbackFonts[] as they are seen, so we list ++ them in the order we want them to be accessed by NextLogicalFont(). ++ */ ++ { "DroidSansArabic.ttf", gFBNames }, ++ { "DroidSansHebrew.ttf", gFBNames }, ++ { "DroidSansThai.ttf", gFBNames }, ++ { "MTLmr3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "MTLc3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "DroidSansJapanese.ttf", gFBNames }, ++ { "DroidSansFallback.ttf", gFBNames } ++}; ++ ++#define DEFAULT_NAMES gSansNames ++ ++// these globals are assigned (once) by load_system_fonts() ++static FamilyRec* gDefaultFamily; ++static SkTypeface* gDefaultNormal; ++ ++/* This is sized conservatively, assuming that it will never be a size issue. ++ It will be initialized in load_system_fonts(), and will be filled with the ++ fontIDs that can be used for fallback consideration, in sorted order (sorted ++ meaning element[0] should be used first, then element[1], etc. When we hit ++ a fontID==0 in the array, the list is done, hence our allocation size is ++ +1 the total number of possible system fonts. Also see NextLogicalFont(). ++ */ ++static uint32_t gFallbackFonts[SK_ARRAY_COUNT(gSystemFonts)+1]; ++ ++/* Called once (ensured by the sentinel check at the beginning of our body). ++ Initializes all the globals, and register the system fonts. ++ */ ++static void load_system_fonts() { ++ // check if we've already be called ++ if (NULL != gDefaultNormal) { ++ return; ++ } ++ ++ const FontInitRec* rec = gSystemFonts; ++ SkTypeface* firstInFamily = NULL; ++ int fallbackCount = 0; ++ ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ // if we're the first in a new family, clear firstInFamily ++ if (rec[i].fNames != NULL) { ++ firstInFamily = NULL; ++ } ++ ++ bool isFixedWidth; ++ SkString name; ++ SkTypeface::Style style; ++ ++ // we expect all the fonts, except the "fallback" fonts ++ bool isExpected = (rec[i].fNames != gFBNames); ++ if (!get_name_and_style(rec[i].fFileName, &name, &style, ++ &isFixedWidth, isExpected)) { ++ continue; ++ } ++ ++ SkTypeface* tf = SkNEW_ARGS(FileTypeface, ++ (style, ++ true, // system-font (cannot delete) ++ firstInFamily, // what family to join ++ rec[i].fFileName, ++ isFixedWidth) // filename ++ ); ++ ++ if (rec[i].fNames != NULL) { ++ // see if this is one of our fallback fonts ++ if (rec[i].fNames == gFBNames) { ++ // SkDebugf("---- adding %s as fallback[%d] fontID %d\n", ++ // rec[i].fFileName, fallbackCount, tf->uniqueID()); ++ gFallbackFonts[fallbackCount++] = tf->uniqueID(); ++ } ++ ++ firstInFamily = tf; ++ FamilyRec* family = find_family(tf); ++ const char* const* names = rec[i].fNames; ++ ++ // record the default family if this is it ++ if (names == DEFAULT_NAMES) { ++ gDefaultFamily = family; ++ } ++ // add the names to map to this family ++ while (*names) { ++ add_name(*names, family); ++ names += 1; ++ } ++ } ++ } ++ ++ // do this after all fonts are loaded. This is our default font, and it ++ // acts as a sentinel so we only execute load_system_fonts() once ++ gDefaultNormal = find_best_face(gDefaultFamily, SkTypeface::kNormal); ++ // now terminate our fallback list with the sentinel value ++ gFallbackFonts[fallbackCount] = 0; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) { ++ const char* name = ((FamilyTypeface*)face)->getUniqueString(); ++ ++ stream->write8((uint8_t)face->style()); ++ ++ if (NULL == name || 0 == *name) { ++ stream->writePackedUInt(0); ++// SkDebugf("--- fonthost serialize null\n"); ++ } else { ++ uint32_t len = strlen(name); ++ stream->writePackedUInt(len); ++ stream->write(name, len); ++// SkDebugf("--- fonthost serialize <%s> %d\n", name, face->style()); ++ } ++} ++ ++SkTypeface* SkFontHost::Deserialize(SkStream* stream) { ++ load_system_fonts(); ++ ++ int style = stream->readU8(); ++ ++ int len = stream->readPackedUInt(); ++ if (len > 0) { ++ SkString str; ++ str.resize(len); ++ stream->read(str.writable_str(), len); ++ ++ const FontInitRec* rec = gSystemFonts; ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ if (strcmp(rec[i].fFileName, str.c_str()) == 0) { ++ // backup until we hit the fNames ++ for (int j = i; j >= 0; --j) { ++ if (rec[j].fNames != NULL) { ++ return SkFontHost::CreateTypeface(NULL, ++ rec[j].fNames[0], (SkTypeface::Style)style); ++ } ++ } ++ } ++ } ++ } ++ return NULL; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, ++ const char familyName[], ++ SkTypeface::Style style) { ++ load_system_fonts(); ++ ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ // clip to legal style bits ++ style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic); ++ ++ SkTypeface* tf = NULL; ++ ++ if (NULL != familyFace) { ++ tf = find_typeface(familyFace, style); ++ } else if (NULL != familyName) { ++// SkDebugf("======= familyName <%s>\n", familyName); ++ tf = find_typeface(familyName, style); ++ } ++ ++ if (NULL == tf) { ++ tf = find_best_face(gDefaultFamily, style); ++ } ++ ++ // we ref(), since the symantic is to return a new instance ++ tf->ref(); ++ return tf; ++} ++ ++SkStream* SkFontHost::OpenStream(uint32_t fontID) { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyTypeface* tf = (FamilyTypeface*)find_from_uniqueID(fontID); ++ SkStream* stream = tf ? tf->openStream() : NULL; ++ ++ if (stream && stream->getLength() == 0) { ++ stream->unref(); ++ stream = NULL; ++ } ++ return stream; ++} ++ ++size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, ++ int32_t* index) { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyTypeface* tf = (FamilyTypeface*)find_from_uniqueID(fontID); ++ const char* src = tf ? tf->getFilePath() : NULL; ++ ++ if (src) { ++ size_t size = strlen(src); ++ if (path) { ++ memcpy(path, src, SkMin32(size, length)); ++ } ++ if (index) { ++ *index = 0; // we don't have collections (yet) ++ } ++ return size; ++ } else { ++ return 0; ++ } ++} ++ ++SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { ++ load_system_fonts(); ++ ++ /* First see if fontID is already one of our fallbacks. If so, return ++ its successor. If fontID is not in our list, then return the first one ++ in our list. Note: list is zero-terminated, and returning zero means ++ we have no more fonts to use for fallbacks. ++ */ ++ const uint32_t* list = gFallbackFonts; ++ for (int i = 0; list[i] != 0; i++) { ++ if (list[i] == currFontID) { ++ return list[i+1]; ++ } ++ } ++ return list[0]; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) { ++ if (NULL == stream || stream->getLength() <= 0) { ++ return NULL; ++ } ++ ++ bool isFixedWidth; ++ SkString name; ++ SkTypeface::Style style; ++ find_name_and_attributes(stream, &name, &style, &isFixedWidth); ++ ++ if (!name.isEmpty()) { ++ return SkNEW_ARGS(StreamTypeface, (style, false, NULL, stream, isFixedWidth)); ++ } else { ++ return NULL; ++ } ++} ++ ++SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) { ++ SkStream* stream = SkNEW_ARGS(SkMMAPStream, (path)); ++ SkTypeface* face = SkFontHost::CreateTypefaceFromStream(stream); ++ // since we created the stream, we let go of our ref() here ++ stream->unref(); ++ return face; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// +-- +1.7.11.4 + diff --git a/gfx/skia/patches/archive/0005-Bug-731384-Fix-clang-SK_OVERRIDE.patch b/gfx/skia/patches/archive/0005-Bug-731384-Fix-clang-SK_OVERRIDE.patch new file mode 100644 index 0000000000..e8b5df635b --- /dev/null +++ b/gfx/skia/patches/archive/0005-Bug-731384-Fix-clang-SK_OVERRIDE.patch @@ -0,0 +1,36 @@ +From 80350275c72921ed5ac405c029ae33727467d7c5 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:15:50 -0400 +Subject: [PATCH 05/10] Bug 755869 - [8] Re-apply bug 731384 - Fix compile + errors on older versions of clang r=mattwoodrow + +--- + gfx/skia/include/core/SkPostConfig.h | 9 +++++++++ + 1 files changed, 9 insertions(+), 0 deletions(-) + +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +index 8316f7a..041fe2a 100644 +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -288,9 +288,18 @@ + #if defined(_MSC_VER) + #define SK_OVERRIDE override + #elif defined(__clang__) ++#if __has_feature(cxx_override_control) + // Some documentation suggests we should be using __attribute__((override)), + // but it doesn't work. + #define SK_OVERRIDE override ++#elif defined(__has_extension) ++#if __has_extension(cxx_override_control) ++#define SK_OVERRIDE override ++#endif ++#endif ++#ifndef SK_OVERRIDE ++#define SK_OVERRIDE ++#endif + #else + // Linux GCC ignores "__attribute__((override))" and rejects "override". + #define SK_OVERRIDE +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0005-Bug-736276-Add-a-new-SkFontHost-that-takes-a-cairo_s.patch b/gfx/skia/patches/archive/0005-Bug-736276-Add-a-new-SkFontHost-that-takes-a-cairo_s.patch new file mode 100644 index 0000000000..cd2f67131c --- /dev/null +++ b/gfx/skia/patches/archive/0005-Bug-736276-Add-a-new-SkFontHost-that-takes-a-cairo_s.patch @@ -0,0 +1,449 @@ +From: George Wright +Date: Wed, 1 Aug 2012 16:43:15 -0400 +Subject: Bug 736276 - Add a new SkFontHost that takes a cairo_scaled_font_t r=karl + + +diff --git a/gfx/skia/include/ports/SkTypeface_cairo.h b/gfx/skia/include/ports/SkTypeface_cairo.h +new file mode 100644 +index 0000000..7e44f04 +--- /dev/null ++++ b/gfx/skia/include/ports/SkTypeface_cairo.h +@@ -0,0 +1,11 @@ ++#ifndef SkTypeface_cairo_DEFINED ++#define SkTypeface_cairo_DEFINED ++ ++#include ++ ++#include "SkTypeface.h" ++ ++SK_API extern SkTypeface* SkCreateTypefaceFromCairoFont(cairo_font_face_t* fontFace, SkTypeface::Style style, bool isFixedWidth); ++ ++#endif ++ +diff --git a/gfx/skia/moz.build b/gfx/skia/moz.build +index 9ceba59..66efd52 100644 +--- a/gfx/skia/moz.build ++++ b/gfx/skia/moz.build +@@ -171,10 +171,12 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + 'SkTime_win.cpp', + ] + elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2': ++ EXPORTS.skia += [ ++ 'include/ports/SkTypeface_cairo.h', ++ ] + CPP_SOURCES += [ +- 'SkFontHost_FreeType.cpp', ++ 'SkFontHost_cairo.cpp', + 'SkFontHost_FreeType_common.cpp', +- 'SkFontHost_linux.cpp', + 'SkThread_pthread.cpp', + 'SkThreadUtils_pthread.cpp', + 'SkThreadUtils_pthread_linux.cpp', +@@ -183,14 +185,15 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2': + ] + elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'qt': + CPP_SOURCES += [ +- 'SkFontHost_FreeType.cpp', ++ 'SkFontHost_cairo.cpp', + 'SkFontHost_FreeType_common.cpp', + 'SkOSFile.cpp', + ] + if CONFIG['OS_TARGET'] == 'Linux': ++ EXPORTS.skia += [ ++ 'include/ports/SkTypeface_cairo.h', ++ ] + CPP_SOURCES += [ +- 'SkFontHost_linux.cpp', +- 'SkFontHost_tables.cpp', + 'SkThread_pthread.cpp', + 'SkThreadUtils_pthread.cpp', + 'SkThreadUtils_pthread_linux.cpp', +@@ -204,11 +207,13 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + # Separate 'if' from above, since the else below applies to all != 'android' + # toolkits. + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': ++ EXPORTS.skia += [ ++ 'include/ports/SkTypeface_cairo.h', ++ ] + CPP_SOURCES += [ + 'ashmem.cpp', + 'SkDebug_android.cpp', +- 'SkFontHost_android_old.cpp', +- 'SkFontHost_FreeType.cpp', ++ 'SkFontHost_cairo.cpp', + 'SkFontHost_FreeType_common.cpp', + 'SkImageRef_ashmem.cpp', + 'SkTime_Unix.cpp', +diff --git a/gfx/skia/src/ports/SkFontHost_cairo.cpp b/gfx/skia/src/ports/SkFontHost_cairo.cpp +new file mode 100644 +index 0000000..bb5b778 +--- /dev/null ++++ b/gfx/skia/src/ports/SkFontHost_cairo.cpp +@@ -0,0 +1,364 @@ ++ ++/* ++ * Copyright 2012 Mozilla Foundation ++ * ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. ++ */ ++ ++#include "cairo.h" ++#include "cairo-ft.h" ++ ++#include "SkFontHost_FreeType_common.h" ++ ++#include "SkAdvancedTypefaceMetrics.h" ++#include "SkFontHost.h" ++#include "SkPath.h" ++#include "SkScalerContext.h" ++#include "SkTypefaceCache.h" ++ ++#include ++#include FT_FREETYPE_H ++ ++static cairo_user_data_key_t kSkTypefaceKey; ++ ++class SkScalerContext_CairoFT : public SkScalerContext_FreeType_Base { ++public: ++ SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc); ++ virtual ~SkScalerContext_CairoFT(); ++ ++protected: ++ virtual unsigned generateGlyphCount() SK_OVERRIDE; ++ virtual uint16_t generateCharToGlyph(SkUnichar uniChar) SK_OVERRIDE; ++ virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE; ++ virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE; ++ virtual void generateImage(const SkGlyph& glyph) SK_OVERRIDE; ++ virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE; ++ virtual void generateFontMetrics(SkPaint::FontMetrics* mx, ++ SkPaint::FontMetrics* my) SK_OVERRIDE; ++ virtual SkUnichar generateGlyphToChar(uint16_t glyph) SK_OVERRIDE; ++private: ++ cairo_scaled_font_t* fScaledFont; ++ uint32_t fLoadGlyphFlags; ++}; ++ ++class CairoLockedFTFace { ++public: ++ CairoLockedFTFace(cairo_scaled_font_t* scaledFont) ++ : fScaledFont(scaledFont) ++ , fFace(cairo_ft_scaled_font_lock_face(scaledFont)) ++ {} ++ ++ ~CairoLockedFTFace() ++ { ++ cairo_ft_scaled_font_unlock_face(fScaledFont); ++ } ++ ++ FT_Face getFace() ++ { ++ return fFace; ++ } ++ ++private: ++ cairo_scaled_font_t* fScaledFont; ++ FT_Face fFace; ++}; ++ ++class SkCairoFTTypeface : public SkTypeface { ++public: ++ static SkTypeface* CreateTypeface(cairo_font_face_t* fontFace, SkTypeface::Style style, bool isFixedWidth) { ++ SkASSERT(fontFace != NULL); ++ SkASSERT(cairo_font_face_get_type(fontFace) == CAIRO_FONT_TYPE_FT); ++ ++ SkFontID newId = SkTypefaceCache::NewFontID(); ++ ++ return SkNEW_ARGS(SkCairoFTTypeface, (fontFace, style, newId, isFixedWidth)); ++ } ++ ++ cairo_font_face_t* getFontFace() { ++ return fFontFace; ++ } ++ ++ virtual SkStream* onOpenStream(int*) const SK_OVERRIDE { return NULL; } ++ ++ virtual SkAdvancedTypefaceMetrics* ++ onGetAdvancedTypefaceMetrics(SkAdvancedTypefaceMetrics::PerGlyphInfo, ++ const uint32_t*, uint32_t) const SK_OVERRIDE ++ { ++ SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetAdvancedTypefaceMetrics unimplemented\n")); ++ return NULL; ++ } ++ ++ virtual SkScalerContext* onCreateScalerContext(const SkDescriptor* desc) const SK_OVERRIDE ++ { ++ return SkNEW_ARGS(SkScalerContext_CairoFT, (const_cast(this), desc)); ++ } ++ ++ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE ++ { ++ SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onFilterRec unimplemented\n")); ++ } ++ ++ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE ++ { ++ SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetFontDescriptor unimplemented\n")); ++ } ++ ++ ++private: ++ ++ SkCairoFTTypeface(cairo_font_face_t* fontFace, SkTypeface::Style style, SkFontID id, bool isFixedWidth) ++ : SkTypeface(style, id, isFixedWidth) ++ , fFontFace(fontFace) ++ { ++ cairo_font_face_set_user_data(fFontFace, &kSkTypefaceKey, this, NULL); ++ cairo_font_face_reference(fFontFace); ++ } ++ ++ ~SkCairoFTTypeface() ++ { ++ cairo_font_face_set_user_data(fFontFace, &kSkTypefaceKey, NULL, NULL); ++ cairo_font_face_destroy(fFontFace); ++ } ++ ++ cairo_font_face_t* fFontFace; ++}; ++ ++SkTypeface* SkCreateTypefaceFromCairoFont(cairo_font_face_t* fontFace, SkTypeface::Style style, bool isFixedWidth) ++{ ++ SkTypeface* typeface = reinterpret_cast(cairo_font_face_get_user_data(fontFace, &kSkTypefaceKey)); ++ ++ if (typeface) { ++ typeface->ref(); ++ } else { ++ typeface = SkCairoFTTypeface::CreateTypeface(fontFace, style, isFixedWidth); ++ SkTypefaceCache::Add(typeface, style); ++ } ++ ++ return typeface; ++} ++ ++SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, ++ const char famillyName[], ++ SkTypeface::Style style) ++{ ++ SkDEBUGFAIL("SkFontHost::FindTypeface unimplemented"); ++ return NULL; ++} ++ ++SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream*) ++{ ++ SkDEBUGFAIL("SkFontHost::CreateTypeface unimplemented"); ++ return NULL; ++} ++ ++SkTypeface* SkFontHost::CreateTypefaceFromFile(char const*) ++{ ++ SkDEBUGFAIL("SkFontHost::CreateTypefaceFromFile unimplemented"); ++ return NULL; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++static bool isLCD(const SkScalerContext::Rec& rec) { ++ switch (rec.fMaskFormat) { ++ case SkMask::kLCD16_Format: ++ case SkMask::kLCD32_Format: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++SkScalerContext_CairoFT::SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc) ++ : SkScalerContext_FreeType_Base(typeface, desc) ++{ ++ SkMatrix matrix; ++ fRec.getSingleMatrix(&matrix); ++ ++ cairo_font_face_t* fontFace = static_cast(typeface)->getFontFace(); ++ ++ cairo_matrix_t fontMatrix, ctMatrix; ++ cairo_matrix_init(&fontMatrix, matrix.getScaleX(), matrix.getSkewY(), matrix.getSkewX(), matrix.getScaleY(), 0.0, 0.0); ++ cairo_matrix_init_scale(&ctMatrix, 1.0, 1.0); ++ ++ // We need to ensure that the font options match for hinting, as generateMetrics() ++ // uses the fScaledFont which uses these font options ++ cairo_font_options_t *fontOptions = cairo_font_options_create(); ++ ++ FT_Int32 loadFlags = FT_LOAD_DEFAULT; ++ ++ if (SkMask::kBW_Format == fRec.fMaskFormat) { ++ // See http://code.google.com/p/chromium/issues/detail?id=43252#c24 ++ loadFlags = FT_LOAD_TARGET_MONO; ++ if (fRec.getHinting() == SkPaint::kNo_Hinting) { ++ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_NONE); ++ loadFlags = FT_LOAD_NO_HINTING; ++ } ++ } else { ++ switch (fRec.getHinting()) { ++ case SkPaint::kNo_Hinting: ++ loadFlags = FT_LOAD_NO_HINTING; ++ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_NONE); ++ break; ++ case SkPaint::kSlight_Hinting: ++ loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT ++ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_SLIGHT); ++ break; ++ case SkPaint::kNormal_Hinting: ++ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_MEDIUM); ++ if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) { ++ loadFlags = FT_LOAD_FORCE_AUTOHINT; ++ } ++ break; ++ case SkPaint::kFull_Hinting: ++ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_FULL); ++ if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) { ++ loadFlags = FT_LOAD_FORCE_AUTOHINT; ++ } ++ if (isLCD(fRec)) { ++ if (SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag)) { ++ loadFlags = FT_LOAD_TARGET_LCD_V; ++ } else { ++ loadFlags = FT_LOAD_TARGET_LCD; ++ } ++ } ++ break; ++ default: ++ SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting()); ++ break; ++ } ++ } ++ ++ fScaledFont = cairo_scaled_font_create(fontFace, &fontMatrix, &ctMatrix, fontOptions); ++ ++ if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) { ++ loadFlags |= FT_LOAD_NO_BITMAP; ++ } ++ ++ // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct ++ // advances, as fontconfig and cairo do. ++ // See http://code.google.com/p/skia/issues/detail?id=222. ++ loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; ++ ++ fLoadGlyphFlags = loadFlags; ++} ++ ++SkScalerContext_CairoFT::~SkScalerContext_CairoFT() ++{ ++ cairo_scaled_font_destroy(fScaledFont); ++} ++ ++unsigned SkScalerContext_CairoFT::generateGlyphCount() ++{ ++ CairoLockedFTFace faceLock(fScaledFont); ++ return faceLock.getFace()->num_glyphs; ++} ++ ++uint16_t SkScalerContext_CairoFT::generateCharToGlyph(SkUnichar uniChar) ++{ ++ CairoLockedFTFace faceLock(fScaledFont); ++ return SkToU16(FT_Get_Char_Index(faceLock.getFace(), uniChar)); ++} ++ ++void SkScalerContext_CairoFT::generateAdvance(SkGlyph* glyph) ++{ ++ generateMetrics(glyph); ++} ++ ++void SkScalerContext_CairoFT::generateMetrics(SkGlyph* glyph) ++{ ++ SkASSERT(fScaledFont != NULL); ++ cairo_text_extents_t extents; ++ cairo_glyph_t cairoGlyph = { glyph->getGlyphID(fBaseGlyphCount), 0.0, 0.0 }; ++ cairo_scaled_font_glyph_extents(fScaledFont, &cairoGlyph, 1, &extents); ++ ++ glyph->fAdvanceX = SkDoubleToFixed(extents.x_advance); ++ glyph->fAdvanceY = SkDoubleToFixed(extents.y_advance); ++ glyph->fWidth = SkToU16(SkScalarCeil(extents.width)); ++ glyph->fHeight = SkToU16(SkScalarCeil(extents.height)); ++ glyph->fLeft = SkToS16(SkScalarCeil(extents.x_bearing)); ++ glyph->fTop = SkToS16(SkScalarCeil(extents.y_bearing)); ++ glyph->fLsbDelta = 0; ++ glyph->fRsbDelta = 0; ++} ++ ++void SkScalerContext_CairoFT::generateImage(const SkGlyph& glyph) ++{ ++ SkASSERT(fScaledFont != NULL); ++ CairoLockedFTFace faceLock(fScaledFont); ++ FT_Face face = faceLock.getFace(); ++ ++ FT_Error err = FT_Load_Glyph(face, glyph.getGlyphID(fBaseGlyphCount), fLoadGlyphFlags); ++ ++ if (err != 0) { ++ memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); ++ return; ++ } ++ ++ generateGlyphImage(face, glyph); ++} ++ ++void SkScalerContext_CairoFT::generatePath(const SkGlyph& glyph, SkPath* path) ++{ ++ SkASSERT(fScaledFont != NULL); ++ CairoLockedFTFace faceLock(fScaledFont); ++ FT_Face face = faceLock.getFace(); ++ ++ SkASSERT(&glyph && path); ++ ++ uint32_t flags = fLoadGlyphFlags; ++ flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline ++ flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline) ++ ++ FT_Error err = FT_Load_Glyph(face, glyph.getGlyphID(fBaseGlyphCount), flags); ++ ++ if (err != 0) { ++ path->reset(); ++ return; ++ } ++ ++ generateGlyphPath(face, path); ++} ++ ++void SkScalerContext_CairoFT::generateFontMetrics(SkPaint::FontMetrics* mx, ++ SkPaint::FontMetrics* my) ++{ ++ SkDEBUGCODE(SkDebugf("SkScalerContext_CairoFT::generateFontMetrics unimplemented\n")); ++} ++ ++SkUnichar SkScalerContext_CairoFT::generateGlyphToChar(uint16_t glyph) ++{ ++ SkASSERT(fScaledFont != NULL); ++ CairoLockedFTFace faceLock(fScaledFont); ++ FT_Face face = faceLock.getFace(); ++ ++ FT_UInt glyphIndex; ++ SkUnichar charCode = FT_Get_First_Char(face, &glyphIndex); ++ while (glyphIndex != 0) { ++ if (glyphIndex == glyph) { ++ return charCode; ++ } ++ charCode = FT_Get_Next_Char(face, charCode, &glyphIndex); ++ } ++ ++ return 0; ++} ++ ++#ifdef SK_BUILD_FOR_ANDROID ++SkTypeface* SkAndroidNextLogicalTypeface(SkFontID currFontID, ++ SkFontID origFontID) { ++ return NULL; ++} ++#endif ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++#include "SkFontMgr.h" ++ ++SkFontMgr* SkFontMgr::Factory() { ++ // todo ++ return NULL; ++} ++ +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0005-Bug-777614-Re-apply-bug-687188-Expand-the-gradient-c.patch b/gfx/skia/patches/archive/0005-Bug-777614-Re-apply-bug-687188-Expand-the-gradient-c.patch new file mode 100644 index 0000000000..cfcb40b9d7 --- /dev/null +++ b/gfx/skia/patches/archive/0005-Bug-777614-Re-apply-bug-687188-Expand-the-gradient-c.patch @@ -0,0 +1,198 @@ +From 1ab13a923399aa638388231baca784ba89f2c82b Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Wed, 12 Sep 2012 12:30:29 -0400 +Subject: [PATCH 5/9] Bug 777614 - Re-apply bug 687188 - Expand the gradient + cache by 2 to store 0/1 colour stop values for + clamping. r=nrc + +--- + .../src/effects/gradients/SkGradientShader.cpp | 22 +++++++++++---- + .../src/effects/gradients/SkGradientShaderPriv.h | 5 +++- + .../src/effects/gradients/SkLinearGradient.cpp | 32 ++++++++++++++++------ + .../gradients/SkTwoPointConicalGradient.cpp | 11 ++++++-- + .../effects/gradients/SkTwoPointRadialGradient.cpp | 11 ++++++-- + 5 files changed, 61 insertions(+), 20 deletions(-) + +diff --git a/gfx/skia/src/effects/gradients/SkGradientShader.cpp b/gfx/skia/src/effects/gradients/SkGradientShader.cpp +index f0dac4d..79e7202 100644 +--- a/gfx/skia/src/effects/gradients/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/gradients/SkGradientShader.cpp +@@ -426,15 +426,15 @@ static void complete_32bit_cache(SkPMColor* cache, int stride) { + + const SkPMColor* SkGradientShaderBase::getCache32() const { + if (fCache32 == NULL) { +- // double the count for dither entries +- const int entryCount = kCache32Count * 2; ++ // double the count for dither entries, and have an extra two entries for clamp values ++ const int entryCount = kCache32Count * 2 + 2; + const size_t allocSize = sizeof(SkPMColor) * entryCount; + + if (NULL == fCache32PixelRef) { + fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + } +- fCache32 = (SkPMColor*)fCache32PixelRef->getAddr(); ++ fCache32 = (SkPMColor*)fCache32PixelRef->getAddr() + 1; + if (fColorCount == 2) { + Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1], + kGradient32Length, fCacheAlpha); +@@ -458,7 +458,7 @@ const SkPMColor* SkGradientShaderBase::getCache32() const { + SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + SkPMColor* linear = fCache32; // just computed linear data +- SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data ++ SkPMColor* mapped = (SkPMColor*)newPR->getAddr() + 1; // storage for mapped data + SkUnitMapper* map = fMapper; + for (int i = 0; i < kGradient32Length; i++) { + int index = map->mapUnit16((i << 8) | i) >> 8; +@@ -467,10 +467,22 @@ const SkPMColor* SkGradientShaderBase::getCache32() const { + } + fCache32PixelRef->unref(); + fCache32PixelRef = newPR; +- fCache32 = (SkPMColor*)newPR->getAddr(); ++ fCache32 = (SkPMColor*)newPR->getAddr() + 1; + } + complete_32bit_cache(fCache32, kCache32Count); + } ++ ++ // Write the clamp colours into the first and last entries of fCache32 ++ fCache32[kCache32ClampLower] = SkPackARGB32(fCacheAlpha, ++ SkColorGetR(fOrigColors[0]), ++ SkColorGetG(fOrigColors[0]), ++ SkColorGetB(fOrigColors[0])); ++ ++ fCache32[kCache32ClampUpper] = SkPackARGB32(fCacheAlpha, ++ SkColorGetR(fOrigColors[fColorCount - 1]), ++ SkColorGetG(fOrigColors[fColorCount - 1]), ++ SkColorGetB(fOrigColors[fColorCount - 1])); ++ + return fCache32; + } + +diff --git a/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h b/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h +index 0e7c2fc..7427935 100644 +--- a/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h ++++ b/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h +@@ -133,7 +133,10 @@ public: + kDitherStride32 = 0, + #endif + kDitherStride16 = kCache16Count, +- kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1 ++ kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1, ++ ++ kCache32ClampLower = -1, ++ kCache32ClampUpper = kCache32Count * 2 + }; + + +diff --git a/gfx/skia/src/effects/gradients/SkLinearGradient.cpp b/gfx/skia/src/effects/gradients/SkLinearGradient.cpp +index bcebc26..d400b4d 100644 +--- a/gfx/skia/src/effects/gradients/SkLinearGradient.cpp ++++ b/gfx/skia/src/effects/gradients/SkLinearGradient.cpp +@@ -126,6 +126,17 @@ void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { ++ if (proc == clamp_tileproc) { ++ // No need to lerp or dither for clamp values ++ if (fx < 0) { ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampLower], count); ++ return; ++ } else if (fx > 0xffff) { ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampUpper], count); ++ return; ++ } ++ } ++ + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. +@@ -144,6 +155,17 @@ void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { ++ if (proc == clamp_tileproc) { ++ // No need to lerp or dither for clamp values ++ if (fx < 0) { ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampLower], count); ++ return; ++ } else if (fx > 0xffff) { ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampUpper], count); ++ return; ++ } ++ } ++ + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. +@@ -169,10 +191,7 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, + range.init(fx, dx, count, 0, SkGradientShaderBase::kGradient32Length); + + if ((count = range.fCount0) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV0], +- cache[(toggle ^ SkGradientShaderBase::kDitherStride32) + range.fV0], +- count); ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampLower], count); + dstC += count; + } + if ((count = range.fCount1) > 0) { +@@ -191,10 +210,7 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, + } + } + if ((count = range.fCount2) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV1], +- cache[(toggle ^ SkGradientShaderBase::kDitherStride32) + range.fV1], +- count); ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampUpper], count); + } + } + +diff --git a/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp b/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp +index 3466d2c..764a444 100644 +--- a/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp ++++ b/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp +@@ -123,9 +123,14 @@ static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC, + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { +- SkFixed index = SkClampMax(t, 0xFFFF); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift]; ++ if (t < 0) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampLower]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampUpper]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> SkGradientShaderBase::kCache32Shift]; ++ } + } + } + } +diff --git a/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp b/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp +index 9362ded..22b028e 100644 +--- a/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp ++++ b/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp +@@ -120,9 +120,14 @@ void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx, + for (; count > 0; --count) { + SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, + fOneOverTwoA, posRoot); +- SkFixed index = SkClampMax(t, 0xFFFF); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift]; ++ if (t < 0) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampLower]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampUpper]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> SkGradientShaderBase::kCache32Shift]; ++ } + fx += dx; + fy += dy; + b += db; +-- +1.7.11.4 + diff --git a/gfx/skia/patches/archive/0006-Bug-751814-ARM-EDSP-ARMv6-Skia-fixes.patch b/gfx/skia/patches/archive/0006-Bug-751814-ARM-EDSP-ARMv6-Skia-fixes.patch new file mode 100644 index 0000000000..eb75691ad7 --- /dev/null +++ b/gfx/skia/patches/archive/0006-Bug-751814-ARM-EDSP-ARMv6-Skia-fixes.patch @@ -0,0 +1,147 @@ +From 94916fbbc7865c6fe23a57d6edc48c6daf93dda8 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:16:08 -0400 +Subject: [PATCH 06/10] Bug 755869 - [9] Re-apply bug 751814 - Various + Skia fixes for ARM without EDSP and ARMv6+ + r=mattwoodrow + +--- + gfx/skia/include/core/SkMath.h | 5 +-- + gfx/skia/include/core/SkPostConfig.h | 45 ++++++++++++++++++++++ + gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp | 6 +- + gfx/skia/src/opts/SkBlitRow_opts_arm.cpp | 9 ++++ + 4 files changed, 58 insertions(+), 7 deletions(-) + +diff --git a/gfx/skia/include/core/SkMath.h b/gfx/skia/include/core/SkMath.h +index 5889103..7a4b707 100644 +--- a/gfx/skia/include/core/SkMath.h ++++ b/gfx/skia/include/core/SkMath.h +@@ -153,10 +153,7 @@ static inline bool SkIsPow2(int value) { + With this requirement, we can generate faster instructions on some + architectures. + */ +-#if defined(__arm__) \ +- && !defined(__thumb__) \ +- && !defined(__ARM_ARCH_4T__) \ +- && !defined(__ARM_ARCH_5T__) ++#ifdef SK_ARM_HAS_EDSP + static inline int32_t SkMulS16(S16CPU x, S16CPU y) { + SkASSERT((int16_t)x == x); + SkASSERT((int16_t)y == y); +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +index 041fe2a..03105e4 100644 +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -311,3 +311,48 @@ + #ifndef SK_ALLOW_STATIC_GLOBAL_INITIALIZERS + #define SK_ALLOW_STATIC_GLOBAL_INITIALIZERS 1 + #endif ++ ++////////////////////////////////////////////////////////////////////// ++// ARM defines ++ ++#if defined(__GNUC__) && defined(__arm__) ++ ++# define SK_ARM_ARCH 3 ++ ++# if defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) \ ++ || defined(_ARM_ARCH_4) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 4 ++# endif ++ ++# if defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) \ ++ || defined(__ARM_ARCH_5E__) || defined(__ARM_ARCH_5TE__) \ ++ || defined(__ARM_ARCH_5TEJ__) || defined(_ARM_ARCH_5) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 5 ++# endif ++ ++# if defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ ++ || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ ++ || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \ ++ || defined(__ARM_ARCH_6M__) || defined(_ARM_ARCH_6) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 6 ++# endif ++ ++# if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ ++ || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ ++ || defined(__ARM_ARCH_7EM__) || defined(_ARM_ARCH_7) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 7 ++# endif ++ ++# undef SK_ARM_HAS_EDSP ++# if defined(__thumb2__) && (SK_ARM_ARCH >= 6) \ ++ || !defined(__thumb__) \ ++ && ((SK_ARM_ARCH > 5) || defined(__ARM_ARCH_5E__) \ ++ || defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__)) ++# define SK_ARM_HAS_EDSP 1 ++# endif ++ ++#endif +diff --git a/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp b/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp +index 20d62e1..deb1bfe 100644 +--- a/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp ++++ b/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp +@@ -11,7 +11,7 @@ + #include "SkColorPriv.h" + #include "SkUtils.h" + +-#if __ARM_ARCH__ >= 6 && !defined(SK_CPU_BENDIAN) ++#if SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN) + void SI8_D16_nofilter_DX_arm( + const SkBitmapProcState& s, + const uint32_t* SK_RESTRICT xy, +@@ -182,7 +182,7 @@ void SI8_opaque_D32_nofilter_DX_arm(const SkBitmapProcState& s, + + s.fBitmap->getColorTable()->unlockColors(false); + } +-#endif //__ARM_ARCH__ >= 6 && !defined(SK_CPU_BENDIAN) ++#endif // SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN) + + /////////////////////////////////////////////////////////////////////////////// + +@@ -200,7 +200,7 @@ void SkBitmapProcState::platformProcs() { + + switch (fBitmap->config()) { + case SkBitmap::kIndex8_Config: +-#if __ARM_ARCH__ >= 6 && !defined(SK_CPU_BENDIAN) ++#if SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN) + if (justDx && !doFilter) { + #if 0 /* crashing on android device */ + fSampleProc16 = SI8_D16_nofilter_DX_arm; +diff --git a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +index 2490371..c928888 100644 +--- a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp ++++ b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +@@ -675,8 +675,13 @@ static void __attribute((noinline,optimize("-fomit-frame-pointer"))) S32A_Blend_ + /* dst1_scale and dst2_scale*/ + "lsr r9, r5, #24 \n\t" /* src >> 24 */ + "lsr r10, r6, #24 \n\t" /* src >> 24 */ ++#ifdef SK_ARM_HAS_EDSP + "smulbb r9, r9, %[alpha] \n\t" /* r9 = SkMulS16 r9 with src_scale */ + "smulbb r10, r10, %[alpha] \n\t" /* r10 = SkMulS16 r10 with src_scale */ ++#else ++ "mul r9, r9, %[alpha] \n\t" /* r9 = SkMulS16 r9 with src_scale */ ++ "mul r10, r10, %[alpha] \n\t" /* r10 = SkMulS16 r10 with src_scale */ ++#endif + "lsr r9, r9, #8 \n\t" /* r9 >> 8 */ + "lsr r10, r10, #8 \n\t" /* r10 >> 8 */ + "rsb r9, r9, #256 \n\t" /* dst1_scale = r9 = 255 - r9 + 1 */ +@@ -745,7 +750,11 @@ static void __attribute((noinline,optimize("-fomit-frame-pointer"))) S32A_Blend_ + + "lsr r6, r5, #24 \n\t" /* src >> 24 */ + "and r8, r12, r5, lsr #8 \n\t" /* ag = r8 = r5 masked by r12 lsr by #8 */ ++#ifdef SK_ARM_HAS_EDSP + "smulbb r6, r6, %[alpha] \n\t" /* r6 = SkMulS16 with src_scale */ ++#else ++ "mul r6, r6, %[alpha] \n\t" /* r6 = SkMulS16 with src_scale */ ++#endif + "and r9, r12, r5 \n\t" /* rb = r9 = r5 masked by r12 */ + "lsr r6, r6, #8 \n\t" /* r6 >> 8 */ + "mul r8, r8, %[alpha] \n\t" /* ag = r8 times scale */ +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0006-Bug-848491-Re-apply-Bug-777614-Add-our-SkUserConfig..patch b/gfx/skia/patches/archive/0006-Bug-848491-Re-apply-Bug-777614-Add-our-SkUserConfig..patch new file mode 100644 index 0000000000..2850000ace --- /dev/null +++ b/gfx/skia/patches/archive/0006-Bug-848491-Re-apply-Bug-777614-Add-our-SkUserConfig..patch @@ -0,0 +1,27 @@ +From: George Wright +Date: Thu, 25 Apr 2013 20:40:12 -0400 +Subject: Bug 848491 - Re-apply Bug 777614 - Add our SkUserConfig.h + + +diff --git a/gfx/skia/include/config/SkUserConfig.h b/gfx/skia/include/config/SkUserConfig.h +index 63fc90d..c965e91 100644 +--- a/gfx/skia/include/config/SkUserConfig.h ++++ b/gfx/skia/include/config/SkUserConfig.h +@@ -201,4 +201,14 @@ + */ + //#define SK_SUPPORT_GPU 1 + ++/* Don't dither 32bit gradients, to match what the canvas test suite expects. ++ */ ++#define SK_DISABLE_DITHER_32BIT_GRADIENT ++ ++/* Don't include stdint.h on windows as it conflicts with our build system. ++ */ ++#ifdef SK_BUILD_FOR_WIN32 ++ #define SK_IGNORE_STDINT_DOT_H ++#endif ++ + #endif +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0007-Bug-719872-Old-Android-FontHost.patch b/gfx/skia/patches/archive/0007-Bug-719872-Old-Android-FontHost.patch new file mode 100644 index 0000000000..ca34e1a457 --- /dev/null +++ b/gfx/skia/patches/archive/0007-Bug-719872-Old-Android-FontHost.patch @@ -0,0 +1,702 @@ +From 6982ad469adcdfa2b7bdbf8bbd843bc22d3832fc Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Fri, 18 May 2012 14:52:40 -0400 +Subject: [PATCH 07/10] Bug 755869 - [10] Re-apply bug 719872 - Fix crash + on Android by reverting to older FontHost impl + r=mattwoodrow + +--- + gfx/skia/Makefile.in | 5 +- + gfx/skia/src/ports/SkFontHost_android_old.cpp | 664 +++++++++++++++++++++++++ + 2 files changed, 668 insertions(+), 1 deletions(-) + create mode 100644 gfx/skia/src/ports/SkFontHost_android_old.cpp + +diff --git a/gfx/skia/Makefile.in b/gfx/skia/Makefile.in +index 9da098a..8184f1c 100644 +--- a/gfx/skia/Makefile.in ++++ b/gfx/skia/Makefile.in +@@ -327,7 +327,10 @@ endif + ifeq (android,$(MOZ_WIDGET_TOOLKIT)) + CPPSRCS += \ + SkDebug_android.cpp \ +- SkFontHost_none.cpp \ ++ SkFontHost_android_old.cpp \ ++ SkFontHost_gamma.cpp \ ++ SkFontHost_FreeType.cpp \ ++ SkFontHost_tables.cpp \ + SkMMapStream.cpp \ + SkTime_Unix.cpp \ + SkThread_pthread.cpp \ +diff --git a/gfx/skia/src/ports/SkFontHost_android_old.cpp b/gfx/skia/src/ports/SkFontHost_android_old.cpp +new file mode 100644 +index 0000000..b5c4f3c +--- /dev/null ++++ b/gfx/skia/src/ports/SkFontHost_android_old.cpp +@@ -0,0 +1,664 @@ ++ ++/* ++ * Copyright 2006 The Android Open Source Project ++ * ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. ++ */ ++ ++ ++#include "SkFontHost.h" ++#include "SkDescriptor.h" ++#include "SkMMapStream.h" ++#include "SkPaint.h" ++#include "SkString.h" ++#include "SkStream.h" ++#include "SkThread.h" ++#include "SkTSearch.h" ++#include ++ ++#define FONT_CACHE_MEMORY_BUDGET (768 * 1024) ++ ++#ifndef SK_FONT_FILE_PREFIX ++ #define SK_FONT_FILE_PREFIX "/fonts/" ++#endif ++ ++bool find_name_and_attributes(SkStream* stream, SkString* name, SkTypeface::Style* style, ++ bool* isFixedWidth); ++ ++static void GetFullPathForSysFonts(SkString* full, const char name[]) { ++ full->set(getenv("ANDROID_ROOT")); ++ full->append(SK_FONT_FILE_PREFIX); ++ full->append(name); ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++struct FamilyRec; ++ ++/* This guy holds a mapping of a name -> family, used for looking up fonts. ++ Since it is stored in a stretchy array that doesn't preserve object ++ semantics, we don't use constructor/destructors, but just have explicit ++ helpers to manage our internal bookkeeping. ++*/ ++struct NameFamilyPair { ++ const char* fName; // we own this ++ FamilyRec* fFamily; // we don't own this, we just reference it ++ ++ void construct(const char name[], FamilyRec* family) { ++ fName = strdup(name); ++ fFamily = family; // we don't own this, so just record the referene ++ } ++ ++ void destruct() { ++ free((char*)fName); ++ // we don't own family, so just ignore our reference ++ } ++}; ++ ++// we use atomic_inc to grow this for each typeface we create ++static int32_t gUniqueFontID; ++ ++// this is the mutex that protects these globals ++static SkMutex gFamilyMutex; ++static FamilyRec* gFamilyHead; ++static SkTDArray gNameList; ++ ++struct FamilyRec { ++ FamilyRec* fNext; ++ SkTypeface* fFaces[4]; ++ ++ FamilyRec() ++ { ++ fNext = gFamilyHead; ++ memset(fFaces, 0, sizeof(fFaces)); ++ gFamilyHead = this; ++ } ++}; ++ ++static SkTypeface* find_best_face(const FamilyRec* family, ++ SkTypeface::Style style) { ++ SkTypeface* const* faces = family->fFaces; ++ ++ if (faces[style] != NULL) { // exact match ++ return faces[style]; ++ } ++ // look for a matching bold ++ style = (SkTypeface::Style)(style ^ SkTypeface::kItalic); ++ if (faces[style] != NULL) { ++ return faces[style]; ++ } ++ // look for the plain ++ if (faces[SkTypeface::kNormal] != NULL) { ++ return faces[SkTypeface::kNormal]; ++ } ++ // look for anything ++ for (int i = 0; i < 4; i++) { ++ if (faces[i] != NULL) { ++ return faces[i]; ++ } ++ } ++ // should never get here, since the faces list should not be empty ++ SkASSERT(!"faces list is empty"); ++ return NULL; ++} ++ ++static FamilyRec* find_family(const SkTypeface* member) { ++ FamilyRec* curr = gFamilyHead; ++ while (curr != NULL) { ++ for (int i = 0; i < 4; i++) { ++ if (curr->fFaces[i] == member) { ++ return curr; ++ } ++ } ++ curr = curr->fNext; ++ } ++ return NULL; ++} ++ ++/* Returns the matching typeface, or NULL. If a typeface is found, its refcnt ++ is not modified. ++ */ ++static SkTypeface* find_from_uniqueID(uint32_t uniqueID) { ++ FamilyRec* curr = gFamilyHead; ++ while (curr != NULL) { ++ for (int i = 0; i < 4; i++) { ++ SkTypeface* face = curr->fFaces[i]; ++ if (face != NULL && face->uniqueID() == uniqueID) { ++ return face; ++ } ++ } ++ curr = curr->fNext; ++ } ++ return NULL; ++} ++ ++/* Remove reference to this face from its family. If the resulting family ++ is empty (has no faces), return that family, otherwise return NULL ++*/ ++static FamilyRec* remove_from_family(const SkTypeface* face) { ++ FamilyRec* family = find_family(face); ++ SkASSERT(family->fFaces[face->style()] == face); ++ family->fFaces[face->style()] = NULL; ++ ++ for (int i = 0; i < 4; i++) { ++ if (family->fFaces[i] != NULL) { // family is non-empty ++ return NULL; ++ } ++ } ++ return family; // return the empty family ++} ++ ++// maybe we should make FamilyRec be doubly-linked ++static void detach_and_delete_family(FamilyRec* family) { ++ FamilyRec* curr = gFamilyHead; ++ FamilyRec* prev = NULL; ++ ++ while (curr != NULL) { ++ FamilyRec* next = curr->fNext; ++ if (curr == family) { ++ if (prev == NULL) { ++ gFamilyHead = next; ++ } else { ++ prev->fNext = next; ++ } ++ SkDELETE(family); ++ return; ++ } ++ prev = curr; ++ curr = next; ++ } ++ SkASSERT(!"Yikes, couldn't find family in our list to remove/delete"); ++} ++ ++static SkTypeface* find_typeface(const char name[], SkTypeface::Style style) { ++ NameFamilyPair* list = gNameList.begin(); ++ int count = gNameList.count(); ++ ++ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); ++ ++ if (index >= 0) { ++ return find_best_face(list[index].fFamily, style); ++ } ++ return NULL; ++} ++ ++static SkTypeface* find_typeface(const SkTypeface* familyMember, ++ SkTypeface::Style style) { ++ const FamilyRec* family = find_family(familyMember); ++ return family ? find_best_face(family, style) : NULL; ++} ++ ++static void add_name(const char name[], FamilyRec* family) { ++ SkAutoAsciiToLC tolc(name); ++ name = tolc.lc(); ++ ++ NameFamilyPair* list = gNameList.begin(); ++ int count = gNameList.count(); ++ ++ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); ++ ++ if (index < 0) { ++ list = gNameList.insert(~index); ++ list->construct(name, family); ++ } ++} ++ ++static void remove_from_names(FamilyRec* emptyFamily) ++{ ++#ifdef SK_DEBUG ++ for (int i = 0; i < 4; i++) { ++ SkASSERT(emptyFamily->fFaces[i] == NULL); ++ } ++#endif ++ ++ SkTDArray& list = gNameList; ++ ++ // must go backwards when removing ++ for (int i = list.count() - 1; i >= 0; --i) { ++ NameFamilyPair* pair = &list[i]; ++ if (pair->fFamily == emptyFamily) { ++ pair->destruct(); ++ list.remove(i); ++ } ++ } ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++class FamilyTypeface : public SkTypeface { ++public: ++ FamilyTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ bool isFixedWidth) ++ : SkTypeface(style, sk_atomic_inc(&gUniqueFontID) + 1, isFixedWidth) { ++ fIsSysFont = sysFont; ++ ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyRec* rec = NULL; ++ if (familyMember) { ++ rec = find_family(familyMember); ++ SkASSERT(rec); ++ } else { ++ rec = SkNEW(FamilyRec); ++ } ++ rec->fFaces[style] = this; ++ } ++ ++ virtual ~FamilyTypeface() { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ // remove us from our family. If the family is now empty, we return ++ // that and then remove that family from the name list ++ FamilyRec* family = remove_from_family(this); ++ if (NULL != family) { ++ remove_from_names(family); ++ detach_and_delete_family(family); ++ } ++ } ++ ++ bool isSysFont() const { return fIsSysFont; } ++ ++ virtual SkStream* openStream() = 0; ++ virtual const char* getUniqueString() const = 0; ++ virtual const char* getFilePath() const = 0; ++ ++private: ++ bool fIsSysFont; ++ ++ typedef SkTypeface INHERITED; ++}; ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++class StreamTypeface : public FamilyTypeface { ++public: ++ StreamTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ SkStream* stream, bool isFixedWidth) ++ : INHERITED(style, sysFont, familyMember, isFixedWidth) { ++ SkASSERT(stream); ++ stream->ref(); ++ fStream = stream; ++ } ++ virtual ~StreamTypeface() { ++ fStream->unref(); ++ } ++ ++ // overrides ++ virtual SkStream* openStream() { ++ // we just ref our existing stream, since the caller will call unref() ++ // when they are through ++ fStream->ref(); ++ // must rewind each time, since the caller assumes a "new" stream ++ fStream->rewind(); ++ return fStream; ++ } ++ virtual const char* getUniqueString() const { return NULL; } ++ virtual const char* getFilePath() const { return NULL; } ++ ++private: ++ SkStream* fStream; ++ ++ typedef FamilyTypeface INHERITED; ++}; ++ ++class FileTypeface : public FamilyTypeface { ++public: ++ FileTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ const char path[], bool isFixedWidth) ++ : INHERITED(style, sysFont, familyMember, isFixedWidth) { ++ SkString fullpath; ++ ++ if (sysFont) { ++ GetFullPathForSysFonts(&fullpath, path); ++ path = fullpath.c_str(); ++ } ++ fPath.set(path); ++ } ++ ++ // overrides ++ virtual SkStream* openStream() { ++ SkStream* stream = SkNEW_ARGS(SkMMAPStream, (fPath.c_str())); ++ ++ // check for failure ++ if (stream->getLength() <= 0) { ++ SkDELETE(stream); ++ // maybe MMAP isn't supported. try FILE ++ stream = SkNEW_ARGS(SkFILEStream, (fPath.c_str())); ++ if (stream->getLength() <= 0) { ++ SkDELETE(stream); ++ stream = NULL; ++ } ++ } ++ return stream; ++ } ++ virtual const char* getUniqueString() const { ++ const char* str = strrchr(fPath.c_str(), '/'); ++ if (str) { ++ str += 1; // skip the '/' ++ } ++ return str; ++ } ++ virtual const char* getFilePath() const { ++ return fPath.c_str(); ++ } ++ ++private: ++ SkString fPath; ++ ++ typedef FamilyTypeface INHERITED; ++}; ++ ++/////////////////////////////////////////////////////////////////////////////// ++/////////////////////////////////////////////////////////////////////////////// ++ ++static bool get_name_and_style(const char path[], SkString* name, ++ SkTypeface::Style* style, ++ bool* isFixedWidth, bool isExpected) { ++ SkString fullpath; ++ GetFullPathForSysFonts(&fullpath, path); ++ ++ SkMMAPStream stream(fullpath.c_str()); ++ if (stream.getLength() > 0) { ++ find_name_and_attributes(&stream, name, style, isFixedWidth); ++ return true; ++ } ++ else { ++ SkFILEStream stream(fullpath.c_str()); ++ if (stream.getLength() > 0) { ++ find_name_and_attributes(&stream, name, style, isFixedWidth); ++ return true; ++ } ++ } ++ ++ if (isExpected) { ++ SkDebugf("---- failed to open <%s> as a font\n", fullpath.c_str()); ++ } ++ return false; ++} ++ ++// used to record our notion of the pre-existing fonts ++struct FontInitRec { ++ const char* fFileName; ++ const char* const* fNames; // null-terminated list ++}; ++ ++static const char* gSansNames[] = { ++ "sans-serif", "arial", "helvetica", "tahoma", "verdana", NULL ++}; ++ ++static const char* gSerifNames[] = { ++ "serif", "times", "times new roman", "palatino", "georgia", "baskerville", ++ "goudy", "fantasy", "cursive", "ITC Stone Serif", NULL ++}; ++ ++static const char* gMonoNames[] = { ++ "monospace", "courier", "courier new", "monaco", NULL ++}; ++ ++// deliberately empty, but we use the address to identify fallback fonts ++static const char* gFBNames[] = { NULL }; ++ ++/* Fonts must be grouped by family, with the first font in a family having the ++ list of names (even if that list is empty), and the following members having ++ null for the list. The names list must be NULL-terminated ++*/ ++static const FontInitRec gSystemFonts[] = { ++ { "DroidSans.ttf", gSansNames }, ++ { "DroidSans-Bold.ttf", NULL }, ++ { "DroidSerif-Regular.ttf", gSerifNames }, ++ { "DroidSerif-Bold.ttf", NULL }, ++ { "DroidSerif-Italic.ttf", NULL }, ++ { "DroidSerif-BoldItalic.ttf", NULL }, ++ { "DroidSansMono.ttf", gMonoNames }, ++ /* These are optional, and can be ignored if not found in the file system. ++ These are appended to gFallbackFonts[] as they are seen, so we list ++ them in the order we want them to be accessed by NextLogicalFont(). ++ */ ++ { "DroidSansArabic.ttf", gFBNames }, ++ { "DroidSansHebrew.ttf", gFBNames }, ++ { "DroidSansThai.ttf", gFBNames }, ++ { "MTLmr3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "MTLc3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "DroidSansJapanese.ttf", gFBNames }, ++ { "DroidSansFallback.ttf", gFBNames } ++}; ++ ++#define DEFAULT_NAMES gSansNames ++ ++// these globals are assigned (once) by load_system_fonts() ++static FamilyRec* gDefaultFamily; ++static SkTypeface* gDefaultNormal; ++ ++/* This is sized conservatively, assuming that it will never be a size issue. ++ It will be initialized in load_system_fonts(), and will be filled with the ++ fontIDs that can be used for fallback consideration, in sorted order (sorted ++ meaning element[0] should be used first, then element[1], etc. When we hit ++ a fontID==0 in the array, the list is done, hence our allocation size is ++ +1 the total number of possible system fonts. Also see NextLogicalFont(). ++ */ ++static uint32_t gFallbackFonts[SK_ARRAY_COUNT(gSystemFonts)+1]; ++ ++/* Called once (ensured by the sentinel check at the beginning of our body). ++ Initializes all the globals, and register the system fonts. ++ */ ++static void load_system_fonts() { ++ // check if we've already be called ++ if (NULL != gDefaultNormal) { ++ return; ++ } ++ ++ const FontInitRec* rec = gSystemFonts; ++ SkTypeface* firstInFamily = NULL; ++ int fallbackCount = 0; ++ ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ // if we're the first in a new family, clear firstInFamily ++ if (rec[i].fNames != NULL) { ++ firstInFamily = NULL; ++ } ++ ++ bool isFixedWidth; ++ SkString name; ++ SkTypeface::Style style; ++ ++ // we expect all the fonts, except the "fallback" fonts ++ bool isExpected = (rec[i].fNames != gFBNames); ++ if (!get_name_and_style(rec[i].fFileName, &name, &style, ++ &isFixedWidth, isExpected)) { ++ continue; ++ } ++ ++ SkTypeface* tf = SkNEW_ARGS(FileTypeface, ++ (style, ++ true, // system-font (cannot delete) ++ firstInFamily, // what family to join ++ rec[i].fFileName, ++ isFixedWidth) // filename ++ ); ++ ++ if (rec[i].fNames != NULL) { ++ // see if this is one of our fallback fonts ++ if (rec[i].fNames == gFBNames) { ++ // SkDebugf("---- adding %s as fallback[%d] fontID %d\n", ++ // rec[i].fFileName, fallbackCount, tf->uniqueID()); ++ gFallbackFonts[fallbackCount++] = tf->uniqueID(); ++ } ++ ++ firstInFamily = tf; ++ FamilyRec* family = find_family(tf); ++ const char* const* names = rec[i].fNames; ++ ++ // record the default family if this is it ++ if (names == DEFAULT_NAMES) { ++ gDefaultFamily = family; ++ } ++ // add the names to map to this family ++ while (*names) { ++ add_name(*names, family); ++ names += 1; ++ } ++ } ++ } ++ ++ // do this after all fonts are loaded. This is our default font, and it ++ // acts as a sentinel so we only execute load_system_fonts() once ++ gDefaultNormal = find_best_face(gDefaultFamily, SkTypeface::kNormal); ++ // now terminate our fallback list with the sentinel value ++ gFallbackFonts[fallbackCount] = 0; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) { ++ const char* name = ((FamilyTypeface*)face)->getUniqueString(); ++ ++ stream->write8((uint8_t)face->style()); ++ ++ if (NULL == name || 0 == *name) { ++ stream->writePackedUInt(0); ++// SkDebugf("--- fonthost serialize null\n"); ++ } else { ++ uint32_t len = strlen(name); ++ stream->writePackedUInt(len); ++ stream->write(name, len); ++// SkDebugf("--- fonthost serialize <%s> %d\n", name, face->style()); ++ } ++} ++ ++SkTypeface* SkFontHost::Deserialize(SkStream* stream) { ++ load_system_fonts(); ++ ++ int style = stream->readU8(); ++ ++ int len = stream->readPackedUInt(); ++ if (len > 0) { ++ SkString str; ++ str.resize(len); ++ stream->read(str.writable_str(), len); ++ ++ const FontInitRec* rec = gSystemFonts; ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ if (strcmp(rec[i].fFileName, str.c_str()) == 0) { ++ // backup until we hit the fNames ++ for (int j = i; j >= 0; --j) { ++ if (rec[j].fNames != NULL) { ++ return SkFontHost::CreateTypeface(NULL, ++ rec[j].fNames[0], (SkTypeface::Style)style); ++ } ++ } ++ } ++ } ++ } ++ return NULL; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, ++ const char familyName[], ++ SkTypeface::Style style) { ++ load_system_fonts(); ++ ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ // clip to legal style bits ++ style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic); ++ ++ SkTypeface* tf = NULL; ++ ++ if (NULL != familyFace) { ++ tf = find_typeface(familyFace, style); ++ } else if (NULL != familyName) { ++// SkDebugf("======= familyName <%s>\n", familyName); ++ tf = find_typeface(familyName, style); ++ } ++ ++ if (NULL == tf) { ++ tf = find_best_face(gDefaultFamily, style); ++ } ++ ++ // we ref(), since the symantic is to return a new instance ++ tf->ref(); ++ return tf; ++} ++ ++SkStream* SkFontHost::OpenStream(uint32_t fontID) { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyTypeface* tf = (FamilyTypeface*)find_from_uniqueID(fontID); ++ SkStream* stream = tf ? tf->openStream() : NULL; ++ ++ if (stream && stream->getLength() == 0) { ++ stream->unref(); ++ stream = NULL; ++ } ++ return stream; ++} ++ ++size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, ++ int32_t* index) { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyTypeface* tf = (FamilyTypeface*)find_from_uniqueID(fontID); ++ const char* src = tf ? tf->getFilePath() : NULL; ++ ++ if (src) { ++ size_t size = strlen(src); ++ if (path) { ++ memcpy(path, src, SkMin32(size, length)); ++ } ++ if (index) { ++ *index = 0; // we don't have collections (yet) ++ } ++ return size; ++ } else { ++ return 0; ++ } ++} ++ ++SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { ++ load_system_fonts(); ++ ++ /* First see if fontID is already one of our fallbacks. If so, return ++ its successor. If fontID is not in our list, then return the first one ++ in our list. Note: list is zero-terminated, and returning zero means ++ we have no more fonts to use for fallbacks. ++ */ ++ const uint32_t* list = gFallbackFonts; ++ for (int i = 0; list[i] != 0; i++) { ++ if (list[i] == currFontID) { ++ return list[i+1]; ++ } ++ } ++ return list[0]; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) { ++ if (NULL == stream || stream->getLength() <= 0) { ++ return NULL; ++ } ++ ++ bool isFixedWidth; ++ SkString name; ++ SkTypeface::Style style; ++ find_name_and_attributes(stream, &name, &style, &isFixedWidth); ++ ++ if (!name.isEmpty()) { ++ return SkNEW_ARGS(StreamTypeface, (style, false, NULL, stream, isFixedWidth)); ++ } else { ++ return NULL; ++ } ++} ++ ++SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) { ++ SkStream* stream = SkNEW_ARGS(SkMMAPStream, (path)); ++ SkTypeface* face = SkFontHost::CreateTypefaceFromStream(stream); ++ // since we created the stream, we let go of our ref() here ++ stream->unref(); ++ return face; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0007-Bug-848491-Re-apply-bug-687188-Expand-the-gradient-c.patch b/gfx/skia/patches/archive/0007-Bug-848491-Re-apply-bug-687188-Expand-the-gradient-c.patch new file mode 100644 index 0000000000..73bca9a48d --- /dev/null +++ b/gfx/skia/patches/archive/0007-Bug-848491-Re-apply-bug-687188-Expand-the-gradient-c.patch @@ -0,0 +1,168 @@ +From: George Wright +Date: Thu, 25 Apr 2013 20:47:06 -0400 +Subject: Bug 848491 - Re-apply bug 687188 - Expand the gradient cache by 2 to store 0/1 colour stop values for clamping. + + +diff --git a/gfx/skia/src/effects/gradients/SkGradientShader.cpp b/gfx/skia/src/effects/gradients/SkGradientShader.cpp +index 684355d..27a9c46 100644 +--- a/gfx/skia/src/effects/gradients/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/gradients/SkGradientShader.cpp +@@ -453,15 +453,15 @@ const uint16_t* SkGradientShaderBase::getCache16() const { + + const SkPMColor* SkGradientShaderBase::getCache32() const { + if (fCache32 == NULL) { +- // double the count for dither entries +- const int entryCount = kCache32Count * 4; ++ // double the count for dither entries, and have an extra two entries for clamp values ++ const int entryCount = kCache32Count * 4 + 2; + const size_t allocSize = sizeof(SkPMColor) * entryCount; + + if (NULL == fCache32PixelRef) { + fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + } +- fCache32 = (SkPMColor*)fCache32PixelRef->getAddr(); ++ fCache32 = (SkPMColor*)fCache32PixelRef->getAddr() + 1; + if (fColorCount == 2) { + Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1], + kCache32Count, fCacheAlpha); +@@ -484,7 +484,7 @@ const SkPMColor* SkGradientShaderBase::getCache32() const { + SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + SkPMColor* linear = fCache32; // just computed linear data +- SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data ++ SkPMColor* mapped = (SkPMColor*)newPR->getAddr() + 1; // storage for mapped data + SkUnitMapper* map = fMapper; + for (int i = 0; i < kCache32Count; i++) { + int index = map->mapUnit16((i << 8) | i) >> 8; +@@ -495,9 +495,21 @@ const SkPMColor* SkGradientShaderBase::getCache32() const { + } + fCache32PixelRef->unref(); + fCache32PixelRef = newPR; +- fCache32 = (SkPMColor*)newPR->getAddr(); ++ fCache32 = (SkPMColor*)newPR->getAddr() + 1; + } + } ++ ++ // Write the clamp colours into the first and last entries of fCache32 ++ fCache32[kCache32ClampLower] = SkPackARGB32(fCacheAlpha, ++ SkColorGetR(fOrigColors[0]), ++ SkColorGetG(fOrigColors[0]), ++ SkColorGetB(fOrigColors[0])); ++ ++ fCache32[kCache32ClampUpper] = SkPackARGB32(fCacheAlpha, ++ SkColorGetR(fOrigColors[fColorCount - 1]), ++ SkColorGetG(fOrigColors[fColorCount - 1]), ++ SkColorGetB(fOrigColors[fColorCount - 1])); ++ + return fCache32; + } + +diff --git a/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h b/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h +index 729ce4e..2cb6a9d 100644 +--- a/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h ++++ b/gfx/skia/src/effects/gradients/SkGradientShaderPriv.h +@@ -86,6 +86,9 @@ public: + /// if dithering is disabled. + kDitherStride32 = kCache32Count, + kDitherStride16 = kCache16Count, ++ ++ kCache32ClampLower = -1, ++ kCache32ClampUpper = kCache32Count * 4 + }; + + +diff --git a/gfx/skia/src/effects/gradients/SkLinearGradient.cpp b/gfx/skia/src/effects/gradients/SkLinearGradient.cpp +index e0f216c..40ab918 100644 +--- a/gfx/skia/src/effects/gradients/SkLinearGradient.cpp ++++ b/gfx/skia/src/effects/gradients/SkLinearGradient.cpp +@@ -127,6 +127,17 @@ void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { ++ if (proc == clamp_tileproc) { ++ // No need to lerp or dither for clamp values ++ if (fx < 0) { ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampLower], count); ++ return; ++ } else if (fx > 0xffff) { ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampUpper], count); ++ return; ++ } ++ } ++ + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. +@@ -154,10 +165,7 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, + range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1); + + if ((count = range.fCount0) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV0], +- cache[next_dither_toggle(toggle) + range.fV0], +- count); ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampLower], count); + dstC += count; + } + if ((count = range.fCount1) > 0) { +@@ -176,10 +184,7 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, + } + } + if ((count = range.fCount2) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV1], +- cache[next_dither_toggle(toggle) + range.fV1], +- count); ++ sk_memset32(dstC, cache[SkGradientShaderBase::kCache32ClampUpper], count); + } + } + +diff --git a/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp b/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp +index abd974b..601fff4 100644 +--- a/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp ++++ b/gfx/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp +@@ -124,10 +124,14 @@ static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC, + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { +- SkFixed index = SkClampMax(t, 0xFFFF); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[toggle + +- (index >> SkGradientShaderBase::kCache32Shift)]; ++ if (t < 0) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampLower]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampUpper]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> SkGradientShaderBase::kCache32Shift]; ++ } + } + toggle = next_dither_toggle(toggle); + } +diff --git a/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp b/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp +index f70b67d..ec2ae75 100644 +--- a/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp ++++ b/gfx/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp +@@ -120,9 +120,14 @@ void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx, + for (; count > 0; --count) { + SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, + fOneOverTwoA, posRoot); +- SkFixed index = SkClampMax(t, 0xFFFF); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift]; ++ if (t < 0) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampLower]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[SkGradientShaderBase::kCache32ClampUpper]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> SkGradientShaderBase::kCache32Shift]; ++ } + fx += dx; + fy += dy; + b += db; +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0008-Bug-687188-Skia-radial-gradients.patch b/gfx/skia/patches/archive/0008-Bug-687188-Skia-radial-gradients.patch new file mode 100644 index 0000000000..0f60dbd8ea --- /dev/null +++ b/gfx/skia/patches/archive/0008-Bug-687188-Skia-radial-gradients.patch @@ -0,0 +1,173 @@ +From f941ea32e44a2436d235e83ef1a434289a9d9c1e Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Wed, 23 May 2012 11:40:25 -0400 +Subject: [PATCH 08/10] Bug 755869 - [11] Re-apply bug 687188 - Skia + radial gradients should use the 0/1 color stop values + for clamping. r=mattwoodrow + +--- + gfx/skia/src/effects/SkGradientShader.cpp | 76 +++++++++++++++++++++++------ + 1 files changed, 61 insertions(+), 15 deletions(-) + +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +index 59ba48c..ea05a39 100644 +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -204,6 +204,7 @@ private: + mutable SkMallocPixelRef* fCache32PixelRef; + mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value + ++ static SkPMColor PremultiplyColor(SkColor c0, U8CPU alpha); + static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count); + static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count, + U8CPU alpha); +@@ -507,6 +508,21 @@ static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) { + return ((n << 1) - (n | (n >> 8))) >> 8; + } + ++SkPMColor Gradient_Shader::PremultiplyColor(SkColor c0, U8CPU paintAlpha) ++{ ++ SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha); ++ SkFixed r = SkColorGetR(c0); ++ SkFixed g = SkColorGetG(c0); ++ SkFixed b = SkColorGetB(c0); ++ ++ a = SkIntToFixed(a) + 0x8000; ++ r = SkIntToFixed(r) + 0x8000; ++ g = SkIntToFixed(g) + 0x8000; ++ b = SkIntToFixed(b) + 0x8000; ++ ++ return SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16); ++} ++ + void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1, + int count, U8CPU paintAlpha) { + SkASSERT(count > 1); +@@ -628,14 +644,14 @@ static void complete_32bit_cache(SkPMColor* cache, int stride) { + const SkPMColor* Gradient_Shader::getCache32() const { + if (fCache32 == NULL) { + // double the count for dither entries +- const int entryCount = kCache32Count * 2; ++ const int entryCount = kCache32Count * 2 + 2; + const size_t allocSize = sizeof(SkPMColor) * entryCount; + + if (NULL == fCache32PixelRef) { + fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + } +- fCache32 = (SkPMColor*)fCache32PixelRef->getAddr(); ++ fCache32 = (SkPMColor*)fCache32PixelRef->getAddr() + 1; + if (fColorCount == 2) { + Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1], + kGradient32Length, fCacheAlpha); +@@ -659,7 +675,7 @@ const SkPMColor* Gradient_Shader::getCache32() const { + SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + SkPMColor* linear = fCache32; // just computed linear data +- SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data ++ SkPMColor* mapped = (SkPMColor*)newPR->getAddr() + 1; // storage for mapped data + SkUnitMapper* map = fMapper; + for (int i = 0; i < kGradient32Length; i++) { + int index = map->mapUnit16((i << 8) | i) >> 8; +@@ -668,10 +684,13 @@ const SkPMColor* Gradient_Shader::getCache32() const { + } + fCache32PixelRef->unref(); + fCache32PixelRef = newPR; +- fCache32 = (SkPMColor*)newPR->getAddr(); ++ fCache32 = (SkPMColor*)newPR->getAddr() + 1; + } + complete_32bit_cache(fCache32, kCache32Count); + } ++ //Write the clamp colours into the first and last entries of fCache32 ++ fCache32[-1] = PremultiplyColor(fOrigColors[0], fCacheAlpha); ++ fCache32[kCache32Count * 2] = PremultiplyColor(fOrigColors[fColorCount - 1], fCacheAlpha); + return fCache32; + } + +@@ -857,6 +876,18 @@ void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { ++ if (proc == clamp_tileproc) { ++ // Read out clamp values from beginning/end of the cache. No need to lerp ++ // or dither ++ if (fx < 0) { ++ sk_memset32(dstC, cache[-1], count); ++ return; ++ } else if (fx > 0xFFFF) { ++ sk_memset32(dstC, cache[Gradient_Shader::kCache32Count * 2], count); ++ return; ++ } ++ } ++ + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. +@@ -875,6 +906,18 @@ void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { ++ if (proc == clamp_tileproc) { ++ // Read out clamp values from beginning/end of the cache. No need to lerp ++ // or dither ++ if (fx < 0) { ++ sk_memset32(dstC, cache[-1], count); ++ return; ++ } else if (fx > 0xFFFF) { ++ sk_memset32(dstC, cache[Gradient_Shader::kCache32Count * 2], count); ++ return; ++ } ++ } ++ + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. +@@ -900,10 +943,8 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, + range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length); + + if ((count = range.fCount0) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV0], +- cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0], +- count); ++ // Shouldn't be any need to dither for clamping? ++ sk_memset32(dstC, cache[-1], count); + dstC += count; + } + if ((count = range.fCount1) > 0) { +@@ -922,10 +963,8 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, + } + } + if ((count = range.fCount2) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV1], +- cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1], +- count); ++ // Shouldn't be any need to dither for clamping? ++ sk_memset32(dstC, cache[Gradient_Shader::kCache32Count * 2], count); + } + } + +@@ -1796,9 +1835,16 @@ void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx, + for (; count > 0; --count) { + SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, + fOneOverTwoA, posRoot); +- SkFixed index = SkClampMax(t, 0xFFFF); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> Gradient_Shader::kCache32Shift]; ++ ++ if (t < 0) { ++ *dstC++ = cache[-1]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[Gradient_Shader::kCache32Count * 2]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> Gradient_Shader::kCache32Shift]; ++ } ++ + fx += dx; + fy += dy; + b += db; +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0008-Bug-848491-Re-apply-759683-Handle-compilers-that-don.patch b/gfx/skia/patches/archive/0008-Bug-848491-Re-apply-759683-Handle-compilers-that-don.patch new file mode 100644 index 0000000000..58961d6e06 --- /dev/null +++ b/gfx/skia/patches/archive/0008-Bug-848491-Re-apply-759683-Handle-compilers-that-don.patch @@ -0,0 +1,35 @@ +From: George Wright +Date: Thu, 25 Apr 2013 20:49:45 -0400 +Subject: Bug 848491 - Re-apply 759683 - Handle compilers that don't support SSSE3 intrinsics + + +diff --git a/gfx/skia/src/opts/opts_check_SSE2.cpp b/gfx/skia/src/opts/opts_check_SSE2.cpp +index 6370058..18f68d6 100644 +--- a/gfx/skia/src/opts/opts_check_SSE2.cpp ++++ b/gfx/skia/src/opts/opts_check_SSE2.cpp +@@ -86,9 +86,13 @@ static inline bool hasSSSE3() { + #else + + static inline bool hasSSSE3() { ++#if defined(SK_BUILD_SSSE3) + int cpu_info[4] = { 0 }; + getcpuid(1, cpu_info); + return (cpu_info[2] & 0x200) != 0; ++#else ++ return false; ++#endif + } + #endif + +@@ -104,7 +108,7 @@ static bool cachedHasSSSE3() { + + void SkBitmapProcState::platformProcs() { + if (cachedHasSSSE3()) { +-#if !defined(SK_BUILD_FOR_ANDROID) ++#if !defined(SK_BUILD_FOR_ANDROID) && defined(SK_BUILD_SSSE3) + // Disable SSSE3 optimization for Android x86 + if (fSampleProc32 == S32_opaque_D32_filter_DX) { + fSampleProc32 = S32_opaque_D32_filter_DX_SSSE3; +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0009-Bug-755869-FreeBSD-Hurd.patch b/gfx/skia/patches/archive/0009-Bug-755869-FreeBSD-Hurd.patch new file mode 100644 index 0000000000..1e9a93f20a --- /dev/null +++ b/gfx/skia/patches/archive/0009-Bug-755869-FreeBSD-Hurd.patch @@ -0,0 +1,28 @@ +From df3be24040f7cb2f9c7ed86ad3e47206630e885f Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Wed, 23 May 2012 14:49:57 -0400 +Subject: [PATCH 09/10] Bug 755869 - [12] Re-apply bug 749533 - Add + support for GNU/kFreeBSD and Hurd in Skia. + r=mattwoodrow + +--- + gfx/skia/include/core/SkPreConfig.h | 3 ++- + 1 files changed, 2 insertions(+), 1 deletions(-) + +diff --git a/gfx/skia/include/core/SkPreConfig.h b/gfx/skia/include/core/SkPreConfig.h +index 46c6929..16c4d6c 100644 +--- a/gfx/skia/include/core/SkPreConfig.h ++++ b/gfx/skia/include/core/SkPreConfig.h +@@ -35,7 +35,8 @@ + #elif defined(ANDROID) + #define SK_BUILD_FOR_ANDROID + #elif defined(linux) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ +- defined(__sun) || defined(__NetBSD__) || defined(__DragonFly__) ++ defined(__sun) || defined(__NetBSD__) || defined(__DragonFly__) || \ ++ defined(__GLIBC__) || defined(__GNU__) + #define SK_BUILD_FOR_UNIX + #elif TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #define SK_BUILD_FOR_IOS +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0009-Bug-777614-Re-apply-759683-Handle-compilers-that-don.patch b/gfx/skia/patches/archive/0009-Bug-777614-Re-apply-759683-Handle-compilers-that-don.patch new file mode 100644 index 0000000000..1da208ed18 --- /dev/null +++ b/gfx/skia/patches/archive/0009-Bug-777614-Re-apply-759683-Handle-compilers-that-don.patch @@ -0,0 +1,40 @@ +From 2c5a8cebc806ed287ce7c3723ea64a233266cd9e Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Thu, 13 Sep 2012 14:55:33 -0400 +Subject: [PATCH 9/9] Bug 777614 - Re-apply 759683 - Handle compilers that + don't support SSSE3 intrinsics r=nrc + +--- + gfx/skia/src/opts/opts_check_SSE2.cpp | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/gfx/skia/src/opts/opts_check_SSE2.cpp b/gfx/skia/src/opts/opts_check_SSE2.cpp +index 96d0dea..add6d5f 100644 +--- a/gfx/skia/src/opts/opts_check_SSE2.cpp ++++ b/gfx/skia/src/opts/opts_check_SSE2.cpp +@@ -86,9 +86,13 @@ static inline bool hasSSSE3() { + #else + + static inline bool hasSSSE3() { ++#if defined(SK_BUILD_SSSE3) + int cpu_info[4] = { 0 }; + getcpuid(1, cpu_info); + return (cpu_info[2] & 0x200) != 0; ++#else ++ return false; ++#endif + } + #endif + +@@ -104,7 +108,7 @@ static bool cachedHasSSSE3() { + + void SkBitmapProcState::platformProcs() { + if (cachedHasSSSE3()) { +-#if !defined(SK_BUILD_FOR_ANDROID) ++#if !defined(SK_BUILD_FOR_ANDROID) && defined(SK_BUILD_SSSE3) + // Disable SSSE3 optimization for Android x86 + if (fSampleProc32 == S32_opaque_D32_filter_DX) { + fSampleProc32 = S32_opaque_D32_filter_DX_SSSE3; +-- +1.7.11.4 + diff --git a/gfx/skia/patches/archive/0009-Bug-848491-Re-apply-bug-751418-Add-our-own-GrUserCon.patch b/gfx/skia/patches/archive/0009-Bug-848491-Re-apply-bug-751418-Add-our-own-GrUserCon.patch new file mode 100644 index 0000000000..9778015c4f --- /dev/null +++ b/gfx/skia/patches/archive/0009-Bug-848491-Re-apply-bug-751418-Add-our-own-GrUserCon.patch @@ -0,0 +1,23 @@ +From: George Wright +Date: Thu, 25 Apr 2013 20:52:32 -0400 +Subject: Bug 848491 - Re-apply bug 751418 - Add our own GrUserConfig + + +diff --git a/gfx/skia/include/gpu/GrUserConfig.h b/gfx/skia/include/gpu/GrUserConfig.h +index 11d4feb..77ab850 100644 +--- a/gfx/skia/include/gpu/GrUserConfig.h ++++ b/gfx/skia/include/gpu/GrUserConfig.h +@@ -43,6 +43,10 @@ + */ + //#define GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT 96 + ++/* ++ * This allows us to set a callback to be called before each GL call to ensure ++ * that our context is set correctly ++ */ + #define GR_GL_PER_GL_FUNC_CALLBACK 1 + + #endif +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0010-Bug-689069-ARM-Opts.patch b/gfx/skia/patches/archive/0010-Bug-689069-ARM-Opts.patch new file mode 100644 index 0000000000..bd6604b4bd --- /dev/null +++ b/gfx/skia/patches/archive/0010-Bug-689069-ARM-Opts.patch @@ -0,0 +1,36 @@ +From dc1292fc8c2b9da900ebcac953120eaffd0d329e Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Wed, 23 May 2012 14:52:36 -0400 +Subject: [PATCH 10/10] Bug 755869 - [13] Re-apply bug 750733 - Use + handles in API object hooks where possible + r=mattwoodrow + +--- + gfx/skia/src/xml/SkJS.cpp | 4 ++-- + 1 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/gfx/skia/src/xml/SkJS.cpp b/gfx/skia/src/xml/SkJS.cpp +index f2e7a83..b2717d7 100644 +--- a/gfx/skia/src/xml/SkJS.cpp ++++ b/gfx/skia/src/xml/SkJS.cpp +@@ -74,7 +74,7 @@ extern "C" { + #endif + + static bool +-global_enumerate(JSContext *cx, JSObject *obj) ++global_enumerate(JSContext *cx, JSHandleObject *obj) + { + #ifdef LAZY_STANDARD_CLASSES + return JS_EnumerateStandardClasses(cx, obj); +@@ -84,7 +84,7 @@ global_enumerate(JSContext *cx, JSObject *obj) + } + + static bool +-global_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp) ++global_resolve(JSContext *cx, JSHandleObject obj, JSHandleId id, unsigned flags, JSObject **objp) + { + #ifdef LAZY_STANDARD_CLASSES + if ((flags & JSRESOLVE_ASSIGNING) == 0) { +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0010-Bug-836892-Add-new-blending-modes-to-SkXfermode.patch b/gfx/skia/patches/archive/0010-Bug-836892-Add-new-blending-modes-to-SkXfermode.patch new file mode 100644 index 0000000000..a446037de0 --- /dev/null +++ b/gfx/skia/patches/archive/0010-Bug-836892-Add-new-blending-modes-to-SkXfermode.patch @@ -0,0 +1,698 @@ +# HG changeset patch +# User Rik Cabanier +# Date 1360273929 -46800 +# Node ID 3ac8edca3a03b3d22240b5a5b95ae3b5ada9877d +# Parent cbb67fe70b864b36165061e1fd3b083cd09af087 +Bug 836892 - Add new blending modes to SkXfermode. r=gw280 + +diff --git a/gfx/skia/include/core/SkXfermode.h b/gfx/skia/include/core/SkXfermode.h +--- a/gfx/skia/include/core/SkXfermode.h ++++ b/gfx/skia/include/core/SkXfermode.h +@@ -96,33 +96,37 @@ public: + kDstOut_Mode, //!< [Da * (1 - Sa), Dc * (1 - Sa)] + kSrcATop_Mode, //!< [Da, Sc * Da + (1 - Sa) * Dc] + kDstATop_Mode, //!< [Sa, Sa * Dc + Sc * (1 - Da)] + kXor_Mode, //!< [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] + + // all remaining modes are defined in the SVG Compositing standard + // http://www.w3.org/TR/2009/WD-SVGCompositing-20090430/ + kPlus_Mode, +- kMultiply_Mode, + + // all above modes can be expressed as pair of src/dst Coeffs + kCoeffModesCnt, + +- kScreen_Mode = kCoeffModesCnt, ++ kMultiply_Mode = kCoeffModesCnt, ++ kScreen_Mode, + kOverlay_Mode, + kDarken_Mode, + kLighten_Mode, + kColorDodge_Mode, + kColorBurn_Mode, + kHardLight_Mode, + kSoftLight_Mode, + kDifference_Mode, + kExclusion_Mode, ++ kHue_Mode, ++ kSaturation_Mode, ++ kColor_Mode, ++ kLuminosity_Mode, + +- kLastMode = kExclusion_Mode ++ kLastMode = kLuminosity_Mode + }; + + /** + * If the xfermode is one of the modes in the Mode enum, then asMode() + * returns true and sets (if not null) mode accordingly. Otherwise it + * returns false and ignores the mode parameter. + */ + virtual bool asMode(Mode* mode); +diff --git a/gfx/skia/src/core/SkXfermode.cpp b/gfx/skia/src/core/SkXfermode.cpp +--- a/gfx/skia/src/core/SkXfermode.cpp ++++ b/gfx/skia/src/core/SkXfermode.cpp +@@ -7,16 +7,18 @@ + */ + + + #include "SkXfermode.h" + #include "SkColorPriv.h" + #include "SkFlattenableBuffers.h" + #include "SkMathPriv.h" + ++#include ++ + SK_DEFINE_INST_COUNT(SkXfermode) + + #define SkAlphaMulAlpha(a, b) SkMulDiv255Round(a, b) + + #if 0 + // idea for higher precision blends in xfer procs (and slightly faster) + // see DstATop as a probable caller + static U8CPU mulmuldiv255round(U8CPU a, U8CPU b, U8CPU c, U8CPU d) { +@@ -176,244 +178,439 @@ static SkPMColor xor_modeproc(SkPMColor + static SkPMColor plus_modeproc(SkPMColor src, SkPMColor dst) { + unsigned b = saturated_add(SkGetPackedB32(src), SkGetPackedB32(dst)); + unsigned g = saturated_add(SkGetPackedG32(src), SkGetPackedG32(dst)); + unsigned r = saturated_add(SkGetPackedR32(src), SkGetPackedR32(dst)); + unsigned a = saturated_add(SkGetPackedA32(src), SkGetPackedA32(dst)); + return SkPackARGB32(a, r, g, b); + } + ++static inline int srcover_byte(int a, int b) { ++ return a + b - SkAlphaMulAlpha(a, b); ++} ++ ++#define blendfunc_byte(sc, dc, sa, da, blendfunc) \ ++ clamp_div255round(sc * (255 - da) + dc * (255 - sa) + blendfunc(sc, dc, sa, da)) ++ + // kMultiply_Mode ++static inline int multiply_byte(int sc, int dc, int sa, int da) { ++ return sc * dc; ++} + static SkPMColor multiply_modeproc(SkPMColor src, SkPMColor dst) { +- int a = SkAlphaMulAlpha(SkGetPackedA32(src), SkGetPackedA32(dst)); +- int r = SkAlphaMulAlpha(SkGetPackedR32(src), SkGetPackedR32(dst)); +- int g = SkAlphaMulAlpha(SkGetPackedG32(src), SkGetPackedG32(dst)); +- int b = SkAlphaMulAlpha(SkGetPackedB32(src), SkGetPackedB32(dst)); ++ int sa = SkGetPackedA32(src); ++ int da = SkGetPackedA32(dst); ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, multiply_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, multiply_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, multiply_byte); + return SkPackARGB32(a, r, g, b); + } + + // kScreen_Mode +-static inline int srcover_byte(int a, int b) { +- return a + b - SkAlphaMulAlpha(a, b); ++static inline int screen_byte(int sc, int dc, int sa, int da) { ++ return sc * da + sa * dc - sc * dc; + } + static SkPMColor screen_modeproc(SkPMColor src, SkPMColor dst) { +- int a = srcover_byte(SkGetPackedA32(src), SkGetPackedA32(dst)); +- int r = srcover_byte(SkGetPackedR32(src), SkGetPackedR32(dst)); +- int g = srcover_byte(SkGetPackedG32(src), SkGetPackedG32(dst)); +- int b = srcover_byte(SkGetPackedB32(src), SkGetPackedB32(dst)); ++ int sa = SkGetPackedA32(src); ++ int da = SkGetPackedA32(dst); ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, screen_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, screen_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, screen_byte); ++ return SkPackARGB32(a, r, g, b); ++} ++ ++// kHardLight_Mode ++static inline int hardlight_byte(int sc, int dc, int sa, int da) { ++ if(!sa || !da) ++ return sc * da; ++ float Sc = (float)sc/sa; ++ float Dc = (float)dc/da; ++ if(Sc <= 0.5) ++ Sc *= 2 * Dc; ++ else ++ Sc = -1 + 2 * Sc + 2 * Dc - 2 * Sc * Dc; ++ ++ return Sc * sa * da; ++} ++static SkPMColor hardlight_modeproc(SkPMColor src, SkPMColor dst) { ++ int sa = SkGetPackedA32(src); ++ int da = SkGetPackedA32(dst); ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, hardlight_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, hardlight_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, hardlight_byte); + return SkPackARGB32(a, r, g, b); + } + + // kOverlay_Mode + static inline int overlay_byte(int sc, int dc, int sa, int da) { +- int tmp = sc * (255 - da) + dc * (255 - sa); +- int rc; +- if (2 * dc <= da) { +- rc = 2 * sc * dc; +- } else { +- rc = sa * da - 2 * (da - dc) * (sa - sc); +- } +- return clamp_div255round(rc + tmp); ++ return hardlight_byte(dc, sc, da, sa); + } + static SkPMColor overlay_modeproc(SkPMColor src, SkPMColor dst) { + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = overlay_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = overlay_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = overlay_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, overlay_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, overlay_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, overlay_byte); + return SkPackARGB32(a, r, g, b); + } + + // kDarken_Mode + static inline int darken_byte(int sc, int dc, int sa, int da) { +- int sd = sc * da; +- int ds = dc * sa; +- if (sd < ds) { +- // srcover +- return sc + dc - SkDiv255Round(ds); +- } else { +- // dstover +- return dc + sc - SkDiv255Round(sd); +- } ++ return SkMin32(sc * da, sa * dc); + } + static SkPMColor darken_modeproc(SkPMColor src, SkPMColor dst) { + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = darken_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = darken_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = darken_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, darken_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, darken_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, darken_byte); + return SkPackARGB32(a, r, g, b); + } + + // kLighten_Mode + static inline int lighten_byte(int sc, int dc, int sa, int da) { +- int sd = sc * da; +- int ds = dc * sa; +- if (sd > ds) { +- // srcover +- return sc + dc - SkDiv255Round(ds); +- } else { +- // dstover +- return dc + sc - SkDiv255Round(sd); +- } ++ return SkMax32(sc * da, sa * dc); + } + static SkPMColor lighten_modeproc(SkPMColor src, SkPMColor dst) { + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = lighten_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = lighten_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = lighten_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, lighten_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, lighten_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, lighten_byte); + return SkPackARGB32(a, r, g, b); + } + + // kColorDodge_Mode + static inline int colordodge_byte(int sc, int dc, int sa, int da) { +- int diff = sa - sc; +- int rc; +- if (0 == diff) { +- rc = sa * da + sc * (255 - da) + dc * (255 - sa); +- rc = SkDiv255Round(rc); +- } else { +- int tmp = (dc * sa << 15) / (da * diff); +- rc = SkDiv255Round(sa * da) * tmp >> 15; +- // don't clamp here, since we'll do it in our modeproc +- } +- return rc; ++ if (dc == 0) ++ return 0; ++ // Avoid division by 0 ++ if (sc == sa) ++ return da * sa; ++ ++ return SkMin32(sa * da, sa * sa * dc / (sa - sc)); + } + static SkPMColor colordodge_modeproc(SkPMColor src, SkPMColor dst) { +- // added to avoid div-by-zero in colordodge_byte +- if (0 == dst) { +- return src; +- } +- + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = colordodge_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = colordodge_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = colordodge_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); +- r = clamp_max(r, a); +- g = clamp_max(g, a); +- b = clamp_max(b, a); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, colordodge_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, colordodge_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, colordodge_byte); + return SkPackARGB32(a, r, g, b); + } + + // kColorBurn_Mode + static inline int colorburn_byte(int sc, int dc, int sa, int da) { +- int rc; +- if (dc == da && 0 == sc) { +- rc = sa * da + dc * (255 - sa); +- } else if (0 == sc) { +- return SkAlphaMulAlpha(dc, 255 - sa); +- } else { +- int tmp = (sa * (da - dc) * 256) / (sc * da); +- if (tmp > 256) { +- tmp = 256; +- } +- int tmp2 = sa * da; +- rc = tmp2 - (tmp2 * tmp >> 8) + sc * (255 - da) + dc * (255 - sa); +- } +- return SkDiv255Round(rc); ++ // Avoid division by 0 ++ if(dc == da) ++ return sa * da; ++ if(sc == 0) ++ return 0; ++ ++ return sa * da - SkMin32(sa * da, sa * sa * (da - dc) / sc); + } + static SkPMColor colorburn_modeproc(SkPMColor src, SkPMColor dst) { +- // added to avoid div-by-zero in colorburn_byte +- if (0 == dst) { +- return src; +- } +- + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = colorburn_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = colorburn_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = colorburn_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); +- return SkPackARGB32(a, r, g, b); +-} +- +-// kHardLight_Mode +-static inline int hardlight_byte(int sc, int dc, int sa, int da) { +- int rc; +- if (2 * sc <= sa) { +- rc = 2 * sc * dc; +- } else { +- rc = sa * da - 2 * (da - dc) * (sa - sc); +- } +- return clamp_div255round(rc + sc * (255 - da) + dc * (255 - sa)); +-} +-static SkPMColor hardlight_modeproc(SkPMColor src, SkPMColor dst) { +- int sa = SkGetPackedA32(src); +- int da = SkGetPackedA32(dst); +- int a = srcover_byte(sa, da); +- int r = hardlight_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = hardlight_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = hardlight_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, colorburn_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, colorburn_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, colorburn_byte); + return SkPackARGB32(a, r, g, b); + } + + // returns 255 * sqrt(n/255) + static U8CPU sqrt_unit_byte(U8CPU n) { + return SkSqrtBits(n, 15+4); + } + + // kSoftLight_Mode + static inline int softlight_byte(int sc, int dc, int sa, int da) { + int m = da ? dc * 256 / da : 0; + int rc; +- if (2 * sc <= sa) { +- rc = dc * (sa + ((2 * sc - sa) * (256 - m) >> 8)); +- } else if (4 * dc <= da) { ++ if (2 * sc <= sa) ++ return dc * (sa + ((2 * sc - sa) * (256 - m) >> 8)); ++ ++ if (4 * dc <= da) { + int tmp = (4 * m * (4 * m + 256) * (m - 256) >> 16) + 7 * m; +- rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8); +- } else { +- int tmp = sqrt_unit_byte(m) - m; +- rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8); ++ return dc * sa + (da * (2 * sc - sa) * tmp >> 8); + } +- return clamp_div255round(rc + sc * (255 - da) + dc * (255 - sa)); ++ int tmp = sqrt_unit_byte(m) - m; ++ return rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8); + } + static SkPMColor softlight_modeproc(SkPMColor src, SkPMColor dst) { + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = softlight_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = softlight_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = softlight_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, softlight_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, softlight_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, softlight_byte); + return SkPackARGB32(a, r, g, b); + } + + // kDifference_Mode + static inline int difference_byte(int sc, int dc, int sa, int da) { +- int tmp = SkMin32(sc * da, dc * sa); +- return clamp_signed_byte(sc + dc - 2 * SkDiv255Round(tmp)); ++ int tmp = dc * sa - sc * da; ++ if(tmp<0) ++ return - tmp; ++ ++ return tmp; + } + static SkPMColor difference_modeproc(SkPMColor src, SkPMColor dst) { + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = difference_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = difference_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = difference_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, difference_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, difference_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, difference_byte); + return SkPackARGB32(a, r, g, b); + } + + // kExclusion_Mode + static inline int exclusion_byte(int sc, int dc, int sa, int da) { +- // this equations is wacky, wait for SVG to confirm it +- int r = sc * da + dc * sa - 2 * sc * dc + sc * (255 - da) + dc * (255 - sa); +- return clamp_div255round(r); ++ return sc * da + dc * sa - 2 * dc * sc; + } + static SkPMColor exclusion_modeproc(SkPMColor src, SkPMColor dst) { + int sa = SkGetPackedA32(src); + int da = SkGetPackedA32(dst); + int a = srcover_byte(sa, da); +- int r = exclusion_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da); +- int g = exclusion_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da); +- int b = exclusion_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da); ++ int r = blendfunc_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da, exclusion_byte); ++ int g = blendfunc_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da, exclusion_byte); ++ int b = blendfunc_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da, exclusion_byte); ++ return SkPackARGB32(a, r, g, b); ++} ++ ++/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ++struct BlendColor { ++ float r; ++ float g; ++ float b; ++ ++ BlendColor(): r(0), g(0), b(0) ++ {} ++}; ++ ++static inline float Lum(BlendColor C) ++{ ++ return C.r * 0.3 + C.g * 0.59 + C.b* 0.11; ++} ++ ++static inline float SkMinFloat(float a, float b) ++{ ++ if (a > b) ++ a = b; ++ return a; ++} ++ ++static inline float SkMaxFloat(float a, float b) ++{ ++ if (a < b) ++ a = b; ++ return a; ++} ++ ++#define minimum(C) SkMinFloat(SkMinFloat(C.r, C.g), C.b) ++#define maximum(C) SkMaxFloat(SkMaxFloat(C.r, C.g), C.b) ++ ++static inline float Sat(BlendColor c) { ++ return maximum(c) - minimum(c); ++} ++ ++static inline void setSaturationComponents(float& Cmin, float& Cmid, float& Cmax, float s) { ++ if(Cmax > Cmin) { ++ Cmid = (((Cmid - Cmin) * s ) / (Cmax - Cmin)); ++ Cmax = s; ++ } else { ++ Cmax = 0; ++ Cmid = 0; ++ } ++ Cmin = 0; ++} ++ ++static inline BlendColor SetSat(BlendColor C, float s) { ++ if(C.r <= C.g) { ++ if(C.g <= C.b) ++ setSaturationComponents(C.r, C.g, C.b, s); ++ else ++ if(C.r <= C.b) ++ setSaturationComponents(C.r, C.b, C.g, s); ++ else ++ setSaturationComponents(C.b, C.r, C.g, s); ++ } else if(C.r <= C.b) ++ setSaturationComponents(C.g, C.r, C.b, s); ++ else ++ if(C.g <= C.b) ++ setSaturationComponents(C.g, C.b, C.r, s); ++ else ++ setSaturationComponents(C.b, C.g, C.r, s); ++ ++ return C; ++} ++ ++static inline BlendColor clipColor(BlendColor C) { ++ float L = Lum(C); ++ float n = minimum(C); ++ float x = maximum(C); ++ if(n < 0) { ++ C.r = L + (((C.r - L) * L) / (L - n)); ++ C.g = L + (((C.g - L) * L) / (L - n)); ++ C.b = L + (((C.b - L) * L) / (L - n)); ++ } ++ ++ if(x > 1) { ++ C.r = L + (((C.r - L) * (1 - L)) / (x - L)); ++ C.g = L + (((C.g - L) * (1 - L)) / (x - L)); ++ C.b = L + (((C.b - L) * (1 - L)) / (x - L)); ++ } ++ return C; ++} ++ ++static inline BlendColor SetLum(BlendColor C, float l) { ++ float d = l - Lum(C); ++ C.r += d; ++ C.g += d; ++ C.b += d; ++ ++ return clipColor(C); ++} ++ ++#define blendfunc_nonsep_byte(sc, dc, sa, da, blendval) \ ++ clamp_div255round(sc * (255 - da) + dc * (255 - sa) + (int)(sa * da * blendval)) ++ ++static SkPMColor hue_modeproc(SkPMColor src, SkPMColor dst) { ++ int sr = SkGetPackedR32(src); ++ int sg = SkGetPackedG32(src); ++ int sb = SkGetPackedB32(src); ++ int sa = SkGetPackedA32(src); ++ ++ int dr = SkGetPackedR32(dst); ++ int dg = SkGetPackedG32(dst); ++ int db = SkGetPackedB32(dst); ++ int da = SkGetPackedA32(dst); ++ ++ BlendColor Cs; ++ if(sa) { ++ Cs.r = (float)sr / sa; ++ Cs.g = (float)sg / sa; ++ Cs.b = (float)sb / sa; ++ BlendColor Cd; ++ if(da) { ++ Cd.r = (float)dr / da; ++ Cd.g = (float)dg / da; ++ Cd.b = (float)db / da; ++ Cs = SetLum(SetSat(Cs, Sat(Cd)), Lum(Cd)); ++ } ++ } ++ ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Cs.r); ++ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Cs.g); ++ int b = blendfunc_nonsep_byte(sb, db, sa, da, Cs.b); ++ return SkPackARGB32(a, r, g, b); ++} ++ ++static SkPMColor saturation_modeproc(SkPMColor src, SkPMColor dst) { ++ int sr = SkGetPackedR32(src); ++ int sg = SkGetPackedG32(src); ++ int sb = SkGetPackedB32(src); ++ int sa = SkGetPackedA32(src); ++ ++ int dr = SkGetPackedR32(dst); ++ int dg = SkGetPackedG32(dst); ++ int db = SkGetPackedB32(dst); ++ int da = SkGetPackedA32(dst); ++ ++ BlendColor Cs; ++ if(sa) { ++ Cs.r = (float)sr / sa; ++ Cs.g = (float)sg / sa; ++ Cs.b = (float)sb / sa; ++ BlendColor Cd; ++ if(da) { ++ Cd.r = (float)dr / da; ++ Cd.g = (float)dg / da; ++ Cd.b = (float)db / da; ++ Cs = SetLum(SetSat(Cd, Sat(Cs)), Lum(Cd)); ++ } ++ } ++ ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Cs.r); ++ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Cs.g); ++ int b = blendfunc_nonsep_byte(sb, db, sa, da, Cs.b); ++ return SkPackARGB32(a, r, g, b); ++} ++ ++static SkPMColor color_modeproc(SkPMColor src, SkPMColor dst) { ++ int sr = SkGetPackedR32(src); ++ int sg = SkGetPackedG32(src); ++ int sb = SkGetPackedB32(src); ++ int sa = SkGetPackedA32(src); ++ ++ int dr = SkGetPackedR32(dst); ++ int dg = SkGetPackedG32(dst); ++ int db = SkGetPackedB32(dst); ++ int da = SkGetPackedA32(dst); ++ ++ BlendColor Cs; ++ if(sa) { ++ Cs.r = (float)sr / sa; ++ Cs.g = (float)sg / sa; ++ Cs.b = (float)sb / sa; ++ BlendColor Cd; ++ if(da) { ++ Cd.r = (float)dr / da; ++ Cd.g = (float)dg / da; ++ Cd.b = (float)db / da; ++ Cs = SetLum(Cs, Lum(Cd)); ++ } ++ } ++ ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Cs.r); ++ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Cs.g); ++ int b = blendfunc_nonsep_byte(sb, db, sa, da, Cs.b); ++ return SkPackARGB32(a, r, g, b); ++} ++ ++static SkPMColor luminosity_modeproc(SkPMColor src, SkPMColor dst) { ++ int sr = SkGetPackedR32(src); ++ int sg = SkGetPackedG32(src); ++ int sb = SkGetPackedB32(src); ++ int sa = SkGetPackedA32(src); ++ ++ int dr = SkGetPackedR32(dst); ++ int dg = SkGetPackedG32(dst); ++ int db = SkGetPackedB32(dst); ++ int da = SkGetPackedA32(dst); ++ ++ BlendColor Cs; ++ if(sa) { ++ Cs.r = (float)sr / sa; ++ Cs.g = (float)sg / sa; ++ Cs.b = (float)sb / sa; ++ BlendColor Cd; ++ if(da) { ++ Cd.r = (float)dr / da; ++ Cd.g = (float)dg / da; ++ Cd.b = (float)db / da; ++ Cs = SetLum(Cd, Lum(Cs)); ++ } ++ } ++ ++ int a = srcover_byte(sa, da); ++ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Cs.r); ++ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Cs.g); ++ int b = blendfunc_nonsep_byte(sb, db, sa, da, Cs.b); + return SkPackARGB32(a, r, g, b); + } + + struct ProcCoeff { + SkXfermodeProc fProc; + SkXfermode::Coeff fSC; + SkXfermode::Coeff fDC; + }; +@@ -430,27 +627,31 @@ static const ProcCoeff gProcCoeffs[] = { + { dstin_modeproc, SkXfermode::kZero_Coeff, SkXfermode::kSA_Coeff }, + { srcout_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kZero_Coeff }, + { dstout_modeproc, SkXfermode::kZero_Coeff, SkXfermode::kISA_Coeff }, + { srcatop_modeproc, SkXfermode::kDA_Coeff, SkXfermode::kISA_Coeff }, + { dstatop_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kSA_Coeff }, + { xor_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kISA_Coeff }, + + { plus_modeproc, SkXfermode::kOne_Coeff, SkXfermode::kOne_Coeff }, +- { multiply_modeproc,SkXfermode::kZero_Coeff, SkXfermode::kSC_Coeff }, ++ { multiply_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF}, + { screen_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { overlay_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { darken_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { lighten_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { colordodge_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { colorburn_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { hardlight_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { softlight_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { difference_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + { exclusion_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, ++ { hue_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, ++ { saturation_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, ++ { color_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, ++ { luminosity_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF }, + }; + + /////////////////////////////////////////////////////////////////////////////// + + bool SkXfermode::asCoeff(Coeff* src, Coeff* dst) { + return false; + } + +@@ -1172,16 +1373,20 @@ static const Proc16Rec gModeProcs16[] = + { darken_modeproc16_0, darken_modeproc16_255, NULL }, // darken + { lighten_modeproc16_0, lighten_modeproc16_255, NULL }, // lighten + { NULL, NULL, NULL }, // colordodge + { NULL, NULL, NULL }, // colorburn + { NULL, NULL, NULL }, // hardlight + { NULL, NULL, NULL }, // softlight + { NULL, NULL, NULL }, // difference + { NULL, NULL, NULL }, // exclusion ++ { NULL, NULL, NULL }, // hue ++ { NULL, NULL, NULL }, // saturation ++ { NULL, NULL, NULL }, // color ++ { NULL, NULL, NULL }, // luminosity + }; + + SkXfermodeProc16 SkXfermode::GetProc16(Mode mode, SkColor srcColor) { + SkXfermodeProc16 proc16 = NULL; + if ((unsigned)mode < kModeCount) { + const Proc16Rec& rec = gModeProcs16[mode]; + unsigned a = SkColorGetA(srcColor); + diff --git a/gfx/skia/patches/archive/0010-Bug-848491-Re-apply-bug-817356-Patch-Skia-to-recogni.patch b/gfx/skia/patches/archive/0010-Bug-848491-Re-apply-bug-817356-Patch-Skia-to-recogni.patch new file mode 100644 index 0000000000..0d44b008d6 --- /dev/null +++ b/gfx/skia/patches/archive/0010-Bug-848491-Re-apply-bug-817356-Patch-Skia-to-recogni.patch @@ -0,0 +1,22 @@ +From: George Wright +Date: Thu, 25 Apr 2013 20:55:02 -0400 +Subject: Bug 848491 - Re-apply bug 817356 - Patch Skia to recognize uppercase PPC/PPC64. + + +diff --git a/gfx/skia/include/core/SkPreConfig.h b/gfx/skia/include/core/SkPreConfig.h +index 11cb223..7e95b99 100644 +--- a/gfx/skia/include/core/SkPreConfig.h ++++ b/gfx/skia/include/core/SkPreConfig.h +@@ -99,7 +99,8 @@ + ////////////////////////////////////////////////////////////////////// + + #if !defined(SK_CPU_BENDIAN) && !defined(SK_CPU_LENDIAN) +- #if defined (__ppc__) || defined(__ppc64__) ++ #if defined (__ppc__) || defined(__PPC__) || defined(__ppc64__) \ ++ || defined(__PPC64__) + #define SK_CPU_BENDIAN + #else + #define SK_CPU_LENDIAN +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0011-Bug-719575-Fix-clang-build.patch b/gfx/skia/patches/archive/0011-Bug-719575-Fix-clang-build.patch new file mode 100644 index 0000000000..95cb08a36f --- /dev/null +++ b/gfx/skia/patches/archive/0011-Bug-719575-Fix-clang-build.patch @@ -0,0 +1,28 @@ +From cf855f31194ff071f2c787a7413d70a43f15f204 Mon Sep 17 00:00:00 2001 +From: Ehsan Akhgari +Date: Tue, 29 May 2012 15:39:55 -0400 +Subject: [PATCH] Bug 755869 - Re-apply patch from bug 719575 to fix clang + builds for the new Skia r=gw280 + +--- + gfx/skia/src/ports/SkFontHost_mac_coretext.cpp | 4 ++-- + 1 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp b/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp +index c43d1a6..ce5f409 100644 +--- a/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp ++++ b/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp +@@ -807,8 +807,8 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& + void SkScalerContext_Mac::getVerticalOffset(CGGlyph glyphID, SkIPoint* offset) const { + CGSize vertOffset; + CTFontGetVerticalTranslationsForGlyphs(fCTVerticalFont, &glyphID, &vertOffset, 1); +- const SkPoint trans = {SkFloatToScalar(vertOffset.width), +- SkFloatToScalar(vertOffset.height)}; ++ const SkPoint trans = {SkScalar(SkFloatToScalar(vertOffset.width)), ++ SkScalar(SkFloatToScalar(vertOffset.height))}; + SkPoint floatOffset; + fVerticalMatrix.mapPoints(&floatOffset, &trans, 1); + if (!isSnowLeopard()) { +-- +1.7.5.4 + diff --git a/gfx/skia/patches/archive/0011-Bug-839347-no-anon-namespace-around-SkNO_RETURN_HINT.patch b/gfx/skia/patches/archive/0011-Bug-839347-no-anon-namespace-around-SkNO_RETURN_HINT.patch new file mode 100644 index 0000000000..854f0b1afe --- /dev/null +++ b/gfx/skia/patches/archive/0011-Bug-839347-no-anon-namespace-around-SkNO_RETURN_HINT.patch @@ -0,0 +1,31 @@ +# HG changeset patch +# Parent 2c6da9f02606f7a02f635d99ef8cf669d3bc5c4b +# User Daniel Holbert +Bug 839347: Move SkNO_RETURN_HINT out of anonymous namespace so that clang won't warn about it being unused. r=mattwoodrow + +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -63,20 +63,18 @@ + * The clang static analyzer likes to know that when the program is not + * expected to continue (crash, assertion failure, etc). It will notice that + * some combination of parameters lead to a function call that does not return. + * It can then make appropriate assumptions about the parameters in code + * executed only if the non-returning function was *not* called. + */ + #if !defined(SkNO_RETURN_HINT) + #if SK_HAS_COMPILER_FEATURE(attribute_analyzer_noreturn) +- namespace { +- inline void SkNO_RETURN_HINT() __attribute__((analyzer_noreturn)); +- inline void SkNO_RETURN_HINT() {} +- } ++ inline void SkNO_RETURN_HINT() __attribute__((analyzer_noreturn)); ++ inline void SkNO_RETURN_HINT() {} + #else + #define SkNO_RETURN_HINT() do {} while (false) + #endif + #endif + + #if defined(SK_ZLIB_INCLUDE) && defined(SK_SYSTEM_ZLIB) + #error "cannot define both SK_ZLIB_INCLUDE and SK_SYSTEM_ZLIB" + #elif defined(SK_ZLIB_INCLUDE) || defined(SK_SYSTEM_ZLIB) diff --git a/gfx/skia/patches/archive/0012-Bug-751418-Add-our-own-GrUserConfig-r-mattwoodrow.patch b/gfx/skia/patches/archive/0012-Bug-751418-Add-our-own-GrUserConfig-r-mattwoodrow.patch new file mode 100644 index 0000000000..cde2940950 --- /dev/null +++ b/gfx/skia/patches/archive/0012-Bug-751418-Add-our-own-GrUserConfig-r-mattwoodrow.patch @@ -0,0 +1,29 @@ +From 4c25387e6e6cdb55f19e51631a78c3fa9b4a3c73 Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Thu, 1 Nov 2012 17:29:50 -0400 +Subject: [PATCH 2/8] Bug 751418 - Add our own GrUserConfig r=mattwoodrow + +--- + gfx/skia/include/gpu/GrUserConfig.h | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/gfx/skia/include/gpu/GrUserConfig.h b/gfx/skia/include/gpu/GrUserConfig.h +index d514486..b729ab3 100644 +--- a/gfx/skia/include/gpu/GrUserConfig.h ++++ b/gfx/skia/include/gpu/GrUserConfig.h +@@ -64,6 +64,12 @@ + #define GR_TEXT_SCALAR_IS_FIXED 0 + #define GR_TEXT_SCALAR_IS_FLOAT 1 + ++/* ++ * This allows us to set a callback to be called before each GL call to ensure ++ * that our context is set correctly ++ */ ++#define GR_GL_PER_GL_FUNC_CALLBACK 1 ++ + #endif + + +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0012-Bug-759683-make-ssse3-conditional.patch b/gfx/skia/patches/archive/0012-Bug-759683-make-ssse3-conditional.patch new file mode 100644 index 0000000000..dc780c5ec6 --- /dev/null +++ b/gfx/skia/patches/archive/0012-Bug-759683-make-ssse3-conditional.patch @@ -0,0 +1,22 @@ +diff --git a/gfx/skia/src/opts/opts_check_SSE2.cpp b/gfx/skia/src/opts/opts_check_SSE2.cpp +--- a/gfx/skia/src/opts/opts_check_SSE2.cpp ++++ b/gfx/skia/src/opts/opts_check_SSE2.cpp +@@ -91,17 +91,17 @@ static bool cachedHasSSE2() { + + static bool cachedHasSSSE3() { + static bool gHasSSSE3 = hasSSSE3(); + return gHasSSSE3; + } + + void SkBitmapProcState::platformProcs() { + if (cachedHasSSSE3()) { +-#if !defined(SK_BUILD_FOR_ANDROID) ++#if defined(SK_BUILD_SSSE3) + // Disable SSSE3 optimization for Android x86 + if (fSampleProc32 == S32_opaque_D32_filter_DX) { + fSampleProc32 = S32_opaque_D32_filter_DX_SSSE3; + } else if (fSampleProc32 == S32_alpha_D32_filter_DX) { + fSampleProc32 = S32_alpha_D32_filter_DX_SSSE3; + } + + if (fSampleProc32 == S32_opaque_D32_filter_DXDY) { diff --git a/gfx/skia/patches/archive/0013-Bug-751418-Fix-compile-error-on-gcc-in-Skia-GL-r-mat.patch b/gfx/skia/patches/archive/0013-Bug-751418-Fix-compile-error-on-gcc-in-Skia-GL-r-mat.patch new file mode 100644 index 0000000000..167e22184d --- /dev/null +++ b/gfx/skia/patches/archive/0013-Bug-751418-Fix-compile-error-on-gcc-in-Skia-GL-r-mat.patch @@ -0,0 +1,26 @@ +From 3d786b1f0c040205ad9ef6d4216ce06b41f7359f Mon Sep 17 00:00:00 2001 +From: George Wright +Date: Mon, 5 Nov 2012 15:49:42 +0000 +Subject: [PATCH 3/8] Bug 751418 - Fix compile error on gcc in Skia/GL + r=mattwoodrow + +--- + gfx/skia/src/gpu/gl/GrGLProgram.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/gfx/skia/src/gpu/gl/GrGLProgram.cpp b/gfx/skia/src/gpu/gl/GrGLProgram.cpp +index 2703110..40cadc3 100644 +--- a/gfx/skia/src/gpu/gl/GrGLProgram.cpp ++++ b/gfx/skia/src/gpu/gl/GrGLProgram.cpp +@@ -575,7 +575,7 @@ bool GrGLProgram::genProgram(const GrCustomStage** customStages) { + POS_ATTR_NAME); + + builder.fVSCode.appendf("void main() {\n" +- "\tvec3 pos3 = %s * vec3("POS_ATTR_NAME", 1);\n" ++ "\tvec3 pos3 = %s * vec3(" POS_ATTR_NAME ", 1);\n" + "\tgl_Position = vec4(pos3.xy, 0, pos3.z);\n", + viewMName); + +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0013-Bug-761890-fonts.patch b/gfx/skia/patches/archive/0013-Bug-761890-fonts.patch new file mode 100644 index 0000000000..f20293d4cc --- /dev/null +++ b/gfx/skia/patches/archive/0013-Bug-761890-fonts.patch @@ -0,0 +1,162 @@ +# HG changeset patch +# User Nicholas Cameron +# Date 1337146927 -43200 +# Node ID 310209abef2c2667e5de41dd2a1f071e8cd42821 +# Parent 93f3ca4d5707b2aae9c6ae52d5d29c2c802e7ef8 +Bug 746883; changes to the Skia library. r=gw280 + +diff --git a/gfx/skia/include/core/SkDraw.h b/gfx/skia/include/core/SkDraw.h +--- a/gfx/skia/include/core/SkDraw.h ++++ b/gfx/skia/include/core/SkDraw.h +@@ -125,23 +125,24 @@ public: + #endif + }; + + class SkGlyphCache; + + class SkTextToPathIter { + public: + SkTextToPathIter(const char text[], size_t length, const SkPaint& paint, +- bool applyStrokeAndPathEffects); ++ bool applyStrokeAndPathEffects, bool useCanonicalTextSize = true); + ~SkTextToPathIter(); + + const SkPaint& getPaint() const { return fPaint; } + SkScalar getPathScale() const { return fScale; } + + const SkPath* next(SkScalar* xpos); //!< returns nil when there are no more paths ++ bool nextWithWhitespace(const SkPath** path, SkScalar* xpos); //!< returns false when there are no more paths + + private: + SkGlyphCache* fCache; + SkPaint fPaint; + SkScalar fScale; + SkFixed fPrevAdvance; + const char* fText; + const char* fStop; +diff --git a/gfx/skia/src/core/SkPaint.cpp b/gfx/skia/src/core/SkPaint.cpp +--- a/gfx/skia/src/core/SkPaint.cpp ++++ b/gfx/skia/src/core/SkPaint.cpp +@@ -1359,30 +1359,32 @@ void SkPaint::getPosTextPath(const void* + const SkPoint pos[], SkPath* path) const { + SkASSERT(length == 0 || textData != NULL); + + const char* text = (const char*)textData; + if (text == NULL || length == 0 || path == NULL) { + return; + } + +- SkTextToPathIter iter(text, length, *this, false); ++ SkTextToPathIter iter(text, length, *this, false, false); + SkMatrix matrix; + SkPoint prevPos; + prevPos.set(0, 0); + + matrix.setScale(iter.getPathScale(), iter.getPathScale()); + path->reset(); + + unsigned int i = 0; + const SkPath* iterPath; +- while ((iterPath = iter.next(NULL)) != NULL) { +- matrix.postTranslate(pos[i].fX - prevPos.fX, pos[i].fY - prevPos.fY); +- path->addPath(*iterPath, matrix); +- prevPos = pos[i]; ++ while (iter.nextWithWhitespace(&iterPath, NULL)) { ++ if (iterPath) { ++ matrix.postTranslate(pos[i].fX - prevPos.fX, pos[i].fY - prevPos.fY); ++ path->addPath(*iterPath, matrix); ++ prevPos = pos[i]; ++ } + i++; + } + } + + static void add_flattenable(SkDescriptor* desc, uint32_t tag, + SkFlattenableWriteBuffer* buffer) { + buffer->flatten(desc->addEntry(tag, buffer->size(), NULL)); + } +@@ -2118,30 +2120,31 @@ const SkRect& SkPaint::doComputeFastBoun + + static bool has_thick_frame(const SkPaint& paint) { + return paint.getStrokeWidth() > 0 && + paint.getStyle() != SkPaint::kFill_Style; + } + + SkTextToPathIter::SkTextToPathIter( const char text[], size_t length, + const SkPaint& paint, +- bool applyStrokeAndPathEffects) ++ bool applyStrokeAndPathEffects, ++ bool useCanonicalTextSize) + : fPaint(paint) { + fGlyphCacheProc = paint.getMeasureCacheProc(SkPaint::kForward_TextBufferDirection, + true); + + fPaint.setLinearText(true); + fPaint.setMaskFilter(NULL); // don't want this affecting our path-cache lookup + + if (fPaint.getPathEffect() == NULL && !has_thick_frame(fPaint)) { + applyStrokeAndPathEffects = false; + } + + // can't use our canonical size if we need to apply patheffects +- if (fPaint.getPathEffect() == NULL) { ++ if (useCanonicalTextSize && fPaint.getPathEffect() == NULL) { + fPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); + fScale = paint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; + if (has_thick_frame(fPaint)) { + fPaint.setStrokeWidth(SkScalarDiv(fPaint.getStrokeWidth(), fScale)); + } + } else { + fScale = SK_Scalar1; + } +@@ -2185,30 +2188,47 @@ SkTextToPathIter::SkTextToPathIter( cons + fXYIndex = paint.isVerticalText() ? 1 : 0; + } + + SkTextToPathIter::~SkTextToPathIter() { + SkGlyphCache::AttachCache(fCache); + } + + const SkPath* SkTextToPathIter::next(SkScalar* xpos) { +- while (fText < fStop) { ++ const SkPath* result; ++ while (nextWithWhitespace(&result, xpos)) { ++ if (result) { ++ if (xpos) { ++ *xpos = fXPos; ++ } ++ return result; ++ } ++ } ++ return NULL; ++} ++ ++bool SkTextToPathIter::nextWithWhitespace(const SkPath** path, SkScalar* xpos) { ++ if (fText < fStop) { + const SkGlyph& glyph = fGlyphCacheProc(fCache, &fText); + + fXPos += SkScalarMul(SkFixedToScalar(fPrevAdvance + fAutoKern.adjust(glyph)), fScale); + fPrevAdvance = advance(glyph, fXYIndex); // + fPaint.getTextTracking(); + + if (glyph.fWidth) { + if (xpos) { + *xpos = fXPos; + } +- return fCache->findPath(glyph); ++ *path = fCache->findPath(glyph); ++ return true; ++ } else { ++ *path = NULL; ++ return true; + } + } +- return NULL; ++ return false; + } + + /////////////////////////////////////////////////////////////////////////////// + + bool SkPaint::nothingToDraw() const { + if (fLooper) { + return false; + } diff --git a/gfx/skia/patches/archive/0014-Bug-765038-Fix-clang-build.patch b/gfx/skia/patches/archive/0014-Bug-765038-Fix-clang-build.patch new file mode 100644 index 0000000000..6cc74914d2 --- /dev/null +++ b/gfx/skia/patches/archive/0014-Bug-765038-Fix-clang-build.patch @@ -0,0 +1,29 @@ +# HG changeset patch +# Parent 9ded7a9f94a863dfa1f3227d3013367f51b8b522 +# User Nicholas Cameron +Bug 765038; fix a Clang compilation bug in Skia; r=jwatt + +diff --git a/gfx/skia/src/sfnt/SkOTTable_head.h b/gfx/skia/src/sfnt/SkOTTable_head.h +--- a/gfx/skia/src/sfnt/SkOTTable_head.h ++++ b/gfx/skia/src/sfnt/SkOTTable_head.h +@@ -109,18 +109,18 @@ struct SkOTTableHead { + } raw; + } macStyle; + SK_OT_USHORT lowestRecPPEM; + struct FontDirectionHint { + SK_TYPED_ENUM(Value, SK_OT_SHORT, + ((FullyMixedDirectionalGlyphs, SkTEndian_SwapBE16(0))) + ((OnlyStronglyLTR, SkTEndian_SwapBE16(1))) + ((StronglyLTR, SkTEndian_SwapBE16(2))) +- ((OnlyStronglyRTL, static_cast(SkTEndian_SwapBE16(-1)))) +- ((StronglyRTL, static_cast(SkTEndian_SwapBE16(-2)))) ++ ((OnlyStronglyRTL, static_cast(SkTEndian_SwapBE16(static_cast(-1))))) ++ ((StronglyRTL, static_cast(SkTEndian_SwapBE16(static_cast(-2))))) + SK_SEQ_END, + (value)SK_SEQ_END) + } fontDirectionHint; + struct IndexToLocFormat { + SK_TYPED_ENUM(Value, SK_OT_SHORT, + ((ShortOffsets, SkTEndian_SwapBE16(0))) + ((LongOffsets, SkTEndian_SwapBE16(1))) + SK_SEQ_END, diff --git a/gfx/skia/patches/archive/0015-Bug-766017-warnings.patch b/gfx/skia/patches/archive/0015-Bug-766017-warnings.patch new file mode 100644 index 0000000000..174dcb9bce --- /dev/null +++ b/gfx/skia/patches/archive/0015-Bug-766017-warnings.patch @@ -0,0 +1,865 @@ +From: David Zbarsky +Bug 766017 - Fix some skia warnings r=gw280 + +diff --git a/gfx/skia/include/utils/mac/SkCGUtils.h b/gfx/skia/include/utils/mac/SkCGUtils.h +--- a/gfx/skia/include/utils/mac/SkCGUtils.h ++++ b/gfx/skia/include/utils/mac/SkCGUtils.h +@@ -39,18 +39,16 @@ static inline CGImageRef SkCreateCGImage + /** + * Draw the bitmap into the specified CG context. The bitmap will be converted + * to a CGImage using the generic RGB colorspace. (x,y) specifies the position + * of the top-left corner of the bitmap. The bitmap is converted using the + * colorspace returned by CGColorSpaceCreateDeviceRGB() + */ + void SkCGDrawBitmap(CGContextRef, const SkBitmap&, float x, float y); + +-bool SkPDFDocumentToBitmap(SkStream* stream, SkBitmap* output); +- + /** + * Return a provider that wraps the specified stream. It will become an + * owner of the stream, so the caller must still manage its ownership. + * + * To hand-off ownership of the stream to the provider, the caller must do + * something like the following: + * + * SkStream* stream = new ...; +diff --git a/gfx/skia/src/core/SkAAClip.cpp b/gfx/skia/src/core/SkAAClip.cpp +--- a/gfx/skia/src/core/SkAAClip.cpp ++++ b/gfx/skia/src/core/SkAAClip.cpp +@@ -246,17 +246,17 @@ static void count_left_right_zeros(const + zeros = 0; + } + row += 2; + width -= n; + } + *riteZ = zeros; + } + +-#ifdef SK_DEBUG ++#if 0 + static void test_count_left_right_zeros() { + static bool gOnce; + if (gOnce) { + return; + } + gOnce = true; + + const uint8_t data0[] = { 0, 0, 10, 0xFF }; +@@ -1319,22 +1319,16 @@ bool SkAAClip::setPath(const SkPath& pat + } + + /////////////////////////////////////////////////////////////////////////////// + + typedef void (*RowProc)(SkAAClip::Builder&, int bottom, + const uint8_t* rowA, const SkIRect& rectA, + const uint8_t* rowB, const SkIRect& rectB); + +-static void sectRowProc(SkAAClip::Builder& builder, int bottom, +- const uint8_t* rowA, const SkIRect& rectA, +- const uint8_t* rowB, const SkIRect& rectB) { +- +-} +- + typedef U8CPU (*AlphaProc)(U8CPU alphaA, U8CPU alphaB); + + static U8CPU sectAlphaProc(U8CPU alphaA, U8CPU alphaB) { + // Multiply + return SkMulDiv255Round(alphaA, alphaB); + } + + static U8CPU unionAlphaProc(U8CPU alphaA, U8CPU alphaB) { +@@ -1429,31 +1423,16 @@ private: + static void adjust_row(RowIter& iter, int& leftA, int& riteA, int rite) { + if (rite == riteA) { + iter.next(); + leftA = iter.left(); + riteA = iter.right(); + } + } + +-static bool intersect(int& min, int& max, int boundsMin, int boundsMax) { +- SkASSERT(min < max); +- SkASSERT(boundsMin < boundsMax); +- if (min >= boundsMax || max <= boundsMin) { +- return false; +- } +- if (min < boundsMin) { +- min = boundsMin; +- } +- if (max > boundsMax) { +- max = boundsMax; +- } +- return true; +-} +- + static void operatorX(SkAAClip::Builder& builder, int lastY, + RowIter& iterA, RowIter& iterB, + AlphaProc proc, const SkIRect& bounds) { + int leftA = iterA.left(); + int riteA = iterA.right(); + int leftB = iterB.left(); + int riteB = iterB.right(); + +@@ -1970,34 +1949,33 @@ static void small_bzero(void* dst, size_ + static inline uint8_t mergeOne(uint8_t value, unsigned alpha) { + return SkMulDiv255Round(value, alpha); + } + static inline uint16_t mergeOne(uint16_t value, unsigned alpha) { + unsigned r = SkGetPackedR16(value); + unsigned g = SkGetPackedG16(value); + unsigned b = SkGetPackedB16(value); + return SkPackRGB16(SkMulDiv255Round(r, alpha), +- SkMulDiv255Round(r, alpha), +- SkMulDiv255Round(r, alpha)); ++ SkMulDiv255Round(g, alpha), ++ SkMulDiv255Round(b, alpha)); + } + static inline SkPMColor mergeOne(SkPMColor value, unsigned alpha) { + unsigned a = SkGetPackedA32(value); + unsigned r = SkGetPackedR32(value); + unsigned g = SkGetPackedG32(value); + unsigned b = SkGetPackedB32(value); + return SkPackARGB32(SkMulDiv255Round(a, alpha), + SkMulDiv255Round(r, alpha), + SkMulDiv255Round(g, alpha), + SkMulDiv255Round(b, alpha)); + } + + template void mergeT(const T* SK_RESTRICT src, int srcN, + const uint8_t* SK_RESTRICT row, int rowN, + T* SK_RESTRICT dst) { +- SkDEBUGCODE(int accumulated = 0;) + for (;;) { + SkASSERT(rowN > 0); + SkASSERT(srcN > 0); + + int n = SkMin32(rowN, srcN); + unsigned rowA = row[1]; + if (0xFF == rowA) { + small_memcpy(dst, src, n * sizeof(T)); +diff --git a/gfx/skia/src/core/SkBlitMask_D32.cpp b/gfx/skia/src/core/SkBlitMask_D32.cpp +--- a/gfx/skia/src/core/SkBlitMask_D32.cpp ++++ b/gfx/skia/src/core/SkBlitMask_D32.cpp +@@ -268,107 +268,49 @@ bool SkBlitMask::BlitColor(const SkBitma + return true; + } + return false; + } + + /////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////// + +-static void BW_RowProc_Blend(SkPMColor* SK_RESTRICT dst, +- const uint8_t* SK_RESTRICT mask, +- const SkPMColor* SK_RESTRICT src, int count) { +- int i, octuple = (count + 7) >> 3; +- for (i = 0; i < octuple; ++i) { +- int m = *mask++; +- if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); } +- if (m & 0x40) { dst[1] = SkPMSrcOver(src[1], dst[1]); } +- if (m & 0x20) { dst[2] = SkPMSrcOver(src[2], dst[2]); } +- if (m & 0x10) { dst[3] = SkPMSrcOver(src[3], dst[3]); } +- if (m & 0x08) { dst[4] = SkPMSrcOver(src[4], dst[4]); } +- if (m & 0x04) { dst[5] = SkPMSrcOver(src[5], dst[5]); } +- if (m & 0x02) { dst[6] = SkPMSrcOver(src[6], dst[6]); } +- if (m & 0x01) { dst[7] = SkPMSrcOver(src[7], dst[7]); } +- src += 8; +- dst += 8; +- } +- count &= 7; +- if (count > 0) { +- int m = *mask; +- do { +- if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); } +- m <<= 1; +- src += 1; +- dst += 1; +- } while (--count > 0); +- } +-} +- +-static void BW_RowProc_Opaque(SkPMColor* SK_RESTRICT dst, +- const uint8_t* SK_RESTRICT mask, +- const SkPMColor* SK_RESTRICT src, int count) { +- int i, octuple = (count + 7) >> 3; +- for (i = 0; i < octuple; ++i) { +- int m = *mask++; +- if (m & 0x80) { dst[0] = src[0]; } +- if (m & 0x40) { dst[1] = src[1]; } +- if (m & 0x20) { dst[2] = src[2]; } +- if (m & 0x10) { dst[3] = src[3]; } +- if (m & 0x08) { dst[4] = src[4]; } +- if (m & 0x04) { dst[5] = src[5]; } +- if (m & 0x02) { dst[6] = src[6]; } +- if (m & 0x01) { dst[7] = src[7]; } +- src += 8; +- dst += 8; +- } +- count &= 7; +- if (count > 0) { +- int m = *mask; +- do { +- if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); } +- m <<= 1; +- src += 1; +- dst += 1; +- } while (--count > 0); +- } +-} +- + static void A8_RowProc_Blend(SkPMColor* SK_RESTRICT dst, + const uint8_t* SK_RESTRICT mask, + const SkPMColor* SK_RESTRICT src, int count) { + for (int i = 0; i < count; ++i) { + if (mask[i]) { + dst[i] = SkBlendARGB32(src[i], dst[i], mask[i]); + } + } + } + + // expand the steps that SkAlphaMulQ performs, but this way we can +-// exand.. add.. combine ++// expand.. add.. combine + // instead of + // expand..combine add expand..combine + // + #define EXPAND0(v, m, s) ((v) & (m)) * (s) + #define EXPAND1(v, m, s) (((v) >> 8) & (m)) * (s) + #define COMBINE(e0, e1, m) ((((e0) >> 8) & (m)) | ((e1) & ~(m))) + + static void A8_RowProc_Opaque(SkPMColor* SK_RESTRICT dst, + const uint8_t* SK_RESTRICT mask, + const SkPMColor* SK_RESTRICT src, int count) { +- const uint32_t rbmask = gMask_00FF00FF; + for (int i = 0; i < count; ++i) { + int m = mask[i]; + if (m) { + m += (m >> 7); + #if 1 + // this is slightly slower than the expand/combine version, but it + // is much closer to the old results, so we use it for now to reduce + // rebaselining. + dst[i] = SkAlphaMulQ(src[i], m) + SkAlphaMulQ(dst[i], 256 - m); + #else ++ const uint32_t rbmask = gMask_00FF00FF; + uint32_t v = src[i]; + uint32_t s0 = EXPAND0(v, rbmask, m); + uint32_t s1 = EXPAND1(v, rbmask, m); + v = dst[i]; + uint32_t d0 = EXPAND0(v, rbmask, m); + uint32_t d1 = EXPAND1(v, rbmask, m); + dst[i] = COMBINE(s0 + d0, s1 + d1, rbmask); + #endif +@@ -559,17 +501,17 @@ SkBlitMask::RowProc SkBlitMask::RowFacto + // make this opt-in until chrome can rebaseline + RowProc proc = PlatformRowProcs(config, format, flags); + if (proc) { + return proc; + } + + static const RowProc gProcs[] = { + // need X coordinate to handle BW +- NULL, NULL, //(RowProc)BW_RowProc_Blend, (RowProc)BW_RowProc_Opaque, ++ NULL, NULL, + (RowProc)A8_RowProc_Blend, (RowProc)A8_RowProc_Opaque, + (RowProc)LCD16_RowProc_Blend, (RowProc)LCD16_RowProc_Opaque, + (RowProc)LCD32_RowProc_Blend, (RowProc)LCD32_RowProc_Opaque, + }; + + int index; + switch (config) { + case SkBitmap::kARGB_8888_Config: +diff --git a/gfx/skia/src/core/SkConcaveToTriangles.cpp b/gfx/skia/src/core/SkConcaveToTriangles.cpp +--- a/gfx/skia/src/core/SkConcaveToTriangles.cpp ++++ b/gfx/skia/src/core/SkConcaveToTriangles.cpp +@@ -37,17 +37,16 @@ + #include "SkTDArray.h" + #include "SkGeometry.h" + #include "SkTSort.h" + + // This is used to prevent runaway code bugs, and can probably be removed after + // the code has been proven robust. + #define kMaxCount 1000 + +-#define DEBUG + #ifdef DEBUG + //------------------------------------------------------------------------------ + // Debugging support + //------------------------------------------------------------------------------ + + #include + #include + +diff --git a/gfx/skia/src/core/SkPath.cpp b/gfx/skia/src/core/SkPath.cpp +--- a/gfx/skia/src/core/SkPath.cpp ++++ b/gfx/skia/src/core/SkPath.cpp +@@ -469,17 +469,16 @@ void SkPath::incReserve(U16CPU inc) { + fPts.setReserve(fPts.count() + inc); + + SkDEBUGCODE(this->validate();) + } + + void SkPath::moveTo(SkScalar x, SkScalar y) { + SkDEBUGCODE(this->validate();) + +- int vc = fVerbs.count(); + SkPoint* pt; + + // remember our index + fLastMoveToIndex = fPts.count(); + + pt = fPts.append(); + *fVerbs.append() = kMove_Verb; + pt->set(x, y); +@@ -1163,17 +1162,16 @@ void SkPath::reversePathTo(const SkPath& + } + pts -= gPtsInVerb[verbs[i]]; + } + } + + void SkPath::reverseAddPath(const SkPath& src) { + this->incReserve(src.fPts.count()); + +- const SkPoint* startPts = src.fPts.begin(); + const SkPoint* pts = src.fPts.end(); + const uint8_t* startVerbs = src.fVerbs.begin(); + const uint8_t* verbs = src.fVerbs.end(); + + fIsOval = false; + + bool needMove = true; + bool needClose = false; +diff --git a/gfx/skia/src/core/SkRegion.cpp b/gfx/skia/src/core/SkRegion.cpp +--- a/gfx/skia/src/core/SkRegion.cpp ++++ b/gfx/skia/src/core/SkRegion.cpp +@@ -920,20 +920,16 @@ static int operate(const SkRegion::RunTy + /* Given count RunTypes in a complex region, return the worst case number of + logical intervals that represents (i.e. number of rects that would be + returned from the iterator). + + We could just return count/2, since there must be at least 2 values per + interval, but we can first trim off the const overhead of the initial TOP + value, plus the final BOTTOM + 2 sentinels. + */ +-static int count_to_intervals(int count) { +- SkASSERT(count >= 6); // a single rect is 6 values +- return (count - 4) >> 1; +-} + + /* Given a number of intervals, what is the worst case representation of that + many intervals? + + Worst case (from a storage perspective), is a vertical stack of single + intervals: TOP + N * (BOTTOM INTERVALCOUNT LEFT RIGHT SENTINEL) + SENTINEL + */ + static int intervals_to_count(int intervals) { +diff --git a/gfx/skia/src/core/SkScalerContext.cpp b/gfx/skia/src/core/SkScalerContext.cpp +--- a/gfx/skia/src/core/SkScalerContext.cpp ++++ b/gfx/skia/src/core/SkScalerContext.cpp +@@ -336,44 +336,16 @@ SK_ERROR: + glyph->fTop = 0; + glyph->fWidth = 0; + glyph->fHeight = 0; + // put a valid value here, in case it was earlier set to + // MASK_FORMAT_JUST_ADVANCE + glyph->fMaskFormat = fRec.fMaskFormat; + } + +-static bool isLCD(const SkScalerContext::Rec& rec) { +- return SkMask::kLCD16_Format == rec.fMaskFormat || +- SkMask::kLCD32_Format == rec.fMaskFormat; +-} +- +-static uint16_t a8_to_rgb565(unsigned a8) { +- return SkPackRGB16(a8 >> 3, a8 >> 2, a8 >> 3); +-} +- +-static void copyToLCD16(const SkBitmap& src, const SkMask& dst) { +- SkASSERT(SkBitmap::kA8_Config == src.config()); +- SkASSERT(SkMask::kLCD16_Format == dst.fFormat); +- +- const int width = dst.fBounds.width(); +- const int height = dst.fBounds.height(); +- const uint8_t* srcP = src.getAddr8(0, 0); +- size_t srcRB = src.rowBytes(); +- uint16_t* dstP = (uint16_t*)dst.fImage; +- size_t dstRB = dst.fRowBytes; +- for (int y = 0; y < height; ++y) { +- for (int x = 0; x < width; ++x) { +- dstP[x] = a8_to_rgb565(srcP[x]); +- } +- srcP += srcRB; +- dstP = (uint16_t*)((char*)dstP + dstRB); +- } +-} +- + #define SK_FREETYPE_LCD_LERP 160 + + static int lerp(int start, int end) { + SkASSERT((unsigned)SK_FREETYPE_LCD_LERP <= 256); + return start + ((end - start) * (SK_FREETYPE_LCD_LERP) >> 8); + } + + static uint16_t packLCD16(unsigned r, unsigned g, unsigned b) { +diff --git a/gfx/skia/src/core/SkScan_AntiPath.cpp b/gfx/skia/src/core/SkScan_AntiPath.cpp +--- a/gfx/skia/src/core/SkScan_AntiPath.cpp ++++ b/gfx/skia/src/core/SkScan_AntiPath.cpp +@@ -230,52 +230,16 @@ void SuperBlitter::blitH(int x, int y, i + fOffsetX); + + #ifdef SK_DEBUG + fRuns.assertValid(y & MASK, (1 << (8 - SHIFT))); + fCurrX = x + width; + #endif + } + +-static void set_left_rite_runs(SkAlphaRuns& runs, int ileft, U8CPU leftA, +- int n, U8CPU riteA) { +- SkASSERT(leftA <= 0xFF); +- SkASSERT(riteA <= 0xFF); +- +- int16_t* run = runs.fRuns; +- uint8_t* aa = runs.fAlpha; +- +- if (ileft > 0) { +- run[0] = ileft; +- aa[0] = 0; +- run += ileft; +- aa += ileft; +- } +- +- SkASSERT(leftA < 0xFF); +- if (leftA > 0) { +- *run++ = 1; +- *aa++ = leftA; +- } +- +- if (n > 0) { +- run[0] = n; +- aa[0] = 0xFF; +- run += n; +- aa += n; +- } +- +- SkASSERT(riteA < 0xFF); +- if (riteA > 0) { +- *run++ = 1; +- *aa++ = riteA; +- } +- run[0] = 0; +-} +- + void SuperBlitter::blitRect(int x, int y, int width, int height) { + SkASSERT(width > 0); + SkASSERT(height > 0); + + // blit leading rows + while ((y & MASK)) { + this->blitH(x, y++, width); + if (--height <= 0) { +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -865,45 +865,16 @@ bool Linear_Gradient::setContext(const S + } while (0) + + namespace { + + typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* dstC, const SkPMColor* cache, + int toggle, int count); + +-// This function is deprecated, and will be replaced by +-// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it. +-void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx, +- SkPMColor* SK_RESTRICT dstC, +- const SkPMColor* SK_RESTRICT cache, +- int toggle, int count) { +- if (proc == clamp_tileproc) { +- // Read out clamp values from beginning/end of the cache. No need to lerp +- // or dither +- if (fx < 0) { +- sk_memset32(dstC, cache[-1], count); +- return; +- } else if (fx > 0xFFFF) { +- sk_memset32(dstC, cache[Gradient_Shader::kCache32Count * 2], count); +- return; +- } +- } +- +- // We're a vertical gradient, so no change in a span. +- // If colors change sharply across the gradient, dithering is +- // insufficient (it subsamples the color space) and we need to lerp. +- unsigned fullIndex = proc(fx); +- unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits); +- sk_memset32_dither(dstC, +- cache[toggle + fi], +- cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], +- count); +-} +- + // Linear interpolation (lerp) is unnecessary if there are no sharp + // discontinuities in the gradient - which must be true if there are + // only 2 colors - but it's cheap. + void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + if (proc == clamp_tileproc) { +@@ -2131,16 +2102,18 @@ protected: + buffer.writePoint(fCenter); + } + + private: + typedef Gradient_Shader INHERITED; + const SkPoint fCenter; + }; + ++#ifndef SK_SCALAR_IS_FLOAT ++ + #ifdef COMPUTE_SWEEP_TABLE + #define PI 3.14159265 + static bool gSweepTableReady; + static uint8_t gSweepTable[65]; + + /* Our table stores precomputed values for atan: [0...1] -> [0..PI/4] + We scale the results to [0..32] + */ +@@ -2168,20 +2141,23 @@ static const uint8_t gSweepTable[] = { + 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18, + 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26, + 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, + 32 + }; + static const uint8_t* build_sweep_table() { return gSweepTable; } + #endif + ++#endif ++ + // divide numer/denom, with a bias of 6bits. Assumes numer <= denom + // and denom != 0. Since our table is 6bits big (+1), this is a nice fit. + // Same as (but faster than) SkFixedDiv(numer, denom) >> 10 + ++#ifndef SK_SCALAR_IS_FLOAT + //unsigned div_64(int numer, int denom); + static unsigned div_64(int numer, int denom) { + SkASSERT(numer <= denom); + SkASSERT(numer > 0); + SkASSERT(denom > 0); + + int nbits = SkCLZ(numer); + int dbits = SkCLZ(denom); +@@ -2294,16 +2270,17 @@ static unsigned atan_0_90(SkFixed y, SkF + result = 64 - result; + // pin to 63 + result -= result >> 6; + } + + SkASSERT(result <= 63); + return result; + } ++#endif + + // returns angle in a circle [0..2PI) -> [0..255] + #ifdef SK_SCALAR_IS_FLOAT + static unsigned SkATan2_255(float y, float x) { + // static const float g255Over2PI = 255 / (2 * SK_ScalarPI); + static const float g255Over2PI = 40.584510488433314f; + + float result = sk_float_atan2(y, x); +diff --git a/gfx/skia/src/opts/SkBlitRect_opts_SSE2.cpp b/gfx/skia/src/opts/SkBlitRect_opts_SSE2.cpp +--- a/gfx/skia/src/opts/SkBlitRect_opts_SSE2.cpp ++++ b/gfx/skia/src/opts/SkBlitRect_opts_SSE2.cpp +@@ -112,17 +112,17 @@ void BlitRect32_OpaqueWide_SSE2(SkPMColo + } + + void ColorRect32_SSE2(SkPMColor* destination, + int width, int height, + size_t rowBytes, uint32_t color) { + if (0 == height || 0 == width || 0 == color) { + return; + } +- unsigned colorA = SkGetPackedA32(color); ++ //unsigned colorA = SkGetPackedA32(color); + //if (255 == colorA) { + //if (width < 31) { + //BlitRect32_OpaqueNarrow_SSE2(destination, width, height, + //rowBytes, color); + //} else { + //BlitRect32_OpaqueWide_SSE2(destination, width, height, + //rowBytes, color); + //} +diff --git a/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp b/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp +--- a/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp ++++ b/gfx/skia/src/ports/SkFontHost_mac_coretext.cpp +@@ -75,20 +75,16 @@ static CGFloat CGRectGetMinY_inline(cons + static CGFloat CGRectGetMaxY_inline(const CGRect& rect) { + return rect.origin.y + rect.size.height; + } + + static CGFloat CGRectGetWidth_inline(const CGRect& rect) { + return rect.size.width; + } + +-static CGFloat CGRectGetHeight(const CGRect& rect) { +- return rect.size.height; +-} +- + /////////////////////////////////////////////////////////////////////////////// + + static void sk_memset_rect32(uint32_t* ptr, uint32_t value, size_t width, + size_t height, size_t rowBytes) { + SkASSERT(width); + SkASSERT(width * sizeof(uint32_t) <= rowBytes); + + if (width >= 32) { +@@ -125,28 +121,30 @@ static void sk_memset_rect32(uint32_t* p + *ptr++ = value; + } while (--w > 0); + ptr = (uint32_t*)((char*)ptr + rowBytes); + height -= 1; + } + } + } + ++#if 0 + // Potentially this should be made (1) public (2) optimized when width is small. + // Also might want 16 and 32 bit version + // + static void sk_memset_rect(void* ptr, U8CPU byte, size_t width, size_t height, + size_t rowBytes) { + uint8_t* dst = (uint8_t*)ptr; + while (height) { + memset(dst, byte, width); + dst += rowBytes; + height -= 1; + } + } ++#endif + + #include + + typedef uint32_t CGRGBPixel; + + static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) { + return pixel & 0xFF; + } +@@ -250,23 +248,16 @@ static CGAffineTransform MatrixToCGAffin + return CGAffineTransformMake(ScalarToCG(matrix[SkMatrix::kMScaleX]) * sx, + -ScalarToCG(matrix[SkMatrix::kMSkewY]) * sy, + -ScalarToCG(matrix[SkMatrix::kMSkewX]) * sx, + ScalarToCG(matrix[SkMatrix::kMScaleY]) * sy, + ScalarToCG(matrix[SkMatrix::kMTransX]) * sx, + ScalarToCG(matrix[SkMatrix::kMTransY]) * sy); + } + +-static void CGAffineTransformToMatrix(const CGAffineTransform& xform, SkMatrix* matrix) { +- matrix->setAll( +- CGToScalar(xform.a), CGToScalar(xform.c), CGToScalar(xform.tx), +- CGToScalar(xform.b), CGToScalar(xform.d), CGToScalar(xform.ty), +- 0, 0, SK_Scalar1); +-} +- + static SkScalar getFontScale(CGFontRef cgFont) { + int unitsPerEm = CGFontGetUnitsPerEm(cgFont); + return SkScalarInvert(SkIntToScalar(unitsPerEm)); + } + + /////////////////////////////////////////////////////////////////////////////// + + #define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) +@@ -1075,16 +1066,17 @@ static const uint8_t* getInverseTable(bo + if (!gInited) { + build_power_table(gWhiteTable, 1.5f); + build_power_table(gTable, 2.2f); + gInited = true; + } + return isWhite ? gWhiteTable : gTable; + } + ++#ifdef SK_USE_COLOR_LUMINANCE + static const uint8_t* getGammaTable(U8CPU luminance) { + static uint8_t gGammaTables[4][256]; + static bool gInited; + if (!gInited) { + #if 1 + float start = 1.1; + float stop = 2.1; + for (int i = 0; i < 4; ++i) { +@@ -1097,45 +1089,49 @@ static const uint8_t* getGammaTable(U8CP + build_power_table(gGammaTables[2], 1); + build_power_table(gGammaTables[3], 1); + #endif + gInited = true; + } + SkASSERT(0 == (luminance >> 8)); + return gGammaTables[luminance >> 6]; + } ++#endif + ++#ifndef SK_USE_COLOR_LUMINANCE + static void invertGammaMask(bool isWhite, CGRGBPixel rgb[], int width, + int height, size_t rb) { + const uint8_t* table = getInverseTable(isWhite); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32_t c = rgb[x]; + int r = (c >> 16) & 0xFF; + int g = (c >> 8) & 0xFF; + int b = (c >> 0) & 0xFF; + rgb[x] = (table[r] << 16) | (table[g] << 8) | table[b]; + } + rgb = (CGRGBPixel*)((char*)rgb + rb); + } + } ++#endif + + static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) { + while (count > 0) { + uint8_t mask = 0; + for (int i = 7; i >= 0; --i) { + mask |= (CGRGBPixel_getAlpha(*src++) >> 7) << i; + if (0 == --count) { + break; + } + } + *dst++ = mask; + } + } + ++#ifdef SK_USE_COLOR_LUMINANCE + static int lerpScale(int dst, int src, int scale) { + return dst + (scale * (src - dst) >> 23); + } + + static CGRGBPixel lerpPixel(CGRGBPixel dst, CGRGBPixel src, + int scaleR, int scaleG, int scaleB) { + int sr = (src >> 16) & 0xFF; + int sg = (src >> 8) & 0xFF; +@@ -1147,37 +1143,31 @@ static CGRGBPixel lerpPixel(CGRGBPixel d + int rr = lerpScale(dr, sr, scaleR); + int rg = lerpScale(dg, sg, scaleG); + int rb = lerpScale(db, sb, scaleB); + return (rr << 16) | (rg << 8) | rb; + } + + static void lerpPixels(CGRGBPixel dst[], const CGRGBPixel src[], int width, + int height, int rowBytes, int lumBits) { +-#ifdef SK_USE_COLOR_LUMINANCE + int scaleR = (1 << 23) * SkColorGetR(lumBits) / 0xFF; + int scaleG = (1 << 23) * SkColorGetG(lumBits) / 0xFF; + int scaleB = (1 << 23) * SkColorGetB(lumBits) / 0xFF; +-#else +- int scale = (1 << 23) * lumBits / SkScalerContext::kLuminance_Max; +- int scaleR = scale; +- int scaleG = scale; +- int scaleB = scale; +-#endif + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // bit-not the src, since it was drawn from black, so we need the + // compliment of those bits + dst[x] = lerpPixel(dst[x], ~src[x], scaleR, scaleG, scaleB); + } + src = (CGRGBPixel*)((char*)src + rowBytes); + dst = (CGRGBPixel*)((char*)dst + rowBytes); + } + } ++#endif + + #if 1 + static inline int r32_to_16(int x) { return SkR32ToR16(x); } + static inline int g32_to_16(int x) { return SkG32ToG16(x); } + static inline int b32_to_16(int x) { return SkB32ToB16(x); } + #else + static inline int round8to5(int x) { + return (x + 3 - (x >> 5) + (x >> 7)) >> 3; +@@ -1212,22 +1202,21 @@ static inline uint32_t rgb_to_lcd32(CGRG + return SkPackARGB32(0xFF, r, g, b); + } + + #define BLACK_LUMINANCE_LIMIT 0x40 + #define WHITE_LUMINANCE_LIMIT 0xA0 + + void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) { + CGGlyph cgGlyph = (CGGlyph) glyph.getGlyphID(fBaseGlyphCount); +- + const bool isLCD = isLCDFormat(glyph.fMaskFormat); ++#ifdef SK_USE_COLOR_LUMINANCE + const bool isBW = SkMask::kBW_Format == glyph.fMaskFormat; + const bool isA8 = !isLCD && !isBW; +- +-#ifdef SK_USE_COLOR_LUMINANCE ++ + unsigned lumBits = fRec.getLuminanceColor(); + uint32_t xorMask = 0; + + if (isA8) { + // for A8, we just want a component (they're all the same) + lumBits = SkColorGetR(lumBits); + } + #else +diff --git a/gfx/skia/src/utils/mac/SkCreateCGImageRef.cpp b/gfx/skia/src/utils/mac/SkCreateCGImageRef.cpp +--- a/gfx/skia/src/utils/mac/SkCreateCGImageRef.cpp ++++ b/gfx/skia/src/utils/mac/SkCreateCGImageRef.cpp +@@ -163,59 +163,8 @@ private: + CGPDFDocumentRef fDoc; + }; + + static void CGDataProviderReleaseData_FromMalloc(void*, const void* data, + size_t size) { + sk_free((void*)data); + } + +-bool SkPDFDocumentToBitmap(SkStream* stream, SkBitmap* output) { +- size_t size = stream->getLength(); +- void* ptr = sk_malloc_throw(size); +- stream->read(ptr, size); +- CGDataProviderRef data = CGDataProviderCreateWithData(NULL, ptr, size, +- CGDataProviderReleaseData_FromMalloc); +- if (NULL == data) { +- return false; +- } +- +- CGPDFDocumentRef pdf = CGPDFDocumentCreateWithProvider(data); +- CGDataProviderRelease(data); +- if (NULL == pdf) { +- return false; +- } +- SkAutoPDFRelease releaseMe(pdf); +- +- CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1); +- if (NULL == page) { +- return false; +- } +- +- CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFMediaBox); +- +- int w = (int)CGRectGetWidth(bounds); +- int h = (int)CGRectGetHeight(bounds); +- +- SkBitmap bitmap; +- bitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h); +- bitmap.allocPixels(); +- bitmap.eraseColor(SK_ColorWHITE); +- +- size_t bitsPerComponent; +- CGBitmapInfo info; +- getBitmapInfo(bitmap, &bitsPerComponent, &info, NULL); +- +- CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); +- CGContextRef ctx = CGBitmapContextCreate(bitmap.getPixels(), w, h, +- bitsPerComponent, bitmap.rowBytes(), +- cs, info); +- CGColorSpaceRelease(cs); +- +- if (ctx) { +- CGContextDrawPDFPage(ctx, page); +- CGContextRelease(ctx); +- } +- +- output->swap(bitmap); +- return true; +-} +- diff --git a/gfx/skia/patches/archive/0016-Bug-718849-Radial-gradients.patch b/gfx/skia/patches/archive/0016-Bug-718849-Radial-gradients.patch new file mode 100644 index 0000000000..e00fd8602e --- /dev/null +++ b/gfx/skia/patches/archive/0016-Bug-718849-Radial-gradients.patch @@ -0,0 +1,400 @@ +# HG changeset patch +# User Matt Woodrow +# Date 1339988782 -43200 +# Node ID 1e9dae659ee6c992f719fd4136efbcc5410ded37 +# Parent 946750f6d95febd199fb7b748e9d2c48fd01c8a6 +[mq]: skia-windows-gradients + +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -847,16 +847,19 @@ bool Linear_Gradient::setContext(const S + fFlags |= SkShader::kConstInY32_Flag; + if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) { + // only claim this if we do have a 16bit mode (i.e. none of our + // colors have alpha), and if we are not dithering (which obviously + // is not const in Y). + fFlags |= SkShader::kConstInY16_Flag; + } + } ++ if (fStart == fEnd) { ++ fFlags &= ~kOpaqueAlpha_Flag; ++ } + return true; + } + + #define NO_CHECK_ITER \ + do { \ + unsigned fi = fx >> Gradient_Shader::kCache32Shift; \ + SkASSERT(fi <= 0xFF); \ + fx += dx; \ +@@ -976,16 +979,21 @@ void Linear_Gradient::shadeSpan(int x, i + TileProc proc = fTileProc; + const SkPMColor* SK_RESTRICT cache = this->getCache32(); + #ifdef USE_DITHER_32BIT_GRADIENT + int toggle = ((x ^ y) & 1) * kDitherStride32; + #else + int toggle = 0; + #endif + ++ if (fStart == fEnd) { ++ sk_bzero(dstC, count * sizeof(*dstC)); ++ return; ++ } ++ + if (fDstToIndexClass != kPerspective_MatrixClass) { + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkFixed dx, fx = SkScalarToFixed(srcPt.fX); + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + SkFixed dxStorage[1]; + (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL); +@@ -1169,16 +1177,21 @@ void Linear_Gradient::shadeSpan16(int x, + SkASSERT(count > 0); + + SkPoint srcPt; + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = fTileProc; + const uint16_t* SK_RESTRICT cache = this->getCache16(); + int toggle = ((x ^ y) & 1) * kDitherStride16; + ++ if (fStart == fEnd) { ++ sk_bzero(dstC, count * sizeof(*dstC)); ++ return; ++ } ++ + if (fDstToIndexClass != kPerspective_MatrixClass) { + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkFixed dx, fx = SkScalarToFixed(srcPt.fX); + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + SkFixed dxStorage[1]; + (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL); +@@ -1739,21 +1752,25 @@ void Radial_Gradient::shadeSpan(int x, i + possible circles on which the point may fall. Solving for t yields + the gradient value to use. + + If a<0, the start circle is entirely contained in the + end circle, and one of the roots will be <0 or >1 (off the line + segment). If a>0, the start circle falls at least partially + outside the end circle (or vice versa), and the gradient + defines a "tube" where a point may be on one circle (on the +- inside of the tube) or the other (outside of the tube). We choose +- one arbitrarily. ++ inside of the tube) or the other (outside of the tube). We choose ++ the one with the highest t value, as long as the radius that it ++ corresponds to is >=0. In the case where neither root has a positive ++ radius, we don't draw anything. + ++ XXXmattwoodrow: I've removed this for now since it breaks ++ down when Dr == 0. Is there something else we can do instead? + In order to keep the math to within the limits of fixed point, +- we divide the entire quadratic by Dr^2, and replace ++ we divide the entire quadratic by Dr, and replace + (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving + + [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2 + + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t + + [x'^2 + y'^2 - Sr^2/Dr^2] = 0 + + (x' and y' are computed by appending the subtract and scale to the + fDstToIndex matrix in the constructor). +@@ -1763,99 +1780,122 @@ void Radial_Gradient::shadeSpan(int x, i + x' and y', if x and y are linear in the span, 'B' can be computed + incrementally with a simple delta (db below). If it is not (e.g., + a perspective projection), it must be computed in the loop. + + */ + + namespace { + +-inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy, +- SkScalar sr2d2, SkScalar foura, +- SkScalar oneOverTwoA, bool posRoot) { ++inline bool two_point_radial(SkScalar b, SkScalar fx, SkScalar fy, ++ SkScalar sr2d2, SkScalar foura, ++ SkScalar oneOverTwoA, SkScalar diffRadius, ++ SkScalar startRadius, SkFixed& t) { + SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2; + if (0 == foura) { +- return SkScalarToFixed(SkScalarDiv(-c, b)); ++ SkScalar result = SkScalarDiv(-c, b); ++ if (result * diffRadius + startRadius >= 0) { ++ t = SkScalarToFixed(result); ++ return true; ++ } ++ return false; + } + + SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c); + if (discrim < 0) { +- discrim = -discrim; ++ return false; + } + SkScalar rootDiscrim = SkScalarSqrt(discrim); +- SkScalar result; +- if (posRoot) { +- result = SkScalarMul(-b + rootDiscrim, oneOverTwoA); +- } else { +- result = SkScalarMul(-b - rootDiscrim, oneOverTwoA); ++ ++ // Make sure the results corresponds to a positive radius. ++ SkScalar result = SkScalarMul(-b + rootDiscrim, oneOverTwoA); ++ if (result * diffRadius + startRadius >= 0) { ++ t = SkScalarToFixed(result); ++ return true; + } +- return SkScalarToFixed(result); ++ result = SkScalarMul(-b - rootDiscrim, oneOverTwoA); ++ if (result * diffRadius + startRadius >= 0) { ++ t = SkScalarToFixed(result); ++ return true; ++ } ++ ++ return false; + } + + typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx, + SkScalar fy, SkScalar dy, + SkScalar b, SkScalar db, +- SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot, ++ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, ++ SkScalar fDiffRadius, SkScalar fRadius1, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count); + + void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx, + SkScalar fy, SkScalar dy, + SkScalar b, SkScalar db, +- SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot, ++ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, ++ SkScalar fDiffRadius, SkScalar fRadius1, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count) { + for (; count > 0; --count) { +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, +- fOneOverTwoA, posRoot); +- +- if (t < 0) { ++ SkFixed t; ++ if (!two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, fDiffRadius, fRadius1, t)) { ++ *(dstC++) = 0; ++ } else if (t < 0) { + *dstC++ = cache[-1]; + } else if (t > 0xFFFF) { + *dstC++ = cache[Gradient_Shader::kCache32Count * 2]; + } else { + SkASSERT(t <= 0xFFFF); + *dstC++ = cache[t >> Gradient_Shader::kCache32Shift]; + } + + fx += dx; + fy += dy; + b += db; + } + } + void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx, + SkScalar fy, SkScalar dy, + SkScalar b, SkScalar db, +- SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot, ++ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, ++ SkScalar fDiffRadius, SkScalar fRadius1, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count) { + for (; count > 0; --count) { +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, +- fOneOverTwoA, posRoot); +- SkFixed index = mirror_tileproc(t); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> Gradient_Shader::kCache32Shift]; ++ SkFixed t; ++ if (!two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, fDiffRadius, fRadius1, t)) { ++ *(dstC++) = 0; ++ } else { ++ SkFixed index = mirror_tileproc(t); ++ SkASSERT(index <= 0xFFFF); ++ *dstC++ = cache[index >> (16 - Gradient_Shader::kCache32Shift)]; ++ } + fx += dx; + fy += dy; + b += db; + } + } + + void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx, + SkScalar fy, SkScalar dy, + SkScalar b, SkScalar db, +- SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot, ++ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, ++ SkScalar fDiffRadius, SkScalar fRadius1, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count) { + for (; count > 0; --count) { +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, +- fOneOverTwoA, posRoot); +- SkFixed index = repeat_tileproc(t); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> Gradient_Shader::kCache32Shift]; ++ SkFixed t; ++ if (!two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, fDiffRadius, fRadius1, t)) { ++ *(dstC++) = 0; ++ } else { ++ SkFixed index = repeat_tileproc(t); ++ SkASSERT(index <= 0xFFFF); ++ *dstC++ = cache[index >> (16 - Gradient_Shader::kCache32Shift)]; ++ } + fx += dx; + fy += dy; + b += db; + } + } + + + +@@ -1935,17 +1975,16 @@ public: + sk_bzero(dstC, count * sizeof(*dstC)); + return; + } + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = fTileProc; + const SkPMColor* SK_RESTRICT cache = this->getCache32(); + + SkScalar foura = fA * 4; +- bool posRoot = fDiffRadius < 0; + if (fDstToIndexClass != kPerspective_MatrixClass) { + SkPoint srcPt; + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkScalar dx, fx = srcPt.fX; + SkScalar dy, fy = srcPt.fY; + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { +@@ -1954,60 +1993,69 @@ public: + dx = SkFixedToScalar(fixedX); + dy = SkFixedToScalar(fixedY); + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = fDstToIndex.getScaleX(); + dy = fDstToIndex.getSkewY(); + } + SkScalar b = (SkScalarMul(fDiff.fX, fx) + +- SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; ++ SkScalarMul(fDiff.fY, fy) - fStartRadius * fDiffRadius) * 2; + SkScalar db = (SkScalarMul(fDiff.fX, dx) + + SkScalarMul(fDiff.fY, dy)) * 2; + + TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat; + if (proc == clamp_tileproc) { + shadeProc = shadeSpan_twopoint_clamp; + } else if (proc == mirror_tileproc) { + shadeProc = shadeSpan_twopoint_mirror; + } else { + SkASSERT(proc == repeat_tileproc); + } + (*shadeProc)(fx, dx, fy, dy, b, db, +- fSr2D2, foura, fOneOverTwoA, posRoot, ++ fSr2D2, foura, fOneOverTwoA, fDiffRadius, fRadius1, + dstC, cache, count); + } else { // perspective case + SkScalar dstX = SkIntToScalar(x); + SkScalar dstY = SkIntToScalar(y); + for (; count > 0; --count) { + SkPoint srcPt; + dstProc(fDstToIndex, dstX, dstY, &srcPt); + SkScalar fx = srcPt.fX; + SkScalar fy = srcPt.fY; + SkScalar b = (SkScalarMul(fDiff.fX, fx) + + SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, +- fOneOverTwoA, posRoot); +- SkFixed index = proc(t); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> Gradient_Shader::kCache32Shift]; ++ SkFixed t; ++ if (!two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, fDiffRadius, fRadius1, t)) { ++ *(dstC++) = 0; ++ } else { ++ SkFixed index = proc(t); ++ SkASSERT(index <= 0xFFFF); ++ *dstC++ = cache[index >> (16 - kCache32Bits)]; ++ } + dstX += SK_Scalar1; + } + } + } + + virtual bool setContext(const SkBitmap& device, + const SkPaint& paint, + const SkMatrix& matrix) SK_OVERRIDE { + if (!this->INHERITED::setContext(device, paint, matrix)) { + return false; + } + + // we don't have a span16 proc + fFlags &= ~kHasSpan16_Flag; ++ ++ // If we might end up wanting to draw nothing as part of the gradient ++ // then we should mark ourselves as not being opaque. ++ if (fA >= 0 || (fDiffRadius == 0 && fCenter1 == fCenter2)) { ++ fFlags &= ~kOpaqueAlpha_Flag; ++ } + return true; + } + + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Radial_Gradient) + + protected: + Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer) + : INHERITED(buffer), +@@ -2033,26 +2081,22 @@ private: + const SkScalar fRadius1; + const SkScalar fRadius2; + SkPoint fDiff; + SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA; + + void init() { + fDiff = fCenter1 - fCenter2; + fDiffRadius = fRadius2 - fRadius1; +- SkScalar inv = SkScalarInvert(fDiffRadius); +- fDiff.fX = SkScalarMul(fDiff.fX, inv); +- fDiff.fY = SkScalarMul(fDiff.fY, inv); +- fStartRadius = SkScalarMul(fRadius1, inv); ++ fStartRadius = fRadius1; + fSr2D2 = SkScalarSquare(fStartRadius); +- fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1; ++ fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SkScalarSquare(fDiffRadius); + fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0; + + fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY); +- fPtsToUnit.postScale(inv, inv); + } + }; + + /////////////////////////////////////////////////////////////////////////////// + + class Sweep_Gradient : public Gradient_Shader { + public: + Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[], +@@ -2488,16 +2532,20 @@ SkShader* SkGradientShader::CreateTwoPoi + int colorCount, + SkShader::TileMode mode, + SkUnitMapper* mapper) { + if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) { + return NULL; + } + EXPAND_1_COLOR(colorCount); + ++ if (start == end && startRadius == 0) { ++ return CreateRadial(start, endRadius, colors, pos, colorCount, mode, mapper); ++ } ++ + return SkNEW_ARGS(Two_Point_Radial_Gradient, + (start, startRadius, end, endRadius, colors, pos, + colorCount, mode, mapper)); + } + + SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], + const SkScalar pos[], diff --git a/gfx/skia/patches/archive/0017-Bug-740194-SkMemory-mozalloc.patch b/gfx/skia/patches/archive/0017-Bug-740194-SkMemory-mozalloc.patch new file mode 100644 index 0000000000..719fda1650 --- /dev/null +++ b/gfx/skia/patches/archive/0017-Bug-740194-SkMemory-mozalloc.patch @@ -0,0 +1,73 @@ +commit 5786f516119bcb677510f3c9256b870c3b5616c8 +Author: George Wright +Date: Wed Aug 15 23:51:34 2012 -0400 + + Bug 740194 - [Skia] Implement a version of SkMemory for Mozilla that uses the infallible mozalloc allocators r=cjones + +diff --git a/gfx/skia/include/config/SkUserConfig.h b/gfx/skia/include/config/SkUserConfig.h +index f98ba85..17be191 100644 +--- a/gfx/skia/include/config/SkUserConfig.h ++++ b/gfx/skia/include/config/SkUserConfig.h +@@ -35,6 +35,16 @@ + commented out, so including it will have no effect. + */ + ++/* ++ Override new/delete with Mozilla's allocator, mozalloc ++ ++ Ideally we shouldn't need to do this here, but until ++ http://code.google.com/p/skia/issues/detail?id=598 is fixed ++ we need to include this here to override operator new and delete ++*/ ++ ++#include "mozilla/mozalloc.h" ++ + /////////////////////////////////////////////////////////////////////////////// + + /* Scalars (the fractional value type in skia) can be implemented either as +diff --git a/gfx/skia/src/ports/SkMemory_mozalloc.cpp b/gfx/skia/src/ports/SkMemory_mozalloc.cpp +new file mode 100644 +index 0000000..1f16ee5 +--- /dev/null ++++ b/gfx/skia/src/ports/SkMemory_mozalloc.cpp +@@ -0,0 +1,40 @@ ++/* ++ * Copyright 2011 Google Inc. ++ * Copyright 2012 Mozilla Foundation ++ * ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. ++ */ ++ ++#include "SkTypes.h" ++ ++#include "mozilla/mozalloc.h" ++#include "mozilla/mozalloc_abort.h" ++#include "mozilla/mozalloc_oom.h" ++ ++void sk_throw() { ++ SkDEBUGFAIL("sk_throw"); ++ mozalloc_abort("Abort from sk_throw"); ++} ++ ++void sk_out_of_memory(void) { ++ SkDEBUGFAIL("sk_out_of_memory"); ++ mozalloc_handle_oom(0); ++} ++ ++void* sk_malloc_throw(size_t size) { ++ return sk_malloc_flags(size, SK_MALLOC_THROW); ++} ++ ++void* sk_realloc_throw(void* addr, size_t size) { ++ return moz_xrealloc(addr, size); ++} ++ ++void sk_free(void* p) { ++ free(p); ++} ++ ++void* sk_malloc_flags(size_t size, unsigned flags) { ++ return (flags & SK_MALLOC_THROW) ? moz_xmalloc(size) : malloc(size); ++} ++ diff --git a/gfx/skia/patches/archive/0018-Bug-817356-PPC-defines.patch b/gfx/skia/patches/archive/0018-Bug-817356-PPC-defines.patch new file mode 100644 index 0000000000..d16ec4b3b4 --- /dev/null +++ b/gfx/skia/patches/archive/0018-Bug-817356-PPC-defines.patch @@ -0,0 +1,14 @@ +Index: gfx/skia/include/core/SkPreConfig.h +=================================================================== +--- gfx/skia/include/core/SkPreConfig.h (revision 6724) ++++ gfx/skia/include/core/SkPreConfig.h (working copy) +@@ -94,7 +94,8 @@ + ////////////////////////////////////////////////////////////////////// + + #if !defined(SK_CPU_BENDIAN) && !defined(SK_CPU_LENDIAN) +- #if defined (__ppc__) || defined(__ppc64__) ++ #if defined (__ppc__) || defined(__PPC__) || defined(__ppc64__) \ ++ || defined(__PPC64__) + #define SK_CPU_BENDIAN + #else + #define SK_CPU_LENDIAN diff --git a/gfx/skia/patches/archive/0022-Bug-848491-Re-apply-bug-795538-Ensure-we-use-the-cor.patch b/gfx/skia/patches/archive/0022-Bug-848491-Re-apply-bug-795538-Ensure-we-use-the-cor.patch new file mode 100644 index 0000000000..97404c431b --- /dev/null +++ b/gfx/skia/patches/archive/0022-Bug-848491-Re-apply-bug-795538-Ensure-we-use-the-cor.patch @@ -0,0 +1,39 @@ +From: George Wright +Date: Thu, 20 Jun 2013 09:21:21 -0400 +Subject: Bug 848491 - Re-apply bug 795538 - Ensure we use the correct colour (and alpha) for the clamp values r=mattwoodrow + + +diff --git a/gfx/skia/src/effects/gradients/SkGradientShader.cpp b/gfx/skia/src/effects/gradients/SkGradientShader.cpp +index 27a9c46..ce077b5 100644 +--- a/gfx/skia/src/effects/gradients/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/gradients/SkGradientShader.cpp +@@ -500,15 +500,17 @@ const SkPMColor* SkGradientShaderBase::getCache32() const { + } + + // Write the clamp colours into the first and last entries of fCache32 +- fCache32[kCache32ClampLower] = SkPackARGB32(fCacheAlpha, +- SkColorGetR(fOrigColors[0]), +- SkColorGetG(fOrigColors[0]), +- SkColorGetB(fOrigColors[0])); +- +- fCache32[kCache32ClampUpper] = SkPackARGB32(fCacheAlpha, +- SkColorGetR(fOrigColors[fColorCount - 1]), +- SkColorGetG(fOrigColors[fColorCount - 1]), +- SkColorGetB(fOrigColors[fColorCount - 1])); ++ fCache32[kCache32ClampLower] = SkPremultiplyARGBInline(SkMulDiv255Round(SkColorGetA(fOrigColors[0]), ++ fCacheAlpha), ++ SkColorGetR(fOrigColors[0]), ++ SkColorGetG(fOrigColors[0]), ++ SkColorGetB(fOrigColors[0])); ++ ++ fCache32[kCache32ClampUpper] = SkPremultiplyARGBInline(SkMulDiv255Round(SkColorGetA(fOrigColors[fColorCount - 1]), ++ fCacheAlpha), ++ SkColorGetR(fOrigColors[fColorCount - 1]), ++ SkColorGetG(fOrigColors[fColorCount - 1]), ++ SkColorGetB(fOrigColors[fColorCount - 1])); + + return fCache32; + } +-- +1.7.11.7 + diff --git a/gfx/skia/patches/archive/0023-Bug-890539-Fix-SK_COMPILE_ASSERT-build-warning.patch b/gfx/skia/patches/archive/0023-Bug-890539-Fix-SK_COMPILE_ASSERT-build-warning.patch new file mode 100644 index 0000000000..9bc7ddec46 --- /dev/null +++ b/gfx/skia/patches/archive/0023-Bug-890539-Fix-SK_COMPILE_ASSERT-build-warning.patch @@ -0,0 +1,39 @@ +# HG changeset patch +# Parent e378875000890099fffcdb4cbc4ab12828ac34ee +# User Daniel Holbert +Bug 890539: Annotate SK_COMPILE_ASSERT's typedef as permissibly unused, to fix GCC 4.8 build warning. r=gw280 + +diff --git a/gfx/skia/include/core/SkTypes.h b/gfx/skia/include/core/SkTypes.h +--- a/gfx/skia/include/core/SkTypes.h ++++ b/gfx/skia/include/core/SkTypes.h +@@ -121,18 +121,29 @@ inline void operator delete(void* p) { + #define SkDEVCODE(code) + #define SK_DEVELOPER_TO_STRING() + #endif + + template + struct SkCompileAssert { + }; + ++/* ++ * The SK_COMPILE_ASSERT definition creates an otherwise-unused typedef. This ++ * triggers compiler warnings with some versions of gcc, so mark the typedef ++ * as permissibly-unused to disable the warnings. ++ */ ++# if defined(__GNUC__) ++# define SK_COMPILE_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) ++# else ++# define SK_COMPILE_ASSERT_UNUSED_ATTRIBUTE /* nothing */ ++# endif ++ + #define SK_COMPILE_ASSERT(expr, msg) \ +- typedef SkCompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] ++ typedef SkCompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] SK_COMPILE_ASSERT_UNUSED_ATTRIBUTE + + /* + * Usage: SK_MACRO_CONCAT(a, b) to construct the symbol ab + * + * SK_MACRO_CONCAT_IMPL_PRIV just exists to make this work. Do not use directly + * + */ + #define SK_MACRO_CONCAT(X, Y) SK_MACRO_CONCAT_IMPL_PRIV(X, Y) diff --git a/gfx/skia/patches/archive/0024-Bug-887318-fix-bgra-readback.patch b/gfx/skia/patches/archive/0024-Bug-887318-fix-bgra-readback.patch new file mode 100644 index 0000000000..864a0af7a9 --- /dev/null +++ b/gfx/skia/patches/archive/0024-Bug-887318-fix-bgra-readback.patch @@ -0,0 +1,217 @@ +diff --git a/gfx/gl/GLContextSkia.cpp b/gfx/gl/GLContextSkia.cpp +--- a/gfx/gl/GLContextSkia.cpp ++++ b/gfx/gl/GLContextSkia.cpp +@@ -303,39 +303,47 @@ const GLubyte* glGetString_mozilla(GrGLe + if (name == LOCAL_GL_VERSION) { + if (sGLContext.get()->IsGLES2()) { + return reinterpret_cast("OpenGL ES 2.0"); + } else { + return reinterpret_cast("2.0"); + } + } else if (name == LOCAL_GL_EXTENSIONS) { + // Only expose the bare minimum extensions we want to support to ensure a functional Ganesh + // as GLContext only exposes certain extensions + static bool extensionsStringBuilt = false; +- static char extensionsString[120]; ++ static char extensionsString[256]; + + if (!extensionsStringBuilt) { + if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_texture_format_BGRA8888)) { + strcpy(extensionsString, "GL_EXT_texture_format_BGRA8888 "); + } + + if (sGLContext.get()->IsExtensionSupported(GLContext::OES_packed_depth_stencil)) { + strcat(extensionsString, "GL_OES_packed_depth_stencil "); + } + + if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_packed_depth_stencil)) { + strcat(extensionsString, "GL_EXT_packed_depth_stencil "); + } + + if (sGLContext.get()->IsExtensionSupported(GLContext::OES_rgb8_rgba8)) { + strcat(extensionsString, "GL_OES_rgb8_rgba8 "); + } + ++ if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_bgra)) { ++ strcat(extensionsString, "GL_EXT_bgra "); ++ } ++ ++ if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_read_format_bgra)) { ++ strcat(extensionsString, "GL_EXT_read_format_bgra "); ++ } ++ + extensionsStringBuilt = true; + } + + return reinterpret_cast(extensionsString); + + } else if (name == LOCAL_GL_SHADING_LANGUAGE_VERSION) { + if (sGLContext.get()->IsGLES2()) { + return reinterpret_cast("OpenGL ES GLSL ES 1.0"); + } else { + return reinterpret_cast("1.10"); +diff --git a/gfx/skia/src/gpu/gl/GrGpuGL.cpp b/gfx/skia/src/gpu/gl/GrGpuGL.cpp +--- a/gfx/skia/src/gpu/gl/GrGpuGL.cpp ++++ b/gfx/skia/src/gpu/gl/GrGpuGL.cpp +@@ -1,18 +1,18 @@ + /* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +- ++#include + #include "GrGpuGL.h" + #include "GrGLStencilBuffer.h" + #include "GrGLPath.h" + #include "GrGLShaderBuilder.h" + #include "GrTemplates.h" + #include "GrTypes.h" + #include "SkTemplates.h" + + static const GrGLuint GR_MAX_GLUINT = ~0U; + static const GrGLint GR_INVAL_GLINT = ~0; +@@ -1381,29 +1381,67 @@ bool GrGpuGL::readPixelsWillPayForYFlip( + // Note the rowBytes might be tight to the passed in data, but if data + // gets clipped in x to the target the rowBytes will no longer be tight. + if (left >= 0 && (left + width) < renderTarget->width()) { + return 0 == rowBytes || + GrBytesPerPixel(config) * width == rowBytes; + } else { + return false; + } + } + ++static void swizzleRow(void* buffer, int byteLen) { ++ uint8_t* src = (uint8_t*)buffer; ++ uint8_t* end = src + byteLen; ++ ++ GrAssert((end - src) % 4 == 0); ++ ++ for (; src != end; src += 4) { ++ std::swap(src[0], src[2]); ++ } ++} ++ ++bool GrGpuGL::canReadBGRA() const ++{ ++ if (kDesktop_GrGLBinding == this->glBinding() || ++ this->hasExtension("GL_EXT_bgra")) ++ return true; ++ ++ if (this->hasExtension("GL_EXT_read_format_bgra")) { ++ GrGLint readFormat = 0; ++ GrGLint readType = 0; ++ ++ GL_CALL(GetIntegerv(GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat)); ++ GL_CALL(GetIntegerv(GR_GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType)); ++ ++ return readFormat == GR_GL_BGRA && readType == GR_GL_UNSIGNED_BYTE; ++ } ++ ++ return false; ++} ++ + bool GrGpuGL::onReadPixels(GrRenderTarget* target, + int left, int top, + int width, int height, + GrPixelConfig config, + void* buffer, + size_t rowBytes) { + GrGLenum format; + GrGLenum type; + bool flipY = kBottomLeft_GrSurfaceOrigin == target->origin(); ++ bool needSwizzle = false; ++ ++ if (kBGRA_8888_GrPixelConfig == config && !this->canReadBGRA()) { ++ // Read RGBA and swizzle after ++ config = kRGBA_8888_GrPixelConfig; ++ needSwizzle = true; ++ } ++ + if (!this->configToGLFormats(config, false, NULL, &format, &type)) { + return false; + } + size_t bpp = GrBytesPerPixel(config); + if (!adjust_pixel_ops_params(target->width(), target->height(), bpp, + &left, &top, &width, &height, + const_cast(&buffer), + &rowBytes)) { + return false; + } +@@ -1478,35 +1516,46 @@ bool GrGpuGL::onReadPixels(GrRenderTarge + scratch.reset(tightRowBytes); + void* tmpRow = scratch.get(); + // flip y in-place by rows + const int halfY = height >> 1; + char* top = reinterpret_cast(buffer); + char* bottom = top + (height - 1) * rowBytes; + for (int y = 0; y < halfY; y++) { + memcpy(tmpRow, top, tightRowBytes); + memcpy(top, bottom, tightRowBytes); + memcpy(bottom, tmpRow, tightRowBytes); ++ ++ if (needSwizzle) { ++ swizzleRow(top, tightRowBytes); ++ swizzleRow(bottom, tightRowBytes); ++ } ++ + top += rowBytes; + bottom -= rowBytes; + } + } + } else { +- GrAssert(readDst != buffer); GrAssert(rowBytes != tightRowBytes); ++ GrAssert(readDst != buffer); ++ GrAssert(rowBytes != tightRowBytes); + // copy from readDst to buffer while flipping y + // const int halfY = height >> 1; + const char* src = reinterpret_cast(readDst); + char* dst = reinterpret_cast(buffer); + if (flipY) { + dst += (height-1) * rowBytes; + } + for (int y = 0; y < height; y++) { + memcpy(dst, src, tightRowBytes); ++ if (needSwizzle) { ++ swizzleRow(dst, tightRowBytes); ++ } ++ + src += readDstRowBytes; + if (!flipY) { + dst += rowBytes; + } else { + dst -= rowBytes; + } + } + } + return true; + } +diff --git a/gfx/skia/src/gpu/gl/GrGpuGL.h b/gfx/skia/src/gpu/gl/GrGpuGL.h +--- a/gfx/skia/src/gpu/gl/GrGpuGL.h ++++ b/gfx/skia/src/gpu/gl/GrGpuGL.h +@@ -243,20 +243,22 @@ private: + GrPixelConfig dataConfig, + const void* data, + size_t rowBytes); + + bool createRenderTargetObjects(int width, int height, + GrGLuint texID, + GrGLRenderTarget::Desc* desc); + + void fillInConfigRenderableTable(); + ++ bool canReadBGRA() const; ++ + GrGLContext fGLContext; + + // GL program-related state + ProgramCache* fProgramCache; + SkAutoTUnref fCurrentProgram; + + /////////////////////////////////////////////////////////////////////////// + ///@name Caching of GL State + ///@{ + int fHWActiveTextureUnitIdx; diff --git a/gfx/skia/patches/archive/0025-Bug-896049-Add-default-Value-SK_OVERRIDE.patch b/gfx/skia/patches/archive/0025-Bug-896049-Add-default-Value-SK_OVERRIDE.patch new file mode 100644 index 0000000000..aff99f75f1 --- /dev/null +++ b/gfx/skia/patches/archive/0025-Bug-896049-Add-default-Value-SK_OVERRIDE.patch @@ -0,0 +1,26 @@ +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -325,19 +325,19 @@ + // Some documentation suggests we should be using __attribute__((override)), + // but it doesn't work. + #define SK_OVERRIDE override + #elif defined(__has_extension) + #if __has_extension(cxx_override_control) + #define SK_OVERRIDE override + #endif + #endif +- #else +- // Linux GCC ignores "__attribute__((override))" and rejects "override". +- #define SK_OVERRIDE ++ #endif ++ #ifndef SK_OVERRIDE ++ #define SK_OVERRIDE + #endif + #endif + + ////////////////////////////////////////////////////////////////////// + + #ifndef SK_PRINTF_LIKE + #if defined(__clang__) || defined(__GNUC__) + #define SK_PRINTF_LIKE(A, B) __attribute__((format(printf, (A), (B)))) diff --git a/gfx/skia/patches/archive/0026-Bug-901208-Fix-ARM-v4t.patch b/gfx/skia/patches/archive/0026-Bug-901208-Fix-ARM-v4t.patch new file mode 100644 index 0000000000..5c95b54014 --- /dev/null +++ b/gfx/skia/patches/archive/0026-Bug-901208-Fix-ARM-v4t.patch @@ -0,0 +1,83 @@ +diff --git a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +--- a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp ++++ b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +@@ -26,66 +26,78 @@ static void S32A_D565_Opaque(uint16_t* S + asm volatile ( + "1: \n\t" + "ldr r3, [%[src]], #4 \n\t" + "cmp r3, #0xff000000 \n\t" + "blo 2f \n\t" + "and r4, r3, #0x0000f8 \n\t" + "and r5, r3, #0x00fc00 \n\t" + "and r6, r3, #0xf80000 \n\t" ++#ifdef SK_ARM_HAS_EDSP + "pld [r1, #32] \n\t" ++#endif + "lsl r3, r4, #8 \n\t" + "orr r3, r3, r5, lsr #5 \n\t" + "orr r3, r3, r6, lsr #19 \n\t" + "subs %[count], %[count], #1 \n\t" + "strh r3, [%[dst]], #2 \n\t" + "bne 1b \n\t" + "b 4f \n\t" + "2: \n\t" + "lsrs r7, r3, #24 \n\t" + "beq 3f \n\t" + "ldrh r4, [%[dst]] \n\t" + "rsb r7, r7, #255 \n\t" + "and r6, r4, #0x001f \n\t" +-#if SK_ARM_ARCH == 6 ++#if SK_ARM_ARCH <= 6 + "lsl r5, r4, #21 \n\t" + "lsr r5, r5, #26 \n\t" + #else + "ubfx r5, r4, #5, #6 \n\t" + #endif ++#ifdef SK_ARM_HAS_EDSP + "pld [r0, #16] \n\t" ++#endif + "lsr r4, r4, #11 \n\t" + #ifdef SK_ARM_HAS_EDSP + "smulbb r6, r6, r7 \n\t" + "smulbb r5, r5, r7 \n\t" + "smulbb r4, r4, r7 \n\t" + #else + "mul r6, r6, r7 \n\t" + "mul r5, r5, r7 \n\t" + "mul r4, r4, r7 \n\t" + #endif ++#if SK_ARM_ARCH >= 6 + "uxtb r7, r3, ROR #16 \n\t" + "uxtb ip, r3, ROR #8 \n\t" ++#else ++ "mov ip, #0xff \n\t" ++ "and r7, ip, r3, ROR #16 \n\t" ++ "and ip, ip, r3, ROR #8 \n\t" ++#endif + "and r3, r3, #0xff \n\t" + "add r6, r6, #16 \n\t" + "add r5, r5, #32 \n\t" + "add r4, r4, #16 \n\t" + "add r6, r6, r6, lsr #5 \n\t" + "add r5, r5, r5, lsr #6 \n\t" + "add r4, r4, r4, lsr #5 \n\t" + "add r6, r7, r6, lsr #5 \n\t" + "add r5, ip, r5, lsr #6 \n\t" + "add r4, r3, r4, lsr #5 \n\t" + "lsr r6, r6, #3 \n\t" + "and r5, r5, #0xfc \n\t" + "and r4, r4, #0xf8 \n\t" + "orr r6, r6, r5, lsl #3 \n\t" + "orr r4, r6, r4, lsl #8 \n\t" + "strh r4, [%[dst]], #2 \n\t" ++#ifdef SK_ARM_HAS_EDSP + "pld [r1, #32] \n\t" ++#endif + "subs %[count], %[count], #1 \n\t" + "bne 1b \n\t" + "b 4f \n\t" + "3: \n\t" + "subs %[count], %[count], #1 \n\t" + "add %[dst], %[dst], #2 \n\t" + "bne 1b \n\t" + "4: \n\t" diff --git a/gfx/skia/patches/archive/0030-Bug-939629-Add-missing-include-guards.patch b/gfx/skia/patches/archive/0030-Bug-939629-Add-missing-include-guards.patch new file mode 100644 index 0000000000..c92bf2aaeb --- /dev/null +++ b/gfx/skia/patches/archive/0030-Bug-939629-Add-missing-include-guards.patch @@ -0,0 +1,94 @@ +# HG changeset patch +# Parent 979e60d9c09f22eb139643da6de7568b603e1aa1 + +diff --git a/gfx/skia/include/images/SkImages.h b/gfx/skia/include/images/SkImages.h +--- a/gfx/skia/include/images/SkImages.h ++++ b/gfx/skia/include/images/SkImages.h +@@ -1,14 +1,19 @@ + /* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + ++#ifndef SkImages_DEFINED ++#define SkImages_DEFINED ++ + class SkImages { + public: + /** + * Initializes flattenables in the images project. + */ + static void InitializeFlattenables(); + }; ++ ++#endif +diff --git a/gfx/skia/src/gpu/GrAAConvexPathRenderer.h b/gfx/skia/src/gpu/GrAAConvexPathRenderer.h +--- a/gfx/skia/src/gpu/GrAAConvexPathRenderer.h ++++ b/gfx/skia/src/gpu/GrAAConvexPathRenderer.h +@@ -1,16 +1,19 @@ + + /* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + ++#ifndef GrAAConvexPathRenderer_DEFINED ++#define GrAAConvexPathRenderer_DEFINED ++ + #include "GrPathRenderer.h" + + + class GrAAConvexPathRenderer : public GrPathRenderer { + public: + GrAAConvexPathRenderer(); + + virtual bool canDrawPath(const SkPath& path, +@@ -19,8 +22,10 @@ public: + bool antiAlias) const SK_OVERRIDE; + + protected: + virtual bool onDrawPath(const SkPath& path, + const SkStrokeRec& stroke, + GrDrawTarget* target, + bool antiAlias) SK_OVERRIDE; + }; ++ ++#endif +diff --git a/gfx/skia/src/gpu/GrReducedClip.h b/gfx/skia/src/gpu/GrReducedClip.h +--- a/gfx/skia/src/gpu/GrReducedClip.h ++++ b/gfx/skia/src/gpu/GrReducedClip.h +@@ -1,16 +1,19 @@ + + /* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + ++#ifndef GrReducedClip_DEFINED ++#define GrReducedClip_DEFINED ++ + #include "SkClipStack.h" + #include "SkTLList.h" + + namespace GrReducedClip { + + typedef SkTLList ElementList; + + enum InitialState { +@@ -33,8 +36,10 @@ enum InitialState { + void ReduceClipStack(const SkClipStack& stack, + const SkIRect& queryBounds, + ElementList* result, + InitialState* initialState, + SkIRect* tighterBounds = NULL, + bool* requiresAA = NULL); + + } // namespace GrReducedClip ++ ++#endif diff --git a/gfx/skia/patches/archive/0031-Bug-945588-Add-include-guard.patch b/gfx/skia/patches/archive/0031-Bug-945588-Add-include-guard.patch new file mode 100644 index 0000000000..f58e7e1659 --- /dev/null +++ b/gfx/skia/patches/archive/0031-Bug-945588-Add-include-guard.patch @@ -0,0 +1,39 @@ +# HG changeset patch +# User Ehsan Akhgari + +Bug 945588 - Add include guards to SkConfig8888.h + +diff --git a/gfx/skia/src/core/SkConfig8888.h b/gfx/skia/src/core/SkConfig8888.h +index 96eaef2..36bc9b4 100644 +--- a/gfx/skia/src/core/SkConfig8888.h ++++ b/gfx/skia/src/core/SkConfig8888.h +@@ -1,16 +1,18 @@ + + /* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + ++#ifndef SkConfig8888_DEFINED ++#define SkConfig8888_DEFINED + + #include "SkCanvas.h" + #include "SkColorPriv.h" + + /** + * Converts pixels from one Config8888 to another Config8888 + */ + void SkConvertConfig8888Pixels(uint32_t* dstPixels, +@@ -69,8 +71,10 @@ static inline void SkCopyConfig8888ToBitmap(const SkBitmap& dstBmp, + int h = dstBmp.height(); + size_t dstRowBytes = dstBmp.rowBytes(); + uint32_t* dstPixels = reinterpret_cast(dstBmp.getPixels()); + + SkConvertConfig8888Pixels(dstPixels, dstRowBytes, SkCanvas::kNative_Premul_Config8888, srcPixels, srcRowBytes, srcConfig8888, w, h); + } + + } ++ ++#endif diff --git a/gfx/skia/patches/archive/0032-Bug-974900-More-missing-include-guards.patch b/gfx/skia/patches/archive/0032-Bug-974900-More-missing-include-guards.patch new file mode 100644 index 0000000000..b6b8461213 --- /dev/null +++ b/gfx/skia/patches/archive/0032-Bug-974900-More-missing-include-guards.patch @@ -0,0 +1,148 @@ +# HG changeset patch +# Parent c8288d0c7a1544a590a0cac9c39397ac10c8a45b +Bug 974900 - Add missing include guards to Skia headers - r=gw280 + +diff --git a/gfx/skia/trunk/include/images/SkImages.h b/gfx/skia/trunk/include/images/SkImages.h +--- a/gfx/skia/trunk/include/images/SkImages.h ++++ b/gfx/skia/trunk/include/images/SkImages.h +@@ -1,14 +1,19 @@ + /* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + ++#ifndef SkImages_DEFINED ++#define SkImages_DEFINED ++ + class SkImages { + public: + /** + * Initializes flattenables in the images project. + */ + static void InitializeFlattenables(); + }; ++ ++#endif +diff --git a/gfx/skia/trunk/src/core/SkConvolver.h b/gfx/skia/trunk/src/core/SkConvolver.h +--- a/gfx/skia/trunk/src/core/SkConvolver.h ++++ b/gfx/skia/trunk/src/core/SkConvolver.h +@@ -8,16 +8,18 @@ + #include "SkSize.h" + #include "SkTypes.h" + #include "SkTArray.h" + + // avoid confusion with Mac OS X's math library (Carbon) + #if defined(__APPLE__) + #undef FloatToConvolutionFixed + #undef ConvolutionFixedToFloat ++#undef FloatToFixed ++#undef FixedToFloat + #endif + + // Represents a filter in one dimension. Each output pixel has one entry in this + // object for the filter values contributing to it. You build up the filter + // list by calling AddFilter for each output pixel (in order). + // + // We do 2-dimensional convolution by first convolving each row by one + // SkConvolutionFilter1D, then convolving each column by another one. +diff --git a/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h b/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h +--- a/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h ++++ b/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h +@@ -3,24 +3,28 @@ + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + #include "GrPathRenderer.h" + ++#ifndef GrAAConvexPathRenderer_DEFINED ++#define GrAAConvexPathRenderer_DEFINED + + class GrAAConvexPathRenderer : public GrPathRenderer { + public: + GrAAConvexPathRenderer(); + + virtual bool canDrawPath(const SkPath& path, + const SkStrokeRec& stroke, + const GrDrawTarget* target, + bool antiAlias) const SK_OVERRIDE; + + protected: + virtual bool onDrawPath(const SkPath& path, + const SkStrokeRec& stroke, + GrDrawTarget* target, + bool antiAlias) SK_OVERRIDE; + }; ++ ++#endif +diff --git a/gfx/skia/trunk/src/gpu/GrReducedClip.h b/gfx/skia/trunk/src/gpu/GrReducedClip.h +--- a/gfx/skia/trunk/src/gpu/GrReducedClip.h ++++ b/gfx/skia/trunk/src/gpu/GrReducedClip.h +@@ -1,16 +1,19 @@ + + /* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + ++#ifndef GrReducedClip_DEFINED ++#define GrReducedClip_DEFINED ++ + #include "SkClipStack.h" + #include "SkTLList.h" + + namespace GrReducedClip { + + typedef SkTLList ElementList; + + enum InitialState { +@@ -36,8 +39,10 @@ SK_API void ReduceClipStack(const SkClip + const SkIRect& queryBounds, + ElementList* result, + int32_t* resultGenID, + InitialState* initialState, + SkIRect* tighterBounds = NULL, + bool* requiresAA = NULL); + + } // namespace GrReducedClip ++ ++#endif +diff --git a/gfx/skia/trunk/src/pathops/SkLineParameters.h b/gfx/skia/trunk/src/pathops/SkLineParameters.h +--- a/gfx/skia/trunk/src/pathops/SkLineParameters.h ++++ b/gfx/skia/trunk/src/pathops/SkLineParameters.h +@@ -1,14 +1,18 @@ + /* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ ++ ++#ifndef SkLineParameters_DEFINED ++#define SkLineParameters_DEFINED ++ + #include "SkPathOpsCubic.h" + #include "SkPathOpsLine.h" + #include "SkPathOpsQuad.h" + + // Sources + // computer-aided design - volume 22 number 9 november 1990 pp 538 - 549 + // online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf + +@@ -164,8 +168,10 @@ public: + return -a; + } + + private: + double a; + double b; + double c; + }; ++ ++#endif diff --git a/gfx/skia/patches/archive/0033-Bug-974900-undef-interface-windows.patch b/gfx/skia/patches/archive/0033-Bug-974900-undef-interface-windows.patch new file mode 100644 index 0000000000..05f17000a0 --- /dev/null +++ b/gfx/skia/patches/archive/0033-Bug-974900-undef-interface-windows.patch @@ -0,0 +1,27 @@ +# HG changeset patch +# Parent b12f9a408740aa5fd93c296a7d41e1b5f54c1b20 +Bug 974900 - #undef interface defined by windows headers - r=gw280 + +diff --git a/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h b/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h +--- a/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h ++++ b/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h +@@ -9,16 +9,19 @@ + #ifndef GrGLCaps_DEFINED + #define GrGLCaps_DEFINED + + #include "GrDrawTargetCaps.h" + #include "GrGLStencilBuffer.h" + #include "SkTArray.h" + #include "SkTDArray.h" + ++// defined in Windows headers ++#undef interface ++ + class GrGLContextInfo; + + /** + * Stores some capabilities of a GL context. Most are determined by the GL + * version and the extensions string. It also tracks formats that have passed + * the FBO completeness test. + */ + class GrGLCaps : public GrDrawTargetCaps { diff --git a/gfx/skia/patches/archive/SkPostConfig.patch b/gfx/skia/patches/archive/SkPostConfig.patch new file mode 100644 index 0000000000..d32341f4ea --- /dev/null +++ b/gfx/skia/patches/archive/SkPostConfig.patch @@ -0,0 +1,32 @@ +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -277,19 +277,28 @@ + #endif + + ////////////////////////////////////////////////////////////////////// + + #ifndef SK_OVERRIDE + #if defined(_MSC_VER) + #define SK_OVERRIDE override + #elif defined(__clang__) ++#if __has_feature(cxx_override_control) + // Some documentation suggests we should be using __attribute__((override)), + // but it doesn't work. + #define SK_OVERRIDE override ++#elif defined(__has_extension) ++#if __has_extension(cxx_override_control) ++#define SK_OVERRIDE override ++#endif ++#endif ++#ifndef SK_OVERRIDE ++#define SK_OVERRIDE ++#endif + #else + // Linux GCC ignores "__attribute__((override))" and rejects "override". + #define SK_OVERRIDE + #endif + #endif + + ////////////////////////////////////////////////////////////////////// + diff --git a/gfx/skia/patches/archive/arm-fixes.patch b/gfx/skia/patches/archive/arm-fixes.patch new file mode 100644 index 0000000000..d9fa430df0 --- /dev/null +++ b/gfx/skia/patches/archive/arm-fixes.patch @@ -0,0 +1,191 @@ +diff --git a/gfx/skia/include/core/SkMath.h b/gfx/skia/include/core/SkMath.h +--- a/gfx/skia/include/core/SkMath.h ++++ b/gfx/skia/include/core/SkMath.h +@@ -148,20 +148,17 @@ static inline bool SkIsPow2(int value) { + } + + /////////////////////////////////////////////////////////////////////////////// + + /** SkMulS16(a, b) multiplies a * b, but requires that a and b are both int16_t. + With this requirement, we can generate faster instructions on some + architectures. + */ +-#if defined(__arm__) \ +- && !defined(__thumb__) \ +- && !defined(__ARM_ARCH_4T__) \ +- && !defined(__ARM_ARCH_5T__) ++#ifdef SK_ARM_HAS_EDSP + static inline int32_t SkMulS16(S16CPU x, S16CPU y) { + SkASSERT((int16_t)x == x); + SkASSERT((int16_t)y == y); + int32_t product; + asm("smulbb %0, %1, %2 \n" + : "=r"(product) + : "r"(x), "r"(y) + ); +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -300,8 +300,53 @@ + #endif + #endif + + ////////////////////////////////////////////////////////////////////// + + #ifndef SK_ALLOW_STATIC_GLOBAL_INITIALIZERS + #define SK_ALLOW_STATIC_GLOBAL_INITIALIZERS 1 + #endif ++ ++////////////////////////////////////////////////////////////////////// ++// ARM defines ++ ++#if defined(__GNUC__) && defined(__arm__) ++ ++# define SK_ARM_ARCH 3 ++ ++# if defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) \ ++ || defined(_ARM_ARCH_4) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 4 ++# endif ++ ++# if defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) \ ++ || defined(__ARM_ARCH_5E__) || defined(__ARM_ARCH_5TE__) \ ++ || defined(__ARM_ARCH_5TEJ__) || defined(_ARM_ARCH_5) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 5 ++# endif ++ ++# if defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ ++ || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ ++ || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \ ++ || defined(__ARM_ARCH_6M__) || defined(_ARM_ARCH_6) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 6 ++# endif ++ ++# if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ ++ || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ ++ || defined(__ARM_ARCH_7EM__) || defined(_ARM_ARCH_7) ++# undef SK_ARM_ARCH ++# define SK_ARM_ARCH 7 ++# endif ++ ++# undef SK_ARM_HAS_EDSP ++# if defined(__thumb2__) && (SK_ARM_ARCH >= 6) \ ++ || !defined(__thumb__) \ ++ && ((SK_ARM_ARCH > 5) || defined(__ARM_ARCH_5E__) \ ++ || defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__)) ++# define SK_ARM_HAS_EDSP 1 ++# endif ++ ++#endif +diff --git a/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp b/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp +--- a/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp ++++ b/gfx/skia/src/opts/SkBitmapProcState_opts_arm.cpp +@@ -6,17 +6,17 @@ + * found in the LICENSE file. + */ + + + #include "SkBitmapProcState.h" + #include "SkColorPriv.h" + #include "SkUtils.h" + +-#if __ARM_ARCH__ >= 6 && !defined(SK_CPU_BENDIAN) ++#if SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN) + void SI8_D16_nofilter_DX_arm( + const SkBitmapProcState& s, + const uint32_t* SK_RESTRICT xy, + int count, + uint16_t* SK_RESTRICT colors) __attribute__((optimize("O1"))); + + void SI8_D16_nofilter_DX_arm(const SkBitmapProcState& s, + const uint32_t* SK_RESTRICT xy, +@@ -177,17 +177,17 @@ void SI8_opaque_D32_nofilter_DX_arm(cons + : [xx] "+r" (xx), [count] "+r" (count), [colors] "+r" (colors) + : [table] "r" (table), [srcAddr] "r" (srcAddr) + : "memory", "cc", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11" + ); + } + + s.fBitmap->getColorTable()->unlockColors(false); + } +-#endif //__ARM_ARCH__ >= 6 && !defined(SK_CPU_BENDIAN) ++#endif // SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN) + + /////////////////////////////////////////////////////////////////////////////// + + /* If we replace a sampleproc, then we null-out the associated shaderproc, + otherwise the shader won't even look at the matrix/sampler + */ + void SkBitmapProcState::platformProcs() { + bool doFilter = fDoFilter; +@@ -195,17 +195,17 @@ void SkBitmapProcState::platformProcs() + bool justDx = false; + + if (fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) { + justDx = true; + } + + switch (fBitmap->config()) { + case SkBitmap::kIndex8_Config: +-#if __ARM_ARCH__ >= 6 && !defined(SK_CPU_BENDIAN) ++#if SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN) + if (justDx && !doFilter) { + #if 0 /* crashing on android device */ + fSampleProc16 = SI8_D16_nofilter_DX_arm; + fShaderProc16 = NULL; + #endif + if (isOpaque) { + // this one is only very slighty faster than the C version + fSampleProc32 = SI8_opaque_D32_nofilter_DX_arm; +diff --git a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +--- a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp ++++ b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +@@ -669,18 +669,23 @@ static void __attribute((noinline,optimi + /* Double Loop */ + "1: \n\t" /* */ + "ldm %[src]!, {r5, r6} \n\t" /* loading src pointers into r5 and r6 */ + "ldm %[dst], {r7, r8} \n\t" /* loading dst pointers into r7 and r8 */ + + /* dst1_scale and dst2_scale*/ + "lsr r9, r5, #24 \n\t" /* src >> 24 */ + "lsr r10, r6, #24 \n\t" /* src >> 24 */ ++#ifdef SK_ARM_HAS_EDSP + "smulbb r9, r9, %[alpha] \n\t" /* r9 = SkMulS16 r9 with src_scale */ + "smulbb r10, r10, %[alpha] \n\t" /* r10 = SkMulS16 r10 with src_scale */ ++#else ++ "mul r9, r9, %[alpha] \n\t" /* r9 = SkMulS16 r9 with src_scale */ ++ "mul r10, r10, %[alpha] \n\t" /* r10 = SkMulS16 r10 with src_scale */ ++#endif + "lsr r9, r9, #8 \n\t" /* r9 >> 8 */ + "lsr r10, r10, #8 \n\t" /* r10 >> 8 */ + "rsb r9, r9, #256 \n\t" /* dst1_scale = r9 = 255 - r9 + 1 */ + "rsb r10, r10, #256 \n\t" /* dst2_scale = r10 = 255 - r10 + 1 */ + + /* ---------------------- */ + + /* src1, src1_scale */ +@@ -739,17 +744,21 @@ static void __attribute((noinline,optimi + /* else get into the single loop */ + /* Single Loop */ + "2: \n\t" /* */ + "ldr r5, [%[src]], #4 \n\t" /* loading src pointer into r5: r5=src */ + "ldr r7, [%[dst]] \n\t" /* loading dst pointer into r7: r7=dst */ + + "lsr r6, r5, #24 \n\t" /* src >> 24 */ + "and r8, r12, r5, lsr #8 \n\t" /* ag = r8 = r5 masked by r12 lsr by #8 */ ++#ifdef SK_ARM_HAS_EDSP + "smulbb r6, r6, %[alpha] \n\t" /* r6 = SkMulS16 with src_scale */ ++#else ++ "mul r6, r6, %[alpha] \n\t" /* r6 = SkMulS16 with src_scale */ ++#endif + "and r9, r12, r5 \n\t" /* rb = r9 = r5 masked by r12 */ + "lsr r6, r6, #8 \n\t" /* r6 >> 8 */ + "mul r8, r8, %[alpha] \n\t" /* ag = r8 times scale */ + "rsb r6, r6, #256 \n\t" /* r6 = 255 - r6 + 1 */ + + /* src, src_scale */ + "mul r9, r9, %[alpha] \n\t" /* rb = r9 times scale */ + "and r8, r8, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */ diff --git a/gfx/skia/patches/archive/arm-opts.patch b/gfx/skia/patches/archive/arm-opts.patch new file mode 100644 index 0000000000..02ad85c9a7 --- /dev/null +++ b/gfx/skia/patches/archive/arm-opts.patch @@ -0,0 +1,41 @@ +diff --git a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +--- a/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp ++++ b/gfx/skia/src/opts/SkBlitRow_opts_arm.cpp +@@ -549,17 +549,17 @@ static void S32A_Opaque_BlitRow32_neon(S + #define S32A_Opaque_BlitRow32_PROC S32A_Opaque_BlitRow32_neon + + #else + + #ifdef TEST_SRC_ALPHA + #error The ARM asm version of S32A_Opaque_BlitRow32 does not support TEST_SRC_ALPHA + #endif + +-static void S32A_Opaque_BlitRow32_arm(SkPMColor* SK_RESTRICT dst, ++static void __attribute((noinline,optimize("-fomit-frame-pointer"))) S32A_Opaque_BlitRow32_arm(SkPMColor* SK_RESTRICT dst, + const SkPMColor* SK_RESTRICT src, + int count, U8CPU alpha) { + + SkASSERT(255 == alpha); + + /* Does not support the TEST_SRC_ALPHA case */ + asm volatile ( + "cmp %[count], #0 \n\t" /* comparing count with 0 */ +@@ -646,17 +646,17 @@ static void S32A_Opaque_BlitRow32_arm(Sk + ); + } + #define S32A_Opaque_BlitRow32_PROC S32A_Opaque_BlitRow32_arm + #endif + + /* + * ARM asm version of S32A_Blend_BlitRow32 + */ +-static void S32A_Blend_BlitRow32_arm(SkPMColor* SK_RESTRICT dst, ++static void __attribute((noinline,optimize("-fomit-frame-pointer"))) S32A_Blend_BlitRow32_arm(SkPMColor* SK_RESTRICT dst, + const SkPMColor* SK_RESTRICT src, + int count, U8CPU alpha) { + asm volatile ( + "cmp %[count], #0 \n\t" /* comparing count with 0 */ + "beq 3f \n\t" /* if zero exit */ + + "mov r12, #0xff \n\t" /* load the 0xff mask in r12 */ + "orr r12, r12, r12, lsl #16 \n\t" /* convert it to 0xff00ff in r12 */ diff --git a/gfx/skia/patches/archive/fix-comma-end-enum-list.patch b/gfx/skia/patches/archive/fix-comma-end-enum-list.patch new file mode 100644 index 0000000000..dea36377e8 --- /dev/null +++ b/gfx/skia/patches/archive/fix-comma-end-enum-list.patch @@ -0,0 +1,380 @@ +diff --git a/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h b/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h +--- a/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h ++++ b/gfx/skia/include/core/SkAdvancedTypefaceMetrics.h +@@ -29,17 +29,17 @@ public: + SkString fFontName; + + enum FontType { + kType1_Font, + kType1CID_Font, + kCFF_Font, + kTrueType_Font, + kOther_Font, +- kNotEmbeddable_Font, ++ kNotEmbeddable_Font + }; + // The type of the underlying font program. This field determines which + // of the following fields are valid. If it is kOther_Font or + // kNotEmbeddable_Font, the per glyph information will never be populated. + FontType fType; + + // fMultiMaster may be true for Type1_Font or CFF_Font. + bool fMultiMaster; +@@ -51,17 +51,17 @@ public: + kFixedPitch_Style = 0x00001, + kSerif_Style = 0x00002, + kSymbolic_Style = 0x00004, + kScript_Style = 0x00008, + kNonsymbolic_Style = 0x00020, + kItalic_Style = 0x00040, + kAllCaps_Style = 0x10000, + kSmallCaps_Style = 0x20000, +- kForceBold_Style = 0x40000, ++ kForceBold_Style = 0x40000 + }; + uint16_t fStyle; // Font style characteristics. + int16_t fItalicAngle; // Counterclockwise degrees from vertical of the + // dominant vertical stroke for an Italic face. + // The following fields are all in font units. + int16_t fAscent; // Max height above baseline, not including accents. + int16_t fDescent; // Max depth below baseline (negative). + int16_t fStemV; // Thickness of dominant vertical stem. +@@ -70,26 +70,26 @@ public: + SkIRect fBBox; // The bounding box of all glyphs (in font units). + + // The type of advance data wanted. + enum PerGlyphInfo { + kNo_PerGlyphInfo = 0x0, // Don't populate any per glyph info. + kHAdvance_PerGlyphInfo = 0x1, // Populate horizontal advance data. + kVAdvance_PerGlyphInfo = 0x2, // Populate vertical advance data. + kGlyphNames_PerGlyphInfo = 0x4, // Populate glyph names (Type 1 only). +- kToUnicode_PerGlyphInfo = 0x8, // Populate ToUnicode table, ignored ++ kToUnicode_PerGlyphInfo = 0x8 // Populate ToUnicode table, ignored + // for Type 1 fonts + }; + + template + struct AdvanceMetric { + enum MetricType { + kDefault, // Default advance: fAdvance.count = 1 + kRange, // Advances for a range: fAdvance.count = fEndID-fStartID +- kRun, // fStartID-fEndID have same advance: fAdvance.count = 1 ++ kRun // fStartID-fEndID have same advance: fAdvance.count = 1 + }; + MetricType fType; + uint16_t fStartId; + uint16_t fEndId; + SkTDArray fAdvance; + SkTScopedPtr > fNext; + }; + +diff --git a/gfx/skia/include/core/SkBlitRow.h b/gfx/skia/include/core/SkBlitRow.h +--- a/gfx/skia/include/core/SkBlitRow.h ++++ b/gfx/skia/include/core/SkBlitRow.h +@@ -44,17 +44,17 @@ public: + + //! Public entry-point to return a blit function ptr + static Proc Factory(unsigned flags, SkBitmap::Config); + + ///////////// D32 version + + enum Flags32 { + kGlobalAlpha_Flag32 = 1 << 0, +- kSrcPixelAlpha_Flag32 = 1 << 1, ++ kSrcPixelAlpha_Flag32 = 1 << 1 + }; + + /** Function pointer that blends 32bit colors onto a 32bit destination. + @param dst array of dst 32bit colors + @param src array of src 32bit colors (w/ or w/o alpha) + @param count number of colors to blend + @param alpha global alpha to be applied to all src colors + */ +diff --git a/gfx/skia/include/core/SkCanvas.h b/gfx/skia/include/core/SkCanvas.h +--- a/gfx/skia/include/core/SkCanvas.h ++++ b/gfx/skia/include/core/SkCanvas.h +@@ -132,17 +132,17 @@ public: + * low byte to high byte: B, G, R, A. + */ + kBGRA_Premul_Config8888, + kBGRA_Unpremul_Config8888, + /** + * low byte to high byte: R, G, B, A. + */ + kRGBA_Premul_Config8888, +- kRGBA_Unpremul_Config8888, ++ kRGBA_Unpremul_Config8888 + }; + + /** + * On success (returns true), copy the canvas pixels into the bitmap. + * On failure, the bitmap parameter is left unchanged and false is + * returned. + * + * The canvas' pixels are converted to the bitmap's config. The only +diff --git a/gfx/skia/include/core/SkDevice.h b/gfx/skia/include/core/SkDevice.h +--- a/gfx/skia/include/core/SkDevice.h ++++ b/gfx/skia/include/core/SkDevice.h +@@ -134,17 +134,17 @@ public: + * Return the device's origin: its offset in device coordinates from + * the default origin in its canvas' matrix/clip + */ + const SkIPoint& getOrigin() const { return fOrigin; } + + protected: + enum Usage { + kGeneral_Usage, +- kSaveLayer_Usage, // + */ + enum Hinting { + kNo_Hinting = 0, + kSlight_Hinting = 1, + kNormal_Hinting = 2, //!< this is the default +- kFull_Hinting = 3, ++ kFull_Hinting = 3 + }; + + Hinting getHinting() const { + return static_cast(fHinting); + } + + void setHinting(Hinting hintingLevel); + +@@ -282,17 +282,17 @@ public: + results may not appear the same as if it was drawn twice, filled and + then stroked. + */ + enum Style { + kFill_Style, //!< fill the geometry + kStroke_Style, //!< stroke the geometry + kStrokeAndFill_Style, //!< fill and stroke the geometry + +- kStyleCount, ++ kStyleCount + }; + + /** Return the paint's style, used for controlling how primitives' + geometries are interpreted (except for drawBitmap, which always assumes + kFill_Style). + @return the paint's Style + */ + Style getStyle() const { return (Style)fStyle; } +diff --git a/gfx/skia/include/core/SkScalerContext.h b/gfx/skia/include/core/SkScalerContext.h +--- a/gfx/skia/include/core/SkScalerContext.h ++++ b/gfx/skia/include/core/SkScalerContext.h +@@ -172,24 +172,24 @@ public: + kHintingBit2_Flag = 0x0100, + + // these should only ever be set if fMaskFormat is LCD16 or LCD32 + kLCD_Vertical_Flag = 0x0200, // else Horizontal + kLCD_BGROrder_Flag = 0x0400, // else RGB order + + // luminance : 0 for black text, kLuminance_Max for white text + kLuminance_Shift = 11, // to shift into the other flags above +- kLuminance_Bits = 3, // ensure Flags doesn't exceed 16bits ++ kLuminance_Bits = 3 // ensure Flags doesn't exceed 16bits + }; + + // computed values + enum { + kHinting_Mask = kHintingBit1_Flag | kHintingBit2_Flag, + kLuminance_Max = (1 << kLuminance_Bits) - 1, +- kLuminance_Mask = kLuminance_Max << kLuminance_Shift, ++ kLuminance_Mask = kLuminance_Max << kLuminance_Shift + }; + + struct Rec { + uint32_t fOrigFontID; + uint32_t fFontID; + SkScalar fTextSize, fPreScaleX, fPreSkewX; + SkScalar fPost2x2[2][2]; + SkScalar fFrameWidth, fMiterLimit; +diff --git a/gfx/skia/include/core/SkTypes.h b/gfx/skia/include/core/SkTypes.h +--- a/gfx/skia/include/core/SkTypes.h ++++ b/gfx/skia/include/core/SkTypes.h +@@ -433,17 +433,17 @@ public: + */ + kAlloc_OnShrink, + + /** + * If the requested size is smaller than the current size, and the + * current block is dynamically allocated, just return the old + * block. + */ +- kReuse_OnShrink, ++ kReuse_OnShrink + }; + + /** + * Reallocates the block to a new size. The ptr may or may not change. + */ + void* reset(size_t size, OnShrink shrink = kAlloc_OnShrink) { + if (size == fSize || (kReuse_OnShrink == shrink && size < fSize)) { + return fPtr; +diff --git a/gfx/skia/include/effects/SkLayerDrawLooper.h b/gfx/skia/include/effects/SkLayerDrawLooper.h +--- a/gfx/skia/include/effects/SkLayerDrawLooper.h ++++ b/gfx/skia/include/effects/SkLayerDrawLooper.h +@@ -36,17 +36,17 @@ public: + + /** + * Use the layer's paint entirely, with these exceptions: + * - We never override the draw's paint's text_encoding, since that is + * used to interpret the text/len parameters in draw[Pos]Text. + * - Flags and Color are always computed using the LayerInfo's + * fFlagsMask and fColorMode. + */ +- kEntirePaint_Bits = -1, ++ kEntirePaint_Bits = -1 + + }; + typedef int32_t BitFlags; + + /** + * Info for how to apply the layer's paint and offset. + * + * fFlagsMask selects which flags in the layer's paint should be applied. +diff --git a/gfx/skia/src/core/SkBitmap.cpp b/gfx/skia/src/core/SkBitmap.cpp +--- a/gfx/skia/src/core/SkBitmap.cpp ++++ b/gfx/skia/src/core/SkBitmap.cpp +@@ -1357,17 +1357,17 @@ bool SkBitmap::extractAlpha(SkBitmap* ds + + /////////////////////////////////////////////////////////////////////////////// + + enum { + SERIALIZE_PIXELTYPE_NONE, + SERIALIZE_PIXELTYPE_RAW_WITH_CTABLE, + SERIALIZE_PIXELTYPE_RAW_NO_CTABLE, + SERIALIZE_PIXELTYPE_REF_DATA, +- SERIALIZE_PIXELTYPE_REF_PTR, ++ SERIALIZE_PIXELTYPE_REF_PTR + }; + + static void writeString(SkFlattenableWriteBuffer& buffer, const char str[]) { + size_t len = strlen(str); + buffer.write32(len); + buffer.writePad(str, len); + } + +diff --git a/gfx/skia/src/core/SkMatrix.cpp b/gfx/skia/src/core/SkMatrix.cpp +--- a/gfx/skia/src/core/SkMatrix.cpp ++++ b/gfx/skia/src/core/SkMatrix.cpp +@@ -1715,17 +1715,17 @@ SkScalar SkMatrix::getMaxStretch() const + const SkMatrix& SkMatrix::I() { + static SkMatrix gIdentity; + static bool gOnce; + if (!gOnce) { + gIdentity.reset(); + gOnce = true; + } + return gIdentity; +-}; ++} + + const SkMatrix& SkMatrix::InvalidMatrix() { + static SkMatrix gInvalid; + static bool gOnce; + if (!gOnce) { + gInvalid.setAll(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, + SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, + SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); diff --git a/gfx/skia/patches/archive/fix-gradient-clamp.patch b/gfx/skia/patches/archive/fix-gradient-clamp.patch new file mode 100644 index 0000000000..91481c2c12 --- /dev/null +++ b/gfx/skia/patches/archive/fix-gradient-clamp.patch @@ -0,0 +1,211 @@ +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -167,16 +167,17 @@ private: + + mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values + mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values + + mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand + mutable SkMallocPixelRef* fCache32PixelRef; + mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value + ++ static SkPMColor PremultiplyColor(SkColor c0, U8CPU alpha); + static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count); + static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count, + U8CPU alpha); + void setCacheAlpha(U8CPU alpha) const; + void initCommon(); + + typedef SkShader INHERITED; + }; +@@ -512,16 +513,31 @@ static inline U8CPU dither_fixed_to_8(Sk + * For dithering with premultiply, we want to ceiling the alpha component, + * to ensure that it is always >= any color component. + */ + static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) { + n >>= 8; + return ((n << 1) - (n | (n >> 8))) >> 8; + } + ++SkPMColor Gradient_Shader::PremultiplyColor(SkColor c0, U8CPU paintAlpha) ++{ ++ SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha); ++ SkFixed r = SkColorGetR(c0); ++ SkFixed g = SkColorGetG(c0); ++ SkFixed b = SkColorGetB(c0); ++ ++ a = SkIntToFixed(a) + 0x8000; ++ r = SkIntToFixed(r) + 0x8000; ++ g = SkIntToFixed(g) + 0x8000; ++ b = SkIntToFixed(b) + 0x8000; ++ ++ return SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16); ++} ++ + void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1, + int count, U8CPU paintAlpha) { + SkASSERT(count > 1); + + // need to apply paintAlpha to our two endpoints + SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha); + SkFixed da; + { +@@ -613,24 +629,24 @@ const uint16_t* Gradient_Shader::getCach + } + } + return fCache16; + } + + const SkPMColor* Gradient_Shader::getCache32() const { + if (fCache32 == NULL) { + // double the count for dither entries +- const int entryCount = kCache32Count * 2; ++ const int entryCount = kCache32Count * 2 + 2; + const size_t allocSize = sizeof(SkPMColor) * entryCount; + + if (NULL == fCache32PixelRef) { + fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + } +- fCache32 = (SkPMColor*)fCache32PixelRef->getAddr(); ++ fCache32 = (SkPMColor*)fCache32PixelRef->getAddr() + 1; + if (fColorCount == 2) { + Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1], + kCache32Count, fCacheAlpha); + } else { + Rec* rec = fRecs; + int prevIndex = 0; + for (int i = 1; i < fColorCount; i++) { + int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits); +@@ -644,28 +660,31 @@ const SkPMColor* Gradient_Shader::getCac + } + SkASSERT(prevIndex == kCache32Count - 1); + } + + if (fMapper) { + SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef, + (NULL, allocSize, NULL)); + SkPMColor* linear = fCache32; // just computed linear data +- SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data ++ SkPMColor* mapped = (SkPMColor*)newPR->getAddr() + 1; // storage for mapped data + SkUnitMapper* map = fMapper; + for (int i = 0; i < kCache32Count; i++) { + int index = map->mapUnit16((i << 8) | i) >> 8; + mapped[i] = linear[index]; + mapped[i + kCache32Count] = linear[index + kCache32Count]; + } + fCache32PixelRef->unref(); + fCache32PixelRef = newPR; +- fCache32 = (SkPMColor*)newPR->getAddr(); ++ fCache32 = (SkPMColor*)newPR->getAddr() + 1; + } + } ++ //Write the clamp colours into the first and last entries of fCache32 ++ fCache32[-1] = PremultiplyColor(fOrigColors[0], fCacheAlpha); ++ fCache32[kCache32Count * 2] = PremultiplyColor(fOrigColors[fColorCount - 1], fCacheAlpha); + return fCache32; + } + + /* + * Because our caller might rebuild the same (logically the same) gradient + * over and over, we'd like to return exactly the same "bitmap" if possible, + * allowing the client to utilize a cache of our bitmap (e.g. with a GPU). + * To do that, we maintain a private cache of built-bitmaps, based on our +@@ -875,28 +894,38 @@ void Linear_Gradient::shadeSpan(int x, i + dx = dxStorage[0]; + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = SkScalarToFixed(fDstToIndex.getScaleX()); + } + + if (SkFixedNearlyZero(dx)) { + // we're a vertical gradient, so no change in a span +- unsigned fi = proc(fx) >> (16 - kCache32Bits); +- sk_memset32_dither(dstC, cache[toggle + fi], +- cache[(toggle ^ TOGGLE_MASK) + fi], count); ++ if (proc == clamp_tileproc) { ++ if (fx < 0) { ++ sk_memset32(dstC, cache[-1], count); ++ } else if (fx > 0xFFFF) { ++ sk_memset32(dstC, cache[kCache32Count * 2], count); ++ } else { ++ unsigned fi = proc(fx) >> (16 - kCache32Bits); ++ sk_memset32_dither(dstC, cache[toggle + fi], ++ cache[(toggle ^ TOGGLE_MASK) + fi], count); ++ } ++ } else { ++ unsigned fi = proc(fx) >> (16 - kCache32Bits); ++ sk_memset32_dither(dstC, cache[toggle + fi], ++ cache[(toggle ^ TOGGLE_MASK) + fi], count); ++ } + } else if (proc == clamp_tileproc) { + SkClampRange range; +- range.init(fx, dx, count, 0, 0xFF); ++ range.init(fx, dx, count, cache[-1], cache[kCache32Count * 2]); + + if ((count = range.fCount0) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV0], +- cache[(toggle ^ TOGGLE_MASK) + range.fV0], +- count); ++ // Do we really want to dither the clamp values? ++ sk_memset32(dstC, range.fV0, count); + dstC += count; + } + if ((count = range.fCount1) > 0) { + int unroll = count >> 3; + fx = range.fFx1; + for (int i = 0; i < unroll; i++) { + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; +@@ -905,20 +934,17 @@ void Linear_Gradient::shadeSpan(int x, i + } + if ((count &= 7) > 0) { + do { + NO_CHECK_ITER; + } while (--count != 0); + } + } + if ((count = range.fCount2) > 0) { +- sk_memset32_dither(dstC, +- cache[toggle + range.fV1], +- cache[(toggle ^ TOGGLE_MASK) + range.fV1], +- count); ++ sk_memset32(dstC, range.fV1, count); + } + } else if (proc == mirror_tileproc) { + do { + unsigned fi = mirror_8bits(fx >> 8); + SkASSERT(fi <= 0xFF); + fx += dx; + *dstC++ = cache[toggle + fi]; + toggle ^= TOGGLE_MASK; +@@ -1670,19 +1699,24 @@ public: + } + SkScalar b = (SkScalarMul(fDiff.fX, fx) + + SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; + SkScalar db = (SkScalarMul(fDiff.fX, dx) + + SkScalarMul(fDiff.fY, dy)) * 2; + if (proc == clamp_tileproc) { + for (; count > 0; --count) { + SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); +- SkFixed index = SkClampMax(t, 0xFFFF); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> (16 - kCache32Bits)]; ++ if (t < 0) { ++ *dstC++ = cache[-1]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[kCache32Count * 2]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> (16 - kCache32Bits)]; ++ } + fx += dx; + fy += dy; + b += db; + } + } else if (proc == mirror_tileproc) { + for (; count > 0; --count) { + SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); + SkFixed index = mirror_tileproc(t); diff --git a/gfx/skia/patches/archive/getpostextpath.patch b/gfx/skia/patches/archive/getpostextpath.patch new file mode 100644 index 0000000000..7181411ec8 --- /dev/null +++ b/gfx/skia/patches/archive/getpostextpath.patch @@ -0,0 +1,70 @@ +diff --git a/gfx/skia/include/core/SkPaint.h b/gfx/skia/include/core/SkPaint.h +--- a/gfx/skia/include/core/SkPaint.h ++++ b/gfx/skia/include/core/SkPaint.h +@@ -836,16 +836,19 @@ public: + + /** Return the path (outline) for the specified text. + Note: just like SkCanvas::drawText, this will respect the Align setting + in the paint. + */ + void getTextPath(const void* text, size_t length, SkScalar x, SkScalar y, + SkPath* path) const; + ++ void getPosTextPath(const void* text, size_t length, ++ const SkPoint pos[], SkPath* path) const; ++ + #ifdef SK_BUILD_FOR_ANDROID + const SkGlyph& getUnicharMetrics(SkUnichar); + const void* findImage(const SkGlyph&); + + uint32_t getGenerationID() const; + #endif + + // returns true if the paint's settings (e.g. xfermode + alpha) resolve to +diff --git a/gfx/skia/src/core/SkPaint.cpp b/gfx/skia/src/core/SkPaint.cpp +--- a/gfx/skia/src/core/SkPaint.cpp ++++ b/gfx/skia/src/core/SkPaint.cpp +@@ -1242,16 +1242,43 @@ void SkPaint::getTextPath(const void* te + const SkPath* iterPath; + while ((iterPath = iter.next(&xpos)) != NULL) { + matrix.postTranslate(xpos - prevXPos, 0); + path->addPath(*iterPath, matrix); + prevXPos = xpos; + } + } + ++void SkPaint::getPosTextPath(const void* textData, size_t length, ++ const SkPoint pos[], SkPath* path) const { ++ SkASSERT(length == 0 || textData != NULL); ++ ++ const char* text = (const char*)textData; ++ if (text == NULL || length == 0 || path == NULL) { ++ return; ++ } ++ ++ SkTextToPathIter iter(text, length, *this, false, true); ++ SkMatrix matrix; ++ SkPoint prevPos; ++ prevPos.set(0, 0); ++ ++ matrix.setScale(iter.getPathScale(), iter.getPathScale()); ++ path->reset(); ++ ++ unsigned int i = 0; ++ const SkPath* iterPath; ++ while ((iterPath = iter.next(NULL)) != NULL) { ++ matrix.postTranslate(pos[i].fX - prevPos.fX, pos[i].fY - prevPos.fY); ++ path->addPath(*iterPath, matrix); ++ prevPos = pos[i]; ++ i++; ++ } ++} ++ + static void add_flattenable(SkDescriptor* desc, uint32_t tag, + SkFlattenableWriteBuffer* buffer) { + buffer->flatten(desc->addEntry(tag, buffer->size(), NULL)); + } + + // SkFontHost can override this choice in FilterRec() + static SkMask::Format computeMaskFormat(const SkPaint& paint) { + uint32_t flags = paint.getFlags(); diff --git a/gfx/skia/patches/archive/mingw-fix.patch b/gfx/skia/patches/archive/mingw-fix.patch new file mode 100644 index 0000000000..d91a16aa70 --- /dev/null +++ b/gfx/skia/patches/archive/mingw-fix.patch @@ -0,0 +1,57 @@ +diff --git a/gfx/skia/include/core/SkPostConfig.h b/gfx/skia/include/core/SkPostConfig.h +index 0135b85..bb108f8 100644 +--- a/gfx/skia/include/core/SkPostConfig.h ++++ b/gfx/skia/include/core/SkPostConfig.h +@@ -253,7 +253,7 @@ + ////////////////////////////////////////////////////////////////////// + + #ifndef SK_OVERRIDE +-#if defined(SK_BUILD_FOR_WIN) ++#if defined(_MSC_VER) + #define SK_OVERRIDE override + #elif defined(__clang__) + // Some documentation suggests we should be using __attribute__((override)), +diff --git a/gfx/skia/src/ports/SkFontHost_win.cpp b/gfx/skia/src/ports/SkFontHost_win.cpp +index dd9c5dc..ca2c3dc 100644 +--- a/gfx/skia/src/ports/SkFontHost_win.cpp ++++ b/gfx/skia/src/ports/SkFontHost_win.cpp +@@ -22,7 +22,7 @@ + #ifdef WIN32 + #include "windows.h" + #include "tchar.h" +-#include "Usp10.h" ++#include "usp10.h" + + // always packed xxRRGGBB + typedef uint32_t SkGdiRGB; +@@ -1033,6 +1033,10 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics( + HFONT savefont = (HFONT)SelectObject(hdc, font); + HFONT designFont = NULL; + ++ const char stem_chars[] = {'i', 'I', '!', '1'}; ++ int16_t min_width; ++ unsigned glyphCount; ++ + // To request design units, create a logical font whose height is specified + // as unitsPerEm. + OUTLINETEXTMETRIC otm; +@@ -1046,7 +1050,7 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics( + if (!GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) { + goto Error; + } +- const unsigned glyphCount = calculateGlyphCount(hdc); ++ glyphCount = calculateGlyphCount(hdc); + + info = new SkAdvancedTypefaceMetrics; + info->fEmSize = otm.otmEMSquare; +@@ -1115,9 +1119,8 @@ SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics( + + // Figure out a good guess for StemV - Min width of i, I, !, 1. + // This probably isn't very good with an italic font. +- int16_t min_width = SHRT_MAX; ++ min_width = SHRT_MAX; + info->fStemV = 0; +- char stem_chars[] = {'i', 'I', '!', '1'}; + for (size_t i = 0; i < SK_ARRAY_COUNT(stem_chars); i++) { + ABC abcWidths; + if (GetCharABCWidths(hdc, stem_chars[i], stem_chars[i], &abcWidths)) { diff --git a/gfx/skia/patches/archive/new-aa.patch b/gfx/skia/patches/archive/new-aa.patch new file mode 100644 index 0000000000..d5e6fbf73d --- /dev/null +++ b/gfx/skia/patches/archive/new-aa.patch @@ -0,0 +1,22 @@ +diff --git a/gfx/skia/src/core/SkScan_AntiPath.cpp b/gfx/skia/src/core/SkScan_AntiPath.cpp +--- a/gfx/skia/src/core/SkScan_AntiPath.cpp ++++ b/gfx/skia/src/core/SkScan_AntiPath.cpp +@@ -31,17 +31,17 @@ + - supersampled coordinates, scale equal to the output * SCALE + + NEW_AA is a set of code-changes to try to make both paths produce identical + results. Its not quite there yet, though the remaining differences may be + in the subsequent blits, and not in the different masks/runs... + */ + //#define FORCE_SUPERMASK + //#define FORCE_RLE +-//#define SK_SUPPORT_NEW_AA ++#define SK_SUPPORT_NEW_AA + + /////////////////////////////////////////////////////////////////////////////// + + /// Base class for a single-pass supersampled blitter. + class BaseSuperBlitter : public SkBlitter { + public: + BaseSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir, + const SkRegion& clip); diff --git a/gfx/skia/patches/archive/old-android-fonthost.patch b/gfx/skia/patches/archive/old-android-fonthost.patch new file mode 100644 index 0000000000..1c64ace7dd --- /dev/null +++ b/gfx/skia/patches/archive/old-android-fonthost.patch @@ -0,0 +1,530 @@ +# HG changeset patch +# Parent 9ee29e4aace683ddf6cf8ddb2893cd34fcfc772c +# User James Willcox +diff --git a/gfx/skia/Makefile.in b/gfx/skia/Makefile.in +--- a/gfx/skia/Makefile.in ++++ b/gfx/skia/Makefile.in +@@ -305,21 +305,20 @@ CPPSRCS += \ + SkFontHost_mac_coretext.cpp \ + SkTime_Unix.cpp \ + $(NULL) + endif + + ifeq (android,$(MOZ_WIDGET_TOOLKIT)) + CPPSRCS += \ + SkFontHost_FreeType.cpp \ + SkFontHost_android.cpp \ + SkFontHost_gamma.cpp \ +- FontHostConfiguration_android.cpp \ + SkMMapStream.cpp \ + SkTime_Unix.cpp \ + $(NULL) + + DEFINES += -DSK_BUILD_FOR_ANDROID_NDK + OS_CXXFLAGS += $(CAIRO_FT_CFLAGS) + endif + + ifeq (gtk2,$(MOZ_WIDGET_TOOLKIT)) + CPPSRCS += \ +diff --git a/gfx/skia/src/ports/SkFontHost_android.cpp b/gfx/skia/src/ports/SkFontHost_android.cpp +--- a/gfx/skia/src/ports/SkFontHost_android.cpp ++++ b/gfx/skia/src/ports/SkFontHost_android.cpp +@@ -1,38 +1,31 @@ ++ + /* +-** +-** Copyright 2006, The Android Open Source Project +-** +-** Licensed under the Apache License, Version 2.0 (the "License"); +-** you may not use this file except in compliance with the License. +-** You may obtain a copy of the License at +-** +-** http://www.apache.org/licenses/LICENSE-2.0 +-** +-** Unless required by applicable law or agreed to in writing, software +-** distributed under the License is distributed on an "AS IS" BASIS, +-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-** See the License for the specific language governing permissions and +-** limitations under the License. +-*/ ++ * Copyright 2006 The Android Open Source Project ++ * ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. ++ */ ++ + + #include "SkFontHost.h" + #include "SkDescriptor.h" + #include "SkMMapStream.h" + #include "SkPaint.h" + #include "SkString.h" + #include "SkStream.h" + #include "SkThread.h" + #include "SkTSearch.h" +-#include "FontHostConfiguration_android.h" + #include + ++#define FONT_CACHE_MEMORY_BUDGET (768 * 1024) ++ + #ifndef SK_FONT_FILE_PREFIX + #define SK_FONT_FILE_PREFIX "/fonts/" + #endif + + SkTypeface::Style find_name_and_attributes(SkStream* stream, SkString* name, + bool* isFixedWidth); + + static void GetFullPathForSysFonts(SkString* full, const char name[]) { + full->set(getenv("ANDROID_ROOT")); + full->append(SK_FONT_FILE_PREFIX); +@@ -99,21 +92,21 @@ static SkTypeface* find_best_face(const + if (faces[SkTypeface::kNormal] != NULL) { + return faces[SkTypeface::kNormal]; + } + // look for anything + for (int i = 0; i < 4; i++) { + if (faces[i] != NULL) { + return faces[i]; + } + } + // should never get here, since the faces list should not be empty +- SkDEBUGFAIL("faces list is empty"); ++ SkASSERT(!"faces list is empty"); + return NULL; + } + + static FamilyRec* find_family(const SkTypeface* member) { + FamilyRec* curr = gFamilyHead; + while (curr != NULL) { + for (int i = 0; i < 4; i++) { + if (curr->fFaces[i] == member) { + return curr; + } +@@ -138,31 +131,27 @@ static SkTypeface* find_from_uniqueID(ui + curr = curr->fNext; + } + return NULL; + } + + /* Remove reference to this face from its family. If the resulting family + is empty (has no faces), return that family, otherwise return NULL + */ + static FamilyRec* remove_from_family(const SkTypeface* face) { + FamilyRec* family = find_family(face); +- if (family) { +- SkASSERT(family->fFaces[face->style()] == face); +- family->fFaces[face->style()] = NULL; ++ SkASSERT(family->fFaces[face->style()] == face); ++ family->fFaces[face->style()] = NULL; + +- for (int i = 0; i < 4; i++) { +- if (family->fFaces[i] != NULL) { // family is non-empty +- return NULL; +- } ++ for (int i = 0; i < 4; i++) { ++ if (family->fFaces[i] != NULL) { // family is non-empty ++ return NULL; + } +- } else { +-// SkDebugf("remove_from_family(%p) face not found", face); + } + return family; // return the empty family + } + + // maybe we should make FamilyRec be doubly-linked + static void detach_and_delete_family(FamilyRec* family) { + FamilyRec* curr = gFamilyHead; + FamilyRec* prev = NULL; + + while (curr != NULL) { +@@ -172,21 +161,21 @@ static void detach_and_delete_family(Fam + gFamilyHead = next; + } else { + prev->fNext = next; + } + SkDELETE(family); + return; + } + prev = curr; + curr = next; + } +- SkDEBUGFAIL("Yikes, couldn't find family in our list to remove/delete"); ++ SkASSERT(!"Yikes, couldn't find family in our list to remove/delete"); + } + + static SkTypeface* find_typeface(const char name[], SkTypeface::Style style) { + NameFamilyPair* list = gNameList.begin(); + int count = gNameList.count(); + + int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); + + if (index >= 0) { + return find_best_face(list[index].fFamily, style); +@@ -387,111 +376,90 @@ static bool get_name_and_style(const cha + } + return false; + } + + // used to record our notion of the pre-existing fonts + struct FontInitRec { + const char* fFileName; + const char* const* fNames; // null-terminated list + }; + ++static const char* gSansNames[] = { ++ "sans-serif", "arial", "helvetica", "tahoma", "verdana", NULL ++}; ++ ++static const char* gSerifNames[] = { ++ "serif", "times", "times new roman", "palatino", "georgia", "baskerville", ++ "goudy", "fantasy", "cursive", "ITC Stone Serif", NULL ++}; ++ ++static const char* gMonoNames[] = { ++ "monospace", "courier", "courier new", "monaco", NULL ++}; ++ + // deliberately empty, but we use the address to identify fallback fonts + static const char* gFBNames[] = { NULL }; + ++/* Fonts must be grouped by family, with the first font in a family having the ++ list of names (even if that list is empty), and the following members having ++ null for the list. The names list must be NULL-terminated ++*/ ++static const FontInitRec gSystemFonts[] = { ++ { "DroidSans.ttf", gSansNames }, ++ { "DroidSans-Bold.ttf", NULL }, ++ { "DroidSerif-Regular.ttf", gSerifNames }, ++ { "DroidSerif-Bold.ttf", NULL }, ++ { "DroidSerif-Italic.ttf", NULL }, ++ { "DroidSerif-BoldItalic.ttf", NULL }, ++ { "DroidSansMono.ttf", gMonoNames }, ++ /* These are optional, and can be ignored if not found in the file system. ++ These are appended to gFallbackFonts[] as they are seen, so we list ++ them in the order we want them to be accessed by NextLogicalFont(). ++ */ ++ { "DroidSansArabic.ttf", gFBNames }, ++ { "DroidSansHebrew.ttf", gFBNames }, ++ { "DroidSansThai.ttf", gFBNames }, ++ { "MTLmr3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "MTLc3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "DroidSansJapanese.ttf", gFBNames }, ++ { "DroidSansFallback.ttf", gFBNames } ++}; + +-/* Fonts are grouped by family, with the first font in a family having the +- list of names (even if that list is empty), and the following members having +- null for the list. The names list must be NULL-terminated. +-*/ +-static FontInitRec *gSystemFonts; +-static size_t gNumSystemFonts = 0; +- +-#define SYSTEM_FONTS_FILE "/system/etc/system_fonts.cfg" ++#define DEFAULT_NAMES gSansNames + + // these globals are assigned (once) by load_system_fonts() + static FamilyRec* gDefaultFamily; + static SkTypeface* gDefaultNormal; +-static char** gDefaultNames = NULL; +-static uint32_t *gFallbackFonts; + +-/* Load info from a configuration file that populates the system/fallback font structures +-*/ +-static void load_font_info() { +-// load_font_info_xml("/system/etc/system_fonts.xml"); +- SkTDArray fontFamilies; +- getFontFamilies(fontFamilies); +- +- SkTDArray fontInfo; +- bool firstInFamily = false; +- for (int i = 0; i < fontFamilies.count(); ++i) { +- FontFamily *family = fontFamilies[i]; +- firstInFamily = true; +- for (int j = 0; j < family->fFileNames.count(); ++j) { +- FontInitRec fontInfoRecord; +- fontInfoRecord.fFileName = family->fFileNames[j]; +- if (j == 0) { +- if (family->fNames.count() == 0) { +- // Fallback font +- fontInfoRecord.fNames = (char **)gFBNames; +- } else { +- SkTDArray names = family->fNames; +- const char **nameList = (const char**) +- malloc((names.count() + 1) * sizeof(char*)); +- if (nameList == NULL) { +- // shouldn't get here +- break; +- } +- if (gDefaultNames == NULL) { +- gDefaultNames = (char**) nameList; +- } +- for (int i = 0; i < names.count(); ++i) { +- nameList[i] = names[i]; +- } +- nameList[names.count()] = NULL; +- fontInfoRecord.fNames = nameList; +- } +- } else { +- fontInfoRecord.fNames = NULL; +- } +- *fontInfo.append() = fontInfoRecord; +- } +- } +- gNumSystemFonts = fontInfo.count(); +- gSystemFonts = (FontInitRec*) malloc(gNumSystemFonts * sizeof(FontInitRec)); +- gFallbackFonts = (uint32_t*) malloc((gNumSystemFonts + 1) * sizeof(uint32_t)); +- if (gSystemFonts == NULL) { +- // shouldn't get here +- gNumSystemFonts = 0; +- } +- for (size_t i = 0; i < gNumSystemFonts; ++i) { +- gSystemFonts[i].fFileName = fontInfo[i].fFileName; +- gSystemFonts[i].fNames = fontInfo[i].fNames; +- } +- fontFamilies.deleteAll(); +-} ++/* This is sized conservatively, assuming that it will never be a size issue. ++ It will be initialized in load_system_fonts(), and will be filled with the ++ fontIDs that can be used for fallback consideration, in sorted order (sorted ++ meaning element[0] should be used first, then element[1], etc. When we hit ++ a fontID==0 in the array, the list is done, hence our allocation size is ++ +1 the total number of possible system fonts. Also see NextLogicalFont(). ++ */ ++static uint32_t gFallbackFonts[SK_ARRAY_COUNT(gSystemFonts)+1]; + + /* Called once (ensured by the sentinel check at the beginning of our body). + Initializes all the globals, and register the system fonts. + */ + static void load_system_fonts() { + // check if we've already be called + if (NULL != gDefaultNormal) { + return; + } + +- load_font_info(); +- + const FontInitRec* rec = gSystemFonts; + SkTypeface* firstInFamily = NULL; + int fallbackCount = 0; + +- for (size_t i = 0; i < gNumSystemFonts; i++) { ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { + // if we're the first in a new family, clear firstInFamily + if (rec[i].fNames != NULL) { + firstInFamily = NULL; + } + + bool isFixedWidth; + SkString name; + SkTypeface::Style style; + + // we expect all the fonts, except the "fallback" fonts +@@ -515,120 +483,75 @@ static void load_system_fonts() { + // SkDebugf("---- adding %s as fallback[%d] fontID %d\n", + // rec[i].fFileName, fallbackCount, tf->uniqueID()); + gFallbackFonts[fallbackCount++] = tf->uniqueID(); + } + + firstInFamily = tf; + FamilyRec* family = find_family(tf); + const char* const* names = rec[i].fNames; + + // record the default family if this is it +- if (names == gDefaultNames) { ++ if (names == DEFAULT_NAMES) { + gDefaultFamily = family; + } + // add the names to map to this family + while (*names) { + add_name(*names, family); + names += 1; + } + } + } + + // do this after all fonts are loaded. This is our default font, and it + // acts as a sentinel so we only execute load_system_fonts() once + gDefaultNormal = find_best_face(gDefaultFamily, SkTypeface::kNormal); + // now terminate our fallback list with the sentinel value + gFallbackFonts[fallbackCount] = 0; + } + + /////////////////////////////////////////////////////////////////////////////// + + void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) { +- // lookup and record if the font is custom (i.e. not a system font) +- bool isCustomFont = !((FamilyTypeface*)face)->isSysFont(); +- stream->writeBool(isCustomFont); ++ const char* name = ((FamilyTypeface*)face)->getUniqueString(); + +- if (isCustomFont) { +- SkStream* fontStream = ((FamilyTypeface*)face)->openStream(); ++ stream->write8((uint8_t)face->style()); + +- // store the length of the custom font +- uint32_t len = fontStream->getLength(); +- stream->write32(len); +- +- // store the entire font in the serialized stream +- void* fontData = malloc(len); +- +- fontStream->read(fontData, len); +- stream->write(fontData, len); +- +- fontStream->unref(); +- free(fontData); +-// SkDebugf("--- fonthost custom serialize %d %d\n", face->style(), len); +- ++ if (NULL == name || 0 == *name) { ++ stream->writePackedUInt(0); ++// SkDebugf("--- fonthost serialize null\n"); + } else { +- const char* name = ((FamilyTypeface*)face)->getUniqueString(); +- +- stream->write8((uint8_t)face->style()); +- +- if (NULL == name || 0 == *name) { +- stream->writePackedUInt(0); +-// SkDebugf("--- fonthost serialize null\n"); +- } else { +- uint32_t len = strlen(name); +- stream->writePackedUInt(len); +- stream->write(name, len); +-// SkDebugf("--- fonthost serialize <%s> %d\n", name, face->style()); +- } ++ uint32_t len = strlen(name); ++ stream->writePackedUInt(len); ++ stream->write(name, len); ++// SkDebugf("--- fonthost serialize <%s> %d\n", name, face->style()); + } + } + + SkTypeface* SkFontHost::Deserialize(SkStream* stream) { + load_system_fonts(); + +- // check if the font is a custom or system font +- bool isCustomFont = stream->readBool(); ++ int style = stream->readU8(); + +- if (isCustomFont) { ++ int len = stream->readPackedUInt(); ++ if (len > 0) { ++ SkString str; ++ str.resize(len); ++ stream->read(str.writable_str(), len); + +- // read the length of the custom font from the stream +- uint32_t len = stream->readU32(); +- +- // generate a new stream to store the custom typeface +- SkMemoryStream* fontStream = new SkMemoryStream(len); +- stream->read((void*)fontStream->getMemoryBase(), len); +- +- SkTypeface* face = CreateTypefaceFromStream(fontStream); +- +- fontStream->unref(); +- +-// SkDebugf("--- fonthost custom deserialize %d %d\n", face->style(), len); +- return face; +- +- } else { +- int style = stream->readU8(); +- +- int len = stream->readPackedUInt(); +- if (len > 0) { +- SkString str; +- str.resize(len); +- stream->read(str.writable_str(), len); +- +- const FontInitRec* rec = gSystemFonts; +- for (size_t i = 0; i < gNumSystemFonts; i++) { +- if (strcmp(rec[i].fFileName, str.c_str()) == 0) { +- // backup until we hit the fNames +- for (int j = i; j >= 0; --j) { +- if (rec[j].fNames != NULL) { +- return SkFontHost::CreateTypeface(NULL, +- rec[j].fNames[0], NULL, 0, +- (SkTypeface::Style)style); +- } ++ const FontInitRec* rec = gSystemFonts; ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ if (strcmp(rec[i].fFileName, str.c_str()) == 0) { ++ // backup until we hit the fNames ++ for (int j = i; j >= 0; --j) { ++ if (rec[j].fNames != NULL) { ++ return SkFontHost::CreateTypeface(NULL, ++ rec[j].fNames[0], NULL, 0, (SkTypeface::Style)style); + } + } + } + } + } + return NULL; + } + + /////////////////////////////////////////////////////////////////////////////// + +@@ -697,49 +620,32 @@ size_t SkFontHost::GetFileName(SkFontID + } + return size; + } else { + return 0; + } + } + + SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { + load_system_fonts(); + +- const SkTypeface* origTypeface = find_from_uniqueID(origFontID); +- const SkTypeface* currTypeface = find_from_uniqueID(currFontID); +- +- SkASSERT(origTypeface != 0); +- SkASSERT(currTypeface != 0); +- +- // Our fallback list always stores the id of the plain in each fallback +- // family, so we transform currFontID to its plain equivalent. +- currFontID = find_typeface(currTypeface, SkTypeface::kNormal)->uniqueID(); +- + /* First see if fontID is already one of our fallbacks. If so, return + its successor. If fontID is not in our list, then return the first one + in our list. Note: list is zero-terminated, and returning zero means + we have no more fonts to use for fallbacks. + */ + const uint32_t* list = gFallbackFonts; + for (int i = 0; list[i] != 0; i++) { + if (list[i] == currFontID) { +- if (list[i+1] == 0) +- return 0; +- const SkTypeface* nextTypeface = find_from_uniqueID(list[i+1]); +- return find_typeface(nextTypeface, origTypeface->style())->uniqueID(); ++ return list[i+1]; + } + } +- +- // If we get here, currFontID was not a fallback, so we start at the +- // beginning of our list. +- const SkTypeface* firstTypeface = find_from_uniqueID(list[0]); +- return find_typeface(firstTypeface, origTypeface->style())->uniqueID(); ++ return list[0]; + } + + /////////////////////////////////////////////////////////////////////////////// + + SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) { + if (NULL == stream || stream->getLength() <= 0) { + return NULL; + } + + bool isFixedWidth; +@@ -754,10 +660,11 @@ SkTypeface* SkFontHost::CreateTypefaceFr + } + + SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) { + SkStream* stream = SkNEW_ARGS(SkMMAPStream, (path)); + SkTypeface* face = SkFontHost::CreateTypefaceFromStream(stream); + // since we created the stream, we let go of our ref() here + stream->unref(); + return face; + } + ++/////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/skia/patches/archive/radial-gradients.patch b/gfx/skia/patches/archive/radial-gradients.patch new file mode 100644 index 0000000000..183923e83e --- /dev/null +++ b/gfx/skia/patches/archive/radial-gradients.patch @@ -0,0 +1,25 @@ +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -1665,17 +1665,20 @@ public: + } + return kRadial2_GradientType; + } + + virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) SK_OVERRIDE { + SkASSERT(count > 0); + + // Zero difference between radii: fill with transparent black. +- if (fDiffRadius == 0) { ++ // TODO: Is removing this actually correct? Two circles with the ++ // same radius, but different centers doesn't sound like it ++ // should be cleared ++ if (fDiffRadius == 0 && fCenter1 == fCenter2) { + sk_bzero(dstC, count * sizeof(*dstC)); + return; + } + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = fTileProc; + const SkPMColor* SK_RESTRICT cache = this->getCache32(); + + SkScalar foura = fA * 4; diff --git a/gfx/skia/patches/archive/skia_restrict_problem.patch b/gfx/skia/patches/archive/skia_restrict_problem.patch new file mode 100644 index 0000000000..c7639ca2ce --- /dev/null +++ b/gfx/skia/patches/archive/skia_restrict_problem.patch @@ -0,0 +1,461 @@ +diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp +--- a/gfx/skia/src/effects/SkGradientShader.cpp ++++ b/gfx/skia/src/effects/SkGradientShader.cpp +@@ -1184,116 +1184,17 @@ public: + { + // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE + SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE); + + rad_to_unit_matrix(center, radius, &fPtsToUnit); + } + + virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE; +- virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) SK_OVERRIDE { +- SkASSERT(count > 0); +- +- SkPoint srcPt; +- SkMatrix::MapXYProc dstProc = fDstToIndexProc; +- TileProc proc = fTileProc; +- const uint16_t* SK_RESTRICT cache = this->getCache16(); +- int toggle = ((x ^ y) & 1) << kCache16Bits; +- +- if (fDstToIndexClass != kPerspective_MatrixClass) { +- dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, +- SkIntToScalar(y) + SK_ScalarHalf, &srcPt); +- SkFixed dx, fx = SkScalarToFixed(srcPt.fX); +- SkFixed dy, fy = SkScalarToFixed(srcPt.fY); +- +- if (fDstToIndexClass == kFixedStepInX_MatrixClass) { +- SkFixed storage[2]; +- (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]); +- dx = storage[0]; +- dy = storage[1]; +- } else { +- SkASSERT(fDstToIndexClass == kLinear_MatrixClass); +- dx = SkScalarToFixed(fDstToIndex.getScaleX()); +- dy = SkScalarToFixed(fDstToIndex.getSkewY()); +- } +- +- if (proc == clamp_tileproc) { +- const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table; +- +- /* knock these down so we can pin against +- 0x7FFF, which is an immediate load, +- rather than 0xFFFF which is slower. This is a compromise, since it reduces our +- precision, but that appears to be visually OK. If we decide this is OK for +- all of our cases, we could (it seems) put this scale-down into fDstToIndex, +- to avoid having to do these extra shifts each time. +- */ +- fx >>= 1; +- dx >>= 1; +- fy >>= 1; +- dy >>= 1; +- if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total +- fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1); +- fy *= fy; +- do { +- unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1); +- unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS); +- fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); +- fx += dx; +- *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))]; +- toggle ^= (1 << kCache16Bits); +- } while (--count != 0); +- } else { +- do { +- unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1); +- unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1); +- fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS); +- fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); +- fx += dx; +- fy += dy; +- *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))]; +- toggle ^= (1 << kCache16Bits); +- } while (--count != 0); +- } +- } else if (proc == mirror_tileproc) { +- do { +- SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy)); +- unsigned fi = mirror_tileproc(dist); +- SkASSERT(fi <= 0xFFFF); +- fx += dx; +- fy += dy; +- *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))]; +- toggle ^= (1 << kCache16Bits); +- } while (--count != 0); +- } else { +- SkASSERT(proc == repeat_tileproc); +- do { +- SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy)); +- unsigned fi = repeat_tileproc(dist); +- SkASSERT(fi <= 0xFFFF); +- fx += dx; +- fy += dy; +- *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))]; +- toggle ^= (1 << kCache16Bits); +- } while (--count != 0); +- } +- } else { // perspective case +- SkScalar dstX = SkIntToScalar(x); +- SkScalar dstY = SkIntToScalar(y); +- do { +- dstProc(fDstToIndex, dstX, dstY, &srcPt); +- unsigned fi = proc(SkScalarToFixed(srcPt.length())); +- SkASSERT(fi <= 0xFFFF); +- +- int index = fi >> (16 - kCache16Bits); +- *dstC++ = cache[toggle + index]; +- toggle ^= (1 << kCache16Bits); +- +- dstX += SK_Scalar1; +- } while (--count != 0); +- } +- } ++ virtual void shadeSpan16(int x, int y, uint16_t* dstC, int count) SK_OVERRIDE; + + virtual BitmapType asABitmap(SkBitmap* bitmap, + SkMatrix* matrix, + TileMode* xy, + SkScalar* twoPointRadialParams) const SK_OVERRIDE { + if (bitmap) { + this->commonAsABitmap(bitmap); + } +@@ -1507,16 +1408,117 @@ void Radial_Gradient::shadeSpan(int x, i + unsigned fi = proc(SkScalarToFixed(srcPt.length())); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[fi >> (16 - kCache32Bits)]; + dstX += SK_Scalar1; + } while (--count != 0); + } + } + ++void Radial_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) { ++ SkASSERT(count > 0); ++ ++ SkPoint srcPt; ++ SkMatrix::MapXYProc dstProc = fDstToIndexProc; ++ TileProc proc = fTileProc; ++ const uint16_t* SK_RESTRICT cache = this->getCache16(); ++ int toggle = ((x ^ y) & 1) << kCache16Bits; ++ ++ if (fDstToIndexClass != kPerspective_MatrixClass) { ++ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, ++ SkIntToScalar(y) + SK_ScalarHalf, &srcPt); ++ SkFixed dx, fx = SkScalarToFixed(srcPt.fX); ++ SkFixed dy, fy = SkScalarToFixed(srcPt.fY); ++ ++ if (fDstToIndexClass == kFixedStepInX_MatrixClass) { ++ SkFixed storage[2]; ++ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]); ++ dx = storage[0]; ++ dy = storage[1]; ++ } else { ++ SkASSERT(fDstToIndexClass == kLinear_MatrixClass); ++ dx = SkScalarToFixed(fDstToIndex.getScaleX()); ++ dy = SkScalarToFixed(fDstToIndex.getSkewY()); ++ } ++ ++ if (proc == clamp_tileproc) { ++ const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table; ++ ++ /* knock these down so we can pin against +- 0x7FFF, which is an immediate load, ++ rather than 0xFFFF which is slower. This is a compromise, since it reduces our ++ precision, but that appears to be visually OK. If we decide this is OK for ++ all of our cases, we could (it seems) put this scale-down into fDstToIndex, ++ to avoid having to do these extra shifts each time. ++ */ ++ fx >>= 1; ++ dx >>= 1; ++ fy >>= 1; ++ dy >>= 1; ++ if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total ++ fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1); ++ fy *= fy; ++ do { ++ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1); ++ unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS); ++ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); ++ fx += dx; ++ *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))]; ++ toggle ^= (1 << kCache16Bits); ++ } while (--count != 0); ++ } else { ++ do { ++ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1); ++ unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1); ++ fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS); ++ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); ++ fx += dx; ++ fy += dy; ++ *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))]; ++ toggle ^= (1 << kCache16Bits); ++ } while (--count != 0); ++ } ++ } else if (proc == mirror_tileproc) { ++ do { ++ SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy)); ++ unsigned fi = mirror_tileproc(dist); ++ SkASSERT(fi <= 0xFFFF); ++ fx += dx; ++ fy += dy; ++ *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))]; ++ toggle ^= (1 << kCache16Bits); ++ } while (--count != 0); ++ } else { ++ SkASSERT(proc == repeat_tileproc); ++ do { ++ SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy)); ++ unsigned fi = repeat_tileproc(dist); ++ SkASSERT(fi <= 0xFFFF); ++ fx += dx; ++ fy += dy; ++ *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))]; ++ toggle ^= (1 << kCache16Bits); ++ } while (--count != 0); ++ } ++ } else { // perspective case ++ SkScalar dstX = SkIntToScalar(x); ++ SkScalar dstY = SkIntToScalar(y); ++ do { ++ dstProc(fDstToIndex, dstX, dstY, &srcPt); ++ unsigned fi = proc(SkScalarToFixed(srcPt.length())); ++ SkASSERT(fi <= 0xFFFF); ++ ++ int index = fi >> (16 - kCache16Bits); ++ *dstC++ = cache[toggle + index]; ++ toggle ^= (1 << kCache16Bits); ++ ++ dstX += SK_Scalar1; ++ } while (--count != 0); ++ } ++} ++ + /* Two-point radial gradients are specified by two circles, each with a center + point and radius. The gradient can be considered to be a series of + concentric circles, with the color interpolated from the start circle + (at t=0) to the end circle (at t=1). + + For each point (x, y) in the span, we want to find the + interpolated circle that intersects that point. The center + of the desired circle (Cx, Cy) falls at some distance t +@@ -1661,109 +1663,17 @@ public: + info->fPoint[0] = fCenter1; + info->fPoint[1] = fCenter2; + info->fRadius[0] = fRadius1; + info->fRadius[1] = fRadius2; + } + return kRadial2_GradientType; + } + +- virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) SK_OVERRIDE { +- SkASSERT(count > 0); +- +- // Zero difference between radii: fill with transparent black. +- // TODO: Is removing this actually correct? Two circles with the +- // same radius, but different centers doesn't sound like it +- // should be cleared +- if (fDiffRadius == 0 && fCenter1 == fCenter2) { +- sk_bzero(dstC, count * sizeof(*dstC)); +- return; +- } +- SkMatrix::MapXYProc dstProc = fDstToIndexProc; +- TileProc proc = fTileProc; +- const SkPMColor* SK_RESTRICT cache = this->getCache32(); +- +- SkScalar foura = fA * 4; +- bool posRoot = fDiffRadius < 0; +- if (fDstToIndexClass != kPerspective_MatrixClass) { +- SkPoint srcPt; +- dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, +- SkIntToScalar(y) + SK_ScalarHalf, &srcPt); +- SkScalar dx, fx = srcPt.fX; +- SkScalar dy, fy = srcPt.fY; +- +- if (fDstToIndexClass == kFixedStepInX_MatrixClass) { +- SkFixed fixedX, fixedY; +- (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY); +- dx = SkFixedToScalar(fixedX); +- dy = SkFixedToScalar(fixedY); +- } else { +- SkASSERT(fDstToIndexClass == kLinear_MatrixClass); +- dx = fDstToIndex.getScaleX(); +- dy = fDstToIndex.getSkewY(); +- } +- SkScalar b = (SkScalarMul(fDiff.fX, fx) + +- SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; +- SkScalar db = (SkScalarMul(fDiff.fX, dx) + +- SkScalarMul(fDiff.fY, dy)) * 2; +- if (proc == clamp_tileproc) { +- for (; count > 0; --count) { +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); +- if (t < 0) { +- *dstC++ = cache[-1]; +- } else if (t > 0xFFFF) { +- *dstC++ = cache[kCache32Count * 2]; +- } else { +- SkASSERT(t <= 0xFFFF); +- *dstC++ = cache[t >> (16 - kCache32Bits)]; +- } +- fx += dx; +- fy += dy; +- b += db; +- } +- } else if (proc == mirror_tileproc) { +- for (; count > 0; --count) { +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); +- SkFixed index = mirror_tileproc(t); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> (16 - kCache32Bits)]; +- fx += dx; +- fy += dy; +- b += db; +- } +- } else { +- SkASSERT(proc == repeat_tileproc); +- for (; count > 0; --count) { +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); +- SkFixed index = repeat_tileproc(t); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> (16 - kCache32Bits)]; +- fx += dx; +- fy += dy; +- b += db; +- } +- } +- } else { // perspective case +- SkScalar dstX = SkIntToScalar(x); +- SkScalar dstY = SkIntToScalar(y); +- for (; count > 0; --count) { +- SkPoint srcPt; +- dstProc(fDstToIndex, dstX, dstY, &srcPt); +- SkScalar fx = srcPt.fX; +- SkScalar fy = srcPt.fY; +- SkScalar b = (SkScalarMul(fDiff.fX, fx) + +- SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; +- SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); +- SkFixed index = proc(t); +- SkASSERT(index <= 0xFFFF); +- *dstC++ = cache[index >> (16 - kCache32Bits)]; +- dstX += SK_Scalar1; +- } +- } +- } ++ virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE; + + virtual bool setContext(const SkBitmap& device, + const SkPaint& paint, + const SkMatrix& matrix) SK_OVERRIDE { + if (!this->INHERITED::setContext(device, paint, matrix)) { + return false; + } + +@@ -1817,16 +1727,110 @@ private: + fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1; + fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0; + + fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY); + fPtsToUnit.postScale(inv, inv); + } + }; + ++void Two_Point_Radial_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) { ++ SkASSERT(count > 0); ++ ++ // Zero difference between radii: fill with transparent black. ++ // TODO: Is removing this actually correct? Two circles with the ++ // same radius, but different centers doesn't sound like it ++ // should be cleared ++ if (fDiffRadius == 0 && fCenter1 == fCenter2) { ++ sk_bzero(dstC, count * sizeof(*dstC)); ++ return; ++ } ++ SkMatrix::MapXYProc dstProc = fDstToIndexProc; ++ TileProc proc = fTileProc; ++ const SkPMColor* SK_RESTRICT cache = this->getCache32(); ++ ++ SkScalar foura = fA * 4; ++ bool posRoot = fDiffRadius < 0; ++ if (fDstToIndexClass != kPerspective_MatrixClass) { ++ SkPoint srcPt; ++ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, ++ SkIntToScalar(y) + SK_ScalarHalf, &srcPt); ++ SkScalar dx, fx = srcPt.fX; ++ SkScalar dy, fy = srcPt.fY; ++ ++ if (fDstToIndexClass == kFixedStepInX_MatrixClass) { ++ SkFixed fixedX, fixedY; ++ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY); ++ dx = SkFixedToScalar(fixedX); ++ dy = SkFixedToScalar(fixedY); ++ } else { ++ SkASSERT(fDstToIndexClass == kLinear_MatrixClass); ++ dx = fDstToIndex.getScaleX(); ++ dy = fDstToIndex.getSkewY(); ++ } ++ SkScalar b = (SkScalarMul(fDiff.fX, fx) + ++ SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; ++ SkScalar db = (SkScalarMul(fDiff.fX, dx) + ++ SkScalarMul(fDiff.fY, dy)) * 2; ++ if (proc == clamp_tileproc) { ++ for (; count > 0; --count) { ++ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); ++ if (t < 0) { ++ *dstC++ = cache[-1]; ++ } else if (t > 0xFFFF) { ++ *dstC++ = cache[kCache32Count * 2]; ++ } else { ++ SkASSERT(t <= 0xFFFF); ++ *dstC++ = cache[t >> (16 - kCache32Bits)]; ++ } ++ fx += dx; ++ fy += dy; ++ b += db; ++ } ++ } else if (proc == mirror_tileproc) { ++ for (; count > 0; --count) { ++ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); ++ SkFixed index = mirror_tileproc(t); ++ SkASSERT(index <= 0xFFFF); ++ *dstC++ = cache[index >> (16 - kCache32Bits)]; ++ fx += dx; ++ fy += dy; ++ b += db; ++ } ++ } else { ++ SkASSERT(proc == repeat_tileproc); ++ for (; count > 0; --count) { ++ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); ++ SkFixed index = repeat_tileproc(t); ++ SkASSERT(index <= 0xFFFF); ++ *dstC++ = cache[index >> (16 - kCache32Bits)]; ++ fx += dx; ++ fy += dy; ++ b += db; ++ } ++ } ++ } else { // perspective case ++ SkScalar dstX = SkIntToScalar(x); ++ SkScalar dstY = SkIntToScalar(y); ++ for (; count > 0; --count) { ++ SkPoint srcPt; ++ dstProc(fDstToIndex, dstX, dstY, &srcPt); ++ SkScalar fx = srcPt.fX; ++ SkScalar fy = srcPt.fY; ++ SkScalar b = (SkScalarMul(fDiff.fX, fx) + ++ SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2; ++ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot); ++ SkFixed index = proc(t); ++ SkASSERT(index <= 0xFFFF); ++ *dstC++ = cache[index >> (16 - kCache32Bits)]; ++ dstX += SK_Scalar1; ++ } ++ } ++} ++ + /////////////////////////////////////////////////////////////////////////////// + + class Sweep_Gradient : public Gradient_Shader { + public: + Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[], + const SkScalar pos[], int count, SkUnitMapper* mapper) + : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper), + fCenter(SkPoint::Make(cx, cy)) diff --git a/gfx/skia/patches/archive/uninitialized-margin.patch b/gfx/skia/patches/archive/uninitialized-margin.patch new file mode 100644 index 0000000000..b8ab213e7b --- /dev/null +++ b/gfx/skia/patches/archive/uninitialized-margin.patch @@ -0,0 +1,22 @@ +diff --git a/gfx/skia/src/core/SkDraw.cpp b/gfx/skia/src/core/SkDraw.cpp +--- a/gfx/skia/src/core/SkDraw.cpp ++++ b/gfx/skia/src/core/SkDraw.cpp +@@ -2529,17 +2529,17 @@ static bool compute_bounds(const SkPath& + + // init our bounds from the path + { + SkRect pathBounds = devPath.getBounds(); + pathBounds.inset(-SK_ScalarHalf, -SK_ScalarHalf); + pathBounds.roundOut(bounds); + } + +- SkIPoint margin; ++ SkIPoint margin = SkIPoint::Make(0, 0); + if (filter) { + SkASSERT(filterMatrix); + + SkMask srcM, dstM; + + srcM.fBounds = *bounds; + srcM.fFormat = SkMask::kA8_Format; + srcM.fImage = NULL; diff --git a/gfx/skia/patches/archive/user-config.patch b/gfx/skia/patches/archive/user-config.patch new file mode 100644 index 0000000000..11c6f1f638 --- /dev/null +++ b/gfx/skia/patches/archive/user-config.patch @@ -0,0 +1,40 @@ +diff --git a/gfx/skia/include/config/SkUserConfig.h b/gfx/skia/include/config/SkUserConfig.h +--- a/gfx/skia/include/config/SkUserConfig.h ++++ b/gfx/skia/include/config/SkUserConfig.h +@@ -140,16 +140,20 @@ + /* If SK_DEBUG is defined, then you can optionally define SK_SUPPORT_UNITTEST + which will run additional self-tests at startup. These can take a long time, + so this flag is optional. + */ + #ifdef SK_DEBUG + //#define SK_SUPPORT_UNITTEST + #endif + ++/* Don't dither 32bit gradients, to match what the canvas test suite expects. ++ */ ++#define SK_DISABLE_DITHER_32BIT_GRADIENT ++ + /* If your system embeds skia and has complex event logging, define this + symbol to name a file that maps the following macros to your system's + equivalents: + SK_TRACE_EVENT0(event) + SK_TRACE_EVENT1(event, name1, value1) + SK_TRACE_EVENT2(event, name1, value1, name2, value2) + src/utils/SkDebugTrace.h has a trivial implementation that writes to + the debug output stream. If SK_USER_TRACE_INCLUDE_FILE is not defined, +@@ -161,9 +165,15 @@ + */ + #ifdef SK_SAMPLES_FOR_X + #define SK_R32_SHIFT 16 + #define SK_G32_SHIFT 8 + #define SK_B32_SHIFT 0 + #define SK_A32_SHIFT 24 + #endif + ++/* Don't include stdint.h on windows as it conflicts with our build system. ++ */ ++#ifdef SK_BUILD_FOR_WIN32 ++ #define SK_IGNORE_STDINT_DOT_H ++#endif ++ + #endif diff --git a/gfx/skia/skia/include/android/SkAndroidFrameworkUtils.h b/gfx/skia/skia/include/android/SkAndroidFrameworkUtils.h new file mode 100644 index 0000000000..d0f7d44fad --- /dev/null +++ b/gfx/skia/skia/include/android/SkAndroidFrameworkUtils.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAndroidFrameworkUtils_DEFINED +#define SkAndroidFrameworkUtils_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + +class SkCanvas; +struct SkRect; +class SkSurface; + +/** + * SkAndroidFrameworkUtils expose private APIs used only by Android framework. + */ +class SkAndroidFrameworkUtils { +public: + +#if SK_SUPPORT_GPU + /** + * clipWithStencil draws the current clip into a stencil buffer with reference value and mask + * set to 0x1. This function works only on a GPU canvas. + * + * @param canvas A GPU canvas that has a non-empty clip. + * + * @return true on success or false if clip is empty or not a GPU canvas. + */ + static bool clipWithStencil(SkCanvas* canvas); +#endif //SK_SUPPORT_GPU + + static void SafetyNetLog(const char*); + + static sk_sp getSurfaceFromCanvas(SkCanvas* canvas); + + static int SaveBehind(SkCanvas* canvas, const SkRect* subset); + + /** + * Unrolls a chain of nested SkPaintFilterCanvas to return the base wrapped canvas. + * + * @param canvas A SkPaintFilterCanvas or any other SkCanvas subclass. + * + * @return SkCanvas that was found in the innermost SkPaintFilterCanvas. + */ + static SkCanvas* getBaseWrappedCanvas(SkCanvas* canvas); +}; + +#endif // SK_BUILD_FOR_ANDROID_ANDROID + +#endif // SkAndroidFrameworkUtils_DEFINED diff --git a/gfx/skia/skia/include/android/SkAnimatedImage.h b/gfx/skia/skia/include/android/SkAnimatedImage.h new file mode 100644 index 0000000000..760dabde76 --- /dev/null +++ b/gfx/skia/skia/include/android/SkAnimatedImage.h @@ -0,0 +1,167 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAnimatedImage_DEFINED +#define SkAnimatedImage_DEFINED + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkBitmap.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" + +class SkAndroidCodec; +class SkPicture; + +/** + * Thread unsafe drawable for drawing animated images (e.g. GIF). + */ +class SK_API SkAnimatedImage : public SkDrawable { +public: + /** + * Create an SkAnimatedImage from the SkAndroidCodec. + * + * Returns null on failure to allocate pixels. On success, this will + * decode the first frame. + * + * @param info Width and height may require scaling. + * @param cropRect Rectangle to crop to after scaling. + * @param postProcess Picture to apply after scaling and cropping. + */ + static sk_sp Make(std::unique_ptr, + const SkImageInfo& info, SkIRect cropRect, sk_sp postProcess); + + /** + * Create an SkAnimatedImage from the SkAndroidCodec. + * + * Returns null on failure to allocate pixels. On success, this will + * decode the first frame. + * + * @param scaledSize Size to draw the image, possibly requiring scaling. + * @param cropRect Rectangle to crop to after scaling. + * @param postProcess Picture to apply after scaling and cropping. + */ + static sk_sp Make(std::unique_ptr, + SkISize scaledSize, SkIRect cropRect, sk_sp postProcess); + + /** + * Simpler version that uses the default size, no cropping, and no postProcess. + */ + static sk_sp Make(std::unique_ptr); + + ~SkAnimatedImage() override; + + /** + * Reset the animation to the beginning. + */ + void reset(); + + /** + * Whether the animation completed. + * + * Returns true after all repetitions are complete, or an error stops the + * animation. Gets reset to false if the animation is restarted. + */ + bool isFinished() const { return fFinished; } + + /** + * Returned by decodeNextFrame and currentFrameDuration if the animation + * is not running. + */ + static constexpr int kFinished = -1; + + /** + * Decode the next frame. + * + * If the animation is on the last frame or has hit an error, returns + * kFinished. + */ + int decodeNextFrame(); + + /** + * How long to display the current frame. + * + * Useful for the first frame, for which decodeNextFrame is called + * internally. + */ + int currentFrameDuration() { + return fCurrentFrameDuration; + } + + /** + * Change the repetition count. + * + * By default, the image will repeat the number of times indicated in the + * encoded data. + * + * Use SkCodec::kRepetitionCountInfinite for infinite, and 0 to show all + * frames once and then stop. + */ + void setRepetitionCount(int count); + + /** + * Return the currently set repetition count. + */ + int getRepetitionCount() const { + return fRepetitionCount; + } + +protected: + SkRect onGetBounds() override; + void onDraw(SkCanvas*) override; + +private: + struct Frame { + SkBitmap fBitmap; + int fIndex; + SkCodecAnimation::DisposalMethod fDisposalMethod; + + // init() may have to create a new SkPixelRef, if the + // current one is already in use by another owner (e.g. + // an SkPicture). This determines whether to copy the + // existing one to the new one. + enum class OnInit { + // Restore the image from the old SkPixelRef to the + // new one. + kRestoreIfNecessary, + // No need to restore. + kNoRestore, + }; + + Frame(); + bool init(const SkImageInfo& info, OnInit); + bool copyTo(Frame*) const; + }; + + std::unique_ptr fCodec; + const SkISize fScaledSize; + const SkImageInfo fDecodeInfo; + const SkIRect fCropRect; + const sk_sp fPostProcess; + const int fFrameCount; + const bool fSimple; // no crop, scale, or postprocess + SkMatrix fMatrix; // used only if !fSimple + + bool fFinished; + int fCurrentFrameDuration; + Frame fDisplayFrame; + Frame fDecodingFrame; + Frame fRestoreFrame; + int fRepetitionCount; + int fRepetitionsCompleted; + + SkAnimatedImage(std::unique_ptr, SkISize scaledSize, + SkImageInfo decodeInfo, SkIRect cropRect, sk_sp postProcess); + SkAnimatedImage(std::unique_ptr); + + int computeNextFrame(int current, bool* animationEnded); + double finish(); + + typedef SkDrawable INHERITED; +}; + +#endif // SkAnimatedImage_DEFINED diff --git a/gfx/skia/skia/include/android/SkBRDAllocator.h b/gfx/skia/skia/include/android/SkBRDAllocator.h new file mode 100644 index 0000000000..ae842b74b1 --- /dev/null +++ b/gfx/skia/skia/include/android/SkBRDAllocator.h @@ -0,0 +1,29 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBRDAllocator_DEFINED +#define SkBRDAllocator_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkBitmap.h" + +/** + * Abstract subclass of SkBitmap's allocator. + * Allows the allocator to indicate if the memory it allocates + * is zero initialized. + */ +class SkBRDAllocator : public SkBitmap::Allocator { +public: + + /** + * Indicates if the memory allocated by this allocator is + * zero initialized. + */ + virtual SkCodec::ZeroInitialized zeroInit() const = 0; +}; + +#endif // SkBRDAllocator_DEFINED diff --git a/gfx/skia/skia/include/android/SkBitmapRegionDecoder.h b/gfx/skia/skia/include/android/SkBitmapRegionDecoder.h new file mode 100644 index 0000000000..18ba90b774 --- /dev/null +++ b/gfx/skia/skia/include/android/SkBitmapRegionDecoder.h @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapRegionDecoder_DEFINED +#define SkBitmapRegionDecoder_DEFINED + +#include "include/android/SkBRDAllocator.h" +#include "include/core/SkBitmap.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkStream.h" + +/* + * This class aims to provide an interface to test multiple implementations of + * SkBitmapRegionDecoder. + */ +class SK_API SkBitmapRegionDecoder { +public: + + enum Strategy { + kAndroidCodec_Strategy, // Uses SkAndroidCodec for scaling and subsetting + }; + + /* + * @param data Refs the data while this object exists, unrefs on destruction + * @param strategy Strategy used for scaling and subsetting + * @return Tries to create an SkBitmapRegionDecoder, returns NULL on failure + */ + static SkBitmapRegionDecoder* Create(sk_sp, Strategy strategy); + + /* + * @param stream Takes ownership of the stream + * @param strategy Strategy used for scaling and subsetting + * @return Tries to create an SkBitmapRegionDecoder, returns NULL on failure + */ + static SkBitmapRegionDecoder* Create( + SkStreamRewindable* stream, Strategy strategy); + + /* + * Decode a scaled region of the encoded image stream + * + * @param bitmap Container for decoded pixels. It is assumed that the pixels + * are initially unallocated and will be allocated by this function. + * @param allocator Allocator for the pixels. If this is NULL, the default + * allocator (HeapAllocator) will be used. + * @param desiredSubset Subset of the original image to decode. + * @param sampleSize An integer downscaling factor for the decode. + * @param colorType Preferred output colorType. + * New implementations should return NULL if they do not support + * decoding to this color type. + * The old kOriginal_Strategy will decode to a default color type + * if this color type is unsupported. + * @param requireUnpremul If the image is not opaque, we will use this to determine the + * alpha type to use. + * @param prefColorSpace If non-null and supported, this is the color space that we will + * decode into. Otherwise, we will choose a default. + * + */ + virtual bool decodeRegion(SkBitmap* bitmap, SkBRDAllocator* allocator, + const SkIRect& desiredSubset, int sampleSize, + SkColorType colorType, bool requireUnpremul, + sk_sp prefColorSpace = nullptr) = 0; + + virtual SkEncodedImageFormat getEncodedFormat() = 0; + + virtual SkColorType computeOutputColorType(SkColorType requestedColorType) = 0; + + virtual sk_sp computeOutputColorSpace(SkColorType outputColorType, + sk_sp prefColorSpace = nullptr) = 0; + + + int width() const { return fWidth; } + int height() const { return fHeight; } + + virtual ~SkBitmapRegionDecoder() {} + +protected: + + SkBitmapRegionDecoder(int width, int height) + : fWidth(width) + , fHeight(height) + {} + +private: + const int fWidth; + const int fHeight; +}; + +#endif diff --git a/gfx/skia/skia/include/atlastext/SkAtlasTextContext.h b/gfx/skia/skia/include/atlastext/SkAtlasTextContext.h new file mode 100644 index 0000000000..d13ec51e23 --- /dev/null +++ b/gfx/skia/skia/include/atlastext/SkAtlasTextContext.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAtlasTextContext_DEFINED +#define SkAtlasTextContext_DEFINED + +#include "include/core/SkRefCnt.h" + +class SkAtlasTextRenderer; +class SkInternalAtlasTextContext; + +SkAtlasTextRenderer* SkGetAtlasTextRendererFromInternalContext(class SkInternalAtlasTextContext&); + +/** + * Class that Atlas Text client uses to register their SkAtlasTextRenderer implementation and + * to create one or more SkAtlasTextTargets (destination surfaces for text rendering). + */ +class SK_API SkAtlasTextContext : public SkRefCnt { +public: + static sk_sp Make(sk_sp); + + SkAtlasTextRenderer* renderer() const { + return SkGetAtlasTextRendererFromInternalContext(*fInternalContext); + } + + SkInternalAtlasTextContext& internal() { return *fInternalContext; } + +private: + SkAtlasTextContext() = delete; + SkAtlasTextContext(const SkAtlasTextContext&) = delete; + SkAtlasTextContext& operator=(const SkAtlasTextContext&) = delete; + + SkAtlasTextContext(sk_sp); + + std::unique_ptr fInternalContext; +}; + +#endif diff --git a/gfx/skia/skia/include/atlastext/SkAtlasTextFont.h b/gfx/skia/skia/include/atlastext/SkAtlasTextFont.h new file mode 100644 index 0000000000..8bab5bfc85 --- /dev/null +++ b/gfx/skia/skia/include/atlastext/SkAtlasTextFont.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAtlasTextFont_DEFINED +#define SkAtlasTextFont_DEFINED + +#include "include/core/SkFont.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypeface.h" + +/** Represents a font at a size. TODO: What else do we need here (skewX, scaleX, vertical, ...)? */ +class SK_API SkAtlasTextFont : public SkRefCnt { +public: + static sk_sp Make(sk_sp typeface, SkScalar size) { + return sk_sp(new SkAtlasTextFont(std::move(typeface), size)); + } + + SkTypeface* typeface() const { return fTypeface.get(); } + + sk_sp refTypeface() const { return fTypeface; } + + SkScalar size() const { return fSize; } + + SkFont makeFont() const { return SkFont(fTypeface, fSize); } + +private: + SkAtlasTextFont(sk_sp typeface, SkScalar size) + : fTypeface(std::move(typeface)), fSize(size) {} + + sk_sp fTypeface; + SkScalar fSize; +}; + +#endif diff --git a/gfx/skia/skia/include/atlastext/SkAtlasTextRenderer.h b/gfx/skia/skia/include/atlastext/SkAtlasTextRenderer.h new file mode 100644 index 0000000000..e572bb6e52 --- /dev/null +++ b/gfx/skia/skia/include/atlastext/SkAtlasTextRenderer.h @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPoint3.h" +#include "include/core/SkRefCnt.h" + +#ifndef SkAtlasTextRenderer_DEFINED +#define SkAtlasTextRenderer_DEFINED + +/** + * This is the base class for a renderer implemented by the SkAtlasText client. The + * SkAtlasTextContext issues texture creations, deletions, uploads, and vertex draws to the + * renderer. The renderer must perform those actions in the order called to correctly render + * the text drawn to SkAtlasTextTargets. + */ +class SK_API SkAtlasTextRenderer : public SkRefCnt { +public: + enum class AtlasFormat { + /** Unsigned normalized 8 bit single channel format. */ + kA8 + }; + + struct SDFVertex { + /** Position in device space (not normalized). The third component is w (not z). */ + SkPoint3 fPosition; + /** Color, same value for all four corners of a glyph quad. */ + uint32_t fColor; + /** Texture coordinate (in texel units, not normalized). */ + int16_t fTextureCoordX; + int16_t fTextureCoordY; + }; + + virtual ~SkAtlasTextRenderer() = default; + + /** + * Create a texture of the provided format with dimensions 'width' x 'height' + * and return a unique handle. + */ + virtual void* createTexture(AtlasFormat, int width, int height) = 0; + + /** + * Delete the texture with the passed handle. + */ + virtual void deleteTexture(void* textureHandle) = 0; + + /** + * Place the pixel data specified by 'data' in the texture with handle + * 'textureHandle' in the rectangle ['x', 'x' + 'width') x ['y', 'y' + 'height'). + * 'rowBytes' specifies the byte offset between successive rows in 'data' and will always be + * a multiple of the number of bytes per pixel. + * The pixel format of data is the same as that of 'textureHandle'. + */ + virtual void setTextureData(void* textureHandle, const void* data, int x, int y, int width, + int height, size_t rowBytes) = 0; + + /** + * Draws glyphs using SDFs. The SDF data resides in 'textureHandle'. The array + * 'vertices' provides interleaved device-space positions, colors, and + * texture coordinates. There are are 4 * 'quadCnt' entries in 'vertices'. + */ + virtual void drawSDFGlyphs(void* targetHandle, void* textureHandle, const SDFVertex vertices[], + int quadCnt) = 0; + + /** Called when a SkAtlasTextureTarget is destroyed. */ + virtual void targetDeleted(void* targetHandle) = 0; +}; + +#endif diff --git a/gfx/skia/skia/include/atlastext/SkAtlasTextTarget.h b/gfx/skia/skia/include/atlastext/SkAtlasTextTarget.h new file mode 100644 index 0000000000..b90dad6fec --- /dev/null +++ b/gfx/skia/skia/include/atlastext/SkAtlasTextTarget.h @@ -0,0 +1,100 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAtlasTextTarget_DEFINED +#define SkAtlasTextTarget_DEFINED + +#include "include/core/SkDeque.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" + +#include + +class SkAtlasTextContext; +class SkAtlasTextFont; +class SkMatrix; +struct SkPoint; + +/** Represents a client-created renderable surface and is used to draw text into the surface. */ +class SK_API SkAtlasTextTarget { +public: + virtual ~SkAtlasTextTarget(); + + /** + * Creates a text drawing target. ‘handle’ is used to identify this rendering surface when + * draws are flushed to the SkAtlasTextContext's SkAtlasTextRenderer. + */ + static std::unique_ptr Make(sk_sp, + int width, + int height, + void* handle); + + /** + * Enqueues a text draw in the target. The caller provides an array of glyphs and their + * positions. The meaning of 'color' here is interpreted by the client's SkAtlasTextRenderer + * when it actually renders the text. + */ + virtual void drawText(const SkGlyphID[], const SkPoint[], int glyphCnt, uint32_t color, + const SkAtlasTextFont&) = 0; + + /** Issues all queued text draws to SkAtlasTextRenderer. */ + virtual void flush() = 0; + + int width() const { return fWidth; } + int height() const { return fHeight; } + + void* handle() const { return fHandle; } + + SkAtlasTextContext* context() const { return fContext.get(); } + + /** Saves the current matrix in a stack. Returns the prior depth of the saved matrix stack. */ + int save(); + /** Pops the top matrix on the stack if the stack is not empty. */ + void restore(); + /** + * Pops the matrix stack until the stack depth is count. Does nothing if the depth is already + * less than count. + */ + void restoreToCount(int count); + + /** Pre-translates the current CTM. */ + void translate(SkScalar dx, SkScalar dy); + /** Pre-scales the current CTM. */ + void scale(SkScalar sx, SkScalar sy); + /** Pre-rotates the current CTM about the origin. */ + void rotate(SkScalar degrees); + /** Pre-rotates the current CTM about the (px, py). */ + void rotate(SkScalar degrees, SkScalar px, SkScalar py); + /** Pre-skews the current CTM. */ + void skew(SkScalar sx, SkScalar sy); + /** Pre-concats the current CTM. */ + void concat(const SkMatrix& matrix); + +protected: + SkAtlasTextTarget(sk_sp, int width, int height, void* handle); + + const SkMatrix& ctm() const { return *static_cast(fMatrixStack.back()); } + + void* const fHandle; + const sk_sp fContext; + const int fWidth; + const int fHeight; + +private: + SkDeque fMatrixStack; + int fSaveCnt; + + SkMatrix* accessCTM() const { + return static_cast(const_cast(fMatrixStack.back())); + } + + SkAtlasTextTarget() = delete; + SkAtlasTextTarget(const SkAtlasTextContext&) = delete; + SkAtlasTextTarget& operator=(const SkAtlasTextContext&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/c/sk_canvas.h b/gfx/skia/skia/include/c/sk_canvas.h new file mode 100644 index 0000000000..c739ed4cff --- /dev/null +++ b/gfx/skia/skia/include/c/sk_canvas.h @@ -0,0 +1,159 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_canvas_DEFINED +#define sk_canvas_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + Save the current matrix and clip on the canvas. When the + balancing call to sk_canvas_restore() is made, the previous matrix + and clip are restored. +*/ +SK_API void sk_canvas_save(sk_canvas_t*); +/** + This behaves the same as sk_canvas_save(), but in addition it + allocates an offscreen surface. All drawing calls are directed + there, and only when the balancing call to sk_canvas_restore() is + made is that offscreen transfered to the canvas (or the previous + layer). + + @param sk_rect_t* (may be null) This rect, if non-null, is used as + a hint to limit the size of the offscreen, and + thus drawing may be clipped to it, though that + clipping is not guaranteed to happen. If exact + clipping is desired, use sk_canvas_clip_rect(). + @param sk_paint_t* (may be null) The paint is copied, and is applied + to the offscreen when sk_canvas_restore() is + called. +*/ +SK_API void sk_canvas_save_layer(sk_canvas_t*, const sk_rect_t*, const sk_paint_t*); +/** + This call balances a previous call to sk_canvas_save() or + sk_canvas_save_layer(), and is used to remove all modifications to + the matrix and clip state since the last save call. It is an + error to call sk_canvas_restore() more times than save and + save_layer were called. +*/ +SK_API void sk_canvas_restore(sk_canvas_t*); + +/** + Preconcat the current coordinate transformation matrix with the + specified translation. +*/ +SK_API void sk_canvas_translate(sk_canvas_t*, float dx, float dy); +/** + Preconcat the current coordinate transformation matrix with the + specified scale. +*/ +SK_API void sk_canvas_scale(sk_canvas_t*, float sx, float sy); +/** + Preconcat the current coordinate transformation matrix with the + specified rotation in degrees. +*/ +SK_API void sk_canvas_rotate_degrees(sk_canvas_t*, float degrees); +/** + Preconcat the current coordinate transformation matrix with the + specified rotation in radians. +*/ +SK_API void sk_canvas_rotate_radians(sk_canvas_t*, float radians); +/** + Preconcat the current coordinate transformation matrix with the + specified skew. +*/ +SK_API void sk_canvas_skew(sk_canvas_t*, float sx, float sy); +/** + Preconcat the current coordinate transformation matrix with the + specified matrix. +*/ +SK_API void sk_canvas_concat(sk_canvas_t*, const sk_matrix_t*); + +/** + Modify the current clip with the specified rectangle. The new + current clip will be the intersection of the old clip and the + rectange. +*/ +SK_API void sk_canvas_clip_rect(sk_canvas_t*, const sk_rect_t*); +/** + Modify the current clip with the specified path. The new + current clip will be the intersection of the old clip and the + path. +*/ +SK_API void sk_canvas_clip_path(sk_canvas_t*, const sk_path_t*); + +/** + Fill the entire canvas (restricted to the current clip) with the + specified paint. +*/ +SK_API void sk_canvas_draw_paint(sk_canvas_t*, const sk_paint_t*); +/** + Draw the specified rectangle using the specified paint. The + rectangle will be filled or stroked based on the style in the + paint. +*/ +SK_API void sk_canvas_draw_rect(sk_canvas_t*, const sk_rect_t*, const sk_paint_t*); +/** + * Draw the circle centered at (cx, cy) with radius rad using the specified paint. + * The circle will be filled or framed based on the style in the paint + */ +SK_API void sk_canvas_draw_circle(sk_canvas_t*, float cx, float cy, float rad, const sk_paint_t*); +/** + Draw the specified oval using the specified paint. The oval will be + filled or framed based on the style in the paint +*/ +SK_API void sk_canvas_draw_oval(sk_canvas_t*, const sk_rect_t*, const sk_paint_t*); +/** + Draw the specified path using the specified paint. The path will be + filled or framed based on the style in the paint +*/ +SK_API void sk_canvas_draw_path(sk_canvas_t*, const sk_path_t*, const sk_paint_t*); +/** + Draw the specified image, with its top/left corner at (x,y), using + the specified paint, transformed by the current matrix. + + @param sk_paint_t* (may be NULL) the paint used to draw the image. +*/ +SK_API void sk_canvas_draw_image(sk_canvas_t*, const sk_image_t*, + float x, float y, const sk_paint_t*); +/** + Draw the specified image, scaling and translating so that it fills + the specified dst rect. If the src rect is non-null, only that + subset of the image is transformed and drawn. + + @param sk_paint_t* (may be NULL) The paint used to draw the image. +*/ +SK_API void sk_canvas_draw_image_rect(sk_canvas_t*, const sk_image_t*, + const sk_rect_t* src, + const sk_rect_t* dst, const sk_paint_t*); + +/** + Draw the picture into this canvas (replay the pciture's drawing commands). + + @param sk_matrix_t* If non-null, apply that matrix to the CTM when + drawing this picture. This is logically + equivalent to: save, concat, draw_picture, + restore. + + @param sk_paint_t* If non-null, draw the picture into a temporary + buffer, and then apply the paint's alpha, + colorfilter, imagefilter, and xfermode to that + buffer as it is drawn to the canvas. This is + logically equivalent to save_layer(paint), + draw_picture, restore. +*/ +SK_API void sk_canvas_draw_picture(sk_canvas_t*, const sk_picture_t*, + const sk_matrix_t*, const sk_paint_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_colorspace.h b/gfx/skia/skia/include/c/sk_colorspace.h new file mode 100644 index 0000000000..31839840d0 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_colorspace.h @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_colorspace_DEFINED +#define sk_colorspace_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +SK_API sk_colorspace_t* sk_colorspace_new_srgb(); + +SK_API void sk_colorspace_ref(sk_colorspace_t*); +SK_API void sk_colorspace_unref(sk_colorspace_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_data.h b/gfx/skia/skia/include/c/sk_data.h new file mode 100644 index 0000000000..27c51c4136 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_data.h @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_data_DEFINED +#define sk_data_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + Returns a new empty sk_data_t. This call must be balanced with a call to + sk_data_unref(). +*/ +SK_API sk_data_t* sk_data_new_empty(void); +/** + Returns a new sk_data_t by copying the specified source data. + This call must be balanced with a call to sk_data_unref(). +*/ +SK_API sk_data_t* sk_data_new_with_copy(const void* src, size_t length); +/** + Pass ownership of the given memory to a new sk_data_t, which will + call free() when the refernce count of the data goes to zero. For + example: + size_t length = 1024; + void* buffer = malloc(length); + memset(buffer, 'X', length); + sk_data_t* data = sk_data_new_from_malloc(buffer, length); + This call must be balanced with a call to sk_data_unref(). +*/ +SK_API sk_data_t* sk_data_new_from_malloc(const void* memory, size_t length); +/** + Returns a new sk_data_t using a subset of the data in the + specified source sk_data_t. This call must be balanced with a + call to sk_data_unref(). +*/ +SK_API sk_data_t* sk_data_new_subset(const sk_data_t* src, size_t offset, size_t length); + +/** + Increment the reference count on the given sk_data_t. Must be + balanced by a call to sk_data_unref(). +*/ +SK_API void sk_data_ref(const sk_data_t*); +/** + Decrement the reference count. If the reference count is 1 before + the decrement, then release both the memory holding the sk_data_t + and the memory it is managing. New sk_data_t are created with a + reference count of 1. +*/ +SK_API void sk_data_unref(const sk_data_t*); + +/** + Returns the number of bytes stored. +*/ +SK_API size_t sk_data_get_size(const sk_data_t*); +/** + Returns the pointer to the data. + */ +SK_API const void* sk_data_get_data(const sk_data_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_image.h b/gfx/skia/skia/include/c/sk_image.h new file mode 100644 index 0000000000..5106d40a62 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_image.h @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_image_DEFINED +#define sk_image_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + * Return a new image that has made a copy of the provided pixels, or NULL on failure. + * Balance with a call to sk_image_unref(). + */ +SK_API sk_image_t* sk_image_new_raster_copy(const sk_imageinfo_t*, const void* pixels, size_t rowBytes); + +/** + * If the specified data can be interpreted as a compressed image (e.g. PNG or JPEG) then this + * returns an image. If the encoded data is not supported, returns NULL. + * + * On success, the encoded data may be processed immediately, or it may be ref()'d for later + * use. + */ +SK_API sk_image_t* sk_image_new_from_encoded(const sk_data_t* encoded, const sk_irect_t* subset); + +/** + * Encode the image's pixels and return the result as a new PNG in a + * sk_data_t, which the caller must manage: call sk_data_unref() when + * they are done. + * + * If the image type cannot be encoded, this will return NULL. + */ +SK_API sk_data_t* sk_image_encode(const sk_image_t*); + +/** + * Increment the reference count on the given sk_image_t. Must be + * balanced by a call to sk_image_unref(). +*/ +SK_API void sk_image_ref(const sk_image_t*); +/** + * Decrement the reference count. If the reference count is 1 before + * the decrement, then release both the memory holding the sk_image_t + * and the memory it is managing. New sk_image_t are created with a + reference count of 1. +*/ +SK_API void sk_image_unref(const sk_image_t*); + +/** + * Return the width of the sk_image_t/ + */ +SK_API int sk_image_get_width(const sk_image_t*); +/** + * Return the height of the sk_image_t/ + */ +SK_API int sk_image_get_height(const sk_image_t*); + +/** + * Returns a non-zero value unique among all images. + */ +SK_API uint32_t sk_image_get_unique_id(const sk_image_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_imageinfo.h b/gfx/skia/skia/include/c/sk_imageinfo.h new file mode 100644 index 0000000000..6c8e9fff24 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_imageinfo.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_imageinfo_DEFINED +#define sk_imageinfo_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +typedef enum { + UNKNOWN_SK_COLORTYPE, + RGBA_8888_SK_COLORTYPE, + BGRA_8888_SK_COLORTYPE, + ALPHA_8_SK_COLORTYPE, + GRAY_8_SK_COLORTYPE, + RGBA_F16_SK_COLORTYPE, + RGBA_F32_SK_COLORTYPE, +} sk_colortype_t; + +typedef enum { + OPAQUE_SK_ALPHATYPE, + PREMUL_SK_ALPHATYPE, + UNPREMUL_SK_ALPHATYPE, +} sk_alphatype_t; + +/** + * Allocate a new imageinfo object. If colorspace is not null, it's owner-count will be + * incremented automatically. + */ +SK_API sk_imageinfo_t* sk_imageinfo_new(int width, int height, sk_colortype_t ct, sk_alphatype_t at, + sk_colorspace_t* cs); + +/** + * Free the imageinfo object. If it contains a reference to a colorspace, its owner-count will + * be decremented automatically. + */ +SK_API void sk_imageinfo_delete(sk_imageinfo_t*); + +SK_API int32_t sk_imageinfo_get_width(const sk_imageinfo_t*); +SK_API int32_t sk_imageinfo_get_height(const sk_imageinfo_t*); +SK_API sk_colortype_t sk_imageinfo_get_colortype(const sk_imageinfo_t*); +SK_API sk_alphatype_t sk_imageinfo_get_alphatype(const sk_imageinfo_t*); + +/** + * Return the colorspace object reference contained in the imageinfo, or null if there is none. + * Note: this does not modify the owner-count on the colorspace object. If the caller needs to + * use the colorspace beyond the lifetime of the imageinfo, it should manually call + * sk_colorspace_ref() (and then call unref() when it is done). + */ +SK_API sk_colorspace_t* sk_imageinfo_get_colorspace(const sk_imageinfo_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_maskfilter.h b/gfx/skia/skia/include/c/sk_maskfilter.h new file mode 100644 index 0000000000..c8aa7ed445 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_maskfilter.h @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_maskfilter_DEFINED +#define sk_maskfilter_DEFINED + +#include "include/c/sk_types.h" + +typedef enum { + NORMAL_SK_BLUR_STYLE, //!< fuzzy inside and outside + SOLID_SK_BLUR_STYLE, //!< solid inside, fuzzy outside + OUTER_SK_BLUR_STYLE, //!< nothing inside, fuzzy outside + INNER_SK_BLUR_STYLE, //!< fuzzy inside, nothing outside +} sk_blurstyle_t; + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + Increment the reference count on the given sk_maskfilter_t. Must be + balanced by a call to sk_maskfilter_unref(). +*/ +SK_API void sk_maskfilter_ref(sk_maskfilter_t*); +/** + Decrement the reference count. If the reference count is 1 before + the decrement, then release both the memory holding the + sk_maskfilter_t and any other associated resources. New + sk_maskfilter_t are created with a reference count of 1. +*/ +SK_API void sk_maskfilter_unref(sk_maskfilter_t*); + +/** + Create a blur maskfilter. + @param sk_blurstyle_t The SkBlurStyle to use + @param sigma Standard deviation of the Gaussian blur to apply. Must be > 0. +*/ +SK_API sk_maskfilter_t* sk_maskfilter_new_blur(sk_blurstyle_t, float sigma); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_matrix.h b/gfx/skia/skia/include/c/sk_matrix.h new file mode 100644 index 0000000000..244863c4f2 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_matrix.h @@ -0,0 +1,49 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_matrix_DEFINED +#define sk_matrix_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** Set the matrix to identity */ +void sk_matrix_set_identity(sk_matrix_t*); + +/** Set the matrix to translate by (tx, ty). */ +void sk_matrix_set_translate(sk_matrix_t*, float tx, float ty); +/** + Preconcats the matrix with the specified translation. + M' = M * T(dx, dy) +*/ +void sk_matrix_pre_translate(sk_matrix_t*, float tx, float ty); +/** + Postconcats the matrix with the specified translation. + M' = T(dx, dy) * M +*/ +void sk_matrix_post_translate(sk_matrix_t*, float tx, float ty); + +/** Set the matrix to scale by sx and sy. */ +void sk_matrix_set_scale(sk_matrix_t*, float sx, float sy); +/** + Preconcats the matrix with the specified scale. + M' = M * S(sx, sy) +*/ +void sk_matrix_pre_scale(sk_matrix_t*, float sx, float sy); +/** + Postconcats the matrix with the specified scale. + M' = S(sx, sy) * M +*/ +void sk_matrix_post_scale(sk_matrix_t*, float sx, float sy); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_paint.h b/gfx/skia/skia/include/c/sk_paint.h new file mode 100644 index 0000000000..98ba4954c1 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_paint.h @@ -0,0 +1,145 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_paint_DEFINED +#define sk_paint_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + Create a new paint with default settings: + antialias : false + stroke : false + stroke width : 0.0f (hairline) + stroke miter : 4.0f + stroke cap : BUTT_SK_STROKE_CAP + stroke join : MITER_SK_STROKE_JOIN + color : opaque black + shader : NULL + maskfilter : NULL + xfermode_mode : SRCOVER_SK_XFERMODE_MODE +*/ +SK_API sk_paint_t* sk_paint_new(void); +/** + Release the memory storing the sk_paint_t and unref() all + associated objects. +*/ +SK_API void sk_paint_delete(sk_paint_t*); + +/** + Return true iff the paint has antialiasing enabled. +*/ +SK_API bool sk_paint_is_antialias(const sk_paint_t*); +/** + Set to true to enable antialiasing, false to disable it on this + sk_paint_t. +*/ +SK_API void sk_paint_set_antialias(sk_paint_t*, bool); + +/** + Return the paint's curent drawing color. +*/ +SK_API sk_color_t sk_paint_get_color(const sk_paint_t*); +/** + Set the paint's curent drawing color. +*/ +SK_API void sk_paint_set_color(sk_paint_t*, sk_color_t); + +/* stroke settings */ + +/** + Return true iff stroking is enabled rather than filling on this + sk_paint_t. +*/ +SK_API bool sk_paint_is_stroke(const sk_paint_t*); +/** + Set to true to enable stroking rather than filling with this + sk_paint_t. +*/ +SK_API void sk_paint_set_stroke(sk_paint_t*, bool); + +/** + Return the width for stroking. A value of 0 strokes in hairline mode. + */ +SK_API float sk_paint_get_stroke_width(const sk_paint_t*); +/** + Set the width for stroking. A value of 0 strokes in hairline mode + (always draw 1-pixel wide, regardless of the matrix). + */ +SK_API void sk_paint_set_stroke_width(sk_paint_t*, float width); + +/** + Return the paint's stroke miter value. This is used to control the + behavior of miter joins when the joins angle is sharp. +*/ +SK_API float sk_paint_get_stroke_miter(const sk_paint_t*); +/** + Set the paint's stroke miter value. This is used to control the + behavior of miter joins when the joins angle is sharp. This value + must be >= 0. +*/ +SK_API void sk_paint_set_stroke_miter(sk_paint_t*, float miter); + +typedef enum { + BUTT_SK_STROKE_CAP, + ROUND_SK_STROKE_CAP, + SQUARE_SK_STROKE_CAP +} sk_stroke_cap_t; + +/** + Return the paint's stroke cap type, controlling how the start and + end of stroked lines and paths are treated. +*/ +SK_API sk_stroke_cap_t sk_paint_get_stroke_cap(const sk_paint_t*); +/** + Set the paint's stroke cap type, controlling how the start and + end of stroked lines and paths are treated. +*/ +SK_API void sk_paint_set_stroke_cap(sk_paint_t*, sk_stroke_cap_t); + +typedef enum { + MITER_SK_STROKE_JOIN, + ROUND_SK_STROKE_JOIN, + BEVEL_SK_STROKE_JOIN +} sk_stroke_join_t; + +/** + Return the paint's stroke join type, specifies the treatment that + is applied to corners in paths and rectangles + */ +SK_API sk_stroke_join_t sk_paint_get_stroke_join(const sk_paint_t*); +/** + Set the paint's stroke join type, specifies the treatment that + is applied to corners in paths and rectangles + */ +SK_API void sk_paint_set_stroke_join(sk_paint_t*, sk_stroke_join_t); + +/** + * Set the paint's shader to the specified parameter. This will automatically call unref() on + * any previous value, and call ref() on the new value. + */ +SK_API void sk_paint_set_shader(sk_paint_t*, sk_shader_t*); + +/** + * Set the paint's maskfilter to the specified parameter. This will automatically call unref() on + * any previous value, and call ref() on the new value. + */ +SK_API void sk_paint_set_maskfilter(sk_paint_t*, sk_maskfilter_t*); + +/** + * Set the paint's xfermode to the specified parameter. + */ +SK_API void sk_paint_set_xfermode_mode(sk_paint_t*, sk_xfermode_mode_t); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_path.h b/gfx/skia/skia/include/c/sk_path.h new file mode 100644 index 0000000000..3898b629f3 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_path.h @@ -0,0 +1,84 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_path_DEFINED +#define sk_path_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +typedef enum { + CW_SK_PATH_DIRECTION, + CCW_SK_PATH_DIRECTION, +} sk_path_direction_t; + +/** Create a new, empty path. */ +SK_API sk_path_t* sk_path_new(void); +/** Release the memory used by a sk_path_t. */ +SK_API void sk_path_delete(sk_path_t*); + +/** Set the beginning of the next contour to the point (x,y). */ +SK_API void sk_path_move_to(sk_path_t*, float x, float y); +/** + Add a line from the last point to the specified point (x,y). If no + sk_path_move_to() call has been made for this contour, the first + point is automatically set to (0,0). +*/ +SK_API void sk_path_line_to(sk_path_t*, float x, float y); +/** + Add a quadratic bezier from the last point, approaching control + point (x0,y0), and ending at (x1,y1). If no sk_path_move_to() call + has been made for this contour, the first point is automatically + set to (0,0). +*/ +SK_API void sk_path_quad_to(sk_path_t*, float x0, float y0, float x1, float y1); +/** + Add a conic curve from the last point, approaching control point + (x0,y01), and ending at (x1,y1) with weight w. If no + sk_path_move_to() call has been made for this contour, the first + point is automatically set to (0,0). +*/ +SK_API void sk_path_conic_to(sk_path_t*, float x0, float y0, float x1, float y1, float w); +/** + Add a cubic bezier from the last point, approaching control points + (x0,y0) and (x1,y1), and ending at (x2,y2). If no + sk_path_move_to() call has been made for this contour, the first + point is automatically set to (0,0). +*/ +SK_API void sk_path_cubic_to(sk_path_t*, + float x0, float y0, + float x1, float y1, + float x2, float y2); +/** + Close the current contour. If the current point is not equal to the + first point of the contour, a line segment is automatically added. +*/ +SK_API void sk_path_close(sk_path_t*); + +/** + Add a closed rectangle contour to the path. +*/ +SK_API void sk_path_add_rect(sk_path_t*, const sk_rect_t*, sk_path_direction_t); +/** + Add a closed oval contour to the path +*/ +SK_API void sk_path_add_oval(sk_path_t*, const sk_rect_t*, sk_path_direction_t); + +/** + * If the path is empty, return false and set the rect parameter to [0, 0, 0, 0]. + * else return true and set the rect parameter to the bounds of the control-points + * of the path. + */ +SK_API bool sk_path_get_bounds(const sk_path_t*, sk_rect_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_picture.h b/gfx/skia/skia/include/c/sk_picture.h new file mode 100644 index 0000000000..e569104074 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_picture.h @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_picture_DEFINED +#define sk_picture_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + Create a new sk_picture_recorder_t. Its resources should be + released with a call to sk_picture_recorder_delete(). +*/ +SK_API sk_picture_recorder_t* sk_picture_recorder_new(void); +/** + Release the memory and other resources used by this + sk_picture_recorder_t. +*/ +SK_API void sk_picture_recorder_delete(sk_picture_recorder_t*); + +/** + Returns the canvas that records the drawing commands + + @param sk_rect_t* the cull rect used when recording this + picture. Any drawing the falls outside of this + rect is undefined, and may be drawn or it may not. +*/ +SK_API sk_canvas_t* sk_picture_recorder_begin_recording(sk_picture_recorder_t*, const sk_rect_t*); +/** + Signal that the caller is done recording. This invalidates the + canvas returned by begin_recording. Ownership of the sk_picture_t + is passed to the caller, who must call sk_picture_unref() when + they are done using it. The returned picture is immutable. +*/ +SK_API sk_picture_t* sk_picture_recorder_end_recording(sk_picture_recorder_t*); + +/** + Increment the reference count on the given sk_picture_t. Must be + balanced by a call to sk_picture_unref(). +*/ +SK_API void sk_picture_ref(sk_picture_t*); +/** + Decrement the reference count. If the reference count is 1 before + the decrement, then release both the memory holding the + sk_picture_t and any resouces it may be managing. New + sk_picture_t are created with a reference count of 1. +*/ +SK_API void sk_picture_unref(sk_picture_t*); + +/** + Returns a non-zero value unique among all pictures. + */ +SK_API uint32_t sk_picture_get_unique_id(sk_picture_t*); + +/** + Return the cull rect specified when this picture was recorded. +*/ +SK_API sk_rect_t sk_picture_get_bounds(sk_picture_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_shader.h b/gfx/skia/skia/include/c/sk_shader.h new file mode 100644 index 0000000000..023ccbaeac --- /dev/null +++ b/gfx/skia/skia/include/c/sk_shader.h @@ -0,0 +1,143 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_shader_DEFINED +#define sk_shader_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +SK_API void sk_shader_ref(sk_shader_t*); +SK_API void sk_shader_unref(sk_shader_t*); + +typedef enum { + CLAMP_SK_SHADER_TILEMODE, + REPEAT_SK_SHADER_TILEMODE, + MIRROR_SK_SHADER_TILEMODE, +} sk_shader_tilemode_t; + +/** + Returns a shader that generates a linear gradient between the two + specified points. + + @param points The start and end points for the gradient. + @param colors The array[count] of colors, to be distributed between + the two points + @param colorPos May be NULL. array[count] of SkScalars, or NULL, of + the relative position of each corresponding color + in the colors array. If this is NULL, the the + colors are distributed evenly between the start + and end point. If this is not null, the values + must begin with 0, end with 1.0, and intermediate + values must be strictly increasing. + @param colorCount Must be >=2. The number of colors (and pos if not + NULL) entries. + @param mode The tiling mode +*/ +SK_API sk_shader_t* sk_shader_new_linear_gradient(const sk_point_t points[2], + const sk_color_t colors[], + const float colorPos[], + int colorCount, + sk_shader_tilemode_t tileMode, + const sk_matrix_t* localMatrix); + + +/** + Returns a shader that generates a radial gradient given the center + and radius. + + @param center The center of the circle for this gradient + @param radius Must be positive. The radius of the circle for this + gradient + @param colors The array[count] of colors, to be distributed + between the center and edge of the circle + @param colorPos May be NULL. The array[count] of the relative + position of each corresponding color in the colors + array. If this is NULL, the the colors are + distributed evenly between the center and edge of + the circle. If this is not null, the values must + begin with 0, end with 1.0, and intermediate + values must be strictly increasing. + @param count Must be >= 2. The number of colors (and pos if not + NULL) entries + @param tileMode The tiling mode + @param localMatrix May be NULL +*/ +SK_API sk_shader_t* sk_shader_new_radial_gradient(const sk_point_t* center, + float radius, + const sk_color_t colors[], + const float colorPos[], + int colorCount, + sk_shader_tilemode_t tileMode, + const sk_matrix_t* localMatrix); + +/** + Returns a shader that generates a sweep gradient given a center. + + @param center The coordinates of the center of the sweep + @param colors The array[count] of colors, to be distributed around + the center. + @param colorPos May be NULL. The array[count] of the relative + position of each corresponding color in the colors + array. If this is NULL, the the colors are + distributed evenly between the center and edge of + the circle. If this is not null, the values must + begin with 0, end with 1.0, and intermediate + values must be strictly increasing. + @param colorCount Must be >= 2. The number of colors (and pos if + not NULL) entries + @param localMatrix May be NULL +*/ +SK_API sk_shader_t* sk_shader_new_sweep_gradient(const sk_point_t* center, + const sk_color_t colors[], + const float colorPos[], + int colorCount, + const sk_matrix_t* localMatrix); + +/** + Returns a shader that generates a conical gradient given two circles, or + returns NULL if the inputs are invalid. The gradient interprets the + two circles according to the following HTML spec. + http://dev.w3.org/html5/2dcontext/#dom-context-2d-createradialgradient + + Returns a shader that generates a sweep gradient given a center. + + @param start, startRadius Defines the first circle. + @param end, endRadius Defines the first circle. + @param colors The array[count] of colors, to be distributed between + the two circles. + @param colorPos May be NULL. The array[count] of the relative + position of each corresponding color in the colors + array. If this is NULL, the the colors are + distributed evenly between the two circles. If + this is not null, the values must begin with 0, + end with 1.0, and intermediate values must be + strictly increasing. + @param colorCount Must be >= 2. The number of colors (and pos if + not NULL) entries + @param tileMode The tiling mode + @param localMatrix May be NULL + +*/ +SK_API sk_shader_t* sk_shader_new_two_point_conical_gradient( + const sk_point_t* start, + float startRadius, + const sk_point_t* end, + float endRadius, + const sk_color_t colors[], + const float colorPos[], + int colorCount, + sk_shader_tilemode_t tileMode, + const sk_matrix_t* localMatrix); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_surface.h b/gfx/skia/skia/include/c/sk_surface.h new file mode 100644 index 0000000000..88c8c87b32 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_surface.h @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_surface_DEFINED +#define sk_surface_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +/** + Return a new surface, with the memory for the pixels automatically + allocated. If the requested surface cannot be created, or the + request is not a supported configuration, NULL will be returned. + + @param sk_imageinfo_t* Specify the width, height, color type, and + alpha type for the surface. + + @param sk_surfaceprops_t* If not NULL, specify additional non-default + properties of the surface. +*/ +SK_API sk_surface_t* sk_surface_new_raster(const sk_imageinfo_t*, const sk_surfaceprops_t*); + +/** + Create a new surface which will draw into the specified pixels + with the specified rowbytes. If the requested surface cannot be + created, or the request is not a supported configuration, NULL + will be returned. + + @param sk_imageinfo_t* Specify the width, height, color type, and + alpha type for the surface. + @param void* pixels Specify the location in memory where the + destination pixels are. This memory must + outlast this surface. + @param size_t rowBytes Specify the difference, in bytes, between + each adjacent row. Should be at least + (width * sizeof(one pixel)). + @param sk_surfaceprops_t* If not NULL, specify additional non-default + properties of the surface. +*/ +SK_API sk_surface_t* sk_surface_new_raster_direct(const sk_imageinfo_t*, + void* pixels, size_t rowBytes, + const sk_surfaceprops_t* props); + +/** + Decrement the reference count. If the reference count is 1 before + the decrement, then release both the memory holding the + sk_surface_t and any pixel memory it may be managing. New + sk_surface_t are created with a reference count of 1. +*/ +SK_API void sk_surface_unref(sk_surface_t*); + +/** + * Return the canvas associated with this surface. Note: the canvas is owned by the surface, + * so the returned object is only valid while the owning surface is valid. + */ +SK_API sk_canvas_t* sk_surface_get_canvas(sk_surface_t*); + +/** + * Call sk_image_unref() when the returned image is no longer used. + */ +SK_API sk_image_t* sk_surface_new_image_snapshot(sk_surface_t*); + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/c/sk_types.h b/gfx/skia/skia/include/c/sk_types.h new file mode 100644 index 0000000000..852526f2b6 --- /dev/null +++ b/gfx/skia/skia/include/c/sk_types.h @@ -0,0 +1,256 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL EXPERIMENTAL +// DO NOT USE -- FOR INTERNAL TESTING ONLY + +#ifndef sk_types_DEFINED +#define sk_types_DEFINED + +#include +#include + +#ifdef __cplusplus + #define SK_C_PLUS_PLUS_BEGIN_GUARD extern "C" { + #define SK_C_PLUS_PLUS_END_GUARD } +#else + #include + #define SK_C_PLUS_PLUS_BEGIN_GUARD + #define SK_C_PLUS_PLUS_END_GUARD +#endif + +#if !defined(SK_API) + #if defined(SKIA_DLL) + #if defined(_MSC_VER) + #if SKIA_IMPLEMENTATION + #define SK_API __declspec(dllexport) + #else + #define SK_API __declspec(dllimport) + #endif + #else + #define SK_API __attribute__((visibility("default"))) + #endif + #else + #define SK_API + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////////////// + +SK_C_PLUS_PLUS_BEGIN_GUARD + +typedef uint32_t sk_color_t; + +/* This macro assumes all arguments are >=0 and <=255. */ +#define sk_color_set_argb(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) +#define sk_color_get_a(c) (((c) >> 24) & 0xFF) +#define sk_color_get_r(c) (((c) >> 16) & 0xFF) +#define sk_color_get_g(c) (((c) >> 8) & 0xFF) +#define sk_color_get_b(c) (((c) >> 0) & 0xFF) + +typedef enum { + INTERSECT_SK_CLIPTYPE, + DIFFERENCE_SK_CLIPTYPE, +} sk_cliptype_t; + +typedef enum { + UNKNOWN_SK_PIXELGEOMETRY, + RGB_H_SK_PIXELGEOMETRY, + BGR_H_SK_PIXELGEOMETRY, + RGB_V_SK_PIXELGEOMETRY, + BGR_V_SK_PIXELGEOMETRY, +} sk_pixelgeometry_t; + +typedef struct { + sk_pixelgeometry_t pixelGeometry; +} sk_surfaceprops_t; + +typedef struct { + float x; + float y; +} sk_point_t; + +typedef struct { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +} sk_irect_t; + +typedef struct { + float left; + float top; + float right; + float bottom; +} sk_rect_t; + +/** + The sk_matrix_t struct holds a 3x3 perspective matrix for + transforming coordinates: + + (X,Y) = T[M]((x,y)) + X = (M[0] * x + M[1] * y + M[2]) / (M[6] * x + M[7] * y + M[8]); + Y = (M[3] * x + M[4] * y + M[5]) / (M[6] * x + M[7] * y + M[8]); + + Therefore, the identity matrix is + + sk_matrix_t identity = {{1, 0, 0, + 0, 1, 0, + 0, 0, 1}}; + + A matrix that scales by sx and sy is: + + sk_matrix_t scale = {{sx, 0, 0, + 0, sy, 0, + 0, 0, 1}}; + + A matrix that translates by tx and ty is: + + sk_matrix_t translate = {{1, 0, tx, + 0, 1, ty, + 0, 0, 1}}; + + A matrix that rotates around the origin by A radians: + + sk_matrix_t rotate = {{cos(A), -sin(A), 0, + sin(A), cos(A), 0, + 0, 0, 1}}; + + Two matrixes can be concatinated by: + + void concat_matrices(sk_matrix_t* dst, + const sk_matrix_t* matrixU, + const sk_matrix_t* matrixV) { + const float* u = matrixU->mat; + const float* v = matrixV->mat; + sk_matrix_t result = {{ + u[0] * v[0] + u[1] * v[3] + u[2] * v[6], + u[0] * v[1] + u[1] * v[4] + u[2] * v[7], + u[0] * v[2] + u[1] * v[5] + u[2] * v[8], + u[3] * v[0] + u[4] * v[3] + u[5] * v[6], + u[3] * v[1] + u[4] * v[4] + u[5] * v[7], + u[3] * v[2] + u[4] * v[5] + u[5] * v[8], + u[6] * v[0] + u[7] * v[3] + u[8] * v[6], + u[6] * v[1] + u[7] * v[4] + u[8] * v[7], + u[6] * v[2] + u[7] * v[5] + u[8] * v[8] + }}; + *dst = result; + } +*/ +typedef struct { + float mat[9]; +} sk_matrix_t; + +/** + A sk_canvas_t encapsulates all of the state about drawing into a + destination This includes a reference to the destination itself, + and a stack of matrix/clip values. +*/ +typedef struct sk_canvas_t sk_canvas_t; +/** + A sk_data_ holds an immutable data buffer. +*/ +typedef struct sk_data_t sk_data_t; +/** + A sk_image_t is an abstraction for drawing a rectagle of pixels. + The content of the image is always immutable, though the actual + storage may change, if for example that image can be re-created via + encoded data or other means. +*/ +typedef struct sk_image_t sk_image_t; + +/** + * Describes the color components. See ICC Profiles. + */ +typedef struct sk_colorspace_t sk_colorspace_t; + +/** + * Describes an image buffer : width, height, pixel type, colorspace, etc. + */ +typedef struct sk_imageinfo_t sk_imageinfo_t; + +/** + A sk_maskfilter_t is an object that perform transformations on an + alpha-channel mask before drawing it; it may be installed into a + sk_paint_t. Each time a primitive is drawn, it is first + scan-converted into a alpha mask, which os handed to the + maskfilter, which may create a new mask is to render into the + destination. + */ +typedef struct sk_maskfilter_t sk_maskfilter_t; +/** + A sk_paint_t holds the style and color information about how to + draw geometries, text and bitmaps. +*/ +typedef struct sk_paint_t sk_paint_t; +/** + A sk_path_t encapsulates compound (multiple contour) geometric + paths consisting of straight line segments, quadratic curves, and + cubic curves. +*/ +typedef struct sk_path_t sk_path_t; +/** + A sk_picture_t holds recorded canvas drawing commands to be played + back at a later time. +*/ +typedef struct sk_picture_t sk_picture_t; +/** + A sk_picture_recorder_t holds a sk_canvas_t that records commands + to create a sk_picture_t. +*/ +typedef struct sk_picture_recorder_t sk_picture_recorder_t; +/** + A sk_shader_t specifies the source color(s) for what is being drawn. If a + paint has no shader, then the paint's color is used. If the paint + has a shader, then the shader's color(s) are use instead, but they + are modulated by the paint's alpha. +*/ +typedef struct sk_shader_t sk_shader_t; +/** + A sk_surface_t holds the destination for drawing to a canvas. For + raster drawing, the destination is an array of pixels in memory. + For GPU drawing, the destination is a texture or a framebuffer. +*/ +typedef struct sk_surface_t sk_surface_t; + +typedef enum { + CLEAR_SK_XFERMODE_MODE, + SRC_SK_XFERMODE_MODE, + DST_SK_XFERMODE_MODE, + SRCOVER_SK_XFERMODE_MODE, + DSTOVER_SK_XFERMODE_MODE, + SRCIN_SK_XFERMODE_MODE, + DSTIN_SK_XFERMODE_MODE, + SRCOUT_SK_XFERMODE_MODE, + DSTOUT_SK_XFERMODE_MODE, + SRCATOP_SK_XFERMODE_MODE, + DSTATOP_SK_XFERMODE_MODE, + XOR_SK_XFERMODE_MODE, + PLUS_SK_XFERMODE_MODE, + MODULATE_SK_XFERMODE_MODE, + SCREEN_SK_XFERMODE_MODE, + OVERLAY_SK_XFERMODE_MODE, + DARKEN_SK_XFERMODE_MODE, + LIGHTEN_SK_XFERMODE_MODE, + COLORDODGE_SK_XFERMODE_MODE, + COLORBURN_SK_XFERMODE_MODE, + HARDLIGHT_SK_XFERMODE_MODE, + SOFTLIGHT_SK_XFERMODE_MODE, + DIFFERENCE_SK_XFERMODE_MODE, + EXCLUSION_SK_XFERMODE_MODE, + MULTIPLY_SK_XFERMODE_MODE, + HUE_SK_XFERMODE_MODE, + SATURATION_SK_XFERMODE_MODE, + COLOR_SK_XFERMODE_MODE, + LUMINOSITY_SK_XFERMODE_MODE, +} sk_xfermode_mode_t; + +////////////////////////////////////////////////////////////////////////////////////////// + +SK_C_PLUS_PLUS_END_GUARD + +#endif diff --git a/gfx/skia/skia/include/codec/SkAndroidCodec.h b/gfx/skia/skia/include/codec/SkAndroidCodec.h new file mode 100644 index 0000000000..26b3f5e0a8 --- /dev/null +++ b/gfx/skia/skia/include/codec/SkAndroidCodec.h @@ -0,0 +1,287 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAndroidCodec_DEFINED +#define SkAndroidCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" + +/** + * Abstract interface defining image codec functionality that is necessary for + * Android. + */ +class SK_API SkAndroidCodec : SkNoncopyable { +public: + enum class ExifOrientationBehavior { + /** + * Ignore any exif orientation markers in the data. + * + * getInfo's width and height will match the header of the image, and + * no processing will be done to match the marker. + */ + kIgnore, + + /** + * Respect the exif orientation marker. + * + * getInfo's width and height will represent what they should be after + * applying the orientation. For example, if the marker specifies a + * rotation by 90 degrees, they will be swapped relative to the header. + * getAndroidPixels will apply the orientation as well. + */ + kRespect, + }; + + /** + * Pass ownership of an SkCodec to a newly-created SkAndroidCodec. + */ + static std::unique_ptr MakeFromCodec(std::unique_ptr, + ExifOrientationBehavior = ExifOrientationBehavior::kIgnore); + + /** + * If this stream represents an encoded image that we know how to decode, + * return an SkAndroidCodec that can decode it. Otherwise return NULL. + * + * The SkPngChunkReader handles unknown chunks in PNGs. + * See SkCodec.h for more details. + * + * If NULL is returned, the stream is deleted immediately. Otherwise, the + * SkCodec takes ownership of it, and will delete it when done with it. + * + * ExifOrientationBehavior is set to kIgnore. + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, + SkPngChunkReader* = nullptr); + + /** + * If this data represents an encoded image that we know how to decode, + * return an SkAndroidCodec that can decode it. Otherwise return NULL. + * + * The SkPngChunkReader handles unknown chunks in PNGs. + * See SkCodec.h for more details. + * + * ExifOrientationBehavior is set to kIgnore. + */ + static std::unique_ptr MakeFromData(sk_sp, SkPngChunkReader* = nullptr); + + virtual ~SkAndroidCodec(); + + const SkImageInfo& getInfo() const { return fInfo; } + + /** + * Format of the encoded data. + */ + SkEncodedImageFormat getEncodedFormat() const { return fCodec->getEncodedFormat(); } + + /** + * @param requestedColorType Color type requested by the client + * + * |requestedColorType| may be overriden. We will default to kF16 + * for high precision images. + * + * In the general case, if it is possible to decode to + * |requestedColorType|, this returns |requestedColorType|. + * Otherwise, this returns a color type that is an appropriate + * match for the the encoded data. + */ + SkColorType computeOutputColorType(SkColorType requestedColorType); + + /** + * @param requestedUnpremul Indicates if the client requested + * unpremultiplied output + * + * Returns the appropriate alpha type to decode to. If the image + * has alpha, the value of requestedUnpremul will be honored. + */ + SkAlphaType computeOutputAlphaType(bool requestedUnpremul); + + /** + * @param outputColorType Color type that the client will decode to. + * @param prefColorSpace Preferred color space to decode to. + * This may not return |prefColorSpace| for a couple reasons. + * (1) Android Principles: 565 must be sRGB, F16 must be + * linear sRGB, transfer function must be parametric. + * (2) Codec Limitations: F16 requires a linear color space. + * + * Returns the appropriate color space to decode to. + */ + sk_sp computeOutputColorSpace(SkColorType outputColorType, + sk_sp prefColorSpace = nullptr); + + /** + * Compute the appropriate sample size to get to |size|. + * + * @param size As an input parameter, the desired output size of + * the decode. As an output parameter, the smallest sampled size + * larger than the input. + * @return the sample size to set AndroidOptions::fSampleSize to decode + * to the output |size|. + */ + int computeSampleSize(SkISize* size) const; + + /** + * Returns the dimensions of the scaled output image, for an input + * sampleSize. + * + * When the sample size divides evenly into the original dimensions, the + * scaled output dimensions will simply be equal to the original + * dimensions divided by the sample size. + * + * When the sample size does not divide even into the original + * dimensions, the codec may round up or down, depending on what is most + * efficient to decode. + * + * Finally, the codec will always recommend a non-zero output, so the output + * dimension will always be one if the sampleSize is greater than the + * original dimension. + */ + SkISize getSampledDimensions(int sampleSize) const; + + /** + * Return (via desiredSubset) a subset which can decoded from this codec, + * or false if the input subset is invalid. + * + * @param desiredSubset in/out parameter + * As input, a desired subset of the original bounds + * (as specified by getInfo). + * As output, if true is returned, desiredSubset may + * have been modified to a subset which is + * supported. Although a particular change may have + * been made to desiredSubset to create something + * supported, it is possible other changes could + * result in a valid subset. If false is returned, + * desiredSubset's value is undefined. + * @return true If the input desiredSubset is valid. + * desiredSubset may be modified to a subset + * supported by the codec. + * false If desiredSubset is invalid (NULL or not fully + * contained within the image). + */ + bool getSupportedSubset(SkIRect* desiredSubset) const; + // TODO: Rename SkCodec::getValidSubset() to getSupportedSubset() + + /** + * Returns the dimensions of the scaled, partial output image, for an + * input sampleSize and subset. + * + * @param sampleSize Factor to scale down by. + * @param subset Must be a valid subset of the original image + * dimensions and a subset supported by SkAndroidCodec. + * getSubset() can be used to obtain a subset supported + * by SkAndroidCodec. + * @return Size of the scaled partial image. Or zero size + * if either of the inputs is invalid. + */ + SkISize getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const; + + /** + * Additional options to pass to getAndroidPixels(). + */ + // FIXME: It's a bit redundant to name these AndroidOptions when this class is already + // called SkAndroidCodec. On the other hand, it's may be a bit confusing to call + // these Options when SkCodec has a slightly different set of Options. Maybe these + // should be DecodeOptions or SamplingOptions? + struct AndroidOptions { + AndroidOptions() + : fZeroInitialized(SkCodec::kNo_ZeroInitialized) + , fSubset(nullptr) + , fSampleSize(1) + {} + + /** + * Indicates is destination pixel memory is zero initialized. + * + * The default is SkCodec::kNo_ZeroInitialized. + */ + SkCodec::ZeroInitialized fZeroInitialized; + + /** + * If not NULL, represents a subset of the original image to decode. + * + * Must be within the bounds returned by getInfo(). + * + * If the EncodedFormat is SkEncodedImageFormat::kWEBP, the top and left + * values must be even. + * + * The default is NULL, meaning a decode of the entire image. + */ + SkIRect* fSubset; + + /** + * The client may provide an integer downscale factor for the decode. + * The codec may implement this downscaling by sampling or another + * method if it is more efficient. + * + * The default is 1, representing no downscaling. + */ + int fSampleSize; + }; + + /** + * Decode into the given pixels, a block of memory of size at + * least (info.fHeight - 1) * rowBytes + (info.fWidth * + * bytesPerPixel) + * + * Repeated calls to this function should give the same results, + * allowing the PixelRef to be immutable. + * + * @param info A description of the format (config, size) + * expected by the caller. This can simply be identical + * to the info returned by getInfo(). + * + * This contract also allows the caller to specify + * different output-configs, which the implementation can + * decide to support or not. + * + * A size that does not match getInfo() implies a request + * to scale or subset. If the codec cannot perform this + * scaling or subsetting, it will return an error code. + * + * The AndroidOptions object is also used to specify any requested scaling or subsetting + * using options->fSampleSize and options->fSubset. If NULL, the defaults (as specified above + * for AndroidOptions) are used. + * + * @return Result kSuccess, or another value explaining the type of failure. + */ + // FIXME: It's a bit redundant to name this getAndroidPixels() when this class is already + // called SkAndroidCodec. On the other hand, it's may be a bit confusing to call + // this getPixels() when it is a slightly different API than SkCodec's getPixels(). + // Maybe this should be decode() or decodeSubset()? + SkCodec::Result getAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions* options); + + /** + * Simplified version of getAndroidPixels() where we supply the default AndroidOptions as + * specified above for AndroidOptions. It will not perform any scaling or subsetting. + */ + SkCodec::Result getAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes); + + SkCodec::Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { + return this->getAndroidPixels(info, pixels, rowBytes); + } + + SkCodec* codec() const { return fCodec.get(); } + +protected: + SkAndroidCodec(SkCodec*, ExifOrientationBehavior = ExifOrientationBehavior::kIgnore); + + virtual SkISize onGetSampledDimensions(int sampleSize) const = 0; + + virtual bool onGetSupportedSubset(SkIRect* desiredSubset) const = 0; + + virtual SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const AndroidOptions& options) = 0; + +private: + const SkImageInfo fInfo; + const ExifOrientationBehavior fOrientationBehavior; + std::unique_ptr fCodec; +}; +#endif // SkAndroidCodec_DEFINED diff --git a/gfx/skia/skia/include/codec/SkCodec.h b/gfx/skia/skia/include/codec/SkCodec.h new file mode 100644 index 0000000000..2f9f93c0e7 --- /dev/null +++ b/gfx/skia/skia/include/codec/SkCodec.h @@ -0,0 +1,954 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodec_DEFINED +#define SkCodec_DEFINED + +#include "include/codec/SkCodecAnimation.h" +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkColor.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkSize.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/core/SkYUVASizeInfo.h" +#include "include/private/SkEncodedInfo.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTemplates.h" + +#include + +class SkColorSpace; +class SkData; +class SkFrameHolder; +class SkPngChunkReader; +class SkSampler; + +namespace DM { +class CodecSrc; +class ColorCodecSrc; +} + +/** + * Abstraction layer directly on top of an image codec. + */ +class SK_API SkCodec : SkNoncopyable { +public: + /** + * Minimum number of bytes that must be buffered in SkStream input. + * + * An SkStream passed to NewFromStream must be able to use this many + * bytes to determine the image type. Then the same SkStream must be + * passed to the correct decoder to read from the beginning. + * + * This can be accomplished by implementing peek() to support peeking + * this many bytes, or by implementing rewind() to be able to rewind() + * after reading this many bytes. + */ + static constexpr size_t MinBufferedBytesNeeded() { return 32; } + + /** + * Error codes for various SkCodec methods. + */ + enum Result { + /** + * General return value for success. + */ + kSuccess, + /** + * The input is incomplete. A partial image was generated. + */ + kIncompleteInput, + /** + * Like kIncompleteInput, except the input had an error. + * + * If returned from an incremental decode, decoding cannot continue, + * even with more data. + */ + kErrorInInput, + /** + * The generator cannot convert to match the request, ignoring + * dimensions. + */ + kInvalidConversion, + /** + * The generator cannot scale to requested size. + */ + kInvalidScale, + /** + * Parameters (besides info) are invalid. e.g. NULL pixels, rowBytes + * too small, etc. + */ + kInvalidParameters, + /** + * The input did not contain a valid image. + */ + kInvalidInput, + /** + * Fulfilling this request requires rewinding the input, which is not + * supported for this input. + */ + kCouldNotRewind, + /** + * An internal error, such as OOM. + */ + kInternalError, + /** + * This method is not implemented by this codec. + * FIXME: Perhaps this should be kUnsupported? + */ + kUnimplemented, + }; + + /** + * Readable string representing the error code. + */ + static const char* ResultToString(Result); + + /** + * For container formats that contain both still images and image sequences, + * instruct the decoder how the output should be selected. (Refer to comments + * for each value for more details.) + */ + enum class SelectionPolicy { + /** + * If the container format contains both still images and image sequences, + * SkCodec should choose one of the still images. This is the default. + */ + kPreferStillImage, + /** + * If the container format contains both still images and image sequences, + * SkCodec should choose one of the image sequences for animation. + */ + kPreferAnimation, + }; + + /** + * If this stream represents an encoded image that we know how to decode, + * return an SkCodec that can decode it. Otherwise return NULL. + * + * As stated above, this call must be able to peek or read + * MinBufferedBytesNeeded to determine the correct format, and then start + * reading from the beginning. First it will attempt to peek, and it + * assumes that if less than MinBufferedBytesNeeded bytes (but more than + * zero) are returned, this is because the stream is shorter than this, + * so falling back to reading would not provide more data. If peek() + * returns zero bytes, this call will instead attempt to read(). This + * will require that the stream can be rewind()ed. + * + * If Result is not NULL, it will be set to either kSuccess if an SkCodec + * is returned or a reason for the failure if NULL is returned. + * + * If SkPngChunkReader is not NULL, take a ref and pass it to libpng if + * the image is a png. + * + * If the SkPngChunkReader is not NULL then: + * If the image is not a PNG, the SkPngChunkReader will be ignored. + * If the image is a PNG, the SkPngChunkReader will be reffed. + * If the PNG has unknown chunks, the SkPngChunkReader will be used + * to handle these chunks. SkPngChunkReader will be called to read + * any unknown chunk at any point during the creation of the codec + * or the decode. Note that if SkPngChunkReader fails to read a + * chunk, this could result in a failure to create the codec or a + * failure to decode the image. + * If the PNG does not contain unknown chunks, the SkPngChunkReader + * will not be used or modified. + * + * If NULL is returned, the stream is deleted immediately. Otherwise, the + * SkCodec takes ownership of it, and will delete it when done with it. + */ + static std::unique_ptr MakeFromStream( + std::unique_ptr, Result* = nullptr, + SkPngChunkReader* = nullptr, + SelectionPolicy selectionPolicy = SelectionPolicy::kPreferStillImage); + + /** + * If this data represents an encoded image that we know how to decode, + * return an SkCodec that can decode it. Otherwise return NULL. + * + * If the SkPngChunkReader is not NULL then: + * If the image is not a PNG, the SkPngChunkReader will be ignored. + * If the image is a PNG, the SkPngChunkReader will be reffed. + * If the PNG has unknown chunks, the SkPngChunkReader will be used + * to handle these chunks. SkPngChunkReader will be called to read + * any unknown chunk at any point during the creation of the codec + * or the decode. Note that if SkPngChunkReader fails to read a + * chunk, this could result in a failure to create the codec or a + * failure to decode the image. + * If the PNG does not contain unknown chunks, the SkPngChunkReader + * will not be used or modified. + */ + static std::unique_ptr MakeFromData(sk_sp, SkPngChunkReader* = nullptr); + + virtual ~SkCodec(); + + /** + * Return a reasonable SkImageInfo to decode into. + */ + SkImageInfo getInfo() const { return fEncodedInfo.makeImageInfo(); } + + SkISize dimensions() const { return {fEncodedInfo.width(), fEncodedInfo.height()}; } + SkIRect bounds() const { + return SkIRect::MakeWH(fEncodedInfo.width(), fEncodedInfo.height()); + } + + /** + * Returns the image orientation stored in the EXIF data. + * If there is no EXIF data, or if we cannot read the EXIF data, returns kTopLeft. + */ + SkEncodedOrigin getOrigin() const { return fOrigin; } + + /** + * Return a size that approximately supports the desired scale factor. + * The codec may not be able to scale efficiently to the exact scale + * factor requested, so return a size that approximates that scale. + * The returned value is the codec's suggestion for the closest valid + * scale that it can natively support + */ + SkISize getScaledDimensions(float desiredScale) const { + // Negative and zero scales are errors. + SkASSERT(desiredScale > 0.0f); + if (desiredScale <= 0.0f) { + return SkISize::Make(0, 0); + } + + // Upscaling is not supported. Return the original size if the client + // requests an upscale. + if (desiredScale >= 1.0f) { + return this->dimensions(); + } + return this->onGetScaledDimensions(desiredScale); + } + + /** + * Return (via desiredSubset) a subset which can decoded from this codec, + * or false if this codec cannot decode subsets or anything similar to + * desiredSubset. + * + * @param desiredSubset In/out parameter. As input, a desired subset of + * the original bounds (as specified by getInfo). If true is returned, + * desiredSubset may have been modified to a subset which is + * supported. Although a particular change may have been made to + * desiredSubset to create something supported, it is possible other + * changes could result in a valid subset. + * If false is returned, desiredSubset's value is undefined. + * @return true if this codec supports decoding desiredSubset (as + * returned, potentially modified) + */ + bool getValidSubset(SkIRect* desiredSubset) const { + return this->onGetValidSubset(desiredSubset); + } + + /** + * Format of the encoded data. + */ + SkEncodedImageFormat getEncodedFormat() const { return this->onGetEncodedFormat(); } + + /** + * Whether or not the memory passed to getPixels is zero initialized. + */ + enum ZeroInitialized { + /** + * The memory passed to getPixels is zero initialized. The SkCodec + * may take advantage of this by skipping writing zeroes. + */ + kYes_ZeroInitialized, + /** + * The memory passed to getPixels has not been initialized to zero, + * so the SkCodec must write all zeroes to memory. + * + * This is the default. It will be used if no Options struct is used. + */ + kNo_ZeroInitialized, + }; + + /** + * Additional options to pass to getPixels. + */ + struct Options { + Options() + : fZeroInitialized(kNo_ZeroInitialized) + , fSubset(nullptr) + , fFrameIndex(0) + , fPriorFrame(kNoFrame) + {} + + ZeroInitialized fZeroInitialized; + /** + * If not NULL, represents a subset of the original image to decode. + * Must be within the bounds returned by getInfo(). + * If the EncodedFormat is SkEncodedImageFormat::kWEBP (the only one which + * currently supports subsets), the top and left values must be even. + * + * In getPixels and incremental decode, we will attempt to decode the + * exact rectangular subset specified by fSubset. + * + * In a scanline decode, it does not make sense to specify a subset + * top or subset height, since the client already controls which rows + * to get and which rows to skip. During scanline decodes, we will + * require that the subset top be zero and the subset height be equal + * to the full height. We will, however, use the values of + * subset left and subset width to decode partial scanlines on calls + * to getScanlines(). + */ + const SkIRect* fSubset; + + /** + * The frame to decode. + * + * Only meaningful for multi-frame images. + */ + int fFrameIndex; + + /** + * If not kNoFrame, the dst already contains the prior frame at this index. + * + * Only meaningful for multi-frame images. + * + * If fFrameIndex needs to be blended with a prior frame (as reported by + * getFrameInfo[fFrameIndex].fRequiredFrame), the client can set this to + * any non-kRestorePrevious frame in [fRequiredFrame, fFrameIndex) to + * indicate that that frame is already in the dst. Options.fZeroInitialized + * is ignored in this case. + * + * If set to kNoFrame, the codec will decode any necessary required frame(s) first. + */ + int fPriorFrame; + }; + + /** + * Decode into the given pixels, a block of memory of size at + * least (info.fHeight - 1) * rowBytes + (info.fWidth * + * bytesPerPixel) + * + * Repeated calls to this function should give the same results, + * allowing the PixelRef to be immutable. + * + * @param info A description of the format (config, size) + * expected by the caller. This can simply be identical + * to the info returned by getInfo(). + * + * This contract also allows the caller to specify + * different output-configs, which the implementation can + * decide to support or not. + * + * A size that does not match getInfo() implies a request + * to scale. If the generator cannot perform this scale, + * it will return kInvalidScale. + * + * If the info contains a non-null SkColorSpace, the codec + * will perform the appropriate color space transformation. + * If the caller passes in the same color space that was + * reported by the codec, the color space transformation is + * a no-op. + * + * If a scanline decode is in progress, scanline mode will end, requiring the client to call + * startScanlineDecode() in order to return to decoding scanlines. + * + * @return Result kSuccess, or another value explaining the type of failure. + */ + Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options*); + + /** + * Simplified version of getPixels() that uses the default Options. + */ + Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { + return this->getPixels(info, pixels, rowBytes, nullptr); + } + + Result getPixels(const SkPixmap& pm, const Options* opts = nullptr) { + return this->getPixels(pm.info(), pm.writable_addr(), pm.rowBytes(), opts); + } + + /** + * If decoding to YUV is supported, this returns true. Otherwise, this + * returns false and does not modify any of the parameters. + * + * @param sizeInfo Output parameter indicating the sizes and required + * allocation widths of the Y, U, V, and A planes. Given current codec + * limitations the size of the A plane will always be 0 and the Y, U, V + * channels will always be planar. + * @param colorSpace Output parameter. If non-NULL this is set to kJPEG, + * otherwise this is ignored. + */ + bool queryYUV8(SkYUVASizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const { + if (nullptr == sizeInfo) { + return false; + } + + bool result = this->onQueryYUV8(sizeInfo, colorSpace); + if (result) { + for (int i = 0; i <= 2; ++i) { + SkASSERT(sizeInfo->fSizes[i].fWidth > 0 && sizeInfo->fSizes[i].fHeight > 0 && + sizeInfo->fWidthBytes[i] > 0); + } + SkASSERT(!sizeInfo->fSizes[3].fWidth && + !sizeInfo->fSizes[3].fHeight && + !sizeInfo->fWidthBytes[3]); + } + return result; + } + + /** + * Returns kSuccess, or another value explaining the type of failure. + * This always attempts to perform a full decode. If the client only + * wants size, it should call queryYUV8(). + * + * @param sizeInfo Needs to exactly match the values returned by the + * query, except the WidthBytes may be larger than the + * recommendation (but not smaller). + * @param planes Memory for each of the Y, U, and V planes. + */ + Result getYUV8Planes(const SkYUVASizeInfo& sizeInfo, void* planes[SkYUVASizeInfo::kMaxCount]) { + if (!planes || !planes[0] || !planes[1] || !planes[2]) { + return kInvalidInput; + } + SkASSERT(!planes[3]); // TODO: is this a fair assumption? + + if (!this->rewindIfNeeded()) { + return kCouldNotRewind; + } + + return this->onGetYUV8Planes(sizeInfo, planes); + } + + /** + * Prepare for an incremental decode with the specified options. + * + * This may require a rewind. + * + * If kIncompleteInput is returned, may be called again after more data has + * been provided to the source SkStream. + * + * @param dstInfo Info of the destination. If the dimensions do not match + * those of getInfo, this implies a scale. + * @param dst Memory to write to. Needs to be large enough to hold the subset, + * if present, or the full image as described in dstInfo. + * @param options Contains decoding options, including if memory is zero + * initialized and whether to decode a subset. + * @return Enum representing success or reason for failure. + */ + Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, + const Options*); + + Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes) { + return this->startIncrementalDecode(dstInfo, dst, rowBytes, nullptr); + } + + /** + * Start/continue the incremental decode. + * + * Not valid to call before a call to startIncrementalDecode() returns + * kSuccess. + * + * If kIncompleteInput is returned, may be called again after more data has + * been provided to the source SkStream. + * + * Unlike getPixels and getScanlines, this does not do any filling. This is + * left up to the caller, since they may be skipping lines or continuing the + * decode later. In the latter case, they may choose to initialize all lines + * first, or only initialize the remaining lines after the first call. + * + * @param rowsDecoded Optional output variable returning the total number of + * lines initialized. Only meaningful if this method returns kIncompleteInput. + * Otherwise the implementation may not set it. + * Note that some implementations may have initialized this many rows, but + * not necessarily finished those rows (e.g. interlaced PNG). This may be + * useful for determining what rows the client needs to initialize. + * @return kSuccess if all lines requested in startIncrementalDecode have + * been completely decoded. kIncompleteInput otherwise. + */ + Result incrementalDecode(int* rowsDecoded = nullptr) { + if (!fStartedIncrementalDecode) { + return kInvalidParameters; + } + return this->onIncrementalDecode(rowsDecoded); + } + + /** + * The remaining functions revolve around decoding scanlines. + */ + + /** + * Prepare for a scanline decode with the specified options. + * + * After this call, this class will be ready to decode the first scanline. + * + * This must be called in order to call getScanlines or skipScanlines. + * + * This may require rewinding the stream. + * + * Not all SkCodecs support this. + * + * @param dstInfo Info of the destination. If the dimensions do not match + * those of getInfo, this implies a scale. + * @param options Contains decoding options, including if memory is zero + * initialized. + * @return Enum representing success or reason for failure. + */ + Result startScanlineDecode(const SkImageInfo& dstInfo, const Options* options); + + /** + * Simplified version of startScanlineDecode() that uses the default Options. + */ + Result startScanlineDecode(const SkImageInfo& dstInfo) { + return this->startScanlineDecode(dstInfo, nullptr); + } + + /** + * Write the next countLines scanlines into dst. + * + * Not valid to call before calling startScanlineDecode(). + * + * @param dst Must be non-null, and large enough to hold countLines + * scanlines of size rowBytes. + * @param countLines Number of lines to write. + * @param rowBytes Number of bytes per row. Must be large enough to hold + * a scanline based on the SkImageInfo used to create this object. + * @return the number of lines successfully decoded. If this value is + * less than countLines, this will fill the remaining lines with a + * default value. + */ + int getScanlines(void* dst, int countLines, size_t rowBytes); + + /** + * Skip count scanlines. + * + * Not valid to call before calling startScanlineDecode(). + * + * The default version just calls onGetScanlines and discards the dst. + * NOTE: If skipped lines are the only lines with alpha, this default + * will make reallyHasAlpha return true, when it could have returned + * false. + * + * @return true if the scanlines were successfully skipped + * false on failure, possible reasons for failure include: + * An incomplete input image stream. + * Calling this function before calling startScanlineDecode(). + * If countLines is less than zero or so large that it moves + * the current scanline past the end of the image. + */ + bool skipScanlines(int countLines); + + /** + * The order in which rows are output from the scanline decoder is not the + * same for all variations of all image types. This explains the possible + * output row orderings. + */ + enum SkScanlineOrder { + /* + * By far the most common, this indicates that the image can be decoded + * reliably using the scanline decoder, and that rows will be output in + * the logical order. + */ + kTopDown_SkScanlineOrder, + + /* + * This indicates that the scanline decoder reliably outputs rows, but + * they will be returned in reverse order. If the scanline format is + * kBottomUp, the nextScanline() API can be used to determine the actual + * y-coordinate of the next output row, but the client is not forced + * to take advantage of this, given that it's not too tough to keep + * track independently. + * + * For full image decodes, it is safe to get all of the scanlines at + * once, since the decoder will handle inverting the rows as it + * decodes. + * + * For subset decodes and sampling, it is simplest to get and skip + * scanlines one at a time, using the nextScanline() API. It is + * possible to ask for larger chunks at a time, but this should be used + * with caution. As with full image decodes, the decoder will handle + * inverting the requested rows, but rows will still be delivered + * starting from the bottom of the image. + * + * Upside down bmps are an example. + */ + kBottomUp_SkScanlineOrder, + }; + + /** + * An enum representing the order in which scanlines will be returned by + * the scanline decoder. + * + * This is undefined before startScanlineDecode() is called. + */ + SkScanlineOrder getScanlineOrder() const { return this->onGetScanlineOrder(); } + + /** + * Returns the y-coordinate of the next row to be returned by the scanline + * decoder. + * + * This will equal fCurrScanline, except in the case of strangely + * encoded image types (bottom-up bmps). + * + * Results are undefined when not in scanline decoding mode. + */ + int nextScanline() const { return this->outputScanline(fCurrScanline); } + + /** + * Returns the output y-coordinate of the row that corresponds to an input + * y-coordinate. The input y-coordinate represents where the scanline + * is located in the encoded data. + * + * This will equal inputScanline, except in the case of strangely + * encoded image types (bottom-up bmps, interlaced gifs). + */ + int outputScanline(int inputScanline) const; + + /** + * Return the number of frames in the image. + * + * May require reading through the stream. + */ + int getFrameCount() { + return this->onGetFrameCount(); + } + + // Sentinel value used when a frame index implies "no frame": + // - FrameInfo::fRequiredFrame set to this value means the frame + // is independent. + // - Options::fPriorFrame set to this value means no (relevant) prior frame + // is residing in dst's memory. + static constexpr int kNoFrame = -1; + + // This transitional definition was added in August 2018, and will eventually be removed. +#ifdef SK_LEGACY_SKCODEC_NONE_ENUM + static constexpr int kNone = kNoFrame; +#endif + + /** + * Information about individual frames in a multi-framed image. + */ + struct FrameInfo { + /** + * The frame that this frame needs to be blended with, or + * kNoFrame if this frame is independent (so it can be + * drawn over an uninitialized buffer). + * + * Note that this is the *earliest* frame that can be used + * for blending. Any frame from [fRequiredFrame, i) can be + * used, unless its fDisposalMethod is kRestorePrevious. + */ + int fRequiredFrame; + + /** + * Number of milliseconds to show this frame. + */ + int fDuration; + + /** + * Whether the end marker for this frame is contained in the stream. + * + * Note: this does not guarantee that an attempt to decode will be complete. + * There could be an error in the stream. + */ + bool fFullyReceived; + + /** + * This is conservative; it will still return non-opaque if e.g. a + * color index-based frame has a color with alpha but does not use it. + */ + SkAlphaType fAlphaType; + + /** + * How this frame should be modified before decoding the next one. + */ + SkCodecAnimation::DisposalMethod fDisposalMethod; + }; + + /** + * Return info about a single frame. + * + * Only supported by multi-frame images. Does not read through the stream, + * so it should be called after getFrameCount() to parse any frames that + * have not already been parsed. + */ + bool getFrameInfo(int index, FrameInfo* info) const { + if (index < 0) { + return false; + } + return this->onGetFrameInfo(index, info); + } + + /** + * Return info about all the frames in the image. + * + * May require reading through the stream to determine info about the + * frames (including the count). + * + * As such, future decoding calls may require a rewind. + * + * For still (non-animated) image codecs, this will return an empty vector. + */ + std::vector getFrameInfo(); + + static constexpr int kRepetitionCountInfinite = -1; + + /** + * Return the number of times to repeat, if this image is animated. This number does not + * include the first play through of each frame. For example, a repetition count of 4 means + * that each frame is played 5 times and then the animation stops. + * + * It can return kRepetitionCountInfinite, a negative number, meaning that the animation + * should loop forever. + * + * May require reading the stream to find the repetition count. + * + * As such, future decoding calls may require a rewind. + * + * For still (non-animated) image codecs, this will return 0. + */ + int getRepetitionCount() { + return this->onGetRepetitionCount(); + } + + // Register a decoder at runtime by passing two function pointers: + // - peek() to return true if the span of bytes appears to be your encoded format; + // - make() to attempt to create an SkCodec from the given stream. + // Not thread safe. + static void Register( + bool (*peek)(const void*, size_t), + std::unique_ptr (*make)(std::unique_ptr, SkCodec::Result*)); + +protected: + const SkEncodedInfo& getEncodedInfo() const { return fEncodedInfo; } + + using XformFormat = skcms_PixelFormat; + + SkCodec(SkEncodedInfo&&, + XformFormat srcFormat, + std::unique_ptr, + SkEncodedOrigin = kTopLeft_SkEncodedOrigin); + + virtual SkISize onGetScaledDimensions(float /*desiredScale*/) const { + // By default, scaling is not supported. + return this->dimensions(); + } + + // FIXME: What to do about subsets?? + /** + * Subclasses should override if they support dimensions other than the + * srcInfo's. + */ + virtual bool onDimensionsSupported(const SkISize&) { + return false; + } + + virtual SkEncodedImageFormat onGetEncodedFormat() const = 0; + + /** + * @param rowsDecoded When the encoded image stream is incomplete, this function + * will return kIncompleteInput and rowsDecoded will be set to + * the number of scanlines that were successfully decoded. + * This will allow getPixels() to fill the uninitialized memory. + */ + virtual Result onGetPixels(const SkImageInfo& info, + void* pixels, size_t rowBytes, const Options&, + int* rowsDecoded) = 0; + + virtual bool onQueryYUV8(SkYUVASizeInfo*, SkYUVColorSpace*) const { + return false; + } + + virtual Result onGetYUV8Planes(const SkYUVASizeInfo&, + void*[SkYUVASizeInfo::kMaxCount] /*planes*/) { + return kUnimplemented; + } + + virtual bool onGetValidSubset(SkIRect* /*desiredSubset*/) const { + // By default, subsets are not supported. + return false; + } + + /** + * If the stream was previously read, attempt to rewind. + * + * If the stream needed to be rewound, call onRewind. + * @returns true if the codec is at the right position and can be used. + * false if there was a failure to rewind. + * + * This is called by getPixels(), getYUV8Planes(), startIncrementalDecode() and + * startScanlineDecode(). Subclasses may call if they need to rewind at another time. + */ + bool SK_WARN_UNUSED_RESULT rewindIfNeeded(); + + /** + * Called by rewindIfNeeded, if the stream needed to be rewound. + * + * Subclasses should do any set up needed after a rewind. + */ + virtual bool onRewind() { + return true; + } + + /** + * Get method for the input stream + */ + SkStream* stream() { + return fStream.get(); + } + + /** + * The remaining functions revolve around decoding scanlines. + */ + + /** + * Most images types will be kTopDown and will not need to override this function. + */ + virtual SkScanlineOrder onGetScanlineOrder() const { return kTopDown_SkScanlineOrder; } + + const SkImageInfo& dstInfo() const { return fDstInfo; } + + const Options& options() const { return fOptions; } + + /** + * Returns the number of scanlines that have been decoded so far. + * This is unaffected by the SkScanlineOrder. + * + * Returns -1 if we have not started a scanline decode. + */ + int currScanline() const { return fCurrScanline; } + + virtual int onOutputScanline(int inputScanline) const; + + /** + * Return whether we can convert to dst. + * + * Will be called for the appropriate frame, prior to initializing the colorXform. + */ + virtual bool conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, + bool needsColorXform); + + // Some classes never need a colorXform e.g. + // - ICO uses its embedded codec's colorXform + // - WBMP is just Black/White + virtual bool usesColorXform() const { return true; } + void applyColorXform(void* dst, const void* src, int count) const; + + bool colorXform() const { return fXformTime != kNo_XformTime; } + bool xformOnDecode() const { return fXformTime == kDecodeRow_XformTime; } + + virtual int onGetFrameCount() { + return 1; + } + + virtual bool onGetFrameInfo(int, FrameInfo*) const { + return false; + } + + virtual int onGetRepetitionCount() { + return 0; + } + +private: + const SkEncodedInfo fEncodedInfo; + const XformFormat fSrcXformFormat; + std::unique_ptr fStream; + bool fNeedsRewind; + const SkEncodedOrigin fOrigin; + + SkImageInfo fDstInfo; + Options fOptions; + + enum XformTime { + kNo_XformTime, + kPalette_XformTime, + kDecodeRow_XformTime, + }; + XformTime fXformTime; + XformFormat fDstXformFormat; // Based on fDstInfo. + skcms_ICCProfile fDstProfile; + skcms_AlphaFormat fDstXformAlphaFormat; + + // Only meaningful during scanline decodes. + int fCurrScanline; + + bool fStartedIncrementalDecode; + + bool initializeColorXform(const SkImageInfo& dstInfo, SkEncodedInfo::Alpha, bool srcIsOpaque); + + /** + * Return whether these dimensions are supported as a scale. + * + * The codec may choose to cache the information about scale and subset. + * Either way, the same information will be passed to onGetPixels/onStart + * on success. + * + * This must return true for a size returned from getScaledDimensions. + */ + bool dimensionsSupported(const SkISize& dim) { + return dim == this->dimensions() || this->onDimensionsSupported(dim); + } + + /** + * For multi-framed images, return the object with information about the frames. + */ + virtual const SkFrameHolder* getFrameHolder() const { + return nullptr; + } + + /** + * Check for a valid Options.fFrameIndex, and decode prior frames if necessary. + */ + Result handleFrameIndex(const SkImageInfo&, void* pixels, size_t rowBytes, const Options&); + + // Methods for scanline decoding. + virtual Result onStartScanlineDecode(const SkImageInfo& /*dstInfo*/, + const Options& /*options*/) { + return kUnimplemented; + } + + virtual Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t, + const Options&) { + return kUnimplemented; + } + + virtual Result onIncrementalDecode(int*) { + return kUnimplemented; + } + + + virtual bool onSkipScanlines(int /*countLines*/) { return false; } + + virtual int onGetScanlines(void* /*dst*/, int /*countLines*/, size_t /*rowBytes*/) { return 0; } + + /** + * On an incomplete decode, getPixels() and getScanlines() will call this function + * to fill any uinitialized memory. + * + * @param dstInfo Contains the destination color type + * Contains the destination alpha type + * Contains the destination width + * The height stored in this info is unused + * @param dst Pointer to the start of destination pixel memory + * @param rowBytes Stride length in destination pixel memory + * @param zeroInit Indicates if memory is zero initialized + * @param linesRequested Number of lines that the client requested + * @param linesDecoded Number of lines that were successfully decoded + */ + void fillIncompleteImage(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, + ZeroInitialized zeroInit, int linesRequested, int linesDecoded); + + /** + * Return an object which will allow forcing scanline decodes to sample in X. + * + * May create a sampler, if one is not currently being used. Otherwise, does + * not affect ownership. + * + * Only valid during scanline decoding or incremental decoding. + */ + virtual SkSampler* getSampler(bool /*createIfNecessary*/) { return nullptr; } + + friend class DM::CodecSrc; // for fillIncompleteImage + friend class SkSampledCodec; + friend class SkIcoCodec; + friend class SkAndroidCodec; // for fEncodedInfo +}; +#endif // SkCodec_DEFINED diff --git a/gfx/skia/skia/include/codec/SkCodecAnimation.h b/gfx/skia/skia/include/codec/SkCodecAnimation.h new file mode 100644 index 0000000000..2ddb78deb3 --- /dev/null +++ b/gfx/skia/skia/include/codec/SkCodecAnimation.h @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodecAnimation_DEFINED +#define SkCodecAnimation_DEFINED + +namespace SkCodecAnimation { + /** + * This specifies how the next frame is based on this frame. + * + * Names are based on the GIF 89a spec. + * + * The numbers correspond to values in a GIF. + */ + enum class DisposalMethod { + /** + * The next frame should be drawn on top of this one. + * + * In a GIF, a value of 0 (not specified) is also treated as Keep. + */ + kKeep = 1, + + /** + * Similar to Keep, except the area inside this frame's rectangle + * should be cleared to the BackGround color (transparent) before + * drawing the next frame. + */ + kRestoreBGColor = 2, + + /** + * The next frame should be drawn on top of the previous frame - i.e. + * disregarding this one. + * + * In a GIF, a value of 4 is also treated as RestorePrevious. + */ + kRestorePrevious = 3, + }; +} +#endif // SkCodecAnimation_DEFINED diff --git a/gfx/skia/skia/include/codec/SkEncodedOrigin.h b/gfx/skia/skia/include/codec/SkEncodedOrigin.h new file mode 100644 index 0000000000..2eed1c6ac6 --- /dev/null +++ b/gfx/skia/skia/include/codec/SkEncodedOrigin.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEncodedOrigin_DEFINED +#define SkEncodedOrigin_DEFINED + +#include "include/core/SkMatrix.h" + +// These values match the orientation www.exif.org/Exif2-2.PDF. +enum SkEncodedOrigin { + kTopLeft_SkEncodedOrigin = 1, // Default + kTopRight_SkEncodedOrigin = 2, // Reflected across y-axis + kBottomRight_SkEncodedOrigin = 3, // Rotated 180 + kBottomLeft_SkEncodedOrigin = 4, // Reflected across x-axis + kLeftTop_SkEncodedOrigin = 5, // Reflected across x-axis, Rotated 90 CCW + kRightTop_SkEncodedOrigin = 6, // Rotated 90 CW + kRightBottom_SkEncodedOrigin = 7, // Reflected across x-axis, Rotated 90 CW + kLeftBottom_SkEncodedOrigin = 8, // Rotated 90 CCW + kDefault_SkEncodedOrigin = kTopLeft_SkEncodedOrigin, + kLast_SkEncodedOrigin = kLeftBottom_SkEncodedOrigin, +}; + +/** + * Given an encoded origin and the width and height of the source data, returns a matrix + * that transforms the source rectangle [0, 0, w, h] to a correctly oriented destination + * rectangle, with the upper left corner still at [0, 0]. + */ +static inline SkMatrix SkEncodedOriginToMatrix(SkEncodedOrigin origin, int w, int h) { + switch (origin) { + case kTopLeft_SkEncodedOrigin: return SkMatrix::I(); + case kTopRight_SkEncodedOrigin: return SkMatrix::MakeAll(-1, 0, w, 0, 1, 0, 0, 0, 1); + case kBottomRight_SkEncodedOrigin: return SkMatrix::MakeAll(-1, 0, w, 0, -1, h, 0, 0, 1); + case kBottomLeft_SkEncodedOrigin: return SkMatrix::MakeAll( 1, 0, 0, 0, -1, h, 0, 0, 1); + case kLeftTop_SkEncodedOrigin: return SkMatrix::MakeAll( 0, 1, 0, 1, 0, 0, 0, 0, 1); + case kRightTop_SkEncodedOrigin: return SkMatrix::MakeAll( 0, -1, h, 1, 0, 0, 0, 0, 1); + case kRightBottom_SkEncodedOrigin: return SkMatrix::MakeAll( 0, -1, h, -1, 0, w, 0, 0, 1); + case kLeftBottom_SkEncodedOrigin: return SkMatrix::MakeAll( 0, 1, 0, -1, 0, w, 0, 0, 1); + } + SK_ABORT("Unexpected origin"); +} + + +#endif // SkEncodedOrigin_DEFINED diff --git a/gfx/skia/skia/include/config/SkUserConfig.h b/gfx/skia/skia/include/config/SkUserConfig.h new file mode 100644 index 0000000000..b262143677 --- /dev/null +++ b/gfx/skia/skia/include/config/SkUserConfig.h @@ -0,0 +1,131 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkUserConfig_DEFINED +#define SkUserConfig_DEFINED + +/* SkTypes.h, the root of the public header files, includes SkPreConfig.h, + then SkUserConfig.h, then SkPostConfig.h. + + SkPreConfig.h runs first, and it is responsible for initializing certain + skia defines. + + SkPostConfig.h runs last, and its job is to just check that the final + defines are consistent (i.e. that we don't have mutually conflicting + defines). + + SkUserConfig.h (this file) runs in the middle. It gets to change or augment + the list of flags initially set in preconfig, and then postconfig checks + that everything still makes sense. + + Below are optional defines that add, subtract, or change default behavior + in Skia. Your port can locally edit this file to enable/disable flags as + you choose, or these can be delared on your command line (i.e. -Dfoo). + + By default, this include file will always default to having all of the flags + commented out, so including it will have no effect. +*/ + +/////////////////////////////////////////////////////////////////////////////// + +/* Skia has lots of debug-only code. Often this is just null checks or other + parameter checking, but sometimes it can be quite intrusive (e.g. check that + each 32bit pixel is in premultiplied form). This code can be very useful + during development, but will slow things down in a shipping product. + + By default, these mutually exclusive flags are defined in SkPreConfig.h, + based on the presence or absence of NDEBUG, but that decision can be changed + here. + */ +//#define SK_DEBUG +//#define SK_RELEASE + +/* To write debug messages to a console, skia will call SkDebugf(...) following + printf conventions (e.g. const char* format, ...). If you want to redirect + this to something other than printf, define yours here + */ +//#define SkDebugf(...) MyFunction(__VA_ARGS__) + +/* + * To specify a different default font cache limit, define this. If this is + * undefined, skia will use a built-in value. + */ +//#define SK_DEFAULT_FONT_CACHE_LIMIT (1024 * 1024) + +/* + * To specify the default size of the image cache, undefine this and set it to + * the desired value (in bytes). SkGraphics.h as a runtime API to set this + * value as well. If this is undefined, a built-in value will be used. + */ +//#define SK_DEFAULT_IMAGE_CACHE_LIMIT (1024 * 1024) + +/* Define this to set the upper limit for text to support LCD. Values that + are very large increase the cost in the font cache and draw slower, without + improving readability. If this is undefined, Skia will use its default + value (e.g. 48) + */ +//#define SK_MAX_SIZE_FOR_LCDTEXT 48 + +/* Change the kN32_SkColorType ordering to BGRA to work in X windows. + */ +//#define SK_R32_SHIFT 16 + + +/* Determines whether to build code that supports the GPU backend. Some classes + that are not GPU-specific, such as SkShader subclasses, have optional code + that is used allows them to interact with the GPU backend. If you'd like to + omit this code set SK_SUPPORT_GPU to 0. This also allows you to omit the gpu + directories from your include search path when you're not building the GPU + backend. Defaults to 1 (build the GPU code). + */ +//#define SK_SUPPORT_GPU 1 + +/* Skia makes use of histogram logging macros to trace the frequency of + * events. By default, Skia provides no-op versions of these macros. + * Skia consumers can provide their own definitions of these macros to + * integrate with their histogram collection backend. + */ +//#define SK_HISTOGRAM_BOOLEAN(name, value) +//#define SK_HISTOGRAM_ENUMERATION(name, value, boundary_value) + +#define MOZ_SKIA + +// On all platforms we have this byte order +#define SK_A32_SHIFT 24 +#define SK_R32_SHIFT 16 +#define SK_G32_SHIFT 8 +#define SK_B32_SHIFT 0 + +#define SK_ALLOW_STATIC_GLOBAL_INITIALIZERS 0 + +#define SK_RASTERIZE_EVEN_ROUNDING + +#define SK_SUPPORT_DEPRECATED_CLIPOPS + +#define SK_USE_FREETYPE_EMBOLDEN + +#define SK_SUPPORT_GPU 0 + +#ifndef MOZ_IMPLICIT +# ifdef MOZ_CLANG_PLUGIN +# define MOZ_IMPLICIT __attribute__((annotate("moz_implicit"))) +# else +# define MOZ_IMPLICIT +# endif +#endif + +#define SK_DISABLE_SLOW_DEBUG_VALIDATION 1 + +#define SK_IGNORE_MAC_BLENDING_MATCH_FIX + +#define I_ACKNOWLEDGE_SKIA_DOES_NOT_SUPPORT_BIG_ENDIAN + +#define SK_DISABLE_TYPEFACE_CACHE + +#endif diff --git a/gfx/skia/skia/include/core/SkAnnotation.h b/gfx/skia/skia/include/core/SkAnnotation.h new file mode 100644 index 0000000000..9048bb6b6d --- /dev/null +++ b/gfx/skia/skia/include/core/SkAnnotation.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAnnotation_DEFINED +#define SkAnnotation_DEFINED + +#include "include/core/SkTypes.h" + +class SkData; +struct SkPoint; +struct SkRect; +class SkCanvas; + +/** + * Annotate the canvas by associating the specified URL with the + * specified rectangle (in local coordinates, just like drawRect). + * + * If the backend of this canvas does not support annotations, this call is + * safely ignored. + * + * The caller is responsible for managing its ownership of the SkData. + */ +SK_API void SkAnnotateRectWithURL(SkCanvas*, const SkRect&, SkData*); + +/** + * Annotate the canvas by associating a name with the specified point. + * + * If the backend of this canvas does not support annotations, this call is + * safely ignored. + * + * The caller is responsible for managing its ownership of the SkData. + */ +SK_API void SkAnnotateNamedDestination(SkCanvas*, const SkPoint&, SkData*); + +/** + * Annotate the canvas by making the specified rectangle link to a named + * destination. + * + * If the backend of this canvas does not support annotations, this call is + * safely ignored. + * + * The caller is responsible for managing its ownership of the SkData. + */ +SK_API void SkAnnotateLinkToDestination(SkCanvas*, const SkRect&, SkData*); + +#endif diff --git a/gfx/skia/skia/include/core/SkBBHFactory.h b/gfx/skia/skia/include/core/SkBBHFactory.h new file mode 100644 index 0000000000..afd223fe9e --- /dev/null +++ b/gfx/skia/skia/include/core/SkBBHFactory.h @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBBHFactory_DEFINED +#define SkBBHFactory_DEFINED + +#include "include/core/SkTypes.h" +class SkBBoxHierarchy; +struct SkRect; + +class SK_API SkBBHFactory { +public: + /** + * Allocate a new SkBBoxHierarchy. Return NULL on failure. + */ + virtual SkBBoxHierarchy* operator()() const = 0; + virtual ~SkBBHFactory() {} +}; + +class SK_API SkRTreeFactory : public SkBBHFactory { +public: + SkBBoxHierarchy* operator()() const override; +private: + typedef SkBBHFactory INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkBitmap.h b/gfx/skia/skia/include/core/SkBitmap.h new file mode 100644 index 0000000000..d561321bc0 --- /dev/null +++ b/gfx/skia/skia/include/core/SkBitmap.h @@ -0,0 +1,1166 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmap_DEFINED +#define SkBitmap_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTileMode.h" + +struct SkMask; +struct SkIRect; +struct SkRect; +class SkPaint; +class SkPixelRef; +class SkShader; +class SkString; + +/** \class SkBitmap + SkBitmap describes a two-dimensional raster pixel array. SkBitmap is built on + SkImageInfo, containing integer width and height, SkColorType and SkAlphaType + describing the pixel format, and SkColorSpace describing the range of colors. + SkBitmap points to SkPixelRef, which describes the physical array of pixels. + SkImageInfo bounds may be located anywhere fully inside SkPixelRef bounds. + + SkBitmap can be drawn using SkCanvas. SkBitmap can be a drawing destination for SkCanvas + draw member functions. SkBitmap flexibility as a pixel container limits some + optimizations available to the target platform. + + If pixel array is primarily read-only, use SkImage for better performance. + If pixel array is primarily written to, use SkSurface for better performance. + + Declaring SkBitmap const prevents altering SkImageInfo: the SkBitmap height, width, + and so on cannot change. It does not affect SkPixelRef: a caller may write its + pixels. Declaring SkBitmap const affects SkBitmap configuration, not its contents. + + SkBitmap is not thread safe. Each thread must have its own copy of SkBitmap fields, + although threads may share the underlying pixel array. +*/ +class SK_API SkBitmap { +public: + class SK_API Allocator; + + /** Creates an empty SkBitmap without pixels, with kUnknown_SkColorType, + kUnknown_SkAlphaType, and with a width and height of zero. SkPixelRef origin is + set to (0, 0). SkBitmap is not volatile. + + Use setInfo() to associate SkColorType, SkAlphaType, width, and height + after SkBitmap has been created. + + @return empty SkBitmap + */ + SkBitmap(); + + /** Copies settings from src to returned SkBitmap. Shares pixels if src has pixels + allocated, so both bitmaps reference the same pixels. + + @param src SkBitmap to copy SkImageInfo, and share SkPixelRef + @return copy of src + */ + SkBitmap(const SkBitmap& src); + + /** Copies settings from src to returned SkBitmap. Moves ownership of src pixels to + SkBitmap. + + @param src SkBitmap to copy SkImageInfo, and reassign SkPixelRef + @return copy of src + */ + SkBitmap(SkBitmap&& src); + + /** Decrements SkPixelRef reference count, if SkPixelRef is not nullptr. + */ + ~SkBitmap(); + + /** Copies settings from src to returned SkBitmap. Shares pixels if src has pixels + allocated, so both bitmaps reference the same pixels. + + @param src SkBitmap to copy SkImageInfo, and share SkPixelRef + @return copy of src + */ + SkBitmap& operator=(const SkBitmap& src); + + /** Copies settings from src to returned SkBitmap. Moves ownership of src pixels to + SkBitmap. + + @param src SkBitmap to copy SkImageInfo, and reassign SkPixelRef + @return copy of src + */ + SkBitmap& operator=(SkBitmap&& src); + + /** Swaps the fields of the two bitmaps. + + @param other SkBitmap exchanged with original + */ + void swap(SkBitmap& other); + + /** Returns a constant reference to the SkPixmap holding the SkBitmap pixel + address, row bytes, and SkImageInfo. + + @return reference to SkPixmap describing this SkBitmap + */ + const SkPixmap& pixmap() const { return fPixmap; } + + /** Returns width, height, SkAlphaType, SkColorType, and SkColorSpace. + + @return reference to SkImageInfo + */ + const SkImageInfo& info() const { return fPixmap.info(); } + + /** Returns pixel count in each row. Should be equal or less than + rowBytes() / info().bytesPerPixel(). + + May be less than pixelRef().width(). Will not exceed pixelRef().width() less + pixelRefOrigin().fX. + + @return pixel width in SkImageInfo + */ + int width() const { return fPixmap.width(); } + + /** Returns pixel row count. + + Maybe be less than pixelRef().height(). Will not exceed pixelRef().height() less + pixelRefOrigin().fY. + + @return pixel height in SkImageInfo + */ + int height() const { return fPixmap.height(); } + + /** Returns SkColorType, one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType. + + @return SkColorType in SkImageInfo + */ + SkColorType colorType() const { return fPixmap.colorType(); } + + /** Returns SkAlphaType, one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType. + + @return SkAlphaType in SkImageInfo + */ + SkAlphaType alphaType() const { return fPixmap.alphaType(); } + + /** Returns SkColorSpace, the range of colors, associated with SkImageInfo. The + reference count of SkColorSpace is unchanged. The returned SkColorSpace is + immutable. + + @return SkColorSpace in SkImageInfo, or nullptr + */ + SkColorSpace* colorSpace() const { return fPixmap.colorSpace(); } + + /** Returns smart pointer to SkColorSpace, the range of colors, associated with + SkImageInfo. The smart pointer tracks the number of objects sharing this + SkColorSpace reference so the memory is released when the owners destruct. + + The returned SkColorSpace is immutable. + + @return SkColorSpace in SkImageInfo wrapped in a smart pointer + */ + sk_sp refColorSpace() const { return fPixmap.info().refColorSpace(); } + + /** Returns number of bytes per pixel required by SkColorType. + Returns zero if colorType( is kUnknown_SkColorType. + + @return bytes in pixel + */ + int bytesPerPixel() const { return fPixmap.info().bytesPerPixel(); } + + /** Returns number of pixels that fit on row. Should be greater than or equal to + width(). + + @return maximum pixels per row + */ + int rowBytesAsPixels() const { return fPixmap.rowBytesAsPixels(); } + + /** Returns bit shift converting row bytes to row pixels. + Returns zero for kUnknown_SkColorType. + + @return one of: 0, 1, 2, 3; left shift to convert pixels to bytes + */ + int shiftPerPixel() const { return fPixmap.shiftPerPixel(); } + + /** Returns true if either width() or height() are zero. + + Does not check if SkPixelRef is nullptr; call drawsNothing() to check width(), + height(), and SkPixelRef. + + @return true if dimensions do not enclose area + */ + bool empty() const { return fPixmap.info().isEmpty(); } + + /** Returns true if SkPixelRef is nullptr. + + Does not check if width() or height() are zero; call drawsNothing() to check + width(), height(), and SkPixelRef. + + @return true if no SkPixelRef is associated + */ + bool isNull() const { return nullptr == fPixelRef; } + + /** Returns true if width() or height() are zero, or if SkPixelRef is nullptr. + If true, SkBitmap has no effect when drawn or drawn into. + + @return true if drawing has no effect + */ + bool drawsNothing() const { + return this->empty() || this->isNull(); + } + + /** Returns row bytes, the interval from one pixel row to the next. Row bytes + is at least as large as: width() * info().bytesPerPixel(). + + Returns zero if colorType() is kUnknown_SkColorType, or if row bytes supplied to + setInfo() is not large enough to hold a row of pixels. + + @return byte length of pixel row + */ + size_t rowBytes() const { return fPixmap.rowBytes(); } + + /** Sets SkAlphaType, if alphaType is compatible with SkColorType. + Returns true unless alphaType is kUnknown_SkAlphaType and current SkAlphaType + is not kUnknown_SkAlphaType. + + Returns true if SkColorType is kUnknown_SkColorType. alphaType is ignored, and + SkAlphaType remains kUnknown_SkAlphaType. + + Returns true if SkColorType is kRGB_565_SkColorType or kGray_8_SkColorType. + alphaType is ignored, and SkAlphaType remains kOpaque_SkAlphaType. + + If SkColorType is kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kBGRA_8888_SkColorType, or kRGBA_F16_SkColorType: returns true unless + alphaType is kUnknown_SkAlphaType and SkAlphaType is not kUnknown_SkAlphaType. + If SkAlphaType is kUnknown_SkAlphaType, alphaType is ignored. + + If SkColorType is kAlpha_8_SkColorType, returns true unless + alphaType is kUnknown_SkAlphaType and SkAlphaType is not kUnknown_SkAlphaType. + If SkAlphaType is kUnknown_SkAlphaType, alphaType is ignored. If alphaType is + kUnpremul_SkAlphaType, it is treated as kPremul_SkAlphaType. + + This changes SkAlphaType in SkPixelRef; all bitmaps sharing SkPixelRef + are affected. + + @param alphaType one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @return true if SkAlphaType is set + */ + bool setAlphaType(SkAlphaType alphaType); + + /** Returns pixel address, the base address corresponding to the pixel origin. + + @return pixel address + */ + void* getPixels() const { return fPixmap.writable_addr(); } + + /** Returns minimum memory required for pixel storage. + Does not include unused memory on last row when rowBytesAsPixels() exceeds width(). + Returns SIZE_MAX if result does not fit in size_t. + Returns zero if height() or width() is 0. + Returns height() times rowBytes() if colorType() is kUnknown_SkColorType. + + @return size in bytes of image buffer + */ + size_t computeByteSize() const { return fPixmap.computeByteSize(); } + + /** Returns true if pixels can not change. + + Most immutable SkBitmap checks trigger an assert only on debug builds. + + @return true if pixels are immutable + */ + bool isImmutable() const; + + /** Sets internal flag to mark SkBitmap as immutable. Once set, pixels can not change. + Any other bitmap sharing the same SkPixelRef are also marked as immutable. + Once SkPixelRef is marked immutable, the setting cannot be cleared. + + Writing to immutable SkBitmap pixels triggers an assert on debug builds. + */ + void setImmutable(); + + /** Returns true if SkAlphaType is set to hint that all pixels are opaque; their + alpha value is implicitly or explicitly 1.0. If true, and all pixels are + not opaque, Skia may draw incorrectly. + + Does not check if SkColorType allows alpha, or if any pixel value has + transparency. + + @return true if SkImageInfo SkAlphaType is kOpaque_SkAlphaType + */ + bool isOpaque() const { + return SkAlphaTypeIsOpaque(this->alphaType()); + } + + /** Provides a hint to caller that pixels should not be cached. Only true if + setIsVolatile() has been called to mark as volatile. + + Volatile state is not shared by other bitmaps sharing the same SkPixelRef. + + @return true if marked volatile + */ + bool isVolatile() const; + + /** Sets if pixels should be read from SkPixelRef on every access. SkBitmap are not + volatile by default; a GPU back end may upload pixel values expecting them to be + accessed repeatedly. Marking temporary SkBitmap as volatile provides a hint to + SkBaseDevice that the SkBitmap pixels should not be cached. This can + improve performance by avoiding overhead and reducing resource + consumption on SkBaseDevice. + + @param isVolatile true if backing pixels are temporary + */ + void setIsVolatile(bool isVolatile); + + /** Resets to its initial state; all fields are set to zero, as if SkBitmap had + been initialized by SkBitmap(). + + Sets width, height, row bytes to zero; pixel address to nullptr; SkColorType to + kUnknown_SkColorType; and SkAlphaType to kUnknown_SkAlphaType. + + If SkPixelRef is allocated, its reference count is decreased by one, releasing + its memory if SkBitmap is the sole owner. + */ + void reset(); + + /** Returns true if all pixels are opaque. SkColorType determines how pixels + are encoded, and whether pixel describes alpha. Returns true for SkColorType + without alpha in each pixel; for other SkColorType, returns true if all + pixels have alpha values equivalent to 1.0 or greater. + + For SkColorType kRGB_565_SkColorType or kGray_8_SkColorType: always + returns true. For SkColorType kAlpha_8_SkColorType, kBGRA_8888_SkColorType, + kRGBA_8888_SkColorType: returns true if all pixel alpha values are 255. + For SkColorType kARGB_4444_SkColorType: returns true if all pixel alpha values are 15. + For kRGBA_F16_SkColorType: returns true if all pixel alpha values are 1.0 or + greater. + + Returns false for kUnknown_SkColorType. + + @param bm SkBitmap to check + @return true if all pixels have opaque values or SkColorType is opaque + */ + static bool ComputeIsOpaque(const SkBitmap& bm) { + return bm.pixmap().computeIsOpaque(); + } + + /** Returns SkRect { 0, 0, width(), height() }. + + @param bounds container for floating point rectangle + */ + void getBounds(SkRect* bounds) const; + + /** Returns SkIRect { 0, 0, width(), height() }. + + @param bounds container for integral rectangle + */ + void getBounds(SkIRect* bounds) const; + + /** Returns SkIRect { 0, 0, width(), height() }. + + @return integral rectangle from origin to width() and height() + */ + SkIRect bounds() const { return fPixmap.info().bounds(); } + + /** Returns SkISize { width(), height() }. + + @return integral size of width() and height() + */ + SkISize dimensions() const { return fPixmap.info().dimensions(); } + + /** Returns the bounds of this bitmap, offset by its SkPixelRef origin. + + @return bounds within SkPixelRef bounds + */ + SkIRect getSubset() const { + SkIPoint origin = this->pixelRefOrigin(); + return SkIRect::MakeXYWH(origin.x(), origin.y(), this->width(), this->height()); + } + + /** Sets width, height, SkAlphaType, SkColorType, SkColorSpace, and optional + rowBytes. Frees pixels, and returns true if successful. + + imageInfo.alphaType() may be altered to a value permitted by imageInfo.colorSpace(). + If imageInfo.colorType() is kUnknown_SkColorType, imageInfo.alphaType() is + set to kUnknown_SkAlphaType. + If imageInfo.colorType() is kAlpha_8_SkColorType and imageInfo.alphaType() is + kUnpremul_SkAlphaType, imageInfo.alphaType() is replaced by kPremul_SkAlphaType. + If imageInfo.colorType() is kRGB_565_SkColorType or kGray_8_SkColorType, + imageInfo.alphaType() is set to kOpaque_SkAlphaType. + If imageInfo.colorType() is kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kBGRA_8888_SkColorType, or kRGBA_F16_SkColorType: imageInfo.alphaType() remains + unchanged. + + rowBytes must equal or exceed imageInfo.minRowBytes(). If imageInfo.colorSpace() is + kUnknown_SkColorType, rowBytes is ignored and treated as zero; for all other + SkColorSpace values, rowBytes of zero is treated as imageInfo.minRowBytes(). + + Calls reset() and returns false if: + - rowBytes exceeds 31 bits + - imageInfo.width() is negative + - imageInfo.height() is negative + - rowBytes is positive and less than imageInfo.width() times imageInfo.bytesPerPixel() + + @param imageInfo contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param rowBytes imageInfo.minRowBytes() or larger; or zero + @return true if SkImageInfo set successfully + */ + bool setInfo(const SkImageInfo& imageInfo, size_t rowBytes = 0); + + /** \enum SkBitmap::AllocFlags + AllocFlags is obsolete. We always zero pixel memory when allocated. + */ + enum AllocFlags { + kZeroPixels_AllocFlag = 1 << 0, //!< zero pixel memory. No effect. This is the default. + }; + + /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel + memory. Memory is zeroed. + + Returns false and calls reset() if SkImageInfo could not be set, or memory could + not be allocated, or memory could not optionally be zeroed. + + On most platforms, allocating pixel memory may succeed even though there is + not sufficient memory to hold pixels; allocation does not take place + until the pixels are written to. The actual behavior depends on the platform + implementation of calloc(). + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param flags kZeroPixels_AllocFlag, or zero + @return true if pixels allocation is successful + */ + bool SK_WARN_UNUSED_RESULT tryAllocPixelsFlags(const SkImageInfo& info, uint32_t flags); + + /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel + memory. Memory is zeroed. + + Aborts execution if SkImageInfo could not be set, or memory could + not be allocated, or memory could not optionally + be zeroed. Abort steps may be provided by the user at compile time by defining + SK_ABORT. + + On most platforms, allocating pixel memory may succeed even though there is + not sufficient memory to hold pixels; allocation does not take place + until the pixels are written to. The actual behavior depends on the platform + implementation of calloc(). + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param flags kZeroPixels_AllocFlag, or zero + */ + void allocPixelsFlags(const SkImageInfo& info, uint32_t flags); + + /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel + memory. rowBytes must equal or exceed info.width() times info.bytesPerPixel(), + or equal zero. Pass in zero for rowBytes to compute the minimum valid value. + + Returns false and calls reset() if SkImageInfo could not be set, or memory could + not be allocated. + + On most platforms, allocating pixel memory may succeed even though there is + not sufficient memory to hold pixels; allocation does not take place + until the pixels are written to. The actual behavior depends on the platform + implementation of malloc(). + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param rowBytes size of pixel row or larger; may be zero + @return true if pixel storage is allocated + */ + bool SK_WARN_UNUSED_RESULT tryAllocPixels(const SkImageInfo& info, size_t rowBytes); + + /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel + memory. rowBytes must equal or exceed info.width() times info.bytesPerPixel(), + or equal zero. Pass in zero for rowBytes to compute the minimum valid value. + + Aborts execution if SkImageInfo could not be set, or memory could + not be allocated. Abort steps may be provided by + the user at compile time by defining SK_ABORT. + + On most platforms, allocating pixel memory may succeed even though there is + not sufficient memory to hold pixels; allocation does not take place + until the pixels are written to. The actual behavior depends on the platform + implementation of malloc(). + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param rowBytes size of pixel row or larger; may be zero + */ + void allocPixels(const SkImageInfo& info, size_t rowBytes); + + /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel + memory. + + Returns false and calls reset() if SkImageInfo could not be set, or memory could + not be allocated. + + On most platforms, allocating pixel memory may succeed even though there is + not sufficient memory to hold pixels; allocation does not take place + until the pixels are written to. The actual behavior depends on the platform + implementation of malloc(). + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @return true if pixel storage is allocated + */ + bool SK_WARN_UNUSED_RESULT tryAllocPixels(const SkImageInfo& info) { + return this->tryAllocPixels(info, info.minRowBytes()); + } + + /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel + memory. + + Aborts execution if SkImageInfo could not be set, or memory could + not be allocated. Abort steps may be provided by + the user at compile time by defining SK_ABORT. + + On most platforms, allocating pixel memory may succeed even though there is + not sufficient memory to hold pixels; allocation does not take place + until the pixels are written to. The actual behavior depends on the platform + implementation of malloc(). + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + */ + void allocPixels(const SkImageInfo& info); + + /** Sets SkImageInfo to width, height, and native color type; and allocates + pixel memory. If isOpaque is true, sets SkImageInfo to kOpaque_SkAlphaType; + otherwise, sets to kPremul_SkAlphaType. + + Calls reset() and returns false if width exceeds 29 bits or is negative, + or height is negative. + + Returns false if allocation fails. + + Use to create SkBitmap that matches SkPMColor, the native pixel arrangement on + the platform. SkBitmap drawn to output device skips converting its pixel format. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @param isOpaque true if pixels do not have transparency + @return true if pixel storage is allocated + */ + bool SK_WARN_UNUSED_RESULT tryAllocN32Pixels(int width, int height, bool isOpaque = false); + + /** Sets SkImageInfo to width, height, and the native color type; and allocates + pixel memory. If isOpaque is true, sets SkImageInfo to kOpaque_SkAlphaType; + otherwise, sets to kPremul_SkAlphaType. + + Aborts if width exceeds 29 bits or is negative, or height is negative, or + allocation fails. Abort steps may be provided by the user at compile time by + defining SK_ABORT. + + Use to create SkBitmap that matches SkPMColor, the native pixel arrangement on + the platform. SkBitmap drawn to output device skips converting its pixel format. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @param isOpaque true if pixels do not have transparency + */ + void allocN32Pixels(int width, int height, bool isOpaque = false); + + /** Sets SkImageInfo to info following the rules in setInfo(), and creates SkPixelRef + containing pixels and rowBytes. releaseProc, if not nullptr, is called + immediately on failure or when pixels are no longer referenced. context may be + nullptr. + + If SkImageInfo could not be set, or rowBytes is less than info.minRowBytes(): + calls releaseProc if present, calls reset(), and returns false. + + Otherwise, if pixels equals nullptr: sets SkImageInfo, calls releaseProc if + present, returns true. + + If SkImageInfo is set, pixels is not nullptr, and releaseProc is not nullptr: + when pixels are no longer referenced, calls releaseProc with pixels and context + as parameters. + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param pixels address or pixel storage; may be nullptr + @param rowBytes size of pixel row or larger + @param releaseProc function called when pixels can be deleted; may be nullptr + @param context caller state passed to releaseProc; may be nullptr + @return true if SkImageInfo is set to info + */ + bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + void (*releaseProc)(void* addr, void* context), void* context); + + /** Sets SkImageInfo to info following the rules in setInfo(), and creates SkPixelRef + containing pixels and rowBytes. + + If SkImageInfo could not be set, or rowBytes is less than info.minRowBytes(): + calls reset(), and returns false. + + Otherwise, if pixels equals nullptr: sets SkImageInfo, returns true. + + Caller must ensure that pixels are valid for the lifetime of SkBitmap and SkPixelRef. + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param pixels address or pixel storage; may be nullptr + @param rowBytes size of pixel row or larger + @return true if SkImageInfo is set to info + */ + bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { + return this->installPixels(info, pixels, rowBytes, nullptr, nullptr); + } + + /** Sets SkImageInfo to pixmap.info() following the rules in setInfo(), and creates + SkPixelRef containing pixmap.addr() and pixmap.rowBytes(). + + If SkImageInfo could not be set, or pixmap.rowBytes() is less than + SkImageInfo::minRowBytes(): calls reset(), and returns false. + + Otherwise, if pixmap.addr() equals nullptr: sets SkImageInfo, returns true. + + Caller must ensure that pixmap is valid for the lifetime of SkBitmap and SkPixelRef. + + @param pixmap SkImageInfo, pixel address, and rowBytes() + @return true if SkImageInfo was set to pixmap.info() + */ + bool installPixels(const SkPixmap& pixmap); + + /** Deprecated. + */ + bool installMaskPixels(const SkMask& mask); + + /** Replaces SkPixelRef with pixels, preserving SkImageInfo and rowBytes(). + Sets SkPixelRef origin to (0, 0). + + If pixels is nullptr, or if info().colorType() equals kUnknown_SkColorType; + release reference to SkPixelRef, and set SkPixelRef to nullptr. + + Caller is responsible for handling ownership pixel memory for the lifetime + of SkBitmap and SkPixelRef. + + @param pixels address of pixel storage, managed by caller + */ + void setPixels(void* pixels); + + /** Allocates pixel memory with HeapAllocator, and replaces existing SkPixelRef. + The allocation size is determined by SkImageInfo width, height, and SkColorType. + + Returns false if info().colorType() is kUnknown_SkColorType, or allocation fails. + + @return true if the allocation succeeds + */ + bool SK_WARN_UNUSED_RESULT tryAllocPixels() { + return this->tryAllocPixels((Allocator*)nullptr); + } + + /** Allocates pixel memory with HeapAllocator, and replaces existing SkPixelRef. + The allocation size is determined by SkImageInfo width, height, and SkColorType. + + Aborts if info().colorType() is kUnknown_SkColorType, or allocation fails. + Abort steps may be provided by the user at compile + time by defining SK_ABORT. + */ + void allocPixels(); + + /** Allocates pixel memory with allocator, and replaces existing SkPixelRef. + The allocation size is determined by SkImageInfo width, height, and SkColorType. + If allocator is nullptr, use HeapAllocator instead. + + Returns false if Allocator::allocPixelRef return false. + + @param allocator instance of SkBitmap::Allocator instantiation + @return true if custom allocator reports success + */ + bool SK_WARN_UNUSED_RESULT tryAllocPixels(Allocator* allocator); + + /** Allocates pixel memory with allocator, and replaces existing SkPixelRef. + The allocation size is determined by SkImageInfo width, height, and SkColorType. + If allocator is nullptr, use HeapAllocator instead. + + Aborts if Allocator::allocPixelRef return false. Abort steps may be provided by + the user at compile time by defining SK_ABORT. + + @param allocator instance of SkBitmap::Allocator instantiation + */ + void allocPixels(Allocator* allocator); + + /** Returns SkPixelRef, which contains: pixel base address; its dimensions; and + rowBytes(), the interval from one row to the next. Does not change SkPixelRef + reference count. SkPixelRef may be shared by multiple bitmaps. + If SkPixelRef has not been set, returns nullptr. + + @return SkPixelRef, or nullptr + */ + SkPixelRef* pixelRef() const { return fPixelRef.get(); } + + /** Returns origin of pixels within SkPixelRef. SkBitmap bounds is always contained + by SkPixelRef bounds, which may be the same size or larger. Multiple SkBitmap + can share the same SkPixelRef, where each SkBitmap has different bounds. + + The returned origin added to SkBitmap dimensions equals or is smaller than the + SkPixelRef dimensions. + + Returns (0, 0) if SkPixelRef is nullptr. + + @return pixel origin within SkPixelRef + */ + SkIPoint pixelRefOrigin() const; + + /** Replaces pixelRef and origin in SkBitmap. dx and dy specify the offset + within the SkPixelRef pixels for the top-left corner of the bitmap. + + Asserts in debug builds if dx or dy are out of range. Pins dx and dy + to legal range in release builds. + + The caller is responsible for ensuring that the pixels match the + SkColorType and SkAlphaType in SkImageInfo. + + @param pixelRef SkPixelRef describing pixel address and rowBytes() + @param dx column offset in SkPixelRef for bitmap origin + @param dy row offset in SkPixelRef for bitmap origin + */ + void setPixelRef(sk_sp pixelRef, int dx, int dy); + + /** Returns true if SkBitmap is can be drawn. + + @return true if getPixels() is not nullptr + */ + bool readyToDraw() const { + return this->getPixels() != nullptr; + } + + /** Returns a unique value corresponding to the pixels in SkPixelRef. + Returns a different value after notifyPixelsChanged() has been called. + Returns zero if SkPixelRef is nullptr. + + Determines if pixels have changed since last examined. + + @return unique value for pixels in SkPixelRef + */ + uint32_t getGenerationID() const; + + /** Marks that pixels in SkPixelRef have changed. Subsequent calls to + getGenerationID() return a different value. + */ + void notifyPixelsChanged() const; + + /** Replaces pixel values with c. All pixels contained by bounds() are affected. + If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then alpha + is ignored; RGB is treated as opaque. If colorType() is kAlpha_8_SkColorType, + then RGB is ignored. + + @param c unpremultiplied color + */ + void eraseColor(SkColor c) const; + + /** Replaces pixel values with unpremultiplied color built from a, r, g, and b. + All pixels contained by bounds() are affected. + If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then a + is ignored; r, g, and b are treated as opaque. If colorType() is kAlpha_8_SkColorType, + then r, g, and b are ignored. + + @param a amount of alpha, from fully transparent (0) to fully opaque (255) + @param r amount of red, from no red (0) to full red (255) + @param g amount of green, from no green (0) to full green (255) + @param b amount of blue, from no blue (0) to full blue (255) + */ + void eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const { + this->eraseColor(SkColorSetARGB(a, r, g, b)); + } + + /** Replaces pixel values inside area with c. If area does not intersect bounds(), + call has no effect. + + If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then alpha + is ignored; RGB is treated as opaque. If colorType() is kAlpha_8_SkColorType, + then RGB is ignored. + + @param c unpremultiplied color + @param area rectangle to fill + */ + void erase(SkColor c, const SkIRect& area) const; + + /** Deprecated. + */ + void eraseArea(const SkIRect& area, SkColor c) const { + this->erase(c, area); + } + + /** Returns pixel at (x, y) as unpremultiplied color. + Returns black with alpha if SkColorType is kAlpha_8_SkColorType. + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined; and returns undefined values or may crash if + SK_RELEASE is defined. Fails if SkColorType is kUnknown_SkColorType or + pixel address is nullptr. + + SkColorSpace in SkImageInfo is ignored. Some color precision may be lost in the + conversion to unpremultiplied color; original pixel data may have additional + precision. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return pixel converted to unpremultiplied color + */ + SkColor getColor(int x, int y) const { + return this->pixmap().getColor(x, y); + } + + /** Look up the pixel at (x,y) and return its alpha component, normalized to [0..1]. + This is roughly equivalent to SkGetColorA(getColor()), but can be more efficent + (and more precise if the pixels store more than 8 bits per component). + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return alpha converted to normalized float + */ + float getAlphaf(int x, int y) const { + return this->pixmap().getAlphaf(x, y); + } + + /** Returns pixel address at (x, y). + + Input is not validated: out of bounds values of x or y, or kUnknown_SkColorType, + trigger an assert() if built with SK_DEBUG defined. Returns nullptr if + SkColorType is kUnknown_SkColorType, or SkPixelRef is nullptr. + + Performs a lookup of pixel size; for better performance, call + one of: getAddr8(), getAddr16(), or getAddr32(). + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return generic pointer to pixel + */ + void* getAddr(int x, int y) const; + + /** Returns address at (x, y). + + Input is not validated. Triggers an assert() if built with SK_DEBUG defined and: + - SkPixelRef is nullptr + - bytesPerPixel() is not four + - x is negative, or not less than width() + - y is negative, or not less than height() + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return unsigned 32-bit pointer to pixel at (x, y) + */ + inline uint32_t* getAddr32(int x, int y) const; + + /** Returns address at (x, y). + + Input is not validated. Triggers an assert() if built with SK_DEBUG defined and: + - SkPixelRef is nullptr + - bytesPerPixel() is not two + - x is negative, or not less than width() + - y is negative, or not less than height() + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return unsigned 16-bit pointer to pixel at (x, y) + */ + inline uint16_t* getAddr16(int x, int y) const; + + /** Returns address at (x, y). + + Input is not validated. Triggers an assert() if built with SK_DEBUG defined and: + - SkPixelRef is nullptr + - bytesPerPixel() is not one + - x is negative, or not less than width() + - y is negative, or not less than height() + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return unsigned 8-bit pointer to pixel at (x, y) + */ + inline uint8_t* getAddr8(int x, int y) const; + + /** Shares SkPixelRef with dst. Pixels are not copied; SkBitmap and dst point + to the same pixels; dst bounds() are set to the intersection of subset + and the original bounds(). + + subset may be larger than bounds(). Any area outside of bounds() is ignored. + + Any contents of dst are discarded. isVolatile() setting is copied to dst. + dst is set to colorType(), alphaType(), and colorSpace(). + + Return false if: + - dst is nullptr + - SkPixelRef is nullptr + - subset does not intersect bounds() + + @param dst SkBitmap set to subset + @param subset rectangle of pixels to reference + @return true if dst is replaced by subset + */ + bool extractSubset(SkBitmap* dst, const SkIRect& subset) const; + + /** Copies a SkRect of pixels from SkBitmap to dstPixels. Copy starts at (srcX, srcY), + and does not exceed SkBitmap (width(), height()). + + dstInfo specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of + destination. dstRowBytes specifics the gap from one destination row to the next. + Returns true if pixels are copied. Returns false if: + - dstInfo has no address + - dstRowBytes is less than dstInfo.minRowBytes() + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. + If SkBitmap colorType() is kGray_8_SkColorType, dstInfo.colorSpace() must match. + If SkBitmap alphaType() is kOpaque_SkAlphaType, dstInfo.alphaType() must + match. If SkBitmap colorSpace() is nullptr, dstInfo.colorSpace() must match. Returns + false if pixel conversion is not possible. + + srcX and srcY may be negative to copy only top or left of source. Returns + false if width() or height() is zero or negative. + Returns false if abs(srcX) >= Bitmap width(), or if abs(srcY) >= Bitmap height(). + + @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace + @param dstPixels destination pixel storage + @param dstRowBytes destination row length + @param srcX column index whose absolute value is less than width() + @param srcY row index whose absolute value is less than height() + @return true if pixels are copied to dstPixels + */ + bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY) const; + + /** Copies a SkRect of pixels from SkBitmap to dst. Copy starts at (srcX, srcY), and + does not exceed SkBitmap (width(), height()). + + dst specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, + and row bytes of destination. dst.rowBytes() specifics the gap from one destination + row to the next. Returns true if pixels are copied. Returns false if: + - dst pixel storage equals nullptr + - dst.rowBytes is less than SkImageInfo::minRowBytes() + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. + If SkBitmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. + If SkBitmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must + match. If SkBitmap colorSpace() is nullptr, dst SkColorSpace must match. Returns + false if pixel conversion is not possible. + + srcX and srcY may be negative to copy only top or left of source. Returns + false if width() or height() is zero or negative. + Returns false if abs(srcX) >= Bitmap width(), or if abs(srcY) >= Bitmap height(). + + @param dst destination SkPixmap: SkImageInfo, pixels, row bytes + @param srcX column index whose absolute value is less than width() + @param srcY row index whose absolute value is less than height() + @return true if pixels are copied to dst + */ + bool readPixels(const SkPixmap& dst, int srcX, int srcY) const; + + /** Copies a SkRect of pixels from SkBitmap to dst. Copy starts at (0, 0), and + does not exceed SkBitmap (width(), height()). + + dst specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, + and row bytes of destination. dst.rowBytes() specifics the gap from one destination + row to the next. Returns true if pixels are copied. Returns false if: + - dst pixel storage equals nullptr + - dst.rowBytes is less than SkImageInfo::minRowBytes() + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. + If SkBitmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. + If SkBitmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must + match. If SkBitmap colorSpace() is nullptr, dst SkColorSpace must match. Returns + false if pixel conversion is not possible. + + @param dst destination SkPixmap: SkImageInfo, pixels, row bytes + @return true if pixels are copied to dst + */ + bool readPixels(const SkPixmap& dst) const { + return this->readPixels(dst, 0, 0); + } + + /** Copies a SkRect of pixels from src. Copy starts at (dstX, dstY), and does not exceed + (src.width(), src.height()). + + src specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, + and row bytes of source. src.rowBytes() specifics the gap from one source + row to the next. Returns true if pixels are copied. Returns false if: + - src pixel storage equals nullptr + - src.rowBytes is less than SkImageInfo::minRowBytes() + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; src SkColorType must match. + If SkBitmap colorType() is kGray_8_SkColorType, src SkColorSpace must match. + If SkBitmap alphaType() is kOpaque_SkAlphaType, src SkAlphaType must + match. If SkBitmap colorSpace() is nullptr, src SkColorSpace must match. Returns + false if pixel conversion is not possible. + + dstX and dstY may be negative to copy only top or left of source. Returns + false if width() or height() is zero or negative. + Returns false if abs(dstX) >= Bitmap width(), or if abs(dstY) >= Bitmap height(). + + @param src source SkPixmap: SkImageInfo, pixels, row bytes + @param dstX column index whose absolute value is less than width() + @param dstY row index whose absolute value is less than height() + @return true if src pixels are copied to SkBitmap + */ + bool writePixels(const SkPixmap& src, int dstX, int dstY); + + /** Copies a SkRect of pixels from src. Copy starts at (0, 0), and does not exceed + (src.width(), src.height()). + + src specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, + and row bytes of source. src.rowBytes() specifics the gap from one source + row to the next. Returns true if pixels are copied. Returns false if: + - src pixel storage equals nullptr + - src.rowBytes is less than SkImageInfo::minRowBytes() + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; src SkColorType must match. + If SkBitmap colorType() is kGray_8_SkColorType, src SkColorSpace must match. + If SkBitmap alphaType() is kOpaque_SkAlphaType, src SkAlphaType must + match. If SkBitmap colorSpace() is nullptr, src SkColorSpace must match. Returns + false if pixel conversion is not possible. + + @param src source SkPixmap: SkImageInfo, pixels, row bytes + @return true if src pixels are copied to SkBitmap + */ + bool writePixels(const SkPixmap& src) { + return this->writePixels(src, 0, 0); + } + + /** Sets dst to alpha described by pixels. Returns false if dst cannot be written to + or dst pixels cannot be allocated. + + Uses HeapAllocator to reserve memory for dst SkPixelRef. + + @param dst holds SkPixelRef to fill with alpha layer + @return true if alpha layer was constructed in dst SkPixelRef + */ + bool extractAlpha(SkBitmap* dst) const { + return this->extractAlpha(dst, nullptr, nullptr, nullptr); + } + + /** Sets dst to alpha described by pixels. Returns false if dst cannot be written to + or dst pixels cannot be allocated. + + If paint is not nullptr and contains SkMaskFilter, SkMaskFilter + generates mask alpha from SkBitmap. Uses HeapAllocator to reserve memory for dst + SkPixelRef. Sets offset to top-left position for dst for alignment with SkBitmap; + (0, 0) unless SkMaskFilter generates mask. + + @param dst holds SkPixelRef to fill with alpha layer + @param paint holds optional SkMaskFilter; may be nullptr + @param offset top-left position for dst; may be nullptr + @return true if alpha layer was constructed in dst SkPixelRef + */ + bool extractAlpha(SkBitmap* dst, const SkPaint* paint, + SkIPoint* offset) const { + return this->extractAlpha(dst, paint, nullptr, offset); + } + + /** Sets dst to alpha described by pixels. Returns false if dst cannot be written to + or dst pixels cannot be allocated. + + If paint is not nullptr and contains SkMaskFilter, SkMaskFilter + generates mask alpha from SkBitmap. allocator may reference a custom allocation + class or be set to nullptr to use HeapAllocator. Sets offset to top-left + position for dst for alignment with SkBitmap; (0, 0) unless SkMaskFilter generates + mask. + + @param dst holds SkPixelRef to fill with alpha layer + @param paint holds optional SkMaskFilter; may be nullptr + @param allocator function to reserve memory for SkPixelRef; may be nullptr + @param offset top-left position for dst; may be nullptr + @return true if alpha layer was constructed in dst SkPixelRef + */ + bool extractAlpha(SkBitmap* dst, const SkPaint* paint, Allocator* allocator, + SkIPoint* offset) const; + + /** Copies SkBitmap pixel address, row bytes, and SkImageInfo to pixmap, if address + is available, and returns true. If pixel address is not available, return + false and leave pixmap unchanged. + + pixmap contents become invalid on any future change to SkBitmap. + + @param pixmap storage for pixel state if pixels are readable; otherwise, ignored + @return true if SkBitmap has direct access to pixels + */ + bool peekPixels(SkPixmap* pixmap) const; + + sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix = nullptr) const; + // defaults to Clamp in x, and y + sk_sp makeShader(const SkMatrix* localMatrix = nullptr) const; + + /** Asserts if internal values are illegal or inconsistent. Only available if + SK_DEBUG is defined at compile time. + */ + SkDEBUGCODE(void validate() const;) + + /** \class SkBitmap::Allocator + Abstract subclass of HeapAllocator. + */ + class Allocator : public SkRefCnt { + public: + + /** Allocates the pixel memory for the bitmap, given its dimensions and + SkColorType. Returns true on success, where success means either setPixels() + or setPixelRef() was called. + + @param bitmap SkBitmap containing SkImageInfo as input, and SkPixelRef as output + @return true if SkPixelRef was allocated + */ + virtual bool allocPixelRef(SkBitmap* bitmap) = 0; + private: + typedef SkRefCnt INHERITED; + }; + + /** \class SkBitmap::HeapAllocator + Subclass of SkBitmap::Allocator that returns a SkPixelRef that allocates its pixel + memory from the heap. This is the default SkBitmap::Allocator invoked by + allocPixels(). + */ + class HeapAllocator : public Allocator { + public: + + /** Allocates the pixel memory for the bitmap, given its dimensions and + SkColorType. Returns true on success, where success means either setPixels() + or setPixelRef() was called. + + @param bitmap SkBitmap containing SkImageInfo as input, and SkPixelRef as output + @return true if pixels are allocated + */ + bool allocPixelRef(SkBitmap* bitmap) override; + }; + +private: + enum Flags { + kImageIsVolatile_Flag = 0x02, + }; + + sk_sp fPixelRef; + SkPixmap fPixmap; + uint8_t fFlags; + + friend class SkReadBuffer; // unflatten +}; + +/////////////////////////////////////////////////////////////////////////////// + +inline uint32_t* SkBitmap::getAddr32(int x, int y) const { + SkASSERT(fPixmap.addr()); + return fPixmap.writable_addr32(x, y); +} + +inline uint16_t* SkBitmap::getAddr16(int x, int y) const { + SkASSERT(fPixmap.addr()); + return fPixmap.writable_addr16(x, y); +} + +inline uint8_t* SkBitmap::getAddr8(int x, int y) const { + SkASSERT(fPixmap.addr()); + return fPixmap.writable_addr8(x, y); +} + +#endif diff --git a/gfx/skia/skia/include/core/SkBlendMode.h b/gfx/skia/skia/include/core/SkBlendMode.h new file mode 100644 index 0000000000..270978f74b --- /dev/null +++ b/gfx/skia/skia/include/core/SkBlendMode.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlendMode_DEFINED +#define SkBlendMode_DEFINED + +#include "include/core/SkTypes.h" + +enum class SkBlendMode { + kClear, //!< replaces destination with zero: fully transparent + kSrc, //!< replaces destination + kDst, //!< preserves destination + kSrcOver, //!< source over destination + kDstOver, //!< destination over source + kSrcIn, //!< source trimmed inside destination + kDstIn, //!< destination trimmed by source + kSrcOut, //!< source trimmed outside destination + kDstOut, //!< destination trimmed outside source + kSrcATop, //!< source inside destination blended with destination + kDstATop, //!< destination inside source blended with source + kXor, //!< each of source and destination trimmed outside the other + kPlus, //!< sum of colors + kModulate, //!< product of premultiplied colors; darkens destination + kScreen, //!< multiply inverse of pixels, inverting result; brightens destination + kLastCoeffMode = kScreen, //!< last porter duff blend mode + kOverlay, //!< multiply or screen, depending on destination + kDarken, //!< darker of source and destination + kLighten, //!< lighter of source and destination + kColorDodge, //!< brighten destination to reflect source + kColorBurn, //!< darken destination to reflect source + kHardLight, //!< multiply or screen, depending on source + kSoftLight, //!< lighten or darken, depending on source + kDifference, //!< subtract darker from lighter with higher contrast + kExclusion, //!< subtract darker from lighter with lower contrast + kMultiply, //!< multiply source with destination, darkening image + kLastSeparableMode = kMultiply, //!< last blend mode operating separately on components + kHue, //!< hue of source with saturation and luminosity of destination + kSaturation, //!< saturation of source with hue and luminosity of destination + kColor, //!< hue and saturation of source with luminosity of destination + kLuminosity, //!< luminosity of source with hue and saturation of destination + kLastMode = kLuminosity, //!< last valid value +}; + +/** Returns name of blendMode as null-terminated C string. + + @param blendMode one of: + SkBlendMode::kClear, SkBlendMode::kSrc, SkBlendMode::kDst, + SkBlendMode::kSrcOver, SkBlendMode::kDstOver, SkBlendMode::kSrcIn, + SkBlendMode::kDstIn, SkBlendMode::kSrcOut, SkBlendMode::kDstOut, + SkBlendMode::kSrcATop, SkBlendMode::kDstATop, SkBlendMode::kXor, + SkBlendMode::kPlus, SkBlendMode::kModulate, SkBlendMode::kScreen, + SkBlendMode::kOverlay, SkBlendMode::kDarken, SkBlendMode::kLighten, + SkBlendMode::kColorDodge, SkBlendMode::kColorBurn, SkBlendMode::kHardLight, + SkBlendMode::kSoftLight, SkBlendMode::kDifference, SkBlendMode::kExclusion, + SkBlendMode::kMultiply, SkBlendMode::kHue, SkBlendMode::kSaturation, + SkBlendMode::kColor, SkBlendMode::kLuminosity + @return C string +*/ +SK_API const char* SkBlendMode_Name(SkBlendMode blendMode); + +#endif diff --git a/gfx/skia/skia/include/core/SkBlurTypes.h b/gfx/skia/skia/include/core/SkBlurTypes.h new file mode 100644 index 0000000000..aec37b6e68 --- /dev/null +++ b/gfx/skia/skia/include/core/SkBlurTypes.h @@ -0,0 +1,22 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlurTypes_DEFINED +#define SkBlurTypes_DEFINED + +#include "include/core/SkTypes.h" + +enum SkBlurStyle : int { + kNormal_SkBlurStyle, //!< fuzzy inside and outside + kSolid_SkBlurStyle, //!< solid inside, fuzzy outside + kOuter_SkBlurStyle, //!< nothing inside, fuzzy outside + kInner_SkBlurStyle, //!< fuzzy inside, nothing outside + + kLastEnum_SkBlurStyle = kInner_SkBlurStyle, +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkCanvas.h b/gfx/skia/skia/include/core/SkCanvas.h new file mode 100644 index 0000000000..6f6df382c8 --- /dev/null +++ b/gfx/skia/skia/include/core/SkCanvas.h @@ -0,0 +1,2789 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCanvas_DEFINED +#define SkCanvas_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkClipOp.h" +#include "include/core/SkColor.h" +#include "include/core/SkDeque.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRasterHandleAllocator.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/core/SkSize.h" +#include "include/core/SkString.h" +#include "include/core/SkSurfaceProps.h" +#include "include/core/SkTypes.h" +#include "include/core/SkVertices.h" +#include "include/private/SkMacros.h" + +#include +#include + +class GrContext; +class GrRenderTargetContext; +class SkBaseDevice; +class SkBitmap; +class SkData; +class SkDrawable; +struct SkDrawShadowRec; +class SkFont; +class SkGlyphRunBuilder; +class SkImage; +class SkImageFilter; +class SkPaintFilterCanvas; +class SkPath; +class SkPicture; +class SkPixmap; +class SkRegion; +class SkRRect; +struct SkRSXform; +class SkSurface; +class SkSurface_Base; +class SkTextBlob; + +/** \class SkCanvas + SkCanvas provides an interface for drawing, and how the drawing is clipped and transformed. + SkCanvas contains a stack of SkMatrix and clip values. + + SkCanvas and SkPaint together provide the state to draw into SkSurface or SkBaseDevice. + Each SkCanvas draw call transforms the geometry of the object by the concatenation of all + SkMatrix values in the stack. The transformed geometry is clipped by the intersection + of all of clip values in the stack. The SkCanvas draw calls use SkPaint to supply drawing + state such as color, SkTypeface, text size, stroke width, SkShader and so on. + + To draw to a pixel-based destination, create raster surface or GPU surface. + Request SkCanvas from SkSurface to obtain the interface to draw. + SkCanvas generated by raster surface draws to memory visible to the CPU. + SkCanvas generated by GPU surface uses Vulkan or OpenGL to draw to the GPU. + + To draw to a document, obtain SkCanvas from SVG canvas, document PDF, or SkPictureRecorder. + SkDocument based SkCanvas and other SkCanvas subclasses reference SkBaseDevice describing the + destination. + + SkCanvas can be constructed to draw to SkBitmap without first creating raster surface. + This approach may be deprecated in the future. +*/ +class SK_API SkCanvas { + enum PrivateSaveLayerFlags { + kDontClipToLayer_PrivateSaveLayerFlag = 1U << 31, + }; + +public: + + /** Allocates raster SkCanvas that will draw directly into pixels. + + SkCanvas is returned if all parameters are valid. + Valid parameters include: + info dimensions are zero or positive; + info contains SkColorType and SkAlphaType supported by raster surface; + pixels is not nullptr; + rowBytes is zero or large enough to contain info width pixels of SkColorType. + + Pass zero for rowBytes to compute rowBytes from info width and size of pixel. + If rowBytes is greater than zero, it must be equal to or greater than + info width times bytes required for SkColorType. + + Pixel buffer size should be info height times computed rowBytes. + Pixels are not initialized. + To access pixels after drawing, call flush() or peekPixels(). + + @param info width, height, SkColorType, SkAlphaType, SkColorSpace, of raster surface; + width, or height, or both, may be zero + @param pixels pointer to destination pixels buffer + @param rowBytes interval from one SkSurface row to the next, or zero + @param props LCD striping orientation and setting for device independent fonts; + may be nullptr + @return SkCanvas if all parameters are valid; otherwise, nullptr + */ + static std::unique_ptr MakeRasterDirect(const SkImageInfo& info, void* pixels, + size_t rowBytes, + const SkSurfaceProps* props = nullptr); + + /** Allocates raster SkCanvas specified by inline image specification. Subsequent SkCanvas + calls draw into pixels. + SkColorType is set to kN32_SkColorType. + SkAlphaType is set to kPremul_SkAlphaType. + To access pixels after drawing, call flush() or peekPixels(). + + SkCanvas is returned if all parameters are valid. + Valid parameters include: + width and height are zero or positive; + pixels is not nullptr; + rowBytes is zero or large enough to contain width pixels of kN32_SkColorType. + + Pass zero for rowBytes to compute rowBytes from width and size of pixel. + If rowBytes is greater than zero, it must be equal to or greater than + width times bytes required for SkColorType. + + Pixel buffer size should be height times rowBytes. + + @param width pixel column count on raster surface created; must be zero or greater + @param height pixel row count on raster surface created; must be zero or greater + @param pixels pointer to destination pixels buffer; buffer size should be height + times rowBytes + @param rowBytes interval from one SkSurface row to the next, or zero + @return SkCanvas if all parameters are valid; otherwise, nullptr + */ + static std::unique_ptr MakeRasterDirectN32(int width, int height, SkPMColor* pixels, + size_t rowBytes) { + return MakeRasterDirect(SkImageInfo::MakeN32Premul(width, height), pixels, rowBytes); + } + + /** Creates an empty SkCanvas with no backing device or pixels, with + a width and height of zero. + + @return empty SkCanvas + */ + SkCanvas(); + + /** Creates SkCanvas of the specified dimensions without a SkSurface. + Used by subclasses with custom implementations for draw member functions. + + If props equals nullptr, SkSurfaceProps are created with + SkSurfaceProps::InitType settings, which choose the pixel striping + direction and order. Since a platform may dynamically change its direction when + the device is rotated, and since a platform may have multiple monitors with + different characteristics, it is best not to rely on this legacy behavior. + + @param width zero or greater + @param height zero or greater + @param props LCD striping orientation and setting for device independent fonts; + may be nullptr + @return SkCanvas placeholder with dimensions + */ + SkCanvas(int width, int height, const SkSurfaceProps* props = nullptr); + + /** Private. For internal use only. + */ + explicit SkCanvas(sk_sp device); + + /** Constructs a canvas that draws into bitmap. + Sets SkSurfaceProps::kLegacyFontHost_InitType in constructed SkSurface. + + SkBitmap is copied so that subsequently editing bitmap will not affect + constructed SkCanvas. + + May be deprecated in the future. + + @param bitmap width, height, SkColorType, SkAlphaType, and pixel + storage of raster surface + @return SkCanvas that can be used to draw into bitmap + */ + explicit SkCanvas(const SkBitmap& bitmap); + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + /** Private. + */ + enum class ColorBehavior { + kLegacy, //!< placeholder + }; + + /** Private. For use by Android framework only. + + @param bitmap specifies a bitmap for the canvas to draw into + @param behavior specializes this constructor; value is unused + @return SkCanvas that can be used to draw into bitmap + */ + SkCanvas(const SkBitmap& bitmap, ColorBehavior behavior); +#endif + + /** Constructs a canvas that draws into bitmap. + Use props to match the device characteristics, like LCD striping. + + bitmap is copied so that subsequently editing bitmap will not affect + constructed SkCanvas. + + @param bitmap width, height, SkColorType, SkAlphaType, + and pixel storage of raster surface + @param props order and orientation of RGB striping; and whether to use + device independent fonts + @return SkCanvas that can be used to draw into bitmap + */ + SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props); + + /** Draws saved layers, if any. + Frees up resources used by SkCanvas. + */ + virtual ~SkCanvas(); + + /** Returns SkImageInfo for SkCanvas. If SkCanvas is not associated with raster surface or + GPU surface, returned SkColorType is set to kUnknown_SkColorType. + + @return dimensions and SkColorType of SkCanvas + */ + SkImageInfo imageInfo() const; + + /** Copies SkSurfaceProps, if SkCanvas is associated with raster surface or + GPU surface, and returns true. Otherwise, returns false and leave props unchanged. + + @param props storage for writable SkSurfaceProps + @return true if SkSurfaceProps was copied + */ + bool getProps(SkSurfaceProps* props) const; + + /** Triggers the immediate execution of all pending draw operations. + If SkCanvas is associated with GPU surface, resolves all pending GPU operations. + If SkCanvas is associated with raster surface, has no effect; raster draw + operations are never deferred. + */ + void flush(); + + /** Gets the size of the base or root layer in global canvas coordinates. The + origin of the base layer is always (0,0). The area available for drawing may be + smaller (due to clipping or saveLayer). + + @return integral width and height of base layer + */ + virtual SkISize getBaseLayerSize() const; + + /** Creates SkSurface matching info and props, and associates it with SkCanvas. + Returns nullptr if no match found. + + If props is nullptr, matches SkSurfaceProps in SkCanvas. If props is nullptr and SkCanvas + does not have SkSurfaceProps, creates SkSurface with default SkSurfaceProps. + + @param info width, height, SkColorType, SkAlphaType, and SkColorSpace + @param props SkSurfaceProps to match; may be nullptr to match SkCanvas + @return SkSurface matching info and props, or nullptr if no match is available + */ + sk_sp makeSurface(const SkImageInfo& info, const SkSurfaceProps* props = nullptr); + + /** Returns GPU context of the GPU surface associated with SkCanvas. + + @return GPU context, if available; nullptr otherwise + */ + virtual GrContext* getGrContext(); + + /** Returns the pixel base address, SkImageInfo, rowBytes, and origin if the pixels + can be read directly. The returned address is only valid + while SkCanvas is in scope and unchanged. Any SkCanvas call or SkSurface call + may invalidate the returned address and other returned values. + + If pixels are inaccessible, info, rowBytes, and origin are unchanged. + + @param info storage for writable pixels' SkImageInfo; may be nullptr + @param rowBytes storage for writable pixels' row bytes; may be nullptr + @param origin storage for SkCanvas top layer origin, its top-left corner; + may be nullptr + @return address of pixels, or nullptr if inaccessible + */ + void* accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin = nullptr); + + /** Returns custom context that tracks the SkMatrix and clip. + + Use SkRasterHandleAllocator to blend Skia drawing with custom drawing, typically performed + by the host platform user interface. The custom context returned is generated by + SkRasterHandleAllocator::MakeCanvas, which creates a custom canvas with raster storage for + the drawing destination. + + @return context of custom allocation + */ + SkRasterHandleAllocator::Handle accessTopRasterHandle() const; + + /** Returns true if SkCanvas has direct access to its pixels. + + Pixels are readable when SkBaseDevice is raster. Pixels are not readable when SkCanvas + is returned from GPU surface, returned by SkDocument::beginPage, returned by + SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class + like DebugCanvas. + + pixmap is valid only while SkCanvas is in scope and unchanged. Any + SkCanvas or SkSurface call may invalidate the pixmap values. + + @param pixmap storage for pixel state if pixels are readable; otherwise, ignored + @return true if SkCanvas has direct access to pixels + */ + bool peekPixels(SkPixmap* pixmap); + + /** Copies SkRect of pixels from SkCanvas into dstPixels. SkMatrix and clip are + ignored. + + Source SkRect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()). + Destination SkRect corners are (0, 0) and (dstInfo.width(), dstInfo.height()). + Copies each readable pixel intersecting both rectangles, without scaling, + converting to dstInfo.colorType() and dstInfo.alphaType() if required. + + Pixels are readable when SkBaseDevice is raster, or backed by a GPU. + Pixels are not readable when SkCanvas is returned by SkDocument::beginPage, + returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility + class like DebugCanvas. + + The destination pixel storage must be allocated by the caller. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. dstPixels contents outside SkRect intersection are unchanged. + + Pass negative values for srcX or srcY to offset pixels across or down destination. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - SkCanvas pixels could not be converted to dstInfo.colorType() or dstInfo.alphaType(). + - SkCanvas pixels are not readable; for instance, SkCanvas is document-based. + - dstRowBytes is too small to contain one row of pixels. + + @param dstInfo width, height, SkColorType, and SkAlphaType of dstPixels + @param dstPixels storage for pixels; dstInfo.height() times dstRowBytes, or larger + @param dstRowBytes size of one destination row; dstInfo.width() times pixel size, or larger + @param srcX offset into readable pixels on x-axis; may be negative + @param srcY offset into readable pixels on y-axis; may be negative + @return true if pixels were copied + */ + bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY); + + /** Copies SkRect of pixels from SkCanvas into pixmap. SkMatrix and clip are + ignored. + + Source SkRect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()). + Destination SkRect corners are (0, 0) and (pixmap.width(), pixmap.height()). + Copies each readable pixel intersecting both rectangles, without scaling, + converting to pixmap.colorType() and pixmap.alphaType() if required. + + Pixels are readable when SkBaseDevice is raster, or backed by a GPU. + Pixels are not readable when SkCanvas is returned by SkDocument::beginPage, + returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility + class like DebugCanvas. + + Caller must allocate pixel storage in pixmap if needed. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination SkRect + are copied. pixmap pixels contents outside SkRect intersection are unchanged. + + Pass negative values for srcX or srcY to offset pixels across or down pixmap. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - SkCanvas pixels could not be converted to pixmap.colorType() or pixmap.alphaType(). + - SkCanvas pixels are not readable; for instance, SkCanvas is document-based. + - SkPixmap pixels could not be allocated. + - pixmap.rowBytes() is too small to contain one row of pixels. + + @param pixmap storage for pixels copied from SkCanvas + @param srcX offset into readable pixels on x-axis; may be negative + @param srcY offset into readable pixels on y-axis; may be negative + @return true if pixels were copied + */ + bool readPixels(const SkPixmap& pixmap, int srcX, int srcY); + + /** Copies SkRect of pixels from SkCanvas into bitmap. SkMatrix and clip are + ignored. + + Source SkRect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()). + Destination SkRect corners are (0, 0) and (bitmap.width(), bitmap.height()). + Copies each readable pixel intersecting both rectangles, without scaling, + converting to bitmap.colorType() and bitmap.alphaType() if required. + + Pixels are readable when SkBaseDevice is raster, or backed by a GPU. + Pixels are not readable when SkCanvas is returned by SkDocument::beginPage, + returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility + class like DebugCanvas. + + Caller must allocate pixel storage in bitmap if needed. + + SkBitmap values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. SkBitmap pixels outside SkRect intersection are unchanged. + + Pass negative values for srcX or srcY to offset pixels across or down bitmap. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - SkCanvas pixels could not be converted to bitmap.colorType() or bitmap.alphaType(). + - SkCanvas pixels are not readable; for instance, SkCanvas is document-based. + - bitmap pixels could not be allocated. + - bitmap.rowBytes() is too small to contain one row of pixels. + + @param bitmap storage for pixels copied from SkCanvas + @param srcX offset into readable pixels on x-axis; may be negative + @param srcY offset into readable pixels on y-axis; may be negative + @return true if pixels were copied + */ + bool readPixels(const SkBitmap& bitmap, int srcX, int srcY); + + /** Copies SkRect from pixels to SkCanvas. SkMatrix and clip are ignored. + Source SkRect corners are (0, 0) and (info.width(), info.height()). + Destination SkRect corners are (x, y) and + (imageInfo().width(), imageInfo().height()). + + Copies each readable pixel intersecting both rectangles, without scaling, + converting to imageInfo().colorType() and imageInfo().alphaType() if required. + + Pixels are writable when SkBaseDevice is raster, or backed by a GPU. + Pixels are not writable when SkCanvas is returned by SkDocument::beginPage, + returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility + class like DebugCanvas. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. SkCanvas pixels outside SkRect intersection are unchanged. + + Pass negative values for x or y to offset pixels to the left or + above SkCanvas pixels. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - pixels could not be converted to SkCanvas imageInfo().colorType() or + imageInfo().alphaType(). + - SkCanvas pixels are not writable; for instance, SkCanvas is document-based. + - rowBytes is too small to contain one row of pixels. + + @param info width, height, SkColorType, and SkAlphaType of pixels + @param pixels pixels to copy, of size info.height() times rowBytes, or larger + @param rowBytes size of one row of pixels; info.width() times pixel size, or larger + @param x offset into SkCanvas writable pixels on x-axis; may be negative + @param y offset into SkCanvas writable pixels on y-axis; may be negative + @return true if pixels were written to SkCanvas + */ + bool writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y); + + /** Copies SkRect from pixels to SkCanvas. SkMatrix and clip are ignored. + Source SkRect corners are (0, 0) and (bitmap.width(), bitmap.height()). + + Destination SkRect corners are (x, y) and + (imageInfo().width(), imageInfo().height()). + + Copies each readable pixel intersecting both rectangles, without scaling, + converting to imageInfo().colorType() and imageInfo().alphaType() if required. + + Pixels are writable when SkBaseDevice is raster, or backed by a GPU. + Pixels are not writable when SkCanvas is returned by SkDocument::beginPage, + returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility + class like DebugCanvas. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. SkCanvas pixels outside SkRect intersection are unchanged. + + Pass negative values for x or y to offset pixels to the left or + above SkCanvas pixels. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - bitmap does not have allocated pixels. + - bitmap pixels could not be converted to SkCanvas imageInfo().colorType() or + imageInfo().alphaType(). + - SkCanvas pixels are not writable; for instance, SkCanvas is document based. + - bitmap pixels are inaccessible; for instance, bitmap wraps a texture. + + @param bitmap contains pixels copied to SkCanvas + @param x offset into SkCanvas writable pixels on x-axis; may be negative + @param y offset into SkCanvas writable pixels on y-axis; may be negative + @return true if pixels were written to SkCanvas + */ + bool writePixels(const SkBitmap& bitmap, int x, int y); + + /** Saves SkMatrix and clip. + Calling restore() discards changes to SkMatrix and clip, + restoring the SkMatrix and clip to their state when save() was called. + + SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix(), + and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), clipPath(), clipRegion(). + + Saved SkCanvas state is put on a stack; multiple calls to save() should be balance + by an equal number of calls to restore(). + + Call restoreToCount() with result to restore this and subsequent saves. + + @return depth of saved stack + */ + int save(); + + /** Saves SkMatrix and clip, and allocates a SkBitmap for subsequent drawing. + Calling restore() discards changes to SkMatrix and clip, and draws the SkBitmap. + + SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), + setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), + clipPath(), clipRegion(). + + SkRect bounds suggests but does not define the SkBitmap size. To clip drawing to + a specific rectangle, use clipRect(). + + Optional SkPaint paint applies alpha, SkColorFilter, SkImageFilter, and + SkBlendMode when restore() is called. + + Call restoreToCount() with returned value to restore this and subsequent saves. + + @param bounds hint to limit the size of the layer; may be nullptr + @param paint graphics state for layer; may be nullptr + @return depth of saved stack + */ + int saveLayer(const SkRect* bounds, const SkPaint* paint); + + /** Saves SkMatrix and clip, and allocates a SkBitmap for subsequent drawing. + Calling restore() discards changes to SkMatrix and clip, and draws the SkBitmap. + + SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), + setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), + clipPath(), clipRegion(). + + SkRect bounds suggests but does not define the layer size. To clip drawing to + a specific rectangle, use clipRect(). + + Optional SkPaint paint applies alpha, SkColorFilter, SkImageFilter, and + SkBlendMode when restore() is called. + + Call restoreToCount() with returned value to restore this and subsequent saves. + + @param bounds hint to limit the size of layer; may be nullptr + @param paint graphics state for layer; may be nullptr + @return depth of saved stack + */ + int saveLayer(const SkRect& bounds, const SkPaint* paint) { + return this->saveLayer(&bounds, paint); + } + + /** Saves SkMatrix and clip, and allocates SkBitmap for subsequent drawing. + + Calling restore() discards changes to SkMatrix and clip, + and blends layer with alpha opacity onto prior layer. + + SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), + setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), + clipPath(), clipRegion(). + + SkRect bounds suggests but does not define layer size. To clip drawing to + a specific rectangle, use clipRect(). + + alpha of zero is fully transparent, 255 is fully opaque. + + Call restoreToCount() with returned value to restore this and subsequent saves. + + @param bounds hint to limit the size of layer; may be nullptr + @param alpha opacity of layer + @return depth of saved stack + */ + int saveLayerAlpha(const SkRect* bounds, U8CPU alpha); + + /** \enum SkCanvas::SaveLayerFlagsSet + SaveLayerFlags provides options that may be used in any combination in SaveLayerRec, + defining how layer allocated by saveLayer() operates. It may be set to zero, + kPreserveLCDText_SaveLayerFlag, kInitWithPrevious_SaveLayerFlag, or both flags. + */ + enum SaveLayerFlagsSet { + kPreserveLCDText_SaveLayerFlag = 1 << 1, + kInitWithPrevious_SaveLayerFlag = 1 << 2, //!< initializes with previous contents + kMaskAgainstCoverage_EXPERIMENTAL_DONT_USE_SaveLayerFlag = + 1 << 3, //!< experimental: do not use + // instead of matching previous layer's colortype, use F16 + kF16ColorType = 1 << 4, +#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG + kDontClipToLayer_Legacy_SaveLayerFlag = + kDontClipToLayer_PrivateSaveLayerFlag, //!< deprecated +#endif + }; + + typedef uint32_t SaveLayerFlags; + + /** \struct SkCanvas::SaveLayerRec + SaveLayerRec contains the state used to create the layer. + */ + struct SaveLayerRec { + + /** Sets fBounds, fPaint, and fBackdrop to nullptr. Clears fSaveLayerFlags. + + @return empty SaveLayerRec + */ + SaveLayerRec() {} + + /** Sets fBounds, fPaint, and fSaveLayerFlags; sets fBackdrop to nullptr. + + @param bounds layer dimensions; may be nullptr + @param paint applied to layer when overlaying prior layer; may be nullptr + @param saveLayerFlags SaveLayerRec options to modify layer + @return SaveLayerRec with empty fBackdrop + */ + SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0) + : fBounds(bounds) + , fPaint(paint) + , fSaveLayerFlags(saveLayerFlags) + {} + + /** Sets fBounds, fPaint, fBackdrop, and fSaveLayerFlags. + + @param bounds layer dimensions; may be nullptr + @param paint applied to layer when overlaying prior layer; + may be nullptr + @param backdrop If not null, this causes the current layer to be filtered by + backdrop, and then drawn into the new layer + (respecting the current clip). + If null, the new layer is initialized with transparent-black. + @param saveLayerFlags SaveLayerRec options to modify layer + @return SaveLayerRec fully specified + */ + SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, + SaveLayerFlags saveLayerFlags) + : fBounds(bounds) + , fPaint(paint) + , fBackdrop(backdrop) + , fSaveLayerFlags(saveLayerFlags) + {} + + /** Experimental. Not ready for general use. + Sets fBounds, fPaint, fBackdrop, fClipMask, fClipMatrix, and fSaveLayerFlags. + clipMatrix uses alpha channel of image, transformed by clipMatrix, to clip + layer when drawn to SkCanvas. + + @param bounds layer dimensions; may be nullptr + @param paint graphics state applied to layer when overlaying prior + layer; may be nullptr + @param backdrop If not null, this causes the current layer to be filtered by + backdrop, and then drawn into the new layer + (respecting the current clip). + If null, the new layer is initialized with transparent-black. + @param clipMask clip applied to layer; may be nullptr + @param clipMatrix matrix applied to clipMask; may be nullptr to use + identity matrix + @param saveLayerFlags SaveLayerRec options to modify layer + @return SaveLayerRec fully specified + */ + SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, + const SkImage* clipMask, const SkMatrix* clipMatrix, + SaveLayerFlags saveLayerFlags) + : fBounds(bounds) + , fPaint(paint) + , fBackdrop(backdrop) + , fClipMask(clipMask) + , fClipMatrix(clipMatrix) + , fSaveLayerFlags(saveLayerFlags) + {} + + /** hints at layer size limit */ + const SkRect* fBounds = nullptr; + + /** modifies overlay */ + const SkPaint* fPaint = nullptr; + + /** + * If not null, this triggers the same initialization behavior as setting + * kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into + * the new layer, rather than initializing the new layer with transparent-black. + * This is then filtered by fBackdrop (respecting the current clip). + */ + const SkImageFilter* fBackdrop = nullptr; + + /** clips layer with mask alpha */ + const SkImage* fClipMask = nullptr; + + /** transforms mask alpha used to clip */ + const SkMatrix* fClipMatrix = nullptr; + + /** preserves LCD text, creates with prior layer contents */ + SaveLayerFlags fSaveLayerFlags = 0; + }; + + /** Saves SkMatrix and clip, and allocates SkBitmap for subsequent drawing. + + Calling restore() discards changes to SkMatrix and clip, + and blends SkBitmap with alpha opacity onto the prior layer. + + SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), + setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), + clipPath(), clipRegion(). + + SaveLayerRec contains the state used to create the layer. + + Call restoreToCount() with returned value to restore this and subsequent saves. + + @param layerRec layer state + @return depth of save state stack before this call was made. + */ + int saveLayer(const SaveLayerRec& layerRec); + + /** Removes changes to SkMatrix and clip since SkCanvas state was + last saved. The state is removed from the stack. + + Does nothing if the stack is empty. + */ + void restore(); + + /** Returns the number of saved states, each containing: SkMatrix and clip. + Equals the number of save() calls less the number of restore() calls plus one. + The save count of a new canvas is one. + + @return depth of save state stack + */ + int getSaveCount() const; + + /** Restores state to SkMatrix and clip values when save(), saveLayer(), + saveLayerPreserveLCDTextRequests(), or saveLayerAlpha() returned saveCount. + + Does nothing if saveCount is greater than state stack count. + Restores state to initial values if saveCount is less than or equal to one. + + @param saveCount depth of state stack to restore + */ + void restoreToCount(int saveCount); + + /** Translates SkMatrix by dx along the x-axis and dy along the y-axis. + + Mathematically, replaces SkMatrix with a translation matrix + premultiplied with SkMatrix. + + This has the effect of moving the drawing by (dx, dy) before transforming + the result with SkMatrix. + + @param dx distance to translate on x-axis + @param dy distance to translate on y-axis + */ + void translate(SkScalar dx, SkScalar dy); + + /** Scales SkMatrix by sx on the x-axis and sy on the y-axis. + + Mathematically, replaces SkMatrix with a scale matrix + premultiplied with SkMatrix. + + This has the effect of scaling the drawing by (sx, sy) before transforming + the result with SkMatrix. + + @param sx amount to scale on x-axis + @param sy amount to scale on y-axis + */ + void scale(SkScalar sx, SkScalar sy); + + /** Rotates SkMatrix by degrees. Positive degrees rotates clockwise. + + Mathematically, replaces SkMatrix with a rotation matrix + premultiplied with SkMatrix. + + This has the effect of rotating the drawing by degrees before transforming + the result with SkMatrix. + + @param degrees amount to rotate, in degrees + */ + void rotate(SkScalar degrees); + + /** Rotates SkMatrix by degrees about a point at (px, py). Positive degrees rotates + clockwise. + + Mathematically, constructs a rotation matrix; premultiplies the rotation matrix by + a translation matrix; then replaces SkMatrix with the resulting matrix + premultiplied with SkMatrix. + + This has the effect of rotating the drawing about a given point before + transforming the result with SkMatrix. + + @param degrees amount to rotate, in degrees + @param px x-axis value of the point to rotate about + @param py y-axis value of the point to rotate about + */ + void rotate(SkScalar degrees, SkScalar px, SkScalar py); + + /** Skews SkMatrix by sx on the x-axis and sy on the y-axis. A positive value of sx + skews the drawing right as y-axis values increase; a positive value of sy skews + the drawing down as x-axis values increase. + + Mathematically, replaces SkMatrix with a skew matrix premultiplied with SkMatrix. + + This has the effect of skewing the drawing by (sx, sy) before transforming + the result with SkMatrix. + + @param sx amount to skew on x-axis + @param sy amount to skew on y-axis + */ + void skew(SkScalar sx, SkScalar sy); + + /** Replaces SkMatrix with matrix premultiplied with existing SkMatrix. + + This has the effect of transforming the drawn geometry by matrix, before + transforming the result with existing SkMatrix. + + @param matrix matrix to premultiply with existing SkMatrix + */ + void concat(const SkMatrix& matrix); + + /** Replaces SkMatrix with matrix. + Unlike concat(), any prior matrix state is overwritten. + + @param matrix matrix to copy, replacing existing SkMatrix + */ + void setMatrix(const SkMatrix& matrix); + + /** Sets SkMatrix to the identity matrix. + Any prior matrix state is overwritten. + */ + void resetMatrix(); + + /** Replaces clip with the intersection or difference of clip and rect, + with an aliased or anti-aliased clip edge. rect is transformed by SkMatrix + before it is combined with clip. + + @param rect SkRect to combine with clip + @param op SkClipOp to apply to clip + @param doAntiAlias true if clip is to be anti-aliased + */ + void clipRect(const SkRect& rect, SkClipOp op, bool doAntiAlias); + + /** Replaces clip with the intersection or difference of clip and rect. + Resulting clip is aliased; pixels are fully contained by the clip. + rect is transformed by SkMatrix before it is combined with clip. + + @param rect SkRect to combine with clip + @param op SkClipOp to apply to clip + */ + void clipRect(const SkRect& rect, SkClipOp op) { + this->clipRect(rect, op, false); + } + + /** Replaces clip with the intersection of clip and rect. + Resulting clip is aliased; pixels are fully contained by the clip. + rect is transformed by SkMatrix + before it is combined with clip. + + @param rect SkRect to combine with clip + @param doAntiAlias true if clip is to be anti-aliased + */ + void clipRect(const SkRect& rect, bool doAntiAlias = false) { + this->clipRect(rect, SkClipOp::kIntersect, doAntiAlias); + } + + /** Sets the maximum clip rectangle, which can be set by clipRect(), clipRRect() and + clipPath() and intersect the current clip with the specified rect. + The maximum clip affects only future clipping operations; it is not retroactive. + The clip restriction is not recorded in pictures. + + Pass an empty rect to disable maximum clip. + This private API is for use by Android framework only. + + @param rect maximum allowed clip in device coordinates + */ + void androidFramework_setDeviceClipRestriction(const SkIRect& rect); + + /** Replaces clip with the intersection or difference of clip and rrect, + with an aliased or anti-aliased clip edge. + rrect is transformed by SkMatrix + before it is combined with clip. + + @param rrect SkRRect to combine with clip + @param op SkClipOp to apply to clip + @param doAntiAlias true if clip is to be anti-aliased + */ + void clipRRect(const SkRRect& rrect, SkClipOp op, bool doAntiAlias); + + /** Replaces clip with the intersection or difference of clip and rrect. + Resulting clip is aliased; pixels are fully contained by the clip. + rrect is transformed by SkMatrix before it is combined with clip. + + @param rrect SkRRect to combine with clip + @param op SkClipOp to apply to clip + */ + void clipRRect(const SkRRect& rrect, SkClipOp op) { + this->clipRRect(rrect, op, false); + } + + /** Replaces clip with the intersection of clip and rrect, + with an aliased or anti-aliased clip edge. + rrect is transformed by SkMatrix before it is combined with clip. + + @param rrect SkRRect to combine with clip + @param doAntiAlias true if clip is to be anti-aliased + */ + void clipRRect(const SkRRect& rrect, bool doAntiAlias = false) { + this->clipRRect(rrect, SkClipOp::kIntersect, doAntiAlias); + } + + /** Replaces clip with the intersection or difference of clip and path, + with an aliased or anti-aliased clip edge. SkPath::FillType determines if path + describes the area inside or outside its contours; and if path contour overlaps + itself or another path contour, whether the overlaps form part of the area. + path is transformed by SkMatrix before it is combined with clip. + + @param path SkPath to combine with clip + @param op SkClipOp to apply to clip + @param doAntiAlias true if clip is to be anti-aliased + */ + void clipPath(const SkPath& path, SkClipOp op, bool doAntiAlias); + + /** Replaces clip with the intersection or difference of clip and path. + Resulting clip is aliased; pixels are fully contained by the clip. + SkPath::FillType determines if path + describes the area inside or outside its contours; and if path contour overlaps + itself or another path contour, whether the overlaps form part of the area. + path is transformed by SkMatrix + before it is combined with clip. + + @param path SkPath to combine with clip + @param op SkClipOp to apply to clip + */ + void clipPath(const SkPath& path, SkClipOp op) { + this->clipPath(path, op, false); + } + + /** Replaces clip with the intersection of clip and path. + Resulting clip is aliased; pixels are fully contained by the clip. + SkPath::FillType determines if path + describes the area inside or outside its contours; and if path contour overlaps + itself or another path contour, whether the overlaps form part of the area. + path is transformed by SkMatrix before it is combined with clip. + + @param path SkPath to combine with clip + @param doAntiAlias true if clip is to be anti-aliased + */ + void clipPath(const SkPath& path, bool doAntiAlias = false) { + this->clipPath(path, SkClipOp::kIntersect, doAntiAlias); + } + + /** Experimental. For testing only. + Set to simplify clip stack using PathOps. + */ + void setAllowSimplifyClip(bool allow) { + fAllowSimplifyClip = allow; + } + + /** Replaces clip with the intersection or difference of clip and SkRegion deviceRgn. + Resulting clip is aliased; pixels are fully contained by the clip. + deviceRgn is unaffected by SkMatrix. + + @param deviceRgn SkRegion to combine with clip + @param op SkClipOp to apply to clip + */ + void clipRegion(const SkRegion& deviceRgn, SkClipOp op = SkClipOp::kIntersect); + + /** Returns true if SkRect rect, transformed by SkMatrix, can be quickly determined to be + outside of clip. May return false even though rect is outside of clip. + + Use to check if an area to be drawn is clipped out, to skip subsequent draw calls. + + @param rect SkRect to compare with clip + @return true if rect, transformed by SkMatrix, does not intersect clip + */ + bool quickReject(const SkRect& rect) const; + + /** Returns true if path, transformed by SkMatrix, can be quickly determined to be + outside of clip. May return false even though path is outside of clip. + + Use to check if an area to be drawn is clipped out, to skip subsequent draw calls. + + @param path SkPath to compare with clip + @return true if path, transformed by SkMatrix, does not intersect clip + */ + bool quickReject(const SkPath& path) const; + + /** Returns bounds of clip, transformed by inverse of SkMatrix. If clip is empty, + return SkRect::MakeEmpty, where all SkRect sides equal zero. + + SkRect returned is outset by one to account for partial pixel coverage if clip + is anti-aliased. + + @return bounds of clip in local coordinates + */ + SkRect getLocalClipBounds() const; + + /** Returns bounds of clip, transformed by inverse of SkMatrix. If clip is empty, + return false, and set bounds to SkRect::MakeEmpty, where all SkRect sides equal zero. + + bounds is outset by one to account for partial pixel coverage if clip + is anti-aliased. + + @param bounds SkRect of clip in local coordinates + @return true if clip bounds is not empty + */ + bool getLocalClipBounds(SkRect* bounds) const { + *bounds = this->getLocalClipBounds(); + return !bounds->isEmpty(); + } + + /** Returns SkIRect bounds of clip, unaffected by SkMatrix. If clip is empty, + return SkRect::MakeEmpty, where all SkRect sides equal zero. + + Unlike getLocalClipBounds(), returned SkIRect is not outset. + + @return bounds of clip in SkBaseDevice coordinates + */ + SkIRect getDeviceClipBounds() const; + + /** Returns SkIRect bounds of clip, unaffected by SkMatrix. If clip is empty, + return false, and set bounds to SkRect::MakeEmpty, where all SkRect sides equal zero. + + Unlike getLocalClipBounds(), bounds is not outset. + + @param bounds SkRect of clip in device coordinates + @return true if clip bounds is not empty + */ + bool getDeviceClipBounds(SkIRect* bounds) const { + *bounds = this->getDeviceClipBounds(); + return !bounds->isEmpty(); + } + + /** Fills clip with color color. + mode determines how ARGB is combined with destination. + + @param color unpremultiplied ARGB + @param mode SkBlendMode used to combine source color and destination + */ + void drawColor(SkColor color, SkBlendMode mode = SkBlendMode::kSrcOver); + + /** Fills clip with color color using SkBlendMode::kSrc. + This has the effect of replacing all pixels contained by clip with color. + + @param color unpremultiplied ARGB + */ + void clear(SkColor color) { + this->drawColor(color, SkBlendMode::kSrc); + } + + /** Makes SkCanvas contents undefined. Subsequent calls that read SkCanvas pixels, + such as drawing with SkBlendMode, return undefined results. discard() does + not change clip or SkMatrix. + + discard() may do nothing, depending on the implementation of SkSurface or SkBaseDevice + that created SkCanvas. + + discard() allows optimized performance on subsequent draws by removing + cached data associated with SkSurface or SkBaseDevice. + It is not necessary to call discard() once done with SkCanvas; + any cached data is deleted when owning SkSurface or SkBaseDevice is deleted. + */ + void discard() { this->onDiscard(); } + + /** Fills clip with SkPaint paint. SkPaint components SkMaskFilter, SkShader, + SkColorFilter, SkImageFilter, and SkBlendMode affect drawing; + SkPathEffect in paint is ignored. + + @param paint graphics state used to fill SkCanvas + */ + void drawPaint(const SkPaint& paint); + + /** \enum SkCanvas::PointMode + Selects if an array of points are drawn as discrete points, as lines, or as + an open polygon. + */ + enum PointMode { + kPoints_PointMode, //!< draw each point separately + kLines_PointMode, //!< draw each pair of points as a line segment + kPolygon_PointMode, //!< draw the array of points as a open polygon + }; + + /** Draws pts using clip, SkMatrix and SkPaint paint. + count is the number of points; if count is less than one, has no effect. + mode may be one of: kPoints_PointMode, kLines_PointMode, or kPolygon_PointMode. + + If mode is kPoints_PointMode, the shape of point drawn depends on paint + SkPaint::Cap. If paint is set to SkPaint::kRound_Cap, each point draws a + circle of diameter SkPaint stroke width. If paint is set to SkPaint::kSquare_Cap + or SkPaint::kButt_Cap, each point draws a square of width and height + SkPaint stroke width. + + If mode is kLines_PointMode, each pair of points draws a line segment. + One line is drawn for every two points; each point is used once. If count is odd, + the final point is ignored. + + If mode is kPolygon_PointMode, each adjacent pair of points draws a line segment. + count minus one lines are drawn; the first and last point are used once. + + Each line segment respects paint SkPaint::Cap and SkPaint stroke width. + SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. + + Always draws each element one at a time; is not affected by + SkPaint::Join, and unlike drawPath(), does not create a mask from all points + and lines before drawing. + + @param mode whether pts draws points or lines + @param count number of points in the array + @param pts array of points to draw + @param paint stroke, blend, color, and so on, used to draw + */ + void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint); + + /** Draws point at (x, y) using clip, SkMatrix and SkPaint paint. + + The shape of point drawn depends on paint SkPaint::Cap. + If paint is set to SkPaint::kRound_Cap, draw a circle of diameter + SkPaint stroke width. If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, + draw a square of width and height SkPaint stroke width. + SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. + + @param x left edge of circle or square + @param y top edge of circle or square + @param paint stroke, blend, color, and so on, used to draw + */ + void drawPoint(SkScalar x, SkScalar y, const SkPaint& paint); + + /** Draws point p using clip, SkMatrix and SkPaint paint. + + The shape of point drawn depends on paint SkPaint::Cap. + If paint is set to SkPaint::kRound_Cap, draw a circle of diameter + SkPaint stroke width. If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, + draw a square of width and height SkPaint stroke width. + SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. + + @param p top-left edge of circle or square + @param paint stroke, blend, color, and so on, used to draw + */ + void drawPoint(SkPoint p, const SkPaint& paint) { + this->drawPoint(p.x(), p.y(), paint); + } + + /** Draws line segment from (x0, y0) to (x1, y1) using clip, SkMatrix, and SkPaint paint. + In paint: SkPaint stroke width describes the line thickness; + SkPaint::Cap draws the end rounded or square; + SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. + + @param x0 start of line segment on x-axis + @param y0 start of line segment on y-axis + @param x1 end of line segment on x-axis + @param y1 end of line segment on y-axis + @param paint stroke, blend, color, and so on, used to draw + */ + void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint); + + /** Draws line segment from p0 to p1 using clip, SkMatrix, and SkPaint paint. + In paint: SkPaint stroke width describes the line thickness; + SkPaint::Cap draws the end rounded or square; + SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. + + @param p0 start of line segment + @param p1 end of line segment + @param paint stroke, blend, color, and so on, used to draw + */ + void drawLine(SkPoint p0, SkPoint p1, const SkPaint& paint) { + this->drawLine(p0.x(), p0.y(), p1.x(), p1.y(), paint); + } + + /** Draws SkRect rect using clip, SkMatrix, and SkPaint paint. + In paint: SkPaint::Style determines if rectangle is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness, and + SkPaint::Join draws the corners rounded or square. + + @param rect rectangle to draw + @param paint stroke or fill, blend, color, and so on, used to draw + */ + void drawRect(const SkRect& rect, const SkPaint& paint); + + /** Draws SkIRect rect using clip, SkMatrix, and SkPaint paint. + In paint: SkPaint::Style determines if rectangle is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness, and + SkPaint::Join draws the corners rounded or square. + + @param rect rectangle to draw + @param paint stroke or fill, blend, color, and so on, used to draw + */ + void drawIRect(const SkIRect& rect, const SkPaint& paint) { + SkRect r; + r.set(rect); // promotes the ints to scalars + this->drawRect(r, paint); + } + + /** Draws SkRegion region using clip, SkMatrix, and SkPaint paint. + In paint: SkPaint::Style determines if rectangle is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness, and + SkPaint::Join draws the corners rounded or square. + + @param region region to draw + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawRegion(const SkRegion& region, const SkPaint& paint); + + /** Draws oval oval using clip, SkMatrix, and SkPaint. + In paint: SkPaint::Style determines if oval is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness. + + @param oval SkRect bounds of oval + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawOval(const SkRect& oval, const SkPaint& paint); + + /** Draws SkRRect rrect using clip, SkMatrix, and SkPaint paint. + In paint: SkPaint::Style determines if rrect is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness. + + rrect may represent a rectangle, circle, oval, uniformly rounded rectangle, or + may have any combination of positive non-square radii for the four corners. + + @param rrect SkRRect with up to eight corner radii to draw + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawRRect(const SkRRect& rrect, const SkPaint& paint); + + /** Draws SkRRect outer and inner + using clip, SkMatrix, and SkPaint paint. + outer must contain inner or the drawing is undefined. + In paint: SkPaint::Style determines if SkRRect is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness. + If stroked and SkRRect corner has zero length radii, SkPaint::Join can + draw corners rounded or square. + + GPU-backed platforms optimize drawing when both outer and inner are + concave and outer contains inner. These platforms may not be able to draw + SkPath built with identical data as fast. + + @param outer SkRRect outer bounds to draw + @param inner SkRRect inner bounds to draw + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint); + + /** Draws circle at (cx, cy) with radius using clip, SkMatrix, and SkPaint paint. + If radius is zero or less, nothing is drawn. + In paint: SkPaint::Style determines if circle is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness. + + @param cx circle center on the x-axis + @param cy circle center on the y-axis + @param radius half the diameter of circle + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint); + + /** Draws circle at center with radius using clip, SkMatrix, and SkPaint paint. + If radius is zero or less, nothing is drawn. + In paint: SkPaint::Style determines if circle is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness. + + @param center circle center + @param radius half the diameter of circle + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawCircle(SkPoint center, SkScalar radius, const SkPaint& paint) { + this->drawCircle(center.x(), center.y(), radius, paint); + } + + /** Draws arc using clip, SkMatrix, and SkPaint paint. + + Arc is part of oval bounded by oval, sweeping from startAngle to startAngle plus + sweepAngle. startAngle and sweepAngle are in degrees. + + startAngle of zero places start point at the right middle edge of oval. + A positive sweepAngle places arc end point clockwise from start point; + a negative sweepAngle places arc end point counterclockwise from start point. + sweepAngle may exceed 360 degrees, a full circle. + If useCenter is true, draw a wedge that includes lines from oval + center to arc end points. If useCenter is false, draw arc between end points. + + If SkRect oval is empty or sweepAngle is zero, nothing is drawn. + + @param oval SkRect bounds of oval containing arc to draw + @param startAngle angle in degrees where arc begins + @param sweepAngle sweep angle in degrees; positive is clockwise + @param useCenter if true, include the center of the oval + @param paint SkPaint stroke or fill, blend, color, and so on, used to draw + */ + void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint); + + /** Draws SkRRect bounded by SkRect rect, with corner radii (rx, ry) using clip, + SkMatrix, and SkPaint paint. + + In paint: SkPaint::Style determines if SkRRect is stroked or filled; + if stroked, SkPaint stroke width describes the line thickness. + If rx or ry are less than zero, they are treated as if they are zero. + If rx plus ry exceeds rect width or rect height, radii are scaled down to fit. + If rx and ry are zero, SkRRect is drawn as SkRect and if stroked is affected by + SkPaint::Join. + + @param rect SkRect bounds of SkRRect to draw + @param rx axis length on x-axis of oval describing rounded corners + @param ry axis length on y-axis of oval describing rounded corners + @param paint stroke, blend, color, and so on, used to draw + */ + void drawRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, const SkPaint& paint); + + /** Draws SkPath path using clip, SkMatrix, and SkPaint paint. + SkPath contains an array of path contour, each of which may be open or closed. + + In paint: SkPaint::Style determines if SkRRect is stroked or filled: + if filled, SkPath::FillType determines whether path contour describes inside or + outside of fill; if stroked, SkPaint stroke width describes the line thickness, + SkPaint::Cap describes line ends, and SkPaint::Join describes how + corners are drawn. + + @param path SkPath to draw + @param paint stroke, blend, color, and so on, used to draw + */ + void drawPath(const SkPath& path, const SkPaint& paint); + + /** Draws SkImage image, with its top-left corner at (left, top), + using clip, SkMatrix, and optional SkPaint paint. + + This is equivalent to drawImageRect() using a dst rect at (x,y) with the + same width and height of the image. + + @param image uncompressed rectangular map of pixels + @param left left side of image + @param top top side of image + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImage(const SkImage* image, SkScalar left, SkScalar top, + const SkPaint* paint = nullptr); + + /** Draws SkImage image, with its top-left corner at (left, top), + using clip, SkMatrix, and optional SkPaint paint. + + This is equivalent to drawImageRect() using a dst rect at (x,y) with the + same width and height of the image. + + @param image uncompressed rectangular map of pixels + @param left left side of image + @param top pop side of image + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImage(const sk_sp& image, SkScalar left, SkScalar top, + const SkPaint* paint = nullptr) { + this->drawImage(image.get(), left, top, paint); + } + + /** \enum SkCanvas::SrcRectConstraint + SrcRectConstraint controls the behavior at the edge of source SkRect, + provided to drawImageRect(), trading off speed for precision. + + SkFilterQuality in SkPaint may sample multiple pixels in the image. Source SkRect + restricts the bounds of pixels that may be read. SkFilterQuality may slow down if + it cannot read outside the bounds, when sampling near the edge of source SkRect. + SrcRectConstraint specifies whether an SkImageFilter is allowed to read pixels + outside source SkRect. + */ + enum SrcRectConstraint { + kStrict_SrcRectConstraint, //!< sample only inside bounds; slower + kFast_SrcRectConstraint, //!< sample outside bounds; faster + }; + + /** Draws SkRect src of SkImage image, scaled and translated to fill SkRect dst. + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + When using a shader or shader mask filter, its coordinate system is based on the + current CTM, so will reflect the dst rect geometry and is equivalent to + drawRect(dst). The src rect is only used to access the provided image. + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within src; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param image SkImage containing pixels, dimensions, and format + @param src source SkRect of image to draw from + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint filter strictly within src or draw faster + */ + void drawImageRect(const SkImage* image, const SkRect& src, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** Draws SkIRect isrc of SkImage image, scaled and translated to fill SkRect dst. + Note that isrc is on integer pixel boundaries; dst may include fractional + boundaries. Additionally transform draw using clip, SkMatrix, and optional SkPaint + paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + When using a shader or shader mask filter, its coordinate system is based on the + current CTM, so will reflect the dst rect geometry and is equivalent to + drawRect(dst). The src rect is only used to access the provided image. + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within isrc; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param image SkImage containing pixels, dimensions, and format + @param isrc source SkIRect of image to draw from + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint filter strictly within isrc or draw faster + */ + void drawImageRect(const SkImage* image, const SkIRect& isrc, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** Draws SkImage image, scaled and translated to fill SkRect dst, using clip, SkMatrix, + and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + When using a shader or shader mask filter, its coordinate system is based on the + current CTM, so will reflect the dst rect geometry and is equivalent to + drawRect(dst). + + @param image SkImage containing pixels, dimensions, and format + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImageRect(const SkImage* image, const SkRect& dst, const SkPaint* paint); + + /** Draws SkRect src of SkImage image, scaled and translated to fill SkRect dst. + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + When using a shader or shader mask filter, its coordinate system is based on the + current CTM, so will reflect the dst rect geometry and is equivalent to + drawRect(dst). The src rect is only used to access the provided image. + + @param image SkImage containing pixels, dimensions, and format + @param src source SkRect of image to draw from + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint filter strictly within src or draw faster + */ + void drawImageRect(const sk_sp& image, const SkRect& src, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint) { + this->drawImageRect(image.get(), src, dst, paint, constraint); + } + + /** Draws SkIRect isrc of SkImage image, scaled and translated to fill SkRect dst. + isrc is on integer pixel boundaries; dst may include fractional boundaries. + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + When using a shader or shader mask filter, its coordinate system is based on the + current CTM, so will reflect the dst rect geometry and is equivalent to + drawRect(dst). The src rect is only used to access the provided image. + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within image; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param image SkImage containing pixels, dimensions, and format + @param isrc source SkIRect of image to draw from + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint filter strictly within image or draw faster + */ + void drawImageRect(const sk_sp& image, const SkIRect& isrc, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint) { + this->drawImageRect(image.get(), isrc, dst, paint, constraint); + } + + /** Draws SkImage image, scaled and translated to fill SkRect dst, + using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + When using a shader or shader mask filter, its coordinate system is based on the + current CTM, so will reflect the dst rect geometry and is equivalent to + drawRect(dst). + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within image; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param image SkImage containing pixels, dimensions, and format + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImageRect(const sk_sp& image, const SkRect& dst, const SkPaint* paint) { + this->drawImageRect(image.get(), dst, paint); + } + + /** Draws SkImage image stretched proportionally to fit into SkRect dst. + SkIRect center divides the image into nine sections: four sides, four corners, and + the center. Corners are unmodified or scaled down proportionately if their sides + are larger than dst; center and four sides are scaled to fit remaining space, if any. + + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. If paint + SkFilterQuality set to kNone_SkFilterQuality, disable pixel filtering. For all + other values of paint SkFilterQuality, use kLow_SkFilterQuality to filter pixels. + Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + @param image SkImage containing pixels, dimensions, and format + @param center SkIRect edge of image corners and sides + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint = nullptr); + + /** Draws SkImage image stretched proportionally to fit into SkRect dst. + SkIRect center divides the image into nine sections: four sides, four corners, and + the center. Corners are not scaled, or scaled down proportionately if their sides + are larger than dst; center and four sides are scaled to fit remaining space, if any. + + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. If paint + SkFilterQuality set to kNone_SkFilterQuality, disable pixel filtering. For all + other values of paint SkFilterQuality, use kLow_SkFilterQuality to filter pixels. + Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. + + If generated mask extends beyond image bounds, replicate image edge colors, just + as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set + replicates the image edge color when it samples outside of its bounds. + + @param image SkImage containing pixels, dimensions, and format + @param center SkIRect edge of image corners and sides + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImageNine(const sk_sp& image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint = nullptr) { + this->drawImageNine(image.get(), center, dst, paint); + } + + /** Draws SkBitmap bitmap, with its top-left corner at (left, top), + using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is not nullptr, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If bitmap is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from bitmap bounds. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + @param bitmap SkBitmap containing pixels, dimensions, and format + @param left left side of bitmap + @param top top side of bitmap + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, + const SkPaint* paint = nullptr); + + /** Draws SkRect src of SkBitmap bitmap, scaled and translated to fill SkRect dst. + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If bitmap is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from bitmap bounds. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within src; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param bitmap SkBitmap containing pixels, dimensions, and format + @param src source SkRect of image to draw from + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint filter strictly within src or draw faster + */ + void drawBitmapRect(const SkBitmap& bitmap, const SkRect& src, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** Draws SkIRect isrc of SkBitmap bitmap, scaled and translated to fill SkRect dst. + isrc is on integer pixel boundaries; dst may include fractional boundaries. + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If bitmap is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from bitmap bounds. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within isrc; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param bitmap SkBitmap containing pixels, dimensions, and format + @param isrc source SkIRect of image to draw from + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint sample strictly within isrc, or draw faster + */ + void drawBitmapRect(const SkBitmap& bitmap, const SkIRect& isrc, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** Draws SkBitmap bitmap, scaled and translated to fill SkRect dst. + bitmap bounds is on integer pixel boundaries; dst may include fractional boundaries. + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If bitmap is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from bitmap bounds. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to + sample within bitmap; set to kFast_SrcRectConstraint allows sampling outside to + improve performance. + + @param bitmap SkBitmap containing pixels, dimensions, and format + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + @param constraint filter strictly within bitmap or draw faster + */ + void drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** Draws SkBitmap bitmap stretched proportionally to fit into SkRect dst. + SkIRect center divides the bitmap into nine sections: four sides, four corners, + and the center. Corners are not scaled, or scaled down proportionately if their + sides are larger than dst; center and four sides are scaled to fit remaining + space, if any. + + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If bitmap is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from bitmap bounds. If paint + SkFilterQuality set to kNone_SkFilterQuality, disable pixel filtering. For all + other values of paint SkFilterQuality, use kLow_SkFilterQuality to filter pixels. + Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + @param bitmap SkBitmap containing pixels, dimensions, and format + @param center SkIRect edge of image corners and sides + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, + const SkPaint* paint = nullptr); + + /** \struct SkCanvas::Lattice + SkCanvas::Lattice divides SkBitmap or SkImage into a rectangular grid. + Grid entries on even columns and even rows are fixed; these entries are + always drawn at their original size if the destination is large enough. + If the destination side is too small to hold the fixed entries, all fixed + entries are proportionately scaled down to fit. + The grid entries not on even columns and rows are scaled to fit the + remaining space, if any. + */ + struct Lattice { + + /** \enum SkCanvas::Lattice::RectType + Optional setting per rectangular grid entry to make it transparent, + or to fill the grid entry with a color. + */ + enum RectType : uint8_t { + kDefault = 0, //!< draws SkBitmap into lattice rectangle + kTransparent, //!< skips lattice rectangle by making it transparent + kFixedColor, //!< draws one of fColors into lattice rectangle + }; + + const int* fXDivs; //!< x-axis values dividing bitmap + const int* fYDivs; //!< y-axis values dividing bitmap + const RectType* fRectTypes; //!< array of fill types + int fXCount; //!< number of x-coordinates + int fYCount; //!< number of y-coordinates + const SkIRect* fBounds; //!< source bounds to draw from + const SkColor* fColors; //!< array of colors + }; + + /** Draws SkBitmap bitmap stretched proportionally to fit into SkRect dst. + + SkCanvas::Lattice lattice divides bitmap into a rectangular grid. + Each intersection of an even-numbered row and column is fixed; like the corners + of drawBitmapNine(), fixed lattice elements never scale larger than their initial + size and shrink proportionately when all fixed elements exceed the bitmap + dimension. All other grid elements scale to fill the available space, if any. + + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If bitmap is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from bitmap bounds. If paint + SkFilterQuality set to kNone_SkFilterQuality, disable pixel filtering. For all + other values of paint SkFilterQuality, use kLow_SkFilterQuality to filter pixels. + Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + @param bitmap SkBitmap containing pixels, dimensions, and format + @param lattice division of bitmap into fixed and variable rectangles + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint = nullptr); + + /** Draws SkImage image stretched proportionally to fit into SkRect dst. + + SkCanvas::Lattice lattice divides image into a rectangular grid. + Each intersection of an even-numbered row and column is fixed; like the corners + of drawBitmapNine(), fixed lattice elements never scale larger than their initial + size and shrink proportionately when all fixed elements exceed the bitmap + dimension. All other grid elements scale to fill the available space, if any. + + Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. + + If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader. + If paint contains SkMaskFilter, generate mask from image bounds. If paint + SkFilterQuality set to kNone_SkFilterQuality, disable pixel filtering. For all + other values of paint SkFilterQuality, use kLow_SkFilterQuality to filter pixels. + Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. + + If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, + just as SkShader made from SkShader::MakeBitmapShader with + SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples + outside of its bounds. + + @param image SkImage containing pixels, dimensions, and format + @param lattice division of bitmap into fixed and variable rectangles + @param dst destination SkRect of image to draw to + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint = nullptr); + + /** + * Experimental. Controls anti-aliasing of each edge of images in an image-set. + */ + enum QuadAAFlags : unsigned { + kLeft_QuadAAFlag = 0b0001, + kTop_QuadAAFlag = 0b0010, + kRight_QuadAAFlag = 0b0100, + kBottom_QuadAAFlag = 0b1000, + + kNone_QuadAAFlags = 0b0000, + kAll_QuadAAFlags = 0b1111, + }; + + /** This is used by the experimental API below. */ + struct SK_API ImageSetEntry { + ImageSetEntry(sk_sp image, const SkRect& srcRect, const SkRect& dstRect, + int matrixIndex, float alpha, unsigned aaFlags, bool hasClip); + + ImageSetEntry(sk_sp image, const SkRect& srcRect, const SkRect& dstRect, + float alpha, unsigned aaFlags); + + ImageSetEntry(); + ~ImageSetEntry(); + ImageSetEntry(const ImageSetEntry&); + ImageSetEntry& operator=(const ImageSetEntry&); + + sk_sp fImage; + SkRect fSrcRect; + SkRect fDstRect; + int fMatrixIndex = -1; // Index into the preViewMatrices arg, or < 0 + float fAlpha = 1.f; + unsigned fAAFlags = kNone_QuadAAFlags; // QuadAAFlags + bool fHasClip = false; // True to use next 4 points in dstClip arg as quad + }; + + /** + * This is an experimental API for the SkiaRenderer Chromium project, and its API will surely + * evolve if it is not removed outright. + * + * This behaves very similarly to drawRect() combined with a clipPath() formed by clip + * quadrilateral. 'rect' and 'clip' are in the same coordinate space. If 'clip' is null, then it + * is as if the rectangle was not clipped (or, alternatively, clipped to itself). If not null, + * then it must provide 4 points. + * + * In addition to combining the draw and clipping into one operation, this function adds the + * additional capability of controlling each of the rectangle's edges anti-aliasing + * independently. The edges of the clip will respect the per-edge AA flags. It is required that + * 'clip' be contained inside 'rect'. In terms of mapping to edge labels, the 'clip' points + * should be ordered top-left, top-right, bottom-right, bottom-left so that the edge between [0] + * and [1] is "top", [1] and [2] is "right", [2] and [3] is "bottom", and [3] and [0] is "left". + * This ordering matches SkRect::toQuad(). + * + * This API only draws solid color, filled rectangles so it does not accept a full SkPaint. + */ + void experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags, + const SkColor4f& color, SkBlendMode mode); + void experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags, + SkColor color, SkBlendMode mode) { + this->experimental_DrawEdgeAAQuad(rect, clip, aaFlags, SkColor4f::FromColor(color), mode); + } + + /** + * This is an bulk variant of experimental_DrawEdgeAAQuad() that renders 'cnt' textured quads. + * For each entry, 'fDstRect' is rendered with its clip (determined by entry's 'fHasClip' and + * the current index in 'dstClip'). The entry's fImage is applied to the destination rectangle + * by sampling from 'fSrcRect' sub-image. The corners of 'fSrcRect' map to the corners of + * 'fDstRect', just like in drawImageRect(), and they will be properly interpolated when + * applying a clip. + * + * Like experimental_DrawEdgeAAQuad(), each entry can specify edge AA flags that apply to both + * the destination rect and its clip. + * + * If provided, the 'dstClips' array must have length equal 4 * the number of entries with + * fHasClip true. If 'dstClips' is null, every entry must have 'fHasClip' set to false. The + * destination clip coordinates will be read consecutively with the image set entries, advancing + * by 4 points every time an entry with fHasClip is passed. + * + * This entry point supports per-entry manipulations to the canvas's current matrix. If an + * entry provides 'fMatrixIndex' >= 0, it will be drawn as if the canvas's CTM was + * canvas->getTotalMatrix() * preViewMatrices[fMatrixIndex]. If 'fMatrixIndex' is less than 0, + * the pre-view matrix transform is implicitly the identity, so it will be drawn using just the + * current canvas matrix. The pre-view matrix modifies the canvas's view matrix, it does not + * affect the local coordinates of each entry. + * + * An optional paint may be provided, which supports the same subset of features usable with + * drawImageRect (i.e. assumed to be filled and no path effects). When a paint is provided, the + * image set is drawn as if each image used the applied paint independently, so each is affected + * by the image, color, and/or mask filter. + */ + void experimental_DrawEdgeAAImageSet(const ImageSetEntry imageSet[], int cnt, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint* paint = nullptr, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** Draws text, with origin at (x, y), using clip, SkMatrix, SkFont font, + and SkPaint paint. + + When encoding is SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, or + SkTextEncoding::kUTF32, this function uses the default + character-to-glyph mapping from the SkTypeface in font. It does not + perform typeface fallback for characters not found in the SkTypeface. + It does not perform kerning or other complex shaping; glyphs are + positioned based on their default advances. + + Text meaning depends on SkTextEncoding. + + Text size is affected by SkMatrix and SkFont text size. Default text + size is 12 point. + + All elements of paint: SkPathEffect, SkMaskFilter, SkShader, + SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By + default, draws filled black glyphs. + + @param text character code points or glyphs drawn + @param byteLength byte length of text array + @param encoding text encoding used in the text array + @param x start of text on x-axis + @param y start of text on y-axis + @param font typeface, text size and so, used to describe the text + @param paint blend, color, and so on, used to draw + */ + void drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding, + SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint); + + /** Draws null terminated string, with origin at (x, y), using clip, SkMatrix, + SkFont font, and SkPaint paint. + + This function uses the default character-to-glyph mapping from the + SkTypeface in font. It does not perform typeface fallback for + characters not found in the SkTypeface. It does not perform kerning; + glyphs are positioned based on their default advances. + + String str is encoded as UTF-8. + + Text size is affected by SkMatrix and font text size. Default text + size is 12 point. + + All elements of paint: SkPathEffect, SkMaskFilter, SkShader, + SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By + default, draws filled black glyphs. + + @param str character code points drawn, + ending with a char value of zero + @param x start of string on x-axis + @param y start of string on y-axis + @param font typeface, text size and so, used to describe the text + @param paint blend, color, and so on, used to draw + */ + void drawString(const char str[], SkScalar x, SkScalar y, const SkFont& font, + const SkPaint& paint) { + this->drawSimpleText(str, strlen(str), SkTextEncoding::kUTF8, x, y, font, paint); + } + + /** Draws SkString, with origin at (x, y), using clip, SkMatrix, SkFont font, + and SkPaint paint. + + This function uses the default character-to-glyph mapping from the + SkTypeface in font. It does not perform typeface fallback for + characters not found in the SkTypeface. It does not perform kerning; + glyphs are positioned based on their default advances. + + SkString str is encoded as UTF-8. + + Text size is affected by SkMatrix and SkFont text size. Default text + size is 12 point. + + All elements of paint: SkPathEffect, SkMaskFilter, SkShader, + SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By + default, draws filled black glyphs. + + @param str character code points drawn, + ending with a char value of zero + @param x start of string on x-axis + @param y start of string on y-axis + @param font typeface, text size and so, used to describe the text + @param paint blend, color, and so on, used to draw + */ + void drawString(const SkString& str, SkScalar x, SkScalar y, const SkFont& font, + const SkPaint& paint) { + this->drawSimpleText(str.c_str(), str.size(), SkTextEncoding::kUTF8, x, y, font, paint); + } + + /** Draws SkTextBlob blob at (x, y), using clip, SkMatrix, and SkPaint paint. + + blob contains glyphs, their positions, and paint attributes specific to text: + SkTypeface, SkPaint text size, SkPaint text scale x, + SkPaint text skew x, SkPaint::Align, SkPaint::Hinting, anti-alias, SkPaint fake bold, + SkPaint font embedded bitmaps, SkPaint full hinting spacing, LCD text, SkPaint linear text, + and SkPaint subpixel text. + + SkTextEncoding must be set to SkTextEncoding::kGlyphID. + + Elements of paint: anti-alias, SkBlendMode, color including alpha, + SkColorFilter, SkPaint dither, SkDrawLooper, SkMaskFilter, SkPathEffect, SkShader, and + SkPaint::Style; apply to blob. If SkPaint contains SkPaint::kStroke_Style: + SkPaint miter limit, SkPaint::Cap, SkPaint::Join, and SkPaint stroke width; + apply to SkPath created from blob. + + @param blob glyphs, positions, and their paints' text size, typeface, and so on + @param x horizontal offset applied to blob + @param y vertical offset applied to blob + @param paint blend, color, stroking, and so on, used to draw + */ + void drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint); + + /** Draws SkTextBlob blob at (x, y), using clip, SkMatrix, and SkPaint paint. + + blob contains glyphs, their positions, and paint attributes specific to text: + SkTypeface, SkPaint text size, SkPaint text scale x, + SkPaint text skew x, SkPaint::Align, SkPaint::Hinting, anti-alias, SkPaint fake bold, + SkPaint font embedded bitmaps, SkPaint full hinting spacing, LCD text, SkPaint linear text, + and SkPaint subpixel text. + + SkTextEncoding must be set to SkTextEncoding::kGlyphID. + + Elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, + SkImageFilter, and SkDrawLooper; apply to blob. + + @param blob glyphs, positions, and their paints' text size, typeface, and so on + @param x horizontal offset applied to blob + @param y vertical offset applied to blob + @param paint blend, color, stroking, and so on, used to draw + */ + void drawTextBlob(const sk_sp& blob, SkScalar x, SkScalar y, const SkPaint& paint) { + this->drawTextBlob(blob.get(), x, y, paint); + } + + /** Draws SkPicture picture, using clip and SkMatrix. + Clip and SkMatrix are unchanged by picture contents, as if + save() was called before and restore() was called after drawPicture(). + + SkPicture records a series of draw commands for later playback. + + @param picture recorded drawing commands to play + */ + void drawPicture(const SkPicture* picture) { + this->drawPicture(picture, nullptr, nullptr); + } + + /** Draws SkPicture picture, using clip and SkMatrix. + Clip and SkMatrix are unchanged by picture contents, as if + save() was called before and restore() was called after drawPicture(). + + SkPicture records a series of draw commands for later playback. + + @param picture recorded drawing commands to play + */ + void drawPicture(const sk_sp& picture) { + this->drawPicture(picture.get()); + } + + /** Draws SkPicture picture, using clip and SkMatrix; transforming picture with + SkMatrix matrix, if provided; and use SkPaint paint alpha, SkColorFilter, + SkImageFilter, and SkBlendMode, if provided. + + matrix transformation is equivalent to: save(), concat(), drawPicture(), restore(). + paint use is equivalent to: saveLayer(), drawPicture(), restore(). + + @param picture recorded drawing commands to play + @param matrix SkMatrix to rotate, scale, translate, and so on; may be nullptr + @param paint SkPaint to apply transparency, filtering, and so on; may be nullptr + */ + void drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint); + + /** Draws SkPicture picture, using clip and SkMatrix; transforming picture with + SkMatrix matrix, if provided; and use SkPaint paint alpha, SkColorFilter, + SkImageFilter, and SkBlendMode, if provided. + + matrix transformation is equivalent to: save(), concat(), drawPicture(), restore(). + paint use is equivalent to: saveLayer(), drawPicture(), restore(). + + @param picture recorded drawing commands to play + @param matrix SkMatrix to rotate, scale, translate, and so on; may be nullptr + @param paint SkPaint to apply transparency, filtering, and so on; may be nullptr + */ + void drawPicture(const sk_sp& picture, const SkMatrix* matrix, + const SkPaint* paint) { + this->drawPicture(picture.get(), matrix, paint); + } + + /** Draws SkVertices vertices, a triangle mesh, using clip and SkMatrix. + If vertices texs and vertices colors are defined in vertices, and SkPaint paint + contains SkShader, SkBlendMode mode combines vertices colors with SkShader. + + @param vertices triangle mesh to draw + @param mode combines vertices colors with SkShader, if both are present + @param paint specifies the SkShader, used as SkVertices texture; may be nullptr + */ + void drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint); + + /** Draws SkVertices vertices, a triangle mesh, using clip and SkMatrix. + If vertices texs and vertices colors are defined in vertices, and SkPaint paint + contains SkShader, SkBlendMode mode combines vertices colors with SkShader. + + @param vertices triangle mesh to draw + @param mode combines vertices colors with SkShader, if both are present + @param paint specifies the SkShader, used as SkVertices texture, may be nullptr + */ + void drawVertices(const sk_sp& vertices, SkBlendMode mode, const SkPaint& paint); + + /** Draws SkVertices vertices, a triangle mesh, using clip and SkMatrix. Bone data is used to + deform vertices with bone weights. + If vertices texs and vertices colors are defined in vertices, and SkPaint paint + contains SkShader, SkBlendMode mode combines vertices colors with SkShader. + The first element of bones should be an object to world space transformation matrix that + will be applied before performing mesh deformations. If no such transformation is needed, + it should be the identity matrix. + boneCount must be at most 80, and thus the size of bones should be at most 80. + + @param vertices triangle mesh to draw + @param bones bone matrix data + @param boneCount number of bone matrices + @param mode combines vertices colors with SkShader, if both are present + @param paint specifies the SkShader, used as SkVertices texture, may be nullptr + */ + void drawVertices(const SkVertices* vertices, const SkVertices::Bone bones[], int boneCount, + SkBlendMode mode, const SkPaint& paint); + + /** Draws SkVertices vertices, a triangle mesh, using clip and SkMatrix. Bone data is used to + deform vertices with bone weights. + If vertices texs and vertices colors are defined in vertices, and SkPaint paint + contains SkShader, SkBlendMode mode combines vertices colors with SkShader. + The first element of bones should be an object to world space transformation matrix that + will be applied before performing mesh deformations. If no such transformation is needed, + it should be the identity matrix. + boneCount must be at most 80, and thus the size of bones should be at most 80. + + @param vertices triangle mesh to draw + @param bones bone matrix data + @param boneCount number of bone matrices + @param mode combines vertices colors with SkShader, if both are present + @param paint specifies the SkShader, used as SkVertices texture, may be nullptr + */ + void drawVertices(const sk_sp& vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode mode, const SkPaint& paint); + + /** Draws a Coons patch: the interpolation of four cubics with shared corners, + associating a color, and optionally a texture SkPoint, with each corner. + + Coons patch uses clip and SkMatrix, paint SkShader, SkColorFilter, + alpha, SkImageFilter, and SkBlendMode. If SkShader is provided it is treated + as Coons patch texture; SkBlendMode mode combines color colors and SkShader if + both are provided. + + SkPoint array cubics specifies four SkPath cubic starting at the top-left corner, + in clockwise order, sharing every fourth point. The last SkPath cubic ends at the + first point. + + Color array color associates colors with corners in top-left, top-right, + bottom-right, bottom-left order. + + If paint contains SkShader, SkPoint array texCoords maps SkShader as texture to + corners in top-left, top-right, bottom-right, bottom-left order. + + @param cubics SkPath cubic array, sharing common points + @param colors color array, one for each corner + @param texCoords SkPoint array of texture coordinates, mapping SkShader to corners; + may be nullptr + @param mode SkBlendMode for colors, and for SkShader if paint has one + @param paint SkShader, SkColorFilter, SkBlendMode, used to draw + */ + void drawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint); + + /** Draws SkPath cubic Coons patch: the interpolation of four cubics with shared corners, + associating a color, and optionally a texture SkPoint, with each corner. + + Coons patch uses clip and SkMatrix, paint SkShader, SkColorFilter, + alpha, SkImageFilter, and SkBlendMode. If SkShader is provided it is treated + as Coons patch texture; SkBlendMode mode combines color colors and SkShader if + both are provided. + + SkPoint array cubics specifies four SkPath cubic starting at the top-left corner, + in clockwise order, sharing every fourth point. The last SkPath cubic ends at the + first point. + + Color array color associates colors with corners in top-left, top-right, + bottom-right, bottom-left order. + + If paint contains SkShader, SkPoint array texCoords maps SkShader as texture to + corners in top-left, top-right, bottom-right, bottom-left order. + + @param cubics SkPath cubic array, sharing common points + @param colors color array, one for each corner + @param texCoords SkPoint array of texture coordinates, mapping SkShader to corners; + may be nullptr + @param paint SkShader, SkColorFilter, SkBlendMode, used to draw + */ + void drawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], const SkPaint& paint) { + this->drawPatch(cubics, colors, texCoords, SkBlendMode::kModulate, paint); + } + + /** Draws a set of sprites from atlas, using clip, SkMatrix, and optional SkPaint paint. + paint uses anti-alias, alpha, SkColorFilter, SkImageFilter, and SkBlendMode + to draw, if present. For each entry in the array, SkRect tex locates sprite in + atlas, and SkRSXform xform transforms it into destination space. + + xform, text, and colors if present, must contain count entries. + Optional colors are applied for each sprite using SkBlendMode mode, treating + sprite as source and colors as destination. + Optional cullRect is a conservative bounds of all transformed sprites. + If cullRect is outside of clip, canvas can skip drawing. + + If atlas is nullptr, this draws nothing. + + @param atlas SkImage containing sprites + @param xform SkRSXform mappings for sprites in atlas + @param tex SkRect locations of sprites in atlas + @param colors one per sprite, blended with sprite using SkBlendMode; may be nullptr + @param count number of sprites to draw + @param mode SkBlendMode combining colors and sprites + @param cullRect bounds of transformed sprites for efficient clipping; may be nullptr + @param paint SkColorFilter, SkImageFilter, SkBlendMode, and so on; may be nullptr + */ + void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], + const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect, + const SkPaint* paint); + + /** Draws a set of sprites from atlas, using clip, SkMatrix, and optional SkPaint paint. + paint uses anti-alias, alpha, SkColorFilter, SkImageFilter, and SkBlendMode + to draw, if present. For each entry in the array, SkRect tex locates sprite in + atlas, and SkRSXform xform transforms it into destination space. + + xform, text, and colors if present, must contain count entries. + Optional colors is applied for each sprite using SkBlendMode. + Optional cullRect is a conservative bounds of all transformed sprites. + If cullRect is outside of clip, canvas can skip drawing. + + @param atlas SkImage containing sprites + @param xform SkRSXform mappings for sprites in atlas + @param tex SkRect locations of sprites in atlas + @param colors one per sprite, blended with sprite using SkBlendMode; may be nullptr + @param count number of sprites to draw + @param mode SkBlendMode combining colors and sprites + @param cullRect bounds of transformed sprites for efficient clipping; may be nullptr + @param paint SkColorFilter, SkImageFilter, SkBlendMode, and so on; may be nullptr + */ + void drawAtlas(const sk_sp& atlas, const SkRSXform xform[], const SkRect tex[], + const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect, + const SkPaint* paint) { + this->drawAtlas(atlas.get(), xform, tex, colors, count, mode, cullRect, paint); + } + + /** Draws a set of sprites from atlas, using clip, SkMatrix, and optional SkPaint paint. + paint uses anti-alias, alpha, SkColorFilter, SkImageFilter, and SkBlendMode + to draw, if present. For each entry in the array, SkRect tex locates sprite in + atlas, and SkRSXform xform transforms it into destination space. + + xform and text must contain count entries. + Optional cullRect is a conservative bounds of all transformed sprites. + If cullRect is outside of clip, canvas can skip drawing. + + @param atlas SkImage containing sprites + @param xform SkRSXform mappings for sprites in atlas + @param tex SkRect locations of sprites in atlas + @param count number of sprites to draw + @param cullRect bounds of transformed sprites for efficient clipping; may be nullptr + @param paint SkColorFilter, SkImageFilter, SkBlendMode, and so on; may be nullptr + */ + void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], int count, + const SkRect* cullRect, const SkPaint* paint) { + this->drawAtlas(atlas, xform, tex, nullptr, count, SkBlendMode::kDst, cullRect, paint); + } + + /** Draws a set of sprites from atlas, using clip, SkMatrix, and optional SkPaint paint. + paint uses anti-alias, alpha, SkColorFilter, SkImageFilter, and SkBlendMode + to draw, if present. For each entry in the array, SkRect tex locates sprite in + atlas, and SkRSXform xform transforms it into destination space. + + xform and text must contain count entries. + Optional cullRect is a conservative bounds of all transformed sprites. + If cullRect is outside of clip, canvas can skip drawing. + + @param atlas SkImage containing sprites + @param xform SkRSXform mappings for sprites in atlas + @param tex SkRect locations of sprites in atlas + @param count number of sprites to draw + @param cullRect bounds of transformed sprites for efficient clipping; may be nullptr + @param paint SkColorFilter, SkImageFilter, SkBlendMode, and so on; may be nullptr + */ + void drawAtlas(const sk_sp& atlas, const SkRSXform xform[], const SkRect tex[], + int count, const SkRect* cullRect, const SkPaint* paint) { + this->drawAtlas(atlas.get(), xform, tex, nullptr, count, SkBlendMode::kDst, + cullRect, paint); + } + + /** Draws SkDrawable drawable using clip and SkMatrix, concatenated with + optional matrix. + + If SkCanvas has an asynchronous implementation, as is the case + when it is recording into SkPicture, then drawable will be referenced, + so that SkDrawable::draw() can be called when the operation is finalized. To force + immediate drawing, call SkDrawable::draw() instead. + + @param drawable custom struct encapsulating drawing commands + @param matrix transformation applied to drawing; may be nullptr + */ + void drawDrawable(SkDrawable* drawable, const SkMatrix* matrix = nullptr); + + /** Draws SkDrawable drawable using clip and SkMatrix, offset by (x, y). + + If SkCanvas has an asynchronous implementation, as is the case + when it is recording into SkPicture, then drawable will be referenced, + so that SkDrawable::draw() can be called when the operation is finalized. To force + immediate drawing, call SkDrawable::draw() instead. + + @param drawable custom struct encapsulating drawing commands + @param x offset into SkCanvas writable pixels on x-axis + @param y offset into SkCanvas writable pixels on y-axis + */ + void drawDrawable(SkDrawable* drawable, SkScalar x, SkScalar y); + + /** Associates SkRect on SkCanvas with an annotation; a key-value pair, where the key is + a null-terminated UTF-8 string, and optional value is stored as SkData. + + Only some canvas implementations, such as recording to SkPicture, or drawing to + document PDF, use annotations. + + @param rect SkRect extent of canvas to annotate + @param key string used for lookup + @param value data holding value stored in annotation + */ + void drawAnnotation(const SkRect& rect, const char key[], SkData* value); + + /** Associates SkRect on SkCanvas when an annotation; a key-value pair, where the key is + a null-terminated UTF-8 string, and optional value is stored as SkData. + + Only some canvas implementations, such as recording to SkPicture, or drawing to + document PDF, use annotations. + + @param rect SkRect extent of canvas to annotate + @param key string used for lookup + @param value data holding value stored in annotation + */ + void drawAnnotation(const SkRect& rect, const char key[], const sk_sp& value) { + this->drawAnnotation(rect, key, value.get()); + } + + /** Returns true if clip is empty; that is, nothing will draw. + + May do work when called; it should not be called + more often than needed. However, once called, subsequent calls perform no + work until clip changes. + + @return true if clip is empty + */ + virtual bool isClipEmpty() const; + + /** Returns true if clip is SkRect and not empty. + Returns false if the clip is empty, or if it is not SkRect. + + @return true if clip is SkRect and not empty + */ + virtual bool isClipRect() const; + + /** Returns SkMatrix. + This does not account for translation by SkBaseDevice or SkSurface. + + @return SkMatrix in SkCanvas + */ + const SkMatrix& getTotalMatrix() const; + + /////////////////////////////////////////////////////////////////////////// + + // don't call + virtual GrRenderTargetContext* internal_private_accessTopLayerRenderTargetContext(); + SkIRect internal_private_getTopLayerBounds() const { return getTopLayerBounds(); } + + // TEMP helpers until we switch virtual over to const& for src-rect + void legacy_drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + void legacy_drawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint = kStrict_SrcRectConstraint); + + /** + * Returns the global clip as a region. If the clip contains AA, then only the bounds + * of the clip may be returned. + */ + void temporary_internal_getRgnClip(SkRegion* region); + + void private_draw_shadow_rec(const SkPath&, const SkDrawShadowRec&); + + +protected: + // default impl defers to getDevice()->newSurface(info) + virtual sk_sp onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props); + + // default impl defers to its device + virtual bool onPeekPixels(SkPixmap* pixmap); + virtual bool onAccessTopLayerPixels(SkPixmap* pixmap); + virtual SkImageInfo onImageInfo() const; + virtual bool onGetProps(SkSurfaceProps* props) const; + virtual void onFlush(); + + // Subclass save/restore notifiers. + // Overriders should call the corresponding INHERITED method up the inheritance chain. + // getSaveLayerStrategy()'s return value may suppress full layer allocation. + enum SaveLayerStrategy { + kFullLayer_SaveLayerStrategy, + kNoLayer_SaveLayerStrategy, + }; + + virtual void willSave() {} + // Overriders should call the corresponding INHERITED method up the inheritance chain. + virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& ) { + return kFullLayer_SaveLayerStrategy; + } + // returns true if we should actually perform the saveBehind, or false if we should just save. + virtual bool onDoSaveBehind(const SkRect*) { return true; } + virtual void willRestore() {} + virtual void didRestore() {} + virtual void didConcat(const SkMatrix& ) {} + virtual void didSetMatrix(const SkMatrix& ) {} + virtual void didTranslate(SkScalar dx, SkScalar dy) { + this->didConcat(SkMatrix::MakeTrans(dx, dy)); + } + + // NOTE: If you are adding a new onDraw virtual to SkCanvas, PLEASE add an override to + // SkCanvasVirtualEnforcer (in SkCanvasVirtualEnforcer.h). This ensures that subclasses using + // that mechanism will be required to implement the new function. + virtual void onDrawPaint(const SkPaint& paint); + virtual void onDrawBehind(const SkPaint& paint); + virtual void onDrawRect(const SkRect& rect, const SkPaint& paint); + virtual void onDrawRRect(const SkRRect& rrect, const SkPaint& paint); + virtual void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint); + virtual void onDrawOval(const SkRect& rect, const SkPaint& paint); + virtual void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint); + virtual void onDrawPath(const SkPath& path, const SkPaint& paint); + virtual void onDrawRegion(const SkRegion& region, const SkPaint& paint); + + virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint); + + virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint); + virtual void onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint); + + // TODO: Remove old signature + virtual void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode, + const SkPaint& paint) { + this->onDrawVerticesObject(vertices, nullptr, 0, mode, paint); + } + virtual void onDrawVerticesObject(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode mode, const SkPaint& paint); + + virtual void onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy, const SkPaint* paint); + virtual void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint); + virtual void onDrawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint); + virtual void onDrawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint); + + virtual void onDrawBitmap(const SkBitmap& bitmap, SkScalar dx, SkScalar dy, + const SkPaint* paint); + virtual void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint); + virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, + const SkPaint* paint); + virtual void onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint); + + virtual void onDrawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect rect[], + const SkColor colors[], int count, SkBlendMode mode, + const SkRect* cull, const SkPaint* paint); + + virtual void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value); + virtual void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&); + + virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix); + virtual void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint); + + virtual void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags, + const SkColor4f& color, SkBlendMode mode); + virtual void onDrawEdgeAAImageSet(const ImageSetEntry imageSet[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint* paint, SrcRectConstraint constraint); + + enum ClipEdgeStyle { + kHard_ClipEdgeStyle, + kSoft_ClipEdgeStyle + }; + + virtual void onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle); + virtual void onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle); + virtual void onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle); + virtual void onClipRegion(const SkRegion& deviceRgn, SkClipOp op); + + virtual void onDiscard(); + + // Clip rectangle bounds. Called internally by saveLayer. + // returns false if the entire rectangle is entirely clipped out + // If non-NULL, The imageFilter parameter will be used to expand the clip + // and offscreen bounds for any margin required by the filter DAG. + bool clipRectBounds(const SkRect* bounds, SaveLayerFlags flags, SkIRect* intersection, + const SkImageFilter* imageFilter = nullptr); + + SkBaseDevice* getTopDevice() const; + +private: + /** After calling saveLayer(), there can be any number of devices that make + up the top-most drawing area. LayerIter can be used to iterate through + those devices. Note that the iterator is only valid until the next API + call made on the canvas. Ownership of all pointers in the iterator stays + with the canvas, so none of them should be modified or deleted. + */ + class LayerIter /*: SkNoncopyable*/ { + public: + /** Initialize iterator with canvas, and set values for 1st device */ + LayerIter(SkCanvas*); + ~LayerIter(); + + /** Return true if the iterator is done */ + bool done() const { return fDone; } + /** Cycle to the next device */ + void next(); + + // These reflect the current device in the iterator + + SkBaseDevice* device() const; + const SkMatrix& matrix() const; + SkIRect clipBounds() const; + const SkPaint& paint() const; + int x() const; + int y() const; + + private: + // used to embed the SkDrawIter object directly in our instance, w/o + // having to expose that class def to the public. There is an assert + // in our constructor to ensure that fStorage is large enough + // (though needs to be a compile-time-assert!). We use intptr_t to work + // safely with 32 and 64 bit machines (to ensure the storage is enough) + intptr_t fStorage[32]; + class SkDrawIter* fImpl; // this points at fStorage + SkPaint fDefaultPaint; + bool fDone; + }; + + static bool BoundsAffectsClip(SaveLayerFlags); + + static void DrawDeviceWithFilter(SkBaseDevice* src, const SkImageFilter* filter, + SkBaseDevice* dst, const SkIPoint& dstOrigin, + const SkMatrix& ctm); + + enum ShaderOverrideOpacity { + kNone_ShaderOverrideOpacity, //!< there is no overriding shader (bitmap or image) + kOpaque_ShaderOverrideOpacity, //!< the overriding shader is opaque + kNotOpaque_ShaderOverrideOpacity, //!< the overriding shader may not be opaque + }; + + // notify our surface (if we have one) that we are about to draw, so it + // can perform copy-on-write or invalidate any cached images + void predrawNotify(bool willOverwritesEntireSurface = false); + void predrawNotify(const SkRect* rect, const SkPaint* paint, ShaderOverrideOpacity); + void predrawNotify(const SkRect* rect, const SkPaint* paint, bool shaderOverrideIsOpaque) { + this->predrawNotify(rect, paint, shaderOverrideIsOpaque ? kOpaque_ShaderOverrideOpacity + : kNotOpaque_ShaderOverrideOpacity); + } + + SkBaseDevice* getDevice() const; + + class MCRec; + + SkDeque fMCStack; + // points to top of stack + MCRec* fMCRec; + + // the first N recs that can fit here mean we won't call malloc + static constexpr int kMCRecSize = 128; // most recent measurement + static constexpr int kMCRecCount = 32; // common depth for save/restores + static constexpr int kDeviceCMSize = 224; // most recent measurement + + intptr_t fMCRecStorage[kMCRecSize * kMCRecCount / sizeof(intptr_t)]; + intptr_t fDeviceCMStorage[kDeviceCMSize / sizeof(intptr_t)]; + + const SkSurfaceProps fProps; + + int fSaveCount; // value returned by getSaveCount() + + std::unique_ptr fAllocator; + + SkSurface_Base* fSurfaceBase; + SkSurface_Base* getSurfaceBase() const { return fSurfaceBase; } + void setSurfaceBase(SkSurface_Base* sb) { + fSurfaceBase = sb; + } + friend class SkSurface_Base; + friend class SkSurface_Gpu; + + SkIRect fClipRestrictionRect = SkIRect::MakeEmpty(); + + void doSave(); + void checkForDeferredSave(); + void internalSetMatrix(const SkMatrix&); + + friend class SkAndroidFrameworkUtils; + friend class SkCanvasPriv; // needs kDontClipToLayer_PrivateSaveLayerFlag + friend class SkDrawIter; // needs setupDrawForLayerDevice() + friend class AutoLayerForImageFilter; + friend class DebugCanvas; // needs experimental fAllowSimplifyClip + friend class SkSurface_Raster; // needs getDevice() + friend class SkNoDrawCanvas; // needs resetForNextPicture() + friend class SkPictureRecord; // predrawNotify (why does it need it? ) + friend class SkOverdrawCanvas; + friend class SkRasterHandleAllocator; +protected: + // For use by SkNoDrawCanvas (via SkCanvasVirtualEnforcer, which can't be a friend) + SkCanvas(const SkIRect& bounds); +private: + SkCanvas(const SkBitmap&, std::unique_ptr, + SkRasterHandleAllocator::Handle); + + SkCanvas(SkCanvas&&) = delete; + SkCanvas(const SkCanvas&) = delete; + SkCanvas& operator=(SkCanvas&&) = delete; + SkCanvas& operator=(const SkCanvas&) = delete; + + /** Experimental + * Saves the specified subset of the current pixels in the current layer, + * and then clears those pixels to transparent black. + * Restores the pixels on restore() by drawing them in SkBlendMode::kDstOver. + * + * @param subset conservative bounds of the area to be saved / restored. + * @return depth of save state stack before this call was made. + */ + int only_axis_aligned_saveBehind(const SkRect* subset); + + /** + * Like drawPaint, but magically clipped to the most recent saveBehind buffer rectangle. + * If there is no active saveBehind, then this draws nothing. + */ + void drawClippedToSaveBehind(const SkPaint&); + + void resetForNextPicture(const SkIRect& bounds); + + // needs gettotalclip() + friend class SkCanvasStateUtils; + + // call this each time we attach ourselves to a device + // - constructor + // - internalSaveLayer + void setupDevice(SkBaseDevice*); + + void init(sk_sp); + + /** + * Gets the bounds of the top level layer in global canvas coordinates. We don't want this + * to be public because it exposes decisions about layer sizes that are internal to the canvas. + */ + SkIRect getTopLayerBounds() const; + + void internalDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, + const SkRect& dst, const SkPaint* paint, + SrcRectConstraint); + void internalDrawPaint(const SkPaint& paint); + void internalSaveLayer(const SaveLayerRec&, SaveLayerStrategy); + void internalSaveBehind(const SkRect*); + void internalDrawDevice(SkBaseDevice*, int x, int y, const SkPaint*, SkImage* clipImage, + const SkMatrix& clipMatrix); + + // shared by save() and saveLayer() + void internalSave(); + void internalRestore(); + + /* + * Returns true if drawing the specified rect (or all if it is null) with the specified + * paint (or default if null) would overwrite the entire root device of the canvas + * (i.e. the canvas' surface if it had one). + */ + bool wouldOverwriteEntireSurface(const SkRect*, const SkPaint*, ShaderOverrideOpacity) const; + + /** + * Returns true if the paint's imagefilter can be invoked directly, without needed a layer. + */ + bool canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const SkPaint&); + + /** + * Returns true if the clip (for any active layer) contains antialiasing. + * If the clip is empty, this will return false. + */ + bool androidFramework_isClipAA() const; + + virtual SkPaintFilterCanvas* internal_private_asPaintFilterCanvas() const { return nullptr; } + + /** + * Keep track of the device clip bounds and if the matrix is scale-translate. This allows + * us to do a fast quick reject in the common case. + */ + bool fIsScaleTranslate; + SkRect fDeviceClipBounds; + + bool fAllowSoftClip; + bool fAllowSimplifyClip; + + class AutoValidateClip { + public: + explicit AutoValidateClip(SkCanvas* canvas) : fCanvas(canvas) { + fCanvas->validateClip(); + } + ~AutoValidateClip() { fCanvas->validateClip(); } + + private: + const SkCanvas* fCanvas; + + AutoValidateClip(AutoValidateClip&&) = delete; + AutoValidateClip(const AutoValidateClip&) = delete; + AutoValidateClip& operator=(AutoValidateClip&&) = delete; + AutoValidateClip& operator=(const AutoValidateClip&) = delete; + }; + +#ifdef SK_DEBUG + void validateClip() const; +#else + void validateClip() const {} +#endif + + std::unique_ptr fScratchGlyphRunBuilder; + + typedef SkRefCnt INHERITED; +}; + +/** \class SkAutoCanvasRestore + Stack helper class calls SkCanvas::restoreToCount when SkAutoCanvasRestore + goes out of scope. Use this to guarantee that the canvas is restored to a known + state. +*/ +class SkAutoCanvasRestore { +public: + + /** Preserves SkCanvas::save() count. Optionally saves SkCanvas clip and SkCanvas matrix. + + @param canvas SkCanvas to guard + @param doSave call SkCanvas::save() + @return utility to restore SkCanvas state on destructor + */ + SkAutoCanvasRestore(SkCanvas* canvas, bool doSave) : fCanvas(canvas), fSaveCount(0) { + if (fCanvas) { + fSaveCount = canvas->getSaveCount(); + if (doSave) { + canvas->save(); + } + } + } + + /** Restores SkCanvas to saved state. Destructor is called when container goes out of + scope. + */ + ~SkAutoCanvasRestore() { + if (fCanvas) { + fCanvas->restoreToCount(fSaveCount); + } + } + + /** Restores SkCanvas to saved state immediately. Subsequent calls and + ~SkAutoCanvasRestore() have no effect. + */ + void restore() { + if (fCanvas) { + fCanvas->restoreToCount(fSaveCount); + fCanvas = nullptr; + } + } + +private: + SkCanvas* fCanvas; + int fSaveCount; + + SkAutoCanvasRestore(SkAutoCanvasRestore&&) = delete; + SkAutoCanvasRestore(const SkAutoCanvasRestore&) = delete; + SkAutoCanvasRestore& operator=(SkAutoCanvasRestore&&) = delete; + SkAutoCanvasRestore& operator=(const SkAutoCanvasRestore&) = delete; +}; + +// Private +#define SkAutoCanvasRestore(...) SK_REQUIRE_LOCAL_VAR(SkAutoCanvasRestore) + +#endif diff --git a/gfx/skia/skia/include/core/SkCanvasVirtualEnforcer.h b/gfx/skia/skia/include/core/SkCanvasVirtualEnforcer.h new file mode 100644 index 0000000000..5911f383c8 --- /dev/null +++ b/gfx/skia/skia/include/core/SkCanvasVirtualEnforcer.h @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCanvasVirtualEnforcer_DEFINED +#define SkCanvasVirtualEnforcer_DEFINED + +#include "include/core/SkCanvas.h" + +// If you would ordinarily want to inherit from Base (eg SkCanvas, SkNWayCanvas), instead +// inherit from SkCanvasVirtualEnforcer, which will make the build fail if you forget +// to override one of SkCanvas' key virtual hooks. +template +class SkCanvasVirtualEnforcer : public Base { +public: + using Base::Base; + +protected: + void onDrawPaint(const SkPaint& paint) override = 0; + void onDrawBehind(const SkPaint&) override {} // make zero after android updates + void onDrawRect(const SkRect& rect, const SkPaint& paint) override = 0; + void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override = 0; + void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) override = 0; + void onDrawOval(const SkRect& rect, const SkPaint& paint) override = 0; + void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, + const SkPaint& paint) override = 0; + void onDrawPath(const SkPath& path, const SkPaint& paint) override = 0; + void onDrawRegion(const SkRegion& region, const SkPaint& paint) override = 0; + + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override = 0; + + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode mode, + const SkPaint& paint) override = 0; + void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) override = 0; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override = 0; + + void onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy, + const SkPaint* paint) override = 0; + void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SkCanvas::SrcRectConstraint constraint) override = 0; + void onDrawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) override = 0; + void onDrawImageLattice(const SkImage* image, const SkCanvas::Lattice& lattice, + const SkRect& dst, const SkPaint* paint) override = 0; + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // This is under active development for Chrome and not used in Android. Hold off on adding + // implementations in Android's SkCanvas subclasses until this stabilizes. + void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) override {} + void onDrawEdgeAAImageSet(const SkCanvas::ImageSetEntry imageSet[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkPaint* paint, + SkCanvas::SrcRectConstraint constraint) override {} +#else + void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) override = 0; + void onDrawEdgeAAImageSet(const SkCanvas::ImageSetEntry imageSet[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkPaint* paint, + SkCanvas::SrcRectConstraint constraint) override = 0; +#endif + + void onDrawBitmap(const SkBitmap& bitmap, SkScalar dx, SkScalar dy, + const SkPaint* paint) override = 0; + void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, + const SkPaint* paint, + SkCanvas::SrcRectConstraint constraint) override = 0; + void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) override = 0; + void onDrawBitmapLattice(const SkBitmap& bitmap, const SkCanvas::Lattice& lattice, + const SkRect& dst, const SkPaint* paint) override = 0; + + void onDrawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect rect[], + const SkColor colors[], int count, SkBlendMode mode, const SkRect* cull, + const SkPaint* paint) override = 0; + + void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override = 0; + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override = 0; + + void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override = 0; + void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) override = 0; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkClipOp.h b/gfx/skia/skia/include/core/SkClipOp.h new file mode 100644 index 0000000000..a9ea7a04b4 --- /dev/null +++ b/gfx/skia/skia/include/core/SkClipOp.h @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkClipOp_DEFINED +#define SkClipOp_DEFINED + +#include "include/core/SkTypes.h" + +enum class SkClipOp { + kDifference = 0, + kIntersect = 1, + +#ifdef SK_SUPPORT_DEPRECATED_CLIPOPS + kUnion_deprecated = 2, + kXOR_deprecated = 3, + kReverseDifference_deprecated = 4, + kReplace_deprecated = 5, +#else + kExtraEnumNeedInternallyPleaseIgnoreWillGoAway2 = 2, + kExtraEnumNeedInternallyPleaseIgnoreWillGoAway3 = 3, + kExtraEnumNeedInternallyPleaseIgnoreWillGoAway4 = 4, + kExtraEnumNeedInternallyPleaseIgnoreWillGoAway5 = 5, +#endif + + // Used internally for validation, can only shrink to 1 when the deprecated flag is gone + kMax_EnumValue = 5, +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkColor.h b/gfx/skia/skia/include/core/SkColor.h new file mode 100644 index 0000000000..bee77d8b60 --- /dev/null +++ b/gfx/skia/skia/include/core/SkColor.h @@ -0,0 +1,414 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColor_DEFINED +#define SkColor_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" + +/** \file SkColor.h + + Types, consts, functions, and macros for colors. +*/ + +/** 8-bit type for an alpha value. 255 is 100% opaque, zero is 100% transparent. +*/ +typedef uint8_t SkAlpha; + +/** 32-bit ARGB color value, unpremultiplied. Color components are always in + a known order. This is different from SkPMColor, which has its bytes in a configuration + dependent order, to match the format of kBGRA_8888_SkColorType bitmaps. SkColor + is the type used to specify colors in SkPaint and in gradients. + + Color that is premultiplied has the same component values as color + that is unpremultiplied if alpha is 255, fully opaque, although may have the + component values in a different order. +*/ +typedef uint32_t SkColor; + +/** Returns color value from 8-bit component values. Asserts if SK_DEBUG is defined + if a, r, g, or b exceed 255. Since color is unpremultiplied, a may be smaller + than the largest of r, g, and b. + + @param a amount of alpha, from fully transparent (0) to fully opaque (255) + @param r amount of red, from no red (0) to full red (255) + @param g amount of green, from no green (0) to full green (255) + @param b amount of blue, from no blue (0) to full blue (255) + @return color and alpha, unpremultiplied +*/ +static constexpr inline SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + return SkASSERT(a <= 255 && r <= 255 && g <= 255 && b <= 255), + (a << 24) | (r << 16) | (g << 8) | (b << 0); +} + +/** Returns color value from 8-bit component values, with alpha set + fully opaque to 255. +*/ +#define SkColorSetRGB(r, g, b) SkColorSetARGB(0xFF, r, g, b) + +/** Returns alpha byte from color value. +*/ +#define SkColorGetA(color) (((color) >> 24) & 0xFF) + +/** Returns red component of color, from zero to 255. +*/ +#define SkColorGetR(color) (((color) >> 16) & 0xFF) + +/** Returns green component of color, from zero to 255. +*/ +#define SkColorGetG(color) (((color) >> 8) & 0xFF) + +/** Returns blue component of color, from zero to 255. +*/ +#define SkColorGetB(color) (((color) >> 0) & 0xFF) + +/** Returns unpremultiplied color with red, blue, and green set from c; and alpha set + from a. Alpha component of c is ignored and is replaced by a in result. + + @param c packed RGB, eight bits per component + @param a alpha: transparent at zero, fully opaque at 255 + @return color with transparency +*/ +static constexpr inline SkColor SkColorSetA(SkColor c, U8CPU a) { + return (c & 0x00FFFFFF) | (a << 24); +} + +/** Represents fully transparent SkAlpha value. SkAlpha ranges from zero, + fully transparent; to 255, fully opaque. +*/ +constexpr SkAlpha SK_AlphaTRANSPARENT = 0x00; + +/** Represents fully opaque SkAlpha value. SkAlpha ranges from zero, + fully transparent; to 255, fully opaque. +*/ +constexpr SkAlpha SK_AlphaOPAQUE = 0xFF; + +/** Represents fully transparent SkColor. May be used to initialize a destination + containing a mask or a non-rectangular image. +*/ +constexpr SkColor SK_ColorTRANSPARENT = SkColorSetARGB(0x00, 0x00, 0x00, 0x00); + +/** Represents fully opaque black. +*/ +constexpr SkColor SK_ColorBLACK = SkColorSetARGB(0xFF, 0x00, 0x00, 0x00); + +/** Represents fully opaque dark gray. + Note that SVG dark gray is equivalent to 0xFFA9A9A9. +*/ +constexpr SkColor SK_ColorDKGRAY = SkColorSetARGB(0xFF, 0x44, 0x44, 0x44); + +/** Represents fully opaque gray. + Note that HTML gray is equivalent to 0xFF808080. +*/ +constexpr SkColor SK_ColorGRAY = SkColorSetARGB(0xFF, 0x88, 0x88, 0x88); + +/** Represents fully opaque light gray. HTML silver is equivalent to 0xFFC0C0C0. + Note that SVG light gray is equivalent to 0xFFD3D3D3. +*/ +constexpr SkColor SK_ColorLTGRAY = SkColorSetARGB(0xFF, 0xCC, 0xCC, 0xCC); + +/** Represents fully opaque white. +*/ +constexpr SkColor SK_ColorWHITE = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); + +/** Represents fully opaque red. +*/ +constexpr SkColor SK_ColorRED = SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00); + +/** Represents fully opaque green. HTML lime is equivalent. + Note that HTML green is equivalent to 0xFF008000. +*/ +constexpr SkColor SK_ColorGREEN = SkColorSetARGB(0xFF, 0x00, 0xFF, 0x00); + +/** Represents fully opaque blue. +*/ +constexpr SkColor SK_ColorBLUE = SkColorSetARGB(0xFF, 0x00, 0x00, 0xFF); + +/** Represents fully opaque yellow. +*/ +constexpr SkColor SK_ColorYELLOW = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0x00); + +/** Represents fully opaque cyan. HTML aqua is equivalent. +*/ +constexpr SkColor SK_ColorCYAN = SkColorSetARGB(0xFF, 0x00, 0xFF, 0xFF); + +/** Represents fully opaque magenta. HTML fuchsia is equivalent. +*/ +constexpr SkColor SK_ColorMAGENTA = SkColorSetARGB(0xFF, 0xFF, 0x00, 0xFF); + +/** Converts RGB to its HSV components. + hsv[0] contains hsv hue, a value from zero to less than 360. + hsv[1] contains hsv saturation, a value from zero to one. + hsv[2] contains hsv value, a value from zero to one. + + @param red red component value from zero to 255 + @param green green component value from zero to 255 + @param blue blue component value from zero to 255 + @param hsv three element array which holds the resulting HSV components +*/ +SK_API void SkRGBToHSV(U8CPU red, U8CPU green, U8CPU blue, SkScalar hsv[3]); + +/** Converts ARGB to its HSV components. Alpha in ARGB is ignored. + hsv[0] contains hsv hue, and is assigned a value from zero to less than 360. + hsv[1] contains hsv saturation, a value from zero to one. + hsv[2] contains hsv value, a value from zero to one. + + @param color ARGB color to convert + @param hsv three element array which holds the resulting HSV components +*/ +static inline void SkColorToHSV(SkColor color, SkScalar hsv[3]) { + SkRGBToHSV(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color), hsv); +} + +/** Converts HSV components to an ARGB color. Alpha is passed through unchanged. + hsv[0] represents hsv hue, an angle from zero to less than 360. + hsv[1] represents hsv saturation, and varies from zero to one. + hsv[2] represents hsv value, and varies from zero to one. + + Out of range hsv values are pinned. + + @param alpha alpha component of the returned ARGB color + @param hsv three element array which holds the input HSV components + @return ARGB equivalent to HSV +*/ +SK_API SkColor SkHSVToColor(U8CPU alpha, const SkScalar hsv[3]); + +/** Converts HSV components to an ARGB color. Alpha is set to 255. + hsv[0] represents hsv hue, an angle from zero to less than 360. + hsv[1] represents hsv saturation, and varies from zero to one. + hsv[2] represents hsv value, and varies from zero to one. + + Out of range hsv values are pinned. + + @param hsv three element array which holds the input HSV components + @return RGB equivalent to HSV +*/ +static inline SkColor SkHSVToColor(const SkScalar hsv[3]) { + return SkHSVToColor(0xFF, hsv); +} + +/** 32-bit ARGB color value, premultiplied. The byte order for this value is + configuration dependent, matching the format of kBGRA_8888_SkColorType bitmaps. + This is different from SkColor, which is unpremultiplied, and is always in the + same byte order. +*/ +typedef uint32_t SkPMColor; + +/** Returns a SkPMColor value from unpremultiplied 8-bit component values. + + @param a amount of alpha, from fully transparent (0) to fully opaque (255) + @param r amount of red, from no red (0) to full red (255) + @param g amount of green, from no green (0) to full green (255) + @param b amount of blue, from no blue (0) to full blue (255) + @return premultiplied color +*/ +SK_API SkPMColor SkPreMultiplyARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b); + +/** Returns pmcolor closest to color c. Multiplies c RGB components by the c alpha, + and arranges the bytes to match the format of kN32_SkColorType. + + @param c unpremultiplied ARGB color + @return premultiplied color +*/ +SK_API SkPMColor SkPreMultiplyColor(SkColor c); + +/** \enum SkColorChannel + Describes different color channels one can manipulate +*/ +enum class SkColorChannel { + kR, // the red channel + kG, // the green channel + kB, // the blue channel + kA, // the alpha channel + + kLastEnum = kA, +}; + +/** \struct SkRGBA4f + RGBA color value, holding four floating point components. Color components are always in + a known order. kAT determines if the SkRGBA4f's R, G, and B components are premultiplied + by alpha or not. + + Skia's public API always uses unpremultiplied colors, which can be stored as + SkRGBA4f. For convenience, this type can also be referred to + as SkColor4f. +*/ +template +struct SkRGBA4f { + float fR; //!< red component + float fG; //!< green component + float fB; //!< blue component + float fA; //!< alpha component + + /** Compares SkRGBA4f with other, and returns true if all components are equal. + + @param other SkRGBA4f to compare + @return true if SkRGBA4f equals other + */ + bool operator==(const SkRGBA4f& other) const { + return fA == other.fA && fR == other.fR && fG == other.fG && fB == other.fB; + } + + /** Compares SkRGBA4f with other, and returns true if not all components are equal. + + @param other SkRGBA4f to compare + @return true if SkRGBA4f is not equal to other + */ + bool operator!=(const SkRGBA4f& other) const { + return !(*this == other); + } + + /** Returns SkRGBA4f multiplied by scale. + + @param scale value to multiply by + @return SkRGBA4f as (fR * scale, fG * scale, fB * scale, fA * scale) + */ + SkRGBA4f operator*(float scale) const { + return { fR * scale, fG * scale, fB * scale, fA * scale }; + } + + /** Returns SkRGBA4f multiplied component-wise by scale. + + @param scale SkRGBA4f to multiply by + @return SkRGBA4f as (fR * scale.fR, fG * scale.fG, fB * scale.fB, fA * scale.fA) + */ + SkRGBA4f operator*(const SkRGBA4f& scale) const { + return { fR * scale.fR, fG * scale.fG, fB * scale.fB, fA * scale.fA }; + } + + /** Returns a pointer to components of SkRGBA4f, for array access. + + @return pointer to array [fR, fG, fB, fA] + */ + const float* vec() const { return &fR; } + + /** Returns a pointer to components of SkRGBA4f, for array access. + + @return pointer to array [fR, fG, fB, fA] + */ + float* vec() { return &fR; } + + /** Returns one component. Asserts if index is out of range and SK_DEBUG is defined. + + @param index one of: 0 (fR), 1 (fG), 2 (fB), 3 (fA) + @return value corresponding to index + */ + float operator[](int index) const { + SkASSERT(index >= 0 && index < 4); + return this->vec()[index]; + } + + /** Returns one component. Asserts if index is out of range and SK_DEBUG is defined. + + @param index one of: 0 (fR), 1 (fG), 2 (fB), 3 (fA) + @return value corresponding to index + */ + float& operator[](int index) { + SkASSERT(index >= 0 && index < 4); + return this->vec()[index]; + } + + /** Returns true if SkRGBA4f is an opaque color. Asserts if fA is out of range and + SK_DEBUG is defined. + + @return true if SkRGBA4f is opaque + */ + bool isOpaque() const { + SkASSERT(fA <= 1.0f && fA >= 0.0f); + return fA == 1.0f; + } + + /** Returns true if all channels are in [0, 1]. */ + bool fitsInBytes() const { + SkASSERT(fA >= 0.0f && fA <= 1.0f); + return fR >= 0.0f && fR <= 1.0f && + fG >= 0.0f && fG <= 1.0f && + fB >= 0.0f && fB <= 1.0f; + } + + /** Returns closest SkRGBA4f to SkColor. Only allowed if SkRGBA4f is unpremultiplied. + + @param color Color with Alpha, red, blue, and green components + @return SkColor as SkRGBA4f + */ + static SkRGBA4f FromColor(SkColor color); // impl. depends on kAT + + /** Returns closest SkColor to SkRGBA4f. Only allowed if SkRGBA4f is unpremultiplied. + + @return color as SkColor + */ + SkColor toSkColor() const; // impl. depends on kAT + + /** Returns closest SkRGBA4f to SkPMColor. Only allowed if SkRGBA4f is premultiplied. + + @return SkPMColor as SkRGBA4f + */ + static SkRGBA4f FromPMColor(SkPMColor); // impl. depends on kAT + + /** Returns SkRGBA4f premultiplied by alpha. Asserts at compile time if SkRGBA4f is + already premultiplied. + + @return premultiplied color + */ + SkRGBA4f premul() const { + static_assert(kAT == kUnpremul_SkAlphaType, ""); + return { fR * fA, fG * fA, fB * fA, fA }; + } + + /** Returns SkRGBA4f unpremultiplied by alpha. Asserts at compile time if SkRGBA4f is + already unpremultiplied. + + @return unpremultiplied color + */ + SkRGBA4f unpremul() const { + static_assert(kAT == kPremul_SkAlphaType, ""); + + if (fA == 0.0f) { + return { 0, 0, 0, 0 }; + } else { + float invAlpha = 1 / fA; + return { fR * invAlpha, fG * invAlpha, fB * invAlpha, fA }; + } + } + + // This produces bytes in RGBA order (eg GrColor). Impl. is the same, regardless of kAT + uint32_t toBytes_RGBA() const; + static SkRGBA4f FromBytes_RGBA(uint32_t color); + + SkRGBA4f makeOpaque() const { + return { fR, fG, fB, 1.0f }; + } +}; + +/** \struct SkColor4f + RGBA color value, holding four floating point components. Color components are always in + a known order, and are unpremultiplied. + + This is a specialization of SkRGBA4f. For details, @see SkRGBA4f. +*/ +using SkColor4f = SkRGBA4f; + +template <> SK_API SkColor4f SkColor4f::FromColor(SkColor); +template <> SK_API SkColor SkColor4f::toSkColor() const; + +namespace SkColors { +constexpr SkColor4f kTransparent = {0, 0, 0, 0}; +constexpr SkColor4f kBlack = {0, 0, 0, 1}; +constexpr SkColor4f kDkGray = {0.25f, 0.25f, 0.25f, 1}; +constexpr SkColor4f kGray = {0.50f, 0.50f, 0.50f, 1}; +constexpr SkColor4f kLtGray = {0.75f, 0.75f, 0.75f, 1}; +constexpr SkColor4f kWhite = {1, 1, 1, 1}; +constexpr SkColor4f kRed = {1, 0, 0, 1}; +constexpr SkColor4f kGreen = {0, 1, 0, 1}; +constexpr SkColor4f kBlue = {0, 0, 1, 1}; +constexpr SkColor4f kYellow = {1, 1, 0, 1}; +constexpr SkColor4f kCyan = {0, 1, 1, 1}; +constexpr SkColor4f kMagenta = {1, 0, 1, 1}; +} // namespace SkColors +#endif diff --git a/gfx/skia/skia/include/core/SkColorFilter.h b/gfx/skia/skia/include/core/SkColorFilter.h new file mode 100644 index 0000000000..7f1e979b5d --- /dev/null +++ b/gfx/skia/skia/include/core/SkColorFilter.h @@ -0,0 +1,167 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorFilter_DEFINED +#define SkColorFilter_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkRefCnt.h" + +class GrColorInfo; +class GrFragmentProcessor; +class GrRecordingContext; +class SkBitmap; +class SkColorMatrix; +class SkColorSpace; +struct SkStageRec; +class SkString; + +/** + * ColorFilters are optional objects in the drawing pipeline. When present in + * a paint, they are called with the "src" colors, and return new colors, which + * are then passed onto the next stage (either ImageFilter or Xfermode). + * + * All subclasses are required to be reentrant-safe : it must be legal to share + * the same instance between several threads. + */ +class SK_API SkColorFilter : public SkFlattenable { +public: + // DEPRECATED. skbug.com/8941 + + bool asColorMode(SkColor* color, SkBlendMode* mode) const { + return this->onAsAColorMode(color, mode); + } + + /** If the filter can be represented by a source color plus Mode, this + * returns true, and sets (if not NULL) the color and mode appropriately. + * If not, this returns false and ignores the parameters. + */ + bool asAColorMode(SkColor* color, SkBlendMode* mode) const { + return this->onAsAColorMode(color, mode); + } + + /** If the filter can be represented by a 5x4 matrix, this + * returns true, and sets the matrix appropriately. + * If not, this returns false and ignores the parameter. + */ + bool asAColorMatrix(float matrix[20]) const { + return this->onAsAColorMatrix(matrix); + } + + bool appendStages(const SkStageRec& rec, bool shaderIsOpaque) const; + + enum Flags { + /** If set the filter methods will not change the alpha channel of the colors. + */ + kAlphaUnchanged_Flag = 1 << 0, + }; + + /** Returns the flags for this filter. Override in subclasses to return custom flags. + */ + virtual uint32_t getFlags() const { return 0; } + + SkColor filterColor(SkColor) const; + + /** + * Converts the src color (in src colorspace), into the dst colorspace, + * then applies this filter to it, returning the filtered color in the dst colorspace. + */ + SkColor4f filterColor4f(const SkColor4f& srcColor, SkColorSpace* srcCS, + SkColorSpace* dstCS) const; + + /** Construct a colorfilter whose effect is to first apply the inner filter and then apply + * this filter, applied to the output of the inner filter. + * + * result = this(inner(...)) + * + * Due to internal limits, it is possible that this will return NULL, so the caller must + * always check. + */ + sk_sp makeComposed(sk_sp inner) const; + +#if SK_SUPPORT_GPU + /** + * A subclass may implement this factory function to work with the GPU backend. It returns + * a GrFragmentProcessor that implemets the color filter in GPU shader code. + * + * The fragment processor receives a premultiplied input color and produces a premultiplied + * output color. + * + * A null return indicates that the color filter isn't implemented for the GPU backend. + */ + virtual std::unique_ptr asFragmentProcessor( + GrRecordingContext*, const GrColorInfo& dstColorInfo) const; +#endif + + bool affectsTransparentBlack() const { + return this->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT; + } + + static void RegisterFlattenables(); + + static SkFlattenable::Type GetFlattenableType() { + return kSkColorFilter_Type; + } + + SkFlattenable::Type getFlattenableType() const override { + return kSkColorFilter_Type; + } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize( + kSkColorFilter_Type, data, size, procs).release())); + } + +protected: + SkColorFilter() {} + + virtual bool onAsAColorMatrix(float[20]) const; + virtual bool onAsAColorMode(SkColor* color, SkBlendMode* bmode) const; + +private: + /* + * Returns 1 if this is a single filter (not a composition of other filters), otherwise it + * reutrns the number of leaf-node filters in a composition. This should be the same value + * as the number of GrFragmentProcessors returned by asFragmentProcessors's array parameter. + * + * e.g. compose(filter, compose(compose(filter, filter), filter)) --> 4 + */ + virtual int privateComposedFilterCount() const { return 1; } + + virtual bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const = 0; + + friend class SkComposeColorFilter; + + typedef SkFlattenable INHERITED; +}; + +class SK_API SkColorFilters { +public: + static sk_sp Compose(sk_sp outer, sk_sp inner) { + return outer ? outer->makeComposed(inner) : inner; + } + static sk_sp Blend(SkColor c, SkBlendMode mode); + static sk_sp Matrix(const SkColorMatrix&); + static sk_sp Matrix(const float rowMajor[20]); + + // A version of Matrix which operates in HSLA space instead of RGBA. + // I.e. HSLA-to-RGBA(Matrix(RGBA-to-HSLA(input))). + static sk_sp HSLAMatrix(const float rowMajor[20]); + + static sk_sp LinearToSRGBGamma(); + static sk_sp SRGBToLinearGamma(); + static sk_sp Lerp(float t, sk_sp dst, sk_sp src); + +private: + SkColorFilters() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkColorPriv.h b/gfx/skia/skia/include/core/SkColorPriv.h new file mode 100644 index 0000000000..7078d92ac5 --- /dev/null +++ b/gfx/skia/skia/include/core/SkColorPriv.h @@ -0,0 +1,151 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorPriv_DEFINED +#define SkColorPriv_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkMath.h" +#include "include/private/SkTo.h" + +/** Turn 0..255 into 0..256 by adding 1 at the half-way point. Used to turn a + byte into a scale value, so that we can say scale * value >> 8 instead of + alpha * value / 255. + + In debugging, asserts that alpha is 0..255 +*/ +static inline unsigned SkAlpha255To256(U8CPU alpha) { + SkASSERT(SkToU8(alpha) == alpha); + // this one assues that blending on top of an opaque dst keeps it that way + // even though it is less accurate than a+(a>>7) for non-opaque dsts + return alpha + 1; +} + +/** Multiplify value by 0..256, and shift the result down 8 + (i.e. return (value * alpha256) >> 8) + */ +#define SkAlphaMul(value, alpha256) (((value) * (alpha256)) >> 8) + +static inline U8CPU SkUnitScalarClampToByte(SkScalar x) { + return static_cast(SkScalarPin(x, 0, 1) * 255 + 0.5); +} + +#define SK_A32_BITS 8 +#define SK_R32_BITS 8 +#define SK_G32_BITS 8 +#define SK_B32_BITS 8 + +#define SK_A32_MASK ((1 << SK_A32_BITS) - 1) +#define SK_R32_MASK ((1 << SK_R32_BITS) - 1) +#define SK_G32_MASK ((1 << SK_G32_BITS) - 1) +#define SK_B32_MASK ((1 << SK_B32_BITS) - 1) + +/* + * Skia's 32bit backend only supports 1 swizzle order at a time (compile-time). + * This is specified by SK_R32_SHIFT=0 or SK_R32_SHIFT=16. + * + * For easier compatibility with Skia's GPU backend, we further restrict these + * to either (in memory-byte-order) RGBA or BGRA. Note that this "order" does + * not directly correspond to the same shift-order, since we have to take endianess + * into account. + * + * Here we enforce this constraint. + */ + +#define SK_RGBA_R32_SHIFT 0 +#define SK_RGBA_G32_SHIFT 8 +#define SK_RGBA_B32_SHIFT 16 +#define SK_RGBA_A32_SHIFT 24 + +#define SK_BGRA_B32_SHIFT 0 +#define SK_BGRA_G32_SHIFT 8 +#define SK_BGRA_R32_SHIFT 16 +#define SK_BGRA_A32_SHIFT 24 + +#if defined(SK_PMCOLOR_IS_RGBA) || defined(SK_PMCOLOR_IS_BGRA) + #error "Configure PMCOLOR by setting SK_R32_SHIFT." +#endif + +// Deduce which SK_PMCOLOR_IS_ to define from the _SHIFT defines + +#if (SK_A32_SHIFT == SK_RGBA_A32_SHIFT && \ + SK_R32_SHIFT == SK_RGBA_R32_SHIFT && \ + SK_G32_SHIFT == SK_RGBA_G32_SHIFT && \ + SK_B32_SHIFT == SK_RGBA_B32_SHIFT) + #define SK_PMCOLOR_IS_RGBA +#elif (SK_A32_SHIFT == SK_BGRA_A32_SHIFT && \ + SK_R32_SHIFT == SK_BGRA_R32_SHIFT && \ + SK_G32_SHIFT == SK_BGRA_G32_SHIFT && \ + SK_B32_SHIFT == SK_BGRA_B32_SHIFT) + #define SK_PMCOLOR_IS_BGRA +#else + #error "need 32bit packing to be either RGBA or BGRA" +#endif + +#define SkGetPackedA32(packed) ((uint32_t)((packed) << (24 - SK_A32_SHIFT)) >> 24) +#define SkGetPackedR32(packed) ((uint32_t)((packed) << (24 - SK_R32_SHIFT)) >> 24) +#define SkGetPackedG32(packed) ((uint32_t)((packed) << (24 - SK_G32_SHIFT)) >> 24) +#define SkGetPackedB32(packed) ((uint32_t)((packed) << (24 - SK_B32_SHIFT)) >> 24) + +#define SkA32Assert(a) SkASSERT((unsigned)(a) <= SK_A32_MASK) +#define SkR32Assert(r) SkASSERT((unsigned)(r) <= SK_R32_MASK) +#define SkG32Assert(g) SkASSERT((unsigned)(g) <= SK_G32_MASK) +#define SkB32Assert(b) SkASSERT((unsigned)(b) <= SK_B32_MASK) + +/** + * Pack the components into a SkPMColor, checking (in the debug version) that + * the components are 0..255, and are already premultiplied (i.e. alpha >= color) + */ +static inline SkPMColor SkPackARGB32(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + SkA32Assert(a); + SkASSERT(r <= a); + SkASSERT(g <= a); + SkASSERT(b <= a); + + return (a << SK_A32_SHIFT) | (r << SK_R32_SHIFT) | + (g << SK_G32_SHIFT) | (b << SK_B32_SHIFT); +} + +/** + * Same as SkPackARGB32, but this version guarantees to not check that the + * values are premultiplied in the debug version. + */ +static inline SkPMColor SkPackARGB32NoCheck(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + return (a << SK_A32_SHIFT) | (r << SK_R32_SHIFT) | + (g << SK_G32_SHIFT) | (b << SK_B32_SHIFT); +} + +static inline +SkPMColor SkPremultiplyARGBInline(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + SkA32Assert(a); + SkR32Assert(r); + SkG32Assert(g); + SkB32Assert(b); + + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + return SkPackARGB32(a, r, g, b); +} + +// When Android is compiled optimizing for size, SkAlphaMulQ doesn't get +// inlined; forcing inlining significantly improves performance. +static SK_ALWAYS_INLINE uint32_t SkAlphaMulQ(uint32_t c, unsigned scale) { + uint32_t mask = 0xFF00FF; + + uint32_t rb = ((c & mask) * scale) >> 8; + uint32_t ag = ((c >> 8) & mask) * scale; + return (rb & mask) | (ag & ~mask); +} + +static inline SkPMColor SkPMSrcOver(SkPMColor src, SkPMColor dst) { + return src + SkAlphaMulQ(dst, SkAlpha255To256(255 - SkGetPackedA32(src))); +} + +#endif diff --git a/gfx/skia/skia/include/core/SkColorSpace.h b/gfx/skia/skia/include/core/SkColorSpace.h new file mode 100644 index 0000000000..9e52e85cc6 --- /dev/null +++ b/gfx/skia/skia/include/core/SkColorSpace.h @@ -0,0 +1,249 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorSpace_DEFINED +#define SkColorSpace_DEFINED + +#include "include/core/SkMatrix44.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkFixed.h" +#include "include/private/SkOnce.h" +#include "include/third_party/skcms/skcms.h" +#include + +class SkData; + +/** + * Describes a color gamut with primaries and a white point. + */ +struct SK_API SkColorSpacePrimaries { + float fRX; + float fRY; + float fGX; + float fGY; + float fBX; + float fBY; + float fWX; + float fWY; + + /** + * Convert primaries and a white point to a toXYZD50 matrix, the preferred color gamut + * representation of SkColorSpace. + */ + bool toXYZD50(skcms_Matrix3x3* toXYZD50) const; +}; + +namespace SkNamedTransferFn { + +// Like SkNamedGamut::kSRGB, keeping this bitwise exactly the same as skcms makes things fastest. +static constexpr skcms_TransferFunction kSRGB = + { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f }; + +static constexpr skcms_TransferFunction k2Dot2 = + { 2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + +static constexpr skcms_TransferFunction kLinear = + { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + +static constexpr skcms_TransferFunction kRec2020 = + {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0}; + +static constexpr skcms_TransferFunction kPQ = + {-2.0f, -107/128.0f, 1.0f, 32/2523.0f, 2413/128.0f, -2392/128.0f, 8192/1305.0f }; + +static constexpr skcms_TransferFunction kHLG = + {-3.0f, 2.0f, 2.0f, 1/0.17883277f, 0.28466892f, 0.55991073f, 0.0f }; + +} + +namespace SkNamedGamut { + +static constexpr skcms_Matrix3x3 kSRGB = {{ + // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync. + // 0.436065674f, 0.385147095f, 0.143066406f, + // 0.222488403f, 0.716873169f, 0.060607910f, + // 0.013916016f, 0.097076416f, 0.714096069f, + { SkFixedToFloat(0x6FA2), SkFixedToFloat(0x6299), SkFixedToFloat(0x24A0) }, + { SkFixedToFloat(0x38F5), SkFixedToFloat(0xB785), SkFixedToFloat(0x0F84) }, + { SkFixedToFloat(0x0390), SkFixedToFloat(0x18DA), SkFixedToFloat(0xB6CF) }, +}}; + +static constexpr skcms_Matrix3x3 kAdobeRGB = {{ + // ICC fixed-point (16.16) repesentation of: + // 0.60974, 0.20528, 0.14919, + // 0.31111, 0.62567, 0.06322, + // 0.01947, 0.06087, 0.74457, + { SkFixedToFloat(0x9c18), SkFixedToFloat(0x348d), SkFixedToFloat(0x2631) }, + { SkFixedToFloat(0x4fa5), SkFixedToFloat(0xa02c), SkFixedToFloat(0x102f) }, + { SkFixedToFloat(0x04fc), SkFixedToFloat(0x0f95), SkFixedToFloat(0xbe9c) }, +}}; + +static constexpr skcms_Matrix3x3 kDCIP3 = {{ + { 0.515102f, 0.291965f, 0.157153f }, + { 0.241182f, 0.692236f, 0.0665819f }, + { -0.00104941f, 0.0418818f, 0.784378f }, +}}; + +static constexpr skcms_Matrix3x3 kRec2020 = {{ + { 0.673459f, 0.165661f, 0.125100f }, + { 0.279033f, 0.675338f, 0.0456288f }, + { -0.00193139f, 0.0299794f, 0.797162f }, +}}; + +static constexpr skcms_Matrix3x3 kXYZ = {{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, +}}; + +} + +class SK_API SkColorSpace : public SkNVRefCnt { +public: + /** + * Create the sRGB color space. + */ + static sk_sp MakeSRGB(); + + /** + * Colorspace with the sRGB primaries, but a linear (1.0) gamma. + */ + static sk_sp MakeSRGBLinear(); + + /** + * Create an SkColorSpace from a transfer function and a row-major 3x3 transformation to XYZ. + */ + static sk_sp MakeRGB(const skcms_TransferFunction& transferFn, + const skcms_Matrix3x3& toXYZ); + + /** + * Create an SkColorSpace from a parsed (skcms) ICC profile. + */ + static sk_sp Make(const skcms_ICCProfile&); + + /** + * Convert this color space to an skcms ICC profile struct. + */ + void toProfile(skcms_ICCProfile*) const; + + /** + * Returns true if the color space gamma is near enough to be approximated as sRGB. + */ + bool gammaCloseToSRGB() const; + + /** + * Returns true if the color space gamma is linear. + */ + bool gammaIsLinear() const; + + /** + * If the transfer function can be represented as coefficients to the standard + * equation, returns true and sets |fn| to the proper values. + * + * If not, returns false. + */ + bool isNumericalTransferFn(skcms_TransferFunction* fn) const; + + /** + * Returns true and sets |toXYZD50| if the color gamut can be described as a matrix. + * Returns false otherwise. + */ + bool toXYZD50(SkMatrix44* toXYZD50) const; + + bool toXYZD50(skcms_Matrix3x3* toXYZD50) const; + + /** + * Returns a hash of the gamut transformation to XYZ D50. Allows for fast equality checking + * of gamuts, at the (very small) risk of collision. + */ + uint32_t toXYZD50Hash() const { return fToXYZD50Hash; } + + /** + * Returns a color space with the same gamut as this one, but with a linear gamma. + * For color spaces whose gamut can not be described in terms of XYZ D50, returns + * linear sRGB. + */ + sk_sp makeLinearGamma() const; + + /** + * Returns a color space with the same gamut as this one, with with the sRGB transfer + * function. For color spaces whose gamut can not be described in terms of XYZ D50, returns + * sRGB. + */ + sk_sp makeSRGBGamma() const; + + /** + * Returns a color space with the same transfer function as this one, but with the primary + * colors rotated. For any XYZ space, this produces a new color space that maps RGB to GBR + * (when applied to a source), and maps RGB to BRG (when applied to a destination). For other + * types of color spaces, returns nullptr. + * + * This is used for testing, to construct color spaces that have severe and testable behavior. + */ + sk_sp makeColorSpin() const; + + /** + * Returns true if the color space is sRGB. + * Returns false otherwise. + * + * This allows a little bit of tolerance, given that we might see small numerical error + * in some cases: converting ICC fixed point to float, converting white point to D50, + * rounding decisions on transfer function and matrix. + * + * This does not consider a 2.2f exponential transfer function to be sRGB. While these + * functions are similar (and it is sometimes useful to consider them together), this + * function checks for logical equality. + */ + bool isSRGB() const; + + /** + * Returns nullptr on failure. Fails when we fallback to serializing ICC data and + * the data is too large to serialize. + */ + sk_sp serialize() const; + + /** + * If |memory| is nullptr, returns the size required to serialize. + * Otherwise, serializes into |memory| and returns the size. + */ + size_t writeToMemory(void* memory) const; + + static sk_sp Deserialize(const void* data, size_t length); + + /** + * If both are null, we return true. If one is null and the other is not, we return false. + * If both are non-null, we do a deeper compare. + */ + static bool Equals(const SkColorSpace*, const SkColorSpace*); + + void transferFn(float gabcdef[7]) const; + void invTransferFn(float gabcdef[7]) const; + void gamutTransformTo(const SkColorSpace* dst, float src_to_dst_row_major[9]) const; + + uint32_t transferFnHash() const { return fTransferFnHash; } + uint64_t hash() const { return (uint64_t)fTransferFnHash << 32 | fToXYZD50Hash; } + +private: + friend class SkColorSpaceSingletonFactory; + + SkColorSpace(const float transferFn[7], + const skcms_Matrix3x3& toXYZ); + + void computeLazyDstFields() const; + + uint32_t fTransferFnHash; + uint32_t fToXYZD50Hash; + + float fTransferFn[7]; + float fToXYZD50_3x3[9]; // row-major + + mutable float fInvTransferFn[7]; + mutable float fFromXYZD50_3x3[9]; // row-major + mutable SkOnce fLazyDstFieldsOnce; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkContourMeasure.h b/gfx/skia/skia/include/core/SkContourMeasure.h new file mode 100644 index 0000000000..dc06122cf0 --- /dev/null +++ b/gfx/skia/skia/include/core/SkContourMeasure.h @@ -0,0 +1,148 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkContourMeasure_DEFINED +#define SkContourMeasure_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkTDArray.h" + +struct SkConic; + +class SK_API SkContourMeasure : public SkRefCnt { +public: + /** Return the length of the contour. + */ + SkScalar length() const { return fLength; } + + /** Pins distance to 0 <= distance <= length(), and then computes the corresponding + * position and tangent. + */ + bool SK_WARN_UNUSED_RESULT getPosTan(SkScalar distance, SkPoint* position, + SkVector* tangent) const; + + enum MatrixFlags { + kGetPosition_MatrixFlag = 0x01, + kGetTangent_MatrixFlag = 0x02, + kGetPosAndTan_MatrixFlag = kGetPosition_MatrixFlag | kGetTangent_MatrixFlag + }; + + /** Pins distance to 0 <= distance <= getLength(), and then computes + the corresponding matrix (by calling getPosTan). + Returns false if there is no path, or a zero-length path was specified, in which case + matrix is unchanged. + */ + bool SK_WARN_UNUSED_RESULT getMatrix(SkScalar distance, SkMatrix* matrix, + MatrixFlags flags = kGetPosAndTan_MatrixFlag) const; + + /** Given a start and stop distance, return in dst the intervening segment(s). + If the segment is zero-length, return false, else return true. + startD and stopD are pinned to legal values (0..getLength()). If startD > stopD + then return false (and leave dst untouched). + Begin the segment with a moveTo if startWithMoveTo is true + */ + bool SK_WARN_UNUSED_RESULT getSegment(SkScalar startD, SkScalar stopD, SkPath* dst, + bool startWithMoveTo) const; + + /** Return true if the contour is closed() + */ + bool isClosed() const { return fIsClosed; } + +private: + struct Segment { + SkScalar fDistance; // total distance up to this point + unsigned fPtIndex; // index into the fPts array + unsigned fTValue : 30; + unsigned fType : 2; // actually the enum SkSegType + // See SkPathMeasurePriv.h + + SkScalar getScalarT() const; + + static const Segment* Next(const Segment* seg) { + unsigned ptIndex = seg->fPtIndex; + do { + ++seg; + } while (seg->fPtIndex == ptIndex); + return seg; + } + + }; + + const SkTDArray fSegments; + const SkTDArray fPts; // Points used to define the segments + + const SkScalar fLength; + const bool fIsClosed; + + SkContourMeasure(SkTDArray&& segs, SkTDArray&& pts, + SkScalar length, bool isClosed); + ~SkContourMeasure() override {} + + const Segment* distanceToSegment(SkScalar distance, SkScalar* t) const; + + friend class SkContourMeasureIter; +}; + +class SK_API SkContourMeasureIter { +public: + SkContourMeasureIter(); + /** + * Initialize the Iter with a path. + * The parts of the path that are needed are copied, so the client is free to modify/delete + * the path after this call. + */ + SkContourMeasureIter(const SkPath& path, bool forceClosed, SkScalar resScale = 1); + ~SkContourMeasureIter(); + + /** + * Reset the Iter with a path. + * The parts of the path that are needed are copied, so the client is free to modify/delete + * the path after this call. + */ + void reset(const SkPath& path, bool forceClosed, SkScalar resScale = 1); + + /** + * Iterates through contours in path, returning a contour-measure object for each contour + * in the path. Returns null when it is done. + * + * This only returns non-zero length contours, where a contour is the segments between + * a kMove_Verb and either ... + * - the next kMove_Verb + * - kClose_Verb (1 or more) + * - kDone_Verb + * If it encounters a zero-length contour, it is skipped. + */ + sk_sp next(); + +private: + SkPath::RawIter fIter; + SkPath fPath; + SkScalar fTolerance; + bool fForceClosed; + + // temporary + SkTDArray fSegments; + SkTDArray fPts; // Points used to define the segments + + SkContourMeasure* buildSegments(); + + SkScalar compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance, unsigned ptIndex); + SkScalar compute_quad_segs(const SkPoint pts[3], SkScalar distance, + int mint, int maxt, unsigned ptIndex); + SkScalar compute_conic_segs(const SkConic& conic, SkScalar distance, + int mint, const SkPoint& minPt, + int maxt, const SkPoint& maxPt, + unsigned ptIndex); + SkScalar compute_cubic_segs(const SkPoint pts[4], SkScalar distance, + int mint, int maxt, unsigned ptIndex); + + SkContourMeasureIter(const SkContourMeasureIter&) = delete; + SkContourMeasureIter& operator=(const SkContourMeasureIter&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkCoverageMode.h b/gfx/skia/skia/include/core/SkCoverageMode.h new file mode 100644 index 0000000000..ea5b73d1a4 --- /dev/null +++ b/gfx/skia/skia/include/core/SkCoverageMode.h @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCoverageMode_DEFINED +#define SkCoverageMode_DEFINED + +#include "include/core/SkTypes.h" + +/** + * Describes geometric operations (ala SkRegion::Op) that can be applied to coverage bytes. + * These can be thought of as variants of porter-duff (SkBlendMode) modes, but only applied + * to the alpha channel. + * + * See SkMaskFilter for ways to use these when combining two different masks. + */ +enum class SkCoverageMode { + kUnion, // A ∪ B A+B-A*B + kIntersect, // A ∩ B A*B + kDifference, // A - B A*(1-B) + kReverseDifference, // B - A B*(1-A) + kXor, // A ⊕ B A+B-2*A*B + + kLast = kXor, +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkCubicMap.h b/gfx/skia/skia/include/core/SkCubicMap.h new file mode 100644 index 0000000000..7389b92afd --- /dev/null +++ b/gfx/skia/skia/include/core/SkCubicMap.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCubicMap_DEFINED +#define SkCubicMap_DEFINED + +#include "include/core/SkPoint.h" + +/** + * Fast evaluation of a cubic ease-in / ease-out curve. This is defined as a parametric cubic + * curve inside the unit square. + * + * pt[0] is implicitly { 0, 0 } + * pt[3] is implicitly { 1, 1 } + * pts[1,2].X are inside the unit [0..1] + */ +class SK_API SkCubicMap { +public: + SkCubicMap(SkPoint p1, SkPoint p2); + + static bool IsLinear(SkPoint p1, SkPoint p2) { + return SkScalarNearlyEqual(p1.fX, p1.fY) && SkScalarNearlyEqual(p2.fX, p2.fY); + } + + float computeYFromX(float x) const; + + SkPoint computeFromT(float t) const; + +private: + enum Type { + kLine_Type, // x == y + kCubeRoot_Type, // At^3 == x + kSolver_Type, // general monotonic cubic solver + }; + + SkPoint fCoeff[3]; + Type fType; +}; + +#endif + diff --git a/gfx/skia/skia/include/core/SkData.h b/gfx/skia/skia/include/core/SkData.h new file mode 100644 index 0000000000..abf4eea75b --- /dev/null +++ b/gfx/skia/skia/include/core/SkData.h @@ -0,0 +1,182 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkData_DEFINED +#define SkData_DEFINED + +#include + +#include "include/core/SkRefCnt.h" + +class SkStream; + +/** + * SkData holds an immutable data buffer. Not only is the data immutable, + * but the actual ptr that is returned (by data() or bytes()) is guaranteed + * to always be the same for the life of this instance. + */ +class SK_API SkData final : public SkNVRefCnt { +public: + /** + * Returns the number of bytes stored. + */ + size_t size() const { return fSize; } + + bool isEmpty() const { return 0 == fSize; } + + /** + * Returns the ptr to the data. + */ + const void* data() const { return fPtr; } + + /** + * Like data(), returns a read-only ptr into the data, but in this case + * it is cast to uint8_t*, to make it easy to add an offset to it. + */ + const uint8_t* bytes() const { + return reinterpret_cast(fPtr); + } + + /** + * USE WITH CAUTION. + * This call will assert that the refcnt is 1, as a precaution against modifying the + * contents when another client/thread has access to the data. + */ + void* writable_data() { + if (fSize) { + // only assert we're unique if we're not empty + SkASSERT(this->unique()); + } + return fPtr; + } + + /** + * Helper to copy a range of the data into a caller-provided buffer. + * Returns the actual number of bytes copied, after clamping offset and + * length to the size of the data. If buffer is NULL, it is ignored, and + * only the computed number of bytes is returned. + */ + size_t copyRange(size_t offset, size_t length, void* buffer) const; + + /** + * Returns true if these two objects have the same length and contents, + * effectively returning 0 == memcmp(...) + */ + bool equals(const SkData* other) const; + + /** + * Function that, if provided, will be called when the SkData goes out + * of scope, allowing for custom allocation/freeing of the data's contents. + */ + typedef void (*ReleaseProc)(const void* ptr, void* context); + + /** + * Create a new dataref by copying the specified data + */ + static sk_sp MakeWithCopy(const void* data, size_t length); + + + /** + * Create a new data with uninitialized contents. The caller should call writable_data() + * to write into the buffer, but this must be done before another ref() is made. + */ + static sk_sp MakeUninitialized(size_t length); + + /** + * Create a new dataref by copying the specified c-string + * (a null-terminated array of bytes). The returned SkData will have size() + * equal to strlen(cstr) + 1. If cstr is NULL, it will be treated the same + * as "". + */ + static sk_sp MakeWithCString(const char cstr[]); + + /** + * Create a new dataref, taking the ptr as is, and using the + * releaseproc to free it. The proc may be NULL. + */ + static sk_sp MakeWithProc(const void* ptr, size_t length, ReleaseProc proc, void* ctx); + + /** + * Call this when the data parameter is already const and will outlive the lifetime of the + * SkData. Suitable for with const globals. + */ + static sk_sp MakeWithoutCopy(const void* data, size_t length) { + return MakeWithProc(data, length, DummyReleaseProc, nullptr); + } + + /** + * Create a new dataref from a pointer allocated by malloc. The Data object + * takes ownership of that allocation, and will handling calling sk_free. + */ + static sk_sp MakeFromMalloc(const void* data, size_t length); + + /** + * Create a new dataref the file with the specified path. + * If the file cannot be opened, this returns NULL. + */ + static sk_sp MakeFromFileName(const char path[]); + + /** + * Create a new dataref from a stdio FILE. + * This does not take ownership of the FILE, nor close it. + * The caller is free to close the FILE at its convenience. + * The FILE must be open for reading only. + * Returns NULL on failure. + */ + static sk_sp MakeFromFILE(FILE* f); + + /** + * Create a new dataref from a file descriptor. + * This does not take ownership of the file descriptor, nor close it. + * The caller is free to close the file descriptor at its convenience. + * The file descriptor must be open for reading only. + * Returns NULL on failure. + */ + static sk_sp MakeFromFD(int fd); + + /** + * Attempt to read size bytes into a SkData. If the read succeeds, return the data, + * else return NULL. Either way the stream's cursor may have been changed as a result + * of calling read(). + */ + static sk_sp MakeFromStream(SkStream*, size_t size); + + /** + * Create a new dataref using a subset of the data in the specified + * src dataref. + */ + static sk_sp MakeSubset(const SkData* src, size_t offset, size_t length); + + /** + * Returns a new empty dataref (or a reference to a shared empty dataref). + * New or shared, the caller must see that unref() is eventually called. + */ + static sk_sp MakeEmpty(); + +private: + friend class SkNVRefCnt; + ReleaseProc fReleaseProc; + void* fReleaseProcContext; + void* fPtr; + size_t fSize; + + SkData(const void* ptr, size_t size, ReleaseProc, void* context); + explicit SkData(size_t size); // inplace new/delete + ~SkData(); + + // Ensure the unsized delete is called. + void operator delete(void* p); + + // shared internal factory + static sk_sp PrivateNewWithCopy(const void* srcOrNull, size_t length); + + static void DummyReleaseProc(const void*, void*); // {} + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkDataTable.h b/gfx/skia/skia/include/core/SkDataTable.h new file mode 100644 index 0000000000..74e7d0ef29 --- /dev/null +++ b/gfx/skia/skia/include/core/SkDataTable.h @@ -0,0 +1,119 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDataTable_DEFINED +#define SkDataTable_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkString.h" +#include "include/private/SkTDArray.h" + +/** + * Like SkData, SkDataTable holds an immutable data buffer. The data buffer is + * organized into a table of entries, each with a length, so the entries are + * not required to all be the same size. + */ +class SK_API SkDataTable : public SkRefCnt { +public: + /** + * Returns true if the table is empty (i.e. has no entries). + */ + bool isEmpty() const { return 0 == fCount; } + + /** + * Return the number of entries in the table. 0 for an empty table + */ + int count() const { return fCount; } + + /** + * Return the size of the index'th entry in the table. The caller must + * ensure that index is valid for this table. + */ + size_t atSize(int index) const; + + /** + * Return a pointer to the data of the index'th entry in the table. + * The caller must ensure that index is valid for this table. + * + * @param size If non-null, this returns the byte size of this entry. This + * will be the same value that atSize(index) would return. + */ + const void* at(int index, size_t* size = nullptr) const; + + template + const T* atT(int index, size_t* size = nullptr) const { + return reinterpret_cast(this->at(index, size)); + } + + /** + * Returns the index'th entry as a c-string, and assumes that the trailing + * null byte had been copied into the table as well. + */ + const char* atStr(int index) const { + size_t size; + const char* str = this->atT(index, &size); + SkASSERT(strlen(str) + 1 == size); + return str; + } + + typedef void (*FreeProc)(void* context); + + static sk_sp MakeEmpty(); + + /** + * Return a new DataTable that contains a copy of the data stored in each + * "array". + * + * @param ptrs array of points to each element to be copied into the table. + * @param sizes array of byte-lengths for each entry in the corresponding + * ptrs[] array. + * @param count the number of array elements in ptrs[] and sizes[] to copy. + */ + static sk_sp MakeCopyArrays(const void * const * ptrs, + const size_t sizes[], int count); + + /** + * Return a new table that contains a copy of the data in array. + * + * @param array contiguous array of data for all elements to be copied. + * @param elemSize byte-length for a given element. + * @param count the number of entries to be copied out of array. The number + * of bytes that will be copied is count * elemSize. + */ + static sk_sp MakeCopyArray(const void* array, size_t elemSize, int count); + + static sk_sp MakeArrayProc(const void* array, size_t elemSize, int count, + FreeProc proc, void* context); + +private: + struct Dir { + const void* fPtr; + uintptr_t fSize; + }; + + int fCount; + size_t fElemSize; + union { + const Dir* fDir; + const char* fElems; + } fU; + + FreeProc fFreeProc; + void* fFreeProcContext; + + SkDataTable(); + SkDataTable(const void* array, size_t elemSize, int count, + FreeProc, void* context); + SkDataTable(const Dir*, int count, FreeProc, void* context); + virtual ~SkDataTable(); + + friend class SkDataTableBuilder; // access to Dir + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkDeferredDisplayListRecorder.h b/gfx/skia/skia/include/core/SkDeferredDisplayListRecorder.h new file mode 100644 index 0000000000..d5e4c0a0e9 --- /dev/null +++ b/gfx/skia/skia/include/core/SkDeferredDisplayListRecorder.h @@ -0,0 +1,173 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDeferredDisplayListRecorder_DEFINED +#define SkDeferredDisplayListRecorder_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceCharacterization.h" +#include "include/core/SkTypes.h" +#include "include/private/SkDeferredDisplayList.h" + +class GrBackendFormat; +class GrBackendTexture; +class GrContext; +class SkCanvas; +class SkImage; +class SkPromiseImageTexture; +class SkSurface; +struct SkYUVAIndex; +struct SkYUVASizeInfo; + +/* + * This class is intended to be used as: + * Get an SkSurfaceCharacterization representing the intended gpu-backed destination SkSurface + * Create one of these (an SkDDLMaker) on the stack + * Get the canvas and render into it + * Snap off and hold on to an SkDeferredDisplayList + * Once your app actually needs the pixels, call SkSurface::draw(SkDeferredDisplayList*) + * + * This class never accesses the GPU but performs all the cpu work it can. It + * is thread-safe (i.e., one can break a scene into tiles and perform their cpu-side + * work in parallel ahead of time). + */ +class SK_API SkDeferredDisplayListRecorder { +public: + SkDeferredDisplayListRecorder(const SkSurfaceCharacterization&); + ~SkDeferredDisplayListRecorder(); + + const SkSurfaceCharacterization& characterization() const { + return fCharacterization; + } + + // The backing canvas will become invalid (and this entry point will return + // null) once 'detach' is called. + // Note: ownership of the SkCanvas is not transferred via this call. + SkCanvas* getCanvas(); + + std::unique_ptr detach(); + + using PromiseImageTextureContext = void*; + using PromiseImageTextureFulfillProc = + sk_sp (*)(PromiseImageTextureContext); + using PromiseImageTextureReleaseProc = void (*)(PromiseImageTextureContext); + using PromiseImageTextureDoneProc = void (*)(PromiseImageTextureContext); + + enum class PromiseImageApiVersion { kLegacy, kNew }; + + /** + Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The + difference is that the caller need not have created the texture nor populated it with the + image pixel data. Moreover, the SkImage may be created on a thread as the creation of the + image does not require access to the backend API or GrContext. Instead of passing a + GrBackendTexture the client supplies a description of the texture consisting of + GrBackendFormat, width, height, and GrMipMapped state. The resulting SkImage can be drawn + to a SkDeferredDisplayListRecorder or directly to a GPU-backed SkSurface. + + When the actual texture is required to perform a backend API draw, textureFulfillProc will + be called to receive a GrBackendTexture. The properties of the GrBackendTexture must match + those set during the SkImage creation, and it must refer to a valid existing texture in the + backend API context/device, and be populated with the image pixel data. The texture contents + cannot be modified until textureReleaseProc is called. The texture cannot be deleted until + textureDoneProc is called. + + When all the following are true: + * the promise SkImage is deleted, + * any SkDeferredDisplayLists that recorded draws referencing the image are deleted, + * and all draws referencing the texture have been flushed (via GrContext::flush or + SkSurface::flush) + the textureReleaseProc is called. When the following additional constraint is met + * the texture is safe to delete in the underlying API + the textureDoneProc is called. For some APIs (e.g. GL) the two states are equivalent. + However, for others (e.g. Vulkan) they are not as it is not legal to delete a texture until + the GPU work referencing it has completed. + + There is at most one call to each of textureFulfillProc, textureReleaseProc, and + textureDoneProc. textureDoneProc is always called even if image creation fails or if the + image is never fulfilled (e.g. it is never drawn or all draws are clipped out). If + textureFulfillProc is called then textureReleaseProc will always be called even if + textureFulfillProc failed. + + If 'version' is set to kLegacy then the textureReleaseProc call is delayed until the + conditions for textureDoneProc are met and then they are both called. + + This call is only valid if the SkDeferredDisplayListRecorder is backed by a GPU context. + + @param backendFormat format of promised gpu texture + @param width width of promised gpu texture + @param height height of promised gpu texture + @param mipMapped mip mapped state of promised gpu texture + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: kUnknown_SkColorType, kAlpha_8_SkColorType, + kRGB_565_SkColorType, kARGB_4444_SkColorType, + kRGBA_8888_SkColorType, kBGRA_8888_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param alphaType one of: kUnknown_SkAlphaType, kOpaque_SkAlphaType, + kPremul_SkAlphaType, kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @param textureFulfillProc function called to get actual gpu texture + @param textureReleaseProc function called when texture can be released + @param textureDoneProc function called when we will no longer call textureFulfillProc + @param textureContext state passed to textureFulfillProc and textureReleaseProc + @param version controls when textureReleaseProc is called + @return created SkImage, or nullptr + */ + sk_sp makePromiseTexture( + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContext, + PromiseImageApiVersion version = PromiseImageApiVersion::kLegacy); + + /** + This entry point operates like 'makePromiseTexture' but it is used to construct a SkImage + from YUV[A] data. The source data may be planar (i.e. spread across multiple textures). In + the extreme Y, U, V, and A are all in different planes and thus the image is specified by + four textures. 'yuvaIndices' specifies the mapping from texture color channels to Y, U, V, + and possibly A components. It therefore indicates how many unique textures compose the full + image. Separate textureFulfillProc, textureReleaseProc, and textureDoneProc calls are made + for each texture and each texture has its own PromiseImageTextureContext. 'yuvFormats', + 'yuvaSizes', and 'textureContexts' have one entry for each of the up to four textures, as + indicated by 'yuvaIndices'. + */ + sk_sp makeYUVAPromiseTexture( + SkYUVColorSpace yuvColorSpace, + const GrBackendFormat yuvaFormats[], + const SkISize yuvaSizes[], + const SkYUVAIndex yuvaIndices[4], + int imageWidth, + int imageHeight, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContexts[], + PromiseImageApiVersion version = PromiseImageApiVersion::kLegacy); + +private: + bool init(); + + const SkSurfaceCharacterization fCharacterization; + +#if SK_SUPPORT_GPU + sk_sp fContext; + sk_sp fLazyProxyData; + sk_sp fSurface; +#endif +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkDeque.h b/gfx/skia/skia/include/core/SkDeque.h new file mode 100644 index 0000000000..199d47b60e --- /dev/null +++ b/gfx/skia/skia/include/core/SkDeque.h @@ -0,0 +1,141 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkDeque_DEFINED +#define SkDeque_DEFINED + +#include "include/core/SkTypes.h" + +/* + * The deque class works by blindly creating memory space of a specified element + * size. It manages the memory as a doubly linked list of blocks each of which + * can contain multiple elements. Pushes and pops add/remove blocks from the + * beginning/end of the list as necessary while each block tracks the used + * portion of its memory. + * One behavior to be aware of is that the pops do not immediately remove an + * empty block from the beginning/end of the list (Presumably so push/pop pairs + * on the block boundaries don't cause thrashing). This can result in the first/ + * last element not residing in the first/last block. + */ +class SK_API SkDeque { +public: + /** + * elemSize specifies the size of each individual element in the deque + * allocCount specifies how many elements are to be allocated as a block + */ + explicit SkDeque(size_t elemSize, int allocCount = 1); + SkDeque(size_t elemSize, void* storage, size_t storageSize, int allocCount = 1); + ~SkDeque(); + + bool empty() const { return 0 == fCount; } + int count() const { return fCount; } + size_t elemSize() const { return fElemSize; } + + const void* front() const { return fFront; } + const void* back() const { return fBack; } + + void* front() { + return (void*)((const SkDeque*)this)->front(); + } + + void* back() { + return (void*)((const SkDeque*)this)->back(); + } + + /** + * push_front and push_back return a pointer to the memory space + * for the new element + */ + void* push_front(); + void* push_back(); + + void pop_front(); + void pop_back(); + +private: + struct Block; + +public: + class Iter { + public: + enum IterStart { + kFront_IterStart, + kBack_IterStart, + }; + + /** + * Creates an uninitialized iterator. Must be reset() + */ + Iter(); + + Iter(const SkDeque& d, IterStart startLoc); + void* next(); + void* prev(); + + void reset(const SkDeque& d, IterStart startLoc); + + private: + SkDeque::Block* fCurBlock; + char* fPos; + size_t fElemSize; + }; + + // Inherit privately from Iter to prevent access to reverse iteration + class F2BIter : private Iter { + public: + F2BIter() {} + + /** + * Wrap Iter's 2 parameter ctor to force initialization to the + * beginning of the deque + */ + F2BIter(const SkDeque& d) : INHERITED(d, kFront_IterStart) {} + + using Iter::next; + + /** + * Wrap Iter::reset to force initialization to the beginning of the + * deque + */ + void reset(const SkDeque& d) { + this->INHERITED::reset(d, kFront_IterStart); + } + + private: + typedef Iter INHERITED; + }; + +private: + // allow unit test to call numBlocksAllocated + friend class DequeUnitTestHelper; + + void* fFront; + void* fBack; + + Block* fFrontBlock; + Block* fBackBlock; + size_t fElemSize; + void* fInitialStorage; + int fCount; // number of elements in the deque + int fAllocCount; // number of elements to allocate per block + + Block* allocateBlock(int allocCount); + void freeBlock(Block* block); + + /** + * This returns the number of chunk blocks allocated by the deque. It + * can be used to gauge the effectiveness of the selected allocCount. + */ + int numBlocksAllocated() const; + + SkDeque(const SkDeque&) = delete; + SkDeque& operator=(const SkDeque&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkDocument.h b/gfx/skia/skia/include/core/SkDocument.h new file mode 100644 index 0000000000..0548711c14 --- /dev/null +++ b/gfx/skia/skia/include/core/SkDocument.h @@ -0,0 +1,91 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDocument_DEFINED +#define SkDocument_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" + +class SkCanvas; +class SkWStream; +struct SkRect; + +/** SK_ScalarDefaultDPI is 72 dots per inch. */ +static constexpr SkScalar SK_ScalarDefaultRasterDPI = 72.0f; + +/** + * High-level API for creating a document-based canvas. To use.. + * + * 1. Create a document, specifying a stream to store the output. + * 2. For each "page" of content: + * a. canvas = doc->beginPage(...) + * b. draw_my_content(canvas); + * c. doc->endPage(); + * 3. Close the document with doc->close(). + */ +class SK_API SkDocument : public SkRefCnt { +public: + + /** + * Begin a new page for the document, returning the canvas that will draw + * into the page. The document owns this canvas, and it will go out of + * scope when endPage() or close() is called, or the document is deleted. + */ + SkCanvas* beginPage(SkScalar width, SkScalar height, const SkRect* content = nullptr); + + /** + * Call endPage() when the content for the current page has been drawn + * (into the canvas returned by beginPage()). After this call the canvas + * returned by beginPage() will be out-of-scope. + */ + void endPage(); + + /** + * Call close() when all pages have been drawn. This will close the file + * or stream holding the document's contents. After close() the document + * can no longer add new pages. Deleting the document will automatically + * call close() if need be. + */ + void close(); + + /** + * Call abort() to stop producing the document immediately. + * The stream output must be ignored, and should not be trusted. + */ + void abort(); + +protected: + SkDocument(SkWStream*); + + // note: subclasses must call close() in their destructor, as the base class + // cannot do this for them. + virtual ~SkDocument(); + + virtual SkCanvas* onBeginPage(SkScalar width, SkScalar height) = 0; + virtual void onEndPage() = 0; + virtual void onClose(SkWStream*) = 0; + virtual void onAbort() = 0; + + // Allows subclasses to write to the stream as pages are written. + SkWStream* getStream() { return fStream; } + + enum State { + kBetweenPages_State, + kInPage_State, + kClosed_State + }; + State getState() const { return fState; } + +private: + SkWStream* fStream; + State fState; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkDrawLooper.h b/gfx/skia/skia/include/core/SkDrawLooper.h new file mode 100644 index 0000000000..ef06c8f7b8 --- /dev/null +++ b/gfx/skia/skia/include/core/SkDrawLooper.h @@ -0,0 +1,136 @@ + +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkDrawLooper_DEFINED +#define SkDrawLooper_DEFINED + +#include "include/core/SkBlurTypes.h" +#include "include/core/SkColor.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkPoint.h" +#include // std::function + +class SkArenaAlloc; +class SkCanvas; +class SkPaint; +struct SkRect; +class SkString; + +/** \class SkDrawLooper + Subclasses of SkDrawLooper can be attached to a SkPaint. Where they are, + and something is drawn to a canvas with that paint, the looper subclass will + be called, allowing it to modify the canvas and/or paint for that draw call. + More than that, via the next() method, the looper can modify the draw to be + invoked multiple times (hence the name loop-er), allow it to perform effects + like shadows or frame/fills, that require more than one pass. +*/ +class SK_API SkDrawLooper : public SkFlattenable { +public: + /** + * Holds state during a draw. Users call next() until it returns false. + * + * Subclasses of SkDrawLooper should create a subclass of this object to + * hold state specific to their subclass. + */ + class SK_API Context { + public: + Context() {} + virtual ~Context() {} + + struct Info { + SkVector fTranslate; + bool fApplyPostCTM; + + void applyToCTM(SkMatrix* ctm) const; + void applyToCanvas(SkCanvas*) const; + }; + + /** + * Called in a loop on objects returned by SkDrawLooper::createContext(). + * Each time true is returned, the object is drawn (possibly with a modified + * canvas and/or paint). When false is finally returned, drawing for the object + * stops. + * + * On each call, the paint will be in its original state, but the + * canvas will be as it was following the previous call to next() or + * createContext(). + * + * The implementation must ensure that, when next() finally returns + * false, the canvas has been restored to the state it was + * initially, before createContext() was first called. + */ + virtual bool next(Info*, SkPaint*) = 0; + + private: + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + }; + + /** + * Called right before something is being drawn. Returns a Context + * whose next() method should be called until it returns false. + */ + virtual Context* makeContext(SkArenaAlloc*) const = 0; + + /** + * The fast bounds functions are used to enable the paint to be culled early + * in the drawing pipeline. If a subclass can support this feature it must + * return true for the canComputeFastBounds() function. If that function + * returns false then computeFastBounds behavior is undefined otherwise it + * is expected to have the following behavior. Given the parent paint and + * the parent's bounding rect the subclass must fill in and return the + * storage rect, where the storage rect is with the union of the src rect + * and the looper's bounding rect. + */ + bool canComputeFastBounds(const SkPaint& paint) const; + void computeFastBounds(const SkPaint& paint, const SkRect& src, SkRect* dst) const; + + struct BlurShadowRec { + SkScalar fSigma; + SkVector fOffset; + SkColor fColor; + SkBlurStyle fStyle; + }; + /** + * If this looper can be interpreted as having two layers, such that + * 1. The first layer (bottom most) just has a blur and translate + * 2. The second layer has no modifications to either paint or canvas + * 3. No other layers. + * then return true, and if not null, fill out the BlurShadowRec). + * + * If any of the above are not met, return false and ignore the BlurShadowRec parameter. + */ + virtual bool asABlurShadow(BlurShadowRec*) const; + + static SkFlattenable::Type GetFlattenableType() { + return kSkDrawLooper_Type; + } + + SkFlattenable::Type getFlattenableType() const override { + return kSkDrawLooper_Type; + } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize( + kSkDrawLooper_Type, data, size, procs).release())); + } + + void apply(SkCanvas* canvas, const SkPaint& paint, + std::function); + +protected: + SkDrawLooper() {} + +private: + typedef SkFlattenable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkDrawable.h b/gfx/skia/skia/include/core/SkDrawable.h new file mode 100644 index 0000000000..8d605f80d6 --- /dev/null +++ b/gfx/skia/skia/include/core/SkDrawable.h @@ -0,0 +1,160 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDrawable_DEFINED +#define SkDrawable_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkScalar.h" + +class GrBackendDrawableInfo; +class SkCanvas; +class SkMatrix; +class SkPicture; +enum class GrBackendApi : unsigned; +struct SkRect; + +/** + * Base-class for objects that draw into SkCanvas. + * + * The object has a generation ID, which is guaranteed to be unique across all drawables. To + * allow for clients of the drawable that may want to cache the results, the drawable must + * change its generation ID whenever its internal state changes such that it will draw differently. + */ +class SK_API SkDrawable : public SkFlattenable { +public: + /** + * Draws into the specified content. The drawing sequence will be balanced upon return + * (i.e. the saveLevel() on the canvas will match what it was when draw() was called, + * and the current matrix and clip settings will not be changed. + */ + void draw(SkCanvas*, const SkMatrix* = nullptr); + void draw(SkCanvas*, SkScalar x, SkScalar y); + + /** + * When using the GPU backend it is possible for a drawable to execute using the underlying 3D + * API rather than the SkCanvas API. It does so by creating a GpuDrawHandler. The GPU backend + * is deferred so the handler will be given access to the 3D API at the correct point in the + * drawing stream as the GPU backend flushes. Since the drawable may mutate, each time it is + * drawn to a GPU-backed canvas a new handler is snapped, representing the drawable's state at + * the time of the snap. + * + * When the GPU backend flushes to the 3D API it will call the draw method on the + * GpuDrawHandler. At this time the drawable may add commands to the stream of GPU commands for + * the unerlying 3D API. The draw function takes a GrBackendDrawableInfo which contains + * information about the current state of 3D API which the caller must respect. See + * GrBackendDrawableInfo for more specific details on what information is sent and the + * requirements for different 3D APIs. + * + * Additionaly there may be a slight delay from when the drawable adds its commands to when + * those commands are actually submitted to the GPU. Thus the drawable or GpuDrawHandler is + * required to keep any resources that are used by its added commands alive and valid until + * those commands are submitted to the GPU. The GpuDrawHandler will be kept alive and then + * deleted once the commands are submitted to the GPU. The dtor of the GpuDrawHandler is the + * signal to the drawable that the commands have all been submitted. Different 3D APIs may have + * additional requirements for certain resources which require waiting for the GPU to finish + * all work on those resources before reusing or deleting them. In this case, the drawable can + * use the dtor call of the GpuDrawHandler to add a fence to the GPU to track when the GPU work + * has completed. + * + * Currently this is only supported for the GPU Vulkan backend. + */ + + class GpuDrawHandler { + public: + virtual ~GpuDrawHandler() {} + + virtual void draw(const GrBackendDrawableInfo&) {} + }; + + /** + * Snaps off a GpuDrawHandler to represent the state of the SkDrawable at the time the snap is + * called. This is used for executing GPU backend specific draws intermixed with normal Skia GPU + * draws. The GPU API, which will be used for the draw, as well as the full matrix, device clip + * bounds and imageInfo of the target buffer are passed in as inputs. + */ + std::unique_ptr snapGpuDrawHandler(GrBackendApi backendApi, + const SkMatrix& matrix, + const SkIRect& clipBounds, + const SkImageInfo& bufferInfo) { + return this->onSnapGpuDrawHandler(backendApi, matrix, clipBounds, bufferInfo); + } + + SkPicture* newPictureSnapshot(); + + /** + * Return a unique value for this instance. If two calls to this return the same value, + * it is presumed that calling the draw() method will render the same thing as well. + * + * Subclasses that change their state should call notifyDrawingChanged() to ensure that + * a new value will be returned the next time it is called. + */ + uint32_t getGenerationID(); + + /** + * Return the (conservative) bounds of what the drawable will draw. If the drawable can + * change what it draws (e.g. animation or in response to some external change), then this + * must return a bounds that is always valid for all possible states. + */ + SkRect getBounds(); + + /** + * Calling this invalidates the previous generation ID, and causes a new one to be computed + * the next time getGenerationID() is called. Typically this is called by the object itself, + * in response to its internal state changing. + */ + void notifyDrawingChanged(); + + static SkFlattenable::Type GetFlattenableType() { + return kSkDrawable_Type; + } + + SkFlattenable::Type getFlattenableType() const override { + return kSkDrawable_Type; + } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize( + kSkDrawable_Type, data, size, procs).release())); + } + + Factory getFactory() const override { return nullptr; } + const char* getTypeName() const override { return nullptr; } + +protected: + SkDrawable(); + + virtual SkRect onGetBounds() = 0; + virtual void onDraw(SkCanvas*) = 0; + + virtual std::unique_ptr onSnapGpuDrawHandler(GrBackendApi, const SkMatrix&, + const SkIRect& /*clipBounds*/, + const SkImageInfo&) { + return nullptr; + } + + // TODO: Delete this once Android gets updated to take the clipBounds version above. + virtual std::unique_ptr onSnapGpuDrawHandler(GrBackendApi, const SkMatrix&) { + return nullptr; + } + + /** + * Default implementation calls onDraw() with a canvas that records into a picture. Subclasses + * may override if they have a more efficient way to return a picture for the current state + * of their drawable. Note: this picture must draw the same as what would be drawn from + * onDraw(). + */ + virtual SkPicture* onNewPictureSnapshot(); + +private: + int32_t fGenerationID; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkEncodedImageFormat.h b/gfx/skia/skia/include/core/SkEncodedImageFormat.h new file mode 100644 index 0000000000..d0a9e5e0ca --- /dev/null +++ b/gfx/skia/skia/include/core/SkEncodedImageFormat.h @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEncodedImageFormat_DEFINED +#define SkEncodedImageFormat_DEFINED + +#include + +/** + * Enum describing format of encoded data. + */ +enum class SkEncodedImageFormat { +#ifdef SK_BUILD_FOR_GOOGLE3 + kUnknown, +#endif + kBMP, + kGIF, + kICO, + kJPEG, + kPNG, + kWBMP, + kWEBP, + kPKM, + kKTX, + kASTC, + kDNG, + kHEIF, +}; + +#endif // SkEncodedImageFormat_DEFINED diff --git a/gfx/skia/skia/include/core/SkExecutor.h b/gfx/skia/skia/include/core/SkExecutor.h new file mode 100644 index 0000000000..b7b823f49e --- /dev/null +++ b/gfx/skia/skia/include/core/SkExecutor.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkExecutor_DEFINED +#define SkExecutor_DEFINED + +#include +#include +#include "include/core/SkTypes.h" + +class SK_API SkExecutor { +public: + virtual ~SkExecutor(); + + // Create a thread pool SkExecutor with a fixed thread count, by default the number of cores. + static std::unique_ptr MakeFIFOThreadPool(int threads = 0); + static std::unique_ptr MakeLIFOThreadPool(int threads = 0); + + // There is always a default SkExecutor available by calling SkExecutor::GetDefault(). + static SkExecutor& GetDefault(); + static void SetDefault(SkExecutor*); // Does not take ownership. Not thread safe. + + // Add work to execute. + virtual void add(std::function) = 0; + + // If it makes sense for this executor, use this thread to execute work for a little while. + virtual void borrow() {} +}; + +#endif//SkExecutor_DEFINED diff --git a/gfx/skia/skia/include/core/SkFilterQuality.h b/gfx/skia/skia/include/core/SkFilterQuality.h new file mode 100644 index 0000000000..1f895171f1 --- /dev/null +++ b/gfx/skia/skia/include/core/SkFilterQuality.h @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFilterQuality_DEFINED +#define SkFilterQuality_DEFINED + +#include "include/core/SkTypes.h" + +/** + * Controls how much filtering to be done when scaling/transforming complex colors + * e.g. images + */ +enum SkFilterQuality { + kNone_SkFilterQuality, //!< fastest but lowest quality, typically nearest-neighbor + kLow_SkFilterQuality, //!< typically bilerp + kMedium_SkFilterQuality, //!< typically bilerp + mipmaps for down-scaling + kHigh_SkFilterQuality, //!< slowest but highest quality, typically bicubic or better + + kLast_SkFilterQuality = kHigh_SkFilterQuality, +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFlattenable.h b/gfx/skia/skia/include/core/SkFlattenable.h new file mode 100644 index 0000000000..402517f2f4 --- /dev/null +++ b/gfx/skia/skia/include/core/SkFlattenable.h @@ -0,0 +1,106 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFlattenable_DEFINED +#define SkFlattenable_DEFINED + +#include "include/core/SkRefCnt.h" + +class SkData; +class SkReadBuffer; +class SkWriteBuffer; + +struct SkSerialProcs; +struct SkDeserialProcs; + +/** \class SkFlattenable + + SkFlattenable is the base class for objects that need to be flattened + into a data stream for either transport or as part of the key to the + font cache. + */ +class SK_API SkFlattenable : public SkRefCnt { +public: + enum Type { + kSkColorFilter_Type, + kSkDrawable_Type, + kSkDrawLooper_Type, + kSkImageFilter_Type, + kSkMaskFilter_Type, + kSkPathEffect_Type, + kSkPixelRef_Type, + kSkUnused_Type4, // used to be SkRasterizer + kSkShaderBase_Type, + kSkUnused_Type, // used to be SkUnitMapper + kSkUnused_Type2, + kSkNormalSource_Type, + }; + + typedef sk_sp (*Factory)(SkReadBuffer&); + + SkFlattenable() {} + + /** Implement this to return a factory function pointer that can be called + to recreate your class given a buffer (previously written to by your + override of flatten(). + */ + virtual Factory getFactory() const = 0; + + /** + * Returns the name of the object's class. + */ + virtual const char* getTypeName() const = 0; + + static Factory NameToFactory(const char name[]); + static const char* FactoryToName(Factory); + + static void Register(const char name[], Factory); + + /** + * Override this if your subclass needs to record data that it will need to recreate itself + * from its CreateProc (returned by getFactory()). + * + * DEPRECATED public : will move to protected ... use serialize() instead + */ + virtual void flatten(SkWriteBuffer&) const {} + + virtual Type getFlattenableType() const = 0; + + // + // public ways to serialize / deserialize + // + sk_sp serialize(const SkSerialProcs* = nullptr) const; + size_t serialize(void* memory, size_t memory_size, + const SkSerialProcs* = nullptr) const; + static sk_sp Deserialize(Type, const void* data, size_t length, + const SkDeserialProcs* procs = nullptr); + +protected: + class PrivateInitializer { + public: + static void InitEffects(); + static void InitImageFilters(); + }; + +private: + static void RegisterFlattenablesIfNeeded(); + static void Finalize(); + + friend class SkGraphics; + + typedef SkRefCnt INHERITED; +}; + +#define SK_REGISTER_FLATTENABLE(type) SkFlattenable::Register(#type, type::CreateProc) + +#define SK_FLATTENABLE_HOOKS(type) \ + static sk_sp CreateProc(SkReadBuffer&); \ + friend class SkFlattenable::PrivateInitializer; \ + Factory getFactory() const override { return type::CreateProc; } \ + const char* getTypeName() const override { return #type; } + +#endif diff --git a/gfx/skia/skia/include/core/SkFont.h b/gfx/skia/skia/include/core/SkFont.h new file mode 100644 index 0000000000..ba4dc37196 --- /dev/null +++ b/gfx/skia/skia/include/core/SkFont.h @@ -0,0 +1,538 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFont_DEFINED +#define SkFont_DEFINED + +#include "include/core/SkFontTypes.h" +#include "include/core/SkScalar.h" +#include "include/core/SkTypeface.h" + +class SkMatrix; +class SkPaint; +class SkPath; +struct SkFontMetrics; + +/** \class SkFont + SkFont controls options applied when drawing and measuring text. +*/ +class SK_API SkFont { +public: + /** Whether edge pixels draw opaque or with partial transparency. + */ + enum class Edging { + kAlias, //!< no transparent pixels on glyph edges + kAntiAlias, //!< may have transparent pixels on glyph edges + kSubpixelAntiAlias, //!< glyph positioned in pixel using transparency + }; + + /** Constructs SkFont with default values. + + @return default initialized SkFont + */ + SkFont(); + + /** Constructs SkFont with default values with SkTypeface and size in points. + + @param typeface font and style used to draw and measure text + @param size typographic height of text + @return initialized SkFont + */ + SkFont(sk_sp typeface, SkScalar size); + + /** Constructs SkFont with default values with SkTypeface. + + @param typeface font and style used to draw and measure text + @return initialized SkFont + */ + explicit SkFont(sk_sp typeface); + + + /** Constructs SkFont with default values with SkTypeface and size in points, + horizontal scale, and horizontal skew. Horizontal scale emulates condensed + and expanded fonts. Horizontal skew emulates oblique fonts. + + @param typeface font and style used to draw and measure text + @param size typographic height of text + @param scaleX text horizontal scale + @param skewX additional shear on x-axis relative to y-axis + @return initialized SkFont + */ + SkFont(sk_sp typeface, SkScalar size, SkScalar scaleX, SkScalar skewX); + + + /** Compares SkFont and font, and returns true if they are equivalent. + May return false if SkTypeface has identical contents but different pointers. + + @param font font to compare + @return true if SkFont pair are equivalent + */ + bool operator==(const SkFont& font) const; + + /** Compares SkFont and font, and returns true if they are not equivalent. + May return true if SkTypeface has identical contents but different pointers. + + @param font font to compare + @return true if SkFont pair are not equivalent + */ + bool operator!=(const SkFont& font) const { return !(*this == font); } + + /** If true, instructs the font manager to always hint glyphs. + Returned value is only meaningful if platform uses FreeType as the font manager. + + @return true if all glyphs are hinted + */ + bool isForceAutoHinting() const { return SkToBool(fFlags & kForceAutoHinting_PrivFlag); } + + /** Returns true if font engine may return glyphs from font bitmaps instead of from outlines. + + @return true if glyphs may be font bitmaps + */ + bool isEmbeddedBitmaps() const { return SkToBool(fFlags & kEmbeddedBitmaps_PrivFlag); } + + /** Returns true if glyphs may be drawn at sub-pixel offsets. + + @return true if glyphs may be drawn at sub-pixel offsets. + */ + bool isSubpixel() const { return SkToBool(fFlags & kSubpixel_PrivFlag); } + + /** Returns true if font and glyph metrics are requested to be linearly scalable. + + @return true if font and glyph metrics are requested to be linearly scalable. + */ + bool isLinearMetrics() const { return SkToBool(fFlags & kLinearMetrics_PrivFlag); } + + /** Returns true if bold is approximated by increasing the stroke width when creating glyph + bitmaps from outlines. + + @return bold is approximated through stroke width + */ + bool isEmbolden() const { return SkToBool(fFlags & kEmbolden_PrivFlag); } + + /** Returns true if baselines will be snapped to pixel positions when the current transformation + matrix is axis aligned. + + @return baselines may be snapped to pixels + */ + bool isBaselineSnap() const { return SkToBool(fFlags & kBaselineSnap_PrivFlag); } + + /** Sets whether to always hint glyphs. + If forceAutoHinting is set, instructs the font manager to always hint glyphs. + + Only affects platforms that use FreeType as the font manager. + + @param forceAutoHinting setting to always hint glyphs + */ + void setForceAutoHinting(bool forceAutoHinting); + + /** Requests, but does not require, to use bitmaps in fonts instead of outlines. + + @param embeddedBitmaps setting to use bitmaps in fonts + */ + void setEmbeddedBitmaps(bool embeddedBitmaps); + + /** Requests, but does not require, that glyphs respect sub-pixel positioning. + + @param subpixel setting for sub-pixel positioning + */ + void setSubpixel(bool subpixel); + + /** Requests, but does not require, linearly scalable font and glyph metrics. + + For outline fonts 'true' means font and glyph metrics should ignore hinting and rounding. + Note that some bitmap formats may not be able to scale linearly and will ignore this flag. + + @param linearMetrics setting for linearly scalable font and glyph metrics. + */ + void setLinearMetrics(bool linearMetrics); + + /** Increases stroke width when creating glyph bitmaps to approximate a bold typeface. + + @param embolden setting for bold approximation + */ + void setEmbolden(bool embolden); + + /** Requests that baselines be snapped to pixels when the current transformation matrix is axis + aligned. + + @param baselineSnap setting for baseline snapping to pixels + */ + void setBaselineSnap(bool baselineSnap); + + /** Whether edge pixels draw opaque or with partial transparency. + + @return one of: Edging::kAlias, Edging::kAntiAlias, Edging::kSubpixelAntiAlias + */ + Edging getEdging() const { return (Edging)fEdging; } + + /** Requests, but does not require, that edge pixels draw opaque or with + partial transparency. + + @param edging one of: Edging::kAlias, Edging::kAntiAlias, Edging::kSubpixelAntiAlias + */ + void setEdging(Edging edging); + + /** Sets level of glyph outline adjustment. + Does not check for valid values of hintingLevel. + + @param hintingLevel one of: SkFontHinting::kNone, SkFontHinting::kSlight, + SkFontHinting::kNormal, SkFontHinting::kFull + */ + void setHinting(SkFontHinting hintingLevel); + + /** Returns level of glyph outline adjustment. + + @return one of: SkFontHinting::kNone, SkFontHinting::kSlight, SkFontHinting::kNormal, + SkFontHinting::kFull + */ + SkFontHinting getHinting() const { return (SkFontHinting)fHinting; } + + /** Returns a font with the same attributes of this font, but with the specified size. + Returns nullptr if size is less than zero, infinite, or NaN. + + @param size typographic height of text + @return initialized SkFont + */ + SkFont makeWithSize(SkScalar size) const; + + /** Returns SkTypeface if set, or nullptr. + Does not alter SkTypeface SkRefCnt. + + @return SkTypeface if previously set, nullptr otherwise + */ + SkTypeface* getTypeface() const {return fTypeface.get(); } + + /** Returns SkTypeface if set, or the default typeface. + Does not alter SkTypeface SkRefCnt. + + @return SkTypeface if previously set or, a pointer to the default typeface if not + previously set. + */ + SkTypeface* getTypefaceOrDefault() const; + + /** Returns text size in points. + + @return typographic height of text + */ + SkScalar getSize() const { return fSize; } + + /** Returns text scale on x-axis. + Default value is 1. + + @return text horizontal scale + */ + SkScalar getScaleX() const { return fScaleX; } + + /** Returns text skew on x-axis. + Default value is zero. + + @return additional shear on x-axis relative to y-axis + */ + SkScalar getSkewX() const { return fSkewX; } + + /** Increases SkTypeface SkRefCnt by one. + + @return SkTypeface if previously set, nullptr otherwise + */ + sk_sp refTypeface() const { return fTypeface; } + + /** Increases SkTypeface SkRefCnt by one. + + @return SkTypeface if previously set or, a pointer to the default typeface if not + previously set. + */ + sk_sp refTypefaceOrDefault() const; + + /** Sets SkTypeface to typeface, decreasing SkRefCnt of the previous SkTypeface. + Pass nullptr to clear SkTypeface and use the default typeface. Increments + tf SkRefCnt by one. + + @param tf font and style used to draw text + */ + void setTypeface(sk_sp tf) { fTypeface = tf; } + + /** Sets text size in points. + Has no effect if textSize is not greater than or equal to zero. + + @param textSize typographic height of text + */ + void setSize(SkScalar textSize); + + /** Sets text scale on x-axis. + Default value is 1. + + @param scaleX text horizontal scale + */ + void setScaleX(SkScalar scaleX); + + /** Sets text skew on x-axis. + Default value is zero. + + @param skewX additional shear on x-axis relative to y-axis + */ + void setSkewX(SkScalar skewX); + + /** Converts text into glyph indices. + Returns the number of glyph indices represented by text. + SkTextEncoding specifies how text represents characters or glyphs. + glyphs may be nullptr, to compute the glyph count. + + Does not check text for valid character codes or valid glyph indices. + + If byteLength equals zero, returns zero. + If byteLength includes a partial character, the partial character is ignored. + + If encoding is SkTextEncoding::kUTF8 and text contains an invalid UTF-8 sequence, + zero is returned. + + When encoding is SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, or + SkTextEncoding::kUTF32; then each Unicode codepoint is mapped to a + single glyph. This function uses the default character-to-glyph + mapping from the SkTypeface and maps characters not found in the + SkTypeface to zero. + + If maxGlyphCount is not sufficient to store all the glyphs, no glyphs are copied. + The total glyph count is returned for subsequent buffer reallocation. + + @param text character storage encoded with SkTextEncoding + @param byteLength length of character storage in bytes + @param encoding one of: SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, + SkTextEncoding::kUTF32, SkTextEncoding::kGlyphID + @param glyphs storage for glyph indices; may be nullptr + @param maxGlyphCount storage capacity + @return number of glyphs represented by text of length byteLength + */ + int textToGlyphs(const void* text, size_t byteLength, SkTextEncoding encoding, + SkGlyphID glyphs[], int maxGlyphCount) const; + + /** Returns glyph index for Unicode character. + + If the character is not supported by the SkTypeface, returns 0. + + @param uni Unicode character + @return glyph index + */ + SkGlyphID unicharToGlyph(SkUnichar uni) const; + + void unicharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const; + + /** Returns number of glyphs represented by text. + + If encoding is SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, or + SkTextEncoding::kUTF32; then each Unicode codepoint is mapped to a + single glyph. + + @param text character storage encoded with SkTextEncoding + @param byteLength length of character storage in bytes + @param encoding one of: SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, + SkTextEncoding::kUTF32, SkTextEncoding::kGlyphID + @return number of glyphs represented by text of length byteLength + */ + int countText(const void* text, size_t byteLength, SkTextEncoding encoding) const { + return this->textToGlyphs(text, byteLength, encoding, nullptr, 0); + } + + /** Returns the advance width of text. + The advance is the normal distance to move before drawing additional text. + Returns the bounding box of text if bounds is not nullptr. + + @param text character storage encoded with SkTextEncoding + @param byteLength length of character storage in bytes + @param encoding one of: SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, + SkTextEncoding::kUTF32, SkTextEncoding::kGlyphID + @param bounds returns bounding box relative to (0, 0) if not nullptr + @return number of glyphs represented by text of length byteLength + */ + SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding, + SkRect* bounds = nullptr) const { + return this->measureText(text, byteLength, encoding, bounds, nullptr); + } + + /** Returns the advance width of text. + The advance is the normal distance to move before drawing additional text. + Returns the bounding box of text if bounds is not nullptr. paint + stroke width or SkPathEffect may modify the advance with. + + @param text character storage encoded with SkTextEncoding + @param byteLength length of character storage in bytes + @param encoding one of: SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, + SkTextEncoding::kUTF32, SkTextEncoding::kGlyphID + @param bounds returns bounding box relative to (0, 0) if not nullptr + @param paint optional; may be nullptr + @return number of glyphs represented by text of length byteLength + */ + SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding, + SkRect* bounds, const SkPaint* paint) const; + + /** DEPRECATED + Retrieves the advance and bounds for each glyph in glyphs. + Both widths and bounds may be nullptr. + If widths is not nullptr, widths must be an array of count entries. + if bounds is not nullptr, bounds must be an array of count entries. + + @param glyphs array of glyph indices to be measured + @param count number of glyphs + @param widths returns text advances for each glyph; may be nullptr + @param bounds returns bounds for each glyph relative to (0, 0); may be nullptr + */ + void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[], SkRect bounds[]) const { + this->getWidthsBounds(glyphs, count, widths, bounds, nullptr); + } + + // DEPRECATED + void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[], std::nullptr_t) const { + this->getWidths(glyphs, count, widths); + } + + /** Retrieves the advance and bounds for each glyph in glyphs. + Both widths and bounds may be nullptr. + If widths is not nullptr, widths must be an array of count entries. + if bounds is not nullptr, bounds must be an array of count entries. + + @param glyphs array of glyph indices to be measured + @param count number of glyphs + @param widths returns text advances for each glyph + */ + void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[]) const { + this->getWidthsBounds(glyphs, count, widths, nullptr, nullptr); + } + + /** Retrieves the advance and bounds for each glyph in glyphs. + Both widths and bounds may be nullptr. + If widths is not nullptr, widths must be an array of count entries. + if bounds is not nullptr, bounds must be an array of count entries. + + @param glyphs array of glyph indices to be measured + @param count number of glyphs + @param widths returns text advances for each glyph; may be nullptr + @param bounds returns bounds for each glyph relative to (0, 0); may be nullptr + @param paint optional, specifies stroking, SkPathEffect and SkMaskFilter + */ + void getWidthsBounds(const SkGlyphID glyphs[], int count, SkScalar widths[], SkRect bounds[], + const SkPaint* paint) const; + + + /** Retrieves the bounds for each glyph in glyphs. + bounds must be an array of count entries. + If paint is not nullptr, its stroking, SkPathEffect, and SkMaskFilter fields are respected. + + @param glyphs array of glyph indices to be measured + @param count number of glyphs + @param bounds returns bounds for each glyph relative to (0, 0); may be nullptr + @param paint optional, specifies stroking, SkPathEffect, and SkMaskFilter + */ + void getBounds(const SkGlyphID glyphs[], int count, SkRect bounds[], + const SkPaint* paint) const { + this->getWidthsBounds(glyphs, count, nullptr, bounds, paint); + } + + /** Retrieves the positions for each glyph, beginning at the specified origin. The caller + must allocated at least count number of elements in the pos[] array. + + @param glyphs array of glyph indices to be positioned + @param count number of glyphs + @param pos returns glyphs positions + @param origin location of the first glyph. Defaults to {0, 0}. + */ + void getPos(const SkGlyphID glyphs[], int count, SkPoint pos[], SkPoint origin = {0, 0}) const; + + /** Retrieves the x-positions for each glyph, beginning at the specified origin. The caller + must allocated at least count number of elements in the xpos[] array. + + @param glyphs array of glyph indices to be positioned + @param count number of glyphs + @param xpos returns glyphs x-positions + @param origin x-position of the first glyph. Defaults to 0. + */ + void getXPos(const SkGlyphID glyphs[], int count, SkScalar xpos[], SkScalar origin = 0) const; + + /** Returns path corresponding to glyph outline. + If glyph has an outline, copies outline to path and returns true. + path returned may be empty. + If glyph is described by a bitmap, returns false and ignores path parameter. + + @param glyphID index of glyph + @param path pointer to existing SkPath + @return true if glyphID is described by path + */ + bool getPath(SkGlyphID glyphID, SkPath* path) const; + + /** Returns path corresponding to glyph array. + + @param glyphIDs array of glyph indices + @param count number of glyphs + @param glyphPathProc function returning one glyph description as path + @param ctx function context + */ + void getPaths(const SkGlyphID glyphIDs[], int count, + void (*glyphPathProc)(const SkPath* pathOrNull, const SkMatrix& mx, void* ctx), + void* ctx) const; + + /** Returns SkFontMetrics associated with SkTypeface. + The return value is the recommended spacing between lines: the sum of metrics + descent, ascent, and leading. + If metrics is not nullptr, SkFontMetrics is copied to metrics. + Results are scaled by text size but does not take into account + dimensions required by text scale, text skew, fake bold, + style stroke, and SkPathEffect. + + @param metrics storage for SkFontMetrics; may be nullptr + @return recommended spacing between lines + */ + SkScalar getMetrics(SkFontMetrics* metrics) const; + + /** Returns the recommended spacing between lines: the sum of metrics + descent, ascent, and leading. + Result is scaled by text size but does not take into account + dimensions required by stroking and SkPathEffect. + Returns the same result as getMetrics(). + + @return recommended spacing between lines + */ + SkScalar getSpacing() const { return this->getMetrics(nullptr); } + + /** Dumps fields of the font to SkDebugf. May change its output over time, so clients should + * not rely on this for anything specific. Used to aid in debugging. + */ + void dump() const; + +private: + enum PrivFlags { + kForceAutoHinting_PrivFlag = 1 << 0, + kEmbeddedBitmaps_PrivFlag = 1 << 1, + kSubpixel_PrivFlag = 1 << 2, + kLinearMetrics_PrivFlag = 1 << 3, + kEmbolden_PrivFlag = 1 << 4, + kBaselineSnap_PrivFlag = 1 << 5, + }; + + static constexpr unsigned kAllFlags = kForceAutoHinting_PrivFlag + | kEmbeddedBitmaps_PrivFlag + | kSubpixel_PrivFlag + | kLinearMetrics_PrivFlag + | kEmbolden_PrivFlag + | kBaselineSnap_PrivFlag; + + sk_sp fTypeface; + SkScalar fSize; + SkScalar fScaleX; + SkScalar fSkewX; + uint8_t fFlags; + uint8_t fEdging; + uint8_t fHinting; + + SkScalar setupForAsPaths(SkPaint*); + bool hasSomeAntiAliasing() const; + + friend class GrTextBlob; + friend class SkFontPriv; + friend class SkGlyphRunListPainter; + friend class SkTextBlobCacheDiffCanvas; + friend class SkStrikeSpec; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontArguments.h b/gfx/skia/skia/include/core/SkFontArguments.h new file mode 100644 index 0000000000..c17a12014e --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontArguments.h @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontArguments_DEFINED +#define SkFontArguments_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" + +/** Represents a set of actual arguments for a font. */ +struct SkFontArguments { + struct VariationPosition { + struct Coordinate { + SkFourByteTag axis; + float value; + }; + const Coordinate* coordinates; + int coordinateCount; + }; + // deprecated, use VariationPosition::Coordinate instead + struct Axis { + SkFourByteTag fTag; + float fStyleValue; + }; + + SkFontArguments() : fCollectionIndex(0), fVariationDesignPosition{nullptr, 0} {} + + /** Specify the index of the desired font. + * + * Font formats like ttc, dfont, cff, cid, pfr, t42, t1, and fon may actually be indexed + * collections of fonts. + */ + SkFontArguments& setCollectionIndex(int collectionIndex) { + fCollectionIndex = collectionIndex; + return *this; + } + + // deprecated, use setVariationDesignPosition instead. + SkFontArguments& setAxes(const Axis* axes, int axisCount) { + fVariationDesignPosition.coordinates = + reinterpret_cast(axes); + fVariationDesignPosition.coordinateCount = axisCount; + return *this; + } + + /** Specify a position in the variation design space. + * + * Any axis not specified will use the default value. + * Any specified axis not actually present in the font will be ignored. + * + * @param position not copied. The value must remain valid for life of SkFontArguments. + */ + SkFontArguments& setVariationDesignPosition(VariationPosition position) { + fVariationDesignPosition.coordinates = position.coordinates; + fVariationDesignPosition.coordinateCount = position.coordinateCount; + return *this; + } + + int getCollectionIndex() const { + return fCollectionIndex; + } + // deprecated, use getVariationDesignPosition instead. + const Axis* getAxes(int* axisCount) const { + *axisCount = fVariationDesignPosition.coordinateCount; + return reinterpret_cast(fVariationDesignPosition.coordinates); + } + VariationPosition getVariationDesignPosition() const { + return fVariationDesignPosition; + } +private: + int fCollectionIndex; + VariationPosition fVariationDesignPosition; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontLCDConfig.h b/gfx/skia/skia/include/core/SkFontLCDConfig.h new file mode 100644 index 0000000000..1d40b82fe7 --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontLCDConfig.h @@ -0,0 +1,58 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontLCDConfig_DEFINED +#define SkFontLCDConfig_DEFINED + +#include "include/core/SkTypes.h" + +class SK_API SkFontLCDConfig { +public: + /** LCDs either have their color elements arranged horizontally or + vertically. When rendering subpixel glyphs we need to know which way + round they are. + + Note, if you change this after startup, you'll need to flush the glyph + cache because it'll have the wrong type of masks cached. + + @deprecated use SkPixelGeometry instead. + */ + enum LCDOrientation { + kHorizontal_LCDOrientation = 0, //!< this is the default + kVertical_LCDOrientation = 1, + }; + + /** @deprecated set on Device creation. */ + static void SetSubpixelOrientation(LCDOrientation orientation); + /** @deprecated get from Device. */ + static LCDOrientation GetSubpixelOrientation(); + + /** LCD color elements can vary in order. For subpixel text we need to know + the order which the LCDs uses so that the color fringes are in the + correct place. + + Note, if you change this after startup, you'll need to flush the glyph + cache because it'll have the wrong type of masks cached. + + kNONE_LCDOrder means that the subpixel elements are not spatially + separated in any usable fashion. + + @deprecated use SkPixelGeometry instead. + */ + enum LCDOrder { + kRGB_LCDOrder = 0, //!< this is the default + kBGR_LCDOrder = 1, + kNONE_LCDOrder = 2, + }; + + /** @deprecated set on Device creation. */ + static void SetSubpixelOrder(LCDOrder order); + /** @deprecated get from Device. */ + static LCDOrder GetSubpixelOrder(); +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontMetrics.h b/gfx/skia/skia/include/core/SkFontMetrics.h new file mode 100644 index 0000000000..6618577efe --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontMetrics.h @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMetrics_DEFINED +#define SkFontMetrics_DEFINED + +#include "include/core/SkScalar.h" + +/** \class SkFontMetrics + The metrics of an SkFont. + The metric values are consistent with the Skia y-down coordinate system. + */ +struct SK_API SkFontMetrics { + + /** \enum FontMetricsFlags + FontMetricsFlags indicate when certain metrics are valid; + the underline or strikeout metrics may be valid and zero. + Fonts with embedded bitmaps may not have valid underline or strikeout metrics. + */ + enum FontMetricsFlags { + kUnderlineThicknessIsValid_Flag = 1 << 0, //!< set if fUnderlineThickness is valid + kUnderlinePositionIsValid_Flag = 1 << 1, //!< set if fUnderlinePosition is valid + kStrikeoutThicknessIsValid_Flag = 1 << 2, //!< set if fStrikeoutThickness is valid + kStrikeoutPositionIsValid_Flag = 1 << 3, //!< set if fStrikeoutPosition is valid + }; + + uint32_t fFlags; //!< FontMetricsFlags indicating which metrics are valid + SkScalar fTop; //!< greatest extent above origin of any glyph bounding box, typically negative; deprecated with variable fonts + SkScalar fAscent; //!< distance to reserve above baseline, typically negative + SkScalar fDescent; //!< distance to reserve below baseline, typically positive + SkScalar fBottom; //!< greatest extent below origin of any glyph bounding box, typically positive; deprecated with variable fonts + SkScalar fLeading; //!< distance to add between lines, typically positive or zero + SkScalar fAvgCharWidth; //!< average character width, zero if unknown + SkScalar fMaxCharWidth; //!< maximum character width, zero if unknown + SkScalar fXMin; //!< greatest extent to left of origin of any glyph bounding box, typically negative; deprecated with variable fonts + SkScalar fXMax; //!< greatest extent to right of origin of any glyph bounding box, typically positive; deprecated with variable fonts + SkScalar fXHeight; //!< height of lower-case 'x', zero if unknown, typically negative + SkScalar fCapHeight; //!< height of an upper-case letter, zero if unknown, typically negative + SkScalar fUnderlineThickness; //!< underline thickness + SkScalar fUnderlinePosition; //!< distance from baseline to top of stroke, typically positive + SkScalar fStrikeoutThickness; //!< strikeout thickness + SkScalar fStrikeoutPosition; //!< distance from baseline to bottom of stroke, typically negative + + /** Returns true if SkFontMetrics has a valid underline thickness, and sets + thickness to that value. If the underline thickness is not valid, + return false, and ignore thickness. + + @param thickness storage for underline width + @return true if font specifies underline width + */ + bool hasUnderlineThickness(SkScalar* thickness) const { + if (SkToBool(fFlags & kUnderlineThicknessIsValid_Flag)) { + *thickness = fUnderlineThickness; + return true; + } + return false; + } + + /** Returns true if SkFontMetrics has a valid underline position, and sets + position to that value. If the underline position is not valid, + return false, and ignore position. + + @param position storage for underline position + @return true if font specifies underline position + */ + bool hasUnderlinePosition(SkScalar* position) const { + if (SkToBool(fFlags & kUnderlinePositionIsValid_Flag)) { + *position = fUnderlinePosition; + return true; + } + return false; + } + + /** Returns true if SkFontMetrics has a valid strikeout thickness, and sets + thickness to that value. If the underline thickness is not valid, + return false, and ignore thickness. + + @param thickness storage for strikeout width + @return true if font specifies strikeout width + */ + bool hasStrikeoutThickness(SkScalar* thickness) const { + if (SkToBool(fFlags & kStrikeoutThicknessIsValid_Flag)) { + *thickness = fStrikeoutThickness; + return true; + } + return false; + } + + /** Returns true if SkFontMetrics has a valid strikeout position, and sets + position to that value. If the underline position is not valid, + return false, and ignore position. + + @param position storage for strikeout position + @return true if font specifies strikeout position + */ + bool hasStrikeoutPosition(SkScalar* position) const { + if (SkToBool(fFlags & kStrikeoutPositionIsValid_Flag)) { + *position = fStrikeoutPosition; + return true; + } + return false; + } + +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontMgr.h b/gfx/skia/skia/include/core/SkFontMgr.h new file mode 100644 index 0000000000..39a6bab36e --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontMgr.h @@ -0,0 +1,163 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_DEFINED +#define SkFontMgr_DEFINED + +#include "include/core/SkFontArguments.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +class SkData; +class SkFontData; +class SkStreamAsset; +class SkString; +class SkTypeface; + +class SK_API SkFontStyleSet : public SkRefCnt { +public: + virtual int count() = 0; + virtual void getStyle(int index, SkFontStyle*, SkString* style) = 0; + virtual SkTypeface* createTypeface(int index) = 0; + virtual SkTypeface* matchStyle(const SkFontStyle& pattern) = 0; + + static SkFontStyleSet* CreateEmpty(); + +protected: + SkTypeface* matchStyleCSS3(const SkFontStyle& pattern); + +private: + typedef SkRefCnt INHERITED; +}; + +class SK_API SkFontMgr : public SkRefCnt { +public: + int countFamilies() const; + void getFamilyName(int index, SkString* familyName) const; + SkFontStyleSet* createStyleSet(int index) const; + + /** + * The caller must call unref() on the returned object. + * Never returns NULL; will return an empty set if the name is not found. + * + * Passing nullptr as the parameter will return the default system family. + * Note that most systems don't have a default system family, so passing nullptr will often + * result in the empty set. + * + * It is possible that this will return a style set not accessible from + * createStyleSet(int) due to hidden or auto-activated fonts. + */ + SkFontStyleSet* matchFamily(const char familyName[]) const; + + /** + * Find the closest matching typeface to the specified familyName and style + * and return a ref to it. The caller must call unref() on the returned + * object. Will return nullptr if no 'good' match is found. + * + * Passing |nullptr| as the parameter for |familyName| will return the + * default system font. + * + * It is possible that this will return a style set not accessible from + * createStyleSet(int) or matchFamily(const char[]) due to hidden or + * auto-activated fonts. + */ + SkTypeface* matchFamilyStyle(const char familyName[], const SkFontStyle&) const; + + /** + * Use the system fallback to find a typeface for the given character. + * Note that bcp47 is a combination of ISO 639, 15924, and 3166-1 codes, + * so it is fine to just pass a ISO 639 here. + * + * Will return NULL if no family can be found for the character + * in the system fallback. + * + * Passing |nullptr| as the parameter for |familyName| will return the + * default system font. + * + * bcp47[0] is the least significant fallback, bcp47[bcp47Count-1] is the + * most significant. If no specified bcp47 codes match, any font with the + * requested character will be matched. + */ + SkTypeface* matchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const; + + SkTypeface* matchFaceStyle(const SkTypeface*, const SkFontStyle&) const; + + /** + * Create a typeface for the specified data and TTC index (pass 0 for none) + * or NULL if the data is not recognized. The caller must call unref() on + * the returned object if it is not null. + */ + sk_sp makeFromData(sk_sp, int ttcIndex = 0) const; + + /** + * Create a typeface for the specified stream and TTC index + * (pass 0 for none) or NULL if the stream is not recognized. The caller + * must call unref() on the returned object if it is not null. + */ + sk_sp makeFromStream(std::unique_ptr, int ttcIndex = 0) const; + + /* Experimental, API subject to change. */ + sk_sp makeFromStream(std::unique_ptr, const SkFontArguments&) const; + + /** + * Create a typeface from the specified font data. + * Will return NULL if the typeface could not be created. + * The caller must call unref() on the returned object if it is not null. + */ + sk_sp makeFromFontData(std::unique_ptr) const; + + /** + * Create a typeface for the specified fileName and TTC index + * (pass 0 for none) or NULL if the file is not found, or its contents are + * not recognized. The caller must call unref() on the returned object + * if it is not null. + */ + sk_sp makeFromFile(const char path[], int ttcIndex = 0) const; + + sk_sp legacyMakeTypeface(const char familyName[], SkFontStyle style) const; + + /** Return the default fontmgr. */ + static sk_sp RefDefault(); + +protected: + virtual int onCountFamilies() const = 0; + virtual void onGetFamilyName(int index, SkString* familyName) const = 0; + virtual SkFontStyleSet* onCreateStyleSet(int index)const = 0; + + /** May return NULL if the name is not found. */ + virtual SkFontStyleSet* onMatchFamily(const char familyName[]) const = 0; + + virtual SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle&) const = 0; + virtual SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const = 0; + virtual SkTypeface* onMatchFaceStyle(const SkTypeface*, + const SkFontStyle&) const = 0; + + virtual sk_sp onMakeFromData(sk_sp, int ttcIndex) const = 0; + virtual sk_sp onMakeFromStreamIndex(std::unique_ptr, + int ttcIndex) const = 0; + virtual sk_sp onMakeFromStreamArgs(std::unique_ptr, + const SkFontArguments&) const; + virtual sk_sp onMakeFromFontData(std::unique_ptr) const; + virtual sk_sp onMakeFromFile(const char path[], int ttcIndex) const = 0; + + virtual sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle) const = 0; + +private: + + /** Implemented by porting layer to return the default factory. */ + static sk_sp Factory(); + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontParameters.h b/gfx/skia/skia/include/core/SkFontParameters.h new file mode 100644 index 0000000000..3bcb7869b7 --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontParameters.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontParameters_DEFINED +#define SkFontParameters_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" + +struct SkFontParameters { + struct Variation { + // Parameters in a variation font axis. + struct Axis { + // Four character identifier of the font axis (weight, width, slant, italic...). + SkFourByteTag tag; + // Minimum value supported by this axis. + float min; + // Default value set by this axis. + float def; + // Maximum value supported by this axis. The maximum can equal the minimum. + float max; + // Return whether this axis is recommended to be remain hidden in user interfaces. + bool isHidden() const { return flags & HIDDEN; } + // Set this axis to be remain hidden in user interfaces. + void setHidden(bool hidden) { flags = hidden ? (flags | HIDDEN) : (flags & ~HIDDEN); } + private: + static constexpr uint16_t HIDDEN = 0x0001; + // Attributes for a font axis. + uint16_t flags; + }; + }; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontStyle.h b/gfx/skia/skia/include/core/SkFontStyle.h new file mode 100644 index 0000000000..50b5bd026d --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontStyle.h @@ -0,0 +1,80 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontStyle_DEFINED +#define SkFontStyle_DEFINED + +#include "include/core/SkTypes.h" + +class SK_API SkFontStyle { +public: + enum Weight { + kInvisible_Weight = 0, + kThin_Weight = 100, + kExtraLight_Weight = 200, + kLight_Weight = 300, + kNormal_Weight = 400, + kMedium_Weight = 500, + kSemiBold_Weight = 600, + kBold_Weight = 700, + kExtraBold_Weight = 800, + kBlack_Weight = 900, + kExtraBlack_Weight = 1000, + }; + + enum Width { + kUltraCondensed_Width = 1, + kExtraCondensed_Width = 2, + kCondensed_Width = 3, + kSemiCondensed_Width = 4, + kNormal_Width = 5, + kSemiExpanded_Width = 6, + kExpanded_Width = 7, + kExtraExpanded_Width = 8, + kUltraExpanded_Width = 9, + }; + + enum Slant { + kUpright_Slant, + kItalic_Slant, + kOblique_Slant, + }; + + constexpr SkFontStyle(int weight, int width, Slant slant) : fValue( + (SkTPin(weight, kInvisible_Weight, kExtraBlack_Weight)) + + (SkTPin(width, kUltraCondensed_Width, kUltraExpanded_Width) << 16) + + (SkTPin(slant, kUpright_Slant, kOblique_Slant) << 24) + ) { } + + constexpr SkFontStyle() : SkFontStyle{kNormal_Weight, kNormal_Width, kUpright_Slant} { } + + bool operator==(const SkFontStyle& rhs) const { + return fValue == rhs.fValue; + } + + int weight() const { return fValue & 0xFFFF; } + int width() const { return (fValue >> 16) & 0xFF; } + Slant slant() const { return (Slant)((fValue >> 24) & 0xFF); } + + static constexpr SkFontStyle Normal() { + return SkFontStyle(kNormal_Weight, kNormal_Width, kUpright_Slant); + } + static constexpr SkFontStyle Bold() { + return SkFontStyle(kBold_Weight, kNormal_Width, kUpright_Slant); + } + static constexpr SkFontStyle Italic() { + return SkFontStyle(kNormal_Weight, kNormal_Width, kItalic_Slant ); + } + static constexpr SkFontStyle BoldItalic() { + return SkFontStyle(kBold_Weight, kNormal_Width, kItalic_Slant ); + } + +private: + uint32_t fValue; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkFontTypes.h b/gfx/skia/skia/include/core/SkFontTypes.h new file mode 100644 index 0000000000..76f5dde67f --- /dev/null +++ b/gfx/skia/skia/include/core/SkFontTypes.h @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontTypes_DEFINED +#define SkFontTypes_DEFINED + +enum class SkTextEncoding { + kUTF8, //!< uses bytes to represent UTF-8 or ASCII + kUTF16, //!< uses two byte words to represent most of Unicode + kUTF32, //!< uses four byte words to represent all of Unicode + kGlyphID, //!< uses two byte words to represent glyph indices +}; + +enum class SkFontHinting { + kNone, //!< glyph outlines unchanged + kSlight, //!< minimal modification to improve constrast + kNormal, //!< glyph outlines modified to improve constrast + kFull, //!< modifies glyph outlines for maximum constrast +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkGraphics.h b/gfx/skia/skia/include/core/SkGraphics.h new file mode 100644 index 0000000000..a7fcfa6d81 --- /dev/null +++ b/gfx/skia/skia/include/core/SkGraphics.h @@ -0,0 +1,179 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGraphics_DEFINED +#define SkGraphics_DEFINED + +#include "include/core/SkRefCnt.h" + +class SkData; +class SkImageGenerator; +class SkTraceMemoryDump; + +class SK_API SkGraphics { +public: + /** + * Call this at process initialization time if your environment does not + * permit static global initializers that execute code. + * Init() is thread-safe and idempotent. + */ + static void Init(); + + // We're in the middle of cleaning this up. + static void Term() {} + + /** + * Return the max number of bytes that should be used by the font cache. + * If the cache needs to allocate more, it will purge previous entries. + * This max can be changed by calling SetFontCacheLimit(). + */ + static size_t GetFontCacheLimit(); + + /** + * Specify the max number of bytes that should be used by the font cache. + * If the cache needs to allocate more, it will purge previous entries. + * + * This function returns the previous setting, as if GetFontCacheLimit() + * had be called before the new limit was set. + */ + static size_t SetFontCacheLimit(size_t bytes); + + /** + * Return the number of bytes currently used by the font cache. + */ + static size_t GetFontCacheUsed(); + + /** + * Return the number of entries in the font cache. + * A cache "entry" is associated with each typeface + pointSize + matrix. + */ + static int GetFontCacheCountUsed(); + + /** + * Return the current limit to the number of entries in the font cache. + * A cache "entry" is associated with each typeface + pointSize + matrix. + */ + static int GetFontCacheCountLimit(); + + /** + * Set the limit to the number of entries in the font cache, and return + * the previous value. If this new value is lower than the previous, + * it will automatically try to purge entries to meet the new limit. + */ + static int SetFontCacheCountLimit(int count); + + /* + * Returns the maximum point size for text that may be cached. + * + * Sizes above this will be drawn directly from the font's outline. + * Setting this to a large value may speed up drawing larger text (repeatedly), + * but could cause the cache to purge other sizes more often. + * + * This value is a hint to the font engine, and the actual limit may be different due to + * implementation specific details. + */ + static int GetFontCachePointSizeLimit(); + + /* + * Set the maximum point size for text that may be cached, returning the previous value. + * + * Sizes above this will be drawn directly from the font's outline. + * Setting this to a large value may speed up drawing larger text (repeatedly), + * but could cause the cache to purge other sizes more often. + * + * This value is a hint to the font engine, and the actual limit may be different due to + * implementation specific details. + */ + static int SetFontCachePointSizeLimit(int maxPointSize); + + /** + * For debugging purposes, this will attempt to purge the font cache. It + * does not change the limit, but will cause subsequent font measures and + * draws to be recreated, since they will no longer be in the cache. + */ + static void PurgeFontCache(); + + /** + * Scaling bitmaps with the kHigh_SkFilterQuality setting is + * expensive, so the result is saved in the global Scaled Image + * Cache. + * + * This function returns the memory usage of the Scaled Image Cache. + */ + static size_t GetResourceCacheTotalBytesUsed(); + + /** + * These functions get/set the memory usage limit for the resource cache, used for temporary + * bitmaps and other resources. Entries are purged from the cache when the memory useage + * exceeds this limit. + */ + static size_t GetResourceCacheTotalByteLimit(); + static size_t SetResourceCacheTotalByteLimit(size_t newLimit); + + /** + * For debugging purposes, this will attempt to purge the resource cache. It + * does not change the limit. + */ + static void PurgeResourceCache(); + + /** + * When the cachable entry is very lage (e.g. a large scaled bitmap), adding it to the cache + * can cause most/all of the existing entries to be purged. To avoid the, the client can set + * a limit for a single allocation. If a cacheable entry would have been cached, but its size + * exceeds this limit, then we do not attempt to cache it at all. + * + * Zero is the default value, meaning we always attempt to cache entries. + */ + static size_t GetResourceCacheSingleAllocationByteLimit(); + static size_t SetResourceCacheSingleAllocationByteLimit(size_t newLimit); + + /** + * Dumps memory usage of caches using the SkTraceMemoryDump interface. See SkTraceMemoryDump + * for usage of this method. + */ + static void DumpMemoryStatistics(SkTraceMemoryDump* dump); + + /** + * Free as much globally cached memory as possible. This will purge all private caches in Skia, + * including font and image caches. + * + * If there are caches associated with GPU context, those will not be affected by this call. + */ + static void PurgeAllCaches(); + + /** + * Applications with command line options may pass optional state, such + * as cache sizes, here, for instance: + * font-cache-limit=12345678 + * + * The flags format is name=value[;name=value...] with no spaces. + * This format is subject to change. + */ + static void SetFlags(const char* flags); + + typedef std::unique_ptr + (*ImageGeneratorFromEncodedDataFactory)(sk_sp); + + /** + * To instantiate images from encoded data, first looks at this runtime function-ptr. If it + * exists, it is called to create an SkImageGenerator from SkData. If there is no function-ptr + * or there is, but it returns NULL, then skia will call its internal default implementation. + * + * Returns the previous factory (which could be NULL). + */ + static ImageGeneratorFromEncodedDataFactory + SetImageGeneratorFromEncodedDataFactory(ImageGeneratorFromEncodedDataFactory); +}; + +class SkAutoGraphics { +public: + SkAutoGraphics() { + SkGraphics::Init(); + } +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkICC.h b/gfx/skia/skia/include/core/SkICC.h new file mode 100644 index 0000000000..cb84c1ffbc --- /dev/null +++ b/gfx/skia/skia/include/core/SkICC.h @@ -0,0 +1,19 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkICC_DEFINED +#define SkICC_DEFINED + +#include "include/core/SkData.h" + +struct skcms_Matrix3x3; +struct skcms_TransferFunction; + +SK_API sk_sp SkWriteICCProfile(const skcms_TransferFunction&, + const skcms_Matrix3x3& toXYZD50); + +#endif//SkICC_DEFINED diff --git a/gfx/skia/skia/include/core/SkImage.h b/gfx/skia/skia/include/core/SkImage.h new file mode 100644 index 0000000000..c4fa7a5444 --- /dev/null +++ b/gfx/skia/skia/include/core/SkImage.h @@ -0,0 +1,1143 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImage_DEFINED +#define SkImage_DEFINED + +#include "include/core/SkFilterQuality.h" +#include "include/core/SkImageEncoder.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/core/SkShader.h" +#include "include/core/SkTileMode.h" +#include "include/gpu/GrTypes.h" +#include // std::function + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 +#include +#endif + +class SkData; +class SkCanvas; +class SkImageFilter; +class SkImageGenerator; +class SkPaint; +class SkPicture; +class SkString; +class SkSurface; +class GrBackendTexture; +class GrContext; +class GrContextThreadSafeProxy; +class GrTexture; + +struct SkYUVAIndex; + +/** \class SkImage + SkImage describes a two dimensional array of pixels to draw. The pixels may be + decoded in a raster bitmap, encoded in a SkPicture or compressed data stream, + or located in GPU memory as a GPU texture. + + SkImage cannot be modified after it is created. SkImage may allocate additional + storage as needed; for instance, an encoded SkImage may decode when drawn. + + SkImage width and height are greater than zero. Creating an SkImage with zero width + or height returns SkImage equal to nullptr. + + SkImage may be created from SkBitmap, SkPixmap, SkSurface, SkPicture, encoded streams, + GPU texture, YUV_ColorSpace data, or hardware buffer. Encoded streams supported + include BMP, GIF, HEIF, ICO, JPEG, PNG, WBMP, WebP. Supported encoding details + vary with platform. +*/ +class SK_API SkImage : public SkRefCnt { +public: + + /** Caller data passed to RasterReleaseProc; may be nullptr. + */ + typedef void* ReleaseContext; + + /** Creates SkImage from SkPixmap and copy of pixels. Since pixels are copied, SkPixmap + pixels may be modified or deleted without affecting SkImage. + + SkImage is returned if SkPixmap is valid. Valid SkPixmap parameters include: + dimensions are greater than zero; + each dimension fits in 29 bits; + SkColorType and SkAlphaType are valid, and SkColorType is not kUnknown_SkColorType; + row bytes are large enough to hold one row of pixels; + pixel address is not nullptr. + + @param pixmap SkImageInfo, pixel address, and row bytes + @return copy of SkPixmap pixels, or nullptr + */ + static sk_sp MakeRasterCopy(const SkPixmap& pixmap); + + /** Creates SkImage from SkImageInfo, sharing pixels. + + SkImage is returned if SkImageInfo is valid. Valid SkImageInfo parameters include: + dimensions are greater than zero; + each dimension fits in 29 bits; + SkColorType and SkAlphaType are valid, and SkColorType is not kUnknown_SkColorType; + rowBytes are large enough to hold one row of pixels; + pixels is not nullptr, and contains enough data for SkImage. + + @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace + @param pixels address or pixel storage + @param rowBytes size of pixel row or larger + @return SkImage sharing pixels, or nullptr + */ + static sk_sp MakeRasterData(const SkImageInfo& info, sk_sp pixels, + size_t rowBytes); + + /** Function called when SkImage no longer shares pixels. ReleaseContext is + provided by caller when SkImage is created, and may be nullptr. + */ + typedef void (*RasterReleaseProc)(const void* pixels, ReleaseContext); + + /** Creates SkImage from pixmap, sharing SkPixmap pixels. Pixels must remain valid and + unchanged until rasterReleaseProc is called. rasterReleaseProc is passed + releaseContext when SkImage is deleted or no longer refers to pixmap pixels. + + Pass nullptr for rasterReleaseProc to share SkPixmap without requiring a callback + when SkImage is released. Pass nullptr for releaseContext if rasterReleaseProc + does not require state. + + SkImage is returned if pixmap is valid. Valid SkPixmap parameters include: + dimensions are greater than zero; + each dimension fits in 29 bits; + SkColorType and SkAlphaType are valid, and SkColorType is not kUnknown_SkColorType; + row bytes are large enough to hold one row of pixels; + pixel address is not nullptr. + + @param pixmap SkImageInfo, pixel address, and row bytes + @param rasterReleaseProc function called when pixels can be released; or nullptr + @param releaseContext state passed to rasterReleaseProc; or nullptr + @return SkImage sharing pixmap + */ + static sk_sp MakeFromRaster(const SkPixmap& pixmap, + RasterReleaseProc rasterReleaseProc, + ReleaseContext releaseContext); + + /** Creates SkImage from bitmap, sharing or copying bitmap pixels. If the bitmap + is marked immutable, and its pixel memory is shareable, it may be shared + instead of copied. + + SkImage is returned if bitmap is valid. Valid SkBitmap parameters include: + dimensions are greater than zero; + each dimension fits in 29 bits; + SkColorType and SkAlphaType are valid, and SkColorType is not kUnknown_SkColorType; + row bytes are large enough to hold one row of pixels; + pixel address is not nullptr. + + @param bitmap SkImageInfo, row bytes, and pixels + @return created SkImage, or nullptr + */ + static sk_sp MakeFromBitmap(const SkBitmap& bitmap); + + /** Creates SkImage from data returned by imageGenerator. Generated data is owned by SkImage and + may not be shared or accessed. + + subset allows selecting a portion of the full image. Pass nullptr to select the entire + image; otherwise, subset must be contained by image bounds. + + SkImage is returned if generator data is valid. Valid data parameters vary by type of data + and platform. + + imageGenerator may wrap SkPicture data, codec data, or custom data. + + @param imageGenerator stock or custom routines to retrieve SkImage + @param subset bounds of returned SkImage; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromGenerator(std::unique_ptr imageGenerator, + const SkIRect* subset = nullptr); + + /** + * Return an image backed by the encoded data, but attempt to defer decoding until the image + * is actually used/drawn. This deferral allows the system to cache the result, either on the + * CPU or on the GPU, depending on where the image is drawn. If memory is low, the cache may + * be purged, causing the next draw of the image to have to re-decode. + * + * The subset parameter specifies a area within the decoded image to create the image from. + * If subset is null, then the entire image is returned. + * + * This is similar to DecodeTo[Raster,Texture], but this method will attempt to defer the + * actual decode, while the DecodeTo... method explicitly decode and allocate the backend + * when the call is made. + * + * If the encoded format is not supported, or subset is outside of the bounds of the decoded + * image, nullptr is returned. + * + * @param encoded the encoded data + * @param length the number of bytes of encoded data + * @param subset the bounds of the pixels within the decoded image to return. may be null. + * @return created SkImage, or nullptr + */ + static sk_sp MakeFromEncoded(sk_sp encoded, const SkIRect* subset = nullptr); + + /** + * Decode the data in encoded/length into a raster image. + * + * The subset parameter specifies a area within the decoded image to create the image from. + * If subset is null, then the entire image is returned. + * + * This is similar to MakeFromEncoded, but this method will always decode immediately, and + * allocate the memory for the pixels for the lifetime of the returned image. + * + * If the encoded format is not supported, or subset is outside of the bounds of the decoded + * image, nullptr is returned. + * + * @param encoded the encoded data + * @param length the number of bytes of encoded data + * @param subset the bounds of the pixels within the decoded image to return. may be null. + * @return created SkImage, or nullptr + */ + static sk_sp DecodeToRaster(const void* encoded, size_t length, + const SkIRect* subset = nullptr); + static sk_sp DecodeToRaster(const sk_sp& data, + const SkIRect* subset = nullptr) { + return DecodeToRaster(data->data(), data->size(), subset); + } + + /** + * Decode the data in encoded/length into a texture-backed image. + * + * The subset parameter specifies a area within the decoded image to create the image from. + * If subset is null, then the entire image is returned. + * + * This is similar to MakeFromEncoded, but this method will always decode immediately, and + * allocate the texture for the pixels for the lifetime of the returned image. + * + * If the encoded format is not supported, or subset is outside of the bounds of the decoded + * image, nullptr is returned. + * + * @param encoded the encoded data + * @param length the number of bytes of encoded data + * @param subset the bounds of the pixels within the decoded image to return. may be null. + * @return created SkImage, or nullptr + */ + static sk_sp DecodeToTexture(GrContext* ctx, const void* encoded, size_t length, + const SkIRect* subset = nullptr); + static sk_sp DecodeToTexture(GrContext* ctx, const sk_sp& data, + const SkIRect* subset = nullptr) { + return DecodeToTexture(ctx, data->data(), data->size(), subset); + } + + // Experimental + enum CompressionType { + kETC1_CompressionType, + kLast_CompressionType = kETC1_CompressionType, + }; + + /** Creates a GPU-backed SkImage from compressed data. + + SkImage is returned if format of the compressed data is supported. + Supported formats vary by platform. + + @param context GPU context + @param data compressed data to store in SkImage + @param width width of full SkImage + @param height height of full SkImage + @param type type of compression used + @return created SkImage, or nullptr + */ + static sk_sp MakeFromCompressed(GrContext* context, sk_sp data, + int width, int height, CompressionType type); + + /** User function called when supplied texture may be deleted. + */ + typedef void (*TextureReleaseProc)(ReleaseContext releaseContext); + + /** Creates SkImage from GPU texture associated with context. Caller is responsible for + managing the lifetime of GPU texture. + + SkImage is returned if format of backendTexture is recognized and supported. + Recognized formats vary by GPU back-end. + + @param context GPU context + @param backendTexture texture residing on GPU + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kRGB_888x_SkColorType, kBGRA_8888_SkColorType, + kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param alphaType one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromTexture(GrContext* context, + const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace) { + return MakeFromTexture(context, backendTexture, origin, colorType, alphaType, colorSpace, + nullptr, nullptr); + } + + /** Creates SkImage from GPU texture associated with context. GPU texture must stay + valid and unchanged until textureReleaseProc is called. textureReleaseProc is + passed releaseContext when SkImage is deleted or no longer refers to texture. + + SkImage is returned if format of backendTexture is recognized and supported. + Recognized formats vary by GPU back-end. + + @param context GPU context + @param backendTexture texture residing on GPU + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, + kRGB_565_SkColorType, kARGB_4444_SkColorType, + kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, + kRGB_101010x_SkColorType, kGray_8_SkColorType, + kRGBA_F16_SkColorType + @param alphaType one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @param textureReleaseProc function called when texture can be released + @param releaseContext state passed to textureReleaseProc + @return created SkImage, or nullptr + */ + static sk_sp MakeFromTexture(GrContext* context, + const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext); + + /** Creates SkImage from pixmap. SkImage is uploaded to GPU back-end using context. + + Created SkImage is available to other GPU contexts, and is available across thread + boundaries. All contexts must be in the same GPU share group, or otherwise + share resources. + + When SkImage is no longer referenced, context releases texture memory + asynchronously. + + GrBackendTexture created from pixmap is uploaded to match SkSurface created with + dstColorSpace. SkColorSpace of SkImage is determined by pixmap.colorSpace(). + + SkImage is returned referring to GPU back-end if context is not nullptr, + format of data is recognized and supported, and if context supports moving + resources between contexts. Otherwise, pixmap pixel data is copied and SkImage + as returned in raster format if possible; nullptr may be returned. + Recognized GPU formats vary by platform and GPU back-end. + + @param context GPU context + @param pixmap SkImageInfo, pixel address, and row bytes + @param buildMips create SkImage as mip map if true + @param dstColorSpace range of colors of matching SkSurface on GPU + @param limitToMaxTextureSize downscale image to GPU maximum texture size, if necessary + @return created SkImage, or nullptr + */ + static sk_sp MakeCrossContextFromPixmap(GrContext* context, const SkPixmap& pixmap, + bool buildMips, + bool limitToMaxTextureSize = false); + + /** Creates SkImage from backendTexture associated with context. backendTexture and + returned SkImage are managed internally, and are released when no longer needed. + + SkImage is returned if format of backendTexture is recognized and supported. + Recognized formats vary by GPU back-end. + + @param context GPU context + @param backendTexture texture residing on GPU + @param surfaceOrigin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, + kRGB_565_SkColorType, kARGB_4444_SkColorType, + kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, + kRGB_101010x_SkColorType, kGray_8_SkColorType, + kRGBA_F16_SkColorType + @param alphaType one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromAdoptedTexture(GrContext* context, + const GrBackendTexture& backendTexture, + GrSurfaceOrigin surfaceOrigin, + SkColorType colorType, + SkAlphaType alphaType = kPremul_SkAlphaType, + sk_sp colorSpace = nullptr); + + /** Creates an SkImage by flattening the specified YUVA planes into a single, interleaved RGBA + image. + + @param context GPU context + @param yuvColorSpace How the YUV values are converted to RGB. One of: + kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, + kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace + @param yuvaTextures array of (up to four) YUVA textures on GPU which contain the, + possibly interleaved, YUVA planes + @param yuvaIndices array indicating which texture in yuvaTextures, and channel + in that texture, maps to each component of YUVA. + @param imageSize size of the resulting image + @param imageOrigin origin of the resulting image. One of: kBottomLeft_GrSurfaceOrigin, + kTopLeft_GrSurfaceOrigin + @param imageColorSpace range of colors of the resulting image; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromYUVATexturesCopy(GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace = nullptr); + + /** Creates an SkImage by flattening the specified YUVA planes into a single, interleaved RGBA + image. 'backendTexture' is used to store the result of the flattening. + + @param context GPU context + @param yuvColorSpace How the YUV values are converted to RGB. One of: + kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, + kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace + @param yuvaTextures array of (up to four) YUVA textures on GPU which contain the, + possibly interleaved, YUVA planes + @param yuvaIndices array indicating which texture in yuvaTextures, and channel + in that texture, maps to each component of YUVA. + @param imageSize size of the resulting image + @param imageOrigin origin of the resulting image. One of: + kBottomLeft_GrSurfaceOrigin, + kTopLeft_GrSurfaceOrigin + @param backendTexture the resource that stores the final pixels + @param imageColorSpace range of colors of the resulting image; may be nullptr + @param textureReleaseProc function called when backendTexture can be released + @param releaseContext state passed to textureReleaseProc + @return created SkImage, or nullptr + */ + static sk_sp MakeFromYUVATexturesCopyWithExternalBackend( + GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace = nullptr, + TextureReleaseProc textureReleaseProc = nullptr, + ReleaseContext releaseContext = nullptr); + + /** Creates an SkImage by storing the specified YUVA planes into an image, to be rendered + via multitexturing. + + @param context GPU context + @param yuvColorSpace How the YUV values are converted to RGB. One of: + kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, + kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace + @param yuvaTextures array of (up to four) YUVA textures on GPU which contain the, + possibly interleaved, YUVA planes + @param yuvaIndices array indicating which texture in yuvaTextures, and channel + in that texture, maps to each component of YUVA. + @param imageSize size of the resulting image + @param imageOrigin origin of the resulting image. One of: kBottomLeft_GrSurfaceOrigin, + kTopLeft_GrSurfaceOrigin + @param imageColorSpace range of colors of the resulting image; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromYUVATextures(GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace = nullptr); + + /** Creates SkImage from pixmap array representing YUVA data. + SkImage is uploaded to GPU back-end using context. + + Each GrBackendTexture created from yuvaPixmaps array is uploaded to match SkSurface + using SkColorSpace of SkPixmap. SkColorSpace of SkImage is determined by imageColorSpace. + + SkImage is returned referring to GPU back-end if context is not nullptr and + format of data is recognized and supported. Otherwise, nullptr is returned. + Recognized GPU formats vary by platform and GPU back-end. + + @param context GPU context + @param yuvColorSpace How the YUV values are converted to RGB. One of: + kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, + kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace + @param yuvaPixmaps array of (up to four) SkPixmap which contain the, + possibly interleaved, YUVA planes + @param yuvaIndices array indicating which pixmap in yuvaPixmaps, and channel + in that pixmap, maps to each component of YUVA. + @param imageSize size of the resulting image + @param imageOrigin origin of the resulting image. One of: + kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param buildMips create internal YUVA textures as mip map if true + @param limitToMaxTextureSize downscale image to GPU maximum texture size, if necessary + @param imageColorSpace range of colors of the resulting image; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromYUVAPixmaps( + GrContext* context, SkYUVColorSpace yuvColorSpace, const SkPixmap yuvaPixmaps[], + const SkYUVAIndex yuvaIndices[4], SkISize imageSize, GrSurfaceOrigin imageOrigin, + bool buildMips, bool limitToMaxTextureSize = false, + sk_sp imageColorSpace = nullptr); + + /** To be deprecated. + */ + static sk_sp MakeFromYUVTexturesCopy(GrContext* context, SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvTextures[3], + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace = nullptr); + + /** To be deprecated. + */ + static sk_sp MakeFromYUVTexturesCopyWithExternalBackend( + GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvTextures[3], + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace = nullptr); + + /** Creates SkImage from copy of nv12Textures, an array of textures on GPU. + nv12Textures[0] contains pixels for YUV component y plane. + nv12Textures[1] contains pixels for YUV component u plane, + followed by pixels for YUV component v plane. + Returned SkImage has the dimensions nv12Textures[2]. + yuvColorSpace describes how YUV colors convert to RGB colors. + + @param context GPU context + @param yuvColorSpace one of: kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, + kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace + @param nv12Textures array of YUV textures on GPU + @param imageOrigin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param imageColorSpace range of colors; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromNV12TexturesCopy(GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture nv12Textures[2], + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace = nullptr); + + /** Creates SkImage from copy of nv12Textures, an array of textures on GPU. + nv12Textures[0] contains pixels for YUV component y plane. + nv12Textures[1] contains pixels for YUV component u plane, + followed by pixels for YUV component v plane. + Returned SkImage has the dimensions nv12Textures[2] and stores pixels in backendTexture. + yuvColorSpace describes how YUV colors convert to RGB colors. + + @param context GPU context + @param yuvColorSpace one of: kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, + kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace + @param nv12Textures array of YUV textures on GPU + @param imageOrigin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param backendTexture the resource that stores the final pixels + @param imageColorSpace range of colors; may be nullptr + @param textureReleaseProc function called when backendTexture can be released + @param releaseContext state passed to textureReleaseProc + @return created SkImage, or nullptr + */ + static sk_sp MakeFromNV12TexturesCopyWithExternalBackend( + GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture nv12Textures[2], + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace = nullptr, + TextureReleaseProc textureReleaseProc = nullptr, + ReleaseContext releaseContext = nullptr); + + enum class BitDepth { + kU8, //!< uses 8-bit unsigned int per color component + kF16, //!< uses 16-bit float per color component + }; + + /** Creates SkImage from picture. Returned SkImage width and height are set by dimensions. + SkImage draws picture with matrix and paint, set to bitDepth and colorSpace. + + If matrix is nullptr, draws with identity SkMatrix. If paint is nullptr, draws + with default SkPaint. colorSpace may be nullptr. + + @param picture stream of drawing commands + @param dimensions width and height + @param matrix SkMatrix to rotate, scale, translate, and so on; may be nullptr + @param paint SkPaint to apply transparency, filtering, and so on; may be nullptr + @param bitDepth 8-bit integer or 16-bit float: per component + @param colorSpace range of colors; may be nullptr + @return created SkImage, or nullptr + */ + static sk_sp MakeFromPicture(sk_sp picture, const SkISize& dimensions, + const SkMatrix* matrix, const SkPaint* paint, + BitDepth bitDepth, + sk_sp colorSpace); + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 + /** (See Skia bug 7447) + Creates SkImage from Android hardware buffer. + Returned SkImage takes a reference on the buffer. + + Only available on Android, when __ANDROID_API__ is defined to be 26 or greater. + + @param hardwareBuffer AHardwareBuffer Android hardware buffer + @param alphaType one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @param surfaceOrigin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @return created SkImage, or nullptr + */ + static sk_sp MakeFromAHardwareBuffer( + AHardwareBuffer* hardwareBuffer, + SkAlphaType alphaType = kPremul_SkAlphaType, + sk_sp colorSpace = nullptr, + GrSurfaceOrigin surfaceOrigin = kTopLeft_GrSurfaceOrigin); + + /** Creates SkImage from Android hardware buffer and uploads the data from the SkPixmap to it. + Returned SkImage takes a reference on the buffer. + + Only available on Android, when __ANDROID_API__ is defined to be 26 or greater. + + @param pixmap SkPixmap that contains data to be uploaded to the AHardwareBuffer + @param hardwareBuffer AHardwareBuffer Android hardware buffer + @param surfaceOrigin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @return created SkImage, or nullptr + */ + static sk_sp MakeFromAHardwareBufferWithData( + GrContext* context, + const SkPixmap& pixmap, + AHardwareBuffer* hardwareBuffer, + GrSurfaceOrigin surfaceOrigin = kTopLeft_GrSurfaceOrigin); +#endif + + /** Returns a SkImageInfo describing the width, height, color type, alpha type, and color space + of the SkImage. + + @return image info of SkImage. + */ + const SkImageInfo& imageInfo() const { return fInfo; } + + /** Returns pixel count in each row. + + @return pixel width in SkImage + */ + int width() const { return fInfo.width(); } + + /** Returns pixel row count. + + @return pixel height in SkImage + */ + int height() const { return fInfo.height(); } + + /** Returns SkISize { width(), height() }. + + @return integral size of width() and height() + */ + SkISize dimensions() const { return SkISize::Make(fInfo.width(), fInfo.height()); } + + /** Returns SkIRect { 0, 0, width(), height() }. + + @return integral rectangle from origin to width() and height() + */ + SkIRect bounds() const { return SkIRect::MakeWH(fInfo.width(), fInfo.height()); } + + /** Returns value unique to image. SkImage contents cannot change after SkImage is + created. Any operation to create a new SkImage will receive generate a new + unique number. + + @return unique identifier + */ + uint32_t uniqueID() const { return fUniqueID; } + + /** Returns SkAlphaType, one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType. + + SkAlphaType returned was a parameter to an SkImage constructor, + or was parsed from encoded data. + + @return SkAlphaType in SkImage + */ + SkAlphaType alphaType() const; + + /** Returns SkColorType if known; otherwise, returns kUnknown_SkColorType. + + @return SkColorType of SkImage + */ + SkColorType colorType() const; + + /** Returns SkColorSpace, the range of colors, associated with SkImage. The + reference count of SkColorSpace is unchanged. The returned SkColorSpace is + immutable. + + SkColorSpace returned was passed to an SkImage constructor, + or was parsed from encoded data. SkColorSpace returned may be ignored when SkImage + is drawn, depending on the capabilities of the SkSurface receiving the drawing. + + @return SkColorSpace in SkImage, or nullptr + */ + SkColorSpace* colorSpace() const; + + /** Returns a smart pointer to SkColorSpace, the range of colors, associated with + SkImage. The smart pointer tracks the number of objects sharing this + SkColorSpace reference so the memory is released when the owners destruct. + + The returned SkColorSpace is immutable. + + SkColorSpace returned was passed to an SkImage constructor, + or was parsed from encoded data. SkColorSpace returned may be ignored when SkImage + is drawn, depending on the capabilities of the SkSurface receiving the drawing. + + @return SkColorSpace in SkImage, or nullptr, wrapped in a smart pointer + */ + sk_sp refColorSpace() const; + + /** Returns true if SkImage pixels represent transparency only. If true, each pixel + is packed in 8 bits as defined by kAlpha_8_SkColorType. + + @return true if pixels represent a transparency mask + */ + bool isAlphaOnly() const; + + /** Returns true if pixels ignore their alpha value and are treated as fully opaque. + + @return true if SkAlphaType is kOpaque_SkAlphaType + */ + bool isOpaque() const { return SkAlphaTypeIsOpaque(this->alphaType()); } + + /** Creates SkShader from SkImage. SkShader dimensions are taken from SkImage. SkShader uses + SkTileMode rules to fill drawn area outside SkImage. localMatrix permits + transforming SkImage before SkCanvas matrix is applied. + + @param tmx tiling in the x direction + @param tmy tiling in the y direction + @param localMatrix SkImage transformation, or nullptr + @return SkShader containing SkImage + */ + sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix = nullptr) const; + + /** Creates SkShader from SkImage. SkShader dimensions are taken from SkImage. SkShader uses + SkShader::kClamp_TileMode to fill drawn area outside SkImage. localMatrix permits + transforming SkImage before SkCanvas matrix is applied. + + @param localMatrix SkImage transformation, or nullptr + @return SkShader containing SkImage + */ + sk_sp makeShader(const SkMatrix* localMatrix = nullptr) const { + return this->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, localMatrix); + } + + /** Copies SkImage pixel address, row bytes, and SkImageInfo to pixmap, if address + is available, and returns true. If pixel address is not available, return + false and leave pixmap unchanged. + + @param pixmap storage for pixel state if pixels are readable; otherwise, ignored + @return true if SkImage has direct access to pixels + */ + bool peekPixels(SkPixmap* pixmap) const; + + /** Deprecated. + */ + GrTexture* getTexture() const; + + /** Returns true the contents of SkImage was created on or uploaded to GPU memory, + and is available as a GPU texture. + + @return true if SkImage is a GPU texture + */ + bool isTextureBacked() const; + + /** Returns true if SkImage can be drawn on either raster surface or GPU surface. + If context is nullptr, tests if SkImage draws on raster surface; + otherwise, tests if SkImage draws on GPU surface associated with context. + + SkImage backed by GPU texture may become invalid if associated GrContext is + invalid. lazy image may be invalid and may not draw to raster surface or + GPU surface or both. + + @param context GPU context + @return true if SkImage can be drawn + */ + bool isValid(GrContext* context) const; + + /** Flushes any pending uses of texture-backed images in the GPU backend. If the image is not + texture-backed (including promise texture images) or if the the GrContext does not + have the same context ID as the context backing the image then this is a no-op. + + If the image was not used in any non-culled draws recorded on the passed GrContext then + this is a no-op unless the GrFlushInfo contains semaphores, a finish proc, or uses + kSyncCpu_GrFlushFlag. Those are respected even when the image has not been used. + + @param context the context on which to flush pending usages of the image. + @param info flush options + @return one of: GrSemaphoresSubmitted::kYes, GrSemaphoresSubmitted::kNo + */ + GrSemaphoresSubmitted flush(GrContext* context, const GrFlushInfo& flushInfo); + + /** Version of flush() that uses a default GrFlushInfo. */ + void flush(GrContext*); + + /** Retrieves the back-end texture. If SkImage has no back-end texture, an invalid + object is returned. Call GrBackendTexture::isValid to determine if the result + is valid. + + If flushPendingGrContextIO is true, completes deferred I/O operations. + + If origin in not nullptr, copies location of content drawn into SkImage. + + @param flushPendingGrContextIO flag to flush outstanding requests + @param origin storage for one of: kTopLeft_GrSurfaceOrigin, + kBottomLeft_GrSurfaceOrigin; or nullptr + @return back-end API texture handle; invalid on failure + */ + GrBackendTexture getBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin = nullptr) const; + + /** \enum SkImage::CachingHint + CachingHint selects whether Skia may internally cache SkBitmap generated by + decoding SkImage, or by copying SkImage from GPU to CPU. The default behavior + allows caching SkBitmap. + + Choose kDisallow_CachingHint if SkImage pixels are to be used only once, or + if SkImage pixels reside in a cache outside of Skia, or to reduce memory pressure. + + Choosing kAllow_CachingHint does not ensure that pixels will be cached. + SkImage pixels may not be cached if memory requirements are too large or + pixels are not accessible. + */ + enum CachingHint { + kAllow_CachingHint, //!< allows internally caching decoded and copied pixels + kDisallow_CachingHint, //!< disallows internally caching decoded and copied pixels + }; + + /** Copies SkRect of pixels from SkImage to dstPixels. Copy starts at offset (srcX, srcY), + and does not exceed SkImage (width(), height()). + + dstInfo specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of + destination. dstRowBytes specifies the gap from one destination row to the next. + Returns true if pixels are copied. Returns false if: + - dstInfo.addr() equals nullptr + - dstRowBytes is less than dstInfo.minRowBytes() + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkImage SkColorType is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. + If SkImage SkColorType is kGray_8_SkColorType, dstInfo.colorSpace() must match. + If SkImage SkAlphaType is kOpaque_SkAlphaType, dstInfo.alphaType() must + match. If SkImage SkColorSpace is nullptr, dstInfo.colorSpace() must match. Returns + false if pixel conversion is not possible. + + srcX and srcY may be negative to copy only top or left of source. Returns + false if width() or height() is zero or negative. + Returns false if abs(srcX) >= Image width(), or if abs(srcY) >= Image height(). + + If cachingHint is kAllow_CachingHint, pixels may be retained locally. + If cachingHint is kDisallow_CachingHint, pixels are not added to the local cache. + + @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace + @param dstPixels destination pixel storage + @param dstRowBytes destination row length + @param srcX column index whose absolute value is less than width() + @param srcY row index whose absolute value is less than height() + @param cachingHint one of: kAllow_CachingHint, kDisallow_CachingHint + @return true if pixels are copied to dstPixels + */ + bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY, CachingHint cachingHint = kAllow_CachingHint) const; + + /** Copies a SkRect of pixels from SkImage to dst. Copy starts at (srcX, srcY), and + does not exceed SkImage (width(), height()). + + dst specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, + and row bytes of destination. dst.rowBytes() specifics the gap from one destination + row to the next. Returns true if pixels are copied. Returns false if: + - dst pixel storage equals nullptr + - dst.rowBytes is less than SkImageInfo::minRowBytes + - SkPixelRef is nullptr + + Pixels are copied only if pixel conversion is possible. If SkImage SkColorType is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst.colorType() must match. + If SkImage SkColorType is kGray_8_SkColorType, dst.colorSpace() must match. + If SkImage SkAlphaType is kOpaque_SkAlphaType, dst.alphaType() must + match. If SkImage SkColorSpace is nullptr, dst.colorSpace() must match. Returns + false if pixel conversion is not possible. + + srcX and srcY may be negative to copy only top or left of source. Returns + false if width() or height() is zero or negative. + Returns false if abs(srcX) >= Image width(), or if abs(srcY) >= Image height(). + + If cachingHint is kAllow_CachingHint, pixels may be retained locally. + If cachingHint is kDisallow_CachingHint, pixels are not added to the local cache. + + @param dst destination SkPixmap: SkImageInfo, pixels, row bytes + @param srcX column index whose absolute value is less than width() + @param srcY row index whose absolute value is less than height() + @param cachingHint one of: kAllow_CachingHint, kDisallow_CachingHint + @return true if pixels are copied to dst + */ + bool readPixels(const SkPixmap& dst, int srcX, int srcY, + CachingHint cachingHint = kAllow_CachingHint) const; + + /** Copies SkImage to dst, scaling pixels to fit dst.width() and dst.height(), and + converting pixels to match dst.colorType() and dst.alphaType(). Returns true if + pixels are copied. Returns false if dst.addr() is nullptr, or dst.rowBytes() is + less than dst SkImageInfo::minRowBytes. + + Pixels are copied only if pixel conversion is possible. If SkImage SkColorType is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst.colorType() must match. + If SkImage SkColorType is kGray_8_SkColorType, dst.colorSpace() must match. + If SkImage SkAlphaType is kOpaque_SkAlphaType, dst.alphaType() must + match. If SkImage SkColorSpace is nullptr, dst.colorSpace() must match. Returns + false if pixel conversion is not possible. + + Scales the image, with filterQuality, to match dst.width() and dst.height(). + filterQuality kNone_SkFilterQuality is fastest, typically implemented with + nearest neighbor filter. kLow_SkFilterQuality is typically implemented with + bilerp filter. kMedium_SkFilterQuality is typically implemented with + bilerp filter, and mip-map filter when size is reduced. + kHigh_SkFilterQuality is slowest, typically implemented with bicubic filter. + + If cachingHint is kAllow_CachingHint, pixels may be retained locally. + If cachingHint is kDisallow_CachingHint, pixels are not added to the local cache. + + @param dst destination SkPixmap: SkImageInfo, pixels, row bytes + @param filterQuality one of: kNone_SkFilterQuality, kLow_SkFilterQuality, + kMedium_SkFilterQuality, kHigh_SkFilterQuality + @param cachingHint one of: kAllow_CachingHint, kDisallow_CachingHint + @return true if pixels are scaled to fit dst + */ + bool scalePixels(const SkPixmap& dst, SkFilterQuality filterQuality, + CachingHint cachingHint = kAllow_CachingHint) const; + + /** Encodes SkImage pixels, returning result as SkData. + + Returns nullptr if encoding fails, or if encodedImageFormat is not supported. + + SkImage encoding in a format requires both building with one or more of: + SK_HAS_JPEG_LIBRARY, SK_HAS_PNG_LIBRARY, SK_HAS_WEBP_LIBRARY; and platform support + for the encoded format. + + If SK_BUILD_FOR_MAC or SK_BUILD_FOR_IOS is defined, encodedImageFormat can + additionally be one of: SkEncodedImageFormat::kICO, SkEncodedImageFormat::kBMP, + SkEncodedImageFormat::kGIF. + + quality is a platform and format specific metric trading off size and encoding + error. When used, quality equaling 100 encodes with the least error. quality may + be ignored by the encoder. + + @param encodedImageFormat one of: SkEncodedImageFormat::kJPEG, SkEncodedImageFormat::kPNG, + SkEncodedImageFormat::kWEBP + @param quality encoder specific metric with 100 equaling best + @return encoded SkImage, or nullptr + */ + sk_sp encodeToData(SkEncodedImageFormat encodedImageFormat, int quality) const; + + /** Encodes SkImage pixels, returning result as SkData. Returns existing encoded data + if present; otherwise, SkImage is encoded with SkEncodedImageFormat::kPNG. Skia + must be built with SK_HAS_PNG_LIBRARY to encode SkImage. + + Returns nullptr if existing encoded data is missing or invalid, and + encoding fails. + + @return encoded SkImage, or nullptr + */ + sk_sp encodeToData() const; + + /** Returns encoded SkImage pixels as SkData, if SkImage was created from supported + encoded stream format. Platform support for formats vary and may require building + with one or more of: SK_HAS_JPEG_LIBRARY, SK_HAS_PNG_LIBRARY, SK_HAS_WEBP_LIBRARY. + + Returns nullptr if SkImage contents are not encoded. + + @return encoded SkImage, or nullptr + */ + sk_sp refEncodedData() const; + + /** Returns subset of SkImage. subset must be fully contained by SkImage dimensions(). + The implementation may share pixels, or may copy them. + + Returns nullptr if subset is empty, or subset is not contained by bounds, or + pixels in SkImage could not be read or copied. + + @param subset bounds of returned SkImage + @return partial or full SkImage, or nullptr + */ + sk_sp makeSubset(const SkIRect& subset) const; + + /** Returns SkImage backed by GPU texture associated with context. Returned SkImage is + compatible with SkSurface created with dstColorSpace. The returned SkImage respects + mipMapped setting; if mipMapped equals GrMipMapped::kYes, the backing texture + allocates mip map levels. Returns original SkImage if context + and dstColorSpace match and mipMapped is compatible with backing GPU texture. + + Returns nullptr if context is nullptr, or if SkImage was created with another + GrContext. + + @param context GPU context + @param dstColorSpace range of colors of matching SkSurface on GPU + @param mipMapped whether created SkImage texture must allocate mip map levels + @return created SkImage, or nullptr + */ + sk_sp makeTextureImage(GrContext* context, GrMipMapped = GrMipMapped::kNo) const; + + /** Returns raster image or lazy image. Copies SkImage backed by GPU texture into + CPU memory if needed. Returns original SkImage if decoded in raster bitmap, + or if encoded in a stream. + + Returns nullptr if backed by GPU texture and copy fails. + + @return raster image, lazy image, or nullptr + */ + sk_sp makeNonTextureImage() const; + + /** Returns raster image. Copies SkImage backed by GPU texture into CPU memory, + or decodes SkImage from lazy image. Returns original SkImage if decoded in + raster bitmap. + + Returns nullptr if copy, decode, or pixel read fails. + + @return raster image, or nullptr + */ + sk_sp makeRasterImage() const; + + /** Creates filtered SkImage. filter processes original SkImage, potentially changing + color, position, and size. subset is the bounds of original SkImage processed + by filter. clipBounds is the expected bounds of the filtered SkImage. outSubset + is required storage for the actual bounds of the filtered SkImage. offset is + required storage for translation of returned SkImage. + + Returns nullptr if SkImage could not be created. If nullptr is returned, outSubset + and offset are undefined. + + Useful for animation of SkImageFilter that varies size from frame to frame. + Returned SkImage is created larger than required by filter so that GPU texture + can be reused with different sized effects. outSubset describes the valid bounds + of GPU texture returned. offset translates the returned SkImage to keep subsequent + animation frames aligned with respect to each other. + + @param context the GrContext in play - if it exists + @param filter how SkImage is sampled when transformed + @param subset bounds of SkImage processed by filter + @param clipBounds expected bounds of filtered SkImage + @param outSubset storage for returned SkImage bounds + @param offset storage for returned SkImage translation + @return filtered SkImage, or nullptr + */ + sk_sp makeWithFilter(GrContext* context, + const SkImageFilter* filter, const SkIRect& subset, + const SkIRect& clipBounds, SkIRect* outSubset, + SkIPoint* offset) const; + + /** To be deprecated. + */ + sk_sp makeWithFilter(const SkImageFilter* filter, const SkIRect& subset, + const SkIRect& clipBounds, SkIRect* outSubset, + SkIPoint* offset) const; + + /** Defines a callback function, taking one parameter of type GrBackendTexture with + no return value. Function is called when back-end texture is to be released. + */ + typedef std::function BackendTextureReleaseProc; + + /** Creates a GrBackendTexture from the provided SkImage. Returns true and + stores result in backendTexture and backendTextureReleaseProc if + texture is created; otherwise, returns false and leaves + backendTexture and backendTextureReleaseProc unmodified. + + Call backendTextureReleaseProc after deleting backendTexture. + backendTextureReleaseProc cleans up auxiliary data related to returned + backendTexture. The caller must delete returned backendTexture after use. + + If SkImage is both texture backed and singly referenced, image is returned in + backendTexture without conversion or making a copy. SkImage is singly referenced + if its was transferred solely using std::move(). + + If SkImage is not texture backed, returns texture with SkImage contents. + + @param context GPU context + @param image SkImage used for texture + @param backendTexture storage for back-end texture + @param backendTextureReleaseProc storage for clean up function + @return true if back-end texture was created + */ + static bool MakeBackendTextureFromSkImage(GrContext* context, + sk_sp image, + GrBackendTexture* backendTexture, + BackendTextureReleaseProc* backendTextureReleaseProc); + + /** Deprecated. + */ + enum LegacyBitmapMode { + kRO_LegacyBitmapMode, //!< returned bitmap is read-only and immutable + }; + + /** Deprecated. + Creates raster SkBitmap with same pixels as SkImage. If legacyBitmapMode is + kRO_LegacyBitmapMode, returned bitmap is read-only and immutable. + Returns true if SkBitmap is stored in bitmap. Returns false and resets bitmap if + SkBitmap write did not succeed. + + @param bitmap storage for legacy SkBitmap + @param legacyBitmapMode bitmap is read-only and immutable + @return true if SkBitmap was created + */ + bool asLegacyBitmap(SkBitmap* bitmap, + LegacyBitmapMode legacyBitmapMode = kRO_LegacyBitmapMode) const; + + /** Returns true if SkImage is backed by an image-generator or other service that creates + and caches its pixels or texture on-demand. + + @return true if SkImage is created as needed + */ + bool isLazyGenerated() const; + + /** Creates SkImage in target SkColorSpace. + Returns nullptr if SkImage could not be created. + + Returns original SkImage if it is in target SkColorSpace. + Otherwise, converts pixels from SkImage SkColorSpace to target SkColorSpace. + If SkImage colorSpace() returns nullptr, SkImage SkColorSpace is assumed to be sRGB. + + @param target SkColorSpace describing color range of returned SkImage + @return created SkImage in target SkColorSpace + */ + sk_sp makeColorSpace(sk_sp target) const; + + /** Experimental. + Creates SkImage in target SkColorType and SkColorSpace. + Returns nullptr if SkImage could not be created. + + Returns original SkImage if it is in target SkColorType and SkColorSpace. + + @param targetColorType SkColorType of returned SkImage + @param targetColorSpace SkColorSpace of returned SkImage + @return created SkImage in target SkColorType and SkColorSpace + */ + sk_sp makeColorTypeAndColorSpace(SkColorType targetColorType, + sk_sp targetColorSpace) const; + + /** Creates a new SkImage identical to this one, but with a different SkColorSpace. + This does not convert the underlying pixel data, so the resulting image will draw + differently. + */ + sk_sp reinterpretColorSpace(sk_sp newColorSpace) const; + +private: + SkImage(const SkImageInfo& info, uint32_t uniqueID); + friend class SkImage_Base; + + SkImageInfo fInfo; + const uint32_t fUniqueID; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkImageEncoder.h b/gfx/skia/skia/include/core/SkImageEncoder.h new file mode 100644 index 0000000000..ec5cfdbde6 --- /dev/null +++ b/gfx/skia/skia/include/core/SkImageEncoder.h @@ -0,0 +1,70 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageEncoder_DEFINED +#define SkImageEncoder_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkStream.h" + +/** + * Encode SkPixmap in the given binary image format. + * + * @param dst results are written to this stream. + * @param src source pixels. + * @param format image format, not all formats are supported. + * @param quality range from 0-100, this is supported by jpeg and webp. + * higher values correspond to improved visual quality, but less compression. + * + * @return false iff input is bad or format is unsupported. + * + * Will always return false if Skia is compiled without image + * encoders. + * + * For SkEncodedImageFormat::kWEBP, if quality is 100, it will use lossless compression. Otherwise + * it will use lossy. + * + * For examples of encoding an image to a file or to a block of memory, + * see tools/ToolUtils.h. + */ +SK_API bool SkEncodeImage(SkWStream* dst, const SkPixmap& src, + SkEncodedImageFormat format, int quality); + +/** + * The following helper function wraps SkEncodeImage(). + */ +inline bool SkEncodeImage(SkWStream* dst, const SkBitmap& src, SkEncodedImageFormat f, int q) { + SkPixmap pixmap; + return src.peekPixels(&pixmap) && SkEncodeImage(dst, pixmap, f, q); +} + +/** + * Encode SkPixmap in the given binary image format. + * + * @param src source pixels. + * @param format image format, not all formats are supported. + * @param quality range from 0-100, this is supported by jpeg and webp. + * higher values correspond to improved visual quality, but less compression. + * + * @return encoded data or nullptr if input is bad or format is unsupported. + * + * Will always return nullptr if Skia is compiled without image + * encoders. + * + * For SkEncodedImageFormat::kWEBP, if quality is 100, it will use lossless compression. Otherwise + * it will use lossy. + */ +SK_API sk_sp SkEncodePixmap(const SkPixmap& src, SkEncodedImageFormat format, int quality); + +/** + * Helper that extracts the pixmap from the bitmap, and then calls SkEncodePixmap() + */ +SK_API sk_sp SkEncodeBitmap(const SkBitmap& src, SkEncodedImageFormat format, int quality); + +#endif // SkImageEncoder_DEFINED diff --git a/gfx/skia/skia/include/core/SkImageFilter.h b/gfx/skia/skia/include/core/SkImageFilter.h new file mode 100644 index 0000000000..d6da4d7238 --- /dev/null +++ b/gfx/skia/skia/include/core/SkImageFilter.h @@ -0,0 +1,164 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageFilter_DEFINED +#define SkImageFilter_DEFINED + +#include "include/core/SkFilterQuality.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" + +class SkColorFilter; + +/** + * Base class for image filters. If one is installed in the paint, then all drawing occurs as + * usual, but it is as if the drawing happened into an offscreen (before the xfermode is applied). + * This offscreen bitmap will then be handed to the imagefilter, who in turn creates a new bitmap + * which is what will finally be drawn to the device (using the original xfermode). + * + * The local space of image filters matches the local space of the drawn geometry. For instance if + * there is rotation on the canvas, the blur will be computed along those rotated axes and not in + * the device space. In order to achieve this result, the actual drawing of the geometry may happen + * in an unrotated coordinate system so that the filtered image can be computed more easily, and + * then it will be post transformed to match what would have been produced if the geometry were + * drawn with the total canvas matrix to begin with. + */ +class SK_API SkImageFilter : public SkFlattenable { +public: + class CropRect { + public: + enum CropEdge { + kHasLeft_CropEdge = 0x01, + kHasTop_CropEdge = 0x02, + kHasWidth_CropEdge = 0x04, + kHasHeight_CropEdge = 0x08, + kHasAll_CropEdge = 0x0F, + }; + CropRect() {} + explicit CropRect(const SkRect& rect, uint32_t flags = kHasAll_CropEdge) + : fRect(rect), fFlags(flags) {} + uint32_t flags() const { return fFlags; } + const SkRect& rect() const { return fRect; } + + /** + * Apply this cropRect to the imageBounds. If a given edge of the cropRect is not set, then + * the corresponding edge from imageBounds will be used. If "embiggen" is true, the crop + * rect is allowed to enlarge the size of the rect, otherwise it may only reduce the rect. + * Filters that can affect transparent black should pass "true", while all other filters + * should pass "false". + * + * Note: imageBounds is in "device" space, as the output cropped rectangle will be, so the + * matrix is ignored for those. It is only applied to the cropRect's bounds. + */ + void applyTo(const SkIRect& imageBounds, const SkMatrix& matrix, bool embiggen, + SkIRect* cropped) const; + + private: + SkRect fRect; + uint32_t fFlags; + }; + + enum MapDirection { + kForward_MapDirection, + kReverse_MapDirection, + }; + /** + * Map a device-space rect recursively forward or backward through the filter DAG. + * kForward_MapDirection is used to determine which pixels of the destination canvas a source + * image rect would touch after filtering. kReverse_MapDirection is used to determine which rect + * of the source image would be required to fill the given rect (typically, clip bounds). Used + * for clipping and temp-buffer allocations, so the result need not be exact, but should never + * be smaller than the real answer. The default implementation recursively unions all input + * bounds, or returns the source rect if no inputs. + * + * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward + * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting answer + * may be incorrect. + */ + SkIRect filterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect = nullptr) const; + + /** + * Returns whether this image filter is a color filter and puts the color filter into the + * "filterPtr" parameter if it can. Does nothing otherwise. + * If this returns false, then the filterPtr is unchanged. + * If this returns true, then if filterPtr is not null, it must be set to a ref'd colorfitler + * (i.e. it may not be set to NULL). + */ + bool isColorFilterNode(SkColorFilter** filterPtr) const; + + // DEPRECATED : use isColorFilterNode() instead + bool asColorFilter(SkColorFilter** filterPtr) const { + return this->isColorFilterNode(filterPtr); + } + + /** + * Returns true (and optionally returns a ref'd filter) if this imagefilter can be completely + * replaced by the returned colorfilter. i.e. the two effects will affect drawing in the same + * way. + */ + bool asAColorFilter(SkColorFilter** filterPtr) const; + + /** + * Returns the number of inputs this filter will accept (some inputs can be NULL). + */ + int countInputs() const; + + /** + * Returns the input filter at a given index, or NULL if no input is connected. The indices + * used are filter-specific. + */ + const SkImageFilter* getInput(int i) const; + + // Default impl returns union of all input bounds. + virtual SkRect computeFastBounds(const SkRect& bounds) const; + + // Can this filter DAG compute the resulting bounds of an object-space rectangle? + bool canComputeFastBounds() const; + + /** + * If this filter can be represented by another filter + a localMatrix, return that filter, + * else return null. + */ + sk_sp makeWithLocalMatrix(const SkMatrix& matrix) const; + + /** + * Return an imagefilter which transforms its input by the given matrix. + * DEPRECATED: Use include/effects/SkImageFilters::MatrixTransform + */ + static sk_sp MakeMatrixFilter(const SkMatrix& matrix, + SkFilterQuality quality, + sk_sp input); + + static SkFlattenable::Type GetFlattenableType() { + return kSkImageFilter_Type; + } + + SkFlattenable::Type getFlattenableType() const override { + return kSkImageFilter_Type; + } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize(kSkImageFilter_Type, data, size, procs).release())); + } + +protected: + + sk_sp refMe() const { + return sk_ref_sp(const_cast(this)); + } + +private: + friend class SkImageFilter_Base; + + typedef SkFlattenable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkImageGenerator.h b/gfx/skia/skia/include/core/SkImageGenerator.h new file mode 100644 index 0000000000..77eb6b7810 --- /dev/null +++ b/gfx/skia/skia/include/core/SkImageGenerator.h @@ -0,0 +1,207 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageGenerator_DEFINED +#define SkImageGenerator_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkColor.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkYUVAIndex.h" +#include "include/core/SkYUVASizeInfo.h" + +class GrRecordingContext; +class GrTextureProxy; +class GrSamplerState; +class SkBitmap; +class SkData; +class SkMatrix; +class SkPaint; +class SkPicture; + +class SK_API SkImageGenerator { +public: + /** + * The PixelRef which takes ownership of this SkImageGenerator + * will call the image generator's destructor. + */ + virtual ~SkImageGenerator() { } + + uint32_t uniqueID() const { return fUniqueID; } + + /** + * Return a ref to the encoded (i.e. compressed) representation + * of this data. + * + * If non-NULL is returned, the caller is responsible for calling + * unref() on the data when it is finished. + */ + sk_sp refEncodedData() { + return this->onRefEncodedData(); + } + + /** + * Return the ImageInfo associated with this generator. + */ + const SkImageInfo& getInfo() const { return fInfo; } + + /** + * Can this generator be used to produce images that will be drawable to the specified context + * (or to CPU, if context is nullptr)? + */ + bool isValid(GrContext* context) const { + return this->onIsValid(context); + } + + /** + * Decode into the given pixels, a block of memory of size at + * least (info.fHeight - 1) * rowBytes + (info.fWidth * + * bytesPerPixel) + * + * Repeated calls to this function should give the same results, + * allowing the PixelRef to be immutable. + * + * @param info A description of the format + * expected by the caller. This can simply be identical + * to the info returned by getInfo(). + * + * This contract also allows the caller to specify + * different output-configs, which the implementation can + * decide to support or not. + * + * A size that does not match getInfo() implies a request + * to scale. If the generator cannot perform this scale, + * it will return false. + * + * @return true on success. + */ + bool getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes); + + /** + * If decoding to YUV is supported, this returns true. Otherwise, this + * returns false and does not modify any of the parameters. + * + * @param sizeInfo Output parameter indicating the sizes and required + * allocation widths of the Y, U, V, and A planes. + * @param yuvaIndices How the YUVA planes are organized/used + * @param colorSpace Output parameter. + */ + bool queryYUVA8(SkYUVASizeInfo* sizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace) const; + + /** + * Returns true on success and false on failure. + * This always attempts to perform a full decode. If the client only + * wants size, it should call queryYUVA8(). + * + * @param sizeInfo Needs to exactly match the values returned by the + * query, except the WidthBytes may be larger than the + * recommendation (but not smaller). + * @param yuvaIndices Needs to exactly match the values returned by the query. + * @param planes Memory for the Y, U, V, and A planes. Note that, depending on the + * settings in yuvaIndices, anywhere from 1..4 planes could be returned. + */ + bool getYUVA8Planes(const SkYUVASizeInfo& sizeInfo, + const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + void* planes[]); + +#if SK_SUPPORT_GPU + /** + * If the generator can natively/efficiently return its pixels as a GPU image (backed by a + * texture) this will return that image. If not, this will return NULL. + * + * This routine also supports retrieving only a subset of the pixels. That subset is specified + * by the following rectangle: + * + * subset = SkIRect::MakeXYWH(origin.x(), origin.y(), info.width(), info.height()) + * + * If subset is not contained inside the generator's bounds, this returns false. + * + * whole = SkIRect::MakeWH(getInfo().width(), getInfo().height()) + * if (!whole.contains(subset)) { + * return false; + * } + * + * Regarding the GrContext parameter: + * + * It must be non-NULL. The generator should only succeed if: + * - its internal context is the same + * - it can somehow convert its texture into one that is valid for the provided context. + * + * If the willNeedMipMaps flag is true, the generator should try to create a TextureProxy that + * at least has the mip levels allocated and the base layer filled in. If this is not possible, + * the generator is allowed to return a non mipped proxy, but this will have some additional + * overhead in later allocating mips and copying of the base layer. + */ + sk_sp generateTexture(GrRecordingContext*, const SkImageInfo& info, + const SkIPoint& origin, + bool willNeedMipMaps); +#endif + + /** + * If the default image decoder system can interpret the specified (encoded) data, then + * this returns a new ImageGenerator for it. Otherwise this returns NULL. Either way + * the caller is still responsible for managing their ownership of the data. + */ + static std::unique_ptr MakeFromEncoded(sk_sp); + + /** Return a new image generator backed by the specified picture. If the size is empty or + * the picture is NULL, this returns NULL. + * The optional matrix and paint arguments are passed to drawPicture() at rasterization + * time. + */ + static std::unique_ptr MakeFromPicture(const SkISize&, sk_sp, + const SkMatrix*, const SkPaint*, + SkImage::BitDepth, + sk_sp); + +protected: + static constexpr int kNeedNewImageUniqueID = 0; + + SkImageGenerator(const SkImageInfo& info, uint32_t uniqueId = kNeedNewImageUniqueID); + + virtual sk_sp onRefEncodedData() { return nullptr; } + struct Options {}; + virtual bool onGetPixels(const SkImageInfo&, void*, size_t, const Options&) { return false; } + virtual bool onIsValid(GrContext*) const { return true; } + virtual bool onQueryYUVA8(SkYUVASizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount], + SkYUVColorSpace*) const { return false; } + virtual bool onGetYUVA8Planes(const SkYUVASizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount], + void*[4] /*planes*/) { return false; } +#if SK_SUPPORT_GPU + enum class TexGenType { + kNone, //image generator does not implement onGenerateTexture + kCheap, //onGenerateTexture is implemented and it is fast (does not render offscreen) + kExpensive, //onGenerateTexture is implemented and it is relatively slow + }; + + virtual TexGenType onCanGenerateTexture() const { return TexGenType::kNone; } + virtual sk_sp onGenerateTexture(GrRecordingContext*, const SkImageInfo&, + const SkIPoint&, + bool willNeedMipMaps); // returns nullptr +#endif + +private: + const SkImageInfo fInfo; + const uint32_t fUniqueID; + + friend class SkImage_Lazy; + + // This is our default impl, which may be different on different platforms. + // It is called from NewFromEncoded() after it has checked for any runtime factory. + // The SkData will never be NULL, as that will have been checked by NewFromEncoded. + static std::unique_ptr MakeFromEncodedImpl(sk_sp); + + SkImageGenerator(SkImageGenerator&&) = delete; + SkImageGenerator(const SkImageGenerator&) = delete; + SkImageGenerator& operator=(SkImageGenerator&&) = delete; + SkImageGenerator& operator=(const SkImageGenerator&) = delete; +}; + +#endif // SkImageGenerator_DEFINED diff --git a/gfx/skia/skia/include/core/SkImageInfo.h b/gfx/skia/skia/include/core/SkImageInfo.h new file mode 100644 index 0000000000..fa4578439d --- /dev/null +++ b/gfx/skia/skia/include/core/SkImageInfo.h @@ -0,0 +1,743 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageInfo_DEFINED +#define SkImageInfo_DEFINED + +#include "include/core/SkColorSpace.h" +#include "include/core/SkMath.h" +#include "include/core/SkRect.h" +#include "include/core/SkSize.h" + +#include "include/private/SkTFitsIn.h" +#include "include/private/SkTo.h" + +class SkReadBuffer; +class SkWriteBuffer; + +/** \enum SkImageInfo::SkAlphaType + Describes how to interpret the alpha component of a pixel. A pixel may + be opaque, or alpha, describing multiple levels of transparency. + + In simple blending, alpha weights the draw color and the destination + color to create a new color. If alpha describes a weight from zero to one: + + new color = draw color * alpha + destination color * (1 - alpha) + + In practice alpha is encoded in two or more bits, where 1.0 equals all bits set. + + RGB may have alpha included in each component value; the stored + value is the original RGB multiplied by alpha. Premultiplied color + components improve performance. +*/ +enum SkAlphaType { + kUnknown_SkAlphaType, //!< uninitialized + kOpaque_SkAlphaType, //!< pixel is opaque + kPremul_SkAlphaType, //!< pixel components are premultiplied by alpha + kUnpremul_SkAlphaType, //!< pixel components are independent of alpha + kLastEnum_SkAlphaType = kUnpremul_SkAlphaType, //!< last valid value +}; + +/** Returns true if SkAlphaType equals kOpaque_SkAlphaType. kOpaque_SkAlphaType is a + hint that the SkColorType is opaque, or that all alpha values are set to + their 1.0 equivalent. If SkAlphaType is kOpaque_SkAlphaType, and SkColorType is not + opaque, then the result of drawing any pixel with a alpha value less than + 1.0 is undefined. + + @param at one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @return true if at equals kOpaque_SkAlphaType +*/ +static inline bool SkAlphaTypeIsOpaque(SkAlphaType at) { + return kOpaque_SkAlphaType == at; +} + +/////////////////////////////////////////////////////////////////////////////// + +/** Temporary macro that allows us to add new color types without breaking Chrome compile. */ +#define SK_EXTENDED_COLOR_TYPES + +/** \enum SkImageInfo::SkColorType + Describes how pixel bits encode color. A pixel may be an alpha mask, a + grayscale, RGB, or ARGB. + + kN32_SkColorType selects the native 32-bit ARGB format. On little endian + processors, pixels containing 8-bit ARGB components pack into 32-bit + kBGRA_8888_SkColorType. On big endian processors, pixels pack into 32-bit + kRGBA_8888_SkColorType. +*/ +enum SkColorType { + kUnknown_SkColorType, //!< uninitialized + kAlpha_8_SkColorType, //!< pixel with alpha in 8-bit byte + kRGB_565_SkColorType, //!< pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word + kARGB_4444_SkColorType, //!< pixel with 4 bits for alpha, red, green, blue; in 16-bit word + kRGBA_8888_SkColorType, //!< pixel with 8 bits for red, green, blue, alpha; in 32-bit word + kRGB_888x_SkColorType, //!< pixel with 8 bits each for red, green, blue; in 32-bit word + kBGRA_8888_SkColorType, //!< pixel with 8 bits for blue, green, red, alpha; in 32-bit word + kRGBA_1010102_SkColorType, //!< 10 bits for red, green, blue; 2 bits for alpha; in 32-bit word + kRGB_101010x_SkColorType, //!< pixel with 10 bits each for red, green, blue; in 32-bit word + kGray_8_SkColorType, //!< pixel with grayscale level in 8-bit byte + kRGBA_F16Norm_SkColorType, //!< pixel with half floats in [0,1] for red, green, blue, alpha; in 64-bit word + kRGBA_F16_SkColorType, //!< pixel with half floats for red, green, blue, alpha; in 64-bit word + kRGBA_F32_SkColorType, //!< pixel using C float for red, green, blue, alpha; in 128-bit word + + // The following 6 colortypes are just for reading from - not for rendering to + kR8G8_unorm_SkColorType, //R, U->G, V->B + + kLastEnum_SkYUVColorSpace = kIdentity_SkYUVColorSpace, //!< last valid value +}; + +/** \struct SkColorInfo + Describes pixel and encoding. SkImageInfo can be created from SkColorInfo by + providing dimensions. + + It encodes how pixel bits describe alpha, transparency; color components red, blue, + and green; and SkColorSpace, the range and linearity of colors. +*/ +class SK_API SkColorInfo { +public: + /** Creates an SkColorInfo with kUnknown_SkColorType, kUnknown_SkAlphaType, + and no SkColorSpace. + + @return empty SkImageInfo + */ + SkColorInfo() = default; + + /** Creates SkColorInfo from SkColorType ct, SkAlphaType at, and optionally SkColorSpace cs. + + If SkColorSpace cs is nullptr and SkColorInfo is part of drawing source: SkColorSpace + defaults to sRGB, mapping into SkSurface SkColorSpace. + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + @return created SkColorInfo + */ + SkColorInfo(SkColorType ct, SkAlphaType at, sk_sp cs) + : fColorSpace(std::move(cs)), fColorType(ct), fAlphaType(at) {} + + SkColorInfo(const SkColorInfo&) = default; + SkColorInfo(SkColorInfo&&) = default; + + SkColorInfo& operator=(const SkColorInfo&) = default; + SkColorInfo& operator=(SkColorInfo&&) = default; + + SkColorSpace* colorSpace() const { return fColorSpace.get(); } + sk_sp refColorSpace() const { return fColorSpace; } + SkColorType colorType() const { return fColorType; } + SkAlphaType alphaType() const { return fAlphaType; } + + bool isOpaque() const { return SkAlphaTypeIsOpaque(fAlphaType); } + + bool gammaCloseToSRGB() const { return fColorSpace && fColorSpace->gammaCloseToSRGB(); } + + /** Does other represent the same color type, alpha type, and color space? */ + bool operator==(const SkColorInfo& other) const { + return fColorType == other.fColorType && fAlphaType == other.fAlphaType && + SkColorSpace::Equals(fColorSpace.get(), other.fColorSpace.get()); + } + + /** Does other represent a different color type, alpha type, or color space? */ + bool operator!=(const SkColorInfo& other) const { return !(*this == other); } + + /** Creates SkColorInfo with same SkColorType, SkColorSpace, with SkAlphaType set + to newAlphaType. + + Created SkColorInfo contains newAlphaType even if it is incompatible with + SkColorType, in which case SkAlphaType in SkColorInfo is ignored. + */ + SkColorInfo makeAlphaType(SkAlphaType newAlphaType) const { + return SkColorInfo(this->colorType(), newAlphaType, this->refColorSpace()); + } + + /** Creates new SkColorInfo with same SkAlphaType, SkColorSpace, with SkColorType + set to newColorType. + */ + SkColorInfo makeColorType(SkColorType newColorType) const { + return SkColorInfo(newColorType, this->alphaType(), this->refColorSpace()); + } + + /** Creates SkColorInfo with same SkAlphaType, SkColorType, with SkColorSpace + set to cs. cs may be nullptr. + */ + SkColorInfo makeColorSpace(sk_sp cs) const { + return SkColorInfo(this->colorType(), this->alphaType(), std::move(cs)); + } + + /** Returns number of bytes per pixel required by SkColorType. + Returns zero if colorType() is kUnknown_SkColorType. + + @return bytes in pixel + */ + int bytesPerPixel() const; + + /** Returns bit shift converting row bytes to row pixels. + Returns zero for kUnknown_SkColorType. + + @return one of: 0, 1, 2, 3, 4; left shift to convert pixels to bytes + */ + int shiftPerPixel() const; + +private: + sk_sp fColorSpace; + SkColorType fColorType = kUnknown_SkColorType; + SkAlphaType fAlphaType = kUnknown_SkAlphaType; +}; + +/** \struct SkImageInfo + Describes pixel dimensions and encoding. SkBitmap, SkImage, PixMap, and SkSurface + can be created from SkImageInfo. SkImageInfo can be retrieved from SkBitmap and + SkPixmap, but not from SkImage and SkSurface. For example, SkImage and SkSurface + implementations may defer pixel depth, so may not completely specify SkImageInfo. + + SkImageInfo contains dimensions, the pixel integral width and height. It encodes + how pixel bits describe alpha, transparency; color components red, blue, + and green; and SkColorSpace, the range and linearity of colors. +*/ +struct SK_API SkImageInfo { +public: + + /** Creates an empty SkImageInfo with kUnknown_SkColorType, kUnknown_SkAlphaType, + a width and height of zero, and no SkColorSpace. + + @return empty SkImageInfo + */ + SkImageInfo() = default; + + /** Creates SkImageInfo from integral dimensions width and height, SkColorType ct, + SkAlphaType at, and optionally SkColorSpace cs. + + If SkColorSpace cs is nullptr and SkImageInfo is part of drawing source: SkColorSpace + defaults to sRGB, mapping into SkSurface SkColorSpace. + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @param ct one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param at one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @param cs range of colors; may be nullptr + @return created SkImageInfo + */ + static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at, + sk_sp cs = nullptr) { + return SkImageInfo({width, height}, {ct, at, std::move(cs)}); + } + static SkImageInfo Make(SkISize dimensions, SkColorType ct, SkAlphaType at, + sk_sp cs = nullptr) { + return SkImageInfo(dimensions, {ct, at, std::move(cs)}); + } + + /** Creates SkImageInfo from integral dimensions and SkColorInfo colorInfo, + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + + @param dimensions pixel column and row count; must be zeros or greater + @param SkColorInfo the pixel encoding consisting of SkColorType, SkAlphaType, and + SkColorSpace (which may be nullptr) + @return created SkImageInfo + */ + static SkImageInfo Make(SkISize dimensions, const SkColorInfo& colorInfo) { + return SkImageInfo(dimensions, colorInfo); + } + static SkImageInfo Make(SkISize dimensions, SkColorInfo&& colorInfo) { + return SkImageInfo(dimensions, std::move(colorInfo)); + } + + /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, + SkAlphaType at, and optionally SkColorSpace cs. kN32_SkColorType will equal either + kBGRA_8888_SkColorType or kRGBA_8888_SkColorType, whichever is optimal. + + If SkColorSpace cs is nullptr and SkImageInfo is part of drawing source: SkColorSpace + defaults to sRGB, mapping into SkSurface SkColorSpace. + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @param at one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @param cs range of colors; may be nullptr + @return created SkImageInfo + */ + static SkImageInfo MakeN32(int width, int height, SkAlphaType at, + sk_sp cs = nullptr) { + return Make({width, height}, kN32_SkColorType, at, std::move(cs)); + } + + /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, + SkAlphaType at, with sRGB SkColorSpace. + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @param at one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @return created SkImageInfo + */ + static SkImageInfo MakeS32(int width, int height, SkAlphaType at); + + /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, + kPremul_SkAlphaType, with optional SkColorSpace. + + If SkColorSpace cs is nullptr and SkImageInfo is part of drawing source: SkColorSpace + defaults to sRGB, mapping into SkSurface SkColorSpace. + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @param cs range of colors; may be nullptr + @return created SkImageInfo + */ + static SkImageInfo MakeN32Premul(int width, int height, sk_sp cs = nullptr) { + return Make({width, height}, kN32_SkColorType, kPremul_SkAlphaType, std::move(cs)); + } + + /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, + kPremul_SkAlphaType, with SkColorSpace set to nullptr. + + If SkImageInfo is part of drawing source: SkColorSpace defaults to sRGB, mapping + into SkSurface SkColorSpace. + + Parameters are not validated to see if their values are legal, or that the + combination is supported. + + @param size width and height, each must be zero or greater + @return created SkImageInfo + */ + static SkImageInfo MakeN32Premul(const SkISize& size) { + return MakeN32Premul(size.width(), size.height()); + } + + /** Creates SkImageInfo from integral dimensions width and height, kAlpha_8_SkColorType, + kPremul_SkAlphaType, with SkColorSpace set to nullptr. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @return created SkImageInfo + */ + static SkImageInfo MakeA8(int width, int height) { + return Make({width, height}, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr); + } + + /** Creates SkImageInfo from integral dimensions width and height, kUnknown_SkColorType, + kUnknown_SkAlphaType, with SkColorSpace set to nullptr. + + Returned SkImageInfo as part of source does not draw, and as part of destination + can not be drawn to. + + @param width pixel column count; must be zero or greater + @param height pixel row count; must be zero or greater + @return created SkImageInfo + */ + static SkImageInfo MakeUnknown(int width, int height) { + return Make({width, height}, kUnknown_SkColorType, kUnknown_SkAlphaType, nullptr); + } + + /** Creates SkImageInfo from integral dimensions width and height set to zero, + kUnknown_SkColorType, kUnknown_SkAlphaType, with SkColorSpace set to nullptr. + + Returned SkImageInfo as part of source does not draw, and as part of destination + can not be drawn to. + + @return created SkImageInfo + */ + static SkImageInfo MakeUnknown() { + return MakeUnknown(0, 0); + } + + /** Returns pixel count in each row. + + @return pixel width + */ + int width() const { return fDimensions.width(); } + + /** Returns pixel row count. + + @return pixel height + */ + int height() const { return fDimensions.height(); } + + /** Returns SkColorType, one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType. + + @return SkColorType + */ + SkColorType colorType() const { return fColorInfo.colorType(); } + + /** Returns SkAlphaType, one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType. + + @return SkAlphaType + */ + SkAlphaType alphaType() const { return fColorInfo.alphaType(); } + + /** Returns SkColorSpace, the range of colors. The reference count of + SkColorSpace is unchanged. The returned SkColorSpace is immutable. + + @return SkColorSpace, or nullptr + */ + SkColorSpace* colorSpace() const { return fColorInfo.colorSpace(); } + + /** Returns smart pointer to SkColorSpace, the range of colors. The smart pointer + tracks the number of objects sharing this SkColorSpace reference so the memory + is released when the owners destruct. + + The returned SkColorSpace is immutable. + + @return SkColorSpace wrapped in a smart pointer + */ + sk_sp refColorSpace() const { return fColorInfo.refColorSpace(); } + + /** Returns if SkImageInfo describes an empty area of pixels by checking if either + width or height is zero or smaller. + + @return true if either dimension is zero or smaller + */ + bool isEmpty() const { return fDimensions.isEmpty(); } + + /** Returns the dimensionless SkColorInfo that represents the same color type, + alpha type, and color space as this SkImageInfo. + */ + const SkColorInfo& colorInfo() const { return fColorInfo; } + + /** Returns true if SkAlphaType is set to hint that all pixels are opaque; their + alpha value is implicitly or explicitly 1.0. If true, and all pixels are + not opaque, Skia may draw incorrectly. + + Does not check if SkColorType allows alpha, or if any pixel value has + transparency. + + @return true if SkAlphaType is kOpaque_SkAlphaType + */ + bool isOpaque() const { return fColorInfo.isOpaque(); } + + /** Returns SkISize { width(), height() }. + + @return integral size of width() and height() + */ + SkISize dimensions() const { return fDimensions; } + + /** Returns SkIRect { 0, 0, width(), height() }. + + @return integral rectangle from origin to width() and height() + */ + SkIRect bounds() const { return SkIRect::MakeSize(fDimensions); } + + /** Returns true if associated SkColorSpace is not nullptr, and SkColorSpace gamma + is approximately the same as sRGB. + This includes the + + @return true if SkColorSpace gamma is approximately the same as sRGB + */ + bool gammaCloseToSRGB() const { return fColorInfo.gammaCloseToSRGB(); } + + /** Creates SkImageInfo with the same SkColorType, SkColorSpace, and SkAlphaType, + with dimensions set to width and height. + + @param newWidth pixel column count; must be zero or greater + @param newHeight pixel row count; must be zero or greater + @return created SkImageInfo + */ + SkImageInfo makeWH(int newWidth, int newHeight) const { + return Make({newWidth, newHeight}, fColorInfo); + } + + /** Creates SkImageInfo with the same SkColorType, SkColorSpace, and SkAlphaType, + with dimensions set to newDimensions. + + @param newSize pixel column and row count; must be zero or greater + @return created SkImageInfo + */ + SkImageInfo makeDimensions(SkISize newSize) const { + return Make(newSize, fColorInfo); + } + + /** Creates SkImageInfo with same SkColorType, SkColorSpace, width, and height, + with SkAlphaType set to newAlphaType. + + Created SkImageInfo contains newAlphaType even if it is incompatible with + SkColorType, in which case SkAlphaType in SkImageInfo is ignored. + + @param newAlphaType one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType + @return created SkImageInfo + */ + SkImageInfo makeAlphaType(SkAlphaType newAlphaType) const { + return Make(fDimensions, fColorInfo.makeAlphaType(newAlphaType)); + } + + /** Creates SkImageInfo with same SkAlphaType, SkColorSpace, width, and height, + with SkColorType set to newColorType. + + @param newColorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, + kRGB_101010x_SkColorType, kGray_8_SkColorType, kRGBA_F16_SkColorType + @return created SkImageInfo + */ + SkImageInfo makeColorType(SkColorType newColorType) const { + return Make(fDimensions, fColorInfo.makeColorType(newColorType)); + } + + /** Creates SkImageInfo with same SkAlphaType, SkColorType, width, and height, + with SkColorSpace set to cs. + + @param cs range of colors; may be nullptr + @return created SkImageInfo + */ + SkImageInfo makeColorSpace(sk_sp cs) const { + return Make(fDimensions, fColorInfo.makeColorSpace(std::move(cs))); + } + + /** Returns number of bytes per pixel required by SkColorType. + Returns zero if colorType( is kUnknown_SkColorType. + + @return bytes in pixel + */ + int bytesPerPixel() const { return fColorInfo.bytesPerPixel(); } + + /** Returns bit shift converting row bytes to row pixels. + Returns zero for kUnknown_SkColorType. + + @return one of: 0, 1, 2, 3; left shift to convert pixels to bytes + */ + int shiftPerPixel() const { return fColorInfo.shiftPerPixel(); } + + /** Returns minimum bytes per row, computed from pixel width() and SkColorType, which + specifies bytesPerPixel(). SkBitmap maximum value for row bytes must fit + in 31 bits. + + @return width() times bytesPerPixel() as unsigned 64-bit integer + */ + uint64_t minRowBytes64() const { return sk_64_mul(this->width(), this->bytesPerPixel()); } + + /** Returns minimum bytes per row, computed from pixel width() and SkColorType, which + specifies bytesPerPixel(). SkBitmap maximum value for row bytes must fit + in 31 bits. + + @return width() times bytesPerPixel() as signed 32-bit integer + */ + size_t minRowBytes() const { + uint64_t minRowBytes = this->minRowBytes64(); + if (!SkTFitsIn(minRowBytes)) { + return 0; + } + return SkTo(minRowBytes); + } + + /** Returns byte offset of pixel from pixel base address. + + Asserts in debug build if x or y is outside of bounds. Does not assert if + rowBytes is smaller than minRowBytes(), even though result may be incorrect. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @param rowBytes size of pixel row or larger + @return offset within pixel array + */ + size_t computeOffset(int x, int y, size_t rowBytes) const; + + /** Compares SkImageInfo with other, and returns true if width, height, SkColorType, + SkAlphaType, and SkColorSpace are equivalent. + + @param other SkImageInfo to compare + @return true if SkImageInfo equals other + */ + bool operator==(const SkImageInfo& other) const { + return fDimensions == other.fDimensions && fColorInfo == other.fColorInfo; + } + + /** Compares SkImageInfo with other, and returns true if width, height, SkColorType, + SkAlphaType, and SkColorSpace are not equivalent. + + @param other SkImageInfo to compare + @return true if SkImageInfo is not equal to other + */ + bool operator!=(const SkImageInfo& other) const { + return !(*this == other); + } + + /** Returns storage required by pixel array, given SkImageInfo dimensions, SkColorType, + and rowBytes. rowBytes is assumed to be at least as large as minRowBytes(). + + Returns zero if height is zero. + Returns SIZE_MAX if answer exceeds the range of size_t. + + @param rowBytes size of pixel row or larger + @return memory required by pixel buffer + */ + size_t computeByteSize(size_t rowBytes) const; + + /** Returns storage required by pixel array, given SkImageInfo dimensions, and + SkColorType. Uses minRowBytes() to compute bytes for pixel row. + + Returns zero if height is zero. + Returns SIZE_MAX if answer exceeds the range of size_t. + + @return least memory required by pixel buffer + */ + size_t computeMinByteSize() const { + return this->computeByteSize(this->minRowBytes()); + } + + /** Returns true if byteSize equals SIZE_MAX. computeByteSize() and + computeMinByteSize() return SIZE_MAX if size_t can not hold buffer size. + + @param byteSize result of computeByteSize() or computeMinByteSize() + @return true if computeByteSize() or computeMinByteSize() result exceeds size_t + */ + static bool ByteSizeOverflowed(size_t byteSize) { + return SIZE_MAX == byteSize; + } + + /** Returns true if rowBytes is smaller than width times pixel size. + + @param rowBytes size of pixel row or larger + @return true if rowBytes is large enough to contain pixel row + */ + bool validRowBytes(size_t rowBytes) const { + return rowBytes >= this->minRowBytes64(); + } + + /** Creates an empty SkImageInfo with kUnknown_SkColorType, kUnknown_SkAlphaType, + a width and height of zero, and no SkColorSpace. + */ + void reset() { *this = {}; } + + /** Asserts if internal values are illegal or inconsistent. Only available if + SK_DEBUG is defined at compile time. + */ + SkDEBUGCODE(void validate() const;) + +private: + SkColorInfo fColorInfo; + SkISize fDimensions = {0, 0}; + + SkImageInfo(SkISize dimensions, const SkColorInfo& colorInfo) + : fColorInfo(colorInfo), fDimensions(dimensions) {} + + SkImageInfo(SkISize dimensions, SkColorInfo&& colorInfo) + : fColorInfo(std::move(colorInfo)), fDimensions(dimensions) {} +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkMallocPixelRef.h b/gfx/skia/skia/include/core/SkMallocPixelRef.h new file mode 100644 index 0000000000..df6ba0d90a --- /dev/null +++ b/gfx/skia/skia/include/core/SkMallocPixelRef.h @@ -0,0 +1,42 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMallocPixelRef_DEFINED +#define SkMallocPixelRef_DEFINED + +#include "include/core/SkPixelRef.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" +class SkData; +struct SkImageInfo; + +/** We explicitly use the same allocator for our pixels that SkMask does, + so that we can freely assign memory allocated by one class to the other. +*/ +namespace SkMallocPixelRef { + /** + * Return a new SkMallocPixelRef, automatically allocating storage for the + * pixels. If rowBytes are 0, an optimal value will be chosen automatically. + * If rowBytes is > 0, then it will be respected, or NULL will be returned + * if rowBytes is invalid for the specified info. + * + * All pixel bytes are zeroed. + * + * Returns NULL on failure. + */ + SK_API sk_sp MakeAllocate(const SkImageInfo&, size_t rowBytes); + + /** + * Return a new SkMallocPixelRef that will use the provided SkData and + * rowBytes as pixel storage. The SkData will be ref()ed and on + * destruction of the PixelRef, the SkData will be unref()ed. + * + * Returns NULL on failure. + */ + SK_API sk_sp MakeWithData(const SkImageInfo&, size_t rowBytes, sk_sp data); +} +#endif diff --git a/gfx/skia/skia/include/core/SkMaskFilter.h b/gfx/skia/skia/include/core/SkMaskFilter.h new file mode 100644 index 0000000000..ffb30e4cbe --- /dev/null +++ b/gfx/skia/skia/include/core/SkMaskFilter.h @@ -0,0 +1,77 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMaskFilter_DEFINED +#define SkMaskFilter_DEFINED + +#include "include/core/SkBlurTypes.h" +#include "include/core/SkCoverageMode.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkScalar.h" + +class SkMatrix; +struct SkRect; +class SkString; + +/** \class SkMaskFilter + + SkMaskFilter is the base class for object that perform transformations on + the mask before drawing it. An example subclass is Blur. +*/ +class SK_API SkMaskFilter : public SkFlattenable { +public: + /** Create a blur maskfilter. + * @param style The SkBlurStyle to use + * @param sigma Standard deviation of the Gaussian blur to apply. Must be > 0. + * @param respectCTM if true the blur's sigma is modified by the CTM. + * @return The new blur maskfilter + */ + static sk_sp MakeBlur(SkBlurStyle style, SkScalar sigma, + bool respectCTM = true); + + /** + * Construct a maskfilter whose effect is to first apply the inner filter and then apply + * the outer filter to the result of the inner's. Returns nullptr on failure. + */ + static sk_sp MakeCompose(sk_sp outer, sk_sp inner); + + /** + * Compose two maskfilters together using a coverage mode. Returns nullptr on failure. + */ + static sk_sp MakeCombine(sk_sp filterA, sk_sp filterB, + SkCoverageMode mode); + + /** + * Construct a maskfilter with an additional transform. + * + * Note: unlike shader local matrices, this transform composes next to the CTM. + * + * TotalMatrix = CTM x MaskFilterMatrix x (optional/downstream) ShaderLocalMatrix + */ + sk_sp makeWithMatrix(const SkMatrix&) const; + + static SkFlattenable::Type GetFlattenableType() { + return kSkMaskFilter_Type; + } + + SkFlattenable::Type getFlattenableType() const override { + return kSkMaskFilter_Type; + } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize( + kSkMaskFilter_Type, data, size, procs).release())); + } + +private: + static void RegisterFlattenables(); + friend class SkFlattenable; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkMath.h b/gfx/skia/skia/include/core/SkMath.h new file mode 100644 index 0000000000..33d929e604 --- /dev/null +++ b/gfx/skia/skia/include/core/SkMath.h @@ -0,0 +1,75 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMath_DEFINED +#define SkMath_DEFINED + +#include "include/core/SkTypes.h" + +// 64bit -> 32bit utilities + +// Handy util that can be passed two ints, and will automatically promote to +// 64bits before the multiply, so the caller doesn't have to remember to cast +// e.g. (int64_t)a * b; +static inline int64_t sk_64_mul(int64_t a, int64_t b) { + return a * b; +} + +/////////////////////////////////////////////////////////////////////////////// + +/** Given an integer and a positive (max) integer, return the value + * pinned against 0 and max, inclusive. + * @param value The value we want returned pinned between [0...max] + * @param max The positive max value + * @return 0 if value < 0, max if value > max, else value + */ +static inline int SkClampMax(int value, int max) { + // ensure that max is positive + SkASSERT(max >= 0); + if (value < 0) { + value = 0; + } + if (value > max) { + value = max; + } + return value; +} + +/** + * Returns true if value is a power of 2. Does not explicitly check for + * value <= 0. + */ +template constexpr inline bool SkIsPow2(T value) { + return (value & (value - 1)) == 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Return a*b/((1 << shift) - 1), rounding any fractional bits. + * Only valid if a and b are unsigned and <= 32767 and shift is > 0 and <= 8 + */ +static inline unsigned SkMul16ShiftRound(U16CPU a, U16CPU b, int shift) { + SkASSERT(a <= 32767); + SkASSERT(b <= 32767); + SkASSERT(shift > 0 && shift <= 8); + unsigned prod = a*b + (1 << (shift - 1)); + return (prod + (prod >> shift)) >> shift; +} + +/** + * Return a*b/255, rounding any fractional bits. + * Only valid if a and b are unsigned and <= 32767. + */ +static inline U8CPU SkMulDiv255Round(U16CPU a, U16CPU b) { + SkASSERT(a <= 32767); + SkASSERT(b <= 32767); + unsigned prod = a*b + 128; + return (prod + (prod >> 8)) >> 8; +} + +#endif diff --git a/gfx/skia/skia/include/core/SkMatrix.h b/gfx/skia/skia/include/core/SkMatrix.h new file mode 100644 index 0000000000..0e89ca82cf --- /dev/null +++ b/gfx/skia/skia/include/core/SkMatrix.h @@ -0,0 +1,1863 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMatrix_DEFINED +#define SkMatrix_DEFINED + +#include "include/core/SkRect.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTo.h" + +struct SkRSXform; +struct SkPoint3; +class SkString; + +/** \class SkMatrix + SkMatrix holds a 3x3 matrix for transforming coordinates. This allows mapping + SkPoint and vectors with translation, scaling, skewing, rotation, and + perspective. + + SkMatrix elements are in row major order. SkMatrix does not have a constructor, + so it must be explicitly initialized. setIdentity() initializes SkMatrix + so it has no effect. setTranslate(), setScale(), setSkew(), setRotate(), set9 and setAll() + initializes all SkMatrix elements with the corresponding mapping. + + SkMatrix includes a hidden variable that classifies the type of matrix to + improve performance. SkMatrix is not thread safe unless getType() is called first. +*/ +SK_BEGIN_REQUIRE_DENSE +class SK_API SkMatrix { +public: + + /** Creates an identity SkMatrix: + + | 1 0 0 | + | 0 1 0 | + | 0 0 1 | + */ + constexpr SkMatrix() : SkMatrix(1,0,0, 0,1,0, 0,0,1, kIdentity_Mask | kRectStaysRect_Mask) {} + + /** Sets SkMatrix to scale by (sx, sy). Returned matrix is: + + | sx 0 0 | + | 0 sy 0 | + | 0 0 1 | + + @param sx horizontal scale factor + @param sy vertical scale factor + @return SkMatrix with scale + */ + static SkMatrix SK_WARN_UNUSED_RESULT MakeScale(SkScalar sx, SkScalar sy) { + SkMatrix m; + m.setScale(sx, sy); + return m; + } + + /** Sets SkMatrix to scale by (scale, scale). Returned matrix is: + + | scale 0 0 | + | 0 scale 0 | + | 0 0 1 | + + @param scale horizontal and vertical scale factor + @return SkMatrix with scale + */ + static SkMatrix SK_WARN_UNUSED_RESULT MakeScale(SkScalar scale) { + SkMatrix m; + m.setScale(scale, scale); + return m; + } + + /** Sets SkMatrix to translate by (dx, dy). Returned matrix is: + + | 1 0 dx | + | 0 1 dy | + | 0 0 1 | + + @param dx horizontal translation + @param dy vertical translation + @return SkMatrix with translation + */ + static SkMatrix SK_WARN_UNUSED_RESULT MakeTrans(SkScalar dx, SkScalar dy) { + SkMatrix m; + m.setTranslate(dx, dy); + return m; + } + + /** Sets SkMatrix to: + + | scaleX skewX transX | + | skewY scaleY transY | + | pers0 pers1 pers2 | + + @param scaleX horizontal scale factor + @param skewX horizontal skew factor + @param transX horizontal translation + @param skewY vertical skew factor + @param scaleY vertical scale factor + @param transY vertical translation + @param pers0 input x-axis perspective factor + @param pers1 input y-axis perspective factor + @param pers2 perspective scale factor + @return SkMatrix constructed from parameters + */ + static SkMatrix SK_WARN_UNUSED_RESULT MakeAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, + SkScalar skewY, SkScalar scaleY, SkScalar transY, + SkScalar pers0, SkScalar pers1, SkScalar pers2) { + SkMatrix m; + m.setAll(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2); + return m; + } + + /** \enum SkMatrix::TypeMask + Enum of bit fields for mask returned by getType(). + Used to identify the complexity of SkMatrix, to optimize performance. + */ + enum TypeMask { + kIdentity_Mask = 0, //!< identity SkMatrix; all bits clear + kTranslate_Mask = 0x01, //!< translation SkMatrix + kScale_Mask = 0x02, //!< scale SkMatrix + kAffine_Mask = 0x04, //!< skew or rotate SkMatrix + kPerspective_Mask = 0x08, //!< perspective SkMatrix + }; + + /** Returns a bit field describing the transformations the matrix may + perform. The bit field is computed conservatively, so it may include + false positives. For example, when kPerspective_Mask is set, all + other bits are set. + + @return kIdentity_Mask, or combinations of: kTranslate_Mask, kScale_Mask, + kAffine_Mask, kPerspective_Mask + */ + TypeMask getType() const { + if (fTypeMask & kUnknown_Mask) { + fTypeMask = this->computeTypeMask(); + } + // only return the public masks + return (TypeMask)(fTypeMask & 0xF); + } + + /** Returns true if SkMatrix is identity. Identity matrix is: + + | 1 0 0 | + | 0 1 0 | + | 0 0 1 | + + @return true if SkMatrix has no effect + */ + bool isIdentity() const { + return this->getType() == 0; + } + + /** Returns true if SkMatrix at most scales and translates. SkMatrix may be identity, + contain only scale elements, only translate elements, or both. SkMatrix form is: + + | scale-x 0 translate-x | + | 0 scale-y translate-y | + | 0 0 1 | + + @return true if SkMatrix is identity; or scales, translates, or both + */ + bool isScaleTranslate() const { + return !(this->getType() & ~(kScale_Mask | kTranslate_Mask)); + } + + /** Returns true if SkMatrix is identity, or translates. SkMatrix form is: + + | 1 0 translate-x | + | 0 1 translate-y | + | 0 0 1 | + + @return true if SkMatrix is identity, or translates + */ + bool isTranslate() const { return !(this->getType() & ~(kTranslate_Mask)); } + + /** Returns true SkMatrix maps SkRect to another SkRect. If true, SkMatrix is identity, + or scales, or rotates a multiple of 90 degrees, or mirrors on axes. In all + cases, SkMatrix may also have translation. SkMatrix form is either: + + | scale-x 0 translate-x | + | 0 scale-y translate-y | + | 0 0 1 | + + or + + | 0 rotate-x translate-x | + | rotate-y 0 translate-y | + | 0 0 1 | + + for non-zero values of scale-x, scale-y, rotate-x, and rotate-y. + + Also called preservesAxisAlignment(); use the one that provides better inline + documentation. + + @return true if SkMatrix maps one SkRect into another + */ + bool rectStaysRect() const { + if (fTypeMask & kUnknown_Mask) { + fTypeMask = this->computeTypeMask(); + } + return (fTypeMask & kRectStaysRect_Mask) != 0; + } + + /** Returns true SkMatrix maps SkRect to another SkRect. If true, SkMatrix is identity, + or scales, or rotates a multiple of 90 degrees, or mirrors on axes. In all + cases, SkMatrix may also have translation. SkMatrix form is either: + + | scale-x 0 translate-x | + | 0 scale-y translate-y | + | 0 0 1 | + + or + + | 0 rotate-x translate-x | + | rotate-y 0 translate-y | + | 0 0 1 | + + for non-zero values of scale-x, scale-y, rotate-x, and rotate-y. + + Also called rectStaysRect(); use the one that provides better inline + documentation. + + @return true if SkMatrix maps one SkRect into another + */ + bool preservesAxisAlignment() const { return this->rectStaysRect(); } + + /** Returns true if the matrix contains perspective elements. SkMatrix form is: + + | -- -- -- | + | -- -- -- | + | perspective-x perspective-y perspective-scale | + + where perspective-x or perspective-y is non-zero, or perspective-scale is + not one. All other elements may have any value. + + @return true if SkMatrix is in most general form + */ + bool hasPerspective() const { + return SkToBool(this->getPerspectiveTypeMaskOnly() & + kPerspective_Mask); + } + + /** Returns true if SkMatrix contains only translation, rotation, reflection, and + uniform scale. + Returns false if SkMatrix contains different scales, skewing, perspective, or + degenerate forms that collapse to a line or point. + + Describes that the SkMatrix makes rendering with and without the matrix are + visually alike; a transformed circle remains a circle. Mathematically, this is + referred to as similarity of a Euclidean space, or a similarity transformation. + + Preserves right angles, keeping the arms of the angle equal lengths. + + @param tol to be deprecated + @return true if SkMatrix only rotates, uniformly scales, translates + */ + bool isSimilarity(SkScalar tol = SK_ScalarNearlyZero) const; + + /** Returns true if SkMatrix contains only translation, rotation, reflection, and + scale. Scale may differ along rotated axes. + Returns false if SkMatrix skewing, perspective, or degenerate forms that collapse + to a line or point. + + Preserves right angles, but not requiring that the arms of the angle + retain equal lengths. + + @param tol to be deprecated + @return true if SkMatrix only rotates, scales, translates + */ + bool preservesRightAngles(SkScalar tol = SK_ScalarNearlyZero) const; + + /** SkMatrix organizes its values in row order. These members correspond to + each value in SkMatrix. + */ + static constexpr int kMScaleX = 0; //!< horizontal scale factor + static constexpr int kMSkewX = 1; //!< horizontal skew factor + static constexpr int kMTransX = 2; //!< horizontal translation + static constexpr int kMSkewY = 3; //!< vertical skew factor + static constexpr int kMScaleY = 4; //!< vertical scale factor + static constexpr int kMTransY = 5; //!< vertical translation + static constexpr int kMPersp0 = 6; //!< input x perspective factor + static constexpr int kMPersp1 = 7; //!< input y perspective factor + static constexpr int kMPersp2 = 8; //!< perspective bias + + /** Affine arrays are in column major order to match the matrix used by + PDF and XPS. + */ + static constexpr int kAScaleX = 0; //!< horizontal scale factor + static constexpr int kASkewY = 1; //!< vertical skew factor + static constexpr int kASkewX = 2; //!< horizontal skew factor + static constexpr int kAScaleY = 3; //!< vertical scale factor + static constexpr int kATransX = 4; //!< horizontal translation + static constexpr int kATransY = 5; //!< vertical translation + + /** Returns one matrix value. Asserts if index is out of range and SK_DEBUG is + defined. + + @param index one of: kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, + kMPersp0, kMPersp1, kMPersp2 + @return value corresponding to index + */ + SkScalar operator[](int index) const { + SkASSERT((unsigned)index < 9); + return fMat[index]; + } + + /** Returns one matrix value. Asserts if index is out of range and SK_DEBUG is + defined. + + @param index one of: kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, + kMPersp0, kMPersp1, kMPersp2 + @return value corresponding to index + */ + SkScalar get(int index) const { + SkASSERT((unsigned)index < 9); + return fMat[index]; + } + + /** Returns scale factor multiplied by x-axis input, contributing to x-axis output. + With mapPoints(), scales SkPoint along the x-axis. + + @return horizontal scale factor + */ + SkScalar getScaleX() const { return fMat[kMScaleX]; } + + /** Returns scale factor multiplied by y-axis input, contributing to y-axis output. + With mapPoints(), scales SkPoint along the y-axis. + + @return vertical scale factor + */ + SkScalar getScaleY() const { return fMat[kMScaleY]; } + + /** Returns scale factor multiplied by x-axis input, contributing to y-axis output. + With mapPoints(), skews SkPoint along the y-axis. + Skewing both axes can rotate SkPoint. + + @return vertical skew factor + */ + SkScalar getSkewY() const { return fMat[kMSkewY]; } + + /** Returns scale factor multiplied by y-axis input, contributing to x-axis output. + With mapPoints(), skews SkPoint along the x-axis. + Skewing both axes can rotate SkPoint. + + @return horizontal scale factor + */ + SkScalar getSkewX() const { return fMat[kMSkewX]; } + + /** Returns translation contributing to x-axis output. + With mapPoints(), moves SkPoint along the x-axis. + + @return horizontal translation factor + */ + SkScalar getTranslateX() const { return fMat[kMTransX]; } + + /** Returns translation contributing to y-axis output. + With mapPoints(), moves SkPoint along the y-axis. + + @return vertical translation factor + */ + SkScalar getTranslateY() const { return fMat[kMTransY]; } + + /** Returns factor scaling input x-axis relative to input y-axis. + + @return input x-axis perspective factor + */ + SkScalar getPerspX() const { return fMat[kMPersp0]; } + + /** Returns factor scaling input y-axis relative to input x-axis. + + @return input y-axis perspective factor + */ + SkScalar getPerspY() const { return fMat[kMPersp1]; } + + /** Returns writable SkMatrix value. Asserts if index is out of range and SK_DEBUG is + defined. Clears internal cache anticipating that caller will change SkMatrix value. + + Next call to read SkMatrix state may recompute cache; subsequent writes to SkMatrix + value must be followed by dirtyMatrixTypeCache(). + + @param index one of: kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, + kMPersp0, kMPersp1, kMPersp2 + @return writable value corresponding to index + */ + SkScalar& operator[](int index) { + SkASSERT((unsigned)index < 9); + this->setTypeMask(kUnknown_Mask); + return fMat[index]; + } + + /** Sets SkMatrix value. Asserts if index is out of range and SK_DEBUG is + defined. Safer than operator[]; internal cache is always maintained. + + @param index one of: kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, + kMPersp0, kMPersp1, kMPersp2 + @param value scalar to store in SkMatrix + */ + SkMatrix& set(int index, SkScalar value) { + SkASSERT((unsigned)index < 9); + fMat[index] = value; + this->setTypeMask(kUnknown_Mask); + return *this; + } + + /** Sets horizontal scale factor. + + @param v horizontal scale factor to store + */ + SkMatrix& setScaleX(SkScalar v) { return this->set(kMScaleX, v); } + + /** Sets vertical scale factor. + + @param v vertical scale factor to store + */ + SkMatrix& setScaleY(SkScalar v) { return this->set(kMScaleY, v); } + + /** Sets vertical skew factor. + + @param v vertical skew factor to store + */ + SkMatrix& setSkewY(SkScalar v) { return this->set(kMSkewY, v); } + + /** Sets horizontal skew factor. + + @param v horizontal skew factor to store + */ + SkMatrix& setSkewX(SkScalar v) { return this->set(kMSkewX, v); } + + /** Sets horizontal translation. + + @param v horizontal translation to store + */ + SkMatrix& setTranslateX(SkScalar v) { return this->set(kMTransX, v); } + + /** Sets vertical translation. + + @param v vertical translation to store + */ + SkMatrix& setTranslateY(SkScalar v) { return this->set(kMTransY, v); } + + /** Sets input x-axis perspective factor, which causes mapXY() to vary input x-axis values + inversely proportional to input y-axis values. + + @param v perspective factor + */ + SkMatrix& setPerspX(SkScalar v) { return this->set(kMPersp0, v); } + + /** Sets input y-axis perspective factor, which causes mapXY() to vary input y-axis values + inversely proportional to input x-axis values. + + @param v perspective factor + */ + SkMatrix& setPerspY(SkScalar v) { return this->set(kMPersp1, v); } + + /** Sets all values from parameters. Sets matrix to: + + | scaleX skewX transX | + | skewY scaleY transY | + | persp0 persp1 persp2 | + + @param scaleX horizontal scale factor to store + @param skewX horizontal skew factor to store + @param transX horizontal translation to store + @param skewY vertical skew factor to store + @param scaleY vertical scale factor to store + @param transY vertical translation to store + @param persp0 input x-axis values perspective factor to store + @param persp1 input y-axis values perspective factor to store + @param persp2 perspective scale factor to store + */ + SkMatrix& setAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, + SkScalar skewY, SkScalar scaleY, SkScalar transY, + SkScalar persp0, SkScalar persp1, SkScalar persp2) { + fMat[kMScaleX] = scaleX; + fMat[kMSkewX] = skewX; + fMat[kMTransX] = transX; + fMat[kMSkewY] = skewY; + fMat[kMScaleY] = scaleY; + fMat[kMTransY] = transY; + fMat[kMPersp0] = persp0; + fMat[kMPersp1] = persp1; + fMat[kMPersp2] = persp2; + this->setTypeMask(kUnknown_Mask); + return *this; + } + + /** Copies nine scalar values contained by SkMatrix into buffer, in member value + ascending order: kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, + kMPersp0, kMPersp1, kMPersp2. + + @param buffer storage for nine scalar values + */ + void get9(SkScalar buffer[9]) const { + memcpy(buffer, fMat, 9 * sizeof(SkScalar)); + } + + /** Sets SkMatrix to nine scalar values in buffer, in member value ascending order: + kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, kMPersp0, kMPersp1, + kMPersp2. + + Sets matrix to: + + | buffer[0] buffer[1] buffer[2] | + | buffer[3] buffer[4] buffer[5] | + | buffer[6] buffer[7] buffer[8] | + + In the future, set9 followed by get9 may not return the same values. Since SkMatrix + maps non-homogeneous coordinates, scaling all nine values produces an equivalent + transformation, possibly improving precision. + + @param buffer nine scalar values + */ + SkMatrix& set9(const SkScalar buffer[9]); + + /** Sets SkMatrix to identity; which has no effect on mapped SkPoint. Sets SkMatrix to: + + | 1 0 0 | + | 0 1 0 | + | 0 0 1 | + + Also called setIdentity(); use the one that provides better inline + documentation. + */ + SkMatrix& reset(); + + /** Sets SkMatrix to identity; which has no effect on mapped SkPoint. Sets SkMatrix to: + + | 1 0 0 | + | 0 1 0 | + | 0 0 1 | + + Also called reset(); use the one that provides better inline + documentation. + */ + SkMatrix& setIdentity() { return this->reset(); } + + /** Sets SkMatrix to translate by (dx, dy). + + @param dx horizontal translation + @param dy vertical translation + */ + SkMatrix& setTranslate(SkScalar dx, SkScalar dy); + + /** Sets SkMatrix to translate by (v.fX, v.fY). + + @param v vector containing horizontal and vertical translation + */ + SkMatrix& setTranslate(const SkVector& v) { return this->setTranslate(v.fX, v.fY); } + + /** Sets SkMatrix to scale by sx and sy, about a pivot point at (px, py). + The pivot point is unchanged when mapped with SkMatrix. + + @param sx horizontal scale factor + @param sy vertical scale factor + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py); + + /** Sets SkMatrix to scale by sx and sy about at pivot point at (0, 0). + + @param sx horizontal scale factor + @param sy vertical scale factor + */ + SkMatrix& setScale(SkScalar sx, SkScalar sy); + + /** Sets SkMatrix to rotate by degrees about a pivot point at (px, py). + The pivot point is unchanged when mapped with SkMatrix. + + Positive degrees rotates clockwise. + + @param degrees angle of axes relative to upright axes + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& setRotate(SkScalar degrees, SkScalar px, SkScalar py); + + /** Sets SkMatrix to rotate by degrees about a pivot point at (0, 0). + Positive degrees rotates clockwise. + + @param degrees angle of axes relative to upright axes + */ + SkMatrix& setRotate(SkScalar degrees); + + /** Sets SkMatrix to rotate by sinValue and cosValue, about a pivot point at (px, py). + The pivot point is unchanged when mapped with SkMatrix. + + Vector (sinValue, cosValue) describes the angle of rotation relative to (0, 1). + Vector length specifies scale. + + @param sinValue rotation vector x-axis component + @param cosValue rotation vector y-axis component + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& setSinCos(SkScalar sinValue, SkScalar cosValue, + SkScalar px, SkScalar py); + + /** Sets SkMatrix to rotate by sinValue and cosValue, about a pivot point at (0, 0). + + Vector (sinValue, cosValue) describes the angle of rotation relative to (0, 1). + Vector length specifies scale. + + @param sinValue rotation vector x-axis component + @param cosValue rotation vector y-axis component + */ + SkMatrix& setSinCos(SkScalar sinValue, SkScalar cosValue); + + /** Sets SkMatrix to rotate, scale, and translate using a compressed matrix form. + + Vector (rsxForm.fSSin, rsxForm.fSCos) describes the angle of rotation relative + to (0, 1). Vector length specifies scale. Mapped point is rotated and scaled + by vector, then translated by (rsxForm.fTx, rsxForm.fTy). + + @param rsxForm compressed SkRSXform matrix + @return reference to SkMatrix + */ + SkMatrix& setRSXform(const SkRSXform& rsxForm); + + /** Sets SkMatrix to skew by kx and ky, about a pivot point at (px, py). + The pivot point is unchanged when mapped with SkMatrix. + + @param kx horizontal skew factor + @param ky vertical skew factor + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& setSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py); + + /** Sets SkMatrix to skew by kx and ky, about a pivot point at (0, 0). + + @param kx horizontal skew factor + @param ky vertical skew factor + */ + SkMatrix& setSkew(SkScalar kx, SkScalar ky); + + /** Sets SkMatrix to SkMatrix a multiplied by SkMatrix b. Either a or b may be this. + + Given: + + | A B C | | J K L | + a = | D E F |, b = | M N O | + | G H I | | P Q R | + + sets SkMatrix to: + + | A B C | | J K L | | AJ+BM+CP AK+BN+CQ AL+BO+CR | + a * b = | D E F | * | M N O | = | DJ+EM+FP DK+EN+FQ DL+EO+FR | + | G H I | | P Q R | | GJ+HM+IP GK+HN+IQ GL+HO+IR | + + @param a SkMatrix on left side of multiply expression + @param b SkMatrix on right side of multiply expression + */ + SkMatrix& setConcat(const SkMatrix& a, const SkMatrix& b); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from translation (dx, dy). + This can be thought of as moving the point to be mapped before applying SkMatrix. + + Given: + + | A B C | | 1 0 dx | + Matrix = | D E F |, T(dx, dy) = | 0 1 dy | + | G H I | | 0 0 1 | + + sets SkMatrix to: + + | A B C | | 1 0 dx | | A B A*dx+B*dy+C | + Matrix * T(dx, dy) = | D E F | | 0 1 dy | = | D E D*dx+E*dy+F | + | G H I | | 0 0 1 | | G H G*dx+H*dy+I | + + @param dx x-axis translation before applying SkMatrix + @param dy y-axis translation before applying SkMatrix + */ + SkMatrix& preTranslate(SkScalar dx, SkScalar dy); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from scaling by (sx, sy) + about pivot point (px, py). + This can be thought of as scaling about a pivot point before applying SkMatrix. + + Given: + + | A B C | | sx 0 dx | + Matrix = | D E F |, S(sx, sy, px, py) = | 0 sy dy | + | G H I | | 0 0 1 | + + where + + dx = px - sx * px + dy = py - sy * py + + sets SkMatrix to: + + | A B C | | sx 0 dx | | A*sx B*sy A*dx+B*dy+C | + Matrix * S(sx, sy, px, py) = | D E F | | 0 sy dy | = | D*sx E*sy D*dx+E*dy+F | + | G H I | | 0 0 1 | | G*sx H*sy G*dx+H*dy+I | + + @param sx horizontal scale factor + @param sy vertical scale factor + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from scaling by (sx, sy) + about pivot point (0, 0). + This can be thought of as scaling about the origin before applying SkMatrix. + + Given: + + | A B C | | sx 0 0 | + Matrix = | D E F |, S(sx, sy) = | 0 sy 0 | + | G H I | | 0 0 1 | + + sets SkMatrix to: + + | A B C | | sx 0 0 | | A*sx B*sy C | + Matrix * S(sx, sy) = | D E F | | 0 sy 0 | = | D*sx E*sy F | + | G H I | | 0 0 1 | | G*sx H*sy I | + + @param sx horizontal scale factor + @param sy vertical scale factor + */ + SkMatrix& preScale(SkScalar sx, SkScalar sy); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from rotating by degrees + about pivot point (px, py). + This can be thought of as rotating about a pivot point before applying SkMatrix. + + Positive degrees rotates clockwise. + + Given: + + | A B C | | c -s dx | + Matrix = | D E F |, R(degrees, px, py) = | s c dy | + | G H I | | 0 0 1 | + + where + + c = cos(degrees) + s = sin(degrees) + dx = s * py + (1 - c) * px + dy = -s * px + (1 - c) * py + + sets SkMatrix to: + + | A B C | | c -s dx | | Ac+Bs -As+Bc A*dx+B*dy+C | + Matrix * R(degrees, px, py) = | D E F | | s c dy | = | Dc+Es -Ds+Ec D*dx+E*dy+F | + | G H I | | 0 0 1 | | Gc+Hs -Gs+Hc G*dx+H*dy+I | + + @param degrees angle of axes relative to upright axes + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& preRotate(SkScalar degrees, SkScalar px, SkScalar py); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from rotating by degrees + about pivot point (0, 0). + This can be thought of as rotating about the origin before applying SkMatrix. + + Positive degrees rotates clockwise. + + Given: + + | A B C | | c -s 0 | + Matrix = | D E F |, R(degrees, px, py) = | s c 0 | + | G H I | | 0 0 1 | + + where + + c = cos(degrees) + s = sin(degrees) + + sets SkMatrix to: + + | A B C | | c -s 0 | | Ac+Bs -As+Bc C | + Matrix * R(degrees, px, py) = | D E F | | s c 0 | = | Dc+Es -Ds+Ec F | + | G H I | | 0 0 1 | | Gc+Hs -Gs+Hc I | + + @param degrees angle of axes relative to upright axes + */ + SkMatrix& preRotate(SkScalar degrees); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from skewing by (kx, ky) + about pivot point (px, py). + This can be thought of as skewing about a pivot point before applying SkMatrix. + + Given: + + | A B C | | 1 kx dx | + Matrix = | D E F |, K(kx, ky, px, py) = | ky 1 dy | + | G H I | | 0 0 1 | + + where + + dx = -kx * py + dy = -ky * px + + sets SkMatrix to: + + | A B C | | 1 kx dx | | A+B*ky A*kx+B A*dx+B*dy+C | + Matrix * K(kx, ky, px, py) = | D E F | | ky 1 dy | = | D+E*ky D*kx+E D*dx+E*dy+F | + | G H I | | 0 0 1 | | G+H*ky G*kx+H G*dx+H*dy+I | + + @param kx horizontal skew factor + @param ky vertical skew factor + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& preSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix constructed from skewing by (kx, ky) + about pivot point (0, 0). + This can be thought of as skewing about the origin before applying SkMatrix. + + Given: + + | A B C | | 1 kx 0 | + Matrix = | D E F |, K(kx, ky) = | ky 1 0 | + | G H I | | 0 0 1 | + + sets SkMatrix to: + + | A B C | | 1 kx 0 | | A+B*ky A*kx+B C | + Matrix * K(kx, ky) = | D E F | | ky 1 0 | = | D+E*ky D*kx+E F | + | G H I | | 0 0 1 | | G+H*ky G*kx+H I | + + @param kx horizontal skew factor + @param ky vertical skew factor + */ + SkMatrix& preSkew(SkScalar kx, SkScalar ky); + + /** Sets SkMatrix to SkMatrix multiplied by SkMatrix other. + This can be thought of mapping by other before applying SkMatrix. + + Given: + + | A B C | | J K L | + Matrix = | D E F |, other = | M N O | + | G H I | | P Q R | + + sets SkMatrix to: + + | A B C | | J K L | | AJ+BM+CP AK+BN+CQ AL+BO+CR | + Matrix * other = | D E F | * | M N O | = | DJ+EM+FP DK+EN+FQ DL+EO+FR | + | G H I | | P Q R | | GJ+HM+IP GK+HN+IQ GL+HO+IR | + + @param other SkMatrix on right side of multiply expression + */ + SkMatrix& preConcat(const SkMatrix& other); + + /** Sets SkMatrix to SkMatrix constructed from translation (dx, dy) multiplied by SkMatrix. + This can be thought of as moving the point to be mapped after applying SkMatrix. + + Given: + + | J K L | | 1 0 dx | + Matrix = | M N O |, T(dx, dy) = | 0 1 dy | + | P Q R | | 0 0 1 | + + sets SkMatrix to: + + | 1 0 dx | | J K L | | J+dx*P K+dx*Q L+dx*R | + T(dx, dy) * Matrix = | 0 1 dy | | M N O | = | M+dy*P N+dy*Q O+dy*R | + | 0 0 1 | | P Q R | | P Q R | + + @param dx x-axis translation after applying SkMatrix + @param dy y-axis translation after applying SkMatrix + */ + SkMatrix& postTranslate(SkScalar dx, SkScalar dy); + + /** Sets SkMatrix to SkMatrix constructed from scaling by (sx, sy) about pivot point + (px, py), multiplied by SkMatrix. + This can be thought of as scaling about a pivot point after applying SkMatrix. + + Given: + + | J K L | | sx 0 dx | + Matrix = | M N O |, S(sx, sy, px, py) = | 0 sy dy | + | P Q R | | 0 0 1 | + + where + + dx = px - sx * px + dy = py - sy * py + + sets SkMatrix to: + + | sx 0 dx | | J K L | | sx*J+dx*P sx*K+dx*Q sx*L+dx+R | + S(sx, sy, px, py) * Matrix = | 0 sy dy | | M N O | = | sy*M+dy*P sy*N+dy*Q sy*O+dy*R | + | 0 0 1 | | P Q R | | P Q R | + + @param sx horizontal scale factor + @param sy vertical scale factor + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py); + + /** Sets SkMatrix to SkMatrix constructed from scaling by (sx, sy) about pivot point + (0, 0), multiplied by SkMatrix. + This can be thought of as scaling about the origin after applying SkMatrix. + + Given: + + | J K L | | sx 0 0 | + Matrix = | M N O |, S(sx, sy) = | 0 sy 0 | + | P Q R | | 0 0 1 | + + sets SkMatrix to: + + | sx 0 0 | | J K L | | sx*J sx*K sx*L | + S(sx, sy) * Matrix = | 0 sy 0 | | M N O | = | sy*M sy*N sy*O | + | 0 0 1 | | P Q R | | P Q R | + + @param sx horizontal scale factor + @param sy vertical scale factor + */ + SkMatrix& postScale(SkScalar sx, SkScalar sy); + + /** Sets SkMatrix to SkMatrix constructed from scaling by (1/divx, 1/divy), + about pivot point (px, py), multiplied by SkMatrix. + + Returns false if either divx or divy is zero. + + Given: + + | J K L | | sx 0 0 | + Matrix = | M N O |, I(divx, divy) = | 0 sy 0 | + | P Q R | | 0 0 1 | + + where + + sx = 1 / divx + sy = 1 / divy + + sets SkMatrix to: + + | sx 0 0 | | J K L | | sx*J sx*K sx*L | + I(divx, divy) * Matrix = | 0 sy 0 | | M N O | = | sy*M sy*N sy*O | + | 0 0 1 | | P Q R | | P Q R | + + @param divx integer divisor for inverse scale in x + @param divy integer divisor for inverse scale in y + @return true on successful scale + */ + bool postIDiv(int divx, int divy); + + /** Sets SkMatrix to SkMatrix constructed from rotating by degrees about pivot point + (px, py), multiplied by SkMatrix. + This can be thought of as rotating about a pivot point after applying SkMatrix. + + Positive degrees rotates clockwise. + + Given: + + | J K L | | c -s dx | + Matrix = | M N O |, R(degrees, px, py) = | s c dy | + | P Q R | | 0 0 1 | + + where + + c = cos(degrees) + s = sin(degrees) + dx = s * py + (1 - c) * px + dy = -s * px + (1 - c) * py + + sets SkMatrix to: + + |c -s dx| |J K L| |cJ-sM+dx*P cK-sN+dx*Q cL-sO+dx+R| + R(degrees, px, py) * Matrix = |s c dy| |M N O| = |sJ+cM+dy*P sK+cN+dy*Q sL+cO+dy*R| + |0 0 1| |P Q R| | P Q R| + + @param degrees angle of axes relative to upright axes + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& postRotate(SkScalar degrees, SkScalar px, SkScalar py); + + /** Sets SkMatrix to SkMatrix constructed from rotating by degrees about pivot point + (0, 0), multiplied by SkMatrix. + This can be thought of as rotating about the origin after applying SkMatrix. + + Positive degrees rotates clockwise. + + Given: + + | J K L | | c -s 0 | + Matrix = | M N O |, R(degrees, px, py) = | s c 0 | + | P Q R | | 0 0 1 | + + where + + c = cos(degrees) + s = sin(degrees) + + sets SkMatrix to: + + | c -s dx | | J K L | | cJ-sM cK-sN cL-sO | + R(degrees, px, py) * Matrix = | s c dy | | M N O | = | sJ+cM sK+cN sL+cO | + | 0 0 1 | | P Q R | | P Q R | + + @param degrees angle of axes relative to upright axes + */ + SkMatrix& postRotate(SkScalar degrees); + + /** Sets SkMatrix to SkMatrix constructed from skewing by (kx, ky) about pivot point + (px, py), multiplied by SkMatrix. + This can be thought of as skewing about a pivot point after applying SkMatrix. + + Given: + + | J K L | | 1 kx dx | + Matrix = | M N O |, K(kx, ky, px, py) = | ky 1 dy | + | P Q R | | 0 0 1 | + + where + + dx = -kx * py + dy = -ky * px + + sets SkMatrix to: + + | 1 kx dx| |J K L| |J+kx*M+dx*P K+kx*N+dx*Q L+kx*O+dx+R| + K(kx, ky, px, py) * Matrix = |ky 1 dy| |M N O| = |ky*J+M+dy*P ky*K+N+dy*Q ky*L+O+dy*R| + | 0 0 1| |P Q R| | P Q R| + + @param kx horizontal skew factor + @param ky vertical skew factor + @param px pivot on x-axis + @param py pivot on y-axis + */ + SkMatrix& postSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py); + + /** Sets SkMatrix to SkMatrix constructed from skewing by (kx, ky) about pivot point + (0, 0), multiplied by SkMatrix. + This can be thought of as skewing about the origin after applying SkMatrix. + + Given: + + | J K L | | 1 kx 0 | + Matrix = | M N O |, K(kx, ky) = | ky 1 0 | + | P Q R | | 0 0 1 | + + sets SkMatrix to: + + | 1 kx 0 | | J K L | | J+kx*M K+kx*N L+kx*O | + K(kx, ky) * Matrix = | ky 1 0 | | M N O | = | ky*J+M ky*K+N ky*L+O | + | 0 0 1 | | P Q R | | P Q R | + + @param kx horizontal skew factor + @param ky vertical skew factor + */ + SkMatrix& postSkew(SkScalar kx, SkScalar ky); + + /** Sets SkMatrix to SkMatrix other multiplied by SkMatrix. + This can be thought of mapping by other after applying SkMatrix. + + Given: + + | J K L | | A B C | + Matrix = | M N O |, other = | D E F | + | P Q R | | G H I | + + sets SkMatrix to: + + | A B C | | J K L | | AJ+BM+CP AK+BN+CQ AL+BO+CR | + other * Matrix = | D E F | * | M N O | = | DJ+EM+FP DK+EN+FQ DL+EO+FR | + | G H I | | P Q R | | GJ+HM+IP GK+HN+IQ GL+HO+IR | + + @param other SkMatrix on left side of multiply expression + */ + SkMatrix& postConcat(const SkMatrix& other); + + /** \enum SkMatrix::ScaleToFit + ScaleToFit describes how SkMatrix is constructed to map one SkRect to another. + ScaleToFit may allow SkMatrix to have unequal horizontal and vertical scaling, + or may restrict SkMatrix to square scaling. If restricted, ScaleToFit specifies + how SkMatrix maps to the side or center of the destination SkRect. + */ + enum ScaleToFit { + kFill_ScaleToFit, //!< scales in x and y to fill destination SkRect + kStart_ScaleToFit, //!< scales and aligns to left and top + kCenter_ScaleToFit, //!< scales and aligns to center + kEnd_ScaleToFit, //!< scales and aligns to right and bottom + }; + + /** Sets SkMatrix to scale and translate src SkRect to dst SkRect. stf selects whether + mapping completely fills dst or preserves the aspect ratio, and how to align + src within dst. Returns false if src is empty, and sets SkMatrix to identity. + Returns true if dst is empty, and sets SkMatrix to: + + | 0 0 0 | + | 0 0 0 | + | 0 0 1 | + + @param src SkRect to map from + @param dst SkRect to map to + @param stf one of: kFill_ScaleToFit, kStart_ScaleToFit, + kCenter_ScaleToFit, kEnd_ScaleToFit + @return true if SkMatrix can represent SkRect mapping + */ + bool setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf); + + /** Returns SkMatrix set to scale and translate src SkRect to dst SkRect. stf selects + whether mapping completely fills dst or preserves the aspect ratio, and how to + align src within dst. Returns the identity SkMatrix if src is empty. If dst is + empty, returns SkMatrix set to: + + | 0 0 0 | + | 0 0 0 | + | 0 0 1 | + + @param src SkRect to map from + @param dst SkRect to map to + @param stf one of: kFill_ScaleToFit, kStart_ScaleToFit, + kCenter_ScaleToFit, kEnd_ScaleToFit + @return SkMatrix mapping src to dst + */ + static SkMatrix MakeRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf) { + SkMatrix m; + m.setRectToRect(src, dst, stf); + return m; + } + + /** Sets SkMatrix to map src to dst. count must be zero or greater, and four or less. + + If count is zero, sets SkMatrix to identity and returns true. + If count is one, sets SkMatrix to translate and returns true. + If count is two or more, sets SkMatrix to map SkPoint if possible; returns false + if SkMatrix cannot be constructed. If count is four, SkMatrix may include + perspective. + + @param src SkPoint to map from + @param dst SkPoint to map to + @param count number of SkPoint in src and dst + @return true if SkMatrix was constructed successfully + */ + bool setPolyToPoly(const SkPoint src[], const SkPoint dst[], int count); + + /** Sets inverse to reciprocal matrix, returning true if SkMatrix can be inverted. + Geometrically, if SkMatrix maps from source to destination, inverse SkMatrix + maps from destination to source. If SkMatrix can not be inverted, inverse is + unchanged. + + @param inverse storage for inverted SkMatrix; may be nullptr + @return true if SkMatrix can be inverted + */ + bool SK_WARN_UNUSED_RESULT invert(SkMatrix* inverse) const { + // Allow the trivial case to be inlined. + if (this->isIdentity()) { + if (inverse) { + inverse->reset(); + } + return true; + } + return this->invertNonIdentity(inverse); + } + + /** Fills affine with identity values in column major order. + Sets affine to: + + | 1 0 0 | + | 0 1 0 | + + Affine 3 by 2 matrices in column major order are used by OpenGL and XPS. + + @param affine storage for 3 by 2 affine matrix + */ + static void SetAffineIdentity(SkScalar affine[6]); + + /** Fills affine in column major order. Sets affine to: + + | scale-x skew-x translate-x | + | skew-y scale-y translate-y | + + If SkMatrix contains perspective, returns false and leaves affine unchanged. + + @param affine storage for 3 by 2 affine matrix; may be nullptr + @return true if SkMatrix does not contain perspective + */ + bool SK_WARN_UNUSED_RESULT asAffine(SkScalar affine[6]) const; + + /** Sets SkMatrix to affine values, passed in column major order. Given affine, + column, then row, as: + + | scale-x skew-x translate-x | + | skew-y scale-y translate-y | + + SkMatrix is set, row, then column, to: + + | scale-x skew-x translate-x | + | skew-y scale-y translate-y | + | 0 0 1 | + + @param affine 3 by 2 affine matrix + */ + SkMatrix& setAffine(const SkScalar affine[6]); + + /** Maps src SkPoint array of length count to dst SkPoint array of equal or greater + length. SkPoint are mapped by multiplying each SkPoint by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, pt = | y | + | G H I | | 1 | + + where + + for (i = 0; i < count; ++i) { + x = src[i].fX + y = src[i].fY + } + + each dst SkPoint is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + src and dst may point to the same storage. + + @param dst storage for mapped SkPoint + @param src SkPoint to transform + @param count number of SkPoint to transform + */ + void mapPoints(SkPoint dst[], const SkPoint src[], int count) const; + + /** Maps pts SkPoint array of length count in place. SkPoint are mapped by multiplying + each SkPoint by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, pt = | y | + | G H I | | 1 | + + where + + for (i = 0; i < count; ++i) { + x = pts[i].fX + y = pts[i].fY + } + + each resulting pts SkPoint is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param pts storage for mapped SkPoint + @param count number of SkPoint to transform + */ + void mapPoints(SkPoint pts[], int count) const { + this->mapPoints(pts, pts, count); + } + + /** Maps src SkPoint3 array of length count to dst SkPoint3 array, which must of length count or + greater. SkPoint3 array is mapped by multiplying each SkPoint3 by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, src = | y | + | G H I | | z | + + each resulting dst SkPoint is computed as: + + |A B C| |x| + Matrix * src = |D E F| |y| = |Ax+By+Cz Dx+Ey+Fz Gx+Hy+Iz| + |G H I| |z| + + @param dst storage for mapped SkPoint3 array + @param src SkPoint3 array to transform + @param count items in SkPoint3 array to transform + */ + void mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const; + + /** Maps SkPoint (x, y) to result. SkPoint is mapped by multiplying by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, pt = | y | + | G H I | | 1 | + + result is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param x x-axis value of SkPoint to map + @param y y-axis value of SkPoint to map + @param result storage for mapped SkPoint + */ + void mapXY(SkScalar x, SkScalar y, SkPoint* result) const; + + /** Returns SkPoint (x, y) multiplied by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, pt = | y | + | G H I | | 1 | + + result is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param x x-axis value of SkPoint to map + @param y y-axis value of SkPoint to map + @return mapped SkPoint + */ + SkPoint mapXY(SkScalar x, SkScalar y) const { + SkPoint result; + this->mapXY(x,y, &result); + return result; + } + + /** Maps src vector array of length count to vector SkPoint array of equal or greater + length. Vectors are mapped by multiplying each vector by SkMatrix, treating + SkMatrix translation as zero. Given: + + | A B 0 | | x | + Matrix = | D E 0 |, src = | y | + | G H I | | 1 | + + where + + for (i = 0; i < count; ++i) { + x = src[i].fX + y = src[i].fY + } + + each dst vector is computed as: + + |A B 0| |x| Ax+By Dx+Ey + Matrix * src = |D E 0| |y| = |Ax+By Dx+Ey Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + src and dst may point to the same storage. + + @param dst storage for mapped vectors + @param src vectors to transform + @param count number of vectors to transform + */ + void mapVectors(SkVector dst[], const SkVector src[], int count) const; + + /** Maps vecs vector array of length count in place, multiplying each vector by + SkMatrix, treating SkMatrix translation as zero. Given: + + | A B 0 | | x | + Matrix = | D E 0 |, vec = | y | + | G H I | | 1 | + + where + + for (i = 0; i < count; ++i) { + x = vecs[i].fX + y = vecs[i].fY + } + + each result vector is computed as: + + |A B 0| |x| Ax+By Dx+Ey + Matrix * vec = |D E 0| |y| = |Ax+By Dx+Ey Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param vecs vectors to transform, and storage for mapped vectors + @param count number of vectors to transform + */ + void mapVectors(SkVector vecs[], int count) const { + this->mapVectors(vecs, vecs, count); + } + + /** Maps vector (dx, dy) to result. Vector is mapped by multiplying by SkMatrix, + treating SkMatrix translation as zero. Given: + + | A B 0 | | dx | + Matrix = | D E 0 |, vec = | dy | + | G H I | | 1 | + + each result vector is computed as: + + |A B 0| |dx| A*dx+B*dy D*dx+E*dy + Matrix * vec = |D E 0| |dy| = |A*dx+B*dy D*dx+E*dy G*dx+H*dy+I| = ----------- , ----------- + |G H I| | 1| G*dx+H*dy+I G*dx+*dHy+I + + @param dx x-axis value of vector to map + @param dy y-axis value of vector to map + @param result storage for mapped vector + */ + void mapVector(SkScalar dx, SkScalar dy, SkVector* result) const { + SkVector vec = { dx, dy }; + this->mapVectors(result, &vec, 1); + } + + /** Returns vector (dx, dy) multiplied by SkMatrix, treating SkMatrix translation as zero. + Given: + + | A B 0 | | dx | + Matrix = | D E 0 |, vec = | dy | + | G H I | | 1 | + + each result vector is computed as: + + |A B 0| |dx| A*dx+B*dy D*dx+E*dy + Matrix * vec = |D E 0| |dy| = |A*dx+B*dy D*dx+E*dy G*dx+H*dy+I| = ----------- , ----------- + |G H I| | 1| G*dx+H*dy+I G*dx+*dHy+I + + @param dx x-axis value of vector to map + @param dy y-axis value of vector to map + @return mapped vector + */ + SkVector mapVector(SkScalar dx, SkScalar dy) const { + SkVector vec = { dx, dy }; + this->mapVectors(&vec, &vec, 1); + return vec; + } + + /** Sets dst to bounds of src corners mapped by SkMatrix. + Returns true if mapped corners are dst corners. + + Returned value is the same as calling rectStaysRect(). + + @param dst storage for bounds of mapped SkPoint + @param src SkRect to map + @return true if dst is equivalent to mapped src + */ + bool mapRect(SkRect* dst, const SkRect& src) const; + + /** Sets rect to bounds of rect corners mapped by SkMatrix. + Returns true if mapped corners are computed rect corners. + + Returned value is the same as calling rectStaysRect(). + + @param rect rectangle to map, and storage for bounds of mapped corners + @return true if result is equivalent to mapped rect + */ + bool mapRect(SkRect* rect) const { + return this->mapRect(rect, *rect); + } + + /** Returns bounds of src corners mapped by SkMatrix. + + @param src rectangle to map + @return mapped bounds + */ + SkRect mapRect(const SkRect& src) const { + SkRect dst; + (void)this->mapRect(&dst, src); + return dst; + } + + /** Maps four corners of rect to dst. SkPoint are mapped by multiplying each + rect corner by SkMatrix. rect corner is processed in this order: + (rect.fLeft, rect.fTop), (rect.fRight, rect.fTop), (rect.fRight, rect.fBottom), + (rect.fLeft, rect.fBottom). + + rect may be empty: rect.fLeft may be greater than or equal to rect.fRight; + rect.fTop may be greater than or equal to rect.fBottom. + + Given: + + | A B C | | x | + Matrix = | D E F |, pt = | y | + | G H I | | 1 | + + where pt is initialized from each of (rect.fLeft, rect.fTop), + (rect.fRight, rect.fTop), (rect.fRight, rect.fBottom), (rect.fLeft, rect.fBottom), + each dst SkPoint is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param dst storage for mapped corner SkPoint + @param rect SkRect to map + */ + void mapRectToQuad(SkPoint dst[4], const SkRect& rect) const { + // This could potentially be faster if we only transformed each x and y of the rect once. + rect.toQuad(dst); + this->mapPoints(dst, 4); + } + + /** Sets dst to bounds of src corners mapped by SkMatrix. If matrix contains + elements other than scale or translate: asserts if SK_DEBUG is defined; + otherwise, results are undefined. + + @param dst storage for bounds of mapped SkPoint + @param src SkRect to map + */ + void mapRectScaleTranslate(SkRect* dst, const SkRect& src) const; + + /** Returns geometric mean radius of ellipse formed by constructing circle of + size radius, and mapping constructed circle with SkMatrix. The result squared is + equal to the major axis length times the minor axis length. + Result is not meaningful if SkMatrix contains perspective elements. + + @param radius circle size to map + @return average mapped radius + */ + SkScalar mapRadius(SkScalar radius) const; + + /** Returns true if a unit step on x-axis at some y-axis value mapped through SkMatrix + can be represented by a constant vector. Returns true if getType() returns + kIdentity_Mask, or combinations of: kTranslate_Mask, kScale_Mask, and kAffine_Mask. + + May return true if getType() returns kPerspective_Mask, but only when SkMatrix + does not include rotation or skewing along the y-axis. + + @return true if SkMatrix does not have complex perspective + */ + bool isFixedStepInX() const; + + /** Returns vector representing a unit step on x-axis at y mapped through SkMatrix. + If isFixedStepInX() is false, returned value is undefined. + + @param y position of line parallel to x-axis + @return vector advance of mapped unit step on x-axis + */ + SkVector fixedStepInX(SkScalar y) const; + + /** Returns true if SkMatrix equals m, using an efficient comparison. + + Returns false when the sign of zero values is the different; when one + matrix has positive zero value and the other has negative zero value. + + Returns true even when both SkMatrix contain NaN. + + NaN never equals any value, including itself. To improve performance, NaN values + are treated as bit patterns that are equal if their bit patterns are equal. + + @param m SkMatrix to compare + @return true if m and SkMatrix are represented by identical bit patterns + */ + bool cheapEqualTo(const SkMatrix& m) const { + return 0 == memcmp(fMat, m.fMat, sizeof(fMat)); + } + + /** Compares a and b; returns true if a and b are numerically equal. Returns true + even if sign of zero values are different. Returns false if either SkMatrix + contains NaN, even if the other SkMatrix also contains NaN. + + @param a SkMatrix to compare + @param b SkMatrix to compare + @return true if SkMatrix a and SkMatrix b are numerically equal + */ + friend SK_API bool operator==(const SkMatrix& a, const SkMatrix& b); + + /** Compares a and b; returns true if a and b are not numerically equal. Returns false + even if sign of zero values are different. Returns true if either SkMatrix + contains NaN, even if the other SkMatrix also contains NaN. + + @param a SkMatrix to compare + @param b SkMatrix to compare + @return true if SkMatrix a and SkMatrix b are numerically not equal + */ + friend SK_API bool operator!=(const SkMatrix& a, const SkMatrix& b) { + return !(a == b); + } + + /** Writes text representation of SkMatrix to standard output. Floating point values + are written with limited precision; it may not be possible to reconstruct + original SkMatrix from output. + */ + void dump() const; + + /** Returns the minimum scaling factor of SkMatrix by decomposing the scaling and + skewing elements. + Returns -1 if scale factor overflows or SkMatrix contains perspective. + + @return minimum scale factor + */ + SkScalar getMinScale() const; + + /** Returns the maximum scaling factor of SkMatrix by decomposing the scaling and + skewing elements. + Returns -1 if scale factor overflows or SkMatrix contains perspective. + + @return maximum scale factor + */ + SkScalar getMaxScale() const; + + /** Sets scaleFactors[0] to the minimum scaling factor, and scaleFactors[1] to the + maximum scaling factor. Scaling factors are computed by decomposing + the SkMatrix scaling and skewing elements. + + Returns true if scaleFactors are found; otherwise, returns false and sets + scaleFactors to undefined values. + + @param scaleFactors storage for minimum and maximum scale factors + @return true if scale factors were computed correctly + */ + bool SK_WARN_UNUSED_RESULT getMinMaxScales(SkScalar scaleFactors[2]) const; + + /** Decomposes SkMatrix into scale components and whatever remains. Returns false if + SkMatrix could not be decomposed. + + Sets scale to portion of SkMatrix that scale axes. Sets remaining to SkMatrix + with scaling factored out. remaining may be passed as nullptr + to determine if SkMatrix can be decomposed without computing remainder. + + Returns true if scale components are found. scale and remaining are + unchanged if SkMatrix contains perspective; scale factors are not finite, or + are nearly zero. + + On success: Matrix = Remaining * scale. + + @param scale axes scaling factors; may be nullptr + @param remaining SkMatrix without scaling; may be nullptr + @return true if scale can be computed + */ + bool decomposeScale(SkSize* scale, SkMatrix* remaining = nullptr) const; + + /** Returns reference to const identity SkMatrix. Returned SkMatrix is set to: + + | 1 0 0 | + | 0 1 0 | + | 0 0 1 | + + @return const identity SkMatrix + */ + static const SkMatrix& I(); + + /** Returns reference to a const SkMatrix with invalid values. Returned SkMatrix is set + to: + + | SK_ScalarMax SK_ScalarMax SK_ScalarMax | + | SK_ScalarMax SK_ScalarMax SK_ScalarMax | + | SK_ScalarMax SK_ScalarMax SK_ScalarMax | + + @return const invalid SkMatrix + */ + static const SkMatrix& InvalidMatrix(); + + /** Returns SkMatrix a multiplied by SkMatrix b. + + Given: + + | A B C | | J K L | + a = | D E F |, b = | M N O | + | G H I | | P Q R | + + sets SkMatrix to: + + | A B C | | J K L | | AJ+BM+CP AK+BN+CQ AL+BO+CR | + a * b = | D E F | * | M N O | = | DJ+EM+FP DK+EN+FQ DL+EO+FR | + | G H I | | P Q R | | GJ+HM+IP GK+HN+IQ GL+HO+IR | + + @param a SkMatrix on left side of multiply expression + @param b SkMatrix on right side of multiply expression + @return SkMatrix computed from a times b + */ + static SkMatrix Concat(const SkMatrix& a, const SkMatrix& b) { + SkMatrix result; + result.setConcat(a, b); + return result; + } + + /** Sets internal cache to unknown state. Use to force update after repeated + modifications to SkMatrix element reference returned by operator[](int index). + */ + void dirtyMatrixTypeCache() { + this->setTypeMask(kUnknown_Mask); + } + + /** Initializes SkMatrix with scale and translate elements. + + | sx 0 tx | + | 0 sy ty | + | 0 0 1 | + + @param sx horizontal scale factor to store + @param sy vertical scale factor to store + @param tx horizontal translation to store + @param ty vertical translation to store + */ + void setScaleTranslate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty) { + fMat[kMScaleX] = sx; + fMat[kMSkewX] = 0; + fMat[kMTransX] = tx; + + fMat[kMSkewY] = 0; + fMat[kMScaleY] = sy; + fMat[kMTransY] = ty; + + fMat[kMPersp0] = 0; + fMat[kMPersp1] = 0; + fMat[kMPersp2] = 1; + + unsigned mask = 0; + if (sx != 1 || sy != 1) { + mask |= kScale_Mask; + } + if (tx || ty) { + mask |= kTranslate_Mask; + } + this->setTypeMask(mask | kRectStaysRect_Mask); + } + + /** Returns true if all elements of the matrix are finite. Returns false if any + element is infinity, or NaN. + + @return true if matrix has only finite elements + */ + bool isFinite() const { return SkScalarsAreFinite(fMat, 9); } + +private: + /** Set if the matrix will map a rectangle to another rectangle. This + can be true if the matrix is scale-only, or rotates a multiple of + 90 degrees. + + This bit will be set on identity matrices + */ + static constexpr int kRectStaysRect_Mask = 0x10; + + /** Set if the perspective bit is valid even though the rest of + the matrix is Unknown. + */ + static constexpr int kOnlyPerspectiveValid_Mask = 0x40; + + static constexpr int kUnknown_Mask = 0x80; + + static constexpr int kORableMasks = kTranslate_Mask | + kScale_Mask | + kAffine_Mask | + kPerspective_Mask; + + static constexpr int kAllMasks = kTranslate_Mask | + kScale_Mask | + kAffine_Mask | + kPerspective_Mask | + kRectStaysRect_Mask; + + SkScalar fMat[9]; + mutable uint32_t fTypeMask; + + constexpr SkMatrix(SkScalar sx, SkScalar kx, SkScalar tx, + SkScalar ky, SkScalar sy, SkScalar ty, + SkScalar p0, SkScalar p1, SkScalar p2, uint32_t typeMask) + : fMat{sx, kx, tx, + ky, sy, ty, + p0, p1, p2} + , fTypeMask(typeMask) {} + + static void ComputeInv(SkScalar dst[9], const SkScalar src[9], double invDet, bool isPersp); + + uint8_t computeTypeMask() const; + uint8_t computePerspectiveTypeMask() const; + + void setTypeMask(int mask) { + // allow kUnknown or a valid mask + SkASSERT(kUnknown_Mask == mask || (mask & kAllMasks) == mask || + ((kUnknown_Mask | kOnlyPerspectiveValid_Mask) & mask) + == (kUnknown_Mask | kOnlyPerspectiveValid_Mask)); + fTypeMask = SkToU8(mask); + } + + void orTypeMask(int mask) { + SkASSERT((mask & kORableMasks) == mask); + fTypeMask = SkToU8(fTypeMask | mask); + } + + void clearTypeMask(int mask) { + // only allow a valid mask + SkASSERT((mask & kAllMasks) == mask); + fTypeMask = fTypeMask & ~mask; + } + + TypeMask getPerspectiveTypeMaskOnly() const { + if ((fTypeMask & kUnknown_Mask) && + !(fTypeMask & kOnlyPerspectiveValid_Mask)) { + fTypeMask = this->computePerspectiveTypeMask(); + } + return (TypeMask)(fTypeMask & 0xF); + } + + /** Returns true if we already know that the matrix is identity; + false otherwise. + */ + bool isTriviallyIdentity() const { + if (fTypeMask & kUnknown_Mask) { + return false; + } + return ((fTypeMask & 0xF) == 0); + } + + inline void updateTranslateMask() { + if ((fMat[kMTransX] != 0) | (fMat[kMTransY] != 0)) { + fTypeMask |= kTranslate_Mask; + } else { + fTypeMask &= ~kTranslate_Mask; + } + } + + typedef void (*MapXYProc)(const SkMatrix& mat, SkScalar x, SkScalar y, + SkPoint* result); + + static MapXYProc GetMapXYProc(TypeMask mask) { + SkASSERT((mask & ~kAllMasks) == 0); + return gMapXYProcs[mask & kAllMasks]; + } + + MapXYProc getMapXYProc() const { + return GetMapXYProc(this->getType()); + } + + typedef void (*MapPtsProc)(const SkMatrix& mat, SkPoint dst[], + const SkPoint src[], int count); + + static MapPtsProc GetMapPtsProc(TypeMask mask) { + SkASSERT((mask & ~kAllMasks) == 0); + return gMapPtsProcs[mask & kAllMasks]; + } + + MapPtsProc getMapPtsProc() const { + return GetMapPtsProc(this->getType()); + } + + bool SK_WARN_UNUSED_RESULT invertNonIdentity(SkMatrix* inverse) const; + + static bool Poly2Proc(const SkPoint[], SkMatrix*); + static bool Poly3Proc(const SkPoint[], SkMatrix*); + static bool Poly4Proc(const SkPoint[], SkMatrix*); + + static void Identity_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + static void Trans_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + static void Scale_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + static void ScaleTrans_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + static void Rot_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + static void RotTrans_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + static void Persp_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); + + static const MapXYProc gMapXYProcs[]; + + static void Identity_pts(const SkMatrix&, SkPoint[], const SkPoint[], int); + static void Trans_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); + static void Scale_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); + static void ScaleTrans_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], + int count); + static void Persp_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); + + static void Affine_vpts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); + + static const MapPtsProc gMapPtsProcs[]; + + // return the number of bytes written, whether or not buffer is null + size_t writeToMemory(void* buffer) const; + /** + * Reads data from the buffer parameter + * + * @param buffer Memory to read from + * @param length Amount of memory available in the buffer + * @return number of bytes read (must be a multiple of 4) or + * 0 if there was not enough memory available + */ + size_t readFromMemory(const void* buffer, size_t length); + + friend class SkPerspIter; + friend class SkMatrixPriv; + friend class SkReader32; + friend class SerializationTest; +}; +SK_END_REQUIRE_DENSE + +#endif diff --git a/gfx/skia/skia/include/core/SkMatrix44.h b/gfx/skia/skia/include/core/SkMatrix44.h new file mode 100644 index 0000000000..4e17b5f1ba --- /dev/null +++ b/gfx/skia/skia/include/core/SkMatrix44.h @@ -0,0 +1,480 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMatrix44_DEFINED +#define SkMatrix44_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkScalar.h" + +#include +#include + +#ifdef SK_MSCALAR_IS_DOUBLE +#ifdef SK_MSCALAR_IS_FLOAT + #error "can't define MSCALAR both as DOUBLE and FLOAT" +#endif + typedef double SkMScalar; + + static inline double SkFloatToMScalar(float x) { + return static_cast(x); + } + static inline float SkMScalarToFloat(double x) { + return static_cast(x); + } + static inline double SkDoubleToMScalar(double x) { + return x; + } + static inline double SkMScalarToDouble(double x) { + return x; + } + static inline double SkMScalarAbs(double x) { + return fabs(x); + } + static const SkMScalar SK_MScalarPI = 3.141592653589793; + static const SkMScalar SK_MScalarNaN = SK_DoubleNaN; + + #define SkMScalarFloor(x) sk_double_floor(x) + #define SkMScalarCeil(x) sk_double_ceil(x) + #define SkMScalarRound(x) sk_double_round(x) + + #define SkMScalarFloorToInt(x) sk_double_floor2int(x) + #define SkMScalarCeilToInt(x) sk_double_ceil2int(x) + #define SkMScalarRoundToInt(x) sk_double_round2int(x) + + +#elif defined SK_MSCALAR_IS_FLOAT +#ifdef SK_MSCALAR_IS_DOUBLE + #error "can't define MSCALAR both as DOUBLE and FLOAT" +#endif + typedef float SkMScalar; + + static inline float SkFloatToMScalar(float x) { + return x; + } + static inline float SkMScalarToFloat(float x) { + return x; + } + static inline float SkDoubleToMScalar(double x) { + return sk_double_to_float(x); + } + static inline double SkMScalarToDouble(float x) { + return static_cast(x); + } + static inline float SkMScalarAbs(float x) { + return sk_float_abs(x); + } + static const SkMScalar SK_MScalarPI = 3.14159265f; + static const SkMScalar SK_MScalarNaN = SK_FloatNaN; + + #define SkMScalarFloor(x) sk_float_floor(x) + #define SkMScalarCeil(x) sk_float_ceil(x) + #define SkMScalarRound(x) sk_float_round(x) + + #define SkMScalarFloorToInt(x) sk_float_floor2int(x) + #define SkMScalarCeilToInt(x) sk_float_ceil2int(x) + #define SkMScalarRoundToInt(x) sk_float_round2int(x) + +#endif + +#define SkIntToMScalar(n) static_cast(n) + +#define SkMScalarToScalar(x) SkMScalarToFloat(x) +#define SkScalarToMScalar(x) SkFloatToMScalar(x) + +static const SkMScalar SK_MScalar1 = 1; + +/////////////////////////////////////////////////////////////////////////////// + +struct SkVector4 { + SkScalar fData[4]; + + SkVector4() { + this->set(0, 0, 0, 1); + } + SkVector4(const SkVector4& src) { + memcpy(fData, src.fData, sizeof(fData)); + } + SkVector4(SkScalar x, SkScalar y, SkScalar z, SkScalar w = SK_Scalar1) { + fData[0] = x; + fData[1] = y; + fData[2] = z; + fData[3] = w; + } + + SkVector4& operator=(const SkVector4& src) { + memcpy(fData, src.fData, sizeof(fData)); + return *this; + } + + bool operator==(const SkVector4& v) const { + return fData[0] == v.fData[0] && fData[1] == v.fData[1] && + fData[2] == v.fData[2] && fData[3] == v.fData[3]; + } + bool operator!=(const SkVector4& v) const { return !(*this == v); } + bool equals(SkScalar x, SkScalar y, SkScalar z, SkScalar w = SK_Scalar1) { + return fData[0] == x && fData[1] == y && + fData[2] == z && fData[3] == w; + } + + void set(SkScalar x, SkScalar y, SkScalar z, SkScalar w = SK_Scalar1) { + fData[0] = x; + fData[1] = y; + fData[2] = z; + fData[3] = w; + } +}; + +/** \class SkMatrix44 + + The SkMatrix44 class holds a 4x4 matrix. + +*/ +class SK_API SkMatrix44 { +public: + + enum Uninitialized_Constructor { + kUninitialized_Constructor + }; + enum Identity_Constructor { + kIdentity_Constructor + }; + enum NaN_Constructor { + kNaN_Constructor + }; + + SkMatrix44(Uninitialized_Constructor) {} // ironically, cannot be constexpr + + constexpr SkMatrix44(Identity_Constructor) + : fMat{{ 1, 0, 0, 0, }, + { 0, 1, 0, 0, }, + { 0, 0, 1, 0, }, + { 0, 0, 0, 1, }} + , fTypeMask(kIdentity_Mask) {} + + SkMatrix44(NaN_Constructor) + : fMat{{ SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN }, + { SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN }, + { SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN }, + { SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN }} + , fTypeMask(kTranslate_Mask | kScale_Mask | kAffine_Mask | kPerspective_Mask) {} + + constexpr SkMatrix44() : SkMatrix44{kIdentity_Constructor} {} + + SkMatrix44(const SkMatrix44& src) = default; + + SkMatrix44& operator=(const SkMatrix44& src) = default; + + SkMatrix44(const SkMatrix44& a, const SkMatrix44& b) { + this->setConcat(a, b); + } + + bool operator==(const SkMatrix44& other) const; + bool operator!=(const SkMatrix44& other) const { + return !(other == *this); + } + + /* When converting from SkMatrix44 to SkMatrix, the third row and + * column is dropped. When converting from SkMatrix to SkMatrix44 + * the third row and column remain as identity: + * [ a b c ] [ a b 0 c ] + * [ d e f ] -> [ d e 0 f ] + * [ g h i ] [ 0 0 1 0 ] + * [ g h 0 i ] + */ + SkMatrix44(const SkMatrix&); + SkMatrix44& operator=(const SkMatrix& src); + operator SkMatrix() const; + + /** + * Return a reference to a const identity matrix + */ + static const SkMatrix44& I(); + + using TypeMask = uint8_t; + enum : TypeMask { + kIdentity_Mask = 0, + kTranslate_Mask = 1 << 0, //!< set if the matrix has translation + kScale_Mask = 1 << 1, //!< set if the matrix has any scale != 1 + kAffine_Mask = 1 << 2, //!< set if the matrix skews or rotates + kPerspective_Mask = 1 << 3, //!< set if the matrix is in perspective + }; + + /** + * Returns a bitfield describing the transformations the matrix may + * perform. The bitfield is computed conservatively, so it may include + * false positives. For example, when kPerspective_Mask is true, all + * other bits may be set to true even in the case of a pure perspective + * transform. + */ + inline TypeMask getType() const { return fTypeMask; } + + /** + * Return true if the matrix is identity. + */ + inline bool isIdentity() const { + return kIdentity_Mask == this->getType(); + } + + /** + * Return true if the matrix contains translate or is identity. + */ + inline bool isTranslate() const { + return !(this->getType() & ~kTranslate_Mask); + } + + /** + * Return true if the matrix only contains scale or translate or is identity. + */ + inline bool isScaleTranslate() const { + return !(this->getType() & ~(kScale_Mask | kTranslate_Mask)); + } + + /** + * Returns true if the matrix only contains scale or is identity. + */ + inline bool isScale() const { + return !(this->getType() & ~kScale_Mask); + } + + inline bool hasPerspective() const { + return SkToBool(this->getType() & kPerspective_Mask); + } + + void setIdentity(); + inline void reset() { this->setIdentity();} + + /** + * get a value from the matrix. The row,col parameters work as follows: + * (0, 0) scale-x + * (0, 3) translate-x + * (3, 0) perspective-x + */ + inline SkMScalar get(int row, int col) const { + SkASSERT((unsigned)row <= 3); + SkASSERT((unsigned)col <= 3); + return fMat[col][row]; + } + + /** + * set a value in the matrix. The row,col parameters work as follows: + * (0, 0) scale-x + * (0, 3) translate-x + * (3, 0) perspective-x + */ + inline void set(int row, int col, SkMScalar value) { + SkASSERT((unsigned)row <= 3); + SkASSERT((unsigned)col <= 3); + fMat[col][row] = value; + this->recomputeTypeMask(); + } + + inline double getDouble(int row, int col) const { + return SkMScalarToDouble(this->get(row, col)); + } + inline void setDouble(int row, int col, double value) { + this->set(row, col, SkDoubleToMScalar(value)); + } + inline float getFloat(int row, int col) const { + return SkMScalarToFloat(this->get(row, col)); + } + inline void setFloat(int row, int col, float value) { + this->set(row, col, SkFloatToMScalar(value)); + } + + /** These methods allow one to efficiently read matrix entries into an + * array. The given array must have room for exactly 16 entries. Whenever + * possible, they will try to use memcpy rather than an entry-by-entry + * copy. + * + * Col major indicates that consecutive elements of columns will be stored + * contiguously in memory. Row major indicates that consecutive elements + * of rows will be stored contiguously in memory. + */ + void asColMajorf(float[]) const; + void asColMajord(double[]) const; + void asRowMajorf(float[]) const; + void asRowMajord(double[]) const; + + /** These methods allow one to efficiently set all matrix entries from an + * array. The given array must have room for exactly 16 entries. Whenever + * possible, they will try to use memcpy rather than an entry-by-entry + * copy. + * + * Col major indicates that input memory will be treated as if consecutive + * elements of columns are stored contiguously in memory. Row major + * indicates that input memory will be treated as if consecutive elements + * of rows are stored contiguously in memory. + */ + void setColMajorf(const float[]); + void setColMajord(const double[]); + void setRowMajorf(const float[]); + void setRowMajord(const double[]); + +#ifdef SK_MSCALAR_IS_FLOAT + void setColMajor(const SkMScalar data[]) { this->setColMajorf(data); } + void setRowMajor(const SkMScalar data[]) { this->setRowMajorf(data); } +#else + void setColMajor(const SkMScalar data[]) { this->setColMajord(data); } + void setRowMajor(const SkMScalar data[]) { this->setRowMajord(data); } +#endif + + /* This sets the top-left of the matrix and clears the translation and + * perspective components (with [3][3] set to 1). m_ij is interpreted + * as the matrix entry at row = i, col = j. */ + void set3x3(SkMScalar m_00, SkMScalar m_10, SkMScalar m_20, + SkMScalar m_01, SkMScalar m_11, SkMScalar m_21, + SkMScalar m_02, SkMScalar m_12, SkMScalar m_22); + void set3x3RowMajorf(const float[]); + + void set4x4(SkMScalar m_00, SkMScalar m_10, SkMScalar m_20, SkMScalar m_30, + SkMScalar m_01, SkMScalar m_11, SkMScalar m_21, SkMScalar m_31, + SkMScalar m_02, SkMScalar m_12, SkMScalar m_22, SkMScalar m_32, + SkMScalar m_03, SkMScalar m_13, SkMScalar m_23, SkMScalar m_33); + + void setTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz); + void preTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz); + void postTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz); + + void setScale(SkMScalar sx, SkMScalar sy, SkMScalar sz); + void preScale(SkMScalar sx, SkMScalar sy, SkMScalar sz); + void postScale(SkMScalar sx, SkMScalar sy, SkMScalar sz); + + inline void setScale(SkMScalar scale) { + this->setScale(scale, scale, scale); + } + inline void preScale(SkMScalar scale) { + this->preScale(scale, scale, scale); + } + inline void postScale(SkMScalar scale) { + this->postScale(scale, scale, scale); + } + + void setRotateDegreesAbout(SkMScalar x, SkMScalar y, SkMScalar z, + SkMScalar degrees) { + this->setRotateAbout(x, y, z, degrees * SK_MScalarPI / 180); + } + + /** Rotate about the vector [x,y,z]. If that vector is not unit-length, + it will be automatically resized. + */ + void setRotateAbout(SkMScalar x, SkMScalar y, SkMScalar z, + SkMScalar radians); + /** Rotate about the vector [x,y,z]. Does not check the length of the + vector, assuming it is unit-length. + */ + void setRotateAboutUnit(SkMScalar x, SkMScalar y, SkMScalar z, + SkMScalar radians); + + void setConcat(const SkMatrix44& a, const SkMatrix44& b); + inline void preConcat(const SkMatrix44& m) { + this->setConcat(*this, m); + } + inline void postConcat(const SkMatrix44& m) { + this->setConcat(m, *this); + } + + friend SkMatrix44 operator*(const SkMatrix44& a, const SkMatrix44& b) { + return SkMatrix44(a, b); + } + + /** If this is invertible, return that in inverse and return true. If it is + not invertible, return false and leave the inverse parameter in an + unspecified state. + */ + bool invert(SkMatrix44* inverse) const; + + /** Transpose this matrix in place. */ + void transpose(); + + /** Apply the matrix to the src vector, returning the new vector in dst. + It is legal for src and dst to point to the same memory. + */ + void mapScalars(const SkScalar src[4], SkScalar dst[4]) const; + inline void mapScalars(SkScalar vec[4]) const { + this->mapScalars(vec, vec); + } + +#ifdef SK_MSCALAR_IS_DOUBLE + void mapMScalars(const SkMScalar src[4], SkMScalar dst[4]) const; +#elif defined SK_MSCALAR_IS_FLOAT + inline void mapMScalars(const SkMScalar src[4], SkMScalar dst[4]) const { + this->mapScalars(src, dst); + } +#endif + inline void mapMScalars(SkMScalar vec[4]) const { + this->mapMScalars(vec, vec); + } + + friend SkVector4 operator*(const SkMatrix44& m, const SkVector4& src) { + SkVector4 dst; + m.mapScalars(src.fData, dst.fData); + return dst; + } + + /** + * map an array of [x, y, 0, 1] through the matrix, returning an array + * of [x', y', z', w']. + * + * @param src2 array of [x, y] pairs, with implied z=0 and w=1 + * @param count number of [x, y] pairs in src2 + * @param dst4 array of [x', y', z', w'] quads as the output. + */ + void map2(const float src2[], int count, float dst4[]) const; + void map2(const double src2[], int count, double dst4[]) const; + + /** Returns true if transformating an axis-aligned square in 2d by this matrix + will produce another 2d axis-aligned square; typically means the matrix + is a scale with perhaps a 90-degree rotation. A 3d rotation through 90 + degrees into a perpendicular plane collapses a square to a line, but + is still considered to be axis-aligned. + + By default, tolerates very slight error due to float imprecisions; + a 90-degree rotation can still end up with 10^-17 of + "non-axis-aligned" result. + */ + bool preserves2dAxisAlignment(SkMScalar epsilon = SK_ScalarNearlyZero) const; + + void dump() const; + + double determinant() const; + +private: + /* This is indexed by [col][row]. */ + SkMScalar fMat[4][4]; + TypeMask fTypeMask; + + static constexpr int kAllPublic_Masks = 0xF; + + void as3x4RowMajorf(float[]) const; + void set3x4RowMajorf(const float[]); + + SkMScalar transX() const { return fMat[3][0]; } + SkMScalar transY() const { return fMat[3][1]; } + SkMScalar transZ() const { return fMat[3][2]; } + + SkMScalar scaleX() const { return fMat[0][0]; } + SkMScalar scaleY() const { return fMat[1][1]; } + SkMScalar scaleZ() const { return fMat[2][2]; } + + SkMScalar perspX() const { return fMat[0][3]; } + SkMScalar perspY() const { return fMat[1][3]; } + SkMScalar perspZ() const { return fMat[2][3]; } + + void recomputeTypeMask(); + + inline void setTypeMask(TypeMask mask) { + SkASSERT(0 == (~kAllPublic_Masks & mask)); + fTypeMask = mask; + } + + inline const SkMScalar* values() const { return &fMat[0][0]; } + + friend class SkColorSpace; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkMilestone.h b/gfx/skia/skia/include/core/SkMilestone.h new file mode 100644 index 0000000000..6ac2a14b07 --- /dev/null +++ b/gfx/skia/skia/include/core/SkMilestone.h @@ -0,0 +1,9 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SK_MILESTONE +#define SK_MILESTONE 79 +#endif diff --git a/gfx/skia/skia/include/core/SkMultiPictureDraw.h b/gfx/skia/skia/include/core/SkMultiPictureDraw.h new file mode 100644 index 0000000000..70c4e16c1f --- /dev/null +++ b/gfx/skia/skia/include/core/SkMultiPictureDraw.h @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMultiPictureDraw_DEFINED +#define SkMultiPictureDraw_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/private/SkTDArray.h" + +class SkCanvas; +class SkPaint; +class SkPicture; + +/** \class SkMultiPictureDraw + + The MultiPictureDraw object accepts several picture/canvas pairs and + then attempts to optimally draw the pictures into the canvases, sharing + as many resources as possible. +*/ +class SK_API SkMultiPictureDraw { +public: + /** + * Create an object to optimize the drawing of multiple pictures. + * @param reserve Hint for the number of add calls expected to be issued + */ + SkMultiPictureDraw(int reserve = 0); + ~SkMultiPictureDraw() { this->reset(); } + + /** + * Add a canvas/picture pair for later rendering. + * @param canvas the canvas in which to draw picture + * @param picture the picture to draw into canvas + * @param matrix if non-NULL, applied to the CTM when drawing + * @param paint if non-NULL, draw picture to a temporary buffer + * and then apply the paint when the result is drawn + */ + void add(SkCanvas* canvas, + const SkPicture* picture, + const SkMatrix* matrix = nullptr, + const SkPaint* paint = nullptr); + + /** + * Perform all the previously added draws. This will reset the state + * of this object. If flush is true, all canvases are flushed after + * draw. + */ + void draw(bool flush = false); + + /** + * Abandon all buffered draws and reset to the initial state. + */ + void reset(); + +private: + struct DrawData { + SkCanvas* fCanvas; + const SkPicture* fPicture; // reffed + SkMatrix fMatrix; + SkPaint* fPaint; // owned + + void init(SkCanvas*, const SkPicture*, const SkMatrix*, const SkPaint*); + void draw(); + + static void Reset(SkTDArray&); + }; + + SkTDArray fThreadSafeDrawData; + SkTDArray fGPUDrawData; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkOverdrawCanvas.h b/gfx/skia/skia/include/core/SkOverdrawCanvas.h new file mode 100644 index 0000000000..87f581a204 --- /dev/null +++ b/gfx/skia/skia/include/core/SkOverdrawCanvas.h @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOverdrawCanvas_DEFINED +#define SkOverdrawCanvas_DEFINED + +#include "include/core/SkCanvasVirtualEnforcer.h" +#include "include/utils/SkNWayCanvas.h" + +/** + * Captures all drawing commands. Rather than draw the actual content, this device + * increments the alpha channel of each pixel every time it would have been touched + * by a draw call. This is useful for detecting overdraw. + */ +class SK_API SkOverdrawCanvas : public SkCanvasVirtualEnforcer { +public: + /* Does not take ownership of canvas */ + SkOverdrawCanvas(SkCanvas*); + + void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override; + void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode, + const SkPaint&) override; + void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint& paint) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawPoints(PointMode, size_t, const SkPoint[], const SkPaint&) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], + int, SkBlendMode, const SkRect*, const SkPaint*) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawImage(const SkImage*, SkScalar, SkScalar, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) override; + void onDrawImageNine(const SkImage*, const SkIRect&, const SkRect&, const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override; + void onDrawBitmap(const SkBitmap&, SkScalar, SkScalar, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect&, const SkRect&, const SkPaint*) override; + void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&, + const SkPaint*) override; + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + + void onDrawAnnotation(const SkRect&, const char key[], SkData* value) override; + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + + void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], SkCanvas::QuadAAFlags, const SkColor4f&, + SkBlendMode) override; + void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[], + const SkPaint*, SrcRectConstraint) override; + +private: + void drawPosTextCommon(const SkGlyphID[], int, const SkScalar[], int, const SkPoint&, + const SkFont&, const SkPaint&); + + inline SkPaint overdrawPaint(const SkPaint& paint); + + SkPaint fPaint; + + typedef SkCanvasVirtualEnforcer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPaint.h b/gfx/skia/skia/include/core/SkPaint.h new file mode 100644 index 0000000000..c9a261f6b5 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPaint.h @@ -0,0 +1,668 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPaint_DEFINED +#define SkPaint_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkTo.h" + +class SkColorFilter; +class SkColorSpace; +struct SkRect; +class SkImageFilter; +class SkMaskFilter; +class SkPath; +class SkPathEffect; +class SkShader; + +/** \class SkPaint + SkPaint controls options applied when drawing. SkPaint collects all + options outside of the SkCanvas clip and SkCanvas matrix. + + Various options apply to strokes and fills, and images. + + SkPaint collects effects and filters that describe single-pass and multiple-pass + algorithms that alter the drawing geometry, color, and transparency. For instance, + SkPaint does not directly implement dashing or blur, but contains the objects that do so. +*/ +class SK_API SkPaint { +public: + + /** Constructs SkPaint with default values. + + @return default initialized SkPaint + */ + SkPaint(); + + /** Constructs SkPaint with default values and the given color. + + Sets alpha and RGB used when stroking and filling. The color is four floating + point values, unpremultiplied. The color values are interpreted as being in + the colorSpace. If colorSpace is nullptr, then color is assumed to be in the + sRGB color space. + + @param color unpremultiplied RGBA + @param colorSpace SkColorSpace describing the encoding of color + @return SkPaint with the given color + */ + explicit SkPaint(const SkColor4f& color, SkColorSpace* colorSpace = nullptr); + + /** Makes a shallow copy of SkPaint. SkPathEffect, SkShader, + SkMaskFilter, SkColorFilter, and SkImageFilter are shared + between the original paint and the copy. Objects containing SkRefCnt increment + their references by one. + + The referenced objects SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, + and SkImageFilter cannot be modified after they are created. + This prevents objects with SkRefCnt from being modified once SkPaint refers to them. + + @param paint original to copy + @return shallow copy of paint + */ + SkPaint(const SkPaint& paint); + + /** Implements a move constructor to avoid increasing the reference counts + of objects referenced by the paint. + + After the call, paint is undefined, and can be safely destructed. + + @param paint original to move + @return content of paint + */ + SkPaint(SkPaint&& paint); + + /** Decreases SkPaint SkRefCnt of owned objects: SkPathEffect, SkShader, + SkMaskFilter, SkColorFilter, and SkImageFilter. If the + objects containing SkRefCnt go to zero, they are deleted. + */ + ~SkPaint(); + + /** Makes a shallow copy of SkPaint. SkPathEffect, SkShader, + SkMaskFilter, SkColorFilter, and SkImageFilter are shared + between the original paint and the copy. Objects containing SkRefCnt in the + prior destination are decreased by one, and the referenced objects are deleted if the + resulting count is zero. Objects containing SkRefCnt in the parameter paint + are increased by one. paint is unmodified. + + @param paint original to copy + @return content of paint + */ + SkPaint& operator=(const SkPaint& paint); + + /** Moves the paint to avoid increasing the reference counts + of objects referenced by the paint parameter. Objects containing SkRefCnt in the + prior destination are decreased by one; those objects are deleted if the resulting count + is zero. + + After the call, paint is undefined, and can be safely destructed. + + @param paint original to move + @return content of paint + */ + SkPaint& operator=(SkPaint&& paint); + + /** Compares a and b, and returns true if a and b are equivalent. May return false + if SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, + or SkImageFilter have identical contents but different pointers. + + @param a SkPaint to compare + @param b SkPaint to compare + @return true if SkPaint pair are equivalent + */ + SK_API friend bool operator==(const SkPaint& a, const SkPaint& b); + + /** Compares a and b, and returns true if a and b are not equivalent. May return true + if SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, + or SkImageFilter have identical contents but different pointers. + + @param a SkPaint to compare + @param b SkPaint to compare + @return true if SkPaint pair are not equivalent + */ + friend bool operator!=(const SkPaint& a, const SkPaint& b) { + return !(a == b); + } + + /** Returns a hash generated from SkPaint values and pointers. + Identical hashes guarantee that the paints are + equivalent, but differing hashes do not guarantee that the paints have differing + contents. + + If operator==(const SkPaint& a, const SkPaint& b) returns true for two paints, + their hashes are also equal. + + The hash returned is platform and implementation specific. + + @return a shallow hash + */ + uint32_t getHash() const; + + /** Sets all SkPaint contents to their initial values. This is equivalent to replacing + SkPaint with the result of SkPaint(). + */ + void reset(); + + /** Returns true if pixels on the active edges of SkPath may be drawn with partial transparency. + @return antialiasing state + */ + bool isAntiAlias() const { + return SkToBool(fBitfields.fAntiAlias); + } + + /** Requests, but does not require, that edge pixels draw opaque or with + partial transparency. + @param aa setting for antialiasing + */ + void setAntiAlias(bool aa) { fBitfields.fAntiAlias = static_cast(aa); } + + /** Returns true if color error may be distributed to smooth color transition. + @return dithering state + */ + bool isDither() const { + return SkToBool(fBitfields.fDither); + } + + /** Requests, but does not require, to distribute color error. + @param dither setting for ditering + */ + void setDither(bool dither) { fBitfields.fDither = static_cast(dither); } + + /** Returns SkFilterQuality, the image filtering level. A lower setting + draws faster; a higher setting looks better when the image is scaled. + + @return one of: kNone_SkFilterQuality, kLow_SkFilterQuality, + kMedium_SkFilterQuality, kHigh_SkFilterQuality + */ + SkFilterQuality getFilterQuality() const { + return (SkFilterQuality)fBitfields.fFilterQuality; + } + + /** Sets SkFilterQuality, the image filtering level. A lower setting + draws faster; a higher setting looks better when the image is scaled. + Does not check to see if quality is valid. + + @param quality one of: kNone_SkFilterQuality, kLow_SkFilterQuality, + kMedium_SkFilterQuality, kHigh_SkFilterQuality + */ + void setFilterQuality(SkFilterQuality quality); + + /** \enum SkPaint::Style + Set Style to fill, stroke, or both fill and stroke geometry. + The stroke and fill + share all paint attributes; for instance, they are drawn with the same color. + + Use kStrokeAndFill_Style to avoid hitting the same pixels twice with a stroke draw and + a fill draw. + */ + enum Style : uint8_t { + kFill_Style, //!< set to fill geometry + kStroke_Style, //!< set to stroke geometry + kStrokeAndFill_Style, //!< sets to stroke and fill geometry + }; + + /** May be used to verify that SkPaint::Style is a legal value. + */ + static constexpr int kStyleCount = kStrokeAndFill_Style + 1; + + /** Returns whether the geometry is filled, stroked, or filled and stroked. + + @return one of:kFill_Style, kStroke_Style, kStrokeAndFill_Style + */ + Style getStyle() const { return (Style)fBitfields.fStyle; } + + /** Sets whether the geometry is filled, stroked, or filled and stroked. + Has no effect if style is not a legal SkPaint::Style value. + + @param style one of: kFill_Style, kStroke_Style, kStrokeAndFill_Style + */ + void setStyle(Style style); + + /** Retrieves alpha and RGB, unpremultiplied, packed into 32 bits. + Use helpers SkColorGetA(), SkColorGetR(), SkColorGetG(), and SkColorGetB() to extract + a color component. + + @return unpremultiplied ARGB + */ + SkColor getColor() const { return fColor4f.toSkColor(); } + + /** Retrieves alpha and RGB, unpremultiplied, as four floating point values. RGB are + are extended sRGB values (sRGB gamut, and encoded with the sRGB transfer function). + + @return unpremultiplied RGBA + */ + SkColor4f getColor4f() const { return fColor4f; } + + /** Sets alpha and RGB used when stroking and filling. The color is a 32-bit value, + unpremultiplied, packing 8-bit components for alpha, red, blue, and green. + + @param color unpremultiplied ARGB + */ + void setColor(SkColor color); + + /** Sets alpha and RGB used when stroking and filling. The color is four floating + point values, unpremultiplied. The color values are interpreted as being in + the colorSpace. If colorSpace is nullptr, then color is assumed to be in the + sRGB color space. + + @param color unpremultiplied RGBA + @param colorSpace SkColorSpace describing the encoding of color + */ + void setColor(const SkColor4f& color, SkColorSpace* colorSpace = nullptr); + + void setColor4f(const SkColor4f& color, SkColorSpace* colorSpace = nullptr) { + this->setColor(color, colorSpace); + } + + /** Retrieves alpha from the color used when stroking and filling. + + @return alpha ranging from zero, fully transparent, to 255, fully opaque + */ + float getAlphaf() const { return fColor4f.fA; } + + // Helper that scales the alpha by 255. + uint8_t getAlpha() const { return uint8_t(sk_float_round2int(this->getAlphaf() * 255)); } + + /** Replaces alpha, leaving RGB + unchanged. An out of range value triggers an assert in the debug + build. a is a value from 0.0 to 1.0. + a set to zero makes color fully transparent; a set to 1.0 makes color + fully opaque. + + @param a alpha component of color + */ + void setAlphaf(float a); + + // Helper that accepts an int between 0 and 255, and divides it by 255.0 + void setAlpha(U8CPU a) { + this->setAlphaf(a * (1.0f / 255)); + } + + /** Sets color used when drawing solid fills. The color components range from 0 to 255. + The color is unpremultiplied; alpha sets the transparency independent of RGB. + + @param a amount of alpha, from fully transparent (0) to fully opaque (255) + @param r amount of red, from no red (0) to full red (255) + @param g amount of green, from no green (0) to full green (255) + @param b amount of blue, from no blue (0) to full blue (255) + */ + void setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b); + + /** Returns the thickness of the pen used by SkPaint to + outline the shape. + + @return zero for hairline, greater than zero for pen thickness + */ + SkScalar getStrokeWidth() const { return fWidth; } + + /** Sets the thickness of the pen used by the paint to + outline the shape. + Has no effect if width is less than zero. + + @param width zero thickness for hairline; greater than zero for pen thickness + */ + void setStrokeWidth(SkScalar width); + + /** Returns the limit at which a sharp corner is drawn beveled. + + @return zero and greater miter limit + */ + SkScalar getStrokeMiter() const { return fMiterLimit; } + + /** Sets the limit at which a sharp corner is drawn beveled. + Valid values are zero and greater. + Has no effect if miter is less than zero. + + @param miter zero and greater miter limit + */ + void setStrokeMiter(SkScalar miter); + + /** \enum SkPaint::Cap + Cap draws at the beginning and end of an open path contour. + */ + enum Cap { + kButt_Cap, //!< no stroke extension + kRound_Cap, //!< adds circle + kSquare_Cap, //!< adds square + kLast_Cap = kSquare_Cap, //!< largest Cap value + kDefault_Cap = kButt_Cap, //!< equivalent to kButt_Cap + }; + + /** May be used to verify that SkPaint::Cap is a legal value. + */ + static constexpr int kCapCount = kLast_Cap + 1; + + /** \enum SkPaint::Join + Join specifies how corners are drawn when a shape is stroked. Join + affects the four corners of a stroked rectangle, and the connected segments in a + stroked path. + + Choose miter join to draw sharp corners. Choose round join to draw a circle with a + radius equal to the stroke width on top of the corner. Choose bevel join to minimally + connect the thick strokes. + + The fill path constructed to describe the stroked path respects the join setting but may + not contain the actual join. For instance, a fill path constructed with round joins does + not necessarily include circles at each connected segment. + */ + enum Join : uint8_t { + kMiter_Join, //!< extends to miter limit + kRound_Join, //!< adds circle + kBevel_Join, //!< connects outside edges + kLast_Join = kBevel_Join, //!< equivalent to the largest value for Join + kDefault_Join = kMiter_Join, //!< equivalent to kMiter_Join + }; + + /** May be used to verify that SkPaint::Join is a legal value. + */ + static constexpr int kJoinCount = kLast_Join + 1; + + /** Returns the geometry drawn at the beginning and end of strokes. + + @return one of: kButt_Cap, kRound_Cap, kSquare_Cap + */ + Cap getStrokeCap() const { return (Cap)fBitfields.fCapType; } + + /** Sets the geometry drawn at the beginning and end of strokes. + + @param cap one of: kButt_Cap, kRound_Cap, kSquare_Cap; + has no effect if cap is not valid + */ + void setStrokeCap(Cap cap); + + /** Returns the geometry drawn at the corners of strokes. + + @return one of: kMiter_Join, kRound_Join, kBevel_Join + */ + Join getStrokeJoin() const { return (Join)fBitfields.fJoinType; } + + /** Sets the geometry drawn at the corners of strokes. + + @param join one of: kMiter_Join, kRound_Join, kBevel_Join; + otherwise, has no effect + */ + void setStrokeJoin(Join join); + + /** Returns the filled equivalent of the stroked path. + + @param src SkPath read to create a filled version + @param dst resulting SkPath; may be the same as src, but may not be nullptr + @param cullRect optional limit passed to SkPathEffect + @param resScale if > 1, increase precision, else if (0 < resScale < 1) reduce precision + to favor speed and size + @return true if the path represents style fill, or false if it represents hairline + */ + bool getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect, + SkScalar resScale = 1) const; + + /** Returns the filled equivalent of the stroked path. + + Replaces dst with the src path modified by SkPathEffect and style stroke. + SkPathEffect, if any, is not culled. stroke width is created with default precision. + + @param src SkPath read to create a filled version + @param dst resulting SkPath dst may be the same as src, but may not be nullptr + @return true if the path represents style fill, or false if it represents hairline + */ + bool getFillPath(const SkPath& src, SkPath* dst) const { + return this->getFillPath(src, dst, nullptr, 1); + } + + /** Returns optional colors used when filling a path, such as a gradient. + + Does not alter SkShader SkRefCnt. + + @return SkShader if previously set, nullptr otherwise + */ + SkShader* getShader() const { return fShader.get(); } + + /** Returns optional colors used when filling a path, such as a gradient. + + Increases SkShader SkRefCnt by one. + + @return SkShader if previously set, nullptr otherwise + */ + sk_sp refShader() const; + + /** Sets optional colors used when filling a path, such as a gradient. + + Sets SkShader to shader, decreasing SkRefCnt of the previous SkShader. + Increments shader SkRefCnt by one. + + @param shader how geometry is filled with color; if nullptr, color is used instead + */ + void setShader(sk_sp shader); + + /** Returns SkColorFilter if set, or nullptr. + Does not alter SkColorFilter SkRefCnt. + + @return SkColorFilter if previously set, nullptr otherwise + */ + SkColorFilter* getColorFilter() const { return fColorFilter.get(); } + + /** Returns SkColorFilter if set, or nullptr. + Increases SkColorFilter SkRefCnt by one. + + @return SkColorFilter if set, or nullptr + */ + sk_sp refColorFilter() const; + + /** Sets SkColorFilter to filter, decreasing SkRefCnt of the previous + SkColorFilter. Pass nullptr to clear SkColorFilter. + + Increments filter SkRefCnt by one. + + @param colorFilter SkColorFilter to apply to subsequent draw + */ + void setColorFilter(sk_sp colorFilter); + + /** Returns SkBlendMode. + By default, returns SkBlendMode::kSrcOver. + + @return mode used to combine source color with destination color + */ + SkBlendMode getBlendMode() const { return (SkBlendMode)fBitfields.fBlendMode; } + + /** Returns true if SkBlendMode is SkBlendMode::kSrcOver, the default. + + @return true if SkBlendMode is SkBlendMode::kSrcOver + */ + bool isSrcOver() const { return (SkBlendMode)fBitfields.fBlendMode == SkBlendMode::kSrcOver; } + + /** Sets SkBlendMode to mode. + Does not check for valid input. + + @param mode SkBlendMode used to combine source color and destination + */ + void setBlendMode(SkBlendMode mode) { fBitfields.fBlendMode = (unsigned)mode; } + + /** Returns SkPathEffect if set, or nullptr. + Does not alter SkPathEffect SkRefCnt. + + @return SkPathEffect if previously set, nullptr otherwise + */ + SkPathEffect* getPathEffect() const { return fPathEffect.get(); } + + /** Returns SkPathEffect if set, or nullptr. + Increases SkPathEffect SkRefCnt by one. + + @return SkPathEffect if previously set, nullptr otherwise + */ + sk_sp refPathEffect() const; + + /** Sets SkPathEffect to pathEffect, decreasing SkRefCnt of the previous + SkPathEffect. Pass nullptr to leave the path geometry unaltered. + + Increments pathEffect SkRefCnt by one. + + @param pathEffect replace SkPath with a modification when drawn + */ + void setPathEffect(sk_sp pathEffect); + + /** Returns SkMaskFilter if set, or nullptr. + Does not alter SkMaskFilter SkRefCnt. + + @return SkMaskFilter if previously set, nullptr otherwise + */ + SkMaskFilter* getMaskFilter() const { return fMaskFilter.get(); } + + /** Returns SkMaskFilter if set, or nullptr. + + Increases SkMaskFilter SkRefCnt by one. + + @return SkMaskFilter if previously set, nullptr otherwise + */ + sk_sp refMaskFilter() const; + + /** Sets SkMaskFilter to maskFilter, decreasing SkRefCnt of the previous + SkMaskFilter. Pass nullptr to clear SkMaskFilter and leave SkMaskFilter effect on + mask alpha unaltered. + + Increments maskFilter SkRefCnt by one. + + @param maskFilter modifies clipping mask generated from drawn geometry + */ + void setMaskFilter(sk_sp maskFilter); + + /** Returns SkImageFilter if set, or nullptr. + Does not alter SkImageFilter SkRefCnt. + + @return SkImageFilter if previously set, nullptr otherwise + */ + SkImageFilter* getImageFilter() const { return fImageFilter.get(); } + + /** Returns SkImageFilter if set, or nullptr. + Increases SkImageFilter SkRefCnt by one. + + @return SkImageFilter if previously set, nullptr otherwise + */ + sk_sp refImageFilter() const; + + /** Sets SkImageFilter to imageFilter, decreasing SkRefCnt of the previous + SkImageFilter. Pass nullptr to clear SkImageFilter, and remove SkImageFilter effect + on drawing. + + Increments imageFilter SkRefCnt by one. + + @param imageFilter how SkImage is sampled when transformed + */ + void setImageFilter(sk_sp imageFilter); + + /** Returns true if SkPaint prevents all drawing; + otherwise, the SkPaint may or may not allow drawing. + + Returns true if, for example, SkBlendMode combined with alpha computes a + new alpha of zero. + + @return true if SkPaint prevents all drawing + */ + bool nothingToDraw() const; + + /** (to be made private) + Returns true if SkPaint does not include elements requiring extensive computation + to compute SkBaseDevice bounds of drawn geometry. For instance, SkPaint with SkPathEffect + always returns false. + + @return true if SkPaint allows for fast computation of bounds + */ + bool canComputeFastBounds() const; + + /** (to be made private) + Only call this if canComputeFastBounds() returned true. This takes a + raw rectangle (the raw bounds of a shape), and adjusts it for stylistic + effects in the paint (e.g. stroking). If needed, it uses the storage + parameter. It returns the adjusted bounds that can then be used + for SkCanvas::quickReject tests. + + The returned SkRect will either be orig or storage, thus the caller + should not rely on storage being set to the result, but should always + use the returned value. It is legal for orig and storage to be the same + SkRect. + For example: + if (!path.isInverseFillType() && paint.canComputeFastBounds()) { + SkRect storage; + if (canvas->quickReject(paint.computeFastBounds(path.getBounds(), &storage))) { + return; // do not draw the path + } + } + // draw the path + + @param orig geometry modified by SkPaint when drawn + @param storage computed bounds of geometry; may not be nullptr + @return fast computed bounds + */ + const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const { + // Things like stroking, etc... will do math on the bounds rect, assuming that it's sorted. + SkASSERT(orig.isSorted()); + SkPaint::Style style = this->getStyle(); + // ultra fast-case: filling with no effects that affect geometry + if (kFill_Style == style) { + uintptr_t effects = 0; + effects |= reinterpret_cast(this->getMaskFilter()); + effects |= reinterpret_cast(this->getPathEffect()); + effects |= reinterpret_cast(this->getImageFilter()); + if (!effects) { + return orig; + } + } + + return this->doComputeFastBounds(orig, storage, style); + } + + /** (to be made private) + + @param orig geometry modified by SkPaint when drawn + @param storage computed bounds of geometry + @return fast computed bounds + */ + const SkRect& computeFastStrokeBounds(const SkRect& orig, + SkRect* storage) const { + return this->doComputeFastBounds(orig, storage, kStroke_Style); + } + + /** (to be made private) + Computes the bounds, overriding the SkPaint SkPaint::Style. This can be used to + account for additional width required by stroking orig, without + altering SkPaint::Style set to fill. + + @param orig geometry modified by SkPaint when drawn + @param storage computed bounds of geometry + @param style overrides SkPaint::Style + @return fast computed bounds + */ + const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage, + Style style) const; + +private: + sk_sp fPathEffect; + sk_sp fShader; + sk_sp fMaskFilter; + sk_sp fColorFilter; + sk_sp fImageFilter; + + SkColor4f fColor4f; + SkScalar fWidth; + SkScalar fMiterLimit; + union { + struct { + unsigned fAntiAlias : 1; + unsigned fDither : 1; + unsigned fCapType : 2; + unsigned fJoinType : 2; + unsigned fStyle : 2; + unsigned fFilterQuality : 2; + unsigned fBlendMode : 8; // only need 5-6? + unsigned fPadding : 14; // 14==32-1-1-2-2-2-2-8 + } fBitfields; + uint32_t fBitfieldsUInt; + }; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPath.h b/gfx/skia/skia/include/core/SkPath.h new file mode 100644 index 0000000000..6a87648685 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPath.h @@ -0,0 +1,1746 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPath_DEFINED +#define SkPath_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkPathTypes.h" +#include "include/private/SkPathRef.h" +#include "include/private/SkTo.h" + +#include + +class SkAutoPathBoundsUpdate; +class SkData; +class SkRRect; +class SkWStream; + +/** \class SkPath + SkPath contain geometry. SkPath may be empty, or contain one or more verbs that + outline a figure. SkPath always starts with a move verb to a Cartesian coordinate, + and may be followed by additional verbs that add lines or curves. + Adding a close verb makes the geometry into a continuous loop, a closed contour. + SkPath may contain any number of contours, each beginning with a move verb. + + SkPath contours may contain only a move verb, or may also contain lines, + quadratic beziers, conics, and cubic beziers. SkPath contours may be open or + closed. + + When used to draw a filled area, SkPath describes whether the fill is inside or + outside the geometry. SkPath also describes the winding rule used to fill + overlapping contours. + + Internally, SkPath lazily computes metrics likes bounds and convexity. Call + SkPath::updateBoundsCache to make SkPath thread safe. +*/ +class SK_API SkPath { +public: + + /** \enum SkPath::Direction + Direction describes whether contour is clockwise or counterclockwise. + When SkPath contains multiple overlapping contours, Direction together with + FillType determines whether overlaps are filled or form holes. + + Direction also determines how contour is measured. For instance, dashing + measures along SkPath to determine where to start and stop stroke; Direction + will change dashed results as it steps clockwise or counterclockwise. + + Closed contours like SkRect, SkRRect, circle, and oval added with + kCW_Direction travel clockwise; the same added with kCCW_Direction + travel counterclockwise. + */ + enum Direction : int { + kCW_Direction = static_cast(SkPathDirection::kCW), + kCCW_Direction = static_cast(SkPathDirection::kCCW) + }; + + /** Constructs an empty SkPath. By default, SkPath has no verbs, no SkPoint, and no weights. + SkPath::FillType is set to kWinding_FillType. + + @return empty SkPath + */ + SkPath(); + + /** Constructs a copy of an existing path. + Copy constructor makes two paths identical by value. Internally, path and + the returned result share pointer values. The underlying verb array, SkPoint array + and weights are copied when modified. + + Creating a SkPath copy is very efficient and never allocates memory. + SkPath are always copied by value from the interface; the underlying shared + pointers are not exposed. + + @param path SkPath to copy by value + @return copy of SkPath + */ + SkPath(const SkPath& path); + + /** Releases ownership of any shared data and deletes data if SkPath is sole owner. + */ + ~SkPath(); + + /** Constructs a copy of an existing path. + SkPath assignment makes two paths identical by value. Internally, assignment + shares pointer values. The underlying verb array, SkPoint array and weights + are copied when modified. + + Copying SkPath by assignment is very efficient and never allocates memory. + SkPath are always copied by value from the interface; the underlying shared + pointers are not exposed. + + @param path verb array, SkPoint array, weights, and SkPath::FillType to copy + @return SkPath copied by value + */ + SkPath& operator=(const SkPath& path); + + /** Compares a and b; returns true if SkPath::FillType, verb array, SkPoint array, and weights + are equivalent. + + @param a SkPath to compare + @param b SkPath to compare + @return true if SkPath pair are equivalent + */ + friend SK_API bool operator==(const SkPath& a, const SkPath& b); + + /** Compares a and b; returns true if SkPath::FillType, verb array, SkPoint array, and weights + are not equivalent. + + @param a SkPath to compare + @param b SkPath to compare + @return true if SkPath pair are not equivalent + */ + friend bool operator!=(const SkPath& a, const SkPath& b) { + return !(a == b); + } + + /** Returns true if SkPath contain equal verbs and equal weights. + If SkPath contain one or more conics, the weights must match. + + conicTo() may add different verbs depending on conic weight, so it is not + trivial to interpolate a pair of SkPath containing conics with different + conic weight values. + + @param compare SkPath to compare + @return true if SkPath verb array and weights are equivalent + */ + bool isInterpolatable(const SkPath& compare) const; + + /** Interpolates between SkPath with SkPoint array of equal size. + Copy verb array and weights to out, and set out SkPoint array to a weighted + average of this SkPoint array and ending SkPoint array, using the formula: + (Path Point * weight) + ending Point * (1 - weight). + + weight is most useful when between zero (ending SkPoint array) and + one (this Point_Array); will work with values outside of this + range. + + interpolate() returns false and leaves out unchanged if SkPoint array is not + the same size as ending SkPoint array. Call isInterpolatable() to check SkPath + compatibility prior to calling interpolate(). + + @param ending SkPoint array averaged with this SkPoint array + @param weight contribution of this SkPoint array, and + one minus contribution of ending SkPoint array + @param out SkPath replaced by interpolated averages + @return true if SkPath contain same number of SkPoint + */ + bool interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const; + + /** \enum SkPath::FillType + FillType selects the rule used to fill SkPath. SkPath set to kWinding_FillType + fills if the sum of contour edges is not zero, where clockwise edges add one, and + counterclockwise edges subtract one. SkPath set to kEvenOdd_FillType fills if the + number of contour edges is odd. Each FillType has an inverse variant that + reverses the rule: + kInverseWinding_FillType fills where the sum of contour edges is zero; + kInverseEvenOdd_FillType fills where the number of contour edges is even. + */ + enum FillType { + kWinding_FillType = static_cast(SkPathFillType::kWinding), + kEvenOdd_FillType = static_cast(SkPathFillType::kEvenOdd), + kInverseWinding_FillType = static_cast(SkPathFillType::kInverseWinding), + kInverseEvenOdd_FillType = static_cast(SkPathFillType::kInverseEvenOdd) + }; + + /** Returns FillType, the rule used to fill SkPath. FillType of a new SkPath is + kWinding_FillType. + + @return one of: kWinding_FillType, kEvenOdd_FillType, kInverseWinding_FillType, + kInverseEvenOdd_FillType + */ + FillType getFillType() const { return (FillType)fFillType; } + + /** Sets FillType, the rule used to fill SkPath. While there is no check + that ft is legal, values outside of FillType are not supported. + + @param ft one of: kWinding_FillType, kEvenOdd_FillType, kInverseWinding_FillType, + kInverseEvenOdd_FillType + */ + void setFillType(FillType ft) { + fFillType = SkToU8(ft); + } + + /** Returns if FillType describes area outside SkPath geometry. The inverse fill area + extends indefinitely. + + @return true if FillType is kInverseWinding_FillType or kInverseEvenOdd_FillType + */ + bool isInverseFillType() const { return IsInverseFillType((FillType)fFillType); } + + /** Replaces FillType with its inverse. The inverse of FillType describes the area + unmodified by the original FillType. + */ + void toggleInverseFillType() { + fFillType ^= 2; + } + + /** \enum SkPath::Convexity + SkPath is convex if it contains one contour and contour loops no more than + 360 degrees, and contour angles all have same Direction. Convex SkPath + may have better performance and require fewer resources on GPU surface. + + SkPath is concave when either at least one Direction change is clockwise and + another is counterclockwise, or the sum of the changes in Direction is not 360 + degrees. + + Initially SkPath Convexity is kUnknown_Convexity. SkPath Convexity is computed + if needed by destination SkSurface. + */ + enum Convexity : uint8_t { + kUnknown_Convexity = static_cast(SkPathConvexityType::kUnknown), + kConvex_Convexity = static_cast(SkPathConvexityType::kConvex), + kConcave_Convexity = static_cast(SkPathConvexityType::kConcave), + }; + + /** Computes SkPath::Convexity if required, and returns stored value. + SkPath::Convexity is computed if stored value is kUnknown_Convexity, + or if SkPath has been altered since SkPath::Convexity was computed or set. + + @return computed or stored SkPath::Convexity + */ + Convexity getConvexity() const { + Convexity convexity = this->getConvexityOrUnknown(); + if (convexity != kUnknown_Convexity) { + return convexity; + } + return this->internalGetConvexity(); + } + + /** Returns last computed SkPath::Convexity, or kUnknown_Convexity if + SkPath has been altered since SkPath::Convexity was computed or set. + + @return stored SkPath::Convexity + */ + Convexity getConvexityOrUnknown() const { return fConvexity.load(std::memory_order_relaxed); } + + /** Stores convexity so that it is later returned by getConvexity() or getConvexityOrUnknown(). + convexity may differ from getConvexity(), although setting an incorrect value may + cause incorrect or inefficient drawing. + + If convexity is kUnknown_Convexity: getConvexity() will + compute SkPath::Convexity, and getConvexityOrUnknown() will return kUnknown_Convexity. + + If convexity is kConvex_Convexity or kConcave_Convexity, getConvexity() + and getConvexityOrUnknown() will return convexity until the path is + altered. + + @param convexity one of: kUnknown_Convexity, kConvex_Convexity, or kConcave_Convexity + */ + void setConvexity(Convexity convexity); + + /** Computes SkPath::Convexity if required, and returns true if value is kConvex_Convexity. + If setConvexity() was called with kConvex_Convexity or kConcave_Convexity, and + the path has not been altered, SkPath::Convexity is not recomputed. + + @return true if SkPath::Convexity stored or computed is kConvex_Convexity + */ + bool isConvex() const { + return kConvex_Convexity == this->getConvexity(); + } + + /** Returns true if this path is recognized as an oval or circle. + + bounds receives bounds of oval. + + bounds is unmodified if oval is not found. + + @param bounds storage for bounding SkRect of oval; may be nullptr + @return true if SkPath is recognized as an oval or circle + */ + bool isOval(SkRect* bounds) const; + + /** Returns true if path is representable as SkRRect. + Returns false if path is representable as oval, circle, or SkRect. + + rrect receives bounds of SkRRect. + + rrect is unmodified if SkRRect is not found. + + @param rrect storage for bounding SkRect of SkRRect; may be nullptr + @return true if SkPath contains only SkRRect + */ + bool isRRect(SkRRect* rrect) const; + + /** Sets SkPath to its initial state. + Removes verb array, SkPoint array, and weights, and sets FillType to kWinding_FillType. + Internal storage associated with SkPath is released. + + @return reference to SkPath + */ + SkPath& reset(); + + /** Sets SkPath to its initial state, preserving internal storage. + Removes verb array, SkPoint array, and weights, and sets FillType to kWinding_FillType. + Internal storage associated with SkPath is retained. + + Use rewind() instead of reset() if SkPath storage will be reused and performance + is critical. + + @return reference to SkPath + */ + SkPath& rewind(); + + /** Returns if SkPath is empty. + Empty SkPath may have FillType but has no SkPoint, SkPath::Verb, or conic weight. + SkPath() constructs empty SkPath; reset() and rewind() make SkPath empty. + + @return true if the path contains no SkPath::Verb array + */ + bool isEmpty() const { + SkDEBUGCODE(this->validate();) + return 0 == fPathRef->countVerbs(); + } + + /** Returns if contour is closed. + Contour is closed if SkPath SkPath::Verb array was last modified by close(). When stroked, + closed contour draws SkPaint::Join instead of SkPaint::Cap at first and last SkPoint. + + @return true if the last contour ends with a kClose_Verb + */ + bool isLastContourClosed() const; + + /** Returns true for finite SkPoint array values between negative SK_ScalarMax and + positive SK_ScalarMax. Returns false for any SkPoint array value of + SK_ScalarInfinity, SK_ScalarNegativeInfinity, or SK_ScalarNaN. + + @return true if all SkPoint values are finite + */ + bool isFinite() const { + SkDEBUGCODE(this->validate();) + return fPathRef->isFinite(); + } + + /** Returns true if the path is volatile; it will not be altered or discarded + by the caller after it is drawn. SkPath by default have volatile set false, allowing + SkSurface to attach a cache of data which speeds repeated drawing. If true, SkSurface + may not speed repeated drawing. + + @return true if caller will alter SkPath after drawing + */ + bool isVolatile() const { + return SkToBool(fIsVolatile); + } + + /** Specifies whether SkPath is volatile; whether it will be altered or discarded + by the caller after it is drawn. SkPath by default have volatile set false, allowing + SkBaseDevice to attach a cache of data which speeds repeated drawing. + + Mark temporary paths, discarded or modified after use, as volatile + to inform SkBaseDevice that the path need not be cached. + + Mark animating SkPath volatile to improve performance. + Mark unchanging SkPath non-volatile to improve repeated rendering. + + raster surface SkPath draws are affected by volatile for some shadows. + GPU surface SkPath draws are affected by volatile for some shadows and concave geometries. + + @param isVolatile true if caller will alter SkPath after drawing + */ + void setIsVolatile(bool isVolatile) { + fIsVolatile = isVolatile; + } + + /** Tests if line between SkPoint pair is degenerate. + Line with no length or that moves a very short distance is degenerate; it is + treated as a point. + + exact changes the equality test. If true, returns true only if p1 equals p2. + If false, returns true if p1 equals or nearly equals p2. + + @param p1 line start point + @param p2 line end point + @param exact if false, allow nearly equals + @return true if line is degenerate; its length is effectively zero + */ + static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact); + + /** Tests if quad is degenerate. + Quad with no length or that moves a very short distance is degenerate; it is + treated as a point. + + @param p1 quad start point + @param p2 quad control point + @param p3 quad end point + @param exact if true, returns true only if p1, p2, and p3 are equal; + if false, returns true if p1, p2, and p3 are equal or nearly equal + @return true if quad is degenerate; its length is effectively zero + */ + static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2, + const SkPoint& p3, bool exact); + + /** Tests if cubic is degenerate. + Cubic with no length or that moves a very short distance is degenerate; it is + treated as a point. + + @param p1 cubic start point + @param p2 cubic control point 1 + @param p3 cubic control point 2 + @param p4 cubic end point + @param exact if true, returns true only if p1, p2, p3, and p4 are equal; + if false, returns true if p1, p2, p3, and p4 are equal or nearly equal + @return true if cubic is degenerate; its length is effectively zero + */ + static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2, + const SkPoint& p3, const SkPoint& p4, bool exact); + + /** Returns true if SkPath contains only one line; + SkPath::Verb array has two entries: kMove_Verb, kLine_Verb. + If SkPath contains one line and line is not nullptr, line is set to + line start point and line end point. + Returns false if SkPath is not one line; line is unaltered. + + @param line storage for line. May be nullptr + @return true if SkPath contains exactly one line + */ + bool isLine(SkPoint line[2]) const; + + /** Returns the number of points in SkPath. + SkPoint count is initially zero. + + @return SkPath SkPoint array length + */ + int countPoints() const; + + /** Returns SkPoint at index in SkPoint array. Valid range for index is + 0 to countPoints() - 1. + Returns (0, 0) if index is out of range. + + @param index SkPoint array element selector + @return SkPoint array value or (0, 0) + */ + SkPoint getPoint(int index) const; + + /** Returns number of points in SkPath. Up to max points are copied. + points may be nullptr; then, max must be zero. + If max is greater than number of points, excess points storage is unaltered. + + @param points storage for SkPath SkPoint array. May be nullptr + @param max maximum to copy; must be greater than or equal to zero + @return SkPath SkPoint array length + */ + int getPoints(SkPoint points[], int max) const; + + /** Returns the number of verbs: kMove_Verb, kLine_Verb, kQuad_Verb, kConic_Verb, + kCubic_Verb, and kClose_Verb; added to SkPath. + + @return length of verb array + */ + int countVerbs() const; + + /** Returns the number of verbs in the path. Up to max verbs are copied. The + verbs are copied as one byte per verb. + + @param verbs storage for verbs, may be nullptr + @param max maximum number to copy into verbs + @return the actual number of verbs in the path + */ + int getVerbs(uint8_t verbs[], int max) const; + + /** Returns the approximate byte size of the SkPath in memory. + + @return approximate size + */ + size_t approximateBytesUsed() const; + + /** Exchanges the verb array, SkPoint array, weights, and SkPath::FillType with other. + Cached state is also exchanged. swap() internally exchanges pointers, so + it is lightweight and does not allocate memory. + + swap() usage has largely been replaced by operator=(const SkPath& path). + SkPath do not copy their content on assignment until they are written to, + making assignment as efficient as swap(). + + @param other SkPath exchanged by value + */ + void swap(SkPath& other); + + /** Returns minimum and maximum axes values of SkPoint array. + Returns (0, 0, 0, 0) if SkPath contains no points. Returned bounds width and height may + be larger or smaller than area affected when SkPath is drawn. + + SkRect returned includes all SkPoint added to SkPath, including SkPoint associated with + kMove_Verb that define empty contours. + + @return bounds of all SkPoint in SkPoint array + */ + const SkRect& getBounds() const { + return fPathRef->getBounds(); + } + + /** Updates internal bounds so that subsequent calls to getBounds() are instantaneous. + Unaltered copies of SkPath may also access cached bounds through getBounds(). + + For now, identical to calling getBounds() and ignoring the returned value. + + Call to prepare SkPath subsequently drawn from multiple threads, + to avoid a race condition where each draw separately computes the bounds. + */ + void updateBoundsCache() const { + // for now, just calling getBounds() is sufficient + this->getBounds(); + } + + /** Returns minimum and maximum axes values of the lines and curves in SkPath. + Returns (0, 0, 0, 0) if SkPath contains no points. + Returned bounds width and height may be larger or smaller than area affected + when SkPath is drawn. + + Includes SkPoint associated with kMove_Verb that define empty + contours. + + Behaves identically to getBounds() when SkPath contains + only lines. If SkPath contains curves, computed bounds includes + the maximum extent of the quad, conic, or cubic; is slower than getBounds(); + and unlike getBounds(), does not cache the result. + + @return tight bounds of curves in SkPath + */ + SkRect computeTightBounds() const; + + /** Returns true if rect is contained by SkPath. + May return false when rect is contained by SkPath. + + For now, only returns true if SkPath has one contour and is convex. + rect may share points and edges with SkPath and be contained. + Returns true if rect is empty, that is, it has zero width or height; and + the SkPoint or line described by rect is contained by SkPath. + + @param rect SkRect, line, or SkPoint checked for containment + @return true if rect is contained + */ + bool conservativelyContainsRect(const SkRect& rect) const; + + /** Grows SkPath verb array and SkPoint array to contain extraPtCount additional SkPoint. + May improve performance and use less memory by + reducing the number and size of allocations when creating SkPath. + + @param extraPtCount number of additional SkPoint to allocate + */ + void incReserve(int extraPtCount); + + /** Shrinks SkPath verb array and SkPoint array storage to discard unused capacity. + May reduce the heap overhead for SkPath known to be fully constructed. + */ + void shrinkToFit(); + + /** Adds beginning of contour at SkPoint (x, y). + + @param x x-axis value of contour start + @param y y-axis value of contour start + @return reference to SkPath + */ + SkPath& moveTo(SkScalar x, SkScalar y); + + /** Adds beginning of contour at SkPoint p. + + @param p contour start + @return reference to SkPath + */ + SkPath& moveTo(const SkPoint& p) { + return this->moveTo(p.fX, p.fY); + } + + /** Adds beginning of contour relative to last point. + If SkPath is empty, starts contour at (dx, dy). + Otherwise, start contour at last point offset by (dx, dy). + Function name stands for "relative move to". + + @param dx offset from last point to contour start on x-axis + @param dy offset from last point to contour start on y-axis + @return reference to SkPath + */ + SkPath& rMoveTo(SkScalar dx, SkScalar dy); + + /** Adds line from last point to (x, y). If SkPath is empty, or last SkPath::Verb is + kClose_Verb, last point is set to (0, 0) before adding line. + + lineTo() appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed. + lineTo() then appends kLine_Verb to verb array and (x, y) to SkPoint array. + + @param x end of added line on x-axis + @param y end of added line on y-axis + @return reference to SkPath + */ + SkPath& lineTo(SkScalar x, SkScalar y); + + /** Adds line from last point to SkPoint p. If SkPath is empty, or last SkPath::Verb is + kClose_Verb, last point is set to (0, 0) before adding line. + + lineTo() first appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed. + lineTo() then appends kLine_Verb to verb array and SkPoint p to SkPoint array. + + @param p end SkPoint of added line + @return reference to SkPath + */ + SkPath& lineTo(const SkPoint& p) { + return this->lineTo(p.fX, p.fY); + } + + /** Adds line from last point to vector (dx, dy). If SkPath is empty, or last SkPath::Verb is + kClose_Verb, last point is set to (0, 0) before adding line. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed; + then appends kLine_Verb to verb array and line end to SkPoint array. + Line end is last point plus vector (dx, dy). + Function name stands for "relative line to". + + @param dx offset from last point to line end on x-axis + @param dy offset from last point to line end on y-axis + @return reference to SkPath + */ + SkPath& rLineTo(SkScalar dx, SkScalar dy); + + /** Adds quad from last point towards (x1, y1), to (x2, y2). + If SkPath is empty, or last SkPath::Verb is kClose_Verb, last point is set to (0, 0) + before adding quad. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed; + then appends kQuad_Verb to verb array; and (x1, y1), (x2, y2) + to SkPoint array. + + @param x1 control SkPoint of quad on x-axis + @param y1 control SkPoint of quad on y-axis + @param x2 end SkPoint of quad on x-axis + @param y2 end SkPoint of quad on y-axis + @return reference to SkPath + */ + SkPath& quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2); + + /** Adds quad from last point towards SkPoint p1, to SkPoint p2. + If SkPath is empty, or last SkPath::Verb is kClose_Verb, last point is set to (0, 0) + before adding quad. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed; + then appends kQuad_Verb to verb array; and SkPoint p1, p2 + to SkPoint array. + + @param p1 control SkPoint of added quad + @param p2 end SkPoint of added quad + @return reference to SkPath + */ + SkPath& quadTo(const SkPoint& p1, const SkPoint& p2) { + return this->quadTo(p1.fX, p1.fY, p2.fX, p2.fY); + } + + /** Adds quad from last point towards vector (dx1, dy1), to vector (dx2, dy2). + If SkPath is empty, or last SkPath::Verb + is kClose_Verb, last point is set to (0, 0) before adding quad. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, + if needed; then appends kQuad_Verb to verb array; and appends quad + control and quad end to SkPoint array. + Quad control is last point plus vector (dx1, dy1). + Quad end is last point plus vector (dx2, dy2). + Function name stands for "relative quad to". + + @param dx1 offset from last point to quad control on x-axis + @param dy1 offset from last point to quad control on y-axis + @param dx2 offset from last point to quad end on x-axis + @param dy2 offset from last point to quad end on y-axis + @return reference to SkPath + */ + SkPath& rQuadTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2); + + /** Adds conic from last point towards (x1, y1), to (x2, y2), weighted by w. + If SkPath is empty, or last SkPath::Verb is kClose_Verb, last point is set to (0, 0) + before adding conic. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed. + + If w is finite and not one, appends kConic_Verb to verb array; + and (x1, y1), (x2, y2) to SkPoint array; and w to conic weights. + + If w is one, appends kQuad_Verb to verb array, and + (x1, y1), (x2, y2) to SkPoint array. + + If w is not finite, appends kLine_Verb twice to verb array, and + (x1, y1), (x2, y2) to SkPoint array. + + @param x1 control SkPoint of conic on x-axis + @param y1 control SkPoint of conic on y-axis + @param x2 end SkPoint of conic on x-axis + @param y2 end SkPoint of conic on y-axis + @param w weight of added conic + @return reference to SkPath + */ + SkPath& conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, + SkScalar w); + + /** Adds conic from last point towards SkPoint p1, to SkPoint p2, weighted by w. + If SkPath is empty, or last SkPath::Verb is kClose_Verb, last point is set to (0, 0) + before adding conic. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed. + + If w is finite and not one, appends kConic_Verb to verb array; + and SkPoint p1, p2 to SkPoint array; and w to conic weights. + + If w is one, appends kQuad_Verb to verb array, and SkPoint p1, p2 + to SkPoint array. + + If w is not finite, appends kLine_Verb twice to verb array, and + SkPoint p1, p2 to SkPoint array. + + @param p1 control SkPoint of added conic + @param p2 end SkPoint of added conic + @param w weight of added conic + @return reference to SkPath + */ + SkPath& conicTo(const SkPoint& p1, const SkPoint& p2, SkScalar w) { + return this->conicTo(p1.fX, p1.fY, p2.fX, p2.fY, w); + } + + /** Adds conic from last point towards vector (dx1, dy1), to vector (dx2, dy2), + weighted by w. If SkPath is empty, or last SkPath::Verb + is kClose_Verb, last point is set to (0, 0) before adding conic. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, + if needed. + + If w is finite and not one, next appends kConic_Verb to verb array, + and w is recorded as conic weight; otherwise, if w is one, appends + kQuad_Verb to verb array; or if w is not finite, appends kLine_Verb + twice to verb array. + + In all cases appends SkPoint control and end to SkPoint array. + control is last point plus vector (dx1, dy1). + end is last point plus vector (dx2, dy2). + + Function name stands for "relative conic to". + + @param dx1 offset from last point to conic control on x-axis + @param dy1 offset from last point to conic control on y-axis + @param dx2 offset from last point to conic end on x-axis + @param dy2 offset from last point to conic end on y-axis + @param w weight of added conic + @return reference to SkPath + */ + SkPath& rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2, + SkScalar w); + + /** Adds cubic from last point towards (x1, y1), then towards (x2, y2), ending at + (x3, y3). If SkPath is empty, or last SkPath::Verb is kClose_Verb, last point is set to + (0, 0) before adding cubic. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed; + then appends kCubic_Verb to verb array; and (x1, y1), (x2, y2), (x3, y3) + to SkPoint array. + + @param x1 first control SkPoint of cubic on x-axis + @param y1 first control SkPoint of cubic on y-axis + @param x2 second control SkPoint of cubic on x-axis + @param y2 second control SkPoint of cubic on y-axis + @param x3 end SkPoint of cubic on x-axis + @param y3 end SkPoint of cubic on y-axis + @return reference to SkPath + */ + SkPath& cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, + SkScalar x3, SkScalar y3); + + /** Adds cubic from last point towards SkPoint p1, then towards SkPoint p2, ending at + SkPoint p3. If SkPath is empty, or last SkPath::Verb is kClose_Verb, last point is set to + (0, 0) before adding cubic. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, if needed; + then appends kCubic_Verb to verb array; and SkPoint p1, p2, p3 + to SkPoint array. + + @param p1 first control SkPoint of cubic + @param p2 second control SkPoint of cubic + @param p3 end SkPoint of cubic + @return reference to SkPath + */ + SkPath& cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3) { + return this->cubicTo(p1.fX, p1.fY, p2.fX, p2.fY, p3.fX, p3.fY); + } + + /** Adds cubic from last point towards vector (dx1, dy1), then towards + vector (dx2, dy2), to vector (dx3, dy3). + If SkPath is empty, or last SkPath::Verb + is kClose_Verb, last point is set to (0, 0) before adding cubic. + + Appends kMove_Verb to verb array and (0, 0) to SkPoint array, + if needed; then appends kCubic_Verb to verb array; and appends cubic + control and cubic end to SkPoint array. + Cubic control is last point plus vector (dx1, dy1). + Cubic end is last point plus vector (dx2, dy2). + Function name stands for "relative cubic to". + + @param dx1 offset from last point to first cubic control on x-axis + @param dy1 offset from last point to first cubic control on y-axis + @param dx2 offset from last point to second cubic control on x-axis + @param dy2 offset from last point to second cubic control on y-axis + @param dx3 offset from last point to cubic end on x-axis + @param dy3 offset from last point to cubic end on y-axis + @return reference to SkPath + */ + SkPath& rCubicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2, + SkScalar dx3, SkScalar dy3); + + /** Appends arc to SkPath. Arc added is part of ellipse + bounded by oval, from startAngle through sweepAngle. Both startAngle and + sweepAngle are measured in degrees, where zero degrees is aligned with the + positive x-axis, and positive sweeps extends arc clockwise. + + arcTo() adds line connecting SkPath last SkPoint to initial arc SkPoint if forceMoveTo + is false and SkPath is not empty. Otherwise, added contour begins with first point + of arc. Angles greater than -360 and less than 360 are treated modulo 360. + + @param oval bounds of ellipse containing arc + @param startAngle starting angle of arc in degrees + @param sweepAngle sweep, in degrees. Positive is clockwise; treated modulo 360 + @param forceMoveTo true to start a new contour with arc + @return reference to SkPath + */ + SkPath& arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo); + + /** Appends arc to SkPath, after appending line if needed. Arc is implemented by conic + weighted to describe part of circle. Arc is contained by tangent from + last SkPath point to (x1, y1), and tangent from (x1, y1) to (x2, y2). Arc + is part of circle sized to radius, positioned so it touches both tangent lines. + + If last Path Point does not start Arc, arcTo appends connecting Line to Path. + The length of Vector from (x1, y1) to (x2, y2) does not affect Arc. + + Arc sweep is always less than 180 degrees. If radius is zero, or if + tangents are nearly parallel, arcTo appends Line from last Path Point to (x1, y1). + + arcTo appends at most one Line and one conic. + arcTo implements the functionality of PostScript arct and HTML Canvas arcTo. + + @param x1 x-axis value common to pair of tangents + @param y1 y-axis value common to pair of tangents + @param x2 x-axis value end of second tangent + @param y2 y-axis value end of second tangent + @param radius distance from arc to circle center + @return reference to SkPath + */ + SkPath& arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius); + + /** Appends arc to SkPath, after appending line if needed. Arc is implemented by conic + weighted to describe part of circle. Arc is contained by tangent from + last SkPath point to p1, and tangent from p1 to p2. Arc + is part of circle sized to radius, positioned so it touches both tangent lines. + + If last SkPath SkPoint does not start arc, arcTo() appends connecting line to SkPath. + The length of vector from p1 to p2 does not affect arc. + + Arc sweep is always less than 180 degrees. If radius is zero, or if + tangents are nearly parallel, arcTo() appends line from last SkPath SkPoint to p1. + + arcTo() appends at most one line and one conic. + arcTo() implements the functionality of PostScript arct and HTML Canvas arcTo. + + @param p1 SkPoint common to pair of tangents + @param p2 end of second tangent + @param radius distance from arc to circle center + @return reference to SkPath + */ + SkPath& arcTo(const SkPoint p1, const SkPoint p2, SkScalar radius) { + return this->arcTo(p1.fX, p1.fY, p2.fX, p2.fY, radius); + } + + /** \enum SkPath::ArcSize + Four oval parts with radii (rx, ry) start at last SkPath SkPoint and ends at (x, y). + ArcSize and Direction select one of the four oval parts. + */ + enum ArcSize { + kSmall_ArcSize, //!< smaller of arc pair + kLarge_ArcSize, //!< larger of arc pair + }; + + /** Appends arc to SkPath. Arc is implemented by one or more conics weighted to + describe part of oval with radii (rx, ry) rotated by xAxisRotate degrees. Arc + curves from last SkPath SkPoint to (x, y), choosing one of four possible routes: + clockwise or counterclockwise, and smaller or larger. + + Arc sweep is always less than 360 degrees. arcTo() appends line to (x, y) if + either radii are zero, or if last SkPath SkPoint equals (x, y). arcTo() scales radii + (rx, ry) to fit last SkPath SkPoint and (x, y) if both are greater than zero but + too small. + + arcTo() appends up to four conic curves. + arcTo() implements the functionality of SVG arc, although SVG sweep-flag value + is opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, + while kCW_Direction cast to int is zero. + + @param rx radius on x-axis before x-axis rotation + @param ry radius on y-axis before x-axis rotation + @param xAxisRotate x-axis rotation in degrees; positive values are clockwise + @param largeArc chooses smaller or larger arc + @param sweep chooses clockwise or counterclockwise arc + @param x end of arc + @param y end of arc + @return reference to SkPath + */ + SkPath& arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, + Direction sweep, SkScalar x, SkScalar y); + + /** Appends arc to SkPath. Arc is implemented by one or more conic weighted to describe + part of oval with radii (r.fX, r.fY) rotated by xAxisRotate degrees. Arc curves + from last SkPath SkPoint to (xy.fX, xy.fY), choosing one of four possible routes: + clockwise or counterclockwise, + and smaller or larger. + + Arc sweep is always less than 360 degrees. arcTo() appends line to xy if either + radii are zero, or if last SkPath SkPoint equals (xy.fX, xy.fY). arcTo() scales radii r to + fit last SkPath SkPoint and xy if both are greater than zero but too small to describe + an arc. + + arcTo() appends up to four conic curves. + arcTo() implements the functionality of SVG arc, although SVG sweep-flag value is + opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while + kCW_Direction cast to int is zero. + + @param r radii on axes before x-axis rotation + @param xAxisRotate x-axis rotation in degrees; positive values are clockwise + @param largeArc chooses smaller or larger arc + @param sweep chooses clockwise or counterclockwise arc + @param xy end of arc + @return reference to SkPath + */ + SkPath& arcTo(const SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep, + const SkPoint xy) { + return this->arcTo(r.fX, r.fY, xAxisRotate, largeArc, sweep, xy.fX, xy.fY); + } + + /** Appends arc to SkPath, relative to last SkPath SkPoint. Arc is implemented by one or + more conic, weighted to describe part of oval with radii (rx, ry) rotated by + xAxisRotate degrees. Arc curves from last SkPath SkPoint to relative end SkPoint: + (dx, dy), choosing one of four possible routes: clockwise or + counterclockwise, and smaller or larger. If SkPath is empty, the start arc SkPoint + is (0, 0). + + Arc sweep is always less than 360 degrees. arcTo() appends line to end SkPoint + if either radii are zero, or if last SkPath SkPoint equals end SkPoint. + arcTo() scales radii (rx, ry) to fit last SkPath SkPoint and end SkPoint if both are + greater than zero but too small to describe an arc. + + arcTo() appends up to four conic curves. + arcTo() implements the functionality of svg arc, although SVG "sweep-flag" value is + opposite the integer value of sweep; SVG "sweep-flag" uses 1 for clockwise, while + kCW_Direction cast to int is zero. + + @param rx radius before x-axis rotation + @param ry radius before x-axis rotation + @param xAxisRotate x-axis rotation in degrees; positive values are clockwise + @param largeArc chooses smaller or larger arc + @param sweep chooses clockwise or counterclockwise arc + @param dx x-axis offset end of arc from last SkPath SkPoint + @param dy y-axis offset end of arc from last SkPath SkPoint + @return reference to SkPath + */ + SkPath& rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, + Direction sweep, SkScalar dx, SkScalar dy); + + /** Appends kClose_Verb to SkPath. A closed contour connects the first and last SkPoint + with line, forming a continuous loop. Open and closed contour draw the same + with SkPaint::kFill_Style. With SkPaint::kStroke_Style, open contour draws + SkPaint::Cap at contour start and end; closed contour draws + SkPaint::Join at contour start and end. + + close() has no effect if SkPath is empty or last SkPath SkPath::Verb is kClose_Verb. + + @return reference to SkPath + */ + SkPath& close(); + + /** Returns true if fill is inverted and SkPath with fill represents area outside + of its geometric bounds. + + @param fill one of: kWinding_FillType, kEvenOdd_FillType, + kInverseWinding_FillType, kInverseEvenOdd_FillType + @return true if SkPath fills outside its bounds + */ + static bool IsInverseFillType(FillType fill) { + static_assert(0 == kWinding_FillType, "fill_type_mismatch"); + static_assert(1 == kEvenOdd_FillType, "fill_type_mismatch"); + static_assert(2 == kInverseWinding_FillType, "fill_type_mismatch"); + static_assert(3 == kInverseEvenOdd_FillType, "fill_type_mismatch"); + return (fill & 2) != 0; + } + + /** Returns equivalent SkPath::FillType representing SkPath fill inside its bounds. + . + + @param fill one of: kWinding_FillType, kEvenOdd_FillType, + kInverseWinding_FillType, kInverseEvenOdd_FillType + @return fill, or kWinding_FillType or kEvenOdd_FillType if fill is inverted + */ + static FillType ConvertToNonInverseFillType(FillType fill) { + static_assert(0 == kWinding_FillType, "fill_type_mismatch"); + static_assert(1 == kEvenOdd_FillType, "fill_type_mismatch"); + static_assert(2 == kInverseWinding_FillType, "fill_type_mismatch"); + static_assert(3 == kInverseEvenOdd_FillType, "fill_type_mismatch"); + return (FillType)(fill & 1); + } + + /** Approximates conic with quad array. Conic is constructed from start SkPoint p0, + control SkPoint p1, end SkPoint p2, and weight w. + Quad array is stored in pts; this storage is supplied by caller. + Maximum quad count is 2 to the pow2. + Every third point in array shares last SkPoint of previous quad and first SkPoint of + next quad. Maximum pts storage size is given by: + (1 + 2 * (1 << pow2)) * sizeof(SkPoint). + + Returns quad count used the approximation, which may be smaller + than the number requested. + + conic weight determines the amount of influence conic control point has on the curve. + w less than one represents an elliptical section. w greater than one represents + a hyperbolic section. w equal to one represents a parabolic section. + + Two quad curves are sufficient to approximate an elliptical conic with a sweep + of up to 90 degrees; in this case, set pow2 to one. + + @param p0 conic start SkPoint + @param p1 conic control SkPoint + @param p2 conic end SkPoint + @param w conic weight + @param pts storage for quad array + @param pow2 quad count, as power of two, normally 0 to 5 (1 to 32 quad curves) + @return number of quad curves written to pts + */ + static int ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, + SkScalar w, SkPoint pts[], int pow2); + + /** Returns true if SkPath is equivalent to SkRect when filled. + If false: rect, isClosed, and direction are unchanged. + If true: rect, isClosed, and direction are written to if not nullptr. + + rect may be smaller than the SkPath bounds. SkPath bounds may include kMove_Verb points + that do not alter the area drawn by the returned rect. + + @param rect storage for bounds of SkRect; may be nullptr + @param isClosed storage set to true if SkPath is closed; may be nullptr + @param direction storage set to SkRect direction; may be nullptr + @return true if SkPath contains SkRect + */ + bool isRect(SkRect* rect, bool* isClosed = nullptr, Direction* direction = nullptr) const; + + /** Adds SkRect to SkPath, appending kMove_Verb, three kLine_Verb, and kClose_Verb, + starting with top-left corner of SkRect; followed by top-right, bottom-right, + and bottom-left if dir is kCW_Direction; or followed by bottom-left, + bottom-right, and top-right if dir is kCCW_Direction. + + @param rect SkRect to add as a closed contour + @param dir SkPath::Direction to wind added contour + @return reference to SkPath + */ + SkPath& addRect(const SkRect& rect, Direction dir = kCW_Direction); + + /** Adds SkRect to SkPath, appending kMove_Verb, three kLine_Verb, and kClose_Verb. + If dir is kCW_Direction, SkRect corners are added clockwise; if dir is + kCCW_Direction, SkRect corners are added counterclockwise. + start determines the first corner added. + + @param rect SkRect to add as a closed contour + @param dir SkPath::Direction to wind added contour + @param start initial corner of SkRect to add + @return reference to SkPath + */ + SkPath& addRect(const SkRect& rect, Direction dir, unsigned start); + + /** Adds SkRect (left, top, right, bottom) to SkPath, + appending kMove_Verb, three kLine_Verb, and kClose_Verb, + starting with top-left corner of SkRect; followed by top-right, bottom-right, + and bottom-left if dir is kCW_Direction; or followed by bottom-left, + bottom-right, and top-right if dir is kCCW_Direction. + + @param left smaller x-axis value of SkRect + @param top smaller y-axis value of SkRect + @param right larger x-axis value of SkRect + @param bottom larger y-axis value of SkRect + @param dir SkPath::Direction to wind added contour + @return reference to SkPath + */ + SkPath& addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom, + Direction dir = kCW_Direction); + + /** Adds oval to path, appending kMove_Verb, four kConic_Verb, and kClose_Verb. + Oval is upright ellipse bounded by SkRect oval with radii equal to half oval width + and half oval height. Oval begins at (oval.fRight, oval.centerY()) and continues + clockwise if dir is kCW_Direction, counterclockwise if dir is kCCW_Direction. + + @param oval bounds of ellipse added + @param dir SkPath::Direction to wind ellipse + @return reference to SkPath + */ + SkPath& addOval(const SkRect& oval, Direction dir = kCW_Direction); + + /** Adds oval to SkPath, appending kMove_Verb, four kConic_Verb, and kClose_Verb. + Oval is upright ellipse bounded by SkRect oval with radii equal to half oval width + and half oval height. Oval begins at start and continues + clockwise if dir is kCW_Direction, counterclockwise if dir is kCCW_Direction. + + @param oval bounds of ellipse added + @param dir SkPath::Direction to wind ellipse + @param start index of initial point of ellipse + @return reference to SkPath + */ + SkPath& addOval(const SkRect& oval, Direction dir, unsigned start); + + /** Adds circle centered at (x, y) of size radius to SkPath, appending kMove_Verb, + four kConic_Verb, and kClose_Verb. Circle begins at: (x + radius, y), continuing + clockwise if dir is kCW_Direction, and counterclockwise if dir is kCCW_Direction. + + Has no effect if radius is zero or negative. + + @param x center of circle + @param y center of circle + @param radius distance from center to edge + @param dir SkPath::Direction to wind circle + @return reference to SkPath + */ + SkPath& addCircle(SkScalar x, SkScalar y, SkScalar radius, + Direction dir = kCW_Direction); + + /** Appends arc to SkPath, as the start of new contour. Arc added is part of ellipse + bounded by oval, from startAngle through sweepAngle. Both startAngle and + sweepAngle are measured in degrees, where zero degrees is aligned with the + positive x-axis, and positive sweeps extends arc clockwise. + + If sweepAngle <= -360, or sweepAngle >= 360; and startAngle modulo 90 is nearly + zero, append oval instead of arc. Otherwise, sweepAngle values are treated + modulo 360, and arc may or may not draw depending on numeric rounding. + + @param oval bounds of ellipse containing arc + @param startAngle starting angle of arc in degrees + @param sweepAngle sweep, in degrees. Positive is clockwise; treated modulo 360 + @return reference to SkPath + */ + SkPath& addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle); + + /** Appends SkRRect to SkPath, creating a new closed contour. SkRRect has bounds + equal to rect; each corner is 90 degrees of an ellipse with radii (rx, ry). If + dir is kCW_Direction, SkRRect starts at top-left of the lower-left corner and + winds clockwise. If dir is kCCW_Direction, SkRRect starts at the bottom-left + of the upper-left corner and winds counterclockwise. + + If either rx or ry is too large, rx and ry are scaled uniformly until the + corners fit. If rx or ry is less than or equal to zero, addRoundRect() appends + SkRect rect to SkPath. + + After appending, SkPath may be empty, or may contain: SkRect, oval, or SkRRect. + + @param rect bounds of SkRRect + @param rx x-axis radius of rounded corners on the SkRRect + @param ry y-axis radius of rounded corners on the SkRRect + @param dir SkPath::Direction to wind SkRRect + @return reference to SkPath + */ + SkPath& addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, + Direction dir = kCW_Direction); + + /** Appends SkRRect to SkPath, creating a new closed contour. SkRRect has bounds + equal to rect; each corner is 90 degrees of an ellipse with radii from the + array. + + @param rect bounds of SkRRect + @param radii array of 8 SkScalar values, a radius pair for each corner + @param dir SkPath::Direction to wind SkRRect + @return reference to SkPath + */ + SkPath& addRoundRect(const SkRect& rect, const SkScalar radii[], + Direction dir = kCW_Direction); + + /** Adds rrect to SkPath, creating a new closed contour. If + dir is kCW_Direction, rrect starts at top-left of the lower-left corner and + winds clockwise. If dir is kCCW_Direction, rrect starts at the bottom-left + of the upper-left corner and winds counterclockwise. + + After appending, SkPath may be empty, or may contain: SkRect, oval, or SkRRect. + + @param rrect bounds and radii of rounded rectangle + @param dir SkPath::Direction to wind SkRRect + @return reference to SkPath + */ + SkPath& addRRect(const SkRRect& rrect, Direction dir = kCW_Direction); + + /** Adds rrect to SkPath, creating a new closed contour. If dir is kCW_Direction, rrect + winds clockwise; if dir is kCCW_Direction, rrect winds counterclockwise. + start determines the first point of rrect to add. + + @param rrect bounds and radii of rounded rectangle + @param dir SkPath::Direction to wind SkRRect + @param start index of initial point of SkRRect + @return reference to SkPath + */ + SkPath& addRRect(const SkRRect& rrect, Direction dir, unsigned start); + + /** Adds contour created from line array, adding (count - 1) line segments. + Contour added starts at pts[0], then adds a line for every additional SkPoint + in pts array. If close is true, appends kClose_Verb to SkPath, connecting + pts[count - 1] and pts[0]. + + If count is zero, append kMove_Verb to path. + Has no effect if count is less than one. + + @param pts array of line sharing end and start SkPoint + @param count length of SkPoint array + @param close true to add line connecting contour end and start + @return reference to SkPath + */ + SkPath& addPoly(const SkPoint pts[], int count, bool close); + + /** Adds contour created from list. Contour added starts at list[0], then adds a line + for every additional SkPoint in list. If close is true, appends kClose_Verb to SkPath, + connecting last and first SkPoint in list. + + If list is empty, append kMove_Verb to path. + + @param list array of SkPoint + @param close true to add line connecting contour end and start + @return reference to SkPath + */ + SkPath& addPoly(const std::initializer_list& list, bool close) { + return this->addPoly(list.begin(), SkToInt(list.size()), close); + } + + /** \enum SkPath::AddPathMode + AddPathMode chooses how addPath() appends. Adding one SkPath to another can extend + the last contour or start a new contour. + */ + enum AddPathMode { + kAppend_AddPathMode, //!< appended to destination unaltered + kExtend_AddPathMode, //!< add line if prior contour is not closed + }; + + /** Appends src to SkPath, offset by (dx, dy). + + If mode is kAppend_AddPathMode, src verb array, SkPoint array, and conic weights are + added unaltered. If mode is kExtend_AddPathMode, add line before appending + verbs, SkPoint, and conic weights. + + @param src SkPath verbs, SkPoint, and conic weights to add + @param dx offset added to src SkPoint array x-axis coordinates + @param dy offset added to src SkPoint array y-axis coordinates + @param mode kAppend_AddPathMode or kExtend_AddPathMode + @return reference to SkPath + */ + SkPath& addPath(const SkPath& src, SkScalar dx, SkScalar dy, + AddPathMode mode = kAppend_AddPathMode); + + /** Appends src to SkPath. + + If mode is kAppend_AddPathMode, src verb array, SkPoint array, and conic weights are + added unaltered. If mode is kExtend_AddPathMode, add line before appending + verbs, SkPoint, and conic weights. + + @param src SkPath verbs, SkPoint, and conic weights to add + @param mode kAppend_AddPathMode or kExtend_AddPathMode + @return reference to SkPath + */ + SkPath& addPath(const SkPath& src, AddPathMode mode = kAppend_AddPathMode) { + SkMatrix m; + m.reset(); + return this->addPath(src, m, mode); + } + + /** Appends src to SkPath, transformed by matrix. Transformed curves may have different + verbs, SkPoint, and conic weights. + + If mode is kAppend_AddPathMode, src verb array, SkPoint array, and conic weights are + added unaltered. If mode is kExtend_AddPathMode, add line before appending + verbs, SkPoint, and conic weights. + + @param src SkPath verbs, SkPoint, and conic weights to add + @param matrix transform applied to src + @param mode kAppend_AddPathMode or kExtend_AddPathMode + @return reference to SkPath + */ + SkPath& addPath(const SkPath& src, const SkMatrix& matrix, + AddPathMode mode = kAppend_AddPathMode); + + /** Appends src to SkPath, from back to front. + Reversed src always appends a new contour to SkPath. + + @param src SkPath verbs, SkPoint, and conic weights to add + @return reference to SkPath + */ + SkPath& reverseAddPath(const SkPath& src); + + /** Offsets SkPoint array by (dx, dy). Offset SkPath replaces dst. + If dst is nullptr, SkPath is replaced by offset data. + + @param dx offset added to SkPoint array x-axis coordinates + @param dy offset added to SkPoint array y-axis coordinates + @param dst overwritten, translated copy of SkPath; may be nullptr + */ + void offset(SkScalar dx, SkScalar dy, SkPath* dst) const; + + /** Offsets SkPoint array by (dx, dy). SkPath is replaced by offset data. + + @param dx offset added to SkPoint array x-axis coordinates + @param dy offset added to SkPoint array y-axis coordinates + */ + void offset(SkScalar dx, SkScalar dy) { + this->offset(dx, dy, this); + } + + /** Transforms verb array, SkPoint array, and weight by matrix. + transform may change verbs and increase their number. + Transformed SkPath replaces dst; if dst is nullptr, original data + is replaced. + + @param matrix SkMatrix to apply to SkPath + @param dst overwritten, transformed copy of SkPath; may be nullptr + */ + void transform(const SkMatrix& matrix, SkPath* dst) const; + + /** Transforms verb array, SkPoint array, and weight by matrix. + transform may change verbs and increase their number. + SkPath is replaced by transformed data. + + @param matrix SkMatrix to apply to SkPath + */ + void transform(const SkMatrix& matrix) { + this->transform(matrix, this); + } + + /** Returns last point on SkPath in lastPt. Returns false if SkPoint array is empty, + storing (0, 0) if lastPt is not nullptr. + + @param lastPt storage for final SkPoint in SkPoint array; may be nullptr + @return true if SkPoint array contains one or more SkPoint + */ + bool getLastPt(SkPoint* lastPt) const; + + /** Sets last point to (x, y). If SkPoint array is empty, append kMove_Verb to + verb array and append (x, y) to SkPoint array. + + @param x set x-axis value of last point + @param y set y-axis value of last point + */ + void setLastPt(SkScalar x, SkScalar y); + + /** Sets the last point on the path. If SkPoint array is empty, append kMove_Verb to + verb array and append p to SkPoint array. + + @param p set value of last point + */ + void setLastPt(const SkPoint& p) { + this->setLastPt(p.fX, p.fY); + } + + /** \enum SkPath::SegmentMask + SegmentMask constants correspond to each drawing Verb type in SkPath; for + instance, if SkPath only contains lines, only the kLine_SegmentMask bit is set. + */ + enum SegmentMask { + kLine_SegmentMask = kLine_SkPathSegmentMask, + kQuad_SegmentMask = kQuad_SkPathSegmentMask, + kConic_SegmentMask = kConic_SkPathSegmentMask, + kCubic_SegmentMask = kCubic_SkPathSegmentMask, + }; + + /** Returns a mask, where each set bit corresponds to a SegmentMask constant + if SkPath contains one or more verbs of that type. + Returns zero if SkPath contains no lines, or curves: quads, conics, or cubics. + + getSegmentMasks() returns a cached result; it is very fast. + + @return SegmentMask bits or zero + */ + uint32_t getSegmentMasks() const { return fPathRef->getSegmentMasks(); } + + /** \enum SkPath::Verb + Verb instructs SkPath how to interpret one or more SkPoint and optional conic weight; + manage contour, and terminate SkPath. + */ + enum Verb { + kMove_Verb = static_cast(SkPathVerb::kMove), + kLine_Verb = static_cast(SkPathVerb::kLine), + kQuad_Verb = static_cast(SkPathVerb::kQuad), + kConic_Verb = static_cast(SkPathVerb::kConic), + kCubic_Verb = static_cast(SkPathVerb::kCubic), + kClose_Verb = static_cast(SkPathVerb::kClose), + kDone_Verb = static_cast(SkPathVerb::kDone), + }; + + /** \class SkPath::Iter + Iterates through verb array, and associated SkPoint array and conic weight. + Provides options to treat open contours as closed, and to ignore + degenerate data. + */ + class SK_API Iter { + public: + + /** Initializes SkPath::Iter with an empty SkPath. next() on SkPath::Iter returns + kDone_Verb. + Call setPath to initialize SkPath::Iter at a later time. + + @return SkPath::Iter of empty SkPath + */ + Iter(); + + /** Sets SkPath::Iter to return elements of verb array, SkPoint array, and conic weight in + path. If forceClose is true, SkPath::Iter will add kLine_Verb and kClose_Verb after each + open contour. path is not altered. + + @param path SkPath to iterate + @param forceClose true if open contours generate kClose_Verb + @return SkPath::Iter of path + */ + Iter(const SkPath& path, bool forceClose); + + /** Sets SkPath::Iter to return elements of verb array, SkPoint array, and conic weight in + path. If forceClose is true, SkPath::Iter will add kLine_Verb and kClose_Verb after each + open contour. path is not altered. + + @param path SkPath to iterate + @param forceClose true if open contours generate kClose_Verb + */ + void setPath(const SkPath& path, bool forceClose); + + /** Returns next SkPath::Verb in verb array, and advances SkPath::Iter. + When verb array is exhausted, returns kDone_Verb. + + Zero to four SkPoint are stored in pts, depending on the returned SkPath::Verb. + + @param pts storage for SkPoint data describing returned SkPath::Verb + @return next SkPath::Verb from verb array + */ + Verb next(SkPoint pts[4]); + + // DEPRECATED + Verb next(SkPoint pts[4], bool /*doConsumeDegenerates*/, bool /*exact*/ = false) { + return this->next(pts); + } + + /** Returns conic weight if next() returned kConic_Verb. + + If next() has not been called, or next() did not return kConic_Verb, + result is undefined. + + @return conic weight for conic SkPoint returned by next() + */ + SkScalar conicWeight() const { return *fConicWeights; } + + /** Returns true if last kLine_Verb returned by next() was generated + by kClose_Verb. When true, the end point returned by next() is + also the start point of contour. + + If next() has not been called, or next() did not return kLine_Verb, + result is undefined. + + @return true if last kLine_Verb was generated by kClose_Verb + */ + bool isCloseLine() const { return SkToBool(fCloseLine); } + + /** Returns true if subsequent calls to next() return kClose_Verb before returning + kMove_Verb. if true, contour SkPath::Iter is processing may end with kClose_Verb, or + SkPath::Iter may have been initialized with force close set to true. + + @return true if contour is closed + */ + bool isClosedContour() const; + + private: + const SkPoint* fPts; + const uint8_t* fVerbs; + const uint8_t* fVerbStop; + const SkScalar* fConicWeights; + SkPoint fMoveTo; + SkPoint fLastPt; + bool fForceClose; + bool fNeedClose; + bool fCloseLine; + enum SegmentState : uint8_t { + /** The current contour is empty. Starting processing or have just closed a contour. */ + kEmptyContour_SegmentState, + /** Have seen a move, but nothing else. */ + kAfterMove_SegmentState, + /** Have seen a primitive but not yet closed the path. Also the initial state. */ + kAfterPrimitive_SegmentState + }; + SegmentState fSegmentState; + + inline const SkPoint& cons_moveTo(); + Verb autoClose(SkPoint pts[2]); + }; + + /** \class SkPath::RawIter + Iterates through verb array, and associated SkPoint array and conic weight. + verb array, SkPoint array, and conic weight are returned unaltered. + */ + class SK_API RawIter { + public: + + /** Initializes RawIter with an empty SkPath. next() on RawIter returns kDone_Verb. + Call setPath to initialize SkPath::Iter at a later time. + + @return RawIter of empty SkPath + */ + RawIter() {} + + /** Sets RawIter to return elements of verb array, SkPoint array, and conic weight in path. + + @param path SkPath to iterate + @return RawIter of path + */ + RawIter(const SkPath& path) { + setPath(path); + } + + /** Sets SkPath::Iter to return elements of verb array, SkPoint array, and conic weight in + path. + + @param path SkPath to iterate + */ + void setPath(const SkPath& path) { + fRawIter.setPathRef(*path.fPathRef.get()); + } + + /** Returns next SkPath::Verb in verb array, and advances RawIter. + When verb array is exhausted, returns kDone_Verb. + Zero to four SkPoint are stored in pts, depending on the returned SkPath::Verb. + + @param pts storage for SkPoint data describing returned SkPath::Verb + @return next SkPath::Verb from verb array + */ + Verb next(SkPoint pts[4]) { + return (Verb) fRawIter.next(pts); + } + + /** Returns next SkPath::Verb, but does not advance RawIter. + + @return next SkPath::Verb from verb array + */ + Verb peek() const { + return (Verb) fRawIter.peek(); + } + + /** Returns conic weight if next() returned kConic_Verb. + + If next() has not been called, or next() did not return kConic_Verb, + result is undefined. + + @return conic weight for conic SkPoint returned by next() + */ + SkScalar conicWeight() const { + return fRawIter.conicWeight(); + } + + private: + SkPathRef::Iter fRawIter; + friend class SkPath; + + }; + + /** Returns true if the point (x, y) is contained by SkPath, taking into + account FillType. + + @param x x-axis value of containment test + @param y y-axis value of containment test + @return true if SkPoint is in SkPath + */ + bool contains(SkScalar x, SkScalar y) const; + + /** Writes text representation of SkPath to stream. If stream is nullptr, writes to + standard output. Set forceClose to true to get edges used to fill SkPath. + Set dumpAsHex true to generate exact binary representations + of floating point numbers used in SkPoint array and conic weights. + + @param stream writable SkWStream receiving SkPath text representation; may be nullptr + @param forceClose true if missing kClose_Verb is output + @param dumpAsHex true if SkScalar values are written as hexadecimal + */ + void dump(SkWStream* stream, bool forceClose, bool dumpAsHex) const; + + /** Writes text representation of SkPath to standard output. The representation may be + directly compiled as C++ code. Floating point values are written + with limited precision; it may not be possible to reconstruct original SkPath + from output. + */ + void dump() const; + + /** Writes text representation of SkPath to standard output. The representation may be + directly compiled as C++ code. Floating point values are written + in hexadecimal to preserve their exact bit pattern. The output reconstructs the + original SkPath. + + Use instead of dump() when submitting + */ + void dumpHex() const; + + /** Writes SkPath to buffer, returning the number of bytes written. + Pass nullptr to obtain the storage size. + + Writes SkPath::FillType, verb array, SkPoint array, conic weight, and + additionally writes computed information like SkPath::Convexity and bounds. + + Use only be used in concert with readFromMemory(); + the format used for SkPath in memory is not guaranteed. + + @param buffer storage for SkPath; may be nullptr + @return size of storage required for SkPath; always a multiple of 4 + */ + size_t writeToMemory(void* buffer) const; + + /** Writes SkPath to buffer, returning the buffer written to, wrapped in SkData. + + serialize() writes SkPath::FillType, verb array, SkPoint array, conic weight, and + additionally writes computed information like SkPath::Convexity and bounds. + + serialize() should only be used in concert with readFromMemory(). + The format used for SkPath in memory is not guaranteed. + + @return SkPath data wrapped in SkData buffer + */ + sk_sp serialize() const; + + /** Initializes SkPath from buffer of size length. Returns zero if the buffer is + data is inconsistent, or the length is too small. + + Reads SkPath::FillType, verb array, SkPoint array, conic weight, and + additionally reads computed information like SkPath::Convexity and bounds. + + Used only in concert with writeToMemory(); + the format used for SkPath in memory is not guaranteed. + + @param buffer storage for SkPath + @param length buffer size in bytes; must be multiple of 4 + @return number of bytes read, or zero on failure + */ + size_t readFromMemory(const void* buffer, size_t length); + + /** (See Skia bug 1762.) + Returns a non-zero, globally unique value. A different value is returned + if verb array, SkPoint array, or conic weight changes. + + Setting SkPath::FillType does not change generation identifier. + + Each time the path is modified, a different generation identifier will be returned. + SkPath::FillType does affect generation identifier on Android framework. + + @return non-zero, globally unique value + */ + uint32_t getGenerationID() const; + + /** Returns if SkPath data is consistent. Corrupt SkPath data is detected if + internal values are out of range or internal storage does not match + array dimensions. + + @return true if SkPath data is consistent + */ + bool isValid() const { return this->isValidImpl() && fPathRef->isValid(); } + +private: + sk_sp fPathRef; + int fLastMoveToIndex; + mutable std::atomic fConvexity; + mutable std::atomic fFirstDirection; // really an SkPathPriv::FirstDirection + uint8_t fFillType : 2; + uint8_t fIsVolatile : 1; + + /** Resets all fields other than fPathRef to their initial 'empty' values. + * Assumes the caller has already emptied fPathRef. + * On Android increments fGenerationID without reseting it. + */ + void resetFields(); + + /** Sets all fields other than fPathRef to the values in 'that'. + * Assumes the caller has already set fPathRef. + * Doesn't change fGenerationID or fSourcePath on Android. + */ + void copyFields(const SkPath& that); + + size_t writeToMemoryAsRRect(void* buffer) const; + size_t readAsRRect(const void*, size_t); + size_t readFromMemory_EQ4Or5(const void*, size_t); + + friend class Iter; + friend class SkPathPriv; + friend class SkPathStroker; + + /* Append, in reverse order, the first contour of path, ignoring path's + last point. If no moveTo() call has been made for this contour, the + first point is automatically set to (0,0). + */ + SkPath& reversePathTo(const SkPath&); + + // called before we add points for lineTo, quadTo, cubicTo, checking to see + // if we need to inject a leading moveTo first + // + // SkPath path; path.lineTo(...); <--- need a leading moveTo(0, 0) + // SkPath path; ... path.close(); path.lineTo(...) <-- need a moveTo(previous moveTo) + // + inline void injectMoveToIfNeeded(); + + inline bool hasOnlyMoveTos() const; + + Convexity internalGetConvexity() const; + + /** Asserts if SkPath data is inconsistent. + Debugging check intended for internal use only. + */ + SkDEBUGCODE(void validate() const { SkASSERT(this->isValidImpl()); } ) + bool isValidImpl() const; + SkDEBUGCODE(void validateRef() const { fPathRef->validate(); } ) + + // called by stroker to see if all points (in the last contour) are equal and worthy of a cap + bool isZeroLengthSincePoint(int startPtIndex) const; + + /** Returns if the path can return a bound at no cost (true) or will have to + perform some computation (false). + */ + bool hasComputedBounds() const { + SkDEBUGCODE(this->validate();) + return fPathRef->hasComputedBounds(); + } + + + // 'rect' needs to be sorted + void setBounds(const SkRect& rect) { + SkPathRef::Editor ed(&fPathRef); + + ed.setBounds(rect); + } + + void setPt(int index, SkScalar x, SkScalar y); + + // Bottlenecks for working with fConvexity and fFirstDirection. + // Notice the setters are const... these are mutable atomic fields. + void setConvexity(Convexity) const; + void setFirstDirection(uint8_t) const; + uint8_t getFirstDirection() const; + + friend class SkAutoPathBoundsUpdate; + friend class SkAutoDisableOvalCheck; + friend class SkAutoDisableDirectionCheck; + friend class SkPathEdgeIter; + friend class SkPathWriter; + friend class SkOpBuilder; + friend class SkBench_AddPathTest; // perf test reversePathTo + friend class PathTest_Private; // unit test reversePathTo + friend class ForceIsRRect_Private; // unit test isRRect + friend class FuzzPath; // for legacy access to validateRef +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPathEffect.h b/gfx/skia/skia/include/core/SkPathEffect.h new file mode 100644 index 0000000000..1a96810f00 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPathEffect.h @@ -0,0 +1,187 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathEffect_DEFINED +#define SkPathEffect_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" + +class SkPath; +class SkStrokeRec; + +/** \class SkPathEffect + + SkPathEffect is the base class for objects in the SkPaint that affect + the geometry of a drawing primitive before it is transformed by the + canvas' matrix and drawn. + + Dashing is implemented as a subclass of SkPathEffect. +*/ +class SK_API SkPathEffect : public SkFlattenable { +public: + /** + * Returns a patheffect that apples each effect (first and second) to the original path, + * and returns a path with the sum of these. + * + * result = first(path) + second(path) + * + */ + static sk_sp MakeSum(sk_sp first, sk_sp second); + + /** + * Returns a patheffect that applies the inner effect to the path, and then applies the + * outer effect to the result of the inner's. + * + * result = outer(inner(path)) + */ + static sk_sp MakeCompose(sk_sp outer, sk_sp inner); + + /** + * Given a src path (input) and a stroke-rec (input and output), apply + * this effect to the src path, returning the new path in dst, and return + * true. If this effect cannot be applied, return false and ignore dst + * and stroke-rec. + * + * The stroke-rec specifies the initial request for stroking (if any). + * The effect can treat this as input only, or it can choose to change + * the rec as well. For example, the effect can decide to change the + * stroke's width or join, or the effect can change the rec from stroke + * to fill (or fill to stroke) in addition to returning a new (dst) path. + * + * If this method returns true, the caller will apply (as needed) the + * resulting stroke-rec to dst and then draw. + */ + bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect* cullR) const; + + /** + * Compute a conservative bounds for its effect, given the src bounds. + * The baseline implementation just assigns src to dst. + */ + void computeFastBounds(SkRect* dst, const SkRect& src) const; + + /** \class PointData + + PointData aggregates all the information needed to draw the point + primitives returned by an 'asPoints' call. + */ + class PointData { + public: + PointData() + : fFlags(0) + , fPoints(nullptr) + , fNumPoints(0) { + fSize.set(SK_Scalar1, SK_Scalar1); + // 'asPoints' needs to initialize/fill-in 'fClipRect' if it sets + // the kUseClip flag + } + ~PointData() { + delete [] fPoints; + } + + // TODO: consider using passed-in flags to limit the work asPoints does. + // For example, a kNoPath flag could indicate don't bother generating + // stamped solutions. + + // Currently none of these flags are supported. + enum PointFlags { + kCircles_PointFlag = 0x01, // draw points as circles (instead of rects) + kUsePath_PointFlag = 0x02, // draw points as stamps of the returned path + kUseClip_PointFlag = 0x04, // apply 'fClipRect' before drawing the points + }; + + uint32_t fFlags; // flags that impact the drawing of the points + SkPoint* fPoints; // the center point of each generated point + int fNumPoints; // number of points in fPoints + SkVector fSize; // the size to draw the points + SkRect fClipRect; // clip required to draw the points (if kUseClip is set) + SkPath fPath; // 'stamp' to be used at each point (if kUsePath is set) + + SkPath fFirst; // If not empty, contains geometry for first point + SkPath fLast; // If not empty, contains geometry for last point + }; + + /** + * Does applying this path effect to 'src' yield a set of points? If so, + * optionally return the points in 'results'. + */ + bool asPoints(PointData* results, const SkPath& src, + const SkStrokeRec&, const SkMatrix&, + const SkRect* cullR) const; + + /** + * If the PathEffect can be represented as a dash pattern, asADash will return kDash_DashType + * and None otherwise. If a non NULL info is passed in, the various DashInfo will be filled + * in if the PathEffect can be a dash pattern. If passed in info has an fCount equal or + * greater to that of the effect, it will memcpy the values of the dash intervals into the + * info. Thus the general approach will be call asADash once with default info to get DashType + * and fCount. If effect can be represented as a dash pattern, allocate space for the intervals + * in info, then call asADash again with the same info and the intervals will get copied in. + */ + + enum DashType { + kNone_DashType, //!< ignores the info parameter + kDash_DashType, //!< fills in all of the info parameter + }; + + struct DashInfo { + DashInfo() : fIntervals(nullptr), fCount(0), fPhase(0) {} + DashInfo(SkScalar* intervals, int32_t count, SkScalar phase) + : fIntervals(intervals), fCount(count), fPhase(phase) {} + + SkScalar* fIntervals; //!< Length of on/off intervals for dashed lines + // Even values represent ons, and odds offs + int32_t fCount; //!< Number of intervals in the dash. Should be even number + SkScalar fPhase; //!< Offset into the dashed interval pattern + // mod the sum of all intervals + }; + + DashType asADash(DashInfo* info) const; + + static void RegisterFlattenables(); + + static SkFlattenable::Type GetFlattenableType() { + return kSkPathEffect_Type; + } + + SkFlattenable::Type getFlattenableType() const override { + return kSkPathEffect_Type; + } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize( + kSkPathEffect_Type, data, size, procs).release())); + } + +protected: + SkPathEffect() {} + + virtual bool onFilterPath(SkPath*, const SkPath&, SkStrokeRec*, const SkRect*) const = 0; + virtual SkRect onComputeFastBounds(const SkRect& src) const { + return src; + } + virtual bool onAsPoints(PointData*, const SkPath&, const SkStrokeRec&, const SkMatrix&, + const SkRect*) const { + return false; + } + virtual DashType onAsADash(DashInfo*) const { + return kNone_DashType; + } + +private: + // illegal + SkPathEffect(const SkPathEffect&); + SkPathEffect& operator=(const SkPathEffect&); + + typedef SkFlattenable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPathMeasure.h b/gfx/skia/skia/include/core/SkPathMeasure.h new file mode 100644 index 0000000000..f799041bff --- /dev/null +++ b/gfx/skia/skia/include/core/SkPathMeasure.h @@ -0,0 +1,88 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathMeasure_DEFINED +#define SkPathMeasure_DEFINED + +#include "include/core/SkContourMeasure.h" +#include "include/core/SkPath.h" +#include "include/private/SkTDArray.h" + +class SK_API SkPathMeasure { +public: + SkPathMeasure(); + /** Initialize the pathmeasure with the specified path. The parts of the path that are needed + * are copied, so the client is free to modify/delete the path after this call. + * + * resScale controls the precision of the measure. values > 1 increase the + * precision (and possible slow down the computation). + */ + SkPathMeasure(const SkPath& path, bool forceClosed, SkScalar resScale = 1); + ~SkPathMeasure(); + + /** Reset the pathmeasure with the specified path. The parts of the path that are needed + * are copied, so the client is free to modify/delete the path after this call.. + */ + void setPath(const SkPath*, bool forceClosed); + + /** Return the total length of the current contour, or 0 if no path + is associated (e.g. resetPath(null)) + */ + SkScalar getLength(); + + /** Pins distance to 0 <= distance <= getLength(), and then computes + the corresponding position and tangent. + Returns false if there is no path, or a zero-length path was specified, in which case + position and tangent are unchanged. + */ + bool SK_WARN_UNUSED_RESULT getPosTan(SkScalar distance, SkPoint* position, + SkVector* tangent); + + enum MatrixFlags { + kGetPosition_MatrixFlag = 0x01, + kGetTangent_MatrixFlag = 0x02, + kGetPosAndTan_MatrixFlag = kGetPosition_MatrixFlag | kGetTangent_MatrixFlag + }; + + /** Pins distance to 0 <= distance <= getLength(), and then computes + the corresponding matrix (by calling getPosTan). + Returns false if there is no path, or a zero-length path was specified, in which case + matrix is unchanged. + */ + bool SK_WARN_UNUSED_RESULT getMatrix(SkScalar distance, SkMatrix* matrix, + MatrixFlags flags = kGetPosAndTan_MatrixFlag); + + /** Given a start and stop distance, return in dst the intervening segment(s). + If the segment is zero-length, return false, else return true. + startD and stopD are pinned to legal values (0..getLength()). If startD > stopD + then return false (and leave dst untouched). + Begin the segment with a moveTo if startWithMoveTo is true + */ + bool getSegment(SkScalar startD, SkScalar stopD, SkPath* dst, bool startWithMoveTo); + + /** Return true if the current contour is closed() + */ + bool isClosed(); + + /** Move to the next contour in the path. Return true if one exists, or false if + we're done with the path. + */ + bool nextContour(); + +#ifdef SK_DEBUG + void dump(); +#endif + +private: + SkContourMeasureIter fIter; + sk_sp fContour; + + SkPathMeasure(const SkPathMeasure&) = delete; + SkPathMeasure& operator=(const SkPathMeasure&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPathTypes.h b/gfx/skia/skia/include/core/SkPathTypes.h new file mode 100644 index 0000000000..4f5adf458c --- /dev/null +++ b/gfx/skia/skia/include/core/SkPathTypes.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathTypes_DEFINED +#define SkPathTypes_DEFINED + +#include "include/core/SkTypes.h" + +enum class SkPathFillType { + /** Specifies that "inside" is computed by a non-zero sum of signed edge crossings */ + kWinding, + /** Specifies that "inside" is computed by an odd number of edge crossings */ + kEvenOdd, + /** Same as Winding, but draws outside of the path, rather than inside */ + kInverseWinding, + /** Same as EvenOdd, but draws outside of the path, rather than inside */ + kInverseEvenOdd +}; + +enum class SkPathConvexityType { + kUnknown, + kConvex, + kConcave +}; + +enum class SkPathDirection { + /** clockwise direction for adding closed contours */ + kCW, + /** counter-clockwise direction for adding closed contours */ + kCCW, +}; + +enum SkPathSegmentMask { + kLine_SkPathSegmentMask = 1 << 0, + kQuad_SkPathSegmentMask = 1 << 1, + kConic_SkPathSegmentMask = 1 << 2, + kCubic_SkPathSegmentMask = 1 << 3, +}; + +enum class SkPathVerb { + kMove, //!< iter.next returns 1 point + kLine, //!< iter.next returns 2 points + kQuad, //!< iter.next returns 3 points + kConic, //!< iter.next returns 3 points + iter.conicWeight() + kCubic, //!< iter.next returns 4 points + kClose, //!< iter.next returns 1 point (contour's moveTo pt) + kDone, //!< iter.next returns 0 points +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPicture.h b/gfx/skia/skia/include/core/SkPicture.h new file mode 100644 index 0000000000..a7ec138b58 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPicture.h @@ -0,0 +1,258 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPicture_DEFINED +#define SkPicture_DEFINED + +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTileMode.h" +#include "include/core/SkTypes.h" + +class SkCanvas; +class SkData; +struct SkDeserialProcs; +class SkImage; +class SkMatrix; +struct SkSerialProcs; +class SkShader; +class SkStream; +class SkWStream; + +/** \class SkPicture + SkPicture records drawing commands made to SkCanvas. The command stream may be + played in whole or in part at a later time. + + SkPicture is an abstract class. SkPicture may be generated by SkPictureRecorder + or SkDrawable, or from SkPicture previously saved to SkData or SkStream. + + SkPicture may contain any SkCanvas drawing command, as well as one or more + SkCanvas matrix or SkCanvas clip. SkPicture has a cull SkRect, which is used as + a bounding box hint. To limit SkPicture bounds, use SkCanvas clip when + recording or drawing SkPicture. +*/ +class SK_API SkPicture : public SkRefCnt { +public: + + /** Recreates SkPicture that was serialized into a stream. Returns constructed SkPicture + if successful; otherwise, returns nullptr. Fails if data does not permit + constructing valid SkPicture. + + procs->fPictureProc permits supplying a custom function to decode SkPicture. + If procs->fPictureProc is nullptr, default decoding is used. procs->fPictureCtx + may be used to provide user context to procs->fPictureProc; procs->fPictureProc + is called with a pointer to data, data byte length, and user context. + + @param stream container for serial data + @param procs custom serial data decoders; may be nullptr + @return SkPicture constructed from stream data + */ + static sk_sp MakeFromStream(SkStream* stream, + const SkDeserialProcs* procs = nullptr); + + /** Recreates SkPicture that was serialized into data. Returns constructed SkPicture + if successful; otherwise, returns nullptr. Fails if data does not permit + constructing valid SkPicture. + + procs->fPictureProc permits supplying a custom function to decode SkPicture. + If procs->fPictureProc is nullptr, default decoding is used. procs->fPictureCtx + may be used to provide user context to procs->fPictureProc; procs->fPictureProc + is called with a pointer to data, data byte length, and user context. + + @param data container for serial data + @param procs custom serial data decoders; may be nullptr + @return SkPicture constructed from data + */ + static sk_sp MakeFromData(const SkData* data, + const SkDeserialProcs* procs = nullptr); + + /** + + @param data pointer to serial data + @param size size of data + @param procs custom serial data decoders; may be nullptr + @return SkPicture constructed from data + */ + static sk_sp MakeFromData(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr); + + /** \class SkPicture::AbortCallback + AbortCallback is an abstract class. An implementation of AbortCallback may + passed as a parameter to SkPicture::playback, to stop it before all drawing + commands have been processed. + + If AbortCallback::abort returns true, SkPicture::playback is interrupted. + */ + class SK_API AbortCallback { + public: + + /** Has no effect. + + @return abstract class cannot be instantiated + */ + AbortCallback() {} + + /** Has no effect. + */ + virtual ~AbortCallback() {} + + /** Stops SkPicture playback when some condition is met. A subclass of + AbortCallback provides an override for abort() that can stop SkPicture::playback. + + The part of SkPicture drawn when aborted is undefined. SkPicture instantiations are + free to stop drawing at different points during playback. + + If the abort happens inside one or more calls to SkCanvas::save(), stack + of SkCanvas matrix and SkCanvas clip values is restored to its state before + SkPicture::playback was called. + + @return true to stop playback + */ + virtual bool abort() = 0; + }; + + /** Replays the drawing commands on the specified canvas. In the case that the + commands are recorded, each command in the SkPicture is sent separately to canvas. + + To add a single command to draw SkPicture to recording canvas, call + SkCanvas::drawPicture instead. + + @param canvas receiver of drawing commands + @param callback allows interruption of playback + */ + virtual void playback(SkCanvas* canvas, AbortCallback* callback = nullptr) const = 0; + + /** Returns cull SkRect for this picture, passed in when SkPicture was created. + Returned SkRect does not specify clipping SkRect for SkPicture; cull is hint + of SkPicture bounds. + + SkPicture is free to discard recorded drawing commands that fall outside + cull. + + @return bounds passed when SkPicture was created + */ + virtual SkRect cullRect() const = 0; + + /** Returns a non-zero value unique among SkPicture in Skia process. + + @return identifier for SkPicture + */ + uint32_t uniqueID() const { return fUniqueID; } + + /** Returns storage containing SkData describing SkPicture, using optional custom + encoders. + + procs->fPictureProc permits supplying a custom function to encode SkPicture. + If procs->fPictureProc is nullptr, default encoding is used. procs->fPictureCtx + may be used to provide user context to procs->fPictureProc; procs->fPictureProc + is called with a pointer to SkPicture and user context. + + @param procs custom serial data encoders; may be nullptr + @return storage containing serialized SkPicture + */ + sk_sp serialize(const SkSerialProcs* procs = nullptr) const; + + /** Writes picture to stream, using optional custom encoders. + + procs->fPictureProc permits supplying a custom function to encode SkPicture. + If procs->fPictureProc is nullptr, default encoding is used. procs->fPictureCtx + may be used to provide user context to procs->fPictureProc; procs->fPictureProc + is called with a pointer to SkPicture and user context. + + @param stream writable serial data stream + @param procs custom serial data encoders; may be nullptr + */ + void serialize(SkWStream* stream, const SkSerialProcs* procs = nullptr) const; + + /** Returns a placeholder SkPicture. Result does not draw, and contains only + cull SkRect, a hint of its bounds. Result is immutable; it cannot be changed + later. Result identifier is unique. + + Returned placeholder can be intercepted during playback to insert other + commands into SkCanvas draw stream. + + @param cull placeholder dimensions + @return placeholder with unique identifier + */ + static sk_sp MakePlaceholder(SkRect cull); + + /** Returns the approximate number of operations in SkPicture. Returned value + may be greater or less than the number of SkCanvas calls + recorded: some calls may be recorded as more than one operation, other + calls may be optimized away. + + @return approximate operation count + */ + virtual int approximateOpCount() const = 0; + + /** Returns the approximate byte size of SkPicture. Does not include large objects + referenced by SkPicture. + + @return approximate size + */ + virtual size_t approximateBytesUsed() const = 0; + + /** Return a new shader that will draw with this picture. + * + * @param tmx The tiling mode to use when sampling in the x-direction. + * @param tmy The tiling mode to use when sampling in the y-direction. + * @param localMatrix Optional matrix used when sampling + * @param tile The tile rectangle in picture coordinates: this represents the subset + * (or superset) of the picture used when building a tile. It is not + * affected by localMatrix and does not imply scaling (only translation + * and cropping). If null, the tile rect is considered equal to the picture + * bounds. + * @return Returns a new shader object. Note: this function never returns null. + */ + sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, const SkRect* tileRect) const; + sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix = nullptr) const; + +private: + // Subclass whitelist. + SkPicture(); + friend class SkBigPicture; + friend class SkEmptyPicture; + friend class SkPicturePriv; + template friend class SkMiniPicture; + + void serialize(SkWStream*, const SkSerialProcs*, class SkRefCntSet* typefaces, + bool textBlobsOnly=false) const; + static sk_sp MakeFromStream(SkStream*, const SkDeserialProcs*, + class SkTypefacePlayback*); + friend class SkPictureData; + + /** Return true if the SkStream/Buffer represents a serialized picture, and + fills out SkPictInfo. After this function returns, the data source is not + rewound so it will have to be manually reset before passing to + MakeFromStream or MakeFromBuffer. Note, MakeFromStream and + MakeFromBuffer perform this check internally so these entry points are + intended for stand alone tools. + If false is returned, SkPictInfo is unmodified. + */ + static bool StreamIsSKP(SkStream*, struct SkPictInfo*); + static bool BufferIsSKP(class SkReadBuffer*, struct SkPictInfo*); + friend bool SkPicture_StreamIsSKP(SkStream*, struct SkPictInfo*); + + // Returns NULL if this is not an SkBigPicture. + virtual const class SkBigPicture* asSkBigPicture() const { return nullptr; } + + friend struct SkPathCounter; + + static bool IsValidPictInfo(const struct SkPictInfo& info); + static sk_sp Forwardport(const struct SkPictInfo&, + const class SkPictureData*, + class SkReadBuffer* buffer); + + struct SkPictInfo createHeader() const; + class SkPictureData* backport() const; + + uint32_t fUniqueID; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPictureRecorder.h b/gfx/skia/skia/include/core/SkPictureRecorder.h new file mode 100644 index 0000000000..1b08f079b0 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPictureRecorder.h @@ -0,0 +1,125 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureRecorder_DEFINED +#define SkPictureRecorder_DEFINED + +#include "include/core/SkBBHFactory.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRefCnt.h" + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK +namespace android { + class Picture; +}; +#endif + +class GrContext; +class SkCanvas; +class SkDrawable; +class SkMiniRecorder; +class SkPictureRecord; +class SkRecord; +class SkRecorder; + +class SK_API SkPictureRecorder { +public: + SkPictureRecorder(); + ~SkPictureRecorder(); + + enum RecordFlags { + // If you call drawPicture() or drawDrawable() on the recording canvas, this flag forces + // that object to playback its contents immediately rather than reffing the object. + kPlaybackDrawPicture_RecordFlag = 1 << 0, + }; + + enum FinishFlags { + }; + + /** Returns the canvas that records the drawing commands. + @param bounds the cull rect used when recording this picture. Any drawing the falls outside + of this rect is undefined, and may be drawn or it may not. + @param bbhFactory factory to create desired acceleration structure + @param recordFlags optional flags that control recording. + @return the canvas. + */ + SkCanvas* beginRecording(const SkRect& bounds, + SkBBHFactory* bbhFactory = nullptr, + uint32_t recordFlags = 0); + + SkCanvas* beginRecording(SkScalar width, SkScalar height, + SkBBHFactory* bbhFactory = nullptr, + uint32_t recordFlags = 0) { + return this->beginRecording(SkRect::MakeWH(width, height), bbhFactory, recordFlags); + } + + /** Returns the recording canvas if one is active, or NULL if recording is + not active. This does not alter the refcnt on the canvas (if present). + */ + SkCanvas* getRecordingCanvas(); + + /** + * Signal that the caller is done recording. This invalidates the canvas returned by + * beginRecording/getRecordingCanvas. Ownership of the object is passed to the caller, who + * must call unref() when they are done using it. + * + * The returned picture is immutable. If during recording drawables were added to the canvas, + * these will have been "drawn" into a recording canvas, so that this resulting picture will + * reflect their current state, but will not contain a live reference to the drawables + * themselves. + */ + sk_sp finishRecordingAsPicture(uint32_t endFlags = 0); + + /** + * Signal that the caller is done recording, and update the cull rect to use for bounding + * box hierarchy (BBH) generation. The behavior is the same as calling + * finishRecordingAsPicture(), except that this method updates the cull rect initially passed + * into beginRecording. + * @param cullRect the new culling rectangle to use as the overall bound for BBH generation + * and subsequent culling operations. + * @return the picture containing the recorded content. + */ + sk_sp finishRecordingAsPictureWithCull(const SkRect& cullRect, + uint32_t endFlags = 0); + + /** + * Signal that the caller is done recording. This invalidates the canvas returned by + * beginRecording/getRecordingCanvas. Ownership of the object is passed to the caller, who + * must call unref() when they are done using it. + * + * Unlike finishRecordingAsPicture(), which returns an immutable picture, the returned drawable + * may contain live references to other drawables (if they were added to the recording canvas) + * and therefore this drawable will reflect the current state of those nested drawables anytime + * it is drawn or a new picture is snapped from it (by calling drawable->newPictureSnapshot()). + */ + sk_sp finishRecordingAsDrawable(uint32_t endFlags = 0); + +private: + void reset(); + + /** Replay the current (partially recorded) operation stream into + canvas. This call doesn't close the current recording. + */ +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + friend class android::Picture; +#endif + friend class SkPictureRecorderReplayTester; // for unit testing + void partialReplay(SkCanvas* canvas) const; + + bool fActivelyRecording; + uint32_t fFlags; + SkRect fCullRect; + sk_sp fBBH; + std::unique_ptr fRecorder; + sk_sp fRecord; + std::unique_ptr fMiniRecorder; + + SkPictureRecorder(SkPictureRecorder&&) = delete; + SkPictureRecorder& operator=(SkPictureRecorder&&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPixelRef.h b/gfx/skia/skia/include/core/SkPixelRef.h new file mode 100644 index 0000000000..48ad377acc --- /dev/null +++ b/gfx/skia/skia/include/core/SkPixelRef.h @@ -0,0 +1,129 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPixelRef_DEFINED +#define SkPixelRef_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSize.h" +#include "include/core/SkString.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTDArray.h" + +#include + +struct SkIRect; + +class GrTexture; +class SkDiscardableMemory; + +/** \class SkPixelRef + + This class is the smart container for pixel memory, and is used with SkBitmap. + This class can be shared/accessed between multiple threads. +*/ +class SK_API SkPixelRef : public SkRefCnt { +public: + SkPixelRef(int width, int height, void* addr, size_t rowBytes); + ~SkPixelRef() override; + + int width() const { return fWidth; } + int height() const { return fHeight; } + void* pixels() const { return fPixels; } + size_t rowBytes() const { return fRowBytes; } + + /** Returns a non-zero, unique value corresponding to the pixels in this + pixelref. Each time the pixels are changed (and notifyPixelsChanged is + called), a different generation ID will be returned. + */ + uint32_t getGenerationID() const; + + /** + * Call this if you have changed the contents of the pixels. This will in- + * turn cause a different generation ID value to be returned from + * getGenerationID(). + */ + void notifyPixelsChanged(); + + /** Returns true if this pixelref is marked as immutable, meaning that the + contents of its pixels will not change for the lifetime of the pixelref. + */ + bool isImmutable() const { return fMutability != kMutable; } + + /** Marks this pixelref is immutable, meaning that the contents of its + pixels will not change for the lifetime of the pixelref. This state can + be set on a pixelref, but it cannot be cleared once it is set. + */ + void setImmutable(); + + // Register a listener that may be called the next time our generation ID changes. + // + // We'll only call the listener if we're confident that we are the only SkPixelRef with this + // generation ID. If our generation ID changes and we decide not to call the listener, we'll + // never call it: you must add a new listener for each generation ID change. We also won't call + // the listener when we're certain no one knows what our generation ID is. + // + // This can be used to invalidate caches keyed by SkPixelRef generation ID. + struct GenIDChangeListener { + virtual ~GenIDChangeListener() {} + virtual void onChange() = 0; + }; + + // Takes ownership of listener. Threadsafe. + void addGenIDChangeListener(GenIDChangeListener* listener); + + // Call when this pixelref is part of the key to a resourcecache entry. This allows the cache + // to know automatically those entries can be purged when this pixelref is changed or deleted. + void notifyAddedToCache() { + fAddedToCache.store(true); + } + + virtual SkDiscardableMemory* diagnostic_only_getDiscardable() const { return nullptr; } + +protected: + void android_only_reset(int width, int height, size_t rowBytes); + +private: + int fWidth; + int fHeight; + void* fPixels; + size_t fRowBytes; + + // Bottom bit indicates the Gen ID is unique. + bool genIDIsUnique() const { return SkToBool(fTaggedGenID.load() & 1); } + mutable std::atomic fTaggedGenID; + + SkMutex fGenIDChangeListenersMutex; + SkTDArray fGenIDChangeListeners; // pointers are owned + + // Set true by caches when they cache content that's derived from the current pixels. + std::atomic fAddedToCache; + + enum Mutability { + kMutable, // PixelRefs begin mutable. + kTemporarilyImmutable, // Considered immutable, but can revert to mutable. + kImmutable, // Once set to this state, it never leaves. + } fMutability : 8; // easily fits inside a byte + + void needsNewGenID(); + void callGenIDChangeListeners(); + + void setTemporarilyImmutable(); + void restoreMutability(); + friend class SkSurface_Raster; // For the two methods above. + + void setImmutableWithID(uint32_t genID); + friend void SkBitmapCache_setImmutableWithID(SkPixelRef*, uint32_t); + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPixmap.h b/gfx/skia/skia/include/core/SkPixmap.h new file mode 100644 index 0000000000..7d460275ff --- /dev/null +++ b/gfx/skia/skia/include/core/SkPixmap.h @@ -0,0 +1,713 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPixmap_DEFINED +#define SkPixmap_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkImageInfo.h" + +class SkData; +struct SkMask; + +/** \class SkPixmap + SkPixmap provides a utility to pair SkImageInfo with pixels and row bytes. + SkPixmap is a low level class which provides convenience functions to access + raster destinations. SkCanvas can not draw SkPixmap, nor does SkPixmap provide + a direct drawing destination. + + Use SkBitmap to draw pixels referenced by SkPixmap; use SkSurface to draw into + pixels referenced by SkPixmap. + + SkPixmap does not try to manage the lifetime of the pixel memory. Use SkPixelRef + to manage pixel memory; SkPixelRef is safe across threads. +*/ +class SK_API SkPixmap { +public: + + /** Creates an empty SkPixmap without pixels, with kUnknown_SkColorType, with + kUnknown_SkAlphaType, and with a width and height of zero. Use + reset() to associate pixels, SkColorType, SkAlphaType, width, and height + after SkPixmap has been created. + + @return empty SkPixmap + */ + SkPixmap() + : fPixels(nullptr), fRowBytes(0), fInfo(SkImageInfo::MakeUnknown(0, 0)) + {} + + /** Creates SkPixmap from info width, height, SkAlphaType, and SkColorType. + addr points to pixels, or nullptr. rowBytes should be info.width() times + info.bytesPerPixel(), or larger. + + No parameter checking is performed; it is up to the caller to ensure that + addr and rowBytes agree with info. + + The memory lifetime of pixels is managed by the caller. When SkPixmap goes + out of scope, addr is unaffected. + + SkPixmap may be later modified by reset() to change its size, pixel type, or + storage. + + @param info width, height, SkAlphaType, SkColorType of SkImageInfo + @param addr pointer to pixels allocated by caller; may be nullptr + @param rowBytes size of one row of addr; width times pixel size, or larger + @return initialized SkPixmap + */ + SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes) + : fPixels(addr), fRowBytes(rowBytes), fInfo(info) + {} + + /** Sets width, height, row bytes to zero; pixel address to nullptr; SkColorType to + kUnknown_SkColorType; and SkAlphaType to kUnknown_SkAlphaType. + + The prior pixels are unaffected; it is up to the caller to release pixels + memory if desired. + */ + void reset(); + + /** Sets width, height, SkAlphaType, and SkColorType from info. + Sets pixel address from addr, which may be nullptr. + Sets row bytes from rowBytes, which should be info.width() times + info.bytesPerPixel(), or larger. + + Does not check addr. Asserts if built with SK_DEBUG defined and if rowBytes is + too small to hold one row of pixels. + + The memory lifetime pixels are managed by the caller. When SkPixmap goes + out of scope, addr is unaffected. + + @param info width, height, SkAlphaType, SkColorType of SkImageInfo + @param addr pointer to pixels allocated by caller; may be nullptr + @param rowBytes size of one row of addr; width times pixel size, or larger + */ + void reset(const SkImageInfo& info, const void* addr, size_t rowBytes); + + /** Changes SkColorSpace in SkImageInfo; preserves width, height, SkAlphaType, and + SkColorType in SkImage, and leaves pixel address and row bytes unchanged. + SkColorSpace reference count is incremented. + + @param colorSpace SkColorSpace moved to SkImageInfo + */ + void setColorSpace(sk_sp colorSpace); + + /** Deprecated. + */ + bool SK_WARN_UNUSED_RESULT reset(const SkMask& mask); + + /** Sets subset width, height, pixel address to intersection of SkPixmap with area, + if intersection is not empty; and return true. Otherwise, leave subset unchanged + and return false. + + Failing to read the return value generates a compile time warning. + + @param subset storage for width, height, pixel address of intersection + @param area bounds to intersect with SkPixmap + @return true if intersection of SkPixmap and area is not empty + */ + bool SK_WARN_UNUSED_RESULT extractSubset(SkPixmap* subset, const SkIRect& area) const; + + /** Returns width, height, SkAlphaType, SkColorType, and SkColorSpace. + + @return reference to SkImageInfo + */ + const SkImageInfo& info() const { return fInfo; } + + /** Returns row bytes, the interval from one pixel row to the next. Row bytes + is at least as large as: width() * info().bytesPerPixel(). + + Returns zero if colorType() is kUnknown_SkColorType. + It is up to the SkBitmap creator to ensure that row bytes is a useful value. + + @return byte length of pixel row + */ + size_t rowBytes() const { return fRowBytes; } + + /** Returns pixel address, the base address corresponding to the pixel origin. + + It is up to the SkPixmap creator to ensure that pixel address is a useful value. + + @return pixel address + */ + const void* addr() const { return fPixels; } + + /** Returns pixel count in each pixel row. Should be equal or less than: + rowBytes() / info().bytesPerPixel(). + + @return pixel width in SkImageInfo + */ + int width() const { return fInfo.width(); } + + /** Returns pixel row count. + + @return pixel height in SkImageInfo + */ + int height() const { return fInfo.height(); } + + /** + * Return the dimensions of the pixmap (from its ImageInfo) + */ + SkISize dimensions() const { return fInfo.dimensions(); } + + /** Returns SkColorType, one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType, + kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType. + + @return SkColorType in SkImageInfo + */ + SkColorType colorType() const { return fInfo.colorType(); } + + /** Returns SkAlphaType, one of: + kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, + kUnpremul_SkAlphaType. + + @return SkAlphaType in SkImageInfo + */ + SkAlphaType alphaType() const { return fInfo.alphaType(); } + + /** Returns SkColorSpace, the range of colors, associated with SkImageInfo. The + reference count of SkColorSpace is unchanged. The returned SkColorSpace is + immutable. + + @return SkColorSpace in SkImageInfo, or nullptr + */ + SkColorSpace* colorSpace() const { return fInfo.colorSpace(); } + + /** Returns smart pointer to SkColorSpace, the range of colors, associated with + SkImageInfo. The smart pointer tracks the number of objects sharing this + SkColorSpace reference so the memory is released when the owners destruct. + + The returned SkColorSpace is immutable. + + @return SkColorSpace in SkImageInfo wrapped in a smart pointer + */ + sk_sp refColorSpace() const { return fInfo.refColorSpace(); } + + /** Returns true if SkAlphaType is kOpaque_SkAlphaType. + Does not check if SkColorType allows alpha, or if any pixel value has + transparency. + + @return true if SkImageInfo has opaque SkAlphaType + */ + bool isOpaque() const { return fInfo.isOpaque(); } + + /** Returns SkIRect { 0, 0, width(), height() }. + + @return integral rectangle from origin to width() and height() + */ + SkIRect bounds() const { return SkIRect::MakeWH(this->width(), this->height()); } + + /** Returns number of pixels that fit on row. Should be greater than or equal to + width(). + + @return maximum pixels per row + */ + int rowBytesAsPixels() const { return int(fRowBytes >> this->shiftPerPixel()); } + + /** Returns bit shift converting row bytes to row pixels. + Returns zero for kUnknown_SkColorType. + + @return one of: 0, 1, 2, 3; left shift to convert pixels to bytes + */ + int shiftPerPixel() const { return fInfo.shiftPerPixel(); } + + /** Returns minimum memory required for pixel storage. + Does not include unused memory on last row when rowBytesAsPixels() exceeds width(). + Returns SIZE_MAX if result does not fit in size_t. + Returns zero if height() or width() is 0. + Returns height() times rowBytes() if colorType() is kUnknown_SkColorType. + + @return size in bytes of image buffer + */ + size_t computeByteSize() const { return fInfo.computeByteSize(fRowBytes); } + + /** Returns true if all pixels are opaque. SkColorType determines how pixels + are encoded, and whether pixel describes alpha. Returns true for SkColorType + without alpha in each pixel; for other SkColorType, returns true if all + pixels have alpha values equivalent to 1.0 or greater. + + For SkColorType kRGB_565_SkColorType or kGray_8_SkColorType: always + returns true. For SkColorType kAlpha_8_SkColorType, kBGRA_8888_SkColorType, + kRGBA_8888_SkColorType: returns true if all pixel alpha values are 255. + For SkColorType kARGB_4444_SkColorType: returns true if all pixel alpha values are 15. + For kRGBA_F16_SkColorType: returns true if all pixel alpha values are 1.0 or + greater. + + Returns false for kUnknown_SkColorType. + + @return true if all pixels have opaque values or SkColorType is opaque + */ + bool computeIsOpaque() const; + + /** Returns pixel at (x, y) as unpremultiplied color. + Returns black with alpha if SkColorType is kAlpha_8_SkColorType. + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined; and returns undefined values or may crash if + SK_RELEASE is defined. Fails if SkColorType is kUnknown_SkColorType or + pixel address is nullptr. + + SkColorSpace in SkImageInfo is ignored. Some color precision may be lost in the + conversion to unpremultiplied color; original pixel data may have additional + precision. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return pixel converted to unpremultiplied color + */ + SkColor getColor(int x, int y) const; + + /** Look up the pixel at (x,y) and return its alpha component, normalized to [0..1]. + This is roughly equivalent to SkGetColorA(getColor()), but can be more efficent + (and more precise if the pixels store more than 8 bits per component). + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return alpha converted to normalized float + */ + float getAlphaf(int x, int y) const; + + /** Returns readable pixel address at (x, y). Returns nullptr if SkPixelRef is nullptr. + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. Returns nullptr if SkColorType is kUnknown_SkColorType. + + Performs a lookup of pixel size; for better performance, call + one of: addr8, addr16, addr32, addr64, or addrF16(). + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return readable generic pointer to pixel + */ + const void* addr(int x, int y) const { + return (const char*)fPixels + fInfo.computeOffset(x, y, fRowBytes); + } + + /** Returns readable base pixel address. Result is addressable as unsigned 8-bit bytes. + Will trigger an assert() if SkColorType is not kAlpha_8_SkColorType or + kGray_8_SkColorType, and is built with SK_DEBUG defined. + + One byte corresponds to one pixel. + + @return readable unsigned 8-bit pointer to pixels + */ + const uint8_t* addr8() const { + SkASSERT(1 == fInfo.bytesPerPixel()); + return reinterpret_cast(fPixels); + } + + /** Returns readable base pixel address. Result is addressable as unsigned 16-bit words. + Will trigger an assert() if SkColorType is not kRGB_565_SkColorType or + kARGB_4444_SkColorType, and is built with SK_DEBUG defined. + + One word corresponds to one pixel. + + @return readable unsigned 16-bit pointer to pixels + */ + const uint16_t* addr16() const { + SkASSERT(2 == fInfo.bytesPerPixel()); + return reinterpret_cast(fPixels); + } + + /** Returns readable base pixel address. Result is addressable as unsigned 32-bit words. + Will trigger an assert() if SkColorType is not kRGBA_8888_SkColorType or + kBGRA_8888_SkColorType, and is built with SK_DEBUG defined. + + One word corresponds to one pixel. + + @return readable unsigned 32-bit pointer to pixels + */ + const uint32_t* addr32() const { + SkASSERT(4 == fInfo.bytesPerPixel()); + return reinterpret_cast(fPixels); + } + + /** Returns readable base pixel address. Result is addressable as unsigned 64-bit words. + Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built + with SK_DEBUG defined. + + One word corresponds to one pixel. + + @return readable unsigned 64-bit pointer to pixels + */ + const uint64_t* addr64() const { + SkASSERT(8 == fInfo.bytesPerPixel()); + return reinterpret_cast(fPixels); + } + + /** Returns readable base pixel address. Result is addressable as unsigned 16-bit words. + Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built + with SK_DEBUG defined. + + Each word represents one color component encoded as a half float. + Four words correspond to one pixel. + + @return readable unsigned 16-bit pointer to first component of pixels + */ + const uint16_t* addrF16() const { + SkASSERT(8 == fInfo.bytesPerPixel()); + SkASSERT(kRGBA_F16_SkColorType == fInfo.colorType() || + kRGBA_F16Norm_SkColorType == fInfo.colorType()); + return reinterpret_cast(fPixels); + } + + /** Returns readable pixel address at (x, y). + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. + + Will trigger an assert() if SkColorType is not kAlpha_8_SkColorType or + kGray_8_SkColorType, and is built with SK_DEBUG defined. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return readable unsigned 8-bit pointer to pixel at (x, y) + */ + const uint8_t* addr8(int x, int y) const { + SkASSERT((unsigned)x < (unsigned)fInfo.width()); + SkASSERT((unsigned)y < (unsigned)fInfo.height()); + return (const uint8_t*)((const char*)this->addr8() + y * fRowBytes + (x << 0)); + } + + /** Returns readable pixel address at (x, y). + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. + + Will trigger an assert() if SkColorType is not kRGB_565_SkColorType or + kARGB_4444_SkColorType, and is built with SK_DEBUG defined. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return readable unsigned 16-bit pointer to pixel at (x, y) + */ + const uint16_t* addr16(int x, int y) const { + SkASSERT((unsigned)x < (unsigned)fInfo.width()); + SkASSERT((unsigned)y < (unsigned)fInfo.height()); + return (const uint16_t*)((const char*)this->addr16() + y * fRowBytes + (x << 1)); + } + + /** Returns readable pixel address at (x, y). + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. + + Will trigger an assert() if SkColorType is not kRGBA_8888_SkColorType or + kBGRA_8888_SkColorType, and is built with SK_DEBUG defined. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return readable unsigned 32-bit pointer to pixel at (x, y) + */ + const uint32_t* addr32(int x, int y) const { + SkASSERT((unsigned)x < (unsigned)fInfo.width()); + SkASSERT((unsigned)y < (unsigned)fInfo.height()); + return (const uint32_t*)((const char*)this->addr32() + y * fRowBytes + (x << 2)); + } + + /** Returns readable pixel address at (x, y). + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. + + Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built + with SK_DEBUG defined. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return readable unsigned 64-bit pointer to pixel at (x, y) + */ + const uint64_t* addr64(int x, int y) const { + SkASSERT((unsigned)x < (unsigned)fInfo.width()); + SkASSERT((unsigned)y < (unsigned)fInfo.height()); + return (const uint64_t*)((const char*)this->addr64() + y * fRowBytes + (x << 3)); + } + + /** Returns readable pixel address at (x, y). + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. + + Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built + with SK_DEBUG defined. + + Each unsigned 16-bit word represents one color component encoded as a half float. + Four words correspond to one pixel. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return readable unsigned 16-bit pointer to pixel component at (x, y) + */ + const uint16_t* addrF16(int x, int y) const { + SkASSERT(kRGBA_F16_SkColorType == fInfo.colorType() || + kRGBA_F16Norm_SkColorType == fInfo.colorType()); + return reinterpret_cast(this->addr64(x, y)); + } + + /** Returns writable base pixel address. + + @return writable generic base pointer to pixels + */ + void* writable_addr() const { return const_cast(fPixels); } + + /** Returns writable pixel address at (x, y). + + Input is not validated: out of bounds values of x or y trigger an assert() if + built with SK_DEBUG defined. Returns zero if SkColorType is kUnknown_SkColorType. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return writable generic pointer to pixel + */ + void* writable_addr(int x, int y) const { + return const_cast(this->addr(x, y)); + } + + /** Returns writable pixel address at (x, y). Result is addressable as unsigned + 8-bit bytes. Will trigger an assert() if SkColorType is not kAlpha_8_SkColorType + or kGray_8_SkColorType, and is built with SK_DEBUG defined. + + One byte corresponds to one pixel. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return writable unsigned 8-bit pointer to pixels + */ + uint8_t* writable_addr8(int x, int y) const { + return const_cast(this->addr8(x, y)); + } + + /** Returns writable_addr pixel address at (x, y). Result is addressable as unsigned + 16-bit words. Will trigger an assert() if SkColorType is not kRGB_565_SkColorType + or kARGB_4444_SkColorType, and is built with SK_DEBUG defined. + + One word corresponds to one pixel. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return writable unsigned 16-bit pointer to pixel + */ + uint16_t* writable_addr16(int x, int y) const { + return const_cast(this->addr16(x, y)); + } + + /** Returns writable pixel address at (x, y). Result is addressable as unsigned + 32-bit words. Will trigger an assert() if SkColorType is not + kRGBA_8888_SkColorType or kBGRA_8888_SkColorType, and is built with SK_DEBUG + defined. + + One word corresponds to one pixel. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return writable unsigned 32-bit pointer to pixel + */ + uint32_t* writable_addr32(int x, int y) const { + return const_cast(this->addr32(x, y)); + } + + /** Returns writable pixel address at (x, y). Result is addressable as unsigned + 64-bit words. Will trigger an assert() if SkColorType is not + kRGBA_F16_SkColorType and is built with SK_DEBUG defined. + + One word corresponds to one pixel. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return writable unsigned 64-bit pointer to pixel + */ + uint64_t* writable_addr64(int x, int y) const { + return const_cast(this->addr64(x, y)); + } + + /** Returns writable pixel address at (x, y). Result is addressable as unsigned + 16-bit words. Will trigger an assert() if SkColorType is not + kRGBA_F16_SkColorType and is built with SK_DEBUG defined. + + Each word represents one color component encoded as a half float. + Four words correspond to one pixel. + + @param x column index, zero or greater, and less than width() + @param y row index, zero or greater, and less than height() + @return writable unsigned 16-bit pointer to first component of pixel + */ + uint16_t* writable_addrF16(int x, int y) const { + return reinterpret_cast(writable_addr64(x, y)); + } + + /** Copies a SkRect of pixels to dstPixels. Copy starts at (0, 0), and does not + exceed SkPixmap (width(), height()). + + dstInfo specifies width, height, SkColorType, SkAlphaType, and + SkColorSpace of destination. dstRowBytes specifics the gap from one destination + row to the next. Returns true if pixels are copied. Returns false if + dstInfo address equals nullptr, or dstRowBytes is less than dstInfo.minRowBytes(). + + Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. + If SkPixmap colorType() is kGray_8_SkColorType, dstInfo.colorSpace() must match. + If SkPixmap alphaType() is kOpaque_SkAlphaType, dstInfo.alphaType() must + match. If SkPixmap colorSpace() is nullptr, dstInfo.colorSpace() must match. Returns + false if pixel conversion is not possible. + + Returns false if SkPixmap width() or height() is zero or negative. + + @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace + @param dstPixels destination pixel storage + @param dstRowBytes destination row length + @return true if pixels are copied to dstPixels + */ + bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes) const { + return this->readPixels(dstInfo, dstPixels, dstRowBytes, 0, 0); + } + + /** Copies a SkRect of pixels to dstPixels. Copy starts at (srcX, srcY), and does not + exceed SkPixmap (width(), height()). + + dstInfo specifies width, height, SkColorType, SkAlphaType, and + SkColorSpace of destination. dstRowBytes specifics the gap from one destination + row to the next. Returns true if pixels are copied. Returns false if + dstInfo address equals nullptr, or dstRowBytes is less than dstInfo.minRowBytes(). + + Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. + If SkPixmap colorType() is kGray_8_SkColorType, dstInfo.colorSpace() must match. + If SkPixmap alphaType() is kOpaque_SkAlphaType, dstInfo.alphaType() must + match. If SkPixmap colorSpace() is nullptr, dstInfo.colorSpace() must match. Returns + false if pixel conversion is not possible. + + srcX and srcY may be negative to copy only top or left of source. Returns + false if SkPixmap width() or height() is zero or negative. Returns false if: + abs(srcX) >= Pixmap width(), or if abs(srcY) >= Pixmap height(). + + @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace + @param dstPixels destination pixel storage + @param dstRowBytes destination row length + @param srcX column index whose absolute value is less than width() + @param srcY row index whose absolute value is less than height() + @return true if pixels are copied to dstPixels + */ + bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, + int srcY) const; + + /** Copies a SkRect of pixels to dst. Copy starts at (srcX, srcY), and does not + exceed SkPixmap (width(), height()). dst specifies width, height, SkColorType, + SkAlphaType, and SkColorSpace of destination. Returns true if pixels are copied. + Returns false if dst address equals nullptr, or dst.rowBytes() is less than + dst SkImageInfo::minRowBytes. + + Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst.info().colorType must match. + If SkPixmap colorType() is kGray_8_SkColorType, dst.info().colorSpace must match. + If SkPixmap alphaType() is kOpaque_SkAlphaType, dst.info().alphaType must + match. If SkPixmap colorSpace() is nullptr, dst.info().colorSpace must match. Returns + false if pixel conversion is not possible. + + srcX and srcY may be negative to copy only top or left of source. Returns + false SkPixmap width() or height() is zero or negative. Returns false if: + abs(srcX) >= Pixmap width(), or if abs(srcY) >= Pixmap height(). + + @param dst SkImageInfo and pixel address to write to + @param srcX column index whose absolute value is less than width() + @param srcY row index whose absolute value is less than height() + @return true if pixels are copied to dst + */ + bool readPixels(const SkPixmap& dst, int srcX, int srcY) const { + return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY); + } + + /** Copies pixels inside bounds() to dst. dst specifies width, height, SkColorType, + SkAlphaType, and SkColorSpace of destination. Returns true if pixels are copied. + Returns false if dst address equals nullptr, or dst.rowBytes() is less than + dst SkImageInfo::minRowBytes. + + Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. + If SkPixmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. + If SkPixmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must + match. If SkPixmap colorSpace() is nullptr, dst SkColorSpace must match. Returns + false if pixel conversion is not possible. + + Returns false if SkPixmap width() or height() is zero or negative. + + @param dst SkImageInfo and pixel address to write to + @return true if pixels are copied to dst + */ + bool readPixels(const SkPixmap& dst) const { + return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), 0, 0); + } + + /** Copies SkBitmap to dst, scaling pixels to fit dst.width() and dst.height(), and + converting pixels to match dst.colorType() and dst.alphaType(). Returns true if + pixels are copied. Returns false if dst address is nullptr, or dst.rowBytes() is + less than dst SkImageInfo::minRowBytes. + + Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is + kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. + If SkPixmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. + If SkPixmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must + match. If SkPixmap colorSpace() is nullptr, dst SkColorSpace must match. Returns + false if pixel conversion is not possible. + + Returns false if SkBitmap width() or height() is zero or negative. + + Scales the image, with filterQuality, to match dst.width() and dst.height(). + filterQuality kNone_SkFilterQuality is fastest, typically implemented with + nearest neighbor filter. kLow_SkFilterQuality is typically implemented with + bilerp filter. kMedium_SkFilterQuality is typically implemented with + bilerp filter, and mip-map filter when size is reduced. + kHigh_SkFilterQuality is slowest, typically implemented with bicubic filter. + + @param dst SkImageInfo and pixel address to write to + @param filterQuality one of: kNone_SkFilterQuality, kLow_SkFilterQuality, + kMedium_SkFilterQuality, kHigh_SkFilterQuality + @return true if pixels are scaled to fit dst + */ + bool scalePixels(const SkPixmap& dst, SkFilterQuality filterQuality) const; + + /** Writes color to pixels bounded by subset; returns true on success. + Returns false if colorType() is kUnknown_SkColorType, or if subset does + not intersect bounds(). + + @param color unpremultiplied color to write + @param subset bounding integer SkRect of written pixels + @return true if pixels are changed + */ + bool erase(SkColor color, const SkIRect& subset) const; + + /** Writes color to pixels inside bounds(); returns true on success. + Returns false if colorType() is kUnknown_SkColorType, or if bounds() + is empty. + + @param color unpremultiplied color to write + @return true if pixels are changed + */ + bool erase(SkColor color) const { return this->erase(color, this->bounds()); } + + /** Writes color to pixels bounded by subset; returns true on success. + if subset is nullptr, writes colors pixels inside bounds(). Returns false if + colorType() is kUnknown_SkColorType, if subset is not nullptr and does + not intersect bounds(), or if subset is nullptr and bounds() is empty. + + @param color unpremultiplied color to write + @param subset bounding integer SkRect of pixels to write; may be nullptr + @return true if pixels are changed + */ + bool erase(const SkColor4f& color, const SkIRect* subset = nullptr) const; + +private: + const void* fPixels; + size_t fRowBytes; + SkImageInfo fInfo; + + friend class SkPixmapPriv; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPngChunkReader.h b/gfx/skia/skia/include/core/SkPngChunkReader.h new file mode 100644 index 0000000000..0ee8a9ecc7 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPngChunkReader.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPngChunkReader_DEFINED +#define SkPngChunkReader_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +/** + * SkPngChunkReader + * + * Base class for optional callbacks to retrieve meta/chunk data out of a PNG + * encoded image as it is being decoded. + * Used by SkCodec. + */ +class SkPngChunkReader : public SkRefCnt { +public: + /** + * This will be called by the decoder when it sees an unknown chunk. + * + * Use by SkCodec: + * Depending on the location of the unknown chunks, this callback may be + * called by + * - the factory (NewFromStream/NewFromData) + * - getPixels + * - startScanlineDecode + * - the first call to getScanlines/skipScanlines + * The callback may be called from a different thread (e.g. if the SkCodec + * is passed to another thread), and it may be called multiple times, if + * the SkCodec is used multiple times. + * + * @param tag Name for this type of chunk. + * @param data Data to be interpreted by the subclass. + * @param length Number of bytes of data in the chunk. + * @return true to continue decoding, or false to indicate an error, which + * will cause the decoder to not return the image. + */ + virtual bool readChunk(const char tag[], const void* data, size_t length) = 0; +}; +#endif // SkPngChunkReader_DEFINED diff --git a/gfx/skia/skia/include/core/SkPoint.h b/gfx/skia/skia/include/core/SkPoint.h new file mode 100644 index 0000000000..62516db4c4 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPoint.h @@ -0,0 +1,552 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPoint_DEFINED +#define SkPoint_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkScalar.h" +#include "include/private/SkSafe32.h" + +struct SkIPoint; + +/** SkIVector provides an alternative name for SkIPoint. SkIVector and SkIPoint + can be used interchangeably for all purposes. +*/ +typedef SkIPoint SkIVector; + +/** \struct SkIPoint + SkIPoint holds two 32-bit integer coordinates. +*/ +struct SkIPoint { + int32_t fX; //!< x-axis value + int32_t fY; //!< y-axis value + + /** Sets fX to x, fY to y. + + @param x integer x-axis value of constructed SkIPoint + @param y integer y-axis value of constructed SkIPoint + @return SkIPoint (x, y) + */ + static constexpr SkIPoint Make(int32_t x, int32_t y) { + return {x, y}; + } + + /** Returns x-axis value of SkIPoint. + + @return fX + */ + constexpr int32_t x() const { return fX; } + + /** Returns y-axis value of SkIPoint. + + @return fY + */ + constexpr int32_t y() const { return fY; } + + /** Returns true if fX and fY are both zero. + + @return true if fX is zero and fY is zero + */ + bool isZero() const { return (fX | fY) == 0; } + + /** Sets fX to x and fY to y. + + @param x new value for fX + @param y new value for fY + */ + void set(int32_t x, int32_t y) { + fX = x; + fY = y; + } + + /** Returns SkIPoint changing the signs of fX and fY. + + @return SkIPoint as (-fX, -fY) + */ + SkIPoint operator-() const { + return {-fX, -fY}; + } + + /** Offsets SkIPoint by ivector v. Sets SkIPoint to (fX + v.fX, fY + v.fY). + + @param v ivector to add + */ + void operator+=(const SkIVector& v) { + fX = Sk32_sat_add(fX, v.fX); + fY = Sk32_sat_add(fY, v.fY); + } + + /** Subtracts ivector v from SkIPoint. Sets SkIPoint to: (fX - v.fX, fY - v.fY). + + @param v ivector to subtract + */ + void operator-=(const SkIVector& v) { + fX = Sk32_sat_sub(fX, v.fX); + fY = Sk32_sat_sub(fY, v.fY); + } + + /** Returns true if SkIPoint is equivalent to SkIPoint constructed from (x, y). + + @param x value compared with fX + @param y value compared with fY + @return true if SkIPoint equals (x, y) + */ + bool equals(int32_t x, int32_t y) const { + return fX == x && fY == y; + } + + /** Returns true if a is equivalent to b. + + @param a SkIPoint to compare + @param b SkIPoint to compare + @return true if a.fX == b.fX and a.fY == b.fY + */ + friend bool operator==(const SkIPoint& a, const SkIPoint& b) { + return a.fX == b.fX && a.fY == b.fY; + } + + /** Returns true if a is not equivalent to b. + + @param a SkIPoint to compare + @param b SkIPoint to compare + @return true if a.fX != b.fX or a.fY != b.fY + */ + friend bool operator!=(const SkIPoint& a, const SkIPoint& b) { + return a.fX != b.fX || a.fY != b.fY; + } + + /** Returns ivector from b to a; computed as (a.fX - b.fX, a.fY - b.fY). + + Can also be used to subtract ivector from ivector, returning ivector. + + @param a SkIPoint or ivector to subtract from + @param b ivector to subtract + @return ivector from b to a + */ + friend SkIVector operator-(const SkIPoint& a, const SkIPoint& b) { + return { Sk32_sat_sub(a.fX, b.fX), Sk32_sat_sub(a.fY, b.fY) }; + } + + /** Returns SkIPoint resulting from SkIPoint a offset by ivector b, computed as: + (a.fX + b.fX, a.fY + b.fY). + + Can also be used to offset SkIPoint b by ivector a, returning SkIPoint. + Can also be used to add ivector to ivector, returning ivector. + + @param a SkIPoint or ivector to add to + @param b SkIPoint or ivector to add + @return SkIPoint equal to a offset by b + */ + friend SkIPoint operator+(const SkIPoint& a, const SkIVector& b) { + return { Sk32_sat_add(a.fX, b.fX), Sk32_sat_add(a.fY, b.fY) }; + } +}; + +struct SkPoint; + +/** SkVector provides an alternative name for SkPoint. SkVector and SkPoint can + be used interchangeably for all purposes. +*/ +typedef SkPoint SkVector; + +/** \struct SkPoint + SkPoint holds two 32-bit floating point coordinates. +*/ +struct SK_API SkPoint { + SkScalar fX; //!< x-axis value + SkScalar fY; //!< y-axis value + + /** Sets fX to x, fY to y. Used both to set SkPoint and vector. + + @param x SkScalar x-axis value of constructed SkPoint or vector + @param y SkScalar y-axis value of constructed SkPoint or vector + @return SkPoint (x, y) + */ + static constexpr SkPoint Make(SkScalar x, SkScalar y) { + return {x, y}; + } + + /** Returns x-axis value of SkPoint or vector. + + @return fX + */ + SkScalar x() const { return fX; } + + /** Returns y-axis value of SkPoint or vector. + + @return fY + */ + SkScalar y() const { return fY; } + + /** Returns true if fX and fY are both zero. + + @return true if fX is zero and fY is zero + */ + bool isZero() const { return (0 == fX) & (0 == fY); } + + /** Sets fX to x and fY to y. + + @param x new value for fX + @param y new value for fY + */ + void set(SkScalar x, SkScalar y) { + fX = x; + fY = y; + } + + /** Sets fX to x and fY to y, promoting integers to SkScalar values. + + Assigning a large integer value directly to fX or fY may cause a compiler + error, triggered by narrowing conversion of int to SkScalar. This safely + casts x and y to avoid the error. + + @param x new value for fX + @param y new value for fY + */ + void iset(int32_t x, int32_t y) { + fX = SkIntToScalar(x); + fY = SkIntToScalar(y); + } + + /** Sets fX to p.fX and fY to p.fY, promoting integers to SkScalar values. + + Assigning an SkIPoint containing a large integer value directly to fX or fY may + cause a compiler error, triggered by narrowing conversion of int to SkScalar. + This safely casts p.fX and p.fY to avoid the error. + + @param p SkIPoint members promoted to SkScalar + */ + void iset(const SkIPoint& p) { + fX = SkIntToScalar(p.fX); + fY = SkIntToScalar(p.fY); + } + + /** Sets fX to absolute value of pt.fX; and fY to absolute value of pt.fY. + + @param pt members providing magnitude for fX and fY + */ + void setAbs(const SkPoint& pt) { + fX = SkScalarAbs(pt.fX); + fY = SkScalarAbs(pt.fY); + } + + /** Adds offset to each SkPoint in points array with count entries. + + @param points SkPoint array + @param count entries in array + @param offset vector added to points + */ + static void Offset(SkPoint points[], int count, const SkVector& offset) { + Offset(points, count, offset.fX, offset.fY); + } + + /** Adds offset (dx, dy) to each SkPoint in points array of length count. + + @param points SkPoint array + @param count entries in array + @param dx added to fX in points + @param dy added to fY in points + */ + static void Offset(SkPoint points[], int count, SkScalar dx, SkScalar dy) { + for (int i = 0; i < count; ++i) { + points[i].offset(dx, dy); + } + } + + /** Adds offset (dx, dy) to SkPoint. + + @param dx added to fX + @param dy added to fY + */ + void offset(SkScalar dx, SkScalar dy) { + fX += dx; + fY += dy; + } + + /** Returns the Euclidean distance from origin, computed as: + + sqrt(fX * fX + fY * fY) + + . + + @return straight-line distance to origin + */ + SkScalar length() const { return SkPoint::Length(fX, fY); } + + /** Returns the Euclidean distance from origin, computed as: + + sqrt(fX * fX + fY * fY) + + . + + @return straight-line distance to origin + */ + SkScalar distanceToOrigin() const { return this->length(); } + + /** Scales (fX, fY) so that length() returns one, while preserving ratio of fX to fY, + if possible. If prior length is nearly zero, sets vector to (0, 0) and returns + false; otherwise returns true. + + @return true if former length is not zero or nearly zero + */ + bool normalize(); + + /** Sets vector to (x, y) scaled so length() returns one, and so that + (fX, fY) is proportional to (x, y). If (x, y) length is nearly zero, + sets vector to (0, 0) and returns false; otherwise returns true. + + @param x proportional value for fX + @param y proportional value for fY + @return true if (x, y) length is not zero or nearly zero + */ + bool setNormalize(SkScalar x, SkScalar y); + + /** Scales vector so that distanceToOrigin() returns length, if possible. If former + length is nearly zero, sets vector to (0, 0) and return false; otherwise returns + true. + + @param length straight-line distance to origin + @return true if former length is not zero or nearly zero + */ + bool setLength(SkScalar length); + + /** Sets vector to (x, y) scaled to length, if possible. If former + length is nearly zero, sets vector to (0, 0) and return false; otherwise returns + true. + + @param x proportional value for fX + @param y proportional value for fY + @param length straight-line distance to origin + @return true if (x, y) length is not zero or nearly zero + */ + bool setLength(SkScalar x, SkScalar y, SkScalar length); + + /** Sets dst to SkPoint times scale. dst may be SkPoint to modify SkPoint in place. + + @param scale factor to multiply SkPoint by + @param dst storage for scaled SkPoint + */ + void scale(SkScalar scale, SkPoint* dst) const; + + /** Scales SkPoint in place by scale. + + @param value factor to multiply SkPoint by + */ + void scale(SkScalar value) { this->scale(value, this); } + + /** Changes the sign of fX and fY. + */ + void negate() { + fX = -fX; + fY = -fY; + } + + /** Returns SkPoint changing the signs of fX and fY. + + @return SkPoint as (-fX, -fY) + */ + SkPoint operator-() const { + return {-fX, -fY}; + } + + /** Adds vector v to SkPoint. Sets SkPoint to: (fX + v.fX, fY + v.fY). + + @param v vector to add + */ + void operator+=(const SkVector& v) { + fX += v.fX; + fY += v.fY; + } + + /** Subtracts vector v from SkPoint. Sets SkPoint to: (fX - v.fX, fY - v.fY). + + @param v vector to subtract + */ + void operator-=(const SkVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + /** Returns SkPoint multiplied by scale. + + @param scale scalar to multiply by + @return SkPoint as (fX * scale, fY * scale) + */ + SkPoint operator*(SkScalar scale) const { + return {fX * scale, fY * scale}; + } + + /** Multiplies SkPoint by scale. Sets SkPoint to: (fX * scale, fY * scale). + + @param scale scalar to multiply by + @return reference to SkPoint + */ + SkPoint& operator*=(SkScalar scale) { + fX *= scale; + fY *= scale; + return *this; + } + + /** Returns true if both fX and fY are measurable values. + + @return true for values other than infinities and NaN + */ + bool isFinite() const { + SkScalar accum = 0; + accum *= fX; + accum *= fY; + + // accum is either NaN or it is finite (zero). + SkASSERT(0 == accum || SkScalarIsNaN(accum)); + + // value==value will be true iff value is not NaN + // TODO: is it faster to say !accum or accum==accum? + return !SkScalarIsNaN(accum); + } + + /** Returns true if SkPoint is equivalent to SkPoint constructed from (x, y). + + @param x value compared with fX + @param y value compared with fY + @return true if SkPoint equals (x, y) + */ + bool equals(SkScalar x, SkScalar y) const { + return fX == x && fY == y; + } + + /** Returns true if a is equivalent to b. + + @param a SkPoint to compare + @param b SkPoint to compare + @return true if a.fX == b.fX and a.fY == b.fY + */ + friend bool operator==(const SkPoint& a, const SkPoint& b) { + return a.fX == b.fX && a.fY == b.fY; + } + + /** Returns true if a is not equivalent to b. + + @param a SkPoint to compare + @param b SkPoint to compare + @return true if a.fX != b.fX or a.fY != b.fY + */ + friend bool operator!=(const SkPoint& a, const SkPoint& b) { + return a.fX != b.fX || a.fY != b.fY; + } + + /** Returns vector from b to a, computed as (a.fX - b.fX, a.fY - b.fY). + + Can also be used to subtract vector from SkPoint, returning SkPoint. + Can also be used to subtract vector from vector, returning vector. + + @param a SkPoint to subtract from + @param b SkPoint to subtract + @return vector from b to a + */ + friend SkVector operator-(const SkPoint& a, const SkPoint& b) { + return {a.fX - b.fX, a.fY - b.fY}; + } + + /** Returns SkPoint resulting from SkPoint a offset by vector b, computed as: + (a.fX + b.fX, a.fY + b.fY). + + Can also be used to offset SkPoint b by vector a, returning SkPoint. + Can also be used to add vector to vector, returning vector. + + @param a SkPoint or vector to add to + @param b SkPoint or vector to add + @return SkPoint equal to a offset by b + */ + friend SkPoint operator+(const SkPoint& a, const SkVector& b) { + return {a.fX + b.fX, a.fY + b.fY}; + } + + /** Returns the Euclidean distance from origin, computed as: + + sqrt(x * x + y * y) + + . + + @param x component of length + @param y component of length + @return straight-line distance to origin + */ + static SkScalar Length(SkScalar x, SkScalar y); + + /** Scales (vec->fX, vec->fY) so that length() returns one, while preserving ratio of vec->fX + to vec->fY, if possible. If original length is nearly zero, sets vec to (0, 0) and returns + zero; otherwise, returns length of vec before vec is scaled. + + Returned prior length may be SK_ScalarInfinity if it can not be represented by SkScalar. + + Note that normalize() is faster if prior length is not required. + + @param vec normalized to unit length + @return original vec length + */ + static SkScalar Normalize(SkVector* vec); + + /** Returns the Euclidean distance between a and b. + + @param a line end point + @param b line end point + @return straight-line distance from a to b + */ + static SkScalar Distance(const SkPoint& a, const SkPoint& b) { + return Length(a.fX - b.fX, a.fY - b.fY); + } + + /** Returns the dot product of vector a and vector b. + + @param a left side of dot product + @param b right side of dot product + @return product of input magnitudes and cosine of the angle between them + */ + static SkScalar DotProduct(const SkVector& a, const SkVector& b) { + return a.fX * b.fX + a.fY * b.fY; + } + + /** Returns the cross product of vector a and vector b. + + a and b form three-dimensional vectors with z-axis value equal to zero. The + cross product is a three-dimensional vector with x-axis and y-axis values equal + to zero. The cross product z-axis component is returned. + + @param a left side of cross product + @param b right side of cross product + @return area spanned by vectors signed by angle direction + */ + static SkScalar CrossProduct(const SkVector& a, const SkVector& b) { + return a.fX * b.fY - a.fY * b.fX; + } + + /** Returns the cross product of vector and vec. + + Vector and vec form three-dimensional vectors with z-axis value equal to zero. + The cross product is a three-dimensional vector with x-axis and y-axis values + equal to zero. The cross product z-axis component is returned. + + @param vec right side of cross product + @return area spanned by vectors signed by angle direction + */ + SkScalar cross(const SkVector& vec) const { + return CrossProduct(*this, vec); + } + + /** Returns the dot product of vector and vector vec. + + @param vec right side of dot product + @return product of input magnitudes and cosine of the angle between them + */ + SkScalar dot(const SkVector& vec) const { + return DotProduct(*this, vec); + } + +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkPoint3.h b/gfx/skia/skia/include/core/SkPoint3.h new file mode 100644 index 0000000000..15b082a427 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPoint3.h @@ -0,0 +1,157 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPoint3_DEFINED +#define SkPoint3_DEFINED + +#include "include/core/SkPoint.h" + +struct SK_API SkPoint3 { + SkScalar fX, fY, fZ; + + static SkPoint3 Make(SkScalar x, SkScalar y, SkScalar z) { + SkPoint3 pt; + pt.set(x, y, z); + return pt; + } + + SkScalar x() const { return fX; } + SkScalar y() const { return fY; } + SkScalar z() const { return fZ; } + + void set(SkScalar x, SkScalar y, SkScalar z) { fX = x; fY = y; fZ = z; } + + friend bool operator==(const SkPoint3& a, const SkPoint3& b) { + return a.fX == b.fX && a.fY == b.fY && a.fZ == b.fZ; + } + + friend bool operator!=(const SkPoint3& a, const SkPoint3& b) { + return !(a == b); + } + + /** Returns the Euclidian distance from (0,0,0) to (x,y,z) + */ + static SkScalar Length(SkScalar x, SkScalar y, SkScalar z); + + /** Return the Euclidian distance from (0,0,0) to the point + */ + SkScalar length() const { return SkPoint3::Length(fX, fY, fZ); } + + /** Set the point (vector) to be unit-length in the same direction as it + already points. If the point has a degenerate length (i.e., nearly 0) + then set it to (0,0,0) and return false; otherwise return true. + */ + bool normalize(); + + /** Return a new point whose X, Y and Z coordinates are scaled. + */ + SkPoint3 makeScale(SkScalar scale) const { + SkPoint3 p; + p.set(scale * fX, scale * fY, scale * fZ); + return p; + } + + /** Scale the point's coordinates by scale. + */ + void scale(SkScalar value) { + fX *= value; + fY *= value; + fZ *= value; + } + + /** Return a new point whose X, Y and Z coordinates are the negative of the + original point's + */ + SkPoint3 operator-() const { + SkPoint3 neg; + neg.fX = -fX; + neg.fY = -fY; + neg.fZ = -fZ; + return neg; + } + + /** Returns a new point whose coordinates are the difference between + a and b (i.e., a - b) + */ + friend SkPoint3 operator-(const SkPoint3& a, const SkPoint3& b) { + SkPoint3 v; + v.set(a.fX - b.fX, a.fY - b.fY, a.fZ - b.fZ); + return v; + } + + /** Returns a new point whose coordinates are the sum of a and b (a + b) + */ + friend SkPoint3 operator+(const SkPoint3& a, const SkPoint3& b) { + SkPoint3 v; + v.set(a.fX + b.fX, a.fY + b.fY, a.fZ + b.fZ); + return v; + } + + /** Add v's coordinates to the point's + */ + void operator+=(const SkPoint3& v) { + fX += v.fX; + fY += v.fY; + fZ += v.fZ; + } + + /** Subtract v's coordinates from the point's + */ + void operator-=(const SkPoint3& v) { + fX -= v.fX; + fY -= v.fY; + fZ -= v.fZ; + } + + /** Returns true if fX, fY, and fZ are measurable values. + + @return true for values other than infinities and NaN + */ + bool isFinite() const { + SkScalar accum = 0; + accum *= fX; + accum *= fY; + accum *= fZ; + + // accum is either NaN or it is finite (zero). + SkASSERT(0 == accum || SkScalarIsNaN(accum)); + + // value==value will be true iff value is not NaN + // TODO: is it faster to say !accum or accum==accum? + return !SkScalarIsNaN(accum); + } + + /** Returns the dot product of a and b, treating them as 3D vectors + */ + static SkScalar DotProduct(const SkPoint3& a, const SkPoint3& b) { + return a.fX * b.fX + a.fY * b.fY + a.fZ * b.fZ; + } + + SkScalar dot(const SkPoint3& vec) const { + return DotProduct(*this, vec); + } + + /** Returns the cross product of a and b, treating them as 3D vectors + */ + static SkPoint3 CrossProduct(const SkPoint3& a, const SkPoint3& b) { + SkPoint3 result; + result.fX = a.fY*b.fZ - a.fZ*b.fY; + result.fY = a.fZ*b.fX - a.fX*b.fZ; + result.fZ = a.fX*b.fY - a.fY*b.fX; + + return result; + } + + SkPoint3 cross(const SkPoint3& vec) const { + return CrossProduct(*this, vec); + } +}; + +typedef SkPoint3 SkVector3; +typedef SkPoint3 SkColor3f; + +#endif diff --git a/gfx/skia/skia/include/core/SkPostConfig.h b/gfx/skia/skia/include/core/SkPostConfig.h new file mode 100644 index 0000000000..d314aea758 --- /dev/null +++ b/gfx/skia/skia/include/core/SkPostConfig.h @@ -0,0 +1,306 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// IWYU pragma: private, include "SkTypes.h" + +#ifndef SkPostConfig_DEFINED +#define SkPostConfig_DEFINED + +#if !defined(SK_DEBUG) && !defined(SK_RELEASE) + #ifdef NDEBUG + #define SK_RELEASE + #else + #define SK_DEBUG + #endif +#endif + +#if defined(SK_DEBUG) && defined(SK_RELEASE) +# error "cannot define both SK_DEBUG and SK_RELEASE" +#elif !defined(SK_DEBUG) && !defined(SK_RELEASE) +# error "must define either SK_DEBUG or SK_RELEASE" +#endif + +/** + * Matrix calculations may be float or double. + * The default is float, as that's what Chromium's using. + */ +#if defined(SK_MSCALAR_IS_DOUBLE) && defined(SK_MSCALAR_IS_FLOAT) +# error "cannot define both SK_MSCALAR_IS_DOUBLE and SK_MSCALAR_IS_FLOAT" +#elif !defined(SK_MSCALAR_IS_DOUBLE) && !defined(SK_MSCALAR_IS_FLOAT) +# define SK_MSCALAR_IS_FLOAT +#endif + +#if defined(SK_CPU_LENDIAN) && defined(SK_CPU_BENDIAN) +# error "cannot define both SK_CPU_LENDIAN and SK_CPU_BENDIAN" +#elif !defined(SK_CPU_LENDIAN) && !defined(SK_CPU_BENDIAN) +# error "must define either SK_CPU_LENDIAN or SK_CPU_BENDIAN" +#endif + +#if defined(SK_CPU_BENDIAN) && !defined(I_ACKNOWLEDGE_SKIA_DOES_NOT_SUPPORT_BIG_ENDIAN) + #error "The Skia team is not endian-savvy enough to support big-endian CPUs." + #error "If you still want to use Skia," + #error "please define I_ACKNOWLEDGE_SKIA_DOES_NOT_SUPPORT_BIG_ENDIAN." +#endif + +#if !defined(SK_HAS_COMPILER_FEATURE) +# if defined(__has_feature) +# define SK_HAS_COMPILER_FEATURE(x) __has_feature(x) +# else +# define SK_HAS_COMPILER_FEATURE(x) 0 +# endif +#endif + +#if !defined(SK_ATTRIBUTE) +# if defined(__clang__) || defined(__GNUC__) +# define SK_ATTRIBUTE(attr) __attribute__((attr)) +# else +# define SK_ATTRIBUTE(attr) +# endif +#endif + +#if !defined(SK_SUPPORT_GPU) +# define SK_SUPPORT_GPU 1 +#endif + +/** + * If GPU is enabled but no GPU backends are enabled then enable GL by default. + * Traditionally clients have relied on Skia always building with the GL backend + * and opting in to additional backends. TODO: Require explicit opt in for GL. + */ +#if SK_SUPPORT_GPU +# if !defined(SK_GL) && !defined(SK_VULKAN) && !defined(SK_METAL) +# define SK_GL +# endif +#endif + +#if !defined(SK_SUPPORT_ATLAS_TEXT) +# define SK_SUPPORT_ATLAS_TEXT 0 +#elif SK_SUPPORT_ATLAS_TEXT && !SK_SUPPORT_GPU +# error "SK_SUPPORT_ATLAS_TEXT requires SK_SUPPORT_GPU" +#endif + +/** + * The clang static analyzer likes to know that when the program is not + * expected to continue (crash, assertion failure, etc). It will notice that + * some combination of parameters lead to a function call that does not return. + * It can then make appropriate assumptions about the parameters in code + * executed only if the non-returning function was *not* called. + */ +#if !defined(SkNO_RETURN_HINT) +# if SK_HAS_COMPILER_FEATURE(attribute_analyzer_noreturn) + static inline void SkNO_RETURN_HINT() __attribute__((analyzer_noreturn)); + static inline void SkNO_RETURN_HINT() {} +# else +# define SkNO_RETURN_HINT() do {} while (false) +# endif +#endif + +#if !defined(SkUNREACHABLE) +# if defined(_MSC_VER) && !defined(__clang__) +# define SkUNREACHABLE __assume(false) +# else +# define SkUNREACHABLE __builtin_unreachable() +# endif +#endif + +/////////////////////////////////////////////////////////////////////////////// + +#if defined(SK_BUILD_FOR_GOOGLE3) + void SkDebugfForDumpStackTrace(const char* data, void* unused); + void DumpStackTrace(int skip_count, void w(const char*, void*), void* arg); +# define SK_DUMP_GOOGLE3_STACK() DumpStackTrace(0, SkDebugfForDumpStackTrace, nullptr) +#else +# define SK_DUMP_GOOGLE3_STACK() +#endif + +#ifdef SK_BUILD_FOR_WIN +// permits visual studio to follow error back to source +#define SK_DUMP_LINE_FORMAT(message) \ + SkDebugf("%s(%d): fatal error: \"%s\"\n", __FILE__, __LINE__, message) +#else +#define SK_DUMP_LINE_FORMAT(message) \ + SkDebugf("%s:%d: fatal error: \"%s\"\n", __FILE__, __LINE__, message) +#endif + +#ifndef SK_ABORT +# define SK_ABORT(message) \ + do { if (sk_abort_is_enabled()) { \ + SkNO_RETURN_HINT(); \ + SK_DUMP_LINE_FORMAT(message); \ + SK_DUMP_GOOGLE3_STACK(); \ + sk_abort_no_print(); \ + SkUNREACHABLE; \ + } } while (false) +#endif + +// If SK_R32_SHIFT is set, we'll use that to choose RGBA or BGRA. +// If not, we'll default to RGBA everywhere except BGRA on Windows. +#if defined(SK_R32_SHIFT) + static_assert(SK_R32_SHIFT == 0 || SK_R32_SHIFT == 16, ""); +#elif defined(SK_BUILD_FOR_WIN) + #define SK_R32_SHIFT 16 +#else + #define SK_R32_SHIFT 0 +#endif + +#if defined(SK_B32_SHIFT) + static_assert(SK_B32_SHIFT == (16-SK_R32_SHIFT), ""); +#else + #define SK_B32_SHIFT (16-SK_R32_SHIFT) +#endif + +#define SK_G32_SHIFT 8 +#define SK_A32_SHIFT 24 + +/** + * SkColor has well defined shift values, but SkPMColor is configurable. This + * macro is a convenience that returns true if the shift values are equal while + * ignoring the machine's endianness. + */ +#define SK_COLOR_MATCHES_PMCOLOR_BYTE_ORDER \ + (SK_A32_SHIFT == 24 && SK_R32_SHIFT == 16 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 0) + +/** + * SK_PMCOLOR_BYTE_ORDER can be used to query the byte order of SkPMColor at compile time. The + * relationship between the byte order and shift values depends on machine endianness. If the shift + * order is R=0, G=8, B=16, A=24 then ((char*)&pmcolor)[0] will produce the R channel on a little + * endian machine and the A channel on a big endian machine. Thus, given those shifts values, + * SK_PMCOLOR_BYTE_ORDER(R,G,B,A) will be true on a little endian machine and + * SK_PMCOLOR_BYTE_ORDER(A,B,G,R) will be true on a big endian machine. + */ +#ifdef SK_CPU_BENDIAN +# define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3) \ + (SK_ ## C3 ## 32_SHIFT == 0 && \ + SK_ ## C2 ## 32_SHIFT == 8 && \ + SK_ ## C1 ## 32_SHIFT == 16 && \ + SK_ ## C0 ## 32_SHIFT == 24) +#else +# define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3) \ + (SK_ ## C0 ## 32_SHIFT == 0 && \ + SK_ ## C1 ## 32_SHIFT == 8 && \ + SK_ ## C2 ## 32_SHIFT == 16 && \ + SK_ ## C3 ## 32_SHIFT == 24) +#endif + +////////////////////////////////////////////////////////////////////////////////////////////// + +#if defined SK_DEBUG && defined SK_BUILD_FOR_WIN + #ifdef free + #undef free + #endif + #include + #undef free +#endif + +////////////////////////////////////////////////////////////////////// + +#if !defined(SK_UNUSED) +# if !defined(__clang__) && defined(_MSC_VER) +# define SK_UNUSED __pragma(warning(suppress:4189)) +# else +# define SK_UNUSED SK_ATTRIBUTE(unused) +# endif +#endif + +/** + * If your judgment is better than the compiler's (i.e. you've profiled it), + * you can use SK_ALWAYS_INLINE to force inlining. E.g. + * inline void someMethod() { ... } // may not be inlined + * SK_ALWAYS_INLINE void someMethod() { ... } // should always be inlined + */ +#if !defined(SK_ALWAYS_INLINE) +# if defined(SK_BUILD_FOR_WIN) +# define SK_ALWAYS_INLINE __forceinline +# else +# define SK_ALWAYS_INLINE SK_ATTRIBUTE(always_inline) inline +# endif +#endif + +/** + * If your judgment is better than the compiler's (i.e. you've profiled it), + * you can use SK_NEVER_INLINE to prevent inlining. + */ +#if !defined(SK_NEVER_INLINE) +# if defined(SK_BUILD_FOR_WIN) +# define SK_NEVER_INLINE __declspec(noinline) +# else +# define SK_NEVER_INLINE SK_ATTRIBUTE(noinline) +# endif +#endif + +////////////////////////////////////////////////////////////////////// + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 + #define SK_PREFETCH(ptr) _mm_prefetch(reinterpret_cast(ptr), _MM_HINT_T0) + #define SK_WRITE_PREFETCH(ptr) _mm_prefetch(reinterpret_cast(ptr), _MM_HINT_T0) +#elif defined(__GNUC__) + #define SK_PREFETCH(ptr) __builtin_prefetch(ptr) + #define SK_WRITE_PREFETCH(ptr) __builtin_prefetch(ptr, 1) +#else + #define SK_PREFETCH(ptr) + #define SK_WRITE_PREFETCH(ptr) +#endif + +////////////////////////////////////////////////////////////////////// + +#ifndef SK_PRINTF_LIKE +# if defined(__clang__) || defined(__GNUC__) +# define SK_PRINTF_LIKE(A, B) __attribute__((format(printf, (A), (B)))) +# else +# define SK_PRINTF_LIKE(A, B) +# endif +#endif + +////////////////////////////////////////////////////////////////////// + +#ifndef SK_SIZE_T_SPECIFIER +# if defined(_MSC_VER) && !defined(__clang__) +# define SK_SIZE_T_SPECIFIER "%Iu" +# else +# define SK_SIZE_T_SPECIFIER "%zu" +# endif +#endif + +////////////////////////////////////////////////////////////////////// + +#ifndef SK_ALLOW_STATIC_GLOBAL_INITIALIZERS + #define SK_ALLOW_STATIC_GLOBAL_INITIALIZERS 0 +#endif + +////////////////////////////////////////////////////////////////////// + +#if !defined(SK_GAMMA_EXPONENT) + #define SK_GAMMA_EXPONENT (0.0f) // SRGB +#endif + +////////////////////////////////////////////////////////////////////// + +#ifndef GR_TEST_UTILS +# define GR_TEST_UTILS 0 +#endif + +////////////////////////////////////////////////////////////////////// + +#if defined(SK_HISTOGRAM_ENUMERATION) && defined(SK_HISTOGRAM_BOOLEAN) +# define SK_HISTOGRAMS_ENABLED 1 +#else +# define SK_HISTOGRAMS_ENABLED 0 +#endif + +#ifndef SK_HISTOGRAM_BOOLEAN +# define SK_HISTOGRAM_BOOLEAN(name, value) +#endif + +#ifndef SK_HISTOGRAM_ENUMERATION +# define SK_HISTOGRAM_ENUMERATION(name, value, boundary_value) +#endif + +#ifndef SK_DISABLE_LEGACY_SHADERCONTEXT +#define SK_ENABLE_LEGACY_SHADERCONTEXT +#endif + +#endif // SkPostConfig_DEFINED diff --git a/gfx/skia/skia/include/core/SkPreConfig.h b/gfx/skia/skia/include/core/SkPreConfig.h new file mode 100644 index 0000000000..923d9650de --- /dev/null +++ b/gfx/skia/skia/include/core/SkPreConfig.h @@ -0,0 +1,200 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// IWYU pragma: private, include "SkTypes.h" + +#ifndef SkPreConfig_DEFINED +#define SkPreConfig_DEFINED + +// Allows embedders that want to disable macros that take arguments to just +// define that symbol to be one of these +#define SK_NOTHING_ARG1(arg1) +#define SK_NOTHING_ARG2(arg1, arg2) +#define SK_NOTHING_ARG3(arg1, arg2, arg3) + +////////////////////////////////////////////////////////////////////// + +#if !defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_WIN) && \ + !defined(SK_BUILD_FOR_UNIX) && !defined(SK_BUILD_FOR_MAC) + + #ifdef __APPLE__ + #include "TargetConditionals.h" + #endif + + #if defined(_WIN32) || defined(__SYMBIAN32__) + #define SK_BUILD_FOR_WIN + #elif defined(ANDROID) || defined(__ANDROID__) + #define SK_BUILD_FOR_ANDROID + #elif defined(linux) || defined(__linux) || defined(__FreeBSD__) || \ + defined(__OpenBSD__) || defined(__sun) || defined(__NetBSD__) || \ + defined(__DragonFly__) || defined(__Fuchsia__) || \ + defined(__GLIBC__) || defined(__GNU__) || defined(__unix__) + #define SK_BUILD_FOR_UNIX + #elif TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #define SK_BUILD_FOR_IOS + #else + #define SK_BUILD_FOR_MAC + #endif + +#endif + +////////////////////////////////////////////////////////////////////// + +#if defined(SK_BUILD_FOR_WIN) && !defined(__clang__) + #if !defined(SK_RESTRICT) + #define SK_RESTRICT __restrict + #endif + #if !defined(SK_WARN_UNUSED_RESULT) + #define SK_WARN_UNUSED_RESULT + #endif +#endif + +#if !defined(SK_RESTRICT) + #define SK_RESTRICT __restrict__ +#endif + +#if !defined(SK_WARN_UNUSED_RESULT) + #define SK_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#endif + +////////////////////////////////////////////////////////////////////// + +#if !defined(SK_CPU_BENDIAN) && !defined(SK_CPU_LENDIAN) + #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define SK_CPU_BENDIAN + #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define SK_CPU_LENDIAN + #elif defined(__sparc) || defined(__sparc__) || \ + defined(_POWER) || defined(__powerpc__) || \ + defined(__ppc__) || defined(__hppa) || \ + defined(__PPC__) || defined(__PPC64__) || \ + defined(_MIPSEB) || defined(__ARMEB__) || \ + defined(__s390__) || \ + (defined(__sh__) && defined(__BIG_ENDIAN__)) || \ + (defined(__ia64) && defined(__BIG_ENDIAN__)) + #define SK_CPU_BENDIAN + #else + #define SK_CPU_LENDIAN + #endif +#endif + +////////////////////////////////////////////////////////////////////// + +#if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) + #define SK_CPU_X86 1 +#endif + +/** + * SK_CPU_SSE_LEVEL + * + * If defined, SK_CPU_SSE_LEVEL should be set to the highest supported level. + * On non-intel CPU this should be undefined. + */ + +#define SK_CPU_SSE_LEVEL_SSE1 10 +#define SK_CPU_SSE_LEVEL_SSE2 20 +#define SK_CPU_SSE_LEVEL_SSE3 30 +#define SK_CPU_SSE_LEVEL_SSSE3 31 +#define SK_CPU_SSE_LEVEL_SSE41 41 +#define SK_CPU_SSE_LEVEL_SSE42 42 +#define SK_CPU_SSE_LEVEL_AVX 51 +#define SK_CPU_SSE_LEVEL_AVX2 52 +#define SK_CPU_SSE_LEVEL_AVX512 60 + +// When targetting iOS and using gyp to generate the build files, it is not +// possible to select files to build depending on the architecture (i.e. it +// is not possible to use hand optimized assembly implementation). In that +// configuration SK_BUILD_NO_OPTS is defined. Remove optimisation then. +#ifdef SK_BUILD_NO_OPTS + #define SK_CPU_SSE_LEVEL 0 +#endif + +// Are we in GCC/Clang? +#ifndef SK_CPU_SSE_LEVEL + // These checks must be done in descending order to ensure we set the highest + // available SSE level. + #if defined(__AVX512F__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX512 + #elif defined(__AVX2__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 + #elif defined(__AVX__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX + #elif defined(__SSE4_2__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE42 + #elif defined(__SSE4_1__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE41 + #elif defined(__SSSE3__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSSE3 + #elif defined(__SSE3__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE3 + #elif defined(__SSE2__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 + #endif +#endif + +// Are we in VisualStudio? +#ifndef SK_CPU_SSE_LEVEL + // These checks must be done in descending order to ensure we set the highest + // available SSE level. 64-bit intel guarantees at least SSE2 support. + #if defined(__AVX2__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 + #elif defined(__AVX__) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX + #elif defined(_M_X64) || defined(_M_AMD64) + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 + #elif defined(_M_IX86_FP) + #if _M_IX86_FP >= 2 + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 + #elif _M_IX86_FP == 1 + #define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE1 + #endif + #endif +#endif + +////////////////////////////////////////////////////////////////////// +// ARM defines + +#if defined(__arm__) && (!defined(__APPLE__) || !TARGET_IPHONE_SIMULATOR) + #define SK_CPU_ARM32 +#elif defined(__aarch64__) && !defined(SK_BUILD_NO_OPTS) + #define SK_CPU_ARM64 +#endif + +// All 64-bit ARM chips have NEON. Many 32-bit ARM chips do too. +#if !defined(SK_ARM_HAS_NEON) && !defined(SK_BUILD_NO_OPTS) && defined(__ARM_NEON) + #define SK_ARM_HAS_NEON +#endif + +// Really this __APPLE__ check shouldn't be necessary, but it seems that Apple's Clang defines +// __ARM_FEATURE_CRC32 for -arch arm64, even though their chips don't support those instructions! +#if defined(__ARM_FEATURE_CRC32) && !defined(__APPLE__) + #define SK_ARM_HAS_CRC32 +#endif + +////////////////////////////////////////////////////////////////////// + +#if !defined(SKIA_IMPLEMENTATION) + #define SKIA_IMPLEMENTATION 0 +#endif + +#if !defined(SK_API) + #if defined(SKIA_DLL) + #if defined(_MSC_VER) + #if SKIA_IMPLEMENTATION + #define SK_API __declspec(dllexport) + #else + #define SK_API __declspec(dllimport) + #endif + #else + #define SK_API __attribute__((visibility("default"))) + #endif + #else + #define SK_API + #endif +#endif + +#endif diff --git a/gfx/skia/skia/include/core/SkPromiseImageTexture.h b/gfx/skia/skia/include/core/SkPromiseImageTexture.h new file mode 100644 index 0000000000..467e501bee --- /dev/null +++ b/gfx/skia/skia/include/core/SkPromiseImageTexture.h @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPromiseImageTexture_DEFINED +#define SkPromiseImageTexture_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/private/GrResourceKey.h" + +#if SK_SUPPORT_GPU +/** + * This type is used to fulfill textures for PromiseImages. Once an instance is returned from a + * PromiseImageTextureFulfillProc it must remain valid until the corresponding + * PromiseImageTextureReleaseProc is called. For performance reasons it is recommended that the + * the client reuse a single PromiseImageTexture every time a given texture is returned by + * the PromiseImageTextureFulfillProc rather than recreating PromiseImageTextures representing + * the same underlying backend API texture. + */ +class SK_API SkPromiseImageTexture : public SkNVRefCnt { +public: + SkPromiseImageTexture() = delete; + SkPromiseImageTexture(const SkPromiseImageTexture&) = delete; + SkPromiseImageTexture(SkPromiseImageTexture&&) = delete; + ~SkPromiseImageTexture(); + SkPromiseImageTexture& operator=(const SkPromiseImageTexture&) = delete; + SkPromiseImageTexture& operator=(SkPromiseImageTexture&&) = delete; + + static sk_sp Make(const GrBackendTexture& backendTexture) { + if (!backendTexture.isValid()) { + return nullptr; + } + return sk_sp(new SkPromiseImageTexture(backendTexture)); + } + + const GrBackendTexture& backendTexture() const { return fBackendTexture; } + + void addKeyToInvalidate(uint32_t contextID, const GrUniqueKey& key); + uint32_t uniqueID() const { return fUniqueID; } + +#if GR_TEST_UTILS + SkTArray testingOnly_uniqueKeysToInvalidate() const; +#endif + +private: + explicit SkPromiseImageTexture(const GrBackendTexture& backendTexture); + + SkSTArray<1, GrUniqueKeyInvalidatedMessage> fMessages; + GrBackendTexture fBackendTexture; + uint32_t fUniqueID = SK_InvalidUniqueID; + static std::atomic gUniqueID; +}; +#endif + +#endif diff --git a/gfx/skia/skia/include/core/SkRRect.h b/gfx/skia/skia/include/core/SkRRect.h new file mode 100644 index 0000000000..7bc838784a --- /dev/null +++ b/gfx/skia/skia/include/core/SkRRect.h @@ -0,0 +1,519 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRRect_DEFINED +#define SkRRect_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" + +class SkPath; +class SkMatrix; + +/** \class SkRRect + SkRRect describes a rounded rectangle with a bounds and a pair of radii for each corner. + The bounds and radii can be set so that SkRRect describes: a rectangle with sharp corners; + a circle; an oval; or a rectangle with one or more rounded corners. + + SkRRect allows implementing CSS properties that describe rounded corners. + SkRRect may have up to eight different radii, one for each axis on each of its four + corners. + + SkRRect may modify the provided parameters when initializing bounds and radii. + If either axis radii is zero or less: radii are stored as zero; corner is square. + If corner curves overlap, radii are proportionally reduced to fit within bounds. +*/ +class SK_API SkRRect { +public: + + /** Initializes bounds at (0, 0), the origin, with zero width and height. + Initializes corner radii to (0, 0), and sets type of kEmpty_Type. + + @return empty SkRRect + */ + SkRRect() = default; + + /** Initializes to copy of rrect bounds and corner radii. + + @param rrect bounds and corner to copy + @return copy of rrect + */ + SkRRect(const SkRRect& rrect) = default; + + /** Copies rrect bounds and corner radii. + + @param rrect bounds and corner to copy + @return copy of rrect + */ + SkRRect& operator=(const SkRRect& rrect) = default; + + /** \enum SkRRect::Type + Type describes possible specializations of SkRRect. Each Type is + exclusive; a SkRRect may only have one type. + + Type members become progressively less restrictive; larger values of + Type have more degrees of freedom than smaller values. + */ + enum Type { + kEmpty_Type, //!< zero width or height + kRect_Type, //!< non-zero width and height, and zeroed radii + kOval_Type, //!< non-zero width and height filled with radii + kSimple_Type, //!< non-zero width and height with equal radii + kNinePatch_Type, //!< non-zero width and height with axis-aligned radii + kComplex_Type, //!< non-zero width and height with arbitrary radii + kLastType = kComplex_Type, //!< largest Type value + }; + + /** Returns SkRRect::Type, one of: + kEmpty_Type, kRect_Type, kOval_Type, kSimple_Type, kNinePatch_Type, + kComplex_Type. + + @return SkRRect::Type + */ + Type getType() const { + SkASSERT(this->isValid()); + return static_cast(fType); + } + + /** Returns SkRRect::Type, one of: + kEmpty_Type, kRect_Type, kOval_Type, kSimple_Type, kNinePatch_Type, + kComplex_Type. + + @return SkRRect::Type + */ + Type type() const { return this->getType(); } + + inline bool isEmpty() const { return kEmpty_Type == this->getType(); } + inline bool isRect() const { return kRect_Type == this->getType(); } + inline bool isOval() const { return kOval_Type == this->getType(); } + inline bool isSimple() const { return kSimple_Type == this->getType(); } + inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); } + inline bool isComplex() const { return kComplex_Type == this->getType(); } + + /** Returns span on the x-axis. This does not check if result fits in 32-bit float; + result may be infinity. + + @return rect().fRight minus rect().fLeft + */ + SkScalar width() const { return fRect.width(); } + + /** Returns span on the y-axis. This does not check if result fits in 32-bit float; + result may be infinity. + + @return rect().fBottom minus rect().fTop + */ + SkScalar height() const { return fRect.height(); } + + /** Returns top-left corner radii. If type() returns kEmpty_Type, kRect_Type, + kOval_Type, or kSimple_Type, returns a value representative of all corner radii. + If type() returns kNinePatch_Type or kComplex_Type, at least one of the + remaining three corners has a different value. + + @return corner radii for simple types + */ + SkVector getSimpleRadii() const { + return fRadii[0]; + } + + /** Sets bounds to zero width and height at (0, 0), the origin. Sets + corner radii to zero and sets type to kEmpty_Type. + */ + void setEmpty() { *this = SkRRect(); } + + /** Sets bounds to sorted rect, and sets corner radii to zero. + If set bounds has width and height, and sets type to kRect_Type; + otherwise, sets type to kEmpty_Type. + + @param rect bounds to set + */ + void setRect(const SkRect& rect) { + if (!this->initializeRect(rect)) { + return; + } + + memset(fRadii, 0, sizeof(fRadii)); + fType = kRect_Type; + + SkASSERT(this->isValid()); + } + + /** Initializes bounds at (0, 0), the origin, with zero width and height. + Initializes corner radii to (0, 0), and sets type of kEmpty_Type. + + @return empty SkRRect + */ + static SkRRect MakeEmpty() { return SkRRect(); } + + /** Initializes to copy of r bounds and zeroes corner radii. + + @param r bounds to copy + @return copy of r + */ + static SkRRect MakeRect(const SkRect& r) { + SkRRect rr; + rr.setRect(r); + return rr; + } + + /** Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis radii + to half oval.height(). If oval bounds is empty, sets to kEmpty_Type. + Otherwise, sets to kOval_Type. + + @param oval bounds of oval + @return oval + */ + static SkRRect MakeOval(const SkRect& oval) { + SkRRect rr; + rr.setOval(oval); + return rr; + } + + /** Sets to rounded rectangle with the same radii for all four corners. + If rect is empty, sets to kEmpty_Type. + Otherwise, if xRad and yRad are zero, sets to kRect_Type. + Otherwise, if xRad is at least half rect.width() and yRad is at least half + rect.height(), sets to kOval_Type. + Otherwise, sets to kSimple_Type. + + @param rect bounds of rounded rectangle + @param xRad x-axis radius of corners + @param yRad y-axis radius of corners + @return rounded rectangle + */ + static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { + SkRRect rr; + rr.setRectXY(rect, xRad, yRad); + return rr; + } + + /** Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis radii + to half oval.height(). If oval bounds is empty, sets to kEmpty_Type. + Otherwise, sets to kOval_Type. + + @param oval bounds of oval + */ + void setOval(const SkRect& oval) { + if (!this->initializeRect(oval)) { + return; + } + + SkScalar xRad = SkScalarHalf(fRect.width()); + SkScalar yRad = SkScalarHalf(fRect.height()); + + for (int i = 0; i < 4; ++i) { + fRadii[i].set(xRad, yRad); + } + fType = kOval_Type; + + SkASSERT(this->isValid()); + } + + /** Sets to rounded rectangle with the same radii for all four corners. + If rect is empty, sets to kEmpty_Type. + Otherwise, if xRad or yRad is zero, sets to kRect_Type. + Otherwise, if xRad is at least half rect.width() and yRad is at least half + rect.height(), sets to kOval_Type. + Otherwise, sets to kSimple_Type. + + @param rect bounds of rounded rectangle + @param xRad x-axis radius of corners + @param yRad y-axis radius of corners + */ + void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad); + + /** Sets bounds to rect. Sets radii to (leftRad, topRad), (rightRad, topRad), + (rightRad, bottomRad), (leftRad, bottomRad). + + If rect is empty, sets to kEmpty_Type. + Otherwise, if leftRad and rightRad are zero, sets to kRect_Type. + Otherwise, if topRad and bottomRad are zero, sets to kRect_Type. + Otherwise, if leftRad and rightRad are equal and at least half rect.width(), and + topRad and bottomRad are equal at least half rect.height(), sets to kOval_Type. + Otherwise, if leftRad and rightRad are equal, and topRad and bottomRad are equal, + sets to kSimple_Type. Otherwise, sets to kNinePatch_Type. + + Nine patch refers to the nine parts defined by the radii: one center rectangle, + four edge patches, and four corner patches. + + @param rect bounds of rounded rectangle + @param leftRad left-top and left-bottom x-axis radius + @param topRad left-top and right-top y-axis radius + @param rightRad right-top and right-bottom x-axis radius + @param bottomRad left-bottom and right-bottom y-axis radius + */ + void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, + SkScalar rightRad, SkScalar bottomRad); + + /** Sets bounds to rect. Sets radii array for individual control of all for corners. + + If rect is empty, sets to kEmpty_Type. + Otherwise, if one of each corner radii are zero, sets to kRect_Type. + Otherwise, if all x-axis radii are equal and at least half rect.width(), and + all y-axis radii are equal at least half rect.height(), sets to kOval_Type. + Otherwise, if all x-axis radii are equal, and all y-axis radii are equal, + sets to kSimple_Type. Otherwise, sets to kNinePatch_Type. + + @param rect bounds of rounded rectangle + @param radii corner x-axis and y-axis radii + */ + void setRectRadii(const SkRect& rect, const SkVector radii[4]); + + /** \enum SkRRect::Corner + The radii are stored: top-left, top-right, bottom-right, bottom-left. + */ + enum Corner { + kUpperLeft_Corner, //!< index of top-left corner radii + kUpperRight_Corner, //!< index of top-right corner radii + kLowerRight_Corner, //!< index of bottom-right corner radii + kLowerLeft_Corner, //!< index of bottom-left corner radii + }; + + /** Returns bounds. Bounds may have zero width or zero height. Bounds right is + greater than or equal to left; bounds bottom is greater than or equal to top. + Result is identical to getBounds(). + + @return bounding box + */ + const SkRect& rect() const { return fRect; } + + /** Returns scalar pair for radius of curve on x-axis and y-axis for one corner. + Both radii may be zero. If not zero, both are positive and finite. + + @param corner one of: kUpperLeft_Corner, kUpperRight_Corner, + kLowerRight_Corner, kLowerLeft_Corner + @return x-axis and y-axis radii for one corner + */ + SkVector radii(Corner corner) const { return fRadii[corner]; } + + /** Returns bounds. Bounds may have zero width or zero height. Bounds right is + greater than or equal to left; bounds bottom is greater than or equal to top. + Result is identical to rect(). + + @return bounding box + */ + const SkRect& getBounds() const { return fRect; } + + /** Returns true if bounds and radii in a are equal to bounds and radii in b. + + a and b are not equal if either contain NaN. a and b are equal if members + contain zeroes with different signs. + + @param a SkRect bounds and radii to compare + @param b SkRect bounds and radii to compare + @return true if members are equal + */ + friend bool operator==(const SkRRect& a, const SkRRect& b) { + return a.fRect == b.fRect && SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8); + } + + /** Returns true if bounds and radii in a are not equal to bounds and radii in b. + + a and b are not equal if either contain NaN. a and b are equal if members + contain zeroes with different signs. + + @param a SkRect bounds and radii to compare + @param b SkRect bounds and radii to compare + @return true if members are not equal + */ + friend bool operator!=(const SkRRect& a, const SkRRect& b) { + return a.fRect != b.fRect || !SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8); + } + + /** Copies SkRRect to dst, then insets dst bounds by dx and dy, and adjusts dst + radii by dx and dy. dx and dy may be positive, negative, or zero. dst may be + SkRRect. + + If either corner radius is zero, the corner has no curvature and is unchanged. + Otherwise, if adjusted radius becomes negative, pins radius to zero. + If dx exceeds half dst bounds width, dst bounds left and right are set to + bounds x-axis center. If dy exceeds half dst bounds height, dst bounds top and + bottom are set to bounds y-axis center. + + If dx or dy cause the bounds to become infinite, dst bounds is zeroed. + + @param dx added to rect().fLeft, and subtracted from rect().fRight + @param dy added to rect().fTop, and subtracted from rect().fBottom + @param dst insets bounds and radii + */ + void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const; + + /** Insets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be + positive, negative, or zero. + + If either corner radius is zero, the corner has no curvature and is unchanged. + Otherwise, if adjusted radius becomes negative, pins radius to zero. + If dx exceeds half bounds width, bounds left and right are set to + bounds x-axis center. If dy exceeds half bounds height, bounds top and + bottom are set to bounds y-axis center. + + If dx or dy cause the bounds to become infinite, bounds is zeroed. + + @param dx added to rect().fLeft, and subtracted from rect().fRight + @param dy added to rect().fTop, and subtracted from rect().fBottom + */ + void inset(SkScalar dx, SkScalar dy) { + this->inset(dx, dy, this); + } + + /** Outsets dst bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be + positive, negative, or zero. + + If either corner radius is zero, the corner has no curvature and is unchanged. + Otherwise, if adjusted radius becomes negative, pins radius to zero. + If dx exceeds half dst bounds width, dst bounds left and right are set to + bounds x-axis center. If dy exceeds half dst bounds height, dst bounds top and + bottom are set to bounds y-axis center. + + If dx or dy cause the bounds to become infinite, dst bounds is zeroed. + + @param dx subtracted from rect().fLeft, and added to rect().fRight + @param dy subtracted from rect().fTop, and added to rect().fBottom + @param dst outset bounds and radii + */ + void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const { + this->inset(-dx, -dy, dst); + } + + /** Outsets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be + positive, negative, or zero. + + If either corner radius is zero, the corner has no curvature and is unchanged. + Otherwise, if adjusted radius becomes negative, pins radius to zero. + If dx exceeds half bounds width, bounds left and right are set to + bounds x-axis center. If dy exceeds half bounds height, bounds top and + bottom are set to bounds y-axis center. + + If dx or dy cause the bounds to become infinite, bounds is zeroed. + + @param dx subtracted from rect().fLeft, and added to rect().fRight + @param dy subtracted from rect().fTop, and added to rect().fBottom + */ + void outset(SkScalar dx, SkScalar dy) { + this->inset(-dx, -dy, this); + } + + /** Translates SkRRect by (dx, dy). + + @param dx offset added to rect().fLeft and rect().fRight + @param dy offset added to rect().fTop and rect().fBottom + */ + void offset(SkScalar dx, SkScalar dy) { + fRect.offset(dx, dy); + } + + /** Returns SkRRect translated by (dx, dy). + + @param dx offset added to rect().fLeft and rect().fRight + @param dy offset added to rect().fTop and rect().fBottom + @return SkRRect bounds offset by (dx, dy), with unchanged corner radii + */ + SkRRect SK_WARN_UNUSED_RESULT makeOffset(SkScalar dx, SkScalar dy) const { + return SkRRect(fRect.makeOffset(dx, dy), fRadii, fType); + } + + /** Returns true if rect is inside the bounds and corner radii, and if + SkRRect and rect are not empty. + + @param rect area tested for containment + @return true if SkRRect contains rect + */ + bool contains(const SkRect& rect) const; + + /** Returns true if bounds and radii values are finite and describe a SkRRect + SkRRect::Type that matches getType(). All SkRRect methods construct valid types, + even if the input values are not valid. Invalid SkRRect data can only + be generated by corrupting memory. + + @return true if bounds and radii match type() + */ + bool isValid() const; + + static constexpr size_t kSizeInMemory = 12 * sizeof(SkScalar); + + /** Writes SkRRect to buffer. Writes kSizeInMemory bytes, and returns + kSizeInMemory, the number of bytes written. + + @param buffer storage for SkRRect + @return bytes written, kSizeInMemory + */ + size_t writeToMemory(void* buffer) const; + + /** Reads SkRRect from buffer, reading kSizeInMemory bytes. + Returns kSizeInMemory, bytes read if length is at least kSizeInMemory. + Otherwise, returns zero. + + @param buffer memory to read from + @param length size of buffer + @return bytes read, or 0 if length is less than kSizeInMemory + */ + size_t readFromMemory(const void* buffer, size_t length); + + /** Transforms by SkRRect by matrix, storing result in dst. + Returns true if SkRRect transformed can be represented by another SkRRect. + Returns false if matrix contains transformations that are not axis aligned. + + Asserts in debug builds if SkRRect equals dst. + + @param matrix SkMatrix specifying the transform + @param dst SkRRect to store the result + @return true if transformation succeeded. + */ + bool transform(const SkMatrix& matrix, SkRRect* dst) const; + + /** Writes text representation of SkRRect to standard output. + Set asHex true to generate exact binary representations + of floating point numbers. + + @param asHex true if SkScalar values are written as hexadecimal + */ + void dump(bool asHex) const; + + /** Writes text representation of SkRRect to standard output. The representation + may be directly compiled as C++ code. Floating point values are written + with limited precision; it may not be possible to reconstruct original + SkRRect from output. + */ + void dump() const { this->dump(false); } + + /** Writes text representation of SkRRect to standard output. The representation + may be directly compiled as C++ code. Floating point values are written + in hexadecimal to preserve their exact bit pattern. The output reconstructs the + original SkRRect. + */ + void dumpHex() const { this->dump(true); } + +private: + static bool AreRectAndRadiiValid(const SkRect&, const SkVector[4]); + + SkRRect(const SkRect& rect, const SkVector radii[4], int32_t type) + : fRect(rect) + , fRadii{radii[0], radii[1], radii[2], radii[3]} + , fType(type) {} + + /** + * Initializes fRect. If the passed in rect is not finite or empty the rrect will be fully + * initialized and false is returned. Otherwise, just fRect is initialized and true is returned. + */ + bool initializeRect(const SkRect&); + + void computeType(); + bool checkCornerContainment(SkScalar x, SkScalar y) const; + void scaleRadii(const SkRect& rect); + + SkRect fRect = SkRect::MakeEmpty(); + // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[] + SkVector fRadii[4] = {{0, 0}, {0, 0}, {0,0}, {0,0}}; + // use an explicitly sized type so we're sure the class is dense (no uninitialized bytes) + int32_t fType = kEmpty_Type; + // TODO: add padding so we can use memcpy for flattening and not copy uninitialized data + + // to access fRadii directly + friend class SkPath; + friend class SkRRectPriv; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkRSXform.h b/gfx/skia/skia/include/core/SkRSXform.h new file mode 100644 index 0000000000..91653311d9 --- /dev/null +++ b/gfx/skia/skia/include/core/SkRSXform.h @@ -0,0 +1,69 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRSXform_DEFINED +#define SkRSXform_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkSize.h" + +/** + * A compressed form of a rotation+scale matrix. + * + * [ fSCos -fSSin fTx ] + * [ fSSin fSCos fTy ] + * [ 0 0 1 ] + */ +struct SkRSXform { + static SkRSXform Make(SkScalar scos, SkScalar ssin, SkScalar tx, SkScalar ty) { + SkRSXform xform = { scos, ssin, tx, ty }; + return xform; + } + + /* + * Initialize a new xform based on the scale, rotation (in radians), final tx,ty location + * and anchor-point ax,ay within the src quad. + * + * Note: the anchor point is not normalized (e.g. 0...1) but is in pixels of the src image. + */ + static SkRSXform MakeFromRadians(SkScalar scale, SkScalar radians, SkScalar tx, SkScalar ty, + SkScalar ax, SkScalar ay) { + const SkScalar s = SkScalarSin(radians) * scale; + const SkScalar c = SkScalarCos(radians) * scale; + return Make(c, s, tx + -c * ax + s * ay, ty + -s * ax - c * ay); + } + + SkScalar fSCos; + SkScalar fSSin; + SkScalar fTx; + SkScalar fTy; + + bool rectStaysRect() const { + return 0 == fSCos || 0 == fSSin; + } + + void setIdentity() { + fSCos = 1; + fSSin = fTx = fTy = 0; + } + + void set(SkScalar scos, SkScalar ssin, SkScalar tx, SkScalar ty) { + fSCos = scos; + fSSin = ssin; + fTx = tx; + fTy = ty; + } + + void toQuad(SkScalar width, SkScalar height, SkPoint quad[4]) const; + void toQuad(const SkSize& size, SkPoint quad[4]) const { + this->toQuad(size.width(), size.height(), quad); + } + void toTriStrip(SkScalar width, SkScalar height, SkPoint strip[4]) const; +}; + +#endif + diff --git a/gfx/skia/skia/include/core/SkRWBuffer.h b/gfx/skia/skia/include/core/SkRWBuffer.h new file mode 100644 index 0000000000..c0d24f6c5f --- /dev/null +++ b/gfx/skia/skia/include/core/SkRWBuffer.h @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRWBuffer_DEFINED +#define SkRWBuffer_DEFINED + +#include "include/core/SkRefCnt.h" + +struct SkBufferBlock; +struct SkBufferHead; +class SkRWBuffer; +class SkStreamAsset; + +/** + * Contains a read-only, thread-sharable block of memory. To access the memory, the caller must + * instantiate a local iterator, as the memory is stored in 1 or more contiguous blocks. + */ +class SK_API SkROBuffer : public SkRefCnt { +public: + /** + * Return the logical length of the data owned/shared by this buffer. It may be stored in + * multiple contiguous blocks, accessible via the iterator. + */ + size_t size() const { return fAvailable; } + + class SK_API Iter { + public: + Iter(const SkROBuffer*); + Iter(const sk_sp&); + + void reset(const SkROBuffer*); + + /** + * Return the current continuous block of memory, or nullptr if the iterator is exhausted + */ + const void* data() const; + + /** + * Returns the number of bytes in the current continguous block of memory, or 0 if the + * iterator is exhausted. + */ + size_t size() const; + + /** + * Advance to the next contiguous block of memory, returning true if there is another + * block, or false if the iterator is exhausted. + */ + bool next(); + + private: + const SkBufferBlock* fBlock; + size_t fRemaining; + const SkROBuffer* fBuffer; + }; + +private: + SkROBuffer(const SkBufferHead* head, size_t available, const SkBufferBlock* fTail); + virtual ~SkROBuffer(); + + const SkBufferHead* fHead; + const size_t fAvailable; + const SkBufferBlock* fTail; + + friend class SkRWBuffer; +}; + +/** + * Accumulates bytes of memory that are "appended" to it, growing internal storage as needed. + * The growth is done such that at any time in the writer's thread, an RBuffer or StreamAsset + * can be snapped off (and safely passed to another thread). The RBuffer/StreamAsset snapshot + * can see the previously stored bytes, but will be unaware of any future writes. + */ +class SK_API SkRWBuffer { +public: + SkRWBuffer(size_t initialCapacity = 0); + ~SkRWBuffer(); + + size_t size() const { return fTotalUsed; } + + /** + * Append |length| bytes from |buffer|. + * + * If the caller knows in advance how much more data they are going to append, they can + * pass a |reserve| hint (representing the number of upcoming bytes *in addition* to the + * current append), to minimize the number of internal allocations. + */ + void append(const void* buffer, size_t length, size_t reserve = 0); + + sk_sp makeROBufferSnapshot() const { + return sk_sp(new SkROBuffer(fHead, fTotalUsed, fTail)); + } + + std::unique_ptr makeStreamSnapshot() const; + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + +private: + SkBufferHead* fHead; + SkBufferBlock* fTail; + size_t fTotalUsed; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkRasterHandleAllocator.h b/gfx/skia/skia/include/core/SkRasterHandleAllocator.h new file mode 100644 index 0000000000..6f5fe682d6 --- /dev/null +++ b/gfx/skia/skia/include/core/SkRasterHandleAllocator.h @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRasterHandleAllocator_DEFINED +#define SkRasterHandleAllocator_DEFINED + +#include "include/core/SkImageInfo.h" + +class SkBitmap; +class SkCanvas; +class SkMatrix; + +/** + * If a client wants to control the allocation of raster layers in a canvas, it should subclass + * SkRasterHandleAllocator. This allocator performs two tasks: + * 1. controls how the memory for the pixels is allocated + * 2. associates a "handle" to a private object that can track the matrix/clip of the SkCanvas + * + * This example allocates a canvas, and defers to the allocator to create the base layer. + * + * std::unique_ptr canvas = SkRasterHandleAllocator::MakeCanvas( + * SkImageInfo::Make(...), + * skstd::make_unique(...), + * nullptr); + * + * If you have already allocated the base layer (and its handle, release-proc etc.) then you + * can pass those in using the last parameter to MakeCanvas(). + * + * Regardless of how the base layer is allocated, each time canvas->saveLayer() is called, + * your allocator's allocHandle() will be called. + */ +class SK_API SkRasterHandleAllocator { +public: + virtual ~SkRasterHandleAllocator() {} + + // The value that is returned to clients of the canvas that has this allocator installed. + typedef void* Handle; + + struct Rec { + // When the allocation goes out of scope, this proc is called to free everything associated + // with it: the pixels, the "handle", etc. This is passed the pixel address and fReleaseCtx. + void (*fReleaseProc)(void* pixels, void* ctx); + void* fReleaseCtx; // context passed to fReleaseProc + void* fPixels; // pixels for this allocation + size_t fRowBytes; // rowbytes for these pixels + Handle fHandle; // public handle returned by SkCanvas::accessTopRasterHandle() + }; + + /** + * Given a requested info, allocate the corresponding pixels/rowbytes, and whatever handle + * is desired to give clients access to those pixels. The rec also contains a proc and context + * which will be called when this allocation goes out of scope. + * + * e.g. + * when canvas->saveLayer() is called, the allocator will be called to allocate the pixels + * for the layer. When canvas->restore() is called, the fReleaseProc will be called. + */ + virtual bool allocHandle(const SkImageInfo&, Rec*) = 0; + + /** + * Clients access the handle for a given layer by calling SkCanvas::accessTopRasterHandle(). + * To allow the handle to reflect the current matrix/clip in the canvs, updateHandle() is + * is called. The subclass is responsible to update the handle as it sees fit. + */ + virtual void updateHandle(Handle, const SkMatrix&, const SkIRect&) = 0; + + /** + * This creates a canvas which will use the allocator to manage pixel allocations, including + * all calls to saveLayer(). + * + * If rec is non-null, then it will be used as the base-layer of pixels/handle. + * If rec is null, then the allocator will be called for the base-layer as well. + */ + static std::unique_ptr MakeCanvas(std::unique_ptr, + const SkImageInfo&, const Rec* rec = nullptr); + +private: + friend class SkBitmapDevice; + + Handle allocBitmap(const SkImageInfo&, SkBitmap*); +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkRect.h b/gfx/skia/skia/include/core/SkRect.h new file mode 100644 index 0000000000..eff1c0a69f --- /dev/null +++ b/gfx/skia/skia/include/core/SkRect.h @@ -0,0 +1,1335 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRect_DEFINED +#define SkRect_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkSize.h" +#include "include/private/SkSafe32.h" +#include "include/private/SkTFitsIn.h" + +#include + +struct SkRect; + +/** \struct SkIRect + SkIRect holds four 32-bit integer coordinates describing the upper and + lower bounds of a rectangle. SkIRect may be created from outer bounds or + from position, width, and height. SkIRect describes an area; if its right + is less than or equal to its left, or if its bottom is less than or equal to + its top, it is considered empty. +*/ +struct SK_API SkIRect { + int32_t fLeft; //!< smaller x-axis bounds + int32_t fTop; //!< smaller y-axis bounds + int32_t fRight; //!< larger x-axis bounds + int32_t fBottom; //!< larger y-axis bounds + + /** Returns constructed SkIRect set to (0, 0, 0, 0). + Many other rectangles are empty; if left is equal to or greater than right, + or if top is equal to or greater than bottom. Setting all members to zero + is a convenience, but does not designate a special empty rectangle. + + @return bounds (0, 0, 0, 0) + */ + static constexpr SkIRect SK_WARN_UNUSED_RESULT MakeEmpty() { + return SkIRect{0, 0, 0, 0}; + } + + /** Returns constructed SkIRect set to (0, 0, w, h). Does not validate input; w or h + may be negative. + + @param w width of constructed SkIRect + @param h height of constructed SkIRect + @return bounds (0, 0, w, h) + */ + static constexpr SkIRect SK_WARN_UNUSED_RESULT MakeWH(int32_t w, int32_t h) { + return SkIRect{0, 0, w, h}; + } + + /** Returns constructed SkIRect set to (0, 0, size.width(), size.height()). + Does not validate input; size.width() or size.height() may be negative. + + @param size values for SkIRect width and height + @return bounds (0, 0, size.width(), size.height()) + */ + static constexpr SkIRect SK_WARN_UNUSED_RESULT MakeSize(const SkISize& size) { + return SkIRect{0, 0, size.fWidth, size.fHeight}; + } + + /** Returns constructed SkIRect set to (l, t, r, b). Does not sort input; SkIRect may + result in fLeft greater than fRight, or fTop greater than fBottom. + + @param l integer stored in fLeft + @param t integer stored in fTop + @param r integer stored in fRight + @param b integer stored in fBottom + @return bounds (l, t, r, b) + */ + static constexpr SkIRect SK_WARN_UNUSED_RESULT MakeLTRB(int32_t l, int32_t t, + int32_t r, int32_t b) { + return SkIRect{l, t, r, b}; + } + + /** Returns constructed SkIRect set to: (x, y, x + w, y + h). + Does not validate input; w or h may be negative. + + @param x stored in fLeft + @param y stored in fTop + @param w added to x and stored in fRight + @param h added to y and stored in fBottom + @return bounds at (x, y) with width w and height h + */ + static constexpr SkIRect SK_WARN_UNUSED_RESULT MakeXYWH(int32_t x, int32_t y, + int32_t w, int32_t h) { + return { x, y, Sk32_sat_add(x, w), Sk32_sat_add(y, h) }; + } + + /** Returns left edge of SkIRect, if sorted. + Call sort() to reverse fLeft and fRight if needed. + + @return fLeft + */ + int32_t left() const { return fLeft; } + + /** Returns top edge of SkIRect, if sorted. Call isEmpty() to see if SkIRect may be invalid, + and sort() to reverse fTop and fBottom if needed. + + @return fTop + */ + int32_t top() const { return fTop; } + + /** Returns right edge of SkIRect, if sorted. + Call sort() to reverse fLeft and fRight if needed. + + @return fRight + */ + int32_t right() const { return fRight; } + + /** Returns bottom edge of SkIRect, if sorted. Call isEmpty() to see if SkIRect may be invalid, + and sort() to reverse fTop and fBottom if needed. + + @return fBottom + */ + int32_t bottom() const { return fBottom; } + + /** Returns left edge of SkIRect, if sorted. Call isEmpty() to see if SkIRect may be invalid, + and sort() to reverse fLeft and fRight if needed. + + @return fLeft + */ + int32_t x() const { return fLeft; } + + /** Returns top edge of SkIRect, if sorted. Call isEmpty() to see if SkIRect may be invalid, + and sort() to reverse fTop and fBottom if needed. + + @return fTop + */ + int32_t y() const { return fTop; } + + // Experimental + SkIPoint topLeft() const { return {fLeft, fTop}; } + + /** Returns span on the x-axis. This does not check if SkIRect is sorted, or if + result fits in 32-bit signed integer; result may be negative. + + @return fRight minus fLeft + */ + int32_t width() const { return Sk32_can_overflow_sub(fRight, fLeft); } + + /** Returns span on the y-axis. This does not check if SkIRect is sorted, or if + result fits in 32-bit signed integer; result may be negative. + + @return fBottom minus fTop + */ + int32_t height() const { return Sk32_can_overflow_sub(fBottom, fTop); } + + /** Returns spans on the x-axis and y-axis. This does not check if SkIRect is sorted, + or if result fits in 32-bit signed integer; result may be negative. + + @return SkISize (width, height) + */ + SkISize size() const { return SkISize::Make(this->width(), this->height()); } + + /** Returns span on the x-axis. This does not check if SkIRect is sorted, so the + result may be negative. This is safer than calling width() since width() might + overflow in its calculation. + + @return fRight minus fLeft cast to int64_t + */ + int64_t width64() const { return (int64_t)fRight - (int64_t)fLeft; } + + /** Returns span on the y-axis. This does not check if SkIRect is sorted, so the + result may be negative. This is safer than calling height() since height() might + overflow in its calculation. + + @return fBottom minus fTop cast to int64_t + */ + int64_t height64() const { return (int64_t)fBottom - (int64_t)fTop; } + + /** Returns true if fLeft is equal to or greater than fRight, or if fTop is equal + to or greater than fBottom. Call sort() to reverse rectangles with negative + width64() or height64(). + + @return true if width64() or height64() are zero or negative + */ + bool isEmpty64() const { return fRight <= fLeft || fBottom <= fTop; } + + /** Returns true if width() or height() are zero or negative. + + @return true if width() or height() are zero or negative + */ + bool isEmpty() const { + int64_t w = this->width64(); + int64_t h = this->height64(); + if (w <= 0 || h <= 0) { + return true; + } + // Return true if either exceeds int32_t + return !SkTFitsIn(w | h); + } + + /** Returns true if all members in a: fLeft, fTop, fRight, and fBottom; are + identical to corresponding members in b. + + @param a SkIRect to compare + @param b SkIRect to compare + @return true if members are equal + */ + friend bool operator==(const SkIRect& a, const SkIRect& b) { + return !memcmp(&a, &b, sizeof(a)); + } + + /** Returns true if any member in a: fLeft, fTop, fRight, and fBottom; is not + identical to the corresponding member in b. + + @param a SkIRect to compare + @param b SkIRect to compare + @return true if members are not equal + */ + friend bool operator!=(const SkIRect& a, const SkIRect& b) { + return !(a == b); + } + + /** Sets SkIRect to (0, 0, 0, 0). + + Many other rectangles are empty; if left is equal to or greater than right, + or if top is equal to or greater than bottom. Setting all members to zero + is a convenience, but does not designate a special empty rectangle. + */ + void setEmpty() { memset(this, 0, sizeof(*this)); } + + /** Sets SkIRect to (left, top, right, bottom). + left and right are not sorted; left is not necessarily less than right. + top and bottom are not sorted; top is not necessarily less than bottom. + + @param left stored in fLeft + @param top stored in fTop + @param right stored in fRight + @param bottom stored in fBottom + */ + void setLTRB(int32_t left, int32_t top, int32_t right, int32_t bottom) { + fLeft = left; + fTop = top; + fRight = right; + fBottom = bottom; + } + + /** Sets SkIRect to: (x, y, x + width, y + height). + Does not validate input; width or height may be negative. + + @param x stored in fLeft + @param y stored in fTop + @param width added to x and stored in fRight + @param height added to y and stored in fBottom + */ + void setXYWH(int32_t x, int32_t y, int32_t width, int32_t height) { + fLeft = x; + fTop = y; + fRight = Sk32_sat_add(x, width); + fBottom = Sk32_sat_add(y, height); + } + + void setWH(int32_t width, int32_t height) { + fLeft = 0; + fTop = 0; + fRight = width; + fBottom = height; + } + + /** Returns SkIRect offset by (dx, dy). + + If dx is negative, SkIRect returned is moved to the left. + If dx is positive, SkIRect returned is moved to the right. + If dy is negative, SkIRect returned is moved upward. + If dy is positive, SkIRect returned is moved downward. + + @param dx offset added to fLeft and fRight + @param dy offset added to fTop and fBottom + @return SkIRect offset by dx and dy, with original width and height + */ + constexpr SkIRect makeOffset(int32_t dx, int32_t dy) const { + return { + Sk32_sat_add(fLeft, dx), Sk32_sat_add(fTop, dy), + Sk32_sat_add(fRight, dx), Sk32_sat_add(fBottom, dy), + }; + } + + /** Returns SkIRect offset by (offset.x(), offset.y()). + + If offset.x() is negative, SkIRect returned is moved to the left. + If offset.x() is positive, SkIRect returned is moved to the right. + If offset.y() is negative, SkIRect returned is moved upward. + If offset.y() is positive, SkIRect returned is moved downward. + + @param offset translation vector + @return SkIRect translated by offset, with original width and height + */ + constexpr SkIRect makeOffset(SkIVector offset) const { + return this->makeOffset(offset.x(), offset.y()); + } + + /** Returns SkIRect, inset by (dx, dy). + + If dx is negative, SkIRect returned is wider. + If dx is positive, SkIRect returned is narrower. + If dy is negative, SkIRect returned is taller. + If dy is positive, SkIRect returned is shorter. + + @param dx offset added to fLeft and subtracted from fRight + @param dy offset added to fTop and subtracted from fBottom + @return SkIRect inset symmetrically left and right, top and bottom + */ + SkIRect makeInset(int32_t dx, int32_t dy) const { + return { + Sk32_sat_add(fLeft, dx), Sk32_sat_add(fTop, dy), + Sk32_sat_sub(fRight, dx), Sk32_sat_sub(fBottom, dy), + }; + } + + /** Returns SkIRect, outset by (dx, dy). + + If dx is negative, SkIRect returned is narrower. + If dx is positive, SkIRect returned is wider. + If dy is negative, SkIRect returned is shorter. + If dy is positive, SkIRect returned is taller. + + @param dx offset subtracted to fLeft and added from fRight + @param dy offset subtracted to fTop and added from fBottom + @return SkIRect outset symmetrically left and right, top and bottom + */ + SkIRect makeOutset(int32_t dx, int32_t dy) const { + return { + Sk32_sat_sub(fLeft, dx), Sk32_sat_sub(fTop, dy), + Sk32_sat_add(fRight, dx), Sk32_sat_add(fBottom, dy), + }; + } + + /** Offsets SkIRect by adding dx to fLeft, fRight; and by adding dy to fTop, fBottom. + + If dx is negative, moves SkIRect returned to the left. + If dx is positive, moves SkIRect returned to the right. + If dy is negative, moves SkIRect returned upward. + If dy is positive, moves SkIRect returned downward. + + @param dx offset added to fLeft and fRight + @param dy offset added to fTop and fBottom + */ + void offset(int32_t dx, int32_t dy) { + fLeft = Sk32_sat_add(fLeft, dx); + fTop = Sk32_sat_add(fTop, dy); + fRight = Sk32_sat_add(fRight, dx); + fBottom = Sk32_sat_add(fBottom, dy); + } + + /** Offsets SkIRect by adding delta.fX to fLeft, fRight; and by adding delta.fY to + fTop, fBottom. + + If delta.fX is negative, moves SkIRect returned to the left. + If delta.fX is positive, moves SkIRect returned to the right. + If delta.fY is negative, moves SkIRect returned upward. + If delta.fY is positive, moves SkIRect returned downward. + + @param delta offset added to SkIRect + */ + void offset(const SkIPoint& delta) { + this->offset(delta.fX, delta.fY); + } + + /** Offsets SkIRect so that fLeft equals newX, and fTop equals newY. width and height + are unchanged. + + @param newX stored in fLeft, preserving width() + @param newY stored in fTop, preserving height() + */ + void offsetTo(int32_t newX, int32_t newY) { + fRight = Sk64_pin_to_s32((int64_t)fRight + newX - fLeft); + fBottom = Sk64_pin_to_s32((int64_t)fBottom + newY - fTop); + fLeft = newX; + fTop = newY; + } + + /** Insets SkIRect by (dx,dy). + + If dx is positive, makes SkIRect narrower. + If dx is negative, makes SkIRect wider. + If dy is positive, makes SkIRect shorter. + If dy is negative, makes SkIRect taller. + + @param dx offset added to fLeft and subtracted from fRight + @param dy offset added to fTop and subtracted from fBottom + */ + void inset(int32_t dx, int32_t dy) { + fLeft = Sk32_sat_add(fLeft, dx); + fTop = Sk32_sat_add(fTop, dy); + fRight = Sk32_sat_sub(fRight, dx); + fBottom = Sk32_sat_sub(fBottom, dy); + } + + /** Outsets SkIRect by (dx, dy). + + If dx is positive, makes SkIRect wider. + If dx is negative, makes SkIRect narrower. + If dy is positive, makes SkIRect taller. + If dy is negative, makes SkIRect shorter. + + @param dx subtracted to fLeft and added from fRight + @param dy subtracted to fTop and added from fBottom + */ + void outset(int32_t dx, int32_t dy) { this->inset(-dx, -dy); } + + /** Adjusts SkIRect by adding dL to fLeft, dT to fTop, dR to fRight, and dB to fBottom. + + If dL is positive, narrows SkIRect on the left. If negative, widens it on the left. + If dT is positive, shrinks SkIRect on the top. If negative, lengthens it on the top. + If dR is positive, narrows SkIRect on the right. If negative, widens it on the right. + If dB is positive, shrinks SkIRect on the bottom. If negative, lengthens it on the bottom. + + The resulting SkIRect is not checked for validity. Thus, if the resulting SkIRect left is + greater than right, the SkIRect will be considered empty. Call sort() after this call + if that is not the desired behavior. + + @param dL offset added to fLeft + @param dT offset added to fTop + @param dR offset added to fRight + @param dB offset added to fBottom + */ + void adjust(int32_t dL, int32_t dT, int32_t dR, int32_t dB) { + fLeft = Sk32_sat_add(fLeft, dL); + fTop = Sk32_sat_add(fTop, dT); + fRight = Sk32_sat_add(fRight, dR); + fBottom = Sk32_sat_add(fBottom, dB); + } + + /** Returns true if: fLeft <= x < fRight && fTop <= y < fBottom. + Returns false if SkIRect is empty. + + Considers input to describe constructed SkIRect: (x, y, x + 1, y + 1) and + returns true if constructed area is completely enclosed by SkIRect area. + + @param x test SkIPoint x-coordinate + @param y test SkIPoint y-coordinate + @return true if (x, y) is inside SkIRect + */ + bool contains(int32_t x, int32_t y) const { + return x >= fLeft && x < fRight && y >= fTop && y < fBottom; + } + + /** Returns true if SkIRect contains r. + Returns false if SkIRect is empty or r is empty. + + SkIRect contains r when SkIRect area completely includes r area. + + @param r SkIRect contained + @return true if all sides of SkIRect are outside r + */ + bool contains(const SkIRect& r) const { + return !r.isEmpty() && !this->isEmpty() && // check for empties + fLeft <= r.fLeft && fTop <= r.fTop && + fRight >= r.fRight && fBottom >= r.fBottom; + } + + /** Returns true if SkIRect contains r. + Returns false if SkIRect is empty or r is empty. + + SkIRect contains r when SkIRect area completely includes r area. + + @param r SkRect contained + @return true if all sides of SkIRect are outside r + */ + inline bool contains(const SkRect& r) const; + + /** Returns true if SkIRect contains construction. + Asserts if SkIRect is empty or construction is empty, and if SK_DEBUG is defined. + + Return is undefined if SkIRect is empty or construction is empty. + + @param r SkIRect contained + @return true if all sides of SkIRect are outside r + */ + bool containsNoEmptyCheck(const SkIRect& r) const { + SkASSERT(fLeft < fRight && fTop < fBottom); + SkASSERT(r.fLeft < r.fRight && r.fTop < r.fBottom); + return fLeft <= r.fLeft && fTop <= r.fTop && fRight >= r.fRight && fBottom >= r.fBottom; + } + + /** Returns true if SkIRect intersects r, and sets SkIRect to intersection. + Returns false if SkIRect does not intersect r, and leaves SkIRect unchanged. + + Returns false if either r or SkIRect is empty, leaving SkIRect unchanged. + + @param r limit of result + @return true if r and SkIRect have area in common + */ + bool intersect(const SkIRect& r) { + return this->intersect(*this, r); + } + + /** Returns true if a intersects b, and sets SkIRect to intersection. + Returns false if a does not intersect b, and leaves SkIRect unchanged. + + Returns false if either a or b is empty, leaving SkIRect unchanged. + + @param a SkIRect to intersect + @param b SkIRect to intersect + @return true if a and b have area in common + */ + bool SK_WARN_UNUSED_RESULT intersect(const SkIRect& a, const SkIRect& b); + + /** Returns true if a intersects b. + Returns false if either a or b is empty, or do not intersect. + + @param a SkIRect to intersect + @param b SkIRect to intersect + @return true if a and b have area in common + */ + static bool Intersects(const SkIRect& a, const SkIRect& b) { + SkIRect dummy; + return dummy.intersect(a, b); + } + + /** Sets SkIRect to the union of itself and r. + + Has no effect if r is empty. Otherwise, if SkIRect is empty, sets SkIRect to r. + + @param r expansion SkIRect + */ + void join(const SkIRect& r); + + /** Swaps fLeft and fRight if fLeft is greater than fRight; and swaps + fTop and fBottom if fTop is greater than fBottom. Result may be empty, + and width() and height() will be zero or positive. + */ + void sort() { + using std::swap; + if (fLeft > fRight) { + swap(fLeft, fRight); + } + if (fTop > fBottom) { + swap(fTop, fBottom); + } + } + + /** Returns SkIRect with fLeft and fRight swapped if fLeft is greater than fRight; and + with fTop and fBottom swapped if fTop is greater than fBottom. Result may be empty; + and width() and height() will be zero or positive. + + @return sorted SkIRect + */ + SkIRect makeSorted() const { + return MakeLTRB(SkMin32(fLeft, fRight), SkMin32(fTop, fBottom), + SkMax32(fLeft, fRight), SkMax32(fTop, fBottom)); + } + + /** Returns a reference to immutable empty SkIRect, set to (0, 0, 0, 0). + + @return global SkIRect set to all zeroes + */ + static const SkIRect& SK_WARN_UNUSED_RESULT EmptyIRect() { + static const SkIRect gEmpty = { 0, 0, 0, 0 }; + return gEmpty; + } +}; + +/** \struct SkRect + SkRect holds four SkScalar coordinates describing the upper and + lower bounds of a rectangle. SkRect may be created from outer bounds or + from position, width, and height. SkRect describes an area; if its right + is less than or equal to its left, or if its bottom is less than or equal to + its top, it is considered empty. +*/ +struct SK_API SkRect { + SkScalar fLeft; //!< smaller x-axis bounds + SkScalar fTop; //!< smaller y-axis bounds + SkScalar fRight; //!< larger x-axis bounds + SkScalar fBottom; //!< larger y-axis bounds + + /** Returns constructed SkRect set to (0, 0, 0, 0). + Many other rectangles are empty; if left is equal to or greater than right, + or if top is equal to or greater than bottom. Setting all members to zero + is a convenience, but does not designate a special empty rectangle. + + @return bounds (0, 0, 0, 0) + */ + static constexpr SkRect SK_WARN_UNUSED_RESULT MakeEmpty() { + return SkRect{0, 0, 0, 0}; + } + + /** Returns constructed SkRect set to SkScalar values (0, 0, w, h). Does not + validate input; w or h may be negative. + + Passing integer values may generate a compiler warning since SkRect cannot + represent 32-bit integers exactly. Use SkIRect for an exact integer rectangle. + + @param w SkScalar width of constructed SkRect + @param h SkScalar height of constructed SkRect + @return bounds (0, 0, w, h) + */ + static constexpr SkRect SK_WARN_UNUSED_RESULT MakeWH(SkScalar w, SkScalar h) { + return SkRect{0, 0, w, h}; + } + + /** Returns constructed SkRect set to integer values (0, 0, w, h). Does not validate + input; w or h may be negative. + + Use to avoid a compiler warning that input may lose precision when stored. + Use SkIRect for an exact integer rectangle. + + @param w integer width of constructed SkRect + @param h integer height of constructed SkRect + @return bounds (0, 0, w, h) + */ + static SkRect SK_WARN_UNUSED_RESULT MakeIWH(int w, int h) { + return {0, 0, SkIntToScalar(w), SkIntToScalar(h)}; + } + + /** Returns constructed SkRect set to (0, 0, size.width(), size.height()). Does not + validate input; size.width() or size.height() may be negative. + + @param size SkScalar values for SkRect width and height + @return bounds (0, 0, size.width(), size.height()) + */ + static constexpr SkRect SK_WARN_UNUSED_RESULT MakeSize(const SkSize& size) { + return SkRect{0, 0, size.fWidth, size.fHeight}; + } + + /** Returns constructed SkRect set to (l, t, r, b). Does not sort input; SkRect may + result in fLeft greater than fRight, or fTop greater than fBottom. + + @param l SkScalar stored in fLeft + @param t SkScalar stored in fTop + @param r SkScalar stored in fRight + @param b SkScalar stored in fBottom + @return bounds (l, t, r, b) + */ + static constexpr SkRect SK_WARN_UNUSED_RESULT MakeLTRB(SkScalar l, SkScalar t, SkScalar r, + SkScalar b) { + return SkRect {l, t, r, b}; + } + + /** Returns constructed SkRect set to (x, y, x + w, y + h). + Does not validate input; w or h may be negative. + + @param x stored in fLeft + @param y stored in fTop + @param w added to x and stored in fRight + @param h added to y and stored in fBottom + @return bounds at (x, y) with width w and height h + */ + static constexpr SkRect SK_WARN_UNUSED_RESULT MakeXYWH(SkScalar x, SkScalar y, SkScalar w, + SkScalar h) { + return SkRect {x, y, x + w, y + h}; + } + + /** Returns constructed SkIRect set to (0, 0, size.width(), size.height()). + Does not validate input; size.width() or size.height() may be negative. + + @param size integer values for SkRect width and height + @return bounds (0, 0, size.width(), size.height()) + */ + static SkRect Make(const SkISize& size) { + return MakeIWH(size.width(), size.height()); + } + + /** Returns constructed SkIRect set to irect, promoting integers to scalar. + Does not validate input; fLeft may be greater than fRight, fTop may be greater + than fBottom. + + @param irect integer unsorted bounds + @return irect members converted to SkScalar + */ + static SkRect SK_WARN_UNUSED_RESULT Make(const SkIRect& irect) { + return { + SkIntToScalar(irect.fLeft), SkIntToScalar(irect.fTop), + SkIntToScalar(irect.fRight), SkIntToScalar(irect.fBottom) + }; + } + + /** Returns true if fLeft is equal to or greater than fRight, or if fTop is equal + to or greater than fBottom. Call sort() to reverse rectangles with negative + width() or height(). + + @return true if width() or height() are zero or negative + */ + bool isEmpty() const { + // We write it as the NOT of a non-empty rect, so we will return true if any values + // are NaN. + return !(fLeft < fRight && fTop < fBottom); + } + + /** Returns true if fLeft is equal to or less than fRight, or if fTop is equal + to or less than fBottom. Call sort() to reverse rectangles with negative + width() or height(). + + @return true if width() or height() are zero or positive + */ + bool isSorted() const { return fLeft <= fRight && fTop <= fBottom; } + + /** Returns true if all values in the rectangle are finite: SK_ScalarMin or larger, + and SK_ScalarMax or smaller. + + @return true if no member is infinite or NaN + */ + bool isFinite() const { + float accum = 0; + accum *= fLeft; + accum *= fTop; + accum *= fRight; + accum *= fBottom; + + // accum is either NaN or it is finite (zero). + SkASSERT(0 == accum || SkScalarIsNaN(accum)); + + // value==value will be true iff value is not NaN + // TODO: is it faster to say !accum or accum==accum? + return !SkScalarIsNaN(accum); + } + + /** Returns left edge of SkRect, if sorted. Call isSorted() to see if SkRect is valid. + Call sort() to reverse fLeft and fRight if needed. + + @return fLeft + */ + SkScalar x() const { return fLeft; } + + /** Returns top edge of SkRect, if sorted. Call isEmpty() to see if SkRect may be invalid, + and sort() to reverse fTop and fBottom if needed. + + @return fTop + */ + SkScalar y() const { return fTop; } + + /** Returns left edge of SkRect, if sorted. Call isSorted() to see if SkRect is valid. + Call sort() to reverse fLeft and fRight if needed. + + @return fLeft + */ + SkScalar left() const { return fLeft; } + + /** Returns top edge of SkRect, if sorted. Call isEmpty() to see if SkRect may be invalid, + and sort() to reverse fTop and fBottom if needed. + + @return fTop + */ + SkScalar top() const { return fTop; } + + /** Returns right edge of SkRect, if sorted. Call isSorted() to see if SkRect is valid. + Call sort() to reverse fLeft and fRight if needed. + + @return fRight + */ + SkScalar right() const { return fRight; } + + /** Returns bottom edge of SkRect, if sorted. Call isEmpty() to see if SkRect may be invalid, + and sort() to reverse fTop and fBottom if needed. + + @return fBottom + */ + SkScalar bottom() const { return fBottom; } + + /** Returns span on the x-axis. This does not check if SkRect is sorted, or if + result fits in 32-bit float; result may be negative or infinity. + + @return fRight minus fLeft + */ + SkScalar width() const { return fRight - fLeft; } + + /** Returns span on the y-axis. This does not check if SkRect is sorted, or if + result fits in 32-bit float; result may be negative or infinity. + + @return fBottom minus fTop + */ + SkScalar height() const { return fBottom - fTop; } + + /** Returns average of left edge and right edge. Result does not change if SkRect + is sorted. Result may overflow to infinity if SkRect is far from the origin. + + @return midpoint on x-axis + */ + SkScalar centerX() const { + // don't use SkScalarHalf(fLeft + fBottom) as that might overflow before the 0.5 + return SkScalarHalf(fLeft) + SkScalarHalf(fRight); + } + + /** Returns average of top edge and bottom edge. Result does not change if SkRect + is sorted. + + @return midpoint on y-axis + */ + SkScalar centerY() const { + // don't use SkScalarHalf(fTop + fBottom) as that might overflow before the 0.5 + return SkScalarHalf(fTop) + SkScalarHalf(fBottom); + } + + /** Returns true if all members in a: fLeft, fTop, fRight, and fBottom; are + equal to the corresponding members in b. + + a and b are not equal if either contain NaN. a and b are equal if members + contain zeroes with different signs. + + @param a SkRect to compare + @param b SkRect to compare + @return true if members are equal + */ + friend bool operator==(const SkRect& a, const SkRect& b) { + return SkScalarsEqual((const SkScalar*)&a, (const SkScalar*)&b, 4); + } + + /** Returns true if any in a: fLeft, fTop, fRight, and fBottom; does not + equal the corresponding members in b. + + a and b are not equal if either contain NaN. a and b are equal if members + contain zeroes with different signs. + + @param a SkRect to compare + @param b SkRect to compare + @return true if members are not equal + */ + friend bool operator!=(const SkRect& a, const SkRect& b) { + return !SkScalarsEqual((const SkScalar*)&a, (const SkScalar*)&b, 4); + } + + /** Returns four points in quad that enclose SkRect ordered as: top-left, top-right, + bottom-right, bottom-left. + + TODO: Consider adding parameter to control whether quad is clockwise or counterclockwise. + + @param quad storage for corners of SkRect + */ + void toQuad(SkPoint quad[4]) const; + + /** Sets SkRect to (0, 0, 0, 0). + + Many other rectangles are empty; if left is equal to or greater than right, + or if top is equal to or greater than bottom. Setting all members to zero + is a convenience, but does not designate a special empty rectangle. + */ + void setEmpty() { *this = MakeEmpty(); } + + /** Sets SkRect to src, promoting src members from integer to scalar. + Very large values in src may lose precision. + + @param src integer SkRect + */ + void set(const SkIRect& src) { + fLeft = SkIntToScalar(src.fLeft); + fTop = SkIntToScalar(src.fTop); + fRight = SkIntToScalar(src.fRight); + fBottom = SkIntToScalar(src.fBottom); + } + + /** Sets SkRect to (left, top, right, bottom). + left and right are not sorted; left is not necessarily less than right. + top and bottom are not sorted; top is not necessarily less than bottom. + + @param left stored in fLeft + @param top stored in fTop + @param right stored in fRight + @param bottom stored in fBottom + */ + void setLTRB(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { + fLeft = left; + fTop = top; + fRight = right; + fBottom = bottom; + } + + /** Sets to bounds of SkPoint array with count entries. If count is zero or smaller, + or if SkPoint array contains an infinity or NaN, sets to (0, 0, 0, 0). + + Result is either empty or sorted: fLeft is less than or equal to fRight, and + fTop is less than or equal to fBottom. + + @param pts SkPoint array + @param count entries in array + */ + void setBounds(const SkPoint pts[], int count) { + (void)this->setBoundsCheck(pts, count); + } + + /** Sets to bounds of SkPoint array with count entries. Returns false if count is + zero or smaller, or if SkPoint array contains an infinity or NaN; in these cases + sets SkRect to (0, 0, 0, 0). + + Result is either empty or sorted: fLeft is less than or equal to fRight, and + fTop is less than or equal to fBottom. + + @param pts SkPoint array + @param count entries in array + @return true if all SkPoint values are finite + */ + bool setBoundsCheck(const SkPoint pts[], int count); + + /** Sets to bounds of SkPoint pts array with count entries. If any SkPoint in pts + contains infinity or NaN, all SkRect dimensions are set to NaN. + + @param pts SkPoint array + @param count entries in array + */ + void setBoundsNoCheck(const SkPoint pts[], int count); + + /** Sets bounds to the smallest SkRect enclosing SkPoint p0 and p1. The result is + sorted and may be empty. Does not check to see if values are finite. + + @param p0 corner to include + @param p1 corner to include + */ + void set(const SkPoint& p0, const SkPoint& p1) { + fLeft = SkMinScalar(p0.fX, p1.fX); + fRight = SkMaxScalar(p0.fX, p1.fX); + fTop = SkMinScalar(p0.fY, p1.fY); + fBottom = SkMaxScalar(p0.fY, p1.fY); + } + + /** Sets SkRect to (x, y, x + width, y + height). + Does not validate input; width or height may be negative. + + @param x stored in fLeft + @param y stored in fTop + @param width added to x and stored in fRight + @param height added to y and stored in fBottom + */ + void setXYWH(SkScalar x, SkScalar y, SkScalar width, SkScalar height) { + fLeft = x; + fTop = y; + fRight = x + width; + fBottom = y + height; + } + + /** Sets SkRect to (0, 0, width, height). Does not validate input; + width or height may be negative. + + @param width stored in fRight + @param height stored in fBottom + */ + void setWH(SkScalar width, SkScalar height) { + fLeft = 0; + fTop = 0; + fRight = width; + fBottom = height; + } + void setIWH(int32_t width, int32_t height) { + this->setWH(SkIntToScalar(width), SkIntToScalar(height)); + } + + /** Returns SkRect offset by (dx, dy). + + If dx is negative, SkRect returned is moved to the left. + If dx is positive, SkRect returned is moved to the right. + If dy is negative, SkRect returned is moved upward. + If dy is positive, SkRect returned is moved downward. + + @param dx added to fLeft and fRight + @param dy added to fTop and fBottom + @return SkRect offset on axes, with original width and height + */ + SkRect makeOffset(SkScalar dx, SkScalar dy) const { + return MakeLTRB(fLeft + dx, fTop + dy, fRight + dx, fBottom + dy); + } + + /** Returns SkRect, inset by (dx, dy). + + If dx is negative, SkRect returned is wider. + If dx is positive, SkRect returned is narrower. + If dy is negative, SkRect returned is taller. + If dy is positive, SkRect returned is shorter. + + @param dx added to fLeft and subtracted from fRight + @param dy added to fTop and subtracted from fBottom + @return SkRect inset symmetrically left and right, top and bottom + */ + SkRect makeInset(SkScalar dx, SkScalar dy) const { + return MakeLTRB(fLeft + dx, fTop + dy, fRight - dx, fBottom - dy); + } + + /** Returns SkRect, outset by (dx, dy). + + If dx is negative, SkRect returned is narrower. + If dx is positive, SkRect returned is wider. + If dy is negative, SkRect returned is shorter. + If dy is positive, SkRect returned is taller. + + @param dx subtracted to fLeft and added from fRight + @param dy subtracted to fTop and added from fBottom + @return SkRect outset symmetrically left and right, top and bottom + */ + SkRect makeOutset(SkScalar dx, SkScalar dy) const { + return MakeLTRB(fLeft - dx, fTop - dy, fRight + dx, fBottom + dy); + } + + /** Offsets SkRect by adding dx to fLeft, fRight; and by adding dy to fTop, fBottom. + + If dx is negative, moves SkRect to the left. + If dx is positive, moves SkRect to the right. + If dy is negative, moves SkRect upward. + If dy is positive, moves SkRect downward. + + @param dx offset added to fLeft and fRight + @param dy offset added to fTop and fBottom + */ + void offset(SkScalar dx, SkScalar dy) { + fLeft += dx; + fTop += dy; + fRight += dx; + fBottom += dy; + } + + /** Offsets SkRect by adding delta.fX to fLeft, fRight; and by adding delta.fY to + fTop, fBottom. + + If delta.fX is negative, moves SkRect to the left. + If delta.fX is positive, moves SkRect to the right. + If delta.fY is negative, moves SkRect upward. + If delta.fY is positive, moves SkRect downward. + + @param delta added to SkRect + */ + void offset(const SkPoint& delta) { + this->offset(delta.fX, delta.fY); + } + + /** Offsets SkRect so that fLeft equals newX, and fTop equals newY. width and height + are unchanged. + + @param newX stored in fLeft, preserving width() + @param newY stored in fTop, preserving height() + */ + void offsetTo(SkScalar newX, SkScalar newY) { + fRight += newX - fLeft; + fBottom += newY - fTop; + fLeft = newX; + fTop = newY; + } + + /** Insets SkRect by (dx, dy). + + If dx is positive, makes SkRect narrower. + If dx is negative, makes SkRect wider. + If dy is positive, makes SkRect shorter. + If dy is negative, makes SkRect taller. + + @param dx added to fLeft and subtracted from fRight + @param dy added to fTop and subtracted from fBottom + */ + void inset(SkScalar dx, SkScalar dy) { + fLeft += dx; + fTop += dy; + fRight -= dx; + fBottom -= dy; + } + + /** Outsets SkRect by (dx, dy). + + If dx is positive, makes SkRect wider. + If dx is negative, makes SkRect narrower. + If dy is positive, makes SkRect taller. + If dy is negative, makes SkRect shorter. + + @param dx subtracted to fLeft and added from fRight + @param dy subtracted to fTop and added from fBottom + */ + void outset(SkScalar dx, SkScalar dy) { this->inset(-dx, -dy); } + + /** Returns true if SkRect intersects r, and sets SkRect to intersection. + Returns false if SkRect does not intersect r, and leaves SkRect unchanged. + + Returns false if either r or SkRect is empty, leaving SkRect unchanged. + + @param r limit of result + @return true if r and SkRect have area in common + */ + bool intersect(const SkRect& r); + + /** Returns true if a intersects b, and sets SkRect to intersection. + Returns false if a does not intersect b, and leaves SkRect unchanged. + + Returns false if either a or b is empty, leaving SkRect unchanged. + + @param a SkRect to intersect + @param b SkRect to intersect + @return true if a and b have area in common + */ + bool SK_WARN_UNUSED_RESULT intersect(const SkRect& a, const SkRect& b); + + +private: + static bool Intersects(SkScalar al, SkScalar at, SkScalar ar, SkScalar ab, + SkScalar bl, SkScalar bt, SkScalar br, SkScalar bb) { + SkScalar L = SkMaxScalar(al, bl); + SkScalar R = SkMinScalar(ar, br); + SkScalar T = SkMaxScalar(at, bt); + SkScalar B = SkMinScalar(ab, bb); + return L < R && T < B; + } + +public: + + /** Returns true if SkRect intersects r. + Returns false if either r or SkRect is empty, or do not intersect. + + @param r SkRect to intersect + @return true if r and SkRect have area in common + */ + bool intersects(const SkRect& r) const { + return Intersects(fLeft, fTop, fRight, fBottom, + r.fLeft, r.fTop, r.fRight, r.fBottom); + } + + /** Returns true if a intersects b. + Returns false if either a or b is empty, or do not intersect. + + @param a SkRect to intersect + @param b SkRect to intersect + @return true if a and b have area in common + */ + static bool Intersects(const SkRect& a, const SkRect& b) { + return Intersects(a.fLeft, a.fTop, a.fRight, a.fBottom, + b.fLeft, b.fTop, b.fRight, b.fBottom); + } + + /** Sets SkRect to the union of itself and r. + + Has no effect if r is empty. Otherwise, if SkRect is empty, sets + SkRect to r. + + @param r expansion SkRect + */ + void join(const SkRect& r); + + /** Sets SkRect to the union of itself and r. + + Asserts if r is empty and SK_DEBUG is defined. + If SkRect is empty, sets SkRect to r. + + May produce incorrect results if r is empty. + + @param r expansion SkRect + */ + void joinNonEmptyArg(const SkRect& r) { + SkASSERT(!r.isEmpty()); + // if we are empty, just assign + if (fLeft >= fRight || fTop >= fBottom) { + *this = r; + } else { + this->joinPossiblyEmptyRect(r); + } + } + + /** Sets SkRect to the union of itself and the construction. + + May produce incorrect results if SkRect or r is empty. + + @param r expansion SkRect + */ + void joinPossiblyEmptyRect(const SkRect& r) { + fLeft = SkMinScalar(fLeft, r.left()); + fTop = SkMinScalar(fTop, r.top()); + fRight = SkMaxScalar(fRight, r.right()); + fBottom = SkMaxScalar(fBottom, r.bottom()); + } + + /** Returns true if: fLeft <= x < fRight && fTop <= y < fBottom. + Returns false if SkRect is empty. + + @param x test SkPoint x-coordinate + @param y test SkPoint y-coordinate + @return true if (x, y) is inside SkRect + */ + bool contains(SkScalar x, SkScalar y) const { + return x >= fLeft && x < fRight && y >= fTop && y < fBottom; + } + + /** Returns true if SkRect contains r. + Returns false if SkRect is empty or r is empty. + + SkRect contains r when SkRect area completely includes r area. + + @param r SkRect contained + @return true if all sides of SkRect are outside r + */ + bool contains(const SkRect& r) const { + // todo: can we eliminate the this->isEmpty check? + return !r.isEmpty() && !this->isEmpty() && + fLeft <= r.fLeft && fTop <= r.fTop && + fRight >= r.fRight && fBottom >= r.fBottom; + } + + /** Returns true if SkRect contains r. + Returns false if SkRect is empty or r is empty. + + SkRect contains r when SkRect area completely includes r area. + + @param r SkIRect contained + @return true if all sides of SkRect are outside r + */ + bool contains(const SkIRect& r) const { + // todo: can we eliminate the this->isEmpty check? + return !r.isEmpty() && !this->isEmpty() && + fLeft <= SkIntToScalar(r.fLeft) && fTop <= SkIntToScalar(r.fTop) && + fRight >= SkIntToScalar(r.fRight) && fBottom >= SkIntToScalar(r.fBottom); + } + + /** Sets SkIRect by adding 0.5 and discarding the fractional portion of SkRect + members, using (SkScalarRoundToInt(fLeft), SkScalarRoundToInt(fTop), + SkScalarRoundToInt(fRight), SkScalarRoundToInt(fBottom)). + + @param dst storage for SkIRect + */ + void round(SkIRect* dst) const { + SkASSERT(dst); + dst->setLTRB(SkScalarRoundToInt(fLeft), SkScalarRoundToInt(fTop), + SkScalarRoundToInt(fRight), SkScalarRoundToInt(fBottom)); + } + + /** Sets SkIRect by discarding the fractional portion of fLeft and fTop; and rounding + up fRight and fBottom, using + (SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop), + SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)). + + @param dst storage for SkIRect + */ + void roundOut(SkIRect* dst) const { + SkASSERT(dst); + dst->setLTRB(SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop), + SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)); + } + + /** Sets SkRect by discarding the fractional portion of fLeft and fTop; and rounding + up fRight and fBottom, using + (SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop), + SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)). + + @param dst storage for SkRect + */ + void roundOut(SkRect* dst) const { + dst->setLTRB(SkScalarFloorToScalar(fLeft), SkScalarFloorToScalar(fTop), + SkScalarCeilToScalar(fRight), SkScalarCeilToScalar(fBottom)); + } + + /** Sets SkRect by rounding up fLeft and fTop; and discarding the fractional portion + of fRight and fBottom, using + (SkScalarCeilToInt(fLeft), SkScalarCeilToInt(fTop), + SkScalarFloorToInt(fRight), SkScalarFloorToInt(fBottom)). + + @param dst storage for SkIRect + */ + void roundIn(SkIRect* dst) const { + SkASSERT(dst); + dst->setLTRB(SkScalarCeilToInt(fLeft), SkScalarCeilToInt(fTop), + SkScalarFloorToInt(fRight), SkScalarFloorToInt(fBottom)); + } + + /** Returns SkIRect by adding 0.5 and discarding the fractional portion of SkRect + members, using (SkScalarRoundToInt(fLeft), SkScalarRoundToInt(fTop), + SkScalarRoundToInt(fRight), SkScalarRoundToInt(fBottom)). + + @return rounded SkIRect + */ + SkIRect round() const { + SkIRect ir; + this->round(&ir); + return ir; + } + + /** Sets SkIRect by discarding the fractional portion of fLeft and fTop; and rounding + up fRight and fBottom, using + (SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop), + SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)). + + @return rounded SkIRect + */ + SkIRect roundOut() const { + SkIRect ir; + this->roundOut(&ir); + return ir; + } + + /** Swaps fLeft and fRight if fLeft is greater than fRight; and swaps + fTop and fBottom if fTop is greater than fBottom. Result may be empty; + and width() and height() will be zero or positive. + */ + void sort() { + using std::swap; + if (fLeft > fRight) { + swap(fLeft, fRight); + } + + if (fTop > fBottom) { + swap(fTop, fBottom); + } + } + + /** Returns SkRect with fLeft and fRight swapped if fLeft is greater than fRight; and + with fTop and fBottom swapped if fTop is greater than fBottom. Result may be empty; + and width() and height() will be zero or positive. + + @return sorted SkRect + */ + SkRect makeSorted() const { + return MakeLTRB(SkMinScalar(fLeft, fRight), SkMinScalar(fTop, fBottom), + SkMaxScalar(fLeft, fRight), SkMaxScalar(fTop, fBottom)); + } + + /** Returns pointer to first scalar in SkRect, to treat it as an array with four + entries. + + @return pointer to fLeft + */ + const SkScalar* asScalars() const { return &fLeft; } + + /** Writes text representation of SkRect to standard output. Set asHex to true to + generate exact binary representations of floating point numbers. + + @param asHex true if SkScalar values are written as hexadecimal + */ + void dump(bool asHex) const; + + /** Writes text representation of SkRect to standard output. The representation may be + directly compiled as C++ code. Floating point values are written + with limited precision; it may not be possible to reconstruct original SkRect + from output. + */ + void dump() const { this->dump(false); } + + /** Writes text representation of SkRect to standard output. The representation may be + directly compiled as C++ code. Floating point values are written + in hexadecimal to preserve their exact bit pattern. The output reconstructs the + original SkRect. + + Use instead of dump() when submitting + */ + void dumpHex() const { this->dump(true); } +}; + +inline bool SkIRect::contains(const SkRect& r) const { + return !r.isEmpty() && !this->isEmpty() && // check for empties + (SkScalar)fLeft <= r.fLeft && (SkScalar)fTop <= r.fTop && + (SkScalar)fRight >= r.fRight && (SkScalar)fBottom >= r.fBottom; +} + +#endif diff --git a/gfx/skia/skia/include/core/SkRefCnt.h b/gfx/skia/skia/include/core/SkRefCnt.h new file mode 100644 index 0000000000..2fab49fe6c --- /dev/null +++ b/gfx/skia/skia/include/core/SkRefCnt.h @@ -0,0 +1,370 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRefCnt_DEFINED +#define SkRefCnt_DEFINED + +#include "include/core/SkTypes.h" + +#include // std::atomic, std::memory_order_* +#include // std::nullptr_t +#include // std::basic_ostream +#include // TODO: unused +#include // std::enable_if, std::is_convertible +#include // std::forward, std::swap + +/** \class SkRefCntBase + + SkRefCntBase is the base class for objects that may be shared by multiple + objects. When an existing owner wants to share a reference, it calls ref(). + When an owner wants to release its reference, it calls unref(). When the + shared object's reference count goes to zero as the result of an unref() + call, its (virtual) destructor is called. It is an error for the + destructor to be called explicitly (or via the object going out of scope on + the stack or calling delete) if getRefCnt() > 1. +*/ +class SK_API SkRefCntBase { +public: + /** Default construct, initializing the reference count to 1. + */ + SkRefCntBase() : fRefCnt(1) {} + + /** Destruct, asserting that the reference count is 1. + */ + virtual ~SkRefCntBase() { + #ifdef SK_DEBUG + SkASSERTF(this->getRefCnt() == 1, "fRefCnt was %d", this->getRefCnt()); + // illegal value, to catch us if we reuse after delete + fRefCnt.store(0, std::memory_order_relaxed); + #endif + } + + /** May return true if the caller is the only owner. + * Ensures that all previous owner's actions are complete. + */ + bool unique() const { + if (1 == fRefCnt.load(std::memory_order_acquire)) { + // The acquire barrier is only really needed if we return true. It + // prevents code conditioned on the result of unique() from running + // until previous owners are all totally done calling unref(). + return true; + } + return false; + } + + /** Increment the reference count. Must be balanced by a call to unref(). + */ + void ref() const { + SkASSERT(this->getRefCnt() > 0); + // No barrier required. + (void)fRefCnt.fetch_add(+1, std::memory_order_relaxed); + } + + /** Decrement the reference count. If the reference count is 1 before the + decrement, then delete the object. Note that if this is the case, then + the object needs to have been allocated via new, and not on the stack. + */ + void unref() const { + SkASSERT(this->getRefCnt() > 0); + // A release here acts in place of all releases we "should" have been doing in ref(). + if (1 == fRefCnt.fetch_add(-1, std::memory_order_acq_rel)) { + // Like unique(), the acquire is only needed on success, to make sure + // code in internal_dispose() doesn't happen before the decrement. + this->internal_dispose(); + } + } + +private: + +#ifdef SK_DEBUG + /** Return the reference count. Use only for debugging. */ + int32_t getRefCnt() const { + return fRefCnt.load(std::memory_order_relaxed); + } +#endif + + /** + * Called when the ref count goes to 0. + */ + virtual void internal_dispose() const { + #ifdef SK_DEBUG + SkASSERT(0 == this->getRefCnt()); + fRefCnt.store(1, std::memory_order_relaxed); + #endif + delete this; + } + + // The following friends are those which override internal_dispose() + // and conditionally call SkRefCnt::internal_dispose(). + friend class SkWeakRefCnt; + + mutable std::atomic fRefCnt; + + SkRefCntBase(SkRefCntBase&&) = delete; + SkRefCntBase(const SkRefCntBase&) = delete; + SkRefCntBase& operator=(SkRefCntBase&&) = delete; + SkRefCntBase& operator=(const SkRefCntBase&) = delete; +}; + +#ifdef SK_REF_CNT_MIXIN_INCLUDE +// It is the responsibility of the following include to define the type SkRefCnt. +// This SkRefCnt should normally derive from SkRefCntBase. +#include SK_REF_CNT_MIXIN_INCLUDE +#else +class SK_API SkRefCnt : public SkRefCntBase { + // "#include SK_REF_CNT_MIXIN_INCLUDE" doesn't work with this build system. + #if defined(SK_BUILD_FOR_GOOGLE3) + public: + void deref() const { this->unref(); } + #endif +}; +#endif + +/////////////////////////////////////////////////////////////////////////////// + +/** Call obj->ref() and return obj. The obj must not be nullptr. + */ +template static inline T* SkRef(T* obj) { + SkASSERT(obj); + obj->ref(); + return obj; +} + +/** Check if the argument is non-null, and if so, call obj->ref() and return obj. + */ +template static inline T* SkSafeRef(T* obj) { + if (obj) { + obj->ref(); + } + return obj; +} + +/** Check if the argument is non-null, and if so, call obj->unref() + */ +template static inline void SkSafeUnref(T* obj) { + if (obj) { + obj->unref(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// This is a variant of SkRefCnt that's Not Virtual, so weighs 4 bytes instead of 8 or 16. +// There's only benefit to using this if the deriving class does not otherwise need a vtable. +template +class SkNVRefCnt { +public: + SkNVRefCnt() : fRefCnt(1) {} + ~SkNVRefCnt() { + #ifdef SK_DEBUG + int rc = fRefCnt.load(std::memory_order_relaxed); + SkASSERTF(rc == 1, "NVRefCnt was %d", rc); + #endif + } + + // Implementation is pretty much the same as SkRefCntBase. All required barriers are the same: + // - unique() needs acquire when it returns true, and no barrier if it returns false; + // - ref() doesn't need any barrier; + // - unref() needs a release barrier, and an acquire if it's going to call delete. + + bool unique() const { return 1 == fRefCnt.load(std::memory_order_acquire); } + void ref() const { (void)fRefCnt.fetch_add(+1, std::memory_order_relaxed); } + void unref() const { + if (1 == fRefCnt.fetch_add(-1, std::memory_order_acq_rel)) { + // restore the 1 for our destructor's assert + SkDEBUGCODE(fRefCnt.store(1, std::memory_order_relaxed)); + delete (const Derived*)this; + } + } + void deref() const { this->unref(); } + +private: + mutable std::atomic fRefCnt; + + SkNVRefCnt(SkNVRefCnt&&) = delete; + SkNVRefCnt(const SkNVRefCnt&) = delete; + SkNVRefCnt& operator=(SkNVRefCnt&&) = delete; + SkNVRefCnt& operator=(const SkNVRefCnt&) = delete; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Shared pointer class to wrap classes that support a ref()/unref() interface. + * + * This can be used for classes inheriting from SkRefCnt, but it also works for other + * classes that match the interface, but have different internal choices: e.g. the hosted class + * may have its ref/unref be thread-safe, but that is not assumed/imposed by sk_sp. + */ +template class sk_sp { +public: + using element_type = T; + + constexpr sk_sp() : fPtr(nullptr) {} + constexpr sk_sp(std::nullptr_t) : fPtr(nullptr) {} + + /** + * Shares the underlying object by calling ref(), so that both the argument and the newly + * created sk_sp both have a reference to it. + */ + sk_sp(const sk_sp& that) : fPtr(SkSafeRef(that.get())) {} + template ::value>::type> + sk_sp(const sk_sp& that) : fPtr(SkSafeRef(that.get())) {} + + /** + * Move the underlying object from the argument to the newly created sk_sp. Afterwards only + * the new sk_sp will have a reference to the object, and the argument will point to null. + * No call to ref() or unref() will be made. + */ + sk_sp(sk_sp&& that) : fPtr(that.release()) {} + template ::value>::type> + sk_sp(sk_sp&& that) : fPtr(that.release()) {} + + /** + * Adopt the bare pointer into the newly created sk_sp. + * No call to ref() or unref() will be made. + */ + explicit sk_sp(T* obj) : fPtr(obj) {} + + /** + * Calls unref() on the underlying object pointer. + */ + ~sk_sp() { + SkSafeUnref(fPtr); + SkDEBUGCODE(fPtr = nullptr); + } + + sk_sp& operator=(std::nullptr_t) { this->reset(); return *this; } + + /** + * Shares the underlying object referenced by the argument by calling ref() on it. If this + * sk_sp previously had a reference to an object (i.e. not null) it will call unref() on that + * object. + */ + sk_sp& operator=(const sk_sp& that) { + if (this != &that) { + this->reset(SkSafeRef(that.get())); + } + return *this; + } + template ::value>::type> + sk_sp& operator=(const sk_sp& that) { + this->reset(SkSafeRef(that.get())); + return *this; + } + + /** + * Move the underlying object from the argument to the sk_sp. If the sk_sp previously held + * a reference to another object, unref() will be called on that object. No call to ref() + * will be made. + */ + sk_sp& operator=(sk_sp&& that) { + this->reset(that.release()); + return *this; + } + template ::value>::type> + sk_sp& operator=(sk_sp&& that) { + this->reset(that.release()); + return *this; + } + + T& operator*() const { + SkASSERT(this->get() != nullptr); + return *this->get(); + } + + explicit operator bool() const { return this->get() != nullptr; } + + T* get() const { return fPtr; } + T* operator->() const { return fPtr; } + + /** + * Adopt the new bare pointer, and call unref() on any previously held object (if not null). + * No call to ref() will be made. + */ + void reset(T* ptr = nullptr) { + // Calling fPtr->unref() may call this->~() or this->reset(T*). + // http://wg21.cmeerw.net/lwg/issue998 + // http://wg21.cmeerw.net/lwg/issue2262 + T* oldPtr = fPtr; + fPtr = ptr; + SkSafeUnref(oldPtr); + } + + /** + * Return the bare pointer, and set the internal object pointer to nullptr. + * The caller must assume ownership of the object, and manage its reference count directly. + * No call to unref() will be made. + */ + T* SK_WARN_UNUSED_RESULT release() { + T* ptr = fPtr; + fPtr = nullptr; + return ptr; + } + + void swap(sk_sp& that) /*noexcept*/ { + using std::swap; + swap(fPtr, that.fPtr); + } + +private: + T* fPtr; +}; + +template inline void swap(sk_sp& a, sk_sp& b) /*noexcept*/ { + a.swap(b); +} + +template inline bool operator==(const sk_sp& a, const sk_sp& b) { + return a.get() == b.get(); +} +template inline bool operator==(const sk_sp& a, std::nullptr_t) /*noexcept*/ { + return !a; +} +template inline bool operator==(std::nullptr_t, const sk_sp& b) /*noexcept*/ { + return !b; +} + +template inline bool operator!=(const sk_sp& a, const sk_sp& b) { + return a.get() != b.get(); +} +template inline bool operator!=(const sk_sp& a, std::nullptr_t) /*noexcept*/ { + return static_cast(a); +} +template inline bool operator!=(std::nullptr_t, const sk_sp& b) /*noexcept*/ { + return static_cast(b); +} + +template +auto operator<<(std::basic_ostream& os, const sk_sp& sp) -> decltype(os << sp.get()) { + return os << sp.get(); +} + +template +sk_sp sk_make_sp(Args&&... args) { + return sk_sp(new T(std::forward(args)...)); +} + +/* + * Returns a sk_sp wrapping the provided ptr AND calls ref on it (if not null). + * + * This is different than the semantics of the constructor for sk_sp, which just wraps the ptr, + * effectively "adopting" it. + */ +template sk_sp sk_ref_sp(T* obj) { + return sk_sp(SkSafeRef(obj)); +} + +template sk_sp sk_ref_sp(const T* obj) { + return sk_sp(const_cast(SkSafeRef(obj))); +} + +#endif diff --git a/gfx/skia/skia/include/core/SkRegion.h b/gfx/skia/skia/include/core/SkRegion.h new file mode 100644 index 0000000000..30d8f8984e --- /dev/null +++ b/gfx/skia/skia/include/core/SkRegion.h @@ -0,0 +1,621 @@ +/* + * Copyright 2005 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRegion_DEFINED +#define SkRegion_DEFINED + +#include "include/core/SkRect.h" + +class SkPath; +class SkRgnBuilder; + +/** \class SkRegion + SkRegion describes the set of pixels used to clip SkCanvas. SkRegion is compact, + efficiently storing a single integer rectangle, or a run length encoded array + of rectangles. SkRegion may reduce the current SkCanvas clip, or may be drawn as + one or more integer rectangles. SkRegion iterator returns the scan lines or + rectangles contained by it, optionally intersecting a bounding rectangle. +*/ +class SK_API SkRegion { + typedef int32_t RunType; +public: + + /** Constructs an empty SkRegion. SkRegion is set to empty bounds + at (0, 0) with zero width and height. + + @return empty SkRegion + */ + SkRegion(); + + /** Constructs a copy of an existing region. + Copy constructor makes two regions identical by value. Internally, region and + the returned result share pointer values. The underlying SkRect array is + copied when modified. + + Creating a SkRegion copy is very efficient and never allocates memory. + SkRegion are always copied by value from the interface; the underlying shared + pointers are not exposed. + + @param region SkRegion to copy by value + @return copy of SkRegion + */ + SkRegion(const SkRegion& region); + + /** Constructs a rectangular SkRegion matching the bounds of rect. + + @param rect bounds of constructed SkRegion + @return rectangular SkRegion + */ + explicit SkRegion(const SkIRect& rect); + + /** Releases ownership of any shared data and deletes data if SkRegion is sole owner. + */ + ~SkRegion(); + + /** Constructs a copy of an existing region. + Makes two regions identical by value. Internally, region and + the returned result share pointer values. The underlying SkRect array is + copied when modified. + + Creating a SkRegion copy is very efficient and never allocates memory. + SkRegion are always copied by value from the interface; the underlying shared + pointers are not exposed. + + @param region SkRegion to copy by value + @return SkRegion to copy by value + */ + SkRegion& operator=(const SkRegion& region); + + /** Compares SkRegion and other; returns true if they enclose exactly + the same area. + + @param other SkRegion to compare + @return true if SkRegion pair are equivalent + */ + bool operator==(const SkRegion& other) const; + + /** Compares SkRegion and other; returns true if they do not enclose the same area. + + @param other SkRegion to compare + @return true if SkRegion pair are not equivalent + */ + bool operator!=(const SkRegion& other) const { + return !(*this == other); + } + + /** Sets SkRegion to src, and returns true if src bounds is not empty. + This makes SkRegion and src identical by value. Internally, + SkRegion and src share pointer values. The underlying SkRect array is + copied when modified. + + Creating a SkRegion copy is very efficient and never allocates memory. + SkRegion are always copied by value from the interface; the underlying shared + pointers are not exposed. + + @param src SkRegion to copy + @return copy of src + */ + bool set(const SkRegion& src) { + *this = src; + return !this->isEmpty(); + } + + /** Exchanges SkIRect array of SkRegion and other. swap() internally exchanges pointers, + so it is lightweight and does not allocate memory. + + swap() usage has largely been replaced by operator=(const SkRegion& region). + SkPath do not copy their content on assignment until they are written to, + making assignment as efficient as swap(). + + @param other operator=(const SkRegion& region) set + */ + void swap(SkRegion& other); + + /** Returns true if SkRegion is empty. + Empty SkRegion has bounds width or height less than or equal to zero. + SkRegion() constructs empty SkRegion; setEmpty() + and setRect() with dimensionless data make SkRegion empty. + + @return true if bounds has no width or height + */ + bool isEmpty() const { return fRunHead == emptyRunHeadPtr(); } + + /** Returns true if SkRegion is one SkIRect with positive dimensions. + + @return true if SkRegion contains one SkIRect + */ + bool isRect() const { return fRunHead == kRectRunHeadPtr; } + + /** Returns true if SkRegion is described by more than one rectangle. + + @return true if SkRegion contains more than one SkIRect + */ + bool isComplex() const { return !this->isEmpty() && !this->isRect(); } + + /** Returns minimum and maximum axes values of SkIRect array. + Returns (0, 0, 0, 0) if SkRegion is empty. + + @return combined bounds of all SkIRect elements + */ + const SkIRect& getBounds() const { return fBounds; } + + /** Returns a value that increases with the number of + elements in SkRegion. Returns zero if SkRegion is empty. + Returns one if SkRegion equals SkIRect; otherwise, returns + value greater than one indicating that SkRegion is complex. + + Call to compare SkRegion for relative complexity. + + @return relative complexity + */ + int computeRegionComplexity() const; + + /** Appends outline of SkRegion to path. + Returns true if SkRegion is not empty; otherwise, returns false, and leaves path + unmodified. + + @param path SkPath to append to + @return true if path changed + */ + bool getBoundaryPath(SkPath* path) const; + + /** Constructs an empty SkRegion. SkRegion is set to empty bounds + at (0, 0) with zero width and height. Always returns false. + + @return false + */ + bool setEmpty(); + + /** Constructs a rectangular SkRegion matching the bounds of rect. + If rect is empty, constructs empty and returns false. + + @param rect bounds of constructed SkRegion + @return true if rect is not empty + */ + bool setRect(const SkIRect& rect); + + /** Constructs SkRegion as the union of SkIRect in rects array. If count is + zero, constructs empty SkRegion. Returns false if constructed SkRegion is empty. + + May be faster than repeated calls to op(). + + @param rects array of SkIRect + @param count array size + @return true if constructed SkRegion is not empty + */ + bool setRects(const SkIRect rects[], int count); + + /** Constructs a copy of an existing region. + Makes two regions identical by value. Internally, region and + the returned result share pointer values. The underlying SkRect array is + copied when modified. + + Creating a SkRegion copy is very efficient and never allocates memory. + SkRegion are always copied by value from the interface; the underlying shared + pointers are not exposed. + + @param region SkRegion to copy by value + @return SkRegion to copy by value + */ + bool setRegion(const SkRegion& region); + + /** Constructs SkRegion to match outline of path within clip. + Returns false if constructed SkRegion is empty. + + Constructed SkRegion draws the same pixels as path through clip when + anti-aliasing is disabled. + + @param path SkPath providing outline + @param clip SkRegion containing path + @return true if constructed SkRegion is not empty + */ + bool setPath(const SkPath& path, const SkRegion& clip); + + /** Returns true if SkRegion intersects rect. + Returns false if either rect or SkRegion is empty, or do not intersect. + + @param rect SkIRect to intersect + @return true if rect and SkRegion have area in common + */ + bool intersects(const SkIRect& rect) const; + + /** Returns true if SkRegion intersects other. + Returns false if either other or SkRegion is empty, or do not intersect. + + @param other SkRegion to intersect + @return true if other and SkRegion have area in common + */ + bool intersects(const SkRegion& other) const; + + /** Returns true if SkIPoint (x, y) is inside SkRegion. + Returns false if SkRegion is empty. + + @param x test SkIPoint x-coordinate + @param y test SkIPoint y-coordinate + @return true if (x, y) is inside SkRegion + */ + bool contains(int32_t x, int32_t y) const; + + /** Returns true if other is completely inside SkRegion. + Returns false if SkRegion or other is empty. + + @param other SkIRect to contain + @return true if other is inside SkRegion + */ + bool contains(const SkIRect& other) const; + + /** Returns true if other is completely inside SkRegion. + Returns false if SkRegion or other is empty. + + @param other SkRegion to contain + @return true if other is inside SkRegion + */ + bool contains(const SkRegion& other) const; + + /** Returns true if SkRegion is a single rectangle and contains r. + May return false even though SkRegion contains r. + + @param r SkIRect to contain + @return true quickly if r points are equal or inside + */ + bool quickContains(const SkIRect& r) const { + SkASSERT(this->isEmpty() == fBounds.isEmpty()); // valid region + + return r.fLeft < r.fRight && r.fTop < r.fBottom && + fRunHead == kRectRunHeadPtr && // this->isRect() + /* fBounds.contains(left, top, right, bottom); */ + fBounds.fLeft <= r.fLeft && fBounds.fTop <= r.fTop && + fBounds.fRight >= r.fRight && fBounds.fBottom >= r.fBottom; + } + + /** Returns true if SkRegion does not intersect rect. + Returns true if rect is empty or SkRegion is empty. + May return false even though SkRegion does not intersect rect. + + @param rect SkIRect to intersect + @return true if rect does not intersect + */ + bool quickReject(const SkIRect& rect) const { + return this->isEmpty() || rect.isEmpty() || + !SkIRect::Intersects(fBounds, rect); + } + + /** Returns true if SkRegion does not intersect rgn. + Returns true if rgn is empty or SkRegion is empty. + May return false even though SkRegion does not intersect rgn. + + @param rgn SkRegion to intersect + @return true if rgn does not intersect + */ + bool quickReject(const SkRegion& rgn) const { + return this->isEmpty() || rgn.isEmpty() || + !SkIRect::Intersects(fBounds, rgn.fBounds); + } + + /** Offsets SkRegion by ivector (dx, dy). Has no effect if SkRegion is empty. + + @param dx x-axis offset + @param dy y-axis offset + */ + void translate(int dx, int dy) { this->translate(dx, dy, this); } + + /** Offsets SkRegion by ivector (dx, dy), writing result to dst. SkRegion may be passed + as dst parameter, translating SkRegion in place. Has no effect if dst is nullptr. + If SkRegion is empty, sets dst to empty. + + @param dx x-axis offset + @param dy y-axis offset + @param dst translated result + */ + void translate(int dx, int dy, SkRegion* dst) const; + + /** \enum SkRegion::Op + The logical operations that can be performed when combining two SkRegion. + */ + enum Op { + kDifference_Op, //!< target minus operand + kIntersect_Op, //!< target intersected with operand + kUnion_Op, //!< target unioned with operand + kXOR_Op, //!< target exclusive or with operand + kReverseDifference_Op, //!< operand minus target + kReplace_Op, //!< replace target with operand + kLastOp = kReplace_Op, //!< last operator + }; + + static const int kOpCnt = kLastOp + 1; + + /** Replaces SkRegion with the result of SkRegion op rect. + Returns true if replaced SkRegion is not empty. + + @param rect SkIRect operand + @param op operator, one of: + kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op, + kReplace_Op + @return false if result is empty + */ + bool op(const SkIRect& rect, Op op) { + if (this->isRect() && kIntersect_Op == op) { + if (!fBounds.intersect(rect)) { + return this->setEmpty(); + } + return true; + } + return this->op(*this, rect, op); + } + + /** Replaces SkRegion with the result of SkRegion op rgn. + Returns true if replaced SkRegion is not empty. + + @param rgn SkRegion operand + @param op operator, one of: + kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op, + kReplace_Op + @return false if result is empty + */ + bool op(const SkRegion& rgn, Op op) { return this->op(*this, rgn, op); } + + /** Replaces SkRegion with the result of rect op rgn. + Returns true if replaced SkRegion is not empty. + + @param rect SkIRect operand + @param rgn SkRegion operand + @param op operator, one of: + kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op, + kReplace_Op + @return false if result is empty + */ + bool op(const SkIRect& rect, const SkRegion& rgn, Op op); + + /** Replaces SkRegion with the result of rgn op rect. + Returns true if replaced SkRegion is not empty. + + @param rgn SkRegion operand + @param rect SkIRect operand + @param op operator, one of: + kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op, + kReplace_Op + @return false if result is empty + */ + bool op(const SkRegion& rgn, const SkIRect& rect, Op op); + + /** Replaces SkRegion with the result of rgna op rgnb. + Returns true if replaced SkRegion is not empty. + + @param rgna SkRegion operand + @param rgnb SkRegion operand + @param op operator, one of: + kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op, + kReplace_Op + @return false if result is empty + */ + bool op(const SkRegion& rgna, const SkRegion& rgnb, Op op); + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + /** Private. Android framework only. + + @return string representation of SkRegion + */ + char* toString(); +#endif + + /** \class SkRegion::Iterator + Returns sequence of rectangles, sorted along y-axis, then x-axis, that make + up SkRegion. + */ + class SK_API Iterator { + public: + + /** Initializes SkRegion::Iterator with an empty SkRegion. done() on SkRegion::Iterator + returns true. + Call reset() to initialized SkRegion::Iterator at a later time. + + @return empty SkRegion iterator + */ + Iterator() : fRgn(nullptr), fDone(true) {} + + /** Sets SkRegion::Iterator to return elements of SkIRect array in region. + + @param region SkRegion to iterate + @return SkRegion iterator + */ + Iterator(const SkRegion& region); + + /** SkPoint SkRegion::Iterator to start of SkRegion. + Returns true if SkRegion was set; otherwise, returns false. + + @return true if SkRegion was set + */ + bool rewind(); + + /** Resets iterator, using the new SkRegion. + + @param region SkRegion to iterate + */ + void reset(const SkRegion& region); + + /** Returns true if SkRegion::Iterator is pointing to final SkIRect in SkRegion. + + @return true if data parsing is complete + */ + bool done() const { return fDone; } + + /** Advances SkRegion::Iterator to next SkIRect in SkRegion if it is not done. + */ + void next(); + + /** Returns SkIRect element in SkRegion. Does not return predictable results if SkRegion + is empty. + + @return part of SkRegion as SkIRect + */ + const SkIRect& rect() const { return fRect; } + + /** Returns SkRegion if set; otherwise, returns nullptr. + + @return iterated SkRegion + */ + const SkRegion* rgn() const { return fRgn; } + + private: + const SkRegion* fRgn; + const SkRegion::RunType* fRuns; + SkIRect fRect = {0, 0, 0, 0}; + bool fDone; + }; + + /** \class SkRegion::Cliperator + Returns the sequence of rectangles, sorted along y-axis, then x-axis, that make + up SkRegion intersected with the specified clip rectangle. + */ + class SK_API Cliperator { + public: + + /** Sets SkRegion::Cliperator to return elements of SkIRect array in SkRegion within clip. + + @param region SkRegion to iterate + @param clip bounds of iteration + @return SkRegion iterator + */ + Cliperator(const SkRegion& region, const SkIRect& clip); + + /** Returns true if SkRegion::Cliperator is pointing to final SkIRect in SkRegion. + + @return true if data parsing is complete + */ + bool done() { return fDone; } + + /** Advances iterator to next SkIRect in SkRegion contained by clip. + */ + void next(); + + /** Returns SkIRect element in SkRegion, intersected with clip passed to + SkRegion::Cliperator constructor. Does not return predictable results if SkRegion + is empty. + + @return part of SkRegion inside clip as SkIRect + */ + const SkIRect& rect() const { return fRect; } + + private: + Iterator fIter; + SkIRect fClip; + SkIRect fRect = {0, 0, 0, 0}; + bool fDone; + }; + + /** \class SkRegion::Spanerator + Returns the line segment ends within SkRegion that intersect a horizontal line. + */ + class Spanerator { + public: + + /** Sets SkRegion::Spanerator to return line segments in SkRegion on scan line. + + @param region SkRegion to iterate + @param y horizontal line to intersect + @param left bounds of iteration + @param right bounds of iteration + @return SkRegion iterator + */ + Spanerator(const SkRegion& region, int y, int left, int right); + + /** Advances iterator to next span intersecting SkRegion within line segment provided + in constructor. Returns true if interval was found. + + @param left pointer to span start; may be nullptr + @param right pointer to span end; may be nullptr + @return true if interval was found + */ + bool next(int* left, int* right); + + private: + const SkRegion::RunType* fRuns; + int fLeft, fRight; + bool fDone; + }; + + /** Writes SkRegion to buffer, and returns number of bytes written. + If buffer is nullptr, returns number number of bytes that would be written. + + @param buffer storage for binary data + @return size of SkRegion + */ + size_t writeToMemory(void* buffer) const; + + /** Constructs SkRegion from buffer of size length. Returns bytes read. + Returned value will be multiple of four or zero if length was too small. + + @param buffer storage for binary data + @param length size of buffer + @return bytes read + */ + size_t readFromMemory(const void* buffer, size_t length); + +private: + static constexpr int kOpCount = kReplace_Op + 1; + + // T + // [B N L R S] + // S + static constexpr int kRectRegionRuns = 7; + + struct RunHead; + + static RunHead* emptyRunHeadPtr() { return (SkRegion::RunHead*) -1; } + static constexpr RunHead* kRectRunHeadPtr = nullptr; + + // allocate space for count runs + void allocateRuns(int count); + void allocateRuns(int count, int ySpanCount, int intervalCount); + void allocateRuns(const RunHead& src); + + SkDEBUGCODE(void dump() const;) + + SkIRect fBounds; + RunHead* fRunHead; + + void freeRuns(); + + /** + * Return the runs from this region, consing up fake runs if the region + * is empty or a rect. In those 2 cases, we use tmpStorage to hold the + * run data. + */ + const RunType* getRuns(RunType tmpStorage[], int* intervals) const; + + // This is called with runs[] that do not yet have their interval-count + // field set on each scanline. That is computed as part of this call + // (inside ComputeRunBounds). + bool setRuns(RunType runs[], int count); + + int count_runtype_values(int* itop, int* ibot) const; + + bool isValid() const; + + static void BuildRectRuns(const SkIRect& bounds, + RunType runs[kRectRegionRuns]); + + // If the runs define a simple rect, return true and set bounds to that + // rect. If not, return false and ignore bounds. + static bool RunsAreARect(const SkRegion::RunType runs[], int count, + SkIRect* bounds); + + /** + * If the last arg is null, just return if the result is non-empty, + * else store the result in the last arg. + */ + static bool Oper(const SkRegion&, const SkRegion&, SkRegion::Op, SkRegion*); + + friend struct RunHead; + friend class Iterator; + friend class Spanerator; + friend class SkRegionPriv; + friend class SkRgnBuilder; + friend class SkFlatRegion; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkScalar.h b/gfx/skia/skia/include/core/SkScalar.h new file mode 100644 index 0000000000..64fea69df4 --- /dev/null +++ b/gfx/skia/skia/include/core/SkScalar.h @@ -0,0 +1,208 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkScalar_DEFINED +#define SkScalar_DEFINED + +#include "include/private/SkFloatingPoint.h" + +#undef SK_SCALAR_IS_FLOAT +#define SK_SCALAR_IS_FLOAT 1 + +typedef float SkScalar; + +#define SK_Scalar1 1.0f +#define SK_ScalarHalf 0.5f +#define SK_ScalarSqrt2 SK_FloatSqrt2 +#define SK_ScalarPI SK_FloatPI +#define SK_ScalarTanPIOver8 0.414213562f +#define SK_ScalarRoot2Over2 0.707106781f +#define SK_ScalarMax 3.402823466e+38f +#define SK_ScalarInfinity SK_FloatInfinity +#define SK_ScalarNegativeInfinity SK_FloatNegativeInfinity +#define SK_ScalarNaN SK_FloatNaN + +#define SkScalarFloorToScalar(x) sk_float_floor(x) +#define SkScalarCeilToScalar(x) sk_float_ceil(x) +#define SkScalarRoundToScalar(x) sk_float_floor((x) + 0.5f) +#define SkScalarTruncToScalar(x) sk_float_trunc(x) + +#define SkScalarFloorToInt(x) sk_float_floor2int(x) +#define SkScalarCeilToInt(x) sk_float_ceil2int(x) +#define SkScalarRoundToInt(x) sk_float_round2int(x) + +#define SkScalarAbs(x) sk_float_abs(x) +#define SkScalarCopySign(x, y) sk_float_copysign(x, y) +#define SkScalarMod(x, y) sk_float_mod(x,y) +#define SkScalarSqrt(x) sk_float_sqrt(x) +#define SkScalarPow(b, e) sk_float_pow(b, e) + +#define SkScalarSin(radians) (float)sk_float_sin(radians) +#define SkScalarCos(radians) (float)sk_float_cos(radians) +#define SkScalarTan(radians) (float)sk_float_tan(radians) +#define SkScalarASin(val) (float)sk_float_asin(val) +#define SkScalarACos(val) (float)sk_float_acos(val) +#define SkScalarATan2(y, x) (float)sk_float_atan2(y,x) +#define SkScalarExp(x) (float)sk_float_exp(x) +#define SkScalarLog(x) (float)sk_float_log(x) +#define SkScalarLog2(x) (float)sk_float_log2(x) + +////////////////////////////////////////////////////////////////////////////////////////////////// + +#define SkIntToScalar(x) static_cast(x) +#define SkIntToFloat(x) static_cast(x) +#define SkScalarTruncToInt(x) sk_float_saturate2int(x) + +#define SkScalarToFloat(x) static_cast(x) +#define SkFloatToScalar(x) static_cast(x) +#define SkScalarToDouble(x) static_cast(x) +#define SkDoubleToScalar(x) sk_double_to_float(x) + +#define SK_ScalarMin (-SK_ScalarMax) + +static inline bool SkScalarIsNaN(SkScalar x) { return x != x; } + +/** Returns true if x is not NaN and not infinite + */ +static inline bool SkScalarIsFinite(SkScalar x) { return sk_float_isfinite(x); } + +static inline bool SkScalarsAreFinite(SkScalar a, SkScalar b) { + return sk_floats_are_finite(a, b); +} + +static inline bool SkScalarsAreFinite(const SkScalar array[], int count) { + return sk_floats_are_finite(array, count); +} + +/** + * Variant of SkScalarRoundToInt, that performs the rounding step (adding 0.5) explicitly using + * double, to avoid possibly losing the low bit(s) of the answer before calling floor(). + * + * This routine will likely be slower than SkScalarRoundToInt(), and should only be used when the + * extra precision is known to be valuable. + * + * In particular, this catches the following case: + * SkScalar x = 0.49999997; + * int ix = SkScalarRoundToInt(x); + * SkASSERT(0 == ix); // <--- fails + * ix = SkDScalarRoundToInt(x); + * SkASSERT(0 == ix); // <--- succeeds + */ +static inline int SkDScalarRoundToInt(SkScalar x) { + double xx = x; + xx += 0.5; + return (int)floor(xx); +} + +/** Returns the fractional part of the scalar. */ +static inline SkScalar SkScalarFraction(SkScalar x) { + return x - SkScalarTruncToScalar(x); +} + +static inline SkScalar SkScalarClampMax(SkScalar x, SkScalar max) { + x = SkTMin(x, max); + x = SkTMax(x, 0); + return x; +} + +static inline SkScalar SkScalarPin(SkScalar x, SkScalar min, SkScalar max) { + return SkTPin(x, min, max); +} + +static inline SkScalar SkScalarSquare(SkScalar x) { return x * x; } + +#define SkScalarInvert(x) sk_ieee_float_divide_TODO_IS_DIVIDE_BY_ZERO_SAFE_HERE(SK_Scalar1, (x)) +#define SkScalarAve(a, b) (((a) + (b)) * SK_ScalarHalf) +#define SkScalarHalf(a) ((a) * SK_ScalarHalf) + +#define SkDegreesToRadians(degrees) ((degrees) * (SK_ScalarPI / 180)) +#define SkRadiansToDegrees(radians) ((radians) * (180 / SK_ScalarPI)) + +static inline SkScalar SkMaxScalar(SkScalar a, SkScalar b) { return a > b ? a : b; } +static inline SkScalar SkMinScalar(SkScalar a, SkScalar b) { return a < b ? a : b; } + +static inline bool SkScalarIsInt(SkScalar x) { + return x == SkScalarFloorToScalar(x); +} + +/** + * Returns -1 || 0 || 1 depending on the sign of value: + * -1 if x < 0 + * 0 if x == 0 + * 1 if x > 0 + */ +static inline int SkScalarSignAsInt(SkScalar x) { + return x < 0 ? -1 : (x > 0); +} + +// Scalar result version of above +static inline SkScalar SkScalarSignAsScalar(SkScalar x) { + return x < 0 ? -SK_Scalar1 : ((x > 0) ? SK_Scalar1 : 0); +} + +#define SK_ScalarNearlyZero (SK_Scalar1 / (1 << 12)) + +static inline bool SkScalarNearlyZero(SkScalar x, + SkScalar tolerance = SK_ScalarNearlyZero) { + SkASSERT(tolerance >= 0); + return SkScalarAbs(x) <= tolerance; +} + +static inline bool SkScalarNearlyEqual(SkScalar x, SkScalar y, + SkScalar tolerance = SK_ScalarNearlyZero) { + SkASSERT(tolerance >= 0); + return SkScalarAbs(x-y) <= tolerance; +} + +static inline float SkScalarSinSnapToZero(SkScalar radians) { + float v = SkScalarSin(radians); + return SkScalarNearlyZero(v) ? 0.0f : v; +} + +static inline float SkScalarCosSnapToZero(SkScalar radians) { + float v = SkScalarCos(radians); + return SkScalarNearlyZero(v) ? 0.0f : v; +} + +/** Linearly interpolate between A and B, based on t. + If t is 0, return A + If t is 1, return B + else interpolate. + t must be [0..SK_Scalar1] +*/ +static inline SkScalar SkScalarInterp(SkScalar A, SkScalar B, SkScalar t) { + SkASSERT(t >= 0 && t <= SK_Scalar1); + return A + (B - A) * t; +} + +/** Interpolate along the function described by (keys[length], values[length]) + for the passed searchKey. SearchKeys outside the range keys[0]-keys[Length] + clamp to the min or max value. This function was inspired by a desire + to change the multiplier for thickness in fakeBold; therefore it assumes + the number of pairs (length) will be small, and a linear search is used. + Repeated keys are allowed for discontinuous functions (so long as keys is + monotonically increasing), and if key is the value of a repeated scalar in + keys, the first one will be used. However, that may change if a binary + search is used. +*/ +SkScalar SkScalarInterpFunc(SkScalar searchKey, const SkScalar keys[], + const SkScalar values[], int length); + +/* + * Helper to compare an array of scalars. + */ +static inline bool SkScalarsEqual(const SkScalar a[], const SkScalar b[], int n) { + SkASSERT(n >= 0); + for (int i = 0; i < n; ++i) { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +#endif diff --git a/gfx/skia/skia/include/core/SkSerialProcs.h b/gfx/skia/skia/include/core/SkSerialProcs.h new file mode 100644 index 0000000000..87e10d847c --- /dev/null +++ b/gfx/skia/skia/include/core/SkSerialProcs.h @@ -0,0 +1,73 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSerialProcs_DEFINED +#define SkSerialProcs_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkPicture.h" +#include "include/core/SkTypeface.h" + +/** + * A serial-proc is asked to serialize the specified object (e.g. picture or image). + * If a data object is returned, it will be used (even if it is zero-length). + * If null is returned, then Skia will take its default action. + * + * The default action for pictures is to use Skia's internal format. + * The default action for images is to encode either in its native format or PNG. + * The default action for typefaces is to use Skia's internal format. + */ + +typedef sk_sp (*SkSerialPictureProc)(SkPicture*, void* ctx); +typedef sk_sp (*SkSerialImageProc)(SkImage*, void* ctx); +typedef sk_sp (*SkSerialTypefaceProc)(SkTypeface*, void* ctx); + +/** + * Called with the encoded form of a picture (previously written with a custom + * SkSerialPictureProc proc). Return a picture object, or nullptr indicating failure. + */ +typedef sk_sp (*SkDeserialPictureProc)(const void* data, size_t length, void* ctx); + +/** + * Called with the encoded from of an image. The proc can return an image object, or if it + * returns nullptr, then Skia will take its default action to try to create an image from the data. + * + * Note that unlike SkDeserialPictureProc and SkDeserialTypefaceProc, return nullptr from this + * does not indicate failure, but is a signal for Skia to take its default action. + */ +typedef sk_sp (*SkDeserialImageProc)(const void* data, size_t length, void* ctx); + +/** + * Called with the encoded form of a typeface (previously written with a custom + * SkSerialTypefaceProc proc). Return a typeface object, or nullptr indicating failure. + */ +typedef sk_sp (*SkDeserialTypefaceProc)(const void* data, size_t length, void* ctx); + +struct SK_API SkSerialProcs { + SkSerialPictureProc fPictureProc = nullptr; + void* fPictureCtx = nullptr; + + SkSerialImageProc fImageProc = nullptr; + void* fImageCtx = nullptr; + + SkSerialTypefaceProc fTypefaceProc = nullptr; + void* fTypefaceCtx = nullptr; +}; + +struct SK_API SkDeserialProcs { + SkDeserialPictureProc fPictureProc = nullptr; + void* fPictureCtx = nullptr; + + SkDeserialImageProc fImageProc = nullptr; + void* fImageCtx = nullptr; + + SkDeserialTypefaceProc fTypefaceProc = nullptr; + void* fTypefaceCtx = nullptr; +}; + +#endif + diff --git a/gfx/skia/skia/include/core/SkShader.h b/gfx/skia/skia/include/core/SkShader.h new file mode 100644 index 0000000000..ef6ba1bdb7 --- /dev/null +++ b/gfx/skia/skia/include/core/SkShader.h @@ -0,0 +1,154 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkShader_DEFINED +#define SkShader_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkTileMode.h" + +class SkArenaAlloc; +class SkBitmap; +class SkColorFilter; +class SkColorSpace; +class SkImage; +class SkPath; +class SkPicture; +class SkRasterPipeline; +class GrContext; +class GrFragmentProcessor; + +/** \class SkShader + * + * Shaders specify the source color(s) for what is being drawn. If a paint + * has no shader, then the paint's color is used. If the paint has a + * shader, then the shader's color(s) are use instead, but they are + * modulated by the paint's alpha. This makes it easy to create a shader + * once (e.g. bitmap tiling or gradient) and then change its transparency + * w/o having to modify the original shader... only the paint's alpha needs + * to be modified. + */ +class SK_API SkShader : public SkFlattenable { +public: + /** + * Returns true if the shader is guaranteed to produce only opaque + * colors, subject to the SkPaint using the shader to apply an opaque + * alpha value. Subclasses should override this to allow some + * optimizations. + */ + virtual bool isOpaque() const { return false; } + + /** + * Iff this shader is backed by a single SkImage, return its ptr (the caller must ref this + * if they want to keep it longer than the lifetime of the shader). If not, return nullptr. + */ + SkImage* isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const; + + bool isAImage() const { + return this->isAImage(nullptr, (SkTileMode*)nullptr) != nullptr; + } + + /** + * If the shader subclass can be represented as a gradient, asAGradient + * returns the matching GradientType enum (or kNone_GradientType if it + * cannot). Also, if info is not null, asAGradient populates info with + * the relevant (see below) parameters for the gradient. fColorCount + * is both an input and output parameter. On input, it indicates how + * many entries in fColors and fColorOffsets can be used, if they are + * non-NULL. After asAGradient has run, fColorCount indicates how + * many color-offset pairs there are in the gradient. If there is + * insufficient space to store all of the color-offset pairs, fColors + * and fColorOffsets will not be altered. fColorOffsets specifies + * where on the range of 0 to 1 to transition to the given color. + * The meaning of fPoint and fRadius is dependant on the type of gradient. + * + * None: + * info is ignored. + * Color: + * fColorOffsets[0] is meaningless. + * Linear: + * fPoint[0] and fPoint[1] are the end-points of the gradient + * Radial: + * fPoint[0] and fRadius[0] are the center and radius + * Conical: + * fPoint[0] and fRadius[0] are the center and radius of the 1st circle + * fPoint[1] and fRadius[1] are the center and radius of the 2nd circle + * Sweep: + * fPoint[0] is the center of the sweep. + */ + + enum GradientType { + kNone_GradientType, + kColor_GradientType, + kLinear_GradientType, + kRadial_GradientType, + kSweep_GradientType, + kConical_GradientType, + kLast_GradientType = kConical_GradientType, + }; + + struct GradientInfo { + int fColorCount; //!< In-out parameter, specifies passed size + // of fColors/fColorOffsets on input, and + // actual number of colors/offsets on + // output. + SkColor* fColors; //!< The colors in the gradient. + SkScalar* fColorOffsets; //!< The unit offset for color transitions. + SkPoint fPoint[2]; //!< Type specific, see above. + SkScalar fRadius[2]; //!< Type specific, see above. + SkTileMode fTileMode; + uint32_t fGradientFlags; //!< see SkGradientShader::Flags + }; + + // DEPRECATED. skbug.com/8941 + virtual GradientType asAGradient(GradientInfo* info) const; + + ////////////////////////////////////////////////////////////////////////// + // Methods to create combinations or variants of shaders + + /** + * Return a shader that will apply the specified localMatrix to this shader. + * The specified matrix will be applied before any matrix associated with this shader. + */ + sk_sp makeWithLocalMatrix(const SkMatrix&) const; + + /** + * Create a new shader that produces the same colors as invoking this shader and then applying + * the colorfilter. + */ + sk_sp makeWithColorFilter(sk_sp) const; + +private: + SkShader() = default; + friend class SkShaderBase; + + typedef SkFlattenable INHERITED; +}; + +class SK_API SkShaders { +public: + static sk_sp Empty(); + static sk_sp Color(SkColor); + static sk_sp Color(const SkColor4f&, sk_sp); + static sk_sp Blend(SkBlendMode mode, sk_sp dst, sk_sp src, + const SkMatrix* localMatrix = nullptr); + static sk_sp Lerp(float t, sk_sp dst, sk_sp src, + const SkMatrix* localMatrix = nullptr); + + static sk_sp Lerp(sk_sp red, sk_sp dst, sk_sp src, + const SkMatrix* localMatrix = nullptr); + +private: + SkShaders() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkSize.h b/gfx/skia/skia/include/core/SkSize.h new file mode 100644 index 0000000000..87be93d007 --- /dev/null +++ b/gfx/skia/skia/include/core/SkSize.h @@ -0,0 +1,92 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSize_DEFINED +#define SkSize_DEFINED + +#include "include/core/SkScalar.h" + +struct SkISize { + int32_t fWidth; + int32_t fHeight; + + static constexpr SkISize Make(int32_t w, int32_t h) { return {w, h}; } + + static constexpr SkISize MakeEmpty() { return {0, 0}; } + + void set(int32_t w, int32_t h) { *this = SkISize{w, h}; } + + /** Returns true iff fWidth == 0 && fHeight == 0 + */ + bool isZero() const { return 0 == fWidth && 0 == fHeight; } + + /** Returns true if either width or height are <= 0 */ + bool isEmpty() const { return fWidth <= 0 || fHeight <= 0; } + + /** Set the width and height to 0 */ + void setEmpty() { fWidth = fHeight = 0; } + + int32_t width() const { return fWidth; } + int32_t height() const { return fHeight; } + + bool equals(int32_t w, int32_t h) const { return fWidth == w && fHeight == h; } +}; + +static inline bool operator==(const SkISize& a, const SkISize& b) { + return a.fWidth == b.fWidth && a.fHeight == b.fHeight; +} + +static inline bool operator!=(const SkISize& a, const SkISize& b) { return !(a == b); } + +/////////////////////////////////////////////////////////////////////////////// + +struct SkSize { + SkScalar fWidth; + SkScalar fHeight; + + static SkSize Make(SkScalar w, SkScalar h) { return {w, h}; } + + static SkSize Make(const SkISize& src) { + return {SkIntToScalar(src.width()), SkIntToScalar(src.height())}; + } + + SkSize& operator=(const SkISize& src) { + return *this = SkSize{SkIntToScalar(src.fWidth), SkIntToScalar(src.fHeight)}; + } + + static SkSize MakeEmpty() { return {0, 0}; } + + void set(SkScalar w, SkScalar h) { *this = SkSize{w, h}; } + + /** Returns true iff fWidth == 0 && fHeight == 0 + */ + bool isZero() const { return 0 == fWidth && 0 == fHeight; } + + /** Returns true if either width or height are <= 0 */ + bool isEmpty() const { return fWidth <= 0 || fHeight <= 0; } + + /** Set the width and height to 0 */ + void setEmpty() { *this = SkSize{0, 0}; } + + SkScalar width() const { return fWidth; } + SkScalar height() const { return fHeight; } + + bool equals(SkScalar w, SkScalar h) const { return fWidth == w && fHeight == h; } + + SkISize toRound() const { return {SkScalarRoundToInt(fWidth), SkScalarRoundToInt(fHeight)}; } + + SkISize toCeil() const { return {SkScalarCeilToInt(fWidth), SkScalarCeilToInt(fHeight)}; } + + SkISize toFloor() const { return {SkScalarFloorToInt(fWidth), SkScalarFloorToInt(fHeight)}; } +}; + +static inline bool operator==(const SkSize& a, const SkSize& b) { + return a.fWidth == b.fWidth && a.fHeight == b.fHeight; +} + +static inline bool operator!=(const SkSize& a, const SkSize& b) { return !(a == b); } +#endif diff --git a/gfx/skia/skia/include/core/SkStream.h b/gfx/skia/skia/include/core/SkStream.h new file mode 100644 index 0000000000..659880d2d6 --- /dev/null +++ b/gfx/skia/skia/include/core/SkStream.h @@ -0,0 +1,515 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStream_DEFINED +#define SkStream_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/private/SkTo.h" + +#include + +class SkStream; +class SkStreamRewindable; +class SkStreamSeekable; +class SkStreamAsset; +class SkStreamMemory; + +/** + * SkStream -- abstraction for a source of bytes. Subclasses can be backed by + * memory, or a file, or something else. + * + * NOTE: + * + * Classic "streams" APIs are sort of async, in that on a request for N + * bytes, they may return fewer than N bytes on a given call, in which case + * the caller can "try again" to get more bytes, eventually (modulo an error) + * receiving their total N bytes. + * + * Skia streams behave differently. They are effectively synchronous, and will + * always return all N bytes of the request if possible. If they return fewer + * (the read() call returns the number of bytes read) then that means there is + * no more data (at EOF or hit an error). The caller should *not* call again + * in hopes of fulfilling more of the request. + */ +class SK_API SkStream { +public: + virtual ~SkStream() {} + SkStream() {} + + /** + * Attempts to open the specified file as a stream, returns nullptr on failure. + */ + static std::unique_ptr MakeFromFile(const char path[]); + + /** Reads or skips size number of bytes. + * If buffer == NULL, skip size bytes, return how many were skipped. + * If buffer != NULL, copy size bytes into buffer, return how many were copied. + * @param buffer when NULL skip size bytes, otherwise copy size bytes into buffer + * @param size the number of bytes to skip or copy + * @return the number of bytes actually read. + */ + virtual size_t read(void* buffer, size_t size) = 0; + + /** Skip size number of bytes. + * @return the actual number bytes that could be skipped. + */ + size_t skip(size_t size) { + return this->read(nullptr, size); + } + + /** + * Attempt to peek at size bytes. + * If this stream supports peeking, copy min(size, peekable bytes) into + * buffer, and return the number of bytes copied. + * If the stream does not support peeking, or cannot peek any bytes, + * return 0 and leave buffer unchanged. + * The stream is guaranteed to be in the same visible state after this + * call, regardless of success or failure. + * @param buffer Must not be NULL, and must be at least size bytes. Destination + * to copy bytes. + * @param size Number of bytes to copy. + * @return The number of bytes peeked/copied. + */ + virtual size_t peek(void* /*buffer*/, size_t /*size*/) const { return 0; } + + /** Returns true when all the bytes in the stream have been read. + * This may return true early (when there are no more bytes to be read) + * or late (after the first unsuccessful read). + */ + virtual bool isAtEnd() const = 0; + + bool SK_WARN_UNUSED_RESULT readS8(int8_t*); + bool SK_WARN_UNUSED_RESULT readS16(int16_t*); + bool SK_WARN_UNUSED_RESULT readS32(int32_t*); + + bool SK_WARN_UNUSED_RESULT readU8(uint8_t* i) { return this->readS8((int8_t*)i); } + bool SK_WARN_UNUSED_RESULT readU16(uint16_t* i) { return this->readS16((int16_t*)i); } + bool SK_WARN_UNUSED_RESULT readU32(uint32_t* i) { return this->readS32((int32_t*)i); } + + bool SK_WARN_UNUSED_RESULT readBool(bool* b) { + uint8_t i; + if (!this->readU8(&i)) { return false; } + *b = (i != 0); + return true; + } + bool SK_WARN_UNUSED_RESULT readScalar(SkScalar*); + bool SK_WARN_UNUSED_RESULT readPackedUInt(size_t*); + +//SkStreamRewindable + /** Rewinds to the beginning of the stream. Returns true if the stream is known + * to be at the beginning after this call returns. + */ + virtual bool rewind() { return false; } + + /** Duplicates this stream. If this cannot be done, returns NULL. + * The returned stream will be positioned at the beginning of its data. + */ + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } + /** Duplicates this stream. If this cannot be done, returns NULL. + * The returned stream will be positioned the same as this stream. + */ + std::unique_ptr fork() const { + return std::unique_ptr(this->onFork()); + } + +//SkStreamSeekable + /** Returns true if this stream can report it's current position. */ + virtual bool hasPosition() const { return false; } + /** Returns the current position in the stream. If this cannot be done, returns 0. */ + virtual size_t getPosition() const { return 0; } + + /** Seeks to an absolute position in the stream. If this cannot be done, returns false. + * If an attempt is made to seek past the end of the stream, the position will be set + * to the end of the stream. + */ + virtual bool seek(size_t /*position*/) { return false; } + + /** Seeks to an relative offset in the stream. If this cannot be done, returns false. + * If an attempt is made to move to a position outside the stream, the position will be set + * to the closest point within the stream (beginning or end). + */ + virtual bool move(long /*offset*/) { return false; } + +//SkStreamAsset + /** Returns true if this stream can report it's total length. */ + virtual bool hasLength() const { return false; } + /** Returns the total length of the stream. If this cannot be done, returns 0. */ + virtual size_t getLength() const { return 0; } + +//SkStreamMemory + /** Returns the starting address for the data. If this cannot be done, returns NULL. */ + //TODO: replace with virtual const SkData* getData() + virtual const void* getMemoryBase() { return nullptr; } + +private: + virtual SkStream* onDuplicate() const { return nullptr; } + virtual SkStream* onFork() const { return nullptr; } + + SkStream(SkStream&&) = delete; + SkStream(const SkStream&) = delete; + SkStream& operator=(SkStream&&) = delete; + SkStream& operator=(const SkStream&) = delete; +}; + +/** SkStreamRewindable is a SkStream for which rewind and duplicate are required. */ +class SK_API SkStreamRewindable : public SkStream { +public: + bool rewind() override = 0; + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } +private: + SkStreamRewindable* onDuplicate() const override = 0; +}; + +/** SkStreamSeekable is a SkStreamRewindable for which position, seek, move, and fork are required. */ +class SK_API SkStreamSeekable : public SkStreamRewindable { +public: + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } + + bool hasPosition() const override { return true; } + size_t getPosition() const override = 0; + bool seek(size_t position) override = 0; + bool move(long offset) override = 0; + + std::unique_ptr fork() const { + return std::unique_ptr(this->onFork()); + } +private: + SkStreamSeekable* onDuplicate() const override = 0; + SkStreamSeekable* onFork() const override = 0; +}; + +/** SkStreamAsset is a SkStreamSeekable for which getLength is required. */ +class SK_API SkStreamAsset : public SkStreamSeekable { +public: + bool hasLength() const override { return true; } + size_t getLength() const override = 0; + + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } + std::unique_ptr fork() const { + return std::unique_ptr(this->onFork()); + } +private: + SkStreamAsset* onDuplicate() const override = 0; + SkStreamAsset* onFork() const override = 0; +}; + +/** SkStreamMemory is a SkStreamAsset for which getMemoryBase is required. */ +class SK_API SkStreamMemory : public SkStreamAsset { +public: + const void* getMemoryBase() override = 0; + + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } + std::unique_ptr fork() const { + return std::unique_ptr(this->onFork()); + } +private: + SkStreamMemory* onDuplicate() const override = 0; + SkStreamMemory* onFork() const override = 0; +}; + +class SK_API SkWStream { +public: + virtual ~SkWStream(); + SkWStream() {} + + /** Called to write bytes to a SkWStream. Returns true on success + @param buffer the address of at least size bytes to be written to the stream + @param size The number of bytes in buffer to write to the stream + @return true on success + */ + virtual bool write(const void* buffer, size_t size) = 0; + virtual void flush(); + + virtual size_t bytesWritten() const = 0; + + // helpers + + bool write8(U8CPU value) { + uint8_t v = SkToU8(value); + return this->write(&v, 1); + } + bool write16(U16CPU value) { + uint16_t v = SkToU16(value); + return this->write(&v, 2); + } + bool write32(uint32_t v) { + return this->write(&v, 4); + } + + bool writeText(const char text[]) { + SkASSERT(text); + return this->write(text, strlen(text)); + } + + bool newline() { return this->write("\n", strlen("\n")); } + + bool writeDecAsText(int32_t); + bool writeBigDecAsText(int64_t, int minDigits = 0); + bool writeHexAsText(uint32_t, int minDigits = 0); + bool writeScalarAsText(SkScalar); + + bool writeBool(bool v) { return this->write8(v); } + bool writeScalar(SkScalar); + bool writePackedUInt(size_t); + + bool writeStream(SkStream* input, size_t length); + + /** + * This returns the number of bytes in the stream required to store + * 'value'. + */ + static int SizeOfPackedUInt(size_t value); + +private: + SkWStream(const SkWStream&) = delete; + SkWStream& operator=(const SkWStream&) = delete; +}; + +class SK_API SkNullWStream : public SkWStream { +public: + SkNullWStream() : fBytesWritten(0) {} + + bool write(const void* , size_t n) override { fBytesWritten += n; return true; } + void flush() override {} + size_t bytesWritten() const override { return fBytesWritten; } + +private: + size_t fBytesWritten; +}; + +//////////////////////////////////////////////////////////////////////////////////////// + +#include + +/** A stream that wraps a C FILE* file stream. */ +class SK_API SkFILEStream : public SkStreamAsset { +public: + /** Initialize the stream by calling sk_fopen on the specified path. + * This internal stream will be closed in the destructor. + */ + explicit SkFILEStream(const char path[] = nullptr); + + /** Initialize the stream with an existing C FILE stream. + * The current position of the C FILE stream will be considered the + * beginning of the SkFILEStream. + * The C FILE stream will be closed in the destructor. + */ + explicit SkFILEStream(FILE* file); + + ~SkFILEStream() override; + + static std::unique_ptr Make(const char path[]) { + std::unique_ptr stream(new SkFILEStream(path)); + return stream->isValid() ? std::move(stream) : nullptr; + } + + /** Returns true if the current path could be opened. */ + bool isValid() const { return fFILE != nullptr; } + + /** Close this SkFILEStream. */ + void close(); + + size_t read(void* buffer, size_t size) override; + bool isAtEnd() const override; + + bool rewind() override; + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } + + size_t getPosition() const override; + bool seek(size_t position) override; + bool move(long offset) override; + + std::unique_ptr fork() const { + return std::unique_ptr(this->onFork()); + } + + size_t getLength() const override; + +private: + explicit SkFILEStream(std::shared_ptr, size_t size, size_t offset); + explicit SkFILEStream(std::shared_ptr, size_t size, size_t offset, size_t originalOffset); + + SkStreamAsset* onDuplicate() const override; + SkStreamAsset* onFork() const override; + + std::shared_ptr fFILE; + // My own council will I keep on sizes and offsets. + size_t fSize; + size_t fOffset; + size_t fOriginalOffset; + + typedef SkStreamAsset INHERITED; +}; + +class SK_API SkMemoryStream : public SkStreamMemory { +public: + SkMemoryStream(); + + /** We allocate (and free) the memory. Write to it via getMemoryBase() */ + SkMemoryStream(size_t length); + + /** If copyData is true, the stream makes a private copy of the data. */ + SkMemoryStream(const void* data, size_t length, bool copyData = false); + + /** Creates the stream to read from the specified data */ + SkMemoryStream(sk_sp data); + + /** Returns a stream with a copy of the input data. */ + static std::unique_ptr MakeCopy(const void* data, size_t length); + + /** Returns a stream with a bare pointer reference to the input data. */ + static std::unique_ptr MakeDirect(const void* data, size_t length); + + /** Returns a stream with a shared reference to the input data. */ + static std::unique_ptr Make(sk_sp data); + + /** Resets the stream to the specified data and length, + just like the constructor. + if copyData is true, the stream makes a private copy of the data + */ + virtual void setMemory(const void* data, size_t length, + bool copyData = false); + /** Replace any memory buffer with the specified buffer. The caller + must have allocated data with sk_malloc or sk_realloc, since it + will be freed with sk_free. + */ + void setMemoryOwned(const void* data, size_t length); + + sk_sp asData() const { return fData; } + void setData(sk_sp data); + + void skipToAlign4(); + const void* getAtPos(); + + size_t read(void* buffer, size_t size) override; + bool isAtEnd() const override; + + size_t peek(void* buffer, size_t size) const override; + + bool rewind() override; + + std::unique_ptr duplicate() const { + return std::unique_ptr(this->onDuplicate()); + } + + size_t getPosition() const override; + bool seek(size_t position) override; + bool move(long offset) override; + + std::unique_ptr fork() const { + return std::unique_ptr(this->onFork()); + } + + size_t getLength() const override; + + const void* getMemoryBase() override; + +private: + SkMemoryStream* onDuplicate() const override; + SkMemoryStream* onFork() const override; + + sk_sp fData; + size_t fOffset; + + typedef SkStreamMemory INHERITED; +}; + +///////////////////////////////////////////////////////////////////////////////////////////// + +class SK_API SkFILEWStream : public SkWStream { +public: + SkFILEWStream(const char path[]); + ~SkFILEWStream() override; + + /** Returns true if the current path could be opened. + */ + bool isValid() const { return fFILE != nullptr; } + + bool write(const void* buffer, size_t size) override; + void flush() override; + void fsync(); + size_t bytesWritten() const override; + +private: + FILE* fFILE; + + typedef SkWStream INHERITED; +}; + +class SK_API SkDynamicMemoryWStream : public SkWStream { +public: + SkDynamicMemoryWStream() = default; + SkDynamicMemoryWStream(SkDynamicMemoryWStream&&); + SkDynamicMemoryWStream& operator=(SkDynamicMemoryWStream&&); + ~SkDynamicMemoryWStream() override; + + bool write(const void* buffer, size_t size) override; + size_t bytesWritten() const override; + + bool read(void* buffer, size_t offset, size_t size); + + /** More efficient version of read(dst, 0, bytesWritten()). */ + void copyTo(void* dst) const; + bool writeToStream(SkWStream* dst) const; + + /** Equivalent to copyTo() followed by reset(), but may save memory use. */ + void copyToAndReset(void* dst); + + /** Equivalent to writeToStream() followed by reset(), but may save memory use. */ + bool writeToAndReset(SkWStream* dst); + + /** Equivalent to writeToStream() followed by reset(), but may save memory use. + When the dst is also a SkDynamicMemoryWStream, the implementation is constant time. */ + bool writeToAndReset(SkDynamicMemoryWStream* dst); + + /** Prepend this stream to dst, resetting this. */ + void prependToAndReset(SkDynamicMemoryWStream* dst); + + /** Return the contents as SkData, and then reset the stream. */ + sk_sp detachAsData(); + + /** Reset, returning a reader stream with the current content. */ + std::unique_ptr detachAsStream(); + + /** Reset the stream to its original, empty, state. */ + void reset(); + void padToAlign4(); +private: + struct Block; + Block* fHead = nullptr; + Block* fTail = nullptr; + size_t fBytesWrittenBeforeTail = 0; + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + // For access to the Block type. + friend class SkBlockMemoryStream; + friend class SkBlockMemoryRefCnt; + + typedef SkWStream INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkString.h b/gfx/skia/skia/include/core/SkString.h new file mode 100644 index 0000000000..fef0aa14a0 --- /dev/null +++ b/gfx/skia/skia/include/core/SkString.h @@ -0,0 +1,299 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkString_DEFINED +#define SkString_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTo.h" + +#include +#include +#include + +/* Some helper functions for C strings */ +static inline bool SkStrStartsWith(const char string[], const char prefixStr[]) { + SkASSERT(string); + SkASSERT(prefixStr); + return !strncmp(string, prefixStr, strlen(prefixStr)); +} +static inline bool SkStrStartsWith(const char string[], const char prefixChar) { + SkASSERT(string); + return (prefixChar == *string); +} + +bool SkStrEndsWith(const char string[], const char suffixStr[]); +bool SkStrEndsWith(const char string[], const char suffixChar); + +int SkStrStartsWithOneOf(const char string[], const char prefixes[]); + +static inline int SkStrFind(const char string[], const char substring[]) { + const char *first = strstr(string, substring); + if (nullptr == first) return -1; + return SkToInt(first - &string[0]); +} + +static inline int SkStrFindLastOf(const char string[], const char subchar) { + const char* last = strrchr(string, subchar); + if (nullptr == last) return -1; + return SkToInt(last - &string[0]); +} + +static inline bool SkStrContains(const char string[], const char substring[]) { + SkASSERT(string); + SkASSERT(substring); + return (-1 != SkStrFind(string, substring)); +} +static inline bool SkStrContains(const char string[], const char subchar) { + SkASSERT(string); + char tmp[2]; + tmp[0] = subchar; + tmp[1] = '\0'; + return (-1 != SkStrFind(string, tmp)); +} + +static inline char *SkStrDup(const char string[]) { + char *ret = (char *) sk_malloc_throw(strlen(string)+1); + memcpy(ret,string,strlen(string)+1); + return ret; +} + +/* + * The SkStrAppend... methods will write into the provided buffer, assuming it is large enough. + * Each method has an associated const (e.g. SkStrAppendU32_MaxSize) which will be the largest + * value needed for that method's buffer. + * + * char storage[SkStrAppendU32_MaxSize]; + * SkStrAppendU32(storage, value); + * + * Note : none of the SkStrAppend... methods write a terminating 0 to their buffers. Instead, + * the methods return the ptr to the end of the written part of the buffer. This can be used + * to compute the length, and/or know where to write a 0 if that is desired. + * + * char storage[SkStrAppendU32_MaxSize + 1]; + * char* stop = SkStrAppendU32(storage, value); + * size_t len = stop - storage; + * *stop = 0; // valid, since storage was 1 byte larger than the max. + */ + +#define SkStrAppendU32_MaxSize 10 +char* SkStrAppendU32(char buffer[], uint32_t); +#define SkStrAppendU64_MaxSize 20 +char* SkStrAppendU64(char buffer[], uint64_t, int minDigits); + +#define SkStrAppendS32_MaxSize (SkStrAppendU32_MaxSize + 1) +char* SkStrAppendS32(char buffer[], int32_t); +#define SkStrAppendS64_MaxSize (SkStrAppendU64_MaxSize + 1) +char* SkStrAppendS64(char buffer[], int64_t, int minDigits); + +/** + * Floats have at most 8 significant digits, so we limit our %g to that. + * However, the total string could be 15 characters: -1.2345678e-005 + * + * In theory we should only expect up to 2 digits for the exponent, but on + * some platforms we have seen 3 (as in the example above). + */ +#define SkStrAppendScalar_MaxSize 15 + +/** + * Write the scaler in decimal format into buffer, and return a pointer to + * the next char after the last one written. Note: a terminating 0 is not + * written into buffer, which must be at least SkStrAppendScalar_MaxSize. + * Thus if the caller wants to add a 0 at the end, buffer must be at least + * SkStrAppendScalar_MaxSize + 1 bytes large. + */ +#define SkStrAppendScalar SkStrAppendFloat + +char* SkStrAppendFloat(char buffer[], float); + +/** \class SkString + + Light weight class for managing strings. Uses reference + counting to make string assignments and copies very fast + with no extra RAM cost. Assumes UTF8 encoding. +*/ +class SK_API SkString { +public: + SkString(); + explicit SkString(size_t len); + explicit SkString(const char text[]); + SkString(const char text[], size_t len); + SkString(const SkString&); + SkString(SkString&&); + ~SkString(); + + bool isEmpty() const { return 0 == fRec->fLength; } + size_t size() const { return (size_t) fRec->fLength; } + const char* c_str() const { return fRec->data(); } + char operator[](size_t n) const { return this->c_str()[n]; } + + bool equals(const SkString&) const; + bool equals(const char text[]) const; + bool equals(const char text[], size_t len) const; + + bool startsWith(const char prefixStr[]) const { + return SkStrStartsWith(fRec->data(), prefixStr); + } + bool startsWith(const char prefixChar) const { + return SkStrStartsWith(fRec->data(), prefixChar); + } + bool endsWith(const char suffixStr[]) const { + return SkStrEndsWith(fRec->data(), suffixStr); + } + bool endsWith(const char suffixChar) const { + return SkStrEndsWith(fRec->data(), suffixChar); + } + bool contains(const char substring[]) const { + return SkStrContains(fRec->data(), substring); + } + bool contains(const char subchar) const { + return SkStrContains(fRec->data(), subchar); + } + int find(const char substring[]) const { + return SkStrFind(fRec->data(), substring); + } + int findLastOf(const char subchar) const { + return SkStrFindLastOf(fRec->data(), subchar); + } + + friend bool operator==(const SkString& a, const SkString& b) { + return a.equals(b); + } + friend bool operator!=(const SkString& a, const SkString& b) { + return !a.equals(b); + } + + // these methods edit the string + + SkString& operator=(const SkString&); + SkString& operator=(SkString&&); + SkString& operator=(const char text[]); + + char* writable_str(); + char& operator[](size_t n) { return this->writable_str()[n]; } + + void reset(); + /** Destructive resize, does not preserve contents. */ + void resize(size_t len) { this->set(nullptr, len); } + void set(const SkString& src) { *this = src; } + void set(const char text[]); + void set(const char text[], size_t len); + + void insert(size_t offset, const SkString& src) { this->insert(offset, src.c_str(), src.size()); } + void insert(size_t offset, const char text[]); + void insert(size_t offset, const char text[], size_t len); + void insertUnichar(size_t offset, SkUnichar); + void insertS32(size_t offset, int32_t value); + void insertS64(size_t offset, int64_t value, int minDigits = 0); + void insertU32(size_t offset, uint32_t value); + void insertU64(size_t offset, uint64_t value, int minDigits = 0); + void insertHex(size_t offset, uint32_t value, int minDigits = 0); + void insertScalar(size_t offset, SkScalar); + + void append(const SkString& str) { this->insert((size_t)-1, str); } + void append(const char text[]) { this->insert((size_t)-1, text); } + void append(const char text[], size_t len) { this->insert((size_t)-1, text, len); } + void appendUnichar(SkUnichar uni) { this->insertUnichar((size_t)-1, uni); } + void appendS32(int32_t value) { this->insertS32((size_t)-1, value); } + void appendS64(int64_t value, int minDigits = 0) { this->insertS64((size_t)-1, value, minDigits); } + void appendU32(uint32_t value) { this->insertU32((size_t)-1, value); } + void appendU64(uint64_t value, int minDigits = 0) { this->insertU64((size_t)-1, value, minDigits); } + void appendHex(uint32_t value, int minDigits = 0) { this->insertHex((size_t)-1, value, minDigits); } + void appendScalar(SkScalar value) { this->insertScalar((size_t)-1, value); } + + void prepend(const SkString& str) { this->insert(0, str); } + void prepend(const char text[]) { this->insert(0, text); } + void prepend(const char text[], size_t len) { this->insert(0, text, len); } + void prependUnichar(SkUnichar uni) { this->insertUnichar(0, uni); } + void prependS32(int32_t value) { this->insertS32(0, value); } + void prependS64(int32_t value, int minDigits = 0) { this->insertS64(0, value, minDigits); } + void prependHex(uint32_t value, int minDigits = 0) { this->insertHex(0, value, minDigits); } + void prependScalar(SkScalar value) { this->insertScalar((size_t)-1, value); } + + void printf(const char format[], ...) SK_PRINTF_LIKE(2, 3); + void appendf(const char format[], ...) SK_PRINTF_LIKE(2, 3); + void appendVAList(const char format[], va_list); + void prependf(const char format[], ...) SK_PRINTF_LIKE(2, 3); + void prependVAList(const char format[], va_list); + + void remove(size_t offset, size_t length); + + SkString& operator+=(const SkString& s) { this->append(s); return *this; } + SkString& operator+=(const char text[]) { this->append(text); return *this; } + SkString& operator+=(const char c) { this->append(&c, 1); return *this; } + + /** + * Swap contents between this and other. This function is guaranteed + * to never fail or throw. + */ + void swap(SkString& other); + +private: + struct Rec { + public: + constexpr Rec(uint32_t len, int32_t refCnt) + : fLength(len), fRefCnt(refCnt), fBeginningOfData(0) + { } + static sk_sp Make(const char text[], size_t len); + uint32_t fLength; // logically size_t, but we want it to stay 32bits + mutable std::atomic fRefCnt; + char fBeginningOfData; + + char* data() { return &fBeginningOfData; } + const char* data() const { return &fBeginningOfData; } + + void ref() const; + void unref() const; + bool unique() const; + private: + // Ensure the unsized delete is called. + void operator delete(void* p) { ::operator delete(p); } + }; + sk_sp fRec; + +#ifdef SK_DEBUG + const SkString& validate() const; +#else + const SkString& validate() const { return *this; } +#endif + + static const Rec gEmptyRec; +}; + +/// Creates a new string and writes into it using a printf()-style format. +SkString SkStringPrintf(const char* format, ...); +/// This makes it easier to write a caller as a VAR_ARGS function where the format string is +/// optional. +static inline SkString SkStringPrintf() { return SkString(); } + +static inline void swap(SkString& a, SkString& b) { + a.swap(b); +} + +enum SkStrSplitMode { + // Strictly return all results. If the input is ",," and the separator is ',' this will return + // an array of three empty strings. + kStrict_SkStrSplitMode, + + // Only nonempty results will be added to the results. Multiple separators will be + // coalesced. Separators at the beginning and end of the input will be ignored. If the input is + // ",," and the separator is ',', this will return an empty vector. + kCoalesce_SkStrSplitMode +}; + +// Split str on any characters in delimiters into out. (Think, strtok with a sane API.) +void SkStrSplit(const char* str, const char* delimiters, SkStrSplitMode splitMode, + SkTArray* out); +inline void SkStrSplit(const char* str, const char* delimiters, SkTArray* out) { + SkStrSplit(str, delimiters, kCoalesce_SkStrSplitMode, out); +} + +#endif diff --git a/gfx/skia/skia/include/core/SkStrokeRec.h b/gfx/skia/skia/include/core/SkStrokeRec.h new file mode 100644 index 0000000000..6fa3872469 --- /dev/null +++ b/gfx/skia/skia/include/core/SkStrokeRec.h @@ -0,0 +1,154 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStrokeRec_DEFINED +#define SkStrokeRec_DEFINED + +#include "include/core/SkPaint.h" +#include "include/private/SkMacros.h" + +class SkPath; + +SK_BEGIN_REQUIRE_DENSE +class SK_API SkStrokeRec { +public: + enum InitStyle { + kHairline_InitStyle, + kFill_InitStyle + }; + SkStrokeRec(InitStyle style); + SkStrokeRec(const SkPaint&, SkPaint::Style, SkScalar resScale = 1); + explicit SkStrokeRec(const SkPaint&, SkScalar resScale = 1); + + enum Style { + kHairline_Style, + kFill_Style, + kStroke_Style, + kStrokeAndFill_Style + }; + + static constexpr int kStyleCount = kStrokeAndFill_Style + 1; + + Style getStyle() const; + SkScalar getWidth() const { return fWidth; } + SkScalar getMiter() const { return fMiterLimit; } + SkPaint::Cap getCap() const { return (SkPaint::Cap)fCap; } + SkPaint::Join getJoin() const { return (SkPaint::Join)fJoin; } + + bool isHairlineStyle() const { + return kHairline_Style == this->getStyle(); + } + + bool isFillStyle() const { + return kFill_Style == this->getStyle(); + } + + void setFillStyle(); + void setHairlineStyle(); + /** + * Specify the strokewidth, and optionally if you want stroke + fill. + * Note, if width==0, then this request is taken to mean: + * strokeAndFill==true -> new style will be Fill + * strokeAndFill==false -> new style will be Hairline + */ + void setStrokeStyle(SkScalar width, bool strokeAndFill = false); + + void setStrokeParams(SkPaint::Cap cap, SkPaint::Join join, SkScalar miterLimit) { + fCap = cap; + fJoin = join; + fMiterLimit = miterLimit; + } + + SkScalar getResScale() const { + return fResScale; + } + + void setResScale(SkScalar rs) { + SkASSERT(rs > 0 && SkScalarIsFinite(rs)); + fResScale = rs; + } + + /** + * Returns true if this specifes any thick stroking, i.e. applyToPath() + * will return true. + */ + bool needToApply() const { + Style style = this->getStyle(); + return (kStroke_Style == style) || (kStrokeAndFill_Style == style); + } + + /** + * Apply these stroke parameters to the src path, returning the result + * in dst. + * + * If there was no change (i.e. style == hairline or fill) this returns + * false and dst is unchanged. Otherwise returns true and the result is + * stored in dst. + * + * src and dst may be the same path. + */ + bool applyToPath(SkPath* dst, const SkPath& src) const; + + /** + * Apply these stroke parameters to a paint. + */ + void applyToPaint(SkPaint* paint) const; + + /** + * Gives a conservative value for the outset that should applied to a + * geometries bounds to account for any inflation due to applying this + * strokeRec to the geometry. + */ + SkScalar getInflationRadius() const; + + /** + * Equivalent to: + * SkStrokeRec rec(paint, style); + * rec.getInflationRadius(); + * This does not account for other effects on the paint (i.e. path + * effect). + */ + static SkScalar GetInflationRadius(const SkPaint&, SkPaint::Style); + + static SkScalar GetInflationRadius(SkPaint::Join, SkScalar miterLimit, SkPaint::Cap, + SkScalar strokeWidth); + + /** + * Compare if two SkStrokeRecs have an equal effect on a path. + * Equal SkStrokeRecs produce equal paths. Equality of produced + * paths does not take the ResScale parameter into account. + */ + bool hasEqualEffect(const SkStrokeRec& other) const { + if (!this->needToApply()) { + return this->getStyle() == other.getStyle(); + } + return fWidth == other.fWidth && + fMiterLimit == other.fMiterLimit && + fCap == other.fCap && + fJoin == other.fJoin && + fStrokeAndFill == other.fStrokeAndFill; + } + +private: + void init(const SkPaint&, SkPaint::Style, SkScalar resScale); + + SkScalar fResScale; + SkScalar fWidth; + SkScalar fMiterLimit; + // The following three members are packed together into a single u32. + // This is to avoid unnecessary padding and ensure binary equality for + // hashing (because the padded areas might contain garbage values). + // + // fCap and fJoin are larger than needed to avoid having to initialize + // any pad values + uint32_t fCap : 16; // SkPaint::Cap + uint32_t fJoin : 15; // SkPaint::Join + uint32_t fStrokeAndFill : 1; // bool +}; +SK_END_REQUIRE_DENSE + +#endif diff --git a/gfx/skia/skia/include/core/SkSurface.h b/gfx/skia/skia/include/core/SkSurface.h new file mode 100644 index 0000000000..eaf530490d --- /dev/null +++ b/gfx/skia/skia/include/core/SkSurface.h @@ -0,0 +1,1046 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSurface_DEFINED +#define SkSurface_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceProps.h" + +#include "include/gpu/GrTypes.h" + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 +#include +#endif + +#ifdef SK_METAL +#include "include/gpu/mtl/GrMtlTypes.h" +#endif + +class SkCanvas; +class SkDeferredDisplayList; +class SkPaint; +class SkSurfaceCharacterization; +class GrBackendRenderTarget; +class GrBackendSemaphore; +class GrBackendTexture; +class GrContext; +class GrRecordingContext; +class GrRenderTarget; + +/** \class SkSurface + SkSurface is responsible for managing the pixels that a canvas draws into. The pixels can be + allocated either in CPU memory (a raster surface) or on the GPU (a GrRenderTarget surface). + SkSurface takes care of allocating a SkCanvas that will draw into the surface. Call + surface->getCanvas() to use that canvas (but don't delete it, it is owned by the surface). + SkSurface always has non-zero dimensions. If there is a request for a new surface, and either + of the requested dimensions are zero, then nullptr will be returned. +*/ +class SK_API SkSurface : public SkRefCnt { +public: + + /** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels. + + SkSurface is returned if all parameters are valid. + Valid parameters include: + info dimensions are greater than zero; + info contains SkColorType and SkAlphaType supported by raster surface; + pixels is not nullptr; + rowBytes is large enough to contain info width pixels of SkColorType. + + Pixel buffer size should be info height times computed rowBytes. + Pixels are not initialized. + To access pixels after drawing, call flush() or peekPixels(). + + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of raster surface; width and height must be greater than zero + @param pixels pointer to destination pixels buffer + @param rowBytes interval from one SkSurface row to the next + @param surfaceProps LCD striping orientation and setting for device independent fonts; + may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRasterDirect(const SkImageInfo& imageInfo, void* pixels, + size_t rowBytes, + const SkSurfaceProps* surfaceProps = nullptr); + + /** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels. + releaseProc is called with pixels and context when SkSurface is deleted. + + SkSurface is returned if all parameters are valid. + Valid parameters include: + info dimensions are greater than zero; + info contains SkColorType and SkAlphaType supported by raster surface; + pixels is not nullptr; + rowBytes is large enough to contain info width pixels of SkColorType. + + Pixel buffer size should be info height times computed rowBytes. + Pixels are not initialized. + To access pixels after drawing, call flush() or peekPixels(). + + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of raster surface; width and height must be greater than zero + @param pixels pointer to destination pixels buffer + @param rowBytes interval from one SkSurface row to the next + @param releaseProc called when SkSurface is deleted; may be nullptr + @param context passed to releaseProc; may be nullptr + @param surfaceProps LCD striping orientation and setting for device independent fonts; + may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRasterDirectReleaseProc(const SkImageInfo& imageInfo, void* pixels, + size_t rowBytes, + void (*releaseProc)(void* pixels, void* context), + void* context, const SkSurfaceProps* surfaceProps = nullptr); + + /** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels. + Allocates and zeroes pixel memory. Pixel memory size is imageInfo.height() times + rowBytes, or times imageInfo.minRowBytes() if rowBytes is zero. + Pixel memory is deleted when SkSurface is deleted. + + SkSurface is returned if all parameters are valid. + Valid parameters include: + info dimensions are greater than zero; + info contains SkColorType and SkAlphaType supported by raster surface; + rowBytes is large enough to contain info width pixels of SkColorType, or is zero. + + If rowBytes is not zero, subsequent images returned by makeImageSnapshot() + have the same rowBytes. + + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of raster surface; width and height must be greater than zero + @param rowBytes interval from one SkSurface row to the next; may be zero + @param surfaceProps LCD striping orientation and setting for device independent fonts; + may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRaster(const SkImageInfo& imageInfo, size_t rowBytes, + const SkSurfaceProps* surfaceProps); + + /** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels. + Allocates and zeroes pixel memory. Pixel memory size is imageInfo.height() times + imageInfo.minRowBytes(). + Pixel memory is deleted when SkSurface is deleted. + + SkSurface is returned if all parameters are valid. + Valid parameters include: + info dimensions are greater than zero; + info contains SkColorType and SkAlphaType supported by raster surface. + + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of raster surface; width and height must be greater than zero + @param props LCD striping orientation and setting for device independent fonts; + may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRaster(const SkImageInfo& imageInfo, + const SkSurfaceProps* props = nullptr) { + return MakeRaster(imageInfo, 0, props); + } + + /** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels. + Allocates and zeroes pixel memory. Pixel memory size is height times width times + four. Pixel memory is deleted when SkSurface is deleted. + + Internally, sets SkImageInfo to width, height, native color type, and + kPremul_SkAlphaType. + + SkSurface is returned if width and height are greater than zero. + + Use to create SkSurface that matches SkPMColor, the native pixel arrangement on + the platform. SkSurface drawn to output device skips converting its pixel format. + + @param width pixel column count; must be greater than zero + @param height pixel row count; must be greater than zero + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRasterN32Premul(int width, int height, + const SkSurfaceProps* surfaceProps = nullptr); + + /** Caller data passed to RenderTarget/TextureReleaseProc; may be nullptr. */ + typedef void* ReleaseContext; + + /** User function called when supplied render target may be deleted. */ + typedef void (*RenderTargetReleaseProc)(ReleaseContext releaseContext); + + /** User function called when supplied texture may be deleted. */ + typedef void (*TextureReleaseProc)(ReleaseContext releaseContext); + + /** Wraps a GPU-backed texture into SkSurface. Caller must ensure the texture is + valid for the lifetime of returned SkSurface. If sampleCnt greater than zero, + creates an intermediate MSAA SkSurface which is used for drawing backendTexture. + + SkSurface is returned if all parameters are valid. backendTexture is valid if + its pixel configuration agrees with colorSpace and context; for instance, if + backendTexture has an sRGB configuration, then context must support sRGB, + and colorSpace must be present. Further, backendTexture width and height must + not exceed context capabilities, and the context must be able to support + back-end textures. + + If SK_SUPPORT_GPU is defined as zero, has no effect and returns nullptr. + + @param context GPU context + @param backendTexture texture residing on GPU + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param sampleCnt samples per pixel, or 0 to disable full scene anti-aliasing + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kRGB_888x_SkColorType, kBGRA_8888_SkColorType, + kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param colorSpace range of colors; may be nullptr + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @param textureReleaseProc function called when texture can be released + @param releaseContext state passed to textureReleaseProc + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeFromBackendTexture(GrContext* context, + const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, int sampleCnt, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + TextureReleaseProc textureReleaseProc = nullptr, + ReleaseContext releaseContext = nullptr); + + /** Wraps a GPU-backed buffer into SkSurface. Caller must ensure backendRenderTarget + is valid for the lifetime of returned SkSurface. + + SkSurface is returned if all parameters are valid. backendRenderTarget is valid if + its pixel configuration agrees with colorSpace and context; for instance, if + backendRenderTarget has an sRGB configuration, then context must support sRGB, + and colorSpace must be present. Further, backendRenderTarget width and height must + not exceed context capabilities, and the context must be able to support + back-end render targets. + + If SK_SUPPORT_GPU is defined as zero, has no effect and returns nullptr. + + @param context GPU context + @param backendRenderTarget GPU intermediate memory buffer + @param origin one of: + kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, + kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kRGB_888x_SkColorType, kBGRA_8888_SkColorType, + kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param colorSpace range of colors + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @param releaseProc function called when texture can be released + @param releaseContext state passed to textureReleaseProc + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeFromBackendRenderTarget(GrContext* context, + const GrBackendRenderTarget& backendRenderTarget, + GrSurfaceOrigin origin, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + RenderTargetReleaseProc releaseProc = nullptr, + ReleaseContext releaseContext = nullptr); + + /** Wraps a GPU-backed texture into SkSurface. Caller must ensure backendTexture is + valid for the lifetime of returned SkSurface. If sampleCnt greater than zero, + creates an intermediate MSAA SkSurface which is used for drawing backendTexture. + + SkSurface is returned if all parameters are valid. backendTexture is valid if + its pixel configuration agrees with colorSpace and context; for instance, if + backendTexture has an sRGB configuration, then context must support sRGB, + and colorSpace must be present. Further, backendTexture width and height must + not exceed context capabilities. + + Returned SkSurface is available only for drawing into, and cannot generate an + SkImage. + + If SK_SUPPORT_GPU is defined as zero, has no effect and returns nullptr. + + @param context GPU context + @param backendTexture texture residing on GPU + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param sampleCnt samples per pixel, or 0 to disable full scene anti-aliasing + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kRGB_888x_SkColorType, kBGRA_8888_SkColorType, + kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param colorSpace range of colors; may be nullptr + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeFromBackendTextureAsRenderTarget(GrContext* context, + const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, + int sampleCnt, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps); + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 + /** Private. + Creates SkSurface from Android hardware buffer. + Returned SkSurface takes a reference on the buffer. The ref on the buffer will be released + when the SkSurface is destroyed and there is no pending work on the GPU involving the + buffer. + + Only available on Android, when __ANDROID_API__ is defined to be 26 or greater. + + Currently this is only supported for buffers that can be textured as well as rendered to. + In other words that must have both AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT and + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE usage bits. + + @param context GPU context + @param hardwareBuffer AHardwareBuffer Android hardware buffer + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorSpace range of colors; may be nullptr + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @return created SkSurface, or nullptr + */ + static sk_sp MakeFromAHardwareBuffer(GrContext* context, + AHardwareBuffer* hardwareBuffer, + GrSurfaceOrigin origin, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps); +#endif + +#ifdef SK_METAL + /** Private. + Creates SkSurface from CAMetalLayer. + Returned SkSurface takes a reference on the CAMetalLayer. The ref on the layer will be + released when the SkSurface is destroyed. + + Only available when Metal API is enabled. + + Will grab the current drawable from the layer and use its texture as a backendRT to + create a renderable surface. + + @param context GPU context + @param layer GrMTLHandle (expected to be a CAMetalLayer*) + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param sampleCnt samples per pixel, or 0 to disable full scene anti-aliasing + @param colorType one of: + kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, + kARGB_4444_SkColorType, kRGBA_8888_SkColorType, + kRGB_888x_SkColorType, kBGRA_8888_SkColorType, + kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param colorSpace range of colors; may be nullptr + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @param drawable Pointer to drawable to be filled in when this surface is + instantiated; may not be nullptr + @return created SkSurface, or nullptr + */ + static sk_sp MakeFromCAMetalLayer(GrContext* context, + GrMTLHandle layer, + GrSurfaceOrigin origin, + int sampleCnt, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + GrMTLHandle* drawable); + +#endif + + /** Returns SkSurface on GPU indicated by context. Allocates memory for + pixels, based on the width, height, and SkColorType in SkImageInfo. budgeted + selects whether allocation for pixels is tracked by context. imageInfo + describes the pixel format in SkColorType, and transparency in + SkAlphaType, and color matching in SkColorSpace. + + sampleCount requests the number of samples per pixel. + Pass zero to disable multi-sample anti-aliasing. The request is rounded + up to the next supported count, or rounded down if it is larger than the + maximum supported count. + + surfaceOrigin pins either the top-left or the bottom-left corner to the origin. + + shouldCreateWithMips hints that SkImage returned by makeImageSnapshot() is mip map. + + If SK_SUPPORT_GPU is defined as zero, has no effect and returns nullptr. + + @param context GPU context + @param budgeted one of: SkBudgeted::kNo, SkBudgeted::kYes + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace; + width, or height, or both, may be zero + @param sampleCount samples per pixel, or 0 to disable full scene anti-aliasing + @param surfaceOrigin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @param shouldCreateWithMips hint that SkSurface will host mip map images + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRenderTarget(GrContext* context, SkBudgeted budgeted, + const SkImageInfo& imageInfo, + int sampleCount, GrSurfaceOrigin surfaceOrigin, + const SkSurfaceProps* surfaceProps, + bool shouldCreateWithMips = false); + + /** Returns SkSurface on GPU indicated by context. Allocates memory for + pixels, based on the width, height, and SkColorType in SkImageInfo. budgeted + selects whether allocation for pixels is tracked by context. imageInfo + describes the pixel format in SkColorType, and transparency in + SkAlphaType, and color matching in SkColorSpace. + + sampleCount requests the number of samples per pixel. + Pass zero to disable multi-sample anti-aliasing. The request is rounded + up to the next supported count, or rounded down if it is larger than the + maximum supported count. + + SkSurface bottom-left corner is pinned to the origin. + + @param context GPU context + @param budgeted one of: SkBudgeted::kNo, SkBudgeted::kYes + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of raster surface; width, or height, or both, may be zero + @param sampleCount samples per pixel, or 0 to disable multi-sample anti-aliasing + @param surfaceProps LCD striping orientation and setting for device independent + fonts; may be nullptr + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRenderTarget(GrContext* context, SkBudgeted budgeted, + const SkImageInfo& imageInfo, int sampleCount, + const SkSurfaceProps* surfaceProps) { + return MakeRenderTarget(context, budgeted, imageInfo, sampleCount, + kBottomLeft_GrSurfaceOrigin, surfaceProps); + } + + /** Returns SkSurface on GPU indicated by context. Allocates memory for + pixels, based on the width, height, and SkColorType in SkImageInfo. budgeted + selects whether allocation for pixels is tracked by context. imageInfo + describes the pixel format in SkColorType, and transparency in + SkAlphaType, and color matching in SkColorSpace. + + SkSurface bottom-left corner is pinned to the origin. + + @param context GPU context + @param budgeted one of: SkBudgeted::kNo, SkBudgeted::kYes + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of raster surface; width, or height, or both, may be zero + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRenderTarget(GrContext* context, SkBudgeted budgeted, + const SkImageInfo& imageInfo) { + if (!imageInfo.width() || !imageInfo.height()) { + return nullptr; + } + return MakeRenderTarget(context, budgeted, imageInfo, 0, kBottomLeft_GrSurfaceOrigin, + nullptr); + } + + /** Returns SkSurface on GPU indicated by context that is compatible with the provided + characterization. budgeted selects whether allocation for pixels is tracked by context. + + @param context GPU context + @param characterization description of the desired SkSurface + @param budgeted one of: SkBudgeted::kNo, SkBudgeted::kYes + @return SkSurface if all parameters are valid; otherwise, nullptr + */ + static sk_sp MakeRenderTarget(GrRecordingContext* context, + const SkSurfaceCharacterization& characterization, + SkBudgeted budgeted); + + /** Wraps a backend texture in an SkSurface - setting up the surface to match the provided + characterization. The caller must ensure the texture is valid for the lifetime of + returned SkSurface. + + If the backend texture and surface characterization are incompatible then null will + be returned. + + Usually, the GrContext::createBackendTexture variant that takes a surface characterization + should be used to create the backend texture. If not, + SkSurfaceCharacterization::isCompatible can be used to determine if a given backend texture + is compatible with a specific surface characterization. + + @param context GPU context + @param characterization characterization of the desired surface + @param backendTexture texture residing on GPU + @param textureReleaseProc function called when texture can be released + @param releaseContext state passed to textureReleaseProc + @return SkSurface if all parameters are compatible; otherwise, nullptr + */ + static sk_sp MakeFromBackendTexture(GrContext* context, + const SkSurfaceCharacterization& characterzation, + const GrBackendTexture& backendTexture, + TextureReleaseProc textureReleaseProc = nullptr, + ReleaseContext releaseContext = nullptr); + + /** Is this surface compatible with the provided characterization? + + This method can be used to determine if an existing SkSurface is a viable destination + for an SkDeferredDisplayList. + + @param characterization The characterization for which a compatibility check is desired + @return true if this surface is compatible with the characterization; + false otherwise + */ + bool isCompatible(const SkSurfaceCharacterization& characterization) const; + + /** Returns SkSurface without backing pixels. Drawing to SkCanvas returned from SkSurface + has no effect. Calling makeImageSnapshot() on returned SkSurface returns nullptr. + + @param width one or greater + @param height one or greater + @return SkSurface if width and height are positive; otherwise, nullptr + */ + static sk_sp MakeNull(int width, int height); + + /** Returns pixel count in each row; may be zero or greater. + + @return number of pixel columns + */ + int width() const { return fWidth; } + + /** Returns pixel row count; may be zero or greater. + + @return number of pixel rows + */ + int height() const { return fHeight; } + + /** Returns an ImageInfo describing the surface. + */ + SkImageInfo imageInfo(); + + /** Returns unique value identifying the content of SkSurface. Returned value changes + each time the content changes. Content is changed by drawing, or by calling + notifyContentWillChange(). + + @return unique content identifier + */ + uint32_t generationID(); + + /** \enum SkSurface::ContentChangeMode + ContentChangeMode members are parameters to notifyContentWillChange(). + */ + enum ContentChangeMode { + kDiscard_ContentChangeMode, //!< discards surface on change + kRetain_ContentChangeMode, //!< preserves surface on change + }; + + /** Notifies that SkSurface contents will be changed by code outside of Skia. + Subsequent calls to generationID() return a different value. + + TODO: Can kRetain_ContentChangeMode be deprecated? + + @param mode one of: kDiscard_ContentChangeMode, kRetain_ContentChangeMode + */ + void notifyContentWillChange(ContentChangeMode mode); + + enum BackendHandleAccess { + kFlushRead_BackendHandleAccess, //!< back-end object is readable + kFlushWrite_BackendHandleAccess, //!< back-end object is writable + kDiscardWrite_BackendHandleAccess, //!< back-end object must be overwritten + }; + + /** Deprecated. + */ + static const BackendHandleAccess kFlushRead_TextureHandleAccess = + kFlushRead_BackendHandleAccess; + + /** Deprecated. + */ + static const BackendHandleAccess kFlushWrite_TextureHandleAccess = + kFlushWrite_BackendHandleAccess; + + /** Deprecated. + */ + static const BackendHandleAccess kDiscardWrite_TextureHandleAccess = + kDiscardWrite_BackendHandleAccess; + + /** Retrieves the back-end texture. If SkSurface has no back-end texture, an invalid + object is returned. Call GrBackendTexture::isValid to determine if the result + is valid. + + The returned GrBackendTexture should be discarded if the SkSurface is drawn to or deleted. + + @param backendHandleAccess one of: kFlushRead_BackendHandleAccess, + kFlushWrite_BackendHandleAccess, + kDiscardWrite_BackendHandleAccess + @return GPU texture reference; invalid on failure + */ + GrBackendTexture getBackendTexture(BackendHandleAccess backendHandleAccess); + + /** Retrieves the back-end render target. If SkSurface has no back-end render target, an invalid + object is returned. Call GrBackendRenderTarget::isValid to determine if the result + is valid. + + The returned GrBackendRenderTarget should be discarded if the SkSurface is drawn to + or deleted. + + @param backendHandleAccess one of: kFlushRead_BackendHandleAccess, + kFlushWrite_BackendHandleAccess, + kDiscardWrite_BackendHandleAccess + @return GPU render target reference; invalid on failure + */ + GrBackendRenderTarget getBackendRenderTarget(BackendHandleAccess backendHandleAccess); + + /** If the surface was made via MakeFromBackendTexture then it's backing texture may be + substituted with a different texture. The contents of the previous backing texture are + copied into the new texture. SkCanvas state is preserved. The original sample count is + used. The GrBackendFormat and dimensions of replacement texture must match that of + the original. + + @param backendTexture the new backing texture for the surface. + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param textureReleaseProc function called when texture can be released + @param releaseContext state passed to textureReleaseProc + */ + bool replaceBackendTexture(const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, + TextureReleaseProc textureReleaseProc = nullptr, + ReleaseContext releaseContext = nullptr); + + /** Returns SkCanvas that draws into SkSurface. Subsequent calls return the same SkCanvas. + SkCanvas returned is managed and owned by SkSurface, and is deleted when SkSurface + is deleted. + + @return drawing SkCanvas for SkSurface + */ + SkCanvas* getCanvas(); + + /** Returns a compatible SkSurface, or nullptr. Returned SkSurface contains + the same raster, GPU, or null properties as the original. Returned SkSurface + does not share the same pixels. + + Returns nullptr if imageInfo width or height are zero, or if imageInfo + is incompatible with SkSurface. + + @param imageInfo width, height, SkColorType, SkAlphaType, SkColorSpace, + of SkSurface; width and height must be greater than zero + @return compatible SkSurface or nullptr + */ + sk_sp makeSurface(const SkImageInfo& imageInfo); + + /** Calls makeSurface(ImageInfo) with the same ImageInfo as this surface, but with the + * specified width and height. + */ + sk_sp makeSurface(int width, int height); + + /** Returns SkImage capturing SkSurface contents. Subsequent drawing to SkSurface contents + are not captured. SkImage allocation is accounted for if SkSurface was created with + SkBudgeted::kYes. + + @return SkImage initialized with SkSurface contents + */ + sk_sp makeImageSnapshot(); + + /** + * Like the no-parameter version, this returns an image of the current surface contents. + * This variant takes a rectangle specifying the subset of the surface that is of interest. + * These bounds will be sanitized before being used. + * - If bounds extends beyond the surface, it will be trimmed to just the intersection of + * it and the surface. + * - If bounds does not intersect the surface, then this returns nullptr. + * - If bounds == the surface, then this is the same as calling the no-parameter variant. + */ + sk_sp makeImageSnapshot(const SkIRect& bounds); + + /** Draws SkSurface contents to canvas, with its top-left corner at (x, y). + + If SkPaint paint is not nullptr, apply SkColorFilter, alpha, SkImageFilter, + SkBlendMode, and SkDrawLooper. + + @param canvas SkCanvas drawn into + @param x horizontal offset in SkCanvas + @param y vertical offset in SkCanvas + @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, + and so on; or nullptr + */ + void draw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint); + + /** Copies SkSurface pixel address, row bytes, and SkImageInfo to SkPixmap, if address + is available, and returns true. If pixel address is not available, return + false and leave SkPixmap unchanged. + + pixmap contents become invalid on any future change to SkSurface. + + @param pixmap storage for pixel state if pixels are readable; otherwise, ignored + @return true if SkSurface has direct access to pixels + */ + bool peekPixels(SkPixmap* pixmap); + + /** Copies SkRect of pixels to dst. + + Source SkRect corners are (srcX, srcY) and SkSurface (width(), height()). + Destination SkRect corners are (0, 0) and (dst.width(), dst.height()). + Copies each readable pixel intersecting both rectangles, without scaling, + converting to dst.colorType() and dst.alphaType() if required. + + Pixels are readable when SkSurface is raster, or backed by a GPU. + + The destination pixel storage must be allocated by the caller. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. dst contents outside SkRect intersection are unchanged. + + Pass negative values for srcX or srcY to offset pixels across or down destination. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - SkPixmap pixels could not be allocated. + - dst.rowBytes() is too small to contain one row of pixels. + + @param dst storage for pixels copied from SkSurface + @param srcX offset into readable pixels on x-axis; may be negative + @param srcY offset into readable pixels on y-axis; may be negative + @return true if pixels were copied + */ + bool readPixels(const SkPixmap& dst, int srcX, int srcY); + + /** Copies SkRect of pixels from SkCanvas into dstPixels. + + Source SkRect corners are (srcX, srcY) and SkSurface (width(), height()). + Destination SkRect corners are (0, 0) and (dstInfo.width(), dstInfo.height()). + Copies each readable pixel intersecting both rectangles, without scaling, + converting to dstInfo.colorType() and dstInfo.alphaType() if required. + + Pixels are readable when SkSurface is raster, or backed by a GPU. + + The destination pixel storage must be allocated by the caller. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. dstPixels contents outside SkRect intersection are unchanged. + + Pass negative values for srcX or srcY to offset pixels across or down destination. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - SkSurface pixels could not be converted to dstInfo.colorType() or dstInfo.alphaType(). + - dstRowBytes is too small to contain one row of pixels. + + @param dstInfo width, height, SkColorType, and SkAlphaType of dstPixels + @param dstPixels storage for pixels; dstInfo.height() times dstRowBytes, or larger + @param dstRowBytes size of one destination row; dstInfo.width() times pixel size, or larger + @param srcX offset into readable pixels on x-axis; may be negative + @param srcY offset into readable pixels on y-axis; may be negative + @return true if pixels were copied + */ + bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY); + + /** Copies SkRect of pixels from SkSurface into bitmap. + + Source SkRect corners are (srcX, srcY) and SkSurface (width(), height()). + Destination SkRect corners are (0, 0) and (bitmap.width(), bitmap.height()). + Copies each readable pixel intersecting both rectangles, without scaling, + converting to bitmap.colorType() and bitmap.alphaType() if required. + + Pixels are readable when SkSurface is raster, or backed by a GPU. + + The destination pixel storage must be allocated by the caller. + + Pixel values are converted only if SkColorType and SkAlphaType + do not match. Only pixels within both source and destination rectangles + are copied. dst contents outside SkRect intersection are unchanged. + + Pass negative values for srcX or srcY to offset pixels across or down destination. + + Does not copy, and returns false if: + - Source and destination rectangles do not intersect. + - SkSurface pixels could not be converted to dst.colorType() or dst.alphaType(). + - dst pixels could not be allocated. + - dst.rowBytes() is too small to contain one row of pixels. + + @param dst storage for pixels copied from SkSurface + @param srcX offset into readable pixels on x-axis; may be negative + @param srcY offset into readable pixels on y-axis; may be negative + @return true if pixels were copied + */ + bool readPixels(const SkBitmap& dst, int srcX, int srcY); + + /** The result from asyncRescaleAndReadPixels() or asyncRescaleAndReadPixelsYUV420(). */ + class AsyncReadResult { + public: + AsyncReadResult(const AsyncReadResult&) = delete; + AsyncReadResult(AsyncReadResult&&) = delete; + AsyncReadResult& operator=(const AsyncReadResult&) = delete; + AsyncReadResult& operator=(AsyncReadResult&&) = delete; + + virtual ~AsyncReadResult() = default; + virtual int count() const = 0; + virtual const void* data(int i) const = 0; + virtual size_t rowBytes(int i) const = 0; + + protected: + AsyncReadResult() = default; + }; + + /** Client-provided context that is passed to client-provided ReadPixelsContext. */ + using ReadPixelsContext = void*; + + /** Client-provided callback to asyncRescaleAndReadPixels() or + asyncRescaleAndReadPixelsYUV420() that is called when read result is ready or on failure. + */ + using ReadPixelsCallback = void(ReadPixelsContext, std::unique_ptr); + + /** Controls the gamma that rescaling occurs in for asyncRescaleAndReadPixels() and + asyncRescaleAndReadPixelsYUV420(). + */ + enum RescaleGamma : bool { kSrc, kLinear }; + + /** Makes surface pixel data available to caller, possibly asynchronously. It can also rescale + the surface pixels. + + Currently asynchronous reads are only supported on the GPU backend and only when the + underlying 3D API supports transfer buffers and CPU/GPU synchronization primitives. In all + other cases this operates synchronously. + + Data is read from the source sub-rectangle, is optionally converted to a linear gamma, is + rescaled to the size indicated by 'info', is then converted to the color space, color type, + and alpha type of 'info'. A 'srcRect' that is not contained by the bounds of the surface + causes failure. + + When the pixel data is ready the caller's ReadPixelsCallback is called with a + AsyncReadResult containing pixel data in the requested color type, alpha type, and color + space. The AsyncReadResult will have count() == 1. Upon failure the callback is called + with nullptr for AsyncReadResult. + + The data is valid for the lifetime of AsyncReadResult with the exception that if the + SkSurface is GPU-backed the data is immediately invalidated if the GrContext is abandoned + or destroyed. + + @param info info of the requested pixels + @param srcRect subrectangle of surface to read + @param rescaleGamma controls whether rescaling is done in the surface's gamma or whether + the source data is transformed to a linear gamma before rescaling. + @param rescaleQuality controls the quality (and cost) of the rescaling + @param callback function to call with result of the read + @param context passed to callback + */ + void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect, + RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, ReadPixelsContext context); + + /** Legacy version of asyncRescaleAndReadPixels() that passes data directly to the callback + rather than using AsyncReadResult. The data is only valid during the lifetime of the + callback. + + Deprecated. + */ + using LegacyReadPixelsCallback = void(ReadPixelsContext, const void* data, size_t rowBytes); + void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect, + RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality, + LegacyReadPixelsCallback callback, ReadPixelsContext context); + + /** + Similar to asyncRescaleAndReadPixels but performs an additional conversion to YUV. The + RGB->YUV conversion is controlled by 'yuvColorSpace'. The YUV data is returned as three + planes ordered y, u, v. The u and v planes are half the width and height of the resized + rectangle. The y, u, and v values are single bytes. Currently this fails if 'dstSize' + width and height are not even. A 'srcRect' that is not contained by the bounds of the + surface causes failure. + + When the pixel data is ready the caller's ReadPixelsCallback is called with a + AsyncReadResult containing the planar data. The AsyncReadResult will have count() == 3. + Upon failure the callback is called with nullptr for AsyncReadResult. + + The data is valid for the lifetime of AsyncReadResult with the exception that if the + SkSurface is GPU-backed the data is immediately invalidated if the GrContext is abandoned + or destroyed. + + @param yuvColorSpace The transformation from RGB to YUV. Applied to the resized image + after it is converted to dstColorSpace. + @param dstColorSpace The color space to convert the resized image to, after rescaling. + @param srcRect The portion of the surface to rescale and convert to YUV planes. + @param dstSize The size to rescale srcRect to + @param rescaleGamma controls whether rescaling is done in the surface's gamma or whether + the source data is transformed to a linear gamma before rescaling. + @param rescaleQuality controls the quality (and cost) of the rescaling + @param callback function to call with the planar read result + @param context passed to callback + */ + void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext); + + /** Legacy version of asyncRescaleAndReadPixelsYUV420() that passes data directly to the + callback rather than using AsyncReadResult. The data is only valid during the lifetime of + the callback. + + Deprecated. + */ + using LegacyReadPixelsCallbackYUV420 = void(ReadPixelsContext, const void* data[3], + size_t rowBytes[3]); + void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + int dstW, int dstH, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + LegacyReadPixelsCallbackYUV420 callback, + ReadPixelsContext); + + /** Copies SkRect of pixels from the src SkPixmap to the SkSurface. + + Source SkRect corners are (0, 0) and (src.width(), src.height()). + Destination SkRect corners are (dstX, dstY) and + (dstX + Surface width(), dstY + Surface height()). + + Copies each readable pixel intersecting both rectangles, without scaling, + converting to SkSurface colorType() and SkSurface alphaType() if required. + + @param src storage for pixels to copy to SkSurface + @param dstX x-axis position relative to SkSurface to begin copy; may be negative + @param dstY y-axis position relative to SkSurface to begin copy; may be negative + */ + void writePixels(const SkPixmap& src, int dstX, int dstY); + + /** Copies SkRect of pixels from the src SkBitmap to the SkSurface. + + Source SkRect corners are (0, 0) and (src.width(), src.height()). + Destination SkRect corners are (dstX, dstY) and + (dstX + Surface width(), dstY + Surface height()). + + Copies each readable pixel intersecting both rectangles, without scaling, + converting to SkSurface colorType() and SkSurface alphaType() if required. + + @param src storage for pixels to copy to SkSurface + @param dstX x-axis position relative to SkSurface to begin copy; may be negative + @param dstY y-axis position relative to SkSurface to begin copy; may be negative + */ + void writePixels(const SkBitmap& src, int dstX, int dstY); + + /** Returns SkSurfaceProps for surface. + + @return LCD striping orientation and setting for device independent fonts + */ + const SkSurfaceProps& props() const { return fProps; } + + /** Issues pending SkSurface commands to the GPU-backed API and resolves any SkSurface MSAA. + + Skia flushes as needed, so it is not necessary to call this if Skia manages + drawing and object lifetime. Call when interleaving Skia calls with native + GPU calls. + */ + void flush(); + + enum class BackendSurfaceAccess { + kNoAccess, //!< back-end object will not be used by client + kPresent, //!< back-end surface will be used for presenting to screen + }; + + /** Issues pending SkSurface commands to the GPU-backed API and resolves any SkSurface MSAA. + The work that is submitted to the GPU will be dependent on the BackendSurfaceAccess that is + passed in. + + If BackendSurfaceAccess::kNoAccess is passed in all commands will be issued to the GPU. + + If BackendSurfaceAccess::kPresent is passed in and the backend API is not Vulkan, it is + treated the same as kNoAccess. If the backend API is Vulkan, the VkImage that backs the + SkSurface will be transferred back to its original queue. If the SkSurface was created by + wrapping a VkImage, the queue will be set to the queue which was originally passed in on + the GrVkImageInfo. Additionally, if the original queue was not external or foreign the + layout of the VkImage will be set to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR. + + The GrFlushInfo describes additional options to flush. Please see documentation at + GrFlushInfo for more info. + + If GrSemaphoresSubmitted::kNo is returned, the GPU back-end did not create or + add any semaphores to signal on the GPU; the caller should not instruct the GPU + to wait on any of the semaphores passed in the GrFlushInfo. + + Pending surface commands are flushed regardless of the return result. + + @param access type of access the call will do on the backend object after flush + @param info flush options + @return one of: GrSemaphoresSubmitted::kYes, GrSemaphoresSubmitted::kNo + */ + GrSemaphoresSubmitted flush(BackendSurfaceAccess access, const GrFlushInfo& info); + + /** Deprecated + */ + GrSemaphoresSubmitted flush(BackendSurfaceAccess access, GrFlushFlags flags, + int numSemaphores, GrBackendSemaphore signalSemaphores[], + GrGpuFinishedProc finishedProc = nullptr, + GrGpuFinishedContext finishedContext = nullptr); + + /** The below enum and flush call are deprecated + */ + enum FlushFlags { + kNone_FlushFlags = 0, + // flush will wait till all submitted GPU work is finished before returning. + kSyncCpu_FlushFlag = 0x1, + }; + GrSemaphoresSubmitted flush(BackendSurfaceAccess access, FlushFlags flags, + int numSemaphores, GrBackendSemaphore signalSemaphores[]); + + /** Deprecated. + */ + GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores, + GrBackendSemaphore signalSemaphores[]); + + /** Inserts a list of GPU semaphores that the current GPU-backed API must wait on before + executing any more commands on the GPU for this surface. Skia will take ownership of the + underlying semaphores and delete them once they have been signaled and waited on. + If this call returns false, then the GPU back-end will not wait on any passed in semaphores, + and the client will still own the semaphores. + + @param numSemaphores size of waitSemaphores array + @param waitSemaphores array of semaphore containers + @return true if GPU is waiting on semaphores + */ + bool wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores); + + /** Initializes SkSurfaceCharacterization that can be used to perform GPU back-end + processing in a separate thread. Typically this is used to divide drawing + into multiple tiles. SkDeferredDisplayListRecorder records the drawing commands + for each tile. + + Return true if SkSurface supports characterization. raster surface returns false. + + @param characterization properties for parallel drawing + @return true if supported + */ + bool characterize(SkSurfaceCharacterization* characterization) const; + + /** Draws deferred display list created using SkDeferredDisplayListRecorder. + Has no effect and returns false if SkSurfaceCharacterization stored in + deferredDisplayList is not compatible with SkSurface. + + raster surface returns false. + + @param deferredDisplayList drawing commands + @return false if deferredDisplayList is not compatible + */ + bool draw(SkDeferredDisplayList* deferredDisplayList); + +protected: + SkSurface(int width, int height, const SkSurfaceProps* surfaceProps); + SkSurface(const SkImageInfo& imageInfo, const SkSurfaceProps* surfaceProps); + + // called by subclass if their contents have changed + void dirtyGenerationID() { + fGenerationID = 0; + } + +private: + const SkSurfaceProps fProps; + const int fWidth; + const int fHeight; + uint32_t fGenerationID; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkSurfaceCharacterization.h b/gfx/skia/skia/include/core/SkSurfaceCharacterization.h new file mode 100644 index 0000000000..4588df9e3d --- /dev/null +++ b/gfx/skia/skia/include/core/SkSurfaceCharacterization.h @@ -0,0 +1,228 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSurfaceCharacterization_DEFINED +#define SkSurfaceCharacterization_DEFINED + +#include "include/gpu/GrTypes.h" + +#include "include/core/SkColorSpace.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceProps.h" + +class SkColorSpace; + +#if SK_SUPPORT_GPU +#include "include/gpu/GrBackendSurface.h" +// TODO: remove the GrContext.h include once Flutter is updated +#include "include/gpu/GrContext.h" +#include "include/gpu/GrContextThreadSafeProxy.h" + +/** \class SkSurfaceCharacterization + A surface characterization contains all the information Ganesh requires to makes its internal + rendering decisions. When passed into a SkDeferredDisplayListRecorder it will copy the + data and pass it on to the SkDeferredDisplayList if/when it is created. Note that both of + those objects (the Recorder and the DisplayList) will take a ref on the + GrContextThreadSafeProxy and SkColorSpace objects. +*/ +class SK_API SkSurfaceCharacterization { +public: + enum class Textureable : bool { kNo = false, kYes = true }; + enum class MipMapped : bool { kNo = false, kYes = true }; + enum class UsesGLFBO0 : bool { kNo = false, kYes = true }; + // This flag indicates if the surface is wrapping a raw Vulkan secondary command buffer. + enum class VulkanSecondaryCBCompatible : bool { kNo = false, kYes = true }; + + SkSurfaceCharacterization() + : fCacheMaxResourceBytes(0) + , fOrigin(kBottomLeft_GrSurfaceOrigin) + , fSampleCnt(0) + , fIsTextureable(Textureable::kYes) + , fIsMipMapped(MipMapped::kYes) + , fUsesGLFBO0(UsesGLFBO0::kNo) + , fVulkanSecondaryCBCompatible(VulkanSecondaryCBCompatible::kNo) + , fIsProtected(GrProtected::kNo) + , fSurfaceProps(0, kUnknown_SkPixelGeometry) { + } + + SkSurfaceCharacterization(SkSurfaceCharacterization&&) = default; + SkSurfaceCharacterization& operator=(SkSurfaceCharacterization&&) = default; + + SkSurfaceCharacterization(const SkSurfaceCharacterization&) = default; + SkSurfaceCharacterization& operator=(const SkSurfaceCharacterization& other) = default; + bool operator==(const SkSurfaceCharacterization& other) const; + bool operator!=(const SkSurfaceCharacterization& other) const { + return !(*this == other); + } + + /* + * Return a new surface characterization with the only difference being a different width + * and height + */ + SkSurfaceCharacterization createResized(int width, int height) const; + + /* + * Return a new surface characterization with only a replaced color space + */ + SkSurfaceCharacterization createColorSpace(sk_sp) const; + + GrContextThreadSafeProxy* contextInfo() const { return fContextInfo.get(); } + sk_sp refContextInfo() const { return fContextInfo; } + size_t cacheMaxResourceBytes() const { return fCacheMaxResourceBytes; } + + bool isValid() const { return kUnknown_SkColorType != fImageInfo.colorType(); } + + const SkImageInfo& imageInfo() const { return fImageInfo; } + const GrBackendFormat& backendFormat() const { return fBackendFormat; } + GrSurfaceOrigin origin() const { return fOrigin; } + int width() const { return fImageInfo.width(); } + int height() const { return fImageInfo.height(); } + SkColorType colorType() const { return fImageInfo.colorType(); } + int sampleCount() const { return fSampleCnt; } + bool isTextureable() const { return Textureable::kYes == fIsTextureable; } + bool isMipMapped() const { return MipMapped::kYes == fIsMipMapped; } + bool usesGLFBO0() const { return UsesGLFBO0::kYes == fUsesGLFBO0; } + bool vulkanSecondaryCBCompatible() const { + return VulkanSecondaryCBCompatible::kYes == fVulkanSecondaryCBCompatible; + } + GrProtected isProtected() const { return fIsProtected; } + SkColorSpace* colorSpace() const { return fImageInfo.colorSpace(); } + sk_sp refColorSpace() const { return fImageInfo.refColorSpace(); } + const SkSurfaceProps& surfaceProps()const { return fSurfaceProps; } + + // Is the provided backend texture compatible with this surface characterization? + bool isCompatible(const GrBackendTexture&) const; + +private: + friend class SkSurface_Gpu; // for 'set' & 'config' + friend class GrVkSecondaryCBDrawContext; // for 'set' & 'config' + friend class GrContextThreadSafeProxy; // for private ctor + friend class SkDeferredDisplayListRecorder; // for 'config' + friend class SkSurface; // for 'config' + + SkDEBUGCODE(void validate() const;) + + SkSurfaceCharacterization(sk_sp contextInfo, + size_t cacheMaxResourceBytes, + const SkImageInfo& ii, + const GrBackendFormat& backendFormat, + GrSurfaceOrigin origin, + int sampleCnt, + Textureable isTextureable, + MipMapped isMipMapped, + UsesGLFBO0 usesGLFBO0, + VulkanSecondaryCBCompatible vulkanSecondaryCBCompatible, + GrProtected isProtected, + const SkSurfaceProps& surfaceProps) + : fContextInfo(std::move(contextInfo)) + , fCacheMaxResourceBytes(cacheMaxResourceBytes) + , fImageInfo(ii) + , fBackendFormat(backendFormat) + , fOrigin(origin) + , fSampleCnt(sampleCnt) + , fIsTextureable(isTextureable) + , fIsMipMapped(isMipMapped) + , fUsesGLFBO0(usesGLFBO0) + , fVulkanSecondaryCBCompatible(vulkanSecondaryCBCompatible) + , fIsProtected(isProtected) + , fSurfaceProps(surfaceProps) { + SkDEBUGCODE(this->validate()); + } + + void set(sk_sp contextInfo, + size_t cacheMaxResourceBytes, + const SkImageInfo& ii, + const GrBackendFormat& backendFormat, + GrSurfaceOrigin origin, + int sampleCnt, + Textureable isTextureable, + MipMapped isMipMapped, + UsesGLFBO0 usesGLFBO0, + VulkanSecondaryCBCompatible vulkanSecondaryCBCompatible, + GrProtected isProtected, + const SkSurfaceProps& surfaceProps) { + SkASSERT(MipMapped::kNo == isMipMapped || Textureable::kYes == isTextureable); + SkASSERT(Textureable::kNo == isTextureable || UsesGLFBO0::kNo == usesGLFBO0); + + SkASSERT(VulkanSecondaryCBCompatible::kNo == vulkanSecondaryCBCompatible || + UsesGLFBO0::kNo == usesGLFBO0); + SkASSERT(Textureable::kNo == isTextureable || + VulkanSecondaryCBCompatible::kNo == vulkanSecondaryCBCompatible); + + fContextInfo = contextInfo; + fCacheMaxResourceBytes = cacheMaxResourceBytes; + + fImageInfo = ii; + fBackendFormat = backendFormat; + fOrigin = origin; + fSampleCnt = sampleCnt; + fIsTextureable = isTextureable; + fIsMipMapped = isMipMapped; + fUsesGLFBO0 = usesGLFBO0; + fVulkanSecondaryCBCompatible = vulkanSecondaryCBCompatible; + fIsProtected = isProtected; + fSurfaceProps = surfaceProps; + + SkDEBUGCODE(this->validate()); + } + + sk_sp fContextInfo; + size_t fCacheMaxResourceBytes; + + SkImageInfo fImageInfo; + GrBackendFormat fBackendFormat; + GrSurfaceOrigin fOrigin; + int fSampleCnt; + Textureable fIsTextureable; + MipMapped fIsMipMapped; + UsesGLFBO0 fUsesGLFBO0; + VulkanSecondaryCBCompatible fVulkanSecondaryCBCompatible; + GrProtected fIsProtected; + SkSurfaceProps fSurfaceProps; +}; + +#else// !SK_SUPPORT_GPU + +class SK_API SkSurfaceCharacterization { +public: + SkSurfaceCharacterization() : fSurfaceProps(0, kUnknown_SkPixelGeometry) { } + + SkSurfaceCharacterization createResized(int width, int height) const { + return *this; + } + + SkSurfaceCharacterization createColorSpace(sk_sp) const { + return *this; + } + + bool operator==(const SkSurfaceCharacterization& other) const { return false; } + bool operator!=(const SkSurfaceCharacterization& other) const { + return !(*this == other); + } + + size_t cacheMaxResourceBytes() const { return 0; } + + bool isValid() const { return false; } + + int width() const { return 0; } + int height() const { return 0; } + int stencilCount() const { return 0; } + bool isTextureable() const { return false; } + bool isMipMapped() const { return false; } + bool usesGLFBO0() const { return false; } + bool vulkanSecondaryCBCompatible() const { return false; } + SkColorSpace* colorSpace() const { return nullptr; } + sk_sp refColorSpace() const { return nullptr; } + const SkSurfaceProps& surfaceProps()const { return fSurfaceProps; } + +private: + SkSurfaceProps fSurfaceProps; +}; + +#endif + +#endif diff --git a/gfx/skia/skia/include/core/SkSurfaceProps.h b/gfx/skia/skia/include/core/SkSurfaceProps.h new file mode 100644 index 0000000000..6eda6a563d --- /dev/null +++ b/gfx/skia/skia/include/core/SkSurfaceProps.h @@ -0,0 +1,89 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSurfaceProps_DEFINED +#define SkSurfaceProps_DEFINED + +#include "include/core/SkTypes.h" + +/** + * Description of how the LCD strips are arranged for each pixel. If this is unknown, or the + * pixels are meant to be "portable" and/or transformed before showing (e.g. rotated, scaled) + * then use kUnknown_SkPixelGeometry. + */ +enum SkPixelGeometry { + kUnknown_SkPixelGeometry, + kRGB_H_SkPixelGeometry, + kBGR_H_SkPixelGeometry, + kRGB_V_SkPixelGeometry, + kBGR_V_SkPixelGeometry, +}; + +// Returns true iff geo is a known geometry and is RGB. +static inline bool SkPixelGeometryIsRGB(SkPixelGeometry geo) { + return kRGB_H_SkPixelGeometry == geo || kRGB_V_SkPixelGeometry == geo; +} + +// Returns true iff geo is a known geometry and is BGR. +static inline bool SkPixelGeometryIsBGR(SkPixelGeometry geo) { + return kBGR_H_SkPixelGeometry == geo || kBGR_V_SkPixelGeometry == geo; +} + +// Returns true iff geo is a known geometry and is horizontal. +static inline bool SkPixelGeometryIsH(SkPixelGeometry geo) { + return kRGB_H_SkPixelGeometry == geo || kBGR_H_SkPixelGeometry == geo; +} + +// Returns true iff geo is a known geometry and is vertical. +static inline bool SkPixelGeometryIsV(SkPixelGeometry geo) { + return kRGB_V_SkPixelGeometry == geo || kBGR_V_SkPixelGeometry == geo; +} + +/** + * Describes properties and constraints of a given SkSurface. The rendering engine can parse these + * during drawing, and can sometimes optimize its performance (e.g. disabling an expensive + * feature). + */ +class SK_API SkSurfaceProps { +public: + enum Flags { + kUseDeviceIndependentFonts_Flag = 1 << 0, + }; + /** Deprecated alias used by Chromium. Will be removed. */ + static const Flags kUseDistanceFieldFonts_Flag = kUseDeviceIndependentFonts_Flag; + + SkSurfaceProps(uint32_t flags, SkPixelGeometry); + + enum InitType { + kLegacyFontHost_InitType + }; + SkSurfaceProps(InitType); + SkSurfaceProps(uint32_t flags, InitType); + SkSurfaceProps(const SkSurfaceProps& other); + + uint32_t flags() const { return fFlags; } + SkPixelGeometry pixelGeometry() const { return fPixelGeometry; } + + bool isUseDeviceIndependentFonts() const { + return SkToBool(fFlags & kUseDeviceIndependentFonts_Flag); + } + + bool operator==(const SkSurfaceProps& that) const { + return fFlags == that.fFlags && fPixelGeometry == that.fPixelGeometry; + } + + bool operator!=(const SkSurfaceProps& that) const { + return !(*this == that); + } +private: + SkSurfaceProps(); + + uint32_t fFlags; + SkPixelGeometry fPixelGeometry; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkSwizzle.h b/gfx/skia/skia/include/core/SkSwizzle.h new file mode 100644 index 0000000000..61e93b2da7 --- /dev/null +++ b/gfx/skia/skia/include/core/SkSwizzle.h @@ -0,0 +1,19 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSwizzle_DEFINED +#define SkSwizzle_DEFINED + +#include "include/core/SkTypes.h" + +/** + Swizzles byte order of |count| 32-bit pixels, swapping R and B. + (RGBA <-> BGRA) +*/ +SK_API void SkSwapRB(uint32_t* dest, const uint32_t* src, int count); + +#endif diff --git a/gfx/skia/skia/include/core/SkTextBlob.h b/gfx/skia/skia/include/core/SkTextBlob.h new file mode 100644 index 0000000000..de4856efdc --- /dev/null +++ b/gfx/skia/skia/include/core/SkTextBlob.h @@ -0,0 +1,418 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTextBlob_DEFINED +#define SkTextBlob_DEFINED + +#include "include/core/SkFont.h" +#include "include/core/SkPaint.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkString.h" +#include "include/private/SkTemplates.h" + +#include + +struct SkRSXform; +struct SkSerialProcs; +struct SkDeserialProcs; + +/** \class SkTextBlob + SkTextBlob combines multiple text runs into an immutable container. Each text + run consists of glyphs, SkPaint, and position. Only parts of SkPaint related to + fonts and text rendering are used by run. +*/ +class SK_API SkTextBlob final : public SkNVRefCnt { +private: + class RunRecord; + +public: + + /** Returns conservative bounding box. Uses SkPaint associated with each glyph to + determine glyph bounds, and unions all bounds. Returned bounds may be + larger than the bounds of all glyphs in runs. + + @return conservative bounding box + */ + const SkRect& bounds() const { return fBounds; } + + /** Returns a non-zero value unique among all text blobs. + + @return identifier for SkTextBlob + */ + uint32_t uniqueID() const { return fUniqueID; } + + /** Returns the number of intervals that intersect bounds. + bounds describes a pair of lines parallel to the text advance. + The return count is zero or a multiple of two, and is at most twice the number of glyphs in + the the blob. + + Pass nullptr for intervals to determine the size of the interval array. + + Runs within the blob that contain SkRSXform are ignored when computing intercepts. + + @param bounds lower and upper line parallel to the advance + @param intervals returned intersections; may be nullptr + @param paint specifies stroking, SkPathEffect that affects the result; may be nullptr + @return number of intersections; may be zero + */ + int getIntercepts(const SkScalar bounds[2], SkScalar intervals[], + const SkPaint* paint = nullptr) const; + + /** Creates SkTextBlob with a single run. + + font contains attributes used to define the run text. + + When encoding is SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, or + SkTextEncoding::kUTF32, this function uses the default + character-to-glyph mapping from the SkTypeface in font. It does not + perform typeface fallback for characters not found in the SkTypeface. + It does not perform kerning or other complex shaping; glyphs are + positioned based on their default advances. + + @param text character code points or glyphs drawn + @param byteLength byte length of text array + @param font text size, typeface, text scale, and so on, used to draw + @param encoding text encoding used in the text array + @return SkTextBlob constructed from one run + */ + static sk_sp MakeFromText(const void* text, size_t byteLength, const SkFont& font, + SkTextEncoding encoding = SkTextEncoding::kUTF8); + + /** Creates SkTextBlob with a single run. string meaning depends on SkTextEncoding; + by default, string is encoded as UTF-8. + + font contains attributes used to define the run text. + + When encoding is SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, or + SkTextEncoding::kUTF32, this function uses the default + character-to-glyph mapping from the SkTypeface in font. It does not + perform typeface fallback for characters not found in the SkTypeface. + It does not perform kerning or other complex shaping; glyphs are + positioned based on their default advances. + + @param string character code points or glyphs drawn + @param font text size, typeface, text scale, and so on, used to draw + @param encoding text encoding used in the text array + @return SkTextBlob constructed from one run + */ + static sk_sp MakeFromString(const char* string, const SkFont& font, + SkTextEncoding encoding = SkTextEncoding::kUTF8) { + if (!string) { + return nullptr; + } + return MakeFromText(string, strlen(string), font, encoding); + } + + /** Returns a textblob built from a single run of text with x-positions and a single y value. + This is equivalent to using SkTextBlobBuilder and calling allocRunPosH(). + Returns nullptr if byteLength is zero. + + @param text character code points or glyphs drawn (based on encoding) + @param byteLength byte length of text array + @param xpos array of x-positions, must contain values for all of the character points. + @param constY shared y-position for each character point, to be paired with each xpos. + @param font SkFont used for this run + @param encoding specifies the encoding of the text array. + @return new textblob or nullptr + */ + static sk_sp MakeFromPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, const SkFont& font, + SkTextEncoding encoding = SkTextEncoding::kUTF8); + + /** Returns a textblob built from a single run of text with positions. + This is equivalent to using SkTextBlobBuilder and calling allocRunPos(). + Returns nullptr if byteLength is zero. + + @param text character code points or glyphs drawn (based on encoding) + @param byteLength byte length of text array + @param pos array of positions, must contain values for all of the character points. + @param font SkFont used for this run + @param encoding specifies the encoding of the text array. + @return new textblob or nullptr + */ + static sk_sp MakeFromPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkFont& font, + SkTextEncoding encoding = SkTextEncoding::kUTF8); + + static sk_sp MakeFromRSXform(const void* text, size_t byteLength, + const SkRSXform xform[], const SkFont& font, + SkTextEncoding encoding = SkTextEncoding::kUTF8); + + /** Writes data to allow later reconstruction of SkTextBlob. memory points to storage + to receive the encoded data, and memory_size describes the size of storage. + Returns bytes used if provided storage is large enough to hold all data; + otherwise, returns zero. + + procs.fTypefaceProc permits supplying a custom function to encode SkTypeface. + If procs.fTypefaceProc is nullptr, default encoding is used. procs.fTypefaceCtx + may be used to provide user context to procs.fTypefaceProc; procs.fTypefaceProc + is called with a pointer to SkTypeface and user context. + + @param procs custom serial data encoders; may be nullptr + @param memory storage for data + @param memory_size size of storage + @return bytes written, or zero if required storage is larger than memory_size + */ + size_t serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const; + + /** Returns storage containing SkData describing SkTextBlob, using optional custom + encoders. + + procs.fTypefaceProc permits supplying a custom function to encode SkTypeface. + If procs.fTypefaceProc is nullptr, default encoding is used. procs.fTypefaceCtx + may be used to provide user context to procs.fTypefaceProc; procs.fTypefaceProc + is called with a pointer to SkTypeface and user context. + + @param procs custom serial data encoders; may be nullptr + @return storage containing serialized SkTextBlob + */ + sk_sp serialize(const SkSerialProcs& procs) const; + + /** Recreates SkTextBlob that was serialized into data. Returns constructed SkTextBlob + if successful; otherwise, returns nullptr. Fails if size is smaller than + required data length, or if data does not permit constructing valid SkTextBlob. + + procs.fTypefaceProc permits supplying a custom function to decode SkTypeface. + If procs.fTypefaceProc is nullptr, default decoding is used. procs.fTypefaceCtx + may be used to provide user context to procs.fTypefaceProc; procs.fTypefaceProc + is called with a pointer to SkTypeface data, data byte length, and user context. + + @param data pointer for serial data + @param size size of data + @param procs custom serial data decoders; may be nullptr + @return SkTextBlob constructed from data in memory + */ + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs& procs); + + class SK_API Iter { + public: + struct Run { + SkTypeface* fTypeface; + int fGlyphCount; + const uint16_t* fGlyphIndices; + }; + + Iter(const SkTextBlob&); + + /** + * Returns true for each "run" inside the textblob, setting the Run fields (if not null). + * If this returns false, there are no more runs, and the Run parameter will be ignored. + */ + bool next(Run*); + + private: + const RunRecord* fRunRecord; + }; + +private: + friend class SkNVRefCnt; + + enum GlyphPositioning : uint8_t; + + explicit SkTextBlob(const SkRect& bounds); + + ~SkTextBlob(); + + // Memory for objects of this class is created with sk_malloc rather than operator new and must + // be freed with sk_free. + void operator delete(void* p); + void* operator new(size_t); + void* operator new(size_t, void* p); + + static unsigned ScalarsPerGlyph(GlyphPositioning pos); + + // Call when this blob is part of the key to a cache entry. This allows the cache + // to know automatically those entries can be purged when this SkTextBlob is deleted. + void notifyAddedToCache(uint32_t cacheID) const { + fCacheID.store(cacheID); + } + + friend class SkGlyphRunList; + friend class GrTextBlobCache; + friend class SkTextBlobBuilder; + friend class SkTextBlobPriv; + friend class SkTextBlobRunIterator; + + const SkRect fBounds; + const uint32_t fUniqueID; + mutable std::atomic fCacheID; + + SkDEBUGCODE(size_t fStorageSize;) + + // The actual payload resides in externally-managed storage, following the object. + // (see the .cpp for more details) + + typedef SkRefCnt INHERITED; +}; + +/** \class SkTextBlobBuilder + Helper class for constructing SkTextBlob. +*/ +class SK_API SkTextBlobBuilder { +public: + + /** Constructs empty SkTextBlobBuilder. By default, SkTextBlobBuilder has no runs. + + @return empty SkTextBlobBuilder + */ + SkTextBlobBuilder(); + + /** Deletes data allocated internally by SkTextBlobBuilder. + */ + ~SkTextBlobBuilder(); + + /** Returns SkTextBlob built from runs of glyphs added by builder. Returned + SkTextBlob is immutable; it may be copied, but its contents may not be altered. + Returns nullptr if no runs of glyphs were added by builder. + + Resets SkTextBlobBuilder to its initial empty state, allowing it to be + reused to build a new set of runs. + + @return SkTextBlob or nullptr + */ + sk_sp make(); + + /** \struct SkTextBlobBuilder::RunBuffer + RunBuffer supplies storage for glyphs and positions within a run. + + A run is a sequence of glyphs sharing font metrics and positioning. + Each run may position its glyphs in one of three ways: + by specifying where the first glyph is drawn, and allowing font metrics to + determine the advance to subsequent glyphs; by specifying a baseline, and + the position on that baseline for each glyph in run; or by providing SkPoint + array, one per glyph. + */ + struct RunBuffer { + SkGlyphID* glyphs; //!< storage for glyphs in run + SkScalar* pos; //!< storage for positions in run + char* utf8text; //!< reserved for future use + uint32_t* clusters; //!< reserved for future use + + // Helpers, since the "pos" field can be different types (always some number of floats). + SkPoint* points() const { return reinterpret_cast(pos); } + SkRSXform* xforms() const { return reinterpret_cast(pos); } + }; + + /** Returns run with storage for glyphs. Caller must write count glyphs to + RunBuffer::glyphs before next call to SkTextBlobBuilder. + + RunBuffer::utf8text, and RunBuffer::clusters should be ignored. + + Glyphs share metrics in font. + + Glyphs are positioned on a baseline at (x, y), using font metrics to + determine their relative placement. + + bounds defines an optional bounding box, used to suppress drawing when SkTextBlob + bounds does not intersect SkSurface bounds. If bounds is nullptr, SkTextBlob bounds + is computed from (x, y) and RunBuffer::glyphs metrics. + + @param font SkFont used for this run + @param count number of glyphs + @param x horizontal offset within the blob + @param y vertical offset within the blob + @param bounds optional run bounding box + @return writable glyph buffer + */ + const RunBuffer& allocRun(const SkFont& font, int count, SkScalar x, SkScalar y, + const SkRect* bounds = nullptr); + + /** Returns run with storage for glyphs and positions along baseline. Caller must + write count glyphs to RunBuffer::glyphs, and count scalars to RunBuffer::pos; + before next call to SkTextBlobBuilder. + + RunBuffer::utf8text, and RunBuffer::clusters should be ignored. + + Glyphs share metrics in font. + + Glyphs are positioned on a baseline at y, using x-axis positions written by + caller to RunBuffer::pos. + + bounds defines an optional bounding box, used to suppress drawing when SkTextBlob + bounds does not intersect SkSurface bounds. If bounds is nullptr, SkTextBlob bounds + is computed from y, RunBuffer::pos, and RunBuffer::glyphs metrics. + + @param font SkFont used for this run + @param count number of glyphs + @param y vertical offset within the blob + @param bounds optional run bounding box + @return writable glyph buffer and x-axis position buffer + */ + const RunBuffer& allocRunPosH(const SkFont& font, int count, SkScalar y, + const SkRect* bounds = nullptr); + + /** Returns run with storage for glyphs and SkPoint positions. Caller must + write count glyphs to RunBuffer::glyphs, and count SkPoint to RunBuffer::pos; + before next call to SkTextBlobBuilder. + + RunBuffer::utf8text, and RunBuffer::clusters should be ignored. + + Glyphs share metrics in font. + + Glyphs are positioned using SkPoint written by caller to RunBuffer::pos, using + two scalar values for each SkPoint. + + bounds defines an optional bounding box, used to suppress drawing when SkTextBlob + bounds does not intersect SkSurface bounds. If bounds is nullptr, SkTextBlob bounds + is computed from RunBuffer::pos, and RunBuffer::glyphs metrics. + + @param font SkFont used for this run + @param count number of glyphs + @param bounds optional run bounding box + @return writable glyph buffer and SkPoint buffer + */ + const RunBuffer& allocRunPos(const SkFont& font, int count, + const SkRect* bounds = nullptr); + + // RunBuffer.pos points to SkRSXform array + const RunBuffer& allocRunRSXform(const SkFont& font, int count); + +private: + const RunBuffer& allocRunText(const SkFont& font, + int count, + SkScalar x, + SkScalar y, + int textByteCount, + SkString lang, + const SkRect* bounds = nullptr); + const RunBuffer& allocRunTextPosH(const SkFont& font, int count, SkScalar y, + int textByteCount, SkString lang, + const SkRect* bounds = nullptr); + const RunBuffer& allocRunTextPos(const SkFont& font, int count, + int textByteCount, SkString lang, + const SkRect* bounds = nullptr); + const RunBuffer& allocRunRSXform(const SkFont& font, int count, + int textByteCount, SkString lang, + const SkRect* bounds = nullptr); + + void reserve(size_t size); + void allocInternal(const SkFont& font, SkTextBlob::GlyphPositioning positioning, + int count, int textBytes, SkPoint offset, const SkRect* bounds); + bool mergeRun(const SkFont& font, SkTextBlob::GlyphPositioning positioning, + uint32_t count, SkPoint offset); + void updateDeferredBounds(); + + static SkRect ConservativeRunBounds(const SkTextBlob::RunRecord&); + static SkRect TightRunBounds(const SkTextBlob::RunRecord&); + + friend class SkTextBlobPriv; + friend class SkTextBlobBuilderPriv; + + SkAutoTMalloc fStorage; + size_t fStorageSize; + size_t fStorageUsed; + + SkRect fBounds; + int fRunCount; + bool fDeferredBounds; + size_t fLastRun; // index into fStorage + + RunBuffer fCurrentRunBuffer; +}; + +#endif // SkTextBlob_DEFINED diff --git a/gfx/skia/skia/include/core/SkTileMode.h b/gfx/skia/skia/include/core/SkTileMode.h new file mode 100644 index 0000000000..8a9d020958 --- /dev/null +++ b/gfx/skia/skia/include/core/SkTileMode.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTileModes_DEFINED +#define SkTileModes_DEFINED + +#include "include/core/SkTypes.h" + +enum class SkTileMode { + /** + * Replicate the edge color if the shader draws outside of its + * original bounds. + */ + kClamp, + + /** + * Repeat the shader's image horizontally and vertically. + */ + kRepeat, + + /** + * Repeat the shader's image horizontally and vertically, alternating + * mirror images so that adjacent images always seam. + */ + kMirror, + + /** + * Only draw within the original domain, return transparent-black everywhere else. + */ + kDecal, + + kLastTileMode = kDecal, +}; + +static constexpr int kSkTileModeCount = static_cast(SkTileMode::kLastTileMode) + 1; + +#endif diff --git a/gfx/skia/skia/include/core/SkTime.h b/gfx/skia/skia/include/core/SkTime.h new file mode 100644 index 0000000000..1f033ef3ad --- /dev/null +++ b/gfx/skia/skia/include/core/SkTime.h @@ -0,0 +1,62 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkTime_DEFINED +#define SkTime_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMacros.h" + +class SkString; + +/** \class SkTime + Platform-implemented utilities to return time of day, and millisecond counter. +*/ +class SK_API SkTime { +public: + struct DateTime { + int16_t fTimeZoneMinutes; // The number of minutes that GetDateTime() + // is ahead of or behind UTC. + uint16_t fYear; //!< e.g. 2005 + uint8_t fMonth; //!< 1..12 + uint8_t fDayOfWeek; //!< 0..6, 0==Sunday + uint8_t fDay; //!< 1..31 + uint8_t fHour; //!< 0..23 + uint8_t fMinute; //!< 0..59 + uint8_t fSecond; //!< 0..59 + + void toISO8601(SkString* dst) const; + }; + static void GetDateTime(DateTime*); + + static double GetSecs() { return GetNSecs() * 1e-9; } + static double GetMSecs() { return GetNSecs() * 1e-6; } + static double GetNSecs(); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkAutoTime { +public: + // The label is not deep-copied, so its address must remain valid for the + // lifetime of this object + SkAutoTime(const char* label = nullptr) + : fLabel(label) + , fNow(SkTime::GetMSecs()) {} + ~SkAutoTime() { + uint64_t dur = static_cast(SkTime::GetMSecs() - fNow); + SkDebugf("%s %ld\n", fLabel ? fLabel : "", dur); + } +private: + const char* fLabel; + double fNow; +}; +#define SkAutoTime(...) SK_REQUIRE_LOCAL_VAR(SkAutoTime) + +#endif diff --git a/gfx/skia/skia/include/core/SkTraceMemoryDump.h b/gfx/skia/skia/include/core/SkTraceMemoryDump.h new file mode 100644 index 0000000000..10f28b4f01 --- /dev/null +++ b/gfx/skia/skia/include/core/SkTraceMemoryDump.h @@ -0,0 +1,90 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTraceMemoryDump_DEFINED +#define SkTraceMemoryDump_DEFINED + +#include "include/core/SkTypes.h" + +class SkDiscardableMemory; + +/** + * Interface for memory tracing. + * This interface is meant to be passed as argument to the memory dump methods of Skia objects. + * The implementation of this interface is provided by the embedder. + */ +class SK_API SkTraceMemoryDump { +public: + /** + * Enum to specify the level of the requested details for the dump from the Skia objects. + */ + enum LevelOfDetail { + // Dump only the minimal details to get the total memory usage (Usually just the totals). + kLight_LevelOfDetail, + + // Dump the detailed breakdown of the objects in the caches. + kObjectsBreakdowns_LevelOfDetail + }; + + /** + * Appends a new memory dump (i.e. a row) to the trace memory infrastructure. + * If dumpName does not exist yet, a new one is created. Otherwise, a new column is appended to + * the previously created dump. + * Arguments: + * dumpName: an absolute, slash-separated, name for the item being dumped + * e.g., "skia/CacheX/EntryY". + * valueName: a string indicating the name of the column. + * e.g., "size", "active_size", "number_of_objects". + * This string is supposed to be long lived and is NOT copied. + * units: a string indicating the units for the value. + * e.g., "bytes", "objects". + * This string is supposed to be long lived and is NOT copied. + * value: the actual value being dumped. + */ + virtual void dumpNumericValue(const char* dumpName, + const char* valueName, + const char* units, + uint64_t value) = 0; + + virtual void dumpStringValue(const char* /*dumpName*/, + const char* /*valueName*/, + const char* /*value*/) { } + + /** + * Sets the memory backing for an existing dump. + * backingType and backingObjectId are used by the embedder to associate the memory dumped via + * dumpNumericValue with the corresponding dump that backs the memory. + */ + virtual void setMemoryBacking(const char* dumpName, + const char* backingType, + const char* backingObjectId) = 0; + + /** + * Specialization for memory backed by discardable memory. + */ + virtual void setDiscardableMemoryBacking( + const char* dumpName, + const SkDiscardableMemory& discardableMemoryObject) = 0; + + /** + * Returns the type of details requested in the dump. The granularity of the dump is supposed to + * match the LevelOfDetail argument. The level of detail must not affect the total size + * reported, but only granularity of the child entries. + */ + virtual LevelOfDetail getRequestedDetails() const = 0; + + /** + * Returns true if we should dump wrapped objects. Wrapped objects come from outside Skia, and + * may be independently tracked there. + */ + virtual bool shouldDumpWrappedObjects() const { return true; } + +protected: + virtual ~SkTraceMemoryDump() { } +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkTypeface.h b/gfx/skia/skia/include/core/SkTypeface.h new file mode 100644 index 0000000000..77f7445aae --- /dev/null +++ b/gfx/skia/skia/include/core/SkTypeface.h @@ -0,0 +1,455 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTypeface_DEFINED +#define SkTypeface_DEFINED + +#include "include/core/SkFontArguments.h" +#include "include/core/SkFontParameters.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkRect.h" +#include "include/core/SkString.h" +#include "include/private/SkOnce.h" +#include "include/private/SkWeakRefCnt.h" + +class SkData; +class SkDescriptor; +class SkFontData; +class SkFontDescriptor; +class SkScalerContext; +class SkStream; +class SkStreamAsset; +class SkWStream; +struct SkAdvancedTypefaceMetrics; +struct SkScalerContextEffects; +struct SkScalerContextRec; + +typedef uint32_t SkFontID; +/** Machine endian. */ +typedef uint32_t SkFontTableTag; + +/** \class SkTypeface + + The SkTypeface class specifies the typeface and intrinsic style of a font. + This is used in the paint, along with optionally algorithmic settings like + textSize, textSkewX, textScaleX, kFakeBoldText_Mask, to specify + how text appears when drawn (and measured). + + Typeface objects are immutable, and so they can be shared between threads. +*/ +class SK_API SkTypeface : public SkWeakRefCnt { +public: + /** Returns the typeface's intrinsic style attributes. */ + SkFontStyle fontStyle() const { + return fStyle; + } + + /** Returns true if style() has the kBold bit set. */ + bool isBold() const { return fStyle.weight() >= SkFontStyle::kSemiBold_Weight; } + + /** Returns true if style() has the kItalic bit set. */ + bool isItalic() const { return fStyle.slant() != SkFontStyle::kUpright_Slant; } + + /** Returns true if the typeface claims to be fixed-pitch. + * This is a style bit, advance widths may vary even if this returns true. + */ + bool isFixedPitch() const { return fIsFixedPitch; } + + /** Copy into 'coordinates' (allocated by the caller) the design variation coordinates. + * + * @param coordinates the buffer into which to write the design variation coordinates. + * @param coordinateCount the number of entries available through 'coordinates'. + * + * @return The number of axes, or -1 if there is an error. + * If 'coordinates != nullptr' and 'coordinateCount >= numAxes' then 'coordinates' will be + * filled with the variation coordinates describing the position of this typeface in design + * variation space. It is possible the number of axes can be retrieved but actual position + * cannot. + */ + int getVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const; + + /** Copy into 'parameters' (allocated by the caller) the design variation parameters. + * + * @param parameters the buffer into which to write the design variation parameters. + * @param coordinateCount the number of entries available through 'parameters'. + * + * @return The number of axes, or -1 if there is an error. + * If 'parameters != nullptr' and 'parameterCount >= numAxes' then 'parameters' will be + * filled with the variation parameters describing the position of this typeface in design + * variation space. It is possible the number of axes can be retrieved but actual parameters + * cannot. + */ + int getVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const; + + /** Return a 32bit value for this typeface, unique for the underlying font + data. Will never return 0. + */ + SkFontID uniqueID() const { return fUniqueID; } + + /** Return the uniqueID for the specified typeface. If the face is null, + resolve it to the default font and return its uniqueID. Will never + return 0. + */ + static SkFontID UniqueID(const SkTypeface* face); + + /** Returns true if the two typefaces reference the same underlying font, + handling either being null (treating null as the default font) + */ + static bool Equal(const SkTypeface* facea, const SkTypeface* faceb); + + /** Returns the default normal typeface, which is never nullptr. */ + static sk_sp MakeDefault(); + + /** Creates a new reference to the typeface that most closely matches the + requested familyName and fontStyle. This method allows extended font + face specifiers as in the SkFontStyle type. Will never return null. + + @param familyName May be NULL. The name of the font family. + @param fontStyle The style of the typeface. + @return reference to the closest-matching typeface. Call must call + unref() when they are done. + */ + static sk_sp MakeFromName(const char familyName[], SkFontStyle fontStyle); + + /** Return a new typeface given a file. If the file does not exist, or is + not a valid font file, returns nullptr. + */ + static sk_sp MakeFromFile(const char path[], int index = 0); + + /** Return a new typeface given a stream. If the stream is + not a valid font file, returns nullptr. Ownership of the stream is + transferred, so the caller must not reference it again. + */ + static sk_sp MakeFromStream(std::unique_ptr stream, int index = 0); + + /** Return a new typeface given a SkData. If the data is null, or is not a valid font file, + * returns nullptr. + */ + static sk_sp MakeFromData(sk_sp, int index = 0); + + /** Return a new typeface given font data and configuration. If the data + is not valid font data, returns nullptr. + */ + static sk_sp MakeFromFontData(std::unique_ptr); + + /** Return a new typeface based on this typeface but parameterized as specified in the + SkFontArguments. If the SkFontArguments does not supply an argument for a parameter + in the font then the value from this typeface will be used as the value for that + argument. If the cloned typeface would be exaclty the same as this typeface then + this typeface may be ref'ed and returned. May return nullptr on failure. + */ + sk_sp makeClone(const SkFontArguments&) const; + + /** + * A typeface can serialize just a descriptor (names, etc.), or it can also include the + * actual font data (which can be large). This enum controls how serialize() decides what + * to serialize. + */ + enum class SerializeBehavior { + kDoIncludeData, + kDontIncludeData, + kIncludeDataIfLocal, + }; + + /** Write a unique signature to a stream, sufficient to reconstruct a + typeface referencing the same font when Deserialize is called. + */ + void serialize(SkWStream*, SerializeBehavior = SerializeBehavior::kIncludeDataIfLocal) const; + + /** + * Same as serialize(SkWStream*, ...) but returns the serialized data in SkData, instead of + * writing it to a stream. + */ + sk_sp serialize(SerializeBehavior = SerializeBehavior::kIncludeDataIfLocal) const; + + /** Given the data previously written by serialize(), return a new instance + of a typeface referring to the same font. If that font is not available, + return nullptr. + Does not affect ownership of SkStream. + */ + static sk_sp MakeDeserialize(SkStream*); + + /** + * Given an array of UTF32 character codes, return their corresponding glyph IDs. + * + * @param chars pointer to the array of UTF32 chars + * @param number of chars and glyphs + * @param glyphs returns the corresponding glyph IDs for each character. + */ + void unicharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const; + + /** + * Return the glyphID that corresponds to the specified unicode code-point + * (in UTF32 encoding). If the unichar is not supported, returns 0. + * + * This is a short-cut for calling unicharsToGlyphs(). + */ + SkGlyphID unicharToGlyph(SkUnichar unichar) const; + + /** + * Return the number of glyphs in the typeface. + */ + int countGlyphs() const; + + // Table getters -- may fail if the underlying font format is not organized + // as 4-byte tables. + + /** Return the number of tables in the font. */ + int countTables() const; + + /** Copy into tags[] (allocated by the caller) the list of table tags in + * the font, and return the number. This will be the same as CountTables() + * or 0 if an error occured. If tags == NULL, this only returns the count + * (the same as calling countTables()). + */ + int getTableTags(SkFontTableTag tags[]) const; + + /** Given a table tag, return the size of its contents, or 0 if not present + */ + size_t getTableSize(SkFontTableTag) const; + + /** Copy the contents of a table into data (allocated by the caller). Note + * that the contents of the table will be in their native endian order + * (which for most truetype tables is big endian). If the table tag is + * not found, or there is an error copying the data, then 0 is returned. + * If this happens, it is possible that some or all of the memory pointed + * to by data may have been written to, even though an error has occured. + * + * @param tag The table tag whose contents are to be copied + * @param offset The offset in bytes into the table's contents where the + * copy should start from. + * @param length The number of bytes, starting at offset, of table data + * to copy. + * @param data storage address where the table contents are copied to + * @return the number of bytes actually copied into data. If offset+length + * exceeds the table's size, then only the bytes up to the table's + * size are actually copied, and this is the value returned. If + * offset > the table's size, or tag is not a valid table, + * then 0 is returned. + */ + size_t getTableData(SkFontTableTag tag, size_t offset, size_t length, + void* data) const; + + /** + * Return an immutable copy of the requested font table, or nullptr if that table was + * not found. This can sometimes be faster than calling getTableData() twice: once to find + * the length, and then again to copy the data. + * + * @param tag The table tag whose contents are to be copied + * @return an immutable copy of the table's data, or nullptr. + */ + sk_sp copyTableData(SkFontTableTag tag) const; + + /** + * Return the units-per-em value for this typeface, or zero if there is an + * error. + */ + int getUnitsPerEm() const; + + /** + * Given a run of glyphs, return the associated horizontal adjustments. + * Adjustments are in "design units", which are integers relative to the + * typeface's units per em (see getUnitsPerEm). + * + * Some typefaces are known to never support kerning. Calling this method + * with all zeros (e.g. getKerningPairAdustments(NULL, 0, NULL)) returns + * a boolean indicating if the typeface might support kerning. If it + * returns false, then it will always return false (no kerning) for all + * possible glyph runs. If it returns true, then it *may* return true for + * somne glyph runs. + * + * If count is non-zero, then the glyphs parameter must point to at least + * [count] valid glyph IDs, and the adjustments parameter must be + * sized to at least [count - 1] entries. If the method returns true, then + * [count-1] entries in the adjustments array will be set. If the method + * returns false, then no kerning should be applied, and the adjustments + * array will be in an undefined state (possibly some values may have been + * written, but none of them should be interpreted as valid values). + */ + bool getKerningPairAdjustments(const SkGlyphID glyphs[], int count, + int32_t adjustments[]) const; + + struct LocalizedString { + SkString fString; + SkString fLanguage; + }; + class LocalizedStrings { + public: + LocalizedStrings() = default; + virtual ~LocalizedStrings() { } + virtual bool next(LocalizedString* localizedString) = 0; + void unref() { delete this; } + + private: + LocalizedStrings(const LocalizedStrings&) = delete; + LocalizedStrings& operator=(const LocalizedStrings&) = delete; + }; + /** + * Returns an iterator which will attempt to enumerate all of the + * family names specified by the font. + * It is the caller's responsibility to unref() the returned pointer. + */ + LocalizedStrings* createFamilyNameIterator() const; + + /** + * Return the family name for this typeface. It will always be returned + * encoded as UTF8, but the language of the name is whatever the host + * platform chooses. + */ + void getFamilyName(SkString* name) const; + + /** + * Return a stream for the contents of the font data, or NULL on failure. + * If ttcIndex is not null, it is set to the TrueTypeCollection index + * of this typeface within the stream, or 0 if the stream is not a + * collection. + * The caller is responsible for deleting the stream. + */ + std::unique_ptr openStream(int* ttcIndex) const; + + /** + * Return the font data, or nullptr on failure. + */ + std::unique_ptr makeFontData() const; + + /** + * Return a scalercontext for the given descriptor. If this fails, then + * if allowFailure is true, this returns NULL, else it returns a + * dummy scalercontext that will not crash, but will draw nothing. + */ + std::unique_ptr createScalerContext(const SkScalerContextEffects&, + const SkDescriptor*, + bool allowFailure = false) const; + + /** + * Return a rectangle (scaled to 1-pt) that represents the union of the bounds of all + * of the glyphs, but each one positioned at (0,). This may be conservatively large, and + * will not take into account any hinting or other size-specific adjustments. + */ + SkRect getBounds() const; + + /*** + * Returns whether this typeface has color glyphs and therefore cannot be + * rendered as a path. e.g. Emojis. + */ + virtual bool hasColorGlyphs() const { return false; } + + // PRIVATE / EXPERIMENTAL -- do not call + void filterRec(SkScalerContextRec* rec) const { + this->onFilterRec(rec); + } + // PRIVATE / EXPERIMENTAL -- do not call + void getFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const { + this->onGetFontDescriptor(desc, isLocal); + } + // PRIVATE / EXPERIMENTAL -- do not call + void* internal_private_getCTFontRef() const { + return this->onGetCTFontRef(); + } + +protected: + /** uniqueID must be unique and non-zero + */ + SkTypeface(const SkFontStyle& style, bool isFixedPitch = false); + virtual ~SkTypeface(); + + virtual sk_sp onMakeClone(const SkFontArguments&) const = 0; + + /** Sets the fixedPitch bit. If used, must be called in the constructor. */ + void setIsFixedPitch(bool isFixedPitch) { fIsFixedPitch = isFixedPitch; } + /** Sets the font style. If used, must be called in the constructor. */ + void setFontStyle(SkFontStyle style) { fStyle = style; } + + virtual SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&, + const SkDescriptor*) const = 0; + virtual void onFilterRec(SkScalerContextRec*) const = 0; + friend class SkScalerContext; // onFilterRec + + // Subclasses *must* override this method to work with the PDF backend. + virtual std::unique_ptr onGetAdvancedMetrics() const = 0; + // For type1 postscript fonts only, set the glyph names for each glyph. + // destination array is non-null, and points to an array of size this->countGlyphs(). + // Backends that do not suport type1 fonts should not override. + virtual void getPostScriptGlyphNames(SkString*) const = 0; + + // The mapping from glyph to Unicode; array indices are glyph ids. + // For each glyph, give the default Unicode value, if it exists. + // dstArray is non-null, and points to an array of size this->countGlyphs(). + virtual void getGlyphToUnicodeMap(SkUnichar* dstArray) const = 0; + + virtual std::unique_ptr onOpenStream(int* ttcIndex) const = 0; + // TODO: make pure virtual. + virtual std::unique_ptr onMakeFontData() const; + + virtual int onGetVariationDesignPosition( + SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const = 0; + + virtual int onGetVariationDesignParameters( + SkFontParameters::Variation::Axis parameters[], int parameterCount) const = 0; + + virtual void onGetFontDescriptor(SkFontDescriptor*, bool* isLocal) const = 0; + + virtual void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const = 0; + virtual int onCountGlyphs() const = 0; + + virtual int onGetUPEM() const = 0; + virtual bool onGetKerningPairAdjustments(const SkGlyphID glyphs[], int count, + int32_t adjustments[]) const; + + /** Returns the family name of the typeface as known by its font manager. + * This name may or may not be produced by the family name iterator. + */ + virtual void onGetFamilyName(SkString* familyName) const = 0; + + /** Returns an iterator over the family names in the font. */ + virtual LocalizedStrings* onCreateFamilyNameIterator() const = 0; + + virtual int onGetTableTags(SkFontTableTag tags[]) const = 0; + virtual size_t onGetTableData(SkFontTableTag, size_t offset, + size_t length, void* data) const = 0; + virtual sk_sp onCopyTableData(SkFontTableTag) const; + + virtual bool onComputeBounds(SkRect*) const; + + virtual void* onGetCTFontRef() const { return nullptr; } + +private: + /** Retrieve detailed typeface metrics. Used by the PDF backend. */ + std::unique_ptr getAdvancedMetrics() const; + friend class SkRandomTypeface; // getAdvancedMetrics + friend class SkPDFFont; // getAdvancedMetrics + + /** Style specifies the intrinsic style attributes of a given typeface */ + enum Style { + kNormal = 0, + kBold = 0x01, + kItalic = 0x02, + + // helpers + kBoldItalic = 0x03 + }; + static SkFontStyle FromOldStyle(Style oldStyle); + static SkTypeface* GetDefaultTypeface(Style style = SkTypeface::kNormal); + + friend class SkFontPriv; // GetDefaultTypeface + friend class SkPaintPriv; // GetDefaultTypeface + friend class SkFont; // getGlyphToUnicodeMap + +private: + SkFontID fUniqueID; + SkFontStyle fStyle; + mutable SkRect fBounds; + mutable SkOnce fBoundsOnce; + bool fIsFixedPitch; + + typedef SkWeakRefCnt INHERITED; +}; +#endif diff --git a/gfx/skia/skia/include/core/SkTypes.h b/gfx/skia/skia/include/core/SkTypes.h new file mode 100644 index 0000000000..e0dbbaf7a4 --- /dev/null +++ b/gfx/skia/skia/include/core/SkTypes.h @@ -0,0 +1,226 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTypes_DEFINED +#define SkTypes_DEFINED + +// IWYU pragma: begin_exports +#include "include/core/SkPreConfig.h" +#if defined (SK_USER_CONFIG_HEADER) + #include SK_USER_CONFIG_HEADER +#else + #include "include/config/SkUserConfig.h" +#endif +#include "include/core/SkPostConfig.h" +#include +#include +// IWYU pragma: end_exports + +/** \file SkTypes.h +*/ + +/** Called internally if we hit an unrecoverable error. + The platform implementation must not return, but should either throw + an exception or otherwise exit. +*/ +SK_API extern void sk_abort_no_print(void); +SK_API extern bool sk_abort_is_enabled(); + +#ifndef SkDebugf + SK_API void SkDebugf(const char format[], ...); +#endif + +// SkASSERT, SkASSERTF and SkASSERT_RELEASE can be used as stand alone assertion expressions, e.g. +// uint32_t foo(int x) { +// SkASSERT(x > 4); +// return x - 4; +// } +// and are also written to be compatible with constexpr functions: +// constexpr uint32_t foo(int x) { +// return SkASSERT(x > 4), +// x - 4; +// } +#define SkASSERT_RELEASE(cond) \ + static_cast( (cond) ? (void)0 : []{ SK_ABORT("assert(" #cond ")"); }() ) + +#ifdef SK_DEBUG + #define SkASSERT(cond) SkASSERT_RELEASE(cond) + #define SkASSERTF(cond, fmt, ...) static_cast( (cond) ? (void)0 : [&]{ \ + SkDebugf(fmt"\n", __VA_ARGS__); \ + SK_ABORT("assert(" #cond ")"); \ + }() ) + #define SkDEBUGFAIL(message) SK_ABORT(message) + #define SkDEBUGFAILF(fmt, ...) SkASSERTF(false, fmt, ##__VA_ARGS__) + #define SkDEBUGCODE(...) __VA_ARGS__ + #define SkDEBUGF(...) SkDebugf(__VA_ARGS__) + #define SkAssertResult(cond) SkASSERT(cond) +#else + #define SkASSERT(cond) static_cast(0) + #define SkASSERTF(cond, fmt, ...) static_cast(0) + #define SkDEBUGFAIL(message) + #define SkDEBUGFAILF(fmt, ...) + #define SkDEBUGCODE(...) + #define SkDEBUGF(...) + + // unlike SkASSERT, this macro executes its condition in the non-debug build. + // The if is present so that this can be used with functions marked SK_WARN_UNUSED_RESULT. + #define SkAssertResult(cond) if (cond) {} do {} while(false) +#endif + +//////////////////////////////////////////////////////////////////////////////// + +/** Fast type for unsigned 8 bits. Use for parameter passing and local + variables, not for storage +*/ +typedef unsigned U8CPU; + +/** Fast type for unsigned 16 bits. Use for parameter passing and local + variables, not for storage +*/ +typedef unsigned U16CPU; + +/** @return false or true based on the condition +*/ +template static constexpr bool SkToBool(const T& x) { return 0 != x; } + +static constexpr int16_t SK_MaxS16 = INT16_MAX; +static constexpr int16_t SK_MinS16 = -SK_MaxS16; + +static constexpr int32_t SK_MaxS32 = INT32_MAX; +static constexpr int32_t SK_MinS32 = -SK_MaxS32; +static constexpr int32_t SK_NaN32 = INT32_MIN; + +static constexpr int64_t SK_MaxS64 = INT64_MAX; +static constexpr int64_t SK_MinS64 = -SK_MaxS64; + +static inline constexpr int32_t SkLeftShift(int32_t value, int32_t shift) { + return (int32_t) ((uint32_t) value << shift); +} + +static inline constexpr int64_t SkLeftShift(int64_t value, int32_t shift) { + return (int64_t) ((uint64_t) value << shift); +} + +//////////////////////////////////////////////////////////////////////////////// + +/** @return the number of entries in an array (not a pointer) +*/ +template char (&SkArrayCountHelper(T (&array)[N]))[N]; +#define SK_ARRAY_COUNT(array) (sizeof(SkArrayCountHelper(array))) + +//////////////////////////////////////////////////////////////////////////////// + +template static constexpr T SkAlign2(T x) { return (x + 1) >> 1 << 1; } +template static constexpr T SkAlign4(T x) { return (x + 3) >> 2 << 2; } +template static constexpr T SkAlign8(T x) { return (x + 7) >> 3 << 3; } + +template static constexpr bool SkIsAlign2(T x) { return 0 == (x & 1); } +template static constexpr bool SkIsAlign4(T x) { return 0 == (x & 3); } +template static constexpr bool SkIsAlign8(T x) { return 0 == (x & 7); } + +template static constexpr T SkAlignPtr(T x) { + return sizeof(void*) == 8 ? SkAlign8(x) : SkAlign4(x); +} +template static constexpr bool SkIsAlignPtr(T x) { + return sizeof(void*) == 8 ? SkIsAlign8(x) : SkIsAlign4(x); +} + +typedef uint32_t SkFourByteTag; +static inline constexpr SkFourByteTag SkSetFourByteTag(char a, char b, char c, char d) { + return (((uint8_t)a << 24) | ((uint8_t)b << 16) | ((uint8_t)c << 8) | (uint8_t)d); +} + +//////////////////////////////////////////////////////////////////////////////// + +/** 32 bit integer to hold a unicode value +*/ +typedef int32_t SkUnichar; + +/** 16 bit unsigned integer to hold a glyph index +*/ +typedef uint16_t SkGlyphID; + +/** 32 bit value to hold a millisecond duration + Note that SK_MSecMax is about 25 days. +*/ +typedef uint32_t SkMSec; + +/** Maximum representable milliseconds; 24d 20h 31m 23.647s. +*/ +static constexpr SkMSec SK_MSecMax = INT32_MAX; + +/** The generation IDs in Skia reserve 0 has an invalid marker. +*/ +static constexpr uint32_t SK_InvalidGenID = 0; + +/** The unique IDs in Skia reserve 0 has an invalid marker. +*/ +static constexpr uint32_t SK_InvalidUniqueID = 0; + +static inline int32_t SkAbs32(int32_t value) { + SkASSERT(value != SK_NaN32); // The most negative int32_t can't be negated. + if (value < 0) { + value = -value; + } + return value; +} + +template static inline T SkTAbs(T value) { + if (value < 0) { + value = -value; + } + return value; +} + +static inline int32_t SkMax32(int32_t a, int32_t b) { + if (a < b) + a = b; + return a; +} + +static inline int32_t SkMin32(int32_t a, int32_t b) { + if (a > b) + a = b; + return a; +} + +template constexpr const T& SkTMin(const T& a, const T& b) { + return (a < b) ? a : b; +} + +template constexpr const T& SkTMax(const T& a, const T& b) { + return (b < a) ? a : b; +} + +template constexpr const T& SkTClamp(const T& x, const T& lo, const T& hi) { + return (x < lo) ? lo : SkTMin(x, hi); +} + +/** @return value pinned (clamped) between min and max, inclusively. +*/ +template static constexpr const T& SkTPin(const T& value, const T& min, const T& max) { + return SkTMax(SkTMin(value, max), min); +} + +//////////////////////////////////////////////////////////////////////////////// + +/** Indicates whether an allocation should count against a cache budget. +*/ +enum class SkBudgeted : bool { + kNo = false, + kYes = true +}; + +/** Indicates whether a backing store needs to be an exact match or can be + larger than is strictly necessary +*/ +enum class SkBackingFit { + kApprox, + kExact +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkUnPreMultiply.h b/gfx/skia/skia/include/core/SkUnPreMultiply.h new file mode 100644 index 0000000000..b492619d07 --- /dev/null +++ b/gfx/skia/skia/include/core/SkUnPreMultiply.h @@ -0,0 +1,56 @@ + +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + + + +#ifndef SkUnPreMultiply_DEFINED +#define SkUnPreMultiply_DEFINED + +#include "include/core/SkColor.h" + +class SK_API SkUnPreMultiply { +public: + typedef uint32_t Scale; + + // index this table with alpha [0..255] + static const Scale* GetScaleTable() { + return gTable; + } + + static Scale GetScale(U8CPU alpha) { + SkASSERT(alpha <= 255); + return gTable[alpha]; + } + + /** Usage: + + const Scale* table = SkUnPreMultiply::GetScaleTable(); + + for (...) { + unsigned a = ... + SkUnPreMultiply::Scale scale = table[a]; + + red = SkUnPreMultiply::ApplyScale(scale, red); + ... + // now red is unpremultiplied + } + */ + static U8CPU ApplyScale(Scale scale, U8CPU component) { + SkASSERT(component <= 255); + return (scale * component + (1 << 23)) >> 24; + } + + static SkColor PMColorToColor(SkPMColor c); + +private: + static const uint32_t gTable[256]; +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkVertices.h b/gfx/skia/skia/include/core/SkVertices.h new file mode 100644 index 0000000000..c07e2dae63 --- /dev/null +++ b/gfx/skia/skia/include/core/SkVertices.h @@ -0,0 +1,281 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkVertices_DEFINED +#define SkVertices_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkData.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" + +/** + * An immutable set of vertex data that can be used with SkCanvas::drawVertices. + */ +class SK_API SkVertices : public SkNVRefCnt { +public: + // BoneIndices indicates which (of a maximum of 4 bones) a given vertex will interpolate + // between. To indicate that a slot is not used, the convention is to assign the bone index + // to 0. + struct BoneIndices { + uint32_t indices[4]; + + uint32_t& operator[] (int i) { + SkASSERT(i >= 0); + SkASSERT(i < 4); + return indices[i]; + } + + const uint32_t& operator[] (int i) const { + SkASSERT(i >= 0); + SkASSERT(i < 4); + return indices[i]; + } + }; + + // BoneWeights stores the interpolation weight for each of the (maximum of 4) bones a given + // vertex interpolates between. To indicate that a slot is not used, the weight for that + // slot should be 0. + struct BoneWeights { + float weights[4]; + + float& operator[] (int i) { + SkASSERT(i >= 0); + SkASSERT(i < 4); + return weights[i]; + } + + const float& operator[] (int i) const { + SkASSERT(i >= 0); + SkASSERT(i < 4); + return weights[i]; + } + }; + + // Bone stores a 3x2 transformation matrix in column major order: + // | scaleX skewX transX | + // | skewY scaleY transY | + // SkRSXform is insufficient because bones can have non uniform scale. + struct Bone { + float values[6]; + + float& operator[] (int i) { + SkASSERT(i >= 0); + SkASSERT(i < 6); + return values[i]; + } + + const float& operator[] (int i) const { + SkASSERT(i >= 0); + SkASSERT(i < 6); + return values[i]; + } + + SkPoint mapPoint(const SkPoint& point) const { + float x = values[0] * point.x() + values[2] * point.y() + values[4]; + float y = values[1] * point.x() + values[3] * point.y() + values[5]; + return SkPoint::Make(x, y); + } + + SkRect mapRect(const SkRect& rect) const { + SkRect dst = SkRect::MakeEmpty(); + SkPoint quad[4]; + rect.toQuad(quad); + for (int i = 0; i < 4; i ++) { + quad[i] = mapPoint(quad[i]); + } + dst.setBoundsNoCheck(quad, 4); + return dst; + } + }; + + enum VertexMode { + kTriangles_VertexMode, + kTriangleStrip_VertexMode, + kTriangleFan_VertexMode, + + kLast_VertexMode = kTriangleFan_VertexMode, + }; + + /** + * Create a vertices by copying the specified arrays. texs, colors, boneIndices, and + * boneWeights may be nullptr, and indices is ignored if indexCount == 0. + * + * boneIndices and boneWeights must either both be nullptr or both point to valid data. + * If specified, they must both contain 'vertexCount' entries. + */ + static sk_sp MakeCopy(VertexMode mode, int vertexCount, + const SkPoint positions[], + const SkPoint texs[], + const SkColor colors[], + const BoneIndices boneIndices[], + const BoneWeights boneWeights[], + int indexCount, + const uint16_t indices[], + bool isVolatile = true); + + static sk_sp MakeCopy(VertexMode mode, int vertexCount, + const SkPoint positions[], + const SkPoint texs[], + const SkColor colors[], + const BoneIndices boneIndices[], + const BoneWeights boneWeights[], + bool isVolatile = true) { + return MakeCopy(mode, + vertexCount, + positions, + texs, + colors, + boneIndices, + boneWeights, + 0, + nullptr, + isVolatile); + } + + static sk_sp MakeCopy(VertexMode mode, int vertexCount, + const SkPoint positions[], + const SkPoint texs[], + const SkColor colors[], + int indexCount, + const uint16_t indices[], + bool isVolatile = true) { + return MakeCopy(mode, + vertexCount, + positions, + texs, + colors, + nullptr, + nullptr, + indexCount, + indices, + isVolatile); + } + + static sk_sp MakeCopy(VertexMode mode, int vertexCount, + const SkPoint positions[], + const SkPoint texs[], + const SkColor colors[], + bool isVolatile = true) { + return MakeCopy(mode, vertexCount, positions, texs, colors, nullptr, nullptr, isVolatile); + } + + struct Sizes; + + enum BuilderFlags { + kHasTexCoords_BuilderFlag = 1 << 0, + kHasColors_BuilderFlag = 1 << 1, + kHasBones_BuilderFlag = 1 << 2, + kIsNonVolatile_BuilderFlag = 1 << 3, + }; + class Builder { + public: + Builder(VertexMode mode, int vertexCount, int indexCount, uint32_t flags); + + bool isValid() const { return fVertices != nullptr; } + + // if the builder is invalid, these will return 0 + int vertexCount() const; + int indexCount() const; + bool isVolatile() const; + SkPoint* positions(); + SkPoint* texCoords(); // returns null if there are no texCoords + SkColor* colors(); // returns null if there are no colors + BoneIndices* boneIndices(); // returns null if there are no bone indices + BoneWeights* boneWeights(); // returns null if there are no bone weights + uint16_t* indices(); // returns null if there are no indices + + // Detach the built vertices object. After the first call, this will always return null. + sk_sp detach(); + + private: + Builder(VertexMode mode, int vertexCount, int indexCount, bool isVolatile, const Sizes&); + + void init(VertexMode mode, int vertexCount, int indexCount, bool isVolatile, const Sizes&); + + // holds a partially complete object. only completed in detach() + sk_sp fVertices; + // Extra storage for intermediate vertices in the case where the client specifies indexed + // triangle fans. These get converted to indexed triangles when the Builder is finalized. + std::unique_ptr fIntermediateFanIndices; + + friend class SkVertices; + }; + + uint32_t uniqueID() const { return fUniqueID; } + VertexMode mode() const { return fMode; } + const SkRect& bounds() const { return fBounds; } + + bool hasColors() const { return SkToBool(this->colors()); } + bool hasTexCoords() const { return SkToBool(this->texCoords()); } + bool hasBones() const { return SkToBool(this->boneIndices()); } + bool hasIndices() const { return SkToBool(this->indices()); } + + int vertexCount() const { return fVertexCnt; } + const SkPoint* positions() const { return fPositions; } + const SkPoint* texCoords() const { return fTexs; } + const SkColor* colors() const { return fColors; } + + const BoneIndices* boneIndices() const { return fBoneIndices; } + const BoneWeights* boneWeights() const { return fBoneWeights; } + + int indexCount() const { return fIndexCnt; } + const uint16_t* indices() const { return fIndices; } + + bool isVolatile() const { return fIsVolatile; } + + sk_sp applyBones(const Bone bones[], int boneCount) const; + + // returns approximate byte size of the vertices object + size_t approximateSize() const; + + /** + * Recreate a vertices from a buffer previously created by calling encode(). + * Returns null if the data is corrupt or the length is incorrect for the contents. + */ + static sk_sp Decode(const void* buffer, size_t length); + + /** + * Pack the vertices object into a byte buffer. This can be used to recreate the vertices + * by calling Decode() with the buffer. + */ + sk_sp encode() const; + +private: + SkVertices() {} + + // these are needed since we've manually sized our allocation (see Builder::init) + friend class SkNVRefCnt; + void operator delete(void* p); + + static sk_sp Alloc(int vCount, int iCount, uint32_t builderFlags, + size_t* arraySize); + + // we store this first, to pair with the refcnt in our base-class, so we don't have an + // unnecessary pad between it and the (possibly 8-byte aligned) ptrs. + uint32_t fUniqueID; + + // these point inside our allocation, so none of these can be "freed" + SkPoint* fPositions; + SkPoint* fTexs; + SkColor* fColors; + BoneIndices* fBoneIndices; + BoneWeights* fBoneWeights; + uint16_t* fIndices; + + SkRect fBounds; // computed to be the union of the fPositions[] + int fVertexCnt; + int fIndexCnt; + + bool fIsVolatile; + + VertexMode fMode; + // below here is where the actual array data is stored. +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkYUVAIndex.h b/gfx/skia/skia/include/core/SkYUVAIndex.h new file mode 100644 index 0000000000..310c9756dc --- /dev/null +++ b/gfx/skia/skia/include/core/SkYUVAIndex.h @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkYUVAIndex_DEFINED +#define SkYUVAIndex_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkTypes.h" + +/** \struct SkYUVAIndex + Describes from which image source and which channel to read each individual YUVA plane. + + SkYUVAIndex contains a index for which image source to read from and a enum for which channel + to read from. +*/ +struct SK_API SkYUVAIndex { + bool operator==(const SkYUVAIndex& that) const { + return this->fIndex == that.fIndex && this->fChannel == that.fChannel; + } + + bool operator!=(const SkYUVAIndex& that) const { + return !(*this == that); + } + + // Index in the array of SkYUVAIndex + // TODO: rename as Component + enum Index { + kY_Index = 0, + kU_Index = 1, + kV_Index = 2, + kA_Index = 3, + + kLast_Index = kA_Index + }; + static constexpr int kIndexCount = kLast_Index + 1; + + /** The index is a number between -1..3 which defines which image source to read from, where -1 + * means the image source doesn't exist. The assumption is we will always have image sources for + * each of YUV planes, but optionally have image source for A plane. */ + int fIndex; + /** The channel describes from which channel to read the info from. Currently we only deal with + * YUV and NV12 and channel info is ignored. */ + SkColorChannel fChannel; + + static bool AreValidIndices(const SkYUVAIndex yuvaIndices[4], int* numPlanes) { + // Note that 'numPlanes' is always filled in even if the indices are not valid. + // This means it can always be used to process the backing resources (but be careful + // of empty intervening slots). + int maxSlotUsed = -1; + bool used[4] = { false, false, false, false }; + bool valid = true; + for (int i = 0; i < 4; ++i) { + if (yuvaIndices[i].fIndex < 0) { + if (SkYUVAIndex::kA_Index != i) { + valid = false; // only the 'A' plane can be omitted + } + } else if (yuvaIndices[i].fIndex > 3) { + valid = false; // A maximum of four input textures is allowed + } else { + maxSlotUsed = SkTMax(yuvaIndices[i].fIndex, maxSlotUsed); + used[i] = true; + } + } + + // All the used slots should be packed starting at 0 with no gaps + for (int i = 0; i <= maxSlotUsed; ++i) { + if (!used[i]) { + valid = false; + } + } + + *numPlanes = maxSlotUsed + 1; + return valid; + } +}; + +#endif diff --git a/gfx/skia/skia/include/core/SkYUVASizeInfo.h b/gfx/skia/skia/include/core/SkYUVASizeInfo.h new file mode 100644 index 0000000000..b723dc6f28 --- /dev/null +++ b/gfx/skia/skia/include/core/SkYUVASizeInfo.h @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkYUVASizeInfo_DEFINED +#define SkYUVASizeInfo_DEFINED + +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkSize.h" + +struct SK_API SkYUVASizeInfo { + static constexpr auto kMaxCount = 4; + + SkISize fSizes[kMaxCount]; + + /** + * While the widths of the Y, U, V and A planes are not restricted, the + * implementation often requires that the width of the memory allocated + * for each plane be a multiple of 8. + * + * This struct allows us to inform the client how many "widthBytes" + * that we need. Note that we use the new idea of "widthBytes" + * because this idea is distinct from "rowBytes" (used elsewhere in + * Skia). "rowBytes" allow the last row of the allocation to not + * include any extra padding, while, in this case, every single row of + * the allocation must be at least "widthBytes". + */ + size_t fWidthBytes[kMaxCount]; + + /** + * YUVA data often comes from formats like JPEG that support EXIF orientation. + * Code that operates on the raw YUV data often needs to know that orientation. + */ + SkEncodedOrigin fOrigin = kDefault_SkEncodedOrigin; + + bool operator==(const SkYUVASizeInfo& that) const { + for (int i = 0; i < kMaxCount; ++i) { + SkASSERT((!fSizes[i].isEmpty() && fWidthBytes[i]) || + (fSizes[i].isEmpty() && !fWidthBytes[i])); + if (fSizes[i] != that.fSizes[i] || fWidthBytes[i] != that.fWidthBytes[i]) { + return false; + } + } + + return true; + } + + size_t computeTotalBytes() const; + + void computePlanes(void* base, void* planes[kMaxCount]) const; + +}; + +#endif // SkYUVASizeInfo_DEFINED diff --git a/gfx/skia/skia/include/docs/SkPDFDocument.h b/gfx/skia/skia/include/docs/SkPDFDocument.h new file mode 100644 index 0000000000..c208b7f156 --- /dev/null +++ b/gfx/skia/skia/include/docs/SkPDFDocument.h @@ -0,0 +1,202 @@ +// Copyright 2018 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkPDFDocument_DEFINED +#define SkPDFDocument_DEFINED + +#include "include/core/SkDocument.h" + +#include "include/core/SkScalar.h" +#include "include/core/SkString.h" +#include "include/core/SkTime.h" + +class SkExecutor; + +namespace SkPDF { + +/** Table 333 in PDF 32000-1:2008 §14.8.4.2 +*/ +enum class DocumentStructureType { + kDocument, //!< Document + kPart, //!< Part + kArt, //!< Article + kSect, //!< Section + kDiv, //!< Division + kBlockQuote, //!< Block quotation + kCaption, //!< Caption + kTOC, //!< Table of Contents + kTOCI, //!< Table of Contents Item + kIndex, //!< Index + kNonStruct, //!< Nonstructural element + kPrivate, //!< Private element + kH, //!< Heading + kH1, //!< Heading level 1 + kH2, //!< Heading level 2 + kH3, //!< Heading level 3 + kH4, //!< Heading level 4 + kH5, //!< Heading level 5 + kH6, //!< Heading level 6 + kP, //!< Paragraph + kL, //!< List + kLI, //!< List item + kLbl, //!< List item label + kLBody, //!< List item body + kTable, //!< Table + kTR, //!< Table row + kTH, //!< Table header cell + kTD, //!< Table data cell + kTHead, //!< Table header row group + kTBody, //!< Table body row group + kTFoot, //!< table footer row group + kSpan, //!< Span + kQuote, //!< Quotation + kNote, //!< Note + kReference, //!< Reference + kBibEntry, //!< Bibliography entry + kCode, //!< Code + kLink, //!< Link + kAnnot, //!< Annotation + kRuby, //!< Ruby annotation + kRB, //!< Ruby base text + kRT, //!< Ruby annotation text + kRP, //!< Ruby punctuation + kWarichu, //!< Warichu annotation + kWT, //!< Warichu text + kWP, //!< Warichu punctuation + kFigure, //!< Figure + kFormula, //!< Formula + kForm, //!< Form control (not like an HTML FORM element) +}; + +/** A node in a PDF structure tree, giving a semantic representation + of the content. Each node ID is associated with content + by passing the SkCanvas and node ID to SkPDF::SetNodeId() when drawing. + NodeIDs should be unique within each tree. +*/ +struct StructureElementNode { + const StructureElementNode* fChildren = nullptr; + size_t fChildCount; + int fNodeId; + DocumentStructureType fType; +}; + +/** Optional metadata to be passed into the PDF factory function. +*/ +struct Metadata { + /** The document's title. + */ + SkString fTitle; + + /** The name of the person who created the document. + */ + SkString fAuthor; + + /** The subject of the document. + */ + SkString fSubject; + + /** Keywords associated with the document. Commas may be used to delineate + keywords within the string. + */ + SkString fKeywords; + + /** If the document was converted to PDF from another format, + the name of the conforming product that created the + original document from which it was converted. + */ + SkString fCreator; + + /** The product that is converting this document to PDF. + Leave fProducer empty to get the default, correct value. + */ + SkString fProducer; + + /** The date and time the document was created. + The zero default value represents an unknown/unset time. + */ + SkTime::DateTime fCreation = {0, 0, 0, 0, 0, 0, 0, 0}; + + /** The date and time the document was most recently modified. + The zero default value represents an unknown/unset time. + */ + SkTime::DateTime fModified = {0, 0, 0, 0, 0, 0, 0, 0}; + + /** The DPI (pixels-per-inch) at which features without native PDF support + will be rasterized (e.g. draw image with perspective, draw text with + perspective, ...) A larger DPI would create a PDF that reflects the + original intent with better fidelity, but it can make for larger PDF + files too, which would use more memory while rendering, and it would be + slower to be processed or sent online or to printer. + */ + SkScalar fRasterDPI = SK_ScalarDefaultRasterDPI; + + /** If true, include XMP metadata, a document UUID, and sRGB output intent + information. This adds length to the document and makes it + non-reproducable, but are necessary features for PDF/A-2b conformance + */ + bool fPDFA = false; + + /** Encoding quality controls the trade-off between size and quality. By + default this is set to 101 percent, which corresponds to lossless + encoding. If this value is set to a value <= 100, and the image is + opaque, it will be encoded (using JPEG) with that quality setting. + */ + int fEncodingQuality = 101; + + /** An optional tree of structured document tags that provide + a semantic representation of the content. The caller + should retain ownership. + */ + const StructureElementNode* fStructureElementTreeRoot = nullptr; + + /** Executor to handle threaded work within PDF Backend. If this is nullptr, + then all work will be done serially on the main thread. To have worker + threads assist with various tasks, set this to a valid SkExecutor + instance. Currently used for executing Deflate algorithm in parallel. + + If set, the PDF output will be non-reproducible in the order and + internal numbering of objects, but should render the same. + + Experimental. + */ + SkExecutor* fExecutor = nullptr; + + /** Preferred Subsetter. Only respected if both are compiled in. + Experimental. + */ + enum Subsetter { + kHarfbuzz_Subsetter, + kSfntly_Subsetter, + } fSubsetter = kHarfbuzz_Subsetter; +}; + +/** Associate a node ID with subsequent drawing commands in an + SkCanvas. The same node ID can appear in a StructureElementNode + in order to associate a document's structure element tree with + its content. + + A node ID of zero indicates no node ID. + + @param canvas The canvas used to draw to the PDF. + @param nodeId The node ID for subsequent drawing commands. +*/ +SK_API void SetNodeId(SkCanvas* dst, int nodeID); + +/** Create a PDF-backed document, writing the results into a SkWStream. + + PDF pages are sized in point units. 1 pt == 1/72 inch == 127/360 mm. + + @param stream A PDF document will be written to this stream. The document may write + to the stream at anytime during its lifetime, until either close() is + called or the document is deleted. + @param metadata a PDFmetadata object. Any fields may be left empty. + + @returns NULL if there is an error, otherwise a newly created PDF-backed SkDocument. +*/ +SK_API sk_sp MakeDocument(SkWStream* stream, const Metadata& metadata); + +static inline sk_sp MakeDocument(SkWStream* stream) { + return MakeDocument(stream, Metadata()); +} + +} // namespace SkPDF +#endif // SkPDFDocument_DEFINED diff --git a/gfx/skia/skia/include/docs/SkXPSDocument.h b/gfx/skia/skia/include/docs/SkXPSDocument.h new file mode 100644 index 0000000000..5cd0777c9b --- /dev/null +++ b/gfx/skia/skia/include/docs/SkXPSDocument.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkXPSDocument_DEFINED +#define SkXPSDocument_DEFINED + +#include "include/core/SkTypes.h" + +#ifdef SK_BUILD_FOR_WIN + +#include "include/core/SkDocument.h" + +struct IXpsOMObjectFactory; + +namespace SkXPS { + +SK_API sk_sp MakeDocument(SkWStream* stream, + IXpsOMObjectFactory* xpsFactory, + SkScalar dpi = SK_ScalarDefaultRasterDPI); + +} // namespace SkXPS +#endif // SK_BUILD_FOR_WIN +#endif // SkXPSDocument_DEFINED diff --git a/gfx/skia/skia/include/effects/Sk1DPathEffect.h b/gfx/skia/skia/include/effects/Sk1DPathEffect.h new file mode 100644 index 0000000000..34edfaa91f --- /dev/null +++ b/gfx/skia/skia/include/effects/Sk1DPathEffect.h @@ -0,0 +1,77 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk1DPathEffect_DEFINED +#define Sk1DPathEffect_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" + +class SkPathMeasure; + +// This class is not exported to java. +class SK_API Sk1DPathEffect : public SkPathEffect { +public: +protected: + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + + /** Called at the start of each contour, returns the initial offset + into that contour. + */ + virtual SkScalar begin(SkScalar contourLength) const = 0; + /** Called with the current distance along the path, with the current matrix + for the point/tangent at the specified distance. + Return the distance to travel for the next call. If return <= 0, then that + contour is done. + */ + virtual SkScalar next(SkPath* dst, SkScalar dist, SkPathMeasure&) const = 0; + +private: + typedef SkPathEffect INHERITED; +}; + +class SK_API SkPath1DPathEffect : public Sk1DPathEffect { +public: + enum Style { + kTranslate_Style, // translate the shape to each position + kRotate_Style, // rotate the shape about its center + kMorph_Style, // transform each point, and turn lines into curves + + kLastEnum_Style = kMorph_Style, + }; + + /** Dash by replicating the specified path. + @param path The path to replicate (dash) + @param advance The space between instances of path + @param phase distance (mod advance) along path for its initial position + @param style how to transform path at each point (based on the current + position and tangent) + */ + static sk_sp Make(const SkPath& path, SkScalar advance, SkScalar phase, Style); + +protected: + SkPath1DPathEffect(const SkPath& path, SkScalar advance, SkScalar phase, Style); + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath*, const SkPath&, SkStrokeRec*, const SkRect*) const override; + + // overrides from Sk1DPathEffect + SkScalar begin(SkScalar contourLength) const override; + SkScalar next(SkPath*, SkScalar, SkPathMeasure&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkPath1DPathEffect) + + SkPath fPath; // copied from constructor + SkScalar fAdvance; // copied from constructor + SkScalar fInitialOffset; // computed from phase + Style fStyle; // copied from constructor + + typedef Sk1DPathEffect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/Sk2DPathEffect.h b/gfx/skia/skia/include/effects/Sk2DPathEffect.h new file mode 100644 index 0000000000..ea9057dfa1 --- /dev/null +++ b/gfx/skia/skia/include/effects/Sk2DPathEffect.h @@ -0,0 +1,105 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk2DPathEffect_DEFINED +#define Sk2DPathEffect_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" + +class SK_API Sk2DPathEffect : public SkPathEffect { +protected: + /** New virtual, to be overridden by subclasses. + This is called once from filterPath, and provides the + uv parameter bounds for the path. Subsequent calls to + next() will receive u and v values within these bounds, + and then a call to end() will signal the end of processing. + */ + virtual void begin(const SkIRect& uvBounds, SkPath* dst) const; + virtual void next(const SkPoint& loc, int u, int v, SkPath* dst) const; + virtual void end(SkPath* dst) const; + + /** Low-level virtual called per span of locations in the u-direction. + The default implementation calls next() repeatedly with each + location. + */ + virtual void nextSpan(int u, int v, int ucount, SkPath* dst) const; + + const SkMatrix& getMatrix() const { return fMatrix; } + + // protected so that subclasses can call this during unflattening + explicit Sk2DPathEffect(const SkMatrix& mat); + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath*, const SkPath&, SkStrokeRec*, const SkRect*) const override; + +private: + SkMatrix fMatrix, fInverse; + bool fMatrixIsInvertible; + + // illegal + Sk2DPathEffect(const Sk2DPathEffect&); + Sk2DPathEffect& operator=(const Sk2DPathEffect&); + + friend class Sk2DPathEffectBlitter; + typedef SkPathEffect INHERITED; +}; + +class SK_API SkLine2DPathEffect : public Sk2DPathEffect { +public: + static sk_sp Make(SkScalar width, const SkMatrix& matrix) { + if (!(width >= 0)) { + return nullptr; + } + return sk_sp(new SkLine2DPathEffect(width, matrix)); + } + + +protected: + SkLine2DPathEffect(SkScalar width, const SkMatrix& matrix) + : Sk2DPathEffect(matrix), fWidth(width) { + SkASSERT(width >= 0); + } + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + + void nextSpan(int u, int v, int ucount, SkPath*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkLine2DPathEffect) + + SkScalar fWidth; + + typedef Sk2DPathEffect INHERITED; +}; + +class SK_API SkPath2DPathEffect : public Sk2DPathEffect { +public: + /** + * Stamp the specified path to fill the shape, using the matrix to define + * the latice. + */ + static sk_sp Make(const SkMatrix& matrix, const SkPath& path) { + return sk_sp(new SkPath2DPathEffect(matrix, path)); + } + +protected: + SkPath2DPathEffect(const SkMatrix&, const SkPath&); + void flatten(SkWriteBuffer&) const override; + + void next(const SkPoint&, int u, int v, SkPath*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkPath2DPathEffect) + + SkPath fPath; + + typedef Sk2DPathEffect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkAlphaThresholdFilter.h b/gfx/skia/skia/include/effects/SkAlphaThresholdFilter.h new file mode 100644 index 0000000000..cd3be18eec --- /dev/null +++ b/gfx/skia/skia/include/effects/SkAlphaThresholdFilter.h @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAlphaThresholdFilter_DEFINED +#define SkAlphaThresholdFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +class SkRegion; + +// DEPRECATED: Use include/effects/SkImageFilters::AlphaThreshold +class SK_API SkAlphaThresholdFilter { +public: + /** + * Creates an image filter that samples a region. If the sample is inside the + * region the alpha of the image is boosted up to a threshold value. If it is + * outside the region then the alpha is decreased to the threshold value. + * The 0,0 point of the region corresponds to the upper left corner of the + * source image. + */ + static sk_sp Make(const SkRegion& region, SkScalar innerMin, + SkScalar outerMax, sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + + static void RegisterFlattenables(); + +private: + SkAlphaThresholdFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkArithmeticImageFilter.h b/gfx/skia/skia/include/effects/SkArithmeticImageFilter.h new file mode 100644 index 0000000000..2945a131c4 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkArithmeticImageFilter.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkArithmeticImageFilter_DEFINED +#define SkArithmeticImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +struct ArithmeticFPInputs { + ArithmeticFPInputs() { + memset(this, 0, sizeof(*this)); + } + + float k[4]; + bool enforcePMColor; +}; + +// DEPRECATED: Use include/effects/SkImageFilters::Arithmetic +class SK_API SkArithmeticImageFilter { +public: + static sk_sp Make(float k1, float k2, float k3, float k4, bool enforcePMColor, + sk_sp background, + sk_sp foreground, + const SkImageFilter::CropRect* cropRect); + + static void RegisterFlattenables(); + +private: + SkArithmeticImageFilter(); // can't instantiate +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkBlurDrawLooper.h b/gfx/skia/skia/include/effects/SkBlurDrawLooper.h new file mode 100644 index 0000000000..4a1ae83142 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkBlurDrawLooper.h @@ -0,0 +1,24 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkBlurDrawLooper_DEFINED +#define SkBlurDrawLooper_DEFINED + +#include "include/core/SkDrawLooper.h" + +/** + * Draws a shadow of the object (possibly offset), and then draws the original object in + * its original position. + */ +namespace SkBlurDrawLooper { + sk_sp SK_API Make(SkColor4f color, SkColorSpace* cs, + SkScalar sigma, SkScalar dx, SkScalar dy); + sk_sp SK_API Make(SkColor color, SkScalar sigma, SkScalar dx, SkScalar dy); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkBlurImageFilter.h b/gfx/skia/skia/include/effects/SkBlurImageFilter.h new file mode 100644 index 0000000000..72335dee47 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkBlurImageFilter.h @@ -0,0 +1,46 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlurImageFilter_DEFINED +#define SkBlurImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +enum class SkTileMode; + +// DEPRECATED: Use include/effects/SkImageFilters::Blur +class SK_API SkBlurImageFilter { +public: + /*! \enum TileMode + * DEPRECATED: Use SkTileMode instead. */ + enum TileMode { + kClamp_TileMode = 0, /*!< Clamp to the image's edge pixels. */ + /*!< This re-weights the filter so samples outside have no effect */ + kRepeat_TileMode, /*!< Wrap around to the image's opposite edge. */ + kClampToBlack_TileMode, /*!< Fill with transparent black. */ + kLast_TileMode = kClampToBlack_TileMode, + + // TODO: remove kMax - it is non-standard but Chromium uses it + kMax_TileMode = kClampToBlack_TileMode + }; + + static sk_sp Make(SkScalar sigmaX, SkScalar sigmaY, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr, + TileMode tileMode = TileMode::kClampToBlack_TileMode); + // EXPERIMENTAL: kMirror is not yet supported + static sk_sp Make(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkBlurImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkBlurMaskFilter.h b/gfx/skia/skia/include/effects/SkBlurMaskFilter.h new file mode 100644 index 0000000000..1b9319869e --- /dev/null +++ b/gfx/skia/skia/include/effects/SkBlurMaskFilter.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlurMaskFilter_DEFINED +#define SkBlurMaskFilter_DEFINED + +// we include this since our callers will need to at least be able to ref/unref +#include "include/core/SkBlurTypes.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkRect.h" +#include "include/core/SkScalar.h" + +class SkRRect; + +class SK_API SkBlurMaskFilter { +public: +#ifdef SK_SUPPORT_LEGACY_EMBOSSMASKFILTER + /** Create an emboss maskfilter + @param blurSigma standard deviation of the Gaussian blur to apply + before applying lighting (e.g. 3) + @param direction array of 3 scalars [x, y, z] specifying the direction of the light source + @param ambient 0...1 amount of ambient light + @param specular coefficient for specular highlights (e.g. 8) + @return the emboss maskfilter + */ + static sk_sp MakeEmboss(SkScalar blurSigma, const SkScalar direction[3], + SkScalar ambient, SkScalar specular); +#endif +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkColorFilterImageFilter.h b/gfx/skia/skia/include/effects/SkColorFilterImageFilter.h new file mode 100644 index 0000000000..345910fa97 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkColorFilterImageFilter.h @@ -0,0 +1,27 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorFilterImageFilter_DEFINED +#define SkColorFilterImageFilter_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkImageFilter.h" + +// DEPRECATED: Use include/effects/SkImageFilters::ColorFilter +class SK_API SkColorFilterImageFilter { +public: + static sk_sp Make(sk_sp cf, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkColorFilterImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkColorMatrix.h b/gfx/skia/skia/include/effects/SkColorMatrix.h new file mode 100644 index 0000000000..f2b7964b5f --- /dev/null +++ b/gfx/skia/skia/include/effects/SkColorMatrix.h @@ -0,0 +1,66 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorMatrix_DEFINED +#define SkColorMatrix_DEFINED + +#include "include/core/SkTypes.h" +#include + +class SK_API SkColorMatrix { +public: + void setIdentity(); + void setScale(float rScale, float gScale, float bScale, float aScale = 1.0f); + + void setRowMajor(const float src[20]) { + memcpy(fMat, src, sizeof(fMat)); + } + + void getRowMajor(float dst[20]) const { + memcpy(dst, fMat, sizeof(fMat)); + } + + enum Axis { + kR_Axis = 0, + kG_Axis = 1, + kB_Axis = 2 + }; + void setRotate(Axis, float degrees); + void setSinCos(Axis, float sine, float cosine); + void preRotate(Axis, float degrees); + void postRotate(Axis, float degrees); + void postTranslate(float dr, float dg, float db, float da); + + void setConcat(const SkColorMatrix& a, const SkColorMatrix& b); + void preConcat(const SkColorMatrix& mat) { this->setConcat(*this, mat); } + void postConcat(const SkColorMatrix& mat) { this->setConcat(mat, *this); } + + void setSaturation(float sat); + void setRGB2YUV(); + void setYUV2RGB(); + + bool operator==(const SkColorMatrix& other) const { + return 0 == memcmp(fMat, other.fMat, sizeof(fMat)); + } + + bool operator!=(const SkColorMatrix& other) const { return !((*this) == other); } + + float* get20(float m[20]) const { + memcpy(m, fMat, sizeof(fMat)); + return m; + } + void set20(const float m[20]) { + memcpy(fMat, m, sizeof(fMat)); + } + +private: + float fMat[20]; + + friend class SkColorFilters; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkColorMatrixFilter.h b/gfx/skia/skia/include/effects/SkColorMatrixFilter.h new file mode 100644 index 0000000000..144c9685f4 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkColorMatrixFilter.h @@ -0,0 +1,25 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorMatrixFilter_DEFINED +#define SkColorMatrixFilter_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/effects/SkColorMatrix.h" + +class SK_API SkColorMatrixFilter : public SkColorFilter { +public: + /** + * Create a colorfilter that multiplies the RGB channels by one color, and + * then adds a second color, pinning the result for each component to + * [0..255]. The alpha components of the mul and add arguments + * are ignored. + */ + static sk_sp MakeLightingFilter(SkColor mul, SkColor add); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkComposeImageFilter.h b/gfx/skia/skia/include/effects/SkComposeImageFilter.h new file mode 100644 index 0000000000..a2f51e2d20 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkComposeImageFilter.h @@ -0,0 +1,24 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkComposeImageFilter_DEFINED +#define SkComposeImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +// DEPRECATED: Use include/effects/SkImageFilters::Compose +class SK_API SkComposeImageFilter { +public: + static sk_sp Make(sk_sp outer, sk_sp inner); + + static void RegisterFlattenables(); + +private: + SkComposeImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkCornerPathEffect.h b/gfx/skia/skia/include/effects/SkCornerPathEffect.h new file mode 100644 index 0000000000..ac041b588d --- /dev/null +++ b/gfx/skia/skia/include/effects/SkCornerPathEffect.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCornerPathEffect_DEFINED +#define SkCornerPathEffect_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkPathEffect.h" + +/** \class SkCornerPathEffect + + SkCornerPathEffect is a subclass of SkPathEffect that can turn sharp corners + into various treatments (e.g. rounded corners) +*/ +class SK_API SkCornerPathEffect : public SkPathEffect { +public: + /** radius must be > 0 to have an effect. It specifies the distance from each corner + that should be "rounded". + */ + static sk_sp Make(SkScalar radius) { + return radius > 0 ? sk_sp(new SkCornerPathEffect(radius)) : nullptr; + } + +protected: + ~SkCornerPathEffect() override; + + explicit SkCornerPathEffect(SkScalar radius); + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkCornerPathEffect) + + SkScalar fRadius; + + typedef SkPathEffect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkDashPathEffect.h b/gfx/skia/skia/include/effects/SkDashPathEffect.h new file mode 100644 index 0000000000..d6ca9122e6 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkDashPathEffect.h @@ -0,0 +1,39 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDashPathEffect_DEFINED +#define SkDashPathEffect_DEFINED + +#include "include/core/SkPathEffect.h" + +class SK_API SkDashPathEffect { +public: + /** intervals: array containing an even number of entries (>=2), with + the even indices specifying the length of "on" intervals, and the odd + indices specifying the length of "off" intervals. This array will be + copied in Make, and can be disposed of freely after. + count: number of elements in the intervals array + phase: offset into the intervals array (mod the sum of all of the + intervals). + + For example: if intervals[] = {10, 20}, count = 2, and phase = 25, + this will set up a dashed path like so: + 5 pixels off + 10 pixels on + 20 pixels off + 10 pixels on + 20 pixels off + ... + A phase of -5, 25, 55, 85, etc. would all result in the same path, + because the sum of all the intervals is 30. + + Note: only affects stroked paths. + */ + static sk_sp Make(const SkScalar intervals[], int count, SkScalar phase); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkDiscretePathEffect.h b/gfx/skia/skia/include/effects/SkDiscretePathEffect.h new file mode 100644 index 0000000000..007844be85 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkDiscretePathEffect.h @@ -0,0 +1,53 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDiscretePathEffect_DEFINED +#define SkDiscretePathEffect_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkPathEffect.h" + +/** \class SkDiscretePathEffect + + This path effect chops a path into discrete segments, and randomly displaces them. +*/ +class SK_API SkDiscretePathEffect : public SkPathEffect { +public: + /** Break the path into segments of segLength length, and randomly move the endpoints + away from the original path by a maximum of deviation. + Note: works on filled or framed paths + + @param seedAssist This is a caller-supplied seedAssist that modifies + the seed value that is used to randomize the path + segments' endpoints. If not supplied it defaults to 0, + in which case filtering a path multiple times will + result in the same set of segments (this is useful for + testing). If a caller does not want this behaviour + they can pass in a different seedAssist to get a + different set of path segments. + */ + static sk_sp Make(SkScalar segLength, SkScalar dev, uint32_t seedAssist = 0); + +protected: + SkDiscretePathEffect(SkScalar segLength, + SkScalar deviation, + uint32_t seedAssist); + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkDiscretePathEffect) + + SkScalar fSegLength, fPerterb; + + /* Caller-supplied 32 bit seed assist */ + uint32_t fSeedAssist; + + typedef SkPathEffect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkDisplacementMapEffect.h b/gfx/skia/skia/include/effects/SkDisplacementMapEffect.h new file mode 100644 index 0000000000..35f0d4d4b1 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkDisplacementMapEffect.h @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDisplacementMapEffect_DEFINED +#define SkDisplacementMapEffect_DEFINED + +#include "include/core/SkImageFilter.h" + +enum class SkColorChannel; + +// DEPRECATED: Use include/effects/SkImageFilters::DisplacementMap +class SK_API SkDisplacementMapEffect { +public: + + // DEPRECATED - Use SkColorChannel instead. + enum ChannelSelectorType { + kUnknown_ChannelSelectorType, + kR_ChannelSelectorType, + kG_ChannelSelectorType, + kB_ChannelSelectorType, + kA_ChannelSelectorType, + + kLast_ChannelSelectorType = kA_ChannelSelectorType + }; + + static sk_sp Make(ChannelSelectorType xChannelSelector, + ChannelSelectorType yChannelSelector, + SkScalar scale, + sk_sp displacement, + sk_sp color, + const SkImageFilter::CropRect* cropRect = nullptr); + static sk_sp Make(SkColorChannel xChannelSelector, + SkColorChannel yChannelSelector, + SkScalar scale, + sk_sp displacement, + sk_sp color, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkDisplacementMapEffect() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkDropShadowImageFilter.h b/gfx/skia/skia/include/effects/SkDropShadowImageFilter.h new file mode 100644 index 0000000000..9d5160f69b --- /dev/null +++ b/gfx/skia/skia/include/effects/SkDropShadowImageFilter.h @@ -0,0 +1,38 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDropShadowImageFilter_DEFINED +#define SkDropShadowImageFilter_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkScalar.h" + +// DEPRECATED: Use include/effects/SkImageFilters::DropShadow and DropShadowOnly +class SK_API SkDropShadowImageFilter { +public: + enum ShadowMode { + kDrawShadowAndForeground_ShadowMode, + kDrawShadowOnly_ShadowMode, + + kLast_ShadowMode = kDrawShadowOnly_ShadowMode + }; + + static const int kShadowModeCount = kLast_ShadowMode+1; + + static sk_sp Make(SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, + SkColor color, ShadowMode shadowMode, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkDropShadowImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkGradientShader.h b/gfx/skia/skia/include/effects/SkGradientShader.h new file mode 100644 index 0000000000..daa6ed0808 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkGradientShader.h @@ -0,0 +1,259 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGradientShader_DEFINED +#define SkGradientShader_DEFINED + +#include "include/core/SkShader.h" + +/** \class SkGradientShader + + SkGradientShader hosts factories for creating subclasses of SkShader that + render linear and radial gradients. In general, degenerate cases should not + produce surprising results, but there are several types of degeneracies: + + * A linear gradient made from the same two points. + * A radial gradient with a radius of zero. + * A sweep gradient where the start and end angle are the same. + * A two point conical gradient where the two centers and the two radii are + the same. + + For any degenerate gradient with a decal tile mode, it will draw empty since the interpolating + region is zero area and the outer region is discarded by the decal mode. + + For any degenerate gradient with a repeat or mirror tile mode, it will draw a solid color that + is the average gradient color, since infinitely many repetitions of the gradients will fill the + shape. + + For a clamped gradient, every type is well-defined at the limit except for linear gradients. The + radial gradient with zero radius becomes the last color. The sweep gradient draws the sector + from 0 to the provided angle with the first color, with a hardstop switching to the last color. + When the provided angle is 0, this is just the solid last color again. Similarly, the two point + conical gradient becomes a circle filled with the first color, sized to the provided radius, + with a hardstop switching to the last color. When the two radii are both zero, this is just the + solid last color. + + As a linear gradient approaches the degenerate case, its shader will approach the appearance of + two half planes, each filled by the first and last colors of the gradient. The planes will be + oriented perpendicular to the vector between the two defining points of the gradient. However, + once they become the same point, Skia cannot reconstruct what that expected orientation is. To + provide a stable and predictable color in this case, Skia just uses the last color as a solid + fill to be similar to many of the other degenerate gradients' behaviors in clamp mode. +*/ +class SK_API SkGradientShader { +public: + enum Flags { + /** By default gradients will interpolate their colors in unpremul space + * and then premultiply each of the results. By setting this flag, the + * gradients will premultiply their colors first, and then interpolate + * between them. + */ + kInterpolateColorsInPremul_Flag = 1 << 0, + }; + + /** Returns a shader that generates a linear gradient between the two specified points. +

+ @param pts The start and end points for the gradient. + @param colors The array[count] of colors, to be distributed between the two points + @param pos May be NULL. array[count] of SkScalars, or NULL, of the relative position of + each corresponding color in the colors array. If this is NULL, + the the colors are distributed evenly between the start and end point. + If this is not null, the values must begin with 0, end with 1.0, and + intermediate values must be strictly increasing. + @param count Must be >=2. The number of colors (and pos if not NULL) entries. + @param mode The tiling mode + */ + static sk_sp MakeLinear(const SkPoint pts[2], + const SkColor colors[], const SkScalar pos[], int count, + SkTileMode mode, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeLinear(const SkPoint pts[2], + const SkColor colors[], const SkScalar pos[], int count, + SkTileMode mode) { + return MakeLinear(pts, colors, pos, count, mode, 0, nullptr); + } + + /** Returns a shader that generates a linear gradient between the two specified points. +

+ @param pts The start and end points for the gradient. + @param colors The array[count] of colors, to be distributed between the two points + @param pos May be NULL. array[count] of SkScalars, or NULL, of the relative position of + each corresponding color in the colors array. If this is NULL, + the the colors are distributed evenly between the start and end point. + If this is not null, the values must begin with 0, end with 1.0, and + intermediate values must be strictly increasing. + @param count Must be >=2. The number of colors (and pos if not NULL) entries. + @param mode The tiling mode + */ + static sk_sp MakeLinear(const SkPoint pts[2], + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count, SkTileMode mode, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeLinear(const SkPoint pts[2], + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count, SkTileMode mode) { + return MakeLinear(pts, colors, std::move(colorSpace), pos, count, mode, 0, nullptr); + } + + /** Returns a shader that generates a radial gradient given the center and radius. +

+ @param center The center of the circle for this gradient + @param radius Must be positive. The radius of the circle for this gradient + @param colors The array[count] of colors, to be distributed between the center and edge of the circle + @param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative position of + each corresponding color in the colors array. If this is NULL, + the the colors are distributed evenly between the center and edge of the circle. + If this is not null, the values must begin with 0, end with 1.0, and + intermediate values must be strictly increasing. + @param count Must be >= 2. The number of colors (and pos if not NULL) entries + @param mode The tiling mode + */ + static sk_sp MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor colors[], const SkScalar pos[], int count, + SkTileMode mode, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor colors[], const SkScalar pos[], int count, + SkTileMode mode) { + return MakeRadial(center, radius, colors, pos, count, mode, 0, nullptr); + } + + /** Returns a shader that generates a radial gradient given the center and radius. +

+ @param center The center of the circle for this gradient + @param radius Must be positive. The radius of the circle for this gradient + @param colors The array[count] of colors, to be distributed between the center and edge of the circle + @param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative position of + each corresponding color in the colors array. If this is NULL, + the the colors are distributed evenly between the center and edge of the circle. + If this is not null, the values must begin with 0, end with 1.0, and + intermediate values must be strictly increasing. + @param count Must be >= 2. The number of colors (and pos if not NULL) entries + @param mode The tiling mode + */ + static sk_sp MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count, SkTileMode mode, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count, SkTileMode mode) { + return MakeRadial(center, radius, colors, std::move(colorSpace), pos, count, mode, + 0, nullptr); + } + + /** + * Returns a shader that generates a conical gradient given two circles, or + * returns NULL if the inputs are invalid. The gradient interprets the + * two circles according to the following HTML spec. + * http://dev.w3.org/html5/2dcontext/#dom-context-2d-createradialgradient + */ + static sk_sp MakeTwoPointConical(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + const SkColor colors[], const SkScalar pos[], + int count, SkTileMode mode, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeTwoPointConical(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + const SkColor colors[], const SkScalar pos[], + int count, SkTileMode mode) { + return MakeTwoPointConical(start, startRadius, end, endRadius, colors, pos, count, mode, + 0, nullptr); + } + + /** + * Returns a shader that generates a conical gradient given two circles, or + * returns NULL if the inputs are invalid. The gradient interprets the + * two circles according to the following HTML spec. + * http://dev.w3.org/html5/2dcontext/#dom-context-2d-createradialgradient + */ + static sk_sp MakeTwoPointConical(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + const SkColor4f colors[], + sk_sp colorSpace, const SkScalar pos[], + int count, SkTileMode mode, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeTwoPointConical(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + const SkColor4f colors[], + sk_sp colorSpace, const SkScalar pos[], + int count, SkTileMode mode) { + return MakeTwoPointConical(start, startRadius, end, endRadius, colors, + std::move(colorSpace), pos, count, mode, 0, nullptr); + } + + /** Returns a shader that generates a sweep gradient given a center. +

+ @param cx The X coordinate of the center of the sweep + @param cx The Y coordinate of the center of the sweep + @param colors The array[count] of colors, to be distributed around the center, within + the gradient angle range. + @param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative + position of each corresponding color in the colors array. If this is + NULL, then the colors are distributed evenly within the angular range. + If this is not null, the values must begin with 0, end with 1.0, and + intermediate values must be strictly increasing. + @param count Must be >= 2. The number of colors (and pos if not NULL) entries + @param mode Tiling mode: controls drawing outside of the gradient angular range. + @param startAngle Start of the angular range, corresponding to pos == 0. + @param endAngle End of the angular range, corresponding to pos == 1. + */ + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], const SkScalar pos[], int count, + SkTileMode mode, + SkScalar startAngle, SkScalar endAngle, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], const SkScalar pos[], int count, + uint32_t flags, const SkMatrix* localMatrix) { + return MakeSweep(cx, cy, colors, pos, count, SkTileMode::kClamp, 0, 360, flags, + localMatrix); + } + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], const SkScalar pos[], int count) { + return MakeSweep(cx, cy, colors, pos, count, 0, nullptr); + } + + /** Returns a shader that generates a sweep gradient given a center. +

+ @param cx The X coordinate of the center of the sweep + @param cx The Y coordinate of the center of the sweep + @param colors The array[count] of colors, to be distributed around the center, within + the gradient angle range. + @param pos May be NULL. The array[count] of SkScalars, or NULL, of the relative + position of each corresponding color in the colors array. If this is + NULL, then the colors are distributed evenly within the angular range. + If this is not null, the values must begin with 0, end with 1.0, and + intermediate values must be strictly increasing. + @param count Must be >= 2. The number of colors (and pos if not NULL) entries + @param mode Tiling mode: controls drawing outside of the gradient angular range. + @param startAngle Start of the angular range, corresponding to pos == 0. + @param endAngle End of the angular range, corresponding to pos == 1. + */ + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count, + SkTileMode mode, + SkScalar startAngle, SkScalar endAngle, + uint32_t flags, const SkMatrix* localMatrix); + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count, + uint32_t flags, const SkMatrix* localMatrix) { + return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count, + SkTileMode::kClamp, 0, 360, flags, localMatrix); + } + static sk_sp MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int count) { + return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count, 0, nullptr); + } + + static void RegisterFlattenables(); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkHighContrastFilter.h b/gfx/skia/skia/include/effects/SkHighContrastFilter.h new file mode 100644 index 0000000000..a8860c22cc --- /dev/null +++ b/gfx/skia/skia/include/effects/SkHighContrastFilter.h @@ -0,0 +1,84 @@ +/* +* Copyright 2017 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef SkHighContrastFilter_DEFINED +#define SkHighContrastFilter_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkPaint.h" + +/** + * Configuration struct for SkHighContrastFilter. + * + * Provides transformations to improve contrast for users with low vision. + */ +struct SkHighContrastConfig { + enum class InvertStyle { + kNoInvert, + kInvertBrightness, + kInvertLightness, + + kLast = kInvertLightness + }; + + SkHighContrastConfig() { + fGrayscale = false; + fInvertStyle = InvertStyle::kNoInvert; + fContrast = 0.0f; + } + + SkHighContrastConfig(bool grayscale, + InvertStyle invertStyle, + SkScalar contrast) + : fGrayscale(grayscale), + fInvertStyle(invertStyle), + fContrast(contrast) {} + + // Returns true if all of the fields are set within the valid range. + bool isValid() const { + return fInvertStyle >= InvertStyle::kNoInvert && + fInvertStyle <= InvertStyle::kInvertLightness && + fContrast >= -1.0 && + fContrast <= 1.0; + } + + // If true, the color will be converted to grayscale. + bool fGrayscale; + + // Whether to invert brightness, lightness, or neither. + InvertStyle fInvertStyle; + + // After grayscale and inverting, the contrast can be adjusted linearly. + // The valid range is -1.0 through 1.0, where 0.0 is no adjustment. + SkScalar fContrast; +}; + +/** + * Color filter that provides transformations to improve contrast + * for users with low vision. + * + * Applies the following transformations in this order. Each of these + * can be configured using SkHighContrastConfig. + * + * - Conversion to grayscale + * - Color inversion (either in RGB or HSL space) + * - Increasing the resulting contrast. + * + * Calling SkHighContrastFilter::Make will return nullptr if the config is + * not valid, e.g. if you try to call it with a contrast outside the range of + * -1.0 to 1.0. + */ + +class SK_API SkHighContrastFilter { +public: + // Returns the filter, or nullptr if the config is invalid. + static sk_sp Make(const SkHighContrastConfig& config); + + static void RegisterFlattenables(); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkImageFilters.h b/gfx/skia/skia/include/effects/SkImageFilters.h new file mode 100644 index 0000000000..5dd6c2bd43 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkImageFilters.h @@ -0,0 +1,434 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageFilters_DEFINED +#define SkImageFilters_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRect.h" +#include "include/core/SkTileMode.h" + +class SkColorFilter; +class SkPaint; +class SkRegion; + +// A set of factory functions providing useful SkImageFilter effects. For image filters that take an +// input filter, providing nullptr means it will automatically use the dynamic source image. This +// source depends on how the filter is applied, but is either the contents of a saved layer when +// drawing with SkCanvas, or an explicit SkImage if using SkImage::makeWithFilter. +class SK_API SkImageFilters { +public: + /** + * Create a filter that updates the alpha of the image based on 'region'. Pixels inside the + * region are made more opaque and pixels outside are made more transparent. + * + * Specifically, if a pixel is inside the region, its alpha will be set to + * max(innerMin, pixel's alpha). If a pixel is outside the region, its alpha will be updated to + * min(outerMax, pixel's alpha). + * @param region The geometric region controlling the inner and outer alpha thresholds. + * @param innerMin The minimum alpha value for pixels inside 'region'. + * @param outerMax The maximum alpha value for pixels outside of 'region'. + * @param input The input filter, or uses the source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp AlphaThreshold(const SkRegion& region, SkScalar innerMin, + SkScalar outerMax, sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that implements a custom blend mode. Each output pixel is the result of + * combining the corresponding background and foreground pixels using the 4 coefficients: + * k1 * foreground * background + k2 * foreground + k3 * background + k4 + * @param k1, k2, k3, k4 The four coefficients used to combine the foreground and background. + * @param enforcePMColor If true, the RGB channels will be clamped to the calculated alpha. + * @param background The background content, using the source bitmap when this is null. + * @param foreground The foreground content, using the source bitmap when this is null. + * @param cropRect Optional rectangle that crops the inputs and output. + */ + static sk_sp Arithmetic(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4, + bool enforcePMColor, sk_sp background, + sk_sp foreground, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that blurs its input by the separate X and Y sigmas. The provided tile mode + * is used when the blur kernel goes outside the input image. + * @param sigmaX The Gaussian sigma value for blurring along the X axis. + * @param sigmaY The Gaussian sigma value for blurring along the Y axis. + * @param tileMode The tile mode applied at edges . + * TODO (michaelludwig) - kMirror is not supported yet + * @param input The input filter that is blurred, uses source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp Blur(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, + sk_sp input, const SkIRect* cropRect = nullptr); + // As above, but defaults to the decal tile mode. + static sk_sp Blur(SkScalar sigmaX, SkScalar sigmaY, sk_sp input, + const SkIRect* cropRect = nullptr) { + return Blur(sigmaX, sigmaY, SkTileMode::kDecal, std::move(input), cropRect); + } + + /** + * Create a filter that applies the color filter to the input filter results. + * @param cf The color filter that transforms the input image. + * @param input The input filter, or uses the source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp ColorFilter(sk_sp cf, sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that composes 'inner' with 'outer', such that the results of 'inner' are + * treated as the source bitmap passed to 'outer', i.e. result = outer(inner(source)). + * @param outer The outer filter that evaluates the results of inner. + * @param inner The inner filter that produces the input to outer. + */ + static sk_sp Compose(sk_sp outer, sk_sp inner); + + /** + * Create a filter that moves each pixel in its color input based on an (x,y) vector encoded + * in its displacement input filter. Two color components of the displacement image are + * mapped into a vector as scale * (color[xChannel], color[yChannel]), where the channel + * selectors are one of R, G, B, or A. + * @param xChannelSelector RGBA channel that encodes the x displacement per pixel. + * @param yChannelSelector RGBA channel that encodes the y displacement per pixel. + * @param scale Scale applied to displacement extracted from image. + * @param displacement The filter defining the displacement image, or null to use source. + * @param color The filter providing the color pixels to be displaced. + * @param cropRect Optional rectangle that crops the color input and output. + */ + static sk_sp DisplacementMap(SkColorChannel xChannelSelector, + SkColorChannel yChannelSelector, + SkScalar scale, sk_sp displacement, + sk_sp color, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that draws a drop shadow under the input content. This filter produces an + * image that includes the inputs' content. + * @param dx The X offset of the shadow. + * @param dy The Y offset of the shadow. + * @param sigmaX The blur radius for the shadow, along the X axis. + * @param sigmaY The blur radius for the shadow, along the Y axis. + * @param color The color of the drop shadow. + * @param input The input filter, or will use the source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp DropShadow(SkScalar dx, SkScalar dy, + SkScalar sigmaX, SkScalar sigmaY, + SkColor color, sk_sp input, + const SkIRect* cropRect = nullptr); + /** + * Create a filter that renders a drop shadow, in exactly the same manner as ::DropShadow, + * except that the resulting image does not include the input content. This allows the shadow + * and input to be composed by a filter DAG in a more flexible manner. + * @param dx The X offset of the shadow. + * @param dy The Y offset of the shadow. + * @param sigmaX The blur radius for the shadow, along the X axis. + * @param sigmaY The blur radius for the shadow, along the Y axis. + * @param color The color of the drop shadow. + * @param input The input filter, or will use the source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp DropShadowOnly(SkScalar dx, SkScalar dy, + SkScalar sigmaX, SkScalar sigmaY, + SkColor color, sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that draws the 'srcRect' portion of image into 'dstRect' using the given + * filter quality. Similar to SkCanvas::drawImageRect. Returns null if 'image' is null. + * @param image The image that is output by the filter, subset by 'srcRect'. + * @param srcRect The source pixels sampled into 'dstRect' + * @param dstRect The local rectangle to draw the image into. + * @param filterQuality The filter quality that is used when sampling the image. + */ + static sk_sp Image(sk_sp image, const SkRect& srcRect, + const SkRect& dstRect, SkFilterQuality filterQuality); + /** + * Create a filter that produces the image contents. + * @param image The image that is output by the filter. + */ + static sk_sp Image(sk_sp image) { + // Defaults to kHigh_SkFilterQuality because the dstRect of the image filter will be mapped + // by the layer matrix set during filtering. If that has a scale factor, then the image + // will not be drawn at a 1-to-1 pixel scale, even that is what this appears to create here. + SkRect r = image ? SkRect::MakeWH(image->width(), image->height()) : SkRect::MakeEmpty(); + return Image(std::move(image), r, r, kHigh_SkFilterQuality); + } + + /** + * Create a filter that mimics a zoom/magnifying lens effect. + * @param srcRect + * @param inset + * @param input The input filter that is magnified, if null the source bitmap is used. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp Magnifier(const SkRect& srcRect, SkScalar inset, + sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that applies an NxM image processing kernel to the input image. This can be + * used to produce effects such as sharpening, blurring, edge detection, etc. + * @param kernelSize The kernel size in pixels, in each dimension (N by M). + * @param kernel The image processing kernel. Must contain N * M elements, in row order. + * @param gain A scale factor applied to each pixel after convolution. This can be + * used to normalize the kernel, if it does not already sum to 1. + * @param bias A bias factor added to each pixel after convolution. + * @param kernelOffset An offset applied to each pixel coordinate before convolution. + * This can be used to center the kernel over the image + * (e.g., a 3x3 kernel should have an offset of {1, 1}). + * @param tileMode How accesses outside the image are treated. + * TODO (michaelludwig) - kMirror is not supported yet + * @param convolveAlpha If true, all channels are convolved. If false, only the RGB channels + * are convolved, and alpha is copied from the source image. + * @param input The input image filter, if null the source bitmap is used instead. + * @param cropRect Optional rectangle to which the output processing will be limited. + */ + static sk_sp MatrixConvolution(const SkISize& kernelSize, + const SkScalar kernel[], SkScalar gain, + SkScalar bias, const SkIPoint& kernelOffset, + SkTileMode tileMode, bool convolveAlpha, + sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that transforms the input image by 'matrix'. This matrix transforms the + * local space, which means it effectively happens prior to any transformation coming from the + * SkCanvas initiating the filtering. + * @param matrix The matrix to apply to the original content. + * @param filterQuality The filter quality to use when sampling the input image. + * @param input The image filter to transform, or null to use the source image. + */ + static sk_sp MatrixTransform(const SkMatrix& matrix, + SkFilterQuality filterQuality, + sk_sp input); + + /** + * Create a filter that merges the 'count' filters together by drawing their results in order + * with src-over blending. + * @param filters The input filter array to merge, which must have 'count' elements. Any null + * filter pointers will use the source bitmap instead. + * @param count The number of input filters to be merged. + * @param cropRect Optional rectangle that crops all input filters and the output. + */ + static sk_sp Merge(sk_sp* const filters, int count, + const SkIRect* cropRect = nullptr); + /** + * Create a filter that merges the results of the two filters together with src-over blending. + * @param first The first input filter, or the source bitmap if this is null. + * @param second The second input filter, or the source bitmap if this null. + * @param cropRect Optional rectangle that crops the inputs and output. + */ + static sk_sp Merge(sk_sp first, sk_sp second, + const SkIRect* cropRect = nullptr) { + sk_sp array[] = { std::move(first), std::move(second) }; + return Merge(array, 2, cropRect); + } + + /** + * Create a filter that offsets the input filter by the given vector. + * @param dx The x offset in local space that the image is shifted. + * @param dy The y offset in local space that the image is shifted. + * @param input The input that will be moved, if null the source bitmap is used instead. + * @param cropRect Optional rectangle to crop the input and output. + */ + static sk_sp Offset(SkScalar dx, SkScalar dy, sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that fills the output with the given paint. + * @param paint The paint to fill + * @param cropRect Optional rectangle that will be filled. If null, the source bitmap's bounds + * are filled even though the source bitmap itself is not used. + */ + static sk_sp Paint(const SkPaint& paint, const SkIRect* cropRect = nullptr); + + /** + * Create a filter that produces the SkPicture as its output, drawn into targetRect. Note that + * the targetRect is not the same as the SkIRect cropRect that many filters accept. Returns + * null if 'pic' is null. + * @param pic The picture that is drawn for the filter output. + * @param targetRect The drawing region for the picture. + */ + static sk_sp Picture(sk_sp pic, const SkRect& targetRect); + // As above, but uses SkPicture::cullRect for the drawing region. + static sk_sp Picture(sk_sp pic) { + SkRect target = pic ? pic->cullRect() : SkRect::MakeEmpty(); + return Picture(std::move(pic), target); + } + + /** + * Create a tile image filter. + * @param src Defines the pixels to tile + * @param dst Defines the pixel region that the tiles will be drawn to + * @param input The input that will be tiled, if null the source bitmap is used instead. + */ + static sk_sp Tile(const SkRect& src, const SkRect& dst, + sk_sp input); + + /** + * This filter takes an SkBlendMode and uses it to composite the two filters together. + * @param background The Dst pixels used in blending, if null the source bitmap is used. + * @param foreground The Src pixels used in blending, if null the source bitmap is used. + * @cropRect Optional rectangle to crop input and output. + */ + static sk_sp Xfermode(SkBlendMode, sk_sp background, + sk_sp foreground = nullptr, + const SkIRect* cropRect = nullptr); + + // Morphology filter effects + + /** + * Create a filter that dilates each input pixel's channel values to the max value within the + * given radii along the x and y axes. + * @param radiusX The distance to dilate along the x axis to either side of each pixel. + * @param radiusY The distance to dilate along the y axis to either side of each pixel. + * @param input The image filter that is dilated, using source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp Dilate(int radiusX, int radiusY, sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that erodes each input pixel's channel values to the minimum channel value + * within the given radii along the x and y axes. + * @param radiusX The distance to erode along the x axis to either side of each pixel. + * @param radiusY The distance to erode along the y axis to either side of each pixel. + * @param input The image filter that is eroded, using source bitmap if this is null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp Erode(int radiusX, int radiusY, sk_sp input, + const SkIRect* cropRect = nullptr); + + // Lighting filter effects + + /** + * Create a filter that calculates the diffuse illumination from a distant light source, + * interpreting the alpha channel of the input as the height profile of the surface (to + * approximate normal vectors). + * @param direction The direction to the distance light. + * @param lightColor The color of the diffuse light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param kd Diffuse reflectance coefficient. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp DistantLitDiffuse(const SkPoint3& direction, SkColor lightColor, + SkScalar surfaceScale, SkScalar kd, + sk_sp input, + const SkIRect* cropRect = nullptr); + /** + * Create a filter that calculates the diffuse illumination from a point light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). + * @param location The location of the point light. + * @param lightColor The color of the diffuse light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param kd Diffuse reflectance coefficient. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp PointLitDiffuse(const SkPoint3& location, SkColor lightColor, + SkScalar surfaceScale, SkScalar kd, + sk_sp input, + const SkIRect* cropRect = nullptr); + /** + * Create a filter that calculates the diffuse illumination from a spot light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). The spot light is restricted to be within 'cutoffAngle' of the vector between + * the location and target. + * @param location The location of the spot light. + * @param target The location that the spot light is point towards + * @param falloffExponent Exponential falloff parameter for illumination outside of cutoffAngle + * @param cutoffAngle Maximum angle from lighting direction that receives full light + * @param lightColor The color of the diffuse light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param kd Diffuse reflectance coefficient. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp SpotLitDiffuse(const SkPoint3& location, const SkPoint3& target, + SkScalar falloffExponent, SkScalar cutoffAngle, + SkColor lightColor, SkScalar surfaceScale, + SkScalar kd, sk_sp input, + const SkIRect* cropRect = nullptr); + + /** + * Create a filter that calculates the specular illumination from a distant light source, + * interpreting the alpha channel of the input as the height profile of the surface (to + * approximate normal vectors). + * @param direction The direction to the distance light. + * @param lightColor The color of the specular light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param ks Specular reflectance coefficient. + * @param shininess The specular exponent determining how shiny the surface is. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp DistantLitSpecular(const SkPoint3& direction, SkColor lightColor, + SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, + const SkIRect* cropRect = nullptr); + /** + * Create a filter that calculates the specular illumination from a point light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). + * @param location The location of the point light. + * @param lightColor The color of the specular light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param ks Specular reflectance coefficient. + * @param shininess The specular exponent determining how shiny the surface is. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp PointLitSpecular(const SkPoint3& location, SkColor lightColor, + SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, + const SkIRect* cropRect = nullptr); + /** + * Create a filter that calculates the specular illumination from a spot light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). The spot light is restricted to be within 'cutoffAngle' of the vector between + * the location and target. + * @param location The location of the spot light. + * @param target The location that the spot light is point towards + * @param falloffExponent Exponential falloff parameter for illumination outside of cutoffAngle + * @param cutoffAngle Maximum angle from lighting direction that receives full light + * @param lightColor The color of the specular light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param ks Specular reflectance coefficient. + * @param shininess The specular exponent determining how shiny the surface is. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + static sk_sp SpotLitSpecular(const SkPoint3& location, const SkPoint3& target, + SkScalar falloffExponent, SkScalar cutoffAngle, + SkColor lightColor, SkScalar surfaceScale, + SkScalar ks, SkScalar shininess, + sk_sp input, + const SkIRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkImageFilters() = delete; +}; + +#endif // SkImageFilters_DEFINED diff --git a/gfx/skia/skia/include/effects/SkImageSource.h b/gfx/skia/skia/include/effects/SkImageSource.h new file mode 100644 index 0000000000..1572df717d --- /dev/null +++ b/gfx/skia/skia/include/effects/SkImageSource.h @@ -0,0 +1,29 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageSource_DEFINED +#define SkImageSource_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkImageFilter.h" + +// DEPRECATED: Use include/effects/SkImageFilters::Image +class SK_API SkImageSource { +public: + static sk_sp Make(sk_sp image); + static sk_sp Make(sk_sp image, + const SkRect& srcRect, + const SkRect& dstRect, + SkFilterQuality filterQuality); + + static void RegisterFlattenables(); + +private: + SkImageSource() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkLayerDrawLooper.h b/gfx/skia/skia/include/effects/SkLayerDrawLooper.h new file mode 100644 index 0000000000..8d10e8c2d6 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkLayerDrawLooper.h @@ -0,0 +1,150 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLayerDrawLooper_DEFINED +#define SkLayerDrawLooper_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkDrawLooper.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPoint.h" + +class SK_API SkLayerDrawLooper : public SkDrawLooper { +public: + ~SkLayerDrawLooper() override; + + /** + * Bits specifies which aspects of the layer's paint should replace the + * corresponding aspects on the draw's paint. + * kEntirePaint_Bits means use the layer's paint completely. + * 0 means ignore the layer's paint... except for fColorMode, which is + * always applied. + */ + enum Bits { + kStyle_Bit = 1 << 0, //!< use this layer's Style/stroke settings + kPathEffect_Bit = 1 << 2, //!< use this layer's patheffect + kMaskFilter_Bit = 1 << 3, //!< use this layer's maskfilter + kShader_Bit = 1 << 4, //!< use this layer's shader + kColorFilter_Bit = 1 << 5, //!< use this layer's colorfilter + kXfermode_Bit = 1 << 6, //!< use this layer's xfermode + + // unsupported kTextSkewX_Bit = 1 << 1, + + /** + * Use the layer's paint entirely, with these exceptions: + * - We never override the draw's paint's text_encoding, since that is + * used to interpret the text/len parameters in draw[Pos]Text. + * - Color is always computed using the LayerInfo's fColorMode. + */ + kEntirePaint_Bits = -1 + + }; + typedef int32_t BitFlags; + + /** + * Info for how to apply the layer's paint and offset. + * + * fColorMode controls how we compute the final color for the layer: + * The layer's paint's color is treated as the SRC + * The draw's paint's color is treated as the DST + * final-color = Mode(layers-color, draws-color); + * Any SkBlendMode will work. Two common choices are: + * kSrc: to use the layer's color, ignoring the draw's + * kDst: to just keep the draw's color, ignoring the layer's + */ + struct SK_API LayerInfo { + BitFlags fPaintBits; + SkBlendMode fColorMode; + SkVector fOffset; + bool fPostTranslate; //!< applies to fOffset + + /** + * Initial the LayerInfo. Defaults to settings that will draw the + * layer with no changes: e.g. + * fPaintBits == 0 + * fColorMode == kDst_Mode + * fOffset == (0, 0) + */ + LayerInfo(); + }; + + SkDrawLooper::Context* makeContext(SkArenaAlloc*) const override; + + bool asABlurShadow(BlurShadowRec* rec) const override; + +protected: + SkLayerDrawLooper(); + + void flatten(SkWriteBuffer&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkLayerDrawLooper) + + struct Rec { + Rec* fNext; + SkPaint fPaint; + LayerInfo fInfo; + }; + Rec* fRecs; + int fCount; + + // state-machine during the init/next cycle + class LayerDrawLooperContext : public SkDrawLooper::Context { + public: + explicit LayerDrawLooperContext(const SkLayerDrawLooper* looper); + + protected: + bool next(Info*, SkPaint* paint) override; + + private: + Rec* fCurrRec; + + static void ApplyInfo(SkPaint* dst, const SkPaint& src, const LayerInfo&); + }; + + typedef SkDrawLooper INHERITED; + +public: + class SK_API Builder { + public: + Builder(); + ~Builder(); + + /** + * Call for each layer you want to add (from top to bottom). + * This returns a paint you can modify, but that ptr is only valid until + * the next call made to addLayer(). + */ + SkPaint* addLayer(const LayerInfo&); + + /** + * This layer will draw with the original paint, at the specified offset + */ + void addLayer(SkScalar dx, SkScalar dy); + + /** + * This layer will with the original paint and no offset. + */ + void addLayer() { this->addLayer(0, 0); } + + /// Similar to addLayer, but adds a layer to the top. + SkPaint* addLayerOnTop(const LayerInfo&); + + /** + * Pass list of layers on to newly built looper and return it. This will + * also reset the builder, so it can be used to build another looper. + */ + sk_sp detach(); + + private: + Rec* fRecs; + Rec* fTopRec; + int fCount; + }; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkLightingImageFilter.h b/gfx/skia/skia/include/effects/SkLightingImageFilter.h new file mode 100644 index 0000000000..024dd4d1dd --- /dev/null +++ b/gfx/skia/skia/include/effects/SkLightingImageFilter.h @@ -0,0 +1,46 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLightingImageFilter_DEFINED +#define SkLightingImageFilter_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkImageFilter.h" + +struct SkPoint3; + +// DEPRECATED: Use include/effects/SkImageFilters::[Diffuse|Specular]Light[Distant|Point|Spot] +class SK_API SkLightingImageFilter { +public: + static sk_sp MakeDistantLitDiffuse(const SkPoint3& direction, + SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkImageFilter::CropRect* cropRect = nullptr); + static sk_sp MakePointLitDiffuse(const SkPoint3& location, + SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkImageFilter::CropRect* cropRect = nullptr); + static sk_sp MakeSpotLitDiffuse(const SkPoint3& location, + const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle, + SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkImageFilter::CropRect* cropRect = nullptr); + static sk_sp MakeDistantLitSpecular(const SkPoint3& direction, + SkColor lightColor, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, + sk_sp input, const SkImageFilter::CropRect* cropRect = nullptr); + static sk_sp MakePointLitSpecular(const SkPoint3& location, + SkColor lightColor, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, + sk_sp input, const SkImageFilter::CropRect* cropRect = nullptr); + static sk_sp MakeSpotLitSpecular(const SkPoint3& location, + const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle, + SkColor lightColor, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, + sk_sp input, const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkLightingImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkLumaColorFilter.h b/gfx/skia/skia/include/effects/SkLumaColorFilter.h new file mode 100644 index 0000000000..d35540c728 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkLumaColorFilter.h @@ -0,0 +1,57 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLumaColorFilter_DEFINED +#define SkLumaColorFilter_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkRefCnt.h" + +class SkRasterPipeline; + +/** + * SkLumaColorFilter multiplies the luma of its input into the alpha channel, + * and sets the red, green, and blue channels to zero. + * + * SkLumaColorFilter(r,g,b,a) = {0,0,0, a * luma(r,g,b)} + * + * This is similar to a luminanceToAlpha feColorMatrix, + * but note how this filter folds in the previous alpha, + * something an feColorMatrix cannot do. + * + * feColorMatrix(luminanceToAlpha; r,g,b,a) = {0,0,0, luma(r,g,b)} + * + * (Despite its name, an feColorMatrix using luminanceToAlpha does + * actually compute luma, a dot-product of gamma-encoded color channels, + * not luminance, a dot-product of linear color channels. So at least + * SkLumaColorFilter and feColorMatrix+luminanceToAlpha agree there.) + */ + + #include "include/core/SkFlattenable.h" + +class SK_API SkLumaColorFilter : public SkColorFilter { +public: + static sk_sp Make(); + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override; +#endif + +protected: + void flatten(SkWriteBuffer&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkLumaColorFilter) + + SkLumaColorFilter(); + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override; + + typedef SkColorFilter INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkMagnifierImageFilter.h b/gfx/skia/skia/include/effects/SkMagnifierImageFilter.h new file mode 100644 index 0000000000..89550f5179 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkMagnifierImageFilter.h @@ -0,0 +1,28 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkMagnifierImageFilter_DEFINED +#define SkMagnifierImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" +#include "include/core/SkRect.h" + +// DEPRECATED: Use include/effects/SkImageFilters::Magnifier +class SK_API SkMagnifierImageFilter { +public: + static sk_sp Make(const SkRect& srcRect, SkScalar inset, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkMagnifierImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkMatrixConvolutionImageFilter.h b/gfx/skia/skia/include/effects/SkMatrixConvolutionImageFilter.h new file mode 100644 index 0000000000..dfb2defa64 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkMatrixConvolutionImageFilter.h @@ -0,0 +1,88 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMatrixConvolutionImageFilter_DEFINED +#define SkMatrixConvolutionImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" +#include "include/core/SkPoint.h" +#include "include/core/SkScalar.h" +#include "include/core/SkSize.h" + +class SkBitmap; +enum class SkTileMode; + +/*! \class SkMatrixConvolutionImageFilter + Matrix convolution image filter. This filter applies an NxM image + processing kernel to a given input image. This can be used to produce + effects such as sharpening, blurring, edge detection, etc. + + DEPRECATED: Use include/effects/SkImageFilters::MatrixConvolution + */ + +class SK_API SkMatrixConvolutionImageFilter { +public: + /*! \enum TileMode + * DEPRECATED: Use SkTileMode instead. */ + enum TileMode { + kClamp_TileMode = 0, /*!< Clamp to the image's edge pixels. */ + kRepeat_TileMode, /*!< Wrap around to the image's opposite edge. */ + kClampToBlack_TileMode, /*!< Fill with transparent black. */ + kLast_TileMode = kClampToBlack_TileMode, + + // TODO: remove kMax - it is non-standard but used by Chromium! + kMax_TileMode = kClampToBlack_TileMode + }; + + static sk_sp Make(const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + TileMode tileMode, + bool convolveAlpha, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + /** Construct a matrix convolution image filter. + @param kernelSize The kernel size in pixels, in each dimension (N by M). + @param kernel The image processing kernel. Must contain N * M + elements, in row order. + @param gain A scale factor applied to each pixel after + convolution. This can be used to normalize the + kernel, if it does not sum to 1. + @param bias A bias factor added to each pixel after convolution. + @param kernelOffset An offset applied to each pixel coordinate before + convolution. This can be used to center the kernel + over the image (e.g., a 3x3 kernel should have an + offset of {1, 1}). + @param tileMode How accesses outside the image are treated. (@see + TileMode). EXPERIMENTAL: kMirror not supported yet. + @param convolveAlpha If true, all channels are convolved. If false, + only the RGB channels are convolved, and + alpha is copied from the source image. + @param input The input image filter. If NULL, the src bitmap + passed to filterImage() is used instead. + @param cropRect The rectangle to which the output processing will be limited. + */ + static sk_sp Make(const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + SkTileMode tileMode, + bool convolveAlpha, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkMatrixConvolutionImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkMergeImageFilter.h b/gfx/skia/skia/include/effects/SkMergeImageFilter.h new file mode 100644 index 0000000000..2ad9a79086 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkMergeImageFilter.h @@ -0,0 +1,34 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMergeImageFilter_DEFINED +#define SkMergeImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +// DEPRECATED: Use include/effects/SkImageFilters::Merge +class SK_API SkMergeImageFilter { +public: + static sk_sp Make(sk_sp* const filters, int count, + const SkImageFilter::CropRect* cropRect = nullptr); + + static sk_sp Make(sk_sp first, sk_sp second, + const SkImageFilter::CropRect* cropRect = nullptr) { + sk_sp array[] = { + std::move(first), + std::move(second), + }; + return Make(array, 2, cropRect); + } + + static void RegisterFlattenables(); + +private: + SkMergeImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkMorphologyImageFilter.h b/gfx/skia/skia/include/effects/SkMorphologyImageFilter.h new file mode 100644 index 0000000000..ea72e0eaca --- /dev/null +++ b/gfx/skia/skia/include/effects/SkMorphologyImageFilter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMorphologyImageFilter_DEFINED +#define SkMorphologyImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +/////////////////////////////////////////////////////////////////////////////// +// DEPRECATED: Use include/effects/SkImageFilters::Dilate +class SK_API SkDilateImageFilter { +public: + static sk_sp Make(int radiusX, int radiusY, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + // Registers all morphology filter implementations + static void RegisterFlattenables(); + +private: + SkDilateImageFilter() = delete; +}; + +/////////////////////////////////////////////////////////////////////////////// +// DEPRECATED: Use include/effects/SkImageFilters::Erode +class SK_API SkErodeImageFilter { +public: + static sk_sp Make(int radiusX, int radiusY, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + +private: + SkErodeImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkOffsetImageFilter.h b/gfx/skia/skia/include/effects/SkOffsetImageFilter.h new file mode 100644 index 0000000000..b8ad790763 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkOffsetImageFilter.h @@ -0,0 +1,26 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOffsetImageFilter_DEFINED +#define SkOffsetImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +// DEPRECATED: Use include/effects/SkImageFilters::Offset +class SK_API SkOffsetImageFilter { +public: + static sk_sp Make(SkScalar dx, SkScalar dy, + sk_sp input, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkOffsetImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkOpPathEffect.h b/gfx/skia/skia/include/effects/SkOpPathEffect.h new file mode 100644 index 0000000000..9686644d76 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkOpPathEffect.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOpPathEffect_DEFINED +#define SkOpPathEffect_DEFINED + +#include "include/core/SkPaint.h" +#include "include/core/SkPathEffect.h" +#include "include/pathops/SkPathOps.h" + +class SK_API SkMergePathEffect { +public: + /* Defers to two other patheffects, and then combines their outputs using the specified op. + * e.g. + * result = output_one op output_two + * + * If either one or two is nullptr, then the original path is passed through to the op. + */ + static sk_sp Make(sk_sp one, sk_sp two, SkPathOp op); +}; + +class SK_API SkMatrixPathEffect { +public: + static sk_sp MakeTranslate(SkScalar dx, SkScalar dy); + static sk_sp Make(const SkMatrix&); +}; + +class SK_API SkStrokePathEffect { +public: + static sk_sp Make(SkScalar width, SkPaint::Join, SkPaint::Cap, + SkScalar miter = 4); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkOverdrawColorFilter.h b/gfx/skia/skia/include/effects/SkOverdrawColorFilter.h new file mode 100644 index 0000000000..a34c3763c9 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkOverdrawColorFilter.h @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkFlattenable.h" + +#ifndef SkOverdrawColorFilter_DEFINED +#define SkOverdrawColorFilter_DEFINED + +/** + * Uses the value in the src alpha channel to set the dst pixel. + * 0 -> fColors[0] + * 1 -> fColors[1] + * ... + * 5 (or larger) -> fColors[5] + * + */ +class SK_API SkOverdrawColorFilter : public SkColorFilter { +public: + static constexpr int kNumColors = 6; + + static sk_sp Make(const SkPMColor colors[kNumColors]) { + return sk_sp(new SkOverdrawColorFilter(colors)); + } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override; +#endif + + static void RegisterFlattenables(); + +protected: + void flatten(SkWriteBuffer& buffer) const override; + +private: + SK_FLATTENABLE_HOOKS(SkOverdrawColorFilter) + + SkOverdrawColorFilter(const SkPMColor colors[kNumColors]) { + memcpy(fColors, colors, kNumColors * sizeof(SkPMColor)); + } + + bool onAppendStages(const SkStageRec&, bool) const override; + + SkPMColor fColors[kNumColors]; + + typedef SkColorFilter INHERITED; +}; + +#endif // SkOverdrawColorFilter_DEFINED diff --git a/gfx/skia/skia/include/effects/SkPaintImageFilter.h b/gfx/skia/skia/include/effects/SkPaintImageFilter.h new file mode 100644 index 0000000000..9430bc4ebf --- /dev/null +++ b/gfx/skia/skia/include/effects/SkPaintImageFilter.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPaintImageFilter_DEFINED +#define SkPaintImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +class SkPaint; + +// DEPRECATED: Use include/effects/SkImageFilters::Paint +class SK_API SkPaintImageFilter { +public: + /** Create a new image filter which fills the given rectangle using the + * given paint. If no rectangle is specified, an output is produced with + * the same bounds as the input primitive (even though the input + * primitive's pixels are not used for processing). + * @param paint Paint to use when filling the rect. + * @param rect Rectangle of output pixels. If NULL or a given crop edge is + * not specified, the source primitive's bounds are used + * instead. + */ + static sk_sp Make(const SkPaint& paint, + const SkImageFilter::CropRect* cropRect = nullptr); + + static void RegisterFlattenables(); + +private: + SkPaintImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkPerlinNoiseShader.h b/gfx/skia/skia/include/effects/SkPerlinNoiseShader.h new file mode 100644 index 0000000000..0765763ba4 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkPerlinNoiseShader.h @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPerlinNoiseShader_DEFINED +#define SkPerlinNoiseShader_DEFINED + +#include "include/core/SkShader.h" + +/** \class SkPerlinNoiseShader + + SkPerlinNoiseShader creates an image using the Perlin turbulence function. + + It can produce tileable noise if asked to stitch tiles and provided a tile size. + In order to fill a large area with repeating noise, set the stitchTiles flag to + true, and render exactly a single tile of noise. Without this flag, the result + will contain visible seams between tiles. + + The algorithm used is described here : + http://www.w3.org/TR/SVG/filters.html#feTurbulenceElement +*/ +class SK_API SkPerlinNoiseShader { +public: + /** + * This will construct Perlin noise of the given type (Fractal Noise or Turbulence). + * + * Both base frequencies (X and Y) have a usual range of (0..1) and must be non-negative. + * + * The number of octaves provided should be fairly small, with a limit of 255 enforced. + * Each octave doubles the frequency, so 10 octaves would produce noise from + * baseFrequency * 1, * 2, * 4, ..., * 512, which quickly yields insignificantly small + * periods and resembles regular unstructured noise rather than Perlin noise. + * + * If tileSize isn't NULL or an empty size, the tileSize parameter will be used to modify + * the frequencies so that the noise will be tileable for the given tile size. If tileSize + * is NULL or an empty size, the frequencies will be used as is without modification. + */ + static sk_sp MakeFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize = nullptr); + static sk_sp MakeTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize = nullptr); + /** + * Creates an Improved Perlin Noise shader. The z value is roughly equivalent to the seed of the + * other two types, but minor variations to z will only slightly change the noise. + */ + static sk_sp MakeImprovedNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY, + int numOctaves, SkScalar z); + + static void RegisterFlattenables(); + +private: + SkPerlinNoiseShader() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkPictureImageFilter.h b/gfx/skia/skia/include/effects/SkPictureImageFilter.h new file mode 100644 index 0000000000..9ceaabcc2d --- /dev/null +++ b/gfx/skia/skia/include/effects/SkPictureImageFilter.h @@ -0,0 +1,35 @@ +/* + * Copyright 2013 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureImageFilter_DEFINED +#define SkPictureImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +class SkPicture; + +// DEPRECATED: Use include/effects/SkImageFilters::Picture +class SK_API SkPictureImageFilter { +public: + /** + * Refs the passed-in picture. + */ + static sk_sp Make(sk_sp picture); + + /** + * Refs the passed-in picture. cropRect can be used to crop or expand the destination rect when + * the picture is drawn. (No scaling is implied by the dest rect; only the CTM is applied.) + */ + static sk_sp Make(sk_sp picture, const SkRect& cropRect); + + static void RegisterFlattenables(); + +private: + SkPictureImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkShaderMaskFilter.h b/gfx/skia/skia/include/effects/SkShaderMaskFilter.h new file mode 100644 index 0000000000..2b25d367a7 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkShaderMaskFilter.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkShaderMaskFilter_DEFINED +#define SkShaderMaskFilter_DEFINED + +#include "include/core/SkMaskFilter.h" + +class SkShader; + +class SK_API SkShaderMaskFilter { +public: + static sk_sp Make(sk_sp shader); + +private: + static void RegisterFlattenables(); + friend class SkFlattenable; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkTableColorFilter.h b/gfx/skia/skia/include/effects/SkTableColorFilter.h new file mode 100644 index 0000000000..ab964aa20d --- /dev/null +++ b/gfx/skia/skia/include/effects/SkTableColorFilter.h @@ -0,0 +1,42 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef SkTableColorFilter_DEFINED +#define SkTableColorFilter_DEFINED + +#include "include/core/SkColorFilter.h" + +class SK_API SkTableColorFilter { +public: + /** + * Create a table colorfilter, copying the table into the filter, and + * applying it to all 4 components. + * a' = table[a]; + * r' = table[r]; + * g' = table[g]; + * b' = table[b]; + * Compoents are operated on in unpremultiplied space. If the incomming + * colors are premultiplied, they are temporarily unpremultiplied, then + * the table is applied, and then the result is remultiplied. + */ + static sk_sp Make(const uint8_t table[256]); + + /** + * Create a table colorfilter, with a different table for each + * component [A, R, G, B]. If a given table is NULL, then it is + * treated as identity, with the component left unchanged. If a table + * is not null, then its contents are copied into the filter. + */ + static sk_sp MakeARGB(const uint8_t tableA[256], + const uint8_t tableR[256], + const uint8_t tableG[256], + const uint8_t tableB[256]); + + static void RegisterFlattenables(); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkTableMaskFilter.h b/gfx/skia/skia/include/effects/SkTableMaskFilter.h new file mode 100644 index 0000000000..03535a6f98 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkTableMaskFilter.h @@ -0,0 +1,37 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTableMaskFilter_DEFINED +#define SkTableMaskFilter_DEFINED + +#include "include/core/SkMaskFilter.h" +#include "include/core/SkScalar.h" + +/** \class SkTableMaskFilter + + Applies a table lookup on each of the alpha values in the mask. + Helper methods create some common tables (e.g. gamma, clipping) + */ +class SK_API SkTableMaskFilter { +public: + /** Utility that sets the gamma table + */ + static void MakeGammaTable(uint8_t table[256], SkScalar gamma); + + /** Utility that creates a clipping table: clamps values below min to 0 + and above max to 255, and rescales the remaining into 0..255 + */ + static void MakeClipTable(uint8_t table[256], uint8_t min, uint8_t max); + + static SkMaskFilter* Create(const uint8_t table[256]); + static SkMaskFilter* CreateGamma(SkScalar gamma); + static SkMaskFilter* CreateClip(uint8_t min, uint8_t max); + + SkTableMaskFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkTileImageFilter.h b/gfx/skia/skia/include/effects/SkTileImageFilter.h new file mode 100644 index 0000000000..348e4f1eff --- /dev/null +++ b/gfx/skia/skia/include/effects/SkTileImageFilter.h @@ -0,0 +1,31 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTileImageFilter_DEFINED +#define SkTileImageFilter_DEFINED + +#include "include/core/SkImageFilter.h" + +// DEPRECATED: Use include/effects/SkImageFilters::Tile +class SK_API SkTileImageFilter { +public: + /** Create a tile image filter + @param src Defines the pixels to tile + @param dst Defines the pixels where tiles are drawn + @param input Input from which the subregion defined by srcRect will be tiled + */ + static sk_sp Make(const SkRect& src, + const SkRect& dst, + sk_sp input); + + static void RegisterFlattenables(); + +private: + SkTileImageFilter() = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkTrimPathEffect.h b/gfx/skia/skia/include/effects/SkTrimPathEffect.h new file mode 100644 index 0000000000..705d1c2be7 --- /dev/null +++ b/gfx/skia/skia/include/effects/SkTrimPathEffect.h @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTrimPathEffect_DEFINED +#define SkTrimPathEffect_DEFINED + +#include "include/core/SkPathEffect.h" + +class SK_API SkTrimPathEffect { +public: + enum class Mode { + kNormal, // return the subset path [start,stop] + kInverted, // return the complement/subset paths [0,start] + [stop,1] + }; + + /** + * Take start and stop "t" values (values between 0...1), and return a path that is that + * subset of the original path. + * + * e.g. + * Make(0.5, 1.0) --> return the 2nd half of the path + * Make(0.33333, 0.66667) --> return the middle third of the path + * + * The trim values apply to the entire path, so if it contains several contours, all of them + * are including in the calculation. + * + * startT and stopT must be 0..1 inclusive. If they are outside of that interval, they will + * be pinned to the nearest legal value. If either is NaN, null will be returned. + * + * Note: for Mode::kNormal, this will return one (logical) segment (even if it is spread + * across multiple contours). For Mode::kInverted, this will return 2 logical + * segments: 0...stopT and startT...1 + */ + static sk_sp Make(SkScalar startT, SkScalar stopT, Mode = Mode::kNormal); +}; + +#endif diff --git a/gfx/skia/skia/include/effects/SkXfermodeImageFilter.h b/gfx/skia/skia/include/effects/SkXfermodeImageFilter.h new file mode 100644 index 0000000000..f2d5ec438c --- /dev/null +++ b/gfx/skia/skia/include/effects/SkXfermodeImageFilter.h @@ -0,0 +1,34 @@ +/* + * Copyright 2013 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkXfermodeImageFilter_DEFINED +#define SkXfermodeImageFilter_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkImageFilter.h" + +/** + * This filter takes a SkBlendMode, and uses it to composite the foreground over the background. + * If foreground or background is NULL, the input bitmap (src) is used instead. + // DEPRECATED: Use include/effects/SkImageFilters::XferMode + */ +class SK_API SkXfermodeImageFilter { +public: + static sk_sp Make(SkBlendMode, sk_sp background, + sk_sp foreground, + const SkImageFilter::CropRect* cropRect); + static sk_sp Make(SkBlendMode mode, sk_sp background) { + return Make(mode, std::move(background), nullptr, nullptr); + } + + static void RegisterFlattenables(); + +private: + SkXfermodeImageFilter(); // can't instantiate +}; + +#endif diff --git a/gfx/skia/skia/include/encode/SkEncoder.h b/gfx/skia/skia/include/encode/SkEncoder.h new file mode 100644 index 0000000000..1a9c37e7f5 --- /dev/null +++ b/gfx/skia/skia/include/encode/SkEncoder.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEncoder_DEFINED +#define SkEncoder_DEFINED + +#include "include/core/SkPixmap.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTemplates.h" + +class SK_API SkEncoder : SkNoncopyable { +public: + + /** + * Encode |numRows| rows of input. If the caller requests more rows than are remaining + * in the src, this will encode all of the remaining rows. |numRows| must be greater + * than zero. + */ + bool encodeRows(int numRows); + + virtual ~SkEncoder() {} + +protected: + + virtual bool onEncodeRows(int numRows) = 0; + + SkEncoder(const SkPixmap& src, size_t storageBytes) + : fSrc(src) + , fCurrRow(0) + , fStorage(storageBytes) + {} + + const SkPixmap& fSrc; + int fCurrRow; + SkAutoTMalloc fStorage; +}; + +#endif diff --git a/gfx/skia/skia/include/encode/SkJpegEncoder.h b/gfx/skia/skia/include/encode/SkJpegEncoder.h new file mode 100644 index 0000000000..e900cd9df0 --- /dev/null +++ b/gfx/skia/skia/include/encode/SkJpegEncoder.h @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkJpegEncoder_DEFINED +#define SkJpegEncoder_DEFINED + +#include "include/encode/SkEncoder.h" + +class SkJpegEncoderMgr; +class SkWStream; + +class SK_API SkJpegEncoder : public SkEncoder { +public: + + enum class AlphaOption { + kIgnore, + kBlendOnBlack, + }; + + enum class Downsample { + /** + * Reduction by a factor of two in both the horizontal and vertical directions. + */ + k420, + + /** + * Reduction by a factor of two in the horizontal direction. + */ + k422, + + /** + * No downsampling. + */ + k444, + }; + + struct Options { + /** + * |fQuality| must be in [0, 100] where 0 corresponds to the lowest quality. + */ + int fQuality = 100; + + /** + * Choose the downsampling factor for the U and V components. This is only + * meaningful if the |src| is not kGray, since kGray will not be encoded as YUV. + * + * Our default value matches the libjpeg-turbo default. + */ + Downsample fDownsample = Downsample::k420; + + /** + * Jpegs must be opaque. This instructs the encoder on how to handle input + * images with alpha. + * + * The default is to ignore the alpha channel and treat the image as opaque. + * Another option is to blend the pixels onto a black background before encoding. + * In the second case, the encoder supports linear or legacy blending. + */ + AlphaOption fAlphaOption = AlphaOption::kIgnore; + }; + + /** + * Encode the |src| pixels to the |dst| stream. + * |options| may be used to control the encoding behavior. + * + * Returns true on success. Returns false on an invalid or unsupported |src|. + */ + static bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options); + + /** + * Create a jpeg encoder that will encode the |src| pixels to the |dst| stream. + * |options| may be used to control the encoding behavior. + * + * |dst| is unowned but must remain valid for the lifetime of the object. + * + * This returns nullptr on an invalid or unsupported |src|. + */ + static std::unique_ptr Make(SkWStream* dst, const SkPixmap& src, + const Options& options); + + ~SkJpegEncoder() override; + +protected: + bool onEncodeRows(int numRows) override; + +private: + SkJpegEncoder(std::unique_ptr, const SkPixmap& src); + + std::unique_ptr fEncoderMgr; + typedef SkEncoder INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/encode/SkPngEncoder.h b/gfx/skia/skia/include/encode/SkPngEncoder.h new file mode 100644 index 0000000000..bf24bc5cce --- /dev/null +++ b/gfx/skia/skia/include/encode/SkPngEncoder.h @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPngEncoder_DEFINED +#define SkPngEncoder_DEFINED + +#include "include/core/SkDataTable.h" +#include "include/encode/SkEncoder.h" + +class SkPngEncoderMgr; +class SkWStream; + +class SK_API SkPngEncoder : public SkEncoder { +public: + + enum class FilterFlag : int { + kZero = 0x00, + kNone = 0x08, + kSub = 0x10, + kUp = 0x20, + kAvg = 0x40, + kPaeth = 0x80, + kAll = kNone | kSub | kUp | kAvg | kPaeth, + }; + + struct Options { + /** + * Selects which filtering strategies to use. + * + * If a single filter is chosen, libpng will use that filter for every row. + * + * If multiple filters are chosen, libpng will use a heuristic to guess which filter + * will encode smallest, then apply that filter. This happens on a per row basis, + * different rows can use different filters. + * + * Using a single filter (or less filters) is typically faster. Trying all of the + * filters may help minimize the output file size. + * + * Our default value matches libpng's default. + */ + FilterFlag fFilterFlags = FilterFlag::kAll; + + /** + * Must be in [0, 9] where 9 corresponds to maximal compression. This value is passed + * directly to zlib. 0 is a special case to skip zlib entirely, creating dramatically + * larger pngs. + * + * Our default value matches libpng's default. + */ + int fZLibLevel = 6; + + /** + * Represents comments in the tEXt ancillary chunk of the png. + * The 2i-th entry is the keyword for the i-th comment, + * and the (2i + 1)-th entry is the text for the i-th comment. + */ + sk_sp fComments; + }; + + /** + * Encode the |src| pixels to the |dst| stream. + * |options| may be used to control the encoding behavior. + * + * Returns true on success. Returns false on an invalid or unsupported |src|. + */ + static bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options); + + /** + * Create a png encoder that will encode the |src| pixels to the |dst| stream. + * |options| may be used to control the encoding behavior. + * + * |dst| is unowned but must remain valid for the lifetime of the object. + * + * This returns nullptr on an invalid or unsupported |src|. + */ + static std::unique_ptr Make(SkWStream* dst, const SkPixmap& src, + const Options& options); + + ~SkPngEncoder() override; + +protected: + bool onEncodeRows(int numRows) override; + + SkPngEncoder(std::unique_ptr, const SkPixmap& src); + + std::unique_ptr fEncoderMgr; + typedef SkEncoder INHERITED; +}; + +static inline SkPngEncoder::FilterFlag operator|(SkPngEncoder::FilterFlag x, + SkPngEncoder::FilterFlag y) { + return (SkPngEncoder::FilterFlag)((int)x | (int)y); +} + +#endif diff --git a/gfx/skia/skia/include/encode/SkWebpEncoder.h b/gfx/skia/skia/include/encode/SkWebpEncoder.h new file mode 100644 index 0000000000..25f986a9d5 --- /dev/null +++ b/gfx/skia/skia/include/encode/SkWebpEncoder.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWebpEncoder_DEFINED +#define SkWebpEncoder_DEFINED + +#include "include/encode/SkEncoder.h" + +class SkWStream; + +namespace SkWebpEncoder { + + enum class Compression { + kLossy, + kLossless, + }; + + struct SK_API Options { + /** + * |fCompression| determines whether we will use webp lossy or lossless compression. + * + * |fQuality| must be in [0.0f, 100.0f]. + * If |fCompression| is kLossy, |fQuality| corresponds to the visual quality of the + * encoding. Decreasing the quality will result in a smaller encoded image. + * If |fCompression| is kLossless, |fQuality| corresponds to the amount of effort + * put into the encoding. Lower values will compress faster into larger files, + * while larger values will compress slower into smaller files. + * + * This scheme is designed to match the libwebp API. + */ + Compression fCompression = Compression::kLossy; + float fQuality = 100.0f; + }; + + /** + * Encode the |src| pixels to the |dst| stream. + * |options| may be used to control the encoding behavior. + * + * Returns true on success. Returns false on an invalid or unsupported |src|. + */ + SK_API bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options); +} + +#endif diff --git a/gfx/skia/skia/include/gpu/GrBackendDrawableInfo.h b/gfx/skia/skia/include/gpu/GrBackendDrawableInfo.h new file mode 100644 index 0000000000..bda1e769fd --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrBackendDrawableInfo.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBackendDrawableInfo_DEFINED +#define GrBackendDrawableInfo_DEFINED + +#include "include/gpu/GrTypes.h" + +#include "include/gpu/vk/GrVkTypes.h" + +class SK_API GrBackendDrawableInfo { +public: + // Creates an invalid backend drawable info. + GrBackendDrawableInfo() : fIsValid(false) {} + + GrBackendDrawableInfo(const GrVkDrawableInfo& info) + : fIsValid(true) + , fBackend(GrBackendApi::kVulkan) + , fVkInfo(info) {} + + // Returns true if the backend texture has been initialized. + bool isValid() const { return fIsValid; } + + GrBackendApi backend() const { return fBackend; } + + bool getVkDrawableInfo(GrVkDrawableInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + *outInfo = fVkInfo; + return true; + } + return false; + } + +private: + bool fIsValid; + GrBackendApi fBackend; + GrVkDrawableInfo fVkInfo; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrBackendSemaphore.h b/gfx/skia/skia/include/gpu/GrBackendSemaphore.h new file mode 100644 index 0000000000..cbeb2e974c --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrBackendSemaphore.h @@ -0,0 +1,96 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBackendSemaphore_DEFINED +#define GrBackendSemaphore_DEFINED + +#include "include/gpu/GrTypes.h" + +#include "include/gpu/gl/GrGLTypes.h" +#include "include/gpu/mtl/GrMtlTypes.h" +#include "include/gpu/vk/GrVkTypes.h" + +/** + * Wrapper class for passing into and receiving data from Ganesh about a backend semaphore object. + */ +class GrBackendSemaphore { +public: + // For convenience we just set the backend here to OpenGL. The GrBackendSemaphore cannot be used + // until either initGL or initVulkan are called which will set the appropriate GrBackend. + GrBackendSemaphore() : fBackend(GrBackendApi::kOpenGL), fGLSync(0), fIsInitialized(false) {} + + void initGL(GrGLsync sync) { + fBackend = GrBackendApi::kOpenGL; + fGLSync = sync; + fIsInitialized = true; + } + + void initVulkan(VkSemaphore semaphore) { + fBackend = GrBackendApi::kVulkan; + fVkSemaphore = semaphore; +#ifdef SK_VULKAN + fIsInitialized = true; +#else + fIsInitialized = false; +#endif + } + + // It is the creator's responsibility to ref the MTLEvent passed in here, via __bridge_retained. + // The other end will wrap this BackendSemaphore and take the ref, via __bridge_transfer. + void initMetal(GrMTLHandle event, uint64_t value) { + fBackend = GrBackendApi::kMetal; + fMtlEvent = event; + fMtlValue = value; +#ifdef SK_METAL + fIsInitialized = true; +#else + fIsInitialized = false; +#endif + } + + bool isInitialized() const { return fIsInitialized; } + + GrGLsync glSync() const { + if (!fIsInitialized || GrBackendApi::kOpenGL != fBackend) { + return 0; + } + return fGLSync; + } + + VkSemaphore vkSemaphore() const { + if (!fIsInitialized || GrBackendApi::kVulkan != fBackend) { + return VK_NULL_HANDLE; + } + return fVkSemaphore; + } + + GrMTLHandle mtlSemaphore() const { + if (!fIsInitialized || GrBackendApi::kMetal != fBackend) { + return nullptr; + } + return fMtlEvent; + } + + uint64_t mtlValue() const { + if (!fIsInitialized || GrBackendApi::kMetal != fBackend) { + return 0; + } + return fMtlValue; + } + +private: + GrBackendApi fBackend; + union { + GrGLsync fGLSync; + VkSemaphore fVkSemaphore; + GrMTLHandle fMtlEvent; // Expected to be an id + }; + uint64_t fMtlValue; + bool fIsInitialized; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrBackendSurface.h b/gfx/skia/skia/include/gpu/GrBackendSurface.h new file mode 100644 index 0000000000..e98f6af2c2 --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrBackendSurface.h @@ -0,0 +1,453 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBackendSurface_DEFINED +#define GrBackendSurface_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/gpu/gl/GrGLTypes.h" +#include "include/gpu/mock/GrMockTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/GrGLTypesPriv.h" +#include "include/private/GrVkTypesPriv.h" + +#ifdef SK_DAWN +#include "include/gpu/dawn/GrDawnTypes.h" +#endif + +class GrVkImageLayout; +class GrGLTextureParameters; + +#ifdef SK_DAWN +#include "dawn/dawncpp.h" +#endif + +#ifdef SK_METAL +#include "include/gpu/mtl/GrMtlTypes.h" +#endif + +#if GR_TEST_UTILS +class SkString; +#endif + +#if !SK_SUPPORT_GPU + +// SkSurface and SkImage rely on a minimal version of these always being available +class SK_API GrBackendTexture { +public: + GrBackendTexture() {} + + bool isValid() const { return false; } +}; + +class SK_API GrBackendRenderTarget { +public: + GrBackendRenderTarget() {} + + bool isValid() const { return false; } +}; +#else + +enum class GrGLFormat; + +class SK_API GrBackendFormat { +public: + // Creates an invalid backend format. + GrBackendFormat() {} + + GrBackendFormat(const GrBackendFormat& src); + + static GrBackendFormat MakeGL(GrGLenum format, GrGLenum target) { + return GrBackendFormat(format, target); + } + + static GrBackendFormat MakeVk(VkFormat format) { + return GrBackendFormat(format, GrVkYcbcrConversionInfo()); + } + + static GrBackendFormat MakeVk(const GrVkYcbcrConversionInfo& ycbcrInfo); + +#ifdef SK_DAWN + static GrBackendFormat MakeDawn(dawn::TextureFormat format) { + return GrBackendFormat(format); + } +#endif + +#ifdef SK_METAL + static GrBackendFormat MakeMtl(GrMTLPixelFormat format) { + return GrBackendFormat(format); + } +#endif + + static GrBackendFormat MakeMock(GrColorType colorType) { + return GrBackendFormat(colorType); + } + + bool operator==(const GrBackendFormat& that) const; + bool operator!=(const GrBackendFormat& that) const { return !(*this == that); } + + GrBackendApi backend() const { return fBackend; } + GrTextureType textureType() const { return fTextureType; } + + /** + * If the backend API is GL this gets the format as a GrGLFormat. Otherwise, returns + * GrGLFormat::kUnknown. + */ + GrGLFormat asGLFormat() const; + + /** + * If the backend API is Vulkan this gets the format as a VkFormat and returns true. Otherwise, + * returns false. + */ + bool asVkFormat(VkFormat*) const; + + const GrVkYcbcrConversionInfo* getVkYcbcrConversionInfo() const; + +#ifdef SK_DAWN + /** + * If the backend API is Dawn this gets the format as a dawn::TextureFormat and returns true. + * Otherwise, returns false. + */ + bool asDawnFormat(dawn::TextureFormat*) const; +#endif + +#ifdef SK_METAL + /** + * If the backend API is Metal this gets the format as a GrMtlPixelFormat. Otherwise, + * Otherwise, returns MTLPixelFormatInvalid. + */ + GrMTLPixelFormat asMtlFormat() const; +#endif + + /** + * If the backend API is Mock this gets the format as a GrColorType. Otherwise, returns + * GrColorType::kUnknown. + */ + GrColorType asMockColorType() const; + + // If possible, copies the GrBackendFormat and forces the texture type to be Texture2D. If the + // GrBackendFormat was for Vulkan and it originally had a GrVkYcbcrConversionInfo, we will + // remove the conversion and set the format to be VK_FORMAT_R8G8B8A8_UNORM. + GrBackendFormat makeTexture2D() const; + + // Returns true if the backend format has been initialized. + bool isValid() const { return fValid; } + +#if GR_TEST_UTILS + SkString toStr() const; +#endif + +private: + GrBackendFormat(GrGLenum format, GrGLenum target); + + GrBackendFormat(const VkFormat vkFormat, const GrVkYcbcrConversionInfo&); + +#ifdef SK_DAWN + GrBackendFormat(dawn::TextureFormat format); +#endif + +#ifdef SK_METAL + GrBackendFormat(const GrMTLPixelFormat mtlFormat); +#endif + + GrBackendFormat(GrColorType colorType); + + GrBackendApi fBackend = GrBackendApi::kMock; + bool fValid = false; + + union { + GrGLenum fGLFormat; // the sized, internal format of the GL resource + struct { + VkFormat fFormat; + GrVkYcbcrConversionInfo fYcbcrConversionInfo; + } fVk; +#ifdef SK_DAWN + dawn::TextureFormat fDawnFormat; +#endif + +#ifdef SK_METAL + GrMTLPixelFormat fMtlFormat; +#endif + GrColorType fMockColorType; + }; + GrTextureType fTextureType = GrTextureType::kNone; +}; + +class SK_API GrBackendTexture { +public: + // Creates an invalid backend texture. + GrBackendTexture() : fIsValid(false) {} + + // The GrGLTextureInfo must have a valid fFormat. + GrBackendTexture(int width, + int height, + GrMipMapped, + const GrGLTextureInfo& glInfo); + + GrBackendTexture(int width, + int height, + const GrVkImageInfo& vkInfo); + +#ifdef SK_METAL + GrBackendTexture(int width, + int height, + GrMipMapped, + const GrMtlTextureInfo& mtlInfo); +#endif + +#ifdef SK_DAWN + GrBackendTexture(int width, + int height, + const GrDawnImageInfo& dawnInfo); +#endif + + GrBackendTexture(int width, + int height, + GrMipMapped, + const GrMockTextureInfo& mockInfo); + + GrBackendTexture(const GrBackendTexture& that); + + ~GrBackendTexture(); + + GrBackendTexture& operator=(const GrBackendTexture& that); + + int width() const { return fWidth; } + int height() const { return fHeight; } + bool hasMipMaps() const { return GrMipMapped::kYes == fMipMapped; } + GrBackendApi backend() const {return fBackend; } + + // If the backend API is GL, copies a snapshot of the GrGLTextureInfo struct into the passed in + // pointer and returns true. Otherwise returns false if the backend API is not GL. + bool getGLTextureInfo(GrGLTextureInfo*) const; + + // Call this to indicate that the texture parameters have been modified in the GL context + // externally to GrContext. + void glTextureParametersModified(); + +#ifdef SK_DAWN + // If the backend API is Dawn, copies a snapshot of the GrDawnImageInfo struct into the passed + // in pointer and returns true. Otherwise returns false if the backend API is not Dawn. + bool getDawnImageInfo(GrDawnImageInfo*) const; +#endif + + // If the backend API is Vulkan, copies a snapshot of the GrVkImageInfo struct into the passed + // in pointer and returns true. This snapshot will set the fImageLayout to the current layout + // state. Otherwise returns false if the backend API is not Vulkan. + bool getVkImageInfo(GrVkImageInfo*) const; + + // Anytime the client changes the VkImageLayout of the VkImage captured by this + // GrBackendTexture, they must call this function to notify Skia of the changed layout. + void setVkImageLayout(VkImageLayout); + +#ifdef SK_METAL + // If the backend API is Metal, copies a snapshot of the GrMtlTextureInfo struct into the passed + // in pointer and returns true. Otherwise returns false if the backend API is not Metal. + bool getMtlTextureInfo(GrMtlTextureInfo*) const; +#endif + + // Get the GrBackendFormat for this texture (or an invalid format if this is not valid). + GrBackendFormat getBackendFormat() const; + + // If the backend API is Mock, copies a snapshot of the GrMockTextureInfo struct into the passed + // in pointer and returns true. Otherwise returns false if the backend API is not Mock. + bool getMockTextureInfo(GrMockTextureInfo*) const; + + // Returns true if we are working with protected content. + bool isProtected() const; + + // Returns true if the backend texture has been initialized. + bool isValid() const { return fIsValid; } + + // Returns true if both textures are valid and refer to the same API texture. + bool isSameTexture(const GrBackendTexture&); + +#if GR_TEST_UTILS + static bool TestingOnly_Equals(const GrBackendTexture& , const GrBackendTexture&); +#endif + +private: + +#ifdef SK_GL + friend class GrGLTexture; + friend class GrGLGpu; // for getGLTextureParams + GrBackendTexture(int width, + int height, + GrMipMapped, + const GrGLTextureInfo, + sk_sp); + sk_sp getGLTextureParams() const; +#endif + +#ifdef SK_VULKAN + friend class GrVkTexture; + friend class GrVkGpu; // for getGrVkImageLayout + GrBackendTexture(int width, + int height, + const GrVkImageInfo& vkInfo, + sk_sp layout); + sk_sp getGrVkImageLayout() const; +#endif + + // Free and release and resources being held by the GrBackendTexture. + void cleanup(); + + bool fIsValid; + int fWidth; // getGrVkImageLayout() const; + + friend class GrVkRenderTarget; + GrBackendRenderTarget(int width, int height, int sampleCnt, const GrVkImageInfo& vkInfo, + sk_sp layout); + + // Free and release and resources being held by the GrBackendTexture. + void cleanup(); + + bool fIsValid; + int fWidth; // +#endif + +/** + * Gr defines are set to 0 or 1, rather than being undefined or defined + */ + +#if !defined(GR_CACHE_STATS) + #if defined(SK_DEBUG) || defined(SK_DUMP_STATS) + #define GR_CACHE_STATS 1 + #else + #define GR_CACHE_STATS 0 + #endif +#endif + +#if !defined(GR_GPU_STATS) + #if defined(SK_DEBUG) || defined(SK_DUMP_STATS) || defined(GR_TEST_UTILS) + #define GR_GPU_STATS 1 + #else + #define GR_GPU_STATS 0 + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/* + * Include stdint.h with defines that trigger declaration of C99 limit/const + * macros here before anyone else has a chance to include stdint.h without + * these. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS +#endif +#include + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// postconfig section: +// + +/** + * GR_STRING makes a string of X where X is expanded before conversion to a string + * if X itself contains macros. + */ +#define GR_STRING(X) GR_STRING_IMPL(X) +#define GR_STRING_IMPL(X) #X + +/** + * GR_CONCAT concatenates X and Y where each is expanded before + * contanenation if either contains macros. + */ +#define GR_CONCAT(X,Y) GR_CONCAT_IMPL(X,Y) +#define GR_CONCAT_IMPL(X,Y) X##Y + +/** + * Creates a string of the form "() : " + */ +#define GR_FILE_AND_LINE_STR __FILE__ "(" GR_STRING(__LINE__) ") : " + +/** + * Compilers have different ways of issuing warnings. This macro + * attempts to abstract them, but may need to be specialized for your + * particular compiler. + * To insert compiler warnings use "#pragma message GR_WARN()" + */ +#if defined(_MSC_VER) + #define GR_WARN(MSG) (GR_FILE_AND_LINE_STR "WARNING: " MSG) +#else//__GNUC__ - may need other defines for different compilers + #define GR_WARN(MSG) ("WARNING: " MSG) +#endif + +/** + * GR_ALWAYSBREAK is an unconditional break in all builds. + */ +#if !defined(GR_ALWAYSBREAK) + #if defined(SK_BUILD_FOR_WIN) + #define GR_ALWAYSBREAK SkNO_RETURN_HINT(); __debugbreak() + #else + // TODO: do other platforms really not have continuable breakpoints? + // sign extend for 64bit architectures to be sure this is + // in the high address range + #define GR_ALWAYSBREAK SkNO_RETURN_HINT(); *((int*)(int64_t)(int32_t)0xbeefcafe) = 0; + #endif +#endif + +/** + * GR_DEBUGBREAK is an unconditional break in debug builds. + */ +#if !defined(GR_DEBUGBREAK) + #ifdef SK_DEBUG + #define GR_DEBUGBREAK GR_ALWAYSBREAK + #else + #define GR_DEBUGBREAK + #endif +#endif + +/** + * GR_ALWAYSASSERT is an assertion in all builds. + */ +#if !defined(GR_ALWAYSASSERT) + #define GR_ALWAYSASSERT(COND) \ + do { \ + if (!(COND)) { \ + SkDebugf("%s %s failed\n", GR_FILE_AND_LINE_STR, #COND); \ + GR_ALWAYSBREAK; \ + } \ + } while (false) +#endif + +/** + * GR_DEBUGASSERT is an assertion in debug builds only. + */ +#if !defined(GR_DEBUGASSERT) + #ifdef SK_DEBUG + #define GR_DEBUGASSERT(COND) GR_ALWAYSASSERT(COND) + #else + #define GR_DEBUGASSERT(COND) + #endif +#endif + +/** + * Prettier forms of the above macros. + */ +#define GrAlwaysAssert(COND) GR_ALWAYSASSERT(COND) + +/** + * GR_STATIC_ASSERT is a compile time assertion. Depending on the platform + * it may print the message in the compiler log. Obviously, the condition must + * be evaluatable at compile time. + */ +#define GR_STATIC_ASSERT(CONDITION) static_assert(CONDITION, "bug") + +#endif diff --git a/gfx/skia/skia/include/gpu/GrContext.h b/gfx/skia/skia/include/gpu/GrContext.h new file mode 100644 index 0000000000..405195476d --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrContext.h @@ -0,0 +1,545 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrContext_DEFINED +#define GrContext_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkTypes.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContextOptions.h" +#include "include/private/GrRecordingContext.h" + +// We shouldn't need this but currently Android is relying on this being include transitively. +#include "include/core/SkUnPreMultiply.h" + +class GrAtlasManager; +class GrBackendSemaphore; +class GrCaps; +class GrClientMappedBufferManager; +class GrContextPriv; +class GrContextThreadSafeProxy; +class GrFragmentProcessor; +struct GrGLInterface; +class GrGpu; +struct GrMockOptions; +class GrPath; +class GrRenderTargetContext; +class GrResourceCache; +class GrResourceProvider; +class GrSamplerState; +class GrSkSLFPFactoryCache; +class GrSurfaceProxy; +class GrSwizzle; +class GrTextContext; +class GrTextureProxy; +struct GrVkBackendContext; + +class SkImage; +class SkSurfaceCharacterization; +class SkSurfaceProps; +class SkTaskGroup; +class SkTraceMemoryDump; + +class SK_API GrContext : public GrRecordingContext { +public: + /** + * Creates a GrContext for a backend context. If no GrGLInterface is provided then the result of + * GrGLMakeNativeInterface() is used if it succeeds. + */ + static sk_sp MakeGL(sk_sp, const GrContextOptions&); + static sk_sp MakeGL(sk_sp); + static sk_sp MakeGL(const GrContextOptions&); + static sk_sp MakeGL(); + + static sk_sp MakeVulkan(const GrVkBackendContext&, const GrContextOptions&); + static sk_sp MakeVulkan(const GrVkBackendContext&); + +#ifdef SK_METAL + /** + * Makes a GrContext which uses Metal as the backend. The device parameter is an MTLDevice + * and queue is an MTLCommandQueue which should be used by the backend. These objects must + * have a ref on them which can be transferred to Ganesh which will release the ref when the + * GrContext is destroyed. + */ + static sk_sp MakeMetal(void* device, void* queue, const GrContextOptions& options); + static sk_sp MakeMetal(void* device, void* queue); +#endif + +#ifdef SK_DAWN + static sk_sp MakeDawn(const dawn::Device& device, const GrContextOptions& options); + static sk_sp MakeDawn(const dawn::Device& device); +#endif + + static sk_sp MakeMock(const GrMockOptions*, const GrContextOptions&); + static sk_sp MakeMock(const GrMockOptions*); + + ~GrContext() override; + + sk_sp threadSafeProxy(); + + /** + * The GrContext normally assumes that no outsider is setting state + * within the underlying 3D API's context/device/whatever. This call informs + * the context that the state was modified and it should resend. Shouldn't + * be called frequently for good performance. + * The flag bits, state, is dpendent on which backend is used by the + * context, either GL or D3D (possible in future). + */ + void resetContext(uint32_t state = kAll_GrBackendState); + + /** + * If the backend is GrBackendApi::kOpenGL, then all texture unit/target combinations for which + * the GrContext has modified the bound texture will have texture id 0 bound. This does not + * flush the GrContext. Calling resetContext() does not change the set that will be bound + * to texture id 0 on the next call to resetGLTextureBindings(). After this is called + * all unit/target combinations are considered to have unmodified bindings until the GrContext + * subsequently modifies them (meaning if this is called twice in a row with no intervening + * GrContext usage then the second call is a no-op.) + */ + void resetGLTextureBindings(); + + /** + * Abandons all GPU resources and assumes the underlying backend 3D API context is no longer + * usable. Call this if you have lost the associated GPU context, and thus internal texture, + * buffer, etc. references/IDs are now invalid. Calling this ensures that the destructors of the + * GrContext and any of its created resource objects will not make backend 3D API calls. Content + * rendered but not previously flushed may be lost. After this function is called all subsequent + * calls on the GrContext will fail or be no-ops. + * + * The typical use case for this function is that the underlying 3D context was lost and further + * API calls may crash. + */ + void abandonContext() override; + + /** + * Returns true if the context was abandoned. + */ + using GrImageContext::abandoned; + + /** + * This is similar to abandonContext() however the underlying 3D context is not yet lost and + * the GrContext will cleanup all allocated resources before returning. After returning it will + * assume that the underlying context may no longer be valid. + * + * The typical use case for this function is that the client is going to destroy the 3D context + * but can't guarantee that GrContext will be destroyed first (perhaps because it may be ref'ed + * elsewhere by either the client or Skia objects). + */ + virtual void releaseResourcesAndAbandonContext(); + + /////////////////////////////////////////////////////////////////////////// + // Resource Cache + + /** DEPRECATED + * Return the current GPU resource cache limits. + * + * @param maxResources If non-null, will be set to -1. + * @param maxResourceBytes If non-null, returns maximum number of bytes of + * video memory that can be held in the cache. + */ + void getResourceCacheLimits(int* maxResources, size_t* maxResourceBytes) const; + + /** + * Return the current GPU resource cache limit in bytes. + */ + size_t getResourceCacheLimit() const; + + /** + * Gets the current GPU resource cache usage. + * + * @param resourceCount If non-null, returns the number of resources that are held in the + * cache. + * @param maxResourceBytes If non-null, returns the total number of bytes of video memory held + * in the cache. + */ + void getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const; + + /** + * Gets the number of bytes in the cache consumed by purgeable (e.g. unlocked) resources. + */ + size_t getResourceCachePurgeableBytes() const; + + /** DEPRECATED + * Specify the GPU resource cache limits. If the current cache exceeds the maxResourceBytes + * limit, it will be purged (LRU) to keep the cache within the limit. + * + * @param maxResources Unused. + * @param maxResourceBytes The maximum number of bytes of video memory + * that can be held in the cache. + */ + void setResourceCacheLimits(int maxResources, size_t maxResourceBytes); + + /** + * Specify the GPU resource cache limit. If the cache currently exceeds this limit, + * it will be purged (LRU) to keep the cache within the limit. + * + * @param maxResourceBytes The maximum number of bytes of video memory + * that can be held in the cache. + */ + void setResourceCacheLimit(size_t maxResourceBytes); + + /** + * Frees GPU created by the context. Can be called to reduce GPU memory + * pressure. + */ + virtual void freeGpuResources(); + + /** + * Purge GPU resources that haven't been used in the past 'msNotUsed' milliseconds or are + * otherwise marked for deletion, regardless of whether the context is under budget. + */ + void performDeferredCleanup(std::chrono::milliseconds msNotUsed); + + // Temporary compatibility API for Android. + void purgeResourcesNotUsedInMs(std::chrono::milliseconds msNotUsed) { + this->performDeferredCleanup(msNotUsed); + } + + /** + * Purge unlocked resources from the cache until the the provided byte count has been reached + * or we have purged all unlocked resources. The default policy is to purge in LRU order, but + * can be overridden to prefer purging scratch resources (in LRU order) prior to purging other + * resource types. + * + * @param maxBytesToPurge the desired number of bytes to be purged. + * @param preferScratchResources If true scratch resources will be purged prior to other + * resource types. + */ + void purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources); + + /** + * This entry point is intended for instances where an app has been backgrounded or + * suspended. + * If 'scratchResourcesOnly' is true all unlocked scratch resources will be purged but the + * unlocked resources with persistent data will remain. If 'scratchResourcesOnly' is false + * then all unlocked resources will be purged. + * In either case, after the unlocked resources are purged a separate pass will be made to + * ensure that resource usage is under budget (i.e., even if 'scratchResourcesOnly' is true + * some resources with persistent data may be purged to be under budget). + * + * @param scratchResourcesOnly If true only unlocked scratch resources will be purged prior + * enforcing the budget requirements. + */ + void purgeUnlockedResources(bool scratchResourcesOnly); + + /** + * Gets the maximum supported texture size. + */ + int maxTextureSize() const; + + /** + * Gets the maximum supported render target size. + */ + int maxRenderTargetSize() const; + + /** + * Can a SkImage be created with the given color type. + */ + bool colorTypeSupportedAsImage(SkColorType) const; + + /** + * Can a SkSurface be created with the given color type. To check whether MSAA is supported + * use maxSurfaceSampleCountForColorType(). + */ + bool colorTypeSupportedAsSurface(SkColorType colorType) const { + if (kR8G8_unorm_SkColorType == colorType || + kR16G16_unorm_SkColorType == colorType || + kA16_unorm_SkColorType == colorType || + kA16_float_SkColorType == colorType || + kR16G16_float_SkColorType == colorType || + kR16G16B16A16_unorm_SkColorType == colorType || + kGray_8_SkColorType == colorType) { + return false; + } + + return this->maxSurfaceSampleCountForColorType(colorType) > 0; + } + + /** + * Gets the maximum supported sample count for a color type. 1 is returned if only non-MSAA + * rendering is supported for the color type. 0 is returned if rendering to this color type + * is not supported at all. + */ + int maxSurfaceSampleCountForColorType(SkColorType) const; + + /////////////////////////////////////////////////////////////////////////// + // Misc. + + + /** + * Inserts a list of GPU semaphores that the current GPU-backed API must wait on before + * executing any more commands on the GPU. Skia will take ownership of the underlying semaphores + * and delete them once they have been signaled and waited on. If this call returns false, then + * the GPU back-end will not wait on any passed in semaphores, and the client will still own the + * semaphores. + */ + bool wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores); + + /** + * Call to ensure all drawing to the context has been issued to the underlying 3D API. + */ + void flush() { + this->flush(GrFlushInfo(), GrPrepareForExternalIORequests()); + } + + /** + * Call to ensure all drawing to the context has been issued to the underlying 3D API. + * + * If this call returns GrSemaphoresSubmitted::kNo, the GPU backend will not have created or + * added any semaphores to signal on the GPU. Thus the client should not have the GPU wait on + * any of the semaphores passed in with the GrFlushInfo. However, any pending commands to the + * context will still be flushed. It should be emphasized that a return value of + * GrSemaphoresSubmitted::kNo does not mean the flush did not happen. It simply means there were + * no semaphores submitted to the GPU. A caller should only take this as a failure if they + * passed in semaphores to be submitted. + */ + GrSemaphoresSubmitted flush(const GrFlushInfo& info) { + return this->flush(info, GrPrepareForExternalIORequests()); + } + + /** + * Call to ensure all drawing to the context has been issued to the underlying 3D API. + * + * If this call returns GrSemaphoresSubmitted::kNo, the GPU backend will not have created or + * added any semaphores to signal on the GPU. Thus the client should not have the GPU wait on + * any of the semaphores passed in with the GrFlushInfo. However, any pending commands to the + * context will still be flushed. It should be emphasized that a return value of + * GrSemaphoresSubmitted::kNo does not mean the flush did not happen. It simply means there were + * no semaphores submitted to the GPU. A caller should only take this as a failure if they + * passed in semaphores to be submitted. + * + * If the GrPrepareForExternalIORequests contains valid gpu backed SkSurfaces or SkImages, Skia + * will put the underlying backend objects into a state that is ready for external uses. See + * declaration of GrPreopareForExternalIORequests for more details. + */ + GrSemaphoresSubmitted flush(const GrFlushInfo&, const GrPrepareForExternalIORequests&); + + /** + * Deprecated. + */ + GrSemaphoresSubmitted flush(GrFlushFlags flags, int numSemaphores, + GrBackendSemaphore signalSemaphores[], + GrGpuFinishedProc finishedProc = nullptr, + GrGpuFinishedContext finishedContext = nullptr) { + GrFlushInfo info; + info.fFlags = flags; + info.fNumSemaphores = numSemaphores; + info.fSignalSemaphores = signalSemaphores; + info.fFinishedProc = finishedProc; + info.fFinishedContext = finishedContext; + return this->flush(info); + } + + /** + * Deprecated. + */ + GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores, + GrBackendSemaphore signalSemaphores[]) { + GrFlushInfo info; + info.fNumSemaphores = numSemaphores; + info.fSignalSemaphores = signalSemaphores; + return this->flush(info); + } + + /** + * Checks whether any asynchronous work is complete and if so calls related callbacks. + */ + void checkAsyncWorkCompletion(); + + // Provides access to functions that aren't part of the public API. + GrContextPriv priv(); + const GrContextPriv priv() const; + + /** Enumerates all cached GPU resources and dumps their memory to traceMemoryDump. */ + // Chrome is using this! + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const; + + bool supportsDistanceFieldText() const; + + void storeVkPipelineCacheData(); + + // Returns the gpu memory size of the the texture that backs the passed in SkImage. Returns 0 if + // the SkImage is not texture backed. + static size_t ComputeImageSize(sk_sp image, GrMipMapped, bool useNextPow2 = false); + + /* + * Retrieve the default GrBackendFormat for a given SkColorType and renderability. + * It is guaranteed that this backend format will be the one used by the following + * SkColorType and SkSurfaceCharacterization-based createBackendTexture methods. + * + * The caller should check that the returned format is valid. + */ + GrBackendFormat defaultBackendFormat(SkColorType ct, GrRenderable renderable) const { + return INHERITED::defaultBackendFormat(ct, renderable); + } + + /* + * The explicitly allocated backend texture API allows clients to use Skia to create backend + * objects outside of Skia proper (i.e., Skia's caching system will not know about them.) + * + * It is the client's responsibility to delete all these objects (using deleteBackendTexture) + * before deleting the GrContext used to create them. Additionally, clients should only + * delete these objects on the thread for which that GrContext is active. + * + * The client is responsible for ensuring synchronization between different uses + * of the backend object (i.e., wrapping it in a surface, rendering to it, deleting the + * surface, rewrapping it in a image and drawing the image will require explicit + * sychronization on the client's part). + */ + + // If possible, create an uninitialized backend texture. The client should ensure that the + // returned backend texture is valid. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_UNDEFINED. + GrBackendTexture createBackendTexture(int width, int height, + const GrBackendFormat&, + GrMipMapped, + GrRenderable, + GrProtected = GrProtected::kNo); + + // If possible, create an uninitialized backend texture. The client should ensure that the + // returned backend texture is valid. + // If successful, the created backend texture will be compatible with the provided + // SkColorType. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_UNDEFINED. + GrBackendTexture createBackendTexture(int width, int height, + SkColorType, + GrMipMapped, + GrRenderable, + GrProtected = GrProtected::kNo); + + + // If possible, create an uninitialized backend texture that is compatible with the + // provided characterization. The client should ensure that the returned backend texture + // is valid. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_UNDEFINED. + GrBackendTexture createBackendTexture(const SkSurfaceCharacterization& characterization); + + // If possible, create a backend texture initialized to a particular color. The client should + // ensure that the returned backend texture is valid. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL if renderable is kNo + // and VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL if renderable is kYes + GrBackendTexture createBackendTexture(int width, int height, + const GrBackendFormat&, + const SkColor4f& color, + GrMipMapped, + GrRenderable, + GrProtected = GrProtected::kNo); + + // If possible, create a backend texture initialized to a particular color. The client should + // ensure that the returned backend texture is valid. + // If successful, the created backend texture will be compatible with the provided + // SkColorType. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL if renderable is kNo + // and VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL if renderable is kYes + GrBackendTexture createBackendTexture(int width, int height, + SkColorType, + const SkColor4f& color, + GrMipMapped, + GrRenderable, + GrProtected = GrProtected::kNo); + + // If possible, create a backend texture initialized to a particular color that is + // compatible with the provided characterization. The client should ensure that the + // returned backend texture is valid. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + GrBackendTexture createBackendTexture(const SkSurfaceCharacterization& characterization, + const SkColor4f& color); + + // If possible, create a backend texture initialized with the provided pixmap data. The client + // should ensure that the returned backend texture is valid. + // If successful, the created backend texture will be compatible with the provided + // pixmap(s). Compatible, in this case, means that the backend format will be the result + // of calling defaultBackendFormat on the base pixmap's colortype. + // If numLevels is 1 a non-mipMapped texture will result. If a mipMapped texture is desired + // the data for all the mipmap levels must be provided. In the mipmapped case all the + // colortypes of the provided pixmaps must be the same. Additionally, all the miplevels + // must be sized correctly (please see SkMipMap::ComputeLevelSize and ComputeLevelCount). + // Note: the pixmap's alphatypes and colorspaces are ignored. + // For the Vulkan backend the layout of the created VkImage will be: + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + // regardless of the renderability setting + GrBackendTexture createBackendTexture(const SkPixmap srcData[], int numLevels, + GrRenderable, GrProtected); + + // Helper version of above for a single level. + GrBackendTexture createBackendTexture(const SkPixmap& srcData, + GrRenderable renderable, + GrProtected isProtected) { + return this->createBackendTexture(&srcData, 1, renderable, isProtected); + } + + void deleteBackendTexture(GrBackendTexture); + + // This interface allows clients to pre-compile shaders and populate the runtime program cache. + // The key and data blobs should be the ones passed to the PersistentCache, in SkSL format. + // + // Steps to use this API: + // + // 1) Create a GrContext as normal, but set fPersistentCache on GrContextOptions to something + // that will save the cached shader blobs. Set fShaderCacheStrategy to kSkSL. This will + // ensure that the blobs are SkSL, and are suitable for pre-compilation. + // 2) Run your application, and save all of the key/data pairs that are fed to the cache. + // + // 3) Switch over to shipping your application. Include the key/data pairs from above. + // 4) At startup (or any convenient time), call precompileShader for each key/data pair. + // This will compile the SkSL to create a GL program, and populate the runtime cache. + // + // This is only guaranteed to work if the context/device used in step #2 are created in the + // same way as the one used in step #4, and the same GrContextOptions are specified. + // Using cached shader blobs on a different device or driver are undefined. + bool precompileShader(const SkData& key, const SkData& data); + +#ifdef SK_ENABLE_DUMP_GPU + /** Returns a string with detailed information about the context & GPU, in JSON format. */ + SkString dump() const; +#endif + +protected: + GrContext(GrBackendApi, const GrContextOptions&, int32_t contextID = SK_InvalidGenID); + + bool init(sk_sp, sk_sp) override; + + GrContext* asDirectContext() override { return this; } + + virtual GrAtlasManager* onGetAtlasManager() = 0; + + sk_sp fThreadSafeProxy; + +private: + // fTaskGroup must appear before anything that uses it (e.g. fGpu), so that it is destroyed + // after all of its users. Clients of fTaskGroup will generally want to ensure that they call + // wait() on it as they are being destroyed, to avoid the possibility of pending tasks being + // invoked after objects they depend upon have already been destroyed. + std::unique_ptr fTaskGroup; + sk_sp fGpu; + GrResourceCache* fResourceCache; + GrResourceProvider* fResourceProvider; + + bool fDidTestPMConversions; + // true if the PM/UPM conversion succeeded; false otherwise + bool fPMUPMConversionsRoundTrip; + + GrContextOptions::PersistentCache* fPersistentCache; + GrContextOptions::ShaderErrorHandler* fShaderErrorHandler; + + std::unique_ptr fMappedBufferManager; + + // TODO: have the GrClipStackClip use renderTargetContexts and rm this friending + friend class GrContextPriv; + + typedef GrRecordingContext INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrContextOptions.h b/gfx/skia/skia/include/gpu/GrContextOptions.h new file mode 100644 index 0000000000..6d1d6ca841 --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrContextOptions.h @@ -0,0 +1,270 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrContextOptions_DEFINED +#define GrContextOptions_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkTypes.h" +#include "include/gpu/GrDriverBugWorkarounds.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrTypesPriv.h" + +#include + +class SkExecutor; + +#if SK_SUPPORT_GPU +struct SK_API GrContextOptions { + enum class Enable { + /** Forces an option to be disabled. */ + kNo, + /** Forces an option to be enabled. */ + kYes, + /** + * Uses Skia's default behavior, which may use runtime properties (e.g. driver version). + */ + kDefault + }; + + enum class ShaderCacheStrategy { + kSkSL, + kBackendSource, + kBackendBinary, + }; + + /** + * Abstract class which stores Skia data in a cache that persists between sessions. Currently, + * Skia stores compiled shader binaries (only when glProgramBinary / glGetProgramBinary are + * supported) when provided a persistent cache, but this may extend to other data in the future. + */ + class SK_API PersistentCache { + public: + virtual ~PersistentCache() {} + + /** + * Returns the data for the key if it exists in the cache, otherwise returns null. + */ + virtual sk_sp load(const SkData& key) = 0; + + virtual void store(const SkData& key, const SkData& data) = 0; + }; + + /** + * Abstract class to report errors when compiling shaders. If fShaderErrorHandler is present, + * it will be called to report any compilation failures. Otherwise, failures will be reported + * via SkDebugf and asserts. + */ + class SK_API ShaderErrorHandler { + public: + virtual ~ShaderErrorHandler() {} + virtual void compileError(const char* shader, const char* errors) = 0; + }; + + GrContextOptions() {} + + // Suppress prints for the GrContext. + bool fSuppressPrints = false; + + /** Overrides: These options override feature detection using backend API queries. These + overrides can only reduce the feature set or limits, never increase them beyond the + detected values. */ + + int fMaxTextureSizeOverride = SK_MaxS32; + + /** the threshold in bytes above which we will use a buffer mapping API to map vertex and index + buffers to CPU memory in order to update them. A value of -1 means the GrContext should + deduce the optimal value for this platform. */ + int fBufferMapThreshold = -1; + + /** + * Executor to handle threaded work within Ganesh. If this is nullptr, then all work will be + * done serially on the main thread. To have worker threads assist with various tasks, set this + * to a valid SkExecutor instance. Currently, used for software path rendering, but may be used + * for other tasks. + */ + SkExecutor* fExecutor = nullptr; + + /** Construct mipmaps manually, via repeated downsampling draw-calls. This is used when + the driver's implementation (glGenerateMipmap) contains bugs. This requires mipmap + level and LOD control (ie desktop or ES3). */ + bool fDoManualMipmapping = false; + + /** + * Disables the use of coverage counting shortcuts to render paths. Coverage counting can cause + * artifacts along shared edges if care isn't taken to ensure both contours wind in the same + * direction. + */ + // FIXME: Once this is removed from Chrome and Android, rename to fEnable"". + bool fDisableCoverageCountingPaths = true; + + /** + * Disables distance field rendering for paths. Distance field computation can be expensive, + * and yields no benefit if a path is not rendered multiple times with different transforms. + */ + bool fDisableDistanceFieldPaths = false; + + /** + * If true this allows path mask textures to be cached. This is only really useful if paths + * are commonly rendered at the same scale and fractional translation. + */ + bool fAllowPathMaskCaching = true; + + /** + * If true, the GPU will not be used to perform YUV -> RGB conversion when generating + * textures from codec-backed images. + */ + bool fDisableGpuYUVConversion = false; + + /** + * The maximum size of cache textures used for Skia's Glyph cache. + */ + size_t fGlyphCacheTextureMaximumBytes = 2048 * 1024 * 4; + + /** + * Below this threshold size in device space distance field fonts won't be used. Distance field + * fonts don't support hinting which is more important at smaller sizes. A negative value means + * use the default threshold. + */ + float fMinDistanceFieldFontSize = -1.f; + + /** + * Above this threshold size in device space glyphs are drawn as individual paths. A negative + * value means use the default threshold. + */ + float fGlyphsAsPathsFontSize = -1.f; + + /** + * Can the glyph atlas use multiple textures. If allowed, the each texture's size is bound by + * fGlypheCacheTextureMaximumBytes. + */ + Enable fAllowMultipleGlyphCacheTextures = Enable::kDefault; + + /** + * Bugs on certain drivers cause stencil buffers to leak. This flag causes Skia to avoid + * allocating stencil buffers and use alternate rasterization paths, avoiding the leak. + */ + bool fAvoidStencilBuffers = false; + + /** + * If true, texture fetches from mip-mapped textures will be biased to read larger MIP levels. + * This has the effect of sharpening those textures, at the cost of some aliasing, and possible + * performance impact. + */ + bool fSharpenMipmappedTextures = false; + + /** + * Enables driver workaround to use draws instead of HW clears, e.g. glClear on the GL backend. + */ + Enable fUseDrawInsteadOfClear = Enable::kDefault; + + /** + * Allow Ganesh to more aggressively reorder operations. + * Eventually this will just be what is done and will not be optional. + */ + Enable fReduceOpsTaskSplitting = Enable::kDefault; + + /** + * Some ES3 contexts report the ES2 external image extension, but not the ES3 version. + * If support for external images is critical, enabling this option will cause Ganesh to limit + * shaders to the ES2 shading language in that situation. + */ + bool fPreferExternalImagesOverES3 = false; + + /** + * Disables correctness workarounds that are enabled for particular GPUs, OSes, or drivers. + * This does not affect code path choices that are made for perfomance reasons nor does it + * override other GrContextOption settings. + */ + bool fDisableDriverCorrectnessWorkarounds = false; + + /** + * Maximum number of GPU programs or pipelines to keep active in the runtime cache. + */ + int fRuntimeProgramCacheSize = 256; + + /** + * Cache in which to store compiled shader binaries between runs. + */ + PersistentCache* fPersistentCache = nullptr; + + /** + * This affects the usage of the PersistentCache. We can cache SkSL, backend source (GLSL), or + * backend binaries (GL program binaries). By default we cache binaries, but if the driver's + * binary loading/storing is believed to have bugs, this can be limited to caching GLSL. + * Caching GLSL strings still saves CPU work when a GL program is created. + */ + ShaderCacheStrategy fShaderCacheStrategy = ShaderCacheStrategy::kBackendBinary; + + /** + * If present, use this object to report shader compilation failures. If not, report failures + * via SkDebugf and assert. + */ + ShaderErrorHandler* fShaderErrorHandler = nullptr; + + /** + * Specifies the number of samples Ganesh should use when performing internal draws with MSAA or + * mixed samples (hardware capabilities permitting). + * + * If 0, Ganesh will disable internal code paths that use multisampling. + */ + int fInternalMultisampleCount = 4; + +#if GR_TEST_UTILS + /** + * Private options that are only meant for testing within Skia's tools. + */ + + /** + * If non-zero, overrides the maximum size of a tile for sw-backed images and bitmaps rendered + * by SkGpuDevice. + */ + int fMaxTileSizeOverride = 0; + + /** + * Prevents use of dual source blending, to test that all xfer modes work correctly without it. + */ + bool fSuppressDualSourceBlending = false; + + /** + * If true, the caps will never support geometry shaders. + */ + bool fSuppressGeometryShaders = false; + + /** + * Render everything in wireframe + */ + bool fWireframeMode = false; + + /** + * Enforces clearing of all textures when they're created. + */ + bool fClearAllTextures = false; + + /** + * Include or exclude specific GPU path renderers. + */ + GpuPathRenderers fGpuPathRenderers = GpuPathRenderers::kAll; +#endif + +#if SK_SUPPORT_ATLAS_TEXT + /** + * Controls whether distance field glyph vertices always have 3 components even when the view + * matrix does not have perspective. + */ + Enable fDistanceFieldGlyphVerticesAlwaysHaveW = Enable::kDefault; +#endif + + GrDriverBugWorkarounds fDriverBugWorkarounds; +}; +#else +struct GrContextOptions { + struct PersistentCache {}; +}; +#endif + +#endif diff --git a/gfx/skia/skia/include/gpu/GrContextThreadSafeProxy.h b/gfx/skia/skia/include/gpu/GrContextThreadSafeProxy.h new file mode 100644 index 0000000000..b329da0c8a --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrContextThreadSafeProxy.h @@ -0,0 +1,102 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrContextThreadSafeProxy_DEFINED +#define GrContextThreadSafeProxy_DEFINED + +#include "include/private/GrContext_Base.h" + +class GrBackendFormat; +class GrContextThreadSafeProxyPriv; +struct SkImageInfo; +class SkSurfaceCharacterization; + +/** + * Can be used to perform actions related to the generating GrContext in a thread safe manner. The + * proxy does not access the 3D API (e.g. OpenGL) that backs the generating GrContext. + */ +class SK_API GrContextThreadSafeProxy : public GrContext_Base { +public: + ~GrContextThreadSafeProxy() override; + + /** + * Create a surface characterization for a DDL that will be replayed into the GrContext + * that created this proxy. On failure the resulting characterization will be invalid (i.e., + * "!c.isValid()"). + * + * @param cacheMaxResourceBytes The max resource bytes limit that will be in effect when the + * DDL created with this characterization is replayed. + * Note: the contract here is that the DDL will be created as + * if it had a full 'cacheMaxResourceBytes' to use. If replayed + * into a GrContext that already has locked GPU memory, the + * replay can exceed the budget. To rephrase, all resource + * allocation decisions are made at record time and at playback + * time the budget limits will be ignored. + * @param ii The image info specifying properties of the SkSurface that + * the DDL created with this characterization will be replayed + * into. + * Note: Ganesh doesn't make use of the SkImageInfo's alphaType + * @param backendFormat Information about the format of the GPU surface that will + * back the SkSurface upon replay + * @param sampleCount The sample count of the SkSurface that the DDL created with + * this characterization will be replayed into + * @param origin The origin of the SkSurface that the DDL created with this + * characterization will be replayed into + * @param surfaceProps The surface properties of the SkSurface that the DDL created + * with this characterization will be replayed into + * @param isMipMapped Will the surface the DDL will be replayed into have space + * allocated for mipmaps? + * @param willUseGLFBO0 Will the surface the DDL will be replayed into be backed by GL + * FBO 0. This flag is only valid if using an GL backend. + * @param isTextureable Will the surface be able to act as a texture? + * @param isProtected Will the (Vulkan) surface be DRM protected? + */ + SkSurfaceCharacterization createCharacterization( + size_t cacheMaxResourceBytes, + const SkImageInfo& ii, const GrBackendFormat& backendFormat, + int sampleCount, GrSurfaceOrigin origin, + const SkSurfaceProps& surfaceProps, + bool isMipMapped, + bool willUseGLFBO0 = false, + bool isTextureable = true, + GrProtected isProtected = GrProtected::kNo); + + /* + * Retrieve the default GrBackendFormat for a given SkColorType and renderability. + * It is guaranteed that this backend format will be the one used by the following + * SkColorType and SkSurfaceCharacterization-based createBackendTexture methods. + * + * The caller should check that the returned format is valid. + */ + GrBackendFormat defaultBackendFormat(SkColorType ct, GrRenderable renderable) const { + return INHERITED::defaultBackendFormat(ct, renderable); + } + + bool operator==(const GrContextThreadSafeProxy& that) const { + // Each GrContext should only ever have a single thread-safe proxy. + SkASSERT((this == &that) == (this->contextID() == that.contextID())); + return this == &that; + } + + bool operator!=(const GrContextThreadSafeProxy& that) const { return !(*this == that); } + + // Provides access to functions that aren't part of the public API. + GrContextThreadSafeProxyPriv priv(); + const GrContextThreadSafeProxyPriv priv() const; + +private: + friend class GrContextThreadSafeProxyPriv; // for ctor and hidden methods + + // DDL TODO: need to add unit tests for backend & maybe options + GrContextThreadSafeProxy(GrBackendApi, const GrContextOptions&, uint32_t contextID); + + bool init(sk_sp, sk_sp) override; + + typedef GrContext_Base INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrDriverBugWorkarounds.h b/gfx/skia/skia/include/gpu/GrDriverBugWorkarounds.h new file mode 100644 index 0000000000..3f370346fd --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrDriverBugWorkarounds.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDriverBugWorkarounds_DEFINED +#define GrDriverBugWorkarounds_DEFINED + +// External embedders of Skia can override this to use their own list +// of workaround names. +#ifdef SK_GPU_WORKAROUNDS_HEADER +#include SK_GPU_WORKAROUNDS_HEADER +#else +// To regenerate this file, set gn arg "skia_generate_workarounds = true". +// This is not rebuilt by default to avoid embedders having to have extra +// build steps. +#include "include/gpu/GrDriverBugWorkaroundsAutogen.h" +#endif + +#include "include/core/SkTypes.h" + +#include +#include + +enum GrDriverBugWorkaroundType { +#define GPU_OP(type, name) type, + GPU_DRIVER_BUG_WORKAROUNDS(GPU_OP) +#undef GPU_OP + NUMBER_OF_GPU_DRIVER_BUG_WORKAROUND_TYPES +}; + +class SK_API GrDriverBugWorkarounds { + public: + GrDriverBugWorkarounds(); + explicit GrDriverBugWorkarounds(const std::vector& workarounds); + + GrDriverBugWorkarounds& operator=(const GrDriverBugWorkarounds&) = default; + + // Turn on any workarounds listed in |workarounds| (but don't turn any off). + void applyOverrides(const GrDriverBugWorkarounds& workarounds); + + ~GrDriverBugWorkarounds(); + +#define GPU_OP(type, name) bool name = false; + GPU_DRIVER_BUG_WORKAROUNDS(GPU_OP) +#undef GPU_OP +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrDriverBugWorkaroundsAutogen.h b/gfx/skia/skia/include/gpu/GrDriverBugWorkaroundsAutogen.h new file mode 100644 index 0000000000..a4db30e66d --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrDriverBugWorkaroundsAutogen.h @@ -0,0 +1,43 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is auto-generated from build_workaround_header.py +// DO NOT EDIT! + +#define GPU_DRIVER_BUG_WORKAROUNDS(GPU_OP) \ + GPU_OP(ADD_AND_TRUE_TO_LOOP_CONDITION, \ + add_and_true_to_loop_condition) \ + GPU_OP(DISABLE_BLEND_EQUATION_ADVANCED, \ + disable_blend_equation_advanced) \ + GPU_OP(DISABLE_DISCARD_FRAMEBUFFER, \ + disable_discard_framebuffer) \ + GPU_OP(DISABLE_TEXTURE_STORAGE, \ + disable_texture_storage) \ + GPU_OP(DISALLOW_LARGE_INSTANCED_DRAW, \ + disallow_large_instanced_draw) \ + GPU_OP(EMULATE_ABS_INT_FUNCTION, \ + emulate_abs_int_function) \ + GPU_OP(FLUSH_ON_FRAMEBUFFER_CHANGE, \ + flush_on_framebuffer_change) \ + GPU_OP(GL_CLEAR_BROKEN, \ + gl_clear_broken) \ + GPU_OP(MAX_FRAGMENT_UNIFORM_VECTORS_32, \ + max_fragment_uniform_vectors_32) \ + GPU_OP(MAX_MSAA_SAMPLE_COUNT_4, \ + max_msaa_sample_count_4) \ + GPU_OP(MAX_TEXTURE_SIZE_LIMIT_4096, \ + max_texture_size_limit_4096) \ + GPU_OP(PACK_PARAMETERS_WORKAROUND_WITH_PACK_BUFFER, \ + pack_parameters_workaround_with_pack_buffer) \ + GPU_OP(REMOVE_POW_WITH_CONSTANT_EXPONENT, \ + remove_pow_with_constant_exponent) \ + GPU_OP(RESTORE_SCISSOR_ON_FBO_CHANGE, \ + restore_scissor_on_fbo_change) \ + GPU_OP(REWRITE_DO_WHILE_LOOPS, \ + rewrite_do_while_loops) \ + GPU_OP(UNBIND_ATTACHMENTS_ON_BOUND_RENDER_FBO_DELETE, \ + unbind_attachments_on_bound_render_fbo_delete) \ + GPU_OP(UNFOLD_SHORT_CIRCUIT_AS_TERNARY_OPERATION, \ + unfold_short_circuit_as_ternary_operation) \ +// The End diff --git a/gfx/skia/skia/include/gpu/GrGpuResource.h b/gfx/skia/skia/include/gpu/GrGpuResource.h new file mode 100644 index 0000000000..3eb98381e8 --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrGpuResource.h @@ -0,0 +1,321 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGpuResource_DEFINED +#define GrGpuResource_DEFINED + +#include "include/private/GrResourceKey.h" +#include "include/private/GrTypesPriv.h" +#include "include/private/SkNoncopyable.h" + +class GrContext; +class GrGpu; +class GrResourceCache; +class SkTraceMemoryDump; + +/** + * Base class for GrGpuResource. Provides the hooks for resources to interact with the cache. + * Separated out as a base class to isolate the ref-cnting behavior and provide friendship without + * exposing all of GrGpuResource. + * + * PRIOR to the last ref being removed DERIVED::notifyRefCntWillBeZero() will be called + * (static poly morphism using CRTP). It is legal for additional ref's to be added + * during this time. AFTER the ref count reaches zero DERIVED::notifyRefCntIsZero() will be + * called. + */ +template class GrIORef : public SkNoncopyable { +public: + bool unique() const { return fRefCnt == 1; } + + void ref() const { + // Only the cache should be able to add the first ref to a resource. + SkASSERT(this->getRefCnt() > 0); + // No barrier required. + (void)fRefCnt.fetch_add(+1, std::memory_order_relaxed); + } + + void unref() const { + SkASSERT(this->getRefCnt() > 0); + if (1 == fRefCnt.fetch_add(-1, std::memory_order_acq_rel)) { + // At this point we better be the only thread accessing this resource. + // Trick out the notifyRefCntWillBeZero() call by adding back one more ref. + fRefCnt.fetch_add(+1, std::memory_order_relaxed); + static_cast(this)->notifyRefCntWillBeZero(); + // notifyRefCntWillBeZero() could have done anything, including re-refing this and + // passing on to another thread. Take away the ref-count we re-added above and see + // if we're back to zero. + // TODO: Consider making it so that refs can't be added and merge + // notifyRefCntWillBeZero()/willRemoveLastRef() with notifyRefCntIsZero(). + if (1 == fRefCnt.fetch_add(-1, std::memory_order_acq_rel)) { + static_cast(this)->notifyRefCntIsZero(); + } + } + } + +#if GR_TEST_UTILS + int32_t testingOnly_getRefCnt() const { return this->getRefCnt(); } +#endif + +protected: + friend class GrResourceCache; // for internalHasRef + + GrIORef() : fRefCnt(1) {} + + bool internalHasRef() const { return SkToBool(this->getRefCnt()); } + + // Privileged method that allows going from ref count = 0 to ref count = 1. + void addInitialRef() const { + SkASSERT(fRefCnt >= 0); + // No barrier required. + (void)fRefCnt.fetch_add(+1, std::memory_order_relaxed); + } + +private: + int32_t getRefCnt() const { return fRefCnt.load(std::memory_order_relaxed); } + + mutable std::atomic fRefCnt; + + typedef SkNoncopyable INHERITED; +}; + +/** + * Base class for objects that can be kept in the GrResourceCache. + */ +class SK_API GrGpuResource : public GrIORef { +public: + /** + * Tests whether a object has been abandoned or released. All objects will + * be in this state after their creating GrContext is destroyed or has + * contextLost called. It's up to the client to test wasDestroyed() before + * attempting to use an object if it holds refs on objects across + * ~GrContext, freeResources with the force flag, or contextLost. + * + * @return true if the object has been released or abandoned, + * false otherwise. + */ + bool wasDestroyed() const { return nullptr == fGpu; } + + /** + * Retrieves the context that owns the object. Note that it is possible for + * this to return NULL. When objects have been release()ed or abandon()ed + * they no longer have an owning context. Destroying a GrContext + * automatically releases all its resources. + */ + const GrContext* getContext() const; + GrContext* getContext(); + + /** + * Retrieves the amount of GPU memory used by this resource in bytes. It is + * approximate since we aren't aware of additional padding or copies made + * by the driver. + * + * @return the amount of GPU memory used in bytes + */ + size_t gpuMemorySize() const { + if (kInvalidGpuMemorySize == fGpuMemorySize) { + fGpuMemorySize = this->onGpuMemorySize(); + SkASSERT(kInvalidGpuMemorySize != fGpuMemorySize); + } + return fGpuMemorySize; + } + + class UniqueID { + public: + UniqueID() = default; + + explicit UniqueID(uint32_t id) : fID(id) {} + + uint32_t asUInt() const { return fID; } + + bool operator==(const UniqueID& other) const { return fID == other.fID; } + bool operator!=(const UniqueID& other) const { return !(*this == other); } + + void makeInvalid() { fID = SK_InvalidUniqueID; } + bool isInvalid() const { return fID == SK_InvalidUniqueID; } + + protected: + uint32_t fID = SK_InvalidUniqueID; + }; + + /** + * Gets an id that is unique for this GrGpuResource object. It is static in that it does + * not change when the content of the GrGpuResource object changes. This will never return + * 0. + */ + UniqueID uniqueID() const { return fUniqueID; } + + /** Returns the current unique key for the resource. It will be invalid if the resource has no + associated unique key. */ + const GrUniqueKey& getUniqueKey() const { return fUniqueKey; } + + /** + * Internal-only helper class used for manipulations of the resource by the cache. + */ + class CacheAccess; + inline CacheAccess cacheAccess(); + inline const CacheAccess cacheAccess() const; + + /** + * Internal-only helper class used for manipulations of the resource by GrSurfaceProxy. + */ + class ProxyAccess; + inline ProxyAccess proxyAccess(); + + /** + * Internal-only helper class used for manipulations of the resource by internal code. + */ + class ResourcePriv; + inline ResourcePriv resourcePriv(); + inline const ResourcePriv resourcePriv() const; + + /** + * Dumps memory usage information for this GrGpuResource to traceMemoryDump. + * Typically, subclasses should not need to override this, and should only + * need to override setMemoryBacking. + **/ + virtual void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const; + + /** + * Describes the type of gpu resource that is represented by the implementing + * class (e.g. texture, buffer object, stencil). This data is used for diagnostic + * purposes by dumpMemoryStatistics(). + * + * The value returned is expected to be long lived and will not be copied by the caller. + */ + virtual const char* getResourceType() const = 0; + + static uint32_t CreateUniqueID(); + +protected: + // This must be called by every non-wrapped GrGpuObject. It should be called once the object is + // fully initialized (i.e. only from the constructors of the final class). + void registerWithCache(SkBudgeted); + + // This must be called by every GrGpuObject that references any wrapped backend objects. It + // should be called once the object is fully initialized (i.e. only from the constructors of the + // final class). + void registerWithCacheWrapped(GrWrapCacheable); + + GrGpuResource(GrGpu*); + virtual ~GrGpuResource(); + + GrGpu* getGpu() const { return fGpu; } + + /** Overridden to free GPU resources in the backend API. */ + virtual void onRelease() { } + /** Overridden to abandon any internal handles, ptrs, etc to backend API resources. + This may be called when the underlying 3D context is no longer valid and so no + backend API calls should be made. */ + virtual void onAbandon() { } + + /** + * Allows subclasses to add additional backing information to the SkTraceMemoryDump. + **/ + virtual void setMemoryBacking(SkTraceMemoryDump*, const SkString&) const {} + + /** + * Returns a string that uniquely identifies this resource. + */ + SkString getResourceName() const; + + /** + * A helper for subclasses that override dumpMemoryStatistics(). This method using a format + * consistent with the default implementation of dumpMemoryStatistics() but allows the caller + * to customize various inputs. + */ + void dumpMemoryStatisticsPriv(SkTraceMemoryDump* traceMemoryDump, const SkString& resourceName, + const char* type, size_t size) const; + + +private: + bool isPurgeable() const; + bool hasRef() const; + + /** + * Called by the registerWithCache if the resource is available to be used as scratch. + * Resource subclasses should override this if the instances should be recycled as scratch + * resources and populate the scratchKey with the key. + * By default resources are not recycled as scratch. + **/ + virtual void computeScratchKey(GrScratchKey*) const {} + + /** + * Removes references to objects in the underlying 3D API without freeing them. + * Called by CacheAccess. + */ + void abandon(); + + /** + * Frees the object in the underlying 3D API. Called by CacheAccess. + */ + void release(); + + virtual size_t onGpuMemorySize() const = 0; + + /** + * Called by GrIORef when a resource is about to lose its last ref + */ + virtual void willRemoveLastRef() {} + + // See comments in CacheAccess and ResourcePriv. + void setUniqueKey(const GrUniqueKey&); + void removeUniqueKey(); + void notifyRefCntWillBeZero() const; + void notifyRefCntIsZero() const; + void removeScratchKey(); + void makeBudgeted(); + void makeUnbudgeted(); + +#ifdef SK_DEBUG + friend class GrGpu; // for assert in GrGpu to access getGpu +#endif + + // An index into a heap when this resource is purgeable or an array when not. This is maintained + // by the cache. + int fCacheArrayIndex; + // This value reflects how recently this resource was accessed in the cache. This is maintained + // by the cache. + uint32_t fTimestamp; + GrStdSteadyClock::time_point fTimeWhenBecamePurgeable; + + static const size_t kInvalidGpuMemorySize = ~static_cast(0); + GrScratchKey fScratchKey; + GrUniqueKey fUniqueKey; + + // This is not ref'ed but abandon() or release() will be called before the GrGpu object + // is destroyed. Those calls set will this to NULL. + GrGpu* fGpu; + mutable size_t fGpuMemorySize = kInvalidGpuMemorySize; + + GrBudgetedType fBudgetedType = GrBudgetedType::kUnbudgetedUncacheable; + bool fRefsWrappedObjects = false; + const UniqueID fUniqueID; + + typedef GrIORef INHERITED; + friend class GrIORef; // to access notifyRefCntWillBeZero and notifyRefCntIsZero. +}; + +class GrGpuResource::ProxyAccess { +private: + ProxyAccess(GrGpuResource* resource) : fResource(resource) {} + + /** Proxies are allowed to take a resource from no refs to one ref. */ + void ref(GrResourceCache* cache); + + // No taking addresses of this type. + const CacheAccess* operator&() const = delete; + CacheAccess* operator&() = delete; + + GrGpuResource* fResource; + + friend class GrGpuResource; + friend class GrSurfaceProxy; +}; + +inline GrGpuResource::ProxyAccess GrGpuResource::proxyAccess() { return ProxyAccess(this); } + +#endif diff --git a/gfx/skia/skia/include/gpu/GrSurface.h b/gfx/skia/skia/include/gpu/GrSurface.h new file mode 100644 index 0000000000..b57ef02b6b --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrSurface.h @@ -0,0 +1,157 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSurface_DEFINED +#define GrSurface_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkRect.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrGpuResource.h" +#include "include/gpu/GrTypes.h" + +class GrRenderTarget; +class GrSurfacePriv; +class GrTexture; + +class GrSurface : public GrGpuResource { +public: + /** + * Retrieves the width of the surface. + */ + int width() const { return fWidth; } + + /** + * Retrieves the height of the surface. + */ + int height() const { return fHeight; } + + /** + * Helper that gets the width and height of the surface as a bounding rectangle. + */ + SkRect getBoundsRect() const { return SkRect::MakeIWH(this->width(), this->height()); } + + /** + * Retrieves the pixel config specified when the surface was created. + * For render targets this can be kUnknown_GrPixelConfig + * if client asked us to render to a target that has a pixel + * config that isn't equivalent with one of our configs. + */ + GrPixelConfig config() const { return fConfig; } + + virtual GrBackendFormat backendFormat() const = 0; + + SK_API void setRelease(sk_sp releaseHelper) { + this->onSetRelease(releaseHelper); + fReleaseHelper = std::move(releaseHelper); + } + + // These match the definitions in SkImage, from whence they came. + // TODO: Remove Chrome's need to call this on a GrTexture + typedef void* ReleaseCtx; + typedef void (*ReleaseProc)(ReleaseCtx); + SK_API void setRelease(ReleaseProc proc, ReleaseCtx ctx) { + sk_sp helper(new GrRefCntedCallback(proc, ctx)); + this->setRelease(std::move(helper)); + } + + /** + * @return the texture associated with the surface, may be null. + */ + virtual GrTexture* asTexture() { return nullptr; } + virtual const GrTexture* asTexture() const { return nullptr; } + + /** + * @return the render target underlying this surface, may be null. + */ + virtual GrRenderTarget* asRenderTarget() { return nullptr; } + virtual const GrRenderTarget* asRenderTarget() const { return nullptr; } + + /** Access methods that are only to be used within Skia code. */ + inline GrSurfacePriv surfacePriv(); + inline const GrSurfacePriv surfacePriv() const; + + static size_t ComputeSize(const GrCaps&, const GrBackendFormat&, int width, int height, + int colorSamplesPerPixel, GrMipMapped, bool binSize = false); + + /** + * The pixel values of this surface cannot be modified (e.g. doesn't support write pixels or + * MIP map level regen). + */ + bool readOnly() const { return fSurfaceFlags & GrInternalSurfaceFlags::kReadOnly; } + + // Returns true if we are working with protected content. + bool isProtected() const { return fIsProtected == GrProtected::kYes; } + +protected: + void setGLRTFBOIDIs0() { + SkASSERT(!this->requiresManualMSAAResolve()); + SkASSERT(!this->asTexture()); + SkASSERT(this->asRenderTarget()); + fSurfaceFlags |= GrInternalSurfaceFlags::kGLRTFBOIDIs0; + } + bool glRTFBOIDis0() const { + return fSurfaceFlags & GrInternalSurfaceFlags::kGLRTFBOIDIs0; + } + + void setRequiresManualMSAAResolve() { + SkASSERT(!this->glRTFBOIDis0()); + SkASSERT(this->asRenderTarget()); + fSurfaceFlags |= GrInternalSurfaceFlags::kRequiresManualMSAAResolve; + } + bool requiresManualMSAAResolve() const { + return fSurfaceFlags & GrInternalSurfaceFlags::kRequiresManualMSAAResolve; + } + + void setReadOnly() { + SkASSERT(!this->asRenderTarget()); + fSurfaceFlags |= GrInternalSurfaceFlags::kReadOnly; + } + + // Provides access to methods that should be public within Skia code. + friend class GrSurfacePriv; + + GrSurface(GrGpu* gpu, const SkISize& size, GrPixelConfig config, GrProtected isProtected) + : INHERITED(gpu) + , fConfig(config) + , fWidth(size.width()) + , fHeight(size.height()) + , fSurfaceFlags(GrInternalSurfaceFlags::kNone) + , fIsProtected(isProtected) {} + + ~GrSurface() override { + // check that invokeReleaseProc has been called (if needed) + SkASSERT(!fReleaseHelper); + } + + void onRelease() override; + void onAbandon() override; + +private: + const char* getResourceType() const override { return "Surface"; } + + // Unmanaged backends (e.g. Vulkan) may want to specially handle the release proc in order to + // ensure it isn't called until GPU work related to the resource is completed. + virtual void onSetRelease(sk_sp) {} + + void invokeReleaseProc() { + // Depending on the ref count of fReleaseHelper this may or may not actually trigger the + // ReleaseProc to be called. + fReleaseHelper.reset(); + } + + GrPixelConfig fConfig; + int fWidth; + int fHeight; + GrInternalSurfaceFlags fSurfaceFlags; + GrProtected fIsProtected; + sk_sp fReleaseHelper; + + typedef GrGpuResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrTexture.h b/gfx/skia/skia/include/gpu/GrTexture.h new file mode 100644 index 0000000000..ad861d5aee --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrTexture.h @@ -0,0 +1,102 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTexture_DEFINED +#define GrTexture_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrSurface.h" +#include "include/private/GrTypesPriv.h" + +class GrTexturePriv; + +class GrTexture : virtual public GrSurface { +public: + GrTexture* asTexture() override { return this; } + const GrTexture* asTexture() const override { return this; } + + virtual GrBackendTexture getBackendTexture() const = 0; + + /** + * This function indicates that the texture parameters (wrap mode, filtering, ...) have been + * changed externally to Skia. + */ + SK_API virtual void textureParamsModified() = 0; + + /** + * This function steals the backend texture from a uniquely owned GrTexture with no pending + * IO, passing it out to the caller. The GrTexture is deleted in the process. + * + * Note that if the GrTexture is not uniquely owned (no other refs), or has pending IO, this + * function will fail. + */ + static bool StealBackendTexture(sk_sp, + GrBackendTexture*, + SkImage::BackendTextureReleaseProc*); + + /** See addIdleProc. */ + enum class IdleState { + kFlushed, + kFinished + }; + /** + * Installs a proc on this texture. It will be called when the texture becomes "idle". There + * are two types of idle states as indicated by IdleState. For managed backends (e.g. GL where + * a driver typically handles CPU/GPU synchronization of resource access) there is no difference + * between the two. They both mean "all work related to the resource has been flushed to the + * backend API and the texture is not owned outside the resource cache". + * + * If the API is unmanaged (e.g. Vulkan) then kFinished has the additional constraint that the + * work flushed to the GPU is finished. + */ + virtual void addIdleProc(sk_sp idleProc, IdleState) { + // This is the default implementation for the managed case where the IdleState can be + // ignored. Unmanaged backends, e.g. Vulkan, must override this to consider IdleState. + fIdleProcs.push_back(std::move(idleProc)); + } + /** Helper version of addIdleProc that creates the ref-counted wrapper. */ + void addIdleProc(GrRefCntedCallback::Callback callback, + GrRefCntedCallback::Context context, + IdleState state) { + this->addIdleProc(sk_make_sp(callback, context), state); + } + + /** Access methods that are only to be used within Skia code. */ + inline GrTexturePriv texturePriv(); + inline const GrTexturePriv texturePriv() const; + +protected: + GrTexture(GrGpu*, const SkISize&, GrPixelConfig, GrProtected, GrTextureType, GrMipMapsStatus); + + virtual bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) = 0; + + SkTArray> fIdleProcs; + + void willRemoveLastRef() override { + // We're about to be idle in the resource cache. Do our part to trigger the idle callbacks. + fIdleProcs.reset(); + } + void computeScratchKey(GrScratchKey*) const override; + +private: + size_t onGpuMemorySize() const override; + void markMipMapsDirty(); + void markMipMapsClean(); + + GrTextureType fTextureType; + GrMipMapsStatus fMipMapsStatus; + int fMaxMipMapLevel; + friend class GrTexturePriv; + + typedef GrSurface INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/GrTypes.h b/gfx/skia/skia/include/gpu/GrTypes.h new file mode 100644 index 0000000000..679a0fa96b --- /dev/null +++ b/gfx/skia/skia/include/gpu/GrTypes.h @@ -0,0 +1,349 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTypes_DEFINED +#define GrTypes_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkTypes.h" +#include "include/gpu/GrConfig.h" + +class GrBackendSemaphore; +class SkImage; +class SkSurface; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Defines overloaded bitwise operators to make it easier to use an enum as a + * bitfield. + */ +#define GR_MAKE_BITFIELD_OPS(X) \ + inline X operator |(X a, X b) { \ + return (X) (+a | +b); \ + } \ + inline X& operator |=(X& a, X b) { \ + return (a = a | b); \ + } \ + inline X operator &(X a, X b) { \ + return (X) (+a & +b); \ + } \ + inline X& operator &=(X& a, X b) { \ + return (a = a & b); \ + } \ + template \ + inline X operator &(T a, X b) { \ + return (X) (+a & +b); \ + } \ + template \ + inline X operator &(X a, T b) { \ + return (X) (+a & +b); \ + } \ + +#define GR_DECL_BITFIELD_OPS_FRIENDS(X) \ + friend X operator |(X a, X b); \ + friend X& operator |=(X& a, X b); \ + \ + friend X operator &(X a, X b); \ + friend X& operator &=(X& a, X b); \ + \ + template \ + friend X operator &(T a, X b); \ + \ + template \ + friend X operator &(X a, T b); \ + +/** + * Wraps a C++11 enum that we use as a bitfield, and enables a limited amount of + * masking with type safety. Instantiated with the ~ operator. + */ +template class GrTFlagsMask { +public: + constexpr explicit GrTFlagsMask(TFlags value) : GrTFlagsMask(static_cast(value)) {} + constexpr explicit GrTFlagsMask(int value) : fValue(value) {} + constexpr int value() const { return fValue; } +private: + const int fValue; +}; + +// Or-ing a mask always returns another mask. +template constexpr GrTFlagsMask operator|(GrTFlagsMask a, + GrTFlagsMask b) { + return GrTFlagsMask(a.value() | b.value()); +} +template constexpr GrTFlagsMask operator|(GrTFlagsMask a, + TFlags b) { + return GrTFlagsMask(a.value() | static_cast(b)); +} +template constexpr GrTFlagsMask operator|(TFlags a, + GrTFlagsMask b) { + return GrTFlagsMask(static_cast(a) | b.value()); +} +template inline GrTFlagsMask& operator|=(GrTFlagsMask& a, + GrTFlagsMask b) { + return (a = a | b); +} + +// And-ing two masks returns another mask; and-ing one with regular flags returns flags. +template constexpr GrTFlagsMask operator&(GrTFlagsMask a, + GrTFlagsMask b) { + return GrTFlagsMask(a.value() & b.value()); +} +template constexpr TFlags operator&(GrTFlagsMask a, TFlags b) { + return static_cast(a.value() & static_cast(b)); +} +template constexpr TFlags operator&(TFlags a, GrTFlagsMask b) { + return static_cast(static_cast(a) & b.value()); +} +template inline TFlags& operator&=(TFlags& a, GrTFlagsMask b) { + return (a = a & b); +} + +/** + * Defines bitwise operators that make it possible to use an enum class as a + * basic bitfield. + */ +#define GR_MAKE_BITFIELD_CLASS_OPS(X) \ + constexpr GrTFlagsMask operator~(X a) { \ + return GrTFlagsMask(~static_cast(a)); \ + } \ + constexpr X operator|(X a, X b) { \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + inline X& operator|=(X& a, X b) { \ + return (a = a | b); \ + } \ + constexpr bool operator&(X a, X b) { \ + return SkToBool(static_cast(a) & static_cast(b)); \ + } \ + +#define GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(X) \ + friend constexpr GrTFlagsMask operator ~(X); \ + friend constexpr X operator |(X, X); \ + friend X& operator |=(X&, X); \ + friend constexpr bool operator &(X, X) + +//////////////////////////////////////////////////////////////////////////////// + +// compile time versions of min/max +#define GR_CT_MAX(a, b) (((b) < (a)) ? (a) : (b)) +#define GR_CT_MIN(a, b) (((b) < (a)) ? (b) : (a)) + +/** + * divide, rounding up + */ +static inline constexpr int32_t GrIDivRoundUp(int x, int y) { + SkASSERT(y > 0); + return (x + (y-1)) / y; +} +static inline constexpr uint32_t GrUIDivRoundUp(uint32_t x, uint32_t y) { + return (x + (y-1)) / y; +} +static inline constexpr size_t GrSizeDivRoundUp(size_t x, size_t y) { return (x + (y - 1)) / y; } + +/** + * align up + */ +static inline constexpr uint32_t GrUIAlignUp(uint32_t x, uint32_t alignment) { + return GrUIDivRoundUp(x, alignment) * alignment; +} +static inline constexpr size_t GrSizeAlignUp(size_t x, size_t alignment) { + return GrSizeDivRoundUp(x, alignment) * alignment; +} + +/** + * amount of pad needed to align up + */ +static inline constexpr uint32_t GrUIAlignUpPad(uint32_t x, uint32_t alignment) { + return (alignment - x % alignment) % alignment; +} +static inline constexpr size_t GrSizeAlignUpPad(size_t x, size_t alignment) { + return (alignment - x % alignment) % alignment; +} + +/** + * align down + */ +static inline constexpr uint32_t GrUIAlignDown(uint32_t x, uint32_t alignment) { + return (x / alignment) * alignment; +} +static inline constexpr size_t GrSizeAlignDown(size_t x, uint32_t alignment) { + return (x / alignment) * alignment; +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Possible 3D APIs that may be used by Ganesh. + */ +enum class GrBackendApi : unsigned { + kMetal, + kDawn, + kOpenGL, + kVulkan, + /** + * Mock is a backend that does not draw anything. It is used for unit tests + * and to measure CPU overhead. + */ + kMock, + + /** + * Added here to support the legacy GrBackend enum value and clients who referenced it using + * GrBackend::kOpenGL_GrBackend. + */ + kOpenGL_GrBackend = kOpenGL, +}; + +/** + * Previously the above enum was not an enum class but a normal enum. To support the legacy use of + * the enum values we define them below so that no clients break. + */ +typedef GrBackendApi GrBackend; + +static constexpr GrBackendApi kMetal_GrBackend = GrBackendApi::kMetal; +static constexpr GrBackendApi kVulkan_GrBackend = GrBackendApi::kVulkan; +static constexpr GrBackendApi kMock_GrBackend = GrBackendApi::kMock; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Used to say whether a texture has mip levels allocated or not. + */ +enum class GrMipMapped : bool { + kNo = false, + kYes = true +}; + +/* + * Can a GrBackendObject be rendered to? + */ +enum class GrRenderable : bool { + kNo = false, + kYes = true +}; + +/* + * Used to say whether texture is backed by protected memory. + */ +enum class GrProtected : bool { + kNo = false, + kYes = true +}; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * GPU SkImage and SkSurfaces can be stored such that (0, 0) in texture space may correspond to + * either the top-left or bottom-left content pixel. + */ +enum GrSurfaceOrigin : int { + kTopLeft_GrSurfaceOrigin, + kBottomLeft_GrSurfaceOrigin, +}; + +/** + * A GrContext's cache of backend context state can be partially invalidated. + * These enums are specific to the GL backend and we'd add a new set for an alternative backend. + */ +enum GrGLBackendState { + kRenderTarget_GrGLBackendState = 1 << 0, + // Also includes samplers bound to texture units. + kTextureBinding_GrGLBackendState = 1 << 1, + // View state stands for scissor and viewport + kView_GrGLBackendState = 1 << 2, + kBlend_GrGLBackendState = 1 << 3, + kMSAAEnable_GrGLBackendState = 1 << 4, + kVertex_GrGLBackendState = 1 << 5, + kStencil_GrGLBackendState = 1 << 6, + kPixelStore_GrGLBackendState = 1 << 7, + kProgram_GrGLBackendState = 1 << 8, + kFixedFunction_GrGLBackendState = 1 << 9, + kMisc_GrGLBackendState = 1 << 10, + kPathRendering_GrGLBackendState = 1 << 11, + kALL_GrGLBackendState = 0xffff +}; + +/** + * This value translates to reseting all the context state for any backend. + */ +static const uint32_t kAll_GrBackendState = 0xffffffff; + +enum GrFlushFlags { + kNone_GrFlushFlags = 0, + // flush will wait till all submitted GPU work is finished before returning. + kSyncCpu_GrFlushFlag = 0x1, +}; + +typedef void* GrGpuFinishedContext; +typedef void (*GrGpuFinishedProc)(GrGpuFinishedContext finishedContext); + +/** + * Struct to supply options to flush calls. + * + * After issuing all commands, fNumSemaphore semaphores will be signaled by the gpu. The client + * passes in an array of fNumSemaphores GrBackendSemaphores. In general these GrBackendSemaphore's + * can be either initialized or not. If they are initialized, the backend uses the passed in + * semaphore. If it is not initialized, a new semaphore is created and the GrBackendSemaphore + * object is initialized with that semaphore. + * + * The client will own and be responsible for deleting the underlying semaphores that are stored + * and returned in initialized GrBackendSemaphore objects. The GrBackendSemaphore objects + * themselves can be deleted as soon as this function returns. + * + * If a finishedProc is provided, the finishedProc will be called when all work submitted to the gpu + * from this flush call and all previous flush calls has finished on the GPU. If the flush call + * fails due to an error and nothing ends up getting sent to the GPU, the finished proc is called + * immediately. + */ +struct GrFlushInfo { + GrFlushFlags fFlags = kNone_GrFlushFlags; + int fNumSemaphores = 0; + GrBackendSemaphore* fSignalSemaphores = nullptr; + GrGpuFinishedProc fFinishedProc = nullptr; + GrGpuFinishedContext fFinishedContext = nullptr; +}; + +/** + * Enum used as return value when flush with semaphores so the client knows whether the semaphores + * were submitted to GPU or not. + */ +enum class GrSemaphoresSubmitted : bool { + kNo = false, + kYes = true +}; + +/** + * Array of SkImages and SkSurfaces which Skia will prepare for external use when passed into a + * flush call on GrContext. All the SkImages and SkSurfaces must be GPU backed. + * + * If fPrepareSurfaceForPresent is not nullptr, then it must be an array the size of fNumSurfaces. + * Each entry in the array corresponds to the SkSurface at the same index in the fSurfaces array. If + * an entry is true, then that surface will be prepared for both external use and present. + * + * Currently this only has an effect if the backend API is Vulkan. In this case, all the underlying + * VkImages associated with the SkImages and SkSurfaces will be transitioned into the VkQueueFamily + * in which they were originally wrapped or created with. This allows a client to wrap a VkImage + * from a queue which is different from the graphics queue and then have Skia transition it back to + * that queue without needing to delete the SkImage or SkSurface. If the an SkSurface is also + * flagged to be prepared for present, then its VkImageLayout will be set to + * VK_IMAGE_LAYOUT_PRESENT_SRC_KHR if the VK_KHR_swapchain extension has been enabled for the + * GrContext and the original queue is not VK_QUEUE_FAMILY_EXTERNAL or VK_QUEUE_FAMILY_FOREIGN_EXT. + * + * If an SkSurface or SkImage is used again, it will be transitioned back to the graphics queue and + * whatever layout is needed for its use. + */ +struct GrPrepareForExternalIORequests { + int fNumImages = 0; + SkImage** fImages = nullptr; + int fNumSurfaces = 0; + SkSurface** fSurfaces = nullptr; + bool* fPrepareSurfaceForPresent = nullptr; + + bool hasRequests() const { return fNumImages || fNumSurfaces; } +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/dawn/GrDawnTypes.h b/gfx/skia/skia/include/gpu/dawn/GrDawnTypes.h new file mode 100644 index 0000000000..f8936c29d8 --- /dev/null +++ b/gfx/skia/skia/include/gpu/dawn/GrDawnTypes.h @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnTypes_DEFINED +#define GrDawnTypes_DEFINED + +#ifdef Always +#undef Always +#endif +#include "dawn/dawncpp.h" + +struct GrDawnImageInfo { + dawn::Texture fTexture; + dawn::TextureFormat fFormat; + uint32_t fLevelCount; + GrDawnImageInfo() : fTexture(nullptr), fFormat(), fLevelCount(0) { + } + GrDawnImageInfo(const GrDawnImageInfo& other) + : fTexture(other.fTexture) + , fFormat(other.fFormat) + , fLevelCount(other.fLevelCount) { + } + GrDawnImageInfo& operator=(const GrDawnImageInfo& other) { + fTexture = other.fTexture; + fFormat = other.fFormat; + fLevelCount = other.fLevelCount; + return *this; + } + bool operator==(const GrDawnImageInfo& other) const { + return fTexture.Get() == other.fTexture.Get() && + fFormat == other.fFormat && + fLevelCount == other.fLevelCount; + } +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/gl/GrGLAssembleHelpers.h b/gfx/skia/skia/include/gpu/gl/GrGLAssembleHelpers.h new file mode 100644 index 0000000000..bfa2aea376 --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLAssembleHelpers.h @@ -0,0 +1,11 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/gl/GrGLAssembleInterface.h" + +void GrGetEGLQueryAndDisplay(GrEGLQueryStringFn** queryString, GrEGLDisplay* display, + void* ctx, GrGLGetProc get); diff --git a/gfx/skia/skia/include/gpu/gl/GrGLAssembleInterface.h b/gfx/skia/skia/include/gpu/gl/GrGLAssembleInterface.h new file mode 100644 index 0000000000..4f9f9f9ee0 --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLAssembleInterface.h @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/gl/GrGLInterface.h" + +typedef GrGLFuncPtr (*GrGLGetProc)(void* ctx, const char name[]); + +/** + * Generic function for creating a GrGLInterface for an either OpenGL or GLES. It calls + * get() to get each function address. ctx is a generic ptr passed to and interpreted by get(). + */ +SK_API sk_sp GrGLMakeAssembledInterface(void *ctx, GrGLGetProc get); + +/** + * Generic function for creating a GrGLInterface for an OpenGL (but not GLES) context. It calls + * get() to get each function address. ctx is a generic ptr passed to and interpreted by get(). + */ +SK_API sk_sp GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get); + +/** + * Generic function for creating a GrGLInterface for an OpenGL ES (but not Open GL) context. It + * calls get() to get each function address. ctx is a generic ptr passed to and interpreted by + * get(). + */ +SK_API sk_sp GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get); + +/** + * Generic function for creating a GrGLInterface for a WebGL (similar to OpenGL ES) context. It + * calls get() to get each function address. ctx is a generic ptr passed to and interpreted by + * get(). + */ +SK_API sk_sp GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get); + +/** Deprecated version of GrGLMakeAssembledInterface() that returns a bare pointer. */ +SK_API const GrGLInterface* GrGLAssembleInterface(void *ctx, GrGLGetProc get); diff --git a/gfx/skia/skia/include/gpu/gl/GrGLConfig.h b/gfx/skia/skia/include/gpu/gl/GrGLConfig.h new file mode 100644 index 0000000000..d6c49f3405 --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLConfig.h @@ -0,0 +1,99 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#ifndef GrGLConfig_DEFINED +#define GrGLConfig_DEFINED + +#include "include/gpu/GrTypes.h" + +/** + * Optional GL config file. + */ +#ifdef GR_GL_CUSTOM_SETUP_HEADER + #include GR_GL_CUSTOM_SETUP_HEADER +#endif + +#if !defined(GR_GL_FUNCTION_TYPE) + #if defined(SK_BUILD_FOR_WIN) + #define GR_GL_FUNCTION_TYPE __stdcall + #else + #define GR_GL_FUNCTION_TYPE + #endif +#endif + +/** + * The following are optional defines that can be enabled at the compiler + * command line, in a IDE project, in a GrUserConfig.h file, or in a GL custom + * file (if one is in use). If a GR_GL_CUSTOM_SETUP_HEADER is used they can + * also be placed there. + * + * GR_GL_LOG_CALLS: if 1 Gr can print every GL call using SkDebugf. Defaults to + * 0. Logging can be enabled and disabled at runtime using a debugger via to + * global gLogCallsGL. The initial value of gLogCallsGL is controlled by + * GR_GL_LOG_CALLS_START. + * + * GR_GL_LOG_CALLS_START: controls the initial value of gLogCallsGL when + * GR_GL_LOG_CALLS is 1. Defaults to 0. + * + * GR_GL_CHECK_ERROR: if enabled Gr can do a glGetError() after every GL call. + * Defaults to 1 if SK_DEBUG is set, otherwise 0. When GR_GL_CHECK_ERROR is 1 + * this can be toggled in a debugger using the gCheckErrorGL global. The initial + * value of gCheckErrorGL is controlled by by GR_GL_CHECK_ERROR_START. + * + * GR_GL_CHECK_ERROR_START: controls the initial value of gCheckErrorGL + * when GR_GL_CHECK_ERROR is 1. Defaults to 1. + * + * GR_GL_CHECK_ALLOC_WITH_GET_ERROR: If set to 1 this will then glTexImage, + * glBufferData, glRenderbufferStorage, etc will be checked for errors. This + * amounts to ensuring the error is GL_NO_ERROR, calling the allocating + * function, and then checking that the error is still GL_NO_ERROR. When the + * value is 0 we will assume no error was generated without checking. + * + * GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT: We will normally check the FBO status + * every time we bind a texture or renderbuffer to an FBO. However, in some + * environments CheckFrameBufferStatus is very expensive. If this is set we will + * check the first time we use a color format or a combination of color / + * stencil formats as attachments. If the FBO is complete we will assume + * subsequent attachments with the same formats are complete as well. + * + * GR_GL_MUST_USE_VBO: Indicates that all vertices and indices must be rendered + * from VBOs. Chromium's command buffer doesn't allow glVertexAttribArray with + * ARARY_BUFFER 0 bound or glDrawElements with ELEMENT_ARRAY_BUFFER 0 bound. + */ + +#if !defined(GR_GL_LOG_CALLS) + #ifdef SK_DEBUG + #define GR_GL_LOG_CALLS 1 + #else + #define GR_GL_LOG_CALLS 0 + #endif +#endif + +#if !defined(GR_GL_LOG_CALLS_START) + #define GR_GL_LOG_CALLS_START 0 +#endif + +#if !defined(GR_GL_CHECK_ERROR) + #ifdef SK_DEBUG + #define GR_GL_CHECK_ERROR 1 + #else + #define GR_GL_CHECK_ERROR 0 + #endif +#endif + +#if !defined(GR_GL_CHECK_ERROR_START) + #define GR_GL_CHECK_ERROR_START 1 +#endif + +#if !defined(GR_GL_CHECK_ALLOC_WITH_GET_ERROR) + #define GR_GL_CHECK_ALLOC_WITH_GET_ERROR 1 +#endif + +#endif diff --git a/gfx/skia/skia/include/gpu/gl/GrGLConfig_chrome.h b/gfx/skia/skia/include/gpu/gl/GrGLConfig_chrome.h new file mode 100644 index 0000000000..683cd97884 --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLConfig_chrome.h @@ -0,0 +1,22 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrGLConfig_chrome_DEFINED +#define GrGLConfig_chrome_DEFINED + +// glGetError() forces a sync with gpu process on chrome +#define GR_GL_CHECK_ERROR_START 0 + +// Check error is even more expensive in chrome (cmd buffer flush). The +// compositor also doesn't check its allocations. +#define GR_GL_CHECK_ALLOC_WITH_GET_ERROR 0 + +#if !defined(GR_GL_IGNORE_ES3_MSAA) + #define GR_GL_IGNORE_ES3_MSAA 1 +#endif + +#endif diff --git a/gfx/skia/skia/include/gpu/gl/GrGLExtensions.h b/gfx/skia/skia/include/gpu/gl/GrGLExtensions.h new file mode 100644 index 0000000000..1e2823f71a --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLExtensions.h @@ -0,0 +1,78 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLExtensions_DEFINED +#define GrGLExtensions_DEFINED + +#include "include/core/SkString.h" +#include "include/gpu/gl/GrGLFunctions.h" +#include "include/private/SkTArray.h" + +#include + +struct GrGLInterface; +class SkJSONWriter; + +/** + * This helper queries the current GL context for its extensions, remembers them, and can be + * queried. It supports both glGetString- and glGetStringi-style extension string APIs and will + * use the latter if it is available. It also will query for EGL extensions if a eglQueryString + * implementation is provided. + */ +class SK_API GrGLExtensions { +public: + GrGLExtensions() {} + + GrGLExtensions(const GrGLExtensions&); + + GrGLExtensions& operator=(const GrGLExtensions&); + + void swap(GrGLExtensions* that) { + using std::swap; + swap(fStrings, that->fStrings); + swap(fInitialized, that->fInitialized); + } + + /** + * We sometimes need to use this class without having yet created a GrGLInterface. This version + * of init expects that getString is always non-NULL while getIntegerv and getStringi are non- + * NULL if on desktop GL with version 3.0 or higher. Otherwise it will fail. + */ + bool init(GrGLStandard standard, + GrGLFunction getString, + GrGLFunction getStringi, + GrGLFunction getIntegerv, + GrGLFunction queryString = nullptr, + GrEGLDisplay eglDisplay = nullptr); + + bool isInitialized() const { return fInitialized; } + + /** + * Queries whether an extension is present. This will fail if init() has not been called. + */ + bool has(const char[]) const; + + /** + * Removes an extension if present. Returns true if the extension was present before the call. + */ + bool remove(const char[]); + + /** + * Adds an extension to list + */ + void add(const char[]); + + void reset() { fStrings.reset(); } + + void dumpJSON(SkJSONWriter*) const; + +private: + bool fInitialized = false; + SkTArray fStrings; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/gl/GrGLFunctions.h b/gfx/skia/skia/include/gpu/gl/GrGLFunctions.h new file mode 100644 index 0000000000..b0585fd3d8 --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLFunctions.h @@ -0,0 +1,320 @@ + +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLFunctions_DEFINED +#define GrGLFunctions_DEFINED + +#include +#include "include/gpu/gl/GrGLTypes.h" +#include "include/private/SkTLogic.h" + + +extern "C" { + +/////////////////////////////////////////////////////////////////////////////// + +using GrGLActiveTextureFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum texture); +using GrGLAttachShaderFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLuint shader); +using GrGLBeginQueryFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLuint id); +using GrGLBindAttribLocationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLuint index, const char* name); +using GrGLBindBufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLuint buffer); +using GrGLBindFramebufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLuint framebuffer); +using GrGLBindRenderbufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLuint renderbuffer); +using GrGLBindTextureFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLuint texture); +using GrGLBindFragDataLocationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLuint colorNumber, const GrGLchar* name); +using GrGLBindFragDataLocationIndexedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLuint colorNumber, GrGLuint index, const GrGLchar* name); +using GrGLBindSamplerFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint unit, GrGLuint sampler); +using GrGLBindVertexArrayFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint array); +using GrGLBlendBarrierFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLBlendColorFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLclampf red, GrGLclampf green, GrGLclampf blue, GrGLclampf alpha); +using GrGLBlendEquationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode); +using GrGLBlendFuncFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum sfactor, GrGLenum dfactor); +using GrGLBlitFramebufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint srcX0, GrGLint srcY0, GrGLint srcX1, GrGLint srcY1, GrGLint dstX0, GrGLint dstY0, GrGLint dstX1, GrGLint dstY1, GrGLbitfield mask, GrGLenum filter); +using GrGLBufferDataFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizeiptr size, const GrGLvoid* data, GrGLenum usage); +using GrGLBufferSubDataFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLintptr offset, GrGLsizeiptr size, const GrGLvoid* data); +using GrGLCheckFramebufferStatusFn = GrGLenum GR_GL_FUNCTION_TYPE(GrGLenum target); +using GrGLClearFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLbitfield mask); +using GrGLClearColorFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLclampf red, GrGLclampf green, GrGLclampf blue, GrGLclampf alpha); +using GrGLClearStencilFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint s); +using GrGLClearTexImageFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint texture, GrGLint level, GrGLenum format, GrGLenum type, const GrGLvoid* data); +using GrGLClearTexSubImageFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint texture, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint zoffset, GrGLsizei width, GrGLsizei height, GrGLsizei depth, GrGLenum format, GrGLenum type, const GrGLvoid* data); +using GrGLColorMaskFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLboolean red, GrGLboolean green, GrGLboolean blue, GrGLboolean alpha); +using GrGLCompileShaderFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader); +using GrGLCompressedTexImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLenum internalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLsizei imageSize, const GrGLvoid* data); +using GrGLCompressedTexSubImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLsizei imageSize, const GrGLvoid* data); +using GrGLCopyTexSubImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height); +using GrGLCreateProgramFn = GrGLuint GR_GL_FUNCTION_TYPE(); +using GrGLCreateShaderFn = GrGLuint GR_GL_FUNCTION_TYPE(GrGLenum type); +using GrGLCullFaceFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode); +using GrGLDeleteBuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLuint* buffers); +using GrGLDeleteFramebuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLuint* framebuffers); +using GrGLDeleteProgramFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program); +using GrGLDeleteQueriesFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLuint* ids); +using GrGLDeleteRenderbuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLuint* renderbuffers); +using GrGLDeleteSamplersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei count, const GrGLuint* samplers); +using GrGLDeleteShaderFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader); +using GrGLDeleteTexturesFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLuint* textures); +using GrGLDeleteVertexArraysFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLuint* arrays); +using GrGLDepthMaskFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLboolean flag); +using GrGLDisableFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum cap); +using GrGLDisableVertexAttribArrayFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint index); +using GrGLDrawArraysFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLint first, GrGLsizei count); +using GrGLDrawArraysInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLint first, GrGLsizei count, GrGLsizei primcount); +using GrGLDrawArraysIndirectFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, const GrGLvoid* indirect); +using GrGLDrawBufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode); +using GrGLDrawBuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, const GrGLenum* bufs); +using GrGLDrawElementsFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLsizei count, GrGLenum type, const GrGLvoid* indices); +using GrGLDrawElementsInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLsizei count, GrGLenum type, const GrGLvoid* indices, GrGLsizei primcount); +using GrGLDrawElementsIndirectFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLenum type, const GrGLvoid* indirect); +using GrGLDrawRangeElementsFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLuint start, GrGLuint end, GrGLsizei count, GrGLenum type, const GrGLvoid* indices); +using GrGLEnableFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum cap); +using GrGLEnableVertexAttribArrayFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint index); +using GrGLEndQueryFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target); +using GrGLFinishFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLFlushFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLFlushMappedBufferRangeFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLintptr offset, GrGLsizeiptr length); +using GrGLFramebufferRenderbufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum attachment, GrGLenum renderbuffertarget, GrGLuint renderbuffer); +using GrGLFramebufferTexture2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level); +using GrGLFramebufferTexture2DMultisampleFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level, GrGLsizei samples); +using GrGLFrontFaceFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode); +using GrGLGenBuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, GrGLuint* buffers); +using GrGLGenFramebuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, GrGLuint* framebuffers); +using GrGLGenerateMipmapFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target); +using GrGLGenQueriesFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, GrGLuint* ids); +using GrGLGenRenderbuffersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, GrGLuint* renderbuffers); +using GrGLGenSamplersFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei count, GrGLuint* samplers); +using GrGLGenTexturesFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, GrGLuint* textures); +using GrGLGenVertexArraysFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei n, GrGLuint* arrays); +using GrGLGetBufferParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, GrGLint* params); +using GrGLGetErrorFn = GrGLenum GR_GL_FUNCTION_TYPE(); +using GrGLGetFramebufferAttachmentParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum attachment, GrGLenum pname, GrGLint* params); +using GrGLGetIntegervFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum pname, GrGLint* params); +using GrGLGetMultisamplefvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum pname, GrGLuint index, GrGLfloat* val); +using GrGLGetProgramBinaryFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLsizei bufsize, GrGLsizei* length, GrGLenum* binaryFormat, void* binary); +using GrGLGetProgramInfoLogFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLsizei bufsize, GrGLsizei* length, char* infolog); +using GrGLGetProgramivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLenum pname, GrGLint* params); +using GrGLGetQueryivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum GLtarget, GrGLenum pname, GrGLint* params); +using GrGLGetQueryObjecti64vFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint id, GrGLenum pname, GrGLint64* params); +using GrGLGetQueryObjectivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint id, GrGLenum pname, GrGLint* params); +using GrGLGetQueryObjectui64vFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint id, GrGLenum pname, GrGLuint64* params); +using GrGLGetQueryObjectuivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint id, GrGLenum pname, GrGLuint* params); +using GrGLGetRenderbufferParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, GrGLint* params); +using GrGLGetShaderInfoLogFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader, GrGLsizei bufsize, GrGLsizei* length, char* infolog); +using GrGLGetShaderivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader, GrGLenum pname, GrGLint* params); +using GrGLGetShaderPrecisionFormatFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum shadertype, GrGLenum precisiontype, GrGLint* range, GrGLint* precision); +using GrGLGetStringFn = const GrGLubyte* GR_GL_FUNCTION_TYPE(GrGLenum name); +using GrGLGetStringiFn = const GrGLubyte* GR_GL_FUNCTION_TYPE(GrGLenum name, GrGLuint index); +using GrGLGetTexLevelParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLenum pname, GrGLint* params); +using GrGLGetUniformLocationFn = GrGLint GR_GL_FUNCTION_TYPE(GrGLuint program, const char* name); +using GrGLInsertEventMarkerFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei length, const char* marker); +using GrGLInvalidateBufferDataFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint buffer); +using GrGLInvalidateBufferSubDataFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr length); +using GrGLInvalidateFramebufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizei numAttachments, const GrGLenum* attachments); +using GrGLInvalidateSubFramebufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizei numAttachments, const GrGLenum* attachments, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height); +using GrGLInvalidateTexImageFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint texture, GrGLint level); +using GrGLInvalidateTexSubImageFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint texture, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint zoffset, GrGLsizei width, GrGLsizei height, GrGLsizei depth); +using GrGLIsTextureFn = GrGLboolean GR_GL_FUNCTION_TYPE(GrGLuint texture); +using GrGLLineWidthFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLfloat width); +using GrGLLinkProgramFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program); +using GrGLMapBufferFn = GrGLvoid* GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum access); +using GrGLMapBufferRangeFn = GrGLvoid* GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLintptr offset, GrGLsizeiptr length, GrGLbitfield access); +using GrGLMapBufferSubDataFn = GrGLvoid* GR_GL_FUNCTION_TYPE(GrGLuint target, GrGLintptr offset, GrGLsizeiptr size, GrGLenum access); +using GrGLMapTexSubImage2DFn = GrGLvoid* GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLenum access); +using GrGLPixelStoreiFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum pname, GrGLint param); +using GrGLPolygonModeFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum face, GrGLenum mode); +using GrGLPopGroupMarkerFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLProgramBinaryFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLenum binaryFormat, void* binary, GrGLsizei length); +using GrGLProgramParameteriFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLenum pname, GrGLint value); +using GrGLPushGroupMarkerFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei length, const char* marker); +using GrGLQueryCounterFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint id, GrGLenum target); +using GrGLReadBufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum src); +using GrGLReadPixelsFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLvoid* pixels); +using GrGLRenderbufferStorageFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLsizei width, GrGLsizei height); +using GrGLRenderbufferStorageMultisampleFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizei samples, GrGLenum internalformat, GrGLsizei width, GrGLsizei height); +using GrGLResolveMultisampleFramebufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLSamplerParameteriFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint sampler, GrGLenum pname, GrGLint params); +using GrGLSamplerParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint sampler, GrGLenum pname, const GrGLint* params); +using GrGLScissorFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height); +// GL_CHROMIUM_bind_uniform_location +using GrGLBindUniformLocationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLint location, const char* name); +using GrGLShaderSourceFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader, GrGLsizei count, const char* const* str, const GrGLint* length); +using GrGLStencilFuncFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum func, GrGLint ref, GrGLuint mask); +using GrGLStencilFuncSeparateFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum face, GrGLenum func, GrGLint ref, GrGLuint mask); +using GrGLStencilMaskFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint mask); +using GrGLStencilMaskSeparateFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum face, GrGLuint mask); +using GrGLStencilOpFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum fail, GrGLenum zfail, GrGLenum zpass); +using GrGLStencilOpSeparateFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum face, GrGLenum fail, GrGLenum zfail, GrGLenum zpass); +using GrGLTexBufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLuint buffer); +using GrGLTexBufferRangeFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr size); +using GrGLTexImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLint internalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid* pixels); +using GrGLTexParameterfFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, GrGLfloat param); +using GrGLTexParameterfvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, const GrGLfloat* params); +using GrGLTexParameteriFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, GrGLint param); +using GrGLTexParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, const GrGLint* params); +using GrGLTexStorage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizei levels, GrGLenum internalformat, GrGLsizei width, GrGLsizei height); +using GrGLDiscardFramebufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizei numAttachments, const GrGLenum* attachments); +using GrGLTexSubImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, const GrGLvoid* pixels); +using GrGLTextureBarrierFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLUniform1fFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLfloat v0); +using GrGLUniform1iFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLint v0); +using GrGLUniform1fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLfloat* v); +using GrGLUniform1ivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLint* v); +using GrGLUniform2fFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLfloat v0, GrGLfloat v1); +using GrGLUniform2iFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLint v0, GrGLint v1); +using GrGLUniform2fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLfloat* v); +using GrGLUniform2ivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLint* v); +using GrGLUniform3fFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLfloat v0, GrGLfloat v1, GrGLfloat v2); +using GrGLUniform3iFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2); +using GrGLUniform3fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLfloat* v); +using GrGLUniform3ivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLint* v); +using GrGLUniform4fFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLfloat v0, GrGLfloat v1, GrGLfloat v2, GrGLfloat v3); +using GrGLUniform4iFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2, GrGLint v3); +using GrGLUniform4fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLfloat* v); +using GrGLUniform4ivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, const GrGLint* v); +using GrGLUniformMatrix2fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, GrGLboolean transpose, const GrGLfloat* value); +using GrGLUniformMatrix3fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, GrGLboolean transpose, const GrGLfloat* value); +using GrGLUniformMatrix4fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint location, GrGLsizei count, GrGLboolean transpose, const GrGLfloat* value); +using GrGLUnmapBufferFn = GrGLboolean GR_GL_FUNCTION_TYPE(GrGLenum target); +using GrGLUnmapBufferSubDataFn = GrGLvoid GR_GL_FUNCTION_TYPE(const GrGLvoid* mem); +using GrGLUnmapTexSubImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(const GrGLvoid* mem); +using GrGLUseProgramFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program); +using GrGLVertexAttrib1fFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint indx, const GrGLfloat value); +using GrGLVertexAttrib2fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint indx, const GrGLfloat* values); +using GrGLVertexAttrib3fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint indx, const GrGLfloat* values); +using GrGLVertexAttrib4fvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint indx, const GrGLfloat* values); +using GrGLVertexAttribDivisorFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint index, GrGLuint divisor); +using GrGLVertexAttribIPointerFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint indx, GrGLint size, GrGLenum type, GrGLsizei stride, const GrGLvoid* ptr); +using GrGLVertexAttribPointerFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint indx, GrGLint size, GrGLenum type, GrGLboolean normalized, GrGLsizei stride, const GrGLvoid* ptr); +using GrGLViewportFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height); + +/* GL_NV_path_rendering */ +using GrGLMatrixLoadfFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum matrixMode, const GrGLfloat* m); +using GrGLMatrixLoadIdentityFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum); +using GrGLPathCommandsFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLsizei numCommands, const GrGLubyte* commands, GrGLsizei numCoords, GrGLenum coordType, const GrGLvoid* coords); +using GrGLPathParameteriFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLenum pname, GrGLint value); +using GrGLPathParameterfFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLenum pname, GrGLfloat value); +using GrGLGenPathsFn = GrGLuint GR_GL_FUNCTION_TYPE(GrGLsizei range); +using GrGLDeletePathsFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLsizei range); +using GrGLIsPathFn = GrGLboolean GR_GL_FUNCTION_TYPE(GrGLuint path); +using GrGLPathStencilFuncFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum func, GrGLint ref, GrGLuint mask); +using GrGLStencilFillPathFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLenum fillMode, GrGLuint mask); +using GrGLStencilStrokePathFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLint reference, GrGLuint mask); +using GrGLStencilFillPathInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid* paths, GrGLuint pathBase, GrGLenum fillMode, GrGLuint mask, GrGLenum transformType, const GrGLfloat* transformValues); +using GrGLStencilStrokePathInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid* paths, GrGLuint pathBase, GrGLint reference, GrGLuint mask, GrGLenum transformType, const GrGLfloat* transformValues); +using GrGLCoverFillPathFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLenum coverMode); +using GrGLCoverStrokePathFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint name, GrGLenum coverMode); +using GrGLCoverFillPathInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid* paths, GrGLuint pathBase, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat* transformValues); +using GrGLCoverStrokePathInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid* paths, GrGLuint pathBase, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat* transformValues); +// NV_path_rendering v1.2 +using GrGLStencilThenCoverFillPathFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLenum fillMode, GrGLuint mask, GrGLenum coverMode); +using GrGLStencilThenCoverStrokePathFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint path, GrGLint reference, GrGLuint mask, GrGLenum coverMode); +using GrGLStencilThenCoverFillPathInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid* paths, GrGLuint pathBase, GrGLenum fillMode, GrGLuint mask, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat* transformValues); +using GrGLStencilThenCoverStrokePathInstancedFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid* paths, GrGLuint pathBase, GrGLint reference, GrGLuint mask, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat* transformValues); +// NV_path_rendering v1.3 +using GrGLProgramPathFragmentInputGenFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLint location, GrGLenum genMode, GrGLint components, const GrGLfloat* coeffs); +// CHROMIUM_path_rendering +using GrGLBindFragmentInputLocationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLint location, const GrGLchar* name); + +/* ARB_program_interface_query */ +using GrGLGetProgramResourceLocationFn = GrGLint GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLenum programInterface, const GrGLchar* name); + +/* GL_NV_framebuffer_mixed_samples */ +using GrGLCoverageModulationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum components); + +/* EXT_multi_draw_indirect */ +using GrGLMultiDrawArraysIndirectFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, const GrGLvoid* indirect, GrGLsizei drawcount, GrGLsizei stride); +using GrGLMultiDrawElementsIndirectFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLenum type, const GrGLvoid* indirect, GrGLsizei drawcount, GrGLsizei stride); + +/* ARB_sync */ +using GrGLFenceSyncFn = GrGLsync GR_GL_FUNCTION_TYPE(GrGLenum condition, GrGLbitfield flags); +using GrGLIsSyncFn = GrGLboolean GR_GL_FUNCTION_TYPE(GrGLsync sync); +using GrGLClientWaitSyncFn = GrGLenum GR_GL_FUNCTION_TYPE(GrGLsync sync, GrGLbitfield flags, GrGLuint64 timeout); +using GrGLWaitSyncFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsync sync, GrGLbitfield flags, GrGLuint64 timeout); +using GrGLDeleteSyncFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsync sync); + +/* ARB_internalformat_query */ +using GrGLGetInternalformativFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLenum pname, GrGLsizei bufSize, GrGLint* params); + +/* KHR_debug */ +using GrGLDebugMessageControlFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum source, GrGLenum type, GrGLenum severity, GrGLsizei count, const GrGLuint* ids, GrGLboolean enabled); +using GrGLDebugMessageInsertFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum source, GrGLenum type, GrGLuint id, GrGLenum severity, GrGLsizei length, const GrGLchar* buf); +using GrGLDebugMessageCallbackFn = GrGLvoid GR_GL_FUNCTION_TYPE(GRGLDEBUGPROC callback, const GrGLvoid* userParam); +using GrGLGetDebugMessageLogFn = GrGLuint GR_GL_FUNCTION_TYPE(GrGLuint count, GrGLsizei bufSize, GrGLenum* sources, GrGLenum* types, GrGLuint* ids, GrGLenum* severities, GrGLsizei* lengths, GrGLchar* messageLog); +using GrGLPushDebugGroupFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum source, GrGLuint id, GrGLsizei length, const GrGLchar* message); +using GrGLPopDebugGroupFn = GrGLvoid GR_GL_FUNCTION_TYPE(); +using GrGLObjectLabelFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum identifier, GrGLuint name, GrGLsizei length, const GrGLchar* label); + +/** EXT_window_rectangles */ +using GrGLWindowRectanglesFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLsizei count, const GrGLint box[]); + +/** GL_QCOM_tiled_rendering */ +using GrGLStartTilingFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint x, GrGLuint y, GrGLuint width, GrGLuint height, GrGLbitfield preserveMask); +using GrGLEndTilingFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLbitfield preserveMask); + +/** EGL functions */ +using GrEGLQueryStringFn = const char* GR_GL_FUNCTION_TYPE(GrEGLDisplay dpy, GrEGLint name); +using GrEGLGetCurrentDisplayFn = GrEGLDisplay GR_GL_FUNCTION_TYPE(); +using GrEGLCreateImageFn = GrEGLImage GR_GL_FUNCTION_TYPE(GrEGLDisplay dpy, GrEGLContext ctx, GrEGLenum target, GrEGLClientBuffer buffer, const GrEGLint* attrib_list); +using GrEGLDestroyImageFn = GrEGLBoolean GR_GL_FUNCTION_TYPE(GrEGLDisplay dpy, GrEGLImage image); +} // extern "C" + +// This is a lighter-weight std::function, trying to reduce code size and compile time +// by only supporting the exact use cases we require. +template class GrGLFunction; + +template +class GrGLFunction { +public: + using Fn = R GR_GL_FUNCTION_TYPE(Args...); + // Construct empty. + GrGLFunction() = default; + GrGLFunction(std::nullptr_t) {} + + // Construct from a simple function pointer. + GrGLFunction(Fn* fn_ptr) { + static_assert(sizeof(fn_ptr) <= sizeof(fBuf), "fBuf is too small"); + if (fn_ptr) { + memcpy(fBuf, &fn_ptr, sizeof(fn_ptr)); + fCall = [](const void* buf, Args... args) { + return (*(Fn**)buf)(std::forward(args)...); + }; + } + } + + // Construct from a small closure. + template + GrGLFunction(Closure closure) : GrGLFunction() { + static_assert(sizeof(Closure) <= sizeof(fBuf), "fBuf is too small"); +#if defined(__APPLE__) // I am having serious trouble getting these to work with all STLs... + static_assert(std::is_trivially_copyable::value, ""); + static_assert(std::is_trivially_destructible::value, ""); +#endif + + memcpy(fBuf, &closure, sizeof(closure)); + fCall = [](const void* buf, Args... args) { + auto closure = (const Closure*)buf; + return (*closure)(args...); + }; + } + + R operator()(Args... args) const { + SkASSERT(fCall); + return fCall(fBuf, std::forward(args)...); + } + + explicit operator bool() const { return fCall != nullptr; } + + void reset() { fCall = nullptr; } + +private: + using Call = R(const void* buf, Args...); + Call* fCall = nullptr; + size_t fBuf[4]; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/gl/GrGLInterface.h b/gfx/skia/skia/include/gpu/gl/GrGLInterface.h new file mode 100644 index 0000000000..99735b7c94 --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLInterface.h @@ -0,0 +1,340 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLInterface_DEFINED +#define GrGLInterface_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/gl/GrGLExtensions.h" +#include "include/gpu/gl/GrGLFunctions.h" + +//////////////////////////////////////////////////////////////////////////////// + +typedef void(*GrGLFuncPtr)(); +struct GrGLInterface; + + +/** + * Rather than depend on platform-specific GL headers and libraries, we require + * the client to provide a struct of GL function pointers. This struct can be + * specified per-GrContext as a parameter to GrContext::MakeGL. If no interface is + * passed to MakeGL then a default GL interface is created using GrGLMakeNativeInterface(). + * If this returns nullptr then GrContext::MakeGL() will fail. + * + * The implementation of GrGLMakeNativeInterface is platform-specific. Several + * implementations have been provided (for GLX, WGL, EGL, etc), along with an + * implementation that simply returns nullptr. Clients should select the most + * appropriate one to build. + */ +SK_API sk_sp GrGLMakeNativeInterface(); +// Deprecated alternative to GrGLMakeNativeInterface(). +SK_API const GrGLInterface* GrGLCreateNativeInterface(); + +/** + * GrContext uses the following interface to make all calls into OpenGL. When a + * GrContext is created it is given a GrGLInterface. The interface's function + * pointers must be valid for the OpenGL context associated with the GrContext. + * On some platforms, such as Windows, function pointers for OpenGL extensions + * may vary between OpenGL contexts. So the caller must be careful to use a + * GrGLInterface initialized for the correct context. All functions that should + * be available based on the OpenGL's version and extension string must be + * non-NULL or GrContext creation will fail. This can be tested with the + * validate() method when the OpenGL context has been made current. + */ +struct SK_API GrGLInterface : public SkRefCnt { +private: + typedef SkRefCnt INHERITED; + +public: + GrGLInterface(); + + // Validates that the GrGLInterface supports its advertised standard. This means the necessary + // function pointers have been initialized for both the GL version and any advertised + // extensions. + bool validate() const; + + // Indicates the type of GL implementation + union { + GrGLStandard fStandard; + GrGLStandard fBindingsExported; // Legacy name, will be remove when Chromium is updated. + }; + + GrGLExtensions fExtensions; + + bool hasExtension(const char ext[]) const { return fExtensions.has(ext); } + + /** + * The function pointers are in a struct so that we can have a compiler generated assignment + * operator. + */ + struct Functions { + GrGLFunction fActiveTexture; + GrGLFunction fAttachShader; + GrGLFunction fBeginQuery; + GrGLFunction fBindAttribLocation; + GrGLFunction fBindBuffer; + GrGLFunction fBindFragDataLocation; + GrGLFunction fBindFragDataLocationIndexed; + GrGLFunction fBindFramebuffer; + GrGLFunction fBindRenderbuffer; + GrGLFunction fBindSampler; + GrGLFunction fBindTexture; + GrGLFunction fBindVertexArray; + GrGLFunction fBlendBarrier; + GrGLFunction fBlendColor; + GrGLFunction fBlendEquation; + GrGLFunction fBlendFunc; + GrGLFunction fBlitFramebuffer; + GrGLFunction fBufferData; + GrGLFunction fBufferSubData; + GrGLFunction fCheckFramebufferStatus; + GrGLFunction fClear; + GrGLFunction fClearColor; + GrGLFunction fClearStencil; + GrGLFunction fClearTexImage; + GrGLFunction fClearTexSubImage; + GrGLFunction fColorMask; + GrGLFunction fCompileShader; + GrGLFunction fCompressedTexImage2D; + GrGLFunction fCompressedTexSubImage2D; + GrGLFunction fCopyTexSubImage2D; + GrGLFunction fCreateProgram; + GrGLFunction fCreateShader; + GrGLFunction fCullFace; + GrGLFunction fDeleteBuffers; + GrGLFunction fDeleteFramebuffers; + GrGLFunction fDeleteProgram; + GrGLFunction fDeleteQueries; + GrGLFunction fDeleteRenderbuffers; + GrGLFunction fDeleteSamplers; + GrGLFunction fDeleteShader; + GrGLFunction fDeleteTextures; + GrGLFunction fDeleteVertexArrays; + GrGLFunction fDepthMask; + GrGLFunction fDisable; + GrGLFunction fDisableVertexAttribArray; + GrGLFunction fDrawArrays; + GrGLFunction fDrawArraysIndirect; + GrGLFunction fDrawArraysInstanced; + GrGLFunction fDrawBuffer; + GrGLFunction fDrawBuffers; + GrGLFunction fDrawElements; + GrGLFunction fDrawElementsIndirect; + GrGLFunction fDrawElementsInstanced; + GrGLFunction fDrawRangeElements; + GrGLFunction fEnable; + GrGLFunction fEnableVertexAttribArray; + GrGLFunction fEndQuery; + GrGLFunction fFinish; + GrGLFunction fFlush; + GrGLFunction fFlushMappedBufferRange; + GrGLFunction fFramebufferRenderbuffer; + GrGLFunction fFramebufferTexture2D; + GrGLFunction fFramebufferTexture2DMultisample; + GrGLFunction fFrontFace; + GrGLFunction fGenBuffers; + GrGLFunction fGenFramebuffers; + GrGLFunction fGenerateMipmap; + GrGLFunction fGenQueries; + GrGLFunction fGenRenderbuffers; + GrGLFunction fGenSamplers; + GrGLFunction fGenTextures; + GrGLFunction fGenVertexArrays; + GrGLFunction fGetBufferParameteriv; + GrGLFunction fGetError; + GrGLFunction fGetFramebufferAttachmentParameteriv; + GrGLFunction fGetIntegerv; + GrGLFunction fGetMultisamplefv; + GrGLFunction fGetProgramBinary; + GrGLFunction fGetProgramInfoLog; + GrGLFunction fGetProgramiv; + GrGLFunction fGetQueryObjecti64v; + GrGLFunction fGetQueryObjectiv; + GrGLFunction fGetQueryObjectui64v; + GrGLFunction fGetQueryObjectuiv; + GrGLFunction fGetQueryiv; + GrGLFunction fGetRenderbufferParameteriv; + GrGLFunction fGetShaderInfoLog; + GrGLFunction fGetShaderiv; + GrGLFunction fGetShaderPrecisionFormat; + GrGLFunction fGetString; + GrGLFunction fGetStringi; + GrGLFunction fGetTexLevelParameteriv; + GrGLFunction fGetUniformLocation; + GrGLFunction fInsertEventMarker; + GrGLFunction fInvalidateBufferData; + GrGLFunction fInvalidateBufferSubData; + GrGLFunction fInvalidateFramebuffer; + GrGLFunction fInvalidateSubFramebuffer; + GrGLFunction fInvalidateTexImage; + GrGLFunction fInvalidateTexSubImage; + GrGLFunction fIsTexture; + GrGLFunction fLineWidth; + GrGLFunction fLinkProgram; + GrGLFunction fProgramBinary; + GrGLFunction fProgramParameteri; + GrGLFunction fMapBuffer; + GrGLFunction fMapBufferRange; + GrGLFunction fMapBufferSubData; + GrGLFunction fMapTexSubImage2D; + GrGLFunction fMultiDrawArraysIndirect; + GrGLFunction fMultiDrawElementsIndirect; + GrGLFunction fPixelStorei; + GrGLFunction fPolygonMode; + GrGLFunction fPopGroupMarker; + GrGLFunction fPushGroupMarker; + GrGLFunction fQueryCounter; + GrGLFunction fReadBuffer; + GrGLFunction fReadPixels; + GrGLFunction fRenderbufferStorage; + + // On OpenGL ES there are multiple incompatible extensions that add support for MSAA + // and ES3 adds MSAA support to the standard. On an ES3 driver we may still use the + // older extensions for performance reasons or due to ES3 driver bugs. We want the function + // that creates the GrGLInterface to provide all available functions and internally + // we will select among them. They all have a method called glRenderbufferStorageMultisample*. + // So we have separate function pointers for GL_IMG/EXT_multisampled_to_texture, + // GL_CHROMIUM/ANGLE_framebuffer_multisample/ES3, and GL_APPLE_framebuffer_multisample + // variations. + // + // If a driver supports multiple GL_ARB_framebuffer_multisample-style extensions then we will + // assume the function pointers for the standard (or equivalent GL_ARB) version have + // been preferred over GL_EXT, GL_CHROMIUM, or GL_ANGLE variations that have reduced + // functionality. + + // GL_EXT_multisampled_render_to_texture (preferred) or GL_IMG_multisampled_render_to_texture + GrGLFunction fRenderbufferStorageMultisampleES2EXT; + // GL_APPLE_framebuffer_multisample + GrGLFunction fRenderbufferStorageMultisampleES2APPLE; + + // This is used to store the pointer for GL_ARB/EXT/ANGLE/CHROMIUM_framebuffer_multisample or + // the standard function in ES3+ or GL 3.0+. + GrGLFunction fRenderbufferStorageMultisample; + + // Pointer to BindUniformLocationCHROMIUM from the GL_CHROMIUM_bind_uniform_location extension. + GrGLFunction fBindUniformLocation; + + GrGLFunction fResolveMultisampleFramebuffer; + GrGLFunction fSamplerParameteri; + GrGLFunction fSamplerParameteriv; + GrGLFunction fScissor; + GrGLFunction fShaderSource; + GrGLFunction fStencilFunc; + GrGLFunction fStencilFuncSeparate; + GrGLFunction fStencilMask; + GrGLFunction fStencilMaskSeparate; + GrGLFunction fStencilOp; + GrGLFunction fStencilOpSeparate; + GrGLFunction fTexBuffer; + GrGLFunction fTexBufferRange; + GrGLFunction fTexImage2D; + GrGLFunction fTexParameterf; + GrGLFunction fTexParameterfv; + GrGLFunction fTexParameteri; + GrGLFunction fTexParameteriv; + GrGLFunction fTexSubImage2D; + GrGLFunction fTexStorage2D; + GrGLFunction fTextureBarrier; + GrGLFunction fDiscardFramebuffer; + GrGLFunction fUniform1f; + GrGLFunction fUniform1i; + GrGLFunction fUniform1fv; + GrGLFunction fUniform1iv; + GrGLFunction fUniform2f; + GrGLFunction fUniform2i; + GrGLFunction fUniform2fv; + GrGLFunction fUniform2iv; + GrGLFunction fUniform3f; + GrGLFunction fUniform3i; + GrGLFunction fUniform3fv; + GrGLFunction fUniform3iv; + GrGLFunction fUniform4f; + GrGLFunction fUniform4i; + GrGLFunction fUniform4fv; + GrGLFunction fUniform4iv; + GrGLFunction fUniformMatrix2fv; + GrGLFunction fUniformMatrix3fv; + GrGLFunction fUniformMatrix4fv; + GrGLFunction fUnmapBuffer; + GrGLFunction fUnmapBufferSubData; + GrGLFunction fUnmapTexSubImage2D; + GrGLFunction fUseProgram; + GrGLFunction fVertexAttrib1f; + GrGLFunction fVertexAttrib2fv; + GrGLFunction fVertexAttrib3fv; + GrGLFunction fVertexAttrib4fv; + GrGLFunction fVertexAttribDivisor; + GrGLFunction fVertexAttribIPointer; + GrGLFunction fVertexAttribPointer; + GrGLFunction fViewport; + + /* GL_NV_path_rendering */ + GrGLFunction fMatrixLoadf; + GrGLFunction fMatrixLoadIdentity; + GrGLFunction fGetProgramResourceLocation; + GrGLFunction fPathCommands; + GrGLFunction fPathParameteri; + GrGLFunction fPathParameterf; + GrGLFunction fGenPaths; + GrGLFunction fDeletePaths; + GrGLFunction fIsPath; + GrGLFunction fPathStencilFunc; + GrGLFunction fStencilFillPath; + GrGLFunction fStencilStrokePath; + GrGLFunction fStencilFillPathInstanced; + GrGLFunction fStencilStrokePathInstanced; + GrGLFunction fCoverFillPath; + GrGLFunction fCoverStrokePath; + GrGLFunction fCoverFillPathInstanced; + GrGLFunction fCoverStrokePathInstanced; + // NV_path_rendering v1.2 + GrGLFunction fStencilThenCoverFillPath; + GrGLFunction fStencilThenCoverStrokePath; + GrGLFunction fStencilThenCoverFillPathInstanced; + GrGLFunction fStencilThenCoverStrokePathInstanced; + // NV_path_rendering v1.3 + GrGLFunction fProgramPathFragmentInputGen; + // CHROMIUM_path_rendering + GrGLFunction fBindFragmentInputLocation; + + /* NV_framebuffer_mixed_samples */ + GrGLFunction fCoverageModulation; + + /* ARB_sync */ + GrGLFunction fFenceSync; + GrGLFunction fIsSync; + GrGLFunction fClientWaitSync; + GrGLFunction fWaitSync; + GrGLFunction fDeleteSync; + + /* ARB_internalforamt_query */ + GrGLFunction fGetInternalformativ; + + /* KHR_debug */ + GrGLFunction fDebugMessageControl; + GrGLFunction fDebugMessageInsert; + GrGLFunction fDebugMessageCallback; + GrGLFunction fGetDebugMessageLog; + GrGLFunction fPushDebugGroup; + GrGLFunction fPopDebugGroup; + GrGLFunction fObjectLabel; + + /* EXT_window_rectangles */ + GrGLFunction fWindowRectangles; + + /* GL_QCOM_tiled_rendering */ + GrGLFunction fStartTiling; + GrGLFunction fEndTiling; + } fFunctions; + +#if GR_TEST_UTILS + // This exists for internal testing. + virtual void abandon() const; +#endif +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/gl/GrGLTypes.h b/gfx/skia/skia/include/gpu/gl/GrGLTypes.h new file mode 100644 index 0000000000..1dba115afe --- /dev/null +++ b/gfx/skia/skia/include/gpu/gl/GrGLTypes.h @@ -0,0 +1,186 @@ + +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLTypes_DEFINED +#define GrGLTypes_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/gl/GrGLConfig.h" + +/** + * Classifies GL contexts by which standard they implement (currently as OpenGL vs. OpenGL ES). + */ +enum GrGLStandard { + kNone_GrGLStandard, + kGL_GrGLStandard, + kGLES_GrGLStandard, + kWebGL_GrGLStandard, +}; +static const int kGrGLStandardCnt = 4; + +// The following allow certain interfaces to be turned off at compile time +// (for example, to lower code size). +#if SK_ASSUME_GL_ES + #define GR_IS_GR_GL(standard) false + #define GR_IS_GR_GL_ES(standard) true + #define GR_IS_GR_WEBGL(standard) false + #define SK_DISABLE_GL_INTERFACE 1 + #define SK_DISABLE_WEBGL_INTERFACE 1 +#elif SK_ASSUME_GL + #define GR_IS_GR_GL(standard) true + #define GR_IS_GR_GL_ES(standard) false + #define GR_IS_GR_WEBGL(standard) false + #define SK_DISABLE_GL_ES_INTERFACE 1 + #define SK_DISABLE_WEBGL_INTERFACE 1 +#elif SK_ASSUME_WEBGL + #define GR_IS_GR_GL(standard) false + #define GR_IS_GR_GL_ES(standard) false + #define GR_IS_GR_WEBGL(standard) true + #define SK_DISABLE_GL_ES_INTERFACE 1 + #define SK_DISABLE_GL_INTERFACE 1 +#else + #define GR_IS_GR_GL(standard) (kGL_GrGLStandard == standard) + #define GR_IS_GR_GL_ES(standard) (kGLES_GrGLStandard == standard) + #define GR_IS_GR_WEBGL(standard) (kWebGL_GrGLStandard == standard) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +/** + * The supported GL formats represented as an enum. Actual support by GrContext depends on GL + * context version and extensions. + */ +enum class GrGLFormat { + kUnknown, + + kRGBA8, + kR8, + kALPHA8, + kLUMINANCE8, + kBGRA8, + kRGB565, + kRGBA16F, + kR16F, + kRGB8, + kRG8, + kRGB10_A2, + kRGBA4, + kSRGB8_ALPHA8, + kCOMPRESSED_RGB8_ETC2, + kCOMPRESSED_ETC1_RGB8, + kR16, + kRG16, + kRGBA16, + kRG16F, + kLUMINANCE16F, + + kLast = kLUMINANCE16F +}; + +/////////////////////////////////////////////////////////////////////////////// +/** + * Declares typedefs for all the GL functions used in GrGLInterface + */ + +typedef unsigned int GrGLenum; +typedef unsigned char GrGLboolean; +typedef unsigned int GrGLbitfield; +typedef signed char GrGLbyte; +typedef char GrGLchar; +typedef short GrGLshort; +typedef int GrGLint; +typedef int GrGLsizei; +typedef int64_t GrGLint64; +typedef unsigned char GrGLubyte; +typedef unsigned short GrGLushort; +typedef unsigned int GrGLuint; +typedef uint64_t GrGLuint64; +typedef unsigned short int GrGLhalf; +typedef float GrGLfloat; +typedef float GrGLclampf; +typedef double GrGLdouble; +typedef double GrGLclampd; +typedef void GrGLvoid; +#ifdef _WIN64 +typedef signed long long int GrGLintptr; +typedef signed long long int GrGLsizeiptr; +#else +typedef signed long int GrGLintptr; +typedef signed long int GrGLsizeiptr; +#endif +typedef void* GrGLeglImage; +typedef struct __GLsync* GrGLsync; + +struct GrGLDrawArraysIndirectCommand { + GrGLuint fCount; + GrGLuint fInstanceCount; + GrGLuint fFirst; + GrGLuint fBaseInstance; // Requires EXT_base_instance on ES. +}; + +GR_STATIC_ASSERT(16 == sizeof(GrGLDrawArraysIndirectCommand)); + +struct GrGLDrawElementsIndirectCommand { + GrGLuint fCount; + GrGLuint fInstanceCount; + GrGLuint fFirstIndex; + GrGLuint fBaseVertex; + GrGLuint fBaseInstance; // Requires EXT_base_instance on ES. +}; + +GR_STATIC_ASSERT(20 == sizeof(GrGLDrawElementsIndirectCommand)); + +/** + * KHR_debug + */ +typedef void (GR_GL_FUNCTION_TYPE* GRGLDEBUGPROC)(GrGLenum source, + GrGLenum type, + GrGLuint id, + GrGLenum severity, + GrGLsizei length, + const GrGLchar* message, + const void* userParam); + +/** + * EGL types. + */ +typedef void* GrEGLImage; +typedef void* GrEGLDisplay; +typedef void* GrEGLContext; +typedef void* GrEGLClientBuffer; +typedef unsigned int GrEGLenum; +typedef int32_t GrEGLint; +typedef unsigned int GrEGLBoolean; + +/////////////////////////////////////////////////////////////////////////////// +/** + * Types for interacting with GL resources created externally to Skia. GrBackendObjects for GL + * textures are really const GrGLTexture*. The fFormat here should be a sized, internal format + * for the texture. We will try to use the sized format if the GL Context supports it, otherwise + * we will internally fall back to using the base internal formats. + */ +struct GrGLTextureInfo { + GrGLenum fTarget; + GrGLuint fID; + GrGLenum fFormat = 0; + + bool operator==(const GrGLTextureInfo& that) const { + return fTarget == that.fTarget && fID == that.fID && fFormat == that.fFormat; + } +}; + +struct GrGLFramebufferInfo { + GrGLuint fFBOID; + GrGLenum fFormat = 0; + + bool operator==(const GrGLFramebufferInfo& that) const { + return fFBOID == that.fFBOID && fFormat == that.fFormat; + } +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/mock/GrMockTypes.h b/gfx/skia/skia/include/gpu/mock/GrMockTypes.h new file mode 100644 index 0000000000..335b48411d --- /dev/null +++ b/gfx/skia/skia/include/gpu/mock/GrMockTypes.h @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMockOptions_DEFINED +#define GrMockOptions_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/private/GrTypesPriv.h" + +class GrBackendFormat; + +struct GrMockTextureInfo { + GrMockTextureInfo() + : fColorType(GrColorType::kUnknown) + , fID(0) {} + + GrMockTextureInfo(GrColorType colorType, int id) + : fColorType(colorType) + , fID(id) { + SkASSERT(fID); + } + + bool operator==(const GrMockTextureInfo& that) const { + return fColorType == that.fColorType && + fID == that.fID; + } + + GrPixelConfig pixelConfig() const { + return GrColorTypeToPixelConfig(fColorType); + } + + GrBackendFormat getBackendFormat() const; + + GrColorType fColorType; + int fID; +}; + +struct GrMockRenderTargetInfo { + GrMockRenderTargetInfo() + : fColorType(GrColorType::kUnknown) + , fID(0) {} + + GrMockRenderTargetInfo(GrColorType colorType, int id) + : fColorType(colorType) + , fID(id) { + SkASSERT(fID); + } + + bool operator==(const GrMockRenderTargetInfo& that) const { + return fColorType == that.fColorType && + fID == that.fID; + } + + GrPixelConfig pixelConfig() const { + return GrColorTypeToPixelConfig(fColorType); + } + + GrBackendFormat getBackendFormat() const; + + GrColorType colorType() const { return fColorType; } + +private: + GrColorType fColorType; + int fID; +}; + +/** + * A pointer to this type is used as the GrBackendContext when creating a Mock GrContext. It can be + * used to specify capability options for the mock context. If nullptr is used a default constructed + * GrMockOptions is used. + */ +struct GrMockOptions { + GrMockOptions() { + using Renderability = ConfigOptions::Renderability; + // By default RGBA_8888 and BGRA_8888 are textureable and renderable and + // A8 and RGB565 are texturable. + fConfigOptions[(int)GrColorType::kRGBA_8888].fRenderability = Renderability::kNonMSAA; + fConfigOptions[(int)GrColorType::kRGBA_8888].fTexturable = true; + fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true; + fConfigOptions[(int)GrColorType::kBGR_565].fTexturable = true; + + fConfigOptions[(int)GrColorType::kBGRA_8888] = fConfigOptions[(int)GrColorType::kRGBA_8888]; + } + + struct ConfigOptions { + enum Renderability { kNo, kNonMSAA, kMSAA }; + Renderability fRenderability = kNo; + bool fTexturable = false; + }; + + // GrCaps options. + bool fMipMapSupport = false; + bool fInstanceAttribSupport = false; + bool fHalfFloatVertexAttributeSupport = false; + uint32_t fMapBufferFlags = 0; + int fMaxTextureSize = 2048; + int fMaxRenderTargetSize = 2048; + int fMaxVertexAttributes = 16; + ConfigOptions fConfigOptions[kGrColorTypeCnt]; + + // GrShaderCaps options. + bool fGeometryShaderSupport = false; + bool fIntegerSupport = false; + bool fFlatInterpolationSupport = false; + int fMaxVertexSamplers = 0; + int fMaxFragmentSamplers = 8; + bool fShaderDerivativeSupport = true; + bool fDualSourceBlendingSupport = false; + + // GrMockGpu options. + bool fFailTextureAllocations = false; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/mtl/GrMtlTypes.h b/gfx/skia/skia/include/gpu/mtl/GrMtlTypes.h new file mode 100644 index 0000000000..773ff9dc75 --- /dev/null +++ b/gfx/skia/skia/include/gpu/mtl/GrMtlTypes.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlTypes_DEFINED +#define GrMtlTypes_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/ports/SkCFObject.h" + +/** + * Declares typedefs for Metal types used in Ganesh cpp code + */ +typedef unsigned int GrMTLPixelFormat; +typedef const void* GrMTLHandle; + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_METAL +/** + * Types for interacting with Metal resources created externally to Skia. + * This is used by GrBackendObjects. + */ +struct GrMtlTextureInfo { +public: + GrMtlTextureInfo() {} + + sk_cf_obj fTexture; + + bool operator==(const GrMtlTextureInfo& that) const { + return fTexture == that.fTexture; + } +}; +#endif + +#endif diff --git a/gfx/skia/skia/include/gpu/vk/GrVkBackendContext.h b/gfx/skia/skia/include/gpu/vk/GrVkBackendContext.h new file mode 100644 index 0000000000..a4fd336ff7 --- /dev/null +++ b/gfx/skia/skia/include/gpu/vk/GrVkBackendContext.h @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkBackendContext_DEFINED +#define GrVkBackendContext_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/vk/GrVkMemoryAllocator.h" +#include "include/gpu/vk/GrVkTypes.h" + +class GrVkExtensions; + +enum GrVkExtensionFlags { + kEXT_debug_report_GrVkExtensionFlag = 0x0001, + kNV_glsl_shader_GrVkExtensionFlag = 0x0002, + kKHR_surface_GrVkExtensionFlag = 0x0004, + kKHR_swapchain_GrVkExtensionFlag = 0x0008, + kKHR_win32_surface_GrVkExtensionFlag = 0x0010, + kKHR_android_surface_GrVkExtensionFlag = 0x0020, + kKHR_xcb_surface_GrVkExtensionFlag = 0x0040, +}; + +enum GrVkFeatureFlags { + kGeometryShader_GrVkFeatureFlag = 0x0001, + kDualSrcBlend_GrVkFeatureFlag = 0x0002, + kSampleRateShading_GrVkFeatureFlag = 0x0004, +}; + +// It is not guarenteed VkPhysicalDeviceProperties2 will be in the client's header so we forward +// declare it here to be safe. +struct VkPhysicalDeviceFeatures2; + +// The BackendContext contains all of the base Vulkan objects needed by the GrVkGpu. The assumption +// is that the client will set these up and pass them to the GrVkGpu constructor. The VkDevice +// created must support at least one graphics queue, which is passed in as well. +// The QueueFamilyIndex must match the family of the given queue. It is needed for CommandPool +// creation, and any GrBackendObjects handed to us (e.g., for wrapped textures) needs to be created +// in or transitioned to that family. The refs held by members of this struct must be released +// (either by deleting the struct or manually releasing the refs) before the underlying vulkan +// device and instance are destroyed. +struct SK_API GrVkBackendContext { + VkInstance fInstance; + VkPhysicalDevice fPhysicalDevice; + VkDevice fDevice; + VkQueue fQueue; + uint32_t fGraphicsQueueIndex; + uint32_t fMinAPIVersion; // Deprecated. Set fInstanceVersion instead. + uint32_t fInstanceVersion = 0; // Deprecated. Set fMaxApiVersion instead + // The max api version set here should match the value set in VkApplicationInfo::apiVersion when + // then VkInstance was created. + uint32_t fMaxAPIVersion = 0; + uint32_t fExtensions = 0; // Deprecated. Use fVkExtensions instead. + const GrVkExtensions* fVkExtensions = nullptr; + uint32_t fFeatures; // Deprecated. Use fDeviceFeatures[2] instead. + // The client can create their VkDevice with either a VkPhysicalDeviceFeatures or + // VkPhysicalDeviceFeatures2 struct, thus we have to support taking both. The + // VkPhysicalDeviceFeatures2 struct is needed so we know if the client enabled any extension + // specific features. If fDeviceFeatures2 is not null then we ignore fDeviceFeatures. If both + // fDeviceFeatures and fDeviceFeatures2 are null we will assume no features are enabled. + const VkPhysicalDeviceFeatures* fDeviceFeatures = nullptr; + const VkPhysicalDeviceFeatures2* fDeviceFeatures2 = nullptr; + sk_sp fMemoryAllocator; + GrVkGetProc fGetProc = nullptr; + // This is deprecated and should be set to false. The client is responsible for managing the + // lifetime of the VkInstance and VkDevice objects. + bool fOwnsInstanceAndDevice = false; + // Indicates that we are working with protected content and all CommandPool and Queue operations + // should be done in a protected context. + GrProtected fProtectedContext = GrProtected::kNo; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/vk/GrVkExtensions.h b/gfx/skia/skia/include/gpu/vk/GrVkExtensions.h new file mode 100644 index 0000000000..6b14c47e71 --- /dev/null +++ b/gfx/skia/skia/include/gpu/vk/GrVkExtensions.h @@ -0,0 +1,63 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkExtensions_DEFINED +#define GrVkExtensions_DEFINED + +#include "include/core/SkString.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/SkTArray.h" + +/** + * Helper class that eats in an array of extensions strings for instance and device and allows for + * quicker querying if an extension is present. + */ +class SK_API GrVkExtensions { +public: + GrVkExtensions() {} + + void init(GrVkGetProc, VkInstance, VkPhysicalDevice, + uint32_t instanceExtensionCount, const char* const* instanceExtensions, + uint32_t deviceExtensionCount, const char* const* deviceExtensions); + + bool hasExtension(const char[], uint32_t minVersion) const; + + struct Info { + Info() {} + Info(const char* name) : fName(name), fSpecVersion(0) {} + + SkString fName; + uint32_t fSpecVersion; + + struct Less { + bool operator() (const Info& a, const SkString& b) { + return strcmp(a.fName.c_str(), b.c_str()) < 0; + } + bool operator() (const SkString& a, const GrVkExtensions::Info& b) { + return strcmp(a.c_str(), b.fName.c_str()) < 0; + } + }; + }; + +#ifdef SK_DEBUG + void dump() const { + SkDebugf("**Vulkan Extensions**\n"); + for (int i = 0; i < fExtensions.count(); ++i) { + SkDebugf("%s. Version: %d\n", + fExtensions[i].fName.c_str(), fExtensions[i].fSpecVersion); + } + SkDebugf("**End Vulkan Extensions**\n"); + } +#endif + +private: + void getSpecVersions(GrVkGetProc getProc, VkInstance, VkPhysicalDevice); + + SkTArray fExtensions; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/vk/GrVkMemoryAllocator.h b/gfx/skia/skia/include/gpu/vk/GrVkMemoryAllocator.h new file mode 100644 index 0000000000..5102496b83 --- /dev/null +++ b/gfx/skia/skia/include/gpu/vk/GrVkMemoryAllocator.h @@ -0,0 +1,90 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkMemoryAllocator_DEFINED +#define GrVkMemoryAllocator_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" + +class GrVkMemoryAllocator : public SkRefCnt { +public: + enum class AllocationPropertyFlags { + kNone = 0, + // Allocation will be placed in its own VkDeviceMemory and not suballocated from some larger + // block. + kDedicatedAllocation = 0x1, + // Says that the backing memory can only be accessed by the device. Additionally the device + // may lazily allocate the memory. This cannot be used with buffers that will be host + // visible. Setting this flag does not guarantee that we will allocate memory that respects + // it, but we will try to prefer memory that can respect it. + kLazyAllocation = 0x2, + // The allocation will be mapped immediately and stay mapped until it is destroyed. This + // flag is only valid for buffers which are host visible (i.e. must have a usage other than + // BufferUsage::kGpuOnly). + kPersistentlyMapped = 0x4, + // Allocation can only be accessed by the device using a protected context. + kProtected = 0x8, + }; + + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(AllocationPropertyFlags); + + enum class BufferUsage { + // Buffers that will only be accessed from the device (large const buffers). Will always be + // in device local memory. + kGpuOnly, + // Buffers that will be accessed on the host and copied to and from a GPU resource (transfer + // buffers). Will always be mappable and coherent memory. + kCpuOnly, + // Buffers that typically will be updated multiple times by the host and read on the gpu + // (e.g. uniform or vertex buffers). Will always be mappable memory, and will prefer to be + // in device local memory. + kCpuWritesGpuReads, + // Buffers which are typically writted to by the GPU and then read on the host. Will always + // be mappable memory, and will prefer coherent and cached memory. + kGpuWritesCpuReads, + }; + + virtual bool allocateMemoryForImage(VkImage image, AllocationPropertyFlags flags, + GrVkBackendMemory*) = 0; + + virtual bool allocateMemoryForBuffer(VkBuffer buffer, BufferUsage usage, + AllocationPropertyFlags flags, GrVkBackendMemory*) = 0; + + // Fills out the passed in GrVkAlloc struct for the passed in GrVkBackendMemory. + virtual void getAllocInfo(const GrVkBackendMemory&, GrVkAlloc*) const = 0; + + // Maps the entire allocation and returns a pointer to the start of the allocation. The + // implementation may map more memory than just the allocation, but the returned pointer must + // point at the start of the memory for the requested allocation. + virtual void* mapMemory(const GrVkBackendMemory&) = 0; + virtual void unmapMemory(const GrVkBackendMemory&) = 0; + + // The following two calls are used for managing non-coherent memory. The offset is relative to + // the start of the allocation and not the underlying VkDeviceMemory. Additionaly the client + // must make sure that the offset + size passed in is less that or equal to the allocation size. + // It is the responsibility of the implementation to make sure all alignment requirements are + // followed. The client should not have to deal with any sort of alignment issues. + virtual void flushMappedMemory(const GrVkBackendMemory&, VkDeviceSize offset, + VkDeviceSize size) = 0; + virtual void invalidateMappedMemory(const GrVkBackendMemory&, VkDeviceSize offset, + VkDeviceSize size)= 0; + + virtual void freeMemory(const GrVkBackendMemory&) = 0; + + // Returns the total amount of memory that is allocated and in use by an allocation for this + // allocator. + virtual uint64_t totalUsedMemory() const = 0; + + // Returns the total amount of memory that is allocated by this allocator. + virtual uint64_t totalAllocatedMemory() const = 0; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrVkMemoryAllocator::AllocationPropertyFlags) + +#endif diff --git a/gfx/skia/skia/include/gpu/vk/GrVkTypes.h b/gfx/skia/skia/include/gpu/vk/GrVkTypes.h new file mode 100644 index 0000000000..9abf14d8d9 --- /dev/null +++ b/gfx/skia/skia/include/gpu/vk/GrVkTypes.h @@ -0,0 +1,256 @@ + +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkTypes_DEFINED +#define GrVkTypes_DEFINED + +#include "include/core/SkTypes.h" +#include "include/gpu/vk/GrVkVulkan.h" + +#ifndef VK_VERSION_1_1 +#error Skia requires the use of Vulkan 1.1 headers +#endif + +#include +#include "include/gpu/GrTypes.h" + +typedef intptr_t GrVkBackendMemory; + +/** + * Types for interacting with Vulkan resources created externally to Skia. GrBackendObjects for + * Vulkan textures are really const GrVkImageInfo* + */ +struct GrVkAlloc { + GrVkAlloc() + : fMemory(VK_NULL_HANDLE) + , fOffset(0) + , fSize(0) + , fFlags(0) + , fBackendMemory(0) + , fUsesSystemHeap(false) {} + + GrVkAlloc(VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, uint32_t flags) + : fMemory(memory) + , fOffset(offset) + , fSize(size) + , fFlags(flags) + , fBackendMemory(0) + , fUsesSystemHeap(false) {} + + VkDeviceMemory fMemory; // can be VK_NULL_HANDLE iff is an RT and is borrowed + VkDeviceSize fOffset; + VkDeviceSize fSize; // this can be indeterminate iff Tex uses borrow semantics + uint32_t fFlags; + GrVkBackendMemory fBackendMemory; // handle to memory allocated via GrVkMemoryAllocator. + + enum Flag { + kNoncoherent_Flag = 0x1, // memory must be flushed to device after mapping + kMappable_Flag = 0x2, // memory is able to be mapped. + }; + + bool operator==(const GrVkAlloc& that) const { + return fMemory == that.fMemory && fOffset == that.fOffset && fSize == that.fSize && + fFlags == that.fFlags && fUsesSystemHeap == that.fUsesSystemHeap; + } + +private: + friend class GrVkHeap; // For access to usesSystemHeap + bool fUsesSystemHeap; +}; + +// This struct is used to pass in the necessary information to create a VkSamplerYcbcrConversion +// object for an VkExternalFormatANDROID. +struct GrVkYcbcrConversionInfo { + GrVkYcbcrConversionInfo() + : fFormat(VK_FORMAT_UNDEFINED) + , fExternalFormat(0) + , fYcbcrModel(VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY) + , fYcbcrRange(VK_SAMPLER_YCBCR_RANGE_ITU_FULL) + , fXChromaOffset(VK_CHROMA_LOCATION_COSITED_EVEN) + , fYChromaOffset(VK_CHROMA_LOCATION_COSITED_EVEN) + , fChromaFilter(VK_FILTER_NEAREST) + , fForceExplicitReconstruction(false) {} + + GrVkYcbcrConversionInfo(VkFormat format, + int64_t externalFormat, + VkSamplerYcbcrModelConversion ycbcrModel, + VkSamplerYcbcrRange ycbcrRange, + VkChromaLocation xChromaOffset, + VkChromaLocation yChromaOffset, + VkFilter chromaFilter, + VkBool32 forceExplicitReconstruction, + VkFormatFeatureFlags formatFeatures) + : fFormat(format) + , fExternalFormat(externalFormat) + , fYcbcrModel(ycbcrModel) + , fYcbcrRange(ycbcrRange) + , fXChromaOffset(xChromaOffset) + , fYChromaOffset(yChromaOffset) + , fChromaFilter(chromaFilter) + , fForceExplicitReconstruction(forceExplicitReconstruction) + , fFormatFeatures(formatFeatures) { + SkASSERT(fYcbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY); + // Either format or externalFormat must be specified. + SkASSERT((fFormat != VK_FORMAT_UNDEFINED) ^ (externalFormat != 0)); + } + + GrVkYcbcrConversionInfo(VkSamplerYcbcrModelConversion ycbcrModel, + VkSamplerYcbcrRange ycbcrRange, + VkChromaLocation xChromaOffset, + VkChromaLocation yChromaOffset, + VkFilter chromaFilter, + VkBool32 forceExplicitReconstruction, + uint64_t externalFormat, + VkFormatFeatureFlags externalFormatFeatures) + : GrVkYcbcrConversionInfo(VK_FORMAT_UNDEFINED, externalFormat, ycbcrModel, ycbcrRange, + xChromaOffset, yChromaOffset, chromaFilter, + forceExplicitReconstruction, externalFormatFeatures) {} + + bool operator==(const GrVkYcbcrConversionInfo& that) const { + // Invalid objects are not required to have all other fields initialized or matching. + if (!this->isValid() && !that.isValid()) { + return true; + } + return this->fFormat == that.fFormat && + this->fExternalFormat == that.fExternalFormat && + this->fYcbcrModel == that.fYcbcrModel && + this->fYcbcrRange == that.fYcbcrRange && + this->fXChromaOffset == that.fXChromaOffset && + this->fYChromaOffset == that.fYChromaOffset && + this->fChromaFilter == that.fChromaFilter && + this->fForceExplicitReconstruction == that.fForceExplicitReconstruction; + } + bool operator!=(const GrVkYcbcrConversionInfo& that) const { return !(*this == that); } + + bool isValid() const { return fYcbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY; } + + // Format of the source image. Must be set to VK_FORMAT_UNDEFINED for external images or + // a valid image format otherwise. + VkFormat fFormat; + + // The external format. Must be non-zero for external images, zero otherwise. + // Should be compatible to be used in a VkExternalFormatANDROID struct. + uint64_t fExternalFormat; + + VkSamplerYcbcrModelConversion fYcbcrModel; + VkSamplerYcbcrRange fYcbcrRange; + VkChromaLocation fXChromaOffset; + VkChromaLocation fYChromaOffset; + VkFilter fChromaFilter; + VkBool32 fForceExplicitReconstruction; + + // For external images format features here should be those returned by a call to + // vkAndroidHardwareBufferFormatPropertiesANDROID + VkFormatFeatureFlags fFormatFeatures; +}; + +struct GrVkImageInfo { + VkImage fImage; + GrVkAlloc fAlloc; + VkImageTiling fImageTiling; + VkImageLayout fImageLayout; + VkFormat fFormat; + uint32_t fLevelCount; + uint32_t fCurrentQueueFamily; + GrProtected fProtected; + GrVkYcbcrConversionInfo fYcbcrConversionInfo; + + GrVkImageInfo() + : fImage(VK_NULL_HANDLE) + , fAlloc() + , fImageTiling(VK_IMAGE_TILING_OPTIMAL) + , fImageLayout(VK_IMAGE_LAYOUT_UNDEFINED) + , fFormat(VK_FORMAT_UNDEFINED) + , fLevelCount(0) + , fCurrentQueueFamily(VK_QUEUE_FAMILY_IGNORED) + , fProtected(GrProtected::kNo) + , fYcbcrConversionInfo() {} + + GrVkImageInfo(VkImage image, + GrVkAlloc alloc, + VkImageTiling imageTiling, + VkImageLayout layout, + VkFormat format, + uint32_t levelCount, + uint32_t currentQueueFamily = VK_QUEUE_FAMILY_IGNORED, + GrProtected isProtected = GrProtected::kNo, + GrVkYcbcrConversionInfo ycbcrConversionInfo = GrVkYcbcrConversionInfo()) + : fImage(image) + , fAlloc(alloc) + , fImageTiling(imageTiling) + , fImageLayout(layout) + , fFormat(format) + , fLevelCount(levelCount) + , fCurrentQueueFamily(currentQueueFamily) + , fProtected(isProtected) + , fYcbcrConversionInfo(ycbcrConversionInfo) {} + + GrVkImageInfo(const GrVkImageInfo& info, VkImageLayout layout) + : fImage(info.fImage) + , fAlloc(info.fAlloc) + , fImageTiling(info.fImageTiling) + , fImageLayout(layout) + , fFormat(info.fFormat) + , fLevelCount(info.fLevelCount) + , fCurrentQueueFamily(info.fCurrentQueueFamily) + , fProtected(info.fProtected) + , fYcbcrConversionInfo(info.fYcbcrConversionInfo) {} + + // This gives a way for a client to update the layout of the Image if they change the layout + // while we're still holding onto the wrapped texture. They will first need to get a handle + // to our internal GrVkImageInfo by calling getTextureHandle on a GrVkTexture. + void updateImageLayout(VkImageLayout layout) { fImageLayout = layout; } + + bool operator==(const GrVkImageInfo& that) const { + return fImage == that.fImage && fAlloc == that.fAlloc && + fImageTiling == that.fImageTiling && fImageLayout == that.fImageLayout && + fFormat == that.fFormat && fLevelCount == that.fLevelCount && + fCurrentQueueFamily == that.fCurrentQueueFamily && fProtected == that.fProtected && + fYcbcrConversionInfo == that.fYcbcrConversionInfo; + } +}; + +using GrVkGetProc = std::function; + +/** + * This object is wrapped in a GrBackendDrawableInfo and passed in as an argument to + * drawBackendGpu() calls on an SkDrawable. The drawable will use this info to inject direct + * Vulkan calls into our stream of GPU draws. + * + * The SkDrawable is given a secondary VkCommandBuffer in which to record draws. The GPU backend + * will then execute that command buffer within a render pass it is using for its own draws. The + * drawable is also given the attachment of the color index, a compatible VkRenderPass, and the + * VkFormat of the color attachment so that it can make VkPipeline objects for the draws. The + * SkDrawable must not alter the state of the VkRenderpass or sub pass. + * + * Additionally, the SkDrawable may fill in the passed in fDrawBounds with the bounds of the draws + * that it submits to the command buffer. This will be used by the GPU backend for setting the + * bounds in vkCmdBeginRenderPass. If fDrawBounds is not updated, we will assume that the entire + * attachment may have been written to. + * + * The SkDrawable is always allowed to create its own command buffers and submit them to the queue + * to render offscreen textures which will be sampled in draws added to the passed in + * VkCommandBuffer. If this is done the SkDrawable is in charge of adding the required memory + * barriers to the queue for the sampled images since the Skia backend will not do this. + * + * The VkImage is informational only and should not be used or modified in any ways. + */ +struct GrVkDrawableInfo { + VkCommandBuffer fSecondaryCommandBuffer; + uint32_t fColorAttachmentIndex; + VkRenderPass fCompatibleRenderPass; + VkFormat fFormat; + VkRect2D* fDrawBounds; + VkImage fImage; +}; + +#endif diff --git a/gfx/skia/skia/include/gpu/vk/GrVkVulkan.h b/gfx/skia/skia/include/gpu/vk/GrVkVulkan.h new file mode 100644 index 0000000000..65cbf9b8ba --- /dev/null +++ b/gfx/skia/skia/include/gpu/vk/GrVkVulkan.h @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkVulkan_DEFINED +#define GrVkVulkan_DEFINED + +#include "include/core/SkTypes.h" + +#if SKIA_IMPLEMENTATION || !defined(SK_VULKAN) +#include "include/third_party/vulkan/vulkan/vulkan_core.h" +#else +// For google3 builds we don't set SKIA_IMPLEMENTATION so we need to make sure that the vulkan +// headers stay up to date for our needs +#include +#endif + +#ifdef SK_BUILD_FOR_ANDROID +// This is needed to get android extensions for external memory +#if SKIA_IMPLEMENTATION || !defined(SK_VULKAN) +#include "include/third_party/vulkan/vulkan/vulkan_android.h" +#else +// For google3 builds we don't set SKIA_IMPLEMENTATION so we need to make sure that the vulkan +// headers stay up to date for our needs +#include +#endif +#endif + +#endif diff --git a/gfx/skia/skia/include/pathops/SkPathOps.h b/gfx/skia/skia/include/pathops/SkPathOps.h new file mode 100644 index 0000000000..2c799d90a0 --- /dev/null +++ b/gfx/skia/skia/include/pathops/SkPathOps.h @@ -0,0 +1,113 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOps_DEFINED +#define SkPathOps_DEFINED + +#include "include/core/SkPreConfig.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" + +class SkPath; +struct SkRect; + + +// FIXME: move everything below into the SkPath class +/** + * The logical operations that can be performed when combining two paths. + */ +enum SkPathOp { + kDifference_SkPathOp, //!< subtract the op path from the first path + kIntersect_SkPathOp, //!< intersect the two paths + kUnion_SkPathOp, //!< union (inclusive-or) the two paths + kXOR_SkPathOp, //!< exclusive-or the two paths + kReverseDifference_SkPathOp, //!< subtract the first path from the op path +}; + +/** Set this path to the result of applying the Op to this path and the + specified path: this = (this op operand). + The resulting path will be constructed from non-overlapping contours. + The curve order is reduced where possible so that cubics may be turned + into quadratics, and quadratics maybe turned into lines. + + Returns true if operation was able to produce a result; + otherwise, result is unmodified. + + @param one The first operand (for difference, the minuend) + @param two The second operand (for difference, the subtrahend) + @param op The operator to apply. + @param result The product of the operands. The result may be one of the + inputs. + @return True if the operation succeeded. + */ +bool SK_API Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result); + +/** Set this path to a set of non-overlapping contours that describe the + same area as the original path. + The curve order is reduced where possible so that cubics may + be turned into quadratics, and quadratics maybe turned into lines. + + Returns true if operation was able to produce a result; + otherwise, result is unmodified. + + @param path The path to simplify. + @param result The simplified path. The result may be the input. + @return True if simplification succeeded. + */ +bool SK_API Simplify(const SkPath& path, SkPath* result); + +/** Set the resulting rectangle to the tight bounds of the path. + + @param path The path measured. + @param result The tight bounds of the path. + @return True if the bounds could be computed. + */ +bool SK_API TightBounds(const SkPath& path, SkRect* result); + +/** Set the result with fill type winding to area equivalent to path. + Returns true if successful. Does not detect if path contains contours which + contain self-crossings or cross other contours; in these cases, may return + true even though result does not fill same area as path. + + Returns true if operation was able to produce a result; + otherwise, result is unmodified. The result may be the input. + + @param path The path typically with fill type set to even odd. + @param result The equivalent path with fill type set to winding. + @return True if winding path was set. + */ +bool SK_API AsWinding(const SkPath& path, SkPath* result); + +/** Perform a series of path operations, optimized for unioning many paths together. + */ +class SK_API SkOpBuilder { +public: + /** Add one or more paths and their operand. The builder is empty before the first + path is added, so the result of a single add is (emptyPath OP path). + + @param path The second operand. + @param _operator The operator to apply to the existing and supplied paths. + */ + void add(const SkPath& path, SkPathOp _operator); + + /** Computes the sum of all paths and operands, and resets the builder to its + initial state. + + @param result The product of the operands. + @return True if the operation succeeded. + */ + bool resolve(SkPath* result); + +private: + SkTArray fPathRefs; + SkTDArray fOps; + + static bool FixWinding(SkPath* path); + static void ReversePath(SkPath* path); + void reset(); +}; + +#endif diff --git a/gfx/skia/skia/include/ports/SkCFObject.h b/gfx/skia/skia/include/ports/SkCFObject.h new file mode 100644 index 0000000000..c1517037f4 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkCFObject.h @@ -0,0 +1,138 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCFObject_DEFINED +#define SkCFObject_DEFINED + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#import + +/** + * Wrapper class for managing lifetime of CoreFoundation objects. It will call + * CFRetain and CFRelease appropriately on creation, assignment, and deletion. + * Based on sk_sp<>. + */ +template static inline T SkCFSafeRetain(T obj) { + if (obj) { + CFRetain(obj); + } + return obj; +} + +template static inline void SkCFSafeRelease(T obj) { + if (obj) { + CFRelease(obj); + } +} + +template class sk_cf_obj { +public: + using element_type = T; + + constexpr sk_cf_obj() : fObject(nullptr) {} + + /** + * Shares the underlying object by calling CFRetain(), so that both the argument and the newly + * created sk_cf_obj both have a reference to it. + */ + sk_cf_obj(const sk_cf_obj& that) : fObject(SkCFSafeRetain(that.get())) {} + + /** + * Move the underlying object from the argument to the newly created sk_cf_obj. Afterwards only + * the new sk_cf_obj will have a reference to the object, and the argument will point to null. + * No call to CFRetain() or CFRelease() will be made. + */ + sk_cf_obj(sk_cf_obj&& that) : fObject(that.release()) {} + + /** + * Adopt the bare object into the newly created sk_cf_obj. + * No call to CFRetain() or CFRelease() will be made. + */ + explicit sk_cf_obj(T obj) { + fObject = obj; + } + + /** + * Calls CFRelease() on the underlying object pointer. + */ + ~sk_cf_obj() { + SkCFSafeRelease(fObject); + SkDEBUGCODE(fObject = nullptr); + } + + /** + * Shares the underlying object referenced by the argument by calling CFRetain() on it. If this + * sk_cf_obj previously had a reference to an object (i.e. not null) it will call CFRelease() + * on that object. + */ + sk_cf_obj& operator=(const sk_cf_obj& that) { + if (this != &that) { + this->reset(SkCFSafeRetain(that.get())); + } + return *this; + } + + /** + * Move the underlying object from the argument to the sk_cf_obj. If the sk_cf_obj + * previously held a reference to another object, CFRelease() will be called on that object. + * No call to CFRetain() will be made. + */ + sk_cf_obj& operator=(sk_cf_obj&& that) { + this->reset(that.release()); + return *this; + } + + T get() const { return fObject; } + + /** + * Adopt the new object, and call CFRelease() on any previously held object (if not null). + * No call to CFRetain() will be made. + */ + void reset(T object = nullptr) { + T oldObject = fObject; + fObject = object; + SkCFSafeRelease(oldObject); + } + + /** + * Shares the new object by calling CFRetain() on it. If this sk_cf_obj previously had a + * reference to an object (i.e. not null) it will call CFRelease() on that object. + */ + void retain(T object) { + if (this->fObject != object) { + this->reset(SkCFSafeRetain(object)); + } + } + + /** + * Return the original object, and set the internal object to nullptr. + * The caller must assume ownership of the object, and manage its reference count directly. + * No call to CFRelease() will be made. + */ + T SK_WARN_UNUSED_RESULT release() { + T obj = fObject; + fObject = nullptr; + return obj; + } + +private: + T fObject; +}; + +template inline bool operator==(const sk_cf_obj& a, + const sk_cf_obj& b) { + return a.get() == b.get(); +} + +template inline bool operator!=(const sk_cf_obj& a, + const sk_cf_obj& b) { + return a.get() != b.get(); +} + +#endif // SK_BUILD_FOR_MAC || SK_BUILD_FOR_IOS +#endif // SkCFOBject_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontConfigInterface.h b/gfx/skia/skia/include/ports/SkFontConfigInterface.h new file mode 100644 index 0000000000..557381b7b2 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontConfigInterface.h @@ -0,0 +1,115 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontConfigInterface_DEFINED +#define SkFontConfigInterface_DEFINED + +#include "include/core/SkFontStyle.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" + +class SkFontMgr; + +/** + * \class SkFontConfigInterface + * + * A simple interface for remotable font management. + * The global instance can be found with RefGlobal(). + */ +class SK_API SkFontConfigInterface : public SkRefCnt { +public: + + /** + * Returns the global SkFontConfigInterface instance. If it is not + * nullptr, calls ref() on it. The caller must balance this with a call to + * unref(). The default SkFontConfigInterface is the result of calling + * GetSingletonDirectInterface. + */ + static sk_sp RefGlobal(); + + /** + * Replace the current global instance with the specified one. + */ + static void SetGlobal(sk_sp fc); + + /** + * This should be treated as private to the impl of SkFontConfigInterface. + * Callers should not change or expect any particular values. It is meant + * to be a union of possible storage types to aid the impl. + */ + struct FontIdentity { + FontIdentity() : fID(0), fTTCIndex(0) {} + + bool operator==(const FontIdentity& other) const { + return fID == other.fID && + fTTCIndex == other.fTTCIndex && + fString == other.fString; + } + bool operator!=(const FontIdentity& other) const { + return !(*this == other); + } + + uint32_t fID; + int32_t fTTCIndex; + SkString fString; + SkFontStyle fStyle; + + // If buffer is NULL, just return the number of bytes that would have + // been written. Will pad contents to a multiple of 4. + size_t writeToMemory(void* buffer = nullptr) const; + + // Recreate from a flattened buffer, returning the number of bytes read. + size_t readFromMemory(const void* buffer, size_t length); + }; + + /** + * Given a familyName and style, find the best match. + * + * If a match is found, return true and set its outFontIdentifier. + * If outFamilyName is not null, assign the found familyName to it + * (which may differ from the requested familyName). + * If outStyle is not null, assign the found style to it + * (which may differ from the requested style). + * + * If a match is not found, return false, and ignore all out parameters. + */ + virtual bool matchFamilyName(const char familyName[], + SkFontStyle requested, + FontIdentity* outFontIdentifier, + SkString* outFamilyName, + SkFontStyle* outStyle) = 0; + + /** + * Given a FontRef, open a stream to access its data, or return null + * if the FontRef's data is not available. The caller is responsible for + * deleting the stream when it is done accessing the data. + */ + virtual SkStreamAsset* openStream(const FontIdentity&) = 0; + + /** + * Return an SkTypeface for the given FontIdentity. + * + * The default implementation simply returns a new typeface built using data obtained from + * openStream(), but derived classes may implement more complex caching schemes. + */ + virtual sk_sp makeTypeface(const FontIdentity& identity) { + return SkTypeface::MakeFromStream(std::unique_ptr(this->openStream(identity)), + identity.fTTCIndex); + + } + + /** + * Return a singleton instance of a direct subclass that calls into + * libfontconfig. This does not affect the refcnt of the returned instance. + */ + static SkFontConfigInterface* GetSingletonDirectInterface(); + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/ports/SkFontMgr_FontConfigInterface.h b/gfx/skia/skia/include/ports/SkFontMgr_FontConfigInterface.h new file mode 100644 index 0000000000..05771257d2 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_FontConfigInterface.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_FontConfigInterface_DEFINED +#define SkFontMgr_FontConfigInterface_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +class SkFontMgr; +class SkFontConfigInterface; + +/** Creates a SkFontMgr which wraps a SkFontConfigInterface. */ +SK_API sk_sp SkFontMgr_New_FCI(sk_sp fci); + +#endif // #ifndef SkFontMgr_FontConfigInterface_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontMgr_android.h b/gfx/skia/skia/include/ports/SkFontMgr_android.h new file mode 100644 index 0000000000..d68f3ba034 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_android.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_android_DEFINED +#define SkFontMgr_android_DEFINED + +#include "include/core/SkRefCnt.h" + +class SkFontMgr; + +struct SkFontMgr_Android_CustomFonts { + /** When specifying custom fonts, indicates how to use system fonts. */ + enum SystemFontUse { + kOnlyCustom, /** Use only custom fonts. NDK compliant. */ + kPreferCustom, /** Use custom fonts before system fonts. */ + kPreferSystem /** Use system fonts before custom fonts. */ + }; + /** Whether or not to use system fonts. */ + SystemFontUse fSystemFontUse; + + /** Base path to resolve relative font file names. If a directory, should end with '/'. */ + const char* fBasePath; + + /** Optional custom configuration file to use. */ + const char* fFontsXml; + + /** Optional custom configuration file for fonts which provide fallback. + * In the new style (version > 21) fontsXml format is used, this should be NULL. + */ + const char* fFallbackFontsXml; + + /** Optional custom flag. If set to true the SkFontMgr will acquire all requisite + * system IO resources on initialization. + */ + bool fIsolated; +}; + +/** Create a font manager for Android. If 'custom' is NULL, use only system fonts. */ +SK_API sk_sp SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts* custom); + +#endif // SkFontMgr_android_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontMgr_directory.h b/gfx/skia/skia/include/ports/SkFontMgr_directory.h new file mode 100644 index 0000000000..b1a60fb4da --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_directory.h @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_directory_DEFINED +#define SkFontMgr_directory_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +class SkFontMgr; + +/** Create a custom font manager which scans a given directory for font files. + * This font manager uses FreeType for rendering. + */ +SK_API sk_sp SkFontMgr_New_Custom_Directory(const char* dir); + +#endif // SkFontMgr_directory_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontMgr_empty.h b/gfx/skia/skia/include/ports/SkFontMgr_empty.h new file mode 100644 index 0000000000..e5756421d0 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_empty.h @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_empty_DEFINED +#define SkFontMgr_empty_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +class SkFontMgr; + +/** Create a custom font manager that contains no built-in fonts. + * This font manager uses FreeType for rendering. + */ +SK_API sk_sp SkFontMgr_New_Custom_Empty(); + +#endif // SkFontMgr_empty_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontMgr_fontconfig.h b/gfx/skia/skia/include/ports/SkFontMgr_fontconfig.h new file mode 100644 index 0000000000..4b2bb2d297 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_fontconfig.h @@ -0,0 +1,22 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_fontconfig_DEFINED +#define SkFontMgr_fontconfig_DEFINED + +#include "include/core/SkRefCnt.h" +#include + +class SkFontMgr; + +/** Create a font manager around a FontConfig instance. + * If 'fc' is NULL, will use a new default config. + * Takes ownership of 'fc' and will call FcConfigDestroy on it. + */ +SK_API sk_sp SkFontMgr_New_FontConfig(FcConfig* fc); + +#endif // #ifndef SkFontMgr_fontconfig_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontMgr_fuchsia.h b/gfx/skia/skia/include/ports/SkFontMgr_fuchsia.h new file mode 100644 index 0000000000..d20530af72 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_fuchsia.h @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_fuchsia_DEFINED +#define SkFontMgr_fuchsia_DEFINED + +#include + +#include "include/core/SkRefCnt.h" + +class SkFontMgr; + +SK_API sk_sp SkFontMgr_New_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider); + +#endif // SkFontMgr_fuchsia_DEFINED diff --git a/gfx/skia/skia/include/ports/SkFontMgr_indirect.h b/gfx/skia/skia/include/ports/SkFontMgr_indirect.h new file mode 100644 index 0000000000..4cdd445afb --- /dev/null +++ b/gfx/skia/skia/include/ports/SkFontMgr_indirect.h @@ -0,0 +1,99 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_indirect_DEFINED +#define SkFontMgr_indirect_DEFINED + +#include "include/core/SkFontMgr.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/ports/SkRemotableFontMgr.h" +#include "include/private/SkMutex.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTArray.h" + +class SkData; +class SkFontStyle; +class SkStreamAsset; +class SkString; + +class SK_API SkFontMgr_Indirect : public SkFontMgr { +public: + // TODO: The SkFontMgr is only used for createFromStream/File/Data. + // In the future these calls should be broken out into their own interface + // with a name like SkFontRenderer. + SkFontMgr_Indirect(sk_sp impl, sk_sp proxy) + : fImpl(std::move(impl)), fProxy(std::move(proxy)) + { } + +protected: + int onCountFamilies() const override; + void onGetFamilyName(int index, SkString* familyName) const override; + SkFontStyleSet* onCreateStyleSet(int index) const override; + + SkFontStyleSet* onMatchFamily(const char familyName[]) const override; + + SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontStyle) const override; + + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle&, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const override; + + SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontStyle) const override; + + sk_sp onMakeFromStreamIndex(std::unique_ptr, int ttcIndex) const override; + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override; + sk_sp onMakeFromData(sk_sp, int ttcIndex) const override; + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override; + +private: + SkTypeface* createTypefaceFromFontId(const SkFontIdentity& fontId) const; + + sk_sp fImpl; + sk_sp fProxy; + + struct DataEntry { + uint32_t fDataId; // key1 + uint32_t fTtcIndex; // key2 + SkTypeface* fTypeface; // value: weak ref to typeface + + DataEntry() { } + + DataEntry(DataEntry&& that) + : fDataId(that.fDataId) + , fTtcIndex(that.fTtcIndex) + , fTypeface(that.fTypeface) + { + SkDEBUGCODE(that.fDataId = SkFontIdentity::kInvalidDataId;) + SkDEBUGCODE(that.fTtcIndex = 0xbbadbeef;) + that.fTypeface = nullptr; + } + + ~DataEntry() { + if (fTypeface) { + fTypeface->weak_unref(); + } + } + }; + /** + * This cache is essentially { dataId: { ttcIndex: typeface } } + * For data caching we want a mapping from data id to weak references to + * typefaces with that data id. By storing the index next to the typeface, + * this data cache also acts as a typeface cache. + */ + mutable SkTArray fDataCache; + mutable SkMutex fDataCacheMutex; + + friend class SkStyleSet_Indirect; +}; + +#endif diff --git a/gfx/skia/skia/include/ports/SkImageGeneratorCG.h b/gfx/skia/skia/include/ports/SkImageGeneratorCG.h new file mode 100644 index 0000000000..756ebb9386 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkImageGeneratorCG.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#include "include/core/SkData.h" +#include "include/core/SkImageGenerator.h" + +#include + +namespace SkImageGeneratorCG { +SK_API std::unique_ptr MakeFromEncodedCG(sk_sp); +} + +#endif //defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) diff --git a/gfx/skia/skia/include/ports/SkImageGeneratorWIC.h b/gfx/skia/skia/include/ports/SkImageGeneratorWIC.h new file mode 100644 index 0000000000..eb57a20956 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkImageGeneratorWIC.h @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_WIN) + +#include "include/core/SkData.h" +#include "include/core/SkImageGenerator.h" + +#include + +/* + * Any Windows program that uses COM must initialize the COM library by calling + * the CoInitializeEx function. In addition, each thread that uses a COM + * interface must make a separate call to this function. + * + * For every successful call to CoInitializeEx, the thread must call + * CoUninitialize before it exits. + * + * SkImageGeneratorWIC requires the COM library and leaves it to the client to + * initialize COM for their application. + * + * For more information on initializing COM, please see: + * https://msdn.microsoft.com/en-us/library/windows/desktop/ff485844.aspx + */ +namespace SkImageGeneratorWIC { +SK_API std::unique_ptr MakeFromEncodedWIC(sk_sp); +} + +#endif // SK_BUILD_FOR_WIN diff --git a/gfx/skia/skia/include/ports/SkRemotableFontMgr.h b/gfx/skia/skia/include/ports/SkRemotableFontMgr.h new file mode 100644 index 0000000000..ebe1bd07d6 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkRemotableFontMgr.h @@ -0,0 +1,139 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRemotableFontMgr_DEFINED +#define SkRemotableFontMgr_DEFINED + +#include "include/core/SkFontStyle.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTemplates.h" + +class SkDataTable; +class SkStreamAsset; + +struct SK_API SkFontIdentity { + static const uint32_t kInvalidDataId = 0xFFFFFFFF; + + // Note that fDataId is a data identifier, not a font identifier. + // (fDataID, fTtcIndex) can be seen as a font identifier. + uint32_t fDataId; + uint32_t fTtcIndex; + + // On Linux/FontConfig there is also the ability to specify preferences for rendering + // antialias, embedded bitmaps, autohint, hinting, hintstyle, lcd rendering + // may all be set or set to no-preference + // (No-preference is resolved against globals set by the platform) + // Since they may be selected against, these are really 'extensions' to SkFontStyle. + // SkFontStyle should pick these up. + SkFontStyle fFontStyle; +}; + +class SK_API SkRemotableFontIdentitySet : public SkRefCnt { +public: + SkRemotableFontIdentitySet(int count, SkFontIdentity** data); + + int count() const { return fCount; } + const SkFontIdentity& at(int index) const { return fData[index]; } + + static SkRemotableFontIdentitySet* NewEmpty(); + +private: + SkRemotableFontIdentitySet() : fCount(0), fData() { } + + friend SkRemotableFontIdentitySet* sk_remotable_font_identity_set_new(); + + int fCount; + SkAutoTMalloc fData; + + typedef SkRefCnt INHERITED; +}; + +class SK_API SkRemotableFontMgr : public SkRefCnt { +public: + /** + * Returns all of the fonts with the given familyIndex. + * Returns NULL if the index is out of bounds. + * Returns empty if there are no fonts at the given index. + * + * The caller must unref() the returned object. + */ + virtual SkRemotableFontIdentitySet* getIndex(int familyIndex) const = 0; + + /** + * Returns the closest match to the given style in the given index. + * If there are no available fonts at the given index, the return value's + * data id will be kInvalidDataId. + */ + virtual SkFontIdentity matchIndexStyle(int familyIndex, const SkFontStyle&) const = 0; + + /** + * Returns all the fonts on the system with the given name. + * If the given name is NULL, will return the default font family. + * Never returns NULL; will return an empty set if the name is not found. + * + * It is possible that this will return fonts not accessible from + * getIndex(int) or matchIndexStyle(int, SkFontStyle) due to + * hidden or auto-activated fonts. + * + * The matching may be done in a system dependent way. The name may be + * matched case-insensitive, there may be system aliases which resolve, + * and names outside the current locale may be considered. However, this + * should only return fonts which are somehow associated with the requested + * name. + * + * The caller must unref() the returned object. + */ + virtual SkRemotableFontIdentitySet* matchName(const char familyName[]) const = 0; + + /** + * Returns the closest matching font to the specified name and style. + * If there are no available fonts which match the name, the return value's + * data id will be kInvalidDataId. + * If the given name is NULL, the match will be against any default fonts. + * + * It is possible that this will return a font identity not accessible from + * methods returning sets due to hidden or auto-activated fonts. + * + * The matching may be done in a system dependent way. The name may be + * matched case-insensitive, there may be system aliases which resolve, + * and names outside the current locale may be considered. However, this + * should only return a font which is somehow associated with the requested + * name. + * + * The caller must unref() the returned object. + */ + virtual SkFontIdentity matchNameStyle(const char familyName[], const SkFontStyle&) const = 0; + + /** + * Use the system fall-back to find a font for the given character. + * If no font can be found for the character, the return value's data id + * will be kInvalidDataId. + * If the name is NULL, the match will start against any default fonts. + * If the bpc47 is NULL, a default locale will be assumed. + * + * Note that bpc47 is a combination of ISO 639, 15924, and 3166-1 codes, + * so it is fine to just pass a ISO 639 here. + */ + virtual SkFontIdentity matchNameStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const=0; + + /** + * Returns the data for the given data id. + * Will return NULL if the data id is invalid. + * Note that this is a data id, not a font id. + * + * The caller must unref() the returned object. + */ + virtual SkStreamAsset* getData(int dataId) const = 0; + +private: + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/ports/SkTypeface_cairo.h b/gfx/skia/skia/include/ports/SkTypeface_cairo.h new file mode 100644 index 0000000000..f70fb07ef5 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkTypeface_cairo.h @@ -0,0 +1,18 @@ +#ifndef SkTypeface_cairo_DEFINED +#define SkTypeface_cairo_DEFINED + +#include "include/core/SkTypeface.h" +#include "include/core/SkSurfaceProps.h" + +struct FT_FaceRec_; +typedef FT_FaceRec_* FT_Face; + +SK_API extern void SkInitCairoFT(bool fontHintingEnabled); + +SK_API extern SkTypeface* SkCreateTypefaceFromCairoFTFont( + FT_Face face = nullptr, void* faceContext = nullptr, + SkPixelGeometry pixelGeometry = kUnknown_SkPixelGeometry, + uint8_t lcdFilter = 0); + +#endif + diff --git a/gfx/skia/skia/include/ports/SkTypeface_mac.h b/gfx/skia/skia/include/ports/SkTypeface_mac.h new file mode 100644 index 0000000000..c17b821900 --- /dev/null +++ b/gfx/skia/skia/include/ports/SkTypeface_mac.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTypeface_mac_DEFINED +#define SkTypeface_mac_DEFINED + +#include "include/core/SkTypeface.h" + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#include + +#ifdef SK_BUILD_FOR_MAC +#import +#endif + +#ifdef SK_BUILD_FOR_IOS +#include +#endif + +/** + * Like the other Typeface create methods, this returns a new reference to the + * corresponding typeface for the specified CTFontRef. The caller must call + * unref() when it is finished. + * + * The CFTypeRef parameter, if provided, will be kept referenced for the + * lifetime of the SkTypeface. This was introduced as a means to work around + * https://crbug.com/413332 . + */ +SK_API extern SkTypeface* SkCreateTypefaceFromCTFont(CTFontRef, CFTypeRef = NULL); + +/** + * Returns the platform-specific CTFontRef handle for a + * given SkTypeface. Note that the returned CTFontRef gets + * released when the source SkTypeface is destroyed. + * + * This method is deprecated. It may only be used by Blink Mac + * legacy code in special cases related to text-shaping + * with AAT fonts, clipboard handling and font fallback. + * See https://code.google.com/p/skia/issues/detail?id=3408 + */ +SK_API extern CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face); + +#endif // defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) +#endif // SkTypeface_mac_DEFINED diff --git a/gfx/skia/skia/include/ports/SkTypeface_win.h b/gfx/skia/skia/include/ports/SkTypeface_win.h new file mode 100644 index 0000000000..c9d1a3ee6a --- /dev/null +++ b/gfx/skia/skia/include/ports/SkTypeface_win.h @@ -0,0 +1,93 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTypeface_win_DEFINED +#define SkTypeface_win_DEFINED + +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" + +#ifdef SK_BUILD_FOR_WIN + +#ifdef UNICODE +typedef struct tagLOGFONTW LOGFONTW; +typedef LOGFONTW LOGFONT; +#else +typedef struct tagLOGFONTA LOGFONTA; +typedef LOGFONTA LOGFONT; +#endif // UNICODE + +/** + * Like the other Typeface create methods, this returns a new reference to the + * corresponding typeface for the specified logfont. The caller is responsible + * for calling unref() when it is finished. + */ +SK_API SkTypeface* SkCreateTypefaceFromLOGFONT(const LOGFONT&); + +/** + * Copy the LOGFONT associated with this typeface into the lf parameter. Note + * that the lfHeight will need to be set afterwards, since the typeface does + * not track this (the paint does). + * typeface may be NULL, in which case we return the logfont for the default font. + */ +SK_API void SkLOGFONTFromTypeface(const SkTypeface* typeface, LOGFONT* lf); + +/** + * Set an optional callback to ensure that the data behind a LOGFONT is loaded. + * This will get called if Skia tries to access the data but hits a failure. + * Normally this is null, and is only required if the font data needs to be + * remotely (re)loaded. + */ +SK_API void SkTypeface_SetEnsureLOGFONTAccessibleProc(void (*)(const LOGFONT&)); + +// Experimental! +// +class SkFontMgr; +class SkRemotableFontMgr; +struct IDWriteFactory; +struct IDWriteFontCollection; +struct IDWriteFontFallback; +struct IDWriteFontFace; + +/** + * Like the other Typeface create methods, this returns a new reference to the + * corresponding typeface for the specified dwrite font. The caller is responsible + * for calling unref() when it is finished. + */ +SK_API SkTypeface* SkCreateTypefaceFromDWriteFont(IDWriteFactory* aFactory, + IDWriteFontFace* aFontFace, + SkFontStyle aStyle, + int aRenderingMode, + float aGamma, + float aContrast, + float aClearTypeLevel); + +SK_API sk_sp SkFontMgr_New_GDI(); +SK_API sk_sp SkFontMgr_New_DirectWrite(IDWriteFactory* factory = NULL, + IDWriteFontCollection* collection = NULL); +SK_API sk_sp SkFontMgr_New_DirectWrite(IDWriteFactory* factory, + IDWriteFontCollection* collection, + IDWriteFontFallback* fallback); + +/** + * Creates an SkFontMgr which renders using DirectWrite and obtains its data + * from the SkRemotableFontMgr. + * + * If DirectWrite could not be initialized, will return NULL. + */ +SK_API sk_sp SkFontMgr_New_DirectWriteRenderer(sk_sp); + +/** + * Creates an SkRemotableFontMgr backed by DirectWrite using the default + * system font collection in the current locale. + * + * If DirectWrite could not be initialized, will return NULL. + */ +SK_API sk_sp SkRemotableFontMgr_New_DirectWrite(); + +#endif // SK_BUILD_FOR_WIN +#endif // SkTypeface_win_DEFINED diff --git a/gfx/skia/skia/include/private/GrContext_Base.h b/gfx/skia/skia/include/private/GrContext_Base.h new file mode 100644 index 0000000000..eae2c8f655 --- /dev/null +++ b/gfx/skia/skia/include/private/GrContext_Base.h @@ -0,0 +1,89 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrContext_Base_DEFINED +#define GrContext_Base_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContextOptions.h" +#include "include/gpu/GrTypes.h" + +class GrBaseContextPriv; +class GrCaps; +class GrContext; +class GrImageContext; +class GrRecordingContext; +class GrSkSLFPFactoryCache; + +class GrContext_Base : public SkRefCnt { +public: + virtual ~GrContext_Base(); + + /* + * The 3D API backing this context + */ + SK_API GrBackendApi backend() const { return fBackend; } + + /* + * Retrieve the default GrBackendFormat for a given SkColorType and renderability. + * It is guaranteed that this backend format will be the one used by the GrContext + * SkColorType and SkSurfaceCharacterization-based createBackendTexture methods. + * + * The caller should check that the returned format is valid. + */ + SK_API GrBackendFormat defaultBackendFormat(SkColorType, GrRenderable) const; + + // Provides access to functions that aren't part of the public API. + GrBaseContextPriv priv(); + const GrBaseContextPriv priv() const; + +protected: + friend class GrBaseContextPriv; // for hidden functions + + GrContext_Base(GrBackendApi backend, const GrContextOptions& options, uint32_t contextID); + + virtual bool init(sk_sp, sk_sp); + + /** + * An identifier for this context. The id is used by all compatible contexts. For example, + * if SkImages are created on one thread using an image creation context, then fed into a + * DDL Recorder on second thread (which has a recording context) and finally replayed on + * a third thread with a direct context, then all three contexts will report the same id. + * It is an error for an image to be used with contexts that report different ids. + */ + uint32_t contextID() const { return fContextID; } + + bool matches(GrContext_Base* candidate) const { + return candidate->contextID() == this->contextID(); + } + + /* + * The options in effect for this context + */ + const GrContextOptions& options() const { return fOptions; } + + const GrCaps* caps() const; + sk_sp refCaps() const; + + sk_sp fpFactoryCache(); + + virtual GrImageContext* asImageContext() { return nullptr; } + virtual GrRecordingContext* asRecordingContext() { return nullptr; } + virtual GrContext* asDirectContext() { return nullptr; } + +private: + const GrBackendApi fBackend; + const GrContextOptions fOptions; + const uint32_t fContextID; + sk_sp fCaps; + sk_sp fFPFactoryCache; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/private/GrGLTypesPriv.h b/gfx/skia/skia/include/private/GrGLTypesPriv.h new file mode 100644 index 0000000000..14f4606346 --- /dev/null +++ b/gfx/skia/skia/include/private/GrGLTypesPriv.h @@ -0,0 +1,94 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRefCnt.h" +#include "include/gpu/gl/GrGLTypes.h" + +#ifndef GrGLTypesPriv_DEFINED +#define GrGLTypesPriv_DEFINED + +static constexpr int kGrGLFormatCount = static_cast(GrGLFormat::kLast) + 1; + +class GrGLTextureParameters : public SkNVRefCnt { +public: + // We currently consider texture parameters invalid on all textures + // GrContext::resetContext(). We use this type to track whether instances of + // GrGLTextureParameters were updated before or after the most recent resetContext(). At 10 + // resets / frame and 60fps a 64bit timestamp will overflow in about a billion years. + // TODO: Require clients to use GrBackendTexture::glTextureParametersModified() to invalidate + // texture parameters and get rid of timestamp checking. + using ResetTimestamp = uint64_t; + + // This initializes the params to have an expired timestamp. They'll be considered invalid the + // first time the texture is used unless set() is called. + GrGLTextureParameters() = default; + + // This is texture parameter state that is overridden when a non-zero sampler object is bound. + struct SamplerOverriddenState { + SamplerOverriddenState(); + void invalidate(); + + GrGLenum fMinFilter; + GrGLenum fMagFilter; + GrGLenum fWrapS; + GrGLenum fWrapT; + GrGLfloat fMinLOD; + GrGLfloat fMaxLOD; + // We always want the border color to be transparent black, so no need to store 4 floats. + // Just track if it's been invalidated and no longer the default + bool fBorderColorInvalid; + }; + + // Texture parameter state that is not overridden by a bound sampler object. + struct NonsamplerState { + NonsamplerState(); + void invalidate(); + + uint32_t fSwizzleKey; + GrGLint fBaseMipMapLevel; + GrGLint fMaxMipMapLevel; + }; + + void invalidate(); + + ResetTimestamp resetTimestamp() const { return fResetTimestamp; } + const SamplerOverriddenState& samplerOverriddenState() const { return fSamplerOverriddenState; } + const NonsamplerState& nonsamplerState() const { return fNonsamplerState; } + + // SamplerOverriddenState is optional because we don't track it when we're using sampler + // objects. + void set(const SamplerOverriddenState* samplerState, + const NonsamplerState& nonsamplerState, + ResetTimestamp currTimestamp); + +private: + static constexpr ResetTimestamp kExpiredTimestamp = 0; + + SamplerOverriddenState fSamplerOverriddenState; + NonsamplerState fNonsamplerState; + ResetTimestamp fResetTimestamp = kExpiredTimestamp; +}; + +class GrGLBackendTextureInfo { +public: + GrGLBackendTextureInfo(const GrGLTextureInfo& info, GrGLTextureParameters* params) + : fInfo(info), fParams(params) {} + GrGLBackendTextureInfo(const GrGLBackendTextureInfo&) = delete; + GrGLBackendTextureInfo& operator=(const GrGLBackendTextureInfo&) = delete; + const GrGLTextureInfo& info() const { return fInfo; } + GrGLTextureParameters* parameters() const { return fParams; } + sk_sp refParameters() const { return sk_ref_sp(fParams); } + + void cleanup(); + void assign(const GrGLBackendTextureInfo&, bool thisIsValid); + +private: + GrGLTextureInfo fInfo; + GrGLTextureParameters* fParams; +}; + +#endif diff --git a/gfx/skia/skia/include/private/GrImageContext.h b/gfx/skia/skia/include/private/GrImageContext.h new file mode 100644 index 0000000000..e651964896 --- /dev/null +++ b/gfx/skia/skia/include/private/GrImageContext.h @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrImageContext_DEFINED +#define GrImageContext_DEFINED + +#include "include/private/GrContext_Base.h" +#include "include/private/GrSingleOwner.h" + +class GrImageContextPriv; +class GrProxyProvider; + +class GrImageContext : public GrContext_Base { +public: + ~GrImageContext() override; + + GrBackendFormat defaultBackendFormat(SkColorType ct, GrRenderable renderable) const { + return INHERITED::defaultBackendFormat(ct, renderable); + } + + // Provides access to functions that aren't part of the public API. + GrImageContextPriv priv(); + const GrImageContextPriv priv() const; + +protected: + friend class GrImageContextPriv; // for hidden functions + + GrImageContext(GrBackendApi, const GrContextOptions&, uint32_t contextID); + + SK_API virtual void abandonContext(); + SK_API bool abandoned() const; + + GrProxyProvider* proxyProvider() { return fProxyProvider.get(); } + const GrProxyProvider* proxyProvider() const { return fProxyProvider.get(); } + + /** This is only useful for debug purposes */ + GrSingleOwner* singleOwner() const { return &fSingleOwner; } + + GrImageContext* asImageContext() override { return this; } + +private: + std::unique_ptr fProxyProvider; + bool fAbandoned = false; + + // In debug builds we guard against improper thread handling + // This guard is passed to the GrDrawingManager and, from there to all the + // GrRenderTargetContexts. It is also passed to the GrResourceProvider and SkGpuDevice. + mutable GrSingleOwner fSingleOwner; + + typedef GrContext_Base INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/private/GrRecordingContext.h b/gfx/skia/skia/include/private/GrRecordingContext.h new file mode 100644 index 0000000000..1f44bee169 --- /dev/null +++ b/gfx/skia/skia/include/private/GrRecordingContext.h @@ -0,0 +1,137 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRecordingContext_DEFINED +#define GrRecordingContext_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/GrImageContext.h" + +class GrAuditTrail; +class GrBackendFormat; +class GrDrawingManager; +class GrOnFlushCallbackObject; +class GrOpMemoryPool; +class GrRecordingContextPriv; +class GrStrikeCache; +class GrSurfaceContext; +class GrSurfaceProxy; +class GrTextBlobCache; +class GrTextureContext; + +class GrRecordingContext : public GrImageContext { +public: + ~GrRecordingContext() override; + + SK_API GrBackendFormat defaultBackendFormat(SkColorType ct, GrRenderable renderable) const { + return INHERITED::defaultBackendFormat(ct, renderable); + } + + // Provides access to functions that aren't part of the public API. + GrRecordingContextPriv priv(); + const GrRecordingContextPriv priv() const; + +protected: + friend class GrRecordingContextPriv; // for hidden functions + + GrRecordingContext(GrBackendApi, const GrContextOptions&, uint32_t contextID); + bool init(sk_sp, sk_sp) override; + void setupDrawingManager(bool sortOpsTasks, bool reduceOpsTaskSplitting); + + void abandonContext() override; + + GrDrawingManager* drawingManager(); + + sk_sp refOpMemoryPool(); + GrOpMemoryPool* opMemoryPool(); + + GrStrikeCache* getGrStrikeCache() { return fStrikeCache.get(); } + GrTextBlobCache* getTextBlobCache(); + const GrTextBlobCache* getTextBlobCache() const; + + /** + * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.) + * + * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to + * ensure its lifetime is tied to that of the context. + */ + void addOnFlushCallbackObject(GrOnFlushCallbackObject*); + + std::unique_ptr makeWrappedSurfaceContext(sk_sp, + GrColorType, + SkAlphaType, + sk_sp = nullptr, + const SkSurfaceProps* = nullptr); + + /** Create a new texture context backed by a deferred-style GrTextureProxy. */ + std::unique_ptr makeDeferredTextureContext( + SkBackingFit, + int width, + int height, + GrColorType, + SkAlphaType, + sk_sp, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin = kTopLeft_GrSurfaceOrigin, + SkBudgeted = SkBudgeted::kYes, + GrProtected = GrProtected::kNo); + + /* + * Create a new render target context backed by a deferred-style + * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for + * renderTargetContexts created via this entry point. + */ + std::unique_ptr makeDeferredRenderTargetContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt = 1, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin, + const SkSurfaceProps* surfaceProps = nullptr, + SkBudgeted = SkBudgeted::kYes, + GrProtected isProtected = GrProtected::kNo); + + /* + * This method will attempt to create a renderTargetContext that has, at least, the number of + * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be + * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA). + * SRGB-ness will be preserved. + */ + std::unique_ptr makeDeferredRenderTargetContextWithFallback( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt = 1, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin, + const SkSurfaceProps* surfaceProps = nullptr, + SkBudgeted budgeted = SkBudgeted::kYes, + GrProtected isProtected = GrProtected::kNo); + + GrAuditTrail* auditTrail() { return fAuditTrail.get(); } + + GrRecordingContext* asRecordingContext() override { return this; } + +private: + std::unique_ptr fDrawingManager; + // All the GrOp-derived classes use this pool. + sk_sp fOpMemoryPool; + + std::unique_ptr fStrikeCache; + std::unique_ptr fTextBlobCache; + + std::unique_ptr fAuditTrail; + + typedef GrImageContext INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/private/GrResourceKey.h b/gfx/skia/skia/include/private/GrResourceKey.h new file mode 100644 index 0000000000..d67fa7d226 --- /dev/null +++ b/gfx/skia/skia/include/private/GrResourceKey.h @@ -0,0 +1,357 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrResourceKey_DEFINED +#define GrResourceKey_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkString.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" + +#include + +uint32_t GrResourceKeyHash(const uint32_t* data, size_t size); + +/** + * Base class for all GrGpuResource cache keys. There are two types of cache keys. Refer to the + * comments for each key type below. + */ +class GrResourceKey { +public: + uint32_t hash() const { + this->validate(); + return fKey[kHash_MetaDataIdx]; + } + + size_t size() const { + this->validate(); + SkASSERT(this->isValid()); + return this->internalSize(); + } + +protected: + static const uint32_t kInvalidDomain = 0; + + GrResourceKey() { this->reset(); } + + /** Reset to an invalid key. */ + void reset() { + GR_STATIC_ASSERT((uint16_t)kInvalidDomain == kInvalidDomain); + fKey.reset(kMetaDataCnt); + fKey[kHash_MetaDataIdx] = 0; + fKey[kDomainAndSize_MetaDataIdx] = kInvalidDomain; + } + + bool operator==(const GrResourceKey& that) const { + return this->hash() == that.hash() && 0 == memcmp(&fKey[kHash_MetaDataIdx + 1], + &that.fKey[kHash_MetaDataIdx + 1], + this->internalSize() - sizeof(uint32_t)); + } + + GrResourceKey& operator=(const GrResourceKey& that) { + if (this != &that) { + if (!that.isValid()) { + this->reset(); + } else { + size_t bytes = that.size(); + SkASSERT(SkIsAlign4(bytes)); + fKey.reset(SkToInt(bytes / sizeof(uint32_t))); + memcpy(fKey.get(), that.fKey.get(), bytes); + this->validate(); + } + } + return *this; + } + + bool isValid() const { return kInvalidDomain != this->domain(); } + + uint32_t domain() const { return fKey[kDomainAndSize_MetaDataIdx] & 0xffff; } + + /** size of the key data, excluding meta-data (hash, domain, etc). */ + size_t dataSize() const { return this->size() - 4 * kMetaDataCnt; } + + /** ptr to the key data, excluding meta-data (hash, domain, etc). */ + const uint32_t* data() const { + this->validate(); + return &fKey[kMetaDataCnt]; + } + +#ifdef SK_DEBUG + void dump() const { + if (!this->isValid()) { + SkDebugf("Invalid Key\n"); + } else { + SkDebugf("hash: %d ", this->hash()); + SkDebugf("domain: %d ", this->domain()); + SkDebugf("size: %dB ", this->internalSize()); + for (size_t i = 0; i < this->internalSize(); ++i) { + SkDebugf("%d ", fKey[SkTo(i)]); + } + SkDebugf("\n"); + } + } +#endif + + /** Used to initialize a key. */ + class Builder { + public: + Builder(GrResourceKey* key, uint32_t domain, int data32Count) : fKey(key) { + SkASSERT(data32Count >= 0); + SkASSERT(domain != kInvalidDomain); + key->fKey.reset(kMetaDataCnt + data32Count); + int size = (data32Count + kMetaDataCnt) * sizeof(uint32_t); + SkASSERT(SkToU16(size) == size); + SkASSERT(SkToU16(domain) == domain); + key->fKey[kDomainAndSize_MetaDataIdx] = domain | (size << 16); + } + + ~Builder() { this->finish(); } + + void finish() { + if (nullptr == fKey) { + return; + } + GR_STATIC_ASSERT(0 == kHash_MetaDataIdx); + uint32_t* hash = &fKey->fKey[kHash_MetaDataIdx]; + *hash = GrResourceKeyHash(hash + 1, fKey->internalSize() - sizeof(uint32_t)); + fKey->validate(); + fKey = nullptr; + } + + uint32_t& operator[](int dataIdx) { + SkASSERT(fKey); + SkDEBUGCODE(size_t dataCount = fKey->internalSize() / sizeof(uint32_t) - kMetaDataCnt;) + SkASSERT(SkToU32(dataIdx) < dataCount); + return fKey->fKey[kMetaDataCnt + dataIdx]; + } + + private: + GrResourceKey* fKey; + }; + +private: + enum MetaDataIdx { + kHash_MetaDataIdx, + // The key domain and size are packed into a single uint32_t. + kDomainAndSize_MetaDataIdx, + + kLastMetaDataIdx = kDomainAndSize_MetaDataIdx + }; + static const uint32_t kMetaDataCnt = kLastMetaDataIdx + 1; + + size_t internalSize() const { return fKey[kDomainAndSize_MetaDataIdx] >> 16; } + + void validate() const { + SkASSERT(this->isValid()); + SkASSERT(fKey[kHash_MetaDataIdx] == + GrResourceKeyHash(&fKey[kHash_MetaDataIdx] + 1, + this->internalSize() - sizeof(uint32_t))); + SkASSERT(SkIsAlign4(this->internalSize())); + } + + friend class TestResource; // For unit test to access kMetaDataCnt. + + // bmp textures require 5 uint32_t values. + SkAutoSTMalloc fKey; +}; + +/** + * A key used for scratch resources. There are three important rules about scratch keys: + * * Multiple resources can share the same scratch key. Therefore resources assigned the same + * scratch key should be interchangeable with respect to the code that uses them. + * * A resource can have at most one scratch key and it is set at resource creation by the + * resource itself. + * * When a scratch resource is ref'ed it will not be returned from the + * cache for a subsequent cache request until all refs are released. This facilitates using + * a scratch key for multiple render-to-texture scenarios. An example is a separable blur: + * + * GrTexture* texture[2]; + * texture[0] = get_scratch_texture(scratchKey); + * texture[1] = get_scratch_texture(scratchKey); // texture[0] is already owned so we will get a + * // different one for texture[1] + * draw_mask(texture[0], path); // draws path mask to texture[0] + * blur_x(texture[0], texture[1]); // blurs texture[0] in y and stores result in texture[1] + * blur_y(texture[1], texture[0]); // blurs texture[1] in y and stores result in texture[0] + * texture[1]->unref(); // texture 1 can now be recycled for the next request with scratchKey + * consume_blur(texture[0]); + * texture[0]->unref(); // texture 0 can now be recycled for the next request with scratchKey + */ +class GrScratchKey : public GrResourceKey { +private: + typedef GrResourceKey INHERITED; + +public: + /** Uniquely identifies the type of resource that is cached as scratch. */ + typedef uint32_t ResourceType; + + /** Generate a unique ResourceType. */ + static ResourceType GenerateResourceType(); + + /** Creates an invalid scratch key. It must be initialized using a Builder object before use. */ + GrScratchKey() {} + + GrScratchKey(const GrScratchKey& that) { *this = that; } + + /** reset() returns the key to the invalid state. */ + using INHERITED::reset; + + using INHERITED::isValid; + + ResourceType resourceType() const { return this->domain(); } + + GrScratchKey& operator=(const GrScratchKey& that) { + this->INHERITED::operator=(that); + return *this; + } + + bool operator==(const GrScratchKey& that) const { return this->INHERITED::operator==(that); } + bool operator!=(const GrScratchKey& that) const { return !(*this == that); } + + class Builder : public INHERITED::Builder { + public: + Builder(GrScratchKey* key, ResourceType type, int data32Count) + : INHERITED::Builder(key, type, data32Count) {} + }; +}; + +/** + * A key that allows for exclusive use of a resource for a use case (AKA "domain"). There are three + * rules governing the use of unique keys: + * * Only one resource can have a given unique key at a time. Hence, "unique". + * * A resource can have at most one unique key at a time. + * * Unlike scratch keys, multiple requests for a unique key will return the same + * resource even if the resource already has refs. + * This key type allows a code path to create cached resources for which it is the exclusive user. + * The code path creates a domain which it sets on its keys. This guarantees that there are no + * cross-domain collisions. + * + * Unique keys preempt scratch keys. While a resource has a unique key it is inaccessible via its + * scratch key. It can become scratch again if the unique key is removed. + */ +class GrUniqueKey : public GrResourceKey { +private: + typedef GrResourceKey INHERITED; + +public: + typedef uint32_t Domain; + /** Generate a Domain for unique keys. */ + static Domain GenerateDomain(); + + /** Creates an invalid unique key. It must be initialized using a Builder object before use. */ + GrUniqueKey() : fTag(nullptr) {} + + GrUniqueKey(const GrUniqueKey& that) { *this = that; } + + /** reset() returns the key to the invalid state. */ + using INHERITED::reset; + + using INHERITED::isValid; + + GrUniqueKey& operator=(const GrUniqueKey& that) { + this->INHERITED::operator=(that); + this->setCustomData(sk_ref_sp(that.getCustomData())); + fTag = that.fTag; + return *this; + } + + bool operator==(const GrUniqueKey& that) const { return this->INHERITED::operator==(that); } + bool operator!=(const GrUniqueKey& that) const { return !(*this == that); } + + void setCustomData(sk_sp data) { fData = std::move(data); } + SkData* getCustomData() const { return fData.get(); } + + const char* tag() const { return fTag; } + +#ifdef SK_DEBUG + void dump(const char* label) const { + SkDebugf("%s tag: %s\n", label, fTag ? fTag : "None"); + this->INHERITED::dump(); + } +#endif + + class Builder : public INHERITED::Builder { + public: + Builder(GrUniqueKey* key, Domain type, int data32Count, const char* tag = nullptr) + : INHERITED::Builder(key, type, data32Count) { + key->fTag = tag; + } + + /** Used to build a key that wraps another key and adds additional data. */ + Builder(GrUniqueKey* key, const GrUniqueKey& innerKey, Domain domain, int extraData32Cnt, + const char* tag = nullptr) + : INHERITED::Builder(key, domain, Data32CntForInnerKey(innerKey) + extraData32Cnt) { + SkASSERT(&innerKey != key); + // add the inner key to the end of the key so that op[] can be indexed normally. + uint32_t* innerKeyData = &this->operator[](extraData32Cnt); + const uint32_t* srcData = innerKey.data(); + (*innerKeyData++) = innerKey.domain(); + memcpy(innerKeyData, srcData, innerKey.dataSize()); + key->fTag = tag; + } + + private: + static int Data32CntForInnerKey(const GrUniqueKey& innerKey) { + // key data + domain + return SkToInt((innerKey.dataSize() >> 2) + 1); + } + }; + +private: + sk_sp fData; + const char* fTag; +}; + +/** + * It is common to need a frequently reused GrUniqueKey where the only requirement is that the key + * is unique. These macros create such a key in a thread safe manner so the key can be truly global + * and only constructed once. + */ + +/** Place outside of function/class definitions. */ +#define GR_DECLARE_STATIC_UNIQUE_KEY(name) static SkOnce name##_once + +/** Place inside function where the key is used. */ +#define GR_DEFINE_STATIC_UNIQUE_KEY(name) \ + static SkAlignedSTStorage<1, GrUniqueKey> name##_storage; \ + name##_once(gr_init_static_unique_key_once, &name##_storage); \ + static const GrUniqueKey& name = *reinterpret_cast(name##_storage.get()) + +static inline void gr_init_static_unique_key_once(SkAlignedSTStorage<1, GrUniqueKey>* keyStorage) { + GrUniqueKey* key = new (keyStorage->get()) GrUniqueKey; + GrUniqueKey::Builder builder(key, GrUniqueKey::GenerateDomain(), 0); +} + +// The cache listens for these messages to purge junk resources proactively. +class GrUniqueKeyInvalidatedMessage { +public: + GrUniqueKeyInvalidatedMessage() = default; + GrUniqueKeyInvalidatedMessage(const GrUniqueKey& key, uint32_t contextUniqueID) + : fKey(key), fContextID(contextUniqueID) { + SkASSERT(SK_InvalidUniqueID != contextUniqueID); + } + + GrUniqueKeyInvalidatedMessage(const GrUniqueKeyInvalidatedMessage&) = default; + + GrUniqueKeyInvalidatedMessage& operator=(const GrUniqueKeyInvalidatedMessage&) = default; + + const GrUniqueKey& key() const { return fKey; } + uint32_t contextID() const { return fContextID; } + +private: + GrUniqueKey fKey; + uint32_t fContextID = SK_InvalidUniqueID; +}; + +static inline bool SkShouldPostMessageToBus(const GrUniqueKeyInvalidatedMessage& msg, + uint32_t msgBusUniqueID) { + return msg.contextID() == msgBusUniqueID; +} + +#endif diff --git a/gfx/skia/skia/include/private/GrSharedEnums.h b/gfx/skia/skia/include/private/GrSharedEnums.h new file mode 100644 index 0000000000..d745b70bd4 --- /dev/null +++ b/gfx/skia/skia/include/private/GrSharedEnums.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSharedEnums_DEFINED +#define GrSharedEnums_DEFINED + +/*************************************************************************************************/ +/* This file is used from both C++ and SkSL, so we need to stick to syntax compatible with both. */ +/*************************************************************************************************/ + +/** + * We have coverage effects that clip rendering to the edge of some geometric primitive. + * This enum specifies how that clipping is performed. Not all factories that take a + * GrProcessorEdgeType will succeed with all values and it is up to the caller to check for + * a NULL return. + */ +enum class GrClipEdgeType { + kFillBW, + kFillAA, + kInverseFillBW, + kInverseFillAA, + kHairlineAA, + + kLast = kHairlineAA +}; + +enum class PMConversion { + kToPremul = 0, + kToUnpremul = 1, + kPMConversionCnt = 2 +}; + +#endif diff --git a/gfx/skia/skia/include/private/GrSingleOwner.h b/gfx/skia/skia/include/private/GrSingleOwner.h new file mode 100644 index 0000000000..6369ae82c8 --- /dev/null +++ b/gfx/skia/skia/include/private/GrSingleOwner.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSingleOwner_DEFINED +#define GrSingleOwner_DEFINED + +#include "include/core/SkTypes.h" + +#ifdef SK_DEBUG +#include "include/private/SkMutex.h" +#include "include/private/SkThreadID.h" + +// This is a debug tool to verify an object is only being used from one thread at a time. +class GrSingleOwner { +public: + GrSingleOwner() : fOwner(kIllegalThreadID), fReentranceCount(0) {} + + struct AutoEnforce { + AutoEnforce(GrSingleOwner* so) : fSO(so) { fSO->enter(); } + ~AutoEnforce() { fSO->exit(); } + + GrSingleOwner* fSO; + }; + +private: + void enter() { + SkAutoMutexExclusive lock(fMutex); + SkThreadID self = SkGetThreadID(); + SkASSERT(fOwner == self || fOwner == kIllegalThreadID); + fReentranceCount++; + fOwner = self; + } + + void exit() { + SkAutoMutexExclusive lock(fMutex); + SkASSERT(fOwner == SkGetThreadID()); + fReentranceCount--; + if (fReentranceCount == 0) { + fOwner = kIllegalThreadID; + } + } + + SkMutex fMutex; + SkThreadID fOwner SK_GUARDED_BY(fMutex); + int fReentranceCount SK_GUARDED_BY(fMutex); +}; +#else +class GrSingleOwner {}; // Provide a dummy implementation so we can pass pointers to constructors +#endif + +#endif diff --git a/gfx/skia/skia/include/private/GrTypesPriv.h b/gfx/skia/skia/include/private/GrTypesPriv.h new file mode 100644 index 0000000000..90718038e0 --- /dev/null +++ b/gfx/skia/skia/include/private/GrTypesPriv.h @@ -0,0 +1,1341 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTypesPriv_DEFINED +#define GrTypesPriv_DEFINED + +#include +#include "include/core/SkCanvas.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPath.h" +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrSharedEnums.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkWeakRefCnt.h" + +class GrBackendFormat; +class GrCaps; + +#ifdef MOZ_SKIA +#include "mozilla/TimeStamp.h" + +struct GrStdSteadyClock +{ + typedef mozilla::TimeStamp time_point; + + static time_point now() { + return mozilla::TimeStamp::NowLoRes(); + } +}; + +static inline GrStdSteadyClock::time_point +operator-(GrStdSteadyClock::time_point t, std::chrono::milliseconds ms) { + return t - mozilla::TimeDuration::FromMilliseconds(ms.count()); +} + +#else + +// The old libstdc++ uses the draft name "monotonic_clock" rather than "steady_clock". This might +// not actually be monotonic, depending on how libstdc++ was built. However, this is only currently +// used for idle resource purging so it shouldn't cause a correctness problem. +#if defined(__GLIBCXX__) && (__GLIBCXX__ < 20130000) +using GrStdSteadyClock = std::chrono::monotonic_clock; +#else +using GrStdSteadyClock = std::chrono::steady_clock; +#endif + +#endif + +/** + * Pixel configurations. This type conflates texture formats, CPU pixel formats, and + * premultipliedness. We are moving away from it towards SkColorType and backend API (GL, Vulkan) + * texture formats in the public API. Right now this mostly refers to texture formats as we're + * migrating. + */ +enum GrPixelConfig { + kUnknown_GrPixelConfig, + kAlpha_8_GrPixelConfig, + kAlpha_8_as_Alpha_GrPixelConfig, + kAlpha_8_as_Red_GrPixelConfig, + kGray_8_GrPixelConfig, + kGray_8_as_Lum_GrPixelConfig, + kGray_8_as_Red_GrPixelConfig, + kRGB_565_GrPixelConfig, + kRGBA_4444_GrPixelConfig, + kRGBA_8888_GrPixelConfig, + kRGB_888_GrPixelConfig, + kRGB_888X_GrPixelConfig, + kRG_88_GrPixelConfig, + kBGRA_8888_GrPixelConfig, + kSRGBA_8888_GrPixelConfig, + kRGBA_1010102_GrPixelConfig, + kAlpha_half_GrPixelConfig, + kAlpha_half_as_Lum_GrPixelConfig, + kAlpha_half_as_Red_GrPixelConfig, + kRGBA_half_GrPixelConfig, + kRGBA_half_Clamped_GrPixelConfig, + kRGB_ETC1_GrPixelConfig, + kAlpha_16_GrPixelConfig, + kRG_1616_GrPixelConfig, + kRGBA_16161616_GrPixelConfig, + kRG_half_GrPixelConfig, + + kLast_GrPixelConfig = kRG_half_GrPixelConfig +}; +static const int kGrPixelConfigCnt = kLast_GrPixelConfig + 1; + +// Aliases for pixel configs that match skia's byte order. +#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A) +static const GrPixelConfig kSkia8888_GrPixelConfig = kBGRA_8888_GrPixelConfig; +#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A) +static const GrPixelConfig kSkia8888_GrPixelConfig = kRGBA_8888_GrPixelConfig; +#else +static const GrPixelConfig kSkia8888_GrPixelConfig = kBGRA_8888_GrPixelConfig; +#endif + +/** + * Geometric primitives used for drawing. + */ +enum class GrPrimitiveType { + kTriangles, + kTriangleStrip, + kPoints, + kLines, // 1 pix wide only + kLineStrip, // 1 pix wide only + kPath +}; +static constexpr int kNumGrPrimitiveTypes = (int)GrPrimitiveType::kPath + 1; + +static constexpr bool GrIsPrimTypeLines(GrPrimitiveType type) { + return GrPrimitiveType::kLines == type || GrPrimitiveType::kLineStrip == type; +} + +static constexpr bool GrIsPrimTypeTris(GrPrimitiveType type) { + return GrPrimitiveType::kTriangles == type || GrPrimitiveType::kTriangleStrip == type; +} + +enum class GrPrimitiveRestart : bool { + kNo = false, + kYes = true +}; + +/** + * Formats for masks, used by the font cache. Important that these are 0-based. + */ +enum GrMaskFormat { + kA8_GrMaskFormat, //!< 1-byte per pixel + kA565_GrMaskFormat, //!< 2-bytes per pixel, RGB represent 3-channel LCD coverage + kARGB_GrMaskFormat, //!< 4-bytes per pixel, color format + + kLast_GrMaskFormat = kARGB_GrMaskFormat +}; +static const int kMaskFormatCount = kLast_GrMaskFormat + 1; + +/** + * Return the number of bytes-per-pixel for the specified mask format. + */ +static inline int GrMaskFormatBytesPerPixel(GrMaskFormat format) { + SkASSERT(format < kMaskFormatCount); + // kA8 (0) -> 1 + // kA565 (1) -> 2 + // kARGB (2) -> 4 + static const int sBytesPerPixel[] = {1, 2, 4}; + static_assert(SK_ARRAY_COUNT(sBytesPerPixel) == kMaskFormatCount, "array_size_mismatch"); + static_assert(kA8_GrMaskFormat == 0, "enum_order_dependency"); + static_assert(kA565_GrMaskFormat == 1, "enum_order_dependency"); + static_assert(kARGB_GrMaskFormat == 2, "enum_order_dependency"); + + return sBytesPerPixel[(int)format]; +} + +/** + * Describes a surface to be created. + */ +struct GrSurfaceDesc { + GrSurfaceDesc() : fWidth(0), fHeight(0), fConfig(kUnknown_GrPixelConfig) {} + + int fWidth; //!< Width of the texture + int fHeight; //!< Height of the texture + + /** + * Format of source data of the texture. Not guaranteed to be the same as + * internal format used by 3D API. + */ + GrPixelConfig fConfig; +}; + +/** Ownership rules for external GPU resources imported into Skia. */ +enum GrWrapOwnership { + /** Skia will assume the client will keep the resource alive and Skia will not free it. */ + kBorrow_GrWrapOwnership, + + /** Skia will assume ownership of the resource and free it. */ + kAdopt_GrWrapOwnership, +}; + +enum class GrWrapCacheable : bool { + /** + * The wrapped resource will be removed from the cache as soon as it becomes purgeable. It may + * still be assigned and found by a unique key, but the presence of the key will not be used to + * keep the resource alive when it has no references. + */ + kNo = false, + /** + * The wrapped resource is allowed to remain in the GrResourceCache when it has no references + * but has a unique key. Such resources should only be given unique keys when it is known that + * the key will eventually be removed from the resource or invalidated via the message bus. + */ + kYes = true +}; + +enum class GrBudgetedType : uint8_t { + /** The resource is budgeted and is subject to purging under budget pressure. */ + kBudgeted, + /** + * The resource is unbudgeted and is purged as soon as it has no refs regardless of whether + * it has a unique or scratch key. + */ + kUnbudgetedUncacheable, + /** + * The resource is unbudgeted and is allowed to remain in the cache with no refs if it + * has a unique key. Scratch keys are ignored. + */ + kUnbudgetedCacheable, +}; + +/** + * Clips are composed from these objects. + */ +enum GrClipType { + kRect_ClipType, + kPath_ClipType +}; + +enum class GrScissorTest : bool { + kDisabled = false, + kEnabled = true +}; + +struct GrMipLevel { + const void* fPixels = nullptr; + size_t fRowBytes = 0; +}; + +/** + * This enum is used to specify the load operation to be used when an GrOpsTask/GrOpsRenderPass + * begins execution. + */ +enum class GrLoadOp { + kLoad, + kClear, + kDiscard, +}; + +/** + * This enum is used to specify the store operation to be used when an GrOpsTask/GrOpsRenderPass + * ends execution. + */ +enum class GrStoreOp { + kStore, + kDiscard, +}; + +/** + * Used to control antialiasing in draw calls. + */ +enum class GrAA : bool { + kNo = false, + kYes = true +}; + +enum class GrFillRule : bool { + kNonzero, + kEvenOdd +}; + +inline GrFillRule GrFillRuleForSkPath(const SkPath& path) { + switch (path.getFillType()) { + case SkPath::kWinding_FillType: + case SkPath::kInverseWinding_FillType: + return GrFillRule::kNonzero; + case SkPath::kEvenOdd_FillType: + case SkPath::kInverseEvenOdd_FillType: + return GrFillRule::kEvenOdd; + } + SkUNREACHABLE; +} + +/** This enum indicates the type of antialiasing to be performed. */ +enum class GrAAType : unsigned { + /** No antialiasing */ + kNone, + /** Use fragment shader code or mixed samples to blend with a fractional pixel coverage. */ + kCoverage, + /** Use normal MSAA. */ + kMSAA +}; + +static constexpr bool GrAATypeIsHW(GrAAType type) { + switch (type) { + case GrAAType::kNone: + return false; + case GrAAType::kCoverage: + return false; + case GrAAType::kMSAA: + return true; + } + SkUNREACHABLE; +} + +/** + * Some pixel configs are inherently clamped to [0,1], some are allowed to go outside that range, + * and some are FP but manually clamped in the XP. + */ +enum class GrClampType { + kAuto, // Normalized, fixed-point configs + kManual, // Clamped FP configs + kNone, // Normal (unclamped) FP configs +}; + +/** + * A number of rectangle/quadrilateral drawing APIs can control anti-aliasing on a per edge basis. + * These masks specify which edges are AA'ed. The intent for this is to support tiling with seamless + * boundaries, where the inner edges are non-AA and the outer edges are AA. Regular draws (where AA + * is specified by GrAA) is almost equivalent to kNone or kAll, with the exception of how MSAA is + * handled. + * + * When tiling and there is MSAA, mixed edge rectangles are processed with MSAA, so in order for the + * tiled edges to remain seamless, inner tiles with kNone must also be processed with MSAA. In + * regular drawing, however, kNone should disable MSAA (if it's supported) to match the expected + * appearance. + * + * Therefore, APIs that use per-edge AA flags also take a GrAA value so that they can differentiate + * between the regular and tiling use case behaviors. Tiling operations should always pass + * GrAA::kYes while regular options should pass GrAA based on the SkPaint's anti-alias state. + */ +enum class GrQuadAAFlags { + kLeft = SkCanvas::kLeft_QuadAAFlag, + kTop = SkCanvas::kTop_QuadAAFlag, + kRight = SkCanvas::kRight_QuadAAFlag, + kBottom = SkCanvas::kBottom_QuadAAFlag, + + kNone = SkCanvas::kNone_QuadAAFlags, + kAll = SkCanvas::kAll_QuadAAFlags +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrQuadAAFlags) + +static inline GrQuadAAFlags SkToGrQuadAAFlags(unsigned flags) { + return static_cast(flags); +} + +/** + * Types of shader-language-specific boxed variables we can create. + */ +enum GrSLType { + kVoid_GrSLType, + kBool_GrSLType, + kByte_GrSLType, + kByte2_GrSLType, + kByte3_GrSLType, + kByte4_GrSLType, + kUByte_GrSLType, + kUByte2_GrSLType, + kUByte3_GrSLType, + kUByte4_GrSLType, + kShort_GrSLType, + kShort2_GrSLType, + kShort3_GrSLType, + kShort4_GrSLType, + kUShort_GrSLType, + kUShort2_GrSLType, + kUShort3_GrSLType, + kUShort4_GrSLType, + kFloat_GrSLType, + kFloat2_GrSLType, + kFloat3_GrSLType, + kFloat4_GrSLType, + kFloat2x2_GrSLType, + kFloat3x3_GrSLType, + kFloat4x4_GrSLType, + kHalf_GrSLType, + kHalf2_GrSLType, + kHalf3_GrSLType, + kHalf4_GrSLType, + kHalf2x2_GrSLType, + kHalf3x3_GrSLType, + kHalf4x4_GrSLType, + kInt_GrSLType, + kInt2_GrSLType, + kInt3_GrSLType, + kInt4_GrSLType, + kUint_GrSLType, + kUint2_GrSLType, + kTexture2DSampler_GrSLType, + kTextureExternalSampler_GrSLType, + kTexture2DRectSampler_GrSLType, + kTexture2D_GrSLType, + kSampler_GrSLType, + + kLast_GrSLType = kSampler_GrSLType +}; +static const int kGrSLTypeCount = kLast_GrSLType + 1; + +/** + * The type of texture. Backends other than GL currently only use the 2D value but the type must + * still be known at the API-neutral layer as it used to determine whether MIP maps, renderability, + * and sampling parameters are legal for proxies that will be instantiated with wrapped textures. + */ +enum class GrTextureType { + kNone, + k2D, + /* Rectangle uses unnormalized texture coordinates. */ + kRectangle, + kExternal +}; + +enum GrShaderType { + kVertex_GrShaderType, + kGeometry_GrShaderType, + kFragment_GrShaderType, + + kLastkFragment_GrShaderType = kFragment_GrShaderType +}; +static const int kGrShaderTypeCount = kLastkFragment_GrShaderType + 1; + +enum GrShaderFlags { + kNone_GrShaderFlags = 0, + kVertex_GrShaderFlag = 1 << kVertex_GrShaderType, + kGeometry_GrShaderFlag = 1 << kGeometry_GrShaderType, + kFragment_GrShaderFlag = 1 << kFragment_GrShaderType +}; +GR_MAKE_BITFIELD_OPS(GrShaderFlags) + +/** Is the shading language type float (including vectors/matrices)? */ +static constexpr bool GrSLTypeIsFloatType(GrSLType type) { + switch (type) { + case kFloat_GrSLType: + case kFloat2_GrSLType: + case kFloat3_GrSLType: + case kFloat4_GrSLType: + case kFloat2x2_GrSLType: + case kFloat3x3_GrSLType: + case kFloat4x4_GrSLType: + case kHalf_GrSLType: + case kHalf2_GrSLType: + case kHalf3_GrSLType: + case kHalf4_GrSLType: + case kHalf2x2_GrSLType: + case kHalf3x3_GrSLType: + case kHalf4x4_GrSLType: + return true; + + case kVoid_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kBool_GrSLType: + case kByte_GrSLType: + case kByte2_GrSLType: + case kByte3_GrSLType: + case kByte4_GrSLType: + case kUByte_GrSLType: + case kUByte2_GrSLType: + case kUByte3_GrSLType: + case kUByte4_GrSLType: + case kShort_GrSLType: + case kShort2_GrSLType: + case kShort3_GrSLType: + case kShort4_GrSLType: + case kUShort_GrSLType: + case kUShort2_GrSLType: + case kUShort3_GrSLType: + case kUShort4_GrSLType: + case kInt_GrSLType: + case kInt2_GrSLType: + case kInt3_GrSLType: + case kInt4_GrSLType: + case kUint_GrSLType: + case kUint2_GrSLType: + case kTexture2D_GrSLType: + case kSampler_GrSLType: + return false; + } + SkUNREACHABLE; +} + +/** If the type represents a single value or vector return the vector length, else -1. */ +static constexpr int GrSLTypeVecLength(GrSLType type) { + switch (type) { + case kFloat_GrSLType: + case kHalf_GrSLType: + case kBool_GrSLType: + case kByte_GrSLType: + case kUByte_GrSLType: + case kShort_GrSLType: + case kUShort_GrSLType: + case kInt_GrSLType: + case kUint_GrSLType: + return 1; + + case kFloat2_GrSLType: + case kHalf2_GrSLType: + case kByte2_GrSLType: + case kUByte2_GrSLType: + case kShort2_GrSLType: + case kUShort2_GrSLType: + case kInt2_GrSLType: + case kUint2_GrSLType: + return 2; + + case kFloat3_GrSLType: + case kHalf3_GrSLType: + case kByte3_GrSLType: + case kUByte3_GrSLType: + case kShort3_GrSLType: + case kUShort3_GrSLType: + case kInt3_GrSLType: + return 3; + + case kFloat4_GrSLType: + case kHalf4_GrSLType: + case kByte4_GrSLType: + case kUByte4_GrSLType: + case kShort4_GrSLType: + case kUShort4_GrSLType: + case kInt4_GrSLType: + return 4; + + case kFloat2x2_GrSLType: + case kFloat3x3_GrSLType: + case kFloat4x4_GrSLType: + case kHalf2x2_GrSLType: + case kHalf3x3_GrSLType: + case kHalf4x4_GrSLType: + case kVoid_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kTexture2D_GrSLType: + case kSampler_GrSLType: + return -1; + } + SkUNREACHABLE; +} + +static inline GrSLType GrSLCombinedSamplerTypeForTextureType(GrTextureType type) { + switch (type) { + case GrTextureType::k2D: + return kTexture2DSampler_GrSLType; + case GrTextureType::kRectangle: + return kTexture2DRectSampler_GrSLType; + case GrTextureType::kExternal: + return kTextureExternalSampler_GrSLType; + default: + SK_ABORT("Unexpected texture type"); + } +} + +/** Rectangle and external textures only support the clamp wrap mode and do not support + * MIP maps. + */ +static inline bool GrTextureTypeHasRestrictedSampling(GrTextureType type) { + switch (type) { + case GrTextureType::k2D: + return false; + case GrTextureType::kRectangle: + return true; + case GrTextureType::kExternal: + return true; + default: + SK_ABORT("Unexpected texture type"); + } +} + +static constexpr bool GrSLTypeIsCombinedSamplerType(GrSLType type) { + switch (type) { + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + return true; + + case kVoid_GrSLType: + case kFloat_GrSLType: + case kFloat2_GrSLType: + case kFloat3_GrSLType: + case kFloat4_GrSLType: + case kFloat2x2_GrSLType: + case kFloat3x3_GrSLType: + case kFloat4x4_GrSLType: + case kHalf_GrSLType: + case kHalf2_GrSLType: + case kHalf3_GrSLType: + case kHalf4_GrSLType: + case kHalf2x2_GrSLType: + case kHalf3x3_GrSLType: + case kHalf4x4_GrSLType: + case kInt_GrSLType: + case kInt2_GrSLType: + case kInt3_GrSLType: + case kInt4_GrSLType: + case kUint_GrSLType: + case kUint2_GrSLType: + case kBool_GrSLType: + case kByte_GrSLType: + case kByte2_GrSLType: + case kByte3_GrSLType: + case kByte4_GrSLType: + case kUByte_GrSLType: + case kUByte2_GrSLType: + case kUByte3_GrSLType: + case kUByte4_GrSLType: + case kShort_GrSLType: + case kShort2_GrSLType: + case kShort3_GrSLType: + case kShort4_GrSLType: + case kUShort_GrSLType: + case kUShort2_GrSLType: + case kUShort3_GrSLType: + case kUShort4_GrSLType: + case kTexture2D_GrSLType: + case kSampler_GrSLType: + return false; + } + SkUNREACHABLE; +} + +////////////////////////////////////////////////////////////////////////////// + +/** + * Types used to describe format of vertices in arrays. + */ +enum GrVertexAttribType { + kFloat_GrVertexAttribType = 0, + kFloat2_GrVertexAttribType, + kFloat3_GrVertexAttribType, + kFloat4_GrVertexAttribType, + kHalf_GrVertexAttribType, + kHalf2_GrVertexAttribType, + kHalf3_GrVertexAttribType, + kHalf4_GrVertexAttribType, + + kInt2_GrVertexAttribType, // vector of 2 32-bit ints + kInt3_GrVertexAttribType, // vector of 3 32-bit ints + kInt4_GrVertexAttribType, // vector of 4 32-bit ints + + + kByte_GrVertexAttribType, // signed byte + kByte2_GrVertexAttribType, // vector of 2 8-bit signed bytes + kByte3_GrVertexAttribType, // vector of 3 8-bit signed bytes + kByte4_GrVertexAttribType, // vector of 4 8-bit signed bytes + kUByte_GrVertexAttribType, // unsigned byte + kUByte2_GrVertexAttribType, // vector of 2 8-bit unsigned bytes + kUByte3_GrVertexAttribType, // vector of 3 8-bit unsigned bytes + kUByte4_GrVertexAttribType, // vector of 4 8-bit unsigned bytes + + kUByte_norm_GrVertexAttribType, // unsigned byte, e.g. coverage, 0 -> 0.0f, 255 -> 1.0f. + kUByte4_norm_GrVertexAttribType, // vector of 4 unsigned bytes, e.g. colors, 0 -> 0.0f, + // 255 -> 1.0f. + + kShort2_GrVertexAttribType, // vector of 2 16-bit shorts. + kShort4_GrVertexAttribType, // vector of 4 16-bit shorts. + + kUShort2_GrVertexAttribType, // vector of 2 unsigned shorts. 0 -> 0, 65535 -> 65535. + kUShort2_norm_GrVertexAttribType, // vector of 2 unsigned shorts. 0 -> 0.0f, 65535 -> 1.0f. + + kInt_GrVertexAttribType, + kUint_GrVertexAttribType, + + kUShort_norm_GrVertexAttribType, + + kUShort4_norm_GrVertexAttribType, // vector of 4 unsigned shorts. 0 -> 0.0f, 65535 -> 1.0f. + + kLast_GrVertexAttribType = kUShort4_norm_GrVertexAttribType +}; +static const int kGrVertexAttribTypeCount = kLast_GrVertexAttribType + 1; + +////////////////////////////////////////////////////////////////////////////// + +static const int kGrClipEdgeTypeCnt = (int) GrClipEdgeType::kLast + 1; + +static constexpr bool GrProcessorEdgeTypeIsFill(const GrClipEdgeType edgeType) { + return (GrClipEdgeType::kFillAA == edgeType || GrClipEdgeType::kFillBW == edgeType); +} + +static constexpr bool GrProcessorEdgeTypeIsInverseFill(const GrClipEdgeType edgeType) { + return (GrClipEdgeType::kInverseFillAA == edgeType || + GrClipEdgeType::kInverseFillBW == edgeType); +} + +static constexpr bool GrProcessorEdgeTypeIsAA(const GrClipEdgeType edgeType) { + return (GrClipEdgeType::kFillBW != edgeType && + GrClipEdgeType::kInverseFillBW != edgeType); +} + +static inline GrClipEdgeType GrInvertProcessorEdgeType(const GrClipEdgeType edgeType) { + switch (edgeType) { + case GrClipEdgeType::kFillBW: + return GrClipEdgeType::kInverseFillBW; + case GrClipEdgeType::kFillAA: + return GrClipEdgeType::kInverseFillAA; + case GrClipEdgeType::kInverseFillBW: + return GrClipEdgeType::kFillBW; + case GrClipEdgeType::kInverseFillAA: + return GrClipEdgeType::kFillAA; + case GrClipEdgeType::kHairlineAA: + SK_ABORT("Hairline fill isn't invertible."); + } + return GrClipEdgeType::kFillAA; // suppress warning. +} + +/** + * Indicates the type of pending IO operations that can be recorded for gpu resources. + */ +enum GrIOType { + kRead_GrIOType, + kWrite_GrIOType, + kRW_GrIOType +}; + +/** + * Indicates the type of data that a GPU buffer will be used for. + */ +enum class GrGpuBufferType { + kVertex, + kIndex, + kXferCpuToGpu, + kXferGpuToCpu, +}; +static const int kGrGpuBufferTypeCount = static_cast(GrGpuBufferType::kXferGpuToCpu) + 1; + +/** + * Provides a performance hint regarding the frequency at which a data store will be accessed. + */ +enum GrAccessPattern { + /** Data store will be respecified repeatedly and used many times. */ + kDynamic_GrAccessPattern, + /** Data store will be specified once and used many times. (Thus disqualified from caching.) */ + kStatic_GrAccessPattern, + /** Data store will be specified once and used at most a few times. (Also can't be cached.) */ + kStream_GrAccessPattern, + + kLast_GrAccessPattern = kStream_GrAccessPattern +}; + +// Flags shared between the GrSurface & GrSurfaceProxy class hierarchies +enum class GrInternalSurfaceFlags { + kNone = 0, + + // Texture-level + + // Means the pixels in the texture are read-only. Cannot also be a GrRenderTarget[Proxy]. + kReadOnly = 1 << 0, + + // RT-level + + // This flag is for use with GL only. It tells us that the internal render target wraps FBO 0. + kGLRTFBOIDIs0 = 1 << 1, + + // This means the render target is multisampled, and internally holds a non-msaa texture for + // resolving into. The render target resolves itself by blitting into this internal texture. + // (asTexture() might or might not return the internal texture, but if it does, we always + // resolve the render target before accessing this texture's data.) + kRequiresManualMSAAResolve = 1 << 2, +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrInternalSurfaceFlags) + +// 'GR_MAKE_BITFIELD_CLASS_OPS' defines the & operator on GrInternalSurfaceFlags to return bool. +// We want to find the bitwise & with these masks, so we declare them as ints. +constexpr static int kGrInternalTextureFlagsMask = static_cast( + GrInternalSurfaceFlags::kReadOnly); + +constexpr static int kGrInternalRenderTargetFlagsMask = static_cast( + GrInternalSurfaceFlags::kGLRTFBOIDIs0 | GrInternalSurfaceFlags::kRequiresManualMSAAResolve); + +constexpr static int kGrInternalTextureRenderTargetFlagsMask = + kGrInternalTextureFlagsMask | kGrInternalRenderTargetFlagsMask; + +#ifdef SK_DEBUG +// Takes a pointer to a GrCaps, and will suppress prints if required +#define GrCapsDebugf(caps, ...) if (!(caps)->suppressPrints()) SkDebugf(__VA_ARGS__) +#else +#define GrCapsDebugf(caps, ...) do {} while (0) +#endif + +/** + * Specifies if the holder owns the backend, OpenGL or Vulkan, object. + */ +enum class GrBackendObjectOwnership : bool { + /** Holder does not destroy the backend object. */ + kBorrowed = false, + /** Holder destroys the backend object. */ + kOwned = true +}; + +template +T* const* unique_ptr_address_as_pointer_address(std::unique_ptr const* up) { + static_assert(sizeof(T*) == sizeof(std::unique_ptr), "unique_ptr not expected size."); + return reinterpret_cast(up); +} + +/* + * Object for CPU-GPU synchronization + */ +typedef uint64_t GrFence; + +/** + * Used to include or exclude specific GPU path renderers for testing purposes. + */ +enum class GpuPathRenderers { + kNone = 0, // Always use software masks and/or GrDefaultPathRenderer. + kDashLine = 1 << 0, + kStencilAndCover = 1 << 1, + kCoverageCounting = 1 << 2, + kAAHairline = 1 << 3, + kAAConvex = 1 << 4, + kAALinearizing = 1 << 5, + kSmall = 1 << 6, + kTessellating = 1 << 7, + + kAll = (kTessellating | (kTessellating - 1)), + kDefault = kAll & ~kCoverageCounting + +}; + +/** + * Used to describe the current state of Mips on a GrTexture + */ +enum class GrMipMapsStatus { + kNotAllocated, // Mips have not been allocated + kDirty, // Mips are allocated but the full mip tree does not have valid data + kValid, // All levels fully allocated and have valid data in them +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GpuPathRenderers) + +/** + * Utility functions for GrPixelConfig + */ + +static constexpr GrPixelConfig GrCompressionTypePixelConfig(SkImage::CompressionType compression) { + switch (compression) { + case SkImage::kETC1_CompressionType: return kRGB_ETC1_GrPixelConfig; + } + SkUNREACHABLE; +} + +/** + * Returns true if the pixel config is a GPU-specific compressed format + * representation. + */ +static constexpr bool GrPixelConfigIsCompressed(GrPixelConfig config) { + switch (config) { + case kRGB_ETC1_GrPixelConfig: + return true; + default: + return false; + } + SkUNREACHABLE; +} + +/** + * Returns the data size for the given SkImage::CompressionType + */ +static inline size_t GrCompressedFormatDataSize(SkImage::CompressionType compressionType, + int width, int height) { + switch (compressionType) { + case SkImage::kETC1_CompressionType: + SkASSERT((width & 3) == 0); + SkASSERT((height & 3) == 0); + return (width >> 2) * (height >> 2) * 8; + } + + SK_ABORT("Invalid pixel config"); +} + +/** + * Like SkColorType this describes a layout of pixel data in CPU memory. It specifies the channels, + * their type, and width. This exists so that the GPU backend can have private types that have no + * analog in the public facing SkColorType enum and omit types not implemented in the GPU backend. + * It does not refer to a texture format and the mapping to texture formats may be many-to-many. + * It does not specify the sRGB encoding of the stored values. The components are listed in order of + * where they appear in memory. In other words the first component listed is in the low bits and + * the last component in the high bits. + */ +enum class GrColorType { + kUnknown, + kAlpha_8, + kBGR_565, + kABGR_4444, // This name differs from SkColorType. kARGB_4444_SkColorType is misnamed. + kRGBA_8888, + kRGBA_8888_SRGB, + kRGB_888x, + kRG_88, + kBGRA_8888, + kRGBA_1010102, + kGray_8, + kAlpha_F16, + kRGBA_F16, + kRGBA_F16_Clamped, + kRGBA_F32, + + kAlpha_16, + kRG_1616, + kRG_F16, + kRGBA_16161616, + + // Unusual formats that come up after reading back in cases where we are reassigning the meaning + // of a texture format's channels to use for a particular color format but have to read back the + // data to a full RGBA quadruple. (e.g. using a R8 texture format as A8 color type but the API + // only supports reading to RGBA8.) None of these have SkColorType equivalents. + kAlpha_8xxx, + kAlpha_F32xxx, + kGray_8xxx, + + kLast = kGray_8xxx +}; + +static const int kGrColorTypeCnt = static_cast(GrColorType::kLast) + 1; + +static constexpr SkColorType GrColorTypeToSkColorType(GrColorType ct) { + switch (ct) { + case GrColorType::kUnknown: return kUnknown_SkColorType; + case GrColorType::kAlpha_8: return kAlpha_8_SkColorType; + case GrColorType::kBGR_565: return kRGB_565_SkColorType; + case GrColorType::kABGR_4444: return kARGB_4444_SkColorType; + case GrColorType::kRGBA_8888: return kRGBA_8888_SkColorType; + // Once we add kRGBA_8888_SRGB_SkColorType we should return that here. + case GrColorType::kRGBA_8888_SRGB: return kRGBA_8888_SkColorType; + case GrColorType::kRGB_888x: return kRGB_888x_SkColorType; + case GrColorType::kRG_88: return kR8G8_unorm_SkColorType; + case GrColorType::kBGRA_8888: return kBGRA_8888_SkColorType; + case GrColorType::kRGBA_1010102: return kRGBA_1010102_SkColorType; + case GrColorType::kGray_8: return kGray_8_SkColorType; + case GrColorType::kAlpha_F16: return kA16_float_SkColorType; + case GrColorType::kRGBA_F16: return kRGBA_F16_SkColorType; + case GrColorType::kRGBA_F16_Clamped: return kRGBA_F16Norm_SkColorType; + case GrColorType::kRGBA_F32: return kRGBA_F32_SkColorType; + case GrColorType::kAlpha_8xxx: return kUnknown_SkColorType; + case GrColorType::kAlpha_F32xxx: return kUnknown_SkColorType; + case GrColorType::kGray_8xxx: return kUnknown_SkColorType; + case GrColorType::kAlpha_16: return kA16_unorm_SkColorType; + case GrColorType::kRG_1616: return kR16G16_unorm_SkColorType; + case GrColorType::kRGBA_16161616: return kR16G16B16A16_unorm_SkColorType; + case GrColorType::kRG_F16: return kR16G16_float_SkColorType; + } + SkUNREACHABLE; +} + +static constexpr GrColorType SkColorTypeToGrColorType(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return GrColorType::kUnknown; + case kAlpha_8_SkColorType: return GrColorType::kAlpha_8; + case kRGB_565_SkColorType: return GrColorType::kBGR_565; + case kARGB_4444_SkColorType: return GrColorType::kABGR_4444; + case kRGBA_8888_SkColorType: return GrColorType::kRGBA_8888; + case kRGB_888x_SkColorType: return GrColorType::kRGB_888x; + case kBGRA_8888_SkColorType: return GrColorType::kBGRA_8888; + case kGray_8_SkColorType: return GrColorType::kGray_8; + case kRGBA_F16Norm_SkColorType: return GrColorType::kRGBA_F16_Clamped; + case kRGBA_F16_SkColorType: return GrColorType::kRGBA_F16; + case kRGBA_1010102_SkColorType: return GrColorType::kRGBA_1010102; + case kRGB_101010x_SkColorType: return GrColorType::kUnknown; + case kRGBA_F32_SkColorType: return GrColorType::kRGBA_F32; + case kR8G8_unorm_SkColorType: return GrColorType::kRG_88; + case kA16_unorm_SkColorType: return GrColorType::kAlpha_16; + case kR16G16_unorm_SkColorType: return GrColorType::kRG_1616; + case kA16_float_SkColorType: return GrColorType::kAlpha_F16; + case kR16G16_float_SkColorType: return GrColorType::kRG_F16; + case kR16G16B16A16_unorm_SkColorType: return GrColorType::kRGBA_16161616; + } + SkUNREACHABLE; +} + +// This is a temporary means of mapping an SkColorType and format to a +// GrColorType::kRGBA_8888_SRGB. Once we have an SRGB SkColorType this can go away. +GrColorType SkColorTypeAndFormatToGrColorType(const GrCaps* caps, + SkColorType skCT, + const GrBackendFormat& format); + +static constexpr uint32_t GrColorTypeComponentFlags(GrColorType ct) { + switch (ct) { + case GrColorType::kUnknown: return 0; + case GrColorType::kAlpha_8: return kAlpha_SkColorTypeComponentFlag; + case GrColorType::kBGR_565: return kRGB_SkColorTypeComponentFlags; + case GrColorType::kABGR_4444: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGBA_8888: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGBA_8888_SRGB: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGB_888x: return kRGB_SkColorTypeComponentFlags; + case GrColorType::kRG_88: return kRed_SkColorTypeComponentFlag | + kGreen_SkColorTypeComponentFlag; + case GrColorType::kBGRA_8888: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGBA_1010102: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kGray_8: return kGray_SkColorTypeComponentFlag; + case GrColorType::kAlpha_F16: return kAlpha_SkColorTypeComponentFlag; + case GrColorType::kRGBA_F16: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGBA_F16_Clamped: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRGBA_F32: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kAlpha_8xxx: return kAlpha_SkColorTypeComponentFlag; + case GrColorType::kAlpha_F32xxx: return kAlpha_SkColorTypeComponentFlag; + case GrColorType::kGray_8xxx: return kGray_SkColorTypeComponentFlag; + case GrColorType::kAlpha_16: return kAlpha_SkColorTypeComponentFlag; + case GrColorType::kRG_1616: return kRed_SkColorTypeComponentFlag | + kGreen_SkColorTypeComponentFlag; + case GrColorType::kRGBA_16161616: return kRGBA_SkColorTypeComponentFlags; + case GrColorType::kRG_F16: return kRed_SkColorTypeComponentFlag | + kGreen_SkColorTypeComponentFlag; + } + SkUNREACHABLE; +} + +/** + * Describes the encoding of channel data in a GrColorType. + */ +enum class GrColorTypeEncoding { + kUnorm, + kSRGBUnorm, + // kSnorm, + kFloat, + // kSint + // kUint +}; + +/** + * Describes a GrColorType by how many bits are used for each color component and how they are + * encoded. Currently all the non-zero channels share a single GrColorTypeEncoding. This could be + * expanded to store separate encodings and to indicate which bits belong to which components. + */ +struct GrColorTypeDesc { +public: + static constexpr GrColorTypeDesc MakeRGBA(int rgba, GrColorTypeEncoding e) { + return {rgba, rgba, rgba, rgba, 0, e}; + } + + static constexpr GrColorTypeDesc MakeRGBA(int rgb, int a, GrColorTypeEncoding e) { + return {rgb, rgb, rgb, a, 0, e}; + } + + static constexpr GrColorTypeDesc MakeRGB(int rgb, GrColorTypeEncoding e) { + return {rgb, rgb, rgb, 0, 0, e}; + } + + static constexpr GrColorTypeDesc MakeRGB(int r, int g, int b, GrColorTypeEncoding e) { + return {r, g, b, 0, 0, e}; + } + + static constexpr GrColorTypeDesc MakeAlpha(int a, GrColorTypeEncoding e) { + return {0, 0, 0, a, 0, e}; + } + + static constexpr GrColorTypeDesc MakeR(int r, GrColorTypeEncoding e) { + return {r, 0, 0, 0, 0, e}; + } + + static constexpr GrColorTypeDesc MakeRG(int rg, GrColorTypeEncoding e) { + return {rg, rg, 0, 0, 0, e}; + } + + static constexpr GrColorTypeDesc MakeGray(int grayBits, GrColorTypeEncoding e) { + return {0, 0, 0, 0, grayBits, e}; + } + + static constexpr GrColorTypeDesc MakeInvalid() { return {}; } + + constexpr int r() const { return fRBits; } + constexpr int g() const { return fGBits; } + constexpr int b() const { return fBBits; } + constexpr int a() const { return fABits; } + constexpr int operator[](int c) const { + switch (c) { + case 0: return this->r(); + case 1: return this->g(); + case 2: return this->b(); + case 3: return this->a(); + } + SkUNREACHABLE; + } + + constexpr int gray() const { return fGrayBits; } + + constexpr GrColorTypeEncoding encoding() const { return fEncoding; } + +private: + int fRBits = 0; + int fGBits = 0; + int fBBits = 0; + int fABits = 0; + int fGrayBits = 0; + GrColorTypeEncoding fEncoding = GrColorTypeEncoding::kUnorm; + + constexpr GrColorTypeDesc() = default; + + constexpr GrColorTypeDesc(int r, int g, int b, int a, int gray, GrColorTypeEncoding encoding) + : fRBits(r), fGBits(g), fBBits(b), fABits(a), fGrayBits(gray), fEncoding(encoding) { + SkASSERT(r >= 0 && g >= 0 && b >= 0 && a >= 0 && gray >= 0); + SkASSERT(!gray || (!r && !g && !b)); + SkASSERT(r || g || b || a || gray); + } +}; + +static constexpr GrColorTypeDesc GrGetColorTypeDesc(GrColorType ct) { + switch (ct) { + case GrColorType::kUnknown: + return GrColorTypeDesc::MakeInvalid(); + case GrColorType::kAlpha_8: + return GrColorTypeDesc::MakeAlpha(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kBGR_565: + return GrColorTypeDesc::MakeRGB(5, 6, 5, GrColorTypeEncoding::kUnorm); + case GrColorType::kABGR_4444: + return GrColorTypeDesc::MakeRGBA(4, GrColorTypeEncoding::kUnorm); + case GrColorType::kRGBA_8888: + return GrColorTypeDesc::MakeRGBA(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kRGBA_8888_SRGB: + return GrColorTypeDesc::MakeRGBA(8, GrColorTypeEncoding::kSRGBUnorm); + case GrColorType::kRGB_888x: + return GrColorTypeDesc::MakeRGB(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kRG_88: + return GrColorTypeDesc::MakeRG(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kBGRA_8888: + return GrColorTypeDesc::MakeRGBA(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kRGBA_1010102: + return GrColorTypeDesc::MakeRGBA(10, 2, GrColorTypeEncoding::kUnorm); + case GrColorType::kGray_8: + return GrColorTypeDesc::MakeGray(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kAlpha_F16: + return GrColorTypeDesc::MakeAlpha(16, GrColorTypeEncoding::kFloat); + case GrColorType::kRGBA_F16: + return GrColorTypeDesc::MakeRGBA(16, GrColorTypeEncoding::kFloat); + case GrColorType::kRGBA_F16_Clamped: + return GrColorTypeDesc::MakeRGBA(16, GrColorTypeEncoding::kFloat); + case GrColorType::kRGBA_F32: + return GrColorTypeDesc::MakeRGBA(32, GrColorTypeEncoding::kFloat); + case GrColorType::kAlpha_8xxx: + return GrColorTypeDesc::MakeAlpha(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kAlpha_F32xxx: + return GrColorTypeDesc::MakeAlpha(32, GrColorTypeEncoding::kFloat); + case GrColorType::kGray_8xxx: + return GrColorTypeDesc::MakeGray(8, GrColorTypeEncoding::kUnorm); + case GrColorType::kAlpha_16: + return GrColorTypeDesc::MakeAlpha(16, GrColorTypeEncoding::kUnorm); + case GrColorType::kRG_1616: + return GrColorTypeDesc::MakeRG(16, GrColorTypeEncoding::kUnorm); + case GrColorType::kRGBA_16161616: + return GrColorTypeDesc::MakeRGBA(16, GrColorTypeEncoding::kUnorm); + case GrColorType::kRG_F16: + return GrColorTypeDesc::MakeRG(16, GrColorTypeEncoding::kFloat); + } + SkUNREACHABLE; +} + +static constexpr GrClampType GrColorTypeClampType(GrColorType colorType) { + if (GrGetColorTypeDesc(colorType).encoding() == GrColorTypeEncoding::kUnorm || + GrGetColorTypeDesc(colorType).encoding() == GrColorTypeEncoding::kSRGBUnorm) { + return GrClampType::kAuto; + } + return GrColorType::kRGBA_F16_Clamped == colorType ? GrClampType::kManual : GrClampType::kNone; +} + +// Consider a color type "wider" than n if it has more than n bits for any its representable +// channels. +static constexpr bool GrColorTypeIsWiderThan(GrColorType colorType, int n) { + SkASSERT(n > 0); + auto desc = GrGetColorTypeDesc(colorType); + return (desc.r() && desc.r() > n )|| + (desc.g() && desc.g() > n) || + (desc.b() && desc.b() > n) || + (desc.a() && desc.a() > n) || + (desc.gray() && desc.gray() > n); +} + +static constexpr bool GrColorTypeIsAlphaOnly(GrColorType ct) { + return kAlpha_SkColorTypeComponentFlag == GrColorTypeComponentFlags(ct); +} + +static constexpr bool GrColorTypeHasAlpha(GrColorType ct) { + return kAlpha_SkColorTypeComponentFlag & GrColorTypeComponentFlags(ct); +} + +static constexpr size_t GrColorTypeBytesPerPixel(GrColorType ct) { + switch (ct) { + case GrColorType::kUnknown: return 0; + case GrColorType::kAlpha_8: return 1; + case GrColorType::kBGR_565: return 2; + case GrColorType::kABGR_4444: return 2; + case GrColorType::kRGBA_8888: return 4; + case GrColorType::kRGBA_8888_SRGB: return 4; + case GrColorType::kRGB_888x: return 4; + case GrColorType::kRG_88: return 2; + case GrColorType::kBGRA_8888: return 4; + case GrColorType::kRGBA_1010102: return 4; + case GrColorType::kGray_8: return 1; + case GrColorType::kAlpha_F16: return 2; + case GrColorType::kRGBA_F16: return 8; + case GrColorType::kRGBA_F16_Clamped: return 8; + case GrColorType::kRGBA_F32: return 16; + case GrColorType::kAlpha_8xxx: return 4; + case GrColorType::kAlpha_F32xxx: return 16; + case GrColorType::kGray_8xxx: return 4; + case GrColorType::kAlpha_16: return 2; + case GrColorType::kRG_1616: return 4; + case GrColorType::kRGBA_16161616: return 8; + case GrColorType::kRG_F16: return 4; + } + SkUNREACHABLE; +} + +static constexpr GrColorType GrPixelConfigToColorType(GrPixelConfig config) { + switch (config) { + case kUnknown_GrPixelConfig: + return GrColorType::kUnknown; + case kAlpha_8_GrPixelConfig: + return GrColorType::kAlpha_8; + case kGray_8_GrPixelConfig: + return GrColorType::kGray_8; + case kRGB_565_GrPixelConfig: + return GrColorType::kBGR_565; + case kRGBA_4444_GrPixelConfig: + return GrColorType::kABGR_4444; + case kRGBA_8888_GrPixelConfig: + return GrColorType::kRGBA_8888; + case kRGB_888_GrPixelConfig: + return GrColorType::kRGB_888x; + case kRGB_888X_GrPixelConfig: + return GrColorType::kRGB_888x; + case kRG_88_GrPixelConfig: + return GrColorType::kRG_88; + case kBGRA_8888_GrPixelConfig: + return GrColorType::kBGRA_8888; + case kSRGBA_8888_GrPixelConfig: + return GrColorType::kRGBA_8888_SRGB; + case kRGBA_1010102_GrPixelConfig: + return GrColorType::kRGBA_1010102; + case kAlpha_half_GrPixelConfig: + return GrColorType::kAlpha_F16; + case kRGBA_half_GrPixelConfig: + return GrColorType::kRGBA_F16; + case kRGBA_half_Clamped_GrPixelConfig: + return GrColorType::kRGBA_F16_Clamped; + case kRGB_ETC1_GrPixelConfig: + // We may need a roughly equivalent color type for a compressed texture. This should be + // the logical format for decompressing the data into. + return GrColorType::kRGB_888x; + case kAlpha_8_as_Alpha_GrPixelConfig: + return GrColorType::kAlpha_8; + case kAlpha_8_as_Red_GrPixelConfig: + return GrColorType::kAlpha_8; + case kAlpha_half_as_Lum_GrPixelConfig: // fall through + case kAlpha_half_as_Red_GrPixelConfig: + return GrColorType::kAlpha_F16; + case kGray_8_as_Lum_GrPixelConfig: + return GrColorType::kGray_8; + case kGray_8_as_Red_GrPixelConfig: + return GrColorType::kGray_8; + case kAlpha_16_GrPixelConfig: + return GrColorType::kAlpha_16; + case kRG_1616_GrPixelConfig: + return GrColorType::kRG_1616; + case kRGBA_16161616_GrPixelConfig: + return GrColorType::kRGBA_16161616; + case kRG_half_GrPixelConfig: + return GrColorType::kRG_F16; + } + SkUNREACHABLE; +} + +static constexpr GrPixelConfig GrColorTypeToPixelConfig(GrColorType colorType) { + switch (colorType) { + case GrColorType::kUnknown: return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_8: return kAlpha_8_GrPixelConfig; + case GrColorType::kGray_8: return kGray_8_GrPixelConfig; + case GrColorType::kBGR_565: return kRGB_565_GrPixelConfig; + case GrColorType::kABGR_4444: return kRGBA_4444_GrPixelConfig; + case GrColorType::kRGBA_8888: return kRGBA_8888_GrPixelConfig; + case GrColorType::kRGBA_8888_SRGB: return kSRGBA_8888_GrPixelConfig; + case GrColorType::kRGB_888x: return kRGB_888_GrPixelConfig; + case GrColorType::kRG_88: return kRG_88_GrPixelConfig; + case GrColorType::kBGRA_8888: return kBGRA_8888_GrPixelConfig; + case GrColorType::kRGBA_1010102: return kRGBA_1010102_GrPixelConfig; + case GrColorType::kRGBA_F32: return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_F16: return kAlpha_half_GrPixelConfig; + case GrColorType::kRGBA_F16: return kRGBA_half_GrPixelConfig; + case GrColorType::kRGBA_F16_Clamped: return kRGBA_half_Clamped_GrPixelConfig; + case GrColorType::kAlpha_8xxx: return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_F32xxx: return kUnknown_GrPixelConfig; + case GrColorType::kGray_8xxx: return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_16: return kAlpha_16_GrPixelConfig; + case GrColorType::kRG_1616: return kRG_1616_GrPixelConfig; + case GrColorType::kRGBA_16161616: return kRGBA_16161616_GrPixelConfig; + case GrColorType::kRG_F16: return kRG_half_GrPixelConfig; + } + SkUNREACHABLE; +} + +/** + * Ref-counted object that calls a callback from its destructor. + */ +class GrRefCntedCallback : public SkRefCnt { +public: + using Context = void*; + using Callback = void (*)(Context); + + GrRefCntedCallback(Callback proc, Context ctx) : fReleaseProc(proc), fReleaseCtx(ctx) { + SkASSERT(proc); + } + ~GrRefCntedCallback() override { fReleaseProc ? fReleaseProc(fReleaseCtx) : void(); } + + Context context() const { return fReleaseCtx; } + +private: + Callback fReleaseProc; + Context fReleaseCtx; +}; + +#if GR_TEST_UTILS || defined(SK_ENABLE_DUMP_GPU) +static constexpr const char* GrBackendApiToStr(GrBackendApi api) { + switch (api) { + case GrBackendApi::kMetal: return "Metal"; + case GrBackendApi::kDawn: return "Dawn"; + case GrBackendApi::kOpenGL: return "OpenGL"; + case GrBackendApi::kVulkan: return "Vulkan"; + case GrBackendApi::kMock: return "Mock"; + } + SkUNREACHABLE; +} + +static constexpr const char* GrColorTypeToStr(GrColorType ct) { + switch (ct) { + case GrColorType::kUnknown: return "kUnknown"; + case GrColorType::kAlpha_8: return "kAlpha_8"; + case GrColorType::kBGR_565: return "kRGB_565"; + case GrColorType::kABGR_4444: return "kABGR_4444"; + case GrColorType::kRGBA_8888: return "kRGBA_8888"; + case GrColorType::kRGBA_8888_SRGB: return "kRGBA_8888_SRGB"; + case GrColorType::kRGB_888x: return "kRGB_888x"; + case GrColorType::kRG_88: return "kRG_88"; + case GrColorType::kBGRA_8888: return "kBGRA_8888"; + case GrColorType::kRGBA_1010102: return "kRGBA_1010102"; + case GrColorType::kGray_8: return "kGray_8"; + case GrColorType::kAlpha_F16: return "kAlpha_F16"; + case GrColorType::kRGBA_F16: return "kRGBA_F16"; + case GrColorType::kRGBA_F16_Clamped: return "kRGBA_F16_Clamped"; + case GrColorType::kRGBA_F32: return "kRGBA_F32"; + case GrColorType::kAlpha_8xxx: return "kAlpha_8xxx"; + case GrColorType::kAlpha_F32xxx: return "kAlpha_F32xxx"; + case GrColorType::kGray_8xxx: return "kGray_8xxx"; + case GrColorType::kAlpha_16: return "kAlpha_16"; + case GrColorType::kRG_1616: return "kRG_1616"; + case GrColorType::kRGBA_16161616: return "kRGBA_16161616"; + case GrColorType::kRG_F16: return "kRG_F16"; + } + SkUNREACHABLE; +} +#endif + +#endif diff --git a/gfx/skia/skia/include/private/GrVkTypesPriv.h b/gfx/skia/skia/include/private/GrVkTypesPriv.h new file mode 100644 index 0000000000..49b392c52b --- /dev/null +++ b/gfx/skia/skia/include/private/GrVkTypesPriv.h @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkTypesPriv_DEFINED +#define GrVkTypesPriv_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/vk/GrVkTypes.h" + +class GrVkImageLayout; + +// This struct is to used to store the the actual information about the vulkan backend image on the +// GrBackendTexture and GrBackendRenderTarget. When a client calls getVkImageInfo on a +// GrBackendTexture/RenderTarget, we use the GrVkBackendSurfaceInfo to create a snapshot +// GrVkImgeInfo object. Internally, this uses a ref count GrVkImageLayout object to track the +// current VkImageLayout which can be shared with an internal GrVkImage so that layout updates can +// be seen by all users of the image. +struct GrVkBackendSurfaceInfo { + GrVkBackendSurfaceInfo(GrVkImageInfo info, GrVkImageLayout* layout) + : fImageInfo(info), fLayout(layout) {} + + void cleanup(); + + GrVkBackendSurfaceInfo& operator=(const GrVkBackendSurfaceInfo&) = delete; + + // Assigns the passed in GrVkBackendSurfaceInfo to this object. if isValid is true we will also + // attempt to unref the old fLayout on this object. + void assign(const GrVkBackendSurfaceInfo&, bool isValid); + + void setImageLayout(VkImageLayout layout); + + sk_sp getGrVkImageLayout() const; + + GrVkImageInfo snapImageInfo() const; + + bool isProtected() const { return fImageInfo.fProtected == GrProtected::kYes; } +#if GR_TEST_UTILS + bool operator==(const GrVkBackendSurfaceInfo& that) const; +#endif + +private: + GrVkImageInfo fImageInfo; + GrVkImageLayout* fLayout; +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkBitmaskEnum.h b/gfx/skia/skia/include/private/SkBitmaskEnum.h new file mode 100644 index 0000000000..71022b2ce5 --- /dev/null +++ b/gfx/skia/skia/include/private/SkBitmaskEnum.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkEnumOperators_DEFINED +#define SkEnumOperators_DEFINED + +#include + +namespace skstd { +template struct is_bitmask_enum : std::false_type {}; + +template +typename std::enable_if::value, bool>::type constexpr Any(E e) { + return static_cast::type>(e) != 0; +} +} // namespace skstd + +template +typename std::enable_if::value, E>::type constexpr operator|(E l, E r) { + using U = typename std::underlying_type::type; + return static_cast(static_cast(l) | static_cast(r)); +} + +template +typename std::enable_if::value, E&>::type constexpr operator|=(E& l, E r) { + return l = l | r; +} + +template +typename std::enable_if::value, E>::type constexpr operator&(E l, E r) { + using U = typename std::underlying_type::type; + return static_cast(static_cast(l) & static_cast(r)); +} + +template +typename std::enable_if::value, E&>::type constexpr operator&=(E& l, E r) { + return l = l & r; +} + +template +typename std::enable_if::value, E>::type constexpr operator~(E e) { + return static_cast(~static_cast::type>(e)); +} + +#endif // SkEnumOperators_DEFINED diff --git a/gfx/skia/skia/include/private/SkChecksum.h b/gfx/skia/skia/include/private/SkChecksum.h new file mode 100644 index 0000000000..553bfcdfab --- /dev/null +++ b/gfx/skia/skia/include/private/SkChecksum.h @@ -0,0 +1,71 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkChecksum_DEFINED +#define SkChecksum_DEFINED + +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTLogic.h" + +// #include "src/core/SkOpts.h" +// It's sort of pesky to be able to include SkOpts.h here, so we'll just re-declare what we need. +namespace SkOpts { + extern uint32_t (*hash_fn)(const void*, size_t, uint32_t); +} + +class SkChecksum : SkNoncopyable { +public: + /** + * uint32_t -> uint32_t hash, useful for when you're about to trucate this hash but you + * suspect its low bits aren't well mixed. + * + * This is the Murmur3 finalizer. + */ + static uint32_t Mix(uint32_t hash) { + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + return hash; + } + + /** + * uint32_t -> uint32_t hash, useful for when you're about to trucate this hash but you + * suspect its low bits aren't well mixed. + * + * This version is 2-lines cheaper than Mix, but seems to be sufficient for the font cache. + */ + static uint32_t CheapMix(uint32_t hash) { + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 16; + return hash; + } +}; + +// SkGoodHash should usually be your first choice in hashing data. +// It should be both reasonably fast and high quality. +struct SkGoodHash { + template + SK_WHEN(sizeof(K) == 4, uint32_t) operator()(const K& k) const { + return SkChecksum::Mix(*(const uint32_t*)&k); + } + + template + SK_WHEN(sizeof(K) != 4, uint32_t) operator()(const K& k) const { + return SkOpts::hash_fn(&k, sizeof(K), 0); + } + + uint32_t operator()(const SkString& k) const { + return SkOpts::hash_fn(k.c_str(), k.size(), 0); + } +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkColorData.h b/gfx/skia/skia/include/private/SkColorData.h new file mode 100644 index 0000000000..00a28952f1 --- /dev/null +++ b/gfx/skia/skia/include/private/SkColorData.h @@ -0,0 +1,444 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorData_DEFINED +#define SkColorData_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkColorPriv.h" +#include "include/private/SkNx.h" +#include "include/private/SkTo.h" + +//////////////////////////////////////////////////////////////////////////////////////////// +// Convert a 16bit pixel to a 32bit pixel + +#define SK_R16_BITS 5 +#define SK_G16_BITS 6 +#define SK_B16_BITS 5 + +#define SK_R16_SHIFT (SK_B16_BITS + SK_G16_BITS) +#define SK_G16_SHIFT (SK_B16_BITS) +#define SK_B16_SHIFT 0 + +#define SK_R16_MASK ((1 << SK_R16_BITS) - 1) +#define SK_G16_MASK ((1 << SK_G16_BITS) - 1) +#define SK_B16_MASK ((1 << SK_B16_BITS) - 1) + +#define SkGetPackedR16(color) (((unsigned)(color) >> SK_R16_SHIFT) & SK_R16_MASK) +#define SkGetPackedG16(color) (((unsigned)(color) >> SK_G16_SHIFT) & SK_G16_MASK) +#define SkGetPackedB16(color) (((unsigned)(color) >> SK_B16_SHIFT) & SK_B16_MASK) + +static inline unsigned SkR16ToR32(unsigned r) { + return (r << (8 - SK_R16_BITS)) | (r >> (2 * SK_R16_BITS - 8)); +} + +static inline unsigned SkG16ToG32(unsigned g) { + return (g << (8 - SK_G16_BITS)) | (g >> (2 * SK_G16_BITS - 8)); +} + +static inline unsigned SkB16ToB32(unsigned b) { + return (b << (8 - SK_B16_BITS)) | (b >> (2 * SK_B16_BITS - 8)); +} + +#define SkPacked16ToR32(c) SkR16ToR32(SkGetPackedR16(c)) +#define SkPacked16ToG32(c) SkG16ToG32(SkGetPackedG16(c)) +#define SkPacked16ToB32(c) SkB16ToB32(SkGetPackedB16(c)) + +////////////////////////////////////////////////////////////////////////////// + +#define SkASSERT_IS_BYTE(x) SkASSERT(0 == ((x) & ~0xFF)) + +// Reverse the bytes coorsponding to RED and BLUE in a packed pixels. Note the +// pair of them are in the same 2 slots in both RGBA and BGRA, thus there is +// no need to pass in the colortype to this function. +static inline uint32_t SkSwizzle_RB(uint32_t c) { + static const uint32_t kRBMask = (0xFF << SK_R32_SHIFT) | (0xFF << SK_B32_SHIFT); + + unsigned c0 = (c >> SK_R32_SHIFT) & 0xFF; + unsigned c1 = (c >> SK_B32_SHIFT) & 0xFF; + return (c & ~kRBMask) | (c0 << SK_B32_SHIFT) | (c1 << SK_R32_SHIFT); +} + +static inline uint32_t SkPackARGB_as_RGBA(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + SkASSERT_IS_BYTE(a); + SkASSERT_IS_BYTE(r); + SkASSERT_IS_BYTE(g); + SkASSERT_IS_BYTE(b); + return (a << SK_RGBA_A32_SHIFT) | (r << SK_RGBA_R32_SHIFT) | + (g << SK_RGBA_G32_SHIFT) | (b << SK_RGBA_B32_SHIFT); +} + +static inline uint32_t SkPackARGB_as_BGRA(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + SkASSERT_IS_BYTE(a); + SkASSERT_IS_BYTE(r); + SkASSERT_IS_BYTE(g); + SkASSERT_IS_BYTE(b); + return (a << SK_BGRA_A32_SHIFT) | (r << SK_BGRA_R32_SHIFT) | + (g << SK_BGRA_G32_SHIFT) | (b << SK_BGRA_B32_SHIFT); +} + +static inline SkPMColor SkSwizzle_RGBA_to_PMColor(uint32_t c) { +#ifdef SK_PMCOLOR_IS_RGBA + return c; +#else + return SkSwizzle_RB(c); +#endif +} + +static inline SkPMColor SkSwizzle_BGRA_to_PMColor(uint32_t c) { +#ifdef SK_PMCOLOR_IS_BGRA + return c; +#else + return SkSwizzle_RB(c); +#endif +} + +////////////////////////////////////////////////////////////////////////////// + +///@{ +/** See ITU-R Recommendation BT.709 at http://www.itu.int/rec/R-REC-BT.709/ .*/ +#define SK_ITU_BT709_LUM_COEFF_R (0.2126f) +#define SK_ITU_BT709_LUM_COEFF_G (0.7152f) +#define SK_ITU_BT709_LUM_COEFF_B (0.0722f) +///@} + +///@{ +/** A float value which specifies this channel's contribution to luminance. */ +#define SK_LUM_COEFF_R SK_ITU_BT709_LUM_COEFF_R +#define SK_LUM_COEFF_G SK_ITU_BT709_LUM_COEFF_G +#define SK_LUM_COEFF_B SK_ITU_BT709_LUM_COEFF_B +///@} + +/** Computes the luminance from the given r, g, and b in accordance with + SK_LUM_COEFF_X. For correct results, r, g, and b should be in linear space. +*/ +static inline U8CPU SkComputeLuminance(U8CPU r, U8CPU g, U8CPU b) { + //The following is + //r * SK_LUM_COEFF_R + g * SK_LUM_COEFF_G + b * SK_LUM_COEFF_B + //with SK_LUM_COEFF_X in 1.8 fixed point (rounding adjusted to sum to 256). + return (r * 54 + g * 183 + b * 19) >> 8; +} + +/** Calculates 256 - (value * alpha256) / 255 in range [0,256], + * for [0,255] value and [0,256] alpha256. + */ +static inline U16CPU SkAlphaMulInv256(U16CPU value, U16CPU alpha256) { + unsigned prod = 0xFFFF - value * alpha256; + return (prod + (prod >> 8)) >> 8; +} + +// The caller may want negative values, so keep all params signed (int) +// so we don't accidentally slip into unsigned math and lose the sign +// extension when we shift (in SkAlphaMul) +static inline int SkAlphaBlend(int src, int dst, int scale256) { + SkASSERT((unsigned)scale256 <= 256); + return dst + SkAlphaMul(src - dst, scale256); +} + +static inline uint16_t SkPackRGB16(unsigned r, unsigned g, unsigned b) { + SkASSERT(r <= SK_R16_MASK); + SkASSERT(g <= SK_G16_MASK); + SkASSERT(b <= SK_B16_MASK); + + return SkToU16((r << SK_R16_SHIFT) | (g << SK_G16_SHIFT) | (b << SK_B16_SHIFT)); +} + +#define SK_R16_MASK_IN_PLACE (SK_R16_MASK << SK_R16_SHIFT) +#define SK_G16_MASK_IN_PLACE (SK_G16_MASK << SK_G16_SHIFT) +#define SK_B16_MASK_IN_PLACE (SK_B16_MASK << SK_B16_SHIFT) + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Abstract 4-byte interpolation, implemented on top of SkPMColor + * utility functions. Third parameter controls blending of the first two: + * (src, dst, 0) returns dst + * (src, dst, 0xFF) returns src + * srcWeight is [0..256], unlike SkFourByteInterp which takes [0..255] + */ +static inline SkPMColor SkFourByteInterp256(SkPMColor src, SkPMColor dst, + unsigned scale) { + unsigned a = SkAlphaBlend(SkGetPackedA32(src), SkGetPackedA32(dst), scale); + unsigned r = SkAlphaBlend(SkGetPackedR32(src), SkGetPackedR32(dst), scale); + unsigned g = SkAlphaBlend(SkGetPackedG32(src), SkGetPackedG32(dst), scale); + unsigned b = SkAlphaBlend(SkGetPackedB32(src), SkGetPackedB32(dst), scale); + + return SkPackARGB32(a, r, g, b); +} + +/** + * Abstract 4-byte interpolation, implemented on top of SkPMColor + * utility functions. Third parameter controls blending of the first two: + * (src, dst, 0) returns dst + * (src, dst, 0xFF) returns src + */ +static inline SkPMColor SkFourByteInterp(SkPMColor src, SkPMColor dst, + U8CPU srcWeight) { + unsigned scale = SkAlpha255To256(srcWeight); + return SkFourByteInterp256(src, dst, scale); +} + +/** + * 0xAARRGGBB -> 0x00AA00GG, 0x00RR00BB + */ +static inline void SkSplay(uint32_t color, uint32_t* ag, uint32_t* rb) { + const uint32_t mask = 0x00FF00FF; + *ag = (color >> 8) & mask; + *rb = color & mask; +} + +/** + * 0xAARRGGBB -> 0x00AA00GG00RR00BB + * (note, ARGB -> AGRB) + */ +static inline uint64_t SkSplay(uint32_t color) { + const uint32_t mask = 0x00FF00FF; + uint64_t agrb = (color >> 8) & mask; // 0x0000000000AA00GG + agrb <<= 32; // 0x00AA00GG00000000 + agrb |= color & mask; // 0x00AA00GG00RR00BB + return agrb; +} + +/** + * 0xAAxxGGxx, 0xRRxxBBxx-> 0xAARRGGBB + */ +static inline uint32_t SkUnsplay(uint32_t ag, uint32_t rb) { + const uint32_t mask = 0xFF00FF00; + return (ag & mask) | ((rb & mask) >> 8); +} + +/** + * 0xAAxxGGxxRRxxBBxx -> 0xAARRGGBB + * (note, AGRB -> ARGB) + */ +static inline uint32_t SkUnsplay(uint64_t agrb) { + const uint32_t mask = 0xFF00FF00; + return SkPMColor( + ((agrb & mask) >> 8) | // 0x00RR00BB + ((agrb >> 32) & mask)); // 0xAARRGGBB +} + +static inline SkPMColor SkFastFourByteInterp256_32(SkPMColor src, SkPMColor dst, unsigned scale) { + SkASSERT(scale <= 256); + + // Two 8-bit blends per two 32-bit registers, with space to make sure the math doesn't collide. + uint32_t src_ag, src_rb, dst_ag, dst_rb; + SkSplay(src, &src_ag, &src_rb); + SkSplay(dst, &dst_ag, &dst_rb); + + const uint32_t ret_ag = src_ag * scale + (256 - scale) * dst_ag; + const uint32_t ret_rb = src_rb * scale + (256 - scale) * dst_rb; + + return SkUnsplay(ret_ag, ret_rb); +} + +static inline SkPMColor SkFastFourByteInterp256_64(SkPMColor src, SkPMColor dst, unsigned scale) { + SkASSERT(scale <= 256); + // Four 8-bit blends in one 64-bit register, with space to make sure the math doesn't collide. + return SkUnsplay(SkSplay(src) * scale + (256-scale) * SkSplay(dst)); +} + +// TODO(mtklein): Replace slow versions with fast versions, using scale + (scale>>7) everywhere. + +/** + * Same as SkFourByteInterp256, but faster. + */ +static inline SkPMColor SkFastFourByteInterp256(SkPMColor src, SkPMColor dst, unsigned scale) { + // On a 64-bit machine, _64 is about 10% faster than _32, but ~40% slower on a 32-bit machine. + if (sizeof(void*) == 4) { + return SkFastFourByteInterp256_32(src, dst, scale); + } else { + return SkFastFourByteInterp256_64(src, dst, scale); + } +} + +/** + * Nearly the same as SkFourByteInterp, but faster and a touch more accurate, due to better + * srcWeight scaling to [0, 256]. + */ +static inline SkPMColor SkFastFourByteInterp(SkPMColor src, + SkPMColor dst, + U8CPU srcWeight) { + SkASSERT(srcWeight <= 255); + // scale = srcWeight + (srcWeight >> 7) is more accurate than + // scale = srcWeight + 1, but 7% slower + return SkFastFourByteInterp256(src, dst, srcWeight + (srcWeight >> 7)); +} + +/** + * Interpolates between colors src and dst using [0,256] scale. + */ +static inline SkPMColor SkPMLerp(SkPMColor src, SkPMColor dst, unsigned scale) { + return SkFastFourByteInterp256(src, dst, scale); +} + +static inline SkPMColor SkBlendARGB32(SkPMColor src, SkPMColor dst, U8CPU aa) { + SkASSERT((unsigned)aa <= 255); + + unsigned src_scale = SkAlpha255To256(aa); + unsigned dst_scale = SkAlphaMulInv256(SkGetPackedA32(src), src_scale); + + const uint32_t mask = 0xFF00FF; + + uint32_t src_rb = (src & mask) * src_scale; + uint32_t src_ag = ((src >> 8) & mask) * src_scale; + + uint32_t dst_rb = (dst & mask) * dst_scale; + uint32_t dst_ag = ((dst >> 8) & mask) * dst_scale; + + return (((src_rb + dst_rb) >> 8) & mask) | ((src_ag + dst_ag) & ~mask); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Convert a 32bit pixel to a 16bit pixel (no dither) + +#define SkR32ToR16_MACRO(r) ((unsigned)(r) >> (SK_R32_BITS - SK_R16_BITS)) +#define SkG32ToG16_MACRO(g) ((unsigned)(g) >> (SK_G32_BITS - SK_G16_BITS)) +#define SkB32ToB16_MACRO(b) ((unsigned)(b) >> (SK_B32_BITS - SK_B16_BITS)) + +#ifdef SK_DEBUG + static inline unsigned SkR32ToR16(unsigned r) { + SkR32Assert(r); + return SkR32ToR16_MACRO(r); + } + static inline unsigned SkG32ToG16(unsigned g) { + SkG32Assert(g); + return SkG32ToG16_MACRO(g); + } + static inline unsigned SkB32ToB16(unsigned b) { + SkB32Assert(b); + return SkB32ToB16_MACRO(b); + } +#else + #define SkR32ToR16(r) SkR32ToR16_MACRO(r) + #define SkG32ToG16(g) SkG32ToG16_MACRO(g) + #define SkB32ToB16(b) SkB32ToB16_MACRO(b) +#endif + +static inline U16CPU SkPixel32ToPixel16(SkPMColor c) { + unsigned r = ((c >> (SK_R32_SHIFT + (8 - SK_R16_BITS))) & SK_R16_MASK) << SK_R16_SHIFT; + unsigned g = ((c >> (SK_G32_SHIFT + (8 - SK_G16_BITS))) & SK_G16_MASK) << SK_G16_SHIFT; + unsigned b = ((c >> (SK_B32_SHIFT + (8 - SK_B16_BITS))) & SK_B16_MASK) << SK_B16_SHIFT; + return r | g | b; +} + +static inline U16CPU SkPack888ToRGB16(U8CPU r, U8CPU g, U8CPU b) { + return (SkR32ToR16(r) << SK_R16_SHIFT) | + (SkG32ToG16(g) << SK_G16_SHIFT) | + (SkB32ToB16(b) << SK_B16_SHIFT); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/* SrcOver the 32bit src color with the 16bit dst, returning a 16bit value + (with dirt in the high 16bits, so caller beware). +*/ +static inline U16CPU SkSrcOver32To16(SkPMColor src, uint16_t dst) { + unsigned sr = SkGetPackedR32(src); + unsigned sg = SkGetPackedG32(src); + unsigned sb = SkGetPackedB32(src); + + unsigned dr = SkGetPackedR16(dst); + unsigned dg = SkGetPackedG16(dst); + unsigned db = SkGetPackedB16(dst); + + unsigned isa = 255 - SkGetPackedA32(src); + + dr = (sr + SkMul16ShiftRound(dr, isa, SK_R16_BITS)) >> (8 - SK_R16_BITS); + dg = (sg + SkMul16ShiftRound(dg, isa, SK_G16_BITS)) >> (8 - SK_G16_BITS); + db = (sb + SkMul16ShiftRound(db, isa, SK_B16_BITS)) >> (8 - SK_B16_BITS); + + return SkPackRGB16(dr, dg, db); +} + +static inline SkColor SkPixel16ToColor(U16CPU src) { + SkASSERT(src == SkToU16(src)); + + unsigned r = SkPacked16ToR32(src); + unsigned g = SkPacked16ToG32(src); + unsigned b = SkPacked16ToB32(src); + + SkASSERT((r >> (8 - SK_R16_BITS)) == SkGetPackedR16(src)); + SkASSERT((g >> (8 - SK_G16_BITS)) == SkGetPackedG16(src)); + SkASSERT((b >> (8 - SK_B16_BITS)) == SkGetPackedB16(src)); + + return SkColorSetRGB(r, g, b); +} + +/////////////////////////////////////////////////////////////////////////////// + +typedef uint16_t SkPMColor16; + +// Put in OpenGL order (r g b a) +#define SK_A4444_SHIFT 0 +#define SK_R4444_SHIFT 12 +#define SK_G4444_SHIFT 8 +#define SK_B4444_SHIFT 4 + +static inline U8CPU SkReplicateNibble(unsigned nib) { + SkASSERT(nib <= 0xF); + return (nib << 4) | nib; +} + +#define SkGetPackedA4444(c) (((unsigned)(c) >> SK_A4444_SHIFT) & 0xF) +#define SkGetPackedR4444(c) (((unsigned)(c) >> SK_R4444_SHIFT) & 0xF) +#define SkGetPackedG4444(c) (((unsigned)(c) >> SK_G4444_SHIFT) & 0xF) +#define SkGetPackedB4444(c) (((unsigned)(c) >> SK_B4444_SHIFT) & 0xF) + +#define SkPacked4444ToA32(c) SkReplicateNibble(SkGetPackedA4444(c)) + +static inline SkPMColor SkPixel4444ToPixel32(U16CPU c) { + uint32_t d = (SkGetPackedA4444(c) << SK_A32_SHIFT) | + (SkGetPackedR4444(c) << SK_R32_SHIFT) | + (SkGetPackedG4444(c) << SK_G32_SHIFT) | + (SkGetPackedB4444(c) << SK_B32_SHIFT); + return d | (d << 4); +} + +static inline Sk4f swizzle_rb(const Sk4f& x) { + return SkNx_shuffle<2, 1, 0, 3>(x); +} + +static inline Sk4f swizzle_rb_if_bgra(const Sk4f& x) { +#ifdef SK_PMCOLOR_IS_BGRA + return swizzle_rb(x); +#else + return x; +#endif +} + +static inline Sk4f Sk4f_fromL32(uint32_t px) { + return SkNx_cast(Sk4b::Load(&px)) * (1 / 255.0f); +} + +static inline uint32_t Sk4f_toL32(const Sk4f& px) { + Sk4f v = px; + +#if !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + // SkNx_cast() pins, and we don't anticipate giant floats +#elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON) + // SkNx_cast() pins, and so does Sk4f_round(). +#else + // No guarantee of a pin. + v = Sk4f::Max(0, Sk4f::Min(v, 1)); +#endif + + uint32_t l32; + SkNx_cast(Sk4f_round(v * 255.0f)).store(&l32); + return l32; +} + +using SkPMColor4f = SkRGBA4f; + +constexpr SkPMColor4f SK_PMColor4fTRANSPARENT = { 0, 0, 0, 0 }; +constexpr SkPMColor4f SK_PMColor4fWHITE = { 1, 1, 1, 1 }; +constexpr SkPMColor4f SK_PMColor4fILLEGAL = { SK_FloatNegativeInfinity, + SK_FloatNegativeInfinity, + SK_FloatNegativeInfinity, + SK_FloatNegativeInfinity }; + +#endif diff --git a/gfx/skia/skia/include/private/SkDeferredDisplayList.h b/gfx/skia/skia/include/private/SkDeferredDisplayList.h new file mode 100644 index 0000000000..e8fc35b611 --- /dev/null +++ b/gfx/skia/skia/include/private/SkDeferredDisplayList.h @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDeferredDisplayList_DEFINED +#define SkDeferredDisplayList_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceCharacterization.h" +#include "include/core/SkTypes.h" + +class SkDeferredDisplayListPriv; + +#if SK_SUPPORT_GPU +#include "include/private/SkTArray.h" +#include +class GrRenderTask; +class GrRenderTargetProxy; +struct GrCCPerOpsTaskPaths; +#endif + +/* + * This class contains pre-processed gpu operations that can be replayed into + * an SkSurface via draw(SkDeferredDisplayList*). + * + * TODO: we probably need to expose this class so users can query it for memory usage. + */ +class SkDeferredDisplayList { +public: + +#if SK_SUPPORT_GPU + // This object is the source from which the lazy proxy backing the DDL will pull its backing + // texture when the DDL is replayed. It has to be separately ref counted bc the lazy proxy + // can outlive the DDL. + class SK_API LazyProxyData : public SkRefCnt { + public: + // Upon being replayed - this field will be filled in (by the DrawingManager) with the proxy + // backing the destination SkSurface. Note that, since there is no good place to clear it + // it can become a dangling pointer. + GrRenderTargetProxy* fReplayDest = nullptr; + }; +#else + class SK_API LazyProxyData : public SkRefCnt {}; +#endif + + SK_API SkDeferredDisplayList(const SkSurfaceCharacterization& characterization, + sk_sp); + SK_API ~SkDeferredDisplayList(); + + SK_API const SkSurfaceCharacterization& characterization() const { + return fCharacterization; + } + + // Provides access to functions that aren't part of the public API. + SkDeferredDisplayListPriv priv(); + const SkDeferredDisplayListPriv priv() const; + +private: + friend class GrDrawingManager; // for access to 'fRenderTasks' and 'fLazyProxyData' + friend class SkDeferredDisplayListRecorder; // for access to 'fLazyProxyData' + friend class SkDeferredDisplayListPriv; + + const SkSurfaceCharacterization fCharacterization; + +#if SK_SUPPORT_GPU + // This needs to match the same type in GrCoverageCountingPathRenderer.h + using PendingPathsMap = std::map>; + + SkTArray> fRenderTasks; + PendingPathsMap fPendingPaths; // This is the path data from CCPR. +#endif + sk_sp fLazyProxyData; +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkEncodedInfo.h b/gfx/skia/skia/include/private/SkEncodedInfo.h new file mode 100644 index 0000000000..887198c1e1 --- /dev/null +++ b/gfx/skia/skia/include/private/SkEncodedInfo.h @@ -0,0 +1,247 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEncodedInfo_DEFINED +#define SkEncodedInfo_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkImageInfo.h" +#include "include/third_party/skcms/skcms.h" + +struct SkEncodedInfo { +public: + class ICCProfile { + public: + static std::unique_ptr Make(sk_sp); + static std::unique_ptr Make(const skcms_ICCProfile&); + + const skcms_ICCProfile* profile() const { return &fProfile; } + private: + ICCProfile(const skcms_ICCProfile&, sk_sp = nullptr); + + skcms_ICCProfile fProfile; + sk_sp fData; + }; + + enum Alpha { + kOpaque_Alpha, + kUnpremul_Alpha, + + // Each pixel is either fully opaque or fully transparent. + // There is no difference between requesting kPremul or kUnpremul. + kBinary_Alpha, + }; + + /* + * We strive to make the number of components per pixel obvious through + * our naming conventions. + * Ex: kRGB has 3 components. kRGBA has 4 components. + * + * This sometimes results in redundant Alpha and Color information. + * Ex: kRGB images must also be kOpaque. + */ + enum Color { + // PNG, WBMP + kGray_Color, + + // PNG + kGrayAlpha_Color, + + // PNG with Skia-specific sBIT + // Like kGrayAlpha, except this expects to be treated as + // kAlpha_8_SkColorType, which ignores the gray component. If + // decoded to full color (e.g. kN32), the gray component is respected + // (so it can share code with kGrayAlpha). + kXAlpha_Color, + + // PNG + // 565 images may be encoded to PNG by specifying the number of + // significant bits for each channel. This is a strange 565 + // representation because the image is still encoded with 8 bits per + // component. + k565_Color, + + // PNG, GIF, BMP + kPalette_Color, + + // PNG, RAW + kRGB_Color, + kRGBA_Color, + + // BMP + kBGR_Color, + kBGRX_Color, + kBGRA_Color, + + // JPEG, WEBP + kYUV_Color, + + // WEBP + kYUVA_Color, + + // JPEG + // Photoshop actually writes inverted CMYK data into JPEGs, where zero + // represents 100% ink coverage. For this reason, we treat CMYK JPEGs + // as having inverted CMYK. libjpeg-turbo warns that this may break + // other applications, but the CMYK JPEGs we see on the web expect to + // be treated as inverted CMYK. + kInvertedCMYK_Color, + kYCCK_Color, + }; + + static SkEncodedInfo Make(int width, int height, Color color, Alpha alpha, + int bitsPerComponent) { + return Make(width, height, color, alpha, bitsPerComponent, nullptr); + } + + static SkEncodedInfo Make(int width, int height, Color color, Alpha alpha, + int bitsPerComponent, std::unique_ptr profile) { + SkASSERT(1 == bitsPerComponent || + 2 == bitsPerComponent || + 4 == bitsPerComponent || + 8 == bitsPerComponent || + 16 == bitsPerComponent); + + switch (color) { + case kGray_Color: + SkASSERT(kOpaque_Alpha == alpha); + break; + case kGrayAlpha_Color: + SkASSERT(kOpaque_Alpha != alpha); + break; + case kPalette_Color: + SkASSERT(16 != bitsPerComponent); + break; + case kRGB_Color: + case kBGR_Color: + case kBGRX_Color: + SkASSERT(kOpaque_Alpha == alpha); + SkASSERT(bitsPerComponent >= 8); + break; + case kYUV_Color: + case kInvertedCMYK_Color: + case kYCCK_Color: + SkASSERT(kOpaque_Alpha == alpha); + SkASSERT(8 == bitsPerComponent); + break; + case kRGBA_Color: + SkASSERT(bitsPerComponent >= 8); + break; + case kBGRA_Color: + case kYUVA_Color: + SkASSERT(8 == bitsPerComponent); + break; + case kXAlpha_Color: + SkASSERT(kUnpremul_Alpha == alpha); + SkASSERT(8 == bitsPerComponent); + break; + case k565_Color: + SkASSERT(kOpaque_Alpha == alpha); + SkASSERT(8 == bitsPerComponent); + break; + default: + SkASSERT(false); + break; + } + + return SkEncodedInfo(width, height, color, alpha, bitsPerComponent, std::move(profile)); + } + + /* + * Returns a recommended SkImageInfo. + * + * TODO: Leave this up to the client. + */ + SkImageInfo makeImageInfo() const { + auto ct = kGray_Color == fColor ? kGray_8_SkColorType : + kXAlpha_Color == fColor ? kAlpha_8_SkColorType : + k565_Color == fColor ? kRGB_565_SkColorType : + kN32_SkColorType ; + auto alpha = kOpaque_Alpha == fAlpha ? kOpaque_SkAlphaType + : kUnpremul_SkAlphaType; + sk_sp cs = fProfile ? SkColorSpace::Make(*fProfile->profile()) + : nullptr; + if (!cs) { + cs = SkColorSpace::MakeSRGB(); + } + return SkImageInfo::Make(fWidth, fHeight, ct, alpha, std::move(cs)); + } + + int width() const { return fWidth; } + int height() const { return fHeight; } + Color color() const { return fColor; } + Alpha alpha() const { return fAlpha; } + bool opaque() const { return fAlpha == kOpaque_Alpha; } + const skcms_ICCProfile* profile() const { + if (!fProfile) return nullptr; + return fProfile->profile(); + } + + uint8_t bitsPerComponent() const { return fBitsPerComponent; } + + uint8_t bitsPerPixel() const { + switch (fColor) { + case kGray_Color: + return fBitsPerComponent; + case kXAlpha_Color: + case kGrayAlpha_Color: + return 2 * fBitsPerComponent; + case kPalette_Color: + return fBitsPerComponent; + case kRGB_Color: + case kBGR_Color: + case kYUV_Color: + case k565_Color: + return 3 * fBitsPerComponent; + case kRGBA_Color: + case kBGRA_Color: + case kBGRX_Color: + case kYUVA_Color: + case kInvertedCMYK_Color: + case kYCCK_Color: + return 4 * fBitsPerComponent; + default: + SkASSERT(false); + return 0; + } + } + + SkEncodedInfo(const SkEncodedInfo& orig) = delete; + SkEncodedInfo& operator=(const SkEncodedInfo&) = delete; + + SkEncodedInfo(SkEncodedInfo&& orig) = default; + SkEncodedInfo& operator=(SkEncodedInfo&&) = default; + + // Explicit copy method, to avoid accidental copying. + SkEncodedInfo copy() const { + auto copy = SkEncodedInfo::Make(fWidth, fHeight, fColor, fAlpha, fBitsPerComponent); + if (fProfile) { + copy.fProfile.reset(new ICCProfile(*fProfile.get())); + } + return copy; + } + +private: + SkEncodedInfo(int width, int height, Color color, Alpha alpha, + uint8_t bitsPerComponent, std::unique_ptr profile) + : fWidth(width) + , fHeight(height) + , fColor(color) + , fAlpha(alpha) + , fBitsPerComponent(bitsPerComponent) + , fProfile(std::move(profile)) + {} + + int fWidth; + int fHeight; + Color fColor; + Alpha fAlpha; + uint8_t fBitsPerComponent; + std::unique_ptr fProfile; +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkFixed.h b/gfx/skia/skia/include/private/SkFixed.h new file mode 100644 index 0000000000..af858efc89 --- /dev/null +++ b/gfx/skia/skia/include/private/SkFixed.h @@ -0,0 +1,140 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFixed_DEFINED +#define SkFixed_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSafe_math.h" +#include "include/private/SkTo.h" + +/** \file SkFixed.h + + Types and macros for 16.16 fixed point +*/ + +/** 32 bit signed integer used to represent fractions values with 16 bits to the right of the decimal point +*/ +typedef int32_t SkFixed; +#define SK_Fixed1 (1 << 16) +#define SK_FixedHalf (1 << 15) +#define SK_FixedQuarter (1 << 14) +#define SK_FixedMax (0x7FFFFFFF) +#define SK_FixedMin (-SK_FixedMax) +#define SK_FixedPI (0x3243F) +#define SK_FixedSqrt2 (92682) +#define SK_FixedTanPIOver8 (0x6A0A) +#define SK_FixedRoot2Over2 (0xB505) + +// NOTE: SkFixedToFloat is exact. SkFloatToFixed seems to lack a rounding step. For all fixed-point +// values, this version is as accurate as possible for (fixed -> float -> fixed). Rounding reduces +// accuracy if the intermediate floats are in the range that only holds integers (adding 0.5f to an +// odd integer then snaps to nearest even). Using double for the rounding math gives maximum +// accuracy for (float -> fixed -> float), but that's usually overkill. +#define SkFixedToFloat(x) ((x) * 1.52587890625e-5f) +#define SkFloatToFixed(x) sk_float_saturate2int((x) * SK_Fixed1) + +#ifdef SK_DEBUG + static inline SkFixed SkFloatToFixed_Check(float x) { + int64_t n64 = (int64_t)(x * SK_Fixed1); + SkFixed n32 = (SkFixed)n64; + SkASSERT(n64 == n32); + return n32; + } +#else + #define SkFloatToFixed_Check(x) SkFloatToFixed(x) +#endif + +#define SkFixedToDouble(x) ((x) * 1.52587890625e-5) +#define SkDoubleToFixed(x) ((SkFixed)((x) * SK_Fixed1)) + +/** Converts an integer to a SkFixed, asserting that the result does not overflow + a 32 bit signed integer +*/ +#ifdef SK_DEBUG + inline SkFixed SkIntToFixed(int n) + { + SkASSERT(n >= -32768 && n <= 32767); + // Left shifting a negative value has undefined behavior in C, so we cast to unsigned before + // shifting. + return (unsigned)n << 16; + } +#else + // Left shifting a negative value has undefined behavior in C, so we cast to unsigned before + // shifting. Then we force the cast to SkFixed to ensure that the answer is signed (like the + // debug version). + #define SkIntToFixed(n) (SkFixed)((unsigned)(n) << 16) +#endif + +#define SkFixedRoundToInt(x) (((x) + SK_FixedHalf) >> 16) +#define SkFixedCeilToInt(x) (((x) + SK_Fixed1 - 1) >> 16) +#define SkFixedFloorToInt(x) ((x) >> 16) + +static inline SkFixed SkFixedRoundToFixed(SkFixed x) { + return (x + SK_FixedHalf) & 0xFFFF0000; +} +static inline SkFixed SkFixedCeilToFixed(SkFixed x) { + return (x + SK_Fixed1 - 1) & 0xFFFF0000; +} +static inline SkFixed SkFixedFloorToFixed(SkFixed x) { + return x & 0xFFFF0000; +} + +#define SkFixedAbs(x) SkAbs32(x) +#define SkFixedAve(a, b) (((a) + (b)) >> 1) + +// The divide may exceed 32 bits. Clamp to a signed 32 bit result. +#define SkFixedDiv(numer, denom) \ + SkToS32(SkTPin((SkLeftShift((int64_t)(numer), 16) / (denom)), SK_MinS32, SK_MaxS32)) + +static inline SkFixed SkFixedMul(SkFixed a, SkFixed b) { + return (SkFixed)((int64_t)a * b >> 16); +} + +/////////////////////////////////////////////////////////////////////////////// +// Platform-specific alternatives to our portable versions. + +// The VCVT float-to-fixed instruction is part of the VFPv3 instruction set. +#if defined(__ARM_VFPV3__) + /* This guy does not handle NaN or other obscurities, but is faster than + than (int)(x*65536). When built on Android with -Os, needs forcing + to inline or we lose the speed benefit. + */ + SK_ALWAYS_INLINE SkFixed SkFloatToFixed_arm(float x) + { + int32_t y; + asm("vcvt.s32.f32 %0, %0, #16": "+w"(x)); + memcpy(&y, &x, sizeof(y)); + return y; + } + #undef SkFloatToFixed + #define SkFloatToFixed(x) SkFloatToFixed_arm(x) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +#define SkFixedToScalar(x) SkFixedToFloat(x) +#define SkScalarToFixed(x) SkFloatToFixed(x) + +/////////////////////////////////////////////////////////////////////////////// + +typedef int64_t SkFixed3232; // 32.32 + +#define SkFixed3232Max SK_MaxS64 +#define SkFixed3232Min (-SkFixed3232Max) + +#define SkIntToFixed3232(x) (SkLeftShift((SkFixed3232)(x), 32)) +#define SkFixed3232ToInt(x) ((int)((x) >> 32)) +#define SkFixedToFixed3232(x) (SkLeftShift((SkFixed3232)(x), 16)) +#define SkFixed3232ToFixed(x) ((SkFixed)((x) >> 16)) +#define SkFloatToFixed3232(x) sk_float_saturate2int64((x) * (65536.0f * 65536.0f)) +#define SkFixed3232ToFloat(x) (x * (1 / (65536.0f * 65536.0f))) + +#define SkScalarToFixed3232(x) SkFloatToFixed3232(x) + +#endif diff --git a/gfx/skia/skia/include/private/SkFloatBits.h b/gfx/skia/skia/include/private/SkFloatBits.h new file mode 100644 index 0000000000..89eea4b9e3 --- /dev/null +++ b/gfx/skia/skia/include/private/SkFloatBits.h @@ -0,0 +1,91 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFloatBits_DEFINED +#define SkFloatBits_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkSafe_math.h" + +#include + +/** Convert a sign-bit int (i.e. float interpreted as int) into a 2s compliement + int. This also converts -0 (0x80000000) to 0. Doing this to a float allows + it to be compared using normal C operators (<, <=, etc.) +*/ +static inline int32_t SkSignBitTo2sCompliment(int32_t x) { + if (x < 0) { + x &= 0x7FFFFFFF; + x = -x; + } + return x; +} + +/** Convert a 2s compliment int to a sign-bit (i.e. int interpreted as float). + This undoes the result of SkSignBitTo2sCompliment(). + */ +static inline int32_t Sk2sComplimentToSignBit(int32_t x) { + int sign = x >> 31; + // make x positive + x = (x ^ sign) - sign; + // set the sign bit as needed + x |= SkLeftShift(sign, 31); + return x; +} + +union SkFloatIntUnion { + float fFloat; + int32_t fSignBitInt; +}; + +// Helper to see a float as its bit pattern (w/o aliasing warnings) +static inline int32_t SkFloat2Bits(float x) { + SkFloatIntUnion data; + data.fFloat = x; + return data.fSignBitInt; +} + +// Helper to see a bit pattern as a float (w/o aliasing warnings) +static inline float SkBits2Float(int32_t floatAsBits) { + SkFloatIntUnion data; + data.fSignBitInt = floatAsBits; + return data.fFloat; +} + +constexpr int32_t gFloatBits_exponent_mask = 0x7F800000; +constexpr int32_t gFloatBits_matissa_mask = 0x007FFFFF; + +static inline bool SkFloatBits_IsFinite(int32_t bits) { + return (bits & gFloatBits_exponent_mask) != gFloatBits_exponent_mask; +} + +static inline bool SkFloatBits_IsInf(int32_t bits) { + return ((bits & gFloatBits_exponent_mask) == gFloatBits_exponent_mask) && + (bits & gFloatBits_matissa_mask) == 0; +} + +/** Return the float as a 2s compliment int. Just to be used to compare floats + to each other or against positive float-bit-constants (like 0). This does + not return the int equivalent of the float, just something cheaper for + compares-only. + */ +static inline int32_t SkFloatAs2sCompliment(float x) { + return SkSignBitTo2sCompliment(SkFloat2Bits(x)); +} + +/** Return the 2s compliment int as a float. This undos the result of + SkFloatAs2sCompliment + */ +static inline float Sk2sComplimentAsFloat(int32_t x) { + return SkBits2Float(Sk2sComplimentToSignBit(x)); +} + +// Scalar wrappers for float-bit routines + +#define SkScalarAs2sCompliment(x) SkFloatAs2sCompliment(x) + +#endif diff --git a/gfx/skia/skia/include/private/SkFloatingPoint.h b/gfx/skia/skia/include/private/SkFloatingPoint.h new file mode 100644 index 0000000000..7b34ac147e --- /dev/null +++ b/gfx/skia/skia/include/private/SkFloatingPoint.h @@ -0,0 +1,265 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFloatingPoint_DEFINED +#define SkFloatingPoint_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkFloatBits.h" +#include "include/private/SkSafe_math.h" +#include +#include +#include +#include +#include + + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 + #include +#elif defined(SK_ARM_HAS_NEON) + #include +#endif + +// For _POSIX_VERSION +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#include +#endif + +constexpr float SK_FloatSqrt2 = 1.41421356f; +constexpr float SK_FloatPI = 3.14159265f; +constexpr double SK_DoublePI = 3.14159265358979323846264338327950288; + +// C++98 cmath std::pow seems to be the earliest portable way to get float pow. +// However, on Linux including cmath undefines isfinite. +// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=14608 +static inline float sk_float_pow(float base, float exp) { + return powf(base, exp); +} + +#define sk_float_sqrt(x) sqrtf(x) +#define sk_float_sin(x) sinf(x) +#define sk_float_cos(x) cosf(x) +#define sk_float_tan(x) tanf(x) +#define sk_float_floor(x) floorf(x) +#define sk_float_ceil(x) ceilf(x) +#define sk_float_trunc(x) truncf(x) +#ifdef SK_BUILD_FOR_MAC +# define sk_float_acos(x) static_cast(acos(x)) +# define sk_float_asin(x) static_cast(asin(x)) +#else +# define sk_float_acos(x) acosf(x) +# define sk_float_asin(x) asinf(x) +#endif +#define sk_float_atan2(y,x) atan2f(y,x) +#define sk_float_abs(x) fabsf(x) +#define sk_float_copysign(x, y) copysignf(x, y) +#define sk_float_mod(x,y) fmodf(x,y) +#define sk_float_exp(x) expf(x) +#define sk_float_log(x) logf(x) + +constexpr float sk_float_degrees_to_radians(float degrees) { + return degrees * (SK_FloatPI / 180); +} + +constexpr float sk_float_radians_to_degrees(float radians) { + return radians * (180 / SK_FloatPI); +} + +#define sk_float_round(x) sk_float_floor((x) + 0.5f) + +// can't find log2f on android, but maybe that just a tool bug? +#ifdef SK_BUILD_FOR_ANDROID + static inline float sk_float_log2(float x) { + const double inv_ln_2 = 1.44269504088896; + return (float)(log(x) * inv_ln_2); + } +#else + #define sk_float_log2(x) log2f(x) +#endif + +static inline bool sk_float_isfinite(float x) { + return SkFloatBits_IsFinite(SkFloat2Bits(x)); +} + +static inline bool sk_floats_are_finite(float a, float b) { + return sk_float_isfinite(a) && sk_float_isfinite(b); +} + +static inline bool sk_floats_are_finite(const float array[], int count) { + float prod = 0; + for (int i = 0; i < count; ++i) { + prod *= array[i]; + } + // At this point, prod will either be NaN or 0 + return prod == 0; // if prod is NaN, this check will return false +} + +static inline bool sk_float_isinf(float x) { + return SkFloatBits_IsInf(SkFloat2Bits(x)); +} + +#ifdef SK_BUILD_FOR_WIN + #define sk_float_isnan(x) _isnan(x) +#elif defined(__clang__) || defined(__GNUC__) + #define sk_float_isnan(x) __builtin_isnan(x) +#else + #define sk_float_isnan(x) isnan(x) +#endif + +#define sk_double_isnan(a) sk_float_isnan(a) + +#define SK_MaxS32FitsInFloat 2147483520 +#define SK_MinS32FitsInFloat -SK_MaxS32FitsInFloat + +#define SK_MaxS64FitsInFloat (SK_MaxS64 >> (63-24) << (63-24)) // 0x7fffff8000000000 +#define SK_MinS64FitsInFloat -SK_MaxS64FitsInFloat + +/** + * Return the closest int for the given float. Returns SK_MaxS32FitsInFloat for NaN. + */ +static inline int sk_float_saturate2int(float x) { + x = SkTMin(x, SK_MaxS32FitsInFloat); + x = SkTMax(x, SK_MinS32FitsInFloat); + return (int)x; +} + +/** + * Return the closest int for the given double. Returns SK_MaxS32 for NaN. + */ +static inline int sk_double_saturate2int(double x) { + x = SkTMin(x, SK_MaxS32); + x = SkTMax(x, SK_MinS32); + return (int)x; +} + +/** + * Return the closest int64_t for the given float. Returns SK_MaxS64FitsInFloat for NaN. + */ +static inline int64_t sk_float_saturate2int64(float x) { + x = SkTMin(x, SK_MaxS64FitsInFloat); + x = SkTMax(x, SK_MinS64FitsInFloat); + return (int64_t)x; +} + +#define sk_float_floor2int(x) sk_float_saturate2int(sk_float_floor(x)) +#define sk_float_round2int(x) sk_float_saturate2int(sk_float_floor((x) + 0.5f)) +#define sk_float_ceil2int(x) sk_float_saturate2int(sk_float_ceil(x)) + +#define sk_float_floor2int_no_saturate(x) (int)sk_float_floor(x) +#define sk_float_round2int_no_saturate(x) (int)sk_float_floor((x) + 0.5f) +#define sk_float_ceil2int_no_saturate(x) (int)sk_float_ceil(x) + +#define sk_double_floor(x) floor(x) +#define sk_double_round(x) floor((x) + 0.5) +#define sk_double_ceil(x) ceil(x) +#define sk_double_floor2int(x) (int)floor(x) +#define sk_double_round2int(x) (int)floor((x) + 0.5) +#define sk_double_ceil2int(x) (int)ceil(x) + +// Cast double to float, ignoring any warning about too-large finite values being cast to float. +// Clang thinks this is undefined, but it's actually implementation defined to return either +// the largest float or infinity (one of the two bracketing representable floats). Good enough! +#if defined(__clang__) && (__clang_major__ * 1000 + __clang_minor__) >= 3007 +__attribute__((no_sanitize("float-cast-overflow"))) +#endif +static inline float sk_double_to_float(double x) { + return static_cast(x); +} + +#define SK_FloatNaN std::numeric_limits::quiet_NaN() +#define SK_FloatInfinity (+std::numeric_limits::infinity()) +#define SK_FloatNegativeInfinity (-std::numeric_limits::infinity()) + +#define SK_DoubleNaN std::numeric_limits::quiet_NaN() + +// Returns false if any of the floats are outside of [0...1] +// Returns true if count is 0 +bool sk_floats_are_unit(const float array[], size_t count); + +static inline float sk_float_rsqrt_portable(float x) { + // Get initial estimate. + int i; + memcpy(&i, &x, 4); + i = 0x5F1FFFF9 - (i>>1); + float estimate; + memcpy(&estimate, &i, 4); + + // One step of Newton's method to refine. + const float estimate_sq = estimate*estimate; + estimate *= 0.703952253f*(2.38924456f-x*estimate_sq); + return estimate; +} + +// Fast, approximate inverse square root. +// Compare to name-brand "1.0f / sk_float_sqrt(x)". Should be around 10x faster on SSE, 2x on NEON. +static inline float sk_float_rsqrt(float x) { +// We want all this inlined, so we'll inline SIMD and just take the hit when we don't know we've got +// it at compile time. This is going to be too fast to productively hide behind a function pointer. +// +// We do one step of Newton's method to refine the estimates in the NEON and portable paths. No +// refinement is faster, but very innacurate. Two steps is more accurate, but slower than 1/sqrt. +// +// Optimized constants in the portable path courtesy of http://rrrola.wz.cz/inv_sqrt.html +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 + return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); +#elif defined(SK_ARM_HAS_NEON) + // Get initial estimate. + const float32x2_t xx = vdup_n_f32(x); // Clever readers will note we're doing everything 2x. + float32x2_t estimate = vrsqrte_f32(xx); + + // One step of Newton's method to refine. + const float32x2_t estimate_sq = vmul_f32(estimate, estimate); + estimate = vmul_f32(estimate, vrsqrts_f32(xx, estimate_sq)); + return vget_lane_f32(estimate, 0); // 1 will work fine too; the answer's in both places. +#else + return sk_float_rsqrt_portable(x); +#endif +} + +// This is the number of significant digits we can print in a string such that when we read that +// string back we get the floating point number we expect. The minimum value C requires is 6, but +// most compilers support 9 +#ifdef FLT_DECIMAL_DIG +#define SK_FLT_DECIMAL_DIG FLT_DECIMAL_DIG +#else +#define SK_FLT_DECIMAL_DIG 9 +#endif + +// IEEE defines how float divide behaves for non-finite values and zero-denoms, but C does not +// so we have a helper that suppresses the possible undefined-behavior warnings. + +#ifdef __clang__ +__attribute__((no_sanitize("float-divide-by-zero"))) +#endif +static inline float sk_ieee_float_divide(float numer, float denom) { + return numer / denom; +} + +#ifdef __clang__ +__attribute__((no_sanitize("float-divide-by-zero"))) +#endif +static inline double sk_ieee_double_divide(double numer, double denom) { + return numer / denom; +} + +// While we clean up divide by zero, we'll replace places that do divide by zero with this TODO. +static inline float sk_ieee_float_divide_TODO_IS_DIVIDE_BY_ZERO_SAFE_HERE(float n, float d) { + return sk_ieee_float_divide(n,d); +} +static inline float sk_ieee_double_divide_TODO_IS_DIVIDE_BY_ZERO_SAFE_HERE(double n, double d) { + return sk_ieee_double_divide(n,d); +} + +static inline float sk_fmaf(float f, float m, float a) { +#if defined(FP_FAST_FMA) + return std::fmaf(f,m,a); +#else + return f*m+a; +#endif +} + +#endif diff --git a/gfx/skia/skia/include/private/SkHalf.h b/gfx/skia/skia/include/private/SkHalf.h new file mode 100644 index 0000000000..d951891310 --- /dev/null +++ b/gfx/skia/skia/include/private/SkHalf.h @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkHalf_DEFINED +#define SkHalf_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkNx.h" + +// 16-bit floating point value +// format is 1 bit sign, 5 bits exponent, 10 bits mantissa +// only used for storage +typedef uint16_t SkHalf; + +static constexpr uint16_t SK_HalfMin = 0x0400; // 2^-14 (minimum positive normal value) +static constexpr uint16_t SK_HalfMax = 0x7bff; // 65504 +static constexpr uint16_t SK_HalfEpsilon = 0x1400; // 2^-10 +static constexpr uint16_t SK_Half1 = 0x3C00; // 1 + +// convert between half and single precision floating point +float SkHalfToFloat(SkHalf h); +SkHalf SkFloatToHalf(float f); + +// Convert between half and single precision floating point, +// assuming inputs and outputs are both finite, and may +// flush values which would be denormal half floats to zero. +static inline Sk4f SkHalfToFloat_finite_ftz(uint64_t); +static inline Sk4h SkFloatToHalf_finite_ftz(const Sk4f&); + +// ~~~~~~~~~~~ impl ~~~~~~~~~~~~~~ // + +// Like the serial versions in SkHalf.cpp, these are based on +// https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ + +// GCC 4.9 lacks the intrinsics to use ARMv8 f16<->f32 instructions, so we use inline assembly. + +static inline Sk4f SkHalfToFloat_finite_ftz(uint64_t rgba) { + Sk4h hs = Sk4h::Load(&rgba); +#if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64) + float32x4_t fs; + asm ("fcvtl %[fs].4s, %[hs].4h \n" // vcvt_f32_f16(...) + : [fs] "=w" (fs) // =w: write-only NEON register + : [hs] "w" (hs.fVec)); // w: read-only NEON register + return fs; +#else + Sk4i bits = SkNx_cast(hs), // Expand to 32 bit. + sign = bits & 0x00008000, // Save the sign bit for later... + positive = bits ^ sign, // ...but strip it off for now. + is_norm = 0x03ff < positive; // Exponent > 0? + + // For normal half floats, extend the mantissa by 13 zero bits, + // then adjust the exponent from 15 bias to 127 bias. + Sk4i norm = (positive << 13) + ((127 - 15) << 23); + + Sk4i merged = (sign << 16) | (norm & is_norm); + return Sk4f::Load(&merged); +#endif +} + +static inline Sk4h SkFloatToHalf_finite_ftz(const Sk4f& fs) { +#if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64) + float32x4_t vec = fs.fVec; + asm ("fcvtn %[vec].4h, %[vec].4s \n" // vcvt_f16_f32(vec) + : [vec] "+w" (vec)); // +w: read-write NEON register + return vreinterpret_u16_f32(vget_low_f32(vec)); +#else + Sk4i bits = Sk4i::Load(&fs), + sign = bits & 0x80000000, // Save the sign bit for later... + positive = bits ^ sign, // ...but strip it off for now. + will_be_norm = 0x387fdfff < positive; // greater than largest denorm half? + + // For normal half floats, adjust the exponent from 127 bias to 15 bias, + // then drop the bottom 13 mantissa bits. + Sk4i norm = (positive - ((127 - 15) << 23)) >> 13; + + Sk4i merged = (sign >> 16) | (will_be_norm & norm); + return SkNx_cast(merged); +#endif +} + +#endif diff --git a/gfx/skia/skia/include/private/SkImageInfoPriv.h b/gfx/skia/skia/include/private/SkImageInfoPriv.h new file mode 100644 index 0000000000..d0ff6fd8df --- /dev/null +++ b/gfx/skia/skia/include/private/SkImageInfoPriv.h @@ -0,0 +1,135 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageInfoPriv_DEFINED +#define SkImageInfoPriv_DEFINED + +#include "include/core/SkImageInfo.h" + +enum SkColorTypeComponentFlag { + kRed_SkColorTypeComponentFlag = 0x1, + kGreen_SkColorTypeComponentFlag = 0x2, + kBlue_SkColorTypeComponentFlag = 0x4, + kAlpha_SkColorTypeComponentFlag = 0x8, + kGray_SkColorTypeComponentFlag = 0x10, + kRG_SkColorTypeComponentFlags = kRed_SkColorTypeComponentFlag | + kGreen_SkColorTypeComponentFlag, + kRGB_SkColorTypeComponentFlags = kRed_SkColorTypeComponentFlag | + kGreen_SkColorTypeComponentFlag | + kBlue_SkColorTypeComponentFlag, + kRGBA_SkColorTypeComponentFlags = kRGB_SkColorTypeComponentFlags | + kAlpha_SkColorTypeComponentFlag, +}; + +static inline uint32_t SkColorTypeComponentFlags(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return 0; + case kAlpha_8_SkColorType: return kAlpha_SkColorTypeComponentFlag; + case kRGB_565_SkColorType: return kRGB_SkColorTypeComponentFlags; + case kARGB_4444_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kRGBA_8888_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kRGB_888x_SkColorType: return kRGB_SkColorTypeComponentFlags; + case kBGRA_8888_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kRGBA_1010102_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kRGB_101010x_SkColorType: return kRGB_SkColorTypeComponentFlags; + case kGray_8_SkColorType: return kGray_SkColorTypeComponentFlag; + case kRGBA_F16Norm_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kRGBA_F16_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kRGBA_F32_SkColorType: return kRGBA_SkColorTypeComponentFlags; + case kR8G8_unorm_SkColorType: return kRG_SkColorTypeComponentFlags; + case kA16_unorm_SkColorType: return kAlpha_SkColorTypeComponentFlag; + case kR16G16_unorm_SkColorType: return kRG_SkColorTypeComponentFlags; + case kA16_float_SkColorType: return kAlpha_SkColorTypeComponentFlag; + case kR16G16_float_SkColorType: return kRG_SkColorTypeComponentFlags; + case kR16G16B16A16_unorm_SkColorType: return kRGBA_SkColorTypeComponentFlags; + } + SkUNREACHABLE; +} + +static inline bool SkColorTypeIsAlphaOnly(SkColorType ct) { + return kAlpha_SkColorTypeComponentFlag == SkColorTypeComponentFlags(ct); +} + +static inline bool SkAlphaTypeIsValid(unsigned value) { + return value <= kLastEnum_SkAlphaType; +} + +static inline bool SkColorTypeIsGray(SkColorType ct) { + auto flags = SkColorTypeComponentFlags(ct); + // Currently assuming that a color type has only gray or does not have gray. + SkASSERT(!(kGray_SkColorTypeComponentFlag & flags) || kGray_SkColorTypeComponentFlag == flags); + return kGray_SkColorTypeComponentFlag == flags; +} + +static int SkColorTypeShiftPerPixel(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return 0; + case kAlpha_8_SkColorType: return 0; + case kRGB_565_SkColorType: return 1; + case kARGB_4444_SkColorType: return 1; + case kRGBA_8888_SkColorType: return 2; + case kRGB_888x_SkColorType: return 2; + case kBGRA_8888_SkColorType: return 2; + case kRGBA_1010102_SkColorType: return 2; + case kRGB_101010x_SkColorType: return 2; + case kGray_8_SkColorType: return 0; + case kRGBA_F16Norm_SkColorType: return 3; + case kRGBA_F16_SkColorType: return 3; + case kRGBA_F32_SkColorType: return 4; + case kR8G8_unorm_SkColorType: return 1; + case kA16_unorm_SkColorType: return 1; + case kR16G16_unorm_SkColorType: return 2; + case kA16_float_SkColorType: return 1; + case kR16G16_float_SkColorType: return 2; + case kR16G16B16A16_unorm_SkColorType: return 3; + } + SkUNREACHABLE; +} + +static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) { + return width * SkColorTypeBytesPerPixel(ct); +} + +static inline bool SkColorTypeIsValid(unsigned value) { + return value <= kLastEnum_SkColorType; +} + +static inline size_t SkColorTypeComputeOffset(SkColorType ct, int x, int y, size_t rowBytes) { + if (kUnknown_SkColorType == ct) { + return 0; + } + return y * rowBytes + (x << SkColorTypeShiftPerPixel(ct)); +} + +/** + * Returns true if |info| contains a valid combination of width, height, colorType, and alphaType. + */ +static inline bool SkImageInfoIsValid(const SkImageInfo& info) { + if (info.width() <= 0 || info.height() <= 0) { + return false; + } + + const int kMaxDimension = SK_MaxS32 >> 2; + if (info.width() > kMaxDimension || info.height() > kMaxDimension) { + return false; + } + + if (kUnknown_SkColorType == info.colorType() || kUnknown_SkAlphaType == info.alphaType()) { + return false; + } + + return true; +} + +/** + * Returns true if Skia has defined a pixel conversion from the |src| to the |dst|. + * Returns false otherwise. + */ +static inline bool SkImageInfoValidConversion(const SkImageInfo& dst, const SkImageInfo& src) { + return SkImageInfoIsValid(dst) && SkImageInfoIsValid(src); +} +#endif // SkImageInfoPriv_DEFINED diff --git a/gfx/skia/skia/include/private/SkMacros.h b/gfx/skia/skia/include/private/SkMacros.h new file mode 100644 index 0000000000..a68193228f --- /dev/null +++ b/gfx/skia/skia/include/private/SkMacros.h @@ -0,0 +1,67 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkMacros_DEFINED +#define SkMacros_DEFINED + +/* + * Usage: SK_MACRO_CONCAT(a, b) to construct the symbol ab + * + * SK_MACRO_CONCAT_IMPL_PRIV just exists to make this work. Do not use directly + * + */ +#define SK_MACRO_CONCAT(X, Y) SK_MACRO_CONCAT_IMPL_PRIV(X, Y) +#define SK_MACRO_CONCAT_IMPL_PRIV(X, Y) X ## Y + +/* + * Usage: SK_MACRO_APPEND_LINE(foo) to make foo123, where 123 is the current + * line number. Easy way to construct + * unique names for local functions or + * variables. + */ +#define SK_MACRO_APPEND_LINE(name) SK_MACRO_CONCAT(name, __LINE__) + +/** + * For some classes, it's almost always an error to instantiate one without a name, e.g. + * { + * SkAutoMutexAcquire(&mutex); + * + * } + * In this case, the writer meant to hold mutex while the rest of the code in the block runs, + * but instead the mutex is acquired and then immediately released. The correct usage is + * { + * SkAutoMutexAcquire lock(&mutex); + * + * } + * + * To prevent callers from instantiating your class without a name, use SK_REQUIRE_LOCAL_VAR + * like this: + * class classname { + * + * }; + * #define classname(...) SK_REQUIRE_LOCAL_VAR(classname) + * + * This won't work with templates, and you must inline the class' constructors and destructors. + * Take a look at SkAutoFree and SkAutoMalloc in this file for examples. + */ +#define SK_REQUIRE_LOCAL_VAR(classname) \ + static_assert(false, "missing name for " #classname) + +//////////////////////////////////////////////////////////////////////////////// + +// Can be used to bracket data types that must be dense, e.g. hash keys. +#if defined(__clang__) // This should work on GCC too, but GCC diagnostic pop didn't seem to work! + #define SK_BEGIN_REQUIRE_DENSE _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic error \"-Wpadded\"") + #define SK_END_REQUIRE_DENSE _Pragma("GCC diagnostic pop") +#else + #define SK_BEGIN_REQUIRE_DENSE + #define SK_END_REQUIRE_DENSE +#endif + +#define SK_INIT_TO_AVOID_WARNING = 0 + +#endif // SkMacros_DEFINED diff --git a/gfx/skia/skia/include/private/SkMalloc.h b/gfx/skia/skia/include/private/SkMalloc.h new file mode 100644 index 0000000000..cb7bbb7035 --- /dev/null +++ b/gfx/skia/skia/include/private/SkMalloc.h @@ -0,0 +1,136 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMalloc_DEFINED +#define SkMalloc_DEFINED + +#include +#include + +#include "include/core/SkTypes.h" + +/* + memory wrappers to be implemented by the porting layer (platform) +*/ + + +/** Free memory returned by sk_malloc(). It is safe to pass null. */ +SK_API extern void sk_free(void*); + +/** + * Called internally if we run out of memory. The platform implementation must + * not return, but should either throw an exception or otherwise exit. + */ +SK_API extern void sk_out_of_memory(void); + +enum { + /** + * If this bit is set, the returned buffer must be zero-initialized. If this bit is not set + * the buffer can be uninitialized. + */ + SK_MALLOC_ZERO_INITIALIZE = 1 << 0, + + /** + * If this bit is set, the implementation must throw/crash/quit if the request cannot + * be fulfilled. If this bit is not set, then it should return nullptr on failure. + */ + SK_MALLOC_THROW = 1 << 1, +}; +/** + * Return a block of memory (at least 4-byte aligned) of at least the specified size. + * If the requested memory cannot be returned, either return nullptr or throw/exit, depending + * on the SK_MALLOC_THROW bit. If the allocation succeeds, the memory will be zero-initialized + * if the SK_MALLOC_ZERO_INITIALIZE bit was set. + * + * To free the memory, call sk_free() + */ +SK_API extern void* sk_malloc_flags(size_t size, unsigned flags); + +/** Same as standard realloc(), but this one never returns null on failure. It will throw + * an exception if it fails. + */ +SK_API extern void* sk_realloc_throw(void* buffer, size_t size); + +static inline void* sk_malloc_throw(size_t size) { + return sk_malloc_flags(size, SK_MALLOC_THROW); +} + +static inline void* sk_calloc_throw(size_t size) { + return sk_malloc_flags(size, SK_MALLOC_THROW | SK_MALLOC_ZERO_INITIALIZE); +} + +static inline void* sk_calloc_canfail(size_t size) { +#if defined(IS_FUZZING_WITH_LIBFUZZER) + // The Libfuzzer environment is very susceptible to OOM, so to avoid those + // just pretend we can't allocate more than 200kb. + if (size > 200000) { + return nullptr; + } +#endif + return sk_malloc_flags(size, SK_MALLOC_ZERO_INITIALIZE); +} + +// Performs a safe multiply count * elemSize, checking for overflow +SK_API extern void* sk_calloc_throw(size_t count, size_t elemSize); +SK_API extern void* sk_malloc_throw(size_t count, size_t elemSize); +SK_API extern void* sk_realloc_throw(void* buffer, size_t count, size_t elemSize); + +/** + * These variants return nullptr on failure + */ +static inline void* sk_malloc_canfail(size_t size) { +#if defined(IS_FUZZING_WITH_LIBFUZZER) + // The Libfuzzer environment is very susceptible to OOM, so to avoid those + // just pretend we can't allocate more than 200kb. + if (size > 200000) { + return nullptr; + } +#endif + return sk_malloc_flags(size, 0); +} +SK_API extern void* sk_malloc_canfail(size_t count, size_t elemSize); + +// bzero is safer than memset, but we can't rely on it, so... sk_bzero() +static inline void sk_bzero(void* buffer, size_t size) { + // Please c.f. sk_careful_memcpy. It's undefined behavior to call memset(null, 0, 0). + if (size) { + memset(buffer, 0, size); + } +} + +/** + * sk_careful_memcpy() is just like memcpy(), but guards against undefined behavior. + * + * It is undefined behavior to call memcpy() with null dst or src, even if len is 0. + * If an optimizer is "smart" enough, it can exploit this to do unexpected things. + * memcpy(dst, src, 0); + * if (src) { + * printf("%x\n", *src); + * } + * In this code the compiler can assume src is not null and omit the if (src) {...} check, + * unconditionally running the printf, crashing the program if src really is null. + * Of the compilers we pay attention to only GCC performs this optimization in practice. + */ +static inline void* sk_careful_memcpy(void* dst, const void* src, size_t len) { + // When we pass >0 len we had better already be passing valid pointers. + // So we just need to skip calling memcpy when len == 0. + if (len) { + memcpy(dst,src,len); + } + return dst; +} + +static inline void* sk_careful_memmove(void* dst, const void* src, size_t len) { + // When we pass >0 len we had better already be passing valid pointers. + // So we just need to skip calling memcpy when len == 0. + if (len) { + memmove(dst,src,len); + } + return dst; +} + +#endif // SkMalloc_DEFINED diff --git a/gfx/skia/skia/include/private/SkMutex.h b/gfx/skia/skia/include/private/SkMutex.h new file mode 100644 index 0000000000..a9af46fdc1 --- /dev/null +++ b/gfx/skia/skia/include/private/SkMutex.h @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMutex_DEFINED +#define SkMutex_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMacros.h" +#include "include/private/SkSemaphore.h" +#include "include/private/SkThreadAnnotations.h" +#include "include/private/SkThreadID.h" + +class SK_CAPABILITY("mutex") SkMutex { +public: + constexpr SkMutex() = default; + + void acquire() SK_ACQUIRE() { + fSemaphore.wait(); + SkDEBUGCODE(fOwner = SkGetThreadID();) + } + + void release() SK_RELEASE_CAPABILITY() { + this->assertHeld(); + SkDEBUGCODE(fOwner = kIllegalThreadID;) + fSemaphore.signal(); + } + + void assertHeld() SK_ASSERT_CAPABILITY(this) { + SkASSERT(fOwner == SkGetThreadID()); + } + +private: + SkSemaphore fSemaphore{1}; + SkDEBUGCODE(SkThreadID fOwner{kIllegalThreadID};) +}; + +class SK_SCOPED_CAPABILITY SkAutoMutexExclusive { +public: + SkAutoMutexExclusive(SkMutex& mutex) SK_ACQUIRE(mutex) : fMutex(mutex) { fMutex.acquire(); } + ~SkAutoMutexExclusive() SK_RELEASE_CAPABILITY() { fMutex.release(); } + +private: + SkMutex& fMutex; +}; + +#define SkAutoMutexExclusive(...) SK_REQUIRE_LOCAL_VAR(SkAutoMutexExclusive) + +#endif // SkMutex_DEFINED diff --git a/gfx/skia/skia/include/private/SkNoncopyable.h b/gfx/skia/skia/include/private/SkNoncopyable.h new file mode 100644 index 0000000000..bda5d50bb6 --- /dev/null +++ b/gfx/skia/skia/include/private/SkNoncopyable.h @@ -0,0 +1,30 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNoncopyable_DEFINED +#define SkNoncopyable_DEFINED + +#include "include/core/SkTypes.h" + +/** \class SkNoncopyable + + SkNoncopyable is the base class for objects that do not want to + be copied. It hides its copy-constructor and its assignment-operator. +*/ +class SK_API SkNoncopyable { +public: + SkNoncopyable() = default; + + SkNoncopyable(SkNoncopyable&&) = default; + SkNoncopyable& operator =(SkNoncopyable&&) = default; + +private: + SkNoncopyable(const SkNoncopyable&) = delete; + SkNoncopyable& operator=(const SkNoncopyable&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkNx.h b/gfx/skia/skia/include/private/SkNx.h new file mode 100644 index 0000000000..06a6df9af2 --- /dev/null +++ b/gfx/skia/skia/include/private/SkNx.h @@ -0,0 +1,441 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNx_DEFINED +#define SkNx_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSafe_math.h" + +#include +#include +#include + +// Every single SkNx method wants to be fully inlined. (We know better than MSVC). +#define AI SK_ALWAYS_INLINE + +namespace { // NOLINT(google-build-namespaces) + +// The default SkNx just proxies down to a pair of SkNx. +template +struct SkNx { + typedef SkNx Half; + + Half fLo, fHi; + + AI SkNx() = default; + AI SkNx(const Half& lo, const Half& hi) : fLo(lo), fHi(hi) {} + + AI SkNx(T v) : fLo(v), fHi(v) {} + + AI SkNx(T a, T b) : fLo(a) , fHi(b) { static_assert(N==2, ""); } + AI SkNx(T a, T b, T c, T d) : fLo(a,b), fHi(c,d) { static_assert(N==4, ""); } + AI SkNx(T a, T b, T c, T d, T e, T f, T g, T h) : fLo(a,b,c,d), fHi(e,f,g,h) { + static_assert(N==8, ""); + } + AI SkNx(T a, T b, T c, T d, T e, T f, T g, T h, + T i, T j, T k, T l, T m, T n, T o, T p) + : fLo(a,b,c,d, e,f,g,h), fHi(i,j,k,l, m,n,o,p) { + static_assert(N==16, ""); + } + + AI T operator[](int k) const { + SkASSERT(0 <= k && k < N); + return k < N/2 ? fLo[k] : fHi[k-N/2]; + } + + AI static SkNx Load(const void* vptr) { + auto ptr = (const char*)vptr; + return { Half::Load(ptr), Half::Load(ptr + N/2*sizeof(T)) }; + } + AI void store(void* vptr) const { + auto ptr = (char*)vptr; + fLo.store(ptr); + fHi.store(ptr + N/2*sizeof(T)); + } + + AI static void Load4(const void* vptr, SkNx* a, SkNx* b, SkNx* c, SkNx* d) { + auto ptr = (const char*)vptr; + Half al, bl, cl, dl, + ah, bh, ch, dh; + Half::Load4(ptr , &al, &bl, &cl, &dl); + Half::Load4(ptr + 4*N/2*sizeof(T), &ah, &bh, &ch, &dh); + *a = SkNx{al, ah}; + *b = SkNx{bl, bh}; + *c = SkNx{cl, ch}; + *d = SkNx{dl, dh}; + } + AI static void Load3(const void* vptr, SkNx* a, SkNx* b, SkNx* c) { + auto ptr = (const char*)vptr; + Half al, bl, cl, + ah, bh, ch; + Half::Load3(ptr , &al, &bl, &cl); + Half::Load3(ptr + 3*N/2*sizeof(T), &ah, &bh, &ch); + *a = SkNx{al, ah}; + *b = SkNx{bl, bh}; + *c = SkNx{cl, ch}; + } + AI static void Load2(const void* vptr, SkNx* a, SkNx* b) { + auto ptr = (const char*)vptr; + Half al, bl, + ah, bh; + Half::Load2(ptr , &al, &bl); + Half::Load2(ptr + 2*N/2*sizeof(T), &ah, &bh); + *a = SkNx{al, ah}; + *b = SkNx{bl, bh}; + } + AI static void Store4(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c, const SkNx& d) { + auto ptr = (char*)vptr; + Half::Store4(ptr, a.fLo, b.fLo, c.fLo, d.fLo); + Half::Store4(ptr + 4*N/2*sizeof(T), a.fHi, b.fHi, c.fHi, d.fHi); + } + AI static void Store3(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c) { + auto ptr = (char*)vptr; + Half::Store3(ptr, a.fLo, b.fLo, c.fLo); + Half::Store3(ptr + 3*N/2*sizeof(T), a.fHi, b.fHi, c.fHi); + } + AI static void Store2(void* vptr, const SkNx& a, const SkNx& b) { + auto ptr = (char*)vptr; + Half::Store2(ptr, a.fLo, b.fLo); + Half::Store2(ptr + 2*N/2*sizeof(T), a.fHi, b.fHi); + } + + AI T min() const { return SkTMin(fLo.min(), fHi.min()); } + AI T max() const { return SkTMax(fLo.max(), fHi.max()); } + AI bool anyTrue() const { return fLo.anyTrue() || fHi.anyTrue(); } + AI bool allTrue() const { return fLo.allTrue() && fHi.allTrue(); } + + AI SkNx abs() const { return { fLo. abs(), fHi. abs() }; } + AI SkNx sqrt() const { return { fLo. sqrt(), fHi. sqrt() }; } + AI SkNx rsqrt() const { return { fLo. rsqrt(), fHi. rsqrt() }; } + AI SkNx floor() const { return { fLo. floor(), fHi. floor() }; } + AI SkNx invert() const { return { fLo.invert(), fHi.invert() }; } + + AI SkNx operator!() const { return { !fLo, !fHi }; } + AI SkNx operator-() const { return { -fLo, -fHi }; } + AI SkNx operator~() const { return { ~fLo, ~fHi }; } + + AI SkNx operator<<(int bits) const { return { fLo << bits, fHi << bits }; } + AI SkNx operator>>(int bits) const { return { fLo >> bits, fHi >> bits }; } + + AI SkNx operator+(const SkNx& y) const { return { fLo + y.fLo, fHi + y.fHi }; } + AI SkNx operator-(const SkNx& y) const { return { fLo - y.fLo, fHi - y.fHi }; } + AI SkNx operator*(const SkNx& y) const { return { fLo * y.fLo, fHi * y.fHi }; } + AI SkNx operator/(const SkNx& y) const { return { fLo / y.fLo, fHi / y.fHi }; } + + AI SkNx operator&(const SkNx& y) const { return { fLo & y.fLo, fHi & y.fHi }; } + AI SkNx operator|(const SkNx& y) const { return { fLo | y.fLo, fHi | y.fHi }; } + AI SkNx operator^(const SkNx& y) const { return { fLo ^ y.fLo, fHi ^ y.fHi }; } + + AI SkNx operator==(const SkNx& y) const { return { fLo == y.fLo, fHi == y.fHi }; } + AI SkNx operator!=(const SkNx& y) const { return { fLo != y.fLo, fHi != y.fHi }; } + AI SkNx operator<=(const SkNx& y) const { return { fLo <= y.fLo, fHi <= y.fHi }; } + AI SkNx operator>=(const SkNx& y) const { return { fLo >= y.fLo, fHi >= y.fHi }; } + AI SkNx operator< (const SkNx& y) const { return { fLo < y.fLo, fHi < y.fHi }; } + AI SkNx operator> (const SkNx& y) const { return { fLo > y.fLo, fHi > y.fHi }; } + + AI SkNx saturatedAdd(const SkNx& y) const { + return { fLo.saturatedAdd(y.fLo), fHi.saturatedAdd(y.fHi) }; + } + + AI SkNx mulHi(const SkNx& m) const { + return { fLo.mulHi(m.fLo), fHi.mulHi(m.fHi) }; + } + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return { fLo.thenElse(t.fLo, e.fLo), fHi.thenElse(t.fHi, e.fHi) }; + } + AI static SkNx Min(const SkNx& x, const SkNx& y) { + return { Half::Min(x.fLo, y.fLo), Half::Min(x.fHi, y.fHi) }; + } + AI static SkNx Max(const SkNx& x, const SkNx& y) { + return { Half::Max(x.fLo, y.fLo), Half::Max(x.fHi, y.fHi) }; + } +}; + +// The N -> N/2 recursion bottoms out at N == 1, a scalar value. +template +struct SkNx<1,T> { + T fVal; + + AI SkNx() = default; + AI SkNx(T v) : fVal(v) {} + + // Android complains against unused parameters, so we guard it + AI T operator[](int SkDEBUGCODE(k)) const { + SkASSERT(k == 0); + return fVal; + } + + AI static SkNx Load(const void* ptr) { + SkNx v; + memcpy(&v, ptr, sizeof(T)); + return v; + } + AI void store(void* ptr) const { memcpy(ptr, &fVal, sizeof(T)); } + + AI static void Load4(const void* vptr, SkNx* a, SkNx* b, SkNx* c, SkNx* d) { + auto ptr = (const char*)vptr; + *a = Load(ptr + 0*sizeof(T)); + *b = Load(ptr + 1*sizeof(T)); + *c = Load(ptr + 2*sizeof(T)); + *d = Load(ptr + 3*sizeof(T)); + } + AI static void Load3(const void* vptr, SkNx* a, SkNx* b, SkNx* c) { + auto ptr = (const char*)vptr; + *a = Load(ptr + 0*sizeof(T)); + *b = Load(ptr + 1*sizeof(T)); + *c = Load(ptr + 2*sizeof(T)); + } + AI static void Load2(const void* vptr, SkNx* a, SkNx* b) { + auto ptr = (const char*)vptr; + *a = Load(ptr + 0*sizeof(T)); + *b = Load(ptr + 1*sizeof(T)); + } + AI static void Store4(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c, const SkNx& d) { + auto ptr = (char*)vptr; + a.store(ptr + 0*sizeof(T)); + b.store(ptr + 1*sizeof(T)); + c.store(ptr + 2*sizeof(T)); + d.store(ptr + 3*sizeof(T)); + } + AI static void Store3(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c) { + auto ptr = (char*)vptr; + a.store(ptr + 0*sizeof(T)); + b.store(ptr + 1*sizeof(T)); + c.store(ptr + 2*sizeof(T)); + } + AI static void Store2(void* vptr, const SkNx& a, const SkNx& b) { + auto ptr = (char*)vptr; + a.store(ptr + 0*sizeof(T)); + b.store(ptr + 1*sizeof(T)); + } + + AI T min() const { return fVal; } + AI T max() const { return fVal; } + AI bool anyTrue() const { return fVal != 0; } + AI bool allTrue() const { return fVal != 0; } + + AI SkNx abs() const { return Abs(fVal); } + AI SkNx sqrt() const { return Sqrt(fVal); } + AI SkNx rsqrt() const { return T(1) / this->sqrt(); } + AI SkNx floor() const { return Floor(fVal); } + AI SkNx invert() const { return T(1) / *this; } + + AI SkNx operator!() const { return !fVal; } + AI SkNx operator-() const { return -fVal; } + AI SkNx operator~() const { return FromBits(~ToBits(fVal)); } + + AI SkNx operator<<(int bits) const { return fVal << bits; } + AI SkNx operator>>(int bits) const { return fVal >> bits; } + + AI SkNx operator+(const SkNx& y) const { return fVal + y.fVal; } + AI SkNx operator-(const SkNx& y) const { return fVal - y.fVal; } + AI SkNx operator*(const SkNx& y) const { return fVal * y.fVal; } + AI SkNx operator/(const SkNx& y) const { return fVal / y.fVal; } + + AI SkNx operator&(const SkNx& y) const { return FromBits(ToBits(fVal) & ToBits(y.fVal)); } + AI SkNx operator|(const SkNx& y) const { return FromBits(ToBits(fVal) | ToBits(y.fVal)); } + AI SkNx operator^(const SkNx& y) const { return FromBits(ToBits(fVal) ^ ToBits(y.fVal)); } + + AI SkNx operator==(const SkNx& y) const { return FromBits(fVal == y.fVal ? ~0 : 0); } + AI SkNx operator!=(const SkNx& y) const { return FromBits(fVal != y.fVal ? ~0 : 0); } + AI SkNx operator<=(const SkNx& y) const { return FromBits(fVal <= y.fVal ? ~0 : 0); } + AI SkNx operator>=(const SkNx& y) const { return FromBits(fVal >= y.fVal ? ~0 : 0); } + AI SkNx operator< (const SkNx& y) const { return FromBits(fVal < y.fVal ? ~0 : 0); } + AI SkNx operator> (const SkNx& y) const { return FromBits(fVal > y.fVal ? ~0 : 0); } + + AI static SkNx Min(const SkNx& x, const SkNx& y) { return x.fVal < y.fVal ? x : y; } + AI static SkNx Max(const SkNx& x, const SkNx& y) { return x.fVal > y.fVal ? x : y; } + + AI SkNx saturatedAdd(const SkNx& y) const { + static_assert(std::is_unsigned::value, ""); + T sum = fVal + y.fVal; + return sum < fVal ? std::numeric_limits::max() : sum; + } + + AI SkNx mulHi(const SkNx& m) const { + static_assert(std::is_unsigned::value, ""); + static_assert(sizeof(T) <= 4, ""); + return static_cast((static_cast(fVal) * m.fVal) >> (sizeof(T)*8)); + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { return fVal != 0 ? t : e; } + +private: + // Helper functions to choose the right float/double methods. (In madness lies...) + AI static int Abs(int val) { return val < 0 ? -val : val; } + + AI static float Abs(float val) { return ::fabsf(val); } + AI static float Sqrt(float val) { return ::sqrtf(val); } + AI static float Floor(float val) { return ::floorf(val); } + + AI static double Abs(double val) { return ::fabs(val); } + AI static double Sqrt(double val) { return ::sqrt(val); } + AI static double Floor(double val) { return ::floor(val); } + + // Helper functions for working with floats/doubles as bit patterns. + template + AI static U ToBits(U v) { return v; } + AI static int32_t ToBits(float v) { int32_t bits; memcpy(&bits, &v, sizeof(v)); return bits; } + AI static int64_t ToBits(double v) { int64_t bits; memcpy(&bits, &v, sizeof(v)); return bits; } + + template + AI static T FromBits(Bits bits) { + static_assert(std::is_pod::value && + std::is_pod::value && + sizeof(T) <= sizeof(Bits), ""); + T val; + memcpy(&val, &bits, sizeof(T)); + return val; + } +}; + +// Allow scalars on the left or right of binary operators, and things like +=, &=, etc. +#define V template AI static SkNx + V operator+ (T x, const SkNx& y) { return SkNx(x) + y; } + V operator- (T x, const SkNx& y) { return SkNx(x) - y; } + template AI static SkNx operator-(int x, const SkNx& y) { return SkNx(x) - y; } + template AI static SkNx operator-(int x, const SkNx& y) { return SkNx(x) - y; } + V operator* (T x, const SkNx& y) { return SkNx(x) * y; } + template AI static SkNx operator*(int x, const SkNx& y) { return SkNx(x) * y; } + V operator/ (T x, const SkNx& y) { return SkNx(x) / y; } + V operator& (T x, const SkNx& y) { return SkNx(x) & y; } + V operator| (T x, const SkNx& y) { return SkNx(x) | y; } + V operator^ (T x, const SkNx& y) { return SkNx(x) ^ y; } + V operator==(T x, const SkNx& y) { return SkNx(x) == y; } + V operator!=(T x, const SkNx& y) { return SkNx(x) != y; } + V operator<=(T x, const SkNx& y) { return SkNx(x) <= y; } + template AI static SkNx operator<=(int x, const SkNx& y) { return SkNx(x) <= y; } + V operator>=(T x, const SkNx& y) { return SkNx(x) >= y; } + V operator< (T x, const SkNx& y) { return SkNx(x) < y; } + V operator> (T x, const SkNx& y) { return SkNx(x) > y; } + + V operator+ (const SkNx& x, T y) { return x + SkNx(y); } + V operator- (const SkNx& x, T y) { return x - SkNx(y); } + V operator* (const SkNx& x, T y) { return x * SkNx(y); } + V operator/ (const SkNx& x, T y) { return x / SkNx(y); } + V operator& (const SkNx& x, T y) { return x & SkNx(y); } + V operator&&(const SkNx& x, const SkNx& y) { return x & y; } + V operator| (const SkNx& x, T y) { return x | SkNx(y); } + V operator||(const SkNx& x, const SkNx& y) { return x | y; } + V operator^ (const SkNx& x, T y) { return x ^ SkNx(y); } + V operator==(const SkNx& x, T y) { return x == SkNx(y); } + V operator!=(const SkNx& x, T y) { return x != SkNx(y); } + V operator<=(const SkNx& x, T y) { return x <= SkNx(y); } + V operator>=(const SkNx& x, T y) { return x >= SkNx(y); } + V operator< (const SkNx& x, T y) { return x < SkNx(y); } + V operator> (const SkNx& x, T y) { return x > SkNx(y); } + + V& operator<<=(SkNx& x, int bits) { return (x = x << bits); } + V& operator>>=(SkNx& x, int bits) { return (x = x >> bits); } + + V& operator +=(SkNx& x, const SkNx& y) { return (x = x + y); } + V& operator -=(SkNx& x, const SkNx& y) { return (x = x - y); } + V& operator *=(SkNx& x, const SkNx& y) { return (x = x * y); } + V& operator /=(SkNx& x, const SkNx& y) { return (x = x / y); } + V& operator &=(SkNx& x, const SkNx& y) { return (x = x & y); } + V& operator |=(SkNx& x, const SkNx& y) { return (x = x | y); } + V& operator ^=(SkNx& x, const SkNx& y) { return (x = x ^ y); } + + V& operator +=(SkNx& x, T y) { return (x = x + SkNx(y)); } + V& operator -=(SkNx& x, T y) { return (x = x - SkNx(y)); } + V& operator *=(SkNx& x, T y) { return (x = x * SkNx(y)); } + V& operator /=(SkNx& x, T y) { return (x = x / SkNx(y)); } + V& operator &=(SkNx& x, T y) { return (x = x & SkNx(y)); } + V& operator |=(SkNx& x, T y) { return (x = x | SkNx(y)); } + V& operator ^=(SkNx& x, T y) { return (x = x ^ SkNx(y)); } +#undef V + +// SkNx ~~> SkNx + SkNx +template +AI static void SkNx_split(const SkNx& v, SkNx* lo, SkNx* hi) { + *lo = v.fLo; + *hi = v.fHi; +} + +// SkNx + SkNx ~~> SkNx +template +AI static SkNx SkNx_join(const SkNx& lo, const SkNx& hi) { + return { lo, hi }; +} + +// A very generic shuffle. Can reorder, duplicate, contract, expand... +// Sk4f v = { R,G,B,A }; +// SkNx_shuffle<2,1,0,3>(v) ~~> {B,G,R,A} +// SkNx_shuffle<2,1>(v) ~~> {B,G} +// SkNx_shuffle<2,1,2,1,2,1,2,1>(v) ~~> {B,G,B,G,B,G,B,G} +// SkNx_shuffle<3,3,3,3>(v) ~~> {A,A,A,A} +template +AI static SkNx SkNx_shuffle(const SkNx& v) { + return { v[Ix]... }; +} + +// Cast from SkNx to SkNx, as if you called static_cast(Src). +template +AI static SkNx SkNx_cast(const SkNx& v) { + return { SkNx_cast(v.fLo), SkNx_cast(v.fHi) }; +} +template +AI static SkNx<1,Dst> SkNx_cast(const SkNx<1,Src>& v) { + return static_cast(v.fVal); +} + +template +AI static SkNx SkNx_fma(const SkNx& f, const SkNx& m, const SkNx& a) { + return f*m+a; +} + +} // namespace + +typedef SkNx<2, float> Sk2f; +typedef SkNx<4, float> Sk4f; +typedef SkNx<8, float> Sk8f; +typedef SkNx<16, float> Sk16f; + +typedef SkNx<2, SkScalar> Sk2s; +typedef SkNx<4, SkScalar> Sk4s; +typedef SkNx<8, SkScalar> Sk8s; +typedef SkNx<16, SkScalar> Sk16s; + +typedef SkNx<4, uint8_t> Sk4b; +typedef SkNx<8, uint8_t> Sk8b; +typedef SkNx<16, uint8_t> Sk16b; + +typedef SkNx<4, uint16_t> Sk4h; +typedef SkNx<8, uint16_t> Sk8h; +typedef SkNx<16, uint16_t> Sk16h; + +typedef SkNx<4, int32_t> Sk4i; +typedef SkNx<8, int32_t> Sk8i; +typedef SkNx<4, uint32_t> Sk4u; +typedef SkNx<8, uint32_t> Sk8u; + +// Include platform specific specializations if available. +#if !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include "include/private/SkNx_sse.h" +#elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON) + #include "include/private/SkNx_neon.h" +#else + +AI static Sk4i Sk4f_round(const Sk4f& x) { + return { (int) lrintf (x[0]), + (int) lrintf (x[1]), + (int) lrintf (x[2]), + (int) lrintf (x[3]), }; +} + +#endif + +AI static void Sk4f_ToBytes(uint8_t p[16], + const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& d) { + SkNx_cast(SkNx_join(SkNx_join(a,b), SkNx_join(c,d))).store(p); +} + +#undef AI + +#endif//SkNx_DEFINED diff --git a/gfx/skia/skia/include/private/SkNx_neon.h b/gfx/skia/skia/include/private/SkNx_neon.h new file mode 100644 index 0000000000..a14a57c277 --- /dev/null +++ b/gfx/skia/skia/include/private/SkNx_neon.h @@ -0,0 +1,740 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNx_neon_DEFINED +#define SkNx_neon_DEFINED + +#include + +namespace { // NOLINT(google-build-namespaces) + +// ARMv8 has vrndm(q)_f32 to floor floats. Here we emulate it: +// - roundtrip through integers via truncation +// - subtract 1 if that's too big (possible for negative values). +// This restricts the domain of our inputs to a maximum somehwere around 2^31. Seems plenty big. +AI static float32x4_t emulate_vrndmq_f32(float32x4_t v) { + auto roundtrip = vcvtq_f32_s32(vcvtq_s32_f32(v)); + auto too_big = vcgtq_f32(roundtrip, v); + return vsubq_f32(roundtrip, (float32x4_t)vandq_u32(too_big, (uint32x4_t)vdupq_n_f32(1))); +} +AI static float32x2_t emulate_vrndm_f32(float32x2_t v) { + auto roundtrip = vcvt_f32_s32(vcvt_s32_f32(v)); + auto too_big = vcgt_f32(roundtrip, v); + return vsub_f32(roundtrip, (float32x2_t)vand_u32(too_big, (uint32x2_t)vdup_n_f32(1))); +} + +template <> +class SkNx<2, float> { +public: + AI SkNx(float32x2_t vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(float val) : fVec(vdup_n_f32(val)) {} + AI SkNx(float a, float b) { fVec = (float32x2_t) { a, b }; } + + AI static SkNx Load(const void* ptr) { return vld1_f32((const float*)ptr); } + AI void store(void* ptr) const { vst1_f32((float*)ptr, fVec); } + + AI static void Load2(const void* ptr, SkNx* x, SkNx* y) { + float32x2x2_t xy = vld2_f32((const float*) ptr); + *x = xy.val[0]; + *y = xy.val[1]; + } + + AI static void Store2(void* dst, const SkNx& a, const SkNx& b) { + float32x2x2_t ab = {{ + a.fVec, + b.fVec, + }}; + vst2_f32((float*) dst, ab); + } + + AI static void Store3(void* dst, const SkNx& a, const SkNx& b, const SkNx& c) { + float32x2x3_t abc = {{ + a.fVec, + b.fVec, + c.fVec, + }}; + vst3_f32((float*) dst, abc); + } + + AI static void Store4(void* dst, const SkNx& a, const SkNx& b, const SkNx& c, const SkNx& d) { + float32x2x4_t abcd = {{ + a.fVec, + b.fVec, + c.fVec, + d.fVec, + }}; + vst4_f32((float*) dst, abcd); + } + + AI SkNx invert() const { + float32x2_t est0 = vrecpe_f32(fVec), + est1 = vmul_f32(vrecps_f32(est0, fVec), est0); + return est1; + } + + AI SkNx operator - () const { return vneg_f32(fVec); } + + AI SkNx operator + (const SkNx& o) const { return vadd_f32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsub_f32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return vmul_f32(fVec, o.fVec); } + AI SkNx operator / (const SkNx& o) const { + #if defined(SK_CPU_ARM64) + return vdiv_f32(fVec, o.fVec); + #else + float32x2_t est0 = vrecpe_f32(o.fVec), + est1 = vmul_f32(vrecps_f32(est0, o.fVec), est0), + est2 = vmul_f32(vrecps_f32(est1, o.fVec), est1); + return vmul_f32(fVec, est2); + #endif + } + + AI SkNx operator==(const SkNx& o) const { return vreinterpret_f32_u32(vceq_f32(fVec, o.fVec)); } + AI SkNx operator <(const SkNx& o) const { return vreinterpret_f32_u32(vclt_f32(fVec, o.fVec)); } + AI SkNx operator >(const SkNx& o) const { return vreinterpret_f32_u32(vcgt_f32(fVec, o.fVec)); } + AI SkNx operator<=(const SkNx& o) const { return vreinterpret_f32_u32(vcle_f32(fVec, o.fVec)); } + AI SkNx operator>=(const SkNx& o) const { return vreinterpret_f32_u32(vcge_f32(fVec, o.fVec)); } + AI SkNx operator!=(const SkNx& o) const { + return vreinterpret_f32_u32(vmvn_u32(vceq_f32(fVec, o.fVec))); + } + + AI static SkNx Min(const SkNx& l, const SkNx& r) { return vmin_f32(l.fVec, r.fVec); } + AI static SkNx Max(const SkNx& l, const SkNx& r) { return vmax_f32(l.fVec, r.fVec); } + + AI SkNx abs() const { return vabs_f32(fVec); } + AI SkNx floor() const { + #if defined(SK_CPU_ARM64) + return vrndm_f32(fVec); + #else + return emulate_vrndm_f32(fVec); + #endif + } + + AI SkNx rsqrt() const { + float32x2_t est0 = vrsqrte_f32(fVec); + return vmul_f32(vrsqrts_f32(fVec, vmul_f32(est0, est0)), est0); + } + + AI SkNx sqrt() const { + #if defined(SK_CPU_ARM64) + return vsqrt_f32(fVec); + #else + float32x2_t est0 = vrsqrte_f32(fVec), + est1 = vmul_f32(vrsqrts_f32(fVec, vmul_f32(est0, est0)), est0), + est2 = vmul_f32(vrsqrts_f32(fVec, vmul_f32(est1, est1)), est1); + return vmul_f32(fVec, est2); + #endif + } + + AI float operator[](int k) const { + SkASSERT(0 <= k && k < 2); + union { float32x2_t v; float fs[2]; } pun = {fVec}; + return pun.fs[k&1]; + } + + AI bool allTrue() const { + #if defined(SK_CPU_ARM64) + return 0 != vminv_u32(vreinterpret_u32_f32(fVec)); + #else + auto v = vreinterpret_u32_f32(fVec); + return vget_lane_u32(v,0) && vget_lane_u32(v,1); + #endif + } + AI bool anyTrue() const { + #if defined(SK_CPU_ARM64) + return 0 != vmaxv_u32(vreinterpret_u32_f32(fVec)); + #else + auto v = vreinterpret_u32_f32(fVec); + return vget_lane_u32(v,0) || vget_lane_u32(v,1); + #endif + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbsl_f32(vreinterpret_u32_f32(fVec), t.fVec, e.fVec); + } + + float32x2_t fVec; +}; + +template <> +class SkNx<4, float> { +public: + AI SkNx(float32x4_t vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(float val) : fVec(vdupq_n_f32(val)) {} + AI SkNx(float a, float b, float c, float d) { fVec = (float32x4_t) { a, b, c, d }; } + + AI static SkNx Load(const void* ptr) { return vld1q_f32((const float*)ptr); } + AI void store(void* ptr) const { vst1q_f32((float*)ptr, fVec); } + + AI static void Load2(const void* ptr, SkNx* x, SkNx* y) { + float32x4x2_t xy = vld2q_f32((const float*) ptr); + *x = xy.val[0]; + *y = xy.val[1]; + } + + AI static void Load4(const void* ptr, SkNx* r, SkNx* g, SkNx* b, SkNx* a) { + float32x4x4_t rgba = vld4q_f32((const float*) ptr); + *r = rgba.val[0]; + *g = rgba.val[1]; + *b = rgba.val[2]; + *a = rgba.val[3]; + } + AI static void Store4(void* dst, const SkNx& r, const SkNx& g, const SkNx& b, const SkNx& a) { + float32x4x4_t rgba = {{ + r.fVec, + g.fVec, + b.fVec, + a.fVec, + }}; + vst4q_f32((float*) dst, rgba); + } + + AI SkNx invert() const { + float32x4_t est0 = vrecpeq_f32(fVec), + est1 = vmulq_f32(vrecpsq_f32(est0, fVec), est0); + return est1; + } + + AI SkNx operator - () const { return vnegq_f32(fVec); } + + AI SkNx operator + (const SkNx& o) const { return vaddq_f32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsubq_f32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return vmulq_f32(fVec, o.fVec); } + AI SkNx operator / (const SkNx& o) const { + #if defined(SK_CPU_ARM64) + return vdivq_f32(fVec, o.fVec); + #else + float32x4_t est0 = vrecpeq_f32(o.fVec), + est1 = vmulq_f32(vrecpsq_f32(est0, o.fVec), est0), + est2 = vmulq_f32(vrecpsq_f32(est1, o.fVec), est1); + return vmulq_f32(fVec, est2); + #endif + } + + AI SkNx operator==(const SkNx& o) const {return vreinterpretq_f32_u32(vceqq_f32(fVec, o.fVec));} + AI SkNx operator <(const SkNx& o) const {return vreinterpretq_f32_u32(vcltq_f32(fVec, o.fVec));} + AI SkNx operator >(const SkNx& o) const {return vreinterpretq_f32_u32(vcgtq_f32(fVec, o.fVec));} + AI SkNx operator<=(const SkNx& o) const {return vreinterpretq_f32_u32(vcleq_f32(fVec, o.fVec));} + AI SkNx operator>=(const SkNx& o) const {return vreinterpretq_f32_u32(vcgeq_f32(fVec, o.fVec));} + AI SkNx operator!=(const SkNx& o) const { + return vreinterpretq_f32_u32(vmvnq_u32(vceqq_f32(fVec, o.fVec))); + } + + AI static SkNx Min(const SkNx& l, const SkNx& r) { return vminq_f32(l.fVec, r.fVec); } + AI static SkNx Max(const SkNx& l, const SkNx& r) { return vmaxq_f32(l.fVec, r.fVec); } + + AI SkNx abs() const { return vabsq_f32(fVec); } + AI SkNx floor() const { + #if defined(SK_CPU_ARM64) + return vrndmq_f32(fVec); + #else + return emulate_vrndmq_f32(fVec); + #endif + } + + + AI SkNx rsqrt() const { + float32x4_t est0 = vrsqrteq_f32(fVec); + return vmulq_f32(vrsqrtsq_f32(fVec, vmulq_f32(est0, est0)), est0); + } + + AI SkNx sqrt() const { + #if defined(SK_CPU_ARM64) + return vsqrtq_f32(fVec); + #else + float32x4_t est0 = vrsqrteq_f32(fVec), + est1 = vmulq_f32(vrsqrtsq_f32(fVec, vmulq_f32(est0, est0)), est0), + est2 = vmulq_f32(vrsqrtsq_f32(fVec, vmulq_f32(est1, est1)), est1); + return vmulq_f32(fVec, est2); + #endif + } + + AI float operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { float32x4_t v; float fs[4]; } pun = {fVec}; + return pun.fs[k&3]; + } + + AI float min() const { + #if defined(SK_CPU_ARM64) + return vminvq_f32(fVec); + #else + SkNx min = Min(*this, vrev64q_f32(fVec)); + return SkTMin(min[0], min[2]); + #endif + } + + AI float max() const { + #if defined(SK_CPU_ARM64) + return vmaxvq_f32(fVec); + #else + SkNx max = Max(*this, vrev64q_f32(fVec)); + return SkTMax(max[0], max[2]); + #endif + } + + AI bool allTrue() const { + #if defined(SK_CPU_ARM64) + return 0 != vminvq_u32(vreinterpretq_u32_f32(fVec)); + #else + auto v = vreinterpretq_u32_f32(fVec); + return vgetq_lane_u32(v,0) && vgetq_lane_u32(v,1) + && vgetq_lane_u32(v,2) && vgetq_lane_u32(v,3); + #endif + } + AI bool anyTrue() const { + #if defined(SK_CPU_ARM64) + return 0 != vmaxvq_u32(vreinterpretq_u32_f32(fVec)); + #else + auto v = vreinterpretq_u32_f32(fVec); + return vgetq_lane_u32(v,0) || vgetq_lane_u32(v,1) + || vgetq_lane_u32(v,2) || vgetq_lane_u32(v,3); + #endif + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbslq_f32(vreinterpretq_u32_f32(fVec), t.fVec, e.fVec); + } + + float32x4_t fVec; +}; + +#if defined(SK_CPU_ARM64) + AI static Sk4f SkNx_fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) { + return vfmaq_f32(a.fVec, f.fVec, m.fVec); + } +#endif + +// It's possible that for our current use cases, representing this as +// half a uint16x8_t might be better than representing it as a uint16x4_t. +// It'd make conversion to Sk4b one step simpler. +template <> +class SkNx<4, uint16_t> { +public: + AI SkNx(const uint16x4_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint16_t val) : fVec(vdup_n_u16(val)) {} + AI SkNx(uint16_t a, uint16_t b, uint16_t c, uint16_t d) { + fVec = (uint16x4_t) { a,b,c,d }; + } + + AI static SkNx Load(const void* ptr) { return vld1_u16((const uint16_t*)ptr); } + AI void store(void* ptr) const { vst1_u16((uint16_t*)ptr, fVec); } + + AI static void Load4(const void* ptr, SkNx* r, SkNx* g, SkNx* b, SkNx* a) { + uint16x4x4_t rgba = vld4_u16((const uint16_t*)ptr); + *r = rgba.val[0]; + *g = rgba.val[1]; + *b = rgba.val[2]; + *a = rgba.val[3]; + } + AI static void Load3(const void* ptr, SkNx* r, SkNx* g, SkNx* b) { + uint16x4x3_t rgba = vld3_u16((const uint16_t*)ptr); + *r = rgba.val[0]; + *g = rgba.val[1]; + *b = rgba.val[2]; + } + AI static void Store4(void* dst, const SkNx& r, const SkNx& g, const SkNx& b, const SkNx& a) { + uint16x4x4_t rgba = {{ + r.fVec, + g.fVec, + b.fVec, + a.fVec, + }}; + vst4_u16((uint16_t*) dst, rgba); + } + + AI SkNx operator + (const SkNx& o) const { return vadd_u16(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsub_u16(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return vmul_u16(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return vand_u16(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return vorr_u16(fVec, o.fVec); } + + AI SkNx operator << (int bits) const { return fVec << SkNx(bits).fVec; } + AI SkNx operator >> (int bits) const { return fVec >> SkNx(bits).fVec; } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return vmin_u16(a.fVec, b.fVec); } + + AI uint16_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { uint16x4_t v; uint16_t us[4]; } pun = {fVec}; + return pun.us[k&3]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbsl_u16(fVec, t.fVec, e.fVec); + } + + uint16x4_t fVec; +}; + +template <> +class SkNx<8, uint16_t> { +public: + AI SkNx(const uint16x8_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint16_t val) : fVec(vdupq_n_u16(val)) {} + AI static SkNx Load(const void* ptr) { return vld1q_u16((const uint16_t*)ptr); } + + AI SkNx(uint16_t a, uint16_t b, uint16_t c, uint16_t d, + uint16_t e, uint16_t f, uint16_t g, uint16_t h) { + fVec = (uint16x8_t) { a,b,c,d, e,f,g,h }; + } + + AI void store(void* ptr) const { vst1q_u16((uint16_t*)ptr, fVec); } + + AI SkNx operator + (const SkNx& o) const { return vaddq_u16(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsubq_u16(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return vmulq_u16(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return vandq_u16(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return vorrq_u16(fVec, o.fVec); } + + AI SkNx operator << (int bits) const { return fVec << SkNx(bits).fVec; } + AI SkNx operator >> (int bits) const { return fVec >> SkNx(bits).fVec; } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return vminq_u16(a.fVec, b.fVec); } + + AI uint16_t operator[](int k) const { + SkASSERT(0 <= k && k < 8); + union { uint16x8_t v; uint16_t us[8]; } pun = {fVec}; + return pun.us[k&7]; + } + + AI SkNx mulHi(const SkNx& m) const { + uint32x4_t hi = vmull_u16(vget_high_u16(fVec), vget_high_u16(m.fVec)); + uint32x4_t lo = vmull_u16( vget_low_u16(fVec), vget_low_u16(m.fVec)); + + return { vcombine_u16(vshrn_n_u32(lo,16), vshrn_n_u32(hi,16)) }; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbslq_u16(fVec, t.fVec, e.fVec); + } + + uint16x8_t fVec; +}; + +template <> +class SkNx<4, uint8_t> { +public: + typedef uint32_t __attribute__((aligned(1))) unaligned_uint32_t; + + AI SkNx(const uint8x8_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + fVec = (uint8x8_t){a,b,c,d, 0,0,0,0}; + } + AI static SkNx Load(const void* ptr) { + return (uint8x8_t)vld1_dup_u32((const unaligned_uint32_t*)ptr); + } + AI void store(void* ptr) const { + return vst1_lane_u32((unaligned_uint32_t*)ptr, (uint32x2_t)fVec, 0); + } + AI uint8_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { uint8x8_t v; uint8_t us[8]; } pun = {fVec}; + return pun.us[k&3]; + } + + // TODO as needed + + uint8x8_t fVec; +}; + +template <> +class SkNx<8, uint8_t> { +public: + AI SkNx(const uint8x8_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint8_t val) : fVec(vdup_n_u8(val)) {} + AI SkNx(uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h) { + fVec = (uint8x8_t) { a,b,c,d, e,f,g,h }; + } + + AI static SkNx Load(const void* ptr) { return vld1_u8((const uint8_t*)ptr); } + AI void store(void* ptr) const { vst1_u8((uint8_t*)ptr, fVec); } + + AI uint8_t operator[](int k) const { + SkASSERT(0 <= k && k < 8); + union { uint8x8_t v; uint8_t us[8]; } pun = {fVec}; + return pun.us[k&7]; + } + + uint8x8_t fVec; +}; + +template <> +class SkNx<16, uint8_t> { +public: + AI SkNx(const uint8x16_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint8_t val) : fVec(vdupq_n_u8(val)) {} + AI SkNx(uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h, + uint8_t i, uint8_t j, uint8_t k, uint8_t l, + uint8_t m, uint8_t n, uint8_t o, uint8_t p) { + fVec = (uint8x16_t) { a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p }; + } + + AI static SkNx Load(const void* ptr) { return vld1q_u8((const uint8_t*)ptr); } + AI void store(void* ptr) const { vst1q_u8((uint8_t*)ptr, fVec); } + + AI SkNx saturatedAdd(const SkNx& o) const { return vqaddq_u8(fVec, o.fVec); } + + AI SkNx operator + (const SkNx& o) const { return vaddq_u8(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsubq_u8(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return vandq_u8(fVec, o.fVec); } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return vminq_u8(a.fVec, b.fVec); } + AI SkNx operator < (const SkNx& o) const { return vcltq_u8(fVec, o.fVec); } + + AI uint8_t operator[](int k) const { + SkASSERT(0 <= k && k < 16); + union { uint8x16_t v; uint8_t us[16]; } pun = {fVec}; + return pun.us[k&15]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbslq_u8(fVec, t.fVec, e.fVec); + } + + uint8x16_t fVec; +}; + +template <> +class SkNx<4, int32_t> { +public: + AI SkNx(const int32x4_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(int32_t v) { + fVec = vdupq_n_s32(v); + } + AI SkNx(int32_t a, int32_t b, int32_t c, int32_t d) { + fVec = (int32x4_t){a,b,c,d}; + } + AI static SkNx Load(const void* ptr) { + return vld1q_s32((const int32_t*)ptr); + } + AI void store(void* ptr) const { + return vst1q_s32((int32_t*)ptr, fVec); + } + AI int32_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { int32x4_t v; int32_t is[4]; } pun = {fVec}; + return pun.is[k&3]; + } + + AI SkNx operator + (const SkNx& o) const { return vaddq_s32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsubq_s32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return vmulq_s32(fVec, o.fVec); } + + AI SkNx operator & (const SkNx& o) const { return vandq_s32(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return vorrq_s32(fVec, o.fVec); } + AI SkNx operator ^ (const SkNx& o) const { return veorq_s32(fVec, o.fVec); } + + AI SkNx operator << (int bits) const { return fVec << SkNx(bits).fVec; } + AI SkNx operator >> (int bits) const { return fVec >> SkNx(bits).fVec; } + + AI SkNx operator == (const SkNx& o) const { + return vreinterpretq_s32_u32(vceqq_s32(fVec, o.fVec)); + } + AI SkNx operator < (const SkNx& o) const { + return vreinterpretq_s32_u32(vcltq_s32(fVec, o.fVec)); + } + AI SkNx operator > (const SkNx& o) const { + return vreinterpretq_s32_u32(vcgtq_s32(fVec, o.fVec)); + } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return vminq_s32(a.fVec, b.fVec); } + AI static SkNx Max(const SkNx& a, const SkNx& b) { return vmaxq_s32(a.fVec, b.fVec); } + // TODO as needed + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbslq_s32(vreinterpretq_u32_s32(fVec), t.fVec, e.fVec); + } + + AI SkNx abs() const { return vabsq_s32(fVec); } + + int32x4_t fVec; +}; + +template <> +class SkNx<4, uint32_t> { +public: + AI SkNx(const uint32x4_t& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint32_t v) { + fVec = vdupq_n_u32(v); + } + AI SkNx(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { + fVec = (uint32x4_t){a,b,c,d}; + } + AI static SkNx Load(const void* ptr) { + return vld1q_u32((const uint32_t*)ptr); + } + AI void store(void* ptr) const { + return vst1q_u32((uint32_t*)ptr, fVec); + } + AI uint32_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { uint32x4_t v; uint32_t us[4]; } pun = {fVec}; + return pun.us[k&3]; + } + + AI SkNx operator + (const SkNx& o) const { return vaddq_u32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return vsubq_u32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return vmulq_u32(fVec, o.fVec); } + + AI SkNx operator & (const SkNx& o) const { return vandq_u32(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return vorrq_u32(fVec, o.fVec); } + AI SkNx operator ^ (const SkNx& o) const { return veorq_u32(fVec, o.fVec); } + + AI SkNx operator << (int bits) const { return fVec << SkNx(bits).fVec; } + AI SkNx operator >> (int bits) const { return fVec >> SkNx(bits).fVec; } + + AI SkNx operator == (const SkNx& o) const { return vceqq_u32(fVec, o.fVec); } + AI SkNx operator < (const SkNx& o) const { return vcltq_u32(fVec, o.fVec); } + AI SkNx operator > (const SkNx& o) const { return vcgtq_u32(fVec, o.fVec); } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return vminq_u32(a.fVec, b.fVec); } + // TODO as needed + + AI SkNx mulHi(const SkNx& m) const { + uint64x2_t hi = vmull_u32(vget_high_u32(fVec), vget_high_u32(m.fVec)); + uint64x2_t lo = vmull_u32( vget_low_u32(fVec), vget_low_u32(m.fVec)); + + return { vcombine_u32(vshrn_n_u64(lo,32), vshrn_n_u64(hi,32)) }; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return vbslq_u32(fVec, t.fVec, e.fVec); + } + + uint32x4_t fVec; +}; + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4f& src) { + return vcvtq_s32_f32(src.fVec); + +} +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4i& src) { + return vcvtq_f32_s32(src.fVec); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4u& src) { + return SkNx_cast(Sk4i::Load(&src)); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const SkNx<4, uint64_t>& src) { + return Sk4f(float(int32_t(src[0])), float(int32_t(src[1])), float(int32_t(src[2])), float(int32_t(src[3]))); +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4f& src) { + return vqmovn_u32(vcvtq_u32_f32(src.fVec)); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4h& src) { + return vcvtq_f32_u32(vmovl_u16(src.fVec)); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4f& src) { + uint32x4_t _32 = vcvtq_u32_f32(src.fVec); + uint16x4_t _16 = vqmovn_u32(_32); + return vqmovn_u16(vcombine_u16(_16, _16)); +} + +template<> AI /*static*/ Sk4u SkNx_cast(const Sk4b& src) { + uint16x8_t _16 = vmovl_u8(src.fVec); + return vmovl_u16(vget_low_u16(_16)); +} + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4b& src) { + return vreinterpretq_s32_u32(SkNx_cast(src).fVec); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4b& src) { + return vcvtq_f32_s32(SkNx_cast(src).fVec); +} + +template<> AI /*static*/ Sk16b SkNx_cast(const Sk16f& src) { + Sk8f ab, cd; + SkNx_split(src, &ab, &cd); + + Sk4f a,b,c,d; + SkNx_split(ab, &a, &b); + SkNx_split(cd, &c, &d); + return vuzpq_u8(vuzpq_u8((uint8x16_t)vcvtq_u32_f32(a.fVec), + (uint8x16_t)vcvtq_u32_f32(b.fVec)).val[0], + vuzpq_u8((uint8x16_t)vcvtq_u32_f32(c.fVec), + (uint8x16_t)vcvtq_u32_f32(d.fVec)).val[0]).val[0]; +} + +template<> AI /*static*/ Sk8b SkNx_cast(const Sk8i& src) { + Sk4i a, b; + SkNx_split(src, &a, &b); + uint16x4_t a16 = vqmovun_s32(a.fVec); + uint16x4_t b16 = vqmovun_s32(b.fVec); + + return vqmovn_u16(vcombine_u16(a16, b16)); +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4b& src) { + return vget_low_u16(vmovl_u8(src.fVec)); +} + +template<> AI /*static*/ Sk8h SkNx_cast(const Sk8b& src) { + return vmovl_u8(src.fVec); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4h& src) { + return vmovn_u16(vcombine_u16(src.fVec, src.fVec)); +} + +template<> AI /*static*/ Sk8b SkNx_cast(const Sk8h& src) { + return vqmovn_u16(src.fVec); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4i& src) { + uint16x4_t _16 = vqmovun_s32(src.fVec); + return vqmovn_u16(vcombine_u16(_16, _16)); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4u& src) { + uint16x4_t _16 = vqmovn_u32(src.fVec); + return vqmovn_u16(vcombine_u16(_16, _16)); +} + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4h& src) { + return vreinterpretq_s32_u32(vmovl_u16(src.fVec)); +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4i& src) { + return vmovn_u32(vreinterpretq_u32_s32(src.fVec)); +} + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4u& src) { + return vreinterpretq_s32_u32(src.fVec); +} + +AI static Sk4i Sk4f_round(const Sk4f& x) { + return vcvtq_s32_f32((x + 0.5f).fVec); +} + +} // namespace + +#endif//SkNx_neon_DEFINED diff --git a/gfx/skia/skia/include/private/SkNx_sse.h b/gfx/skia/skia/include/private/SkNx_sse.h new file mode 100644 index 0000000000..cc6ace3680 --- /dev/null +++ b/gfx/skia/skia/include/private/SkNx_sse.h @@ -0,0 +1,894 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNx_sse_DEFINED +#define SkNx_sse_DEFINED + +#include "include/core/SkTypes.h" + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + #include +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + #include +#else + #include +#endif + +// This file may assume <= SSE2, but must check SK_CPU_SSE_LEVEL for anything more recent. +// If you do, make sure this is in a static inline function... anywhere else risks violating ODR. + +namespace { // NOLINT(google-build-namespaces) + +// Emulate _mm_floor_ps() with SSE2: +// - roundtrip through integers via truncation +// - subtract 1 if that's too big (possible for negative values). +// This restricts the domain of our inputs to a maximum somehwere around 2^31. +// Seems plenty big. +AI static __m128 emulate_mm_floor_ps(__m128 v) { + __m128 roundtrip = _mm_cvtepi32_ps(_mm_cvttps_epi32(v)); + __m128 too_big = _mm_cmpgt_ps(roundtrip, v); + return _mm_sub_ps(roundtrip, _mm_and_ps(too_big, _mm_set1_ps(1.0f))); +} + +template <> +class SkNx<2, float> { +public: + AI SkNx(const __m128& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(float val) : fVec(_mm_set1_ps(val)) {} + AI static SkNx Load(const void* ptr) { + return _mm_castsi128_ps(_mm_loadl_epi64((const __m128i*)ptr)); + } + AI SkNx(float a, float b) : fVec(_mm_setr_ps(a,b,0,0)) {} + + AI void store(void* ptr) const { _mm_storel_pi((__m64*)ptr, fVec); } + + AI static void Load2(const void* ptr, SkNx* x, SkNx* y) { + const float* m = (const float*)ptr; + *x = SkNx{m[0], m[2]}; + *y = SkNx{m[1], m[3]}; + } + + AI static void Store2(void* dst, const SkNx& a, const SkNx& b) { + auto vals = _mm_unpacklo_ps(a.fVec, b.fVec); + _mm_storeu_ps((float*)dst, vals); + } + + AI static void Store3(void* dst, const SkNx& a, const SkNx& b, const SkNx& c) { + auto lo = _mm_setr_ps(a[0], b[0], c[0], a[1]), + hi = _mm_setr_ps(b[1], c[1], 0, 0); + _mm_storeu_ps((float*)dst, lo); + _mm_storel_pi(((__m64*)dst) + 2, hi); + } + + AI static void Store4(void* dst, const SkNx& a, const SkNx& b, const SkNx& c, const SkNx& d) { + auto lo = _mm_setr_ps(a[0], b[0], c[0], d[0]), + hi = _mm_setr_ps(a[1], b[1], c[1], d[1]); + _mm_storeu_ps((float*)dst, lo); + _mm_storeu_ps(((float*)dst) + 4, hi); + } + + AI SkNx operator - () const { return _mm_xor_ps(_mm_set1_ps(-0.0f), fVec); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_ps(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_ps(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return _mm_mul_ps(fVec, o.fVec); } + AI SkNx operator / (const SkNx& o) const { return _mm_div_ps(fVec, o.fVec); } + + AI SkNx operator == (const SkNx& o) const { return _mm_cmpeq_ps (fVec, o.fVec); } + AI SkNx operator != (const SkNx& o) const { return _mm_cmpneq_ps(fVec, o.fVec); } + AI SkNx operator < (const SkNx& o) const { return _mm_cmplt_ps (fVec, o.fVec); } + AI SkNx operator > (const SkNx& o) const { return _mm_cmpgt_ps (fVec, o.fVec); } + AI SkNx operator <= (const SkNx& o) const { return _mm_cmple_ps (fVec, o.fVec); } + AI SkNx operator >= (const SkNx& o) const { return _mm_cmpge_ps (fVec, o.fVec); } + + AI static SkNx Min(const SkNx& l, const SkNx& r) { return _mm_min_ps(l.fVec, r.fVec); } + AI static SkNx Max(const SkNx& l, const SkNx& r) { return _mm_max_ps(l.fVec, r.fVec); } + + AI SkNx abs() const { return _mm_andnot_ps(_mm_set1_ps(-0.0f), fVec); } + AI SkNx floor() const { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_floor_ps(fVec); + #else + return emulate_mm_floor_ps(fVec); + #endif + } + + AI SkNx sqrt() const { return _mm_sqrt_ps (fVec); } + AI SkNx rsqrt() const { return _mm_rsqrt_ps(fVec); } + AI SkNx invert() const { return _mm_rcp_ps(fVec); } + + AI float operator[](int k) const { + SkASSERT(0 <= k && k < 2); + union { __m128 v; float fs[4]; } pun = {fVec}; + return pun.fs[k&1]; + } + + AI bool allTrue() const { return 0xff == (_mm_movemask_epi8(_mm_castps_si128(fVec)) & 0xff); } + AI bool anyTrue() const { return 0x00 != (_mm_movemask_epi8(_mm_castps_si128(fVec)) & 0xff); } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_blendv_ps(e.fVec, t.fVec, fVec); + #else + return _mm_or_ps(_mm_and_ps (fVec, t.fVec), + _mm_andnot_ps(fVec, e.fVec)); + #endif + } + + __m128 fVec; +}; + +template <> +class SkNx<4, float> { +public: + AI SkNx(const __m128& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(float val) : fVec( _mm_set1_ps(val) ) {} + AI SkNx(float a, float b, float c, float d) : fVec(_mm_setr_ps(a,b,c,d)) {} + + AI static SkNx Load(const void* ptr) { return _mm_loadu_ps((const float*)ptr); } + AI void store(void* ptr) const { _mm_storeu_ps((float*)ptr, fVec); } + + AI static void Load2(const void* ptr, SkNx* x, SkNx* y) { + SkNx lo = SkNx::Load((const float*)ptr+0), + hi = SkNx::Load((const float*)ptr+4); + *x = SkNx{lo[0], lo[2], hi[0], hi[2]}; + *y = SkNx{lo[1], lo[3], hi[1], hi[3]}; + } + + AI static void Load4(const void* ptr, SkNx* r, SkNx* g, SkNx* b, SkNx* a) { + __m128 v0 = _mm_loadu_ps(((float*)ptr) + 0), + v1 = _mm_loadu_ps(((float*)ptr) + 4), + v2 = _mm_loadu_ps(((float*)ptr) + 8), + v3 = _mm_loadu_ps(((float*)ptr) + 12); + _MM_TRANSPOSE4_PS(v0, v1, v2, v3); + *r = v0; + *g = v1; + *b = v2; + *a = v3; + } + AI static void Store4(void* dst, const SkNx& r, const SkNx& g, const SkNx& b, const SkNx& a) { + __m128 v0 = r.fVec, + v1 = g.fVec, + v2 = b.fVec, + v3 = a.fVec; + _MM_TRANSPOSE4_PS(v0, v1, v2, v3); + _mm_storeu_ps(((float*) dst) + 0, v0); + _mm_storeu_ps(((float*) dst) + 4, v1); + _mm_storeu_ps(((float*) dst) + 8, v2); + _mm_storeu_ps(((float*) dst) + 12, v3); + } + + AI SkNx operator - () const { return _mm_xor_ps(_mm_set1_ps(-0.0f), fVec); } + AI SkNx operator ~ () const { return _mm_xor_ps(fVec, _mm_castsi128_ps(_mm_cmpeq_epi32(_mm_castps_si128(fVec), _mm_castps_si128(fVec)))); } + AI SkNx operator ! () const { return ~*this; } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_ps(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_ps(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return _mm_mul_ps(fVec, o.fVec); } + AI SkNx operator / (const SkNx& o) const { return _mm_div_ps(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return _mm_and_ps(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return _mm_or_ps(fVec, o.fVec); } + + AI SkNx operator == (const SkNx& o) const { return _mm_cmpeq_ps (fVec, o.fVec); } + AI SkNx operator != (const SkNx& o) const { return _mm_cmpneq_ps(fVec, o.fVec); } + AI SkNx operator < (const SkNx& o) const { return _mm_cmplt_ps (fVec, o.fVec); } + AI SkNx operator > (const SkNx& o) const { return _mm_cmpgt_ps (fVec, o.fVec); } + AI SkNx operator <= (const SkNx& o) const { return _mm_cmple_ps (fVec, o.fVec); } + AI SkNx operator >= (const SkNx& o) const { return _mm_cmpge_ps (fVec, o.fVec); } + + AI static SkNx Min(const SkNx& l, const SkNx& r) { return _mm_min_ps(l.fVec, r.fVec); } + AI static SkNx Max(const SkNx& l, const SkNx& r) { return _mm_max_ps(l.fVec, r.fVec); } + + AI SkNx abs() const { return _mm_andnot_ps(_mm_set1_ps(-0.0f), fVec); } + AI SkNx floor() const { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_floor_ps(fVec); + #else + return emulate_mm_floor_ps(fVec); + #endif + } + + AI SkNx sqrt() const { return _mm_sqrt_ps (fVec); } + AI SkNx rsqrt() const { return _mm_rsqrt_ps(fVec); } + AI SkNx invert() const { return _mm_rcp_ps(fVec); } + + AI float operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { __m128 v; float fs[4]; } pun = {fVec}; + return pun.fs[k&3]; + } + + AI float min() const { + SkNx min = Min(*this, _mm_shuffle_ps(fVec, fVec, _MM_SHUFFLE(2,3,0,1))); + min = Min(min, _mm_shuffle_ps(min.fVec, min.fVec, _MM_SHUFFLE(0,1,2,3))); + return min[0]; + } + + AI float max() const { + SkNx max = Max(*this, _mm_shuffle_ps(fVec, fVec, _MM_SHUFFLE(2,3,0,1))); + max = Max(max, _mm_shuffle_ps(max.fVec, max.fVec, _MM_SHUFFLE(0,1,2,3))); + return max[0]; + } + + AI bool allTrue() const { return 0xffff == _mm_movemask_epi8(_mm_castps_si128(fVec)); } + AI bool anyTrue() const { return 0x0000 != _mm_movemask_epi8(_mm_castps_si128(fVec)); } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_blendv_ps(e.fVec, t.fVec, fVec); + #else + return _mm_or_ps(_mm_and_ps (fVec, t.fVec), + _mm_andnot_ps(fVec, e.fVec)); + #endif + } + + operator __m128() const { return fVec; } + + __m128 fVec; +}; + +AI static __m128i mullo32(__m128i a, __m128i b) { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_mullo_epi32(a, b); +#else + __m128i mul20 = _mm_mul_epu32(a, b), + mul31 = _mm_mul_epu32(_mm_srli_si128(a, 4), _mm_srli_si128(b, 4)); + return _mm_unpacklo_epi32(_mm_shuffle_epi32(mul20, _MM_SHUFFLE(0,0,2,0)), + _mm_shuffle_epi32(mul31, _MM_SHUFFLE(0,0,2,0))); +#endif +} + +template <> +class SkNx<4, int32_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(int32_t val) : fVec(_mm_set1_epi32(val)) {} + AI static SkNx Load(const void* ptr) { return _mm_loadu_si128((const __m128i*)ptr); } + AI SkNx(int32_t a, int32_t b, int32_t c, int32_t d) : fVec(_mm_setr_epi32(a,b,c,d)) {} + + AI void store(void* ptr) const { _mm_storeu_si128((__m128i*)ptr, fVec); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return mullo32(fVec, o.fVec); } + + AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return _mm_or_si128(fVec, o.fVec); } + AI SkNx operator ^ (const SkNx& o) const { return _mm_xor_si128(fVec, o.fVec); } + + AI SkNx operator ~ () const { return _mm_xor_si128(fVec, _mm_cmpeq_epi32(fVec, fVec)); } + + AI SkNx operator << (int bits) const { return _mm_slli_epi32(fVec, bits); } + AI SkNx operator >> (int bits) const { return _mm_srai_epi32(fVec, bits); } + + AI SkNx operator == (const SkNx& o) const { return _mm_cmpeq_epi32 (fVec, o.fVec); } + AI SkNx operator < (const SkNx& o) const { return _mm_cmplt_epi32 (fVec, o.fVec); } + AI SkNx operator > (const SkNx& o) const { return _mm_cmpgt_epi32 (fVec, o.fVec); } + + AI int32_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { __m128i v; int32_t is[4]; } pun = {fVec}; + return pun.is[k&3]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_blendv_epi8(e.fVec, t.fVec, fVec); + #else + return _mm_or_si128(_mm_and_si128 (fVec, t.fVec), + _mm_andnot_si128(fVec, e.fVec)); + #endif + } + + AI SkNx abs() const { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + return _mm_abs_epi32(fVec); +#else + SkNx mask = (*this) >> 31; + return (mask ^ (*this)) - mask; +#endif + } + + AI static SkNx Min(const SkNx& x, const SkNx& y) { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_min_epi32(x.fVec, y.fVec); +#else + return (x < y).thenElse(x, y); +#endif + } + + AI static SkNx Max(const SkNx& x, const SkNx& y) { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_max_epi32(x.fVec, y.fVec); +#else + return (x > y).thenElse(x, y); +#endif + } + + operator __m128i() const { return fVec; } + + __m128i fVec; +}; + +template <> +class SkNx<2, uint32_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint32_t val) : fVec(_mm_set1_epi32(val)) {} + AI static SkNx Load(const void* ptr) { return _mm_loadl_epi64((const __m128i*)ptr); } + AI SkNx(uint32_t a, uint32_t b) : fVec(_mm_setr_epi32(a,b,0,0)) {} + + AI void store(void* ptr) const { _mm_storel_epi64((__m128i*)ptr, fVec); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return mullo32(fVec, o.fVec); } + + AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return _mm_or_si128(fVec, o.fVec); } + AI SkNx operator ^ (const SkNx& o) const { return _mm_xor_si128(fVec, o.fVec); } + + AI SkNx operator << (int bits) const { return _mm_slli_epi32(fVec, bits); } + AI SkNx operator >> (int bits) const { return _mm_srli_epi32(fVec, bits); } + + AI SkNx operator == (const SkNx& o) const { return _mm_cmpeq_epi32 (fVec, o.fVec); } + AI SkNx operator != (const SkNx& o) const { return (*this == o) ^ 0xffffffff; } + // operator < and > take a little extra fiddling to make work for unsigned ints. + + AI uint32_t operator[](int k) const { + SkASSERT(0 <= k && k < 2); + union { __m128i v; uint32_t us[4]; } pun = {fVec}; + return pun.us[k&1]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_blendv_epi8(e.fVec, t.fVec, fVec); +#else + return _mm_or_si128(_mm_and_si128 (fVec, t.fVec), + _mm_andnot_si128(fVec, e.fVec)); +#endif + } + + AI bool allTrue() const { return 0xff == (_mm_movemask_epi8(fVec) & 0xff); } + + __m128i fVec; +}; + +template <> +class SkNx<4, uint32_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint32_t val) : fVec(_mm_set1_epi32(val)) {} + AI static SkNx Load(const void* ptr) { return _mm_loadu_si128((const __m128i*)ptr); } + AI SkNx(uint32_t a, uint32_t b, uint32_t c, uint32_t d) : fVec(_mm_setr_epi32(a,b,c,d)) {} + + AI void store(void* ptr) const { _mm_storeu_si128((__m128i*)ptr, fVec); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi32(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi32(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return mullo32(fVec, o.fVec); } + + AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return _mm_or_si128(fVec, o.fVec); } + AI SkNx operator ^ (const SkNx& o) const { return _mm_xor_si128(fVec, o.fVec); } + + AI SkNx operator ~ () const { return _mm_xor_si128(fVec, _mm_cmpeq_epi32(fVec, fVec)); } + + AI SkNx operator << (int bits) const { return _mm_slli_epi32(fVec, bits); } + AI SkNx operator >> (int bits) const { return _mm_srli_epi32(fVec, bits); } + + AI SkNx operator == (const SkNx& o) const { return _mm_cmpeq_epi32 (fVec, o.fVec); } + AI SkNx operator != (const SkNx& o) const { return (*this == o) ^ 0xffffffff; } + + // operator < and > take a little extra fiddling to make work for unsigned ints. + + AI uint32_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { __m128i v; uint32_t us[4]; } pun = {fVec}; + return pun.us[k&3]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_blendv_epi8(e.fVec, t.fVec, fVec); + #else + return _mm_or_si128(_mm_and_si128 (fVec, t.fVec), + _mm_andnot_si128(fVec, e.fVec)); + #endif + } + + AI SkNx mulHi(SkNx m) const { + SkNx v20{_mm_mul_epu32(m.fVec, fVec)}; + SkNx v31{_mm_mul_epu32(_mm_srli_si128(m.fVec, 4), _mm_srli_si128(fVec, 4))}; + + return SkNx{v20[1], v31[1], v20[3], v31[3]}; + } + + operator __m128i() const { return fVec; } + + __m128i fVec; +}; + +template <> +class SkNx<4, uint16_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint16_t val) : fVec(_mm_set1_epi16(val)) {} + AI SkNx(uint16_t a, uint16_t b, uint16_t c, uint16_t d) + : fVec(_mm_setr_epi16(a,b,c,d,0,0,0,0)) {} + + AI static SkNx Load(const void* ptr) { return _mm_loadl_epi64((const __m128i*)ptr); } + AI void store(void* ptr) const { _mm_storel_epi64((__m128i*)ptr, fVec); } + + AI static void Load4(const void* ptr, SkNx* r, SkNx* g, SkNx* b, SkNx* a) { + __m128i lo = _mm_loadu_si128(((__m128i*)ptr) + 0), + hi = _mm_loadu_si128(((__m128i*)ptr) + 1); + __m128i even = _mm_unpacklo_epi16(lo, hi), // r0 r2 g0 g2 b0 b2 a0 a2 + odd = _mm_unpackhi_epi16(lo, hi); // r1 r3 ... + __m128i rg = _mm_unpacklo_epi16(even, odd), // r0 r1 r2 r3 g0 g1 g2 g3 + ba = _mm_unpackhi_epi16(even, odd); // b0 b1 ... a0 a1 ... + *r = rg; + *g = _mm_srli_si128(rg, 8); + *b = ba; + *a = _mm_srli_si128(ba, 8); + } + AI static void Load3(const void* ptr, SkNx* r, SkNx* g, SkNx* b) { + // The idea here is to get 4 vectors that are R G B _ _ _ _ _. + // The second load is at a funny location to make sure we don't read past + // the bounds of memory. This is fine, we just need to shift it a little bit. + const uint8_t* ptr8 = (const uint8_t*) ptr; + __m128i rgb0 = _mm_loadu_si128((const __m128i*) (ptr8 + 0)); + __m128i rgb1 = _mm_srli_si128(rgb0, 3*2); + __m128i rgb2 = _mm_srli_si128(_mm_loadu_si128((const __m128i*) (ptr8 + 4*2)), 2*2); + __m128i rgb3 = _mm_srli_si128(rgb2, 3*2); + + __m128i rrggbb01 = _mm_unpacklo_epi16(rgb0, rgb1); + __m128i rrggbb23 = _mm_unpacklo_epi16(rgb2, rgb3); + *r = _mm_unpacklo_epi32(rrggbb01, rrggbb23); + *g = _mm_srli_si128(r->fVec, 4*2); + *b = _mm_unpackhi_epi32(rrggbb01, rrggbb23); + } + AI static void Store4(void* dst, const SkNx& r, const SkNx& g, const SkNx& b, const SkNx& a) { + __m128i rg = _mm_unpacklo_epi16(r.fVec, g.fVec); + __m128i ba = _mm_unpacklo_epi16(b.fVec, a.fVec); + __m128i lo = _mm_unpacklo_epi32(rg, ba); + __m128i hi = _mm_unpackhi_epi32(rg, ba); + _mm_storeu_si128(((__m128i*) dst) + 0, lo); + _mm_storeu_si128(((__m128i*) dst) + 1, hi); + } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi16(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi16(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return _mm_mullo_epi16(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return _mm_or_si128(fVec, o.fVec); } + + AI SkNx operator << (int bits) const { return _mm_slli_epi16(fVec, bits); } + AI SkNx operator >> (int bits) const { return _mm_srli_epi16(fVec, bits); } + + AI uint16_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { __m128i v; uint16_t us[8]; } pun = {fVec}; + return pun.us[k&3]; + } + + operator __m128i() const { return fVec; } + + __m128i fVec; +}; + +template <> +class SkNx<8, uint16_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint16_t val) : fVec(_mm_set1_epi16(val)) {} + AI SkNx(uint16_t a, uint16_t b, uint16_t c, uint16_t d, + uint16_t e, uint16_t f, uint16_t g, uint16_t h) + : fVec(_mm_setr_epi16(a,b,c,d,e,f,g,h)) {} + + AI static SkNx Load(const void* ptr) { return _mm_loadu_si128((const __m128i*)ptr); } + AI void store(void* ptr) const { _mm_storeu_si128((__m128i*)ptr, fVec); } + + AI static void Load4(const void* ptr, SkNx* r, SkNx* g, SkNx* b, SkNx* a) { + __m128i _01 = _mm_loadu_si128(((__m128i*)ptr) + 0), + _23 = _mm_loadu_si128(((__m128i*)ptr) + 1), + _45 = _mm_loadu_si128(((__m128i*)ptr) + 2), + _67 = _mm_loadu_si128(((__m128i*)ptr) + 3); + + __m128i _02 = _mm_unpacklo_epi16(_01, _23), // r0 r2 g0 g2 b0 b2 a0 a2 + _13 = _mm_unpackhi_epi16(_01, _23), // r1 r3 g1 g3 b1 b3 a1 a3 + _46 = _mm_unpacklo_epi16(_45, _67), + _57 = _mm_unpackhi_epi16(_45, _67); + + __m128i rg0123 = _mm_unpacklo_epi16(_02, _13), // r0 r1 r2 r3 g0 g1 g2 g3 + ba0123 = _mm_unpackhi_epi16(_02, _13), // b0 b1 b2 b3 a0 a1 a2 a3 + rg4567 = _mm_unpacklo_epi16(_46, _57), + ba4567 = _mm_unpackhi_epi16(_46, _57); + + *r = _mm_unpacklo_epi64(rg0123, rg4567); + *g = _mm_unpackhi_epi64(rg0123, rg4567); + *b = _mm_unpacklo_epi64(ba0123, ba4567); + *a = _mm_unpackhi_epi64(ba0123, ba4567); + } + AI static void Load3(const void* ptr, SkNx* r, SkNx* g, SkNx* b) { + const uint8_t* ptr8 = (const uint8_t*) ptr; + __m128i rgb0 = _mm_loadu_si128((const __m128i*) (ptr8 + 0*2)); + __m128i rgb1 = _mm_srli_si128(rgb0, 3*2); + __m128i rgb2 = _mm_loadu_si128((const __m128i*) (ptr8 + 6*2)); + __m128i rgb3 = _mm_srli_si128(rgb2, 3*2); + __m128i rgb4 = _mm_loadu_si128((const __m128i*) (ptr8 + 12*2)); + __m128i rgb5 = _mm_srli_si128(rgb4, 3*2); + __m128i rgb6 = _mm_srli_si128(_mm_loadu_si128((const __m128i*) (ptr8 + 16*2)), 2*2); + __m128i rgb7 = _mm_srli_si128(rgb6, 3*2); + + __m128i rgb01 = _mm_unpacklo_epi16(rgb0, rgb1); + __m128i rgb23 = _mm_unpacklo_epi16(rgb2, rgb3); + __m128i rgb45 = _mm_unpacklo_epi16(rgb4, rgb5); + __m128i rgb67 = _mm_unpacklo_epi16(rgb6, rgb7); + + __m128i rg03 = _mm_unpacklo_epi32(rgb01, rgb23); + __m128i bx03 = _mm_unpackhi_epi32(rgb01, rgb23); + __m128i rg47 = _mm_unpacklo_epi32(rgb45, rgb67); + __m128i bx47 = _mm_unpackhi_epi32(rgb45, rgb67); + + *r = _mm_unpacklo_epi64(rg03, rg47); + *g = _mm_unpackhi_epi64(rg03, rg47); + *b = _mm_unpacklo_epi64(bx03, bx47); + } + AI static void Store4(void* ptr, const SkNx& r, const SkNx& g, const SkNx& b, const SkNx& a) { + __m128i rg0123 = _mm_unpacklo_epi16(r.fVec, g.fVec), // r0 g0 r1 g1 r2 g2 r3 g3 + rg4567 = _mm_unpackhi_epi16(r.fVec, g.fVec), // r4 g4 r5 g5 r6 g6 r7 g7 + ba0123 = _mm_unpacklo_epi16(b.fVec, a.fVec), + ba4567 = _mm_unpackhi_epi16(b.fVec, a.fVec); + + _mm_storeu_si128((__m128i*)ptr + 0, _mm_unpacklo_epi32(rg0123, ba0123)); + _mm_storeu_si128((__m128i*)ptr + 1, _mm_unpackhi_epi32(rg0123, ba0123)); + _mm_storeu_si128((__m128i*)ptr + 2, _mm_unpacklo_epi32(rg4567, ba4567)); + _mm_storeu_si128((__m128i*)ptr + 3, _mm_unpackhi_epi32(rg4567, ba4567)); + } + + AI SkNx operator ~ () const { return _mm_xor_si128(fVec, _mm_cmpeq_epi16(fVec, fVec)); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi16(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi16(fVec, o.fVec); } + AI SkNx operator * (const SkNx& o) const { return _mm_mullo_epi16(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); } + AI SkNx operator | (const SkNx& o) const { return _mm_or_si128(fVec, o.fVec); } + AI SkNx operator < (const SkNx& o) const { + auto flip = _mm_set1_epi16(short(0x8000)); + return _mm_cmplt_epi16(_mm_xor_si128(flip, fVec), _mm_xor_si128(flip, o.fVec)); + } + AI SkNx operator << (int bits) const { return _mm_slli_epi16(fVec, bits); } + AI SkNx operator >> (int bits) const { return _mm_srli_epi16(fVec, bits); } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { + // No unsigned _mm_min_epu16, so we'll shift into a space where we can use the + // signed version, _mm_min_epi16, then shift back. + const uint16_t top = 0x8000; // Keep this separate from _mm_set1_epi16 or MSVC will whine. + const __m128i top_8x = _mm_set1_epi16(top); + return _mm_add_epi8(top_8x, _mm_min_epi16(_mm_sub_epi8(a.fVec, top_8x), + _mm_sub_epi8(b.fVec, top_8x))); + } + AI static SkNx Max(const SkNx& a, const SkNx& b) { + const uint16_t top = 0x8000; + const __m128i top_8x = _mm_set1_epi16(top); + return _mm_add_epi8(top_8x, _mm_max_epi16(_mm_sub_epi8(a.fVec, top_8x), + _mm_sub_epi8(b.fVec, top_8x))); + } + + AI SkNx mulHi(const SkNx& m) const { + return _mm_mulhi_epu16(fVec, m.fVec); + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return _mm_or_si128(_mm_and_si128 (fVec, t.fVec), + _mm_andnot_si128(fVec, e.fVec)); + } + + AI uint16_t operator[](int k) const { + SkASSERT(0 <= k && k < 8); + union { __m128i v; uint16_t us[8]; } pun = {fVec}; + return pun.us[k&7]; + } + + operator __m128i() const { return fVec; } + + __m128i fVec; +}; + +template <> +class SkNx<4, uint8_t> { +public: + AI SkNx() {} + AI SkNx(uint8_t val) : fVec(_mm_set1_epi8(val)) {} + AI SkNx(const __m128i& vec) : fVec(vec) {} + AI SkNx(uint8_t a, uint8_t b, uint8_t c, uint8_t d) + : fVec(_mm_setr_epi8(a,b,c,d, 0,0,0,0, 0,0,0,0, 0,0,0,0)) {} + + AI static SkNx Load(const void* ptr) { return _mm_cvtsi32_si128(*(const int*)ptr); } + AI void store(void* ptr) const { *(int*)ptr = _mm_cvtsi128_si32(fVec); } + + AI uint8_t operator[](int k) const { + SkASSERT(0 <= k && k < 4); + union { __m128i v; uint8_t us[16]; } pun = {fVec}; + return pun.us[k&3]; + } + + // TODO as needed + + __m128i fVec; +}; + +template <> +class SkNx<8, uint8_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint8_t val) : fVec(_mm_set1_epi8(val)) {} + AI static SkNx Load(const void* ptr) { return _mm_loadl_epi64((const __m128i*)ptr); } + AI SkNx(uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h) + : fVec(_mm_setr_epi8(a,b,c,d, e,f,g,h, 0,0,0,0, 0,0,0,0)) {} + + AI void store(void* ptr) const {_mm_storel_epi64((__m128i*)ptr, fVec);} + + AI SkNx saturatedAdd(const SkNx& o) const { return _mm_adds_epu8(fVec, o.fVec); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi8(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi8(fVec, o.fVec); } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return _mm_min_epu8(a.fVec, b.fVec); } + AI SkNx operator < (const SkNx& o) const { + // There's no unsigned _mm_cmplt_epu8, so we flip the sign bits then use a signed compare. + auto flip = _mm_set1_epi8(char(0x80)); + return _mm_cmplt_epi8(_mm_xor_si128(flip, fVec), _mm_xor_si128(flip, o.fVec)); + } + + AI uint8_t operator[](int k) const { + SkASSERT(0 <= k && k < 16); + union { __m128i v; uint8_t us[16]; } pun = {fVec}; + return pun.us[k&15]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return _mm_or_si128(_mm_and_si128 (fVec, t.fVec), + _mm_andnot_si128(fVec, e.fVec)); + } + + __m128i fVec; +}; + +template <> +class SkNx<16, uint8_t> { +public: + AI SkNx(const __m128i& vec) : fVec(vec) {} + + AI SkNx() {} + AI SkNx(uint8_t val) : fVec(_mm_set1_epi8(val)) {} + AI static SkNx Load(const void* ptr) { return _mm_loadu_si128((const __m128i*)ptr); } + AI SkNx(uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h, + uint8_t i, uint8_t j, uint8_t k, uint8_t l, + uint8_t m, uint8_t n, uint8_t o, uint8_t p) + : fVec(_mm_setr_epi8(a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p)) {} + + AI void store(void* ptr) const { _mm_storeu_si128((__m128i*)ptr, fVec); } + + AI SkNx saturatedAdd(const SkNx& o) const { return _mm_adds_epu8(fVec, o.fVec); } + + AI SkNx operator + (const SkNx& o) const { return _mm_add_epi8(fVec, o.fVec); } + AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi8(fVec, o.fVec); } + AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); } + + AI static SkNx Min(const SkNx& a, const SkNx& b) { return _mm_min_epu8(a.fVec, b.fVec); } + AI SkNx operator < (const SkNx& o) const { + // There's no unsigned _mm_cmplt_epu8, so we flip the sign bits then use a signed compare. + auto flip = _mm_set1_epi8(char(0x80)); + return _mm_cmplt_epi8(_mm_xor_si128(flip, fVec), _mm_xor_si128(flip, o.fVec)); + } + + AI uint8_t operator[](int k) const { + SkASSERT(0 <= k && k < 16); + union { __m128i v; uint8_t us[16]; } pun = {fVec}; + return pun.us[k&15]; + } + + AI SkNx thenElse(const SkNx& t, const SkNx& e) const { + return _mm_or_si128(_mm_and_si128 (fVec, t.fVec), + _mm_andnot_si128(fVec, e.fVec)); + } + + __m128i fVec; +}; + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4i& src) { + return _mm_cvtepi32_ps(src.fVec); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4u& src) { + return SkNx_cast(Sk4i::Load(&src)); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const SkNx<4, uint64_t>& src) { + return Sk4f(float(int32_t(src[0])), float(int32_t(src[1])), float(int32_t(src[2])), float(int32_t(src[3]))); +} + +template <> AI /*static*/ Sk4i SkNx_cast(const Sk4f& src) { + return _mm_cvttps_epi32(src.fVec); +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4i& src) { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_packus_epi32(src.fVec, src.fVec); +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + // With SSSE3, we can just shuffle the low 2 bytes from each lane right into place. + const int _ = ~0; + return _mm_shuffle_epi8(src.fVec, _mm_setr_epi8(0,1, 4,5, 8,9, 12,13, _,_,_,_,_,_,_,_)); +#else + // With SSE2, we have to sign extend our input, making _mm_packs_epi32 do the pack we want. + __m128i x = _mm_srai_epi32(_mm_slli_epi32(src.fVec, 16), 16); + return _mm_packs_epi32(x,x); +#endif +} + +template<> AI /*static*/ Sk8h SkNx_cast(const Sk8i& src) { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + return _mm_packus_epi32(src.fLo.fVec, src.fHi.fVec); +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + // With SSSE3, we can just shuffle the low 2 bytes from each lane right into place. + const int _ = ~0; + __m128i mask = _mm_setr_epi8(0,1, 4,5, 8,9, 12,13, _,_,_,_,_,_,_,_); + return _mm_unpacklo_epi64(_mm_shuffle_epi8(src.fLo.fVec, mask), + _mm_shuffle_epi8(src.fHi.fVec, mask)); +#else + // With SSE2, we have to sign extend our input, making _mm_packs_epi32 do the pack we want. + __m128i x = _mm_srai_epi32(_mm_slli_epi32(src.fLo.fVec, 16), 16); + __m128i y = _mm_srai_epi32(_mm_slli_epi32(src.fHi.fVec, 16), 16); + return _mm_packs_epi32(x,y); +#endif +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4f& src) { + return SkNx_cast(SkNx_cast(src)); +} + +template<> AI /*static*/ Sk8h SkNx_cast(const Sk8f& src) { + return SkNx_cast(SkNx_cast(src)); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4f& src) { + auto _32 = _mm_cvttps_epi32(src.fVec); +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + const int _ = ~0; + return _mm_shuffle_epi8(_32, _mm_setr_epi8(0,4,8,12, _,_,_,_, _,_,_,_, _,_,_,_)); +#else + auto _16 = _mm_packus_epi16(_32, _32); + return _mm_packus_epi16(_16, _16); +#endif +} + +template<> AI /*static*/ Sk4u SkNx_cast(const Sk4b& src) { +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + const int _ = ~0; + return _mm_shuffle_epi8(src.fVec, _mm_setr_epi8(0,_,_,_, 1,_,_,_, 2,_,_,_, 3,_,_,_)); +#else + auto _16 = _mm_unpacklo_epi8(src.fVec, _mm_setzero_si128()); + return _mm_unpacklo_epi16(_16, _mm_setzero_si128()); +#endif +} + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4b& src) { + return SkNx_cast(src).fVec; +} + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4b& src) { + return _mm_cvtepi32_ps(SkNx_cast(src).fVec); +} + +template<> AI /*static*/ Sk4f SkNx_cast(const Sk4h& src) { + auto _32 = _mm_unpacklo_epi16(src.fVec, _mm_setzero_si128()); + return _mm_cvtepi32_ps(_32); +} + +template<> AI /*static*/ Sk8b SkNx_cast(const Sk8i& src) { + Sk4i lo, hi; + SkNx_split(src, &lo, &hi); + + auto t = _mm_packs_epi32(lo.fVec, hi.fVec); + return _mm_packus_epi16(t, t); +} + +template<> AI /*static*/ Sk16b SkNx_cast(const Sk16f& src) { + Sk8f ab, cd; + SkNx_split(src, &ab, &cd); + + Sk4f a,b,c,d; + SkNx_split(ab, &a, &b); + SkNx_split(cd, &c, &d); + + return _mm_packus_epi16(_mm_packus_epi16(_mm_cvttps_epi32(a.fVec), + _mm_cvttps_epi32(b.fVec)), + _mm_packus_epi16(_mm_cvttps_epi32(c.fVec), + _mm_cvttps_epi32(d.fVec))); +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4b& src) { + return _mm_unpacklo_epi8(src.fVec, _mm_setzero_si128()); +} + +template<> AI /*static*/ Sk8h SkNx_cast(const Sk8b& src) { + return _mm_unpacklo_epi8(src.fVec, _mm_setzero_si128()); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4h& src) { + return _mm_packus_epi16(src.fVec, src.fVec); +} + +template<> AI /*static*/ Sk8b SkNx_cast(const Sk8h& src) { + return _mm_packus_epi16(src.fVec, src.fVec); +} + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4h& src) { + return _mm_unpacklo_epi16(src.fVec, _mm_setzero_si128()); +} + +template<> AI /*static*/ Sk4u SkNx_cast(const Sk4h& src) { + return _mm_unpacklo_epi16(src.fVec, _mm_setzero_si128()); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4i& src) { + return _mm_packus_epi16(_mm_packus_epi16(src.fVec, src.fVec), src.fVec); +} + +template<> AI /*static*/ Sk4b SkNx_cast(const Sk4u& src) { + return _mm_packus_epi16(_mm_packus_epi16(src.fVec, src.fVec), src.fVec); +} + +template<> AI /*static*/ Sk4i SkNx_cast(const Sk4u& src) { + return src.fVec; +} + +template<> AI /*static*/ Sk4u SkNx_cast(const Sk4i& src) { + return src.fVec; +} + +template<> AI /*static*/ Sk4h SkNx_cast(const Sk4u& src) { + return SkNx_cast(SkNx_cast(src)); +} + +template<> AI /*static*/ Sk8h SkNx_cast(const Sk8u& src) { + return SkNx_cast(SkNx_cast(src)); +} + +template<> AI /*static*/ Sk8u SkNx_cast(const Sk8h& src) { + return Sk8u(_mm_unpacklo_epi16(src.fVec, _mm_setzero_si128()), + _mm_unpackhi_epi16(src.fVec, _mm_setzero_si128())); +} + +AI static Sk4i Sk4f_round(const Sk4f& x) { + return _mm_cvtps_epi32(x.fVec); +} + +} // namespace + +#endif//SkNx_sse_DEFINED diff --git a/gfx/skia/skia/include/private/SkOnce.h b/gfx/skia/skia/include/private/SkOnce.h new file mode 100644 index 0000000000..662bffb1cd --- /dev/null +++ b/gfx/skia/skia/include/private/SkOnce.h @@ -0,0 +1,50 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOnce_DEFINED +#define SkOnce_DEFINED + +#include +#include + +// SkOnce provides call-once guarantees for Skia, much like std::once_flag/std::call_once(). +// +// There should be no particularly error-prone gotcha use cases when using SkOnce. +// It works correctly as a class member, a local, a global, a function-scoped static, whatever. + +class SkOnce { +public: + constexpr SkOnce() = default; + + template + void operator()(Fn&& fn, Args&&... args) { + auto state = fState.load(std::memory_order_acquire); + + if (state == Done) { + return; + } + + // If it looks like no one has started calling fn(), try to claim that job. + if (state == NotStarted && fState.compare_exchange_strong(state, Claimed, + std::memory_order_relaxed, + std::memory_order_relaxed)) { + // Great! We'll run fn() then notify the other threads by releasing Done into fState. + fn(std::forward(args)...); + return fState.store(Done, std::memory_order_release); + } + + // Some other thread is calling fn(). + // We'll just spin here acquiring until it releases Done into fState. + while (fState.load(std::memory_order_acquire) != Done) { /*spin*/ } + } + +private: + enum State : uint8_t { NotStarted, Claimed, Done}; + std::atomic fState{NotStarted}; +}; + +#endif // SkOnce_DEFINED diff --git a/gfx/skia/skia/include/private/SkPathRef.h b/gfx/skia/skia/include/private/SkPathRef.h new file mode 100644 index 0000000000..4775ad08ff --- /dev/null +++ b/gfx/skia/skia/include/private/SkPathRef.h @@ -0,0 +1,492 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathRef_DEFINED +#define SkPathRef_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include +#include + +class SkRBuffer; +class SkWBuffer; + +/** + * Holds the path verbs and points. It is versioned by a generation ID. None of its public methods + * modify the contents. To modify or append to the verbs/points wrap the SkPathRef in an + * SkPathRef::Editor object. Installing the editor resets the generation ID. It also performs + * copy-on-write if the SkPathRef is shared by multiple SkPaths. The caller passes the Editor's + * constructor a pointer to a sk_sp, which may be updated to point to a new SkPathRef + * after the editor's constructor returns. + * + * The points and verbs are stored in a single allocation. The points are at the begining of the + * allocation while the verbs are stored at end of the allocation, in reverse order. Thus the points + * and verbs both grow into the middle of the allocation until the meet. To access verb i in the + * verb array use ref.verbs()[~i] (because verbs() returns a pointer just beyond the first + * logical verb or the last verb in memory). + */ + +class SK_API SkPathRef final : public SkNVRefCnt { +public: + class Editor { + public: + Editor(sk_sp* pathRef, + int incReserveVerbs = 0, + int incReservePoints = 0); + + ~Editor() { SkDEBUGCODE(fPathRef->fEditorsAttached--;) } + + /** + * Returns the array of points. + */ + SkPoint* writablePoints() { return fPathRef->getWritablePoints(); } + const SkPoint* points() const { return fPathRef->points(); } + + /** + * Gets the ith point. Shortcut for this->points() + i + */ + SkPoint* atPoint(int i) { return fPathRef->getWritablePoints() + i; } + const SkPoint* atPoint(int i) const { return &fPathRef->fPoints[i]; } + + /** + * Adds the verb and allocates space for the number of points indicated by the verb. The + * return value is a pointer to where the points for the verb should be written. + * 'weight' is only used if 'verb' is kConic_Verb + */ + SkPoint* growForVerb(int /*SkPath::Verb*/ verb, SkScalar weight = 0) { + SkDEBUGCODE(fPathRef->validate();) + return fPathRef->growForVerb(verb, weight); + } + + /** + * Allocates space for multiple instances of a particular verb and the + * requisite points & weights. + * The return pointer points at the first new point (indexed normally []). + * If 'verb' is kConic_Verb, 'weights' will return a pointer to the + * space for the conic weights (indexed normally). + */ + SkPoint* growForRepeatedVerb(int /*SkPath::Verb*/ verb, + int numVbs, + SkScalar** weights = nullptr) { + return fPathRef->growForRepeatedVerb(verb, numVbs, weights); + } + + /** + * Resets the path ref to a new verb and point count. The new verbs and points are + * uninitialized. + */ + void resetToSize(int newVerbCnt, int newPointCnt, int newConicCount) { + fPathRef->resetToSize(newVerbCnt, newPointCnt, newConicCount); + } + + /** + * Gets the path ref that is wrapped in the Editor. + */ + SkPathRef* pathRef() { return fPathRef; } + + void setIsOval(bool isOval, bool isCCW, unsigned start) { + fPathRef->setIsOval(isOval, isCCW, start); + } + + void setIsRRect(bool isRRect, bool isCCW, unsigned start) { + fPathRef->setIsRRect(isRRect, isCCW, start); + } + + void setBounds(const SkRect& rect) { fPathRef->setBounds(rect); } + + private: + SkPathRef* fPathRef; + }; + + class SK_API Iter { + public: + Iter(); + Iter(const SkPathRef&); + + void setPathRef(const SkPathRef&); + + /** Return the next verb in this iteration of the path. When all + segments have been visited, return kDone_Verb. + + If any point in the path is non-finite, return kDone_Verb immediately. + + @param pts The points representing the current verb and/or segment + This must not be NULL. + @return The verb for the current segment + */ + uint8_t next(SkPoint pts[4]); + uint8_t peek() const; + + SkScalar conicWeight() const { return *fConicWeights; } + + private: + const SkPoint* fPts; + const uint8_t* fVerbs; + const uint8_t* fVerbStop; + const SkScalar* fConicWeights; + }; + +public: + /** + * Gets a path ref with no verbs or points. + */ + static SkPathRef* CreateEmpty(); + + /** + * Returns true if all of the points in this path are finite, meaning there + * are no infinities and no NaNs. + */ + bool isFinite() const { + if (fBoundsIsDirty) { + this->computeBounds(); + } + return SkToBool(fIsFinite); + } + + /** + * Returns a mask, where each bit corresponding to a SegmentMask is + * set if the path contains 1 or more segments of that type. + * Returns 0 for an empty path (no segments). + */ + uint32_t getSegmentMasks() const { return fSegmentMask; } + + /** Returns true if the path is an oval. + * + * @param rect returns the bounding rect of this oval. It's a circle + * if the height and width are the same. + * @param isCCW is the oval CCW (or CW if false). + * @param start indicates where the contour starts on the oval (see + * SkPath::addOval for intepretation of the index). + * + * @return true if this path is an oval. + * Tracking whether a path is an oval is considered an + * optimization for performance and so some paths that are in + * fact ovals can report false. + */ + bool isOval(SkRect* rect, bool* isCCW, unsigned* start) const { + if (fIsOval) { + if (rect) { + *rect = this->getBounds(); + } + if (isCCW) { + *isCCW = SkToBool(fRRectOrOvalIsCCW); + } + if (start) { + *start = fRRectOrOvalStartIdx; + } + } + + return SkToBool(fIsOval); + } + + bool isRRect(SkRRect* rrect, bool* isCCW, unsigned* start) const { + if (fIsRRect) { + if (rrect) { + *rrect = this->getRRect(); + } + if (isCCW) { + *isCCW = SkToBool(fRRectOrOvalIsCCW); + } + if (start) { + *start = fRRectOrOvalStartIdx; + } + } + return SkToBool(fIsRRect); + } + + + bool hasComputedBounds() const { + return !fBoundsIsDirty; + } + + /** Returns the bounds of the path's points. If the path contains 0 or 1 + points, the bounds is set to (0,0,0,0), and isEmpty() will return true. + Note: this bounds may be larger than the actual shape, since curves + do not extend as far as their control points. + */ + const SkRect& getBounds() const { + if (fBoundsIsDirty) { + this->computeBounds(); + } + return fBounds; + } + + SkRRect getRRect() const; + + /** + * Transforms a path ref by a matrix, allocating a new one only if necessary. + */ + static void CreateTransformedCopy(sk_sp* dst, + const SkPathRef& src, + const SkMatrix& matrix); + + // static SkPathRef* CreateFromBuffer(SkRBuffer* buffer); + + /** + * Rollsback a path ref to zero verbs and points with the assumption that the path ref will be + * repopulated with approximately the same number of verbs and points. A new path ref is created + * only if necessary. + */ + static void Rewind(sk_sp* pathRef); + + ~SkPathRef(); + int countPoints() const { return fPoints.count(); } + int countVerbs() const { return fVerbs.count(); } + int countWeights() const { return fConicWeights.count(); } + + /** + * Returns a pointer one beyond the first logical verb (last verb in memory order). + */ + const uint8_t* verbsBegin() const { return fVerbs.begin(); } + + /** + * Returns a const pointer to the first verb in memory (which is the last logical verb). + */ + const uint8_t* verbsEnd() const { return fVerbs.end(); } + + /** + * Returns a const pointer to the first point. + */ + const SkPoint* points() const { return fPoints.begin(); } + + /** + * Shortcut for this->points() + this->countPoints() + */ + const SkPoint* pointsEnd() const { return this->points() + this->countPoints(); } + + const SkScalar* conicWeights() const { return fConicWeights.begin(); } + const SkScalar* conicWeightsEnd() const { return fConicWeights.end(); } + + /** + * Convenience methods for getting to a verb or point by index. + */ + uint8_t atVerb(int index) const { return fVerbs[index]; } + const SkPoint& atPoint(int index) const { return fPoints[index]; } + + bool operator== (const SkPathRef& ref) const; + + /** + * Writes the path points and verbs to a buffer. + */ + void writeToBuffer(SkWBuffer* buffer) const; + + /** + * Gets the number of bytes that would be written in writeBuffer() + */ + uint32_t writeSize() const; + + void interpolate(const SkPathRef& ending, SkScalar weight, SkPathRef* out) const; + + /** + * Gets an ID that uniquely identifies the contents of the path ref. If two path refs have the + * same ID then they have the same verbs and points. However, two path refs may have the same + * contents but different genIDs. + */ + uint32_t genID() const; + + class GenIDChangeListener : public SkRefCnt { + public: + GenIDChangeListener() : fShouldUnregisterFromPath(false) {} + virtual ~GenIDChangeListener() {} + + virtual void onChange() = 0; + + // The caller can use this method to notify the path that it no longer needs to listen. Once + // called, the path will remove this listener from the list at some future point. + void markShouldUnregisterFromPath() { + fShouldUnregisterFromPath.store(true, std::memory_order_relaxed); + } + bool shouldUnregisterFromPath() { + return fShouldUnregisterFromPath.load(std::memory_order_acquire); + } + + private: + std::atomic fShouldUnregisterFromPath; + }; + + void addGenIDChangeListener(sk_sp); // Threadsafe. + + bool isValid() const; + SkDEBUGCODE(void validate() const { SkASSERT(this->isValid()); } ) + +private: + enum SerializationOffsets { + kLegacyRRectOrOvalStartIdx_SerializationShift = 28, // requires 3 bits, ignored. + kLegacyRRectOrOvalIsCCW_SerializationShift = 27, // requires 1 bit, ignored. + kLegacyIsRRect_SerializationShift = 26, // requires 1 bit, ignored. + kIsFinite_SerializationShift = 25, // requires 1 bit + kLegacyIsOval_SerializationShift = 24, // requires 1 bit, ignored. + kSegmentMask_SerializationShift = 0 // requires 4 bits (deprecated) + }; + + SkPathRef() { + fBoundsIsDirty = true; // this also invalidates fIsFinite + fGenerationID = kEmptyGenID; + fSegmentMask = 0; + fIsOval = false; + fIsRRect = false; + // The next two values don't matter unless fIsOval or fIsRRect are true. + fRRectOrOvalIsCCW = false; + fRRectOrOvalStartIdx = 0xAC; + SkDEBUGCODE(fEditorsAttached.store(0);) + SkDEBUGCODE(this->validate();) + } + + void copy(const SkPathRef& ref, int additionalReserveVerbs, int additionalReservePoints); + + // Doesn't read fSegmentMask, but (re)computes it from the verbs array + unsigned computeSegmentMask() const; + + // Return true if the computed bounds are finite. + static bool ComputePtBounds(SkRect* bounds, const SkPathRef& ref) { + return bounds->setBoundsCheck(ref.points(), ref.countPoints()); + } + + // called, if dirty, by getBounds() + void computeBounds() const { + SkDEBUGCODE(this->validate();) + // TODO(mtklein): remove fBoundsIsDirty and fIsFinite, + // using an inverted rect instead of fBoundsIsDirty and always recalculating fIsFinite. + SkASSERT(fBoundsIsDirty); + + fIsFinite = ComputePtBounds(&fBounds, *this); + fBoundsIsDirty = false; + } + + void setBounds(const SkRect& rect) { + SkASSERT(rect.fLeft <= rect.fRight && rect.fTop <= rect.fBottom); + fBounds = rect; + fBoundsIsDirty = false; + fIsFinite = fBounds.isFinite(); + } + + /** Makes additional room but does not change the counts or change the genID */ + void incReserve(int additionalVerbs, int additionalPoints) { + SkDEBUGCODE(this->validate();) + fPoints.setReserve(fPoints.count() + additionalPoints); + fVerbs.setReserve(fVerbs.count() + additionalVerbs); + SkDEBUGCODE(this->validate();) + } + + /** Resets the path ref with verbCount verbs and pointCount points, all uninitialized. Also + * allocates space for reserveVerb additional verbs and reservePoints additional points.*/ + void resetToSize(int verbCount, int pointCount, int conicCount, + int reserveVerbs = 0, int reservePoints = 0) { + SkDEBUGCODE(this->validate();) + this->callGenIDChangeListeners(); + fBoundsIsDirty = true; // this also invalidates fIsFinite + fGenerationID = 0; + + fSegmentMask = 0; + fIsOval = false; + fIsRRect = false; + + fPoints.setReserve(pointCount + reservePoints); + fPoints.setCount(pointCount); + fVerbs.setReserve(verbCount + reserveVerbs); + fVerbs.setCount(verbCount); + fConicWeights.setCount(conicCount); + SkDEBUGCODE(this->validate();) + } + + /** + * Increases the verb count by numVbs and point count by the required amount. + * The new points are uninitialized. All the new verbs are set to the specified + * verb. If 'verb' is kConic_Verb, 'weights' will return a pointer to the + * uninitialized conic weights. + */ + SkPoint* growForRepeatedVerb(int /*SkPath::Verb*/ verb, int numVbs, SkScalar** weights); + + /** + * Increases the verb count 1, records the new verb, and creates room for the requisite number + * of additional points. A pointer to the first point is returned. Any new points are + * uninitialized. + */ + SkPoint* growForVerb(int /*SkPath::Verb*/ verb, SkScalar weight); + + /** + * Private, non-const-ptr version of the public function verbsMemBegin(). + */ + uint8_t* verbsBeginWritable() { return fVerbs.begin(); } + + /** + * Called the first time someone calls CreateEmpty to actually create the singleton. + */ + friend SkPathRef* sk_create_empty_pathref(); + + void setIsOval(bool isOval, bool isCCW, unsigned start) { + fIsOval = isOval; + fRRectOrOvalIsCCW = isCCW; + fRRectOrOvalStartIdx = SkToU8(start); + } + + void setIsRRect(bool isRRect, bool isCCW, unsigned start) { + fIsRRect = isRRect; + fRRectOrOvalIsCCW = isCCW; + fRRectOrOvalStartIdx = SkToU8(start); + } + + // called only by the editor. Note that this is not a const function. + SkPoint* getWritablePoints() { + SkDEBUGCODE(this->validate();) + fIsOval = false; + fIsRRect = false; + return fPoints.begin(); + } + + const SkPoint* getPoints() const { + SkDEBUGCODE(this->validate();) + return fPoints.begin(); + } + + void callGenIDChangeListeners(); + + enum { + kMinSize = 256, + }; + + mutable SkRect fBounds; + + SkTDArray fPoints; + SkTDArray fVerbs; + SkTDArray fConicWeights; + + enum { + kEmptyGenID = 1, // GenID reserved for path ref with zero points and zero verbs. + }; + mutable uint32_t fGenerationID; + SkDEBUGCODE(std::atomic fEditorsAttached;) // assert only one editor in use at any time. + + SkMutex fGenIDChangeListenersMutex; + SkTDArray fGenIDChangeListeners; // pointers are reffed + + mutable uint8_t fBoundsIsDirty; + mutable bool fIsFinite; // only meaningful if bounds are valid + + bool fIsOval; + bool fIsRRect; + // Both the circle and rrect special cases have a notion of direction and starting point + // The next two variables store that information for either. + bool fRRectOrOvalIsCCW; + uint8_t fRRectOrOvalStartIdx; + uint8_t fSegmentMask; + + friend class PathRefTest_Private; + friend class ForceIsRRect_Private; // unit test isRRect + friend class SkPath; + friend class SkPathPriv; +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkSafe32.h b/gfx/skia/skia/include/private/SkSafe32.h new file mode 100644 index 0000000000..7e59f2b004 --- /dev/null +++ b/gfx/skia/skia/include/private/SkSafe32.h @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSafe32_DEFINED +#define SkSafe32_DEFINED + +#include "include/core/SkTypes.h" + +static constexpr int32_t Sk64_pin_to_s32(int64_t x) { + return x < SK_MinS32 ? SK_MinS32 : (x > SK_MaxS32 ? SK_MaxS32 : (int32_t)x); +} + +static constexpr int32_t Sk32_sat_add(int32_t a, int32_t b) { + return Sk64_pin_to_s32((int64_t)a + (int64_t)b); +} + +static constexpr int32_t Sk32_sat_sub(int32_t a, int32_t b) { + return Sk64_pin_to_s32((int64_t)a - (int64_t)b); +} + +// To avoid UBSAN complaints about 2's compliment overflows +// +static constexpr int32_t Sk32_can_overflow_add(int32_t a, int32_t b) { + return (int32_t)((uint32_t)a + (uint32_t)b); +} +static constexpr int32_t Sk32_can_overflow_sub(int32_t a, int32_t b) { + return (int32_t)((uint32_t)a - (uint32_t)b); +} + +#endif diff --git a/gfx/skia/skia/include/private/SkSafe_math.h b/gfx/skia/skia/include/private/SkSafe_math.h new file mode 100644 index 0000000000..144b28a4a3 --- /dev/null +++ b/gfx/skia/skia/include/private/SkSafe_math.h @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSafe_math_DEFINED +#define SkSafe_math_DEFINED + +// This file protects against known bugs in ucrt\math.h. +// Namely, that header defines inline methods without marking them static, +// which makes it very easy to cause ODR violations and ensuing chaos. +// +// TODO: other headers? Here are some potential problem headers: +// $ grep -R __inline * | grep -v static | cut -f 1 -d: | sort | uniq +// corecrt.h +// corecrt_stdio_config.h +// ctype.h +// fenv.h +// locale.h +// malloc.h +// math.h +// tchar.h +// wchar.h +// I took a quick look through other headers outside math.h. +// Nothing looks anywhere near as likely to be used by Skia as math.h. + +#if defined(_MSC_VER) && !defined(_INC_MATH) + // Our strategy here is to simply inject "static" into the headers + // where it should have been written, just before __inline. + // + // Most inline-but-not-static methods in math.h are 32-bit only, + // but not all of them (see frexpf, hypothf, ldexpf...). So to + // be safe, 32- and 64-bit builds both get this treatment. + + #define __inline static __inline + #include + #undef __inline + + #if !defined(_INC_MATH) + #error Hmm. Looks like math.h has changed its header guards. + #endif + + #define INC_MATH_IS_SAFE_NOW + +#else + #include + +#endif + +#endif//SkSafe_math_DEFINED diff --git a/gfx/skia/skia/include/private/SkSemaphore.h b/gfx/skia/skia/include/private/SkSemaphore.h new file mode 100644 index 0000000000..29bbca6fa9 --- /dev/null +++ b/gfx/skia/skia/include/private/SkSemaphore.h @@ -0,0 +1,79 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSemaphore_DEFINED +#define SkSemaphore_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkOnce.h" +#include + +class SkSemaphore { +public: + constexpr SkSemaphore(int count = 0) : fCount(count), fOSSemaphore(nullptr) {} + + // Cleanup the underlying OS semaphore. + ~SkSemaphore(); + + // Increment the counter n times. + // Generally it's better to call signal(n) instead of signal() n times. + void signal(int n = 1); + + // Decrement the counter by 1, + // then if the counter is < 0, sleep this thread until the counter is >= 0. + void wait(); + + // If the counter is positive, decrement it by 1 and return true, otherwise return false. + bool try_wait(); + +private: + // This implementation follows the general strategy of + // 'A Lightweight Semaphore with Partial Spinning' + // found here + // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ + // That article (and entire blog) are very much worth reading. + // + // We wrap an OS-provided semaphore with a user-space atomic counter that + // lets us avoid interacting with the OS semaphore unless strictly required: + // moving the count from >=0 to <0 or vice-versa, i.e. sleeping or waking threads. + struct OSSemaphore; + + void osSignal(int n); + void osWait(); + + std::atomic fCount; + SkOnce fOSSemaphoreOnce; + OSSemaphore* fOSSemaphore; +}; + +inline void SkSemaphore::signal(int n) { + int prev = fCount.fetch_add(n, std::memory_order_release); + + // We only want to call the OS semaphore when our logical count crosses + // from <0 to >=0 (when we need to wake sleeping threads). + // + // This is easiest to think about with specific examples of prev and n. + // If n == 5 and prev == -3, there are 3 threads sleeping and we signal + // SkTMin(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2. + // + // If prev >= 0, no threads are waiting, SkTMin(-prev, n) is always <= 0, + // so we don't call the OS semaphore, leaving the count at (prev + n). + int toSignal = SkTMin(-prev, n); + if (toSignal > 0) { + this->osSignal(toSignal); + } +} + +inline void SkSemaphore::wait() { + // Since this fetches the value before the subtract, zero and below means that there are no + // resources left, so the thread needs to wait. + if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) { + this->osWait(); + } +} + +#endif//SkSemaphore_DEFINED diff --git a/gfx/skia/skia/include/private/SkShadowFlags.h b/gfx/skia/skia/include/private/SkShadowFlags.h new file mode 100644 index 0000000000..8caf632988 --- /dev/null +++ b/gfx/skia/skia/include/private/SkShadowFlags.h @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkShadowFlags_DEFINED +#define SkShadowFlags_DEFINED + +// A set of flags shared between the SkAmbientShadowMaskFilter and the SkSpotShadowMaskFilter +enum SkShadowFlags { + kNone_ShadowFlag = 0x00, + /** The occluding object is not opaque. Knowing that the occluder is opaque allows + * us to cull shadow geometry behind it and improve performance. */ + kTransparentOccluder_ShadowFlag = 0x01, + /** Don't try to use analytic shadows. */ + kGeometricOnly_ShadowFlag = 0x02, + /** mask for all shadow flags */ + kAll_ShadowFlag = 0x03 +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkSpinlock.h b/gfx/skia/skia/include/private/SkSpinlock.h new file mode 100644 index 0000000000..e1d5011681 --- /dev/null +++ b/gfx/skia/skia/include/private/SkSpinlock.h @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSpinlock_DEFINED +#define SkSpinlock_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkThreadAnnotations.h" +#include + +class SK_CAPABILITY("mutex") SkSpinlock { +public: + constexpr SkSpinlock() = default; + + void acquire() SK_ACQUIRE() { + // To act as a mutex, we need an acquire barrier when we acquire the lock. + if (fLocked.exchange(true, std::memory_order_acquire)) { + // Lock was contended. Fall back to an out-of-line spin loop. + this->contendedAcquire(); + } + } + + // Acquire the lock or fail (quickly). Lets the caller decide to do something other than wait. + bool tryAcquire() SK_TRY_ACQUIRE(true) { + // To act as a mutex, we need an acquire barrier when we acquire the lock. + if (fLocked.exchange(true, std::memory_order_acquire)) { + // Lock was contended. Let the caller decide what to do. + return false; + } + return true; + } + + void release() SK_RELEASE_CAPABILITY() { + // To act as a mutex, we need a release barrier when we release the lock. + fLocked.store(false, std::memory_order_release); + } + +private: + SK_API void contendedAcquire(); + + std::atomic fLocked{false}; +}; + +class SK_SCOPED_CAPABILITY SkAutoSpinlock { +public: + SkAutoSpinlock(SkSpinlock& mutex) SK_ACQUIRE(mutex) : fSpinlock(mutex) { fSpinlock.acquire(); } + ~SkAutoSpinlock() SK_RELEASE_CAPABILITY() { fSpinlock.release(); } + +private: + SkSpinlock& fSpinlock; +}; + +#endif//SkSpinlock_DEFINED diff --git a/gfx/skia/skia/include/private/SkTArray.h b/gfx/skia/skia/include/private/SkTArray.h new file mode 100644 index 0000000000..e526921471 --- /dev/null +++ b/gfx/skia/skia/include/private/SkTArray.h @@ -0,0 +1,641 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTArray_DEFINED +#define SkTArray_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkTypes.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkSafe32.h" +#include "include/private/SkTLogic.h" +#include "include/private/SkTemplates.h" + +#include +#include +#include +#include + +/** When MEM_MOVE is true T will be bit copied when moved. + When MEM_MOVE is false, T will be copy constructed / destructed. + In all cases T will be default-initialized on allocation, + and its destructor will be called from this object's destructor. +*/ +template class SkTArray { +public: + /** + * Creates an empty array with no initial storage + */ + SkTArray() { this->init(); } + + /** + * Creates an empty array that will preallocate space for reserveCount + * elements. + */ + explicit SkTArray(int reserveCount) { this->init(0, reserveCount); } + + /** + * Copies one array to another. The new array will be heap allocated. + */ + SkTArray(const SkTArray& that) { + this->init(that.fCount); + this->copy(that.fItemArray); + } + + SkTArray(SkTArray&& that) { + // TODO: If 'that' owns its memory why don't we just steal the pointer? + this->init(that.fCount); + that.move(fMemArray); + that.fCount = 0; + } + + /** + * Creates a SkTArray by copying contents of a standard C array. The new + * array will be heap allocated. Be careful not to use this constructor + * when you really want the (void*, int) version. + */ + SkTArray(const T* array, int count) { + this->init(count); + this->copy(array); + } + + SkTArray& operator=(const SkTArray& that) { + if (this == &that) { + return *this; + } + for (int i = 0; i < fCount; ++i) { + fItemArray[i].~T(); + } + fCount = 0; + this->checkRealloc(that.count()); + fCount = that.count(); + this->copy(that.fItemArray); + return *this; + } + SkTArray& operator=(SkTArray&& that) { + if (this == &that) { + return *this; + } + for (int i = 0; i < fCount; ++i) { + fItemArray[i].~T(); + } + fCount = 0; + this->checkRealloc(that.count()); + fCount = that.count(); + that.move(fMemArray); + that.fCount = 0; + return *this; + } + + ~SkTArray() { + for (int i = 0; i < fCount; ++i) { + fItemArray[i].~T(); + } + if (fOwnMemory) { + sk_free(fMemArray); + } + } + + /** + * Resets to count() == 0 and resets any reserve count. + */ + void reset() { + this->pop_back_n(fCount); + fReserved = false; + } + + /** + * Resets to count() = n newly constructed T objects and resets any reserve count. + */ + void reset(int n) { + SkASSERT(n >= 0); + for (int i = 0; i < fCount; ++i) { + fItemArray[i].~T(); + } + // Set fCount to 0 before calling checkRealloc so that no elements are moved. + fCount = 0; + this->checkRealloc(n); + fCount = n; + for (int i = 0; i < fCount; ++i) { + new (fItemArray + i) T; + } + fReserved = false; + } + + /** + * Resets to a copy of a C array and resets any reserve count. + */ + void reset(const T* array, int count) { + for (int i = 0; i < fCount; ++i) { + fItemArray[i].~T(); + } + fCount = 0; + this->checkRealloc(count); + fCount = count; + this->copy(array); + fReserved = false; + } + + /** + * Ensures there is enough reserved space for n additional elements. The is guaranteed at least + * until the array size grows above n and subsequently shrinks below n, any version of reset() + * is called, or reserve() is called again. + */ + void reserve(int n) { + SkASSERT(n >= 0); + if (n > 0) { + this->checkRealloc(n); + fReserved = fOwnMemory; + } else { + fReserved = false; + } + } + + void removeShuffle(int n) { + SkASSERT(n < fCount); + int newCount = fCount - 1; + fCount = newCount; + fItemArray[n].~T(); + if (n != newCount) { + this->move(n, newCount); + } + } + + /** + * Number of elements in the array. + */ + int count() const { return fCount; } + + /** + * Is the array empty. + */ + bool empty() const { return !fCount; } + + /** + * Adds 1 new default-initialized T value and returns it by reference. Note + * the reference only remains valid until the next call that adds or removes + * elements. + */ + T& push_back() { + void* newT = this->push_back_raw(1); + return *new (newT) T; + } + + /** + * Version of above that uses a copy constructor to initialize the new item + */ + T& push_back(const T& t) { + void* newT = this->push_back_raw(1); + return *new (newT) T(t); + } + + /** + * Version of above that uses a move constructor to initialize the new item + */ + T& push_back(T&& t) { + void* newT = this->push_back_raw(1); + return *new (newT) T(std::move(t)); + } + + /** + * Construct a new T at the back of this array. + */ + template T& emplace_back(Args&&... args) { + void* newT = this->push_back_raw(1); + return *new (newT) T(std::forward(args)...); + } + + /** + * Allocates n more default-initialized T values, and returns the address of + * the start of that new range. Note: this address is only valid until the + * next API call made on the array that might add or remove elements. + */ + T* push_back_n(int n) { + SkASSERT(n >= 0); + void* newTs = this->push_back_raw(n); + for (int i = 0; i < n; ++i) { + new (static_cast(newTs) + i * sizeof(T)) T; + } + return static_cast(newTs); + } + + /** + * Version of above that uses a copy constructor to initialize all n items + * to the same T. + */ + T* push_back_n(int n, const T& t) { + SkASSERT(n >= 0); + void* newTs = this->push_back_raw(n); + for (int i = 0; i < n; ++i) { + new (static_cast(newTs) + i * sizeof(T)) T(t); + } + return static_cast(newTs); + } + + /** + * Version of above that uses a copy constructor to initialize the n items + * to separate T values. + */ + T* push_back_n(int n, const T t[]) { + SkASSERT(n >= 0); + this->checkRealloc(n); + for (int i = 0; i < n; ++i) { + new (fItemArray + fCount + i) T(t[i]); + } + fCount += n; + return fItemArray + fCount - n; + } + + /** + * Version of above that uses the move constructor to set n items. + */ + T* move_back_n(int n, T* t) { + SkASSERT(n >= 0); + this->checkRealloc(n); + for (int i = 0; i < n; ++i) { + new (fItemArray + fCount + i) T(std::move(t[i])); + } + fCount += n; + return fItemArray + fCount - n; + } + + /** + * Removes the last element. Not safe to call when count() == 0. + */ + void pop_back() { + SkASSERT(fCount > 0); + --fCount; + fItemArray[fCount].~T(); + this->checkRealloc(0); + } + + /** + * Removes the last n elements. Not safe to call when count() < n. + */ + void pop_back_n(int n) { + SkASSERT(n >= 0); + SkASSERT(fCount >= n); + fCount -= n; + for (int i = 0; i < n; ++i) { + fItemArray[fCount + i].~T(); + } + this->checkRealloc(0); + } + + /** + * Pushes or pops from the back to resize. Pushes will be default + * initialized. + */ + void resize_back(int newCount) { + SkASSERT(newCount >= 0); + + if (newCount > fCount) { + this->push_back_n(newCount - fCount); + } else if (newCount < fCount) { + this->pop_back_n(fCount - newCount); + } + } + + /** Swaps the contents of this array with that array. Does a pointer swap if possible, + otherwise copies the T values. */ + void swap(SkTArray& that) { + using std::swap; + if (this == &that) { + return; + } + if (fOwnMemory && that.fOwnMemory) { + swap(fItemArray, that.fItemArray); + swap(fCount, that.fCount); + swap(fAllocCount, that.fAllocCount); + } else { + // This could be more optimal... + SkTArray copy(std::move(that)); + that = std::move(*this); + *this = std::move(copy); + } + } + + T* begin() { + return fItemArray; + } + const T* begin() const { + return fItemArray; + } + T* end() { + return fItemArray ? fItemArray + fCount : nullptr; + } + const T* end() const { + return fItemArray ? fItemArray + fCount : nullptr; + } + T* data() { return fItemArray; } + const T* data() const { return fItemArray; } + size_t size() const { return (size_t)fCount; } + void resize(size_t count) { this->resize_back((int)count); } + + /** + * Get the i^th element. + */ + T& operator[] (int i) { + SkASSERT(i < fCount); + SkASSERT(i >= 0); + return fItemArray[i]; + } + + const T& operator[] (int i) const { + SkASSERT(i < fCount); + SkASSERT(i >= 0); + return fItemArray[i]; + } + + /** + * equivalent to operator[](0) + */ + T& front() { SkASSERT(fCount > 0); return fItemArray[0];} + + const T& front() const { SkASSERT(fCount > 0); return fItemArray[0];} + + /** + * equivalent to operator[](count() - 1) + */ + T& back() { SkASSERT(fCount); return fItemArray[fCount - 1];} + + const T& back() const { SkASSERT(fCount > 0); return fItemArray[fCount - 1];} + + /** + * equivalent to operator[](count()-1-i) + */ + T& fromBack(int i) { + SkASSERT(i >= 0); + SkASSERT(i < fCount); + return fItemArray[fCount - i - 1]; + } + + const T& fromBack(int i) const { + SkASSERT(i >= 0); + SkASSERT(i < fCount); + return fItemArray[fCount - i - 1]; + } + + bool operator==(const SkTArray& right) const { + int leftCount = this->count(); + if (leftCount != right.count()) { + return false; + } + for (int index = 0; index < leftCount; ++index) { + if (fItemArray[index] != right.fItemArray[index]) { + return false; + } + } + return true; + } + + bool operator!=(const SkTArray& right) const { + return !(*this == right); + } + + inline int allocCntForTest() const; + +protected: + /** + * Creates an empty array that will use the passed storage block until it + * is insufficiently large to hold the entire array. + */ + template + SkTArray(SkAlignedSTStorage* storage) { + this->initWithPreallocatedStorage(0, storage->get(), N); + } + + /** + * Copy another array, using preallocated storage if preAllocCount >= + * array.count(). Otherwise storage will only be used when array shrinks + * to fit. + */ + template + SkTArray(const SkTArray& array, SkAlignedSTStorage* storage) { + this->initWithPreallocatedStorage(array.fCount, storage->get(), N); + this->copy(array.fItemArray); + } + + /** + * Move another array, using preallocated storage if preAllocCount >= + * array.count(). Otherwise storage will only be used when array shrinks + * to fit. + */ + template + SkTArray(SkTArray&& array, SkAlignedSTStorage* storage) { + this->initWithPreallocatedStorage(array.fCount, storage->get(), N); + array.move(fMemArray); + array.fCount = 0; + } + + /** + * Copy a C array, using preallocated storage if preAllocCount >= + * count. Otherwise storage will only be used when array shrinks + * to fit. + */ + template + SkTArray(const T* array, int count, SkAlignedSTStorage* storage) { + this->initWithPreallocatedStorage(count, storage->get(), N); + this->copy(array); + } + +private: + void init(int count = 0, int reserveCount = 0) { + SkASSERT(count >= 0); + SkASSERT(reserveCount >= 0); + fCount = count; + if (!count && !reserveCount) { + fAllocCount = 0; + fMemArray = nullptr; + fOwnMemory = true; + fReserved = false; + } else { + fAllocCount = SkTMax(count, SkTMax(kMinHeapAllocCount, reserveCount)); + fMemArray = sk_malloc_throw(fAllocCount, sizeof(T)); + fOwnMemory = true; + fReserved = reserveCount > 0; + } + } + + void initWithPreallocatedStorage(int count, void* preallocStorage, int preallocCount) { + SkASSERT(count >= 0); + SkASSERT(preallocCount > 0); + SkASSERT(preallocStorage); + fCount = count; + fMemArray = nullptr; + fReserved = false; + if (count > preallocCount) { + fAllocCount = SkTMax(count, kMinHeapAllocCount); + fMemArray = sk_malloc_throw(fAllocCount, sizeof(T)); + fOwnMemory = true; + } else { + fAllocCount = preallocCount; + fMemArray = preallocStorage; + fOwnMemory = false; + } + } + + /** In the following move and copy methods, 'dst' is assumed to be uninitialized raw storage. + * In the following move methods, 'src' is destroyed leaving behind uninitialized raw storage. + */ + void copy(const T* src) { + // Some types may be trivially copyable, in which case we *could* use memcopy; but + // MEM_MOVE == true implies that the type is trivially movable, and not necessarily + // trivially copyable (think sk_sp<>). So short of adding another template arg, we + // must be conservative and use copy construction. + for (int i = 0; i < fCount; ++i) { + new (fItemArray + i) T(src[i]); + } + } + + template SK_WHEN(E, void) move(int dst, int src) { + memcpy(&fItemArray[dst], &fItemArray[src], sizeof(T)); + } + template SK_WHEN(E, void) move(void* dst) { + sk_careful_memcpy(dst, fMemArray, fCount * sizeof(T)); + } + + template SK_WHEN(!E, void) move(int dst, int src) { + new (&fItemArray[dst]) T(std::move(fItemArray[src])); + fItemArray[src].~T(); + } + template SK_WHEN(!E, void) move(void* dst) { + for (int i = 0; i < fCount; ++i) { + new (static_cast(dst) + sizeof(T) * i) T(std::move(fItemArray[i])); + fItemArray[i].~T(); + } + } + + static constexpr int kMinHeapAllocCount = 8; + + // Helper function that makes space for n objects, adjusts the count, but does not initialize + // the new objects. + void* push_back_raw(int n) { + this->checkRealloc(n); + void* ptr = fItemArray + fCount; + fCount += n; + return ptr; + } + + void checkRealloc(int delta) { + SkASSERT(fCount >= 0); + SkASSERT(fAllocCount >= 0); + SkASSERT(-delta <= fCount); + + // Move into 64bit math temporarily, to avoid local overflows + int64_t newCount = fCount + delta; + + // We allow fAllocCount to be in the range [newCount, 3*newCount]. We also never shrink + // when we're currently using preallocated memory, would allocate less than + // kMinHeapAllocCount, or a reserve count was specified that has yet to be exceeded. + bool mustGrow = newCount > fAllocCount; + bool shouldShrink = fAllocCount > 3 * newCount && fOwnMemory && !fReserved; + if (!mustGrow && !shouldShrink) { + return; + } + + + // Whether we're growing or shrinking, we leave at least 50% extra space for future growth. + int64_t newAllocCount = newCount + ((newCount + 1) >> 1); + // Align the new allocation count to kMinHeapAllocCount. + static_assert(SkIsPow2(kMinHeapAllocCount), "min alloc count not power of two."); + newAllocCount = (newAllocCount + (kMinHeapAllocCount - 1)) & ~(kMinHeapAllocCount - 1); + // At small sizes the old and new alloc count can both be kMinHeapAllocCount. + if (newAllocCount == fAllocCount) { + return; + } + + fAllocCount = Sk64_pin_to_s32(newAllocCount); + SkASSERT(fAllocCount >= newCount); + void* newMemArray = sk_malloc_throw(fAllocCount, sizeof(T)); + this->move(newMemArray); + if (fOwnMemory) { + sk_free(fMemArray); + + } + fMemArray = newMemArray; + fOwnMemory = true; + fReserved = false; + } + + union { + T* fItemArray; + void* fMemArray; + }; + int fCount; + int fAllocCount; + bool fOwnMemory : 1; + bool fReserved : 1; +}; + +template static inline void swap(SkTArray& a, SkTArray& b) { + a.swap(b); +} + +template constexpr int SkTArray::kMinHeapAllocCount; + +/** + * Subclass of SkTArray that contains a preallocated memory block for the array. + */ +template +class SkSTArray : public SkTArray { +private: + typedef SkTArray INHERITED; + +public: + SkSTArray() : INHERITED(&fStorage) { + } + + SkSTArray(const SkSTArray& array) + : INHERITED(array, &fStorage) { + } + + SkSTArray(SkSTArray&& array) + : INHERITED(std::move(array), &fStorage) { + } + + explicit SkSTArray(const INHERITED& array) + : INHERITED(array, &fStorage) { + } + + SkSTArray(INHERITED&& array) + : INHERITED(std::move(array), &fStorage) { + } + + explicit SkSTArray(int reserveCount) + : INHERITED(reserveCount) { + } + + SkSTArray(const T* array, int count) + : INHERITED(array, count, &fStorage) { + } + + SkSTArray& operator=(const SkSTArray& array) { + INHERITED::operator=(array); + return *this; + } + + SkSTArray& operator=(SkSTArray&& array) { + INHERITED::operator=(std::move(array)); + return *this; + } + + SkSTArray& operator=(const INHERITED& array) { + INHERITED::operator=(array); + return *this; + } + + SkSTArray& operator=(INHERITED&& array) { + INHERITED::operator=(std::move(array)); + return *this; + } + +private: + SkAlignedSTStorage fStorage; +}; + +#endif diff --git a/gfx/skia/skia/include/private/SkTDArray.h b/gfx/skia/skia/include/private/SkTDArray.h new file mode 100644 index 0000000000..59c180bcde --- /dev/null +++ b/gfx/skia/skia/include/private/SkTDArray.h @@ -0,0 +1,372 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkTDArray_DEFINED +#define SkTDArray_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" + +#include +#include + +template class SkTDArray { +public: + SkTDArray() : fArray(nullptr), fReserve(0), fCount(0) {} + SkTDArray(const T src[], int count) { + SkASSERT(src || count == 0); + + fReserve = fCount = 0; + fArray = nullptr; + if (count) { + fArray = (T*)sk_malloc_throw(count * sizeof(T)); + memcpy(fArray, src, sizeof(T) * count); + fReserve = fCount = count; + } + } + SkTDArray(const std::initializer_list& list) : SkTDArray(list.begin(), list.size()) {} + SkTDArray(const SkTDArray& src) : fArray(nullptr), fReserve(0), fCount(0) { + SkTDArray tmp(src.fArray, src.fCount); + this->swap(tmp); + } + SkTDArray(SkTDArray&& src) : fArray(nullptr), fReserve(0), fCount(0) { + this->swap(src); + } + ~SkTDArray() { + sk_free(fArray); + } + + SkTDArray& operator=(const SkTDArray& src) { + if (this != &src) { + if (src.fCount > fReserve) { + SkTDArray tmp(src.fArray, src.fCount); + this->swap(tmp); + } else { + sk_careful_memcpy(fArray, src.fArray, sizeof(T) * src.fCount); + fCount = src.fCount; + } + } + return *this; + } + SkTDArray& operator=(SkTDArray&& src) { + if (this != &src) { + this->swap(src); + src.reset(); + } + return *this; + } + + friend bool operator==(const SkTDArray& a, const SkTDArray& b) { + return a.fCount == b.fCount && + (a.fCount == 0 || + !memcmp(a.fArray, b.fArray, a.fCount * sizeof(T))); + } + friend bool operator!=(const SkTDArray& a, const SkTDArray& b) { + return !(a == b); + } + + void swap(SkTDArray& that) { + using std::swap; + swap(fArray, that.fArray); + swap(fReserve, that.fReserve); + swap(fCount, that.fCount); + } + + bool isEmpty() const { return fCount == 0; } + bool empty() const { return this->isEmpty(); } + + /** + * Return the number of elements in the array + */ + int count() const { return fCount; } + size_t size() const { return fCount; } + + /** + * Return the total number of elements allocated. + * reserved() - count() gives you the number of elements you can add + * without causing an allocation. + */ + int reserved() const { return fReserve; } + + /** + * return the number of bytes in the array: count * sizeof(T) + */ + size_t bytes() const { return fCount * sizeof(T); } + + T* begin() { return fArray; } + const T* begin() const { return fArray; } + T* end() { return fArray ? fArray + fCount : nullptr; } + const T* end() const { return fArray ? fArray + fCount : nullptr; } + + T& operator[](int index) { + SkASSERT(index < fCount); + return fArray[index]; + } + const T& operator[](int index) const { + SkASSERT(index < fCount); + return fArray[index]; + } + + T& getAt(int index) { + return (*this)[index]; + } + + + void reset() { + if (fArray) { + sk_free(fArray); + fArray = nullptr; + fReserve = fCount = 0; + } else { + SkASSERT(fReserve == 0 && fCount == 0); + } + } + + void rewind() { + // same as setCount(0) + fCount = 0; + } + + /** + * Sets the number of elements in the array. + * If the array does not have space for count elements, it will increase + * the storage allocated to some amount greater than that required. + * It will never shrink the storage. + */ + void setCount(int count) { + SkASSERT(count >= 0); + if (count > fReserve) { + this->resizeStorageToAtLeast(count); + } + fCount = count; + } + + void setReserve(int reserve) { + SkASSERT(reserve >= 0); + if (reserve > fReserve) { + this->resizeStorageToAtLeast(reserve); + } + } + void reserve(size_t n) { + SkASSERT_RELEASE(SkTFitsIn(n)); + this->setReserve(SkToInt(n)); + } + + T* prepend() { + this->adjustCount(1); + memmove(fArray + 1, fArray, (fCount - 1) * sizeof(T)); + return fArray; + } + + T* append() { + return this->append(1, nullptr); + } + T* append(int count, const T* src = nullptr) { + int oldCount = fCount; + if (count) { + SkASSERT(src == nullptr || fArray == nullptr || + src + count <= fArray || fArray + oldCount <= src); + + this->adjustCount(count); + if (src) { + memcpy(fArray + oldCount, src, sizeof(T) * count); + } + } + return fArray + oldCount; + } + + T* insert(int index) { + return this->insert(index, 1, nullptr); + } + T* insert(int index, int count, const T* src = nullptr) { + SkASSERT(count); + SkASSERT(index <= fCount); + size_t oldCount = fCount; + this->adjustCount(count); + T* dst = fArray + index; + memmove(dst + count, dst, sizeof(T) * (oldCount - index)); + if (src) { + memcpy(dst, src, sizeof(T) * count); + } + return dst; + } + + void remove(int index, int count = 1) { + SkASSERT(index + count <= fCount); + fCount = fCount - count; + memmove(fArray + index, fArray + index + count, sizeof(T) * (fCount - index)); + } + + void removeShuffle(int index) { + SkASSERT(index < fCount); + int newCount = fCount - 1; + fCount = newCount; + if (index != newCount) { + memcpy(fArray + index, fArray + newCount, sizeof(T)); + } + } + + int find(const T& elem) const { + const T* iter = fArray; + const T* stop = fArray + fCount; + + for (; iter < stop; iter++) { + if (*iter == elem) { + return SkToInt(iter - fArray); + } + } + return -1; + } + + int rfind(const T& elem) const { + const T* iter = fArray + fCount; + const T* stop = fArray; + + while (iter > stop) { + if (*--iter == elem) { + return SkToInt(iter - stop); + } + } + return -1; + } + + /** + * Returns true iff the array contains this element. + */ + bool contains(const T& elem) const { + return (this->find(elem) >= 0); + } + + /** + * Copies up to max elements into dst. The number of items copied is + * capped by count - index. The actual number copied is returned. + */ + int copyRange(T* dst, int index, int max) const { + SkASSERT(max >= 0); + SkASSERT(!max || dst); + if (index >= fCount) { + return 0; + } + int count = SkMin32(max, fCount - index); + memcpy(dst, fArray + index, sizeof(T) * count); + return count; + } + + void copy(T* dst) const { + this->copyRange(dst, 0, fCount); + } + + // routines to treat the array like a stack + void push_back(const T& v) { *this->append() = v; } + T* push() { return this->append(); } + const T& top() const { return (*this)[fCount - 1]; } + T& top() { return (*this)[fCount - 1]; } + void pop(T* elem) { SkASSERT(fCount > 0); if (elem) *elem = (*this)[fCount - 1]; --fCount; } + void pop() { SkASSERT(fCount > 0); --fCount; } + + void deleteAll() { + T* iter = fArray; + T* stop = fArray + fCount; + while (iter < stop) { + delete *iter; + iter += 1; + } + this->reset(); + } + + void freeAll() { + T* iter = fArray; + T* stop = fArray + fCount; + while (iter < stop) { + sk_free(*iter); + iter += 1; + } + this->reset(); + } + + void unrefAll() { + T* iter = fArray; + T* stop = fArray + fCount; + while (iter < stop) { + (*iter)->unref(); + iter += 1; + } + this->reset(); + } + + void safeUnrefAll() { + T* iter = fArray; + T* stop = fArray + fCount; + while (iter < stop) { + SkSafeUnref(*iter); + iter += 1; + } + this->reset(); + } + +#ifdef SK_DEBUG + void validate() const { + SkASSERT((fReserve == 0 && fArray == nullptr) || + (fReserve > 0 && fArray != nullptr)); + SkASSERT(fCount <= fReserve); + } +#endif + + void shrinkToFit() { + fReserve = fCount; + fArray = (T*)sk_realloc_throw(fArray, fReserve * sizeof(T)); + } + +private: + T* fArray; + int fReserve; // size of the allocation in fArray (#elements) + int fCount; // logical number of elements (fCount <= fReserve) + + /** + * Adjusts the number of elements in the array. + * This is the same as calling setCount(count() + delta). + */ + void adjustCount(int delta) { + SkASSERT(delta > 0); + + // We take care to avoid overflow here. + // The sum of fCount and delta is at most 4294967294, which fits fine in uint32_t. + uint32_t count = (uint32_t)fCount + (uint32_t)delta; + SkASSERT_RELEASE( SkTFitsIn(count) ); + + this->setCount(SkTo(count)); + } + + /** + * Increase the storage allocation such that it can hold (fCount + extra) + * elements. + * It never shrinks the allocation, and it may increase the allocation by + * more than is strictly required, based on a private growth heuristic. + * + * note: does NOT modify fCount + */ + void resizeStorageToAtLeast(int count) { + SkASSERT(count > fReserve); + + // We take care to avoid overflow here. + // The maximum value we can get for reserve here is 2684354563, which fits in uint32_t. + uint32_t reserve = (uint32_t)count + 4; + reserve += reserve / 4; + SkASSERT_RELEASE( SkTFitsIn(reserve) ); + + fReserve = SkTo(reserve); + fArray = (T*)sk_realloc_throw(fArray, fReserve * sizeof(T)); + } +}; + +template static inline void swap(SkTDArray& a, SkTDArray& b) { + a.swap(b); +} + +#endif diff --git a/gfx/skia/skia/include/private/SkTFitsIn.h b/gfx/skia/skia/include/private/SkTFitsIn.h new file mode 100644 index 0000000000..a912f13e08 --- /dev/null +++ b/gfx/skia/skia/include/private/SkTFitsIn.h @@ -0,0 +1,99 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTFitsIn_DEFINED +#define SkTFitsIn_DEFINED + +#include +#include +#include + +/** + * std::underlying_type is only defined for enums. For integral types, we just want the type. + */ +template +struct sk_strip_enum { + typedef T type; +}; + +template +struct sk_strip_enum::value>::type> { + typedef typename std::underlying_type::type type; +}; + + +/** + * In C++ an unsigned to signed cast where the source value cannot be represented in the destination + * type results in an implementation defined destination value. Unlike C, C++ does not allow a trap. + * This makes "(S)(D)s == s" a possibly useful test. However, there are two cases where this is + * incorrect: + * + * when testing if a value of a smaller signed type can be represented in a larger unsigned type + * (int8_t)(uint16_t)-1 == -1 => (int8_t)0xFFFF == -1 => [implementation defined] == -1 + * + * when testing if a value of a larger unsigned type can be represented in a smaller signed type + * (uint16_t)(int8_t)0xFFFF == 0xFFFF => (uint16_t)-1 == 0xFFFF => 0xFFFF == 0xFFFF => true. + * + * Consider the cases: + * u = unsigned, less digits + * U = unsigned, more digits + * s = signed, less digits + * S = signed, more digits + * v is the value we're considering. + * + * u -> U: (u)(U)v == v, trivially true + * U -> u: (U)(u)v == v, both casts well defined, test works + * s -> S: (s)(S)v == v, trivially true + * S -> s: (S)(s)v == v, first cast implementation value, second cast defined, test works + * s -> U: (s)(U)v == v, *this is bad*, the second cast results in implementation defined value + * S -> u: (S)(u)v == v, the second cast is required to prevent promotion of rhs to unsigned + * u -> S: (u)(S)v == v, trivially true + * U -> s: (U)(s)v == v, *this is bad*, + * first cast results in implementation defined value, + * second cast is defined. However, this creates false positives + * uint16_t x = 0xFFFF + * (uint16_t)(int8_t)x == x + * => (uint16_t)-1 == x + * => 0xFFFF == x + * => true + * + * So for the eight cases three are trivially true, three more are valid casts, and two are special. + * The two 'full' checks which otherwise require two comparisons are valid cast checks. + * The two remaining checks s -> U [v >= 0] and U -> s [v <= max(s)] can be done with one op. + */ + +template +static constexpr inline +typename std::enable_if<(std::is_integral::value || std::is_enum::value) && + (std::is_integral::value || std::is_enum::value), bool>::type +/*bool*/ SkTFitsIn(S src) { + // SkTFitsIn() is used in public headers, so needs to be written targeting at most C++11. + return + + // E.g. (int8_t)(uint8_t) int8_t(-1) == -1, but the uint8_t == 255, not -1. + (std::is_signed::value && std::is_unsigned::value && sizeof(S) <= sizeof(D)) ? + (S)0 <= src : + + // E.g. (uint8_t)(int8_t) uint8_t(255) == 255, but the int8_t == -1. + (std::is_signed::value && std::is_unsigned::value && sizeof(D) <= sizeof(S)) ? + src <= (S)std::numeric_limits::type>::max() : + +#if !defined(SK_DEBUG) && !defined(__MSVC_RUNTIME_CHECKS ) + // Correct (simple) version. This trips up MSVC's /RTCc run-time checking. + (S)(D)src == src; +#else + // More complex version that's safe with /RTCc. Used in all debug builds, for coverage. + (std::is_signed::value) ? + (intmax_t)src >= (intmax_t)std::numeric_limits::type>::min() && + (intmax_t)src <= (intmax_t)std::numeric_limits::type>::max() : + + // std::is_unsigned ? + (uintmax_t)src <= (uintmax_t)std::numeric_limits::type>::max(); +#endif +} + +#endif diff --git a/gfx/skia/skia/include/private/SkTHash.h b/gfx/skia/skia/include/private/SkTHash.h new file mode 100644 index 0000000000..bc563d168c --- /dev/null +++ b/gfx/skia/skia/include/private/SkTHash.h @@ -0,0 +1,362 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTHash_DEFINED +#define SkTHash_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkChecksum.h" +#include "include/private/SkTemplates.h" +#include + +// Before trying to use SkTHashTable, look below to see if SkTHashMap or SkTHashSet works for you. +// They're easier to use, usually perform the same, and have fewer sharp edges. + +// T and K are treated as ordinary copyable C++ types. +// Traits must have: +// - static K GetKey(T) +// - static uint32_t Hash(K) +// If the key is large and stored inside T, you may want to make K a const&. +// Similarly, if T is large you might want it to be a pointer. +template +class SkTHashTable { +public: + SkTHashTable() : fCount(0), fCapacity(0) {} + SkTHashTable(SkTHashTable&& other) + : fCount(other.fCount) + , fCapacity(other.fCapacity) + , fSlots(std::move(other.fSlots)) { other.fCount = other.fCapacity = 0; } + + SkTHashTable& operator=(SkTHashTable&& other) { + if (this != &other) { + this->~SkTHashTable(); + new (this) SkTHashTable(std::move(other)); + } + return *this; + } + + // Clear the table. + void reset() { *this = SkTHashTable(); } + + // How many entries are in the table? + int count() const { return fCount; } + + // Approximately how many bytes of memory do we use beyond sizeof(*this)? + size_t approxBytesUsed() const { return fCapacity * sizeof(Slot); } + + // !!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!! + // set(), find() and foreach() all allow mutable access to table entries. + // If you change an entry so that it no longer has the same key, all hell + // will break loose. Do not do that! + // + // Please prefer to use SkTHashMap or SkTHashSet, which do not have this danger. + + // The pointers returned by set() and find() are valid only until the next call to set(). + // The pointers you receive in foreach() are only valid for its duration. + + // Copy val into the hash table, returning a pointer to the copy now in the table. + // If there already is an entry in the table with the same key, we overwrite it. + T* set(T val) { + if (4 * fCount >= 3 * fCapacity) { + this->resize(fCapacity > 0 ? fCapacity * 2 : 4); + } + return this->uncheckedSet(std::move(val)); + } + + // If there is an entry in the table with this key, return a pointer to it. If not, null. + T* find(const K& key) const { + uint32_t hash = Hash(key); + int index = hash & (fCapacity-1); + for (int n = 0; n < fCapacity; n++) { + Slot& s = fSlots[index]; + if (s.empty()) { + return nullptr; + } + if (hash == s.hash && key == Traits::GetKey(s.val)) { + return &s.val; + } + index = this->next(index); + } + SkASSERT(fCapacity == 0); + return nullptr; + } + + // If there is an entry in the table with this key, return it. If not, null. + // This only works for pointer type T, and cannot be used to find an nullptr entry. + T findOrNull(const K& key) const { + if (T* p = this->find(key)) { + return *p; + } + return nullptr; + } + + // Remove the value with this key from the hash table. + void remove(const K& key) { + SkASSERT(this->find(key)); + + uint32_t hash = Hash(key); + int index = hash & (fCapacity-1); + for (int n = 0; n < fCapacity; n++) { + Slot& s = fSlots[index]; + SkASSERT(!s.empty()); + if (hash == s.hash && key == Traits::GetKey(s.val)) { + fCount--; + break; + } + index = this->next(index); + } + + // Rearrange elements to restore the invariants for linear probing. + for (;;) { + Slot& emptySlot = fSlots[index]; + int emptyIndex = index; + int originalIndex; + // Look for an element that can be moved into the empty slot. + // If the empty slot is in between where an element landed, and its native slot, then + // move it to the empty slot. Don't move it if its native slot is in between where + // the element landed and the empty slot. + // [native] <= [empty] < [candidate] == GOOD, can move candidate to empty slot + // [empty] < [native] < [candidate] == BAD, need to leave candidate where it is + do { + index = this->next(index); + Slot& s = fSlots[index]; + if (s.empty()) { + // We're done shuffling elements around. Clear the last empty slot. + emptySlot = Slot(); + return; + } + originalIndex = s.hash & (fCapacity - 1); + } while ((index <= originalIndex && originalIndex < emptyIndex) + || (originalIndex < emptyIndex && emptyIndex < index) + || (emptyIndex < index && index <= originalIndex)); + // Move the element to the empty slot. + Slot& moveFrom = fSlots[index]; + emptySlot = std::move(moveFrom); + } + } + + // Call fn on every entry in the table. You may mutate the entries, but be very careful. + template // f(T*) + void foreach(Fn&& fn) { + for (int i = 0; i < fCapacity; i++) { + if (!fSlots[i].empty()) { + fn(&fSlots[i].val); + } + } + } + + // Call fn on every entry in the table. You may not mutate anything. + template // f(T) or f(const T&) + void foreach(Fn&& fn) const { + for (int i = 0; i < fCapacity; i++) { + if (!fSlots[i].empty()) { + fn(fSlots[i].val); + } + } + } + +private: + T* uncheckedSet(T&& val) { + const K& key = Traits::GetKey(val); + uint32_t hash = Hash(key); + int index = hash & (fCapacity-1); + for (int n = 0; n < fCapacity; n++) { + Slot& s = fSlots[index]; + if (s.empty()) { + // New entry. + s.val = std::move(val); + s.hash = hash; + fCount++; + return &s.val; + } + if (hash == s.hash && key == Traits::GetKey(s.val)) { + // Overwrite previous entry. + // Note: this triggers extra copies when adding the same value repeatedly. + s.val = std::move(val); + return &s.val; + } + + index = this->next(index); + } + SkASSERT(false); + return nullptr; + } + + void resize(int capacity) { + int oldCapacity = fCapacity; + SkDEBUGCODE(int oldCount = fCount); + + fCount = 0; + fCapacity = capacity; + SkAutoTArray oldSlots = std::move(fSlots); + fSlots = SkAutoTArray(capacity); + + for (int i = 0; i < oldCapacity; i++) { + Slot& s = oldSlots[i]; + if (!s.empty()) { + this->uncheckedSet(std::move(s.val)); + } + } + SkASSERT(fCount == oldCount); + } + + int next(int index) const { + index--; + if (index < 0) { index += fCapacity; } + return index; + } + + static uint32_t Hash(const K& key) { + uint32_t hash = Traits::Hash(key) & 0xffffffff; + return hash ? hash : 1; // We reserve hash 0 to mark empty. + } + + struct Slot { + Slot() : val{}, hash(0) {} + Slot(T&& v, uint32_t h) : val(std::move(v)), hash(h) {} + Slot(Slot&& o) { *this = std::move(o); } + Slot& operator=(Slot&& o) { + val = std::move(o.val); + hash = o.hash; + return *this; + } + + bool empty() const { return this->hash == 0; } + + T val; + uint32_t hash; + }; + + int fCount, fCapacity; + SkAutoTArray fSlots; + + SkTHashTable(const SkTHashTable&) = delete; + SkTHashTable& operator=(const SkTHashTable&) = delete; +}; + +// Maps K->V. A more user-friendly wrapper around SkTHashTable, suitable for most use cases. +// K and V are treated as ordinary copyable C++ types, with no assumed relationship between the two. +template +class SkTHashMap { +public: + SkTHashMap() {} + SkTHashMap(SkTHashMap&&) = default; + SkTHashMap& operator=(SkTHashMap&&) = default; + + // Clear the map. + void reset() { fTable.reset(); } + + // How many key/value pairs are in the table? + int count() const { return fTable.count(); } + + // Approximately how many bytes of memory do we use beyond sizeof(*this)? + size_t approxBytesUsed() const { return fTable.approxBytesUsed(); } + + // N.B. The pointers returned by set() and find() are valid only until the next call to set(). + + // Set key to val in the table, replacing any previous value with the same key. + // We copy both key and val, and return a pointer to the value copy now in the table. + V* set(K key, V val) { + Pair* out = fTable.set({std::move(key), std::move(val)}); + return &out->val; + } + + // If there is key/value entry in the table with this key, return a pointer to the value. + // If not, return null. + V* find(const K& key) const { + if (Pair* p = fTable.find(key)) { + return &p->val; + } + return nullptr; + } + + // Remove the key/value entry in the table with this key. + void remove(const K& key) { + SkASSERT(this->find(key)); + fTable.remove(key); + } + + // Call fn on every key/value pair in the table. You may mutate the value but not the key. + template // f(K, V*) or f(const K&, V*) + void foreach(Fn&& fn) { + fTable.foreach([&fn](Pair* p){ fn(p->key, &p->val); }); + } + + // Call fn on every key/value pair in the table. You may not mutate anything. + template // f(K, V), f(const K&, V), f(K, const V&) or f(const K&, const V&). + void foreach(Fn&& fn) const { + fTable.foreach([&fn](const Pair& p){ fn(p.key, p.val); }); + } + +private: + struct Pair { + K key; + V val; + static const K& GetKey(const Pair& p) { return p.key; } + static auto Hash(const K& key) { return HashK()(key); } + }; + + SkTHashTable fTable; + + SkTHashMap(const SkTHashMap&) = delete; + SkTHashMap& operator=(const SkTHashMap&) = delete; +}; + +// A set of T. T is treated as an ordinary copyable C++ type. +template +class SkTHashSet { +public: + SkTHashSet() {} + SkTHashSet(SkTHashSet&&) = default; + SkTHashSet& operator=(SkTHashSet&&) = default; + + // Clear the set. + void reset() { fTable.reset(); } + + // How many items are in the set? + int count() const { return fTable.count(); } + + // Is empty? + bool empty() const { return fTable.count() == 0; } + + // Approximately how many bytes of memory do we use beyond sizeof(*this)? + size_t approxBytesUsed() const { return fTable.approxBytesUsed(); } + + // Copy an item into the set. + void add(T item) { fTable.set(std::move(item)); } + + // Is this item in the set? + bool contains(const T& item) const { return SkToBool(this->find(item)); } + + // If an item equal to this is in the set, return a pointer to it, otherwise null. + // This pointer remains valid until the next call to add(). + const T* find(const T& item) const { return fTable.find(item); } + + // Remove the item in the set equal to this. + void remove(const T& item) { + SkASSERT(this->contains(item)); + fTable.remove(item); + } + + // Call fn on every item in the set. You may not mutate anything. + template // f(T), f(const T&) + void foreach (Fn&& fn) const { + fTable.foreach(fn); + } + +private: + struct Traits { + static const T& GetKey(const T& item) { return item; } + static auto Hash(const T& item) { return HashT()(item); } + }; + SkTHashTable fTable; + + SkTHashSet(const SkTHashSet&) = delete; + SkTHashSet& operator=(const SkTHashSet&) = delete; +}; + +#endif//SkTHash_DEFINED diff --git a/gfx/skia/skia/include/private/SkTLogic.h b/gfx/skia/skia/include/private/SkTLogic.h new file mode 100644 index 0000000000..d33d7bfa9d --- /dev/null +++ b/gfx/skia/skia/include/private/SkTLogic.h @@ -0,0 +1,106 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * + * This header provides some of the helpers (like std::enable_if_t) which will + * become available with C++14 in the type_traits header (in the skstd + * namespace). This header also provides several Skia specific additions such + * as SK_WHEN and the sknonstd namespace. + */ + +#ifndef SkTLogic_DEFINED +#define SkTLogic_DEFINED + +#include +#include // see bug 981264 +#include + +namespace skstd { + +template using bool_constant = std::integral_constant; + +template using conditional_t = typename std::conditional::type; +template using enable_if_t = typename std::enable_if::type; + +template using remove_const_t = typename std::remove_const::type; +template using remove_volatile_t = typename std::remove_volatile::type; +template using remove_cv_t = typename std::remove_cv::type; +template using remove_pointer_t = typename std::remove_pointer::type; +template using remove_reference_t = typename std::remove_reference::type; +template using remove_extent_t = typename std::remove_extent::type; + +template using add_const_t = typename std::add_const::type; +template using add_volatile_t = typename std::add_volatile::type; +template using add_cv_t = typename std::add_cv::type; +template using add_pointer_t = typename std::add_pointer::type; +template using add_lvalue_reference_t = typename std::add_lvalue_reference::type; + +template using common_type_t = typename std::common_type::type; + +template struct index_sequence { + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept { return sizeof...(Ints); } +}; + +template struct make_index_sequence_combine; +template +struct make_index_sequence_combine, skstd::index_sequence> + : skstd::index_sequence +{ }; + +template struct make_index_sequence + : make_index_sequence_combine::type, + typename skstd::make_index_sequence::type>{}; +template<> struct make_index_sequence<0> : skstd::index_sequence< >{}; +template<> struct make_index_sequence<1> : skstd::index_sequence<0>{}; + +struct monostate {}; + +template struct conjunction : std::true_type { }; +template struct conjunction : B0 { }; +template +struct conjunction : std::conditional, B0>::type { }; +} // namespace skstd + +// The sknonstd namespace contains things we would like to be proposed and feel std-ish. +namespace sknonstd { + +// The name 'copy' here is fraught with peril. In this case it means 'append', not 'overwrite'. +// Alternate proposed names are 'propagate', 'augment', or 'append' (and 'add', but already taken). +// std::experimental::propagate_const already exists for other purposes in TSv2. +// These also follow the pattern used by boost. +template struct copy_const { + using type = skstd::conditional_t::value, skstd::add_const_t, D>; +}; +template using copy_const_t = typename copy_const::type; + +template struct copy_volatile { + using type = skstd::conditional_t::value, skstd::add_volatile_t, D>; +}; +template using copy_volatile_t = typename copy_volatile::type; + +template struct copy_cv { + using type = copy_volatile_t, S>; +}; +template using copy_cv_t = typename copy_cv::type; + +// The name 'same' here means 'overwrite'. +// Alternate proposed names are 'replace', 'transfer', or 'qualify_from'. +// same_xxx can be written as copy_xxx, S> +template using same_const = copy_const, S>; +template using same_const_t = typename same_const::type; +template using same_volatile =copy_volatile,S>; +template using same_volatile_t = typename same_volatile::type; +template using same_cv = copy_cv, S>; +template using same_cv_t = typename same_cv::type; + +} // namespace sknonstd + +// Just a pithier wrapper for enable_if_t. +#define SK_WHEN(condition, T) skstd::enable_if_t + +#endif diff --git a/gfx/skia/skia/include/private/SkTemplates.h b/gfx/skia/skia/include/private/SkTemplates.h new file mode 100644 index 0000000000..6b3969459a --- /dev/null +++ b/gfx/skia/skia/include/private/SkTemplates.h @@ -0,0 +1,455 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTemplates_DEFINED +#define SkTemplates_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTLogic.h" + +#include +#include +#include +#include +#include +#include + +/** \file SkTemplates.h + + This file contains light-weight template classes for type-safe and exception-safe + resource management. +*/ + +/** + * Marks a local variable as known to be unused (to avoid warnings). + * Note that this does *not* prevent the local variable from being optimized away. + */ +template inline void sk_ignore_unused_variable(const T&) { } + +/** + * Returns a pointer to a D which comes immediately after S[count]. + */ +template static D* SkTAfter(S* ptr, size_t count = 1) { + return reinterpret_cast(ptr + count); +} + +/** + * Returns a pointer to a D which comes byteOffset bytes after S. + */ +template static D* SkTAddOffset(S* ptr, size_t byteOffset) { + // The intermediate char* has the same cv-ness as D as this produces better error messages. + // This relies on the fact that reinterpret_cast can add constness, but cannot remove it. + return reinterpret_cast(reinterpret_cast*>(ptr) + byteOffset); +} + +// TODO: when C++17 the language is available, use template +template struct SkFunctionWrapper { + template + auto operator()(Args&&... args) const -> decltype(P(std::forward(args)...)) { + return P(std::forward(args)...); + } +}; + +/** \class SkAutoTCallVProc + + Call a function when this goes out of scope. The template uses two + parameters, the object, and a function that is to be called in the destructor. + If release() is called, the object reference is set to null. If the object + reference is null when the destructor is called, we do not call the + function. +*/ +template class SkAutoTCallVProc + : public std::unique_ptr, P>> { +public: + SkAutoTCallVProc(T* obj) + : std::unique_ptr, P>>(obj) {} + + operator T*() const { return this->get(); } +}; + +/** Allocate an array of T elements, and free the array in the destructor + */ +template class SkAutoTArray { +public: + SkAutoTArray() {} + /** Allocate count number of T elements + */ + explicit SkAutoTArray(int count) { + SkASSERT(count >= 0); + if (count) { + fArray.reset(new T[count]); + } + SkDEBUGCODE(fCount = count;) + } + + SkAutoTArray(SkAutoTArray&& other) : fArray(std::move(other.fArray)) { + SkDEBUGCODE(fCount = other.fCount; other.fCount = 0;) + } + SkAutoTArray& operator=(SkAutoTArray&& other) { + if (this != &other) { + fArray = std::move(other.fArray); + SkDEBUGCODE(fCount = other.fCount; other.fCount = 0;) + } + return *this; + } + + /** Reallocates given a new count. Reallocation occurs even if new count equals old count. + */ + void reset(int count) { *this = SkAutoTArray(count); } + + /** Return the array of T elements. Will be NULL if count == 0 + */ + T* get() const { return fArray.get(); } + + /** Return the nth element in the array + */ + T& operator[](int index) const { + SkASSERT((unsigned)index < (unsigned)fCount); + return fArray[index]; + } + +private: + std::unique_ptr fArray; + SkDEBUGCODE(int fCount = 0;) +}; + +/** Wraps SkAutoTArray, with room for kCountRequested elements preallocated. + */ +template class SkAutoSTArray { +public: + SkAutoSTArray(SkAutoSTArray&&) = delete; + SkAutoSTArray(const SkAutoSTArray&) = delete; + SkAutoSTArray& operator=(SkAutoSTArray&&) = delete; + SkAutoSTArray& operator=(const SkAutoSTArray&) = delete; + + /** Initialize with no objects */ + SkAutoSTArray() { + fArray = nullptr; + fCount = 0; + } + + /** Allocate count number of T elements + */ + SkAutoSTArray(int count) { + fArray = nullptr; + fCount = 0; + this->reset(count); + } + + ~SkAutoSTArray() { + this->reset(0); + } + + /** Destroys previous objects in the array and default constructs count number of objects */ + void reset(int count) { + T* start = fArray; + T* iter = start + fCount; + while (iter > start) { + (--iter)->~T(); + } + + SkASSERT(count >= 0); + if (fCount != count) { + if (fCount > kCount) { + // 'fArray' was allocated last time so free it now + SkASSERT((T*) fStorage != fArray); + sk_free(fArray); + } + + if (count > kCount) { + fArray = (T*) sk_malloc_throw(count, sizeof(T)); + } else if (count > 0) { + fArray = (T*) fStorage; + } else { + fArray = nullptr; + } + + fCount = count; + } + + iter = fArray; + T* stop = fArray + count; + while (iter < stop) { + new (iter++) T; + } + } + + /** Return the number of T elements in the array + */ + int count() const { return fCount; } + + /** Return the array of T elements. Will be NULL if count == 0 + */ + T* get() const { return fArray; } + + T* begin() { return fArray; } + + const T* begin() const { return fArray; } + + T* end() { return fArray + fCount; } + + const T* end() const { return fArray + fCount; } + + /** Return the nth element in the array + */ + T& operator[](int index) const { + SkASSERT(index < fCount); + return fArray[index]; + } + +private: +#if defined(SK_BUILD_FOR_GOOGLE3) + // Stack frame size is limited for SK_BUILD_FOR_GOOGLE3. 4k is less than the actual max, but some functions + // have multiple large stack allocations. + static const int kMaxBytes = 4 * 1024; + static const int kCount = kCountRequested * sizeof(T) > kMaxBytes + ? kMaxBytes / sizeof(T) + : kCountRequested; +#else + static const int kCount = kCountRequested; +#endif + + int fCount; + T* fArray; + // since we come right after fArray, fStorage should be properly aligned + char fStorage[kCount * sizeof(T)]; +}; + +/** Manages an array of T elements, freeing the array in the destructor. + * Does NOT call any constructors/destructors on T (T must be POD). + */ +template class SkAutoTMalloc { +public: + /** Takes ownership of the ptr. The ptr must be a value which can be passed to sk_free. */ + explicit SkAutoTMalloc(T* ptr = nullptr) : fPtr(ptr) {} + + /** Allocates space for 'count' Ts. */ + explicit SkAutoTMalloc(size_t count) + : fPtr(count ? (T*)sk_malloc_throw(count, sizeof(T)) : nullptr) {} + + SkAutoTMalloc(SkAutoTMalloc&&) = default; + SkAutoTMalloc& operator=(SkAutoTMalloc&&) = default; + + /** Resize the memory area pointed to by the current ptr preserving contents. */ + void realloc(size_t count) { + fPtr.reset(count ? (T*)sk_realloc_throw(fPtr.release(), count * sizeof(T)) : nullptr); + } + + /** Resize the memory area pointed to by the current ptr without preserving contents. */ + T* reset(size_t count = 0) { + fPtr.reset(count ? (T*)sk_malloc_throw(count, sizeof(T)) : nullptr); + return this->get(); + } + + T* get() const { return fPtr.get(); } + + operator T*() { return fPtr.get(); } + + operator const T*() const { return fPtr.get(); } + + T& operator[](int index) { return fPtr.get()[index]; } + + const T& operator[](int index) const { return fPtr.get()[index]; } + + /** + * Transfer ownership of the ptr to the caller, setting the internal + * pointer to NULL. Note that this differs from get(), which also returns + * the pointer, but it does not transfer ownership. + */ + T* release() { return fPtr.release(); } + +private: + std::unique_ptr> fPtr; +}; + +template class SkAutoSTMalloc { +public: + SkAutoSTMalloc() : fPtr(fTStorage) {} + + SkAutoSTMalloc(size_t count) { + if (count > kCount) { + fPtr = (T*)sk_malloc_throw(count, sizeof(T)); + } else if (count) { + fPtr = fTStorage; + } else { + fPtr = nullptr; + } + } + + SkAutoSTMalloc(SkAutoSTMalloc&&) = delete; + SkAutoSTMalloc(const SkAutoSTMalloc&) = delete; + SkAutoSTMalloc& operator=(SkAutoSTMalloc&&) = delete; + SkAutoSTMalloc& operator=(const SkAutoSTMalloc&) = delete; + + ~SkAutoSTMalloc() { + if (fPtr != fTStorage) { + sk_free(fPtr); + } + } + + // doesn't preserve contents + T* reset(size_t count) { + if (fPtr != fTStorage) { + sk_free(fPtr); + } + if (count > kCount) { + fPtr = (T*)sk_malloc_throw(count, sizeof(T)); + } else if (count) { + fPtr = fTStorage; + } else { + fPtr = nullptr; + } + return fPtr; + } + + T* get() const { return fPtr; } + + operator T*() { + return fPtr; + } + + operator const T*() const { + return fPtr; + } + + T& operator[](int index) { + return fPtr[index]; + } + + const T& operator[](int index) const { + return fPtr[index]; + } + + // Reallocs the array, can be used to shrink the allocation. Makes no attempt to be intelligent + void realloc(size_t count) { + if (count > kCount) { + if (fPtr == fTStorage) { + fPtr = (T*)sk_malloc_throw(count, sizeof(T)); + memcpy(fPtr, fTStorage, kCount * sizeof(T)); + } else { + fPtr = (T*)sk_realloc_throw(fPtr, count, sizeof(T)); + } + } else if (count) { + if (fPtr != fTStorage) { + fPtr = (T*)sk_realloc_throw(fPtr, count, sizeof(T)); + } + } else { + this->reset(0); + } + } + +private: + // Since we use uint32_t storage, we might be able to get more elements for free. + static const size_t kCountWithPadding = SkAlign4(kCountRequested*sizeof(T)) / sizeof(T); +#if defined(SK_BUILD_FOR_GOOGLE3) + // Stack frame size is limited for SK_BUILD_FOR_GOOGLE3. 4k is less than the actual max, but some functions + // have multiple large stack allocations. + static const size_t kMaxBytes = 4 * 1024; + static const size_t kCount = kCountRequested * sizeof(T) > kMaxBytes + ? kMaxBytes / sizeof(T) + : kCountWithPadding; +#else + static const size_t kCount = kCountWithPadding; +#endif + + T* fPtr; + union { + uint32_t fStorage32[SkAlign4(kCount*sizeof(T)) >> 2]; + T fTStorage[1]; // do NOT want to invoke T::T() + }; +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Pass the object and the storage that was offered during SkInPlaceNewCheck, and this will + * safely destroy (and free if it was dynamically allocated) the object. + */ +template void SkInPlaceDeleteCheck(T* obj, void* storage) { + if (storage == obj) { + obj->~T(); + } else { + delete obj; + } +} + +/** + * Allocates T, using storage if it is large enough, and allocating on the heap (via new) if + * storage is not large enough. + * + * obj = SkInPlaceNewCheck(storage, size); + * ... + * SkInPlaceDeleteCheck(obj, storage); + */ +template +T* SkInPlaceNewCheck(void* storage, size_t size, Args&&... args) { + return (sizeof(T) <= size) ? new (storage) T(std::forward(args)...) + : new T(std::forward(args)...); +} +/** + * Reserves memory that is aligned on double and pointer boundaries. + * Hopefully this is sufficient for all practical purposes. + */ +template class SkAlignedSStorage { +public: + SkAlignedSStorage() {} + SkAlignedSStorage(SkAlignedSStorage&&) = delete; + SkAlignedSStorage(const SkAlignedSStorage&) = delete; + SkAlignedSStorage& operator=(SkAlignedSStorage&&) = delete; + SkAlignedSStorage& operator=(const SkAlignedSStorage&) = delete; + + size_t size() const { return N; } + void* get() { return fData; } + const void* get() const { return fData; } + +private: + union { + void* fPtr; + double fDouble; + char fData[N]; + }; +}; + +/** + * Reserves memory that is aligned on double and pointer boundaries. + * Hopefully this is sufficient for all practical purposes. Otherwise, + * we have to do some arcane trickery to determine alignment of non-POD + * types. Lifetime of the memory is the lifetime of the object. + */ +template class SkAlignedSTStorage { +public: + SkAlignedSTStorage() {} + SkAlignedSTStorage(SkAlignedSTStorage&&) = delete; + SkAlignedSTStorage(const SkAlignedSTStorage&) = delete; + SkAlignedSTStorage& operator=(SkAlignedSTStorage&&) = delete; + SkAlignedSTStorage& operator=(const SkAlignedSTStorage&) = delete; + + /** + * Returns void* because this object does not initialize the + * memory. Use placement new for types that require a cons. + */ + void* get() { return fStorage.get(); } + const void* get() const { return fStorage.get(); } +private: + SkAlignedSStorage fStorage; +}; + +using SkAutoFree = std::unique_ptr>; + +template +constexpr auto SkMakeArrayFromIndexSequence(C c, std::index_sequence is) +-> std::array())), sizeof...(Is)> { + return {{ c(Is)... }}; +} + +template constexpr auto SkMakeArray(C c) +-> std::array::value_type>())), N> { + return SkMakeArrayFromIndexSequence(c, std::make_index_sequence{}); +} + +#endif diff --git a/gfx/skia/skia/include/private/SkThreadAnnotations.h b/gfx/skia/skia/include/private/SkThreadAnnotations.h new file mode 100644 index 0000000000..fd312b5885 --- /dev/null +++ b/gfx/skia/skia/include/private/SkThreadAnnotations.h @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkThreadAnnotations_DEFINED +#define SkThreadAnnotations_DEFINED + +// The bulk of this code is cribbed from: +// http://clang.llvm.org/docs/ThreadSafetyAnalysis.html + +#if defined(__clang__) && (!defined(SWIG)) +#define SK_THREAD_ANNOTATION_ATTRIBUTE(x) __attribute__((x)) +#else +#define SK_THREAD_ANNOTATION_ATTRIBUTE(x) // no-op +#endif + +#define SK_CAPABILITY(x) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(capability(x)) + +#define SK_SCOPED_CAPABILITY \ + SK_THREAD_ANNOTATION_ATTRIBUTE(scoped_lockable) + +#define SK_GUARDED_BY(x) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(guarded_by(x)) + +#define SK_PT_GUARDED_BY(x) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(pt_guarded_by(x)) + +#define SK_ACQUIRED_BEFORE(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(acquired_before(__VA_ARGS__)) + +#define SK_ACQUIRED_AFTER(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(acquired_after(__VA_ARGS__)) + +#define SK_REQUIRES(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(requires_capability(__VA_ARGS__)) + +#define SK_REQUIRES_SHARED(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(requires_shared_capability(__VA_ARGS__)) + +#define SK_ACQUIRE(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(acquire_capability(__VA_ARGS__)) + +#define SK_ACQUIRE_SHARED(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(acquire_shared_capability(__VA_ARGS__)) + +// Would be SK_RELEASE, but that is already in use by SkPostConfig. +#define SK_RELEASE_CAPABILITY(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(release_capability(__VA_ARGS__)) + +// For symmetry with SK_RELEASE_CAPABILITY. +#define SK_RELEASE_SHARED_CAPABILITY(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(release_shared_capability(__VA_ARGS__)) + +#define SK_TRY_ACQUIRE(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(try_acquire_capability(__VA_ARGS__)) + +#define SK_TRY_ACQUIRE_SHARED(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(try_acquire_shared_capability(__VA_ARGS__)) + +#define SK_EXCLUDES(...) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(locks_excluded(__VA_ARGS__)) + +#define SK_ASSERT_CAPABILITY(x) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(assert_capability(x)) + +#define SK_ASSERT_SHARED_CAPABILITY(x) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(assert_shared_capability(x)) + +#define SK_RETURN_CAPABILITY(x) \ + SK_THREAD_ANNOTATION_ATTRIBUTE(lock_returned(x)) + +#define SK_NO_THREAD_SAFETY_ANALYSIS \ + SK_THREAD_ANNOTATION_ATTRIBUTE(no_thread_safety_analysis) + + +#endif // SkThreadAnnotations_DEFINED diff --git a/gfx/skia/skia/include/private/SkThreadID.h b/gfx/skia/skia/include/private/SkThreadID.h new file mode 100644 index 0000000000..06b9be7317 --- /dev/null +++ b/gfx/skia/skia/include/private/SkThreadID.h @@ -0,0 +1,19 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkThreadID_DEFINED +#define SkThreadID_DEFINED + +#include "include/core/SkTypes.h" + +typedef int64_t SkThreadID; + +SkThreadID SkGetThreadID(); + +const SkThreadID kIllegalThreadID = 0; + +#endif // SkThreadID_DEFINED diff --git a/gfx/skia/skia/include/private/SkTo.h b/gfx/skia/skia/include/private/SkTo.h new file mode 100644 index 0000000000..d788f7b269 --- /dev/null +++ b/gfx/skia/skia/include/private/SkTo.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkTo_DEFINED +#define SkTo_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkTFitsIn.h" + +template constexpr D SkTo(S s) { + return SkASSERT(SkTFitsIn(s)), + static_cast(s); +} + +template constexpr int8_t SkToS8(S x) { return SkTo(x); } +template constexpr uint8_t SkToU8(S x) { return SkTo(x); } +template constexpr int16_t SkToS16(S x) { return SkTo(x); } +template constexpr uint16_t SkToU16(S x) { return SkTo(x); } +template constexpr int32_t SkToS32(S x) { return SkTo(x); } +template constexpr uint32_t SkToU32(S x) { return SkTo(x); } +template constexpr int SkToInt(S x) { return SkTo(x); } +template constexpr unsigned SkToUInt(S x) { return SkTo(x); } +template constexpr size_t SkToSizeT(S x) { return SkTo(x); } + +#endif // SkTo_DEFINED diff --git a/gfx/skia/skia/include/private/SkVx.h b/gfx/skia/skia/include/private/SkVx.h new file mode 100644 index 0000000000..7e71da6e43 --- /dev/null +++ b/gfx/skia/skia/include/private/SkVx.h @@ -0,0 +1,527 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKVX_DEFINED +#define SKVX_DEFINED + +// skvx::Vec are SIMD vectors of N T's, a v1.5 successor to SkNx. +// +// This time we're leaning a bit less on platform-specific intrinsics and a bit +// more on Clang/GCC vector extensions, but still keeping the option open to +// drop in platform-specific intrinsics, actually more easily than before. +// +// We've also fixed a few of the caveats that used to make SkNx awkward to work +// with across translation units. skvx::Vec always has N*sizeof(T) size +// and alignment[1][2] and is safe to use across translation units freely. +// +// [1] Ideally we'd only align to T, but that tanks ARMv7 NEON codegen. +// [2] Some compilers barf if we try to use N*sizeof(T), so instead we leave them at T. + +// Please try to keep this file independent of Skia headers. +#include // std::min, std::max +#include // std::ceil, std::floor, std::trunc, std::round, std::sqrt, etc. +#include // intXX_t +#include // memcpy() +#include // std::initializer_list + +#if defined(__SSE__) + #include +#elif defined(__ARM_NEON) + #include +#endif + +#if !defined(__clang__) && defined(__GNUC__) && defined(__mips64) + // GCC 7 hits an internal compiler error when targeting MIPS64. + #define SKVX_ALIGNMENT +#elif !defined(__clang__) && defined(_MSC_VER) && defined(_M_IX86) + // Our SkVx unit tests fail when built by MSVC for 32-bit x86. + #define SKVX_ALIGNMENT +#else + #define SKVX_ALIGNMENT alignas(N * sizeof(T)) +#endif + + +namespace skvx { + +// All Vec have the same simple memory layout, the same as `T vec[N]`. +// This gives Vec a consistent ABI, letting them pass between files compiled with +// different instruction sets (e.g. SSE2 and AVX2) without fear of ODR violation. +template +struct SKVX_ALIGNMENT Vec { + static_assert((N & (N-1)) == 0, "N must be a power of 2."); + static_assert(sizeof(T) >= alignof(T), "What kind of crazy T is this?"); + + Vec lo, hi; + + // Methods belong here in the class declaration of Vec only if: + // - they must be here, like constructors or operator[]; + // - they'll definitely never want a specialized implementation. + // Other operations on Vec should be defined outside the type. + + Vec() = default; + + template ::value>::type> + Vec(U x) : lo(x), hi(x) {} + + Vec(std::initializer_list xs) { + T vals[N] = {0}; + memcpy(vals, xs.begin(), std::min(xs.size(), (size_t)N)*sizeof(T)); + + lo = Vec::Load(vals + 0); + hi = Vec::Load(vals + N/2); + } + + T operator[](int i) const { return i < N/2 ? lo[i] : hi[i-N/2]; } + T& operator[](int i) { return i < N/2 ? lo[i] : hi[i-N/2]; } + + static Vec Load(const void* ptr) { + Vec v; + memcpy(&v, ptr, sizeof(Vec)); + return v; + } + void store(void* ptr) const { + memcpy(ptr, this, sizeof(Vec)); + } +}; + +template +struct Vec<1,T> { + T val; + + Vec() = default; + + template ::value>::type> + Vec(U x) : val(x) {} + + Vec(std::initializer_list xs) : val(xs.size() ? *xs.begin() : 0) {} + + T operator[](int) const { return val; } + T& operator[](int) { return val; } + + static Vec Load(const void* ptr) { + Vec v; + memcpy(&v, ptr, sizeof(Vec)); + return v; + } + void store(void* ptr) const { + memcpy(ptr, this, sizeof(Vec)); + } +}; + +#if defined(__GNUC__) && !defined(__clang__) && defined(__SSE__) + // GCC warns about ABI changes when returning >= 32 byte vectors when -mavx is not enabled. + // This only happens for types like VExt whose ABI we don't care about, not for Vec itself. + #pragma GCC diagnostic ignored "-Wpsabi" +#endif + +// Helps tamp down on the repetitive boilerplate. +#define SIT template < typename T> static inline +#define SINT template static inline +#define SINTU template ::value>::type> \ + static inline + +template +static inline D bit_pun(const S& s) { + static_assert(sizeof(D) == sizeof(S), ""); + D d; + memcpy(&d, &s, sizeof(D)); + return d; +} + +// Translate from a value type T to its corresponding Mask, the result of a comparison. +template struct Mask { using type = T; }; +template <> struct Mask { using type = int32_t; }; +template <> struct Mask { using type = int64_t; }; +template using M = typename Mask::type; + +// Join two Vec into one Vec<2N,T>. +SINT Vec<2*N,T> join(const Vec& lo, const Vec& hi) { + Vec<2*N,T> v; + v.lo = lo; + v.hi = hi; + return v; +} + +// We have two default strategies for implementing most operations: +// 1) lean on Clang/GCC vector extensions when available; +// 2) recurse to scalar portable implementations when not. +// At the end we can drop in platform-specific implementations that override either default. + +#if !defined(SKNX_NO_SIMD) && (defined(__clang__) || defined(__GNUC__)) + + // VExt types have the same size as Vec and support most operations directly. + // N.B. VExt alignment is N*alignof(T), stricter than Vec's alignof(T). + #if defined(__clang__) + template + using VExt = T __attribute__((ext_vector_type(N))); + + #elif defined(__GNUC__) + template + struct VExtHelper { + typedef T __attribute__((vector_size(N*sizeof(T)))) type; + }; + + template + using VExt = typename VExtHelper::type; + + // For some reason some (new!) versions of GCC cannot seem to deduce N in the generic + // to_vec() below for N=4 and T=float. This workaround seems to help... + static inline Vec<4,float> to_vec(VExt<4,float> v) { return bit_pun>(v); } + #endif + + SINT VExt to_vext(const Vec& v) { return bit_pun>(v); } + SINT Vec to_vec(const VExt& v) { return bit_pun>(v); } + + SINT Vec operator+(const Vec& x, const Vec& y) { return to_vec(to_vext(x) + to_vext(y)); } + SINT Vec operator-(const Vec& x, const Vec& y) { return to_vec(to_vext(x) - to_vext(y)); } + SINT Vec operator*(const Vec& x, const Vec& y) { return to_vec(to_vext(x) * to_vext(y)); } + SINT Vec operator/(const Vec& x, const Vec& y) { return to_vec(to_vext(x) / to_vext(y)); } + + SINT Vec operator^(const Vec& x, const Vec& y) { return to_vec(to_vext(x) ^ to_vext(y)); } + SINT Vec operator&(const Vec& x, const Vec& y) { return to_vec(to_vext(x) & to_vext(y)); } + SINT Vec operator|(const Vec& x, const Vec& y) { return to_vec(to_vext(x) | to_vext(y)); } + + SINT Vec operator!(const Vec& x) { return to_vec(!to_vext(x)); } + SINT Vec operator-(const Vec& x) { return to_vec(-to_vext(x)); } + SINT Vec operator~(const Vec& x) { return to_vec(~to_vext(x)); } + + SINT Vec operator<<(const Vec& x, int bits) { return to_vec(to_vext(x) << bits); } + SINT Vec operator>>(const Vec& x, int bits) { return to_vec(to_vext(x) >> bits); } + + SINT Vec> operator==(const Vec& x, const Vec& y) { return bit_pun>>(to_vext(x) == to_vext(y)); } + SINT Vec> operator!=(const Vec& x, const Vec& y) { return bit_pun>>(to_vext(x) != to_vext(y)); } + SINT Vec> operator<=(const Vec& x, const Vec& y) { return bit_pun>>(to_vext(x) <= to_vext(y)); } + SINT Vec> operator>=(const Vec& x, const Vec& y) { return bit_pun>>(to_vext(x) >= to_vext(y)); } + SINT Vec> operator< (const Vec& x, const Vec& y) { return bit_pun>>(to_vext(x) < to_vext(y)); } + SINT Vec> operator> (const Vec& x, const Vec& y) { return bit_pun>>(to_vext(x) > to_vext(y)); } + +#else + + // Either SKNX_NO_SIMD is defined, or Clang/GCC vector extensions are not available. + // We'll implement things portably, in a way that should be easily autovectorizable. + + // N == 1 scalar implementations. + SIT Vec<1,T> operator+(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val + y.val; } + SIT Vec<1,T> operator-(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val - y.val; } + SIT Vec<1,T> operator*(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val * y.val; } + SIT Vec<1,T> operator/(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val / y.val; } + + SIT Vec<1,T> operator^(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val ^ y.val; } + SIT Vec<1,T> operator&(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val & y.val; } + SIT Vec<1,T> operator|(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val | y.val; } + + SIT Vec<1,T> operator!(const Vec<1,T>& x) { return !x.val; } + SIT Vec<1,T> operator-(const Vec<1,T>& x) { return -x.val; } + SIT Vec<1,T> operator~(const Vec<1,T>& x) { return ~x.val; } + + SIT Vec<1,T> operator<<(const Vec<1,T>& x, int bits) { return x.val << bits; } + SIT Vec<1,T> operator>>(const Vec<1,T>& x, int bits) { return x.val >> bits; } + + SIT Vec<1,M> operator==(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val == y.val ? ~0 : 0; } + SIT Vec<1,M> operator!=(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val != y.val ? ~0 : 0; } + SIT Vec<1,M> operator<=(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val <= y.val ? ~0 : 0; } + SIT Vec<1,M> operator>=(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val >= y.val ? ~0 : 0; } + SIT Vec<1,M> operator< (const Vec<1,T>& x, const Vec<1,T>& y) { return x.val < y.val ? ~0 : 0; } + SIT Vec<1,M> operator> (const Vec<1,T>& x, const Vec<1,T>& y) { return x.val > y.val ? ~0 : 0; } + + // All default N != 1 implementations just recurse on lo and hi halves. + SINT Vec operator+(const Vec& x, const Vec& y) { return join(x.lo + y.lo, x.hi + y.hi); } + SINT Vec operator-(const Vec& x, const Vec& y) { return join(x.lo - y.lo, x.hi - y.hi); } + SINT Vec operator*(const Vec& x, const Vec& y) { return join(x.lo * y.lo, x.hi * y.hi); } + SINT Vec operator/(const Vec& x, const Vec& y) { return join(x.lo / y.lo, x.hi / y.hi); } + + SINT Vec operator^(const Vec& x, const Vec& y) { return join(x.lo ^ y.lo, x.hi ^ y.hi); } + SINT Vec operator&(const Vec& x, const Vec& y) { return join(x.lo & y.lo, x.hi & y.hi); } + SINT Vec operator|(const Vec& x, const Vec& y) { return join(x.lo | y.lo, x.hi | y.hi); } + + SINT Vec operator!(const Vec& x) { return join(!x.lo, !x.hi); } + SINT Vec operator-(const Vec& x) { return join(-x.lo, -x.hi); } + SINT Vec operator~(const Vec& x) { return join(~x.lo, ~x.hi); } + + SINT Vec operator<<(const Vec& x, int bits) { return join(x.lo << bits, x.hi << bits); } + SINT Vec operator>>(const Vec& x, int bits) { return join(x.lo >> bits, x.hi >> bits); } + + SINT Vec> operator==(const Vec& x, const Vec& y) { return join(x.lo == y.lo, x.hi == y.hi); } + SINT Vec> operator!=(const Vec& x, const Vec& y) { return join(x.lo != y.lo, x.hi != y.hi); } + SINT Vec> operator<=(const Vec& x, const Vec& y) { return join(x.lo <= y.lo, x.hi <= y.hi); } + SINT Vec> operator>=(const Vec& x, const Vec& y) { return join(x.lo >= y.lo, x.hi >= y.hi); } + SINT Vec> operator< (const Vec& x, const Vec& y) { return join(x.lo < y.lo, x.hi < y.hi); } + SINT Vec> operator> (const Vec& x, const Vec& y) { return join(x.lo > y.lo, x.hi > y.hi); } +#endif + +// Some operations we want are not expressible with Clang/GCC vector +// extensions, so we implement them using the recursive approach. + +// N == 1 scalar implementations. +SIT Vec<1,T> if_then_else(const Vec<1,M>& cond, const Vec<1,T>& t, const Vec<1,T>& e) { + auto t_bits = bit_pun>(t), + e_bits = bit_pun>(e); + return bit_pun( (cond.val & t_bits) | (~cond.val & e_bits) ); +} + +SIT bool any(const Vec<1,T>& x) { return x.val != 0; } +SIT bool all(const Vec<1,T>& x) { return x.val != 0; } + +SIT T min(const Vec<1,T>& x) { return x.val; } +SIT T max(const Vec<1,T>& x) { return x.val; } + +SIT Vec<1,T> min(const Vec<1,T>& x, const Vec<1,T>& y) { return std::min(x.val, y.val); } +SIT Vec<1,T> max(const Vec<1,T>& x, const Vec<1,T>& y) { return std::max(x.val, y.val); } + +SIT Vec<1,T> ceil(const Vec<1,T>& x) { return std:: ceil(x.val); } +SIT Vec<1,T> floor(const Vec<1,T>& x) { return std::floor(x.val); } +SIT Vec<1,T> trunc(const Vec<1,T>& x) { return std::trunc(x.val); } +SIT Vec<1,T> round(const Vec<1,T>& x) { return std::round(x.val); } +SIT Vec<1,T> sqrt(const Vec<1,T>& x) { return std:: sqrt(x.val); } +SIT Vec<1,T> abs(const Vec<1,T>& x) { return std:: abs(x.val); } + +SIT Vec<1,T> rcp(const Vec<1,T>& x) { return 1 / x.val; } +SIT Vec<1,T> rsqrt(const Vec<1,T>& x) { return rcp(sqrt(x)); } +SIT Vec<1,T> mad(const Vec<1,T>& f, + const Vec<1,T>& m, + const Vec<1,T>& a) { return f*m+a; } + +// All default N != 1 implementations just recurse on lo and hi halves. +SINT Vec if_then_else(const Vec>& cond, const Vec& t, const Vec& e) { + return join(if_then_else(cond.lo, t.lo, e.lo), + if_then_else(cond.hi, t.hi, e.hi)); +} + +SINT bool any(const Vec& x) { return any(x.lo) || any(x.hi); } +SINT bool all(const Vec& x) { return all(x.lo) && all(x.hi); } + +SINT T min(const Vec& x) { return std::min(min(x.lo), min(x.hi)); } +SINT T max(const Vec& x) { return std::max(max(x.lo), max(x.hi)); } + +SINT Vec min(const Vec& x, const Vec& y) { return join(min(x.lo, y.lo), min(x.hi, y.hi)); } +SINT Vec max(const Vec& x, const Vec& y) { return join(max(x.lo, y.lo), max(x.hi, y.hi)); } + +SINT Vec ceil(const Vec& x) { return join( ceil(x.lo), ceil(x.hi)); } +SINT Vec floor(const Vec& x) { return join(floor(x.lo), floor(x.hi)); } +SINT Vec trunc(const Vec& x) { return join(trunc(x.lo), trunc(x.hi)); } +SINT Vec round(const Vec& x) { return join(round(x.lo), round(x.hi)); } +SINT Vec sqrt(const Vec& x) { return join( sqrt(x.lo), sqrt(x.hi)); } +SINT Vec abs(const Vec& x) { return join( abs(x.lo), abs(x.hi)); } + +SINT Vec rcp(const Vec& x) { return join( rcp(x.lo), rcp(x.hi)); } +SINT Vec rsqrt(const Vec& x) { return join(rsqrt(x.lo), rsqrt(x.hi)); } +SINT Vec mad(const Vec& f, + const Vec& m, + const Vec& a) { return join(mad(f.lo, m.lo, a.lo), mad(f.hi, m.hi, a.hi)); } + + +// Scalar/vector operations just splat the scalar to a vector... +SINTU Vec operator+ (U x, const Vec& y) { return Vec(x) + y; } +SINTU Vec operator- (U x, const Vec& y) { return Vec(x) - y; } +SINTU Vec operator* (U x, const Vec& y) { return Vec(x) * y; } +SINTU Vec operator/ (U x, const Vec& y) { return Vec(x) / y; } +SINTU Vec operator^ (U x, const Vec& y) { return Vec(x) ^ y; } +SINTU Vec operator& (U x, const Vec& y) { return Vec(x) & y; } +SINTU Vec operator| (U x, const Vec& y) { return Vec(x) | y; } +SINTU Vec> operator==(U x, const Vec& y) { return Vec(x) == y; } +SINTU Vec> operator!=(U x, const Vec& y) { return Vec(x) != y; } +SINTU Vec> operator<=(U x, const Vec& y) { return Vec(x) <= y; } +SINTU Vec> operator>=(U x, const Vec& y) { return Vec(x) >= y; } +SINTU Vec> operator< (U x, const Vec& y) { return Vec(x) < y; } +SINTU Vec> operator> (U x, const Vec& y) { return Vec(x) > y; } +SINTU Vec min(U x, const Vec& y) { return min(Vec(x), y); } +SINTU Vec max(U x, const Vec& y) { return max(Vec(x), y); } + +// ... and same deal for vector/scalar operations. +SINTU Vec operator+ (const Vec& x, U y) { return x + Vec(y); } +SINTU Vec operator- (const Vec& x, U y) { return x - Vec(y); } +SINTU Vec operator* (const Vec& x, U y) { return x * Vec(y); } +SINTU Vec operator/ (const Vec& x, U y) { return x / Vec(y); } +SINTU Vec operator^ (const Vec& x, U y) { return x ^ Vec(y); } +SINTU Vec operator& (const Vec& x, U y) { return x & Vec(y); } +SINTU Vec operator| (const Vec& x, U y) { return x | Vec(y); } +SINTU Vec> operator==(const Vec& x, U y) { return x == Vec(y); } +SINTU Vec> operator!=(const Vec& x, U y) { return x != Vec(y); } +SINTU Vec> operator<=(const Vec& x, U y) { return x <= Vec(y); } +SINTU Vec> operator>=(const Vec& x, U y) { return x >= Vec(y); } +SINTU Vec> operator< (const Vec& x, U y) { return x < Vec(y); } +SINTU Vec> operator> (const Vec& x, U y) { return x > Vec(y); } +SINTU Vec min(const Vec& x, U y) { return min(x, Vec(y)); } +SINTU Vec max(const Vec& x, U y) { return max(x, Vec(y)); } + +// All vector/scalar combinations for mad() with at least one vector. +SINTU Vec mad(U f, const Vec& m, const Vec& a) { return Vec(f)*m + a; } +SINTU Vec mad(const Vec& f, U m, const Vec& a) { return f*Vec(m) + a; } +SINTU Vec mad(const Vec& f, const Vec& m, U a) { return f*m + Vec(a); } +SINTU Vec mad(const Vec& f, U m, U a) { return f*Vec(m) + Vec(a); } +SINTU Vec mad(U f, const Vec& m, U a) { return Vec(f)*m + Vec(a); } +SINTU Vec mad(U f, U m, const Vec& a) { return Vec(f)*Vec(m) + a; } + +// The various op= operators, for vectors... +SINT Vec& operator+=(Vec& x, const Vec& y) { return (x = x + y); } +SINT Vec& operator-=(Vec& x, const Vec& y) { return (x = x - y); } +SINT Vec& operator*=(Vec& x, const Vec& y) { return (x = x * y); } +SINT Vec& operator/=(Vec& x, const Vec& y) { return (x = x / y); } +SINT Vec& operator^=(Vec& x, const Vec& y) { return (x = x ^ y); } +SINT Vec& operator&=(Vec& x, const Vec& y) { return (x = x & y); } +SINT Vec& operator|=(Vec& x, const Vec& y) { return (x = x | y); } + +// ... for scalars... +SINTU Vec& operator+=(Vec& x, U y) { return (x = x + Vec(y)); } +SINTU Vec& operator-=(Vec& x, U y) { return (x = x - Vec(y)); } +SINTU Vec& operator*=(Vec& x, U y) { return (x = x * Vec(y)); } +SINTU Vec& operator/=(Vec& x, U y) { return (x = x / Vec(y)); } +SINTU Vec& operator^=(Vec& x, U y) { return (x = x ^ Vec(y)); } +SINTU Vec& operator&=(Vec& x, U y) { return (x = x & Vec(y)); } +SINTU Vec& operator|=(Vec& x, U y) { return (x = x | Vec(y)); } + +// ... and for shifts. +SINT Vec& operator<<=(Vec& x, int bits) { return (x = x << bits); } +SINT Vec& operator>>=(Vec& x, int bits) { return (x = x >> bits); } + +// cast() Vec to Vec, as if applying a C-cast to each lane. +template +static inline Vec<1,D> cast(const Vec<1,S>& src) { return (D)src.val; } + +template +static inline Vec cast(const Vec& src) { +#if !defined(SKNX_NO_SIMD) && defined(__clang__) + return to_vec(__builtin_convertvector(to_vext(src), VExt)); +#else + return join(cast(src.lo), cast(src.hi)); +#endif +} + +// Shuffle values from a vector pretty arbitrarily: +// skvx::Vec<4,float> rgba = {R,G,B,A}; +// shuffle<2,1,0,3> (rgba) ~> {B,G,R,A} +// shuffle<2,1> (rgba) ~> {B,G} +// shuffle<2,1,2,1,2,1,2,1>(rgba) ~> {B,G,B,G,B,G,B,G} +// shuffle<3,3,3,3> (rgba) ~> {A,A,A,A} +// The only real restriction is that the output also be a legal N=power-of-two sknx::Vec. +template +static inline Vec shuffle(const Vec& x) { +#if !defined(SKNX_NO_SIMD) && defined(__clang__) + return to_vec(__builtin_shufflevector(to_vext(x), to_vext(x), Ix...)); +#else + return { x[Ix]... }; +#endif +} + +// div255(x) = (x + 127) / 255 is a bit-exact rounding divide-by-255, packing down to 8-bit. +template +static inline Vec div255(const Vec& x) { + return cast( (x+127)/255 ); +} + +// approx_scale(x,y) approximates div255(cast(x)*cast(y)) within a bit, +// and is always perfect when x or y is 0 or 255. +template +static inline Vec approx_scale(const Vec& x, const Vec& y) { + // All of (x*y+x)/256, (x*y+y)/256, and (x*y+255)/256 meet the criteria above. + // We happen to have historically picked (x*y+x)/256. + auto X = cast(x), + Y = cast(y); + return cast( (X*Y+X)/256 ); +} + +#if !defined(SKNX_NO_SIMD) && defined(__ARM_NEON) + // With NEON we can do eight u8*u8 -> u16 in one instruction, vmull_u8 (read, mul-long). + static inline Vec<8,uint16_t> mull(const Vec<8,uint8_t>& x, + const Vec<8,uint8_t>& y) { + return to_vec<8,uint16_t>(vmull_u8(to_vext(x), + to_vext(y))); + } + + template + static inline typename std::enable_if<(N < 8), + Vec>::type mull(const Vec& x, + const Vec& y) { + // N < 8 --> double up data until N == 8, returning the part we need. + return mull(join(x,x), + join(y,y)).lo; + } + + template + static inline typename std::enable_if<(N > 8), + Vec>::type mull(const Vec& x, + const Vec& y) { + // N > 8 --> usual join(lo,hi) strategy to recurse down to N == 8. + return join(mull(x.lo, y.lo), + mull(x.hi, y.hi)); + } +#else + // Nothing special when we don't have NEON... just cast up to 16-bit and multiply. + template + static inline Vec mull(const Vec& x, + const Vec& y) { + return cast(x) + * cast(y); + } +#endif + +#if !defined(SKNX_NO_SIMD) + + // Platform-specific specializations and overloads can now drop in here. + + #if defined(__SSE__) + static inline Vec<4,float> sqrt(const Vec<4,float>& x) { + return bit_pun>(_mm_sqrt_ps(bit_pun<__m128>(x))); + } + static inline Vec<4,float> rsqrt(const Vec<4,float>& x) { + return bit_pun>(_mm_rsqrt_ps(bit_pun<__m128>(x))); + } + static inline Vec<4,float> rcp(const Vec<4,float>& x) { + return bit_pun>(_mm_rcp_ps(bit_pun<__m128>(x))); + } + + static inline Vec<2,float> sqrt(const Vec<2,float>& x) { + return shuffle<0,1>( sqrt(shuffle<0,1,0,1>(x))); + } + static inline Vec<2,float> rsqrt(const Vec<2,float>& x) { + return shuffle<0,1>(rsqrt(shuffle<0,1,0,1>(x))); + } + static inline Vec<2,float> rcp(const Vec<2,float>& x) { + return shuffle<0,1>( rcp(shuffle<0,1,0,1>(x))); + } + #endif + + #if defined(__SSE4_1__) + static inline Vec<4,float> if_then_else(const Vec<4,int >& c, + const Vec<4,float>& t, + const Vec<4,float>& e) { + return bit_pun>(_mm_blendv_ps(bit_pun<__m128>(e), + bit_pun<__m128>(t), + bit_pun<__m128>(c))); + } + #elif defined(__SSE__) + static inline Vec<4,float> if_then_else(const Vec<4,int >& c, + const Vec<4,float>& t, + const Vec<4,float>& e) { + return bit_pun>(_mm_or_ps(_mm_and_ps (bit_pun<__m128>(c), + bit_pun<__m128>(t)), + _mm_andnot_ps(bit_pun<__m128>(c), + bit_pun<__m128>(e)))); + } + #elif defined(__ARM_NEON) + static inline Vec<4,float> if_then_else(const Vec<4,int >& c, + const Vec<4,float>& t, + const Vec<4,float>& e) { + return bit_pun>(vbslq_f32(bit_pun (c), + bit_pun(t), + bit_pun(e))); + } + #endif + +#endif // !defined(SKNX_NO_SIMD) + +} // namespace skvx + +#undef SINTU +#undef SINT +#undef SIT +#undef SKVX_ALIGNMENT + +#endif//SKVX_DEFINED diff --git a/gfx/skia/skia/include/private/SkWeakRefCnt.h b/gfx/skia/skia/include/private/SkWeakRefCnt.h new file mode 100644 index 0000000000..c360697cd9 --- /dev/null +++ b/gfx/skia/skia/include/private/SkWeakRefCnt.h @@ -0,0 +1,170 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWeakRefCnt_DEFINED +#define SkWeakRefCnt_DEFINED + +#include "include/core/SkRefCnt.h" +#include + +/** \class SkWeakRefCnt + + SkWeakRefCnt is the base class for objects that may be shared by multiple + objects. When an existing strong owner wants to share a reference, it calls + ref(). When a strong owner wants to release its reference, it calls + unref(). When the shared object's strong reference count goes to zero as + the result of an unref() call, its (virtual) weak_dispose method is called. + It is an error for the destructor to be called explicitly (or via the + object going out of scope on the stack or calling delete) if + getRefCnt() > 1. + + In addition to strong ownership, an owner may instead obtain a weak + reference by calling weak_ref(). A call to weak_ref() must be balanced by a + call to weak_unref(). To obtain a strong reference from a weak reference, + call try_ref(). If try_ref() returns true, the owner's pointer is now also + a strong reference on which unref() must be called. Note that this does not + affect the original weak reference, weak_unref() must still be called. When + the weak reference count goes to zero, the object is deleted. While the + weak reference count is positive and the strong reference count is zero the + object still exists, but will be in the disposed state. It is up to the + object to define what this means. + + Note that a strong reference implicitly implies a weak reference. As a + result, it is allowable for the owner of a strong ref to call try_ref(). + This will have the same effect as calling ref(), but may be more expensive. + + Example: + + SkWeakRefCnt myRef = strongRef.weak_ref(); + ... // strongRef.unref() may or may not be called + if (myRef.try_ref()) { + ... // use myRef + myRef.unref(); + } else { + // myRef is in the disposed state + } + myRef.weak_unref(); +*/ +class SK_API SkWeakRefCnt : public SkRefCnt { +public: + /** Default construct, initializing the reference counts to 1. + The strong references collectively hold one weak reference. When the + strong reference count goes to zero, the collectively held weak + reference is released. + */ + SkWeakRefCnt() : SkRefCnt(), fWeakCnt(1) {} + + /** Destruct, asserting that the weak reference count is 1. + */ + ~SkWeakRefCnt() override { +#ifdef SK_DEBUG + SkASSERT(getWeakCnt() == 1); + fWeakCnt.store(0, std::memory_order_relaxed); +#endif + } + +#ifdef SK_DEBUG + /** Return the weak reference count. */ + int32_t getWeakCnt() const { + return fWeakCnt.load(std::memory_order_relaxed); + } +#endif + +private: + /** If fRefCnt is 0, returns 0. + * Otherwise increments fRefCnt, acquires, and returns the old value. + */ + int32_t atomic_conditional_acquire_strong_ref() const { + int32_t prev = fRefCnt.load(std::memory_order_relaxed); + do { + if (0 == prev) { + break; + } + } while(!fRefCnt.compare_exchange_weak(prev, prev+1, std::memory_order_acquire, + std::memory_order_relaxed)); + return prev; + } + +public: + /** Creates a strong reference from a weak reference, if possible. The + caller must already be an owner. If try_ref() returns true the owner + is in posession of an additional strong reference. Both the original + reference and new reference must be properly unreferenced. If try_ref() + returns false, no strong reference could be created and the owner's + reference is in the same state as before the call. + */ + bool SK_WARN_UNUSED_RESULT try_ref() const { + if (atomic_conditional_acquire_strong_ref() != 0) { + // Acquire barrier (L/SL), if not provided above. + // Prevents subsequent code from happening before the increment. + return true; + } + return false; + } + + /** Increment the weak reference count. Must be balanced by a call to + weak_unref(). + */ + void weak_ref() const { + SkASSERT(getRefCnt() > 0); + SkASSERT(getWeakCnt() > 0); + // No barrier required. + (void)fWeakCnt.fetch_add(+1, std::memory_order_relaxed); + } + + /** Decrement the weak reference count. If the weak reference count is 1 + before the decrement, then call delete on the object. Note that if this + is the case, then the object needs to have been allocated via new, and + not on the stack. + */ + void weak_unref() const { + SkASSERT(getWeakCnt() > 0); + // A release here acts in place of all releases we "should" have been doing in ref(). + if (1 == fWeakCnt.fetch_add(-1, std::memory_order_acq_rel)) { + // Like try_ref(), the acquire is only needed on success, to make sure + // code in internal_dispose() doesn't happen before the decrement. +#ifdef SK_DEBUG + // so our destructor won't complain + fWeakCnt.store(1, std::memory_order_relaxed); +#endif + this->INHERITED::internal_dispose(); + } + } + + /** Returns true if there are no strong references to the object. When this + is the case all future calls to try_ref() will return false. + */ + bool weak_expired() const { + return fRefCnt.load(std::memory_order_relaxed) == 0; + } + +protected: + /** Called when the strong reference count goes to zero. This allows the + object to free any resources it may be holding. Weak references may + still exist and their level of allowed access to the object is defined + by the object's class. + */ + virtual void weak_dispose() const { + } + +private: + /** Called when the strong reference count goes to zero. Calls weak_dispose + on the object and releases the implicit weak reference held + collectively by the strong references. + */ + void internal_dispose() const override { + weak_dispose(); + weak_unref(); + } + + /* Invariant: fWeakCnt = #weak + (fRefCnt > 0 ? 1 : 0) */ + mutable std::atomic fWeakCnt; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/svg/SkSVGCanvas.h b/gfx/skia/skia/include/svg/SkSVGCanvas.h new file mode 100644 index 0000000000..86e8975230 --- /dev/null +++ b/gfx/skia/skia/include/svg/SkSVGCanvas.h @@ -0,0 +1,36 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGCanvas_DEFINED +#define SkSVGCanvas_DEFINED + +#include "include/core/SkCanvas.h" + +class SkWStream; + +class SK_API SkSVGCanvas { +public: + enum { + kConvertTextToPaths_Flag = 0x01, // emit text as s + kNoPrettyXML_Flag = 0x02, // suppress newlines and tabs in output + }; + + /** + * Returns a new canvas that will generate SVG commands from its draw calls, and send + * them to the provided stream. Ownership of the stream is not transfered, and it must + * remain valid for the lifetime of the returned canvas. + * + * The canvas may buffer some drawing calls, so the output is not guaranteed to be valid + * or complete until the canvas instance is deleted. + * + * The 'bounds' parameter defines an initial SVG viewport (viewBox attribute on the root + * SVG element). + */ + static std::unique_ptr Make(const SkRect& bounds, SkWStream*, uint32_t flags = 0); +}; + +#endif diff --git a/gfx/skia/skia/include/third_party/skcms/LICENSE b/gfx/skia/skia/include/third_party/skcms/LICENSE new file mode 100644 index 0000000000..6c7c5be360 --- /dev/null +++ b/gfx/skia/skia/include/third_party/skcms/LICENSE @@ -0,0 +1,29 @@ +// Copyright (c) 2018 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- diff --git a/gfx/skia/skia/include/third_party/skcms/skcms.h b/gfx/skia/skia/include/third_party/skcms/skcms.h new file mode 100644 index 0000000000..f458784558 --- /dev/null +++ b/gfx/skia/skia/include/third_party/skcms/skcms.h @@ -0,0 +1,332 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#pragma once + +// skcms.h contains the entire public API for skcms. + +#ifndef SKCMS_API + #define SKCMS_API +#endif + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// A row-major 3x3 matrix (ie vals[row][col]) +typedef struct skcms_Matrix3x3 { + float vals[3][3]; +} skcms_Matrix3x3; + +// It is _not_ safe to alias the pointers to invert in-place. +SKCMS_API bool skcms_Matrix3x3_invert(const skcms_Matrix3x3*, skcms_Matrix3x3*); +SKCMS_API skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3*, const skcms_Matrix3x3*); + +// A row-major 3x4 matrix (ie vals[row][col]) +typedef struct skcms_Matrix3x4 { + float vals[3][4]; +} skcms_Matrix3x4; + +// A transfer function mapping encoded values to linear values, +// represented by this 7-parameter piecewise function: +// +// linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d +// = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded| +// +// (A simple gamma transfer function sets g to gamma and a to 1.) +typedef struct skcms_TransferFunction { + float g, a,b,c,d,e,f; +} skcms_TransferFunction; + +SKCMS_API float skcms_TransferFunction_eval (const skcms_TransferFunction*, float); +SKCMS_API bool skcms_TransferFunction_invert(const skcms_TransferFunction*, + skcms_TransferFunction*); + +// We can jam a couple alternate transfer function forms into skcms_TransferFunction, +// including those matching the general forms of the SMPTE ST 2084 PQ function or HLG. +// +// PQish: +// max(A + B|encoded|^C, 0) +// linear = sign(encoded) * (------------------------) ^ F +// D + E|encoded|^C +SKCMS_API bool skcms_TransferFunction_makePQish(skcms_TransferFunction*, + float A, float B, float C, + float D, float E, float F); +// HLGish: +// { sign(encoded) * ( (R|encoded|)^G ) when 0 <= |encoded| <= 1/R +// linear = { sign(encoded) * ( e^(a(|encoded|-c)) + b ) when 1/R < |encoded| +SKCMS_API bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction*, + float R, float G, + float a, float b, float c); + +// PQ mapping encoded [0,1] to linear [0,1]. +static inline bool skcms_TransferFunction_makePQ(skcms_TransferFunction* tf) { + return skcms_TransferFunction_makePQish(tf, -107/128.0f, 1.0f, 32/2523.0f + , 2413/128.0f, -2392/128.0f, 8192/1305.0f); +} +// HLG mapping encoded [0,1] to linear [0,12]. +static inline bool skcms_TransferFunction_makeHLG(skcms_TransferFunction* tf) { + return skcms_TransferFunction_makeHLGish(tf, 2.0f, 2.0f + , 1/0.17883277f, 0.28466892f, 0.55991073f); +} + +// Unified representation of 'curv' or 'para' tag data, or a 1D table from 'mft1' or 'mft2' +typedef union skcms_Curve { + struct { + uint32_t alias_of_table_entries; + skcms_TransferFunction parametric; + }; + struct { + uint32_t table_entries; + const uint8_t* table_8; + const uint8_t* table_16; + }; +} skcms_Curve; + +typedef struct skcms_A2B { + // Optional: N 1D curves, followed by an N-dimensional CLUT. + // If input_channels == 0, these curves and CLUT are skipped, + // Otherwise, input_channels must be in [1, 4]. + uint32_t input_channels; + skcms_Curve input_curves[4]; + uint8_t grid_points[4]; + const uint8_t* grid_8; + const uint8_t* grid_16; + + // Optional: 3 1D curves, followed by a color matrix. + // If matrix_channels == 0, these curves and matrix are skipped, + // Otherwise, matrix_channels must be 3. + uint32_t matrix_channels; + skcms_Curve matrix_curves[3]; + skcms_Matrix3x4 matrix; + + // Required: 3 1D curves. Always present, and output_channels must be 3. + uint32_t output_channels; + skcms_Curve output_curves[3]; +} skcms_A2B; + +typedef struct skcms_ICCProfile { + const uint8_t* buffer; + + uint32_t size; + uint32_t data_color_space; + uint32_t pcs; + uint32_t tag_count; + + // skcms_Parse() will set commonly-used fields for you when possible: + + // If we can parse red, green and blue transfer curves from the profile, + // trc will be set to those three curves, and has_trc will be true. + bool has_trc; + skcms_Curve trc[3]; + + // If this profile's gamut can be represented by a 3x3 transform to XYZD50, + // skcms_Parse() sets toXYZD50 to that transform and has_toXYZD50 to true. + bool has_toXYZD50; + skcms_Matrix3x3 toXYZD50; + + // If the profile has a valid A2B0 tag, skcms_Parse() sets A2B to that data, + // and has_A2B to true. + bool has_A2B; + skcms_A2B A2B; +} skcms_ICCProfile; + +// The sRGB color profile is so commonly used that we offer a canonical skcms_ICCProfile for it. +SKCMS_API const skcms_ICCProfile* skcms_sRGB_profile(void); +// Ditto for XYZD50, the most common profile connection space. +SKCMS_API const skcms_ICCProfile* skcms_XYZD50_profile(void); + +SKCMS_API const skcms_TransferFunction* skcms_sRGB_TransferFunction(void); +SKCMS_API const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction(void); +SKCMS_API const skcms_TransferFunction* skcms_Identity_TransferFunction(void); + +// Practical equality test for two skcms_ICCProfiles. +// The implementation is subject to change, but it will always try to answer +// "can I substitute A for B?" and "can I skip transforming from A to B?". +SKCMS_API bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, + const skcms_ICCProfile* B); + +// Practical test that answers: Is curve roughly the inverse of inv_tf? Typically used by passing +// the inverse of a known parametric transfer function (like sRGB), to determine if a particular +// curve is very close to sRGB. +SKCMS_API bool skcms_AreApproximateInverses(const skcms_Curve* curve, + const skcms_TransferFunction* inv_tf); + +// Similar to above, answering the question for all three TRC curves of the given profile. Again, +// passing skcms_sRGB_InverseTransferFunction as inv_tf will answer the question: +// "Does this profile have a transfer function that is very close to sRGB?" +SKCMS_API bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile, + const skcms_TransferFunction* inv_tf); + +// Parse an ICC profile and return true if possible, otherwise return false. +// The buffer is not copied, it must remain valid as long as the skcms_ICCProfile +// will be used. +SKCMS_API bool skcms_Parse(const void*, size_t, skcms_ICCProfile*); + +SKCMS_API bool skcms_ApproximateCurve(const skcms_Curve* curve, + skcms_TransferFunction* approx, + float* max_error); + +typedef struct skcms_ICCTag { + uint32_t signature; + uint32_t type; + uint32_t size; + const uint8_t* buf; +} skcms_ICCTag; + +SKCMS_API void skcms_GetTagByIndex (const skcms_ICCProfile*, uint32_t idx, skcms_ICCTag*); +SKCMS_API bool skcms_GetTagBySignature(const skcms_ICCProfile*, uint32_t sig, skcms_ICCTag*); + +// These are common ICC signature values +enum { + // data_color_space + skcms_Signature_CMYK = 0x434D594B, + skcms_Signature_Gray = 0x47524159, + skcms_Signature_RGB = 0x52474220, + + // pcs + skcms_Signature_Lab = 0x4C616220, + skcms_Signature_XYZ = 0x58595A20, +}; + +typedef enum skcms_PixelFormat { + skcms_PixelFormat_A_8, + skcms_PixelFormat_A_8_, + skcms_PixelFormat_G_8, + skcms_PixelFormat_G_8_, + skcms_PixelFormat_RGBA_8888_Palette8, + skcms_PixelFormat_BGRA_8888_Palette8, + + skcms_PixelFormat_RGB_565, + skcms_PixelFormat_BGR_565, + + skcms_PixelFormat_ABGR_4444, + skcms_PixelFormat_ARGB_4444, + + skcms_PixelFormat_RGB_888, + skcms_PixelFormat_BGR_888, + skcms_PixelFormat_RGBA_8888, + skcms_PixelFormat_BGRA_8888, + + skcms_PixelFormat_RGBA_1010102, + skcms_PixelFormat_BGRA_1010102, + + skcms_PixelFormat_RGB_161616LE, // Little-endian. Pointers must be 16-bit aligned. + skcms_PixelFormat_BGR_161616LE, + skcms_PixelFormat_RGBA_16161616LE, + skcms_PixelFormat_BGRA_16161616LE, + + skcms_PixelFormat_RGB_161616BE, // Big-endian. Pointers must be 16-bit aligned. + skcms_PixelFormat_BGR_161616BE, + skcms_PixelFormat_RGBA_16161616BE, + skcms_PixelFormat_BGRA_16161616BE, + + skcms_PixelFormat_RGB_hhh_Norm, // 1-5-10 half-precision float in [0,1] + skcms_PixelFormat_BGR_hhh_Norm, // Pointers must be 16-bit aligned. + skcms_PixelFormat_RGBA_hhhh_Norm, + skcms_PixelFormat_BGRA_hhhh_Norm, + + skcms_PixelFormat_RGB_hhh, // 1-5-10 half-precision float. + skcms_PixelFormat_BGR_hhh, // Pointers must be 16-bit aligned. + skcms_PixelFormat_RGBA_hhhh, + skcms_PixelFormat_BGRA_hhhh, + + skcms_PixelFormat_RGB_fff, // 1-8-23 single-precision float (the normal kind). + skcms_PixelFormat_BGR_fff, // Pointers must be 32-bit aligned. + skcms_PixelFormat_RGBA_ffff, + skcms_PixelFormat_BGRA_ffff, +} skcms_PixelFormat; + +// We always store any alpha channel linearly. In the chart below, tf-1() is the inverse +// transfer function for the given color profile (applying the transfer function linearizes). + +// We treat opaque as a strong requirement, not just a performance hint: we will ignore +// any source alpha and treat it as 1.0, and will make sure that any destination alpha +// channel is filled with the equivalent of 1.0. + +// We used to offer multiple types of premultiplication, but now just one, PremulAsEncoded. +// This is the premul you're probably used to working with. + +typedef enum skcms_AlphaFormat { + skcms_AlphaFormat_Opaque, // alpha is always opaque + // tf-1(r), tf-1(g), tf-1(b), 1.0 + skcms_AlphaFormat_Unpremul, // alpha and color are unassociated + // tf-1(r), tf-1(g), tf-1(b), a + skcms_AlphaFormat_PremulAsEncoded, // premultiplied while encoded + // tf-1(r)*a, tf-1(g)*a, tf-1(b)*a, a +} skcms_AlphaFormat; + +// Convert npixels pixels from src format and color profile to dst format and color profile +// and return true, otherwise return false. It is safe to alias dst == src if dstFmt == srcFmt. +SKCMS_API bool skcms_Transform(const void* src, + skcms_PixelFormat srcFmt, + skcms_AlphaFormat srcAlpha, + const skcms_ICCProfile* srcProfile, + void* dst, + skcms_PixelFormat dstFmt, + skcms_AlphaFormat dstAlpha, + const skcms_ICCProfile* dstProfile, + size_t npixels); + +// As skcms_Transform(), supporting srcFmts with a palette. +SKCMS_API bool skcms_TransformWithPalette(const void* src, + skcms_PixelFormat srcFmt, + skcms_AlphaFormat srcAlpha, + const skcms_ICCProfile* srcProfile, + void* dst, + skcms_PixelFormat dstFmt, + skcms_AlphaFormat dstAlpha, + const skcms_ICCProfile* dstProfile, + size_t npixels, + const void* palette); + +// If profile can be used as a destination in skcms_Transform, return true. Otherwise, attempt to +// rewrite it with approximations where reasonable. If successful, return true. If no reasonable +// approximation exists, leave the profile unchanged and return false. +SKCMS_API bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile); + +// If profile can be used as a destination with a single parametric transfer function (ie for +// rasterization), return true. Otherwise, attempt to rewrite it with approximations where +// reasonable. If successful, return true. If no reasonable approximation exists, leave the +// profile unchanged and return false. +SKCMS_API bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile); + +SKCMS_API bool skcms_PrimariesToXYZD50(float rx, float ry, + float gx, float gy, + float bx, float by, + float wx, float wy, + skcms_Matrix3x3* toXYZD50); + +// Utilities for programmatically constructing profiles +static inline void skcms_Init(skcms_ICCProfile* p) { + memset(p, 0, sizeof(*p)); + p->data_color_space = skcms_Signature_RGB; + p->pcs = skcms_Signature_XYZ; +} + +static inline void skcms_SetTransferFunction(skcms_ICCProfile* p, + const skcms_TransferFunction* tf) { + p->has_trc = true; + for (int i = 0; i < 3; ++i) { + p->trc[i].table_entries = 0; + p->trc[i].parametric = *tf; + } +} + +static inline void skcms_SetXYZD50(skcms_ICCProfile* p, const skcms_Matrix3x3* m) { + p->has_toXYZD50 = true; + p->toXYZD50 = *m; +} + +#ifdef __cplusplus +} +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/LICENSE b/gfx/skia/skia/include/third_party/vulkan/LICENSE new file mode 100644 index 0000000000..6c7c5be360 --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/LICENSE @@ -0,0 +1,29 @@ +// Copyright (c) 2018 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vk_platform.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vk_platform.h new file mode 100644 index 0000000000..7289299240 --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vk_platform.h @@ -0,0 +1,92 @@ +// +// File: vk_platform.h +// +/* +** Copyright (c) 2014-2017 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + + +#ifndef VK_PLATFORM_H_ +#define VK_PLATFORM_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +/* +*************************************************************************************************** +* Platform-specific directives and type declarations +*************************************************************************************************** +*/ + +/* Platform-specific calling convention macros. + * + * Platforms should define these so that Vulkan clients call Vulkan commands + * with the same calling conventions that the Vulkan implementation expects. + * + * VKAPI_ATTR - Placed before the return type in function declarations. + * Useful for C++11 and GCC/Clang-style function attribute syntax. + * VKAPI_CALL - Placed after the return type in function declarations. + * Useful for MSVC-style calling convention syntax. + * VKAPI_PTR - Placed between the '(' and '*' in function pointer types. + * + * Function declaration: VKAPI_ATTR void VKAPI_CALL vkCommand(void); + * Function pointer type: typedef void (VKAPI_PTR *PFN_vkCommand)(void); + */ +#if defined(_WIN32) + // On Windows, Vulkan commands use the stdcall convention + #define VKAPI_ATTR + #define VKAPI_CALL __stdcall + #define VKAPI_PTR VKAPI_CALL +#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7 + #error "Vulkan isn't supported for the 'armeabi' NDK ABI" +#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE) + // On Android 32-bit ARM targets, Vulkan functions use the "hardfloat" + // calling convention, i.e. float parameters are passed in registers. This + // is true even if the rest of the application passes floats on the stack, + // as it does by default when compiling for the armeabi-v7a NDK ABI. + #define VKAPI_ATTR __attribute__((pcs("aapcs-vfp"))) + #define VKAPI_CALL + #define VKAPI_PTR VKAPI_ATTR +#else + // On other platforms, use the default calling convention + #define VKAPI_ATTR + #define VKAPI_CALL + #define VKAPI_PTR +#endif + +#include + +#if !defined(VK_NO_STDINT_H) + #if defined(_MSC_VER) && (_MSC_VER < 1600) + typedef signed __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef signed __int16 int16_t; + typedef unsigned __int16 uint16_t; + typedef signed __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; + #else + #include + #endif +#endif // !defined(VK_NO_STDINT_H) + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan.h new file mode 100644 index 0000000000..4fba7c977a --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan.h @@ -0,0 +1,79 @@ +#ifndef VULKAN_H_ +#define VULKAN_H_ 1 + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "include/third_party/vulkan/vulkan/vk_platform.h" +#include "include/third_party/vulkan/vulkan/vulkan_core.h" + +#ifdef VK_USE_PLATFORM_ANDROID_KHR +#include "include/third_party/vulkan/vulkan/vulkan_android.h" +#endif + + +#ifdef VK_USE_PLATFORM_IOS_MVK +#include "include/third_party/vulkan/vulkan/vulkan_ios.h" +#endif + + +#ifdef VK_USE_PLATFORM_MACOS_MVK +#include "include/third_party/vulkan/vulkan/vulkan_macos.h" +#endif + + +#ifdef VK_USE_PLATFORM_MIR_KHR +#include +#include "vulkan_mir.h" +#endif + + +#ifdef VK_USE_PLATFORM_VI_NN +#include "vulkan_vi.h" +#endif + + +#ifdef VK_USE_PLATFORM_WAYLAND_KHR +#include +#include "vulkan_wayland.h" +#endif + + +#ifdef VK_USE_PLATFORM_WIN32_KHR +#include +#include "include/third_party/vulkan/vulkan/vulkan_win32.h" +#endif + + +#ifdef VK_USE_PLATFORM_XCB_KHR +#include +#include "include/third_party/vulkan/vulkan/vulkan_xcb.h" +#endif + + +#ifdef VK_USE_PLATFORM_XLIB_KHR +#include +#include "vulkan_xlib.h" +#endif + + +#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT +#include +#include +#include "vulkan_xlib_xrandr.h" +#endif + +#endif // VULKAN_H_ diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_android.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_android.h new file mode 100644 index 0000000000..07aaeda28e --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_android.h @@ -0,0 +1,126 @@ +#ifndef VULKAN_ANDROID_H_ +#define VULKAN_ANDROID_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/* +** This header is generated from the Khronos Vulkan XML API Registry. +** +*/ + + +#define VK_KHR_android_surface 1 +struct ANativeWindow; + +#define VK_KHR_ANDROID_SURFACE_SPEC_VERSION 6 +#define VK_KHR_ANDROID_SURFACE_EXTENSION_NAME "VK_KHR_android_surface" + +typedef VkFlags VkAndroidSurfaceCreateFlagsKHR; + +typedef struct VkAndroidSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkAndroidSurfaceCreateFlagsKHR flags; + struct ANativeWindow* window; +} VkAndroidSurfaceCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateAndroidSurfaceKHR)(VkInstance instance, const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateAndroidSurfaceKHR( + VkInstance instance, + const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface); +#endif + +#define VK_ANDROID_external_memory_android_hardware_buffer 1 +struct AHardwareBuffer; + +#define VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION 3 +#define VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME "VK_ANDROID_external_memory_android_hardware_buffer" + +typedef struct VkAndroidHardwareBufferUsageANDROID { + VkStructureType sType; + void* pNext; + uint64_t androidHardwareBufferUsage; +} VkAndroidHardwareBufferUsageANDROID; + +typedef struct VkAndroidHardwareBufferPropertiesANDROID { + VkStructureType sType; + void* pNext; + VkDeviceSize allocationSize; + uint32_t memoryTypeBits; +} VkAndroidHardwareBufferPropertiesANDROID; + +typedef struct VkAndroidHardwareBufferFormatPropertiesANDROID { + VkStructureType sType; + void* pNext; + VkFormat format; + uint64_t externalFormat; + VkFormatFeatureFlags formatFeatures; + VkComponentMapping samplerYcbcrConversionComponents; + VkSamplerYcbcrModelConversion suggestedYcbcrModel; + VkSamplerYcbcrRange suggestedYcbcrRange; + VkChromaLocation suggestedXChromaOffset; + VkChromaLocation suggestedYChromaOffset; +} VkAndroidHardwareBufferFormatPropertiesANDROID; + +typedef struct VkImportAndroidHardwareBufferInfoANDROID { + VkStructureType sType; + const void* pNext; + struct AHardwareBuffer* buffer; +} VkImportAndroidHardwareBufferInfoANDROID; + +typedef struct VkMemoryGetAndroidHardwareBufferInfoANDROID { + VkStructureType sType; + const void* pNext; + VkDeviceMemory memory; +} VkMemoryGetAndroidHardwareBufferInfoANDROID; + +typedef struct VkExternalFormatANDROID { + VkStructureType sType; + void* pNext; + uint64_t externalFormat; +} VkExternalFormatANDROID; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetAndroidHardwareBufferPropertiesANDROID)(VkDevice device, const struct AHardwareBuffer* buffer, VkAndroidHardwareBufferPropertiesANDROID* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryAndroidHardwareBufferANDROID)(VkDevice device, const VkMemoryGetAndroidHardwareBufferInfoANDROID* pInfo, struct AHardwareBuffer** pBuffer); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetAndroidHardwareBufferPropertiesANDROID( + VkDevice device, + const struct AHardwareBuffer* buffer, + VkAndroidHardwareBufferPropertiesANDROID* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryAndroidHardwareBufferANDROID( + VkDevice device, + const VkMemoryGetAndroidHardwareBufferInfoANDROID* pInfo, + struct AHardwareBuffer** pBuffer); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_core.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_core.h new file mode 100644 index 0000000000..4d8762f8e6 --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_core.h @@ -0,0 +1,7576 @@ +#ifndef VULKAN_CORE_H_ +#define VULKAN_CORE_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/* +** This header is generated from the Khronos Vulkan XML API Registry. +** +*/ + + +#define VK_VERSION_1_0 1 +#include "include/third_party/vulkan/vulkan/vk_platform.h" + +#define VK_MAKE_VERSION(major, minor, patch) \ + (((major) << 22) | ((minor) << 12) | (patch)) + +// DEPRECATED: This define has been removed. Specific version defines (e.g. VK_API_VERSION_1_0), or the VK_MAKE_VERSION macro, should be used instead. +//#define VK_API_VERSION VK_MAKE_VERSION(1, 0, 0) // Patch version should always be set to 0 + +// Vulkan 1.0 version number +#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0)// Patch version should always be set to 0 + +#define VK_VERSION_MAJOR(version) ((uint32_t)(version) >> 22) +#define VK_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3ff) +#define VK_VERSION_PATCH(version) ((uint32_t)(version) & 0xfff) +// Version of this file +#define VK_HEADER_VERSION 77 + + +#define VK_NULL_HANDLE 0 + + + +#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; + + +#if !defined(VK_DEFINE_NON_DISPATCHABLE_HANDLE) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; +#else + #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#endif +#endif + + + +typedef uint32_t VkFlags; +typedef uint32_t VkBool32; +typedef uint64_t VkDeviceSize; +typedef uint32_t VkSampleMask; + +VK_DEFINE_HANDLE(VkInstance) +VK_DEFINE_HANDLE(VkPhysicalDevice) +VK_DEFINE_HANDLE(VkDevice) +VK_DEFINE_HANDLE(VkQueue) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSemaphore) +VK_DEFINE_HANDLE(VkCommandBuffer) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFence) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeviceMemory) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBuffer) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImage) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkEvent) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkQueryPool) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBufferView) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImageView) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkShaderModule) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineCache) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineLayout) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkRenderPass) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipeline) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSetLayout) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSampler) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorPool) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSet) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFramebuffer) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCommandPool) + +#define VK_LOD_CLAMP_NONE 1000.0f +#define VK_REMAINING_MIP_LEVELS (~0U) +#define VK_REMAINING_ARRAY_LAYERS (~0U) +#define VK_WHOLE_SIZE (~0ULL) +#define VK_ATTACHMENT_UNUSED (~0U) +#define VK_TRUE 1 +#define VK_FALSE 0 +#define VK_QUEUE_FAMILY_IGNORED (~0U) +#define VK_SUBPASS_EXTERNAL (~0U) +#define VK_MAX_PHYSICAL_DEVICE_NAME_SIZE 256 +#define VK_UUID_SIZE 16 +#define VK_MAX_MEMORY_TYPES 32 +#define VK_MAX_MEMORY_HEAPS 16 +#define VK_MAX_EXTENSION_NAME_SIZE 256 +#define VK_MAX_DESCRIPTION_SIZE 256 + + +typedef enum VkPipelineCacheHeaderVersion { + VK_PIPELINE_CACHE_HEADER_VERSION_ONE = 1, + VK_PIPELINE_CACHE_HEADER_VERSION_BEGIN_RANGE = VK_PIPELINE_CACHE_HEADER_VERSION_ONE, + VK_PIPELINE_CACHE_HEADER_VERSION_END_RANGE = VK_PIPELINE_CACHE_HEADER_VERSION_ONE, + VK_PIPELINE_CACHE_HEADER_VERSION_RANGE_SIZE = (VK_PIPELINE_CACHE_HEADER_VERSION_ONE - VK_PIPELINE_CACHE_HEADER_VERSION_ONE + 1), + VK_PIPELINE_CACHE_HEADER_VERSION_MAX_ENUM = 0x7FFFFFFF +} VkPipelineCacheHeaderVersion; + +typedef enum VkResult { + VK_SUCCESS = 0, + VK_NOT_READY = 1, + VK_TIMEOUT = 2, + VK_EVENT_SET = 3, + VK_EVENT_RESET = 4, + VK_INCOMPLETE = 5, + VK_ERROR_OUT_OF_HOST_MEMORY = -1, + VK_ERROR_OUT_OF_DEVICE_MEMORY = -2, + VK_ERROR_INITIALIZATION_FAILED = -3, + VK_ERROR_DEVICE_LOST = -4, + VK_ERROR_MEMORY_MAP_FAILED = -5, + VK_ERROR_LAYER_NOT_PRESENT = -6, + VK_ERROR_EXTENSION_NOT_PRESENT = -7, + VK_ERROR_FEATURE_NOT_PRESENT = -8, + VK_ERROR_INCOMPATIBLE_DRIVER = -9, + VK_ERROR_TOO_MANY_OBJECTS = -10, + VK_ERROR_FORMAT_NOT_SUPPORTED = -11, + VK_ERROR_FRAGMENTED_POOL = -12, + VK_ERROR_OUT_OF_POOL_MEMORY = -1000069000, + VK_ERROR_INVALID_EXTERNAL_HANDLE = -1000072003, + VK_ERROR_SURFACE_LOST_KHR = -1000000000, + VK_ERROR_NATIVE_WINDOW_IN_USE_KHR = -1000000001, + VK_SUBOPTIMAL_KHR = 1000001003, + VK_ERROR_OUT_OF_DATE_KHR = -1000001004, + VK_ERROR_INCOMPATIBLE_DISPLAY_KHR = -1000003001, + VK_ERROR_VALIDATION_FAILED_EXT = -1000011001, + VK_ERROR_INVALID_SHADER_NV = -1000012000, + VK_ERROR_FRAGMENTATION_EXT = -1000161000, + VK_ERROR_NOT_PERMITTED_EXT = -1000174001, + VK_ERROR_OUT_OF_POOL_MEMORY_KHR = VK_ERROR_OUT_OF_POOL_MEMORY, + VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR = VK_ERROR_INVALID_EXTERNAL_HANDLE, + VK_RESULT_BEGIN_RANGE = VK_ERROR_FRAGMENTED_POOL, + VK_RESULT_END_RANGE = VK_INCOMPLETE, + VK_RESULT_RANGE_SIZE = (VK_INCOMPLETE - VK_ERROR_FRAGMENTED_POOL + 1), + VK_RESULT_MAX_ENUM = 0x7FFFFFFF +} VkResult; + +typedef enum VkStructureType { + VK_STRUCTURE_TYPE_APPLICATION_INFO = 0, + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1, + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO = 2, + VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO = 3, + VK_STRUCTURE_TYPE_SUBMIT_INFO = 4, + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO = 5, + VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE = 6, + VK_STRUCTURE_TYPE_BIND_SPARSE_INFO = 7, + VK_STRUCTURE_TYPE_FENCE_CREATE_INFO = 8, + VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO = 9, + VK_STRUCTURE_TYPE_EVENT_CREATE_INFO = 10, + VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO = 11, + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO = 12, + VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO = 13, + VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO = 14, + VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO = 15, + VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO = 16, + VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO = 17, + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO = 18, + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO = 19, + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO = 20, + VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO = 21, + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO = 22, + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO = 23, + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO = 24, + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO = 25, + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO = 26, + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO = 27, + VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO = 28, + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO = 29, + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO = 30, + VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO = 31, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO = 32, + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO = 33, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO = 34, + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET = 35, + VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET = 36, + VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO = 37, + VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO = 38, + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO = 39, + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO = 40, + VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO = 41, + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO = 42, + VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO = 43, + VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER = 44, + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER = 45, + VK_STRUCTURE_TYPE_MEMORY_BARRIER = 46, + VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO = 47, + VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO = 48, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES = 1000094000, + VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO = 1000157000, + VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO = 1000157001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES = 1000083000, + VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS = 1000127000, + VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO = 1000127001, + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO = 1000060000, + VK_STRUCTURE_TYPE_DEVICE_GROUP_RENDER_PASS_BEGIN_INFO = 1000060003, + VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO = 1000060004, + VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO = 1000060005, + VK_STRUCTURE_TYPE_DEVICE_GROUP_BIND_SPARSE_INFO = 1000060006, + VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_DEVICE_GROUP_INFO = 1000060013, + VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO = 1000060014, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES = 1000070000, + VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO = 1000070001, + VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2 = 1000146000, + VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2 = 1000146001, + VK_STRUCTURE_TYPE_IMAGE_SPARSE_MEMORY_REQUIREMENTS_INFO_2 = 1000146002, + VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 = 1000146003, + VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2 = 1000146004, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 = 1000059000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 = 1000059001, + VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2 = 1000059002, + VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2 = 1000059003, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2 = 1000059004, + VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2 = 1000059005, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2 = 1000059006, + VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2 = 1000059007, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2 = 1000059008, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES = 1000117000, + VK_STRUCTURE_TYPE_RENDER_PASS_INPUT_ATTACHMENT_ASPECT_CREATE_INFO = 1000117001, + VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO = 1000117002, + VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO = 1000117003, + VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO = 1000053000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES = 1000053001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES = 1000053002, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES = 1000120000, + VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO = 1000145000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES = 1000145001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES = 1000145002, + VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2 = 1000145003, + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO = 1000156000, + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO = 1000156001, + VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO = 1000156002, + VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO = 1000156003, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES = 1000156004, + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES = 1000156005, + VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO = 1000085000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO = 1000071000, + VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES = 1000071001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO = 1000071002, + VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES = 1000071003, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES = 1000071004, + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO = 1000072000, + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO = 1000072001, + VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO = 1000072002, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO = 1000112000, + VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES = 1000112001, + VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO = 1000113000, + VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO = 1000077000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO = 1000076000, + VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES = 1000076001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES = 1000168000, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT = 1000168001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES = 1000063000, + VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR = 1000001000, + VK_STRUCTURE_TYPE_PRESENT_INFO_KHR = 1000001001, + VK_STRUCTURE_TYPE_DEVICE_GROUP_PRESENT_CAPABILITIES_KHR = 1000060007, + VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR = 1000060008, + VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR = 1000060009, + VK_STRUCTURE_TYPE_ACQUIRE_NEXT_IMAGE_INFO_KHR = 1000060010, + VK_STRUCTURE_TYPE_DEVICE_GROUP_PRESENT_INFO_KHR = 1000060011, + VK_STRUCTURE_TYPE_DEVICE_GROUP_SWAPCHAIN_CREATE_INFO_KHR = 1000060012, + VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR = 1000002000, + VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR = 1000002001, + VK_STRUCTURE_TYPE_DISPLAY_PRESENT_INFO_KHR = 1000003000, + VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000, + VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000, + VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, + VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000, + VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR = 1000008000, + VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, + VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT = 1000011000, + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_RASTERIZATION_ORDER_AMD = 1000018000, + VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT = 1000022000, + VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT = 1000022001, + VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT = 1000022002, + VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_IMAGE_CREATE_INFO_NV = 1000026000, + VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_BUFFER_CREATE_INFO_NV = 1000026001, + VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_MEMORY_ALLOCATE_INFO_NV = 1000026002, + VK_STRUCTURE_TYPE_TEXTURE_LOD_GATHER_FORMAT_PROPERTIES_AMD = 1000041000, + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_NV = 1000056000, + VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_NV = 1000056001, + VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_NV = 1000057000, + VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_NV = 1000057001, + VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_NV = 1000058000, + VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT = 1000061000, + VK_STRUCTURE_TYPE_VI_SURFACE_CREATE_INFO_NN = 1000062000, + VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR = 1000073000, + VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR = 1000073001, + VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR = 1000073002, + VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR = 1000073003, + VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR = 1000074000, + VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR = 1000074001, + VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR = 1000074002, + VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_KHR = 1000075000, + VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR = 1000078000, + VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR = 1000078001, + VK_STRUCTURE_TYPE_D3D12_FENCE_SUBMIT_INFO_KHR = 1000078002, + VK_STRUCTURE_TYPE_SEMAPHORE_GET_WIN32_HANDLE_INFO_KHR = 1000078003, + VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR = 1000079000, + VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR = 1000079001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR = 1000080000, + VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR = 1000084000, + VK_STRUCTURE_TYPE_OBJECT_TABLE_CREATE_INFO_NVX = 1000086000, + VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_CREATE_INFO_NVX = 1000086001, + VK_STRUCTURE_TYPE_CMD_PROCESS_COMMANDS_INFO_NVX = 1000086002, + VK_STRUCTURE_TYPE_CMD_RESERVE_SPACE_FOR_COMMANDS_INFO_NVX = 1000086003, + VK_STRUCTURE_TYPE_DEVICE_GENERATED_COMMANDS_LIMITS_NVX = 1000086004, + VK_STRUCTURE_TYPE_DEVICE_GENERATED_COMMANDS_FEATURES_NVX = 1000086005, + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_W_SCALING_STATE_CREATE_INFO_NV = 1000087000, + VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_EXT = 1000090000, + VK_STRUCTURE_TYPE_DISPLAY_POWER_INFO_EXT = 1000091000, + VK_STRUCTURE_TYPE_DEVICE_EVENT_INFO_EXT = 1000091001, + VK_STRUCTURE_TYPE_DISPLAY_EVENT_INFO_EXT = 1000091002, + VK_STRUCTURE_TYPE_SWAPCHAIN_COUNTER_CREATE_INFO_EXT = 1000091003, + VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE = 1000092000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_ATTRIBUTES_PROPERTIES_NVX = 1000097000, + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV = 1000098000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DISCARD_RECTANGLE_PROPERTIES_EXT = 1000099000, + VK_STRUCTURE_TYPE_PIPELINE_DISCARD_RECTANGLE_STATE_CREATE_INFO_EXT = 1000099001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT = 1000101000, + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT = 1000101001, + VK_STRUCTURE_TYPE_HDR_METADATA_EXT = 1000105000, + VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR = 1000111000, + VK_STRUCTURE_TYPE_IMPORT_FENCE_WIN32_HANDLE_INFO_KHR = 1000114000, + VK_STRUCTURE_TYPE_EXPORT_FENCE_WIN32_HANDLE_INFO_KHR = 1000114001, + VK_STRUCTURE_TYPE_FENCE_GET_WIN32_HANDLE_INFO_KHR = 1000114002, + VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR = 1000115000, + VK_STRUCTURE_TYPE_FENCE_GET_FD_INFO_KHR = 1000115001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR = 1000119000, + VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR = 1000119001, + VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR = 1000119002, + VK_STRUCTURE_TYPE_DISPLAY_PROPERTIES_2_KHR = 1000121000, + VK_STRUCTURE_TYPE_DISPLAY_PLANE_PROPERTIES_2_KHR = 1000121001, + VK_STRUCTURE_TYPE_DISPLAY_MODE_PROPERTIES_2_KHR = 1000121002, + VK_STRUCTURE_TYPE_DISPLAY_PLANE_INFO_2_KHR = 1000121003, + VK_STRUCTURE_TYPE_DISPLAY_PLANE_CAPABILITIES_2_KHR = 1000121004, + VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK = 1000122000, + VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000, + VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT = 1000128000, + VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT = 1000128001, + VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT = 1000128002, + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT = 1000128003, + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000128004, + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID = 1000129000, + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID = 1000129001, + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID = 1000129002, + VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID = 1000129003, + VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID = 1000129004, + VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID = 1000129005, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_FILTER_MINMAX_PROPERTIES_EXT = 1000130000, + VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT = 1000130001, + VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT = 1000143000, + VK_STRUCTURE_TYPE_RENDER_PASS_SAMPLE_LOCATIONS_BEGIN_INFO_EXT = 1000143001, + VK_STRUCTURE_TYPE_PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT = 1000143002, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLE_LOCATIONS_PROPERTIES_EXT = 1000143003, + VK_STRUCTURE_TYPE_MULTISAMPLE_PROPERTIES_EXT = 1000143004, + VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR = 1000147000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT = 1000148000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT = 1000148001, + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_ADVANCED_STATE_CREATE_INFO_EXT = 1000148002, + VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_TO_COLOR_STATE_CREATE_INFO_NV = 1000149000, + VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_MODULATION_STATE_CREATE_INFO_NV = 1000152000, + VK_STRUCTURE_TYPE_VALIDATION_CACHE_CREATE_INFO_EXT = 1000160000, + VK_STRUCTURE_TYPE_SHADER_MODULE_VALIDATION_CACHE_CREATE_INFO_EXT = 1000160001, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT = 1000161000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT = 1000161001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT = 1000161002, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT = 1000161003, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_LAYOUT_SUPPORT_EXT = 1000161004, + VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT = 1000174000, + VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT = 1000178000, + VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT = 1000178001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT = 1000178002, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_PROPERTIES_AMD = 1000185000, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT = 1000190000, + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT = 1000190001, + VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2, + VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2, + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, + VK_STRUCTURE_TYPE_DEVICE_GROUP_RENDER_PASS_BEGIN_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_RENDER_PASS_BEGIN_INFO, + VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO, + VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO, + VK_STRUCTURE_TYPE_DEVICE_GROUP_BIND_SPARSE_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_BIND_SPARSE_INFO, + VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_DEVICE_GROUP_INFO_KHR = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_DEVICE_GROUP_INFO, + VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO_KHR = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES, + VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO, + VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES, + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, + VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, + VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, + VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, + VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES, + VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES, + VK_STRUCTURE_TYPE_RENDER_PASS_INPUT_ATTACHMENT_ASPECT_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_RENDER_PASS_INPUT_ATTACHMENT_ASPECT_CREATE_INFO, + VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES, + VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS, + VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2, + VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2, + VK_STRUCTURE_TYPE_IMAGE_SPARSE_MEMORY_REQUIREMENTS_INFO_2_KHR = VK_STRUCTURE_TYPE_IMAGE_SPARSE_MEMORY_REQUIREMENTS_INFO_2, + VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2_KHR = VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2, + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, + VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO_KHR = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO, + VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO_KHR = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES_KHR = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES, + VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO, + VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES, + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT_KHR = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT, + VK_STRUCTURE_TYPE_BEGIN_RANGE = VK_STRUCTURE_TYPE_APPLICATION_INFO, + VK_STRUCTURE_TYPE_END_RANGE = VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, + VK_STRUCTURE_TYPE_RANGE_SIZE = (VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO - VK_STRUCTURE_TYPE_APPLICATION_INFO + 1), + VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkStructureType; + +typedef enum VkSystemAllocationScope { + VK_SYSTEM_ALLOCATION_SCOPE_COMMAND = 0, + VK_SYSTEM_ALLOCATION_SCOPE_OBJECT = 1, + VK_SYSTEM_ALLOCATION_SCOPE_CACHE = 2, + VK_SYSTEM_ALLOCATION_SCOPE_DEVICE = 3, + VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE = 4, + VK_SYSTEM_ALLOCATION_SCOPE_BEGIN_RANGE = VK_SYSTEM_ALLOCATION_SCOPE_COMMAND, + VK_SYSTEM_ALLOCATION_SCOPE_END_RANGE = VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE, + VK_SYSTEM_ALLOCATION_SCOPE_RANGE_SIZE = (VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE - VK_SYSTEM_ALLOCATION_SCOPE_COMMAND + 1), + VK_SYSTEM_ALLOCATION_SCOPE_MAX_ENUM = 0x7FFFFFFF +} VkSystemAllocationScope; + +typedef enum VkInternalAllocationType { + VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE = 0, + VK_INTERNAL_ALLOCATION_TYPE_BEGIN_RANGE = VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE, + VK_INTERNAL_ALLOCATION_TYPE_END_RANGE = VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE, + VK_INTERNAL_ALLOCATION_TYPE_RANGE_SIZE = (VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE - VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE + 1), + VK_INTERNAL_ALLOCATION_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkInternalAllocationType; + +typedef enum VkFormat { + VK_FORMAT_UNDEFINED = 0, + VK_FORMAT_R4G4_UNORM_PACK8 = 1, + VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, + VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, + VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, + VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, + VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6, + VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7, + VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8, + VK_FORMAT_R8_UNORM = 9, + VK_FORMAT_R8_SNORM = 10, + VK_FORMAT_R8_USCALED = 11, + VK_FORMAT_R8_SSCALED = 12, + VK_FORMAT_R8_UINT = 13, + VK_FORMAT_R8_SINT = 14, + VK_FORMAT_R8_SRGB = 15, + VK_FORMAT_R8G8_UNORM = 16, + VK_FORMAT_R8G8_SNORM = 17, + VK_FORMAT_R8G8_USCALED = 18, + VK_FORMAT_R8G8_SSCALED = 19, + VK_FORMAT_R8G8_UINT = 20, + VK_FORMAT_R8G8_SINT = 21, + VK_FORMAT_R8G8_SRGB = 22, + VK_FORMAT_R8G8B8_UNORM = 23, + VK_FORMAT_R8G8B8_SNORM = 24, + VK_FORMAT_R8G8B8_USCALED = 25, + VK_FORMAT_R8G8B8_SSCALED = 26, + VK_FORMAT_R8G8B8_UINT = 27, + VK_FORMAT_R8G8B8_SINT = 28, + VK_FORMAT_R8G8B8_SRGB = 29, + VK_FORMAT_B8G8R8_UNORM = 30, + VK_FORMAT_B8G8R8_SNORM = 31, + VK_FORMAT_B8G8R8_USCALED = 32, + VK_FORMAT_B8G8R8_SSCALED = 33, + VK_FORMAT_B8G8R8_UINT = 34, + VK_FORMAT_B8G8R8_SINT = 35, + VK_FORMAT_B8G8R8_SRGB = 36, + VK_FORMAT_R8G8B8A8_UNORM = 37, + VK_FORMAT_R8G8B8A8_SNORM = 38, + VK_FORMAT_R8G8B8A8_USCALED = 39, + VK_FORMAT_R8G8B8A8_SSCALED = 40, + VK_FORMAT_R8G8B8A8_UINT = 41, + VK_FORMAT_R8G8B8A8_SINT = 42, + VK_FORMAT_R8G8B8A8_SRGB = 43, + VK_FORMAT_B8G8R8A8_UNORM = 44, + VK_FORMAT_B8G8R8A8_SNORM = 45, + VK_FORMAT_B8G8R8A8_USCALED = 46, + VK_FORMAT_B8G8R8A8_SSCALED = 47, + VK_FORMAT_B8G8R8A8_UINT = 48, + VK_FORMAT_B8G8R8A8_SINT = 49, + VK_FORMAT_B8G8R8A8_SRGB = 50, + VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51, + VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52, + VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53, + VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54, + VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55, + VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56, + VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57, + VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58, + VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59, + VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60, + VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61, + VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62, + VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63, + VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64, + VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65, + VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66, + VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67, + VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68, + VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69, + VK_FORMAT_R16_UNORM = 70, + VK_FORMAT_R16_SNORM = 71, + VK_FORMAT_R16_USCALED = 72, + VK_FORMAT_R16_SSCALED = 73, + VK_FORMAT_R16_UINT = 74, + VK_FORMAT_R16_SINT = 75, + VK_FORMAT_R16_SFLOAT = 76, + VK_FORMAT_R16G16_UNORM = 77, + VK_FORMAT_R16G16_SNORM = 78, + VK_FORMAT_R16G16_USCALED = 79, + VK_FORMAT_R16G16_SSCALED = 80, + VK_FORMAT_R16G16_UINT = 81, + VK_FORMAT_R16G16_SINT = 82, + VK_FORMAT_R16G16_SFLOAT = 83, + VK_FORMAT_R16G16B16_UNORM = 84, + VK_FORMAT_R16G16B16_SNORM = 85, + VK_FORMAT_R16G16B16_USCALED = 86, + VK_FORMAT_R16G16B16_SSCALED = 87, + VK_FORMAT_R16G16B16_UINT = 88, + VK_FORMAT_R16G16B16_SINT = 89, + VK_FORMAT_R16G16B16_SFLOAT = 90, + VK_FORMAT_R16G16B16A16_UNORM = 91, + VK_FORMAT_R16G16B16A16_SNORM = 92, + VK_FORMAT_R16G16B16A16_USCALED = 93, + VK_FORMAT_R16G16B16A16_SSCALED = 94, + VK_FORMAT_R16G16B16A16_UINT = 95, + VK_FORMAT_R16G16B16A16_SINT = 96, + VK_FORMAT_R16G16B16A16_SFLOAT = 97, + VK_FORMAT_R32_UINT = 98, + VK_FORMAT_R32_SINT = 99, + VK_FORMAT_R32_SFLOAT = 100, + VK_FORMAT_R32G32_UINT = 101, + VK_FORMAT_R32G32_SINT = 102, + VK_FORMAT_R32G32_SFLOAT = 103, + VK_FORMAT_R32G32B32_UINT = 104, + VK_FORMAT_R32G32B32_SINT = 105, + VK_FORMAT_R32G32B32_SFLOAT = 106, + VK_FORMAT_R32G32B32A32_UINT = 107, + VK_FORMAT_R32G32B32A32_SINT = 108, + VK_FORMAT_R32G32B32A32_SFLOAT = 109, + VK_FORMAT_R64_UINT = 110, + VK_FORMAT_R64_SINT = 111, + VK_FORMAT_R64_SFLOAT = 112, + VK_FORMAT_R64G64_UINT = 113, + VK_FORMAT_R64G64_SINT = 114, + VK_FORMAT_R64G64_SFLOAT = 115, + VK_FORMAT_R64G64B64_UINT = 116, + VK_FORMAT_R64G64B64_SINT = 117, + VK_FORMAT_R64G64B64_SFLOAT = 118, + VK_FORMAT_R64G64B64A64_UINT = 119, + VK_FORMAT_R64G64B64A64_SINT = 120, + VK_FORMAT_R64G64B64A64_SFLOAT = 121, + VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123, + VK_FORMAT_D16_UNORM = 124, + VK_FORMAT_X8_D24_UNORM_PACK32 = 125, + VK_FORMAT_D32_SFLOAT = 126, + VK_FORMAT_S8_UINT = 127, + VK_FORMAT_D16_UNORM_S8_UINT = 128, + VK_FORMAT_D24_UNORM_S8_UINT = 129, + VK_FORMAT_D32_SFLOAT_S8_UINT = 130, + VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131, + VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132, + VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133, + VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134, + VK_FORMAT_BC2_UNORM_BLOCK = 135, + VK_FORMAT_BC2_SRGB_BLOCK = 136, + VK_FORMAT_BC3_UNORM_BLOCK = 137, + VK_FORMAT_BC3_SRGB_BLOCK = 138, + VK_FORMAT_BC4_UNORM_BLOCK = 139, + VK_FORMAT_BC4_SNORM_BLOCK = 140, + VK_FORMAT_BC5_UNORM_BLOCK = 141, + VK_FORMAT_BC5_SNORM_BLOCK = 142, + VK_FORMAT_BC6H_UFLOAT_BLOCK = 143, + VK_FORMAT_BC6H_SFLOAT_BLOCK = 144, + VK_FORMAT_BC7_UNORM_BLOCK = 145, + VK_FORMAT_BC7_SRGB_BLOCK = 146, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148, + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149, + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150, + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152, + VK_FORMAT_EAC_R11_UNORM_BLOCK = 153, + VK_FORMAT_EAC_R11_SNORM_BLOCK = 154, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158, + VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159, + VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160, + VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161, + VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162, + VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163, + VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166, + VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167, + VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168, + VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169, + VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170, + VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171, + VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172, + VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173, + VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174, + VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175, + VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176, + VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177, + VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178, + VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179, + VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180, + VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181, + VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182, + VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183, + VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184, + VK_FORMAT_G8B8G8R8_422_UNORM = 1000156000, + VK_FORMAT_B8G8R8G8_422_UNORM = 1000156001, + VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM = 1000156002, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM = 1000156003, + VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM = 1000156004, + VK_FORMAT_G8_B8R8_2PLANE_422_UNORM = 1000156005, + VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM = 1000156006, + VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007, + VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008, + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009, + VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010, + VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16 = 1000156012, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16 = 1000156013, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16 = 1000156014, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16 = 1000156015, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16 = 1000156016, + VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017, + VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018, + VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019, + VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020, + VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16 = 1000156022, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16 = 1000156023, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16 = 1000156024, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16 = 1000156025, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16 = 1000156026, + VK_FORMAT_G16B16G16R16_422_UNORM = 1000156027, + VK_FORMAT_B16G16R16G16_422_UNORM = 1000156028, + VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM = 1000156029, + VK_FORMAT_G16_B16R16_2PLANE_420_UNORM = 1000156030, + VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM = 1000156031, + VK_FORMAT_G16_B16R16_2PLANE_422_UNORM = 1000156032, + VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM = 1000156033, + VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG = 1000054000, + VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG = 1000054001, + VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG = 1000054002, + VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG = 1000054003, + VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG = 1000054004, + VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG = 1000054005, + VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG = 1000054006, + VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG = 1000054007, + VK_FORMAT_G8B8G8R8_422_UNORM_KHR = VK_FORMAT_G8B8G8R8_422_UNORM, + VK_FORMAT_B8G8R8G8_422_UNORM_KHR = VK_FORMAT_B8G8R8G8_422_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR = VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, + VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR = VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR = VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, + VK_FORMAT_R10X6_UNORM_PACK16_KHR = VK_FORMAT_R10X6_UNORM_PACK16, + VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR = VK_FORMAT_R10X6G10X6_UNORM_2PACK16, + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16_KHR = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, + VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR = VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, + VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR = VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, + VK_FORMAT_R12X4_UNORM_PACK16_KHR = VK_FORMAT_R12X4_UNORM_PACK16, + VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR = VK_FORMAT_R12X4G12X4_UNORM_2PACK16, + VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16_KHR = VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16, + VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR = VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, + VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR = VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, + VK_FORMAT_G16B16G16R16_422_UNORM_KHR = VK_FORMAT_G16B16G16R16_422_UNORM, + VK_FORMAT_B16G16R16G16_422_UNORM_KHR = VK_FORMAT_B16G16R16G16_422_UNORM, + VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR = VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, + VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, + VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR = VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM, + VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR = VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, + VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR = VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM, + VK_FORMAT_BEGIN_RANGE = VK_FORMAT_UNDEFINED, + VK_FORMAT_END_RANGE = VK_FORMAT_ASTC_12x12_SRGB_BLOCK, + VK_FORMAT_RANGE_SIZE = (VK_FORMAT_ASTC_12x12_SRGB_BLOCK - VK_FORMAT_UNDEFINED + 1), + VK_FORMAT_MAX_ENUM = 0x7FFFFFFF +} VkFormat; + +typedef enum VkImageType { + VK_IMAGE_TYPE_1D = 0, + VK_IMAGE_TYPE_2D = 1, + VK_IMAGE_TYPE_3D = 2, + VK_IMAGE_TYPE_BEGIN_RANGE = VK_IMAGE_TYPE_1D, + VK_IMAGE_TYPE_END_RANGE = VK_IMAGE_TYPE_3D, + VK_IMAGE_TYPE_RANGE_SIZE = (VK_IMAGE_TYPE_3D - VK_IMAGE_TYPE_1D + 1), + VK_IMAGE_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkImageType; + +typedef enum VkImageTiling { + VK_IMAGE_TILING_OPTIMAL = 0, + VK_IMAGE_TILING_LINEAR = 1, + VK_IMAGE_TILING_BEGIN_RANGE = VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_TILING_END_RANGE = VK_IMAGE_TILING_LINEAR, + VK_IMAGE_TILING_RANGE_SIZE = (VK_IMAGE_TILING_LINEAR - VK_IMAGE_TILING_OPTIMAL + 1), + VK_IMAGE_TILING_MAX_ENUM = 0x7FFFFFFF +} VkImageTiling; + +typedef enum VkPhysicalDeviceType { + VK_PHYSICAL_DEVICE_TYPE_OTHER = 0, + VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1, + VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2, + VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3, + VK_PHYSICAL_DEVICE_TYPE_CPU = 4, + VK_PHYSICAL_DEVICE_TYPE_BEGIN_RANGE = VK_PHYSICAL_DEVICE_TYPE_OTHER, + VK_PHYSICAL_DEVICE_TYPE_END_RANGE = VK_PHYSICAL_DEVICE_TYPE_CPU, + VK_PHYSICAL_DEVICE_TYPE_RANGE_SIZE = (VK_PHYSICAL_DEVICE_TYPE_CPU - VK_PHYSICAL_DEVICE_TYPE_OTHER + 1), + VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkPhysicalDeviceType; + +typedef enum VkQueryType { + VK_QUERY_TYPE_OCCLUSION = 0, + VK_QUERY_TYPE_PIPELINE_STATISTICS = 1, + VK_QUERY_TYPE_TIMESTAMP = 2, + VK_QUERY_TYPE_BEGIN_RANGE = VK_QUERY_TYPE_OCCLUSION, + VK_QUERY_TYPE_END_RANGE = VK_QUERY_TYPE_TIMESTAMP, + VK_QUERY_TYPE_RANGE_SIZE = (VK_QUERY_TYPE_TIMESTAMP - VK_QUERY_TYPE_OCCLUSION + 1), + VK_QUERY_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkQueryType; + +typedef enum VkSharingMode { + VK_SHARING_MODE_EXCLUSIVE = 0, + VK_SHARING_MODE_CONCURRENT = 1, + VK_SHARING_MODE_BEGIN_RANGE = VK_SHARING_MODE_EXCLUSIVE, + VK_SHARING_MODE_END_RANGE = VK_SHARING_MODE_CONCURRENT, + VK_SHARING_MODE_RANGE_SIZE = (VK_SHARING_MODE_CONCURRENT - VK_SHARING_MODE_EXCLUSIVE + 1), + VK_SHARING_MODE_MAX_ENUM = 0x7FFFFFFF +} VkSharingMode; + +typedef enum VkImageLayout { + VK_IMAGE_LAYOUT_UNDEFINED = 0, + VK_IMAGE_LAYOUT_GENERAL = 1, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL = 2, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL = 3, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL = 4, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL = 5, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL = 6, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL = 7, + VK_IMAGE_LAYOUT_PREINITIALIZED = 8, + VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL = 1000117000, + VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL = 1000117001, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR = 1000001002, + VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR = 1000111000, + VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_BEGIN_RANGE = VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_END_RANGE = VK_IMAGE_LAYOUT_PREINITIALIZED, + VK_IMAGE_LAYOUT_RANGE_SIZE = (VK_IMAGE_LAYOUT_PREINITIALIZED - VK_IMAGE_LAYOUT_UNDEFINED + 1), + VK_IMAGE_LAYOUT_MAX_ENUM = 0x7FFFFFFF +} VkImageLayout; + +typedef enum VkImageViewType { + VK_IMAGE_VIEW_TYPE_1D = 0, + VK_IMAGE_VIEW_TYPE_2D = 1, + VK_IMAGE_VIEW_TYPE_3D = 2, + VK_IMAGE_VIEW_TYPE_CUBE = 3, + VK_IMAGE_VIEW_TYPE_1D_ARRAY = 4, + VK_IMAGE_VIEW_TYPE_2D_ARRAY = 5, + VK_IMAGE_VIEW_TYPE_CUBE_ARRAY = 6, + VK_IMAGE_VIEW_TYPE_BEGIN_RANGE = VK_IMAGE_VIEW_TYPE_1D, + VK_IMAGE_VIEW_TYPE_END_RANGE = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, + VK_IMAGE_VIEW_TYPE_RANGE_SIZE = (VK_IMAGE_VIEW_TYPE_CUBE_ARRAY - VK_IMAGE_VIEW_TYPE_1D + 1), + VK_IMAGE_VIEW_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkImageViewType; + +typedef enum VkComponentSwizzle { + VK_COMPONENT_SWIZZLE_IDENTITY = 0, + VK_COMPONENT_SWIZZLE_ZERO = 1, + VK_COMPONENT_SWIZZLE_ONE = 2, + VK_COMPONENT_SWIZZLE_R = 3, + VK_COMPONENT_SWIZZLE_G = 4, + VK_COMPONENT_SWIZZLE_B = 5, + VK_COMPONENT_SWIZZLE_A = 6, + VK_COMPONENT_SWIZZLE_BEGIN_RANGE = VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_END_RANGE = VK_COMPONENT_SWIZZLE_A, + VK_COMPONENT_SWIZZLE_RANGE_SIZE = (VK_COMPONENT_SWIZZLE_A - VK_COMPONENT_SWIZZLE_IDENTITY + 1), + VK_COMPONENT_SWIZZLE_MAX_ENUM = 0x7FFFFFFF +} VkComponentSwizzle; + +typedef enum VkVertexInputRate { + VK_VERTEX_INPUT_RATE_VERTEX = 0, + VK_VERTEX_INPUT_RATE_INSTANCE = 1, + VK_VERTEX_INPUT_RATE_BEGIN_RANGE = VK_VERTEX_INPUT_RATE_VERTEX, + VK_VERTEX_INPUT_RATE_END_RANGE = VK_VERTEX_INPUT_RATE_INSTANCE, + VK_VERTEX_INPUT_RATE_RANGE_SIZE = (VK_VERTEX_INPUT_RATE_INSTANCE - VK_VERTEX_INPUT_RATE_VERTEX + 1), + VK_VERTEX_INPUT_RATE_MAX_ENUM = 0x7FFFFFFF +} VkVertexInputRate; + +typedef enum VkPrimitiveTopology { + VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0, + VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1, + VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5, + VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY = 6, + VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY = 7, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY = 8, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY = 9, + VK_PRIMITIVE_TOPOLOGY_PATCH_LIST = 10, + VK_PRIMITIVE_TOPOLOGY_BEGIN_RANGE = VK_PRIMITIVE_TOPOLOGY_POINT_LIST, + VK_PRIMITIVE_TOPOLOGY_END_RANGE = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, + VK_PRIMITIVE_TOPOLOGY_RANGE_SIZE = (VK_PRIMITIVE_TOPOLOGY_PATCH_LIST - VK_PRIMITIVE_TOPOLOGY_POINT_LIST + 1), + VK_PRIMITIVE_TOPOLOGY_MAX_ENUM = 0x7FFFFFFF +} VkPrimitiveTopology; + +typedef enum VkPolygonMode { + VK_POLYGON_MODE_FILL = 0, + VK_POLYGON_MODE_LINE = 1, + VK_POLYGON_MODE_POINT = 2, + VK_POLYGON_MODE_FILL_RECTANGLE_NV = 1000153000, + VK_POLYGON_MODE_BEGIN_RANGE = VK_POLYGON_MODE_FILL, + VK_POLYGON_MODE_END_RANGE = VK_POLYGON_MODE_POINT, + VK_POLYGON_MODE_RANGE_SIZE = (VK_POLYGON_MODE_POINT - VK_POLYGON_MODE_FILL + 1), + VK_POLYGON_MODE_MAX_ENUM = 0x7FFFFFFF +} VkPolygonMode; + +typedef enum VkFrontFace { + VK_FRONT_FACE_COUNTER_CLOCKWISE = 0, + VK_FRONT_FACE_CLOCKWISE = 1, + VK_FRONT_FACE_BEGIN_RANGE = VK_FRONT_FACE_COUNTER_CLOCKWISE, + VK_FRONT_FACE_END_RANGE = VK_FRONT_FACE_CLOCKWISE, + VK_FRONT_FACE_RANGE_SIZE = (VK_FRONT_FACE_CLOCKWISE - VK_FRONT_FACE_COUNTER_CLOCKWISE + 1), + VK_FRONT_FACE_MAX_ENUM = 0x7FFFFFFF +} VkFrontFace; + +typedef enum VkCompareOp { + VK_COMPARE_OP_NEVER = 0, + VK_COMPARE_OP_LESS = 1, + VK_COMPARE_OP_EQUAL = 2, + VK_COMPARE_OP_LESS_OR_EQUAL = 3, + VK_COMPARE_OP_GREATER = 4, + VK_COMPARE_OP_NOT_EQUAL = 5, + VK_COMPARE_OP_GREATER_OR_EQUAL = 6, + VK_COMPARE_OP_ALWAYS = 7, + VK_COMPARE_OP_BEGIN_RANGE = VK_COMPARE_OP_NEVER, + VK_COMPARE_OP_END_RANGE = VK_COMPARE_OP_ALWAYS, + VK_COMPARE_OP_RANGE_SIZE = (VK_COMPARE_OP_ALWAYS - VK_COMPARE_OP_NEVER + 1), + VK_COMPARE_OP_MAX_ENUM = 0x7FFFFFFF +} VkCompareOp; + +typedef enum VkStencilOp { + VK_STENCIL_OP_KEEP = 0, + VK_STENCIL_OP_ZERO = 1, + VK_STENCIL_OP_REPLACE = 2, + VK_STENCIL_OP_INCREMENT_AND_CLAMP = 3, + VK_STENCIL_OP_DECREMENT_AND_CLAMP = 4, + VK_STENCIL_OP_INVERT = 5, + VK_STENCIL_OP_INCREMENT_AND_WRAP = 6, + VK_STENCIL_OP_DECREMENT_AND_WRAP = 7, + VK_STENCIL_OP_BEGIN_RANGE = VK_STENCIL_OP_KEEP, + VK_STENCIL_OP_END_RANGE = VK_STENCIL_OP_DECREMENT_AND_WRAP, + VK_STENCIL_OP_RANGE_SIZE = (VK_STENCIL_OP_DECREMENT_AND_WRAP - VK_STENCIL_OP_KEEP + 1), + VK_STENCIL_OP_MAX_ENUM = 0x7FFFFFFF +} VkStencilOp; + +typedef enum VkLogicOp { + VK_LOGIC_OP_CLEAR = 0, + VK_LOGIC_OP_AND = 1, + VK_LOGIC_OP_AND_REVERSE = 2, + VK_LOGIC_OP_COPY = 3, + VK_LOGIC_OP_AND_INVERTED = 4, + VK_LOGIC_OP_NO_OP = 5, + VK_LOGIC_OP_XOR = 6, + VK_LOGIC_OP_OR = 7, + VK_LOGIC_OP_NOR = 8, + VK_LOGIC_OP_EQUIVALENT = 9, + VK_LOGIC_OP_INVERT = 10, + VK_LOGIC_OP_OR_REVERSE = 11, + VK_LOGIC_OP_COPY_INVERTED = 12, + VK_LOGIC_OP_OR_INVERTED = 13, + VK_LOGIC_OP_NAND = 14, + VK_LOGIC_OP_SET = 15, + VK_LOGIC_OP_BEGIN_RANGE = VK_LOGIC_OP_CLEAR, + VK_LOGIC_OP_END_RANGE = VK_LOGIC_OP_SET, + VK_LOGIC_OP_RANGE_SIZE = (VK_LOGIC_OP_SET - VK_LOGIC_OP_CLEAR + 1), + VK_LOGIC_OP_MAX_ENUM = 0x7FFFFFFF +} VkLogicOp; + +typedef enum VkBlendFactor { + VK_BLEND_FACTOR_ZERO = 0, + VK_BLEND_FACTOR_ONE = 1, + VK_BLEND_FACTOR_SRC_COLOR = 2, + VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR = 3, + VK_BLEND_FACTOR_DST_COLOR = 4, + VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR = 5, + VK_BLEND_FACTOR_SRC_ALPHA = 6, + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA = 7, + VK_BLEND_FACTOR_DST_ALPHA = 8, + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA = 9, + VK_BLEND_FACTOR_CONSTANT_COLOR = 10, + VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR = 11, + VK_BLEND_FACTOR_CONSTANT_ALPHA = 12, + VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA = 13, + VK_BLEND_FACTOR_SRC_ALPHA_SATURATE = 14, + VK_BLEND_FACTOR_SRC1_COLOR = 15, + VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR = 16, + VK_BLEND_FACTOR_SRC1_ALPHA = 17, + VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA = 18, + VK_BLEND_FACTOR_BEGIN_RANGE = VK_BLEND_FACTOR_ZERO, + VK_BLEND_FACTOR_END_RANGE = VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, + VK_BLEND_FACTOR_RANGE_SIZE = (VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA - VK_BLEND_FACTOR_ZERO + 1), + VK_BLEND_FACTOR_MAX_ENUM = 0x7FFFFFFF +} VkBlendFactor; + +typedef enum VkBlendOp { + VK_BLEND_OP_ADD = 0, + VK_BLEND_OP_SUBTRACT = 1, + VK_BLEND_OP_REVERSE_SUBTRACT = 2, + VK_BLEND_OP_MIN = 3, + VK_BLEND_OP_MAX = 4, + VK_BLEND_OP_ZERO_EXT = 1000148000, + VK_BLEND_OP_SRC_EXT = 1000148001, + VK_BLEND_OP_DST_EXT = 1000148002, + VK_BLEND_OP_SRC_OVER_EXT = 1000148003, + VK_BLEND_OP_DST_OVER_EXT = 1000148004, + VK_BLEND_OP_SRC_IN_EXT = 1000148005, + VK_BLEND_OP_DST_IN_EXT = 1000148006, + VK_BLEND_OP_SRC_OUT_EXT = 1000148007, + VK_BLEND_OP_DST_OUT_EXT = 1000148008, + VK_BLEND_OP_SRC_ATOP_EXT = 1000148009, + VK_BLEND_OP_DST_ATOP_EXT = 1000148010, + VK_BLEND_OP_XOR_EXT = 1000148011, + VK_BLEND_OP_MULTIPLY_EXT = 1000148012, + VK_BLEND_OP_SCREEN_EXT = 1000148013, + VK_BLEND_OP_OVERLAY_EXT = 1000148014, + VK_BLEND_OP_DARKEN_EXT = 1000148015, + VK_BLEND_OP_LIGHTEN_EXT = 1000148016, + VK_BLEND_OP_COLORDODGE_EXT = 1000148017, + VK_BLEND_OP_COLORBURN_EXT = 1000148018, + VK_BLEND_OP_HARDLIGHT_EXT = 1000148019, + VK_BLEND_OP_SOFTLIGHT_EXT = 1000148020, + VK_BLEND_OP_DIFFERENCE_EXT = 1000148021, + VK_BLEND_OP_EXCLUSION_EXT = 1000148022, + VK_BLEND_OP_INVERT_EXT = 1000148023, + VK_BLEND_OP_INVERT_RGB_EXT = 1000148024, + VK_BLEND_OP_LINEARDODGE_EXT = 1000148025, + VK_BLEND_OP_LINEARBURN_EXT = 1000148026, + VK_BLEND_OP_VIVIDLIGHT_EXT = 1000148027, + VK_BLEND_OP_LINEARLIGHT_EXT = 1000148028, + VK_BLEND_OP_PINLIGHT_EXT = 1000148029, + VK_BLEND_OP_HARDMIX_EXT = 1000148030, + VK_BLEND_OP_HSL_HUE_EXT = 1000148031, + VK_BLEND_OP_HSL_SATURATION_EXT = 1000148032, + VK_BLEND_OP_HSL_COLOR_EXT = 1000148033, + VK_BLEND_OP_HSL_LUMINOSITY_EXT = 1000148034, + VK_BLEND_OP_PLUS_EXT = 1000148035, + VK_BLEND_OP_PLUS_CLAMPED_EXT = 1000148036, + VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT = 1000148037, + VK_BLEND_OP_PLUS_DARKER_EXT = 1000148038, + VK_BLEND_OP_MINUS_EXT = 1000148039, + VK_BLEND_OP_MINUS_CLAMPED_EXT = 1000148040, + VK_BLEND_OP_CONTRAST_EXT = 1000148041, + VK_BLEND_OP_INVERT_OVG_EXT = 1000148042, + VK_BLEND_OP_RED_EXT = 1000148043, + VK_BLEND_OP_GREEN_EXT = 1000148044, + VK_BLEND_OP_BLUE_EXT = 1000148045, + VK_BLEND_OP_BEGIN_RANGE = VK_BLEND_OP_ADD, + VK_BLEND_OP_END_RANGE = VK_BLEND_OP_MAX, + VK_BLEND_OP_RANGE_SIZE = (VK_BLEND_OP_MAX - VK_BLEND_OP_ADD + 1), + VK_BLEND_OP_MAX_ENUM = 0x7FFFFFFF +} VkBlendOp; + +typedef enum VkDynamicState { + VK_DYNAMIC_STATE_VIEWPORT = 0, + VK_DYNAMIC_STATE_SCISSOR = 1, + VK_DYNAMIC_STATE_LINE_WIDTH = 2, + VK_DYNAMIC_STATE_DEPTH_BIAS = 3, + VK_DYNAMIC_STATE_BLEND_CONSTANTS = 4, + VK_DYNAMIC_STATE_DEPTH_BOUNDS = 5, + VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK = 6, + VK_DYNAMIC_STATE_STENCIL_WRITE_MASK = 7, + VK_DYNAMIC_STATE_STENCIL_REFERENCE = 8, + VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_NV = 1000087000, + VK_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT = 1000099000, + VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT = 1000143000, + VK_DYNAMIC_STATE_BEGIN_RANGE = VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_END_RANGE = VK_DYNAMIC_STATE_STENCIL_REFERENCE, + VK_DYNAMIC_STATE_RANGE_SIZE = (VK_DYNAMIC_STATE_STENCIL_REFERENCE - VK_DYNAMIC_STATE_VIEWPORT + 1), + VK_DYNAMIC_STATE_MAX_ENUM = 0x7FFFFFFF +} VkDynamicState; + +typedef enum VkFilter { + VK_FILTER_NEAREST = 0, + VK_FILTER_LINEAR = 1, + VK_FILTER_CUBIC_IMG = 1000015000, + VK_FILTER_BEGIN_RANGE = VK_FILTER_NEAREST, + VK_FILTER_END_RANGE = VK_FILTER_LINEAR, + VK_FILTER_RANGE_SIZE = (VK_FILTER_LINEAR - VK_FILTER_NEAREST + 1), + VK_FILTER_MAX_ENUM = 0x7FFFFFFF +} VkFilter; + +typedef enum VkSamplerMipmapMode { + VK_SAMPLER_MIPMAP_MODE_NEAREST = 0, + VK_SAMPLER_MIPMAP_MODE_LINEAR = 1, + VK_SAMPLER_MIPMAP_MODE_BEGIN_RANGE = VK_SAMPLER_MIPMAP_MODE_NEAREST, + VK_SAMPLER_MIPMAP_MODE_END_RANGE = VK_SAMPLER_MIPMAP_MODE_LINEAR, + VK_SAMPLER_MIPMAP_MODE_RANGE_SIZE = (VK_SAMPLER_MIPMAP_MODE_LINEAR - VK_SAMPLER_MIPMAP_MODE_NEAREST + 1), + VK_SAMPLER_MIPMAP_MODE_MAX_ENUM = 0x7FFFFFFF +} VkSamplerMipmapMode; + +typedef enum VkSamplerAddressMode { + VK_SAMPLER_ADDRESS_MODE_REPEAT = 0, + VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT = 1, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE = 2, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER = 3, + VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE = 4, + VK_SAMPLER_ADDRESS_MODE_BEGIN_RANGE = VK_SAMPLER_ADDRESS_MODE_REPEAT, + VK_SAMPLER_ADDRESS_MODE_END_RANGE = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + VK_SAMPLER_ADDRESS_MODE_RANGE_SIZE = (VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER - VK_SAMPLER_ADDRESS_MODE_REPEAT + 1), + VK_SAMPLER_ADDRESS_MODE_MAX_ENUM = 0x7FFFFFFF +} VkSamplerAddressMode; + +typedef enum VkBorderColor { + VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK = 0, + VK_BORDER_COLOR_INT_TRANSPARENT_BLACK = 1, + VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK = 2, + VK_BORDER_COLOR_INT_OPAQUE_BLACK = 3, + VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE = 4, + VK_BORDER_COLOR_INT_OPAQUE_WHITE = 5, + VK_BORDER_COLOR_BEGIN_RANGE = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, + VK_BORDER_COLOR_END_RANGE = VK_BORDER_COLOR_INT_OPAQUE_WHITE, + VK_BORDER_COLOR_RANGE_SIZE = (VK_BORDER_COLOR_INT_OPAQUE_WHITE - VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK + 1), + VK_BORDER_COLOR_MAX_ENUM = 0x7FFFFFFF +} VkBorderColor; + +typedef enum VkDescriptorType { + VK_DESCRIPTOR_TYPE_SAMPLER = 0, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1, + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE = 2, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3, + VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER = 4, + VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER = 5, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC = 8, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC = 9, + VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT = 10, + VK_DESCRIPTOR_TYPE_BEGIN_RANGE = VK_DESCRIPTOR_TYPE_SAMPLER, + VK_DESCRIPTOR_TYPE_END_RANGE = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + VK_DESCRIPTOR_TYPE_RANGE_SIZE = (VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT - VK_DESCRIPTOR_TYPE_SAMPLER + 1), + VK_DESCRIPTOR_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkDescriptorType; + +typedef enum VkAttachmentLoadOp { + VK_ATTACHMENT_LOAD_OP_LOAD = 0, + VK_ATTACHMENT_LOAD_OP_CLEAR = 1, + VK_ATTACHMENT_LOAD_OP_DONT_CARE = 2, + VK_ATTACHMENT_LOAD_OP_BEGIN_RANGE = VK_ATTACHMENT_LOAD_OP_LOAD, + VK_ATTACHMENT_LOAD_OP_END_RANGE = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_LOAD_OP_RANGE_SIZE = (VK_ATTACHMENT_LOAD_OP_DONT_CARE - VK_ATTACHMENT_LOAD_OP_LOAD + 1), + VK_ATTACHMENT_LOAD_OP_MAX_ENUM = 0x7FFFFFFF +} VkAttachmentLoadOp; + +typedef enum VkAttachmentStoreOp { + VK_ATTACHMENT_STORE_OP_STORE = 0, + VK_ATTACHMENT_STORE_OP_DONT_CARE = 1, + VK_ATTACHMENT_STORE_OP_BEGIN_RANGE = VK_ATTACHMENT_STORE_OP_STORE, + VK_ATTACHMENT_STORE_OP_END_RANGE = VK_ATTACHMENT_STORE_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_RANGE_SIZE = (VK_ATTACHMENT_STORE_OP_DONT_CARE - VK_ATTACHMENT_STORE_OP_STORE + 1), + VK_ATTACHMENT_STORE_OP_MAX_ENUM = 0x7FFFFFFF +} VkAttachmentStoreOp; + +typedef enum VkPipelineBindPoint { + VK_PIPELINE_BIND_POINT_GRAPHICS = 0, + VK_PIPELINE_BIND_POINT_COMPUTE = 1, + VK_PIPELINE_BIND_POINT_BEGIN_RANGE = VK_PIPELINE_BIND_POINT_GRAPHICS, + VK_PIPELINE_BIND_POINT_END_RANGE = VK_PIPELINE_BIND_POINT_COMPUTE, + VK_PIPELINE_BIND_POINT_RANGE_SIZE = (VK_PIPELINE_BIND_POINT_COMPUTE - VK_PIPELINE_BIND_POINT_GRAPHICS + 1), + VK_PIPELINE_BIND_POINT_MAX_ENUM = 0x7FFFFFFF +} VkPipelineBindPoint; + +typedef enum VkCommandBufferLevel { + VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0, + VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1, + VK_COMMAND_BUFFER_LEVEL_BEGIN_RANGE = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + VK_COMMAND_BUFFER_LEVEL_END_RANGE = VK_COMMAND_BUFFER_LEVEL_SECONDARY, + VK_COMMAND_BUFFER_LEVEL_RANGE_SIZE = (VK_COMMAND_BUFFER_LEVEL_SECONDARY - VK_COMMAND_BUFFER_LEVEL_PRIMARY + 1), + VK_COMMAND_BUFFER_LEVEL_MAX_ENUM = 0x7FFFFFFF +} VkCommandBufferLevel; + +typedef enum VkIndexType { + VK_INDEX_TYPE_UINT16 = 0, + VK_INDEX_TYPE_UINT32 = 1, + VK_INDEX_TYPE_BEGIN_RANGE = VK_INDEX_TYPE_UINT16, + VK_INDEX_TYPE_END_RANGE = VK_INDEX_TYPE_UINT32, + VK_INDEX_TYPE_RANGE_SIZE = (VK_INDEX_TYPE_UINT32 - VK_INDEX_TYPE_UINT16 + 1), + VK_INDEX_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkIndexType; + +typedef enum VkSubpassContents { + VK_SUBPASS_CONTENTS_INLINE = 0, + VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS = 1, + VK_SUBPASS_CONTENTS_BEGIN_RANGE = VK_SUBPASS_CONTENTS_INLINE, + VK_SUBPASS_CONTENTS_END_RANGE = VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS, + VK_SUBPASS_CONTENTS_RANGE_SIZE = (VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS - VK_SUBPASS_CONTENTS_INLINE + 1), + VK_SUBPASS_CONTENTS_MAX_ENUM = 0x7FFFFFFF +} VkSubpassContents; + +typedef enum VkObjectType { + VK_OBJECT_TYPE_UNKNOWN = 0, + VK_OBJECT_TYPE_INSTANCE = 1, + VK_OBJECT_TYPE_PHYSICAL_DEVICE = 2, + VK_OBJECT_TYPE_DEVICE = 3, + VK_OBJECT_TYPE_QUEUE = 4, + VK_OBJECT_TYPE_SEMAPHORE = 5, + VK_OBJECT_TYPE_COMMAND_BUFFER = 6, + VK_OBJECT_TYPE_FENCE = 7, + VK_OBJECT_TYPE_DEVICE_MEMORY = 8, + VK_OBJECT_TYPE_BUFFER = 9, + VK_OBJECT_TYPE_IMAGE = 10, + VK_OBJECT_TYPE_EVENT = 11, + VK_OBJECT_TYPE_QUERY_POOL = 12, + VK_OBJECT_TYPE_BUFFER_VIEW = 13, + VK_OBJECT_TYPE_IMAGE_VIEW = 14, + VK_OBJECT_TYPE_SHADER_MODULE = 15, + VK_OBJECT_TYPE_PIPELINE_CACHE = 16, + VK_OBJECT_TYPE_PIPELINE_LAYOUT = 17, + VK_OBJECT_TYPE_RENDER_PASS = 18, + VK_OBJECT_TYPE_PIPELINE = 19, + VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT = 20, + VK_OBJECT_TYPE_SAMPLER = 21, + VK_OBJECT_TYPE_DESCRIPTOR_POOL = 22, + VK_OBJECT_TYPE_DESCRIPTOR_SET = 23, + VK_OBJECT_TYPE_FRAMEBUFFER = 24, + VK_OBJECT_TYPE_COMMAND_POOL = 25, + VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION = 1000156000, + VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE = 1000085000, + VK_OBJECT_TYPE_SURFACE_KHR = 1000000000, + VK_OBJECT_TYPE_SWAPCHAIN_KHR = 1000001000, + VK_OBJECT_TYPE_DISPLAY_KHR = 1000002000, + VK_OBJECT_TYPE_DISPLAY_MODE_KHR = 1000002001, + VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT = 1000011000, + VK_OBJECT_TYPE_OBJECT_TABLE_NVX = 1000086000, + VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX = 1000086001, + VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT = 1000128000, + VK_OBJECT_TYPE_VALIDATION_CACHE_EXT = 1000160000, + VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR = VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE, + VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR = VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION, + VK_OBJECT_TYPE_BEGIN_RANGE = VK_OBJECT_TYPE_UNKNOWN, + VK_OBJECT_TYPE_END_RANGE = VK_OBJECT_TYPE_COMMAND_POOL, + VK_OBJECT_TYPE_RANGE_SIZE = (VK_OBJECT_TYPE_COMMAND_POOL - VK_OBJECT_TYPE_UNKNOWN + 1), + VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkObjectType; + +typedef VkFlags VkInstanceCreateFlags; + +typedef enum VkFormatFeatureFlagBits { + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT = 0x00000001, + VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT = 0x00000002, + VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT = 0x00000004, + VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000008, + VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT = 0x00000010, + VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT = 0x00000020, + VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT = 0x00000040, + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT = 0x00000080, + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT = 0x00000100, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000200, + VK_FORMAT_FEATURE_BLIT_SRC_BIT = 0x00000400, + VK_FORMAT_FEATURE_BLIT_DST_BIT = 0x00000800, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT = 0x00001000, + VK_FORMAT_FEATURE_TRANSFER_SRC_BIT = 0x00004000, + VK_FORMAT_FEATURE_TRANSFER_DST_BIT = 0x00008000, + VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT = 0x00020000, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT = 0x00040000, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT = 0x00080000, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT = 0x00100000, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT = 0x00200000, + VK_FORMAT_FEATURE_DISJOINT_BIT = 0x00400000, + VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT = 0x00800000, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG = 0x00002000, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT_EXT = 0x00010000, + VK_FORMAT_FEATURE_TRANSFER_SRC_BIT_KHR = VK_FORMAT_FEATURE_TRANSFER_SRC_BIT, + VK_FORMAT_FEATURE_TRANSFER_DST_BIT_KHR = VK_FORMAT_FEATURE_TRANSFER_DST_BIT, + VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT_KHR = VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT, + VK_FORMAT_FEATURE_DISJOINT_BIT_KHR = VK_FORMAT_FEATURE_DISJOINT_BIT, + VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT_KHR = VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT, + VK_FORMAT_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkFormatFeatureFlagBits; +typedef VkFlags VkFormatFeatureFlags; + +typedef enum VkImageUsageFlagBits { + VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001, + VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002, + VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004, + VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020, + VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040, + VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT = 0x00000080, + VK_IMAGE_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkImageUsageFlagBits; +typedef VkFlags VkImageUsageFlags; + +typedef enum VkImageCreateFlagBits { + VK_IMAGE_CREATE_SPARSE_BINDING_BIT = 0x00000001, + VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002, + VK_IMAGE_CREATE_SPARSE_ALIASED_BIT = 0x00000004, + VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT = 0x00000008, + VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT = 0x00000010, + VK_IMAGE_CREATE_ALIAS_BIT = 0x00000400, + VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT = 0x00000040, + VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT = 0x00000020, + VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT = 0x00000080, + VK_IMAGE_CREATE_EXTENDED_USAGE_BIT = 0x00000100, + VK_IMAGE_CREATE_PROTECTED_BIT = 0x00000800, + VK_IMAGE_CREATE_DISJOINT_BIT = 0x00000200, + VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT = 0x00001000, + VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR = VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT, + VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT_KHR = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, + VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT_KHR = VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT, + VK_IMAGE_CREATE_EXTENDED_USAGE_BIT_KHR = VK_IMAGE_CREATE_EXTENDED_USAGE_BIT, + VK_IMAGE_CREATE_DISJOINT_BIT_KHR = VK_IMAGE_CREATE_DISJOINT_BIT, + VK_IMAGE_CREATE_ALIAS_BIT_KHR = VK_IMAGE_CREATE_ALIAS_BIT, + VK_IMAGE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkImageCreateFlagBits; +typedef VkFlags VkImageCreateFlags; + +typedef enum VkSampleCountFlagBits { + VK_SAMPLE_COUNT_1_BIT = 0x00000001, + VK_SAMPLE_COUNT_2_BIT = 0x00000002, + VK_SAMPLE_COUNT_4_BIT = 0x00000004, + VK_SAMPLE_COUNT_8_BIT = 0x00000008, + VK_SAMPLE_COUNT_16_BIT = 0x00000010, + VK_SAMPLE_COUNT_32_BIT = 0x00000020, + VK_SAMPLE_COUNT_64_BIT = 0x00000040, + VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkSampleCountFlagBits; +typedef VkFlags VkSampleCountFlags; + +typedef enum VkQueueFlagBits { + VK_QUEUE_GRAPHICS_BIT = 0x00000001, + VK_QUEUE_COMPUTE_BIT = 0x00000002, + VK_QUEUE_TRANSFER_BIT = 0x00000004, + VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008, + VK_QUEUE_PROTECTED_BIT = 0x00000010, + VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkQueueFlagBits; +typedef VkFlags VkQueueFlags; + +typedef enum VkMemoryPropertyFlagBits { + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002, + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004, + VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008, + VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010, + VK_MEMORY_PROPERTY_PROTECTED_BIT = 0x00000020, + VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkMemoryPropertyFlagBits; +typedef VkFlags VkMemoryPropertyFlags; + +typedef enum VkMemoryHeapFlagBits { + VK_MEMORY_HEAP_DEVICE_LOCAL_BIT = 0x00000001, + VK_MEMORY_HEAP_MULTI_INSTANCE_BIT = 0x00000002, + VK_MEMORY_HEAP_MULTI_INSTANCE_BIT_KHR = VK_MEMORY_HEAP_MULTI_INSTANCE_BIT, + VK_MEMORY_HEAP_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkMemoryHeapFlagBits; +typedef VkFlags VkMemoryHeapFlags; +typedef VkFlags VkDeviceCreateFlags; + +typedef enum VkDeviceQueueCreateFlagBits { + VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT = 0x00000001, + VK_DEVICE_QUEUE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkDeviceQueueCreateFlagBits; +typedef VkFlags VkDeviceQueueCreateFlags; + +typedef enum VkPipelineStageFlagBits { + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT = 0x00000001, + VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT = 0x00000002, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT = 0x00000004, + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT = 0x00000008, + VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT = 0x00000010, + VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT = 0x00000020, + VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT = 0x00000040, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT = 0x00000080, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT = 0x00000100, + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT = 0x00000200, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT = 0x00000400, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT = 0x00000800, + VK_PIPELINE_STAGE_TRANSFER_BIT = 0x00001000, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT = 0x00002000, + VK_PIPELINE_STAGE_HOST_BIT = 0x00004000, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT = 0x00008000, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000, + VK_PIPELINE_STAGE_COMMAND_PROCESS_BIT_NVX = 0x00020000, + VK_PIPELINE_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkPipelineStageFlagBits; +typedef VkFlags VkPipelineStageFlags; +typedef VkFlags VkMemoryMapFlags; + +typedef enum VkImageAspectFlagBits { + VK_IMAGE_ASPECT_COLOR_BIT = 0x00000001, + VK_IMAGE_ASPECT_DEPTH_BIT = 0x00000002, + VK_IMAGE_ASPECT_STENCIL_BIT = 0x00000004, + VK_IMAGE_ASPECT_METADATA_BIT = 0x00000008, + VK_IMAGE_ASPECT_PLANE_0_BIT = 0x00000010, + VK_IMAGE_ASPECT_PLANE_1_BIT = 0x00000020, + VK_IMAGE_ASPECT_PLANE_2_BIT = 0x00000040, + VK_IMAGE_ASPECT_PLANE_0_BIT_KHR = VK_IMAGE_ASPECT_PLANE_0_BIT, + VK_IMAGE_ASPECT_PLANE_1_BIT_KHR = VK_IMAGE_ASPECT_PLANE_1_BIT, + VK_IMAGE_ASPECT_PLANE_2_BIT_KHR = VK_IMAGE_ASPECT_PLANE_2_BIT, + VK_IMAGE_ASPECT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkImageAspectFlagBits; +typedef VkFlags VkImageAspectFlags; + +typedef enum VkSparseImageFormatFlagBits { + VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT = 0x00000001, + VK_SPARSE_IMAGE_FORMAT_ALIGNED_MIP_SIZE_BIT = 0x00000002, + VK_SPARSE_IMAGE_FORMAT_NONSTANDARD_BLOCK_SIZE_BIT = 0x00000004, + VK_SPARSE_IMAGE_FORMAT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkSparseImageFormatFlagBits; +typedef VkFlags VkSparseImageFormatFlags; + +typedef enum VkSparseMemoryBindFlagBits { + VK_SPARSE_MEMORY_BIND_METADATA_BIT = 0x00000001, + VK_SPARSE_MEMORY_BIND_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkSparseMemoryBindFlagBits; +typedef VkFlags VkSparseMemoryBindFlags; + +typedef enum VkFenceCreateFlagBits { + VK_FENCE_CREATE_SIGNALED_BIT = 0x00000001, + VK_FENCE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkFenceCreateFlagBits; +typedef VkFlags VkFenceCreateFlags; +typedef VkFlags VkSemaphoreCreateFlags; +typedef VkFlags VkEventCreateFlags; +typedef VkFlags VkQueryPoolCreateFlags; + +typedef enum VkQueryPipelineStatisticFlagBits { + VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT = 0x00000001, + VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT = 0x00000002, + VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT = 0x00000004, + VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT = 0x00000008, + VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT = 0x00000010, + VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT = 0x00000020, + VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT = 0x00000040, + VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT = 0x00000080, + VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT = 0x00000100, + VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT = 0x00000200, + VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT = 0x00000400, + VK_QUERY_PIPELINE_STATISTIC_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkQueryPipelineStatisticFlagBits; +typedef VkFlags VkQueryPipelineStatisticFlags; + +typedef enum VkQueryResultFlagBits { + VK_QUERY_RESULT_64_BIT = 0x00000001, + VK_QUERY_RESULT_WAIT_BIT = 0x00000002, + VK_QUERY_RESULT_WITH_AVAILABILITY_BIT = 0x00000004, + VK_QUERY_RESULT_PARTIAL_BIT = 0x00000008, + VK_QUERY_RESULT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkQueryResultFlagBits; +typedef VkFlags VkQueryResultFlags; + +typedef enum VkBufferCreateFlagBits { + VK_BUFFER_CREATE_SPARSE_BINDING_BIT = 0x00000001, + VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002, + VK_BUFFER_CREATE_SPARSE_ALIASED_BIT = 0x00000004, + VK_BUFFER_CREATE_PROTECTED_BIT = 0x00000008, + VK_BUFFER_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkBufferCreateFlagBits; +typedef VkFlags VkBufferCreateFlags; + +typedef enum VkBufferUsageFlagBits { + VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001, + VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002, + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004, + VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080, + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100, + VK_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkBufferUsageFlagBits; +typedef VkFlags VkBufferUsageFlags; +typedef VkFlags VkBufferViewCreateFlags; +typedef VkFlags VkImageViewCreateFlags; +typedef VkFlags VkShaderModuleCreateFlags; +typedef VkFlags VkPipelineCacheCreateFlags; + +typedef enum VkPipelineCreateFlagBits { + VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT = 0x00000001, + VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT = 0x00000002, + VK_PIPELINE_CREATE_DERIVATIVE_BIT = 0x00000004, + VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT = 0x00000008, + VK_PIPELINE_CREATE_DISPATCH_BASE = 0x00000010, + VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT_KHR = VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT, + VK_PIPELINE_CREATE_DISPATCH_BASE_KHR = VK_PIPELINE_CREATE_DISPATCH_BASE, + VK_PIPELINE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkPipelineCreateFlagBits; +typedef VkFlags VkPipelineCreateFlags; +typedef VkFlags VkPipelineShaderStageCreateFlags; + +typedef enum VkShaderStageFlagBits { + VK_SHADER_STAGE_VERTEX_BIT = 0x00000001, + VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT = 0x00000002, + VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004, + VK_SHADER_STAGE_GEOMETRY_BIT = 0x00000008, + VK_SHADER_STAGE_FRAGMENT_BIT = 0x00000010, + VK_SHADER_STAGE_COMPUTE_BIT = 0x00000020, + VK_SHADER_STAGE_ALL_GRAPHICS = 0x0000001F, + VK_SHADER_STAGE_ALL = 0x7FFFFFFF, + VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkShaderStageFlagBits; +typedef VkFlags VkPipelineVertexInputStateCreateFlags; +typedef VkFlags VkPipelineInputAssemblyStateCreateFlags; +typedef VkFlags VkPipelineTessellationStateCreateFlags; +typedef VkFlags VkPipelineViewportStateCreateFlags; +typedef VkFlags VkPipelineRasterizationStateCreateFlags; + +typedef enum VkCullModeFlagBits { + VK_CULL_MODE_NONE = 0, + VK_CULL_MODE_FRONT_BIT = 0x00000001, + VK_CULL_MODE_BACK_BIT = 0x00000002, + VK_CULL_MODE_FRONT_AND_BACK = 0x00000003, + VK_CULL_MODE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkCullModeFlagBits; +typedef VkFlags VkCullModeFlags; +typedef VkFlags VkPipelineMultisampleStateCreateFlags; +typedef VkFlags VkPipelineDepthStencilStateCreateFlags; +typedef VkFlags VkPipelineColorBlendStateCreateFlags; + +typedef enum VkColorComponentFlagBits { + VK_COLOR_COMPONENT_R_BIT = 0x00000001, + VK_COLOR_COMPONENT_G_BIT = 0x00000002, + VK_COLOR_COMPONENT_B_BIT = 0x00000004, + VK_COLOR_COMPONENT_A_BIT = 0x00000008, + VK_COLOR_COMPONENT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkColorComponentFlagBits; +typedef VkFlags VkColorComponentFlags; +typedef VkFlags VkPipelineDynamicStateCreateFlags; +typedef VkFlags VkPipelineLayoutCreateFlags; +typedef VkFlags VkShaderStageFlags; +typedef VkFlags VkSamplerCreateFlags; + +typedef enum VkDescriptorSetLayoutCreateFlagBits { + VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR = 0x00000001, + VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT = 0x00000002, + VK_DESCRIPTOR_SET_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkDescriptorSetLayoutCreateFlagBits; +typedef VkFlags VkDescriptorSetLayoutCreateFlags; + +typedef enum VkDescriptorPoolCreateFlagBits { + VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT = 0x00000001, + VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT = 0x00000002, + VK_DESCRIPTOR_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkDescriptorPoolCreateFlagBits; +typedef VkFlags VkDescriptorPoolCreateFlags; +typedef VkFlags VkDescriptorPoolResetFlags; +typedef VkFlags VkFramebufferCreateFlags; +typedef VkFlags VkRenderPassCreateFlags; + +typedef enum VkAttachmentDescriptionFlagBits { + VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT = 0x00000001, + VK_ATTACHMENT_DESCRIPTION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkAttachmentDescriptionFlagBits; +typedef VkFlags VkAttachmentDescriptionFlags; + +typedef enum VkSubpassDescriptionFlagBits { + VK_SUBPASS_DESCRIPTION_PER_VIEW_ATTRIBUTES_BIT_NVX = 0x00000001, + VK_SUBPASS_DESCRIPTION_PER_VIEW_POSITION_X_ONLY_BIT_NVX = 0x00000002, + VK_SUBPASS_DESCRIPTION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkSubpassDescriptionFlagBits; +typedef VkFlags VkSubpassDescriptionFlags; + +typedef enum VkAccessFlagBits { + VK_ACCESS_INDIRECT_COMMAND_READ_BIT = 0x00000001, + VK_ACCESS_INDEX_READ_BIT = 0x00000002, + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT = 0x00000004, + VK_ACCESS_UNIFORM_READ_BIT = 0x00000008, + VK_ACCESS_INPUT_ATTACHMENT_READ_BIT = 0x00000010, + VK_ACCESS_SHADER_READ_BIT = 0x00000020, + VK_ACCESS_SHADER_WRITE_BIT = 0x00000040, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT = 0x00000080, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT = 0x00000100, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT = 0x00000200, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT = 0x00000400, + VK_ACCESS_TRANSFER_READ_BIT = 0x00000800, + VK_ACCESS_TRANSFER_WRITE_BIT = 0x00001000, + VK_ACCESS_HOST_READ_BIT = 0x00002000, + VK_ACCESS_HOST_WRITE_BIT = 0x00004000, + VK_ACCESS_MEMORY_READ_BIT = 0x00008000, + VK_ACCESS_MEMORY_WRITE_BIT = 0x00010000, + VK_ACCESS_COMMAND_PROCESS_READ_BIT_NVX = 0x00020000, + VK_ACCESS_COMMAND_PROCESS_WRITE_BIT_NVX = 0x00040000, + VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT = 0x00080000, + VK_ACCESS_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkAccessFlagBits; +typedef VkFlags VkAccessFlags; + +typedef enum VkDependencyFlagBits { + VK_DEPENDENCY_BY_REGION_BIT = 0x00000001, + VK_DEPENDENCY_DEVICE_GROUP_BIT = 0x00000004, + VK_DEPENDENCY_VIEW_LOCAL_BIT = 0x00000002, + VK_DEPENDENCY_VIEW_LOCAL_BIT_KHR = VK_DEPENDENCY_VIEW_LOCAL_BIT, + VK_DEPENDENCY_DEVICE_GROUP_BIT_KHR = VK_DEPENDENCY_DEVICE_GROUP_BIT, + VK_DEPENDENCY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkDependencyFlagBits; +typedef VkFlags VkDependencyFlags; + +typedef enum VkCommandPoolCreateFlagBits { + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT = 0x00000001, + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT = 0x00000002, + VK_COMMAND_POOL_CREATE_PROTECTED_BIT = 0x00000004, + VK_COMMAND_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkCommandPoolCreateFlagBits; +typedef VkFlags VkCommandPoolCreateFlags; + +typedef enum VkCommandPoolResetFlagBits { + VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT = 0x00000001, + VK_COMMAND_POOL_RESET_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkCommandPoolResetFlagBits; +typedef VkFlags VkCommandPoolResetFlags; + +typedef enum VkCommandBufferUsageFlagBits { + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT = 0x00000001, + VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT = 0x00000002, + VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT = 0x00000004, + VK_COMMAND_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkCommandBufferUsageFlagBits; +typedef VkFlags VkCommandBufferUsageFlags; + +typedef enum VkQueryControlFlagBits { + VK_QUERY_CONTROL_PRECISE_BIT = 0x00000001, + VK_QUERY_CONTROL_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkQueryControlFlagBits; +typedef VkFlags VkQueryControlFlags; + +typedef enum VkCommandBufferResetFlagBits { + VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT = 0x00000001, + VK_COMMAND_BUFFER_RESET_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkCommandBufferResetFlagBits; +typedef VkFlags VkCommandBufferResetFlags; + +typedef enum VkStencilFaceFlagBits { + VK_STENCIL_FACE_FRONT_BIT = 0x00000001, + VK_STENCIL_FACE_BACK_BIT = 0x00000002, + VK_STENCIL_FRONT_AND_BACK = 0x00000003, + VK_STENCIL_FACE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkStencilFaceFlagBits; +typedef VkFlags VkStencilFaceFlags; + +typedef struct VkApplicationInfo { + VkStructureType sType; + const void* pNext; + const char* pApplicationName; + uint32_t applicationVersion; + const char* pEngineName; + uint32_t engineVersion; + uint32_t apiVersion; +} VkApplicationInfo; + +typedef struct VkInstanceCreateInfo { + VkStructureType sType; + const void* pNext; + VkInstanceCreateFlags flags; + const VkApplicationInfo* pApplicationInfo; + uint32_t enabledLayerCount; + const char* const* ppEnabledLayerNames; + uint32_t enabledExtensionCount; + const char* const* ppEnabledExtensionNames; +} VkInstanceCreateInfo; + +typedef void* (VKAPI_PTR *PFN_vkAllocationFunction)( + void* pUserData, + size_t size, + size_t alignment, + VkSystemAllocationScope allocationScope); + +typedef void* (VKAPI_PTR *PFN_vkReallocationFunction)( + void* pUserData, + void* pOriginal, + size_t size, + size_t alignment, + VkSystemAllocationScope allocationScope); + +typedef void (VKAPI_PTR *PFN_vkFreeFunction)( + void* pUserData, + void* pMemory); + +typedef void (VKAPI_PTR *PFN_vkInternalAllocationNotification)( + void* pUserData, + size_t size, + VkInternalAllocationType allocationType, + VkSystemAllocationScope allocationScope); + +typedef void (VKAPI_PTR *PFN_vkInternalFreeNotification)( + void* pUserData, + size_t size, + VkInternalAllocationType allocationType, + VkSystemAllocationScope allocationScope); + +typedef struct VkAllocationCallbacks { + void* pUserData; + PFN_vkAllocationFunction pfnAllocation; + PFN_vkReallocationFunction pfnReallocation; + PFN_vkFreeFunction pfnFree; + PFN_vkInternalAllocationNotification pfnInternalAllocation; + PFN_vkInternalFreeNotification pfnInternalFree; +} VkAllocationCallbacks; + +typedef struct VkPhysicalDeviceFeatures { + VkBool32 robustBufferAccess; + VkBool32 fullDrawIndexUint32; + VkBool32 imageCubeArray; + VkBool32 independentBlend; + VkBool32 geometryShader; + VkBool32 tessellationShader; + VkBool32 sampleRateShading; + VkBool32 dualSrcBlend; + VkBool32 logicOp; + VkBool32 multiDrawIndirect; + VkBool32 drawIndirectFirstInstance; + VkBool32 depthClamp; + VkBool32 depthBiasClamp; + VkBool32 fillModeNonSolid; + VkBool32 depthBounds; + VkBool32 wideLines; + VkBool32 largePoints; + VkBool32 alphaToOne; + VkBool32 multiViewport; + VkBool32 samplerAnisotropy; + VkBool32 textureCompressionETC2; + VkBool32 textureCompressionASTC_LDR; + VkBool32 textureCompressionBC; + VkBool32 occlusionQueryPrecise; + VkBool32 pipelineStatisticsQuery; + VkBool32 vertexPipelineStoresAndAtomics; + VkBool32 fragmentStoresAndAtomics; + VkBool32 shaderTessellationAndGeometryPointSize; + VkBool32 shaderImageGatherExtended; + VkBool32 shaderStorageImageExtendedFormats; + VkBool32 shaderStorageImageMultisample; + VkBool32 shaderStorageImageReadWithoutFormat; + VkBool32 shaderStorageImageWriteWithoutFormat; + VkBool32 shaderUniformBufferArrayDynamicIndexing; + VkBool32 shaderSampledImageArrayDynamicIndexing; + VkBool32 shaderStorageBufferArrayDynamicIndexing; + VkBool32 shaderStorageImageArrayDynamicIndexing; + VkBool32 shaderClipDistance; + VkBool32 shaderCullDistance; + VkBool32 shaderFloat64; + VkBool32 shaderInt64; + VkBool32 shaderInt16; + VkBool32 shaderResourceResidency; + VkBool32 shaderResourceMinLod; + VkBool32 sparseBinding; + VkBool32 sparseResidencyBuffer; + VkBool32 sparseResidencyImage2D; + VkBool32 sparseResidencyImage3D; + VkBool32 sparseResidency2Samples; + VkBool32 sparseResidency4Samples; + VkBool32 sparseResidency8Samples; + VkBool32 sparseResidency16Samples; + VkBool32 sparseResidencyAliased; + VkBool32 variableMultisampleRate; + VkBool32 inheritedQueries; +} VkPhysicalDeviceFeatures; + +typedef struct VkFormatProperties { + VkFormatFeatureFlags linearTilingFeatures; + VkFormatFeatureFlags optimalTilingFeatures; + VkFormatFeatureFlags bufferFeatures; +} VkFormatProperties; + +typedef struct VkExtent3D { + uint32_t width; + uint32_t height; + uint32_t depth; +} VkExtent3D; + +typedef struct VkImageFormatProperties { + VkExtent3D maxExtent; + uint32_t maxMipLevels; + uint32_t maxArrayLayers; + VkSampleCountFlags sampleCounts; + VkDeviceSize maxResourceSize; +} VkImageFormatProperties; + +typedef struct VkPhysicalDeviceLimits { + uint32_t maxImageDimension1D; + uint32_t maxImageDimension2D; + uint32_t maxImageDimension3D; + uint32_t maxImageDimensionCube; + uint32_t maxImageArrayLayers; + uint32_t maxTexelBufferElements; + uint32_t maxUniformBufferRange; + uint32_t maxStorageBufferRange; + uint32_t maxPushConstantsSize; + uint32_t maxMemoryAllocationCount; + uint32_t maxSamplerAllocationCount; + VkDeviceSize bufferImageGranularity; + VkDeviceSize sparseAddressSpaceSize; + uint32_t maxBoundDescriptorSets; + uint32_t maxPerStageDescriptorSamplers; + uint32_t maxPerStageDescriptorUniformBuffers; + uint32_t maxPerStageDescriptorStorageBuffers; + uint32_t maxPerStageDescriptorSampledImages; + uint32_t maxPerStageDescriptorStorageImages; + uint32_t maxPerStageDescriptorInputAttachments; + uint32_t maxPerStageResources; + uint32_t maxDescriptorSetSamplers; + uint32_t maxDescriptorSetUniformBuffers; + uint32_t maxDescriptorSetUniformBuffersDynamic; + uint32_t maxDescriptorSetStorageBuffers; + uint32_t maxDescriptorSetStorageBuffersDynamic; + uint32_t maxDescriptorSetSampledImages; + uint32_t maxDescriptorSetStorageImages; + uint32_t maxDescriptorSetInputAttachments; + uint32_t maxVertexInputAttributes; + uint32_t maxVertexInputBindings; + uint32_t maxVertexInputAttributeOffset; + uint32_t maxVertexInputBindingStride; + uint32_t maxVertexOutputComponents; + uint32_t maxTessellationGenerationLevel; + uint32_t maxTessellationPatchSize; + uint32_t maxTessellationControlPerVertexInputComponents; + uint32_t maxTessellationControlPerVertexOutputComponents; + uint32_t maxTessellationControlPerPatchOutputComponents; + uint32_t maxTessellationControlTotalOutputComponents; + uint32_t maxTessellationEvaluationInputComponents; + uint32_t maxTessellationEvaluationOutputComponents; + uint32_t maxGeometryShaderInvocations; + uint32_t maxGeometryInputComponents; + uint32_t maxGeometryOutputComponents; + uint32_t maxGeometryOutputVertices; + uint32_t maxGeometryTotalOutputComponents; + uint32_t maxFragmentInputComponents; + uint32_t maxFragmentOutputAttachments; + uint32_t maxFragmentDualSrcAttachments; + uint32_t maxFragmentCombinedOutputResources; + uint32_t maxComputeSharedMemorySize; + uint32_t maxComputeWorkGroupCount[3]; + uint32_t maxComputeWorkGroupInvocations; + uint32_t maxComputeWorkGroupSize[3]; + uint32_t subPixelPrecisionBits; + uint32_t subTexelPrecisionBits; + uint32_t mipmapPrecisionBits; + uint32_t maxDrawIndexedIndexValue; + uint32_t maxDrawIndirectCount; + float maxSamplerLodBias; + float maxSamplerAnisotropy; + uint32_t maxViewports; + uint32_t maxViewportDimensions[2]; + float viewportBoundsRange[2]; + uint32_t viewportSubPixelBits; + size_t minMemoryMapAlignment; + VkDeviceSize minTexelBufferOffsetAlignment; + VkDeviceSize minUniformBufferOffsetAlignment; + VkDeviceSize minStorageBufferOffsetAlignment; + int32_t minTexelOffset; + uint32_t maxTexelOffset; + int32_t minTexelGatherOffset; + uint32_t maxTexelGatherOffset; + float minInterpolationOffset; + float maxInterpolationOffset; + uint32_t subPixelInterpolationOffsetBits; + uint32_t maxFramebufferWidth; + uint32_t maxFramebufferHeight; + uint32_t maxFramebufferLayers; + VkSampleCountFlags framebufferColorSampleCounts; + VkSampleCountFlags framebufferDepthSampleCounts; + VkSampleCountFlags framebufferStencilSampleCounts; + VkSampleCountFlags framebufferNoAttachmentsSampleCounts; + uint32_t maxColorAttachments; + VkSampleCountFlags sampledImageColorSampleCounts; + VkSampleCountFlags sampledImageIntegerSampleCounts; + VkSampleCountFlags sampledImageDepthSampleCounts; + VkSampleCountFlags sampledImageStencilSampleCounts; + VkSampleCountFlags storageImageSampleCounts; + uint32_t maxSampleMaskWords; + VkBool32 timestampComputeAndGraphics; + float timestampPeriod; + uint32_t maxClipDistances; + uint32_t maxCullDistances; + uint32_t maxCombinedClipAndCullDistances; + uint32_t discreteQueuePriorities; + float pointSizeRange[2]; + float lineWidthRange[2]; + float pointSizeGranularity; + float lineWidthGranularity; + VkBool32 strictLines; + VkBool32 standardSampleLocations; + VkDeviceSize optimalBufferCopyOffsetAlignment; + VkDeviceSize optimalBufferCopyRowPitchAlignment; + VkDeviceSize nonCoherentAtomSize; +} VkPhysicalDeviceLimits; + +typedef struct VkPhysicalDeviceSparseProperties { + VkBool32 residencyStandard2DBlockShape; + VkBool32 residencyStandard2DMultisampleBlockShape; + VkBool32 residencyStandard3DBlockShape; + VkBool32 residencyAlignedMipSize; + VkBool32 residencyNonResidentStrict; +} VkPhysicalDeviceSparseProperties; + +typedef struct VkPhysicalDeviceProperties { + uint32_t apiVersion; + uint32_t driverVersion; + uint32_t vendorID; + uint32_t deviceID; + VkPhysicalDeviceType deviceType; + char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; + uint8_t pipelineCacheUUID[VK_UUID_SIZE]; + VkPhysicalDeviceLimits limits; + VkPhysicalDeviceSparseProperties sparseProperties; +} VkPhysicalDeviceProperties; + +typedef struct VkQueueFamilyProperties { + VkQueueFlags queueFlags; + uint32_t queueCount; + uint32_t timestampValidBits; + VkExtent3D minImageTransferGranularity; +} VkQueueFamilyProperties; + +typedef struct VkMemoryType { + VkMemoryPropertyFlags propertyFlags; + uint32_t heapIndex; +} VkMemoryType; + +typedef struct VkMemoryHeap { + VkDeviceSize size; + VkMemoryHeapFlags flags; +} VkMemoryHeap; + +typedef struct VkPhysicalDeviceMemoryProperties { + uint32_t memoryTypeCount; + VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES]; + uint32_t memoryHeapCount; + VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS]; +} VkPhysicalDeviceMemoryProperties; + +typedef void (VKAPI_PTR *PFN_vkVoidFunction)(void); +typedef struct VkDeviceQueueCreateInfo { + VkStructureType sType; + const void* pNext; + VkDeviceQueueCreateFlags flags; + uint32_t queueFamilyIndex; + uint32_t queueCount; + const float* pQueuePriorities; +} VkDeviceQueueCreateInfo; + +typedef struct VkDeviceCreateInfo { + VkStructureType sType; + const void* pNext; + VkDeviceCreateFlags flags; + uint32_t queueCreateInfoCount; + const VkDeviceQueueCreateInfo* pQueueCreateInfos; + uint32_t enabledLayerCount; + const char* const* ppEnabledLayerNames; + uint32_t enabledExtensionCount; + const char* const* ppEnabledExtensionNames; + const VkPhysicalDeviceFeatures* pEnabledFeatures; +} VkDeviceCreateInfo; + +typedef struct VkExtensionProperties { + char extensionName[VK_MAX_EXTENSION_NAME_SIZE]; + uint32_t specVersion; +} VkExtensionProperties; + +typedef struct VkLayerProperties { + char layerName[VK_MAX_EXTENSION_NAME_SIZE]; + uint32_t specVersion; + uint32_t implementationVersion; + char description[VK_MAX_DESCRIPTION_SIZE]; +} VkLayerProperties; + +typedef struct VkSubmitInfo { + VkStructureType sType; + const void* pNext; + uint32_t waitSemaphoreCount; + const VkSemaphore* pWaitSemaphores; + const VkPipelineStageFlags* pWaitDstStageMask; + uint32_t commandBufferCount; + const VkCommandBuffer* pCommandBuffers; + uint32_t signalSemaphoreCount; + const VkSemaphore* pSignalSemaphores; +} VkSubmitInfo; + +typedef struct VkMemoryAllocateInfo { + VkStructureType sType; + const void* pNext; + VkDeviceSize allocationSize; + uint32_t memoryTypeIndex; +} VkMemoryAllocateInfo; + +typedef struct VkMappedMemoryRange { + VkStructureType sType; + const void* pNext; + VkDeviceMemory memory; + VkDeviceSize offset; + VkDeviceSize size; +} VkMappedMemoryRange; + +typedef struct VkMemoryRequirements { + VkDeviceSize size; + VkDeviceSize alignment; + uint32_t memoryTypeBits; +} VkMemoryRequirements; + +typedef struct VkSparseImageFormatProperties { + VkImageAspectFlags aspectMask; + VkExtent3D imageGranularity; + VkSparseImageFormatFlags flags; +} VkSparseImageFormatProperties; + +typedef struct VkSparseImageMemoryRequirements { + VkSparseImageFormatProperties formatProperties; + uint32_t imageMipTailFirstLod; + VkDeviceSize imageMipTailSize; + VkDeviceSize imageMipTailOffset; + VkDeviceSize imageMipTailStride; +} VkSparseImageMemoryRequirements; + +typedef struct VkSparseMemoryBind { + VkDeviceSize resourceOffset; + VkDeviceSize size; + VkDeviceMemory memory; + VkDeviceSize memoryOffset; + VkSparseMemoryBindFlags flags; +} VkSparseMemoryBind; + +typedef struct VkSparseBufferMemoryBindInfo { + VkBuffer buffer; + uint32_t bindCount; + const VkSparseMemoryBind* pBinds; +} VkSparseBufferMemoryBindInfo; + +typedef struct VkSparseImageOpaqueMemoryBindInfo { + VkImage image; + uint32_t bindCount; + const VkSparseMemoryBind* pBinds; +} VkSparseImageOpaqueMemoryBindInfo; + +typedef struct VkImageSubresource { + VkImageAspectFlags aspectMask; + uint32_t mipLevel; + uint32_t arrayLayer; +} VkImageSubresource; + +typedef struct VkOffset3D { + int32_t x; + int32_t y; + int32_t z; +} VkOffset3D; + +typedef struct VkSparseImageMemoryBind { + VkImageSubresource subresource; + VkOffset3D offset; + VkExtent3D extent; + VkDeviceMemory memory; + VkDeviceSize memoryOffset; + VkSparseMemoryBindFlags flags; +} VkSparseImageMemoryBind; + +typedef struct VkSparseImageMemoryBindInfo { + VkImage image; + uint32_t bindCount; + const VkSparseImageMemoryBind* pBinds; +} VkSparseImageMemoryBindInfo; + +typedef struct VkBindSparseInfo { + VkStructureType sType; + const void* pNext; + uint32_t waitSemaphoreCount; + const VkSemaphore* pWaitSemaphores; + uint32_t bufferBindCount; + const VkSparseBufferMemoryBindInfo* pBufferBinds; + uint32_t imageOpaqueBindCount; + const VkSparseImageOpaqueMemoryBindInfo* pImageOpaqueBinds; + uint32_t imageBindCount; + const VkSparseImageMemoryBindInfo* pImageBinds; + uint32_t signalSemaphoreCount; + const VkSemaphore* pSignalSemaphores; +} VkBindSparseInfo; + +typedef struct VkFenceCreateInfo { + VkStructureType sType; + const void* pNext; + VkFenceCreateFlags flags; +} VkFenceCreateInfo; + +typedef struct VkSemaphoreCreateInfo { + VkStructureType sType; + const void* pNext; + VkSemaphoreCreateFlags flags; +} VkSemaphoreCreateInfo; + +typedef struct VkEventCreateInfo { + VkStructureType sType; + const void* pNext; + VkEventCreateFlags flags; +} VkEventCreateInfo; + +typedef struct VkQueryPoolCreateInfo { + VkStructureType sType; + const void* pNext; + VkQueryPoolCreateFlags flags; + VkQueryType queryType; + uint32_t queryCount; + VkQueryPipelineStatisticFlags pipelineStatistics; +} VkQueryPoolCreateInfo; + +typedef struct VkBufferCreateInfo { + VkStructureType sType; + const void* pNext; + VkBufferCreateFlags flags; + VkDeviceSize size; + VkBufferUsageFlags usage; + VkSharingMode sharingMode; + uint32_t queueFamilyIndexCount; + const uint32_t* pQueueFamilyIndices; +} VkBufferCreateInfo; + +typedef struct VkBufferViewCreateInfo { + VkStructureType sType; + const void* pNext; + VkBufferViewCreateFlags flags; + VkBuffer buffer; + VkFormat format; + VkDeviceSize offset; + VkDeviceSize range; +} VkBufferViewCreateInfo; + +typedef struct VkImageCreateInfo { + VkStructureType sType; + const void* pNext; + VkImageCreateFlags flags; + VkImageType imageType; + VkFormat format; + VkExtent3D extent; + uint32_t mipLevels; + uint32_t arrayLayers; + VkSampleCountFlagBits samples; + VkImageTiling tiling; + VkImageUsageFlags usage; + VkSharingMode sharingMode; + uint32_t queueFamilyIndexCount; + const uint32_t* pQueueFamilyIndices; + VkImageLayout initialLayout; +} VkImageCreateInfo; + +typedef struct VkSubresourceLayout { + VkDeviceSize offset; + VkDeviceSize size; + VkDeviceSize rowPitch; + VkDeviceSize arrayPitch; + VkDeviceSize depthPitch; +} VkSubresourceLayout; + +typedef struct VkComponentMapping { + VkComponentSwizzle r; + VkComponentSwizzle g; + VkComponentSwizzle b; + VkComponentSwizzle a; +} VkComponentMapping; + +typedef struct VkImageSubresourceRange { + VkImageAspectFlags aspectMask; + uint32_t baseMipLevel; + uint32_t levelCount; + uint32_t baseArrayLayer; + uint32_t layerCount; +} VkImageSubresourceRange; + +typedef struct VkImageViewCreateInfo { + VkStructureType sType; + const void* pNext; + VkImageViewCreateFlags flags; + VkImage image; + VkImageViewType viewType; + VkFormat format; + VkComponentMapping components; + VkImageSubresourceRange subresourceRange; +} VkImageViewCreateInfo; + +typedef struct VkShaderModuleCreateInfo { + VkStructureType sType; + const void* pNext; + VkShaderModuleCreateFlags flags; + size_t codeSize; + const uint32_t* pCode; +} VkShaderModuleCreateInfo; + +typedef struct VkPipelineCacheCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineCacheCreateFlags flags; + size_t initialDataSize; + const void* pInitialData; +} VkPipelineCacheCreateInfo; + +typedef struct VkSpecializationMapEntry { + uint32_t constantID; + uint32_t offset; + size_t size; +} VkSpecializationMapEntry; + +typedef struct VkSpecializationInfo { + uint32_t mapEntryCount; + const VkSpecializationMapEntry* pMapEntries; + size_t dataSize; + const void* pData; +} VkSpecializationInfo; + +typedef struct VkPipelineShaderStageCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineShaderStageCreateFlags flags; + VkShaderStageFlagBits stage; + VkShaderModule module; + const char* pName; + const VkSpecializationInfo* pSpecializationInfo; +} VkPipelineShaderStageCreateInfo; + +typedef struct VkVertexInputBindingDescription { + uint32_t binding; + uint32_t stride; + VkVertexInputRate inputRate; +} VkVertexInputBindingDescription; + +typedef struct VkVertexInputAttributeDescription { + uint32_t location; + uint32_t binding; + VkFormat format; + uint32_t offset; +} VkVertexInputAttributeDescription; + +typedef struct VkPipelineVertexInputStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineVertexInputStateCreateFlags flags; + uint32_t vertexBindingDescriptionCount; + const VkVertexInputBindingDescription* pVertexBindingDescriptions; + uint32_t vertexAttributeDescriptionCount; + const VkVertexInputAttributeDescription* pVertexAttributeDescriptions; +} VkPipelineVertexInputStateCreateInfo; + +typedef struct VkPipelineInputAssemblyStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineInputAssemblyStateCreateFlags flags; + VkPrimitiveTopology topology; + VkBool32 primitiveRestartEnable; +} VkPipelineInputAssemblyStateCreateInfo; + +typedef struct VkPipelineTessellationStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineTessellationStateCreateFlags flags; + uint32_t patchControlPoints; +} VkPipelineTessellationStateCreateInfo; + +typedef struct VkViewport { + float x; + float y; + float width; + float height; + float minDepth; + float maxDepth; +} VkViewport; + +typedef struct VkOffset2D { + int32_t x; + int32_t y; +} VkOffset2D; + +typedef struct VkExtent2D { + uint32_t width; + uint32_t height; +} VkExtent2D; + +typedef struct VkRect2D { + VkOffset2D offset; + VkExtent2D extent; +} VkRect2D; + +typedef struct VkPipelineViewportStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineViewportStateCreateFlags flags; + uint32_t viewportCount; + const VkViewport* pViewports; + uint32_t scissorCount; + const VkRect2D* pScissors; +} VkPipelineViewportStateCreateInfo; + +typedef struct VkPipelineRasterizationStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineRasterizationStateCreateFlags flags; + VkBool32 depthClampEnable; + VkBool32 rasterizerDiscardEnable; + VkPolygonMode polygonMode; + VkCullModeFlags cullMode; + VkFrontFace frontFace; + VkBool32 depthBiasEnable; + float depthBiasConstantFactor; + float depthBiasClamp; + float depthBiasSlopeFactor; + float lineWidth; +} VkPipelineRasterizationStateCreateInfo; + +typedef struct VkPipelineMultisampleStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineMultisampleStateCreateFlags flags; + VkSampleCountFlagBits rasterizationSamples; + VkBool32 sampleShadingEnable; + float minSampleShading; + const VkSampleMask* pSampleMask; + VkBool32 alphaToCoverageEnable; + VkBool32 alphaToOneEnable; +} VkPipelineMultisampleStateCreateInfo; + +typedef struct VkStencilOpState { + VkStencilOp failOp; + VkStencilOp passOp; + VkStencilOp depthFailOp; + VkCompareOp compareOp; + uint32_t compareMask; + uint32_t writeMask; + uint32_t reference; +} VkStencilOpState; + +typedef struct VkPipelineDepthStencilStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineDepthStencilStateCreateFlags flags; + VkBool32 depthTestEnable; + VkBool32 depthWriteEnable; + VkCompareOp depthCompareOp; + VkBool32 depthBoundsTestEnable; + VkBool32 stencilTestEnable; + VkStencilOpState front; + VkStencilOpState back; + float minDepthBounds; + float maxDepthBounds; +} VkPipelineDepthStencilStateCreateInfo; + +typedef struct VkPipelineColorBlendAttachmentState { + VkBool32 blendEnable; + VkBlendFactor srcColorBlendFactor; + VkBlendFactor dstColorBlendFactor; + VkBlendOp colorBlendOp; + VkBlendFactor srcAlphaBlendFactor; + VkBlendFactor dstAlphaBlendFactor; + VkBlendOp alphaBlendOp; + VkColorComponentFlags colorWriteMask; +} VkPipelineColorBlendAttachmentState; + +typedef struct VkPipelineColorBlendStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineColorBlendStateCreateFlags flags; + VkBool32 logicOpEnable; + VkLogicOp logicOp; + uint32_t attachmentCount; + const VkPipelineColorBlendAttachmentState* pAttachments; + float blendConstants[4]; +} VkPipelineColorBlendStateCreateInfo; + +typedef struct VkPipelineDynamicStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineDynamicStateCreateFlags flags; + uint32_t dynamicStateCount; + const VkDynamicState* pDynamicStates; +} VkPipelineDynamicStateCreateInfo; + +typedef struct VkGraphicsPipelineCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineCreateFlags flags; + uint32_t stageCount; + const VkPipelineShaderStageCreateInfo* pStages; + const VkPipelineVertexInputStateCreateInfo* pVertexInputState; + const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState; + const VkPipelineTessellationStateCreateInfo* pTessellationState; + const VkPipelineViewportStateCreateInfo* pViewportState; + const VkPipelineRasterizationStateCreateInfo* pRasterizationState; + const VkPipelineMultisampleStateCreateInfo* pMultisampleState; + const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState; + const VkPipelineColorBlendStateCreateInfo* pColorBlendState; + const VkPipelineDynamicStateCreateInfo* pDynamicState; + VkPipelineLayout layout; + VkRenderPass renderPass; + uint32_t subpass; + VkPipeline basePipelineHandle; + int32_t basePipelineIndex; +} VkGraphicsPipelineCreateInfo; + +typedef struct VkComputePipelineCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineCreateFlags flags; + VkPipelineShaderStageCreateInfo stage; + VkPipelineLayout layout; + VkPipeline basePipelineHandle; + int32_t basePipelineIndex; +} VkComputePipelineCreateInfo; + +typedef struct VkPushConstantRange { + VkShaderStageFlags stageFlags; + uint32_t offset; + uint32_t size; +} VkPushConstantRange; + +typedef struct VkPipelineLayoutCreateInfo { + VkStructureType sType; + const void* pNext; + VkPipelineLayoutCreateFlags flags; + uint32_t setLayoutCount; + const VkDescriptorSetLayout* pSetLayouts; + uint32_t pushConstantRangeCount; + const VkPushConstantRange* pPushConstantRanges; +} VkPipelineLayoutCreateInfo; + +typedef struct VkSamplerCreateInfo { + VkStructureType sType; + const void* pNext; + VkSamplerCreateFlags flags; + VkFilter magFilter; + VkFilter minFilter; + VkSamplerMipmapMode mipmapMode; + VkSamplerAddressMode addressModeU; + VkSamplerAddressMode addressModeV; + VkSamplerAddressMode addressModeW; + float mipLodBias; + VkBool32 anisotropyEnable; + float maxAnisotropy; + VkBool32 compareEnable; + VkCompareOp compareOp; + float minLod; + float maxLod; + VkBorderColor borderColor; + VkBool32 unnormalizedCoordinates; +} VkSamplerCreateInfo; + +typedef struct VkDescriptorSetLayoutBinding { + uint32_t binding; + VkDescriptorType descriptorType; + uint32_t descriptorCount; + VkShaderStageFlags stageFlags; + const VkSampler* pImmutableSamplers; +} VkDescriptorSetLayoutBinding; + +typedef struct VkDescriptorSetLayoutCreateInfo { + VkStructureType sType; + const void* pNext; + VkDescriptorSetLayoutCreateFlags flags; + uint32_t bindingCount; + const VkDescriptorSetLayoutBinding* pBindings; +} VkDescriptorSetLayoutCreateInfo; + +typedef struct VkDescriptorPoolSize { + VkDescriptorType type; + uint32_t descriptorCount; +} VkDescriptorPoolSize; + +typedef struct VkDescriptorPoolCreateInfo { + VkStructureType sType; + const void* pNext; + VkDescriptorPoolCreateFlags flags; + uint32_t maxSets; + uint32_t poolSizeCount; + const VkDescriptorPoolSize* pPoolSizes; +} VkDescriptorPoolCreateInfo; + +typedef struct VkDescriptorSetAllocateInfo { + VkStructureType sType; + const void* pNext; + VkDescriptorPool descriptorPool; + uint32_t descriptorSetCount; + const VkDescriptorSetLayout* pSetLayouts; +} VkDescriptorSetAllocateInfo; + +typedef struct VkDescriptorImageInfo { + VkSampler sampler; + VkImageView imageView; + VkImageLayout imageLayout; +} VkDescriptorImageInfo; + +typedef struct VkDescriptorBufferInfo { + VkBuffer buffer; + VkDeviceSize offset; + VkDeviceSize range; +} VkDescriptorBufferInfo; + +typedef struct VkWriteDescriptorSet { + VkStructureType sType; + const void* pNext; + VkDescriptorSet dstSet; + uint32_t dstBinding; + uint32_t dstArrayElement; + uint32_t descriptorCount; + VkDescriptorType descriptorType; + const VkDescriptorImageInfo* pImageInfo; + const VkDescriptorBufferInfo* pBufferInfo; + const VkBufferView* pTexelBufferView; +} VkWriteDescriptorSet; + +typedef struct VkCopyDescriptorSet { + VkStructureType sType; + const void* pNext; + VkDescriptorSet srcSet; + uint32_t srcBinding; + uint32_t srcArrayElement; + VkDescriptorSet dstSet; + uint32_t dstBinding; + uint32_t dstArrayElement; + uint32_t descriptorCount; +} VkCopyDescriptorSet; + +typedef struct VkFramebufferCreateInfo { + VkStructureType sType; + const void* pNext; + VkFramebufferCreateFlags flags; + VkRenderPass renderPass; + uint32_t attachmentCount; + const VkImageView* pAttachments; + uint32_t width; + uint32_t height; + uint32_t layers; +} VkFramebufferCreateInfo; + +typedef struct VkAttachmentDescription { + VkAttachmentDescriptionFlags flags; + VkFormat format; + VkSampleCountFlagBits samples; + VkAttachmentLoadOp loadOp; + VkAttachmentStoreOp storeOp; + VkAttachmentLoadOp stencilLoadOp; + VkAttachmentStoreOp stencilStoreOp; + VkImageLayout initialLayout; + VkImageLayout finalLayout; +} VkAttachmentDescription; + +typedef struct VkAttachmentReference { + uint32_t attachment; + VkImageLayout layout; +} VkAttachmentReference; + +typedef struct VkSubpassDescription { + VkSubpassDescriptionFlags flags; + VkPipelineBindPoint pipelineBindPoint; + uint32_t inputAttachmentCount; + const VkAttachmentReference* pInputAttachments; + uint32_t colorAttachmentCount; + const VkAttachmentReference* pColorAttachments; + const VkAttachmentReference* pResolveAttachments; + const VkAttachmentReference* pDepthStencilAttachment; + uint32_t preserveAttachmentCount; + const uint32_t* pPreserveAttachments; +} VkSubpassDescription; + +typedef struct VkSubpassDependency { + uint32_t srcSubpass; + uint32_t dstSubpass; + VkPipelineStageFlags srcStageMask; + VkPipelineStageFlags dstStageMask; + VkAccessFlags srcAccessMask; + VkAccessFlags dstAccessMask; + VkDependencyFlags dependencyFlags; +} VkSubpassDependency; + +typedef struct VkRenderPassCreateInfo { + VkStructureType sType; + const void* pNext; + VkRenderPassCreateFlags flags; + uint32_t attachmentCount; + const VkAttachmentDescription* pAttachments; + uint32_t subpassCount; + const VkSubpassDescription* pSubpasses; + uint32_t dependencyCount; + const VkSubpassDependency* pDependencies; +} VkRenderPassCreateInfo; + +typedef struct VkCommandPoolCreateInfo { + VkStructureType sType; + const void* pNext; + VkCommandPoolCreateFlags flags; + uint32_t queueFamilyIndex; +} VkCommandPoolCreateInfo; + +typedef struct VkCommandBufferAllocateInfo { + VkStructureType sType; + const void* pNext; + VkCommandPool commandPool; + VkCommandBufferLevel level; + uint32_t commandBufferCount; +} VkCommandBufferAllocateInfo; + +typedef struct VkCommandBufferInheritanceInfo { + VkStructureType sType; + const void* pNext; + VkRenderPass renderPass; + uint32_t subpass; + VkFramebuffer framebuffer; + VkBool32 occlusionQueryEnable; + VkQueryControlFlags queryFlags; + VkQueryPipelineStatisticFlags pipelineStatistics; +} VkCommandBufferInheritanceInfo; + +typedef struct VkCommandBufferBeginInfo { + VkStructureType sType; + const void* pNext; + VkCommandBufferUsageFlags flags; + const VkCommandBufferInheritanceInfo* pInheritanceInfo; +} VkCommandBufferBeginInfo; + +typedef struct VkBufferCopy { + VkDeviceSize srcOffset; + VkDeviceSize dstOffset; + VkDeviceSize size; +} VkBufferCopy; + +typedef struct VkImageSubresourceLayers { + VkImageAspectFlags aspectMask; + uint32_t mipLevel; + uint32_t baseArrayLayer; + uint32_t layerCount; +} VkImageSubresourceLayers; + +typedef struct VkImageCopy { + VkImageSubresourceLayers srcSubresource; + VkOffset3D srcOffset; + VkImageSubresourceLayers dstSubresource; + VkOffset3D dstOffset; + VkExtent3D extent; +} VkImageCopy; + +typedef struct VkImageBlit { + VkImageSubresourceLayers srcSubresource; + VkOffset3D srcOffsets[2]; + VkImageSubresourceLayers dstSubresource; + VkOffset3D dstOffsets[2]; +} VkImageBlit; + +typedef struct VkBufferImageCopy { + VkDeviceSize bufferOffset; + uint32_t bufferRowLength; + uint32_t bufferImageHeight; + VkImageSubresourceLayers imageSubresource; + VkOffset3D imageOffset; + VkExtent3D imageExtent; +} VkBufferImageCopy; + +typedef union VkClearColorValue { + float float32[4]; + int32_t int32[4]; + uint32_t uint32[4]; +} VkClearColorValue; + +typedef struct VkClearDepthStencilValue { + float depth; + uint32_t stencil; +} VkClearDepthStencilValue; + +typedef union VkClearValue { + VkClearColorValue color; + VkClearDepthStencilValue depthStencil; +} VkClearValue; + +typedef struct VkClearAttachment { + VkImageAspectFlags aspectMask; + uint32_t colorAttachment; + VkClearValue clearValue; +} VkClearAttachment; + +typedef struct VkClearRect { + VkRect2D rect; + uint32_t baseArrayLayer; + uint32_t layerCount; +} VkClearRect; + +typedef struct VkImageResolve { + VkImageSubresourceLayers srcSubresource; + VkOffset3D srcOffset; + VkImageSubresourceLayers dstSubresource; + VkOffset3D dstOffset; + VkExtent3D extent; +} VkImageResolve; + +typedef struct VkMemoryBarrier { + VkStructureType sType; + const void* pNext; + VkAccessFlags srcAccessMask; + VkAccessFlags dstAccessMask; +} VkMemoryBarrier; + +typedef struct VkBufferMemoryBarrier { + VkStructureType sType; + const void* pNext; + VkAccessFlags srcAccessMask; + VkAccessFlags dstAccessMask; + uint32_t srcQueueFamilyIndex; + uint32_t dstQueueFamilyIndex; + VkBuffer buffer; + VkDeviceSize offset; + VkDeviceSize size; +} VkBufferMemoryBarrier; + +typedef struct VkImageMemoryBarrier { + VkStructureType sType; + const void* pNext; + VkAccessFlags srcAccessMask; + VkAccessFlags dstAccessMask; + VkImageLayout oldLayout; + VkImageLayout newLayout; + uint32_t srcQueueFamilyIndex; + uint32_t dstQueueFamilyIndex; + VkImage image; + VkImageSubresourceRange subresourceRange; +} VkImageMemoryBarrier; + +typedef struct VkRenderPassBeginInfo { + VkStructureType sType; + const void* pNext; + VkRenderPass renderPass; + VkFramebuffer framebuffer; + VkRect2D renderArea; + uint32_t clearValueCount; + const VkClearValue* pClearValues; +} VkRenderPassBeginInfo; + +typedef struct VkDispatchIndirectCommand { + uint32_t x; + uint32_t y; + uint32_t z; +} VkDispatchIndirectCommand; + +typedef struct VkDrawIndexedIndirectCommand { + uint32_t indexCount; + uint32_t instanceCount; + uint32_t firstIndex; + int32_t vertexOffset; + uint32_t firstInstance; +} VkDrawIndexedIndirectCommand; + +typedef struct VkDrawIndirectCommand { + uint32_t vertexCount; + uint32_t instanceCount; + uint32_t firstVertex; + uint32_t firstInstance; +} VkDrawIndirectCommand; + +typedef struct VkBaseOutStructure { + VkStructureType sType; + struct VkBaseOutStructure* pNext; +} VkBaseOutStructure; + +typedef struct VkBaseInStructure { + VkStructureType sType; + const struct VkBaseInStructure* pNext; +} VkBaseInStructure; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateInstance)(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance); +typedef void (VKAPI_PTR *PFN_vkDestroyInstance)(VkInstance instance, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDevices)(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties* pFormatProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* pImageFormatProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties); +typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); +typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetDeviceProcAddr)(VkDevice device, const char* pName); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDevice)(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice); +typedef void (VKAPI_PTR *PFN_vkDestroyDevice)(VkDevice device, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceExtensionProperties)(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkEnumerateDeviceExtensionProperties)(VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceLayerProperties)(uint32_t* pPropertyCount, VkLayerProperties* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkEnumerateDeviceLayerProperties)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkLayerProperties* pProperties); +typedef void (VKAPI_PTR *PFN_vkGetDeviceQueue)(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue); +typedef VkResult (VKAPI_PTR *PFN_vkQueueSubmit)(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence); +typedef VkResult (VKAPI_PTR *PFN_vkQueueWaitIdle)(VkQueue queue); +typedef VkResult (VKAPI_PTR *PFN_vkDeviceWaitIdle)(VkDevice device); +typedef VkResult (VKAPI_PTR *PFN_vkAllocateMemory)(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory); +typedef void (VKAPI_PTR *PFN_vkFreeMemory)(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkMapMemory)(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData); +typedef void (VKAPI_PTR *PFN_vkUnmapMemory)(VkDevice device, VkDeviceMemory memory); +typedef VkResult (VKAPI_PTR *PFN_vkFlushMappedMemoryRanges)(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges); +typedef VkResult (VKAPI_PTR *PFN_vkInvalidateMappedMemoryRanges)(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges); +typedef void (VKAPI_PTR *PFN_vkGetDeviceMemoryCommitment)(VkDevice device, VkDeviceMemory memory, VkDeviceSize* pCommittedMemoryInBytes); +typedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory)(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset); +typedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory)(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset); +typedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements)(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements)(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements)(VkDevice device, VkImage image, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements* pSparseMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkSampleCountFlagBits samples, VkImageUsageFlags usage, VkImageTiling tiling, uint32_t* pPropertyCount, VkSparseImageFormatProperties* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkQueueBindSparse)(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence fence); +typedef VkResult (VKAPI_PTR *PFN_vkCreateFence)(VkDevice device, const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence); +typedef void (VKAPI_PTR *PFN_vkDestroyFence)(VkDevice device, VkFence fence, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkResetFences)(VkDevice device, uint32_t fenceCount, const VkFence* pFences); +typedef VkResult (VKAPI_PTR *PFN_vkGetFenceStatus)(VkDevice device, VkFence fence); +typedef VkResult (VKAPI_PTR *PFN_vkWaitForFences)(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout); +typedef VkResult (VKAPI_PTR *PFN_vkCreateSemaphore)(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore); +typedef void (VKAPI_PTR *PFN_vkDestroySemaphore)(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateEvent)(VkDevice device, const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent); +typedef void (VKAPI_PTR *PFN_vkDestroyEvent)(VkDevice device, VkEvent event, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkGetEventStatus)(VkDevice device, VkEvent event); +typedef VkResult (VKAPI_PTR *PFN_vkSetEvent)(VkDevice device, VkEvent event); +typedef VkResult (VKAPI_PTR *PFN_vkResetEvent)(VkDevice device, VkEvent event); +typedef VkResult (VKAPI_PTR *PFN_vkCreateQueryPool)(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool); +typedef void (VKAPI_PTR *PFN_vkDestroyQueryPool)(VkDevice device, VkQueryPool queryPool, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkGetQueryPoolResults)(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void* pData, VkDeviceSize stride, VkQueryResultFlags flags); +typedef VkResult (VKAPI_PTR *PFN_vkCreateBuffer)(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer); +typedef void (VKAPI_PTR *PFN_vkDestroyBuffer)(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateBufferView)(VkDevice device, const VkBufferViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBufferView* pView); +typedef void (VKAPI_PTR *PFN_vkDestroyBufferView)(VkDevice device, VkBufferView bufferView, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateImage)(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage); +typedef void (VKAPI_PTR *PFN_vkDestroyImage)(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkGetImageSubresourceLayout)(VkDevice device, VkImage image, const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout); +typedef VkResult (VKAPI_PTR *PFN_vkCreateImageView)(VkDevice device, const VkImageViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImageView* pView); +typedef void (VKAPI_PTR *PFN_vkDestroyImageView)(VkDevice device, VkImageView imageView, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateShaderModule)(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule); +typedef void (VKAPI_PTR *PFN_vkDestroyShaderModule)(VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreatePipelineCache)(VkDevice device, const VkPipelineCacheCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineCache* pPipelineCache); +typedef void (VKAPI_PTR *PFN_vkDestroyPipelineCache)(VkDevice device, VkPipelineCache pipelineCache, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkGetPipelineCacheData)(VkDevice device, VkPipelineCache pipelineCache, size_t* pDataSize, void* pData); +typedef VkResult (VKAPI_PTR *PFN_vkMergePipelineCaches)(VkDevice device, VkPipelineCache dstCache, uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches); +typedef VkResult (VKAPI_PTR *PFN_vkCreateGraphicsPipelines)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines); +typedef VkResult (VKAPI_PTR *PFN_vkCreateComputePipelines)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines); +typedef void (VKAPI_PTR *PFN_vkDestroyPipeline)(VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreatePipelineLayout)(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout); +typedef void (VKAPI_PTR *PFN_vkDestroyPipelineLayout)(VkDevice device, VkPipelineLayout pipelineLayout, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateSampler)(VkDevice device, const VkSamplerCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSampler* pSampler); +typedef void (VKAPI_PTR *PFN_vkDestroySampler)(VkDevice device, VkSampler sampler, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorSetLayout)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout); +typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorSetLayout)(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorPool)(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool); +typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorPool)(VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkResetDescriptorPool)(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags); +typedef VkResult (VKAPI_PTR *PFN_vkAllocateDescriptorSets)(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets); +typedef VkResult (VKAPI_PTR *PFN_vkFreeDescriptorSets)(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets); +typedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSets)(VkDevice device, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount, const VkCopyDescriptorSet* pDescriptorCopies); +typedef VkResult (VKAPI_PTR *PFN_vkCreateFramebuffer)(VkDevice device, const VkFramebufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFramebuffer* pFramebuffer); +typedef void (VKAPI_PTR *PFN_vkDestroyFramebuffer)(VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateRenderPass)(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass); +typedef void (VKAPI_PTR *PFN_vkDestroyRenderPass)(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkGetRenderAreaGranularity)(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity); +typedef VkResult (VKAPI_PTR *PFN_vkCreateCommandPool)(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool); +typedef void (VKAPI_PTR *PFN_vkDestroyCommandPool)(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkResetCommandPool)(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags); +typedef VkResult (VKAPI_PTR *PFN_vkAllocateCommandBuffers)(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers); +typedef void (VKAPI_PTR *PFN_vkFreeCommandBuffers)(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers); +typedef VkResult (VKAPI_PTR *PFN_vkBeginCommandBuffer)(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo); +typedef VkResult (VKAPI_PTR *PFN_vkEndCommandBuffer)(VkCommandBuffer commandBuffer); +typedef VkResult (VKAPI_PTR *PFN_vkResetCommandBuffer)(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags); +typedef void (VKAPI_PTR *PFN_vkCmdBindPipeline)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline); +typedef void (VKAPI_PTR *PFN_vkCmdSetViewport)(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport* pViewports); +typedef void (VKAPI_PTR *PFN_vkCmdSetScissor)(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D* pScissors); +typedef void (VKAPI_PTR *PFN_vkCmdSetLineWidth)(VkCommandBuffer commandBuffer, float lineWidth); +typedef void (VKAPI_PTR *PFN_vkCmdSetDepthBias)(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp, float depthBiasSlopeFactor); +typedef void (VKAPI_PTR *PFN_vkCmdSetBlendConstants)(VkCommandBuffer commandBuffer, const float blendConstants[4]); +typedef void (VKAPI_PTR *PFN_vkCmdSetDepthBounds)(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds); +typedef void (VKAPI_PTR *PFN_vkCmdSetStencilCompareMask)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t compareMask); +typedef void (VKAPI_PTR *PFN_vkCmdSetStencilWriteMask)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t writeMask); +typedef void (VKAPI_PTR *PFN_vkCmdSetStencilReference)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t reference); +typedef void (VKAPI_PTR *PFN_vkCmdBindDescriptorSets)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets); +typedef void (VKAPI_PTR *PFN_vkCmdBindIndexBuffer)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType); +typedef void (VKAPI_PTR *PFN_vkCmdBindVertexBuffers)(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets); +typedef void (VKAPI_PTR *PFN_vkCmdDraw)(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance); +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexed)(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance); +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride); +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride); +typedef void (VKAPI_PTR *PFN_vkCmdDispatch)(VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ); +typedef void (VKAPI_PTR *PFN_vkCmdDispatchIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset); +typedef void (VKAPI_PTR *PFN_vkCmdCopyBuffer)(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferCopy* pRegions); +typedef void (VKAPI_PTR *PFN_vkCmdCopyImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy* pRegions); +typedef void (VKAPI_PTR *PFN_vkCmdBlitImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageBlit* pRegions, VkFilter filter); +typedef void (VKAPI_PTR *PFN_vkCmdCopyBufferToImage)(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkBufferImageCopy* pRegions); +typedef void (VKAPI_PTR *PFN_vkCmdCopyImageToBuffer)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy* pRegions); +typedef void (VKAPI_PTR *PFN_vkCmdUpdateBuffer)(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize dataSize, const void* pData); +typedef void (VKAPI_PTR *PFN_vkCmdFillBuffer)(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize size, uint32_t data); +typedef void (VKAPI_PTR *PFN_vkCmdClearColorImage)(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearColorValue* pColor, uint32_t rangeCount, const VkImageSubresourceRange* pRanges); +typedef void (VKAPI_PTR *PFN_vkCmdClearDepthStencilImage)(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearDepthStencilValue* pDepthStencil, uint32_t rangeCount, const VkImageSubresourceRange* pRanges); +typedef void (VKAPI_PTR *PFN_vkCmdClearAttachments)(VkCommandBuffer commandBuffer, uint32_t attachmentCount, const VkClearAttachment* pAttachments, uint32_t rectCount, const VkClearRect* pRects); +typedef void (VKAPI_PTR *PFN_vkCmdResolveImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageResolve* pRegions); +typedef void (VKAPI_PTR *PFN_vkCmdSetEvent)(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask); +typedef void (VKAPI_PTR *PFN_vkCmdResetEvent)(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask); +typedef void (VKAPI_PTR *PFN_vkCmdWaitEvents)(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers); +typedef void (VKAPI_PTR *PFN_vkCmdPipelineBarrier)(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers); +typedef void (VKAPI_PTR *PFN_vkCmdBeginQuery)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags); +typedef void (VKAPI_PTR *PFN_vkCmdEndQuery)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query); +typedef void (VKAPI_PTR *PFN_vkCmdResetQueryPool)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount); +typedef void (VKAPI_PTR *PFN_vkCmdWriteTimestamp)(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage, VkQueryPool queryPool, uint32_t query); +typedef void (VKAPI_PTR *PFN_vkCmdCopyQueryPoolResults)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags); +typedef void (VKAPI_PTR *PFN_vkCmdPushConstants)(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void* pValues); +typedef void (VKAPI_PTR *PFN_vkCmdBeginRenderPass)(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents); +typedef void (VKAPI_PTR *PFN_vkCmdNextSubpass)(VkCommandBuffer commandBuffer, VkSubpassContents contents); +typedef void (VKAPI_PTR *PFN_vkCmdEndRenderPass)(VkCommandBuffer commandBuffer); +typedef void (VKAPI_PTR *PFN_vkCmdExecuteCommands)(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance); + +VKAPI_ATTR void VKAPI_CALL vkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices( + VkInstance instance, + uint32_t* pPhysicalDeviceCount, + VkPhysicalDevice* pPhysicalDevices); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceFeatures* pFeatures); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkFormatProperties* pFormatProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkImageType type, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkImageCreateFlags flags, + VkImageFormatProperties* pImageFormatProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties* pProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties* pQueueFamilyProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties* pMemoryProperties); + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( + VkInstance instance, + const char* pName); + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr( + VkDevice device, + const char* pName); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties( + VkPhysicalDevice physicalDevice, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( + uint32_t* pPropertyCount, + VkLayerProperties* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pPropertyCount, + VkLayerProperties* pProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue( + VkDevice device, + uint32_t queueFamilyIndex, + uint32_t queueIndex, + VkQueue* pQueue); + +VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence); + +VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle( + VkQueue queue); + +VKAPI_ATTR VkResult VKAPI_CALL vkDeviceWaitIdle( + VkDevice device); + +VKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory( + VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory); + +VKAPI_ATTR void VKAPI_CALL vkFreeMemory( + VkDevice device, + VkDeviceMemory memory, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory( + VkDevice device, + VkDeviceMemory memory, + VkDeviceSize offset, + VkDeviceSize size, + VkMemoryMapFlags flags, + void** ppData); + +VKAPI_ATTR void VKAPI_CALL vkUnmapMemory( + VkDevice device, + VkDeviceMemory memory); + +VKAPI_ATTR VkResult VKAPI_CALL vkFlushMappedMemoryRanges( + VkDevice device, + uint32_t memoryRangeCount, + const VkMappedMemoryRange* pMemoryRanges); + +VKAPI_ATTR VkResult VKAPI_CALL vkInvalidateMappedMemoryRanges( + VkDevice device, + uint32_t memoryRangeCount, + const VkMappedMemoryRange* pMemoryRanges); + +VKAPI_ATTR void VKAPI_CALL vkGetDeviceMemoryCommitment( + VkDevice device, + VkDeviceMemory memory, + VkDeviceSize* pCommittedMemoryInBytes); + +VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory( + VkDevice device, + VkBuffer buffer, + VkDeviceMemory memory, + VkDeviceSize memoryOffset); + +VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + VkDeviceSize memoryOffset); + +VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements( + VkDevice device, + VkBuffer buffer, + VkMemoryRequirements* pMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements( + VkDevice device, + VkImage image, + VkMemoryRequirements* pMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements( + VkDevice device, + VkImage image, + uint32_t* pSparseMemoryRequirementCount, + VkSparseImageMemoryRequirements* pSparseMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkImageType type, + VkSampleCountFlagBits samples, + VkImageUsageFlags usage, + VkImageTiling tiling, + uint32_t* pPropertyCount, + VkSparseImageFormatProperties* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkQueueBindSparse( + VkQueue queue, + uint32_t bindInfoCount, + const VkBindSparseInfo* pBindInfo, + VkFence fence); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateFence( + VkDevice device, + const VkFenceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkFence* pFence); + +VKAPI_ATTR void VKAPI_CALL vkDestroyFence( + VkDevice device, + VkFence fence, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkResetFences( + VkDevice device, + uint32_t fenceCount, + const VkFence* pFences); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceStatus( + VkDevice device, + VkFence fence); + +VKAPI_ATTR VkResult VKAPI_CALL vkWaitForFences( + VkDevice device, + uint32_t fenceCount, + const VkFence* pFences, + VkBool32 waitAll, + uint64_t timeout); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateSemaphore( + VkDevice device, + const VkSemaphoreCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore); + +VKAPI_ATTR void VKAPI_CALL vkDestroySemaphore( + VkDevice device, + VkSemaphore semaphore, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateEvent( + VkDevice device, + const VkEventCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkEvent* pEvent); + +VKAPI_ATTR void VKAPI_CALL vkDestroyEvent( + VkDevice device, + VkEvent event, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetEventStatus( + VkDevice device, + VkEvent event); + +VKAPI_ATTR VkResult VKAPI_CALL vkSetEvent( + VkDevice device, + VkEvent event); + +VKAPI_ATTR VkResult VKAPI_CALL vkResetEvent( + VkDevice device, + VkEvent event); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateQueryPool( + VkDevice device, + const VkQueryPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkQueryPool* pQueryPool); + +VKAPI_ATTR void VKAPI_CALL vkDestroyQueryPool( + VkDevice device, + VkQueryPool queryPool, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetQueryPoolResults( + VkDevice device, + VkQueryPool queryPool, + uint32_t firstQuery, + uint32_t queryCount, + size_t dataSize, + void* pData, + VkDeviceSize stride, + VkQueryResultFlags flags); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer( + VkDevice device, + const VkBufferCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkBuffer* pBuffer); + +VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer( + VkDevice device, + VkBuffer buffer, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateBufferView( + VkDevice device, + const VkBufferViewCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkBufferView* pView); + +VKAPI_ATTR void VKAPI_CALL vkDestroyBufferView( + VkDevice device, + VkBufferView bufferView, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage( + VkDevice device, + const VkImageCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImage* pImage); + +VKAPI_ATTR void VKAPI_CALL vkDestroyImage( + VkDevice device, + VkImage image, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR void VKAPI_CALL vkGetImageSubresourceLayout( + VkDevice device, + VkImage image, + const VkImageSubresource* pSubresource, + VkSubresourceLayout* pLayout); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView( + VkDevice device, + const VkImageViewCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImageView* pView); + +VKAPI_ATTR void VKAPI_CALL vkDestroyImageView( + VkDevice device, + VkImageView imageView, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule( + VkDevice device, + const VkShaderModuleCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkShaderModule* pShaderModule); + +VKAPI_ATTR void VKAPI_CALL vkDestroyShaderModule( + VkDevice device, + VkShaderModule shaderModule, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineCache( + VkDevice device, + const VkPipelineCacheCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkPipelineCache* pPipelineCache); + +VKAPI_ATTR void VKAPI_CALL vkDestroyPipelineCache( + VkDevice device, + VkPipelineCache pipelineCache, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPipelineCacheData( + VkDevice device, + VkPipelineCache pipelineCache, + size_t* pDataSize, + void* pData); + +VKAPI_ATTR VkResult VKAPI_CALL vkMergePipelineCaches( + VkDevice device, + VkPipelineCache dstCache, + uint32_t srcCacheCount, + const VkPipelineCache* pSrcCaches); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateGraphicsPipelines( + VkDevice device, + VkPipelineCache pipelineCache, + uint32_t createInfoCount, + const VkGraphicsPipelineCreateInfo* pCreateInfos, + const VkAllocationCallbacks* pAllocator, + VkPipeline* pPipelines); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateComputePipelines( + VkDevice device, + VkPipelineCache pipelineCache, + uint32_t createInfoCount, + const VkComputePipelineCreateInfo* pCreateInfos, + const VkAllocationCallbacks* pAllocator, + VkPipeline* pPipelines); + +VKAPI_ATTR void VKAPI_CALL vkDestroyPipeline( + VkDevice device, + VkPipeline pipeline, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineLayout( + VkDevice device, + const VkPipelineLayoutCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkPipelineLayout* pPipelineLayout); + +VKAPI_ATTR void VKAPI_CALL vkDestroyPipelineLayout( + VkDevice device, + VkPipelineLayout pipelineLayout, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateSampler( + VkDevice device, + const VkSamplerCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSampler* pSampler); + +VKAPI_ATTR void VKAPI_CALL vkDestroySampler( + VkDevice device, + VkSampler sampler, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorSetLayout( + VkDevice device, + const VkDescriptorSetLayoutCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDescriptorSetLayout* pSetLayout); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorSetLayout( + VkDevice device, + VkDescriptorSetLayout descriptorSetLayout, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorPool( + VkDevice device, + const VkDescriptorPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDescriptorPool* pDescriptorPool); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorPool( + VkDevice device, + VkDescriptorPool descriptorPool, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkResetDescriptorPool( + VkDevice device, + VkDescriptorPool descriptorPool, + VkDescriptorPoolResetFlags flags); + +VKAPI_ATTR VkResult VKAPI_CALL vkAllocateDescriptorSets( + VkDevice device, + const VkDescriptorSetAllocateInfo* pAllocateInfo, + VkDescriptorSet* pDescriptorSets); + +VKAPI_ATTR VkResult VKAPI_CALL vkFreeDescriptorSets( + VkDevice device, + VkDescriptorPool descriptorPool, + uint32_t descriptorSetCount, + const VkDescriptorSet* pDescriptorSets); + +VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSets( + VkDevice device, + uint32_t descriptorWriteCount, + const VkWriteDescriptorSet* pDescriptorWrites, + uint32_t descriptorCopyCount, + const VkCopyDescriptorSet* pDescriptorCopies); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateFramebuffer( + VkDevice device, + const VkFramebufferCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkFramebuffer* pFramebuffer); + +VKAPI_ATTR void VKAPI_CALL vkDestroyFramebuffer( + VkDevice device, + VkFramebuffer framebuffer, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass( + VkDevice device, + const VkRenderPassCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass); + +VKAPI_ATTR void VKAPI_CALL vkDestroyRenderPass( + VkDevice device, + VkRenderPass renderPass, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR void VKAPI_CALL vkGetRenderAreaGranularity( + VkDevice device, + VkRenderPass renderPass, + VkExtent2D* pGranularity); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool); + +VKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandPool( + VkDevice device, + VkCommandPool commandPool, + VkCommandPoolResetFlags flags); + +VKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers); + +VKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers); + +VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer( + VkCommandBuffer commandBuffer); + +VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer( + VkCommandBuffer commandBuffer, + VkCommandBufferResetFlags flags); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindPipeline( + VkCommandBuffer commandBuffer, + VkPipelineBindPoint pipelineBindPoint, + VkPipeline pipeline); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetViewport( + VkCommandBuffer commandBuffer, + uint32_t firstViewport, + uint32_t viewportCount, + const VkViewport* pViewports); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetScissor( + VkCommandBuffer commandBuffer, + uint32_t firstScissor, + uint32_t scissorCount, + const VkRect2D* pScissors); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetLineWidth( + VkCommandBuffer commandBuffer, + float lineWidth); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBias( + VkCommandBuffer commandBuffer, + float depthBiasConstantFactor, + float depthBiasClamp, + float depthBiasSlopeFactor); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetBlendConstants( + VkCommandBuffer commandBuffer, + const float blendConstants[4]); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBounds( + VkCommandBuffer commandBuffer, + float minDepthBounds, + float maxDepthBounds); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilCompareMask( + VkCommandBuffer commandBuffer, + VkStencilFaceFlags faceMask, + uint32_t compareMask); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilWriteMask( + VkCommandBuffer commandBuffer, + VkStencilFaceFlags faceMask, + uint32_t writeMask); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilReference( + VkCommandBuffer commandBuffer, + VkStencilFaceFlags faceMask, + uint32_t reference); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindDescriptorSets( + VkCommandBuffer commandBuffer, + VkPipelineBindPoint pipelineBindPoint, + VkPipelineLayout layout, + uint32_t firstSet, + uint32_t descriptorSetCount, + const VkDescriptorSet* pDescriptorSets, + uint32_t dynamicOffsetCount, + const uint32_t* pDynamicOffsets); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindIndexBuffer( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkIndexType indexType); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindVertexBuffers( + VkCommandBuffer commandBuffer, + uint32_t firstBinding, + uint32_t bindingCount, + const VkBuffer* pBuffers, + const VkDeviceSize* pOffsets); + +VKAPI_ATTR void VKAPI_CALL vkCmdDraw( + VkCommandBuffer commandBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexed( + VkCommandBuffer commandBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + uint32_t drawCount, + uint32_t stride); + +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + uint32_t drawCount, + uint32_t stride); + +VKAPI_ATTR void VKAPI_CALL vkCmdDispatch( + VkCommandBuffer commandBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +VKAPI_ATTR void VKAPI_CALL vkCmdDispatchIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset); + +VKAPI_ATTR void VKAPI_CALL vkCmdCopyBuffer( + VkCommandBuffer commandBuffer, + VkBuffer srcBuffer, + VkBuffer dstBuffer, + uint32_t regionCount, + const VkBufferCopy* pRegions); + +VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageCopy* pRegions); + +VKAPI_ATTR void VKAPI_CALL vkCmdBlitImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageBlit* pRegions, + VkFilter filter); + +VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage( + VkCommandBuffer commandBuffer, + VkBuffer srcBuffer, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkBufferImageCopy* pRegions); + +VKAPI_ATTR void VKAPI_CALL vkCmdCopyImageToBuffer( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkBuffer dstBuffer, + uint32_t regionCount, + const VkBufferImageCopy* pRegions); + +VKAPI_ATTR void VKAPI_CALL vkCmdUpdateBuffer( + VkCommandBuffer commandBuffer, + VkBuffer dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize dataSize, + const void* pData); + +VKAPI_ATTR void VKAPI_CALL vkCmdFillBuffer( + VkCommandBuffer commandBuffer, + VkBuffer dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize size, + uint32_t data); + +VKAPI_ATTR void VKAPI_CALL vkCmdClearColorImage( + VkCommandBuffer commandBuffer, + VkImage image, + VkImageLayout imageLayout, + const VkClearColorValue* pColor, + uint32_t rangeCount, + const VkImageSubresourceRange* pRanges); + +VKAPI_ATTR void VKAPI_CALL vkCmdClearDepthStencilImage( + VkCommandBuffer commandBuffer, + VkImage image, + VkImageLayout imageLayout, + const VkClearDepthStencilValue* pDepthStencil, + uint32_t rangeCount, + const VkImageSubresourceRange* pRanges); + +VKAPI_ATTR void VKAPI_CALL vkCmdClearAttachments( + VkCommandBuffer commandBuffer, + uint32_t attachmentCount, + const VkClearAttachment* pAttachments, + uint32_t rectCount, + const VkClearRect* pRects); + +VKAPI_ATTR void VKAPI_CALL vkCmdResolveImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageResolve* pRegions); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetEvent( + VkCommandBuffer commandBuffer, + VkEvent event, + VkPipelineStageFlags stageMask); + +VKAPI_ATTR void VKAPI_CALL vkCmdResetEvent( + VkCommandBuffer commandBuffer, + VkEvent event, + VkPipelineStageFlags stageMask); + +VKAPI_ATTR void VKAPI_CALL vkCmdWaitEvents( + VkCommandBuffer commandBuffer, + uint32_t eventCount, + const VkEvent* pEvents, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers); + +VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers); + +VKAPI_ATTR void VKAPI_CALL vkCmdBeginQuery( + VkCommandBuffer commandBuffer, + VkQueryPool queryPool, + uint32_t query, + VkQueryControlFlags flags); + +VKAPI_ATTR void VKAPI_CALL vkCmdEndQuery( + VkCommandBuffer commandBuffer, + VkQueryPool queryPool, + uint32_t query); + +VKAPI_ATTR void VKAPI_CALL vkCmdResetQueryPool( + VkCommandBuffer commandBuffer, + VkQueryPool queryPool, + uint32_t firstQuery, + uint32_t queryCount); + +VKAPI_ATTR void VKAPI_CALL vkCmdWriteTimestamp( + VkCommandBuffer commandBuffer, + VkPipelineStageFlagBits pipelineStage, + VkQueryPool queryPool, + uint32_t query); + +VKAPI_ATTR void VKAPI_CALL vkCmdCopyQueryPoolResults( + VkCommandBuffer commandBuffer, + VkQueryPool queryPool, + uint32_t firstQuery, + uint32_t queryCount, + VkBuffer dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize stride, + VkQueryResultFlags flags); + +VKAPI_ATTR void VKAPI_CALL vkCmdPushConstants( + VkCommandBuffer commandBuffer, + VkPipelineLayout layout, + VkShaderStageFlags stageFlags, + uint32_t offset, + uint32_t size, + const void* pValues); + +VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + VkSubpassContents contents); + +VKAPI_ATTR void VKAPI_CALL vkCmdNextSubpass( + VkCommandBuffer commandBuffer, + VkSubpassContents contents); + +VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass( + VkCommandBuffer commandBuffer); + +VKAPI_ATTR void VKAPI_CALL vkCmdExecuteCommands( + VkCommandBuffer commandBuffer, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers); +#endif + +#define VK_VERSION_1_1 1 +// Vulkan 1.1 version number +#define VK_API_VERSION_1_1 VK_MAKE_VERSION(1, 1, 0)// Patch version should always be set to 0 + + +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSamplerYcbcrConversion) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorUpdateTemplate) + +#define VK_MAX_DEVICE_GROUP_SIZE 32 +#define VK_LUID_SIZE 8 +#define VK_QUEUE_FAMILY_EXTERNAL (~0U-1) + + +typedef enum VkPointClippingBehavior { + VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES = 0, + VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY = 1, + VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES_KHR = VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES, + VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY_KHR = VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY, + VK_POINT_CLIPPING_BEHAVIOR_BEGIN_RANGE = VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES, + VK_POINT_CLIPPING_BEHAVIOR_END_RANGE = VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY, + VK_POINT_CLIPPING_BEHAVIOR_RANGE_SIZE = (VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY - VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES + 1), + VK_POINT_CLIPPING_BEHAVIOR_MAX_ENUM = 0x7FFFFFFF +} VkPointClippingBehavior; + +typedef enum VkTessellationDomainOrigin { + VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT = 0, + VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT = 1, + VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT_KHR = VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT, + VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT_KHR = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT, + VK_TESSELLATION_DOMAIN_ORIGIN_BEGIN_RANGE = VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT, + VK_TESSELLATION_DOMAIN_ORIGIN_END_RANGE = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT, + VK_TESSELLATION_DOMAIN_ORIGIN_RANGE_SIZE = (VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT - VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT + 1), + VK_TESSELLATION_DOMAIN_ORIGIN_MAX_ENUM = 0x7FFFFFFF +} VkTessellationDomainOrigin; + +typedef enum VkSamplerYcbcrModelConversion { + VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY = 0, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY = 1, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709 = 2, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601 = 3, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020 = 4, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_BEGIN_RANGE = VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_END_RANGE = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020, + VK_SAMPLER_YCBCR_MODEL_CONVERSION_RANGE_SIZE = (VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020 - VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY + 1), + VK_SAMPLER_YCBCR_MODEL_CONVERSION_MAX_ENUM = 0x7FFFFFFF +} VkSamplerYcbcrModelConversion; + +typedef enum VkSamplerYcbcrRange { + VK_SAMPLER_YCBCR_RANGE_ITU_FULL = 0, + VK_SAMPLER_YCBCR_RANGE_ITU_NARROW = 1, + VK_SAMPLER_YCBCR_RANGE_ITU_FULL_KHR = VK_SAMPLER_YCBCR_RANGE_ITU_FULL, + VK_SAMPLER_YCBCR_RANGE_ITU_NARROW_KHR = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, + VK_SAMPLER_YCBCR_RANGE_BEGIN_RANGE = VK_SAMPLER_YCBCR_RANGE_ITU_FULL, + VK_SAMPLER_YCBCR_RANGE_END_RANGE = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, + VK_SAMPLER_YCBCR_RANGE_RANGE_SIZE = (VK_SAMPLER_YCBCR_RANGE_ITU_NARROW - VK_SAMPLER_YCBCR_RANGE_ITU_FULL + 1), + VK_SAMPLER_YCBCR_RANGE_MAX_ENUM = 0x7FFFFFFF +} VkSamplerYcbcrRange; + +typedef enum VkChromaLocation { + VK_CHROMA_LOCATION_COSITED_EVEN = 0, + VK_CHROMA_LOCATION_MIDPOINT = 1, + VK_CHROMA_LOCATION_COSITED_EVEN_KHR = VK_CHROMA_LOCATION_COSITED_EVEN, + VK_CHROMA_LOCATION_MIDPOINT_KHR = VK_CHROMA_LOCATION_MIDPOINT, + VK_CHROMA_LOCATION_BEGIN_RANGE = VK_CHROMA_LOCATION_COSITED_EVEN, + VK_CHROMA_LOCATION_END_RANGE = VK_CHROMA_LOCATION_MIDPOINT, + VK_CHROMA_LOCATION_RANGE_SIZE = (VK_CHROMA_LOCATION_MIDPOINT - VK_CHROMA_LOCATION_COSITED_EVEN + 1), + VK_CHROMA_LOCATION_MAX_ENUM = 0x7FFFFFFF +} VkChromaLocation; + +typedef enum VkDescriptorUpdateTemplateType { + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET = 0, + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR = 1, + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET, + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_BEGIN_RANGE = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET, + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_END_RANGE = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET, + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_RANGE_SIZE = (VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET - VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET + 1), + VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_MAX_ENUM = 0x7FFFFFFF +} VkDescriptorUpdateTemplateType; + + +typedef enum VkSubgroupFeatureFlagBits { + VK_SUBGROUP_FEATURE_BASIC_BIT = 0x00000001, + VK_SUBGROUP_FEATURE_VOTE_BIT = 0x00000002, + VK_SUBGROUP_FEATURE_ARITHMETIC_BIT = 0x00000004, + VK_SUBGROUP_FEATURE_BALLOT_BIT = 0x00000008, + VK_SUBGROUP_FEATURE_SHUFFLE_BIT = 0x00000010, + VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT = 0x00000020, + VK_SUBGROUP_FEATURE_CLUSTERED_BIT = 0x00000040, + VK_SUBGROUP_FEATURE_QUAD_BIT = 0x00000080, + VK_SUBGROUP_FEATURE_PARTITIONED_BIT_NV = 0x00000100, + VK_SUBGROUP_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkSubgroupFeatureFlagBits; +typedef VkFlags VkSubgroupFeatureFlags; + +typedef enum VkPeerMemoryFeatureFlagBits { + VK_PEER_MEMORY_FEATURE_COPY_SRC_BIT = 0x00000001, + VK_PEER_MEMORY_FEATURE_COPY_DST_BIT = 0x00000002, + VK_PEER_MEMORY_FEATURE_GENERIC_SRC_BIT = 0x00000004, + VK_PEER_MEMORY_FEATURE_GENERIC_DST_BIT = 0x00000008, + VK_PEER_MEMORY_FEATURE_COPY_SRC_BIT_KHR = VK_PEER_MEMORY_FEATURE_COPY_SRC_BIT, + VK_PEER_MEMORY_FEATURE_COPY_DST_BIT_KHR = VK_PEER_MEMORY_FEATURE_COPY_DST_BIT, + VK_PEER_MEMORY_FEATURE_GENERIC_SRC_BIT_KHR = VK_PEER_MEMORY_FEATURE_GENERIC_SRC_BIT, + VK_PEER_MEMORY_FEATURE_GENERIC_DST_BIT_KHR = VK_PEER_MEMORY_FEATURE_GENERIC_DST_BIT, + VK_PEER_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkPeerMemoryFeatureFlagBits; +typedef VkFlags VkPeerMemoryFeatureFlags; + +typedef enum VkMemoryAllocateFlagBits { + VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT = 0x00000001, + VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT_KHR = VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT, + VK_MEMORY_ALLOCATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkMemoryAllocateFlagBits; +typedef VkFlags VkMemoryAllocateFlags; +typedef VkFlags VkCommandPoolTrimFlags; +typedef VkFlags VkDescriptorUpdateTemplateCreateFlags; + +typedef enum VkExternalMemoryHandleTypeFlagBits { + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT = 0x00000001, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT = 0x00000002, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT = 0x00000004, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT = 0x00000008, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT = 0x00000010, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT = 0x00000020, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT = 0x00000040, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT = 0x00000200, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID = 0x00000400, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT = 0x00000080, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT = 0x00000100, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkExternalMemoryHandleTypeFlagBits; +typedef VkFlags VkExternalMemoryHandleTypeFlags; + +typedef enum VkExternalMemoryFeatureFlagBits { + VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT = 0x00000001, + VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT = 0x00000002, + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT = 0x00000004, + VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_KHR = VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT, + VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_KHR = VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT, + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR = VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT, + VK_EXTERNAL_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkExternalMemoryFeatureFlagBits; +typedef VkFlags VkExternalMemoryFeatureFlags; + +typedef enum VkExternalFenceHandleTypeFlagBits { + VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT = 0x00000001, + VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT = 0x00000002, + VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT = 0x00000004, + VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT = 0x00000008, + VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT, + VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT, + VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT, + VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, + VK_EXTERNAL_FENCE_HANDLE_TYPE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkExternalFenceHandleTypeFlagBits; +typedef VkFlags VkExternalFenceHandleTypeFlags; + +typedef enum VkExternalFenceFeatureFlagBits { + VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT = 0x00000001, + VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT = 0x00000002, + VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT_KHR = VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT, + VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT_KHR = VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT, + VK_EXTERNAL_FENCE_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkExternalFenceFeatureFlagBits; +typedef VkFlags VkExternalFenceFeatureFlags; + +typedef enum VkFenceImportFlagBits { + VK_FENCE_IMPORT_TEMPORARY_BIT = 0x00000001, + VK_FENCE_IMPORT_TEMPORARY_BIT_KHR = VK_FENCE_IMPORT_TEMPORARY_BIT, + VK_FENCE_IMPORT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkFenceImportFlagBits; +typedef VkFlags VkFenceImportFlags; + +typedef enum VkSemaphoreImportFlagBits { + VK_SEMAPHORE_IMPORT_TEMPORARY_BIT = 0x00000001, + VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, + VK_SEMAPHORE_IMPORT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkSemaphoreImportFlagBits; +typedef VkFlags VkSemaphoreImportFlags; + +typedef enum VkExternalSemaphoreHandleTypeFlagBits { + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT = 0x00000001, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT = 0x00000002, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT = 0x00000004, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT = 0x00000008, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT = 0x00000010, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkExternalSemaphoreHandleTypeFlagBits; +typedef VkFlags VkExternalSemaphoreHandleTypeFlags; + +typedef enum VkExternalSemaphoreFeatureFlagBits { + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT = 0x00000001, + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT = 0x00000002, + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT_KHR = VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT, + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT_KHR = VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT, + VK_EXTERNAL_SEMAPHORE_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VkExternalSemaphoreFeatureFlagBits; +typedef VkFlags VkExternalSemaphoreFeatureFlags; + +typedef struct VkPhysicalDeviceSubgroupProperties { + VkStructureType sType; + void* pNext; + uint32_t subgroupSize; + VkShaderStageFlags supportedStages; + VkSubgroupFeatureFlags supportedOperations; + VkBool32 quadOperationsInAllStages; +} VkPhysicalDeviceSubgroupProperties; + +typedef struct VkBindBufferMemoryInfo { + VkStructureType sType; + const void* pNext; + VkBuffer buffer; + VkDeviceMemory memory; + VkDeviceSize memoryOffset; +} VkBindBufferMemoryInfo; + +typedef struct VkBindImageMemoryInfo { + VkStructureType sType; + const void* pNext; + VkImage image; + VkDeviceMemory memory; + VkDeviceSize memoryOffset; +} VkBindImageMemoryInfo; + +typedef struct VkPhysicalDevice16BitStorageFeatures { + VkStructureType sType; + void* pNext; + VkBool32 storageBuffer16BitAccess; + VkBool32 uniformAndStorageBuffer16BitAccess; + VkBool32 storagePushConstant16; + VkBool32 storageInputOutput16; +} VkPhysicalDevice16BitStorageFeatures; + +typedef struct VkMemoryDedicatedRequirements { + VkStructureType sType; + void* pNext; + VkBool32 prefersDedicatedAllocation; + VkBool32 requiresDedicatedAllocation; +} VkMemoryDedicatedRequirements; + +typedef struct VkMemoryDedicatedAllocateInfo { + VkStructureType sType; + const void* pNext; + VkImage image; + VkBuffer buffer; +} VkMemoryDedicatedAllocateInfo; + +typedef struct VkMemoryAllocateFlagsInfo { + VkStructureType sType; + const void* pNext; + VkMemoryAllocateFlags flags; + uint32_t deviceMask; +} VkMemoryAllocateFlagsInfo; + +typedef struct VkDeviceGroupRenderPassBeginInfo { + VkStructureType sType; + const void* pNext; + uint32_t deviceMask; + uint32_t deviceRenderAreaCount; + const VkRect2D* pDeviceRenderAreas; +} VkDeviceGroupRenderPassBeginInfo; + +typedef struct VkDeviceGroupCommandBufferBeginInfo { + VkStructureType sType; + const void* pNext; + uint32_t deviceMask; +} VkDeviceGroupCommandBufferBeginInfo; + +typedef struct VkDeviceGroupSubmitInfo { + VkStructureType sType; + const void* pNext; + uint32_t waitSemaphoreCount; + const uint32_t* pWaitSemaphoreDeviceIndices; + uint32_t commandBufferCount; + const uint32_t* pCommandBufferDeviceMasks; + uint32_t signalSemaphoreCount; + const uint32_t* pSignalSemaphoreDeviceIndices; +} VkDeviceGroupSubmitInfo; + +typedef struct VkDeviceGroupBindSparseInfo { + VkStructureType sType; + const void* pNext; + uint32_t resourceDeviceIndex; + uint32_t memoryDeviceIndex; +} VkDeviceGroupBindSparseInfo; + +typedef struct VkBindBufferMemoryDeviceGroupInfo { + VkStructureType sType; + const void* pNext; + uint32_t deviceIndexCount; + const uint32_t* pDeviceIndices; +} VkBindBufferMemoryDeviceGroupInfo; + +typedef struct VkBindImageMemoryDeviceGroupInfo { + VkStructureType sType; + const void* pNext; + uint32_t deviceIndexCount; + const uint32_t* pDeviceIndices; + uint32_t splitInstanceBindRegionCount; + const VkRect2D* pSplitInstanceBindRegions; +} VkBindImageMemoryDeviceGroupInfo; + +typedef struct VkPhysicalDeviceGroupProperties { + VkStructureType sType; + void* pNext; + uint32_t physicalDeviceCount; + VkPhysicalDevice physicalDevices[VK_MAX_DEVICE_GROUP_SIZE]; + VkBool32 subsetAllocation; +} VkPhysicalDeviceGroupProperties; + +typedef struct VkDeviceGroupDeviceCreateInfo { + VkStructureType sType; + const void* pNext; + uint32_t physicalDeviceCount; + const VkPhysicalDevice* pPhysicalDevices; +} VkDeviceGroupDeviceCreateInfo; + +typedef struct VkBufferMemoryRequirementsInfo2 { + VkStructureType sType; + const void* pNext; + VkBuffer buffer; +} VkBufferMemoryRequirementsInfo2; + +typedef struct VkImageMemoryRequirementsInfo2 { + VkStructureType sType; + const void* pNext; + VkImage image; +} VkImageMemoryRequirementsInfo2; + +typedef struct VkImageSparseMemoryRequirementsInfo2 { + VkStructureType sType; + const void* pNext; + VkImage image; +} VkImageSparseMemoryRequirementsInfo2; + +typedef struct VkMemoryRequirements2 { + VkStructureType sType; + void* pNext; + VkMemoryRequirements memoryRequirements; +} VkMemoryRequirements2; + +typedef struct VkSparseImageMemoryRequirements2 { + VkStructureType sType; + void* pNext; + VkSparseImageMemoryRequirements memoryRequirements; +} VkSparseImageMemoryRequirements2; + +typedef struct VkPhysicalDeviceFeatures2 { + VkStructureType sType; + void* pNext; + VkPhysicalDeviceFeatures features; +} VkPhysicalDeviceFeatures2; + +typedef struct VkPhysicalDeviceProperties2 { + VkStructureType sType; + void* pNext; + VkPhysicalDeviceProperties properties; +} VkPhysicalDeviceProperties2; + +typedef struct VkFormatProperties2 { + VkStructureType sType; + void* pNext; + VkFormatProperties formatProperties; +} VkFormatProperties2; + +typedef struct VkImageFormatProperties2 { + VkStructureType sType; + void* pNext; + VkImageFormatProperties imageFormatProperties; +} VkImageFormatProperties2; + +typedef struct VkPhysicalDeviceImageFormatInfo2 { + VkStructureType sType; + const void* pNext; + VkFormat format; + VkImageType type; + VkImageTiling tiling; + VkImageUsageFlags usage; + VkImageCreateFlags flags; +} VkPhysicalDeviceImageFormatInfo2; + +typedef struct VkQueueFamilyProperties2 { + VkStructureType sType; + void* pNext; + VkQueueFamilyProperties queueFamilyProperties; +} VkQueueFamilyProperties2; + +typedef struct VkPhysicalDeviceMemoryProperties2 { + VkStructureType sType; + void* pNext; + VkPhysicalDeviceMemoryProperties memoryProperties; +} VkPhysicalDeviceMemoryProperties2; + +typedef struct VkSparseImageFormatProperties2 { + VkStructureType sType; + void* pNext; + VkSparseImageFormatProperties properties; +} VkSparseImageFormatProperties2; + +typedef struct VkPhysicalDeviceSparseImageFormatInfo2 { + VkStructureType sType; + const void* pNext; + VkFormat format; + VkImageType type; + VkSampleCountFlagBits samples; + VkImageUsageFlags usage; + VkImageTiling tiling; +} VkPhysicalDeviceSparseImageFormatInfo2; + +typedef struct VkPhysicalDevicePointClippingProperties { + VkStructureType sType; + void* pNext; + VkPointClippingBehavior pointClippingBehavior; +} VkPhysicalDevicePointClippingProperties; + +typedef struct VkInputAttachmentAspectReference { + uint32_t subpass; + uint32_t inputAttachmentIndex; + VkImageAspectFlags aspectMask; +} VkInputAttachmentAspectReference; + +typedef struct VkRenderPassInputAttachmentAspectCreateInfo { + VkStructureType sType; + const void* pNext; + uint32_t aspectReferenceCount; + const VkInputAttachmentAspectReference* pAspectReferences; +} VkRenderPassInputAttachmentAspectCreateInfo; + +typedef struct VkImageViewUsageCreateInfo { + VkStructureType sType; + const void* pNext; + VkImageUsageFlags usage; +} VkImageViewUsageCreateInfo; + +typedef struct VkPipelineTessellationDomainOriginStateCreateInfo { + VkStructureType sType; + const void* pNext; + VkTessellationDomainOrigin domainOrigin; +} VkPipelineTessellationDomainOriginStateCreateInfo; + +typedef struct VkRenderPassMultiviewCreateInfo { + VkStructureType sType; + const void* pNext; + uint32_t subpassCount; + const uint32_t* pViewMasks; + uint32_t dependencyCount; + const int32_t* pViewOffsets; + uint32_t correlationMaskCount; + const uint32_t* pCorrelationMasks; +} VkRenderPassMultiviewCreateInfo; + +typedef struct VkPhysicalDeviceMultiviewFeatures { + VkStructureType sType; + void* pNext; + VkBool32 multiview; + VkBool32 multiviewGeometryShader; + VkBool32 multiviewTessellationShader; +} VkPhysicalDeviceMultiviewFeatures; + +typedef struct VkPhysicalDeviceMultiviewProperties { + VkStructureType sType; + void* pNext; + uint32_t maxMultiviewViewCount; + uint32_t maxMultiviewInstanceIndex; +} VkPhysicalDeviceMultiviewProperties; + +typedef struct VkPhysicalDeviceVariablePointerFeatures { + VkStructureType sType; + void* pNext; + VkBool32 variablePointersStorageBuffer; + VkBool32 variablePointers; +} VkPhysicalDeviceVariablePointerFeatures; + +typedef struct VkPhysicalDeviceProtectedMemoryFeatures { + VkStructureType sType; + void* pNext; + VkBool32 protectedMemory; +} VkPhysicalDeviceProtectedMemoryFeatures; + +typedef struct VkPhysicalDeviceProtectedMemoryProperties { + VkStructureType sType; + void* pNext; + VkBool32 protectedNoFault; +} VkPhysicalDeviceProtectedMemoryProperties; + +typedef struct VkDeviceQueueInfo2 { + VkStructureType sType; + const void* pNext; + VkDeviceQueueCreateFlags flags; + uint32_t queueFamilyIndex; + uint32_t queueIndex; +} VkDeviceQueueInfo2; + +typedef struct VkProtectedSubmitInfo { + VkStructureType sType; + const void* pNext; + VkBool32 protectedSubmit; +} VkProtectedSubmitInfo; + +typedef struct VkSamplerYcbcrConversionCreateInfo { + VkStructureType sType; + const void* pNext; + VkFormat format; + VkSamplerYcbcrModelConversion ycbcrModel; + VkSamplerYcbcrRange ycbcrRange; + VkComponentMapping components; + VkChromaLocation xChromaOffset; + VkChromaLocation yChromaOffset; + VkFilter chromaFilter; + VkBool32 forceExplicitReconstruction; +} VkSamplerYcbcrConversionCreateInfo; + +typedef struct VkSamplerYcbcrConversionInfo { + VkStructureType sType; + const void* pNext; + VkSamplerYcbcrConversion conversion; +} VkSamplerYcbcrConversionInfo; + +typedef struct VkBindImagePlaneMemoryInfo { + VkStructureType sType; + const void* pNext; + VkImageAspectFlagBits planeAspect; +} VkBindImagePlaneMemoryInfo; + +typedef struct VkImagePlaneMemoryRequirementsInfo { + VkStructureType sType; + const void* pNext; + VkImageAspectFlagBits planeAspect; +} VkImagePlaneMemoryRequirementsInfo; + +typedef struct VkPhysicalDeviceSamplerYcbcrConversionFeatures { + VkStructureType sType; + void* pNext; + VkBool32 samplerYcbcrConversion; +} VkPhysicalDeviceSamplerYcbcrConversionFeatures; + +typedef struct VkSamplerYcbcrConversionImageFormatProperties { + VkStructureType sType; + void* pNext; + uint32_t combinedImageSamplerDescriptorCount; +} VkSamplerYcbcrConversionImageFormatProperties; + +typedef struct VkDescriptorUpdateTemplateEntry { + uint32_t dstBinding; + uint32_t dstArrayElement; + uint32_t descriptorCount; + VkDescriptorType descriptorType; + size_t offset; + size_t stride; +} VkDescriptorUpdateTemplateEntry; + +typedef struct VkDescriptorUpdateTemplateCreateInfo { + VkStructureType sType; + void* pNext; + VkDescriptorUpdateTemplateCreateFlags flags; + uint32_t descriptorUpdateEntryCount; + const VkDescriptorUpdateTemplateEntry* pDescriptorUpdateEntries; + VkDescriptorUpdateTemplateType templateType; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineBindPoint pipelineBindPoint; + VkPipelineLayout pipelineLayout; + uint32_t set; +} VkDescriptorUpdateTemplateCreateInfo; + +typedef struct VkExternalMemoryProperties { + VkExternalMemoryFeatureFlags externalMemoryFeatures; + VkExternalMemoryHandleTypeFlags exportFromImportedHandleTypes; + VkExternalMemoryHandleTypeFlags compatibleHandleTypes; +} VkExternalMemoryProperties; + +typedef struct VkPhysicalDeviceExternalImageFormatInfo { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagBits handleType; +} VkPhysicalDeviceExternalImageFormatInfo; + +typedef struct VkExternalImageFormatProperties { + VkStructureType sType; + void* pNext; + VkExternalMemoryProperties externalMemoryProperties; +} VkExternalImageFormatProperties; + +typedef struct VkPhysicalDeviceExternalBufferInfo { + VkStructureType sType; + const void* pNext; + VkBufferCreateFlags flags; + VkBufferUsageFlags usage; + VkExternalMemoryHandleTypeFlagBits handleType; +} VkPhysicalDeviceExternalBufferInfo; + +typedef struct VkExternalBufferProperties { + VkStructureType sType; + void* pNext; + VkExternalMemoryProperties externalMemoryProperties; +} VkExternalBufferProperties; + +typedef struct VkPhysicalDeviceIDProperties { + VkStructureType sType; + void* pNext; + uint8_t deviceUUID[VK_UUID_SIZE]; + uint8_t driverUUID[VK_UUID_SIZE]; + uint8_t deviceLUID[VK_LUID_SIZE]; + uint32_t deviceNodeMask; + VkBool32 deviceLUIDValid; +} VkPhysicalDeviceIDProperties; + +typedef struct VkExternalMemoryImageCreateInfo { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlags handleTypes; +} VkExternalMemoryImageCreateInfo; + +typedef struct VkExternalMemoryBufferCreateInfo { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlags handleTypes; +} VkExternalMemoryBufferCreateInfo; + +typedef struct VkExportMemoryAllocateInfo { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlags handleTypes; +} VkExportMemoryAllocateInfo; + +typedef struct VkPhysicalDeviceExternalFenceInfo { + VkStructureType sType; + const void* pNext; + VkExternalFenceHandleTypeFlagBits handleType; +} VkPhysicalDeviceExternalFenceInfo; + +typedef struct VkExternalFenceProperties { + VkStructureType sType; + void* pNext; + VkExternalFenceHandleTypeFlags exportFromImportedHandleTypes; + VkExternalFenceHandleTypeFlags compatibleHandleTypes; + VkExternalFenceFeatureFlags externalFenceFeatures; +} VkExternalFenceProperties; + +typedef struct VkExportFenceCreateInfo { + VkStructureType sType; + const void* pNext; + VkExternalFenceHandleTypeFlags handleTypes; +} VkExportFenceCreateInfo; + +typedef struct VkExportSemaphoreCreateInfo { + VkStructureType sType; + const void* pNext; + VkExternalSemaphoreHandleTypeFlags handleTypes; +} VkExportSemaphoreCreateInfo; + +typedef struct VkPhysicalDeviceExternalSemaphoreInfo { + VkStructureType sType; + const void* pNext; + VkExternalSemaphoreHandleTypeFlagBits handleType; +} VkPhysicalDeviceExternalSemaphoreInfo; + +typedef struct VkExternalSemaphoreProperties { + VkStructureType sType; + void* pNext; + VkExternalSemaphoreHandleTypeFlags exportFromImportedHandleTypes; + VkExternalSemaphoreHandleTypeFlags compatibleHandleTypes; + VkExternalSemaphoreFeatureFlags externalSemaphoreFeatures; +} VkExternalSemaphoreProperties; + +typedef struct VkPhysicalDeviceMaintenance3Properties { + VkStructureType sType; + void* pNext; + uint32_t maxPerSetDescriptors; + VkDeviceSize maxMemoryAllocationSize; +} VkPhysicalDeviceMaintenance3Properties; + +typedef struct VkDescriptorSetLayoutSupport { + VkStructureType sType; + void* pNext; + VkBool32 supported; +} VkDescriptorSetLayoutSupport; + +typedef struct VkPhysicalDeviceShaderDrawParameterFeatures { + VkStructureType sType; + void* pNext; + VkBool32 shaderDrawParameters; +} VkPhysicalDeviceShaderDrawParameterFeatures; + + +typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceVersion)(uint32_t* pApiVersion); +typedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory2)(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos); +typedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory2)(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); +typedef void (VKAPI_PTR *PFN_vkGetDeviceGroupPeerMemoryFeatures)(VkDevice device, uint32_t heapIndex, uint32_t localDeviceIndex, uint32_t remoteDeviceIndex, VkPeerMemoryFeatureFlags* pPeerMemoryFeatures); +typedef void (VKAPI_PTR *PFN_vkCmdSetDeviceMask)(VkCommandBuffer commandBuffer, uint32_t deviceMask); +typedef void (VKAPI_PTR *PFN_vkCmdDispatchBase)(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ); +typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDeviceGroups)(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties); +typedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements2)(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements2)(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements2)(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures2)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties2)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties2)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties2)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties2)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties2)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties2)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties); +typedef void (VKAPI_PTR *PFN_vkTrimCommandPool)(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags); +typedef void (VKAPI_PTR *PFN_vkGetDeviceQueue2)(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue); +typedef VkResult (VKAPI_PTR *PFN_vkCreateSamplerYcbcrConversion)(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion); +typedef void (VKAPI_PTR *PFN_vkDestroySamplerYcbcrConversion)(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorUpdateTemplate)(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate); +typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorUpdateTemplate)(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSetWithTemplate)(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalBufferProperties)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalFenceProperties)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, VkExternalFenceProperties* pExternalFenceProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalSemaphoreProperties)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties); +typedef void (VKAPI_PTR *PFN_vkGetDescriptorSetLayoutSupport)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayoutSupport* pSupport); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceVersion( + uint32_t* pApiVersion); + +VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2( + VkDevice device, + uint32_t bindInfoCount, + const VkBindBufferMemoryInfo* pBindInfos); + +VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2( + VkDevice device, + uint32_t bindInfoCount, + const VkBindImageMemoryInfo* pBindInfos); + +VKAPI_ATTR void VKAPI_CALL vkGetDeviceGroupPeerMemoryFeatures( + VkDevice device, + uint32_t heapIndex, + uint32_t localDeviceIndex, + uint32_t remoteDeviceIndex, + VkPeerMemoryFeatureFlags* pPeerMemoryFeatures); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetDeviceMask( + VkCommandBuffer commandBuffer, + uint32_t deviceMask); + +VKAPI_ATTR void VKAPI_CALL vkCmdDispatchBase( + VkCommandBuffer commandBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups( + VkInstance instance, + uint32_t* pPhysicalDeviceGroupCount, + VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements2( + VkDevice device, + const VkImageMemoryRequirementsInfo2* pInfo, + VkMemoryRequirements2* pMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2( + VkDevice device, + const VkBufferMemoryRequirementsInfo2* pInfo, + VkMemoryRequirements2* pMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements2( + VkDevice device, + const VkImageSparseMemoryRequirementsInfo2* pInfo, + uint32_t* pSparseMemoryRequirementCount, + VkSparseImageMemoryRequirements2* pSparseMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceFeatures2* pFeatures); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties2( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties2* pProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkFormatProperties2* pFormatProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, + VkImageFormatProperties2* pImageFormatProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties2( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties2* pQueueFamilyProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties2( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties2* pMemoryProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties2( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, + uint32_t* pPropertyCount, + VkSparseImageFormatProperties2* pProperties); + +VKAPI_ATTR void VKAPI_CALL vkTrimCommandPool( + VkDevice device, + VkCommandPool commandPool, + VkCommandPoolTrimFlags flags); + +VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue2( + VkDevice device, + const VkDeviceQueueInfo2* pQueueInfo, + VkQueue* pQueue); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateSamplerYcbcrConversion( + VkDevice device, + const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSamplerYcbcrConversion* pYcbcrConversion); + +VKAPI_ATTR void VKAPI_CALL vkDestroySamplerYcbcrConversion( + VkDevice device, + VkSamplerYcbcrConversion ycbcrConversion, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorUpdateTemplate( + VkDevice device, + const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorUpdateTemplate( + VkDevice device, + VkDescriptorUpdateTemplate descriptorUpdateTemplate, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSetWithTemplate( + VkDevice device, + VkDescriptorSet descriptorSet, + VkDescriptorUpdateTemplate descriptorUpdateTemplate, + const void* pData); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalBufferProperties( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, + VkExternalBufferProperties* pExternalBufferProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalFenceProperties( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, + VkExternalFenceProperties* pExternalFenceProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalSemaphoreProperties( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, + VkExternalSemaphoreProperties* pExternalSemaphoreProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetDescriptorSetLayoutSupport( + VkDevice device, + const VkDescriptorSetLayoutCreateInfo* pCreateInfo, + VkDescriptorSetLayoutSupport* pSupport); +#endif + +#define VK_KHR_surface 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR) + +#define VK_KHR_SURFACE_SPEC_VERSION 25 +#define VK_KHR_SURFACE_EXTENSION_NAME "VK_KHR_surface" +#define VK_COLORSPACE_SRGB_NONLINEAR_KHR VK_COLOR_SPACE_SRGB_NONLINEAR_KHR + + +typedef enum VkColorSpaceKHR { + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR = 0, + VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT = 1000104001, + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT = 1000104002, + VK_COLOR_SPACE_DCI_P3_LINEAR_EXT = 1000104003, + VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT = 1000104004, + VK_COLOR_SPACE_BT709_LINEAR_EXT = 1000104005, + VK_COLOR_SPACE_BT709_NONLINEAR_EXT = 1000104006, + VK_COLOR_SPACE_BT2020_LINEAR_EXT = 1000104007, + VK_COLOR_SPACE_HDR10_ST2084_EXT = 1000104008, + VK_COLOR_SPACE_DOLBYVISION_EXT = 1000104009, + VK_COLOR_SPACE_HDR10_HLG_EXT = 1000104010, + VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT = 1000104011, + VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT = 1000104012, + VK_COLOR_SPACE_PASS_THROUGH_EXT = 1000104013, + VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT = 1000104014, + VK_COLOR_SPACE_BEGIN_RANGE_KHR = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + VK_COLOR_SPACE_END_RANGE_KHR = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + VK_COLOR_SPACE_RANGE_SIZE_KHR = (VK_COLOR_SPACE_SRGB_NONLINEAR_KHR - VK_COLOR_SPACE_SRGB_NONLINEAR_KHR + 1), + VK_COLOR_SPACE_MAX_ENUM_KHR = 0x7FFFFFFF +} VkColorSpaceKHR; + +typedef enum VkPresentModeKHR { + VK_PRESENT_MODE_IMMEDIATE_KHR = 0, + VK_PRESENT_MODE_MAILBOX_KHR = 1, + VK_PRESENT_MODE_FIFO_KHR = 2, + VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3, + VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR = 1000111000, + VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR = 1000111001, + VK_PRESENT_MODE_BEGIN_RANGE_KHR = VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_END_RANGE_KHR = VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_RANGE_SIZE_KHR = (VK_PRESENT_MODE_FIFO_RELAXED_KHR - VK_PRESENT_MODE_IMMEDIATE_KHR + 1), + VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF +} VkPresentModeKHR; + + +typedef enum VkSurfaceTransformFlagBitsKHR { + VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001, + VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002, + VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004, + VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR = 0x00000008, + VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR = 0x00000010, + VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR = 0x00000020, + VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR = 0x00000040, + VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR = 0x00000080, + VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR = 0x00000100, + VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF +} VkSurfaceTransformFlagBitsKHR; +typedef VkFlags VkSurfaceTransformFlagsKHR; + +typedef enum VkCompositeAlphaFlagBitsKHR { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR = 0x00000001, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR = 0x00000002, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR = 0x00000004, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR = 0x00000008, + VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF +} VkCompositeAlphaFlagBitsKHR; +typedef VkFlags VkCompositeAlphaFlagsKHR; + +typedef struct VkSurfaceCapabilitiesKHR { + uint32_t minImageCount; + uint32_t maxImageCount; + VkExtent2D currentExtent; + VkExtent2D minImageExtent; + VkExtent2D maxImageExtent; + uint32_t maxImageArrayLayers; + VkSurfaceTransformFlagsKHR supportedTransforms; + VkSurfaceTransformFlagBitsKHR currentTransform; + VkCompositeAlphaFlagsKHR supportedCompositeAlpha; + VkImageUsageFlags supportedUsageFlags; +} VkSurfaceCapabilitiesKHR; + +typedef struct VkSurfaceFormatKHR { + VkFormat format; + VkColorSpaceKHR colorSpace; +} VkSurfaceFormatKHR; + + +typedef void (VKAPI_PTR *PFN_vkDestroySurfaceKHR)(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceFormatsKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfacePresentModesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR( + VkInstance instance, + VkSurfaceKHR surface, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceSupportKHR( + VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + VkSurfaceKHR surface, + VkBool32* pSupported); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR* pSurfaceCapabilities); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + uint32_t* pSurfaceFormatCount, + VkSurfaceFormatKHR* pSurfaceFormats); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + uint32_t* pPresentModeCount, + VkPresentModeKHR* pPresentModes); +#endif + +#define VK_KHR_swapchain 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSwapchainKHR) + +#define VK_KHR_SWAPCHAIN_SPEC_VERSION 70 +#define VK_KHR_SWAPCHAIN_EXTENSION_NAME "VK_KHR_swapchain" + + +typedef enum VkSwapchainCreateFlagBitsKHR { + VK_SWAPCHAIN_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR = 0x00000001, + VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR = 0x00000002, + VK_SWAPCHAIN_CREATE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF +} VkSwapchainCreateFlagBitsKHR; +typedef VkFlags VkSwapchainCreateFlagsKHR; + +typedef enum VkDeviceGroupPresentModeFlagBitsKHR { + VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR = 0x00000001, + VK_DEVICE_GROUP_PRESENT_MODE_REMOTE_BIT_KHR = 0x00000002, + VK_DEVICE_GROUP_PRESENT_MODE_SUM_BIT_KHR = 0x00000004, + VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_MULTI_DEVICE_BIT_KHR = 0x00000008, + VK_DEVICE_GROUP_PRESENT_MODE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF +} VkDeviceGroupPresentModeFlagBitsKHR; +typedef VkFlags VkDeviceGroupPresentModeFlagsKHR; + +typedef struct VkSwapchainCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkSwapchainCreateFlagsKHR flags; + VkSurfaceKHR surface; + uint32_t minImageCount; + VkFormat imageFormat; + VkColorSpaceKHR imageColorSpace; + VkExtent2D imageExtent; + uint32_t imageArrayLayers; + VkImageUsageFlags imageUsage; + VkSharingMode imageSharingMode; + uint32_t queueFamilyIndexCount; + const uint32_t* pQueueFamilyIndices; + VkSurfaceTransformFlagBitsKHR preTransform; + VkCompositeAlphaFlagBitsKHR compositeAlpha; + VkPresentModeKHR presentMode; + VkBool32 clipped; + VkSwapchainKHR oldSwapchain; +} VkSwapchainCreateInfoKHR; + +typedef struct VkPresentInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t waitSemaphoreCount; + const VkSemaphore* pWaitSemaphores; + uint32_t swapchainCount; + const VkSwapchainKHR* pSwapchains; + const uint32_t* pImageIndices; + VkResult* pResults; +} VkPresentInfoKHR; + +typedef struct VkImageSwapchainCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkSwapchainKHR swapchain; +} VkImageSwapchainCreateInfoKHR; + +typedef struct VkBindImageMemorySwapchainInfoKHR { + VkStructureType sType; + const void* pNext; + VkSwapchainKHR swapchain; + uint32_t imageIndex; +} VkBindImageMemorySwapchainInfoKHR; + +typedef struct VkAcquireNextImageInfoKHR { + VkStructureType sType; + const void* pNext; + VkSwapchainKHR swapchain; + uint64_t timeout; + VkSemaphore semaphore; + VkFence fence; + uint32_t deviceMask; +} VkAcquireNextImageInfoKHR; + +typedef struct VkDeviceGroupPresentCapabilitiesKHR { + VkStructureType sType; + const void* pNext; + uint32_t presentMask[VK_MAX_DEVICE_GROUP_SIZE]; + VkDeviceGroupPresentModeFlagsKHR modes; +} VkDeviceGroupPresentCapabilitiesKHR; + +typedef struct VkDeviceGroupPresentInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t swapchainCount; + const uint32_t* pDeviceMasks; + VkDeviceGroupPresentModeFlagBitsKHR mode; +} VkDeviceGroupPresentInfoKHR; + +typedef struct VkDeviceGroupSwapchainCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceGroupPresentModeFlagsKHR modes; +} VkDeviceGroupSwapchainCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateSwapchainKHR)(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain); +typedef void (VKAPI_PTR *PFN_vkDestroySwapchainKHR)(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainImagesKHR)(VkDevice device, VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages); +typedef VkResult (VKAPI_PTR *PFN_vkAcquireNextImageKHR)(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex); +typedef VkResult (VKAPI_PTR *PFN_vkQueuePresentKHR)(VkQueue queue, const VkPresentInfoKHR* pPresentInfo); +typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceGroupPresentCapabilitiesKHR)(VkDevice device, VkDeviceGroupPresentCapabilitiesKHR* pDeviceGroupPresentCapabilities); +typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceGroupSurfacePresentModesKHR)(VkDevice device, VkSurfaceKHR surface, VkDeviceGroupPresentModeFlagsKHR* pModes); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDevicePresentRectanglesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pRectCount, VkRect2D* pRects); +typedef VkResult (VKAPI_PTR *PFN_vkAcquireNextImage2KHR)(VkDevice device, const VkAcquireNextImageInfoKHR* pAcquireInfo, uint32_t* pImageIndex); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain); + +VKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainImagesKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pSwapchainImageCount, + VkImage* pSwapchainImages); + +VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t* pImageIndex); + +VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupPresentCapabilitiesKHR( + VkDevice device, + VkDeviceGroupPresentCapabilitiesKHR* pDeviceGroupPresentCapabilities); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupSurfacePresentModesKHR( + VkDevice device, + VkSurfaceKHR surface, + VkDeviceGroupPresentModeFlagsKHR* pModes); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDevicePresentRectanglesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + uint32_t* pRectCount, + VkRect2D* pRects); + +VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImage2KHR( + VkDevice device, + const VkAcquireNextImageInfoKHR* pAcquireInfo, + uint32_t* pImageIndex); +#endif + +#define VK_KHR_display 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDisplayKHR) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDisplayModeKHR) + +#define VK_KHR_DISPLAY_SPEC_VERSION 21 +#define VK_KHR_DISPLAY_EXTENSION_NAME "VK_KHR_display" + + +typedef enum VkDisplayPlaneAlphaFlagBitsKHR { + VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR = 0x00000001, + VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR = 0x00000002, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR = 0x00000004, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR = 0x00000008, + VK_DISPLAY_PLANE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF +} VkDisplayPlaneAlphaFlagBitsKHR; +typedef VkFlags VkDisplayPlaneAlphaFlagsKHR; +typedef VkFlags VkDisplayModeCreateFlagsKHR; +typedef VkFlags VkDisplaySurfaceCreateFlagsKHR; + +typedef struct VkDisplayPropertiesKHR { + VkDisplayKHR display; + const char* displayName; + VkExtent2D physicalDimensions; + VkExtent2D physicalResolution; + VkSurfaceTransformFlagsKHR supportedTransforms; + VkBool32 planeReorderPossible; + VkBool32 persistentContent; +} VkDisplayPropertiesKHR; + +typedef struct VkDisplayModeParametersKHR { + VkExtent2D visibleRegion; + uint32_t refreshRate; +} VkDisplayModeParametersKHR; + +typedef struct VkDisplayModePropertiesKHR { + VkDisplayModeKHR displayMode; + VkDisplayModeParametersKHR parameters; +} VkDisplayModePropertiesKHR; + +typedef struct VkDisplayModeCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkDisplayModeCreateFlagsKHR flags; + VkDisplayModeParametersKHR parameters; +} VkDisplayModeCreateInfoKHR; + +typedef struct VkDisplayPlaneCapabilitiesKHR { + VkDisplayPlaneAlphaFlagsKHR supportedAlpha; + VkOffset2D minSrcPosition; + VkOffset2D maxSrcPosition; + VkExtent2D minSrcExtent; + VkExtent2D maxSrcExtent; + VkOffset2D minDstPosition; + VkOffset2D maxDstPosition; + VkExtent2D minDstExtent; + VkExtent2D maxDstExtent; +} VkDisplayPlaneCapabilitiesKHR; + +typedef struct VkDisplayPlanePropertiesKHR { + VkDisplayKHR currentDisplay; + uint32_t currentStackIndex; +} VkDisplayPlanePropertiesKHR; + +typedef struct VkDisplaySurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkDisplaySurfaceCreateFlagsKHR flags; + VkDisplayModeKHR displayMode; + uint32_t planeIndex; + uint32_t planeStackIndex; + VkSurfaceTransformFlagBitsKHR transform; + float globalAlpha; + VkDisplayPlaneAlphaFlagBitsKHR alphaMode; + VkExtent2D imageExtent; +} VkDisplaySurfaceCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPropertiesKHR* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlanePropertiesKHR* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneSupportedDisplaysKHR)(VkPhysicalDevice physicalDevice, uint32_t planeIndex, uint32_t* pDisplayCount, VkDisplayKHR* pDisplays); +typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayModePropertiesKHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t* pPropertyCount, VkDisplayModePropertiesKHR* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDisplayModeKHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, const VkDisplayModeCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDisplayModeKHR* pMode); +typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneCapabilitiesKHR)(VkPhysicalDevice physicalDevice, VkDisplayModeKHR mode, uint32_t planeIndex, VkDisplayPlaneCapabilitiesKHR* pCapabilities); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDisplayPlaneSurfaceKHR)(VkInstance instance, const VkDisplaySurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPropertiesKHR( + VkPhysicalDevice physicalDevice, + uint32_t* pPropertyCount, + VkDisplayPropertiesKHR* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPlanePropertiesKHR( + VkPhysicalDevice physicalDevice, + uint32_t* pPropertyCount, + VkDisplayPlanePropertiesKHR* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneSupportedDisplaysKHR( + VkPhysicalDevice physicalDevice, + uint32_t planeIndex, + uint32_t* pDisplayCount, + VkDisplayKHR* pDisplays); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayModePropertiesKHR( + VkPhysicalDevice physicalDevice, + VkDisplayKHR display, + uint32_t* pPropertyCount, + VkDisplayModePropertiesKHR* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDisplayModeKHR( + VkPhysicalDevice physicalDevice, + VkDisplayKHR display, + const VkDisplayModeCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDisplayModeKHR* pMode); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneCapabilitiesKHR( + VkPhysicalDevice physicalDevice, + VkDisplayModeKHR mode, + uint32_t planeIndex, + VkDisplayPlaneCapabilitiesKHR* pCapabilities); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDisplayPlaneSurfaceKHR( + VkInstance instance, + const VkDisplaySurfaceCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface); +#endif + +#define VK_KHR_display_swapchain 1 +#define VK_KHR_DISPLAY_SWAPCHAIN_SPEC_VERSION 9 +#define VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME "VK_KHR_display_swapchain" + +typedef struct VkDisplayPresentInfoKHR { + VkStructureType sType; + const void* pNext; + VkRect2D srcRect; + VkRect2D dstRect; + VkBool32 persistent; +} VkDisplayPresentInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateSharedSwapchainsKHR)(VkDevice device, uint32_t swapchainCount, const VkSwapchainCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchains); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateSharedSwapchainsKHR( + VkDevice device, + uint32_t swapchainCount, + const VkSwapchainCreateInfoKHR* pCreateInfos, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchains); +#endif + +#define VK_KHR_sampler_mirror_clamp_to_edge 1 +#define VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_SPEC_VERSION 1 +#define VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME "VK_KHR_sampler_mirror_clamp_to_edge" + + +#define VK_KHR_multiview 1 +#define VK_KHR_MULTIVIEW_SPEC_VERSION 1 +#define VK_KHR_MULTIVIEW_EXTENSION_NAME "VK_KHR_multiview" + +typedef VkRenderPassMultiviewCreateInfo VkRenderPassMultiviewCreateInfoKHR; + +typedef VkPhysicalDeviceMultiviewFeatures VkPhysicalDeviceMultiviewFeaturesKHR; + +typedef VkPhysicalDeviceMultiviewProperties VkPhysicalDeviceMultiviewPropertiesKHR; + + + +#define VK_KHR_get_physical_device_properties2 1 +#define VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_SPEC_VERSION 1 +#define VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME "VK_KHR_get_physical_device_properties2" + +typedef VkPhysicalDeviceFeatures2 VkPhysicalDeviceFeatures2KHR; + +typedef VkPhysicalDeviceProperties2 VkPhysicalDeviceProperties2KHR; + +typedef VkFormatProperties2 VkFormatProperties2KHR; + +typedef VkImageFormatProperties2 VkImageFormatProperties2KHR; + +typedef VkPhysicalDeviceImageFormatInfo2 VkPhysicalDeviceImageFormatInfo2KHR; + +typedef VkQueueFamilyProperties2 VkQueueFamilyProperties2KHR; + +typedef VkPhysicalDeviceMemoryProperties2 VkPhysicalDeviceMemoryProperties2KHR; + +typedef VkSparseImageFormatProperties2 VkSparseImageFormatProperties2KHR; + +typedef VkPhysicalDeviceSparseImageFormatInfo2 VkPhysicalDeviceSparseImageFormatInfo2KHR; + + +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures2KHR)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties2KHR)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties2KHR)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties2KHR)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2KHR( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceFeatures2* pFeatures); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties2KHR( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties2* pProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2KHR( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkFormatProperties2* pFormatProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, + VkImageFormatProperties2* pImageFormatProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties2KHR( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties2* pQueueFamilyProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties2KHR( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties2* pMemoryProperties); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, + uint32_t* pPropertyCount, + VkSparseImageFormatProperties2* pProperties); +#endif + +#define VK_KHR_device_group 1 +#define VK_KHR_DEVICE_GROUP_SPEC_VERSION 3 +#define VK_KHR_DEVICE_GROUP_EXTENSION_NAME "VK_KHR_device_group" + +typedef VkPeerMemoryFeatureFlags VkPeerMemoryFeatureFlagsKHR; + +typedef VkPeerMemoryFeatureFlagBits VkPeerMemoryFeatureFlagBitsKHR; + +typedef VkMemoryAllocateFlags VkMemoryAllocateFlagsKHR; + +typedef VkMemoryAllocateFlagBits VkMemoryAllocateFlagBitsKHR; + + +typedef VkMemoryAllocateFlagsInfo VkMemoryAllocateFlagsInfoKHR; + +typedef VkDeviceGroupRenderPassBeginInfo VkDeviceGroupRenderPassBeginInfoKHR; + +typedef VkDeviceGroupCommandBufferBeginInfo VkDeviceGroupCommandBufferBeginInfoKHR; + +typedef VkDeviceGroupSubmitInfo VkDeviceGroupSubmitInfoKHR; + +typedef VkDeviceGroupBindSparseInfo VkDeviceGroupBindSparseInfoKHR; + +typedef VkBindBufferMemoryDeviceGroupInfo VkBindBufferMemoryDeviceGroupInfoKHR; + +typedef VkBindImageMemoryDeviceGroupInfo VkBindImageMemoryDeviceGroupInfoKHR; + + +typedef void (VKAPI_PTR *PFN_vkGetDeviceGroupPeerMemoryFeaturesKHR)(VkDevice device, uint32_t heapIndex, uint32_t localDeviceIndex, uint32_t remoteDeviceIndex, VkPeerMemoryFeatureFlags* pPeerMemoryFeatures); +typedef void (VKAPI_PTR *PFN_vkCmdSetDeviceMaskKHR)(VkCommandBuffer commandBuffer, uint32_t deviceMask); +typedef void (VKAPI_PTR *PFN_vkCmdDispatchBaseKHR)(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetDeviceGroupPeerMemoryFeaturesKHR( + VkDevice device, + uint32_t heapIndex, + uint32_t localDeviceIndex, + uint32_t remoteDeviceIndex, + VkPeerMemoryFeatureFlags* pPeerMemoryFeatures); + +VKAPI_ATTR void VKAPI_CALL vkCmdSetDeviceMaskKHR( + VkCommandBuffer commandBuffer, + uint32_t deviceMask); + +VKAPI_ATTR void VKAPI_CALL vkCmdDispatchBaseKHR( + VkCommandBuffer commandBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); +#endif + +#define VK_KHR_shader_draw_parameters 1 +#define VK_KHR_SHADER_DRAW_PARAMETERS_SPEC_VERSION 1 +#define VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME "VK_KHR_shader_draw_parameters" + + +#define VK_KHR_maintenance1 1 +#define VK_KHR_MAINTENANCE1_SPEC_VERSION 2 +#define VK_KHR_MAINTENANCE1_EXTENSION_NAME "VK_KHR_maintenance1" + +typedef VkCommandPoolTrimFlags VkCommandPoolTrimFlagsKHR; + + +typedef void (VKAPI_PTR *PFN_vkTrimCommandPoolKHR)(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkTrimCommandPoolKHR( + VkDevice device, + VkCommandPool commandPool, + VkCommandPoolTrimFlags flags); +#endif + +#define VK_KHR_device_group_creation 1 +#define VK_KHR_DEVICE_GROUP_CREATION_SPEC_VERSION 1 +#define VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME "VK_KHR_device_group_creation" +#define VK_MAX_DEVICE_GROUP_SIZE_KHR VK_MAX_DEVICE_GROUP_SIZE + +typedef VkPhysicalDeviceGroupProperties VkPhysicalDeviceGroupPropertiesKHR; + +typedef VkDeviceGroupDeviceCreateInfo VkDeviceGroupDeviceCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDeviceGroupsKHR)(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroupsKHR( + VkInstance instance, + uint32_t* pPhysicalDeviceGroupCount, + VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties); +#endif + +#define VK_KHR_external_memory_capabilities 1 +#define VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME "VK_KHR_external_memory_capabilities" +#define VK_LUID_SIZE_KHR VK_LUID_SIZE + +typedef VkExternalMemoryHandleTypeFlags VkExternalMemoryHandleTypeFlagsKHR; + +typedef VkExternalMemoryHandleTypeFlagBits VkExternalMemoryHandleTypeFlagBitsKHR; + +typedef VkExternalMemoryFeatureFlags VkExternalMemoryFeatureFlagsKHR; + +typedef VkExternalMemoryFeatureFlagBits VkExternalMemoryFeatureFlagBitsKHR; + + +typedef VkExternalMemoryProperties VkExternalMemoryPropertiesKHR; + +typedef VkPhysicalDeviceExternalImageFormatInfo VkPhysicalDeviceExternalImageFormatInfoKHR; + +typedef VkExternalImageFormatProperties VkExternalImageFormatPropertiesKHR; + +typedef VkPhysicalDeviceExternalBufferInfo VkPhysicalDeviceExternalBufferInfoKHR; + +typedef VkExternalBufferProperties VkExternalBufferPropertiesKHR; + +typedef VkPhysicalDeviceIDProperties VkPhysicalDeviceIDPropertiesKHR; + + +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalBufferPropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalBufferPropertiesKHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, + VkExternalBufferProperties* pExternalBufferProperties); +#endif + +#define VK_KHR_external_memory 1 +#define VK_KHR_EXTERNAL_MEMORY_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME "VK_KHR_external_memory" +#define VK_QUEUE_FAMILY_EXTERNAL_KHR VK_QUEUE_FAMILY_EXTERNAL + +typedef VkExternalMemoryImageCreateInfo VkExternalMemoryImageCreateInfoKHR; + +typedef VkExternalMemoryBufferCreateInfo VkExternalMemoryBufferCreateInfoKHR; + +typedef VkExportMemoryAllocateInfo VkExportMemoryAllocateInfoKHR; + + + +#define VK_KHR_external_memory_fd 1 +#define VK_KHR_EXTERNAL_MEMORY_FD_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME "VK_KHR_external_memory_fd" + +typedef struct VkImportMemoryFdInfoKHR { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagBits handleType; + int fd; +} VkImportMemoryFdInfoKHR; + +typedef struct VkMemoryFdPropertiesKHR { + VkStructureType sType; + void* pNext; + uint32_t memoryTypeBits; +} VkMemoryFdPropertiesKHR; + +typedef struct VkMemoryGetFdInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceMemory memory; + VkExternalMemoryHandleTypeFlagBits handleType; +} VkMemoryGetFdInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryFdKHR)(VkDevice device, const VkMemoryGetFdInfoKHR* pGetFdInfo, int* pFd); +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryFdPropertiesKHR)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, int fd, VkMemoryFdPropertiesKHR* pMemoryFdProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryFdKHR( + VkDevice device, + const VkMemoryGetFdInfoKHR* pGetFdInfo, + int* pFd); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryFdPropertiesKHR( + VkDevice device, + VkExternalMemoryHandleTypeFlagBits handleType, + int fd, + VkMemoryFdPropertiesKHR* pMemoryFdProperties); +#endif + +#define VK_KHR_external_semaphore_capabilities 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME "VK_KHR_external_semaphore_capabilities" + +typedef VkExternalSemaphoreHandleTypeFlags VkExternalSemaphoreHandleTypeFlagsKHR; + +typedef VkExternalSemaphoreHandleTypeFlagBits VkExternalSemaphoreHandleTypeFlagBitsKHR; + +typedef VkExternalSemaphoreFeatureFlags VkExternalSemaphoreFeatureFlagsKHR; + +typedef VkExternalSemaphoreFeatureFlagBits VkExternalSemaphoreFeatureFlagBitsKHR; + + +typedef VkPhysicalDeviceExternalSemaphoreInfo VkPhysicalDeviceExternalSemaphoreInfoKHR; + +typedef VkExternalSemaphoreProperties VkExternalSemaphorePropertiesKHR; + + +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalSemaphorePropertiesKHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, + VkExternalSemaphoreProperties* pExternalSemaphoreProperties); +#endif + +#define VK_KHR_external_semaphore 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME "VK_KHR_external_semaphore" + +typedef VkSemaphoreImportFlags VkSemaphoreImportFlagsKHR; + +typedef VkSemaphoreImportFlagBits VkSemaphoreImportFlagBitsKHR; + + +typedef VkExportSemaphoreCreateInfo VkExportSemaphoreCreateInfoKHR; + + + +#define VK_KHR_external_semaphore_fd 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_FD_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME "VK_KHR_external_semaphore_fd" + +typedef struct VkImportSemaphoreFdInfoKHR { + VkStructureType sType; + const void* pNext; + VkSemaphore semaphore; + VkSemaphoreImportFlags flags; + VkExternalSemaphoreHandleTypeFlagBits handleType; + int fd; +} VkImportSemaphoreFdInfoKHR; + +typedef struct VkSemaphoreGetFdInfoKHR { + VkStructureType sType; + const void* pNext; + VkSemaphore semaphore; + VkExternalSemaphoreHandleTypeFlagBits handleType; +} VkSemaphoreGetFdInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreFdKHR)(VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo); +typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreFdKHR)(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreFdKHR( + VkDevice device, + const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreFdKHR( + VkDevice device, + const VkSemaphoreGetFdInfoKHR* pGetFdInfo, + int* pFd); +#endif + +#define VK_KHR_push_descriptor 1 +#define VK_KHR_PUSH_DESCRIPTOR_SPEC_VERSION 2 +#define VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME "VK_KHR_push_descriptor" + +typedef struct VkPhysicalDevicePushDescriptorPropertiesKHR { + VkStructureType sType; + void* pNext; + uint32_t maxPushDescriptors; +} VkPhysicalDevicePushDescriptorPropertiesKHR; + + +typedef void (VKAPI_PTR *PFN_vkCmdPushDescriptorSetKHR)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites); +typedef void (VKAPI_PTR *PFN_vkCmdPushDescriptorSetWithTemplateKHR)(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdPushDescriptorSetKHR( + VkCommandBuffer commandBuffer, + VkPipelineBindPoint pipelineBindPoint, + VkPipelineLayout layout, + uint32_t set, + uint32_t descriptorWriteCount, + const VkWriteDescriptorSet* pDescriptorWrites); + +VKAPI_ATTR void VKAPI_CALL vkCmdPushDescriptorSetWithTemplateKHR( + VkCommandBuffer commandBuffer, + VkDescriptorUpdateTemplate descriptorUpdateTemplate, + VkPipelineLayout layout, + uint32_t set, + const void* pData); +#endif + +#define VK_KHR_16bit_storage 1 +#define VK_KHR_16BIT_STORAGE_SPEC_VERSION 1 +#define VK_KHR_16BIT_STORAGE_EXTENSION_NAME "VK_KHR_16bit_storage" + +typedef VkPhysicalDevice16BitStorageFeatures VkPhysicalDevice16BitStorageFeaturesKHR; + + + +#define VK_KHR_incremental_present 1 +#define VK_KHR_INCREMENTAL_PRESENT_SPEC_VERSION 1 +#define VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME "VK_KHR_incremental_present" + +typedef struct VkRectLayerKHR { + VkOffset2D offset; + VkExtent2D extent; + uint32_t layer; +} VkRectLayerKHR; + +typedef struct VkPresentRegionKHR { + uint32_t rectangleCount; + const VkRectLayerKHR* pRectangles; +} VkPresentRegionKHR; + +typedef struct VkPresentRegionsKHR { + VkStructureType sType; + const void* pNext; + uint32_t swapchainCount; + const VkPresentRegionKHR* pRegions; +} VkPresentRegionsKHR; + + + +#define VK_KHR_descriptor_update_template 1 +typedef VkDescriptorUpdateTemplate VkDescriptorUpdateTemplateKHR; + + +#define VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_SPEC_VERSION 1 +#define VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME "VK_KHR_descriptor_update_template" + +typedef VkDescriptorUpdateTemplateType VkDescriptorUpdateTemplateTypeKHR; + + +typedef VkDescriptorUpdateTemplateCreateFlags VkDescriptorUpdateTemplateCreateFlagsKHR; + + +typedef VkDescriptorUpdateTemplateEntry VkDescriptorUpdateTemplateEntryKHR; + +typedef VkDescriptorUpdateTemplateCreateInfo VkDescriptorUpdateTemplateCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorUpdateTemplateKHR)(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate); +typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorUpdateTemplateKHR)(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSetWithTemplateKHR)(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorUpdateTemplateKHR( + VkDevice device, + const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorUpdateTemplateKHR( + VkDevice device, + VkDescriptorUpdateTemplate descriptorUpdateTemplate, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSetWithTemplateKHR( + VkDevice device, + VkDescriptorSet descriptorSet, + VkDescriptorUpdateTemplate descriptorUpdateTemplate, + const void* pData); +#endif + +#define VK_KHR_shared_presentable_image 1 +#define VK_KHR_SHARED_PRESENTABLE_IMAGE_SPEC_VERSION 1 +#define VK_KHR_SHARED_PRESENTABLE_IMAGE_EXTENSION_NAME "VK_KHR_shared_presentable_image" + +typedef struct VkSharedPresentSurfaceCapabilitiesKHR { + VkStructureType sType; + void* pNext; + VkImageUsageFlags sharedPresentSupportedUsageFlags; +} VkSharedPresentSurfaceCapabilitiesKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainStatusKHR)(VkDevice device, VkSwapchainKHR swapchain); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainStatusKHR( + VkDevice device, + VkSwapchainKHR swapchain); +#endif + +#define VK_KHR_external_fence_capabilities 1 +#define VK_KHR_EXTERNAL_FENCE_CAPABILITIES_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME "VK_KHR_external_fence_capabilities" + +typedef VkExternalFenceHandleTypeFlags VkExternalFenceHandleTypeFlagsKHR; + +typedef VkExternalFenceHandleTypeFlagBits VkExternalFenceHandleTypeFlagBitsKHR; + +typedef VkExternalFenceFeatureFlags VkExternalFenceFeatureFlagsKHR; + +typedef VkExternalFenceFeatureFlagBits VkExternalFenceFeatureFlagBitsKHR; + + +typedef VkPhysicalDeviceExternalFenceInfo VkPhysicalDeviceExternalFenceInfoKHR; + +typedef VkExternalFenceProperties VkExternalFencePropertiesKHR; + + +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalFencePropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, VkExternalFenceProperties* pExternalFenceProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalFencePropertiesKHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, + VkExternalFenceProperties* pExternalFenceProperties); +#endif + +#define VK_KHR_external_fence 1 +#define VK_KHR_EXTERNAL_FENCE_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME "VK_KHR_external_fence" + +typedef VkFenceImportFlags VkFenceImportFlagsKHR; + +typedef VkFenceImportFlagBits VkFenceImportFlagBitsKHR; + + +typedef VkExportFenceCreateInfo VkExportFenceCreateInfoKHR; + + + +#define VK_KHR_external_fence_fd 1 +#define VK_KHR_EXTERNAL_FENCE_FD_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME "VK_KHR_external_fence_fd" + +typedef struct VkImportFenceFdInfoKHR { + VkStructureType sType; + const void* pNext; + VkFence fence; + VkFenceImportFlags flags; + VkExternalFenceHandleTypeFlagBits handleType; + int fd; +} VkImportFenceFdInfoKHR; + +typedef struct VkFenceGetFdInfoKHR { + VkStructureType sType; + const void* pNext; + VkFence fence; + VkExternalFenceHandleTypeFlagBits handleType; +} VkFenceGetFdInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkImportFenceFdKHR)(VkDevice device, const VkImportFenceFdInfoKHR* pImportFenceFdInfo); +typedef VkResult (VKAPI_PTR *PFN_vkGetFenceFdKHR)(VkDevice device, const VkFenceGetFdInfoKHR* pGetFdInfo, int* pFd); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkImportFenceFdKHR( + VkDevice device, + const VkImportFenceFdInfoKHR* pImportFenceFdInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceFdKHR( + VkDevice device, + const VkFenceGetFdInfoKHR* pGetFdInfo, + int* pFd); +#endif + +#define VK_KHR_maintenance2 1 +#define VK_KHR_MAINTENANCE2_SPEC_VERSION 1 +#define VK_KHR_MAINTENANCE2_EXTENSION_NAME "VK_KHR_maintenance2" + +typedef VkPointClippingBehavior VkPointClippingBehaviorKHR; + +typedef VkTessellationDomainOrigin VkTessellationDomainOriginKHR; + + +typedef VkPhysicalDevicePointClippingProperties VkPhysicalDevicePointClippingPropertiesKHR; + +typedef VkRenderPassInputAttachmentAspectCreateInfo VkRenderPassInputAttachmentAspectCreateInfoKHR; + +typedef VkInputAttachmentAspectReference VkInputAttachmentAspectReferenceKHR; + +typedef VkImageViewUsageCreateInfo VkImageViewUsageCreateInfoKHR; + +typedef VkPipelineTessellationDomainOriginStateCreateInfo VkPipelineTessellationDomainOriginStateCreateInfoKHR; + + + +#define VK_KHR_get_surface_capabilities2 1 +#define VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION 1 +#define VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME "VK_KHR_get_surface_capabilities2" + +typedef struct VkPhysicalDeviceSurfaceInfo2KHR { + VkStructureType sType; + const void* pNext; + VkSurfaceKHR surface; +} VkPhysicalDeviceSurfaceInfo2KHR; + +typedef struct VkSurfaceCapabilities2KHR { + VkStructureType sType; + void* pNext; + VkSurfaceCapabilitiesKHR surfaceCapabilities; +} VkSurfaceCapabilities2KHR; + +typedef struct VkSurfaceFormat2KHR { + VkStructureType sType; + void* pNext; + VkSurfaceFormatKHR surfaceFormat; +} VkSurfaceFormat2KHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, VkSurfaceCapabilities2KHR* pSurfaceCapabilities); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceFormats2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilities2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + VkSurfaceCapabilities2KHR* pSurfaceCapabilities); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormats2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + uint32_t* pSurfaceFormatCount, + VkSurfaceFormat2KHR* pSurfaceFormats); +#endif + +#define VK_KHR_variable_pointers 1 +#define VK_KHR_VARIABLE_POINTERS_SPEC_VERSION 1 +#define VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME "VK_KHR_variable_pointers" + +typedef VkPhysicalDeviceVariablePointerFeatures VkPhysicalDeviceVariablePointerFeaturesKHR; + + + +#define VK_KHR_get_display_properties2 1 +#define VK_KHR_GET_DISPLAY_PROPERTIES_2_SPEC_VERSION 1 +#define VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME "VK_KHR_get_display_properties2" + +typedef struct VkDisplayProperties2KHR { + VkStructureType sType; + void* pNext; + VkDisplayPropertiesKHR displayProperties; +} VkDisplayProperties2KHR; + +typedef struct VkDisplayPlaneProperties2KHR { + VkStructureType sType; + void* pNext; + VkDisplayPlanePropertiesKHR displayPlaneProperties; +} VkDisplayPlaneProperties2KHR; + +typedef struct VkDisplayModeProperties2KHR { + VkStructureType sType; + void* pNext; + VkDisplayModePropertiesKHR displayModeProperties; +} VkDisplayModeProperties2KHR; + +typedef struct VkDisplayPlaneInfo2KHR { + VkStructureType sType; + const void* pNext; + VkDisplayModeKHR mode; + uint32_t planeIndex; +} VkDisplayPlaneInfo2KHR; + +typedef struct VkDisplayPlaneCapabilities2KHR { + VkStructureType sType; + void* pNext; + VkDisplayPlaneCapabilitiesKHR capabilities; +} VkDisplayPlaneCapabilities2KHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayProperties2KHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayProperties2KHR* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlaneProperties2KHR* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayModeProperties2KHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t* pPropertyCount, VkDisplayModeProperties2KHR* pProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneCapabilities2KHR)(VkPhysicalDevice physicalDevice, const VkDisplayPlaneInfo2KHR* pDisplayPlaneInfo, VkDisplayPlaneCapabilities2KHR* pCapabilities); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayProperties2KHR( + VkPhysicalDevice physicalDevice, + uint32_t* pPropertyCount, + VkDisplayProperties2KHR* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPlaneProperties2KHR( + VkPhysicalDevice physicalDevice, + uint32_t* pPropertyCount, + VkDisplayPlaneProperties2KHR* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayModeProperties2KHR( + VkPhysicalDevice physicalDevice, + VkDisplayKHR display, + uint32_t* pPropertyCount, + VkDisplayModeProperties2KHR* pProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneCapabilities2KHR( + VkPhysicalDevice physicalDevice, + const VkDisplayPlaneInfo2KHR* pDisplayPlaneInfo, + VkDisplayPlaneCapabilities2KHR* pCapabilities); +#endif + +#define VK_KHR_dedicated_allocation 1 +#define VK_KHR_DEDICATED_ALLOCATION_SPEC_VERSION 3 +#define VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME "VK_KHR_dedicated_allocation" + +typedef VkMemoryDedicatedRequirements VkMemoryDedicatedRequirementsKHR; + +typedef VkMemoryDedicatedAllocateInfo VkMemoryDedicatedAllocateInfoKHR; + + + +#define VK_KHR_storage_buffer_storage_class 1 +#define VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_SPEC_VERSION 1 +#define VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME "VK_KHR_storage_buffer_storage_class" + + +#define VK_KHR_relaxed_block_layout 1 +#define VK_KHR_RELAXED_BLOCK_LAYOUT_SPEC_VERSION 1 +#define VK_KHR_RELAXED_BLOCK_LAYOUT_EXTENSION_NAME "VK_KHR_relaxed_block_layout" + + +#define VK_KHR_get_memory_requirements2 1 +#define VK_KHR_GET_MEMORY_REQUIREMENTS_2_SPEC_VERSION 1 +#define VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME "VK_KHR_get_memory_requirements2" + +typedef VkBufferMemoryRequirementsInfo2 VkBufferMemoryRequirementsInfo2KHR; + +typedef VkImageMemoryRequirementsInfo2 VkImageMemoryRequirementsInfo2KHR; + +typedef VkImageSparseMemoryRequirementsInfo2 VkImageSparseMemoryRequirementsInfo2KHR; + +typedef VkMemoryRequirements2 VkMemoryRequirements2KHR; + +typedef VkSparseImageMemoryRequirements2 VkSparseImageMemoryRequirements2KHR; + + +typedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements2KHR)(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements2KHR)(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements); +typedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements2KHR)(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements2KHR( + VkDevice device, + const VkImageMemoryRequirementsInfo2* pInfo, + VkMemoryRequirements2* pMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2KHR( + VkDevice device, + const VkBufferMemoryRequirementsInfo2* pInfo, + VkMemoryRequirements2* pMemoryRequirements); + +VKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements2KHR( + VkDevice device, + const VkImageSparseMemoryRequirementsInfo2* pInfo, + uint32_t* pSparseMemoryRequirementCount, + VkSparseImageMemoryRequirements2* pSparseMemoryRequirements); +#endif + +#define VK_KHR_image_format_list 1 +#define VK_KHR_IMAGE_FORMAT_LIST_SPEC_VERSION 1 +#define VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME "VK_KHR_image_format_list" + +typedef struct VkImageFormatListCreateInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t viewFormatCount; + const VkFormat* pViewFormats; +} VkImageFormatListCreateInfoKHR; + + + +#define VK_KHR_sampler_ycbcr_conversion 1 +typedef VkSamplerYcbcrConversion VkSamplerYcbcrConversionKHR; + + +#define VK_KHR_SAMPLER_YCBCR_CONVERSION_SPEC_VERSION 1 +#define VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME "VK_KHR_sampler_ycbcr_conversion" + +typedef VkSamplerYcbcrModelConversion VkSamplerYcbcrModelConversionKHR; + +typedef VkSamplerYcbcrRange VkSamplerYcbcrRangeKHR; + +typedef VkChromaLocation VkChromaLocationKHR; + + +typedef VkSamplerYcbcrConversionCreateInfo VkSamplerYcbcrConversionCreateInfoKHR; + +typedef VkSamplerYcbcrConversionInfo VkSamplerYcbcrConversionInfoKHR; + +typedef VkBindImagePlaneMemoryInfo VkBindImagePlaneMemoryInfoKHR; + +typedef VkImagePlaneMemoryRequirementsInfo VkImagePlaneMemoryRequirementsInfoKHR; + +typedef VkPhysicalDeviceSamplerYcbcrConversionFeatures VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR; + +typedef VkSamplerYcbcrConversionImageFormatProperties VkSamplerYcbcrConversionImageFormatPropertiesKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateSamplerYcbcrConversionKHR)(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion); +typedef void (VKAPI_PTR *PFN_vkDestroySamplerYcbcrConversionKHR)(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateSamplerYcbcrConversionKHR( + VkDevice device, + const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSamplerYcbcrConversion* pYcbcrConversion); + +VKAPI_ATTR void VKAPI_CALL vkDestroySamplerYcbcrConversionKHR( + VkDevice device, + VkSamplerYcbcrConversion ycbcrConversion, + const VkAllocationCallbacks* pAllocator); +#endif + +#define VK_KHR_bind_memory2 1 +#define VK_KHR_BIND_MEMORY_2_SPEC_VERSION 1 +#define VK_KHR_BIND_MEMORY_2_EXTENSION_NAME "VK_KHR_bind_memory2" + +typedef VkBindBufferMemoryInfo VkBindBufferMemoryInfoKHR; + +typedef VkBindImageMemoryInfo VkBindImageMemoryInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory2KHR)(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos); +typedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory2KHR)(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2KHR( + VkDevice device, + uint32_t bindInfoCount, + const VkBindBufferMemoryInfo* pBindInfos); + +VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2KHR( + VkDevice device, + uint32_t bindInfoCount, + const VkBindImageMemoryInfo* pBindInfos); +#endif + +#define VK_KHR_maintenance3 1 +#define VK_KHR_MAINTENANCE3_SPEC_VERSION 1 +#define VK_KHR_MAINTENANCE3_EXTENSION_NAME "VK_KHR_maintenance3" + +typedef VkPhysicalDeviceMaintenance3Properties VkPhysicalDeviceMaintenance3PropertiesKHR; + +typedef VkDescriptorSetLayoutSupport VkDescriptorSetLayoutSupportKHR; + + +typedef void (VKAPI_PTR *PFN_vkGetDescriptorSetLayoutSupportKHR)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayoutSupport* pSupport); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkGetDescriptorSetLayoutSupportKHR( + VkDevice device, + const VkDescriptorSetLayoutCreateInfo* pCreateInfo, + VkDescriptorSetLayoutSupport* pSupport); +#endif + +#define VK_KHR_draw_indirect_count 1 +#define VK_KHR_DRAW_INDIRECT_COUNT_SPEC_VERSION 1 +#define VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME "VK_KHR_draw_indirect_count" + +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectCountKHR)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride); +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirectCountKHR)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCountKHR( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); + +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCountKHR( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); +#endif + +#define VK_EXT_debug_report 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDebugReportCallbackEXT) + +#define VK_EXT_DEBUG_REPORT_SPEC_VERSION 9 +#define VK_EXT_DEBUG_REPORT_EXTENSION_NAME "VK_EXT_debug_report" +#define VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT +#define VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT + + +typedef enum VkDebugReportObjectTypeEXT { + VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT = 0, + VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT = 1, + VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT = 2, + VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT = 3, + VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT = 4, + VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT = 5, + VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT = 6, + VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT = 7, + VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT = 8, + VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT = 9, + VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT = 10, + VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT = 11, + VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT = 12, + VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT = 13, + VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT = 14, + VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT = 15, + VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT = 16, + VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT = 17, + VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT = 18, + VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT = 19, + VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT = 20, + VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT = 21, + VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT = 22, + VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT = 23, + VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT = 24, + VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT = 25, + VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT = 26, + VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT = 27, + VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT = 28, + VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT = 29, + VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT = 30, + VK_DEBUG_REPORT_OBJECT_TYPE_OBJECT_TABLE_NVX_EXT = 31, + VK_DEBUG_REPORT_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX_EXT = 32, + VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT = 33, + VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT = 1000156000, + VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT = 1000085000, + VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT, + VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT, + VK_DEBUG_REPORT_OBJECT_TYPE_BEGIN_RANGE_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, + VK_DEBUG_REPORT_OBJECT_TYPE_END_RANGE_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT, + VK_DEBUG_REPORT_OBJECT_TYPE_RANGE_SIZE_EXT = (VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT - VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT + 1), + VK_DEBUG_REPORT_OBJECT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDebugReportObjectTypeEXT; + + +typedef enum VkDebugReportFlagBitsEXT { + VK_DEBUG_REPORT_INFORMATION_BIT_EXT = 0x00000001, + VK_DEBUG_REPORT_WARNING_BIT_EXT = 0x00000002, + VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT = 0x00000004, + VK_DEBUG_REPORT_ERROR_BIT_EXT = 0x00000008, + VK_DEBUG_REPORT_DEBUG_BIT_EXT = 0x00000010, + VK_DEBUG_REPORT_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDebugReportFlagBitsEXT; +typedef VkFlags VkDebugReportFlagsEXT; + +typedef VkBool32 (VKAPI_PTR *PFN_vkDebugReportCallbackEXT)( + VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, + uint64_t object, + size_t location, + int32_t messageCode, + const char* pLayerPrefix, + const char* pMessage, + void* pUserData); + +typedef struct VkDebugReportCallbackCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkDebugReportFlagsEXT flags; + PFN_vkDebugReportCallbackEXT pfnCallback; + void* pUserData; +} VkDebugReportCallbackCreateInfoEXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateDebugReportCallbackEXT)(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback); +typedef void (VKAPI_PTR *PFN_vkDestroyDebugReportCallbackEXT)(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkDebugReportMessageEXT)(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugReportCallbackEXT( + VkInstance instance, + const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugReportCallbackEXT* pCallback); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDebugReportCallbackEXT( + VkInstance instance, + VkDebugReportCallbackEXT callback, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR void VKAPI_CALL vkDebugReportMessageEXT( + VkInstance instance, + VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, + uint64_t object, + size_t location, + int32_t messageCode, + const char* pLayerPrefix, + const char* pMessage); +#endif + +#define VK_NV_glsl_shader 1 +#define VK_NV_GLSL_SHADER_SPEC_VERSION 1 +#define VK_NV_GLSL_SHADER_EXTENSION_NAME "VK_NV_glsl_shader" + + +#define VK_EXT_depth_range_unrestricted 1 +#define VK_EXT_DEPTH_RANGE_UNRESTRICTED_SPEC_VERSION 1 +#define VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME "VK_EXT_depth_range_unrestricted" + + +#define VK_IMG_filter_cubic 1 +#define VK_IMG_FILTER_CUBIC_SPEC_VERSION 1 +#define VK_IMG_FILTER_CUBIC_EXTENSION_NAME "VK_IMG_filter_cubic" + + +#define VK_AMD_rasterization_order 1 +#define VK_AMD_RASTERIZATION_ORDER_SPEC_VERSION 1 +#define VK_AMD_RASTERIZATION_ORDER_EXTENSION_NAME "VK_AMD_rasterization_order" + + +typedef enum VkRasterizationOrderAMD { + VK_RASTERIZATION_ORDER_STRICT_AMD = 0, + VK_RASTERIZATION_ORDER_RELAXED_AMD = 1, + VK_RASTERIZATION_ORDER_BEGIN_RANGE_AMD = VK_RASTERIZATION_ORDER_STRICT_AMD, + VK_RASTERIZATION_ORDER_END_RANGE_AMD = VK_RASTERIZATION_ORDER_RELAXED_AMD, + VK_RASTERIZATION_ORDER_RANGE_SIZE_AMD = (VK_RASTERIZATION_ORDER_RELAXED_AMD - VK_RASTERIZATION_ORDER_STRICT_AMD + 1), + VK_RASTERIZATION_ORDER_MAX_ENUM_AMD = 0x7FFFFFFF +} VkRasterizationOrderAMD; + +typedef struct VkPipelineRasterizationStateRasterizationOrderAMD { + VkStructureType sType; + const void* pNext; + VkRasterizationOrderAMD rasterizationOrder; +} VkPipelineRasterizationStateRasterizationOrderAMD; + + + +#define VK_AMD_shader_trinary_minmax 1 +#define VK_AMD_SHADER_TRINARY_MINMAX_SPEC_VERSION 1 +#define VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME "VK_AMD_shader_trinary_minmax" + + +#define VK_AMD_shader_explicit_vertex_parameter 1 +#define VK_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER_SPEC_VERSION 1 +#define VK_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER_EXTENSION_NAME "VK_AMD_shader_explicit_vertex_parameter" + + +#define VK_EXT_debug_marker 1 +#define VK_EXT_DEBUG_MARKER_SPEC_VERSION 4 +#define VK_EXT_DEBUG_MARKER_EXTENSION_NAME "VK_EXT_debug_marker" + +typedef struct VkDebugMarkerObjectNameInfoEXT { + VkStructureType sType; + const void* pNext; + VkDebugReportObjectTypeEXT objectType; + uint64_t object; + const char* pObjectName; +} VkDebugMarkerObjectNameInfoEXT; + +typedef struct VkDebugMarkerObjectTagInfoEXT { + VkStructureType sType; + const void* pNext; + VkDebugReportObjectTypeEXT objectType; + uint64_t object; + uint64_t tagName; + size_t tagSize; + const void* pTag; +} VkDebugMarkerObjectTagInfoEXT; + +typedef struct VkDebugMarkerMarkerInfoEXT { + VkStructureType sType; + const void* pNext; + const char* pMarkerName; + float color[4]; +} VkDebugMarkerMarkerInfoEXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkDebugMarkerSetObjectTagEXT)(VkDevice device, const VkDebugMarkerObjectTagInfoEXT* pTagInfo); +typedef VkResult (VKAPI_PTR *PFN_vkDebugMarkerSetObjectNameEXT)(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo); +typedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerBeginEXT)(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo); +typedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerEndEXT)(VkCommandBuffer commandBuffer); +typedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerInsertEXT)(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectTagEXT( + VkDevice device, + const VkDebugMarkerObjectTagInfoEXT* pTagInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT( + VkDevice device, + const VkDebugMarkerObjectNameInfoEXT* pNameInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerBeginEXT( + VkCommandBuffer commandBuffer, + const VkDebugMarkerMarkerInfoEXT* pMarkerInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerEndEXT( + VkCommandBuffer commandBuffer); + +VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT( + VkCommandBuffer commandBuffer, + const VkDebugMarkerMarkerInfoEXT* pMarkerInfo); +#endif + +#define VK_AMD_gcn_shader 1 +#define VK_AMD_GCN_SHADER_SPEC_VERSION 1 +#define VK_AMD_GCN_SHADER_EXTENSION_NAME "VK_AMD_gcn_shader" + + +#define VK_NV_dedicated_allocation 1 +#define VK_NV_DEDICATED_ALLOCATION_SPEC_VERSION 1 +#define VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME "VK_NV_dedicated_allocation" + +typedef struct VkDedicatedAllocationImageCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkBool32 dedicatedAllocation; +} VkDedicatedAllocationImageCreateInfoNV; + +typedef struct VkDedicatedAllocationBufferCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkBool32 dedicatedAllocation; +} VkDedicatedAllocationBufferCreateInfoNV; + +typedef struct VkDedicatedAllocationMemoryAllocateInfoNV { + VkStructureType sType; + const void* pNext; + VkImage image; + VkBuffer buffer; +} VkDedicatedAllocationMemoryAllocateInfoNV; + + + +#define VK_AMD_draw_indirect_count 1 +#define VK_AMD_DRAW_INDIRECT_COUNT_SPEC_VERSION 1 +#define VK_AMD_DRAW_INDIRECT_COUNT_EXTENSION_NAME "VK_AMD_draw_indirect_count" + +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectCountAMD)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride); +typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirectCountAMD)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCountAMD( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); + +VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCountAMD( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); +#endif + +#define VK_AMD_negative_viewport_height 1 +#define VK_AMD_NEGATIVE_VIEWPORT_HEIGHT_SPEC_VERSION 1 +#define VK_AMD_NEGATIVE_VIEWPORT_HEIGHT_EXTENSION_NAME "VK_AMD_negative_viewport_height" + + +#define VK_AMD_gpu_shader_half_float 1 +#define VK_AMD_GPU_SHADER_HALF_FLOAT_SPEC_VERSION 1 +#define VK_AMD_GPU_SHADER_HALF_FLOAT_EXTENSION_NAME "VK_AMD_gpu_shader_half_float" + + +#define VK_AMD_shader_ballot 1 +#define VK_AMD_SHADER_BALLOT_SPEC_VERSION 1 +#define VK_AMD_SHADER_BALLOT_EXTENSION_NAME "VK_AMD_shader_ballot" + + +#define VK_AMD_texture_gather_bias_lod 1 +#define VK_AMD_TEXTURE_GATHER_BIAS_LOD_SPEC_VERSION 1 +#define VK_AMD_TEXTURE_GATHER_BIAS_LOD_EXTENSION_NAME "VK_AMD_texture_gather_bias_lod" + +typedef struct VkTextureLODGatherFormatPropertiesAMD { + VkStructureType sType; + void* pNext; + VkBool32 supportsTextureGatherLODBiasAMD; +} VkTextureLODGatherFormatPropertiesAMD; + + + +#define VK_AMD_shader_info 1 +#define VK_AMD_SHADER_INFO_SPEC_VERSION 1 +#define VK_AMD_SHADER_INFO_EXTENSION_NAME "VK_AMD_shader_info" + + +typedef enum VkShaderInfoTypeAMD { + VK_SHADER_INFO_TYPE_STATISTICS_AMD = 0, + VK_SHADER_INFO_TYPE_BINARY_AMD = 1, + VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD = 2, + VK_SHADER_INFO_TYPE_BEGIN_RANGE_AMD = VK_SHADER_INFO_TYPE_STATISTICS_AMD, + VK_SHADER_INFO_TYPE_END_RANGE_AMD = VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD, + VK_SHADER_INFO_TYPE_RANGE_SIZE_AMD = (VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD - VK_SHADER_INFO_TYPE_STATISTICS_AMD + 1), + VK_SHADER_INFO_TYPE_MAX_ENUM_AMD = 0x7FFFFFFF +} VkShaderInfoTypeAMD; + +typedef struct VkShaderResourceUsageAMD { + uint32_t numUsedVgprs; + uint32_t numUsedSgprs; + uint32_t ldsSizePerLocalWorkGroup; + size_t ldsUsageSizeInBytes; + size_t scratchMemUsageInBytes; +} VkShaderResourceUsageAMD; + +typedef struct VkShaderStatisticsInfoAMD { + VkShaderStageFlags shaderStageMask; + VkShaderResourceUsageAMD resourceUsage; + uint32_t numPhysicalVgprs; + uint32_t numPhysicalSgprs; + uint32_t numAvailableVgprs; + uint32_t numAvailableSgprs; + uint32_t computeWorkGroupSize[3]; +} VkShaderStatisticsInfoAMD; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetShaderInfoAMD)(VkDevice device, VkPipeline pipeline, VkShaderStageFlagBits shaderStage, VkShaderInfoTypeAMD infoType, size_t* pInfoSize, void* pInfo); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetShaderInfoAMD( + VkDevice device, + VkPipeline pipeline, + VkShaderStageFlagBits shaderStage, + VkShaderInfoTypeAMD infoType, + size_t* pInfoSize, + void* pInfo); +#endif + +#define VK_AMD_shader_image_load_store_lod 1 +#define VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_SPEC_VERSION 1 +#define VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_EXTENSION_NAME "VK_AMD_shader_image_load_store_lod" + + +#define VK_IMG_format_pvrtc 1 +#define VK_IMG_FORMAT_PVRTC_SPEC_VERSION 1 +#define VK_IMG_FORMAT_PVRTC_EXTENSION_NAME "VK_IMG_format_pvrtc" + + +#define VK_NV_external_memory_capabilities 1 +#define VK_NV_EXTERNAL_MEMORY_CAPABILITIES_SPEC_VERSION 1 +#define VK_NV_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME "VK_NV_external_memory_capabilities" + + +typedef enum VkExternalMemoryHandleTypeFlagBitsNV { + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_NV = 0x00000001, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_NV = 0x00000002, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_BIT_NV = 0x00000004, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_KMT_BIT_NV = 0x00000008, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF +} VkExternalMemoryHandleTypeFlagBitsNV; +typedef VkFlags VkExternalMemoryHandleTypeFlagsNV; + +typedef enum VkExternalMemoryFeatureFlagBitsNV { + VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_NV = 0x00000001, + VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_NV = 0x00000002, + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_NV = 0x00000004, + VK_EXTERNAL_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF +} VkExternalMemoryFeatureFlagBitsNV; +typedef VkFlags VkExternalMemoryFeatureFlagsNV; + +typedef struct VkExternalImageFormatPropertiesNV { + VkImageFormatProperties imageFormatProperties; + VkExternalMemoryFeatureFlagsNV externalMemoryFeatures; + VkExternalMemoryHandleTypeFlagsNV exportFromImportedHandleTypes; + VkExternalMemoryHandleTypeFlagsNV compatibleHandleTypes; +} VkExternalImageFormatPropertiesNV; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalImageFormatPropertiesNV)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkExternalMemoryHandleTypeFlagsNV externalHandleType, VkExternalImageFormatPropertiesNV* pExternalImageFormatProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceExternalImageFormatPropertiesNV( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkImageType type, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkImageCreateFlags flags, + VkExternalMemoryHandleTypeFlagsNV externalHandleType, + VkExternalImageFormatPropertiesNV* pExternalImageFormatProperties); +#endif + +#define VK_NV_external_memory 1 +#define VK_NV_EXTERNAL_MEMORY_SPEC_VERSION 1 +#define VK_NV_EXTERNAL_MEMORY_EXTENSION_NAME "VK_NV_external_memory" + +typedef struct VkExternalMemoryImageCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagsNV handleTypes; +} VkExternalMemoryImageCreateInfoNV; + +typedef struct VkExportMemoryAllocateInfoNV { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagsNV handleTypes; +} VkExportMemoryAllocateInfoNV; + + + +#define VK_EXT_validation_flags 1 +#define VK_EXT_VALIDATION_FLAGS_SPEC_VERSION 1 +#define VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME "VK_EXT_validation_flags" + + +typedef enum VkValidationCheckEXT { + VK_VALIDATION_CHECK_ALL_EXT = 0, + VK_VALIDATION_CHECK_SHADERS_EXT = 1, + VK_VALIDATION_CHECK_BEGIN_RANGE_EXT = VK_VALIDATION_CHECK_ALL_EXT, + VK_VALIDATION_CHECK_END_RANGE_EXT = VK_VALIDATION_CHECK_SHADERS_EXT, + VK_VALIDATION_CHECK_RANGE_SIZE_EXT = (VK_VALIDATION_CHECK_SHADERS_EXT - VK_VALIDATION_CHECK_ALL_EXT + 1), + VK_VALIDATION_CHECK_MAX_ENUM_EXT = 0x7FFFFFFF +} VkValidationCheckEXT; + +typedef struct VkValidationFlagsEXT { + VkStructureType sType; + const void* pNext; + uint32_t disabledValidationCheckCount; + VkValidationCheckEXT* pDisabledValidationChecks; +} VkValidationFlagsEXT; + + + +#define VK_EXT_shader_subgroup_ballot 1 +#define VK_EXT_SHADER_SUBGROUP_BALLOT_SPEC_VERSION 1 +#define VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME "VK_EXT_shader_subgroup_ballot" + + +#define VK_EXT_shader_subgroup_vote 1 +#define VK_EXT_SHADER_SUBGROUP_VOTE_SPEC_VERSION 1 +#define VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME "VK_EXT_shader_subgroup_vote" + + +#define VK_NVX_device_generated_commands 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkObjectTableNVX) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkIndirectCommandsLayoutNVX) + +#define VK_NVX_DEVICE_GENERATED_COMMANDS_SPEC_VERSION 3 +#define VK_NVX_DEVICE_GENERATED_COMMANDS_EXTENSION_NAME "VK_NVX_device_generated_commands" + + +typedef enum VkIndirectCommandsTokenTypeNVX { + VK_INDIRECT_COMMANDS_TOKEN_TYPE_PIPELINE_NVX = 0, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_DESCRIPTOR_SET_NVX = 1, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_NVX = 2, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_NVX = 3, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_NVX = 4, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_NVX = 5, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_NVX = 6, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_NVX = 7, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_BEGIN_RANGE_NVX = VK_INDIRECT_COMMANDS_TOKEN_TYPE_PIPELINE_NVX, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_END_RANGE_NVX = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_NVX, + VK_INDIRECT_COMMANDS_TOKEN_TYPE_RANGE_SIZE_NVX = (VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_NVX - VK_INDIRECT_COMMANDS_TOKEN_TYPE_PIPELINE_NVX + 1), + VK_INDIRECT_COMMANDS_TOKEN_TYPE_MAX_ENUM_NVX = 0x7FFFFFFF +} VkIndirectCommandsTokenTypeNVX; + +typedef enum VkObjectEntryTypeNVX { + VK_OBJECT_ENTRY_TYPE_DESCRIPTOR_SET_NVX = 0, + VK_OBJECT_ENTRY_TYPE_PIPELINE_NVX = 1, + VK_OBJECT_ENTRY_TYPE_INDEX_BUFFER_NVX = 2, + VK_OBJECT_ENTRY_TYPE_VERTEX_BUFFER_NVX = 3, + VK_OBJECT_ENTRY_TYPE_PUSH_CONSTANT_NVX = 4, + VK_OBJECT_ENTRY_TYPE_BEGIN_RANGE_NVX = VK_OBJECT_ENTRY_TYPE_DESCRIPTOR_SET_NVX, + VK_OBJECT_ENTRY_TYPE_END_RANGE_NVX = VK_OBJECT_ENTRY_TYPE_PUSH_CONSTANT_NVX, + VK_OBJECT_ENTRY_TYPE_RANGE_SIZE_NVX = (VK_OBJECT_ENTRY_TYPE_PUSH_CONSTANT_NVX - VK_OBJECT_ENTRY_TYPE_DESCRIPTOR_SET_NVX + 1), + VK_OBJECT_ENTRY_TYPE_MAX_ENUM_NVX = 0x7FFFFFFF +} VkObjectEntryTypeNVX; + + +typedef enum VkIndirectCommandsLayoutUsageFlagBitsNVX { + VK_INDIRECT_COMMANDS_LAYOUT_USAGE_UNORDERED_SEQUENCES_BIT_NVX = 0x00000001, + VK_INDIRECT_COMMANDS_LAYOUT_USAGE_SPARSE_SEQUENCES_BIT_NVX = 0x00000002, + VK_INDIRECT_COMMANDS_LAYOUT_USAGE_EMPTY_EXECUTIONS_BIT_NVX = 0x00000004, + VK_INDIRECT_COMMANDS_LAYOUT_USAGE_INDEXED_SEQUENCES_BIT_NVX = 0x00000008, + VK_INDIRECT_COMMANDS_LAYOUT_USAGE_FLAG_BITS_MAX_ENUM_NVX = 0x7FFFFFFF +} VkIndirectCommandsLayoutUsageFlagBitsNVX; +typedef VkFlags VkIndirectCommandsLayoutUsageFlagsNVX; + +typedef enum VkObjectEntryUsageFlagBitsNVX { + VK_OBJECT_ENTRY_USAGE_GRAPHICS_BIT_NVX = 0x00000001, + VK_OBJECT_ENTRY_USAGE_COMPUTE_BIT_NVX = 0x00000002, + VK_OBJECT_ENTRY_USAGE_FLAG_BITS_MAX_ENUM_NVX = 0x7FFFFFFF +} VkObjectEntryUsageFlagBitsNVX; +typedef VkFlags VkObjectEntryUsageFlagsNVX; + +typedef struct VkDeviceGeneratedCommandsFeaturesNVX { + VkStructureType sType; + const void* pNext; + VkBool32 computeBindingPointSupport; +} VkDeviceGeneratedCommandsFeaturesNVX; + +typedef struct VkDeviceGeneratedCommandsLimitsNVX { + VkStructureType sType; + const void* pNext; + uint32_t maxIndirectCommandsLayoutTokenCount; + uint32_t maxObjectEntryCounts; + uint32_t minSequenceCountBufferOffsetAlignment; + uint32_t minSequenceIndexBufferOffsetAlignment; + uint32_t minCommandsTokenBufferOffsetAlignment; +} VkDeviceGeneratedCommandsLimitsNVX; + +typedef struct VkIndirectCommandsTokenNVX { + VkIndirectCommandsTokenTypeNVX tokenType; + VkBuffer buffer; + VkDeviceSize offset; +} VkIndirectCommandsTokenNVX; + +typedef struct VkIndirectCommandsLayoutTokenNVX { + VkIndirectCommandsTokenTypeNVX tokenType; + uint32_t bindingUnit; + uint32_t dynamicCount; + uint32_t divisor; +} VkIndirectCommandsLayoutTokenNVX; + +typedef struct VkIndirectCommandsLayoutCreateInfoNVX { + VkStructureType sType; + const void* pNext; + VkPipelineBindPoint pipelineBindPoint; + VkIndirectCommandsLayoutUsageFlagsNVX flags; + uint32_t tokenCount; + const VkIndirectCommandsLayoutTokenNVX* pTokens; +} VkIndirectCommandsLayoutCreateInfoNVX; + +typedef struct VkCmdProcessCommandsInfoNVX { + VkStructureType sType; + const void* pNext; + VkObjectTableNVX objectTable; + VkIndirectCommandsLayoutNVX indirectCommandsLayout; + uint32_t indirectCommandsTokenCount; + const VkIndirectCommandsTokenNVX* pIndirectCommandsTokens; + uint32_t maxSequencesCount; + VkCommandBuffer targetCommandBuffer; + VkBuffer sequencesCountBuffer; + VkDeviceSize sequencesCountOffset; + VkBuffer sequencesIndexBuffer; + VkDeviceSize sequencesIndexOffset; +} VkCmdProcessCommandsInfoNVX; + +typedef struct VkCmdReserveSpaceForCommandsInfoNVX { + VkStructureType sType; + const void* pNext; + VkObjectTableNVX objectTable; + VkIndirectCommandsLayoutNVX indirectCommandsLayout; + uint32_t maxSequencesCount; +} VkCmdReserveSpaceForCommandsInfoNVX; + +typedef struct VkObjectTableCreateInfoNVX { + VkStructureType sType; + const void* pNext; + uint32_t objectCount; + const VkObjectEntryTypeNVX* pObjectEntryTypes; + const uint32_t* pObjectEntryCounts; + const VkObjectEntryUsageFlagsNVX* pObjectEntryUsageFlags; + uint32_t maxUniformBuffersPerDescriptor; + uint32_t maxStorageBuffersPerDescriptor; + uint32_t maxStorageImagesPerDescriptor; + uint32_t maxSampledImagesPerDescriptor; + uint32_t maxPipelineLayouts; +} VkObjectTableCreateInfoNVX; + +typedef struct VkObjectTableEntryNVX { + VkObjectEntryTypeNVX type; + VkObjectEntryUsageFlagsNVX flags; +} VkObjectTableEntryNVX; + +typedef struct VkObjectTablePipelineEntryNVX { + VkObjectEntryTypeNVX type; + VkObjectEntryUsageFlagsNVX flags; + VkPipeline pipeline; +} VkObjectTablePipelineEntryNVX; + +typedef struct VkObjectTableDescriptorSetEntryNVX { + VkObjectEntryTypeNVX type; + VkObjectEntryUsageFlagsNVX flags; + VkPipelineLayout pipelineLayout; + VkDescriptorSet descriptorSet; +} VkObjectTableDescriptorSetEntryNVX; + +typedef struct VkObjectTableVertexBufferEntryNVX { + VkObjectEntryTypeNVX type; + VkObjectEntryUsageFlagsNVX flags; + VkBuffer buffer; +} VkObjectTableVertexBufferEntryNVX; + +typedef struct VkObjectTableIndexBufferEntryNVX { + VkObjectEntryTypeNVX type; + VkObjectEntryUsageFlagsNVX flags; + VkBuffer buffer; + VkIndexType indexType; +} VkObjectTableIndexBufferEntryNVX; + +typedef struct VkObjectTablePushConstantEntryNVX { + VkObjectEntryTypeNVX type; + VkObjectEntryUsageFlagsNVX flags; + VkPipelineLayout pipelineLayout; + VkShaderStageFlags stageFlags; +} VkObjectTablePushConstantEntryNVX; + + +typedef void (VKAPI_PTR *PFN_vkCmdProcessCommandsNVX)(VkCommandBuffer commandBuffer, const VkCmdProcessCommandsInfoNVX* pProcessCommandsInfo); +typedef void (VKAPI_PTR *PFN_vkCmdReserveSpaceForCommandsNVX)(VkCommandBuffer commandBuffer, const VkCmdReserveSpaceForCommandsInfoNVX* pReserveSpaceInfo); +typedef VkResult (VKAPI_PTR *PFN_vkCreateIndirectCommandsLayoutNVX)(VkDevice device, const VkIndirectCommandsLayoutCreateInfoNVX* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkIndirectCommandsLayoutNVX* pIndirectCommandsLayout); +typedef void (VKAPI_PTR *PFN_vkDestroyIndirectCommandsLayoutNVX)(VkDevice device, VkIndirectCommandsLayoutNVX indirectCommandsLayout, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkCreateObjectTableNVX)(VkDevice device, const VkObjectTableCreateInfoNVX* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkObjectTableNVX* pObjectTable); +typedef void (VKAPI_PTR *PFN_vkDestroyObjectTableNVX)(VkDevice device, VkObjectTableNVX objectTable, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkRegisterObjectsNVX)(VkDevice device, VkObjectTableNVX objectTable, uint32_t objectCount, const VkObjectTableEntryNVX* const* ppObjectTableEntries, const uint32_t* pObjectIndices); +typedef VkResult (VKAPI_PTR *PFN_vkUnregisterObjectsNVX)(VkDevice device, VkObjectTableNVX objectTable, uint32_t objectCount, const VkObjectEntryTypeNVX* pObjectEntryTypes, const uint32_t* pObjectIndices); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceGeneratedCommandsPropertiesNVX)(VkPhysicalDevice physicalDevice, VkDeviceGeneratedCommandsFeaturesNVX* pFeatures, VkDeviceGeneratedCommandsLimitsNVX* pLimits); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdProcessCommandsNVX( + VkCommandBuffer commandBuffer, + const VkCmdProcessCommandsInfoNVX* pProcessCommandsInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdReserveSpaceForCommandsNVX( + VkCommandBuffer commandBuffer, + const VkCmdReserveSpaceForCommandsInfoNVX* pReserveSpaceInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateIndirectCommandsLayoutNVX( + VkDevice device, + const VkIndirectCommandsLayoutCreateInfoNVX* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkIndirectCommandsLayoutNVX* pIndirectCommandsLayout); + +VKAPI_ATTR void VKAPI_CALL vkDestroyIndirectCommandsLayoutNVX( + VkDevice device, + VkIndirectCommandsLayoutNVX indirectCommandsLayout, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateObjectTableNVX( + VkDevice device, + const VkObjectTableCreateInfoNVX* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkObjectTableNVX* pObjectTable); + +VKAPI_ATTR void VKAPI_CALL vkDestroyObjectTableNVX( + VkDevice device, + VkObjectTableNVX objectTable, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkRegisterObjectsNVX( + VkDevice device, + VkObjectTableNVX objectTable, + uint32_t objectCount, + const VkObjectTableEntryNVX* const* ppObjectTableEntries, + const uint32_t* pObjectIndices); + +VKAPI_ATTR VkResult VKAPI_CALL vkUnregisterObjectsNVX( + VkDevice device, + VkObjectTableNVX objectTable, + uint32_t objectCount, + const VkObjectEntryTypeNVX* pObjectEntryTypes, + const uint32_t* pObjectIndices); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceGeneratedCommandsPropertiesNVX( + VkPhysicalDevice physicalDevice, + VkDeviceGeneratedCommandsFeaturesNVX* pFeatures, + VkDeviceGeneratedCommandsLimitsNVX* pLimits); +#endif + +#define VK_NV_clip_space_w_scaling 1 +#define VK_NV_CLIP_SPACE_W_SCALING_SPEC_VERSION 1 +#define VK_NV_CLIP_SPACE_W_SCALING_EXTENSION_NAME "VK_NV_clip_space_w_scaling" + +typedef struct VkViewportWScalingNV { + float xcoeff; + float ycoeff; +} VkViewportWScalingNV; + +typedef struct VkPipelineViewportWScalingStateCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkBool32 viewportWScalingEnable; + uint32_t viewportCount; + const VkViewportWScalingNV* pViewportWScalings; +} VkPipelineViewportWScalingStateCreateInfoNV; + + +typedef void (VKAPI_PTR *PFN_vkCmdSetViewportWScalingNV)(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewportWScalingNV* pViewportWScalings); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdSetViewportWScalingNV( + VkCommandBuffer commandBuffer, + uint32_t firstViewport, + uint32_t viewportCount, + const VkViewportWScalingNV* pViewportWScalings); +#endif + +#define VK_EXT_direct_mode_display 1 +#define VK_EXT_DIRECT_MODE_DISPLAY_SPEC_VERSION 1 +#define VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME "VK_EXT_direct_mode_display" + +typedef VkResult (VKAPI_PTR *PFN_vkReleaseDisplayEXT)(VkPhysicalDevice physicalDevice, VkDisplayKHR display); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkReleaseDisplayEXT( + VkPhysicalDevice physicalDevice, + VkDisplayKHR display); +#endif + +#define VK_EXT_display_surface_counter 1 +#define VK_EXT_DISPLAY_SURFACE_COUNTER_SPEC_VERSION 1 +#define VK_EXT_DISPLAY_SURFACE_COUNTER_EXTENSION_NAME "VK_EXT_display_surface_counter" +#define VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES2_EXT VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_EXT + + +typedef enum VkSurfaceCounterFlagBitsEXT { + VK_SURFACE_COUNTER_VBLANK_EXT = 0x00000001, + VK_SURFACE_COUNTER_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkSurfaceCounterFlagBitsEXT; +typedef VkFlags VkSurfaceCounterFlagsEXT; + +typedef struct VkSurfaceCapabilities2EXT { + VkStructureType sType; + void* pNext; + uint32_t minImageCount; + uint32_t maxImageCount; + VkExtent2D currentExtent; + VkExtent2D minImageExtent; + VkExtent2D maxImageExtent; + uint32_t maxImageArrayLayers; + VkSurfaceTransformFlagsKHR supportedTransforms; + VkSurfaceTransformFlagBitsKHR currentTransform; + VkCompositeAlphaFlagsKHR supportedCompositeAlpha; + VkImageUsageFlags supportedUsageFlags; + VkSurfaceCounterFlagsEXT supportedSurfaceCounters; +} VkSurfaceCapabilities2EXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilities2EXT)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilities2EXT* pSurfaceCapabilities); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilities2EXT( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + VkSurfaceCapabilities2EXT* pSurfaceCapabilities); +#endif + +#define VK_EXT_display_control 1 +#define VK_EXT_DISPLAY_CONTROL_SPEC_VERSION 1 +#define VK_EXT_DISPLAY_CONTROL_EXTENSION_NAME "VK_EXT_display_control" + + +typedef enum VkDisplayPowerStateEXT { + VK_DISPLAY_POWER_STATE_OFF_EXT = 0, + VK_DISPLAY_POWER_STATE_SUSPEND_EXT = 1, + VK_DISPLAY_POWER_STATE_ON_EXT = 2, + VK_DISPLAY_POWER_STATE_BEGIN_RANGE_EXT = VK_DISPLAY_POWER_STATE_OFF_EXT, + VK_DISPLAY_POWER_STATE_END_RANGE_EXT = VK_DISPLAY_POWER_STATE_ON_EXT, + VK_DISPLAY_POWER_STATE_RANGE_SIZE_EXT = (VK_DISPLAY_POWER_STATE_ON_EXT - VK_DISPLAY_POWER_STATE_OFF_EXT + 1), + VK_DISPLAY_POWER_STATE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDisplayPowerStateEXT; + +typedef enum VkDeviceEventTypeEXT { + VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT = 0, + VK_DEVICE_EVENT_TYPE_BEGIN_RANGE_EXT = VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT, + VK_DEVICE_EVENT_TYPE_END_RANGE_EXT = VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT, + VK_DEVICE_EVENT_TYPE_RANGE_SIZE_EXT = (VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT - VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT + 1), + VK_DEVICE_EVENT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDeviceEventTypeEXT; + +typedef enum VkDisplayEventTypeEXT { + VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT = 0, + VK_DISPLAY_EVENT_TYPE_BEGIN_RANGE_EXT = VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT, + VK_DISPLAY_EVENT_TYPE_END_RANGE_EXT = VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT, + VK_DISPLAY_EVENT_TYPE_RANGE_SIZE_EXT = (VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT - VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT + 1), + VK_DISPLAY_EVENT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDisplayEventTypeEXT; + +typedef struct VkDisplayPowerInfoEXT { + VkStructureType sType; + const void* pNext; + VkDisplayPowerStateEXT powerState; +} VkDisplayPowerInfoEXT; + +typedef struct VkDeviceEventInfoEXT { + VkStructureType sType; + const void* pNext; + VkDeviceEventTypeEXT deviceEvent; +} VkDeviceEventInfoEXT; + +typedef struct VkDisplayEventInfoEXT { + VkStructureType sType; + const void* pNext; + VkDisplayEventTypeEXT displayEvent; +} VkDisplayEventInfoEXT; + +typedef struct VkSwapchainCounterCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkSurfaceCounterFlagsEXT surfaceCounters; +} VkSwapchainCounterCreateInfoEXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkDisplayPowerControlEXT)(VkDevice device, VkDisplayKHR display, const VkDisplayPowerInfoEXT* pDisplayPowerInfo); +typedef VkResult (VKAPI_PTR *PFN_vkRegisterDeviceEventEXT)(VkDevice device, const VkDeviceEventInfoEXT* pDeviceEventInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence); +typedef VkResult (VKAPI_PTR *PFN_vkRegisterDisplayEventEXT)(VkDevice device, VkDisplayKHR display, const VkDisplayEventInfoEXT* pDisplayEventInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence); +typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainCounterEXT)(VkDevice device, VkSwapchainKHR swapchain, VkSurfaceCounterFlagBitsEXT counter, uint64_t* pCounterValue); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkDisplayPowerControlEXT( + VkDevice device, + VkDisplayKHR display, + const VkDisplayPowerInfoEXT* pDisplayPowerInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkRegisterDeviceEventEXT( + VkDevice device, + const VkDeviceEventInfoEXT* pDeviceEventInfo, + const VkAllocationCallbacks* pAllocator, + VkFence* pFence); + +VKAPI_ATTR VkResult VKAPI_CALL vkRegisterDisplayEventEXT( + VkDevice device, + VkDisplayKHR display, + const VkDisplayEventInfoEXT* pDisplayEventInfo, + const VkAllocationCallbacks* pAllocator, + VkFence* pFence); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainCounterEXT( + VkDevice device, + VkSwapchainKHR swapchain, + VkSurfaceCounterFlagBitsEXT counter, + uint64_t* pCounterValue); +#endif + +#define VK_GOOGLE_display_timing 1 +#define VK_GOOGLE_DISPLAY_TIMING_SPEC_VERSION 1 +#define VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME "VK_GOOGLE_display_timing" + +typedef struct VkRefreshCycleDurationGOOGLE { + uint64_t refreshDuration; +} VkRefreshCycleDurationGOOGLE; + +typedef struct VkPastPresentationTimingGOOGLE { + uint32_t presentID; + uint64_t desiredPresentTime; + uint64_t actualPresentTime; + uint64_t earliestPresentTime; + uint64_t presentMargin; +} VkPastPresentationTimingGOOGLE; + +typedef struct VkPresentTimeGOOGLE { + uint32_t presentID; + uint64_t desiredPresentTime; +} VkPresentTimeGOOGLE; + +typedef struct VkPresentTimesInfoGOOGLE { + VkStructureType sType; + const void* pNext; + uint32_t swapchainCount; + const VkPresentTimeGOOGLE* pTimes; +} VkPresentTimesInfoGOOGLE; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetRefreshCycleDurationGOOGLE)(VkDevice device, VkSwapchainKHR swapchain, VkRefreshCycleDurationGOOGLE* pDisplayTimingProperties); +typedef VkResult (VKAPI_PTR *PFN_vkGetPastPresentationTimingGOOGLE)(VkDevice device, VkSwapchainKHR swapchain, uint32_t* pPresentationTimingCount, VkPastPresentationTimingGOOGLE* pPresentationTimings); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetRefreshCycleDurationGOOGLE( + VkDevice device, + VkSwapchainKHR swapchain, + VkRefreshCycleDurationGOOGLE* pDisplayTimingProperties); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetPastPresentationTimingGOOGLE( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pPresentationTimingCount, + VkPastPresentationTimingGOOGLE* pPresentationTimings); +#endif + +#define VK_NV_sample_mask_override_coverage 1 +#define VK_NV_SAMPLE_MASK_OVERRIDE_COVERAGE_SPEC_VERSION 1 +#define VK_NV_SAMPLE_MASK_OVERRIDE_COVERAGE_EXTENSION_NAME "VK_NV_sample_mask_override_coverage" + + +#define VK_NV_geometry_shader_passthrough 1 +#define VK_NV_GEOMETRY_SHADER_PASSTHROUGH_SPEC_VERSION 1 +#define VK_NV_GEOMETRY_SHADER_PASSTHROUGH_EXTENSION_NAME "VK_NV_geometry_shader_passthrough" + + +#define VK_NV_viewport_array2 1 +#define VK_NV_VIEWPORT_ARRAY2_SPEC_VERSION 1 +#define VK_NV_VIEWPORT_ARRAY2_EXTENSION_NAME "VK_NV_viewport_array2" + + +#define VK_NVX_multiview_per_view_attributes 1 +#define VK_NVX_MULTIVIEW_PER_VIEW_ATTRIBUTES_SPEC_VERSION 1 +#define VK_NVX_MULTIVIEW_PER_VIEW_ATTRIBUTES_EXTENSION_NAME "VK_NVX_multiview_per_view_attributes" + +typedef struct VkPhysicalDeviceMultiviewPerViewAttributesPropertiesNVX { + VkStructureType sType; + void* pNext; + VkBool32 perViewPositionAllComponents; +} VkPhysicalDeviceMultiviewPerViewAttributesPropertiesNVX; + + + +#define VK_NV_viewport_swizzle 1 +#define VK_NV_VIEWPORT_SWIZZLE_SPEC_VERSION 1 +#define VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME "VK_NV_viewport_swizzle" + + +typedef enum VkViewportCoordinateSwizzleNV { + VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_X_NV = 0, + VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_X_NV = 1, + VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Y_NV = 2, + VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Y_NV = 3, + VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Z_NV = 4, + VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Z_NV = 5, + VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_W_NV = 6, + VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV = 7, + VK_VIEWPORT_COORDINATE_SWIZZLE_BEGIN_RANGE_NV = VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_X_NV, + VK_VIEWPORT_COORDINATE_SWIZZLE_END_RANGE_NV = VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV, + VK_VIEWPORT_COORDINATE_SWIZZLE_RANGE_SIZE_NV = (VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV - VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_X_NV + 1), + VK_VIEWPORT_COORDINATE_SWIZZLE_MAX_ENUM_NV = 0x7FFFFFFF +} VkViewportCoordinateSwizzleNV; + +typedef VkFlags VkPipelineViewportSwizzleStateCreateFlagsNV; + +typedef struct VkViewportSwizzleNV { + VkViewportCoordinateSwizzleNV x; + VkViewportCoordinateSwizzleNV y; + VkViewportCoordinateSwizzleNV z; + VkViewportCoordinateSwizzleNV w; +} VkViewportSwizzleNV; + +typedef struct VkPipelineViewportSwizzleStateCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkPipelineViewportSwizzleStateCreateFlagsNV flags; + uint32_t viewportCount; + const VkViewportSwizzleNV* pViewportSwizzles; +} VkPipelineViewportSwizzleStateCreateInfoNV; + + + +#define VK_EXT_discard_rectangles 1 +#define VK_EXT_DISCARD_RECTANGLES_SPEC_VERSION 1 +#define VK_EXT_DISCARD_RECTANGLES_EXTENSION_NAME "VK_EXT_discard_rectangles" + + +typedef enum VkDiscardRectangleModeEXT { + VK_DISCARD_RECTANGLE_MODE_INCLUSIVE_EXT = 0, + VK_DISCARD_RECTANGLE_MODE_EXCLUSIVE_EXT = 1, + VK_DISCARD_RECTANGLE_MODE_BEGIN_RANGE_EXT = VK_DISCARD_RECTANGLE_MODE_INCLUSIVE_EXT, + VK_DISCARD_RECTANGLE_MODE_END_RANGE_EXT = VK_DISCARD_RECTANGLE_MODE_EXCLUSIVE_EXT, + VK_DISCARD_RECTANGLE_MODE_RANGE_SIZE_EXT = (VK_DISCARD_RECTANGLE_MODE_EXCLUSIVE_EXT - VK_DISCARD_RECTANGLE_MODE_INCLUSIVE_EXT + 1), + VK_DISCARD_RECTANGLE_MODE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDiscardRectangleModeEXT; + +typedef VkFlags VkPipelineDiscardRectangleStateCreateFlagsEXT; + +typedef struct VkPhysicalDeviceDiscardRectanglePropertiesEXT { + VkStructureType sType; + void* pNext; + uint32_t maxDiscardRectangles; +} VkPhysicalDeviceDiscardRectanglePropertiesEXT; + +typedef struct VkPipelineDiscardRectangleStateCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkPipelineDiscardRectangleStateCreateFlagsEXT flags; + VkDiscardRectangleModeEXT discardRectangleMode; + uint32_t discardRectangleCount; + const VkRect2D* pDiscardRectangles; +} VkPipelineDiscardRectangleStateCreateInfoEXT; + + +typedef void (VKAPI_PTR *PFN_vkCmdSetDiscardRectangleEXT)(VkCommandBuffer commandBuffer, uint32_t firstDiscardRectangle, uint32_t discardRectangleCount, const VkRect2D* pDiscardRectangles); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdSetDiscardRectangleEXT( + VkCommandBuffer commandBuffer, + uint32_t firstDiscardRectangle, + uint32_t discardRectangleCount, + const VkRect2D* pDiscardRectangles); +#endif + +#define VK_EXT_conservative_rasterization 1 +#define VK_EXT_CONSERVATIVE_RASTERIZATION_SPEC_VERSION 1 +#define VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME "VK_EXT_conservative_rasterization" + + +typedef enum VkConservativeRasterizationModeEXT { + VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT = 0, + VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT = 1, + VK_CONSERVATIVE_RASTERIZATION_MODE_UNDERESTIMATE_EXT = 2, + VK_CONSERVATIVE_RASTERIZATION_MODE_BEGIN_RANGE_EXT = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT, + VK_CONSERVATIVE_RASTERIZATION_MODE_END_RANGE_EXT = VK_CONSERVATIVE_RASTERIZATION_MODE_UNDERESTIMATE_EXT, + VK_CONSERVATIVE_RASTERIZATION_MODE_RANGE_SIZE_EXT = (VK_CONSERVATIVE_RASTERIZATION_MODE_UNDERESTIMATE_EXT - VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT + 1), + VK_CONSERVATIVE_RASTERIZATION_MODE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkConservativeRasterizationModeEXT; + +typedef VkFlags VkPipelineRasterizationConservativeStateCreateFlagsEXT; + +typedef struct VkPhysicalDeviceConservativeRasterizationPropertiesEXT { + VkStructureType sType; + void* pNext; + float primitiveOverestimationSize; + float maxExtraPrimitiveOverestimationSize; + float extraPrimitiveOverestimationSizeGranularity; + VkBool32 primitiveUnderestimation; + VkBool32 conservativePointAndLineRasterization; + VkBool32 degenerateTrianglesRasterized; + VkBool32 degenerateLinesRasterized; + VkBool32 fullyCoveredFragmentShaderInputVariable; + VkBool32 conservativeRasterizationPostDepthCoverage; +} VkPhysicalDeviceConservativeRasterizationPropertiesEXT; + +typedef struct VkPipelineRasterizationConservativeStateCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkPipelineRasterizationConservativeStateCreateFlagsEXT flags; + VkConservativeRasterizationModeEXT conservativeRasterizationMode; + float extraPrimitiveOverestimationSize; +} VkPipelineRasterizationConservativeStateCreateInfoEXT; + + + +#define VK_EXT_swapchain_colorspace 1 +#define VK_EXT_SWAPCHAIN_COLOR_SPACE_SPEC_VERSION 3 +#define VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME "VK_EXT_swapchain_colorspace" + + +#define VK_EXT_hdr_metadata 1 +#define VK_EXT_HDR_METADATA_SPEC_VERSION 1 +#define VK_EXT_HDR_METADATA_EXTENSION_NAME "VK_EXT_hdr_metadata" + +typedef struct VkXYColorEXT { + float x; + float y; +} VkXYColorEXT; + +typedef struct VkHdrMetadataEXT { + VkStructureType sType; + const void* pNext; + VkXYColorEXT displayPrimaryRed; + VkXYColorEXT displayPrimaryGreen; + VkXYColorEXT displayPrimaryBlue; + VkXYColorEXT whitePoint; + float maxLuminance; + float minLuminance; + float maxContentLightLevel; + float maxFrameAverageLightLevel; +} VkHdrMetadataEXT; + + +typedef void (VKAPI_PTR *PFN_vkSetHdrMetadataEXT)(VkDevice device, uint32_t swapchainCount, const VkSwapchainKHR* pSwapchains, const VkHdrMetadataEXT* pMetadata); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkSetHdrMetadataEXT( + VkDevice device, + uint32_t swapchainCount, + const VkSwapchainKHR* pSwapchains, + const VkHdrMetadataEXT* pMetadata); +#endif + +#define VK_EXT_external_memory_dma_buf 1 +#define VK_EXT_EXTERNAL_MEMORY_DMA_BUF_SPEC_VERSION 1 +#define VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME "VK_EXT_external_memory_dma_buf" + + +#define VK_EXT_queue_family_foreign 1 +#define VK_EXT_QUEUE_FAMILY_FOREIGN_SPEC_VERSION 1 +#define VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME "VK_EXT_queue_family_foreign" +#define VK_QUEUE_FAMILY_FOREIGN_EXT (~0U-2) + + +#define VK_EXT_debug_utils 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDebugUtilsMessengerEXT) + +#define VK_EXT_DEBUG_UTILS_SPEC_VERSION 1 +#define VK_EXT_DEBUG_UTILS_EXTENSION_NAME "VK_EXT_debug_utils" + +typedef VkFlags VkDebugUtilsMessengerCallbackDataFlagsEXT; +typedef VkFlags VkDebugUtilsMessengerCreateFlagsEXT; + +typedef enum VkDebugUtilsMessageSeverityFlagBitsEXT { + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT = 0x00000001, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT = 0x00000010, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT = 0x00000100, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT = 0x00001000, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDebugUtilsMessageSeverityFlagBitsEXT; +typedef VkFlags VkDebugUtilsMessageSeverityFlagsEXT; + +typedef enum VkDebugUtilsMessageTypeFlagBitsEXT { + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT = 0x00000001, + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT = 0x00000002, + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT = 0x00000004, + VK_DEBUG_UTILS_MESSAGE_TYPE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDebugUtilsMessageTypeFlagBitsEXT; +typedef VkFlags VkDebugUtilsMessageTypeFlagsEXT; + +typedef struct VkDebugUtilsObjectNameInfoEXT { + VkStructureType sType; + const void* pNext; + VkObjectType objectType; + uint64_t objectHandle; + const char* pObjectName; +} VkDebugUtilsObjectNameInfoEXT; + +typedef struct VkDebugUtilsObjectTagInfoEXT { + VkStructureType sType; + const void* pNext; + VkObjectType objectType; + uint64_t objectHandle; + uint64_t tagName; + size_t tagSize; + const void* pTag; +} VkDebugUtilsObjectTagInfoEXT; + +typedef struct VkDebugUtilsLabelEXT { + VkStructureType sType; + const void* pNext; + const char* pLabelName; + float color[4]; +} VkDebugUtilsLabelEXT; + +typedef struct VkDebugUtilsMessengerCallbackDataEXT { + VkStructureType sType; + const void* pNext; + VkDebugUtilsMessengerCallbackDataFlagsEXT flags; + const char* pMessageIdName; + int32_t messageIdNumber; + const char* pMessage; + uint32_t queueLabelCount; + VkDebugUtilsLabelEXT* pQueueLabels; + uint32_t cmdBufLabelCount; + VkDebugUtilsLabelEXT* pCmdBufLabels; + uint32_t objectCount; + VkDebugUtilsObjectNameInfoEXT* pObjects; +} VkDebugUtilsMessengerCallbackDataEXT; + +typedef VkBool32 (VKAPI_PTR *PFN_vkDebugUtilsMessengerCallbackEXT)( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); + +typedef struct VkDebugUtilsMessengerCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkDebugUtilsMessengerCreateFlagsEXT flags; + VkDebugUtilsMessageSeverityFlagsEXT messageSeverity; + VkDebugUtilsMessageTypeFlagsEXT messageType; + PFN_vkDebugUtilsMessengerCallbackEXT pfnUserCallback; + void* pUserData; +} VkDebugUtilsMessengerCreateInfoEXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkSetDebugUtilsObjectNameEXT)(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo); +typedef VkResult (VKAPI_PTR *PFN_vkSetDebugUtilsObjectTagEXT)(VkDevice device, const VkDebugUtilsObjectTagInfoEXT* pTagInfo); +typedef void (VKAPI_PTR *PFN_vkQueueBeginDebugUtilsLabelEXT)(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo); +typedef void (VKAPI_PTR *PFN_vkQueueEndDebugUtilsLabelEXT)(VkQueue queue); +typedef void (VKAPI_PTR *PFN_vkQueueInsertDebugUtilsLabelEXT)(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo); +typedef void (VKAPI_PTR *PFN_vkCmdBeginDebugUtilsLabelEXT)(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo); +typedef void (VKAPI_PTR *PFN_vkCmdEndDebugUtilsLabelEXT)(VkCommandBuffer commandBuffer); +typedef void (VKAPI_PTR *PFN_vkCmdInsertDebugUtilsLabelEXT)(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo); +typedef VkResult (VKAPI_PTR *PFN_vkCreateDebugUtilsMessengerEXT)(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger); +typedef void (VKAPI_PTR *PFN_vkDestroyDebugUtilsMessengerEXT)(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator); +typedef void (VKAPI_PTR *PFN_vkSubmitDebugUtilsMessageEXT)(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT( + VkDevice device, + const VkDebugUtilsObjectNameInfoEXT* pNameInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectTagEXT( + VkDevice device, + const VkDebugUtilsObjectTagInfoEXT* pTagInfo); + +VKAPI_ATTR void VKAPI_CALL vkQueueBeginDebugUtilsLabelEXT( + VkQueue queue, + const VkDebugUtilsLabelEXT* pLabelInfo); + +VKAPI_ATTR void VKAPI_CALL vkQueueEndDebugUtilsLabelEXT( + VkQueue queue); + +VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT( + VkQueue queue, + const VkDebugUtilsLabelEXT* pLabelInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdBeginDebugUtilsLabelEXT( + VkCommandBuffer commandBuffer, + const VkDebugUtilsLabelEXT* pLabelInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdEndDebugUtilsLabelEXT( + VkCommandBuffer commandBuffer); + +VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT( + VkCommandBuffer commandBuffer, + const VkDebugUtilsLabelEXT* pLabelInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pMessenger); + +VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT( + VkInstance instance, + VkDebugUtilsMessengerEXT messenger, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT( + VkInstance instance, + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData); +#endif + +#define VK_EXT_sampler_filter_minmax 1 +#define VK_EXT_SAMPLER_FILTER_MINMAX_SPEC_VERSION 1 +#define VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME "VK_EXT_sampler_filter_minmax" + + +typedef enum VkSamplerReductionModeEXT { + VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT = 0, + VK_SAMPLER_REDUCTION_MODE_MIN_EXT = 1, + VK_SAMPLER_REDUCTION_MODE_MAX_EXT = 2, + VK_SAMPLER_REDUCTION_MODE_BEGIN_RANGE_EXT = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT, + VK_SAMPLER_REDUCTION_MODE_END_RANGE_EXT = VK_SAMPLER_REDUCTION_MODE_MAX_EXT, + VK_SAMPLER_REDUCTION_MODE_RANGE_SIZE_EXT = (VK_SAMPLER_REDUCTION_MODE_MAX_EXT - VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT + 1), + VK_SAMPLER_REDUCTION_MODE_MAX_ENUM_EXT = 0x7FFFFFFF +} VkSamplerReductionModeEXT; + +typedef struct VkSamplerReductionModeCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkSamplerReductionModeEXT reductionMode; +} VkSamplerReductionModeCreateInfoEXT; + +typedef struct VkPhysicalDeviceSamplerFilterMinmaxPropertiesEXT { + VkStructureType sType; + void* pNext; + VkBool32 filterMinmaxSingleComponentFormats; + VkBool32 filterMinmaxImageComponentMapping; +} VkPhysicalDeviceSamplerFilterMinmaxPropertiesEXT; + + + +#define VK_AMD_gpu_shader_int16 1 +#define VK_AMD_GPU_SHADER_INT16_SPEC_VERSION 1 +#define VK_AMD_GPU_SHADER_INT16_EXTENSION_NAME "VK_AMD_gpu_shader_int16" + + +#define VK_AMD_mixed_attachment_samples 1 +#define VK_AMD_MIXED_ATTACHMENT_SAMPLES_SPEC_VERSION 1 +#define VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME "VK_AMD_mixed_attachment_samples" + + +#define VK_AMD_shader_fragment_mask 1 +#define VK_AMD_SHADER_FRAGMENT_MASK_SPEC_VERSION 1 +#define VK_AMD_SHADER_FRAGMENT_MASK_EXTENSION_NAME "VK_AMD_shader_fragment_mask" + + +#define VK_EXT_shader_stencil_export 1 +#define VK_EXT_SHADER_STENCIL_EXPORT_SPEC_VERSION 1 +#define VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME "VK_EXT_shader_stencil_export" + + +#define VK_EXT_sample_locations 1 +#define VK_EXT_SAMPLE_LOCATIONS_SPEC_VERSION 1 +#define VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME "VK_EXT_sample_locations" + +typedef struct VkSampleLocationEXT { + float x; + float y; +} VkSampleLocationEXT; + +typedef struct VkSampleLocationsInfoEXT { + VkStructureType sType; + const void* pNext; + VkSampleCountFlagBits sampleLocationsPerPixel; + VkExtent2D sampleLocationGridSize; + uint32_t sampleLocationsCount; + const VkSampleLocationEXT* pSampleLocations; +} VkSampleLocationsInfoEXT; + +typedef struct VkAttachmentSampleLocationsEXT { + uint32_t attachmentIndex; + VkSampleLocationsInfoEXT sampleLocationsInfo; +} VkAttachmentSampleLocationsEXT; + +typedef struct VkSubpassSampleLocationsEXT { + uint32_t subpassIndex; + VkSampleLocationsInfoEXT sampleLocationsInfo; +} VkSubpassSampleLocationsEXT; + +typedef struct VkRenderPassSampleLocationsBeginInfoEXT { + VkStructureType sType; + const void* pNext; + uint32_t attachmentInitialSampleLocationsCount; + const VkAttachmentSampleLocationsEXT* pAttachmentInitialSampleLocations; + uint32_t postSubpassSampleLocationsCount; + const VkSubpassSampleLocationsEXT* pPostSubpassSampleLocations; +} VkRenderPassSampleLocationsBeginInfoEXT; + +typedef struct VkPipelineSampleLocationsStateCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkBool32 sampleLocationsEnable; + VkSampleLocationsInfoEXT sampleLocationsInfo; +} VkPipelineSampleLocationsStateCreateInfoEXT; + +typedef struct VkPhysicalDeviceSampleLocationsPropertiesEXT { + VkStructureType sType; + void* pNext; + VkSampleCountFlags sampleLocationSampleCounts; + VkExtent2D maxSampleLocationGridSize; + float sampleLocationCoordinateRange[2]; + uint32_t sampleLocationSubPixelBits; + VkBool32 variableSampleLocations; +} VkPhysicalDeviceSampleLocationsPropertiesEXT; + +typedef struct VkMultisamplePropertiesEXT { + VkStructureType sType; + void* pNext; + VkExtent2D maxSampleLocationGridSize; +} VkMultisamplePropertiesEXT; + + +typedef void (VKAPI_PTR *PFN_vkCmdSetSampleLocationsEXT)(VkCommandBuffer commandBuffer, const VkSampleLocationsInfoEXT* pSampleLocationsInfo); +typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMultisamplePropertiesEXT)(VkPhysicalDevice physicalDevice, VkSampleCountFlagBits samples, VkMultisamplePropertiesEXT* pMultisampleProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdSetSampleLocationsEXT( + VkCommandBuffer commandBuffer, + const VkSampleLocationsInfoEXT* pSampleLocationsInfo); + +VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMultisamplePropertiesEXT( + VkPhysicalDevice physicalDevice, + VkSampleCountFlagBits samples, + VkMultisamplePropertiesEXT* pMultisampleProperties); +#endif + +#define VK_EXT_blend_operation_advanced 1 +#define VK_EXT_BLEND_OPERATION_ADVANCED_SPEC_VERSION 2 +#define VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME "VK_EXT_blend_operation_advanced" + + +typedef enum VkBlendOverlapEXT { + VK_BLEND_OVERLAP_UNCORRELATED_EXT = 0, + VK_BLEND_OVERLAP_DISJOINT_EXT = 1, + VK_BLEND_OVERLAP_CONJOINT_EXT = 2, + VK_BLEND_OVERLAP_BEGIN_RANGE_EXT = VK_BLEND_OVERLAP_UNCORRELATED_EXT, + VK_BLEND_OVERLAP_END_RANGE_EXT = VK_BLEND_OVERLAP_CONJOINT_EXT, + VK_BLEND_OVERLAP_RANGE_SIZE_EXT = (VK_BLEND_OVERLAP_CONJOINT_EXT - VK_BLEND_OVERLAP_UNCORRELATED_EXT + 1), + VK_BLEND_OVERLAP_MAX_ENUM_EXT = 0x7FFFFFFF +} VkBlendOverlapEXT; + +typedef struct VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT { + VkStructureType sType; + void* pNext; + VkBool32 advancedBlendCoherentOperations; +} VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT; + +typedef struct VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT { + VkStructureType sType; + void* pNext; + uint32_t advancedBlendMaxColorAttachments; + VkBool32 advancedBlendIndependentBlend; + VkBool32 advancedBlendNonPremultipliedSrcColor; + VkBool32 advancedBlendNonPremultipliedDstColor; + VkBool32 advancedBlendCorrelatedOverlap; + VkBool32 advancedBlendAllOperations; +} VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT; + +typedef struct VkPipelineColorBlendAdvancedStateCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkBool32 srcPremultiplied; + VkBool32 dstPremultiplied; + VkBlendOverlapEXT blendOverlap; +} VkPipelineColorBlendAdvancedStateCreateInfoEXT; + + + +#define VK_NV_fragment_coverage_to_color 1 +#define VK_NV_FRAGMENT_COVERAGE_TO_COLOR_SPEC_VERSION 1 +#define VK_NV_FRAGMENT_COVERAGE_TO_COLOR_EXTENSION_NAME "VK_NV_fragment_coverage_to_color" + +typedef VkFlags VkPipelineCoverageToColorStateCreateFlagsNV; + +typedef struct VkPipelineCoverageToColorStateCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkPipelineCoverageToColorStateCreateFlagsNV flags; + VkBool32 coverageToColorEnable; + uint32_t coverageToColorLocation; +} VkPipelineCoverageToColorStateCreateInfoNV; + + + +#define VK_NV_framebuffer_mixed_samples 1 +#define VK_NV_FRAMEBUFFER_MIXED_SAMPLES_SPEC_VERSION 1 +#define VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME "VK_NV_framebuffer_mixed_samples" + + +typedef enum VkCoverageModulationModeNV { + VK_COVERAGE_MODULATION_MODE_NONE_NV = 0, + VK_COVERAGE_MODULATION_MODE_RGB_NV = 1, + VK_COVERAGE_MODULATION_MODE_ALPHA_NV = 2, + VK_COVERAGE_MODULATION_MODE_RGBA_NV = 3, + VK_COVERAGE_MODULATION_MODE_BEGIN_RANGE_NV = VK_COVERAGE_MODULATION_MODE_NONE_NV, + VK_COVERAGE_MODULATION_MODE_END_RANGE_NV = VK_COVERAGE_MODULATION_MODE_RGBA_NV, + VK_COVERAGE_MODULATION_MODE_RANGE_SIZE_NV = (VK_COVERAGE_MODULATION_MODE_RGBA_NV - VK_COVERAGE_MODULATION_MODE_NONE_NV + 1), + VK_COVERAGE_MODULATION_MODE_MAX_ENUM_NV = 0x7FFFFFFF +} VkCoverageModulationModeNV; + +typedef VkFlags VkPipelineCoverageModulationStateCreateFlagsNV; + +typedef struct VkPipelineCoverageModulationStateCreateInfoNV { + VkStructureType sType; + const void* pNext; + VkPipelineCoverageModulationStateCreateFlagsNV flags; + VkCoverageModulationModeNV coverageModulationMode; + VkBool32 coverageModulationTableEnable; + uint32_t coverageModulationTableCount; + const float* pCoverageModulationTable; +} VkPipelineCoverageModulationStateCreateInfoNV; + + + +#define VK_NV_fill_rectangle 1 +#define VK_NV_FILL_RECTANGLE_SPEC_VERSION 1 +#define VK_NV_FILL_RECTANGLE_EXTENSION_NAME "VK_NV_fill_rectangle" + + +#define VK_EXT_post_depth_coverage 1 +#define VK_EXT_POST_DEPTH_COVERAGE_SPEC_VERSION 1 +#define VK_EXT_POST_DEPTH_COVERAGE_EXTENSION_NAME "VK_EXT_post_depth_coverage" + + +#define VK_EXT_validation_cache 1 +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkValidationCacheEXT) + +#define VK_EXT_VALIDATION_CACHE_SPEC_VERSION 1 +#define VK_EXT_VALIDATION_CACHE_EXTENSION_NAME "VK_EXT_validation_cache" +#define VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT + + +typedef enum VkValidationCacheHeaderVersionEXT { + VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT = 1, + VK_VALIDATION_CACHE_HEADER_VERSION_BEGIN_RANGE_EXT = VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT, + VK_VALIDATION_CACHE_HEADER_VERSION_END_RANGE_EXT = VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT, + VK_VALIDATION_CACHE_HEADER_VERSION_RANGE_SIZE_EXT = (VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT - VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT + 1), + VK_VALIDATION_CACHE_HEADER_VERSION_MAX_ENUM_EXT = 0x7FFFFFFF +} VkValidationCacheHeaderVersionEXT; + +typedef VkFlags VkValidationCacheCreateFlagsEXT; + +typedef struct VkValidationCacheCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkValidationCacheCreateFlagsEXT flags; + size_t initialDataSize; + const void* pInitialData; +} VkValidationCacheCreateInfoEXT; + +typedef struct VkShaderModuleValidationCacheCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkValidationCacheEXT validationCache; +} VkShaderModuleValidationCacheCreateInfoEXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateValidationCacheEXT)(VkDevice device, const VkValidationCacheCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkValidationCacheEXT* pValidationCache); +typedef void (VKAPI_PTR *PFN_vkDestroyValidationCacheEXT)(VkDevice device, VkValidationCacheEXT validationCache, const VkAllocationCallbacks* pAllocator); +typedef VkResult (VKAPI_PTR *PFN_vkMergeValidationCachesEXT)(VkDevice device, VkValidationCacheEXT dstCache, uint32_t srcCacheCount, const VkValidationCacheEXT* pSrcCaches); +typedef VkResult (VKAPI_PTR *PFN_vkGetValidationCacheDataEXT)(VkDevice device, VkValidationCacheEXT validationCache, size_t* pDataSize, void* pData); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateValidationCacheEXT( + VkDevice device, + const VkValidationCacheCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkValidationCacheEXT* pValidationCache); + +VKAPI_ATTR void VKAPI_CALL vkDestroyValidationCacheEXT( + VkDevice device, + VkValidationCacheEXT validationCache, + const VkAllocationCallbacks* pAllocator); + +VKAPI_ATTR VkResult VKAPI_CALL vkMergeValidationCachesEXT( + VkDevice device, + VkValidationCacheEXT dstCache, + uint32_t srcCacheCount, + const VkValidationCacheEXT* pSrcCaches); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetValidationCacheDataEXT( + VkDevice device, + VkValidationCacheEXT validationCache, + size_t* pDataSize, + void* pData); +#endif + +#define VK_EXT_descriptor_indexing 1 +#define VK_EXT_DESCRIPTOR_INDEXING_SPEC_VERSION 2 +#define VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME "VK_EXT_descriptor_indexing" + + +typedef enum VkDescriptorBindingFlagBitsEXT { + VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT = 0x00000001, + VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT = 0x00000002, + VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT = 0x00000004, + VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT = 0x00000008, + VK_DESCRIPTOR_BINDING_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkDescriptorBindingFlagBitsEXT; +typedef VkFlags VkDescriptorBindingFlagsEXT; + +typedef struct VkDescriptorSetLayoutBindingFlagsCreateInfoEXT { + VkStructureType sType; + const void* pNext; + uint32_t bindingCount; + const VkDescriptorBindingFlagsEXT* pBindingFlags; +} VkDescriptorSetLayoutBindingFlagsCreateInfoEXT; + +typedef struct VkPhysicalDeviceDescriptorIndexingFeaturesEXT { + VkStructureType sType; + void* pNext; + VkBool32 shaderInputAttachmentArrayDynamicIndexing; + VkBool32 shaderUniformTexelBufferArrayDynamicIndexing; + VkBool32 shaderStorageTexelBufferArrayDynamicIndexing; + VkBool32 shaderUniformBufferArrayNonUniformIndexing; + VkBool32 shaderSampledImageArrayNonUniformIndexing; + VkBool32 shaderStorageBufferArrayNonUniformIndexing; + VkBool32 shaderStorageImageArrayNonUniformIndexing; + VkBool32 shaderInputAttachmentArrayNonUniformIndexing; + VkBool32 shaderUniformTexelBufferArrayNonUniformIndexing; + VkBool32 shaderStorageTexelBufferArrayNonUniformIndexing; + VkBool32 descriptorBindingUniformBufferUpdateAfterBind; + VkBool32 descriptorBindingSampledImageUpdateAfterBind; + VkBool32 descriptorBindingStorageImageUpdateAfterBind; + VkBool32 descriptorBindingStorageBufferUpdateAfterBind; + VkBool32 descriptorBindingUniformTexelBufferUpdateAfterBind; + VkBool32 descriptorBindingStorageTexelBufferUpdateAfterBind; + VkBool32 descriptorBindingUpdateUnusedWhilePending; + VkBool32 descriptorBindingPartiallyBound; + VkBool32 descriptorBindingVariableDescriptorCount; + VkBool32 runtimeDescriptorArray; +} VkPhysicalDeviceDescriptorIndexingFeaturesEXT; + +typedef struct VkPhysicalDeviceDescriptorIndexingPropertiesEXT { + VkStructureType sType; + void* pNext; + uint32_t maxUpdateAfterBindDescriptorsInAllPools; + VkBool32 shaderUniformBufferArrayNonUniformIndexingNative; + VkBool32 shaderSampledImageArrayNonUniformIndexingNative; + VkBool32 shaderStorageBufferArrayNonUniformIndexingNative; + VkBool32 shaderStorageImageArrayNonUniformIndexingNative; + VkBool32 shaderInputAttachmentArrayNonUniformIndexingNative; + VkBool32 robustBufferAccessUpdateAfterBind; + VkBool32 quadDivergentImplicitLod; + uint32_t maxPerStageDescriptorUpdateAfterBindSamplers; + uint32_t maxPerStageDescriptorUpdateAfterBindUniformBuffers; + uint32_t maxPerStageDescriptorUpdateAfterBindStorageBuffers; + uint32_t maxPerStageDescriptorUpdateAfterBindSampledImages; + uint32_t maxPerStageDescriptorUpdateAfterBindStorageImages; + uint32_t maxPerStageDescriptorUpdateAfterBindInputAttachments; + uint32_t maxPerStageUpdateAfterBindResources; + uint32_t maxDescriptorSetUpdateAfterBindSamplers; + uint32_t maxDescriptorSetUpdateAfterBindUniformBuffers; + uint32_t maxDescriptorSetUpdateAfterBindUniformBuffersDynamic; + uint32_t maxDescriptorSetUpdateAfterBindStorageBuffers; + uint32_t maxDescriptorSetUpdateAfterBindStorageBuffersDynamic; + uint32_t maxDescriptorSetUpdateAfterBindSampledImages; + uint32_t maxDescriptorSetUpdateAfterBindStorageImages; + uint32_t maxDescriptorSetUpdateAfterBindInputAttachments; +} VkPhysicalDeviceDescriptorIndexingPropertiesEXT; + +typedef struct VkDescriptorSetVariableDescriptorCountAllocateInfoEXT { + VkStructureType sType; + const void* pNext; + uint32_t descriptorSetCount; + const uint32_t* pDescriptorCounts; +} VkDescriptorSetVariableDescriptorCountAllocateInfoEXT; + +typedef struct VkDescriptorSetVariableDescriptorCountLayoutSupportEXT { + VkStructureType sType; + void* pNext; + uint32_t maxVariableDescriptorCount; +} VkDescriptorSetVariableDescriptorCountLayoutSupportEXT; + + + +#define VK_EXT_shader_viewport_index_layer 1 +#define VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_SPEC_VERSION 1 +#define VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME "VK_EXT_shader_viewport_index_layer" + + +#define VK_EXT_global_priority 1 +#define VK_EXT_GLOBAL_PRIORITY_SPEC_VERSION 2 +#define VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME "VK_EXT_global_priority" + + +typedef enum VkQueueGlobalPriorityEXT { + VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT = 128, + VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_EXT = 256, + VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT = 512, + VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT = 1024, + VK_QUEUE_GLOBAL_PRIORITY_BEGIN_RANGE_EXT = VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT, + VK_QUEUE_GLOBAL_PRIORITY_END_RANGE_EXT = VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT, + VK_QUEUE_GLOBAL_PRIORITY_RANGE_SIZE_EXT = (VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT - VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT + 1), + VK_QUEUE_GLOBAL_PRIORITY_MAX_ENUM_EXT = 0x7FFFFFFF +} VkQueueGlobalPriorityEXT; + +typedef struct VkDeviceQueueGlobalPriorityCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkQueueGlobalPriorityEXT globalPriority; +} VkDeviceQueueGlobalPriorityCreateInfoEXT; + + + +#define VK_EXT_external_memory_host 1 +#define VK_EXT_EXTERNAL_MEMORY_HOST_SPEC_VERSION 1 +#define VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME "VK_EXT_external_memory_host" + +typedef struct VkImportMemoryHostPointerInfoEXT { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagBits handleType; + void* pHostPointer; +} VkImportMemoryHostPointerInfoEXT; + +typedef struct VkMemoryHostPointerPropertiesEXT { + VkStructureType sType; + void* pNext; + uint32_t memoryTypeBits; +} VkMemoryHostPointerPropertiesEXT; + +typedef struct VkPhysicalDeviceExternalMemoryHostPropertiesEXT { + VkStructureType sType; + void* pNext; + VkDeviceSize minImportedHostPointerAlignment; +} VkPhysicalDeviceExternalMemoryHostPropertiesEXT; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryHostPointerPropertiesEXT)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, const void* pHostPointer, VkMemoryHostPointerPropertiesEXT* pMemoryHostPointerProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryHostPointerPropertiesEXT( + VkDevice device, + VkExternalMemoryHandleTypeFlagBits handleType, + const void* pHostPointer, + VkMemoryHostPointerPropertiesEXT* pMemoryHostPointerProperties); +#endif + +#define VK_AMD_buffer_marker 1 +#define VK_AMD_BUFFER_MARKER_SPEC_VERSION 1 +#define VK_AMD_BUFFER_MARKER_EXTENSION_NAME "VK_AMD_buffer_marker" + +typedef void (VKAPI_PTR *PFN_vkCmdWriteBufferMarkerAMD)(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage, VkBuffer dstBuffer, VkDeviceSize dstOffset, uint32_t marker); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR void VKAPI_CALL vkCmdWriteBufferMarkerAMD( + VkCommandBuffer commandBuffer, + VkPipelineStageFlagBits pipelineStage, + VkBuffer dstBuffer, + VkDeviceSize dstOffset, + uint32_t marker); +#endif + +#define VK_AMD_shader_core_properties 1 +#define VK_AMD_SHADER_CORE_PROPERTIES_SPEC_VERSION 1 +#define VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME "VK_AMD_shader_core_properties" + +typedef struct VkPhysicalDeviceShaderCorePropertiesAMD { + VkStructureType sType; + void* pNext; + uint32_t shaderEngineCount; + uint32_t shaderArraysPerEngineCount; + uint32_t computeUnitsPerShaderArray; + uint32_t simdPerComputeUnit; + uint32_t wavefrontsPerSimd; + uint32_t wavefrontSize; + uint32_t sgprsPerSimd; + uint32_t minSgprAllocation; + uint32_t maxSgprAllocation; + uint32_t sgprAllocationGranularity; + uint32_t vgprsPerSimd; + uint32_t minVgprAllocation; + uint32_t maxVgprAllocation; + uint32_t vgprAllocationGranularity; +} VkPhysicalDeviceShaderCorePropertiesAMD; + + + +#define VK_EXT_vertex_attribute_divisor 1 +#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_SPEC_VERSION 1 +#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME "VK_EXT_vertex_attribute_divisor" + +typedef struct VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT { + VkStructureType sType; + void* pNext; + uint32_t maxVertexAttribDivisor; +} VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT; + +typedef struct VkVertexInputBindingDivisorDescriptionEXT { + uint32_t binding; + uint32_t divisor; +} VkVertexInputBindingDivisorDescriptionEXT; + +typedef struct VkPipelineVertexInputDivisorStateCreateInfoEXT { + VkStructureType sType; + const void* pNext; + uint32_t vertexBindingDivisorCount; + const VkVertexInputBindingDivisorDescriptionEXT* pVertexBindingDivisors; +} VkPipelineVertexInputDivisorStateCreateInfoEXT; + + + +#define VK_NV_shader_subgroup_partitioned 1 +#define VK_NV_SHADER_SUBGROUP_PARTITIONED_SPEC_VERSION 1 +#define VK_NV_SHADER_SUBGROUP_PARTITIONED_EXTENSION_NAME "VK_NV_shader_subgroup_partitioned" + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_ios.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_ios.h new file mode 100644 index 0000000000..a0924816d5 --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_ios.h @@ -0,0 +1,58 @@ +#ifndef VULKAN_IOS_H_ +#define VULKAN_IOS_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/* +** This header is generated from the Khronos Vulkan XML API Registry. +** +*/ + + +#define VK_MVK_ios_surface 1 +#define VK_MVK_IOS_SURFACE_SPEC_VERSION 2 +#define VK_MVK_IOS_SURFACE_EXTENSION_NAME "VK_MVK_ios_surface" + +typedef VkFlags VkIOSSurfaceCreateFlagsMVK; + +typedef struct VkIOSSurfaceCreateInfoMVK { + VkStructureType sType; + const void* pNext; + VkIOSSurfaceCreateFlagsMVK flags; + const void* pView; +} VkIOSSurfaceCreateInfoMVK; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateIOSSurfaceMVK)(VkInstance instance, const VkIOSSurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateIOSSurfaceMVK( + VkInstance instance, + const VkIOSSurfaceCreateInfoMVK* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_macos.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_macos.h new file mode 100644 index 0000000000..ff0b701801 --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_macos.h @@ -0,0 +1,58 @@ +#ifndef VULKAN_MACOS_H_ +#define VULKAN_MACOS_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/* +** This header is generated from the Khronos Vulkan XML API Registry. +** +*/ + + +#define VK_MVK_macos_surface 1 +#define VK_MVK_MACOS_SURFACE_SPEC_VERSION 2 +#define VK_MVK_MACOS_SURFACE_EXTENSION_NAME "VK_MVK_macos_surface" + +typedef VkFlags VkMacOSSurfaceCreateFlagsMVK; + +typedef struct VkMacOSSurfaceCreateInfoMVK { + VkStructureType sType; + const void* pNext; + VkMacOSSurfaceCreateFlagsMVK flags; + const void* pView; +} VkMacOSSurfaceCreateInfoMVK; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateMacOSSurfaceMVK)(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateMacOSSurfaceMVK( + VkInstance instance, + const VkMacOSSurfaceCreateInfoMVK* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_win32.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_win32.h new file mode 100644 index 0000000000..6a85409ebe --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_win32.h @@ -0,0 +1,276 @@ +#ifndef VULKAN_WIN32_H_ +#define VULKAN_WIN32_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/* +** This header is generated from the Khronos Vulkan XML API Registry. +** +*/ + + +#define VK_KHR_win32_surface 1 +#define VK_KHR_WIN32_SURFACE_SPEC_VERSION 6 +#define VK_KHR_WIN32_SURFACE_EXTENSION_NAME "VK_KHR_win32_surface" + +typedef VkFlags VkWin32SurfaceCreateFlagsKHR; + +typedef struct VkWin32SurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkWin32SurfaceCreateFlagsKHR flags; + HINSTANCE hinstance; + HWND hwnd; +} VkWin32SurfaceCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); +typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateWin32SurfaceKHR( + VkInstance instance, + const VkWin32SurfaceCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface); + +VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWin32PresentationSupportKHR( + VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex); +#endif + +#define VK_KHR_external_memory_win32 1 +#define VK_KHR_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME "VK_KHR_external_memory_win32" + +typedef struct VkImportMemoryWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagBits handleType; + HANDLE handle; + LPCWSTR name; +} VkImportMemoryWin32HandleInfoKHR; + +typedef struct VkExportMemoryWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + const SECURITY_ATTRIBUTES* pAttributes; + DWORD dwAccess; + LPCWSTR name; +} VkExportMemoryWin32HandleInfoKHR; + +typedef struct VkMemoryWin32HandlePropertiesKHR { + VkStructureType sType; + void* pNext; + uint32_t memoryTypeBits; +} VkMemoryWin32HandlePropertiesKHR; + +typedef struct VkMemoryGetWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceMemory memory; + VkExternalMemoryHandleTypeFlagBits handleType; +} VkMemoryGetWin32HandleInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleKHR)(VkDevice device, const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle); +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandlePropertiesKHR)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, HANDLE handle, VkMemoryWin32HandlePropertiesKHR* pMemoryWin32HandleProperties); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleKHR( + VkDevice device, + const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo, + HANDLE* pHandle); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandlePropertiesKHR( + VkDevice device, + VkExternalMemoryHandleTypeFlagBits handleType, + HANDLE handle, + VkMemoryWin32HandlePropertiesKHR* pMemoryWin32HandleProperties); +#endif + +#define VK_KHR_win32_keyed_mutex 1 +#define VK_KHR_WIN32_KEYED_MUTEX_SPEC_VERSION 1 +#define VK_KHR_WIN32_KEYED_MUTEX_EXTENSION_NAME "VK_KHR_win32_keyed_mutex" + +typedef struct VkWin32KeyedMutexAcquireReleaseInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t acquireCount; + const VkDeviceMemory* pAcquireSyncs; + const uint64_t* pAcquireKeys; + const uint32_t* pAcquireTimeouts; + uint32_t releaseCount; + const VkDeviceMemory* pReleaseSyncs; + const uint64_t* pReleaseKeys; +} VkWin32KeyedMutexAcquireReleaseInfoKHR; + + + +#define VK_KHR_external_semaphore_win32 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_WIN32_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME "VK_KHR_external_semaphore_win32" + +typedef struct VkImportSemaphoreWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + VkSemaphore semaphore; + VkSemaphoreImportFlags flags; + VkExternalSemaphoreHandleTypeFlagBits handleType; + HANDLE handle; + LPCWSTR name; +} VkImportSemaphoreWin32HandleInfoKHR; + +typedef struct VkExportSemaphoreWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + const SECURITY_ATTRIBUTES* pAttributes; + DWORD dwAccess; + LPCWSTR name; +} VkExportSemaphoreWin32HandleInfoKHR; + +typedef struct VkD3D12FenceSubmitInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t waitSemaphoreValuesCount; + const uint64_t* pWaitSemaphoreValues; + uint32_t signalSemaphoreValuesCount; + const uint64_t* pSignalSemaphoreValues; +} VkD3D12FenceSubmitInfoKHR; + +typedef struct VkSemaphoreGetWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + VkSemaphore semaphore; + VkExternalSemaphoreHandleTypeFlagBits handleType; +} VkSemaphoreGetWin32HandleInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreWin32HandleKHR)(VkDevice device, const VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo); +typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreWin32HandleKHR)(VkDevice device, const VkSemaphoreGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreWin32HandleKHR( + VkDevice device, + const VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreWin32HandleKHR( + VkDevice device, + const VkSemaphoreGetWin32HandleInfoKHR* pGetWin32HandleInfo, + HANDLE* pHandle); +#endif + +#define VK_KHR_external_fence_win32 1 +#define VK_KHR_EXTERNAL_FENCE_WIN32_SPEC_VERSION 1 +#define VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME "VK_KHR_external_fence_win32" + +typedef struct VkImportFenceWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + VkFence fence; + VkFenceImportFlags flags; + VkExternalFenceHandleTypeFlagBits handleType; + HANDLE handle; + LPCWSTR name; +} VkImportFenceWin32HandleInfoKHR; + +typedef struct VkExportFenceWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + const SECURITY_ATTRIBUTES* pAttributes; + DWORD dwAccess; + LPCWSTR name; +} VkExportFenceWin32HandleInfoKHR; + +typedef struct VkFenceGetWin32HandleInfoKHR { + VkStructureType sType; + const void* pNext; + VkFence fence; + VkExternalFenceHandleTypeFlagBits handleType; +} VkFenceGetWin32HandleInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkImportFenceWin32HandleKHR)(VkDevice device, const VkImportFenceWin32HandleInfoKHR* pImportFenceWin32HandleInfo); +typedef VkResult (VKAPI_PTR *PFN_vkGetFenceWin32HandleKHR)(VkDevice device, const VkFenceGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkImportFenceWin32HandleKHR( + VkDevice device, + const VkImportFenceWin32HandleInfoKHR* pImportFenceWin32HandleInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceWin32HandleKHR( + VkDevice device, + const VkFenceGetWin32HandleInfoKHR* pGetWin32HandleInfo, + HANDLE* pHandle); +#endif + +#define VK_NV_external_memory_win32 1 +#define VK_NV_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1 +#define VK_NV_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME "VK_NV_external_memory_win32" + +typedef struct VkImportMemoryWin32HandleInfoNV { + VkStructureType sType; + const void* pNext; + VkExternalMemoryHandleTypeFlagsNV handleType; + HANDLE handle; +} VkImportMemoryWin32HandleInfoNV; + +typedef struct VkExportMemoryWin32HandleInfoNV { + VkStructureType sType; + const void* pNext; + const SECURITY_ATTRIBUTES* pAttributes; + DWORD dwAccess; +} VkExportMemoryWin32HandleInfoNV; + + +typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleNV)(VkDevice device, VkDeviceMemory memory, VkExternalMemoryHandleTypeFlagsNV handleType, HANDLE* pHandle); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleNV( + VkDevice device, + VkDeviceMemory memory, + VkExternalMemoryHandleTypeFlagsNV handleType, + HANDLE* pHandle); +#endif + +#define VK_NV_win32_keyed_mutex 1 +#define VK_NV_WIN32_KEYED_MUTEX_SPEC_VERSION 1 +#define VK_NV_WIN32_KEYED_MUTEX_EXTENSION_NAME "VK_NV_win32_keyed_mutex" + +typedef struct VkWin32KeyedMutexAcquireReleaseInfoNV { + VkStructureType sType; + const void* pNext; + uint32_t acquireCount; + const VkDeviceMemory* pAcquireSyncs; + const uint64_t* pAcquireKeys; + const uint32_t* pAcquireTimeoutMilliseconds; + uint32_t releaseCount; + const VkDeviceMemory* pReleaseSyncs; + const uint64_t* pReleaseKeys; +} VkWin32KeyedMutexAcquireReleaseInfoNV; + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_xcb.h b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_xcb.h new file mode 100644 index 0000000000..ba03600602 --- /dev/null +++ b/gfx/skia/skia/include/third_party/vulkan/vulkan/vulkan_xcb.h @@ -0,0 +1,66 @@ +#ifndef VULKAN_XCB_H_ +#define VULKAN_XCB_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2015-2018 The Khronos Group Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/* +** This header is generated from the Khronos Vulkan XML API Registry. +** +*/ + + +#define VK_KHR_xcb_surface 1 +#define VK_KHR_XCB_SURFACE_SPEC_VERSION 6 +#define VK_KHR_XCB_SURFACE_EXTENSION_NAME "VK_KHR_xcb_surface" + +typedef VkFlags VkXcbSurfaceCreateFlagsKHR; + +typedef struct VkXcbSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkXcbSurfaceCreateFlagsKHR flags; + xcb_connection_t* connection; + xcb_window_t window; +} VkXcbSurfaceCreateInfoKHR; + + +typedef VkResult (VKAPI_PTR *PFN_vkCreateXcbSurfaceKHR)(VkInstance instance, const VkXcbSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); +typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, xcb_connection_t* connection, xcb_visualid_t visual_id); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkCreateXcbSurfaceKHR( + VkInstance instance, + const VkXcbSurfaceCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSurfaceKHR* pSurface); + +VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXcbPresentationSupportKHR( + VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + xcb_connection_t* connection, + xcb_visualid_t visual_id); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/skia/skia/include/utils/Sk3D.h b/gfx/skia/skia/include/utils/Sk3D.h new file mode 100644 index 0000000000..9b233ba0c9 --- /dev/null +++ b/gfx/skia/skia/include/utils/Sk3D.h @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk3D_DEFINED +#define Sk3D_DEFINED + +#include "include/core/SkMatrix44.h" +#include "include/core/SkPoint3.h" + +SK_API void Sk3LookAt(SkMatrix44* dst, const SkPoint3& eye, const SkPoint3& center, const SkPoint3& up); +SK_API bool Sk3Perspective(SkMatrix44* dst, float near, float far, float angle); +SK_API void Sk3MapPts(SkPoint dst[], const SkMatrix44& m4, const SkPoint3 src[], int count); + +#endif + diff --git a/gfx/skia/skia/include/utils/SkAnimCodecPlayer.h b/gfx/skia/skia/include/utils/SkAnimCodecPlayer.h new file mode 100644 index 0000000000..40660e13a5 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkAnimCodecPlayer.h @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAnimCodecPlayer_DEFINED +#define SkAnimCodecPlayer_DEFINED + +#include "include/codec/SkCodec.h" + +class SkImage; + +class SkAnimCodecPlayer { +public: + SkAnimCodecPlayer(std::unique_ptr codec); + ~SkAnimCodecPlayer(); + + /** + * Returns the current frame of the animation. This defaults to the first frame for + * animated codecs (i.e. msec = 0). Calling this multiple times (without calling seek()) + * will always return the same image object (or null if there was an error). + */ + sk_sp getFrame(); + + /** + * Return the size of the image(s) that will be returned by getFrame(). + */ + SkISize dimensions(); + + /** + * Returns the total duration of the animation in milliseconds. Returns 0 for a single-frame + * image. + */ + uint32_t duration() { return fTotalDuration; } + + /** + * Finds the closest frame associated with the time code (in milliseconds) and sets that + * to be the current frame (call getFrame() to retrieve that image). + * Returns true iff this call to seek() changed the "current frame" for the animation. + * Thus if seek() returns false, then getFrame() will return the same image as it did + * before this call to seek(). + */ + bool seek(uint32_t msec); + + +private: + std::unique_ptr fCodec; + SkImageInfo fImageInfo; + std::vector fFrameInfos; + std::vector > fImages; + int fCurrIndex = 0; + uint32_t fTotalDuration; + + sk_sp getFrameAt(int index); +}; + +#endif + diff --git a/gfx/skia/skia/include/utils/SkBase64.h b/gfx/skia/skia/include/utils/SkBase64.h new file mode 100644 index 0000000000..d547cb4d0a --- /dev/null +++ b/gfx/skia/skia/include/utils/SkBase64.h @@ -0,0 +1,39 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBase64_DEFINED +#define SkBase64_DEFINED + +#include "include/core/SkTypes.h" + +struct SkBase64 { +public: + enum Error { + kNoError, + kPadError, + kBadCharError + }; + + SkBase64(); + Error decode(const char* src, size_t length); + char* getData() { return fData; } + /** + Base64 encodes src into dst. encode is a pointer to at least 65 chars. + encode[64] will be used as the pad character. Encodings other than the + default encoding cannot be decoded. + */ + static size_t Encode(const void* src, size_t length, void* dest, const char* encode = nullptr); + +private: + Error decode(const void* srcPtr, size_t length, bool writeDestination); + + size_t fLength; + char* fData; + friend class SkImageBaseBitmap; +}; + +#endif // SkBase64_DEFINED diff --git a/gfx/skia/skia/include/utils/SkCamera.h b/gfx/skia/skia/include/utils/SkCamera.h new file mode 100644 index 0000000000..c216b6d6c4 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkCamera.h @@ -0,0 +1,154 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Inspired by Rob Johnson's most excellent QuickDraw GX sample code + +#ifndef SkCamera_DEFINED +#define SkCamera_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/private/SkNoncopyable.h" + +class SkCanvas; + +struct SkUnit3D { + SkScalar fX, fY, fZ; + + void set(SkScalar x, SkScalar y, SkScalar z) { + fX = x; fY = y; fZ = z; + } + static SkScalar Dot(const SkUnit3D&, const SkUnit3D&); + static void Cross(const SkUnit3D&, const SkUnit3D&, SkUnit3D* cross); +}; + +struct SkPoint3D { + SkScalar fX, fY, fZ; + + void set(SkScalar x, SkScalar y, SkScalar z) { + fX = x; fY = y; fZ = z; + } + SkScalar normalize(SkUnit3D*) const; +}; +typedef SkPoint3D SkVector3D; + +struct SkMatrix3D { + SkScalar fMat[3][4]; + + void reset(); + + void setRow(int row, SkScalar a, SkScalar b, SkScalar c, SkScalar d = 0) { + SkASSERT((unsigned)row < 3); + fMat[row][0] = a; + fMat[row][1] = b; + fMat[row][2] = c; + fMat[row][3] = d; + } + + void setRotateX(SkScalar deg); + void setRotateY(SkScalar deg); + void setRotateZ(SkScalar deg); + void setTranslate(SkScalar x, SkScalar y, SkScalar z); + + void preRotateX(SkScalar deg); + void preRotateY(SkScalar deg); + void preRotateZ(SkScalar deg); + void preTranslate(SkScalar x, SkScalar y, SkScalar z); + + void setConcat(const SkMatrix3D& a, const SkMatrix3D& b); + void mapPoint(const SkPoint3D& src, SkPoint3D* dst) const; + void mapVector(const SkVector3D& src, SkVector3D* dst) const; + + void mapPoint(SkPoint3D* v) const { + this->mapPoint(*v, v); + } + + void mapVector(SkVector3D* v) const { + this->mapVector(*v, v); + } +}; + +class SkPatch3D { +public: + SkPatch3D(); + + void reset(); + void transform(const SkMatrix3D&, SkPatch3D* dst = nullptr) const; + + // dot a unit vector with the patch's normal + SkScalar dotWith(SkScalar dx, SkScalar dy, SkScalar dz) const; + SkScalar dotWith(const SkVector3D& v) const { + return this->dotWith(v.fX, v.fY, v.fZ); + } + + // deprecated, but still here for animator (for now) + void rotate(SkScalar /*x*/, SkScalar /*y*/, SkScalar /*z*/) {} + void rotateDegrees(SkScalar /*x*/, SkScalar /*y*/, SkScalar /*z*/) {} + +private: +public: // make public for SkDraw3D for now + SkVector3D fU, fV; + SkPoint3D fOrigin; + + friend class SkCamera3D; +}; + +class SkCamera3D { +public: + SkCamera3D(); + + void reset(); + void update(); + void patchToMatrix(const SkPatch3D&, SkMatrix* matrix) const; + + SkPoint3D fLocation; // origin of the camera's space + SkPoint3D fAxis; // view direction + SkPoint3D fZenith; // up direction + SkPoint3D fObserver; // eye position (may not be the same as the origin) + +private: + mutable SkMatrix fOrientation; + mutable bool fNeedToUpdate; + + void doUpdate() const; +}; + +class SK_API Sk3DView : SkNoncopyable { +public: + Sk3DView(); + ~Sk3DView(); + + void save(); + void restore(); + + void translate(SkScalar x, SkScalar y, SkScalar z); + void rotateX(SkScalar deg); + void rotateY(SkScalar deg); + void rotateZ(SkScalar deg); + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + void setCameraLocation(SkScalar x, SkScalar y, SkScalar z); + SkScalar getCameraLocationX() const; + SkScalar getCameraLocationY() const; + SkScalar getCameraLocationZ() const; +#endif + + void getMatrix(SkMatrix*) const; + void applyToCanvas(SkCanvas*) const; + + SkScalar dotWithNormal(SkScalar dx, SkScalar dy, SkScalar dz) const; + +private: + struct Rec { + Rec* fNext; + SkMatrix3D fMatrix; + }; + Rec* fRec; + Rec fInitialRec; + SkCamera3D fCamera; +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkCanvasStateUtils.h b/gfx/skia/skia/include/utils/SkCanvasStateUtils.h new file mode 100644 index 0000000000..8b5c65ec08 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkCanvasStateUtils.h @@ -0,0 +1,78 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCanvasStateUtils_DEFINED +#define SkCanvasStateUtils_DEFINED + +#include "include/core/SkCanvas.h" + +class SkCanvasState; + +/** + * A set of functions that are useful for copying the state of an SkCanvas + * across a library boundary where the Skia library on the other side of the + * boundary may be newer. The expected usage is outline below... + * + * Lib Boundary + * CaptureCanvasState(...) ||| + * SkCanvas --> SkCanvasState ||| + * ||| CreateFromCanvasState(...) + * ||| SkCanvasState --> SkCanvas` + * ||| Draw into SkCanvas` + * ||| Unref SkCanvas` + * ReleaseCanvasState(...) ||| + * + */ +class SK_API SkCanvasStateUtils { +public: + /** + * Captures the current state of the canvas into an opaque ptr that is safe + * to pass to a different instance of Skia (which may be the same version, + * or may be newer). The function will return NULL in the event that one of the + * following conditions are true. + * 1) the canvas device type is not supported (currently only raster is supported) + * 2) the canvas clip type is not supported (currently only non-AA clips are supported) + * + * It is recommended that the original canvas also not be used until all + * canvases that have been created using its captured state have been dereferenced. + * + * Finally, it is important to note that any draw filters attached to the + * canvas are NOT currently captured. + * + * @param canvas The canvas you wish to capture the current state of. + * @return NULL or an opaque ptr that can be passed to CreateFromCanvasState + * to reconstruct the canvas. The caller is responsible for calling + * ReleaseCanvasState to free the memory associated with this state. + */ + static SkCanvasState* CaptureCanvasState(SkCanvas* canvas); + + /** + * Create a new SkCanvas from the captured state of another SkCanvas. The + * function will return NULL in the event that one of the + * following conditions are true. + * 1) the captured state is in an unrecognized format + * 2) the captured canvas device type is not supported + * + * @param state Opaque object created by CaptureCanvasState. + * @return NULL or an SkCanvas* whose devices and matrix/clip state are + * identical to the captured canvas. The caller is responsible for + * calling unref on the SkCanvas. + */ + static std::unique_ptr MakeFromCanvasState(const SkCanvasState* state); + + /** + * Free the memory associated with the captured canvas state. The state + * should not be released until all SkCanvas objects created using that + * state have been dereferenced. Must be called from the same library + * instance that created the state via CaptureCanvasState. + * + * @param state The captured state you wish to dispose of. + */ + static void ReleaseCanvasState(SkCanvasState* state); +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkEventTracer.h b/gfx/skia/skia/include/utils/SkEventTracer.h new file mode 100644 index 0000000000..03e9d5564d --- /dev/null +++ b/gfx/skia/skia/include/utils/SkEventTracer.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 Google Inc. All rights reserved. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEventTracer_DEFINED +#define SkEventTracer_DEFINED + +// The class in this header defines the interface between Skia's internal +// tracing macros and an external entity (e.g., Chrome) that will consume them. +// Such an entity should subclass SkEventTracer and provide an instance of +// that event to SkEventTracer::SetInstance. + +// If you're looking for the tracing macros to instrument Skia itself, those +// live in src/core/SkTraceEvent.h + +#include "include/core/SkTypes.h" + +class SK_API SkEventTracer { +public: + + typedef uint64_t Handle; + + /** + * If this is the first call to SetInstance or GetInstance then the passed instance is + * installed and true is returned. Otherwise, false is returned. In either case ownership of the + * tracer is transferred and it will be deleted when no longer needed. + */ + static bool SetInstance(SkEventTracer*); + + /** + * Gets the event tracer. If this is the first call to SetInstance or GetIntance then a default + * event tracer is installed and returned. + */ + static SkEventTracer* GetInstance(); + + virtual ~SkEventTracer() { } + + // The pointer returned from GetCategoryGroupEnabled() points to a + // value with zero or more of the following bits. Used in this class only. + // The TRACE_EVENT macros should only use the value as a bool. + // These values must be in sync with macro values in trace_event.h in chromium. + enum CategoryGroupEnabledFlags { + // Category group enabled for the recording mode. + kEnabledForRecording_CategoryGroupEnabledFlags = 1 << 0, + // Category group enabled for the monitoring mode. + kEnabledForMonitoring_CategoryGroupEnabledFlags = 1 << 1, + // Category group enabled by SetEventCallbackEnabled(). + kEnabledForEventCallback_CategoryGroupEnabledFlags = 1 << 2, + }; + + virtual const uint8_t* getCategoryGroupEnabled(const char* name) = 0; + virtual const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag) = 0; + + virtual SkEventTracer::Handle + addTraceEvent(char phase, + const uint8_t* categoryEnabledFlag, + const char* name, + uint64_t id, + int32_t numArgs, + const char** argNames, + const uint8_t* argTypes, + const uint64_t* argValues, + uint8_t flags) = 0; + + virtual void + updateTraceEventDuration(const uint8_t* categoryEnabledFlag, + const char* name, + SkEventTracer::Handle handle) = 0; +}; + +#endif // SkEventTracer_DEFINED diff --git a/gfx/skia/skia/include/utils/SkFrontBufferedStream.h b/gfx/skia/skia/include/utils/SkFrontBufferedStream.h new file mode 100644 index 0000000000..52a0178b63 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkFrontBufferedStream.h @@ -0,0 +1,39 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFrontBufferedStream_DEFINED +#define SkFrontBufferedStream_DEFINED + +#include "include/core/SkStream.h" + +/** + * Specialized stream that buffers the first X bytes of a stream, + * where X is passed in by the user. Note that unlike some buffered + * stream APIs, once more bytes than can fit in the buffer are read, + * no more buffering is done. This stream is designed for a use case + * where the caller knows that rewind will only be called from within + * X bytes (inclusive), and the wrapped stream is not necessarily + * able to rewind at all. + */ +class SK_API SkFrontBufferedStream { +public: + /** + * Creates a new stream that wraps and buffers an SkStream. + * @param stream SkStream to buffer. If stream is NULL, NULL is + * returned. When this call succeeds (i.e. returns non NULL), + * SkFrontBufferedStream is expected to be the only owner of + * stream, so it should no be longer used directly. + * SkFrontBufferedStream will delete stream upon deletion. + * @param minBufferSize Minimum size of buffer required. + * @return An SkStream that can buffer at least minBufferSize, or + * NULL on failure. The caller is required to delete when finished with + * this object. + */ + static std::unique_ptr Make(std::unique_ptr stream, + size_t minBufferSize); +}; +#endif // SkFrontBufferedStream_DEFINED diff --git a/gfx/skia/skia/include/utils/SkInterpolator.h b/gfx/skia/skia/include/utils/SkInterpolator.h new file mode 100644 index 0000000000..ac08ca44f3 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkInterpolator.h @@ -0,0 +1,138 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkInterpolator_DEFINED +#define SkInterpolator_DEFINED + +#include "include/core/SkScalar.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTo.h" + +class SK_API SkInterpolatorBase : SkNoncopyable { +public: + enum Result { + kNormal_Result, + kFreezeStart_Result, + kFreezeEnd_Result + }; +protected: + SkInterpolatorBase(); + ~SkInterpolatorBase(); +public: + void reset(int elemCount, int frameCount); + + /** Return the start and end time for this interpolator. + If there are no key frames, return false. + @param startTime If not null, returns the time (in milliseconds) of the + first keyframe. If there are no keyframes, this param + is ignored (left unchanged). + @param endTime If not null, returns the time (in milliseconds) of the + last keyframe. If there are no keyframes, this parameter + is ignored (left unchanged). + @return True if there are key frames, or false if there are none. + */ + bool getDuration(SkMSec* startTime, SkMSec* endTime) const; + + + /** Set the whether the repeat is mirrored. + @param mirror If true, the odd repeats interpolate from the last key + frame and the first. + */ + void setMirror(bool mirror) { + fFlags = SkToU8((fFlags & ~kMirror) | (int)mirror); + } + + /** Set the repeat count. The repeat count may be fractional. + @param repeatCount Multiplies the total time by this scalar. + */ + void setRepeatCount(SkScalar repeatCount) { fRepeat = repeatCount; } + + /** Set the whether the repeat is mirrored. + @param reset If true, the odd repeats interpolate from the last key + frame and the first. + */ + void setReset(bool reset) { + fFlags = SkToU8((fFlags & ~kReset) | (int)reset); + } + + Result timeToT(SkMSec time, SkScalar* T, int* index, bool* exact) const; + +protected: + enum Flags { + kMirror = 1, + kReset = 2, + kHasBlend = 4 + }; + static SkScalar ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime, + const SkScalar blend[4] = nullptr); + int16_t fFrameCount; + uint8_t fElemCount; + uint8_t fFlags; + SkScalar fRepeat; + struct SkTimeCode { + SkMSec fTime; + SkScalar fBlend[4]; + }; + SkTimeCode* fTimes; // pointer into fStorage + void* fStorage; +#ifdef SK_DEBUG + SkTimeCode(* fTimesArray)[10]; +#endif +}; + +class SK_API SkInterpolator : public SkInterpolatorBase { +public: + SkInterpolator(); + SkInterpolator(int elemCount, int frameCount); + void reset(int elemCount, int frameCount); + + /** Add or replace a key frame, copying the values[] data into the + interpolator. + @param index The index of this frame (frames must be ordered by time) + @param time The millisecond time for this frame + @param values The array of values [elemCount] for this frame. The data + is copied into the interpolator. + @param blend A positive scalar specifying how to blend between this + and the next key frame. [0...1) is a cubic lag/log/lag + blend (slow to change at the beginning and end) + 1 is a linear blend (default) + */ + bool setKeyFrame(int index, SkMSec time, const SkScalar values[], + const SkScalar blend[4] = nullptr); + + /** Return the computed values given the specified time. Return whether + those values are the result of pinning to either the first + (kFreezeStart) or last (kFreezeEnd), or from interpolated the two + nearest key values (kNormal). + @param time The time to sample (in milliseconds) + @param (may be null) where to write the computed values. + */ + Result timeToValues(SkMSec time, SkScalar values[] = nullptr) const; + +private: + SkScalar* fValues; // pointer into fStorage +#ifdef SK_DEBUG + SkScalar(* fScalarsArray)[10]; +#endif + typedef SkInterpolatorBase INHERITED; +}; + +/** Interpolate a cubic curve, typically to provide an ease-in ease-out transition. + All the parameters are in the range of [0...1]. + The input value is treated as the x-coordinate of the cubic. + The output value is the y-coordinate on the cubic at the x-coordinate. + + @param value The x-coordinate pinned between [0..1]. + @param bx,by,cx,cy The cubic control points where the cubic is specified + as (0,0) (bx,by) (cx,cy) (1,1) + @return the corresponding y-coordinate value, from [0..1]. +*/ +SkScalar SkUnitCubicInterp(SkScalar value, SkScalar bx, SkScalar by, + SkScalar cx, SkScalar cy); + +#endif diff --git a/gfx/skia/skia/include/utils/SkLua.h b/gfx/skia/skia/include/utils/SkLua.h new file mode 100644 index 0000000000..2d963eff13 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkLua.h @@ -0,0 +1,69 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLua_DEFINED +#define SkLua_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkScalar.h" +#include "include/core/SkString.h" + +struct lua_State; + +class SkCanvas; +class SkMatrix; +class SkPaint; +class SkPath; +struct SkRect; +class SkRRect; +class SkTextBlob; + +#define SkScalarToLua(x) SkScalarToDouble(x) +#define SkLuaToScalar(x) SkDoubleToScalar(x) + +class SkLua { +public: + static void Load(lua_State*); + + SkLua(const char termCode[] = nullptr); // creates a new L, will close it + SkLua(lua_State*); // uses L, will not close it + ~SkLua(); + + lua_State* get() const { return fL; } + lua_State* operator*() const { return fL; } + lua_State* operator->() const { return fL; } + + bool runCode(const char code[]); + bool runCode(const void* code, size_t size); + + void pushBool(bool, const char tableKey[] = nullptr); + void pushString(const char[], const char tableKey[] = nullptr); + void pushString(const char[], size_t len, const char tableKey[] = nullptr); + void pushString(const SkString&, const char tableKey[] = nullptr); + void pushArrayU16(const uint16_t[], int count, const char tableKey[] = nullptr); + void pushArrayPoint(const SkPoint[], int count, const char key[] = nullptr); + void pushArrayScalar(const SkScalar[], int count, const char key[] = nullptr); + void pushColor(SkColor, const char tableKey[] = nullptr); + void pushU32(uint32_t, const char tableKey[] = nullptr); + void pushScalar(SkScalar, const char tableKey[] = nullptr); + void pushRect(const SkRect&, const char tableKey[] = nullptr); + void pushRRect(const SkRRect&, const char tableKey[] = nullptr); + void pushDash(const SkPathEffect::DashInfo&, const char tableKey[] = nullptr); + void pushMatrix(const SkMatrix&, const char tableKey[] = nullptr); + void pushPaint(const SkPaint&, const char tableKey[] = nullptr); + void pushPath(const SkPath&, const char tableKey[] = nullptr); + void pushCanvas(SkCanvas*, const char tableKey[] = nullptr); + void pushTextBlob(const SkTextBlob*, const char tableKey[] = nullptr); + +private: + lua_State* fL; + SkString fTermCode; + bool fWeOwnL; +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkLuaCanvas.h b/gfx/skia/skia/include/utils/SkLuaCanvas.h new file mode 100644 index 0000000000..f75c8f219c --- /dev/null +++ b/gfx/skia/skia/include/utils/SkLuaCanvas.h @@ -0,0 +1,72 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLuaCanvas_DEFINED +#define SkLuaCanvas_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkString.h" +#include "include/core/SkVertices.h" + +struct lua_State; + +class SkLuaCanvas : public SkCanvas { +public: + void pushThis(); + + SkLuaCanvas(int width, int height, lua_State*, const char function[]); + ~SkLuaCanvas() override; + +protected: + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + bool onDoSaveBehind(const SkRect*) override; + void willRestore() override; + + void didConcat(const SkMatrix&) override; + void didSetMatrix(const SkMatrix&) override; + + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + + void onDrawPaint(const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*, + SrcRectConstraint) override; + void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; + + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + +private: + lua_State* fL; + SkString fFunc; + + void sendverb(const char verb[]); + + typedef SkCanvas INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkNWayCanvas.h b/gfx/skia/skia/include/utils/SkNWayCanvas.h new file mode 100644 index 0000000000..72bed680bf --- /dev/null +++ b/gfx/skia/skia/include/utils/SkNWayCanvas.h @@ -0,0 +1,94 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNWayCanvas_DEFINED +#define SkNWayCanvas_DEFINED + +#include "include/core/SkCanvasVirtualEnforcer.h" +#include "include/private/SkTDArray.h" +#include "include/utils/SkNoDrawCanvas.h" + +class SK_API SkNWayCanvas : public SkCanvasVirtualEnforcer { +public: + SkNWayCanvas(int width, int height); + ~SkNWayCanvas() override; + + virtual void addCanvas(SkCanvas*); + virtual void removeCanvas(SkCanvas*); + virtual void removeAll(); + +protected: + SkTDArray fList; + + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + bool onDoSaveBehind(const SkRect*) override; + void willRestore() override; + + void didConcat(const SkMatrix&) override; + void didSetMatrix(const SkMatrix&) override; + + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode, + const SkPaint& paint) override; + + void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*, + SrcRectConstraint) override; + void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) override; + void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&, + const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override; + void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], + int, SkBlendMode, const SkRect*, const SkPaint*) override; + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; + + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + void onDrawAnnotation(const SkRect&, const char[], SkData*) override; + + void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, const SkColor4f&, + SkBlendMode) override; + void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[], + const SkPaint*, SrcRectConstraint) override; + + void onFlush() override; + + class Iter; + +private: + typedef SkCanvasVirtualEnforcer INHERITED; +}; + + +#endif diff --git a/gfx/skia/skia/include/utils/SkNoDrawCanvas.h b/gfx/skia/skia/include/utils/SkNoDrawCanvas.h new file mode 100644 index 0000000000..a68d41a0fe --- /dev/null +++ b/gfx/skia/skia/include/utils/SkNoDrawCanvas.h @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNoDrawCanvas_DEFINED +#define SkNoDrawCanvas_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkCanvasVirtualEnforcer.h" +#include "include/core/SkVertices.h" + +struct SkIRect; + +// SkNoDrawCanvas is a helper for SkCanvas subclasses which do not need to +// actually rasterize (e.g., analysis of the draw calls). +// +// It provides the following simplifications: +// +// * not backed by any device/pixels +// * conservative clipping (clipping calls only use rectangles) +// +class SK_API SkNoDrawCanvas : public SkCanvasVirtualEnforcer { +public: + SkNoDrawCanvas(int width, int height); + SkNoDrawCanvas(const SkIRect&); + + explicit SkNoDrawCanvas(sk_sp device); + + // Optimization to reset state to be the same as after construction. + void resetCanvas(int w, int h) { this->resetForNextPicture(SkIRect::MakeWH(w, h)); } + void resetCanvas(const SkIRect& rect) { this->resetForNextPicture(rect); } + +protected: + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override; + bool onDoSaveBehind(const SkRect*) override; + + // No-op overrides for aborting rasterization earlier than SkNullBlitter. + void onDrawAnnotation(const SkRect&, const char[], SkData*) override {} + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override {} + void onDrawDrawable(SkDrawable*, const SkMatrix*) override {} + void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override {} + void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode, + const SkPaint&) override {} + + void onDrawPaint(const SkPaint&) override {} + void onDrawBehind(const SkPaint&) override {} + void onDrawPoints(PointMode, size_t, const SkPoint[], const SkPaint&) override {} + void onDrawRect(const SkRect&, const SkPaint&) override {} + void onDrawRegion(const SkRegion&, const SkPaint&) override {} + void onDrawOval(const SkRect&, const SkPaint&) override {} + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override {} + void onDrawRRect(const SkRRect&, const SkPaint&) override {} + void onDrawPath(const SkPath&, const SkPaint&) override {} + void onDrawBitmap(const SkBitmap&, SkScalar, SkScalar, const SkPaint*) override {} + void onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) override {} + void onDrawImage(const SkImage*, SkScalar, SkScalar, const SkPaint*) override {} + void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) override {} + void onDrawImageNine(const SkImage*, const SkIRect&, const SkRect&, const SkPaint*) override {} + void onDrawBitmapNine(const SkBitmap&, const SkIRect&, const SkRect&, + const SkPaint*) override {} + void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, + const SkPaint*) override {} + void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&, + const SkPaint*) override {} + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode, + const SkPaint&) override {} + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], + int, SkBlendMode, const SkRect*, const SkPaint*) override {} + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override {} + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override {} + + void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, const SkColor4f&, + SkBlendMode) override {} + void onDrawEdgeAAImageSet(const ImageSetEntry[], int, const SkPoint[], + const SkMatrix[], const SkPaint*, SrcRectConstraint) override {} + +private: + typedef SkCanvasVirtualEnforcer INHERITED; +}; + +#endif // SkNoDrawCanvas_DEFINED diff --git a/gfx/skia/skia/include/utils/SkNullCanvas.h b/gfx/skia/skia/include/utils/SkNullCanvas.h new file mode 100644 index 0000000000..d63bf5c414 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkNullCanvas.h @@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNullCanvas_DEFINED +#define SkNullCanvas_DEFINED + +#include "include/core/SkCanvas.h" + +/** + * Creates a canvas that draws nothing. This is useful for performance testing. + */ +SK_API std::unique_ptr SkMakeNullCanvas(); + +#endif diff --git a/gfx/skia/skia/include/utils/SkPaintFilterCanvas.h b/gfx/skia/skia/include/utils/SkPaintFilterCanvas.h new file mode 100644 index 0000000000..3583e904ee --- /dev/null +++ b/gfx/skia/skia/include/utils/SkPaintFilterCanvas.h @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPaintFilterCanvas_DEFINED +#define SkPaintFilterCanvas_DEFINED + +#include "include/core/SkCanvasVirtualEnforcer.h" +#include "include/utils/SkNWayCanvas.h" + +class SkAndroidFrameworkUtils; + +/** \class SkPaintFilterCanvas + + A utility proxy base class for implementing draw/paint filters. +*/ +class SK_API SkPaintFilterCanvas : public SkCanvasVirtualEnforcer { +public: + /** + * The new SkPaintFilterCanvas is configured for forwarding to the + * specified canvas. Also copies the target canvas matrix and clip bounds. + */ + SkPaintFilterCanvas(SkCanvas* canvas); + + enum Type { + kPicture_Type, + }; + + // Forwarded to the wrapped canvas. + SkISize getBaseLayerSize() const override { return proxy()->getBaseLayerSize(); } + GrContext* getGrContext() override { return proxy()->getGrContext(); } + GrRenderTargetContext* internal_private_accessTopLayerRenderTargetContext() override { + return proxy()->internal_private_accessTopLayerRenderTargetContext(); + } + +protected: + /** + * Called with the paint that will be used to draw the specified type. + * The implementation may modify the paint as they wish. + * + * The result bool is used to determine whether the draw op is to be + * executed (true) or skipped (false). + * + * Note: The base implementation calls onFilter() for top-level/explicit paints only. + * To also filter encapsulated paints (e.g. SkPicture, SkTextBlob), clients may need to + * override the relevant methods (i.e. drawPicture, drawTextBlob). + */ + virtual bool onFilter(SkPaint& paint) const = 0; + + void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*, + SrcRectConstraint) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&, + const SkPaint*) override; + void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) override; + void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, + const SkPaint*) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode, + const SkPaint& paint) override; + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], + int, SkBlendMode, const SkRect*, const SkPaint*) override; + void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override; + void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override; + + void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, const SkColor4f&, + SkBlendMode) override; + void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[], + const SkPaint*, SrcRectConstraint) override; + + // Forwarded to the wrapped canvas. + sk_sp onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; + bool onPeekPixels(SkPixmap* pixmap) override; + bool onAccessTopLayerPixels(SkPixmap* pixmap) override; + SkImageInfo onImageInfo() const override; + bool onGetProps(SkSurfaceProps* props) const override; + +private: + class AutoPaintFilter; + + SkCanvas* proxy() const { SkASSERT(fList.count() == 1); return fList[0]; } + + SkPaintFilterCanvas* internal_private_asPaintFilterCanvas() const override { + return const_cast(this); + } + + friend class SkAndroidFrameworkUtils; +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkParse.h b/gfx/skia/skia/include/utils/SkParse.h new file mode 100644 index 0000000000..9a738bace1 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkParse.h @@ -0,0 +1,32 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkParse_DEFINED +#define SkParse_DEFINED + +#include "include/core/SkColor.h" + +class SK_API SkParse { +public: + static int Count(const char str[]); // number of scalars or int values + static int Count(const char str[], char separator); + static const char* FindColor(const char str[], SkColor* value); + static const char* FindHex(const char str[], uint32_t* value); + static const char* FindMSec(const char str[], SkMSec* value); + static const char* FindNamedColor(const char str[], size_t len, SkColor* color); + static const char* FindS32(const char str[], int32_t* value); + static const char* FindScalar(const char str[], SkScalar* value); + static const char* FindScalars(const char str[], SkScalar value[], int count); + + static bool FindBool(const char str[], bool* value); + // return the index of str in list[], or -1 if not found + static int FindList(const char str[], const char list[]); +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkParsePath.h b/gfx/skia/skia/include/utils/SkParsePath.h new file mode 100644 index 0000000000..9a79f945d9 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkParsePath.h @@ -0,0 +1,23 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkParsePath_DEFINED +#define SkParsePath_DEFINED + +#include "include/core/SkPath.h" + +class SkString; + +class SK_API SkParsePath { +public: + static bool FromSVGString(const char str[], SkPath*); + static void ToSVGString(const SkPath&, SkString*); +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkRandom.h b/gfx/skia/skia/include/utils/SkRandom.h new file mode 100644 index 0000000000..0678010362 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkRandom.h @@ -0,0 +1,169 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRandom_DEFINED +#define SkRandom_DEFINED + +#include "include/core/SkScalar.h" +#include "include/private/SkFixed.h" +#include "include/private/SkFloatBits.h" + +/** \class SkRandom + + Utility class that implements pseudo random 32bit numbers using Marsaglia's + multiply-with-carry "mother of all" algorithm. Unlike rand(), this class holds + its own state, so that multiple instances can be used with no side-effects. + + Has a large period and all bits are well-randomized. + */ +class SkRandom { +public: + SkRandom() { init(0); } + SkRandom(uint32_t seed) { init(seed); } + SkRandom(const SkRandom& rand) : fK(rand.fK), fJ(rand.fJ) {} + + SkRandom& operator=(const SkRandom& rand) { + fK = rand.fK; + fJ = rand.fJ; + + return *this; + } + + /** Return the next pseudo random number as an unsigned 32bit value. + */ + uint32_t nextU() { + fK = kKMul*(fK & 0xffff) + (fK >> 16); + fJ = kJMul*(fJ & 0xffff) + (fJ >> 16); + return (((fK << 16) | (fK >> 16)) + fJ); + } + + /** Return the next pseudo random number as a signed 32bit value. + */ + int32_t nextS() { return (int32_t)this->nextU(); } + + /** + * Returns value [0...1) as an IEEE float + */ + float nextF() { + unsigned int floatint = 0x3f800000 | (this->nextU() >> 9); + float f = SkBits2Float(floatint) - 1.0f; + return f; + } + + /** + * Returns value [min...max) as a float + */ + float nextRangeF(float min, float max) { + return min + this->nextF() * (max - min); + } + + /** Return the next pseudo random number, as an unsigned value of + at most bitCount bits. + @param bitCount The maximum number of bits to be returned + */ + uint32_t nextBits(unsigned bitCount) { + SkASSERT(bitCount > 0 && bitCount <= 32); + return this->nextU() >> (32 - bitCount); + } + + /** Return the next pseudo random unsigned number, mapped to lie within + [min, max] inclusive. + */ + uint32_t nextRangeU(uint32_t min, uint32_t max) { + SkASSERT(min <= max); + uint32_t range = max - min + 1; + if (0 == range) { + return this->nextU(); + } else { + return min + this->nextU() % range; + } + } + + /** Return the next pseudo random unsigned number, mapped to lie within + [0, count). + */ + uint32_t nextULessThan(uint32_t count) { + SkASSERT(count > 0); + return this->nextRangeU(0, count - 1); + } + + /** Return the next pseudo random number expressed as a SkScalar + in the range [0..SK_Scalar1). + */ + SkScalar nextUScalar1() { return SkFixedToScalar(this->nextUFixed1()); } + + /** Return the next pseudo random number expressed as a SkScalar + in the range [min..max). + */ + SkScalar nextRangeScalar(SkScalar min, SkScalar max) { + return this->nextUScalar1() * (max - min) + min; + } + + /** Return the next pseudo random number expressed as a SkScalar + in the range [-SK_Scalar1..SK_Scalar1). + */ + SkScalar nextSScalar1() { return SkFixedToScalar(this->nextSFixed1()); } + + /** Return the next pseudo random number as a bool. + */ + bool nextBool() { return this->nextU() >= 0x80000000; } + + /** A biased version of nextBool(). + */ + bool nextBiasedBool(SkScalar fractionTrue) { + SkASSERT(fractionTrue >= 0 && fractionTrue <= SK_Scalar1); + return this->nextUScalar1() <= fractionTrue; + } + + /** Reset the random object. + */ + void setSeed(uint32_t seed) { init(seed); } + +private: + // Initialize state variables with LCG. + // We must ensure that both J and K are non-zero, otherwise the + // multiply-with-carry step will forevermore return zero. + void init(uint32_t seed) { + fK = NextLCG(seed); + if (0 == fK) { + fK = NextLCG(fK); + } + fJ = NextLCG(fK); + if (0 == fJ) { + fJ = NextLCG(fJ); + } + SkASSERT(0 != fK && 0 != fJ); + } + static uint32_t NextLCG(uint32_t seed) { return kMul*seed + kAdd; } + + /** Return the next pseudo random number expressed as an unsigned SkFixed + in the range [0..SK_Fixed1). + */ + SkFixed nextUFixed1() { return this->nextU() >> 16; } + + /** Return the next pseudo random number expressed as a signed SkFixed + in the range [-SK_Fixed1..SK_Fixed1). + */ + SkFixed nextSFixed1() { return this->nextS() >> 15; } + + // See "Numerical Recipes in C", 1992 page 284 for these constants + // For the LCG that sets the initial state from a seed + enum { + kMul = 1664525, + kAdd = 1013904223 + }; + // Constants for the multiply-with-carry steps + enum { + kKMul = 30345, + kJMul = 18000, + }; + + uint32_t fK; + uint32_t fJ; +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkShadowUtils.h b/gfx/skia/skia/include/utils/SkShadowUtils.h new file mode 100644 index 0000000000..d659ffccf4 --- /dev/null +++ b/gfx/skia/skia/include/utils/SkShadowUtils.h @@ -0,0 +1,56 @@ + +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkShadowUtils_DEFINED +#define SkShadowUtils_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkScalar.h" +#include "include/private/SkShadowFlags.h" + +class SkCanvas; +class SkPath; +class SkResourceCache; + +class SK_API SkShadowUtils { +public: + /** + * Draw an offset spot shadow and outlining ambient shadow for the given path using a disc + * light. The shadow may be cached, depending on the path type and canvas matrix. If the + * matrix is perspective or the path is volatile, it will not be cached. + * + * @param canvas The canvas on which to draw the shadows. + * @param path The occluder used to generate the shadows. + * @param zPlaneParams Values for the plane function which returns the Z offset of the + * occluder from the canvas based on local x and y values (the current matrix is not applied). + * @param lightPos The 3D position of the light relative to the canvas plane. This is + * independent of the canvas's current matrix. + * @param lightRadius The radius of the disc light. + * @param ambientColor The color of the ambient shadow. + * @param spotColor The color of the spot shadow. + * @param flags Options controlling opaque occluder optimizations and shadow appearance. See + * SkShadowFlags. + */ + static void DrawShadow(SkCanvas* canvas, const SkPath& path, const SkPoint3& zPlaneParams, + const SkPoint3& lightPos, SkScalar lightRadius, + SkColor ambientColor, SkColor spotColor, + uint32_t flags = SkShadowFlags::kNone_ShadowFlag); + + /** + * Helper routine to compute color values for one-pass tonal alpha. + * + * @param inAmbientColor Original ambient color + * @param inSpotColor Original spot color + * @param outAmbientColor Modified ambient color + * @param outSpotColor Modified spot color + */ + static void ComputeTonalColors(SkColor inAmbientColor, SkColor inSpotColor, + SkColor* outAmbientColor, SkColor* outSpotColor); +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkTextUtils.h b/gfx/skia/skia/include/utils/SkTextUtils.h new file mode 100644 index 0000000000..6cd3771e3d --- /dev/null +++ b/gfx/skia/skia/include/utils/SkTextUtils.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTextUtils_DEFINED +#define SkTextUtils_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkFont.h" +#include "include/core/SkPaint.h" +#include "include/core/SkString.h" + +class SkPath; + +class SK_API SkTextUtils { +public: + enum Align { + kLeft_Align, + kCenter_Align, + kRight_Align, + }; + + static void Draw(SkCanvas*, const void* text, size_t size, SkTextEncoding, + SkScalar x, SkScalar y, const SkFont&, const SkPaint&, Align = kLeft_Align); + + static void DrawString(SkCanvas* canvas, const char text[], SkScalar x, SkScalar y, + const SkFont& font, const SkPaint& paint, Align align = kLeft_Align) { + Draw(canvas, text, strlen(text), SkTextEncoding::kUTF8, x, y, font, paint, align); + } + + static void GetPath(const void* text, size_t length, SkTextEncoding, SkScalar x, SkScalar y, + const SkFont&, SkPath*); +}; + +#endif diff --git a/gfx/skia/skia/include/utils/SkTraceEventPhase.h b/gfx/skia/skia/include/utils/SkTraceEventPhase.h new file mode 100644 index 0000000000..38457be24b --- /dev/null +++ b/gfx/skia/skia/include/utils/SkTraceEventPhase.h @@ -0,0 +1,19 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef SkTraceEventPhase_DEFINED +#define SkTraceEventPhase_DEFINED + +// Phase indicates the nature of an event entry. E.g. part of a begin/end pair. +#define TRACE_EVENT_PHASE_BEGIN ('B') +#define TRACE_EVENT_PHASE_END ('E') +#define TRACE_EVENT_PHASE_COMPLETE ('X') +#define TRACE_EVENT_PHASE_INSTANT ('I') +#define TRACE_EVENT_PHASE_ASYNC_BEGIN ('S') +#define TRACE_EVENT_PHASE_ASYNC_END ('F') +#define TRACE_EVENT_PHASE_COUNTER ('C') +#define TRACE_EVENT_PHASE_CREATE_OBJECT ('N') +#define TRACE_EVENT_PHASE_SNAPSHOT_OBJECT ('O') +#define TRACE_EVENT_PHASE_DELETE_OBJECT ('D') + +#endif // SkTraceEventPhase_DEFINED diff --git a/gfx/skia/skia/include/utils/mac/SkCGUtils.h b/gfx/skia/skia/include/utils/mac/SkCGUtils.h new file mode 100644 index 0000000000..a8bde9950d --- /dev/null +++ b/gfx/skia/skia/include/utils/mac/SkCGUtils.h @@ -0,0 +1,86 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkCGUtils_DEFINED +#define SkCGUtils_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkSize.h" + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#ifdef SK_BUILD_FOR_MAC +#include +#endif + +#ifdef SK_BUILD_FOR_IOS +#include +#endif + +class SkBitmap; +class SkData; +class SkPixmap; +class SkStreamRewindable; + +SK_API CGContextRef SkCreateCGContext(const SkPixmap&); + +/** + * Given a CGImage, allocate an SkBitmap and copy the image's pixels into it. If scaleToFit is not + * null, use it to determine the size of the bitmap, and scale the image to fill the bitmap. + * Otherwise use the image's width/height. + * + * On failure, return false, and leave bitmap unchanged. + */ +SK_API bool SkCreateBitmapFromCGImage(SkBitmap* dst, CGImageRef src); + +SK_API sk_sp SkMakeImageFromCGImage(CGImageRef); + +/** + * Copy the pixels from src into the memory specified by info/rowBytes/dstPixels. On failure, + * return false (e.g. ImageInfo incompatible with src). + */ +SK_API bool SkCopyPixelsFromCGImage(const SkImageInfo& info, size_t rowBytes, void* dstPixels, + CGImageRef src); +static inline bool SkCopyPixelsFromCGImage(const SkPixmap& dst, CGImageRef src) { + return SkCopyPixelsFromCGImage(dst.info(), dst.rowBytes(), dst.writable_addr(), src); +} + +/** + * Create an imageref from the specified bitmap using the specified colorspace. + * If space is NULL, then CGColorSpaceCreateDeviceRGB() is used. + */ +SK_API CGImageRef SkCreateCGImageRefWithColorspace(const SkBitmap& bm, + CGColorSpaceRef space); + +/** + * Create an imageref from the specified bitmap using the colorspace returned + * by CGColorSpaceCreateDeviceRGB() + */ +static inline CGImageRef SkCreateCGImageRef(const SkBitmap& bm) { + return SkCreateCGImageRefWithColorspace(bm, NULL); +} + +/** + * Draw the bitmap into the specified CG context. The bitmap will be converted + * to a CGImage using the generic RGB colorspace. (x,y) specifies the position + * of the top-left corner of the bitmap. The bitmap is converted using the + * colorspace returned by CGColorSpaceCreateDeviceRGB() + */ +void SkCGDrawBitmap(CGContextRef, const SkBitmap&, float x, float y); + +/** + * Return a provider that wraps the specified stream. + * When the provider is finally deleted, it will delete the stream. + */ +CGDataProviderRef SkCreateDataProviderFromStream(std::unique_ptr); + +CGDataProviderRef SkCreateDataProviderFromData(sk_sp); + +#endif // defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) +#endif // SkCGUtils_DEFINED diff --git a/gfx/skia/skia/src/android/SkAndroidFrameworkUtils.cpp b/gfx/skia/skia/src/android/SkAndroidFrameworkUtils.cpp new file mode 100644 index 0000000000..d5d533365b --- /dev/null +++ b/gfx/skia/skia/src/android/SkAndroidFrameworkUtils.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/android/SkAndroidFrameworkUtils.h" +#include "include/core/SkCanvas.h" +#include "include/utils/SkPaintFilterCanvas.h" +#include "src/core/SkDevice.h" +#include "src/image/SkSurface_Base.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrClip.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrUserStencilSettings.h" +#include "src/gpu/effects/GrDisableColorXP.h" +#endif //SK_SUPPORT_GPU + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + +#include + +#if SK_SUPPORT_GPU +bool SkAndroidFrameworkUtils::clipWithStencil(SkCanvas* canvas) { + SkRegion clipRegion; + canvas->temporary_internal_getRgnClip(&clipRegion); + if (clipRegion.isEmpty()) { + return false; + } + SkBaseDevice* device = canvas->getDevice(); + if (!device) { + return false; + } + GrRenderTargetContext* rtc = device->accessRenderTargetContext(); + if (!rtc) { + return false; + } + GrPaint grPaint; + grPaint.setXPFactory(GrDisableColorXPFactory::Get()); + GrNoClip noClip; + static constexpr GrUserStencilSettings kDrawToStencil( + GrUserStencilSettings::StaticInit< + 0x1, + GrUserStencilTest::kAlways, + 0x1, + GrUserStencilOp::kReplace, + GrUserStencilOp::kReplace, + 0x1>() + ); + rtc->drawRegion(noClip, std::move(grPaint), GrAA::kNo, SkMatrix::I(), clipRegion, + GrStyle::SimpleFill(), &kDrawToStencil); + return true; +} +#endif //SK_SUPPORT_GPU + +void SkAndroidFrameworkUtils::SafetyNetLog(const char* bugNumber) { + android_errorWriteLog(0x534e4554, bugNumber); +} + +sk_sp SkAndroidFrameworkUtils::getSurfaceFromCanvas(SkCanvas* canvas) { + sk_sp surface(SkSafeRef(canvas->getSurfaceBase())); + return surface; +} + +int SkAndroidFrameworkUtils::SaveBehind(SkCanvas* canvas, const SkRect* subset) { + return canvas->only_axis_aligned_saveBehind(subset); +} + +SkCanvas* SkAndroidFrameworkUtils::getBaseWrappedCanvas(SkCanvas* canvas) { + auto pfc = canvas->internal_private_asPaintFilterCanvas(); + auto result = canvas; + while (pfc) { + result = pfc->proxy(); + pfc = result->internal_private_asPaintFilterCanvas(); + } + return result; +} +#endif // SK_BUILD_FOR_ANDROID_FRAMEWORK + diff --git a/gfx/skia/skia/src/android/SkAnimatedImage.cpp b/gfx/skia/skia/src/android/SkAnimatedImage.cpp new file mode 100644 index 0000000000..6db6afde4a --- /dev/null +++ b/gfx/skia/skia/src/android/SkAnimatedImage.cpp @@ -0,0 +1,359 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/android/SkAnimatedImage.h" +#include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkPicture.h" +#include "include/core/SkPictureRecorder.h" +#include "include/core/SkPixelRef.h" +#include "src/codec/SkCodecPriv.h" +#include "src/core/SkImagePriv.h" + +#include +#include + +sk_sp SkAnimatedImage::Make(std::unique_ptr codec, + SkISize scaledSize, SkIRect cropRect, sk_sp postProcess) { + if (!codec) { + return nullptr; + } + auto info = codec->getInfo().makeDimensions(scaledSize); + return Make(std::move(codec), info, cropRect, std::move(postProcess)); +} + +sk_sp SkAnimatedImage::Make(std::unique_ptr codec, + const SkImageInfo& requestedInfo, SkIRect cropRect, sk_sp postProcess) { + if (!codec) { + return nullptr; + } + + auto scaledSize = requestedInfo.dimensions(); + auto decodeInfo = requestedInfo; + if (codec->getEncodedFormat() != SkEncodedImageFormat::kWEBP + || scaledSize.width() >= decodeInfo.width() + || scaledSize.height() >= decodeInfo.height()) { + // Only libwebp can decode to arbitrary smaller sizes. + auto dims = codec->getInfo().dimensions(); + decodeInfo = decodeInfo.makeDimensions(dims); + } + + auto image = sk_sp(new SkAnimatedImage(std::move(codec), scaledSize, + decodeInfo, cropRect, std::move(postProcess))); + if (!image->fDisplayFrame.fBitmap.getPixels()) { + // tryAllocPixels failed. + return nullptr; + } + + return image; +} + +sk_sp SkAnimatedImage::Make(std::unique_ptr codec) { + if (!codec) { + return nullptr; + } + + const auto decodeInfo = codec->getInfo(); + const auto scaledSize = decodeInfo.dimensions(); + const auto cropRect = SkIRect::MakeSize(scaledSize); + auto image = sk_sp(new SkAnimatedImage(std::move(codec), scaledSize, + decodeInfo, cropRect, nullptr)); + + if (!image->fDisplayFrame.fBitmap.getPixels()) { + // tryAllocPixels failed. + return nullptr; + } + + SkASSERT(image->fSimple); + return image; +} + +SkAnimatedImage::SkAnimatedImage(std::unique_ptr codec, SkISize scaledSize, + SkImageInfo decodeInfo, SkIRect cropRect, sk_sp postProcess) + : fCodec(std::move(codec)) + , fScaledSize(scaledSize) + , fDecodeInfo(decodeInfo) + , fCropRect(cropRect) + , fPostProcess(std::move(postProcess)) + , fFrameCount(fCodec->codec()->getFrameCount()) + , fSimple(fScaledSize == fDecodeInfo.dimensions() && !fPostProcess + && fCropRect == fDecodeInfo.bounds()) + , fFinished(false) + , fRepetitionCount(fCodec->codec()->getRepetitionCount()) + , fRepetitionsCompleted(0) +{ + if (!fDecodingFrame.fBitmap.tryAllocPixels(fDecodeInfo)) { + return; + } + + if (!fSimple) { + fMatrix = SkMatrix::MakeTrans(-fCropRect.fLeft, -fCropRect.fTop); + float scaleX = (float) fScaledSize.width() / fDecodeInfo.width(); + float scaleY = (float) fScaledSize.height() / fDecodeInfo.height(); + fMatrix.preConcat(SkMatrix::MakeScale(scaleX, scaleY)); + } + this->decodeNextFrame(); +} + +SkAnimatedImage::~SkAnimatedImage() { } + +SkRect SkAnimatedImage::onGetBounds() { + return SkRect::MakeIWH(fCropRect.width(), fCropRect.height()); +} + +SkAnimatedImage::Frame::Frame() + : fIndex(SkCodec::kNoFrame) +{} + +bool SkAnimatedImage::Frame::init(const SkImageInfo& info, OnInit onInit) { + if (fBitmap.getPixels()) { + if (fBitmap.pixelRef()->unique()) { + SkAssertResult(fBitmap.setAlphaType(info.alphaType())); + return true; + } + + // An SkCanvas provided to onDraw is still holding a reference. + // Copy before we decode to ensure that we don't overwrite the + // expected contents of the image. + if (OnInit::kRestoreIfNecessary == onInit) { + SkBitmap tmp; + if (!tmp.tryAllocPixels(info)) { + return false; + } + + memcpy(tmp.getPixels(), fBitmap.getPixels(), fBitmap.computeByteSize()); + using std::swap; + swap(tmp, fBitmap); + return true; + } + } + + return fBitmap.tryAllocPixels(info); +} + +bool SkAnimatedImage::Frame::copyTo(Frame* dst) const { + if (!dst->init(fBitmap.info(), OnInit::kNoRestore)) { + return false; + } + + memcpy(dst->fBitmap.getPixels(), fBitmap.getPixels(), fBitmap.computeByteSize()); + dst->fIndex = fIndex; + dst->fDisposalMethod = fDisposalMethod; + return true; +} + +void SkAnimatedImage::reset() { + fFinished = false; + fRepetitionsCompleted = 0; + if (fDisplayFrame.fIndex != 0) { + fDisplayFrame.fIndex = SkCodec::kNoFrame; + this->decodeNextFrame(); + } +} + +static bool is_restore_previous(SkCodecAnimation::DisposalMethod dispose) { + return SkCodecAnimation::DisposalMethod::kRestorePrevious == dispose; +} + +int SkAnimatedImage::computeNextFrame(int current, bool* animationEnded) { + SkASSERT(animationEnded != nullptr); + *animationEnded = false; + + const int frameToDecode = current + 1; + if (frameToDecode == fFrameCount - 1) { + // Final frame. Check to determine whether to stop. + fRepetitionsCompleted++; + if (fRepetitionCount != SkCodec::kRepetitionCountInfinite + && fRepetitionsCompleted > fRepetitionCount) { + *animationEnded = true; + } + } else if (frameToDecode == fFrameCount) { + return 0; + } + return frameToDecode; +} + +double SkAnimatedImage::finish() { + fFinished = true; + fCurrentFrameDuration = kFinished; + return kFinished; +} + +int SkAnimatedImage::decodeNextFrame() { + if (fFinished) { + return kFinished; + } + + bool animationEnded = false; + const int frameToDecode = this->computeNextFrame(fDisplayFrame.fIndex, &animationEnded); + + SkCodec::FrameInfo frameInfo; + if (fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) { + if (!frameInfo.fFullyReceived) { + SkCodecPrintf("Frame %i not fully received\n", frameToDecode); + return this->finish(); + } + + fCurrentFrameDuration = frameInfo.fDuration; + } else { + animationEnded = true; + if (0 == frameToDecode) { + // Static image. This is okay. + frameInfo.fRequiredFrame = SkCodec::kNoFrame; + frameInfo.fAlphaType = fCodec->getInfo().alphaType(); + frameInfo.fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep; + // These fields won't be read. + frameInfo.fDuration = INT_MAX; + frameInfo.fFullyReceived = true; + fCurrentFrameDuration = kFinished; + } else { + SkCodecPrintf("Error getting frameInfo for frame %i\n", + frameToDecode); + return this->finish(); + } + } + + if (frameToDecode == fDisplayFrame.fIndex) { + if (animationEnded) { + return this->finish(); + } + return fCurrentFrameDuration; + } + + for (Frame* frame : { &fRestoreFrame, &fDecodingFrame }) { + if (frameToDecode == frame->fIndex) { + using std::swap; + swap(fDisplayFrame, *frame); + if (animationEnded) { + return this->finish(); + } + return fCurrentFrameDuration; + } + } + + // The following code makes an effort to avoid overwriting a frame that will + // be used again. If frame |i| is_restore_previous, frame |i+1| will not + // depend on frame |i|, so do not overwrite frame |i-1|, which may be needed + // for frame |i+1|. + // We could be even smarter about which frames to save by looking at the + // entire dependency chain. + SkCodec::Options options; + options.fFrameIndex = frameToDecode; + if (frameInfo.fRequiredFrame == SkCodec::kNoFrame) { + if (is_restore_previous(frameInfo.fDisposalMethod)) { + // frameToDecode will be discarded immediately after drawing, so + // do not overwrite a frame which could possibly be used in the + // future. + if (fDecodingFrame.fIndex != SkCodec::kNoFrame && + !is_restore_previous(fDecodingFrame.fDisposalMethod)) { + using std::swap; + swap(fDecodingFrame, fRestoreFrame); + } + } + } else { + auto validPriorFrame = [&frameInfo, &frameToDecode](const Frame& frame) { + if (SkCodec::kNoFrame == frame.fIndex || + is_restore_previous(frame.fDisposalMethod)) { + return false; + } + + return frame.fIndex >= frameInfo.fRequiredFrame && frame.fIndex < frameToDecode; + }; + if (validPriorFrame(fDecodingFrame)) { + if (is_restore_previous(frameInfo.fDisposalMethod)) { + // fDecodingFrame is a good frame to use for this one, but we + // don't want to overwrite it. + fDecodingFrame.copyTo(&fRestoreFrame); + } + options.fPriorFrame = fDecodingFrame.fIndex; + } else if (validPriorFrame(fDisplayFrame)) { + if (!fDisplayFrame.copyTo(&fDecodingFrame)) { + SkCodecPrintf("Failed to allocate pixels for frame\n"); + return this->finish(); + } + options.fPriorFrame = fDecodingFrame.fIndex; + } else if (validPriorFrame(fRestoreFrame)) { + if (!is_restore_previous(frameInfo.fDisposalMethod)) { + using std::swap; + swap(fDecodingFrame, fRestoreFrame); + } else if (!fRestoreFrame.copyTo(&fDecodingFrame)) { + SkCodecPrintf("Failed to restore frame\n"); + return this->finish(); + } + options.fPriorFrame = fDecodingFrame.fIndex; + } + } + + auto alphaType = kOpaque_SkAlphaType == frameInfo.fAlphaType ? + kOpaque_SkAlphaType : kPremul_SkAlphaType; + auto info = fDecodeInfo.makeAlphaType(alphaType); + SkBitmap* dst = &fDecodingFrame.fBitmap; + if (!fDecodingFrame.init(info, Frame::OnInit::kRestoreIfNecessary)) { + return this->finish(); + } + + auto result = fCodec->codec()->getPixels(dst->info(), dst->getPixels(), dst->rowBytes(), + &options); + if (result != SkCodec::kSuccess) { + SkCodecPrintf("error %i, frame %i of %i\n", result, frameToDecode, fFrameCount); + return this->finish(); + } + + fDecodingFrame.fIndex = frameToDecode; + fDecodingFrame.fDisposalMethod = frameInfo.fDisposalMethod; + + using std::swap; + swap(fDecodingFrame, fDisplayFrame); + fDisplayFrame.fBitmap.notifyPixelsChanged(); + + if (animationEnded) { + return this->finish(); + } else if (fCodec->getEncodedFormat() == SkEncodedImageFormat::kHEIF) { + // HEIF doesn't know the frame duration until after decoding. Update to + // the correct value. Note that earlier returns in this method either + // return kFinished, or fCurrentFrameDuration. If they return the + // latter, it is a frame that was previously decoded, so it has the + // updated value. + if (fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) { + fCurrentFrameDuration = frameInfo.fDuration; + } else { + SkCodecPrintf("Failed to getFrameInfo on second attempt (HEIF)"); + } + } + return fCurrentFrameDuration; +} + +void SkAnimatedImage::onDraw(SkCanvas* canvas) { + auto image = SkMakeImageFromRasterBitmap(fDisplayFrame.fBitmap, + kNever_SkCopyPixelsMode); + + if (fSimple) { + canvas->drawImage(image, 0, 0); + return; + } + + SkRect bounds = this->getBounds(); + if (fPostProcess) { + canvas->saveLayer(&bounds, nullptr); + } + { + SkAutoCanvasRestore acr(canvas, fPostProcess != nullptr); + canvas->concat(fMatrix); + SkPaint paint; + paint.setFilterQuality(kLow_SkFilterQuality); + canvas->drawImage(image, 0, 0, &paint); + } + if (fPostProcess) { + canvas->drawPicture(fPostProcess); + canvas->restore(); + } +} + +void SkAnimatedImage::setRepetitionCount(int newCount) { + fRepetitionCount = newCount; +} diff --git a/gfx/skia/skia/src/android/SkBitmapRegionCodec.cpp b/gfx/skia/skia/src/android/SkBitmapRegionCodec.cpp new file mode 100644 index 0000000000..5cb9ccb0c4 --- /dev/null +++ b/gfx/skia/skia/src/android/SkBitmapRegionCodec.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkAndroidCodec.h" +#include "src/android/SkBitmapRegionCodec.h" +#include "src/android/SkBitmapRegionDecoderPriv.h" +#include "src/codec/SkCodecPriv.h" + +SkBitmapRegionCodec::SkBitmapRegionCodec(SkAndroidCodec* codec) + : INHERITED(codec->getInfo().width(), codec->getInfo().height()) + , fCodec(codec) +{} + +bool SkBitmapRegionCodec::decodeRegion(SkBitmap* bitmap, SkBRDAllocator* allocator, + const SkIRect& desiredSubset, int sampleSize, SkColorType dstColorType, + bool requireUnpremul, sk_sp dstColorSpace) { + + // Fix the input sampleSize if necessary. + if (sampleSize < 1) { + sampleSize = 1; + } + + // The size of the output bitmap is determined by the size of the + // requested subset, not by the size of the intersection of the subset + // and the image dimensions. + // If inputX is negative, we will need to place decoded pixels into the + // output bitmap starting at a left offset. Call this outX. + // If outX is non-zero, subsetX must be zero. + // If inputY is negative, we will need to place decoded pixels into the + // output bitmap starting at a top offset. Call this outY. + // If outY is non-zero, subsetY must be zero. + int outX; + int outY; + SkIRect subset = desiredSubset; + SubsetType type = adjust_subset_rect(fCodec->getInfo().dimensions(), &subset, &outX, &outY); + if (SubsetType::kOutside_SubsetType == type) { + return false; + } + + // Ask the codec for a scaled subset + if (!fCodec->getSupportedSubset(&subset)) { + SkCodecPrintf("Error: Could not get subset.\n"); + return false; + } + SkISize scaledSize = fCodec->getSampledSubsetDimensions(sampleSize, subset); + + // Create the image info for the decode + SkAlphaType dstAlphaType = fCodec->computeOutputAlphaType(requireUnpremul); + SkImageInfo decodeInfo = + SkImageInfo::Make(scaledSize, dstColorType, dstAlphaType, dstColorSpace); + + // Initialize the destination bitmap + int scaledOutX = 0; + int scaledOutY = 0; + int scaledOutWidth = scaledSize.width(); + int scaledOutHeight = scaledSize.height(); + if (SubsetType::kPartiallyInside_SubsetType == type) { + scaledOutX = outX / sampleSize; + scaledOutY = outY / sampleSize; + // We need to be safe here because getSupportedSubset() may have modified the subset. + const int extraX = SkTMax(0, desiredSubset.width() - outX - subset.width()); + const int extraY = SkTMax(0, desiredSubset.height() - outY - subset.height()); + const int scaledExtraX = extraX / sampleSize; + const int scaledExtraY = extraY / sampleSize; + scaledOutWidth += scaledOutX + scaledExtraX; + scaledOutHeight += scaledOutY + scaledExtraY; + } + SkImageInfo outInfo = decodeInfo.makeWH(scaledOutWidth, scaledOutHeight); + if (kGray_8_SkColorType == dstColorType) { + // The legacy implementations of BitmapFactory and BitmapRegionDecoder + // used kAlpha8 for grayscale images (before kGray8 existed). While + // the codec recognizes kGray8, we need to decode into a kAlpha8 + // bitmap in order to avoid a behavior change. + outInfo = outInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType); + } + bitmap->setInfo(outInfo); + if (!bitmap->tryAllocPixels(allocator)) { + SkCodecPrintf("Error: Could not allocate pixels.\n"); + return false; + } + + // Zero the bitmap if the region is not completely within the image. + // TODO (msarett): Can we make this faster by implementing it to only + // zero parts of the image that we won't overwrite with + // pixels? + SkCodec::ZeroInitialized zeroInit = allocator ? allocator->zeroInit() : + SkCodec::kNo_ZeroInitialized; + if (SubsetType::kPartiallyInside_SubsetType == type && + SkCodec::kNo_ZeroInitialized == zeroInit) { + void* pixels = bitmap->getPixels(); + size_t bytes = outInfo.computeByteSize(bitmap->rowBytes()); + memset(pixels, 0, bytes); + } + + // Decode into the destination bitmap + SkAndroidCodec::AndroidOptions options; + options.fSampleSize = sampleSize; + options.fSubset = ⊂ + options.fZeroInitialized = zeroInit; + void* dst = bitmap->getAddr(scaledOutX, scaledOutY); + + SkCodec::Result result = fCodec->getAndroidPixels(decodeInfo, dst, bitmap->rowBytes(), + &options); + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + SkCodecPrintf("Error: Could not get pixels with message \"%s\".\n", + SkCodec::ResultToString(result)); + return false; + } +} diff --git a/gfx/skia/skia/src/android/SkBitmapRegionCodec.h b/gfx/skia/skia/src/android/SkBitmapRegionCodec.h new file mode 100644 index 0000000000..21859514af --- /dev/null +++ b/gfx/skia/skia/src/android/SkBitmapRegionCodec.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapRegionCodec_DEFINED +#define SkBitmapRegionCodec_DEFINED + +#include "include/android/SkBitmapRegionDecoder.h" +#include "include/codec/SkAndroidCodec.h" +#include "include/core/SkBitmap.h" + +/* + * This class implements SkBitmapRegionDecoder using an SkAndroidCodec. + */ +class SkBitmapRegionCodec : public SkBitmapRegionDecoder { +public: + + /* + * Takes ownership of pointer to codec + */ + SkBitmapRegionCodec(SkAndroidCodec* codec); + + bool decodeRegion(SkBitmap* bitmap, SkBRDAllocator* allocator, + const SkIRect& desiredSubset, int sampleSize, + SkColorType colorType, bool requireUnpremul, + sk_sp prefColorSpace) override; + + SkEncodedImageFormat getEncodedFormat() override { return fCodec->getEncodedFormat(); } + + SkColorType computeOutputColorType(SkColorType requestedColorType) override { + return fCodec->computeOutputColorType(requestedColorType); + } + + sk_sp computeOutputColorSpace(SkColorType outputColorType, + sk_sp prefColorSpace = nullptr) override { + return fCodec->computeOutputColorSpace(outputColorType, prefColorSpace); + } + +private: + + std::unique_ptr fCodec; + + typedef SkBitmapRegionDecoder INHERITED; + +}; +#endif // SkBitmapRegionCodec_DEFINED diff --git a/gfx/skia/skia/src/android/SkBitmapRegionDecoder.cpp b/gfx/skia/skia/src/android/SkBitmapRegionDecoder.cpp new file mode 100644 index 0000000000..328daa3bc9 --- /dev/null +++ b/gfx/skia/skia/src/android/SkBitmapRegionDecoder.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/android/SkBitmapRegionDecoder.h" +#include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" +#include "src/android/SkBitmapRegionCodec.h" +#include "src/codec/SkCodecPriv.h" + +SkBitmapRegionDecoder* SkBitmapRegionDecoder::Create( + sk_sp data, Strategy strategy) { + return SkBitmapRegionDecoder::Create(new SkMemoryStream(data), + strategy); +} + +SkBitmapRegionDecoder* SkBitmapRegionDecoder::Create( + SkStreamRewindable* stream, Strategy strategy) { + std::unique_ptr streamDeleter(stream); + switch (strategy) { + case kAndroidCodec_Strategy: { + auto codec = SkAndroidCodec::MakeFromStream(std::move(streamDeleter)); + if (nullptr == codec) { + SkCodecPrintf("Error: Failed to create codec.\n"); + return nullptr; + } + + switch ((SkEncodedImageFormat)codec->getEncodedFormat()) { + case SkEncodedImageFormat::kJPEG: + case SkEncodedImageFormat::kPNG: + case SkEncodedImageFormat::kWEBP: + case SkEncodedImageFormat::kHEIF: + break; + default: + return nullptr; + } + + return new SkBitmapRegionCodec(codec.release()); + } + default: + SkASSERT(false); + return nullptr; + } +} diff --git a/gfx/skia/skia/src/android/SkBitmapRegionDecoderPriv.h b/gfx/skia/skia/src/android/SkBitmapRegionDecoderPriv.h new file mode 100644 index 0000000000..5f1613a1a6 --- /dev/null +++ b/gfx/skia/skia/src/android/SkBitmapRegionDecoderPriv.h @@ -0,0 +1,61 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapRegionDecoderPriv_DEFINED +#define SkBitmapRegionDecoderPriv_DEFINED + +#include "include/core/SkRect.h" + +enum SubsetType { + kFullyInside_SubsetType, + kPartiallyInside_SubsetType, + kOutside_SubsetType, +}; + +/* + * Corrects image subset offsets and dimensions in order to perform a valid decode. + * Also indicates if the image subset should be placed at an offset within the + * output bitmap. + * + * Values of output variables are undefined if the SubsetType is kInvalid. + * + * @param imageDims Original image dimensions. + * @param subset As input, the subset that the client requested. + * As output, the image subset that we will decode. + * @param outX The left offset of the image subset within the output bitmap. + * @param outY The top offset of the image subset within the output bitmap. + * + * @return An indication of how the subset is contained in the image. + * If the return value is kInvalid, values of output variables are undefined. + */ +inline SubsetType adjust_subset_rect(const SkISize& imageDims, SkIRect* subset, int* outX, + int* outY) { + // These must be at least zero, we can't start decoding the image at a negative coordinate. + int left = SkTMax(0, subset->fLeft); + int top = SkTMax(0, subset->fTop); + + // If input offsets are less than zero, we decode to an offset location in the output bitmap. + *outX = left - subset->fLeft; + *outY = top - subset->fTop; + + // Make sure we don't decode pixels past the edge of the image or past the edge of the subset. + int width = SkTMin(imageDims.width() - left, subset->width() - *outX); + int height = SkTMin(imageDims.height() - top, subset->height() - *outY); + if (width <= 0 || height <= 0) { + return SubsetType::kOutside_SubsetType; + } + + subset->setXYWH(left, top, width, height); + if ((*outX != 0) || (*outY != 0) || (width != subset->width()) || + (height != subset->height())) { + return SubsetType::kPartiallyInside_SubsetType; + } + + return SubsetType::kFullyInside_SubsetType; +} + +#endif // SkBitmapRegionDecoderPriv_DEFINED diff --git a/gfx/skia/skia/src/atlastext/SkAtlasTextContext.cpp b/gfx/skia/skia/src/atlastext/SkAtlasTextContext.cpp new file mode 100644 index 0000000000..91e7713c12 --- /dev/null +++ b/gfx/skia/skia/src/atlastext/SkAtlasTextContext.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/atlastext/SkAtlasTextContext.h" +#include "include/atlastext/SkAtlasTextRenderer.h" +#include "src/atlastext/SkInternalAtlasTextContext.h" + +sk_sp SkAtlasTextContext::Make(sk_sp renderer) { + return sk_sp(new SkAtlasTextContext(std::move(renderer))); +} + +SkAtlasTextContext::SkAtlasTextContext(sk_sp renderer) + : fInternalContext(SkInternalAtlasTextContext::Make(std::move(renderer))) {} diff --git a/gfx/skia/skia/src/atlastext/SkAtlasTextTarget.cpp b/gfx/skia/skia/src/atlastext/SkAtlasTextTarget.cpp new file mode 100644 index 0000000000..80ef553333 --- /dev/null +++ b/gfx/skia/skia/src/atlastext/SkAtlasTextTarget.cpp @@ -0,0 +1,255 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/atlastext/SkAtlasTextTarget.h" + +#include "include/atlastext/SkAtlasTextContext.h" +#include "include/atlastext/SkAtlasTextFont.h" +#include "include/atlastext/SkAtlasTextRenderer.h" +#include "src/atlastext/SkInternalAtlasTextContext.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/ops/GrAtlasTextOp.h" +#include "src/gpu/text/GrTextContext.h" + +static constexpr int kMaxBatchLookBack = 10; + +SkAtlasTextTarget::SkAtlasTextTarget(sk_sp context, int width, int height, + void* handle) + : fHandle(handle) + , fContext(std::move(context)) + , fWidth(width) + , fHeight(height) + , fMatrixStack(sizeof(SkMatrix), 4) + , fSaveCnt(0) { + fMatrixStack.push_back(); + this->accessCTM()->reset(); +} + +SkAtlasTextTarget::~SkAtlasTextTarget() { fContext->renderer()->targetDeleted(fHandle); } + +int SkAtlasTextTarget::save() { + const auto& currCTM = this->ctm(); + *static_cast(fMatrixStack.push_back()) = currCTM; + return fSaveCnt++; +} + +void SkAtlasTextTarget::restore() { + if (fSaveCnt) { + fMatrixStack.pop_back(); + fSaveCnt--; + } +} + +void SkAtlasTextTarget::restoreToCount(int count) { + while (fSaveCnt > count) { + this->restore(); + } +} + +void SkAtlasTextTarget::translate(SkScalar dx, SkScalar dy) { + this->accessCTM()->preTranslate(dx, dy); +} + +void SkAtlasTextTarget::scale(SkScalar sx, SkScalar sy) { this->accessCTM()->preScale(sx, sy); } + +void SkAtlasTextTarget::rotate(SkScalar degrees) { this->accessCTM()->preRotate(degrees); } + +void SkAtlasTextTarget::rotate(SkScalar degrees, SkScalar px, SkScalar py) { + this->accessCTM()->preRotate(degrees, px, py); +} + +void SkAtlasTextTarget::skew(SkScalar sx, SkScalar sy) { this->accessCTM()->preSkew(sx, sy); } + +void SkAtlasTextTarget::concat(const SkMatrix& matrix) { this->accessCTM()->preConcat(matrix); } + +////////////////////////////////////////////////////////////////////////////// + +static const GrColorInfo kColorInfo(GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr); +static const SkSurfaceProps kProps( + SkSurfaceProps::kUseDistanceFieldFonts_Flag, kUnknown_SkPixelGeometry); + +////////////////////////////////////////////////////////////////////////////// + +class SkInternalAtlasTextTarget : public GrTextTarget, public SkAtlasTextTarget { +public: + SkInternalAtlasTextTarget(sk_sp context, int width, int height, + void* handle) + : GrTextTarget(width, height, kColorInfo) + , SkAtlasTextTarget(std::move(context), width, height, handle) + , fGlyphPainter(kProps, kColorInfo) { + fOpMemoryPool = fContext->internal().grContext()->priv().refOpMemoryPool(); + } + + ~SkInternalAtlasTextTarget() override { + this->deleteOps(); + } + + /** GrTextTarget overrides */ + + void addDrawOp(const GrClip&, std::unique_ptr op) override; + + void drawShape(const GrClip&, const SkPaint&, const SkMatrix& viewMatrix, + const GrShape&) override { + SkDebugf("Path glyph??"); + } + + void makeGrPaint(GrMaskFormat, const SkPaint& skPaint, const SkMatrix&, + GrPaint* grPaint) override { + grPaint->setColor4f(skPaint.getColor4f().premul()); + } + + GrContext* getContext() override { + return this->context()->internal().grContext(); + } + + SkGlyphRunListPainter* glyphPainter() override { + return &fGlyphPainter; + } + + /** SkAtlasTextTarget overrides */ + + void drawText(const SkGlyphID[], const SkPoint[], int glyphCnt, uint32_t color, + const SkAtlasTextFont&) override; + void flush() override; + +private: + void deleteOps(); + + uint32_t fColor; + using SkAtlasTextTarget::fWidth; + using SkAtlasTextTarget::fHeight; + SkTArray, true> fOps; + sk_sp fOpMemoryPool; + SkGlyphRunListPainter fGlyphPainter; +}; + +////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr SkAtlasTextTarget::Make(sk_sp context, + int width, int height, void* handle) { + return std::unique_ptr( + new SkInternalAtlasTextTarget(std::move(context), width, height, handle)); +} + +////////////////////////////////////////////////////////////////////////////// + +void SkInternalAtlasTextTarget::drawText(const SkGlyphID glyphs[], const SkPoint positions[], + int glyphCnt, uint32_t color, + const SkAtlasTextFont& font) { + SkPaint paint; + paint.setAntiAlias(true); + + // The atlas text context does munging of the paint color. We store the client's color here + // and then overwrite the generated op's color when addDrawOp() is called. + fColor = color; + + SkSurfaceProps props(SkSurfaceProps::kUseDistanceFieldFonts_Flag, kUnknown_SkPixelGeometry); + auto grContext = this->context()->internal().grContext(); + auto atlasTextContext = grContext->priv().drawingManager()->getTextContext(); + SkGlyphRunBuilder builder; + builder.drawGlyphsWithPositions(paint, font.makeFont(), + SkSpan{glyphs, SkTo(glyphCnt)}, + positions); + auto glyphRunList = builder.useGlyphRunList(); + if (!glyphRunList.empty()) { + atlasTextContext->drawGlyphRunList(grContext, this, GrNoClip(), this->ctm(), props, + glyphRunList); + } +} + +void SkInternalAtlasTextTarget::addDrawOp(const GrClip& clip, std::unique_ptr op) { + SkASSERT(clip.quickContains(SkRect::MakeIWH(fWidth, fHeight))); + // The SkAtlasTextRenderer currently only handles grayscale SDF glyphs. + if (op->maskType() != GrAtlasTextOp::kGrayscaleDistanceField_MaskType) { + return; + } + const GrCaps& caps = *this->context()->internal().grContext()->priv().caps(); + op->finalizeForTextTarget(fColor, caps); + int n = SkTMin(kMaxBatchLookBack, fOps.count()); + for (int i = 0; i < n; ++i) { + GrAtlasTextOp* other = fOps.fromBack(i).get(); + if (other->combineIfPossible(op.get(), caps) == GrOp::CombineResult::kMerged) { + fOpMemoryPool->release(std::move(op)); + return; + } + if (GrRectsOverlap(op->bounds(), other->bounds())) { + break; + } + } + fOps.emplace_back(std::move(op)); +} + +void SkInternalAtlasTextTarget::deleteOps() { + for (int i = 0; i < fOps.count(); ++i) { + if (fOps[i]) { + fOpMemoryPool->release(std::move(fOps[i])); + } + } + fOps.reset(); +} + +void SkInternalAtlasTextTarget::flush() { + for (int i = 0; i < fOps.count(); ++i) { + fOps[i]->executeForTextTarget(this); + } + this->context()->internal().flush(); + this->deleteOps(); +} + +void GrAtlasTextOp::finalizeForTextTarget(uint32_t color, const GrCaps& caps) { + // TODO4F: Odd handling of client colors among AtlasTextTarget and AtlasTextRenderer + SkPMColor4f color4f = SkPMColor4f::FromBytes_RGBA(color); + for (int i = 0; i < fGeoCount; ++i) { + fGeoData[i].fColor = color4f; + } + // Atlas text doesn't use MSAA, so no need to handle mixed samples. + // Also, no need to support normalized F16 with manual clamp? + this->finalize(caps, nullptr /* applied clip */, false /* mixed samples */, GrClampType::kAuto); +} + +void GrAtlasTextOp::executeForTextTarget(SkAtlasTextTarget* target) { + FlushInfo flushInfo; + SkExclusiveStrikePtr autoGlyphCache; + auto& context = target->context()->internal(); + auto glyphCache = context.grContext()->priv().getGrStrikeCache(); + auto atlasManager = context.grContext()->priv().getAtlasManager(); + auto resourceProvider = context.grContext()->priv().resourceProvider(); + + unsigned int numProxies; + if (!atlasManager->getProxies(kA8_GrMaskFormat, &numProxies)) { + return; + } + + for (int i = 0; i < fGeoCount; ++i) { + // TODO4F: Preserve float colors + GrTextBlob::VertexRegenerator regenerator( + resourceProvider, fGeoData[i].fBlob, fGeoData[i].fRun, fGeoData[i].fSubRun, + fGeoData[i].fViewMatrix, fGeoData[i].fX, fGeoData[i].fY, + fGeoData[i].fColor.toBytes_RGBA(), &context, glyphCache, atlasManager, + &autoGlyphCache); + bool done = false; + while (!done) { + GrTextBlob::VertexRegenerator::Result result; + if (!regenerator.regenerate(&result)) { + break; + } + done = result.fFinished; + + context.recordDraw(result.fFirstVertex, result.fGlyphsRegenerated, + fGeoData[i].fViewMatrix, target->handle()); + if (!result.fFinished) { + // Make space in the atlas so we can continue generating vertices. + context.flush(); + } + } + } +} diff --git a/gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.cpp b/gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.cpp new file mode 100644 index 0000000000..725be72e6d --- /dev/null +++ b/gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.cpp @@ -0,0 +1,128 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/atlastext/SkAtlasTextContext.h" +#include "include/atlastext/SkAtlasTextRenderer.h" +#include "include/gpu/GrContext.h" +#include "src/atlastext/SkInternalAtlasTextContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/text/GrStrikeCache.h" + +SkAtlasTextRenderer* SkGetAtlasTextRendererFromInternalContext( + class SkInternalAtlasTextContext& internal) { + return internal.renderer(); +} + +////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr SkInternalAtlasTextContext::Make( + sk_sp renderer) { + return std::unique_ptr( + new SkInternalAtlasTextContext(std::move(renderer))); +} + +SkInternalAtlasTextContext::SkInternalAtlasTextContext(sk_sp renderer) + : fRenderer(std::move(renderer)) { + GrContextOptions options; + options.fAllowMultipleGlyphCacheTextures = GrContextOptions::Enable::kNo; + options.fMinDistanceFieldFontSize = 0.f; + options.fGlyphsAsPathsFontSize = SK_ScalarInfinity; + options.fDistanceFieldGlyphVerticesAlwaysHaveW = GrContextOptions::Enable::kYes; + fGrContext = GrContext::MakeMock(nullptr, options); +} + +SkInternalAtlasTextContext::~SkInternalAtlasTextContext() { + if (fDistanceFieldAtlas.fProxy) { +#ifdef SK_DEBUG + auto atlasManager = fGrContext->priv().getAtlasManager(); + if (atlasManager) { + unsigned int numProxies; + atlasManager->getProxies(kA8_GrMaskFormat, &numProxies); + SkASSERT(1 == numProxies); + } +#endif + fRenderer->deleteTexture(fDistanceFieldAtlas.fTextureHandle); + } +} + +GrStrikeCache* SkInternalAtlasTextContext::glyphCache() { + return fGrContext->priv().getGrStrikeCache(); +} + +GrTextBlobCache* SkInternalAtlasTextContext::textBlobCache() { + return fGrContext->priv().getTextBlobCache(); +} + +GrDeferredUploadToken SkInternalAtlasTextContext::addInlineUpload( + GrDeferredTextureUploadFn&& upload) { + auto token = fTokenTracker.nextDrawToken(); + fInlineUploads.append(&fArena, InlineUpload{std::move(upload), token}); + return token; +} + +GrDeferredUploadToken SkInternalAtlasTextContext::addASAPUpload( + GrDeferredTextureUploadFn&& upload) { + fASAPUploads.append(&fArena, std::move(upload)); + return fTokenTracker.nextTokenToFlush(); +} + +void SkInternalAtlasTextContext::recordDraw(const void* srcVertexData, int glyphCnt, + const SkMatrix& matrix, void* targetHandle) { + auto vertexDataSize = sizeof(SkAtlasTextRenderer::SDFVertex) * 4 * glyphCnt; + auto vertexData = fArena.makeArrayDefault(vertexDataSize); + memcpy(vertexData, srcVertexData, vertexDataSize); + for (int i = 0; i < 4 * glyphCnt; ++i) { + auto* vertex = reinterpret_cast(vertexData) + i; + // GrTextContext encodes a texture index into the lower bit of each texture coord. + // This isn't expected by SkAtlasTextRenderer subclasses. + vertex->fTextureCoordX /= 2; + vertex->fTextureCoordY /= 2; + matrix.mapHomogeneousPoints(&vertex->fPosition, &vertex->fPosition, 1); + } + fDraws.append(&fArena, + Draw{glyphCnt, fTokenTracker.issueDrawToken(), targetHandle, vertexData}); +} + +void SkInternalAtlasTextContext::flush() { + auto* atlasManager = fGrContext->priv().getAtlasManager(); + if (!fDistanceFieldAtlas.fProxy) { + unsigned int numProxies; + fDistanceFieldAtlas.fProxy = atlasManager->getProxies(kA8_GrMaskFormat, &numProxies)->get(); + SkASSERT(1 == numProxies); + fDistanceFieldAtlas.fTextureHandle = + fRenderer->createTexture(SkAtlasTextRenderer::AtlasFormat::kA8, + fDistanceFieldAtlas.fProxy->width(), + fDistanceFieldAtlas.fProxy->height()); + } + GrDeferredTextureUploadWritePixelsFn writePixelsFn = + [this](GrTextureProxy* proxy, int left, int top, int width, int height, + GrColorType colorType, const void* data, size_t rowBytes) -> bool { + SkASSERT(GrColorType::kAlpha_8 == colorType); + SkASSERT(proxy == this->fDistanceFieldAtlas.fProxy); + void* handle = fDistanceFieldAtlas.fTextureHandle; + this->fRenderer->setTextureData(handle, data, left, top, width, height, rowBytes); + return true; + }; + for (const auto& upload : fASAPUploads) { + upload(writePixelsFn); + } + auto inlineUpload = fInlineUploads.begin(); + for (const auto& draw : fDraws) { + while (inlineUpload != fInlineUploads.end() && inlineUpload->fToken == draw.fToken) { + inlineUpload->fUpload(writePixelsFn); + ++inlineUpload; + } + auto vertices = reinterpret_cast(draw.fVertexData); + fRenderer->drawSDFGlyphs(draw.fTargetHandle, fDistanceFieldAtlas.fTextureHandle, vertices, + draw.fGlyphCnt); + fTokenTracker.flushToken(); + } + fASAPUploads.reset(); + fInlineUploads.reset(); + fDraws.reset(); + fArena.reset(); +} diff --git a/gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.h b/gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.h new file mode 100644 index 0000000000..d0f7f23d2f --- /dev/null +++ b/gfx/skia/skia/src/atlastext/SkInternalAtlasTextContext.h @@ -0,0 +1,83 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkInternalAtlasTextContext_DEFINED +#define SkInternalAtlasTextContext_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkArenaAllocList.h" +#include "src/gpu/GrDeferredUpload.h" + +class GrContext; +class GrStrikeCache; +class GrTextBlobCache; + +class SkAtlasTextRenderer; +class SkMatrix; + +/** + * The implementation of SkAtlasTextContext. This exists to hide the details from the public class. + * and to be able to use other private types. + */ +class SkInternalAtlasTextContext : public GrDeferredUploadTarget { +public: + static std::unique_ptr Make(sk_sp); + + ~SkInternalAtlasTextContext() override; + + SkAtlasTextRenderer* renderer() const { return fRenderer.get(); } + + GrContext* grContext() const { return fGrContext.get(); } + GrStrikeCache* glyphCache(); + GrTextBlobCache* textBlobCache(); + + const GrTokenTracker* tokenTracker() final { return &fTokenTracker; } + GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) final; + GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&&) final; + + void recordDraw(const void* vertexData, int glyphCnt, const SkMatrix&, void* targetHandle); + + void flush(); + +private: + class DeferredUploader; + SkInternalAtlasTextContext() = delete; + SkInternalAtlasTextContext(const SkInternalAtlasTextContext&) = delete; + SkInternalAtlasTextContext& operator=(const SkInternalAtlasTextContext&) = delete; + + SkInternalAtlasTextContext(sk_sp); + + sk_sp fRenderer; + + struct AtlasTexture { + void* fTextureHandle = nullptr; + GrTextureProxy* fProxy = nullptr; + }; + + struct Draw { + int fGlyphCnt; + GrDeferredUploadToken fToken; + void* fTargetHandle; + const void* fVertexData; + }; + + struct InlineUpload { + GrDeferredTextureUploadFn fUpload; + GrDeferredUploadToken fToken; + }; + + GrTokenTracker fTokenTracker; + SkArenaAllocList fInlineUploads; + SkArenaAllocList fDraws; + SkArenaAllocList fASAPUploads; + SkArenaAlloc fArena{1024 * 40}; + sk_sp fGrContext; + AtlasTexture fDistanceFieldAtlas; +}; + +#endif diff --git a/gfx/skia/skia/src/c/sk_c_from_to.h b/gfx/skia/skia/src/c/sk_c_from_to.h new file mode 100644 index 0000000000..19fda37a26 --- /dev/null +++ b/gfx/skia/skia/src/c/sk_c_from_to.h @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +static bool find_sk(CType from, SKType* to) { + for (size_t i = 0; i < SK_ARRAY_COUNT(CTypeSkTypeMap); ++i) { + if (CTypeSkTypeMap[i].fC == from) { + if (to) { + *to = CTypeSkTypeMap[i].fSK; + } + return true; + } + } + return false; +} + +static bool find_c(SKType from, CType* to) { + for (size_t i = 0; i < SK_ARRAY_COUNT(CTypeSkTypeMap); ++i) { + if (CTypeSkTypeMap[i].fSK == from) { + if (to) { + *to = CTypeSkTypeMap[i].fC; + } + return true; + } + } + return false; +} + +#undef CType +#undef SKType +#undef CTypeSkTypeMap diff --git a/gfx/skia/skia/src/c/sk_effects.cpp b/gfx/skia/skia/src/c/sk_effects.cpp new file mode 100644 index 0000000000..7e226cd756 --- /dev/null +++ b/gfx/skia/skia/src/c/sk_effects.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "src/c/sk_types_priv.h" + +static void from_c_matrix(const sk_matrix_t* cmatrix, SkMatrix* matrix) { + matrix->setAll(cmatrix->mat[0], cmatrix->mat[1], cmatrix->mat[2], + cmatrix->mat[3], cmatrix->mat[4], cmatrix->mat[5], + cmatrix->mat[6], cmatrix->mat[7], cmatrix->mat[8]); +} + +#include "include/c/sk_shader.h" +#include "include/effects/SkGradientShader.h" + +const struct { + sk_shader_tilemode_t fC; + SkTileMode fSK; +} gTileModeMap[] = { + { CLAMP_SK_SHADER_TILEMODE, SkTileMode::kClamp }, + { REPEAT_SK_SHADER_TILEMODE, SkTileMode::kRepeat }, + { MIRROR_SK_SHADER_TILEMODE, SkTileMode::kMirror }, +}; + +static bool from_c_tilemode(sk_shader_tilemode_t cMode, SkTileMode* skMode) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gTileModeMap); ++i) { + if (cMode == gTileModeMap[i].fC) { + if (skMode) { + *skMode = gTileModeMap[i].fSK; + } + return true; + } + } + return false; +} + +void sk_shader_ref(sk_shader_t* cshader) { + SkSafeRef(AsShader(cshader)); +} + +void sk_shader_unref(sk_shader_t* cshader) { + SkSafeUnref(AsShader(cshader)); +} + +sk_shader_t* sk_shader_new_linear_gradient(const sk_point_t pts[2], + const sk_color_t colors[], + const float colorPos[], + int colorCount, + sk_shader_tilemode_t cmode, + const sk_matrix_t* cmatrix) { + SkTileMode mode; + if (!from_c_tilemode(cmode, &mode)) { + return nullptr; + } + SkMatrix matrix; + if (cmatrix) { + from_c_matrix(cmatrix, &matrix); + } else { + matrix.setIdentity(); + } + return (sk_shader_t*)SkGradientShader::MakeLinear(reinterpret_cast(pts), + reinterpret_cast(colors), + colorPos, colorCount, + mode, 0, &matrix).release(); +} + +static const SkPoint& to_skpoint(const sk_point_t& p) { + return reinterpret_cast(p); +} + +sk_shader_t* sk_shader_new_radial_gradient(const sk_point_t* ccenter, + float radius, + const sk_color_t colors[], + const float colorPos[], + int colorCount, + sk_shader_tilemode_t cmode, + const sk_matrix_t* cmatrix) { + SkTileMode mode; + if (!from_c_tilemode(cmode, &mode)) { + return nullptr; + } + SkMatrix matrix; + if (cmatrix) { + from_c_matrix(cmatrix, &matrix); + } else { + matrix.setIdentity(); + } + SkPoint center = to_skpoint(*ccenter); + return (sk_shader_t*)SkGradientShader::MakeRadial(center, (SkScalar)radius, + reinterpret_cast(colors), + reinterpret_cast(colorPos), + colorCount, mode, 0, &matrix).release(); +} + +sk_shader_t* sk_shader_new_sweep_gradient(const sk_point_t* ccenter, + const sk_color_t colors[], + const float colorPos[], + int colorCount, + const sk_matrix_t* cmatrix) { + SkMatrix matrix; + if (cmatrix) { + from_c_matrix(cmatrix, &matrix); + } else { + matrix.setIdentity(); + } + return (sk_shader_t*)SkGradientShader::MakeSweep((SkScalar)(ccenter->x), + (SkScalar)(ccenter->y), + reinterpret_cast(colors), + reinterpret_cast(colorPos), + colorCount, 0, &matrix).release(); +} + +sk_shader_t* sk_shader_new_two_point_conical_gradient(const sk_point_t* start, + float startRadius, + const sk_point_t* end, + float endRadius, + const sk_color_t colors[], + const float colorPos[], + int colorCount, + sk_shader_tilemode_t cmode, + const sk_matrix_t* cmatrix) { + SkTileMode mode; + if (!from_c_tilemode(cmode, &mode)) { + return nullptr; + } + SkMatrix matrix; + if (cmatrix) { + from_c_matrix(cmatrix, &matrix); + } else { + matrix.setIdentity(); + } + SkPoint skstart = to_skpoint(*start); + SkPoint skend = to_skpoint(*end); + return (sk_shader_t*)SkGradientShader::MakeTwoPointConical(skstart, (SkScalar)startRadius, + skend, (SkScalar)endRadius, + reinterpret_cast(colors), + reinterpret_cast(colorPos), + colorCount, mode, 0, &matrix).release(); +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +#include "include/c/sk_maskfilter.h" +#include "include/core/SkMaskFilter.h" + +const struct { + sk_blurstyle_t fC; + SkBlurStyle fSk; +} gBlurStylePairs[] = { + { NORMAL_SK_BLUR_STYLE, kNormal_SkBlurStyle }, + { SOLID_SK_BLUR_STYLE, kSolid_SkBlurStyle }, + { OUTER_SK_BLUR_STYLE, kOuter_SkBlurStyle }, + { INNER_SK_BLUR_STYLE, kInner_SkBlurStyle }, +}; + +static bool find_blurstyle(sk_blurstyle_t csrc, SkBlurStyle* dst) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gBlurStylePairs); ++i) { + if (gBlurStylePairs[i].fC == csrc) { + if (dst) { + *dst = gBlurStylePairs[i].fSk; + } + return true; + } + } + return false; +} + +void sk_maskfilter_ref(sk_maskfilter_t* cfilter) { + SkSafeRef(AsMaskFilter(cfilter)); +} + +void sk_maskfilter_unref(sk_maskfilter_t* cfilter) { + SkSafeUnref(AsMaskFilter(cfilter)); +} + +sk_maskfilter_t* sk_maskfilter_new_blur(sk_blurstyle_t cstyle, float sigma) { + SkBlurStyle style; + if (!find_blurstyle(cstyle, &style)) { + return nullptr; + } + return ToMaskFilter(SkMaskFilter::MakeBlur(style, sigma).release()); +} diff --git a/gfx/skia/skia/src/c/sk_imageinfo.cpp b/gfx/skia/skia/src/c/sk_imageinfo.cpp new file mode 100644 index 0000000000..160c7f9ab7 --- /dev/null +++ b/gfx/skia/skia/src/c/sk_imageinfo.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" + +#include "include/c/sk_colorspace.h" +#include "include/c/sk_imageinfo.h" + +const struct { + sk_colortype_t fC; + SkColorType fSK; +} gColorTypeMap[] = { + { UNKNOWN_SK_COLORTYPE, kUnknown_SkColorType }, + { RGBA_8888_SK_COLORTYPE, kRGBA_8888_SkColorType }, + { BGRA_8888_SK_COLORTYPE, kBGRA_8888_SkColorType }, + { ALPHA_8_SK_COLORTYPE, kAlpha_8_SkColorType }, + { GRAY_8_SK_COLORTYPE, kGray_8_SkColorType }, + { RGBA_F16_SK_COLORTYPE, kRGBA_F16_SkColorType }, + { RGBA_F32_SK_COLORTYPE, kRGBA_F32_SkColorType }, +}; + +const struct { + sk_alphatype_t fC; + SkAlphaType fSK; +} gAlphaTypeMap[] = { + { OPAQUE_SK_ALPHATYPE, kOpaque_SkAlphaType }, + { PREMUL_SK_ALPHATYPE, kPremul_SkAlphaType }, + { UNPREMUL_SK_ALPHATYPE, kUnpremul_SkAlphaType }, +}; + +static bool from_c_colortype(sk_colortype_t cCT, SkColorType* skCT) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gColorTypeMap); ++i) { + if (gColorTypeMap[i].fC == cCT) { + if (skCT) { + *skCT = gColorTypeMap[i].fSK; + } + return true; + } + } + return false; +} + +static bool to_c_colortype(SkColorType skCT, sk_colortype_t* cCT) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gColorTypeMap); ++i) { + if (gColorTypeMap[i].fSK == skCT) { + if (cCT) { + *cCT = gColorTypeMap[i].fC; + } + return true; + } + } + return false; +} + +static bool from_c_alphatype(sk_alphatype_t cAT, SkAlphaType* skAT) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gAlphaTypeMap); ++i) { + if (gAlphaTypeMap[i].fC == cAT) { + if (skAT) { + *skAT = gAlphaTypeMap[i].fSK; + } + return true; + } + } + return false; +} + +static bool to_c_alphatype(SkAlphaType skAT, sk_alphatype_t* cAT) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gAlphaTypeMap); ++i) { + if (gAlphaTypeMap[i].fSK == skAT) { + if (cAT) { + *cAT = gAlphaTypeMap[i].fC; + } + return true; + } + } + return false; +} + +const SkImageInfo* ToImageInfo(const sk_imageinfo_t* cinfo) { + return reinterpret_cast(cinfo); +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +sk_imageinfo_t* sk_imageinfo_new(int w, int h, sk_colortype_t cct, sk_alphatype_t cat, + sk_colorspace_t* ccs) { + SkColorType ct; + SkAlphaType at; + if (!from_c_colortype(cct, &ct) || !from_c_alphatype(cat, &at)) { + return nullptr; + } + SkColorSpace* cs = (SkColorSpace*)ccs; + + SkImageInfo* info = new SkImageInfo(SkImageInfo::Make(w, h, ct, at, sk_ref_sp(cs))); + return reinterpret_cast(info); +} + +void sk_imageinfo_delete(sk_imageinfo_t* cinfo) { + delete ToImageInfo(cinfo); +} + +int32_t sk_imageinfo_get_width(const sk_imageinfo_t* cinfo) { + return ToImageInfo(cinfo)->width(); +} + +int32_t sk_imageinfo_get_height(const sk_imageinfo_t* cinfo) { + return ToImageInfo(cinfo)->height(); +} + +sk_colortype_t sk_imageinfo_get_colortype(const sk_imageinfo_t* cinfo) { + sk_colortype_t ct; + return to_c_colortype(ToImageInfo(cinfo)->colorType(), &ct) ? ct : UNKNOWN_SK_COLORTYPE; +} + +sk_alphatype_t sk_imageinfo_get_alphatype(const sk_imageinfo_t* cinfo) { + sk_alphatype_t at; + // odd that we return premul on failure... + return to_c_alphatype(ToImageInfo(cinfo)->alphaType(), &at) ? at : PREMUL_SK_ALPHATYPE; +} + +sk_colorspace_t* sk_imageinfo_get_colorspace(const sk_imageinfo_t* cinfo) { + return reinterpret_cast(ToImageInfo(cinfo)->colorSpace()); +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +sk_colorspace_t* sk_colorspace_new_srgb() { + return reinterpret_cast(SkColorSpace::MakeSRGB().release()); +} + +void sk_colorspace_ref(sk_colorspace_t* cs) { + SkSafeRef(reinterpret_cast(cs)); +} + +void sk_colorspace_unref(sk_colorspace_t* cs) { + SkSafeUnref(reinterpret_cast(cs)); +} + diff --git a/gfx/skia/skia/src/c/sk_paint.cpp b/gfx/skia/skia/src/c/sk_paint.cpp new file mode 100644 index 0000000000..0236270b15 --- /dev/null +++ b/gfx/skia/skia/src/c/sk_paint.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBlendMode.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" + +#include "include/c/sk_paint.h" +#include "src/c/sk_types_priv.h" + +#define MAKE_FROM_TO_NAME(FROM) g_ ## FROM ## _map + +const struct { + sk_stroke_cap_t fC; + SkPaint::Cap fSK; +} MAKE_FROM_TO_NAME(sk_stroke_cap_t)[] = { + { BUTT_SK_STROKE_CAP, SkPaint::kButt_Cap }, + { ROUND_SK_STROKE_CAP, SkPaint::kRound_Cap }, + { SQUARE_SK_STROKE_CAP, SkPaint::kSquare_Cap }, +}; + +const struct { + sk_stroke_join_t fC; + SkPaint::Join fSK; +} MAKE_FROM_TO_NAME(sk_stroke_join_t)[] = { + { MITER_SK_STROKE_JOIN, SkPaint::kMiter_Join }, + { ROUND_SK_STROKE_JOIN, SkPaint::kRound_Join }, + { BEVEL_SK_STROKE_JOIN, SkPaint::kBevel_Join }, +}; + +#define CType sk_stroke_cap_t +#define SKType SkPaint::Cap +#define CTypeSkTypeMap MAKE_FROM_TO_NAME(sk_stroke_cap_t) +#include "src/c/sk_c_from_to.h" + +#define CType sk_stroke_join_t +#define SKType SkPaint::Join +#define CTypeSkTypeMap MAKE_FROM_TO_NAME(sk_stroke_join_t) +#include "src/c/sk_c_from_to.h" + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_paint_t* sk_paint_new() { return (sk_paint_t*)new SkPaint; } + +void sk_paint_delete(sk_paint_t* cpaint) { delete AsPaint(cpaint); } + +bool sk_paint_is_antialias(const sk_paint_t* cpaint) { + return AsPaint(*cpaint).isAntiAlias(); +} + +void sk_paint_set_antialias(sk_paint_t* cpaint, bool aa) { + AsPaint(cpaint)->setAntiAlias(aa); +} + +sk_color_t sk_paint_get_color(const sk_paint_t* cpaint) { + return AsPaint(*cpaint).getColor(); +} + +void sk_paint_set_color(sk_paint_t* cpaint, sk_color_t c) { + AsPaint(cpaint)->setColor(c); +} + +void sk_paint_set_shader(sk_paint_t* cpaint, sk_shader_t* cshader) { + AsPaint(cpaint)->setShader(sk_ref_sp(AsShader(cshader))); +} + +void sk_paint_set_maskfilter(sk_paint_t* cpaint, sk_maskfilter_t* cfilter) { + AsPaint(cpaint)->setMaskFilter(sk_ref_sp(AsMaskFilter(cfilter))); +} + +bool sk_paint_is_stroke(const sk_paint_t* cpaint) { + return AsPaint(*cpaint).getStyle() != SkPaint::kFill_Style; +} + +void sk_paint_set_stroke(sk_paint_t* cpaint, bool doStroke) { + AsPaint(cpaint)->setStyle(doStroke ? SkPaint::kStroke_Style : SkPaint::kFill_Style); +} + +float sk_paint_get_stroke_width(const sk_paint_t* cpaint) { + return AsPaint(*cpaint).getStrokeWidth(); +} + +void sk_paint_set_stroke_width(sk_paint_t* cpaint, float width) { + AsPaint(cpaint)->setStrokeWidth(width); +} + +float sk_paint_get_stroke_miter(const sk_paint_t* cpaint) { + return AsPaint(*cpaint).getStrokeMiter(); +} + +void sk_paint_set_stroke_miter(sk_paint_t* cpaint, float miter) { + AsPaint(cpaint)->setStrokeMiter(miter); +} + +sk_stroke_cap_t sk_paint_get_stroke_cap(const sk_paint_t* cpaint) { + sk_stroke_cap_t ccap; + if (find_c(AsPaint(*cpaint).getStrokeCap(), &ccap)) { + ccap = BUTT_SK_STROKE_CAP; + } + return ccap; +} + +void sk_paint_set_stroke_cap(sk_paint_t* cpaint, sk_stroke_cap_t ccap) { + SkPaint::Cap skcap; + if (find_sk(ccap, &skcap)) { + AsPaint(cpaint)->setStrokeCap(skcap); + } else { + // unknown ccap + } +} + +sk_stroke_join_t sk_paint_get_stroke_join(const sk_paint_t* cpaint) { + sk_stroke_join_t cjoin; + if (find_c(AsPaint(*cpaint).getStrokeJoin(), &cjoin)) { + cjoin = MITER_SK_STROKE_JOIN; + } + return cjoin; +} + +void sk_paint_set_stroke_join(sk_paint_t* cpaint, sk_stroke_join_t cjoin) { + SkPaint::Join skjoin; + if (find_sk(cjoin, &skjoin)) { + AsPaint(cpaint)->setStrokeJoin(skjoin); + } else { + // unknown cjoin + } +} + +void sk_paint_set_xfermode_mode(sk_paint_t* paint, sk_xfermode_mode_t mode) { + SkASSERT(paint); + SkBlendMode skmode; + switch (mode) { + #define MAP(X, Y) case (X): skmode = (Y); break + MAP( CLEAR_SK_XFERMODE_MODE, SkBlendMode::kClear ); + MAP( SRC_SK_XFERMODE_MODE, SkBlendMode::kSrc ); + MAP( DST_SK_XFERMODE_MODE, SkBlendMode::kDst ); + MAP( SRCOVER_SK_XFERMODE_MODE, SkBlendMode::kSrcOver ); + MAP( DSTOVER_SK_XFERMODE_MODE, SkBlendMode::kDstOver ); + MAP( SRCIN_SK_XFERMODE_MODE, SkBlendMode::kSrcIn ); + MAP( DSTIN_SK_XFERMODE_MODE, SkBlendMode::kDstIn ); + MAP( SRCOUT_SK_XFERMODE_MODE, SkBlendMode::kSrcOut ); + MAP( DSTOUT_SK_XFERMODE_MODE, SkBlendMode::kDstOut ); + MAP( SRCATOP_SK_XFERMODE_MODE, SkBlendMode::kSrcATop ); + MAP( DSTATOP_SK_XFERMODE_MODE, SkBlendMode::kDstATop ); + MAP( XOR_SK_XFERMODE_MODE, SkBlendMode::kXor ); + MAP( PLUS_SK_XFERMODE_MODE, SkBlendMode::kPlus ); + MAP( MODULATE_SK_XFERMODE_MODE, SkBlendMode::kModulate ); + MAP( SCREEN_SK_XFERMODE_MODE, SkBlendMode::kScreen ); + MAP( OVERLAY_SK_XFERMODE_MODE, SkBlendMode::kOverlay ); + MAP( DARKEN_SK_XFERMODE_MODE, SkBlendMode::kDarken ); + MAP( LIGHTEN_SK_XFERMODE_MODE, SkBlendMode::kLighten ); + MAP( COLORDODGE_SK_XFERMODE_MODE, SkBlendMode::kColorDodge ); + MAP( COLORBURN_SK_XFERMODE_MODE, SkBlendMode::kColorBurn ); + MAP( HARDLIGHT_SK_XFERMODE_MODE, SkBlendMode::kHardLight ); + MAP( SOFTLIGHT_SK_XFERMODE_MODE, SkBlendMode::kSoftLight ); + MAP( DIFFERENCE_SK_XFERMODE_MODE, SkBlendMode::kDifference ); + MAP( EXCLUSION_SK_XFERMODE_MODE, SkBlendMode::kExclusion ); + MAP( MULTIPLY_SK_XFERMODE_MODE, SkBlendMode::kMultiply ); + MAP( HUE_SK_XFERMODE_MODE, SkBlendMode::kHue ); + MAP( SATURATION_SK_XFERMODE_MODE, SkBlendMode::kSaturation ); + MAP( COLOR_SK_XFERMODE_MODE, SkBlendMode::kColor ); + MAP( LUMINOSITY_SK_XFERMODE_MODE, SkBlendMode::kLuminosity ); + #undef MAP + default: + return; + } + AsPaint(paint)->setBlendMode(skmode); +} diff --git a/gfx/skia/skia/src/c/sk_surface.cpp b/gfx/skia/skia/src/c/sk_surface.cpp new file mode 100644 index 0000000000..b3f203fc24 --- /dev/null +++ b/gfx/skia/skia/src/c/sk_surface.cpp @@ -0,0 +1,443 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/core/SkImage.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkPictureRecorder.h" +#include "include/core/SkSurface.h" + +#include "include/c/sk_canvas.h" +#include "include/c/sk_data.h" +#include "include/c/sk_image.h" +#include "include/c/sk_paint.h" +#include "include/c/sk_path.h" +#include "include/c/sk_picture.h" +#include "include/c/sk_surface.h" +#include "src/c/sk_types_priv.h" + +const struct { + sk_pixelgeometry_t fC; + SkPixelGeometry fSK; +} gPixelGeometryMap[] = { + { UNKNOWN_SK_PIXELGEOMETRY, kUnknown_SkPixelGeometry }, + { RGB_H_SK_PIXELGEOMETRY, kRGB_H_SkPixelGeometry }, + { BGR_H_SK_PIXELGEOMETRY, kBGR_H_SkPixelGeometry }, + { RGB_V_SK_PIXELGEOMETRY, kRGB_V_SkPixelGeometry }, + { BGR_V_SK_PIXELGEOMETRY, kBGR_V_SkPixelGeometry }, +}; + + +static bool from_c_pixelgeometry(sk_pixelgeometry_t cGeom, SkPixelGeometry* skGeom) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gPixelGeometryMap); ++i) { + if (gPixelGeometryMap[i].fC == cGeom) { + if (skGeom) { + *skGeom = gPixelGeometryMap[i].fSK; + } + return true; + } + } + return false; +} + +static void from_c_matrix(const sk_matrix_t* cmatrix, SkMatrix* matrix) { + matrix->setAll(cmatrix->mat[0], cmatrix->mat[1], cmatrix->mat[2], + cmatrix->mat[3], cmatrix->mat[4], cmatrix->mat[5], + cmatrix->mat[6], cmatrix->mat[7], cmatrix->mat[8]); +} + +const struct { + sk_path_direction_t fC; + SkPath::Direction fSk; +} gPathDirMap[] = { + { CW_SK_PATH_DIRECTION, SkPath::kCW_Direction }, + { CCW_SK_PATH_DIRECTION, SkPath::kCCW_Direction }, +}; + +static bool from_c_path_direction(sk_path_direction_t cdir, SkPath::Direction* dir) { + for (size_t i = 0; i < SK_ARRAY_COUNT(gPathDirMap); ++i) { + if (gPathDirMap[i].fC == cdir) { + if (dir) { + *dir = gPathDirMap[i].fSk; + } + return true; + } + } + return false; +} + +static SkData* AsData(const sk_data_t* cdata) { + return reinterpret_cast(const_cast(cdata)); +} + +static sk_data_t* ToData(SkData* data) { + return reinterpret_cast(data); +} + +static sk_rect_t ToRect(const SkRect& rect) { + return reinterpret_cast(rect); +} + +static const SkRect& AsRect(const sk_rect_t& crect) { + return reinterpret_cast(crect); +} + +static const SkPath& AsPath(const sk_path_t& cpath) { + return reinterpret_cast(cpath); +} + +static SkPath* as_path(sk_path_t* cpath) { + return reinterpret_cast(cpath); +} + +static const SkImage* AsImage(const sk_image_t* cimage) { + return reinterpret_cast(cimage); +} + +static sk_image_t* ToImage(SkImage* cimage) { + return reinterpret_cast(cimage); +} + +static sk_canvas_t* ToCanvas(SkCanvas* canvas) { + return reinterpret_cast(canvas); +} + +static SkCanvas* AsCanvas(sk_canvas_t* ccanvas) { + return reinterpret_cast(ccanvas); +} + +static SkPictureRecorder* AsPictureRecorder(sk_picture_recorder_t* crec) { + return reinterpret_cast(crec); +} + +static sk_picture_recorder_t* ToPictureRecorder(SkPictureRecorder* rec) { + return reinterpret_cast(rec); +} + +static const SkPicture* AsPicture(const sk_picture_t* cpic) { + return reinterpret_cast(cpic); +} + +static SkPicture* AsPicture(sk_picture_t* cpic) { + return reinterpret_cast(cpic); +} + +static sk_picture_t* ToPicture(SkPicture* pic) { + return reinterpret_cast(pic); +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +sk_image_t* sk_image_new_raster_copy(const sk_imageinfo_t* cinfo, const void* pixels, + size_t rowBytes) { + const SkImageInfo* info = reinterpret_cast(cinfo); + return (sk_image_t*)SkImage::MakeRasterCopy(SkPixmap(*info, pixels, rowBytes)).release(); +} + +sk_image_t* sk_image_new_from_encoded(const sk_data_t* cdata, const sk_irect_t* subset) { + return ToImage(SkImage::MakeFromEncoded(sk_ref_sp(AsData(cdata)), + reinterpret_cast(subset)).release()); +} + +sk_data_t* sk_image_encode(const sk_image_t* cimage) { + return ToData(AsImage(cimage)->encodeToData().release()); +} + +void sk_image_ref(const sk_image_t* cimage) { + AsImage(cimage)->ref(); +} + +void sk_image_unref(const sk_image_t* cimage) { + AsImage(cimage)->unref(); +} + +int sk_image_get_width(const sk_image_t* cimage) { + return AsImage(cimage)->width(); +} + +int sk_image_get_height(const sk_image_t* cimage) { + return AsImage(cimage)->height(); +} + +uint32_t sk_image_get_unique_id(const sk_image_t* cimage) { + return AsImage(cimage)->uniqueID(); +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +sk_path_t* sk_path_new() { return (sk_path_t*)new SkPath; } + +void sk_path_delete(sk_path_t* cpath) { delete as_path(cpath); } + +void sk_path_move_to(sk_path_t* cpath, float x, float y) { + as_path(cpath)->moveTo(x, y); +} + +void sk_path_line_to(sk_path_t* cpath, float x, float y) { + as_path(cpath)->lineTo(x, y); +} + +void sk_path_quad_to(sk_path_t* cpath, float x0, float y0, float x1, float y1) { + as_path(cpath)->quadTo(x0, y0, x1, y1); +} + +void sk_path_conic_to(sk_path_t* cpath, float x0, float y0, float x1, float y1, float w) { + as_path(cpath)->conicTo(x0, y0, x1, y1, w); +} + +void sk_path_cubic_to(sk_path_t* cpath, float x0, float y0, float x1, float y1, float x2, float y2) { + as_path(cpath)->cubicTo(x0, y0, x1, y1, x2, y2); +} + +void sk_path_close(sk_path_t* cpath) { + as_path(cpath)->close(); +} + +void sk_path_add_rect(sk_path_t* cpath, const sk_rect_t* crect, sk_path_direction_t cdir) { + SkPath::Direction dir; + if (!from_c_path_direction(cdir, &dir)) { + return; + } + as_path(cpath)->addRect(AsRect(*crect), dir); +} + +void sk_path_add_oval(sk_path_t* cpath, const sk_rect_t* crect, sk_path_direction_t cdir) { + SkPath::Direction dir; + if (!from_c_path_direction(cdir, &dir)) { + return; + } + as_path(cpath)->addOval(AsRect(*crect), dir); +} + +bool sk_path_get_bounds(const sk_path_t* cpath, sk_rect_t* crect) { + const SkPath& path = AsPath(*cpath); + + if (path.isEmpty()) { + if (crect) { + *crect = ToRect(SkRect::MakeEmpty()); + } + return false; + } + + if (crect) { + *crect = ToRect(path.getBounds()); + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +void sk_canvas_save(sk_canvas_t* ccanvas) { + AsCanvas(ccanvas)->save(); +} + +void sk_canvas_save_layer(sk_canvas_t* ccanvas, const sk_rect_t* crect, const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawRect(AsRect(*crect), AsPaint(*cpaint)); +} + +void sk_canvas_restore(sk_canvas_t* ccanvas) { + AsCanvas(ccanvas)->restore(); +} + +void sk_canvas_translate(sk_canvas_t* ccanvas, float dx, float dy) { + AsCanvas(ccanvas)->translate(dx, dy); +} + +void sk_canvas_scale(sk_canvas_t* ccanvas, float sx, float sy) { + AsCanvas(ccanvas)->scale(sx, sy); +} + +void sk_canvas_rotate_degress(sk_canvas_t* ccanvas, float degrees) { + AsCanvas(ccanvas)->rotate(degrees); +} + +void sk_canvas_rotate_radians(sk_canvas_t* ccanvas, float radians) { + AsCanvas(ccanvas)->rotate(SkRadiansToDegrees(radians)); +} + +void sk_canvas_skew(sk_canvas_t* ccanvas, float sx, float sy) { + AsCanvas(ccanvas)->skew(sx, sy); +} + +void sk_canvas_concat(sk_canvas_t* ccanvas, const sk_matrix_t* cmatrix) { + SkASSERT(cmatrix); + SkMatrix matrix; + from_c_matrix(cmatrix, &matrix); + AsCanvas(ccanvas)->concat(matrix); +} + +void sk_canvas_clip_rect(sk_canvas_t* ccanvas, const sk_rect_t* crect) { + AsCanvas(ccanvas)->clipRect(AsRect(*crect)); +} + +void sk_canvas_clip_path(sk_canvas_t* ccanvas, const sk_path_t* cpath) { + AsCanvas(ccanvas)->clipPath(AsPath(*cpath)); +} + +void sk_canvas_draw_paint(sk_canvas_t* ccanvas, const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawPaint(AsPaint(*cpaint)); +} + +void sk_canvas_draw_rect(sk_canvas_t* ccanvas, const sk_rect_t* crect, const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawRect(AsRect(*crect), AsPaint(*cpaint)); +} + +void sk_canvas_draw_circle(sk_canvas_t* ccanvas, float cx, float cy, float rad, + const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawCircle(cx, cy, rad, AsPaint(*cpaint)); +} + +void sk_canvas_draw_oval(sk_canvas_t* ccanvas, const sk_rect_t* crect, const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawOval(AsRect(*crect), AsPaint(*cpaint)); +} + +void sk_canvas_draw_path(sk_canvas_t* ccanvas, const sk_path_t* cpath, const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawPath(AsPath(*cpath), AsPaint(*cpaint)); +} + +void sk_canvas_draw_image(sk_canvas_t* ccanvas, const sk_image_t* cimage, float x, float y, + const sk_paint_t* cpaint) { + AsCanvas(ccanvas)->drawImage(AsImage(cimage), x, y, AsPaint(cpaint)); +} + +void sk_canvas_draw_image_rect(sk_canvas_t* ccanvas, const sk_image_t* cimage, + const sk_rect_t* csrcR, const sk_rect_t* cdstR, + const sk_paint_t* cpaint) { + SkCanvas* canvas = AsCanvas(ccanvas); + const SkImage* image = AsImage(cimage); + const SkRect& dst = AsRect(*cdstR); + const SkPaint* paint = AsPaint(cpaint); + + if (csrcR) { + canvas->drawImageRect(image, AsRect(*csrcR), dst, paint); + } else { + canvas->drawImageRect(image, dst, paint); + } +} + +void sk_canvas_draw_picture(sk_canvas_t* ccanvas, const sk_picture_t* cpicture, + const sk_matrix_t* cmatrix, const sk_paint_t* cpaint) { + const SkMatrix* matrixPtr = NULL; + SkMatrix matrix; + if (cmatrix) { + from_c_matrix(cmatrix, &matrix); + matrixPtr = &matrix; + } + AsCanvas(ccanvas)->drawPicture(AsPicture(cpicture), matrixPtr, AsPaint(cpaint)); +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +sk_surface_t* sk_surface_new_raster(const sk_imageinfo_t* cinfo, + const sk_surfaceprops_t* props) { + const SkImageInfo* info = reinterpret_cast(cinfo); + SkPixelGeometry geo = kUnknown_SkPixelGeometry; + if (props && !from_c_pixelgeometry(props->pixelGeometry, &geo)) { + return NULL; + } + + SkSurfaceProps surfProps(0, geo); + return (sk_surface_t*)SkSurface::MakeRaster(*info, &surfProps).release(); +} + +sk_surface_t* sk_surface_new_raster_direct(const sk_imageinfo_t* cinfo, void* pixels, + size_t rowBytes, + const sk_surfaceprops_t* props) { + const SkImageInfo* info = reinterpret_cast(cinfo); + SkPixelGeometry geo = kUnknown_SkPixelGeometry; + if (props && !from_c_pixelgeometry(props->pixelGeometry, &geo)) { + return NULL; + } + + SkSurfaceProps surfProps(0, geo); + return (sk_surface_t*)SkSurface::MakeRasterDirect(*info, pixels, rowBytes, &surfProps).release(); +} + +void sk_surface_unref(sk_surface_t* csurf) { + SkSafeUnref((SkSurface*)csurf); +} + +sk_canvas_t* sk_surface_get_canvas(sk_surface_t* csurf) { + SkSurface* surf = (SkSurface*)csurf; + return (sk_canvas_t*)surf->getCanvas(); +} + +sk_image_t* sk_surface_new_image_snapshot(sk_surface_t* csurf) { + SkSurface* surf = (SkSurface*)csurf; + return (sk_image_t*)surf->makeImageSnapshot().release(); +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +sk_picture_recorder_t* sk_picture_recorder_new() { + return ToPictureRecorder(new SkPictureRecorder); +} + +void sk_picture_recorder_delete(sk_picture_recorder_t* crec) { + delete AsPictureRecorder(crec); +} + +sk_canvas_t* sk_picture_recorder_begin_recording(sk_picture_recorder_t* crec, + const sk_rect_t* cbounds) { + return ToCanvas(AsPictureRecorder(crec)->beginRecording(AsRect(*cbounds))); +} + +sk_picture_t* sk_picture_recorder_end_recording(sk_picture_recorder_t* crec) { + return ToPicture(AsPictureRecorder(crec)->finishRecordingAsPicture().release()); +} + +void sk_picture_ref(sk_picture_t* cpic) { + SkSafeRef(AsPicture(cpic)); +} + +void sk_picture_unref(sk_picture_t* cpic) { + SkSafeUnref(AsPicture(cpic)); +} + +uint32_t sk_picture_get_unique_id(sk_picture_t* cpic) { + return AsPicture(cpic)->uniqueID(); +} + +sk_rect_t sk_picture_get_bounds(sk_picture_t* cpic) { + return ToRect(AsPicture(cpic)->cullRect()); +} + +/////////////////////////////////////////////////////////////////////////////////////////// + +sk_data_t* sk_data_new_with_copy(const void* src, size_t length) { + return ToData(SkData::MakeWithCopy(src, length).release()); +} + +sk_data_t* sk_data_new_from_malloc(const void* memory, size_t length) { + return ToData(SkData::MakeFromMalloc(memory, length).release()); +} + +sk_data_t* sk_data_new_subset(const sk_data_t* csrc, size_t offset, size_t length) { + return ToData(SkData::MakeSubset(AsData(csrc), offset, length).release()); +} + +void sk_data_ref(const sk_data_t* cdata) { + SkSafeRef(AsData(cdata)); +} + +void sk_data_unref(const sk_data_t* cdata) { + SkSafeUnref(AsData(cdata)); +} + +size_t sk_data_get_size(const sk_data_t* cdata) { + return AsData(cdata)->size(); +} + +const void* sk_data_get_data(const sk_data_t* cdata) { + return AsData(cdata)->data(); +} + +/////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/skia/skia/src/c/sk_types_priv.h b/gfx/skia/skia/src/c/sk_types_priv.h new file mode 100644 index 0000000000..ea62646700 --- /dev/null +++ b/gfx/skia/skia/src/c/sk_types_priv.h @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef sk_types_priv_DEFINED +#define sk_types_priv_DEFINED + +#include "include/c/sk_types.h" + +class SkMaskFilter; +class SkPaint; +class SkShader; + +static inline const SkPaint& AsPaint(const sk_paint_t& cpaint) { + return reinterpret_cast(cpaint); +} + +static inline const SkPaint* AsPaint(const sk_paint_t* cpaint) { + return reinterpret_cast(cpaint); +} + +static inline SkPaint* AsPaint(sk_paint_t* cpaint) { + return reinterpret_cast(cpaint); +} + +static inline SkMaskFilter* AsMaskFilter(sk_maskfilter_t* cfilter) { + return reinterpret_cast(cfilter); +} + +static inline sk_maskfilter_t* ToMaskFilter(SkMaskFilter* filter) { + return reinterpret_cast(filter); +} + +static inline SkShader* AsShader(sk_shader_t* cshader) { + return reinterpret_cast(cshader); +} + +#endif diff --git a/gfx/skia/skia/src/codec/SkAndroidCodec.cpp b/gfx/skia/skia/src/codec/SkAndroidCodec.cpp new file mode 100644 index 0000000000..b90dbe7af0 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkAndroidCodec.cpp @@ -0,0 +1,416 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" +#include "include/core/SkPixmap.h" +#include "src/codec/SkAndroidCodecAdapter.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSampledCodec.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkPixmapPriv.h" + +static bool is_valid_sample_size(int sampleSize) { + // FIXME: As Leon has mentioned elsewhere, surely there is also a maximum sampleSize? + return sampleSize > 0; +} + +/** + * Loads the gamut as a set of three points (triangle). + */ +static void load_gamut(SkPoint rgb[], const skcms_Matrix3x3& xyz) { + // rx = rX / (rX + rY + rZ) + // ry = rY / (rX + rY + rZ) + // gx, gy, bx, and gy are calulcated similarly. + for (int rgbIdx = 0; rgbIdx < 3; rgbIdx++) { + float sum = xyz.vals[rgbIdx][0] + xyz.vals[rgbIdx][1] + xyz.vals[rgbIdx][2]; + rgb[rgbIdx].fX = xyz.vals[rgbIdx][0] / sum; + rgb[rgbIdx].fY = xyz.vals[rgbIdx][1] / sum; + } +} + +/** + * Calculates the area of the triangular gamut. + */ +static float calculate_area(SkPoint abc[]) { + SkPoint a = abc[0]; + SkPoint b = abc[1]; + SkPoint c = abc[2]; + return 0.5f * SkTAbs(a.fX*b.fY + b.fX*c.fY - a.fX*c.fY - c.fX*b.fY - b.fX*a.fY); +} + +static constexpr float kSRGB_D50_GamutArea = 0.084f; + +static bool is_wide_gamut(const skcms_ICCProfile& profile) { + // Determine if the source image has a gamut that is wider than sRGB. If so, we + // will use P3 as the output color space to avoid clipping the gamut. + if (profile.has_toXYZD50) { + SkPoint rgb[3]; + load_gamut(rgb, profile.toXYZD50); + return calculate_area(rgb) > kSRGB_D50_GamutArea; + } + + return false; +} + +static inline SkImageInfo adjust_info(SkCodec* codec, + SkAndroidCodec::ExifOrientationBehavior orientationBehavior) { + auto info = codec->getInfo(); + if (orientationBehavior == SkAndroidCodec::ExifOrientationBehavior::kIgnore + || !SkPixmapPriv::ShouldSwapWidthHeight(codec->getOrigin())) { + return info; + } + return SkPixmapPriv::SwapWidthHeight(info); +} + +SkAndroidCodec::SkAndroidCodec(SkCodec* codec, ExifOrientationBehavior orientationBehavior) + : fInfo(adjust_info(codec, orientationBehavior)) + , fOrientationBehavior(orientationBehavior) + , fCodec(codec) +{} + +SkAndroidCodec::~SkAndroidCodec() {} + +std::unique_ptr SkAndroidCodec::MakeFromStream(std::unique_ptr stream, + SkPngChunkReader* chunkReader) { + auto codec = SkCodec::MakeFromStream(std::move(stream), nullptr, chunkReader); + return MakeFromCodec(std::move(codec)); +} + +std::unique_ptr SkAndroidCodec::MakeFromCodec(std::unique_ptr codec, + ExifOrientationBehavior orientationBehavior) { + if (nullptr == codec) { + return nullptr; + } + + switch ((SkEncodedImageFormat)codec->getEncodedFormat()) { + case SkEncodedImageFormat::kPNG: + case SkEncodedImageFormat::kICO: + case SkEncodedImageFormat::kJPEG: +#ifndef SK_HAS_WUFFS_LIBRARY + case SkEncodedImageFormat::kGIF: +#endif + case SkEncodedImageFormat::kBMP: + case SkEncodedImageFormat::kWBMP: + case SkEncodedImageFormat::kHEIF: + return skstd::make_unique(codec.release(), orientationBehavior); +#ifdef SK_HAS_WUFFS_LIBRARY + case SkEncodedImageFormat::kGIF: +#endif +#ifdef SK_HAS_WEBP_LIBRARY + case SkEncodedImageFormat::kWEBP: +#endif +#ifdef SK_CODEC_DECODES_RAW + case SkEncodedImageFormat::kDNG: +#endif +#if defined(SK_HAS_WEBP_LIBRARY) || defined(SK_CODEC_DECODES_RAW) || defined(SK_HAS_WUFFS_LIBRARY) + return skstd::make_unique(codec.release(), orientationBehavior); +#endif + + default: + return nullptr; + } +} + +std::unique_ptr SkAndroidCodec::MakeFromData(sk_sp data, + SkPngChunkReader* chunkReader) { + if (!data) { + return nullptr; + } + + return MakeFromStream(SkMemoryStream::Make(std::move(data)), chunkReader); +} + +SkColorType SkAndroidCodec::computeOutputColorType(SkColorType requestedColorType) { + bool highPrecision = fCodec->getEncodedInfo().bitsPerComponent() > 8; + switch (requestedColorType) { + case kARGB_4444_SkColorType: + return kN32_SkColorType; + case kN32_SkColorType: + break; + case kAlpha_8_SkColorType: + // Fall through to kGray_8. Before kGray_8_SkColorType existed, + // we allowed clients to request kAlpha_8 when they wanted a + // grayscale decode. + case kGray_8_SkColorType: + if (kGray_8_SkColorType == this->getInfo().colorType()) { + return kGray_8_SkColorType; + } + break; + case kRGB_565_SkColorType: + if (kOpaque_SkAlphaType == this->getInfo().alphaType()) { + return kRGB_565_SkColorType; + } + break; + case kRGBA_F16_SkColorType: + return kRGBA_F16_SkColorType; + default: + break; + } + + // F16 is the Android default for high precision images. + return highPrecision ? kRGBA_F16_SkColorType : kN32_SkColorType; +} + +SkAlphaType SkAndroidCodec::computeOutputAlphaType(bool requestedUnpremul) { + if (kOpaque_SkAlphaType == this->getInfo().alphaType()) { + return kOpaque_SkAlphaType; + } + return requestedUnpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; +} + +sk_sp SkAndroidCodec::computeOutputColorSpace(SkColorType outputColorType, + sk_sp prefColorSpace) { + switch (outputColorType) { + case kRGBA_F16_SkColorType: + case kRGB_565_SkColorType: + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + // If |prefColorSpace| is supplied, choose it. + if (prefColorSpace) { + return prefColorSpace; + } + + const skcms_ICCProfile* encodedProfile = fCodec->getEncodedInfo().profile(); + if (encodedProfile) { + if (auto encodedSpace = SkColorSpace::Make(*encodedProfile)) { + // Leave the pixels in the encoded color space. Color space conversion + // will be handled after decode time. + return encodedSpace; + } + + if (is_wide_gamut(*encodedProfile)) { + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); + } + } + + return SkColorSpace::MakeSRGB(); + } + default: + // Color correction not supported for kGray. + return nullptr; + } +} + +static bool supports_any_down_scale(const SkCodec* codec) { + return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP; +} + +// There are a variety of ways two SkISizes could be compared. This method +// returns true if either dimensions of a is < that of b. +// computeSampleSize also uses the opposite, which means that both +// dimensions of a >= b. +static inline bool smaller_than(const SkISize& a, const SkISize& b) { + return a.width() < b.width() || a.height() < b.height(); +} + +// Both dimensions of a > that of b. +static inline bool strictly_bigger_than(const SkISize& a, const SkISize& b) { + return a.width() > b.width() && a.height() > b.height(); +} + +int SkAndroidCodec::computeSampleSize(SkISize* desiredSize) const { + SkASSERT(desiredSize); + + if (!desiredSize || *desiredSize == fInfo.dimensions()) { + return 1; + } + + if (smaller_than(fInfo.dimensions(), *desiredSize)) { + *desiredSize = fInfo.dimensions(); + return 1; + } + + // Handle bad input: + if (desiredSize->width() < 1 || desiredSize->height() < 1) { + *desiredSize = SkISize::Make(std::max(1, desiredSize->width()), + std::max(1, desiredSize->height())); + } + + if (supports_any_down_scale(fCodec.get())) { + return 1; + } + + int sampleX = fInfo.width() / desiredSize->width(); + int sampleY = fInfo.height() / desiredSize->height(); + int sampleSize = std::min(sampleX, sampleY); + auto computedSize = this->getSampledDimensions(sampleSize); + if (computedSize == *desiredSize) { + return sampleSize; + } + + if (computedSize == fInfo.dimensions() || sampleSize == 1) { + // Cannot downscale + *desiredSize = computedSize; + return 1; + } + + if (strictly_bigger_than(computedSize, *desiredSize)) { + // See if there is a tighter fit. + while (true) { + auto smaller = this->getSampledDimensions(sampleSize + 1); + if (smaller == *desiredSize) { + return sampleSize + 1; + } + if (smaller == computedSize || smaller_than(smaller, *desiredSize)) { + // Cannot get any smaller without being smaller than desired. + *desiredSize = computedSize; + return sampleSize; + } + + sampleSize++; + computedSize = smaller; + } + + SkASSERT(false); + } + + if (!smaller_than(computedSize, *desiredSize)) { + // This means one of the computed dimensions is equal to desired, and + // the other is bigger. This is as close as we can get. + *desiredSize = computedSize; + return sampleSize; + } + + // computedSize is too small. Make it larger. + while (sampleSize > 2) { + auto bigger = this->getSampledDimensions(sampleSize - 1); + if (bigger == *desiredSize || !smaller_than(bigger, *desiredSize)) { + *desiredSize = bigger; + return sampleSize - 1; + } + sampleSize--; + } + + *desiredSize = fInfo.dimensions(); + return 1; +} + +SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const { + if (!is_valid_sample_size(sampleSize)) { + return {0, 0}; + } + + // Fast path for when we are not scaling. + if (1 == sampleSize) { + return fInfo.dimensions(); + } + + auto dims = this->onGetSampledDimensions(sampleSize); + if (fOrientationBehavior == SkAndroidCodec::ExifOrientationBehavior::kIgnore + || !SkPixmapPriv::ShouldSwapWidthHeight(fCodec->getOrigin())) { + return dims; + } + + return { dims.height(), dims.width() }; +} + +bool SkAndroidCodec::getSupportedSubset(SkIRect* desiredSubset) const { + if (!desiredSubset || !is_valid_subset(*desiredSubset, fInfo.dimensions())) { + return false; + } + + return this->onGetSupportedSubset(desiredSubset); +} + +SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const { + if (!is_valid_sample_size(sampleSize)) { + return {0, 0}; + } + + // We require that the input subset is a subset that is supported by SkAndroidCodec. + // We test this by calling getSupportedSubset() and verifying that no modifications + // are made to the subset. + SkIRect copySubset = subset; + if (!this->getSupportedSubset(©Subset) || copySubset != subset) { + return {0, 0}; + } + + // If the subset is the entire image, for consistency, use getSampledDimensions(). + if (fInfo.dimensions() == subset.size()) { + return this->getSampledDimensions(sampleSize); + } + + // This should perhaps call a virtual function, but currently both of our subclasses + // want the same implementation. + return {get_scaled_dimension(subset.width(), sampleSize), + get_scaled_dimension(subset.height(), sampleSize)}; +} + +static bool acceptable_result(SkCodec::Result result) { + switch (result) { + // These results mean a partial or complete image. They should be considered + // a success by SkPixmapPriv. + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + return false; + } +} + +SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& requestInfo, + void* requestPixels, size_t requestRowBytes, const AndroidOptions* options) { + if (!requestPixels) { + return SkCodec::kInvalidParameters; + } + if (requestRowBytes < requestInfo.minRowBytes()) { + return SkCodec::kInvalidParameters; + } + + SkImageInfo adjustedInfo = fInfo; + if (ExifOrientationBehavior::kRespect == fOrientationBehavior + && SkPixmapPriv::ShouldSwapWidthHeight(fCodec->getOrigin())) { + adjustedInfo = SkPixmapPriv::SwapWidthHeight(adjustedInfo); + } + + AndroidOptions defaultOptions; + if (!options) { + options = &defaultOptions; + } else if (options->fSubset) { + if (!is_valid_subset(*options->fSubset, adjustedInfo.dimensions())) { + return SkCodec::kInvalidParameters; + } + + if (SkIRect::MakeSize(adjustedInfo.dimensions()) == *options->fSubset) { + // The caller wants the whole thing, rather than a subset. Modify + // the AndroidOptions passed to onGetAndroidPixels to not specify + // a subset. + defaultOptions = *options; + defaultOptions.fSubset = nullptr; + options = &defaultOptions; + } + } + + if (ExifOrientationBehavior::kIgnore == fOrientationBehavior) { + return this->onGetAndroidPixels(requestInfo, requestPixels, requestRowBytes, *options); + } + + SkCodec::Result result; + auto decode = [this, options, &result](const SkPixmap& pm) { + result = this->onGetAndroidPixels(pm.info(), pm.writable_addr(), pm.rowBytes(), *options); + return acceptable_result(result); + }; + + SkPixmap dst(requestInfo, requestPixels, requestRowBytes); + if (SkPixmapPriv::Orient(dst, fCodec->getOrigin(), decode)) { + return result; + } + + // Orient returned false. If onGetAndroidPixels succeeded, then Orient failed internally. + if (acceptable_result(result)) { + return SkCodec::kInternalError; + } + + return result; +} + +SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes) { + return this->getAndroidPixels(info, pixels, rowBytes, nullptr); +} diff --git a/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp new file mode 100644 index 0000000000..5551136fa6 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkAndroidCodecAdapter.h" +#include "src/codec/SkCodecPriv.h" + +SkAndroidCodecAdapter::SkAndroidCodecAdapter(SkCodec* codec, ExifOrientationBehavior behavior) + : INHERITED(codec, behavior) +{} + +SkISize SkAndroidCodecAdapter::onGetSampledDimensions(int sampleSize) const { + float scale = get_scale_from_sample_size(sampleSize); + return this->codec()->getScaledDimensions(scale); +} + +bool SkAndroidCodecAdapter::onGetSupportedSubset(SkIRect* desiredSubset) const { + return this->codec()->getValidSubset(desiredSubset); +} + +SkCodec::Result SkAndroidCodecAdapter::onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const AndroidOptions& options) { + SkCodec::Options codecOptions; + codecOptions.fZeroInitialized = options.fZeroInitialized; + codecOptions.fSubset = options.fSubset; + return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions); +} diff --git a/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h new file mode 100644 index 0000000000..7a5d093d5b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkAndroidCodecAdapter_DEFINED +#define SkAndroidCodecAdapter_DEFINED + +#include "include/codec/SkAndroidCodec.h" + +/** + * This class wraps SkCodec to implement the functionality of SkAndroidCodec. + * The underlying SkCodec implements sampled decodes. SkCodec's that do not + * implement that are wrapped with SkSampledCodec instead. + */ +class SkAndroidCodecAdapter : public SkAndroidCodec { +public: + + explicit SkAndroidCodecAdapter(SkCodec*, ExifOrientationBehavior); + + ~SkAndroidCodecAdapter() override {} + +protected: + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override; + + SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions& options) override; + +private: + + typedef SkAndroidCodec INHERITED; +}; +#endif // SkAndroidCodecAdapter_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp b/gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp new file mode 100644 index 0000000000..32a1d37384 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/private/SkMalloc.h" +#include "src/codec/SkBmpBaseCodec.h" + +SkBmpBaseCodec::~SkBmpBaseCodec() {} + +SkBmpBaseCodec::SkBmpBaseCodec(SkEncodedInfo&& info, std::unique_ptr stream, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fSrcBuffer(sk_malloc_canfail(this->srcRowBytes())) +{} diff --git a/gfx/skia/skia/src/codec/SkBmpBaseCodec.h b/gfx/skia/skia/src/codec/SkBmpBaseCodec.h new file mode 100644 index 0000000000..1c57ce6de4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpBaseCodec.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpBaseCodec_DEFINED +#define SkBmpBaseCodec_DEFINED + +#include "include/private/SkTemplates.h" +#include "src/codec/SkBmpCodec.h" + +/* + * Common base class for SkBmpStandardCodec and SkBmpMaskCodec. + */ +class SkBmpBaseCodec : public SkBmpCodec { +public: + ~SkBmpBaseCodec() override; + + /* + * Whether fSrcBuffer was successfully created. + * + * If false, this Codec must not be used. + */ + bool didCreateSrcBuffer() const { return fSrcBuffer != nullptr; } + +protected: + SkBmpBaseCodec(SkEncodedInfo&& info, std::unique_ptr, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder); + + uint8_t* srcBuffer() { return reinterpret_cast(fSrcBuffer.get()); } + +private: + SkAutoFree fSrcBuffer; + + typedef SkBmpCodec INHERITED; +}; +#endif // SkBmpBaseCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpCodec.cpp b/gfx/skia/skia/src/codec/SkBmpCodec.cpp new file mode 100644 index 0000000000..615c8a4212 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpCodec.cpp @@ -0,0 +1,650 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkBmpMaskCodec.h" +#include "src/codec/SkBmpRLECodec.h" +#include "src/codec/SkBmpStandardCodec.h" +#include "src/codec/SkCodecPriv.h" + +/* + * Defines the version and type of the second bitmap header + */ +enum BmpHeaderType { + kInfoV1_BmpHeaderType, + kInfoV2_BmpHeaderType, + kInfoV3_BmpHeaderType, + kInfoV4_BmpHeaderType, + kInfoV5_BmpHeaderType, + kOS2V1_BmpHeaderType, + kOS2VX_BmpHeaderType, + kUnknown_BmpHeaderType +}; + +/* + * Possible bitmap compression types + */ +enum BmpCompressionMethod { + kNone_BmpCompressionMethod = 0, + k8BitRLE_BmpCompressionMethod = 1, + k4BitRLE_BmpCompressionMethod = 2, + kBitMasks_BmpCompressionMethod = 3, + kJpeg_BmpCompressionMethod = 4, + kPng_BmpCompressionMethod = 5, + kAlphaBitMasks_BmpCompressionMethod = 6, + kCMYK_BmpCompressionMethod = 11, + kCMYK8BitRLE_BmpCompressionMethod = 12, + kCMYK4BitRLE_BmpCompressionMethod = 13 +}; + +/* + * Used to define the input format of the bmp + */ +enum BmpInputFormat { + kStandard_BmpInputFormat, + kRLE_BmpInputFormat, + kBitMask_BmpInputFormat, + kUnknown_BmpInputFormat +}; + +/* + * Checks the start of the stream to see if the image is a bitmap + */ +bool SkBmpCodec::IsBmp(const void* buffer, size_t bytesRead) { + // TODO: Support "IC", "PT", "CI", "CP", "BA" + const char bmpSig[] = { 'B', 'M' }; + return bytesRead >= sizeof(bmpSig) && !memcmp(buffer, bmpSig, sizeof(bmpSig)); +} + +/* + * Assumes IsBmp was called and returned true + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ +std::unique_ptr SkBmpCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + return SkBmpCodec::MakeFromStream(std::move(stream), result, false); +} + +/* + * Creates a bmp decoder for a bmp embedded in ico + * Reads enough of the stream to determine the image format + */ +std::unique_ptr SkBmpCodec::MakeFromIco(std::unique_ptr stream, Result* result) { + return SkBmpCodec::MakeFromStream(std::move(stream), result, true); +} + +// Header size constants +static constexpr uint32_t kBmpHeaderBytes = 14; +static constexpr uint32_t kBmpHeaderBytesPlusFour = kBmpHeaderBytes + 4; +static constexpr uint32_t kBmpOS2V1Bytes = 12; +static constexpr uint32_t kBmpOS2V2Bytes = 64; +static constexpr uint32_t kBmpInfoBaseBytes = 16; +static constexpr uint32_t kBmpInfoV1Bytes = 40; +static constexpr uint32_t kBmpInfoV2Bytes = 52; +static constexpr uint32_t kBmpInfoV3Bytes = 56; +static constexpr uint32_t kBmpInfoV4Bytes = 108; +static constexpr uint32_t kBmpInfoV5Bytes = 124; +static constexpr uint32_t kBmpMaskBytes = 12; + +static BmpHeaderType get_header_type(size_t infoBytes) { + if (infoBytes >= kBmpInfoBaseBytes) { + // Check the version of the header + switch (infoBytes) { + case kBmpInfoV1Bytes: + return kInfoV1_BmpHeaderType; + case kBmpInfoV2Bytes: + return kInfoV2_BmpHeaderType; + case kBmpInfoV3Bytes: + return kInfoV3_BmpHeaderType; + case kBmpInfoV4Bytes: + return kInfoV4_BmpHeaderType; + case kBmpInfoV5Bytes: + return kInfoV5_BmpHeaderType; + case 16: + case 20: + case 24: + case 28: + case 32: + case 36: + case 42: + case 46: + case 48: + case 60: + case kBmpOS2V2Bytes: + return kOS2VX_BmpHeaderType; + default: + SkCodecPrintf("Error: unknown bmp header format.\n"); + return kUnknown_BmpHeaderType; + } + } if (infoBytes >= kBmpOS2V1Bytes) { + // The OS2V1 is treated separately because it has a unique format + return kOS2V1_BmpHeaderType; + } else { + // There are no valid bmp headers + SkCodecPrintf("Error: second bitmap header size is invalid.\n"); + return kUnknown_BmpHeaderType; + } +} + +SkCodec::Result SkBmpCodec::ReadHeader(SkStream* stream, bool inIco, + std::unique_ptr* codecOut) { + // The total bytes in the bmp file + // We only need to use this value for RLE decoding, so we will only + // check that it is valid in the RLE case. + uint32_t totalBytes; + // The offset from the start of the file where the pixel data begins + uint32_t offset; + // The size of the second (info) header in bytes + uint32_t infoBytes; + + // Bmps embedded in Icos skip the first Bmp header + if (!inIco) { + // Read the first header and the size of the second header + uint8_t hBuffer[kBmpHeaderBytesPlusFour]; + if (stream->read(hBuffer, kBmpHeaderBytesPlusFour) != + kBmpHeaderBytesPlusFour) { + SkCodecPrintf("Error: unable to read first bitmap header.\n"); + return kIncompleteInput; + } + + totalBytes = get_int(hBuffer, 2); + offset = get_int(hBuffer, 10); + if (offset < kBmpHeaderBytes + kBmpOS2V1Bytes) { + SkCodecPrintf("Error: invalid starting location for pixel data\n"); + return kInvalidInput; + } + + // The size of the second (info) header in bytes + // The size is the first field of the second header, so we have already + // read the first four infoBytes. + infoBytes = get_int(hBuffer, 14); + if (infoBytes < kBmpOS2V1Bytes) { + SkCodecPrintf("Error: invalid second header size.\n"); + return kInvalidInput; + } + } else { + // This value is only used by RLE compression. Bmp in Ico files do not + // use RLE. If the compression field is incorrectly signaled as RLE, + // we will catch this and signal an error below. + totalBytes = 0; + + // Bmps in Ico cannot specify an offset. We will always assume that + // pixel data begins immediately after the color table. This value + // will be corrected below. + offset = 0; + + // Read the size of the second header + uint8_t hBuffer[4]; + if (stream->read(hBuffer, 4) != 4) { + SkCodecPrintf("Error: unable to read size of second bitmap header.\n"); + return kIncompleteInput; + } + infoBytes = get_int(hBuffer, 0); + if (infoBytes < kBmpOS2V1Bytes) { + SkCodecPrintf("Error: invalid second header size.\n"); + return kInvalidInput; + } + } + + // Determine image information depending on second header format + const BmpHeaderType headerType = get_header_type(infoBytes); + if (kUnknown_BmpHeaderType == headerType) { + return kInvalidInput; + } + + // We already read the first four bytes of the info header to get the size + const uint32_t infoBytesRemaining = infoBytes - 4; + + // Read the second header + std::unique_ptr iBuffer(new uint8_t[infoBytesRemaining]); + if (stream->read(iBuffer.get(), infoBytesRemaining) != infoBytesRemaining) { + SkCodecPrintf("Error: unable to read second bitmap header.\n"); + return kIncompleteInput; + } + + // The number of bits used per pixel in the pixel data + uint16_t bitsPerPixel; + + // The compression method for the pixel data + uint32_t compression = kNone_BmpCompressionMethod; + + // Number of colors in the color table, defaults to 0 or max (see below) + uint32_t numColors = 0; + + // Bytes per color in the color table, early versions use 3, most use 4 + uint32_t bytesPerColor; + + // The image width and height + int width, height; + + switch (headerType) { + case kInfoV1_BmpHeaderType: + case kInfoV2_BmpHeaderType: + case kInfoV3_BmpHeaderType: + case kInfoV4_BmpHeaderType: + case kInfoV5_BmpHeaderType: + case kOS2VX_BmpHeaderType: + // We check the size of the header before entering the if statement. + // We should not reach this point unless the size is large enough for + // these required fields. + SkASSERT(infoBytesRemaining >= 12); + width = get_int(iBuffer.get(), 0); + height = get_int(iBuffer.get(), 4); + bitsPerPixel = get_short(iBuffer.get(), 10); + + // Some versions do not have these fields, so we check before + // overwriting the default value. + if (infoBytesRemaining >= 16) { + compression = get_int(iBuffer.get(), 12); + if (infoBytesRemaining >= 32) { + numColors = get_int(iBuffer.get(), 28); + } + } + + // All of the headers that reach this point, store color table entries + // using 4 bytes per pixel. + bytesPerColor = 4; + break; + case kOS2V1_BmpHeaderType: + // The OS2V1 is treated separately because it has a unique format + width = (int) get_short(iBuffer.get(), 0); + height = (int) get_short(iBuffer.get(), 2); + bitsPerPixel = get_short(iBuffer.get(), 6); + bytesPerColor = 3; + break; + case kUnknown_BmpHeaderType: + // We'll exit above in this case. + SkASSERT(false); + return kInvalidInput; + } + + // Check for valid dimensions from header + SkCodec::SkScanlineOrder rowOrder = SkCodec::kBottomUp_SkScanlineOrder; + if (height < 0) { + // We can't negate INT32_MIN. + if (height == INT32_MIN) { + return kInvalidInput; + } + + height = -height; + rowOrder = SkCodec::kTopDown_SkScanlineOrder; + } + // The height field for bmp in ico is double the actual height because they + // contain an XOR mask followed by an AND mask + if (inIco) { + height /= 2; + } + + // Arbitrary maximum. Matches Chromium. + constexpr int kMaxDim = 1 << 16; + if (width <= 0 || height <= 0 || width >= kMaxDim || height >= kMaxDim) { + SkCodecPrintf("Error: invalid bitmap dimensions.\n"); + return kInvalidInput; + } + + // Create mask struct + SkMasks::InputMasks inputMasks; + memset(&inputMasks, 0, sizeof(SkMasks::InputMasks)); + + // Determine the input compression format and set bit masks if necessary + uint32_t maskBytes = 0; + BmpInputFormat inputFormat = kUnknown_BmpInputFormat; + switch (compression) { + case kNone_BmpCompressionMethod: + inputFormat = kStandard_BmpInputFormat; + + // In addition to more standard pixel compression formats, bmp supports + // the use of bit masks to determine pixel components. The standard + // format for representing 16-bit colors is 555 (XRRRRRGGGGGBBBBB), + // which does not map well to any Skia color formats. For this reason, + // we will always enable mask mode with 16 bits per pixel. + if (16 == bitsPerPixel) { + inputMasks.red = 0x7C00; + inputMasks.green = 0x03E0; + inputMasks.blue = 0x001F; + inputFormat = kBitMask_BmpInputFormat; + } + break; + case k8BitRLE_BmpCompressionMethod: + if (bitsPerPixel != 8) { + SkCodecPrintf("Warning: correcting invalid bitmap format.\n"); + bitsPerPixel = 8; + } + inputFormat = kRLE_BmpInputFormat; + break; + case k4BitRLE_BmpCompressionMethod: + if (bitsPerPixel != 4) { + SkCodecPrintf("Warning: correcting invalid bitmap format.\n"); + bitsPerPixel = 4; + } + inputFormat = kRLE_BmpInputFormat; + break; + case kAlphaBitMasks_BmpCompressionMethod: + case kBitMasks_BmpCompressionMethod: + // Load the masks + inputFormat = kBitMask_BmpInputFormat; + switch (headerType) { + case kInfoV1_BmpHeaderType: { + // The V1 header stores the bit masks after the header + uint8_t buffer[kBmpMaskBytes]; + if (stream->read(buffer, kBmpMaskBytes) != kBmpMaskBytes) { + SkCodecPrintf("Error: unable to read bit inputMasks.\n"); + return kIncompleteInput; + } + maskBytes = kBmpMaskBytes; + inputMasks.red = get_int(buffer, 0); + inputMasks.green = get_int(buffer, 4); + inputMasks.blue = get_int(buffer, 8); + break; + } + case kInfoV2_BmpHeaderType: + case kInfoV3_BmpHeaderType: + case kInfoV4_BmpHeaderType: + case kInfoV5_BmpHeaderType: + // Header types are matched based on size. If the header + // is V2+, we are guaranteed to be able to read at least + // this size. + SkASSERT(infoBytesRemaining >= 48); + inputMasks.red = get_int(iBuffer.get(), 36); + inputMasks.green = get_int(iBuffer.get(), 40); + inputMasks.blue = get_int(iBuffer.get(), 44); + + if (kInfoV2_BmpHeaderType == headerType || + (kInfoV3_BmpHeaderType == headerType && !inIco)) { + break; + } + + // V3+ bmp files introduce an alpha mask and allow the creator of the image + // to use the alpha channels. However, many of these images leave the + // alpha channel blank and expect to be rendered as opaque. This is the + // case for almost all V3 images, so we ignore the alpha mask. For V4+ + // images in kMask mode, we will use the alpha mask. Additionally, V3 + // bmp-in-ico expect us to use the alpha mask. + // + // skbug.com/4116: We should perhaps also apply the alpha mask in kStandard + // mode. We just haven't seen any images that expect this + // behavior. + // + // Header types are matched based on size. If the header is + // V3+, we are guaranteed to be able to read at least this size. + SkASSERT(infoBytesRemaining >= 52); + inputMasks.alpha = get_int(iBuffer.get(), 48); + break; + case kOS2VX_BmpHeaderType: + // TODO: Decide if we intend to support this. + // It is unsupported in the previous version and + // in chromium. I have not come across a test case + // that uses this format. + SkCodecPrintf("Error: huffman format unsupported.\n"); + return kUnimplemented; + default: + SkCodecPrintf("Error: invalid bmp bit masks header.\n"); + return kInvalidInput; + } + break; + case kJpeg_BmpCompressionMethod: + if (24 == bitsPerPixel) { + inputFormat = kRLE_BmpInputFormat; + break; + } + // Fall through + case kPng_BmpCompressionMethod: + // TODO: Decide if we intend to support this. + // It is unsupported in the previous version and + // in chromium. I think it is used mostly for printers. + SkCodecPrintf("Error: compression format not supported.\n"); + return kUnimplemented; + case kCMYK_BmpCompressionMethod: + case kCMYK8BitRLE_BmpCompressionMethod: + case kCMYK4BitRLE_BmpCompressionMethod: + // TODO: Same as above. + SkCodecPrintf("Error: CMYK not supported for bitmap decoding.\n"); + return kUnimplemented; + default: + SkCodecPrintf("Error: invalid format for bitmap decoding.\n"); + return kInvalidInput; + } + iBuffer.reset(); + + // Calculate the number of bytes read so far + const uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes; + if (!inIco && offset < bytesRead) { + // TODO (msarett): Do we really want to fail if the offset in the header is invalid? + // Seems like we can just assume that the offset is zero and try to decode? + // Maybe we don't want to try to decode corrupt images? + SkCodecPrintf("Error: pixel data offset less than header size.\n"); + return kInvalidInput; + } + + + + switch (inputFormat) { + case kStandard_BmpInputFormat: { + // BMPs are generally opaque, however BMPs-in-ICOs may contain + // a transparency mask after the image. Therefore, we mark the + // alpha as kBinary if the BMP is contained in an ICO. + // We use |isOpaque| to indicate if the BMP itself is opaque. + SkEncodedInfo::Alpha alpha = inIco ? SkEncodedInfo::kBinary_Alpha : + SkEncodedInfo::kOpaque_Alpha; + bool isOpaque = true; + + SkEncodedInfo::Color color; + uint8_t bitsPerComponent; + switch (bitsPerPixel) { + // Palette formats + case 1: + case 2: + case 4: + case 8: + // In the case of ICO, kBGRA is actually the closest match, + // since we will need to apply a transparency mask. + if (inIco) { + color = SkEncodedInfo::kBGRA_Color; + bitsPerComponent = 8; + } else { + color = SkEncodedInfo::kPalette_Color; + bitsPerComponent = (uint8_t) bitsPerPixel; + } + break; + case 24: + // In the case of ICO, kBGRA is actually the closest match, + // since we will need to apply a transparency mask. + color = inIco ? SkEncodedInfo::kBGRA_Color : SkEncodedInfo::kBGR_Color; + bitsPerComponent = 8; + break; + case 32: + // 32-bit BMP-in-ICOs actually use the alpha channel in place of a + // transparency mask. + if (inIco) { + isOpaque = false; + alpha = SkEncodedInfo::kUnpremul_Alpha; + color = SkEncodedInfo::kBGRA_Color; + } else { + color = SkEncodedInfo::kBGRX_Color; + } + bitsPerComponent = 8; + break; + default: + SkCodecPrintf("Error: invalid input value for bits per pixel.\n"); + return kInvalidInput; + } + + if (codecOut) { + // We require streams to have a memory base for Bmp-in-Ico decodes. + SkASSERT(!inIco || nullptr != stream->getMemoryBase()); + + // Set the image info and create a codec. + auto info = SkEncodedInfo::Make(width, height, color, alpha, bitsPerComponent); + codecOut->reset(new SkBmpStandardCodec(std::move(info), + std::unique_ptr(stream), + bitsPerPixel, numColors, bytesPerColor, + offset - bytesRead, rowOrder, isOpaque, + inIco)); + return static_cast(codecOut->get())->didCreateSrcBuffer() + ? kSuccess : kInvalidInput; + } + return kSuccess; + } + + case kBitMask_BmpInputFormat: { + // Bmp-in-Ico must be standard mode + if (inIco) { + SkCodecPrintf("Error: Icos may not use bit mask format.\n"); + return kInvalidInput; + } + + switch (bitsPerPixel) { + case 16: + case 24: + case 32: + break; + default: + SkCodecPrintf("Error: invalid input value for bits per pixel.\n"); + return kInvalidInput; + } + + // Skip to the start of the pixel array. + // We can do this here because there is no color table to read + // in bit mask mode. + if (stream->skip(offset - bytesRead) != offset - bytesRead) { + SkCodecPrintf("Error: unable to skip to image data.\n"); + return kIncompleteInput; + } + + if (codecOut) { + // Check that input bit masks are valid and create the masks object + SkASSERT(bitsPerPixel % 8 == 0); + std::unique_ptr masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel/8)); + if (nullptr == masks) { + SkCodecPrintf("Error: invalid input masks.\n"); + return kInvalidInput; + } + + // Masked bmps are not a great fit for SkEncodedInfo, since they have + // arbitrary component orderings and bits per component. Here we choose + // somewhat reasonable values - it's ok that we don't match exactly + // because SkBmpMaskCodec has its own mask swizzler anyway. + SkEncodedInfo::Color color; + SkEncodedInfo::Alpha alpha; + if (masks->getAlphaMask()) { + color = SkEncodedInfo::kBGRA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } else { + color = SkEncodedInfo::kBGR_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + auto info = SkEncodedInfo::Make(width, height, color, alpha, 8); + codecOut->reset(new SkBmpMaskCodec(std::move(info), + std::unique_ptr(stream), bitsPerPixel, + masks.release(), rowOrder)); + return static_cast(codecOut->get())->didCreateSrcBuffer() + ? kSuccess : kInvalidInput; + } + return kSuccess; + } + + case kRLE_BmpInputFormat: { + // We should not reach this point without a valid value of bitsPerPixel. + SkASSERT(4 == bitsPerPixel || 8 == bitsPerPixel || 24 == bitsPerPixel); + + // Check for a valid number of total bytes when in RLE mode + if (totalBytes <= offset) { + SkCodecPrintf("Error: RLE requires valid input size.\n"); + return kInvalidInput; + } + + // Bmp-in-Ico must be standard mode + // When inIco is true, this line cannot be reached, since we + // require that RLE Bmps have a valid number of totalBytes, and + // Icos skip the header that contains totalBytes. + SkASSERT(!inIco); + + if (codecOut) { + // RLE inputs may skip pixels, leaving them as transparent. This + // is uncommon, but we cannot be certain that an RLE bmp will be + // opaque or that we will be able to represent it with a palette. + // For that reason, we always indicate that we are kBGRA. + auto info = SkEncodedInfo::Make(width, height, SkEncodedInfo::kBGRA_Color, + SkEncodedInfo::kBinary_Alpha, 8); + codecOut->reset(new SkBmpRLECodec(std::move(info), + std::unique_ptr(stream), bitsPerPixel, + numColors, bytesPerColor, offset - bytesRead, + rowOrder)); + } + return kSuccess; + } + default: + SkASSERT(false); + return kInvalidInput; + } +} + +/* + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ +std::unique_ptr SkBmpCodec::MakeFromStream(std::unique_ptr stream, + Result* result, bool inIco) { + std::unique_ptr codec; + *result = ReadHeader(stream.get(), inIco, &codec); + if (codec) { + // codec has taken ownership of stream, so we do not need to delete it. + stream.release(); + } + return kSuccess == *result ? std::move(codec) : nullptr; +} + +SkBmpCodec::SkBmpCodec(SkEncodedInfo&& info, std::unique_ptr stream, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), kXformSrcColorFormat, std::move(stream)) + , fBitsPerPixel(bitsPerPixel) + , fRowOrder(rowOrder) + , fSrcRowBytes(SkAlign4(compute_row_bytes(this->dimensions().width(), fBitsPerPixel))) + , fXformBuffer(nullptr) +{} + +bool SkBmpCodec::onRewind() { + return SkBmpCodec::ReadHeader(this->stream(), this->inIco(), nullptr) == kSuccess; +} + +int32_t SkBmpCodec::getDstRow(int32_t y, int32_t height) const { + if (SkCodec::kTopDown_SkScanlineOrder == fRowOrder) { + return y; + } + SkASSERT(SkCodec::kBottomUp_SkScanlineOrder == fRowOrder); + return height - y - 1; +} + +SkCodec::Result SkBmpCodec::prepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + return this->onPrepareToDecode(dstInfo, options); +} + +SkCodec::Result SkBmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + return prepareToDecode(dstInfo, options); +} + +int SkBmpCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { + // Create a new image info representing the portion of the image to decode + SkImageInfo rowInfo = this->dstInfo().makeWH(this->dstInfo().width(), count); + + // Decode the requested rows + return this->decodeRows(rowInfo, dst, rowBytes, this->options()); +} + +bool SkBmpCodec::skipRows(int count) { + const size_t bytesToSkip = count * fSrcRowBytes; + return this->stream()->skip(bytesToSkip) == bytesToSkip; +} + +bool SkBmpCodec::onSkipScanlines(int count) { + return this->skipRows(count); +} diff --git a/gfx/skia/skia/src/codec/SkBmpCodec.h b/gfx/skia/skia/src/codec/SkBmpCodec.h new file mode 100644 index 0000000000..97a15b6d23 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpCodec.h @@ -0,0 +1,151 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpCodec_DEFINED +#define SkBmpCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +/* + * This class enables code sharing between its bmp codec subclasses. The + * subclasses actually do the work. + */ +class SkBmpCodec : public SkCodec { +public: + static bool IsBmp(const void*, size_t); + + /* + * Assumes IsBmp was called and returned true + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + + /* + * Creates a bmp decoder for a bmp embedded in ico + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr MakeFromIco(std::unique_ptr, Result*); + +protected: + + SkBmpCodec(SkEncodedInfo&& info, std::unique_ptr, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder); + + SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kBMP; } + + /* + * Read enough of the stream to initialize the SkBmpCodec. + * On kSuccess, if codecOut is not nullptr, it will be set to a new SkBmpCodec. + */ + static Result ReadHeader(SkStream*, bool inIco, std::unique_ptr* codecOut); + + bool onRewind() override; + + /* + * Returns whether this BMP is part of an ICO image. + */ + bool inIco() const { + return this->onInIco(); + } + + virtual bool onInIco() const { + return false; + } + + /* + * Get the destination row number corresponding to the encoded row number. + * For kTopDown, we simply return y, but for kBottomUp, the rows will be + * decoded in reverse order. + * + * @param y Iterates from 0 to height, indicating the current row. + * @param height The height of the current subset of the image that we are + * decoding. This is generally equal to the full height + * when we want to decode the full or one when we are + * sampling. + */ + int32_t getDstRow(int32_t y, int32_t height) const; + + /* + * Accessors used by subclasses + */ + uint16_t bitsPerPixel() const { return fBitsPerPixel; } + SkScanlineOrder onGetScanlineOrder() const override { return fRowOrder; } + size_t srcRowBytes() const { return fSrcRowBytes; } + + /* + * To be overriden by bmp subclasses, which provide unique implementations. + * Performs subclass specific setup. + * + * @param dstInfo Contains output information. Height specifies + * the total number of rows that will be decoded. + * @param options Additonal options to pass to the decoder. + */ + virtual SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) = 0; + SkCodec::Result prepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options); + + uint32_t* xformBuffer() const { return fXformBuffer.get(); } + void resetXformBuffer(int count) { fXformBuffer.reset(new uint32_t[count]); } + + /* + * BMPs are typically encoded as BGRA/BGR so this is a more efficient choice + * than RGBA. + */ + static constexpr SkColorType kXformSrcColorType = kBGRA_8888_SkColorType; + static constexpr auto kXformSrcColorFormat = skcms_PixelFormat_BGRA_8888; + +private: + + /* + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*, bool inIco); + + /* + * Decodes the next dstInfo.height() lines. + * + * onGetPixels() uses this for full image decodes. + * SkScaledCodec::onGetPixels() uses the scanline decoder to call this with + * dstInfo.height() = 1, in order to implement sampling. + * A potential future use is to allow the caller to decode a subset of the + * lines in the image. + * + * @param dstInfo Contains output information. Height specifies the + * number of rows to decode at this time. + * @param dst Memory location to store output pixels + * @param dstRowBytes Bytes in a row of the destination + * @return Number of rows successfully decoded + */ + virtual int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) = 0; + + virtual bool skipRows(int count); + + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options&) override; + + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + + bool onSkipScanlines(int count) override; + + const uint16_t fBitsPerPixel; + const SkScanlineOrder fRowOrder; + const size_t fSrcRowBytes; + std::unique_ptr fXformBuffer; + + typedef SkCodec INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp b/gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp new file mode 100644 index 0000000000..874056a08d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpMaskCodec.h" +#include "src/codec/SkCodecPriv.h" + +/* + * Creates an instance of the decoder + */ +SkBmpMaskCodec::SkBmpMaskCodec(SkEncodedInfo&& info, + std::unique_ptr stream, + uint16_t bitsPerPixel, SkMasks* masks, + SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fMasks(masks) + , fMaskSwizzler(nullptr) +{} + +/* + * Initiates the bitmap decode + */ +SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + if (opts.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + if (dstInfo.dimensions() != this->dimensions()) { + SkCodecPrintf("Error: scaling not supported.\n"); + return kInvalidScale; + } + + Result result = this->prepareToDecode(dstInfo, opts); + if (kSuccess != result) { + return result; + } + + int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + *rowsDecoded = rows; + return kIncompleteInput; + } + return kSuccess; +} + +SkCodec::Result SkBmpMaskCodec::onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + if (this->colorXform()) { + this->resetXformBuffer(dstInfo.width()); + } + + SkImageInfo swizzlerInfo = dstInfo; + if (this->colorXform()) { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + } + + bool srcIsOpaque = this->getEncodedInfo().opaque(); + fMaskSwizzler.reset(SkMaskSwizzler::CreateMaskSwizzler(swizzlerInfo, srcIsOpaque, + fMasks.get(), this->bitsPerPixel(), options)); + SkASSERT(fMaskSwizzler); + + return SkCodec::kSuccess; +} + +/* + * Performs the decoding + */ +int SkBmpMaskCodec::decodeRows(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts) { + // Iterate over rows of the image + uint8_t* srcRow = this->srcBuffer(); + const int height = dstInfo.height(); + for (int y = 0; y < height; y++) { + // Read a row of the input + if (this->stream()->read(srcRow, this->srcRowBytes()) != this->srcRowBytes()) { + SkCodecPrintf("Warning: incomplete input stream.\n"); + return y; + } + + // Decode the row in destination format + uint32_t row = this->getDstRow(y, height); + void* dstRow = SkTAddOffset(dst, row * dstRowBytes); + + if (this->colorXform()) { + fMaskSwizzler->swizzle(this->xformBuffer(), srcRow); + this->applyColorXform(dstRow, this->xformBuffer(), fMaskSwizzler->swizzleWidth()); + } else { + fMaskSwizzler->swizzle(dstRow, srcRow); + } + } + + // Finished decoding the entire image + return height; +} diff --git a/gfx/skia/skia/src/codec/SkBmpMaskCodec.h b/gfx/skia/skia/src/codec/SkBmpMaskCodec.h new file mode 100644 index 0000000000..eaef50d20b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpMaskCodec.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBmpMaskCodec_DEFINED +#define SkBmpMaskCodec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkBmpBaseCodec.h" +#include "src/codec/SkMaskSwizzler.h" + +/* + * This class implements the decoding for bmp images using bit masks + */ +class SkBmpMaskCodec : public SkBmpBaseCodec { +public: + + /* + * Creates an instance of the decoder + * + * Called only by SkBmpCodec::MakeFromStream + * There should be no other callers despite this being public + * + * @param info contains properties of the encoded data + * @param stream the stream of encoded image data + * @param bitsPerPixel the number of bits used to store each pixel + * @param masks color masks for certain bmp formats + * @param rowOrder indicates whether rows are ordered top-down or bottom-up + */ + SkBmpMaskCodec(SkEncodedInfo&& info, std::unique_ptr, + uint16_t bitsPerPixel, SkMasks* masks, + SkCodec::SkScanlineOrder rowOrder); + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options&, + int*) override; + + SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + +private: + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fMaskSwizzler); + return fMaskSwizzler.get(); + } + + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; + + std::unique_ptr fMasks; + std::unique_ptr fMaskSwizzler; + + typedef SkBmpBaseCodec INHERITED; +}; +#endif // SkBmpMaskCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpRLECodec.cpp b/gfx/skia/skia/src/codec/SkBmpRLECodec.cpp new file mode 100644 index 0000000000..fc5d298e2f --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpRLECodec.cpp @@ -0,0 +1,568 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpRLECodec.h" +#include "src/codec/SkCodecPriv.h" + +/* + * Creates an instance of the decoder + * Called only by NewFromStream + */ +SkBmpRLECodec::SkBmpRLECodec(SkEncodedInfo&& info, + std::unique_ptr stream, + uint16_t bitsPerPixel, uint32_t numColors, + uint32_t bytesPerColor, uint32_t offset, + SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fColorTable(nullptr) + , fNumColors(numColors) + , fBytesPerColor(bytesPerColor) + , fOffset(offset) + , fBytesBuffered(0) + , fCurrRLEByte(0) + , fSampleX(1) +{} + +/* + * Initiates the bitmap decode + */ +SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + if (opts.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + Result result = this->prepareToDecode(dstInfo, opts); + if (kSuccess != result) { + return result; + } + + // Perform the decode + int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + // We set rowsDecoded equal to the height because the background has already + // been filled. RLE encodings sometimes skip pixels, so we always start by + // filling the background. + *rowsDecoded = dstInfo.height(); + return kIncompleteInput; + } + + return kSuccess; +} + +/* + * Process the color table for the bmp input + */ + bool SkBmpRLECodec::createColorTable(SkColorType dstColorType) { + // Allocate memory for color table + uint32_t colorBytes = 0; + SkPMColor colorTable[256]; + if (this->bitsPerPixel() <= 8) { + // Inform the caller of the number of colors + uint32_t maxColors = 1 << this->bitsPerPixel(); + // Don't bother reading more than maxColors. + const uint32_t numColorsToRead = + fNumColors == 0 ? maxColors : SkTMin(fNumColors, maxColors); + + // Read the color table from the stream + colorBytes = numColorsToRead * fBytesPerColor; + std::unique_ptr cBuffer(new uint8_t[colorBytes]); + if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) { + SkCodecPrintf("Error: unable to read color table.\n"); + return false; + } + + // Fill in the color table + PackColorProc packARGB = choose_pack_color_proc(false, dstColorType); + uint32_t i = 0; + for (; i < numColorsToRead; i++) { + uint8_t blue = get_byte(cBuffer.get(), i*fBytesPerColor); + uint8_t green = get_byte(cBuffer.get(), i*fBytesPerColor + 1); + uint8_t red = get_byte(cBuffer.get(), i*fBytesPerColor + 2); + colorTable[i] = packARGB(0xFF, red, green, blue); + } + + // To avoid segmentation faults on bad pixel data, fill the end of the + // color table with black. This is the same the behavior as the + // chromium decoder. + for (; i < maxColors; i++) { + colorTable[i] = SkPackARGB32NoCheck(0xFF, 0, 0, 0); + } + + // Set the color table + fColorTable.reset(new SkColorTable(colorTable, maxColors)); + } + + // Check that we have not read past the pixel array offset + if(fOffset < colorBytes) { + // This may occur on OS 2.1 and other old versions where the color + // table defaults to max size, and the bmp tries to use a smaller + // color table. This is invalid, and our decision is to indicate + // an error, rather than try to guess the intended size of the + // color table. + SkCodecPrintf("Error: pixel data offset less than color table size.\n"); + return false; + } + + // After reading the color table, skip to the start of the pixel array + if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) { + SkCodecPrintf("Error: unable to skip to image data.\n"); + return false; + } + + // Return true on success + return true; +} + +bool SkBmpRLECodec::initializeStreamBuffer() { + fBytesBuffered = this->stream()->read(fStreamBuffer, kBufferSize); + if (fBytesBuffered == 0) { + SkCodecPrintf("Error: could not read RLE image data.\n"); + return false; + } + fCurrRLEByte = 0; + return true; +} + +/* + * @return the number of bytes remaining in the stream buffer after + * attempting to read more bytes from the stream + */ +size_t SkBmpRLECodec::checkForMoreData() { + const size_t remainingBytes = fBytesBuffered - fCurrRLEByte; + uint8_t* buffer = fStreamBuffer; + + // We will be reusing the same buffer, starting over from the beginning. + // Move any remaining bytes to the start of the buffer. + // We use memmove() instead of memcpy() because there is risk that the dst + // and src memory will overlap in corrupt images. + memmove(buffer, SkTAddOffset(buffer, fCurrRLEByte), remainingBytes); + + // Adjust the buffer ptr to the start of the unfilled data. + buffer += remainingBytes; + + // Try to read additional bytes from the stream. There are fCurrRLEByte + // bytes of additional space remaining in the buffer, assuming that we + // have already copied remainingBytes to the start of the buffer. + size_t additionalBytes = this->stream()->read(buffer, fCurrRLEByte); + + // Update counters and return the number of bytes we currently have + // available. We are at the start of the buffer again. + fCurrRLEByte = 0; + fBytesBuffered = remainingBytes + additionalBytes; + return fBytesBuffered; +} + +/* + * Set an RLE pixel using the color table + */ +void SkBmpRLECodec::setPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, uint32_t y, + uint8_t index) { + if (dst && is_coord_necessary(x, fSampleX, dstInfo.width())) { + // Set the row + uint32_t row = this->getDstRow(y, dstInfo.height()); + + // Set the pixel based on destination color type + const int dstX = get_dst_coord(x, fSampleX); + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + SkPMColor* dstRow = SkTAddOffset(dst, row * (int) dstRowBytes); + dstRow[dstX] = fColorTable->operator[](index); + break; + } + case kRGB_565_SkColorType: { + uint16_t* dstRow = SkTAddOffset(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPixel32ToPixel16(fColorTable->operator[](index)); + break; + } + default: + // This case should not be reached. We should catch an invalid + // color type when we check that the conversion is possible. + SkASSERT(false); + break; + } + } +} + +/* + * Set an RLE pixel from R, G, B values + */ +void SkBmpRLECodec::setRGBPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, + uint32_t y, uint8_t red, uint8_t green, + uint8_t blue) { + if (dst && is_coord_necessary(x, fSampleX, dstInfo.width())) { + // Set the row + uint32_t row = this->getDstRow(y, dstInfo.height()); + + // Set the pixel based on destination color type + const int dstX = get_dst_coord(x, fSampleX); + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: { + SkPMColor* dstRow = SkTAddOffset(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + break; + } + case kBGRA_8888_SkColorType: { + SkPMColor* dstRow = SkTAddOffset(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + break; + } + case kRGB_565_SkColorType: { + uint16_t* dstRow = SkTAddOffset(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPack888ToRGB16(red, green, blue); + break; + } + default: + // This case should not be reached. We should catch an invalid + // color type when we check that the conversion is possible. + SkASSERT(false); + break; + } + } +} + +SkCodec::Result SkBmpRLECodec::onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + // FIXME: Support subsets for scanline decodes. + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + // Reset fSampleX. If it needs to be a value other than 1, it will get modified by + // the sampler. + fSampleX = 1; + fLinesToSkip = 0; + + SkColorType colorTableColorType = dstInfo.colorType(); + if (this->colorXform()) { + // Just set a known colorType for the colorTable. No need to actually transform + // the colors in the colorTable. + colorTableColorType = kBGRA_8888_SkColorType; + } + + // Create the color table if necessary and prepare the stream for decode + // Note that if it is non-NULL, inputColorCount will be modified + if (!this->createColorTable(colorTableColorType)) { + SkCodecPrintf("Error: could not create color table.\n"); + return SkCodec::kInvalidInput; + } + + // Initialize a buffer for encoded RLE data + if (!this->initializeStreamBuffer()) { + SkCodecPrintf("Error: cannot initialize stream buffer.\n"); + return SkCodec::kInvalidInput; + } + + return SkCodec::kSuccess; +} + +/* + * Performs the bitmap decoding for RLE input format + * RLE decoding is performed all at once, rather than a one row at a time + */ +int SkBmpRLECodec::decodeRows(const SkImageInfo& info, void* dst, size_t dstRowBytes, + const Options& opts) { + int height = info.height(); + + // Account for sampling. + SkImageInfo dstInfo = info.makeWH(this->fillWidth(), height); + + // Set the background as transparent. Then, if the RLE code skips pixels, + // the skipped pixels will be transparent. + if (dst) { + SkSampler::Fill(dstInfo, dst, dstRowBytes, opts.fZeroInitialized); + } + + // Adjust the height and the dst if the previous call to decodeRows() left us + // with lines that need to be skipped. + if (height > fLinesToSkip) { + height -= fLinesToSkip; + if (dst) { + dst = SkTAddOffset(dst, fLinesToSkip * dstRowBytes); + } + fLinesToSkip = 0; + + dstInfo = dstInfo.makeWH(dstInfo.width(), height); + } else { + fLinesToSkip -= height; + return height; + } + + void* decodeDst = dst; + size_t decodeRowBytes = dstRowBytes; + SkImageInfo decodeInfo = dstInfo; + if (decodeDst) { + if (this->colorXform()) { + decodeInfo = decodeInfo.makeColorType(kXformSrcColorType); + if (kRGBA_F16_SkColorType == dstInfo.colorType()) { + int count = height * dstInfo.width(); + this->resetXformBuffer(count); + sk_bzero(this->xformBuffer(), count * sizeof(uint32_t)); + decodeDst = this->xformBuffer(); + decodeRowBytes = dstInfo.width() * sizeof(uint32_t); + } + } + } + + int decodedHeight = this->decodeRLE(decodeInfo, decodeDst, decodeRowBytes); + if (this->colorXform() && decodeDst) { + for (int y = 0; y < decodedHeight; y++) { + this->applyColorXform(dst, decodeDst, dstInfo.width()); + decodeDst = SkTAddOffset(decodeDst, decodeRowBytes); + dst = SkTAddOffset(dst, dstRowBytes); + } + } + + return decodedHeight; +} + +int SkBmpRLECodec::decodeRLE(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes) { + // Use the original width to count the number of pixels in each row. + const int width = this->dimensions().width(); + + // This tells us the number of rows that we are meant to decode. + const int height = dstInfo.height(); + + // Set RLE flags + constexpr uint8_t RLE_ESCAPE = 0; + constexpr uint8_t RLE_EOL = 0; + constexpr uint8_t RLE_EOF = 1; + constexpr uint8_t RLE_DELTA = 2; + + // Destination parameters + int x = 0; + int y = 0; + + while (true) { + // If we have reached a row that is beyond the requested height, we have + // succeeded. + if (y >= height) { + // It would be better to check for the EOF marker before indicating + // success, but we may be performing a scanline decode, which + // would require us to stop before decoding the full height. + return height; + } + + // Every entry takes at least two bytes + if ((int) fBytesBuffered - fCurrRLEByte < 2) { + if (this->checkForMoreData() < 2) { + return y; + } + } + + // Read the next two bytes. These bytes have different meanings + // depending on their values. In the first interpretation, the first + // byte is an escape flag and the second byte indicates what special + // task to perform. + const uint8_t flag = fStreamBuffer[fCurrRLEByte++]; + const uint8_t task = fStreamBuffer[fCurrRLEByte++]; + + // Perform decoding + if (RLE_ESCAPE == flag) { + switch (task) { + case RLE_EOL: + x = 0; + y++; + break; + case RLE_EOF: + return height; + case RLE_DELTA: { + // Two bytes are needed to specify delta + if ((int) fBytesBuffered - fCurrRLEByte < 2) { + if (this->checkForMoreData() < 2) { + return y; + } + } + // Modify x and y + const uint8_t dx = fStreamBuffer[fCurrRLEByte++]; + const uint8_t dy = fStreamBuffer[fCurrRLEByte++]; + x += dx; + y += dy; + if (x > width) { + SkCodecPrintf("Warning: invalid RLE input.\n"); + return y - dy; + } else if (y > height) { + fLinesToSkip = y - height; + return height; + } + break; + } + default: { + // If task does not match any of the above signals, it + // indicates that we have a sequence of non-RLE pixels. + // Furthermore, the value of task is equal to the number + // of pixels to interpret. + uint8_t numPixels = task; + const size_t rowBytes = compute_row_bytes(numPixels, + this->bitsPerPixel()); + // Abort if setting numPixels moves us off the edge of the + // image. + if (x + numPixels > width) { + SkCodecPrintf("Warning: invalid RLE input.\n"); + return y; + } + + // Also abort if there are not enough bytes + // remaining in the stream to set numPixels. + + // At most, alignedRowBytes can be 255 (max uint8_t) * + // 3 (max bytes per pixel) + 1 (aligned) = 766. If + // fStreamBuffer was smaller than this, + // checkForMoreData would never succeed for some bmps. + static_assert(255 * 3 + 1 < kBufferSize, + "kBufferSize needs to be larger!"); + const size_t alignedRowBytes = SkAlign2(rowBytes); + if ((int) fBytesBuffered - fCurrRLEByte < alignedRowBytes) { + SkASSERT(alignedRowBytes < kBufferSize); + if (this->checkForMoreData() < alignedRowBytes) { + return y; + } + } + // Set numPixels number of pixels + while (numPixels > 0) { + switch(this->bitsPerPixel()) { + case 4: { + SkASSERT(fCurrRLEByte < fBytesBuffered); + uint8_t val = fStreamBuffer[fCurrRLEByte++]; + setPixel(dst, dstRowBytes, dstInfo, x++, + y, val >> 4); + numPixels--; + if (numPixels != 0) { + setPixel(dst, dstRowBytes, dstInfo, + x++, y, val & 0xF); + numPixels--; + } + break; + } + case 8: + SkASSERT(fCurrRLEByte < fBytesBuffered); + setPixel(dst, dstRowBytes, dstInfo, x++, + y, fStreamBuffer[fCurrRLEByte++]); + numPixels--; + break; + case 24: { + SkASSERT(fCurrRLEByte + 2 < fBytesBuffered); + uint8_t blue = fStreamBuffer[fCurrRLEByte++]; + uint8_t green = fStreamBuffer[fCurrRLEByte++]; + uint8_t red = fStreamBuffer[fCurrRLEByte++]; + setRGBPixel(dst, dstRowBytes, dstInfo, + x++, y, red, green, blue); + numPixels--; + break; + } + default: + SkASSERT(false); + return y; + } + } + // Skip a byte if necessary to maintain alignment + if (!SkIsAlign2(rowBytes)) { + fCurrRLEByte++; + } + break; + } + } + } else { + // If the first byte read is not a flag, it indicates the number of + // pixels to set in RLE mode. + const uint8_t numPixels = flag; + const int endX = SkTMin(x + numPixels, width); + + if (24 == this->bitsPerPixel()) { + // In RLE24, the second byte read is part of the pixel color. + // There are two more required bytes to finish encoding the + // color. + if ((int) fBytesBuffered - fCurrRLEByte < 2) { + if (this->checkForMoreData() < 2) { + return y; + } + } + + // Fill the pixels up to endX with the specified color + uint8_t blue = task; + uint8_t green = fStreamBuffer[fCurrRLEByte++]; + uint8_t red = fStreamBuffer[fCurrRLEByte++]; + while (x < endX) { + setRGBPixel(dst, dstRowBytes, dstInfo, x++, y, red, green, blue); + } + } else { + // In RLE8 or RLE4, the second byte read gives the index in the + // color table to look up the pixel color. + // RLE8 has one color index that gets repeated + // RLE4 has two color indexes in the upper and lower 4 bits of + // the bytes, which are alternated + uint8_t indices[2] = { task, task }; + if (4 == this->bitsPerPixel()) { + indices[0] >>= 4; + indices[1] &= 0xf; + } + + // Set the indicated number of pixels + for (int which = 0; x < endX; x++) { + setPixel(dst, dstRowBytes, dstInfo, x, y, indices[which]); + which = !which; + } + } + } + } +} + +bool SkBmpRLECodec::skipRows(int count) { + const SkImageInfo rowInfo = SkImageInfo::Make(this->dimensions().width(), count, + kN32_SkColorType, kUnpremul_SkAlphaType); + return count == this->decodeRows(rowInfo, nullptr, 0, this->options()); +} + +// FIXME: Make SkBmpRLECodec have no knowledge of sampling. +// Or it should do all sampling natively. +// It currently is a hybrid that needs to know what SkScaledCodec is doing. +class SkBmpRLESampler : public SkSampler { +public: + SkBmpRLESampler(SkBmpRLECodec* codec) + : fCodec(codec) + { + SkASSERT(fCodec); + } + + int fillWidth() const override { + return fCodec->fillWidth(); + } + +private: + int onSetSampleX(int sampleX) override { + return fCodec->setSampleX(sampleX); + } + + // Unowned pointer. fCodec will delete this class in its destructor. + SkBmpRLECodec* fCodec; +}; + +SkSampler* SkBmpRLECodec::getSampler(bool createIfNecessary) { + if (!fSampler && createIfNecessary) { + fSampler.reset(new SkBmpRLESampler(this)); + } + + return fSampler.get(); +} + +int SkBmpRLECodec::setSampleX(int sampleX) { + fSampleX = sampleX; + return this->fillWidth(); +} + +int SkBmpRLECodec::fillWidth() const { + return get_scaled_dimension(this->dimensions().width(), fSampleX); +} diff --git a/gfx/skia/skia/src/codec/SkBmpRLECodec.h b/gfx/skia/skia/src/codec/SkBmpRLECodec.h new file mode 100644 index 0000000000..80acf423ce --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpRLECodec.h @@ -0,0 +1,120 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpRLECodec_DEFINED +#define SkBmpRLECodec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSampler.h" + +/* + * This class implements the decoding for bmp images that use an RLE encoding + */ +class SkBmpRLECodec : public SkBmpCodec { +public: + + /* + * Creates an instance of the decoder + * + * Called only by SkBmpCodec::MakeFromStream + * There should be no other callers despite this being public + * + * @param info contains properties of the encoded data + * @param stream the stream of encoded image data + * @param bitsPerPixel the number of bits used to store each pixel + * @param numColors the number of colors in the color table + * @param bytesPerColor the number of bytes in the stream used to represent + each color in the color table + * @param offset the offset of the image pixel data from the end of the + * headers + * @param rowOrder indicates whether rows are ordered top-down or bottom-up + */ + SkBmpRLECodec(SkEncodedInfo&& info, std::unique_ptr, + uint16_t bitsPerPixel, uint32_t numColors, uint32_t bytesPerColor, + uint32_t offset, SkCodec::SkScanlineOrder rowOrder); + + int setSampleX(int); + + int fillWidth() const; + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options&, + int*) override; + + SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + +private: + + /* + * Creates the color table + * Sets colorCount to the new color count if it is non-nullptr + */ + bool createColorTable(SkColorType dstColorType); + + bool initializeStreamBuffer(); + + /* + * Before signalling kIncompleteInput, we should attempt to load the + * stream buffer with additional data. + * + * @return the number of bytes remaining in the stream buffer after + * attempting to read more bytes from the stream + */ + size_t checkForMoreData(); + + /* + * Set an RLE pixel using the color table + */ + void setPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, uint32_t y, + uint8_t index); + /* + * Set an RLE24 pixel from R, G, B values + */ + void setRGBPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, uint32_t y, + uint8_t red, uint8_t green, uint8_t blue); + + /* + * If dst is NULL, this is a signal to skip the rows. + */ + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; + int decodeRLE(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes); + + bool skipRows(int count) override; + + SkSampler* getSampler(bool createIfNecessary) override; + + sk_sp fColorTable; + // fNumColors is the number specified in the header, or 0 if not present in the header. + const uint32_t fNumColors; + const uint32_t fBytesPerColor; + const uint32_t fOffset; + + static constexpr size_t kBufferSize = 4096; + uint8_t fStreamBuffer[kBufferSize]; + size_t fBytesBuffered; + + uint32_t fCurrRLEByte; + int fSampleX; + std::unique_ptr fSampler; + + // Scanline decodes allow the client to ask for a single scanline at a time. + // This can be tricky when the RLE encoding instructs the decoder to jump down + // multiple lines. This field keeps track of lines that need to be skipped + // on subsequent calls to decodeRows(). + int fLinesToSkip; + + typedef SkBmpCodec INHERITED; +}; +#endif // SkBmpRLECodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp b/gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp new file mode 100644 index 0000000000..e60100a50f --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp @@ -0,0 +1,341 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpStandardCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/core/SkMathPriv.h" + +/* + * Creates an instance of the decoder + * Called only by NewFromStream + */ +SkBmpStandardCodec::SkBmpStandardCodec(SkEncodedInfo&& info, std::unique_ptr stream, + uint16_t bitsPerPixel, uint32_t numColors, + uint32_t bytesPerColor, uint32_t offset, + SkCodec::SkScanlineOrder rowOrder, + bool isOpaque, bool inIco) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fColorTable(nullptr) + , fNumColors(numColors) + , fBytesPerColor(bytesPerColor) + , fOffset(offset) + , fSwizzler(nullptr) + , fIsOpaque(isOpaque) + , fInIco(inIco) + , fAndMaskRowBytes(fInIco ? SkAlign4(compute_row_bytes(this->dimensions().width(), 1)) : 0) +{} + +/* + * Initiates the bitmap decode + */ +SkCodec::Result SkBmpStandardCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + if (opts.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + if (dstInfo.dimensions() != this->dimensions()) { + SkCodecPrintf("Error: scaling not supported.\n"); + return kInvalidScale; + } + + Result result = this->prepareToDecode(dstInfo, opts); + if (kSuccess != result) { + return result; + } + int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + *rowsDecoded = rows; + return kIncompleteInput; + } + return kSuccess; +} + +/* + * Process the color table for the bmp input + */ + bool SkBmpStandardCodec::createColorTable(SkColorType dstColorType, SkAlphaType dstAlphaType) { + // Allocate memory for color table + uint32_t colorBytes = 0; + SkPMColor colorTable[256]; + if (this->bitsPerPixel() <= 8) { + // Inform the caller of the number of colors + uint32_t maxColors = 1 << this->bitsPerPixel(); + // Don't bother reading more than maxColors. + const uint32_t numColorsToRead = + fNumColors == 0 ? maxColors : SkTMin(fNumColors, maxColors); + + // Read the color table from the stream + colorBytes = numColorsToRead * fBytesPerColor; + std::unique_ptr cBuffer(new uint8_t[colorBytes]); + if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) { + SkCodecPrintf("Error: unable to read color table.\n"); + return false; + } + + SkColorType packColorType = dstColorType; + SkAlphaType packAlphaType = dstAlphaType; + if (this->colorXform()) { + packColorType = kBGRA_8888_SkColorType; + packAlphaType = kUnpremul_SkAlphaType; + } + + // Choose the proper packing function + bool isPremul = (kPremul_SkAlphaType == packAlphaType) && !fIsOpaque; + PackColorProc packARGB = choose_pack_color_proc(isPremul, packColorType); + + // Fill in the color table + uint32_t i = 0; + for (; i < numColorsToRead; i++) { + uint8_t blue = get_byte(cBuffer.get(), i*fBytesPerColor); + uint8_t green = get_byte(cBuffer.get(), i*fBytesPerColor + 1); + uint8_t red = get_byte(cBuffer.get(), i*fBytesPerColor + 2); + uint8_t alpha; + if (fIsOpaque) { + alpha = 0xFF; + } else { + alpha = get_byte(cBuffer.get(), i*fBytesPerColor + 3); + } + colorTable[i] = packARGB(alpha, red, green, blue); + } + + // To avoid segmentation faults on bad pixel data, fill the end of the + // color table with black. This is the same the behavior as the + // chromium decoder. + for (; i < maxColors; i++) { + colorTable[i] = SkPackARGB32NoCheck(0xFF, 0, 0, 0); + } + + if (this->colorXform() && !this->xformOnDecode()) { + this->applyColorXform(colorTable, colorTable, maxColors); + } + + // Set the color table + fColorTable.reset(new SkColorTable(colorTable, maxColors)); + } + + // Bmp-in-Ico files do not use an offset to indicate where the pixel data + // begins. Pixel data always begins immediately after the color table. + if (!fInIco) { + // Check that we have not read past the pixel array offset + if(fOffset < colorBytes) { + // This may occur on OS 2.1 and other old versions where the color + // table defaults to max size, and the bmp tries to use a smaller + // color table. This is invalid, and our decision is to indicate + // an error, rather than try to guess the intended size of the + // color table. + SkCodecPrintf("Error: pixel data offset less than color table size.\n"); + return false; + } + + // After reading the color table, skip to the start of the pixel array + if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) { + SkCodecPrintf("Error: unable to skip to image data.\n"); + return false; + } + } + + // Return true on success + return true; +} + +static SkEncodedInfo make_info(SkEncodedInfo::Color color, + SkEncodedInfo::Alpha alpha, int bitsPerPixel) { + // This is just used for the swizzler, which does not need the width or height. + return SkEncodedInfo::Make(0, 0, color, alpha, bitsPerPixel); +} + +SkEncodedInfo SkBmpStandardCodec::swizzlerInfo() const { + const auto& info = this->getEncodedInfo(); + if (fInIco) { + if (this->bitsPerPixel() <= 8) { + return make_info(SkEncodedInfo::kPalette_Color, + info.alpha(), this->bitsPerPixel()); + } + if (this->bitsPerPixel() == 24) { + return make_info(SkEncodedInfo::kBGR_Color, + SkEncodedInfo::kOpaque_Alpha, 8); + } + } + + return make_info(info.color(), info.alpha(), info.bitsPerComponent()); +} + +void SkBmpStandardCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& opts) { + // In the case of bmp-in-icos, we will report BGRA to the client, + // since we may be required to apply an alpha mask after the decode. + // However, the swizzler needs to know the actual format of the bmp. + SkEncodedInfo encodedInfo = this->swizzlerInfo(); + + // Get a pointer to the color table if it exists + const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); + + SkImageInfo swizzlerInfo = dstInfo; + SkCodec::Options swizzlerOptions = opts; + if (this->colorXform()) { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + + swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; + } + + fSwizzler = SkSwizzler::Make(encodedInfo, colorPtr, swizzlerInfo, swizzlerOptions); + SkASSERT(fSwizzler); +} + +SkCodec::Result SkBmpStandardCodec::onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + if (this->xformOnDecode()) { + this->resetXformBuffer(dstInfo.width()); + } + + // Create the color table if necessary and prepare the stream for decode + // Note that if it is non-NULL, inputColorCount will be modified + if (!this->createColorTable(dstInfo.colorType(), dstInfo.alphaType())) { + SkCodecPrintf("Error: could not create color table.\n"); + return SkCodec::kInvalidInput; + } + + // Initialize a swizzler + this->initializeSwizzler(dstInfo, options); + return SkCodec::kSuccess; +} + +/* + * Performs the bitmap decoding for standard input format + */ +int SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) { + // Iterate over rows of the image + const int height = dstInfo.height(); + for (int y = 0; y < height; y++) { + // Read a row of the input + if (this->stream()->read(this->srcBuffer(), this->srcRowBytes()) != this->srcRowBytes()) { + SkCodecPrintf("Warning: incomplete input stream.\n"); + return y; + } + + // Decode the row in destination format + uint32_t row = this->getDstRow(y, dstInfo.height()); + + void* dstRow = SkTAddOffset(dst, row * dstRowBytes); + + if (this->xformOnDecode()) { + SkASSERT(this->colorXform()); + fSwizzler->swizzle(this->xformBuffer(), this->srcBuffer()); + this->applyColorXform(dstRow, this->xformBuffer(), fSwizzler->swizzleWidth()); + } else { + fSwizzler->swizzle(dstRow, this->srcBuffer()); + } + } + + if (fInIco && fIsOpaque) { + const int startScanline = this->currScanline(); + if (startScanline < 0) { + // We are not performing a scanline decode. + // Just decode the entire ICO mask and return. + decodeIcoMask(this->stream(), dstInfo, dst, dstRowBytes); + return height; + } + + // In order to perform a scanline ICO decode, we must be able + // to skip ahead in the stream in order to apply the AND mask + // to the requested scanlines. + // We will do this by taking advantage of the fact that + // SkIcoCodec always uses a SkMemoryStream as its underlying + // representation of the stream. + const void* memoryBase = this->stream()->getMemoryBase(); + SkASSERT(nullptr != memoryBase); + SkASSERT(this->stream()->hasLength()); + SkASSERT(this->stream()->hasPosition()); + + const size_t length = this->stream()->getLength(); + const size_t currPosition = this->stream()->getPosition(); + + // Calculate how many bytes we must skip to reach the AND mask. + const int remainingScanlines = this->dimensions().height() - startScanline - height; + const size_t bytesToSkip = remainingScanlines * this->srcRowBytes() + + startScanline * fAndMaskRowBytes; + const size_t subStreamStartPosition = currPosition + bytesToSkip; + if (subStreamStartPosition >= length) { + // FIXME: How can we indicate that this decode was actually incomplete? + return height; + } + + // Create a subStream to pass to decodeIcoMask(). It is useful to encapsulate + // the memory base into a stream in order to safely handle incomplete images + // without reading out of bounds memory. + const void* subStreamMemoryBase = SkTAddOffset(memoryBase, + subStreamStartPosition); + const size_t subStreamLength = length - subStreamStartPosition; + // This call does not transfer ownership of the subStreamMemoryBase. + SkMemoryStream subStream(subStreamMemoryBase, subStreamLength, false); + + // FIXME: If decodeIcoMask does not succeed, is there a way that we can + // indicate the decode was incomplete? + decodeIcoMask(&subStream, dstInfo, dst, dstRowBytes); + } + + return height; +} + +void SkBmpStandardCodec::decodeIcoMask(SkStream* stream, const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes) { + // BMP in ICO have transparency, so this cannot be 565. The below code depends + // on the output being an SkPMColor. + SkASSERT(kRGBA_8888_SkColorType == dstInfo.colorType() || + kBGRA_8888_SkColorType == dstInfo.colorType() || + kRGBA_F16_SkColorType == dstInfo.colorType()); + + // If we are sampling, make sure that we only mask the sampled pixels. + // We do not need to worry about sampling in the y-dimension because that + // should be handled by SkSampledCodec. + const int sampleX = fSwizzler->sampleX(); + const int sampledWidth = get_scaled_dimension(this->dimensions().width(), sampleX); + const int srcStartX = get_start_coord(sampleX); + + + SkPMColor* dstPtr = (SkPMColor*) dst; + for (int y = 0; y < dstInfo.height(); y++) { + // The srcBuffer will at least be large enough + if (stream->read(this->srcBuffer(), fAndMaskRowBytes) != fAndMaskRowBytes) { + SkCodecPrintf("Warning: incomplete AND mask for bmp-in-ico.\n"); + return; + } + + auto applyMask = [dstInfo](void* dstRow, int x, uint64_t bit) { + if (kRGBA_F16_SkColorType == dstInfo.colorType()) { + uint64_t* dst64 = (uint64_t*) dstRow; + dst64[x] &= bit - 1; + } else { + uint32_t* dst32 = (uint32_t*) dstRow; + dst32[x] &= bit - 1; + } + }; + + int row = this->getDstRow(y, dstInfo.height()); + + void* dstRow = SkTAddOffset(dstPtr, row * dstRowBytes); + + int srcX = srcStartX; + for (int dstX = 0; dstX < sampledWidth; dstX++) { + int quotient; + int modulus; + SkTDivMod(srcX, 8, "ient, &modulus); + uint32_t shift = 7 - modulus; + uint64_t alphaBit = (this->srcBuffer()[quotient] >> shift) & 0x1; + applyMask(dstRow, dstX, alphaBit); + srcX += sampleX; + } + } +} diff --git a/gfx/skia/skia/src/codec/SkBmpStandardCodec.h b/gfx/skia/skia/src/codec/SkBmpStandardCodec.h new file mode 100644 index 0000000000..966330ef4a --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpStandardCodec.h @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpStandardCodec_DEFINED +#define SkBmpStandardCodec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkBmpBaseCodec.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +/* + * This class implements the decoding for bmp images that use "standard" modes, + * which essentially means they do not contain bit masks or RLE codes. + */ +class SkBmpStandardCodec : public SkBmpBaseCodec { +public: + + /* + * Creates an instance of the decoder + * + * Called only by SkBmpCodec::MakeFromStream + * There should be no other callers despite this being public + * + * @param info contains properties of the encoded data + * @param stream the stream of encoded image data + * @param bitsPerPixel the number of bits used to store each pixel + * @param numColors the number of colors in the color table + * @param bytesPerColor the number of bytes in the stream used to represent + each color in the color table + * @param offset the offset of the image pixel data from the end of the + * headers + * @param rowOrder indicates whether rows are ordered top-down or bottom-up + * @param isOpaque indicates if the bmp itself is opaque (before applying + * the icp mask, if there is one) + * @param inIco indicates if the bmp is embedded in an ico file + */ + SkBmpStandardCodec(SkEncodedInfo&& info, std::unique_ptr stream, + uint16_t bitsPerPixel, uint32_t numColors, uint32_t bytesPerColor, + uint32_t offset, SkCodec::SkScanlineOrder rowOrder, + bool isOpaque, bool inIco); + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options&, + int*) override; + + bool onInIco() const override { + return fInIco; + } + + SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler.get(); + } + +private: + bool createColorTable(SkColorType colorType, SkAlphaType alphaType); + SkEncodedInfo swizzlerInfo() const; + void initializeSwizzler(const SkImageInfo& dstInfo, const Options& opts); + + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; + + /* + * @param stream This may be a pointer to the stream owned by the parent SkCodec + * or a sub-stream of the stream owned by the parent SkCodec. + * Either way, this stream is unowned. + */ + void decodeIcoMask(SkStream* stream, const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes); + + sk_sp fColorTable; + // fNumColors is the number specified in the header, or 0 if not present in the header. + const uint32_t fNumColors; + const uint32_t fBytesPerColor; + const uint32_t fOffset; + std::unique_ptr fSwizzler; + const bool fIsOpaque; + const bool fInIco; + const size_t fAndMaskRowBytes; // only used for fInIco decodes + + typedef SkBmpBaseCodec INHERITED; +}; +#endif // SkBmpStandardCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkCodec.cpp b/gfx/skia/skia/src/codec/SkCodec.cpp new file mode 100644 index 0000000000..cc5b9bff6e --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodec.cpp @@ -0,0 +1,867 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkData.h" +#include "include/private/SkHalf.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkFrameHolder.h" +#ifdef SK_HAS_HEIF_LIBRARY +#include "src/codec/SkHeifCodec.h" +#endif +#include "src/codec/SkIcoCodec.h" +#include "src/codec/SkJpegCodec.h" +#ifdef SK_HAS_PNG_LIBRARY +#include "src/codec/SkPngCodec.h" +#endif +#include "include/core/SkStream.h" +#include "src/codec/SkRawCodec.h" +#include "src/codec/SkWbmpCodec.h" +#include "src/codec/SkWebpCodec.h" +#ifdef SK_HAS_WUFFS_LIBRARY +#include "src/codec/SkWuffsCodec.h" +#else +#include "src/codec/SkGifCodec.h" +#endif + +struct DecoderProc { + bool (*IsFormat)(const void*, size_t); + std::unique_ptr (*MakeFromStream)(std::unique_ptr, SkCodec::Result*); +}; + +static std::vector* decoders() { + static auto* decoders = new std::vector { + #ifdef SK_HAS_JPEG_LIBRARY + { SkJpegCodec::IsJpeg, SkJpegCodec::MakeFromStream }, + #endif + #ifdef SK_HAS_WEBP_LIBRARY + { SkWebpCodec::IsWebp, SkWebpCodec::MakeFromStream }, + #endif + #ifdef SK_HAS_WUFFS_LIBRARY + { SkWuffsCodec_IsFormat, SkWuffsCodec_MakeFromStream }, + #else + { SkGifCodec::IsGif, SkGifCodec::MakeFromStream }, + #endif + #ifdef SK_HAS_PNG_LIBRARY + { SkIcoCodec::IsIco, SkIcoCodec::MakeFromStream }, + #endif + { SkBmpCodec::IsBmp, SkBmpCodec::MakeFromStream }, + { SkWbmpCodec::IsWbmp, SkWbmpCodec::MakeFromStream }, + }; + return decoders; +} + +void SkCodec::Register( + bool (*peek)(const void*, size_t), + std::unique_ptr (*make)(std::unique_ptr, SkCodec::Result*)) { + decoders()->push_back(DecoderProc{peek, make}); +} + +std::unique_ptr SkCodec::MakeFromStream( + std::unique_ptr stream, Result* outResult, + SkPngChunkReader* chunkReader, SelectionPolicy selectionPolicy) { + Result resultStorage; + if (!outResult) { + outResult = &resultStorage; + } + + if (!stream) { + *outResult = kInvalidInput; + return nullptr; + } + + if (selectionPolicy != SelectionPolicy::kPreferStillImage + && selectionPolicy != SelectionPolicy::kPreferAnimation) { + *outResult = kInvalidParameters; + return nullptr; + } + + constexpr size_t bytesToRead = MinBufferedBytesNeeded(); + + char buffer[bytesToRead]; + size_t bytesRead = stream->peek(buffer, bytesToRead); + + // It is also possible to have a complete image less than bytesToRead bytes + // (e.g. a 1 x 1 wbmp), meaning peek() would return less than bytesToRead. + // Assume that if bytesRead < bytesToRead, but > 0, the stream is shorter + // than bytesToRead, so pass that directly to the decoder. + // It also is possible the stream uses too small a buffer for peeking, but + // we trust the caller to use a large enough buffer. + + if (0 == bytesRead) { + // TODO: After implementing peek in CreateJavaOutputStreamAdaptor.cpp, this + // printf could be useful to notice failures. + // SkCodecPrintf("Encoded image data failed to peek!\n"); + + // It is possible the stream does not support peeking, but does support + // rewinding. + // Attempt to read() and pass the actual amount read to the decoder. + bytesRead = stream->read(buffer, bytesToRead); + if (!stream->rewind()) { + SkCodecPrintf("Encoded image data could not peek or rewind to determine format!\n"); + *outResult = kCouldNotRewind; + return nullptr; + } + } + + // PNG is special, since we want to be able to supply an SkPngChunkReader. + // But this code follows the same pattern as the loop. +#ifdef SK_HAS_PNG_LIBRARY + if (SkPngCodec::IsPng(buffer, bytesRead)) { + return SkPngCodec::MakeFromStream(std::move(stream), outResult, chunkReader); + } else +#endif + { + for (DecoderProc proc : *decoders()) { + if (proc.IsFormat(buffer, bytesRead)) { + return proc.MakeFromStream(std::move(stream), outResult); + } + } + +#ifdef SK_HAS_HEIF_LIBRARY + if (SkHeifCodec::IsHeif(buffer, bytesRead)) { + return SkHeifCodec::MakeFromStream(std::move(stream), selectionPolicy, outResult); + } +#endif + +#ifdef SK_CODEC_DECODES_RAW + // Try to treat the input as RAW if all the other checks failed. + return SkRawCodec::MakeFromStream(std::move(stream), outResult); +#endif + } + + if (bytesRead < bytesToRead) { + *outResult = kIncompleteInput; + } else { + *outResult = kUnimplemented; + } + + return nullptr; +} + +std::unique_ptr SkCodec::MakeFromData(sk_sp data, SkPngChunkReader* reader) { + if (!data) { + return nullptr; + } + return MakeFromStream(SkMemoryStream::Make(std::move(data)), nullptr, reader); +} + +SkCodec::SkCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr stream, + SkEncodedOrigin origin) + : fEncodedInfo(std::move(info)) + , fSrcXformFormat(srcFormat) + , fStream(std::move(stream)) + , fNeedsRewind(false) + , fOrigin(origin) + , fDstInfo() + , fOptions() + , fCurrScanline(-1) + , fStartedIncrementalDecode(false) +{} + +SkCodec::~SkCodec() {} + +bool SkCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, bool needsColorXform) { + if (!valid_alpha(dst.alphaType(), srcIsOpaque)) { + return false; + } + + switch (dst.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + return true; + case kRGBA_F16_SkColorType: + return dst.colorSpace(); + case kRGB_565_SkColorType: + return srcIsOpaque; + case kGray_8_SkColorType: + return SkEncodedInfo::kGray_Color == fEncodedInfo.color() && srcIsOpaque; + case kAlpha_8_SkColorType: + // conceptually we can convert anything into alpha_8, but we haven't actually coded + // all of those other conversions yet. + return SkEncodedInfo::kXAlpha_Color == fEncodedInfo.color(); + default: + return false; + } +} + +bool SkCodec::rewindIfNeeded() { + // Store the value of fNeedsRewind so we can update it. Next read will + // require a rewind. + const bool needsRewind = fNeedsRewind; + fNeedsRewind = true; + if (!needsRewind) { + return true; + } + + // startScanlineDecode will need to be called before decoding scanlines. + fCurrScanline = -1; + // startIncrementalDecode will need to be called before incrementalDecode. + fStartedIncrementalDecode = false; + + // Some codecs do not have a stream. They may hold onto their own data or another codec. + // They must handle rewinding themselves. + if (fStream && !fStream->rewind()) { + return false; + } + + return this->onRewind(); +} + +static SkIRect frame_rect_on_screen(SkIRect frameRect, + const SkIRect& screenRect) { + if (!frameRect.intersect(screenRect)) { + return SkIRect::MakeEmpty(); + } + + return frameRect; +} + +bool zero_rect(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + SkISize srcDimensions, SkIRect prevRect) { + prevRect = frame_rect_on_screen(prevRect, SkIRect::MakeSize(srcDimensions)); + if (prevRect.isEmpty()) { + return true; + } + const auto dimensions = dstInfo.dimensions(); + if (dimensions != srcDimensions) { + SkRect src = SkRect::Make(srcDimensions); + SkRect dst = SkRect::Make(dimensions); + SkMatrix map = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kCenter_ScaleToFit); + SkRect asRect = SkRect::Make(prevRect); + if (!map.mapRect(&asRect)) { + return false; + } + asRect.roundIn(&prevRect); + if (prevRect.isEmpty()) { + // Down-scaling shrank the empty portion to nothing, + // so nothing to zero. + return true; + } + } + + const SkImageInfo info = dstInfo.makeDimensions(prevRect.size()); + const size_t bpp = dstInfo.bytesPerPixel(); + const size_t offset = prevRect.x() * bpp + prevRect.y() * rowBytes; + void* eraseDst = SkTAddOffset(pixels, offset); + SkSampler::Fill(info, eraseDst, rowBytes, SkCodec::kNo_ZeroInitialized); + return true; +} + +SkCodec::Result SkCodec::handleFrameIndex(const SkImageInfo& info, void* pixels, size_t rowBytes, + const Options& options) { + const int index = options.fFrameIndex; + if (0 == index) { + return this->initializeColorXform(info, fEncodedInfo.alpha(), fEncodedInfo.opaque()) + ? kSuccess : kInvalidConversion; + } + + if (index < 0) { + return kInvalidParameters; + } + + if (options.fSubset) { + // If we add support for this, we need to update the code that zeroes + // a kRestoreBGColor frame. + return kInvalidParameters; + } + + if (index >= this->onGetFrameCount()) { + return kIncompleteInput; + } + + const auto* frameHolder = this->getFrameHolder(); + SkASSERT(frameHolder); + + const auto* frame = frameHolder->getFrame(index); + SkASSERT(frame); + + const int requiredFrame = frame->getRequiredFrame(); + if (requiredFrame != kNoFrame) { + if (options.fPriorFrame != kNoFrame) { + // Check for a valid frame as a starting point. Alternatively, we could + // treat an invalid frame as not providing one, but rejecting it will + // make it easier to catch the mistake. + if (options.fPriorFrame < requiredFrame || options.fPriorFrame >= index) { + return kInvalidParameters; + } + const auto* prevFrame = frameHolder->getFrame(options.fPriorFrame); + switch (prevFrame->getDisposalMethod()) { + case SkCodecAnimation::DisposalMethod::kRestorePrevious: + return kInvalidParameters; + case SkCodecAnimation::DisposalMethod::kRestoreBGColor: + // If a frame after the required frame is provided, there is no + // need to clear, since it must be covered by the desired frame. + if (options.fPriorFrame == requiredFrame) { + SkIRect prevRect = prevFrame->frameRect(); + if (!zero_rect(info, pixels, rowBytes, this->dimensions(), prevRect)) { + return kInternalError; + } + } + break; + default: + break; + } + } else { + Options prevFrameOptions(options); + prevFrameOptions.fFrameIndex = requiredFrame; + prevFrameOptions.fZeroInitialized = kNo_ZeroInitialized; + const Result result = this->getPixels(info, pixels, rowBytes, &prevFrameOptions); + if (result != kSuccess) { + return result; + } + const auto* prevFrame = frameHolder->getFrame(requiredFrame); + const auto disposalMethod = prevFrame->getDisposalMethod(); + if (disposalMethod == SkCodecAnimation::DisposalMethod::kRestoreBGColor) { + auto prevRect = prevFrame->frameRect(); + if (!zero_rect(info, pixels, rowBytes, this->dimensions(), prevRect)) { + return kInternalError; + } + } + } + } + + return this->initializeColorXform(info, frame->reportedAlpha(), !frame->hasAlpha()) + ? kSuccess : kInvalidConversion; +} + +SkCodec::Result SkCodec::getPixels(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const Options* options) { + SkImageInfo info = dstInfo; + if (!info.colorSpace()) { + info = info.makeColorSpace(SkColorSpace::MakeSRGB()); + } + + if (kUnknown_SkColorType == info.colorType()) { + return kInvalidConversion; + } + if (nullptr == pixels) { + return kInvalidParameters; + } + if (rowBytes < info.minRowBytes()) { + return kInvalidParameters; + } + + if (!this->rewindIfNeeded()) { + return kCouldNotRewind; + } + + // Default options. + Options optsStorage; + if (nullptr == options) { + options = &optsStorage; + } else { + if (options->fSubset) { + SkIRect subset(*options->fSubset); + if (!this->onGetValidSubset(&subset) || subset != *options->fSubset) { + // FIXME: How to differentiate between not supporting subset at all + // and not supporting this particular subset? + return kUnimplemented; + } + } + } + + const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, + *options); + if (frameIndexResult != kSuccess) { + return frameIndexResult; + } + + // FIXME: Support subsets somehow? Note that this works for SkWebpCodec + // because it supports arbitrary scaling/subset combinations. + if (!this->dimensionsSupported(info.dimensions())) { + return kInvalidScale; + } + + fDstInfo = info; + fOptions = *options; + + // On an incomplete decode, the subclass will specify the number of scanlines that it decoded + // successfully. + int rowsDecoded = 0; + const Result result = this->onGetPixels(info, pixels, rowBytes, *options, &rowsDecoded); + + // A return value of kIncompleteInput indicates a truncated image stream. + // In this case, we will fill any uninitialized memory with a default value. + // Some subclasses will take care of filling any uninitialized memory on + // their own. They indicate that all of the memory has been filled by + // setting rowsDecoded equal to the height. + if ((kIncompleteInput == result || kErrorInInput == result) && rowsDecoded != info.height()) { + // FIXME: (skbug.com/5772) fillIncompleteImage will fill using the swizzler's width, unless + // there is a subset. In that case, it will use the width of the subset. From here, the + // subset will only be non-null in the case of SkWebpCodec, but it treats the subset + // differenty from the other codecs, and it needs to use the width specified by the info. + // Set the subset to null so SkWebpCodec uses the correct width. + fOptions.fSubset = nullptr; + this->fillIncompleteImage(info, pixels, rowBytes, options->fZeroInitialized, info.height(), + rowsDecoded); + } + + return result; +} + +SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, + size_t rowBytes, const SkCodec::Options* options) { + fStartedIncrementalDecode = false; + + SkImageInfo info = dstInfo; + if (!info.colorSpace()) { + info = info.makeColorSpace(SkColorSpace::MakeSRGB()); + } + if (kUnknown_SkColorType == info.colorType()) { + return kInvalidConversion; + } + if (nullptr == pixels) { + return kInvalidParameters; + } + + // FIXME: If the rows come after the rows of a previous incremental decode, + // we might be able to skip the rewind, but only the implementation knows + // that. (e.g. PNG will always need to rewind, since we called longjmp, but + // a bottom-up BMP could skip rewinding if the new rows are above the old + // rows.) + if (!this->rewindIfNeeded()) { + return kCouldNotRewind; + } + + // Set options. + Options optsStorage; + if (nullptr == options) { + options = &optsStorage; + } else { + if (options->fSubset) { + SkIRect size = SkIRect::MakeSize(info.dimensions()); + if (!size.contains(*options->fSubset)) { + return kInvalidParameters; + } + + const int top = options->fSubset->top(); + const int bottom = options->fSubset->bottom(); + if (top < 0 || top >= info.height() || top >= bottom || bottom > info.height()) { + return kInvalidParameters; + } + } + } + + const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, + *options); + if (frameIndexResult != kSuccess) { + return frameIndexResult; + } + + if (!this->dimensionsSupported(info.dimensions())) { + return kInvalidScale; + } + + fDstInfo = info; + fOptions = *options; + + const Result result = this->onStartIncrementalDecode(info, pixels, rowBytes, fOptions); + if (kSuccess == result) { + fStartedIncrementalDecode = true; + } else if (kUnimplemented == result) { + // FIXME: This is temporarily necessary, until we transition SkCodec + // implementations from scanline decoding to incremental decoding. + // SkAndroidCodec will first attempt to use incremental decoding, but + // will fall back to scanline decoding if incremental returns + // kUnimplemented. rewindIfNeeded(), above, set fNeedsRewind to true + // (after potentially rewinding), but we do not want the next call to + // startScanlineDecode() to do a rewind. + fNeedsRewind = false; + } + return result; +} + + +SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options* options) { + // Reset fCurrScanline in case of failure. + fCurrScanline = -1; + + SkImageInfo info = dstInfo; + if (!info.colorSpace()) { + info = info.makeColorSpace(SkColorSpace::MakeSRGB()); + } + + if (!this->rewindIfNeeded()) { + return kCouldNotRewind; + } + + // Set options. + Options optsStorage; + if (nullptr == options) { + options = &optsStorage; + } else if (options->fSubset) { + SkIRect size = SkIRect::MakeSize(info.dimensions()); + if (!size.contains(*options->fSubset)) { + return kInvalidInput; + } + + // We only support subsetting in the x-dimension for scanline decoder. + // Subsetting in the y-dimension can be accomplished using skipScanlines(). + if (options->fSubset->top() != 0 || options->fSubset->height() != info.height()) { + return kInvalidInput; + } + } + + // Scanline decoding only supports decoding the first frame. + if (options->fFrameIndex != 0) { + return kUnimplemented; + } + + // The void* dst and rowbytes in handleFrameIndex or only used for decoding prior + // frames, which is not supported here anyway, so it is safe to pass nullptr/0. + const Result frameIndexResult = this->handleFrameIndex(info, nullptr, 0, *options); + if (frameIndexResult != kSuccess) { + return frameIndexResult; + } + + // FIXME: Support subsets somehow? + if (!this->dimensionsSupported(info.dimensions())) { + return kInvalidScale; + } + + const Result result = this->onStartScanlineDecode(info, *options); + if (result != SkCodec::kSuccess) { + return result; + } + + fCurrScanline = 0; + fDstInfo = info; + fOptions = *options; + return kSuccess; +} + +int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) { + if (fCurrScanline < 0) { + return 0; + } + + SkASSERT(!fDstInfo.isEmpty()); + if (countLines <= 0 || fCurrScanline + countLines > fDstInfo.height()) { + return 0; + } + + const int linesDecoded = this->onGetScanlines(dst, countLines, rowBytes); + if (linesDecoded < countLines) { + this->fillIncompleteImage(this->dstInfo(), dst, rowBytes, this->options().fZeroInitialized, + countLines, linesDecoded); + } + fCurrScanline += countLines; + return linesDecoded; +} + +bool SkCodec::skipScanlines(int countLines) { + if (fCurrScanline < 0) { + return false; + } + + SkASSERT(!fDstInfo.isEmpty()); + if (countLines < 0 || fCurrScanline + countLines > fDstInfo.height()) { + // Arguably, we could just skip the scanlines which are remaining, + // and return true. We choose to return false so the client + // can catch their bug. + return false; + } + + bool result = this->onSkipScanlines(countLines); + fCurrScanline += countLines; + return result; +} + +int SkCodec::outputScanline(int inputScanline) const { + SkASSERT(0 <= inputScanline && inputScanline < fEncodedInfo.height()); + return this->onOutputScanline(inputScanline); +} + +int SkCodec::onOutputScanline(int inputScanline) const { + switch (this->getScanlineOrder()) { + case kTopDown_SkScanlineOrder: + return inputScanline; + case kBottomUp_SkScanlineOrder: + return fEncodedInfo.height() - inputScanline - 1; + default: + // This case indicates an interlaced gif and is implemented by SkGifCodec. + SkASSERT(false); + return 0; + } +} + +void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t rowBytes, + ZeroInitialized zeroInit, int linesRequested, int linesDecoded) { + if (kYes_ZeroInitialized == zeroInit) { + return; + } + + const int linesRemaining = linesRequested - linesDecoded; + SkSampler* sampler = this->getSampler(false); + + const int fillWidth = sampler ? sampler->fillWidth() : + fOptions.fSubset ? fOptions.fSubset->width() : + info.width() ; + void* fillDst = this->getScanlineOrder() == kBottomUp_SkScanlineOrder ? dst : + SkTAddOffset(dst, linesDecoded * rowBytes); + const auto fillInfo = info.makeWH(fillWidth, linesRemaining); + SkSampler::Fill(fillInfo, fillDst, rowBytes, kNo_ZeroInitialized); +} + +bool sk_select_xform_format(SkColorType colorType, bool forColorTable, + skcms_PixelFormat* outFormat) { + SkASSERT(outFormat); + + switch (colorType) { + case kRGBA_8888_SkColorType: + *outFormat = skcms_PixelFormat_RGBA_8888; + break; + case kBGRA_8888_SkColorType: + *outFormat = skcms_PixelFormat_BGRA_8888; + break; + case kRGB_565_SkColorType: + if (forColorTable) { +#ifdef SK_PMCOLOR_IS_RGBA + *outFormat = skcms_PixelFormat_RGBA_8888; +#else + *outFormat = skcms_PixelFormat_BGRA_8888; +#endif + break; + } + *outFormat = skcms_PixelFormat_BGR_565; + break; + case kRGBA_F16_SkColorType: + *outFormat = skcms_PixelFormat_RGBA_hhhh; + break; + case kGray_8_SkColorType: + *outFormat = skcms_PixelFormat_G_8; + break; + default: + return false; + } + return true; +} + +bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo, SkEncodedInfo::Alpha encodedAlpha, + bool srcIsOpaque) { + fXformTime = kNo_XformTime; + bool needsColorXform = false; + if (this->usesColorXform() && dstInfo.colorSpace()) { + dstInfo.colorSpace()->toProfile(&fDstProfile); + if (kRGBA_F16_SkColorType == dstInfo.colorType()) { + needsColorXform = true; + } else { + const auto* srcProfile = fEncodedInfo.profile(); + if (!srcProfile) { + srcProfile = skcms_sRGB_profile(); + } + if (!skcms_ApproximatelyEqualProfiles(srcProfile, &fDstProfile) ) { + needsColorXform = true; + } + } + } + + if (!this->conversionSupported(dstInfo, srcIsOpaque, needsColorXform)) { + return false; + } + + if (needsColorXform) { + fXformTime = SkEncodedInfo::kPalette_Color != fEncodedInfo.color() + || kRGBA_F16_SkColorType == dstInfo.colorType() + ? kDecodeRow_XformTime : kPalette_XformTime; + if (!sk_select_xform_format(dstInfo.colorType(), fXformTime == kPalette_XformTime, + &fDstXformFormat)) { + return false; + } + if (encodedAlpha == SkEncodedInfo::kUnpremul_Alpha + && dstInfo.alphaType() == kPremul_SkAlphaType) { + fDstXformAlphaFormat = skcms_AlphaFormat_PremulAsEncoded; + } else { + fDstXformAlphaFormat = skcms_AlphaFormat_Unpremul; + } + } + return true; +} + +void SkCodec::applyColorXform(void* dst, const void* src, int count) const { + // It is okay for srcProfile to be null. This will use sRGB. + const auto* srcProfile = fEncodedInfo.profile(); + SkAssertResult(skcms_Transform(src, fSrcXformFormat, skcms_AlphaFormat_Unpremul, srcProfile, + dst, fDstXformFormat, fDstXformAlphaFormat, &fDstProfile, + count)); +} + +std::vector SkCodec::getFrameInfo() { + const int frameCount = this->getFrameCount(); + SkASSERT(frameCount >= 0); + if (frameCount <= 0) { + return std::vector{}; + } + + if (frameCount == 1 && !this->onGetFrameInfo(0, nullptr)) { + // Not animated. + return std::vector{}; + } + + std::vector result(frameCount); + for (int i = 0; i < frameCount; ++i) { + SkAssertResult(this->onGetFrameInfo(i, &result[i])); + } + return result; +} + +const char* SkCodec::ResultToString(Result result) { + switch (result) { + case kSuccess: + return "success"; + case kIncompleteInput: + return "incomplete input"; + case kErrorInInput: + return "error in input"; + case kInvalidConversion: + return "invalid conversion"; + case kInvalidScale: + return "invalid scale"; + case kInvalidParameters: + return "invalid parameters"; + case kInvalidInput: + return "invalid input"; + case kCouldNotRewind: + return "could not rewind"; + case kInternalError: + return "internal error"; + case kUnimplemented: + return "unimplemented"; + default: + SkASSERT(false); + return "bogus result value"; + } +} + +static bool independent(const SkFrame& frame) { + return frame.getRequiredFrame() == SkCodec::kNoFrame; +} + +static bool restore_bg(const SkFrame& frame) { + return frame.getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestoreBGColor; +} + +// As its name suggests, this method computes a frame's alpha (e.g. completely +// opaque, unpremul, binary) and its required frame (a preceding frame that +// this frame depends on, to draw the complete image at this frame's point in +// the animation stream), and calls this frame's setter methods with that +// computed information. +// +// A required frame of kNoFrame means that this frame is independent: drawing +// the complete image at this frame's point in the animation stream does not +// require first preparing the pixel buffer based on another frame. Instead, +// drawing can start from an uninitialized pixel buffer. +// +// "Uninitialized" is from the SkCodec's caller's point of view. In the SkCodec +// implementation, for independent frames, first party Skia code (in src/codec) +// will typically fill the buffer with a uniform background color (e.g. +// transparent black) before calling into third party codec-specific code (e.g. +// libjpeg or libpng). Pixels outside of the frame's rect will remain this +// background color after drawing this frame. For incomplete decodes, pixels +// inside that rect may be (at least temporarily) set to that background color. +// In an incremental decode, later passes may then overwrite that background +// color. +// +// Determining kNoFrame or otherwise involves testing a number of conditions +// sequentially. The first satisfied condition results in setting the required +// frame to kNoFrame (an "INDx" condition) or to a non-negative frame number (a +// "DEPx" condition), and the function returning early. Those "INDx" and "DEPx" +// labels also map to comments in the function body. +// +// - IND1: this frame is the first frame. +// - IND2: this frame fills out the whole image, and it is completely opaque +// or it overwrites (not blends with) the previous frame. +// - IND3: all preceding frames' disposals are kRestorePrevious. +// - IND4: the prevFrame's disposal is kRestoreBGColor, and it fills out the +// whole image or it is itself otherwise independent. +// - DEP5: this frame reports alpha (it is not completely opaque) and it +// blends with (not overwrites) the previous frame. +// - IND6: this frame's rect covers the rects of all preceding frames back to +// and including the most recent independent frame before this frame. +// - DEP7: unconditional. +// +// The "prevFrame" variable initially points to the previous frame (also known +// as the prior frame), but that variable may iterate further backwards over +// the course of this computation. +void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) { + const bool reportsAlpha = frame->reportedAlpha() != SkEncodedInfo::kOpaque_Alpha; + const auto screenRect = SkIRect::MakeWH(fScreenWidth, fScreenHeight); + const auto frameRect = frame_rect_on_screen(frame->frameRect(), screenRect); + + const int i = frame->frameId(); + if (0 == i) { + frame->setHasAlpha(reportsAlpha || frameRect != screenRect); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND1 + return; + } + + + const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kPriorFrame; + if ((!reportsAlpha || !blendWithPrevFrame) && frameRect == screenRect) { + frame->setHasAlpha(reportsAlpha); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND2 + return; + } + + const SkFrame* prevFrame = this->getFrame(i-1); + while (prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestorePrevious) { + const int prevId = prevFrame->frameId(); + if (0 == prevId) { + frame->setHasAlpha(true); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND3 + return; + } + + prevFrame = this->getFrame(prevId - 1); + } + + const bool clearPrevFrame = restore_bg(*prevFrame); + auto prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect); + + if (clearPrevFrame) { + if (prevFrameRect == screenRect || independent(*prevFrame)) { + frame->setHasAlpha(true); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND4 + return; + } + } + + if (reportsAlpha && blendWithPrevFrame) { + // Note: We could be more aggressive here. If prevFrame clears + // to background color and covers its required frame (and that + // frame is independent), prevFrame could be marked independent. + // Would this extra complexity be worth it? + frame->setRequiredFrame(prevFrame->frameId()); // DEP5 + frame->setHasAlpha(prevFrame->hasAlpha() || clearPrevFrame); + return; + } + + while (frameRect.contains(prevFrameRect)) { + const int prevRequiredFrame = prevFrame->getRequiredFrame(); + if (prevRequiredFrame == SkCodec::kNoFrame) { + frame->setRequiredFrame(SkCodec::kNoFrame); // IND6 + frame->setHasAlpha(true); + return; + } + + prevFrame = this->getFrame(prevRequiredFrame); + prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect); + } + + frame->setRequiredFrame(prevFrame->frameId()); // DEP7 + if (restore_bg(*prevFrame)) { + frame->setHasAlpha(true); + return; + } + SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kKeep); + frame->setHasAlpha(prevFrame->hasAlpha() || (reportsAlpha && !blendWithPrevFrame)); +} + diff --git a/gfx/skia/skia/src/codec/SkCodecAnimationPriv.h b/gfx/skia/skia/src/codec/SkCodecAnimationPriv.h new file mode 100644 index 0000000000..233a79b211 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecAnimationPriv.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodecAnimationPriv_DEFINED +#define SkCodecAnimationPriv_DEFINED + +namespace SkCodecAnimation { + /** + * How to blend the current frame. + */ + enum class Blend { + /** + * Blend with the prior frame. This is the typical case, supported + * by all animated image types. + */ + kPriorFrame, + + /** + * Do not blend. + * + * This frames pixels overwrite previous pixels "blending" with + * the background color of transparent. + */ + kBG, + }; + +} +#endif // SkCodecAnimationPriv_DEFINED diff --git a/gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp b/gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp new file mode 100644 index 0000000000..b7909479a1 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkYUVAIndex.h" +#include "src/codec/SkCodecImageGenerator.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkPixmapPriv.h" + +std::unique_ptr SkCodecImageGenerator::MakeFromEncodedCodec(sk_sp data) { + auto codec = SkCodec::MakeFromData(data); + if (nullptr == codec) { + return nullptr; + } + + return std::unique_ptr(new SkCodecImageGenerator(std::move(codec), data)); +} + +std::unique_ptr +SkCodecImageGenerator::MakeFromCodec(std::unique_ptr codec) { + return codec + ? std::unique_ptr(new SkCodecImageGenerator(std::move(codec), nullptr)) + : nullptr; +} + +static SkImageInfo adjust_info(SkCodec* codec) { + SkImageInfo info = codec->getInfo(); + if (kUnpremul_SkAlphaType == info.alphaType()) { + info = info.makeAlphaType(kPremul_SkAlphaType); + } + if (SkPixmapPriv::ShouldSwapWidthHeight(codec->getOrigin())) { + info = SkPixmapPriv::SwapWidthHeight(info); + } + return info; +} + +SkCodecImageGenerator::SkCodecImageGenerator(std::unique_ptr codec, sk_sp data) + : INHERITED(adjust_info(codec.get())) + , fCodec(std::move(codec)) + , fData(std::move(data)) +{} + +sk_sp SkCodecImageGenerator::onRefEncodedData() { + return fData; +} + +bool SkCodecImageGenerator::onGetPixels(const SkImageInfo& requestInfo, void* requestPixels, + size_t requestRowBytes, const Options&) { + SkPixmap dst(requestInfo, requestPixels, requestRowBytes); + + auto decode = [this](const SkPixmap& pm) { + SkCodec::Result result = fCodec->getPixels(pm); + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + return false; + } + }; + + return SkPixmapPriv::Orient(dst, fCodec->getOrigin(), decode); +} + +bool SkCodecImageGenerator::onQueryYUVA8(SkYUVASizeInfo* sizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace) const { + // This image generator always returns 3 separate non-interleaved planes + yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0; + yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR; + yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1; + yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kR; + yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 2; + yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kR; + yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1; + yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR; + + return fCodec->queryYUV8(sizeInfo, colorSpace); +} + +bool SkCodecImageGenerator::onGetYUVA8Planes(const SkYUVASizeInfo& sizeInfo, + const SkYUVAIndex indices[SkYUVAIndex::kIndexCount], + void* planes[]) { + SkCodec::Result result = fCodec->getYUV8Planes(sizeInfo, planes); + // TODO: check indices + + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + return false; + } +} diff --git a/gfx/skia/skia/src/codec/SkCodecImageGenerator.h b/gfx/skia/skia/src/codec/SkCodecImageGenerator.h new file mode 100644 index 0000000000..3d8404f2dc --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecImageGenerator.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkCodecImageGenerator_DEFINED +#define SkCodecImageGenerator_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkImageGenerator.h" + +class SkCodecImageGenerator : public SkImageGenerator { +public: + /* + * If this data represents an encoded image that we know how to decode, + * return an SkCodecImageGenerator. Otherwise return nullptr. + */ + static std::unique_ptr MakeFromEncodedCodec(sk_sp); + + static std::unique_ptr MakeFromCodec(std::unique_ptr); + +protected: + sk_sp onRefEncodedData() override; + + bool onGetPixels( + const SkImageInfo& info, void* pixels, size_t rowBytes, const Options& opts) override; + + bool onQueryYUVA8( + SkYUVASizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount], SkYUVColorSpace*) const override; + + bool onGetYUVA8Planes(const SkYUVASizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount], + void* planes[]) override; + +private: + /* + * Takes ownership of codec + */ + SkCodecImageGenerator(std::unique_ptr, sk_sp); + + std::unique_ptr fCodec; + sk_sp fData; + + typedef SkImageGenerator INHERITED; +}; +#endif // SkCodecImageGenerator_DEFINED diff --git a/gfx/skia/skia/src/codec/SkCodecPriv.h b/gfx/skia/skia/src/codec/SkCodecPriv.h new file mode 100644 index 0000000000..d2f2a37f31 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecPriv.h @@ -0,0 +1,251 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodecPriv_DEFINED +#define SkCodecPriv_DEFINED + +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkEncodedInfo.h" +#include "src/codec/SkColorTable.h" +#include "src/core/SkEndian.h" + +#ifdef SK_PRINT_CODEC_MESSAGES + #define SkCodecPrintf SkDebugf +#else + #define SkCodecPrintf(...) +#endif + +// Defined in SkCodec.cpp +bool sk_select_xform_format(SkColorType colorType, bool forColorTable, + skcms_PixelFormat* outFormat); + +// FIXME: Consider sharing with dm, nanbench, and tools. +static inline float get_scale_from_sample_size(int sampleSize) { + return 1.0f / ((float) sampleSize); +} + +static inline bool is_valid_subset(const SkIRect& subset, const SkISize& imageDims) { + return SkIRect::MakeSize(imageDims).contains(subset); +} + +/* + * returns a scaled dimension based on the original dimension and the sampleSize + * NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder + * FIXME: I think we should call this get_sampled_dimension(). + */ +static inline int get_scaled_dimension(int srcDimension, int sampleSize) { + if (sampleSize > srcDimension) { + return 1; + } + return srcDimension / sampleSize; +} + +/* + * Returns the first coordinate that we will keep during a scaled decode. + * The output can be interpreted as an x-coordinate or a y-coordinate. + * + * This does not need to be called and is not called when sampleFactor == 1. + */ +static inline int get_start_coord(int sampleFactor) { return sampleFactor / 2; }; + +/* + * Given a coordinate in the original image, this returns the corresponding + * coordinate in the scaled image. This function is meaningless if + * IsCoordNecessary returns false. + * The output can be interpreted as an x-coordinate or a y-coordinate. + * + * This does not need to be called and is not called when sampleFactor == 1. + */ +static inline int get_dst_coord(int srcCoord, int sampleFactor) { return srcCoord / sampleFactor; }; + +/* + * When scaling, we will discard certain y-coordinates (rows) and + * x-coordinates (columns). This function returns true if we should keep the + * coordinate and false otherwise. + * The inputs may be x-coordinates or y-coordinates. + * + * This does not need to be called and is not called when sampleFactor == 1. + */ +static inline bool is_coord_necessary(int srcCoord, int sampleFactor, int scaledDim) { + // Get the first coordinate that we want to keep + int startCoord = get_start_coord(sampleFactor); + + // Return false on edge cases + if (srcCoord < startCoord || get_dst_coord(srcCoord, sampleFactor) >= scaledDim) { + return false; + } + + // Every sampleFactor rows are necessary + return ((srcCoord - startCoord) % sampleFactor) == 0; +} + +static inline bool valid_alpha(SkAlphaType dstAlpha, bool srcIsOpaque) { + if (kUnknown_SkAlphaType == dstAlpha) { + return false; + } + + if (srcIsOpaque) { + if (kOpaque_SkAlphaType != dstAlpha) { + SkCodecPrintf("Warning: an opaque image should be decoded as opaque " + "- it is being decoded as non-opaque, which will draw slower\n"); + } + return true; + } + + return dstAlpha != kOpaque_SkAlphaType; +} + +/* + * If there is a color table, get a pointer to the colors, otherwise return nullptr + */ +static inline const SkPMColor* get_color_ptr(SkColorTable* colorTable) { + return nullptr != colorTable ? colorTable->readColors() : nullptr; +} + +/* + * Compute row bytes for an image using pixels per byte + */ +static inline size_t compute_row_bytes_ppb(int width, uint32_t pixelsPerByte) { + return (width + pixelsPerByte - 1) / pixelsPerByte; +} + +/* + * Compute row bytes for an image using bytes per pixel + */ +static inline size_t compute_row_bytes_bpp(int width, uint32_t bytesPerPixel) { + return width * bytesPerPixel; +} + +/* + * Compute row bytes for an image + */ +static inline size_t compute_row_bytes(int width, uint32_t bitsPerPixel) { + if (bitsPerPixel < 16) { + SkASSERT(0 == 8 % bitsPerPixel); + const uint32_t pixelsPerByte = 8 / bitsPerPixel; + return compute_row_bytes_ppb(width, pixelsPerByte); + } else { + SkASSERT(0 == bitsPerPixel % 8); + const uint32_t bytesPerPixel = bitsPerPixel / 8; + return compute_row_bytes_bpp(width, bytesPerPixel); + } +} + +/* + * Get a byte from a buffer + * This method is unsafe, the caller is responsible for performing a check + */ +static inline uint8_t get_byte(uint8_t* buffer, uint32_t i) { + return buffer[i]; +} + +/* + * Get a short from a buffer + * This method is unsafe, the caller is responsible for performing a check + */ +static inline uint16_t get_short(uint8_t* buffer, uint32_t i) { + uint16_t result; + memcpy(&result, &(buffer[i]), 2); +#ifdef SK_CPU_BENDIAN + return SkEndianSwap16(result); +#else + return result; +#endif +} + +/* + * Get an int from a buffer + * This method is unsafe, the caller is responsible for performing a check + */ +static inline uint32_t get_int(uint8_t* buffer, uint32_t i) { + uint32_t result; + memcpy(&result, &(buffer[i]), 4); +#ifdef SK_CPU_BENDIAN + return SkEndianSwap32(result); +#else + return result; +#endif +} + +/* + * @param data Buffer to read bytes from + * @param isLittleEndian Output parameter + * Indicates if the data is little endian + * Is unaffected on false returns + */ +static inline bool is_valid_endian_marker(const uint8_t* data, bool* isLittleEndian) { + // II indicates Intel (little endian) and MM indicates motorola (big endian). + if (('I' != data[0] || 'I' != data[1]) && ('M' != data[0] || 'M' != data[1])) { + return false; + } + + *isLittleEndian = ('I' == data[0]); + return true; +} + +static inline uint16_t get_endian_short(const uint8_t* data, bool littleEndian) { + if (littleEndian) { + return (data[1] << 8) | (data[0]); + } + + return (data[0] << 8) | (data[1]); +} + +static inline SkPMColor premultiply_argb_as_rgba(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + + return SkPackARGB_as_RGBA(a, r, g, b); +} + +static inline SkPMColor premultiply_argb_as_bgra(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + + return SkPackARGB_as_BGRA(a, r, g, b); +} + +static inline bool is_rgba(SkColorType colorType) { +#ifdef SK_PMCOLOR_IS_RGBA + return (kBGRA_8888_SkColorType != colorType); +#else + return (kRGBA_8888_SkColorType == colorType); +#endif +} + +// Method for coverting to a 32 bit pixel. +typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b); + +static inline PackColorProc choose_pack_color_proc(bool isPremul, SkColorType colorType) { + bool isRGBA = is_rgba(colorType); + if (isPremul) { + if (isRGBA) { + return &premultiply_argb_as_rgba; + } else { + return &premultiply_argb_as_bgra; + } + } else { + if (isRGBA) { + return &SkPackARGB_as_RGBA; + } else { + return &SkPackARGB_as_BGRA; + } + } +} + +bool is_orientation_marker(const uint8_t* data, size_t data_length, SkEncodedOrigin* orientation); + +#endif // SkCodecPriv_DEFINED diff --git a/gfx/skia/skia/src/codec/SkColorTable.cpp b/gfx/skia/skia/src/codec/SkColorTable.cpp new file mode 100644 index 0000000000..c15c452478 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkColorTable.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" +#include "src/codec/SkColorTable.h" + +SkColorTable::SkColorTable(const SkPMColor colors[], int count) { + SkASSERT(0 == count || colors); + SkASSERT(count >= 0 && count <= 256); + + fCount = count; + fColors = reinterpret_cast(sk_malloc_throw(count * sizeof(SkPMColor))); + + memcpy(fColors, colors, count * sizeof(SkPMColor)); +} + +SkColorTable::~SkColorTable() { + sk_free(fColors); +} diff --git a/gfx/skia/skia/src/codec/SkColorTable.h b/gfx/skia/skia/src/codec/SkColorTable.h new file mode 100644 index 0000000000..e83498ead8 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkColorTable.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorTable_DEFINED +#define SkColorTable_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkRefCnt.h" + +/** \class SkColorTable + + SkColorTable holds an array SkPMColors (premultiplied 32-bit colors) used by + 8-bit bitmaps, where the bitmap bytes are interpreted as indices into the colortable. + + SkColorTable is thread-safe. +*/ +class SkColorTable : public SkRefCnt { +public: + /** Copy up to 256 colors into a new SkColorTable. + */ + SkColorTable(const SkPMColor colors[], int count); + ~SkColorTable() override; + + /** Returns the number of colors in the table. + */ + int count() const { return fCount; } + + /** Returns the specified color from the table. In the debug build, this asserts that + * the index is in range (0 <= index < count). + */ + SkPMColor operator[](int index) const { + SkASSERT(fColors != nullptr && (unsigned)index < (unsigned)fCount); + return fColors[index]; + } + + /** Return the array of colors for reading. */ + const SkPMColor* readColors() const { return fColors; } + +private: + SkPMColor* fColors; + int fCount; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkEncodedInfo.cpp b/gfx/skia/skia/src/codec/SkEncodedInfo.cpp new file mode 100644 index 0000000000..75c4d3061d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkEncodedInfo.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkEncodedInfo.h" + +std::unique_ptr SkEncodedInfo::ICCProfile::Make(sk_sp data) { + if (data) { + skcms_ICCProfile profile; + if (skcms_Parse(data->data(), data->size(), &profile)) { + return std::unique_ptr(new ICCProfile(profile, std::move(data))); + } + } + return nullptr; +} + +std::unique_ptr SkEncodedInfo::ICCProfile::Make( + const skcms_ICCProfile& profile) { + return std::unique_ptr(new ICCProfile(profile)); +} + +SkEncodedInfo::ICCProfile::ICCProfile(const skcms_ICCProfile& profile, sk_sp data) + : fProfile(profile) + , fData(std::move(data)) +{} diff --git a/gfx/skia/skia/src/codec/SkFrameHolder.h b/gfx/skia/skia/src/codec/SkFrameHolder.h new file mode 100644 index 0000000000..c44d2e048c --- /dev/null +++ b/gfx/skia/skia/src/codec/SkFrameHolder.h @@ -0,0 +1,201 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFrameHolder_DEFINED +#define SkFrameHolder_DEFINED + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkRect.h" +#include "include/core/SkTypes.h" +#include "include/private/SkEncodedInfo.h" +#include "include/private/SkNoncopyable.h" +#include "src/codec/SkCodecAnimationPriv.h" + +/** + * Base class for a single frame of an animated image. + * + * Separate from SkCodec::FrameInfo, which is a pared down + * interface that only contains the info the client needs. + */ +class SkFrame : public SkNoncopyable { +public: + SkFrame(int id) + : fId(id) + , fHasAlpha(false) + , fRequiredFrame(kUninitialized) + , fDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep) + , fDuration(0) + , fBlend(SkCodecAnimation::Blend::kPriorFrame) + { + fRect.setEmpty(); + } + + virtual ~SkFrame() {} + + /** + * An explicit move constructor, as + * https://en.cppreference.com/w/cpp/language/move_constructor says that + * there is no implicit move constructor if there are user-declared + * destructors, and we have one, immediately above. + * + * Without a move constructor, it is harder to use an SkFrame, or an + * SkFrame subclass, inside a std::vector. + */ + SkFrame(SkFrame&&) = default; + + /** + * 0-based index of the frame in the image sequence. + */ + int frameId() const { return fId; } + + /** + * How this frame reports its alpha. + * + * This only considers the rectangle of this frame, and + * considers it to have alpha even if it is opaque once + * blended with the frame behind it. + */ + SkEncodedInfo::Alpha reportedAlpha() const { + return this->onReportedAlpha(); + } + + /** + * Cached value representing whether the frame has alpha, + * after compositing with the prior frame. + */ + bool hasAlpha() const { return fHasAlpha; } + + /** + * Cache whether the finished frame has alpha. + */ + void setHasAlpha(bool alpha) { fHasAlpha = alpha; } + + /** + * Whether enough of the frame has been read to determine + * fRequiredFrame and fHasAlpha. + */ + bool reachedStartOfData() const { return fRequiredFrame != kUninitialized; } + + /** + * The frame this one depends on. + * + * Must not be called until fRequiredFrame has been set properly. + */ + int getRequiredFrame() const { + SkASSERT(this->reachedStartOfData()); + return fRequiredFrame; + } + + /** + * Set the frame that this frame depends on. + */ + void setRequiredFrame(int req) { fRequiredFrame = req; } + + /** + * Set the rectangle that is updated by this frame. + */ + void setXYWH(int x, int y, int width, int height) { + fRect.setXYWH(x, y, width, height); + } + + /** + * The rectangle that is updated by this frame. + */ + SkIRect frameRect() const { return fRect; } + + int xOffset() const { return fRect.x(); } + int yOffset() const { return fRect.y(); } + int width() const { return fRect.width(); } + int height() const { return fRect.height(); } + + SkCodecAnimation::DisposalMethod getDisposalMethod() const { + return fDisposalMethod; + } + + void setDisposalMethod(SkCodecAnimation::DisposalMethod disposalMethod) { + fDisposalMethod = disposalMethod; + } + + /** + * Set the duration (in ms) to show this frame. + */ + void setDuration(int duration) { + fDuration = duration; + } + + /** + * Duration in ms to show this frame. + */ + int getDuration() const { + return fDuration; + } + + void setBlend(SkCodecAnimation::Blend blend) { + fBlend = blend; + } + + SkCodecAnimation::Blend getBlend() const { + return fBlend; + } + +protected: + virtual SkEncodedInfo::Alpha onReportedAlpha() const = 0; + +private: + static constexpr int kUninitialized = -2; + + const int fId; + bool fHasAlpha; + int fRequiredFrame; + SkIRect fRect; + SkCodecAnimation::DisposalMethod fDisposalMethod; + int fDuration; + SkCodecAnimation::Blend fBlend; +}; + +/** + * Base class for an object which holds the SkFrames of an + * image sequence. + */ +class SkFrameHolder : public SkNoncopyable { +public: + SkFrameHolder() + : fScreenWidth(0) + , fScreenHeight(0) + {} + + virtual ~SkFrameHolder() {} + + /** + * Size of the image. Each frame will be contained in + * these dimensions (possibly after clipping). + */ + int screenWidth() const { return fScreenWidth; } + int screenHeight() const { return fScreenHeight; } + + /** + * Compute the opacity and required frame, based on + * the frame's reportedAlpha and how it blends + * with prior frames. + */ + void setAlphaAndRequiredFrame(SkFrame*); + + /** + * Return the frame with frameId i. + */ + const SkFrame* getFrame(int i) const { + return this->onGetFrame(i); + } + +protected: + int fScreenWidth; + int fScreenHeight; + + virtual const SkFrame* onGetFrame(int i) const = 0; +}; + +#endif // SkFrameHolder_DEFINED diff --git a/gfx/skia/skia/src/codec/SkGifCodec.cpp b/gfx/skia/skia/src/codec/SkGifCodec.cpp new file mode 100644 index 0000000000..b2185d184e --- /dev/null +++ b/gfx/skia/skia/src/codec/SkGifCodec.cpp @@ -0,0 +1,533 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkGifCodec.h" +#include "src/codec/SkSwizzler.h" +#include "src/core/SkMakeUnique.h" + +#include + +#define GIF87_STAMP "GIF87a" +#define GIF89_STAMP "GIF89a" +#define GIF_STAMP_LEN 6 + +/* + * Checks the start of the stream to see if the image is a gif + */ +bool SkGifCodec::IsGif(const void* buf, size_t bytesRead) { + if (bytesRead >= GIF_STAMP_LEN) { + if (memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) + { + return true; + } + } + return false; +} + +/* + * Error function + */ +static SkCodec::Result gif_error(const char* msg, SkCodec::Result result = SkCodec::kInvalidInput) { + SkCodecPrintf("Gif Error: %s\n", msg); + return result; +} + +std::unique_ptr SkGifCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + std::unique_ptr reader(new SkGifImageReader(std::move(stream))); + *result = reader->parse(SkGifImageReader::SkGIFSizeQuery); + if (*result != kSuccess) { + return nullptr; + } + + // If no images are in the data, or the first header is not yet defined, we cannot + // create a codec. In either case, the width and height are not yet known. + auto* frame = reader->frameContext(0); + if (!frame || !frame->isHeaderDefined()) { + *result = kInvalidInput; + return nullptr; + } + + // isHeaderDefined() will not return true if the screen size is empty. + SkASSERT(reader->screenHeight() > 0 && reader->screenWidth() > 0); + + const auto alpha = reader->firstFrameHasAlpha() ? SkEncodedInfo::kBinary_Alpha + : SkEncodedInfo::kOpaque_Alpha; + // Use kPalette since Gifs are encoded with a color table. + // FIXME: Gifs can actually be encoded with 4-bits per pixel. Using 8 works, but we could skip + // expanding to 8 bits and take advantage of the SkSwizzler to work from 4. + auto encodedInfo = SkEncodedInfo::Make(reader->screenWidth(), reader->screenHeight(), + SkEncodedInfo::kPalette_Color, alpha, 8); + return std::unique_ptr(new SkGifCodec(std::move(encodedInfo), reader.release())); +} + +bool SkGifCodec::onRewind() { + fReader->clearDecodeState(); + return true; +} + +SkGifCodec::SkGifCodec(SkEncodedInfo&& encodedInfo, SkGifImageReader* reader) + : INHERITED(std::move(encodedInfo), skcms_PixelFormat_RGBA_8888, nullptr) + , fReader(reader) + , fTmpBuffer(nullptr) + , fSwizzler(nullptr) + , fCurrColorTable(nullptr) + , fCurrColorTableIsReal(false) + , fFilledBackground(false) + , fFirstCallToIncrementalDecode(false) + , fDst(nullptr) + , fDstRowBytes(0) + , fRowsDecoded(0) +{ + reader->setClient(this); +} + +int SkGifCodec::onGetFrameCount() { + fReader->parse(SkGifImageReader::SkGIFFrameCountQuery); + return fReader->imagesCount(); +} + +bool SkGifCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const { + if (i >= fReader->imagesCount()) { + return false; + } + + const SkGIFFrameContext* frameContext = fReader->frameContext(i); + SkASSERT(frameContext->reachedStartOfData()); + + if (frameInfo) { + frameInfo->fDuration = frameContext->getDuration(); + frameInfo->fRequiredFrame = frameContext->getRequiredFrame(); + frameInfo->fFullyReceived = frameContext->isComplete(); + frameInfo->fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType + : kOpaque_SkAlphaType; + frameInfo->fDisposalMethod = frameContext->getDisposalMethod(); + } + return true; +} + +int SkGifCodec::onGetRepetitionCount() { + fReader->parse(SkGifImageReader::SkGIFLoopCountQuery); + return fReader->loopCount(); +} + +static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; + +void SkGifCodec::initializeColorTable(const SkImageInfo& dstInfo, int frameIndex) { + SkColorType colorTableColorType = dstInfo.colorType(); + if (this->colorXform()) { + colorTableColorType = kXformSrcColorType; + } + + sk_sp currColorTable = fReader->getColorTable(colorTableColorType, frameIndex); + fCurrColorTableIsReal = static_cast(currColorTable); + if (!fCurrColorTableIsReal) { + // This is possible for an empty frame. Create a dummy with one value (transparent). + SkPMColor color = SK_ColorTRANSPARENT; + fCurrColorTable.reset(new SkColorTable(&color, 1)); + } else if (this->colorXform() && !this->xformOnDecode()) { + SkPMColor dstColors[256]; + this->applyColorXform(dstColors, currColorTable->readColors(), + currColorTable->count()); + fCurrColorTable.reset(new SkColorTable(dstColors, currColorTable->count())); + } else { + fCurrColorTable = std::move(currColorTable); + } +} + + +SkCodec::Result SkGifCodec::prepareToDecode(const SkImageInfo& dstInfo, const Options& opts) { + if (opts.fSubset) { + return gif_error("Subsets not supported.\n", kUnimplemented); + } + + const int frameIndex = opts.fFrameIndex; + if (frameIndex > 0 && kRGB_565_SkColorType == dstInfo.colorType()) { + // FIXME: In theory, we might be able to support this, but it's not clear that it + // is necessary (Chromium does not decode to 565, and Android does not decode + // frames beyond the first). Disabling it because it is somewhat difficult: + // - If there is a transparent pixel, and this frame draws on top of another frame + // (if the frame is independent with a transparent pixel, we should not decode to + // 565 anyway, since it is not opaque), we need to skip drawing the transparent + // pixels (see writeTransparentPixels in haveDecodedRow). We currently do this by + // first swizzling into temporary memory, then copying into the destination. (We + // let the swizzler handle it first because it may need to sample.) After + // swizzling to 565, we do not know which pixels in our temporary memory + // correspond to the transparent pixel, so we do not know what to skip. We could + // special case the non-sampled case (no need to swizzle), but as this is + // currently unused we can just not support it. + return gif_error("Cannot decode multiframe gif (except frame 0) as 565.\n", + kInvalidConversion); + } + + const auto* frame = fReader->frameContext(frameIndex); + SkASSERT(frame); + if (0 == frameIndex) { + // SkCodec does not have a way to just parse through frame 0, so we + // have to do so manually, here. + fReader->parse((SkGifImageReader::SkGIFParseQuery) 0); + if (!frame->reachedStartOfData()) { + // We have parsed enough to know that there is a color map, but cannot + // parse the map itself yet. Exit now, so we do not build an incorrect + // table. + return gif_error("color map not available yet\n", kIncompleteInput); + } + } else { + // Parsing happened in SkCodec::getPixels. + SkASSERT(frameIndex < fReader->imagesCount()); + SkASSERT(frame->reachedStartOfData()); + } + + if (this->xformOnDecode()) { + fXformBuffer.reset(new uint32_t[dstInfo.width()]); + sk_bzero(fXformBuffer.get(), dstInfo.width() * sizeof(uint32_t)); + } + + fTmpBuffer.reset(new uint8_t[dstInfo.minRowBytes()]); + + this->initializeColorTable(dstInfo, frameIndex); + this->initializeSwizzler(dstInfo, frameIndex); + + SkASSERT(fCurrColorTable); + return kSuccess; +} + +void SkGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex) { + const SkGIFFrameContext* frame = fReader->frameContext(frameIndex); + // This is only called by prepareToDecode, which ensures frameIndex is in range. + SkASSERT(frame); + + const int xBegin = frame->xOffset(); + const int xEnd = std::min(frame->frameRect().right(), fReader->screenWidth()); + + // CreateSwizzler only reads left and right of the frame. We cannot use the frame's raw + // frameRect, since it might extend beyond the edge of the frame. + SkIRect swizzleRect = SkIRect::MakeLTRB(xBegin, 0, xEnd, 0); + + SkImageInfo swizzlerInfo = dstInfo; + if (this->colorXform()) { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + } + + // The default Options should be fine: + // - we'll ignore if the memory is zero initialized - unless we're the first frame, this won't + // matter anyway. + // - subsets are not supported for gif + // - the swizzler does not need to know about the frame. + // We may not be able to use the real Options anyway, since getPixels does not store it (due to + // a bug). + fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), fCurrColorTable->readColors(), + swizzlerInfo, Options(), &swizzleRect); + SkASSERT(fSwizzler.get()); +} + +/* + * Initiates the gif decode + */ +SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, + void* pixels, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + Result result = this->prepareToDecode(dstInfo, opts); + switch (result) { + case kSuccess: + break; + case kIncompleteInput: + // onStartIncrementalDecode treats this as incomplete, since it may + // provide more data later, but in this case, no more data will be + // provided, and there is nothing to draw. We also cannot return + // kIncompleteInput, which will make SkCodec attempt to fill + // remaining rows, but that requires an SkSwizzler, which we have + // not created. + return kInvalidInput; + default: + return result; + } + + if (dstInfo.dimensions() != this->dimensions()) { + return gif_error("Scaling not supported.\n", kInvalidScale); + } + + fDst = pixels; + fDstRowBytes = dstRowBytes; + + return this->decodeFrame(true, opts, rowsDecoded); +} + +SkCodec::Result SkGifCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* pixels, size_t dstRowBytes, + const SkCodec::Options& opts) { + Result result = this->prepareToDecode(dstInfo, opts); + if (result != kSuccess) { + return result; + } + + fDst = pixels; + fDstRowBytes = dstRowBytes; + + fFirstCallToIncrementalDecode = true; + + return kSuccess; +} + +SkCodec::Result SkGifCodec::onIncrementalDecode(int* rowsDecoded) { + // It is possible the client has appended more data. Parse, if needed. + const auto& options = this->options(); + const int frameIndex = options.fFrameIndex; + fReader->parse((SkGifImageReader::SkGIFParseQuery) frameIndex); + + const bool firstCallToIncrementalDecode = fFirstCallToIncrementalDecode; + fFirstCallToIncrementalDecode = false; + return this->decodeFrame(firstCallToIncrementalDecode, options, rowsDecoded); +} + +SkCodec::Result SkGifCodec::decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded) { + const SkImageInfo& dstInfo = this->dstInfo(); + const int scaledHeight = get_scaled_dimension(dstInfo.height(), fSwizzler->sampleY()); + + const int frameIndex = opts.fFrameIndex; + SkASSERT(frameIndex < fReader->imagesCount()); + const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex); + if (firstAttempt) { + // rowsDecoded reports how many rows have been initialized, so a layer above + // can fill the rest. In some cases, we fill the background before decoding + // (or it is already filled for us), so we report rowsDecoded to be the full + // height. + bool filledBackground = false; + if (frameContext->getRequiredFrame() == kNoFrame) { + // We may need to clear to transparent for one of the following reasons: + // - The frameRect does not cover the full bounds. haveDecodedRow will + // only draw inside the frameRect, so we need to clear the rest. + // - The frame is interlaced. There is no obvious way to fill + // afterwards for an incomplete image. (FIXME: Does the first pass + // cover all rows? If so, we do not have to fill here.) + // - There is no color table for this frame. In that case will not + // draw anything, so we need to fill. + if (frameContext->frameRect() != this->bounds() + || frameContext->interlaced() || !fCurrColorTableIsReal) { + auto fillInfo = dstInfo.makeWH(fSwizzler->fillWidth(), scaledHeight); + SkSampler::Fill(fillInfo, fDst, fDstRowBytes, opts.fZeroInitialized); + filledBackground = true; + } + } else { + // Not independent. + // SkCodec ensured that the prior frame has been decoded. + filledBackground = true; + } + + fFilledBackground = filledBackground; + if (filledBackground) { + // Report the full (scaled) height, since the client will never need to fill. + fRowsDecoded = scaledHeight; + } else { + // This will be updated by haveDecodedRow. + fRowsDecoded = 0; + } + } + + if (!fCurrColorTableIsReal) { + // Nothing to draw this frame. + return kSuccess; + } + + bool frameDecoded = false; + const bool fatalError = !fReader->decode(frameIndex, &frameDecoded); + if (fatalError || !frameDecoded || fRowsDecoded != scaledHeight) { + if (rowsDecoded) { + *rowsDecoded = fRowsDecoded; + } + if (fatalError) { + return kErrorInInput; + } + return kIncompleteInput; + } + + return kSuccess; +} + +void SkGifCodec::applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const { + if (this->xformOnDecode()) { + SkASSERT(this->colorXform()); + fSwizzler->swizzle(fXformBuffer.get(), src); + + const int xformWidth = get_scaled_dimension(dstInfo.width(), fSwizzler->sampleX()); + this->applyColorXform(dst, fXformBuffer.get(), xformWidth); + } else { + fSwizzler->swizzle(dst, src); + } +} + +template +static void blend_line(void* dstAsVoid, const void* srcAsVoid, int width) { + T* dst = reinterpret_cast(dstAsVoid); + const T* src = reinterpret_cast(srcAsVoid); + while (width --> 0) { + if (*src != 0) { // GIF pixels are either transparent (== 0) or opaque (!= 0). + *dst = *src; + } + src++; + dst++; + } +} + +void SkGifCodec::haveDecodedRow(int frameIndex, const unsigned char* rowBegin, + int rowNumber, int repeatCount, bool writeTransparentPixels) +{ + const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex); + // The pixel data and coordinates supplied to us are relative to the frame's + // origin within the entire image size, i.e. + // (frameContext->xOffset, frameContext->yOffset). There is no guarantee + // that width == (size().width() - frameContext->xOffset), so + // we must ensure we don't run off the end of either the source data or the + // row's X-coordinates. + const int width = frameContext->width(); + const int xBegin = frameContext->xOffset(); + const int yBegin = frameContext->yOffset() + rowNumber; + const int xEnd = std::min(xBegin + width, this->dimensions().width()); + const int yEnd = std::min(yBegin + rowNumber + repeatCount, this->dimensions().height()); + // FIXME: No need to make the checks on width/xBegin/xEnd for every row. We could instead do + // this once in prepareToDecode. + if (!width || (xBegin < 0) || (yBegin < 0) || (xEnd <= xBegin) || (yEnd <= yBegin)) + return; + + // yBegin is the first row in the non-sampled image. dstRow will be the row in the output, + // after potentially scaling it. + int dstRow = yBegin; + + const int sampleY = fSwizzler->sampleY(); + if (sampleY > 1) { + // Check to see whether this row or one that falls in the repeatCount is needed in the + // output. + bool foundNecessaryRow = false; + for (int i = 0; i < repeatCount; i++) { + const int potentialRow = yBegin + i; + if (fSwizzler->rowNeeded(potentialRow)) { + dstRow = potentialRow / sampleY; + const int scaledHeight = get_scaled_dimension(this->dstInfo().height(), sampleY); + if (dstRow >= scaledHeight) { + return; + } + + foundNecessaryRow = true; + repeatCount -= i; + + repeatCount = (repeatCount - 1) / sampleY + 1; + + // Make sure the repeatCount does not take us beyond the end of the dst + if (dstRow + repeatCount > scaledHeight) { + repeatCount = scaledHeight - dstRow; + SkASSERT(repeatCount >= 1); + } + break; + } + } + + if (!foundNecessaryRow) { + return; + } + } else { + // Make sure the repeatCount does not take us beyond the end of the dst + SkASSERT(this->dstInfo().height() >= yBegin); + repeatCount = SkTMin(repeatCount, this->dstInfo().height() - yBegin); + } + + if (!fFilledBackground) { + // At this point, we are definitely going to write the row, so count it towards the number + // of rows decoded. + // We do not consider the repeatCount, which only happens for interlaced, in which case we + // have already set fRowsDecoded to the proper value (reflecting that we have filled the + // background). + fRowsDecoded++; + } + + // decodeFrame will early exit if this is false, so this method will not be + // called. + SkASSERT(fCurrColorTableIsReal); + + // The swizzler takes care of offsetting into the dst width-wise. + void* dstLine = SkTAddOffset(fDst, dstRow * fDstRowBytes); + + // We may or may not need to write transparent pixels to the buffer. + // If we're compositing against a previous image, it's wrong, but if + // we're decoding an interlaced gif and displaying it "Haeberli"-style, + // we must write these for passes beyond the first, or the initial passes + // will "show through" the later ones. + const auto dstInfo = this->dstInfo(); + if (writeTransparentPixels) { + this->applyXformRow(dstInfo, dstLine, rowBegin); + } else { + this->applyXformRow(dstInfo, fTmpBuffer.get(), rowBegin); + + size_t offsetBytes = fSwizzler->swizzleOffsetBytes(); + if (dstInfo.colorType() == kRGBA_F16_SkColorType) { + // Account for the fact that post-swizzling we converted to F16, + // which is twice as wide. + offsetBytes *= 2; + } + const void* src = SkTAddOffset(fTmpBuffer.get(), offsetBytes); + void* dst = SkTAddOffset(dstLine, offsetBytes); + + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + case kRGBA_8888_SkColorType: + blend_line(dst, src, fSwizzler->swizzleWidth()); + break; + case kRGBA_F16_SkColorType: + blend_line(dst, src, fSwizzler->swizzleWidth()); + break; + default: + SkASSERT(false); + return; + } + } + + // Tell the frame to copy the row data if need be. + if (repeatCount > 1) { + const size_t bytesPerPixel = this->dstInfo().bytesPerPixel(); + const size_t bytesToCopy = fSwizzler->swizzleWidth() * bytesPerPixel; + void* copiedLine = SkTAddOffset(dstLine, fSwizzler->swizzleOffsetBytes()); + void* dst = copiedLine; + for (int i = 1; i < repeatCount; i++) { + dst = SkTAddOffset(dst, fDstRowBytes); + memcpy(dst, copiedLine, bytesToCopy); + } + } +} diff --git a/gfx/skia/skia/src/codec/SkGifCodec.h b/gfx/skia/skia/src/codec/SkGifCodec.h new file mode 100644 index 0000000000..1a825fa166 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkGifCodec.h @@ -0,0 +1,156 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkGifCodec_DEFINED +#define SkGifCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +#include "third_party/gif/SkGifImageReader.h" + +/* + * + * This class implements the decoding for gif images + * + */ +class SkGifCodec : public SkCodec { +public: + static bool IsGif(const void*, size_t); + + /* + * Assumes IsGif was called and returned true + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + + // Callback for SkGifImageReader when a row is available. + void haveDecodedRow(int frameIndex, const unsigned char* rowBegin, + int rowNumber, int repeatCount, bool writeTransparentPixels); +protected: + /* + * Performs the full gif decode + */ + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, + int*) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kGIF; + } + + bool onRewind() override; + + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + + Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t, + const SkCodec::Options&) override; + + Result onIncrementalDecode(int*) override; + + const SkFrameHolder* getFrameHolder() const override { + return fReader.get(); + } + +private: + + /* + * Initializes the color table that we will use for decoding. + * + * @param dstInfo Contains the requested dst color type. + * @param frameIndex Frame whose color table to use. + */ + void initializeColorTable(const SkImageInfo& dstInfo, int frameIndex); + + /* + * Does necessary setup, including setting up the color table and swizzler. + */ + Result prepareToDecode(const SkImageInfo& dstInfo, const Options& opts); + + /* + * Initializes the swizzler. + * + * @param dstInfo Output image information. Dimensions may have been + * adjusted if the image frame size does not match the size + * indicated in the header. + * @param frameIndex Which frame we are decoding. This determines the frameRect + * to use. + */ + void initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex); + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler.get(); + } + + /* + * Recursive function to decode a frame. + * + * @param firstAttempt Whether this is the first call to decodeFrame since + * starting. e.g. true in onGetPixels, and true in the + * first call to onIncrementalDecode after calling + * onStartIncrementalDecode. + * When true, this method may have to initialize the + * frame, for example by filling or decoding the prior + * frame. + * @param opts Options for decoding. May be different from + * this->options() for decoding prior frames. Specifies + * the frame to decode and whether the prior frame has + * already been decoded to fDst. If not, and the frame + * is not independent, this method will recursively + * decode the frame it depends on. + * @param rowsDecoded Out-parameter to report the total number of rows + * that have been decoded (or at least written to, if + * it had to fill), including rows decoded by prior + * calls to onIncrementalDecode. + * @return kSuccess if the frame is complete, kIncompleteInput + * otherwise. + */ + Result decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded); + + /* + * Swizzles and color xforms (if necessary) into dst. + */ + void applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const; + + /* + * Creates an instance of the decoder + * Called only by NewFromStream + * Takes ownership of the SkGifImageReader + */ + SkGifCodec(SkEncodedInfo&&, SkGifImageReader*); + + std::unique_ptr fReader; + std::unique_ptr fTmpBuffer; + std::unique_ptr fSwizzler; + sk_sp fCurrColorTable; + // We may create a dummy table if there is not a Map in the input data. In + // that case, we set this value to false, and we can skip a lot of decoding + // work (which would not be meaningful anyway). We create a "fake"/"dummy" + // one in that case, so the client and the swizzler have something to draw. + bool fCurrColorTableIsReal; + // Whether the background was filled. + bool fFilledBackground; + // True on the first call to onIncrementalDecode. This value is passed to + // decodeFrame. + bool fFirstCallToIncrementalDecode; + + void* fDst; + size_t fDstRowBytes; + + // Updated inside haveDecodedRow when rows are decoded, unless we filled + // the background, in which case it is set once and left alone. + int fRowsDecoded; + std::unique_ptr fXformBuffer; + + typedef SkCodec INHERITED; +}; +#endif // SkGifCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkHeifCodec.cpp b/gfx/skia/skia/src/codec/SkHeifCodec.cpp new file mode 100644 index 0000000000..de51f1b408 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkHeifCodec.cpp @@ -0,0 +1,472 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#ifdef SK_HAS_HEIF_LIBRARY +#include "include/codec/SkCodec.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkHeifCodec.h" +#include "src/core/SkEndian.h" + +#define FOURCC(c1, c2, c3, c4) \ + ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4)) + +bool SkHeifCodec::IsHeif(const void* buffer, size_t bytesRead) { + // Parse the ftyp box up to bytesRead to determine if this is HEIF. + // Any valid ftyp box should have at least 8 bytes. + if (bytesRead < 8) { + return false; + } + + uint32_t* ptr = (uint32_t*)buffer; + uint64_t chunkSize = SkEndian_SwapBE32(ptr[0]); + uint32_t chunkType = SkEndian_SwapBE32(ptr[1]); + + if (chunkType != FOURCC('f', 't', 'y', 'p')) { + return false; + } + + int64_t offset = 8; + if (chunkSize == 1) { + // This indicates that the next 8 bytes represent the chunk size, + // and chunk data comes after that. + if (bytesRead < 16) { + return false; + } + auto* chunkSizePtr = SkTAddOffset(buffer, offset); + chunkSize = SkEndian_SwapBE64(*chunkSizePtr); + if (chunkSize < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return false; + } + offset += 8; + } else if (chunkSize < 8) { + // The smallest valid chunk is 8 bytes long. + return false; + } + + if (chunkSize > bytesRead) { + chunkSize = bytesRead; + } + int64_t chunkDataSize = chunkSize - offset; + // It should at least have major brand (4-byte) and minor version (4-bytes). + // The rest of the chunk (if any) is a list of (4-byte) compatible brands. + if (chunkDataSize < 8) { + return false; + } + + uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4; + for (size_t i = 0; i < numCompatibleBrands + 2; ++i) { + if (i == 1) { + // Skip this index, it refers to the minorVersion, + // not a brand. + continue; + } + auto* brandPtr = SkTAddOffset(buffer, offset + 4 * i); + uint32_t brand = SkEndian_SwapBE32(*brandPtr); + if (brand == FOURCC('m', 'i', 'f', '1') || brand == FOURCC('h', 'e', 'i', 'c') + || brand == FOURCC('m', 's', 'f', '1') || brand == FOURCC('h', 'e', 'v', 'c')) { + return true; + } + } + return false; +} + +static SkEncodedOrigin get_orientation(const HeifFrameInfo& frameInfo) { + switch (frameInfo.mRotationAngle) { + case 0: return kTopLeft_SkEncodedOrigin; + case 90: return kRightTop_SkEncodedOrigin; + case 180: return kBottomRight_SkEncodedOrigin; + case 270: return kLeftBottom_SkEncodedOrigin; + } + return kDefault_SkEncodedOrigin; +} + +struct SkHeifStreamWrapper : public HeifStream { + SkHeifStreamWrapper(SkStream* stream) : fStream(stream) {} + + ~SkHeifStreamWrapper() override {} + + size_t read(void* buffer, size_t size) override { + return fStream->read(buffer, size); + } + + bool rewind() override { + return fStream->rewind(); + } + + bool seek(size_t position) override { + return fStream->seek(position); + } + + bool hasLength() const override { + return fStream->hasLength(); + } + + size_t getLength() const override { + return fStream->getLength(); + } + +private: + std::unique_ptr fStream; +}; + +static void releaseProc(const void* ptr, void* context) { + delete reinterpret_cast*>(context); +} + +std::unique_ptr SkHeifCodec::MakeFromStream(std::unique_ptr stream, + SkCodec::SelectionPolicy selectionPolicy, Result* result) { + std::unique_ptr heifDecoder(createHeifDecoder()); + if (heifDecoder.get() == nullptr) { + *result = kInternalError; + return nullptr; + } + + HeifFrameInfo heifInfo; + if (!heifDecoder->init(new SkHeifStreamWrapper(stream.release()), &heifInfo)) { + *result = kInvalidInput; + return nullptr; + } + + size_t frameCount = 1; + if (selectionPolicy == SkCodec::SelectionPolicy::kPreferAnimation) { + HeifFrameInfo sequenceInfo; + if (heifDecoder->getSequenceInfo(&sequenceInfo, &frameCount) && + frameCount > 1) { + heifInfo = std::move(sequenceInfo); + } + } + + std::unique_ptr profile = nullptr; + if (heifInfo.mIccData.size() > 0) { + auto iccData = new std::vector(std::move(heifInfo.mIccData)); + auto icc = SkData::MakeWithProc(iccData->data(), iccData->size(), releaseProc, iccData); + profile = SkEncodedInfo::ICCProfile::Make(std::move(icc)); + } + if (profile && profile->profile()->data_color_space != skcms_Signature_RGB) { + // This will result in sRGB. + profile = nullptr; + } + + SkEncodedInfo info = SkEncodedInfo::Make(heifInfo.mWidth, heifInfo.mHeight, + SkEncodedInfo::kYUV_Color, SkEncodedInfo::kOpaque_Alpha, 8, std::move(profile)); + SkEncodedOrigin orientation = get_orientation(heifInfo); + + *result = kSuccess; + return std::unique_ptr(new SkHeifCodec( + std::move(info), heifDecoder.release(), orientation, frameCount > 1)); +} + +SkHeifCodec::SkHeifCodec( + SkEncodedInfo&& info, + HeifDecoder* heifDecoder, + SkEncodedOrigin origin, + bool useAnimation) + : INHERITED(std::move(info), skcms_PixelFormat_RGBA_8888, nullptr, origin) + , fHeifDecoder(heifDecoder) + , fSwizzleSrcRow(nullptr) + , fColorXformSrcRow(nullptr) + , fUseAnimation(useAnimation) +{} + +bool SkHeifCodec::conversionSupported(const SkImageInfo& dstInfo, bool srcIsOpaque, + bool needsColorXform) { + SkASSERT(srcIsOpaque); + + if (kUnknown_SkAlphaType == dstInfo.alphaType()) { + return false; + } + + if (kOpaque_SkAlphaType != dstInfo.alphaType()) { + SkCodecPrintf("Warning: an opaque image should be decoded as opaque " + "- it is being decoded as non-opaque, which will draw slower\n"); + } + + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); + + case kBGRA_8888_SkColorType: + return fHeifDecoder->setOutputColor(kHeifColorFormat_BGRA_8888); + + case kRGB_565_SkColorType: + if (needsColorXform) { + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); + } else { + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGB565); + } + + case kRGBA_F16_SkColorType: + SkASSERT(needsColorXform); + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); + + default: + return false; + } +} + +int SkHeifCodec::readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, + const Options& opts) { + // When fSwizzleSrcRow is non-null, it means that we need to swizzle. In this case, + // we will always decode into fSwizzlerSrcRow before swizzling into the next buffer. + // We can never swizzle "in place" because the swizzler may perform sampling and/or + // subsetting. + // When fColorXformSrcRow is non-null, it means that we need to color xform and that + // we cannot color xform "in place" (many times we can, but not when the dst is F16). + // In this case, we will color xform from fColorXformSrcRow into the dst. + uint8_t* decodeDst = (uint8_t*) dst; + uint32_t* swizzleDst = (uint32_t*) dst; + size_t decodeDstRowBytes = rowBytes; + size_t swizzleDstRowBytes = rowBytes; + int dstWidth = opts.fSubset ? opts.fSubset->width() : dstInfo.width(); + if (fSwizzleSrcRow && fColorXformSrcRow) { + decodeDst = fSwizzleSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } else if (fColorXformSrcRow) { + decodeDst = (uint8_t*) fColorXformSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + } else if (fSwizzleSrcRow) { + decodeDst = fSwizzleSrcRow; + decodeDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } + + for (int y = 0; y < count; y++) { + if (!fHeifDecoder->getScanline(decodeDst)) { + return y; + } + + if (fSwizzler) { + fSwizzler->swizzle(swizzleDst, decodeDst); + } + + if (this->colorXform()) { + this->applyColorXform(dst, swizzleDst, dstWidth); + dst = SkTAddOffset(dst, rowBytes); + } + + decodeDst = SkTAddOffset(decodeDst, decodeDstRowBytes); + swizzleDst = SkTAddOffset(swizzleDst, swizzleDstRowBytes); + } + + return count; +} + +int SkHeifCodec::onGetFrameCount() { + if (!fUseAnimation) { + return 1; + } + + if (fFrameHolder.size() == 0) { + size_t frameCount; + HeifFrameInfo frameInfo; + if (!fHeifDecoder->getSequenceInfo(&frameInfo, &frameCount) + || frameCount <= 1) { + fUseAnimation = false; + return 1; + } + fFrameHolder.reserve(frameCount); + for (size_t i = 0; i < frameCount; i++) { + Frame* frame = fFrameHolder.appendNewFrame(); + frame->setXYWH(0, 0, frameInfo.mWidth, frameInfo.mHeight); + frame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep); + // Currently we don't know the duration until the frame is actually + // decoded (onGetFrameInfo is also called before frame is decoded). + // For now, fill it base on the value reported for the sequence. + frame->setDuration(frameInfo.mDurationUs / 1000); + frame->setRequiredFrame(SkCodec::kNoFrame); + frame->setHasAlpha(false); + } + } + + return fFrameHolder.size(); +} + +const SkFrame* SkHeifCodec::FrameHolder::onGetFrame(int i) const { + return static_cast(this->frame(i)); +} + +SkHeifCodec::Frame* SkHeifCodec::FrameHolder::appendNewFrame() { + const int i = this->size(); + fFrames.emplace_back(i); // TODO: need to handle frame duration here + return &fFrames[i]; +} + +const SkHeifCodec::Frame* SkHeifCodec::FrameHolder::frame(int i) const { + SkASSERT(i >= 0 && i < this->size()); + return &fFrames[i]; +} + +SkHeifCodec::Frame* SkHeifCodec::FrameHolder::editFrameAt(int i) { + SkASSERT(i >= 0 && i < this->size()); + return &fFrames[i]; +} + +bool SkHeifCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { + if (i >= fFrameHolder.size()) { + return false; + } + + const Frame* frame = fFrameHolder.frame(i); + if (!frame) { + return false; + } + + if (frameInfo) { + frameInfo->fRequiredFrame = SkCodec::kNoFrame; + frameInfo->fDuration = frame->getDuration(); + frameInfo->fFullyReceived = true; + frameInfo->fAlphaType = kOpaque_SkAlphaType; + frameInfo->fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep; + } + + return true; +} + +int SkHeifCodec::onGetRepetitionCount() { + return kRepetitionCountInfinite; +} + +/* + * Performs the heif decode + */ +SkCodec::Result SkHeifCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& options, + int* rowsDecoded) { + if (options.fSubset) { + // Not supporting subsets on this path for now. + // TODO: if the heif has tiles, we can support subset here, but + // need to retrieve tile config from metadata retriever first. + return kUnimplemented; + } + + bool success; + if (fUseAnimation) { + success = fHeifDecoder->decodeSequence(options.fFrameIndex, &fFrameInfo); + fFrameHolder.editFrameAt(options.fFrameIndex)->setDuration( + fFrameInfo.mDurationUs / 1000); + } else { + success = fHeifDecoder->decode(&fFrameInfo); + } + + if (!success) { + return kInvalidInput; + } + + fSwizzler.reset(nullptr); + this->allocateStorage(dstInfo); + + int rows = this->readRows(dstInfo, dst, dstRowBytes, dstInfo.height(), options); + if (rows < dstInfo.height()) { + *rowsDecoded = rows; + return kIncompleteInput; + } + + return kSuccess; +} + +void SkHeifCodec::allocateStorage(const SkImageInfo& dstInfo) { + int dstWidth = dstInfo.width(); + + size_t swizzleBytes = 0; + if (fSwizzler) { + swizzleBytes = fFrameInfo.mBytesPerPixel * fFrameInfo.mWidth; + dstWidth = fSwizzler->swizzleWidth(); + SkASSERT(!this->colorXform() || SkIsAlign4(swizzleBytes)); + } + + size_t xformBytes = 0; + if (this->colorXform() && (kRGBA_F16_SkColorType == dstInfo.colorType() || + kRGB_565_SkColorType == dstInfo.colorType())) { + xformBytes = dstWidth * sizeof(uint32_t); + } + + size_t totalBytes = swizzleBytes + xformBytes; + fStorage.reset(totalBytes); + if (totalBytes > 0) { + fSwizzleSrcRow = (swizzleBytes > 0) ? fStorage.get() : nullptr; + fColorXformSrcRow = (xformBytes > 0) ? + SkTAddOffset(fStorage.get(), swizzleBytes) : nullptr; + } +} + +void SkHeifCodec::initializeSwizzler( + const SkImageInfo& dstInfo, const Options& options) { + SkImageInfo swizzlerDstInfo = dstInfo; + if (this->colorXform()) { + // The color xform will be expecting RGBA 8888 input. + swizzlerDstInfo = swizzlerDstInfo.makeColorType(kRGBA_8888_SkColorType); + } + + int srcBPP = 4; + if (dstInfo.colorType() == kRGB_565_SkColorType && !this->colorXform()) { + srcBPP = 2; + } + + fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerDstInfo, options); + SkASSERT(fSwizzler); +} + +SkSampler* SkHeifCodec::getSampler(bool createIfNecessary) { + if (!createIfNecessary || fSwizzler) { + SkASSERT(!fSwizzler || (fSwizzleSrcRow && fStorage.get() == fSwizzleSrcRow)); + return fSwizzler.get(); + } + + this->initializeSwizzler(this->dstInfo(), this->options()); + this->allocateStorage(this->dstInfo()); + return fSwizzler.get(); +} + +bool SkHeifCodec::onRewind() { + fSwizzler.reset(nullptr); + fSwizzleSrcRow = nullptr; + fColorXformSrcRow = nullptr; + fStorage.reset(); + + return true; +} + +SkCodec::Result SkHeifCodec::onStartScanlineDecode( + const SkImageInfo& dstInfo, const Options& options) { + // TODO: For now, just decode the whole thing even when there is a subset. + // If the heif image has tiles, we could potentially do this much faster, + // but the tile configuration needs to be retrieved from the metadata. + if (!fHeifDecoder->decode(&fFrameInfo)) { + return kInvalidInput; + } + + if (options.fSubset) { + this->initializeSwizzler(dstInfo, options); + } else { + fSwizzler.reset(nullptr); + } + + this->allocateStorage(dstInfo); + + return kSuccess; +} + +int SkHeifCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { + return this->readRows(this->dstInfo(), dst, dstRowBytes, count, this->options()); +} + +bool SkHeifCodec::onSkipScanlines(int count) { + return count == (int) fHeifDecoder->skipScanlines(count); +} + +#endif // SK_HAS_HEIF_LIBRARY diff --git a/gfx/skia/skia/src/codec/SkHeifCodec.h b/gfx/skia/skia/src/codec/SkHeifCodec.h new file mode 100644 index 0000000000..bbcf722295 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkHeifCodec.h @@ -0,0 +1,128 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkHeifCodec_DEFINED +#define SkHeifCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "src/codec/SkFrameHolder.h" +#include "src/codec/SkSwizzler.h" + +#if __has_include("HeifDecoderAPI.h") + #include "HeifDecoderAPI.h" +#else + #include "src/codec/SkStubHeifDecoderAPI.h" +#endif + +class SkHeifCodec : public SkCodec { +public: + static bool IsHeif(const void*, size_t); + + /* + * Assumes IsHeif was called and returned true. + */ + static std::unique_ptr MakeFromStream( + std::unique_ptr, SkCodec::SelectionPolicy selectionPolicy, Result*); + +protected: + + Result onGetPixels( + const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& options, + int* rowsDecoded) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kHEIF; + } + + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + const SkFrameHolder* getFrameHolder() const override { + return &fFrameHolder; + } + + bool conversionSupported(const SkImageInfo&, bool, bool) override; + + bool onRewind() override; + +private: + /* + * Creates an instance of the decoder + * Called only by NewFromStream + */ + SkHeifCodec(SkEncodedInfo&&, HeifDecoder*, SkEncodedOrigin, bool animation); + + void initializeSwizzler(const SkImageInfo& dstInfo, const Options& options); + void allocateStorage(const SkImageInfo& dstInfo); + int readRows(const SkImageInfo& dstInfo, void* dst, + size_t rowBytes, int count, const Options&); + + /* + * Scanline decoding. + */ + SkSampler* getSampler(bool createIfNecessary) override; + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + bool onSkipScanlines(int count) override; + + std::unique_ptr fHeifDecoder; + HeifFrameInfo fFrameInfo; + SkAutoTMalloc fStorage; + uint8_t* fSwizzleSrcRow; + uint32_t* fColorXformSrcRow; + + std::unique_ptr fSwizzler; + bool fUseAnimation; + + class Frame : public SkFrame { + public: + Frame(int i) : INHERITED(i) {} + + protected: + SkEncodedInfo::Alpha onReportedAlpha() const override { + return SkEncodedInfo::Alpha::kOpaque_Alpha; + } + + private: + typedef SkFrame INHERITED; + }; + + class FrameHolder : public SkFrameHolder { + public: + ~FrameHolder() override {} + void setScreenSize(int w, int h) { + fScreenWidth = w; + fScreenHeight = h; + } + Frame* appendNewFrame(); + const Frame* frame(int i) const; + Frame* editFrameAt(int i); + int size() const { + return static_cast(fFrames.size()); + } + void reserve(int size) { + fFrames.reserve(size); + } + + protected: + const SkFrame* onGetFrame(int i) const override; + + private: + std::vector fFrames; + }; + + FrameHolder fFrameHolder; + typedef SkCodec INHERITED; +}; + +#endif // SkHeifCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkIcoCodec.cpp b/gfx/skia/skia/src/codec/SkIcoCodec.cpp new file mode 100644 index 0000000000..e70a9cded4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkIcoCodec.cpp @@ -0,0 +1,384 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTDArray.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkIcoCodec.h" +#include "src/codec/SkPngCodec.h" +#include "src/core/SkTSort.h" + +/* + * Checks the start of the stream to see if the image is an Ico or Cur + */ +bool SkIcoCodec::IsIco(const void* buffer, size_t bytesRead) { + const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' }; + const char curSig[] = { '\x00', '\x00', '\x02', '\x00' }; + return bytesRead >= sizeof(icoSig) && + (!memcmp(buffer, icoSig, sizeof(icoSig)) || + !memcmp(buffer, curSig, sizeof(curSig))); +} + +std::unique_ptr SkIcoCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + // Header size constants + constexpr uint32_t kIcoDirectoryBytes = 6; + constexpr uint32_t kIcoDirEntryBytes = 16; + + // Read the directory header + std::unique_ptr dirBuffer(new uint8_t[kIcoDirectoryBytes]); + if (stream->read(dirBuffer.get(), kIcoDirectoryBytes) != kIcoDirectoryBytes) { + SkCodecPrintf("Error: unable to read ico directory header.\n"); + *result = kIncompleteInput; + return nullptr; + } + + // Process the directory header + const uint16_t numImages = get_short(dirBuffer.get(), 4); + if (0 == numImages) { + SkCodecPrintf("Error: No images embedded in ico.\n"); + *result = kInvalidInput; + return nullptr; + } + + // This structure is used to represent the vital information about entries + // in the directory header. We will obtain this information for each + // directory entry. + struct Entry { + uint32_t offset; + uint32_t size; + }; + SkAutoFree dirEntryBuffer(sk_malloc_canfail(sizeof(Entry) * numImages)); + if (!dirEntryBuffer) { + SkCodecPrintf("Error: OOM allocating ICO directory for %i images.\n", + numImages); + *result = kInternalError; + return nullptr; + } + auto* directoryEntries = reinterpret_cast(dirEntryBuffer.get()); + + // Iterate over directory entries + for (uint32_t i = 0; i < numImages; i++) { + uint8_t entryBuffer[kIcoDirEntryBytes]; + if (stream->read(entryBuffer, kIcoDirEntryBytes) != kIcoDirEntryBytes) { + SkCodecPrintf("Error: Dir entries truncated in ico.\n"); + *result = kIncompleteInput; + return nullptr; + } + + // The directory entry contains information such as width, height, + // bits per pixel, and number of colors in the color palette. We will + // ignore these fields since they are repeated in the header of the + // embedded image. In the event of an inconsistency, we would always + // defer to the value in the embedded header anyway. + + // Specifies the size of the embedded image, including the header + uint32_t size = get_int(entryBuffer, 8); + + // Specifies the offset of the embedded image from the start of file. + // It does not indicate the start of the pixel data, but rather the + // start of the embedded image header. + uint32_t offset = get_int(entryBuffer, 12); + + // Save the vital fields + directoryEntries[i].offset = offset; + directoryEntries[i].size = size; + } + + // Default Result, if no valid embedded codecs are found. + *result = kInvalidInput; + + // It is "customary" that the embedded images will be stored in order of + // increasing offset. However, the specification does not indicate that + // they must be stored in this order, so we will not trust that this is the + // case. Here we sort the embedded images by increasing offset. + struct EntryLessThan { + bool operator() (Entry a, Entry b) const { + return a.offset < b.offset; + } + }; + EntryLessThan lessThan; + SkTQSort(directoryEntries, &directoryEntries[numImages - 1], lessThan); + + // Now will construct a candidate codec for each of the embedded images + uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes; + std::unique_ptr, true>> codecs( + new SkTArray, true>(numImages)); + for (uint32_t i = 0; i < numImages; i++) { + uint32_t offset = directoryEntries[i].offset; + uint32_t size = directoryEntries[i].size; + + // Ensure that the offset is valid + if (offset < bytesRead) { + SkCodecPrintf("Warning: invalid ico offset.\n"); + continue; + } + + // If we cannot skip, assume we have reached the end of the stream and + // stop trying to make codecs + if (stream->skip(offset - bytesRead) != offset - bytesRead) { + SkCodecPrintf("Warning: could not skip to ico offset.\n"); + break; + } + bytesRead = offset; + + // Create a new stream for the embedded codec + SkAutoFree buffer(sk_malloc_canfail(size)); + if (!buffer) { + SkCodecPrintf("Warning: OOM trying to create embedded stream.\n"); + break; + } + + if (stream->read(buffer.get(), size) != size) { + SkCodecPrintf("Warning: could not create embedded stream.\n"); + *result = kIncompleteInput; + break; + } + + sk_sp data(SkData::MakeFromMalloc(buffer.release(), size)); + auto embeddedStream = SkMemoryStream::Make(data); + bytesRead += size; + + // Check if the embedded codec is bmp or png and create the codec + std::unique_ptr codec; + Result dummyResult; + if (SkPngCodec::IsPng((const char*) data->bytes(), data->size())) { + codec = SkPngCodec::MakeFromStream(std::move(embeddedStream), &dummyResult); + } else { + codec = SkBmpCodec::MakeFromIco(std::move(embeddedStream), &dummyResult); + } + + // Save a valid codec + if (nullptr != codec) { + codecs->push_back().reset(codec.release()); + } + } + + // Recognize if there are no valid codecs + if (0 == codecs->count()) { + SkCodecPrintf("Error: could not find any valid embedded ico codecs.\n"); + return nullptr; + } + + // Use the largest codec as a "suggestion" for image info + size_t maxSize = 0; + int maxIndex = 0; + for (int i = 0; i < codecs->count(); i++) { + SkImageInfo info = codecs->operator[](i)->getInfo(); + size_t size = info.computeMinByteSize(); + + if (size > maxSize) { + maxSize = size; + maxIndex = i; + } + } + + auto maxInfo = codecs->operator[](maxIndex)->getEncodedInfo().copy(); + + *result = kSuccess; + // The original stream is no longer needed, because the embedded codecs own their + // own streams. + return std::unique_ptr(new SkIcoCodec(std::move(maxInfo), codecs.release())); +} + +SkIcoCodec::SkIcoCodec(SkEncodedInfo&& info, SkTArray, true>* codecs) + // The source skcms_PixelFormat will not be used. The embedded + // codec's will be used instead. + : INHERITED(std::move(info), skcms_PixelFormat(), nullptr) + , fEmbeddedCodecs(codecs) + , fCurrCodec(nullptr) +{} + +/* + * Chooses the best dimensions given the desired scale + */ +SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const { + // We set the dimensions to the largest candidate image by default. + // Regardless of the scale request, this is the largest image that we + // will decode. + int origWidth = this->dimensions().width(); + int origHeight = this->dimensions().height(); + float desiredSize = desiredScale * origWidth * origHeight; + // At least one image will have smaller error than this initial value + float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f; + int32_t minIndex = -1; + for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) { + auto dimensions = fEmbeddedCodecs->operator[](i)->dimensions(); + int width = dimensions.width(); + int height = dimensions.height(); + float error = SkTAbs(((float) (width * height)) - desiredSize); + if (error < minError) { + minError = error; + minIndex = i; + } + } + SkASSERT(minIndex >= 0); + + return fEmbeddedCodecs->operator[](minIndex)->dimensions(); +} + +int SkIcoCodec::chooseCodec(const SkISize& requestedSize, int startIndex) { + SkASSERT(startIndex >= 0); + + // FIXME: Cache the index from onGetScaledDimensions? + for (int i = startIndex; i < fEmbeddedCodecs->count(); i++) { + if (fEmbeddedCodecs->operator[](i)->dimensions() == requestedSize) { + return i; + } + } + + return -1; +} + +bool SkIcoCodec::onDimensionsSupported(const SkISize& dim) { + return this->chooseCodec(dim, 0) >= 0; +} + +/* + * Initiates the Ico decode + */ +SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + if (opts.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + int index = 0; + SkCodec::Result result = kInvalidScale; + while (true) { + index = this->chooseCodec(dstInfo.dimensions(), index); + if (index < 0) { + break; + } + + SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get(); + result = embeddedCodec->getPixels(dstInfo, dst, dstRowBytes, &opts); + switch (result) { + case kSuccess: + case kIncompleteInput: + // The embedded codec will handle filling incomplete images, so we will indicate + // that all of the rows are initialized. + *rowsDecoded = dstInfo.height(); + return result; + default: + // Continue trying to find a valid embedded codec on a failed decode. + break; + } + + index++; + } + + SkCodecPrintf("Error: No matching candidate image in ico.\n"); + return result; +} + +SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + int index = 0; + SkCodec::Result result = kInvalidScale; + while (true) { + index = this->chooseCodec(dstInfo.dimensions(), index); + if (index < 0) { + break; + } + + SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get(); + result = embeddedCodec->startScanlineDecode(dstInfo, &options); + if (kSuccess == result) { + fCurrCodec = embeddedCodec; + return result; + } + + index++; + } + + SkCodecPrintf("Error: No matching candidate image in ico.\n"); + return result; +} + +int SkIcoCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { + SkASSERT(fCurrCodec); + return fCurrCodec->getScanlines(dst, count, rowBytes); +} + +bool SkIcoCodec::onSkipScanlines(int count) { + SkASSERT(fCurrCodec); + return fCurrCodec->skipScanlines(count); +} + +SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* pixels, size_t rowBytes, const SkCodec::Options& options) { + int index = 0; + while (true) { + index = this->chooseCodec(dstInfo.dimensions(), index); + if (index < 0) { + break; + } + + SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get(); + switch (embeddedCodec->startIncrementalDecode(dstInfo, + pixels, rowBytes, &options)) { + case kSuccess: + fCurrCodec = embeddedCodec; + return kSuccess; + case kUnimplemented: + // FIXME: embeddedCodec is a BMP. If scanline decoding would work, + // return kUnimplemented so that SkSampledCodec will fall through + // to use the scanline decoder. + // Note that calling startScanlineDecode will require an extra + // rewind. The embedded codec has an SkMemoryStream, which is + // cheap to rewind, though it will do extra work re-reading the + // header. + // Also note that we pass nullptr for Options. This is because + // Options that are valid for incremental decoding may not be + // valid for scanline decoding. + // Once BMP supports incremental decoding this workaround can go + // away. + if (embeddedCodec->startScanlineDecode(dstInfo) == kSuccess) { + return kUnimplemented; + } + // Move on to the next embedded codec. + break; + default: + break; + } + + index++; + } + + SkCodecPrintf("Error: No matching candidate image in ico.\n"); + return kInvalidScale; +} + +SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) { + SkASSERT(fCurrCodec); + return fCurrCodec->incrementalDecode(rowsDecoded); +} + +SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const { + // FIXME: This function will possibly return the wrong value if it is called + // before startScanlineDecode()/startIncrementalDecode(). + if (fCurrCodec) { + return fCurrCodec->getScanlineOrder(); + } + + return INHERITED::onGetScanlineOrder(); +} + +SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) { + if (fCurrCodec) { + return fCurrCodec->getSampler(createIfNecessary); + } + + return nullptr; +} diff --git a/gfx/skia/skia/src/codec/SkIcoCodec.h b/gfx/skia/skia/src/codec/SkIcoCodec.h new file mode 100644 index 0000000000..c1b27dc50d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkIcoCodec.h @@ -0,0 +1,99 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIcoCodec_DEFINED +#define SkIcoCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" + +/* + * This class implements the decoding for bmp images + */ +class SkIcoCodec : public SkCodec { +public: + static bool IsIco(const void*, size_t); + + /* + * Assumes IsIco was called and returned true + * Creates an Ico decoder + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + +protected: + + /* + * Chooses the best dimensions given the desired scale + */ + SkISize onGetScaledDimensions(float desiredScale) const override; + + bool onDimensionsSupported(const SkISize&) override; + + /* + * Initiates the Ico decode + */ + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + int*) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kICO; + } + + SkScanlineOrder onGetScanlineOrder() const override; + + bool conversionSupported(const SkImageInfo&, bool, bool) override { + // This will be checked by the embedded codec. + return true; + } + + // Handled by the embedded codec. + bool usesColorXform() const override { return false; } +private: + + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + + bool onSkipScanlines(int count) override; + + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const SkCodec::Options&) override; + + Result onIncrementalDecode(int* rowsDecoded) override; + + SkSampler* getSampler(bool createIfNecessary) override; + + /* + * Searches fEmbeddedCodecs for a codec that matches requestedSize. + * The search starts at startIndex and ends when an appropriate codec + * is found, or we have reached the end of the array. + * + * @return the index of the matching codec or -1 if there is no + * matching codec between startIndex and the end of + * the array. + */ + int chooseCodec(const SkISize& requestedSize, int startIndex); + + /* + * Constructor called by NewFromStream + * @param embeddedCodecs codecs for the embedded images, takes ownership + */ + SkIcoCodec(SkEncodedInfo&& info, SkTArray, true>* embeddedCodecs); + + std::unique_ptr, true>> fEmbeddedCodecs; + + // fCurrCodec is owned by this class, but should not be an + // std::unique_ptr. It will be deleted by the destructor of fEmbeddedCodecs. + SkCodec* fCurrCodec; + + typedef SkCodec INHERITED; +}; +#endif // SkIcoCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkJpegCodec.cpp b/gfx/skia/skia/src/codec/SkJpegCodec.cpp new file mode 100644 index 0000000000..45bd35dd5d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegCodec.cpp @@ -0,0 +1,974 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkJpegCodec.h" + +#include "include/codec/SkCodec.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkJpegDecoderMgr.h" +#include "src/codec/SkParseEncodedOrigin.h" +#include "src/pdf/SkJpegInfo.h" + +// stdio is needed for libjpeg-turbo +#include +#include "src/codec/SkJpegUtility.h" + +// This warning triggers false postives way too often in here. +#if defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic ignored "-Wclobbered" +#endif + +extern "C" { + #include "jerror.h" + #include "jpeglib.h" +} + +bool SkJpegCodec::IsJpeg(const void* buffer, size_t bytesRead) { + constexpr uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF }; + return bytesRead >= 3 && !memcmp(buffer, jpegSig, sizeof(jpegSig)); +} + +const uint32_t kExifHeaderSize = 14; +const uint32_t kExifMarker = JPEG_APP0 + 1; + +static bool is_orientation_marker(jpeg_marker_struct* marker, SkEncodedOrigin* orientation) { + if (kExifMarker != marker->marker || marker->data_length < kExifHeaderSize) { + return false; + } + + constexpr uint8_t kExifSig[] { 'E', 'x', 'i', 'f', '\0' }; + if (memcmp(marker->data, kExifSig, sizeof(kExifSig))) { + return false; + } + + // Account for 'E', 'x', 'i', 'f', '\0', ''. + constexpr size_t kOffset = 6; + return SkParseEncodedOrigin(marker->data + kOffset, marker->data_length - kOffset, + orientation); +} + +static SkEncodedOrigin get_exif_orientation(jpeg_decompress_struct* dinfo) { + SkEncodedOrigin orientation; + for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) { + if (is_orientation_marker(marker, &orientation)) { + return orientation; + } + } + + return kDefault_SkEncodedOrigin; +} + +static bool is_icc_marker(jpeg_marker_struct* marker) { + if (kICCMarker != marker->marker || marker->data_length < kICCMarkerHeaderSize) { + return false; + } + + return !memcmp(marker->data, kICCSig, sizeof(kICCSig)); +} + +/* + * ICC profiles may be stored using a sequence of multiple markers. We obtain the ICC profile + * in two steps: + * (1) Discover all ICC profile markers and verify that they are numbered properly. + * (2) Copy the data from each marker into a contiguous ICC profile. + */ +static std::unique_ptr read_color_profile(jpeg_decompress_struct* dinfo) +{ + // Note that 256 will be enough storage space since each markerIndex is stored in 8-bits. + jpeg_marker_struct* markerSequence[256]; + memset(markerSequence, 0, sizeof(markerSequence)); + uint8_t numMarkers = 0; + size_t totalBytes = 0; + + // Discover any ICC markers and verify that they are numbered properly. + for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) { + if (is_icc_marker(marker)) { + // Verify that numMarkers is valid and consistent. + if (0 == numMarkers) { + numMarkers = marker->data[13]; + if (0 == numMarkers) { + SkCodecPrintf("ICC Profile Error: numMarkers must be greater than zero.\n"); + return nullptr; + } + } else if (numMarkers != marker->data[13]) { + SkCodecPrintf("ICC Profile Error: numMarkers must be consistent.\n"); + return nullptr; + } + + // Verify that the markerIndex is valid and unique. Note that zero is not + // a valid index. + uint8_t markerIndex = marker->data[12]; + if (markerIndex == 0 || markerIndex > numMarkers) { + SkCodecPrintf("ICC Profile Error: markerIndex is invalid.\n"); + return nullptr; + } + if (markerSequence[markerIndex]) { + SkCodecPrintf("ICC Profile Error: Duplicate value of markerIndex.\n"); + return nullptr; + } + markerSequence[markerIndex] = marker; + SkASSERT(marker->data_length >= kICCMarkerHeaderSize); + totalBytes += marker->data_length - kICCMarkerHeaderSize; + } + } + + if (0 == totalBytes) { + // No non-empty ICC profile markers were found. + return nullptr; + } + + // Combine the ICC marker data into a contiguous profile. + sk_sp iccData = SkData::MakeUninitialized(totalBytes); + void* dst = iccData->writable_data(); + for (uint32_t i = 1; i <= numMarkers; i++) { + jpeg_marker_struct* marker = markerSequence[i]; + if (!marker) { + SkCodecPrintf("ICC Profile Error: Missing marker %d of %d.\n", i, numMarkers); + return nullptr; + } + + void* src = SkTAddOffset(marker->data, kICCMarkerHeaderSize); + size_t bytes = marker->data_length - kICCMarkerHeaderSize; + memcpy(dst, src, bytes); + dst = SkTAddOffset(dst, bytes); + } + + return SkEncodedInfo::ICCProfile::Make(std::move(iccData)); +} + +SkCodec::Result SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, + JpegDecoderMgr** decoderMgrOut, + std::unique_ptr defaultColorProfile) { + + // Create a JpegDecoderMgr to own all of the decompress information + std::unique_ptr decoderMgr(new JpegDecoderMgr(stream)); + + // libjpeg errors will be caught and reported here + skjpeg_error_mgr::AutoPushJmpBuf jmp(decoderMgr->errorMgr()); + if (setjmp(jmp)) { + return decoderMgr->returnFailure("ReadHeader", kInvalidInput); + } + + // Initialize the decompress info and the source manager + decoderMgr->init(); + auto* dinfo = decoderMgr->dinfo(); + + // Instruct jpeg library to save the markers that we care about. Since + // the orientation and color profile will not change, we can skip this + // step on rewinds. + if (codecOut) { + jpeg_save_markers(dinfo, kExifMarker, 0xFFFF); + jpeg_save_markers(dinfo, kICCMarker, 0xFFFF); + } + + // Read the jpeg header + switch (jpeg_read_header(dinfo, true)) { + case JPEG_HEADER_OK: + break; + case JPEG_SUSPENDED: + return decoderMgr->returnFailure("ReadHeader", kIncompleteInput); + default: + return decoderMgr->returnFailure("ReadHeader", kInvalidInput); + } + + if (codecOut) { + // Get the encoded color type + SkEncodedInfo::Color color; + if (!decoderMgr->getEncodedColor(&color)) { + return kInvalidInput; + } + + SkEncodedOrigin orientation = get_exif_orientation(dinfo); + auto profile = read_color_profile(dinfo); + if (profile) { + auto type = profile->profile()->data_color_space; + switch (decoderMgr->dinfo()->jpeg_color_space) { + case JCS_CMYK: + case JCS_YCCK: + if (type != skcms_Signature_CMYK) { + profile = nullptr; + } + break; + case JCS_GRAYSCALE: + if (type != skcms_Signature_Gray && + type != skcms_Signature_RGB) + { + profile = nullptr; + } + break; + default: + if (type != skcms_Signature_RGB) { + profile = nullptr; + } + break; + } + } + if (!profile) { + profile = std::move(defaultColorProfile); + } + + SkEncodedInfo info = SkEncodedInfo::Make(dinfo->image_width, dinfo->image_height, + color, SkEncodedInfo::kOpaque_Alpha, 8, + std::move(profile)); + + SkJpegCodec* codec = new SkJpegCodec(std::move(info), std::unique_ptr(stream), + decoderMgr.release(), orientation); + *codecOut = codec; + } else { + SkASSERT(nullptr != decoderMgrOut); + *decoderMgrOut = decoderMgr.release(); + } + return kSuccess; +} + +std::unique_ptr SkJpegCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + return SkJpegCodec::MakeFromStream(std::move(stream), result, nullptr); +} + +std::unique_ptr SkJpegCodec::MakeFromStream(std::unique_ptr stream, + Result* result, std::unique_ptr defaultColorProfile) { + SkCodec* codec = nullptr; + *result = ReadHeader(stream.get(), &codec, nullptr, std::move(defaultColorProfile)); + if (kSuccess == *result) { + // Codec has taken ownership of the stream, we do not need to delete it + SkASSERT(codec); + stream.release(); + return std::unique_ptr(codec); + } + return nullptr; +} + +SkJpegCodec::SkJpegCodec(SkEncodedInfo&& info, std::unique_ptr stream, + JpegDecoderMgr* decoderMgr, SkEncodedOrigin origin) + : INHERITED(std::move(info), skcms_PixelFormat_RGBA_8888, std::move(stream), origin) + , fDecoderMgr(decoderMgr) + , fReadyState(decoderMgr->dinfo()->global_state) + , fSwizzleSrcRow(nullptr) + , fColorXformSrcRow(nullptr) + , fSwizzlerSubset(SkIRect::MakeEmpty()) +{} + +/* + * Return the row bytes of a particular image type and width + */ +static size_t get_row_bytes(const j_decompress_ptr dinfo) { + const size_t colorBytes = (dinfo->out_color_space == JCS_RGB565) ? 2 : + dinfo->out_color_components; + return dinfo->output_width * colorBytes; + +} + +/* + * Calculate output dimensions based on the provided factors. + * + * Not to be used on the actual jpeg_decompress_struct used for decoding, since it will + * incorrectly modify num_components. + */ +void calc_output_dimensions(jpeg_decompress_struct* dinfo, unsigned int num, unsigned int denom) { + dinfo->num_components = 0; + dinfo->scale_num = num; + dinfo->scale_denom = denom; + jpeg_calc_output_dimensions(dinfo); +} + +/* + * Return a valid set of output dimensions for this decoder, given an input scale + */ +SkISize SkJpegCodec::onGetScaledDimensions(float desiredScale) const { + // libjpeg-turbo supports scaling by 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, and 1/1, so we will + // support these as well + unsigned int num; + unsigned int denom = 8; + if (desiredScale >= 0.9375) { + num = 8; + } else if (desiredScale >= 0.8125) { + num = 7; + } else if (desiredScale >= 0.6875f) { + num = 6; + } else if (desiredScale >= 0.5625f) { + num = 5; + } else if (desiredScale >= 0.4375f) { + num = 4; + } else if (desiredScale >= 0.3125f) { + num = 3; + } else if (desiredScale >= 0.1875f) { + num = 2; + } else { + num = 1; + } + + // Set up a fake decompress struct in order to use libjpeg to calculate output dimensions + jpeg_decompress_struct dinfo; + sk_bzero(&dinfo, sizeof(dinfo)); + dinfo.image_width = this->dimensions().width(); + dinfo.image_height = this->dimensions().height(); + dinfo.global_state = fReadyState; + calc_output_dimensions(&dinfo, num, denom); + + // Return the calculated output dimensions for the given scale + return SkISize::Make(dinfo.output_width, dinfo.output_height); +} + +bool SkJpegCodec::onRewind() { + JpegDecoderMgr* decoderMgr = nullptr; + if (kSuccess != ReadHeader(this->stream(), nullptr, &decoderMgr, nullptr)) { + return fDecoderMgr->returnFalse("onRewind"); + } + SkASSERT(nullptr != decoderMgr); + fDecoderMgr.reset(decoderMgr); + + fSwizzler.reset(nullptr); + fSwizzleSrcRow = nullptr; + fColorXformSrcRow = nullptr; + fStorage.reset(); + + return true; +} + +bool SkJpegCodec::conversionSupported(const SkImageInfo& dstInfo, bool srcIsOpaque, + bool needsColorXform) { + SkASSERT(srcIsOpaque); + + if (kUnknown_SkAlphaType == dstInfo.alphaType()) { + return false; + } + + if (kOpaque_SkAlphaType != dstInfo.alphaType()) { + SkCodecPrintf("Warning: an opaque image should be decoded as opaque " + "- it is being decoded as non-opaque, which will draw slower\n"); + } + + J_COLOR_SPACE encodedColorType = fDecoderMgr->dinfo()->jpeg_color_space; + + // Check for valid color types and set the output color space + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + break; + case kBGRA_8888_SkColorType: + if (needsColorXform) { + // Always using RGBA as the input format for color xforms makes the + // implementation a little simpler. + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + } else { + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_BGRA; + } + break; + case kRGB_565_SkColorType: + if (needsColorXform) { + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + } else { + fDecoderMgr->dinfo()->dither_mode = JDITHER_NONE; + fDecoderMgr->dinfo()->out_color_space = JCS_RGB565; + } + break; + case kGray_8_SkColorType: + if (JCS_GRAYSCALE != encodedColorType) { + return false; + } + + if (needsColorXform) { + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + } else { + fDecoderMgr->dinfo()->out_color_space = JCS_GRAYSCALE; + } + break; + case kRGBA_F16_SkColorType: + SkASSERT(needsColorXform); + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + break; + default: + return false; + } + + // Check if we will decode to CMYK. libjpeg-turbo does not convert CMYK to RGBA, so + // we must do it ourselves. + if (JCS_CMYK == encodedColorType || JCS_YCCK == encodedColorType) { + fDecoderMgr->dinfo()->out_color_space = JCS_CMYK; + } + + return true; +} + +/* + * Checks if we can natively scale to the requested dimensions and natively scales the + * dimensions if possible + */ +bool SkJpegCodec::onDimensionsSupported(const SkISize& size) { + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFalse("onDimensionsSupported"); + } + + const unsigned int dstWidth = size.width(); + const unsigned int dstHeight = size.height(); + + // Set up a fake decompress struct in order to use libjpeg to calculate output dimensions + // FIXME: Why is this necessary? + jpeg_decompress_struct dinfo; + sk_bzero(&dinfo, sizeof(dinfo)); + dinfo.image_width = this->dimensions().width(); + dinfo.image_height = this->dimensions().height(); + dinfo.global_state = fReadyState; + + // libjpeg-turbo can scale to 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, and 1/1 + unsigned int num = 8; + const unsigned int denom = 8; + calc_output_dimensions(&dinfo, num, denom); + while (dinfo.output_width != dstWidth || dinfo.output_height != dstHeight) { + + // Return a failure if we have tried all of the possible scales + if (1 == num || dstWidth > dinfo.output_width || dstHeight > dinfo.output_height) { + return false; + } + + // Try the next scale + num -= 1; + calc_output_dimensions(&dinfo, num, denom); + } + + fDecoderMgr->dinfo()->scale_num = num; + fDecoderMgr->dinfo()->scale_denom = denom; + return true; +} + +int SkJpegCodec::readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, + const Options& opts) { + // Set the jump location for libjpeg-turbo errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return 0; + } + + // When fSwizzleSrcRow is non-null, it means that we need to swizzle. In this case, + // we will always decode into fSwizzlerSrcRow before swizzling into the next buffer. + // We can never swizzle "in place" because the swizzler may perform sampling and/or + // subsetting. + // When fColorXformSrcRow is non-null, it means that we need to color xform and that + // we cannot color xform "in place" (many times we can, but not when the src and dst + // are different sizes). + // In this case, we will color xform from fColorXformSrcRow into the dst. + JSAMPLE* decodeDst = (JSAMPLE*) dst; + uint32_t* swizzleDst = (uint32_t*) dst; + size_t decodeDstRowBytes = rowBytes; + size_t swizzleDstRowBytes = rowBytes; + int dstWidth = opts.fSubset ? opts.fSubset->width() : dstInfo.width(); + if (fSwizzleSrcRow && fColorXformSrcRow) { + decodeDst = (JSAMPLE*) fSwizzleSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } else if (fColorXformSrcRow) { + decodeDst = (JSAMPLE*) fColorXformSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + } else if (fSwizzleSrcRow) { + decodeDst = (JSAMPLE*) fSwizzleSrcRow; + decodeDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } + + for (int y = 0; y < count; y++) { + uint32_t lines = jpeg_read_scanlines(fDecoderMgr->dinfo(), &decodeDst, 1); + if (0 == lines) { + return y; + } + + if (fSwizzler) { + fSwizzler->swizzle(swizzleDst, decodeDst); + } + + if (this->colorXform()) { + this->applyColorXform(dst, swizzleDst, dstWidth); + dst = SkTAddOffset(dst, rowBytes); + } + + decodeDst = SkTAddOffset(decodeDst, decodeDstRowBytes); + swizzleDst = SkTAddOffset(swizzleDst, swizzleDstRowBytes); + } + + return count; +} + +/* + * This is a bit tricky. We only need the swizzler to do format conversion if the jpeg is + * encoded as CMYK. + * And even then we still may not need it. If the jpeg has a CMYK color profile and a color + * xform, the color xform will handle the CMYK->RGB conversion. + */ +static inline bool needs_swizzler_to_convert_from_cmyk(J_COLOR_SPACE jpegColorType, + const skcms_ICCProfile* srcProfile, + bool hasColorSpaceXform) { + if (JCS_CMYK != jpegColorType) { + return false; + } + + bool hasCMYKColorSpace = srcProfile && srcProfile->data_color_space == skcms_Signature_CMYK; + return !hasCMYKColorSpace || !hasColorSpaceXform; +} + +/* + * Performs the jpeg decode + */ +SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& options, + int* rowsDecoded) { + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + // Get a pointer to the decompress info since we will use it quite frequently + jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); + + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFailure("setjmp", kInvalidInput); + } + + if (!jpeg_start_decompress(dinfo)) { + return fDecoderMgr->returnFailure("startDecompress", kInvalidInput); + } + + // The recommended output buffer height should always be 1 in high quality modes. + // If it's not, we want to know because it means our strategy is not optimal. + SkASSERT(1 == dinfo->rec_outbuf_height); + + if (needs_swizzler_to_convert_from_cmyk(dinfo->out_color_space, + this->getEncodedInfo().profile(), this->colorXform())) { + this->initializeSwizzler(dstInfo, options, true); + } + + this->allocateStorage(dstInfo); + + int rows = this->readRows(dstInfo, dst, dstRowBytes, dstInfo.height(), options); + if (rows < dstInfo.height()) { + *rowsDecoded = rows; + return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput); + } + + return kSuccess; +} + +void SkJpegCodec::allocateStorage(const SkImageInfo& dstInfo) { + int dstWidth = dstInfo.width(); + + size_t swizzleBytes = 0; + if (fSwizzler) { + swizzleBytes = get_row_bytes(fDecoderMgr->dinfo()); + dstWidth = fSwizzler->swizzleWidth(); + SkASSERT(!this->colorXform() || SkIsAlign4(swizzleBytes)); + } + + size_t xformBytes = 0; + + if (this->colorXform() && sizeof(uint32_t) != dstInfo.bytesPerPixel()) { + xformBytes = dstWidth * sizeof(uint32_t); + } + + size_t totalBytes = swizzleBytes + xformBytes; + if (totalBytes > 0) { + fStorage.reset(totalBytes); + fSwizzleSrcRow = (swizzleBytes > 0) ? fStorage.get() : nullptr; + fColorXformSrcRow = (xformBytes > 0) ? + SkTAddOffset(fStorage.get(), swizzleBytes) : nullptr; + } +} + +void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, + bool needsCMYKToRGB) { + Options swizzlerOptions = options; + if (options.fSubset) { + // Use fSwizzlerSubset if this is a subset decode. This is necessary in the case + // where libjpeg-turbo provides a subset and then we need to subset it further. + // Also, verify that fSwizzlerSubset is initialized and valid. + SkASSERT(!fSwizzlerSubset.isEmpty() && fSwizzlerSubset.x() <= options.fSubset->x() && + fSwizzlerSubset.width() == options.fSubset->width()); + swizzlerOptions.fSubset = &fSwizzlerSubset; + } + + SkImageInfo swizzlerDstInfo = dstInfo; + if (this->colorXform()) { + // The color xform will be expecting RGBA 8888 input. + swizzlerDstInfo = swizzlerDstInfo.makeColorType(kRGBA_8888_SkColorType); + } + + if (needsCMYKToRGB) { + // The swizzler is used to convert to from CMYK. + // The swizzler does not use the width or height on SkEncodedInfo. + auto swizzlerInfo = SkEncodedInfo::Make(0, 0, SkEncodedInfo::kInvertedCMYK_Color, + SkEncodedInfo::kOpaque_Alpha, 8); + fSwizzler = SkSwizzler::Make(swizzlerInfo, nullptr, swizzlerDstInfo, swizzlerOptions); + } else { + int srcBPP = 0; + switch (fDecoderMgr->dinfo()->out_color_space) { + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_CMYK: + srcBPP = 4; + break; + case JCS_RGB565: + srcBPP = 2; + break; + case JCS_GRAYSCALE: + srcBPP = 1; + break; + default: + SkASSERT(false); + break; + } + fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerDstInfo, swizzlerOptions); + } + SkASSERT(fSwizzler); +} + +SkSampler* SkJpegCodec::getSampler(bool createIfNecessary) { + if (!createIfNecessary || fSwizzler) { + SkASSERT(!fSwizzler || (fSwizzleSrcRow && fStorage.get() == fSwizzleSrcRow)); + return fSwizzler.get(); + } + + bool needsCMYKToRGB = needs_swizzler_to_convert_from_cmyk( + fDecoderMgr->dinfo()->out_color_space, this->getEncodedInfo().profile(), + this->colorXform()); + this->initializeSwizzler(this->dstInfo(), this->options(), needsCMYKToRGB); + this->allocateStorage(this->dstInfo()); + return fSwizzler.get(); +} + +SkCodec::Result SkJpegCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) { + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + SkCodecPrintf("setjmp: Error from libjpeg\n"); + return kInvalidInput; + } + + if (!jpeg_start_decompress(fDecoderMgr->dinfo())) { + SkCodecPrintf("start decompress failed\n"); + return kInvalidInput; + } + + bool needsCMYKToRGB = needs_swizzler_to_convert_from_cmyk( + fDecoderMgr->dinfo()->out_color_space, this->getEncodedInfo().profile(), + this->colorXform()); + if (options.fSubset) { + uint32_t startX = options.fSubset->x(); + uint32_t width = options.fSubset->width(); + + // libjpeg-turbo may need to align startX to a multiple of the IDCT + // block size. If this is the case, it will decrease the value of + // startX to the appropriate alignment and also increase the value + // of width so that the right edge of the requested subset remains + // the same. + jpeg_crop_scanline(fDecoderMgr->dinfo(), &startX, &width); + + SkASSERT(startX <= (uint32_t) options.fSubset->x()); + SkASSERT(width >= (uint32_t) options.fSubset->width()); + SkASSERT(startX + width >= (uint32_t) options.fSubset->right()); + + // Instruct the swizzler (if it is necessary) to further subset the + // output provided by libjpeg-turbo. + // + // We set this here (rather than in the if statement below), so that + // if (1) we don't need a swizzler for the subset, and (2) we need a + // swizzler for CMYK, the swizzler will still use the proper subset + // dimensions. + // + // Note that the swizzler will ignore the y and height parameters of + // the subset. Since the scanline decoder (and the swizzler) handle + // one row at a time, only the subsetting in the x-dimension matters. + fSwizzlerSubset.setXYWH(options.fSubset->x() - startX, 0, + options.fSubset->width(), options.fSubset->height()); + + // We will need a swizzler if libjpeg-turbo cannot provide the exact + // subset that we request. + if (startX != (uint32_t) options.fSubset->x() || + width != (uint32_t) options.fSubset->width()) { + this->initializeSwizzler(dstInfo, options, needsCMYKToRGB); + } + } + + // Make sure we have a swizzler if we are converting from CMYK. + if (!fSwizzler && needsCMYKToRGB) { + this->initializeSwizzler(dstInfo, options, true); + } + + this->allocateStorage(dstInfo); + + return kSuccess; +} + +int SkJpegCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { + int rows = this->readRows(this->dstInfo(), dst, dstRowBytes, count, this->options()); + if (rows < count) { + // This allows us to skip calling jpeg_finish_decompress(). + fDecoderMgr->dinfo()->output_scanline = this->dstInfo().height(); + } + + return rows; +} + +bool SkJpegCodec::onSkipScanlines(int count) { + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFalse("onSkipScanlines"); + } + + return (uint32_t) count == jpeg_skip_scanlines(fDecoderMgr->dinfo(), count); +} + +static bool is_yuv_supported(jpeg_decompress_struct* dinfo) { + // Scaling is not supported in raw data mode. + SkASSERT(dinfo->scale_num == dinfo->scale_denom); + + // I can't imagine that this would ever change, but we do depend on it. + static_assert(8 == DCTSIZE, "DCTSIZE (defined in jpeg library) should always be 8."); + + if (JCS_YCbCr != dinfo->jpeg_color_space) { + return false; + } + + SkASSERT(3 == dinfo->num_components); + SkASSERT(dinfo->comp_info); + + // It is possible to perform a YUV decode for any combination of + // horizontal and vertical sampling that is supported by + // libjpeg/libjpeg-turbo. However, we will start by supporting only the + // common cases (where U and V have samp_factors of one). + // + // The definition of samp_factor is kind of the opposite of what SkCodec + // thinks of as a sampling factor. samp_factor is essentially a + // multiplier, and the larger the samp_factor is, the more samples that + // there will be. Ex: + // U_plane_width = image_width * (U_h_samp_factor / max_h_samp_factor) + // + // Supporting cases where the samp_factors for U or V were larger than + // that of Y would be an extremely difficult change, given that clients + // allocate memory as if the size of the Y plane is always the size of the + // image. However, this case is very, very rare. + if ((1 != dinfo->comp_info[1].h_samp_factor) || + (1 != dinfo->comp_info[1].v_samp_factor) || + (1 != dinfo->comp_info[2].h_samp_factor) || + (1 != dinfo->comp_info[2].v_samp_factor)) + { + return false; + } + + // Support all common cases of Y samp_factors. + // TODO (msarett): As mentioned above, it would be possible to support + // more combinations of samp_factors. The issues are: + // (1) Are there actually any images that are not covered + // by these cases? + // (2) How much complexity would be added to the + // implementation in order to support these rare + // cases? + int hSampY = dinfo->comp_info[0].h_samp_factor; + int vSampY = dinfo->comp_info[0].v_samp_factor; + return (1 == hSampY && 1 == vSampY) || + (2 == hSampY && 1 == vSampY) || + (2 == hSampY && 2 == vSampY) || + (1 == hSampY && 2 == vSampY) || + (4 == hSampY && 1 == vSampY) || + (4 == hSampY && 2 == vSampY); +} + +bool SkJpegCodec::onQueryYUV8(SkYUVASizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const { + jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); + if (!is_yuv_supported(dinfo)) { + return false; + } + + jpeg_component_info * comp_info = dinfo->comp_info; + for (int i = 0; i < 3; ++i) { + sizeInfo->fSizes[i].set(comp_info[i].downsampled_width, comp_info[i].downsampled_height); + sizeInfo->fWidthBytes[i] = comp_info[i].width_in_blocks * DCTSIZE; + } + + // JPEG never has an alpha channel + sizeInfo->fSizes[3].fHeight = sizeInfo->fSizes[3].fWidth = sizeInfo->fWidthBytes[3] = 0; + + sizeInfo->fOrigin = this->getOrigin(); + + if (colorSpace) { + *colorSpace = kJPEG_SkYUVColorSpace; + } + + return true; +} + +SkCodec::Result SkJpegCodec::onGetYUV8Planes(const SkYUVASizeInfo& sizeInfo, + void* planes[SkYUVASizeInfo::kMaxCount]) { + SkYUVASizeInfo defaultInfo; + + // This will check is_yuv_supported(), so we don't need to here. + bool supportsYUV = this->onQueryYUV8(&defaultInfo, nullptr); + if (!supportsYUV || + sizeInfo.fSizes[0] != defaultInfo.fSizes[0] || + sizeInfo.fSizes[1] != defaultInfo.fSizes[1] || + sizeInfo.fSizes[2] != defaultInfo.fSizes[2] || + sizeInfo.fWidthBytes[0] < defaultInfo.fWidthBytes[0] || + sizeInfo.fWidthBytes[1] < defaultInfo.fWidthBytes[1] || + sizeInfo.fWidthBytes[2] < defaultInfo.fWidthBytes[2]) { + return fDecoderMgr->returnFailure("onGetYUV8Planes", kInvalidInput); + } + + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFailure("setjmp", kInvalidInput); + } + + // Get a pointer to the decompress info since we will use it quite frequently + jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); + + dinfo->raw_data_out = TRUE; + if (!jpeg_start_decompress(dinfo)) { + return fDecoderMgr->returnFailure("startDecompress", kInvalidInput); + } + + // A previous implementation claims that the return value of is_yuv_supported() + // may change after calling jpeg_start_decompress(). It looks to me like this + // was caused by a bug in the old code, but we'll be safe and check here. + SkASSERT(is_yuv_supported(dinfo)); + + // Currently, we require that the Y plane dimensions match the image dimensions + // and that the U and V planes are the same dimensions. + SkASSERT(sizeInfo.fSizes[1] == sizeInfo.fSizes[2]); + SkASSERT((uint32_t) sizeInfo.fSizes[0].width() == dinfo->output_width && + (uint32_t) sizeInfo.fSizes[0].height() == dinfo->output_height); + + // Build a JSAMPIMAGE to handle output from libjpeg-turbo. A JSAMPIMAGE has + // a 2-D array of pixels for each of the components (Y, U, V) in the image. + // Cheat Sheet: + // JSAMPIMAGE == JSAMPLEARRAY* == JSAMPROW** == JSAMPLE*** + JSAMPARRAY yuv[3]; + + // Set aside enough space for pointers to rows of Y, U, and V. + JSAMPROW rowptrs[2 * DCTSIZE + DCTSIZE + DCTSIZE]; + yuv[0] = &rowptrs[0]; // Y rows (DCTSIZE or 2 * DCTSIZE) + yuv[1] = &rowptrs[2 * DCTSIZE]; // U rows (DCTSIZE) + yuv[2] = &rowptrs[3 * DCTSIZE]; // V rows (DCTSIZE) + + // Initialize rowptrs. + int numYRowsPerBlock = DCTSIZE * dinfo->comp_info[0].v_samp_factor; + for (int i = 0; i < numYRowsPerBlock; i++) { + rowptrs[i] = SkTAddOffset(planes[0], i * sizeInfo.fWidthBytes[0]); + } + for (int i = 0; i < DCTSIZE; i++) { + rowptrs[i + 2 * DCTSIZE] = + SkTAddOffset(planes[1], i * sizeInfo.fWidthBytes[1]); + rowptrs[i + 3 * DCTSIZE] = + SkTAddOffset(planes[2], i * sizeInfo.fWidthBytes[2]); + } + + // After each loop iteration, we will increment pointers to Y, U, and V. + size_t blockIncrementY = numYRowsPerBlock * sizeInfo.fWidthBytes[0]; + size_t blockIncrementU = DCTSIZE * sizeInfo.fWidthBytes[1]; + size_t blockIncrementV = DCTSIZE * sizeInfo.fWidthBytes[2]; + + uint32_t numRowsPerBlock = numYRowsPerBlock; + + // We intentionally round down here, as this first loop will only handle + // full block rows. As a special case at the end, we will handle any + // remaining rows that do not make up a full block. + const int numIters = dinfo->output_height / numRowsPerBlock; + for (int i = 0; i < numIters; i++) { + JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); + if (linesRead < numRowsPerBlock) { + // FIXME: Handle incomplete YUV decodes without signalling an error. + return kInvalidInput; + } + + // Update rowptrs. + for (int i = 0; i < numYRowsPerBlock; i++) { + rowptrs[i] += blockIncrementY; + } + for (int i = 0; i < DCTSIZE; i++) { + rowptrs[i + 2 * DCTSIZE] += blockIncrementU; + rowptrs[i + 3 * DCTSIZE] += blockIncrementV; + } + } + + uint32_t remainingRows = dinfo->output_height - dinfo->output_scanline; + SkASSERT(remainingRows == dinfo->output_height % numRowsPerBlock); + SkASSERT(dinfo->output_scanline == numIters * numRowsPerBlock); + if (remainingRows > 0) { + // libjpeg-turbo needs memory to be padded by the block sizes. We will fulfill + // this requirement using a dummy row buffer. + // FIXME: Should SkCodec have an extra memory buffer that can be shared among + // all of the implementations that use temporary/garbage memory? + SkAutoTMalloc dummyRow(sizeInfo.fWidthBytes[0]); + for (int i = remainingRows; i < numYRowsPerBlock; i++) { + rowptrs[i] = dummyRow.get(); + } + int remainingUVRows = dinfo->comp_info[1].downsampled_height - DCTSIZE * numIters; + for (int i = remainingUVRows; i < DCTSIZE; i++) { + rowptrs[i + 2 * DCTSIZE] = dummyRow.get(); + rowptrs[i + 3 * DCTSIZE] = dummyRow.get(); + } + + JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); + if (linesRead < remainingRows) { + // FIXME: Handle incomplete YUV decodes without signalling an error. + return kInvalidInput; + } + } + + return kSuccess; +} + +// This function is declared in SkJpegInfo.h, used by SkPDF. +bool SkGetJpegInfo(const void* data, size_t len, + SkISize* size, + SkEncodedInfo::Color* colorType, + SkEncodedOrigin* orientation) { + if (!SkJpegCodec::IsJpeg(data, len)) { + return false; + } + + SkMemoryStream stream(data, len); + JpegDecoderMgr decoderMgr(&stream); + // libjpeg errors will be caught and reported here + skjpeg_error_mgr::AutoPushJmpBuf jmp(decoderMgr.errorMgr()); + if (setjmp(jmp)) { + return false; + } + decoderMgr.init(); + jpeg_decompress_struct* dinfo = decoderMgr.dinfo(); + jpeg_save_markers(dinfo, kExifMarker, 0xFFFF); + jpeg_save_markers(dinfo, kICCMarker, 0xFFFF); + if (JPEG_HEADER_OK != jpeg_read_header(dinfo, true)) { + return false; + } + SkEncodedInfo::Color encodedColorType; + if (!decoderMgr.getEncodedColor(&encodedColorType)) { + return false; // Unable to interpret the color channels as colors. + } + if (colorType) { + *colorType = encodedColorType; + } + if (orientation) { + *orientation = get_exif_orientation(dinfo); + } + if (size) { + *size = {SkToS32(dinfo->image_width), SkToS32(dinfo->image_height)}; + } + return true; +} diff --git a/gfx/skia/skia/src/codec/SkJpegCodec.h b/gfx/skia/skia/src/codec/SkJpegCodec.h new file mode 100644 index 0000000000..986d283b3a --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegCodec.h @@ -0,0 +1,144 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkJpegCodec_DEFINED +#define SkJpegCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkSwizzler.h" + +class JpegDecoderMgr; + +/* + * + * This class implements the decoding for jpeg images + * + */ +class SkJpegCodec : public SkCodec { +public: + static bool IsJpeg(const void*, size_t); + + /* + * Assumes IsJpeg was called and returned true + * Takes ownership of the stream + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + +protected: + + /* + * Recommend a set of destination dimensions given a requested scale + */ + SkISize onGetScaledDimensions(float desiredScale) const override; + + /* + * Initiates the jpeg decode + */ + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + int*) override; + + bool onQueryYUV8(SkYUVASizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const override; + + Result onGetYUV8Planes(const SkYUVASizeInfo& sizeInfo, + void* planes[SkYUVASizeInfo::kMaxCount]) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kJPEG; + } + + bool onRewind() override; + + bool onDimensionsSupported(const SkISize&) override; + + bool conversionSupported(const SkImageInfo&, bool, bool) override; + +private: + /* + * Allows SkRawCodec to communicate the color profile from the exif data. + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*, + std::unique_ptr defaultColorProfile); + + /* + * Read enough of the stream to initialize the SkJpegCodec. + * Returns a bool representing success or failure. + * + * @param codecOut + * If this returns true, and codecOut was not nullptr, + * codecOut will be set to a new SkJpegCodec. + * + * @param decoderMgrOut + * If this returns true, and codecOut was nullptr, + * decoderMgrOut must be non-nullptr and decoderMgrOut will be set to a new + * JpegDecoderMgr pointer. + * + * @param stream + * Deleted on failure. + * codecOut will take ownership of it in the case where we created a codec. + * Ownership is unchanged when we set decoderMgrOut. + * + * @param defaultColorProfile + * If the jpeg does not have an embedded color profile, the image data should + * be tagged with this color profile. + */ + static Result ReadHeader(SkStream* stream, SkCodec** codecOut, + JpegDecoderMgr** decoderMgrOut, + std::unique_ptr defaultColorProfile); + + /* + * Creates an instance of the decoder + * Called only by NewFromStream + * + * @param info contains properties of the encoded data + * @param stream the encoded image data + * @param decoderMgr holds decompress struct, src manager, and error manager + * takes ownership + */ + SkJpegCodec(SkEncodedInfo&& info, std::unique_ptr stream, + JpegDecoderMgr* decoderMgr, SkEncodedOrigin origin); + + void initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, + bool needsCMYKToRGB); + void allocateStorage(const SkImageInfo& dstInfo); + int readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, const Options&); + + /* + * Scanline decoding. + */ + SkSampler* getSampler(bool createIfNecessary) override; + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + bool onSkipScanlines(int count) override; + + std::unique_ptr fDecoderMgr; + + // We will save the state of the decompress struct after reading the header. + // This allows us to safely call onGetScaledDimensions() at any time. + const int fReadyState; + + + SkAutoTMalloc fStorage; + uint8_t* fSwizzleSrcRow; + uint32_t* fColorXformSrcRow; + + // libjpeg-turbo provides some subsetting. In the case that libjpeg-turbo + // cannot take the exact the subset that we need, we will use the swizzler + // to further subset the output from libjpeg-turbo. + SkIRect fSwizzlerSubset; + + std::unique_ptr fSwizzler; + + friend class SkRawCodec; + + typedef SkCodec INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp new file mode 100644 index 0000000000..0ada6d8592 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkJpegDecoderMgr.h" + +#include "src/codec/SkJpegUtility.h" + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #include "include/android/SkAndroidFrameworkUtils.h" +#endif + +/* + * Print information, warning, and error messages + */ +static void print_message(const j_common_ptr info, const char caller[]) { + char buffer[JMSG_LENGTH_MAX]; + info->err->format_message(info, buffer); + SkCodecPrintf("libjpeg error %d <%s> from %s\n", info->err->msg_code, buffer, caller); +} + +/* + * Reporting function for error and warning messages. + */ +static void output_message(j_common_ptr info) { + print_message(info, "output_message"); +} + +static void progress_monitor(j_common_ptr info) { + int scan = ((j_decompress_ptr)info)->input_scan_number; + // Progressive images with a very large number of scans can cause the + // decoder to hang. Here we use the progress monitor to abort on + // a very large number of scans. 100 is arbitrary, but much larger + // than the number of scans we might expect in a normal image. + if (scan >= 100) { + skjpeg_err_exit(info); + } +} + +bool JpegDecoderMgr::returnFalse(const char caller[]) { + print_message((j_common_ptr) &fDInfo, caller); + return false; +} + +SkCodec::Result JpegDecoderMgr::returnFailure(const char caller[], SkCodec::Result result) { + print_message((j_common_ptr) &fDInfo, caller); + return result; +} + +bool JpegDecoderMgr::getEncodedColor(SkEncodedInfo::Color* outColor) { + switch (fDInfo.jpeg_color_space) { + case JCS_GRAYSCALE: + *outColor = SkEncodedInfo::kGray_Color; + return true; + case JCS_YCbCr: + *outColor = SkEncodedInfo::kYUV_Color; + return true; + case JCS_RGB: +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkAndroidFrameworkUtils::SafetyNetLog("118372692"); +#endif + *outColor = SkEncodedInfo::kRGB_Color; + return true; + case JCS_YCCK: + *outColor = SkEncodedInfo::kYCCK_Color; + return true; + case JCS_CMYK: + *outColor = SkEncodedInfo::kInvertedCMYK_Color; + return true; + default: + return false; + } +} + +JpegDecoderMgr::JpegDecoderMgr(SkStream* stream) + : fSrcMgr(stream) + , fInit(false) +{ + // Error manager must be set before any calls to libjeg in order to handle failures + fDInfo.err = jpeg_std_error(&fErrorMgr); + fErrorMgr.error_exit = skjpeg_err_exit; +} + +void JpegDecoderMgr::init() { + jpeg_create_decompress(&fDInfo); + fInit = true; + fDInfo.src = &fSrcMgr; + fDInfo.err->output_message = &output_message; + fDInfo.progress = &fProgressMgr; + fProgressMgr.progress_monitor = &progress_monitor; +} + +JpegDecoderMgr::~JpegDecoderMgr() { + if (fInit) { + jpeg_destroy_decompress(&fDInfo); + } +} diff --git a/gfx/skia/skia/src/codec/SkJpegDecoderMgr.h b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.h new file mode 100644 index 0000000000..f992bf5411 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.h @@ -0,0 +1,75 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkJpegDecoderMgr_DEFINED +#define SkJpegDecoderMgr_DEFINED + +#include "include/codec/SkCodec.h" +#include "src/codec/SkCodecPriv.h" +#include +#include "src/codec/SkJpegUtility.h" + +extern "C" { + #include "jpeglib.h" +} + +class JpegDecoderMgr : SkNoncopyable { +public: + + /* + * Print a useful error message and return false + */ + bool returnFalse(const char caller[]); + + /* + * Print a useful error message and return a decode failure + */ + SkCodec::Result returnFailure(const char caller[], SkCodec::Result result); + + /* + * Create the decode manager + * Does not take ownership of stream + */ + JpegDecoderMgr(SkStream* stream); + + /* + * Initialize decompress struct + * Initialize the source manager + */ + void init(); + + /* + * Returns true if it successfully sets outColor to the encoded color, + * and false otherwise. + */ + bool getEncodedColor(SkEncodedInfo::Color* outColor); + + /* + * Free memory used by the decode manager + */ + ~JpegDecoderMgr(); + + /* + * Get the skjpeg_error_mgr in order to set an error return jmp_buf + */ + skjpeg_error_mgr* errorMgr() { return &fErrorMgr; } + + /* + * Get function for the decompress info struct + */ + jpeg_decompress_struct* dinfo() { return &fDInfo; } + +private: + + jpeg_decompress_struct fDInfo; + skjpeg_source_mgr fSrcMgr; + skjpeg_error_mgr fErrorMgr; + jpeg_progress_mgr fProgressMgr; + bool fInit; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkJpegPriv.h b/gfx/skia/skia/src/codec/SkJpegPriv.h new file mode 100644 index 0000000000..2e36397714 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegPriv.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkJpegPriv_DEFINED +#define SkJpegPriv_DEFINED + +#include "include/core/SkStream.h" +#include "include/private/SkTArray.h" + +#include +// stdio is needed for jpeglib +#include + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +static constexpr uint32_t kICCMarker = JPEG_APP0 + 2; +static constexpr uint32_t kICCMarkerHeaderSize = 14; +static constexpr uint8_t kICCSig[] = { + 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0', +}; + +/* + * Error handling struct + */ +struct skjpeg_error_mgr : jpeg_error_mgr { + class AutoPushJmpBuf { + public: + AutoPushJmpBuf(skjpeg_error_mgr* mgr) : fMgr(mgr) { + fMgr->fJmpBufStack.push_back(&fJmpBuf); + } + ~AutoPushJmpBuf() { + SkASSERT(fMgr->fJmpBufStack.back() == &fJmpBuf); + fMgr->fJmpBufStack.pop_back(); + } + operator jmp_buf&() { return fJmpBuf; } + + private: + skjpeg_error_mgr* const fMgr; + jmp_buf fJmpBuf; + }; + + SkSTArray<4, jmp_buf*> fJmpBufStack; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkJpegUtility.cpp b/gfx/skia/skia/src/codec/SkJpegUtility.cpp new file mode 100644 index 0000000000..d313892cc3 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegUtility.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkJpegUtility.h" + +#include "src/codec/SkCodecPriv.h" + +/* + * Call longjmp to continue execution on an error + */ +void skjpeg_err_exit(j_common_ptr dinfo) { + // Simply return to Skia client code + // JpegDecoderMgr will take care of freeing memory + skjpeg_error_mgr* error = (skjpeg_error_mgr*) dinfo->err; + (*error->output_message) (dinfo); + if (error->fJmpBufStack.empty()) { + SK_ABORT("JPEG error with no jmp_buf set."); + } + longjmp(*error->fJmpBufStack.back(), 1); +} + +// Functions for buffered sources // + +/* + * Initialize the buffered source manager + */ +static void sk_init_buffered_source(j_decompress_ptr dinfo) { + skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src; + src->next_input_byte = (const JOCTET*) src->fBuffer; + src->bytes_in_buffer = 0; +} + +/* + * Fill the input buffer from the stream + */ +static boolean sk_fill_buffered_input_buffer(j_decompress_ptr dinfo) { + skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src; + size_t bytes = src->fStream->read(src->fBuffer, skjpeg_source_mgr::kBufferSize); + + // libjpeg is still happy with a less than full read, as long as the result is non-zero + if (bytes == 0) { + // Let libjpeg know that the buffer needs to be refilled + src->next_input_byte = nullptr; + src->bytes_in_buffer = 0; + return false; + } + + src->next_input_byte = (const JOCTET*) src->fBuffer; + src->bytes_in_buffer = bytes; + return true; +} + +/* + * Skip a certain number of bytes in the stream + */ +static void sk_skip_buffered_input_data(j_decompress_ptr dinfo, long numBytes) { + skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src; + size_t bytes = (size_t) numBytes; + + if (bytes > src->bytes_in_buffer) { + size_t bytesToSkip = bytes - src->bytes_in_buffer; + if (bytesToSkip != src->fStream->skip(bytesToSkip)) { + SkCodecPrintf("Failure to skip.\n"); + dinfo->err->error_exit((j_common_ptr) dinfo); + return; + } + + src->next_input_byte = (const JOCTET*) src->fBuffer; + src->bytes_in_buffer = 0; + } else { + src->next_input_byte += numBytes; + src->bytes_in_buffer -= numBytes; + } +} + +/* + * We do not need to do anything to terminate our stream + */ +static void sk_term_source(j_decompress_ptr dinfo) +{ + // The current implementation of SkJpegCodec does not call + // jpeg_finish_decompress(), so this function is never called. + // If we want to modify this function to do something, we also + // need to modify SkJpegCodec to call jpeg_finish_decompress(). +} + +// Functions for memory backed sources // + +/* + * Initialize the mem backed source manager + */ +static void sk_init_mem_source(j_decompress_ptr dinfo) { + /* no work necessary here, all things are done in constructor */ +} + +static void sk_skip_mem_input_data (j_decompress_ptr cinfo, long num_bytes) { + jpeg_source_mgr* src = cinfo->src; + size_t bytes = static_cast(num_bytes); + if(bytes > src->bytes_in_buffer) { + src->next_input_byte = nullptr; + src->bytes_in_buffer = 0; + } else { + src->next_input_byte += bytes; + src->bytes_in_buffer -= bytes; + } +} + +static boolean sk_fill_mem_input_buffer (j_decompress_ptr cinfo) { + /* The whole JPEG data is expected to reside in the supplied memory, + * buffer, so any request for more data beyond the given buffer size + * is treated as an error. + */ + return false; +} + +/* + * Constructor for the source manager that we provide to libjpeg + * We provide skia implementations of all of the stream processing functions required by libjpeg + */ +skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream) + : fStream(stream) +{ + if (stream->hasLength() && stream->getMemoryBase()) { + init_source = sk_init_mem_source; + fill_input_buffer = sk_fill_mem_input_buffer; + skip_input_data = sk_skip_mem_input_data; + resync_to_restart = jpeg_resync_to_restart; + term_source = sk_term_source; + bytes_in_buffer = static_cast(stream->getLength()); + next_input_byte = static_cast(stream->getMemoryBase()); + } else { + init_source = sk_init_buffered_source; + fill_input_buffer = sk_fill_buffered_input_buffer; + skip_input_data = sk_skip_buffered_input_data; + resync_to_restart = jpeg_resync_to_restart; + term_source = sk_term_source; + } +} diff --git a/gfx/skia/skia/src/codec/SkJpegUtility.h b/gfx/skia/skia/src/codec/SkJpegUtility.h new file mode 100644 index 0000000000..350f15484a --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegUtility.h @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkJpegUtility_codec_DEFINED +#define SkJpegUtility_codec_DEFINED + +#include "include/core/SkStream.h" +#include "src/codec/SkJpegPriv.h" + +#include +// stdio is needed for jpeglib +#include + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +/* + * Error handling function + */ +void skjpeg_err_exit(j_common_ptr cinfo); + +/* + * Source handling struct for that allows libjpeg to use our stream object + */ +struct skjpeg_source_mgr : jpeg_source_mgr { + skjpeg_source_mgr(SkStream* stream); + + SkStream* fStream; // unowned + enum { + // TODO (msarett): Experiment with different buffer sizes. + // This size was chosen because it matches SkImageDecoder. + kBufferSize = 1024 + }; + uint8_t fBuffer[kBufferSize]; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkMaskSwizzler.cpp b/gfx/skia/skia/src/codec/SkMaskSwizzler.cpp new file mode 100644 index 0000000000..d3f1f36c51 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMaskSwizzler.cpp @@ -0,0 +1,568 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkMaskSwizzler.h" + +static void swizzle_mask16_to_rgba_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_bgra_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_rgba_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_bgra_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_rgba_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_bgra_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +// TODO (msarett): We have promoted a two byte per pixel image to 8888, only to +// convert it back to 565. Instead, we should swizzle to 565 directly. +static void swizzle_mask16_to_565( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + uint16_t* dstPtr = (uint16_t*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPack888ToRGB16(red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask24_to_rgba_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_bgra_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_rgba_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_bgra_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_rgba_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_bgra_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_565( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + uint16_t* dstPtr = (uint16_t*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPack888ToRGB16(red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask32_to_rgba_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_bgra_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_rgba_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_bgra_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_rgba_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_bgra_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_565( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + uint16_t* dstPtr = (uint16_t*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPack888ToRGB16(red, green, blue); + srcPtr += sampleX; + } +} + +/* + * + * Create a new mask swizzler + * + */ +SkMaskSwizzler* SkMaskSwizzler::CreateMaskSwizzler(const SkImageInfo& dstInfo, + bool srcIsOpaque, SkMasks* masks, uint32_t bitsPerPixel, + const SkCodec::Options& options) { + + // Choose the appropriate row procedure + RowProc proc = nullptr; + switch (bitsPerPixel) { + case 16: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask16_to_rgba_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask16_to_rgba_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask16_to_rgba_premul; + break; + default: + break; + } + } + break; + case kBGRA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask16_to_bgra_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask16_to_bgra_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask16_to_bgra_premul; + break; + default: + break; + } + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_mask16_to_565; + break; + default: + break; + } + break; + case 24: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask24_to_rgba_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask24_to_rgba_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask24_to_rgba_premul; + break; + default: + break; + } + } + break; + case kBGRA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask24_to_bgra_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask24_to_bgra_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask24_to_bgra_premul; + break; + default: + break; + } + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_mask24_to_565; + break; + default: + break; + } + break; + case 32: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask32_to_rgba_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask32_to_rgba_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask32_to_rgba_premul; + break; + default: + break; + } + } + break; + case kBGRA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask32_to_bgra_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask32_to_bgra_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask32_to_bgra_premul; + break; + default: + break; + } + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_mask32_to_565; + break; + default: + break; + } + break; + default: + SkASSERT(false); + return nullptr; + } + + int srcOffset = 0; + int srcWidth = dstInfo.width(); + if (options.fSubset) { + srcOffset = options.fSubset->left(); + srcWidth = options.fSubset->width(); + } + + return new SkMaskSwizzler(masks, proc, srcOffset, srcWidth); +} + +/* + * + * Constructor for mask swizzler + * + */ +SkMaskSwizzler::SkMaskSwizzler(SkMasks* masks, RowProc proc, int srcOffset, int subsetWidth) + : fMasks(masks) + , fRowProc(proc) + , fSubsetWidth(subsetWidth) + , fDstWidth(subsetWidth) + , fSampleX(1) + , fSrcOffset(srcOffset) + , fX0(srcOffset) +{} + +int SkMaskSwizzler::onSetSampleX(int sampleX) { + // FIXME: Share this function with SkSwizzler? + SkASSERT(sampleX > 0); // Surely there is an upper limit? Should there be + // way to report failure? + fSampleX = sampleX; + fX0 = get_start_coord(sampleX) + fSrcOffset; + fDstWidth = get_scaled_dimension(fSubsetWidth, sampleX); + + // check that fX0 is valid + SkASSERT(fX0 >= 0); + return fDstWidth; +} + +/* + * + * Swizzle the specified row + * + */ +void SkMaskSwizzler::swizzle(void* dst, const uint8_t* SK_RESTRICT src) { + SkASSERT(nullptr != dst && nullptr != src); + fRowProc(dst, src, fDstWidth, fMasks, fX0, fSampleX); +} diff --git a/gfx/skia/skia/src/codec/SkMaskSwizzler.h b/gfx/skia/skia/src/codec/SkMaskSwizzler.h new file mode 100644 index 0000000000..c7c44fd354 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMaskSwizzler.h @@ -0,0 +1,72 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkMaskSwizzler_DEFINED +#define SkMaskSwizzler_DEFINED + +#include "include/core/SkTypes.h" +#include "src/codec/SkMasks.h" +#include "src/codec/SkSampler.h" +#include "src/codec/SkSwizzler.h" + +/* + * + * Used to swizzle images whose pixel components are extracted by bit masks + * Currently only used by bmp + * + */ +class SkMaskSwizzler : public SkSampler { +public: + + /* + * @param masks Unowned pointer to helper class + */ + static SkMaskSwizzler* CreateMaskSwizzler(const SkImageInfo& dstInfo, + bool srcIsOpaque, + SkMasks* masks, + uint32_t bitsPerPixel, + const SkCodec::Options& options); + + /* + * Swizzle a row + */ + void swizzle(void* dst, const uint8_t* SK_RESTRICT src); + + int fillWidth() const override { + return fDstWidth; + } + + /** + * Returns the byte offset at which we write to destination memory, taking + * scaling, subsetting, and partial frames into account. + * A similar function exists on SkSwizzler. + */ + int swizzleWidth() const { return fDstWidth; } + +private: + + /* + * Row procedure used for swizzle + */ + typedef void (*RowProc)(void* dstRow, const uint8_t* srcRow, int width, + SkMasks* masks, uint32_t startX, uint32_t sampleX); + + SkMaskSwizzler(SkMasks* masks, RowProc proc, int subsetWidth, int srcOffset); + + int onSetSampleX(int) override; + + SkMasks* fMasks; // unowned + const RowProc fRowProc; + + // FIXME: Can this class share more with SkSwizzler? These variables are all the same. + const int fSubsetWidth; // Width of the subset of source before any sampling. + int fDstWidth; // Width of dst, which may differ with sampling. + int fSampleX; + int fSrcOffset; + int fX0; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkMasks.cpp b/gfx/skia/skia/src/codec/SkMasks.cpp new file mode 100644 index 0000000000..e10551cfe4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMasks.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkMasks.h" + +/* + * + * Used to convert 1-7 bit color components into 8-bit color components + * + */ +static constexpr uint8_t n_bit_to_8_bit_lookup_table[] = { + // 1 bit + 0, 255, + // 2 bits + 0, 85, 170, 255, + // 3 bits + 0, 36, 73, 109, 146, 182, 219, 255, + // 4 bits + 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, + // 5 bits + 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, + 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255, + // 6 bits + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, + 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, + 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, + 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, + // 7 bits + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, + 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, + 112, 114, 116, 118, 120, 122, 124, 126, 129, 131, 133, 135, 137, 139, 141, + 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, + 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, + 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, + 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 +}; + +/* + * + * Convert an n bit component to an 8-bit component + * + */ +static uint8_t convert_to_8(uint8_t component, uint32_t n) { + if (0 == n) { + return 0; + } else if (8 > n) { + return n_bit_to_8_bit_lookup_table[(1 << n) - 2 + component]; + } else { + SkASSERT(8 == n); + return component; + } +} + +static uint8_t get_comp(uint32_t pixel, uint32_t mask, uint32_t shift, + uint32_t size) { + return convert_to_8((pixel & mask) >> shift, size); +} + +/* + * + * Get a color component + * + */ +uint8_t SkMasks::getRed(uint32_t pixel) const { + return get_comp(pixel, fRed.mask, fRed.shift, fRed.size); +} +uint8_t SkMasks::getGreen(uint32_t pixel) const { + return get_comp(pixel, fGreen.mask, fGreen.shift, fGreen.size); +} +uint8_t SkMasks::getBlue(uint32_t pixel) const { + return get_comp(pixel, fBlue.mask, fBlue.shift, fBlue.size); +} +uint8_t SkMasks::getAlpha(uint32_t pixel) const { + return get_comp(pixel, fAlpha.mask, fAlpha.shift, fAlpha.size); +} + +/* + * + * Process an input mask to obtain the necessary information + * + */ +static const SkMasks::MaskInfo process_mask(uint32_t mask) { + // Determine properties of the mask + uint32_t tempMask = mask; + uint32_t shift = 0; + uint32_t size = 0; + if (tempMask != 0) { + // Count trailing zeros on masks + for (; (tempMask & 1) == 0; tempMask >>= 1) { + shift++; + } + // Count the size of the mask + for (; tempMask & 1; tempMask >>= 1) { + size++; + } + // Verify that the mask is continuous + if (tempMask) { + SkCodecPrintf("Warning: Bit mask is not continuous.\n"); + // Finish processing the mask + for (; tempMask; tempMask >>= 1) { + size++; + } + } + // Truncate masks greater than 8 bits + if (size > 8) { + shift += size - 8; + size = 8; + mask &= 0xFF << shift; + } + } + + return { mask, shift, size }; +} + +/* + * + * Create the masks object + * + */ +SkMasks* SkMasks::CreateMasks(InputMasks masks, int bytesPerPixel) { + SkASSERT(0 < bytesPerPixel && bytesPerPixel <= 4); + + // Trim the input masks to match bytesPerPixel. + if (bytesPerPixel < 4) { + int bitsPerPixel = 8*bytesPerPixel; + masks.red &= (1 << bitsPerPixel) - 1; + masks.green &= (1 << bitsPerPixel) - 1; + masks.blue &= (1 << bitsPerPixel) - 1; + masks.alpha &= (1 << bitsPerPixel) - 1; + } + + // Check that masks do not overlap. + if (((masks.red & masks.green) | + (masks.red & masks.blue ) | + (masks.red & masks.alpha) | + (masks.green & masks.blue ) | + (masks.green & masks.alpha) | + (masks.blue & masks.alpha) ) != 0) { + return nullptr; + } + + return new SkMasks(process_mask(masks.red ), + process_mask(masks.green), + process_mask(masks.blue ), + process_mask(masks.alpha)); +} + + +SkMasks::SkMasks(const MaskInfo& red, const MaskInfo& green, + const MaskInfo& blue, const MaskInfo& alpha) + : fRed(red) + , fGreen(green) + , fBlue(blue) + , fAlpha(alpha) +{} diff --git a/gfx/skia/skia/src/codec/SkMasks.h b/gfx/skia/skia/src/codec/SkMasks.h new file mode 100644 index 0000000000..473d6f8baf --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMasks.h @@ -0,0 +1,86 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkMasks_DEFINED +#define SkMasks_DEFINED + +#include "include/core/SkTypes.h" + +/* + * + * Contains useful mask routines for SkMaskSwizzler + * + */ +class SkMasks { +public: + + /* + * + * Input bit masks format + * + */ + struct InputMasks { + uint32_t red; + uint32_t green; + uint32_t blue; + uint32_t alpha; + }; + + /* + * + * Contains all of the information for a single mask + * + */ + struct MaskInfo { + uint32_t mask; + uint32_t shift; + uint32_t size; + }; + + /* + * + * Create the masks object + * + */ + static SkMasks* CreateMasks(InputMasks masks, int bytesPerPixel); + + /* + * + * Get a color component + * + */ + uint8_t getRed(uint32_t pixel) const; + uint8_t getGreen(uint32_t pixel) const; + uint8_t getBlue(uint32_t pixel) const; + uint8_t getAlpha(uint32_t pixel) const; + + /* + * + * Getter for the alpha mask + * The alpha mask may be used in other decoding modes + * + */ + uint32_t getAlphaMask() const { + return fAlpha.mask; + } + +private: + + /* + * + * Constructor + * + */ + SkMasks(const MaskInfo& red, const MaskInfo& green, const MaskInfo& blue, + const MaskInfo& alpha); + + const MaskInfo fRed; + const MaskInfo fGreen; + const MaskInfo fBlue; + const MaskInfo fAlpha; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp new file mode 100644 index 0000000000..aaee8c2d38 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/codec/SkCodecPriv.h" + +bool SkParseEncodedOrigin(const uint8_t* data, size_t data_length, SkEncodedOrigin* orientation) { + SkASSERT(orientation); + bool littleEndian; + // We need eight bytes to read the endian marker and the offset, below. + if (data_length < 8 || !is_valid_endian_marker(data, &littleEndian)) { + return false; + } + + auto getEndianInt = [](const uint8_t* data, bool littleEndian) -> uint32_t { + if (littleEndian) { + return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | (data[0]); + } + + return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); + }; + + // Get the offset from the start of the marker. + // Though this only reads four bytes, use a larger int in case it overflows. + uint64_t offset = getEndianInt(data + 4, littleEndian); + + // Require that the marker is at least large enough to contain the number of entries. + if (data_length < offset + 2) { + return false; + } + uint32_t numEntries = get_endian_short(data + offset, littleEndian); + + // Tag (2 bytes), Datatype (2 bytes), Number of elements (4 bytes), Data (4 bytes) + const uint32_t kEntrySize = 12; + const auto max = SkTo((data_length - offset - 2) / kEntrySize); + numEntries = SkTMin(numEntries, max); + + // Advance the data to the start of the entries. + data += offset + 2; + + const uint16_t kOriginTag = 0x112; + const uint16_t kOriginType = 3; + for (uint32_t i = 0; i < numEntries; i++, data += kEntrySize) { + uint16_t tag = get_endian_short(data, littleEndian); + uint16_t type = get_endian_short(data + 2, littleEndian); + uint32_t count = getEndianInt(data + 4, littleEndian); + if (kOriginTag == tag && kOriginType == type && 1 == count) { + uint16_t val = get_endian_short(data + 8, littleEndian); + if (0 < val && val <= kLast_SkEncodedOrigin) { + *orientation = (SkEncodedOrigin) val; + return true; + } + } + } + + return false; +} diff --git a/gfx/skia/skia/src/codec/SkParseEncodedOrigin.h b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.h new file mode 100644 index 0000000000..4891557a19 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.h @@ -0,0 +1,19 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkParseEncodedOrigin_DEFINED +#define SkParseEncodedOrigin_DEFINED + +#include "include/codec/SkEncodedOrigin.h" + +/** + * If |data| is an EXIF tag representing an SkEncodedOrigin, return true and set |out| + * appropriately. Otherwise return false. + */ +bool SkParseEncodedOrigin(const uint8_t* data, size_t data_length, SkEncodedOrigin* out); + +#endif // SkParseEncodedOrigin_DEFINED diff --git a/gfx/skia/skia/src/codec/SkPngCodec.cpp b/gfx/skia/skia/src/codec/SkPngCodec.cpp new file mode 100644 index 0000000000..4eaa034e64 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkPngCodec.cpp @@ -0,0 +1,1194 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkMath.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkSize.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkPngCodec.h" +#include "src/codec/SkPngPriv.h" +#include "src/codec/SkSwizzler.h" +#include "src/core/SkOpts.h" +#include "src/core/SkUtils.h" + +#include "png.h" +#include + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #include "include/android/SkAndroidFrameworkUtils.h" +#endif + +// This warning triggers false postives way too often in here. +#if defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic ignored "-Wclobbered" +#endif + +// FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6 +#define PNG_JMPBUF(x) png_jmpbuf((png_structp) x) + +/////////////////////////////////////////////////////////////////////////////// +// Callback functions +/////////////////////////////////////////////////////////////////////////////// + +// When setjmp is first called, it returns 0, meaning longjmp was not called. +constexpr int kSetJmpOkay = 0; +// An error internal to libpng. +constexpr int kPngError = 1; +// Passed to longjmp when we have decoded as many lines as we need. +constexpr int kStopDecoding = 2; + +static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { + SkCodecPrintf("------ png error %s\n", msg); + longjmp(PNG_JMPBUF(png_ptr), kPngError); +} + +void sk_warning_fn(png_structp, png_const_charp msg) { + SkCodecPrintf("----- png warning %s\n", msg); +} + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) { + SkPngChunkReader* chunkReader = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr); + // readChunk() returning true means continue decoding + return chunkReader->readChunk((const char*)chunk->name, chunk->data, chunk->size) ? 1 : -1; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers +/////////////////////////////////////////////////////////////////////////////// + +class AutoCleanPng : public SkNoncopyable { +public: + /* + * This class does not take ownership of stream or reader, but if codecPtr + * is non-NULL, and decodeBounds succeeds, it will have created a new + * SkCodec (pointed to by *codecPtr) which will own/ref them, as well as + * the png_ptr and info_ptr. + */ + AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader, + SkCodec** codecPtr) + : fPng_ptr(png_ptr) + , fInfo_ptr(nullptr) + , fStream(stream) + , fChunkReader(reader) + , fOutCodec(codecPtr) + {} + + ~AutoCleanPng() { + // fInfo_ptr will never be non-nullptr unless fPng_ptr is. + if (fPng_ptr) { + png_infopp info_pp = fInfo_ptr ? &fInfo_ptr : nullptr; + png_destroy_read_struct(&fPng_ptr, info_pp, nullptr); + } + } + + void setInfoPtr(png_infop info_ptr) { + SkASSERT(nullptr == fInfo_ptr); + fInfo_ptr = info_ptr; + } + + /** + * Reads enough of the input stream to decode the bounds. + * @return false if the stream is not a valid PNG (or too short). + * true if it read enough of the stream to determine the bounds. + * In the latter case, the stream may have been read beyond the + * point to determine the bounds, and the png_ptr will have saved + * any extra data. Further, if the codecPtr supplied to the + * constructor was not NULL, it will now point to a new SkCodec, + * which owns (or refs, in the case of the SkPngChunkReader) the + * inputs. If codecPtr was NULL, the png_ptr and info_ptr are + * unowned, and it is up to the caller to destroy them. + */ + bool decodeBounds(); + +private: + png_structp fPng_ptr; + png_infop fInfo_ptr; + SkStream* fStream; + SkPngChunkReader* fChunkReader; + SkCodec** fOutCodec; + + void infoCallback(size_t idatLength); + + void releasePngPtrs() { + fPng_ptr = nullptr; + fInfo_ptr = nullptr; + } +}; +#define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng) + +static inline bool is_chunk(const png_byte* chunk, const char* tag) { + return memcmp(chunk + 4, tag, 4) == 0; +} + +static inline bool process_data(png_structp png_ptr, png_infop info_ptr, + SkStream* stream, void* buffer, size_t bufferSize, size_t length) { + while (length > 0) { + const size_t bytesToProcess = std::min(bufferSize, length); + const size_t bytesRead = stream->read(buffer, bytesToProcess); + png_process_data(png_ptr, info_ptr, (png_bytep) buffer, bytesRead); + if (bytesRead < bytesToProcess) { + return false; + } + length -= bytesToProcess; + } + return true; +} + +bool AutoCleanPng::decodeBounds() { + if (setjmp(PNG_JMPBUF(fPng_ptr))) { + return false; + } + + png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr); + + // Arbitrary buffer size, though note that it matches (below) + // SkPngCodec::processData(). FIXME: Can we better suit this to the size of + // the PNG header? + constexpr size_t kBufferSize = 4096; + char buffer[kBufferSize]; + + { + // Parse the signature. + if (fStream->read(buffer, 8) < 8) { + return false; + } + + png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, 8); + } + + while (true) { + // Parse chunk length and type. + if (fStream->read(buffer, 8) < 8) { + // We have read to the end of the input without decoding bounds. + break; + } + + png_byte* chunk = reinterpret_cast(buffer); + const size_t length = png_get_uint_32(chunk); + + if (is_chunk(chunk, "IDAT")) { + this->infoCallback(length); + return true; + } + + png_process_data(fPng_ptr, fInfo_ptr, chunk, 8); + // Process the full chunk + CRC. + if (!process_data(fPng_ptr, fInfo_ptr, fStream, buffer, kBufferSize, length + 4)) { + return false; + } + } + + return false; +} + +bool SkPngCodec::processData() { + switch (setjmp(PNG_JMPBUF(fPng_ptr))) { + case kPngError: + // There was an error. Stop processing data. + // FIXME: Do we need to discard png_ptr? + return false; + case kStopDecoding: + // We decoded all the lines we want. + return true; + case kSetJmpOkay: + // Everything is okay. + break; + default: + // No other values should be passed to longjmp. + SkASSERT(false); + } + + // Arbitrary buffer size + constexpr size_t kBufferSize = 4096; + char buffer[kBufferSize]; + + bool iend = false; + while (true) { + size_t length; + if (fDecodedIdat) { + // Parse chunk length and type. + if (this->stream()->read(buffer, 8) < 8) { + break; + } + + png_byte* chunk = reinterpret_cast(buffer); + png_process_data(fPng_ptr, fInfo_ptr, chunk, 8); + if (is_chunk(chunk, "IEND")) { + iend = true; + } + + length = png_get_uint_32(chunk); + } else { + length = fIdatLength; + png_byte idat[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; + png_save_uint_32(idat, length); + png_process_data(fPng_ptr, fInfo_ptr, idat, 8); + fDecodedIdat = true; + } + + // Process the full chunk + CRC. + if (!process_data(fPng_ptr, fInfo_ptr, this->stream(), buffer, kBufferSize, length + 4) + || iend) { + break; + } + } + + return true; +} + +static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; + +static inline bool needs_premul(SkAlphaType dstAT, SkEncodedInfo::Alpha encodedAlpha) { + return kPremul_SkAlphaType == dstAT && SkEncodedInfo::kUnpremul_Alpha == encodedAlpha; +} + +// Note: SkColorTable claims to store SkPMColors, which is not necessarily the case here. +bool SkPngCodec::createColorTable(const SkImageInfo& dstInfo) { + + int numColors; + png_color* palette; + if (!png_get_PLTE(fPng_ptr, fInfo_ptr, &palette, &numColors)) { + return false; + } + + // Contents depend on tableColorType and our choice of if/when to premultiply: + // { kPremul, kUnpremul, kOpaque } x { RGBA, BGRA } + SkPMColor colorTable[256]; + SkColorType tableColorType = this->colorXform() ? kXformSrcColorType : dstInfo.colorType(); + + png_bytep alphas; + int numColorsWithAlpha = 0; + if (png_get_tRNS(fPng_ptr, fInfo_ptr, &alphas, &numColorsWithAlpha, nullptr)) { + bool premultiply = needs_premul(dstInfo.alphaType(), this->getEncodedInfo().alpha()); + + // Choose which function to use to create the color table. If the final destination's + // colortype is unpremultiplied, the color table will store unpremultiplied colors. + PackColorProc proc = choose_pack_color_proc(premultiply, tableColorType); + + for (int i = 0; i < numColorsWithAlpha; i++) { + // We don't have a function in SkOpts that combines a set of alphas with a set + // of RGBs. We could write one, but it's hardly worth it, given that this + // is such a small fraction of the total decode time. + colorTable[i] = proc(alphas[i], palette->red, palette->green, palette->blue); + palette++; + } + } + + if (numColorsWithAlpha < numColors) { + // The optimized code depends on a 3-byte png_color struct with the colors + // in RGB order. These checks make sure it is safe to use. + static_assert(3 == sizeof(png_color), "png_color struct has changed. Opts are broken."); +#ifdef SK_DEBUG + SkASSERT(&palette->red < &palette->green); + SkASSERT(&palette->green < &palette->blue); +#endif + + if (is_rgba(tableColorType)) { + SkOpts::RGB_to_RGB1(colorTable + numColorsWithAlpha, (const uint8_t*)palette, + numColors - numColorsWithAlpha); + } else { + SkOpts::RGB_to_BGR1(colorTable + numColorsWithAlpha, (const uint8_t*)palette, + numColors - numColorsWithAlpha); + } + } + + if (this->colorXform() && !this->xformOnDecode()) { + this->applyColorXform(colorTable, colorTable, numColors); + } + + // Pad the color table with the last color in the table (or black) in the case that + // invalid pixel indices exceed the number of colors in the table. + const int maxColors = 1 << fBitDepth; + if (numColors < maxColors) { + SkPMColor lastColor = numColors > 0 ? colorTable[numColors - 1] : SK_ColorBLACK; + sk_memset32(colorTable + numColors, lastColor, maxColors - numColors); + } + + fColorTable.reset(new SkColorTable(colorTable, maxColors)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Creation +/////////////////////////////////////////////////////////////////////////////// + +bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) { + return !png_sig_cmp((png_bytep) buf, (png_size_t)0, bytesRead); +} + +#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) + +static float png_fixed_point_to_float(png_fixed_point x) { + // We multiply by the same factor that libpng used to convert + // fixed point -> double. Since we want floats, we choose to + // do the conversion ourselves rather than convert + // fixed point -> double -> float. + return ((float) x) * 0.00001f; +} + +static float png_inverted_fixed_point_to_float(png_fixed_point x) { + // This is necessary because the gAMA chunk actually stores 1/gamma. + return 1.0f / png_fixed_point_to_float(x); +} + +#endif // LIBPNG >= 1.6 + +// If there is no color profile information, it will use sRGB. +std::unique_ptr read_color_profile(png_structp png_ptr, + png_infop info_ptr) { + +#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) + // First check for an ICC profile + png_bytep profile; + png_uint_32 length; + // The below variables are unused, however, we need to pass them in anyway or + // png_get_iCCP() will return nothing. + // Could knowing the |name| of the profile ever be interesting? Maybe for debugging? + png_charp name; + // The |compression| is uninteresting since: + // (1) libpng has already decompressed the profile for us. + // (2) "deflate" is the only mode of decompression that libpng supports. + int compression; + if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile, + &length)) { + auto data = SkData::MakeWithCopy(profile, length); + return SkEncodedInfo::ICCProfile::Make(std::move(data)); + } + + // Second, check for sRGB. + // Note that Blink does this first. This code checks ICC first, with the thinking that + // an image has both truly wants the potentially more specific ICC chunk, with sRGB as a + // backup in case the decoder does not support full color management. + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { + // sRGB chunks also store a rendering intent: Absolute, Relative, + // Perceptual, and Saturation. + // FIXME (scroggo): Extract this information from the sRGB chunk once + // we are able to handle this information in + // skcms_ICCProfile + return nullptr; + } + + // Default to SRGB gamut. + skcms_Matrix3x3 toXYZD50 = skcms_sRGB_profile()->toXYZD50; + // Next, check for chromaticities. + png_fixed_point chrm[8]; + png_fixed_point gamma; + if (png_get_cHRM_fixed(png_ptr, info_ptr, &chrm[0], &chrm[1], &chrm[2], &chrm[3], &chrm[4], + &chrm[5], &chrm[6], &chrm[7])) + { + float rx = png_fixed_point_to_float(chrm[2]); + float ry = png_fixed_point_to_float(chrm[3]); + float gx = png_fixed_point_to_float(chrm[4]); + float gy = png_fixed_point_to_float(chrm[5]); + float bx = png_fixed_point_to_float(chrm[6]); + float by = png_fixed_point_to_float(chrm[7]); + float wx = png_fixed_point_to_float(chrm[0]); + float wy = png_fixed_point_to_float(chrm[1]); + + skcms_Matrix3x3 tmp; + if (skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &tmp)) { + toXYZD50 = tmp; + } else { + // Note that Blink simply returns nullptr in this case. We'll fall + // back to srgb. + } + } + + skcms_TransferFunction fn; + if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { + fn.a = 1.0f; + fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f; + fn.g = png_inverted_fixed_point_to_float(gamma); + } else { + // Default to sRGB gamma if the image has color space information, + // but does not specify gamma. + // Note that Blink would again return nullptr in this case. + fn = *skcms_sRGB_TransferFunction(); + } + + skcms_ICCProfile skcmsProfile; + skcms_Init(&skcmsProfile); + skcms_SetTransferFunction(&skcmsProfile, &fn); + skcms_SetXYZD50(&skcmsProfile, &toXYZD50); + + return SkEncodedInfo::ICCProfile::Make(skcmsProfile); +#else // LIBPNG >= 1.6 + return nullptr; +#endif // LIBPNG >= 1.6 +} + +void SkPngCodec::allocateStorage(const SkImageInfo& dstInfo) { + switch (fXformMode) { + case kSwizzleOnly_XformMode: + break; + case kColorOnly_XformMode: + // Intentional fall through. A swizzler hasn't been created yet, but one will + // be created later if we are sampling. We'll go ahead and allocate + // enough memory to swizzle if necessary. + case kSwizzleColor_XformMode: { + const int bitsPerPixel = this->getEncodedInfo().bitsPerPixel(); + + // If we have more than 8-bits (per component) of precision, we will keep that + // extra precision. Otherwise, we will swizzle to RGBA_8888 before transforming. + const size_t bytesPerPixel = (bitsPerPixel > 32) ? bitsPerPixel / 8 : 4; + const size_t colorXformBytes = dstInfo.width() * bytesPerPixel; + fStorage.reset(colorXformBytes); + fColorXformSrcRow = fStorage.get(); + break; + } + } +} + +static skcms_PixelFormat png_select_xform_format(const SkEncodedInfo& info) { + // We use kRGB and kRGBA formats because color PNGs are always RGB or RGBA. + if (16 == info.bitsPerComponent()) { + if (SkEncodedInfo::kRGBA_Color == info.color()) { + return skcms_PixelFormat_RGBA_16161616BE; + } else if (SkEncodedInfo::kRGB_Color == info.color()) { + return skcms_PixelFormat_RGB_161616BE; + } + } else if (SkEncodedInfo::kGray_Color == info.color()) { + return skcms_PixelFormat_G_8; + } + + return skcms_PixelFormat_RGBA_8888; +} + +void SkPngCodec::applyXformRow(void* dst, const void* src) { + switch (fXformMode) { + case kSwizzleOnly_XformMode: + fSwizzler->swizzle(dst, (const uint8_t*) src); + break; + case kColorOnly_XformMode: + this->applyColorXform(dst, src, fXformWidth); + break; + case kSwizzleColor_XformMode: + fSwizzler->swizzle(fColorXformSrcRow, (const uint8_t*) src); + this->applyColorXform(dst, fColorXformSrcRow, fXformWidth); + break; + } +} + +static SkCodec::Result log_and_return_error(bool success) { + if (success) return SkCodec::kIncompleteInput; +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkAndroidFrameworkUtils::SafetyNetLog("117838472"); +#endif + return SkCodec::kErrorInInput; +} + +class SkPngNormalDecoder : public SkPngCodec { +public: + SkPngNormalDecoder(SkEncodedInfo&& info, std::unique_ptr stream, + SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth) + : INHERITED(std::move(info), std::move(stream), reader, png_ptr, info_ptr, bitDepth) + , fRowsWrittenToOutput(0) + , fDst(nullptr) + , fRowBytes(0) + , fFirstRow(0) + , fLastRow(0) + {} + + static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { + GetDecoder(png_ptr)->allRowsCallback(row, rowNum); + } + + static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { + GetDecoder(png_ptr)->rowCallback(row, rowNum); + } + +private: + int fRowsWrittenToOutput; + void* fDst; + size_t fRowBytes; + + // Variables for partial decode + int fFirstRow; // FIXME: Move to baseclass? + int fLastRow; + int fRowsNeeded; + + typedef SkPngCodec INHERITED; + + static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) { + return static_cast(png_get_progressive_ptr(png_ptr)); + } + + Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { + const int height = this->dimensions().height(); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, AllRowsCallback, nullptr); + fDst = dst; + fRowBytes = rowBytes; + + fRowsWrittenToOutput = 0; + fFirstRow = 0; + fLastRow = height - 1; + + const bool success = this->processData(); + if (success && fRowsWrittenToOutput == height) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fRowsWrittenToOutput; + } + + return log_and_return_error(success); + } + + void allRowsCallback(png_bytep row, int rowNum) { + SkASSERT(rowNum == fRowsWrittenToOutput); + fRowsWrittenToOutput++; + this->applyXformRow(fDst, row); + fDst = SkTAddOffset(fDst, fRowBytes); + } + + void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, RowCallback, nullptr); + fFirstRow = firstRow; + fLastRow = lastRow; + fDst = dst; + fRowBytes = rowBytes; + fRowsWrittenToOutput = 0; + fRowsNeeded = fLastRow - fFirstRow + 1; + } + + Result decode(int* rowsDecoded) override { + if (this->swizzler()) { + const int sampleY = this->swizzler()->sampleY(); + fRowsNeeded = get_scaled_dimension(fLastRow - fFirstRow + 1, sampleY); + } + + const bool success = this->processData(); + if (success && fRowsWrittenToOutput == fRowsNeeded) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fRowsWrittenToOutput; + } + + return log_and_return_error(success); + } + + void rowCallback(png_bytep row, int rowNum) { + if (rowNum < fFirstRow) { + // Ignore this row. + return; + } + + SkASSERT(rowNum <= fLastRow); + SkASSERT(fRowsWrittenToOutput < fRowsNeeded); + + // If there is no swizzler, all rows are needed. + if (!this->swizzler() || this->swizzler()->rowNeeded(rowNum - fFirstRow)) { + this->applyXformRow(fDst, row); + fDst = SkTAddOffset(fDst, fRowBytes); + fRowsWrittenToOutput++; + } + + if (fRowsWrittenToOutput == fRowsNeeded) { + // Fake error to stop decoding scanlines. + longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding); + } + } +}; + +class SkPngInterlacedDecoder : public SkPngCodec { +public: + SkPngInterlacedDecoder(SkEncodedInfo&& info, std::unique_ptr stream, + SkPngChunkReader* reader, png_structp png_ptr, + png_infop info_ptr, int bitDepth, int numberPasses) + : INHERITED(std::move(info), std::move(stream), reader, png_ptr, info_ptr, bitDepth) + , fNumberPasses(numberPasses) + , fFirstRow(0) + , fLastRow(0) + , fLinesDecoded(0) + , fInterlacedComplete(false) + , fPng_rowbytes(0) + {} + + static void InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) { + auto decoder = static_cast(png_get_progressive_ptr(png_ptr)); + decoder->interlacedRowCallback(row, rowNum, pass); + } + +private: + const int fNumberPasses; + int fFirstRow; + int fLastRow; + void* fDst; + size_t fRowBytes; + int fLinesDecoded; + bool fInterlacedComplete; + size_t fPng_rowbytes; + SkAutoTMalloc fInterlaceBuffer; + + typedef SkPngCodec INHERITED; + + // FIXME: Currently sharing interlaced callback for all rows and subset. It's not + // as expensive as the subset version of non-interlaced, but it still does extra + // work. + void interlacedRowCallback(png_bytep row, int rowNum, int pass) { + if (rowNum < fFirstRow || rowNum > fLastRow || fInterlacedComplete) { + // Ignore this row + return; + } + + png_bytep oldRow = fInterlaceBuffer.get() + (rowNum - fFirstRow) * fPng_rowbytes; + png_progressive_combine_row(this->png_ptr(), oldRow, row); + + if (0 == pass) { + // The first pass initializes all rows. + SkASSERT(row); + SkASSERT(fLinesDecoded == rowNum - fFirstRow); + fLinesDecoded++; + } else { + SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1); + if (fNumberPasses - 1 == pass && rowNum == fLastRow) { + // Last pass, and we have read all of the rows we care about. + fInterlacedComplete = true; + if (fLastRow != this->dimensions().height() - 1 || + (this->swizzler() && this->swizzler()->sampleY() != 1)) { + // Fake error to stop decoding scanlines. Only stop if we're not decoding the + // whole image, in which case processing the rest of the image might be + // expensive. When decoding the whole image, read through the IEND chunk to + // preserve Android behavior of leaving the input stream in the right place. + longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding); + } + } + } + } + + Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { + const int height = this->dimensions().height(); + this->setUpInterlaceBuffer(height); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, + nullptr); + + fFirstRow = 0; + fLastRow = height - 1; + fLinesDecoded = 0; + + const bool success = this->processData(); + png_bytep srcRow = fInterlaceBuffer.get(); + // FIXME: When resuming, this may rewrite rows that did not change. + for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) { + this->applyXformRow(dst, srcRow); + dst = SkTAddOffset(dst, rowBytes); + srcRow = SkTAddOffset(srcRow, fPng_rowbytes); + } + if (success && fInterlacedComplete) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fLinesDecoded; + } + + return log_and_return_error(success); + } + + void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { + // FIXME: We could skip rows in the interlace buffer that we won't put in the output. + this->setUpInterlaceBuffer(lastRow - firstRow + 1); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr); + fFirstRow = firstRow; + fLastRow = lastRow; + fDst = dst; + fRowBytes = rowBytes; + fLinesDecoded = 0; + } + + Result decode(int* rowsDecoded) override { + const bool success = this->processData(); + + // Now apply Xforms on all the rows that were decoded. + if (!fLinesDecoded) { + if (rowsDecoded) { + *rowsDecoded = 0; + } + return log_and_return_error(success); + } + + const int sampleY = this->swizzler() ? this->swizzler()->sampleY() : 1; + const int rowsNeeded = get_scaled_dimension(fLastRow - fFirstRow + 1, sampleY); + + // FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it + // may be too tricky/expensive to handle that correctly. + + // Offset srcRow by get_start_coord rows. We do not need to account for fFirstRow, + // since the first row in fInterlaceBuffer corresponds to fFirstRow. + int srcRow = get_start_coord(sampleY); + void* dst = fDst; + int rowsWrittenToOutput = 0; + while (rowsWrittenToOutput < rowsNeeded && srcRow < fLinesDecoded) { + png_bytep src = SkTAddOffset(fInterlaceBuffer.get(), fPng_rowbytes * srcRow); + this->applyXformRow(dst, src); + dst = SkTAddOffset(dst, fRowBytes); + + rowsWrittenToOutput++; + srcRow += sampleY; + } + + if (success && fInterlacedComplete) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = rowsWrittenToOutput; + } + return log_and_return_error(success); + } + + void setUpInterlaceBuffer(int height) { + fPng_rowbytes = png_get_rowbytes(this->png_ptr(), this->info_ptr()); + fInterlaceBuffer.reset(fPng_rowbytes * height); + fInterlacedComplete = false; + } +}; + +// Reads the header and initializes the output fields, if not NULL. +// +// @param stream Input data. Will be read to get enough information to properly +// setup the codec. +// @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL. +// If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is +// expected to continue to own it for the lifetime of the png_ptr. +// @param outCodec Optional output variable. If non-NULL, will be set to a new +// SkPngCodec on success. +// @param png_ptrp Optional output variable. If non-NULL, will be set to a new +// png_structp on success. +// @param info_ptrp Optional output variable. If non-NULL, will be set to a new +// png_infop on success; +// @return if kSuccess, the caller is responsible for calling +// png_destroy_read_struct(png_ptrp, info_ptrp). +// Otherwise, the passed in fields (except stream) are unchanged. +static SkCodec::Result read_header(SkStream* stream, SkPngChunkReader* chunkReader, + SkCodec** outCodec, + png_structp* png_ptrp, png_infop* info_ptrp) { + // The image is known to be a PNG. Decode enough to know the SkImageInfo. + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, + sk_error_fn, sk_warning_fn); + if (!png_ptr) { + return SkCodec::kInternalError; + } + +#ifdef PNG_SET_OPTION_SUPPORTED + // This setting ensures that we display images with incorrect CMF bytes. + // See crbug.com/807324. + png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); +#endif + + AutoCleanPng autoClean(png_ptr, stream, chunkReader, outCodec); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) { + return SkCodec::kInternalError; + } + + autoClean.setInfoPtr(info_ptr); + + if (setjmp(PNG_JMPBUF(png_ptr))) { + return SkCodec::kInvalidInput; + } + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + // Hookup our chunkReader so we can see any user-chunks the caller may be interested in. + // This needs to be installed before we read the png header. Android may store ninepatch + // chunks in the header. + if (chunkReader) { + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0); + png_set_read_user_chunk_fn(png_ptr, (png_voidp) chunkReader, sk_read_user_chunk); + } +#endif + + const bool decodedBounds = autoClean.decodeBounds(); + + if (!decodedBounds) { + return SkCodec::kIncompleteInput; + } + + // On success, decodeBounds releases ownership of png_ptr and info_ptr. + if (png_ptrp) { + *png_ptrp = png_ptr; + } + if (info_ptrp) { + *info_ptrp = info_ptr; + } + + // decodeBounds takes care of setting outCodec + if (outCodec) { + SkASSERT(*outCodec); + } + return SkCodec::kSuccess; +} + +void AutoCleanPng::infoCallback(size_t idatLength) { + png_uint_32 origWidth, origHeight; + int bitDepth, encodedColorType; + png_get_IHDR(fPng_ptr, fInfo_ptr, &origWidth, &origHeight, &bitDepth, + &encodedColorType, nullptr, nullptr, nullptr); + + // TODO: Should we support 16-bits of precision for gray images? + if (bitDepth == 16 && (PNG_COLOR_TYPE_GRAY == encodedColorType || + PNG_COLOR_TYPE_GRAY_ALPHA == encodedColorType)) { + bitDepth = 8; + png_set_strip_16(fPng_ptr); + } + + // Now determine the default colorType and alphaType and set the required transforms. + // Often, we depend on SkSwizzler to perform any transforms that we need. However, we + // still depend on libpng for many of the rare and PNG-specific cases. + SkEncodedInfo::Color color; + SkEncodedInfo::Alpha alpha; + switch (encodedColorType) { + case PNG_COLOR_TYPE_PALETTE: + // Extract multiple pixels with bit depths of 1, 2, and 4 from a single + // byte into separate bytes (useful for paletted and grayscale images). + if (bitDepth < 8) { + // TODO: Should we use SkSwizzler here? + bitDepth = 8; + png_set_packing(fPng_ptr); + } + + color = SkEncodedInfo::kPalette_Color; + // Set the alpha depending on if a transparency chunk exists. + alpha = png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS) ? + SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha; + break; + case PNG_COLOR_TYPE_RGB: + if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { + // Convert to RGBA if transparency chunk exists. + png_set_tRNS_to_alpha(fPng_ptr); + color = SkEncodedInfo::kRGBA_Color; + alpha = SkEncodedInfo::kBinary_Alpha; + } else { + color = SkEncodedInfo::kRGB_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + case PNG_COLOR_TYPE_GRAY: + // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. + if (bitDepth < 8) { + // TODO: Should we use SkSwizzler here? + bitDepth = 8; + png_set_expand_gray_1_2_4_to_8(fPng_ptr); + } + + if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(fPng_ptr); + color = SkEncodedInfo::kGrayAlpha_Color; + alpha = SkEncodedInfo::kBinary_Alpha; + } else { + color = SkEncodedInfo::kGray_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + color = SkEncodedInfo::kGrayAlpha_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + break; + case PNG_COLOR_TYPE_RGBA: + color = SkEncodedInfo::kRGBA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + break; + default: + // All the color types have been covered above. + SkASSERT(false); + color = SkEncodedInfo::kRGBA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } + + const int numberPasses = png_set_interlace_handling(fPng_ptr); + + if (fOutCodec) { + SkASSERT(nullptr == *fOutCodec); + auto profile = read_color_profile(fPng_ptr, fInfo_ptr); + if (profile) { + switch (profile->profile()->data_color_space) { + case skcms_Signature_CMYK: + profile = nullptr; + break; + case skcms_Signature_Gray: + if (SkEncodedInfo::kGray_Color != color && + SkEncodedInfo::kGrayAlpha_Color != color) + { + profile = nullptr; + } + break; + default: + break; + } + } + + if (encodedColorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_color_8p sigBits; + if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) { + if (8 == sigBits->alpha && kGraySigBit_GrayAlphaIsJustAlpha == sigBits->gray) { + color = SkEncodedInfo::kXAlpha_Color; + } + } + } else if (SkEncodedInfo::kOpaque_Alpha == alpha) { + png_color_8p sigBits; + if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) { + if (5 == sigBits->red && 6 == sigBits->green && 5 == sigBits->blue) { + // Recommend a decode to 565 if the sBIT indicates 565. + color = SkEncodedInfo::k565_Color; + } + } + } + + SkEncodedInfo encodedInfo = SkEncodedInfo::Make(origWidth, origHeight, color, alpha, + bitDepth, std::move(profile)); + if (1 == numberPasses) { + *fOutCodec = new SkPngNormalDecoder(std::move(encodedInfo), + std::unique_ptr(fStream), fChunkReader, fPng_ptr, fInfo_ptr, bitDepth); + } else { + *fOutCodec = new SkPngInterlacedDecoder(std::move(encodedInfo), + std::unique_ptr(fStream), fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, + numberPasses); + } + static_cast(*fOutCodec)->setIdatLength(idatLength); + } + + // Release the pointers, which are now owned by the codec or the caller is expected to + // take ownership. + this->releasePngPtrs(); +} + +SkPngCodec::SkPngCodec(SkEncodedInfo&& encodedInfo, std::unique_ptr stream, + SkPngChunkReader* chunkReader, void* png_ptr, void* info_ptr, int bitDepth) + : INHERITED(std::move(encodedInfo), png_select_xform_format(encodedInfo), std::move(stream)) + , fPngChunkReader(SkSafeRef(chunkReader)) + , fPng_ptr(png_ptr) + , fInfo_ptr(info_ptr) + , fColorXformSrcRow(nullptr) + , fBitDepth(bitDepth) + , fIdatLength(0) + , fDecodedIdat(false) +{} + +SkPngCodec::~SkPngCodec() { + this->destroyReadStruct(); +} + +void SkPngCodec::destroyReadStruct() { + if (fPng_ptr) { + // We will never have a nullptr fInfo_ptr with a non-nullptr fPng_ptr + SkASSERT(fInfo_ptr); + png_destroy_read_struct((png_struct**)&fPng_ptr, (png_info**)&fInfo_ptr, nullptr); + fPng_ptr = nullptr; + fInfo_ptr = nullptr; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Getting the pixels +/////////////////////////////////////////////////////////////////////////////// + +SkCodec::Result SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options) { + if (setjmp(PNG_JMPBUF((png_struct*)fPng_ptr))) { + SkCodecPrintf("Failed on png_read_update_info.\n"); + return kInvalidInput; + } + png_read_update_info(fPng_ptr, fInfo_ptr); + + // Reset fSwizzler and this->colorXform(). We can't do this in onRewind() because the + // interlaced scanline decoder may need to rewind. + fSwizzler.reset(nullptr); + + // If skcms directly supports the encoded PNG format, we should skip format + // conversion in the swizzler (or skip swizzling altogether). + bool skipFormatConversion = false; + switch (this->getEncodedInfo().color()) { + case SkEncodedInfo::kRGB_Color: + if (this->getEncodedInfo().bitsPerComponent() != 16) { + break; + } + + // Fall through + case SkEncodedInfo::kRGBA_Color: + case SkEncodedInfo::kGray_Color: + skipFormatConversion = this->colorXform(); + break; + default: + break; + } + if (skipFormatConversion && !options.fSubset) { + fXformMode = kColorOnly_XformMode; + return kSuccess; + } + + if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) { + if (!this->createColorTable(dstInfo)) { + return kInvalidInput; + } + } + + this->initializeSwizzler(dstInfo, options, skipFormatConversion); + return kSuccess; +} + +void SkPngCodec::initializeXformParams() { + switch (fXformMode) { + case kColorOnly_XformMode: + fXformWidth = this->dstInfo().width(); + break; + case kSwizzleColor_XformMode: + fXformWidth = this->swizzler()->swizzleWidth(); + break; + default: + break; + } +} + +void SkPngCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, + bool skipFormatConversion) { + SkImageInfo swizzlerInfo = dstInfo; + Options swizzlerOptions = options; + fXformMode = kSwizzleOnly_XformMode; + if (this->colorXform() && this->xformOnDecode()) { + if (SkEncodedInfo::kGray_Color == this->getEncodedInfo().color()) { + swizzlerInfo = swizzlerInfo.makeColorType(kGray_8_SkColorType); + } else { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + } + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + + fXformMode = kSwizzleColor_XformMode; + + // Here, we swizzle into temporary memory, which is not zero initialized. + // FIXME (msarett): + // Is this a problem? + swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; + } + + if (skipFormatConversion) { + // We cannot skip format conversion when there is a color table. + SkASSERT(!fColorTable); + int srcBPP = 0; + switch (this->getEncodedInfo().color()) { + case SkEncodedInfo::kRGB_Color: + SkASSERT(this->getEncodedInfo().bitsPerComponent() == 16); + srcBPP = 6; + break; + case SkEncodedInfo::kRGBA_Color: + srcBPP = this->getEncodedInfo().bitsPerComponent() / 2; + break; + case SkEncodedInfo::kGray_Color: + srcBPP = 1; + break; + default: + SkASSERT(false); + break; + } + fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerInfo, swizzlerOptions); + } else { + const SkPMColor* colors = get_color_ptr(fColorTable.get()); + fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), colors, swizzlerInfo, + swizzlerOptions); + } + SkASSERT(fSwizzler); +} + +SkSampler* SkPngCodec::getSampler(bool createIfNecessary) { + if (fSwizzler || !createIfNecessary) { + return fSwizzler.get(); + } + + this->initializeSwizzler(this->dstInfo(), this->options(), true); + return fSwizzler.get(); +} + +bool SkPngCodec::onRewind() { + // This sets fPng_ptr and fInfo_ptr to nullptr. If read_header + // succeeds, they will be repopulated, and if it fails, they will + // remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will + // come through this function which will rewind and again attempt + // to reinitialize them. + this->destroyReadStruct(); + + png_structp png_ptr; + png_infop info_ptr; + if (kSuccess != read_header(this->stream(), fPngChunkReader.get(), nullptr, + &png_ptr, &info_ptr)) { + return false; + } + + fPng_ptr = png_ptr; + fInfo_ptr = info_ptr; + fDecodedIdat = false; + return true; +} + +SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t rowBytes, const Options& options, + int* rowsDecoded) { + Result result = this->initializeXforms(dstInfo, options); + if (kSuccess != result) { + return result; + } + + if (options.fSubset) { + return kUnimplemented; + } + + this->allocateStorage(dstInfo); + this->initializeXformParams(); + return this->decodeAllRows(dst, rowBytes, rowsDecoded); +} + +SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* dst, size_t rowBytes, const SkCodec::Options& options) { + Result result = this->initializeXforms(dstInfo, options); + if (kSuccess != result) { + return result; + } + + this->allocateStorage(dstInfo); + + int firstRow, lastRow; + if (options.fSubset) { + firstRow = options.fSubset->top(); + lastRow = options.fSubset->bottom() - 1; + } else { + firstRow = 0; + lastRow = dstInfo.height() - 1; + } + this->setRange(firstRow, lastRow, dst, rowBytes); + return kSuccess; +} + +SkCodec::Result SkPngCodec::onIncrementalDecode(int* rowsDecoded) { + // FIXME: Only necessary on the first call. + this->initializeXformParams(); + + return this->decode(rowsDecoded); +} + +std::unique_ptr SkPngCodec::MakeFromStream(std::unique_ptr stream, + Result* result, SkPngChunkReader* chunkReader) { + SkCodec* outCodec = nullptr; + *result = read_header(stream.get(), chunkReader, &outCodec, nullptr, nullptr); + if (kSuccess == *result) { + // Codec has taken ownership of the stream. + SkASSERT(outCodec); + stream.release(); + } + return std::unique_ptr(outCodec); +} diff --git a/gfx/skia/skia/src/codec/SkPngCodec.h b/gfx/skia/skia/src/codec/SkPngCodec.h new file mode 100644 index 0000000000..423331647c --- /dev/null +++ b/gfx/skia/skia/src/codec/SkPngCodec.h @@ -0,0 +1,121 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPngCodec_DEFINED +#define SkPngCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPngChunkReader.h" +#include "include/core/SkRefCnt.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +class SkStream; + +class SkPngCodec : public SkCodec { +public: + static bool IsPng(const char*, size_t); + + // Assume IsPng was called and returned true. + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*, + SkPngChunkReader* = nullptr); + + // FIXME (scroggo): Temporarily needed by AutoCleanPng. + void setIdatLength(size_t len) { fIdatLength = len; } + + ~SkPngCodec() override; + +protected: + // We hold the png_ptr and info_ptr as voidp to avoid having to include png.h + // or forward declare their types here. voidp auto-casts to the real pointer types. + struct voidp { + voidp(void* ptr) : fPtr(ptr) {} + + template + operator T*() const { return (T*)fPtr; } + + explicit operator bool() const { return fPtr != nullptr; } + + void* fPtr; + }; + + SkPngCodec(SkEncodedInfo&&, std::unique_ptr, SkPngChunkReader*, + void* png_ptr, void* info_ptr, int bitDepth); + + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) + override; + SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kPNG; } + bool onRewind() override; + + SkSampler* getSampler(bool createIfNecessary) override; + void applyXformRow(void* dst, const void* src); + + voidp png_ptr() { return fPng_ptr; } + voidp info_ptr() { return fInfo_ptr; } + + SkSwizzler* swizzler() { return fSwizzler.get(); } + + // Initialize variables used by applyXformRow. + void initializeXformParams(); + + /** + * Pass available input to libpng to process it. + * + * libpng will call any relevant callbacks installed. This will continue decoding + * until it reaches the end of the file, or until a callback tells libpng to stop. + */ + bool processData(); + + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const SkCodec::Options&) override; + Result onIncrementalDecode(int*) override; + + sk_sp fPngChunkReader; + voidp fPng_ptr; + voidp fInfo_ptr; + + // These are stored here so they can be used both by normal decoding and scanline decoding. + sk_sp fColorTable; // May be unpremul. + std::unique_ptr fSwizzler; + SkAutoTMalloc fStorage; + void* fColorXformSrcRow; + const int fBitDepth; + +private: + + enum XformMode { + // Requires only a swizzle pass. + kSwizzleOnly_XformMode, + + // Requires only a color xform pass. + kColorOnly_XformMode, + + // Requires a swizzle and a color xform. + kSwizzleColor_XformMode, + }; + + bool createColorTable(const SkImageInfo& dstInfo); + // Helper to set up swizzler, color xforms, and color table. Also calls png_read_update_info. + SkCodec::Result initializeXforms(const SkImageInfo& dstInfo, const Options&); + void initializeSwizzler(const SkImageInfo& dstInfo, const Options&, bool skipFormatConversion); + void allocateStorage(const SkImageInfo& dstInfo); + void destroyReadStruct(); + + virtual Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) = 0; + virtual void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) = 0; + virtual Result decode(int* rowsDecoded) = 0; + + XformMode fXformMode; + int fXformWidth; + + size_t fIdatLength; + bool fDecodedIdat; + + typedef SkCodec INHERITED; +}; +#endif // SkPngCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkPngPriv.h b/gfx/skia/skia/src/codec/SkPngPriv.h new file mode 100644 index 0000000000..5760179b60 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkPngPriv.h @@ -0,0 +1,19 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPngPriv_DEFINED +#define SkPngPriv_DEFINED + +#include "include/core/SkTypes.h" + +// We store kAlpha_8 images as GrayAlpha in png. Our private signal is significant bits for gray. +// If that is set to 1, we assume the gray channel can be ignored, and we output just alpha. +// We tried 0 at first, but png doesn't like a 0 sigbit for a channel it expects, hence we chose 1. + +static constexpr int kGraySigBit_GrayAlphaIsJustAlpha = 1; + +#endif diff --git a/gfx/skia/skia/src/codec/SkRawCodec.cpp b/gfx/skia/skia/src/codec/SkRawCodec.cpp new file mode 100644 index 0000000000..f8dff7a862 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkRawCodec.cpp @@ -0,0 +1,797 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkJpegCodec.h" +#include "src/codec/SkRawCodec.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkStreamPriv.h" +#include "src/core/SkTaskGroup.h" + +#include "dng_area_task.h" +#include "dng_color_space.h" +#include "dng_errors.h" +#include "dng_exceptions.h" +#include "dng_host.h" +#include "dng_info.h" +#include "dng_memory.h" +#include "dng_render.h" +#include "dng_stream.h" + +#include "src/piex.h" + +#include // for std::round,floor,ceil +#include + +namespace { + +// Caluclates the number of tiles of tile_size that fit into the area in vertical and horizontal +// directions. +dng_point num_tiles_in_area(const dng_point &areaSize, + const dng_point_real64 &tileSize) { + // FIXME: Add a ceil_div() helper in SkCodecPriv.h + return dng_point(static_cast((areaSize.v + tileSize.v - 1) / tileSize.v), + static_cast((areaSize.h + tileSize.h - 1) / tileSize.h)); +} + +int num_tasks_required(const dng_point& tilesInTask, + const dng_point& tilesInArea) { + return ((tilesInArea.v + tilesInTask.v - 1) / tilesInTask.v) * + ((tilesInArea.h + tilesInTask.h - 1) / tilesInTask.h); +} + +// Calculate the number of tiles to process per task, taking into account the maximum number of +// tasks. It prefers to increase horizontally for better locality of reference. +dng_point num_tiles_per_task(const int maxTasks, + const dng_point &tilesInArea) { + dng_point tilesInTask = {1, 1}; + while (num_tasks_required(tilesInTask, tilesInArea) > maxTasks) { + if (tilesInTask.h < tilesInArea.h) { + ++tilesInTask.h; + } else if (tilesInTask.v < tilesInArea.v) { + ++tilesInTask.v; + } else { + ThrowProgramError("num_tiles_per_task calculation is wrong."); + } + } + return tilesInTask; +} + +std::vector compute_task_areas(const int maxTasks, const dng_rect& area, + const dng_point& tileSize) { + std::vector taskAreas; + const dng_point tilesInArea = num_tiles_in_area(area.Size(), tileSize); + const dng_point tilesPerTask = num_tiles_per_task(maxTasks, tilesInArea); + const dng_point taskAreaSize = {tilesPerTask.v * tileSize.v, + tilesPerTask.h * tileSize.h}; + for (int v = 0; v < tilesInArea.v; v += tilesPerTask.v) { + for (int h = 0; h < tilesInArea.h; h += tilesPerTask.h) { + dng_rect taskArea; + taskArea.t = area.t + v * tileSize.v; + taskArea.l = area.l + h * tileSize.h; + taskArea.b = Min_int32(taskArea.t + taskAreaSize.v, area.b); + taskArea.r = Min_int32(taskArea.l + taskAreaSize.h, area.r); + + taskAreas.push_back(taskArea); + } + } + return taskAreas; +} + +class SkDngHost : public dng_host { +public: + explicit SkDngHost(dng_memory_allocator* allocater) : dng_host(allocater) {} + + void PerformAreaTask(dng_area_task& task, const dng_rect& area) override { + SkTaskGroup taskGroup; + + // tileSize is typically 256x256 + const dng_point tileSize(task.FindTileSize(area)); + const std::vector taskAreas = compute_task_areas(this->PerformAreaTaskThreads(), + area, tileSize); + const int numTasks = static_cast(taskAreas.size()); + + SkMutex mutex; + SkTArray exceptions; + task.Start(numTasks, tileSize, &Allocator(), Sniffer()); + for (int taskIndex = 0; taskIndex < numTasks; ++taskIndex) { + taskGroup.add([&mutex, &exceptions, &task, this, taskIndex, taskAreas, tileSize] { + try { + task.ProcessOnThread(taskIndex, taskAreas[taskIndex], tileSize, this->Sniffer()); + } catch (dng_exception& exception) { + SkAutoMutexExclusive lock(mutex); + exceptions.push_back(exception); + } catch (...) { + SkAutoMutexExclusive lock(mutex); + exceptions.push_back(dng_exception(dng_error_unknown)); + } + }); + } + + taskGroup.wait(); + task.Finish(numTasks); + + // We only re-throw the first exception. + if (!exceptions.empty()) { + Throw_dng_error(exceptions.front().ErrorCode(), nullptr, nullptr); + } + } + + uint32 PerformAreaTaskThreads() override { +#ifdef SK_BUILD_FOR_ANDROID + // Only use 1 thread. DNGs with the warp effect require a lot of memory, + // and the amount of memory required scales linearly with the number of + // threads. The sample used in CTS requires over 500 MB, so even two + // threads is significantly expensive. There is no good way to tell + // whether the image has the warp effect. + return 1; +#else + return kMaxMPThreads; +#endif + } + +private: + typedef dng_host INHERITED; +}; + +// T must be unsigned type. +template +bool safe_add_to_size_t(T arg1, T arg2, size_t* result) { + SkASSERT(arg1 >= 0); + SkASSERT(arg2 >= 0); + if (arg1 >= 0 && arg2 <= std::numeric_limits::max() - arg1) { + T sum = arg1 + arg2; + if (sum <= std::numeric_limits::max()) { + *result = static_cast(sum); + return true; + } + } + return false; +} + +bool is_asset_stream(const SkStream& stream) { + return stream.hasLength() && stream.hasPosition(); +} + +} // namespace + +class SkRawStream { +public: + virtual ~SkRawStream() {} + + /* + * Gets the length of the stream. Depending on the type of stream, this may require reading to + * the end of the stream. + */ + virtual uint64 getLength() = 0; + + virtual bool read(void* data, size_t offset, size_t length) = 0; + + /* + * Creates an SkMemoryStream from the offset with size. + * Note: for performance reason, this function is destructive to the SkRawStream. One should + * abandon current object after the function call. + */ + virtual std::unique_ptr transferBuffer(size_t offset, size_t size) = 0; +}; + +class SkRawLimitedDynamicMemoryWStream : public SkDynamicMemoryWStream { +public: + ~SkRawLimitedDynamicMemoryWStream() override {} + + bool write(const void* buffer, size_t size) override { + size_t newSize; + if (!safe_add_to_size_t(this->bytesWritten(), size, &newSize) || + newSize > kMaxStreamSize) + { + SkCodecPrintf("Error: Stream size exceeds the limit.\n"); + return false; + } + return this->INHERITED::write(buffer, size); + } + +private: + // Most of valid RAW images will not be larger than 100MB. This limit is helpful to avoid + // streaming too large data chunk. We can always adjust the limit here if we need. + const size_t kMaxStreamSize = 100 * 1024 * 1024; // 100MB + + typedef SkDynamicMemoryWStream INHERITED; +}; + +// Note: the maximum buffer size is 100MB (limited by SkRawLimitedDynamicMemoryWStream). +class SkRawBufferedStream : public SkRawStream { +public: + explicit SkRawBufferedStream(std::unique_ptr stream) + : fStream(std::move(stream)) + , fWholeStreamRead(false) + { + // Only use SkRawBufferedStream when the stream is not an asset stream. + SkASSERT(!is_asset_stream(*fStream)); + } + + ~SkRawBufferedStream() override {} + + uint64 getLength() override { + if (!this->bufferMoreData(kReadToEnd)) { // read whole stream + ThrowReadFile(); + } + return fStreamBuffer.bytesWritten(); + } + + bool read(void* data, size_t offset, size_t length) override { + if (length == 0) { + return true; + } + + size_t sum; + if (!safe_add_to_size_t(offset, length, &sum)) { + return false; + } + + return this->bufferMoreData(sum) && fStreamBuffer.read(data, offset, length); + } + + std::unique_ptr transferBuffer(size_t offset, size_t size) override { + sk_sp data(SkData::MakeUninitialized(size)); + if (offset > fStreamBuffer.bytesWritten()) { + // If the offset is not buffered, read from fStream directly and skip the buffering. + const size_t skipLength = offset - fStreamBuffer.bytesWritten(); + if (fStream->skip(skipLength) != skipLength) { + return nullptr; + } + const size_t bytesRead = fStream->read(data->writable_data(), size); + if (bytesRead < size) { + data = SkData::MakeSubset(data.get(), 0, bytesRead); + } + } else { + const size_t alreadyBuffered = SkTMin(fStreamBuffer.bytesWritten() - offset, size); + if (alreadyBuffered > 0 && + !fStreamBuffer.read(data->writable_data(), offset, alreadyBuffered)) { + return nullptr; + } + + const size_t remaining = size - alreadyBuffered; + if (remaining) { + auto* dst = static_cast(data->writable_data()) + alreadyBuffered; + const size_t bytesRead = fStream->read(dst, remaining); + size_t newSize; + if (bytesRead < remaining) { + if (!safe_add_to_size_t(alreadyBuffered, bytesRead, &newSize)) { + return nullptr; + } + data = SkData::MakeSubset(data.get(), 0, newSize); + } + } + } + return SkMemoryStream::Make(data); + } + +private: + // Note: if the newSize == kReadToEnd (0), this function will read to the end of stream. + bool bufferMoreData(size_t newSize) { + if (newSize == kReadToEnd) { + if (fWholeStreamRead) { // already read-to-end. + return true; + } + + // TODO: optimize for the special case when the input is SkMemoryStream. + return SkStreamCopy(&fStreamBuffer, fStream.get()); + } + + if (newSize <= fStreamBuffer.bytesWritten()) { // already buffered to newSize + return true; + } + if (fWholeStreamRead) { // newSize is larger than the whole stream. + return false; + } + + // Try to read at least 8192 bytes to avoid to many small reads. + const size_t kMinSizeToRead = 8192; + const size_t sizeRequested = newSize - fStreamBuffer.bytesWritten(); + const size_t sizeToRead = SkTMax(kMinSizeToRead, sizeRequested); + SkAutoSTMalloc tempBuffer(sizeToRead); + const size_t bytesRead = fStream->read(tempBuffer.get(), sizeToRead); + if (bytesRead < sizeRequested) { + return false; + } + return fStreamBuffer.write(tempBuffer.get(), bytesRead); + } + + std::unique_ptr fStream; + bool fWholeStreamRead; + + // Use a size-limited stream to avoid holding too huge buffer. + SkRawLimitedDynamicMemoryWStream fStreamBuffer; + + const size_t kReadToEnd = 0; +}; + +class SkRawAssetStream : public SkRawStream { +public: + explicit SkRawAssetStream(std::unique_ptr stream) + : fStream(std::move(stream)) + { + // Only use SkRawAssetStream when the stream is an asset stream. + SkASSERT(is_asset_stream(*fStream)); + } + + ~SkRawAssetStream() override {} + + uint64 getLength() override { + return fStream->getLength(); + } + + + bool read(void* data, size_t offset, size_t length) override { + if (length == 0) { + return true; + } + + size_t sum; + if (!safe_add_to_size_t(offset, length, &sum)) { + return false; + } + + return fStream->seek(offset) && (fStream->read(data, length) == length); + } + + std::unique_ptr transferBuffer(size_t offset, size_t size) override { + if (fStream->getLength() < offset) { + return nullptr; + } + + size_t sum; + if (!safe_add_to_size_t(offset, size, &sum)) { + return nullptr; + } + + // This will allow read less than the requested "size", because the JPEG codec wants to + // handle also a partial JPEG file. + const size_t bytesToRead = SkTMin(sum, fStream->getLength()) - offset; + if (bytesToRead == 0) { + return nullptr; + } + + if (fStream->getMemoryBase()) { // directly copy if getMemoryBase() is available. + sk_sp data(SkData::MakeWithCopy( + static_cast(fStream->getMemoryBase()) + offset, bytesToRead)); + fStream.reset(); + return SkMemoryStream::Make(data); + } else { + sk_sp data(SkData::MakeUninitialized(bytesToRead)); + if (!fStream->seek(offset)) { + return nullptr; + } + const size_t bytesRead = fStream->read(data->writable_data(), bytesToRead); + if (bytesRead < bytesToRead) { + data = SkData::MakeSubset(data.get(), 0, bytesRead); + } + return SkMemoryStream::Make(data); + } + } +private: + std::unique_ptr fStream; +}; + +class SkPiexStream : public ::piex::StreamInterface { +public: + // Will NOT take the ownership of the stream. + explicit SkPiexStream(SkRawStream* stream) : fStream(stream) {} + + ~SkPiexStream() override {} + + ::piex::Error GetData(const size_t offset, const size_t length, + uint8* data) override { + return fStream->read(static_cast(data), offset, length) ? + ::piex::Error::kOk : ::piex::Error::kFail; + } + +private: + SkRawStream* fStream; +}; + +class SkDngStream : public dng_stream { +public: + // Will NOT take the ownership of the stream. + SkDngStream(SkRawStream* stream) : fStream(stream) {} + + ~SkDngStream() override {} + + uint64 DoGetLength() override { return fStream->getLength(); } + + void DoRead(void* data, uint32 count, uint64 offset) override { + size_t sum; + if (!safe_add_to_size_t(static_cast(count), offset, &sum) || + !fStream->read(data, static_cast(offset), static_cast(count))) { + ThrowReadFile(); + } + } + +private: + SkRawStream* fStream; +}; + +class SkDngImage { +public: + /* + * Initializes the object with the information from Piex in a first attempt. This way it can + * save time and storage to obtain the DNG dimensions and color filter array (CFA) pattern + * which is essential for the demosaicing of the sensor image. + * Note: this will take the ownership of the stream. + */ + static SkDngImage* NewFromStream(SkRawStream* stream) { + std::unique_ptr dngImage(new SkDngImage(stream)); +#if defined(IS_FUZZING_WITH_LIBFUZZER) + // Libfuzzer easily runs out of memory after here. To avoid that + // We just pretend all streams are invalid. Our AFL-fuzzer + // should still exercise this code; it's more resistant to OOM. + return nullptr; +#endif + if (!dngImage->initFromPiex() && !dngImage->readDng()) { + return nullptr; + } + + return dngImage.release(); + } + + /* + * Renders the DNG image to the size. The DNG SDK only allows scaling close to integer factors + * down to 80 pixels on the short edge. The rendered image will be close to the specified size, + * but there is no guarantee that any of the edges will match the requested size. E.g. + * 100% size: 4000 x 3000 + * requested size: 1600 x 1200 + * returned size could be: 2000 x 1500 + */ + dng_image* render(int width, int height) { + if (!fHost || !fInfo || !fNegative || !fDngStream) { + if (!this->readDng()) { + return nullptr; + } + } + + // DNG SDK preserves the aspect ratio, so it only needs to know the longer dimension. + const int preferredSize = SkTMax(width, height); + try { + // render() takes ownership of fHost, fInfo, fNegative and fDngStream when available. + std::unique_ptr host(fHost.release()); + std::unique_ptr info(fInfo.release()); + std::unique_ptr negative(fNegative.release()); + std::unique_ptr dngStream(fDngStream.release()); + + host->SetPreferredSize(preferredSize); + host->ValidateSizes(); + + negative->ReadStage1Image(*host, *dngStream, *info); + + if (info->fMaskIndex != -1) { + negative->ReadTransparencyMask(*host, *dngStream, *info); + } + + negative->ValidateRawImageDigest(*host); + if (negative->IsDamaged()) { + return nullptr; + } + + const int32 kMosaicPlane = -1; + negative->BuildStage2Image(*host); + negative->BuildStage3Image(*host, kMosaicPlane); + + dng_render render(*host, *negative); + render.SetFinalSpace(dng_space_sRGB::Get()); + render.SetFinalPixelType(ttByte); + + dng_point stage3_size = negative->Stage3Image()->Size(); + render.SetMaximumSize(SkTMax(stage3_size.h, stage3_size.v)); + + return render.Render(); + } catch (...) { + return nullptr; + } + } + + int width() const { + return fWidth; + } + + int height() const { + return fHeight; + } + + bool isScalable() const { + return fIsScalable; + } + + bool isXtransImage() const { + return fIsXtransImage; + } + + // Quick check if the image contains a valid TIFF header as requested by DNG format. + // Does not affect ownership of stream. + static bool IsTiffHeaderValid(SkRawStream* stream) { + const size_t kHeaderSize = 4; + unsigned char header[kHeaderSize]; + if (!stream->read(header, 0 /* offset */, kHeaderSize)) { + return false; + } + + // Check if the header is valid (endian info and magic number "42"). + bool littleEndian; + if (!is_valid_endian_marker(header, &littleEndian)) { + return false; + } + + return 0x2A == get_endian_short(header + 2, littleEndian); + } + +private: + bool init(int width, int height, const dng_point& cfaPatternSize) { + fWidth = width; + fHeight = height; + + // The DNG SDK scales only during demosaicing, so scaling is only possible when + // a mosaic info is available. + fIsScalable = cfaPatternSize.v != 0 && cfaPatternSize.h != 0; + fIsXtransImage = fIsScalable ? (cfaPatternSize.v == 6 && cfaPatternSize.h == 6) : false; + + return width > 0 && height > 0; + } + + bool initFromPiex() { + // Does not take the ownership of rawStream. + SkPiexStream piexStream(fStream.get()); + ::piex::PreviewImageData imageData; + if (::piex::IsRaw(&piexStream) + && ::piex::GetPreviewImageData(&piexStream, &imageData) == ::piex::Error::kOk) + { + dng_point cfaPatternSize(imageData.cfa_pattern_dim[1], imageData.cfa_pattern_dim[0]); + return this->init(static_cast(imageData.full_width), + static_cast(imageData.full_height), cfaPatternSize); + } + return false; + } + + bool readDng() { + try { + // Due to the limit of DNG SDK, we need to reset host and info. + fHost.reset(new SkDngHost(&fAllocator)); + fInfo.reset(new dng_info); + fDngStream.reset(new SkDngStream(fStream.get())); + + fHost->ValidateSizes(); + fInfo->Parse(*fHost, *fDngStream); + fInfo->PostParse(*fHost); + if (!fInfo->IsValidDNG()) { + return false; + } + + fNegative.reset(fHost->Make_dng_negative()); + fNegative->Parse(*fHost, *fDngStream, *fInfo); + fNegative->PostParse(*fHost, *fDngStream, *fInfo); + fNegative->SynchronizeMetadata(); + + dng_point cfaPatternSize(0, 0); + if (fNegative->GetMosaicInfo() != nullptr) { + cfaPatternSize = fNegative->GetMosaicInfo()->fCFAPatternSize; + } + return this->init(static_cast(fNegative->DefaultCropSizeH().As_real64()), + static_cast(fNegative->DefaultCropSizeV().As_real64()), + cfaPatternSize); + } catch (...) { + return false; + } + } + + SkDngImage(SkRawStream* stream) + : fStream(stream) + {} + + dng_memory_allocator fAllocator; + std::unique_ptr fStream; + std::unique_ptr fHost; + std::unique_ptr fInfo; + std::unique_ptr fNegative; + std::unique_ptr fDngStream; + + int fWidth; + int fHeight; + bool fIsScalable; + bool fIsXtransImage; +}; + +/* + * Tries to handle the image with PIEX. If PIEX returns kOk and finds the preview image, create a + * SkJpegCodec. If PIEX returns kFail, then the file is invalid, return nullptr. In other cases, + * fallback to create SkRawCodec for DNG images. + */ +std::unique_ptr SkRawCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + std::unique_ptr rawStream; + if (is_asset_stream(*stream)) { + rawStream.reset(new SkRawAssetStream(std::move(stream))); + } else { + rawStream.reset(new SkRawBufferedStream(std::move(stream))); + } + + // Does not take the ownership of rawStream. + SkPiexStream piexStream(rawStream.get()); + ::piex::PreviewImageData imageData; + if (::piex::IsRaw(&piexStream)) { + ::piex::Error error = ::piex::GetPreviewImageData(&piexStream, &imageData); + if (error == ::piex::Error::kFail) { + *result = kInvalidInput; + return nullptr; + } + + std::unique_ptr profile; + if (imageData.color_space == ::piex::PreviewImageData::kAdobeRgb) { + skcms_ICCProfile skcmsProfile; + skcms_Init(&skcmsProfile); + skcms_SetTransferFunction(&skcmsProfile, &SkNamedTransferFn::k2Dot2); + skcms_SetXYZD50(&skcmsProfile, &SkNamedGamut::kAdobeRGB); + profile = SkEncodedInfo::ICCProfile::Make(skcmsProfile); + } + + // Theoretically PIEX can return JPEG compressed image or uncompressed RGB image. We only + // handle the JPEG compressed preview image here. + if (error == ::piex::Error::kOk && imageData.preview.length > 0 && + imageData.preview.format == ::piex::Image::kJpegCompressed) + { + // transferBuffer() is destructive to the rawStream. Abandon the rawStream after this + // function call. + // FIXME: one may avoid the copy of memoryStream and use the buffered rawStream. + auto memoryStream = rawStream->transferBuffer(imageData.preview.offset, + imageData.preview.length); + if (!memoryStream) { + *result = kInvalidInput; + return nullptr; + } + return SkJpegCodec::MakeFromStream(std::move(memoryStream), result, + std::move(profile)); + } + } + + if (!SkDngImage::IsTiffHeaderValid(rawStream.get())) { + *result = kUnimplemented; + return nullptr; + } + + // Takes the ownership of the rawStream. + std::unique_ptr dngImage(SkDngImage::NewFromStream(rawStream.release())); + if (!dngImage) { + *result = kInvalidInput; + return nullptr; + } + + *result = kSuccess; + return std::unique_ptr(new SkRawCodec(dngImage.release())); +} + +SkCodec::Result SkRawCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options& options, + int* rowsDecoded) { + const int width = dstInfo.width(); + const int height = dstInfo.height(); + std::unique_ptr image(fDngImage->render(width, height)); + if (!image) { + return kInvalidInput; + } + + // Because the DNG SDK can not guarantee to render to requested size, we allow a small + // difference. Only the overlapping region will be converted. + const float maxDiffRatio = 1.03f; + const dng_point& imageSize = image->Size(); + if (imageSize.h / (float) width > maxDiffRatio || imageSize.h < width || + imageSize.v / (float) height > maxDiffRatio || imageSize.v < height) { + return SkCodec::kInvalidScale; + } + + void* dstRow = dst; + SkAutoTMalloc srcRow(width * 3); + + dng_pixel_buffer buffer; + buffer.fData = &srcRow[0]; + buffer.fPlane = 0; + buffer.fPlanes = 3; + buffer.fColStep = buffer.fPlanes; + buffer.fPlaneStep = 1; + buffer.fPixelType = ttByte; + buffer.fPixelSize = sizeof(uint8_t); + buffer.fRowStep = width * 3; + + constexpr auto srcFormat = skcms_PixelFormat_RGB_888; + skcms_PixelFormat dstFormat; + if (!sk_select_xform_format(dstInfo.colorType(), false, &dstFormat)) { + return kInvalidConversion; + } + + const skcms_ICCProfile* const srcProfile = this->getEncodedInfo().profile(); + skcms_ICCProfile dstProfileStorage; + const skcms_ICCProfile* dstProfile = nullptr; + if (auto cs = dstInfo.colorSpace()) { + cs->toProfile(&dstProfileStorage); + dstProfile = &dstProfileStorage; + } + + for (int i = 0; i < height; ++i) { + buffer.fArea = dng_rect(i, 0, i + 1, width); + + try { + image->Get(buffer, dng_image::edge_zero); + } catch (...) { + *rowsDecoded = i; + return kIncompleteInput; + } + + if (!skcms_Transform(&srcRow[0], srcFormat, skcms_AlphaFormat_Unpremul, srcProfile, + dstRow, dstFormat, skcms_AlphaFormat_Unpremul, dstProfile, + dstInfo.width())) { + SkDebugf("failed to transform\n"); + *rowsDecoded = i; + return kInternalError; + } + + dstRow = SkTAddOffset(dstRow, dstRowBytes); + } + return kSuccess; +} + +SkISize SkRawCodec::onGetScaledDimensions(float desiredScale) const { + SkASSERT(desiredScale <= 1.f); + + const SkISize dim = this->dimensions(); + SkASSERT(dim.fWidth != 0 && dim.fHeight != 0); + + if (!fDngImage->isScalable()) { + return dim; + } + + // Limits the minimum size to be 80 on the short edge. + const float shortEdge = static_cast(SkTMin(dim.fWidth, dim.fHeight)); + if (desiredScale < 80.f / shortEdge) { + desiredScale = 80.f / shortEdge; + } + + // For Xtrans images, the integer-factor scaling does not support the half-size scaling case + // (stronger downscalings are fine). In this case, returns the factor "3" scaling instead. + if (fDngImage->isXtransImage() && desiredScale > 1.f / 3.f && desiredScale < 1.f) { + desiredScale = 1.f / 3.f; + } + + // Round to integer-factors. + const float finalScale = std::floor(1.f/ desiredScale); + return SkISize::Make(static_cast(std::floor(dim.fWidth / finalScale)), + static_cast(std::floor(dim.fHeight / finalScale))); +} + +bool SkRawCodec::onDimensionsSupported(const SkISize& dim) { + const SkISize fullDim = this->dimensions(); + const float fullShortEdge = static_cast(SkTMin(fullDim.fWidth, fullDim.fHeight)); + const float shortEdge = static_cast(SkTMin(dim.fWidth, dim.fHeight)); + + SkISize sizeFloor = this->onGetScaledDimensions(1.f / std::floor(fullShortEdge / shortEdge)); + SkISize sizeCeil = this->onGetScaledDimensions(1.f / std::ceil(fullShortEdge / shortEdge)); + return sizeFloor == dim || sizeCeil == dim; +} + +SkRawCodec::~SkRawCodec() {} + +SkRawCodec::SkRawCodec(SkDngImage* dngImage) + : INHERITED(SkEncodedInfo::Make(dngImage->width(), dngImage->height(), + SkEncodedInfo::kRGB_Color, + SkEncodedInfo::kOpaque_Alpha, 8), + skcms_PixelFormat_RGBA_8888, nullptr) + , fDngImage(dngImage) {} diff --git a/gfx/skia/skia/src/codec/SkRawCodec.h b/gfx/skia/skia/src/codec/SkRawCodec.h new file mode 100644 index 0000000000..d1c5131afa --- /dev/null +++ b/gfx/skia/skia/src/codec/SkRawCodec.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRawCodec_DEFINED +#define SkRawCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" + +class SkDngImage; +class SkStream; + +/* + * + * This class implements the decoding for RAW images + * + */ +class SkRawCodec : public SkCodec { +public: + + /* + * Creates a RAW decoder + * Takes ownership of the stream + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + + ~SkRawCodec() override; + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + int*) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kDNG; + } + + SkISize onGetScaledDimensions(float desiredScale) const override; + + bool onDimensionsSupported(const SkISize&) override; + + // SkCodec only applies the colorXform if it's necessary for color space + // conversion. SkRawCodec will always convert, so tell SkCodec not to. + bool usesColorXform() const override { return false; } + +private: + + /* + * Creates an instance of the decoder + * Called only by NewFromStream, takes ownership of dngImage. + */ + SkRawCodec(SkDngImage* dngImage); + + std::unique_ptr fDngImage; + + typedef SkCodec INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkSampledCodec.cpp b/gfx/skia/skia/src/codec/SkSampledCodec.cpp new file mode 100644 index 0000000000..c0c8cf02c4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampledCodec.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkMath.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSampledCodec.h" +#include "src/codec/SkSampler.h" +#include "src/core/SkMathPriv.h" + +SkSampledCodec::SkSampledCodec(SkCodec* codec, ExifOrientationBehavior behavior) + : INHERITED(codec, behavior) +{} + +SkISize SkSampledCodec::accountForNativeScaling(int* sampleSizePtr, int* nativeSampleSize) const { + SkISize preSampledSize = this->codec()->dimensions(); + int sampleSize = *sampleSizePtr; + SkASSERT(sampleSize > 1); + + if (nativeSampleSize) { + *nativeSampleSize = 1; + } + + // Only JPEG supports native downsampling. + if (this->codec()->getEncodedFormat() == SkEncodedImageFormat::kJPEG) { + // See if libjpeg supports this scale directly + switch (sampleSize) { + case 2: + case 4: + case 8: + // This class does not need to do any sampling. + *sampleSizePtr = 1; + return this->codec()->getScaledDimensions(get_scale_from_sample_size(sampleSize)); + default: + break; + } + + // Check if sampleSize is a multiple of something libjpeg can support. + int remainder; + const int sampleSizes[] = { 8, 4, 2 }; + for (int supportedSampleSize : sampleSizes) { + int actualSampleSize; + SkTDivMod(sampleSize, supportedSampleSize, &actualSampleSize, &remainder); + if (0 == remainder) { + float scale = get_scale_from_sample_size(supportedSampleSize); + + // this->codec() will scale to this size. + preSampledSize = this->codec()->getScaledDimensions(scale); + + // And then this class will sample it. + *sampleSizePtr = actualSampleSize; + if (nativeSampleSize) { + *nativeSampleSize = supportedSampleSize; + } + break; + } + } + } + + return preSampledSize; +} + +SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const { + const SkISize size = this->accountForNativeScaling(&sampleSize); + return SkISize::Make(get_scaled_dimension(size.width(), sampleSize), + get_scaled_dimension(size.height(), sampleSize)); +} + +SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const AndroidOptions& options) { + // Create an Options struct for the codec. + SkCodec::Options codecOptions; + codecOptions.fZeroInitialized = options.fZeroInitialized; + + SkIRect* subset = options.fSubset; + if (!subset || subset->size() == this->codec()->dimensions()) { + if (this->codec()->dimensionsSupported(info.dimensions())) { + return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions); + } + + // If the native codec does not support the requested scale, scale by sampling. + return this->sampledDecode(info, pixels, rowBytes, options); + } + + // We are performing a subset decode. + int sampleSize = options.fSampleSize; + SkISize scaledSize = this->getSampledDimensions(sampleSize); + if (!this->codec()->dimensionsSupported(scaledSize)) { + // If the native codec does not support the requested scale, scale by sampling. + return this->sampledDecode(info, pixels, rowBytes, options); + } + + // Calculate the scaled subset bounds. + int scaledSubsetX = subset->x() / sampleSize; + int scaledSubsetY = subset->y() / sampleSize; + int scaledSubsetWidth = info.width(); + int scaledSubsetHeight = info.height(); + + const SkImageInfo scaledInfo = info.makeDimensions(scaledSize); + + { + // Although startScanlineDecode expects the bottom and top to match the + // SkImageInfo, startIncrementalDecode uses them to determine which rows to + // decode. + SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY, + scaledSubsetWidth, scaledSubsetHeight); + codecOptions.fSubset = &incrementalSubset; + const SkCodec::Result startResult = this->codec()->startIncrementalDecode( + scaledInfo, pixels, rowBytes, &codecOptions); + if (SkCodec::kSuccess == startResult) { + int rowsDecoded = 0; + const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded); + if (incResult == SkCodec::kSuccess) { + return SkCodec::kSuccess; + } + SkASSERT(incResult == SkCodec::kIncompleteInput || incResult == SkCodec::kErrorInInput); + + // FIXME: Can zero initialized be read from SkCodec::fOptions? + this->codec()->fillIncompleteImage(scaledInfo, pixels, rowBytes, + options.fZeroInitialized, scaledSubsetHeight, rowsDecoded); + return incResult; + } else if (startResult != SkCodec::kUnimplemented) { + return startResult; + } + // Otherwise fall down to use the old scanline decoder. + // codecOptions.fSubset will be reset below, so it will not continue to + // point to the object that is no longer on the stack. + } + + // Start the scanline decode. + SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth, + scaledSize.height()); + codecOptions.fSubset = &scanlineSubset; + + SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo, + &codecOptions); + if (SkCodec::kSuccess != result) { + return result; + } + + // At this point, we are only concerned with subsetting. Either no scale was + // requested, or the this->codec() is handling the scale. + // Note that subsetting is only supported for kTopDown, so this code will not be + // reached for other orders. + SkASSERT(this->codec()->getScanlineOrder() == SkCodec::kTopDown_SkScanlineOrder); + if (!this->codec()->skipScanlines(scaledSubsetY)) { + this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + scaledSubsetHeight, 0); + return SkCodec::kIncompleteInput; + } + + int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes); + if (decodedLines != scaledSubsetHeight) { + return SkCodec::kIncompleteInput; + } + return SkCodec::kSuccess; +} + + +SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, + size_t rowBytes, const AndroidOptions& options) { + // We should only call this function when sampling. + SkASSERT(options.fSampleSize > 1); + + // Create options struct for the codec. + SkCodec::Options sampledOptions; + sampledOptions.fZeroInitialized = options.fZeroInitialized; + + // FIXME: This was already called by onGetAndroidPixels. Can we reduce that? + int sampleSize = options.fSampleSize; + int nativeSampleSize; + SkISize nativeSize = this->accountForNativeScaling(&sampleSize, &nativeSampleSize); + + // Check if there is a subset. + SkIRect subset; + int subsetY = 0; + int subsetWidth = nativeSize.width(); + int subsetHeight = nativeSize.height(); + if (options.fSubset) { + // We will need to know about subsetting in the y-dimension in order to use the + // scanline decoder. + // Update the subset to account for scaling done by this->codec(). + const SkIRect* subsetPtr = options.fSubset; + + // Do the divide ourselves, instead of calling get_scaled_dimension. If + // X and Y are 0, they should remain 0, rather than being upgraded to 1 + // due to being smaller than the sampleSize. + const int subsetX = subsetPtr->x() / nativeSampleSize; + subsetY = subsetPtr->y() / nativeSampleSize; + + subsetWidth = get_scaled_dimension(subsetPtr->width(), nativeSampleSize); + subsetHeight = get_scaled_dimension(subsetPtr->height(), nativeSampleSize); + + // The scanline decoder only needs to be aware of subsetting in the x-dimension. + subset.setXYWH(subsetX, 0, subsetWidth, nativeSize.height()); + sampledOptions.fSubset = ⊂ + } + + // Since we guarantee that output dimensions are always at least one (even if the sampleSize + // is greater than a given dimension), the input sampleSize is not always the sampleSize that + // we use in practice. + const int sampleX = subsetWidth / info.width(); + const int sampleY = subsetHeight / info.height(); + + const int samplingOffsetY = get_start_coord(sampleY); + const int startY = samplingOffsetY + subsetY; + const int dstHeight = info.height(); + + const SkImageInfo nativeInfo = info.makeDimensions(nativeSize); + + { + // Although startScanlineDecode expects the bottom and top to match the + // SkImageInfo, startIncrementalDecode uses them to determine which rows to + // decode. + SkCodec::Options incrementalOptions = sampledOptions; + SkIRect incrementalSubset; + if (sampledOptions.fSubset) { + incrementalSubset.fTop = subsetY; + incrementalSubset.fBottom = subsetY + subsetHeight; + incrementalSubset.fLeft = sampledOptions.fSubset->fLeft; + incrementalSubset.fRight = sampledOptions.fSubset->fRight; + incrementalOptions.fSubset = &incrementalSubset; + } + const SkCodec::Result startResult = this->codec()->startIncrementalDecode(nativeInfo, + pixels, rowBytes, &incrementalOptions); + if (SkCodec::kSuccess == startResult) { + SkSampler* sampler = this->codec()->getSampler(true); + if (!sampler) { + return SkCodec::kUnimplemented; + } + + if (sampler->setSampleX(sampleX) != info.width()) { + return SkCodec::kInvalidScale; + } + if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) { + return SkCodec::kInvalidScale; + } + + sampler->setSampleY(sampleY); + + int rowsDecoded = 0; + const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded); + if (incResult == SkCodec::kSuccess) { + return SkCodec::kSuccess; + } + SkASSERT(incResult == SkCodec::kIncompleteInput || incResult == SkCodec::kErrorInInput); + + SkASSERT(rowsDecoded <= info.height()); + this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + info.height(), rowsDecoded); + return incResult; + } else if (startResult == SkCodec::kIncompleteInput + || startResult == SkCodec::kErrorInInput) { + return SkCodec::kInvalidInput; + } else if (startResult != SkCodec::kUnimplemented) { + return startResult; + } // kUnimplemented means use the old method. + } + + // Start the scanline decode. + SkCodec::Result result = this->codec()->startScanlineDecode(nativeInfo, + &sampledOptions); + if (SkCodec::kIncompleteInput == result || SkCodec::kErrorInInput == result) { + return SkCodec::kInvalidInput; + } else if (SkCodec::kSuccess != result) { + return result; + } + + SkSampler* sampler = this->codec()->getSampler(true); + if (!sampler) { + return SkCodec::kUnimplemented; + } + + if (sampler->setSampleX(sampleX) != info.width()) { + return SkCodec::kInvalidScale; + } + if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) { + return SkCodec::kInvalidScale; + } + + switch(this->codec()->getScanlineOrder()) { + case SkCodec::kTopDown_SkScanlineOrder: { + if (!this->codec()->skipScanlines(startY)) { + this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + dstHeight, 0); + return SkCodec::kIncompleteInput; + } + void* pixelPtr = pixels; + for (int y = 0; y < dstHeight; y++) { + if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { + this->codec()->fillIncompleteImage(info, pixels, rowBytes, + options.fZeroInitialized, dstHeight, y + 1); + return SkCodec::kIncompleteInput; + } + if (y < dstHeight - 1) { + if (!this->codec()->skipScanlines(sampleY - 1)) { + this->codec()->fillIncompleteImage(info, pixels, rowBytes, + options.fZeroInitialized, dstHeight, y + 1); + return SkCodec::kIncompleteInput; + } + } + pixelPtr = SkTAddOffset(pixelPtr, rowBytes); + } + return SkCodec::kSuccess; + } + case SkCodec::kBottomUp_SkScanlineOrder: { + // Note that these modes do not support subsetting. + SkASSERT(0 == subsetY && nativeSize.height() == subsetHeight); + int y; + for (y = 0; y < nativeSize.height(); y++) { + int srcY = this->codec()->nextScanline(); + if (is_coord_necessary(srcY, sampleY, dstHeight)) { + void* pixelPtr = SkTAddOffset(pixels, + rowBytes * get_dst_coord(srcY, sampleY)); + if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { + break; + } + } else { + if (!this->codec()->skipScanlines(1)) { + break; + } + } + } + + if (nativeSize.height() == y) { + return SkCodec::kSuccess; + } + + // We handle filling uninitialized memory here instead of using this->codec(). + // this->codec() does not know that we are sampling. + const SkImageInfo fillInfo = info.makeWH(info.width(), 1); + for (; y < nativeSize.height(); y++) { + int srcY = this->codec()->outputScanline(y); + if (!is_coord_necessary(srcY, sampleY, dstHeight)) { + continue; + } + + void* rowPtr = SkTAddOffset(pixels, rowBytes * get_dst_coord(srcY, sampleY)); + SkSampler::Fill(fillInfo, rowPtr, rowBytes, options.fZeroInitialized); + } + return SkCodec::kIncompleteInput; + } + default: + SkASSERT(false); + return SkCodec::kUnimplemented; + } +} diff --git a/gfx/skia/skia/src/codec/SkSampledCodec.h b/gfx/skia/skia/src/codec/SkSampledCodec.h new file mode 100644 index 0000000000..c92f944a62 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampledCodec.h @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkSampledCodec_DEFINED +#define SkSampledCodec_DEFINED + +#include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" + +/** + * This class implements the functionality of SkAndroidCodec. Scaling will + * be provided by sampling if it cannot be provided by fCodec. + */ +class SkSampledCodec : public SkAndroidCodec { +public: + explicit SkSampledCodec(SkCodec*, ExifOrientationBehavior); + + ~SkSampledCodec() override {} + +protected: + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override { return true; } + + SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions& options) override; + +private: + /** + * Find the best way to account for native scaling. + * + * Return a size that fCodec can scale to, and adjust sampleSize to finish scaling. + * + * @param sampleSize As an input, the requested sample size. + * As an output, sampling needed after letting fCodec + * scale to the returned dimensions. + * @param nativeSampleSize Optional output parameter. Will be set to the + * effective sample size done by fCodec. + * @return SkISize The size that fCodec should scale to. + */ + SkISize accountForNativeScaling(int* sampleSize, int* nativeSampleSize = nullptr) const; + + /** + * This fulfills the same contract as onGetAndroidPixels(). + * + * We call this function from onGetAndroidPixels() if we have determined + * that fCodec does not support the requested scale, and we need to + * provide the scale by sampling. + */ + SkCodec::Result sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions& options); + + typedef SkAndroidCodec INHERITED; +}; +#endif // SkSampledCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkSampler.cpp b/gfx/skia/skia/src/codec/SkSampler.cpp new file mode 100644 index 0000000000..3820d71495 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampler.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSampler.h" +#include "src/core/SkUtils.h" + +void SkSampler::Fill(const SkImageInfo& info, void* dst, size_t rowBytes, + SkCodec::ZeroInitialized zeroInit) { + SkASSERT(dst != nullptr); + + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + return; + } + + const int width = info.width(); + const int numRows = info.height(); + + // Use the proper memset routine to fill the remaining bytes + switch (info.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + uint32_t* dstRow = (uint32_t*) dst; + for (int row = 0; row < numRows; row++) { + sk_memset32(dstRow, 0, width); + dstRow = SkTAddOffset(dstRow, rowBytes); + } + break; + } + case kRGB_565_SkColorType: { + uint16_t* dstRow = (uint16_t*) dst; + for (int row = 0; row < numRows; row++) { + sk_memset16(dstRow, 0, width); + dstRow = SkTAddOffset(dstRow, rowBytes); + } + break; + } + case kGray_8_SkColorType: { + uint8_t* dstRow = (uint8_t*) dst; + for (int row = 0; row < numRows; row++) { + memset(dstRow, 0, width); + dstRow = SkTAddOffset(dstRow, rowBytes); + } + break; + } + case kRGBA_F16_SkColorType: { + uint64_t* dstRow = (uint64_t*) dst; + for (int row = 0; row < numRows; row++) { + sk_memset64(dstRow, 0, width); + dstRow = SkTAddOffset(dstRow, rowBytes); + } + break; + } + default: + SkCodecPrintf("Error: Unsupported dst color type for fill(). Doing nothing.\n"); + SkASSERT(false); + break; + } +} diff --git a/gfx/skia/skia/src/codec/SkSampler.h b/gfx/skia/skia/src/codec/SkSampler.h new file mode 100644 index 0000000000..d03b80aa26 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampler.h @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkSampler_DEFINED +#define SkSampler_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkCodecPriv.h" + +class SkSampler : public SkNoncopyable { +public: + /** + * Update the sampler to sample every sampleX'th pixel. Returns the + * width after sampling. + */ + int setSampleX(int sampleX) { + return this->onSetSampleX(sampleX); + } + + /** + * Update the sampler to sample every sampleY'th row. + */ + void setSampleY(int sampleY) { + fSampleY = sampleY; + } + + /** + * Retrieve the value set for sampleY. + */ + int sampleY() const { + return fSampleY; + } + + /** + * Based on fSampleY, return whether this row belongs in the output. + * + * @param row Row of the image, starting with the first row in the subset. + */ + bool rowNeeded(int row) const { + return (row - get_start_coord(fSampleY)) % fSampleY == 0; + } + + /** + * Fill the remainder of the destination with 0. + * + * 0 has a different meaning depending on the SkColorType. For color types + * with transparency, this means transparent. For k565 and kGray, 0 is + * black. + * + * @param info + * Contains the color type of the rows to fill. + * Contains the pixel width of the destination rows to fill + * Contains the number of rows that we need to fill. + * + * @param dst + * The destination row to fill. + * + * @param rowBytes + * Stride in bytes of the destination. + * + * @param zeroInit + * Indicates whether memory is already zero initialized. + */ + static void Fill(const SkImageInfo& info, void* dst, size_t rowBytes, + SkCodec::ZeroInitialized zeroInit); + + virtual int fillWidth() const = 0; + + SkSampler() + : fSampleY(1) + {} + + virtual ~SkSampler() {} +private: + int fSampleY; + + virtual int onSetSampleX(int) = 0; +}; + +#endif // SkSampler_DEFINED diff --git a/gfx/skia/skia/src/codec/SkScalingCodec.h b/gfx/skia/skia/src/codec/SkScalingCodec.h new file mode 100644 index 0000000000..799ca3852b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkScalingCodec.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkScalingCodec_DEFINED +#define SkScalingCodec_DEFINED + +#include "include/codec/SkCodec.h" + +// Helper class for an SkCodec that supports arbitrary downscaling. +class SkScalingCodec : public SkCodec { +protected: + SkScalingCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr stream, + SkEncodedOrigin origin = kTopLeft_SkEncodedOrigin) + : INHERITED(std::move(info), srcFormat, std::move(stream), origin) {} + + SkISize onGetScaledDimensions(float desiredScale) const override { + SkISize dim = this->dimensions(); + // SkCodec treats zero dimensional images as errors, so the minimum size + // that we will recommend is 1x1. + dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth)); + dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight)); + return dim; + } + + bool onDimensionsSupported(const SkISize& requested) override { + SkISize dim = this->dimensions(); + int w = requested.width(); + int h = requested.height(); + return 1 <= w && w <= dim.width() && 1 <= h && h <= dim.height(); + } + +private: + typedef SkCodec INHERITED; +}; + +#endif // SkScalingCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkStreamBuffer.cpp b/gfx/skia/skia/src/codec/SkStreamBuffer.cpp new file mode 100644 index 0000000000..cdac862fdd --- /dev/null +++ b/gfx/skia/skia/src/codec/SkStreamBuffer.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkStreamBuffer.h" + +SkStreamBuffer::SkStreamBuffer(std::unique_ptr stream) + : fStream(std::move(stream)) + , fPosition(0) + , fBytesBuffered(0) + , fHasLengthAndPosition(fStream->hasLength() && fStream->hasPosition()) + , fTrulyBuffered(0) +{} + +SkStreamBuffer::~SkStreamBuffer() { + fMarkedData.foreach([](size_t, SkData** data) { (*data)->unref(); }); +} + +const char* SkStreamBuffer::get() const { + SkASSERT(fBytesBuffered >= 1); + if (fHasLengthAndPosition && fTrulyBuffered < fBytesBuffered) { + const size_t bytesToBuffer = fBytesBuffered - fTrulyBuffered; + char* dst = SkTAddOffset(const_cast(fBuffer), fTrulyBuffered); + SkDEBUGCODE(const size_t bytesRead =) + // This stream is rewindable, so it should be safe to call the non-const + // read() + const_cast(fStream.get())->read(dst, bytesToBuffer); + SkASSERT(bytesRead == bytesToBuffer); + fTrulyBuffered = fBytesBuffered; + } + return fBuffer; +} + +bool SkStreamBuffer::buffer(size_t totalBytesToBuffer) { + // FIXME (scroggo): What should we do if the client tries to read too much? + // Should not be a problem in GIF. + SkASSERT(totalBytesToBuffer <= kMaxSize); + + if (totalBytesToBuffer <= fBytesBuffered) { + return true; + } + + if (fHasLengthAndPosition) { + const size_t remaining = fStream->getLength() - fStream->getPosition() + fTrulyBuffered; + fBytesBuffered = SkTMin(remaining, totalBytesToBuffer); + } else { + const size_t extraBytes = totalBytesToBuffer - fBytesBuffered; + const size_t bytesBuffered = fStream->read(fBuffer + fBytesBuffered, extraBytes); + fBytesBuffered += bytesBuffered; + } + return fBytesBuffered == totalBytesToBuffer; +} + +size_t SkStreamBuffer::markPosition() { + SkASSERT(fBytesBuffered >= 1); + if (!fHasLengthAndPosition) { + sk_sp data(SkData::MakeWithCopy(fBuffer, fBytesBuffered)); + SkASSERT(nullptr == fMarkedData.find(fPosition)); + fMarkedData.set(fPosition, data.release()); + } + return fPosition; +} + +sk_sp SkStreamBuffer::getDataAtPosition(size_t position, size_t length) { + if (!fHasLengthAndPosition) { + SkData** data = fMarkedData.find(position); + SkASSERT(data); + SkASSERT((*data)->size() == length); + return sk_ref_sp(*data); + } + + SkASSERT(length <= fStream->getLength() && + position <= fStream->getLength() - length); + + const size_t oldPosition = fStream->getPosition(); + if (!fStream->seek(position)) { + return nullptr; + } + + sk_sp data(SkData::MakeUninitialized(length)); + void* dst = data->writable_data(); + const bool success = fStream->read(dst, length) == length; + fStream->seek(oldPosition); + return success ? data : nullptr; +} diff --git a/gfx/skia/skia/src/codec/SkStreamBuffer.h b/gfx/skia/skia/src/codec/SkStreamBuffer.h new file mode 100644 index 0000000000..465d2f54ee --- /dev/null +++ b/gfx/skia/skia/src/codec/SkStreamBuffer.h @@ -0,0 +1,116 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStreamBuffer_DEFINED +#define SkStreamBuffer_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTHash.h" + +/** + * Helper class for reading from a stream that may not have all its data + * available yet. + * + * Used by GIFImageReader, and currently set up for that use case. + * + * Buffers up to 256 * 3 bytes (256 colors, with 3 bytes each) to support GIF. + * FIXME (scroggo): Make this more general purpose? + */ +class SkStreamBuffer : SkNoncopyable { +public: + SkStreamBuffer(std::unique_ptr); + ~SkStreamBuffer(); + + /** + * Return a pointer the buffered data. + * + * The number of bytes buffered is the number passed to buffer() + * after the last call to flush(). + */ + const char* get() const; + + /** + * Buffer from the stream into our buffer. + * + * If this call returns true, get() can be used to access |bytes| bytes + * from the stream. In addition, markPosition() can be called to mark this + * position and enable calling getAtPosition() later to retrieve |bytes| + * bytes. + * + * @param bytes Total number of bytes desired. + * + * @return Whether all bytes were successfully buffered. + */ + bool buffer(size_t bytes); + + /** + * Flush the buffer. + * + * After this call, no bytes are buffered. + */ + void flush() { + if (fHasLengthAndPosition) { + if (fTrulyBuffered < fBytesBuffered) { + fStream->move(fBytesBuffered - fTrulyBuffered); + } + fTrulyBuffered = 0; + } + fPosition += fBytesBuffered; + fBytesBuffered = 0; + } + + /** + * Mark the current position in the stream to return to it later. + * + * This is the position of the start of the buffer. After this call, a + * a client can call getDataAtPosition to retrieve all the bytes currently + * buffered. + * + * @return size_t Position which can be passed to getDataAtPosition later + * to retrieve the data currently buffered. + */ + size_t markPosition(); + + /** + * Retrieve data at position, as previously marked by markPosition(). + * + * @param position Position to retrieve data, as marked by markPosition(). + * @param length Amount of data required at position. + * @return SkData The data at position. + */ + sk_sp getDataAtPosition(size_t position, size_t length); + +private: + static constexpr size_t kMaxSize = 256 * 3; + + std::unique_ptr fStream; + size_t fPosition; + char fBuffer[kMaxSize]; + size_t fBytesBuffered; + // If the stream has a length and position, we can make two optimizations: + // - We can skip buffering + // - During parsing, we can store the position and size of data that is + // needed later during decoding. + const bool fHasLengthAndPosition; + // When fHasLengthAndPosition is true, we do not need to actually buffer + // inside buffer(). We'll buffer inside get(). This keeps track of how many + // bytes we've buffered inside get(), for the (non-existent) case of: + // buffer(n) + // get() + // buffer(n + u) + // get() + // The second call to get() needs to only truly buffer the part that was + // not already buffered. + mutable size_t fTrulyBuffered; + // Only used if !fHasLengthAndPosition. In that case, markPosition will + // copy into an SkData, stored here. + SkTHashMap fMarkedData; +}; +#endif // SkStreamBuffer_DEFINED + diff --git a/gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h b/gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h new file mode 100644 index 0000000000..413ec62800 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStubHeifDecoderAPI_DEFINED +#define SkStubHeifDecoderAPI_DEFINED + +// This stub implementation of HeifDecoderAPI.h lets us compile SkHeifCodec.cpp +// even when libheif is not available. It, of course, does nothing and fails to decode. + +#include +#include +#include + +enum HeifColorFormat { + kHeifColorFormat_RGB565, + kHeifColorFormat_RGBA_8888, + kHeifColorFormat_BGRA_8888, +}; + +struct HeifStream { + virtual ~HeifStream() {} + + virtual size_t read(void*, size_t) = 0; + virtual bool rewind() = 0; + virtual bool seek(size_t) = 0; + virtual bool hasLength() const = 0; + virtual size_t getLength() const = 0; +}; + +struct HeifFrameInfo { + uint32_t mWidth; + uint32_t mHeight; + int32_t mRotationAngle; // Rotation angle, clockwise, should be multiple of 90 + uint32_t mBytesPerPixel; // Number of bytes for one pixel + int64_t mDurationUs; // Duration of the frame in us + std::vector mIccData; // ICC data array +}; + +struct HeifDecoder { + bool init(HeifStream* stream, HeifFrameInfo*) { + delete stream; + return false; + } + + bool getSequenceInfo(HeifFrameInfo* frameInfo, size_t *frameCount) { + return false; + } + + bool decode(HeifFrameInfo*) { + return false; + } + + bool decodeSequence(int frameIndex, HeifFrameInfo* frameInfo) { + return false; + } + + bool setOutputColor(HeifColorFormat) { + return false; + } + + bool getScanline(uint8_t*) { + return false; + } + + int skipScanlines(int) { + return 0; + } +}; + +static inline HeifDecoder* createHeifDecoder() { return new HeifDecoder; } + +#endif//SkStubHeifDecoderAPI_DEFINED diff --git a/gfx/skia/skia/src/codec/SkSwizzler.cpp b/gfx/skia/skia/src/codec/SkSwizzler.cpp new file mode 100644 index 0000000000..e29c41428b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSwizzler.cpp @@ -0,0 +1,1237 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSwizzler.h" +#include "src/core/SkOpts.h" + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #include "include/android/SkAndroidFrameworkUtils.h" +#endif + +static void copy(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + memcpy(dst, src + offset, width * bpp); +} + +static void sample1(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint8_t* dst8 = (uint8_t*) dst; + for (int x = 0; x < width; x++) { + dst8[x] = *src; + src += deltaSrc; + } +} + +static void sample2(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint16_t* dst16 = (uint16_t*) dst; + for (int x = 0; x < width; x++) { + dst16[x] = *((const uint16_t*) src); + src += deltaSrc; + } +} + +static void sample4(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = *((const uint32_t*) src); + src += deltaSrc; + } +} + +static void sample6(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint8_t* dst8 = (uint8_t*) dst; + for (int x = 0; x < width; x++) { + memcpy(dst8, src, 6); + dst8 += 6; + src += deltaSrc; + } +} + +static void sample8(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint64_t* dst64 = (uint64_t*) dst; + for (int x = 0; x < width; x++) { + dst64[x] = *((const uint64_t*) src); + src += deltaSrc; + } +} + +// kBit +// These routines exclusively choose between white and black + +#define GRAYSCALE_BLACK 0 +#define GRAYSCALE_WHITE 0xFF + + +// same as swizzle_bit_to_index and swizzle_bit_to_n32 except for value assigned to dst[x] +static void swizzle_bit_to_grayscale( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + + uint8_t* SK_RESTRICT dst = (uint8_t*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7-bitIndex)) & 1) ? GRAYSCALE_WHITE : GRAYSCALE_BLACK; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7-bitIndex)) & 1) ? GRAYSCALE_WHITE : GRAYSCALE_BLACK; + } +} + +#undef GRAYSCALE_BLACK +#undef GRAYSCALE_WHITE + +// same as swizzle_bit_to_grayscale and swizzle_bit_to_index except for value assigned to dst[x] +static void swizzle_bit_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + SkPMColor* SK_RESTRICT dst = (SkPMColor*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7 - bitIndex)) & 1) ? SK_ColorWHITE : SK_ColorBLACK; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7 - bitIndex)) & 1) ? SK_ColorWHITE : SK_ColorBLACK; + } +} + +#define RGB565_BLACK 0 +#define RGB565_WHITE 0xFFFF + +static void swizzle_bit_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + uint16_t* SK_RESTRICT dst = (uint16_t*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7 - bitIndex)) & 1) ? RGB565_WHITE : RGB565_BLACK; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7 - bitIndex)) & 1) ? RGB565_WHITE : RGB565_BLACK; + } +} + +#undef RGB565_BLACK +#undef RGB565_WHITE + +static void swizzle_bit_to_f16( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + constexpr uint64_t kWhite = (((uint64_t) SK_Half1) << 0) | + (((uint64_t) SK_Half1) << 16) | + (((uint64_t) SK_Half1) << 32) | + (((uint64_t) SK_Half1) << 48); + constexpr uint64_t kBlack = (((uint64_t) 0) << 0) | + (((uint64_t) 0) << 16) | + (((uint64_t) 0) << 32) | + (((uint64_t) SK_Half1) << 48); + + uint64_t* SK_RESTRICT dst = (uint64_t*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7 - bitIndex)) & 1) ? kWhite : kBlack; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7 - bitIndex)) & 1) ? kWhite : kBlack; + } +} + +// kIndex1, kIndex2, kIndex4 + +static void swizzle_small_index_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + uint16_t* dst = (uint16_t*) dstRow; + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + const uint8_t mask = (1 << bpp) - 1; + uint8_t index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[0] = SkPixel32ToPixel16(ctable[index]); + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[x] = SkPixel32ToPixel16(ctable[index]); + } +} + +static void swizzle_small_index_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + SkPMColor* dst = (SkPMColor*) dstRow; + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + const uint8_t mask = (1 << bpp) - 1; + uint8_t index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[0] = ctable[index]; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[x] = ctable[index]; + } +} + +// kIndex + +static void swizzle_index_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + SkPMColor c = ctable[*src]; + dst[x] = c; + src += deltaSrc; + } +} + +static void swizzle_index_to_n32_skipZ( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + SkPMColor c = ctable[*src]; + if (c != 0) { + dst[x] = c; + } + src += deltaSrc; + } +} + +static void swizzle_index_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bytesPerPixel, int deltaSrc, int offset, const SkPMColor ctable[]) { + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPixel32ToPixel16(ctable[*src]); + src += deltaSrc; + } +} + +// kGray + +static void swizzle_gray_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPackARGB32NoCheck(0xFF, *src, *src, *src); + src += deltaSrc; + } +} + +static void fast_swizzle_gray_to_n32( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + // Note that there is no need to distinguish between RGB and BGR. + // Each color channel will get the same value. + SkOpts::gray_to_RGB1((uint32_t*) dst, src + offset, width); +} + +static void swizzle_gray_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bytesPerPixel, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPack888ToRGB16(src[0], src[0], src[0]); + src += deltaSrc; + } +} + +// kGrayAlpha + +static void swizzle_grayalpha_to_n32_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + src += offset; + SkPMColor* dst32 = (SkPMColor*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = SkPackARGB32NoCheck(src[1], src[0], src[0], src[0]); + src += deltaSrc; + } +} + +static void fast_swizzle_grayalpha_to_n32_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + // Note that there is no need to distinguish between RGB and BGR. + // Each color channel will get the same value. + SkOpts::grayA_to_RGBA((uint32_t*) dst, src + offset, width); +} + +static void swizzle_grayalpha_to_n32_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + src += offset; + SkPMColor* dst32 = (SkPMColor*) dst; + for (int x = 0; x < width; x++) { + uint8_t pmgray = SkMulDiv255Round(src[1], src[0]); + dst32[x] = SkPackARGB32NoCheck(src[1], pmgray, pmgray, pmgray); + src += deltaSrc; + } +} + +static void fast_swizzle_grayalpha_to_n32_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + // Note that there is no need to distinguish between rgb and bgr. + // Each color channel will get the same value. + SkOpts::grayA_to_rgbA((uint32_t*) dst, src + offset, width); +} + +static void swizzle_grayalpha_to_a8(void* dst, const uint8_t* src, int width, int bpp, + int deltaSrc, int offset, const SkPMColor[]) { + src += offset; + uint8_t* dst8 = (uint8_t*)dst; + for (int x = 0; x < width; ++x) { + dst8[x] = src[1]; // src[0] is gray, ignored + src += deltaSrc; + } +} + +// kBGR + +static void swizzle_bgr_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPack888ToRGB16(src[2], src[1], src[0]); + src += deltaSrc; + } +} + +// kRGB + +static void swizzle_rgb_to_rgba( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPackARGB_as_RGBA(0xFF, src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void swizzle_rgb_to_bgra( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPackARGB_as_BGRA(0xFF, src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void fast_swizzle_rgb_to_rgba( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGB_to_RGB1((uint32_t*) dst, src + offset, width); +} + +static void fast_swizzle_rgb_to_bgra( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGB_to_BGR1((uint32_t*) dst, src + offset, width); +} + +static void swizzle_rgb_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bytesPerPixel, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPack888ToRGB16(src[0], src[1], src[2]); + src += deltaSrc; + } +} + +// kRGBA + +static void swizzle_rgba_to_rgba_premul( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = premultiply_argb_as_rgba(src[3], src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void swizzle_rgba_to_bgra_premul( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = premultiply_argb_as_bgra(src[3], src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void fast_swizzle_rgba_to_rgba_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGBA_to_rgbA((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void fast_swizzle_rgba_to_bgra_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGBA_to_bgrA((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void swizzle_rgba_to_bgra_unpremul( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint32_t* SK_RESTRICT dst = reinterpret_cast(dstRow); + for (int x = 0; x < dstWidth; x++) { + unsigned alpha = src[3]; + dst[x] = SkPackARGB_as_BGRA(alpha, src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void fast_swizzle_rgba_to_bgra_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGBA_to_BGRA((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +// 16-bits per component kRGB and kRGBA + +static void swizzle_rgb16_to_rgba( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return 0xFF000000 | (ptr[4] << 16) | (ptr[2] << 8) | ptr[0]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgb16_to_bgra( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return 0xFF000000 | (ptr[0] << 16) | (ptr[2] << 8) | ptr[4]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgb16_to_565( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to565 = [](const uint8_t* ptr) { + return SkPack888ToRGB16(ptr[0], ptr[2], ptr[4]); + }; + + src += offset; + uint16_t* dst16 = (uint16_t*) dst; + for (int x = 0; x < width; x++) { + dst16[x] = strip16to565(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_rgba_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return (ptr[6] << 24) | (ptr[4] << 16) | (ptr[2] << 8) | ptr[0]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_rgba_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto stripAndPremul16to8 = [](const uint8_t* ptr) { + return premultiply_argb_as_rgba(ptr[6], ptr[0], ptr[2], ptr[4]); + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = stripAndPremul16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_bgra_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return (ptr[6] << 24) | (ptr[0] << 16) | (ptr[2] << 8) | ptr[4]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_bgra_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto stripAndPremul16to8 = [](const uint8_t* ptr) { + return premultiply_argb_as_bgra(ptr[6], ptr[0], ptr[2], ptr[4]); + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = stripAndPremul16to8(src); + src += deltaSrc; + } +} + +// kCMYK +// +// CMYK is stored as four bytes per pixel. +// +// We will implement a crude conversion from CMYK -> RGB using formulas +// from easyrgb.com. +// +// CMYK -> CMY +// C = C * (1 - K) + K +// M = M * (1 - K) + K +// Y = Y * (1 - K) + K +// +// libjpeg actually gives us inverted CMYK, so we must subtract the +// original terms from 1. +// CMYK -> CMY +// C = (1 - C) * (1 - (1 - K)) + (1 - K) +// M = (1 - M) * (1 - (1 - K)) + (1 - K) +// Y = (1 - Y) * (1 - (1 - K)) + (1 - K) +// +// Simplifying the above expression. +// CMYK -> CMY +// C = 1 - CK +// M = 1 - MK +// Y = 1 - YK +// +// CMY -> RGB +// R = (1 - C) * 255 +// G = (1 - M) * 255 +// B = (1 - Y) * 255 +// +// Therefore the full conversion is below. This can be verified at +// www.rapidtables.com (assuming inverted CMYK). +// CMYK -> RGB +// R = C * K * 255 +// G = M * K * 255 +// B = Y * K * 255 +// +// As a final note, we have treated the CMYK values as if they were on +// a scale from 0-1, when in fact they are 8-bit ints scaling from 0-255. +// We must divide each CMYK component by 255 to obtain the true conversion +// we should perform. +// CMYK -> RGB +// R = C * K / 255 +// G = M * K / 255 +// B = Y * K / 255 +static void swizzle_cmyk_to_rgba( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + const uint8_t r = SkMulDiv255Round(src[0], src[3]); + const uint8_t g = SkMulDiv255Round(src[1], src[3]); + const uint8_t b = SkMulDiv255Round(src[2], src[3]); + + dst[x] = SkPackARGB_as_RGBA(0xFF, r, g, b); + src += deltaSrc; + } +} + +static void swizzle_cmyk_to_bgra( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + const uint8_t r = SkMulDiv255Round(src[0], src[3]); + const uint8_t g = SkMulDiv255Round(src[1], src[3]); + const uint8_t b = SkMulDiv255Round(src[2], src[3]); + + dst[x] = SkPackARGB_as_BGRA(0xFF, r, g, b); + src += deltaSrc; + } +} + +static void fast_swizzle_cmyk_to_rgba( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::inverted_CMYK_to_RGB1((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void fast_swizzle_cmyk_to_bgra( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::inverted_CMYK_to_BGR1((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void swizzle_cmyk_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + const uint8_t r = SkMulDiv255Round(src[0], src[3]); + const uint8_t g = SkMulDiv255Round(src[1], src[3]); + const uint8_t b = SkMulDiv255Round(src[2], src[3]); + + dst[x] = SkPack888ToRGB16(r, g, b); + src += deltaSrc; + } +} + +template +void SkSwizzler::SkipLeadingGrayAlphaZerosThen( + void* dst, const uint8_t* src, int width, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + SkASSERT(!ctable); + + const uint16_t* src16 = (const uint16_t*) (src + offset); + uint32_t* dst32 = (uint32_t*) dst; + + // This may miss opportunities to skip when the output is premultiplied, + // e.g. for a src pixel 0x00FF which is not zero but becomes zero after premultiplication. + while (width > 0 && *src16 == 0x0000) { + width--; + dst32++; + src16 += deltaSrc / 2; + } + proc(dst32, (const uint8_t*)src16, width, bpp, deltaSrc, 0, ctable); +} + +template +void SkSwizzler::SkipLeading8888ZerosThen( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + SkASSERT(!ctable); + + auto src32 = (const uint32_t*)(src+offset); + auto dst32 = (uint32_t*)dstRow; + + // This may miss opportunities to skip when the output is premultiplied, + // e.g. for a src pixel 0x00FFFFFF which is not zero but becomes zero after premultiplication. + while (dstWidth > 0 && *src32 == 0x00000000) { + dstWidth--; + dst32++; + src32 += deltaSrc/4; + } + proc(dst32, (const uint8_t*)src32, dstWidth, bpp, deltaSrc, 0, ctable); +} + +std::unique_ptr SkSwizzler::MakeSimple(int srcBPP, const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + RowProc proc = nullptr; + switch (srcBPP) { + case 1: // kGray_8_SkColorType + proc = &sample1; + break; + case 2: // kRGB_565_SkColorType + proc = &sample2; + break; + case 4: // kRGBA_8888_SkColorType + // kBGRA_8888_SkColorType + proc = &sample4; + break; + case 6: // 16 bit PNG no alpha + proc = &sample6; + break; + case 8: // 16 bit PNG with alpha + proc = &sample8; + break; + default: + return nullptr; + } + + return Make(dstInfo, ©, proc, nullptr /*ctable*/, srcBPP, + dstInfo.bytesPerPixel(), options, nullptr /*frame*/); +} + +std::unique_ptr SkSwizzler::Make(const SkEncodedInfo& encodedInfo, + const SkPMColor* ctable, + const SkImageInfo& dstInfo, + const SkCodec::Options& options, + const SkIRect* frame) { + if (SkEncodedInfo::kPalette_Color == encodedInfo.color() && nullptr == ctable) { + return nullptr; + } + + RowProc fastProc = nullptr; + RowProc proc = nullptr; + SkCodec::ZeroInitialized zeroInit = options.fZeroInitialized; + const bool premultiply = (SkEncodedInfo::kOpaque_Alpha != encodedInfo.alpha()) && + (kPremul_SkAlphaType == dstInfo.alphaType()); + + switch (encodedInfo.color()) { + case SkEncodedInfo::kGray_Color: + switch (encodedInfo.bitsPerComponent()) { + case 1: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc = &swizzle_bit_to_n32; + break; + case kRGB_565_SkColorType: + proc = &swizzle_bit_to_565; + break; + case kGray_8_SkColorType: + proc = &swizzle_bit_to_grayscale; + break; + case kRGBA_F16_SkColorType: + proc = &swizzle_bit_to_f16; + break; + default: + return nullptr; + } + break; + case 8: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc = &swizzle_gray_to_n32; + fastProc = &fast_swizzle_gray_to_n32; + break; + case kGray_8_SkColorType: + proc = &sample1; + fastProc = © + break; + case kRGB_565_SkColorType: + proc = &swizzle_gray_to_565; + break; + default: + return nullptr; + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kXAlpha_Color: + case SkEncodedInfo::kGrayAlpha_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeadingGrayAlphaZerosThen + ; + fastProc = &SkipLeadingGrayAlphaZerosThen + ; + } else { + proc = &swizzle_grayalpha_to_n32_premul; + fastProc = &fast_swizzle_grayalpha_to_n32_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeadingGrayAlphaZerosThen + ; + fastProc = &SkipLeadingGrayAlphaZerosThen + ; + } else { + proc = &swizzle_grayalpha_to_n32_unpremul; + fastProc = &fast_swizzle_grayalpha_to_n32_unpremul; + } + } + break; + case kAlpha_8_SkColorType: + proc = &swizzle_grayalpha_to_a8; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kPalette_Color: + // We assume that the color table is premultiplied and swizzled + // as desired. + switch (encodedInfo.bitsPerComponent()) { + case 1: + case 2: + case 4: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc = &swizzle_small_index_to_n32; + break; + case kRGB_565_SkColorType: + proc = &swizzle_small_index_to_565; + break; + default: + return nullptr; + } + break; + case 8: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &swizzle_index_to_n32_skipZ; + } else { + proc = &swizzle_index_to_n32; + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_index_to_565; + break; + default: + return nullptr; + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::k565_Color: + // Treat 565 exactly like RGB (since it's still encoded as 8 bits per component). + // We just mark as 565 when we have a hint that there are only 5/6/5 "significant" + // bits in each channel. + case SkEncodedInfo::kRGB_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = &swizzle_rgb16_to_rgba; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + proc = &swizzle_rgb_to_rgba; + fastProc = &fast_swizzle_rgb_to_rgba; + break; + case kBGRA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = &swizzle_rgb16_to_bgra; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + proc = &swizzle_rgb_to_bgra; + fastProc = &fast_swizzle_rgb_to_bgra; + break; + case kRGB_565_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = &swizzle_rgb16_to_565; + break; + } + + proc = &swizzle_rgb_to_565; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kRGBA_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = premultiply ? &swizzle_rgba16_to_rgba_premul : + &swizzle_rgba16_to_rgba_unpremul; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen + ; + } else { + proc = &swizzle_rgba_to_rgba_premul; + fastProc = &fast_swizzle_rgba_to_rgba_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen; + } else { + proc = &sample4; + fastProc = © + } + } + break; + case kBGRA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = premultiply ? &swizzle_rgba16_to_bgra_premul : + &swizzle_rgba16_to_bgra_unpremul; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen + ; + } else { + proc = &swizzle_rgba_to_bgra_premul; + fastProc = &fast_swizzle_rgba_to_bgra_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen + ; + } else { + proc = &swizzle_rgba_to_bgra_unpremul; + fastProc = &fast_swizzle_rgba_to_bgra_unpremul; + } + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kBGR_Color: + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + proc = &swizzle_rgb_to_rgba; + fastProc = &fast_swizzle_rgb_to_rgba; + break; + case kRGBA_8888_SkColorType: + proc = &swizzle_rgb_to_bgra; + fastProc = &fast_swizzle_rgb_to_bgra; + break; + case kRGB_565_SkColorType: + proc = &swizzle_bgr_to_565; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kBGRX_Color: + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + proc = &swizzle_rgb_to_rgba; + break; + case kRGBA_8888_SkColorType: + proc = &swizzle_rgb_to_bgra; + break; + case kRGB_565_SkColorType: + proc = &swizzle_bgr_to_565; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kBGRA_Color: + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen + ; + } else { + proc = &swizzle_rgba_to_rgba_premul; + fastProc = &fast_swizzle_rgba_to_rgba_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen; + } else { + proc = &sample4; + fastProc = © + } + } + break; + case kRGBA_8888_SkColorType: + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen + ; + } else { + proc = &swizzle_rgba_to_bgra_premul; + fastProc = &fast_swizzle_rgba_to_bgra_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen; + fastProc = &SkipLeading8888ZerosThen + ; + } else { + proc = &swizzle_rgba_to_bgra_unpremul; + fastProc = &fast_swizzle_rgba_to_bgra_unpremul; + } + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kInvertedCMYK_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + proc = &swizzle_cmyk_to_rgba; + fastProc = &fast_swizzle_cmyk_to_rgba; + break; + case kBGRA_8888_SkColorType: + proc = &swizzle_cmyk_to_bgra; + fastProc = &fast_swizzle_cmyk_to_bgra; + break; + case kRGB_565_SkColorType: + proc = &swizzle_cmyk_to_565; + break; + default: + return nullptr; + } + break; + default: + return nullptr; + } + + // Store bpp in bytes if it is an even multiple, otherwise use bits + uint8_t bitsPerPixel = encodedInfo.bitsPerPixel(); + int srcBPP = SkIsAlign8(bitsPerPixel) ? bitsPerPixel / 8 : bitsPerPixel; + int dstBPP = dstInfo.bytesPerPixel(); + return Make(dstInfo, fastProc, proc, ctable, srcBPP, dstBPP, options, frame); +} + +std::unique_ptr SkSwizzler::Make(const SkImageInfo& dstInfo, + RowProc fastProc, RowProc proc, const SkPMColor* ctable, int srcBPP, + int dstBPP, const SkCodec::Options& options, const SkIRect* frame) { + int srcOffset = 0; + int srcWidth = dstInfo.width(); + int dstOffset = 0; + int dstWidth = srcWidth; + if (options.fSubset) { + // We do not currently support subset decodes for image types that may have + // frames (gif). + SkASSERT(!frame); + srcOffset = options.fSubset->left(); + srcWidth = options.fSubset->width(); + dstWidth = srcWidth; + } else if (frame) { + dstOffset = frame->left(); + srcWidth = frame->width(); + } + + return std::unique_ptr(new SkSwizzler(fastProc, proc, ctable, srcOffset, srcWidth, + dstOffset, dstWidth, srcBPP, dstBPP)); +} + +SkSwizzler::SkSwizzler(RowProc fastProc, RowProc proc, const SkPMColor* ctable, int srcOffset, + int srcWidth, int dstOffset, int dstWidth, int srcBPP, int dstBPP) + : fFastProc(fastProc) + , fSlowProc(proc) + , fActualProc(fFastProc ? fFastProc : fSlowProc) + , fColorTable(ctable) + , fSrcOffset(srcOffset) + , fDstOffset(dstOffset) + , fSrcOffsetUnits(srcOffset * srcBPP) + , fDstOffsetBytes(dstOffset * dstBPP) + , fSrcWidth(srcWidth) + , fDstWidth(dstWidth) + , fSwizzleWidth(srcWidth) + , fAllocatedWidth(dstWidth) + , fSampleX(1) + , fSrcBPP(srcBPP) + , fDstBPP(dstBPP) +{} + +int SkSwizzler::onSetSampleX(int sampleX) { + SkASSERT(sampleX > 0); + + fSampleX = sampleX; + fDstOffsetBytes = (fDstOffset / sampleX) * fDstBPP; + fSwizzleWidth = get_scaled_dimension(fSrcWidth, sampleX); + fAllocatedWidth = get_scaled_dimension(fDstWidth, sampleX); + + int frameSampleX = sampleX; + if (fSrcWidth < fDstWidth) { + // Although SkSampledCodec adjusted sampleX so that it will never be + // larger than the width of the image (or subset, if applicable), it + // doesn't account for the width of a subset frame (i.e. gif). As a + // result, get_start_coord(sampleX) could result in fSrcOffsetUnits + // being wider than fSrcWidth. Compute a sampling rate based on the + // frame width to ensure that fSrcOffsetUnits is sensible. + frameSampleX = fSrcWidth / fSwizzleWidth; + } + fSrcOffsetUnits = (get_start_coord(frameSampleX) + fSrcOffset) * fSrcBPP; + + if (fDstOffsetBytes > 0) { + const size_t dstSwizzleBytes = fSwizzleWidth * fDstBPP; + const size_t dstAllocatedBytes = fAllocatedWidth * fDstBPP; + if (fDstOffsetBytes + dstSwizzleBytes > dstAllocatedBytes) { +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkAndroidFrameworkUtils::SafetyNetLog("118143775"); +#endif + SkASSERT(dstSwizzleBytes <= dstAllocatedBytes); + fDstOffsetBytes = dstAllocatedBytes - dstSwizzleBytes; + } + } + + // The optimized swizzler functions do not support sampling. Sampled swizzles + // are already fast because they skip pixels. We haven't seen a situation + // where speeding up sampling has a significant impact on total decode time. + if (1 == fSampleX && fFastProc) { + fActualProc = fFastProc; + } else { + fActualProc = fSlowProc; + } + + return fAllocatedWidth; +} + +void SkSwizzler::swizzle(void* dst, const uint8_t* SK_RESTRICT src) { + SkASSERT(nullptr != dst && nullptr != src); + fActualProc(SkTAddOffset(dst, fDstOffsetBytes), src, fSwizzleWidth, fSrcBPP, + fSampleX * fSrcBPP, fSrcOffsetUnits, fColorTable); +} diff --git a/gfx/skia/skia/src/codec/SkSwizzler.h b/gfx/skia/skia/src/codec/SkSwizzler.h new file mode 100644 index 0000000000..c71b93a464 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSwizzler.h @@ -0,0 +1,222 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSwizzler_DEFINED +#define SkSwizzler_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColor.h" +#include "include/core/SkImageInfo.h" +#include "src/codec/SkSampler.h" + +class SkSwizzler : public SkSampler { +public: + /** + * Create a new SkSwizzler. + * @param encodedInfo Description of the format of the encoded data. + * @param ctable Unowned pointer to an array of up to 256 colors for an + * index source. + * @param dstInfo Describes the destination. + * @param options Contains partial scanline information and whether the dst is zero- + * initialized. + * @param frame Is non-NULL if the source pixels are part of an image + * frame that is a subset of the full image. + * + * Note that a deeper discussion of partial scanline subsets and image frame + * subsets is below. Currently, we do not support both simultaneously. If + * options->fSubset is non-NULL, frame must be NULL. + * + * @return A new SkSwizzler or nullptr on failure. + */ + static std::unique_ptr Make(const SkEncodedInfo& encodedInfo, + const SkPMColor* ctable, const SkImageInfo& dstInfo, const SkCodec::Options&, + const SkIRect* frame = nullptr); + + /** + * Create a simplified swizzler that does not need to do format conversion. The swizzler + * only needs to sample and/or subset. + * + * @param srcBPP Bytes per pixel of the source. + * @param dstInfo Describes the destination. + * @param options Contains partial scanline information and whether the dst is zero- + * initialized. + * @return A new SkSwizzler or nullptr on failure. + */ + static std::unique_ptr MakeSimple(int srcBPP, const SkImageInfo& dstInfo, + const SkCodec::Options&); + + /** + * Swizzle a line. Generally this will be called height times, once + * for each row of source. + * By allowing the caller to pass in the dst pointer, we give the caller + * flexibility to use the swizzler even when the encoded data does not + * store the rows in order. This also improves usability for scaled and + * subset decodes. + * @param dst Where we write the output. + * @param src The next row of the source data. + */ + void swizzle(void* dst, const uint8_t* SK_RESTRICT src); + + int fillWidth() const override { + return fAllocatedWidth; + } + + /** + * If fSampleX > 1, the swizzler is sampling every fSampleX'th pixel and + * discarding the rest. + * + * This getter is currently used by SkBmpStandardCodec for Bmp-in-Ico decodes. + * Ideally, the subclasses of SkCodec would have no knowledge of sampling, but + * this allows us to apply a transparency mask to pixels after swizzling. + */ + int sampleX() const { return fSampleX; } + + /** + * Returns the actual number of pixels written to destination memory, taking + * scaling, subsetting, and partial frames into account. + */ + int swizzleWidth() const { return fSwizzleWidth; } + + /** + * Returns the byte offset at which we write to destination memory, taking + * scaling, subsetting, and partial frames into account. + */ + size_t swizzleOffsetBytes() const { return fDstOffsetBytes; } + +private: + + /** + * Method for converting raw data to Skia pixels. + * @param dstRow Row in which to write the resulting pixels. + * @param src Row of src data, in format specified by SrcConfig + * @param dstWidth Width in pixels of the destination + * @param bpp if bitsPerPixel % 8 == 0, deltaSrc is bytesPerPixel + * else, deltaSrc is bitsPerPixel + * @param deltaSrc bpp * sampleX + * @param ctable Colors (used for kIndex source). + * @param offset The offset before the first pixel to sample. + Is in bytes or bits based on what deltaSrc is in. + */ + typedef void (*RowProc)(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int dstWidth, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]); + + template + static void SkipLeading8888ZerosThen(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int dstWidth, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]); + + template + static void SkipLeadingGrayAlphaZerosThen(void* dst, const uint8_t* src, int width, int bpp, + int deltaSrc, int offset, const SkPMColor ctable[]); + + // May be NULL. We have not implemented optimized functions for all supported transforms. + const RowProc fFastProc; + // Always non-NULL. Supports sampling. + const RowProc fSlowProc; + // The actual RowProc we are using. This depends on if fFastProc is non-NULL and + // whether or not we are sampling. + RowProc fActualProc; + + const SkPMColor* fColorTable; // Unowned pointer + + // Subset Swizzles + // There are two types of subset swizzles that we support. We do not + // support both at the same time. + // TODO: If we want to support partial scanlines for gifs (which may + // use frame subsets), we will need to support both subsetting + // modes at the same time. + // (1) Partial Scanlines + // The client only wants to write a subset of the source pixels + // to the destination. This subset is specified to CreateSwizzler + // using options->fSubset. We will store subset information in + // the following fields. + // + // fSrcOffset: The starting pixel of the source. + // fSrcOffsetUnits: Derived from fSrcOffset with two key + // differences: + // (1) This takes the size of source pixels into + // account by multiplying by fSrcBPP. This may + // be measured in bits or bytes depending on + // which is natural for the SrcConfig. + // (2) If we are sampling, this will be larger + // than fSrcOffset * fSrcBPP, since sampling + // implies that we will skip some pixels. + // fDstOffset: Will be zero. There is no destination offset + // for this type of subset. + // fDstOffsetBytes: Will be zero. + // fSrcWidth: The width of the desired subset of source + // pixels, before any sampling is performed. + // fDstWidth: Will be equal to fSrcWidth, since this is also + // calculated before any sampling is performed. + // For this type of subset, the destination width + // matches the desired subset of the source. + // fSwizzleWidth: The actual number of pixels that will be + // written by the RowProc. This is a scaled + // version of fSrcWidth/fDstWidth. + // fAllocatedWidth: Will be equal to fSwizzleWidth. For this type + // of subset, the number of pixels written is the + // same as the actual width of the destination. + // (2) Frame Subset + // The client will decode the entire width of the source into a + // subset of destination memory. This subset is specified to + // CreateSwizzler in the "frame" parameter. We store subset + // information in the following fields. + // + // fSrcOffset: Will be zero. The starting pixel of the source. + // fSrcOffsetUnits: Will only be non-zero if we are sampling, + // since sampling implies that we will skip some + // pixels. Note that this is measured in bits + // or bytes depending on which is natural for + // SrcConfig. + // fDstOffset: First pixel to write in destination. + // fDstOffsetBytes: fDstOffset * fDstBPP. + // fSrcWidth: The entire width of the source pixels, before + // any sampling is performed. + // fDstWidth: The entire width of the destination memory, + // before any sampling is performed. + // fSwizzleWidth: The actual number of pixels that will be + // written by the RowProc. This is a scaled + // version of fSrcWidth. + // fAllocatedWidth: The actual number of pixels in destination + // memory. This is a scaled version of + // fDstWidth. + // + // If we are not subsetting, these fields are more straightforward. + // fSrcOffset = fDstOffet = fDstOffsetBytes = 0 + // fSrcOffsetUnits may be non-zero (we will skip the first few pixels when sampling) + // fSrcWidth = fDstWidth = Full original width + // fSwizzleWidth = fAllcoatedWidth = Scaled width (if we are sampling) + const int fSrcOffset; + const int fDstOffset; + int fSrcOffsetUnits; + int fDstOffsetBytes; + const int fSrcWidth; + const int fDstWidth; + int fSwizzleWidth; + int fAllocatedWidth; + + int fSampleX; // Step between X samples + const int fSrcBPP; // Bits/bytes per pixel for the SrcConfig + // if bitsPerPixel % 8 == 0 + // fBPP is bytesPerPixel + // else + // fBPP is bitsPerPixel + const int fDstBPP; // Bytes per pixel for the destination color type + + SkSwizzler(RowProc fastProc, RowProc proc, const SkPMColor* ctable, int srcOffset, + int srcWidth, int dstOffset, int dstWidth, int srcBPP, int dstBPP); + static std::unique_ptr Make(const SkImageInfo& dstInfo, RowProc fastProc, + RowProc proc, const SkPMColor* ctable, int srcBPP, int dstBPP, + const SkCodec::Options& options, const SkIRect* frame); + + int onSetSampleX(int) override; + +}; +#endif // SkSwizzler_DEFINED diff --git a/gfx/skia/skia/src/codec/SkWbmpCodec.cpp b/gfx/skia/skia/src/codec/SkWbmpCodec.cpp new file mode 100644 index 0000000000..8190c50322 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWbmpCodec.cpp @@ -0,0 +1,193 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkWbmpCodec.h" + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkColorTable.h" + +// Each bit represents a pixel, so width is actually a number of bits. +// A row will always be stored in bytes, so we round width up to the +// nearest multiple of 8 to get the number of bits actually in the row. +// We then divide by 8 to convert to bytes. +static inline size_t get_src_row_bytes(int width) { + return SkAlign8(width) >> 3; +} + +static inline bool valid_color_type(const SkImageInfo& dstInfo) { + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + case kGray_8_SkColorType: + case kRGB_565_SkColorType: + return true; + case kRGBA_F16_SkColorType: + return dstInfo.colorSpace(); + default: + return false; + } +} + +static bool read_byte(SkStream* stream, uint8_t* data) +{ + return stream->read(data, 1) == 1; +} + +// http://en.wikipedia.org/wiki/Variable-length_quantity +static bool read_mbf(SkStream* stream, uint64_t* value) { + uint64_t n = 0; + uint8_t data; + const uint64_t kLimit = 0xFE00000000000000; + SkASSERT(kLimit == ~((~static_cast(0)) >> 7)); + do { + if (n & kLimit) { // Will overflow on shift by 7. + return false; + } + if (stream->read(&data, 1) != 1) { + return false; + } + n = (n << 7) | (data & 0x7F); + } while (data & 0x80); + *value = n; + return true; +} + +static bool read_header(SkStream* stream, SkISize* size) { + { + uint8_t data; + if (!read_byte(stream, &data) || data != 0) { // unknown type + return false; + } + if (!read_byte(stream, &data) || (data & 0x9F)) { // skip fixed header + return false; + } + } + + uint64_t width, height; + if (!read_mbf(stream, &width) || width > 0xFFFF || !width) { + return false; + } + if (!read_mbf(stream, &height) || height > 0xFFFF || !height) { + return false; + } + if (size) { + *size = SkISize::Make(SkToS32(width), SkToS32(height)); + } + return true; +} + +bool SkWbmpCodec::onRewind() { + return read_header(this->stream(), nullptr); +} + +bool SkWbmpCodec::readRow(uint8_t* row) { + return this->stream()->read(row, fSrcRowBytes) == fSrcRowBytes; +} + +SkWbmpCodec::SkWbmpCodec(SkEncodedInfo&& info, std::unique_ptr stream) + // Wbmp does not need a colorXform, so choose an arbitrary srcFormat. + : INHERITED(std::move(info), skcms_PixelFormat(), + std::move(stream)) + , fSrcRowBytes(get_src_row_bytes(this->dimensions().width())) + , fSwizzler(nullptr) +{} + +SkEncodedImageFormat SkWbmpCodec::onGetEncodedFormat() const { + return SkEncodedImageFormat::kWBMP; +} + +bool SkWbmpCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, + bool /*needsColorXform*/) { + return valid_color_type(dst) && valid_alpha(dst.alphaType(), srcIsOpaque); +} + +SkCodec::Result SkWbmpCodec::onGetPixels(const SkImageInfo& info, + void* dst, + size_t rowBytes, + const Options& options, + int* rowsDecoded) { + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + // Initialize the swizzler + std::unique_ptr swizzler = SkSwizzler::Make(this->getEncodedInfo(), nullptr, info, + options); + SkASSERT(swizzler); + + // Perform the decode + SkISize size = info.dimensions(); + SkAutoTMalloc src(fSrcRowBytes); + void* dstRow = dst; + for (int y = 0; y < size.height(); ++y) { + if (!this->readRow(src.get())) { + *rowsDecoded = y; + return kIncompleteInput; + } + swizzler->swizzle(dstRow, src.get()); + dstRow = SkTAddOffset(dstRow, rowBytes); + } + return kSuccess; +} + +bool SkWbmpCodec::IsWbmp(const void* buffer, size_t bytesRead) { + SkMemoryStream stream(buffer, bytesRead, false); + return read_header(&stream, nullptr); +} + +std::unique_ptr SkWbmpCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + SkISize size; + if (!read_header(stream.get(), &size)) { + // This already succeeded in IsWbmp, so this stream was corrupted in/ + // after rewind. + *result = kCouldNotRewind; + return nullptr; + } + *result = kSuccess; + auto info = SkEncodedInfo::Make(size.width(), size.height(), SkEncodedInfo::kGray_Color, + SkEncodedInfo::kOpaque_Alpha, 1); + return std::unique_ptr(new SkWbmpCodec(std::move(info), std::move(stream))); +} + +int SkWbmpCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { + void* dstRow = dst; + for (int y = 0; y < count; ++y) { + if (!this->readRow(fSrcBuffer.get())) { + return y; + } + fSwizzler->swizzle(dstRow, fSrcBuffer.get()); + dstRow = SkTAddOffset(dstRow, dstRowBytes); + } + return count; +} + +bool SkWbmpCodec::onSkipScanlines(int count) { + const size_t bytesToSkip = count * fSrcRowBytes; + return this->stream()->skip(bytesToSkip) == bytesToSkip; +} + +SkCodec::Result SkWbmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) { + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), nullptr, dstInfo, options); + SkASSERT(fSwizzler); + + fSrcBuffer.reset(fSrcRowBytes); + + return kSuccess; +} diff --git a/gfx/skia/skia/src/codec/SkWbmpCodec.h b/gfx/skia/skia/src/codec/SkWbmpCodec.h new file mode 100644 index 0000000000..30af5d7a5f --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWbmpCodec.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodec_wbmp_DEFINED +#define SkCodec_wbmp_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "src/codec/SkSwizzler.h" + +class SkWbmpCodec final : public SkCodec { +public: + static bool IsWbmp(const void*, size_t); + + /* + * Assumes IsWbmp was called and returned true + * Creates a wbmp codec + * Takes ownership of the stream + */ + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + +protected: + SkEncodedImageFormat onGetEncodedFormat() const override; + Result onGetPixels(const SkImageInfo&, void*, size_t, + const Options&, int*) override; + bool onRewind() override; + bool conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, + bool needsXform) override; + // No need to Xform; all pixels are either black or white. + bool usesColorXform() const override { return false; } +private: + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler || !createIfNecessary); + return fSwizzler.get(); + } + + /* + * Read a src row from the encoded stream + */ + bool readRow(uint8_t* row); + + SkWbmpCodec(SkEncodedInfo&&, std::unique_ptr); + + const size_t fSrcRowBytes; + + // Used for scanline decodes: + std::unique_ptr fSwizzler; + SkAutoTMalloc fSrcBuffer; + + int onGetScanlines(void* dst, int count, size_t dstRowBytes) override; + bool onSkipScanlines(int count) override; + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) override; + + typedef SkCodec INHERITED; +}; + +#endif // SkCodec_wbmp_DEFINED diff --git a/gfx/skia/skia/src/codec/SkWebpCodec.cpp b/gfx/skia/skia/src/codec/SkWebpCodec.cpp new file mode 100644 index 0000000000..a90d452b68 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWebpCodec.cpp @@ -0,0 +1,567 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkWebpCodec.h" + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/codec/SkCodecAnimationPriv.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkParseEncodedOrigin.h" +#include "src/codec/SkSampler.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkStreamPriv.h" + +// A WebP decoder on top of (subset of) libwebp +// For more information on WebP image format, and libwebp library, see: +// https://code.google.com/speed/webp/ +// http://www.webmproject.org/code/#libwebp-webp-image-library +// https://chromium.googlesource.com/webm/libwebp + +// If moving libwebp out of skia source tree, path for webp headers must be +// updated accordingly. Here, we enforce using local copy in webp sub-directory. +#include "webp/decode.h" +#include "webp/demux.h" +#include "webp/encode.h" + +bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) { + // WEBP starts with the following: + // RIFFXXXXWEBPVP + // Where XXXX is unspecified. + const char* bytes = static_cast(buf); + return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6); +} + +// Parse headers of RIFF container, and check for valid Webp (VP8) content. +// Returns an SkWebpCodec on success +std::unique_ptr SkWebpCodec::MakeFromStream(std::unique_ptr stream, + Result* result) { + // Webp demux needs a contiguous data buffer. + sk_sp data = nullptr; + if (stream->getMemoryBase()) { + // It is safe to make without copy because we'll hold onto the stream. + data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength()); + } else { + data = SkCopyStreamToData(stream.get()); + + // If we are forced to copy the stream to a data, we can go ahead and delete the stream. + stream.reset(nullptr); + } + + // It's a little strange that the |demux| will outlive |webpData|, though it needs the + // pointer in |webpData| to remain valid. This works because the pointer remains valid + // until the SkData is freed. + WebPData webpData = { data->bytes(), data->size() }; + WebPDemuxState state; + SkAutoTCallVProc demux(WebPDemuxPartial(&webpData, &state)); + switch (state) { + case WEBP_DEMUX_PARSE_ERROR: + *result = kInvalidInput; + return nullptr; + case WEBP_DEMUX_PARSING_HEADER: + *result = kIncompleteInput; + return nullptr; + case WEBP_DEMUX_PARSED_HEADER: + case WEBP_DEMUX_DONE: + SkASSERT(demux); + break; + } + + const int width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); + const int height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); + + // Sanity check for image size that's about to be decoded. + { + const int64_t size = sk_64_mul(width, height); + // now check that if we are 4-bytes per pixel, we also don't overflow + if (!SkTFitsIn(size) || SkTo(size) > (0x7FFFFFFF >> 2)) { + *result = kInvalidInput; + return nullptr; + } + } + + std::unique_ptr profile = nullptr; + { + WebPChunkIterator chunkIterator; + SkAutoTCallVProc autoCI(&chunkIterator); + if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { + // FIXME: I think this could be MakeWithoutCopy + auto chunk = SkData::MakeWithCopy(chunkIterator.chunk.bytes, chunkIterator.chunk.size); + profile = SkEncodedInfo::ICCProfile::Make(std::move(chunk)); + } + if (profile && profile->profile()->data_color_space != skcms_Signature_RGB) { + profile = nullptr; + } + } + + SkEncodedOrigin origin = kDefault_SkEncodedOrigin; + { + WebPChunkIterator chunkIterator; + SkAutoTCallVProc autoCI(&chunkIterator); + if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunkIterator)) { + SkParseEncodedOrigin(chunkIterator.chunk.bytes, chunkIterator.chunk.size, &origin); + } + } + + // Get the first frame and its "features" to determine the color and alpha types. + WebPIterator frame; + SkAutoTCallVProc autoFrame(&frame); + if (!WebPDemuxGetFrame(demux, 1, &frame)) { + *result = kIncompleteInput; + return nullptr; + } + + WebPBitstreamFeatures features; + switch (WebPGetFeatures(frame.fragment.bytes, frame.fragment.size, &features)) { + case VP8_STATUS_OK: + break; + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_NOT_ENOUGH_DATA: + *result = kIncompleteInput; + return nullptr; + default: + *result = kInvalidInput; + return nullptr; + } + + const bool hasAlpha = SkToBool(frame.has_alpha) + || frame.width != width || frame.height != height; + SkEncodedInfo::Color color; + SkEncodedInfo::Alpha alpha; + switch (features.format) { + case 0: + // This indicates a "mixed" format. We could see this for + // animated webps (multiple fragments). + // We could also guess kYUV here, but I think it makes more + // sense to guess kBGRA which is likely closer to the final + // output. Otherwise, we might end up converting + // BGRA->YUVA->BGRA. + // Fallthrough: + case 2: + // This is the lossless format (BGRA). + if (hasAlpha) { + color = SkEncodedInfo::kBGRA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } else { + color = SkEncodedInfo::kBGRX_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + case 1: + // This is the lossy format (YUV). + if (hasAlpha) { + color = SkEncodedInfo::kYUVA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } else { + color = SkEncodedInfo::kYUV_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + default: + *result = kInvalidInput; + return nullptr; + } + + + *result = kSuccess; + SkEncodedInfo info = SkEncodedInfo::Make(width, height, color, alpha, 8, std::move(profile)); + return std::unique_ptr(new SkWebpCodec(std::move(info), std::move(stream), + demux.release(), std::move(data), origin)); +} + +static WEBP_CSP_MODE webp_decode_mode(SkColorType dstCT, bool premultiply) { + switch (dstCT) { + case kBGRA_8888_SkColorType: + return premultiply ? MODE_bgrA : MODE_BGRA; + case kRGBA_8888_SkColorType: + return premultiply ? MODE_rgbA : MODE_RGBA; + case kRGB_565_SkColorType: + return MODE_RGB_565; + default: + return MODE_LAST; + } +} + +SkWebpCodec::Frame* SkWebpCodec::FrameHolder::appendNewFrame(bool hasAlpha) { + const int i = this->size(); + fFrames.emplace_back(i, hasAlpha ? SkEncodedInfo::kUnpremul_Alpha + : SkEncodedInfo::kOpaque_Alpha); + return &fFrames[i]; +} + +bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { + if (!desiredSubset) { + return false; + } + + if (!this->bounds().contains(*desiredSubset)) { + return false; + } + + // As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we + // decode this exact subset. + // Leave right and bottom unmodified, so we suggest a slightly larger subset than requested. + desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1; + desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1; + return true; +} + +int SkWebpCodec::onGetRepetitionCount() { + auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); + if (!(flags & ANIMATION_FLAG)) { + return 0; + } + + const int repCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT); + if (0 == repCount) { + return kRepetitionCountInfinite; + } + + return repCount; +} + +int SkWebpCodec::onGetFrameCount() { + auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); + if (!(flags & ANIMATION_FLAG)) { + return 1; + } + + const uint32_t oldFrameCount = fFrameHolder.size(); + if (fFailed) { + return oldFrameCount; + } + + const uint32_t frameCount = WebPDemuxGetI(fDemux, WEBP_FF_FRAME_COUNT); + if (oldFrameCount == frameCount) { + // We have already parsed this. + return frameCount; + } + + fFrameHolder.reserve(frameCount); + + for (uint32_t i = oldFrameCount; i < frameCount; i++) { + WebPIterator iter; + SkAutoTCallVProc autoIter(&iter); + + if (!WebPDemuxGetFrame(fDemux.get(), i + 1, &iter)) { + fFailed = true; + break; + } + + // libwebp only reports complete frames of an animated image. + SkASSERT(iter.complete); + + Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha); + frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height); + frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? + SkCodecAnimation::DisposalMethod::kRestoreBGColor : + SkCodecAnimation::DisposalMethod::kKeep); + frame->setDuration(iter.duration); + if (WEBP_MUX_BLEND != iter.blend_method) { + frame->setBlend(SkCodecAnimation::Blend::kBG); + } + fFrameHolder.setAlphaAndRequiredFrame(frame); + } + + return fFrameHolder.size(); + +} + +const SkFrame* SkWebpCodec::FrameHolder::onGetFrame(int i) const { + return static_cast(this->frame(i)); +} + +const SkWebpCodec::Frame* SkWebpCodec::FrameHolder::frame(int i) const { + SkASSERT(i >= 0 && i < this->size()); + return &fFrames[i]; +} + +bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { + if (i >= fFrameHolder.size()) { + return false; + } + + const Frame* frame = fFrameHolder.frame(i); + if (!frame) { + return false; + } + + if (frameInfo) { + frameInfo->fRequiredFrame = frame->getRequiredFrame(); + frameInfo->fDuration = frame->getDuration(); + // libwebp only reports fully received frames for an + // animated image. + frameInfo->fFullyReceived = true; + frameInfo->fAlphaType = frame->hasAlpha() ? kUnpremul_SkAlphaType + : kOpaque_SkAlphaType; + frameInfo->fDisposalMethod = frame->getDisposalMethod(); + } + + return true; +} + +static bool is_8888(SkColorType colorType) { + switch (colorType) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + return true; + default: + return false; + } +} + +// Requires that the src input be unpremultiplied (or opaque). +static void blend_line(SkColorType dstCT, void* dst, + SkColorType srcCT, const void* src, + SkAlphaType dstAt, + bool srcHasAlpha, + int width) { + SkRasterPipeline_MemoryCtx dst_ctx = { (void*)dst, 0 }, + src_ctx = { (void*)src, 0 }; + + SkRasterPipeline_<256> p; + + p.append_load_dst(dstCT, &dst_ctx); + if (kUnpremul_SkAlphaType == dstAt) { + p.append(SkRasterPipeline::premul_dst); + } + + p.append_load(srcCT, &src_ctx); + if (srcHasAlpha) { + p.append(SkRasterPipeline::premul); + } + + p.append(SkRasterPipeline::srcover); + + if (kUnpremul_SkAlphaType == dstAt) { + p.append(SkRasterPipeline::unpremul); + } + p.append_store(dstCT, &dst_ctx); + + p.run(0,0, width,1); +} + +SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, + const Options& options, int* rowsDecodedPtr) { + const int index = options.fFrameIndex; + SkASSERT(0 == index || index < fFrameHolder.size()); + SkASSERT(0 == index || !options.fSubset); + + WebPDecoderConfig config; + if (0 == WebPInitDecoderConfig(&config)) { + // ABI mismatch. + // FIXME: New enum for this? + return kInvalidInput; + } + + // Free any memory associated with the buffer. Must be called last, so we declare it first. + SkAutoTCallVProc autoFree(&(config.output)); + + WebPIterator frame; + SkAutoTCallVProc autoFrame(&frame); + // If this succeeded in onGetFrameCount(), it should succeed again here. + SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame)); + + const bool independent = index == 0 ? true : + (fFrameHolder.frame(index)->getRequiredFrame() == kNoFrame); + // Get the frameRect. libwebp will have already signaled an error if this is not fully + // contained by the canvas. + auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height); + SkASSERT(this->bounds().contains(frameRect)); + const bool frameIsSubset = frameRect != this->bounds(); + if (independent && frameIsSubset) { + SkSampler::Fill(dstInfo, dst, rowBytes, options.fZeroInitialized); + } + + int dstX = frameRect.x(); + int dstY = frameRect.y(); + int subsetWidth = frameRect.width(); + int subsetHeight = frameRect.height(); + if (options.fSubset) { + SkIRect subset = *options.fSubset; + SkASSERT(this->bounds().contains(subset)); + SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); + SkASSERT(this->getValidSubset(&subset) && subset == *options.fSubset); + + if (!SkIRect::Intersects(subset, frameRect)) { + return kSuccess; + } + + int minXOffset = SkTMin(dstX, subset.x()); + int minYOffset = SkTMin(dstY, subset.y()); + dstX -= minXOffset; + dstY -= minYOffset; + frameRect.offset(-minXOffset, -minYOffset); + subset.offset(-minXOffset, -minYOffset); + + // Just like we require that the requested subset x and y offset are even, libwebp + // guarantees that the frame x and y offset are even (it's actually impossible to specify + // an odd frame offset). So we can still guarantee that the adjusted offsets are even. + SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); + + SkIRect intersection; + SkAssertResult(intersection.intersect(frameRect, subset)); + subsetWidth = intersection.width(); + subsetHeight = intersection.height(); + + config.options.use_cropping = 1; + config.options.crop_left = subset.x(); + config.options.crop_top = subset.y(); + config.options.crop_width = subsetWidth; + config.options.crop_height = subsetHeight; + } + + // Ignore the frame size and offset when determining if scaling is necessary. + int scaledWidth = subsetWidth; + int scaledHeight = subsetHeight; + SkISize srcSize = options.fSubset ? options.fSubset->size() : this->dimensions(); + if (srcSize != dstInfo.dimensions()) { + config.options.use_scaling = 1; + + if (frameIsSubset) { + float scaleX = ((float) dstInfo.width()) / srcSize.width(); + float scaleY = ((float) dstInfo.height()) / srcSize.height(); + + // We need to be conservative here and floor rather than round. + // Otherwise, we may find ourselves decoding off the end of memory. + dstX = scaleX * dstX; + scaledWidth = scaleX * scaledWidth; + dstY = scaleY * dstY; + scaledHeight = scaleY * scaledHeight; + if (0 == scaledWidth || 0 == scaledHeight) { + return kSuccess; + } + } else { + scaledWidth = dstInfo.width(); + scaledHeight = dstInfo.height(); + } + + config.options.scaled_width = scaledWidth; + config.options.scaled_height = scaledHeight; + } + + const bool blendWithPrevFrame = !independent && frame.blend_method == WEBP_MUX_BLEND + && frame.has_alpha; + + SkBitmap webpDst; + auto webpInfo = dstInfo; + if (!frame.has_alpha) { + webpInfo = webpInfo.makeAlphaType(kOpaque_SkAlphaType); + } + if (this->colorXform()) { + // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a + // color transform, we should decode to whatever is easiest for libwebp, and then let the + // color transform swizzle if necessary. + // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is + // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA. + webpInfo = webpInfo.makeColorType(kBGRA_8888_SkColorType); + + if (webpInfo.alphaType() == kPremul_SkAlphaType) { + webpInfo = webpInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + } + + if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) { + // We will decode the entire image and then perform the color transform. libwebp + // does not provide a row-by-row API. This is a shame particularly when we do not want + // 8888, since we will need to create another image sized buffer. + webpDst.allocPixels(webpInfo); + } else { + // libwebp can decode directly into the output memory. + webpDst.installPixels(webpInfo, dst, rowBytes); + } + + config.output.colorspace = webp_decode_mode(webpInfo.colorType(), + frame.has_alpha && dstInfo.alphaType() == kPremul_SkAlphaType && !this->colorXform()); + config.output.is_external_memory = 1; + + config.output.u.RGBA.rgba = reinterpret_cast(webpDst.getAddr(dstX, dstY)); + config.output.u.RGBA.stride = static_cast(webpDst.rowBytes()); + config.output.u.RGBA.size = webpDst.computeByteSize(); + + SkAutoTCallVProc idec(WebPIDecode(nullptr, 0, &config)); + if (!idec) { + return kInvalidInput; + } + + int rowsDecoded = 0; + SkCodec::Result result; + switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) { + case VP8_STATUS_OK: + rowsDecoded = scaledHeight; + result = kSuccess; + break; + case VP8_STATUS_SUSPENDED: + if (!WebPIDecGetRGB(idec, &rowsDecoded, nullptr, nullptr, nullptr) + || rowsDecoded <= 0) { + return kInvalidInput; + } + *rowsDecodedPtr = rowsDecoded + dstY; + result = kIncompleteInput; + break; + default: + return kInvalidInput; + } + + const size_t dstBpp = dstInfo.bytesPerPixel(); + dst = SkTAddOffset(dst, dstBpp * dstX + rowBytes * dstY); + const size_t srcRowBytes = config.output.u.RGBA.stride; + + const auto dstCT = dstInfo.colorType(); + if (this->colorXform()) { + uint32_t* xformSrc = (uint32_t*) config.output.u.RGBA.rgba; + SkBitmap tmp; + void* xformDst; + + if (blendWithPrevFrame) { + // Xform into temporary bitmap big enough for one row. + tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1)); + xformDst = tmp.getPixels(); + } else { + xformDst = dst; + } + + for (int y = 0; y < rowsDecoded; y++) { + this->applyColorXform(xformDst, xformSrc, scaledWidth); + if (blendWithPrevFrame) { + blend_line(dstCT, dst, dstCT, xformDst, + dstInfo.alphaType(), frame.has_alpha, scaledWidth); + dst = SkTAddOffset(dst, rowBytes); + } else { + xformDst = SkTAddOffset(xformDst, rowBytes); + } + xformSrc = SkTAddOffset(xformSrc, srcRowBytes); + } + } else if (blendWithPrevFrame) { + const uint8_t* src = config.output.u.RGBA.rgba; + + for (int y = 0; y < rowsDecoded; y++) { + blend_line(dstCT, dst, webpDst.colorType(), src, + dstInfo.alphaType(), frame.has_alpha, scaledWidth); + src = SkTAddOffset(src, srcRowBytes); + dst = SkTAddOffset(dst, rowBytes); + } + } + + return result; +} + +SkWebpCodec::SkWebpCodec(SkEncodedInfo&& info, std::unique_ptr stream, + WebPDemuxer* demux, sk_sp data, SkEncodedOrigin origin) + : INHERITED(std::move(info), skcms_PixelFormat_BGRA_8888, std::move(stream), + origin) + , fDemux(demux) + , fData(std::move(data)) + , fFailed(false) +{ + const auto& eInfo = this->getEncodedInfo(); + fFrameHolder.setScreenSize(eInfo.width(), eInfo.height()); +} diff --git a/gfx/skia/skia/src/codec/SkWebpCodec.h b/gfx/skia/skia/src/codec/SkWebpCodec.h new file mode 100644 index 0000000000..36ff5357f0 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWebpCodec.h @@ -0,0 +1,104 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWebpCodec_DEFINED +#define SkWebpCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkFrameHolder.h" +#include "src/codec/SkScalingCodec.h" + +#include + +class SkStream; +extern "C" { + struct WebPDemuxer; + void WebPDemuxDelete(WebPDemuxer* dmux); +} + +class SkWebpCodec final : public SkScalingCodec { +public: + // Assumes IsWebp was called and returned true. + static std::unique_ptr MakeFromStream(std::unique_ptr, Result*); + static bool IsWebp(const void*, size_t); +protected: + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) override; + SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kWEBP; } + + bool onGetValidSubset(SkIRect* /* desiredSubset */) const override; + + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + + const SkFrameHolder* getFrameHolder() const override { + return &fFrameHolder; + } + +private: + SkWebpCodec(SkEncodedInfo&&, std::unique_ptr, WebPDemuxer*, sk_sp, + SkEncodedOrigin); + + SkAutoTCallVProc fDemux; + + // fDemux has a pointer into this data. + // This should not be freed until the decode is completed. + sk_sp fData; + + class Frame : public SkFrame { + public: + Frame(int i, SkEncodedInfo::Alpha alpha) + : INHERITED(i) + , fReportedAlpha(alpha) + {} + + protected: + SkEncodedInfo::Alpha onReportedAlpha() const override { + return fReportedAlpha; + } + + private: + const SkEncodedInfo::Alpha fReportedAlpha; + + typedef SkFrame INHERITED; + }; + + class FrameHolder : public SkFrameHolder { + public: + ~FrameHolder() override {} + void setScreenSize(int w, int h) { + fScreenWidth = w; + fScreenHeight = h; + } + Frame* appendNewFrame(bool hasAlpha); + const Frame* frame(int i) const; + int size() const { + return static_cast(fFrames.size()); + } + void reserve(int size) { + fFrames.reserve(size); + } + + protected: + const SkFrame* onGetFrame(int i) const override; + + private: + std::vector fFrames; + }; + + FrameHolder fFrameHolder; + // Set to true if WebPDemuxGetFrame fails. This only means + // that we will cap the frame count to the frames that + // succeed. + bool fFailed; + + typedef SkScalingCodec INHERITED; +}; +#endif // SkWebpCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkWuffsCodec.cpp b/gfx/skia/skia/src/codec/SkWuffsCodec.cpp new file mode 100644 index 0000000000..f7ada1f548 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWuffsCodec.cpp @@ -0,0 +1,870 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkWuffsCodec.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/private/SkMalloc.h" +#include "src/codec/SkFrameHolder.h" +#include "src/codec/SkSampler.h" +#include "src/codec/SkScalingCodec.h" +#include "src/core/SkDraw.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkUtils.h" + +#include + +// Wuffs ships as a "single file C library" or "header file library" as per +// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt +// +// As we have not #define'd WUFFS_IMPLEMENTATION, the #include here is +// including a header file, even though that file name ends in ".c". +#if defined(WUFFS_IMPLEMENTATION) +#error "SkWuffsCodec should not #define WUFFS_IMPLEMENTATION" +#endif +#include "wuffs-v0.2.c" +#if WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT < 1942 +#error "Wuffs version is too old. Upgrade to the latest version." +#endif + +#define SK_WUFFS_CODEC_BUFFER_SIZE 4096 + +static bool fill_buffer(wuffs_base__io_buffer* b, SkStream* s) { + b->compact(); + size_t num_read = s->read(b->data.ptr + b->meta.wi, b->data.len - b->meta.wi); + b->meta.wi += num_read; + b->meta.closed = s->isAtEnd(); + return num_read > 0; +} + +static bool seek_buffer(wuffs_base__io_buffer* b, SkStream* s, uint64_t pos) { + // Try to re-position the io_buffer's meta.ri read-index first, which is + // cheaper than seeking in the backing SkStream. + if ((pos >= b->meta.pos) && (pos - b->meta.pos <= b->meta.wi)) { + b->meta.ri = pos - b->meta.pos; + return true; + } + // Seek in the backing SkStream. + if ((pos > SIZE_MAX) || (!s->seek(pos))) { + return false; + } + b->meta.wi = 0; + b->meta.ri = 0; + b->meta.pos = pos; + b->meta.closed = false; + return true; +} + +static SkEncodedInfo::Alpha wuffs_blend_to_skia_alpha(wuffs_base__animation_blend w) { + return (w == WUFFS_BASE__ANIMATION_BLEND__OPAQUE) ? SkEncodedInfo::kOpaque_Alpha + : SkEncodedInfo::kUnpremul_Alpha; +} + +static SkCodecAnimation::Blend wuffs_blend_to_skia_blend(wuffs_base__animation_blend w) { + return (w == WUFFS_BASE__ANIMATION_BLEND__SRC) ? SkCodecAnimation::Blend::kBG + : SkCodecAnimation::Blend::kPriorFrame; +} + +static SkCodecAnimation::DisposalMethod wuffs_disposal_to_skia_disposal( + wuffs_base__animation_disposal w) { + switch (w) { + case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND: + return SkCodecAnimation::DisposalMethod::kRestoreBGColor; + case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS: + return SkCodecAnimation::DisposalMethod::kRestorePrevious; + default: + return SkCodecAnimation::DisposalMethod::kKeep; + } +} + +// -------------------------------- Class definitions + +class SkWuffsCodec; + +class SkWuffsFrame final : public SkFrame { +public: + SkWuffsFrame(wuffs_base__frame_config* fc); + + SkCodec::FrameInfo frameInfo(bool fullyReceived) const; + uint64_t ioPosition() const; + + // SkFrame overrides. + SkEncodedInfo::Alpha onReportedAlpha() const override; + +private: + uint64_t fIOPosition; + SkEncodedInfo::Alpha fReportedAlpha; + + typedef SkFrame INHERITED; +}; + +// SkWuffsFrameHolder is a trivial indirector that forwards its calls onto a +// SkWuffsCodec. It is a separate class as SkWuffsCodec would otherwise +// inherit from both SkCodec and SkFrameHolder, and Skia style discourages +// multiple inheritance (e.g. with its "typedef Foo INHERITED" convention). +class SkWuffsFrameHolder final : public SkFrameHolder { +public: + SkWuffsFrameHolder() : INHERITED() {} + + void init(SkWuffsCodec* codec, int width, int height); + + // SkFrameHolder overrides. + const SkFrame* onGetFrame(int i) const override; + +private: + const SkWuffsCodec* fCodec; + + typedef SkFrameHolder INHERITED; +}; + +class SkWuffsCodec final : public SkScalingCodec { +public: + SkWuffsCodec(SkEncodedInfo&& encodedInfo, + std::unique_ptr stream, + std::unique_ptr dec, + std::unique_ptr pixbuf_ptr, + std::unique_ptr workbuf_ptr, + size_t workbuf_len, + wuffs_base__image_config imgcfg, + wuffs_base__pixel_buffer pixbuf, + wuffs_base__io_buffer iobuf); + + const SkWuffsFrame* frame(int i) const; + +private: + // SkCodec overrides. + SkEncodedImageFormat onGetEncodedFormat() const override; + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) override; + const SkFrameHolder* getFrameHolder() const override; + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* dst, + size_t rowBytes, + const SkCodec::Options& options) override; + Result onIncrementalDecode(int* rowsDecoded) override; + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + + void readFrames(); + Result seekFrame(int frameIndex); + + Result resetDecoder(); + const char* decodeFrameConfig(); + const char* decodeFrame(); + void updateNumFullyReceivedFrames(); + + SkWuffsFrameHolder fFrameHolder; + std::unique_ptr fStream; + std::unique_ptr fDecoder; + std::unique_ptr fPixbufPtr; + std::unique_ptr fWorkbufPtr; + size_t fWorkbufLen; + + const uint64_t fFirstFrameIOPosition; + wuffs_base__frame_config fFrameConfig; + wuffs_base__pixel_buffer fPixelBuffer; + wuffs_base__io_buffer fIOBuffer; + + // Incremental decoding state. + uint8_t* fIncrDecDst; + size_t fIncrDecRowBytes; + bool fFirstCallToIncrementalDecode; + + uint64_t fNumFullyReceivedFrames; + std::vector fFrames; + bool fFramesComplete; + + // If calling an fDecoder method returns an incomplete status, then + // fDecoder is suspended in a coroutine (i.e. waiting on I/O or halted on a + // non-recoverable error). To keep its internal proof-of-safety invariants + // consistent, there's only two things you can safely do with a suspended + // Wuffs object: resume the coroutine, or reset all state (memset to zero + // and start again). + // + // If fDecoderIsSuspended, and we aren't sure that we're going to resume + // the coroutine, then we will need to call this->resetDecoder before + // calling other fDecoder methods. + bool fDecoderIsSuspended; + + uint8_t fBuffer[SK_WUFFS_CODEC_BUFFER_SIZE]; + + typedef SkScalingCodec INHERITED; +}; + +// -------------------------------- SkWuffsFrame implementation + +SkWuffsFrame::SkWuffsFrame(wuffs_base__frame_config* fc) + : INHERITED(fc->index()), + fIOPosition(fc->io_position()), + fReportedAlpha(wuffs_blend_to_skia_alpha(fc->blend())) { + wuffs_base__rect_ie_u32 r = fc->bounds(); + this->setXYWH(r.min_incl_x, r.min_incl_y, r.width(), r.height()); + this->setDisposalMethod(wuffs_disposal_to_skia_disposal(fc->disposal())); + this->setDuration(fc->duration() / WUFFS_BASE__FLICKS_PER_MILLISECOND); + this->setBlend(wuffs_blend_to_skia_blend(fc->blend())); +} + +SkCodec::FrameInfo SkWuffsFrame::frameInfo(bool fullyReceived) const { + SkCodec::FrameInfo ret; + ret.fRequiredFrame = getRequiredFrame(); + ret.fDuration = getDuration(); + ret.fFullyReceived = fullyReceived; + ret.fAlphaType = hasAlpha() ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType; + ret.fDisposalMethod = getDisposalMethod(); + return ret; +} + +uint64_t SkWuffsFrame::ioPosition() const { + return fIOPosition; +} + +SkEncodedInfo::Alpha SkWuffsFrame::onReportedAlpha() const { + return fReportedAlpha; +} + +// -------------------------------- SkWuffsFrameHolder implementation + +void SkWuffsFrameHolder::init(SkWuffsCodec* codec, int width, int height) { + fCodec = codec; + // Initialize SkFrameHolder's (the superclass) fields. + fScreenWidth = width; + fScreenHeight = height; +} + +const SkFrame* SkWuffsFrameHolder::onGetFrame(int i) const { + return fCodec->frame(i); +}; + +// -------------------------------- SkWuffsCodec implementation + +SkWuffsCodec::SkWuffsCodec(SkEncodedInfo&& encodedInfo, + std::unique_ptr stream, + std::unique_ptr dec, + std::unique_ptr pixbuf_ptr, + std::unique_ptr workbuf_ptr, + size_t workbuf_len, + wuffs_base__image_config imgcfg, + wuffs_base__pixel_buffer pixbuf, + wuffs_base__io_buffer iobuf) + : INHERITED(std::move(encodedInfo), + skcms_PixelFormat_RGBA_8888, + // Pass a nullptr SkStream to the SkCodec constructor. We + // manage the stream ourselves, as the default SkCodec behavior + // is too trigger-happy on rewinding the stream. + nullptr), + fFrameHolder(), + fStream(std::move(stream)), + fDecoder(std::move(dec)), + fPixbufPtr(std::move(pixbuf_ptr)), + fWorkbufPtr(std::move(workbuf_ptr)), + fWorkbufLen(workbuf_len), + fFirstFrameIOPosition(imgcfg.first_frame_io_position()), + fFrameConfig(wuffs_base__null_frame_config()), + fPixelBuffer(pixbuf), + fIOBuffer(wuffs_base__empty_io_buffer()), + fIncrDecDst(nullptr), + fIncrDecRowBytes(0), + fFirstCallToIncrementalDecode(false), + fNumFullyReceivedFrames(0), + fFramesComplete(false), + fDecoderIsSuspended(false) { + fFrameHolder.init(this, imgcfg.pixcfg.width(), imgcfg.pixcfg.height()); + + // Initialize fIOBuffer's fields, copying any outstanding data from iobuf to + // fIOBuffer, as iobuf's backing array may not be valid for the lifetime of + // this SkWuffsCodec object, but fIOBuffer's backing array (fBuffer) is. + SkASSERT(iobuf.data.len == SK_WUFFS_CODEC_BUFFER_SIZE); + memmove(fBuffer, iobuf.data.ptr, iobuf.meta.wi); + fIOBuffer.data = wuffs_base__make_slice_u8(fBuffer, SK_WUFFS_CODEC_BUFFER_SIZE); + fIOBuffer.meta = iobuf.meta; +} + +const SkWuffsFrame* SkWuffsCodec::frame(int i) const { + if ((0 <= i) && (static_cast(i) < fFrames.size())) { + return &fFrames[i]; + } + return nullptr; +} + +SkEncodedImageFormat SkWuffsCodec::onGetEncodedFormat() const { + return SkEncodedImageFormat::kGIF; +} + +SkCodec::Result SkWuffsCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, + size_t rowBytes, + const Options& options, + int* rowsDecoded) { + SkCodec::Result result = this->onStartIncrementalDecode(dstInfo, dst, rowBytes, options); + if (result != kSuccess) { + return result; + } + return this->onIncrementalDecode(rowsDecoded); +} + +const SkFrameHolder* SkWuffsCodec::getFrameHolder() const { + return &fFrameHolder; +} + +SkCodec::Result SkWuffsCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* dst, + size_t rowBytes, + const SkCodec::Options& options) { + if (!dst) { + return SkCodec::kInvalidParameters; + } + if (options.fSubset) { + return SkCodec::kUnimplemented; + } + if (options.fFrameIndex > 0 && SkColorTypeIsAlwaysOpaque(dstInfo.colorType())) { + return SkCodec::kInvalidConversion; + } + SkCodec::Result result = this->seekFrame(options.fFrameIndex); + if (result != SkCodec::kSuccess) { + return result; + } + + const char* status = this->decodeFrameConfig(); + if (status == wuffs_base__suspension__short_read) { + return SkCodec::kIncompleteInput; + } else if (status != nullptr) { + SkCodecPrintf("decodeFrameConfig: %s", status); + return SkCodec::kErrorInInput; + } + + uint32_t src_bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(fPixelBuffer.pixcfg.pixel_format()); + if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) { + return SkCodec::kInternalError; + } + size_t src_bytes_per_pixel = src_bits_per_pixel / 8; + + // Zero-initialize Wuffs' buffer covering the frame rect. + wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds(); + wuffs_base__table_u8 pixels = fPixelBuffer.plane(0); + for (uint32_t y = frame_rect.min_incl_y; y < frame_rect.max_excl_y; y++) { + sk_bzero(pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bytes_per_pixel), + frame_rect.width() * src_bytes_per_pixel); + } + + fIncrDecDst = static_cast(dst); + fIncrDecRowBytes = rowBytes; + fFirstCallToIncrementalDecode = true; + return SkCodec::kSuccess; +} + +static SkAlphaType to_alpha_type(bool opaque) { + return opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType; +} + +SkCodec::Result SkWuffsCodec::onIncrementalDecode(int* rowsDecoded) { + if (!fIncrDecDst) { + return SkCodec::kInternalError; + } + + SkCodec::Result result = SkCodec::kSuccess; + const char* status = this->decodeFrame(); + bool independent; + SkAlphaType alphaType; + const int index = options().fFrameIndex; + if (index == 0) { + independent = true; + alphaType = to_alpha_type(getEncodedInfo().opaque()); + } else { + const SkWuffsFrame* f = this->frame(index); + independent = f->getRequiredFrame() == SkCodec::kNoFrame; + alphaType = to_alpha_type(f->reportedAlpha() == SkEncodedInfo::kOpaque_Alpha); + } + if (status != nullptr) { + if (status == wuffs_base__suspension__short_read) { + result = SkCodec::kIncompleteInput; + } else { + SkCodecPrintf("decodeFrame: %s", status); + result = SkCodec::kErrorInInput; + } + + if (!independent) { + // For a dependent frame, we cannot blend the partial result, since + // that will overwrite the contribution from prior frames. + return result; + } + } + + uint32_t src_bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(fPixelBuffer.pixcfg.pixel_format()); + if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) { + return SkCodec::kInternalError; + } + size_t src_bytes_per_pixel = src_bits_per_pixel / 8; + + wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds(); + if (fFirstCallToIncrementalDecode) { + if (frame_rect.width() > (SIZE_MAX / src_bytes_per_pixel)) { + return SkCodec::kInternalError; + } + + auto bounds = SkIRect::MakeLTRB(frame_rect.min_incl_x, frame_rect.min_incl_y, + frame_rect.max_excl_x, frame_rect.max_excl_y); + + // If the frame rect does not fill the output, ensure that those pixels are not + // left uninitialized. + if (independent && (bounds != this->bounds() || result != kSuccess)) { + SkSampler::Fill(dstInfo(), fIncrDecDst, fIncrDecRowBytes, + options().fZeroInitialized); + } + fFirstCallToIncrementalDecode = false; + } else { + // Existing clients intend to only show frames beyond the first if they + // are complete (based on FrameInfo::fFullyReceived), since it might + // look jarring to draw a partial frame over an existing frame. If they + // changed their behavior and expected to continue decoding a partial + // frame after the first one, we'll need to update our blending code. + // Otherwise, if the frame were interlaced and not independent, the + // second pass may have an overlapping dirty_rect with the first, + // resulting in blending with the first pass. + SkASSERT(index == 0); + } + + if (rowsDecoded) { + *rowsDecoded = dstInfo().height(); + } + + // If the frame's dirty rect is empty, no need to swizzle. + wuffs_base__rect_ie_u32 dirty_rect = fDecoder->frame_dirty_rect(); + if (!dirty_rect.is_empty()) { + wuffs_base__table_u8 pixels = fPixelBuffer.plane(0); + + // The Wuffs model is that the dst buffer is the image, not the frame. + // The expectation is that you allocate the buffer once, but re-use it + // for the N frames, regardless of each frame's top-left co-ordinate. + // + // To get from the start (in the X-direction) of the image to the start + // of the dirty_rect, we adjust s by (dirty_rect.min_incl_x * src_bytes_per_pixel). + uint8_t* s = pixels.ptr + (dirty_rect.min_incl_y * pixels.stride) + + (dirty_rect.min_incl_x * src_bytes_per_pixel); + + // Currently, this is only used for GIF, which will never have an ICC profile. When it is + // used for other formats that might have one, we will need to transform from profiles that + // do not have corresponding SkColorSpaces. + SkASSERT(!getEncodedInfo().profile()); + + auto srcInfo = getInfo().makeWH(dirty_rect.width(), dirty_rect.height()) + .makeAlphaType(alphaType); + SkBitmap src; + src.installPixels(srcInfo, s, pixels.stride); + SkPaint paint; + if (independent) { + paint.setBlendMode(SkBlendMode::kSrc); + } + + SkDraw draw; + draw.fDst.reset(dstInfo(), fIncrDecDst, fIncrDecRowBytes); + SkMatrix matrix = SkMatrix::MakeRectToRect(SkRect::Make(this->dimensions()), + SkRect::Make(this->dstInfo().dimensions()), + SkMatrix::kFill_ScaleToFit); + draw.fMatrix = &matrix; + SkRasterClip rc(SkIRect::MakeSize(this->dstInfo().dimensions())); + draw.fRC = &rc; + + SkMatrix translate = SkMatrix::MakeTrans(dirty_rect.min_incl_x, dirty_rect.min_incl_y); + draw.drawBitmap(src, translate, nullptr, paint); + } + + if (result == SkCodec::kSuccess) { + fIncrDecDst = nullptr; + fIncrDecRowBytes = 0; + } + return result; +} + +int SkWuffsCodec::onGetFrameCount() { + // It is valid, in terms of the SkCodec API, to call SkCodec::getFrameCount + // while in an incremental decode (after onStartIncrementalDecode returns + // and before onIncrementalDecode returns kSuccess). + // + // We should not advance the SkWuffsCodec' stream while doing so, even + // though other SkCodec implementations can return increasing values from + // onGetFrameCount when given more data. If we tried to do so, the + // subsequent resume of the incremental decode would continue reading from + // a different position in the I/O stream, leading to an incorrect error. + // + // Other SkCodec implementations can move the stream forward during + // onGetFrameCount because they assume that the stream is rewindable / + // seekable. For example, an alternative GIF implementation may choose to + // store, for each frame walked past when merely counting the number of + // frames, the I/O position of each of the frame's GIF data blocks. (A GIF + // frame's compressed data can have multiple data blocks, each at most 255 + // bytes in length). Obviously, this can require O(numberOfFrames) extra + // memory to store these I/O positions. The constant factor is small, but + // it's still O(N), not O(1). + // + // Wuffs and SkWuffsCodec tries to minimize relying on the rewindable / + // seekable assumption. By design, Wuffs per se aims for O(1) memory use + // (after any pixel buffers are allocated) instead of O(N), and its I/O + // type, wuffs_base__io_buffer, is not necessarily rewindable or seekable. + // + // The Wuffs API provides a limited, optional form of seeking, to the start + // of an animation frame's data, but does not provide arbitrary save and + // load of its internal state whilst in the middle of an animation frame. + bool incrementalDecodeIsInProgress = fIncrDecDst != nullptr; + + if (!fFramesComplete && !incrementalDecodeIsInProgress) { + this->readFrames(); + this->updateNumFullyReceivedFrames(); + } + return fFrames.size(); +} + +bool SkWuffsCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const { + const SkWuffsFrame* f = this->frame(i); + if (!f) { + return false; + } + if (frameInfo) { + *frameInfo = f->frameInfo(static_cast(i) < this->fNumFullyReceivedFrames); + } + return true; +} + +int SkWuffsCodec::onGetRepetitionCount() { + // Convert from Wuffs's loop count to Skia's repeat count. Wuffs' uint32_t + // number is how many times to play the loop. Skia's int number is how many + // times to play the loop *after the first play*. Wuffs and Skia use 0 and + // kRepetitionCountInfinite respectively to mean loop forever. + uint32_t n = fDecoder->num_animation_loops(); + if (n == 0) { + return SkCodec::kRepetitionCountInfinite; + } + n--; + return n < INT_MAX ? n : INT_MAX; +} + +void SkWuffsCodec::readFrames() { + size_t n = fFrames.size(); + int i = n ? n - 1 : 0; + if (this->seekFrame(i) != SkCodec::kSuccess) { + return; + } + + // Iterate through the frames, converting from Wuffs' + // wuffs_base__frame_config type to Skia's SkWuffsFrame type. + for (; i < INT_MAX; i++) { + const char* status = this->decodeFrameConfig(); + if (status == nullptr) { + // No-op. + } else if (status == wuffs_base__warning__end_of_data) { + break; + } else { + return; + } + + if (static_cast(i) < fFrames.size()) { + continue; + } + fFrames.emplace_back(&fFrameConfig); + SkWuffsFrame* f = &fFrames[fFrames.size() - 1]; + fFrameHolder.setAlphaAndRequiredFrame(f); + } + + fFramesComplete = true; +} + +SkCodec::Result SkWuffsCodec::seekFrame(int frameIndex) { + if (fDecoderIsSuspended) { + SkCodec::Result res = this->resetDecoder(); + if (res != SkCodec::kSuccess) { + return res; + } + } + + uint64_t pos = 0; + if (frameIndex < 0) { + return SkCodec::kInternalError; + } else if (frameIndex == 0) { + pos = fFirstFrameIOPosition; + } else if (static_cast(frameIndex) < fFrames.size()) { + pos = fFrames[frameIndex].ioPosition(); + } else { + return SkCodec::kInternalError; + } + + if (!seek_buffer(&fIOBuffer, fStream.get(), pos)) { + return SkCodec::kInternalError; + } + const char* status = fDecoder->restart_frame(frameIndex, fIOBuffer.reader_io_position()); + if (status != nullptr) { + return SkCodec::kInternalError; + } + return SkCodec::kSuccess; +} + +// An overview of the Wuffs decoding API: +// +// An animated image (such as GIF) has an image header and then N frames. The +// image header gives e.g. the overall image's width and height. Each frame +// consists of a frame header (e.g. frame rectangle bounds, display duration) +// and a payload (the pixels). +// +// In Wuffs terminology, there is one image config and then N pairs of +// (frame_config, frame). To decode everything (without knowing N in advance) +// sequentially: +// - call wuffs_gif__decoder::decode_image_config +// - while (true) { +// - call wuffs_gif__decoder::decode_frame_config +// - if that returned wuffs_base__warning__end_of_data, break +// - call wuffs_gif__decoder::decode_frame +// - } +// +// The first argument to each decode_foo method is the destination struct to +// store the decoded information. +// +// For random (instead of sequential) access to an image's frames, call +// wuffs_gif__decoder::restart_frame to prepare to decode the i'th frame. +// Essentially, it restores the state to be at the top of the while loop above. +// The wuffs_base__io_buffer's reader position will also need to be set at the +// right point in the source data stream. The position for the i'th frame is +// calculated by the i'th decode_frame_config call. You can only call +// restart_frame after decode_image_config is called, explicitly or implicitly +// (see below), as decoding a single frame might require for-all-frames +// information like the overall image dimensions and the global palette. +// +// All of those decode_xxx calls are optional. For example, if +// decode_image_config is not called, then the first decode_frame_config call +// will implicitly parse and verify the image header, before parsing the first +// frame's header. Similarly, you can call only decode_frame N times, without +// calling decode_image_config or decode_frame_config, if you already know +// metadata like N and each frame's rectangle bounds by some other means (e.g. +// this is a first party, statically known image). +// +// Specifically, starting with an unknown (but re-windable) GIF image, if you +// want to just find N (i.e. count the number of frames), you can loop calling +// only the decode_frame_config method and avoid calling the more expensive +// decode_frame method. In terms of the underlying GIF image format, this will +// skip over the LZW-encoded pixel data, avoiding the costly LZW decompression. +// +// Those decode_xxx methods are also suspendible. They will return early (with +// a status code that is_suspendible and therefore isn't is_complete) if there +// isn't enough source data to complete the operation: an incremental decode. +// Calling decode_xxx again with additional source data will resume the +// previous operation, instead of starting a new operation. Calling decode_yyy +// whilst decode_xxx is suspended will result in an error. +// +// Once an error is encountered, whether from invalid source data or from a +// programming error such as calling decode_yyy while suspended in decode_xxx, +// all subsequent calls will be no-ops that return an error. To reset the +// decoder into something that does productive work, memset the entire struct +// to zero, check the Wuffs version and then, in order to be able to call +// restart_frame, call decode_image_config. The io_buffer and its associated +// stream will also need to be rewound. + +static SkCodec::Result reset_and_decode_image_config(wuffs_gif__decoder* decoder, + wuffs_base__image_config* imgcfg, + wuffs_base__io_buffer* b, + SkStream* s) { + // Calling decoder->initialize will memset it to zero. + const char* status = decoder->initialize(sizeof__wuffs_gif__decoder(), WUFFS_VERSION, 0); + if (status != nullptr) { + SkCodecPrintf("initialize: %s", status); + return SkCodec::kInternalError; + } + while (true) { + status = decoder->decode_image_config(imgcfg, b); + if (status == nullptr) { + break; + } else if (status != wuffs_base__suspension__short_read) { + SkCodecPrintf("decode_image_config: %s", status); + return SkCodec::kErrorInInput; + } else if (!fill_buffer(b, s)) { + return SkCodec::kIncompleteInput; + } + } + + // A GIF image's natural color model is indexed color: 1 byte per pixel, + // indexing a 256-element palette. + // + // For Skia, we override that to decode to 4 bytes per pixel, BGRA or RGBA. + wuffs_base__pixel_format pixfmt = 0; + switch (kN32_SkColorType) { + case kBGRA_8888_SkColorType: + pixfmt = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL; + break; + case kRGBA_8888_SkColorType: + pixfmt = WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL; + break; + default: + return SkCodec::kInternalError; + } + if (imgcfg) { + imgcfg->pixcfg.set(pixfmt, WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, imgcfg->pixcfg.width(), + imgcfg->pixcfg.height()); + } + + return SkCodec::kSuccess; +} + +SkCodec::Result SkWuffsCodec::resetDecoder() { + if (!fStream->rewind()) { + return SkCodec::kInternalError; + } + fIOBuffer.meta = wuffs_base__empty_io_buffer_meta(); + + SkCodec::Result result = + reset_and_decode_image_config(fDecoder.get(), nullptr, &fIOBuffer, fStream.get()); + if (result == SkCodec::kIncompleteInput) { + return SkCodec::kInternalError; + } else if (result != SkCodec::kSuccess) { + return result; + } + + fDecoderIsSuspended = false; + return SkCodec::kSuccess; +} + +const char* SkWuffsCodec::decodeFrameConfig() { + while (true) { + const char* status = fDecoder->decode_frame_config(&fFrameConfig, &fIOBuffer); + if ((status == wuffs_base__suspension__short_read) && + fill_buffer(&fIOBuffer, fStream.get())) { + continue; + } + fDecoderIsSuspended = !wuffs_base__status__is_complete(status); + this->updateNumFullyReceivedFrames(); + return status; + } +} + +const char* SkWuffsCodec::decodeFrame() { + while (true) { + const char* status = + fDecoder->decode_frame(&fPixelBuffer, &fIOBuffer, + wuffs_base__make_slice_u8(fWorkbufPtr.get(), fWorkbufLen), NULL); + if ((status == wuffs_base__suspension__short_read) && + fill_buffer(&fIOBuffer, fStream.get())) { + continue; + } + fDecoderIsSuspended = !wuffs_base__status__is_complete(status); + this->updateNumFullyReceivedFrames(); + return status; + } +} + +void SkWuffsCodec::updateNumFullyReceivedFrames() { + // num_decoded_frames's return value, n, can change over time, both up and + // down, as we seek back and forth in the underlying stream. + // fNumFullyReceivedFrames is the highest n we've seen. + uint64_t n = fDecoder->num_decoded_frames(); + if (fNumFullyReceivedFrames < n) { + fNumFullyReceivedFrames = n; + } +} + +// -------------------------------- SkWuffsCodec.h functions + +bool SkWuffsCodec_IsFormat(const void* buf, size_t bytesRead) { + constexpr const char* gif_ptr = "GIF8"; + constexpr size_t gif_len = 4; + return (bytesRead >= gif_len) && (memcmp(buf, gif_ptr, gif_len) == 0); +} + +std::unique_ptr SkWuffsCodec_MakeFromStream(std::unique_ptr stream, + SkCodec::Result* result) { + uint8_t buffer[SK_WUFFS_CODEC_BUFFER_SIZE]; + wuffs_base__io_buffer iobuf = + wuffs_base__make_io_buffer(wuffs_base__make_slice_u8(buffer, SK_WUFFS_CODEC_BUFFER_SIZE), + wuffs_base__empty_io_buffer_meta()); + wuffs_base__image_config imgcfg = wuffs_base__null_image_config(); + + // Wuffs is primarily a C library, not a C++ one. Furthermore, outside of + // the wuffs_base__etc types, the sizeof a file format specific type like + // GIF's wuffs_gif__decoder can vary between Wuffs versions. If p is of + // type wuffs_gif__decoder*, then the supported API treats p as a pointer + // to an opaque type: a private implementation detail. The API is always + // "set_foo(p, etc)" and not "p->foo = etc". + // + // See https://en.wikipedia.org/wiki/Opaque_pointer#C + // + // Thus, we don't use C++'s new operator (which requires knowing the sizeof + // the struct at compile time). Instead, we use sk_malloc_canfail, with + // sizeof__wuffs_gif__decoder returning the appropriate value for the + // (statically or dynamically) linked version of the Wuffs library. + // + // As a C (not C++) library, none of the Wuffs types have constructors or + // destructors. + // + // In RAII style, we can still use std::unique_ptr with these pointers, but + // we pair the pointer with sk_free instead of C++'s delete. + void* decoder_raw = sk_malloc_canfail(sizeof__wuffs_gif__decoder()); + if (!decoder_raw) { + *result = SkCodec::kInternalError; + return nullptr; + } + std::unique_ptr decoder( + reinterpret_cast(decoder_raw), &sk_free); + + SkCodec::Result reset_result = + reset_and_decode_image_config(decoder.get(), &imgcfg, &iobuf, stream.get()); + if (reset_result != SkCodec::kSuccess) { + *result = reset_result; + return nullptr; + } + + uint32_t width = imgcfg.pixcfg.width(); + uint32_t height = imgcfg.pixcfg.height(); + if ((width == 0) || (width > INT_MAX) || (height == 0) || (height > INT_MAX)) { + *result = SkCodec::kInvalidInput; + return nullptr; + } + + uint64_t workbuf_len = decoder->workbuf_len().max_incl; + void* workbuf_ptr_raw = nullptr; + if (workbuf_len) { + workbuf_ptr_raw = workbuf_len <= SIZE_MAX ? sk_malloc_canfail(workbuf_len) : nullptr; + if (!workbuf_ptr_raw) { + *result = SkCodec::kInternalError; + return nullptr; + } + } + std::unique_ptr workbuf_ptr( + reinterpret_cast(workbuf_ptr_raw), &sk_free); + + uint64_t pixbuf_len = imgcfg.pixcfg.pixbuf_len(); + void* pixbuf_ptr_raw = pixbuf_len <= SIZE_MAX ? sk_malloc_canfail(pixbuf_len) : nullptr; + if (!pixbuf_ptr_raw) { + *result = SkCodec::kInternalError; + return nullptr; + } + std::unique_ptr pixbuf_ptr( + reinterpret_cast(pixbuf_ptr_raw), &sk_free); + wuffs_base__pixel_buffer pixbuf = wuffs_base__null_pixel_buffer(); + + const char* status = pixbuf.set_from_slice( + &imgcfg.pixcfg, wuffs_base__make_slice_u8(pixbuf_ptr.get(), SkToSizeT(pixbuf_len))); + if (status != nullptr) { + SkCodecPrintf("set_from_slice: %s", status); + *result = SkCodec::kInternalError; + return nullptr; + } + + SkEncodedInfo::Color color = + (imgcfg.pixcfg.pixel_format() == WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL) + ? SkEncodedInfo::kBGRA_Color + : SkEncodedInfo::kRGBA_Color; + + // In Skia's API, the alpha we calculate here and return is only for the + // first frame. + SkEncodedInfo::Alpha alpha = imgcfg.first_frame_is_opaque() ? SkEncodedInfo::kOpaque_Alpha + : SkEncodedInfo::kBinary_Alpha; + + SkEncodedInfo encodedInfo = SkEncodedInfo::Make(width, height, color, alpha, 8); + + *result = SkCodec::kSuccess; + return std::unique_ptr(new SkWuffsCodec( + std::move(encodedInfo), std::move(stream), std::move(decoder), std::move(pixbuf_ptr), + std::move(workbuf_ptr), workbuf_len, imgcfg, pixbuf, iobuf)); +} diff --git a/gfx/skia/skia/src/codec/SkWuffsCodec.h b/gfx/skia/skia/src/codec/SkWuffsCodec.h new file mode 100644 index 0000000000..373c89c131 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWuffsCodec.h @@ -0,0 +1,17 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWuffsCodec_DEFINED +#define SkWuffsCodec_DEFINED + +#include "include/codec/SkCodec.h" + +// These functions' types match DecoderProc in SkCodec.cpp. +bool SkWuffsCodec_IsFormat(const void*, size_t); +std::unique_ptr SkWuffsCodec_MakeFromStream(std::unique_ptr, SkCodec::Result*); + +#endif // SkWuffsCodec_DEFINED diff --git a/gfx/skia/skia/src/core/Sk4px.h b/gfx/skia/skia/src/core/Sk4px.h new file mode 100644 index 0000000000..f2c4ea4e6b --- /dev/null +++ b/gfx/skia/skia/src/core/Sk4px.h @@ -0,0 +1,251 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4px_DEFINED +#define Sk4px_DEFINED + +#include "include/core/SkColor.h" +#include "include/private/SkColorData.h" +#include "include/private/SkNx.h" + +// This file may be included multiple times by .cpp files with different flags, leading +// to different definitions. Usually that doesn't matter because it's all inlined, but +// in Debug modes the compilers may not inline everything. So wrap everything in an +// anonymous namespace to give each includer their own silo of this code (or the linker +// will probably pick one randomly for us, which is rarely correct). +namespace { // NOLINT(google-build-namespaces) + +// 1, 2 or 4 SkPMColors, generally vectorized. +class Sk4px : public Sk16b { +public: + Sk4px(const Sk16b& v) : INHERITED(v) {} + + static Sk4px DupPMColor(SkPMColor c) { + Sk4u splat(c); + + Sk4px v; + memcpy(&v, &splat, 16); + return v; + } + + Sk4px alphas() const; // ARGB argb XYZW xyzw -> AAAA aaaa XXXX xxxx + Sk4px inv() const { return Sk16b(255) - *this; } + + // When loading or storing fewer than 4 SkPMColors, we use the low lanes. + static Sk4px Load4(const SkPMColor px[4]) { + Sk4px v; + memcpy(&v, px, 16); + return v; + } + static Sk4px Load2(const SkPMColor px[2]) { + Sk4px v; + memcpy(&v, px, 8); + return v; + } + static Sk4px Load1(const SkPMColor px[1]) { + Sk4px v; + memcpy(&v, px, 4); + return v; + } + + // Ditto for Alphas... Load2Alphas fills the low two lanes of Sk4px. + static Sk4px Load4Alphas(const SkAlpha[4]); // AaXx -> AAAA aaaa XXXX xxxx + static Sk4px Load2Alphas(const SkAlpha[2]); // Aa -> AAAA aaaa ???? ???? + + void store4(SkPMColor px[4]) const { memcpy(px, this, 16); } + void store2(SkPMColor px[2]) const { memcpy(px, this, 8); } + void store1(SkPMColor px[1]) const { memcpy(px, this, 4); } + + // 1, 2, or 4 SkPMColors with 16-bit components. + // This is most useful as the result of a multiply, e.g. from mulWiden(). + class Wide : public Sk16h { + public: + Wide(const Sk16h& v) : Sk16h(v) {} + + // Add, then pack the top byte of each component back down into 4 SkPMColors. + Sk4px addNarrowHi(const Sk16h&) const; + + // Rounds, i.e. (x+127) / 255. + Sk4px div255() const; + + // These just keep the types as Wide so the user doesn't have to keep casting. + Wide operator * (const Wide& o) const { return INHERITED::operator*(o); } + Wide operator + (const Wide& o) const { return INHERITED::operator+(o); } + Wide operator - (const Wide& o) const { return INHERITED::operator-(o); } + Wide operator >> (int bits) const { return INHERITED::operator>>(bits); } + Wide operator << (int bits) const { return INHERITED::operator<<(bits); } + + private: + typedef Sk16h INHERITED; + }; + + Wide widen() const; // Widen 8-bit values to low 8-bits of 16-bit lanes. + Wide mulWiden(const Sk16b&) const; // 8-bit x 8-bit -> 16-bit components. + + // The only 8-bit multiply we use is 8-bit x 8-bit -> 16-bit. Might as well make it pithy. + Wide operator * (const Sk4px& o) const { return this->mulWiden(o); } + + // These just keep the types as Sk4px so the user doesn't have to keep casting. + Sk4px operator + (const Sk4px& o) const { return INHERITED::operator+(o); } + Sk4px operator - (const Sk4px& o) const { return INHERITED::operator-(o); } + Sk4px operator < (const Sk4px& o) const { return INHERITED::operator<(o); } + Sk4px thenElse(const Sk4px& t, const Sk4px& e) const { return INHERITED::thenElse(t,e); } + + // Generally faster than (*this * o).div255(). + // May be incorrect by +-1, but is always exactly correct when *this or o is 0 or 255. + Sk4px approxMulDiv255(const Sk16b& o) const { + // (x*y + x) / 256 meets these criteria. (As of course does (x*y + y) / 256 by symmetry.) + // FYI: (x*y + 255) / 256 also meets these criteria. In my brief testing, it was slower. + return this->widen().addNarrowHi(*this * o); + } + + // A generic driver that maps fn over a src array into a dst array. + // fn should take an Sk4px (4 src pixels) and return an Sk4px (4 dst pixels). + template + static void MapSrc(int n, SkPMColor* dst, const SkPMColor* src, const Fn& fn) { + SkASSERT(dst); + SkASSERT(src); + // This looks a bit odd, but it helps loop-invariant hoisting across different calls to fn. + // Basically, we need to make sure we keep things inside a single loop. + while (n > 0) { + if (n >= 8) { + Sk4px dst0 = fn(Load4(src+0)), + dst4 = fn(Load4(src+4)); + dst0.store4(dst+0); + dst4.store4(dst+4); + dst += 8; src += 8; n -= 8; + continue; // Keep our stride at 8 pixels as long as possible. + } + SkASSERT(n <= 7); + if (n >= 4) { + fn(Load4(src)).store4(dst); + dst += 4; src += 4; n -= 4; + } + if (n >= 2) { + fn(Load2(src)).store2(dst); + dst += 2; src += 2; n -= 2; + } + if (n >= 1) { + fn(Load1(src)).store1(dst); + } + break; + } + } + + // As above, but with dst4' = fn(dst4, src4). + template + static void MapDstSrc(int n, SkPMColor* dst, const SkPMColor* src, const Fn& fn) { + SkASSERT(dst); + SkASSERT(src); + while (n > 0) { + if (n >= 8) { + Sk4px dst0 = fn(Load4(dst+0), Load4(src+0)), + dst4 = fn(Load4(dst+4), Load4(src+4)); + dst0.store4(dst+0); + dst4.store4(dst+4); + dst += 8; src += 8; n -= 8; + continue; // Keep our stride at 8 pixels as long as possible. + } + SkASSERT(n <= 7); + if (n >= 4) { + fn(Load4(dst), Load4(src)).store4(dst); + dst += 4; src += 4; n -= 4; + } + if (n >= 2) { + fn(Load2(dst), Load2(src)).store2(dst); + dst += 2; src += 2; n -= 2; + } + if (n >= 1) { + fn(Load1(dst), Load1(src)).store1(dst); + } + break; + } + } + + // As above, but with dst4' = fn(dst4, alpha4). + template + static void MapDstAlpha(int n, SkPMColor* dst, const SkAlpha* a, const Fn& fn) { + SkASSERT(dst); + SkASSERT(a); + while (n > 0) { + if (n >= 8) { + Sk4px dst0 = fn(Load4(dst+0), Load4Alphas(a+0)), + dst4 = fn(Load4(dst+4), Load4Alphas(a+4)); + dst0.store4(dst+0); + dst4.store4(dst+4); + dst += 8; a += 8; n -= 8; + continue; // Keep our stride at 8 pixels as long as possible. + } + SkASSERT(n <= 7); + if (n >= 4) { + fn(Load4(dst), Load4Alphas(a)).store4(dst); + dst += 4; a += 4; n -= 4; + } + if (n >= 2) { + fn(Load2(dst), Load2Alphas(a)).store2(dst); + dst += 2; a += 2; n -= 2; + } + if (n >= 1) { + fn(Load1(dst), Sk16b(*a)).store1(dst); + } + break; + } + } + + // As above, but with dst4' = fn(dst4, src4, alpha4). + template + static void MapDstSrcAlpha(int n, SkPMColor* dst, const SkPMColor* src, const SkAlpha* a, + const Fn& fn) { + SkASSERT(dst); + SkASSERT(src); + SkASSERT(a); + while (n > 0) { + if (n >= 8) { + Sk4px dst0 = fn(Load4(dst+0), Load4(src+0), Load4Alphas(a+0)), + dst4 = fn(Load4(dst+4), Load4(src+4), Load4Alphas(a+4)); + dst0.store4(dst+0); + dst4.store4(dst+4); + dst += 8; src += 8; a += 8; n -= 8; + continue; // Keep our stride at 8 pixels as long as possible. + } + SkASSERT(n <= 7); + if (n >= 4) { + fn(Load4(dst), Load4(src), Load4Alphas(a)).store4(dst); + dst += 4; src += 4; a += 4; n -= 4; + } + if (n >= 2) { + fn(Load2(dst), Load2(src), Load2Alphas(a)).store2(dst); + dst += 2; src += 2; a += 2; n -= 2; + } + if (n >= 1) { + fn(Load1(dst), Load1(src), Sk16b(*a)).store1(dst); + } + break; + } + } + +private: + Sk4px() = default; + + typedef Sk16b INHERITED; +}; + +} // namespace + +#ifdef SKNX_NO_SIMD + #include "src/opts/Sk4px_none.h" +#else + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include "src/opts/Sk4px_SSE2.h" + #elif defined(SK_ARM_HAS_NEON) + #include "src/opts/Sk4px_NEON.h" + #else + #include "src/opts/Sk4px_none.h" + #endif +#endif + +#endif//Sk4px_DEFINED diff --git a/gfx/skia/skia/src/core/SkAAClip.cpp b/gfx/skia/skia/src/core/SkAAClip.cpp new file mode 100644 index 0000000000..2f39a24457 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAAClip.cpp @@ -0,0 +1,2162 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkAAClip.h" + +#include "include/core/SkPath.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTo.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkScan.h" +#include "src/utils/SkUTF.h" +#include +#include + +class AutoAAClipValidate { +public: + AutoAAClipValidate(const SkAAClip& clip) : fClip(clip) { + fClip.validate(); + } + ~AutoAAClipValidate() { + fClip.validate(); + } +private: + const SkAAClip& fClip; +}; + +#ifdef SK_DEBUG + #define AUTO_AACLIP_VALIDATE(clip) AutoAAClipValidate acv(clip) +#else + #define AUTO_AACLIP_VALIDATE(clip) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +#define kMaxInt32 0x7FFFFFFF + +#ifdef SK_DEBUG +static inline bool x_in_rect(int x, const SkIRect& rect) { + return (unsigned)(x - rect.fLeft) < (unsigned)rect.width(); +} +#endif + +static inline bool y_in_rect(int y, const SkIRect& rect) { + return (unsigned)(y - rect.fTop) < (unsigned)rect.height(); +} + +/* + * Data runs are packed [count, alpha] + */ + +struct SkAAClip::YOffset { + int32_t fY; + uint32_t fOffset; +}; + +struct SkAAClip::RunHead { + std::atomic fRefCnt; + int32_t fRowCount; + size_t fDataSize; + + YOffset* yoffsets() { + return (YOffset*)((char*)this + sizeof(RunHead)); + } + const YOffset* yoffsets() const { + return (const YOffset*)((const char*)this + sizeof(RunHead)); + } + uint8_t* data() { + return (uint8_t*)(this->yoffsets() + fRowCount); + } + const uint8_t* data() const { + return (const uint8_t*)(this->yoffsets() + fRowCount); + } + + static RunHead* Alloc(int rowCount, size_t dataSize) { + size_t size = sizeof(RunHead) + rowCount * sizeof(YOffset) + dataSize; + RunHead* head = (RunHead*)sk_malloc_throw(size); + head->fRefCnt.store(1); + head->fRowCount = rowCount; + head->fDataSize = dataSize; + return head; + } + + static int ComputeRowSizeForWidth(int width) { + // 2 bytes per segment, where each segment can store up to 255 for count + int segments = 0; + while (width > 0) { + segments += 1; + int n = SkMin32(width, 255); + width -= n; + } + return segments * 2; // each segment is row[0] + row[1] (n + alpha) + } + + static RunHead* AllocRect(const SkIRect& bounds) { + SkASSERT(!bounds.isEmpty()); + int width = bounds.width(); + size_t rowSize = ComputeRowSizeForWidth(width); + RunHead* head = RunHead::Alloc(1, rowSize); + YOffset* yoff = head->yoffsets(); + yoff->fY = bounds.height() - 1; + yoff->fOffset = 0; + uint8_t* row = head->data(); + while (width > 0) { + int n = SkMin32(width, 255); + row[0] = n; + row[1] = 0xFF; + width -= n; + row += 2; + } + return head; + } +}; + +class SkAAClip::Iter { +public: + Iter(const SkAAClip&); + + bool done() const { return fDone; } + int top() const { return fTop; } + int bottom() const { return fBottom; } + const uint8_t* data() const { return fData; } + void next(); + +private: + const YOffset* fCurrYOff; + const YOffset* fStopYOff; + const uint8_t* fData; + + int fTop, fBottom; + bool fDone; +}; + +SkAAClip::Iter::Iter(const SkAAClip& clip) { + if (clip.isEmpty()) { + fDone = true; + fTop = fBottom = clip.fBounds.fBottom; + fData = nullptr; + fCurrYOff = nullptr; + fStopYOff = nullptr; + return; + } + + const RunHead* head = clip.fRunHead; + fCurrYOff = head->yoffsets(); + fStopYOff = fCurrYOff + head->fRowCount; + fData = head->data() + fCurrYOff->fOffset; + + // setup first value + fTop = clip.fBounds.fTop; + fBottom = clip.fBounds.fTop + fCurrYOff->fY + 1; + fDone = false; +} + +void SkAAClip::Iter::next() { + if (!fDone) { + const YOffset* prev = fCurrYOff; + const YOffset* curr = prev + 1; + SkASSERT(curr <= fStopYOff); + + fTop = fBottom; + if (curr >= fStopYOff) { + fDone = true; + fBottom = kMaxInt32; + fData = nullptr; + } else { + fBottom += curr->fY - prev->fY; + fData += curr->fOffset - prev->fOffset; + fCurrYOff = curr; + } + } +} + +#ifdef SK_DEBUG +// assert we're exactly width-wide, and then return the number of bytes used +static size_t compute_row_length(const uint8_t row[], int width) { + const uint8_t* origRow = row; + while (width > 0) { + int n = row[0]; + SkASSERT(n > 0); + SkASSERT(n <= width); + row += 2; + width -= n; + } + SkASSERT(0 == width); + return row - origRow; +} + +void SkAAClip::validate() const { + if (nullptr == fRunHead) { + SkASSERT(fBounds.isEmpty()); + return; + } + SkASSERT(!fBounds.isEmpty()); + + const RunHead* head = fRunHead; + SkASSERT(head->fRefCnt.load() > 0); + SkASSERT(head->fRowCount > 0); + + const YOffset* yoff = head->yoffsets(); + const YOffset* ystop = yoff + head->fRowCount; + const int lastY = fBounds.height() - 1; + + // Y and offset must be monotonic + int prevY = -1; + int32_t prevOffset = -1; + while (yoff < ystop) { + SkASSERT(prevY < yoff->fY); + SkASSERT(yoff->fY <= lastY); + prevY = yoff->fY; + SkASSERT(prevOffset < (int32_t)yoff->fOffset); + prevOffset = yoff->fOffset; + const uint8_t* row = head->data() + yoff->fOffset; + size_t rowLength = compute_row_length(row, fBounds.width()); + SkASSERT(yoff->fOffset + rowLength <= head->fDataSize); + yoff += 1; + } + // check the last entry; + --yoff; + SkASSERT(yoff->fY == lastY); +} + +static void dump_one_row(const uint8_t* SK_RESTRICT row, + int width, int leading_num) { + if (leading_num) { + SkDebugf( "%03d ", leading_num ); + } + while (width > 0) { + int n = row[0]; + int val = row[1]; + char out = '.'; + if (val == 0xff) { + out = '*'; + } else if (val > 0) { + out = '+'; + } + for (int i = 0 ; i < n ; i++) { + SkDebugf( "%c", out ); + } + row += 2; + width -= n; + } + SkDebugf( "\n" ); +} + +void SkAAClip::debug(bool compress_y) const { + Iter iter(*this); + const int width = fBounds.width(); + + int y = fBounds.fTop; + while (!iter.done()) { + if (compress_y) { + dump_one_row(iter.data(), width, iter.bottom() - iter.top() + 1); + } else { + do { + dump_one_row(iter.data(), width, 0); + } while (++y < iter.bottom()); + } + iter.next(); + } +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// Count the number of zeros on the left and right edges of the passed in +// RLE row. If 'row' is all zeros return 'width' in both variables. +static void count_left_right_zeros(const uint8_t* row, int width, + int* leftZ, int* riteZ) { + int zeros = 0; + do { + if (row[1]) { + break; + } + int n = row[0]; + SkASSERT(n > 0); + SkASSERT(n <= width); + zeros += n; + row += 2; + width -= n; + } while (width > 0); + *leftZ = zeros; + + if (0 == width) { + // this line is completely empty return 'width' in both variables + *riteZ = *leftZ; + return; + } + + zeros = 0; + while (width > 0) { + int n = row[0]; + SkASSERT(n > 0); + if (0 == row[1]) { + zeros += n; + } else { + zeros = 0; + } + row += 2; + width -= n; + } + *riteZ = zeros; +} + +// modify row in place, trimming off (zeros) from the left and right sides. +// return the number of bytes that were completely eliminated from the left +static int trim_row_left_right(uint8_t* row, int width, int leftZ, int riteZ) { + int trim = 0; + while (leftZ > 0) { + SkASSERT(0 == row[1]); + int n = row[0]; + SkASSERT(n > 0); + SkASSERT(n <= width); + width -= n; + row += 2; + if (n > leftZ) { + row[-2] = n - leftZ; + break; + } + trim += 2; + leftZ -= n; + SkASSERT(leftZ >= 0); + } + + if (riteZ) { + // walk row to the end, and then we'll back up to trim riteZ + while (width > 0) { + int n = row[0]; + SkASSERT(n <= width); + width -= n; + row += 2; + } + // now skip whole runs of zeros + do { + row -= 2; + SkASSERT(0 == row[1]); + int n = row[0]; + SkASSERT(n > 0); + if (n > riteZ) { + row[0] = n - riteZ; + break; + } + riteZ -= n; + SkASSERT(riteZ >= 0); + } while (riteZ > 0); + } + + return trim; +} + +bool SkAAClip::trimLeftRight() { + if (this->isEmpty()) { + return false; + } + + AUTO_AACLIP_VALIDATE(*this); + + const int width = fBounds.width(); + RunHead* head = fRunHead; + YOffset* yoff = head->yoffsets(); + YOffset* stop = yoff + head->fRowCount; + uint8_t* base = head->data(); + + // After this loop, 'leftZeros' & 'rightZeros' will contain the minimum + // number of zeros on the left and right of the clip. This information + // can be used to shrink the bounding box. + int leftZeros = width; + int riteZeros = width; + while (yoff < stop) { + int L, R; + count_left_right_zeros(base + yoff->fOffset, width, &L, &R); + SkASSERT(L + R < width || (L == width && R == width)); + if (L < leftZeros) { + leftZeros = L; + } + if (R < riteZeros) { + riteZeros = R; + } + if (0 == (leftZeros | riteZeros)) { + // no trimming to do + return true; + } + yoff += 1; + } + + SkASSERT(leftZeros || riteZeros); + if (width == leftZeros) { + SkASSERT(width == riteZeros); + return this->setEmpty(); + } + + this->validate(); + + fBounds.fLeft += leftZeros; + fBounds.fRight -= riteZeros; + SkASSERT(!fBounds.isEmpty()); + + // For now we don't realloc the storage (for time), we just shrink in place + // This means we don't have to do any memmoves either, since we can just + // play tricks with the yoff->fOffset for each row + yoff = head->yoffsets(); + while (yoff < stop) { + uint8_t* row = base + yoff->fOffset; + SkDEBUGCODE((void)compute_row_length(row, width);) + yoff->fOffset += trim_row_left_right(row, width, leftZeros, riteZeros); + SkDEBUGCODE((void)compute_row_length(base + yoff->fOffset, width - leftZeros - riteZeros);) + yoff += 1; + } + return true; +} + +static bool row_is_all_zeros(const uint8_t* row, int width) { + SkASSERT(width > 0); + do { + if (row[1]) { + return false; + } + int n = row[0]; + SkASSERT(n <= width); + width -= n; + row += 2; + } while (width > 0); + SkASSERT(0 == width); + return true; +} + +bool SkAAClip::trimTopBottom() { + if (this->isEmpty()) { + return false; + } + + this->validate(); + + const int width = fBounds.width(); + RunHead* head = fRunHead; + YOffset* yoff = head->yoffsets(); + YOffset* stop = yoff + head->fRowCount; + const uint8_t* base = head->data(); + + // Look to trim away empty rows from the top. + // + int skip = 0; + while (yoff < stop) { + const uint8_t* data = base + yoff->fOffset; + if (!row_is_all_zeros(data, width)) { + break; + } + skip += 1; + yoff += 1; + } + SkASSERT(skip <= head->fRowCount); + if (skip == head->fRowCount) { + return this->setEmpty(); + } + if (skip > 0) { + // adjust fRowCount and fBounds.fTop, and slide all the data up + // as we remove [skip] number of YOffset entries + yoff = head->yoffsets(); + int dy = yoff[skip - 1].fY + 1; + for (int i = skip; i < head->fRowCount; ++i) { + SkASSERT(yoff[i].fY >= dy); + yoff[i].fY -= dy; + } + YOffset* dst = head->yoffsets(); + size_t size = head->fRowCount * sizeof(YOffset) + head->fDataSize; + memmove(dst, dst + skip, size - skip * sizeof(YOffset)); + + fBounds.fTop += dy; + SkASSERT(!fBounds.isEmpty()); + head->fRowCount -= skip; + SkASSERT(head->fRowCount > 0); + + this->validate(); + // need to reset this after the memmove + base = head->data(); + } + + // Look to trim away empty rows from the bottom. + // We know that we have at least one non-zero row, so we can just walk + // backwards without checking for running past the start. + // + stop = yoff = head->yoffsets() + head->fRowCount; + do { + yoff -= 1; + } while (row_is_all_zeros(base + yoff->fOffset, width)); + skip = SkToInt(stop - yoff - 1); + SkASSERT(skip >= 0 && skip < head->fRowCount); + if (skip > 0) { + // removing from the bottom is easier than from the top, as we don't + // have to adjust any of the Y values, we just have to trim the array + memmove(stop - skip, stop, head->fDataSize); + + fBounds.fBottom = fBounds.fTop + yoff->fY + 1; + SkASSERT(!fBounds.isEmpty()); + head->fRowCount -= skip; + SkASSERT(head->fRowCount > 0); + } + this->validate(); + + return true; +} + +// can't validate before we're done, since trimming is part of the process of +// making us valid after the Builder. Since we build from top to bottom, its +// possible our fBounds.fBottom is bigger than our last scanline of data, so +// we trim fBounds.fBottom back up. +// +// TODO: check for duplicates in X and Y to further compress our data +// +bool SkAAClip::trimBounds() { + if (this->isEmpty()) { + return false; + } + + const RunHead* head = fRunHead; + const YOffset* yoff = head->yoffsets(); + + SkASSERT(head->fRowCount > 0); + const YOffset& lastY = yoff[head->fRowCount - 1]; + SkASSERT(lastY.fY + 1 <= fBounds.height()); + fBounds.fBottom = fBounds.fTop + lastY.fY + 1; + SkASSERT(lastY.fY + 1 == fBounds.height()); + SkASSERT(!fBounds.isEmpty()); + + return this->trimTopBottom() && this->trimLeftRight(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkAAClip::freeRuns() { + if (fRunHead) { + SkASSERT(fRunHead->fRefCnt.load() >= 1); + if (1 == fRunHead->fRefCnt--) { + sk_free(fRunHead); + } + } +} + +SkAAClip::SkAAClip() { + fBounds.setEmpty(); + fRunHead = nullptr; +} + +SkAAClip::SkAAClip(const SkAAClip& src) { + SkDEBUGCODE(fBounds.setEmpty();) // need this for validate + fRunHead = nullptr; + *this = src; +} + +SkAAClip::~SkAAClip() { + this->freeRuns(); +} + +SkAAClip& SkAAClip::operator=(const SkAAClip& src) { + AUTO_AACLIP_VALIDATE(*this); + src.validate(); + + if (this != &src) { + this->freeRuns(); + fBounds = src.fBounds; + fRunHead = src.fRunHead; + if (fRunHead) { + fRunHead->fRefCnt++; + } + } + return *this; +} + +bool operator==(const SkAAClip& a, const SkAAClip& b) { + a.validate(); + b.validate(); + + if (&a == &b) { + return true; + } + if (a.fBounds != b.fBounds) { + return false; + } + + const SkAAClip::RunHead* ah = a.fRunHead; + const SkAAClip::RunHead* bh = b.fRunHead; + + // this catches empties and rects being equal + if (ah == bh) { + return true; + } + + // now we insist that both are complex (but different ptrs) + if (!a.fRunHead || !b.fRunHead) { + return false; + } + + return ah->fRowCount == bh->fRowCount && + ah->fDataSize == bh->fDataSize && + !memcmp(ah->data(), bh->data(), ah->fDataSize); +} + +void SkAAClip::swap(SkAAClip& other) { + AUTO_AACLIP_VALIDATE(*this); + other.validate(); + + using std::swap; + swap(fBounds, other.fBounds); + swap(fRunHead, other.fRunHead); +} + +bool SkAAClip::set(const SkAAClip& src) { + *this = src; + return !this->isEmpty(); +} + +bool SkAAClip::setEmpty() { + this->freeRuns(); + fBounds.setEmpty(); + fRunHead = nullptr; + return false; +} + +bool SkAAClip::setRect(const SkIRect& bounds) { + if (bounds.isEmpty()) { + return this->setEmpty(); + } + + AUTO_AACLIP_VALIDATE(*this); + +#if 0 + SkRect r; + r.set(bounds); + SkPath path; + path.addRect(r); + return this->setPath(path); +#else + this->freeRuns(); + fBounds = bounds; + fRunHead = RunHead::AllocRect(bounds); + SkASSERT(!this->isEmpty()); + return true; +#endif +} + +bool SkAAClip::isRect() const { + if (this->isEmpty()) { + return false; + } + + const RunHead* head = fRunHead; + if (head->fRowCount != 1) { + return false; + } + const YOffset* yoff = head->yoffsets(); + if (yoff->fY != fBounds.fBottom - 1) { + return false; + } + + const uint8_t* row = head->data() + yoff->fOffset; + int width = fBounds.width(); + do { + if (row[1] != 0xFF) { + return false; + } + int n = row[0]; + SkASSERT(n <= width); + width -= n; + row += 2; + } while (width > 0); + return true; +} + +bool SkAAClip::setRect(const SkRect& r, bool doAA) { + if (r.isEmpty()) { + return this->setEmpty(); + } + + AUTO_AACLIP_VALIDATE(*this); + + // TODO: special case this + + SkPath path; + path.addRect(r); + return this->setPath(path, nullptr, doAA); +} + +static void append_run(SkTDArray& array, uint8_t value, int count) { + SkASSERT(count >= 0); + while (count > 0) { + int n = count; + if (n > 255) { + n = 255; + } + uint8_t* data = array.append(2); + data[0] = n; + data[1] = value; + count -= n; + } +} + +bool SkAAClip::setRegion(const SkRegion& rgn) { + if (rgn.isEmpty()) { + return this->setEmpty(); + } + if (rgn.isRect()) { + return this->setRect(rgn.getBounds()); + } + +#if 0 + SkAAClip clip; + SkRegion::Iterator iter(rgn); + for (; !iter.done(); iter.next()) { + clip.op(iter.rect(), SkRegion::kUnion_Op); + } + this->swap(clip); + return !this->isEmpty(); +#else + const SkIRect& bounds = rgn.getBounds(); + const int offsetX = bounds.fLeft; + const int offsetY = bounds.fTop; + + SkTDArray yArray; + SkTDArray xArray; + + yArray.setReserve(SkMin32(bounds.height(), 1024)); + xArray.setReserve(SkMin32(bounds.width(), 512) * 128); + + SkRegion::Iterator iter(rgn); + int prevRight = 0; + int prevBot = 0; + YOffset* currY = nullptr; + + for (; !iter.done(); iter.next()) { + const SkIRect& r = iter.rect(); + SkASSERT(bounds.contains(r)); + + int bot = r.fBottom - offsetY; + SkASSERT(bot >= prevBot); + if (bot > prevBot) { + if (currY) { + // flush current row + append_run(xArray, 0, bounds.width() - prevRight); + } + // did we introduce an empty-gap from the prev row? + int top = r.fTop - offsetY; + if (top > prevBot) { + currY = yArray.append(); + currY->fY = top - 1; + currY->fOffset = xArray.count(); + append_run(xArray, 0, bounds.width()); + } + // create a new record for this Y value + currY = yArray.append(); + currY->fY = bot - 1; + currY->fOffset = xArray.count(); + prevRight = 0; + prevBot = bot; + } + + int x = r.fLeft - offsetX; + append_run(xArray, 0, x - prevRight); + + int w = r.fRight - r.fLeft; + append_run(xArray, 0xFF, w); + prevRight = x + w; + SkASSERT(prevRight <= bounds.width()); + } + // flush last row + append_run(xArray, 0, bounds.width() - prevRight); + + // now pack everything into a RunHead + RunHead* head = RunHead::Alloc(yArray.count(), xArray.bytes()); + memcpy(head->yoffsets(), yArray.begin(), yArray.bytes()); + memcpy(head->data(), xArray.begin(), xArray.bytes()); + + this->setEmpty(); + fBounds = bounds; + fRunHead = head; + this->validate(); + return true; +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +const uint8_t* SkAAClip::findRow(int y, int* lastYForRow) const { + SkASSERT(fRunHead); + + if (!y_in_rect(y, fBounds)) { + return nullptr; + } + y -= fBounds.y(); // our yoffs values are relative to the top + + const YOffset* yoff = fRunHead->yoffsets(); + while (yoff->fY < y) { + yoff += 1; + SkASSERT(yoff - fRunHead->yoffsets() < fRunHead->fRowCount); + } + + if (lastYForRow) { + *lastYForRow = fBounds.y() + yoff->fY; + } + return fRunHead->data() + yoff->fOffset; +} + +const uint8_t* SkAAClip::findX(const uint8_t data[], int x, int* initialCount) const { + SkASSERT(x_in_rect(x, fBounds)); + x -= fBounds.x(); + + // first skip up to X + for (;;) { + int n = data[0]; + if (x < n) { + if (initialCount) { + *initialCount = n - x; + } + break; + } + data += 2; + x -= n; + } + return data; +} + +bool SkAAClip::quickContains(int left, int top, int right, int bottom) const { + if (this->isEmpty()) { + return false; + } + if (!fBounds.contains(SkIRect{left, top, right, bottom})) { + return false; + } +#if 0 + if (this->isRect()) { + return true; + } +#endif + + int lastY SK_INIT_TO_AVOID_WARNING; + const uint8_t* row = this->findRow(top, &lastY); + if (lastY < bottom) { + return false; + } + // now just need to check in X + int count; + row = this->findX(row, left, &count); +#if 0 + return count >= (right - left) && 0xFF == row[1]; +#else + int rectWidth = right - left; + while (0xFF == row[1]) { + if (count >= rectWidth) { + return true; + } + rectWidth -= count; + row += 2; + count = row[0]; + } + return false; +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkAAClip::Builder { + SkIRect fBounds; + struct Row { + int fY; + int fWidth; + SkTDArray* fData; + }; + SkTDArray fRows; + Row* fCurrRow; + int fPrevY; + int fWidth; + int fMinY; + +public: + Builder(const SkIRect& bounds) : fBounds(bounds) { + fPrevY = -1; + fWidth = bounds.width(); + fCurrRow = nullptr; + fMinY = bounds.fTop; + } + + ~Builder() { + Row* row = fRows.begin(); + Row* stop = fRows.end(); + while (row < stop) { + delete row->fData; + row += 1; + } + } + + const SkIRect& getBounds() const { return fBounds; } + + void addRun(int x, int y, U8CPU alpha, int count) { + SkASSERT(count > 0); + SkASSERT(fBounds.contains(x, y)); + SkASSERT(fBounds.contains(x + count - 1, y)); + + x -= fBounds.left(); + y -= fBounds.top(); + + Row* row = fCurrRow; + if (y != fPrevY) { + SkASSERT(y > fPrevY); + fPrevY = y; + row = this->flushRow(true); + row->fY = y; + row->fWidth = 0; + SkASSERT(row->fData); + SkASSERT(0 == row->fData->count()); + fCurrRow = row; + } + + SkASSERT(row->fWidth <= x); + SkASSERT(row->fWidth < fBounds.width()); + + SkTDArray& data = *row->fData; + + int gap = x - row->fWidth; + if (gap) { + AppendRun(data, 0, gap); + row->fWidth += gap; + SkASSERT(row->fWidth < fBounds.width()); + } + + AppendRun(data, alpha, count); + row->fWidth += count; + SkASSERT(row->fWidth <= fBounds.width()); + } + + void addColumn(int x, int y, U8CPU alpha, int height) { + SkASSERT(fBounds.contains(x, y + height - 1)); + + this->addRun(x, y, alpha, 1); + this->flushRowH(fCurrRow); + y -= fBounds.fTop; + SkASSERT(y == fCurrRow->fY); + fCurrRow->fY = y + height - 1; + } + + void addRectRun(int x, int y, int width, int height) { + SkASSERT(fBounds.contains(x + width - 1, y + height - 1)); + this->addRun(x, y, 0xFF, width); + + // we assum the rect must be all we'll see for these scanlines + // so we ensure our row goes all the way to our right + this->flushRowH(fCurrRow); + + y -= fBounds.fTop; + SkASSERT(y == fCurrRow->fY); + fCurrRow->fY = y + height - 1; + } + + void addAntiRectRun(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) { + // According to SkBlitter.cpp, no matter whether leftAlpha is 0 or positive, + // we should always consider [x, x+1] as the left-most column and [x+1, x+1+width] + // as the rect with full alpha. + SkASSERT(fBounds.contains(x + width + (rightAlpha > 0 ? 1 : 0), + y + height - 1)); + SkASSERT(width >= 0); + + // Conceptually we're always adding 3 runs, but we should + // merge or omit them if possible. + if (leftAlpha == 0xFF) { + width++; + } else if (leftAlpha > 0) { + this->addRun(x++, y, leftAlpha, 1); + } else { + // leftAlpha is 0, ignore the left column + x++; + } + if (rightAlpha == 0xFF) { + width++; + } + if (width > 0) { + this->addRun(x, y, 0xFF, width); + } + if (rightAlpha > 0 && rightAlpha < 255) { + this->addRun(x + width, y, rightAlpha, 1); + } + + // if we never called addRun, we might not have a fCurrRow yet + if (fCurrRow) { + // we assume the rect must be all we'll see for these scanlines + // so we ensure our row goes all the way to our right + this->flushRowH(fCurrRow); + + y -= fBounds.fTop; + SkASSERT(y == fCurrRow->fY); + fCurrRow->fY = y + height - 1; + } + } + + bool finish(SkAAClip* target) { + this->flushRow(false); + + const Row* row = fRows.begin(); + const Row* stop = fRows.end(); + + size_t dataSize = 0; + while (row < stop) { + dataSize += row->fData->count(); + row += 1; + } + + if (0 == dataSize) { + return target->setEmpty(); + } + + SkASSERT(fMinY >= fBounds.fTop); + SkASSERT(fMinY < fBounds.fBottom); + int adjustY = fMinY - fBounds.fTop; + fBounds.fTop = fMinY; + + RunHead* head = RunHead::Alloc(fRows.count(), dataSize); + YOffset* yoffset = head->yoffsets(); + uint8_t* data = head->data(); + uint8_t* baseData = data; + + row = fRows.begin(); + SkDEBUGCODE(int prevY = row->fY - 1;) + while (row < stop) { + SkASSERT(prevY < row->fY); // must be monotonic + SkDEBUGCODE(prevY = row->fY); + + yoffset->fY = row->fY - adjustY; + yoffset->fOffset = SkToU32(data - baseData); + yoffset += 1; + + size_t n = row->fData->count(); + memcpy(data, row->fData->begin(), n); +#ifdef SK_DEBUG + size_t bytesNeeded = compute_row_length(data, fBounds.width()); + SkASSERT(bytesNeeded == n); +#endif + data += n; + + row += 1; + } + + target->freeRuns(); + target->fBounds = fBounds; + target->fRunHead = head; + return target->trimBounds(); + } + + void dump() { + this->validate(); + int y; + for (y = 0; y < fRows.count(); ++y) { + const Row& row = fRows[y]; + SkDebugf("Y:%3d W:%3d", row.fY, row.fWidth); + const SkTDArray& data = *row.fData; + int count = data.count(); + SkASSERT(!(count & 1)); + const uint8_t* ptr = data.begin(); + for (int x = 0; x < count; x += 2) { + SkDebugf(" [%3d:%02X]", ptr[0], ptr[1]); + ptr += 2; + } + SkDebugf("\n"); + } + } + + void validate() { +#ifdef SK_DEBUG + int prevY = -1; + for (int i = 0; i < fRows.count(); ++i) { + const Row& row = fRows[i]; + SkASSERT(prevY < row.fY); + SkASSERT(fWidth == row.fWidth); + int count = row.fData->count(); + const uint8_t* ptr = row.fData->begin(); + SkASSERT(!(count & 1)); + int w = 0; + for (int x = 0; x < count; x += 2) { + int n = ptr[0]; + SkASSERT(n > 0); + w += n; + SkASSERT(w <= fWidth); + ptr += 2; + } + SkASSERT(w == fWidth); + prevY = row.fY; + } +#endif + } + + // only called by BuilderBlitter + void setMinY(int y) { + fMinY = y; + } + +private: + void flushRowH(Row* row) { + // flush current row if needed + if (row->fWidth < fWidth) { + AppendRun(*row->fData, 0, fWidth - row->fWidth); + row->fWidth = fWidth; + } + } + + Row* flushRow(bool readyForAnother) { + Row* next = nullptr; + int count = fRows.count(); + if (count > 0) { + this->flushRowH(&fRows[count - 1]); + } + if (count > 1) { + // are our last two runs the same? + Row* prev = &fRows[count - 2]; + Row* curr = &fRows[count - 1]; + SkASSERT(prev->fWidth == fWidth); + SkASSERT(curr->fWidth == fWidth); + if (*prev->fData == *curr->fData) { + prev->fY = curr->fY; + if (readyForAnother) { + curr->fData->rewind(); + next = curr; + } else { + delete curr->fData; + fRows.removeShuffle(count - 1); + } + } else { + if (readyForAnother) { + next = fRows.append(); + next->fData = new SkTDArray; + } + } + } else { + if (readyForAnother) { + next = fRows.append(); + next->fData = new SkTDArray; + } + } + return next; + } + + static void AppendRun(SkTDArray& data, U8CPU alpha, int count) { + do { + int n = count; + if (n > 255) { + n = 255; + } + uint8_t* ptr = data.append(2); + ptr[0] = n; + ptr[1] = alpha; + count -= n; + } while (count > 0); + } +}; + +class SkAAClip::BuilderBlitter : public SkBlitter { + int fLastY; + + /* + If we see a gap of 1 or more empty scanlines while building in Y-order, + we inject an explicit empty scanline (alpha==0) + + See AAClipTest.cpp : test_path_with_hole() + */ + void checkForYGap(int y) { + SkASSERT(y >= fLastY); + if (fLastY > -SK_MaxS32) { + int gap = y - fLastY; + if (gap > 1) { + fBuilder->addRun(fLeft, y - 1, 0, fRight - fLeft); + } + } + fLastY = y; + } + +public: + + BuilderBlitter(Builder* builder) { + fBuilder = builder; + fLeft = builder->getBounds().fLeft; + fRight = builder->getBounds().fRight; + fMinY = SK_MaxS32; + fLastY = -SK_MaxS32; // sentinel + } + + void finish() { + if (fMinY < SK_MaxS32) { + fBuilder->setMinY(fMinY); + } + } + + /** + Must evaluate clips in scan-line order, so don't want to allow blitV(), + but an AAClip can be clipped down to a single pixel wide, so we + must support it (given AntiRect semantics: minimum width is 2). + Instead we'll rely on the runtime asserts to guarantee Y monotonicity; + any failure cases that misses may have minor artifacts. + */ + void blitV(int x, int y, int height, SkAlpha alpha) override { + if (height == 1) { + // We're still in scan-line order if height is 1 + // This is useful for Analytic AA + const SkAlpha alphas[2] = {alpha, 0}; + const int16_t runs[2] = {1, 0}; + this->blitAntiH(x, y, alphas, runs); + } else { + this->recordMinY(y); + fBuilder->addColumn(x, y, alpha, height); + fLastY = y + height - 1; + } + } + + void blitRect(int x, int y, int width, int height) override { + this->recordMinY(y); + this->checkForYGap(y); + fBuilder->addRectRun(x, y, width, height); + fLastY = y + height - 1; + } + + virtual void blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) override { + this->recordMinY(y); + this->checkForYGap(y); + fBuilder->addAntiRectRun(x, y, width, height, leftAlpha, rightAlpha); + fLastY = y + height - 1; + } + + void blitMask(const SkMask&, const SkIRect& clip) override + { unexpected(); } + + const SkPixmap* justAnOpaqueColor(uint32_t*) override { + return nullptr; + } + + void blitH(int x, int y, int width) override { + this->recordMinY(y); + this->checkForYGap(y); + fBuilder->addRun(x, y, 0xFF, width); + } + + virtual void blitAntiH(int x, int y, const SkAlpha alpha[], + const int16_t runs[]) override { + this->recordMinY(y); + this->checkForYGap(y); + for (;;) { + int count = *runs; + if (count <= 0) { + return; + } + + // The supersampler's buffer can be the width of the device, so + // we may have to trim the run to our bounds. Previously, we assert that + // the extra spans are always alpha==0. + // However, the analytic AA is too sensitive to precision errors + // so it may have extra spans with very tiny alpha because after several + // arithmatic operations, the edge may bleed the path boundary a little bit. + // Therefore, instead of always asserting alpha==0, we assert alpha < 0x10. + int localX = x; + int localCount = count; + if (x < fLeft) { + SkASSERT(0x10 > *alpha); + int gap = fLeft - x; + SkASSERT(gap <= count); + localX += gap; + localCount -= gap; + } + int right = x + count; + if (right > fRight) { + SkASSERT(0x10 > *alpha); + localCount -= right - fRight; + SkASSERT(localCount >= 0); + } + + if (localCount) { + fBuilder->addRun(localX, y, *alpha, localCount); + } + // Next run + runs += count; + alpha += count; + x += count; + } + } + +private: + Builder* fBuilder; + int fLeft; // cache of builder's bounds' left edge + int fRight; + int fMinY; + + /* + * We track this, in case the scan converter skipped some number of + * scanlines at the (relative to the bounds it was given). This allows + * the builder, during its finish, to trip its bounds down to the "real" + * top. + */ + void recordMinY(int y) { + if (y < fMinY) { + fMinY = y; + } + } + + void unexpected() { + SK_ABORT("---- did not expect to get called here"); + } +}; + +bool SkAAClip::setPath(const SkPath& path, const SkRegion* clip, bool doAA) { + AUTO_AACLIP_VALIDATE(*this); + + if (clip && clip->isEmpty()) { + return this->setEmpty(); + } + + SkIRect ibounds; + path.getBounds().roundOut(&ibounds); + + SkRegion tmpClip; + if (nullptr == clip) { + tmpClip.setRect(ibounds); + clip = &tmpClip; + } + + // Since we assert that the BuilderBlitter will never blit outside the intersection + // of clip and ibounds, we create this snugClip to be that intersection and send it + // to the scan-converter. + SkRegion snugClip(*clip); + + if (path.isInverseFillType()) { + ibounds = clip->getBounds(); + } else { + if (ibounds.isEmpty() || !ibounds.intersect(clip->getBounds())) { + return this->setEmpty(); + } + snugClip.op(ibounds, SkRegion::kIntersect_Op); + } + + Builder builder(ibounds); + BuilderBlitter blitter(&builder); + + if (doAA) { + SkScan::AntiFillPath(path, snugClip, &blitter, true); + } else { + SkScan::FillPath(path, snugClip, &blitter); + } + + blitter.finish(); + return builder.finish(this); +} + +/////////////////////////////////////////////////////////////////////////////// + +typedef void (*RowProc)(SkAAClip::Builder&, int bottom, + const uint8_t* rowA, const SkIRect& rectA, + const uint8_t* rowB, const SkIRect& rectB); + +typedef U8CPU (*AlphaProc)(U8CPU alphaA, U8CPU alphaB); + +static U8CPU sectAlphaProc(U8CPU alphaA, U8CPU alphaB) { + // Multiply + return SkMulDiv255Round(alphaA, alphaB); +} + +static U8CPU unionAlphaProc(U8CPU alphaA, U8CPU alphaB) { + // SrcOver + return alphaA + alphaB - SkMulDiv255Round(alphaA, alphaB); +} + +static U8CPU diffAlphaProc(U8CPU alphaA, U8CPU alphaB) { + // SrcOut + return SkMulDiv255Round(alphaA, 0xFF - alphaB); +} + +static U8CPU xorAlphaProc(U8CPU alphaA, U8CPU alphaB) { + // XOR + return alphaA + alphaB - 2 * SkMulDiv255Round(alphaA, alphaB); +} + +static AlphaProc find_alpha_proc(SkRegion::Op op) { + switch (op) { + case SkRegion::kIntersect_Op: + return sectAlphaProc; + case SkRegion::kDifference_Op: + return diffAlphaProc; + case SkRegion::kUnion_Op: + return unionAlphaProc; + case SkRegion::kXOR_Op: + return xorAlphaProc; + default: + SkDEBUGFAIL("unexpected region op"); + return sectAlphaProc; + } +} + +class RowIter { +public: + RowIter(const uint8_t* row, const SkIRect& bounds) { + fRow = row; + fLeft = bounds.fLeft; + fBoundsRight = bounds.fRight; + if (row) { + fRight = bounds.fLeft + row[0]; + SkASSERT(fRight <= fBoundsRight); + fAlpha = row[1]; + fDone = false; + } else { + fDone = true; + fRight = kMaxInt32; + fAlpha = 0; + } + } + + bool done() const { return fDone; } + int left() const { return fLeft; } + int right() const { return fRight; } + U8CPU alpha() const { return fAlpha; } + void next() { + if (!fDone) { + fLeft = fRight; + if (fRight == fBoundsRight) { + fDone = true; + fRight = kMaxInt32; + fAlpha = 0; + } else { + fRow += 2; + fRight += fRow[0]; + fAlpha = fRow[1]; + SkASSERT(fRight <= fBoundsRight); + } + } + } + +private: + const uint8_t* fRow; + int fLeft; + int fRight; + int fBoundsRight; + bool fDone; + uint8_t fAlpha; +}; + +static void adjust_row(RowIter& iter, int& leftA, int& riteA, int rite) { + if (rite == riteA) { + iter.next(); + leftA = iter.left(); + riteA = iter.right(); + } +} + +#if 0 // UNUSED +static bool intersect(int& min, int& max, int boundsMin, int boundsMax) { + SkASSERT(min < max); + SkASSERT(boundsMin < boundsMax); + if (min >= boundsMax || max <= boundsMin) { + return false; + } + if (min < boundsMin) { + min = boundsMin; + } + if (max > boundsMax) { + max = boundsMax; + } + return true; +} +#endif + +static void operatorX(SkAAClip::Builder& builder, int lastY, + RowIter& iterA, RowIter& iterB, + AlphaProc proc, const SkIRect& bounds) { + int leftA = iterA.left(); + int riteA = iterA.right(); + int leftB = iterB.left(); + int riteB = iterB.right(); + + int prevRite = bounds.fLeft; + + do { + U8CPU alphaA = 0; + U8CPU alphaB = 0; + int left, rite; + + if (leftA < leftB) { + left = leftA; + alphaA = iterA.alpha(); + if (riteA <= leftB) { + rite = riteA; + } else { + rite = leftA = leftB; + } + } else if (leftB < leftA) { + left = leftB; + alphaB = iterB.alpha(); + if (riteB <= leftA) { + rite = riteB; + } else { + rite = leftB = leftA; + } + } else { + left = leftA; // or leftB, since leftA == leftB + rite = leftA = leftB = SkMin32(riteA, riteB); + alphaA = iterA.alpha(); + alphaB = iterB.alpha(); + } + + if (left >= bounds.fRight) { + break; + } + if (rite > bounds.fRight) { + rite = bounds.fRight; + } + + if (left >= bounds.fLeft) { + SkASSERT(rite > left); + builder.addRun(left, lastY, proc(alphaA, alphaB), rite - left); + prevRite = rite; + } + + adjust_row(iterA, leftA, riteA, rite); + adjust_row(iterB, leftB, riteB, rite); + } while (!iterA.done() || !iterB.done()); + + if (prevRite < bounds.fRight) { + builder.addRun(prevRite, lastY, 0, bounds.fRight - prevRite); + } +} + +static void adjust_iter(SkAAClip::Iter& iter, int& topA, int& botA, int bot) { + if (bot == botA) { + iter.next(); + topA = botA; + SkASSERT(botA == iter.top()); + botA = iter.bottom(); + } +} + +static void operateY(SkAAClip::Builder& builder, const SkAAClip& A, + const SkAAClip& B, SkRegion::Op op) { + AlphaProc proc = find_alpha_proc(op); + const SkIRect& bounds = builder.getBounds(); + + SkAAClip::Iter iterA(A); + SkAAClip::Iter iterB(B); + + SkASSERT(!iterA.done()); + int topA = iterA.top(); + int botA = iterA.bottom(); + SkASSERT(!iterB.done()); + int topB = iterB.top(); + int botB = iterB.bottom(); + + do { + const uint8_t* rowA = nullptr; + const uint8_t* rowB = nullptr; + int top, bot; + + if (topA < topB) { + top = topA; + rowA = iterA.data(); + if (botA <= topB) { + bot = botA; + } else { + bot = topA = topB; + } + + } else if (topB < topA) { + top = topB; + rowB = iterB.data(); + if (botB <= topA) { + bot = botB; + } else { + bot = topB = topA; + } + } else { + top = topA; // or topB, since topA == topB + bot = topA = topB = SkMin32(botA, botB); + rowA = iterA.data(); + rowB = iterB.data(); + } + + if (top >= bounds.fBottom) { + break; + } + + if (bot > bounds.fBottom) { + bot = bounds.fBottom; + } + SkASSERT(top < bot); + + if (!rowA && !rowB) { + builder.addRun(bounds.fLeft, bot - 1, 0, bounds.width()); + } else if (top >= bounds.fTop) { + SkASSERT(bot <= bounds.fBottom); + RowIter rowIterA(rowA, rowA ? A.getBounds() : bounds); + RowIter rowIterB(rowB, rowB ? B.getBounds() : bounds); + operatorX(builder, bot - 1, rowIterA, rowIterB, proc, bounds); + } + + adjust_iter(iterA, topA, botA, bot); + adjust_iter(iterB, topB, botB, bot); + } while (!iterA.done() || !iterB.done()); +} + +bool SkAAClip::op(const SkAAClip& clipAOrig, const SkAAClip& clipBOrig, + SkRegion::Op op) { + AUTO_AACLIP_VALIDATE(*this); + + if (SkRegion::kReplace_Op == op) { + return this->set(clipBOrig); + } + + const SkAAClip* clipA = &clipAOrig; + const SkAAClip* clipB = &clipBOrig; + + if (SkRegion::kReverseDifference_Op == op) { + using std::swap; + swap(clipA, clipB); + op = SkRegion::kDifference_Op; + } + + bool a_empty = clipA->isEmpty(); + bool b_empty = clipB->isEmpty(); + + SkIRect bounds; + switch (op) { + case SkRegion::kDifference_Op: + if (a_empty) { + return this->setEmpty(); + } + if (b_empty || !SkIRect::Intersects(clipA->fBounds, clipB->fBounds)) { + return this->set(*clipA); + } + bounds = clipA->fBounds; + break; + + case SkRegion::kIntersect_Op: + if ((a_empty | b_empty) || !bounds.intersect(clipA->fBounds, + clipB->fBounds)) { + return this->setEmpty(); + } + break; + + case SkRegion::kUnion_Op: + case SkRegion::kXOR_Op: + if (a_empty) { + return this->set(*clipB); + } + if (b_empty) { + return this->set(*clipA); + } + bounds = clipA->fBounds; + bounds.join(clipB->fBounds); + break; + + default: + SkDEBUGFAIL("unknown region op"); + return !this->isEmpty(); + } + + SkASSERT(SkIRect::Intersects(bounds, clipB->fBounds)); + SkASSERT(SkIRect::Intersects(bounds, clipB->fBounds)); + + Builder builder(bounds); + operateY(builder, *clipA, *clipB, op); + + return builder.finish(this); +} + +/* + * It can be expensive to build a local aaclip before applying the op, so + * we first see if we can restrict the bounds of new rect to our current + * bounds, or note that the new rect subsumes our current clip. + */ + +bool SkAAClip::op(const SkIRect& rOrig, SkRegion::Op op) { + SkIRect rStorage; + const SkIRect* r = &rOrig; + + switch (op) { + case SkRegion::kIntersect_Op: + if (!rStorage.intersect(rOrig, fBounds)) { + // no overlap, so we're empty + return this->setEmpty(); + } + if (rStorage == fBounds) { + // we were wholly inside the rect, no change + return !this->isEmpty(); + } + if (this->quickContains(rStorage)) { + // the intersection is wholly inside us, we're a rect + return this->setRect(rStorage); + } + r = &rStorage; // use the intersected bounds + break; + case SkRegion::kDifference_Op: + break; + case SkRegion::kUnion_Op: + if (rOrig.contains(fBounds)) { + return this->setRect(rOrig); + } + break; + default: + break; + } + + SkAAClip clip; + clip.setRect(*r); + return this->op(*this, clip, op); +} + +bool SkAAClip::op(const SkRect& rOrig, SkRegion::Op op, bool doAA) { + SkRect rStorage, boundsStorage; + const SkRect* r = &rOrig; + + boundsStorage.set(fBounds); + switch (op) { + case SkRegion::kIntersect_Op: + case SkRegion::kDifference_Op: + if (!rStorage.intersect(rOrig, boundsStorage)) { + if (SkRegion::kIntersect_Op == op) { + return this->setEmpty(); + } else { // kDifference + return !this->isEmpty(); + } + } + r = &rStorage; // use the intersected bounds + break; + case SkRegion::kUnion_Op: + if (rOrig.contains(boundsStorage)) { + return this->setRect(rOrig); + } + break; + default: + break; + } + + SkAAClip clip; + clip.setRect(*r, doAA); + return this->op(*this, clip, op); +} + +bool SkAAClip::op(const SkAAClip& clip, SkRegion::Op op) { + return this->op(*this, clip, op); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkAAClip::translate(int dx, int dy, SkAAClip* dst) const { + if (nullptr == dst) { + return !this->isEmpty(); + } + + if (this->isEmpty()) { + return dst->setEmpty(); + } + + if (this != dst) { + fRunHead->fRefCnt++; + dst->freeRuns(); + dst->fRunHead = fRunHead; + dst->fBounds = fBounds; + } + dst->fBounds.offset(dx, dy); + return true; +} + +static void expand_row_to_mask(uint8_t* SK_RESTRICT mask, + const uint8_t* SK_RESTRICT row, + int width) { + while (width > 0) { + int n = row[0]; + SkASSERT(width >= n); + memset(mask, row[1], n); + mask += n; + row += 2; + width -= n; + } + SkASSERT(0 == width); +} + +void SkAAClip::copyToMask(SkMask* mask) const { + mask->fFormat = SkMask::kA8_Format; + if (this->isEmpty()) { + mask->fBounds.setEmpty(); + mask->fImage = nullptr; + mask->fRowBytes = 0; + return; + } + + mask->fBounds = fBounds; + mask->fRowBytes = fBounds.width(); + size_t size = mask->computeImageSize(); + mask->fImage = SkMask::AllocImage(size); + + Iter iter(*this); + uint8_t* dst = mask->fImage; + const int width = fBounds.width(); + + int y = fBounds.fTop; + while (!iter.done()) { + do { + expand_row_to_mask(dst, iter.data(), width); + dst += mask->fRowBytes; + } while (++y < iter.bottom()); + iter.next(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static void expandToRuns(const uint8_t* SK_RESTRICT data, int initialCount, int width, + int16_t* SK_RESTRICT runs, SkAlpha* SK_RESTRICT aa) { + // we don't read our initial n from data, since the caller may have had to + // clip it, hence the initialCount parameter. + int n = initialCount; + for (;;) { + if (n > width) { + n = width; + } + SkASSERT(n > 0); + runs[0] = n; + runs += n; + + aa[0] = data[1]; + aa += n; + + data += 2; + width -= n; + if (0 == width) { + break; + } + // load the next count + n = data[0]; + } + runs[0] = 0; // sentinel +} + +SkAAClipBlitter::~SkAAClipBlitter() { + sk_free(fScanlineScratch); +} + +void SkAAClipBlitter::ensureRunsAndAA() { + if (nullptr == fScanlineScratch) { + // add 1 so we can store the terminating run count of 0 + int count = fAAClipBounds.width() + 1; + // we use this either for fRuns + fAA, or a scaline of a mask + // which may be as deep as 32bits + fScanlineScratch = sk_malloc_throw(count * sizeof(SkPMColor)); + fRuns = (int16_t*)fScanlineScratch; + fAA = (SkAlpha*)(fRuns + count); + } +} + +void SkAAClipBlitter::blitH(int x, int y, int width) { + SkASSERT(width > 0); + SkASSERT(fAAClipBounds.contains(x, y)); + SkASSERT(fAAClipBounds.contains(x + width - 1, y)); + + const uint8_t* row = fAAClip->findRow(y); + int initialCount; + row = fAAClip->findX(row, x, &initialCount); + + if (initialCount >= width) { + SkAlpha alpha = row[1]; + if (0 == alpha) { + return; + } + if (0xFF == alpha) { + fBlitter->blitH(x, y, width); + return; + } + } + + this->ensureRunsAndAA(); + expandToRuns(row, initialCount, width, fRuns, fAA); + + fBlitter->blitAntiH(x, y, fAA, fRuns); +} + +static void merge(const uint8_t* SK_RESTRICT row, int rowN, + const SkAlpha* SK_RESTRICT srcAA, + const int16_t* SK_RESTRICT srcRuns, + SkAlpha* SK_RESTRICT dstAA, + int16_t* SK_RESTRICT dstRuns, + int width) { + SkDEBUGCODE(int accumulated = 0;) + int srcN = srcRuns[0]; + // do we need this check? + if (0 == srcN) { + return; + } + + for (;;) { + SkASSERT(rowN > 0); + SkASSERT(srcN > 0); + + unsigned newAlpha = SkMulDiv255Round(srcAA[0], row[1]); + int minN = SkMin32(srcN, rowN); + dstRuns[0] = minN; + dstRuns += minN; + dstAA[0] = newAlpha; + dstAA += minN; + + if (0 == (srcN -= minN)) { + srcN = srcRuns[0]; // refresh + srcRuns += srcN; + srcAA += srcN; + srcN = srcRuns[0]; // reload + if (0 == srcN) { + break; + } + } + if (0 == (rowN -= minN)) { + row += 2; + rowN = row[0]; // reload + } + + SkDEBUGCODE(accumulated += minN;) + SkASSERT(accumulated <= width); + } + dstRuns[0] = 0; +} + +void SkAAClipBlitter::blitAntiH(int x, int y, const SkAlpha aa[], + const int16_t runs[]) { + + const uint8_t* row = fAAClip->findRow(y); + int initialCount; + row = fAAClip->findX(row, x, &initialCount); + + this->ensureRunsAndAA(); + + merge(row, initialCount, aa, runs, fAA, fRuns, fAAClipBounds.width()); + fBlitter->blitAntiH(x, y, fAA, fRuns); +} + +void SkAAClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + if (fAAClip->quickContains(x, y, x + 1, y + height)) { + fBlitter->blitV(x, y, height, alpha); + return; + } + + for (;;) { + int lastY SK_INIT_TO_AVOID_WARNING; + const uint8_t* row = fAAClip->findRow(y, &lastY); + int dy = lastY - y + 1; + if (dy > height) { + dy = height; + } + height -= dy; + + row = fAAClip->findX(row, x); + SkAlpha newAlpha = SkMulDiv255Round(alpha, row[1]); + if (newAlpha) { + fBlitter->blitV(x, y, dy, newAlpha); + } + SkASSERT(height >= 0); + if (height <= 0) { + break; + } + y = lastY + 1; + } +} + +void SkAAClipBlitter::blitRect(int x, int y, int width, int height) { + if (fAAClip->quickContains(x, y, x + width, y + height)) { + fBlitter->blitRect(x, y, width, height); + return; + } + + while (--height >= 0) { + this->blitH(x, y, width); + y += 1; + } +} + +typedef void (*MergeAAProc)(const void* src, int width, const uint8_t* row, + int initialRowCount, void* dst); + +static void small_memcpy(void* dst, const void* src, size_t n) { + memcpy(dst, src, n); +} + +static void small_bzero(void* dst, size_t n) { + sk_bzero(dst, n); +} + +static inline uint8_t mergeOne(uint8_t value, unsigned alpha) { + return SkMulDiv255Round(value, alpha); +} + +static inline uint16_t mergeOne(uint16_t value, unsigned alpha) { + unsigned r = SkGetPackedR16(value); + unsigned g = SkGetPackedG16(value); + unsigned b = SkGetPackedB16(value); + return SkPackRGB16(SkMulDiv255Round(r, alpha), + SkMulDiv255Round(g, alpha), + SkMulDiv255Round(b, alpha)); +} + +template +void mergeT(const void* inSrc, int srcN, const uint8_t* SK_RESTRICT row, int rowN, void* inDst) { + const T* SK_RESTRICT src = static_cast(inSrc); + T* SK_RESTRICT dst = static_cast(inDst); + for (;;) { + SkASSERT(rowN > 0); + SkASSERT(srcN > 0); + + int n = SkMin32(rowN, srcN); + unsigned rowA = row[1]; + if (0xFF == rowA) { + small_memcpy(dst, src, n * sizeof(T)); + } else if (0 == rowA) { + small_bzero(dst, n * sizeof(T)); + } else { + for (int i = 0; i < n; ++i) { + dst[i] = mergeOne(src[i], rowA); + } + } + + if (0 == (srcN -= n)) { + break; + } + + src += n; + dst += n; + + SkASSERT(rowN == n); + row += 2; + rowN = row[0]; + } +} + +static MergeAAProc find_merge_aa_proc(SkMask::Format format) { + switch (format) { + case SkMask::kBW_Format: + SkDEBUGFAIL("unsupported"); + return nullptr; + case SkMask::kA8_Format: + case SkMask::k3D_Format: + return mergeT ; + case SkMask::kLCD16_Format: + return mergeT; + default: + SkDEBUGFAIL("unsupported"); + return nullptr; + } +} + +static U8CPU bit2byte(int bitInAByte) { + SkASSERT(bitInAByte <= 0xFF); + // negation turns any non-zero into 0xFFFFFF??, so we just shift down + // some value >= 8 to get a full FF value + return -bitInAByte >> 8; +} + +static void upscaleBW2A8(SkMask* dstMask, const SkMask& srcMask) { + SkASSERT(SkMask::kBW_Format == srcMask.fFormat); + SkASSERT(SkMask::kA8_Format == dstMask->fFormat); + + const int width = srcMask.fBounds.width(); + const int height = srcMask.fBounds.height(); + + const uint8_t* SK_RESTRICT src = (const uint8_t*)srcMask.fImage; + const size_t srcRB = srcMask.fRowBytes; + uint8_t* SK_RESTRICT dst = (uint8_t*)dstMask->fImage; + const size_t dstRB = dstMask->fRowBytes; + + const int wholeBytes = width >> 3; + const int leftOverBits = width & 7; + + for (int y = 0; y < height; ++y) { + uint8_t* SK_RESTRICT d = dst; + for (int i = 0; i < wholeBytes; ++i) { + int srcByte = src[i]; + d[0] = bit2byte(srcByte & (1 << 7)); + d[1] = bit2byte(srcByte & (1 << 6)); + d[2] = bit2byte(srcByte & (1 << 5)); + d[3] = bit2byte(srcByte & (1 << 4)); + d[4] = bit2byte(srcByte & (1 << 3)); + d[5] = bit2byte(srcByte & (1 << 2)); + d[6] = bit2byte(srcByte & (1 << 1)); + d[7] = bit2byte(srcByte & (1 << 0)); + d += 8; + } + if (leftOverBits) { + int srcByte = src[wholeBytes]; + for (int x = 0; x < leftOverBits; ++x) { + *d++ = bit2byte(srcByte & 0x80); + srcByte <<= 1; + } + } + src += srcRB; + dst += dstRB; + } +} + +void SkAAClipBlitter::blitMask(const SkMask& origMask, const SkIRect& clip) { + SkASSERT(fAAClip->getBounds().contains(clip)); + + if (fAAClip->quickContains(clip)) { + fBlitter->blitMask(origMask, clip); + return; + } + + const SkMask* mask = &origMask; + + // if we're BW, we need to upscale to A8 (ugh) + SkMask grayMask; + if (SkMask::kBW_Format == origMask.fFormat) { + grayMask.fFormat = SkMask::kA8_Format; + grayMask.fBounds = origMask.fBounds; + grayMask.fRowBytes = origMask.fBounds.width(); + size_t size = grayMask.computeImageSize(); + grayMask.fImage = (uint8_t*)fGrayMaskScratch.reset(size, + SkAutoMalloc::kReuse_OnShrink); + + upscaleBW2A8(&grayMask, origMask); + mask = &grayMask; + } + + this->ensureRunsAndAA(); + + // HACK -- we are devolving 3D into A8, need to copy the rest of the 3D + // data into a temp block to support it better (ugh) + + const void* src = mask->getAddr(clip.fLeft, clip.fTop); + const size_t srcRB = mask->fRowBytes; + const int width = clip.width(); + MergeAAProc mergeProc = find_merge_aa_proc(mask->fFormat); + + SkMask rowMask; + rowMask.fFormat = SkMask::k3D_Format == mask->fFormat ? SkMask::kA8_Format : mask->fFormat; + rowMask.fBounds.fLeft = clip.fLeft; + rowMask.fBounds.fRight = clip.fRight; + rowMask.fRowBytes = mask->fRowBytes; // doesn't matter, since our height==1 + rowMask.fImage = (uint8_t*)fScanlineScratch; + + int y = clip.fTop; + const int stopY = y + clip.height(); + + do { + int localStopY SK_INIT_TO_AVOID_WARNING; + const uint8_t* row = fAAClip->findRow(y, &localStopY); + // findRow returns last Y, not stop, so we add 1 + localStopY = SkMin32(localStopY + 1, stopY); + + int initialCount; + row = fAAClip->findX(row, clip.fLeft, &initialCount); + do { + mergeProc(src, width, row, initialCount, rowMask.fImage); + rowMask.fBounds.fTop = y; + rowMask.fBounds.fBottom = y + 1; + fBlitter->blitMask(rowMask, rowMask.fBounds); + src = (const void*)((const char*)src + srcRB); + } while (++y < localStopY); + } while (y < stopY); +} + +const SkPixmap* SkAAClipBlitter::justAnOpaqueColor(uint32_t* value) { + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkAAClip.h b/gfx/skia/skia/src/core/SkAAClip.h new file mode 100644 index 0000000000..3c5521fb81 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAAClip.h @@ -0,0 +1,137 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAAClip_DEFINED +#define SkAAClip_DEFINED + +#include "include/core/SkRegion.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkBlitter.h" + +class SkAAClip { +public: + SkAAClip(); + SkAAClip(const SkAAClip&); + ~SkAAClip(); + + SkAAClip& operator=(const SkAAClip&); + friend bool operator==(const SkAAClip&, const SkAAClip&); + friend bool operator!=(const SkAAClip& a, const SkAAClip& b) { + return !(a == b); + } + + void swap(SkAAClip&); + + bool isEmpty() const { return nullptr == fRunHead; } + const SkIRect& getBounds() const { return fBounds; } + + // Returns true iff the clip is not empty, and is just a hard-edged rect (no partial alpha). + // If true, getBounds() can be used in place of this clip. + bool isRect() const; + + bool setEmpty(); + bool setRect(const SkIRect&); + bool setRect(const SkRect&, bool doAA = true); + bool setPath(const SkPath&, const SkRegion* clip = nullptr, bool doAA = true); + bool setRegion(const SkRegion&); + bool set(const SkAAClip&); + + bool op(const SkAAClip&, const SkAAClip&, SkRegion::Op); + + // Helpers for op() + bool op(const SkIRect&, SkRegion::Op); + bool op(const SkRect&, SkRegion::Op, bool doAA); + bool op(const SkAAClip&, SkRegion::Op); + + bool translate(int dx, int dy, SkAAClip* dst) const; + bool translate(int dx, int dy) { + return this->translate(dx, dy, this); + } + + /** + * Allocates a mask the size of the aaclip, and expands its data into + * the mask, using kA8_Format + */ + void copyToMask(SkMask*) const; + + // called internally + + bool quickContains(int left, int top, int right, int bottom) const; + bool quickContains(const SkIRect& r) const { + return this->quickContains(r.fLeft, r.fTop, r.fRight, r.fBottom); + } + + const uint8_t* findRow(int y, int* lastYForRow = nullptr) const; + const uint8_t* findX(const uint8_t data[], int x, int* initialCount = nullptr) const; + + class Iter; + struct RunHead; + struct YOffset; + class Builder; + +#ifdef SK_DEBUG + void validate() const; + void debug(bool compress_y=false) const; +#else + void validate() const {} + void debug(bool compress_y=false) const {} +#endif + +private: + SkIRect fBounds; + RunHead* fRunHead; + + void freeRuns(); + bool trimBounds(); + bool trimTopBottom(); + bool trimLeftRight(); + + friend class Builder; + class BuilderBlitter; + friend class BuilderBlitter; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkAAClipBlitter : public SkBlitter { +public: + SkAAClipBlitter() : fScanlineScratch(nullptr) {} + ~SkAAClipBlitter() override; + + void init(SkBlitter* blitter, const SkAAClip* aaclip) { + SkASSERT(aaclip && !aaclip->isEmpty()); + fBlitter = blitter; + fAAClip = aaclip; + fAAClipBounds = aaclip->getBounds(); + } + + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitMask(const SkMask&, const SkIRect& clip) override; + const SkPixmap* justAnOpaqueColor(uint32_t* value) override; + +private: + SkBlitter* fBlitter; + const SkAAClip* fAAClip; + SkIRect fAAClipBounds; + + // point into fScanlineScratch + int16_t* fRuns; + SkAlpha* fAA; + + enum { + kSize = 32 * 32 + }; + SkAutoSMalloc fGrayMaskScratch; // used for blitMask + void* fScanlineScratch; // enough for a mask at 32bit, or runs+aa + + void ensureRunsAndAA(); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkATrace.cpp b/gfx/skia/skia/src/core/SkATrace.cpp new file mode 100644 index 0000000000..87cdca9289 --- /dev/null +++ b/gfx/skia/skia/src/core/SkATrace.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkATrace.h" + +#include "src/core/SkTraceEvent.h" + +#include "src/core/SkTraceEventCommon.h" + +#ifdef SK_BUILD_FOR_ANDROID +#include +#endif + +SkATrace::SkATrace() : fBeginSection(nullptr), fEndSection(nullptr), fIsEnabled(nullptr) { +#if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) + fIsEnabled = []{ return static_cast(CC_UNLIKELY(ATRACE_ENABLED())); }; + fBeginSection = [](const char* name){ ATRACE_BEGIN(name); }; + fEndSection = []{ ATRACE_END(); }; +#elif defined(SK_BUILD_FOR_ANDROID) + if (void* lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL)) { + fBeginSection = (decltype(fBeginSection))dlsym(lib, "ATrace_beginSection"); + fEndSection = (decltype(fEndSection))dlsym(lib, "ATrace_endSection"); + fIsEnabled = (decltype(fIsEnabled))dlsym(lib, "ATrace_isEnabled"); + } +#endif + + if (!fIsEnabled) { + fIsEnabled = []{ return false; }; + } +} + +SkEventTracer::Handle SkATrace::addTraceEvent(char phase, + const uint8_t* categoryEnabledFlag, + const char* name, + uint64_t id, + int numArgs, + const char** argNames, + const uint8_t* argTypes, + const uint64_t* argValues, + uint8_t flags) { + if (fIsEnabled()) { + if (TRACE_EVENT_PHASE_COMPLETE == phase || + TRACE_EVENT_PHASE_INSTANT == phase) { + fBeginSection(name); + } + + if (TRACE_EVENT_PHASE_INSTANT == phase) { + fEndSection(); + } + } + return 0; +} + +void SkATrace::updateTraceEventDuration(const uint8_t* categoryEnabledFlag, + const char* name, + SkEventTracer::Handle handle) { + // This is only ever called from a scoped trace event so we will just end the ATrace section. + if (fIsEnabled()) { + fEndSection(); + } +} + +const uint8_t* SkATrace::getCategoryGroupEnabled(const char* name) { + // Chrome tracing is setup to not repeatly call this function once it has been initialized. So + // we can't use this to do a check for ATrace isEnabled(). Thus we will always return yes here + // and then check to see if ATrace is enabled when beginning and ending a section. + static uint8_t yes = SkEventTracer::kEnabledForRecording_CategoryGroupEnabledFlags; + return &yes; +} + + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + +bool SkAndroidFrameworkTraceUtil::gEnableAndroidTracing = false; + +#endif //SK_BUILD_FOR_ANDROID_FRAMEWORK + + + diff --git a/gfx/skia/skia/src/core/SkATrace.h b/gfx/skia/skia/src/core/SkATrace.h new file mode 100644 index 0000000000..eae6d31a5d --- /dev/null +++ b/gfx/skia/skia/src/core/SkATrace.h @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkATrace_DEFINED +#define SkATrace_DEFINED + +#include "include/utils/SkEventTracer.h" + +/** + * This class is used to support ATrace in android apps. It hooks into the SkEventTracer system. It + * currently supports the macros TRACE_EVENT*, TRACE_EVENT_INSTANT*, and TRACE_EVENT_BEGIN/END*. + * For versions of these calls that take additoinal args and value pairs we currently just drop them + * and report only the name. Since ATrace is a simple push and pop system (all traces are fully + * nested), if using BEGIN and END you should also make sure your calls are properly nested (i.e. if + * startA is before startB, then endB is before endA). + */ +class SkATrace : public SkEventTracer { +public: + SkATrace(); + + SkEventTracer::Handle addTraceEvent(char phase, + const uint8_t* categoryEnabledFlag, + const char* name, + uint64_t id, + int numArgs, + const char** argNames, + const uint8_t* argTypes, + const uint64_t* argValues, + uint8_t flags) override; + + + void updateTraceEventDuration(const uint8_t* categoryEnabledFlag, + const char* name, + SkEventTracer::Handle handle) override; + + const uint8_t* getCategoryGroupEnabled(const char* name) override; + + const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag) override { + static const char* category = "skiaATrace"; + return category; + } + +private: + void (*fBeginSection)(const char*); + void (*fEndSection)(void); + bool (*fIsEnabled)(void); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkAdvancedTypefaceMetrics.h b/gfx/skia/skia/src/core/SkAdvancedTypefaceMetrics.h new file mode 100644 index 0000000000..84fd020af7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAdvancedTypefaceMetrics.h @@ -0,0 +1,73 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAdvancedTypefaceMetrics_DEFINED +#define SkAdvancedTypefaceMetrics_DEFINED + +#include "include/core/SkRect.h" +#include "include/core/SkString.h" +#include "include/private/SkBitmaskEnum.h" + +/** \class SkAdvancedTypefaceMetrics + + The SkAdvancedTypefaceMetrics class is used by the PDF backend to correctly + embed typefaces. This class is created and filled in with information by + SkTypeface::getAdvancedMetrics. +*/ +struct SkAdvancedTypefaceMetrics { + // The PostScript name of the font. See `FontName` and `BaseFont` in PDF standard. + SkString fPostScriptName; + SkString fFontName; + + // These enum values match the values used in the PDF file format. + enum StyleFlags : uint32_t { + kFixedPitch_Style = 0x00000001, + kSerif_Style = 0x00000002, + kScript_Style = 0x00000008, + kItalic_Style = 0x00000040, + kAllCaps_Style = 0x00010000, + kSmallCaps_Style = 0x00020000, + kForceBold_Style = 0x00040000 + }; + StyleFlags fStyle = (StyleFlags)0; // Font style characteristics. + + enum FontType : uint8_t { + kType1_Font, + kType1CID_Font, + kCFF_Font, + kTrueType_Font, + kOther_Font, + }; + // The type of the underlying font program. This field determines which + // of the following fields are valid. If it is kOther_Font the per glyph + // information will never be populated. + FontType fType = kOther_Font; + + enum FontFlags : uint8_t { + kMultiMaster_FontFlag = 0x01, //! struct is_bitmask_enum : std::true_type {}; +template <> struct is_bitmask_enum : std::true_type {}; +} + +#endif diff --git a/gfx/skia/skia/src/core/SkAlphaRuns.cpp b/gfx/skia/skia/src/core/SkAlphaRuns.cpp new file mode 100644 index 0000000000..4d98ececdc --- /dev/null +++ b/gfx/skia/skia/src/core/SkAlphaRuns.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkTo.h" +#include "src/core/SkAntiRun.h" +#include "src/core/SkUtils.h" + +void SkAlphaRuns::reset(int width) { + SkASSERT(width > 0); + +#ifdef SK_DEBUG +#ifndef SK_DISABLE_SLOW_DEBUG_VALIDATION + sk_memset16((uint16_t*)fRuns, (uint16_t)(-42), width); +#endif +#endif + fRuns[0] = SkToS16(width); + fRuns[width] = 0; + fAlpha[0] = 0; + + SkDEBUGCODE(fWidth = width;) + SkDEBUGCODE(this->validate();) +} + +#ifdef SK_DEBUG + void SkAlphaRuns::assertValid(int y, int maxStep) const { +#ifndef SK_DISABLE_SLOW_DEBUG_VALIDATION + int max = (y + 1) * maxStep - (y == maxStep - 1); + + const int16_t* runs = fRuns; + const uint8_t* alpha = fAlpha; + + while (*runs) { + SkASSERT(*alpha <= max); + alpha += *runs; + runs += *runs; + } +#endif + } + + void SkAlphaRuns::dump() const { + const int16_t* runs = fRuns; + const uint8_t* alpha = fAlpha; + + SkDebugf("Runs"); + while (*runs) { + int n = *runs; + + SkDebugf(" %02x", *alpha); + if (n > 1) { + SkDebugf(",%d", n); + } + alpha += n; + runs += n; + } + SkDebugf("\n"); + } + + void SkAlphaRuns::validate() const { +#ifndef SK_DISABLE_SLOW_DEBUG_VALIDATION + SkASSERT(fWidth > 0); + + int count = 0; + const int16_t* runs = fRuns; + + while (*runs) { + SkASSERT(*runs > 0); + count += *runs; + SkASSERT(count <= fWidth); + runs += *runs; + } + SkASSERT(count == fWidth); +#endif + } +#endif diff --git a/gfx/skia/skia/src/core/SkAnalyticEdge.cpp b/gfx/skia/skia/src/core/SkAnalyticEdge.cpp new file mode 100644 index 0000000000..2062512501 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAnalyticEdge.cpp @@ -0,0 +1,512 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkTo.h" +#include "src/core/SkAnalyticEdge.h" +#include "src/core/SkFDot6.h" +#include "src/core/SkMathPriv.h" +#include + +static const int kInverseTableSize = 1024; // SK_FDot6One * 16 + +static inline SkFixed quick_inverse(SkFDot6 x) { + SkASSERT(SkAbs32(x) < kInverseTableSize); + static const int32_t table[kInverseTableSize * 2] = { + -4096, -4100, -4104, -4108, -4112, -4116, -4120, -4124, -4128, -4132, -4136, + -4140, -4144, -4148, -4152, -4156, -4161, -4165, -4169, -4173, -4177, -4181, + -4185, -4190, -4194, -4198, -4202, -4206, -4211, -4215, -4219, -4223, -4228, + -4232, -4236, -4240, -4245, -4249, -4253, -4258, -4262, -4266, -4271, -4275, + -4279, -4284, -4288, -4293, -4297, -4301, -4306, -4310, -4315, -4319, -4324, + -4328, -4332, -4337, -4341, -4346, -4350, -4355, -4359, -4364, -4369, -4373, + -4378, -4382, -4387, -4391, -4396, -4401, -4405, -4410, -4415, -4419, -4424, + -4429, -4433, -4438, -4443, -4447, -4452, -4457, -4462, -4466, -4471, -4476, + -4481, -4485, -4490, -4495, -4500, -4505, -4510, -4514, -4519, -4524, -4529, + -4534, -4539, -4544, -4549, -4554, -4559, -4563, -4568, -4573, -4578, -4583, + -4588, -4593, -4599, -4604, -4609, -4614, -4619, -4624, -4629, -4634, -4639, + -4644, -4650, -4655, -4660, -4665, -4670, -4675, -4681, -4686, -4691, -4696, + -4702, -4707, -4712, -4718, -4723, -4728, -4733, -4739, -4744, -4750, -4755, + -4760, -4766, -4771, -4777, -4782, -4788, -4793, -4798, -4804, -4809, -4815, + -4821, -4826, -4832, -4837, -4843, -4848, -4854, -4860, -4865, -4871, -4877, + -4882, -4888, -4894, -4899, -4905, -4911, -4917, -4922, -4928, -4934, -4940, + -4946, -4951, -4957, -4963, -4969, -4975, -4981, -4987, -4993, -4999, -5005, + -5011, -5017, -5023, -5029, -5035, -5041, -5047, -5053, -5059, -5065, -5071, + -5077, -5084, -5090, -5096, -5102, -5108, -5115, -5121, -5127, -5133, -5140, + -5146, -5152, -5159, -5165, -5171, -5178, -5184, -5190, -5197, -5203, -5210, + -5216, -5223, -5229, -5236, -5242, -5249, -5256, -5262, -5269, -5275, -5282, + -5289, -5295, -5302, -5309, -5315, -5322, -5329, -5336, -5343, -5349, -5356, + -5363, -5370, -5377, -5384, -5391, -5398, -5405, -5412, -5418, -5426, -5433, + -5440, -5447, -5454, -5461, -5468, -5475, -5482, -5489, -5497, -5504, -5511, + -5518, -5526, -5533, -5540, -5548, -5555, -5562, -5570, -5577, -5584, -5592, + -5599, -5607, -5614, -5622, -5629, -5637, -5645, -5652, -5660, -5667, -5675, + -5683, -5691, -5698, -5706, -5714, -5722, -5729, -5737, -5745, -5753, -5761, + -5769, -5777, -5785, -5793, -5801, -5809, -5817, -5825, -5833, -5841, -5849, + -5857, -5866, -5874, -5882, -5890, -5899, -5907, -5915, -5924, -5932, -5940, + -5949, -5957, -5966, -5974, -5983, -5991, -6000, -6009, -6017, -6026, -6034, + -6043, -6052, -6061, -6069, -6078, -6087, -6096, -6105, -6114, -6123, -6132, + -6141, -6150, -6159, -6168, -6177, -6186, -6195, -6204, -6213, -6223, -6232, + -6241, -6250, -6260, -6269, -6278, -6288, -6297, -6307, -6316, -6326, -6335, + -6345, -6355, -6364, -6374, -6384, -6393, -6403, -6413, -6423, -6432, -6442, + -6452, -6462, -6472, -6482, -6492, -6502, -6512, -6523, -6533, -6543, -6553, + -6563, -6574, -6584, -6594, -6605, -6615, -6626, -6636, -6647, -6657, -6668, + -6678, -6689, -6700, -6710, -6721, -6732, -6743, -6754, -6765, -6775, -6786, + -6797, -6808, -6820, -6831, -6842, -6853, -6864, -6875, -6887, -6898, -6909, + -6921, -6932, -6944, -6955, -6967, -6978, -6990, -7002, -7013, -7025, -7037, + -7049, -7061, -7073, -7084, -7096, -7108, -7121, -7133, -7145, -7157, -7169, + -7182, -7194, -7206, -7219, -7231, -7244, -7256, -7269, -7281, -7294, -7307, + -7319, -7332, -7345, -7358, -7371, -7384, -7397, -7410, -7423, -7436, -7449, + -7463, -7476, -7489, -7503, -7516, -7530, -7543, -7557, -7570, -7584, -7598, + -7612, -7626, -7639, -7653, -7667, -7681, -7695, -7710, -7724, -7738, -7752, + -7767, -7781, -7796, -7810, -7825, -7839, -7854, -7869, -7884, -7898, -7913, + -7928, -7943, -7958, -7973, -7989, -8004, -8019, -8035, -8050, -8065, -8081, + -8097, -8112, -8128, -8144, -8160, -8176, -8192, -8208, -8224, -8240, -8256, + -8272, -8289, -8305, -8322, -8338, -8355, -8371, -8388, -8405, -8422, -8439, + -8456, -8473, -8490, -8507, -8525, -8542, -8559, -8577, -8594, -8612, -8630, + -8648, -8665, -8683, -8701, -8719, -8738, -8756, -8774, -8793, -8811, -8830, + -8848, -8867, -8886, -8905, -8924, -8943, -8962, -8981, -9000, -9020, -9039, + -9058, -9078, -9098, -9118, -9137, -9157, -9177, -9198, -9218, -9238, -9258, + -9279, -9300, -9320, -9341, -9362, -9383, -9404, -9425, -9446, -9467, -9489, + -9510, -9532, -9554, -9576, -9597, -9619, -9642, -9664, -9686, -9709, -9731, + -9754, -9776, -9799, -9822, -9845, -9868, -9892, -9915, -9939, -9962, -9986, + -10010, -10034, -10058, -10082, -10106, -10131, -10155, -10180, -10205, -10230, + -10255, -10280, -10305, -10330, -10356, -10381, -10407, -10433, -10459, -10485, + -10512, -10538, -10564, -10591, -10618, -10645, -10672, -10699, -10727, -10754, + -10782, -10810, -10837, -10866, -10894, -10922, -10951, -10979, -11008, -11037, + -11066, -11096, -11125, -11155, -11184, -11214, -11244, -11275, -11305, -11335, + -11366, -11397, -11428, -11459, -11491, -11522, -11554, -11586, -11618, -11650, + -11683, -11715, -11748, -11781, -11814, -11848, -11881, -11915, -11949, -11983, + -12018, -12052, -12087, -12122, -12157, -12192, -12228, -12264, -12300, -12336, + -12372, -12409, -12446, -12483, -12520, -12557, -12595, -12633, -12671, -12710, + -12748, -12787, -12826, -12865, -12905, -12945, -12985, -13025, -13066, -13107, + -13148, -13189, -13231, -13273, -13315, -13357, -13400, -13443, -13486, -13530, + -13573, -13617, -13662, -13706, -13751, -13797, -13842, -13888, -13934, -13981, + -14027, -14074, -14122, -14169, -14217, -14266, -14315, -14364, -14413, -14463, + -14513, -14563, -14614, -14665, -14716, -14768, -14820, -14873, -14926, -14979, + -15033, -15087, -15141, -15196, -15252, -15307, -15363, -15420, -15477, -15534, + -15592, -15650, -15709, -15768, -15827, -15887, -15947, -16008, -16070, -16131, + -16194, -16256, -16320, -16384, -16448, -16513, -16578, -16644, -16710, -16777, + -16844, -16912, -16980, -17050, -17119, -17189, -17260, -17331, -17403, -17476, + -17549, -17623, -17697, -17772, -17848, -17924, -18001, -18078, -18157, -18236, + -18315, -18396, -18477, -18558, -18641, -18724, -18808, -18893, -18978, -19065, + -19152, -19239, -19328, -19418, -19508, -19599, -19691, -19784, -19878, -19972, + -20068, -20164, -20262, -20360, -20460, -20560, -20661, -20763, -20867, -20971, + -21076, -21183, -21290, -21399, -21509, -21620, -21732, -21845, -21959, -22075, + -22192, -22310, -22429, -22550, -22671, -22795, -22919, -23045, -23172, -23301, + -23431, -23563, -23696, -23831, -23967, -24105, -24244, -24385, -24528, -24672, + -24818, -24966, -25115, -25266, -25420, -25575, -25731, -25890, -26051, -26214, + -26379, -26546, -26715, -26886, -27060, -27235, -27413, -27594, -27776, -27962, + -28149, -28339, -28532, -28728, -28926, -29127, -29330, -29537, -29746, -29959, + -30174, -30393, -30615, -30840, -31068, -31300, -31536, -31775, -32017, -32263, + -32513, -32768, -33026, -33288, -33554, -33825, -34100, -34379, -34663, -34952, + -35246, -35544, -35848, -36157, -36472, -36792, -37117, -37449, -37786, -38130, + -38479, -38836, -39199, -39568, -39945, -40329, -40721, -41120, -41527, -41943, + -42366, -42799, -43240, -43690, -44150, -44620, -45100, -45590, -46091, -46603, + -47127, -47662, -48210, -48770, -49344, -49932, -50533, -51150, -51781, -52428, + -53092, -53773, -54471, -55188, -55924, -56679, -57456, -58254, -59074, -59918, + -60787, -61680, -62601, -63550, -64527, -65536, -66576, -67650, -68759, -69905, + -71089, -72315, -73584, -74898, -76260, -77672, -79137, -80659, -82241, -83886, + -85598, -87381, -89240, -91180, -93206, -95325, -97541, -99864, -102300, + -104857, -107546, -110376, -113359, -116508, -119837, -123361, -127100, -131072, + -135300, -139810, -144631, -149796, -155344, -161319, -167772, -174762, -182361, + -190650, -199728, -209715, -220752, -233016, -246723, -262144, -279620, -299593, + -322638, -349525, -381300, -419430, -466033, -524288, -599186, -699050, -838860, + -1048576, -1398101, -2097152, -4194304, 0, 4194304, 2097152, 1398101, 1048576, + 838860, 699050, 599186, 524288, 466033, 419430, 381300, 349525, 322638, 299593, + 279620, 262144, 246723, 233016, 220752, 209715, 199728, 190650, 182361, 174762, + 167772, 161319, 155344, 149796, 144631, 139810, 135300, 131072, 127100, 123361, + 119837, 116508, 113359, 110376, 107546, 104857, 102300, 99864, 97541, 95325, + 93206, 91180, 89240, 87381, 85598, 83886, 82241, 80659, 79137, 77672, 76260, + 74898, 73584, 72315, 71089, 69905, 68759, 67650, 66576, 65536, 64527, 63550, + 62601, 61680, 60787, 59918, 59074, 58254, 57456, 56679, 55924, 55188, 54471, + 53773, 53092, 52428, 51781, 51150, 50533, 49932, 49344, 48770, 48210, 47662, + 47127, 46603, 46091, 45590, 45100, 44620, 44150, 43690, 43240, 42799, 42366, + 41943, 41527, 41120, 40721, 40329, 39945, 39568, 39199, 38836, 38479, 38130, + 37786, 37449, 37117, 36792, 36472, 36157, 35848, 35544, 35246, 34952, 34663, + 34379, 34100, 33825, 33554, 33288, 33026, 32768, 32513, 32263, 32017, 31775, + 31536, 31300, 31068, 30840, 30615, 30393, 30174, 29959, 29746, 29537, 29330, + 29127, 28926, 28728, 28532, 28339, 28149, 27962, 27776, 27594, 27413, 27235, + 27060, 26886, 26715, 26546, 26379, 26214, 26051, 25890, 25731, 25575, 25420, + 25266, 25115, 24966, 24818, 24672, 24528, 24385, 24244, 24105, 23967, 23831, + 23696, 23563, 23431, 23301, 23172, 23045, 22919, 22795, 22671, 22550, 22429, + 22310, 22192, 22075, 21959, 21845, 21732, 21620, 21509, 21399, 21290, 21183, + 21076, 20971, 20867, 20763, 20661, 20560, 20460, 20360, 20262, 20164, 20068, + 19972, 19878, 19784, 19691, 19599, 19508, 19418, 19328, 19239, 19152, 19065, + 18978, 18893, 18808, 18724, 18641, 18558, 18477, 18396, 18315, 18236, 18157, + 18078, 18001, 17924, 17848, 17772, 17697, 17623, 17549, 17476, 17403, 17331, + 17260, 17189, 17119, 17050, 16980, 16912, 16844, 16777, 16710, 16644, 16578, + 16513, 16448, 16384, 16320, 16256, 16194, 16131, 16070, 16008, 15947, 15887, + 15827, 15768, 15709, 15650, 15592, 15534, 15477, 15420, 15363, 15307, 15252, + 15196, 15141, 15087, 15033, 14979, 14926, 14873, 14820, 14768, 14716, 14665, + 14614, 14563, 14513, 14463, 14413, 14364, 14315, 14266, 14217, 14169, 14122, + 14074, 14027, 13981, 13934, 13888, 13842, 13797, 13751, 13706, 13662, 13617, + 13573, 13530, 13486, 13443, 13400, 13357, 13315, 13273, 13231, 13189, 13148, + 13107, 13066, 13025, 12985, 12945, 12905, 12865, 12826, 12787, 12748, 12710, + 12671, 12633, 12595, 12557, 12520, 12483, 12446, 12409, 12372, 12336, 12300, + 12264, 12228, 12192, 12157, 12122, 12087, 12052, 12018, 11983, 11949, 11915, + 11881, 11848, 11814, 11781, 11748, 11715, 11683, 11650, 11618, 11586, 11554, + 11522, 11491, 11459, 11428, 11397, 11366, 11335, 11305, 11275, 11244, 11214, + 11184, 11155, 11125, 11096, 11066, 11037, 11008, 10979, 10951, 10922, 10894, + 10866, 10837, 10810, 10782, 10754, 10727, 10699, 10672, 10645, 10618, 10591, + 10564, 10538, 10512, 10485, 10459, 10433, 10407, 10381, 10356, 10330, 10305, + 10280, 10255, 10230, 10205, 10180, 10155, 10131, 10106, 10082, 10058, 10034, + 10010, 9986, 9962, 9939, 9915, 9892, 9868, 9845, 9822, 9799, 9776, 9754, 9731, + 9709, 9686, 9664, 9642, 9619, 9597, 9576, 9554, 9532, 9510, 9489, 9467, 9446, + 9425, 9404, 9383, 9362, 9341, 9320, 9300, 9279, 9258, 9238, 9218, 9198, 9177, + 9157, 9137, 9118, 9098, 9078, 9058, 9039, 9020, 9000, 8981, 8962, 8943, 8924, + 8905, 8886, 8867, 8848, 8830, 8811, 8793, 8774, 8756, 8738, 8719, 8701, 8683, + 8665, 8648, 8630, 8612, 8594, 8577, 8559, 8542, 8525, 8507, 8490, 8473, 8456, + 8439, 8422, 8405, 8388, 8371, 8355, 8338, 8322, 8305, 8289, 8272, 8256, 8240, + 8224, 8208, 8192, 8176, 8160, 8144, 8128, 8112, 8097, 8081, 8065, 8050, 8035, + 8019, 8004, 7989, 7973, 7958, 7943, 7928, 7913, 7898, 7884, 7869, 7854, 7839, + 7825, 7810, 7796, 7781, 7767, 7752, 7738, 7724, 7710, 7695, 7681, 7667, 7653, + 7639, 7626, 7612, 7598, 7584, 7570, 7557, 7543, 7530, 7516, 7503, 7489, 7476, + 7463, 7449, 7436, 7423, 7410, 7397, 7384, 7371, 7358, 7345, 7332, 7319, 7307, + 7294, 7281, 7269, 7256, 7244, 7231, 7219, 7206, 7194, 7182, 7169, 7157, 7145, + 7133, 7121, 7108, 7096, 7084, 7073, 7061, 7049, 7037, 7025, 7013, 7002, 6990, + 6978, 6967, 6955, 6944, 6932, 6921, 6909, 6898, 6887, 6875, 6864, 6853, 6842, + 6831, 6820, 6808, 6797, 6786, 6775, 6765, 6754, 6743, 6732, 6721, 6710, 6700, + 6689, 6678, 6668, 6657, 6647, 6636, 6626, 6615, 6605, 6594, 6584, 6574, 6563, + 6553, 6543, 6533, 6523, 6512, 6502, 6492, 6482, 6472, 6462, 6452, 6442, 6432, + 6423, 6413, 6403, 6393, 6384, 6374, 6364, 6355, 6345, 6335, 6326, 6316, 6307, + 6297, 6288, 6278, 6269, 6260, 6250, 6241, 6232, 6223, 6213, 6204, 6195, 6186, + 6177, 6168, 6159, 6150, 6141, 6132, 6123, 6114, 6105, 6096, 6087, 6078, 6069, + 6061, 6052, 6043, 6034, 6026, 6017, 6009, 6000, 5991, 5983, 5974, 5966, 5957, + 5949, 5940, 5932, 5924, 5915, 5907, 5899, 5890, 5882, 5874, 5866, 5857, 5849, + 5841, 5833, 5825, 5817, 5809, 5801, 5793, 5785, 5777, 5769, 5761, 5753, 5745, + 5737, 5729, 5722, 5714, 5706, 5698, 5691, 5683, 5675, 5667, 5660, 5652, 5645, + 5637, 5629, 5622, 5614, 5607, 5599, 5592, 5584, 5577, 5570, 5562, 5555, 5548, + 5540, 5533, 5526, 5518, 5511, 5504, 5497, 5489, 5482, 5475, 5468, 5461, 5454, + 5447, 5440, 5433, 5426, 5418, 5412, 5405, 5398, 5391, 5384, 5377, 5370, 5363, + 5356, 5349, 5343, 5336, 5329, 5322, 5315, 5309, 5302, 5295, 5289, 5282, 5275, + 5269, 5262, 5256, 5249, 5242, 5236, 5229, 5223, 5216, 5210, 5203, 5197, 5190, + 5184, 5178, 5171, 5165, 5159, 5152, 5146, 5140, 5133, 5127, 5121, 5115, 5108, + 5102, 5096, 5090, 5084, 5077, 5071, 5065, 5059, 5053, 5047, 5041, 5035, 5029, + 5023, 5017, 5011, 5005, 4999, 4993, 4987, 4981, 4975, 4969, 4963, 4957, 4951, + 4946, 4940, 4934, 4928, 4922, 4917, 4911, 4905, 4899, 4894, 4888, 4882, 4877, + 4871, 4865, 4860, 4854, 4848, 4843, 4837, 4832, 4826, 4821, 4815, 4809, 4804, + 4798, 4793, 4788, 4782, 4777, 4771, 4766, 4760, 4755, 4750, 4744, 4739, 4733, + 4728, 4723, 4718, 4712, 4707, 4702, 4696, 4691, 4686, 4681, 4675, 4670, 4665, + 4660, 4655, 4650, 4644, 4639, 4634, 4629, 4624, 4619, 4614, 4609, 4604, 4599, + 4593, 4588, 4583, 4578, 4573, 4568, 4563, 4559, 4554, 4549, 4544, 4539, 4534, + 4529, 4524, 4519, 4514, 4510, 4505, 4500, 4495, 4490, 4485, 4481, 4476, 4471, + 4466, 4462, 4457, 4452, 4447, 4443, 4438, 4433, 4429, 4424, 4419, 4415, 4410, + 4405, 4401, 4396, 4391, 4387, 4382, 4378, 4373, 4369, 4364, 4359, 4355, 4350, + 4346, 4341, 4337, 4332, 4328, 4324, 4319, 4315, 4310, 4306, 4301, 4297, 4293, + 4288, 4284, 4279, 4275, 4271, 4266, 4262, 4258, 4253, 4249, 4245, 4240, 4236, + 4232, 4228, 4223, 4219, 4215, 4211, 4206, 4202, 4198, 4194, 4190, 4185, 4181, + 4177, 4173, 4169, 4165, 4161, 4156, 4152, 4148, 4144, 4140, 4136, 4132, 4128, + 4124, 4120, 4116, 4112, 4108, 4104, 4100 + }; + return table[kInverseTableSize + x]; +} + +static inline SkFixed quick_div(SkFDot6 a, SkFDot6 b) { + const int kMinBits = 3; // abs(b) should be at least (1 << kMinBits) for quick division + const int kMaxBits = 31; // Number of bits available in signed int + // Given abs(b) <= (1 << kMinBits), the inverse of abs(b) is at most 1 << (22 - kMinBits) in + // SkFixed format. Hence abs(a) should be less than kMaxAbsA + const int kMaxAbsA = 1 << (kMaxBits - (22 - kMinBits)); + SkFDot6 abs_a = SkAbs32(a); + SkFDot6 abs_b = SkAbs32(b); + if (abs_b >= (1 << kMinBits) && abs_b < kInverseTableSize && abs_a < kMaxAbsA) { + SkASSERT((int64_t)a * quick_inverse(b) <= SK_MaxS32 + && (int64_t)a * quick_inverse(b) >= SK_MinS32); + SkFixed ourAnswer = (a * quick_inverse(b)) >> 6; + SkASSERT( + (SkFDot6Div(a,b) == 0 && ourAnswer == 0) || + SkFixedDiv(SkAbs32(SkFDot6Div(a,b) - ourAnswer), SkAbs32(SkFDot6Div(a,b))) <= 1 << 10 + ); + return ourAnswer; + } + return SkFDot6Div(a, b); +} + +bool SkAnalyticEdge::setLine(const SkPoint& p0, const SkPoint& p1) { + fRiteE = nullptr; + + // We must set X/Y using the same way (e.g., times 4, to FDot6, then to Fixed) as Quads/Cubics. + // Otherwise the order of the edge might be wrong due to precision limit. + const int accuracy = kDefaultAccuracy; +#ifdef SK_RASTERIZE_EVEN_ROUNDING + SkFixed x0 = SkFDot6ToFixed(SkScalarRoundToFDot6(p0.fX, accuracy)) >> accuracy; + SkFixed y0 = SnapY(SkFDot6ToFixed(SkScalarRoundToFDot6(p0.fY, accuracy)) >> accuracy); + SkFixed x1 = SkFDot6ToFixed(SkScalarRoundToFDot6(p1.fX, accuracy)) >> accuracy; + SkFixed y1 = SnapY(SkFDot6ToFixed(SkScalarRoundToFDot6(p1.fY, accuracy)) >> accuracy); +#else + const int multiplier = (1 << kDefaultAccuracy); + SkFixed x0 = SkFDot6ToFixed(SkScalarToFDot6(p0.fX * multiplier)) >> accuracy; + SkFixed y0 = SnapY(SkFDot6ToFixed(SkScalarToFDot6(p0.fY * multiplier)) >> accuracy); + SkFixed x1 = SkFDot6ToFixed(SkScalarToFDot6(p1.fX * multiplier)) >> accuracy; + SkFixed y1 = SnapY(SkFDot6ToFixed(SkScalarToFDot6(p1.fY * multiplier)) >> accuracy); +#endif + + int winding = 1; + + if (y0 > y1) { + using std::swap; + swap(x0, x1); + swap(y0, y1); + winding = -1; + } + + // are we a zero-height line? + SkFDot6 dy = SkFixedToFDot6(y1 - y0); + if (dy == 0) { + return false; + } + SkFDot6 dx = SkFixedToFDot6(x1 - x0); + SkFixed slope = quick_div(dx, dy); + SkFixed absSlope = SkAbs32(slope); + + fX = x0; + fDX = slope; + fUpperX = x0; + fY = y0; + fUpperY = y0; + fLowerY = y1; + fDY = dx == 0 || slope == 0 ? SK_MaxS32 : absSlope < kInverseTableSize + ? quick_inverse(absSlope) + : SkAbs32(quick_div(dy, dx)); + fCurveCount = 0; + fWinding = SkToS8(winding); + fCurveShift = 0; + + return true; +} + +// This will become a bottleneck for small ovals rendering if we call SkFixedDiv twice here. +// Therefore, we'll let the outter function compute the slope once and send in the value. +// Moreover, we'll compute fDY by quickly lookup the inverse table (if possible). +bool SkAnalyticEdge::updateLine(SkFixed x0, SkFixed y0, SkFixed x1, SkFixed y1, SkFixed slope) { + // Since we send in the slope, we can no longer snap y inside this function. + // If we don't send in the slope, or we do some more sophisticated snapping, this function + // could be a performance bottleneck. + SkASSERT(fWinding == 1 || fWinding == -1); + SkASSERT(fCurveCount != 0); + + // We don't chop at y extrema for cubics so the y is not guaranteed to be increasing for them. + // In that case, we have to swap x/y and negate the winding. + if (y0 > y1) { + using std::swap; + swap(x0, x1); + swap(y0, y1); + fWinding = -fWinding; + } + + SkASSERT(y0 <= y1); + + SkFDot6 dx = SkFixedToFDot6(x1 - x0); + SkFDot6 dy = SkFixedToFDot6(y1 - y0); + + // are we a zero-height line? + if (dy == 0) { + return false; + } + + SkASSERT(slope < SK_MaxS32); + + SkFDot6 absSlope = SkAbs32(SkFixedToFDot6(slope)); + fX = x0; + fDX = slope; + fUpperX = x0; + fY = y0; + fUpperY = y0; + fLowerY = y1; + fDY = (dx == 0 || slope == 0) + ? SK_MaxS32 + : absSlope < kInverseTableSize + ? quick_inverse(absSlope) + : SkAbs32(quick_div(dy, dx)); + + return true; +} + +bool SkAnalyticEdge::update(SkFixed last_y, bool sortY) { + SkASSERT(last_y >= fLowerY); // we shouldn't update edge if last_y < fLowerY + if (fCurveCount < 0) { + return static_cast(this)->updateCubic(sortY); + } else if (fCurveCount > 0) { + return static_cast(this)->updateQuadratic(); + } + return false; +} + +bool SkAnalyticQuadraticEdge::setQuadratic(const SkPoint pts[3]) { + fRiteE = nullptr; + + if (!fQEdge.setQuadraticWithoutUpdate(pts, kDefaultAccuracy)) { + return false; + } + fQEdge.fQx >>= kDefaultAccuracy; + fQEdge.fQy >>= kDefaultAccuracy; + fQEdge.fQDx >>= kDefaultAccuracy; + fQEdge.fQDy >>= kDefaultAccuracy; + fQEdge.fQDDx >>= kDefaultAccuracy; + fQEdge.fQDDy >>= kDefaultAccuracy; + fQEdge.fQLastX >>= kDefaultAccuracy; + fQEdge.fQLastY >>= kDefaultAccuracy; + fQEdge.fQy = SnapY(fQEdge.fQy); + fQEdge.fQLastY = SnapY(fQEdge.fQLastY); + + fWinding = fQEdge.fWinding; + fCurveCount = fQEdge.fCurveCount; + fCurveShift = fQEdge.fCurveShift; + + fSnappedX = fQEdge.fQx; + fSnappedY = fQEdge.fQy; + + return this->updateQuadratic(); +} + +bool SkAnalyticQuadraticEdge::updateQuadratic() { + int success = 0; // initialize to fail! + int count = fCurveCount; + SkFixed oldx = fQEdge.fQx; + SkFixed oldy = fQEdge.fQy; + SkFixed dx = fQEdge.fQDx; + SkFixed dy = fQEdge.fQDy; + SkFixed newx, newy, newSnappedX, newSnappedY; + int shift = fCurveShift; + + SkASSERT(count > 0); + + do { + SkFixed slope; + if (--count > 0) + { + newx = oldx + (dx >> shift); + newy = oldy + (dy >> shift); + if (SkAbs32(dy >> shift) >= SK_Fixed1 * 2) { // only snap when dy is large enough + SkFDot6 diffY = SkFixedToFDot6(newy - fSnappedY); + slope = diffY ? quick_div(SkFixedToFDot6(newx - fSnappedX), diffY) + : SK_MaxS32; + newSnappedY = SkTMin(fQEdge.fQLastY, SkFixedRoundToFixed(newy)); + newSnappedX = newx - SkFixedMul(slope, newy - newSnappedY); + } else { + newSnappedY = SkTMin(fQEdge.fQLastY, SnapY(newy)); + newSnappedX = newx; + SkFDot6 diffY = SkFixedToFDot6(newSnappedY - fSnappedY); + slope = diffY ? quick_div(SkFixedToFDot6(newx - fSnappedX), diffY) + : SK_MaxS32; + } + dx += fQEdge.fQDDx; + dy += fQEdge.fQDDy; + } + else // last segment + { + newx = fQEdge.fQLastX; + newy = fQEdge.fQLastY; + newSnappedY = newy; + newSnappedX = newx; + SkFDot6 diffY = (newy - fSnappedY) >> 10; + slope = diffY ? quick_div((newx - fSnappedX) >> 10, diffY) : SK_MaxS32; + } + if (slope < SK_MaxS32) { + success = this->updateLine(fSnappedX, fSnappedY, newSnappedX, newSnappedY, slope); + } + oldx = newx; + oldy = newy; + } while (count > 0 && !success); + + SkASSERT(newSnappedY <= fQEdge.fQLastY); + + fQEdge.fQx = newx; + fQEdge.fQy = newy; + fQEdge.fQDx = dx; + fQEdge.fQDy = dy; + fSnappedX = newSnappedX; + fSnappedY = newSnappedY; + fCurveCount = SkToS8(count); + return success; +} + +bool SkAnalyticCubicEdge::setCubic(const SkPoint pts[4], bool sortY) { + fRiteE = nullptr; + + if (!fCEdge.setCubicWithoutUpdate(pts, kDefaultAccuracy, sortY)) { + return false; + } + + fCEdge.fCx >>= kDefaultAccuracy; + fCEdge.fCy >>= kDefaultAccuracy; + fCEdge.fCDx >>= kDefaultAccuracy; + fCEdge.fCDy >>= kDefaultAccuracy; + fCEdge.fCDDx >>= kDefaultAccuracy; + fCEdge.fCDDy >>= kDefaultAccuracy; + fCEdge.fCDDDx >>= kDefaultAccuracy; + fCEdge.fCDDDy >>= kDefaultAccuracy; + fCEdge.fCLastX >>= kDefaultAccuracy; + fCEdge.fCLastY >>= kDefaultAccuracy; + fCEdge.fCy = SnapY(fCEdge.fCy); + fCEdge.fCLastY = SnapY(fCEdge.fCLastY); + + fWinding = fCEdge.fWinding; + fCurveCount = fCEdge.fCurveCount; + fCurveShift = fCEdge.fCurveShift; + fCubicDShift = fCEdge.fCubicDShift; + + fSnappedY = fCEdge.fCy; + + return this->updateCubic(sortY); +} + +bool SkAnalyticCubicEdge::updateCubic(bool sortY) { + int success; + int count = fCurveCount; + SkFixed oldx = fCEdge.fCx; + SkFixed oldy = fCEdge.fCy; + SkFixed newx, newy; + const int ddshift = fCurveShift; + const int dshift = fCubicDShift; + + SkASSERT(count < 0); + + do { + if (++count < 0) { + newx = oldx + (fCEdge.fCDx >> dshift); + fCEdge.fCDx += fCEdge.fCDDx >> ddshift; + fCEdge.fCDDx += fCEdge.fCDDDx; + + newy = oldy + (fCEdge.fCDy >> dshift); + fCEdge.fCDy += fCEdge.fCDDy >> ddshift; + fCEdge.fCDDy += fCEdge.fCDDDy; + } + else { // last segment + newx = fCEdge.fCLastX; + newy = fCEdge.fCLastY; + } + + // we want to say SkASSERT(oldy <= newy), but our finite fixedpoint + // doesn't always achieve that, so we have to explicitly pin it here. + if (sortY && newy < oldy) { + newy = oldy; + } + + SkFixed newSnappedY = SnapY(newy); + // we want to SkASSERT(snappedNewY <= fCEdge.fCLastY), but our finite fixedpoint + // doesn't always achieve that, so we have to explicitly pin it here. + if (sortY && fCEdge.fCLastY < newSnappedY) { + newSnappedY = fCEdge.fCLastY; + count = 0; + } + + SkFixed slope = SkFixedToFDot6(newSnappedY - fSnappedY) == 0 + ? SK_MaxS32 + : SkFDot6Div(SkFixedToFDot6(newx - oldx), + SkFixedToFDot6(newSnappedY - fSnappedY)); + + success = this->updateLine(oldx, fSnappedY, newx, newSnappedY, slope); + + oldx = newx; + oldy = newy; + fSnappedY = newSnappedY; + } while (count < 0 && !success); + + fCEdge.fCx = newx; + fCEdge.fCy = newy; + fCurveCount = SkToS8(count); + return success; +} diff --git a/gfx/skia/skia/src/core/SkAnalyticEdge.h b/gfx/skia/skia/src/core/SkAnalyticEdge.h new file mode 100644 index 0000000000..85d012234a --- /dev/null +++ b/gfx/skia/skia/src/core/SkAnalyticEdge.h @@ -0,0 +1,204 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAnalyticEdge_DEFINED +#define SkAnalyticEdge_DEFINED + +#include "include/private/SkTo.h" +#include "src/core/SkEdge.h" + +#include + +struct SkAnalyticEdge { + // Similar to SkEdge, the conic edges will be converted to quadratic edges + enum Type { + kLine_Type, + kQuad_Type, + kCubic_Type + }; + + SkAnalyticEdge* fNext; + SkAnalyticEdge* fPrev; + + // During aaa_walk_edges, if this edge is a left edge, + // then fRiteE is its corresponding right edge. Otherwise it's nullptr. + SkAnalyticEdge* fRiteE; + + SkFixed fX; + SkFixed fDX; + SkFixed fUpperX; // The x value when y = fUpperY + SkFixed fY; // The current y + SkFixed fUpperY; // The upper bound of y (our edge is from y = fUpperY to y = fLowerY) + SkFixed fLowerY; // The lower bound of y (our edge is from y = fUpperY to y = fLowerY) + SkFixed fDY; // abs(1/fDX); may be SK_MaxS32 when fDX is close to 0. + // fDY is only used for blitting trapezoids. + + SkFixed fSavedX; // For deferred blitting + SkFixed fSavedY; // For deferred blitting + SkFixed fSavedDY; // For deferred blitting + + int8_t fCurveCount; // only used by kQuad(+) and kCubic(-) + uint8_t fCurveShift; // appled to all Dx/DDx/DDDx except for fCubicDShift exception + uint8_t fCubicDShift; // applied to fCDx and fCDy only in cubic + int8_t fWinding; // 1 or -1 + + static const int kDefaultAccuracy = 2; // default accuracy for snapping + + static inline SkFixed SnapY(SkFixed y) { + const int accuracy = kDefaultAccuracy; + // This approach is safer than left shift, round, then right shift + return ((unsigned)y + (SK_Fixed1 >> (accuracy + 1))) >> (16 - accuracy) << (16 - accuracy); + } + + // Update fX, fY of this edge so fY = y + inline void goY(SkFixed y) { + if (y == fY + SK_Fixed1) { + fX = fX + fDX; + fY = y; + } else if (y != fY) { + // Drop lower digits as our alpha only has 8 bits + // (fDX and y - fUpperY may be greater than SK_Fixed1) + fX = fUpperX + SkFixedMul(fDX, y - fUpperY); + fY = y; + } + } + + inline void goY(SkFixed y, int yShift) { + SkASSERT(yShift >= 0 && yShift <= kDefaultAccuracy); + SkASSERT(fDX == 0 || y - fY == SK_Fixed1 >> yShift); + fY = y; + fX += fDX >> yShift; + } + + inline void saveXY(SkFixed x, SkFixed y, SkFixed dY) { + fSavedX = x; + fSavedY = y; + fSavedDY = dY; + } + + bool setLine(const SkPoint& p0, const SkPoint& p1); + bool updateLine(SkFixed ax, SkFixed ay, SkFixed bx, SkFixed by, SkFixed slope); + + // return true if we're NOT done with this edge + bool update(SkFixed last_y, bool sortY = true); + +#ifdef SK_DEBUG + void dump() const { + SkDebugf("edge: upperY:%d lowerY:%d y:%g x:%g dx:%g w:%d\n", + fUpperY, fLowerY, SkFixedToFloat(fY), SkFixedToFloat(fX), + SkFixedToFloat(fDX), fWinding); + } + + void validate() const { + SkASSERT(fPrev && fNext); + SkASSERT(fPrev->fNext == this); + SkASSERT(fNext->fPrev == this); + + SkASSERT(fUpperY < fLowerY); + SkASSERT(SkAbs32(fWinding) == 1); + } +#endif +}; + +struct SkAnalyticQuadraticEdge : public SkAnalyticEdge { + SkQuadraticEdge fQEdge; + + // snap y to integer points in the middle of the curve to accelerate AAA path filling + SkFixed fSnappedX, fSnappedY; + + bool setQuadratic(const SkPoint pts[3]); + bool updateQuadratic(); + inline void keepContinuous() { + // We use fX as the starting x to ensure the continuouty. + // Without it, we may break the sorted edge list. + SkASSERT(SkAbs32(fX - SkFixedMul(fY - fSnappedY, fDX) - fSnappedX) < SK_Fixed1); + SkASSERT(SkAbs32(fY - fSnappedY) < SK_Fixed1); // This may differ due to smooth jump + fSnappedX = fX; + fSnappedY = fY; + } +}; + +struct SkAnalyticCubicEdge : public SkAnalyticEdge { + SkCubicEdge fCEdge; + + SkFixed fSnappedY; // to make sure that y is increasing with smooth jump and snapping + + bool setCubic(const SkPoint pts[4], bool sortY = true); + bool updateCubic(bool sortY = true); + inline void keepContinuous() { + SkASSERT(SkAbs32(fX - SkFixedMul(fDX, fY - SnapY(fCEdge.fCy)) - fCEdge.fCx) < SK_Fixed1); + fCEdge.fCx = fX; + fSnappedY = fY; + } +}; + +struct SkBezier { + int fCount; // 2 line, 3 quad, 4 cubic + SkPoint fP0; + SkPoint fP1; + + // See if left shift, covert to SkFDot6, and round has the same top and bottom y. + // If so, the edge will be empty. + static inline bool IsEmpty(SkScalar y0, SkScalar y1, int shift = 2) { +#ifdef SK_RASTERIZE_EVEN_ROUNDING + return SkScalarRoundToFDot6(y0, shift) == SkScalarRoundToFDot6(y1, shift); +#else + SkScalar scale = (1 << (shift + 6)); + return SkFDot6Round(int(y0 * scale)) == SkFDot6Round(int(y1 * scale)); +#endif + } +}; + +struct SkLine : public SkBezier { + bool set(const SkPoint pts[2]){ + if (IsEmpty(pts[0].fY, pts[1].fY)) { + return false; + } + fCount = 2; + fP0 = pts[0]; + fP1 = pts[1]; + return true; + } +}; + +struct SkQuad : public SkBezier { + SkPoint fP2; + + bool set(const SkPoint pts[3]){ + if (IsEmpty(pts[0].fY, pts[2].fY)) { + return false; + } + fCount = 3; + fP0 = pts[0]; + fP1 = pts[1]; + fP2 = pts[2]; + return true; + } +}; + +struct SkCubic : public SkBezier { + SkPoint fP2; + SkPoint fP3; + + bool set(const SkPoint pts[4]){ + // We do not chop at y extrema for cubics so pts[0], pts[1], pts[2], pts[3] may not be + // monotonic. Therefore, we have to check the emptiness for all three pairs, instead of just + // checking IsEmpty(pts[0].fY, pts[3].fY). + if (IsEmpty(pts[0].fY, pts[1].fY) && IsEmpty(pts[1].fY, pts[2].fY) && + IsEmpty(pts[2].fY, pts[3].fY)) { + return false; + } + fCount = 4; + fP0 = pts[0]; + fP1 = pts[1]; + fP2 = pts[2]; + fP3 = pts[3]; + return true; + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkAnnotation.cpp b/gfx/skia/skia/src/core/SkAnnotation.cpp new file mode 100644 index 0000000000..43b3170f2b --- /dev/null +++ b/gfx/skia/skia/src/core/SkAnnotation.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkAnnotation.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" +#include "src/core/SkAnnotationKeys.h" + +const char* SkAnnotationKeys::URL_Key() { + return "SkAnnotationKey_URL"; +}; + +const char* SkAnnotationKeys::Define_Named_Dest_Key() { + return "SkAnnotationKey_Define_Named_Dest"; +}; + +const char* SkAnnotationKeys::Link_Named_Dest_Key() { + return "SkAnnotationKey_Link_Named_Dest"; +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkAnnotateRectWithURL(SkCanvas* canvas, const SkRect& rect, SkData* value) { + if (nullptr == value) { + return; + } + canvas->drawAnnotation(rect, SkAnnotationKeys::URL_Key(), value); +} + +void SkAnnotateNamedDestination(SkCanvas* canvas, const SkPoint& point, SkData* name) { + if (nullptr == name) { + return; + } + const SkRect rect = SkRect::MakeXYWH(point.x(), point.y(), 0, 0); + canvas->drawAnnotation(rect, SkAnnotationKeys::Define_Named_Dest_Key(), name); +} + +void SkAnnotateLinkToDestination(SkCanvas* canvas, const SkRect& rect, SkData* name) { + if (nullptr == name) { + return; + } + canvas->drawAnnotation(rect, SkAnnotationKeys::Link_Named_Dest_Key(), name); +} diff --git a/gfx/skia/skia/src/core/SkAnnotationKeys.h b/gfx/skia/skia/src/core/SkAnnotationKeys.h new file mode 100644 index 0000000000..90fdc6d30a --- /dev/null +++ b/gfx/skia/skia/src/core/SkAnnotationKeys.h @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAnnotationKeys_DEFINED +#define SkAnnotationKeys_DEFINED + +#include "include/core/SkTypes.h" + +class SkAnnotationKeys { +public: + /** + * Returns the canonical key whose payload is a URL + */ + static const char* URL_Key(); + + /** + * Returns the canonical key whose payload is the name of a destination to + * be defined. + */ + static const char* Define_Named_Dest_Key(); + + /** + * Returns the canonical key whose payload is the name of a destination to + * be linked to. + */ + static const char* Link_Named_Dest_Key(); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkAntiRun.h b/gfx/skia/skia/src/core/SkAntiRun.h new file mode 100644 index 0000000000..70ef751a96 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAntiRun.h @@ -0,0 +1,196 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAntiRun_DEFINED +#define SkAntiRun_DEFINED + +#include "include/private/SkTo.h" +#include "src/core/SkBlitter.h" + +/** Sparse array of run-length-encoded alpha (supersampling coverage) values. + Sparseness allows us to independently compose several paths into the + same SkAlphaRuns buffer. +*/ + +class SkAlphaRuns { +public: + int16_t* fRuns; + uint8_t* fAlpha; + + // Return 0-255 given 0-256 + static inline SkAlpha CatchOverflow(int alpha) { + SkASSERT(alpha >= 0 && alpha <= 256); + return alpha - (alpha >> 8); + } + + /// Returns true if the scanline contains only a single run, + /// of alpha value 0. + bool empty() const { + SkASSERT(fRuns[0] > 0); + return fAlpha[0] == 0 && fRuns[fRuns[0]] == 0; + } + + /// Reinitialize for a new scanline. + void reset(int width); + + /** + * Insert into the buffer a run starting at (x-offsetX): + * if startAlpha > 0 + * one pixel with value += startAlpha, + * max 255 + * if middleCount > 0 + * middleCount pixels with value += maxValue + * if stopAlpha > 0 + * one pixel with value += stopAlpha + * Returns the offsetX value that should be passed on the next call, + * assuming we're on the same scanline. If the caller is switching + * scanlines, then offsetX should be 0 when this is called. + */ + SK_ALWAYS_INLINE int add(int x, U8CPU startAlpha, int middleCount, U8CPU stopAlpha, + U8CPU maxValue, int offsetX) { + SkASSERT(middleCount >= 0); + SkASSERT(x >= 0 && x + (startAlpha != 0) + middleCount + (stopAlpha != 0) <= fWidth); + + SkASSERT(fRuns[offsetX] >= 0); + + int16_t* runs = fRuns + offsetX; + uint8_t* alpha = fAlpha + offsetX; + uint8_t* lastAlpha = alpha; + x -= offsetX; + + if (startAlpha) { + SkAlphaRuns::Break(runs, alpha, x, 1); + /* I should be able to just add alpha[x] + startAlpha. + However, if the trailing edge of the previous span and the leading + edge of the current span round to the same super-sampled x value, + I might overflow to 256 with this add, hence the funny subtract (crud). + */ + unsigned tmp = alpha[x] + startAlpha; + SkASSERT(tmp <= 256); + alpha[x] = SkToU8(tmp - (tmp >> 8)); // was (tmp >> 7), but that seems wrong if we're trying to catch 256 + + runs += x + 1; + alpha += x + 1; + x = 0; + SkDEBUGCODE(this->validate();) + } + + if (middleCount) { + SkAlphaRuns::Break(runs, alpha, x, middleCount); + alpha += x; + runs += x; + x = 0; + do { + alpha[0] = SkToU8(CatchOverflow(alpha[0] + maxValue)); + int n = runs[0]; + SkASSERT(n <= middleCount); + alpha += n; + runs += n; + middleCount -= n; + } while (middleCount > 0); + SkDEBUGCODE(this->validate();) + lastAlpha = alpha; + } + + if (stopAlpha) { + SkAlphaRuns::Break(runs, alpha, x, 1); + alpha += x; + alpha[0] = SkToU8(alpha[0] + stopAlpha); + SkDEBUGCODE(this->validate();) + lastAlpha = alpha; + } + + return SkToS32(lastAlpha - fAlpha); // new offsetX + } + + SkDEBUGCODE(void assertValid(int y, int maxStep) const;) + SkDEBUGCODE(void dump() const;) + + /** + * Break the runs in the buffer at offsets x and x+count, properly + * updating the runs to the right and left. + * i.e. from the state AAAABBBB, run-length encoded as A4B4, + * Break(..., 2, 5) would produce AAAABBBB rle as A2A2B3B1. + * Allows add() to sum another run to some of the new sub-runs. + * i.e. adding ..CCCCC. would produce AADDEEEB, rle as A2D2E3B1. + */ + static void Break(int16_t runs[], uint8_t alpha[], int x, int count) { + SkASSERT(count > 0 && x >= 0); + + // SkAlphaRuns::BreakAt(runs, alpha, x); + // SkAlphaRuns::BreakAt(&runs[x], &alpha[x], count); + + int16_t* next_runs = runs + x; + uint8_t* next_alpha = alpha + x; + + while (x > 0) { + int n = runs[0]; + SkASSERT(n > 0); + + if (x < n) { + alpha[x] = alpha[0]; + runs[0] = SkToS16(x); + runs[x] = SkToS16(n - x); + break; + } + runs += n; + alpha += n; + x -= n; + } + + runs = next_runs; + alpha = next_alpha; + x = count; + + for (;;) { + int n = runs[0]; + SkASSERT(n > 0); + + if (x < n) { + alpha[x] = alpha[0]; + runs[0] = SkToS16(x); + runs[x] = SkToS16(n - x); + break; + } + x -= n; + if (x <= 0) { + break; + } + runs += n; + alpha += n; + } + } + + /** + * Cut (at offset x in the buffer) a run into two shorter runs with + * matching alpha values. + * Used by the RectClipBlitter to trim a RLE encoding to match the + * clipping rectangle. + */ + static void BreakAt(int16_t runs[], uint8_t alpha[], int x) { + while (x > 0) { + int n = runs[0]; + SkASSERT(n > 0); + + if (x < n) { + alpha[x] = alpha[0]; + runs[0] = SkToS16(x); + runs[x] = SkToS16(n - x); + break; + } + runs += n; + alpha += n; + x -= n; + } + } + +private: + SkDEBUGCODE(int fWidth;) + SkDEBUGCODE(void validate() const;) +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkArenaAlloc.cpp b/gfx/skia/skia/src/core/SkArenaAlloc.cpp new file mode 100644 index 0000000000..fc67c2d58e --- /dev/null +++ b/gfx/skia/skia/src/core/SkArenaAlloc.cpp @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkArenaAlloc.h" +#include +#include + +static char* end_chain(char*) { return nullptr; } + +static uint32_t first_allocated_block(uint32_t blockSize, uint32_t firstHeapAllocation) { + return firstHeapAllocation > 0 ? firstHeapAllocation : + blockSize > 0 ? blockSize : 1024; +} + +SkArenaAlloc::SkArenaAlloc(char* block, size_t size, size_t firstHeapAllocation) + : fDtorCursor {block} + , fCursor {block} + , fEnd {block + ToU32(size)} + , fFirstBlock {block} + , fFirstSize {ToU32(size)} + , fFirstHeapAllocationSize {first_allocated_block(ToU32(size), ToU32(firstHeapAllocation))} +{ + if (size < sizeof(Footer)) { + fEnd = fCursor = fDtorCursor = nullptr; + } + + if (fCursor != nullptr) { + this->installFooter(end_chain, 0); + } +} + +SkArenaAlloc::~SkArenaAlloc() { + RunDtorsOnBlock(fDtorCursor); +} + +void SkArenaAlloc::reset() { + this->~SkArenaAlloc(); + new (this) SkArenaAlloc{fFirstBlock, fFirstSize, fFirstHeapAllocationSize}; +} + +void SkArenaAlloc::installFooter(FooterAction* action, uint32_t padding) { + assert(padding < 64); + int64_t actionInt = (int64_t)(intptr_t)action; + + // The top 14 bits should be either all 0s or all 1s. Check this. + assert((actionInt << 6) >> 6 == actionInt); + Footer encodedFooter = (actionInt << 6) | padding; + memmove(fCursor, &encodedFooter, sizeof(Footer)); + fCursor += sizeof(Footer); + fDtorCursor = fCursor; +} + +void SkArenaAlloc::installPtrFooter(FooterAction* action, char* ptr, uint32_t padding) { + memmove(fCursor, &ptr, sizeof(char*)); + fCursor += sizeof(char*); + this->installFooter(action, padding); +} + +char* SkArenaAlloc::SkipPod(char* footerEnd) { + char* objEnd = footerEnd - (sizeof(Footer) + sizeof(int32_t)); + int32_t skip; + memmove(&skip, objEnd, sizeof(int32_t)); + return objEnd - skip; +} + +void SkArenaAlloc::RunDtorsOnBlock(char* footerEnd) { + while (footerEnd != nullptr) { + Footer footer; + memcpy(&footer, footerEnd - sizeof(Footer), sizeof(Footer)); + + FooterAction* action = (FooterAction*)(footer >> 6); + ptrdiff_t padding = footer & 63; + + footerEnd = action(footerEnd) - padding; + } +} + +char* SkArenaAlloc::NextBlock(char* footerEnd) { + char* objEnd = footerEnd - (sizeof(Footer) + sizeof(char*)); + char* next; + memmove(&next, objEnd, sizeof(char*)); + RunDtorsOnBlock(next); + delete [] objEnd; + return nullptr; +} + +void SkArenaAlloc::installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding) { + memmove(fCursor, &value, sizeof(uint32_t)); + fCursor += sizeof(uint32_t); + this->installFooter(action, padding); +} + +void SkArenaAlloc::ensureSpace(uint32_t size, uint32_t alignment) { + constexpr uint32_t headerSize = sizeof(Footer) + sizeof(ptrdiff_t); + // The chrome c++ library we use does not define std::max_align_t. + // This must be conservative to add the right amount of extra memory to handle the alignment + // padding. + constexpr uint32_t alignof_max_align_t = 8; + constexpr uint32_t maxSize = std::numeric_limits::max(); + constexpr uint32_t overhead = headerSize + sizeof(Footer); + AssertRelease(size <= maxSize - overhead); + uint32_t objSizeAndOverhead = size + overhead; + if (alignment > alignof_max_align_t) { + uint32_t alignmentOverhead = alignment - 1; + AssertRelease(objSizeAndOverhead <= maxSize - alignmentOverhead); + objSizeAndOverhead += alignmentOverhead; + } + + uint32_t minAllocationSize; + if (fFirstHeapAllocationSize <= maxSize / fFib0) { + minAllocationSize = fFirstHeapAllocationSize * fFib0; + fFib0 += fFib1; + std::swap(fFib0, fFib1); + } else { + minAllocationSize = maxSize; + } + uint32_t allocationSize = std::max(objSizeAndOverhead, minAllocationSize); + + // Round up to a nice size. If > 32K align to 4K boundary else up to max_align_t. The > 32K + // heuristic is from the JEMalloc behavior. + { + uint32_t mask = allocationSize > (1 << 15) ? (1 << 12) - 1 : 16 - 1; + AssertRelease(allocationSize <= maxSize - mask); + allocationSize = (allocationSize + mask) & ~mask; + } + + char* newBlock = new char[allocationSize]; + + auto previousDtor = fDtorCursor; + fCursor = newBlock; + fDtorCursor = newBlock; + fEnd = fCursor + allocationSize; + this->installPtrFooter(NextBlock, previousDtor, 0); +} + +char* SkArenaAlloc::allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment) { + uintptr_t mask = alignment - 1; + +restart: + uint32_t skipOverhead = 0; + bool needsSkipFooter = fCursor != fDtorCursor; + if (needsSkipFooter) { + skipOverhead = sizeof(Footer) + sizeof(uint32_t); + } + char* objStart = (char*)((uintptr_t)(fCursor + skipOverhead + mask) & ~mask); + uint32_t totalSize = sizeIncludingFooter + skipOverhead; + + if ((ptrdiff_t)totalSize > fEnd - objStart) { + this->ensureSpace(totalSize, alignment); + goto restart; + } + + AssertRelease((ptrdiff_t)totalSize <= fEnd - objStart); + + // Install a skip footer if needed, thus terminating a run of POD data. The calling code is + // responsible for installing the footer after the object. + if (needsSkipFooter) { + this->installUint32Footer(SkipPod, ToU32(fCursor - fDtorCursor), 0); + } + + return objStart; +} diff --git a/gfx/skia/skia/src/core/SkArenaAlloc.h b/gfx/skia/skia/src/core/SkArenaAlloc.h new file mode 100644 index 0000000000..bb8d05f98e --- /dev/null +++ b/gfx/skia/skia/src/core/SkArenaAlloc.h @@ -0,0 +1,240 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkArenaAlloc_DEFINED +#define SkArenaAlloc_DEFINED + +#include "include/private/SkTFitsIn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed +// to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an +// (optional) user-provided block of memory, and when that's exhausted it allocates on the heap, +// starting with an allocation of firstHeapAllocation bytes. If your data (plus a small overhead) +// fits in the user-provided block, SkArenaAlloc never uses the heap, and if it fits in +// firstHeapAllocation bytes, it'll use the heap only once. If 0 is specified for +// firstHeapAllocation, then blockSize is used unless that too is 0, then 1024 is used. +// +// Examples: +// +// char block[mostCasesSize]; +// SkArenaAlloc arena(block, mostCasesSize); +// +// If mostCasesSize is too large for the stack, you can use the following pattern. +// +// std::unique_ptr block{new char[mostCasesSize]}; +// SkArenaAlloc arena(block.get(), mostCasesSize, almostAllCasesSize); +// +// If the program only sometimes allocates memory, use the following pattern. +// +// SkArenaAlloc arena(nullptr, 0, almostAllCasesSize); +// +// The storage does not necessarily need to be on the stack. Embedding the storage in a class also +// works. +// +// class Foo { +// char storage[mostCasesSize]; +// SkArenaAlloc arena (storage, mostCasesSize); +// }; +// +// In addition, the system is optimized to handle POD data including arrays of PODs (where +// POD is really data with no destructors). For POD data it has zero overhead per item, and a +// typical per block overhead of 8 bytes. For non-POD objects there is a per item overhead of 4 +// bytes. For arrays of non-POD objects there is a per array overhead of typically 8 bytes. There +// is an addition overhead when switching from POD data to non-POD data of typically 8 bytes. +// +// If additional blocks are needed they are increased exponentially. This strategy bounds the +// recursion of the RunDtorsOnBlock to be limited to O(log size-of-memory). Block size grow using +// the Fibonacci sequence which means that for 2^32 memory there are 48 allocations, and for 2^48 +// there are 71 allocations. +class SkArenaAlloc { +public: + SkArenaAlloc(char* block, size_t blockSize, size_t firstHeapAllocation); + + explicit SkArenaAlloc(size_t firstHeapAllocation) + : SkArenaAlloc(nullptr, 0, firstHeapAllocation) + {} + + ~SkArenaAlloc(); + + template + T* make(Args&&... args) { + uint32_t size = ToU32(sizeof(T)); + uint32_t alignment = ToU32(alignof(T)); + char* objStart; + if (std::is_trivially_destructible::value) { + objStart = this->allocObject(size, alignment); + fCursor = objStart + size; + } else { + objStart = this->allocObjectWithFooter(size + sizeof(Footer), alignment); + // Can never be UB because max value is alignof(T). + uint32_t padding = ToU32(objStart - fCursor); + + // Advance to end of object to install footer. + fCursor = objStart + size; + FooterAction* releaser = [](char* objEnd) { + char* objStart = objEnd - (sizeof(T) + sizeof(Footer)); + ((T*)objStart)->~T(); + return objStart; + }; + this->installFooter(releaser, padding); + } + + // This must be last to make objects with nested use of this allocator work. + return new(objStart) T(std::forward(args)...); + } + + template + T* makeArrayDefault(size_t count) { + AssertRelease(SkTFitsIn(count)); + uint32_t safeCount = ToU32(count); + T* array = (T*)this->commonArrayAlloc(safeCount); + + // If T is primitive then no initialization takes place. + for (size_t i = 0; i < safeCount; i++) { + new (&array[i]) T; + } + return array; + } + + template + T* makeArray(size_t count) { + AssertRelease(SkTFitsIn(count)); + uint32_t safeCount = ToU32(count); + T* array = (T*)this->commonArrayAlloc(safeCount); + + // If T is primitive then the memory is initialized. For example, an array of chars will + // be zeroed. + for (size_t i = 0; i < safeCount; i++) { + new (&array[i]) T(); + } + return array; + } + + // Only use makeBytesAlignedTo if none of the typed variants are impractical to use. + void* makeBytesAlignedTo(size_t size, size_t align) { + AssertRelease(SkTFitsIn(size)); + auto objStart = this->allocObject(ToU32(size), ToU32(align)); + fCursor = objStart + size; + return objStart; + } + + // Destroy all allocated objects, free any heap allocations. + void reset(); + +private: + static void AssertRelease(bool cond) { if (!cond) { ::abort(); } } + static uint32_t ToU32(size_t v) { + assert(SkTFitsIn(v)); + return (uint32_t)v; + } + + using Footer = int64_t; + using FooterAction = char* (char*); + + static char* SkipPod(char* footerEnd); + static void RunDtorsOnBlock(char* footerEnd); + static char* NextBlock(char* footerEnd); + + void installFooter(FooterAction* releaser, uint32_t padding); + void installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding); + void installPtrFooter(FooterAction* action, char* ptr, uint32_t padding); + + void ensureSpace(uint32_t size, uint32_t alignment); + + char* allocObject(uint32_t size, uint32_t alignment) { + uintptr_t mask = alignment - 1; + uintptr_t alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; + uintptr_t totalSize = size + alignedOffset; + AssertRelease(totalSize >= size); + if (totalSize > static_cast(fEnd - fCursor)) { + this->ensureSpace(size, alignment); + alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; + } + return fCursor + alignedOffset; + } + + char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment); + + template + char* commonArrayAlloc(uint32_t count) { + char* objStart; + AssertRelease(count <= std::numeric_limits::max() / sizeof(T)); + uint32_t arraySize = ToU32(count * sizeof(T)); + uint32_t alignment = ToU32(alignof(T)); + + if (std::is_trivially_destructible::value) { + objStart = this->allocObject(arraySize, alignment); + fCursor = objStart + arraySize; + } else { + constexpr uint32_t overhead = sizeof(Footer) + sizeof(uint32_t); + AssertRelease(arraySize <= std::numeric_limits::max() - overhead); + uint32_t totalSize = arraySize + overhead; + objStart = this->allocObjectWithFooter(totalSize, alignment); + + // Can never be UB because max value is alignof(T). + uint32_t padding = ToU32(objStart - fCursor); + + // Advance to end of array to install footer.? + fCursor = objStart + arraySize; + this->installUint32Footer( + [](char* footerEnd) { + char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t)); + uint32_t count; + memmove(&count, objEnd, sizeof(uint32_t)); + char* objStart = objEnd - count * sizeof(T); + T* array = (T*) objStart; + for (uint32_t i = 0; i < count; i++) { + array[i].~T(); + } + return objStart; + }, + ToU32(count), + padding); + } + + return objStart; + } + + char* fDtorCursor; + char* fCursor; + char* fEnd; + char* const fFirstBlock; + const uint32_t fFirstSize; + const uint32_t fFirstHeapAllocationSize; + + // Use the Fibonacci sequence as the growth factor for block size. The size of the block + // allocated is fFib0 * fFirstHeapAllocationSize. Using 2 ^ n * fFirstHeapAllocationSize + // had too much slop for Android. + uint32_t fFib0 {1}, fFib1 {1}; +}; + +// Helper for defining allocators with inline/reserved storage. +// For argument declarations, stick to the base type (SkArenaAlloc). +template +class SkSTArenaAlloc : public SkArenaAlloc { +public: + explicit SkSTArenaAlloc(size_t firstHeapAllocation = InlineStorageSize) + : INHERITED(fInlineStorage, InlineStorageSize, firstHeapAllocation) {} + +private: + char fInlineStorage[InlineStorageSize]; + + using INHERITED = SkArenaAlloc; +}; + +#endif // SkArenaAlloc_DEFINED diff --git a/gfx/skia/skia/src/core/SkArenaAllocList.h b/gfx/skia/skia/src/core/SkArenaAllocList.h new file mode 100644 index 0000000000..c544b863d0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkArenaAllocList.h @@ -0,0 +1,80 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkArenaAllocList_DEFINED +#define SkArenaAllocList_DEFINED + +#include "include/core/SkTypes.h" +#include "src/core/SkArenaAlloc.h" + +/** + * A singly linked list of Ts stored in a SkArenaAlloc. The arena rather than the list owns + * the elements. This supports forward iteration and range based for loops. + */ +template +class SkArenaAllocList { +private: + struct Node; + +public: + SkArenaAllocList() = default; + + void reset() { fHead = fTail = nullptr; } + + template + inline T& append(SkArenaAlloc* arena, Args... args); + + class Iter { + public: + Iter() = default; + inline Iter& operator++(); + T& operator*() const { return fCurr->fT; } + T* operator->() const { return &fCurr->fT; } + bool operator==(const Iter& that) const { return fCurr == that.fCurr; } + bool operator!=(const Iter& that) const { return !(*this == that); } + + private: + friend class SkArenaAllocList; + explicit Iter(Node* node) : fCurr(node) {} + Node* fCurr = nullptr; + }; + + Iter begin() { return Iter(fHead); } + Iter end() { return Iter(); } + Iter tail() { return Iter(fTail); } + +private: + struct Node { + template + Node(Args... args) : fT(std::forward(args)...) {} + T fT; + Node* fNext = nullptr; + }; + Node* fHead = nullptr; + Node* fTail = nullptr; +}; + +template +template +T& SkArenaAllocList::append(SkArenaAlloc* arena, Args... args) { + SkASSERT(!fHead == !fTail); + auto* n = arena->make(std::forward(args)...); + if (!fTail) { + fHead = fTail = n; + } else { + fTail = fTail->fNext = n; + } + return fTail->fT; +} + +template +typename SkArenaAllocList::Iter& SkArenaAllocList::Iter::operator++() { + fCurr = fCurr->fNext; + return *this; +} + +#endif diff --git a/gfx/skia/skia/src/core/SkAutoBlitterChoose.h b/gfx/skia/skia/src/core/SkAutoBlitterChoose.h new file mode 100644 index 0000000000..9098c7b2bd --- /dev/null +++ b/gfx/skia/skia/src/core/SkAutoBlitterChoose.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAutoBlitterChoose_DEFINED +#define SkAutoBlitterChoose_DEFINED + +#include "include/private/SkMacros.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkDraw.h" + +class SkMatrix; +class SkPaint; +class SkPixmap; + +class SkAutoBlitterChoose : SkNoncopyable { +public: + SkAutoBlitterChoose() {} + SkAutoBlitterChoose(const SkDraw& draw, const SkMatrix* matrix, const SkPaint& paint, + bool drawCoverage = false) { + this->choose(draw, matrix, paint, drawCoverage); + } + + SkBlitter* operator->() { return fBlitter; } + SkBlitter* get() const { return fBlitter; } + + SkBlitter* choose(const SkDraw& draw, const SkMatrix* matrix, const SkPaint& paint, + bool drawCoverage = false) { + SkASSERT(!fBlitter); + if (!matrix) { + matrix = draw.fMatrix; + } + fBlitter = SkBlitter::Choose(draw.fDst, *matrix, paint, &fAlloc, drawCoverage); + + if (draw.fCoverage) { + // hmm, why can't choose ignore the paint if drawCoverage is true? + SkBlitter* coverageBlitter = SkBlitter::Choose(*draw.fCoverage, *matrix, SkPaint(), + &fAlloc, true); + fBlitter = fAlloc.make(fBlitter, coverageBlitter); + } + return fBlitter; + } + +private: + // Owned by fAlloc, which will handle the delete. + SkBlitter* fBlitter = nullptr; + + SkSTArenaAlloc fAlloc; +}; +#define SkAutoBlitterChoose(...) SK_REQUIRE_LOCAL_VAR(SkAutoBlitterChoose) + +#endif diff --git a/gfx/skia/skia/src/core/SkAutoMalloc.h b/gfx/skia/skia/src/core/SkAutoMalloc.h new file mode 100644 index 0000000000..4be8ddb03e --- /dev/null +++ b/gfx/skia/skia/src/core/SkAutoMalloc.h @@ -0,0 +1,177 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAutoMalloc_DEFINED +#define SkAutoMalloc_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMacros.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkNoncopyable.h" + +#include + +/** + * Manage an allocated block of heap memory. This object is the sole manager of + * the lifetime of the block, so the caller must not call sk_free() or delete + * on the block, unless release() was called. + */ +class SkAutoMalloc : SkNoncopyable { +public: + explicit SkAutoMalloc(size_t size = 0) + : fPtr(size ? sk_malloc_throw(size) : nullptr), fSize(size) {} + + /** + * Passed to reset to specify what happens if the requested size is smaller + * than the current size (and the current block was dynamically allocated). + */ + enum OnShrink { + /** + * If the requested size is smaller than the current size, and the + * current block is dynamically allocated, free the old block and + * malloc a new block of the smaller size. + */ + kAlloc_OnShrink, + + /** + * If the requested size is smaller than the current size, and the + * current block is dynamically allocated, just return the old + * block. + */ + kReuse_OnShrink + }; + + /** + * Reallocates the block to a new size. The ptr may or may not change. + */ + void* reset(size_t size = 0, OnShrink shrink = kAlloc_OnShrink) { + if (size != fSize && (size > fSize || kReuse_OnShrink != shrink)) { + fPtr.reset(size ? sk_malloc_throw(size) : nullptr); + fSize = size; + } + return fPtr.get(); + } + + /** + * Return the allocated block. + */ + void* get() { return fPtr.get(); } + const void* get() const { return fPtr.get(); } + + /** Transfer ownership of the current ptr to the caller, setting the + internal reference to null. Note the caller is reponsible for calling + sk_free on the returned address. + */ + void* release() { + fSize = 0; + return fPtr.release(); + } + +private: + struct WrapFree { + void operator()(void* p) { sk_free(p); } + }; + std::unique_ptr fPtr; + size_t fSize; // can be larger than the requested size (see kReuse) +}; +#define SkAutoMalloc(...) SK_REQUIRE_LOCAL_VAR(SkAutoMalloc) + +/** + * Manage an allocated block of memory. If the requested size is <= kSizeRequested (or slightly + * more), then the allocation will come from the stack rather than the heap. This object is the + * sole manager of the lifetime of the block, so the caller must not call sk_free() or delete on + * the block. + */ +template class SkAutoSMalloc : SkNoncopyable { +public: + /** + * Creates initially empty storage. get() returns a ptr, but it is to a zero-byte allocation. + * Must call reset(size) to return an allocated block. + */ + SkAutoSMalloc() { + fPtr = fStorage; + fSize = kSize; + } + + /** + * Allocate a block of the specified size. If size <= kSizeRequested (or slightly more), then + * the allocation will come from the stack, otherwise it will be dynamically allocated. + */ + explicit SkAutoSMalloc(size_t size) { + fPtr = fStorage; + fSize = kSize; + this->reset(size); + } + + /** + * Free the allocated block (if any). If the block was small enough to have been allocated on + * the stack, then this does nothing. + */ + ~SkAutoSMalloc() { + if (fPtr != (void*)fStorage) { + sk_free(fPtr); + } + } + + /** + * Return the allocated block. May return non-null even if the block is of zero size. Since + * this may be on the stack or dynamically allocated, the caller must not call sk_free() on it, + * but must rely on SkAutoSMalloc to manage it. + */ + void* get() const { return fPtr; } + + /** + * Return a new block of the requested size, freeing (as necessary) any previously allocated + * block. As with the constructor, if size <= kSizeRequested (or slightly more) then the return + * block may be allocated locally, rather than from the heap. + */ + void* reset(size_t size, + SkAutoMalloc::OnShrink shrink = SkAutoMalloc::kAlloc_OnShrink, + bool* didChangeAlloc = nullptr) { + size = (size < kSize) ? kSize : size; + bool alloc = size != fSize && (SkAutoMalloc::kAlloc_OnShrink == shrink || size > fSize); + if (didChangeAlloc) { + *didChangeAlloc = alloc; + } + if (alloc) { + if (fPtr != (void*)fStorage) { + sk_free(fPtr); + } + + if (size == kSize) { + SkASSERT(fPtr != fStorage); // otherwise we lied when setting didChangeAlloc. + fPtr = fStorage; + } else { + fPtr = sk_malloc_throw(size); + } + + fSize = size; + } + SkASSERT(fSize >= size && fSize >= kSize); + SkASSERT((fPtr == fStorage) || fSize > kSize); + return fPtr; + } + +private: + // Align up to 32 bits. + static const size_t kSizeAlign4 = SkAlign4(kSizeRequested); +#if defined(SK_BUILD_FOR_GOOGLE3) + // Stack frame size is limited for SK_BUILD_FOR_GOOGLE3. 4k is less than the actual max, but some functions + // have multiple large stack allocations. + static const size_t kMaxBytes = 4 * 1024; + static const size_t kSize = kSizeRequested > kMaxBytes ? kMaxBytes : kSizeAlign4; +#else + static const size_t kSize = kSizeAlign4; +#endif + + void* fPtr; + size_t fSize; // can be larger than the requested size (see kReuse) + uint32_t fStorage[kSize >> 2]; +}; +// Can't guard the constructor because it's a template class. + +#endif diff --git a/gfx/skia/skia/src/core/SkAutoPixmapStorage.cpp b/gfx/skia/skia/src/core/SkAutoPixmapStorage.cpp new file mode 100644 index 0000000000..0a63385e55 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAutoPixmapStorage.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "src/core/SkAutoPixmapStorage.h" + +SkAutoPixmapStorage::SkAutoPixmapStorage() : fStorage(nullptr) {} + +SkAutoPixmapStorage::~SkAutoPixmapStorage() { + this->freeStorage(); +} + +SkAutoPixmapStorage& SkAutoPixmapStorage::operator=(SkAutoPixmapStorage&& other) { + this->fStorage = other.fStorage; + this->INHERITED::reset(other.info(), this->fStorage, other.rowBytes()); + + other.fStorage = nullptr; + other.INHERITED::reset(); + + return *this; +} + +size_t SkAutoPixmapStorage::AllocSize(const SkImageInfo& info, size_t* rowBytes) { + size_t rb = info.minRowBytes(); + if (rowBytes) { + *rowBytes = rb; + } + return info.computeByteSize(rb); +} + +bool SkAutoPixmapStorage::tryAlloc(const SkImageInfo& info) { + this->freeStorage(); + + size_t rb; + size_t size = AllocSize(info, &rb); + if (SkImageInfo::ByteSizeOverflowed(size)) { + return false; + } + void* pixels = sk_malloc_canfail(size); + if (nullptr == pixels) { + return false; + } + this->reset(info, pixels, rb); + fStorage = pixels; + return true; +} + +void SkAutoPixmapStorage::alloc(const SkImageInfo& info) { + SkASSERT_RELEASE(this->tryAlloc(info)); +} + +sk_sp SkAutoPixmapStorage::detachPixelsAsData() { + if (!fStorage) { + return nullptr; + } + + sk_sp data = SkData::MakeFromMalloc(fStorage, this->computeByteSize()); + fStorage = nullptr; + this->INHERITED::reset(); + + return data; +} diff --git a/gfx/skia/skia/src/core/SkAutoPixmapStorage.h b/gfx/skia/skia/src/core/SkAutoPixmapStorage.h new file mode 100644 index 0000000000..494e0a93f9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkAutoPixmapStorage.h @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkAutoPixmapStorage_DEFINED +#define SkAutoPixmapStorage_DEFINED + +#include "include/core/SkPixmap.h" +#include "include/private/SkMalloc.h" + +class SkAutoPixmapStorage : public SkPixmap { +public: + SkAutoPixmapStorage(); + ~SkAutoPixmapStorage(); + + /** + * Leave the moved-from object in a free-but-valid state. + */ + SkAutoPixmapStorage& operator=(SkAutoPixmapStorage&& other); + + /** + * Try to allocate memory for the pixels needed to match the specified Info. On success + * return true and fill out the pixmap to point to that memory. The storage will be freed + * when this object is destroyed, or if another call to tryAlloc() or alloc() is made. + * + * On failure, return false and reset() the pixmap to empty. + */ + bool tryAlloc(const SkImageInfo&); + + /** + * Allocate memory for the pixels needed to match the specified Info and fill out the pixmap + * to point to that memory. The storage will be freed when this object is destroyed, + * or if another call to tryAlloc() or alloc() is made. + * + * If the memory cannot be allocated, calls SK_ABORT(). + */ + void alloc(const SkImageInfo&); + + /** + * Gets the size and optionally the rowBytes that would be allocated by SkAutoPixmapStorage if + * alloc/tryAlloc was called. + */ + static size_t AllocSize(const SkImageInfo& info, size_t* rowBytes); + + /** + * Returns an SkData object wrapping the allocated pixels memory, and resets the pixmap. + * If the storage hasn't been allocated, the result is NULL. + */ + sk_sp SK_WARN_UNUSED_RESULT detachPixelsAsData(); + + // We wrap these so we can clear our internal storage + + void reset() { + this->freeStorage(); + this->INHERITED::reset(); + } + void reset(const SkImageInfo& info, const void* addr, size_t rb) { + this->freeStorage(); + this->INHERITED::reset(info, addr, rb); + } + + bool SK_WARN_UNUSED_RESULT reset(const SkMask& mask) { + this->freeStorage(); + return this->INHERITED::reset(mask); + } + +private: + void* fStorage; + + void freeStorage() { + sk_free(fStorage); + fStorage = nullptr; + } + + typedef SkPixmap INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBBHFactory.cpp b/gfx/skia/skia/src/core/SkBBHFactory.cpp new file mode 100644 index 0000000000..b4b091852a --- /dev/null +++ b/gfx/skia/skia/src/core/SkBBHFactory.cpp @@ -0,0 +1,15 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBBHFactory.h" +#include "include/core/SkRect.h" +#include "include/core/SkScalar.h" +#include "src/core/SkRTree.h" + +SkBBoxHierarchy* SkRTreeFactory::operator()() const { + return new SkRTree; +} diff --git a/gfx/skia/skia/src/core/SkBBoxHierarchy.h b/gfx/skia/skia/src/core/SkBBoxHierarchy.h new file mode 100644 index 0000000000..741f9d77b2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBBoxHierarchy.h @@ -0,0 +1,43 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBBoxHierarchy_DEFINED +#define SkBBoxHierarchy_DEFINED + +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkTDArray.h" + +/** + * Interface for a spatial data structure that stores axis-aligned bounding + * boxes and allows efficient retrieval of intersections with query rectangles. + */ +class SkBBoxHierarchy : public SkRefCnt { +public: + SkBBoxHierarchy() {} + virtual ~SkBBoxHierarchy() {} + + /** + * Insert N bounding boxes into the hierarchy. + */ + virtual void insert(const SkRect[], int N) = 0; + + /** + * Populate results with the indices of bounding boxes interesecting that query. + */ + virtual void search(const SkRect& query, SkTDArray* results) const = 0; + + virtual size_t bytesUsed() const = 0; + + // Get the root bound. + virtual SkRect getRootBound() const = 0; + +private: + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBigPicture.cpp b/gfx/skia/skia/src/core/SkBigPicture.cpp new file mode 100644 index 0000000000..3ade96bf6a --- /dev/null +++ b/gfx/skia/skia/src/core/SkBigPicture.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBBoxHierarchy.h" +#include "src/core/SkBigPicture.h" +#include "src/core/SkPictureCommon.h" +#include "src/core/SkRecord.h" +#include "src/core/SkRecordDraw.h" +#include "src/core/SkTraceEvent.h" + +SkBigPicture::SkBigPicture(const SkRect& cull, + SkRecord* record, + SnapshotArray* drawablePicts, + SkBBoxHierarchy* bbh, + size_t approxBytesUsedBySubPictures) + : fCullRect(cull) + , fApproxBytesUsedBySubPictures(approxBytesUsedBySubPictures) + , fRecord(record) // Take ownership of caller's ref. + , fDrawablePicts(drawablePicts) // Take ownership. + , fBBH(bbh) // Take ownership of caller's ref. +{} + +void SkBigPicture::playback(SkCanvas* canvas, AbortCallback* callback) const { + SkASSERT(canvas); + + // If the query contains the whole picture, don't bother with the BBH. + const bool useBBH = !canvas->getLocalClipBounds().contains(this->cullRect()); + + SkRecordDraw(*fRecord, + canvas, + this->drawablePicts(), + nullptr, + this->drawableCount(), + useBBH ? fBBH.get() : nullptr, + callback); +} + +void SkBigPicture::partialPlayback(SkCanvas* canvas, + int start, + int stop, + const SkMatrix& initialCTM) const { + SkASSERT(canvas); + SkRecordPartialDraw(*fRecord, + canvas, + this->drawablePicts(), + this->drawableCount(), + start, + stop, + initialCTM); +} + +SkRect SkBigPicture::cullRect() const { return fCullRect; } +int SkBigPicture::approximateOpCount() const { return fRecord->count(); } +size_t SkBigPicture::approximateBytesUsed() const { + size_t bytes = sizeof(*this) + fRecord->bytesUsed() + fApproxBytesUsedBySubPictures; + if (fBBH) { bytes += fBBH->bytesUsed(); } + return bytes; +} + +int SkBigPicture::drawableCount() const { + return fDrawablePicts ? fDrawablePicts->count() : 0; +} + +SkPicture const* const* SkBigPicture::drawablePicts() const { + return fDrawablePicts ? fDrawablePicts->begin() : nullptr; +} + diff --git a/gfx/skia/skia/src/core/SkBigPicture.h b/gfx/skia/skia/src/core/SkBigPicture.h new file mode 100644 index 0000000000..a4d71aa463 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBigPicture.h @@ -0,0 +1,71 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBigPicture_DEFINED +#define SkBigPicture_DEFINED + +#include "include/core/SkPicture.h" +#include "include/core/SkRect.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTemplates.h" + +class SkBBoxHierarchy; +class SkMatrix; +class SkRecord; + +// An implementation of SkPicture supporting an arbitrary number of drawing commands. +class SkBigPicture final : public SkPicture { +public: + // An array of refcounted const SkPicture pointers. + class SnapshotArray : ::SkNoncopyable { + public: + SnapshotArray(const SkPicture* pics[], int count) : fPics(pics), fCount(count) {} + ~SnapshotArray() { for (int i = 0; i < fCount; i++) { fPics[i]->unref(); } } + + const SkPicture* const* begin() const { return fPics; } + int count() const { return fCount; } + private: + SkAutoTMalloc fPics; + int fCount; + }; + + SkBigPicture(const SkRect& cull, + SkRecord*, // We take ownership of the caller's ref. + SnapshotArray*, // We take exclusive ownership. + SkBBoxHierarchy*, // We take ownership of the caller's ref. + size_t approxBytesUsedBySubPictures); + + +// SkPicture overrides + void playback(SkCanvas*, AbortCallback*) const override; + SkRect cullRect() const override; + int approximateOpCount() const override; + size_t approximateBytesUsed() const override; + const SkBigPicture* asSkBigPicture() const override { return this; } + +// Used by GrLayerHoister + void partialPlayback(SkCanvas*, + int start, + int stop, + const SkMatrix& initialCTM) const; +// Used by GrRecordReplaceDraw + const SkBBoxHierarchy* bbh() const { return fBBH.get(); } + const SkRecord* record() const { return fRecord.get(); } + +private: + int drawableCount() const; + SkPicture const* const* drawablePicts() const; + + const SkRect fCullRect; + const size_t fApproxBytesUsedBySubPictures; + sk_sp fRecord; + std::unique_ptr fDrawablePicts; + sk_sp fBBH; +}; + +#endif//SkBigPicture_DEFINED diff --git a/gfx/skia/skia/src/core/SkBitmap.cpp b/gfx/skia/skia/src/core/SkBitmap.cpp new file mode 100644 index 0000000000..f25991de25 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmap.cpp @@ -0,0 +1,630 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" + +#include "include/core/SkData.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkMallocPixelRef.h" +#include "include/core/SkMath.h" +#include "include/core/SkPixelRef.h" +#include "include/core/SkRect.h" +#include "include/core/SkScalar.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkMask.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkPixelRefPriv.h" +#include "src/core/SkPixmapPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/core/SkWritePixelsRec.h" + +#include +#include + +static bool reset_return_false(SkBitmap* bm) { + bm->reset(); + return false; +} + +SkBitmap::SkBitmap() : fFlags(0) {} + +SkBitmap::SkBitmap(const SkBitmap& src) + : fPixelRef (src.fPixelRef) + , fPixmap (src.fPixmap) + , fFlags (src.fFlags) +{ + SkDEBUGCODE(src.validate();) + SkDEBUGCODE(this->validate();) +} + +SkBitmap::SkBitmap(SkBitmap&& other) + : fPixelRef (std::move(other.fPixelRef)) + , fPixmap (std::move(other.fPixmap)) + , fFlags (other.fFlags) +{ + SkASSERT(!other.fPixelRef); + other.fPixmap.reset(); + other.fFlags = 0; +} + +SkBitmap::~SkBitmap() {} + +SkBitmap& SkBitmap::operator=(const SkBitmap& src) { + if (this != &src) { + fPixelRef = src.fPixelRef; + fPixmap = src.fPixmap; + fFlags = src.fFlags; + } + SkDEBUGCODE(this->validate();) + return *this; +} + +SkBitmap& SkBitmap::operator=(SkBitmap&& other) { + if (this != &other) { + fPixelRef = std::move(other.fPixelRef); + fPixmap = std::move(other.fPixmap); + fFlags = other.fFlags; + SkASSERT(!other.fPixelRef); + other.fPixmap.reset(); + other.fFlags = 0; + } + return *this; +} + +void SkBitmap::swap(SkBitmap& other) { + using std::swap; + swap(*this, other); + SkDEBUGCODE(this->validate();) +} + +void SkBitmap::reset() { + fPixelRef = nullptr; // Free pixels. + fPixmap.reset(); + fFlags = 0; +} + +void SkBitmap::getBounds(SkRect* bounds) const { + SkASSERT(bounds); + *bounds = SkRect::Make(this->dimensions()); +} + +void SkBitmap::getBounds(SkIRect* bounds) const { + SkASSERT(bounds); + *bounds = fPixmap.bounds(); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkBitmap::setInfo(const SkImageInfo& info, size_t rowBytes) { + SkAlphaType newAT = info.alphaType(); + if (!SkColorTypeValidateAlphaType(info.colorType(), info.alphaType(), &newAT)) { + return reset_return_false(this); + } + // don't look at info.alphaType(), since newAT is the real value... + + // require that rowBytes fit in 31bits + int64_t mrb = info.minRowBytes64(); + if (!SkTFitsIn(mrb)) { + return reset_return_false(this); + } + if (!SkTFitsIn(rowBytes)) { + return reset_return_false(this); + } + + if (info.width() < 0 || info.height() < 0) { + return reset_return_false(this); + } + + if (kUnknown_SkColorType == info.colorType()) { + rowBytes = 0; + } else if (0 == rowBytes) { + rowBytes = (size_t)mrb; + } else if (!info.validRowBytes(rowBytes)) { + return reset_return_false(this); + } + + fPixelRef = nullptr; // Free pixels. + fPixmap.reset(info.makeAlphaType(newAT), nullptr, SkToU32(rowBytes)); + SkDEBUGCODE(this->validate();) + return true; +} + + + +bool SkBitmap::setAlphaType(SkAlphaType newAlphaType) { + if (!SkColorTypeValidateAlphaType(this->colorType(), newAlphaType, &newAlphaType)) { + return false; + } + if (this->alphaType() != newAlphaType) { + auto newInfo = fPixmap.info().makeAlphaType(newAlphaType); + fPixmap.reset(std::move(newInfo), fPixmap.addr(), fPixmap.rowBytes()); + } + SkDEBUGCODE(this->validate();) + return true; +} + +SkIPoint SkBitmap::pixelRefOrigin() const { + const char* addr = (const char*)fPixmap.addr(); + const char* pix = (const char*)(fPixelRef ? fPixelRef->pixels() : nullptr); + size_t rb = this->rowBytes(); + if (!pix || 0 == rb) { + return {0, 0}; + } + SkASSERT(this->bytesPerPixel() > 0); + SkASSERT(this->bytesPerPixel() == (1 << this->shiftPerPixel())); + SkASSERT(addr >= pix); + size_t off = addr - pix; + return {SkToS32((off % rb) >> this->shiftPerPixel()), SkToS32(off / rb)}; +} + +void SkBitmap::setPixelRef(sk_sp pr, int dx, int dy) { +#ifdef SK_DEBUG + if (pr) { + if (kUnknown_SkColorType != this->colorType()) { + SkASSERT(dx >= 0 && this->width() + dx <= pr->width()); + SkASSERT(dy >= 0 && this->height() + dy <= pr->height()); + } + } +#endif + fPixelRef = kUnknown_SkColorType != this->colorType() ? std::move(pr) : nullptr; + void* p = nullptr; + size_t rowBytes = this->rowBytes(); + // ignore dx,dy if there is no pixelref + if (fPixelRef) { + rowBytes = fPixelRef->rowBytes(); + // TODO(reed): Enforce that PixelRefs must have non-null pixels. + p = fPixelRef->pixels(); + if (p) { + p = (char*)p + dy * rowBytes + dx * this->bytesPerPixel(); + } + } + SkPixmapPriv::ResetPixmapKeepInfo(&fPixmap, p, rowBytes); + SkDEBUGCODE(this->validate();) +} + +void SkBitmap::setPixels(void* p) { + if (kUnknown_SkColorType == this->colorType()) { + p = nullptr; + } + size_t rb = this->rowBytes(); + SkPixmapPriv::ResetPixmapKeepInfo(&fPixmap, p, rb); + fPixelRef = p ? sk_make_sp(this->width(), this->height(), p, rb) : nullptr; + SkDEBUGCODE(this->validate();) +} + +bool SkBitmap::tryAllocPixels(Allocator* allocator) { + HeapAllocator stdalloc; + + if (nullptr == allocator) { + allocator = &stdalloc; + } + return allocator->allocPixelRef(this); +} + +bool SkBitmap::tryAllocN32Pixels(int width, int height, bool isOpaque) { + SkImageInfo info = SkImageInfo::MakeN32(width, height, + isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType); + return this->tryAllocPixels(info); +} + +void SkBitmap::allocN32Pixels(int width, int height, bool isOpaque) { + SkImageInfo info = SkImageInfo::MakeN32(width, height, + isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType); + this->allocPixels(info); +} + +void SkBitmap::allocPixels() { + this->allocPixels((Allocator*)nullptr); +} + +void SkBitmap::allocPixels(Allocator* allocator) { + SkASSERT_RELEASE(this->tryAllocPixels(allocator)); +} + +void SkBitmap::allocPixelsFlags(const SkImageInfo& info, uint32_t flags) { + SkASSERT_RELEASE(this->tryAllocPixelsFlags(info, flags)); +} + +void SkBitmap::allocPixels(const SkImageInfo& info, size_t rowBytes) { + SkASSERT_RELEASE(this->tryAllocPixels(info, rowBytes)); +} + +void SkBitmap::allocPixels(const SkImageInfo& info) { + this->allocPixels(info, info.minRowBytes()); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkBitmap::tryAllocPixels(const SkImageInfo& requestedInfo, size_t rowBytes) { + if (!this->setInfo(requestedInfo, rowBytes)) { + return reset_return_false(this); + } + + // setInfo may have corrected info (e.g. 565 is always opaque). + const SkImageInfo& correctedInfo = this->info(); + if (kUnknown_SkColorType == correctedInfo.colorType()) { + return true; + } + // setInfo may have computed a valid rowbytes if 0 were passed in + rowBytes = this->rowBytes(); + + sk_sp pr = SkMallocPixelRef::MakeAllocate(correctedInfo, rowBytes); + if (!pr) { + return reset_return_false(this); + } + this->setPixelRef(std::move(pr), 0, 0); + if (nullptr == this->getPixels()) { + return reset_return_false(this); + } + SkDEBUGCODE(this->validate();) + return true; +} + +bool SkBitmap::tryAllocPixelsFlags(const SkImageInfo& requestedInfo, uint32_t allocFlags) { + if (!this->setInfo(requestedInfo)) { + return reset_return_false(this); + } + + // setInfo may have corrected info (e.g. 565 is always opaque). + const SkImageInfo& correctedInfo = this->info(); + + sk_sp pr = SkMallocPixelRef::MakeAllocate(correctedInfo, + correctedInfo.minRowBytes()); + if (!pr) { + return reset_return_false(this); + } + this->setPixelRef(std::move(pr), 0, 0); + if (nullptr == this->getPixels()) { + return reset_return_false(this); + } + SkDEBUGCODE(this->validate();) + return true; +} + +static void invoke_release_proc(void (*proc)(void* pixels, void* ctx), void* pixels, void* ctx) { + if (proc) { + proc(pixels, ctx); + } +} + +bool SkBitmap::installPixels(const SkImageInfo& requestedInfo, void* pixels, size_t rb, + void (*releaseProc)(void* addr, void* context), void* context) { + if (!this->setInfo(requestedInfo, rb)) { + invoke_release_proc(releaseProc, pixels, context); + this->reset(); + return false; + } + if (nullptr == pixels) { + invoke_release_proc(releaseProc, pixels, context); + return true; // we behaved as if they called setInfo() + } + + // setInfo may have corrected info (e.g. 565 is always opaque). + const SkImageInfo& correctedInfo = this->info(); + this->setPixelRef( + SkMakePixelRefWithProc(correctedInfo.width(), correctedInfo.height(), + rb, pixels, releaseProc, context), 0, 0); + SkDEBUGCODE(this->validate();) + return true; +} + +bool SkBitmap::installPixels(const SkPixmap& pixmap) { + return this->installPixels(pixmap.info(), pixmap.writable_addr(), pixmap.rowBytes(), + nullptr, nullptr); +} + +bool SkBitmap::installMaskPixels(const SkMask& mask) { + if (SkMask::kA8_Format != mask.fFormat) { + this->reset(); + return false; + } + return this->installPixels(SkImageInfo::MakeA8(mask.fBounds.width(), + mask.fBounds.height()), + mask.fImage, mask.fRowBytes); +} + +/////////////////////////////////////////////////////////////////////////////// + +uint32_t SkBitmap::getGenerationID() const { + return fPixelRef ? fPixelRef->getGenerationID() : 0; +} + +void SkBitmap::notifyPixelsChanged() const { + SkASSERT(!this->isImmutable()); + if (fPixelRef) { + fPixelRef->notifyPixelsChanged(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +/** We explicitly use the same allocator for our pixels that SkMask does, + so that we can freely assign memory allocated by one class to the other. + */ +bool SkBitmap::HeapAllocator::allocPixelRef(SkBitmap* dst) { + const SkImageInfo info = dst->info(); + if (kUnknown_SkColorType == info.colorType()) { +// SkDebugf("unsupported config for info %d\n", dst->config()); + return false; + } + + sk_sp pr = SkMallocPixelRef::MakeAllocate(info, dst->rowBytes()); + if (!pr) { + return false; + } + + dst->setPixelRef(std::move(pr), 0, 0); + SkDEBUGCODE(dst->validate();) + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkBitmap::isImmutable() const { + return fPixelRef ? fPixelRef->isImmutable() : false; +} + +void SkBitmap::setImmutable() { + if (fPixelRef) { + fPixelRef->setImmutable(); + } +} + +bool SkBitmap::isVolatile() const { + return (fFlags & kImageIsVolatile_Flag) != 0; +} + +void SkBitmap::setIsVolatile(bool isVolatile) { + if (isVolatile) { + fFlags |= kImageIsVolatile_Flag; + } else { + fFlags &= ~kImageIsVolatile_Flag; + } +} + +void* SkBitmap::getAddr(int x, int y) const { + SkASSERT((unsigned)x < (unsigned)this->width()); + SkASSERT((unsigned)y < (unsigned)this->height()); + + char* base = (char*)this->getPixels(); + if (base) { + base += (y * this->rowBytes()) + (x << this->shiftPerPixel()); + } + return base; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +void SkBitmap::erase(SkColor c, const SkIRect& area) const { + SkDEBUGCODE(this->validate();) + + if (kUnknown_SkColorType == this->colorType()) { + // TODO: can we ASSERT that we never get here? + return; // can't erase. Should we bzero so the memory is not uninitialized? + } + + SkPixmap result; + if (!this->peekPixels(&result)) { + return; + } + + if (result.erase(c, area)) { + this->notifyPixelsChanged(); + } +} + +void SkBitmap::eraseColor(SkColor c) const { + this->erase(c, SkIRect::MakeWH(this->width(), this->height())); +} + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + +bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const { + SkDEBUGCODE(this->validate();) + + if (nullptr == result || !fPixelRef) { + return false; // no src pixels + } + + SkIRect srcRect, r; + srcRect.setWH(this->width(), this->height()); + if (!r.intersect(srcRect, subset)) { + return false; // r is empty (i.e. no intersection) + } + + // If the upper left of the rectangle was outside the bounds of this SkBitmap, we should have + // exited above. + SkASSERT(static_cast(r.fLeft) < static_cast(this->width())); + SkASSERT(static_cast(r.fTop) < static_cast(this->height())); + + SkBitmap dst; + dst.setInfo(this->info().makeDimensions(r.size()), this->rowBytes()); + dst.setIsVolatile(this->isVolatile()); + + if (fPixelRef) { + SkIPoint origin = this->pixelRefOrigin(); + // share the pixelref with a custom offset + dst.setPixelRef(fPixelRef, origin.x() + r.fLeft, origin.y() + r.fTop); + } + SkDEBUGCODE(dst.validate();) + + // we know we're good, so commit to result + result->swap(dst); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkBitmap::readPixels(const SkImageInfo& requestedDstInfo, void* dstPixels, size_t dstRB, + int x, int y) const { + SkPixmap src; + if (!this->peekPixels(&src)) { + return false; + } + return src.readPixels(requestedDstInfo, dstPixels, dstRB, x, y); +} + +bool SkBitmap::readPixels(const SkPixmap& dst, int srcX, int srcY) const { + return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY); +} + +bool SkBitmap::writePixels(const SkPixmap& src, int dstX, int dstY) { + if (!SkImageInfoValidConversion(this->info(), src.info())) { + return false; + } + + SkWritePixelsRec rec(src.info(), src.addr(), src.rowBytes(), dstX, dstY); + if (!rec.trim(this->width(), this->height())) { + return false; + } + + void* dstPixels = this->getAddr(rec.fX, rec.fY); + const SkImageInfo dstInfo = this->info().makeDimensions(rec.fInfo.dimensions()); + SkConvertPixels(dstInfo, dstPixels, this->rowBytes(), rec.fInfo, rec.fPixels, rec.fRowBytes); + this->notifyPixelsChanged(); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool GetBitmapAlpha(const SkBitmap& src, uint8_t* SK_RESTRICT alpha, int alphaRowBytes) { + SkASSERT(alpha != nullptr); + SkASSERT(alphaRowBytes >= src.width()); + + SkPixmap pmap; + if (!src.peekPixels(&pmap)) { + for (int y = 0; y < src.height(); ++y) { + memset(alpha, 0, src.width()); + alpha += alphaRowBytes; + } + return false; + } + SkConvertPixels(SkImageInfo::MakeA8(pmap.width(), pmap.height()), alpha, alphaRowBytes, + pmap.info(), pmap.addr(), pmap.rowBytes()); + return true; +} + +#include "include/core/SkMaskFilter.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" + +bool SkBitmap::extractAlpha(SkBitmap* dst, const SkPaint* paint, + Allocator *allocator, SkIPoint* offset) const { + SkDEBUGCODE(this->validate();) + + SkBitmap tmpBitmap; + SkMatrix identity; + SkMask srcM, dstM; + + if (this->width() == 0 || this->height() == 0) { + return false; + } + srcM.fBounds.setWH(this->width(), this->height()); + srcM.fRowBytes = SkAlign4(this->width()); + srcM.fFormat = SkMask::kA8_Format; + + SkMaskFilter* filter = paint ? paint->getMaskFilter() : nullptr; + + // compute our (larger?) dst bounds if we have a filter + if (filter) { + identity.reset(); + if (!as_MFB(filter)->filterMask(&dstM, srcM, identity, nullptr)) { + goto NO_FILTER_CASE; + } + dstM.fRowBytes = SkAlign4(dstM.fBounds.width()); + } else { + NO_FILTER_CASE: + tmpBitmap.setInfo(SkImageInfo::MakeA8(this->width(), this->height()), srcM.fRowBytes); + if (!tmpBitmap.tryAllocPixels(allocator)) { + // Allocation of pixels for alpha bitmap failed. + SkDebugf("extractAlpha failed to allocate (%d,%d) alpha bitmap\n", + tmpBitmap.width(), tmpBitmap.height()); + return false; + } + GetBitmapAlpha(*this, tmpBitmap.getAddr8(0, 0), srcM.fRowBytes); + if (offset) { + offset->set(0, 0); + } + tmpBitmap.swap(*dst); + return true; + } + srcM.fImage = SkMask::AllocImage(srcM.computeImageSize()); + SkAutoMaskFreeImage srcCleanup(srcM.fImage); + + GetBitmapAlpha(*this, srcM.fImage, srcM.fRowBytes); + if (!as_MFB(filter)->filterMask(&dstM, srcM, identity, nullptr)) { + goto NO_FILTER_CASE; + } + SkAutoMaskFreeImage dstCleanup(dstM.fImage); + + tmpBitmap.setInfo(SkImageInfo::MakeA8(dstM.fBounds.width(), dstM.fBounds.height()), + dstM.fRowBytes); + if (!tmpBitmap.tryAllocPixels(allocator)) { + // Allocation of pixels for alpha bitmap failed. + SkDebugf("extractAlpha failed to allocate (%d,%d) alpha bitmap\n", + tmpBitmap.width(), tmpBitmap.height()); + return false; + } + memcpy(tmpBitmap.getPixels(), dstM.fImage, dstM.computeImageSize()); + if (offset) { + offset->set(dstM.fBounds.fLeft, dstM.fBounds.fTop); + } + SkDEBUGCODE(tmpBitmap.validate();) + + tmpBitmap.swap(*dst); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +void SkBitmap::validate() const { + this->info().validate(); + + SkASSERT(this->info().validRowBytes(this->rowBytes())); + uint8_t allFlags = kImageIsVolatile_Flag; + SkASSERT((~allFlags & fFlags) == 0); + + if (fPixelRef && fPixelRef->pixels()) { + SkASSERT(this->getPixels()); + } else { + SkASSERT(!this->getPixels()); + } + + if (this->getPixels()) { + SkASSERT(fPixelRef); + SkASSERT(fPixelRef->rowBytes() == this->rowBytes()); + SkIPoint origin = this->pixelRefOrigin(); + SkASSERT(origin.fX >= 0); + SkASSERT(origin.fY >= 0); + SkASSERT(fPixelRef->width() >= (int)this->width() + origin.fX); + SkASSERT(fPixelRef->height() >= (int)this->height() + origin.fY); + SkASSERT(fPixelRef->rowBytes() >= this->info().minRowBytes()); + } +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +bool SkBitmap::peekPixels(SkPixmap* pmap) const { + if (this->getPixels()) { + if (pmap) { + *pmap = fPixmap; + } + return true; + } + return false; +} diff --git a/gfx/skia/skia/src/core/SkBitmapCache.cpp b/gfx/skia/skia/src/core/SkBitmapCache.cpp new file mode 100644 index 0000000000..6767c59093 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapCache.cpp @@ -0,0 +1,300 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImage.h" +#include "include/core/SkPixelRef.h" +#include "include/core/SkRect.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkResourceCache.h" +#include "src/image/SkImage_Base.h" + +/** + * Use this for bitmapcache and mipmapcache entries. + */ +uint64_t SkMakeResourceCacheSharedIDForBitmap(uint32_t bitmapGenID) { + uint64_t sharedID = SkSetFourByteTag('b', 'm', 'a', 'p'); + return (sharedID << 32) | bitmapGenID; +} + +void SkNotifyBitmapGenIDIsStale(uint32_t bitmapGenID) { + SkResourceCache::PostPurgeSharedID(SkMakeResourceCacheSharedIDForBitmap(bitmapGenID)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkBitmapCacheDesc SkBitmapCacheDesc::Make(uint32_t imageID, const SkIRect& subset) { + SkASSERT(imageID); + SkASSERT(subset.width() > 0 && subset.height() > 0); + return { imageID, subset }; +} + +SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkImage* image) { + SkIRect bounds = SkIRect::MakeWH(image->width(), image->height()); + return Make(image->uniqueID(), bounds); +} + +namespace { +static unsigned gBitmapKeyNamespaceLabel; + +struct BitmapKey : public SkResourceCache::Key { +public: + BitmapKey(const SkBitmapCacheDesc& desc) : fDesc(desc) { + this->init(&gBitmapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(fDesc.fImageID), + sizeof(fDesc)); + } + + const SkBitmapCacheDesc fDesc; +}; +} + +////////////////////// +#include "src/core/SkDiscardableMemory.h" +#include "src/core/SkNextID.h" + +void SkBitmapCache_setImmutableWithID(SkPixelRef* pr, uint32_t id) { + pr->setImmutableWithID(id); +} + +class SkBitmapCache::Rec : public SkResourceCache::Rec { +public: + Rec(const SkBitmapCacheDesc& desc, const SkImageInfo& info, size_t rowBytes, + std::unique_ptr dm, void* block) + : fKey(desc) + , fDM(std::move(dm)) + , fMalloc(block) + , fInfo(info) + , fRowBytes(rowBytes) + { + SkASSERT(!(fDM && fMalloc)); // can't have both + + // We need an ID to return with the bitmap/pixelref. We can't necessarily use the key/desc + // ID - lazy images cache the same ID with multiple keys (in different color types). + fPrUniqueID = SkNextID::ImageID(); + } + + ~Rec() override { + SkASSERT(0 == fExternalCounter); + if (fDM && fDiscardableIsLocked) { + SkASSERT(fDM->data()); + fDM->unlock(); + } + sk_free(fMalloc); // may be null + } + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { + return sizeof(fKey) + fInfo.computeByteSize(fRowBytes); + } + bool canBePurged() override { + SkAutoMutexExclusive ama(fMutex); + return fExternalCounter == 0; + } + void postAddInstall(void* payload) override { + SkAssertResult(this->install(static_cast(payload))); + } + + const char* getCategory() const override { return "bitmap"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { + return fDM.get(); + } + + static void ReleaseProc(void* addr, void* ctx) { + Rec* rec = static_cast(ctx); + SkAutoMutexExclusive ama(rec->fMutex); + + SkASSERT(rec->fExternalCounter > 0); + rec->fExternalCounter -= 1; + if (rec->fDM) { + SkASSERT(rec->fMalloc == nullptr); + if (rec->fExternalCounter == 0) { + rec->fDM->unlock(); + rec->fDiscardableIsLocked = false; + } + } else { + SkASSERT(rec->fMalloc != nullptr); + } + } + + bool install(SkBitmap* bitmap) { + SkAutoMutexExclusive ama(fMutex); + + if (!fDM && !fMalloc) { + return false; + } + + if (fDM) { + if (!fDiscardableIsLocked) { + SkASSERT(fExternalCounter == 0); + if (!fDM->lock()) { + fDM.reset(nullptr); + return false; + } + fDiscardableIsLocked = true; + } + SkASSERT(fDM->data()); + } + + bitmap->installPixels(fInfo, fDM ? fDM->data() : fMalloc, fRowBytes, ReleaseProc, this); + SkBitmapCache_setImmutableWithID(bitmap->pixelRef(), fPrUniqueID); + fExternalCounter++; + + return true; + } + + static bool Finder(const SkResourceCache::Rec& baseRec, void* contextBitmap) { + Rec* rec = (Rec*)&baseRec; + SkBitmap* result = (SkBitmap*)contextBitmap; + return rec->install(result); + } + +private: + BitmapKey fKey; + + SkMutex fMutex; + + // either fDM or fMalloc can be non-null, but not both + std::unique_ptr fDM; + void* fMalloc; + + SkImageInfo fInfo; + size_t fRowBytes; + uint32_t fPrUniqueID; + + // This field counts the number of external pixelrefs we have created. + // They notify us when they are destroyed so we can decrement this. + int fExternalCounter = 0; + bool fDiscardableIsLocked = true; +}; + +void SkBitmapCache::PrivateDeleteRec(Rec* rec) { delete rec; } + +SkBitmapCache::RecPtr SkBitmapCache::Alloc(const SkBitmapCacheDesc& desc, const SkImageInfo& info, + SkPixmap* pmap) { + // Ensure that the info matches the subset (i.e. the subset is the entire image) + SkASSERT(info.width() == desc.fSubset.width()); + SkASSERT(info.height() == desc.fSubset.height()); + + const size_t rb = info.minRowBytes(); + size_t size = info.computeByteSize(rb); + if (SkImageInfo::ByteSizeOverflowed(size)) { + return nullptr; + } + + std::unique_ptr dm; + void* block = nullptr; + + auto factory = SkResourceCache::GetDiscardableFactory(); + if (factory) { + dm.reset(factory(size)); + } else { + block = sk_malloc_canfail(size); + } + if (!dm && !block) { + return nullptr; + } + *pmap = SkPixmap(info, dm ? dm->data() : block, rb); + return RecPtr(new Rec(desc, info, rb, std::move(dm), block)); +} + +void SkBitmapCache::Add(RecPtr rec, SkBitmap* bitmap) { + SkResourceCache::Add(rec.release(), bitmap); +} + +bool SkBitmapCache::Find(const SkBitmapCacheDesc& desc, SkBitmap* result) { + desc.validate(); + return SkResourceCache::Find(BitmapKey(desc), SkBitmapCache::Rec::Finder, result); +} + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// + +#define CHECK_LOCAL(localCache, localName, globalName, ...) \ + ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__)) + +namespace { +static unsigned gMipMapKeyNamespaceLabel; + +struct MipMapKey : public SkResourceCache::Key { +public: + MipMapKey(const SkBitmapCacheDesc& desc) : fDesc(desc) { + this->init(&gMipMapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(fDesc.fImageID), + sizeof(fDesc)); + } + + const SkBitmapCacheDesc fDesc; +}; + +struct MipMapRec : public SkResourceCache::Rec { + MipMapRec(const SkBitmapCacheDesc& desc, const SkMipMap* result) + : fKey(desc) + , fMipMap(result) + { + fMipMap->attachToCacheAndRef(); + } + + ~MipMapRec() override { + fMipMap->detachFromCacheAndUnref(); + } + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { return sizeof(fKey) + fMipMap->size(); } + const char* getCategory() const override { return "mipmap"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { + return fMipMap->diagnostic_only_getDiscardable(); + } + + static bool Finder(const SkResourceCache::Rec& baseRec, void* contextMip) { + const MipMapRec& rec = static_cast(baseRec); + const SkMipMap* mm = SkRef(rec.fMipMap); + // the call to ref() above triggers a "lock" in the case of discardable memory, + // which means we can now check for null (in case the lock failed). + if (nullptr == mm->data()) { + mm->unref(); // balance our call to ref() + return false; + } + // the call must call unref() when they are done. + *(const SkMipMap**)contextMip = mm; + return true; + } + +private: + MipMapKey fKey; + const SkMipMap* fMipMap; +}; +} + +const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmapCacheDesc& desc, + SkResourceCache* localCache) { + MipMapKey key(desc); + const SkMipMap* result; + + if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Finder, &result)) { + result = nullptr; + } + return result; +} + +static SkResourceCache::DiscardableFactory get_fact(SkResourceCache* localCache) { + return localCache ? localCache->GetDiscardableFactory() + : SkResourceCache::GetDiscardableFactory(); +} + +const SkMipMap* SkMipMapCache::AddAndRef(const SkImage_Base* image, SkResourceCache* localCache) { + SkBitmap src; + if (!image->getROPixels(&src)) { + return nullptr; + } + + SkMipMap* mipmap = SkMipMap::Build(src, get_fact(localCache)); + if (mipmap) { + MipMapRec* rec = new MipMapRec(SkBitmapCacheDesc::Make(image), mipmap); + CHECK_LOCAL(localCache, add, Add, rec); + image->notifyAddedToRasterCache(); + } + return mipmap; +} diff --git a/gfx/skia/skia/src/core/SkBitmapCache.h b/gfx/skia/skia/src/core/SkBitmapCache.h new file mode 100644 index 0000000000..66021e4431 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapCache.h @@ -0,0 +1,67 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapCache_DEFINED +#define SkBitmapCache_DEFINED + +#include "include/core/SkRect.h" +#include + +class SkBitmap; +class SkImage; +class SkImage_Base; +struct SkImageInfo; +class SkMipMap; +class SkPixmap; +class SkResourceCache; + +uint64_t SkMakeResourceCacheSharedIDForBitmap(uint32_t bitmapGenID); + +void SkNotifyBitmapGenIDIsStale(uint32_t bitmapGenID); + +struct SkBitmapCacheDesc { + uint32_t fImageID; // != 0 + SkIRect fSubset; // always set to a valid rect (entire or subset) + + void validate() const { + SkASSERT(fImageID); + SkASSERT(fSubset.fLeft >= 0 && fSubset.fTop >= 0); + SkASSERT(fSubset.width() > 0 && fSubset.height() > 0); + } + + static SkBitmapCacheDesc Make(const SkImage*); + static SkBitmapCacheDesc Make(uint32_t genID, const SkIRect& subset); +}; + +class SkBitmapCache { +public: + /** + * Search based on the desc. If found, returns true and + * result will be set to the matching bitmap with its pixels already locked. + */ + static bool Find(const SkBitmapCacheDesc&, SkBitmap* result); + + class Rec; + struct RecDeleter { void operator()(Rec* r) { PrivateDeleteRec(r); } }; + typedef std::unique_ptr RecPtr; + + static RecPtr Alloc(const SkBitmapCacheDesc&, const SkImageInfo&, SkPixmap*); + static void Add(RecPtr, SkBitmap*); + +private: + static void PrivateDeleteRec(Rec*); +}; + +class SkMipMapCache { +public: + static const SkMipMap* FindAndRef(const SkBitmapCacheDesc&, + SkResourceCache* localCache = nullptr); + static const SkMipMap* AddAndRef(const SkImage_Base*, + SkResourceCache* localCache = nullptr); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBitmapController.cpp b/gfx/skia/skia/src/core/SkBitmapController.cpp new file mode 100644 index 0000000000..35cd0410e3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapController.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkMatrix.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkBitmapController.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkMipMap.h" +#include "src/image/SkImage_Base.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkBitmapController::State* SkBitmapController::RequestBitmap(const SkImage_Base* image, + const SkMatrix& inv, + SkFilterQuality quality, + SkArenaAlloc* alloc) { + auto* state = alloc->make(image, inv, quality); + + return state->pixmap().addr() ? state : nullptr; +} + +bool SkBitmapController::State::processHighRequest(const SkImage_Base* image) { + if (fQuality != kHigh_SkFilterQuality) { + return false; + } + + if (SkMatrixPriv::AdjustHighQualityFilterLevel(fInvMatrix, true) != kHigh_SkFilterQuality) { + fQuality = kMedium_SkFilterQuality; + return false; + } + + (void)image->getROPixels(&fResultBitmap); + return true; +} + +/* + * Modulo internal errors, this should always succeed *if* the matrix is downscaling + * (in this case, we have the inverse, so it succeeds if fInvMatrix is upscaling) + */ +bool SkBitmapController::State::processMediumRequest(const SkImage_Base* image) { + SkASSERT(fQuality <= kMedium_SkFilterQuality); + if (fQuality != kMedium_SkFilterQuality) { + return false; + } + + // Our default return state is to downgrade the request to Low, w/ or w/o setting fBitmap + // to a valid bitmap. + fQuality = kLow_SkFilterQuality; + + SkSize invScaleSize; + if (!fInvMatrix.decomposeScale(&invScaleSize, nullptr)) { + return false; + } + + if (invScaleSize.width() > SK_Scalar1 || invScaleSize.height() > SK_Scalar1) { + fCurrMip.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image))); + if (nullptr == fCurrMip.get()) { + fCurrMip.reset(SkMipMapCache::AddAndRef(image)); + if (nullptr == fCurrMip.get()) { + return false; + } + } + // diagnostic for a crasher... + SkASSERT_RELEASE(fCurrMip->data()); + + const SkSize scale = SkSize::Make(SkScalarInvert(invScaleSize.width()), + SkScalarInvert(invScaleSize.height())); + SkMipMap::Level level; + if (fCurrMip->extractLevel(scale, &level)) { + const SkSize& invScaleFixup = level.fScale; + fInvMatrix.postScale(invScaleFixup.width(), invScaleFixup.height()); + + // todo: if we could wrap the fCurrMip in a pixelref, then we could just install + // that here, and not need to explicitly track it ourselves. + return fResultBitmap.installPixels(level.fPixmap); + } else { + // failed to extract, so release the mipmap + fCurrMip.reset(nullptr); + } + } + return false; +} + +SkBitmapController::State::State(const SkImage_Base* image, + const SkMatrix& inv, + SkFilterQuality qual) { + fInvMatrix = inv; + fQuality = qual; + + if (this->processHighRequest(image) || this->processMediumRequest(image)) { + SkASSERT(fResultBitmap.getPixels()); + } else { + (void)image->getROPixels(&fResultBitmap); + } + + // fResultBitmap.getPixels() may be null, but our caller knows to check fPixmap.addr() + // and will destroy us if it is nullptr. + fPixmap.reset(fResultBitmap.info(), fResultBitmap.getPixels(), fResultBitmap.rowBytes()); +} diff --git a/gfx/skia/skia/src/core/SkBitmapController.h b/gfx/skia/skia/src/core/SkBitmapController.h new file mode 100644 index 0000000000..6e08f94db5 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapController.h @@ -0,0 +1,53 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapController_DEFINED +#define SkBitmapController_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkMatrix.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkMipMap.h" + +class SkImage_Base; + +/** + * Handles request to scale, filter, and lock a bitmap to be rasterized. + */ +class SkBitmapController : ::SkNoncopyable { +public: + class State : ::SkNoncopyable { + public: + State(const SkImage_Base*, const SkMatrix& inv, SkFilterQuality); + + const SkPixmap& pixmap() const { return fPixmap; } + const SkMatrix& invMatrix() const { return fInvMatrix; } + SkFilterQuality quality() const { return fQuality; } + + private: + bool processHighRequest(const SkImage_Base*); + bool processMediumRequest(const SkImage_Base*); + + SkPixmap fPixmap; + SkMatrix fInvMatrix; + SkFilterQuality fQuality; + + // Pixmap storage. + SkBitmap fResultBitmap; + sk_sp fCurrMip; + + }; + + static State* RequestBitmap(const SkImage_Base*, const SkMatrix& inverse, SkFilterQuality, + SkArenaAlloc*); + +private: + SkBitmapController() = delete; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBitmapDevice.cpp b/gfx/skia/skia/src/core/SkBitmapDevice.cpp new file mode 100644 index 0000000000..9bf45a0fbe --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapDevice.cpp @@ -0,0 +1,820 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImageFilter.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkRasterHandleAllocator.h" +#include "include/core/SkShader.h" +#include "include/core/SkSurface.h" +#include "include/core/SkVertices.h" +#include "src/core/SkBitmapDevice.h" +#include "src/core/SkDraw.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTLazy.h" + +struct Bounder { + SkRect fBounds; + bool fHasBounds; + + Bounder(const SkRect& r, const SkPaint& paint) { + if ((fHasBounds = paint.canComputeFastBounds())) { + fBounds = paint.computeFastBounds(r, &fBounds); + } + } + + bool hasBounds() const { return fHasBounds; } + const SkRect* bounds() const { return fHasBounds ? &fBounds : nullptr; } + operator const SkRect* () const { return this->bounds(); } +}; + +class SkDrawTiler { + enum { + // 8K is 1 too big, since 8K << supersample == 32768 which is too big for SkFixed + kMaxDim = 8192 - 1 + }; + + SkBitmapDevice* fDevice; + SkPixmap fRootPixmap; + SkIRect fSrcBounds; + + // Used for tiling and non-tiling + SkDraw fDraw; + + // fCurr... are only used if fNeedTiling + SkMatrix fTileMatrix; + SkRasterClip fTileRC; + SkIPoint fOrigin; + + bool fDone, fNeedsTiling; + +public: + static bool NeedsTiling(SkBitmapDevice* dev) { + return dev->width() > kMaxDim || dev->height() > kMaxDim; + } + + SkDrawTiler(SkBitmapDevice* dev, const SkRect* bounds) : fDevice(dev) { + fDone = false; + + // we need fDst to be set, and if we're actually drawing, to dirty the genID + if (!dev->accessPixels(&fRootPixmap)) { + // NoDrawDevice uses us (why?) so we have to catch this case w/ no pixels + fRootPixmap.reset(dev->imageInfo(), nullptr, 0); + } + + // do a quick check, so we don't even have to process "bounds" if there is no need + const SkIRect clipR = dev->fRCStack.rc().getBounds(); + fNeedsTiling = clipR.right() > kMaxDim || clipR.bottom() > kMaxDim; + if (fNeedsTiling) { + if (bounds) { + // Make sure we round first, and then intersect. We can't rely on promoting the + // clipR to floats (and then intersecting with devBounds) since promoting + // int --> float can make the float larger than the int. + // rounding(out) first runs the risk of clamping if the float is larger an intmax + // but our roundOut() is saturating, which is fine for this use case + // + // e.g. the older version of this code did this: + // devBounds = mapRect(bounds); + // if (devBounds.intersect(SkRect::Make(clipR))) { + // fSrcBounds = devBounds.roundOut(); + // The problem being that the promotion of clipR to SkRect was unreliable + // + fSrcBounds = dev->ctm().mapRect(*bounds).roundOut(); + if (fSrcBounds.intersect(clipR)) { + // Check again, now that we have computed srcbounds. + fNeedsTiling = fSrcBounds.right() > kMaxDim || fSrcBounds.bottom() > kMaxDim; + } else { + fNeedsTiling = false; + fDone = true; + } + } else { + fSrcBounds = clipR; + } + } + + if (fNeedsTiling) { + // fDraw.fDst is reset each time in setupTileDraw() + fDraw.fMatrix = &fTileMatrix; + fDraw.fRC = &fTileRC; + // we'll step/increase it before using it + fOrigin.set(fSrcBounds.fLeft - kMaxDim, fSrcBounds.fTop); + } else { + // don't reference fSrcBounds, as it may not have been set + fDraw.fDst = fRootPixmap; + fDraw.fMatrix = &dev->ctm(); + fDraw.fRC = &dev->fRCStack.rc(); + fOrigin.set(0, 0); + + fDraw.fCoverage = dev->accessCoverage(); + } + } + + bool needsTiling() const { return fNeedsTiling; } + + const SkDraw* next() { + if (fDone) { + return nullptr; + } + if (fNeedsTiling) { + do { + this->stepAndSetupTileDraw(); // might set the clip to empty and fDone to true + } while (!fDone && fTileRC.isEmpty()); + // if we exit the loop and we're still empty, we're (past) done + if (fTileRC.isEmpty()) { + SkASSERT(fDone); + return nullptr; + } + SkASSERT(!fTileRC.isEmpty()); + } else { + fDone = true; // only draw untiled once + } + return &fDraw; + } + +private: + void stepAndSetupTileDraw() { + SkASSERT(!fDone); + SkASSERT(fNeedsTiling); + + // We do fRootPixmap.width() - kMaxDim instead of fOrigin.fX + kMaxDim to avoid overflow. + if (fOrigin.fX >= fSrcBounds.fRight - kMaxDim) { // too far + fOrigin.fX = fSrcBounds.fLeft; + fOrigin.fY += kMaxDim; + } else { + fOrigin.fX += kMaxDim; + } + // fDone = next origin will be invalid. + fDone = fOrigin.fX >= fSrcBounds.fRight - kMaxDim && + fOrigin.fY >= fSrcBounds.fBottom - kMaxDim; + + SkIRect bounds = SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), kMaxDim, kMaxDim); + SkASSERT(!bounds.isEmpty()); + bool success = fRootPixmap.extractSubset(&fDraw.fDst, bounds); + SkASSERT_RELEASE(success); + // now don't use bounds, since fDst has the clipped dimensions. + + fTileMatrix = fDevice->ctm(); + fTileMatrix.postTranslate(SkIntToScalar(-fOrigin.x()), SkIntToScalar(-fOrigin.y())); + fDevice->fRCStack.rc().translate(-fOrigin.x(), -fOrigin.y(), &fTileRC); + fTileRC.op(SkIRect::MakeWH(fDraw.fDst.width(), fDraw.fDst.height()), + SkRegion::kIntersect_Op); + } +}; + +// Passing a bounds allows the tiler to only visit the dst-tiles that might intersect the +// drawing. If null is passed, the tiler has to visit everywhere. The bounds is expected to be +// in local coordinates, as the tiler itself will transform that into device coordinates. +// +#define LOOP_TILER(code, boundsPtr) \ + SkDrawTiler priv_tiler(this, boundsPtr); \ + while (const SkDraw* priv_draw = priv_tiler.next()) { \ + priv_draw->code; \ + } + +// Helper to create an SkDraw from a device +class SkBitmapDevice::BDDraw : public SkDraw { +public: + BDDraw(SkBitmapDevice* dev) { + // we need fDst to be set, and if we're actually drawing, to dirty the genID + if (!dev->accessPixels(&fDst)) { + // NoDrawDevice uses us (why?) so we have to catch this case w/ no pixels + fDst.reset(dev->imageInfo(), nullptr, 0); + } + fMatrix = &dev->ctm(); + fRC = &dev->fRCStack.rc(); + fCoverage = dev->accessCoverage(); + } +}; + +static bool valid_for_bitmap_device(const SkImageInfo& info, + SkAlphaType* newAlphaType) { + if (info.width() < 0 || info.height() < 0 || kUnknown_SkColorType == info.colorType()) { + return false; + } + + if (newAlphaType) { + *newAlphaType = SkColorTypeIsAlwaysOpaque(info.colorType()) ? kOpaque_SkAlphaType + : info.alphaType(); + } + + return true; +} + +SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap) + : INHERITED(bitmap.info(), SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)) + , fBitmap(bitmap) + , fRCStack(bitmap.width(), bitmap.height()) + , fGlyphPainter(this->surfaceProps(), + bitmap.colorType(), + bitmap.colorSpace(), + SkStrikeCache::GlobalStrikeCache()) { + SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr)); +} + +SkBitmapDevice* SkBitmapDevice::Create(const SkImageInfo& info) { + return Create(info, SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)); +} + +SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps, + SkRasterHandleAllocator::Handle hndl, const SkBitmap* coverage) + : INHERITED(bitmap.info(), surfaceProps) + , fBitmap(bitmap) + , fRasterHandle(hndl) + , fRCStack(bitmap.width(), bitmap.height()) + , fGlyphPainter(this->surfaceProps(), + bitmap.colorType(), + bitmap.colorSpace(), + SkStrikeCache::GlobalStrikeCache()) { + SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr)); + + if (coverage) { + SkASSERT(coverage->width() == bitmap.width()); + SkASSERT(coverage->height() == bitmap.height()); + fCoverage = skstd::make_unique(*coverage); + } +} + +SkBitmapDevice* SkBitmapDevice::Create(const SkImageInfo& origInfo, + const SkSurfaceProps& surfaceProps, + bool trackCoverage, + SkRasterHandleAllocator* allocator) { + SkAlphaType newAT = origInfo.alphaType(); + if (!valid_for_bitmap_device(origInfo, &newAT)) { + return nullptr; + } + + SkRasterHandleAllocator::Handle hndl = nullptr; + const SkImageInfo info = origInfo.makeAlphaType(newAT); + SkBitmap bitmap; + + if (kUnknown_SkColorType == info.colorType()) { + if (!bitmap.setInfo(info)) { + return nullptr; + } + } else if (allocator) { + hndl = allocator->allocBitmap(info, &bitmap); + if (!hndl) { + return nullptr; + } + } else if (info.isOpaque()) { + // If this bitmap is opaque, we don't have any sensible default color, + // so we just return uninitialized pixels. + if (!bitmap.tryAllocPixels(info)) { + return nullptr; + } + } else { + // This bitmap has transparency, so we'll zero the pixels (to transparent). + // We use the flag as a faster alloc-then-eraseColor(SK_ColorTRANSPARENT). + if (!bitmap.tryAllocPixelsFlags(info, SkBitmap::kZeroPixels_AllocFlag)) { + return nullptr; + } + } + + SkBitmap coverage; + if (trackCoverage) { + SkImageInfo ci = + SkImageInfo::Make(info.dimensions(), kAlpha_8_SkColorType, kPremul_SkAlphaType); + if (!coverage.tryAllocPixelsFlags(ci, SkBitmap::kZeroPixels_AllocFlag)) { + return nullptr; + } + } + + return new SkBitmapDevice(bitmap, surfaceProps, hndl, trackCoverage ? &coverage : nullptr); +} + +void SkBitmapDevice::replaceBitmapBackendForRasterSurface(const SkBitmap& bm) { + SkASSERT(bm.width() == fBitmap.width()); + SkASSERT(bm.height() == fBitmap.height()); + fBitmap = bm; // intent is to use bm's pixelRef (and rowbytes/config) + this->privateResize(fBitmap.info().width(), fBitmap.info().height()); +} + +SkBaseDevice* SkBitmapDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) { + const SkSurfaceProps surfaceProps(this->surfaceProps().flags(), cinfo.fPixelGeometry); + + // Need to force L32 for now if we have an image filter. + // If filters ever support other colortypes, e.g. F16, we can modify this check. + SkImageInfo info = cinfo.fInfo; + if (layerPaint && layerPaint->getImageFilter()) { + // TODO: can we query the imagefilter, to see if it can handle floats (so we don't always + // use N32 when the layer itself was float)? + info = info.makeColorType(kN32_SkColorType); + } + + return SkBitmapDevice::Create(info, surfaceProps, cinfo.fTrackCoverage, cinfo.fAllocator); +} + +bool SkBitmapDevice::onAccessPixels(SkPixmap* pmap) { + if (this->onPeekPixels(pmap)) { + fBitmap.notifyPixelsChanged(); + return true; + } + return false; +} + +bool SkBitmapDevice::onPeekPixels(SkPixmap* pmap) { + const SkImageInfo info = fBitmap.info(); + if (fBitmap.getPixels() && (kUnknown_SkColorType != info.colorType())) { + pmap->reset(fBitmap.info(), fBitmap.getPixels(), fBitmap.rowBytes()); + return true; + } + return false; +} + +bool SkBitmapDevice::onWritePixels(const SkPixmap& pm, int x, int y) { + // since we don't stop creating un-pixeled devices yet, check for no pixels here + if (nullptr == fBitmap.getPixels()) { + return false; + } + + if (fBitmap.writePixels(pm, x, y)) { + fBitmap.notifyPixelsChanged(); + return true; + } + return false; +} + +bool SkBitmapDevice::onReadPixels(const SkPixmap& pm, int x, int y) { + return fBitmap.readPixels(pm, x, y); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkBitmapDevice::drawPaint(const SkPaint& paint) { + BDDraw(this).drawPaint(paint); +} + +void SkBitmapDevice::drawPoints(SkCanvas::PointMode mode, size_t count, + const SkPoint pts[], const SkPaint& paint) { + LOOP_TILER( drawPoints(mode, count, pts, paint, nullptr), nullptr) +} + +void SkBitmapDevice::drawRect(const SkRect& r, const SkPaint& paint) { + LOOP_TILER( drawRect(r, paint), Bounder(r, paint)) +} + +void SkBitmapDevice::drawOval(const SkRect& oval, const SkPaint& paint) { + SkPath path; + path.addOval(oval); + // call the VIRTUAL version, so any subclasses who do handle drawPath aren't + // required to override drawOval. + this->drawPath(path, paint, true); +} + +void SkBitmapDevice::drawRRect(const SkRRect& rrect, const SkPaint& paint) { +#ifdef SK_IGNORE_BLURRED_RRECT_OPT + SkPath path; + + path.addRRect(rrect); + // call the VIRTUAL version, so any subclasses who do handle drawPath aren't + // required to override drawRRect. + this->drawPath(path, paint, true); +#else + LOOP_TILER( drawRRect(rrect, paint), Bounder(rrect.getBounds(), paint)) +#endif +} + +void SkBitmapDevice::drawPath(const SkPath& path, + const SkPaint& paint, + bool pathIsMutable) { + const SkRect* bounds = nullptr; + if (SkDrawTiler::NeedsTiling(this) && !path.isInverseFillType()) { + bounds = &path.getBounds(); + } + SkDrawTiler tiler(this, bounds ? Bounder(*bounds, paint).bounds() : nullptr); + if (tiler.needsTiling()) { + pathIsMutable = false; + } + while (const SkDraw* draw = tiler.next()) { + draw->drawPath(path, paint, nullptr, pathIsMutable); + } +} + +void SkBitmapDevice::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, + const SkRect* dstOrNull, const SkPaint& paint) { + const SkRect* bounds = dstOrNull; + SkRect storage; + if (!bounds && SkDrawTiler::NeedsTiling(this)) { + matrix.mapRect(&storage, SkRect::MakeIWH(bitmap.width(), bitmap.height())); + Bounder b(storage, paint); + if (b.hasBounds()) { + storage = *b.bounds(); + bounds = &storage; + } + } + LOOP_TILER(drawBitmap(bitmap, matrix, dstOrNull, paint), bounds) +} + +static inline bool CanApplyDstMatrixAsCTM(const SkMatrix& m, const SkPaint& paint) { + if (!paint.getMaskFilter()) { + return true; + } + + // Some mask filters parameters (sigma) depend on the CTM/scale. + return m.getType() <= SkMatrix::kTranslate_Mask; +} + +void SkBitmapDevice::drawBitmapRect(const SkBitmap& bitmap, + const SkRect* src, const SkRect& dst, + const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { + SkASSERT(dst.isFinite()); + SkASSERT(dst.isSorted()); + + SkMatrix matrix; + SkRect bitmapBounds, tmpSrc, tmpDst; + SkBitmap tmpBitmap; + + bitmapBounds.setIWH(bitmap.width(), bitmap.height()); + + // Compute matrix from the two rectangles + if (src) { + tmpSrc = *src; + } else { + tmpSrc = bitmapBounds; + } + matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); + + LogDrawScaleFactor(this->ctm(), matrix, paint.getFilterQuality()); + + const SkRect* dstPtr = &dst; + const SkBitmap* bitmapPtr = &bitmap; + + // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if + // needed (if the src was clipped). No check needed if src==null. + if (src) { + if (!bitmapBounds.contains(*src)) { + if (!tmpSrc.intersect(bitmapBounds)) { + return; // nothing to draw + } + // recompute dst, based on the smaller tmpSrc + matrix.mapRect(&tmpDst, tmpSrc); + if (!tmpDst.isFinite()) { + return; + } + dstPtr = &tmpDst; + } + } + + if (src && !src->contains(bitmapBounds) && + SkCanvas::kFast_SrcRectConstraint == constraint && + paint.getFilterQuality() != kNone_SkFilterQuality) { + // src is smaller than the bounds of the bitmap, and we are filtering, so we don't know + // how much more of the bitmap we need, so we can't use extractSubset or drawBitmap, + // but we must use a shader w/ dst bounds (which can access all of the bitmap needed). + goto USE_SHADER; + } + + if (src) { + // since we may need to clamp to the borders of the src rect within + // the bitmap, we extract a subset. + const SkIRect srcIR = tmpSrc.roundOut(); + if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { + return; + } + bitmapPtr = &tmpBitmap; + + // Since we did an extract, we need to adjust the matrix accordingly + SkScalar dx = 0, dy = 0; + if (srcIR.fLeft > 0) { + dx = SkIntToScalar(srcIR.fLeft); + } + if (srcIR.fTop > 0) { + dy = SkIntToScalar(srcIR.fTop); + } + if (dx || dy) { + matrix.preTranslate(dx, dy); + } + +#ifdef SK_DRAWBITMAPRECT_FAST_OFFSET + SkRect extractedBitmapBounds = SkRect::MakeXYWH(dx, dy, + SkIntToScalar(bitmapPtr->width()), + SkIntToScalar(bitmapPtr->height())); +#else + SkRect extractedBitmapBounds; + extractedBitmapBounds.setIWH(bitmapPtr->width(), bitmapPtr->height()); +#endif + if (extractedBitmapBounds == tmpSrc) { + // no fractional part in src, we can just call drawBitmap + goto USE_DRAWBITMAP; + } + } else { + USE_DRAWBITMAP: + // We can go faster by just calling drawBitmap, which will concat the + // matrix with the CTM, and try to call drawSprite if it can. If not, + // it will make a shader and call drawRect, as we do below. + if (CanApplyDstMatrixAsCTM(matrix, paint)) { + this->drawBitmap(*bitmapPtr, matrix, dstPtr, paint); + return; + } + } + + USE_SHADER: + + // TODO(herb): Move this over to SkArenaAlloc when arena alloc has a facility to return sk_sps. + // Since the shader need only live for our stack-frame, pass in a custom allocator. This + // can save malloc calls, and signals to SkMakeBitmapShader to not try to copy the bitmap + // if its mutable, since that precaution is not needed (give the short lifetime of the shader). + + // construct a shader, so we can call drawRect with the dst + auto s = SkMakeBitmapShaderForPaint(paint, *bitmapPtr, SkTileMode::kClamp, + SkTileMode::kClamp, &matrix, kNever_SkCopyPixelsMode); + if (!s) { + return; + } + + SkPaint paintWithShader(paint); + paintWithShader.setStyle(SkPaint::kFill_Style); + paintWithShader.setShader(std::move(s)); + + // Call ourself, in case the subclass wanted to share this setup code + // but handle the drawRect code themselves. + this->drawRect(*dstPtr, paintWithShader); +} + +void SkBitmapDevice::drawSprite(const SkBitmap& bitmap, int x, int y, const SkPaint& paint) { + BDDraw(this).drawSprite(bitmap, x, y, paint); +} + +void SkBitmapDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) { + LOOP_TILER( drawGlyphRunList(glyphRunList, &fGlyphPainter), nullptr ) +} + +void SkBitmapDevice::drawVertices(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode bmode, const SkPaint& paint) { + BDDraw(this).drawVertices(vertices->mode(), vertices->vertexCount(), vertices->positions(), + vertices->texCoords(), vertices->colors(), vertices->boneIndices(), + vertices->boneWeights(), bmode, vertices->indices(), + vertices->indexCount(), paint, bones, boneCount); +} + +void SkBitmapDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint& origPaint) { + SkASSERT(!origPaint.getImageFilter()); + + // todo: can we unify with similar adjustment in SkGpuDevice? + SkTCopyOnFirstWrite paint(origPaint); + if (paint->getMaskFilter()) { + paint.writable()->setMaskFilter(paint->getMaskFilter()->makeWithMatrix(this->ctm())); + } + + // hack to test coverage + SkBitmapDevice* src = static_cast(device); + if (src->fCoverage) { + SkDraw draw; + draw.fDst = fBitmap.pixmap(); + draw.fMatrix = &SkMatrix::I(); + draw.fRC = &fRCStack.rc(); + SkPaint paint(origPaint); + paint.setShader(src->fBitmap.makeShader()); + draw.drawBitmap(*src->fCoverage.get(), + SkMatrix::MakeTrans(SkIntToScalar(x),SkIntToScalar(y)), nullptr, paint); + } else { + this->drawSprite(src->fBitmap, x, y, *paint); + } +} + +void SkBitmapDevice::drawAtlas(const SkImage* atlas, const SkRSXform xform[], + const SkRect tex[], const SkColor colors[], int count, + SkBlendMode mode, const SkPaint& paint) { + // set this to true for performance comparisons with the old drawVertices way + if (false) { + this->INHERITED::drawAtlas(atlas, xform, tex, colors, count, mode, paint); + return; + } + BDDraw(this).drawAtlas(atlas, xform, tex, colors, count, mode, paint); +} + +/////////////////////////////////////////////////////////////////////////////// + +namespace { + +class SkAutoDeviceClipRestore { +public: + SkAutoDeviceClipRestore(SkBaseDevice* device, const SkIRect& clip) + : fDevice(device) + , fPrevCTM(device->ctm()) { + fDevice->save(); + fDevice->setCTM(SkMatrix::I()); + fDevice->clipRect(SkRect::Make(clip), SkClipOp::kIntersect, false); + fDevice->setCTM(fPrevCTM); + } + + ~SkAutoDeviceClipRestore() { + fDevice->restore(fPrevCTM); + } + +private: + SkBaseDevice* fDevice; + const SkMatrix fPrevCTM; +}; + +} // anonymous ns + +void SkBitmapDevice::drawSpecial(SkSpecialImage* src, int x, int y, const SkPaint& origPaint, + SkImage* clipImage, const SkMatrix& clipMatrix) { + SkASSERT(!src->isTextureBacked()); + + sk_sp filteredImage; + SkTCopyOnFirstWrite paint(origPaint); + + if (SkImageFilter* filter = paint->getImageFilter()) { + SkIPoint offset = SkIPoint::Make(0, 0); + const SkMatrix matrix = SkMatrix::Concat( + SkMatrix::MakeTrans(SkIntToScalar(-x), SkIntToScalar(-y)), this->ctm()); + const SkIRect clipBounds = fRCStack.rc().getBounds().makeOffset(-x, -y); + sk_sp cache(this->getImageFilterCache()); + SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), fBitmap.colorType(), + fBitmap.colorSpace(), src); + + filteredImage = as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset); + if (!filteredImage) { + return; + } + + src = filteredImage.get(); + paint.writable()->setImageFilter(nullptr); + x += offset.x(); + y += offset.y(); + } + + if (paint->getMaskFilter()) { + paint.writable()->setMaskFilter(paint->getMaskFilter()->makeWithMatrix(this->ctm())); + } + + if (!clipImage) { + SkBitmap resultBM; + if (src->getROPixels(&resultBM)) { + this->drawSprite(resultBM, x, y, *paint); + } + return; + } + + // Clip image case. + sk_sp srcImage(src->asImage()); + if (!srcImage) { + return; + } + + const SkMatrix totalMatrix = SkMatrix::Concat(this->ctm(), clipMatrix); + SkRect clipBounds; + totalMatrix.mapRect(&clipBounds, SkRect::Make(clipImage->bounds())); + const SkIRect srcBounds = srcImage->bounds().makeOffset(x, y); + + SkIRect maskBounds = fRCStack.rc().getBounds(); + if (!maskBounds.intersect(clipBounds.roundOut()) || !maskBounds.intersect(srcBounds)) { + return; + } + + sk_sp mask; + SkMatrix maskMatrix, shaderMatrix; + SkTLazy autoClipRestore; + + SkMatrix totalInverse; + if (clipImage->isAlphaOnly() && totalMatrix.invert(&totalInverse)) { + // If the mask is already in A8 format, we can draw it directly + // (while compensating in the shader matrix). + mask = sk_ref_sp(clipImage); + maskMatrix = totalMatrix; + shaderMatrix = SkMatrix::Concat(totalInverse, SkMatrix::MakeTrans(x, y)); + + // If the mask is not fully contained within the src layer, we must clip. + if (!srcBounds.contains(clipBounds)) { + autoClipRestore.init(this, srcBounds); + } + + maskBounds.offsetTo(0, 0); + } else { + // Otherwise, we convert the mask to A8 explicitly. + sk_sp surf = SkSurface::MakeRaster(SkImageInfo::MakeA8(maskBounds.width(), + maskBounds.height())); + SkCanvas* canvas = surf->getCanvas(); + canvas->translate(-maskBounds.x(), -maskBounds.y()); + canvas->concat(totalMatrix); + canvas->drawImage(clipImage, 0, 0); + + mask = surf->makeImageSnapshot(); + maskMatrix = SkMatrix::I(); + shaderMatrix = SkMatrix::MakeTrans(x - maskBounds.x(), y - maskBounds.y()); + } + + SkAutoDeviceCTMRestore adctmr(this, maskMatrix); + paint.writable()->setShader(srcImage->makeShader(&shaderMatrix)); + this->drawImageRect(mask.get(), nullptr, + SkRect::MakeXYWH(maskBounds.x(), maskBounds.y(), + mask->width(), mask->height()), + *paint, SkCanvas::kFast_SrcRectConstraint); +} + +sk_sp SkBitmapDevice::makeSpecial(const SkBitmap& bitmap) { + return SkSpecialImage::MakeFromRaster(bitmap.bounds(), bitmap); +} + +sk_sp SkBitmapDevice::makeSpecial(const SkImage* image) { + return SkSpecialImage::MakeFromImage(nullptr, SkIRect::MakeWH(image->width(), image->height()), + image->makeNonTextureImage()); +} + +sk_sp SkBitmapDevice::snapSpecial(const SkIRect& bounds, bool forceCopy) { + if (forceCopy) { + return SkSpecialImage::CopyFromRaster(bounds, fBitmap, &this->surfaceProps()); + } else { + return SkSpecialImage::MakeFromRaster(bounds, fBitmap); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkBitmapDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) { + return SkSurface::MakeRaster(info, &props); +} + +SkImageFilterCache* SkBitmapDevice::getImageFilterCache() { + SkImageFilterCache* cache = SkImageFilterCache::Get(); + cache->ref(); + return cache; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkBitmapDevice::onSave() { + fRCStack.save(); +} + +void SkBitmapDevice::onRestore() { + fRCStack.restore(); +} + +void SkBitmapDevice::onClipRect(const SkRect& rect, SkClipOp op, bool aa) { + fRCStack.clipRect(this->ctm(), rect, op, aa); +} + +void SkBitmapDevice::onClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) { + fRCStack.clipRRect(this->ctm(), rrect, op, aa); +} + +void SkBitmapDevice::onClipPath(const SkPath& path, SkClipOp op, bool aa) { + fRCStack.clipPath(this->ctm(), path, op, aa); +} + +void SkBitmapDevice::onClipRegion(const SkRegion& rgn, SkClipOp op) { + SkIPoint origin = this->getOrigin(); + SkRegion tmp; + const SkRegion* ptr = &rgn; + if (origin.fX | origin.fY) { + // translate from "global/canvas" coordinates to relative to this device + rgn.translate(-origin.fX, -origin.fY, &tmp); + ptr = &tmp; + } + fRCStack.clipRegion(*ptr, op); +} + +void SkBitmapDevice::onSetDeviceClipRestriction(SkIRect* mutableClipRestriction) { + fRCStack.setDeviceClipRestriction(mutableClipRestriction); + if (!mutableClipRestriction->isEmpty()) { + SkRegion rgn(*mutableClipRestriction); + fRCStack.clipRegion(rgn, SkClipOp::kIntersect); + } +} + +bool SkBitmapDevice::onClipIsAA() const { + const SkRasterClip& rc = fRCStack.rc(); + return !rc.isEmpty() && rc.isAA(); +} + +void SkBitmapDevice::onAsRgnClip(SkRegion* rgn) const { + const SkRasterClip& rc = fRCStack.rc(); + if (rc.isAA()) { + rgn->setRect(rc.getBounds()); + } else { + *rgn = rc.bwRgn(); + } +} + +void SkBitmapDevice::validateDevBounds(const SkIRect& drawClipBounds) { +#ifdef SK_DEBUG + const SkIRect& stackBounds = fRCStack.rc().getBounds(); + SkASSERT(drawClipBounds == stackBounds); +#endif +} + +SkBaseDevice::ClipType SkBitmapDevice::onGetClipType() const { + const SkRasterClip& rc = fRCStack.rc(); + if (rc.isEmpty()) { + return ClipType::kEmpty; + } else if (rc.isRect()) { + return ClipType::kRect; + } else { + return ClipType::kComplex; + } +} diff --git a/gfx/skia/skia/src/core/SkBitmapDevice.h b/gfx/skia/skia/src/core/SkBitmapDevice.h new file mode 100644 index 0000000000..e0a777fc65 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapDevice.h @@ -0,0 +1,191 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapDevice_DEFINED +#define SkBitmapDevice_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkRect.h" +#include "include/core/SkScalar.h" +#include "include/core/SkSize.h" +#include "include/core/SkSurfaceProps.h" +#include "src/core/SkDevice.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkRasterClipStack.h" + +class SkImageFilterCache; +class SkMatrix; +class SkPaint; +class SkPath; +class SkPixmap; +class SkRasterHandleAllocator; +class SkRRect; +class SkSurface; +struct SkPoint; + +/////////////////////////////////////////////////////////////////////////////// +class SkBitmapDevice : public SkBaseDevice { +public: + /** + * Construct a new device with the specified bitmap as its backend. It is + * valid for the bitmap to have no pixels associated with it. In that case, + * any drawing to this device will have no effect. + */ + SkBitmapDevice(const SkBitmap& bitmap); + + /** + * Create a new device along with its requisite pixel memory using + * default SkSurfaceProps (i.e., kLegacyFontHost_InitType-style). + * Note: this entry point is slated for removal - no one should call it. + */ + static SkBitmapDevice* Create(const SkImageInfo& info); + + /** + * Construct a new device with the specified bitmap as its backend. It is + * valid for the bitmap to have no pixels associated with it. In that case, + * any drawing to this device will have no effect. + */ + SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps, + void* externalHandle, const SkBitmap* coverage); + + static SkBitmapDevice* Create(const SkImageInfo&, const SkSurfaceProps&, + bool trackCoverage, + SkRasterHandleAllocator*); + + static SkBitmapDevice* Create(const SkImageInfo& info, const SkSurfaceProps& props) { + return Create(info, props, false, nullptr); + } + + const SkPixmap* accessCoverage() const { + return fCoverage ? &fCoverage->pixmap() : nullptr; + } + +protected: + void* getRasterHandle() const override { return fRasterHandle; } + + /** These are called inside the per-device-layer loop for each draw call. + When these are called, we have already applied any saveLayer operations, + and are handling any looping from the paint. + */ + void drawPaint(const SkPaint& paint) override; + void drawPoints(SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint) override; + void drawRect(const SkRect& r, const SkPaint& paint) override; + void drawOval(const SkRect& oval, const SkPaint& paint) override; + void drawRRect(const SkRRect& rr, const SkPaint& paint) override; + + /** + * If pathIsMutable, then the implementation is allowed to cast path to a + * non-const pointer and modify it in place (as an optimization). Canvas + * may do this to implement helpers such as drawOval, by placing a temp + * path on the stack to hold the representation of the oval. + */ + void drawPath(const SkPath&, const SkPaint&, bool pathIsMutable) override; + void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) override; + + /** + * The default impl. will create a bitmap-shader from the bitmap, + * and call drawRect with it. + */ + void drawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, + const SkPaint&, SkCanvas::SrcRectConstraint) override; + + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override; + void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, SkBlendMode, + const SkPaint& paint) override; + void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int count, + SkBlendMode, const SkPaint&) override; + void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) override; + + /////////////////////////////////////////////////////////////////////////// + + void drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&, + SkImage*, const SkMatrix&) override; + sk_sp makeSpecial(const SkBitmap&) override; + sk_sp makeSpecial(const SkImage*) override; + sk_sp snapSpecial(const SkIRect&, bool = false) override; + void setImmutable() override { fBitmap.setImmutable(); } + + /////////////////////////////////////////////////////////////////////////// + + bool onReadPixels(const SkPixmap&, int x, int y) override; + bool onWritePixels(const SkPixmap&, int, int) override; + bool onPeekPixels(SkPixmap*) override; + bool onAccessPixels(SkPixmap*) override; + + void onSave() override; + void onRestore() override; + void onClipRect(const SkRect& rect, SkClipOp, bool aa) override; + void onClipRRect(const SkRRect& rrect, SkClipOp, bool aa) override; + void onClipPath(const SkPath& path, SkClipOp, bool aa) override; + void onClipRegion(const SkRegion& deviceRgn, SkClipOp) override; + void onSetDeviceClipRestriction(SkIRect* mutableClipRestriction) override; + bool onClipIsAA() const override; + void onAsRgnClip(SkRegion*) const override; + void validateDevBounds(const SkIRect& r) override; + ClipType onGetClipType() const override; + + virtual void drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect* dstOrNull, + const SkPaint&); + +private: + friend class SkCanvas; + friend struct DeviceCM; //for setMatrixClip + friend class SkDraw; + friend class SkDrawIter; + friend class SkDrawTiler; + friend class SkSurface_Raster; + + class BDDraw; + + // used to change the backend's pixels (and possibly config/rowbytes) + // but cannot change the width/height, so there should be no change to + // any clip information. + void replaceBitmapBackendForRasterSurface(const SkBitmap&) override; + + SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override; + + sk_sp makeSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + SkImageFilterCache* getImageFilterCache() override; + + SkBitmap fBitmap; + void* fRasterHandle = nullptr; + SkRasterClipStack fRCStack; + std::unique_ptr fCoverage; // if non-null, will have the same dimensions as fBitmap + SkGlyphRunListPainter fGlyphPainter; + + + typedef SkBaseDevice INHERITED; +}; + +class SkBitmapDeviceFilteredSurfaceProps { +public: + SkBitmapDeviceFilteredSurfaceProps(const SkBitmap& bitmap, const SkPaint& paint, + const SkSurfaceProps& surfaceProps) + : fSurfaceProps((kN32_SkColorType != bitmap.colorType() || !paint.isSrcOver()) + ? fLazy.init(surfaceProps.flags(), kUnknown_SkPixelGeometry) + : &surfaceProps) + { } + + SkBitmapDeviceFilteredSurfaceProps(const SkBitmapDeviceFilteredSurfaceProps&) = delete; + SkBitmapDeviceFilteredSurfaceProps& operator=(const SkBitmapDeviceFilteredSurfaceProps&) = delete; + SkBitmapDeviceFilteredSurfaceProps(SkBitmapDeviceFilteredSurfaceProps&&) = delete; + SkBitmapDeviceFilteredSurfaceProps& operator=(SkBitmapDeviceFilteredSurfaceProps&&) = delete; + + const SkSurfaceProps& operator()() const { return *fSurfaceProps; } + +private: + SkTLazy fLazy; + SkSurfaceProps const * const fSurfaceProps; +}; + +#endif // SkBitmapDevice_DEFINED diff --git a/gfx/skia/skia/src/core/SkBitmapFilter.h b/gfx/skia/skia/src/core/SkBitmapFilter.h new file mode 100644 index 0000000000..f7f1f07f24 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapFilter.h @@ -0,0 +1,209 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapFilter_DEFINED +#define SkBitmapFilter_DEFINED + +#include "include/private/SkFixed.h" +#include "include/core/SkMath.h" +#include "include/core/SkScalar.h" + +#include "include/private/SkNx.h" + +// size of the precomputed bitmap filter tables for high quality filtering. +// Used to precompute the shape of the filter kernel. +// Table size chosen from experiments to see where I could start to see a difference. + +#define SKBITMAP_FILTER_TABLE_SIZE 128 + +class SkBitmapFilter { +public: + SkBitmapFilter(float width) : fWidth(width), fInvWidth(1.f/width) { + fPrecomputed = false; + fLookupMultiplier = this->invWidth() * (SKBITMAP_FILTER_TABLE_SIZE-1); + } + virtual ~SkBitmapFilter() {} + + SkScalar lookupScalar(float x) const { + if (!fPrecomputed) { + precomputeTable(); + } + int filter_idx = int(sk_float_abs(x * fLookupMultiplier)); + SkASSERT(filter_idx < SKBITMAP_FILTER_TABLE_SIZE); + return fFilterTableScalar[filter_idx]; + } + + float width() const { return fWidth; } + float invWidth() const { return fInvWidth; } + virtual float evaluate(float x) const = 0; + + virtual float evaluate_n(float val, float diff, int count, float* output) const { + float sum = 0; + for (int index = 0; index < count; index++) { + float filterValue = evaluate(val); + *output++ = filterValue; + sum += filterValue; + val += diff; + } + return sum; + } + +protected: + float fWidth; + float fInvWidth; + float fLookupMultiplier; + + mutable bool fPrecomputed; + mutable SkScalar fFilterTableScalar[SKBITMAP_FILTER_TABLE_SIZE]; + +private: + void precomputeTable() const { + fPrecomputed = true; + SkScalar *ftpScalar = fFilterTableScalar; + for (int x = 0; x < SKBITMAP_FILTER_TABLE_SIZE; ++x) { + float fx = ((float)x + .5f) * this->width() / SKBITMAP_FILTER_TABLE_SIZE; + float filter_value = evaluate(fx); + *ftpScalar++ = filter_value; + } + } +}; + +class SkMitchellFilter final : public SkBitmapFilter { +public: + SkMitchellFilter() + : INHERITED(2) + , fB(1.f / 3.f) + , fC(1.f / 3.f) + , fA1(-fB - 6*fC) + , fB1(6*fB + 30*fC) + , fC1(-12*fB - 48*fC) + , fD1(8*fB + 24*fC) + , fA2(12 - 9*fB - 6*fC) + , fB2(-18 + 12*fB + 6*fC) + , fD2(6 - 2*fB) + {} + + float evaluate(float x) const override { + x = fabsf(x); + if (x > 2.f) { + return 0; + } else if (x > 1.f) { + return (((fA1 * x + fB1) * x + fC1) * x + fD1) * (1.f/6.f); + } else { + return ((fA2 * x + fB2) * x*x + fD2) * (1.f/6.f); + } + } + + Sk4f evalcore_n(const Sk4f& val) const { + Sk4f x = val.abs(); + Sk4f over2 = x > Sk4f(2); + Sk4f over1 = x > Sk4f(1); + Sk4f poly1 = (((Sk4f(fA1) * x + Sk4f(fB1)) * x + Sk4f(fC1)) * x + Sk4f(fD1)) + * Sk4f(1.f/6.f); + Sk4f poly0 = ((Sk4f(fA2) * x + Sk4f(fB2)) * x*x + Sk4f(fD2)) * Sk4f(1.f/6.f); + return over2.thenElse(Sk4f(0), over1.thenElse(poly1, poly0)); + } + + float evaluate_n(float val, float diff, int count, float* output) const override { + Sk4f sum(0); + while (count >= 4) { + float v0 = val; + float v1 = val += diff; + float v2 = val += diff; + float v3 = val += diff; + val += diff; + Sk4f filterValue = evalcore_n(Sk4f(v0, v1, v2, v3)); + filterValue.store(output); + output += 4; + sum = sum + filterValue; + count -= 4; + } + float sums[4]; + sum.store(sums); + float result = sums[0] + sums[1] + sums[2] + sums[3]; + result += INHERITED::evaluate_n(val, diff, count, output); + return result; + } + + protected: + float fB, fC; + float fA1, fB1, fC1, fD1; + float fA2, fB2, fD2; +private: + typedef SkBitmapFilter INHERITED; +}; + +class SkGaussianFilter final : public SkBitmapFilter { + float fAlpha, fExpWidth; + +public: + SkGaussianFilter(float a, float width = 2) + : SkBitmapFilter(width) + , fAlpha(a) + , fExpWidth(expf(-a * width * width)) + {} + + float evaluate(float x) const override { + return SkTMax(0.f, float(expf(-fAlpha*x*x) - fExpWidth)); + } +}; + +class SkTriangleFilter final : public SkBitmapFilter { +public: + SkTriangleFilter(float width = 1) : SkBitmapFilter(width) {} + + float evaluate(float x) const override { + return SkTMax(0.f, fWidth - fabsf(x)); + } +}; + +class SkBoxFilter final : public SkBitmapFilter { +public: + SkBoxFilter(float width = 0.5f) : SkBitmapFilter(width) {} + + float evaluate(float x) const override { + return (x >= -fWidth && x < fWidth) ? 1.0f : 0.0f; + } +}; + +class SkHammingFilter final : public SkBitmapFilter { +public: + SkHammingFilter(float width = 1) : SkBitmapFilter(width) {} + + float evaluate(float x) const override { + if (x <= -fWidth || x >= fWidth) { + return 0.0f; // Outside of the window. + } + if (x > -FLT_EPSILON && x < FLT_EPSILON) { + return 1.0f; // Special case the sinc discontinuity at the origin. + } + const float xpi = x * static_cast(SK_ScalarPI); + + return ((sk_float_sin(xpi) / xpi) * // sinc(x) + (0.54f + 0.46f * sk_float_cos(xpi / fWidth))); // hamming(x) + } +}; + +class SkLanczosFilter final : public SkBitmapFilter { +public: + SkLanczosFilter(float width = 3.f) : SkBitmapFilter(width) {} + + float evaluate(float x) const override { + if (x <= -fWidth || x >= fWidth) { + return 0.0f; // Outside of the window. + } + if (x > -FLT_EPSILON && x < FLT_EPSILON) { + return 1.0f; // Special case the discontinuity at the origin. + } + float xpi = x * static_cast(SK_ScalarPI); + return (sk_float_sin(xpi) / xpi) * // sinc(x) + sk_float_sin(xpi / fWidth) / (xpi / fWidth); // sinc(x/fWidth) + } +}; + + +#endif diff --git a/gfx/skia/skia/src/core/SkBitmapProcState.cpp b/gfx/skia/skia/src/core/SkBitmapProcState.cpp new file mode 100644 index 0000000000..064f5d19ba --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapProcState.cpp @@ -0,0 +1,660 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImageEncoder.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkBitmapController.h" +#include "src/core/SkBitmapProcState.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkOpts.h" +#include "src/core/SkResourceCache.h" +#include "src/core/SkUtils.h" + +// One-stop-shop shader for, +// - nearest-neighbor sampling (_nofilter_), +// - clamp tiling in X and Y both (Clamp_), +// - with at most a scale and translate matrix (_DX_), +// - and no extra alpha applied (_opaque_), +// - sampling from 8888 (_S32_) and drawing to 8888 (_S32_). +static void Clamp_S32_opaque_D32_nofilter_DX_shaderproc(const void* sIn, int x, int y, + SkPMColor* dst, int count) { + const SkBitmapProcState& s = *static_cast(sIn); + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | + SkMatrix::kScale_Mask)) == 0); + SkASSERT(s.fAlphaScale == 256); + + const unsigned maxX = s.fPixmap.width() - 1; + SkFractionalInt fx; + int dstY; + { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + const unsigned maxY = s.fPixmap.height() - 1; + dstY = SkClampMax(mapper.intY(), maxY); + fx = mapper.fractionalIntX(); + } + + const SkPMColor* src = s.fPixmap.addr32(0, dstY); + const SkFractionalInt dx = s.fInvSxFractionalInt; + + // Check if we're safely inside [0...maxX] so no need to clamp each computed index. + // + if ((uint64_t)SkFractionalIntToInt(fx) <= maxX && + (uint64_t)SkFractionalIntToInt(fx + dx * (count - 1)) <= maxX) + { + int count4 = count >> 2; + for (int i = 0; i < count4; ++i) { + SkPMColor src0 = src[SkFractionalIntToInt(fx)]; fx += dx; + SkPMColor src1 = src[SkFractionalIntToInt(fx)]; fx += dx; + SkPMColor src2 = src[SkFractionalIntToInt(fx)]; fx += dx; + SkPMColor src3 = src[SkFractionalIntToInt(fx)]; fx += dx; + dst[0] = src0; + dst[1] = src1; + dst[2] = src2; + dst[3] = src3; + dst += 4; + } + for (int i = (count4 << 2); i < count; ++i) { + unsigned index = SkFractionalIntToInt(fx); + SkASSERT(index <= maxX); + *dst++ = src[index]; + fx += dx; + } + } else { + for (int i = 0; i < count; ++i) { + dst[i] = src[SkClampMax(SkFractionalIntToInt(fx), maxX)]; + fx += dx; + } + } +} + +static void S32_alpha_D32_nofilter_DX(const SkBitmapProcState& s, + const uint32_t* xy, int count, SkPMColor* colors) { + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(s.fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)); + SkASSERT(kNone_SkFilterQuality == s.fFilterQuality); + SkASSERT(4 == s.fPixmap.info().bytesPerPixel()); + SkASSERT(s.fAlphaScale <= 256); + + // xy is a 32-bit y-coordinate, followed by 16-bit x-coordinates. + unsigned y = *xy++; + SkASSERT(y < (unsigned)s.fPixmap.height()); + + auto row = (const SkPMColor*)( (const char*)s.fPixmap.addr() + y * s.fPixmap.rowBytes() ); + + if (1 == s.fPixmap.width()) { + sk_memset32(colors, SkAlphaMulQ(row[0], s.fAlphaScale), count); + return; + } + + // Step 4 xs == 2 uint32_t at a time. + while (count >= 4) { + uint32_t x01 = *xy++, + x23 = *xy++; + + SkPMColor p0 = row[UNPACK_PRIMARY_SHORT (x01)]; + SkPMColor p1 = row[UNPACK_SECONDARY_SHORT(x01)]; + SkPMColor p2 = row[UNPACK_PRIMARY_SHORT (x23)]; + SkPMColor p3 = row[UNPACK_SECONDARY_SHORT(x23)]; + + *colors++ = SkAlphaMulQ(p0, s.fAlphaScale); + *colors++ = SkAlphaMulQ(p1, s.fAlphaScale); + *colors++ = SkAlphaMulQ(p2, s.fAlphaScale); + *colors++ = SkAlphaMulQ(p3, s.fAlphaScale); + + count -= 4; + } + + // Step 1 x == 1 uint16_t at a time. + auto x = (const uint16_t*)xy; + while (count --> 0) { + *colors++ = SkAlphaMulQ(row[*x++], s.fAlphaScale); + } +} + +SkBitmapProcInfo::SkBitmapProcInfo(const SkImage_Base* image, SkTileMode tmx, SkTileMode tmy) + : fImage(image) + , fTileModeX(tmx) + , fTileModeY(tmy) + , fBMState(nullptr) +{} + +SkBitmapProcInfo::~SkBitmapProcInfo() {} + + +// true iff the matrix has a scale and no more than an optional translate. +static bool matrix_only_scale_translate(const SkMatrix& m) { + return (m.getType() & ~SkMatrix::kTranslate_Mask) == SkMatrix::kScale_Mask; +} + +/** + * For the purposes of drawing bitmaps, if a matrix is "almost" translate + * go ahead and treat it as if it were, so that subsequent code can go fast. + */ +static bool just_trans_general(const SkMatrix& matrix) { + SkASSERT(matrix_only_scale_translate(matrix)); + + const SkScalar tol = SK_Scalar1 / 32768; + + return SkScalarNearlyZero(matrix[SkMatrix::kMScaleX] - SK_Scalar1, tol) + && SkScalarNearlyZero(matrix[SkMatrix::kMScaleY] - SK_Scalar1, tol); +} + +/** + * Determine if the matrix can be treated as integral-only-translate, + * for the purpose of filtering. + */ +static bool just_trans_integral(const SkMatrix& m) { + static constexpr SkScalar tol = SK_Scalar1 / 256; + + return m.getType() <= SkMatrix::kTranslate_Mask + && SkScalarNearlyEqual(m.getTranslateX(), SkScalarRoundToScalar(m.getTranslateX()), tol) + && SkScalarNearlyEqual(m.getTranslateY(), SkScalarRoundToScalar(m.getTranslateY()), tol); +} + +static bool valid_for_filtering(unsigned dimension) { + // for filtering, width and height must fit in 14bits, since we use steal + // 2 bits from each to store our 4bit subpixel data + return (dimension & ~0x3FFF) == 0; +} + +bool SkBitmapProcInfo::init(const SkMatrix& inv, const SkPaint& paint) { + SkASSERT(inv.isScaleTranslate()); + + fPixmap.reset(); + fInvMatrix = inv; + fFilterQuality = paint.getFilterQuality(); + + fBMState = SkBitmapController::RequestBitmap(fImage, inv, paint.getFilterQuality(), &fAlloc); + + // Note : we allow the controller to return an empty (zero-dimension) result. Should we? + if (nullptr == fBMState || fBMState->pixmap().info().isEmpty()) { + return false; + } + fPixmap = fBMState->pixmap(); + fInvMatrix = fBMState->invMatrix(); + fRealInvMatrix = fBMState->invMatrix(); + fPaintColor = paint.getColor(); + fFilterQuality = fBMState->quality(); + SkASSERT(fFilterQuality <= kLow_SkFilterQuality); + SkASSERT(fPixmap.addr()); + + bool integral_translate_only = just_trans_integral(fInvMatrix); + if (!integral_translate_only) { + // Most of the scanline procs deal with "unit" texture coordinates, as this + // makes it easy to perform tiling modes (repeat = (x & 0xFFFF)). To generate + // those, we divide the matrix by its dimensions here. + // + // We don't do this if we're either trivial (can ignore the matrix) or clamping + // in both X and Y since clamping to width,height is just as easy as to 0xFFFF. + + if (fTileModeX != SkTileMode::kClamp || fTileModeY != SkTileMode::kClamp) { + fInvMatrix.postIDiv(fPixmap.width(), fPixmap.height()); + } + + // Now that all possible changes to the matrix have taken place, check + // to see if we're really close to a no-scale matrix. If so, explicitly + // set it to be so. Subsequent code may inspect this matrix to choose + // a faster path in this case. + + // This code will only execute if the matrix has some scale component; + // if it's already pure translate then we won't do this inversion. + + if (matrix_only_scale_translate(fInvMatrix)) { + SkMatrix forward; + if (fInvMatrix.invert(&forward) && just_trans_general(forward)) { + fInvMatrix.setTranslate(-forward.getTranslateX(), -forward.getTranslateY()); + } + } + + // Recompute the flag after matrix adjustments. + integral_translate_only = just_trans_integral(fInvMatrix); + } + + fInvType = fInvMatrix.getType(); + + if (kLow_SkFilterQuality == fFilterQuality && + (!valid_for_filtering(fPixmap.width() | fPixmap.height()) || + integral_translate_only)) { + fFilterQuality = kNone_SkFilterQuality; + } + + return true; +} + +/* + * Analyze filter-quality and matrix, and decide how to implement that. + * + * In general, we cascade down the request level [ High ... None ] + * - for a given level, if we can fulfill it, fine, else + * - else we downgrade to the next lower level and try again. + * We can always fulfill requests for Low and None + * - sometimes we will "ignore" Low and give None, but this is likely a legacy perf hack + * and may be removed. + */ +bool SkBitmapProcState::chooseProcs() { + SkASSERT(fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)); + SkASSERT(fPixmap.colorType() == kN32_SkColorType); + SkASSERT(fPixmap.alphaType() == kPremul_SkAlphaType || + fPixmap.alphaType() == kOpaque_SkAlphaType); + SkASSERT(fTileModeX == fTileModeY); + SkASSERT(fTileModeX != SkTileMode::kDecal); + SkASSERT(fFilterQuality < kHigh_SkFilterQuality); + + fInvProc = SkMatrixPriv::GetMapXYProc(fInvMatrix); + fInvSx = SkScalarToFixed (fInvMatrix.getScaleX()); + fInvSxFractionalInt = SkScalarToFractionalInt(fInvMatrix.getScaleX()); + fInvKy = SkScalarToFixed (fInvMatrix.getSkewY()); + fInvKyFractionalInt = SkScalarToFractionalInt(fInvMatrix.getSkewY()); + + fAlphaScale = SkAlpha255To256(SkColorGetA(fPaintColor)); + + bool translate_only = (fInvMatrix.getType() & ~SkMatrix::kTranslate_Mask) == 0; + fMatrixProc = this->chooseMatrixProc(translate_only); + SkASSERT(fMatrixProc); + + if (fFilterQuality > kNone_SkFilterQuality) { + fSampleProc32 = SkOpts::S32_alpha_D32_filter_DX; + } else { + fSampleProc32 = S32_alpha_D32_nofilter_DX; + } + + fShaderProc32 = this->chooseShaderProc32(); + + // our special-case shaderprocs + // TODO: move this one into chooseShaderProc32() or pull all that in here. + if (nullptr == fShaderProc32 + && fAlphaScale == 256 + && fFilterQuality == kNone_SkFilterQuality + && SkTileMode::kClamp == fTileModeX) { + fShaderProc32 = Clamp_S32_opaque_D32_nofilter_DX_shaderproc; + } + + return true; +} + +static void Clamp_S32_D32_nofilter_trans_shaderproc(const void* sIn, + int x, int y, + SkPMColor* colors, + int count) { + const SkBitmapProcState& s = *static_cast(sIn); + SkASSERT(((s.fInvType & ~SkMatrix::kTranslate_Mask)) == 0); + SkASSERT(s.fInvKy == 0); + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(kNone_SkFilterQuality == s.fFilterQuality); + + const int maxX = s.fPixmap.width() - 1; + const int maxY = s.fPixmap.height() - 1; + int ix = s.fFilterOneX + x; + int iy = SkClampMax(s.fFilterOneY + y, maxY); + const SkPMColor* row = s.fPixmap.addr32(0, iy); + + // clamp to the left + if (ix < 0) { + int n = SkMin32(-ix, count); + sk_memset32(colors, row[0], n); + count -= n; + if (0 == count) { + return; + } + colors += n; + SkASSERT(-ix == n); + ix = 0; + } + // copy the middle + if (ix <= maxX) { + int n = SkMin32(maxX - ix + 1, count); + memcpy(colors, row + ix, n * sizeof(SkPMColor)); + count -= n; + if (0 == count) { + return; + } + colors += n; + } + SkASSERT(count > 0); + // clamp to the right + sk_memset32(colors, row[maxX], count); +} + +static inline int sk_int_mod(int x, int n) { + SkASSERT(n > 0); + if ((unsigned)x >= (unsigned)n) { + if (x < 0) { + x = n + ~(~x % n); + } else { + x = x % n; + } + } + return x; +} + +static inline int sk_int_mirror(int x, int n) { + x = sk_int_mod(x, 2 * n); + if (x >= n) { + x = n + ~(x - n); + } + return x; +} + +static void Repeat_S32_D32_nofilter_trans_shaderproc(const void* sIn, + int x, int y, + SkPMColor* colors, + int count) { + const SkBitmapProcState& s = *static_cast(sIn); + SkASSERT(((s.fInvType & ~SkMatrix::kTranslate_Mask)) == 0); + SkASSERT(s.fInvKy == 0); + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(kNone_SkFilterQuality == s.fFilterQuality); + + const int stopX = s.fPixmap.width(); + const int stopY = s.fPixmap.height(); + int ix = s.fFilterOneX + x; + int iy = sk_int_mod(s.fFilterOneY + y, stopY); + const SkPMColor* row = s.fPixmap.addr32(0, iy); + + ix = sk_int_mod(ix, stopX); + for (;;) { + int n = SkMin32(stopX - ix, count); + memcpy(colors, row + ix, n * sizeof(SkPMColor)); + count -= n; + if (0 == count) { + return; + } + colors += n; + ix = 0; + } +} + +static inline void filter_32_alpha(unsigned t, + SkPMColor color0, + SkPMColor color1, + SkPMColor* dstColor, + unsigned alphaScale) { + SkASSERT((unsigned)t <= 0xF); + SkASSERT(alphaScale <= 256); + + const uint32_t mask = 0xFF00FF; + + int scale = 256 - 16*t; + uint32_t lo = (color0 & mask) * scale; + uint32_t hi = ((color0 >> 8) & mask) * scale; + + scale = 16*t; + lo += (color1 & mask) * scale; + hi += ((color1 >> 8) & mask) * scale; + + // TODO: if (alphaScale < 256) ... + lo = ((lo >> 8) & mask) * alphaScale; + hi = ((hi >> 8) & mask) * alphaScale; + + *dstColor = ((lo >> 8) & mask) | (hi & ~mask); +} + +static void S32_D32_constX_shaderproc(const void* sIn, + int x, int y, + SkPMColor* colors, + int count) { + const SkBitmapProcState& s = *static_cast(sIn); + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) == 0); + SkASSERT(s.fInvKy == 0); + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(1 == s.fPixmap.width()); + + int iY0; + int iY1 SK_INIT_TO_AVOID_WARNING; + int iSubY SK_INIT_TO_AVOID_WARNING; + + if (kNone_SkFilterQuality != s.fFilterQuality) { + SkBitmapProcState::MatrixProc mproc = s.getMatrixProc(); + uint32_t xy[2]; + + mproc(s, xy, 1, x, y); + + iY0 = xy[0] >> 18; + iY1 = xy[0] & 0x3FFF; + iSubY = (xy[0] >> 14) & 0xF; + } else { + int yTemp; + + if (s.fInvType > SkMatrix::kTranslate_Mask) { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + + // When the matrix has a scale component the setup code in + // chooseProcs multiples the inverse matrix by the inverse of the + // bitmap's width and height. Since this method is going to do + // its own tiling and sampling we need to undo that here. + if (SkTileMode::kClamp != s.fTileModeX || SkTileMode::kClamp != s.fTileModeY) { + yTemp = SkFractionalIntToInt(mapper.fractionalIntY() * s.fPixmap.height()); + } else { + yTemp = mapper.intY(); + } + } else { + yTemp = s.fFilterOneY + y; + } + + const int stopY = s.fPixmap.height(); + switch (s.fTileModeY) { + case SkTileMode::kClamp: + iY0 = SkClampMax(yTemp, stopY-1); + break; + case SkTileMode::kRepeat: + iY0 = sk_int_mod(yTemp, stopY); + break; + case SkTileMode::kMirror: + default: + iY0 = sk_int_mirror(yTemp, stopY); + break; + } + +#ifdef SK_DEBUG + { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + int iY2; + + if (s.fInvType > SkMatrix::kTranslate_Mask && + (SkTileMode::kClamp != s.fTileModeX || SkTileMode::kClamp != s.fTileModeY)) { + iY2 = SkFractionalIntToInt(mapper.fractionalIntY() * s.fPixmap.height()); + } else { + iY2 = mapper.intY(); + } + + switch (s.fTileModeY) { + case SkTileMode::kClamp: + iY2 = SkClampMax(iY2, stopY-1); + break; + case SkTileMode::kRepeat: + iY2 = sk_int_mod(iY2, stopY); + break; + case SkTileMode::kMirror: + default: + iY2 = sk_int_mirror(iY2, stopY); + break; + } + + SkASSERT(iY0 == iY2); + } +#endif + } + + const SkPMColor* row0 = s.fPixmap.addr32(0, iY0); + SkPMColor color; + + if (kNone_SkFilterQuality != s.fFilterQuality) { + const SkPMColor* row1 = s.fPixmap.addr32(0, iY1); + filter_32_alpha(iSubY, *row0, *row1, &color, s.fAlphaScale); + } else { + if (s.fAlphaScale < 256) { + color = SkAlphaMulQ(*row0, s.fAlphaScale); + } else { + color = *row0; + } + } + + sk_memset32(colors, color, count); +} + +static void DoNothing_shaderproc(const void*, int x, int y, + SkPMColor* colors, int count) { + // if we get called, the matrix is too tricky, so we just draw nothing + sk_memset32(colors, 0, count); +} + +bool SkBitmapProcState::setupForTranslate() { + SkPoint pt; + const SkBitmapProcStateAutoMapper mapper(*this, 0, 0, &pt); + + /* + * if the translate is larger than our ints, we can get random results, or + * worse, we might get 0x80000000, which wreaks havoc on us, since we can't + * negate it. + */ + const SkScalar too_big = SkIntToScalar(1 << 30); + if (SkScalarAbs(pt.fX) > too_big || SkScalarAbs(pt.fY) > too_big) { + return false; + } + + // Since we know we're not filtered, we re-purpose these fields allow + // us to go from device -> src coordinates w/ just an integer add, + // rather than running through the inverse-matrix + fFilterOneX = mapper.intX(); + fFilterOneY = mapper.intY(); + + return true; +} + +SkBitmapProcState::ShaderProc32 SkBitmapProcState::chooseShaderProc32() { + + if (kN32_SkColorType != fPixmap.colorType()) { + return nullptr; + } + + static const unsigned kMask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask; + + if (1 == fPixmap.width() && 0 == (fInvType & ~kMask)) { + if (kNone_SkFilterQuality == fFilterQuality && + fInvType <= SkMatrix::kTranslate_Mask && + !this->setupForTranslate()) { + return DoNothing_shaderproc; + } + return S32_D32_constX_shaderproc; + } + + if (fAlphaScale < 256) { + return nullptr; + } + if (fInvType > SkMatrix::kTranslate_Mask) { + return nullptr; + } + if (kNone_SkFilterQuality != fFilterQuality) { + return nullptr; + } + + SkTileMode tx = fTileModeX; + SkTileMode ty = fTileModeY; + + if (SkTileMode::kClamp == tx && SkTileMode::kClamp == ty) { + if (this->setupForTranslate()) { + return Clamp_S32_D32_nofilter_trans_shaderproc; + } + return DoNothing_shaderproc; + } + if (SkTileMode::kRepeat == tx && SkTileMode::kRepeat == ty) { + if (this->setupForTranslate()) { + return Repeat_S32_D32_nofilter_trans_shaderproc; + } + return DoNothing_shaderproc; + } + return nullptr; +} + +#ifdef SK_DEBUG + +static void check_scale_nofilter(uint32_t bitmapXY[], int count, + unsigned mx, unsigned my) { + unsigned y = *bitmapXY++; + SkASSERT(y < my); + + const uint16_t* xptr = reinterpret_cast(bitmapXY); + for (int i = 0; i < count; ++i) { + SkASSERT(xptr[i] < mx); + } +} + +static void check_scale_filter(uint32_t bitmapXY[], int count, + unsigned mx, unsigned my) { + uint32_t YY = *bitmapXY++; + unsigned y0 = YY >> 18; + unsigned y1 = YY & 0x3FFF; + SkASSERT(y0 < my); + SkASSERT(y1 < my); + + for (int i = 0; i < count; ++i) { + uint32_t XX = bitmapXY[i]; + unsigned x0 = XX >> 18; + unsigned x1 = XX & 0x3FFF; + SkASSERT(x0 < mx); + SkASSERT(x1 < mx); + } +} + +void SkBitmapProcState::DebugMatrixProc(const SkBitmapProcState& state, + uint32_t bitmapXY[], int count, + int x, int y) { + SkASSERT(bitmapXY); + SkASSERT(count > 0); + + state.fMatrixProc(state, bitmapXY, count, x, y); + + void (*proc)(uint32_t bitmapXY[], int count, unsigned mx, unsigned my); + + // There are two formats possible: + // filter -vs- nofilter + SkASSERT(state.fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)); + proc = state.fFilterQuality != kNone_SkFilterQuality ? + check_scale_filter : check_scale_nofilter; + proc(bitmapXY, count, state.fPixmap.width(), state.fPixmap.height()); +} + +SkBitmapProcState::MatrixProc SkBitmapProcState::getMatrixProc() const { + return DebugMatrixProc; +} + +#endif + +/* + The storage requirements for the different matrix procs are as follows, + where each X or Y is 2 bytes, and N is the number of pixels/elements: + + scale/translate nofilter Y(4bytes) + N * X + affine/perspective nofilter N * (X Y) + scale/translate filter Y Y + N * (X X) + affine filter N * (Y Y X X) + */ +int SkBitmapProcState::maxCountForBufferSize(size_t bufferSize) const { + int32_t size = static_cast(bufferSize); + + size &= ~3; // only care about 4-byte aligned chunks + if (fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) { + size -= 4; // the shared Y (or YY) coordinate + if (size < 0) { + size = 0; + } + size >>= 1; + } else { + size >>= 2; + } + + if (fFilterQuality != kNone_SkFilterQuality) { + size >>= 1; + } + + return size; +} + diff --git a/gfx/skia/skia/src/core/SkBitmapProcState.h b/gfx/skia/skia/src/core/SkBitmapProcState.h new file mode 100644 index 0000000000..c1df487740 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapProcState.h @@ -0,0 +1,216 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapProcState_DEFINED +#define SkBitmapProcState_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" +#include "include/private/SkFixed.h" +#include "include/private/SkFloatBits.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBitmapController.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkMipMap.h" + +typedef SkFixed3232 SkFractionalInt; +#define SkScalarToFractionalInt(x) SkScalarToFixed3232(x) +#define SkFractionalIntToFixed(x) SkFixed3232ToFixed(x) +#define SkFixedToFractionalInt(x) SkFixedToFixed3232(x) +#define SkFractionalIntToInt(x) SkFixed3232ToInt(x) + +class SkPaint; + +struct SkBitmapProcInfo { + SkBitmapProcInfo(const SkImage_Base*, SkTileMode tmx, SkTileMode tmy); + ~SkBitmapProcInfo(); + + const SkImage_Base* fImage; + + SkPixmap fPixmap; + SkMatrix fInvMatrix; // This changes based on tile mode. + // TODO: combine fInvMatrix and fRealInvMatrix. + SkMatrix fRealInvMatrix; // The actual inverse matrix. + SkColor fPaintColor; + SkTileMode fTileModeX; + SkTileMode fTileModeY; + SkFilterQuality fFilterQuality; + SkMatrix::TypeMask fInvType; + + bool init(const SkMatrix& inverse, const SkPaint&); + +private: + enum { + kBMStateSize = 136 // found by inspection. if too small, we will call new/delete + }; + SkSTArenaAlloc fAlloc; + SkBitmapController::State* fBMState; +}; + +struct SkBitmapProcState : public SkBitmapProcInfo { + SkBitmapProcState(const SkImage_Base* image, SkTileMode tmx, SkTileMode tmy) + : SkBitmapProcInfo(image, tmx, tmy) {} + + bool setup(const SkMatrix& inv, const SkPaint& paint) { + return this->init(inv, paint) && this->chooseProcs(); + } + + typedef void (*ShaderProc32)(const void* ctx, int x, int y, SkPMColor[], int count); + + typedef void (*MatrixProc)(const SkBitmapProcState&, + uint32_t bitmapXY[], + int count, + int x, int y); + + typedef void (*SampleProc32)(const SkBitmapProcState&, + const uint32_t[], + int count, + SkPMColor colors[]); + + SkMatrixPriv::MapXYProc fInvProc; // chooseProcs + SkFractionalInt fInvSxFractionalInt; + SkFractionalInt fInvKyFractionalInt; + + SkFixed fFilterOneX; + SkFixed fFilterOneY; + + SkFixed fInvSx; // chooseProcs + SkFixed fInvKy; // chooseProcs + SkPMColor fPaintPMColor; // chooseProcs - A8 config + uint16_t fAlphaScale; // chooseProcs + + /** Given the byte size of the index buffer to be passed to the matrix proc, + return the maximum number of resulting pixels that can be computed + (i.e. the number of SkPMColor values to be written by the sample proc). + This routine takes into account that filtering and scale-vs-affine + affect the amount of buffer space needed. + + Only valid to call after chooseProcs (setContext) has been called. It is + safe to call this inside the shader's shadeSpan() method. + */ + int maxCountForBufferSize(size_t bufferSize) const; + + // If a shader proc is present, then the corresponding matrix/sample procs + // are ignored + ShaderProc32 getShaderProc32() const { return fShaderProc32; } + +#ifdef SK_DEBUG + MatrixProc getMatrixProc() const; +#else + MatrixProc getMatrixProc() const { return fMatrixProc; } +#endif + SampleProc32 getSampleProc32() const { return fSampleProc32; } + +private: + ShaderProc32 fShaderProc32; // chooseProcs + // These are used if the shaderproc is nullptr + MatrixProc fMatrixProc; // chooseProcs + SampleProc32 fSampleProc32; // chooseProcs + + MatrixProc chooseMatrixProc(bool trivial_matrix); + bool chooseProcs(); // caller must have called init() first (on our base-class) + ShaderProc32 chooseShaderProc32(); + + // Return false if we failed to setup for fast translate (e.g. overflow) + bool setupForTranslate(); + +#ifdef SK_DEBUG + static void DebugMatrixProc(const SkBitmapProcState&, + uint32_t[], int count, int x, int y); +#endif +}; + +/* Macros for packing and unpacking pairs of 16bit values in a 32bit uint. + Used to allow access to a stream of uint16_t either one at a time, or + 2 at a time by unpacking a uint32_t + */ +#ifdef SK_CPU_BENDIAN + #define PACK_TWO_SHORTS(pri, sec) ((pri) << 16 | (sec)) + #define UNPACK_PRIMARY_SHORT(packed) ((uint32_t)(packed) >> 16) + #define UNPACK_SECONDARY_SHORT(packed) ((packed) & 0xFFFF) +#else + #define PACK_TWO_SHORTS(pri, sec) ((pri) | ((sec) << 16)) + #define UNPACK_PRIMARY_SHORT(packed) ((packed) & 0xFFFF) + #define UNPACK_SECONDARY_SHORT(packed) ((uint32_t)(packed) >> 16) +#endif + +#ifdef SK_DEBUG + static inline uint32_t pack_two_shorts(U16CPU pri, U16CPU sec) { + SkASSERT((uint16_t)pri == pri); + SkASSERT((uint16_t)sec == sec); + return PACK_TWO_SHORTS(pri, sec); + } +#else + #define pack_two_shorts(pri, sec) PACK_TWO_SHORTS(pri, sec) +#endif + +// Helper class for mapping the middle of pixel (x, y) into SkFractionalInt bitmap space. +// Discussion: +// Overall, this code takes a point in destination space, and uses the center of the pixel +// at (x, y) to determine the sample point in source space. It then adjusts the pixel by different +// amounts based in filtering and tiling. +// This code can be broken into two main cases based on filtering: +// * no filtering (nearest neighbor) - when using nearest neighbor filtering all tile modes reduce +// the sampled by one ulp. If a simple point pt lies precisely on XXX.1/2 then it forced down +// when positive making 1/2 + 1/2 = .999999 instead of 1.0. +// * filtering - in the filtering case, the code calculates the -1/2 shift for starting the +// bilerp kernel. There is a twist; there is a big difference between clamp and the other tile +// modes. In tile and repeat the matrix has been reduced by an additional 1/width and 1/height +// factor. This maps from destination space to [0, 1) (instead of source space) to allow easy +// modulo arithmetic. This means that the -1/2 needed by bilerp is actually 1/2 * 1/width for x +// and 1/2 * 1/height for y. This is what happens when the poorly named fFilterOne{X|Y} is +// divided by two. +class SkBitmapProcStateAutoMapper { +public: + SkBitmapProcStateAutoMapper(const SkBitmapProcState& s, int x, int y, + SkPoint* scalarPoint = nullptr) { + SkPoint pt; + s.fInvProc(s.fInvMatrix, + SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &pt); + + SkFixed biasX, biasY; + if (s.fFilterQuality == kNone_SkFilterQuality) { + // SkFixed epsilon bias to ensure inverse-mapped bitmap coordinates are rounded + // consistently WRT geometry. Note that we only need the bias for positive scales: + // for negative scales, the rounding is intrinsically correct. + // We scale it to persist SkFractionalInt -> SkFixed conversions. + biasX = (s.fInvMatrix.getScaleX() > 0); + biasY = (s.fInvMatrix.getScaleY() > 0); + } else { + biasX = s.fFilterOneX >> 1; + biasY = s.fFilterOneY >> 1; + } + + // punt to unsigned for defined underflow behavior + fX = (SkFractionalInt)((uint64_t)SkScalarToFractionalInt(pt.x()) - + (uint64_t)SkFixedToFractionalInt(biasX)); + fY = (SkFractionalInt)((uint64_t)SkScalarToFractionalInt(pt.y()) - + (uint64_t)SkFixedToFractionalInt(biasY)); + + if (scalarPoint) { + scalarPoint->set(pt.x() - SkFixedToScalar(biasX), + pt.y() - SkFixedToScalar(biasY)); + } + } + + SkFractionalInt fractionalIntX() const { return fX; } + SkFractionalInt fractionalIntY() const { return fY; } + + SkFixed fixedX() const { return SkFractionalIntToFixed(fX); } + SkFixed fixedY() const { return SkFractionalIntToFixed(fY); } + + int intX() const { return SkFractionalIntToInt(fX); } + int intY() const { return SkFractionalIntToInt(fY); } + +private: + SkFractionalInt fX, fY; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBitmapProcState_matrixProcs.cpp b/gfx/skia/skia/src/core/SkBitmapProcState_matrixProcs.cpp new file mode 100644 index 0000000000..4baa5d3a44 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapProcState_matrixProcs.cpp @@ -0,0 +1,830 @@ +/* + * Copyright 2008 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// The copyright below was added in 2009, but I see no record of moto contributions...? + +/* NEON optimized code (C) COPYRIGHT 2009 Motorola + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkShader.h" +#include "include/private/SkTo.h" +#include "src/core/SkBitmapProcState.h" +#include "src/core/SkUtils.h" + +/* + * The decal_ functions require that + * 1. dx > 0 + * 2. [fx, fx+dx, fx+2dx, fx+3dx, ... fx+(count-1)dx] are all <= maxX + * + * In addition, we use SkFractionalInt to keep more fractional precision than + * just SkFixed, so we will abort the decal_ call if dx is very small, since + * the decal_ function just operates on SkFixed. If that were changed, we could + * skip the very_small test here. + */ +static inline bool can_truncate_to_fixed_for_decal(SkFixed fx, + SkFixed dx, + int count, unsigned max) { + SkASSERT(count > 0); + + // if decal_ kept SkFractionalInt precision, this would just be dx <= 0 + // I just made up the 1/256. Just don't want to perceive accumulated error + // if we truncate frDx and lose its low bits. + if (dx <= SK_Fixed1 / 256) { + return false; + } + + // Note: it seems the test should be (fx <= max && lastFx <= max); but + // historically it's been a strict inequality check, and changing produces + // unexpected diffs. Further investigation is needed. + + // We cast to unsigned so we don't have to check for negative values, which + // will now appear as very large positive values, and thus fail our test! + if ((unsigned)SkFixedFloorToInt(fx) >= max) { + return false; + } + + // Promote to 64bit (48.16) to avoid overflow. + const uint64_t lastFx = fx + sk_64_mul(dx, count - 1); + + return SkTFitsIn(lastFx) && (unsigned)SkFixedFloorToInt(SkTo(lastFx)) < max; +} + +// When not filtering, we store 32-bit y, 16-bit x, 16-bit x, 16-bit x, ... +// When filtering we write out 32-bit encodings, pairing 14.4 x0 with 14-bit x1. + +// The clamp routines may try to fall into one of these unclamped decal fast-paths. +// (Only clamp works in the right coordinate space to check for decal.) +static void decal_nofilter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count) { + // can_truncate_to_fixed_for_decal() checked only that stepping fx+=dx count-1 + // times doesn't overflow fx, so we take unusual care not to step count times. + for (; count > 2; count -= 2) { + *dst++ = pack_two_shorts( (fx + 0) >> 16, + (fx + dx) >> 16); + fx += dx+dx; + } + + SkASSERT(count <= 2); + switch (count) { + case 2: ((uint16_t*)dst)[1] = SkToU16((fx + dx) >> 16); + case 1: ((uint16_t*)dst)[0] = SkToU16((fx + 0) >> 16); + } +} + +// A generic implementation for unfiltered scale+translate, templated on tiling method. +template +static void nofilter_scale(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | + SkMatrix::kScale_Mask)) == 0); + + // Write out our 32-bit y, and get our intial fx. + SkFractionalInt fx; + { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + *xy++ = tile(mapper.fixedY(), s.fPixmap.height() - 1); + fx = mapper.fractionalIntX(); + } + + const unsigned maxX = s.fPixmap.width() - 1; + if (0 == maxX) { + // If width == 1, all the x-values must refer to that pixel, and must be zero. + memset(xy, 0, count * sizeof(uint16_t)); + return; + } + + const SkFractionalInt dx = s.fInvSxFractionalInt; + + if (tryDecal) { + const SkFixed fixedFx = SkFractionalIntToFixed(fx); + const SkFixed fixedDx = SkFractionalIntToFixed(dx); + + if (can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) { + decal_nofilter_scale(xy, fixedFx, fixedDx, count); + return; + } + } + + // Remember, each x-coordinate is 16-bit. + for (; count >= 2; count -= 2) { + *xy++ = pack_two_shorts(tile(SkFractionalIntToFixed(fx ), maxX), + tile(SkFractionalIntToFixed(fx + dx), maxX)); + fx += dx+dx; + } + + auto xx = (uint16_t*)xy; + while (count --> 0) { + *xx++ = tile(SkFractionalIntToFixed(fx), maxX); + fx += dx; + } +} + +// Extract the high four fractional bits from fx, the lerp parameter when filtering. +static unsigned extract_low_bits_clamp(SkFixed fx, int /*max*/) { + // If we're already scaled up to by max like clamp/decal, + // just grab the high four fractional bits. + return (fx >> 12) & 0xf; +} +static unsigned extract_low_bits_repeat_mirror(SkFixed fx, int max) { + // In repeat or mirror fx is in [0,1], so scale up by max first. + // TODO: remove the +1 here and the -1 at the call sites... + return extract_low_bits_clamp((fx & 0xffff) * (max+1), max); +} + +template +static void filter_scale(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | + SkMatrix::kScale_Mask)) == 0); + SkASSERT(s.fInvKy == 0); + + auto pack = [](SkFixed f, unsigned max, SkFixed one) { + unsigned i = tile(f, max); + i = (i << 4) | extract_low_bits(f, max); + return (i << 14) | (tile((f + one), max)); + }; + + const unsigned maxX = s.fPixmap.width() - 1; + const SkFractionalInt dx = s.fInvSxFractionalInt; + SkFractionalInt fx; + { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + const SkFixed fy = mapper.fixedY(); + const unsigned maxY = s.fPixmap.height() - 1; + // compute our two Y values up front + *xy++ = pack(fy, maxY, s.fFilterOneY); + // now initialize fx + fx = mapper.fractionalIntX(); + } + + // For historical reasons we check both ends are < maxX rather than <= maxX. + // TODO: try changing this? See also can_truncate_to_fixed_for_decal(). + if (tryDecal && + (unsigned)SkFractionalIntToInt(fx ) < maxX && + (unsigned)SkFractionalIntToInt(fx + dx*(count-1)) < maxX) { + while (count --> 0) { + SkFixed fixedFx = SkFractionalIntToFixed(fx); + SkASSERT((fixedFx >> (16 + 14)) == 0); + *xy++ = (fixedFx >> 12 << 14) | ((fixedFx >> 16) + 1); + fx += dx; + } + return; + } + + while (count --> 0) { + SkFixed fixedFx = SkFractionalIntToFixed(fx); + *xy++ = pack(fixedFx, maxX, s.fFilterOneX); + fx += dx; + } +} + +// Helper to ensure that when we shift down, we do it w/o sign-extension +// so the caller doesn't have to manually mask off the top 16 bits. +static inline unsigned SK_USHIFT16(unsigned x) { + return x >> 16; +} + +static unsigned clamp(SkFixed fx, int max) { + return SkClampMax(fx >> 16, max); +} +static unsigned repeat(SkFixed fx, int max) { + SkASSERT(max < 65535); + return SK_USHIFT16((unsigned)(fx & 0xFFFF) * (max + 1)); +} +static unsigned mirror(SkFixed fx, int max) { + SkASSERT(max < 65535); + // s is 0xFFFFFFFF if we're on an odd interval, or 0 if an even interval + SkFixed s = SkLeftShift(fx, 15) >> 31; + + // This should be exactly the same as repeat(fx ^ s, max) from here on. + return SK_USHIFT16( ((fx ^ s) & 0xFFFF) * (max + 1) ); +} + +// Mirror/Mirror's always just portable code. +static const SkBitmapProcState::MatrixProc MirrorX_MirrorY_Procs[] = { + nofilter_scale, + filter_scale, +}; + +// Clamp/Clamp and Repeat/Repeat have NEON or portable implementations. +#if defined(SK_ARM_HAS_NEON) + #include + + // TODO: this is a fine drop-in for decal_nofilter_scale() generally. + static void decal_nofilter_scale_neon(uint32_t dst[], SkFixed fx, SkFixed dx, int count) { + if (count >= 8) { + // SkFixed is 16.16 fixed point + SkFixed dx8 = dx * 8; + int32x4_t vdx8 = vdupq_n_s32(dx8); + + // setup lbase and hbase + int32x4_t lbase, hbase; + lbase = vdupq_n_s32(fx); + lbase = vsetq_lane_s32(fx + dx, lbase, 1); + lbase = vsetq_lane_s32(fx + dx + dx, lbase, 2); + lbase = vsetq_lane_s32(fx + dx + dx + dx, lbase, 3); + hbase = lbase + vdupq_n_s32(4 * dx); + + do { + // store the upper 16 bits + vst1q_u32(dst, vreinterpretq_u32_s16( + vuzpq_s16(vreinterpretq_s16_s32(lbase), vreinterpretq_s16_s32(hbase)).val[1] + )); + + // on to the next group of 8 + lbase += vdx8; + hbase += vdx8; + dst += 4; // we did 8 elements but the result is twice smaller + count -= 8; + fx += dx8; + } while (count >= 8); + } + + uint16_t* xx = (uint16_t*)dst; + for (int i = count; i > 0; --i) { + *xx++ = SkToU16(fx >> 16); fx += dx; + } + } + + static void decal_filter_scale_neon(uint32_t dst[], SkFixed fx, SkFixed dx, int count) { + if (count >= 8) { + SkFixed dx8 = dx * 8; + int32x4_t vdx8 = vdupq_n_s32(dx8); + + int32x4_t wide_fx, wide_fx2; + wide_fx = vdupq_n_s32(fx); + wide_fx = vsetq_lane_s32(fx + dx, wide_fx, 1); + wide_fx = vsetq_lane_s32(fx + dx + dx, wide_fx, 2); + wide_fx = vsetq_lane_s32(fx + dx + dx + dx, wide_fx, 3); + + wide_fx2 = vaddq_s32(wide_fx, vdupq_n_s32(4 * dx)); + + while (count >= 8) { + int32x4_t wide_out; + int32x4_t wide_out2; + + wide_out = vshlq_n_s32(vshrq_n_s32(wide_fx, 12), 14); + wide_out = wide_out | (vshrq_n_s32(wide_fx,16) + vdupq_n_s32(1)); + + wide_out2 = vshlq_n_s32(vshrq_n_s32(wide_fx2, 12), 14); + wide_out2 = wide_out2 | (vshrq_n_s32(wide_fx2,16) + vdupq_n_s32(1)); + + vst1q_u32(dst, vreinterpretq_u32_s32(wide_out)); + vst1q_u32(dst+4, vreinterpretq_u32_s32(wide_out2)); + + dst += 8; + fx += dx8; + wide_fx += vdx8; + wide_fx2 += vdx8; + count -= 8; + } + } + + if (count & 1) + { + SkASSERT((fx >> (16 + 14)) == 0); + *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1); + fx += dx; + } + while ((count -= 2) >= 0) + { + SkASSERT((fx >> (16 + 14)) == 0); + *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1); + fx += dx; + + *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1); + fx += dx; + } + } + + static inline int16x8_t clamp8(int32x4_t low, int32x4_t high, unsigned max) { + int16x8_t res; + + // get the hi 16s of all those 32s + res = vuzpq_s16(vreinterpretq_s16_s32(low), vreinterpretq_s16_s32(high)).val[1]; + + // clamp + res = vmaxq_s16(res, vdupq_n_s16(0)); + res = vminq_s16(res, vdupq_n_s16(max)); + + return res; + } + + static inline int32x4_t clamp4(int32x4_t f, unsigned max) { + int32x4_t res; + + // get the hi 16s of all those 32s + res = vshrq_n_s32(f, 16); + + // clamp + res = vmaxq_s32(res, vdupq_n_s32(0)); + res = vminq_s32(res, vdupq_n_s32(max)); + + return res; + } + + static inline int32x4_t extract_low_bits_clamp4(int32x4_t fx, unsigned) { + int32x4_t ret; + + ret = vshrq_n_s32(fx, 12); + + /* We don't need the mask below because the caller will + * overwrite the non-masked bits + */ + //ret = vandq_s32(ret, vdupq_n_s32(0xF)); + + return ret; + } + + static inline int16x8_t repeat8(int32x4_t low, int32x4_t high, unsigned max) { + uint16x8_t res; + uint32x4_t tmpl, tmph; + + // get the lower 16 bits + res = vuzpq_u16(vreinterpretq_u16_s32(low), vreinterpretq_u16_s32(high)).val[0]; + + // bare multiplication, not SkFixedMul + tmpl = vmull_u16(vget_low_u16(res), vdup_n_u16(max+1)); + tmph = vmull_u16(vget_high_u16(res), vdup_n_u16(max+1)); + + // extraction of the 16 upper bits + res = vuzpq_u16(vreinterpretq_u16_u32(tmpl), vreinterpretq_u16_u32(tmph)).val[1]; + + return vreinterpretq_s16_u16(res); + } + + static inline int32x4_t repeat4(int32x4_t f, unsigned max) { + uint16x4_t res; + uint32x4_t tmp; + + // get the lower 16 bits + res = vmovn_u32(vreinterpretq_u32_s32(f)); + + // bare multiplication, not SkFixedMul + tmp = vmull_u16(res, vdup_n_u16(max+1)); + + // extraction of the 16 upper bits + tmp = vshrq_n_u32(tmp, 16); + + return vreinterpretq_s32_u32(tmp); + } + + static inline int32x4_t extract_low_bits_repeat_mirror4(int32x4_t fx, unsigned max) { + uint16x4_t res; + uint32x4_t tmp; + int32x4_t ret; + + // get the lower 16 bits + res = vmovn_u32(vreinterpretq_u32_s32(fx)); + + // bare multiplication, not SkFixedMul + tmp = vmull_u16(res, vdup_n_u16(max + 1)); + + // shift and mask + ret = vshrq_n_s32(vreinterpretq_s32_u32(tmp), 12); + + /* We don't need the mask below because the caller will + * overwrite the non-masked bits + */ + //ret = vandq_s32(ret, vdupq_n_s32(0xF)); + + return ret; + } + + template + static void nofilter_scale_neon(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | + SkMatrix::kScale_Mask)) == 0); + + // we store y, x, x, x, x, x + const unsigned maxX = s.fPixmap.width() - 1; + SkFractionalInt fx; + { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + const unsigned maxY = s.fPixmap.height() - 1; + *xy++ = tile(mapper.fixedY(), maxY); + fx = mapper.fractionalIntX(); + } + + if (0 == maxX) { + // all of the following X values must be 0 + memset(xy, 0, count * sizeof(uint16_t)); + return; + } + + const SkFractionalInt dx = s.fInvSxFractionalInt; + + // test if we don't need to apply the tile proc + const SkFixed fixedFx = SkFractionalIntToFixed(fx); + const SkFixed fixedDx = SkFractionalIntToFixed(dx); + if (tryDecal && can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) { + decal_nofilter_scale_neon(xy, fixedFx, fixedDx, count); + return; + } + + if (count >= 8) { + SkFractionalInt dx2 = dx+dx; + SkFractionalInt dx4 = dx2+dx2; + SkFractionalInt dx8 = dx4+dx4; + + // now build fx/fx+dx/fx+2dx/fx+3dx + SkFractionalInt fx1, fx2, fx3; + int32x4_t lbase, hbase; + int16_t *dst16 = (int16_t *)xy; + + fx1 = fx+dx; + fx2 = fx1+dx; + fx3 = fx2+dx; + + lbase = vdupq_n_s32(SkFractionalIntToFixed(fx)); + lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx1), lbase, 1); + lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx2), lbase, 2); + lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx3), lbase, 3); + hbase = vaddq_s32(lbase, vdupq_n_s32(SkFractionalIntToFixed(dx4))); + + // store & bump + while (count >= 8) { + + int16x8_t fx8; + + fx8 = tile8(lbase, hbase, maxX); + + vst1q_s16(dst16, fx8); + + // but preserving base & on to the next + lbase = vaddq_s32 (lbase, vdupq_n_s32(SkFractionalIntToFixed(dx8))); + hbase = vaddq_s32 (hbase, vdupq_n_s32(SkFractionalIntToFixed(dx8))); + dst16 += 8; + count -= 8; + fx += dx8; + } + xy = (uint32_t *) dst16; + } + + uint16_t* xx = (uint16_t*)xy; + for (int i = count; i > 0; --i) { + *xx++ = tile(SkFractionalIntToFixed(fx), maxX); + fx += dx; + } + } + + template + static void filter_scale_neon(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | + SkMatrix::kScale_Mask)) == 0); + SkASSERT(s.fInvKy == 0); + + auto pack = [&](SkFixed f, unsigned max, SkFixed one) { + unsigned i = tile(f, max); + i = (i << 4) | extract_low_bits(f, max); + return (i << 14) | (tile((f + one), max)); + }; + + auto pack4 = [&](int32x4_t f, unsigned max, SkFixed one) { + int32x4_t ret, res; + + res = tile4(f, max); + + ret = extract_low_bits4(f, max); + ret = vsliq_n_s32(ret, res, 4); + + res = tile4(f + vdupq_n_s32(one), max); + ret = vorrq_s32(vshlq_n_s32(ret, 14), res); + + return ret; + }; + + const unsigned maxX = s.fPixmap.width() - 1; + const SkFixed one = s.fFilterOneX; + const SkFractionalInt dx = s.fInvSxFractionalInt; + SkFractionalInt fx; + + { + const SkBitmapProcStateAutoMapper mapper(s, x, y); + const SkFixed fy = mapper.fixedY(); + const unsigned maxY = s.fPixmap.height() - 1; + // compute our two Y values up front + *xy++ = pack(fy, maxY, s.fFilterOneY); + // now initialize fx + fx = mapper.fractionalIntX(); + } + + // test if we don't need to apply the tile proc + const SkFixed fixedFx = SkFractionalIntToFixed(fx); + const SkFixed fixedDx = SkFractionalIntToFixed(dx); + if (tryDecal && can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) { + decal_filter_scale_neon(xy, fixedFx, fixedDx, count); + return; + } + + if (count >= 4) { + int32x4_t wide_fx; + + wide_fx = vdupq_n_s32(SkFractionalIntToFixed(fx)); + wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx), wide_fx, 1); + wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx+dx), wide_fx, 2); + wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx+dx+dx), wide_fx, 3); + + while (count >= 4) { + int32x4_t res; + + res = pack4(wide_fx, maxX, one); + + vst1q_u32(xy, vreinterpretq_u32_s32(res)); + + wide_fx += vdupq_n_s32(SkFractionalIntToFixed(dx+dx+dx+dx)); + fx += dx+dx+dx+dx; + xy += 4; + count -= 4; + } + } + + while (--count >= 0) { + *xy++ = pack(SkFractionalIntToFixed(fx), maxX, one); + fx += dx; + } + } + + static const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs[] = { + nofilter_scale_neon, + filter_scale_neon, + }; + + static const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs[] = { + nofilter_scale_neon, + filter_scale_neon, + }; + +#else + static const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs[] = { + nofilter_scale, + filter_scale, + }; + + static const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs[] = { + nofilter_scale, + filter_scale, + }; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// This next chunk has some specializations for unfiltered translate-only matrices. + +static inline U16CPU int_clamp(int x, int n) { + if (x < 0) { x = 0; } + if (x >= n) { x = n - 1; } + return x; +} + +/* returns 0...(n-1) given any x (positive or negative). + + As an example, if n (which is always positive) is 5... + + x: -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 + returns: 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 + */ +static inline int sk_int_mod(int x, int n) { + SkASSERT(n > 0); + if ((unsigned)x >= (unsigned)n) { + if (x < 0) { + x = n + ~(~x % n); + } else { + x = x % n; + } + } + return x; +} + +static inline U16CPU int_repeat(int x, int n) { + return sk_int_mod(x, n); +} + +static inline U16CPU int_mirror(int x, int n) { + x = sk_int_mod(x, 2 * n); + if (x >= n) { + x = n + ~(x - n); + } + return x; +} + +static void fill_sequential(uint16_t xptr[], int pos, int count) { + while (count --> 0) { + *xptr++ = pos++; + } +} + +static void fill_backwards(uint16_t xptr[], int pos, int count) { + while (count --> 0) { + SkASSERT(pos >= 0); + *xptr++ = pos--; + } +} + +static void clampx_nofilter_trans(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0); + + const SkBitmapProcStateAutoMapper mapper(s, x, y); + *xy++ = int_clamp(mapper.intY(), s.fPixmap.height()); + int xpos = mapper.intX(); + + const int width = s.fPixmap.width(); + if (1 == width) { + // all of the following X values must be 0 + memset(xy, 0, count * sizeof(uint16_t)); + return; + } + + uint16_t* xptr = reinterpret_cast(xy); + int n; + + // fill before 0 as needed + if (xpos < 0) { + n = -xpos; + if (n > count) { + n = count; + } + memset(xptr, 0, n * sizeof(uint16_t)); + count -= n; + if (0 == count) { + return; + } + xptr += n; + xpos = 0; + } + + // fill in 0..width-1 if needed + if (xpos < width) { + n = width - xpos; + if (n > count) { + n = count; + } + fill_sequential(xptr, xpos, n); + count -= n; + if (0 == count) { + return; + } + xptr += n; + } + + // fill the remaining with the max value + sk_memset16(xptr, width - 1, count); +} + +static void repeatx_nofilter_trans(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0); + + const SkBitmapProcStateAutoMapper mapper(s, x, y); + *xy++ = int_repeat(mapper.intY(), s.fPixmap.height()); + int xpos = mapper.intX(); + + const int width = s.fPixmap.width(); + if (1 == width) { + // all of the following X values must be 0 + memset(xy, 0, count * sizeof(uint16_t)); + return; + } + + uint16_t* xptr = reinterpret_cast(xy); + int start = sk_int_mod(xpos, width); + int n = width - start; + if (n > count) { + n = count; + } + fill_sequential(xptr, start, n); + xptr += n; + count -= n; + + while (count >= width) { + fill_sequential(xptr, 0, width); + xptr += width; + count -= width; + } + + if (count > 0) { + fill_sequential(xptr, 0, count); + } +} + +static void mirrorx_nofilter_trans(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0); + + const SkBitmapProcStateAutoMapper mapper(s, x, y); + *xy++ = int_mirror(mapper.intY(), s.fPixmap.height()); + int xpos = mapper.intX(); + + const int width = s.fPixmap.width(); + if (1 == width) { + // all of the following X values must be 0 + memset(xy, 0, count * sizeof(uint16_t)); + return; + } + + uint16_t* xptr = reinterpret_cast(xy); + // need to know our start, and our initial phase (forward or backward) + bool forward; + int n; + int start = sk_int_mod(xpos, 2 * width); + if (start >= width) { + start = width + ~(start - width); + forward = false; + n = start + 1; // [start .. 0] + } else { + forward = true; + n = width - start; // [start .. width) + } + if (n > count) { + n = count; + } + if (forward) { + fill_sequential(xptr, start, n); + } else { + fill_backwards(xptr, start, n); + } + forward = !forward; + xptr += n; + count -= n; + + while (count >= width) { + if (forward) { + fill_sequential(xptr, 0, width); + } else { + fill_backwards(xptr, width - 1, width); + } + forward = !forward; + xptr += width; + count -= width; + } + + if (count > 0) { + if (forward) { + fill_sequential(xptr, 0, count); + } else { + fill_backwards(xptr, width - 1, count); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// The main entry point to the file, choosing between everything above. + +SkBitmapProcState::MatrixProc SkBitmapProcState::chooseMatrixProc(bool translate_only_matrix) { + SkASSERT(fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)); + SkASSERT(fTileModeX == fTileModeY); + SkASSERT(fTileModeX != SkTileMode::kDecal); + + // Check for our special case translate methods when there is no scale/affine/perspective. + if (translate_only_matrix && kNone_SkFilterQuality == fFilterQuality) { + switch (fTileModeX) { + default: SkASSERT(false); + case SkTileMode::kClamp: return clampx_nofilter_trans; + case SkTileMode::kRepeat: return repeatx_nofilter_trans; + case SkTileMode::kMirror: return mirrorx_nofilter_trans; + } + } + + // The arrays are all [ nofilter, filter ]. + int index = fFilterQuality > kNone_SkFilterQuality ? 1 : 0; + + if (fTileModeX == SkTileMode::kClamp) { + // clamp gets special version of filterOne, working in non-normalized space (allowing decal) + fFilterOneX = SK_Fixed1; + fFilterOneY = SK_Fixed1; + return ClampX_ClampY_Procs[index]; + } + + // all remaining procs use this form for filterOne, putting them into normalized space. + fFilterOneX = SK_Fixed1 / fPixmap.width(); + fFilterOneY = SK_Fixed1 / fPixmap.height(); + + if (fTileModeX == SkTileMode::kRepeat) { + return RepeatX_RepeatY_Procs[index]; + } + + return MirrorX_MirrorY_Procs[index]; +} diff --git a/gfx/skia/skia/src/core/SkBitmapScaler.cpp b/gfx/skia/skia/src/core/SkBitmapScaler.cpp new file mode 100644 index 0000000000..c8f0340171 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapScaler.cpp @@ -0,0 +1,254 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBitmapScaler.h" +#include "src/core/SkBitmapFilter.h" +#include "src/core/SkConvolver.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkRect.h" +#include "include/private/SkTArray.h" + +// SkResizeFilter ---------------------------------------------------------------- + +// Encapsulates computation and storage of the filters required for one complete +// resize operation. +class SkResizeFilter { +public: + SkResizeFilter(SkBitmapScaler::ResizeMethod method, + int srcFullWidth, int srcFullHeight, + float destWidth, float destHeight, + const SkRect& destSubset); + ~SkResizeFilter() { delete fBitmapFilter; } + + // Returns the filled filter values. + const SkConvolutionFilter1D& xFilter() { return fXFilter; } + const SkConvolutionFilter1D& yFilter() { return fYFilter; } + +private: + + SkBitmapFilter* fBitmapFilter; + + // Computes one set of filters either horizontally or vertically. The caller + // will specify the "min" and "max" rather than the bottom/top and + // right/bottom so that the same code can be re-used in each dimension. + // + // |srcDependLo| and |srcDependSize| gives the range for the source + // depend rectangle (horizontally or vertically at the caller's discretion + // -- see above for what this means). + // + // Likewise, the range of destination values to compute and the scale factor + // for the transform is also specified. + + void computeFilters(int srcSize, + float destSubsetLo, float destSubsetSize, + float scale, + SkConvolutionFilter1D* output); + + SkConvolutionFilter1D fXFilter; + SkConvolutionFilter1D fYFilter; +}; + +SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method, + int srcFullWidth, int srcFullHeight, + float destWidth, float destHeight, + const SkRect& destSubset) { + + SkASSERT(method >= SkBitmapScaler::RESIZE_FirstMethod && + method <= SkBitmapScaler::RESIZE_LastMethod); + + fBitmapFilter = nullptr; + switch(method) { + case SkBitmapScaler::RESIZE_BOX: + fBitmapFilter = new SkBoxFilter; + break; + case SkBitmapScaler::RESIZE_TRIANGLE: + fBitmapFilter = new SkTriangleFilter; + break; + case SkBitmapScaler::RESIZE_MITCHELL: + fBitmapFilter = new SkMitchellFilter; + break; + case SkBitmapScaler::RESIZE_HAMMING: + fBitmapFilter = new SkHammingFilter; + break; + case SkBitmapScaler::RESIZE_LANCZOS3: + fBitmapFilter = new SkLanczosFilter; + break; + } + + + float scaleX = destWidth / srcFullWidth; + float scaleY = destHeight / srcFullHeight; + + this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(), + scaleX, &fXFilter); + if (srcFullWidth == srcFullHeight && + destSubset.fLeft == destSubset.fTop && + destSubset.width() == destSubset.height()&& + scaleX == scaleY) { + fYFilter = fXFilter; + } else { + this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(), + scaleY, &fYFilter); + } +} + +// TODO(egouriou): Take advantage of periods in the convolution. +// Practical resizing filters are periodic outside of the border area. +// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the +// source become p pixels in the destination) will have a period of p. +// A nice consequence is a period of 1 when downscaling by an integral +// factor. Downscaling from typical display resolutions is also bound +// to produce interesting periods as those are chosen to have multiple +// small factors. +// Small periods reduce computational load and improve cache usage if +// the coefficients can be shared. For periods of 1 we can consider +// loading the factors only once outside the borders. +void SkResizeFilter::computeFilters(int srcSize, + float destSubsetLo, float destSubsetSize, + float scale, + SkConvolutionFilter1D* output) { + float destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi) + + // When we're doing a magnification, the scale will be larger than one. This + // means the destination pixels are much smaller than the source pixels, and + // that the range covered by the filter won't necessarily cover any source + // pixel boundaries. Therefore, we use these clamped values (max of 1) for + // some computations. + float clampedScale = SkTMin(1.0f, scale); + + // This is how many source pixels from the center we need to count + // to support the filtering function. + float srcSupport = fBitmapFilter->width() / clampedScale; + + float invScale = 1.0f / scale; + + SkSTArray<64, float, true> filterValuesArray; + SkSTArray<64, SkConvolutionFilter1D::ConvolutionFixed, true> fixedFilterValuesArray; + + // Loop over all pixels in the output range. We will generate one set of + // filter values for each one. Those values will tell us how to blend the + // source pixels to compute the destination pixel. + + // This is the pixel in the source directly under the pixel in the dest. + // Note that we base computations on the "center" of the pixels. To see + // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x + // downscale should "cover" the pixels around the pixel with *its center* + // at coordinates (2.5, 2.5) in the source, not those around (0, 0). + // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). + destSubsetLo = SkScalarFloorToScalar(destSubsetLo); + destSubsetHi = SkScalarCeilToScalar(destSubsetHi); + float srcPixel = (destSubsetLo + 0.5f) * invScale; + int destLimit = SkScalarTruncToInt(destSubsetHi - destSubsetLo); + output->reserveAdditional(destLimit, SkScalarCeilToInt(destLimit * srcSupport * 2)); + for (int destI = 0; destI < destLimit; srcPixel += invScale, destI++) { + // Compute the (inclusive) range of source pixels the filter covers. + float srcBegin = SkTMax(0.f, SkScalarFloorToScalar(srcPixel - srcSupport)); + float srcEnd = SkTMin(srcSize - 1.f, SkScalarCeilToScalar(srcPixel + srcSupport)); + + // Compute the unnormalized filter value at each location of the source + // it covers. + + // Sum of the filter values for normalizing. + // Distance from the center of the filter, this is the filter coordinate + // in source space. We also need to consider the center of the pixel + // when comparing distance against 'srcPixel'. In the 5x downscale + // example used above the distance from the center of the filter to + // the pixel with coordinates (2, 2) should be 0, because its center + // is at (2.5, 2.5). + float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale; + int filterCount = SkScalarTruncToInt(srcEnd - srcBegin) + 1; + if (filterCount <= 0) { + // true when srcSize is equal to srcPixel - srcSupport; this may be a bug + return; + } + filterValuesArray.reset(filterCount); + float filterSum = fBitmapFilter->evaluate_n(destFilterDist, clampedScale, filterCount, + filterValuesArray.begin()); + + // The filter must be normalized so that we don't affect the brightness of + // the image. Convert to normalized fixed point. + int fixedSum = 0; + fixedFilterValuesArray.reset(filterCount); + const float* filterValues = filterValuesArray.begin(); + SkConvolutionFilter1D::ConvolutionFixed* fixedFilterValues = fixedFilterValuesArray.begin(); + float invFilterSum = 1 / filterSum; + for (int fixedI = 0; fixedI < filterCount; fixedI++) { + int curFixed = SkConvolutionFilter1D::FloatToFixed(filterValues[fixedI] * invFilterSum); + fixedSum += curFixed; + fixedFilterValues[fixedI] = SkToS16(curFixed); + } + SkASSERT(fixedSum <= 0x7FFF); + + // The conversion to fixed point will leave some rounding errors, which + // we add back in to avoid affecting the brightness of the image. We + // arbitrarily add this to the center of the filter array (this won't always + // be the center of the filter function since it could get clipped on the + // edges, but it doesn't matter enough to worry about that case). + int leftovers = SkConvolutionFilter1D::FloatToFixed(1) - fixedSum; + fixedFilterValues[filterCount / 2] += leftovers; + + // Now it's ready to go. + output->AddFilter(SkScalarFloorToInt(srcBegin), fixedFilterValues, filterCount); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool valid_for_resize(const SkPixmap& source, int dstW, int dstH) { + // TODO: Seems like we shouldn't care about the swizzle of source, just that it's 8888 + return source.addr() && source.colorType() == kN32_SkColorType && + source.width() >= 1 && source.height() >= 1 && dstW >= 1 && dstH >= 1; +} + +bool SkBitmapScaler::Resize(const SkPixmap& result, const SkPixmap& source, ResizeMethod method) { + if (!valid_for_resize(source, result.width(), result.height())) { + return false; + } + if (!result.addr() || result.colorType() != source.colorType()) { + return false; + } + + SkRect destSubset = SkRect::MakeIWH(result.width(), result.height()); + + SkResizeFilter filter(method, source.width(), source.height(), + result.width(), result.height(), destSubset); + + // Get a subset encompassing this touched area. We construct the + // offsets and row strides such that it looks like a new bitmap, while + // referring to the old data. + const uint8_t* sourceSubset = reinterpret_cast(source.addr()); + + return BGRAConvolve2D(sourceSubset, static_cast(source.rowBytes()), + !source.isOpaque(), filter.xFilter(), filter.yFilter(), + static_cast(result.rowBytes()), + static_cast(result.writable_addr())); +} + +bool SkBitmapScaler::Resize(SkBitmap* resultPtr, const SkPixmap& source, ResizeMethod method, + int destWidth, int destHeight, SkBitmap::Allocator* allocator) { + // Preflight some of the checks, to avoid allocating the result if we don't need it. + if (!valid_for_resize(source, destWidth, destHeight)) { + return false; + } + + SkBitmap result; + // Note: pass along the profile information even thought this is no the right answer because + // this could be scaling in sRGB. + result.setInfo(SkImageInfo::MakeN32(destWidth, destHeight, source.alphaType(), + sk_ref_sp(source.info().colorSpace()))); + result.allocPixels(allocator); + + SkPixmap resultPM; + if (!result.peekPixels(&resultPM) || !Resize(resultPM, source, method)) { + return false; + } + + *resultPtr = result; + SkASSERT(resultPtr->getPixels()); + return true; +} diff --git a/gfx/skia/skia/src/core/SkBitmapScaler.h b/gfx/skia/skia/src/core/SkBitmapScaler.h new file mode 100644 index 0000000000..b2c3ab77d1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBitmapScaler.h @@ -0,0 +1,46 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapScaler_DEFINED +#define SkBitmapScaler_DEFINED + +#include "include/core/SkBitmap.h" +#include "src/core/SkConvolver.h" + +/** \class SkBitmapScaler + + Provides the interface for high quality image resampling. + */ + +class SK_API SkBitmapScaler { +public: + enum ResizeMethod { + RESIZE_BOX, + RESIZE_TRIANGLE, + RESIZE_LANCZOS3, + RESIZE_HAMMING, + RESIZE_MITCHELL, + + RESIZE_FirstMethod = RESIZE_BOX, + RESIZE_LastMethod = RESIZE_MITCHELL, + }; + + /** + * Given already-allocated src and dst pixmaps, this will scale the src pixels using the + * specified resize-method and write the results into the pixels pointed to by dst. + */ + static bool Resize(const SkPixmap& dst, const SkPixmap& src, ResizeMethod method); + + /** + * Helper function that manages allocating a bitmap to hold the dst pixels, and then calls + * the pixmap version of Resize. + */ + static bool Resize(SkBitmap* result, const SkPixmap& src, ResizeMethod method, + int dest_width, int dest_height, SkBitmap::Allocator* = nullptr); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBlendMode.cpp b/gfx/skia/skia/src/core/SkBlendMode.cpp new file mode 100644 index 0000000000..b19481c182 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlendMode.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkCoverageModePriv.h" +#include "src/core/SkRasterPipeline.h" + +bool SkBlendMode_ShouldPreScaleCoverage(SkBlendMode mode, bool rgb_coverage) { + // The most important things we do here are: + // 1) never pre-scale with rgb coverage if the blend mode involves a source-alpha term; + // 2) always pre-scale Plus. + // + // When we pre-scale with rgb coverage, we scale each of source r,g,b, with a distinct value, + // and source alpha with one of those three values. This process destructively updates the + // source-alpha term, so we can't evaluate blend modes that need its original value. + // + // Plus always requires pre-scaling as a specific quirk of its implementation in + // SkRasterPipeline. This lets us put the clamp inside the blend mode itself rather + // than as a separate stage that'd come after the lerp. + // + // This function is a finer-grained breakdown of SkBlendMode_SupportsCoverageAsAlpha(). + switch (mode) { + case SkBlendMode::kDst: // d --> no sa term, ok! + case SkBlendMode::kDstOver: // d + s*inv(da) --> no sa term, ok! + case SkBlendMode::kPlus: // clamp(s+d) --> no sa term, ok! + return true; + + case SkBlendMode::kDstOut: // d * inv(sa) + case SkBlendMode::kSrcATop: // s*da + d*inv(sa) + case SkBlendMode::kSrcOver: // s + d*inv(sa) + case SkBlendMode::kXor: // s*inv(da) + d*inv(sa) + return !rgb_coverage; + + default: break; + } + return false; +} + +// Users of this function may want to switch to the rgb-coverage aware version above. +bool SkBlendMode_SupportsCoverageAsAlpha(SkBlendMode mode) { + return SkBlendMode_ShouldPreScaleCoverage(mode, false); +} + +struct CoeffRec { + SkBlendModeCoeff fSrc; + SkBlendModeCoeff fDst; +}; + +const CoeffRec gCoeffs[] = { + { SkBlendModeCoeff::kZero, SkBlendModeCoeff::kZero }, + { SkBlendModeCoeff::kOne, SkBlendModeCoeff::kZero }, + { SkBlendModeCoeff::kZero, SkBlendModeCoeff::kOne }, + { SkBlendModeCoeff::kOne, SkBlendModeCoeff::kISA }, + { SkBlendModeCoeff::kIDA, SkBlendModeCoeff::kOne }, + { SkBlendModeCoeff::kDA, SkBlendModeCoeff::kZero }, + { SkBlendModeCoeff::kZero, SkBlendModeCoeff::kSA }, + { SkBlendModeCoeff::kIDA, SkBlendModeCoeff::kZero }, + { SkBlendModeCoeff::kZero, SkBlendModeCoeff::kISA }, + { SkBlendModeCoeff::kDA, SkBlendModeCoeff::kISA }, + { SkBlendModeCoeff::kIDA, SkBlendModeCoeff::kSA }, + { SkBlendModeCoeff::kIDA, SkBlendModeCoeff::kISA }, + + { SkBlendModeCoeff::kOne, SkBlendModeCoeff::kOne }, + { SkBlendModeCoeff::kZero, SkBlendModeCoeff::kSC }, + { SkBlendModeCoeff::kOne, SkBlendModeCoeff::kISC }, // screen +}; + +bool SkBlendMode_AsCoeff(SkBlendMode mode, SkBlendModeCoeff* src, SkBlendModeCoeff* dst) { + if (mode > SkBlendMode::kScreen) { + return false; + } + if (src) { + *src = gCoeffs[static_cast(mode)].fSrc; + } + if (dst) { + *dst = gCoeffs[static_cast(mode)].fDst; + } + return true; +} + +void SkBlendMode_AppendStages(SkBlendMode mode, SkRasterPipeline* p) { + auto stage = SkRasterPipeline::srcover; + switch (mode) { + case SkBlendMode::kClear: stage = SkRasterPipeline::clear; break; + case SkBlendMode::kSrc: return; // This stage is a no-op. + case SkBlendMode::kDst: stage = SkRasterPipeline::move_dst_src; break; + case SkBlendMode::kSrcOver: stage = SkRasterPipeline::srcover; break; + case SkBlendMode::kDstOver: stage = SkRasterPipeline::dstover; break; + case SkBlendMode::kSrcIn: stage = SkRasterPipeline::srcin; break; + case SkBlendMode::kDstIn: stage = SkRasterPipeline::dstin; break; + case SkBlendMode::kSrcOut: stage = SkRasterPipeline::srcout; break; + case SkBlendMode::kDstOut: stage = SkRasterPipeline::dstout; break; + case SkBlendMode::kSrcATop: stage = SkRasterPipeline::srcatop; break; + case SkBlendMode::kDstATop: stage = SkRasterPipeline::dstatop; break; + case SkBlendMode::kXor: stage = SkRasterPipeline::xor_; break; + case SkBlendMode::kPlus: stage = SkRasterPipeline::plus_; break; + case SkBlendMode::kModulate: stage = SkRasterPipeline::modulate; break; + + case SkBlendMode::kScreen: stage = SkRasterPipeline::screen; break; + case SkBlendMode::kOverlay: stage = SkRasterPipeline::overlay; break; + case SkBlendMode::kDarken: stage = SkRasterPipeline::darken; break; + case SkBlendMode::kLighten: stage = SkRasterPipeline::lighten; break; + case SkBlendMode::kColorDodge: stage = SkRasterPipeline::colordodge; break; + case SkBlendMode::kColorBurn: stage = SkRasterPipeline::colorburn; break; + case SkBlendMode::kHardLight: stage = SkRasterPipeline::hardlight; break; + case SkBlendMode::kSoftLight: stage = SkRasterPipeline::softlight; break; + case SkBlendMode::kDifference: stage = SkRasterPipeline::difference; break; + case SkBlendMode::kExclusion: stage = SkRasterPipeline::exclusion; break; + case SkBlendMode::kMultiply: stage = SkRasterPipeline::multiply; break; + + case SkBlendMode::kHue: stage = SkRasterPipeline::hue; break; + case SkBlendMode::kSaturation: stage = SkRasterPipeline::saturation; break; + case SkBlendMode::kColor: stage = SkRasterPipeline::color; break; + case SkBlendMode::kLuminosity: stage = SkRasterPipeline::luminosity; break; + } + p->append(stage); +} + +SkPMColor4f SkBlendMode_Apply(SkBlendMode mode, const SkPMColor4f& src, const SkPMColor4f& dst) { + // special-case simple/common modes... + switch (mode) { + case SkBlendMode::kClear: return SK_PMColor4fTRANSPARENT; + case SkBlendMode::kSrc: return src; + case SkBlendMode::kDst: return dst; + case SkBlendMode::kSrcOver: { + Sk4f r = Sk4f::Load(src.vec()) + Sk4f::Load(dst.vec()) * Sk4f(1 - src.fA); + return { r[0], r[1], r[2], r[3] }; + } + default: + break; + } + + SkRasterPipeline_<256> p; + SkPMColor4f src_storage = src, + dst_storage = dst, + res_storage; + SkRasterPipeline_MemoryCtx src_ctx = { &src_storage, 0 }, + dst_ctx = { &dst_storage, 0 }, + res_ctx = { &res_storage, 0 }; + + p.append(SkRasterPipeline::load_f32, &dst_ctx); + p.append(SkRasterPipeline::move_src_dst); + p.append(SkRasterPipeline::load_f32, &src_ctx); + SkBlendMode_AppendStages(mode, &p); + p.append(SkRasterPipeline::store_f32, &res_ctx); + p.run(0,0, 1,1); + return res_storage; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +const SkBlendMode gUncorrelatedCoverageToBlend[] = { + SkBlendMode::kSrcOver, // or DstOver + SkBlendMode::kSrcIn, // or kDstIn + SkBlendMode::kSrcOut, + SkBlendMode::kDstOut, + SkBlendMode::kXor, +}; + +SkBlendMode SkUncorrelatedCoverageModeToBlendMode(SkCoverageMode cm) { + unsigned index = static_cast(cm); + SkASSERT(index < SK_ARRAY_COUNT(gUncorrelatedCoverageToBlend)); + return gUncorrelatedCoverageToBlend[index]; +} diff --git a/gfx/skia/skia/src/core/SkBlendModePriv.h b/gfx/skia/skia/src/core/SkBlendModePriv.h new file mode 100644 index 0000000000..c5e688adb6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlendModePriv.h @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlendModePriv_DEFINED +#define SkBlendModePriv_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/private/SkColorData.h" + +class SkRasterPipeline; + +bool SkBlendMode_SupportsCoverageAsAlpha(SkBlendMode); + +static inline bool SkBlendMode_CaresAboutRBOrder(SkBlendMode mode) { + return (mode > SkBlendMode::kLastSeparableMode); +} + +bool SkBlendMode_ShouldPreScaleCoverage(SkBlendMode, bool rgb_coverage); +void SkBlendMode_AppendStages(SkBlendMode, SkRasterPipeline*); + +enum class SkBlendModeCoeff { + kZero, /** 0 */ + kOne, /** 1 */ + kSC, /** src color */ + kISC, /** inverse src color (i.e. 1 - sc) */ + kDC, /** dst color */ + kIDC, /** inverse dst color (i.e. 1 - dc) */ + kSA, /** src alpha */ + kISA, /** inverse src alpha (i.e. 1 - sa) */ + kDA, /** dst alpha */ + kIDA, /** inverse dst alpha (i.e. 1 - da) */ + + kCoeffCount +}; + +bool SkBlendMode_AsCoeff(SkBlendMode mode, SkBlendModeCoeff* src, SkBlendModeCoeff* dst); + +SkPMColor4f SkBlendMode_Apply(SkBlendMode, const SkPMColor4f& src, const SkPMColor4f& dst); + +#if SK_SUPPORT_GPU +#include "src/gpu/GrXferProcessor.h" +const GrXPFactory* SkBlendMode_AsXPFactory(SkBlendMode); +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkBlitBWMaskTemplate.h b/gfx/skia/skia/src/core/SkBlitBWMaskTemplate.h new file mode 100644 index 0000000000..991e699b9d --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitBWMaskTemplate.h @@ -0,0 +1,127 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkBitmap.h" +#include "src/core/SkMask.h" + +#ifndef ClearLow3Bits_DEFINED +#define ClearLow3Bits_DEFINED + #define ClearLow3Bits(x) ((unsigned)(x) >> 3 << 3) +#endif + +/* + SK_BLITBWMASK_NAME name of function(const SkBitmap& bitmap, const SkMask& mask, const SkIRect& clip, SK_BLITBWMASK_ARGS) + SK_BLITBWMASK_ARGS list of additional arguments to SK_BLITBWMASK_NAME, beginning with a comma + SK_BLITBWMASK_BLIT8 name of function(U8CPU byteMask, SK_BLITBWMASK_DEVTYPE* dst, int x, int y) + SK_BLITBWMASK_GETADDR either writable_addr[8,16,32] + SK_BLITBWMASK_DEVTYPE either U32 or U16 or U8 +*/ + +static void SK_BLITBWMASK_NAME(const SkPixmap& dst, const SkMask& srcMask, + const SkIRect& clip SK_BLITBWMASK_ARGS) { + SkASSERT(clip.fRight <= srcMask.fBounds.fRight); + + int cx = clip.fLeft; + int cy = clip.fTop; + int maskLeft = srcMask.fBounds.fLeft; + unsigned mask_rowBytes = srcMask.fRowBytes; + size_t bitmap_rowBytes = dst.rowBytes(); + unsigned height = clip.height(); + + SkASSERT(mask_rowBytes != 0); + SkASSERT(bitmap_rowBytes != 0); + SkASSERT(height != 0); + + const uint8_t* bits = srcMask.getAddr1(cx, cy); + SK_BLITBWMASK_DEVTYPE* device = dst.SK_BLITBWMASK_GETADDR(cx, cy); + + if (cx == maskLeft && clip.fRight == srcMask.fBounds.fRight) + { + do { + SK_BLITBWMASK_DEVTYPE* dst = device; + unsigned rb = mask_rowBytes; + do { + U8CPU mask = *bits++; + SK_BLITBWMASK_BLIT8(mask, dst); + dst += 8; + } while (--rb != 0); + device = (SK_BLITBWMASK_DEVTYPE*)((char*)device + bitmap_rowBytes); + } while (--height != 0); + } + else + { + int left_edge = cx - maskLeft; + SkASSERT(left_edge >= 0); + int rite_edge = clip.fRight - maskLeft; + SkASSERT(rite_edge > left_edge); + + int left_mask = 0xFF >> (left_edge & 7); + int rite_mask = 0xFF << (8 - (rite_edge & 7)); + rite_mask &= 0xFF; // only want low-8 bits of mask + int full_runs = (rite_edge >> 3) - ((left_edge + 7) >> 3); + + // check for empty right mask, so we don't read off the end (or go slower than we need to) + if (rite_mask == 0) + { + SkASSERT(full_runs >= 0); + full_runs -= 1; + rite_mask = 0xFF; + } + if (left_mask == 0xFF) + full_runs -= 1; + + // back up manually so we can keep in sync with our byte-aligned src + // and not trigger an assert from the getAddr## function + device -= left_edge & 7; + + if (full_runs < 0) + { + left_mask &= rite_mask; + SkASSERT(left_mask != 0); + do { + U8CPU mask = *bits & left_mask; + SK_BLITBWMASK_BLIT8(mask, device); + bits += mask_rowBytes; + device = (SK_BLITBWMASK_DEVTYPE*)((char*)device + bitmap_rowBytes); + } while (--height != 0); + } + else + { + do { + int runs = full_runs; + SK_BLITBWMASK_DEVTYPE* dst = device; + const uint8_t* b = bits; + U8CPU mask; + + mask = *b++ & left_mask; + SK_BLITBWMASK_BLIT8(mask, dst); + dst += 8; + + while (--runs >= 0) + { + mask = *b++; + SK_BLITBWMASK_BLIT8(mask, dst); + dst += 8; + } + + mask = *b & rite_mask; + SK_BLITBWMASK_BLIT8(mask, dst); + + bits += mask_rowBytes; + device = (SK_BLITBWMASK_DEVTYPE*)((char*)device + bitmap_rowBytes); + } while (--height != 0); + } + } +} + +#undef SK_BLITBWMASK_NAME +#undef SK_BLITBWMASK_ARGS +#undef SK_BLITBWMASK_BLIT8 +#undef SK_BLITBWMASK_GETADDR +#undef SK_BLITBWMASK_DEVTYPE +#undef SK_BLITBWMASK_DOROWSETUP diff --git a/gfx/skia/skia/src/core/SkBlitRow.h b/gfx/skia/skia/src/core/SkBlitRow.h new file mode 100644 index 0000000000..cc4ba86407 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitRow.h @@ -0,0 +1,38 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlitRow_DEFINED +#define SkBlitRow_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkColor.h" + +class SkBlitRow { +public: + enum Flags32 { + kGlobalAlpha_Flag32 = 1 << 0, + kSrcPixelAlpha_Flag32 = 1 << 1 + }; + + /** Function pointer that blends 32bit colors onto a 32bit destination. + @param dst array of dst 32bit colors + @param src array of src 32bit colors (w/ or w/o alpha) + @param count number of colors to blend + @param alpha global alpha to be applied to all src colors + */ + typedef void (*Proc32)(uint32_t dst[], const SkPMColor src[], int count, U8CPU alpha); + + static Proc32 Factory32(unsigned flags32); + + /** Blend a single color onto a row of S32 pixels, writing the result + into a row of D32 pixels. src and dst may be the same memory, but + if they are not, they may not overlap. + */ + static void Color32(SkPMColor dst[], const SkPMColor src[], int count, SkPMColor color); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBlitRow_D32.cpp b/gfx/skia/skia/src/core/SkBlitRow_D32.cpp new file mode 100644 index 0000000000..82b3cca2cb --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitRow_D32.cpp @@ -0,0 +1,314 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "src/core/SkBlitRow.h" +#include "src/core/SkOpts.h" +#include "src/core/SkUtils.h" + +// Everyone agrees memcpy() is the best way to do this. +static void blit_row_s32_opaque(SkPMColor* dst, + const SkPMColor* src, + int count, + U8CPU alpha) { + SkASSERT(255 == alpha); + memcpy(dst, src, count * sizeof(SkPMColor)); +} + +// We have SSE2, NEON, and portable implementations of +// blit_row_s32_blend() and blit_row_s32a_blend(). + +// TODO(mtklein): can we do better in NEON than 2 pixels at a time? + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include + + static inline __m128i SkPMLerp_SSE2(const __m128i& src, + const __m128i& dst, + const unsigned src_scale) { + // Computes dst + (((src - dst)*src_scale)>>8) + const __m128i mask = _mm_set1_epi32(0x00FF00FF); + + // Unpack the 16x8-bit source into 2 8x16-bit splayed halves. + __m128i src_rb = _mm_and_si128(mask, src); + __m128i src_ag = _mm_srli_epi16(src, 8); + __m128i dst_rb = _mm_and_si128(mask, dst); + __m128i dst_ag = _mm_srli_epi16(dst, 8); + + // Compute scaled differences. + __m128i diff_rb = _mm_sub_epi16(src_rb, dst_rb); + __m128i diff_ag = _mm_sub_epi16(src_ag, dst_ag); + __m128i s = _mm_set1_epi16(src_scale); + diff_rb = _mm_mullo_epi16(diff_rb, s); + diff_ag = _mm_mullo_epi16(diff_ag, s); + + // Pack the differences back together. + diff_rb = _mm_srli_epi16(diff_rb, 8); + diff_ag = _mm_andnot_si128(mask, diff_ag); + __m128i diff = _mm_or_si128(diff_rb, diff_ag); + + // Add difference to destination. + return _mm_add_epi8(dst, diff); + } + + + static void blit_row_s32_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) { + SkASSERT(alpha <= 255); + + auto src4 = (const __m128i*)src; + auto dst4 = ( __m128i*)dst; + + while (count >= 4) { + _mm_storeu_si128(dst4, SkPMLerp_SSE2(_mm_loadu_si128(src4), + _mm_loadu_si128(dst4), + SkAlpha255To256(alpha))); + src4++; + dst4++; + count -= 4; + } + + src = (const SkPMColor*)src4; + dst = ( SkPMColor*)dst4; + + while (count --> 0) { + *dst = SkPMLerp(*src, *dst, SkAlpha255To256(alpha)); + src++; + dst++; + } + } + + static inline __m128i SkBlendARGB32_SSE2(const __m128i& src, + const __m128i& dst, + const unsigned aa) { + unsigned alpha = SkAlpha255To256(aa); + __m128i src_scale = _mm_set1_epi16(alpha); + // SkAlphaMulInv256(SkGetPackedA32(src), src_scale) + __m128i dst_scale = _mm_srli_epi32(src, 24); + // High words in dst_scale are 0, so it's safe to multiply with 16-bit src_scale. + dst_scale = _mm_mullo_epi16(dst_scale, src_scale); + dst_scale = _mm_sub_epi32(_mm_set1_epi32(0xFFFF), dst_scale); + dst_scale = _mm_add_epi32(dst_scale, _mm_srli_epi32(dst_scale, 8)); + dst_scale = _mm_srli_epi32(dst_scale, 8); + // Duplicate scales into 2x16-bit pattern per pixel. + dst_scale = _mm_shufflelo_epi16(dst_scale, _MM_SHUFFLE(2, 2, 0, 0)); + dst_scale = _mm_shufflehi_epi16(dst_scale, _MM_SHUFFLE(2, 2, 0, 0)); + + const __m128i mask = _mm_set1_epi32(0x00FF00FF); + + // Unpack the 16x8-bit source/destination into 2 8x16-bit splayed halves. + __m128i src_rb = _mm_and_si128(mask, src); + __m128i src_ag = _mm_srli_epi16(src, 8); + __m128i dst_rb = _mm_and_si128(mask, dst); + __m128i dst_ag = _mm_srli_epi16(dst, 8); + + // Scale them. + src_rb = _mm_mullo_epi16(src_rb, src_scale); + src_ag = _mm_mullo_epi16(src_ag, src_scale); + dst_rb = _mm_mullo_epi16(dst_rb, dst_scale); + dst_ag = _mm_mullo_epi16(dst_ag, dst_scale); + + // Add the scaled source and destination. + dst_rb = _mm_add_epi16(src_rb, dst_rb); + dst_ag = _mm_add_epi16(src_ag, dst_ag); + + // Unsplay the halves back together. + dst_rb = _mm_srli_epi16(dst_rb, 8); + dst_ag = _mm_andnot_si128(mask, dst_ag); + return _mm_or_si128(dst_rb, dst_ag); + } + + static void blit_row_s32a_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) { + SkASSERT(alpha <= 255); + + auto src4 = (const __m128i*)src; + auto dst4 = ( __m128i*)dst; + + while (count >= 4) { + _mm_storeu_si128(dst4, SkBlendARGB32_SSE2(_mm_loadu_si128(src4), + _mm_loadu_si128(dst4), + alpha)); + src4++; + dst4++; + count -= 4; + } + + src = (const SkPMColor*)src4; + dst = ( SkPMColor*)dst4; + + while (count --> 0) { + *dst = SkBlendARGB32(*src, *dst, alpha); + src++; + dst++; + } + } + +#elif defined(SK_ARM_HAS_NEON) + #include + + static void blit_row_s32_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) { + SkASSERT(alpha <= 255); + + uint16_t src_scale = SkAlpha255To256(alpha); + uint16_t dst_scale = 256 - src_scale; + + while (count >= 2) { + uint8x8_t vsrc, vdst, vres; + uint16x8_t vsrc_wide, vdst_wide; + + vsrc = vreinterpret_u8_u32(vld1_u32(src)); + vdst = vreinterpret_u8_u32(vld1_u32(dst)); + + vsrc_wide = vmovl_u8(vsrc); + vsrc_wide = vmulq_u16(vsrc_wide, vdupq_n_u16(src_scale)); + + vdst_wide = vmull_u8(vdst, vdup_n_u8(dst_scale)); + + vdst_wide += vsrc_wide; + vres = vshrn_n_u16(vdst_wide, 8); + + vst1_u32(dst, vreinterpret_u32_u8(vres)); + + src += 2; + dst += 2; + count -= 2; + } + + if (count == 1) { + uint8x8_t vsrc = vdup_n_u8(0), vdst = vdup_n_u8(0), vres; + uint16x8_t vsrc_wide, vdst_wide; + + vsrc = vreinterpret_u8_u32(vld1_lane_u32(src, vreinterpret_u32_u8(vsrc), 0)); + vdst = vreinterpret_u8_u32(vld1_lane_u32(dst, vreinterpret_u32_u8(vdst), 0)); + + vsrc_wide = vmovl_u8(vsrc); + vsrc_wide = vmulq_u16(vsrc_wide, vdupq_n_u16(src_scale)); + vdst_wide = vmull_u8(vdst, vdup_n_u8(dst_scale)); + vdst_wide += vsrc_wide; + vres = vshrn_n_u16(vdst_wide, 8); + + vst1_lane_u32(dst, vreinterpret_u32_u8(vres), 0); + } + } + + static void blit_row_s32a_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) { + SkASSERT(alpha < 255); + + unsigned alpha256 = SkAlpha255To256(alpha); + + if (count & 1) { + uint8x8_t vsrc = vdup_n_u8(0), vdst = vdup_n_u8(0), vres; + uint16x8_t vdst_wide, vsrc_wide; + unsigned dst_scale; + + vsrc = vreinterpret_u8_u32(vld1_lane_u32(src, vreinterpret_u32_u8(vsrc), 0)); + vdst = vreinterpret_u8_u32(vld1_lane_u32(dst, vreinterpret_u32_u8(vdst), 0)); + + dst_scale = vget_lane_u8(vsrc, 3); + dst_scale = SkAlphaMulInv256(dst_scale, alpha256); + + vsrc_wide = vmovl_u8(vsrc); + vsrc_wide = vmulq_n_u16(vsrc_wide, alpha256); + + vdst_wide = vmovl_u8(vdst); + vdst_wide = vmulq_n_u16(vdst_wide, dst_scale); + + vdst_wide += vsrc_wide; + vres = vshrn_n_u16(vdst_wide, 8); + + vst1_lane_u32(dst, vreinterpret_u32_u8(vres), 0); + dst++; + src++; + count--; + } + + uint8x8_t alpha_mask; + static const uint8_t alpha_mask_setup[] = {3,3,3,3,7,7,7,7}; + alpha_mask = vld1_u8(alpha_mask_setup); + + while (count) { + + uint8x8_t vsrc, vdst, vres, vsrc_alphas; + uint16x8_t vdst_wide, vsrc_wide, vsrc_scale, vdst_scale; + + __builtin_prefetch(src+32); + __builtin_prefetch(dst+32); + + vsrc = vreinterpret_u8_u32(vld1_u32(src)); + vdst = vreinterpret_u8_u32(vld1_u32(dst)); + + vsrc_scale = vdupq_n_u16(alpha256); + + vsrc_alphas = vtbl1_u8(vsrc, alpha_mask); + vdst_scale = vmovl_u8(vsrc_alphas); + // Calculate SkAlphaMulInv256(vdst_scale, vsrc_scale). + // A 16-bit lane would overflow if we used 0xFFFF here, + // so use an approximation with 0xFF00 that is off by 1, + // and add back 1 after to get the correct value. + // This is valid if alpha256 <= 255. + vdst_scale = vmlsq_u16(vdupq_n_u16(0xFF00), vdst_scale, vsrc_scale); + vdst_scale = vsraq_n_u16(vdst_scale, vdst_scale, 8); + vdst_scale = vsraq_n_u16(vdupq_n_u16(1), vdst_scale, 8); + + vsrc_wide = vmovl_u8(vsrc); + vsrc_wide *= vsrc_scale; + + vdst_wide = vmovl_u8(vdst); + vdst_wide *= vdst_scale; + + vdst_wide += vsrc_wide; + vres = vshrn_n_u16(vdst_wide, 8); + + vst1_u32(dst, vreinterpret_u32_u8(vres)); + + src += 2; + dst += 2; + count -= 2; + } + } + +#else + static void blit_row_s32_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) { + SkASSERT(alpha <= 255); + while (count --> 0) { + *dst = SkPMLerp(*src, *dst, SkAlpha255To256(alpha)); + src++; + dst++; + } + } + + static void blit_row_s32a_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) { + SkASSERT(alpha <= 255); + while (count --> 0) { + *dst = SkBlendARGB32(*src, *dst, alpha); + src++; + dst++; + } + } +#endif + +SkBlitRow::Proc32 SkBlitRow::Factory32(unsigned flags) { + static const SkBlitRow::Proc32 kProcs[] = { + blit_row_s32_opaque, + blit_row_s32_blend, + nullptr, // blit_row_s32a_opaque is in SkOpts + blit_row_s32a_blend + }; + + SkASSERT(flags < SK_ARRAY_COUNT(kProcs)); + flags &= SK_ARRAY_COUNT(kProcs) - 1; // just to be safe + + return flags == 2 ? SkOpts::blit_row_s32a_opaque + : kProcs[flags]; +} + +void SkBlitRow::Color32(SkPMColor dst[], const SkPMColor src[], int count, SkPMColor color) { + switch (SkGetPackedA32(color)) { + case 0: memmove(dst, src, count * sizeof(SkPMColor)); return; + case 255: sk_memset32(dst, color, count); return; + } + return SkOpts::blit_row_color32(dst, src, count, color); +} diff --git a/gfx/skia/skia/src/core/SkBlitter.cpp b/gfx/skia/skia/src/core/SkBlitter.cpp new file mode 100644 index 0000000000..c8cf2bb8a8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitter.cpp @@ -0,0 +1,884 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBlitter.h" + +#include "include/core/SkColor.h" +#include "include/core/SkColorFilter.h" +#include "include/core/SkString.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/core/SkAntiRun.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkMask.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkRegionPriv.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkUtils.h" +#include "src/core/SkWriteBuffer.h" +#include "src/core/SkXfermodeInterpretation.h" +#include "src/shaders/SkShaderBase.h" + +SkBlitter::~SkBlitter() {} + +bool SkBlitter::isNullBlitter() const { return false; } + +const SkPixmap* SkBlitter::justAnOpaqueColor(uint32_t* value) { + return nullptr; +} + +/* +void SkBlitter::blitH(int x, int y, int width) { + SkDEBUGFAIL("unimplemented"); +} + + +void SkBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) { + SkDEBUGFAIL("unimplemented"); +} + */ + +inline static SkAlpha ScalarToAlpha(SkScalar a) { + SkAlpha alpha = (SkAlpha)(a * 255); + return alpha > 247 ? 0xFF : alpha < 8 ? 0 : alpha; +} + +void SkBlitter::blitFatAntiRect(const SkRect& rect) { + SkIRect bounds = rect.roundOut(); + SkASSERT(bounds.width() >= 3); + + // skbug.com/7813 + // To ensure consistency of the threaded backend (a rect that's considered fat in the init-once + // phase must also be considered fat in the draw phase), we have to deal with rects with small + // heights because the horizontal tiling in the threaded backend may change the height. + // + // This also implies that we cannot do vertical tiling unless we can blit any rect (not just the + // fat one.) + if (bounds.height() == 0) { + return; + } + + int runSize = bounds.width() + 1; // +1 so we can set runs[bounds.width()] = 0 + void* storage = this->allocBlitMemory(runSize * (sizeof(int16_t) + sizeof(SkAlpha))); + int16_t* runs = reinterpret_cast(storage); + SkAlpha* alphas = reinterpret_cast(runs + runSize); + + runs[0] = 1; + runs[1] = bounds.width() - 2; + runs[bounds.width() - 1] = 1; + runs[bounds.width()] = 0; + + SkScalar partialL = bounds.fLeft + 1 - rect.fLeft; + SkScalar partialR = rect.fRight - (bounds.fRight - 1); + SkScalar partialT = bounds.fTop + 1 - rect.fTop; + SkScalar partialB = rect.fBottom - (bounds.fBottom - 1); + + if (bounds.height() == 1) { + partialT = rect.fBottom - rect.fTop; + } + + alphas[0] = ScalarToAlpha(partialL * partialT); + alphas[1] = ScalarToAlpha(partialT); + alphas[bounds.width() - 1] = ScalarToAlpha(partialR * partialT); + this->blitAntiH(bounds.fLeft, bounds.fTop, alphas, runs); + + if (bounds.height() > 2) { + this->blitAntiRect(bounds.fLeft, bounds.fTop + 1, bounds.width() - 2, bounds.height() - 2, + ScalarToAlpha(partialL), ScalarToAlpha(partialR)); + } + + if (bounds.height() > 1) { + alphas[0] = ScalarToAlpha(partialL * partialB); + alphas[1] = ScalarToAlpha(partialB); + alphas[bounds.width() - 1] = ScalarToAlpha(partialR * partialB); + this->blitAntiH(bounds.fLeft, bounds.fBottom - 1, alphas, runs); + } +} + +void SkBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + if (alpha == 255) { + this->blitRect(x, y, 1, height); + } else { + int16_t runs[2]; + runs[0] = 1; + runs[1] = 0; + + while (--height >= 0) { + this->blitAntiH(x, y++, &alpha, runs); + } + } +} + +void SkBlitter::blitRect(int x, int y, int width, int height) { + SkASSERT(width > 0); + while (--height >= 0) { + this->blitH(x, y++, width); + } +} + +/// Default implementation doesn't check for easy optimizations +/// such as alpha == 255; also uses blitV(), which some subclasses +/// may not support. +void SkBlitter::blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) { + if (leftAlpha > 0) { // we may send in x = -1 with leftAlpha = 0 + this->blitV(x, y, height, leftAlpha); + } + x++; + if (width > 0) { + this->blitRect(x, y, width, height); + x += width; + } + if (rightAlpha > 0) { + this->blitV(x, y, height, rightAlpha); + } +} + +////////////////////////////////////////////////////////////////////////////// + +static inline void bits_to_runs(SkBlitter* blitter, int x, int y, + const uint8_t bits[], + uint8_t left_mask, ptrdiff_t rowBytes, + uint8_t right_mask) { + int inFill = 0; + int pos = 0; + + while (--rowBytes >= 0) { + uint8_t b = *bits++ & left_mask; + if (rowBytes == 0) { + b &= right_mask; + } + + for (uint8_t test = 0x80U; test != 0; test >>= 1) { + if (b & test) { + if (!inFill) { + pos = x; + inFill = true; + } + } else { + if (inFill) { + blitter->blitH(pos, y, x - pos); + inFill = false; + } + } + x += 1; + } + left_mask = 0xFFU; + } + + // final cleanup + if (inFill) { + blitter->blitH(pos, y, x - pos); + } +} + +// maskBitCount is the number of 1's to place in the mask. It must be in the range between 1 and 8. +static uint8_t generate_right_mask(int maskBitCount) { + return static_cast((0xFF00U >> maskBitCount) & 0xFF); +} + +void SkBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { + SkASSERT(mask.fBounds.contains(clip)); + + if (mask.fFormat == SkMask::kLCD16_Format) { + return; // needs to be handled by subclass + } + + if (mask.fFormat == SkMask::kBW_Format) { + int cx = clip.fLeft; + int cy = clip.fTop; + int maskLeft = mask.fBounds.fLeft; + int maskRowBytes = mask.fRowBytes; + int height = clip.height(); + + const uint8_t* bits = mask.getAddr1(cx, cy); + + SkDEBUGCODE(const uint8_t* endOfImage = + mask.fImage + (mask.fBounds.height() - 1) * maskRowBytes + + ((mask.fBounds.width() + 7) >> 3)); + + if (cx == maskLeft && clip.fRight == mask.fBounds.fRight) { + while (--height >= 0) { + int affectedRightBit = mask.fBounds.width() - 1; + ptrdiff_t rowBytes = (affectedRightBit >> 3) + 1; + SkASSERT(bits + rowBytes <= endOfImage); + U8CPU rightMask = generate_right_mask((affectedRightBit & 7) + 1); + bits_to_runs(this, cx, cy, bits, 0xFF, rowBytes, rightMask); + bits += maskRowBytes; + cy += 1; + } + } else { + // Bits is calculated as the offset into the mask at the point {cx, cy} therefore, all + // addressing into the bit mask is relative to that point. Since this is an address + // calculated from a arbitrary bit in that byte, calculate the left most bit. + int bitsLeft = cx - ((cx - maskLeft) & 7); + + // Everything is relative to the bitsLeft. + int leftEdge = cx - bitsLeft; + SkASSERT(leftEdge >= 0); + int rightEdge = clip.fRight - bitsLeft; + SkASSERT(rightEdge > leftEdge); + + // Calculate left byte and mask + const uint8_t* leftByte = bits; + U8CPU leftMask = 0xFFU >> (leftEdge & 7); + + // Calculate right byte and mask + int affectedRightBit = rightEdge - 1; + const uint8_t* rightByte = bits + (affectedRightBit >> 3); + U8CPU rightMask = generate_right_mask((affectedRightBit & 7) + 1); + + // leftByte and rightByte are byte locations therefore, to get a count of bytes the + // code must add one. + ptrdiff_t rowBytes = rightByte - leftByte + 1; + + while (--height >= 0) { + SkASSERT(bits + rowBytes <= endOfImage); + bits_to_runs(this, bitsLeft, cy, bits, leftMask, rowBytes, rightMask); + bits += maskRowBytes; + cy += 1; + } + } + } else { + int width = clip.width(); + SkAutoSTMalloc<64, int16_t> runStorage(width + 1); + int16_t* runs = runStorage.get(); + const uint8_t* aa = mask.getAddr8(clip.fLeft, clip.fTop); + + sk_memset16((uint16_t*)runs, 1, width); + runs[width] = 0; + + int height = clip.height(); + int y = clip.fTop; + while (--height >= 0) { + this->blitAntiH(clip.fLeft, y, aa, runs); + aa += mask.fRowBytes; + y += 1; + } + } +} + +/////////////////////// these guys are not virtual, just a helpers + +void SkBlitter::blitMaskRegion(const SkMask& mask, const SkRegion& clip) { + if (clip.quickReject(mask.fBounds)) { + return; + } + + SkRegion::Cliperator clipper(clip, mask.fBounds); + + while (!clipper.done()) { + const SkIRect& cr = clipper.rect(); + this->blitMask(mask, cr); + clipper.next(); + } +} + +void SkBlitter::blitRectRegion(const SkIRect& rect, const SkRegion& clip) { + SkRegion::Cliperator clipper(clip, rect); + + while (!clipper.done()) { + const SkIRect& cr = clipper.rect(); + this->blitRect(cr.fLeft, cr.fTop, cr.width(), cr.height()); + clipper.next(); + } +} + +void SkBlitter::blitRegion(const SkRegion& clip) { + SkRegionPriv::VisitSpans(clip, [this](const SkIRect& r) { + this->blitRect(r.left(), r.top(), r.width(), r.height()); + }); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkNullBlitter::blitH(int x, int y, int width) {} + +void SkNullBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) {} + +void SkNullBlitter::blitV(int x, int y, int height, SkAlpha alpha) {} + +void SkNullBlitter::blitRect(int x, int y, int width, int height) {} + +void SkNullBlitter::blitMask(const SkMask& mask, const SkIRect& clip) {} + +const SkPixmap* SkNullBlitter::justAnOpaqueColor(uint32_t* value) { + return nullptr; +} + +bool SkNullBlitter::isNullBlitter() const { return true; } + +/////////////////////////////////////////////////////////////////////////////// + +static int compute_anti_width(const int16_t runs[]) { + int width = 0; + + for (;;) { + int count = runs[0]; + + SkASSERT(count >= 0); + if (count == 0) { + break; + } + width += count; + runs += count; + } + return width; +} + +static inline bool y_in_rect(int y, const SkIRect& rect) { + return (unsigned)(y - rect.fTop) < (unsigned)rect.height(); +} + +static inline bool x_in_rect(int x, const SkIRect& rect) { + return (unsigned)(x - rect.fLeft) < (unsigned)rect.width(); +} + +void SkRectClipBlitter::blitH(int left, int y, int width) { + SkASSERT(width > 0); + + if (!y_in_rect(y, fClipRect)) { + return; + } + + int right = left + width; + + if (left < fClipRect.fLeft) { + left = fClipRect.fLeft; + } + if (right > fClipRect.fRight) { + right = fClipRect.fRight; + } + + width = right - left; + if (width > 0) { + fBlitter->blitH(left, y, width); + } +} + +void SkRectClipBlitter::blitAntiH(int left, int y, const SkAlpha aa[], + const int16_t runs[]) { + if (!y_in_rect(y, fClipRect) || left >= fClipRect.fRight) { + return; + } + + int x0 = left; + int x1 = left + compute_anti_width(runs); + + if (x1 <= fClipRect.fLeft) { + return; + } + + SkASSERT(x0 < x1); + if (x0 < fClipRect.fLeft) { + int dx = fClipRect.fLeft - x0; + SkAlphaRuns::BreakAt((int16_t*)runs, (uint8_t*)aa, dx); + runs += dx; + aa += dx; + x0 = fClipRect.fLeft; + } + + SkASSERT(x0 < x1 && runs[x1 - x0] == 0); + if (x1 > fClipRect.fRight) { + x1 = fClipRect.fRight; + SkAlphaRuns::BreakAt((int16_t*)runs, (uint8_t*)aa, x1 - x0); + ((int16_t*)runs)[x1 - x0] = 0; + } + + SkASSERT(x0 < x1 && runs[x1 - x0] == 0); + SkASSERT(compute_anti_width(runs) == x1 - x0); + + fBlitter->blitAntiH(x0, y, aa, runs); +} + +void SkRectClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + SkASSERT(height > 0); + + if (!x_in_rect(x, fClipRect)) { + return; + } + + int y0 = y; + int y1 = y + height; + + if (y0 < fClipRect.fTop) { + y0 = fClipRect.fTop; + } + if (y1 > fClipRect.fBottom) { + y1 = fClipRect.fBottom; + } + + if (y0 < y1) { + fBlitter->blitV(x, y0, y1 - y0, alpha); + } +} + +void SkRectClipBlitter::blitRect(int left, int y, int width, int height) { + SkIRect r; + + r.setLTRB(left, y, left + width, y + height); + if (r.intersect(fClipRect)) { + fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height()); + } +} + +void SkRectClipBlitter::blitAntiRect(int left, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) { + SkIRect r; + + // The *true* width of the rectangle blitted is width+2: + r.setLTRB(left, y, left + width + 2, y + height); + if (r.intersect(fClipRect)) { + if (r.fLeft != left) { + SkASSERT(r.fLeft > left); + leftAlpha = 255; + } + if (r.fRight != left + width + 2) { + SkASSERT(r.fRight < left + width + 2); + rightAlpha = 255; + } + if (255 == leftAlpha && 255 == rightAlpha) { + fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height()); + } else if (1 == r.width()) { + if (r.fLeft == left) { + fBlitter->blitV(r.fLeft, r.fTop, r.height(), leftAlpha); + } else { + SkASSERT(r.fLeft == left + width + 1); + fBlitter->blitV(r.fLeft, r.fTop, r.height(), rightAlpha); + } + } else { + fBlitter->blitAntiRect(r.fLeft, r.fTop, r.width() - 2, r.height(), + leftAlpha, rightAlpha); + } + } +} + +void SkRectClipBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { + SkASSERT(mask.fBounds.contains(clip)); + + SkIRect r = clip; + + if (r.intersect(fClipRect)) { + fBlitter->blitMask(mask, r); + } +} + +const SkPixmap* SkRectClipBlitter::justAnOpaqueColor(uint32_t* value) { + return fBlitter->justAnOpaqueColor(value); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkRgnClipBlitter::blitH(int x, int y, int width) { + SkRegion::Spanerator span(*fRgn, y, x, x + width); + int left, right; + + while (span.next(&left, &right)) { + SkASSERT(left < right); + fBlitter->blitH(left, y, right - left); + } +} + +void SkRgnClipBlitter::blitAntiH(int x, int y, const SkAlpha aa[], + const int16_t runs[]) { + int width = compute_anti_width(runs); + SkRegion::Spanerator span(*fRgn, y, x, x + width); + int left, right; + SkDEBUGCODE(const SkIRect& bounds = fRgn->getBounds();) + + int prevRite = x; + while (span.next(&left, &right)) { + SkASSERT(x <= left); + SkASSERT(left < right); + SkASSERT(left >= bounds.fLeft && right <= bounds.fRight); + + SkAlphaRuns::Break((int16_t*)runs, (uint8_t*)aa, left - x, right - left); + + // now zero before left + if (left > prevRite) { + int index = prevRite - x; + ((uint8_t*)aa)[index] = 0; // skip runs after right + ((int16_t*)runs)[index] = SkToS16(left - prevRite); + } + + prevRite = right; + } + + if (prevRite > x) { + ((int16_t*)runs)[prevRite - x] = 0; + + if (x < 0) { + int skip = runs[0]; + SkASSERT(skip >= -x); + aa += skip; + runs += skip; + x += skip; + } + fBlitter->blitAntiH(x, y, aa, runs); + } +} + +void SkRgnClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + SkIRect bounds; + bounds.setXYWH(x, y, 1, height); + + SkRegion::Cliperator iter(*fRgn, bounds); + + while (!iter.done()) { + const SkIRect& r = iter.rect(); + SkASSERT(bounds.contains(r)); + + fBlitter->blitV(x, r.fTop, r.height(), alpha); + iter.next(); + } +} + +void SkRgnClipBlitter::blitRect(int x, int y, int width, int height) { + SkIRect bounds; + bounds.setXYWH(x, y, width, height); + + SkRegion::Cliperator iter(*fRgn, bounds); + + while (!iter.done()) { + const SkIRect& r = iter.rect(); + SkASSERT(bounds.contains(r)); + + fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height()); + iter.next(); + } +} + +void SkRgnClipBlitter::blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) { + // The *true* width of the rectangle to blit is width + 2 + SkIRect bounds; + bounds.setXYWH(x, y, width + 2, height); + + SkRegion::Cliperator iter(*fRgn, bounds); + + while (!iter.done()) { + const SkIRect& r = iter.rect(); + SkASSERT(bounds.contains(r)); + SkASSERT(r.fLeft >= x); + SkASSERT(r.fRight <= x + width + 2); + + SkAlpha effectiveLeftAlpha = (r.fLeft == x) ? leftAlpha : 255; + SkAlpha effectiveRightAlpha = (r.fRight == x + width + 2) ? + rightAlpha : 255; + + if (255 == effectiveLeftAlpha && 255 == effectiveRightAlpha) { + fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height()); + } else if (1 == r.width()) { + if (r.fLeft == x) { + fBlitter->blitV(r.fLeft, r.fTop, r.height(), + effectiveLeftAlpha); + } else { + SkASSERT(r.fLeft == x + width + 1); + fBlitter->blitV(r.fLeft, r.fTop, r.height(), + effectiveRightAlpha); + } + } else { + fBlitter->blitAntiRect(r.fLeft, r.fTop, r.width() - 2, r.height(), + effectiveLeftAlpha, effectiveRightAlpha); + } + iter.next(); + } +} + + +void SkRgnClipBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { + SkASSERT(mask.fBounds.contains(clip)); + + SkRegion::Cliperator iter(*fRgn, clip); + const SkIRect& r = iter.rect(); + SkBlitter* blitter = fBlitter; + + while (!iter.done()) { + blitter->blitMask(mask, r); + iter.next(); + } +} + +const SkPixmap* SkRgnClipBlitter::justAnOpaqueColor(uint32_t* value) { + return fBlitter->justAnOpaqueColor(value); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkBlitter* SkBlitterClipper::apply(SkBlitter* blitter, const SkRegion* clip, + const SkIRect* ir) { + if (clip) { + const SkIRect& clipR = clip->getBounds(); + + if (clip->isEmpty() || (ir && !SkIRect::Intersects(clipR, *ir))) { + blitter = &fNullBlitter; + } else if (clip->isRect()) { + if (ir == nullptr || !clipR.contains(*ir)) { + fRectBlitter.init(blitter, clipR); + blitter = &fRectBlitter; + } + } else { + fRgnBlitter.init(blitter, clip); + blitter = &fRgnBlitter; + } + } + return blitter; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkCoreBlitters.h" + +// hack for testing, not to be exposed to clients +bool gSkForceRasterPipelineBlitter; + +bool SkBlitter::UseRasterPipelineBlitter(const SkPixmap& device, const SkPaint& paint, + const SkMatrix& matrix) { + if (gSkForceRasterPipelineBlitter) { + return true; + } +#if 0 || defined(SK_FORCE_RASTER_PIPELINE_BLITTER) + return true; +#else + + const SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter()); + + // The legacy blitters cannot handle any of these complex features (anymore). + if (device.alphaType() == kUnpremul_SkAlphaType || + matrix.hasPerspective() || + paint.getColorFilter() || + paint.getBlendMode() > SkBlendMode::kLastCoeffMode || + paint.getFilterQuality() == kHigh_SkFilterQuality || + (mf && mf->getFormat() == SkMask::k3D_Format)) { + return true; + } + + // All the real legacy fast paths are for shaders and SrcOver. + // Choosing SkRasterPipelineBlitter will also let us to hit its single-color memset path. + if (!paint.getShader() && paint.getBlendMode() != SkBlendMode::kSrcOver) { + return true; + } + + auto cs = device.colorSpace(); + // We check (indirectly via makeContext()) later on if the shader can handle the colorspace + // in legacy mode, so here we just focus on if a single color needs raster-pipeline. + if (cs && !paint.getShader()) { + if (!paint.getColor4f().fitsInBytes() || !cs->isSRGB()) { + return true; + } + } + + // Only kN32 and 565 are handled by legacy blitters now, 565 mostly just for Android. + return device.colorType() != kN32_SkColorType + && device.colorType() != kRGB_565_SkColorType; +#endif +} + +SkBlitter* SkBlitter::Choose(const SkPixmap& device, + const SkMatrix& matrix, + const SkPaint& origPaint, + SkArenaAlloc* alloc, + bool drawCoverage) { + SkASSERT(alloc); + + if (kUnknown_SkColorType == device.colorType()) { + return alloc->make(); + } + + // We may tweak the original paint as we go. + SkTCopyOnFirstWrite paint(origPaint); + + // We have the most fast-paths for SrcOver, so see if we can act like SrcOver. + if (paint->getBlendMode() != SkBlendMode::kSrcOver) { + switch (SkInterpretXfermode(*paint, SkColorTypeIsAlwaysOpaque(device.colorType()))) { + case kSrcOver_SkXfermodeInterpretation: + paint.writable()->setBlendMode(SkBlendMode::kSrcOver); + break; + case kSkipDrawing_SkXfermodeInterpretation: + return alloc->make(); + default: + break; + } + } + + // A Clear blend mode will ignore the entire color pipeline, as if Src mode with 0x00000000. + if (paint->getBlendMode() == SkBlendMode::kClear) { + SkPaint* p = paint.writable(); + p->setShader(nullptr); + p->setColorFilter(nullptr); + p->setBlendMode(SkBlendMode::kSrc); + p->setColor(0x00000000); + } + +#ifndef SK_SUPPORT_LEGACY_COLORFILTER_NO_SHADER + if (paint->getColorFilter() && !paint->getShader()) { + // apply the filter to the paint's color, and then remove the filter + auto dstCS = device.colorSpace(); + SkPaint* p = paint.writable(); + p->setColor(p->getColorFilter()->filterColor4f(p->getColor4f(), sk_srgb_singleton(), dstCS), + dstCS); + p->setColorFilter(nullptr); + } +#endif + + if (drawCoverage) { + if (device.colorType() == kAlpha_8_SkColorType) { + SkASSERT(!paint->getShader()); + SkASSERT(paint->isSrcOver()); + return alloc->make(device, *paint); + } + return alloc->make(); + } + + if (paint->isDither() && !SkPaintPriv::ShouldDither(*paint, device.colorType())) { + paint.writable()->setDither(false); + } + +#if defined(SK_USE_SKVM_BLITTER) + if (auto blitter = SkCreateSkVMBlitter(device, *paint, matrix, alloc)) { + return blitter; + } +#endif + + // We'll end here for many interesting cases: color spaces, color filters, most color types. + if (UseRasterPipelineBlitter(device, *paint, matrix)) { + auto blitter = SkCreateRasterPipelineBlitter(device, *paint, matrix, alloc); + SkASSERT(blitter); + return blitter; + } + + // Everything but legacy kN32_SkColorType and kRGB_565_SkColorType should already be handled. + SkASSERT(device.colorType() == kN32_SkColorType || + device.colorType() == kRGB_565_SkColorType); + + // And we should either have a shader, be blending with SrcOver, or both. + SkASSERT(paint->getShader() || paint->getBlendMode() == SkBlendMode::kSrcOver); + + // Legacy blitters keep their shader state on a shader context. + SkShaderBase::Context* shaderContext = nullptr; + if (paint->getShader()) { + shaderContext = as_SB(paint->getShader())->makeContext( + {*paint, matrix, nullptr, device.colorType(), device.colorSpace()}, + alloc); + + // Creating the context isn't always possible... we'll just fall back to raster pipeline. + if (!shaderContext) { + auto blitter = SkCreateRasterPipelineBlitter(device, *paint, matrix, alloc); + SkASSERT(blitter); + return blitter; + } + } + + switch (device.colorType()) { + case kN32_SkColorType: + if (shaderContext) { + return alloc->make(device, *paint, shaderContext); + } else if (paint->getColor() == SK_ColorBLACK) { + return alloc->make(device, *paint); + } else if (paint->getAlpha() == 0xFF) { + return alloc->make(device, *paint); + } else { + return alloc->make(device, *paint); + } + + case kRGB_565_SkColorType: + if (shaderContext && SkRGB565_Shader_Blitter::Supports(device, *paint)) { + return alloc->make(device, *paint, shaderContext); + } else { + return SkCreateRasterPipelineBlitter(device, *paint, matrix, alloc); + } + + default: + SkASSERT(false); + return alloc->make(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +SkShaderBlitter::SkShaderBlitter(const SkPixmap& device, const SkPaint& paint, + SkShaderBase::Context* shaderContext) + : INHERITED(device) + , fShader(paint.getShader()) + , fShaderContext(shaderContext) { + SkASSERT(fShader); + SkASSERT(fShaderContext); + + fShader->ref(); + fShaderFlags = fShaderContext->getFlags(); + fConstInY = SkToBool(fShaderFlags & SkShaderBase::kConstInY32_Flag); +} + +SkShaderBlitter::~SkShaderBlitter() { + fShader->unref(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +void SkRectClipCheckBlitter::blitH(int x, int y, int width) { + SkASSERT(fClipRect.contains(SkIRect::MakeXYWH(x, y, width, 1))); + fBlitter->blitH(x, y, width); +} + +void SkRectClipCheckBlitter::blitAntiH(int x, int y, const SkAlpha aa[], const int16_t runs[]) { + const int16_t* iter = runs; + for (; *iter; iter += *iter) + ; + int width = iter - runs; + SkASSERT(fClipRect.contains(SkIRect::MakeXYWH(x, y, width, 1))); + fBlitter->blitAntiH(x, y, aa, runs); +} + +void SkRectClipCheckBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + SkASSERT(fClipRect.contains(SkIRect::MakeXYWH(x, y, 1, height))); + fBlitter->blitV(x, y, height, alpha); +} + +void SkRectClipCheckBlitter::blitRect(int x, int y, int width, int height) { + SkASSERT(fClipRect.contains(SkIRect::MakeXYWH(x, y, width, height))); + fBlitter->blitRect(x, y, width, height); +} + +void SkRectClipCheckBlitter::blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) { + bool skipLeft = !leftAlpha; + bool skipRight = !rightAlpha; +#ifdef SK_DEBUG + SkIRect r = SkIRect::MakeXYWH(x + skipLeft, y, width + 2 - skipRight - skipLeft, height); + SkASSERT(r.isEmpty() || fClipRect.contains(r)); +#endif + fBlitter->blitAntiRect(x, y, width, height, leftAlpha, rightAlpha); +} + +void SkRectClipCheckBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { + SkASSERT(mask.fBounds.contains(clip)); + SkASSERT(fClipRect.contains(clip)); + fBlitter->blitMask(mask, clip); +} + +const SkPixmap* SkRectClipCheckBlitter::justAnOpaqueColor(uint32_t* value) { + return fBlitter->justAnOpaqueColor(value); +} + +void SkRectClipCheckBlitter::blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) { + SkASSERT(fClipRect.contains(SkIRect::MakeXYWH(x, y, 2, 1))); + fBlitter->blitAntiH2(x, y, a0, a1); +} + +void SkRectClipCheckBlitter::blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) { + SkASSERT(fClipRect.contains(SkIRect::MakeXYWH(x, y, 1, 2))); + fBlitter->blitAntiV2(x, y, a0, a1); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkBlitter.h b/gfx/skia/skia/src/core/SkBlitter.h new file mode 100644 index 0000000000..ef37a1fca8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitter.h @@ -0,0 +1,321 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlitter_DEFINED +#define SkBlitter_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkRect.h" +#include "include/core/SkRegion.h" +#include "include/private/SkTo.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkImagePriv.h" +#include "src/shaders/SkShaderBase.h" + +class SkArenaAlloc; +class SkMatrix; +class SkPaint; +class SkPixmap; +struct SkMask; + +/** SkBlitter and its subclasses are responsible for actually writing pixels + into memory. Besides efficiency, they handle clipping and antialiasing. + A SkBlitter subclass contains all the context needed to generate pixels + for the destination and how src/generated pixels map to the destination. + The coordinates passed to the blitX calls are in destination pixel space. +*/ +class SkBlitter { +public: + virtual ~SkBlitter(); + + /// Blit a horizontal run of one or more pixels. + virtual void blitH(int x, int y, int width) = 0; + + /// Blit a horizontal run of antialiased pixels; runs[] is a *sparse* + /// zero-terminated run-length encoding of spans of constant alpha values. + /// The runs[] and antialias[] work together to represent long runs of pixels with the same + /// alphas. The runs[] contains the number of pixels with the same alpha, and antialias[] + /// contain the coverage value for that number of pixels. The runs[] (and antialias[]) are + /// encoded in a clever way. The runs array is zero terminated, and has enough entries for + /// each pixel plus one, in most cases some of the entries will not contain valid data. An entry + /// in the runs array contains the number of pixels (np) that have the same alpha value. The + /// next np value is found np entries away. For example, if runs[0] = 7, then the next valid + /// entry will by at runs[7]. The runs array and antialias[] are coupled by index. So, if the + /// np entry is at runs[45] = 12 then the alpha value can be found at antialias[45] = 0x88. + /// This would mean to use an alpha value of 0x88 for the next 12 pixels starting at pixel 45. + virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) = 0; + + /// Blit a vertical run of pixels with a constant alpha value. + virtual void blitV(int x, int y, int height, SkAlpha alpha); + + /// Blit a solid rectangle one or more pixels wide. + virtual void blitRect(int x, int y, int width, int height); + + /** Blit a rectangle with one alpha-blended column on the left, + width (zero or more) opaque pixels, and one alpha-blended column + on the right. + The result will always be at least two pixels wide. + */ + virtual void blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha); + + // Blit a rect in AA with size at least 3 x 3 (small rect has too many edge cases...) + void blitFatAntiRect(const SkRect& rect); + + /// Blit a pattern of pixels defined by a rectangle-clipped mask; + /// typically used for text. + virtual void blitMask(const SkMask&, const SkIRect& clip); + + /** If the blitter just sets a single value for each pixel, return the + bitmap it draws into, and assign value. If not, return nullptr and ignore + the value parameter. + */ + virtual const SkPixmap* justAnOpaqueColor(uint32_t* value); + + // (x, y), (x + 1, y) + virtual void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) { + int16_t runs[3]; + uint8_t aa[2]; + + runs[0] = 1; + runs[1] = 1; + runs[2] = 0; + aa[0] = SkToU8(a0); + aa[1] = SkToU8(a1); + this->blitAntiH(x, y, aa, runs); + } + + // (x, y), (x, y + 1) + virtual void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) { + int16_t runs[2]; + uint8_t aa[1]; + + runs[0] = 1; + runs[1] = 0; + aa[0] = SkToU8(a0); + this->blitAntiH(x, y, aa, runs); + // reset in case the clipping blitter modified runs + runs[0] = 1; + runs[1] = 0; + aa[0] = SkToU8(a1); + this->blitAntiH(x, y + 1, aa, runs); + } + + /** + * Special method just to identify the null blitter, which is returned + * from Choose() if the request cannot be fulfilled. Default impl + * returns false. + */ + virtual bool isNullBlitter() const; + + /** + * Special methods for blitters that can blit more than one row at a time. + * This function returns the number of rows that this blitter could optimally + * process at a time. It is still required to support blitting one scanline + * at a time. + */ + virtual int requestRowsPreserved() const { return 1; } + + /** + * This function allocates memory for the blitter that the blitter then owns. + * The memory can be used by the calling function at will, but it will be + * released when the blitter's destructor is called. This function returns + * nullptr if no persistent memory is needed by the blitter. + */ + virtual void* allocBlitMemory(size_t sz) { + return fBlitMemory.reset(sz, SkAutoMalloc::kReuse_OnShrink); + } + + ///@name non-virtual helpers + void blitMaskRegion(const SkMask& mask, const SkRegion& clip); + void blitRectRegion(const SkIRect& rect, const SkRegion& clip); + void blitRegion(const SkRegion& clip); + ///@} + + /** @name Factories + Return the correct blitter to use given the specified context. + */ + static SkBlitter* Choose(const SkPixmap& dst, + const SkMatrix& matrix, + const SkPaint& paint, + SkArenaAlloc*, + bool drawCoverage = false); + + static SkBlitter* ChooseSprite(const SkPixmap& dst, + const SkPaint&, + const SkPixmap& src, + int left, int top, + SkArenaAlloc*); + ///@} + + static bool UseRasterPipelineBlitter(const SkPixmap&, const SkPaint&, const SkMatrix&); + +protected: + SkAutoMalloc fBlitMemory; +}; + +/** This blitter silently never draws anything. +*/ +class SkNullBlitter : public SkBlitter { +public: + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitMask(const SkMask&, const SkIRect& clip) override; + const SkPixmap* justAnOpaqueColor(uint32_t* value) override; + bool isNullBlitter() const override; +}; + +/** Wraps another (real) blitter, and ensures that the real blitter is only + called with coordinates that have been clipped by the specified clipRect. + This means the caller need not perform the clipping ahead of time. +*/ +class SkRectClipBlitter : public SkBlitter { +public: + void init(SkBlitter* blitter, const SkIRect& clipRect) { + SkASSERT(!clipRect.isEmpty()); + fBlitter = blitter; + fClipRect = clipRect; + } + + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + virtual void blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) override; + void blitMask(const SkMask&, const SkIRect& clip) override; + const SkPixmap* justAnOpaqueColor(uint32_t* value) override; + + int requestRowsPreserved() const override { + return fBlitter->requestRowsPreserved(); + } + + void* allocBlitMemory(size_t sz) override { + return fBlitter->allocBlitMemory(sz); + } + +private: + SkBlitter* fBlitter; + SkIRect fClipRect; +}; + +/** Wraps another (real) blitter, and ensures that the real blitter is only + called with coordinates that have been clipped by the specified clipRgn. + This means the caller need not perform the clipping ahead of time. +*/ +class SkRgnClipBlitter : public SkBlitter { +public: + void init(SkBlitter* blitter, const SkRegion* clipRgn) { + SkASSERT(clipRgn && !clipRgn->isEmpty()); + fBlitter = blitter; + fRgn = clipRgn; + } + + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) override; + void blitMask(const SkMask&, const SkIRect& clip) override; + const SkPixmap* justAnOpaqueColor(uint32_t* value) override; + + int requestRowsPreserved() const override { + return fBlitter->requestRowsPreserved(); + } + + void* allocBlitMemory(size_t sz) override { + return fBlitter->allocBlitMemory(sz); + } + +private: + SkBlitter* fBlitter; + const SkRegion* fRgn; +}; + +#ifdef SK_DEBUG +class SkRectClipCheckBlitter : public SkBlitter { +public: + void init(SkBlitter* blitter, const SkIRect& clipRect) { + SkASSERT(blitter); + SkASSERT(!clipRect.isEmpty()); + fBlitter = blitter; + fClipRect = clipRect; + } + + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) override; + void blitMask(const SkMask&, const SkIRect& clip) override; + const SkPixmap* justAnOpaqueColor(uint32_t* value) override; + void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) override; + void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) override; + + int requestRowsPreserved() const override { + return fBlitter->requestRowsPreserved(); + } + + void* allocBlitMemory(size_t sz) override { + return fBlitter->allocBlitMemory(sz); + } + +private: + SkBlitter* fBlitter; + SkIRect fClipRect; +}; +#endif + +/** Factory to set up the appropriate most-efficient wrapper blitter + to apply a clip. Returns a pointer to a member, so lifetime must + be managed carefully. +*/ +class SkBlitterClipper { +public: + SkBlitter* apply(SkBlitter* blitter, const SkRegion* clip, + const SkIRect* bounds = nullptr); + +private: + SkNullBlitter fNullBlitter; + SkRectClipBlitter fRectBlitter; + SkRgnClipBlitter fRgnBlitter; +}; + +#define SHARD(code) fA->code; fB->code; + +class SkPairBlitter : public SkBlitter { + SkBlitter* fA = nullptr; + SkBlitter* fB = nullptr; +public: + SkPairBlitter(SkBlitter* a, SkBlitter* b) : fA(a), fB(b) {} + + void blitH(int x, int y, int width) override { SHARD(blitH(x, y, width)) } + void blitAntiH(int x, int y, const SkAlpha alphas[], const int16_t runs[]) override { + SHARD(blitAntiH(x, y, alphas, runs)) + } + void blitV(int x, int y, int height, SkAlpha alpha) override { + SHARD(blitV(x, y, height, alpha)) + } + void blitRect(int x, int y, int width, int height) override { + SHARD(blitRect(x, y, width, height)) + } + void blitAntiRect(int x, int y, int width, int height, + SkAlpha leftAlpha, SkAlpha rightAlpha) override { + SHARD(blitAntiRect(x, y, width, height, leftAlpha, rightAlpha)) + } + void blitMask(const SkMask& mask, const SkIRect& clip) override { SHARD(blitMask(mask, clip)) } + const SkPixmap* justAnOpaqueColor(uint32_t* value) override { return nullptr; } + void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) override { SHARD(blitAntiH2(x, y, a0, a1)) } + void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) override { SHARD(blitAntiV2(x, y, a0, a1)) } +}; +#undef SHARD + +#endif diff --git a/gfx/skia/skia/src/core/SkBlitter_A8.cpp b/gfx/skia/skia/src/core/SkBlitter_A8.cpp new file mode 100644 index 0000000000..7217c9e6b6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitter_A8.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkShader.h" +#include "include/private/SkColorData.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkXfermodePriv.h" + +SkA8_Coverage_Blitter::SkA8_Coverage_Blitter(const SkPixmap& device, + const SkPaint& paint) : SkRasterBlitter(device) { + SkASSERT(nullptr == paint.getShader()); + SkASSERT(paint.isSrcOver()); + SkASSERT(nullptr == paint.getColorFilter()); +} + +void SkA8_Coverage_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) { + uint8_t* device = fDevice.writable_addr8(x, y); + SkDEBUGCODE(int totalCount = 0;) + + for (;;) { + int count = runs[0]; + SkASSERT(count >= 0); + if (count == 0) { + return; + } + if (antialias[0]) { + memset(device, antialias[0], count); + } + runs += count; + antialias += count; + device += count; + + SkDEBUGCODE(totalCount += count;) + } + SkASSERT(fDevice.width() == totalCount); +} + +void SkA8_Coverage_Blitter::blitH(int x, int y, int width) { + memset(fDevice.writable_addr8(x, y), 0xFF, width); +} + +void SkA8_Coverage_Blitter::blitV(int x, int y, int height, SkAlpha alpha) { + if (0 == alpha) { + return; + } + + uint8_t* dst = fDevice.writable_addr8(x, y); + const size_t dstRB = fDevice.rowBytes(); + while (--height >= 0) { + *dst = alpha; + dst += dstRB; + } +} + +void SkA8_Coverage_Blitter::blitRect(int x, int y, int width, int height) { + uint8_t* dst = fDevice.writable_addr8(x, y); + const size_t dstRB = fDevice.rowBytes(); + while (--height >= 0) { + memset(dst, 0xFF, width); + dst += dstRB; + } +} + +void SkA8_Coverage_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) { + if (SkMask::kA8_Format != mask.fFormat) { + this->INHERITED::blitMask(mask, clip); + return; + } + + int x = clip.fLeft; + int y = clip.fTop; + int width = clip.width(); + int height = clip.height(); + + uint8_t* dst = fDevice.writable_addr8(x, y); + const uint8_t* src = mask.getAddr8(x, y); + const size_t srcRB = mask.fRowBytes; + const size_t dstRB = fDevice.rowBytes(); + + while (--height >= 0) { + memcpy(dst, src, width); + dst += dstRB; + src += srcRB; + } +} + +const SkPixmap* SkA8_Coverage_Blitter::justAnOpaqueColor(uint32_t*) { + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkBlitter_ARGB32.cpp b/gfx/skia/skia/src/core/SkBlitter_ARGB32.cpp new file mode 100644 index 0000000000..a67d2e250c --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitter_ARGB32.cpp @@ -0,0 +1,1420 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkShader.h" +#include "include/private/SkColorData.h" +#include "include/private/SkVx.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkUtils.h" +#include "src/core/SkXfermodePriv.h" + +static inline int upscale_31_to_32(int value) { + SkASSERT((unsigned)value <= 31); + return value + (value >> 4); +} + +static inline int blend_32(int src, int dst, int scale) { + SkASSERT((unsigned)src <= 0xFF); + SkASSERT((unsigned)dst <= 0xFF); + SkASSERT((unsigned)scale <= 32); + return dst + ((src - dst) * scale >> 5); +} + +static inline SkPMColor blend_lcd16(int srcA, int srcR, int srcG, int srcB, + SkPMColor dst, uint16_t mask) { + if (mask == 0) { + return dst; + } + + /* We want all of these in 5bits, hence the shifts in case one of them + * (green) is 6bits. + */ + int maskR = SkGetPackedR16(mask) >> (SK_R16_BITS - 5); + int maskG = SkGetPackedG16(mask) >> (SK_G16_BITS - 5); + int maskB = SkGetPackedB16(mask) >> (SK_B16_BITS - 5); + + // Now upscale them to 0..32, so we can use blend32 + maskR = upscale_31_to_32(maskR); + maskG = upscale_31_to_32(maskG); + maskB = upscale_31_to_32(maskB); + + // srcA has been upscaled to 256 before passed into this function + maskR = maskR * srcA >> 8; + maskG = maskG * srcA >> 8; + maskB = maskB * srcA >> 8; + + int dstR = SkGetPackedR32(dst); + int dstG = SkGetPackedG32(dst); + int dstB = SkGetPackedB32(dst); + + // LCD blitting is only supported if the dst is known/required + // to be opaque + return SkPackARGB32(0xFF, + blend_32(srcR, dstR, maskR), + blend_32(srcG, dstG, maskG), + blend_32(srcB, dstB, maskB)); +} + +static inline SkPMColor blend_lcd16_opaque(int srcR, int srcG, int srcB, + SkPMColor dst, uint16_t mask, + SkPMColor opaqueDst) { + if (mask == 0) { + return dst; + } + + if (0xFFFF == mask) { + return opaqueDst; + } + + /* We want all of these in 5bits, hence the shifts in case one of them + * (green) is 6bits. + */ + int maskR = SkGetPackedR16(mask) >> (SK_R16_BITS - 5); + int maskG = SkGetPackedG16(mask) >> (SK_G16_BITS - 5); + int maskB = SkGetPackedB16(mask) >> (SK_B16_BITS - 5); + + // Now upscale them to 0..32, so we can use blend32 + maskR = upscale_31_to_32(maskR); + maskG = upscale_31_to_32(maskG); + maskB = upscale_31_to_32(maskB); + + int dstR = SkGetPackedR32(dst); + int dstG = SkGetPackedG32(dst); + int dstB = SkGetPackedB32(dst); + + // LCD blitting is only supported if the dst is known/required + // to be opaque + return SkPackARGB32(0xFF, + blend_32(srcR, dstR, maskR), + blend_32(srcG, dstG, maskG), + blend_32(srcB, dstB, maskB)); +} + + +// TODO: rewrite at least the SSE code here. It's miserable. + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include + + // The following (left) shifts cause the top 5 bits of the mask components to + // line up with the corresponding components in an SkPMColor. + // Note that the mask's RGB16 order may differ from the SkPMColor order. + #define SK_R16x5_R32x5_SHIFT (SK_R32_SHIFT - SK_R16_SHIFT - SK_R16_BITS + 5) + #define SK_G16x5_G32x5_SHIFT (SK_G32_SHIFT - SK_G16_SHIFT - SK_G16_BITS + 5) + #define SK_B16x5_B32x5_SHIFT (SK_B32_SHIFT - SK_B16_SHIFT - SK_B16_BITS + 5) + + #if SK_R16x5_R32x5_SHIFT == 0 + #define SkPackedR16x5ToUnmaskedR32x5_SSE2(x) (x) + #elif SK_R16x5_R32x5_SHIFT > 0 + #define SkPackedR16x5ToUnmaskedR32x5_SSE2(x) (_mm_slli_epi32(x, SK_R16x5_R32x5_SHIFT)) + #else + #define SkPackedR16x5ToUnmaskedR32x5_SSE2(x) (_mm_srli_epi32(x, -SK_R16x5_R32x5_SHIFT)) + #endif + + #if SK_G16x5_G32x5_SHIFT == 0 + #define SkPackedG16x5ToUnmaskedG32x5_SSE2(x) (x) + #elif SK_G16x5_G32x5_SHIFT > 0 + #define SkPackedG16x5ToUnmaskedG32x5_SSE2(x) (_mm_slli_epi32(x, SK_G16x5_G32x5_SHIFT)) + #else + #define SkPackedG16x5ToUnmaskedG32x5_SSE2(x) (_mm_srli_epi32(x, -SK_G16x5_G32x5_SHIFT)) + #endif + + #if SK_B16x5_B32x5_SHIFT == 0 + #define SkPackedB16x5ToUnmaskedB32x5_SSE2(x) (x) + #elif SK_B16x5_B32x5_SHIFT > 0 + #define SkPackedB16x5ToUnmaskedB32x5_SSE2(x) (_mm_slli_epi32(x, SK_B16x5_B32x5_SHIFT)) + #else + #define SkPackedB16x5ToUnmaskedB32x5_SSE2(x) (_mm_srli_epi32(x, -SK_B16x5_B32x5_SHIFT)) + #endif + + static __m128i blend_lcd16_sse2(__m128i &src, __m128i &dst, __m128i &mask, __m128i &srcA) { + // In the following comments, the components of src, dst and mask are + // abbreviated as (s)rc, (d)st, and (m)ask. Color components are marked + // by an R, G, B, or A suffix. Components of one of the four pixels that + // are processed in parallel are marked with 0, 1, 2, and 3. "d1B", for + // example is the blue channel of the second destination pixel. Memory + // layout is shown for an ARGB byte order in a color value. + + // src and srcA store 8-bit values interleaved with zeros. + // src = (0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0) + // srcA = (srcA, 0, srcA, 0, srcA, 0, srcA, 0, + // srcA, 0, srcA, 0, srcA, 0, srcA, 0) + // mask stores 16-bit values (compressed three channels) interleaved with zeros. + // Lo and Hi denote the low and high bytes of a 16-bit value, respectively. + // mask = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0, + // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0) + + // Get the R,G,B of each 16bit mask pixel, we want all of them in 5 bits. + // r = (0, m0R, 0, 0, 0, m1R, 0, 0, 0, m2R, 0, 0, 0, m3R, 0, 0) + __m128i r = _mm_and_si128(SkPackedR16x5ToUnmaskedR32x5_SSE2(mask), + _mm_set1_epi32(0x1F << SK_R32_SHIFT)); + + // g = (0, 0, m0G, 0, 0, 0, m1G, 0, 0, 0, m2G, 0, 0, 0, m3G, 0) + __m128i g = _mm_and_si128(SkPackedG16x5ToUnmaskedG32x5_SSE2(mask), + _mm_set1_epi32(0x1F << SK_G32_SHIFT)); + + // b = (0, 0, 0, m0B, 0, 0, 0, m1B, 0, 0, 0, m2B, 0, 0, 0, m3B) + __m128i b = _mm_and_si128(SkPackedB16x5ToUnmaskedB32x5_SSE2(mask), + _mm_set1_epi32(0x1F << SK_B32_SHIFT)); + + // Pack the 4 16bit mask pixels into 4 32bit pixels, (p0, p1, p2, p3) + // Each component (m0R, m0G, etc.) is then a 5-bit value aligned to an + // 8-bit position + // mask = (0, m0R, m0G, m0B, 0, m1R, m1G, m1B, + // 0, m2R, m2G, m2B, 0, m3R, m3G, m3B) + mask = _mm_or_si128(_mm_or_si128(r, g), b); + + // Interleave R,G,B into the lower byte of word. + // i.e. split the sixteen 8-bit values from mask into two sets of eight + // 16-bit values, padded by zero. + __m128i maskLo, maskHi; + // maskLo = (0, 0, m0R, 0, m0G, 0, m0B, 0, 0, 0, m1R, 0, m1G, 0, m1B, 0) + maskLo = _mm_unpacklo_epi8(mask, _mm_setzero_si128()); + // maskHi = (0, 0, m2R, 0, m2G, 0, m2B, 0, 0, 0, m3R, 0, m3G, 0, m3B, 0) + maskHi = _mm_unpackhi_epi8(mask, _mm_setzero_si128()); + + // Upscale from 0..31 to 0..32 + // (allows to replace division by left-shift further down) + // Left-shift each component by 4 and add the result back to that component, + // mapping numbers in the range 0..15 to 0..15, and 16..31 to 17..32 + maskLo = _mm_add_epi16(maskLo, _mm_srli_epi16(maskLo, 4)); + maskHi = _mm_add_epi16(maskHi, _mm_srli_epi16(maskHi, 4)); + + // Multiply each component of maskLo and maskHi by srcA + maskLo = _mm_mullo_epi16(maskLo, srcA); + maskHi = _mm_mullo_epi16(maskHi, srcA); + + // Left shift mask components by 8 (divide by 256) + maskLo = _mm_srli_epi16(maskLo, 8); + maskHi = _mm_srli_epi16(maskHi, 8); + + // Interleave R,G,B into the lower byte of the word + // dstLo = (0, 0, d0R, 0, d0G, 0, d0B, 0, 0, 0, d1R, 0, d1G, 0, d1B, 0) + __m128i dstLo = _mm_unpacklo_epi8(dst, _mm_setzero_si128()); + // dstLo = (0, 0, d2R, 0, d2G, 0, d2B, 0, 0, 0, d3R, 0, d3G, 0, d3B, 0) + __m128i dstHi = _mm_unpackhi_epi8(dst, _mm_setzero_si128()); + + // mask = (src - dst) * mask + maskLo = _mm_mullo_epi16(maskLo, _mm_sub_epi16(src, dstLo)); + maskHi = _mm_mullo_epi16(maskHi, _mm_sub_epi16(src, dstHi)); + + // mask = (src - dst) * mask >> 5 + maskLo = _mm_srai_epi16(maskLo, 5); + maskHi = _mm_srai_epi16(maskHi, 5); + + // Add two pixels into result. + // result = dst + ((src - dst) * mask >> 5) + __m128i resultLo = _mm_add_epi16(dstLo, maskLo); + __m128i resultHi = _mm_add_epi16(dstHi, maskHi); + + // Pack into 4 32bit dst pixels. + // resultLo and resultHi contain eight 16-bit components (two pixels) each. + // Merge into one SSE regsiter with sixteen 8-bit values (four pixels), + // clamping to 255 if necessary. + return _mm_packus_epi16(resultLo, resultHi); + } + + static __m128i blend_lcd16_opaque_sse2(__m128i &src, __m128i &dst, __m128i &mask) { + // In the following comments, the components of src, dst and mask are + // abbreviated as (s)rc, (d)st, and (m)ask. Color components are marked + // by an R, G, B, or A suffix. Components of one of the four pixels that + // are processed in parallel are marked with 0, 1, 2, and 3. "d1B", for + // example is the blue channel of the second destination pixel. Memory + // layout is shown for an ARGB byte order in a color value. + + // src and srcA store 8-bit values interleaved with zeros. + // src = (0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0) + // mask stores 16-bit values (shown as high and low bytes) interleaved with + // zeros + // mask = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0, + // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0) + + // Get the R,G,B of each 16bit mask pixel, we want all of them in 5 bits. + // r = (0, m0R, 0, 0, 0, m1R, 0, 0, 0, m2R, 0, 0, 0, m3R, 0, 0) + __m128i r = _mm_and_si128(SkPackedR16x5ToUnmaskedR32x5_SSE2(mask), + _mm_set1_epi32(0x1F << SK_R32_SHIFT)); + + // g = (0, 0, m0G, 0, 0, 0, m1G, 0, 0, 0, m2G, 0, 0, 0, m3G, 0) + __m128i g = _mm_and_si128(SkPackedG16x5ToUnmaskedG32x5_SSE2(mask), + _mm_set1_epi32(0x1F << SK_G32_SHIFT)); + + // b = (0, 0, 0, m0B, 0, 0, 0, m1B, 0, 0, 0, m2B, 0, 0, 0, m3B) + __m128i b = _mm_and_si128(SkPackedB16x5ToUnmaskedB32x5_SSE2(mask), + _mm_set1_epi32(0x1F << SK_B32_SHIFT)); + + // Pack the 4 16bit mask pixels into 4 32bit pixels, (p0, p1, p2, p3) + // Each component (m0R, m0G, etc.) is then a 5-bit value aligned to an + // 8-bit position + // mask = (0, m0R, m0G, m0B, 0, m1R, m1G, m1B, + // 0, m2R, m2G, m2B, 0, m3R, m3G, m3B) + mask = _mm_or_si128(_mm_or_si128(r, g), b); + + // Interleave R,G,B into the lower byte of word. + // i.e. split the sixteen 8-bit values from mask into two sets of eight + // 16-bit values, padded by zero. + __m128i maskLo, maskHi; + // maskLo = (0, 0, m0R, 0, m0G, 0, m0B, 0, 0, 0, m1R, 0, m1G, 0, m1B, 0) + maskLo = _mm_unpacklo_epi8(mask, _mm_setzero_si128()); + // maskHi = (0, 0, m2R, 0, m2G, 0, m2B, 0, 0, 0, m3R, 0, m3G, 0, m3B, 0) + maskHi = _mm_unpackhi_epi8(mask, _mm_setzero_si128()); + + // Upscale from 0..31 to 0..32 + // (allows to replace division by left-shift further down) + // Left-shift each component by 4 and add the result back to that component, + // mapping numbers in the range 0..15 to 0..15, and 16..31 to 17..32 + maskLo = _mm_add_epi16(maskLo, _mm_srli_epi16(maskLo, 4)); + maskHi = _mm_add_epi16(maskHi, _mm_srli_epi16(maskHi, 4)); + + // Interleave R,G,B into the lower byte of the word + // dstLo = (0, 0, d0R, 0, d0G, 0, d0B, 0, 0, 0, d1R, 0, d1G, 0, d1B, 0) + __m128i dstLo = _mm_unpacklo_epi8(dst, _mm_setzero_si128()); + // dstLo = (0, 0, d2R, 0, d2G, 0, d2B, 0, 0, 0, d3R, 0, d3G, 0, d3B, 0) + __m128i dstHi = _mm_unpackhi_epi8(dst, _mm_setzero_si128()); + + // mask = (src - dst) * mask + maskLo = _mm_mullo_epi16(maskLo, _mm_sub_epi16(src, dstLo)); + maskHi = _mm_mullo_epi16(maskHi, _mm_sub_epi16(src, dstHi)); + + // mask = (src - dst) * mask >> 5 + maskLo = _mm_srai_epi16(maskLo, 5); + maskHi = _mm_srai_epi16(maskHi, 5); + + // Add two pixels into result. + // result = dst + ((src - dst) * mask >> 5) + __m128i resultLo = _mm_add_epi16(dstLo, maskLo); + __m128i resultHi = _mm_add_epi16(dstHi, maskHi); + + // Pack into 4 32bit dst pixels and force opaque. + // resultLo and resultHi contain eight 16-bit components (two pixels) each. + // Merge into one SSE regsiter with sixteen 8-bit values (four pixels), + // clamping to 255 if necessary. Set alpha components to 0xFF. + return _mm_or_si128(_mm_packus_epi16(resultLo, resultHi), + _mm_set1_epi32(SK_A32_MASK << SK_A32_SHIFT)); + } + + void blit_row_lcd16(SkPMColor dst[], const uint16_t mask[], SkColor src, int width, SkPMColor) { + if (width <= 0) { + return; + } + + int srcA = SkColorGetA(src); + int srcR = SkColorGetR(src); + int srcG = SkColorGetG(src); + int srcB = SkColorGetB(src); + + srcA = SkAlpha255To256(srcA); + + if (width >= 4) { + SkASSERT(((size_t)dst & 0x03) == 0); + while (((size_t)dst & 0x0F) != 0) { + *dst = blend_lcd16(srcA, srcR, srcG, srcB, *dst, *mask); + mask++; + dst++; + width--; + } + + __m128i *d = reinterpret_cast<__m128i*>(dst); + // Set alpha to 0xFF and replicate source four times in SSE register. + __m128i src_sse = _mm_set1_epi32(SkPackARGB32(0xFF, srcR, srcG, srcB)); + // Interleave with zeros to get two sets of four 16-bit values. + src_sse = _mm_unpacklo_epi8(src_sse, _mm_setzero_si128()); + // Set srcA_sse to contain eight copies of srcA, padded with zero. + // src_sse=(0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0) + __m128i srcA_sse = _mm_set1_epi16(srcA); + while (width >= 4) { + // Load four destination pixels into dst_sse. + __m128i dst_sse = _mm_load_si128(d); + // Load four 16-bit masks into lower half of mask_sse. + __m128i mask_sse = _mm_loadl_epi64( + reinterpret_cast(mask)); + + // Check whether masks are equal to 0 and get the highest bit + // of each byte of result, if masks are all zero, we will get + // pack_cmp to 0xFFFF + int pack_cmp = _mm_movemask_epi8(_mm_cmpeq_epi16(mask_sse, + _mm_setzero_si128())); + + // if mask pixels are not all zero, we will blend the dst pixels + if (pack_cmp != 0xFFFF) { + // Unpack 4 16bit mask pixels to + // mask_sse = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0, + // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0) + mask_sse = _mm_unpacklo_epi16(mask_sse, + _mm_setzero_si128()); + + // Process 4 32bit dst pixels + __m128i result = blend_lcd16_sse2(src_sse, dst_sse, mask_sse, srcA_sse); + _mm_store_si128(d, result); + } + + d++; + mask += 4; + width -= 4; + } + + dst = reinterpret_cast(d); + } + + while (width > 0) { + *dst = blend_lcd16(srcA, srcR, srcG, srcB, *dst, *mask); + mask++; + dst++; + width--; + } + } + + void blit_row_lcd16_opaque(SkPMColor dst[], const uint16_t mask[], + SkColor src, int width, SkPMColor opaqueDst) { + if (width <= 0) { + return; + } + + int srcR = SkColorGetR(src); + int srcG = SkColorGetG(src); + int srcB = SkColorGetB(src); + + if (width >= 4) { + SkASSERT(((size_t)dst & 0x03) == 0); + while (((size_t)dst & 0x0F) != 0) { + *dst = blend_lcd16_opaque(srcR, srcG, srcB, *dst, *mask, opaqueDst); + mask++; + dst++; + width--; + } + + __m128i *d = reinterpret_cast<__m128i*>(dst); + // Set alpha to 0xFF and replicate source four times in SSE register. + __m128i src_sse = _mm_set1_epi32(SkPackARGB32(0xFF, srcR, srcG, srcB)); + // Set srcA_sse to contain eight copies of srcA, padded with zero. + // src_sse=(0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0) + src_sse = _mm_unpacklo_epi8(src_sse, _mm_setzero_si128()); + while (width >= 4) { + // Load four destination pixels into dst_sse. + __m128i dst_sse = _mm_load_si128(d); + // Load four 16-bit masks into lower half of mask_sse. + __m128i mask_sse = _mm_loadl_epi64( + reinterpret_cast(mask)); + + // Check whether masks are equal to 0 and get the highest bit + // of each byte of result, if masks are all zero, we will get + // pack_cmp to 0xFFFF + int pack_cmp = _mm_movemask_epi8(_mm_cmpeq_epi16(mask_sse, + _mm_setzero_si128())); + + // if mask pixels are not all zero, we will blend the dst pixels + if (pack_cmp != 0xFFFF) { + // Unpack 4 16bit mask pixels to + // mask_sse = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0, + // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0) + mask_sse = _mm_unpacklo_epi16(mask_sse, + _mm_setzero_si128()); + + // Process 4 32bit dst pixels + __m128i result = blend_lcd16_opaque_sse2(src_sse, dst_sse, mask_sse); + _mm_store_si128(d, result); + } + + d++; + mask += 4; + width -= 4; + } + + dst = reinterpret_cast(d); + } + + while (width > 0) { + *dst = blend_lcd16_opaque(srcR, srcG, srcB, *dst, *mask, opaqueDst); + mask++; + dst++; + width--; + } + } + +#elif defined(SK_ARM_HAS_NEON) + #include + + #define NEON_A (SK_A32_SHIFT / 8) + #define NEON_R (SK_R32_SHIFT / 8) + #define NEON_G (SK_G32_SHIFT / 8) + #define NEON_B (SK_B32_SHIFT / 8) + + static inline uint8x8_t blend_32_neon(uint8x8_t src, uint8x8_t dst, uint16x8_t scale) { + int16x8_t src_wide, dst_wide; + + src_wide = vreinterpretq_s16_u16(vmovl_u8(src)); + dst_wide = vreinterpretq_s16_u16(vmovl_u8(dst)); + + src_wide = (src_wide - dst_wide) * vreinterpretq_s16_u16(scale); + + dst_wide += vshrq_n_s16(src_wide, 5); + + return vmovn_u16(vreinterpretq_u16_s16(dst_wide)); + } + + void blit_row_lcd16_opaque(SkPMColor dst[], const uint16_t src[], + SkColor color, int width, + SkPMColor opaqueDst) { + int colR = SkColorGetR(color); + int colG = SkColorGetG(color); + int colB = SkColorGetB(color); + + uint8x8_t vcolR = vdup_n_u8(colR); + uint8x8_t vcolG = vdup_n_u8(colG); + uint8x8_t vcolB = vdup_n_u8(colB); + uint8x8_t vopqDstA = vdup_n_u8(SkGetPackedA32(opaqueDst)); + uint8x8_t vopqDstR = vdup_n_u8(SkGetPackedR32(opaqueDst)); + uint8x8_t vopqDstG = vdup_n_u8(SkGetPackedG32(opaqueDst)); + uint8x8_t vopqDstB = vdup_n_u8(SkGetPackedB32(opaqueDst)); + + while (width >= 8) { + uint8x8x4_t vdst; + uint16x8_t vmask; + uint16x8_t vmaskR, vmaskG, vmaskB; + uint8x8_t vsel_trans, vsel_opq; + + vdst = vld4_u8((uint8_t*)dst); + vmask = vld1q_u16(src); + + // Prepare compare masks + vsel_trans = vmovn_u16(vceqq_u16(vmask, vdupq_n_u16(0))); + vsel_opq = vmovn_u16(vceqq_u16(vmask, vdupq_n_u16(0xFFFF))); + + // Get all the color masks on 5 bits + vmaskR = vshrq_n_u16(vmask, SK_R16_SHIFT); + vmaskG = vshrq_n_u16(vshlq_n_u16(vmask, SK_R16_BITS), + SK_B16_BITS + SK_R16_BITS + 1); + vmaskB = vmask & vdupq_n_u16(SK_B16_MASK); + + // Upscale to 0..32 + vmaskR = vmaskR + vshrq_n_u16(vmaskR, 4); + vmaskG = vmaskG + vshrq_n_u16(vmaskG, 4); + vmaskB = vmaskB + vshrq_n_u16(vmaskB, 4); + + vdst.val[NEON_A] = vbsl_u8(vsel_trans, vdst.val[NEON_A], vdup_n_u8(0xFF)); + vdst.val[NEON_A] = vbsl_u8(vsel_opq, vopqDstA, vdst.val[NEON_A]); + + vdst.val[NEON_R] = blend_32_neon(vcolR, vdst.val[NEON_R], vmaskR); + vdst.val[NEON_G] = blend_32_neon(vcolG, vdst.val[NEON_G], vmaskG); + vdst.val[NEON_B] = blend_32_neon(vcolB, vdst.val[NEON_B], vmaskB); + + vdst.val[NEON_R] = vbsl_u8(vsel_opq, vopqDstR, vdst.val[NEON_R]); + vdst.val[NEON_G] = vbsl_u8(vsel_opq, vopqDstG, vdst.val[NEON_G]); + vdst.val[NEON_B] = vbsl_u8(vsel_opq, vopqDstB, vdst.val[NEON_B]); + + vst4_u8((uint8_t*)dst, vdst); + + dst += 8; + src += 8; + width -= 8; + } + + // Leftovers + for (int i = 0; i < width; i++) { + dst[i] = blend_lcd16_opaque(colR, colG, colB, dst[i], src[i], opaqueDst); + } + } + + void blit_row_lcd16(SkPMColor dst[], const uint16_t src[], + SkColor color, int width, SkPMColor) { + int colA = SkColorGetA(color); + int colR = SkColorGetR(color); + int colG = SkColorGetG(color); + int colB = SkColorGetB(color); + + colA = SkAlpha255To256(colA); + + uint16x8_t vcolA = vdupq_n_u16(colA); + uint8x8_t vcolR = vdup_n_u8(colR); + uint8x8_t vcolG = vdup_n_u8(colG); + uint8x8_t vcolB = vdup_n_u8(colB); + + while (width >= 8) { + uint8x8x4_t vdst; + uint16x8_t vmask; + uint16x8_t vmaskR, vmaskG, vmaskB; + + vdst = vld4_u8((uint8_t*)dst); + vmask = vld1q_u16(src); + + // Get all the color masks on 5 bits + vmaskR = vshrq_n_u16(vmask, SK_R16_SHIFT); + vmaskG = vshrq_n_u16(vshlq_n_u16(vmask, SK_R16_BITS), + SK_B16_BITS + SK_R16_BITS + 1); + vmaskB = vmask & vdupq_n_u16(SK_B16_MASK); + + // Upscale to 0..32 + vmaskR = vmaskR + vshrq_n_u16(vmaskR, 4); + vmaskG = vmaskG + vshrq_n_u16(vmaskG, 4); + vmaskB = vmaskB + vshrq_n_u16(vmaskB, 4); + + vmaskR = vshrq_n_u16(vmaskR * vcolA, 8); + vmaskG = vshrq_n_u16(vmaskG * vcolA, 8); + vmaskB = vshrq_n_u16(vmaskB * vcolA, 8); + + vdst.val[NEON_A] = vdup_n_u8(0xFF); + vdst.val[NEON_R] = blend_32_neon(vcolR, vdst.val[NEON_R], vmaskR); + vdst.val[NEON_G] = blend_32_neon(vcolG, vdst.val[NEON_G], vmaskG); + vdst.val[NEON_B] = blend_32_neon(vcolB, vdst.val[NEON_B], vmaskB); + + vst4_u8((uint8_t*)dst, vdst); + + dst += 8; + src += 8; + width -= 8; + } + + for (int i = 0; i < width; i++) { + dst[i] = blend_lcd16(colA, colR, colG, colB, dst[i], src[i]); + } + } + +#else + + static inline void blit_row_lcd16(SkPMColor dst[], const uint16_t mask[], + SkColor src, int width, SkPMColor) { + int srcA = SkColorGetA(src); + int srcR = SkColorGetR(src); + int srcG = SkColorGetG(src); + int srcB = SkColorGetB(src); + + srcA = SkAlpha255To256(srcA); + + for (int i = 0; i < width; i++) { + dst[i] = blend_lcd16(srcA, srcR, srcG, srcB, dst[i], mask[i]); + } + } + + static inline void blit_row_lcd16_opaque(SkPMColor dst[], const uint16_t mask[], + SkColor src, int width, + SkPMColor opaqueDst) { + int srcR = SkColorGetR(src); + int srcG = SkColorGetG(src); + int srcB = SkColorGetB(src); + + for (int i = 0; i < width; i++) { + dst[i] = blend_lcd16_opaque(srcR, srcG, srcB, dst[i], mask[i], opaqueDst); + } + } + +#endif + +static bool blit_color(const SkPixmap& device, + const SkMask& mask, + const SkIRect& clip, + SkColor color) { + int x = clip.fLeft, + y = clip.fTop; + + if (device.colorType() == kN32_SkColorType && mask.fFormat == SkMask::kA8_Format) { + SkOpts::blit_mask_d32_a8(device.writable_addr32(x,y), device.rowBytes(), + (const SkAlpha*)mask.getAddr(x,y), mask.fRowBytes, + color, clip.width(), clip.height()); + return true; + } + + if (device.colorType() == kN32_SkColorType && mask.fFormat == SkMask::kLCD16_Format) { + auto dstRow = device.writable_addr32(x,y); + auto maskRow = (const uint16_t*)mask.getAddr(x,y); + + auto blit_row = blit_row_lcd16; + SkPMColor opaqueDst = 0; // ignored unless opaque + + if (0xff == SkColorGetA(color)) { + blit_row = blit_row_lcd16_opaque; + opaqueDst = SkPreMultiplyColor(color); + } + + for (int height = clip.height(); height --> 0; ) { + blit_row(dstRow, maskRow, color, clip.width(), opaqueDst); + + dstRow = (SkPMColor*) (( char*) dstRow + device.rowBytes()); + maskRow = (const uint16_t*)((const char*)maskRow + mask.fRowBytes); + } + return true; + } + + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +static void SkARGB32_Blit32(const SkPixmap& device, const SkMask& mask, + const SkIRect& clip, SkPMColor srcColor) { + U8CPU alpha = SkGetPackedA32(srcColor); + unsigned flags = SkBlitRow::kSrcPixelAlpha_Flag32; + if (alpha != 255) { + flags |= SkBlitRow::kGlobalAlpha_Flag32; + } + SkBlitRow::Proc32 proc = SkBlitRow::Factory32(flags); + + int x = clip.fLeft; + int y = clip.fTop; + int width = clip.width(); + int height = clip.height(); + + SkPMColor* dstRow = device.writable_addr32(x, y); + const SkPMColor* srcRow = reinterpret_cast(mask.getAddr8(x, y)); + + do { + proc(dstRow, srcRow, width, alpha); + dstRow = (SkPMColor*)((char*)dstRow + device.rowBytes()); + srcRow = (const SkPMColor*)((const char*)srcRow + mask.fRowBytes); + } while (--height != 0); +} + +////////////////////////////////////////////////////////////////////////////////////// + +SkARGB32_Blitter::SkARGB32_Blitter(const SkPixmap& device, const SkPaint& paint) + : INHERITED(device) { + SkColor color = paint.getColor(); + fColor = color; + + fSrcA = SkColorGetA(color); + unsigned scale = SkAlpha255To256(fSrcA); + fSrcR = SkAlphaMul(SkColorGetR(color), scale); + fSrcG = SkAlphaMul(SkColorGetG(color), scale); + fSrcB = SkAlphaMul(SkColorGetB(color), scale); + + fPMColor = SkPackARGB32(fSrcA, fSrcR, fSrcG, fSrcB); +} + +const SkPixmap* SkARGB32_Blitter::justAnOpaqueColor(uint32_t* value) { + if (255 == fSrcA) { + *value = fPMColor; + return &fDevice; + } + return nullptr; +} + +#if defined _WIN32 // disable warning : local variable used without having been initialized +#pragma warning ( push ) +#pragma warning ( disable : 4701 ) +#endif + +void SkARGB32_Blitter::blitH(int x, int y, int width) { + SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width()); + + uint32_t* device = fDevice.writable_addr32(x, y); + SkBlitRow::Color32(device, device, width, fPMColor); +} + +void SkARGB32_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) { + if (fSrcA == 0) { + return; + } + + uint32_t color = fPMColor; + uint32_t* device = fDevice.writable_addr32(x, y); + unsigned opaqueMask = fSrcA; // if fSrcA is 0xFF, then we will catch the fast opaque case + + for (;;) { + int count = runs[0]; + SkASSERT(count >= 0); + if (count <= 0) { + return; + } + unsigned aa = antialias[0]; + if (aa) { + if ((opaqueMask & aa) == 255) { + sk_memset32(device, color, count); + } else { + uint32_t sc = SkAlphaMulQ(color, SkAlpha255To256(aa)); + SkBlitRow::Color32(device, device, count, sc); + } + } + runs += count; + antialias += count; + device += count; + } +} + +void SkARGB32_Blitter::blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkDEBUGCODE((void)fDevice.writable_addr32(x + 1, y);) + + device[0] = SkBlendARGB32(fPMColor, device[0], a0); + device[1] = SkBlendARGB32(fPMColor, device[1], a1); +} + +void SkARGB32_Blitter::blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkDEBUGCODE((void)fDevice.writable_addr32(x, y + 1);) + + device[0] = SkBlendARGB32(fPMColor, device[0], a0); + device = (uint32_t*)((char*)device + fDevice.rowBytes()); + device[0] = SkBlendARGB32(fPMColor, device[0], a1); +} + +////////////////////////////////////////////////////////////////////////////////////// + +#define solid_8_pixels(mask, dst, color) \ + do { \ + if (mask & 0x80) dst[0] = color; \ + if (mask & 0x40) dst[1] = color; \ + if (mask & 0x20) dst[2] = color; \ + if (mask & 0x10) dst[3] = color; \ + if (mask & 0x08) dst[4] = color; \ + if (mask & 0x04) dst[5] = color; \ + if (mask & 0x02) dst[6] = color; \ + if (mask & 0x01) dst[7] = color; \ + } while (0) + +#define SK_BLITBWMASK_NAME SkARGB32_BlitBW +#define SK_BLITBWMASK_ARGS , SkPMColor color +#define SK_BLITBWMASK_BLIT8(mask, dst) solid_8_pixels(mask, dst, color) +#define SK_BLITBWMASK_GETADDR writable_addr32 +#define SK_BLITBWMASK_DEVTYPE uint32_t +#include "src/core/SkBlitBWMaskTemplate.h" + +#define blend_8_pixels(mask, dst, sc, dst_scale) \ + do { \ + if (mask & 0x80) { dst[0] = sc + SkAlphaMulQ(dst[0], dst_scale); } \ + if (mask & 0x40) { dst[1] = sc + SkAlphaMulQ(dst[1], dst_scale); } \ + if (mask & 0x20) { dst[2] = sc + SkAlphaMulQ(dst[2], dst_scale); } \ + if (mask & 0x10) { dst[3] = sc + SkAlphaMulQ(dst[3], dst_scale); } \ + if (mask & 0x08) { dst[4] = sc + SkAlphaMulQ(dst[4], dst_scale); } \ + if (mask & 0x04) { dst[5] = sc + SkAlphaMulQ(dst[5], dst_scale); } \ + if (mask & 0x02) { dst[6] = sc + SkAlphaMulQ(dst[6], dst_scale); } \ + if (mask & 0x01) { dst[7] = sc + SkAlphaMulQ(dst[7], dst_scale); } \ + } while (0) + +#define SK_BLITBWMASK_NAME SkARGB32_BlendBW +#define SK_BLITBWMASK_ARGS , uint32_t sc, unsigned dst_scale +#define SK_BLITBWMASK_BLIT8(mask, dst) blend_8_pixels(mask, dst, sc, dst_scale) +#define SK_BLITBWMASK_GETADDR writable_addr32 +#define SK_BLITBWMASK_DEVTYPE uint32_t +#include "src/core/SkBlitBWMaskTemplate.h" + +void SkARGB32_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) { + SkASSERT(mask.fBounds.contains(clip)); + SkASSERT(fSrcA != 0xFF); + + if (fSrcA == 0) { + return; + } + + if (blit_color(fDevice, mask, clip, fColor)) { + return; + } + + switch (mask.fFormat) { + case SkMask::kBW_Format: + SkARGB32_BlendBW(fDevice, mask, clip, fPMColor, SkAlpha255To256(255 - fSrcA)); + break; + case SkMask::kARGB32_Format: + SkARGB32_Blit32(fDevice, mask, clip, fPMColor); + break; + default: + SK_ABORT("Mask format not handled."); + } +} + +void SkARGB32_Opaque_Blitter::blitMask(const SkMask& mask, + const SkIRect& clip) { + SkASSERT(mask.fBounds.contains(clip)); + + if (blit_color(fDevice, mask, clip, fColor)) { + return; + } + + switch (mask.fFormat) { + case SkMask::kBW_Format: + SkARGB32_BlitBW(fDevice, mask, clip, fPMColor); + break; + case SkMask::kARGB32_Format: + SkARGB32_Blit32(fDevice, mask, clip, fPMColor); + break; + default: + SK_ABORT("Mask format not handled."); + } +} + +void SkARGB32_Opaque_Blitter::blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkDEBUGCODE((void)fDevice.writable_addr32(x + 1, y);) + + device[0] = SkFastFourByteInterp(fPMColor, device[0], a0); + device[1] = SkFastFourByteInterp(fPMColor, device[1], a1); +} + +void SkARGB32_Opaque_Blitter::blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkDEBUGCODE((void)fDevice.writable_addr32(x, y + 1);) + + device[0] = SkFastFourByteInterp(fPMColor, device[0], a0); + device = (uint32_t*)((char*)device + fDevice.rowBytes()); + device[0] = SkFastFourByteInterp(fPMColor, device[0], a1); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkARGB32_Blitter::blitV(int x, int y, int height, SkAlpha alpha) { + if (alpha == 0 || fSrcA == 0) { + return; + } + + uint32_t* device = fDevice.writable_addr32(x, y); + uint32_t color = fPMColor; + + if (alpha != 255) { + color = SkAlphaMulQ(color, SkAlpha255To256(alpha)); + } + + unsigned dst_scale = SkAlpha255To256(255 - SkGetPackedA32(color)); + size_t rowBytes = fDevice.rowBytes(); + while (--height >= 0) { + device[0] = color + SkAlphaMulQ(device[0], dst_scale); + device = (uint32_t*)((char*)device + rowBytes); + } +} + +void SkARGB32_Blitter::blitRect(int x, int y, int width, int height) { + SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width() && y + height <= fDevice.height()); + + if (fSrcA == 0) { + return; + } + + uint32_t* device = fDevice.writable_addr32(x, y); + uint32_t color = fPMColor; + size_t rowBytes = fDevice.rowBytes(); + + if (SkGetPackedA32(fPMColor) == 0xFF) { + SkOpts::rect_memset32(device, color, width, rowBytes, height); + } else { + while (height --> 0) { + SkBlitRow::Color32(device, device, width, color); + device = (uint32_t*)((char*)device + rowBytes); + } + } +} + +#if defined _WIN32 +#pragma warning ( pop ) +#endif + +/////////////////////////////////////////////////////////////////////// + +void SkARGB32_Black_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkPMColor black = (SkPMColor)(SK_A32_MASK << SK_A32_SHIFT); + + for (;;) { + int count = runs[0]; + SkASSERT(count >= 0); + if (count <= 0) { + return; + } + unsigned aa = antialias[0]; + if (aa) { + if (aa == 255) { + sk_memset32(device, black, count); + } else { + SkPMColor src = aa << SK_A32_SHIFT; + unsigned dst_scale = 256 - aa; + int n = count; + do { + --n; + device[n] = src + SkAlphaMulQ(device[n], dst_scale); + } while (n > 0); + } + } + runs += count; + antialias += count; + device += count; + } +} + +void SkARGB32_Black_Blitter::blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkDEBUGCODE((void)fDevice.writable_addr32(x + 1, y);) + + device[0] = (a0 << SK_A32_SHIFT) + SkAlphaMulQ(device[0], 256 - a0); + device[1] = (a1 << SK_A32_SHIFT) + SkAlphaMulQ(device[1], 256 - a1); +} + +void SkARGB32_Black_Blitter::blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) { + uint32_t* device = fDevice.writable_addr32(x, y); + SkDEBUGCODE((void)fDevice.writable_addr32(x, y + 1);) + + device[0] = (a0 << SK_A32_SHIFT) + SkAlphaMulQ(device[0], 256 - a0); + device = (uint32_t*)((char*)device + fDevice.rowBytes()); + device[0] = (a1 << SK_A32_SHIFT) + SkAlphaMulQ(device[0], 256 - a1); +} + +/////////////////////////////////////////////////////////////////////////////// + +// Special version of SkBlitRow::Factory32 that knows we're in kSrc_Mode, +// instead of kSrcOver_Mode +static void blend_srcmode(SkPMColor* SK_RESTRICT device, + const SkPMColor* SK_RESTRICT span, + int count, U8CPU aa) { + int aa256 = SkAlpha255To256(aa); + for (int i = 0; i < count; ++i) { + device[i] = SkFourByteInterp256(span[i], device[i], aa256); + } +} + +SkARGB32_Shader_Blitter::SkARGB32_Shader_Blitter(const SkPixmap& device, + const SkPaint& paint, SkShaderBase::Context* shaderContext) + : INHERITED(device, paint, shaderContext) +{ + fBuffer = (SkPMColor*)sk_malloc_throw(device.width() * (sizeof(SkPMColor))); + + fXfermode = SkXfermode::Peek(paint.getBlendMode()); + + int flags = 0; + if (!(shaderContext->getFlags() & SkShaderBase::kOpaqueAlpha_Flag)) { + flags |= SkBlitRow::kSrcPixelAlpha_Flag32; + } + // we call this on the output from the shader + fProc32 = SkBlitRow::Factory32(flags); + // we call this on the output from the shader + alpha from the aa buffer + fProc32Blend = SkBlitRow::Factory32(flags | SkBlitRow::kGlobalAlpha_Flag32); + + fShadeDirectlyIntoDevice = false; + if (fXfermode == nullptr) { + if (shaderContext->getFlags() & SkShaderBase::kOpaqueAlpha_Flag) { + fShadeDirectlyIntoDevice = true; + } + } else { + if (SkBlendMode::kSrc == paint.getBlendMode()) { + fShadeDirectlyIntoDevice = true; + fProc32Blend = blend_srcmode; + } + } + + fConstInY = SkToBool(shaderContext->getFlags() & SkShaderBase::kConstInY32_Flag); +} + +SkARGB32_Shader_Blitter::~SkARGB32_Shader_Blitter() { + sk_free(fBuffer); +} + +void SkARGB32_Shader_Blitter::blitH(int x, int y, int width) { + SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width()); + + uint32_t* device = fDevice.writable_addr32(x, y); + + if (fShadeDirectlyIntoDevice) { + fShaderContext->shadeSpan(x, y, device, width); + } else { + SkPMColor* span = fBuffer; + fShaderContext->shadeSpan(x, y, span, width); + if (fXfermode) { + fXfermode->xfer32(device, span, width, nullptr); + } else { + fProc32(device, span, width, 255); + } + } +} + +void SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height) { + SkASSERT(x >= 0 && y >= 0 && + x + width <= fDevice.width() && y + height <= fDevice.height()); + + uint32_t* device = fDevice.writable_addr32(x, y); + size_t deviceRB = fDevice.rowBytes(); + auto* shaderContext = fShaderContext; + SkPMColor* span = fBuffer; + + if (fConstInY) { + if (fShadeDirectlyIntoDevice) { + // shade the first row directly into the device + shaderContext->shadeSpan(x, y, device, width); + span = device; + while (--height > 0) { + device = (uint32_t*)((char*)device + deviceRB); + memcpy(device, span, width << 2); + } + } else { + shaderContext->shadeSpan(x, y, span, width); + SkXfermode* xfer = fXfermode; + if (xfer) { + do { + xfer->xfer32(device, span, width, nullptr); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + SkBlitRow::Proc32 proc = fProc32; + do { + proc(device, span, width, 255); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } + } + return; + } + + if (fShadeDirectlyIntoDevice) { + do { + shaderContext->shadeSpan(x, y, device, width); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + SkXfermode* xfer = fXfermode; + if (xfer) { + do { + shaderContext->shadeSpan(x, y, span, width); + xfer->xfer32(device, span, width, nullptr); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + SkBlitRow::Proc32 proc = fProc32; + do { + shaderContext->shadeSpan(x, y, span, width); + proc(device, span, width, 255); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } + } +} + +void SkARGB32_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) { + SkPMColor* span = fBuffer; + uint32_t* device = fDevice.writable_addr32(x, y); + auto* shaderContext = fShaderContext; + + if (fXfermode && !fShadeDirectlyIntoDevice) { + for (;;) { + SkXfermode* xfer = fXfermode; + + int count = *runs; + if (count <= 0) + break; + int aa = *antialias; + if (aa) { + shaderContext->shadeSpan(x, y, span, count); + if (aa == 255) { + xfer->xfer32(device, span, count, nullptr); + } else { + // count is almost always 1 + for (int i = count - 1; i >= 0; --i) { + xfer->xfer32(&device[i], &span[i], 1, antialias); + } + } + } + device += count; + runs += count; + antialias += count; + x += count; + } + } else if (fShadeDirectlyIntoDevice || + (shaderContext->getFlags() & SkShaderBase::kOpaqueAlpha_Flag)) { + for (;;) { + int count = *runs; + if (count <= 0) { + break; + } + int aa = *antialias; + if (aa) { + if (aa == 255) { + // cool, have the shader draw right into the device + shaderContext->shadeSpan(x, y, device, count); + } else { + shaderContext->shadeSpan(x, y, span, count); + fProc32Blend(device, span, count, aa); + } + } + device += count; + runs += count; + antialias += count; + x += count; + } + } else { + for (;;) { + int count = *runs; + if (count <= 0) { + break; + } + int aa = *antialias; + if (aa) { + shaderContext->shadeSpan(x, y, span, count); + if (aa == 255) { + fProc32(device, span, count, 255); + } else { + fProc32Blend(device, span, count, aa); + } + } + device += count; + runs += count; + antialias += count; + x += count; + } + } +} + +using U32 = skvx::Vec< 4, uint32_t>; +using U8x4 = skvx::Vec<16, uint8_t>; +using U8 = skvx::Vec< 4, uint8_t>; + +static void drive(SkPMColor* dst, const SkPMColor* src, const uint8_t* cov, int n, + U8x4 (*kernel)(U8x4,U8x4,U8x4)) { + + auto apply = [kernel](U32 dst, U32 src, U8 cov) -> U32 { + U8x4 cov_splat = skvx::shuffle<0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3>(cov); + return skvx::bit_pun(kernel(skvx::bit_pun(dst), + skvx::bit_pun(src), + cov_splat)); + }; + while (n >= 4) { + apply(U32::Load(dst), U32::Load(src), U8::Load(cov)).store(dst); + dst += 4; + src += 4; + cov += 4; + n -= 4; + } + while (n --> 0) { + *dst = apply(U32{*dst}, U32{*src}, U8{*cov})[0]; + dst++; + src++; + cov++; + } +} + +static void blend_row_A8(SkPMColor* dst, const void* mask, const SkPMColor* src, int n) { + auto cov = (const uint8_t*)mask; + drive(dst, src, cov, n, [](U8x4 d, U8x4 s, U8x4 c) { + U8x4 s_aa = skvx::approx_scale(s, c), + alpha = skvx::shuffle<3,3,3,3, 7,7,7,7, 11,11,11,11, 15,15,15,15>(s_aa); + return s_aa + skvx::approx_scale(d, 255 - alpha); + }); +} + +static void blend_row_A8_opaque(SkPMColor* dst, const void* mask, const SkPMColor* src, int n) { + auto cov = (const uint8_t*)mask; + drive(dst, src, cov, n, [](U8x4 d, U8x4 s, U8x4 c) { + return skvx::div255( skvx::cast(s) * skvx::cast( c ) + + skvx::cast(d) * skvx::cast(255-c)); + }); +} + +static void blend_row_lcd16(SkPMColor* dst, const void* vmask, const SkPMColor* src, int n) { + auto src_alpha_blend = [](int s, int d, int sa, int m) { + return d + SkAlphaMul(s - SkAlphaMul(sa, d), m); + }; + + auto upscale_31_to_255 = [](int v) { + return (v << 3) | (v >> 2); + }; + + auto mask = (const uint16_t*)vmask; + for (int i = 0; i < n; ++i) { + uint16_t m = mask[i]; + if (0 == m) { + continue; + } + + SkPMColor s = src[i]; + SkPMColor d = dst[i]; + + int srcA = SkGetPackedA32(s); + int srcR = SkGetPackedR32(s); + int srcG = SkGetPackedG32(s); + int srcB = SkGetPackedB32(s); + + srcA += srcA >> 7; + + // We're ignoring the least significant bit of the green coverage channel here. + int maskR = SkGetPackedR16(m) >> (SK_R16_BITS - 5); + int maskG = SkGetPackedG16(m) >> (SK_G16_BITS - 5); + int maskB = SkGetPackedB16(m) >> (SK_B16_BITS - 5); + + // Scale up to 8-bit coverage to work with SkAlphaMul() in src_alpha_blend(). + maskR = upscale_31_to_255(maskR); + maskG = upscale_31_to_255(maskG); + maskB = upscale_31_to_255(maskB); + + // This LCD blit routine only works if the destination is opaque. + dst[i] = SkPackARGB32(0xFF, + src_alpha_blend(srcR, SkGetPackedR32(d), srcA, maskR), + src_alpha_blend(srcG, SkGetPackedG32(d), srcA, maskG), + src_alpha_blend(srcB, SkGetPackedB32(d), srcA, maskB)); + } +} + +static void blend_row_LCD16_opaque(SkPMColor* dst, const void* vmask, const SkPMColor* src, int n) { + auto mask = (const uint16_t*)vmask; + + for (int i = 0; i < n; ++i) { + uint16_t m = mask[i]; + if (0 == m) { + continue; + } + + SkPMColor s = src[i]; + SkPMColor d = dst[i]; + + int srcR = SkGetPackedR32(s); + int srcG = SkGetPackedG32(s); + int srcB = SkGetPackedB32(s); + + // We're ignoring the least significant bit of the green coverage channel here. + int maskR = SkGetPackedR16(m) >> (SK_R16_BITS - 5); + int maskG = SkGetPackedG16(m) >> (SK_G16_BITS - 5); + int maskB = SkGetPackedB16(m) >> (SK_B16_BITS - 5); + + // Now upscale them to 0..32, so we can use blend_32. + maskR = upscale_31_to_32(maskR); + maskG = upscale_31_to_32(maskG); + maskB = upscale_31_to_32(maskB); + + // This LCD blit routine only works if the destination is opaque. + dst[i] = SkPackARGB32(0xFF, + blend_32(srcR, SkGetPackedR32(d), maskR), + blend_32(srcG, SkGetPackedG32(d), maskG), + blend_32(srcB, SkGetPackedB32(d), maskB)); + } +} + +void SkARGB32_Shader_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) { + // we only handle kA8 with an xfermode + if (fXfermode && (SkMask::kA8_Format != mask.fFormat)) { + this->INHERITED::blitMask(mask, clip); + return; + } + + SkASSERT(mask.fBounds.contains(clip)); + + void (*blend_row)(SkPMColor*, const void* mask, const SkPMColor*, int) = nullptr; + + if (!fXfermode) { + bool opaque = (fShaderContext->getFlags() & SkShaderBase::kOpaqueAlpha_Flag); + + if (mask.fFormat == SkMask::kA8_Format && opaque) { + blend_row = blend_row_A8_opaque; + } else if (mask.fFormat == SkMask::kA8_Format) { + blend_row = blend_row_A8; + } else if (mask.fFormat == SkMask::kLCD16_Format && opaque) { + blend_row = blend_row_LCD16_opaque; + } else if (mask.fFormat == SkMask::kLCD16_Format) { + blend_row = blend_row_lcd16; + } else { + this->INHERITED::blitMask(mask, clip); + return; + } + } + + const int x = clip.fLeft; + const int width = clip.width(); + int y = clip.fTop; + int height = clip.height(); + + char* dstRow = (char*)fDevice.writable_addr32(x, y); + const size_t dstRB = fDevice.rowBytes(); + const uint8_t* maskRow = (const uint8_t*)mask.getAddr(x, y); + const size_t maskRB = mask.fRowBytes; + + SkPMColor* span = fBuffer; + + if (fXfermode) { + SkASSERT(SkMask::kA8_Format == mask.fFormat); + SkXfermode* xfer = fXfermode; + do { + fShaderContext->shadeSpan(x, y, span, width); + xfer->xfer32(reinterpret_cast(dstRow), span, width, maskRow); + dstRow += dstRB; + maskRow += maskRB; + y += 1; + } while (--height > 0); + } else { + SkASSERT(blend_row); + do { + fShaderContext->shadeSpan(x, y, span, width); + blend_row(reinterpret_cast(dstRow), maskRow, span, width); + dstRow += dstRB; + maskRow += maskRB; + y += 1; + } while (--height > 0); + } +} + +void SkARGB32_Shader_Blitter::blitV(int x, int y, int height, SkAlpha alpha) { + SkASSERT(x >= 0 && y >= 0 && y + height <= fDevice.height()); + + uint32_t* device = fDevice.writable_addr32(x, y); + size_t deviceRB = fDevice.rowBytes(); + + if (fConstInY) { + SkPMColor c; + fShaderContext->shadeSpan(x, y, &c, 1); + + if (fShadeDirectlyIntoDevice) { + if (255 == alpha) { + do { + *device = c; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + do { + *device = SkFourByteInterp(c, *device, alpha); + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } + } else { + SkXfermode* xfer = fXfermode; + if (xfer) { + do { + xfer->xfer32(device, &c, 1, &alpha); + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + SkBlitRow::Proc32 proc = (255 == alpha) ? fProc32 : fProc32Blend; + do { + proc(device, &c, 1, alpha); + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } + } + return; + } + + if (fShadeDirectlyIntoDevice) { + if (255 == alpha) { + do { + fShaderContext->shadeSpan(x, y, device, 1); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + do { + SkPMColor c; + fShaderContext->shadeSpan(x, y, &c, 1); + *device = SkFourByteInterp(c, *device, alpha); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } + } else { + SkPMColor* span = fBuffer; + SkXfermode* xfer = fXfermode; + if (xfer) { + do { + fShaderContext->shadeSpan(x, y, span, 1); + xfer->xfer32(device, span, 1, &alpha); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } else { + SkBlitRow::Proc32 proc = (255 == alpha) ? fProc32 : fProc32Blend; + do { + fShaderContext->shadeSpan(x, y, span, 1); + proc(device, span, 1, alpha); + y += 1; + device = (uint32_t*)((char*)device + deviceRB); + } while (--height > 0); + } + } +} diff --git a/gfx/skia/skia/src/core/SkBlitter_RGB565.cpp b/gfx/skia/skia/src/core/SkBlitter_RGB565.cpp new file mode 100644 index 0000000000..3df8217f69 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitter_RGB565.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkShader.h" +#include "include/private/SkColorData.h" +#include "include/private/SkColorData.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkXfermodePriv.h" +#include "src/utils/SkUTF.h" + +#include "include/private/SkNx.h" + +static void D16_S32X_src(uint16_t dst[], const SkPMColor src[], int count, uint8_t coverage) { + SkASSERT(coverage == 0xFF); + for (int i = 0; i < count; ++i) { + dst[i] = SkPixel32ToPixel16(src[i]); + } +} + +static void D16_S32X_src_coverage(uint16_t dst[], const SkPMColor src[], int count, + uint8_t coverage) { + switch (coverage) { + case 0: break; + case 0xFF: + for (int i = 0; i < count; ++i) { + dst[i] = SkPixel32ToPixel16(src[i]); + } + break; + default: + unsigned scale = coverage + (coverage >> 7); + for (int i = 0; i < count; ++i) { + dst[i] = SkSrcOver32To16(SkAlphaMulQ(src[i], scale), dst[i]); + } + break; + } +} + +static void D16_S32A_srcover(uint16_t dst[], const SkPMColor src[], int count, uint8_t coverage) { + SkASSERT(coverage == 0xFF); + for (int i = 0; i < count; ++i) { + dst[i] = SkSrcOver32To16(src[i], dst[i]); + } +} + +static void D16_S32A_srcover_coverage(uint16_t dst[], const SkPMColor src[], int count, + uint8_t coverage) { + switch (coverage) { + case 0: break; + case 0xFF: + for (int i = 0; i < count; ++i) { + dst[i] = SkSrcOver32To16(src[i], dst[i]); + } + break; + default: + unsigned scale = coverage + (coverage >> 7); + for (int i = 0; i < count; ++i) { + dst[i] = SkSrcOver32To16(SkAlphaMulQ(src[i], scale), dst[i]); + } + break; + } +} + +bool SkRGB565_Shader_Blitter::Supports(const SkPixmap& device, const SkPaint& paint) { + if (device.colorType() != kRGB_565_SkColorType) { + return false; + } + if (device.colorSpace()) { + return false; + } + if (paint.getBlendMode() != SkBlendMode::kSrcOver && + paint.getBlendMode() != SkBlendMode::kSrc) { + return false; + } + if (paint.isDither()) { + return false; + } + return true; +} + +SkRGB565_Shader_Blitter::SkRGB565_Shader_Blitter(const SkPixmap& device, + const SkPaint& paint, SkShaderBase::Context* shaderContext) + : INHERITED(device, paint, shaderContext) +{ + SkASSERT(shaderContext); + SkASSERT(Supports(device, paint)); + + fBuffer = (SkPMColor*)sk_malloc_throw(device.width() * (sizeof(SkPMColor))); + + bool isOpaque = SkToBool(shaderContext->getFlags() & SkShaderBase::kOpaqueAlpha_Flag); + + if (paint.getBlendMode() == SkBlendMode::kSrc || isOpaque) { + fBlend = D16_S32X_src; + fBlendCoverage = D16_S32X_src_coverage; + } else { // srcover + fBlend = isOpaque ? D16_S32X_src : D16_S32A_srcover; + fBlendCoverage = isOpaque ? D16_S32X_src_coverage : D16_S32A_srcover_coverage; + } +} + +SkRGB565_Shader_Blitter::~SkRGB565_Shader_Blitter() { + sk_free(fBuffer); +} + +void SkRGB565_Shader_Blitter::blitH(int x, int y, int width) { + SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width()); + + uint16_t* device = fDevice.writable_addr16(x, y); + + SkPMColor* span = fBuffer; + fShaderContext->shadeSpan(x, y, span, width); + fBlend(device, span, width, 0xFF); +} + +void SkRGB565_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha coverage[], + const int16_t runs[]) { + SkPMColor* span = fBuffer; + uint16_t* device = fDevice.writable_addr16(x, y); + auto* shaderContext = fShaderContext; + + for (;;) { + int count = *runs; + if (count <= 0) { + break; + } + int aa = *coverage; + if (aa) { + shaderContext->shadeSpan(x, y, span, count); + fBlendCoverage(device, span, count, aa); + } + device += count; + runs += count; + coverage += count; + x += count; + } +} diff --git a/gfx/skia/skia/src/core/SkBlitter_Sprite.cpp b/gfx/skia/skia/src/core/SkBlitter_Sprite.cpp new file mode 100644 index 0000000000..89b107b939 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlitter_Sprite.cpp @@ -0,0 +1,215 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorSpace.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkSpriteBlitter.h" + +SkSpriteBlitter::SkSpriteBlitter(const SkPixmap& source) + : fSource(source) {} + +void SkSpriteBlitter::setup(const SkPixmap& dst, int left, int top, const SkPaint& paint) { + fDst = dst; + fLeft = left; + fTop = top; + fPaint = &paint; +} + +void SkSpriteBlitter::blitH(int x, int y, int width) { + SkDEBUGFAIL("how did we get here?"); + + // Fallback to blitRect. + this->blitRect(x, y, width, 1); +} + +void SkSpriteBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) { + SkDEBUGFAIL("how did we get here?"); + + // No fallback strategy. +} + +void SkSpriteBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + SkDEBUGFAIL("how did we get here?"); + + // Fall back to superclass if the code gets here in release mode. + INHERITED::blitV(x, y, height, alpha); +} + +void SkSpriteBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { + SkDEBUGFAIL("how did we get here?"); + + // Fall back to superclass if the code gets here in release mode. + INHERITED::blitMask(mask, clip); +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkSpriteBlitter_Memcpy final : public SkSpriteBlitter { +public: + static bool Supports(const SkPixmap& dst, const SkPixmap& src, const SkPaint& paint) { + // the caller has already inspected the colorspace on src and dst + SkASSERT(!SkColorSpaceXformSteps::Required(src.colorSpace(), dst.colorSpace())); + + if (dst.colorType() != src.colorType()) { + return false; + } + if (paint.getMaskFilter() || paint.getColorFilter() || paint.getImageFilter()) { + return false; + } + if (0xFF != paint.getAlpha()) { + return false; + } + SkBlendMode mode = paint.getBlendMode(); + return SkBlendMode::kSrc == mode || (SkBlendMode::kSrcOver == mode && src.isOpaque()); + } + + SkSpriteBlitter_Memcpy(const SkPixmap& src) + : INHERITED(src) {} + + void blitRect(int x, int y, int width, int height) override { + SkASSERT(fDst.colorType() == fSource.colorType()); + SkASSERT(width > 0 && height > 0); + + char* dst = (char*)fDst.writable_addr(x, y); + const char* src = (const char*)fSource.addr(x - fLeft, y - fTop); + const size_t dstRB = fDst.rowBytes(); + const size_t srcRB = fSource.rowBytes(); + const size_t bytesToCopy = width << fSource.shiftPerPixel(); + + while (height --> 0) { + memcpy(dst, src, bytesToCopy); + dst += dstRB; + src += srcRB; + } + } + +private: + typedef SkSpriteBlitter INHERITED; +}; + +class SkRasterPipelineSpriteBlitter : public SkSpriteBlitter { +public: + SkRasterPipelineSpriteBlitter(const SkPixmap& src, SkArenaAlloc* alloc) + : INHERITED(src) + , fAlloc(alloc) + , fBlitter(nullptr) + , fSrcPtr{nullptr, 0} + {} + + void setup(const SkPixmap& dst, int left, int top, const SkPaint& paint) override { + fDst = dst; + fLeft = left; + fTop = top; + fPaintColor = paint.getColor4f(); + + SkRasterPipeline p(fAlloc); + p.append_load(fSource.colorType(), &fSrcPtr); + + if (fSource.colorType() == kAlpha_8_SkColorType) { + // The color for A8 images comes from the (sRGB) paint color. + p.append_set_rgb(fAlloc, fPaintColor); + p.append(SkRasterPipeline::premul); + } + if (auto dstCS = fDst.colorSpace()) { + auto srcCS = fSource.colorSpace(); + if (!srcCS || fSource.colorType() == kAlpha_8_SkColorType) { + // We treat untagged images as sRGB. + // A8 images get their r,g,b from the paint color, so they're also sRGB. + srcCS = sk_srgb_singleton(); + } + auto srcAT = fSource.isOpaque() ? kOpaque_SkAlphaType + : kPremul_SkAlphaType; + fAlloc->make(srcCS, srcAT, + dstCS, kPremul_SkAlphaType) + ->apply(&p, fSource.colorType()); + } + if (fPaintColor.fA != 1.0f) { + p.append(SkRasterPipeline::scale_1_float, &fPaintColor.fA); + } + + bool is_opaque = fSource.isOpaque() && fPaintColor.fA == 1.0f; + fBlitter = SkCreateRasterPipelineBlitter(fDst, paint, p, is_opaque, fAlloc); + } + + void blitRect(int x, int y, int width, int height) override { + fSrcPtr.stride = fSource.rowBytesAsPixels(); + + // We really want fSrcPtr.pixels = fSource.addr(-fLeft, -fTop) here, but that asserts. + // Instead we ask for addr(-fLeft+x, -fTop+y), then back up (x,y) manually. + // Representing bpp as a size_t keeps all this math in size_t instead of int, + // which could wrap around with large enough fSrcPtr.stride and y. + size_t bpp = fSource.info().bytesPerPixel(); + fSrcPtr.pixels = (char*)fSource.addr(-fLeft+x, -fTop+y) - bpp * x + - bpp * y * fSrcPtr.stride; + + fBlitter->blitRect(x,y,width,height); + } + +private: + SkArenaAlloc* fAlloc; + SkBlitter* fBlitter; + SkRasterPipeline_MemoryCtx fSrcPtr; + SkColor4f fPaintColor; + + typedef SkSpriteBlitter INHERITED; +}; + +// returning null means the caller will call SkBlitter::Choose() and +// have wrapped the source bitmap inside a shader +SkBlitter* SkBlitter::ChooseSprite(const SkPixmap& dst, const SkPaint& paint, + const SkPixmap& source, int left, int top, SkArenaAlloc* allocator) { + /* We currently ignore antialiasing and filtertype, meaning we will take our + special blitters regardless of these settings. Ignoring filtertype seems fine + since by definition there is no scale in the matrix. Ignoring antialiasing is + a bit of a hack, since we "could" pass in the fractional left/top for the bitmap, + and respect that by blending the edges of the bitmap against the device. To support + this we could either add more special blitters here, or detect antialiasing in the + paint and return null if it is set, forcing the client to take the slow shader case + (which does respect soft edges). + */ + SkASSERT(allocator != nullptr); + + if (source.alphaType() == kUnpremul_SkAlphaType) { + return nullptr; + } + + SkSpriteBlitter* blitter = nullptr; + + if (!SkColorSpaceXformSteps::Required(source.colorSpace(), dst.colorSpace())) { + if (!blitter && SkSpriteBlitter_Memcpy::Supports(dst, source, paint)) { + blitter = allocator->make(source); + } + if (!blitter) { + switch (dst.colorType()) { + case kN32_SkColorType: + blitter = SkSpriteBlitter::ChooseL32(source, paint, allocator); + break; + case kRGB_565_SkColorType: + blitter = SkSpriteBlitter::ChooseL565(source, paint, allocator); + break; + case kAlpha_8_SkColorType: + blitter = SkSpriteBlitter::ChooseLA8(source, paint, allocator); + break; + default: + break; + } + } + } + if (!blitter && !paint.getMaskFilter()) { + blitter = allocator->make(source, allocator); + } + + if (blitter) { + blitter->setup(dst, left, top, paint); + } + return blitter; +} diff --git a/gfx/skia/skia/src/core/SkBlurMF.cpp b/gfx/skia/skia/src/core/SkBlurMF.cpp new file mode 100644 index 0000000000..d385af8ae0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlurMF.cpp @@ -0,0 +1,938 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMaskFilter.h" +#include "include/core/SkRRect.h" +#include "include/core/SkStrokeRec.h" +#include "include/core/SkVertices.h" +#include "src/core/SkBlurMask.h" +#include "src/core/SkBlurPriv.h" +#include "src/core/SkGpuBlurUtils.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkRRectPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkStringUtils.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h" +#include "src/gpu/effects/generated/GrRRectBlurEffect.h" +#include "src/gpu/effects/generated/GrRectBlurEffect.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#endif + +class SkBlurMaskFilterImpl : public SkMaskFilterBase { +public: + SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle, bool respectCTM); + + // overrides from SkMaskFilter + SkMask::Format getFormat() const override; + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, + SkIPoint* margin) const override; + +#if SK_SUPPORT_GPU + bool canFilterMaskGPU(const GrShape& shape, + const SkIRect& devSpaceShapeBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkIRect* maskRect) const override; + bool directFilterMaskGPU(GrRecordingContext*, + GrRenderTargetContext* renderTargetContext, + GrPaint&&, + const GrClip&, + const SkMatrix& viewMatrix, + const GrShape& shape) const override; + sk_sp filterMaskGPU(GrRecordingContext*, + sk_sp srcProxy, + GrColorType srcColorType, + SkAlphaType srcAlphaType, + const SkMatrix& ctm, + const SkIRect& maskRect) const override; +#endif + + void computeFastBounds(const SkRect&, SkRect*) const override; + bool asABlur(BlurRec*) const override; + + +protected: + FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&, + const SkIRect& clipBounds, + NinePatch*) const override; + + FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&, + const SkIRect& clipBounds, + NinePatch*) const override; + + bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix, + SkIPoint* margin, SkMask::CreateMode createMode) const; + bool filterRRectMask(SkMask* dstM, const SkRRect& r, const SkMatrix& matrix, + SkIPoint* margin, SkMask::CreateMode createMode) const; + + bool ignoreXform() const { return !fRespectCTM; } + +private: + SK_FLATTENABLE_HOOKS(SkBlurMaskFilterImpl) + // To avoid unseemly allocation requests (esp. for finite platforms like + // handset) we limit the radius so something manageable. (as opposed to + // a request like 10,000) + static const SkScalar kMAX_BLUR_SIGMA; + + SkScalar fSigma; + SkBlurStyle fBlurStyle; + bool fRespectCTM; + + SkBlurMaskFilterImpl(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + + SkScalar computeXformedSigma(const SkMatrix& ctm) const { + SkScalar xformedSigma = this->ignoreXform() ? fSigma : ctm.mapRadius(fSigma); + return SkMinScalar(xformedSigma, kMAX_BLUR_SIGMA); + } + + friend class SkBlurMaskFilter; + + typedef SkMaskFilter INHERITED; + friend void sk_register_blur_maskfilter_createproc(); +}; + +const SkScalar SkBlurMaskFilterImpl::kMAX_BLUR_SIGMA = SkIntToScalar(128); + +// linearly interpolate between y1 & y3 to match x2's position between x1 & x3 +static SkScalar interp(SkScalar x1, SkScalar x2, SkScalar x3, SkScalar y1, SkScalar y3) { + SkASSERT(x1 <= x2 && x2 <= x3); + SkASSERT(y1 <= y3); + + SkScalar t = (x2 - x1) / (x3 - x1); + return y1 + t * (y3 - y1); +} + +// Insert 'lower' and 'higher' into 'array1' and insert a new value at each matching insertion +// point in 'array2' that linearly interpolates between the existing values. +// Return a bit mask which contains a copy of 'inputMask' for all the cells between the two +// insertion points. +static uint32_t insert_into_arrays(SkScalar* array1, SkScalar* array2, + SkScalar lower, SkScalar higher, + int* num, uint32_t inputMask, int maskSize) { + SkASSERT(lower < higher); + SkASSERT(lower >= array1[0] && higher <= array1[*num-1]); + + int32_t skipMask = 0x0; + int i; + for (i = 0; i < *num; ++i) { + if (lower >= array1[i] && lower < array1[i+1]) { + if (!SkScalarNearlyEqual(lower, array1[i])) { + memmove(&array1[i+2], &array1[i+1], (*num-i-1)*sizeof(SkScalar)); + array1[i+1] = lower; + memmove(&array2[i+2], &array2[i+1], (*num-i-1)*sizeof(SkScalar)); + array2[i+1] = interp(array1[i], lower, array1[i+2], array2[i], array2[i+2]); + i++; + (*num)++; + } + break; + } + } + for ( ; i < *num; ++i) { + skipMask |= inputMask << (i*maskSize); + if (higher > array1[i] && higher <= array1[i+1]) { + if (!SkScalarNearlyEqual(higher, array1[i+1])) { + memmove(&array1[i+2], &array1[i+1], (*num-i-1)*sizeof(SkScalar)); + array1[i+1] = higher; + memmove(&array2[i+2], &array2[i+1], (*num-i-1)*sizeof(SkScalar)); + array2[i+1] = interp(array1[i], higher, array1[i+2], array2[i], array2[i+2]); + (*num)++; + } + break; + } + } + + return skipMask; +} + +bool SkComputeBlurredRRectParams(const SkRRect& srcRRect, const SkRRect& devRRect, + const SkRect& occluder, + SkScalar sigma, SkScalar xformedSigma, + SkRRect* rrectToDraw, + SkISize* widthHeight, + SkScalar rectXs[kSkBlurRRectMaxDivisions], + SkScalar rectYs[kSkBlurRRectMaxDivisions], + SkScalar texXs[kSkBlurRRectMaxDivisions], + SkScalar texYs[kSkBlurRRectMaxDivisions], + int* numXs, int* numYs, uint32_t* skipMask) { + unsigned int devBlurRadius = 3*SkScalarCeilToInt(xformedSigma-1/6.0f); + SkScalar srcBlurRadius = 3.0f * sigma; + + const SkRect& devOrig = devRRect.getBounds(); + const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner); + const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner); + const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner); + const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner); + + const int devLeft = SkScalarCeilToInt(SkTMax(devRadiiUL.fX, devRadiiLL.fX)); + const int devTop = SkScalarCeilToInt(SkTMax(devRadiiUL.fY, devRadiiUR.fY)); + const int devRight = SkScalarCeilToInt(SkTMax(devRadiiUR.fX, devRadiiLR.fX)); + const int devBot = SkScalarCeilToInt(SkTMax(devRadiiLL.fY, devRadiiLR.fY)); + + // This is a conservative check for nine-patchability + if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight - devRight - devBlurRadius || + devOrig.fTop + devTop + devBlurRadius >= devOrig.fBottom - devBot - devBlurRadius) { + return false; + } + + const SkVector& srcRadiiUL = srcRRect.radii(SkRRect::kUpperLeft_Corner); + const SkVector& srcRadiiUR = srcRRect.radii(SkRRect::kUpperRight_Corner); + const SkVector& srcRadiiLR = srcRRect.radii(SkRRect::kLowerRight_Corner); + const SkVector& srcRadiiLL = srcRRect.radii(SkRRect::kLowerLeft_Corner); + + const SkScalar srcLeft = SkTMax(srcRadiiUL.fX, srcRadiiLL.fX); + const SkScalar srcTop = SkTMax(srcRadiiUL.fY, srcRadiiUR.fY); + const SkScalar srcRight = SkTMax(srcRadiiUR.fX, srcRadiiLR.fX); + const SkScalar srcBot = SkTMax(srcRadiiLL.fY, srcRadiiLR.fY); + + int newRRWidth = 2*devBlurRadius + devLeft + devRight + 1; + int newRRHeight = 2*devBlurRadius + devTop + devBot + 1; + widthHeight->fWidth = newRRWidth + 2 * devBlurRadius; + widthHeight->fHeight = newRRHeight + 2 * devBlurRadius; + + const SkRect srcProxyRect = srcRRect.getBounds().makeOutset(srcBlurRadius, srcBlurRadius); + + rectXs[0] = srcProxyRect.fLeft; + rectXs[1] = srcProxyRect.fLeft + 2*srcBlurRadius + srcLeft; + rectXs[2] = srcProxyRect.fRight - 2*srcBlurRadius - srcRight; + rectXs[3] = srcProxyRect.fRight; + + rectYs[0] = srcProxyRect.fTop; + rectYs[1] = srcProxyRect.fTop + 2*srcBlurRadius + srcTop; + rectYs[2] = srcProxyRect.fBottom - 2*srcBlurRadius - srcBot; + rectYs[3] = srcProxyRect.fBottom; + + texXs[0] = 0.0f; + texXs[1] = 2.0f*devBlurRadius + devLeft; + texXs[2] = 2.0f*devBlurRadius + devLeft + 1; + texXs[3] = SkIntToScalar(widthHeight->fWidth); + + texYs[0] = 0.0f; + texYs[1] = 2.0f*devBlurRadius + devTop; + texYs[2] = 2.0f*devBlurRadius + devTop + 1; + texYs[3] = SkIntToScalar(widthHeight->fHeight); + + SkRect temp = occluder; + + *numXs = 4; + *numYs = 4; + *skipMask = 0; + if (!temp.isEmpty() && (srcProxyRect.contains(temp) || temp.intersect(srcProxyRect))) { + *skipMask = insert_into_arrays(rectXs, texXs, temp.fLeft, temp.fRight, numXs, 0x1, 1); + *skipMask = insert_into_arrays(rectYs, texYs, temp.fTop, temp.fBottom, + numYs, *skipMask, *numXs-1); + } + + const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius), + SkIntToScalar(devBlurRadius), + SkIntToScalar(newRRWidth), + SkIntToScalar(newRRHeight)); + SkVector newRadii[4]; + newRadii[0] = { SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY) }; + newRadii[1] = { SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY) }; + newRadii[2] = { SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY) }; + newRadii[3] = { SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY) }; + + rrectToDraw->setRectRadii(newRect, newRadii); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style, bool respectCTM) + : fSigma(sigma) + , fBlurStyle(style) + , fRespectCTM(respectCTM) { + SkASSERT(fSigma > 0); + SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle); +} + +SkMask::Format SkBlurMaskFilterImpl::getFormat() const { + return SkMask::kA8_Format; +} + +bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const { + if (this->ignoreXform()) { + return false; + } + + if (rec) { + rec->fSigma = fSigma; + rec->fStyle = fBlurStyle; + } + return true; +} + +bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, + const SkMatrix& matrix, + SkIPoint* margin) const { + SkScalar sigma = this->computeXformedSigma(matrix); + return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, margin); +} + +bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r, + const SkMatrix& matrix, + SkIPoint* margin, SkMask::CreateMode createMode) const { + SkScalar sigma = computeXformedSigma(matrix); + + return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode); +} + +bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r, + const SkMatrix& matrix, + SkIPoint* margin, SkMask::CreateMode createMode) const { + SkScalar sigma = computeXformedSigma(matrix); + + return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode); +} + +#include "include/core/SkCanvas.h" + +static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) { + SkASSERT(mask != nullptr); + + mask->fBounds = bounds.roundOut(); + mask->fRowBytes = SkAlign4(mask->fBounds.width()); + mask->fFormat = SkMask::kA8_Format; + const size_t size = mask->computeImageSize(); + mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc); + if (nullptr == mask->fImage) { + return false; + } + return true; +} + +static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) { + if (!prepare_to_draw_into_mask(rrect.rect(), mask)) { + return false; + } + + // FIXME: This code duplicates code in draw_rects_into_mask, below. Is there a + // clean way to share more code? + SkBitmap bitmap; + bitmap.installMaskPixels(*mask); + + SkCanvas canvas(bitmap); + canvas.translate(-SkIntToScalar(mask->fBounds.left()), + -SkIntToScalar(mask->fBounds.top())); + + SkPaint paint; + paint.setAntiAlias(true); + canvas.drawRRect(rrect, paint); + return true; +} + +static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) { + if (!prepare_to_draw_into_mask(rects[0], mask)) { + return false; + } + + SkBitmap bitmap; + bitmap.installPixels(SkImageInfo::Make(mask->fBounds.width(), + mask->fBounds.height(), + kAlpha_8_SkColorType, + kPremul_SkAlphaType), + mask->fImage, mask->fRowBytes); + + SkCanvas canvas(bitmap); + canvas.translate(-SkIntToScalar(mask->fBounds.left()), + -SkIntToScalar(mask->fBounds.top())); + + SkPaint paint; + paint.setAntiAlias(true); + + if (1 == count) { + canvas.drawRect(rects[0], paint); + } else { + // todo: do I need a fast way to do this? + SkPath path; + path.addRect(rects[0]); + path.addRect(rects[1]); + path.setFillType(SkPath::kEvenOdd_FillType); + canvas.drawPath(path, paint); + } + return true; +} + +static bool rect_exceeds(const SkRect& r, SkScalar v) { + return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v || + r.width() > v || r.height() > v; +} + +#include "src/core/SkMaskCache.h" + +static SkCachedData* copy_mask_to_cacheddata(SkMask* mask) { + const size_t size = mask->computeTotalImageSize(); + SkCachedData* data = SkResourceCache::NewCachedData(size); + if (data) { + memcpy(data->writable_data(), mask->fImage, size); + SkMask::FreeImage(mask->fImage); + mask->fImage = (uint8_t*)data->data(); + } + return data; +} + +static SkCachedData* find_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style, + const SkRRect& rrect) { + return SkMaskCache::FindAndRef(sigma, style, rrect, mask); +} + +static SkCachedData* add_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style, + const SkRRect& rrect) { + SkCachedData* cache = copy_mask_to_cacheddata(mask); + if (cache) { + SkMaskCache::Add(sigma, style, rrect, *mask, cache); + } + return cache; +} + +static SkCachedData* find_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style, + const SkRect rects[], int count) { + return SkMaskCache::FindAndRef(sigma, style, rects, count, mask); +} + +static SkCachedData* add_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style, + const SkRect rects[], int count) { + SkCachedData* cache = copy_mask_to_cacheddata(mask); + if (cache) { + SkMaskCache::Add(sigma, style, rects, count, *mask, cache); + } + return cache; +} + +static const bool c_analyticBlurRRect{true}; + +SkMaskFilterBase::FilterReturn +SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix, + const SkIRect& clipBounds, + NinePatch* patch) const { + SkASSERT(patch != nullptr); + switch (rrect.getType()) { + case SkRRect::kEmpty_Type: + // Nothing to draw. + return kFalse_FilterReturn; + + case SkRRect::kRect_Type: + // We should have caught this earlier. + SkASSERT(false); + // Fall through. + case SkRRect::kOval_Type: + // The nine patch special case does not handle ovals, and we + // already have code for rectangles. + return kUnimplemented_FilterReturn; + + // These three can take advantage of this fast path. + case SkRRect::kSimple_Type: + case SkRRect::kNinePatch_Type: + case SkRRect::kComplex_Type: + break; + } + + // TODO: report correct metrics for innerstyle, where we do not grow the + // total bounds, but we do need an inset the size of our blur-radius + if (kInner_SkBlurStyle == fBlurStyle) { + return kUnimplemented_FilterReturn; + } + + // TODO: take clipBounds into account to limit our coordinates up front + // for now, just skip too-large src rects (to take the old code path). + if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) { + return kUnimplemented_FilterReturn; + } + + SkIPoint margin; + SkMask srcM, dstM; + srcM.fBounds = rrect.rect().roundOut(); + srcM.fFormat = SkMask::kA8_Format; + srcM.fRowBytes = 0; + + bool filterResult = false; + if (c_analyticBlurRRect) { + // special case for fast round rect blur + // don't actually do the blur the first time, just compute the correct size + filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin, + SkMask::kJustComputeBounds_CreateMode); + } + + if (!filterResult) { + filterResult = this->filterMask(&dstM, srcM, matrix, &margin); + } + + if (!filterResult) { + return kFalse_FilterReturn; + } + + // Now figure out the appropriate width and height of the smaller round rectangle + // to stretch. It will take into account the larger radius per side as well as double + // the margin, to account for inner and outer blur. + const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner); + const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner); + const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner); + const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner); + + const SkScalar leftUnstretched = SkTMax(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX); + const SkScalar rightUnstretched = SkTMax(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX); + + // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover + // any fractional space on either side plus 1 for the part to stretch. + const SkScalar stretchSize = SkIntToScalar(3); + + const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize; + if (totalSmallWidth >= rrect.rect().width()) { + // There is no valid piece to stretch. + return kUnimplemented_FilterReturn; + } + + const SkScalar topUnstretched = SkTMax(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY); + const SkScalar bottomUnstretched = SkTMax(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY); + + const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize; + if (totalSmallHeight >= rrect.rect().height()) { + // There is no valid piece to stretch. + return kUnimplemented_FilterReturn; + } + + SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight); + + SkRRect smallRR; + SkVector radii[4]; + radii[SkRRect::kUpperLeft_Corner] = UL; + radii[SkRRect::kUpperRight_Corner] = UR; + radii[SkRRect::kLowerRight_Corner] = LR; + radii[SkRRect::kLowerLeft_Corner] = LL; + smallRR.setRectRadii(smallR, radii); + + const SkScalar sigma = this->computeXformedSigma(matrix); + SkCachedData* cache = find_cached_rrect(&patch->fMask, sigma, fBlurStyle, smallRR); + if (!cache) { + bool analyticBlurWorked = false; + if (c_analyticBlurRRect) { + analyticBlurWorked = + this->filterRRectMask(&patch->fMask, smallRR, matrix, &margin, + SkMask::kComputeBoundsAndRenderImage_CreateMode); + } + + if (!analyticBlurWorked) { + if (!draw_rrect_into_mask(smallRR, &srcM)) { + return kFalse_FilterReturn; + } + + SkAutoMaskFreeImage amf(srcM.fImage); + + if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) { + return kFalse_FilterReturn; + } + } + cache = add_cached_rrect(&patch->fMask, sigma, fBlurStyle, smallRR); + } + + patch->fMask.fBounds.offsetTo(0, 0); + patch->fOuterRect = dstM.fBounds; + patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1; + patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1; + SkASSERT(nullptr == patch->fCache); + patch->fCache = cache; // transfer ownership to patch + return kTrue_FilterReturn; +} + +// Use the faster analytic blur approach for ninepatch rects +static const bool c_analyticBlurNinepatch{true}; + +SkMaskFilterBase::FilterReturn +SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count, + const SkMatrix& matrix, + const SkIRect& clipBounds, + NinePatch* patch) const { + if (count < 1 || count > 2) { + return kUnimplemented_FilterReturn; + } + + // TODO: report correct metrics for innerstyle, where we do not grow the + // total bounds, but we do need an inset the size of our blur-radius + if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) { + return kUnimplemented_FilterReturn; + } + + // TODO: take clipBounds into account to limit our coordinates up front + // for now, just skip too-large src rects (to take the old code path). + if (rect_exceeds(rects[0], SkIntToScalar(32767))) { + return kUnimplemented_FilterReturn; + } + + SkIPoint margin; + SkMask srcM, dstM; + srcM.fBounds = rects[0].roundOut(); + srcM.fFormat = SkMask::kA8_Format; + srcM.fRowBytes = 0; + + bool filterResult = false; + if (count == 1 && c_analyticBlurNinepatch) { + // special case for fast rect blur + // don't actually do the blur the first time, just compute the correct size + filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin, + SkMask::kJustComputeBounds_CreateMode); + } else { + filterResult = this->filterMask(&dstM, srcM, matrix, &margin); + } + + if (!filterResult) { + return kFalse_FilterReturn; + } + + /* + * smallR is the smallest version of 'rect' that will still guarantee that + * we get the same blur results on all edges, plus 1 center row/col that is + * representative of the extendible/stretchable edges of the ninepatch. + * Since our actual edge may be fractional we inset 1 more to be sure we + * don't miss any interior blur. + * x is an added pixel of blur, and { and } are the (fractional) edge + * pixels from the original rect. + * + * x x { x x .... x x } x x + * + * Thus, in this case, we inset by a total of 5 (on each side) beginning + * with our outer-rect (dstM.fBounds) + */ + SkRect smallR[2]; + SkIPoint center; + + // +2 is from +1 for each edge (to account for possible fractional edges + int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2; + int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2; + SkIRect innerIR; + + if (1 == count) { + innerIR = srcM.fBounds; + center.set(smallW, smallH); + } else { + SkASSERT(2 == count); + rects[1].roundIn(&innerIR); + center.set(smallW + (innerIR.left() - srcM.fBounds.left()), + smallH + (innerIR.top() - srcM.fBounds.top())); + } + + // +1 so we get a clean, stretchable, center row/col + smallW += 1; + smallH += 1; + + // we want the inset amounts to be integral, so we don't change any + // fractional phase on the fRight or fBottom of our smallR. + const SkScalar dx = SkIntToScalar(innerIR.width() - smallW); + const SkScalar dy = SkIntToScalar(innerIR.height() - smallH); + if (dx < 0 || dy < 0) { + // we're too small, relative to our blur, to break into nine-patch, + // so we ask to have our normal filterMask() be called. + return kUnimplemented_FilterReturn; + } + + smallR[0].setLTRB(rects[0].left(), rects[0].top(), + rects[0].right() - dx, rects[0].bottom() - dy); + if (smallR[0].width() < 2 || smallR[0].height() < 2) { + return kUnimplemented_FilterReturn; + } + if (2 == count) { + smallR[1].setLTRB(rects[1].left(), rects[1].top(), + rects[1].right() - dx, rects[1].bottom() - dy); + SkASSERT(!smallR[1].isEmpty()); + } + + const SkScalar sigma = this->computeXformedSigma(matrix); + SkCachedData* cache = find_cached_rects(&patch->fMask, sigma, fBlurStyle, smallR, count); + if (!cache) { + if (count > 1 || !c_analyticBlurNinepatch) { + if (!draw_rects_into_mask(smallR, count, &srcM)) { + return kFalse_FilterReturn; + } + + SkAutoMaskFreeImage amf(srcM.fImage); + + if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) { + return kFalse_FilterReturn; + } + } else { + if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin, + SkMask::kComputeBoundsAndRenderImage_CreateMode)) { + return kFalse_FilterReturn; + } + } + cache = add_cached_rects(&patch->fMask, sigma, fBlurStyle, smallR, count); + } + patch->fMask.fBounds.offsetTo(0, 0); + patch->fOuterRect = dstM.fBounds; + patch->fCenter = center; + SkASSERT(nullptr == patch->fCache); + patch->fCache = cache; // transfer ownership to patch + return kTrue_FilterReturn; +} + +void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src, + SkRect* dst) const { + SkScalar pad = 3.0f * fSigma; + + dst->setLTRB(src.fLeft - pad, src.fTop - pad, + src.fRight + pad, src.fBottom + pad); +} + +sk_sp SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) { + const SkScalar sigma = buffer.readScalar(); + SkBlurStyle style = buffer.read32LE(kLastEnum_SkBlurStyle); + + uint32_t flags = buffer.read32LE(0x3); // historically we only recorded 2 bits + bool respectCTM = !(flags & 1); // historically we stored ignoreCTM in low bit + + if (buffer.isVersionLT(SkPicturePriv::kRemoveOccluderFromBlurMaskFilter)) { + SkRect unused; + buffer.readRect(&unused); + } + + return SkMaskFilter::MakeBlur((SkBlurStyle)style, sigma, respectCTM); +} + +void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fSigma); + buffer.writeUInt(fBlurStyle); + buffer.writeUInt(!fRespectCTM); // historically we recorded ignoreCTM +} + + +#if SK_SUPPORT_GPU + +bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrClip& clip, + const SkMatrix& viewMatrix, + const GrShape& shape) const { + SkASSERT(renderTargetContext); + + if (fBlurStyle != kNormal_SkBlurStyle) { + return false; + } + + if (!viewMatrix.isScaleTranslate()) { + return false; + } + + // TODO: we could handle blurred stroked circles + if (!shape.style().isSimpleFill()) { + return false; + } + + SkScalar xformedSigma = this->computeXformedSigma(viewMatrix); + if (xformedSigma <= 0) { + return false; + } + + SkRRect srcRRect; + bool inverted; + if (!shape.asRRect(&srcRRect, nullptr, nullptr, &inverted) || inverted) { + return false; + } + + SkRRect devRRect; + if (!srcRRect.transform(viewMatrix, &devRRect)) { + return false; + } + + if (!SkRRectPriv::AllCornersCircular(devRRect)) { + return false; + } + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + std::unique_ptr fp; + + if (devRRect.isRect() || SkRRectPriv::IsCircle(devRRect)) { + if (devRRect.isRect()) { + fp = GrRectBlurEffect::Make(proxyProvider, *context->priv().caps()->shaderCaps(), + devRRect.rect(), xformedSigma); + } else { + fp = GrCircleBlurFragmentProcessor::Make(proxyProvider, devRRect.rect(), xformedSigma); + } + + if (!fp) { + return false; + } + paint.addCoverageFragmentProcessor(std::move(fp)); + + SkRect srcProxyRect = srcRRect.rect(); + SkScalar outsetX = 3.0f*fSigma; + SkScalar outsetY = 3.0f*fSigma; + if (this->ignoreXform()) { + // When we're ignoring the CTM the padding added to the source rect also needs to ignore + // the CTM. The matrix passed in here is guaranteed to be just scale and translate so we + // can just grab the X and Y scales off the matrix and pre-undo the scale. + outsetX /= SkScalarAbs(viewMatrix.getScaleX()); + outsetY /= SkScalarAbs(viewMatrix.getScaleY()); + } + srcProxyRect.outset(outsetX, outsetY); + + renderTargetContext->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect); + return true; + } + + fp = GrRRectBlurEffect::Make(context, fSigma, xformedSigma, srcRRect, devRRect); + if (!fp) { + return false; + } + + if (!this->ignoreXform()) { + SkRect srcProxyRect = srcRRect.rect(); + srcProxyRect.outset(3.0f*fSigma, 3.0f*fSigma); + + SkVertices::Builder builder(SkVertices::kTriangles_VertexMode, 4, 6, 0); + srcProxyRect.toQuad(builder.positions()); + + static const uint16_t fullIndices[6] = { 0, 1, 2, 0, 2, 3 }; + memcpy(builder.indices(), fullIndices, sizeof(fullIndices)); + sk_sp vertices = builder.detach(); + + paint.addCoverageFragmentProcessor(std::move(fp)); + renderTargetContext->drawVertices(clip, std::move(paint), viewMatrix, std::move(vertices), + nullptr, 0); + } else { + SkMatrix inverse; + if (!viewMatrix.invert(&inverse)) { + return false; + } + + float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f); + SkRect proxyRect = devRRect.rect(); + proxyRect.outset(extra, extra); + + paint.addCoverageFragmentProcessor(std::move(fp)); + renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, + SkMatrix::I(), proxyRect, inverse); + } + + return true; +} + +bool SkBlurMaskFilterImpl::canFilterMaskGPU(const GrShape& shape, + const SkIRect& devSpaceShapeBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkIRect* maskRect) const { + SkScalar xformedSigma = this->computeXformedSigma(ctm); + if (xformedSigma <= 0) { + maskRect->setEmpty(); + return false; + } + + if (maskRect) { + float sigma3 = 3 * SkScalarToFloat(xformedSigma); + + // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area. + SkIRect clipRect = clipBounds.makeOutset(sigma3, sigma3); + SkIRect srcRect = devSpaceShapeBounds.makeOutset(sigma3, sigma3); + + if (!srcRect.intersect(clipRect)) { + srcRect.setEmpty(); + } + *maskRect = srcRect; + } + + // We prefer to blur paths with small blur radii on the CPU. + if (ctm.rectStaysRect()) { + static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64); + static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32); + + if (devSpaceShapeBounds.width() <= kMIN_GPU_BLUR_SIZE && + devSpaceShapeBounds.height() <= kMIN_GPU_BLUR_SIZE && + xformedSigma <= kMIN_GPU_BLUR_SIGMA) { + return false; + } + } + + return true; +} + +sk_sp SkBlurMaskFilterImpl::filterMaskGPU(GrRecordingContext* context, + sk_sp srcProxy, + GrColorType srcColorType, + SkAlphaType srcAlphaType, + const SkMatrix& ctm, + const SkIRect& maskRect) const { + // 'maskRect' isn't snapped to the UL corner but the mask in 'src' is. + const SkIRect clipRect = SkIRect::MakeWH(maskRect.width(), maskRect.height()); + + SkScalar xformedSigma = this->computeXformedSigma(ctm); + SkASSERT(xformedSigma > 0); + + // If we're doing a normal blur, we can clobber the pathTexture in the + // gaussianBlur. Otherwise, we need to save it for later compositing. + bool isNormalBlur = (kNormal_SkBlurStyle == fBlurStyle); + auto renderTargetContext = SkGpuBlurUtils::GaussianBlur(context, + srcProxy, + srcColorType, + srcAlphaType, + SkIPoint::Make(0, 0), + nullptr, + clipRect, + SkIRect::EmptyIRect(), + xformedSigma, + xformedSigma, + GrTextureDomain::kIgnore_Mode); + if (!renderTargetContext) { + return nullptr; + } + + if (!isNormalBlur) { + GrPaint paint; + // Blend pathTexture over blurTexture. + paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Make(std::move(srcProxy), + srcColorType, + SkMatrix::I())); + if (kInner_SkBlurStyle == fBlurStyle) { + // inner: dst = dst * src + paint.setCoverageSetOpXPFactory(SkRegion::kIntersect_Op); + } else if (kSolid_SkBlurStyle == fBlurStyle) { + // solid: dst = src + dst - src * dst + // = src + (1 - src) * dst + paint.setCoverageSetOpXPFactory(SkRegion::kUnion_Op); + } else if (kOuter_SkBlurStyle == fBlurStyle) { + // outer: dst = dst * (1 - src) + // = 0 * src + (1 - src) * dst + paint.setCoverageSetOpXPFactory(SkRegion::kDifference_Op); + } else { + paint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op); + } + + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::Make(clipRect)); + } + + return renderTargetContext->asTextureProxyRef(); +} + +#endif // SK_SUPPORT_GPU + +void sk_register_blur_maskfilter_createproc() { SK_REGISTER_FLATTENABLE(SkBlurMaskFilterImpl); } + +sk_sp SkMaskFilter::MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM) { + if (SkScalarIsFinite(sigma) && sigma > 0) { + return sk_sp(new SkBlurMaskFilterImpl(sigma, style, respectCTM)); + } + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkBlurMask.cpp b/gfx/skia/skia/src/core/SkBlurMask.cpp new file mode 100644 index 0000000000..9963fffe37 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlurMask.cpp @@ -0,0 +1,658 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBlurMask.h" + +#include "include/core/SkColorPriv.h" +#include "include/core/SkMath.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkEndian.h" +#include "src/core/SkMaskBlurFilter.h" +#include "src/core/SkMathPriv.h" + +// This constant approximates the scaling done in the software path's +// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)). +// IMHO, it actually should be 1: we blur "less" than we should do +// according to the CSS and canvas specs, simply because Safari does the same. +// Firefox used to do the same too, until 4.0 where they fixed it. So at some +// point we should probably get rid of these scaling constants and rebaseline +// all the blur tests. +static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f; + +SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) { + return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f; +} + +SkScalar SkBlurMask::ConvertSigmaToRadius(SkScalar sigma) { + return sigma > 0.5f ? (sigma - 0.5f) / kBLUR_SIGMA_SCALE : 0.0f; +} + + +template +static void merge_src_with_blur(uint8_t dst[], int dstRB, + AlphaIter src, int srcRB, + const uint8_t blur[], int blurRB, + int sw, int sh) { + dstRB -= sw; + blurRB -= sw; + while (--sh >= 0) { + AlphaIter rowSrc(src); + for (int x = sw - 1; x >= 0; --x) { + *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*rowSrc))); + ++dst; + ++rowSrc; + ++blur; + } + dst += dstRB; + src >>= srcRB; + blur += blurRB; + } +} + +template +static void clamp_solid_with_orig(uint8_t dst[], int dstRowBytes, + AlphaIter src, int srcRowBytes, + int sw, int sh) { + int x; + while (--sh >= 0) { + AlphaIter rowSrc(src); + for (x = sw - 1; x >= 0; --x) { + int s = *rowSrc; + int d = *dst; + *dst = SkToU8(s + d - SkMulDiv255Round(s, d)); + ++dst; + ++rowSrc; + } + dst += dstRowBytes - sw; + src >>= srcRowBytes; + } +} + +template +static void clamp_outer_with_orig(uint8_t dst[], int dstRowBytes, + AlphaIter src, int srcRowBytes, + int sw, int sh) { + int x; + while (--sh >= 0) { + AlphaIter rowSrc(src); + for (x = sw - 1; x >= 0; --x) { + int srcValue = *rowSrc; + if (srcValue) { + *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - srcValue))); + } + ++dst; + ++rowSrc; + } + dst += dstRowBytes - sw; + src >>= srcRowBytes; + } +} +/////////////////////////////////////////////////////////////////////////////// + +// we use a local function to wrap the class static method to work around +// a bug in gcc98 +void SkMask_FreeImage(uint8_t* image); +void SkMask_FreeImage(uint8_t* image) { + SkMask::FreeImage(image); +} + +bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src, SkScalar sigma, SkBlurStyle style, + SkIPoint* margin) { + if (src.fFormat != SkMask::kBW_Format && + src.fFormat != SkMask::kA8_Format && + src.fFormat != SkMask::kARGB32_Format && + src.fFormat != SkMask::kLCD16_Format) + { + return false; + } + + SkMaskBlurFilter blurFilter{sigma, sigma}; + if (blurFilter.hasNoBlur()) { + // If there is no effective blur most styles will just produce the original mask. + // However, kOuter_SkBlurStyle will produce an empty mask. + if (style == kOuter_SkBlurStyle) { + dst->fImage = nullptr; + dst->fBounds = SkIRect::MakeEmpty(); + dst->fRowBytes = dst->fBounds.width(); + dst->fFormat = SkMask::kA8_Format; + if (margin != nullptr) { + // This filter will disregard the src.fImage completely. + // The margin is actually {-(src.fBounds.width() / 2), -(src.fBounds.height() / 2)} + // but it is not clear if callers will fall over with negative margins. + *margin = SkIPoint{0,0}; + } + return true; + } + return false; + } + const SkIPoint border = blurFilter.blur(src, dst); + // If src.fImage is null, then this call is only to calculate the border. + if (src.fImage != nullptr && dst->fImage == nullptr) { + return false; + } + + if (margin != nullptr) { + *margin = border; + } + + if (src.fImage == nullptr) { + if (style == kInner_SkBlurStyle) { + dst->fBounds = src.fBounds; // restore trimmed bounds + dst->fRowBytes = dst->fBounds.width(); + } + return true; + } + + switch (style) { + case kNormal_SkBlurStyle: + break; + case kSolid_SkBlurStyle: { + auto dstStart = &dst->fImage[border.x() + border.y() * dst->fRowBytes]; + switch (src.fFormat) { + case SkMask::kBW_Format: + clamp_solid_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(src.fImage, 0), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + break; + case SkMask::kA8_Format: + clamp_solid_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(src.fImage), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + break; + case SkMask::kARGB32_Format: { + uint32_t* srcARGB = reinterpret_cast(src.fImage); + clamp_solid_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(srcARGB), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + } break; + case SkMask::kLCD16_Format: { + uint16_t* srcLCD = reinterpret_cast(src.fImage); + clamp_solid_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(srcLCD), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + } break; + default: + SK_ABORT("Unhandled format."); + } + } break; + case kOuter_SkBlurStyle: { + auto dstStart = &dst->fImage[border.x() + border.y() * dst->fRowBytes]; + switch (src.fFormat) { + case SkMask::kBW_Format: + clamp_outer_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(src.fImage, 0), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + break; + case SkMask::kA8_Format: + clamp_outer_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(src.fImage), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + break; + case SkMask::kARGB32_Format: { + uint32_t* srcARGB = reinterpret_cast(src.fImage); + clamp_outer_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(srcARGB), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + } break; + case SkMask::kLCD16_Format: { + uint16_t* srcLCD = reinterpret_cast(src.fImage); + clamp_outer_with_orig( + dstStart, dst->fRowBytes, + SkMask::AlphaIter(srcLCD), src.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + } break; + default: + SK_ABORT("Unhandled format."); + } + } break; + case kInner_SkBlurStyle: { + // now we allocate the "real" dst, mirror the size of src + SkMask blur = *dst; + SkAutoMaskFreeImage autoFreeBlurMask(blur.fImage); + dst->fBounds = src.fBounds; + dst->fRowBytes = dst->fBounds.width(); + size_t dstSize = dst->computeImageSize(); + if (0 == dstSize) { + return false; // too big to allocate, abort + } + dst->fImage = SkMask::AllocImage(dstSize); + auto blurStart = &blur.fImage[border.x() + border.y() * blur.fRowBytes]; + switch (src.fFormat) { + case SkMask::kBW_Format: + merge_src_with_blur( + dst->fImage, dst->fRowBytes, + SkMask::AlphaIter(src.fImage, 0), src.fRowBytes, + blurStart, blur.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + break; + case SkMask::kA8_Format: + merge_src_with_blur( + dst->fImage, dst->fRowBytes, + SkMask::AlphaIter(src.fImage), src.fRowBytes, + blurStart, blur.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + break; + case SkMask::kARGB32_Format: { + uint32_t* srcARGB = reinterpret_cast(src.fImage); + merge_src_with_blur( + dst->fImage, dst->fRowBytes, + SkMask::AlphaIter(srcARGB), src.fRowBytes, + blurStart, blur.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + } break; + case SkMask::kLCD16_Format: { + uint16_t* srcLCD = reinterpret_cast(src.fImage); + merge_src_with_blur( + dst->fImage, dst->fRowBytes, + SkMask::AlphaIter(srcLCD), src.fRowBytes, + blurStart, blur.fRowBytes, + src.fBounds.width(), src.fBounds.height()); + } break; + default: + SK_ABORT("Unhandled format."); + } + } break; + } + + return true; +} + +/* Convolving a box with itself three times results in a piecewise + quadratic function: + + 0 x <= -1.5 + 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5 + 3/4 - x^2 -.5 < x <= .5 + 9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5 + 0 1.5 < x + + Mathematica: + + g[x_] := Piecewise [ { + {9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5}, + {3/4 - x^2 , -.5 < x <= .5}, + {9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5} + }, 0] + + To get the profile curve of the blurred step function at the rectangle + edge, we evaluate the indefinite integral, which is piecewise cubic: + + 0 x <= -1.5 + 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3 -1.5 < x <= -0.5 + 1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5 + 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3 .5 < x <= 1.5 + 1 1.5 < x + + in Mathematica code: + + gi[x_] := Piecewise[ { + { 0 , x <= -1.5 }, + { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 }, + { 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5}, + { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5} + },1] +*/ + +static float gaussianIntegral(float x) { + if (x > 1.5f) { + return 0.0f; + } + if (x < -1.5f) { + return 1.0f; + } + + float x2 = x*x; + float x3 = x2*x; + + if ( x > 0.5f ) { + return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x); + } + if ( x > -0.5f ) { + return 0.5f - (0.75f * x - x3 / 3.0f); + } + return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x); +} + +/* ComputeBlurProfile fills in an array of floating + point values between 0 and 255 for the profile signature of + a blurred half-plane with the given blur radius. Since we're + going to be doing screened multiplications (i.e., 1 - (1-x)(1-y)) + all the time, we actually fill in the profile pre-inverted + (already done 255-x). +*/ + +void SkBlurMask::ComputeBlurProfile(uint8_t* profile, int size, SkScalar sigma) { + SkASSERT(SkScalarCeilToInt(6*sigma) == size); + + int center = size >> 1; + + float invr = 1.f/(2*sigma); + + profile[0] = 255; + for (int x = 1 ; x < size ; ++x) { + float scaled_x = (center - x - .5f) * invr; + float gi = gaussianIntegral(scaled_x); + profile[x] = 255 - (uint8_t) (255.f * gi); + } +} + +// TODO MAYBE: Maintain a profile cache to avoid recomputing this for +// commonly used radii. Consider baking some of the most common blur radii +// directly in as static data? + +// Implementation adapted from Michael Herf's approach: +// http://stereopsis.com/shadowrect/ + +uint8_t SkBlurMask::ProfileLookup(const uint8_t *profile, int loc, + int blurredWidth, int sharpWidth) { + // how far are we from the original edge? + int dx = SkAbs32(((loc << 1) + 1) - blurredWidth) - sharpWidth; + int ox = dx >> 1; + if (ox < 0) { + ox = 0; + } + + return profile[ox]; +} + +void SkBlurMask::ComputeBlurredScanline(uint8_t *pixels, const uint8_t *profile, + unsigned int width, SkScalar sigma) { + + unsigned int profile_size = SkScalarCeilToInt(6*sigma); + SkAutoTMalloc horizontalScanline(width); + + unsigned int sw = width - profile_size; + // nearest odd number less than the profile size represents the center + // of the (2x scaled) profile + int center = ( profile_size & ~1 ) - 1; + + int w = sw - center; + + for (unsigned int x = 0 ; x < width ; ++x) { + if (profile_size <= sw) { + pixels[x] = ProfileLookup(profile, x, width, w); + } else { + float span = float(sw)/(2*sigma); + float giX = 1.5f - (x+.5f)/(2*sigma); + pixels[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span))); + } + } +} + +bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst, + const SkRect &src, SkBlurStyle style, + SkIPoint *margin, SkMask::CreateMode createMode) { + int profileSize = SkScalarCeilToInt(6*sigma); + if (profileSize <= 0) { + return false; // no blur to compute + } + + int pad = profileSize/2; + if (margin) { + margin->set( pad, pad ); + } + + dst->fBounds.setLTRB(SkScalarRoundToInt(src.fLeft - pad), + SkScalarRoundToInt(src.fTop - pad), + SkScalarRoundToInt(src.fRight + pad), + SkScalarRoundToInt(src.fBottom + pad)); + + dst->fRowBytes = dst->fBounds.width(); + dst->fFormat = SkMask::kA8_Format; + dst->fImage = nullptr; + + int sw = SkScalarFloorToInt(src.width()); + int sh = SkScalarFloorToInt(src.height()); + + if (createMode == SkMask::kJustComputeBounds_CreateMode) { + if (style == kInner_SkBlurStyle) { + dst->fBounds = src.round(); // restore trimmed bounds + dst->fRowBytes = sw; + } + return true; + } + + SkAutoTMalloc profile(profileSize); + + ComputeBlurProfile(profile, profileSize, sigma); + + size_t dstSize = dst->computeImageSize(); + if (0 == dstSize) { + return false; // too big to allocate, abort + } + + uint8_t* dp = SkMask::AllocImage(dstSize); + + dst->fImage = dp; + + int dstHeight = dst->fBounds.height(); + int dstWidth = dst->fBounds.width(); + + uint8_t *outptr = dp; + + SkAutoTMalloc horizontalScanline(dstWidth); + SkAutoTMalloc verticalScanline(dstHeight); + + ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma); + ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma); + + for (int y = 0 ; y < dstHeight ; ++y) { + for (int x = 0 ; x < dstWidth ; x++) { + unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], verticalScanline[y]); + *(outptr++) = maskval; + } + } + + if (style == kInner_SkBlurStyle) { + // now we allocate the "real" dst, mirror the size of src + size_t srcSize = (size_t)(src.width() * src.height()); + if (0 == srcSize) { + return false; // too big to allocate, abort + } + dst->fImage = SkMask::AllocImage(srcSize); + for (int y = 0 ; y < sh ; y++) { + uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad; + uint8_t *inner_scanline = dst->fImage + y*sw; + memcpy(inner_scanline, blur_scanline, sw); + } + SkMask::FreeImage(dp); + + dst->fBounds = src.round(); // restore trimmed bounds + dst->fRowBytes = sw; + + } else if (style == kOuter_SkBlurStyle) { + for (int y = pad ; y < dstHeight-pad ; y++) { + uint8_t *dst_scanline = dp + y*dstWidth + pad; + memset(dst_scanline, 0, sw); + } + } else if (style == kSolid_SkBlurStyle) { + for (int y = pad ; y < dstHeight-pad ; y++) { + uint8_t *dst_scanline = dp + y*dstWidth + pad; + memset(dst_scanline, 0xff, sw); + } + } + // normal and solid styles are the same for analytic rect blurs, so don't + // need to handle solid specially. + + return true; +} + +bool SkBlurMask::BlurRRect(SkScalar sigma, SkMask *dst, + const SkRRect &src, SkBlurStyle style, + SkIPoint *margin, SkMask::CreateMode createMode) { + // Temporary for now -- always fail, should cause caller to fall back + // to old path. Plumbing just to land API and parallelize effort. + + return false; +} + +// The "simple" blur is a direct implementation of separable convolution with a discrete +// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very +// useful for correctness comparisons. + +bool SkBlurMask::BlurGroundTruth(SkScalar sigma, SkMask* dst, const SkMask& src, + SkBlurStyle style, SkIPoint* margin) { + + if (src.fFormat != SkMask::kA8_Format) { + return false; + } + + float variance = sigma * sigma; + + int windowSize = SkScalarCeilToInt(sigma*6); + // round window size up to nearest odd number + windowSize |= 1; + + SkAutoTMalloc gaussWindow(windowSize); + + int halfWindow = windowSize >> 1; + + gaussWindow[halfWindow] = 1; + + float windowSum = 1; + for (int x = 1 ; x <= halfWindow ; ++x) { + float gaussian = expf(-x*x / (2*variance)); + gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian; + windowSum += 2*gaussian; + } + + // leave the filter un-normalized for now; we will divide by the normalization + // sum later; + + int pad = halfWindow; + if (margin) { + margin->set( pad, pad ); + } + + dst->fBounds = src.fBounds; + dst->fBounds.outset(pad, pad); + + dst->fRowBytes = dst->fBounds.width(); + dst->fFormat = SkMask::kA8_Format; + dst->fImage = nullptr; + + if (src.fImage) { + + size_t dstSize = dst->computeImageSize(); + if (0 == dstSize) { + return false; // too big to allocate, abort + } + + int srcWidth = src.fBounds.width(); + int srcHeight = src.fBounds.height(); + int dstWidth = dst->fBounds.width(); + + const uint8_t* srcPixels = src.fImage; + uint8_t* dstPixels = SkMask::AllocImage(dstSize); + SkAutoMaskFreeImage autoFreeDstPixels(dstPixels); + + // do the actual blur. First, make a padded copy of the source. + // use double pad so we never have to check if we're outside anything + + int padWidth = srcWidth + 4*pad; + int padHeight = srcHeight; + int padSize = padWidth * padHeight; + + SkAutoTMalloc padPixels(padSize); + memset(padPixels, 0, padSize); + + for (int y = 0 ; y < srcHeight; ++y) { + uint8_t* padptr = padPixels + y * padWidth + 2*pad; + const uint8_t* srcptr = srcPixels + y * srcWidth; + memcpy(padptr, srcptr, srcWidth); + } + + // blur in X, transposing the result into a temporary floating point buffer. + // also double-pad the intermediate result so that the second blur doesn't + // have to do extra conditionals. + + int tmpWidth = padHeight + 4*pad; + int tmpHeight = padWidth - 2*pad; + int tmpSize = tmpWidth * tmpHeight; + + SkAutoTMalloc tmpImage(tmpSize); + memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0])); + + for (int y = 0 ; y < padHeight ; ++y) { + uint8_t *srcScanline = padPixels + y*padWidth; + for (int x = pad ; x < padWidth - pad ; ++x) { + float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output + uint8_t *windowCenter = srcScanline + x; + for (int i = -pad ; i <= pad ; ++i) { + *outPixel += gaussWindow[pad+i]*windowCenter[i]; + } + *outPixel /= windowSum; + } + } + + // blur in Y; now filling in the actual desired destination. We have to do + // the transpose again; these transposes guarantee that we read memory in + // linear order. + + for (int y = 0 ; y < tmpHeight ; ++y) { + float *srcScanline = tmpImage + y*tmpWidth; + for (int x = pad ; x < tmpWidth - pad ; ++x) { + float *windowCenter = srcScanline + x; + float finalValue = 0; + for (int i = -pad ; i <= pad ; ++i) { + finalValue += gaussWindow[pad+i]*windowCenter[i]; + } + finalValue /= windowSum; + uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output + int integerPixel = int(finalValue + 0.5f); + *outPixel = SkClampMax( SkClampPos(integerPixel), 255 ); + } + } + + dst->fImage = dstPixels; + switch (style) { + case kNormal_SkBlurStyle: + break; + case kSolid_SkBlurStyle: { + clamp_solid_with_orig( + dstPixels + pad*dst->fRowBytes + pad, dst->fRowBytes, + SkMask::AlphaIter(srcPixels), src.fRowBytes, + srcWidth, srcHeight); + } break; + case kOuter_SkBlurStyle: { + clamp_outer_with_orig( + dstPixels + pad*dst->fRowBytes + pad, dst->fRowBytes, + SkMask::AlphaIter(srcPixels), src.fRowBytes, + srcWidth, srcHeight); + } break; + case kInner_SkBlurStyle: { + // now we allocate the "real" dst, mirror the size of src + size_t srcSize = src.computeImageSize(); + if (0 == srcSize) { + return false; // too big to allocate, abort + } + dst->fImage = SkMask::AllocImage(srcSize); + merge_src_with_blur(dst->fImage, src.fRowBytes, + SkMask::AlphaIter(srcPixels), src.fRowBytes, + dstPixels + pad*dst->fRowBytes + pad, + dst->fRowBytes, srcWidth, srcHeight); + SkMask::FreeImage(dstPixels); + } break; + } + autoFreeDstPixels.release(); + } + + if (style == kInner_SkBlurStyle) { + dst->fBounds = src.fBounds; // restore trimmed bounds + dst->fRowBytes = src.fRowBytes; + } + + return true; +} diff --git a/gfx/skia/skia/src/core/SkBlurMask.h b/gfx/skia/skia/src/core/SkBlurMask.h new file mode 100644 index 0000000000..55c999e4e1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlurMask.h @@ -0,0 +1,87 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlurMask_DEFINED +#define SkBlurMask_DEFINED + +#include "include/core/SkBlurTypes.h" +#include "include/core/SkRRect.h" +#include "include/core/SkShader.h" +#include "src/core/SkMask.h" + +class SkBlurMask { +public: + static bool SK_WARN_UNUSED_RESULT BlurRect(SkScalar sigma, SkMask *dst, const SkRect &src, + SkBlurStyle, SkIPoint *margin = nullptr, + SkMask::CreateMode createMode = + SkMask::kComputeBoundsAndRenderImage_CreateMode); + static bool SK_WARN_UNUSED_RESULT BlurRRect(SkScalar sigma, SkMask *dst, const SkRRect &src, + SkBlurStyle, SkIPoint *margin = nullptr, + SkMask::CreateMode createMode = + SkMask::kComputeBoundsAndRenderImage_CreateMode); + + // forceQuality will prevent BoxBlur from falling back to the low quality approach when sigma + // is very small -- this can be used predict the margin bump ahead of time without completely + // replicating the internal logic. This permits not only simpler caching of blurred results, + // but also being able to predict precisely at what pixels the blurred profile of e.g. a + // rectangle will lie. + // + // Calling details: + // * calculate margin - if src.fImage is null, then this call only calculates the border. + // * failure - if src.fImage is not null, failure is signal with dst->fImage being + // null. + + static bool SK_WARN_UNUSED_RESULT BoxBlur(SkMask* dst, const SkMask& src, + SkScalar sigma, SkBlurStyle style, + SkIPoint* margin = nullptr); + + // the "ground truth" blur does a gaussian convolution; it's slow + // but useful for comparison purposes. + static bool SK_WARN_UNUSED_RESULT BlurGroundTruth(SkScalar sigma, SkMask* dst, + const SkMask& src, + SkBlurStyle, SkIPoint* margin = nullptr); + + // If radius > 0, return the corresponding sigma, else return 0 + static SkScalar SK_API ConvertRadiusToSigma(SkScalar radius); + // If sigma > 0.5, return the corresponding radius, else return 0 + static SkScalar SK_API ConvertSigmaToRadius(SkScalar sigma); + + /* Helper functions for analytic rectangle blurs */ + + /** Look up the intensity of the (one dimnensional) blurred half-plane. + @param profile The precomputed 1D blur profile; initialized by ComputeBlurProfile below. + @param loc the location to look up; The lookup will clamp invalid inputs, but + meaningful data are available between 0 and blurred_width + @param blurred_width The width of the final, blurred rectangle + @param sharp_width The width of the original, unblurred rectangle. + */ + static uint8_t ProfileLookup(const uint8_t* profile, int loc, int blurredWidth, int sharpWidth); + + /** Populate the profile of a 1D blurred halfplane. + @param profile The 1D table to fill in + @param size Should be 6*sigma bytes + @param sigma The standard deviation of the gaussian blur kernel + */ + static void ComputeBlurProfile(uint8_t* profile, int size, SkScalar sigma); + + /** Compute an entire scanline of a blurred step function. This is a 1D helper that + will produce both the horizontal and vertical profiles of the blurry rectangle. + @param pixels Location to store the resulting pixel data; allocated and managed by caller + @param profile Precomputed blur profile computed by ComputeBlurProfile above. + @param width Size of the pixels array. + @param sigma Standard deviation of the gaussian blur kernel used to compute the profile; + this implicitly gives the size of the pixels array. + */ + + static void ComputeBlurredScanline(uint8_t* pixels, const uint8_t* profile, + unsigned int width, SkScalar sigma); + + + +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkBlurPriv.h b/gfx/skia/skia/src/core/SkBlurPriv.h new file mode 100644 index 0000000000..2509d499b1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkBlurPriv.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlurPriv_DEFINED +#define SkBlurPriv_DEFINED + +#include "include/core/SkRRect.h" +#include "include/core/SkSize.h" + +static const int kSkBlurRRectMaxDivisions = 6; + +// This method computes all the parameters for drawing a partially occluded nine-patched +// blurred rrect mask: +// rrectToDraw - the integerized rrect to draw in the mask +// widthHeight - how large to make the mask (rrectToDraw will be centered in this coord sys) +// rectXs, rectYs - the x & y coordinates of the covering geometry lattice +// texXs, texYs - the texture coordinate at each point in rectXs & rectYs +// numXs, numYs - number of coordinates in the x & y directions +// skipMask - bit mask that contains a 1-bit whenever one of the cells is occluded +// It returns true if 'devRRect' is nine-patchable +bool SkComputeBlurredRRectParams(const SkRRect& srcRRect, const SkRRect& devRRect, + const SkRect& occluder, + SkScalar sigma, SkScalar xformedSigma, + SkRRect* rrectToDraw, + SkISize* widthHeight, + SkScalar rectXs[kSkBlurRRectMaxDivisions], + SkScalar rectYs[kSkBlurRRectMaxDivisions], + SkScalar texXs[kSkBlurRRectMaxDivisions], + SkScalar texYs[kSkBlurRRectMaxDivisions], + int* numXs, int* numYs, uint32_t* skipMask); + +extern void sk_register_blur_maskfilter_createproc(); + +#endif diff --git a/gfx/skia/skia/src/core/SkBuffer.cpp b/gfx/skia/skia/src/core/SkBuffer.cpp new file mode 100644 index 0000000000..3bb38bf73b --- /dev/null +++ b/gfx/skia/skia/src/core/SkBuffer.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" +#include "src/core/SkBuffer.h" +#include + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +const void* SkRBuffer::skip(size_t size) { + if (fValid && size <= this->available()) { + const void* pos = fPos; + fPos += size; + return pos; + } + fValid = false; + return nullptr; +} + +bool SkRBuffer::read(void* buffer, size_t size) { + if (const void* src = this->skip(size)) { + sk_careful_memcpy(buffer, src, size); + return true; + } + return false; +} + +bool SkRBuffer::skipToAlign4() { + intptr_t pos = reinterpret_cast(fPos); + size_t n = SkAlign4(pos) - pos; + if (fValid && n <= this->available()) { + fPos += n; + return true; + } else { + fValid = false; + return false; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void* SkWBuffer::skip(size_t size) { + void* result = fPos; + writeNoSizeCheck(nullptr, size); + return fData == nullptr ? nullptr : result; +} + +void SkWBuffer::writeNoSizeCheck(const void* buffer, size_t size) { + SkASSERT(fData == nullptr || fStop == nullptr || fPos + size <= fStop); + if (fData && buffer) { + sk_careful_memcpy(fPos, buffer, size); + } + fPos += size; +} + +size_t SkWBuffer::padToAlign4() { + size_t pos = this->pos(); + size_t n = SkAlign4(pos) - pos; + + if (n && fData) + { + char* p = fPos; + char* stop = p + n; + do { + *p++ = 0; + } while (p < stop); + } + fPos += n; + return n; +} + +#if 0 +#ifdef SK_DEBUG + static void AssertBuffer32(const void* buffer) + { + SkASSERT(buffer); + SkASSERT(((size_t)buffer & 3) == 0); + } +#else + #define AssertBuffer32(buffer) +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkBuffer.h b/gfx/skia/skia/src/core/SkBuffer.h new file mode 100644 index 0000000000..3f5e45f0ad --- /dev/null +++ b/gfx/skia/skia/src/core/SkBuffer.h @@ -0,0 +1,132 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBuffer_DEFINED +#define SkBuffer_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "include/private/SkNoncopyable.h" +#include "src/core/SkSafeMath.h" + +#include + +/** \class SkRBuffer + + Light weight class for reading data from a memory block. + The RBuffer is given the buffer to read from, with either a specified size + or no size (in which case no range checking is performed). It is iillegal + to attempt to read a value from an empty RBuffer (data == null). +*/ +class SkRBuffer : SkNoncopyable { +public: + SkRBuffer() : fData(nullptr), fPos(nullptr), fStop(nullptr) {} + + /** Initialize RBuffer with a data point and length. + */ + SkRBuffer(const void* data, size_t size) { + SkASSERT(data != nullptr || size == 0); + fData = (const char*)data; + fPos = (const char*)data; + fStop = (const char*)data + size; + } + + /** Return the number of bytes that have been read from the beginning + of the data pointer. + */ + size_t pos() const { return fPos - fData; } + /** Return the total size of the data pointer. Only defined if the length was + specified in the constructor or in a call to reset(). + */ + size_t size() const { return fStop - fData; } + /** Return true if the buffer has read to the end of the data pointer. + Only defined if the length was specified in the constructor or in a call + to reset(). Always returns true if the length was not specified. + */ + bool eof() const { return fPos >= fStop; } + + size_t available() const { return fStop - fPos; } + + bool isValid() const { return fValid; } + + /** Read the specified number of bytes from the data pointer. If buffer is not + null, copy those bytes into buffer. + */ + bool read(void* buffer, size_t size); + bool skipToAlign4(); + + bool readU8(uint8_t* x) { return this->read(x, 1); } + bool readS32(int32_t* x) { return this->read(x, 4); } + bool readU32(uint32_t* x) { return this->read(x, 4); } + + // returns nullptr on failure + const void* skip(size_t bytes); + template const T* skipCount(size_t count) { + return static_cast(this->skip(SkSafeMath::Mul(count, sizeof(T)))); + } + +private: + const char* fData; + const char* fPos; + const char* fStop; + bool fValid = true; +}; + +/** \class SkWBuffer + + Light weight class for writing data to a memory block. + The WBuffer is given the buffer to write into, with either a specified size + or no size, in which case no range checking is performed. An empty WBuffer + is legal, in which case no data is ever written, but the relative pos() + is updated. +*/ +class SkWBuffer : SkNoncopyable { +public: + SkWBuffer() : fData(nullptr), fPos(nullptr), fStop(nullptr) {} + SkWBuffer(void* data) { reset(data); } + SkWBuffer(void* data, size_t size) { reset(data, size); } + + void reset(void* data) { + fData = (char*)data; + fPos = (char*)data; + fStop = nullptr; // no bounds checking + } + + void reset(void* data, size_t size) { + SkASSERT(data != nullptr || size == 0); + fData = (char*)data; + fPos = (char*)data; + fStop = (char*)data + size; + } + + size_t pos() const { return fPos - fData; } + void* skip(size_t size); // return start of skipped data + + void write(const void* buffer, size_t size) { + if (size) { + this->writeNoSizeCheck(buffer, size); + } + } + + size_t padToAlign4(); + + void writePtr(const void* x) { this->writeNoSizeCheck(&x, sizeof(x)); } + void writeScalar(SkScalar x) { this->writeNoSizeCheck(&x, 4); } + void write32(int32_t x) { this->writeNoSizeCheck(&x, 4); } + void write16(int16_t x) { this->writeNoSizeCheck(&x, 2); } + void write8(int8_t x) { this->writeNoSizeCheck(&x, 1); } + void writeBool(bool x) { this->write8(x); } + +private: + void writeNoSizeCheck(const void* buffer, size_t size); + + char* fData; + char* fPos; + char* fStop; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkCachedData.cpp b/gfx/skia/skia/src/core/SkCachedData.cpp new file mode 100644 index 0000000000..1a6f423190 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCachedData.cpp @@ -0,0 +1,177 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" +#include "src/core/SkCachedData.h" +#include "src/core/SkDiscardableMemory.h" + +SkCachedData::SkCachedData(void* data, size_t size) + : fData(data) + , fSize(size) + , fRefCnt(1) + , fStorageType(kMalloc_StorageType) + , fInCache(false) + , fIsLocked(true) +{ + fStorage.fMalloc = data; +} + +SkCachedData::SkCachedData(size_t size, SkDiscardableMemory* dm) + : fData(dm->data()) + , fSize(size) + , fRefCnt(1) + , fStorageType(kDiscardableMemory_StorageType) + , fInCache(false) + , fIsLocked(true) +{ + fStorage.fDM = dm; +} + +SkCachedData::~SkCachedData() { + switch (fStorageType) { + case kMalloc_StorageType: + sk_free(fStorage.fMalloc); + break; + case kDiscardableMemory_StorageType: + delete fStorage.fDM; + break; + } +} + +class SkCachedData::AutoMutexWritable { +public: + AutoMutexWritable(const SkCachedData* cd) : fCD(const_cast(cd)) { + fCD->fMutex.acquire(); + fCD->validate(); + } + ~AutoMutexWritable() { + fCD->validate(); + fCD->fMutex.release(); + } + + SkCachedData* get() { return fCD; } + SkCachedData* operator->() { return fCD; } + +private: + SkCachedData* fCD; +}; + +void SkCachedData::internalRef(bool fromCache) const { + AutoMutexWritable(this)->inMutexRef(fromCache); +} + +void SkCachedData::internalUnref(bool fromCache) const { + if (AutoMutexWritable(this)->inMutexUnref(fromCache)) { + // can't delete inside doInternalUnref, since it is locking a mutex (which we own) + delete this; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkCachedData::inMutexRef(bool fromCache) { + if ((1 == fRefCnt) && fInCache) { + this->inMutexLock(); + } + + fRefCnt += 1; + if (fromCache) { + SkASSERT(!fInCache); + fInCache = true; + } +} + +bool SkCachedData::inMutexUnref(bool fromCache) { + switch (--fRefCnt) { + case 0: + // we're going to be deleted, so we need to be unlocked (for DiscardableMemory) + if (fIsLocked) { + this->inMutexUnlock(); + } + break; + case 1: + if (fInCache && !fromCache) { + // If we're down to 1 owner, and that owner is the cache, this it is safe + // to unlock (and mutate fData) even if the cache is in a different thread, + // as the cache is NOT allowed to inspect or use fData. + this->inMutexUnlock(); + } + break; + default: + break; + } + + if (fromCache) { + SkASSERT(fInCache); + fInCache = false; + } + + // return true when we need to be deleted + return 0 == fRefCnt; +} + +void SkCachedData::inMutexLock() { + fMutex.assertHeld(); + + SkASSERT(!fIsLocked); + fIsLocked = true; + + switch (fStorageType) { + case kMalloc_StorageType: + this->setData(fStorage.fMalloc); + break; + case kDiscardableMemory_StorageType: + if (fStorage.fDM->lock()) { + void* ptr = fStorage.fDM->data(); + SkASSERT(ptr); + this->setData(ptr); + } else { + this->setData(nullptr); // signal failure to lock, contents are gone + } + break; + } +} + +void SkCachedData::inMutexUnlock() { + fMutex.assertHeld(); + + SkASSERT(fIsLocked); + fIsLocked = false; + + switch (fStorageType) { + case kMalloc_StorageType: + // nothing to do/check + break; + case kDiscardableMemory_StorageType: + if (fData) { // did the previous lock succeed? + fStorage.fDM->unlock(); + } + break; + } + this->setData(nullptr); // signal that we're in an unlocked state +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +void SkCachedData::validate() const { + if (fIsLocked) { + SkASSERT((fInCache && fRefCnt > 1) || !fInCache); + switch (fStorageType) { + case kMalloc_StorageType: + SkASSERT(fData == fStorage.fMalloc); + break; + case kDiscardableMemory_StorageType: + // fData can be null or the actual value, depending if DM's lock succeeded + break; + } + } else { + SkASSERT((fInCache && 1 == fRefCnt) || (0 == fRefCnt)); + SkASSERT(nullptr == fData); + } +} +#endif diff --git a/gfx/skia/skia/src/core/SkCachedData.h b/gfx/skia/skia/src/core/SkCachedData.h new file mode 100644 index 0000000000..d7b49ddd38 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCachedData.h @@ -0,0 +1,113 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCachedData_DEFINED +#define SkCachedData_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMutex.h" +#include "include/private/SkNoncopyable.h" + +class SkDiscardableMemory; + +class SkCachedData : ::SkNoncopyable { +public: + SkCachedData(void* mallocData, size_t size); + SkCachedData(size_t size, SkDiscardableMemory*); + virtual ~SkCachedData(); + + size_t size() const { return fSize; } + const void* data() const { return fData; } + + void* writable_data() { return fData; } + + void ref() const { this->internalRef(false); } + void unref() const { this->internalUnref(false); } + + int testing_only_getRefCnt() const { return fRefCnt; } + bool testing_only_isLocked() const { return fIsLocked; } + bool testing_only_isInCache() const { return fInCache; } + + SkDiscardableMemory* diagnostic_only_getDiscardable() const { + return kDiscardableMemory_StorageType == fStorageType ? fStorage.fDM : nullptr; + } + +protected: + // called when fData changes. could be nullptr. + virtual void onDataChange(void* oldData, void* newData) {} + +private: + SkMutex fMutex; // could use a pool of these... + + enum StorageType { + kDiscardableMemory_StorageType, + kMalloc_StorageType + }; + + union { + SkDiscardableMemory* fDM; + void* fMalloc; + } fStorage; + void* fData; + size_t fSize; + int fRefCnt; // low-bit means we're owned by the cache + StorageType fStorageType; + bool fInCache; + bool fIsLocked; + + void internalRef(bool fromCache) const; + void internalUnref(bool fromCache) const; + + void inMutexRef(bool fromCache); + bool inMutexUnref(bool fromCache); // returns true if we should delete "this" + void inMutexLock(); + void inMutexUnlock(); + + // called whenever our fData might change (lock or unlock) + void setData(void* newData) { + if (newData != fData) { + // notify our subclasses of the change + this->onDataChange(fData, newData); + fData = newData; + } + } + + class AutoMutexWritable; + +public: +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + /* + * Attaching a data to to a SkResourceCache (only one at a time) enables the data to be + * unlocked when the cache is the only owner, thus freeing it to be purged (assuming the + * data is backed by a SkDiscardableMemory). + * + * When attached, it also automatically attempts to "lock" the data when the first client + * ref's the data (typically from a find(key, visitor) call). + * + * Thus the data will always be "locked" when a non-cache has a ref on it (whether or not + * the lock succeeded to recover the memory -- check data() to see if it is nullptr). + */ + + /* + * Call when adding this instance to a SkResourceCache::Rec subclass + * (typically in the Rec's constructor). + */ + void attachToCacheAndRef() const { this->internalRef(true); } + + /* + * Call when removing this instance from a SkResourceCache::Rec subclass + * (typically in the Rec's destructor). + */ + void detachFromCacheAndUnref() const { this->internalUnref(true); } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkCanvas.cpp b/gfx/skia/skia/src/core/SkCanvas.cpp new file mode 100644 index 0000000000..94b1835421 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCanvas.cpp @@ -0,0 +1,3155 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" + +#include "include/core/SkColorFilter.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRasterHandleAllocator.h" +#include "include/core/SkString.h" +#include "include/core/SkTextBlob.h" +#include "include/core/SkVertices.h" +#include "include/private/SkNx.h" +#include "include/private/SkTo.h" +#include "include/utils/SkNoDrawCanvas.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBitmapDevice.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkClipStack.h" +#include "src/core/SkDraw.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkLatticeIter.h" +#include "src/core/SkMSAN.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMatrixUtils.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkTextFormatParams.h" +#include "src/core/SkTraceEvent.h" +#include "src/image/SkImage_Base.h" +#include "src/image/SkSurface_Base.h" +#include "src/utils/SkPatchUtils.h" + +#include + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/SkGr.h" +#endif + +#define RETURN_ON_NULL(ptr) do { if (nullptr == (ptr)) return; } while (0) +#define RETURN_ON_FALSE(pred) do { if (!(pred)) return; } while (0) + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * Return true if the drawing this rect would hit every pixels in the canvas. + * + * Returns false if + * - rect does not contain the canvas' bounds + * - paint is not fill + * - paint would blur or otherwise change the coverage of the rect + */ +bool SkCanvas::wouldOverwriteEntireSurface(const SkRect* rect, const SkPaint* paint, + ShaderOverrideOpacity overrideOpacity) const { + static_assert((int)SkPaintPriv::kNone_ShaderOverrideOpacity == + (int)kNone_ShaderOverrideOpacity, + "need_matching_enums0"); + static_assert((int)SkPaintPriv::kOpaque_ShaderOverrideOpacity == + (int)kOpaque_ShaderOverrideOpacity, + "need_matching_enums1"); + static_assert((int)SkPaintPriv::kNotOpaque_ShaderOverrideOpacity == + (int)kNotOpaque_ShaderOverrideOpacity, + "need_matching_enums2"); + + const SkISize size = this->getBaseLayerSize(); + const SkRect bounds = SkRect::MakeIWH(size.width(), size.height()); + + // if we're clipped at all, we can't overwrite the entire surface + { + SkBaseDevice* base = this->getDevice(); + SkBaseDevice* top = this->getTopDevice(); + if (base != top) { + return false; // we're in a saveLayer, so conservatively don't assume we'll overwrite + } + if (!base->clipIsWideOpen()) { + return false; + } + } + + if (rect) { + if (!this->getTotalMatrix().isScaleTranslate()) { + return false; // conservative + } + + SkRect devRect; + this->getTotalMatrix().mapRectScaleTranslate(&devRect, *rect); + if (!devRect.contains(bounds)) { + return false; + } + } + + if (paint) { + SkPaint::Style paintStyle = paint->getStyle(); + if (!(paintStyle == SkPaint::kFill_Style || + paintStyle == SkPaint::kStrokeAndFill_Style)) { + return false; + } + if (paint->getMaskFilter() || paint->getPathEffect() || paint->getImageFilter()) { + return false; // conservative + } + } + return SkPaintPriv::Overwrites(paint, (SkPaintPriv::ShaderOverrideOpacity)overrideOpacity); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// experimental for faster tiled drawing... +//#define SK_TRACE_SAVERESTORE + +#ifdef SK_TRACE_SAVERESTORE + static int gLayerCounter; + static void inc_layer() { ++gLayerCounter; printf("----- inc layer %d\n", gLayerCounter); } + static void dec_layer() { --gLayerCounter; printf("----- dec layer %d\n", gLayerCounter); } + + static int gRecCounter; + static void inc_rec() { ++gRecCounter; printf("----- inc rec %d\n", gRecCounter); } + static void dec_rec() { --gRecCounter; printf("----- dec rec %d\n", gRecCounter); } + + static int gCanvasCounter; + static void inc_canvas() { ++gCanvasCounter; printf("----- inc canvas %d\n", gCanvasCounter); } + static void dec_canvas() { --gCanvasCounter; printf("----- dec canvas %d\n", gCanvasCounter); } +#else + #define inc_layer() + #define dec_layer() + #define inc_rec() + #define dec_rec() + #define inc_canvas() + #define dec_canvas() +#endif + +typedef SkTLazy SkLazyPaint; + +void SkCanvas::predrawNotify(bool willOverwritesEntireSurface) { + if (fSurfaceBase) { + fSurfaceBase->aboutToDraw(willOverwritesEntireSurface + ? SkSurface::kDiscard_ContentChangeMode + : SkSurface::kRetain_ContentChangeMode); + } +} + +void SkCanvas::predrawNotify(const SkRect* rect, const SkPaint* paint, + ShaderOverrideOpacity overrideOpacity) { + if (fSurfaceBase) { + SkSurface::ContentChangeMode mode = SkSurface::kRetain_ContentChangeMode; + // Since willOverwriteAllPixels() may not be complete free to call, we only do so if + // there is an outstanding snapshot, since w/o that, there will be no copy-on-write + // and therefore we don't care which mode we're in. + // + if (fSurfaceBase->outstandingImageSnapshot()) { + if (this->wouldOverwriteEntireSurface(rect, paint, overrideOpacity)) { + mode = SkSurface::kDiscard_ContentChangeMode; + } + } + fSurfaceBase->aboutToDraw(mode); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +/* This is the record we keep for each SkBaseDevice that the user installs. + The clip/matrix/proc are fields that reflect the top of the save/restore + stack. Whenever the canvas changes, it marks a dirty flag, and then before + these are used (assuming we're not on a layer) we rebuild these cache + values: they reflect the top of the save stack, but translated and clipped + by the device's XY offset and bitmap-bounds. +*/ +struct DeviceCM { + DeviceCM* fNext; + sk_sp fDevice; + SkRasterClip fClip; + std::unique_ptr fPaint; // may be null (in the future) + SkMatrix fStashedMatrix; // original CTM; used by imagefilter in saveLayer + sk_sp fClipImage; + SkMatrix fClipMatrix; + + DeviceCM(sk_sp device, const SkPaint* paint, const SkMatrix& stashed, + const SkImage* clipImage, const SkMatrix* clipMatrix) + : fNext(nullptr) + , fDevice(std::move(device)) + , fPaint(paint ? skstd::make_unique(*paint) : nullptr) + , fStashedMatrix(stashed) + , fClipImage(sk_ref_sp(const_cast(clipImage))) + , fClipMatrix(clipMatrix ? *clipMatrix : SkMatrix::I()) + {} + + void reset(const SkIRect& bounds) { + SkASSERT(!fPaint); + SkASSERT(!fNext); + SkASSERT(fDevice); + fClip.setRect(bounds); + } +}; + +namespace { +// Encapsulate state needed to restore from saveBehind() +struct BackImage { + sk_sp fImage; + SkIPoint fLoc; +}; +} + +/* This is the record we keep for each save/restore level in the stack. + Since a level optionally copies the matrix and/or stack, we have pointers + for these fields. If the value is copied for this level, the copy is + stored in the ...Storage field, and the pointer points to that. If the + value is not copied for this level, we ignore ...Storage, and just point + at the corresponding value in the previous level in the stack. +*/ +class SkCanvas::MCRec { +public: + DeviceCM* fLayer; + /* If there are any layers in the stack, this points to the top-most + one that is at or below this level in the stack (so we know what + bitmap/device to draw into from this level. This value is NOT + reference counted, since the real owner is either our fLayer field, + or a previous one in a lower level.) + */ + DeviceCM* fTopLayer; + std::unique_ptr fBackImage; + SkConservativeClip fRasterClip; + SkMatrix fMatrix; + int fDeferredSaveCount; + + MCRec() { + fLayer = nullptr; + fTopLayer = nullptr; + fMatrix.reset(); + fDeferredSaveCount = 0; + + // don't bother initializing fNext + inc_rec(); + } + MCRec(const MCRec& prev) : fRasterClip(prev.fRasterClip), fMatrix(prev.fMatrix) { + fLayer = nullptr; + fTopLayer = prev.fTopLayer; + fDeferredSaveCount = 0; + + // don't bother initializing fNext + inc_rec(); + } + ~MCRec() { + delete fLayer; + dec_rec(); + } + + void reset(const SkIRect& bounds) { + SkASSERT(fLayer); + SkASSERT(fDeferredSaveCount == 0); + + fMatrix.reset(); + fRasterClip.setRect(bounds); + fLayer->reset(bounds); + } +}; + +class SkDrawIter { +public: + SkDrawIter(SkCanvas* canvas) + : fDevice(nullptr), fCurrLayer(canvas->fMCRec->fTopLayer), fPaint(nullptr) + {} + + bool next() { + const DeviceCM* rec = fCurrLayer; + if (rec && rec->fDevice) { + fDevice = rec->fDevice.get(); + fPaint = rec->fPaint.get(); + fCurrLayer = rec->fNext; + // fCurrLayer may be nullptr now + return true; + } + return false; + } + + int getX() const { return fDevice->getOrigin().x(); } + int getY() const { return fDevice->getOrigin().y(); } + const SkPaint* getPaint() const { return fPaint; } + + SkBaseDevice* fDevice; + +private: + const DeviceCM* fCurrLayer; + const SkPaint* fPaint; // May be null. +}; + +#define FOR_EACH_TOP_DEVICE( code ) \ + do { \ + DeviceCM* layer = fMCRec->fTopLayer; \ + while (layer) { \ + SkBaseDevice* device = layer->fDevice.get(); \ + if (device) { \ + code; \ + } \ + layer = layer->fNext; \ + } \ + } while (0) + +///////////////////////////////////////////////////////////////////////////// + +/** + * If the paint has an imagefilter, but it can be simplified to just a colorfilter, return that + * colorfilter, else return nullptr. + */ +static sk_sp image_to_color_filter(const SkPaint& paint) { + SkImageFilter* imgf = paint.getImageFilter(); + if (!imgf) { + return nullptr; + } + + SkColorFilter* imgCFPtr; + if (!imgf->asAColorFilter(&imgCFPtr)) { + return nullptr; + } + sk_sp imgCF(imgCFPtr); + + SkColorFilter* paintCF = paint.getColorFilter(); + if (nullptr == paintCF) { + // there is no existing paint colorfilter, so we can just return the imagefilter's + return imgCF; + } + + // The paint has both a colorfilter(paintCF) and an imagefilter-which-is-a-colorfilter(imgCF) + // and we need to combine them into a single colorfilter. + return imgCF->makeComposed(sk_ref_sp(paintCF)); +} + +/** + * There are many bounds in skia. A circle's bounds is just its center extended by its radius. + * However, if we stroke a circle, then the "bounds" of that is larger, since it will now draw + * outside of its raw-bounds by 1/2 the stroke width. SkPaint has lots of optional + * effects/attributes that can modify the effective bounds of a given primitive -- maskfilters, + * patheffects, stroking, etc. This function takes a raw bounds and a paint, and returns the + * conservative "effective" bounds based on the settings in the paint... with one exception. This + * function does *not* look at the imagefilter, which can also modify the effective bounds. It is + * deliberately ignored. + */ +static const SkRect& apply_paint_to_bounds_sans_imagefilter(const SkPaint& paint, + const SkRect& rawBounds, + SkRect* storage) { + SkPaint tmpUnfiltered(paint); + tmpUnfiltered.setImageFilter(nullptr); + if (tmpUnfiltered.canComputeFastBounds()) { + return tmpUnfiltered.computeFastBounds(rawBounds, storage); + } else { + return rawBounds; + } +} + +class AutoLayerForImageFilter { +public: + // "rawBounds" is the original bounds of the primitive about to be drawn, unmodified by the + // paint. It's used to determine the size of the offscreen layer for filters. + // If null, the clip will be used instead. + AutoLayerForImageFilter(SkCanvas* canvas, const SkPaint& origPaint, + bool skipLayerForImageFilter = false, + const SkRect* rawBounds = nullptr) { + fCanvas = canvas; + fPaint = &origPaint; + fSaveCount = canvas->getSaveCount(); + fTempLayerForImageFilter = false; + + if (auto simplifiedCF = image_to_color_filter(origPaint)) { + SkASSERT(!fLazyPaint.isValid()); + SkPaint* paint = fLazyPaint.set(origPaint); + paint->setColorFilter(std::move(simplifiedCF)); + paint->setImageFilter(nullptr); + fPaint = paint; + } + + if (!skipLayerForImageFilter && fPaint->getImageFilter()) { + /** + * We implement ImageFilters for a given draw by creating a layer, then applying the + * imagefilter to the pixels of that layer (its backing surface/image), and then + * we call restore() to xfer that layer to the main canvas. + * + * 1. SaveLayer (with a paint containing the current imagefilter and xfermode) + * 2. Generate the src pixels: + * Remove the imagefilter and the xfermode from the paint that we (AutoDrawLooper) + * return (fPaint). We then draw the primitive (using srcover) into a cleared + * buffer/surface. + * 3. Restore the layer created in #1 + * The imagefilter is passed the buffer/surface from the layer (now filled with the + * src pixels of the primitive). It returns a new "filtered" buffer, which we + * draw onto the previous layer using the xfermode from the original paint. + */ + + SkPaint restorePaint; + restorePaint.setImageFilter(fPaint->refImageFilter()); + restorePaint.setBlendMode(fPaint->getBlendMode()); + + SkRect storage; + if (rawBounds) { + // Make rawBounds include all paint outsets except for those due to image filters. + rawBounds = &apply_paint_to_bounds_sans_imagefilter(*fPaint, *rawBounds, &storage); + } + (void)canvas->internalSaveLayer(SkCanvas::SaveLayerRec(rawBounds, &restorePaint), + SkCanvas::kFullLayer_SaveLayerStrategy); + fTempLayerForImageFilter = true; + + // Remove the restorePaint fields from our "working" paint + SkASSERT(!fLazyPaint.isValid()); + SkPaint* paint = fLazyPaint.set(origPaint); + paint->setImageFilter(nullptr); + paint->setBlendMode(SkBlendMode::kSrcOver); + fPaint = paint; + } + } + + ~AutoLayerForImageFilter() { + if (fTempLayerForImageFilter) { + fCanvas->internalRestore(); + } + SkASSERT(fCanvas->getSaveCount() == fSaveCount); + } + + const SkPaint& paint() const { + SkASSERT(fPaint); + return *fPaint; + } + +private: + SkLazyPaint fLazyPaint; // base paint storage in case we need to modify it + SkCanvas* fCanvas; + const SkPaint* fPaint; // points to either the original paint, or lazy (if we needed it) + int fSaveCount; + bool fTempLayerForImageFilter; +}; + +////////// macros to place around the internal draw calls ////////////////// + +#define DRAW_BEGIN_DRAWBITMAP(paint, skipLayerForFilter, bounds) \ + this->predrawNotify(); \ + AutoLayerForImageFilter draw(this, paint, skipLayerForFilter, bounds); \ + { SkDrawIter iter(this); + + +#define DRAW_BEGIN_DRAWDEVICE(paint) \ + this->predrawNotify(); \ + AutoLayerForImageFilter draw(this, paint, true); \ + { SkDrawIter iter(this); + +#define DRAW_BEGIN(paint, bounds) \ + this->predrawNotify(); \ + AutoLayerForImageFilter draw(this, paint, false, bounds); \ + { SkDrawIter iter(this); + +#define DRAW_BEGIN_CHECK_COMPLETE_OVERWRITE(paint, bounds, auxOpaque) \ + this->predrawNotify(bounds, &paint, auxOpaque); \ + AutoLayerForImageFilter draw(this, paint, false, bounds); \ + { SkDrawIter iter(this); + +#define DRAW_END } + +//////////////////////////////////////////////////////////////////////////// + +static inline SkRect qr_clip_bounds(const SkIRect& bounds) { + if (bounds.isEmpty()) { + return SkRect::MakeEmpty(); + } + + // Expand bounds out by 1 in case we are anti-aliasing. We store the + // bounds as floats to enable a faster quick reject implementation. + SkRect dst; + SkNx_cast(Sk4i::Load(&bounds.fLeft) + Sk4i(-1,-1,1,1)).store(&dst.fLeft); + return dst; +} + +void SkCanvas::resetForNextPicture(const SkIRect& bounds) { + this->restoreToCount(1); + fMCRec->reset(bounds); + + // We're peering through a lot of structs here. Only at this scope do we + // know that the device is a SkNoPixelsDevice. + static_cast(fMCRec->fLayer->fDevice.get())->resetForNextPicture(bounds); + fDeviceClipBounds = qr_clip_bounds(bounds); + fIsScaleTranslate = true; +} + +void SkCanvas::init(sk_sp device) { + fAllowSimplifyClip = false; + fSaveCount = 1; + + fMCRec = (MCRec*)fMCStack.push_back(); + new (fMCRec) MCRec; + fMCRec->fRasterClip.setDeviceClipRestriction(&fClipRestrictionRect); + fIsScaleTranslate = true; + + SkASSERT(sizeof(DeviceCM) <= sizeof(fDeviceCMStorage)); + fMCRec->fLayer = (DeviceCM*)fDeviceCMStorage; + new (fDeviceCMStorage) DeviceCM(device, nullptr, fMCRec->fMatrix, nullptr, nullptr); + + fMCRec->fTopLayer = fMCRec->fLayer; + + fSurfaceBase = nullptr; + + if (device) { + // The root device and the canvas should always have the same pixel geometry + SkASSERT(fProps.pixelGeometry() == device->surfaceProps().pixelGeometry()); + fMCRec->fRasterClip.setRect(device->getGlobalBounds()); + fDeviceClipBounds = qr_clip_bounds(device->getGlobalBounds()); + + device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect); + } + + fScratchGlyphRunBuilder = skstd::make_unique(); +} + +SkCanvas::SkCanvas() + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(SkSurfaceProps::kLegacyFontHost_InitType) +{ + inc_canvas(); + + this->init(nullptr); +} + +SkCanvas::SkCanvas(int width, int height, const SkSurfaceProps* props) + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(SkSurfacePropsCopyOrDefault(props)) +{ + inc_canvas(); + this->init(sk_make_sp( + SkIRect::MakeWH(SkTMax(width, 0), SkTMax(height, 0)), fProps)); +} + +SkCanvas::SkCanvas(const SkIRect& bounds) + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(SkSurfaceProps::kLegacyFontHost_InitType) +{ + inc_canvas(); + + SkIRect r = bounds.isEmpty() ? SkIRect::MakeEmpty() : bounds; + this->init(sk_make_sp(r, fProps)); +} + +SkCanvas::SkCanvas(sk_sp device) + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(device->surfaceProps()) +{ + inc_canvas(); + + this->init(device); +} + +SkCanvas::SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props) + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(props) +{ + inc_canvas(); + + sk_sp device(new SkBitmapDevice(bitmap, fProps, nullptr, nullptr)); + this->init(device); +} + +SkCanvas::SkCanvas(const SkBitmap& bitmap, std::unique_ptr alloc, + SkRasterHandleAllocator::Handle hndl) + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(SkSurfaceProps::kLegacyFontHost_InitType) + , fAllocator(std::move(alloc)) +{ + inc_canvas(); + + sk_sp device(new SkBitmapDevice(bitmap, fProps, hndl, nullptr)); + this->init(device); +} + +SkCanvas::SkCanvas(const SkBitmap& bitmap) : SkCanvas(bitmap, nullptr, nullptr) {} + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK +SkCanvas::SkCanvas(const SkBitmap& bitmap, ColorBehavior) + : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) + , fProps(SkSurfaceProps::kLegacyFontHost_InitType) + , fAllocator(nullptr) +{ + inc_canvas(); + + SkBitmap tmp(bitmap); + *const_cast(&tmp.info()) = tmp.info().makeColorSpace(nullptr); + sk_sp device(new SkBitmapDevice(tmp, fProps, nullptr, nullptr)); + this->init(device); +} +#endif + +SkCanvas::~SkCanvas() { + // free up the contents of our deque + this->restoreToCount(1); // restore everything but the last + + this->internalRestore(); // restore the last, since we're going away + + dec_canvas(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkCanvas::flush() { + this->onFlush(); +} + +void SkCanvas::onFlush() { + SkBaseDevice* device = this->getDevice(); + if (device) { + device->flush(); + } +} + +SkISize SkCanvas::getBaseLayerSize() const { + SkBaseDevice* d = this->getDevice(); + return d ? SkISize::Make(d->width(), d->height()) : SkISize::Make(0, 0); +} + +SkIRect SkCanvas::getTopLayerBounds() const { + SkBaseDevice* d = this->getTopDevice(); + if (!d) { + return SkIRect::MakeEmpty(); + } + return SkIRect::MakeXYWH(d->getOrigin().x(), d->getOrigin().y(), d->width(), d->height()); +} + +SkBaseDevice* SkCanvas::getDevice() const { + // return root device + MCRec* rec = (MCRec*) fMCStack.front(); + SkASSERT(rec && rec->fLayer); + return rec->fLayer->fDevice.get(); +} + +SkBaseDevice* SkCanvas::getTopDevice() const { + return fMCRec->fTopLayer->fDevice.get(); +} + +bool SkCanvas::readPixels(const SkPixmap& pm, int x, int y) { + SkBaseDevice* device = this->getDevice(); + return device && pm.addr() && device->readPixels(pm, x, y); +} + +bool SkCanvas::readPixels(const SkImageInfo& dstInfo, void* dstP, size_t rowBytes, int x, int y) { + return this->readPixels({ dstInfo, dstP, rowBytes}, x, y); +} + +bool SkCanvas::readPixels(const SkBitmap& bm, int x, int y) { + SkPixmap pm; + return bm.peekPixels(&pm) && this->readPixels(pm, x, y); +} + +bool SkCanvas::writePixels(const SkBitmap& bitmap, int x, int y) { + SkPixmap pm; + if (bitmap.peekPixels(&pm)) { + return this->writePixels(pm.info(), pm.addr(), pm.rowBytes(), x, y); + } + return false; +} + +bool SkCanvas::writePixels(const SkImageInfo& srcInfo, const void* pixels, size_t rowBytes, + int x, int y) { + SkBaseDevice* device = this->getDevice(); + if (!device) { + return false; + } + + // This check gives us an early out and prevents generation ID churn on the surface. + // This is purely optional: it is a subset of the checks performed by SkWritePixelsRec. + SkIRect srcRect = SkIRect::MakeXYWH(x, y, srcInfo.width(), srcInfo.height()); + if (!srcRect.intersect({0, 0, device->width(), device->height()})) { + return false; + } + + // Tell our owning surface to bump its generation ID. + const bool completeOverwrite = + srcRect.size() == SkISize::Make(device->width(), device->height()); + this->predrawNotify(completeOverwrite); + + // This can still fail, most notably in the case of a invalid color type or alpha type + // conversion. We could pull those checks into this function and avoid the unnecessary + // generation ID bump. But then we would be performing those checks twice, since they + // are also necessary at the bitmap/pixmap entry points. + return device->writePixels({srcInfo, pixels, rowBytes}, x, y); +} + +////////////////////////////////////////////////////////////////////////////// + +void SkCanvas::checkForDeferredSave() { + if (fMCRec->fDeferredSaveCount > 0) { + this->doSave(); + } +} + +int SkCanvas::getSaveCount() const { +#ifdef SK_DEBUG + int count = 0; + SkDeque::Iter iter(fMCStack, SkDeque::Iter::kFront_IterStart); + for (;;) { + const MCRec* rec = (const MCRec*)iter.next(); + if (!rec) { + break; + } + count += 1 + rec->fDeferredSaveCount; + } + SkASSERT(count == fSaveCount); +#endif + return fSaveCount; +} + +int SkCanvas::save() { + fSaveCount += 1; + fMCRec->fDeferredSaveCount += 1; + return this->getSaveCount() - 1; // return our prev value +} + +void SkCanvas::doSave() { + this->willSave(); + + SkASSERT(fMCRec->fDeferredSaveCount > 0); + fMCRec->fDeferredSaveCount -= 1; + this->internalSave(); +} + +void SkCanvas::restore() { + if (fMCRec->fDeferredSaveCount > 0) { + SkASSERT(fSaveCount > 1); + fSaveCount -= 1; + fMCRec->fDeferredSaveCount -= 1; + } else { + // check for underflow + if (fMCStack.count() > 1) { + this->willRestore(); + SkASSERT(fSaveCount > 1); + fSaveCount -= 1; + this->internalRestore(); + this->didRestore(); + } + } +} + +void SkCanvas::restoreToCount(int count) { + // sanity check + if (count < 1) { + count = 1; + } + + int n = this->getSaveCount() - count; + for (int i = 0; i < n; ++i) { + this->restore(); + } +} + +void SkCanvas::internalSave() { + MCRec* newTop = (MCRec*)fMCStack.push_back(); + new (newTop) MCRec(*fMCRec); // balanced in restore() + fMCRec = newTop; + + FOR_EACH_TOP_DEVICE(device->save()); +} + +bool SkCanvas::BoundsAffectsClip(SaveLayerFlags saveLayerFlags) { + return !(saveLayerFlags & SkCanvasPriv::kDontClipToLayer_SaveLayerFlag); +} + +bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveLayerFlags saveLayerFlags, + SkIRect* intersection, const SkImageFilter* imageFilter) { + // clipRectBounds() is called to determine the input layer size needed for a given image filter. + // The coordinate space of the rectangle passed to filterBounds(kReverse) is meant to be in the + // filtering layer space. Here, 'clipBounds' is always in the true device space. When an image + // filter does not require a decomposed CTM matrix, the filter space and device space are the + // same. When it has been decomposed, we want the original image filter node to process the + // bounds in the layer space represented by the decomposed scale matrix. 'imageFilter' is no + // longer the original filter, but has the remainder matrix baked into it, and passing in the + // the true device clip bounds ensures that the matrix image filter provides a layer clip bounds + // to the original filter node (barring inflation from consecutive calls to mapRect). While + // initially counter-intuitive given the apparent inconsistency of coordinate spaces, always + // passing getDeviceClipBounds() to 'imageFilter' is correct. + // FIXME (michaelludwig) - When the remainder matrix is instead applied as a final draw, it will + // be important to more accurately calculate the clip bounds in the layer space for the original + // image filter (similar to how matrix image filter does it, but ideally without the inflation). + SkIRect clipBounds = this->getDeviceClipBounds(); + if (clipBounds.isEmpty()) { + return false; + } + + const SkMatrix& ctm = fMCRec->fMatrix; // this->getTotalMatrix() + + if (imageFilter && bounds && !imageFilter->canComputeFastBounds()) { + // If the image filter DAG affects transparent black then we will need to render + // out to the clip bounds + bounds = nullptr; + } + + SkIRect inputSaveLayerBounds; + if (bounds) { + SkRect r; + ctm.mapRect(&r, *bounds); + r.roundOut(&inputSaveLayerBounds); + } else { // no user bounds, so just use the clip + inputSaveLayerBounds = clipBounds; + } + + if (imageFilter) { + // expand the clip bounds by the image filter DAG to include extra content that might + // be required by the image filters. + clipBounds = imageFilter->filterBounds(clipBounds, ctm, + SkImageFilter::kReverse_MapDirection, + &inputSaveLayerBounds); + } + + SkIRect clippedSaveLayerBounds; + if (bounds) { + // For better or for worse, user bounds currently act as a hard clip on the layer's + // extent (i.e., they implement the CSS filter-effects 'filter region' feature). + clippedSaveLayerBounds = inputSaveLayerBounds; + } else { + // If there are no user bounds, we don't want to artificially restrict the resulting + // layer bounds, so allow the expanded clip bounds free reign. + clippedSaveLayerBounds = clipBounds; + } + + // early exit if the layer's bounds are clipped out + if (!clippedSaveLayerBounds.intersect(clipBounds)) { + if (BoundsAffectsClip(saveLayerFlags)) { + fMCRec->fTopLayer->fDevice->clipRegion(SkRegion(), SkClipOp::kIntersect); // empty + fMCRec->fRasterClip.setEmpty(); + fDeviceClipBounds.setEmpty(); + } + return false; + } + SkASSERT(!clippedSaveLayerBounds.isEmpty()); + + if (BoundsAffectsClip(saveLayerFlags)) { + // Simplify the current clips since they will be applied properly during restore() + fMCRec->fRasterClip.setRect(clippedSaveLayerBounds); + fDeviceClipBounds = qr_clip_bounds(clippedSaveLayerBounds); + } + + if (intersection) { + *intersection = clippedSaveLayerBounds; + } + + return true; +} + +int SkCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint) { + return this->saveLayer(SaveLayerRec(bounds, paint, 0)); +} + +int SkCanvas::saveLayer(const SaveLayerRec& rec) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (rec.fPaint && rec.fPaint->nothingToDraw()) { + // no need for the layer (or any of the draws until the matching restore() + this->save(); + this->clipRect({0,0,0,0}); + } else { + SaveLayerStrategy strategy = this->getSaveLayerStrategy(rec); + fSaveCount += 1; + this->internalSaveLayer(rec, strategy); + } + return this->getSaveCount() - 1; +} + +int SkCanvas::only_axis_aligned_saveBehind(const SkRect* bounds) { + if (bounds && !this->getLocalClipBounds().intersects(*bounds)) { + // Assuming clips never expand, if the request bounds is outside of the current clip + // there is no need to copy/restore the area, so just devolve back to a regular save. + this->save(); + } else { + bool doTheWork = this->onDoSaveBehind(bounds); + fSaveCount += 1; + this->internalSave(); + if (doTheWork) { + this->internalSaveBehind(bounds); + } + } + return this->getSaveCount() - 1; +} + +void SkCanvas::DrawDeviceWithFilter(SkBaseDevice* src, const SkImageFilter* filter, + SkBaseDevice* dst, const SkIPoint& dstOrigin, + const SkMatrix& ctm) { + // The local bounds of the src device; all the bounds passed to snapSpecial must be intersected + // with this rect. + const SkIRect srcDevRect = SkIRect::MakeWH(src->width(), src->height()); + + if (!filter) { + // All non-filtered devices are currently axis aligned, so they only differ by their origin. + // This means that we only have to copy a dst-sized block of pixels out of src and translate + // it to the matching position relative to dst's origin. + SkIRect snapBounds = SkIRect::MakeXYWH(dstOrigin.x() - src->getOrigin().x(), + dstOrigin.y() - src->getOrigin().y(), + dst->width(), dst->height()); + if (!snapBounds.intersect(srcDevRect)) { + return; + } + + auto special = src->snapSpecial(snapBounds); + if (special) { + // The image is drawn at 1-1 scale with integer translation, so no filtering is needed. + SkPaint p; + dst->drawSpecial(special.get(), 0, 0, p, nullptr, SkMatrix::I()); + } + return; + } + + // First decompose the ctm into a post-filter transform and a filter matrix that is supported + // by the backdrop filter. + SkMatrix toRoot, layerMatrix; + SkSize scale; + if (ctm.isScaleTranslate() || as_IFB(filter)->canHandleComplexCTM()) { + toRoot = SkMatrix::I(); + layerMatrix = ctm; + } else if (ctm.decomposeScale(&scale, &toRoot)) { + layerMatrix = SkMatrix::MakeScale(scale.fWidth, scale.fHeight); + } else { + // Perspective, for now, do no scaling of the layer itself. + // TODO (michaelludwig) - perhaps it'd be better to explore a heuristic scale pulled from + // the matrix, e.g. based on the midpoint of the near/far planes? + toRoot = ctm; + layerMatrix = SkMatrix::I(); + } + + // We have to map the dst bounds from the root space into the layer space where filtering will + // occur. If we knew the input bounds of the content that defined the original dst bounds, we + // could map that forward by layerMatrix and have tighter bounds, but toRoot^-1 * dst bounds + // is a safe, conservative estimate. + SkMatrix fromRoot; + if (!toRoot.invert(&fromRoot)) { + return; + } + + // This represents what the backdrop filter needs to produce in the layer space, and is sized + // such that drawing it into dst with the toRoot transform will cover the actual dst device. + SkIRect layerTargetBounds = fromRoot.mapRect( + SkRect::MakeXYWH(dstOrigin.x(), dstOrigin.y(), dst->width(), dst->height())).roundOut(); + // While layerTargetBounds is what needs to be output by the filter, the filtering process may + // require some extra input pixels. + SkIRect layerInputBounds = filter->filterBounds( + layerTargetBounds, layerMatrix, SkImageFilter::kReverse_MapDirection, + &layerTargetBounds); + + // Map the required input into the root space, then make relative to the src device. This will + // be the conservative contents required to fill a layerInputBounds-sized surface with the + // backdrop content (transformed back into the layer space using fromRoot). + SkIRect backdropBounds = toRoot.mapRect(SkRect::Make(layerInputBounds)).roundOut(); + backdropBounds.offset(-src->getOrigin().x(), -src->getOrigin().y()); + if (!backdropBounds.intersect(srcDevRect)) { + return; + } + + auto special = src->snapSpecial(backdropBounds); + if (!special) { + return; + } + + SkColorType colorType = src->imageInfo().colorType(); + if (colorType == kUnknown_SkColorType) { + colorType = kRGBA_8888_SkColorType; + } + SkColorSpace* colorSpace = src->imageInfo().colorSpace(); + + SkPaint p; + if (!toRoot.isIdentity()) { + // Drawing the temporary and final filtered image requires a higher filter quality if the + // 'toRoot' transformation is not identity, in order to minimize the impact on already + // rendered edges/content. + // TODO (michaelludwig) - Explore reducing this quality, identify visual tradeoffs + p.setFilterQuality(kHigh_SkFilterQuality); + + // The snapped backdrop content needs to be transformed by fromRoot into the layer space, + // and stored in a temporary surface, which is then used as the input to the actual filter. + auto tmpSurface = special->makeSurface(colorType, colorSpace, layerInputBounds.size()); + if (!tmpSurface) { + return; + } + + auto tmpCanvas = tmpSurface->getCanvas(); + tmpCanvas->clear(SK_ColorTRANSPARENT); + // Reading in reverse, this takes the backdrop bounds from src device space into the root + // space, then maps from root space into the layer space, then maps it so the input layer's + // top left corner is (0, 0). This transformation automatically accounts for any cropping + // performed on backdropBounds. + tmpCanvas->translate(-layerInputBounds.fLeft, -layerInputBounds.fTop); + tmpCanvas->concat(fromRoot); + tmpCanvas->translate(src->getOrigin().x(), src->getOrigin().y()); + + tmpCanvas->drawImageRect(special->asImage(), special->subset(), + SkRect::Make(backdropBounds), &p, kStrict_SrcRectConstraint); + special = tmpSurface->makeImageSnapshot(); + } else { + // Since there is no extra transform that was done, update the input bounds to reflect + // cropping of the snapped backdrop image. In this case toRoot = I, so layerInputBounds + // was equal to backdropBounds before it was made relative to the src device and cropped. + // When we use the original snapped image directly, just map the update backdrop bounds + // back into the shared layer space + layerInputBounds = backdropBounds; + layerInputBounds.offset(src->getOrigin().x(), src->getOrigin().y()); + + // Similar to the unfiltered case above, when toRoot is the identity, then the final + // draw will be 1-1 so there is no need to increase filter quality. + p.setFilterQuality(kNone_SkFilterQuality); + } + + // Now evaluate the filter on 'special', which contains the backdrop content mapped back into + // layer space. This has to further offset everything so that filter evaluation thinks the + // source image's top left corner is (0, 0). + // TODO (michaelludwig) - Once image filters are robust to non-(0,0) image origins for inputs, + // this can be simplified. + layerTargetBounds.offset(-layerInputBounds.fLeft, -layerInputBounds.fTop); + SkMatrix filterCTM = layerMatrix; + filterCTM.postTranslate(-layerInputBounds.fLeft, -layerInputBounds.fTop); + skif::Context ctx(filterCTM, layerTargetBounds, nullptr, colorType, colorSpace, special.get()); + + SkIPoint offset; + special = as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset); + if (special) { + // Draw the filtered backdrop content into the dst device. We add layerInputBounds origin + // to offset because the original value in 'offset' was relative to 'filterCTM'. 'filterCTM' + // had subtracted the layerInputBounds origin, so adding that back makes 'offset' relative + // to 'layerMatrix' (what we need it to be when drawing the image by 'toRoot'). + offset += layerInputBounds.topLeft(); + + // Manually setting the device's CTM requires accounting for the device's origin. + // TODO (michaelludwig) - This could be simpler if the dst device had its origin configured + // before filtering the backdrop device, and if SkAutoDeviceCTMRestore had a way to accept + // a global CTM instead of a device CTM. + SkMatrix dstCTM = toRoot; + dstCTM.postTranslate(-dstOrigin.x(), -dstOrigin.y()); + SkAutoDeviceCTMRestore acr(dst, dstCTM); + + // And because devices don't have a special-image draw function that supports arbitrary + // matrices, we are abusing the asImage() functionality here... + SkRect specialSrc = SkRect::Make(special->subset()); + auto looseImage = special->asImage(); + dst->drawImageRect( + looseImage.get(), &specialSrc, + SkRect::MakeXYWH(offset.x(), offset.y(), special->width(), special->height()), + p, kStrict_SrcRectConstraint); + } +} + +static SkImageInfo make_layer_info(const SkImageInfo& prev, int w, int h, const SkPaint* paint) { + SkColorType ct = prev.colorType(); + if (prev.bytesPerPixel() <= 4 && + prev.colorType() != kRGBA_8888_SkColorType && + prev.colorType() != kBGRA_8888_SkColorType) { + // "Upgrade" A8, G8, 565, 4444, 1010102, 101010x, and 888x to 8888, + // ensuring plenty of alpha bits for the layer, perhaps losing some color bits in return. + ct = kN32_SkColorType; + } + return SkImageInfo::Make(w, h, ct, kPremul_SkAlphaType, prev.refColorSpace()); +} + +void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy strategy) { + TRACE_EVENT0("skia", TRACE_FUNC); + const SkRect* bounds = rec.fBounds; + const SkPaint* paint = rec.fPaint; + SaveLayerFlags saveLayerFlags = rec.fSaveLayerFlags; + + // If we have a backdrop filter, then we must apply it to the entire layer (clip-bounds) + // regardless of any hint-rect from the caller. skbug.com/8783 + if (rec.fBackdrop) { + bounds = nullptr; + } + + SkLazyPaint lazyP; + SkImageFilter* imageFilter = paint ? paint->getImageFilter() : nullptr; + SkMatrix stashedMatrix = fMCRec->fMatrix; + MCRec* modifiedRec = nullptr; + + /* + * Many ImageFilters (so far) do not (on their own) correctly handle matrices (CTM) that + * contain rotation/skew/etc. We rely on applyCTM to create a new image filter DAG as needed to + * accommodate this, but it requires update the CTM we use when drawing into the layer. + * + * 1. Stash off the current CTM + * 2. Apply the CTM to imagefilter, which decomposes it into simple and complex transforms + * if necessary. + * 3. Wack the CTM to be the remaining scale matrix and use the modified imagefilter, which + * is a MatrixImageFilter that contains the complex matrix. + * 4. Proceed as usual, allowing the client to draw into the layer (now with a scale-only CTM) + * 5. During restore, the MatrixImageFilter automatically applies complex stage to the output + * of the original imagefilter, and draw that (via drawSprite) + * 6. Unwack the CTM to its original state (i.e. stashedMatrix) + * + * Perhaps in the future we could augment #5 to apply REMAINDER as part of the draw (no longer + * a sprite operation) to avoid the extra buffer/overhead of MatrixImageFilter. + */ + if (imageFilter) { + SkMatrix modifiedCTM; + sk_sp modifiedFilter = as_IFB(imageFilter)->applyCTM(stashedMatrix, + &modifiedCTM); + if (as_IFB(modifiedFilter)->uniqueID() != as_IFB(imageFilter)->uniqueID()) { + // The original filter couldn't support the CTM entirely + SkASSERT(modifiedCTM.isScaleTranslate() || as_IFB(imageFilter)->canHandleComplexCTM()); + modifiedRec = fMCRec; + this->internalSetMatrix(modifiedCTM); + SkPaint* p = lazyP.set(*paint); + p->setImageFilter(std::move(modifiedFilter)); + imageFilter = p->getImageFilter(); + paint = p; + } + // Else the filter didn't change, so modifiedCTM == stashedMatrix and there's nothing + // left to do since the stack already has that as the CTM. + } + + // do this before we create the layer. We don't call the public save() since + // that would invoke a possibly overridden virtual + this->internalSave(); + + SkIRect ir; + if (!this->clipRectBounds(bounds, saveLayerFlags, &ir, imageFilter)) { + if (modifiedRec) { + // In this case there will be no layer in which to stash the matrix so we need to + // revert the prior MCRec to its earlier state. + modifiedRec->fMatrix = stashedMatrix; + } + return; + } + + // FIXME: do willSaveLayer() overriders returning kNoLayer_SaveLayerStrategy really care about + // the clipRectBounds() call above? + if (kNoLayer_SaveLayerStrategy == strategy) { + return; + } + + SkPixelGeometry geo = fProps.pixelGeometry(); + if (paint) { + // TODO: perhaps add a query to filters so we might preserve opaqueness... + if (paint->getImageFilter() || paint->getColorFilter()) { + geo = kUnknown_SkPixelGeometry; + } + } + + SkBaseDevice* priorDevice = this->getTopDevice(); + if (nullptr == priorDevice) { // Do we still need this check??? + SkDebugf("Unable to find device for layer."); + return; + } + + SkImageInfo info = make_layer_info(priorDevice->imageInfo(), ir.width(), ir.height(), paint); + if (rec.fSaveLayerFlags & kF16ColorType) { + info = info.makeColorType(kRGBA_F16_SkColorType); + } + + sk_sp newDevice; + { + const bool preserveLCDText = kOpaque_SkAlphaType == info.alphaType() || + (saveLayerFlags & kPreserveLCDText_SaveLayerFlag); + const SkBaseDevice::TileUsage usage = + preserveLCDText ? SkBaseDevice::kPossible_TileUsage : SkBaseDevice::kNever_TileUsage; + const bool trackCoverage = + SkToBool(saveLayerFlags & kMaskAgainstCoverage_EXPERIMENTAL_DONT_USE_SaveLayerFlag); + const SkBaseDevice::CreateInfo createInfo = SkBaseDevice::CreateInfo(info, usage, geo, + trackCoverage, + fAllocator.get()); + newDevice.reset(priorDevice->onCreateDevice(createInfo, paint)); + if (!newDevice) { + return; + } + } + DeviceCM* layer = new DeviceCM(newDevice, paint, stashedMatrix, rec.fClipMask, rec.fClipMatrix); + + // only have a "next" if this new layer doesn't affect the clip (rare) + layer->fNext = BoundsAffectsClip(saveLayerFlags) ? nullptr : fMCRec->fTopLayer; + fMCRec->fLayer = layer; + fMCRec->fTopLayer = layer; // this field is NOT an owner of layer + + if ((rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) || rec.fBackdrop) { + DrawDeviceWithFilter(priorDevice, rec.fBackdrop, newDevice.get(), { ir.fLeft, ir.fTop }, + fMCRec->fMatrix); + } + + newDevice->setOrigin(fMCRec->fMatrix, ir.fLeft, ir.fTop); + + newDevice->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect); + if (layer->fNext) { + // need to punch a hole in the previous device, so we don't draw there, given that + // the new top-layer will allow drawing to happen "below" it. + SkRegion hole(ir); + do { + layer = layer->fNext; + layer->fDevice->clipRegion(hole, SkClipOp::kDifference); + } while (layer->fNext); + } +} + +int SkCanvas::saveLayerAlpha(const SkRect* bounds, U8CPU alpha) { + if (0xFF == alpha) { + return this->saveLayer(bounds, nullptr); + } else { + SkPaint tmpPaint; + tmpPaint.setAlpha(alpha); + return this->saveLayer(bounds, &tmpPaint); + } +} + +void SkCanvas::internalSaveBehind(const SkRect* localBounds) { + SkIRect devBounds; + if (localBounds) { + SkRect tmp; + fMCRec->fMatrix.mapRect(&tmp, *localBounds); + if (!devBounds.intersect(tmp.round(), this->getDeviceClipBounds())) { + devBounds.setEmpty(); + } + } else { + devBounds = this->getDeviceClipBounds(); + } + if (devBounds.isEmpty()) { + return; + } + + SkBaseDevice* device = this->getTopDevice(); + if (nullptr == device) { // Do we still need this check??? + return; + } + + // need the bounds relative to the device itself + devBounds.offset(-device->fOrigin.fX, -device->fOrigin.fY); + + // This is getting the special image from the current device, which is then drawn into (both by + // a client, and the drawClippedToSaveBehind below). Since this is not saving a layer, with its + // own device, we need to explicitly copy the back image contents so that its original content + // is available when we splat it back later during restore. + auto backImage = device->snapSpecial(devBounds, /* copy */ true); + if (!backImage) { + return; + } + + // we really need the save, so we can wack the fMCRec + this->checkForDeferredSave(); + + fMCRec->fBackImage.reset(new BackImage{std::move(backImage), devBounds.topLeft()}); + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kClear); + this->drawClippedToSaveBehind(paint); +} + +void SkCanvas::internalRestore() { + SkASSERT(fMCStack.count() != 0); + + // reserve our layer (if any) + DeviceCM* layer = fMCRec->fLayer; // may be null + // now detach it from fMCRec so we can pop(). Gets freed after its drawn + fMCRec->fLayer = nullptr; + + // move this out before we do the actual restore + auto backImage = std::move(fMCRec->fBackImage); + + // now do the normal restore() + fMCRec->~MCRec(); // balanced in save() + fMCStack.pop_back(); + fMCRec = (MCRec*)fMCStack.back(); + + if (fMCRec) { + FOR_EACH_TOP_DEVICE(device->restore(fMCRec->fMatrix)); + } + + if (backImage) { + SkPaint paint; + paint.setBlendMode(SkBlendMode::kDstOver); + const int x = backImage->fLoc.x(); + const int y = backImage->fLoc.y(); + this->getTopDevice()->drawSpecial(backImage->fImage.get(), x, y, paint, + nullptr, SkMatrix::I()); + } + + /* Time to draw the layer's offscreen. We can't call the public drawSprite, + since if we're being recorded, we don't want to record this (the + recorder will have already recorded the restore). + */ + if (layer) { + if (fMCRec) { + const SkIPoint& origin = layer->fDevice->getOrigin(); + layer->fDevice->setImmutable(); + this->internalDrawDevice(layer->fDevice.get(), origin.x(), origin.y(), + layer->fPaint.get(), + layer->fClipImage.get(), layer->fClipMatrix); + // restore what we smashed in internalSaveLayer + this->internalSetMatrix(layer->fStashedMatrix); + // reset this, since internalDrawDevice will have set it to true + delete layer; + } else { + // we're at the root + SkASSERT(layer == (void*)fDeviceCMStorage); + layer->~DeviceCM(); + // no need to update fMCRec, 'cause we're killing the canvas + } + } + + if (fMCRec) { + fIsScaleTranslate = fMCRec->fMatrix.isScaleTranslate(); + fDeviceClipBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); + } +} + +sk_sp SkCanvas::makeSurface(const SkImageInfo& info, const SkSurfaceProps* props) { + if (nullptr == props) { + props = &fProps; + } + return this->onNewSurface(info, *props); +} + +sk_sp SkCanvas::onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props) { + SkBaseDevice* dev = this->getDevice(); + return dev ? dev->makeSurface(info, props) : nullptr; +} + +SkImageInfo SkCanvas::imageInfo() const { + return this->onImageInfo(); +} + +SkImageInfo SkCanvas::onImageInfo() const { + SkBaseDevice* dev = this->getDevice(); + if (dev) { + return dev->imageInfo(); + } else { + return SkImageInfo::MakeUnknown(0, 0); + } +} + +bool SkCanvas::getProps(SkSurfaceProps* props) const { + return this->onGetProps(props); +} + +bool SkCanvas::onGetProps(SkSurfaceProps* props) const { + SkBaseDevice* dev = this->getDevice(); + if (dev) { + if (props) { + *props = fProps; + } + return true; + } else { + return false; + } +} + +bool SkCanvas::peekPixels(SkPixmap* pmap) { + return this->onPeekPixels(pmap); +} + +bool SkCanvas::onPeekPixels(SkPixmap* pmap) { + SkBaseDevice* dev = this->getDevice(); + return dev && dev->peekPixels(pmap); +} + +void* SkCanvas::accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin) { + SkPixmap pmap; + if (!this->onAccessTopLayerPixels(&pmap)) { + return nullptr; + } + if (info) { + *info = pmap.info(); + } + if (rowBytes) { + *rowBytes = pmap.rowBytes(); + } + if (origin) { + *origin = this->getTopDevice()->getOrigin(); + } + return pmap.writable_addr(); +} + +bool SkCanvas::onAccessTopLayerPixels(SkPixmap* pmap) { + SkBaseDevice* dev = this->getTopDevice(); + return dev && dev->accessPixels(pmap); +} + +///////////////////////////////////////////////////////////////////////////// + +// In our current design/features, we should never have a layer (src) in a different colorspace +// than its parent (dst), so we assert that here. This is called out from other asserts, in case +// we add some feature in the future to allow a given layer/imagefilter to operate in a specific +// colorspace. +static void check_drawdevice_colorspaces(SkColorSpace* src, SkColorSpace* dst) { + SkASSERT(src == dst); +} + +void SkCanvas::internalDrawDevice(SkBaseDevice* srcDev, int x, int y, const SkPaint* paint, + SkImage* clipImage, const SkMatrix& clipMatrix) { + SkPaint tmp; + if (nullptr == paint) { + paint = &tmp; + } + + DRAW_BEGIN_DRAWDEVICE(*paint) + + while (iter.next()) { + SkBaseDevice* dstDev = iter.fDevice; + check_drawdevice_colorspaces(dstDev->imageInfo().colorSpace(), + srcDev->imageInfo().colorSpace()); + paint = &draw.paint(); + SkImageFilter* filter = paint->getImageFilter(); + SkIPoint pos = { x - iter.getX(), y - iter.getY() }; + if (filter || clipImage) { + sk_sp specialImage = srcDev->snapSpecial(); + if (specialImage) { + check_drawdevice_colorspaces(dstDev->imageInfo().colorSpace(), + specialImage->getColorSpace()); + dstDev->drawSpecial(specialImage.get(), pos.x(), pos.y(), *paint, + clipImage, clipMatrix); + } + } else { + dstDev->drawDevice(srcDev, pos.x(), pos.y(), *paint); + } + } + + DRAW_END +} + +///////////////////////////////////////////////////////////////////////////// + +void SkCanvas::translate(SkScalar dx, SkScalar dy) { + if (dx || dy) { + this->checkForDeferredSave(); + fMCRec->fMatrix.preTranslate(dx,dy); + + // Translate shouldn't affect the is-scale-translateness of the matrix. + SkASSERT(fIsScaleTranslate == fMCRec->fMatrix.isScaleTranslate()); + + FOR_EACH_TOP_DEVICE(device->setGlobalCTM(fMCRec->fMatrix)); + + this->didTranslate(dx,dy); + } +} + +void SkCanvas::scale(SkScalar sx, SkScalar sy) { + SkMatrix m; + m.setScale(sx, sy); + this->concat(m); +} + +void SkCanvas::rotate(SkScalar degrees) { + SkMatrix m; + m.setRotate(degrees); + this->concat(m); +} + +void SkCanvas::rotate(SkScalar degrees, SkScalar px, SkScalar py) { + SkMatrix m; + m.setRotate(degrees, px, py); + this->concat(m); +} + +void SkCanvas::skew(SkScalar sx, SkScalar sy) { + SkMatrix m; + m.setSkew(sx, sy); + this->concat(m); +} + +void SkCanvas::concat(const SkMatrix& matrix) { + if (matrix.isIdentity()) { + return; + } + + this->checkForDeferredSave(); + fMCRec->fMatrix.preConcat(matrix); + fIsScaleTranslate = fMCRec->fMatrix.isScaleTranslate(); + + FOR_EACH_TOP_DEVICE(device->setGlobalCTM(fMCRec->fMatrix)); + + this->didConcat(matrix); +} + +void SkCanvas::internalSetMatrix(const SkMatrix& matrix) { + fMCRec->fMatrix = matrix; + fIsScaleTranslate = matrix.isScaleTranslate(); + + FOR_EACH_TOP_DEVICE(device->setGlobalCTM(fMCRec->fMatrix)); +} + +void SkCanvas::setMatrix(const SkMatrix& matrix) { + this->checkForDeferredSave(); + this->internalSetMatrix(matrix); + this->didSetMatrix(matrix); +} + +void SkCanvas::resetMatrix() { + this->setMatrix(SkMatrix::I()); +} + +////////////////////////////////////////////////////////////////////////////// + +void SkCanvas::clipRect(const SkRect& rect, SkClipOp op, bool doAA) { + if (!rect.isFinite()) { + return; + } + this->checkForDeferredSave(); + ClipEdgeStyle edgeStyle = doAA ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle; + this->onClipRect(rect, op, edgeStyle); +} + +void SkCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) { + const bool isAA = kSoft_ClipEdgeStyle == edgeStyle; + + FOR_EACH_TOP_DEVICE(device->clipRect(rect, op, isAA)); + + AutoValidateClip avc(this); + fMCRec->fRasterClip.opRect(rect, fMCRec->fMatrix, this->getTopLayerBounds(), (SkRegion::Op)op, + isAA); + fDeviceClipBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); +} + +void SkCanvas::androidFramework_setDeviceClipRestriction(const SkIRect& rect) { + fClipRestrictionRect = rect; + if (fClipRestrictionRect.isEmpty()) { + // we notify the device, but we *dont* resolve deferred saves (since we're just + // removing the restriction if the rect is empty. how I hate this api. + FOR_EACH_TOP_DEVICE(device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect)); + } else { + this->checkForDeferredSave(); + FOR_EACH_TOP_DEVICE(device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect)); + AutoValidateClip avc(this); + fMCRec->fRasterClip.opIRect(fClipRestrictionRect, SkRegion::kIntersect_Op); + fDeviceClipBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); + } +} + +void SkCanvas::clipRRect(const SkRRect& rrect, SkClipOp op, bool doAA) { + this->checkForDeferredSave(); + ClipEdgeStyle edgeStyle = doAA ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle; + if (rrect.isRect()) { + this->onClipRect(rrect.getBounds(), op, edgeStyle); + } else { + this->onClipRRect(rrect, op, edgeStyle); + } +} + +void SkCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) { + AutoValidateClip avc(this); + + bool isAA = kSoft_ClipEdgeStyle == edgeStyle; + + FOR_EACH_TOP_DEVICE(device->clipRRect(rrect, op, isAA)); + + fMCRec->fRasterClip.opRRect(rrect, fMCRec->fMatrix, this->getTopLayerBounds(), (SkRegion::Op)op, + isAA); + fDeviceClipBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); +} + +void SkCanvas::clipPath(const SkPath& path, SkClipOp op, bool doAA) { + this->checkForDeferredSave(); + ClipEdgeStyle edgeStyle = doAA ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle; + + if (!path.isInverseFillType() && fMCRec->fMatrix.rectStaysRect()) { + SkRect r; + if (path.isRect(&r)) { + this->onClipRect(r, op, edgeStyle); + return; + } + SkRRect rrect; + if (path.isOval(&r)) { + rrect.setOval(r); + this->onClipRRect(rrect, op, edgeStyle); + return; + } + if (path.isRRect(&rrect)) { + this->onClipRRect(rrect, op, edgeStyle); + return; + } + } + + this->onClipPath(path, op, edgeStyle); +} + +void SkCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) { + AutoValidateClip avc(this); + + bool isAA = kSoft_ClipEdgeStyle == edgeStyle; + + FOR_EACH_TOP_DEVICE(device->clipPath(path, op, isAA)); + + const SkPath* rasterClipPath = &path; + const SkMatrix* matrix = &fMCRec->fMatrix; + fMCRec->fRasterClip.opPath(*rasterClipPath, *matrix, this->getTopLayerBounds(), + (SkRegion::Op)op, isAA); + fDeviceClipBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); +} + +void SkCanvas::clipRegion(const SkRegion& rgn, SkClipOp op) { + this->checkForDeferredSave(); + this->onClipRegion(rgn, op); +} + +void SkCanvas::onClipRegion(const SkRegion& rgn, SkClipOp op) { + FOR_EACH_TOP_DEVICE(device->clipRegion(rgn, op)); + + AutoValidateClip avc(this); + + fMCRec->fRasterClip.opRegion(rgn, (SkRegion::Op)op); + fDeviceClipBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); +} + +#ifdef SK_DEBUG +void SkCanvas::validateClip() const { +#ifndef SK_DISABLE_SLOW_DEBUG_VALIDATION + // construct clipRgn from the clipstack + const SkBaseDevice* device = this->getDevice(); + if (!device) { + SkASSERT(this->isClipEmpty()); + return; + } +#endif +} +#endif + +bool SkCanvas::androidFramework_isClipAA() const { + bool containsAA = false; + + FOR_EACH_TOP_DEVICE(containsAA |= device->onClipIsAA()); + + return containsAA; +} + +class RgnAccumulator { + SkRegion* fRgn; +public: + RgnAccumulator(SkRegion* total) : fRgn(total) {} + void accumulate(SkBaseDevice* device, SkRegion* rgn) { + SkIPoint origin = device->getOrigin(); + if (origin.x() | origin.y()) { + rgn->translate(origin.x(), origin.y()); + } + fRgn->op(*rgn, SkRegion::kUnion_Op); + } +}; + +void SkCanvas::temporary_internal_getRgnClip(SkRegion* rgn) { + RgnAccumulator accum(rgn); + SkRegion tmp; + + rgn->setEmpty(); + FOR_EACH_TOP_DEVICE(device->onAsRgnClip(&tmp); accum.accumulate(device, &tmp)); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkCanvas::isClipEmpty() const { + return fMCRec->fRasterClip.isEmpty(); + + // TODO: should we only use the conservative answer in a recording canvas? +#if 0 + SkBaseDevice* dev = this->getTopDevice(); + // if no device we return true + return !dev || dev->onGetClipType() == SkBaseDevice::kEmpty_ClipType; +#endif +} + +bool SkCanvas::isClipRect() const { + SkBaseDevice* dev = this->getTopDevice(); + // if no device we return false + return dev && dev->onGetClipType() == SkBaseDevice::ClipType::kRect; +} + +static inline bool is_nan_or_clipped(const Sk4f& devRect, const Sk4f& devClip) { +#if !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + __m128 lLtT = _mm_unpacklo_ps(devRect.fVec, devClip.fVec); + __m128 RrBb = _mm_unpackhi_ps(devClip.fVec, devRect.fVec); + __m128 mask = _mm_cmplt_ps(lLtT, RrBb); + return 0xF != _mm_movemask_ps(mask); +#elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON) + float32x4_t lLtT = vzipq_f32(devRect.fVec, devClip.fVec).val[0]; + float32x4_t RrBb = vzipq_f32(devClip.fVec, devRect.fVec).val[1]; + uint32x4_t mask = vcltq_f32(lLtT, RrBb); + return 0xFFFFFFFFFFFFFFFF != (uint64_t) vmovn_u32(mask); +#else + SkRect devRectAsRect; + SkRect devClipAsRect; + devRect.store(&devRectAsRect.fLeft); + devClip.store(&devClipAsRect.fLeft); + return !devRectAsRect.isFinite() || !devRectAsRect.intersect(devClipAsRect); +#endif +} + +// It's important for this function to not be inlined. Otherwise the compiler will share code +// between the fast path and the slow path, resulting in two slow paths. +static SK_NEVER_INLINE bool quick_reject_slow_path(const SkRect& src, const SkRect& deviceClip, + const SkMatrix& matrix) { + SkRect deviceRect; + matrix.mapRect(&deviceRect, src); + return !deviceRect.isFinite() || !deviceRect.intersect(deviceClip); +} + +bool SkCanvas::quickReject(const SkRect& src) const { +#ifdef SK_DEBUG + // Verify that fDeviceClipBounds are set properly. + SkRect tmp = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); + if (fMCRec->fRasterClip.isEmpty()) { + SkASSERT(fDeviceClipBounds.isEmpty()); + } else { + SkASSERT(tmp == fDeviceClipBounds); + } + + // Verify that fIsScaleTranslate is set properly. + SkASSERT(fIsScaleTranslate == fMCRec->fMatrix.isScaleTranslate()); +#endif + + if (!fIsScaleTranslate) { + return quick_reject_slow_path(src, fDeviceClipBounds, fMCRec->fMatrix); + } + + // We inline the implementation of mapScaleTranslate() for the fast path. + float sx = fMCRec->fMatrix.getScaleX(); + float sy = fMCRec->fMatrix.getScaleY(); + float tx = fMCRec->fMatrix.getTranslateX(); + float ty = fMCRec->fMatrix.getTranslateY(); + Sk4f scale(sx, sy, sx, sy); + Sk4f trans(tx, ty, tx, ty); + + // Apply matrix. + Sk4f ltrb = Sk4f::Load(&src.fLeft) * scale + trans; + + // Make sure left < right, top < bottom. + Sk4f rblt(ltrb[2], ltrb[3], ltrb[0], ltrb[1]); + Sk4f min = Sk4f::Min(ltrb, rblt); + Sk4f max = Sk4f::Max(ltrb, rblt); + // We can extract either pair [0,1] or [2,3] from min and max and be correct, but on + // ARM this sequence generates the fastest (a single instruction). + Sk4f devRect = Sk4f(min[2], min[3], max[0], max[1]); + + // Check if the device rect is NaN or outside the clip. + return is_nan_or_clipped(devRect, Sk4f::Load(&fDeviceClipBounds.fLeft)); +} + +bool SkCanvas::quickReject(const SkPath& path) const { + return path.isEmpty() || this->quickReject(path.getBounds()); +} + +SkRect SkCanvas::getLocalClipBounds() const { + SkIRect ibounds = this->getDeviceClipBounds(); + if (ibounds.isEmpty()) { + return SkRect::MakeEmpty(); + } + + SkMatrix inverse; + // if we can't invert the CTM, we can't return local clip bounds + if (!fMCRec->fMatrix.invert(&inverse)) { + return SkRect::MakeEmpty(); + } + + SkRect bounds; + // adjust it outwards in case we are antialiasing + const int margin = 1; + + SkRect r = SkRect::Make(ibounds.makeOutset(margin, margin)); + inverse.mapRect(&bounds, r); + return bounds; +} + +SkIRect SkCanvas::getDeviceClipBounds() const { + return fMCRec->fRasterClip.getBounds(); +} + +const SkMatrix& SkCanvas::getTotalMatrix() const { + return fMCRec->fMatrix; +} + +GrRenderTargetContext* SkCanvas::internal_private_accessTopLayerRenderTargetContext() { + SkBaseDevice* dev = this->getTopDevice(); + return dev ? dev->accessRenderTargetContext() : nullptr; +} + +GrContext* SkCanvas::getGrContext() { + SkBaseDevice* device = this->getTopDevice(); + return device ? device->context() : nullptr; +} + +void SkCanvas::drawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (outer.isEmpty()) { + return; + } + if (inner.isEmpty()) { + this->drawRRect(outer, paint); + return; + } + + // We don't have this method (yet), but technically this is what we should + // be able to return ... + // if (!outer.contains(inner))) { + // + // For now at least check for containment of bounds + if (!outer.getBounds().contains(inner.getBounds())) { + return; + } + + this->onDrawDRRect(outer, inner, paint); +} + +void SkCanvas::drawPaint(const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawPaint(paint); +} + +void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + // To avoid redundant logic in our culling code and various backends, we always sort rects + // before passing them along. + this->onDrawRect(r.makeSorted(), paint); +} + +void SkCanvas::drawClippedToSaveBehind(const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawBehind(paint); +} + +void SkCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (region.isEmpty()) { + return; + } + + if (region.isRect()) { + return this->drawIRect(region.getBounds(), paint); + } + + this->onDrawRegion(region, paint); +} + +void SkCanvas::drawOval(const SkRect& r, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + // To avoid redundant logic in our culling code and various backends, we always sort rects + // before passing them along. + this->onDrawOval(r.makeSorted(), paint); +} + +void SkCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawRRect(rrect, paint); +} + +void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawPoints(mode, count, pts, paint); +} + +void SkCanvas::drawVertices(const sk_sp& vertices, SkBlendMode mode, + const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(vertices); + // We expect fans to be converted to triangles when building or deserializing SkVertices. + SkASSERT(vertices->mode() != SkVertices::kTriangleFan_VertexMode); + this->onDrawVerticesObject(vertices.get(), nullptr, 0, mode, paint); +} + +void SkCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(vertices); + this->onDrawVerticesObject(vertices, nullptr, 0, mode, paint); +} + +void SkCanvas::drawVertices(const sk_sp& vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode mode, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(vertices); + SkASSERT(boneCount <= 80); + this->onDrawVerticesObject(vertices.get(), bones, boneCount, mode, paint); +} + +void SkCanvas::drawVertices(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode mode, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(vertices); + SkASSERT(boneCount <= 80); + this->onDrawVerticesObject(vertices, bones, boneCount, mode, paint); +} + +void SkCanvas::drawPath(const SkPath& path, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawPath(path, paint); +} + +void SkCanvas::drawImage(const SkImage* image, SkScalar x, SkScalar y, const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(image); + this->onDrawImage(image, x, y, paint); +} + +// Returns true if the rect can be "filled" : non-empty and finite +static bool fillable(const SkRect& r) { + SkScalar w = r.width(); + SkScalar h = r.height(); + return SkScalarIsFinite(w) && w > 0 && SkScalarIsFinite(h) && h > 0; +} + +void SkCanvas::drawImageRect(const SkImage* image, const SkRect& src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(image); + if (!fillable(dst) || !fillable(src)) { + return; + } + this->onDrawImageRect(image, &src, dst, paint, constraint); +} + +void SkCanvas::drawImageRect(const SkImage* image, const SkIRect& isrc, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + RETURN_ON_NULL(image); + this->drawImageRect(image, SkRect::Make(isrc), dst, paint, constraint); +} + +void SkCanvas::drawImageRect(const SkImage* image, const SkRect& dst, const SkPaint* paint) { + RETURN_ON_NULL(image); + this->drawImageRect(image, SkRect::MakeIWH(image->width(), image->height()), dst, paint, + kFast_SrcRectConstraint); +} + +namespace { +class LatticePaint : SkNoncopyable { +public: + LatticePaint(const SkPaint* origPaint) : fPaint(origPaint) { + if (!origPaint) { + return; + } + if (origPaint->getFilterQuality() > kLow_SkFilterQuality) { + fPaint.writable()->setFilterQuality(kLow_SkFilterQuality); + } + if (origPaint->getMaskFilter()) { + fPaint.writable()->setMaskFilter(nullptr); + } + if (origPaint->isAntiAlias()) { + fPaint.writable()->setAntiAlias(false); + } + } + + const SkPaint* get() const { + return fPaint; + } + +private: + SkTCopyOnFirstWrite fPaint; +}; +} // namespace + +void SkCanvas::drawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(image); + if (dst.isEmpty()) { + return; + } + if (SkLatticeIter::Valid(image->width(), image->height(), center)) { + LatticePaint latticePaint(paint); + this->onDrawImageNine(image, center, dst, latticePaint.get()); + } else { + this->drawImageRect(image, dst, paint); + } +} + +void SkCanvas::drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(image); + if (dst.isEmpty()) { + return; + } + + SkIRect bounds; + Lattice latticePlusBounds = lattice; + if (!latticePlusBounds.fBounds) { + bounds = SkIRect::MakeWH(image->width(), image->height()); + latticePlusBounds.fBounds = &bounds; + } + + if (SkLatticeIter::Valid(image->width(), image->height(), latticePlusBounds)) { + LatticePaint latticePaint(paint); + this->onDrawImageLattice(image, latticePlusBounds, dst, latticePaint.get()); + } else { + this->drawImageRect(image, dst, paint); + } +} + +void SkCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar dx, SkScalar dy, const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (bitmap.drawsNothing()) { + return; + } + this->onDrawBitmap(bitmap, dx, dy, paint); +} + +void SkCanvas::drawBitmapRect(const SkBitmap& bitmap, const SkRect& src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (bitmap.drawsNothing() || dst.isEmpty() || src.isEmpty()) { + return; + } + this->onDrawBitmapRect(bitmap, &src, dst, paint, constraint); +} + +void SkCanvas::drawBitmapRect(const SkBitmap& bitmap, const SkIRect& isrc, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + this->drawBitmapRect(bitmap, SkRect::Make(isrc), dst, paint, constraint); +} + +void SkCanvas::drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint, + SrcRectConstraint constraint) { + this->drawBitmapRect(bitmap, SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst, paint, + constraint); +} + +void SkCanvas::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (bitmap.drawsNothing() || dst.isEmpty()) { + return; + } + if (SkLatticeIter::Valid(bitmap.width(), bitmap.height(), center)) { + LatticePaint latticePaint(paint); + this->onDrawBitmapNine(bitmap, center, dst, latticePaint.get()); + } else { + this->drawBitmapRect(bitmap, dst, paint); + } +} + +void SkCanvas::drawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (bitmap.drawsNothing() || dst.isEmpty()) { + return; + } + + SkIRect bounds; + Lattice latticePlusBounds = lattice; + if (!latticePlusBounds.fBounds) { + bounds = SkIRect::MakeWH(bitmap.width(), bitmap.height()); + latticePlusBounds.fBounds = &bounds; + } + + if (SkLatticeIter::Valid(bitmap.width(), bitmap.height(), latticePlusBounds)) { + LatticePaint latticePaint(paint); + this->onDrawBitmapLattice(bitmap, latticePlusBounds, dst, latticePaint.get()); + } else { + this->drawBitmapRect(bitmap, dst, paint); + } +} + +void SkCanvas::drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], + const SkColor colors[], int count, SkBlendMode mode, + const SkRect* cull, const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(atlas); + if (count <= 0) { + return; + } + SkASSERT(atlas); + SkASSERT(tex); + this->onDrawAtlas(atlas, xform, tex, colors, count, mode, cull, paint); +} + +void SkCanvas::drawAnnotation(const SkRect& rect, const char key[], SkData* value) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (key) { + this->onDrawAnnotation(rect, key, value); + } +} + +void SkCanvas::legacy_drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + if (src) { + this->drawImageRect(image, *src, dst, paint, constraint); + } else { + this->drawImageRect(image, SkRect::MakeIWH(image->width(), image->height()), + dst, paint, constraint); + } +} +void SkCanvas::legacy_drawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + if (src) { + this->drawBitmapRect(bitmap, *src, dst, paint, constraint); + } else { + this->drawBitmapRect(bitmap, SkRect::MakeIWH(bitmap.width(), bitmap.height()), + dst, paint, constraint); + } +} + +void SkCanvas::private_draw_shadow_rec(const SkPath& path, const SkDrawShadowRec& rec) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawShadowRec(path, rec); +} + +void SkCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + SkPaint paint; + const SkRect& pathBounds = path.getBounds(); + + DRAW_BEGIN(paint, &pathBounds) + while (iter.next()) { + iter.fDevice->drawShadow(path, rec); + } + DRAW_END +} + +void SkCanvas::experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + QuadAAFlags aaFlags, const SkColor4f& color, + SkBlendMode mode) { + TRACE_EVENT0("skia", TRACE_FUNC); + // Make sure the rect is sorted before passing it along + this->onDrawEdgeAAQuad(rect.makeSorted(), clip, aaFlags, color, mode); +} + +void SkCanvas::experimental_DrawEdgeAAImageSet(const ImageSetEntry imageSet[], int cnt, + const SkPoint dstClips[], + const SkMatrix preViewMatrices[], + const SkPaint* paint, + SrcRectConstraint constraint) { + TRACE_EVENT0("skia", TRACE_FUNC); + this->onDrawEdgeAAImageSet(imageSet, cnt, dstClips, preViewMatrices, paint, constraint); +} + +////////////////////////////////////////////////////////////////////////////// +// These are the virtual drawing methods +////////////////////////////////////////////////////////////////////////////// + +void SkCanvas::onDiscard() { + if (fSurfaceBase) { + fSurfaceBase->aboutToDraw(SkSurface::kDiscard_ContentChangeMode); + } +} + +void SkCanvas::onDrawPaint(const SkPaint& paint) { + this->internalDrawPaint(paint); +} + +void SkCanvas::internalDrawPaint(const SkPaint& paint) { + DRAW_BEGIN_CHECK_COMPLETE_OVERWRITE(paint, nullptr, false) + + while (iter.next()) { + iter.fDevice->drawPaint(draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + if ((long)count <= 0) { + return; + } + + SkRect r; + const SkRect* bounds = nullptr; + if (paint.canComputeFastBounds()) { + // special-case 2 points (common for drawing a single line) + if (2 == count) { + r.set(pts[0], pts[1]); + } else { + r.setBounds(pts, SkToInt(count)); + } + if (!r.isFinite()) { + return; + } + SkRect storage; + if (this->quickReject(paint.computeFastStrokeBounds(r, &storage))) { + return; + } + bounds = &r; + } + + SkASSERT(pts != nullptr); + + DRAW_BEGIN(paint, bounds) + + while (iter.next()) { + iter.fDevice->drawPoints(mode, count, pts, draw.paint()); + } + + DRAW_END +} + +static bool needs_autodrawlooper(SkCanvas* canvas, const SkPaint& paint) { + return paint.getImageFilter() != nullptr; +} + +void SkCanvas::onDrawRect(const SkRect& r, const SkPaint& paint) { + SkASSERT(r.isSorted()); + if (paint.canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint.computeFastBounds(r, &storage))) { + return; + } + } + + if (needs_autodrawlooper(this, paint)) { + DRAW_BEGIN_CHECK_COMPLETE_OVERWRITE(paint, &r, false) + + while (iter.next()) { + iter.fDevice->drawRect(r, draw.paint()); + } + + DRAW_END + } else if (!paint.nothingToDraw()) { + this->predrawNotify(&r, &paint, false); + SkDrawIter iter(this); + while (iter.next()) { + iter.fDevice->drawRect(r, paint); + } + } +} + +void SkCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + SkRect regionRect = SkRect::Make(region.getBounds()); + if (paint.canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint.computeFastBounds(regionRect, &storage))) { + return; + } + } + + DRAW_BEGIN(paint, ®ionRect) + + while (iter.next()) { + iter.fDevice->drawRegion(region, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawBehind(const SkPaint& paint) { + SkIRect bounds; + SkDeque::Iter iter(fMCStack, SkDeque::Iter::kBack_IterStart); + for (;;) { + const MCRec* rec = (const MCRec*)iter.prev(); + if (!rec) { + return; // no backimages, so nothing to draw + } + if (rec->fBackImage) { + bounds = SkIRect::MakeXYWH(rec->fBackImage->fLoc.fX, rec->fBackImage->fLoc.fY, + rec->fBackImage->fImage->width(), + rec->fBackImage->fImage->height()); + break; + } + } + + DRAW_BEGIN(paint, nullptr) + + while (iter.next()) { + SkBaseDevice* dev = iter.fDevice; + + dev->save(); + // We use clipRegion because it is already defined to operate in dev-space + // (i.e. ignores the ctm). However, it is going to first translate by -origin, + // but we don't want that, so we undo that before calling in. + SkRegion rgn(bounds.makeOffset(dev->fOrigin)); + dev->clipRegion(rgn, SkClipOp::kIntersect); + dev->drawPaint(draw.paint()); + dev->restore(fMCRec->fMatrix); + } + + DRAW_END +} + +void SkCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) { + SkASSERT(oval.isSorted()); + if (paint.canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint.computeFastBounds(oval, &storage))) { + return; + } + } + + DRAW_BEGIN(paint, &oval) + + while (iter.next()) { + iter.fDevice->drawOval(oval, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawArc(const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, + const SkPaint& paint) { + SkASSERT(oval.isSorted()); + if (paint.canComputeFastBounds()) { + SkRect storage; + // Note we're using the entire oval as the bounds. + if (this->quickReject(paint.computeFastBounds(oval, &storage))) { + return; + } + } + + DRAW_BEGIN(paint, &oval) + + while (iter.next()) { + iter.fDevice->drawArc(oval, startAngle, sweepAngle, useCenter, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { + if (paint.canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint.computeFastBounds(rrect.getBounds(), &storage))) { + return; + } + } + + if (rrect.isRect()) { + // call the non-virtual version + this->SkCanvas::drawRect(rrect.getBounds(), paint); + return; + } else if (rrect.isOval()) { + // call the non-virtual version + this->SkCanvas::drawOval(rrect.getBounds(), paint); + return; + } + + DRAW_BEGIN(paint, &rrect.getBounds()) + + while (iter.next()) { + iter.fDevice->drawRRect(rrect, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + if (paint.canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint.computeFastBounds(outer.getBounds(), &storage))) { + return; + } + } + + DRAW_BEGIN(paint, &outer.getBounds()) + + while (iter.next()) { + iter.fDevice->drawDRRect(outer, inner, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { + if (!path.isFinite()) { + return; + } + + const SkRect& pathBounds = path.getBounds(); + if (!path.isInverseFillType() && paint.canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint.computeFastBounds(pathBounds, &storage))) { + return; + } + } + + if (pathBounds.width() <= 0 && pathBounds.height() <= 0) { + if (path.isInverseFillType()) { + this->internalDrawPaint(paint); + return; + } + } + + DRAW_BEGIN(paint, &pathBounds) + + while (iter.next()) { + iter.fDevice->drawPath(path, draw.paint()); + } + + DRAW_END +} + +bool SkCanvas::canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const SkPaint& paint) { + if (!paint.getImageFilter()) { + return false; + } + + const SkMatrix& ctm = this->getTotalMatrix(); + if (!SkTreatAsSprite(ctm, SkISize::Make(w, h), paint)) { + return false; + } + + // Currently we can only use the filterSprite code if we are clipped to the bitmap's bounds. + // Once we can filter and the filter will return a result larger than itself, we should be + // able to remove this constraint. + // skbug.com/4526 + // + SkPoint pt; + ctm.mapXY(x, y, &pt); + SkIRect ir = SkIRect::MakeXYWH(SkScalarRoundToInt(pt.x()), SkScalarRoundToInt(pt.y()), w, h); + return ir.contains(fMCRec->fRasterClip.getBounds()); +} + +// Given storage for a real paint, and an optional paint parameter, clean-up the param (if non-null) +// given the drawing semantics for drawImage/bitmap (skbug.com/7804) and return it, or the original +// null. +static const SkPaint* init_image_paint(SkPaint* real, const SkPaint* paintParam) { + if (paintParam) { + *real = *paintParam; + real->setStyle(SkPaint::kFill_Style); + real->setPathEffect(nullptr); + paintParam = real; + } + return paintParam; +} + +void SkCanvas::onDrawImage(const SkImage* image, SkScalar x, SkScalar y, const SkPaint* paint) { + SkPaint realPaint; + paint = init_image_paint(&realPaint, paint); + + SkRect bounds = SkRect::MakeXYWH(x, y, + SkIntToScalar(image->width()), SkIntToScalar(image->height())); + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect tmp = bounds; + if (paint) { + paint->computeFastBounds(tmp, &tmp); + } + if (this->quickReject(tmp)) { + return; + } + } + // At this point we need a real paint object. If the caller passed null, then we should + // use realPaint (in its default state). If the caller did pass a paint, then we have copied + // (and modified) it in realPaint. Thus either way, "realPaint" is what we want to use. + paint = &realPaint; + + sk_sp special; + bool drawAsSprite = this->canDrawBitmapAsSprite(x, y, image->width(), image->height(), + *paint); + if (drawAsSprite && paint->getImageFilter()) { + special = this->getDevice()->makeSpecial(image); + if (!special) { + drawAsSprite = false; + } + } + + DRAW_BEGIN_DRAWBITMAP(*paint, drawAsSprite, &bounds) + + while (iter.next()) { + const SkPaint& pnt = draw.paint(); + if (special) { + SkPoint pt; + iter.fDevice->ctm().mapXY(x, y, &pt); + iter.fDevice->drawSpecial(special.get(), + SkScalarRoundToInt(pt.fX), + SkScalarRoundToInt(pt.fY), pnt, + nullptr, SkMatrix::I()); + } else { + iter.fDevice->drawImageRect( + image, nullptr, SkRect::MakeXYWH(x, y, image->width(), image->height()), pnt, + kStrict_SrcRectConstraint); + } + } + + DRAW_END +} + +void SkCanvas::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + SkPaint realPaint; + paint = init_image_paint(&realPaint, paint); + + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect storage = dst; + if (paint) { + paint->computeFastBounds(dst, &storage); + } + if (this->quickReject(storage)) { + return; + } + } + paint = &realPaint; + + DRAW_BEGIN_CHECK_COMPLETE_OVERWRITE(*paint, &dst, image->isOpaque()) + + while (iter.next()) { + iter.fDevice->drawImageRect(image, src, dst, draw.paint(), constraint); + } + + DRAW_END +} + +void SkCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y, const SkPaint* paint) { + SkDEBUGCODE(bitmap.validate();) + + if (bitmap.drawsNothing()) { + return; + } + + SkPaint realPaint; + init_image_paint(&realPaint, paint); + paint = &realPaint; + + SkRect bounds; + bitmap.getBounds(&bounds); + bounds.offset(x, y); + bool canFastBounds = paint->canComputeFastBounds(); + if (canFastBounds) { + SkRect storage; + if (this->quickReject(paint->computeFastBounds(bounds, &storage))) { + return; + } + } + + sk_sp special; + bool drawAsSprite = canFastBounds && this->canDrawBitmapAsSprite(x, y, bitmap.width(), + bitmap.height(), *paint); + if (drawAsSprite && paint->getImageFilter()) { + special = this->getDevice()->makeSpecial(bitmap); + if (!special) { + drawAsSprite = false; + } + } + + DRAW_BEGIN_DRAWBITMAP(*paint, drawAsSprite, &bounds) + + while (iter.next()) { + const SkPaint& pnt = draw.paint(); + if (special) { + SkPoint pt; + iter.fDevice->ctm().mapXY(x, y, &pt); + iter.fDevice->drawSpecial(special.get(), + SkScalarRoundToInt(pt.fX), + SkScalarRoundToInt(pt.fY), pnt, + nullptr, SkMatrix::I()); + } else { + SkRect fullImage = SkRect::MakeWH(bitmap.width(), bitmap.height()); + iter.fDevice->drawBitmapRect(bitmap, &fullImage, fullImage.makeOffset(x, y), pnt, + kStrict_SrcRectConstraint); + } + } + + DRAW_END +} + +// this one is non-virtual, so it can be called safely by other canvas apis +void SkCanvas::internalDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, + const SkRect& dst, const SkPaint* paint, + SrcRectConstraint constraint) { + if (bitmap.drawsNothing() || dst.isEmpty()) { + return; + } + + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint ? paint->computeFastBounds(dst, &storage) : dst)) { + return; + } + } + + SkLazyPaint lazy; + if (nullptr == paint) { + paint = lazy.init(); + } + + DRAW_BEGIN_CHECK_COMPLETE_OVERWRITE(*paint, &dst, bitmap.isOpaque()) + + while (iter.next()) { + iter.fDevice->drawBitmapRect(bitmap, src, dst, draw.paint(), constraint); + } + + DRAW_END +} + +void SkCanvas::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + SkDEBUGCODE(bitmap.validate();) + this->internalDrawBitmapRect(bitmap, src, dst, paint, constraint); +} + +void SkCanvas::onDrawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + SkPaint realPaint; + paint = init_image_paint(&realPaint, paint); + + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint ? paint->computeFastBounds(dst, &storage) : dst)) { + return; + } + } + paint = &realPaint; + + DRAW_BEGIN(*paint, &dst) + + while (iter.next()) { + iter.fDevice->drawImageNine(image, center, dst, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + SkDEBUGCODE(bitmap.validate();) + SkPaint realPaint; + paint = init_image_paint(&realPaint, paint); + + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint ? paint->computeFastBounds(dst, &storage) : dst)) { + return; + } + } + paint = &realPaint; + + DRAW_BEGIN(*paint, &dst) + + while (iter.next()) { + iter.fDevice->drawBitmapNine(bitmap, center, dst, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint) { + SkPaint realPaint; + paint = init_image_paint(&realPaint, paint); + + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint ? paint->computeFastBounds(dst, &storage) : dst)) { + return; + } + } + paint = &realPaint; + + DRAW_BEGIN(*paint, &dst) + + while (iter.next()) { + iter.fDevice->drawImageLattice(image, lattice, dst, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + SkPaint realPaint; + paint = init_image_paint(&realPaint, paint); + + if (nullptr == paint || paint->canComputeFastBounds()) { + SkRect storage; + if (this->quickReject(paint ? paint->computeFastBounds(dst, &storage) : dst)) { + return; + } + } + paint = &realPaint; + + DRAW_BEGIN(*paint, &dst) + + while (iter.next()) { + iter.fDevice->drawBitmapLattice(bitmap, lattice, dst, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + SkRect storage; + const SkRect* bounds = nullptr; + if (paint.canComputeFastBounds()) { + storage = blob->bounds().makeOffset(x, y); + SkRect tmp; + if (this->quickReject(paint.computeFastBounds(storage, &tmp))) { + return; + } + bounds = &storage; + } + + // We cannot filter in the looper as we normally do, because the paint is + // incomplete at this point (text-related attributes are embedded within blob run paints). + DRAW_BEGIN(paint, bounds) + + while (iter.next()) { + fScratchGlyphRunBuilder->drawTextBlob(draw.paint(), *blob, {x, y}, iter.fDevice); + } + + DRAW_END +} + +// These call the (virtual) onDraw... method +void SkCanvas::drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding, + SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (byteLength) { + sk_msan_assert_initialized(text, SkTAddOffset(text, byteLength)); + this->drawTextBlob(SkTextBlob::MakeFromText(text, byteLength, font, encoding), x, y, paint); + } +} + +void SkCanvas::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(blob); + RETURN_ON_FALSE(blob->bounds().makeOffset(x, y).isFinite()); + this->onDrawTextBlob(blob, x, y, paint); +} + +void SkCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode bmode, const SkPaint& paint) { + DRAW_BEGIN(paint, nullptr) + + while (iter.next()) { + // In the common case of one iteration we could std::move vertices here. + iter.fDevice->drawVertices(vertices, bones, boneCount, bmode, draw.paint()); + } + + DRAW_END +} + +void SkCanvas::drawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode bmode, + const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (nullptr == cubics) { + return; + } + + this->onDrawPatch(cubics, colors, texCoords, bmode, paint); +} + +void SkCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode bmode, + const SkPaint& paint) { + // Since a patch is always within the convex hull of the control points, we discard it when its + // bounding rectangle is completely outside the current clip. + SkRect bounds; + bounds.setBounds(cubics, SkPatchUtils::kNumCtrlPts); + if (this->quickReject(bounds)) { + return; + } + + DRAW_BEGIN(paint, nullptr) + + while (iter.next()) { + iter.fDevice->drawPatch(cubics, colors, texCoords, bmode, paint); + } + + DRAW_END +} + +void SkCanvas::drawDrawable(SkDrawable* dr, SkScalar x, SkScalar y) { +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + TRACE_EVENT0("skia", TRACE_FUNC); +#endif + RETURN_ON_NULL(dr); + if (x || y) { + SkMatrix matrix = SkMatrix::MakeTrans(x, y); + this->onDrawDrawable(dr, &matrix); + } else { + this->onDrawDrawable(dr, nullptr); + } +} + +void SkCanvas::drawDrawable(SkDrawable* dr, const SkMatrix* matrix) { +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + TRACE_EVENT0("skia", TRACE_FUNC); +#endif + RETURN_ON_NULL(dr); + if (matrix && matrix->isIdentity()) { + matrix = nullptr; + } + this->onDrawDrawable(dr, matrix); +} + +void SkCanvas::onDrawDrawable(SkDrawable* dr, const SkMatrix* matrix) { + // drawable bounds are no longer reliable (e.g. android displaylist) + // so don't use them for quick-reject + this->getDevice()->drawDrawable(dr, matrix, this); +} + +void SkCanvas::onDrawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], + const SkColor colors[], int count, SkBlendMode bmode, + const SkRect* cull, const SkPaint* paint) { + if (cull && this->quickReject(*cull)) { + return; + } + + SkPaint pnt; + if (paint) { + pnt = *paint; + } + + DRAW_BEGIN(pnt, nullptr) + while (iter.next()) { + iter.fDevice->drawAtlas(atlas, xform, tex, colors, count, bmode, pnt); + } + DRAW_END +} + +void SkCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) { + SkASSERT(key); + + SkPaint paint; + DRAW_BEGIN(paint, nullptr) + while (iter.next()) { + iter.fDevice->drawAnnotation(rect, key, value); + } + DRAW_END +} + +void SkCanvas::onDrawEdgeAAQuad(const SkRect& r, const SkPoint clip[4], QuadAAFlags edgeAA, + const SkColor4f& color, SkBlendMode mode) { + SkASSERT(r.isSorted()); + + // If this used a paint, it would be a filled color with blend mode, which does not + // need to use an autodraw loop, so use SkDrawIter directly. + if (this->quickReject(r)) { + return; + } + + this->predrawNotify(&r, nullptr, false); + SkDrawIter iter(this); + while(iter.next()) { + iter.fDevice->drawEdgeAAQuad(r, clip, edgeAA, color, mode); + } +} + +void SkCanvas::onDrawEdgeAAImageSet(const ImageSetEntry imageSet[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint* paint, SrcRectConstraint constraint) { + if (count <= 0) { + // Nothing to draw + return; + } + + SkPaint realPaint; + init_image_paint(&realPaint, paint); + + // We could calculate the set's dstRect union to always check quickReject(), but we can't reject + // individual entries and Chromium's occlusion culling already makes it likely that at least one + // entry will be visible. So, we only calculate the draw bounds when it's trivial (count == 1), + // or we need it for the autolooper (since it greatly improves image filter perf). + bool needsAutoLooper = needs_autodrawlooper(this, realPaint); + bool setBoundsValid = count == 1 || needsAutoLooper; + SkRect setBounds = imageSet[0].fDstRect; + if (imageSet[0].fMatrixIndex >= 0) { + // Account for the per-entry transform that is applied prior to the CTM when drawing + preViewMatrices[imageSet[0].fMatrixIndex].mapRect(&setBounds); + } + if (needsAutoLooper) { + for (int i = 1; i < count; ++i) { + SkRect entryBounds = imageSet[i].fDstRect; + if (imageSet[i].fMatrixIndex >= 0) { + preViewMatrices[imageSet[i].fMatrixIndex].mapRect(&entryBounds); + } + setBounds.joinPossiblyEmptyRect(entryBounds); + } + } + + // If we happen to have the draw bounds, though, might as well check quickReject(). + if (setBoundsValid && realPaint.canComputeFastBounds()) { + SkRect tmp; + if (this->quickReject(realPaint.computeFastBounds(setBounds, &tmp))) { + return; + } + } + + if (needsAutoLooper) { + SkASSERT(setBoundsValid); + DRAW_BEGIN(realPaint, &setBounds) + while (iter.next()) { + iter.fDevice->drawEdgeAAImageSet( + imageSet, count, dstClips, preViewMatrices, draw.paint(), constraint); + } + DRAW_END + } else { + this->predrawNotify(); + SkDrawIter iter(this); + while(iter.next()) { + iter.fDevice->drawEdgeAAImageSet( + imageSet, count, dstClips, preViewMatrices, realPaint, constraint); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// These methods are NOT virtual, and therefore must call back into virtual +// methods, rather than actually drawing themselves. +////////////////////////////////////////////////////////////////////////////// + +void SkCanvas::drawColor(SkColor c, SkBlendMode mode) { + SkPaint paint; + paint.setColor(c); + paint.setBlendMode(mode); + this->drawPaint(paint); +} + +void SkCanvas::drawPoint(SkScalar x, SkScalar y, const SkPaint& paint) { + const SkPoint pt = { x, y }; + this->drawPoints(kPoints_PointMode, 1, &pt, paint); +} + +void SkCanvas::drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint) { + SkPoint pts[2]; + pts[0].set(x0, y0); + pts[1].set(x1, y1); + this->drawPoints(kLines_PointMode, 2, pts, paint); +} + +void SkCanvas::drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint) { + if (radius < 0) { + radius = 0; + } + + SkRect r; + r.setLTRB(cx - radius, cy - radius, cx + radius, cy + radius); + this->drawOval(r, paint); +} + +void SkCanvas::drawRoundRect(const SkRect& r, SkScalar rx, SkScalar ry, + const SkPaint& paint) { + if (rx > 0 && ry > 0) { + SkRRect rrect; + rrect.setRectXY(r, rx, ry); + this->drawRRect(rrect, paint); + } else { + this->drawRect(r, paint); + } +} + +void SkCanvas::drawArc(const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, + const SkPaint& paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (oval.isEmpty() || !sweepAngle) { + return; + } + this->onDrawArc(oval, startAngle, sweepAngle, useCenter, paint); +} + +/////////////////////////////////////////////////////////////////////////////// +#ifdef SK_DISABLE_SKPICTURE +void SkCanvas::drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) {} + + +void SkCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) {} +#else +/** + * This constant is trying to balance the speed of ref'ing a subpicture into a parent picture, + * against the playback cost of recursing into the subpicture to get at its actual ops. + * + * For now we pick a conservatively small value, though measurement (and other heuristics like + * the type of ops contained) may justify changing this value. + */ +#define kMaxPictureOpsToUnrollInsteadOfRef 1 + +void SkCanvas::drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) { + TRACE_EVENT0("skia", TRACE_FUNC); + RETURN_ON_NULL(picture); + + if (matrix && matrix->isIdentity()) { + matrix = nullptr; + } + if (picture->approximateOpCount() <= kMaxPictureOpsToUnrollInsteadOfRef) { + SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect()); + picture->playback(this); + } else { + this->onDrawPicture(picture, matrix, paint); + } +} + +void SkCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) { + if (!paint || paint->canComputeFastBounds()) { + SkRect bounds = picture->cullRect(); + if (paint) { + paint->computeFastBounds(bounds, &bounds); + } + if (matrix) { + matrix->mapRect(&bounds); + } + if (this->quickReject(bounds)) { + return; + } + } + + SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect()); + picture->playback(this); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SkCanvas::LayerIter::LayerIter(SkCanvas* canvas) { + static_assert(sizeof(fStorage) >= sizeof(SkDrawIter), "fStorage_too_small"); + + SkASSERT(canvas); + + fImpl = new (fStorage) SkDrawIter(canvas); + fDone = !fImpl->next(); +} + +SkCanvas::LayerIter::~LayerIter() { + fImpl->~SkDrawIter(); +} + +void SkCanvas::LayerIter::next() { + fDone = !fImpl->next(); +} + +SkBaseDevice* SkCanvas::LayerIter::device() const { + return fImpl->fDevice; +} + +const SkMatrix& SkCanvas::LayerIter::matrix() const { + return fImpl->fDevice->ctm(); +} + +const SkPaint& SkCanvas::LayerIter::paint() const { + const SkPaint* paint = fImpl->getPaint(); + if (nullptr == paint) { + paint = &fDefaultPaint; + } + return *paint; +} + +SkIRect SkCanvas::LayerIter::clipBounds() const { + return fImpl->fDevice->getGlobalBounds(); +} + +int SkCanvas::LayerIter::x() const { return fImpl->getX(); } +int SkCanvas::LayerIter::y() const { return fImpl->getY(); } + +/////////////////////////////////////////////////////////////////////////////// + +SkCanvas::ImageSetEntry::ImageSetEntry() = default; +SkCanvas::ImageSetEntry::~ImageSetEntry() = default; +SkCanvas::ImageSetEntry::ImageSetEntry(const ImageSetEntry&) = default; +SkCanvas::ImageSetEntry& SkCanvas::ImageSetEntry::operator=(const ImageSetEntry&) = default; + +SkCanvas::ImageSetEntry::ImageSetEntry(sk_sp image, const SkRect& srcRect, + const SkRect& dstRect, int matrixIndex, float alpha, + unsigned aaFlags, bool hasClip) + : fImage(std::move(image)) + , fSrcRect(srcRect) + , fDstRect(dstRect) + , fMatrixIndex(matrixIndex) + , fAlpha(alpha) + , fAAFlags(aaFlags) + , fHasClip(hasClip) {} + +SkCanvas::ImageSetEntry::ImageSetEntry(sk_sp image, const SkRect& srcRect, + const SkRect& dstRect, float alpha, unsigned aaFlags) + : fImage(std::move(image)) + , fSrcRect(srcRect) + , fDstRect(dstRect) + , fAlpha(alpha) + , fAAFlags(aaFlags) {} + +/////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr SkCanvas::MakeRasterDirect(const SkImageInfo& info, void* pixels, + size_t rowBytes, const SkSurfaceProps* props) { + if (!SkSurfaceValidateRasterInfo(info, rowBytes)) { + return nullptr; + } + + SkBitmap bitmap; + if (!bitmap.installPixels(info, pixels, rowBytes)) { + return nullptr; + } + + return props ? + skstd::make_unique(bitmap, *props) : + skstd::make_unique(bitmap); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkNoDrawCanvas::SkNoDrawCanvas(int width, int height) + : INHERITED(SkIRect::MakeWH(width, height)) {} + +SkNoDrawCanvas::SkNoDrawCanvas(const SkIRect& bounds) + : INHERITED(bounds) {} + +SkNoDrawCanvas::SkNoDrawCanvas(sk_sp device) + : INHERITED(device) {} + +SkCanvas::SaveLayerStrategy SkNoDrawCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) { + (void)this->INHERITED::getSaveLayerStrategy(rec); + return kNoLayer_SaveLayerStrategy; +} + +bool SkNoDrawCanvas::onDoSaveBehind(const SkRect*) { + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +static_assert((int)SkRegion::kDifference_Op == (int)kDifference_SkClipOp, ""); +static_assert((int)SkRegion::kIntersect_Op == (int)kIntersect_SkClipOp, ""); +static_assert((int)SkRegion::kUnion_Op == (int)kUnion_SkClipOp, ""); +static_assert((int)SkRegion::kXOR_Op == (int)kXOR_SkClipOp, ""); +static_assert((int)SkRegion::kReverseDifference_Op == (int)kReverseDifference_SkClipOp, ""); +static_assert((int)SkRegion::kReplace_Op == (int)kReplace_SkClipOp, ""); + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkRasterHandleAllocator::Handle SkCanvas::accessTopRasterHandle() const { + if (fAllocator && fMCRec->fTopLayer->fDevice) { + const auto& dev = fMCRec->fTopLayer->fDevice; + SkRasterHandleAllocator::Handle handle = dev->getRasterHandle(); + SkIPoint origin = dev->getOrigin(); + SkMatrix ctm = this->getTotalMatrix(); + ctm.preTranslate(SkIntToScalar(-origin.x()), SkIntToScalar(-origin.y())); + + SkIRect clip = fMCRec->fRasterClip.getBounds(); + clip.offset(-origin.x(), -origin.y()); + if (!clip.intersect({0, 0, dev->width(), dev->height()})) { + clip.setEmpty(); + } + + fAllocator->updateHandle(handle, ctm, clip); + return handle; + } + return nullptr; +} + +static bool install(SkBitmap* bm, const SkImageInfo& info, + const SkRasterHandleAllocator::Rec& rec) { + return bm->installPixels(info, rec.fPixels, rec.fRowBytes, rec.fReleaseProc, rec.fReleaseCtx); +} + +SkRasterHandleAllocator::Handle SkRasterHandleAllocator::allocBitmap(const SkImageInfo& info, + SkBitmap* bm) { + SkRasterHandleAllocator::Rec rec; + if (!this->allocHandle(info, &rec) || !install(bm, info, rec)) { + return nullptr; + } + return rec.fHandle; +} + +std::unique_ptr +SkRasterHandleAllocator::MakeCanvas(std::unique_ptr alloc, + const SkImageInfo& info, const Rec* rec) { + if (!alloc || !SkSurfaceValidateRasterInfo(info, rec ? rec->fRowBytes : kIgnoreRowBytesValue)) { + return nullptr; + } + + SkBitmap bm; + Handle hndl; + + if (rec) { + hndl = install(&bm, info, *rec) ? rec->fHandle : nullptr; + } else { + hndl = alloc->allocBitmap(info, &bm); + } + return hndl ? std::unique_ptr(new SkCanvas(bm, std::move(alloc), hndl)) : nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/skia/skia/src/core/SkCanvasPriv.cpp b/gfx/skia/skia/src/core/SkCanvasPriv.cpp new file mode 100644 index 0000000000..cf90419d9f --- /dev/null +++ b/gfx/skia/skia/src/core/SkCanvasPriv.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriter32.h" + +SkAutoCanvasMatrixPaint::SkAutoCanvasMatrixPaint(SkCanvas* canvas, const SkMatrix* matrix, + const SkPaint* paint, const SkRect& bounds) +: fCanvas(canvas) +, fSaveCount(canvas->getSaveCount()) +{ + if (paint) { + SkRect newBounds = bounds; + if (matrix) { + matrix->mapRect(&newBounds); + } + canvas->saveLayer(&newBounds, paint); + } else if (matrix) { + canvas->save(); + } + + if (matrix) { + canvas->concat(*matrix); + } +} + +SkAutoCanvasMatrixPaint::~SkAutoCanvasMatrixPaint() { + fCanvas->restoreToCount(fSaveCount); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkCanvasPriv::ReadLattice(SkReadBuffer& buffer, SkCanvas::Lattice* lattice) { + lattice->fXCount = buffer.readInt(); + lattice->fXDivs = buffer.skipT(lattice->fXCount); + lattice->fYCount = buffer.readInt(); + lattice->fYDivs = buffer.skipT(lattice->fYCount); + int flagCount = buffer.readInt(); + lattice->fRectTypes = nullptr; + lattice->fColors = nullptr; + if (flagCount) { + lattice->fRectTypes = buffer.skipT(flagCount); + lattice->fColors = buffer.skipT(flagCount); + } + lattice->fBounds = buffer.skipT(); + return buffer.isValid(); +} + +size_t SkCanvasPriv::WriteLattice(void* buffer, const SkCanvas::Lattice& lattice) { + int flagCount = lattice.fRectTypes ? (lattice.fXCount + 1) * (lattice.fYCount + 1) : 0; + + const size_t size = (1 + lattice.fXCount + 1 + lattice.fYCount + 1) * sizeof(int32_t) + + SkAlign4(flagCount * sizeof(SkCanvas::Lattice::RectType)) + + SkAlign4(flagCount * sizeof(SkColor)) + + sizeof(SkIRect); + + if (buffer) { + SkWriter32 writer(buffer, size); + writer.write32(lattice.fXCount); + writer.write(lattice.fXDivs, lattice.fXCount * sizeof(uint32_t)); + writer.write32(lattice.fYCount); + writer.write(lattice.fYDivs, lattice.fYCount * sizeof(uint32_t)); + writer.write32(flagCount); + writer.writePad(lattice.fRectTypes, flagCount * sizeof(uint8_t)); + writer.write(lattice.fColors, flagCount * sizeof(SkColor)); + SkASSERT(lattice.fBounds); + writer.write(lattice.fBounds, sizeof(SkIRect)); + SkASSERT(writer.bytesWritten() == size); + } + return size; +}; + +void SkCanvasPriv::WriteLattice(SkWriteBuffer& buffer, const SkCanvas::Lattice& lattice) { + const size_t size = WriteLattice(nullptr, lattice); + SkAutoSMalloc<1024> storage(size); + WriteLattice(storage.get(), lattice); + buffer.writePad32(storage.get(), size); +} + +void SkCanvasPriv::GetDstClipAndMatrixCounts(const SkCanvas::ImageSetEntry set[], int count, + int* totalDstClipCount, int* totalMatrixCount) { + int dstClipCount = 0; + int maxMatrixIndex = -1; + for (int i = 0; i < count; ++i) { + dstClipCount += 4 * set[i].fHasClip; + if (set[i].fMatrixIndex > maxMatrixIndex) { + maxMatrixIndex = set[i].fMatrixIndex; + } + } + + *totalDstClipCount = dstClipCount; + *totalMatrixCount = maxMatrixIndex + 1; +} diff --git a/gfx/skia/skia/src/core/SkCanvasPriv.h b/gfx/skia/skia/src/core/SkCanvasPriv.h new file mode 100644 index 0000000000..5bef882900 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCanvasPriv.h @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCanvasPriv_DEFINED +#define SkCanvasPriv_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/private/SkNoncopyable.h" + +class SkReadBuffer; +class SkWriteBuffer; + +class SkAutoCanvasMatrixPaint : SkNoncopyable { +public: + SkAutoCanvasMatrixPaint(SkCanvas*, const SkMatrix*, const SkPaint*, const SkRect& bounds); + ~SkAutoCanvasMatrixPaint(); + +private: + SkCanvas* fCanvas; + int fSaveCount; +}; + +class SkCanvasPriv { +public: + enum { + kDontClipToLayer_SaveLayerFlag = SkCanvas::kDontClipToLayer_PrivateSaveLayerFlag, + }; + + // The lattice has pointers directly into the readbuffer + static bool ReadLattice(SkReadBuffer&, SkCanvas::Lattice*); + + static void WriteLattice(SkWriteBuffer&, const SkCanvas::Lattice&); + + // return the byte-size of the lattice, even if the buffer is null + // storage must be 4-byte aligned + static size_t WriteLattice(void* storage, const SkCanvas::Lattice&); + + static SkCanvas::SaveLayerFlags LegacySaveFlagsToSaveLayerFlags(uint32_t legacySaveFlags); + + static int SaveBehind(SkCanvas* canvas, const SkRect* subset) { + return canvas->only_axis_aligned_saveBehind(subset); + } + static void DrawBehind(SkCanvas* canvas, const SkPaint& paint) { + canvas->drawClippedToSaveBehind(paint); + } + + // The experimental_DrawEdgeAAImageSet API accepts separate dstClips and preViewMatrices arrays, + // where entries refer into them, but no explicit size is provided. Given a set of entries, + // computes the minimum length for these arrays that would provide index access errors. + static void GetDstClipAndMatrixCounts(const SkCanvas::ImageSetEntry set[], int count, + int* totalDstClipCount, int* totalMatrixCount); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkClipOpPriv.h b/gfx/skia/skia/src/core/SkClipOpPriv.h new file mode 100644 index 0000000000..02c8ffae5c --- /dev/null +++ b/gfx/skia/skia/src/core/SkClipOpPriv.h @@ -0,0 +1,21 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkClipOpPriv_DEFINED +#define SkClipOpPriv_DEFINED + +#include "include/core/SkClipOp.h" + +const SkClipOp kDifference_SkClipOp = SkClipOp::kDifference; +const SkClipOp kIntersect_SkClipOp = SkClipOp::kIntersect; + +const SkClipOp kUnion_SkClipOp = (SkClipOp)2; +const SkClipOp kXOR_SkClipOp = (SkClipOp)3; +const SkClipOp kReverseDifference_SkClipOp = (SkClipOp)4; +const SkClipOp kReplace_SkClipOp = (SkClipOp)5; + +#endif diff --git a/gfx/skia/skia/src/core/SkClipStack.cpp b/gfx/skia/skia/src/core/SkClipStack.cpp new file mode 100644 index 0000000000..9219f67b04 --- /dev/null +++ b/gfx/skia/skia/src/core/SkClipStack.cpp @@ -0,0 +1,1100 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkPath.h" +#include "include/pathops/SkPathOps.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkClipStack.h" +#include +#include + +#if SK_SUPPORT_GPU +#include "src/gpu/GrProxyProvider.h" +#endif + +SkClipStack::Element::Element(const Element& that) { + switch (that.getDeviceSpaceType()) { + case DeviceSpaceType::kEmpty: + fDeviceSpaceRRect.setEmpty(); + fDeviceSpacePath.reset(); + break; + case DeviceSpaceType::kRect: // Rect uses rrect + case DeviceSpaceType::kRRect: + fDeviceSpacePath.reset(); + fDeviceSpaceRRect = that.fDeviceSpaceRRect; + break; + case DeviceSpaceType::kPath: + fDeviceSpacePath.set(that.getDeviceSpacePath()); + break; + } + + fSaveCount = that.fSaveCount; + fOp = that.fOp; + fDeviceSpaceType = that.fDeviceSpaceType; + fDoAA = that.fDoAA; + fFiniteBoundType = that.fFiniteBoundType; + fFiniteBound = that.fFiniteBound; + fIsIntersectionOfRects = that.fIsIntersectionOfRects; + fGenID = that.fGenID; +} + +SkClipStack::Element::~Element() { +#if SK_SUPPORT_GPU + for (int i = 0; i < fKeysToInvalidate.count(); ++i) { + fProxyProvider->processInvalidUniqueKey(fKeysToInvalidate[i], nullptr, + GrProxyProvider::InvalidateGPUResource::kYes); + } +#endif +} + +bool SkClipStack::Element::operator== (const Element& element) const { + if (this == &element) { + return true; + } + if (fOp != element.fOp || fDeviceSpaceType != element.fDeviceSpaceType || + fDoAA != element.fDoAA || fSaveCount != element.fSaveCount) { + return false; + } + switch (fDeviceSpaceType) { + case DeviceSpaceType::kPath: + return this->getDeviceSpacePath() == element.getDeviceSpacePath(); + case DeviceSpaceType::kRRect: + return fDeviceSpaceRRect == element.fDeviceSpaceRRect; + case DeviceSpaceType::kRect: + return this->getDeviceSpaceRect() == element.getDeviceSpaceRect(); + case DeviceSpaceType::kEmpty: + return true; + default: + SkDEBUGFAIL("Unexpected type."); + return false; + } +} + +const SkRect& SkClipStack::Element::getBounds() const { + static const SkRect kEmpty = {0, 0, 0, 0}; + switch (fDeviceSpaceType) { + case DeviceSpaceType::kRect: // fallthrough + case DeviceSpaceType::kRRect: + return fDeviceSpaceRRect.getBounds(); + case DeviceSpaceType::kPath: + return fDeviceSpacePath.get()->getBounds(); + case DeviceSpaceType::kEmpty: + return kEmpty; + default: + SkDEBUGFAIL("Unexpected type."); + return kEmpty; + } +} + +bool SkClipStack::Element::contains(const SkRect& rect) const { + switch (fDeviceSpaceType) { + case DeviceSpaceType::kRect: + return this->getDeviceSpaceRect().contains(rect); + case DeviceSpaceType::kRRect: + return fDeviceSpaceRRect.contains(rect); + case DeviceSpaceType::kPath: + return fDeviceSpacePath.get()->conservativelyContainsRect(rect); + case DeviceSpaceType::kEmpty: + return false; + default: + SkDEBUGFAIL("Unexpected type."); + return false; + } +} + +bool SkClipStack::Element::contains(const SkRRect& rrect) const { + switch (fDeviceSpaceType) { + case DeviceSpaceType::kRect: + return this->getDeviceSpaceRect().contains(rrect.getBounds()); + case DeviceSpaceType::kRRect: + // We don't currently have a generalized rrect-rrect containment. + return fDeviceSpaceRRect.contains(rrect.getBounds()) || rrect == fDeviceSpaceRRect; + case DeviceSpaceType::kPath: + return fDeviceSpacePath.get()->conservativelyContainsRect(rrect.getBounds()); + case DeviceSpaceType::kEmpty: + return false; + default: + SkDEBUGFAIL("Unexpected type."); + return false; + } +} + +void SkClipStack::Element::invertShapeFillType() { + switch (fDeviceSpaceType) { + case DeviceSpaceType::kRect: + fDeviceSpacePath.init(); + fDeviceSpacePath.get()->addRect(this->getDeviceSpaceRect()); + fDeviceSpacePath.get()->setFillType(SkPath::kInverseEvenOdd_FillType); + fDeviceSpaceType = DeviceSpaceType::kPath; + break; + case DeviceSpaceType::kRRect: + fDeviceSpacePath.init(); + fDeviceSpacePath.get()->addRRect(fDeviceSpaceRRect); + fDeviceSpacePath.get()->setFillType(SkPath::kInverseEvenOdd_FillType); + fDeviceSpaceType = DeviceSpaceType::kPath; + break; + case DeviceSpaceType::kPath: + fDeviceSpacePath.get()->toggleInverseFillType(); + break; + case DeviceSpaceType::kEmpty: + // Should this set to an empty, inverse filled path? + break; + } +} + +void SkClipStack::Element::initCommon(int saveCount, SkClipOp op, bool doAA) { + fSaveCount = saveCount; + fOp = op; + fDoAA = doAA; + // A default of inside-out and empty bounds means the bounds are effectively void as it + // indicates that nothing is known to be outside the clip. + fFiniteBoundType = kInsideOut_BoundsType; + fFiniteBound.setEmpty(); + fIsIntersectionOfRects = false; + fGenID = kInvalidGenID; +} + +void SkClipStack::Element::initRect(int saveCount, const SkRect& rect, const SkMatrix& m, + SkClipOp op, bool doAA) { + if (m.rectStaysRect()) { + SkRect devRect; + m.mapRect(&devRect, rect); + fDeviceSpaceRRect.setRect(devRect); + fDeviceSpaceType = DeviceSpaceType::kRect; + this->initCommon(saveCount, op, doAA); + return; + } + SkPath path; + path.addRect(rect); + path.setIsVolatile(true); + this->initAsPath(saveCount, path, m, op, doAA); +} + +void SkClipStack::Element::initRRect(int saveCount, const SkRRect& rrect, const SkMatrix& m, + SkClipOp op, bool doAA) { + if (rrect.transform(m, &fDeviceSpaceRRect)) { + SkRRect::Type type = fDeviceSpaceRRect.getType(); + if (SkRRect::kRect_Type == type || SkRRect::kEmpty_Type == type) { + fDeviceSpaceType = DeviceSpaceType::kRect; + } else { + fDeviceSpaceType = DeviceSpaceType::kRRect; + } + this->initCommon(saveCount, op, doAA); + return; + } + SkPath path; + path.addRRect(rrect); + path.setIsVolatile(true); + this->initAsPath(saveCount, path, m, op, doAA); +} + +void SkClipStack::Element::initPath(int saveCount, const SkPath& path, const SkMatrix& m, + SkClipOp op, bool doAA) { + if (!path.isInverseFillType()) { + SkRect r; + if (path.isRect(&r)) { + this->initRect(saveCount, r, m, op, doAA); + return; + } + SkRect ovalRect; + if (path.isOval(&ovalRect)) { + SkRRect rrect; + rrect.setOval(ovalRect); + this->initRRect(saveCount, rrect, m, op, doAA); + return; + } + } + this->initAsPath(saveCount, path, m, op, doAA); +} + +void SkClipStack::Element::initAsPath(int saveCount, const SkPath& path, const SkMatrix& m, + SkClipOp op, bool doAA) { + path.transform(m, fDeviceSpacePath.init()); + fDeviceSpacePath.get()->setIsVolatile(true); + fDeviceSpaceType = DeviceSpaceType::kPath; + this->initCommon(saveCount, op, doAA); +} + +void SkClipStack::Element::asDeviceSpacePath(SkPath* path) const { + switch (fDeviceSpaceType) { + case DeviceSpaceType::kEmpty: + path->reset(); + break; + case DeviceSpaceType::kRect: + path->reset(); + path->addRect(this->getDeviceSpaceRect()); + break; + case DeviceSpaceType::kRRect: + path->reset(); + path->addRRect(fDeviceSpaceRRect); + break; + case DeviceSpaceType::kPath: + *path = *fDeviceSpacePath.get(); + break; + } + path->setIsVolatile(true); +} + +void SkClipStack::Element::setEmpty() { + fDeviceSpaceType = DeviceSpaceType::kEmpty; + fFiniteBound.setEmpty(); + fFiniteBoundType = kNormal_BoundsType; + fIsIntersectionOfRects = false; + fDeviceSpaceRRect.setEmpty(); + fDeviceSpacePath.reset(); + fGenID = kEmptyGenID; + SkDEBUGCODE(this->checkEmpty();) +} + +void SkClipStack::Element::checkEmpty() const { + SkASSERT(fFiniteBound.isEmpty()); + SkASSERT(kNormal_BoundsType == fFiniteBoundType); + SkASSERT(!fIsIntersectionOfRects); + SkASSERT(kEmptyGenID == fGenID); + SkASSERT(fDeviceSpaceRRect.isEmpty()); + SkASSERT(!fDeviceSpacePath.isValid()); +} + +bool SkClipStack::Element::canBeIntersectedInPlace(int saveCount, SkClipOp op) const { + if (DeviceSpaceType::kEmpty == fDeviceSpaceType && + (kDifference_SkClipOp == op || kIntersect_SkClipOp == op)) { + return true; + } + // Only clips within the same save/restore frame (as captured by + // the save count) can be merged + return fSaveCount == saveCount && + kIntersect_SkClipOp == op && + (kIntersect_SkClipOp == fOp || kReplace_SkClipOp == fOp); +} + +bool SkClipStack::Element::rectRectIntersectAllowed(const SkRect& newR, bool newAA) const { + SkASSERT(DeviceSpaceType::kRect == fDeviceSpaceType); + + if (fDoAA == newAA) { + // if the AA setting is the same there is no issue + return true; + } + + if (!SkRect::Intersects(this->getDeviceSpaceRect(), newR)) { + // The calling code will correctly set the result to the empty clip + return true; + } + + if (this->getDeviceSpaceRect().contains(newR)) { + // if the new rect carves out a portion of the old one there is no + // issue + return true; + } + + // So either the two overlap in some complex manner or newR contains oldR. + // In the first, case the edges will require different AA. In the second, + // the AA setting that would be carried forward is incorrect (e.g., oldR + // is AA while newR is BW but since newR contains oldR, oldR will be + // drawn BW) since the new AA setting will predominate. + return false; +} + +// a mirror of combineBoundsRevDiff +void SkClipStack::Element::combineBoundsDiff(FillCombo combination, const SkRect& prevFinite) { + switch (combination) { + case kInvPrev_InvCur_FillCombo: + // In this case the only pixels that can remain set + // are inside the current clip rect since the extensions + // to infinity of both clips cancel out and whatever + // is outside of the current clip is removed + fFiniteBoundType = kNormal_BoundsType; + break; + case kInvPrev_Cur_FillCombo: + // In this case the current op is finite so the only pixels + // that aren't set are whatever isn't set in the previous + // clip and whatever this clip carves out + fFiniteBound.join(prevFinite); + fFiniteBoundType = kInsideOut_BoundsType; + break; + case kPrev_InvCur_FillCombo: + // In this case everything outside of this clip's bound + // is erased, so the only pixels that can remain set + // occur w/in the intersection of the two finite bounds + if (!fFiniteBound.intersect(prevFinite)) { + fFiniteBound.setEmpty(); + fGenID = kEmptyGenID; + } + fFiniteBoundType = kNormal_BoundsType; + break; + case kPrev_Cur_FillCombo: + // The most conservative result bound is that of the + // prior clip. This could be wildly incorrect if the + // second clip either exactly matches the first clip + // (which should yield the empty set) or reduces the + // size of the prior bound (e.g., if the second clip + // exactly matched the bottom half of the prior clip). + // We ignore these two possibilities. + fFiniteBound = prevFinite; + break; + default: + SkDEBUGFAIL("SkClipStack::Element::combineBoundsDiff Invalid fill combination"); + break; + } +} + +void SkClipStack::Element::combineBoundsXOR(int combination, const SkRect& prevFinite) { + + switch (combination) { + case kInvPrev_Cur_FillCombo: // fall through + case kPrev_InvCur_FillCombo: + // With only one of the clips inverted the result will always + // extend to infinity. The only pixels that may be un-writeable + // lie within the union of the two finite bounds + fFiniteBound.join(prevFinite); + fFiniteBoundType = kInsideOut_BoundsType; + break; + case kInvPrev_InvCur_FillCombo: + // The only pixels that can survive are within the + // union of the two bounding boxes since the extensions + // to infinity of both clips cancel out + // fall through! + case kPrev_Cur_FillCombo: + // The most conservative bound for xor is the + // union of the two bounds. If the two clips exactly overlapped + // the xor could yield the empty set. Similarly the xor + // could reduce the size of the original clip's bound (e.g., + // if the second clip exactly matched the bottom half of the + // first clip). We ignore these two cases. + fFiniteBound.join(prevFinite); + fFiniteBoundType = kNormal_BoundsType; + break; + default: + SkDEBUGFAIL("SkClipStack::Element::combineBoundsXOR Invalid fill combination"); + break; + } +} + +// a mirror of combineBoundsIntersection +void SkClipStack::Element::combineBoundsUnion(int combination, const SkRect& prevFinite) { + + switch (combination) { + case kInvPrev_InvCur_FillCombo: + if (!fFiniteBound.intersect(prevFinite)) { + fFiniteBound.setEmpty(); + fGenID = kWideOpenGenID; + } + fFiniteBoundType = kInsideOut_BoundsType; + break; + case kInvPrev_Cur_FillCombo: + // The only pixels that won't be drawable are inside + // the prior clip's finite bound + fFiniteBound = prevFinite; + fFiniteBoundType = kInsideOut_BoundsType; + break; + case kPrev_InvCur_FillCombo: + // The only pixels that won't be drawable are inside + // this clip's finite bound + break; + case kPrev_Cur_FillCombo: + fFiniteBound.join(prevFinite); + break; + default: + SkDEBUGFAIL("SkClipStack::Element::combineBoundsUnion Invalid fill combination"); + break; + } +} + +// a mirror of combineBoundsUnion +void SkClipStack::Element::combineBoundsIntersection(int combination, const SkRect& prevFinite) { + + switch (combination) { + case kInvPrev_InvCur_FillCombo: + // The only pixels that aren't writable in this case + // occur in the union of the two finite bounds + fFiniteBound.join(prevFinite); + fFiniteBoundType = kInsideOut_BoundsType; + break; + case kInvPrev_Cur_FillCombo: + // In this case the only pixels that will remain writeable + // are within the current clip + break; + case kPrev_InvCur_FillCombo: + // In this case the only pixels that will remain writeable + // are with the previous clip + fFiniteBound = prevFinite; + fFiniteBoundType = kNormal_BoundsType; + break; + case kPrev_Cur_FillCombo: + if (!fFiniteBound.intersect(prevFinite)) { + this->setEmpty(); + } + break; + default: + SkDEBUGFAIL("SkClipStack::Element::combineBoundsIntersection Invalid fill combination"); + break; + } +} + +// a mirror of combineBoundsDiff +void SkClipStack::Element::combineBoundsRevDiff(int combination, const SkRect& prevFinite) { + + switch (combination) { + case kInvPrev_InvCur_FillCombo: + // The only pixels that can survive are in the + // previous bound since the extensions to infinity in + // both clips cancel out + fFiniteBound = prevFinite; + fFiniteBoundType = kNormal_BoundsType; + break; + case kInvPrev_Cur_FillCombo: + if (!fFiniteBound.intersect(prevFinite)) { + this->setEmpty(); + } else { + fFiniteBoundType = kNormal_BoundsType; + } + break; + case kPrev_InvCur_FillCombo: + fFiniteBound.join(prevFinite); + fFiniteBoundType = kInsideOut_BoundsType; + break; + case kPrev_Cur_FillCombo: + // Fall through - as with the kDifference_Op case, the + // most conservative result bound is the bound of the + // current clip. The prior clip could reduce the size of this + // bound (as in the kDifference_Op case) but we are ignoring + // those cases. + break; + default: + SkDEBUGFAIL("SkClipStack::Element::combineBoundsRevDiff Invalid fill combination"); + break; + } +} + +void SkClipStack::Element::updateBoundAndGenID(const Element* prior) { + // We set this first here but we may overwrite it later if we determine that the clip is + // either wide-open or empty. + fGenID = GetNextGenID(); + + // First, optimistically update the current Element's bound information + // with the current clip's bound + fIsIntersectionOfRects = false; + switch (fDeviceSpaceType) { + case DeviceSpaceType::kRect: + fFiniteBound = this->getDeviceSpaceRect(); + fFiniteBoundType = kNormal_BoundsType; + + if (kReplace_SkClipOp == fOp || (kIntersect_SkClipOp == fOp && nullptr == prior) || + (kIntersect_SkClipOp == fOp && prior->fIsIntersectionOfRects && + prior->rectRectIntersectAllowed(this->getDeviceSpaceRect(), fDoAA))) { + fIsIntersectionOfRects = true; + } + break; + case DeviceSpaceType::kRRect: + fFiniteBound = fDeviceSpaceRRect.getBounds(); + fFiniteBoundType = kNormal_BoundsType; + break; + case DeviceSpaceType::kPath: + fFiniteBound = fDeviceSpacePath.get()->getBounds(); + + if (fDeviceSpacePath.get()->isInverseFillType()) { + fFiniteBoundType = kInsideOut_BoundsType; + } else { + fFiniteBoundType = kNormal_BoundsType; + } + break; + case DeviceSpaceType::kEmpty: + SkDEBUGFAIL("We shouldn't get here with an empty element."); + break; + } + + // Now determine the previous Element's bound information taking into + // account that there may be no previous clip + SkRect prevFinite; + SkClipStack::BoundsType prevType; + + if (nullptr == prior) { + // no prior clip means the entire plane is writable + prevFinite.setEmpty(); // there are no pixels that cannot be drawn to + prevType = kInsideOut_BoundsType; + } else { + prevFinite = prior->fFiniteBound; + prevType = prior->fFiniteBoundType; + } + + FillCombo combination = kPrev_Cur_FillCombo; + if (kInsideOut_BoundsType == fFiniteBoundType) { + combination = (FillCombo) (combination | 0x01); + } + if (kInsideOut_BoundsType == prevType) { + combination = (FillCombo) (combination | 0x02); + } + + SkASSERT(kInvPrev_InvCur_FillCombo == combination || + kInvPrev_Cur_FillCombo == combination || + kPrev_InvCur_FillCombo == combination || + kPrev_Cur_FillCombo == combination); + + // Now integrate with clip with the prior clips + switch (fOp) { + case kDifference_SkClipOp: + this->combineBoundsDiff(combination, prevFinite); + break; + case kXOR_SkClipOp: + this->combineBoundsXOR(combination, prevFinite); + break; + case kUnion_SkClipOp: + this->combineBoundsUnion(combination, prevFinite); + break; + case kIntersect_SkClipOp: + this->combineBoundsIntersection(combination, prevFinite); + break; + case kReverseDifference_SkClipOp: + this->combineBoundsRevDiff(combination, prevFinite); + break; + case kReplace_SkClipOp: + // Replace just ignores everything prior + // The current clip's bound information is already filled in + // so nothing to do + break; + default: + SkDebugf("SkClipOp error\n"); + SkASSERT(0); + break; + } +} + +// This constant determines how many Element's are allocated together as a block in +// the deque. As such it needs to balance allocating too much memory vs. +// incurring allocation/deallocation thrashing. It should roughly correspond to +// the deepest save/restore stack we expect to see. +static const int kDefaultElementAllocCnt = 8; + +SkClipStack::SkClipStack() + : fDeque(sizeof(Element), kDefaultElementAllocCnt) + , fSaveCount(0) { +} + +SkClipStack::SkClipStack(void* storage, size_t size) + : fDeque(sizeof(Element), storage, size, kDefaultElementAllocCnt) + , fSaveCount(0) { +} + +SkClipStack::SkClipStack(const SkClipStack& b) + : fDeque(sizeof(Element), kDefaultElementAllocCnt) { + *this = b; +} + +SkClipStack::~SkClipStack() { + reset(); +} + +SkClipStack& SkClipStack::operator=(const SkClipStack& b) { + if (this == &b) { + return *this; + } + reset(); + + fSaveCount = b.fSaveCount; + SkDeque::F2BIter recIter(b.fDeque); + for (const Element* element = (const Element*)recIter.next(); + element != nullptr; + element = (const Element*)recIter.next()) { + new (fDeque.push_back()) Element(*element); + } + + return *this; +} + +bool SkClipStack::operator==(const SkClipStack& b) const { + if (this->getTopmostGenID() == b.getTopmostGenID()) { + return true; + } + if (fSaveCount != b.fSaveCount || + fDeque.count() != b.fDeque.count()) { + return false; + } + SkDeque::F2BIter myIter(fDeque); + SkDeque::F2BIter bIter(b.fDeque); + const Element* myElement = (const Element*)myIter.next(); + const Element* bElement = (const Element*)bIter.next(); + + while (myElement != nullptr && bElement != nullptr) { + if (*myElement != *bElement) { + return false; + } + myElement = (const Element*)myIter.next(); + bElement = (const Element*)bIter.next(); + } + return myElement == nullptr && bElement == nullptr; +} + +void SkClipStack::reset() { + // We used a placement new for each object in fDeque, so we're responsible + // for calling the destructor on each of them as well. + while (!fDeque.empty()) { + Element* element = (Element*)fDeque.back(); + element->~Element(); + fDeque.pop_back(); + } + + fSaveCount = 0; +} + +void SkClipStack::save() { + fSaveCount += 1; +} + +void SkClipStack::restore() { + fSaveCount -= 1; + restoreTo(fSaveCount); +} + +void SkClipStack::restoreTo(int saveCount) { + while (!fDeque.empty()) { + Element* element = (Element*)fDeque.back(); + if (element->fSaveCount <= saveCount) { + break; + } + element->~Element(); + fDeque.pop_back(); + } +} + +SkRect SkClipStack::bounds(const SkIRect& deviceBounds) const { + // TODO: optimize this. + SkRect r; + SkClipStack::BoundsType bounds; + this->getBounds(&r, &bounds); + if (bounds == SkClipStack::kInsideOut_BoundsType) { + return SkRect::Make(deviceBounds); + } + return r.intersect(SkRect::Make(deviceBounds)) ? r : SkRect::MakeEmpty(); +} + +// TODO: optimize this. +bool SkClipStack::isEmpty(const SkIRect& r) const { return this->bounds(r).isEmpty(); } + +void SkClipStack::getBounds(SkRect* canvFiniteBound, + BoundsType* boundType, + bool* isIntersectionOfRects) const { + SkASSERT(canvFiniteBound && boundType); + + Element* element = (Element*)fDeque.back(); + + if (nullptr == element) { + // the clip is wide open - the infinite plane w/ no pixels un-writeable + canvFiniteBound->setEmpty(); + *boundType = kInsideOut_BoundsType; + if (isIntersectionOfRects) { + *isIntersectionOfRects = false; + } + return; + } + + *canvFiniteBound = element->fFiniteBound; + *boundType = element->fFiniteBoundType; + if (isIntersectionOfRects) { + *isIntersectionOfRects = element->fIsIntersectionOfRects; + } +} + +bool SkClipStack::internalQuickContains(const SkRect& rect) const { + + Iter iter(*this, Iter::kTop_IterStart); + const Element* element = iter.prev(); + while (element != nullptr) { + if (kIntersect_SkClipOp != element->getOp() && kReplace_SkClipOp != element->getOp()) + return false; + if (element->isInverseFilled()) { + // Part of 'rect' could be trimmed off by the inverse-filled clip element + if (SkRect::Intersects(element->getBounds(), rect)) { + return false; + } + } else { + if (!element->contains(rect)) { + return false; + } + } + if (kReplace_SkClipOp == element->getOp()) { + break; + } + element = iter.prev(); + } + return true; +} + +bool SkClipStack::internalQuickContains(const SkRRect& rrect) const { + + Iter iter(*this, Iter::kTop_IterStart); + const Element* element = iter.prev(); + while (element != nullptr) { + if (kIntersect_SkClipOp != element->getOp() && kReplace_SkClipOp != element->getOp()) + return false; + if (element->isInverseFilled()) { + // Part of 'rrect' could be trimmed off by the inverse-filled clip element + if (SkRect::Intersects(element->getBounds(), rrect.getBounds())) { + return false; + } + } else { + if (!element->contains(rrect)) { + return false; + } + } + if (kReplace_SkClipOp == element->getOp()) { + break; + } + element = iter.prev(); + } + return true; +} + +bool SkClipStack::asPath(SkPath *path) const { + bool isAA = false; + + path->reset(); + path->setFillType(SkPath::kInverseEvenOdd_FillType); + + SkClipStack::Iter iter(*this, SkClipStack::Iter::kBottom_IterStart); + while (const SkClipStack::Element* element = iter.next()) { + SkPath operand; + if (element->getDeviceSpaceType() != SkClipStack::Element::DeviceSpaceType::kEmpty) { + element->asDeviceSpacePath(&operand); + } + + SkClipOp elementOp = element->getOp(); + if (elementOp == kReplace_SkClipOp) { + *path = operand; + } else { + Op(*path, operand, (SkPathOp)elementOp, path); + } + + // if the prev and curr clips disagree about aa -vs- not, favor the aa request. + // perhaps we need an API change to avoid this sort of mixed-signals about + // clipping. + isAA = (isAA || element->isAA()); + } + + return isAA; +} + +void SkClipStack::pushElement(const Element& element) { + // Use reverse iterator instead of back because Rect path may need previous + SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart); + Element* prior = (Element*) iter.prev(); + + if (prior) { + if (prior->canBeIntersectedInPlace(fSaveCount, element.getOp())) { + switch (prior->fDeviceSpaceType) { + case Element::DeviceSpaceType::kEmpty: + SkDEBUGCODE(prior->checkEmpty();) + return; + case Element::DeviceSpaceType::kRect: + if (Element::DeviceSpaceType::kRect == element.getDeviceSpaceType()) { + if (prior->rectRectIntersectAllowed(element.getDeviceSpaceRect(), + element.isAA())) { + SkRect isectRect; + if (!isectRect.intersect(prior->getDeviceSpaceRect(), + element.getDeviceSpaceRect())) { + prior->setEmpty(); + return; + } + + prior->fDeviceSpaceRRect.setRect(isectRect); + prior->fDoAA = element.isAA(); + Element* priorPrior = (Element*) iter.prev(); + prior->updateBoundAndGenID(priorPrior); + return; + } + break; + } + // fallthrough + default: + if (!SkRect::Intersects(prior->getBounds(), element.getBounds())) { + prior->setEmpty(); + return; + } + break; + } + } else if (kReplace_SkClipOp == element.getOp()) { + this->restoreTo(fSaveCount - 1); + prior = (Element*) fDeque.back(); + } + } + Element* newElement = new (fDeque.push_back()) Element(element); + newElement->updateBoundAndGenID(prior); +} + +void SkClipStack::clipRRect(const SkRRect& rrect, const SkMatrix& matrix, SkClipOp op, + bool doAA) { + Element element(fSaveCount, rrect, matrix, op, doAA); + this->pushElement(element); + if (this->hasClipRestriction(op)) { + Element restriction(fSaveCount, fClipRestrictionRect, SkMatrix::I(), kIntersect_SkClipOp, + false); + this->pushElement(restriction); + } +} + +void SkClipStack::clipRect(const SkRect& rect, const SkMatrix& matrix, SkClipOp op, + bool doAA) { + Element element(fSaveCount, rect, matrix, op, doAA); + this->pushElement(element); + if (this->hasClipRestriction(op)) { + Element restriction(fSaveCount, fClipRestrictionRect, SkMatrix::I(), kIntersect_SkClipOp, + false); + this->pushElement(restriction); + } +} + +void SkClipStack::clipPath(const SkPath& path, const SkMatrix& matrix, SkClipOp op, + bool doAA) { + Element element(fSaveCount, path, matrix, op, doAA); + this->pushElement(element); + if (this->hasClipRestriction(op)) { + Element restriction(fSaveCount, fClipRestrictionRect, SkMatrix::I(), kIntersect_SkClipOp, + false); + this->pushElement(restriction); + } +} + +void SkClipStack::clipEmpty() { + Element* element = (Element*) fDeque.back(); + + if (element && element->canBeIntersectedInPlace(fSaveCount, kIntersect_SkClipOp)) { + element->setEmpty(); + } + new (fDeque.push_back()) Element(fSaveCount); + + ((Element*)fDeque.back())->fGenID = kEmptyGenID; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkClipStack::Iter::Iter() : fStack(nullptr) { +} + +SkClipStack::Iter::Iter(const SkClipStack& stack, IterStart startLoc) + : fStack(&stack) { + this->reset(stack, startLoc); +} + +const SkClipStack::Element* SkClipStack::Iter::next() { + return (const SkClipStack::Element*)fIter.next(); +} + +const SkClipStack::Element* SkClipStack::Iter::prev() { + return (const SkClipStack::Element*)fIter.prev(); +} + +const SkClipStack::Element* SkClipStack::Iter::skipToTopmost(SkClipOp op) { + + if (nullptr == fStack) { + return nullptr; + } + + fIter.reset(fStack->fDeque, SkDeque::Iter::kBack_IterStart); + + const SkClipStack::Element* element = nullptr; + + for (element = (const SkClipStack::Element*) fIter.prev(); + element; + element = (const SkClipStack::Element*) fIter.prev()) { + + if (op == element->fOp) { + // The Deque's iterator is actually one pace ahead of the + // returned value. So while "element" is the element we want to + // return, the iterator is actually pointing at (and will + // return on the next "next" or "prev" call) the element + // in front of it in the deque. Bump the iterator forward a + // step so we get the expected result. + if (nullptr == fIter.next()) { + // The reverse iterator has run off the front of the deque + // (i.e., the "op" clip is the first clip) and can't + // recover. Reset the iterator to start at the front. + fIter.reset(fStack->fDeque, SkDeque::Iter::kFront_IterStart); + } + break; + } + } + + if (nullptr == element) { + // There were no "op" clips + fIter.reset(fStack->fDeque, SkDeque::Iter::kFront_IterStart); + } + + return this->next(); +} + +void SkClipStack::Iter::reset(const SkClipStack& stack, IterStart startLoc) { + fStack = &stack; + fIter.reset(stack.fDeque, static_cast(startLoc)); +} + +// helper method +void SkClipStack::getConservativeBounds(int offsetX, + int offsetY, + int maxWidth, + int maxHeight, + SkRect* devBounds, + bool* isIntersectionOfRects) const { + SkASSERT(devBounds); + + devBounds->setLTRB(0, 0, + SkIntToScalar(maxWidth), SkIntToScalar(maxHeight)); + + SkRect temp; + SkClipStack::BoundsType boundType; + + // temp starts off in canvas space here + this->getBounds(&temp, &boundType, isIntersectionOfRects); + if (SkClipStack::kInsideOut_BoundsType == boundType) { + return; + } + + // but is converted to device space here + temp.offset(SkIntToScalar(offsetX), SkIntToScalar(offsetY)); + + if (!devBounds->intersect(temp)) { + devBounds->setEmpty(); + } +} + +bool SkClipStack::isRRect(const SkRect& bounds, SkRRect* rrect, bool* aa) const { + const Element* back = static_cast(fDeque.back()); + if (!back) { + // TODO: return bounds? + return false; + } + // First check if the entire stack is known to be a rect by the top element. + if (back->fIsIntersectionOfRects && back->fFiniteBoundType == BoundsType::kNormal_BoundsType) { + rrect->setRect(back->fFiniteBound); + *aa = back->isAA(); + return true; + } + + if (back->getDeviceSpaceType() != SkClipStack::Element::DeviceSpaceType::kRect && + back->getDeviceSpaceType() != SkClipStack::Element::DeviceSpaceType::kRRect) { + return false; + } + if (back->getOp() == kReplace_SkClipOp) { + *rrect = back->asDeviceSpaceRRect(); + *aa = back->isAA(); + return true; + } + + if (back->getOp() == kIntersect_SkClipOp) { + SkRect backBounds; + if (!backBounds.intersect(bounds, back->asDeviceSpaceRRect().rect())) { + return false; + } + // We limit to 17 elements. This means the back element will be bounds checked at most 16 + // times if it is an rrect. + int cnt = fDeque.count(); + if (cnt > 17) { + return false; + } + if (cnt > 1) { + SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart); + SkAssertResult(static_cast(iter.prev()) == back); + while (const Element* prior = (const Element*)iter.prev()) { + if ((prior->getOp() != kIntersect_SkClipOp && + prior->getOp() != kReplace_SkClipOp) || + !prior->contains(backBounds)) { + return false; + } + if (prior->getOp() == kReplace_SkClipOp) { + break; + } + } + } + *rrect = back->asDeviceSpaceRRect(); + *aa = back->isAA(); + return true; + } + return false; +} + +uint32_t SkClipStack::GetNextGenID() { + // 0-2 are reserved for invalid, empty & wide-open + static const uint32_t kFirstUnreservedGenID = 3; + static std::atomic nextID{kFirstUnreservedGenID}; + + uint32_t id; + do { + id = nextID++; + } while (id < kFirstUnreservedGenID); + return id; +} + +uint32_t SkClipStack::getTopmostGenID() const { + if (fDeque.empty()) { + return kWideOpenGenID; + } + + const Element* back = static_cast(fDeque.back()); + if (kInsideOut_BoundsType == back->fFiniteBoundType && back->fFiniteBound.isEmpty()) { + return kWideOpenGenID; + } + + return back->getGenID(); +} + +#ifdef SK_DEBUG +void SkClipStack::Element::dump() const { + static const char* kTypeStrings[] = { + "empty", + "rect", + "rrect", + "path" + }; + static_assert(0 == static_cast(DeviceSpaceType::kEmpty), "enum mismatch"); + static_assert(1 == static_cast(DeviceSpaceType::kRect), "enum mismatch"); + static_assert(2 == static_cast(DeviceSpaceType::kRRect), "enum mismatch"); + static_assert(3 == static_cast(DeviceSpaceType::kPath), "enum mismatch"); + static_assert(SK_ARRAY_COUNT(kTypeStrings) == kTypeCnt, "enum mismatch"); + + static const char* kOpStrings[] = { + "difference", + "intersect", + "union", + "xor", + "reverse-difference", + "replace", + }; + static_assert(0 == static_cast(kDifference_SkClipOp), "enum mismatch"); + static_assert(1 == static_cast(kIntersect_SkClipOp), "enum mismatch"); + static_assert(2 == static_cast(kUnion_SkClipOp), "enum mismatch"); + static_assert(3 == static_cast(kXOR_SkClipOp), "enum mismatch"); + static_assert(4 == static_cast(kReverseDifference_SkClipOp), "enum mismatch"); + static_assert(5 == static_cast(kReplace_SkClipOp), "enum mismatch"); + static_assert(SK_ARRAY_COUNT(kOpStrings) == SkRegion::kOpCnt, "enum mismatch"); + + SkDebugf("Type: %s, Op: %s, AA: %s, Save Count: %d\n", kTypeStrings[(int)fDeviceSpaceType], + kOpStrings[static_cast(fOp)], (fDoAA ? "yes" : "no"), fSaveCount); + switch (fDeviceSpaceType) { + case DeviceSpaceType::kEmpty: + SkDebugf("\n"); + break; + case DeviceSpaceType::kRect: + this->getDeviceSpaceRect().dump(); + SkDebugf("\n"); + break; + case DeviceSpaceType::kRRect: + this->getDeviceSpaceRRect().dump(); + SkDebugf("\n"); + break; + case DeviceSpaceType::kPath: + this->getDeviceSpacePath().dump(nullptr, true, false); + break; + } +} + +void SkClipStack::dump() const { + B2TIter iter(*this); + const Element* e; + while ((e = iter.next())) { + e->dump(); + SkDebugf("\n"); + } +} +#endif diff --git a/gfx/skia/skia/src/core/SkClipStack.h b/gfx/skia/skia/src/core/SkClipStack.h new file mode 100644 index 0000000000..5f502ee336 --- /dev/null +++ b/gfx/skia/skia/src/core/SkClipStack.h @@ -0,0 +1,522 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkClipStack_DEFINED +#define SkClipStack_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkDeque.h" +#include "include/core/SkPath.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRect.h" +#include "include/core/SkRegion.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkMessageBus.h" +#include "src/core/SkTLazy.h" + +#if SK_SUPPORT_GPU +class GrProxyProvider; + +#include "include/private/GrResourceKey.h" +#endif + +// Because a single save/restore state can have multiple clips, this class +// stores the stack depth (fSaveCount) and clips (fDeque) separately. +// Each clip in fDeque stores the stack state to which it belongs +// (i.e., the fSaveCount in force when it was added). Restores are thus +// implemented by removing clips from fDeque that have an fSaveCount larger +// then the freshly decremented count. +class SkClipStack { +public: + enum BoundsType { + // The bounding box contains all the pixels that can be written to + kNormal_BoundsType, + // The bounding box contains all the pixels that cannot be written to. + // The real bound extends out to infinity and all the pixels outside + // of the bound can be written to. Note that some of the pixels inside + // the bound may also be writeable but all pixels that cannot be + // written to are guaranteed to be inside. + kInsideOut_BoundsType + }; + + /** + * An element of the clip stack. It represents a shape combined with the prevoius clip using a + * set operator. Each element can be antialiased or not. + */ + class Element { + public: + /** This indicates the shape type of the clip element in device space. */ + enum class DeviceSpaceType { + //!< This element makes the clip empty (regardless of previous elements). + kEmpty, + //!< This element combines a device space rect with the current clip. + kRect, + //!< This element combines a device space round-rect with the current clip. + kRRect, + //!< This element combines a device space path with the current clip. + kPath, + + kLastType = kPath + }; + static const int kTypeCnt = (int)DeviceSpaceType::kLastType + 1; + + Element() { + this->initCommon(0, kReplace_SkClipOp, false); + this->setEmpty(); + } + + Element(const Element&); + + Element(const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) { + this->initRect(0, rect, m, op, doAA); + } + + Element(const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) { + this->initRRect(0, rrect, m, op, doAA); + } + + Element(const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) { + this->initPath(0, path, m, op, doAA); + } + + ~Element(); + + bool operator== (const Element& element) const; + bool operator!= (const Element& element) const { return !(*this == element); } + + //!< Call to get the type of the clip element. + DeviceSpaceType getDeviceSpaceType() const { return fDeviceSpaceType; } + + //!< Call to get the save count associated with this clip element. + int getSaveCount() const { return fSaveCount; } + + //!< Call if getDeviceSpaceType() is kPath to get the path. + const SkPath& getDeviceSpacePath() const { + SkASSERT(DeviceSpaceType::kPath == fDeviceSpaceType); + return *fDeviceSpacePath.get(); + } + + //!< Call if getDeviceSpaceType() is kRRect to get the round-rect. + const SkRRect& getDeviceSpaceRRect() const { + SkASSERT(DeviceSpaceType::kRRect == fDeviceSpaceType); + return fDeviceSpaceRRect; + } + + //!< Call if getDeviceSpaceType() is kRect to get the rect. + const SkRect& getDeviceSpaceRect() const { + SkASSERT(DeviceSpaceType::kRect == fDeviceSpaceType && + (fDeviceSpaceRRect.isRect() || fDeviceSpaceRRect.isEmpty())); + return fDeviceSpaceRRect.getBounds(); + } + + //!< Call if getDeviceSpaceType() is not kEmpty to get the set operation used to combine + //!< this element. + SkClipOp getOp() const { return fOp; } + + //!< Call to get the element as a path, regardless of its type. + void asDeviceSpacePath(SkPath* path) const; + + //!< Call if getType() is not kPath to get the element as a round rect. + const SkRRect& asDeviceSpaceRRect() const { + SkASSERT(DeviceSpaceType::kPath != fDeviceSpaceType); + return fDeviceSpaceRRect; + } + + /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased + when it is rasterized. */ + bool isAA() const { return fDoAA; } + + //!< Inverts the fill of the clip shape. Note that a kEmpty element remains kEmpty. + void invertShapeFillType(); + + //!< Sets the set operation represented by the element. + void setOp(SkClipOp op) { fOp = op; } + + /** The GenID can be used by clip stack clients to cache representations of the clip. The + ID corresponds to the set of clip elements up to and including this element within the + stack not to the element itself. That is the same clip path in different stacks will + have a different ID since the elements produce different clip result in the context of + their stacks. */ + uint32_t getGenID() const { SkASSERT(kInvalidGenID != fGenID); return fGenID; } + + /** + * Gets the bounds of the clip element, either the rect or path bounds. (Whether the shape + * is inverse filled is not considered.) + */ + const SkRect& getBounds() const; + + /** + * Conservatively checks whether the clip shape contains the rect/rrect. (Whether the shape + * is inverse filled is not considered.) + */ + bool contains(const SkRect& rect) const; + bool contains(const SkRRect& rrect) const; + + /** + * Is the clip shape inverse filled. + */ + bool isInverseFilled() const { + return DeviceSpaceType::kPath == fDeviceSpaceType && + fDeviceSpacePath.get()->isInverseFillType(); + } + +#ifdef SK_DEBUG + /** + * Dumps the element to SkDebugf. This is intended for Skia development debugging + * Don't rely on the existence of this function or the formatting of its output. + */ + void dump() const; +#endif + +#if SK_SUPPORT_GPU + /** + * This is used to purge any GPU resource cache items that become unreachable when + * the element is destroyed because their key is based on this element's gen ID. + */ + void addResourceInvalidationMessage(GrProxyProvider* proxyProvider, + const GrUniqueKey& key) const { + SkASSERT(proxyProvider); + + if (!fProxyProvider) { + fProxyProvider = proxyProvider; + } + SkASSERT(fProxyProvider == proxyProvider); + + fKeysToInvalidate.push_back(key); + } +#endif + + private: + friend class SkClipStack; + + SkTLazy fDeviceSpacePath; + SkRRect fDeviceSpaceRRect; + int fSaveCount; // save count of stack when this element was added. + SkClipOp fOp; + DeviceSpaceType fDeviceSpaceType; + bool fDoAA; + + /* fFiniteBoundType and fFiniteBound are used to incrementally update the clip stack's + bound. When fFiniteBoundType is kNormal_BoundsType, fFiniteBound represents the + conservative bounding box of the pixels that aren't clipped (i.e., any pixels that can be + drawn to are inside the bound). When fFiniteBoundType is kInsideOut_BoundsType (which + occurs when a clip is inverse filled), fFiniteBound represents the conservative bounding + box of the pixels that _are_ clipped (i.e., any pixels that cannot be drawn to are inside + the bound). When fFiniteBoundType is kInsideOut_BoundsType the actual bound is the + infinite plane. This behavior of fFiniteBoundType and fFiniteBound is required so that we + can capture the cancelling out of the extensions to infinity when two inverse filled + clips are Booleaned together. */ + SkClipStack::BoundsType fFiniteBoundType; + SkRect fFiniteBound; + + // When element is applied to the previous elements in the stack is the result known to be + // equivalent to a single rect intersection? IIOW, is the clip effectively a rectangle. + bool fIsIntersectionOfRects; + + uint32_t fGenID; +#if SK_SUPPORT_GPU + mutable GrProxyProvider* fProxyProvider = nullptr; + mutable SkTArray fKeysToInvalidate; +#endif + Element(int saveCount) { + this->initCommon(saveCount, kReplace_SkClipOp, false); + this->setEmpty(); + } + + Element(int saveCount, const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) { + this->initRRect(saveCount, rrect, m, op, doAA); + } + + Element(int saveCount, const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) { + this->initRect(saveCount, rect, m, op, doAA); + } + + Element(int saveCount, const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) { + this->initPath(saveCount, path, m, op, doAA); + } + + void initCommon(int saveCount, SkClipOp op, bool doAA); + void initRect(int saveCount, const SkRect&, const SkMatrix&, SkClipOp, bool doAA); + void initRRect(int saveCount, const SkRRect&, const SkMatrix&, SkClipOp, bool doAA); + void initPath(int saveCount, const SkPath&, const SkMatrix&, SkClipOp, bool doAA); + void initAsPath(int saveCount, const SkPath&, const SkMatrix&, SkClipOp, bool doAA); + + void setEmpty(); + + // All Element methods below are only used within SkClipStack.cpp + inline void checkEmpty() const; + inline bool canBeIntersectedInPlace(int saveCount, SkClipOp op) const; + /* This method checks to see if two rect clips can be safely merged into one. The issue here + is that to be strictly correct all the edges of the resulting rect must have the same + anti-aliasing. */ + bool rectRectIntersectAllowed(const SkRect& newR, bool newAA) const; + /** Determines possible finite bounds for the Element given the previous element of the + stack */ + void updateBoundAndGenID(const Element* prior); + // The different combination of fill & inverse fill when combining bounding boxes + enum FillCombo { + kPrev_Cur_FillCombo, + kPrev_InvCur_FillCombo, + kInvPrev_Cur_FillCombo, + kInvPrev_InvCur_FillCombo + }; + // per-set operation functions used by updateBoundAndGenID(). + inline void combineBoundsDiff(FillCombo combination, const SkRect& prevFinite); + inline void combineBoundsXOR(int combination, const SkRect& prevFinite); + inline void combineBoundsUnion(int combination, const SkRect& prevFinite); + inline void combineBoundsIntersection(int combination, const SkRect& prevFinite); + inline void combineBoundsRevDiff(int combination, const SkRect& prevFinite); + }; + + SkClipStack(); + SkClipStack(void* storage, size_t size); + SkClipStack(const SkClipStack& b); + ~SkClipStack(); + + SkClipStack& operator=(const SkClipStack& b); + bool operator==(const SkClipStack& b) const; + bool operator!=(const SkClipStack& b) const { return !(*this == b); } + + void reset(); + + int getSaveCount() const { return fSaveCount; } + void save(); + void restore(); + + class AutoRestore { + public: + AutoRestore(SkClipStack* cs, bool doSave) + : fCS(cs), fSaveCount(cs->getSaveCount()) + { + if (doSave) { + fCS->save(); + } + } + ~AutoRestore() { + SkASSERT(fCS->getSaveCount() >= fSaveCount); // no underflow + while (fCS->getSaveCount() > fSaveCount) { + fCS->restore(); + } + } + + private: + SkClipStack* fCS; + const int fSaveCount; + }; + + /** + * getBounds places the current finite bound in its first parameter. In its + * second, it indicates which kind of bound is being returned. If + * 'canvFiniteBound' is a normal bounding box then it encloses all writeable + * pixels. If 'canvFiniteBound' is an inside out bounding box then it + * encloses all the un-writeable pixels and the true/normal bound is the + * infinite plane. isIntersectionOfRects is an optional parameter + * that is true if 'canvFiniteBound' resulted from an intersection of rects. + */ + void getBounds(SkRect* canvFiniteBound, + BoundsType* boundType, + bool* isIntersectionOfRects = nullptr) const; + + SkRect bounds(const SkIRect& deviceBounds) const; + bool isEmpty(const SkIRect& deviceBounds) const; + + /** + * Returns true if the input (r)rect in device space is entirely contained + * by the clip. A return value of false does not guarantee that the (r)rect + * is not contained by the clip. + */ + bool quickContains(const SkRect& devRect) const { + return this->isWideOpen() || this->internalQuickContains(devRect); + } + + bool quickContains(const SkRRect& devRRect) const { + return this->isWideOpen() || this->internalQuickContains(devRRect); + } + + /** + * Flattens the clip stack into a single SkPath. Returns true if any of + * the clip stack components requires anti-aliasing. + */ + bool asPath(SkPath* path) const; + + void clipDevRect(const SkIRect& ir, SkClipOp op) { + SkRect r; + r.set(ir); + this->clipRect(r, SkMatrix::I(), op, false); + } + void clipRect(const SkRect&, const SkMatrix& matrix, SkClipOp, bool doAA); + void clipRRect(const SkRRect&, const SkMatrix& matrix, SkClipOp, bool doAA); + void clipPath(const SkPath&, const SkMatrix& matrix, SkClipOp, bool doAA); + // An optimized version of clipDevRect(emptyRect, kIntersect, ...) + void clipEmpty(); + void setDeviceClipRestriction(const SkIRect& rect) { + fClipRestrictionRect = SkRect::Make(rect); + } + + /** + * isWideOpen returns true if the clip state corresponds to the infinite + * plane (i.e., draws are not limited at all) + */ + bool isWideOpen() const { return this->getTopmostGenID() == kWideOpenGenID; } + + /** + * This method quickly and conservatively determines whether the entire stack is equivalent to + * intersection with a rrect given a bounds, where the rrect must not contain the entire bounds. + * + * @param bounds A bounds on what will be drawn through the clip. The clip only need be + * equivalent to a intersection with a rrect for draws within the bounds. The + * returned rrect must intersect the bounds but need not be contained by the + * bounds. + * @param rrect If return is true rrect will contain the rrect equivalent to the stack. + * @param aa If return is true aa will indicate whether the equivalent rrect clip is + * antialiased. + * @return true if the stack is equivalent to a single rrect intersect clip, false otherwise. + */ + bool isRRect(const SkRect& bounds, SkRRect* rrect, bool* aa) const; + + /** + * The generation ID has three reserved values to indicate special + * (potentially ignorable) cases + */ + static const uint32_t kInvalidGenID = 0; //!< Invalid id that is never returned by + //!< SkClipStack. Useful when caching clips + //!< based on GenID. + static const uint32_t kEmptyGenID = 1; // no pixels writeable + static const uint32_t kWideOpenGenID = 2; // all pixels writeable + + uint32_t getTopmostGenID() const; + +#ifdef SK_DEBUG + /** + * Dumps the contents of the clip stack to SkDebugf. This is intended for Skia development + * debugging. Don't rely on the existence of this function or the formatting of its output. + */ + void dump() const; +#endif + +public: + class Iter { + public: + enum IterStart { + kBottom_IterStart = SkDeque::Iter::kFront_IterStart, + kTop_IterStart = SkDeque::Iter::kBack_IterStart + }; + + /** + * Creates an uninitialized iterator. Must be reset() + */ + Iter(); + + Iter(const SkClipStack& stack, IterStart startLoc); + + /** + * Return the clip element for this iterator. If next()/prev() returns NULL, then the + * iterator is done. + */ + const Element* next(); + const Element* prev(); + + /** + * Moves the iterator to the topmost element with the specified RegionOp and returns that + * element. If no clip element with that op is found, the first element is returned. + */ + const Element* skipToTopmost(SkClipOp op); + + /** + * Restarts the iterator on a clip stack. + */ + void reset(const SkClipStack& stack, IterStart startLoc); + + private: + const SkClipStack* fStack; + SkDeque::Iter fIter; + }; + + /** + * The B2TIter iterates from the bottom of the stack to the top. + * It inherits privately from Iter to prevent access to reverse iteration. + */ + class B2TIter : private Iter { + public: + B2TIter() {} + + /** + * Wrap Iter's 2 parameter ctor to force initialization to the + * beginning of the deque/bottom of the stack + */ + B2TIter(const SkClipStack& stack) + : INHERITED(stack, kBottom_IterStart) { + } + + using Iter::next; + + /** + * Wrap Iter::reset to force initialization to the + * beginning of the deque/bottom of the stack + */ + void reset(const SkClipStack& stack) { + this->INHERITED::reset(stack, kBottom_IterStart); + } + + private: + + typedef Iter INHERITED; + }; + + /** + * GetConservativeBounds returns a conservative bound of the current clip. + * Since this could be the infinite plane (if inverse fills were involved) the + * maxWidth and maxHeight parameters can be used to limit the returned bound + * to the expected drawing area. Similarly, the offsetX and offsetY parameters + * allow the caller to offset the returned bound to account for translated + * drawing areas (i.e., those resulting from a saveLayer). For finite bounds, + * the translation (+offsetX, +offsetY) is applied before the clamp to the + * maximum rectangle: [0,maxWidth) x [0,maxHeight). + * isIntersectionOfRects is an optional parameter that is true when + * 'devBounds' is the result of an intersection of rects. In this case + * 'devBounds' is the exact answer/clip. + */ + void getConservativeBounds(int offsetX, + int offsetY, + int maxWidth, + int maxHeight, + SkRect* devBounds, + bool* isIntersectionOfRects = nullptr) const; + +private: + friend class Iter; + + SkDeque fDeque; + int fSaveCount; + + SkRect fClipRestrictionRect = SkRect::MakeEmpty(); + + bool internalQuickContains(const SkRect& devRect) const; + bool internalQuickContains(const SkRRect& devRRect) const; + + /** + * Helper for clipDevPath, etc. + */ + void pushElement(const Element& element); + + /** + * Restore the stack back to the specified save count. + */ + void restoreTo(int saveCount); + + inline bool hasClipRestriction(SkClipOp op) { + return op >= kUnion_SkClipOp && !fClipRestrictionRect.isEmpty(); + } + + /** + * Return the next unique generation ID. + */ + static uint32_t GetNextGenID(); +}; + +#endif + diff --git a/gfx/skia/skia/src/core/SkClipStackDevice.cpp b/gfx/skia/skia/src/core/SkClipStackDevice.cpp new file mode 100644 index 0000000000..72bfd1bb6c --- /dev/null +++ b/gfx/skia/skia/src/core/SkClipStackDevice.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkClipStackDevice.h" +#include "src/core/SkDraw.h" +#include "src/core/SkRasterClip.h" + +SkIRect SkClipStackDevice::devClipBounds() const { + SkIRect r = fClipStack.bounds(this->imageInfo().bounds()).roundOut(); + if (!r.isEmpty()) { + SkASSERT(this->imageInfo().bounds().contains(r)); + } + return r; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkClipStackDevice::onSave() { + fClipStack.save(); +} + +void SkClipStackDevice::onRestore() { + fClipStack.restore(); +} + +void SkClipStackDevice::onClipRect(const SkRect& rect, SkClipOp op, bool aa) { + fClipStack.clipRect(rect, this->ctm(), op, aa); +} + +void SkClipStackDevice::onClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) { + fClipStack.clipRRect(rrect, this->ctm(), op, aa); +} + +void SkClipStackDevice::onClipPath(const SkPath& path, SkClipOp op, bool aa) { + fClipStack.clipPath(path, this->ctm(), op, aa); +} + +void SkClipStackDevice::onClipRegion(const SkRegion& rgn, SkClipOp op) { + SkIPoint origin = this->getOrigin(); + SkRegion tmp; + const SkRegion* ptr = &rgn; + if (origin.fX | origin.fY) { + // translate from "global/canvas" coordinates to relative to this device + rgn.translate(-origin.fX, -origin.fY, &tmp); + ptr = &tmp; + } + fClipStack.clipDevRect(ptr->getBounds(), op); +} + +void SkClipStackDevice::onSetDeviceClipRestriction(SkIRect* clipRestriction) { + if (clipRestriction->isEmpty()) { + fClipStack.setDeviceClipRestriction(*clipRestriction); + } else { + SkIPoint origin = this->getOrigin(); + SkIRect rect = clipRestriction->makeOffset(-origin); + fClipStack.setDeviceClipRestriction(rect); + fClipStack.clipDevRect(rect, SkClipOp::kIntersect); + } +} + +bool SkClipStackDevice::onClipIsAA() const { + SkClipStack::B2TIter iter(fClipStack); + const SkClipStack::Element* element; + + while ((element = iter.next()) != nullptr) { + if (element->isAA()) { + return true; + } + } + return false; +} + +void SkClipStackDevice::onAsRgnClip(SkRegion* rgn) const { + SkClipStack::BoundsType boundType; + bool isIntersectionOfRects; + SkRect bounds; + fClipStack.getBounds(&bounds, &boundType, &isIntersectionOfRects); + if (isIntersectionOfRects && SkClipStack::kNormal_BoundsType == boundType) { + rgn->setRect(bounds.round()); + } else { + SkPath path; + fClipStack.asPath(&path); + rgn->setPath(path, SkRegion(SkIRect::MakeWH(this->width(), this->height()))); + } +} + +SkBaseDevice::ClipType SkClipStackDevice::onGetClipType() const { + if (fClipStack.isWideOpen()) { + return ClipType::kRect; + } + if (fClipStack.isEmpty(SkIRect::MakeWH(this->width(), this->height()))) { + return ClipType::kEmpty; + } else { + SkClipStack::BoundsType boundType; + bool isIntersectionOfRects; + SkRect bounds; + fClipStack.getBounds(&bounds, &boundType, &isIntersectionOfRects); + if (isIntersectionOfRects && SkClipStack::kNormal_BoundsType == boundType) { + return ClipType::kRect; + } else { + return ClipType::kComplex; + } + } +} diff --git a/gfx/skia/skia/src/core/SkClipStackDevice.h b/gfx/skia/skia/src/core/SkClipStackDevice.h new file mode 100644 index 0000000000..64b7021f87 --- /dev/null +++ b/gfx/skia/skia/src/core/SkClipStackDevice.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkClipStackDevice_DEFINED +#define SkClipStackDevice_DEFINED + +#include "src/core/SkClipStack.h" +#include "src/core/SkDevice.h" + +class SkClipStackDevice : public SkBaseDevice { +public: + SkClipStackDevice(const SkImageInfo& info, const SkSurfaceProps& props) + : SkBaseDevice(info, props) + , fClipStack(fStorage, sizeof(fStorage)) + {} + + SkClipStack& cs() { return fClipStack; } + const SkClipStack& cs() const { return fClipStack; } + + SkIRect devClipBounds() const; + +protected: + void onSave() override; + void onRestore() override; + void onClipRect(const SkRect& rect, SkClipOp, bool aa) override; + void onClipRRect(const SkRRect& rrect, SkClipOp, bool aa) override; + void onClipPath(const SkPath& path, SkClipOp, bool aa) override; + void onClipRegion(const SkRegion& deviceRgn, SkClipOp) override; + void onSetDeviceClipRestriction(SkIRect* mutableClipRestriction) override; + bool onClipIsAA() const override; + void onAsRgnClip(SkRegion*) const override; + ClipType onGetClipType() const override; + +private: + enum { + kPreallocCount = 16 // empirically determined, adjust as needed to reduce mallocs + }; + intptr_t fStorage[kPreallocCount * sizeof(SkClipStack::Element) / sizeof(intptr_t)]; + SkClipStack fClipStack; + + typedef SkBaseDevice INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkColor.cpp b/gfx/skia/skia/src/core/SkColor.cpp new file mode 100644 index 0000000000..38f619bdb1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkColor.cpp @@ -0,0 +1,165 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColor.h" +#include "include/private/SkColorData.h" +#include "include/private/SkFixed.h" + +SkPMColor SkPreMultiplyARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + return SkPremultiplyARGBInline(a, r, g, b); +} + +SkPMColor SkPreMultiplyColor(SkColor c) { + return SkPremultiplyARGBInline(SkColorGetA(c), SkColorGetR(c), + SkColorGetG(c), SkColorGetB(c)); +} + +/////////////////////////////////////////////////////////////////////////////// + +static inline SkScalar ByteToScalar(U8CPU x) { + SkASSERT(x <= 255); + return SkIntToScalar(x) / 255; +} + +static inline SkScalar ByteDivToScalar(int numer, U8CPU denom) { + // cast to keep the answer signed + return SkIntToScalar(numer) / (int)denom; +} + +void SkRGBToHSV(U8CPU r, U8CPU g, U8CPU b, SkScalar hsv[3]) { + SkASSERT(hsv); + + unsigned min = SkMin32(r, SkMin32(g, b)); + unsigned max = SkMax32(r, SkMax32(g, b)); + unsigned delta = max - min; + + SkScalar v = ByteToScalar(max); + SkASSERT(v >= 0 && v <= SK_Scalar1); + + if (0 == delta) { // we're a shade of gray + hsv[0] = 0; + hsv[1] = 0; + hsv[2] = v; + return; + } + + SkScalar s = ByteDivToScalar(delta, max); + SkASSERT(s >= 0 && s <= SK_Scalar1); + + SkScalar h; + if (r == max) { + h = ByteDivToScalar(g - b, delta); + } else if (g == max) { + h = SkIntToScalar(2) + ByteDivToScalar(b - r, delta); + } else { // b == max + h = SkIntToScalar(4) + ByteDivToScalar(r - g, delta); + } + + h *= 60; + if (h < 0) { + h += SkIntToScalar(360); + } + SkASSERT(h >= 0 && h < SkIntToScalar(360)); + + hsv[0] = h; + hsv[1] = s; + hsv[2] = v; +} + +SkColor SkHSVToColor(U8CPU a, const SkScalar hsv[3]) { + SkASSERT(hsv); + + SkScalar s = SkScalarPin(hsv[1], 0, 1); + SkScalar v = SkScalarPin(hsv[2], 0, 1); + + U8CPU v_byte = SkScalarRoundToInt(v * 255); + + if (SkScalarNearlyZero(s)) { // shade of gray + return SkColorSetARGB(a, v_byte, v_byte, v_byte); + } + SkScalar hx = (hsv[0] < 0 || hsv[0] >= SkIntToScalar(360)) ? 0 : hsv[0]/60; + SkScalar w = SkScalarFloorToScalar(hx); + SkScalar f = hx - w; + + unsigned p = SkScalarRoundToInt((SK_Scalar1 - s) * v * 255); + unsigned q = SkScalarRoundToInt((SK_Scalar1 - (s * f)) * v * 255); + unsigned t = SkScalarRoundToInt((SK_Scalar1 - (s * (SK_Scalar1 - f))) * v * 255); + + unsigned r, g, b; + + SkASSERT((unsigned)(w) < 6); + switch ((unsigned)(w)) { + case 0: r = v_byte; g = t; b = p; break; + case 1: r = q; g = v_byte; b = p; break; + case 2: r = p; g = v_byte; b = t; break; + case 3: r = p; g = q; b = v_byte; break; + case 4: r = t; g = p; b = v_byte; break; + default: r = v_byte; g = p; b = q; break; + } + return SkColorSetARGB(a, r, g, b); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +template <> +SkColor4f SkColor4f::FromColor(SkColor bgra) { + SkColor4f rgba; + Sk4f c4f = Sk4f_fromL32(bgra); +#ifdef SK_CPU_BENDIAN + // ARGB -> RGBA + c4f = SkNx_shuffle<1, 2, 3, 0>(c4f); +#else + // BGRA -> RGBA + c4f = swizzle_rb(c4f); +#endif + c4f.store(rgba.vec()); + return rgba; +} + +template <> +SkColor SkColor4f::toSkColor() const { + Sk4f c4f = Sk4f::Load(this->vec()); +#ifdef SK_CPU_BENDIAN + // RGBA -> ARGB + c4f = SkNx_shuffle<3, 0, 1, 2>(c4f); +#else + // RGBA -> BGRA + c4f = swizzle_rb(c4f); +#endif + return Sk4f_toL32(c4f); +} + +template <> +uint32_t SkColor4f::toBytes_RGBA() const { + return Sk4f_toL32(Sk4f::Load(this->vec())); +} + +template <> +SkColor4f SkColor4f::FromBytes_RGBA(uint32_t c) { + SkColor4f color; + Sk4f_fromL32(c).store(&color); + return color; +} + +template <> +SkPMColor4f SkPMColor4f::FromPMColor(SkPMColor c) { + SkPMColor4f color; + swizzle_rb_if_bgra(Sk4f_fromL32(c)).store(&color); + return color; +} + +template <> +uint32_t SkPMColor4f::toBytes_RGBA() const { + return Sk4f_toL32(Sk4f::Load(this->vec())); +} + +template <> +SkPMColor4f SkPMColor4f::FromBytes_RGBA(uint32_t c) { + SkPMColor4f color; + Sk4f_fromL32(c).store(&color); + return color; +} diff --git a/gfx/skia/skia/src/core/SkColorFilter.cpp b/gfx/skia/skia/src/core/SkColorFilter.cpp new file mode 100644 index 0000000000..babab34a5e --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorFilter.cpp @@ -0,0 +1,501 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkString.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkNx.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkColorFilterPriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/effects/generated/GrMixerEffect.h" +#endif + +bool SkColorFilter::onAsAColorMode(SkColor*, SkBlendMode*) const { + return false; +} + +bool SkColorFilter::onAsAColorMatrix(float matrix[20]) const { + return false; +} + +#if SK_SUPPORT_GPU +std::unique_ptr SkColorFilter::asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const { + return nullptr; +} +#endif + +bool SkColorFilter::appendStages(const SkStageRec& rec, bool shaderIsOpaque) const { + return this->onAppendStages(rec, shaderIsOpaque); +} + +SkColor SkColorFilter::filterColor(SkColor c) const { + // This is mostly meaningless. We should phase-out this call entirely. + SkColorSpace* cs = nullptr; + return this->filterColor4f(SkColor4f::FromColor(c), cs, cs).toSkColor(); +} + +SkColor4f SkColorFilter::filterColor4f(const SkColor4f& origSrcColor, SkColorSpace* srcCS, + SkColorSpace* dstCS) const { +#ifdef SK_SUPPORT_LEGACY_COLORFILTER_NO_SHADER + SkPMColor4f src = origSrcColor.premul(); + SkColor4f color = *(SkColor4f*)&src; +#else + SkColor4f color = origSrcColor; + SkColorSpaceXformSteps(srcCS, kUnpremul_SkAlphaType, + dstCS, kPremul_SkAlphaType).apply(color.vec()); +#endif + + constexpr size_t kEnoughForCommonFilters = 512; // big enough for compose+colormatrix + SkSTArenaAlloc alloc; + SkRasterPipeline pipeline(&alloc); + pipeline.append_constant_color(&alloc, color.vec()); + SkPaint dummyPaint; + SkStageRec rec = { + &pipeline, &alloc, kRGBA_F32_SkColorType, dstCS, dummyPaint, nullptr, SkMatrix::I() + }; + this->onAppendStages(rec, color.fA == 1); + + SkPMColor4f dst; + SkRasterPipeline_MemoryCtx dstPtr = { &dst, 0 }; + pipeline.append(SkRasterPipeline::store_f32, &dstPtr); + pipeline.run(0,0, 1,1); + return dst.unpremul(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * Since colorfilters may be used on the GPU backend, and in that case we may string together + * many GrFragmentProcessors, we might exceed some internal instruction/resource limit. + * + * Since we don't yet know *what* those limits might be when we construct the final shader, + * we just set an arbitrary limit during construction. If later we find smarter ways to know what + * the limnits are, we can change this constant (or remove it). + */ +#define SK_MAX_COMPOSE_COLORFILTER_COUNT 4 + +class SkComposeColorFilter : public SkColorFilter { +public: + uint32_t getFlags() const override { + // Can only claim alphaunchanged support if both our proxys do. + return fOuter->getFlags() & fInner->getFlags(); + } + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override { + bool innerIsOpaque = shaderIsOpaque; + if (!(fInner->getFlags() & kAlphaUnchanged_Flag)) { + innerIsOpaque = false; + } + return fInner->appendStages(rec, shaderIsOpaque) && + fOuter->appendStages(rec, innerIsOpaque); + } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor( + GrRecordingContext* context, const GrColorInfo& dstColorInfo) const override { + auto innerFP = fInner->asFragmentProcessor(context, dstColorInfo); + auto outerFP = fOuter->asFragmentProcessor(context, dstColorInfo); + if (!innerFP || !outerFP) { + return nullptr; + } + std::unique_ptr series[] = { std::move(innerFP), std::move(outerFP) }; + return GrFragmentProcessor::RunInSeries(series, 2); + } +#endif + +protected: + void flatten(SkWriteBuffer& buffer) const override { + buffer.writeFlattenable(fOuter.get()); + buffer.writeFlattenable(fInner.get()); + } + +private: + SK_FLATTENABLE_HOOKS(SkComposeColorFilter) + + SkComposeColorFilter(sk_sp outer, sk_sp inner, + int composedFilterCount) + : fOuter(std::move(outer)) + , fInner(std::move(inner)) + , fComposedFilterCount(composedFilterCount) + { + SkASSERT(composedFilterCount >= 2); + SkASSERT(composedFilterCount <= SK_MAX_COMPOSE_COLORFILTER_COUNT); + } + + int privateComposedFilterCount() const override { + return fComposedFilterCount; + } + + sk_sp fOuter; + sk_sp fInner; + const int fComposedFilterCount; + + friend class SkColorFilter; + + typedef SkColorFilter INHERITED; +}; + +sk_sp SkComposeColorFilter::CreateProc(SkReadBuffer& buffer) { + sk_sp outer(buffer.readColorFilter()); + sk_sp inner(buffer.readColorFilter()); + return outer ? outer->makeComposed(std::move(inner)) : inner; +} + + +sk_sp SkColorFilter::makeComposed(sk_sp inner) const { + if (!inner) { + return sk_ref_sp(this); + } + + int count = inner->privateComposedFilterCount() + this->privateComposedFilterCount(); + if (count > SK_MAX_COMPOSE_COLORFILTER_COUNT) { + return nullptr; + } + return sk_sp(new SkComposeColorFilter(sk_ref_sp(this), std::move(inner), count)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU +#include "src/gpu/effects/GrSRGBEffect.h" +#endif + +class SkSRGBGammaColorFilter : public SkColorFilter { +public: + enum class Direction { + kLinearToSRGB, + kSRGBToLinear, + }; + SkSRGBGammaColorFilter(Direction dir) : fDir(dir), fSteps([&]{ + // We handle premul/unpremul separately, so here just always upm->upm. + if (dir == Direction::kLinearToSRGB) { + return SkColorSpaceXformSteps{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType, + sk_srgb_singleton(), kUnpremul_SkAlphaType}; + } else { + return SkColorSpaceXformSteps{sk_srgb_singleton(), kUnpremul_SkAlphaType, + sk_srgb_linear_singleton(), kUnpremul_SkAlphaType}; + } + }()) {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override { + // wish our caller would let us know if our input was opaque... + GrSRGBEffect::Alpha alpha = GrSRGBEffect::Alpha::kPremul; + switch (fDir) { + case Direction::kLinearToSRGB: + return GrSRGBEffect::Make(GrSRGBEffect::Mode::kLinearToSRGB, alpha); + case Direction::kSRGBToLinear: + return GrSRGBEffect::Make(GrSRGBEffect::Mode::kSRGBToLinear, alpha); + } + return nullptr; + } +#endif + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override { + if (!shaderIsOpaque) { + rec.fPipeline->append(SkRasterPipeline::unpremul); + } + + // TODO: is it valuable to thread this through appendStages()? + bool shaderIsNormalized = false; + fSteps.apply(rec.fPipeline, shaderIsNormalized); + + if (!shaderIsOpaque) { + rec.fPipeline->append(SkRasterPipeline::premul); + } + return true; + } + +protected: + void flatten(SkWriteBuffer& buffer) const override { + buffer.write32(static_cast(fDir)); + } + +private: + SK_FLATTENABLE_HOOKS(SkSRGBGammaColorFilter) + + const Direction fDir; + SkColorSpaceXformSteps fSteps; + + friend class SkColorFilter; + typedef SkColorFilter INHERITED; +}; + +sk_sp SkSRGBGammaColorFilter::CreateProc(SkReadBuffer& buffer) { + uint32_t dir = buffer.read32(); + if (!buffer.validate(dir <= 1)) { + return nullptr; + } + return sk_sp(new SkSRGBGammaColorFilter(static_cast(dir))); +} + +template +sk_sp MakeSRGBGammaCF() { + static SkColorFilter* gSingleton = new SkSRGBGammaColorFilter(dir); + return sk_ref_sp(gSingleton); +} + +sk_sp SkColorFilters::LinearToSRGBGamma() { + return MakeSRGBGammaCF(); +} + +sk_sp SkColorFilters::SRGBToLinearGamma() { + return MakeSRGBGammaCF(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkMixerColorFilter : public SkColorFilter { +public: + SkMixerColorFilter(sk_sp cf0, sk_sp cf1, float weight) + : fCF0(std::move(cf0)), fCF1(std::move(cf1)), fWeight(weight) + { + SkASSERT(fCF0); + SkASSERT(fWeight >= 0 && fWeight <= 1); + } + + uint32_t getFlags() const override { + uint32_t f0 = fCF0->getFlags(); + uint32_t f1 = fCF1 ? fCF1->getFlags() : ~0U; + return f0 & f1; + } + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override { + // want cf0 * (1 - w) + cf1 * w == lerp(w) + // which means + // dr,dg,db,da <-- cf0 + // r,g,b,a <-- cf1 + struct State { + float orig_rgba[4 * SkRasterPipeline_kMaxStride]; + float filtered_rgba[4 * SkRasterPipeline_kMaxStride]; + }; + auto state = rec.fAlloc->make(); + SkRasterPipeline* p = rec.fPipeline; + + p->append(SkRasterPipeline::store_src, state->orig_rgba); + if (!fCF1) { + fCF0->appendStages(rec, shaderIsOpaque); + p->append(SkRasterPipeline::move_src_dst); + p->append(SkRasterPipeline::load_src, state->orig_rgba); + } else { + fCF0->appendStages(rec, shaderIsOpaque); + p->append(SkRasterPipeline::store_src, state->filtered_rgba); + p->append(SkRasterPipeline::load_src, state->orig_rgba); + fCF1->appendStages(rec, shaderIsOpaque); + p->append(SkRasterPipeline::load_dst, state->filtered_rgba); + } + float* storage = rec.fAlloc->make(fWeight); + p->append(SkRasterPipeline::lerp_1_float, storage); + return true; + } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor( + GrRecordingContext* context, const GrColorInfo& dstColorInfo) const override { + return GrMixerEffect::Make( + fCF0->asFragmentProcessor(context, dstColorInfo), + fCF1 ? fCF1->asFragmentProcessor(context, dstColorInfo) : nullptr, + fWeight); + } +#endif + +protected: + void flatten(SkWriteBuffer& buffer) const override { + buffer.writeFlattenable(fCF0.get()); + buffer.writeFlattenable(fCF1.get()); + buffer.writeScalar(fWeight); + } + +private: + SK_FLATTENABLE_HOOKS(SkMixerColorFilter) + + sk_sp fCF0; + sk_sp fCF1; + const float fWeight; + + friend class SkColorFilter; + + typedef SkColorFilter INHERITED; +}; + +sk_sp SkMixerColorFilter::CreateProc(SkReadBuffer& buffer) { + sk_sp cf0(buffer.readColorFilter()); + sk_sp cf1(buffer.readColorFilter()); + const float weight = buffer.readScalar(); + return SkColorFilters::Lerp(weight, std::move(cf0), std::move(cf1)); +} + +sk_sp SkColorFilters::Lerp(float weight, sk_sp cf0, + sk_sp cf1) { + if (!cf0 && !cf1) { + return nullptr; + } + if (SkScalarIsNaN(weight)) { + return nullptr; + } + + if (cf0 == cf1) { + return cf0; // or cf1 + } + + if (weight <= 0) { + return cf0; + } + if (weight >= 1) { + return cf1; + } + + return sk_sp(cf0 + ? new SkMixerColorFilter(std::move(cf0), std::move(cf1), weight) + : new SkMixerColorFilter(std::move(cf1), nullptr, 1 - weight)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "include/private/SkMutex.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/effects/GrSkSLFP.h" +#include "src/sksl/SkSLByteCode.h" + +class SkRuntimeColorFilter : public SkColorFilter { +public: + SkRuntimeColorFilter(int index, SkString sksl, sk_sp inputs, + void (*cpuFunction)(float[4], const void*)) + : fIndex(index) + , fSkSL(std::move(sksl)) + , fInputs(std::move(inputs)) + , fCpuFunction(cpuFunction) {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext* context, + const GrColorInfo&) const override { + return GrSkSLFP::Make(context, fIndex, "Runtime Color Filter", fSkSL, + fInputs ? fInputs->data() : nullptr, + fInputs ? fInputs->size() : 0); + } +#endif + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override { + if (fCpuFunction) { + struct CpuFuncCtx : public SkRasterPipeline_CallbackCtx { + SkRuntimeColorFilterFn cpuFn; + const void* inputs; + }; + auto ctx = rec.fAlloc->make(); + ctx->inputs = fInputs->data(); + ctx->cpuFn = fCpuFunction; + ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) { + auto ctx = (CpuFuncCtx*)arg; + for (int i = 0; i < active_pixels; i++) { + ctx->cpuFn(ctx->rgba + i * 4, ctx->inputs); + } + }; + rec.fPipeline->append(SkRasterPipeline::callback, ctx); + } else { + auto ctx = rec.fAlloc->make(); + // don't need to set ctx->paintColor + ctx->inputs = fInputs->data(); + ctx->ninputs = fInputs->size() / 4; + ctx->shaderConvention = false; + + SkAutoMutexExclusive ama(fByteCodeMutex); + if (!fByteCode) { + SkSL::Compiler c; + auto prog = c.convertProgram(SkSL::Program::kPipelineStage_Kind, + SkSL::String(fSkSL.c_str()), + SkSL::Program::Settings()); + if (c.errorCount()) { + SkDebugf("%s\n", c.errorText().c_str()); + return false; + } + fByteCode = c.toByteCode(*prog); + } + ctx->byteCode = fByteCode.get(); + ctx->fn = ctx->byteCode->getFunction("main"); + rec.fPipeline->append(SkRasterPipeline::interpreter, ctx); + } + return true; + } + +protected: + void flatten(SkWriteBuffer& buffer) const override { + // the client is responsible for ensuring that the indices match up between flattening and + // unflattening; we don't have a reasonable way to enforce that at the moment + buffer.writeInt(fIndex); + buffer.writeString(fSkSL.c_str()); + if (fInputs) { + buffer.writeDataAsByteArray(fInputs.get()); + } else { + buffer.writeByteArray(nullptr, 0); + } + } + +private: + SK_FLATTENABLE_HOOKS(SkRuntimeColorFilter) + + int fIndex; + SkString fSkSL; + sk_sp fInputs; + SkRuntimeColorFilterFn fCpuFunction; + + mutable SkMutex fByteCodeMutex; + mutable std::unique_ptr fByteCode; + + friend class SkColorFilter; + + typedef SkColorFilter INHERITED; +}; + +sk_sp SkRuntimeColorFilter::CreateProc(SkReadBuffer& buffer) { + int index = buffer.readInt(); + SkString sksl; + buffer.readString(&sksl); + sk_sp inputs = buffer.readByteArrayAsData(); + return sk_sp(new SkRuntimeColorFilter(index, std::move(sksl), std::move(inputs), + nullptr)); +} + +SkRuntimeColorFilterFactory::SkRuntimeColorFilterFactory(SkString sksl, + SkRuntimeColorFilterFn cpuFunc) + : fIndex(GrSkSLFP::NewIndex()) + , fSkSL(std::move(sksl)) + , fCpuFunc(cpuFunc) {} + +sk_sp SkRuntimeColorFilterFactory::make(sk_sp inputs) { + return sk_sp(new SkRuntimeColorFilter(fIndex, fSkSL, std::move(inputs), + fCpuFunc)); +} + +#endif // SK_SUPPORT_GPU + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkModeColorFilter.h" + +void SkColorFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkComposeColorFilter); + SK_REGISTER_FLATTENABLE(SkModeColorFilter); + SK_REGISTER_FLATTENABLE(SkSRGBGammaColorFilter); + SK_REGISTER_FLATTENABLE(SkMixerColorFilter); +#if SK_SUPPORT_GPU + SK_REGISTER_FLATTENABLE(SkRuntimeColorFilter); +#endif +} diff --git a/gfx/skia/skia/src/core/SkColorFilterPriv.h b/gfx/skia/skia/src/core/SkColorFilterPriv.h new file mode 100644 index 0000000000..a386e6ccb2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorFilterPriv.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkColorFilterPriv_DEFINED +#define SkColorFilterPriv_DEFINED + +#ifdef SK_SUPPORT_GPU + +#include "include/core/SkColorFilter.h" +#include "include/core/SkString.h" + +using SkRuntimeColorFilterFn = void(*)(float[4], const void*); + +class SK_API SkRuntimeColorFilterFactory { +public: + /** + * Creates a factory which creates runtime color filters. The SkSL must define a 'main' function + * with the signature 'void main(inout half4 color)'. The SkSL will be used when rendering in + * GPU mode, with the 'color' parameter providing the current value on input and receiving the + * new color value on exit. In software mode, the cpuFunc will be called with the current color + * and a pointer to the 'inputs' bytes. cpuFunc may be left null, in which case only GPU + * rendering is supported. + */ + SkRuntimeColorFilterFactory(SkString sksl, SkRuntimeColorFilterFn cpuFunc = nullptr); + + /** + * Creates a color filter instance with the specified inputs. In GPU rendering, the inputs are + * used to populate the values of 'in' variables. For instance, given the color filter: + * in uniform float x; + * in uniform float y; + * void main(inout half4 color) { + * ... + * } + * The values of the x and y inputs come from the 'inputs' SkData, which are laid out as a + * struct with two float elements. If there are no inputs, the 'inputs' parameter may be null. + * + * In CPU rendering, a pointer to the input bytes is passed as the second parameter to + * 'cpuFunc'. + */ + sk_sp make(sk_sp inputs); + +private: + int fIndex; + SkString fSkSL; + SkRuntimeColorFilterFn fCpuFunc; +}; + +#endif // SK_SUPPORT_GPU + +#endif // SkColorFilterPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkColorFilter_Matrix.cpp b/gfx/skia/skia/src/core/SkColorFilter_Matrix.cpp new file mode 100644 index 0000000000..cdc15dd018 --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorFilter_Matrix.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRefCnt.h" +#include "include/core/SkString.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/effects/SkColorMatrix.h" +#include "include/private/SkColorData.h" +#include "include/private/SkNx.h" +#include "src/core/SkColorFilter_Matrix.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +static uint16_t ComputeFlags(const float matrix[20]) { + const float* srcA = matrix + 15; + + return SkScalarNearlyZero (srcA[0]) + && SkScalarNearlyZero (srcA[1]) + && SkScalarNearlyZero (srcA[2]) + && SkScalarNearlyEqual(srcA[3], 1) + && SkScalarNearlyZero (srcA[4]) + ? SkColorFilter::kAlphaUnchanged_Flag : 0; +} + +SkColorFilter_Matrix::SkColorFilter_Matrix(const float array[20], Domain domain) + : fFlags(ComputeFlags(array)) + , fDomain(domain) { + memcpy(fMatrix, array, 20 * sizeof(float)); +} + +uint32_t SkColorFilter_Matrix::getFlags() const { + return this->INHERITED::getFlags() | fFlags; +} + +void SkColorFilter_Matrix::flatten(SkWriteBuffer& buffer) const { + SkASSERT(sizeof(fMatrix)/sizeof(float) == 20); + buffer.writeScalarArray(fMatrix, 20); + + // RGBA flag + buffer.writeBool(fDomain == Domain::kRGBA); +} + +sk_sp SkColorFilter_Matrix::CreateProc(SkReadBuffer& buffer) { + float matrix[20]; + if (!buffer.readScalarArray(matrix, 20)) { + return nullptr; + } + + auto is_rgba = buffer.isVersionLT(SkPicturePriv::kMatrixColorFilterDomain_Version) || + buffer.readBool(); + return is_rgba ? SkColorFilters::Matrix(matrix) + : SkColorFilters::HSLAMatrix(matrix); +} + +bool SkColorFilter_Matrix::onAsAColorMatrix(float matrix[20]) const { + if (matrix) { + memcpy(matrix, fMatrix, 20 * sizeof(float)); + } + return true; +} + +bool SkColorFilter_Matrix::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const { + const bool willStayOpaque = shaderIsOpaque && (fFlags & kAlphaUnchanged_Flag), + hsla = fDomain == Domain::kHSLA; + + SkRasterPipeline* p = rec.fPipeline; + if (!shaderIsOpaque) { p->append(SkRasterPipeline::unpremul); } + if ( hsla) { p->append(SkRasterPipeline::rgb_to_hsl); } + if ( true) { p->append(SkRasterPipeline::matrix_4x5, fMatrix); } + if ( hsla) { p->append(SkRasterPipeline::hsl_to_rgb); } + if ( true) { p->append(SkRasterPipeline::clamp_0); } + if ( true) { p->append(SkRasterPipeline::clamp_1); } + if (!willStayOpaque) { p->append(SkRasterPipeline::premul); } + return true; +} + +#if SK_SUPPORT_GPU +#include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h" +std::unique_ptr SkColorFilter_Matrix::asFragmentProcessor( + GrRecordingContext*, const GrColorInfo&) const { + if (fDomain == Domain::kHSLA) { + // TODO + return nullptr; + } + return GrColorMatrixFragmentProcessor::Make(fMatrix, + /* premulInput = */ true, + /* clampRGBOutput = */ true, + /* premulOutput = */ true); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +static sk_sp MakeMatrix(const float array[20], + SkColorFilter_Matrix::Domain domain) { + return sk_floats_are_finite(array, 20) + ? sk_make_sp(array, domain) + : nullptr; +} + +sk_sp SkColorFilters::Matrix(const float array[20]) { + return MakeMatrix(array, SkColorFilter_Matrix::Domain::kRGBA); +} + +sk_sp SkColorFilters::Matrix(const SkColorMatrix& cm) { + return MakeMatrix(cm.fMat, SkColorFilter_Matrix::Domain::kRGBA); +} + +sk_sp SkColorFilters::HSLAMatrix(const float array[20]) { + return MakeMatrix(array, SkColorFilter_Matrix::Domain::kHSLA); +} + +void SkColorFilter_Matrix::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkColorFilter_Matrix); + + // This subclass was removed 4/2019 + SkFlattenable::Register("SkColorMatrixFilterRowMajor255", + [](SkReadBuffer& buffer) -> sk_sp { + float matrix[20]; + if (buffer.readScalarArray(matrix, 20)) { + matrix[ 4] *= (1.0f/255); + matrix[ 9] *= (1.0f/255); + matrix[14] *= (1.0f/255); + matrix[19] *= (1.0f/255); + return SkColorFilters::Matrix(matrix); + } + return nullptr; + }); +} diff --git a/gfx/skia/skia/src/core/SkColorFilter_Matrix.h b/gfx/skia/skia/src/core/SkColorFilter_Matrix.h new file mode 100644 index 0000000000..7f019882a8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorFilter_Matrix.h @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorFilter_Matrix_DEFINED +#define SkColorFilter_Matrix_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkFlattenable.h" + +class SkColorFilter_Matrix : public SkColorFilter { +public: + enum class Domain : uint8_t { kRGBA, kHSLA }; + + explicit SkColorFilter_Matrix(const float array[20], Domain); + + uint32_t getFlags() const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override; +#endif + + static void RegisterFlattenables(); + +protected: + void flatten(SkWriteBuffer&) const override; + bool onAsAColorMatrix(float matrix[20]) const override; + +private: + SK_FLATTENABLE_HOOKS(SkColorFilter_Matrix) + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override; + + float fMatrix[20]; + uint16_t fFlags; + Domain fDomain; + + typedef SkColorFilter INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkColorSpace.cpp b/gfx/skia/skia/src/core/SkColorSpace.cpp new file mode 100644 index 0000000000..4e77d7c3ec --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorSpace.cpp @@ -0,0 +1,419 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorSpace.h" +#include "include/core/SkData.h" +#include "include/third_party/skcms/skcms.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkOpts.h" + +bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const { + return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50); +} + +SkColorSpace::SkColorSpace(const float transferFn[7], + const skcms_Matrix3x3& toXYZD50) { + memcpy(fToXYZD50_3x3, &toXYZD50.vals[0][0], 9*sizeof(float)); + fToXYZD50Hash = SkOpts::hash_fn(fToXYZD50_3x3, 9*sizeof(float), 0); + + memcpy(fTransferFn, transferFn, 7*sizeof(float)); + fTransferFnHash = SkOpts::hash_fn(fTransferFn, 7*sizeof(float), 0); +} + +static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) { + for (int r = 0; r < 3; ++r) { + for (int c = 0; c < 3; ++c) { + if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) { + return false; + } + } + } + + return true; +} + +sk_sp SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn, + const skcms_Matrix3x3& toXYZ) { + if (classify_transfer_fn(transferFn) == Bad_TF) { + return nullptr; + } + + const float* tf = &transferFn.g; + + if (is_almost_srgb(transferFn)) { + if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) { + return SkColorSpace::MakeSRGB(); + } + tf = &SkNamedTransferFn::kSRGB.g; + } else if (is_almost_2dot2(transferFn)) { + tf = &SkNamedTransferFn::k2Dot2.g; + } else if (is_almost_linear(transferFn)) { + if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) { + return SkColorSpace::MakeSRGBLinear(); + } + tf = &SkNamedTransferFn::kLinear.g; + } + + return sk_sp(new SkColorSpace(tf, toXYZ)); +} + +class SkColorSpaceSingletonFactory { +public: + static SkColorSpace* Make(const skcms_TransferFunction& transferFn, + const skcms_Matrix3x3& to_xyz) { + return new SkColorSpace(&transferFn.g, to_xyz); + } +}; + +SkColorSpace* sk_srgb_singleton() { + static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB, + SkNamedGamut::kSRGB); + return cs; +} + +SkColorSpace* sk_srgb_linear_singleton() { + static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear, + SkNamedGamut::kSRGB); + return cs; +} + +sk_sp SkColorSpace::MakeSRGB() { + return sk_ref_sp(sk_srgb_singleton()); +} + +sk_sp SkColorSpace::MakeSRGBLinear() { + return sk_ref_sp(sk_srgb_linear_singleton()); +} + +void SkColorSpace::computeLazyDstFields() const { + fLazyDstFieldsOnce([this] { + + // Invert 3x3 gamut, defaulting to sRGB if we can't. + { + skcms_Matrix3x3 fwd, inv; + memcpy(&fwd, fToXYZD50_3x3, 9*sizeof(float)); + if (!skcms_Matrix3x3_invert(&fwd, &inv)) { + SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50, &inv)); + } + memcpy(fFromXYZD50_3x3, &inv, 9*sizeof(float)); + } + + // Invert transfer function, defaulting to sRGB if we can't. + { + skcms_TransferFunction fwd, inv; + this->transferFn(&fwd.g); + if (!skcms_TransferFunction_invert(&fwd, &inv)) { + inv = *skcms_sRGB_Inverse_TransferFunction(); + } + memcpy(fInvTransferFn, &inv, 7*sizeof(float)); + } + + }); +} + +bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const { + // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers + // already pass pointers to an skcms struct). Then remove this function, and update the two + // remaining callers to do the right thing with transferFn and classify. + this->transferFn(&coeffs->g); + return classify_transfer_fn(*coeffs) == sRGBish_TF; +} + +void SkColorSpace::transferFn(float gabcdef[7]) const { + memcpy(gabcdef, &fTransferFn, 7*sizeof(float)); +} + +void SkColorSpace::invTransferFn(float gabcdef[7]) const { + this->computeLazyDstFields(); + memcpy(gabcdef, &fInvTransferFn, 7*sizeof(float)); +} + +bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const { + toXYZD50->set3x3RowMajorf(fToXYZD50_3x3); + return true; +} + +bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const { + memcpy(toXYZD50, fToXYZD50_3x3, 9*sizeof(float)); + return true; +} + +void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, float src_to_dst[9]) const { + dst->computeLazyDstFields(); + + skcms_Matrix3x3 toXYZD50, + fromXYZD50; + + memcpy(& toXYZD50, this-> fToXYZD50_3x3, 9*sizeof(float)); + memcpy(&fromXYZD50, dst ->fFromXYZD50_3x3, 9*sizeof(float)); + + skcms_Matrix3x3 srcToDst = skcms_Matrix3x3_concat(&fromXYZD50, &toXYZD50); + memcpy(src_to_dst, &srcToDst, 9*sizeof(float)); +} + +bool SkColorSpace::isSRGB() const { + return sk_srgb_singleton() == this; +} + +bool SkColorSpace::gammaCloseToSRGB() const { + // Nearly-equal transfer functions were snapped at construction time, so just do an exact test + return memcmp(fTransferFn, &SkNamedTransferFn::kSRGB.g, 7*sizeof(float)) == 0; +} + +bool SkColorSpace::gammaIsLinear() const { + // Nearly-equal transfer functions were snapped at construction time, so just do an exact test + return memcmp(fTransferFn, &SkNamedTransferFn::kLinear.g, 7*sizeof(float)) == 0; +} + +sk_sp SkColorSpace::makeLinearGamma() const { + if (this->gammaIsLinear()) { + return sk_ref_sp(const_cast(this)); + } + skcms_Matrix3x3 gamut; + this->toXYZD50(&gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut); +} + +sk_sp SkColorSpace::makeSRGBGamma() const { + if (this->gammaCloseToSRGB()) { + return sk_ref_sp(const_cast(this)); + } + skcms_Matrix3x3 gamut; + this->toXYZD50(&gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut); +} + +sk_sp SkColorSpace::makeColorSpin() const { + skcms_Matrix3x3 spin = {{ + { 0, 0, 1 }, + { 1, 0, 0 }, + { 0, 1, 0 }, + }}; + + skcms_Matrix3x3 toXYZ; + this->toXYZD50(&toXYZ); + + skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&toXYZ, &spin); + + return sk_sp(new SkColorSpace(fTransferFn, spun)); +} + +void SkColorSpace::toProfile(skcms_ICCProfile* profile) const { + skcms_TransferFunction tf; + skcms_Matrix3x3 toXYZD50; + + memcpy(&tf, fTransferFn, 7*sizeof(float)); + memcpy(&toXYZD50, fToXYZD50_3x3, 9*sizeof(float)); + + skcms_Init (profile); + skcms_SetTransferFunction(profile, &tf); + skcms_SetXYZD50 (profile, &toXYZD50); +} + +sk_sp SkColorSpace::Make(const skcms_ICCProfile& profile) { + // TODO: move below ≈sRGB test? + if (!profile.has_toXYZD50 || !profile.has_trc) { + return nullptr; + } + + if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) { + return SkColorSpace::MakeSRGB(); + } + + // TODO: can we save this work and skip lazily inverting the matrix later? + skcms_Matrix3x3 inv; + if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) { + return nullptr; + } + + // We can't work with tables or mismatched parametric curves, + // but if they all look close enough to sRGB, that's fine. + // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB? + const skcms_Curve* trc = profile.trc; + if (trc[0].table_entries != 0 || + trc[1].table_entries != 0 || + trc[2].table_entries != 0 || + 0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) || + 0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric))) + { + if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) { + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50); + } + return nullptr; + } + + return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +enum Version { + k0_Version, // Initial version, header + flags for matrix and profile + k1_Version, // Simple header (version tag) + 16 floats + + kCurrent_Version = k1_Version, +}; + +enum NamedColorSpace { + kSRGB_NamedColorSpace, + kAdobeRGB_NamedColorSpace, + kSRGBLinear_NamedColorSpace, +}; + +enum NamedGamma { + kLinear_NamedGamma, + kSRGB_NamedGamma, + k2Dot2_NamedGamma, +}; + +struct ColorSpaceHeader { + // Flag values, only used by old (k0_Version) serialization + static constexpr uint8_t kMatrix_Flag = 1 << 0; + static constexpr uint8_t kICC_Flag = 1 << 1; + static constexpr uint8_t kTransferFn_Flag = 1 << 3; + + uint8_t fVersion = kCurrent_Version; + + // Other fields are only used by k0_Version. Could be re-purposed in future versions. + uint8_t fNamed = 0; + uint8_t fGammaNamed = 0; + uint8_t fFlags = 0; +}; + +size_t SkColorSpace::writeToMemory(void* memory) const { + if (memory) { + *((ColorSpaceHeader*) memory) = ColorSpaceHeader(); + memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); + + memcpy(memory, fTransferFn, 7 * sizeof(float)); + memory = SkTAddOffset(memory, 7 * sizeof(float)); + + memcpy(memory, fToXYZD50_3x3, 9 * sizeof(float)); + } + + return sizeof(ColorSpaceHeader) + 16 * sizeof(float); +} + +sk_sp SkColorSpace::serialize() const { + sk_sp data = SkData::MakeUninitialized(this->writeToMemory(nullptr)); + this->writeToMemory(data->writable_data()); + return data; +} + +sk_sp SkColorSpace::Deserialize(const void* data, size_t length) { + if (length < sizeof(ColorSpaceHeader)) { + return nullptr; + } + + ColorSpaceHeader header = *((const ColorSpaceHeader*) data); + data = SkTAddOffset(data, sizeof(ColorSpaceHeader)); + length -= sizeof(ColorSpaceHeader); + if (k1_Version == header.fVersion) { + if (length < 16 * sizeof(float)) { + return nullptr; + } + + skcms_TransferFunction transferFn; + memcpy(&transferFn, data, 7 * sizeof(float)); + data = SkTAddOffset(data, 7 * sizeof(float)); + + skcms_Matrix3x3 toXYZ; + memcpy(&toXYZ, data, 9 * sizeof(float)); + return SkColorSpace::MakeRGB(transferFn, toXYZ); + } else if (k0_Version == header.fVersion) { + if (0 == header.fFlags) { + switch ((NamedColorSpace)header.fNamed) { + case kSRGB_NamedColorSpace: + return SkColorSpace::MakeSRGB(); + case kSRGBLinear_NamedColorSpace: + return SkColorSpace::MakeSRGBLinear(); + case kAdobeRGB_NamedColorSpace: + return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, + SkNamedGamut::kAdobeRGB); + } + } + + auto make_named_tf = [=](const skcms_TransferFunction& tf) { + if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) { + return sk_sp(nullptr); + } + + // Version 0 matrix is row-major 3x4 + skcms_Matrix3x3 toXYZ; + memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float)); + memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float)); + memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float)); + return SkColorSpace::MakeRGB(tf, toXYZ); + }; + + switch ((NamedGamma) header.fGammaNamed) { + case kSRGB_NamedGamma: + return make_named_tf(SkNamedTransferFn::kSRGB); + case k2Dot2_NamedGamma: + return make_named_tf(SkNamedTransferFn::k2Dot2); + case kLinear_NamedGamma: + return make_named_tf(SkNamedTransferFn::kLinear); + default: + break; + } + + switch (header.fFlags) { + case ColorSpaceHeader::kICC_Flag: { + // Deprecated and unsupported code path + return nullptr; + } + case ColorSpaceHeader::kTransferFn_Flag: { + if (length < 19 * sizeof(float)) { + return nullptr; + } + + // Version 0 TF is in abcdefg order + skcms_TransferFunction transferFn; + transferFn.a = *(((const float*) data) + 0); + transferFn.b = *(((const float*) data) + 1); + transferFn.c = *(((const float*) data) + 2); + transferFn.d = *(((const float*) data) + 3); + transferFn.e = *(((const float*) data) + 4); + transferFn.f = *(((const float*) data) + 5); + transferFn.g = *(((const float*) data) + 6); + data = SkTAddOffset(data, 7 * sizeof(float)); + + // Version 0 matrix is row-major 3x4 + skcms_Matrix3x3 toXYZ; + memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float)); + memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float)); + memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float)); + return SkColorSpace::MakeRGB(transferFn, toXYZ); + } + default: + return nullptr; + } + } else { + return nullptr; + } +} + +bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) { + if (x == y) { + return true; + } + + if (!x || !y) { + return false; + } + + if (x->hash() == y->hash()) { + for (int i = 0; i < 7; i++) { + SkASSERT(x-> fTransferFn[i] == y-> fTransferFn[i] && "Hash collsion"); + } + for (int i = 0; i < 9; i++) { + SkASSERT(x->fToXYZD50_3x3[i] == y->fToXYZD50_3x3[i] && "Hash collsion"); + } + return true; + } + return false; +} diff --git a/gfx/skia/skia/src/core/SkColorSpacePriv.h b/gfx/skia/skia/src/core/SkColorSpacePriv.h new file mode 100644 index 0000000000..425357db7b --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorSpacePriv.h @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkColorSpacePriv_DEFINED +#define SkColorSpacePriv_DEFINED + +#include + +#include "include/core/SkColorSpace.h" +#include "include/private/SkFixed.h" + +#define SkColorSpacePrintf(...) + +// A gamut narrower than sRGB, useful for testing. +static constexpr skcms_Matrix3x3 gNarrow_toXYZD50 = {{ + { 0.190974f, 0.404865f, 0.368380f }, + { 0.114746f, 0.582937f, 0.302318f }, + { 0.032925f, 0.153615f, 0.638669f }, +}}; + +static inline bool color_space_almost_equal(float a, float b) { + return SkTAbs(a - b) < 0.01f; +} + +// Let's use a stricter version for transfer functions. Worst case, these are encoded +// in ICC format, which offers 16-bits of fractional precision. +static inline bool transfer_fn_almost_equal(float a, float b) { + return SkTAbs(a - b) < 0.001f; +} + +// NOTE: All of this logic is copied from skcms.cc, and needs to be kept in sync. + +// Most transfer functions we work with are sRGBish. +// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense, +// and repurpose the other fields to hold the parameters of the HDR functions. +enum TFKind { Bad_TF, sRGBish_TF, PQish_TF, HLGish_TF, HLGinvish_TF }; + +static inline TFKind classify_transfer_fn(const skcms_TransferFunction& tf) { + if (tf.g < 0 && (int)tf.g == tf.g) { + // TODO: sanity checks for PQ/HLG like we do for sRGBish. + switch (-(int)tf.g) { + case PQish_TF: return PQish_TF; + case HLGish_TF: return HLGish_TF; + case HLGinvish_TF: return HLGinvish_TF; + } + return Bad_TF; + } + + // Basic sanity checks for sRGBish transfer functions. + if (sk_float_isfinite(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g) + // a,c,d,g should be non-negative to make any sense. + && tf.a >= 0 + && tf.c >= 0 + && tf.d >= 0 + && tf.g >= 0 + // Raising a negative value to a fractional tf->g produces complex numbers. + && tf.a * tf.d + tf.b >= 0) { + return sRGBish_TF; + } + + return Bad_TF; +} + +static inline bool is_almost_srgb(const skcms_TransferFunction& coeffs) { + return transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.a, coeffs.a) && + transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.b, coeffs.b) && + transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.c, coeffs.c) && + transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.d, coeffs.d) && + transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.e, coeffs.e) && + transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.f, coeffs.f) && + transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.g, coeffs.g); +} + +static inline bool is_almost_2dot2(const skcms_TransferFunction& coeffs) { + return transfer_fn_almost_equal(1.0f, coeffs.a) && + transfer_fn_almost_equal(0.0f, coeffs.b) && + transfer_fn_almost_equal(0.0f, coeffs.e) && + transfer_fn_almost_equal(2.2f, coeffs.g) && + coeffs.d <= 0.0f; +} + +static inline bool is_almost_linear(const skcms_TransferFunction& coeffs) { + // OutputVal = InputVal ^ 1.0f + const bool linearExp = + transfer_fn_almost_equal(1.0f, coeffs.a) && + transfer_fn_almost_equal(0.0f, coeffs.b) && + transfer_fn_almost_equal(0.0f, coeffs.e) && + transfer_fn_almost_equal(1.0f, coeffs.g) && + coeffs.d <= 0.0f; + + // OutputVal = 1.0f * InputVal + const bool linearFn = + transfer_fn_almost_equal(1.0f, coeffs.c) && + transfer_fn_almost_equal(0.0f, coeffs.f) && + coeffs.d >= 1.0f; + + return linearExp || linearFn; +} + +// Return raw pointers to commonly used SkColorSpaces. +// No need to ref/unref these, but if you do, do it in pairs. +SkColorSpace* sk_srgb_singleton(); +SkColorSpace* sk_srgb_linear_singleton(); + +#endif // SkColorSpacePriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkColorSpaceXformSteps.cpp b/gfx/skia/skia/src/core/SkColorSpaceXformSteps.cpp new file mode 100644 index 0000000000..fe03bca043 --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorSpaceXformSteps.cpp @@ -0,0 +1,165 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/third_party/skcms/skcms.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkRasterPipeline.h" + +// TODO(mtklein): explain the logic of this file + +bool SkColorSpaceXformSteps::Required(SkColorSpace* src, SkColorSpace* dst) { + // Any SkAlphaType will work fine here as long as we use the same one. + SkAlphaType at = kPremul_SkAlphaType; + return 0 != SkColorSpaceXformSteps(src, at, + dst, at).flags.mask(); + // TODO(mtklein): quicker impl. that doesn't construct an SkColorSpaceXformSteps? +} + +SkColorSpaceXformSteps::SkColorSpaceXformSteps(SkColorSpace* src, SkAlphaType srcAT, + SkColorSpace* dst, SkAlphaType dstAT) { + // Opaque outputs are treated as the same alpha type as the source input. + // TODO: we'd really like to have a good way of explaining why we think this is useful. + if (dstAT == kOpaque_SkAlphaType) { + dstAT = srcAT; + } + + // We have some options about what to do with null src or dst here. + // This pair seems to be the most consistent with legacy expectations. + if (!src) { src = sk_srgb_singleton(); } + if (!dst) { dst = src; } + + if (src->hash() == dst->hash() && srcAT == dstAT) { + SkASSERT(SkColorSpace::Equals(src,dst)); + return; + } + + this->flags.unpremul = srcAT == kPremul_SkAlphaType; + this->flags.linearize = !src->gammaIsLinear(); + this->flags.gamut_transform = src->toXYZD50Hash() != dst->toXYZD50Hash(); + this->flags.encode = !dst->gammaIsLinear(); + this->flags.premul = srcAT != kOpaque_SkAlphaType && dstAT == kPremul_SkAlphaType; + + if (this->flags.gamut_transform) { + float row_major[9]; // TODO: switch src_to_dst_matrix to row-major + src->gamutTransformTo(dst, row_major); + + this->src_to_dst_matrix[0] = row_major[0]; + this->src_to_dst_matrix[1] = row_major[3]; + this->src_to_dst_matrix[2] = row_major[6]; + + this->src_to_dst_matrix[3] = row_major[1]; + this->src_to_dst_matrix[4] = row_major[4]; + this->src_to_dst_matrix[5] = row_major[7]; + + this->src_to_dst_matrix[6] = row_major[2]; + this->src_to_dst_matrix[7] = row_major[5]; + this->src_to_dst_matrix[8] = row_major[8]; + } else { + #ifdef SK_DEBUG + skcms_Matrix3x3 srcM, dstM; + src->toXYZD50(&srcM); + dst->toXYZD50(&dstM); + SkASSERT(0 == memcmp(&srcM, &dstM, 9*sizeof(float)) && "Hash collision"); + #endif + } + + // Fill out all the transfer functions we'll use. + src-> transferFn(&this->srcTF .g); + dst->invTransferFn(&this->dstTFInv.g); + + this->srcTF_is_sRGB = src->gammaCloseToSRGB(); + this->dstTF_is_sRGB = dst->gammaCloseToSRGB(); + + // If we linearize then immediately reencode with the same transfer function, skip both. + if ( this->flags.linearize && + !this->flags.gamut_transform && + this->flags.encode && + src->transferFnHash() == dst->transferFnHash()) + { + #ifdef SK_DEBUG + float dstTF[7]; + dst->transferFn(dstTF); + for (int i = 0; i < 7; i++) { + SkASSERT( (&srcTF.g)[i] == dstTF[i] && "Hash collision" ); + } + #endif + this->flags.linearize = false; + this->flags.encode = false; + } + + // Skip unpremul...premul if there are no non-linear operations between. + if ( this->flags.unpremul && + !this->flags.linearize && + !this->flags.encode && + this->flags.premul) + { + this->flags.unpremul = false; + this->flags.premul = false; + } +} + +void SkColorSpaceXformSteps::apply(float* rgba) const { + if (flags.unpremul) { + // I don't know why isfinite(x) stopped working on the Chromecast bots... + auto is_finite = [](float x) { return x*0 == 0; }; + + float invA = is_finite(1.0f / rgba[3]) ? 1.0f / rgba[3] : 0; + rgba[0] *= invA; + rgba[1] *= invA; + rgba[2] *= invA; + } + if (flags.linearize) { + rgba[0] = skcms_TransferFunction_eval(&srcTF, rgba[0]); + rgba[1] = skcms_TransferFunction_eval(&srcTF, rgba[1]); + rgba[2] = skcms_TransferFunction_eval(&srcTF, rgba[2]); + } + if (flags.gamut_transform) { + float temp[3] = { rgba[0], rgba[1], rgba[2] }; + for (int i = 0; i < 3; ++i) { + rgba[i] = src_to_dst_matrix[ i] * temp[0] + + src_to_dst_matrix[3 + i] * temp[1] + + src_to_dst_matrix[6 + i] * temp[2]; + } + } + if (flags.encode) { + rgba[0] = skcms_TransferFunction_eval(&dstTFInv, rgba[0]); + rgba[1] = skcms_TransferFunction_eval(&dstTFInv, rgba[1]); + rgba[2] = skcms_TransferFunction_eval(&dstTFInv, rgba[2]); + } + if (flags.premul) { + rgba[0] *= rgba[3]; + rgba[1] *= rgba[3]; + rgba[2] *= rgba[3]; + } +} + +void SkColorSpaceXformSteps::apply(SkRasterPipeline* p, bool src_is_normalized) const { +#if defined(SK_LEGACY_SRGB_STAGE_CHOICE) + src_is_normalized = true; +#endif + if (flags.unpremul) { p->append(SkRasterPipeline::unpremul); } + if (flags.linearize) { + if (src_is_normalized && srcTF_is_sRGB) { + p->append(SkRasterPipeline::from_srgb); + } else { + p->append_transfer_function(srcTF); + } + } + if (flags.gamut_transform) { + p->append(SkRasterPipeline::matrix_3x3, &src_to_dst_matrix); + } + if (flags.encode) { + if (src_is_normalized && dstTF_is_sRGB) { + p->append(SkRasterPipeline::to_srgb); + } else { + p->append_transfer_function(dstTFInv); + } + } + if (flags.premul) { p->append(SkRasterPipeline::premul); } +} + diff --git a/gfx/skia/skia/src/core/SkColorSpaceXformSteps.h b/gfx/skia/skia/src/core/SkColorSpaceXformSteps.h new file mode 100644 index 0000000000..1a15643839 --- /dev/null +++ b/gfx/skia/skia/src/core/SkColorSpaceXformSteps.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorSpaceXformSteps_DEFINED +#define SkColorSpaceXformSteps_DEFINED + +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" + +class SkRasterPipeline; + +struct SkColorSpaceXformSteps { + // Returns true if SkColorSpaceXformSteps must be applied + // to draw content in `src` into a destination in `dst`. + static bool Required(SkColorSpace* src, SkColorSpace* dst); + + struct Flags { + bool unpremul = false; + bool linearize = false; + bool gamut_transform = false; + bool encode = false; + bool premul = false; + + uint32_t mask() const { + return (unpremul ? 1 : 0) + | (linearize ? 2 : 0) + | (gamut_transform ? 4 : 0) + | (encode ? 8 : 0) + | (premul ? 16 : 0); + } + }; + + SkColorSpaceXformSteps(SkColorSpace* src, SkAlphaType srcAT, + SkColorSpace* dst, SkAlphaType dstAT); + + void apply(float rgba[4]) const; + void apply(SkRasterPipeline*, bool src_is_normalized) const; + + void apply(SkRasterPipeline* p, SkColorType srcCT) const { + #if 0 + this->apply(p, srcCT < kRGBA_F16_SkColorType); + #else + // F16Norm is normalized, but to make diffing with F16 easier we + // intentionally take the slower, non-normalized path here. + this->apply(p, srcCT < kRGBA_F16Norm_SkColorType); + #endif + } + + Flags flags; + + bool srcTF_is_sRGB, + dstTF_is_sRGB; + skcms_TransferFunction srcTF, // Apply for linearize. + dstTFInv; // Apply for encode. + float src_to_dst_matrix[9]; // Apply this 3x3 column-major matrix for gamut_transform. +}; + +#endif//SkColorSpaceXformSteps_DEFINED diff --git a/gfx/skia/skia/src/core/SkContourMeasure.cpp b/gfx/skia/skia/src/core/SkContourMeasure.cpp new file mode 100644 index 0000000000..836b0a2e16 --- /dev/null +++ b/gfx/skia/skia/src/core/SkContourMeasure.cpp @@ -0,0 +1,651 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkContourMeasure.h" +#include "include/core/SkPath.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPathMeasurePriv.h" +#include "src/core/SkTSearch.h" + +#define kMaxTValue 0x3FFFFFFF + +constexpr static inline SkScalar tValue2Scalar(int t) { + SkASSERT((unsigned)t <= kMaxTValue); + // 1/kMaxTValue can't be represented as a float, but it's close and the limits work fine. + const SkScalar kMaxTReciprocal = 1.0f / (SkScalar)kMaxTValue; + return t * kMaxTReciprocal; +} + +static_assert(0.0f == tValue2Scalar( 0), "Lower limit should be exact."); +static_assert(1.0f == tValue2Scalar(kMaxTValue), "Upper limit should be exact."); + +SkScalar SkContourMeasure::Segment::getScalarT() const { + return tValue2Scalar(fTValue); +} + +void SkContourMeasure_segTo(const SkPoint pts[], unsigned segType, + SkScalar startT, SkScalar stopT, SkPath* dst) { + SkASSERT(startT >= 0 && startT <= SK_Scalar1); + SkASSERT(stopT >= 0 && stopT <= SK_Scalar1); + SkASSERT(startT <= stopT); + + if (startT == stopT) { + if (!dst->isEmpty()) { + /* if the dash as a zero-length on segment, add a corresponding zero-length line. + The stroke code will add end caps to zero length lines as appropriate */ + SkPoint lastPt; + SkAssertResult(dst->getLastPt(&lastPt)); + dst->lineTo(lastPt); + } + return; + } + + SkPoint tmp0[7], tmp1[7]; + + switch (segType) { + case kLine_SegType: + if (SK_Scalar1 == stopT) { + dst->lineTo(pts[1]); + } else { + dst->lineTo(SkScalarInterp(pts[0].fX, pts[1].fX, stopT), + SkScalarInterp(pts[0].fY, pts[1].fY, stopT)); + } + break; + case kQuad_SegType: + if (0 == startT) { + if (SK_Scalar1 == stopT) { + dst->quadTo(pts[1], pts[2]); + } else { + SkChopQuadAt(pts, tmp0, stopT); + dst->quadTo(tmp0[1], tmp0[2]); + } + } else { + SkChopQuadAt(pts, tmp0, startT); + if (SK_Scalar1 == stopT) { + dst->quadTo(tmp0[3], tmp0[4]); + } else { + SkChopQuadAt(&tmp0[2], tmp1, (stopT - startT) / (1 - startT)); + dst->quadTo(tmp1[1], tmp1[2]); + } + } + break; + case kConic_SegType: { + SkConic conic(pts[0], pts[2], pts[3], pts[1].fX); + + if (0 == startT) { + if (SK_Scalar1 == stopT) { + dst->conicTo(conic.fPts[1], conic.fPts[2], conic.fW); + } else { + SkConic tmp[2]; + if (conic.chopAt(stopT, tmp)) { + dst->conicTo(tmp[0].fPts[1], tmp[0].fPts[2], tmp[0].fW); + } + } + } else { + if (SK_Scalar1 == stopT) { + SkConic tmp1[2]; + if (conic.chopAt(startT, tmp1)) { + dst->conicTo(tmp1[1].fPts[1], tmp1[1].fPts[2], tmp1[1].fW); + } + } else { + SkConic tmp; + conic.chopAt(startT, stopT, &tmp); + dst->conicTo(tmp.fPts[1], tmp.fPts[2], tmp.fW); + } + } + } break; + case kCubic_SegType: + if (0 == startT) { + if (SK_Scalar1 == stopT) { + dst->cubicTo(pts[1], pts[2], pts[3]); + } else { + SkChopCubicAt(pts, tmp0, stopT); + dst->cubicTo(tmp0[1], tmp0[2], tmp0[3]); + } + } else { + SkChopCubicAt(pts, tmp0, startT); + if (SK_Scalar1 == stopT) { + dst->cubicTo(tmp0[4], tmp0[5], tmp0[6]); + } else { + SkChopCubicAt(&tmp0[3], tmp1, (stopT - startT) / (1 - startT)); + dst->cubicTo(tmp1[1], tmp1[2], tmp1[3]); + } + } + break; + default: + SK_ABORT("unknown segType"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +static inline int tspan_big_enough(int tspan) { + SkASSERT((unsigned)tspan <= kMaxTValue); + return tspan >> 10; +} + +// can't use tangents, since we need [0..1..................2] to be seen +// as definitely not a line (it is when drawn, but not parametrically) +// so we compare midpoints +#define CHEAP_DIST_LIMIT (SK_Scalar1/2) // just made this value up + +static bool quad_too_curvy(const SkPoint pts[3], SkScalar tolerance) { + // diff = (a/4 + b/2 + c/4) - (a/2 + c/2) + // diff = -a/4 + b/2 - c/4 + SkScalar dx = SkScalarHalf(pts[1].fX) - + SkScalarHalf(SkScalarHalf(pts[0].fX + pts[2].fX)); + SkScalar dy = SkScalarHalf(pts[1].fY) - + SkScalarHalf(SkScalarHalf(pts[0].fY + pts[2].fY)); + + SkScalar dist = SkMaxScalar(SkScalarAbs(dx), SkScalarAbs(dy)); + return dist > tolerance; +} + +static bool conic_too_curvy(const SkPoint& firstPt, const SkPoint& midTPt, + const SkPoint& lastPt, SkScalar tolerance) { + SkPoint midEnds = firstPt + lastPt; + midEnds *= 0.5f; + SkVector dxy = midTPt - midEnds; + SkScalar dist = SkMaxScalar(SkScalarAbs(dxy.fX), SkScalarAbs(dxy.fY)); + return dist > tolerance; +} + +static bool cheap_dist_exceeds_limit(const SkPoint& pt, SkScalar x, SkScalar y, + SkScalar tolerance) { + SkScalar dist = SkMaxScalar(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY)); + // just made up the 1/2 + return dist > tolerance; +} + +static bool cubic_too_curvy(const SkPoint pts[4], SkScalar tolerance) { + return cheap_dist_exceeds_limit(pts[1], + SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1/3), + SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1/3), tolerance) + || + cheap_dist_exceeds_limit(pts[2], + SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1*2/3), + SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3), tolerance); +} + +SkScalar SkContourMeasureIter::compute_quad_segs(const SkPoint pts[3], SkScalar distance, + int mint, int maxt, unsigned ptIndex) { + if (tspan_big_enough(maxt - mint) && quad_too_curvy(pts, fTolerance)) { + SkPoint tmp[5]; + int halft = (mint + maxt) >> 1; + + SkChopQuadAtHalf(pts, tmp); + distance = this->compute_quad_segs(tmp, distance, mint, halft, ptIndex); + distance = this->compute_quad_segs(&tmp[2], distance, halft, maxt, ptIndex); + } else { + SkScalar d = SkPoint::Distance(pts[0], pts[2]); + SkScalar prevD = distance; + distance += d; + if (distance > prevD) { + SkASSERT(ptIndex < (unsigned)fPts.count()); + SkContourMeasure::Segment* seg = fSegments.append(); + seg->fDistance = distance; + seg->fPtIndex = ptIndex; + seg->fType = kQuad_SegType; + seg->fTValue = maxt; + } + } + return distance; +} + +SkScalar SkContourMeasureIter::compute_conic_segs(const SkConic& conic, SkScalar distance, + int mint, const SkPoint& minPt, + int maxt, const SkPoint& maxPt, + unsigned ptIndex) { + int halft = (mint + maxt) >> 1; + SkPoint halfPt = conic.evalAt(tValue2Scalar(halft)); + if (!halfPt.isFinite()) { + return distance; + } + if (tspan_big_enough(maxt - mint) && conic_too_curvy(minPt, halfPt, maxPt, fTolerance)) { + distance = this->compute_conic_segs(conic, distance, mint, minPt, halft, halfPt, ptIndex); + distance = this->compute_conic_segs(conic, distance, halft, halfPt, maxt, maxPt, ptIndex); + } else { + SkScalar d = SkPoint::Distance(minPt, maxPt); + SkScalar prevD = distance; + distance += d; + if (distance > prevD) { + SkASSERT(ptIndex < (unsigned)fPts.count()); + SkContourMeasure::Segment* seg = fSegments.append(); + seg->fDistance = distance; + seg->fPtIndex = ptIndex; + seg->fType = kConic_SegType; + seg->fTValue = maxt; + } + } + return distance; +} + +SkScalar SkContourMeasureIter::compute_cubic_segs(const SkPoint pts[4], SkScalar distance, + int mint, int maxt, unsigned ptIndex) { + if (tspan_big_enough(maxt - mint) && cubic_too_curvy(pts, fTolerance)) { + SkPoint tmp[7]; + int halft = (mint + maxt) >> 1; + + SkChopCubicAtHalf(pts, tmp); + distance = this->compute_cubic_segs(tmp, distance, mint, halft, ptIndex); + distance = this->compute_cubic_segs(&tmp[3], distance, halft, maxt, ptIndex); + } else { + SkScalar d = SkPoint::Distance(pts[0], pts[3]); + SkScalar prevD = distance; + distance += d; + if (distance > prevD) { + SkASSERT(ptIndex < (unsigned)fPts.count()); + SkContourMeasure::Segment* seg = fSegments.append(); + seg->fDistance = distance; + seg->fPtIndex = ptIndex; + seg->fType = kCubic_SegType; + seg->fTValue = maxt; + } + } + return distance; +} + +SkScalar SkContourMeasureIter::compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance, + unsigned ptIndex) { + SkScalar d = SkPoint::Distance(p0, p1); + SkASSERT(d >= 0); + SkScalar prevD = distance; + distance += d; + if (distance > prevD) { + SkASSERT((unsigned)ptIndex < (unsigned)fPts.count()); + SkContourMeasure::Segment* seg = fSegments.append(); + seg->fDistance = distance; + seg->fPtIndex = ptIndex; + seg->fType = kLine_SegType; + seg->fTValue = kMaxTValue; + } + return distance; +} + +SkContourMeasure* SkContourMeasureIter::buildSegments() { + SkPoint pts[4]; + int ptIndex = -1; + SkScalar distance = 0; + bool haveSeenClose = fForceClosed; + bool haveSeenMoveTo = false; + + /* Note: + * as we accumulate distance, we have to check that the result of += + * actually made it larger, since a very small delta might be > 0, but + * still have no effect on distance (if distance >>> delta). + * + * We do this check below, and in compute_quad_segs and compute_cubic_segs + */ + + fSegments.reset(); + fPts.reset(); + + bool done = false; + do { + if (haveSeenMoveTo && fIter.peek() == SkPath::kMove_Verb) { + break; + } + switch (fIter.next(pts)) { + case SkPath::kMove_Verb: + ptIndex += 1; + fPts.append(1, pts); + SkASSERT(!haveSeenMoveTo); + haveSeenMoveTo = true; + break; + + case SkPath::kLine_Verb: { + SkASSERT(haveSeenMoveTo); + SkScalar prevD = distance; + distance = this->compute_line_seg(pts[0], pts[1], distance, ptIndex); + if (distance > prevD) { + fPts.append(1, pts + 1); + ptIndex++; + } + } break; + + case SkPath::kQuad_Verb: { + SkASSERT(haveSeenMoveTo); + SkScalar prevD = distance; + distance = this->compute_quad_segs(pts, distance, 0, kMaxTValue, ptIndex); + if (distance > prevD) { + fPts.append(2, pts + 1); + ptIndex += 2; + } + } break; + + case SkPath::kConic_Verb: { + SkASSERT(haveSeenMoveTo); + const SkConic conic(pts, fIter.conicWeight()); + SkScalar prevD = distance; + distance = this->compute_conic_segs(conic, distance, 0, conic.fPts[0], + kMaxTValue, conic.fPts[2], ptIndex); + if (distance > prevD) { + // we store the conic weight in our next point, followed by the last 2 pts + // thus to reconstitue a conic, you'd need to say + // SkConic(pts[0], pts[2], pts[3], weight = pts[1].fX) + fPts.append()->set(conic.fW, 0); + fPts.append(2, pts + 1); + ptIndex += 3; + } + } break; + + case SkPath::kCubic_Verb: { + SkASSERT(haveSeenMoveTo); + SkScalar prevD = distance; + distance = this->compute_cubic_segs(pts, distance, 0, kMaxTValue, ptIndex); + if (distance > prevD) { + fPts.append(3, pts + 1); + ptIndex += 3; + } + } break; + + case SkPath::kClose_Verb: + haveSeenClose = true; + break; + + case SkPath::kDone_Verb: + done = true; + break; + } + + } while (!done); + + if (!SkScalarIsFinite(distance)) { + return nullptr; + } + if (fSegments.count() == 0) { + return nullptr; + } + + // Handle the close segment ourselves, since we're using RawIter + if (haveSeenClose) { + SkScalar prevD = distance; + SkPoint firstPt = fPts[0]; + distance = this->compute_line_seg(fPts[ptIndex], firstPt, distance, ptIndex); + if (distance > prevD) { + *fPts.append() = firstPt; + } + } + +#ifdef SK_DEBUG +#ifndef SK_DISABLE_SLOW_DEBUG_VALIDATION + { + const SkContourMeasure::Segment* seg = fSegments.begin(); + const SkContourMeasure::Segment* stop = fSegments.end(); + unsigned ptIndex = 0; + SkScalar distance = 0; + // limit the loop to a reasonable number; pathological cases can run for minutes + int maxChecks = 10000000; // set to INT_MAX to defeat the check + while (seg < stop) { + SkASSERT(seg->fDistance > distance); + SkASSERT(seg->fPtIndex >= ptIndex); + SkASSERT(seg->fTValue > 0); + + const SkContourMeasure::Segment* s = seg; + while (s < stop - 1 && s[0].fPtIndex == s[1].fPtIndex && --maxChecks > 0) { + SkASSERT(s[0].fType == s[1].fType); + SkASSERT(s[0].fTValue < s[1].fTValue); + s += 1; + } + + distance = seg->fDistance; + ptIndex = seg->fPtIndex; + seg += 1; + } + // SkDebugf("\n"); + } +#endif +#endif + + return new SkContourMeasure(std::move(fSegments), std::move(fPts), distance, haveSeenClose); +} + +static void compute_pos_tan(const SkPoint pts[], unsigned segType, + SkScalar t, SkPoint* pos, SkVector* tangent) { + switch (segType) { + case kLine_SegType: + if (pos) { + pos->set(SkScalarInterp(pts[0].fX, pts[1].fX, t), + SkScalarInterp(pts[0].fY, pts[1].fY, t)); + } + if (tangent) { + tangent->setNormalize(pts[1].fX - pts[0].fX, pts[1].fY - pts[0].fY); + } + break; + case kQuad_SegType: + SkEvalQuadAt(pts, t, pos, tangent); + if (tangent) { + tangent->normalize(); + } + break; + case kConic_SegType: { + SkConic(pts[0], pts[2], pts[3], pts[1].fX).evalAt(t, pos, tangent); + if (tangent) { + tangent->normalize(); + } + } break; + case kCubic_SegType: + SkEvalCubicAt(pts, t, pos, tangent, nullptr); + if (tangent) { + tangent->normalize(); + } + break; + default: + SkDEBUGFAIL("unknown segType"); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +SkContourMeasureIter::SkContourMeasureIter() { + fTolerance = CHEAP_DIST_LIMIT; + fForceClosed = false; +} + +SkContourMeasureIter::SkContourMeasureIter(const SkPath& path, bool forceClosed, + SkScalar resScale) { + fPath = path.isFinite() ? path : SkPath(); + fTolerance = CHEAP_DIST_LIMIT * SkScalarInvert(resScale); + fForceClosed = forceClosed; + + fIter.setPath(fPath); +} + +SkContourMeasureIter::~SkContourMeasureIter() {} + +/** Assign a new path, or null to have none. +*/ +void SkContourMeasureIter::reset(const SkPath& path, bool forceClosed, SkScalar resScale) { + if (path.isFinite()) { + fPath = path; + } else { + fPath.reset(); + } + fForceClosed = forceClosed; + + fIter.setPath(fPath); + fSegments.reset(); + fPts.reset(); +} + +sk_sp SkContourMeasureIter::next() { + while (fIter.peek() != SkPath::kDone_Verb) { + auto cm = this->buildSegments(); + if (cm) { + return sk_sp(cm); + } + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +SkContourMeasure::SkContourMeasure(SkTDArray&& segs, SkTDArray&& pts, SkScalar length, bool isClosed) + : fSegments(std::move(segs)) + , fPts(std::move(pts)) + , fLength(length) + , fIsClosed(isClosed) + {} + +template +int SkTKSearch(const T base[], int count, const K& key) { + SkASSERT(count >= 0); + if (count <= 0) { + return ~0; + } + + SkASSERT(base != nullptr); // base may be nullptr if count is zero + + unsigned lo = 0; + unsigned hi = count - 1; + + while (lo < hi) { + unsigned mid = (hi + lo) >> 1; + if (base[mid].fDistance < key) { + lo = mid + 1; + } else { + hi = mid; + } + } + + if (base[hi].fDistance < key) { + hi += 1; + hi = ~hi; + } else if (key < base[hi].fDistance) { + hi = ~hi; + } + return hi; +} + +const SkContourMeasure::Segment* SkContourMeasure::distanceToSegment( SkScalar distance, + SkScalar* t) const { + SkDEBUGCODE(SkScalar length = ) this->length(); + SkASSERT(distance >= 0 && distance <= length); + + const Segment* seg = fSegments.begin(); + int count = fSegments.count(); + + int index = SkTKSearch(seg, count, distance); + // don't care if we hit an exact match or not, so we xor index if it is negative + index ^= (index >> 31); + seg = &seg[index]; + + // now interpolate t-values with the prev segment (if possible) + SkScalar startT = 0, startD = 0; + // check if the prev segment is legal, and references the same set of points + if (index > 0) { + startD = seg[-1].fDistance; + if (seg[-1].fPtIndex == seg->fPtIndex) { + SkASSERT(seg[-1].fType == seg->fType); + startT = seg[-1].getScalarT(); + } + } + + SkASSERT(seg->getScalarT() > startT); + SkASSERT(distance >= startD); + SkASSERT(seg->fDistance > startD); + + *t = startT + (seg->getScalarT() - startT) * (distance - startD) / (seg->fDistance - startD); + return seg; +} + +bool SkContourMeasure::getPosTan(SkScalar distance, SkPoint* pos, SkVector* tangent) const { + if (SkScalarIsNaN(distance)) { + return false; + } + + const SkScalar length = this->length(); + SkASSERT(length > 0 && fSegments.count() > 0); + + // pin the distance to a legal range + if (distance < 0) { + distance = 0; + } else if (distance > length) { + distance = length; + } + + SkScalar t; + const Segment* seg = this->distanceToSegment(distance, &t); + if (SkScalarIsNaN(t)) { + return false; + } + + SkASSERT((unsigned)seg->fPtIndex < (unsigned)fPts.count()); + compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent); + return true; +} + +bool SkContourMeasure::getMatrix(SkScalar distance, SkMatrix* matrix, MatrixFlags flags) const { + SkPoint position; + SkVector tangent; + + if (this->getPosTan(distance, &position, &tangent)) { + if (matrix) { + if (flags & kGetTangent_MatrixFlag) { + matrix->setSinCos(tangent.fY, tangent.fX, 0, 0); + } else { + matrix->reset(); + } + if (flags & kGetPosition_MatrixFlag) { + matrix->postTranslate(position.fX, position.fY); + } + } + return true; + } + return false; +} + +bool SkContourMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst, + bool startWithMoveTo) const { + SkASSERT(dst); + + SkScalar length = this->length(); // ensure we have built our segments + + if (startD < 0) { + startD = 0; + } + if (stopD > length) { + stopD = length; + } + if (!(startD <= stopD)) { // catch NaN values as well + return false; + } + if (!fSegments.count()) { + return false; + } + + SkPoint p; + SkScalar startT, stopT; + const Segment* seg = this->distanceToSegment(startD, &startT); + if (!SkScalarIsFinite(startT)) { + return false; + } + const Segment* stopSeg = this->distanceToSegment(stopD, &stopT); + if (!SkScalarIsFinite(stopT)) { + return false; + } + SkASSERT(seg <= stopSeg); + if (startWithMoveTo) { + compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, startT, &p, nullptr); + dst->moveTo(p); + } + + if (seg->fPtIndex == stopSeg->fPtIndex) { + SkContourMeasure_segTo(&fPts[seg->fPtIndex], seg->fType, startT, stopT, dst); + } else { + do { + SkContourMeasure_segTo(&fPts[seg->fPtIndex], seg->fType, startT, SK_Scalar1, dst); + seg = SkContourMeasure::Segment::Next(seg); + startT = 0; + } while (seg->fPtIndex < stopSeg->fPtIndex); + SkContourMeasure_segTo(&fPts[seg->fPtIndex], seg->fType, 0, stopT, dst); + } + + return true; +} diff --git a/gfx/skia/skia/src/core/SkConvertPixels.cpp b/gfx/skia/skia/src/core/SkConvertPixels.cpp new file mode 100644 index 0000000000..ca50a95784 --- /dev/null +++ b/gfx/skia/skia/src/core/SkConvertPixels.cpp @@ -0,0 +1,234 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRasterPipeline.h" + +static bool rect_memcpy(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB, + const SkColorSpaceXformSteps& steps) { + // We can copy the pixels when no color type, alpha type, or color space changes. + if (dstInfo.colorType() != srcInfo.colorType()) { + return false; + } + if (dstInfo.colorType() != kAlpha_8_SkColorType + && steps.flags.mask() != 0b00000) { + return false; + } + + SkRectMemcpy(dstPixels, dstRB, + srcPixels, srcRB, dstInfo.minRowBytes(), dstInfo.height()); + return true; +} + +static bool swizzle_or_premul(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB, + const SkColorSpaceXformSteps& steps) { + auto is_8888 = [](SkColorType ct) { + return ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType; + }; + if (!is_8888(dstInfo.colorType()) || + !is_8888(srcInfo.colorType()) || + steps.flags.linearize || + steps.flags.gamut_transform || + steps.flags.unpremul || + steps.flags.encode) { + return false; + } + + const bool swapRB = dstInfo.colorType() != srcInfo.colorType(); + + void (*fn)(uint32_t*, const uint32_t*, int) = nullptr; + + if (steps.flags.premul) { + fn = swapRB ? SkOpts::RGBA_to_bgrA + : SkOpts::RGBA_to_rgbA; + } else { + // If we're not swizzling, we ought to have used rect_memcpy(). + SkASSERT(swapRB); + fn = SkOpts::RGBA_to_BGRA; + } + + for (int y = 0; y < dstInfo.height(); y++) { + fn((uint32_t*)dstPixels, (const uint32_t*)srcPixels, dstInfo.width()); + dstPixels = SkTAddOffset(dstPixels, dstRB); + srcPixels = SkTAddOffset(srcPixels, srcRB); + } + return true; +} + +static bool convert_to_alpha8(const SkImageInfo& dstInfo, void* vdst, size_t dstRB, + const SkImageInfo& srcInfo, const void* src, size_t srcRB, + const SkColorSpaceXformSteps&) { + if (dstInfo.colorType() != kAlpha_8_SkColorType) { + return false; + } + auto dst = (uint8_t*)vdst; + + switch (srcInfo.colorType()) { + case kUnknown_SkColorType: + case kAlpha_8_SkColorType: { + // Unknown should never happen. + // Alpha8 should have been handled by rect_memcpy(). + SkASSERT(false); + return false; + } + + case kA16_unorm_SkColorType: { + auto src16 = (const uint16_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = src16[x] >> 8; + } + dst = SkTAddOffset(dst, dstRB); + src16 = SkTAddOffset(src16, srcRB); + } + return true; + } + + case kGray_8_SkColorType: + case kRGB_565_SkColorType: + case kR8G8_unorm_SkColorType: + case kR16G16_unorm_SkColorType: + case kR16G16_float_SkColorType: + case kRGB_888x_SkColorType: + case kRGB_101010x_SkColorType: { + for (int y = 0; y < srcInfo.height(); ++y) { + memset(dst, 0xFF, srcInfo.width()); + dst = SkTAddOffset(dst, dstRB); + } + return true; + } + + case kARGB_4444_SkColorType: { + auto src16 = (const uint16_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = SkPacked4444ToA32(src16[x]); + } + dst = SkTAddOffset(dst, dstRB); + src16 = SkTAddOffset(src16, srcRB); + } + return true; + } + + case kBGRA_8888_SkColorType: + case kRGBA_8888_SkColorType: { + auto src32 = (const uint32_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = src32[x] >> 24; + } + dst = SkTAddOffset(dst, dstRB); + src32 = SkTAddOffset(src32, srcRB); + } + return true; + } + + case kRGBA_1010102_SkColorType: { + auto src32 = (const uint32_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = (src32[x] >> 30) * 0x55; + } + dst = SkTAddOffset(dst, dstRB); + src32 = SkTAddOffset(src32, srcRB); + } + return true; + } + + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: { + auto src64 = (const uint64_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = (uint8_t) (255.0f * SkHalfToFloat(src64[x] >> 48)); + } + dst = SkTAddOffset(dst, dstRB); + src64 = SkTAddOffset(src64, srcRB); + } + return true; + } + + case kRGBA_F32_SkColorType: { + auto rgba = (const float*)src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = (uint8_t)(255.0f * rgba[4*x+3]); + } + dst = SkTAddOffset(dst, dstRB); + rgba = SkTAddOffset(rgba, srcRB); + } + return true; + } + + case kA16_float_SkColorType: { + auto srcF16 = (const uint16_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = (uint8_t) (255.0f * SkHalfToFloat(srcF16[x])); + } + dst = SkTAddOffset(dst, dstRB); + srcF16 = SkTAddOffset(srcF16, srcRB); + } + return true; + } + + case kR16G16B16A16_unorm_SkColorType: { + auto src64 = (const uint64_t*) src; + for (int y = 0; y < srcInfo.height(); y++) { + for (int x = 0; x < srcInfo.width(); x++) { + dst[x] = (src64[x] >> 48) >> 8; + } + dst = SkTAddOffset(dst, dstRB); + src64 = SkTAddOffset(src64, srcRB); + } + return true; + } + } + return false; +} + +// Default: Use the pipeline. +static void convert_with_pipeline(const SkImageInfo& dstInfo, void* dstRow, size_t dstRB, + const SkImageInfo& srcInfo, const void* srcRow, size_t srcRB, + const SkColorSpaceXformSteps& steps) { + + SkRasterPipeline_MemoryCtx src = { (void*)srcRow, (int)(srcRB / srcInfo.bytesPerPixel()) }, + dst = { (void*)dstRow, (int)(dstRB / dstInfo.bytesPerPixel()) }; + + SkRasterPipeline_<256> pipeline; + pipeline.append_load(srcInfo.colorType(), &src); + steps.apply(&pipeline, srcInfo.colorType()); + + pipeline.append_gamut_clamp_if_normalized(dstInfo); + + pipeline.append_store(dstInfo.colorType(), &dst); + pipeline.run(0,0, srcInfo.width(), srcInfo.height()); +} + +void SkConvertPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB) { + SkASSERT(dstInfo.dimensions() == srcInfo.dimensions()); + SkASSERT(SkImageInfoValidConversion(dstInfo, srcInfo)); + + SkColorSpaceXformSteps steps{srcInfo.colorSpace(), srcInfo.alphaType(), + dstInfo.colorSpace(), dstInfo.alphaType()}; + + for (auto fn : {rect_memcpy, swizzle_or_premul, convert_to_alpha8}) { + if (fn(dstInfo, dstPixels, dstRB, srcInfo, srcPixels, srcRB, steps)) { + return; + } + } + convert_with_pipeline(dstInfo, dstPixels, dstRB, srcInfo, srcPixels, srcRB, steps); +} diff --git a/gfx/skia/skia/src/core/SkConvertPixels.h b/gfx/skia/skia/src/core/SkConvertPixels.h new file mode 100644 index 0000000000..99d6a017c7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkConvertPixels.h @@ -0,0 +1,35 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkConvertPixels_DEFINED +#define SkConvertPixels_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/private/SkTemplates.h" + +class SkColorTable; + +void SkConvertPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRowBytes); + +static inline void SkRectMemcpy(void* dst, size_t dstRB, const void* src, size_t srcRB, + size_t trimRowBytes, int rowCount) { + SkASSERT(trimRowBytes <= dstRB); + SkASSERT(trimRowBytes <= srcRB); + if (trimRowBytes == dstRB && trimRowBytes == srcRB) { + memcpy(dst, src, trimRowBytes * rowCount); + return; + } + + for (int i = 0; i < rowCount; ++i) { + memcpy(dst, src, trimRowBytes); + dst = SkTAddOffset(dst, dstRB); + src = SkTAddOffset(src, srcRB); + } +} + +#endif diff --git a/gfx/skia/skia/src/core/SkConvolver.cpp b/gfx/skia/skia/src/core/SkConvolver.cpp new file mode 100644 index 0000000000..23cc2b7744 --- /dev/null +++ b/gfx/skia/skia/src/core/SkConvolver.cpp @@ -0,0 +1,272 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/core/SkConvolver.h" +#include "src/core/SkOpts.h" +#include "include/private/SkTArray.h" + +namespace { + // Stores a list of rows in a circular buffer. The usage is you write into it + // by calling AdvanceRow. It will keep track of which row in the buffer it + // should use next, and the total number of rows added. + class CircularRowBuffer { + public: + // The number of pixels in each row is given in |sourceRowPixelWidth|. + // The maximum number of rows needed in the buffer is |maxYFilterSize| + // (we only need to store enough rows for the biggest filter). + // + // We use the |firstInputRow| to compute the coordinates of all of the + // following rows returned by Advance(). + CircularRowBuffer(int destRowPixelWidth, int maxYFilterSize, + int firstInputRow) + : fRowByteWidth(destRowPixelWidth * 4), + fNumRows(maxYFilterSize), + fNextRow(0), + fNextRowCoordinate(firstInputRow) { + fBuffer.reset(fRowByteWidth * maxYFilterSize); + fRowAddresses.reset(fNumRows); + } + + // Moves to the next row in the buffer, returning a pointer to the beginning + // of it. + unsigned char* advanceRow() { + unsigned char* row = &fBuffer[fNextRow * fRowByteWidth]; + fNextRowCoordinate++; + + // Set the pointer to the next row to use, wrapping around if necessary. + fNextRow++; + if (fNextRow == fNumRows) { + fNextRow = 0; + } + return row; + } + + // Returns a pointer to an "unrolled" array of rows. These rows will start + // at the y coordinate placed into |*firstRowIndex| and will continue in + // order for the maximum number of rows in this circular buffer. + // + // The |firstRowIndex_| may be negative. This means the circular buffer + // starts before the top of the image (it hasn't been filled yet). + unsigned char* const* GetRowAddresses(int* firstRowIndex) { + // Example for a 4-element circular buffer holding coords 6-9. + // Row 0 Coord 8 + // Row 1 Coord 9 + // Row 2 Coord 6 <- fNextRow = 2, fNextRowCoordinate = 10. + // Row 3 Coord 7 + // + // The "next" row is also the first (lowest) coordinate. This computation + // may yield a negative value, but that's OK, the math will work out + // since the user of this buffer will compute the offset relative + // to the firstRowIndex and the negative rows will never be used. + *firstRowIndex = fNextRowCoordinate - fNumRows; + + int curRow = fNextRow; + for (int i = 0; i < fNumRows; i++) { + fRowAddresses[i] = &fBuffer[curRow * fRowByteWidth]; + + // Advance to the next row, wrapping if necessary. + curRow++; + if (curRow == fNumRows) { + curRow = 0; + } + } + return &fRowAddresses[0]; + } + + private: + // The buffer storing the rows. They are packed, each one fRowByteWidth. + SkTArray fBuffer; + + // Number of bytes per row in the |buffer|. + int fRowByteWidth; + + // The number of rows available in the buffer. + int fNumRows; + + // The next row index we should write into. This wraps around as the + // circular buffer is used. + int fNextRow; + + // The y coordinate of the |fNextRow|. This is incremented each time a + // new row is appended and does not wrap. + int fNextRowCoordinate; + + // Buffer used by GetRowAddresses(). + SkTArray fRowAddresses; + }; + +} // namespace + +// SkConvolutionFilter1D --------------------------------------------------------- + +SkConvolutionFilter1D::SkConvolutionFilter1D() +: fMaxFilter(0) { +} + +SkConvolutionFilter1D::~SkConvolutionFilter1D() { +} + +void SkConvolutionFilter1D::AddFilter(int filterOffset, + const ConvolutionFixed* filterValues, + int filterLength) { + // It is common for leading/trailing filter values to be zeros. In such + // cases it is beneficial to only store the central factors. + // For a scaling to 1/4th in each dimension using a Lanczos-2 filter on + // a 1080p image this optimization gives a ~10% speed improvement. + int filterSize = filterLength; + int firstNonZero = 0; + while (firstNonZero < filterLength && filterValues[firstNonZero] == 0) { + firstNonZero++; + } + + if (firstNonZero < filterLength) { + // Here we have at least one non-zero factor. + int lastNonZero = filterLength - 1; + while (lastNonZero >= 0 && filterValues[lastNonZero] == 0) { + lastNonZero--; + } + + filterOffset += firstNonZero; + filterLength = lastNonZero + 1 - firstNonZero; + SkASSERT(filterLength > 0); + + fFilterValues.append(filterLength, &filterValues[firstNonZero]); + } else { + // Here all the factors were zeroes. + filterLength = 0; + } + + FilterInstance instance; + + // We pushed filterLength elements onto fFilterValues + instance.fDataLocation = (static_cast(fFilterValues.count()) - + filterLength); + instance.fOffset = filterOffset; + instance.fTrimmedLength = filterLength; + instance.fLength = filterSize; + fFilters.push_back(instance); + + fMaxFilter = SkTMax(fMaxFilter, filterLength); +} + +const SkConvolutionFilter1D::ConvolutionFixed* SkConvolutionFilter1D::GetSingleFilter( + int* specifiedFilterlength, + int* filterOffset, + int* filterLength) const { + const FilterInstance& filter = fFilters[0]; + *filterOffset = filter.fOffset; + *filterLength = filter.fTrimmedLength; + *specifiedFilterlength = filter.fLength; + if (filter.fTrimmedLength == 0) { + return nullptr; + } + + return &fFilterValues[filter.fDataLocation]; +} + +bool BGRAConvolve2D(const unsigned char* sourceData, + int sourceByteRowStride, + bool sourceHasAlpha, + const SkConvolutionFilter1D& filterX, + const SkConvolutionFilter1D& filterY, + int outputByteRowStride, + unsigned char* output) { + + int maxYFilterSize = filterY.maxFilter(); + + // The next row in the input that we will generate a horizontally + // convolved row for. If the filter doesn't start at the beginning of the + // image (this is the case when we are only resizing a subset), then we + // don't want to generate any output rows before that. Compute the starting + // row for convolution as the first pixel for the first vertical filter. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filterY.FilterForValue(0, &filterOffset, &filterLength); + int nextXRow = filterOffset; + + // We loop over each row in the input doing a horizontal convolution. This + // will result in a horizontally convolved image. We write the results into + // a circular buffer of convolved rows and do vertical convolution as rows + // are available. This prevents us from having to store the entire + // intermediate image and helps cache coherency. + // We will need four extra rows to allow horizontal convolution could be done + // simultaneously. We also pad each row in row buffer to be aligned-up to + // 32 bytes. + // TODO(jiesun): We do not use aligned load from row buffer in vertical + // convolution pass yet. Somehow Windows does not like it. + int rowBufferWidth = (filterX.numValues() + 31) & ~0x1F; + int rowBufferHeight = maxYFilterSize + + (SkOpts::convolve_4_rows_horizontally != nullptr ? 4 : 0); + + // check for too-big allocation requests : crbug.com/528628 + { + int64_t size = sk_64_mul(rowBufferWidth, rowBufferHeight); + // need some limit, to avoid over-committing success from malloc, but then + // crashing when we try to actually use the memory. + // 100meg seems big enough to allow "normal" zoom factors and image sizes through + // while avoiding the crash seen by the bug (crbug.com/528628) + if (size > 100 * 1024 * 1024) { +// SkDebugf("BGRAConvolve2D: tmp allocation [%lld] too big\n", size); + return false; + } + } + + CircularRowBuffer rowBuffer(rowBufferWidth, + rowBufferHeight, + filterOffset); + + // Loop over every possible output row, processing just enough horizontal + // convolutions to run each subsequent vertical convolution. + SkASSERT(outputByteRowStride >= filterX.numValues() * 4); + int numOutputRows = filterY.numValues(); + + // We need to check which is the last line to convolve before we advance 4 + // lines in one iteration. + int lastFilterOffset, lastFilterLength; + filterY.FilterForValue(numOutputRows - 1, &lastFilterOffset, + &lastFilterLength); + + for (int outY = 0; outY < numOutputRows; outY++) { + filterValues = filterY.FilterForValue(outY, + &filterOffset, &filterLength); + + // Generate output rows until we have enough to run the current filter. + while (nextXRow < filterOffset + filterLength) { + if (SkOpts::convolve_4_rows_horizontally != nullptr && + nextXRow + 3 < lastFilterOffset + lastFilterLength) { + const unsigned char* src[4]; + unsigned char* outRow[4]; + for (int i = 0; i < 4; ++i) { + src[i] = &sourceData[(uint64_t)(nextXRow + i) * sourceByteRowStride]; + outRow[i] = rowBuffer.advanceRow(); + } + SkOpts::convolve_4_rows_horizontally(src, filterX, outRow, 4*rowBufferWidth); + nextXRow += 4; + } else { + SkOpts::convolve_horizontally( + &sourceData[(uint64_t)nextXRow * sourceByteRowStride], + filterX, rowBuffer.advanceRow(), sourceHasAlpha); + nextXRow++; + } + } + + // Compute where in the output image this row of final data will go. + unsigned char* curOutputRow = &output[(uint64_t)outY * outputByteRowStride]; + + // Get the list of rows that the circular buffer has, in order. + int firstRowInCircularBuffer; + unsigned char* const* rowsToConvolve = + rowBuffer.GetRowAddresses(&firstRowInCircularBuffer); + + // Now compute the start of the subset of those rows that the filter needs. + unsigned char* const* firstRowForFilter = + &rowsToConvolve[filterOffset - firstRowInCircularBuffer]; + + SkOpts::convolve_vertically(filterValues, filterLength, + firstRowForFilter, + filterX.numValues(), curOutputRow, + sourceHasAlpha); + } + return true; +} diff --git a/gfx/skia/skia/src/core/SkConvolver.h b/gfx/skia/skia/src/core/SkConvolver.h new file mode 100644 index 0000000000..f7d80c9f1d --- /dev/null +++ b/gfx/skia/skia/src/core/SkConvolver.h @@ -0,0 +1,173 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SK_CONVOLVER_H +#define SK_CONVOLVER_H + +#include "include/core/SkSize.h" +#include "include/private/SkTDArray.h" + +// avoid confusion with Mac OS X's math library (Carbon) +#if defined(__APPLE__) +#undef FloatToConvolutionFixed +#undef ConvolutionFixedToFloat +#undef FloatToFixed +#undef FixedToFloat +#endif + +// Represents a filter in one dimension. Each output pixel has one entry in this +// object for the filter values contributing to it. You build up the filter +// list by calling AddFilter for each output pixel (in order). +// +// We do 2-dimensional convolution by first convolving each row by one +// SkConvolutionFilter1D, then convolving each column by another one. +// +// Entries are stored in ConvolutionFixed point, shifted left by kShiftBits. +class SkConvolutionFilter1D { +public: + typedef short ConvolutionFixed; + + // The number of bits that ConvolutionFixed point values are shifted by. + enum { kShiftBits = 14 }; + + SK_API SkConvolutionFilter1D(); + SK_API ~SkConvolutionFilter1D(); + + // Convert between floating point and our ConvolutionFixed point representation. + static ConvolutionFixed FloatToFixed(float f) { + return static_cast(f * (1 << kShiftBits)); + } + static unsigned char FixedToChar(ConvolutionFixed x) { + return static_cast(x >> kShiftBits); + } + static float FixedToFloat(ConvolutionFixed x) { + // The cast relies on ConvolutionFixed being a short, implying that on + // the platforms we care about all (16) bits will fit into + // the mantissa of a (32-bit) float. + static_assert(sizeof(ConvolutionFixed) == 2, "ConvolutionFixed_type_should_fit_in_float_mantissa"); + float raw = static_cast(x); + return ldexpf(raw, -kShiftBits); + } + + // Returns the maximum pixel span of a filter. + int maxFilter() const { return fMaxFilter; } + + // Returns the number of filters in this filter. This is the dimension of the + // output image. + int numValues() const { return static_cast(fFilters.count()); } + + void reserveAdditional(int filterCount, int filterValueCount) { + fFilters.setReserve(fFilters.count() + filterCount); + fFilterValues.setReserve(fFilterValues.count() + filterValueCount); + } + + // Appends the given list of scaling values for generating a given output + // pixel. |filterOffset| is the distance from the edge of the image to where + // the scaling factors start. The scaling factors apply to the source pixels + // starting from this position, and going for the next |filterLength| pixels. + // + // You will probably want to make sure your input is normalized (that is, + // all entries in |filterValuesg| sub to one) to prevent affecting the overall + // brighness of the image. + // + // The filterLength must be > 0. + void AddFilter(int filterOffset, + const ConvolutionFixed* filterValues, + int filterLength); + + // Retrieves a filter for the given |valueOffset|, a position in the output + // image in the direction we're convolving. The offset and length of the + // filter values are put into the corresponding out arguments (see AddFilter + // above for what these mean), and a pointer to the first scaling factor is + // returned. There will be |filterLength| values in this array. + inline const ConvolutionFixed* FilterForValue(int valueOffset, + int* filterOffset, + int* filterLength) const { + const FilterInstance& filter = fFilters[valueOffset]; + *filterOffset = filter.fOffset; + *filterLength = filter.fTrimmedLength; + if (filter.fTrimmedLength == 0) { + return nullptr; + } + return &fFilterValues[filter.fDataLocation]; + } + + // Retrieves the filter for the offset 0, presumed to be the one and only. + // The offset and length of the filter values are put into the corresponding + // out arguments (see AddFilter). Note that |filterLegth| and + // |specifiedFilterLength| may be different if leading/trailing zeros of the + // original floating point form were clipped. + // There will be |filterLength| values in the return array. + // Returns nullptr if the filter is 0-length (for instance when all floating + // point values passed to AddFilter were clipped to 0). + SK_API const ConvolutionFixed* GetSingleFilter(int* specifiedFilterLength, + int* filterOffset, + int* filterLength) const; + + // Add another value to the fFilterValues array -- useful for + // SIMD padding which happens outside of this class. + + void addFilterValue( ConvolutionFixed val ) { + fFilterValues.push_back( val ); + } +private: + struct FilterInstance { + // Offset within filterValues for this instance of the filter. + int fDataLocation; + + // Distance from the left of the filter to the center. IN PIXELS + int fOffset; + + // Number of values in this filter instance. + int fTrimmedLength; + + // Filter length as specified. Note that this may be different from + // 'trimmed_length' if leading/trailing zeros of the original floating + // point form were clipped differently on each tail. + int fLength; + }; + + // Stores the information for each filter added to this class. + SkTDArray fFilters; + + // We store all the filter values in this flat list, indexed by + // |FilterInstance.data_location| to avoid the mallocs required for storing + // each one separately. + SkTDArray fFilterValues; + + // The maximum size of any filter we've added. + int fMaxFilter; +}; + +// Does a two-dimensional convolution on the given source image. +// +// It is assumed the source pixel offsets referenced in the input filters +// reference only valid pixels, so the source image size is not required. Each +// row of the source image starts |sourceByteRowStride| after the previous +// one (this allows you to have rows with some padding at the end). +// +// The result will be put into the given output buffer. The destination image +// size will be xfilter.numValues() * yfilter.numValues() pixels. It will be +// in rows of exactly xfilter.numValues() * 4 bytes. +// +// |sourceHasAlpha| is a hint that allows us to avoid doing computations on +// the alpha channel if the image is opaque. If you don't know, set this to +// true and it will work properly, but setting this to false will be a few +// percent faster if you know the image is opaque. +// +// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order +// (this is ARGB when loaded into 32-bit words on a little-endian machine). +/** + * Returns false if it was unable to perform the convolution/rescale. in which case the output + * buffer is assumed to be undefined. + */ +SK_API bool BGRAConvolve2D(const unsigned char* sourceData, + int sourceByteRowStride, + bool sourceHasAlpha, + const SkConvolutionFilter1D& xfilter, + const SkConvolutionFilter1D& yfilter, + int outputByteRowStride, + unsigned char* output); + +#endif // SK_CONVOLVER_H diff --git a/gfx/skia/skia/src/core/SkCoreBlitters.h b/gfx/skia/skia/src/core/SkCoreBlitters.h new file mode 100644 index 0000000000..600a9d1680 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCoreBlitters.h @@ -0,0 +1,179 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCoreBlitters_DEFINED +#define SkCoreBlitters_DEFINED + +#include "include/core/SkPaint.h" +#include "src/core/SkBlitRow.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkXfermodePriv.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkShaderBase.h" + +class SkRasterBlitter : public SkBlitter { +public: + SkRasterBlitter(const SkPixmap& device) : fDevice(device) {} + +protected: + const SkPixmap fDevice; + +private: + typedef SkBlitter INHERITED; +}; + +class SkShaderBlitter : public SkRasterBlitter { +public: + /** + * The storage for shaderContext is owned by the caller, but the object itself is not. + * The blitter only ensures that the storage always holds a live object, but it may + * exchange that object. + */ + SkShaderBlitter(const SkPixmap& device, const SkPaint& paint, + SkShaderBase::Context* shaderContext); + virtual ~SkShaderBlitter(); + +protected: + uint32_t fShaderFlags; + const SkShader* fShader; + SkShaderBase::Context* fShaderContext; + bool fConstInY; + +private: + // illegal + SkShaderBlitter& operator=(const SkShaderBlitter&); + + typedef SkRasterBlitter INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkA8_Coverage_Blitter : public SkRasterBlitter { +public: + SkA8_Coverage_Blitter(const SkPixmap& device, const SkPaint& paint); + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitMask(const SkMask&, const SkIRect&) override; + const SkPixmap* justAnOpaqueColor(uint32_t*) override; + +private: + typedef SkRasterBlitter INHERITED; +}; + +//////////////////////////////////////////////////////////////// + +class SkARGB32_Blitter : public SkRasterBlitter { +public: + SkARGB32_Blitter(const SkPixmap& device, const SkPaint& paint); + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitMask(const SkMask&, const SkIRect&) override; + const SkPixmap* justAnOpaqueColor(uint32_t*) override; + void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) override; + void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) override; + +protected: + SkColor fColor; + SkPMColor fPMColor; + +private: + unsigned fSrcA, fSrcR, fSrcG, fSrcB; + + // illegal + SkARGB32_Blitter& operator=(const SkARGB32_Blitter&); + + typedef SkRasterBlitter INHERITED; +}; + +class SkARGB32_Opaque_Blitter : public SkARGB32_Blitter { +public: + SkARGB32_Opaque_Blitter(const SkPixmap& device, const SkPaint& paint) + : INHERITED(device, paint) { SkASSERT(paint.getAlpha() == 0xFF); } + void blitMask(const SkMask&, const SkIRect&) override; + void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) override; + void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) override; + +private: + typedef SkARGB32_Blitter INHERITED; +}; + +class SkARGB32_Black_Blitter : public SkARGB32_Opaque_Blitter { +public: + SkARGB32_Black_Blitter(const SkPixmap& device, const SkPaint& paint) + : INHERITED(device, paint) {} + void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override; + void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) override; + void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) override; + +private: + typedef SkARGB32_Opaque_Blitter INHERITED; +}; + +class SkARGB32_Shader_Blitter : public SkShaderBlitter { +public: + SkARGB32_Shader_Blitter(const SkPixmap& device, const SkPaint& paint, + SkShaderBase::Context* shaderContext); + ~SkARGB32_Shader_Blitter() override; + void blitH(int x, int y, int width) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t[]) override; + void blitMask(const SkMask&, const SkIRect&) override; + +private: + SkXfermode* fXfermode; + SkPMColor* fBuffer; + SkBlitRow::Proc32 fProc32; + SkBlitRow::Proc32 fProc32Blend; + bool fShadeDirectlyIntoDevice; + + // illegal + SkARGB32_Shader_Blitter& operator=(const SkARGB32_Shader_Blitter&); + + typedef SkShaderBlitter INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef void (*SkS32D16BlendProc)(uint16_t*, const SkPMColor*, int, uint8_t); + +class SkRGB565_Shader_Blitter : public SkShaderBlitter { +public: + SkRGB565_Shader_Blitter(const SkPixmap& device, const SkPaint&, SkShaderBase::Context*); + ~SkRGB565_Shader_Blitter() override; + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha[], const int16_t[]) override; + + static bool Supports(const SkPixmap& device, const SkPaint&); + +private: + SkPMColor* fBuffer; + SkS32D16BlendProc fBlend; + SkS32D16BlendProc fBlendCoverage; + + typedef SkShaderBlitter INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Neither of these ever returns nullptr, but this first factory may return a SkNullBlitter. +SkBlitter* SkCreateRasterPipelineBlitter(const SkPixmap&, const SkPaint&, const SkMatrix& ctm, + SkArenaAlloc*); +// Use this if you've pre-baked a shader pipeline, including modulating with paint alpha. +// This factory never returns an SkNullBlitter. +SkBlitter* SkCreateRasterPipelineBlitter(const SkPixmap&, const SkPaint&, + const SkRasterPipeline& shaderPipeline, + bool shader_is_opaque, + SkArenaAlloc*); + +SkBlitter* SkCreateSkVMBlitter(const SkPixmap&, const SkPaint&, const SkMatrix& ctm, SkArenaAlloc*); + +#endif diff --git a/gfx/skia/skia/src/core/SkCoverageModePriv.h b/gfx/skia/skia/src/core/SkCoverageModePriv.h new file mode 100644 index 0000000000..09bfb5bddb --- /dev/null +++ b/gfx/skia/skia/src/core/SkCoverageModePriv.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCoverageModePriv_DEFINED +#define SkCoverageModePriv_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkCoverageMode.h" + +SkBlendMode SkUncorrelatedCoverageModeToBlendMode(SkCoverageMode); + +#if 0 +// Experimental idea to extend to overlap types + +Master calculation = X(S,D) + Y(S,D) + Z(S,D) + +enum class SkCoverageOverlap { + // X Y Z + kUncorrelated, // S*D S*(1-D) D*(1-S) + kConjoint, // min(S,D) max(S-D,0) max(D-S,0) + kDisjoint, // max(S+D-1,0) min(S,1-D) min(D,1-S) + + kLast = kDisjoint +}; + +// The coverage modes each have a set of coefficients to be applied to the general equation (above) +// +// e.g. +// kXor+conjoint = max(S-D,0) + max(D-S,0) ==> abs(D-S) +// +kUnion, // 1,1,1 +kIntersect, // 1,0,0 +kDifference, // 0,1,0 +kReverseDifference, // 0,0,1 +kXor, // 0,1,1 + +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkCpu.cpp b/gfx/skia/skia/src/core/SkCpu.cpp new file mode 100644 index 0000000000..bdae9d3a8a --- /dev/null +++ b/gfx/skia/skia/src/core/SkCpu.cpp @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/private/SkOnce.h" +#include "src/core/SkCpu.h" + +#if defined(SK_CPU_X86) + #if defined(SK_BUILD_FOR_WIN) && !defined(__MINGW32__) + #include + static void cpuid (uint32_t abcd[4]) { __cpuid ((int*)abcd, 1); } + static void cpuid7(uint32_t abcd[4]) { __cpuidex((int*)abcd, 7, 0); } + static uint64_t xgetbv(uint32_t xcr) { return _xgetbv(xcr); } + #else + #include + #if !defined(__cpuid_count) // Old Mac Clang doesn't have this defined. + #define __cpuid_count(eax, ecx, a, b, c, d) \ + __asm__("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(eax), "2"(ecx)) + #endif + static void cpuid (uint32_t abcd[4]) { __get_cpuid(1, abcd+0, abcd+1, abcd+2, abcd+3); } + static void cpuid7(uint32_t abcd[4]) { + __cpuid_count(7, 0, abcd[0], abcd[1], abcd[2], abcd[3]); + } + static uint64_t xgetbv(uint32_t xcr) { + uint32_t eax, edx; + __asm__ __volatile__ ( "xgetbv" : "=a"(eax), "=d"(edx) : "c"(xcr)); + return (uint64_t)(edx) << 32 | eax; + } + #endif + + static uint32_t read_cpu_features() { + uint32_t features = 0; + uint32_t abcd[4] = {0,0,0,0}; + + // You might want to refer to http://www.sandpile.org/x86/cpuid.htm + + cpuid(abcd); + if (abcd[3] & (1<<25)) { features |= SkCpu:: SSE1; } + if (abcd[3] & (1<<26)) { features |= SkCpu:: SSE2; } + if (abcd[2] & (1<< 0)) { features |= SkCpu:: SSE3; } + if (abcd[2] & (1<< 9)) { features |= SkCpu::SSSE3; } + if (abcd[2] & (1<<19)) { features |= SkCpu::SSE41; } + if (abcd[2] & (1<<20)) { features |= SkCpu::SSE42; } + + if ((abcd[2] & (3<<26)) == (3<<26) // XSAVE + OSXSAVE + && (xgetbv(0) & (3<<1)) == (3<<1)) { // XMM and YMM state enabled. + if (abcd[2] & (1<<28)) { features |= SkCpu:: AVX; } + if (abcd[2] & (1<<29)) { features |= SkCpu::F16C; } + if (abcd[2] & (1<<12)) { features |= SkCpu:: FMA; } + + cpuid7(abcd); + if (abcd[1] & (1<<5)) { features |= SkCpu::AVX2; } + if (abcd[1] & (1<<3)) { features |= SkCpu::BMI1; } + if (abcd[1] & (1<<8)) { features |= SkCpu::BMI2; } + + if ((xgetbv(0) & (7<<5)) == (7<<5)) { // All ZMM state bits enabled too. + if (abcd[1] & (1<<16)) { features |= SkCpu::AVX512F; } + if (abcd[1] & (1<<17)) { features |= SkCpu::AVX512DQ; } + if (abcd[1] & (1<<21)) { features |= SkCpu::AVX512IFMA; } + if (abcd[1] & (1<<26)) { features |= SkCpu::AVX512PF; } + if (abcd[1] & (1<<27)) { features |= SkCpu::AVX512ER; } + if (abcd[1] & (1<<28)) { features |= SkCpu::AVX512CD; } + if (abcd[1] & (1<<30)) { features |= SkCpu::AVX512BW; } + if (abcd[1] & (1<<31)) { features |= SkCpu::AVX512VL; } + } + } + return features; + } + +#elif defined(SK_CPU_ARM64) && __has_include() + #include + + static uint32_t read_cpu_features() { + const uint32_t kHWCAP_CRC32 = (1<< 7), + kHWCAP_ASIMDHP = (1<<10); + + uint32_t features = 0; + uint32_t hwcaps = getauxval(AT_HWCAP); + if (hwcaps & kHWCAP_CRC32 ) { features |= SkCpu::CRC32; } + if (hwcaps & kHWCAP_ASIMDHP) { features |= SkCpu::ASIMDHP; } + + // The Samsung Mongoose 3 core sets the ASIMDHP bit but doesn't support it. + for (int core = 0; features & SkCpu::ASIMDHP; core++) { + // These /sys files contain the core's MIDR_EL1 register, the source of + // CPU {implementer, variant, part, revision} you'd see in /proc/cpuinfo. + SkString path = + SkStringPrintf("/sys/devices/system/cpu/cpu%d/regs/identification/midr_el1", core); + + // Can't use SkData::MakeFromFileName() here, I think because /sys can't be mmap()'d. + SkFILEStream midr_el1(path.c_str()); + if (!midr_el1.isValid()) { + // This is our ordinary exit path. + // If we ask for MIDR_EL1 from a core that doesn't exist, we've checked all cores. + if (core == 0) { + // On the other hand, if we can't read MIDR_EL1 from any core, assume the worst. + features &= ~(SkCpu::ASIMDHP); + } + break; + } + + const char kMongoose3[] = "0x00000000531f0020"; // 53 == Samsung. + char buf[SK_ARRAY_COUNT(kMongoose3) - 1]; // No need for the terminating \0. + + if (SK_ARRAY_COUNT(buf) != midr_el1.read(buf, SK_ARRAY_COUNT(buf)) + || 0 == memcmp(kMongoose3, buf, SK_ARRAY_COUNT(buf))) { + features &= ~(SkCpu::ASIMDHP); + } + } + return features; + } + +#elif defined(SK_CPU_ARM32) && __has_include() && \ + (!defined(__ANDROID_API__) || __ANDROID_API__ >= 18) + // sys/auxv.h will always be present in the Android NDK due to unified + //headers, but getauxval is only defined for API >= 18. + #include + + static uint32_t read_cpu_features() { + const uint32_t kHWCAP_NEON = (1<<12); + const uint32_t kHWCAP_VFPv4 = (1<<16); + + uint32_t features = 0; + uint32_t hwcaps = getauxval(AT_HWCAP); + if (hwcaps & kHWCAP_NEON ) { + features |= SkCpu::NEON; + if (hwcaps & kHWCAP_VFPv4) { features |= SkCpu::NEON_FMA|SkCpu::VFP_FP16; } + } + return features; + } + +#elif defined(SK_CPU_ARM32) && __has_include() + #include + + static uint32_t read_cpu_features() { + uint32_t features = 0; + uint64_t cpu_features = android_getCpuFeatures(); + if (cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) { features |= SkCpu::NEON; } + if (cpu_features & ANDROID_CPU_ARM_FEATURE_NEON_FMA) { features |= SkCpu::NEON_FMA; } + if (cpu_features & ANDROID_CPU_ARM_FEATURE_VFP_FP16) { features |= SkCpu::VFP_FP16; } + return features; + } + +#else + static uint32_t read_cpu_features() { + return 0; + } + +#endif + +uint32_t SkCpu::gCachedFeatures = 0; + +void SkCpu::CacheRuntimeFeatures() { + static SkOnce once; + once([] { gCachedFeatures = read_cpu_features(); }); +} diff --git a/gfx/skia/skia/src/core/SkCpu.h b/gfx/skia/skia/src/core/SkCpu.h new file mode 100644 index 0000000000..d4a9cdc359 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCpu.h @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCpu_DEFINED +#define SkCpu_DEFINED + +#include "include/core/SkTypes.h" + +struct SkCpu { + enum { + SSE1 = 1 << 0, + SSE2 = 1 << 1, + SSE3 = 1 << 2, + SSSE3 = 1 << 3, + SSE41 = 1 << 4, + SSE42 = 1 << 5, + AVX = 1 << 6, + F16C = 1 << 7, + FMA = 1 << 8, + AVX2 = 1 << 9, + BMI1 = 1 << 10, + BMI2 = 1 << 11, + // Handy alias for all the cool Haswell+ instructions. + HSW = AVX2 | BMI1 | BMI2 | F16C | FMA, + + AVX512F = 1 << 12, + AVX512DQ = 1 << 13, + AVX512IFMA = 1 << 14, + AVX512PF = 1 << 15, + AVX512ER = 1 << 16, + AVX512CD = 1 << 17, + AVX512BW = 1 << 18, + AVX512VL = 1 << 19, + + // Handy alias for all the cool Skylake Xeon+ instructions. + SKX = AVX512F | AVX512DQ | AVX512CD | AVX512BW | AVX512VL, + }; + enum { + NEON = 1 << 0, + NEON_FMA = 1 << 1, + VFP_FP16 = 1 << 2, + CRC32 = 1 << 3, + ASIMDHP = 1 << 4, + }; + + static void CacheRuntimeFeatures(); + static bool Supports(uint32_t); +private: + static uint32_t gCachedFeatures; +}; + +inline bool SkCpu::Supports(uint32_t mask) { + uint32_t features = gCachedFeatures; + + // If we mask in compile-time known lower limits, the compiler can + // often compile away this entire function. +#if SK_CPU_X86 + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 + features |= SSE1; + #endif + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + features |= SSE2; + #endif + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE3 + features |= SSE3; + #endif + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + features |= SSSE3; + #endif + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + features |= SSE41; + #endif + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE42 + features |= SSE42; + #endif + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX + features |= AVX; + #endif + // F16C goes here if we add SK_CPU_SSE_LEVEL_F16C + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + features |= AVX2; + #endif + // FMA doesn't fit neatly into this total ordering. + // It's available on Haswell+ just like AVX2, but it's technically a different bit. + // TODO: circle back on this if we find ourselves limited by lack of compile-time FMA + + #if defined(SK_CPU_LIMIT_SSE41) + features &= (SkCpu::SSE1 | SkCpu::SSE2 | SkCpu::SSE3 | SkCpu::SSSE3 | SkCpu::SSE41); + #elif defined(SK_CPU_LIMIT_SSE2) + features &= (SkCpu::SSE1 | SkCpu::SSE2); + #endif + +#else + #if defined(SK_ARM_HAS_NEON) + features |= NEON; + #endif + + #if defined(SK_CPU_ARM64) + features |= NEON|NEON_FMA|VFP_FP16; + #endif + + #if defined(SK_ARM_HAS_CRC32) + features |= CRC32; + #endif + +#endif + return (features & mask) == mask; +} + +#endif//SkCpu_DEFINED diff --git a/gfx/skia/skia/src/core/SkCubicClipper.cpp b/gfx/skia/skia/src/core/SkCubicClipper.cpp new file mode 100644 index 0000000000..b774f15301 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCubicClipper.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/core/SkCubicClipper.h" +#include "src/core/SkGeometry.h" + +#include + +SkCubicClipper::SkCubicClipper() { + fClip.setEmpty(); +} + +void SkCubicClipper::setClip(const SkIRect& clip) { + // conver to scalars, since that's where we'll see the points + fClip.set(clip); +} + + +bool SkCubicClipper::ChopMonoAtY(const SkPoint pts[4], SkScalar y, SkScalar* t) { + SkScalar ycrv[4]; + ycrv[0] = pts[0].fY - y; + ycrv[1] = pts[1].fY - y; + ycrv[2] = pts[2].fY - y; + ycrv[3] = pts[3].fY - y; + +#ifdef NEWTON_RAPHSON // Quadratic convergence, typically <= 3 iterations. + // Initial guess. + // TODO(turk): Check for zero denominator? Shouldn't happen unless the curve + // is not only monotonic but degenerate. + SkScalar t1 = ycrv[0] / (ycrv[0] - ycrv[3]); + + // Newton's iterations. + const SkScalar tol = SK_Scalar1 / 16384; // This leaves 2 fixed noise bits. + SkScalar t0; + const int maxiters = 5; + int iters = 0; + bool converged; + do { + t0 = t1; + SkScalar y01 = SkScalarInterp(ycrv[0], ycrv[1], t0); + SkScalar y12 = SkScalarInterp(ycrv[1], ycrv[2], t0); + SkScalar y23 = SkScalarInterp(ycrv[2], ycrv[3], t0); + SkScalar y012 = SkScalarInterp(y01, y12, t0); + SkScalar y123 = SkScalarInterp(y12, y23, t0); + SkScalar y0123 = SkScalarInterp(y012, y123, t0); + SkScalar yder = (y123 - y012) * 3; + // TODO(turk): check for yder==0: horizontal. + t1 -= y0123 / yder; + converged = SkScalarAbs(t1 - t0) <= tol; // NaN-safe + ++iters; + } while (!converged && (iters < maxiters)); + *t = t1; // Return the result. + + // The result might be valid, even if outside of the range [0, 1], but + // we never evaluate a Bezier outside this interval, so we return false. + if (t1 < 0 || t1 > SK_Scalar1) + return false; // This shouldn't happen, but check anyway. + return converged; + +#else // BISECTION // Linear convergence, typically 16 iterations. + + // Check that the endpoints straddle zero. + SkScalar tNeg, tPos; // Negative and positive function parameters. + if (ycrv[0] < 0) { + if (ycrv[3] < 0) + return false; + tNeg = 0; + tPos = SK_Scalar1; + } else if (ycrv[0] > 0) { + if (ycrv[3] > 0) + return false; + tNeg = SK_Scalar1; + tPos = 0; + } else { + *t = 0; + return true; + } + + const SkScalar tol = SK_Scalar1 / 65536; // 1 for fixed, 1e-5 for float. + int iters = 0; + do { + SkScalar tMid = (tPos + tNeg) / 2; + SkScalar y01 = SkScalarInterp(ycrv[0], ycrv[1], tMid); + SkScalar y12 = SkScalarInterp(ycrv[1], ycrv[2], tMid); + SkScalar y23 = SkScalarInterp(ycrv[2], ycrv[3], tMid); + SkScalar y012 = SkScalarInterp(y01, y12, tMid); + SkScalar y123 = SkScalarInterp(y12, y23, tMid); + SkScalar y0123 = SkScalarInterp(y012, y123, tMid); + if (y0123 == 0) { + *t = tMid; + return true; + } + if (y0123 < 0) tNeg = tMid; + else tPos = tMid; + ++iters; + } while (!(SkScalarAbs(tPos - tNeg) <= tol)); // Nan-safe + + *t = (tNeg + tPos) / 2; + return true; +#endif // BISECTION +} + + +bool SkCubicClipper::clipCubic(const SkPoint srcPts[4], SkPoint dst[4]) { + bool reverse; + + // we need the data to be monotonically descending in Y + if (srcPts[0].fY > srcPts[3].fY) { + dst[0] = srcPts[3]; + dst[1] = srcPts[2]; + dst[2] = srcPts[1]; + dst[3] = srcPts[0]; + reverse = true; + } else { + memcpy(dst, srcPts, 4 * sizeof(SkPoint)); + reverse = false; + } + + // are we completely above or below + const SkScalar ctop = fClip.fTop; + const SkScalar cbot = fClip.fBottom; + if (dst[3].fY <= ctop || dst[0].fY >= cbot) { + return false; + } + + SkScalar t; + SkPoint tmp[7]; // for SkChopCubicAt + + // are we partially above + if (dst[0].fY < ctop && ChopMonoAtY(dst, ctop, &t)) { + SkChopCubicAt(dst, tmp, t); + dst[0] = tmp[3]; + dst[1] = tmp[4]; + dst[2] = tmp[5]; + } + + // are we partially below + if (dst[3].fY > cbot && ChopMonoAtY(dst, cbot, &t)) { + SkChopCubicAt(dst, tmp, t); + dst[1] = tmp[1]; + dst[2] = tmp[2]; + dst[3] = tmp[3]; + } + + if (reverse) { + using std::swap; + swap(dst[0], dst[3]); + swap(dst[1], dst[2]); + } + return true; +} diff --git a/gfx/skia/skia/src/core/SkCubicClipper.h b/gfx/skia/skia/src/core/SkCubicClipper.h new file mode 100644 index 0000000000..a92a08d398 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCubicClipper.h @@ -0,0 +1,34 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkCubicClipper_DEFINED +#define SkCubicClipper_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" + +/** This class is initialized with a clip rectangle, and then can be fed cubics, + which must already be monotonic in Y. + + In the future, it might return a series of segments, allowing it to clip + also in X, to ensure that all segments fit in a finite coordinate system. + */ +class SkCubicClipper { +public: + SkCubicClipper(); + + void setClip(const SkIRect& clip); + + bool SK_WARN_UNUSED_RESULT clipCubic(const SkPoint src[4], SkPoint dst[4]); + + static bool SK_WARN_UNUSED_RESULT ChopMonoAtY(const SkPoint pts[4], SkScalar y, SkScalar* t); +private: + SkRect fClip; +}; + +#endif // SkCubicClipper_DEFINED diff --git a/gfx/skia/skia/src/core/SkCubicMap.cpp b/gfx/skia/skia/src/core/SkCubicMap.cpp new file mode 100644 index 0000000000..30cd3b4bed --- /dev/null +++ b/gfx/skia/skia/src/core/SkCubicMap.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCubicMap.h" +#include "include/private/SkNx.h" +#include "src/core/SkOpts.h" + +//#define CUBICMAP_TRACK_MAX_ERROR + +#ifdef CUBICMAP_TRACK_MAX_ERROR +#include "src/pathops/SkPathOpsCubic.h" +#endif + +static inline bool nearly_zero(SkScalar x) { + SkASSERT(x >= 0); + return x <= 0.0000000001f; +} + +#ifdef CUBICMAP_TRACK_MAX_ERROR + static int max_iters; +#endif + +#ifdef CUBICMAP_TRACK_MAX_ERROR +static float compute_slow(float A, float B, float C, float x) { + double roots[3]; + SkDEBUGCODE(int count =) SkDCubic::RootsValidT(A, B, C, -x, roots); + SkASSERT(count == 1); + return (float)roots[0]; +} + +static float max_err; +#endif + +static float compute_t_from_x(float A, float B, float C, float x) { +#ifdef CUBICMAP_TRACK_MAX_ERROR + float answer = compute_slow(A, B, C, x); +#endif + float answer2 = SkOpts::cubic_solver(A, B, C, -x); + +#ifdef CUBICMAP_TRACK_MAX_ERROR + float err = sk_float_abs(answer - answer2); + if (err > max_err) { + max_err = err; + SkDebugf("max error %g\n", max_err); + } +#endif + return answer2; +} + +float SkCubicMap::computeYFromX(float x) const { + x = SkScalarPin(x, 0, 1); + + if (nearly_zero(x) || nearly_zero(1 - x)) { + return x; + } + if (fType == kLine_Type) { + return x; + } + float t; + if (fType == kCubeRoot_Type) { + t = sk_float_pow(x / fCoeff[0].fX, 1.0f / 3); + } else { + t = compute_t_from_x(fCoeff[0].fX, fCoeff[1].fX, fCoeff[2].fX, x); + } + float a = fCoeff[0].fY; + float b = fCoeff[1].fY; + float c = fCoeff[2].fY; + float y = ((a * t + b) * t + c) * t; + + return y; +} + +static inline bool coeff_nearly_zero(float delta) { + return sk_float_abs(delta) <= 0.0000001f; +} + +SkCubicMap::SkCubicMap(SkPoint p1, SkPoint p2) { + // Clamp X values only (we allow Ys outside [0..1]). + p1.fX = SkTMin(SkTMax(p1.fX, 0.0f), 1.0f); + p2.fX = SkTMin(SkTMax(p2.fX, 0.0f), 1.0f); + + Sk2s s1 = Sk2s::Load(&p1) * 3; + Sk2s s2 = Sk2s::Load(&p2) * 3; + + (Sk2s(1) + s1 - s2).store(&fCoeff[0]); + (s2 - s1 - s1).store(&fCoeff[1]); + s1.store(&fCoeff[2]); + + fType = kSolver_Type; + if (SkScalarNearlyEqual(p1.fX, p1.fY) && SkScalarNearlyEqual(p2.fX, p2.fY)) { + fType = kLine_Type; + } else if (coeff_nearly_zero(fCoeff[1].fX) && coeff_nearly_zero(fCoeff[2].fX)) { + fType = kCubeRoot_Type; + } +} + +SkPoint SkCubicMap::computeFromT(float t) const { + Sk2s a = Sk2s::Load(&fCoeff[0]); + Sk2s b = Sk2s::Load(&fCoeff[1]); + Sk2s c = Sk2s::Load(&fCoeff[2]); + + SkPoint result; + (((a * t + b) * t + c) * t).store(&result); + return result; +} diff --git a/gfx/skia/skia/src/core/SkCubicSolver.h b/gfx/skia/skia/src/core/SkCubicSolver.h new file mode 100644 index 0000000000..f5bd9fef62 --- /dev/null +++ b/gfx/skia/skia/src/core/SkCubicSolver.h @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCubicSolver_DEFINED +#define SkCubicSolver_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkFloatingPoint.h" + +//#define CUBICMAP_TRACK_MAX_ERROR + +namespace SK_OPTS_NS { + + static float eval_poly(float t, float b) { + return b; + } + + template + static float eval_poly(float t, float m, float b, Rest... rest) { + return eval_poly(t, sk_fmaf(m,t,b), rest...); + } + + inline float cubic_solver(float A, float B, float C, float D) { + #ifdef CUBICMAP_TRACK_MAX_ERROR + static int max_iters = 0; + #endif + + #ifdef SK_DEBUG + auto valid = [](float t) { + return t >= 0 && t <= 1; + }; + #endif + + auto guess_nice_cubic_root = [](float a, float b, float c, float d) { + return -d; + }; + float t = guess_nice_cubic_root(A, B, C, D); + + int iters = 0; + const int MAX_ITERS = 8; + for (; iters < MAX_ITERS; ++iters) { + SkASSERT(valid(t)); + float f = eval_poly(t, A,B,C,D); // f = At^3 + Bt^2 + Ct + D + if (sk_float_abs(f) <= 0.00005f) { + break; + } + float fp = eval_poly(t, 3*A, 2*B, C); // f' = 3At^2 + 2Bt + C + float fpp = eval_poly(t, 3*A+3*A, 2*B); // f'' = 6At + 2B + + float numer = 2 * fp * f; + float denom = sk_fmaf(2*fp, fp, -(f*fpp)); + + t -= numer / denom; + } + + #ifdef CUBICMAP_TRACK_MAX_ERROR + if (max_iters < iters) { + max_iters = iters; + SkDebugf("max_iters %d\n", max_iters); + } + #endif + SkASSERT(valid(t)); + return t; + } + +} // namespace SK_OPTS_NS +#endif diff --git a/gfx/skia/skia/src/core/SkData.cpp b/gfx/skia/skia/src/core/SkData.cpp new file mode 100644 index 0000000000..ef3f42e5ce --- /dev/null +++ b/gfx/skia/skia/src/core/SkData.cpp @@ -0,0 +1,198 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/private/SkOnce.h" +#include "src/core/SkOSFile.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include + +SkData::SkData(const void* ptr, size_t size, ReleaseProc proc, void* context) { + fPtr = const_cast(ptr); + fSize = size; + fReleaseProc = proc; + fReleaseProcContext = context; +} + +/** This constructor means we are inline with our fPtr's contents. + * Thus we set fPtr to point right after this. + */ +SkData::SkData(size_t size) { + fPtr = (char*)(this + 1); // contents are immediately after this + fSize = size; + fReleaseProc = nullptr; + fReleaseProcContext = nullptr; +} + +SkData::~SkData() { + if (fReleaseProc) { + fReleaseProc(fPtr, fReleaseProcContext); + } +} + +bool SkData::equals(const SkData* other) const { + if (nullptr == other) { + return false; + } + + return fSize == other->fSize && !memcmp(fPtr, other->fPtr, fSize); +} + +size_t SkData::copyRange(size_t offset, size_t length, void* buffer) const { + size_t available = fSize; + if (offset >= available || 0 == length) { + return 0; + } + available -= offset; + if (length > available) { + length = available; + } + SkASSERT(length > 0); + + memcpy(buffer, this->bytes() + offset, length); + return length; +} + +void SkData::operator delete(void* p) { + ::operator delete(p); +} + +sk_sp SkData::PrivateNewWithCopy(const void* srcOrNull, size_t length) { + if (0 == length) { + return SkData::MakeEmpty(); + } + + const size_t actualLength = length + sizeof(SkData); + SkASSERT_RELEASE(length < actualLength); // Check for overflow. + + void* storage = ::operator new (actualLength); + sk_sp data(new (storage) SkData(length)); + if (srcOrNull) { + memcpy(data->writable_data(), srcOrNull, length); + } + return data; +} + +void SkData::DummyReleaseProc(const void*, void*) {} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkData::MakeEmpty() { + static SkOnce once; + static SkData* empty; + + once([]{ empty = new SkData(nullptr, 0, nullptr, nullptr); }); + return sk_ref_sp(empty); +} + +// assumes fPtr was allocated via sk_malloc +static void sk_free_releaseproc(const void* ptr, void*) { + sk_free((void*)ptr); +} + +sk_sp SkData::MakeFromMalloc(const void* data, size_t length) { + return sk_sp(new SkData(data, length, sk_free_releaseproc, nullptr)); +} + +sk_sp SkData::MakeWithCopy(const void* src, size_t length) { + SkASSERT(src); + return PrivateNewWithCopy(src, length); +} + +sk_sp SkData::MakeUninitialized(size_t length) { + return PrivateNewWithCopy(nullptr, length); +} + +sk_sp SkData::MakeWithProc(const void* ptr, size_t length, ReleaseProc proc, void* ctx) { + return sk_sp(new SkData(ptr, length, proc, ctx)); +} + +// assumes fPtr was allocated with sk_fmmap +static void sk_mmap_releaseproc(const void* addr, void* ctx) { + size_t length = reinterpret_cast(ctx); + sk_fmunmap(addr, length); +} + +sk_sp SkData::MakeFromFILE(FILE* f) { + size_t size; + void* addr = sk_fmmap(f, &size); + if (nullptr == addr) { + return nullptr; + } + + return SkData::MakeWithProc(addr, size, sk_mmap_releaseproc, reinterpret_cast(size)); +} + +sk_sp SkData::MakeFromFileName(const char path[]) { + FILE* f = path ? sk_fopen(path, kRead_SkFILE_Flag) : nullptr; + if (nullptr == f) { + return nullptr; + } + auto data = MakeFromFILE(f); + sk_fclose(f); + return data; +} + +sk_sp SkData::MakeFromFD(int fd) { + size_t size; + void* addr = sk_fdmmap(fd, &size); + if (nullptr == addr) { + return nullptr; + } + return SkData::MakeWithProc(addr, size, sk_mmap_releaseproc, reinterpret_cast(size)); +} + +// assumes context is a SkData +static void sk_dataref_releaseproc(const void*, void* context) { + SkData* src = reinterpret_cast(context); + src->unref(); +} + +sk_sp SkData::MakeSubset(const SkData* src, size_t offset, size_t length) { + /* + We could, if we wanted/need to, just make a deep copy of src's data, + rather than referencing it. This would duplicate the storage (of the + subset amount) but would possibly allow src to go out of scope sooner. + */ + + size_t available = src->size(); + if (offset >= available || 0 == length) { + return SkData::MakeEmpty(); + } + available -= offset; + if (length > available) { + length = available; + } + SkASSERT(length > 0); + + src->ref(); // this will be balanced in sk_dataref_releaseproc + return sk_sp(new SkData(src->bytes() + offset, length, sk_dataref_releaseproc, + const_cast(src))); +} + +sk_sp SkData::MakeWithCString(const char cstr[]) { + size_t size; + if (nullptr == cstr) { + cstr = ""; + size = 1; + } else { + size = strlen(cstr) + 1; + } + return MakeWithCopy(cstr, size); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkData::MakeFromStream(SkStream* stream, size_t size) { + sk_sp data(SkData::MakeUninitialized(size)); + if (stream->read(data->writable_data(), size) != size) { + return nullptr; + } + return data; +} diff --git a/gfx/skia/skia/src/core/SkDataTable.cpp b/gfx/skia/skia/src/core/SkDataTable.cpp new file mode 100644 index 0000000000..9a1d7bd6a8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDataTable.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkDataTable.h" +#include "include/private/SkOnce.h" + +static void malloc_freeproc(void* context) { + sk_free(context); +} + +// Makes empty table +SkDataTable::SkDataTable() { + fCount = 0; + fElemSize = 0; // 0 signals that we use fDir instead of fElems + fU.fDir = nullptr; + fFreeProc = nullptr; + fFreeProcContext = nullptr; +} + +SkDataTable::SkDataTable(const void* array, size_t elemSize, int count, + FreeProc proc, void* context) { + SkASSERT(count > 0); + + fCount = count; + fElemSize = elemSize; // non-zero signals we use fElems instead of fDir + fU.fElems = (const char*)array; + fFreeProc = proc; + fFreeProcContext = context; +} + +SkDataTable::SkDataTable(const Dir* dir, int count, FreeProc proc, void* ctx) { + SkASSERT(count > 0); + + fCount = count; + fElemSize = 0; // 0 signals that we use fDir instead of fElems + fU.fDir = dir; + fFreeProc = proc; + fFreeProcContext = ctx; +} + +SkDataTable::~SkDataTable() { + if (fFreeProc) { + fFreeProc(fFreeProcContext); + } +} + +size_t SkDataTable::atSize(int index) const { + SkASSERT((unsigned)index < (unsigned)fCount); + + if (fElemSize) { + return fElemSize; + } else { + return fU.fDir[index].fSize; + } +} + +const void* SkDataTable::at(int index, size_t* size) const { + SkASSERT((unsigned)index < (unsigned)fCount); + + if (fElemSize) { + if (size) { + *size = fElemSize; + } + return fU.fElems + index * fElemSize; + } else { + if (size) { + *size = fU.fDir[index].fSize; + } + return fU.fDir[index].fPtr; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkDataTable::MakeEmpty() { + static SkDataTable* singleton; + static SkOnce once; + once([]{ singleton = new SkDataTable(); }); + return sk_ref_sp(singleton); +} + +sk_sp SkDataTable::MakeCopyArrays(const void * const * ptrs, + const size_t sizes[], int count) { + if (count <= 0) { + return SkDataTable::MakeEmpty(); + } + + size_t dataSize = 0; + for (int i = 0; i < count; ++i) { + dataSize += sizes[i]; + } + + size_t bufferSize = count * sizeof(Dir) + dataSize; + void* buffer = sk_malloc_throw(bufferSize); + + Dir* dir = (Dir*)buffer; + char* elem = (char*)(dir + count); + for (int i = 0; i < count; ++i) { + dir[i].fPtr = elem; + dir[i].fSize = sizes[i]; + memcpy(elem, ptrs[i], sizes[i]); + elem += sizes[i]; + } + + return sk_sp(new SkDataTable(dir, count, malloc_freeproc, buffer)); +} + +sk_sp SkDataTable::MakeCopyArray(const void* array, size_t elemSize, int count) { + if (count <= 0) { + return SkDataTable::MakeEmpty(); + } + + size_t bufferSize = elemSize * count; + void* buffer = sk_malloc_throw(bufferSize); + memcpy(buffer, array, bufferSize); + + return sk_sp(new SkDataTable(buffer, elemSize, count, malloc_freeproc, buffer)); +} + +sk_sp SkDataTable::MakeArrayProc(const void* array, size_t elemSize, int count, + FreeProc proc, void* ctx) { + if (count <= 0) { + return SkDataTable::MakeEmpty(); + } + return sk_sp(new SkDataTable(array, elemSize, count, proc, ctx)); +} diff --git a/gfx/skia/skia/src/core/SkDebug.cpp b/gfx/skia/skia/src/core/SkDebug.cpp new file mode 100644 index 0000000000..b02ddf7fa9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDebug.cpp @@ -0,0 +1,14 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_GOOGLE3) +void SkDebugfForDumpStackTrace(const char* data, void* unused) { + SkDebugf("%s", data); +} +#endif diff --git a/gfx/skia/skia/src/core/SkDeferredDisplayList.cpp b/gfx/skia/skia/src/core/SkDeferredDisplayList.cpp new file mode 100644 index 0000000000..c89cd990bb --- /dev/null +++ b/gfx/skia/skia/src/core/SkDeferredDisplayList.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" +#include "include/private/SkDeferredDisplayList.h" +#include +class SkSurfaceCharacterization; + +#if SK_SUPPORT_GPU +#include "src/gpu/GrRenderTask.h" +#include "src/gpu/ccpr/GrCCPerOpsTaskPaths.h" +#endif + +SkDeferredDisplayList::SkDeferredDisplayList(const SkSurfaceCharacterization& characterization, + sk_sp lazyProxyData) + : fCharacterization(characterization) + , fLazyProxyData(std::move(lazyProxyData)) { +} + +SkDeferredDisplayList::~SkDeferredDisplayList() { +} diff --git a/gfx/skia/skia/src/core/SkDeferredDisplayListPriv.h b/gfx/skia/skia/src/core/SkDeferredDisplayListPriv.h new file mode 100644 index 0000000000..4b6fccdbe2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDeferredDisplayListPriv.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDeferredDisplayListPriv_DEFINED +#define SkDeferredDisplayListPriv_DEFINED + +#include "include/private/SkDeferredDisplayList.h" + +/** Class that adds methods to SkDeferredDisplayList that are only intended for use internal to Skia. + This class is purely a privileged window into SkDeferredDisplayList. It should never have + additional data members or virtual methods. */ +class SkDeferredDisplayListPriv { +public: + int numRenderTasks() const { +#if SK_SUPPORT_GPU + return fDDL->fRenderTasks.count(); +#else + return 0; +#endif + } + + const SkDeferredDisplayList::LazyProxyData* lazyProxyData() const { +#if SK_SUPPORT_GPU + return fDDL->fLazyProxyData.get(); +#else + return nullptr; +#endif + } + +private: + explicit SkDeferredDisplayListPriv(SkDeferredDisplayList* ddl) : fDDL(ddl) {} + SkDeferredDisplayListPriv(const SkDeferredDisplayListPriv&); // unimpl + SkDeferredDisplayListPriv& operator=(const SkDeferredDisplayListPriv&); // unimpl + + // No taking addresses of this type. + const SkDeferredDisplayListPriv* operator&() const; + SkDeferredDisplayListPriv* operator&(); + + SkDeferredDisplayList* fDDL; + + friend class SkDeferredDisplayList; // to construct/copy this type. +}; + +inline SkDeferredDisplayListPriv SkDeferredDisplayList::priv() { + return SkDeferredDisplayListPriv(this); +} + +inline const SkDeferredDisplayListPriv SkDeferredDisplayList::priv () const { + return SkDeferredDisplayListPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkDeferredDisplayListRecorder.cpp b/gfx/skia/skia/src/core/SkDeferredDisplayListRecorder.cpp new file mode 100644 index 0000000000..1149c15d55 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDeferredDisplayListRecorder.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkDeferredDisplayListRecorder.h" +#include "include/core/SkSurface.h" +#include "include/core/SkSurfaceCharacterization.h" +#include "include/private/SkDeferredDisplayList.h" +#include "src/core/SkMessageBus.h" + +#if !SK_SUPPORT_GPU +SkDeferredDisplayListRecorder::SkDeferredDisplayListRecorder(const SkSurfaceCharacterization&) {} + +SkDeferredDisplayListRecorder::~SkDeferredDisplayListRecorder() {} + +bool SkDeferredDisplayListRecorder::init() { return false; } + +SkCanvas* SkDeferredDisplayListRecorder::getCanvas() { return nullptr; } + +std::unique_ptr SkDeferredDisplayListRecorder::detach() { return nullptr; } + +sk_sp SkDeferredDisplayListRecorder::makePromiseTexture( + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContext, + PromiseImageApiVersion) { + return nullptr; +} + +sk_sp SkDeferredDisplayListRecorder::makeYUVAPromiseTexture( + SkYUVColorSpace yuvColorSpace, + const GrBackendFormat yuvaFormats[], + const SkISize yuvaSizes[], + const SkYUVAIndex yuvaIndices[4], + int imageWidth, + int imageHeight, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContexts[], + PromiseImageApiVersion) { + return nullptr; +} + +#else + +#include "include/core/SkPromiseImageTexture.h" +#include "include/core/SkYUVASizeInfo.h" +#include "include/gpu/GrTexture.h" +#include "src/core/SkMakeUnique.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/SkGr.h" +#include "src/image/SkImage_Gpu.h" +#include "src/image/SkImage_GpuYUVA.h" +#include "src/image/SkSurface_Gpu.h" + +SkDeferredDisplayListRecorder::SkDeferredDisplayListRecorder(const SkSurfaceCharacterization& c) + : fCharacterization(c) { + if (fCharacterization.isValid()) { + fContext = GrContextPriv::MakeDDL(fCharacterization.refContextInfo()); + } +} + +SkDeferredDisplayListRecorder::~SkDeferredDisplayListRecorder() { + if (fContext) { + auto proxyProvider = fContext->priv().proxyProvider(); + + // This allows the uniquely keyed proxies to keep their keys but removes their back + // pointer to the about-to-be-deleted proxy provider. The proxies will use their + // unique key to reattach to cached versions of themselves or to appropriately tag new + // resources (if a cached version was not found). This system operates independent of + // the replaying context's proxy provider (i.e., these uniquely keyed proxies will not + // appear in the replaying proxy providers uniquely keyed proxy map). This should be fine + // since no one else should be trying to reconnect to the orphaned proxies and orphaned + // proxies from different DDLs that share the same key should simply reconnect to the + // same cached resource. + proxyProvider->orphanAllUniqueKeys(); + } +} + + +bool SkDeferredDisplayListRecorder::init() { + SkASSERT(fContext); + SkASSERT(!fLazyProxyData); + SkASSERT(!fSurface); + + if (!fCharacterization.isValid()) { + return false; + } + + fLazyProxyData = sk_sp( + new SkDeferredDisplayList::LazyProxyData); + + auto proxyProvider = fContext->priv().proxyProvider(); + const GrCaps* caps = fContext->priv().caps(); + + bool usesGLFBO0 = fCharacterization.usesGLFBO0(); + if (usesGLFBO0) { + if (GrBackendApi::kOpenGL != fContext->backend() || + fCharacterization.isTextureable()) { + return false; + } + } + + if (fCharacterization.vulkanSecondaryCBCompatible()) { + // Because of the restrictive API allowed for a GrVkSecondaryCBDrawContext, we know ahead + // of time that we don't be able to support certain parameter combinations. Specifially we + // fail on usesGLFBO0 since we can't mix GL and Vulkan. We can't have a texturable object. + // And finally the GrVkSecondaryCBDrawContext always assumes a top left origin. + if (usesGLFBO0 || + fCharacterization.isTextureable() || + fCharacterization.origin() == kBottomLeft_GrSurfaceOrigin) { + return false; + } + } + + GrColorType grColorType = SkColorTypeToGrColorType(fCharacterization.colorType()); + + GrPixelConfig config = caps->getConfigFromBackendFormat(fCharacterization.backendFormat(), + grColorType); + if (config == kUnknown_GrPixelConfig) { + return false; + } + + GrSurfaceDesc desc; + desc.fWidth = fCharacterization.width(); + desc.fHeight = fCharacterization.height(); + desc.fConfig = config; + + sk_sp lazyProxyData = fLazyProxyData; + + // What we're doing here is we're creating a lazy proxy to back the SkSurface. The lazy + // proxy, when instantiated, will use the GrRenderTarget that backs the SkSurface that the + // DDL is being replayed into. + + GrInternalSurfaceFlags surfaceFlags = GrInternalSurfaceFlags::kNone; + if (usesGLFBO0) { + surfaceFlags |= GrInternalSurfaceFlags::kGLRTFBOIDIs0; + } + // FIXME: Why do we use GrMipMapped::kNo instead of SkSurfaceCharacterization::fIsMipMapped? + static constexpr GrProxyProvider::TextureInfo kTextureInfo{GrMipMapped::kNo, + GrTextureType::k2D}; + const GrProxyProvider::TextureInfo* optionalTextureInfo = nullptr; + if (fCharacterization.isTextureable()) { + optionalTextureInfo = &kTextureInfo; + } + + sk_sp proxy = proxyProvider->createLazyRenderTargetProxy( + [lazyProxyData](GrResourceProvider* resourceProvider) { + // The proxy backing the destination surface had better have been instantiated + // prior to the proxy backing the DLL's surface. Steal its GrRenderTarget. + SkASSERT(lazyProxyData->fReplayDest->peekSurface()); + auto surface = sk_ref_sp(lazyProxyData->fReplayDest->peekSurface()); + return GrSurfaceProxy::LazyCallbackResult(std::move(surface)); + }, + fCharacterization.backendFormat(), + desc, + fCharacterization.sampleCount(), + fCharacterization.origin(), + surfaceFlags, + optionalTextureInfo, + GrMipMapsStatus::kNotAllocated, + SkBackingFit::kExact, + SkBudgeted::kYes, + fCharacterization.isProtected(), + fCharacterization.vulkanSecondaryCBCompatible(), + GrSurfaceProxy::UseAllocator::kYes); + + if (!proxy) { + return false; + } + + auto c = fContext->priv().makeWrappedSurfaceContext(std::move(proxy), + grColorType, + kPremul_SkAlphaType, + fCharacterization.refColorSpace(), + &fCharacterization.surfaceProps()); + SkASSERT(c->asRenderTargetContext()); + std::unique_ptr rtc(c.release()->asRenderTargetContext()); + fSurface = SkSurface_Gpu::MakeWrappedRenderTarget(fContext.get(), std::move(rtc)); + return SkToBool(fSurface.get()); +} + +SkCanvas* SkDeferredDisplayListRecorder::getCanvas() { + if (!fContext) { + return nullptr; + } + + if (!fSurface && !this->init()) { + return nullptr; + } + + return fSurface->getCanvas(); +} + +std::unique_ptr SkDeferredDisplayListRecorder::detach() { + if (!fContext) { + return nullptr; + } + + if (fSurface) { + SkCanvas* canvas = fSurface->getCanvas(); + + canvas->restoreToCount(0); + } + + auto ddl = std::unique_ptr( + new SkDeferredDisplayList(fCharacterization, std::move(fLazyProxyData))); + + fContext->priv().moveRenderTasksToDDL(ddl.get()); + + // We want a new lazy proxy target for each recorded DDL so force the (lazy proxy-backed) + // SkSurface to be regenerated for each DDL. + fSurface = nullptr; + return ddl; +} + +sk_sp SkDeferredDisplayListRecorder::makePromiseTexture( + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContext, + PromiseImageApiVersion version) { + if (!fContext) { + return nullptr; + } + + return SkImage_Gpu::MakePromiseTexture(fContext.get(), + backendFormat, + width, + height, + mipMapped, + origin, + colorType, + alphaType, + std::move(colorSpace), + textureFulfillProc, + textureReleaseProc, + textureDoneProc, + textureContext, + version); +} + +sk_sp SkDeferredDisplayListRecorder::makeYUVAPromiseTexture( + SkYUVColorSpace yuvColorSpace, + const GrBackendFormat yuvaFormats[], + const SkISize yuvaSizes[], + const SkYUVAIndex yuvaIndices[4], + int imageWidth, + int imageHeight, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContexts[], + PromiseImageApiVersion version) { + if (!fContext) { + return nullptr; + } + + return SkImage_GpuYUVA::MakePromiseYUVATexture(fContext.get(), + yuvColorSpace, + yuvaFormats, + yuvaSizes, + yuvaIndices, + imageWidth, + imageHeight, + imageOrigin, + std::move(imageColorSpace), + textureFulfillProc, + textureReleaseProc, + textureDoneProc, + textureContexts, + version); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkDeque.cpp b/gfx/skia/skia/src/core/SkDeque.cpp new file mode 100644 index 0000000000..6df4cc04be --- /dev/null +++ b/gfx/skia/skia/src/core/SkDeque.cpp @@ -0,0 +1,307 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkDeque.h" +#include "include/private/SkMalloc.h" + +struct SkDeque::Block { + Block* fNext; + Block* fPrev; + char* fBegin; // start of used section in this chunk + char* fEnd; // end of used section in this chunk + char* fStop; // end of the allocated chunk + + char* start() { return (char*)(this + 1); } + const char* start() const { return (const char*)(this + 1); } + + void init(size_t size) { + fNext = fPrev = nullptr; + fBegin = fEnd = nullptr; + fStop = (char*)this + size; + } +}; + +SkDeque::SkDeque(size_t elemSize, int allocCount) + : fElemSize(elemSize) + , fInitialStorage(nullptr) + , fCount(0) + , fAllocCount(allocCount) { + SkASSERT(allocCount >= 1); + fFrontBlock = fBackBlock = nullptr; + fFront = fBack = nullptr; +} + +SkDeque::SkDeque(size_t elemSize, void* storage, size_t storageSize, int allocCount) + : fElemSize(elemSize) + , fInitialStorage(storage) + , fCount(0) + , fAllocCount(allocCount) { + SkASSERT(storageSize == 0 || storage != nullptr); + SkASSERT(allocCount >= 1); + + if (storageSize >= sizeof(Block) + elemSize) { + fFrontBlock = (Block*)storage; + fFrontBlock->init(storageSize); + } else { + fFrontBlock = nullptr; + } + fBackBlock = fFrontBlock; + fFront = fBack = nullptr; +} + +SkDeque::~SkDeque() { + Block* head = fFrontBlock; + Block* initialHead = (Block*)fInitialStorage; + + while (head) { + Block* next = head->fNext; + if (head != initialHead) { + this->freeBlock(head); + } + head = next; + } +} + +void* SkDeque::push_front() { + fCount += 1; + + if (nullptr == fFrontBlock) { + fFrontBlock = this->allocateBlock(fAllocCount); + fBackBlock = fFrontBlock; // update our linklist + } + + Block* first = fFrontBlock; + char* begin; + + if (nullptr == first->fBegin) { + INIT_CHUNK: + first->fEnd = first->fStop; + begin = first->fStop - fElemSize; + } else { + begin = first->fBegin - fElemSize; + if (begin < first->start()) { // no more room in this chunk + // should we alloc more as we accumulate more elements? + first = this->allocateBlock(fAllocCount); + first->fNext = fFrontBlock; + fFrontBlock->fPrev = first; + fFrontBlock = first; + goto INIT_CHUNK; + } + } + + first->fBegin = begin; + + if (nullptr == fFront) { + SkASSERT(nullptr == fBack); + fFront = fBack = begin; + } else { + SkASSERT(fBack); + fFront = begin; + } + + return begin; +} + +void* SkDeque::push_back() { + fCount += 1; + + if (nullptr == fBackBlock) { + fBackBlock = this->allocateBlock(fAllocCount); + fFrontBlock = fBackBlock; // update our linklist + } + + Block* last = fBackBlock; + char* end; + + if (nullptr == last->fBegin) { + INIT_CHUNK: + last->fBegin = last->start(); + end = last->fBegin + fElemSize; + } else { + end = last->fEnd + fElemSize; + if (end > last->fStop) { // no more room in this chunk + // should we alloc more as we accumulate more elements? + last = this->allocateBlock(fAllocCount); + last->fPrev = fBackBlock; + fBackBlock->fNext = last; + fBackBlock = last; + goto INIT_CHUNK; + } + } + + last->fEnd = end; + end -= fElemSize; + + if (nullptr == fBack) { + SkASSERT(nullptr == fFront); + fFront = fBack = end; + } else { + SkASSERT(fFront); + fBack = end; + } + + return end; +} + +void SkDeque::pop_front() { + SkASSERT(fCount > 0); + fCount -= 1; + + Block* first = fFrontBlock; + + SkASSERT(first != nullptr); + + if (first->fBegin == nullptr) { // we were marked empty from before + first = first->fNext; + SkASSERT(first != nullptr); // else we popped too far + first->fPrev = nullptr; + this->freeBlock(fFrontBlock); + fFrontBlock = first; + } + + char* begin = first->fBegin + fElemSize; + SkASSERT(begin <= first->fEnd); + + if (begin < fFrontBlock->fEnd) { + first->fBegin = begin; + SkASSERT(first->fBegin); + fFront = first->fBegin; + } else { + first->fBegin = first->fEnd = nullptr; // mark as empty + if (nullptr == first->fNext) { + fFront = fBack = nullptr; + } else { + SkASSERT(first->fNext->fBegin); + fFront = first->fNext->fBegin; + } + } +} + +void SkDeque::pop_back() { + SkASSERT(fCount > 0); + fCount -= 1; + + Block* last = fBackBlock; + + SkASSERT(last != nullptr); + + if (last->fEnd == nullptr) { // we were marked empty from before + last = last->fPrev; + SkASSERT(last != nullptr); // else we popped too far + last->fNext = nullptr; + this->freeBlock(fBackBlock); + fBackBlock = last; + } + + char* end = last->fEnd - fElemSize; + SkASSERT(end >= last->fBegin); + + if (end > last->fBegin) { + last->fEnd = end; + SkASSERT(last->fEnd); + fBack = last->fEnd - fElemSize; + } else { + last->fBegin = last->fEnd = nullptr; // mark as empty + if (nullptr == last->fPrev) { + fFront = fBack = nullptr; + } else { + SkASSERT(last->fPrev->fEnd); + fBack = last->fPrev->fEnd - fElemSize; + } + } +} + +int SkDeque::numBlocksAllocated() const { + int numBlocks = 0; + + for (const Block* temp = fFrontBlock; temp; temp = temp->fNext) { + ++numBlocks; + } + + return numBlocks; +} + +SkDeque::Block* SkDeque::allocateBlock(int allocCount) { + Block* newBlock = (Block*)sk_malloc_throw(sizeof(Block) + allocCount * fElemSize); + newBlock->init(sizeof(Block) + allocCount * fElemSize); + return newBlock; +} + +void SkDeque::freeBlock(Block* block) { + sk_free(block); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkDeque::Iter::Iter() : fCurBlock(nullptr), fPos(nullptr), fElemSize(0) {} + +SkDeque::Iter::Iter(const SkDeque& d, IterStart startLoc) { + this->reset(d, startLoc); +} + +// Due to how reset and next work, next actually returns the current element +// pointed to by fPos and then updates fPos to point to the next one. +void* SkDeque::Iter::next() { + char* pos = fPos; + + if (pos) { // if we were valid, try to move to the next setting + char* next = pos + fElemSize; + SkASSERT(next <= fCurBlock->fEnd); + if (next == fCurBlock->fEnd) { // exhausted this chunk, move to next + do { + fCurBlock = fCurBlock->fNext; + } while (fCurBlock != nullptr && fCurBlock->fBegin == nullptr); + next = fCurBlock ? fCurBlock->fBegin : nullptr; + } + fPos = next; + } + return pos; +} + +// Like next, prev actually returns the current element pointed to by fPos and +// then makes fPos point to the previous element. +void* SkDeque::Iter::prev() { + char* pos = fPos; + + if (pos) { // if we were valid, try to move to the prior setting + char* prev = pos - fElemSize; + SkASSERT(prev >= fCurBlock->fBegin - fElemSize); + if (prev < fCurBlock->fBegin) { // exhausted this chunk, move to prior + do { + fCurBlock = fCurBlock->fPrev; + } while (fCurBlock != nullptr && fCurBlock->fEnd == nullptr); + prev = fCurBlock ? fCurBlock->fEnd - fElemSize : nullptr; + } + fPos = prev; + } + return pos; +} + +// reset works by skipping through the spare blocks at the start (or end) +// of the doubly linked list until a non-empty one is found. The fPos +// member is then set to the first (or last) element in the block. If +// there are no elements in the deque both fCurBlock and fPos will come +// out of this routine nullptr. +void SkDeque::Iter::reset(const SkDeque& d, IterStart startLoc) { + fElemSize = d.fElemSize; + + if (kFront_IterStart == startLoc) { + // initialize the iterator to start at the front + fCurBlock = d.fFrontBlock; + while (fCurBlock && nullptr == fCurBlock->fBegin) { + fCurBlock = fCurBlock->fNext; + } + fPos = fCurBlock ? fCurBlock->fBegin : nullptr; + } else { + // initialize the iterator to start at the back + fCurBlock = d.fBackBlock; + while (fCurBlock && nullptr == fCurBlock->fEnd) { + fCurBlock = fCurBlock->fPrev; + } + fPos = fCurBlock ? fCurBlock->fEnd - fElemSize : nullptr; + } +} diff --git a/gfx/skia/skia/src/core/SkDescriptor.cpp b/gfx/skia/skia/src/core/SkDescriptor.cpp new file mode 100644 index 0000000000..28eb677105 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDescriptor.cpp @@ -0,0 +1,138 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDescriptor.h" + +#include + +#include "include/core/SkTypes.h" +#include "include/private/SkTo.h" +#include "src/core/SkOpts.h" + +std::unique_ptr SkDescriptor::Alloc(size_t length) { + SkASSERT(SkAlign4(length) == length); + return std::unique_ptr(static_cast(::operator new (length))); +} + +void SkDescriptor::operator delete(void* p) { ::operator delete(p); } + +void* SkDescriptor::addEntry(uint32_t tag, size_t length, const void* data) { + SkASSERT(tag); + SkASSERT(SkAlign4(length) == length); + SkASSERT(this->findEntry(tag, nullptr) == nullptr); + + Entry* entry = (Entry*)((char*)this + fLength); + entry->fTag = tag; + entry->fLen = SkToU32(length); + if (data) { + memcpy(entry + 1, data, length); + } + + fCount += 1; + fLength = SkToU32(fLength + sizeof(Entry) + length); + return (entry + 1); // return its data +} + +void SkDescriptor::computeChecksum() { + fChecksum = SkDescriptor::ComputeChecksum(this); +} + +const void* SkDescriptor::findEntry(uint32_t tag, uint32_t* length) const { + const Entry* entry = (const Entry*)(this + 1); + int count = fCount; + + while (--count >= 0) { + if (entry->fTag == tag) { + if (length) { + *length = entry->fLen; + } + return entry + 1; + } + entry = (const Entry*)((const char*)(entry + 1) + entry->fLen); + } + return nullptr; +} + +std::unique_ptr SkDescriptor::copy() const { + std::unique_ptr desc = SkDescriptor::Alloc(fLength); + memcpy(desc.get(), this, fLength); + return desc; +} + +bool SkDescriptor::operator==(const SkDescriptor& other) const { + + // the first value we should look at is the checksum, so this loop + // should terminate early if they descriptors are different. + // NOTE: if we wrote a sentinel value at the end of each, we could + // remove the aa < stop test in the loop... + const uint32_t* aa = (const uint32_t*)this; + const uint32_t* bb = (const uint32_t*)&other; + const uint32_t* stop = (const uint32_t*)((const char*)aa + fLength); + do { + if (*aa++ != *bb++) + return false; + } while (aa < stop); + return true; +} + +uint32_t SkDescriptor::ComputeChecksum(const SkDescriptor* desc) { + const uint32_t* ptr = (const uint32_t*)desc + 1; // skip the checksum field + size_t len = desc->fLength - sizeof(uint32_t); + return SkOpts::hash(ptr, len); +} + +bool SkDescriptor::isValid() const { + uint32_t count = 0; + size_t offset = sizeof(SkDescriptor); + + while (offset < fLength) { + const Entry* entry = (const Entry*)(reinterpret_cast(this) + offset); + // rec tags are always a known size. + if (entry->fTag == kRec_SkDescriptorTag && entry->fLen != sizeof(SkScalerContextRec)) { + return false; + } + offset += sizeof(Entry) + entry->fLen; + count++; + } + return offset <= fLength && count == fCount; +} + +SkAutoDescriptor::SkAutoDescriptor() = default; +SkAutoDescriptor::SkAutoDescriptor(size_t size) { this->reset(size); } +SkAutoDescriptor::SkAutoDescriptor(const SkDescriptor& desc) { this->reset(desc); } +SkAutoDescriptor::SkAutoDescriptor(const SkAutoDescriptor& ad) { + this->reset(*ad.getDesc()); +} +SkAutoDescriptor& SkAutoDescriptor::operator=(const SkAutoDescriptor& ad) { + this->reset(*ad.getDesc()); + return *this; +} + +SkAutoDescriptor::~SkAutoDescriptor() { this->free(); } + +void SkAutoDescriptor::reset(size_t size) { + this->free(); + if (size <= sizeof(fStorage)) { + fDesc = reinterpret_cast(&fStorage); + } else { + fDesc = SkDescriptor::Alloc(size).release(); + } +} + +void SkAutoDescriptor::reset(const SkDescriptor& desc) { + size_t size = desc.getLength(); + this->reset(size); + memcpy(fDesc, &desc, size); +} + +void SkAutoDescriptor::free() { + if (fDesc != (SkDescriptor*)&fStorage) { + delete fDesc; + } +} + + diff --git a/gfx/skia/skia/src/core/SkDescriptor.h b/gfx/skia/skia/src/core/SkDescriptor.h new file mode 100644 index 0000000000..88202c3eb3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDescriptor.h @@ -0,0 +1,105 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDescriptor_DEFINED +#define SkDescriptor_DEFINED + +#include + +#include "include/private/SkMacros.h" +#include "include/private/SkNoncopyable.h" +#include "src/core/SkScalerContext.h" + +class SkDescriptor : SkNoncopyable { +public: + static size_t ComputeOverhead(int entryCount) { + SkASSERT(entryCount >= 0); + return sizeof(SkDescriptor) + entryCount * sizeof(Entry); + } + + static std::unique_ptr Alloc(size_t length); + + // Ensure the unsized delete is called. + void operator delete(void* p); + void init() { + fLength = sizeof(SkDescriptor); + fCount = 0; + } + uint32_t getLength() const { return fLength; } + void* addEntry(uint32_t tag, size_t length, const void* data = nullptr); + void computeChecksum(); + + // Assumes that getLength <= capacity of this SkDescriptor. + bool isValid() const; + +#ifdef SK_DEBUG + void assertChecksum() const { + SkASSERT(SkDescriptor::ComputeChecksum(this) == fChecksum); + } +#endif + + const void* findEntry(uint32_t tag, uint32_t* length) const; + + std::unique_ptr copy() const; + + // This assumes that all memory added has a length that is a multiple of 4. This is checked + // by the assert in addEntry. + bool operator==(const SkDescriptor& other) const; + bool operator!=(const SkDescriptor& other) const { return !(*this == other); } + + uint32_t getChecksum() const { return fChecksum; } + + struct Entry { + uint32_t fTag; + uint32_t fLen; + }; + +#ifdef SK_DEBUG + uint32_t getCount() const { return fCount; } +#endif + +private: + // private so no one can create one except our factories + SkDescriptor() = default; + friend class SkDescriptorTestHelper; + + static uint32_t ComputeChecksum(const SkDescriptor* desc); + + uint32_t fChecksum; // must be first + uint32_t fLength; // must be second + uint32_t fCount; +}; + +class SkAutoDescriptor { +public: + SkAutoDescriptor(); + explicit SkAutoDescriptor(size_t size); + explicit SkAutoDescriptor(const SkDescriptor& desc); + SkAutoDescriptor(const SkAutoDescriptor& ad); + SkAutoDescriptor& operator= (const SkAutoDescriptor& ad); + SkAutoDescriptor(SkAutoDescriptor&&) = delete; + SkAutoDescriptor& operator= (SkAutoDescriptor&&) = delete; + + ~SkAutoDescriptor(); + + void reset(size_t size); + void reset(const SkDescriptor& desc); + SkDescriptor* getDesc() const { SkASSERT(fDesc); return fDesc; } + +private: + void free(); + static constexpr size_t kStorageSize + = sizeof(SkDescriptor) + + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec) // for rec + + sizeof(SkDescriptor::Entry) + sizeof(void*) // for typeface + + 32; // slop for occasional small extras + + SkDescriptor* fDesc{nullptr}; + std::aligned_storage::type fStorage; +}; + +#endif //SkDescriptor_DEFINED diff --git a/gfx/skia/skia/src/core/SkDevice.cpp b/gfx/skia/skia/src/core/SkDevice.cpp new file mode 100644 index 0000000000..094f273e91 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDevice.cpp @@ -0,0 +1,475 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDevice.h" + +#include "include/core/SkColorFilter.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkPathMeasure.h" +#include "include/core/SkRSXform.h" +#include "include/core/SkShader.h" +#include "include/core/SkVertices.h" +#include "include/private/SkTo.h" +#include "src/core/SkDraw.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkLatticeIter.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/core/SkUtils.h" +#include "src/image/SkImage_Base.h" +#include "src/shaders/SkLocalMatrixShader.h" +#include "src/utils/SkPatchUtils.h" + +SkBaseDevice::SkBaseDevice(const SkImageInfo& info, const SkSurfaceProps& surfaceProps) + : fInfo(info) + , fSurfaceProps(surfaceProps) +{ + fOrigin = {0, 0}; + fCTM.reset(); +} + +void SkBaseDevice::setOrigin(const SkMatrix& globalCTM, int x, int y) { + fOrigin.set(x, y); + fCTM = globalCTM; + fCTM.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y)); +} + +void SkBaseDevice::setGlobalCTM(const SkMatrix& ctm) { + fCTM = ctm; + if (fOrigin.fX | fOrigin.fY) { + fCTM.postTranslate(-SkIntToScalar(fOrigin.fX), -SkIntToScalar(fOrigin.fY)); + } +} + +bool SkBaseDevice::clipIsWideOpen() const { + if (ClipType::kRect == this->onGetClipType()) { + SkRegion rgn; + this->onAsRgnClip(&rgn); + SkASSERT(rgn.isRect()); + return rgn.getBounds() == SkIRect::MakeWH(this->width(), this->height()); + } else { + return false; + } +} + +SkPixelGeometry SkBaseDevice::CreateInfo::AdjustGeometry(TileUsage tileUsage, SkPixelGeometry geo) { + switch (tileUsage) { + case kPossible_TileUsage: + // (we think) for compatibility with old clients, we assume this layer can support LCD + // even though they may not have marked it as opaque... seems like we should update + // our callers (reed/robertphilips). + break; + case kNever_TileUsage: + geo = kUnknown_SkPixelGeometry; + break; + } + return geo; +} + +static inline bool is_int(float x) { + return x == (float) sk_float_round2int(x); +} + +void SkBaseDevice::drawRegion(const SkRegion& region, const SkPaint& paint) { + const SkMatrix& ctm = this->ctm(); + bool isNonTranslate = ctm.getType() & ~(SkMatrix::kTranslate_Mask); + bool complexPaint = paint.getStyle() != SkPaint::kFill_Style || paint.getMaskFilter() || + paint.getPathEffect(); + bool antiAlias = paint.isAntiAlias() && (!is_int(ctm.getTranslateX()) || + !is_int(ctm.getTranslateY())); + if (isNonTranslate || complexPaint || antiAlias) { + SkPath path; + region.getBoundaryPath(&path); + path.setIsVolatile(true); + return this->drawPath(path, paint, true); + } + + SkRegion::Iterator it(region); + while (!it.done()) { + this->drawRect(SkRect::Make(it.rect()), paint); + it.next(); + } +} + +void SkBaseDevice::drawArc(const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, const SkPaint& paint) { + SkPath path; + bool isFillNoPathEffect = SkPaint::kFill_Style == paint.getStyle() && !paint.getPathEffect(); + SkPathPriv::CreateDrawArcPath(&path, oval, startAngle, sweepAngle, useCenter, + isFillNoPathEffect); + this->drawPath(path, paint); +} + +void SkBaseDevice::drawDRRect(const SkRRect& outer, + const SkRRect& inner, const SkPaint& paint) { + SkPath path; + path.addRRect(outer); + path.addRRect(inner); + path.setFillType(SkPath::kEvenOdd_FillType); + path.setIsVolatile(true); + + this->drawPath(path, paint, true); +} + +void SkBaseDevice::drawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode bmode, const SkPaint& paint) { + SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &this->ctm()); + auto vertices = SkPatchUtils::MakeVertices(cubics, colors, texCoords, lod.width(), lod.height(), + this->imageInfo().colorSpace()); + if (vertices) { + this->drawVertices(vertices.get(), nullptr, 0, bmode, paint); + } +} + +void SkBaseDevice::drawImageRect(const SkImage* image, const SkRect* src, + const SkRect& dst, const SkPaint& paint, + SkCanvas::SrcRectConstraint constraint) { + SkBitmap bm; + if (as_IB(image)->getROPixels(&bm)) { + this->drawBitmapRect(bm, src, dst, paint, constraint); + } +} + +void SkBaseDevice::drawImageNine(const SkImage* image, const SkIRect& center, + const SkRect& dst, const SkPaint& paint) { + SkLatticeIter iter(image->width(), image->height(), center, dst); + + SkRect srcR, dstR; + while (iter.next(&srcR, &dstR)) { + this->drawImageRect(image, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint); + } +} + +void SkBaseDevice::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint& paint) { + SkLatticeIter iter(bitmap.width(), bitmap.height(), center, dst); + + SkRect srcR, dstR; + while (iter.next(&srcR, &dstR)) { + this->drawBitmapRect(bitmap, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint); + } +} + +void SkBaseDevice::drawImageLattice(const SkImage* image, + const SkCanvas::Lattice& lattice, const SkRect& dst, + const SkPaint& paint) { + SkLatticeIter iter(lattice, dst); + + SkRect srcR, dstR; + SkColor c; + bool isFixedColor = false; + const SkImageInfo info = SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType); + + while (iter.next(&srcR, &dstR, &isFixedColor, &c)) { + if (isFixedColor || (srcR.width() <= 1.0f && srcR.height() <= 1.0f && + image->readPixels(info, &c, 4, srcR.fLeft, srcR.fTop))) { + // Fast draw with drawRect, if this is a patch containing a single color + // or if this is a patch containing a single pixel. + if (0 != c || !paint.isSrcOver()) { + SkPaint paintCopy(paint); + int alpha = SkAlphaMul(SkColorGetA(c), SkAlpha255To256(paint.getAlpha())); + paintCopy.setColor(SkColorSetA(c, alpha)); + this->drawRect(dstR, paintCopy); + } + } else { + this->drawImageRect(image, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint); + } + } +} + +void SkBaseDevice::drawBitmapLattice(const SkBitmap& bitmap, + const SkCanvas::Lattice& lattice, const SkRect& dst, + const SkPaint& paint) { + SkLatticeIter iter(lattice, dst); + + SkRect srcR, dstR; + while (iter.next(&srcR, &dstR)) { + this->drawBitmapRect(bitmap, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint); + } +} + +static SkPoint* quad_to_tris(SkPoint tris[6], const SkPoint quad[4]) { + tris[0] = quad[0]; + tris[1] = quad[1]; + tris[2] = quad[2]; + + tris[3] = quad[0]; + tris[4] = quad[2]; + tris[5] = quad[3]; + + return tris + 6; +} + +void SkBaseDevice::drawAtlas(const SkImage* atlas, const SkRSXform xform[], + const SkRect tex[], const SkColor colors[], int quadCount, + SkBlendMode mode, const SkPaint& paint) { + const int triCount = quadCount << 1; + const int vertexCount = triCount * 3; + uint32_t flags = SkVertices::kHasTexCoords_BuilderFlag; + if (colors) { + flags |= SkVertices::kHasColors_BuilderFlag; + } + SkVertices::Builder builder(SkVertices::kTriangles_VertexMode, vertexCount, 0, flags); + + SkPoint* vPos = builder.positions(); + SkPoint* vTex = builder.texCoords(); + SkColor* vCol = builder.colors(); + for (int i = 0; i < quadCount; ++i) { + SkPoint tmp[4]; + xform[i].toQuad(tex[i].width(), tex[i].height(), tmp); + vPos = quad_to_tris(vPos, tmp); + + tex[i].toQuad(tmp); + vTex = quad_to_tris(vTex, tmp); + + if (colors) { + sk_memset32(vCol, colors[i], 6); + vCol += 6; + } + } + SkPaint p(paint); + p.setShader(atlas->makeShader()); + this->drawVertices(builder.detach().get(), nullptr, 0, mode, p); +} + + +void SkBaseDevice::drawEdgeAAQuad(const SkRect& r, const SkPoint clip[4], SkCanvas::QuadAAFlags aa, + const SkColor4f& color, SkBlendMode mode) { + SkPaint paint; + paint.setColor4f(color); + paint.setBlendMode(mode); + paint.setAntiAlias(aa == SkCanvas::kAll_QuadAAFlags); + + if (clip) { + // Draw the clip directly as a quad since it's a filled color with no local coords + SkPath clipPath; + clipPath.addPoly(clip, 4, true); + this->drawPath(clipPath, paint); + } else { + this->drawRect(r, paint); + } +} + +void SkBaseDevice::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry images[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint& paint, + SkCanvas::SrcRectConstraint constraint) { + SkASSERT(paint.getStyle() == SkPaint::kFill_Style); + SkASSERT(!paint.getPathEffect()); + + SkPaint entryPaint = paint; + const SkMatrix baseCTM = this->ctm(); + int clipIndex = 0; + for (int i = 0; i < count; ++i) { + // TODO: Handle per-edge AA. Right now this mirrors the SkiaRenderer component of Chrome + // which turns off antialiasing unless all four edges should be antialiased. This avoids + // seaming in tiled composited layers. + entryPaint.setAntiAlias(images[i].fAAFlags == SkCanvas::kAll_QuadAAFlags); + entryPaint.setAlphaf(paint.getAlphaf() * images[i].fAlpha); + + bool needsRestore = false; + SkASSERT(images[i].fMatrixIndex < 0 || preViewMatrices); + if (images[i].fMatrixIndex >= 0) { + this->save(); + this->setGlobalCTM(SkMatrix::Concat( + baseCTM, preViewMatrices[images[i].fMatrixIndex])); + needsRestore = true; + } + + SkASSERT(!images[i].fHasClip || dstClips); + if (images[i].fHasClip) { + // Since drawImageRect requires a srcRect, the dst clip is implemented as a true clip + if (!needsRestore) { + this->save(); + needsRestore = true; + } + SkPath clipPath; + clipPath.addPoly(dstClips + clipIndex, 4, true); + this->clipPath(clipPath, SkClipOp::kIntersect, entryPaint.isAntiAlias()); + clipIndex += 4; + } + this->drawImageRect(images[i].fImage.get(), &images[i].fSrcRect, images[i].fDstRect, + entryPaint, constraint); + if (needsRestore) { + this->restore(baseCTM); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkBaseDevice::drawDrawable(SkDrawable* drawable, const SkMatrix* matrix, SkCanvas* canvas) { + drawable->draw(canvas, matrix); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkBaseDevice::drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&, + SkImage*, const SkMatrix&) {} +sk_sp SkBaseDevice::makeSpecial(const SkBitmap&) { return nullptr; } +sk_sp SkBaseDevice::makeSpecial(const SkImage*) { return nullptr; } +sk_sp SkBaseDevice::snapSpecial(const SkIRect&, bool) { return nullptr; } +sk_sp SkBaseDevice::snapSpecial() { + return this->snapSpecial(SkIRect::MakeWH(this->width(), this->height())); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkBaseDevice::readPixels(const SkPixmap& pm, int x, int y) { + return this->onReadPixels(pm, x, y); +} + +bool SkBaseDevice::writePixels(const SkPixmap& pm, int x, int y) { + return this->onWritePixels(pm, x, y); +} + +bool SkBaseDevice::onWritePixels(const SkPixmap&, int, int) { + return false; +} + +bool SkBaseDevice::onReadPixels(const SkPixmap&, int x, int y) { + return false; +} + +bool SkBaseDevice::accessPixels(SkPixmap* pmap) { + SkPixmap tempStorage; + if (nullptr == pmap) { + pmap = &tempStorage; + } + return this->onAccessPixels(pmap); +} + +bool SkBaseDevice::peekPixels(SkPixmap* pmap) { + SkPixmap tempStorage; + if (nullptr == pmap) { + pmap = &tempStorage; + } + return this->onPeekPixels(pmap); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkUtils.h" + +void SkBaseDevice::drawGlyphRunRSXform(const SkFont& font, const SkGlyphID glyphs[], + const SkRSXform xform[], int count, SkPoint origin, + const SkPaint& paint) { + const SkMatrix originalCTM = this->ctm(); + if (!originalCTM.isFinite() || !SkScalarIsFinite(font.getSize()) || + !SkScalarIsFinite(font.getScaleX()) || + !SkScalarIsFinite(font.getSkewX())) { + return; + } + + SkPoint sharedPos{0, 0}; // we're at the origin + SkGlyphID glyphID; + SkGlyphRun glyphRun{ + font, + SkSpan{&sharedPos, 1}, + SkSpan{&glyphID, 1}, + SkSpan{}, + SkSpan{} + }; + + for (int i = 0; i < count; i++) { + glyphID = glyphs[i]; + // now "glyphRun" is pointing at the current glyphID + + SkMatrix ctm; + ctm.setRSXform(xform[i]).postTranslate(origin.fX, origin.fY); + + // We want to rotate each glyph by the rsxform, but we don't want to rotate "space" + // (i.e. the shader that cares about the ctm) so we have to undo our little ctm trick + // with a localmatrixshader so that the shader draws as if there was no change to the ctm. + SkPaint transformingPaint{paint}; + auto shader = transformingPaint.getShader(); + if (shader) { + SkMatrix inverse; + if (ctm.invert(&inverse)) { + transformingPaint.setShader(shader->makeWithLocalMatrix(inverse)); + } else { + transformingPaint.setShader(nullptr); // can't handle this xform + } + } + + ctm.setConcat(originalCTM, ctm); + this->setCTM(ctm); + + this->drawGlyphRunList(SkGlyphRunList{glyphRun, transformingPaint}); + } + this->setCTM(originalCTM); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkBaseDevice::makeSurface(SkImageInfo const&, SkSurfaceProps const&) { + return nullptr; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +void SkBaseDevice::LogDrawScaleFactor(const SkMatrix& view, const SkMatrix& srcToDst, + SkFilterQuality filterQuality) { +#if SK_HISTOGRAMS_ENABLED + SkMatrix matrix = SkMatrix::Concat(view, srcToDst); + enum ScaleFactor { + kUpscale_ScaleFactor, + kNoScale_ScaleFactor, + kDownscale_ScaleFactor, + kLargeDownscale_ScaleFactor, + + kLast_ScaleFactor = kLargeDownscale_ScaleFactor + }; + + float rawScaleFactor = matrix.getMinScale(); + + ScaleFactor scaleFactor; + if (rawScaleFactor < 0.5f) { + scaleFactor = kLargeDownscale_ScaleFactor; + } else if (rawScaleFactor < 1.0f) { + scaleFactor = kDownscale_ScaleFactor; + } else if (rawScaleFactor > 1.0f) { + scaleFactor = kUpscale_ScaleFactor; + } else { + scaleFactor = kNoScale_ScaleFactor; + } + + switch (filterQuality) { + case kNone_SkFilterQuality: + SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.NoneFilterQuality", scaleFactor, + kLast_ScaleFactor + 1); + break; + case kLow_SkFilterQuality: + SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.LowFilterQuality", scaleFactor, + kLast_ScaleFactor + 1); + break; + case kMedium_SkFilterQuality: + SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.MediumFilterQuality", scaleFactor, + kLast_ScaleFactor + 1); + break; + case kHigh_SkFilterQuality: + SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.HighFilterQuality", scaleFactor, + kLast_ScaleFactor + 1); + break; + } + + // Also log filter quality independent scale factor. + SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.AnyFilterQuality", scaleFactor, + kLast_ScaleFactor + 1); + + // Also log an overall histogram of filter quality. + SK_HISTOGRAM_ENUMERATION("FilterQuality", filterQuality, kLast_SkFilterQuality + 1); +#endif +} diff --git a/gfx/skia/skia/src/core/SkDevice.h b/gfx/skia/skia/src/core/SkDevice.h new file mode 100644 index 0000000000..ce2629fd80 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDevice.h @@ -0,0 +1,452 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDevice_DEFINED +#define SkDevice_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkRegion.h" +#include "include/core/SkSurfaceProps.h" +#include "include/private/SkNoncopyable.h" + +class SkBitmap; +struct SkDrawShadowRec; +class SkGlyphRun; +class SkGlyphRunList; +class SkImageFilterCache; +struct SkIRect; +class SkMatrix; +class SkRasterHandleAllocator; +class SkSpecialImage; + +class SkBaseDevice : public SkRefCnt { +public: + SkBaseDevice(const SkImageInfo&, const SkSurfaceProps&); + + /** + * Return ImageInfo for this device. If the canvas is not backed by pixels + * (cpu or gpu), then the info's ColorType will be kUnknown_SkColorType. + */ + const SkImageInfo& imageInfo() const { return fInfo; } + + /** + * Return SurfaceProps for this device. + */ + const SkSurfaceProps& surfaceProps() const { + return fSurfaceProps; + } + + /** + * Return the bounds of the device in the coordinate space of the root + * canvas. The root device will have its top-left at 0,0, but other devices + * such as those associated with saveLayer may have a non-zero origin. + */ + void getGlobalBounds(SkIRect* bounds) const { + SkASSERT(bounds); + const SkIPoint& origin = this->getOrigin(); + bounds->setXYWH(origin.x(), origin.y(), this->width(), this->height()); + } + + SkIRect getGlobalBounds() const { + SkIRect bounds; + this->getGlobalBounds(&bounds); + return bounds; + } + + int width() const { + return this->imageInfo().width(); + } + + int height() const { + return this->imageInfo().height(); + } + + bool isOpaque() const { + return this->imageInfo().isOpaque(); + } + + bool writePixels(const SkPixmap&, int x, int y); + + /** + * Try to get write-access to the pixels behind the device. If successful, this returns true + * and fills-out the pixmap parameter. On success it also bumps the genID of the underlying + * bitmap. + * + * On failure, returns false and ignores the pixmap parameter. + */ + bool accessPixels(SkPixmap* pmap); + + /** + * Try to get read-only-access to the pixels behind the device. If successful, this returns + * true and fills-out the pixmap parameter. + * + * On failure, returns false and ignores the pixmap parameter. + */ + bool peekPixels(SkPixmap*); + + /** + * Return the device's origin: its offset in device coordinates from + * the default origin in its canvas' matrix/clip + */ + const SkIPoint& getOrigin() const { return fOrigin; } + + virtual void* getRasterHandle() const { return nullptr; } + + void save() { this->onSave(); } + void restore(const SkMatrix& ctm) { + this->onRestore(); + this->setGlobalCTM(ctm); + } + void clipRect(const SkRect& rect, SkClipOp op, bool aa) { + this->onClipRect(rect, op, aa); + } + void clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) { + this->onClipRRect(rrect, op, aa); + } + void clipPath(const SkPath& path, SkClipOp op, bool aa) { + this->onClipPath(path, op, aa); + } + void clipRegion(const SkRegion& region, SkClipOp op) { + this->onClipRegion(region, op); + } + void androidFramework_setDeviceClipRestriction(SkIRect* mutableClipRestriction) { + this->onSetDeviceClipRestriction(mutableClipRestriction); + } + bool clipIsWideOpen() const; + + const SkMatrix& ctm() const { return fCTM; } + void setCTM(const SkMatrix& ctm) { + fCTM = ctm; + } + void setGlobalCTM(const SkMatrix& ctm); + virtual void validateDevBounds(const SkIRect&) {} + +protected: + enum TileUsage { + kPossible_TileUsage, //!< the created device may be drawn tiled + kNever_TileUsage, //!< the created device will never be drawn tiled + }; + + struct TextFlags { + uint32_t fFlags; // SkPaint::getFlags() + }; + + virtual void onSave() {} + virtual void onRestore() {} + virtual void onClipRect(const SkRect& rect, SkClipOp, bool aa) {} + virtual void onClipRRect(const SkRRect& rrect, SkClipOp, bool aa) {} + virtual void onClipPath(const SkPath& path, SkClipOp, bool aa) {} + virtual void onClipRegion(const SkRegion& deviceRgn, SkClipOp) {} + virtual void onSetDeviceClipRestriction(SkIRect* mutableClipRestriction) {} + virtual bool onClipIsAA() const = 0; + virtual void onAsRgnClip(SkRegion*) const = 0; + enum class ClipType { + kEmpty, + kRect, + kComplex + }; + virtual ClipType onGetClipType() const = 0; + + /** These are called inside the per-device-layer loop for each draw call. + When these are called, we have already applied any saveLayer operations, + and are handling any looping from the paint. + */ + virtual void drawPaint(const SkPaint& paint) = 0; + virtual void drawPoints(SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint) = 0; + virtual void drawRect(const SkRect& r, + const SkPaint& paint) = 0; + virtual void drawRegion(const SkRegion& r, + const SkPaint& paint); + virtual void drawOval(const SkRect& oval, + const SkPaint& paint) = 0; + /** By the time this is called we know that abs(sweepAngle) is in the range [0, 360). */ + virtual void drawArc(const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, const SkPaint& paint); + virtual void drawRRect(const SkRRect& rr, + const SkPaint& paint) = 0; + + // Default impl calls drawPath() + virtual void drawDRRect(const SkRRect& outer, + const SkRRect& inner, const SkPaint&); + + /** + * If pathIsMutable, then the implementation is allowed to cast path to a + * non-const pointer and modify it in place (as an optimization). Canvas + * may do this to implement helpers such as drawOval, by placing a temp + * path on the stack to hold the representation of the oval. + */ + virtual void drawPath(const SkPath& path, + const SkPaint& paint, + bool pathIsMutable = false) = 0; + virtual void drawSprite(const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) = 0; + + /** + * The default impl. will create a bitmap-shader from the bitmap, + * and call drawRect with it. + */ + virtual void drawBitmapRect(const SkBitmap&, + const SkRect* srcOrNull, const SkRect& dst, + const SkPaint& paint, + SkCanvas::SrcRectConstraint) = 0; + virtual void drawBitmapNine(const SkBitmap&, const SkIRect& center, + const SkRect& dst, const SkPaint&); + virtual void drawBitmapLattice(const SkBitmap&, const SkCanvas::Lattice&, + const SkRect& dst, const SkPaint&); + + virtual void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint&, SkCanvas::SrcRectConstraint); + virtual void drawImageNine(const SkImage*, const SkIRect& center, + const SkRect& dst, const SkPaint&); + virtual void drawImageLattice(const SkImage*, const SkCanvas::Lattice&, + const SkRect& dst, const SkPaint&); + + virtual void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) = 0; + virtual void drawShadow(const SkPath&, const SkDrawShadowRec&); + + virtual void drawGlyphRunList(const SkGlyphRunList& glyphRunList) = 0; + // default implementation calls drawVertices + virtual void drawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode, const SkPaint& paint); + + // default implementation calls drawPath + virtual void drawAtlas(const SkImage* atlas, const SkRSXform[], const SkRect[], + const SkColor[], int count, SkBlendMode, const SkPaint&); + + virtual void drawAnnotation(const SkRect&, const char[], SkData*) {} + + // Default impl always calls drawRect() with a solid-color paint, setting it to anti-aliased + // only when all edge flags are set. If there's a clip region, it draws that using drawPath, + // or uses clipPath(). + virtual void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, + SkBlendMode mode); + // Default impl uses drawImageRect per entry, being anti-aliased only when an entry's edge flags + // are all set. If there's a clip region, it will be applied using clipPath(). + virtual void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint& paint, SkCanvas::SrcRectConstraint); + + /** The SkDevice passed will be an SkDevice which was returned by a call to + onCreateDevice on this device with kNeverTile_TileExpectation. + */ + virtual void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) = 0; + + void drawGlyphRunRSXform(const SkFont&, const SkGlyphID[], const SkRSXform[], int count, + SkPoint origin, const SkPaint& paint); + + virtual void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas*); + + virtual void drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&, + SkImage* clipImage, const SkMatrix& clipMatrix); + virtual sk_sp makeSpecial(const SkBitmap&); + virtual sk_sp makeSpecial(const SkImage*); + // Get a view of the entire device's current contents as an image. + sk_sp snapSpecial(); + // Snap the 'subset' contents from this device, possibly as a read-only view. If 'forceCopy' + // is true then the returned image's pixels must not be affected by subsequent draws into the + // device. When 'forceCopy' is false, the image can be a view into the device's pixels + // (avoiding a copy for performance, at the expense of safety). Default returns null. + virtual sk_sp snapSpecial(const SkIRect& subset, bool forceCopy = false); + + virtual void setImmutable() {} + + bool readPixels(const SkPixmap&, int x, int y); + + /////////////////////////////////////////////////////////////////////////// + + virtual GrContext* context() const { return nullptr; } + + virtual sk_sp makeSurface(const SkImageInfo&, const SkSurfaceProps&); + virtual bool onPeekPixels(SkPixmap*) { return false; } + + /** + * The caller is responsible for "pre-clipping" the dst. The impl can assume that the dst + * image at the specified x,y offset will fit within the device's bounds. + * + * This is explicitly asserted in readPixels(), the public way to call this. + */ + virtual bool onReadPixels(const SkPixmap&, int x, int y); + + /** + * The caller is responsible for "pre-clipping" the src. The impl can assume that the src + * image at the specified x,y offset will fit within the device's bounds. + * + * This is explicitly asserted in writePixelsDirect(), the public way to call this. + */ + virtual bool onWritePixels(const SkPixmap&, int x, int y); + + virtual bool onAccessPixels(SkPixmap*) { return false; } + + struct CreateInfo { + static SkPixelGeometry AdjustGeometry(TileUsage, SkPixelGeometry); + + // The constructor may change the pixel geometry based on other parameters. + CreateInfo(const SkImageInfo& info, + TileUsage tileUsage, + SkPixelGeometry geo, + bool trackCoverage, + SkRasterHandleAllocator* allocator) + : fInfo(info) + , fTileUsage(tileUsage) + , fPixelGeometry(AdjustGeometry(tileUsage, geo)) + , fTrackCoverage(trackCoverage) + , fAllocator(allocator) + {} + + const SkImageInfo fInfo; + const TileUsage fTileUsage; + const SkPixelGeometry fPixelGeometry; + const bool fTrackCoverage = false; + SkRasterHandleAllocator* fAllocator = nullptr; + }; + + /** + * Create a new device based on CreateInfo. If the paint is not null, then it represents a + * preview of how the new device will be composed with its creator device (this). + * + * The subclass may be handed this device in drawDevice(), so it must always return + * a device that it knows how to draw, and that it knows how to identify if it is not of the + * same subclass (since drawDevice is passed a SkBaseDevice*). If the subclass cannot fulfill + * that contract (e.g. PDF cannot support some settings on the paint) it should return NULL, + * and the caller may then decide to explicitly create a bitmapdevice, knowing that later + * it could not call drawDevice with it (but it could call drawSprite or drawBitmap). + */ + virtual SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) { + return nullptr; + } + + // A helper function used by derived classes to log the scale factor of a bitmap or image draw. + static void LogDrawScaleFactor(const SkMatrix& view, const SkMatrix& srcToDst, SkFilterQuality); + +private: + friend class SkAndroidFrameworkUtils; + friend class SkCanvas; + friend struct DeviceCM; //for setMatrixClip + friend class SkDraw; + friend class SkDrawIter; + friend class SkSurface_Raster; + friend class DeviceTestingAccess; + + // Temporarily friend the SkGlyphRunBuilder until drawPosText is gone. + friend class SkGlyphRun; + friend class SkGlyphRunList; + friend class SkGlyphRunBuilder; + + // used to change the backend's pixels (and possibly config/rowbytes) + // but cannot change the width/height, so there should be no change to + // any clip information. + // TODO: move to SkBitmapDevice + virtual void replaceBitmapBackendForRasterSurface(const SkBitmap&) {} + + virtual bool forceConservativeRasterClip() const { return false; } + + /** + * Don't call this! + */ + virtual GrRenderTargetContext* accessRenderTargetContext() { return nullptr; } + + // just called by SkCanvas when built as a layer + void setOrigin(const SkMatrix& ctm, int x, int y); + + /** Causes any deferred drawing to the device to be completed. + */ + virtual void flush() {} + + virtual SkImageFilterCache* getImageFilterCache() { return nullptr; } + + friend class SkNoPixelsDevice; + friend class SkBitmapDevice; + void privateResize(int w, int h) { + *const_cast(&fInfo) = fInfo.makeWH(w, h); + } + + SkIPoint fOrigin; + const SkImageInfo fInfo; + const SkSurfaceProps fSurfaceProps; + SkMatrix fCTM; + + typedef SkRefCnt INHERITED; +}; + +class SkNoPixelsDevice : public SkBaseDevice { +public: + SkNoPixelsDevice(const SkIRect& bounds, const SkSurfaceProps& props, + sk_sp colorSpace = nullptr) + : SkBaseDevice(SkImageInfo::Make(bounds.size(), kUnknown_SkColorType, + kUnknown_SkAlphaType, std::move(colorSpace)), + props) { + // this fails if we enable this assert: DiscardableImageMapTest.GetDiscardableImagesInRectMaxImage + //SkASSERT(bounds.width() >= 0 && bounds.height() >= 0); + + this->setOrigin(SkMatrix::I(), bounds.left(), bounds.top()); + } + + void resetForNextPicture(const SkIRect& bounds) { + //SkASSERT(bounds.width() >= 0 && bounds.height() >= 0); + this->privateResize(bounds.width(), bounds.height()); + this->setOrigin(SkMatrix::I(), bounds.left(), bounds.top()); + } + +protected: + // We don't track the clip at all (for performance), but we have to respond to some queries. + // We pretend to be wide-open. We could pretend to always be empty, but that *seems* worse. + void onSave() override {} + void onRestore() override {} + void onClipRect(const SkRect& rect, SkClipOp, bool aa) override {} + void onClipRRect(const SkRRect& rrect, SkClipOp, bool aa) override {} + void onClipPath(const SkPath& path, SkClipOp, bool aa) override {} + void onClipRegion(const SkRegion& deviceRgn, SkClipOp) override {} + void onSetDeviceClipRestriction(SkIRect* mutableClipRestriction) override {} + bool onClipIsAA() const override { return false; } + void onAsRgnClip(SkRegion* rgn) const override { + rgn->setRect(SkIRect::MakeWH(this->width(), this->height())); + } + ClipType onGetClipType() const override { + return ClipType::kRect; + } + + void drawPaint(const SkPaint& paint) override {} + void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&) override {} + void drawRect(const SkRect&, const SkPaint&) override {} + void drawOval(const SkRect&, const SkPaint&) override {} + void drawRRect(const SkRRect&, const SkPaint&) override {} + void drawPath(const SkPath&, const SkPaint&, bool) override {} + void drawSprite(const SkBitmap&, int, int, const SkPaint&) override {} + void drawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint&, + SkCanvas::SrcRectConstraint) override {} + void drawDevice(SkBaseDevice*, int, int, const SkPaint&) override {} + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override {} + void drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode, + const SkPaint&) override {} + +private: + typedef SkBaseDevice INHERITED; +}; + +class SkAutoDeviceCTMRestore : SkNoncopyable { +public: + SkAutoDeviceCTMRestore(SkBaseDevice* device, const SkMatrix& ctm) + : fDevice(device) + , fPrevCTM(device->ctm()) + { + fDevice->setCTM(ctm); + } + ~SkAutoDeviceCTMRestore() { + fDevice->setCTM(fPrevCTM); + } + +private: + SkBaseDevice* fDevice; + const SkMatrix fPrevCTM; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkDiscardableMemory.h b/gfx/skia/skia/src/core/SkDiscardableMemory.h new file mode 100644 index 0000000000..613dbd763f --- /dev/null +++ b/gfx/skia/skia/src/core/SkDiscardableMemory.h @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDiscardableMemory_DEFINED +#define SkDiscardableMemory_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" + +/** + * Interface for discardable memory. Implementation is provided by the + * embedder. + */ +class SK_API SkDiscardableMemory { +public: + /** + * Factory method that creates, initializes and locks an SkDiscardableMemory + * object. If either of these steps fails, a nullptr pointer will be returned. + */ + static SkDiscardableMemory* Create(size_t bytes); + + /** + * Factory class that creates, initializes and locks an SkDiscardableMemory + * object. If either of these steps fails, a nullptr pointer will be returned. + */ + class Factory : public SkRefCnt { + public: + virtual SkDiscardableMemory* create(size_t bytes) = 0; + private: + typedef SkRefCnt INHERITED; + }; + + /** Must not be called while locked. + */ + virtual ~SkDiscardableMemory() {} + + /** + * Locks the memory, prevent it from being discarded. Once locked. you may + * obtain a pointer to that memory using the data() method. + * + * lock() may return false, indicating that the underlying memory was + * discarded and that the lock failed. + * + * Nested calls to lock are not allowed. + */ + virtual bool SK_WARN_UNUSED_RESULT lock() = 0; + + /** + * Returns the current pointer for the discardable memory. This call is ONLY + * valid when the discardable memory object is locked. + */ + virtual void* data() = 0; + + /** + * Unlock the memory so that it can be purged by the system. Must be called + * after every successful lock call. + */ + virtual void unlock() = 0; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkDistanceFieldGen.cpp b/gfx/skia/skia/src/core/SkDistanceFieldGen.cpp new file mode 100644 index 0000000000..9471e1cd38 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDistanceFieldGen.cpp @@ -0,0 +1,562 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/core/SkMask.h" +#include "src/core/SkPointPriv.h" + +#include + +struct DFData { + float fAlpha; // alpha value of source texel + float fDistSq; // distance squared to nearest (so far) edge texel + SkPoint fDistVector; // distance vector to nearest (so far) edge texel +}; + +enum NeighborFlags { + kLeft_NeighborFlag = 0x01, + kRight_NeighborFlag = 0x02, + kTopLeft_NeighborFlag = 0x04, + kTop_NeighborFlag = 0x08, + kTopRight_NeighborFlag = 0x10, + kBottomLeft_NeighborFlag = 0x20, + kBottom_NeighborFlag = 0x40, + kBottomRight_NeighborFlag = 0x80, + kAll_NeighborFlags = 0xff, + + kNeighborFlagCount = 8 +}; + +// We treat an "edge" as a place where we cross from >=128 to <128, or vice versa, or +// where we have two non-zero pixels that are <128. +// 'neighborFlags' is used to limit the directions in which we test to avoid indexing +// outside of the image +static bool found_edge(const unsigned char* imagePtr, int width, int neighborFlags) { + // the order of these should match the neighbor flags above + const int kNum8ConnectedNeighbors = 8; + const int offsets[8] = {-1, 1, -width-1, -width, -width+1, width-1, width, width+1 }; + SkASSERT(kNum8ConnectedNeighbors == kNeighborFlagCount); + + // search for an edge + unsigned char currVal = *imagePtr; + unsigned char currCheck = (currVal >> 7); + for (int i = 0; i < kNum8ConnectedNeighbors; ++i) { + unsigned char neighborVal; + if ((1 << i) & neighborFlags) { + const unsigned char* checkPtr = imagePtr + offsets[i]; + neighborVal = *checkPtr; + } else { + neighborVal = 0; + } + unsigned char neighborCheck = (neighborVal >> 7); + SkASSERT(currCheck == 0 || currCheck == 1); + SkASSERT(neighborCheck == 0 || neighborCheck == 1); + // if sharp transition + if (currCheck != neighborCheck || + // or both <128 and >0 + (!currCheck && !neighborCheck && currVal && neighborVal)) { + return true; + } + } + + return false; +} + +static void init_glyph_data(DFData* data, unsigned char* edges, const unsigned char* image, + int dataWidth, int dataHeight, + int imageWidth, int imageHeight, + int pad) { + data += pad*dataWidth; + data += pad; + edges += (pad*dataWidth + pad); + + for (int j = 0; j < imageHeight; ++j) { + for (int i = 0; i < imageWidth; ++i) { + if (255 == *image) { + data->fAlpha = 1.0f; + } else { + data->fAlpha = (*image)*0.00392156862f; // 1/255 + } + int checkMask = kAll_NeighborFlags; + if (i == 0) { + checkMask &= ~(kLeft_NeighborFlag|kTopLeft_NeighborFlag|kBottomLeft_NeighborFlag); + } + if (i == imageWidth-1) { + checkMask &= ~(kRight_NeighborFlag|kTopRight_NeighborFlag|kBottomRight_NeighborFlag); + } + if (j == 0) { + checkMask &= ~(kTopLeft_NeighborFlag|kTop_NeighborFlag|kTopRight_NeighborFlag); + } + if (j == imageHeight-1) { + checkMask &= ~(kBottomLeft_NeighborFlag|kBottom_NeighborFlag|kBottomRight_NeighborFlag); + } + if (found_edge(image, imageWidth, checkMask)) { + *edges = 255; // using 255 makes for convenient debug rendering + } + ++data; + ++image; + ++edges; + } + data += 2*pad; + edges += 2*pad; + } +} + +// from Gustavson (2011) +// computes the distance to an edge given an edge normal vector and a pixel's alpha value +// assumes that direction has been pre-normalized +static float edge_distance(const SkPoint& direction, float alpha) { + float dx = direction.fX; + float dy = direction.fY; + float distance; + if (SkScalarNearlyZero(dx) || SkScalarNearlyZero(dy)) { + distance = 0.5f - alpha; + } else { + // this is easier if we treat the direction as being in the first octant + // (other octants are symmetrical) + dx = SkScalarAbs(dx); + dy = SkScalarAbs(dy); + if (dx < dy) { + using std::swap; + swap(dx, dy); + } + + // a1 = 0.5*dy/dx is the smaller fractional area chopped off by the edge + // to avoid the divide, we just consider the numerator + float a1num = 0.5f*dy; + + // we now compute the approximate distance, depending where the alpha falls + // relative to the edge fractional area + + // if 0 <= alpha < a1 + if (alpha*dx < a1num) { + // TODO: find a way to do this without square roots? + distance = 0.5f*(dx + dy) - SkScalarSqrt(2.0f*dx*dy*alpha); + // if a1 <= alpha <= 1 - a1 + } else if (alpha*dx < (dx - a1num)) { + distance = (0.5f - alpha)*dx; + // if 1 - a1 < alpha <= 1 + } else { + // TODO: find a way to do this without square roots? + distance = -0.5f*(dx + dy) + SkScalarSqrt(2.0f*dx*dy*(1.0f - alpha)); + } + } + + return distance; +} + +static void init_distances(DFData* data, unsigned char* edges, int width, int height) { + // skip one pixel border + DFData* currData = data; + DFData* prevData = data - width; + DFData* nextData = data + width; + + for (int j = 0; j < height; ++j) { + for (int i = 0; i < width; ++i) { + if (*edges) { + // we should not be in the one-pixel outside band + SkASSERT(i > 0 && i < width-1 && j > 0 && j < height-1); + // gradient will point from low to high + // +y is down in this case + // i.e., if you're outside, gradient points towards edge + // if you're inside, gradient points away from edge + SkPoint currGrad; + currGrad.fX = (prevData+1)->fAlpha - (prevData-1)->fAlpha + + SK_ScalarSqrt2*(currData+1)->fAlpha + - SK_ScalarSqrt2*(currData-1)->fAlpha + + (nextData+1)->fAlpha - (nextData-1)->fAlpha; + currGrad.fY = (nextData-1)->fAlpha - (prevData-1)->fAlpha + + SK_ScalarSqrt2*nextData->fAlpha + - SK_ScalarSqrt2*prevData->fAlpha + + (nextData+1)->fAlpha - (prevData+1)->fAlpha; + SkPointPriv::SetLengthFast(&currGrad, 1.0f); + + // init squared distance to edge and distance vector + float dist = edge_distance(currGrad, currData->fAlpha); + currGrad.scale(dist, &currData->fDistVector); + currData->fDistSq = dist*dist; + } else { + // init distance to "far away" + currData->fDistSq = 2000000.f; + currData->fDistVector.fX = 1000.f; + currData->fDistVector.fY = 1000.f; + } + ++currData; + ++prevData; + ++nextData; + ++edges; + } + } +} + +// Danielsson's 8SSEDT + +// first stage forward pass +// (forward in Y, forward in X) +static void F1(DFData* curr, int width) { + // upper left + DFData* check = curr - width-1; + SkPoint distVec = check->fDistVector; + float distSq = check->fDistSq - 2.0f*(distVec.fX + distVec.fY - 1.0f); + if (distSq < curr->fDistSq) { + distVec.fX -= 1.0f; + distVec.fY -= 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } + + // up + check = curr - width; + distVec = check->fDistVector; + distSq = check->fDistSq - 2.0f*distVec.fY + 1.0f; + if (distSq < curr->fDistSq) { + distVec.fY -= 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } + + // upper right + check = curr - width+1; + distVec = check->fDistVector; + distSq = check->fDistSq + 2.0f*(distVec.fX - distVec.fY + 1.0f); + if (distSq < curr->fDistSq) { + distVec.fX += 1.0f; + distVec.fY -= 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } + + // left + check = curr - 1; + distVec = check->fDistVector; + distSq = check->fDistSq - 2.0f*distVec.fX + 1.0f; + if (distSq < curr->fDistSq) { + distVec.fX -= 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } +} + +// second stage forward pass +// (forward in Y, backward in X) +static void F2(DFData* curr, int width) { + // right + DFData* check = curr + 1; + SkPoint distVec = check->fDistVector; + float distSq = check->fDistSq + 2.0f*distVec.fX + 1.0f; + if (distSq < curr->fDistSq) { + distVec.fX += 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } +} + +// first stage backward pass +// (backward in Y, forward in X) +static void B1(DFData* curr, int width) { + // left + DFData* check = curr - 1; + SkPoint distVec = check->fDistVector; + float distSq = check->fDistSq - 2.0f*distVec.fX + 1.0f; + if (distSq < curr->fDistSq) { + distVec.fX -= 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } +} + +// second stage backward pass +// (backward in Y, backwards in X) +static void B2(DFData* curr, int width) { + // right + DFData* check = curr + 1; + SkPoint distVec = check->fDistVector; + float distSq = check->fDistSq + 2.0f*distVec.fX + 1.0f; + if (distSq < curr->fDistSq) { + distVec.fX += 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } + + // bottom left + check = curr + width-1; + distVec = check->fDistVector; + distSq = check->fDistSq - 2.0f*(distVec.fX - distVec.fY - 1.0f); + if (distSq < curr->fDistSq) { + distVec.fX -= 1.0f; + distVec.fY += 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } + + // bottom + check = curr + width; + distVec = check->fDistVector; + distSq = check->fDistSq + 2.0f*distVec.fY + 1.0f; + if (distSq < curr->fDistSq) { + distVec.fY += 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } + + // bottom right + check = curr + width+1; + distVec = check->fDistVector; + distSq = check->fDistSq + 2.0f*(distVec.fX + distVec.fY + 1.0f); + if (distSq < curr->fDistSq) { + distVec.fX += 1.0f; + distVec.fY += 1.0f; + curr->fDistSq = distSq; + curr->fDistVector = distVec; + } +} + +// enable this to output edge data rather than the distance field +#define DUMP_EDGE 0 + +#if !DUMP_EDGE +template +static unsigned char pack_distance_field_val(float dist) { + // The distance field is constructed as unsigned char values, so that the zero value is at 128, + // Beside 128, we have 128 values in range [0, 128), but only 127 values in range (128, 255]. + // So we multiply distanceMagnitude by 127/128 at the latter range to avoid overflow. + dist = SkScalarPin(-dist, -distanceMagnitude, distanceMagnitude * 127.0f / 128.0f); + + // Scale into the positive range for unsigned distance. + dist += distanceMagnitude; + + // Scale into unsigned char range. + // Round to place negative and positive values as equally as possible around 128 + // (which represents zero). + return (unsigned char)SkScalarRoundToInt(dist / (2 * distanceMagnitude) * 256.0f); +} +#endif + +// assumes a padded 8-bit image and distance field +// width and height are the original width and height of the image +static bool generate_distance_field_from_image(unsigned char* distanceField, + const unsigned char* copyPtr, + int width, int height) { + SkASSERT(distanceField); + SkASSERT(copyPtr); + + // we expand our temp data by one more on each side to simplify + // the scanning code -- will always be treated as infinitely far away + int pad = SK_DistanceFieldPad + 1; + + // set params for distance field data + int dataWidth = width + 2*pad; + int dataHeight = height + 2*pad; + + // create zeroed temp DFData+edge storage + SkAutoFree storage(sk_calloc_throw(dataWidth*dataHeight*(sizeof(DFData) + 1))); + DFData* dataPtr = (DFData*)storage.get(); + unsigned char* edgePtr = (unsigned char*)storage.get() + dataWidth*dataHeight*sizeof(DFData); + + // copy glyph into distance field storage + init_glyph_data(dataPtr, edgePtr, copyPtr, + dataWidth, dataHeight, + width+2, height+2, SK_DistanceFieldPad); + + // create initial distance data, particularly at edges + init_distances(dataPtr, edgePtr, dataWidth, dataHeight); + + // now perform Euclidean distance transform to propagate distances + + // forwards in y + DFData* currData = dataPtr+dataWidth+1; // skip outer buffer + unsigned char* currEdge = edgePtr+dataWidth+1; + for (int j = 1; j < dataHeight-1; ++j) { + // forwards in x + for (int i = 1; i < dataWidth-1; ++i) { + // don't need to calculate distance for edge pixels + if (!*currEdge) { + F1(currData, dataWidth); + } + ++currData; + ++currEdge; + } + + // backwards in x + --currData; // reset to end + --currEdge; + for (int i = 1; i < dataWidth-1; ++i) { + // don't need to calculate distance for edge pixels + if (!*currEdge) { + F2(currData, dataWidth); + } + --currData; + --currEdge; + } + + currData += dataWidth+1; + currEdge += dataWidth+1; + } + + // backwards in y + currData = dataPtr+dataWidth*(dataHeight-2) - 1; // skip outer buffer + currEdge = edgePtr+dataWidth*(dataHeight-2) - 1; + for (int j = 1; j < dataHeight-1; ++j) { + // forwards in x + for (int i = 1; i < dataWidth-1; ++i) { + // don't need to calculate distance for edge pixels + if (!*currEdge) { + B1(currData, dataWidth); + } + ++currData; + ++currEdge; + } + + // backwards in x + --currData; // reset to end + --currEdge; + for (int i = 1; i < dataWidth-1; ++i) { + // don't need to calculate distance for edge pixels + if (!*currEdge) { + B2(currData, dataWidth); + } + --currData; + --currEdge; + } + + currData -= dataWidth-1; + currEdge -= dataWidth-1; + } + + // copy results to final distance field data + currData = dataPtr + dataWidth+1; + currEdge = edgePtr + dataWidth+1; + unsigned char *dfPtr = distanceField; + for (int j = 1; j < dataHeight-1; ++j) { + for (int i = 1; i < dataWidth-1; ++i) { +#if DUMP_EDGE + float alpha = currData->fAlpha; + float edge = 0.0f; + if (*currEdge) { + edge = 0.25f; + } + // blend with original image + float result = alpha + (1.0f-alpha)*edge; + unsigned char val = sk_float_round2int(255*result); + *dfPtr++ = val; +#else + float dist; + if (currData->fAlpha > 0.5f) { + dist = -SkScalarSqrt(currData->fDistSq); + } else { + dist = SkScalarSqrt(currData->fDistSq); + } + *dfPtr++ = pack_distance_field_val(dist); +#endif + ++currData; + ++currEdge; + } + currData += 2; + currEdge += 2; + } + + return true; +} + +// assumes an 8-bit image and distance field +bool SkGenerateDistanceFieldFromA8Image(unsigned char* distanceField, + const unsigned char* image, + int width, int height, size_t rowBytes) { + SkASSERT(distanceField); + SkASSERT(image); + + // create temp data + SkAutoSMalloc<1024> copyStorage((width+2)*(height+2)*sizeof(char)); + unsigned char* copyPtr = (unsigned char*) copyStorage.get(); + + // we copy our source image into a padded copy to ensure we catch edge transitions + // around the outside + const unsigned char* currSrcScanLine = image; + sk_bzero(copyPtr, (width+2)*sizeof(char)); + unsigned char* currDestPtr = copyPtr + width + 2; + for (int i = 0; i < height; ++i) { + *currDestPtr++ = 0; + memcpy(currDestPtr, currSrcScanLine, width); + currSrcScanLine += rowBytes; + currDestPtr += width; + *currDestPtr++ = 0; + } + sk_bzero(currDestPtr, (width+2)*sizeof(char)); + + return generate_distance_field_from_image(distanceField, copyPtr, width, height); +} + +// assumes a 16-bit lcd mask and 8-bit distance field +bool SkGenerateDistanceFieldFromLCD16Mask(unsigned char* distanceField, + const unsigned char* image, + int w, int h, size_t rowBytes) { + SkASSERT(distanceField); + SkASSERT(image); + + // create temp data + SkAutoSMalloc<1024> copyStorage((w+2)*(h+2)*sizeof(char)); + unsigned char* copyPtr = (unsigned char*) copyStorage.get(); + + // we copy our source image into a padded copy to ensure we catch edge transitions + // around the outside + const uint16_t* start = reinterpret_cast(image); + auto currSrcScanline = SkMask::AlphaIter(start); + auto endSrcScanline = SkMask::AlphaIter(start + w); + sk_bzero(copyPtr, (w+2)*sizeof(char)); + unsigned char* currDestPtr = copyPtr + w + 2; + for (int i = 0; i < h; ++i, currSrcScanline >>= rowBytes, endSrcScanline >>= rowBytes) { + *currDestPtr++ = 0; + for (auto src = currSrcScanline; src < endSrcScanline; ++src) { + *currDestPtr++ = *src; + } + *currDestPtr++ = 0; + } + sk_bzero(currDestPtr, (w+2)*sizeof(char)); + + return generate_distance_field_from_image(distanceField, copyPtr, w, h); +} + +// assumes a 1-bit image and 8-bit distance field +bool SkGenerateDistanceFieldFromBWImage(unsigned char* distanceField, + const unsigned char* image, + int width, int height, size_t rowBytes) { + SkASSERT(distanceField); + SkASSERT(image); + + // create temp data + SkAutoSMalloc<1024> copyStorage((width+2)*(height+2)*sizeof(char)); + unsigned char* copyPtr = (unsigned char*) copyStorage.get(); + + // we copy our source image into a padded copy to ensure we catch edge transitions + // around the outside + const unsigned char* currSrcScanLine = image; + sk_bzero(copyPtr, (width+2)*sizeof(char)); + unsigned char* currDestPtr = copyPtr + width + 2; + for (int i = 0; i < height; ++i) { + *currDestPtr++ = 0; + + + int rowWritesLeft = width; + const unsigned char *maskPtr = currSrcScanLine; + while (rowWritesLeft > 0) { + unsigned mask = *maskPtr++; + for (int i = 7; i >= 0 && rowWritesLeft; --i, --rowWritesLeft) { + *currDestPtr++ = (mask & (1 << i)) ? 0xff : 0; + } + } + currSrcScanLine += rowBytes; + + + *currDestPtr++ = 0; + } + sk_bzero(currDestPtr, (width+2)*sizeof(char)); + + return generate_distance_field_from_image(distanceField, copyPtr, width, height); +} diff --git a/gfx/skia/skia/src/core/SkDistanceFieldGen.h b/gfx/skia/skia/src/core/SkDistanceFieldGen.h new file mode 100644 index 0000000000..64458db8de --- /dev/null +++ b/gfx/skia/skia/src/core/SkDistanceFieldGen.h @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkDistanceFieldGen_DEFINED +#define SkDistanceFieldGen_DEFINED + +#include "include/core/SkTypes.h" + +// the max magnitude for the distance field +// distance values are limited to the range (-SK_DistanceFieldMagnitude, SK_DistanceFieldMagnitude] +#define SK_DistanceFieldMagnitude 4 +// we need to pad around the original glyph to allow our maximum distance of +// SK_DistanceFieldMagnitude texels away from any edge +#define SK_DistanceFieldPad 4 +// the rect we render with is inset from the distance field glyph size to allow for bilerp +#define SK_DistanceFieldInset 2 + +// For the fragment shader: +// The distance field is constructed as unsigned char values, +// so that the zero value is at 128, and the supported range of distances is [-4 * 127/128, 4]. +// Hence our multiplier (width of the range) is 4 * 255/128 and zero threshold is 128/255. +#define SK_DistanceFieldMultiplier "7.96875" +#define SK_DistanceFieldThreshold "0.50196078431" + +/** Given 8-bit mask data, generate the associated distance field + + * @param distanceField The distance field to be generated. Should already be allocated + * by the client with the padding above. + * @param image 8-bit mask we're using to generate the distance field. + * @param w Width of the original image. + * @param h Height of the original image. + * @param rowBytes Size of each row in the image, in bytes + */ +bool SkGenerateDistanceFieldFromA8Image(unsigned char* distanceField, + const unsigned char* image, + int w, int h, size_t rowBytes); + +/** Given LCD16 mask data (not a 16-bit image), generate the associated distance field + + * @param distanceField The distance field to be generated. Should already be allocated + * by the client with the padding above. + * @param image 16-bit LCD data we're using to generate the distance field. + * @param w Width of the original image. + * @param h Height of the original image. + * @param rowBytes Size of each row in the image, in bytes + */ +bool SkGenerateDistanceFieldFromLCD16Mask(unsigned char* distanceField, + const unsigned char* image, + int w, int h, size_t rowBytes); + +/** Given 1-bit mask data, generate the associated distance field + + * @param distanceField The distance field to be generated. Should already be allocated + * by the client with the padding above. + * @param image 1-bit mask we're using to generate the distance field. + * @param w Width of the original image. + * @param h Height of the original image. + * @param rowBytes Size of each row in the image, in bytes + */ +bool SkGenerateDistanceFieldFromBWImage(unsigned char* distanceField, + const unsigned char* image, + int w, int h, size_t rowBytes); + +/** Given width and height of original image, return size (in bytes) of distance field + * @param w Width of the original image. + * @param h Height of the original image. + */ +inline size_t SkComputeDistanceFieldSize(int w, int h) { + return (w + 2*SK_DistanceFieldPad) * (h + 2*SK_DistanceFieldPad) * sizeof(unsigned char); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkDocument.cpp b/gfx/skia/skia/src/core/SkDocument.cpp new file mode 100644 index 0000000000..30c94d8317 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDocument.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkDocument.h" +#include "include/core/SkStream.h" + +SkDocument::SkDocument(SkWStream* stream) : fStream(stream), fState(kBetweenPages_State) {} + +SkDocument::~SkDocument() { + this->close(); +} + +static SkCanvas* trim(SkCanvas* canvas, SkScalar width, SkScalar height, + const SkRect* content) { + if (content && canvas) { + SkRect inner = *content; + if (!inner.intersect({0, 0, width, height})) { + return nullptr; + } + canvas->clipRect(inner); + canvas->translate(inner.x(), inner.y()); + } + return canvas; +} + +SkCanvas* SkDocument::beginPage(SkScalar width, SkScalar height, + const SkRect* content) { + if (width <= 0 || height <= 0 || kClosed_State == fState) { + return nullptr; + } + if (kInPage_State == fState) { + this->endPage(); + } + SkASSERT(kBetweenPages_State == fState); + fState = kInPage_State; + return trim(this->onBeginPage(width, height), width, height, content); +} + +void SkDocument::endPage() { + if (kInPage_State == fState) { + fState = kBetweenPages_State; + this->onEndPage(); + } +} + +void SkDocument::close() { + for (;;) { + switch (fState) { + case kBetweenPages_State: { + fState = kClosed_State; + this->onClose(fStream); + // we don't own the stream, but we mark it nullptr since we can + // no longer write to it. + fStream = nullptr; + return; + } + case kInPage_State: + this->endPage(); + break; + case kClosed_State: + return; + } + } +} + +void SkDocument::abort() { + this->onAbort(); + + fState = kClosed_State; + // we don't own the stream, but we mark it nullptr since we can + // no longer write to it. + fStream = nullptr; +} diff --git a/gfx/skia/skia/src/core/SkDraw.cpp b/gfx/skia/skia/src/core/SkDraw.cpp new file mode 100644 index 0000000000..5ef63948a1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDraw.cpp @@ -0,0 +1,1307 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDraw.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkRRect.h" +#include "include/core/SkShader.h" +#include "include/core/SkString.h" +#include "include/core/SkStrokeRec.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkAutoBlitterChoose.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkDevice.h" +#include "src/core/SkDrawProcs.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkMatrixUtils.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkScan.h" +#include "src/core/SkStroke.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkUtils.h" + +#include + +static SkPaint make_paint_with_image( + const SkPaint& origPaint, const SkBitmap& bitmap, SkMatrix* matrix = nullptr) { + SkPaint paint(origPaint); + paint.setShader(SkMakeBitmapShaderForPaint(origPaint, bitmap, SkTileMode::kClamp, + SkTileMode::kClamp, matrix, + kNever_SkCopyPixelsMode)); + return paint; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkDraw::SkDraw() {} + +bool SkDraw::computeConservativeLocalClipBounds(SkRect* localBounds) const { + if (fRC->isEmpty()) { + return false; + } + + SkMatrix inverse; + if (!fMatrix->invert(&inverse)) { + return false; + } + + SkIRect devBounds = fRC->getBounds(); + // outset to have slop for antialasing and hairlines + devBounds.outset(1, 1); + inverse.mapRect(localBounds, SkRect::Make(devBounds)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkDraw::drawPaint(const SkPaint& paint) const { + SkDEBUGCODE(this->validate();) + + if (fRC->isEmpty()) { + return; + } + + SkIRect devRect; + devRect.setWH(fDst.width(), fDst.height()); + + SkAutoBlitterChoose blitter(*this, nullptr, paint); + SkScan::FillIRect(devRect, *fRC, blitter.get()); +} + +/////////////////////////////////////////////////////////////////////////////// + +struct PtProcRec { + SkCanvas::PointMode fMode; + const SkPaint* fPaint; + const SkRegion* fClip; + const SkRasterClip* fRC; + + // computed values + SkRect fClipBounds; + SkScalar fRadius; + + typedef void (*Proc)(const PtProcRec&, const SkPoint devPts[], int count, + SkBlitter*); + + bool init(SkCanvas::PointMode, const SkPaint&, const SkMatrix* matrix, + const SkRasterClip*); + Proc chooseProc(SkBlitter** blitter); + +private: + SkAAClipBlitterWrapper fWrapper; +}; + +static void bw_pt_rect_hair_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + SkASSERT(rec.fClip->isRect()); + const SkIRect& r = rec.fClip->getBounds(); + + for (int i = 0; i < count; i++) { + int x = SkScalarFloorToInt(devPts[i].fX); + int y = SkScalarFloorToInt(devPts[i].fY); + if (r.contains(x, y)) { + blitter->blitH(x, y, 1); + } + } +} + +static void bw_pt_rect_16_hair_proc(const PtProcRec& rec, + const SkPoint devPts[], int count, + SkBlitter* blitter) { + SkASSERT(rec.fRC->isRect()); + const SkIRect& r = rec.fRC->getBounds(); + uint32_t value; + const SkPixmap* dst = blitter->justAnOpaqueColor(&value); + SkASSERT(dst); + + uint16_t* addr = dst->writable_addr16(0, 0); + size_t rb = dst->rowBytes(); + + for (int i = 0; i < count; i++) { + int x = SkScalarFloorToInt(devPts[i].fX); + int y = SkScalarFloorToInt(devPts[i].fY); + if (r.contains(x, y)) { + ((uint16_t*)((char*)addr + y * rb))[x] = SkToU16(value); + } + } +} + +static void bw_pt_rect_32_hair_proc(const PtProcRec& rec, + const SkPoint devPts[], int count, + SkBlitter* blitter) { + SkASSERT(rec.fRC->isRect()); + const SkIRect& r = rec.fRC->getBounds(); + uint32_t value; + const SkPixmap* dst = blitter->justAnOpaqueColor(&value); + SkASSERT(dst); + + SkPMColor* addr = dst->writable_addr32(0, 0); + size_t rb = dst->rowBytes(); + + for (int i = 0; i < count; i++) { + int x = SkScalarFloorToInt(devPts[i].fX); + int y = SkScalarFloorToInt(devPts[i].fY); + if (r.contains(x, y)) { + ((SkPMColor*)((char*)addr + y * rb))[x] = value; + } + } +} + +static void bw_pt_hair_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + for (int i = 0; i < count; i++) { + int x = SkScalarFloorToInt(devPts[i].fX); + int y = SkScalarFloorToInt(devPts[i].fY); + if (rec.fClip->contains(x, y)) { + blitter->blitH(x, y, 1); + } + } +} + +static void bw_line_hair_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + for (int i = 0; i < count; i += 2) { + SkScan::HairLine(&devPts[i], 2, *rec.fRC, blitter); + } +} + +static void bw_poly_hair_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + SkScan::HairLine(devPts, count, *rec.fRC, blitter); +} + +// aa versions + +static void aa_line_hair_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + for (int i = 0; i < count; i += 2) { + SkScan::AntiHairLine(&devPts[i], 2, *rec.fRC, blitter); + } +} + +static void aa_poly_hair_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + SkScan::AntiHairLine(devPts, count, *rec.fRC, blitter); +} + +// square procs (strokeWidth > 0 but matrix is square-scale (sx == sy) + +static SkRect make_square_rad(SkPoint center, SkScalar radius) { + return { + center.fX - radius, center.fY - radius, + center.fX + radius, center.fY + radius + }; +} + +static SkXRect make_xrect(const SkRect& r) { + SkASSERT(SkRectPriv::FitsInFixed(r)); + return { + SkScalarToFixed(r.fLeft), SkScalarToFixed(r.fTop), + SkScalarToFixed(r.fRight), SkScalarToFixed(r.fBottom) + }; +} + +static void bw_square_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + for (int i = 0; i < count; i++) { + SkRect r = make_square_rad(devPts[i], rec.fRadius); + if (r.intersect(rec.fClipBounds)) { + SkScan::FillXRect(make_xrect(r), *rec.fRC, blitter); + } + } +} + +static void aa_square_proc(const PtProcRec& rec, const SkPoint devPts[], + int count, SkBlitter* blitter) { + for (int i = 0; i < count; i++) { + SkRect r = make_square_rad(devPts[i], rec.fRadius); + if (r.intersect(rec.fClipBounds)) { + SkScan::AntiFillXRect(make_xrect(r), *rec.fRC, blitter); + } + } +} + +// If this guy returns true, then chooseProc() must return a valid proc +bool PtProcRec::init(SkCanvas::PointMode mode, const SkPaint& paint, + const SkMatrix* matrix, const SkRasterClip* rc) { + if ((unsigned)mode > (unsigned)SkCanvas::kPolygon_PointMode) { + return false; + } + if (paint.getPathEffect()) { + return false; + } + SkScalar width = paint.getStrokeWidth(); + SkScalar radius = -1; // sentinel value, a "valid" value must be > 0 + + if (0 == width) { + radius = 0.5f; + } else if (paint.getStrokeCap() != SkPaint::kRound_Cap && + matrix->isScaleTranslate() && SkCanvas::kPoints_PointMode == mode) { + SkScalar sx = matrix->get(SkMatrix::kMScaleX); + SkScalar sy = matrix->get(SkMatrix::kMScaleY); + if (SkScalarNearlyZero(sx - sy)) { + radius = SkScalarHalf(width * SkScalarAbs(sx)); + } + } + if (radius > 0) { + SkRect clipBounds = SkRect::Make(rc->getBounds()); + // if we return true, the caller may assume that the constructed shapes can be represented + // using SkFixed (after clipping), so we preflight that here. + if (!SkRectPriv::FitsInFixed(clipBounds)) { + return false; + } + fMode = mode; + fPaint = &paint; + fClip = nullptr; + fRC = rc; + fClipBounds = clipBounds; + fRadius = radius; + return true; + } + return false; +} + +PtProcRec::Proc PtProcRec::chooseProc(SkBlitter** blitterPtr) { + Proc proc = nullptr; + + SkBlitter* blitter = *blitterPtr; + if (fRC->isBW()) { + fClip = &fRC->bwRgn(); + } else { + fWrapper.init(*fRC, blitter); + fClip = &fWrapper.getRgn(); + blitter = fWrapper.getBlitter(); + *blitterPtr = blitter; + } + + // for our arrays + SkASSERT(0 == SkCanvas::kPoints_PointMode); + SkASSERT(1 == SkCanvas::kLines_PointMode); + SkASSERT(2 == SkCanvas::kPolygon_PointMode); + SkASSERT((unsigned)fMode <= (unsigned)SkCanvas::kPolygon_PointMode); + + if (fPaint->isAntiAlias()) { + if (0 == fPaint->getStrokeWidth()) { + static const Proc gAAProcs[] = { + aa_square_proc, aa_line_hair_proc, aa_poly_hair_proc + }; + proc = gAAProcs[fMode]; + } else if (fPaint->getStrokeCap() != SkPaint::kRound_Cap) { + SkASSERT(SkCanvas::kPoints_PointMode == fMode); + proc = aa_square_proc; + } + } else { // BW + if (fRadius <= 0.5f) { // small radii and hairline + if (SkCanvas::kPoints_PointMode == fMode && fClip->isRect()) { + uint32_t value; + const SkPixmap* bm = blitter->justAnOpaqueColor(&value); + if (bm && kRGB_565_SkColorType == bm->colorType()) { + proc = bw_pt_rect_16_hair_proc; + } else if (bm && kN32_SkColorType == bm->colorType()) { + proc = bw_pt_rect_32_hair_proc; + } else { + proc = bw_pt_rect_hair_proc; + } + } else { + static Proc gBWProcs[] = { + bw_pt_hair_proc, bw_line_hair_proc, bw_poly_hair_proc + }; + proc = gBWProcs[fMode]; + } + } else { + proc = bw_square_proc; + } + } + return proc; +} + +// each of these costs 8-bytes of stack space, so don't make it too large +// must be even for lines/polygon to work +#define MAX_DEV_PTS 32 + +void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count, + const SkPoint pts[], const SkPaint& paint, + SkBaseDevice* device) const { + // if we're in lines mode, force count to be even + if (SkCanvas::kLines_PointMode == mode) { + count &= ~(size_t)1; + } + + if ((long)count <= 0) { + return; + } + + SkASSERT(pts != nullptr); + SkDEBUGCODE(this->validate();) + + // nothing to draw + if (fRC->isEmpty()) { + return; + } + + PtProcRec rec; + if (!device && rec.init(mode, paint, fMatrix, fRC)) { + SkAutoBlitterChoose blitter(*this, nullptr, paint); + + SkPoint devPts[MAX_DEV_PTS]; + const SkMatrix* matrix = fMatrix; + SkBlitter* bltr = blitter.get(); + PtProcRec::Proc proc = rec.chooseProc(&bltr); + // we have to back up subsequent passes if we're in polygon mode + const size_t backup = (SkCanvas::kPolygon_PointMode == mode); + + do { + int n = SkToInt(count); + if (n > MAX_DEV_PTS) { + n = MAX_DEV_PTS; + } + matrix->mapPoints(devPts, pts, n); + if (!SkScalarsAreFinite(&devPts[0].fX, n * 2)) { + return; + } + proc(rec, devPts, n, bltr); + pts += n - backup; + SkASSERT(SkToInt(count) >= n); + count -= n; + if (count > 0) { + count += backup; + } + } while (count != 0); + } else { + switch (mode) { + case SkCanvas::kPoints_PointMode: { + // temporarily mark the paint as filling. + SkPaint newPaint(paint); + newPaint.setStyle(SkPaint::kFill_Style); + + SkScalar width = newPaint.getStrokeWidth(); + SkScalar radius = SkScalarHalf(width); + + if (newPaint.getStrokeCap() == SkPaint::kRound_Cap) { + if (device) { + for (size_t i = 0; i < count; ++i) { + SkRect r = SkRect::MakeLTRB(pts[i].fX - radius, pts[i].fY - radius, + pts[i].fX + radius, pts[i].fY + radius); + device->drawOval(r, newPaint); + } + } else { + SkPath path; + SkMatrix preMatrix; + + path.addCircle(0, 0, radius); + for (size_t i = 0; i < count; i++) { + preMatrix.setTranslate(pts[i].fX, pts[i].fY); + // pass true for the last point, since we can modify + // then path then + path.setIsVolatile((count-1) == i); + this->drawPath(path, newPaint, &preMatrix, (count-1) == i); + } + } + } else { + SkRect r; + + for (size_t i = 0; i < count; i++) { + r.fLeft = pts[i].fX - radius; + r.fTop = pts[i].fY - radius; + r.fRight = r.fLeft + width; + r.fBottom = r.fTop + width; + if (device) { + device->drawRect(r, newPaint); + } else { + this->drawRect(r, newPaint); + } + } + } + break; + } + case SkCanvas::kLines_PointMode: + if (2 == count && paint.getPathEffect()) { + // most likely a dashed line - see if it is one of the ones + // we can accelerate + SkStrokeRec rec(paint); + SkPathEffect::PointData pointData; + + SkPath path; + path.moveTo(pts[0]); + path.lineTo(pts[1]); + + SkRect cullRect = SkRect::Make(fRC->getBounds()); + + if (paint.getPathEffect()->asPoints(&pointData, path, rec, + *fMatrix, &cullRect)) { + // 'asPoints' managed to find some fast path + + SkPaint newP(paint); + newP.setPathEffect(nullptr); + newP.setStyle(SkPaint::kFill_Style); + + if (!pointData.fFirst.isEmpty()) { + if (device) { + device->drawPath(pointData.fFirst, newP); + } else { + this->drawPath(pointData.fFirst, newP); + } + } + + if (!pointData.fLast.isEmpty()) { + if (device) { + device->drawPath(pointData.fLast, newP); + } else { + this->drawPath(pointData.fLast, newP); + } + } + + if (pointData.fSize.fX == pointData.fSize.fY) { + // The rest of the dashed line can just be drawn as points + SkASSERT(pointData.fSize.fX == SkScalarHalf(newP.getStrokeWidth())); + + if (SkPathEffect::PointData::kCircles_PointFlag & pointData.fFlags) { + newP.setStrokeCap(SkPaint::kRound_Cap); + } else { + newP.setStrokeCap(SkPaint::kButt_Cap); + } + + if (device) { + device->drawPoints(SkCanvas::kPoints_PointMode, + pointData.fNumPoints, + pointData.fPoints, + newP); + } else { + this->drawPoints(SkCanvas::kPoints_PointMode, + pointData.fNumPoints, + pointData.fPoints, + newP, + device); + } + break; + } else { + // The rest of the dashed line must be drawn as rects + SkASSERT(!(SkPathEffect::PointData::kCircles_PointFlag & + pointData.fFlags)); + + SkRect r; + + for (int i = 0; i < pointData.fNumPoints; ++i) { + r.setLTRB(pointData.fPoints[i].fX - pointData.fSize.fX, + pointData.fPoints[i].fY - pointData.fSize.fY, + pointData.fPoints[i].fX + pointData.fSize.fX, + pointData.fPoints[i].fY + pointData.fSize.fY); + if (device) { + device->drawRect(r, newP); + } else { + this->drawRect(r, newP); + } + } + } + + break; + } + } + // couldn't take fast path so fall through! + case SkCanvas::kPolygon_PointMode: { + count -= 1; + SkPath path; + SkPaint p(paint); + p.setStyle(SkPaint::kStroke_Style); + size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1; + path.setIsVolatile(true); + for (size_t i = 0; i < count; i += inc) { + path.moveTo(pts[i]); + path.lineTo(pts[i+1]); + if (device) { + device->drawPath(path, p, true); + } else { + this->drawPath(path, p, nullptr, true); + } + path.rewind(); + } + break; + } + } + } +} + +static inline SkPoint compute_stroke_size(const SkPaint& paint, const SkMatrix& matrix) { + SkASSERT(matrix.rectStaysRect()); + SkASSERT(SkPaint::kFill_Style != paint.getStyle()); + + SkVector size; + SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() }; + matrix.mapVectors(&size, &pt, 1); + return SkPoint::Make(SkScalarAbs(size.fX), SkScalarAbs(size.fY)); +} + +static bool easy_rect_join(const SkPaint& paint, const SkMatrix& matrix, + SkPoint* strokeSize) { + if (SkPaint::kMiter_Join != paint.getStrokeJoin() || + paint.getStrokeMiter() < SK_ScalarSqrt2) { + return false; + } + + *strokeSize = compute_stroke_size(paint, matrix); + return true; +} + +SkDraw::RectType SkDraw::ComputeRectType(const SkPaint& paint, + const SkMatrix& matrix, + SkPoint* strokeSize) { + RectType rtype; + const SkScalar width = paint.getStrokeWidth(); + const bool zeroWidth = (0 == width); + SkPaint::Style style = paint.getStyle(); + + if ((SkPaint::kStrokeAndFill_Style == style) && zeroWidth) { + style = SkPaint::kFill_Style; + } + + if (paint.getPathEffect() || paint.getMaskFilter() || + !matrix.rectStaysRect() || SkPaint::kStrokeAndFill_Style == style) { + rtype = kPath_RectType; + } else if (SkPaint::kFill_Style == style) { + rtype = kFill_RectType; + } else if (zeroWidth) { + rtype = kHair_RectType; + } else if (easy_rect_join(paint, matrix, strokeSize)) { + rtype = kStroke_RectType; + } else { + rtype = kPath_RectType; + } + return rtype; +} + +static const SkPoint* rect_points(const SkRect& r) { + return reinterpret_cast(&r); +} + +static SkPoint* rect_points(SkRect& r) { + return reinterpret_cast(&r); +} + +static void draw_rect_as_path(const SkDraw& orig, const SkRect& prePaintRect, + const SkPaint& paint, const SkMatrix* matrix) { + SkDraw draw(orig); + draw.fMatrix = matrix; + SkPath tmp; + tmp.addRect(prePaintRect); + tmp.setFillType(SkPath::kWinding_FillType); + draw.drawPath(tmp, paint, nullptr, true); +} + +void SkDraw::drawRect(const SkRect& prePaintRect, const SkPaint& paint, + const SkMatrix* paintMatrix, const SkRect* postPaintRect) const { + SkDEBUGCODE(this->validate();) + + // nothing to draw + if (fRC->isEmpty()) { + return; + } + + const SkMatrix* matrix; + SkMatrix combinedMatrixStorage; + if (paintMatrix) { + SkASSERT(postPaintRect); + combinedMatrixStorage.setConcat(*fMatrix, *paintMatrix); + matrix = &combinedMatrixStorage; + } else { + SkASSERT(!postPaintRect); + matrix = fMatrix; + } + + SkPoint strokeSize; + RectType rtype = ComputeRectType(paint, *fMatrix, &strokeSize); + + if (kPath_RectType == rtype) { + draw_rect_as_path(*this, prePaintRect, paint, matrix); + return; + } + + SkRect devRect; + const SkRect& paintRect = paintMatrix ? *postPaintRect : prePaintRect; + // skip the paintMatrix when transforming the rect by the CTM + fMatrix->mapPoints(rect_points(devRect), rect_points(paintRect), 2); + devRect.sort(); + + // look for the quick exit, before we build a blitter + SkRect bbox = devRect; + if (paint.getStyle() != SkPaint::kFill_Style) { + // extra space for hairlines + if (paint.getStrokeWidth() == 0) { + bbox.outset(1, 1); + } else { + // For kStroke_RectType, strokeSize is already computed. + const SkPoint& ssize = (kStroke_RectType == rtype) + ? strokeSize + : compute_stroke_size(paint, *fMatrix); + bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y())); + } + } + if (SkPathPriv::TooBigForMath(bbox)) { + return; + } + + if (!SkRectPriv::FitsInFixed(bbox) && rtype != kHair_RectType) { + draw_rect_as_path(*this, prePaintRect, paint, matrix); + return; + } + + SkIRect ir = bbox.roundOut(); + if (fRC->quickReject(ir)) { + return; + } + + SkAutoBlitterChoose blitterStorage(*this, matrix, paint); + const SkRasterClip& clip = *fRC; + SkBlitter* blitter = blitterStorage.get(); + + // we want to "fill" if we are kFill or kStrokeAndFill, since in the latter + // case we are also hairline (if we've gotten to here), which devolves to + // effectively just kFill + switch (rtype) { + case kFill_RectType: + if (paint.isAntiAlias()) { + SkScan::AntiFillRect(devRect, clip, blitter); + } else { + SkScan::FillRect(devRect, clip, blitter); + } + break; + case kStroke_RectType: + if (paint.isAntiAlias()) { + SkScan::AntiFrameRect(devRect, strokeSize, clip, blitter); + } else { + SkScan::FrameRect(devRect, strokeSize, clip, blitter); + } + break; + case kHair_RectType: + if (paint.isAntiAlias()) { + SkScan::AntiHairRect(devRect, clip, blitter); + } else { + SkScan::HairRect(devRect, clip, blitter); + } + break; + default: + SkDEBUGFAIL("bad rtype"); + } +} + +void SkDraw::drawDevMask(const SkMask& srcM, const SkPaint& paint) const { + if (srcM.fBounds.isEmpty()) { + return; + } + + const SkMask* mask = &srcM; + + SkMask dstM; + if (paint.getMaskFilter() && + as_MFB(paint.getMaskFilter())->filterMask(&dstM, srcM, *fMatrix, nullptr)) { + mask = &dstM; + } + SkAutoMaskFreeImage ami(dstM.fImage); + + SkAutoBlitterChoose blitterChooser(*this, nullptr, paint); + SkBlitter* blitter = blitterChooser.get(); + + SkAAClipBlitterWrapper wrapper; + const SkRegion* clipRgn; + + if (fRC->isBW()) { + clipRgn = &fRC->bwRgn(); + } else { + wrapper.init(*fRC, blitter); + clipRgn = &wrapper.getRgn(); + blitter = wrapper.getBlitter(); + } + blitter->blitMaskRegion(*mask, *clipRgn); +} + +static SkScalar fast_len(const SkVector& vec) { + SkScalar x = SkScalarAbs(vec.fX); + SkScalar y = SkScalarAbs(vec.fY); + if (x < y) { + using std::swap; + swap(x, y); + } + return x + SkScalarHalf(y); +} + +bool SkDrawTreatAAStrokeAsHairline(SkScalar strokeWidth, const SkMatrix& matrix, + SkScalar* coverage) { + SkASSERT(strokeWidth > 0); + // We need to try to fake a thick-stroke with a modulated hairline. + + if (matrix.hasPerspective()) { + return false; + } + + SkVector src[2], dst[2]; + src[0].set(strokeWidth, 0); + src[1].set(0, strokeWidth); + matrix.mapVectors(dst, src, 2); + SkScalar len0 = fast_len(dst[0]); + SkScalar len1 = fast_len(dst[1]); + if (len0 <= SK_Scalar1 && len1 <= SK_Scalar1) { + if (coverage) { + *coverage = SkScalarAve(len0, len1); + } + return true; + } + return false; +} + +void SkDraw::drawRRect(const SkRRect& rrect, const SkPaint& paint) const { + SkDEBUGCODE(this->validate()); + + if (fRC->isEmpty()) { + return; + } + + { + // TODO: Investigate optimizing these options. They are in the same + // order as SkDraw::drawPath, which handles each case. It may be + // that there is no way to optimize for these using the SkRRect path. + SkScalar coverage; + if (SkDrawTreatAsHairline(paint, *fMatrix, &coverage)) { + goto DRAW_PATH; + } + + if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) { + goto DRAW_PATH; + } + } + + if (paint.getMaskFilter()) { + // Transform the rrect into device space. + SkRRect devRRect; + if (rrect.transform(*fMatrix, &devRRect)) { + SkAutoBlitterChoose blitter(*this, nullptr, paint); + if (as_MFB(paint.getMaskFilter())->filterRRect(devRRect, *fMatrix, + *fRC, blitter.get())) { + return; // filterRRect() called the blitter, so we're done + } + } + } + +DRAW_PATH: + // Now fall back to the default case of using a path. + SkPath path; + path.addRRect(rrect); + this->drawPath(path, paint, nullptr, true); +} + +SkScalar SkDraw::ComputeResScaleForStroking(const SkMatrix& matrix) { + // Not sure how to handle perspective differently, so we just don't try (yet) + SkScalar sx = SkPoint::Length(matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewY]); + SkScalar sy = SkPoint::Length(matrix[SkMatrix::kMSkewX], matrix[SkMatrix::kMScaleY]); + if (SkScalarsAreFinite(sx, sy)) { + SkScalar scale = SkTMax(sx, sy); + if (scale > 0) { + static const SkScalar kMaxStrokeScale = 1e5f; + return SkTMin(scale, kMaxStrokeScale); + } + } + return 1; +} + +void SkDraw::drawDevPath(const SkPath& devPath, const SkPaint& paint, bool drawCoverage, + SkBlitter* customBlitter, bool doFill) const { + if (SkPathPriv::TooBigForMath(devPath)) { + return; + } + SkBlitter* blitter = nullptr; + SkAutoBlitterChoose blitterStorage; + if (nullptr == customBlitter) { + blitter = blitterStorage.choose(*this, nullptr, paint, drawCoverage); + } else { + blitter = customBlitter; + } + + if (paint.getMaskFilter()) { + SkStrokeRec::InitStyle style = doFill ? SkStrokeRec::kFill_InitStyle + : SkStrokeRec::kHairline_InitStyle; + if (as_MFB(paint.getMaskFilter())->filterPath(devPath, *fMatrix, *fRC, blitter, style)) { + return; // filterPath() called the blitter, so we're done + } + } + + void (*proc)(const SkPath&, const SkRasterClip&, SkBlitter*); + if (doFill) { + if (paint.isAntiAlias()) { + proc = SkScan::AntiFillPath; + } else { + proc = SkScan::FillPath; + } + } else { // hairline + if (paint.isAntiAlias()) { + switch (paint.getStrokeCap()) { + case SkPaint::kButt_Cap: + proc = SkScan::AntiHairPath; + break; + case SkPaint::kSquare_Cap: + proc = SkScan::AntiHairSquarePath; + break; + case SkPaint::kRound_Cap: + proc = SkScan::AntiHairRoundPath; + break; + default: + proc SK_INIT_TO_AVOID_WARNING; + SkDEBUGFAIL("unknown paint cap type"); + } + } else { + switch (paint.getStrokeCap()) { + case SkPaint::kButt_Cap: + proc = SkScan::HairPath; + break; + case SkPaint::kSquare_Cap: + proc = SkScan::HairSquarePath; + break; + case SkPaint::kRound_Cap: + proc = SkScan::HairRoundPath; + break; + default: + proc SK_INIT_TO_AVOID_WARNING; + SkDEBUGFAIL("unknown paint cap type"); + } + } + } + + proc(devPath, *fRC, blitter); +} + +void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint, + const SkMatrix* prePathMatrix, bool pathIsMutable, + bool drawCoverage, SkBlitter* customBlitter) const { + SkDEBUGCODE(this->validate();) + + // nothing to draw + if (fRC->isEmpty()) { + return; + } + + SkPath* pathPtr = (SkPath*)&origSrcPath; + bool doFill = true; + SkPath tmpPathStorage; + SkPath* tmpPath = &tmpPathStorage; + SkMatrix tmpMatrix; + const SkMatrix* matrix = fMatrix; + tmpPath->setIsVolatile(true); + + if (prePathMatrix) { + if (origPaint.getPathEffect() || origPaint.getStyle() != SkPaint::kFill_Style) { + SkPath* result = pathPtr; + + if (!pathIsMutable) { + result = tmpPath; + pathIsMutable = true; + } + pathPtr->transform(*prePathMatrix, result); + pathPtr = result; + } else { + tmpMatrix.setConcat(*matrix, *prePathMatrix); + matrix = &tmpMatrix; + } + } + // at this point we're done with prePathMatrix + SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;) + + SkTCopyOnFirstWrite paint(origPaint); + + { + SkScalar coverage; + if (SkDrawTreatAsHairline(origPaint, *matrix, &coverage)) { + if (SK_Scalar1 == coverage) { + paint.writable()->setStrokeWidth(0); + } else if (SkBlendMode_SupportsCoverageAsAlpha(origPaint.getBlendMode())) { + U8CPU newAlpha; +#if 0 + newAlpha = SkToU8(SkScalarRoundToInt(coverage * + origPaint.getAlpha())); +#else + // this is the old technique, which we preserve for now so + // we don't change previous results (testing) + // the new way seems fine, its just (a tiny bit) different + int scale = (int)(coverage * 256); + newAlpha = origPaint.getAlpha() * scale >> 8; +#endif + SkPaint* writablePaint = paint.writable(); + writablePaint->setStrokeWidth(0); + writablePaint->setAlpha(newAlpha); + } + } + } + + if (paint->getPathEffect() || paint->getStyle() != SkPaint::kFill_Style) { + SkRect cullRect; + const SkRect* cullRectPtr = nullptr; + if (this->computeConservativeLocalClipBounds(&cullRect)) { + cullRectPtr = &cullRect; + } + doFill = paint->getFillPath(*pathPtr, tmpPath, cullRectPtr, + ComputeResScaleForStroking(*fMatrix)); + pathPtr = tmpPath; + } + + // avoid possibly allocating a new path in transform if we can + SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath; + + // transform the path into device space + pathPtr->transform(*matrix, devPathPtr); + + this->drawDevPath(*devPathPtr, *paint, drawCoverage, customBlitter, doFill); +} + +void SkDraw::drawBitmapAsMask(const SkBitmap& bitmap, const SkPaint& paint) const { + SkASSERT(bitmap.colorType() == kAlpha_8_SkColorType); + + // nothing to draw + if (fRC->isEmpty()) { + return; + } + + if (SkTreatAsSprite(*fMatrix, bitmap.dimensions(), paint)) { + int ix = SkScalarRoundToInt(fMatrix->getTranslateX()); + int iy = SkScalarRoundToInt(fMatrix->getTranslateY()); + + SkPixmap pmap; + if (!bitmap.peekPixels(&pmap)) { + return; + } + SkMask mask; + mask.fBounds.setXYWH(ix, iy, pmap.width(), pmap.height()); + mask.fFormat = SkMask::kA8_Format; + mask.fRowBytes = SkToU32(pmap.rowBytes()); + // fImage is typed as writable, but in this case it is used read-only + mask.fImage = (uint8_t*)pmap.addr8(0, 0); + + this->drawDevMask(mask, paint); + } else { // need to xform the bitmap first + SkRect r; + SkMask mask; + + r.setIWH(bitmap.width(), bitmap.height()); + fMatrix->mapRect(&r); + r.round(&mask.fBounds); + + // set the mask's bounds to the transformed bitmap-bounds, + // clipped to the actual device and further limited by the clip bounds + { + SkASSERT(fDst.bounds().contains(fRC->getBounds())); + SkIRect devBounds = fDst.bounds(); + devBounds.intersect(fRC->getBounds().makeOutset(1, 1)); + // need intersect(l, t, r, b) on irect + if (!mask.fBounds.intersect(devBounds)) { + return; + } + } + + mask.fFormat = SkMask::kA8_Format; + mask.fRowBytes = SkAlign4(mask.fBounds.width()); + size_t size = mask.computeImageSize(); + if (0 == size) { + // the mask is too big to allocated, draw nothing + return; + } + + // allocate (and clear) our temp buffer to hold the transformed bitmap + SkAutoTMalloc storage(size); + mask.fImage = storage.get(); + memset(mask.fImage, 0, size); + + // now draw our bitmap(src) into mask(dst), transformed by the matrix + { + SkBitmap device; + device.installPixels(SkImageInfo::MakeA8(mask.fBounds.width(), mask.fBounds.height()), + mask.fImage, mask.fRowBytes); + + SkCanvas c(device); + // need the unclipped top/left for the translate + c.translate(-SkIntToScalar(mask.fBounds.fLeft), + -SkIntToScalar(mask.fBounds.fTop)); + c.concat(*fMatrix); + + // We can't call drawBitmap, or we'll infinitely recurse. Instead + // we manually build a shader and draw that into our new mask + SkPaint tmpPaint; + tmpPaint.setAntiAlias(paint.isAntiAlias()); + tmpPaint.setDither(paint.isDither()); + tmpPaint.setFilterQuality(paint.getFilterQuality()); + SkPaint paintWithShader = make_paint_with_image(tmpPaint, bitmap); + SkRect rr; + rr.setIWH(bitmap.width(), bitmap.height()); + c.drawRect(rr, paintWithShader); + } + this->drawDevMask(mask, paint); + } +} + +static bool clipped_out(const SkMatrix& m, const SkRasterClip& c, + const SkRect& srcR) { + SkRect dstR; + m.mapRect(&dstR, srcR); + return c.quickReject(dstR.roundOut()); +} + +static bool clipped_out(const SkMatrix& matrix, const SkRasterClip& clip, + int width, int height) { + SkRect r; + r.setIWH(width, height); + return clipped_out(matrix, clip, r); +} + +static bool clipHandlesSprite(const SkRasterClip& clip, int x, int y, const SkPixmap& pmap) { + return clip.isBW() || clip.quickContains(x, y, x + pmap.width(), y + pmap.height()); +} + +void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix, + const SkRect* dstBounds, const SkPaint& origPaint) const { + SkDEBUGCODE(this->validate();) + + // nothing to draw + if (fRC->isEmpty() || + bitmap.width() == 0 || bitmap.height() == 0 || + bitmap.colorType() == kUnknown_SkColorType) { + return; + } + + SkTCopyOnFirstWrite paint(origPaint); + if (origPaint.getStyle() != SkPaint::kFill_Style) { + paint.writable()->setStyle(SkPaint::kFill_Style); + } + + SkMatrix matrix; + matrix.setConcat(*fMatrix, prematrix); + + if (clipped_out(matrix, *fRC, bitmap.width(), bitmap.height())) { + return; + } + + if (bitmap.colorType() != kAlpha_8_SkColorType + && SkTreatAsSprite(matrix, bitmap.dimensions(), *paint)) { + // + // It is safe to call lock pixels now, since we know the matrix is + // (more or less) identity. + // + SkPixmap pmap; + if (!bitmap.peekPixels(&pmap)) { + return; + } + int ix = SkScalarRoundToInt(matrix.getTranslateX()); + int iy = SkScalarRoundToInt(matrix.getTranslateY()); + if (clipHandlesSprite(*fRC, ix, iy, pmap)) { + SkSTArenaAlloc allocator; + // blitter will be owned by the allocator. + SkBlitter* blitter = SkBlitter::ChooseSprite(fDst, *paint, pmap, ix, iy, &allocator); + if (blitter) { + SkScan::FillIRect(SkIRect::MakeXYWH(ix, iy, pmap.width(), pmap.height()), + *fRC, blitter); + return; + } + // if !blitter, then we fall-through to the slower case + } + } + + // now make a temp draw on the stack, and use it + // + SkDraw draw(*this); + draw.fMatrix = &matrix; + + if (bitmap.colorType() == kAlpha_8_SkColorType && !paint->getColorFilter()) { + draw.drawBitmapAsMask(bitmap, *paint); + } else { + SkPaint paintWithShader = make_paint_with_image(*paint, bitmap); + const SkRect srcBounds = SkRect::MakeIWH(bitmap.width(), bitmap.height()); + if (dstBounds) { + this->drawRect(srcBounds, paintWithShader, &prematrix, dstBounds); + } else { + draw.drawRect(srcBounds, paintWithShader); + } + } +} + +void SkDraw::drawSprite(const SkBitmap& bitmap, int x, int y, const SkPaint& origPaint) const { + SkDEBUGCODE(this->validate();) + + // nothing to draw + if (fRC->isEmpty() || + bitmap.width() == 0 || bitmap.height() == 0 || + bitmap.colorType() == kUnknown_SkColorType) { + return; + } + + const SkIRect bounds = SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()); + + if (fRC->quickReject(bounds)) { + return; // nothing to draw + } + + SkPaint paint(origPaint); + paint.setStyle(SkPaint::kFill_Style); + + SkPixmap pmap; + if (!bitmap.peekPixels(&pmap)) { + return; + } + + if (nullptr == paint.getColorFilter() && clipHandlesSprite(*fRC, x, y, pmap)) { + // blitter will be owned by the allocator. + SkSTArenaAlloc allocator; + SkBlitter* blitter = SkBlitter::ChooseSprite(fDst, paint, pmap, x, y, &allocator); + if (blitter) { + SkScan::FillIRect(bounds, *fRC, blitter); + return; + } + } + + SkMatrix matrix; + SkRect r; + + // get a scalar version of our rect + r.set(bounds); + + // create shader with offset + matrix.setTranslate(r.fLeft, r.fTop); + SkPaint paintWithShader = make_paint_with_image(paint, bitmap, &matrix); + SkDraw draw(*this); + matrix.reset(); + draw.fMatrix = &matrix; + // call ourself with a rect + draw.drawRect(r, paintWithShader); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +void SkDraw::validate() const { + SkASSERT(fMatrix != nullptr); + SkASSERT(fRC != nullptr); + + const SkIRect& cr = fRC->getBounds(); + SkIRect br; + + br.setWH(fDst.width(), fDst.height()); + SkASSERT(cr.isEmpty() || br.contains(cr)); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkPath.h" +#include "include/core/SkRegion.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkDraw.h" + +bool SkDraw::ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect* clipBounds, + const SkMaskFilter* filter, const SkMatrix* filterMatrix, + SkIRect* bounds) { + // init our bounds from the path + *bounds = devPathBounds.makeOutset(SK_ScalarHalf, SK_ScalarHalf).roundOut(); + + SkIPoint margin = SkIPoint::Make(0, 0); + if (filter) { + SkASSERT(filterMatrix); + + SkMask srcM, dstM; + + srcM.fBounds = *bounds; + srcM.fFormat = SkMask::kA8_Format; + if (!as_MFB(filter)->filterMask(&dstM, srcM, *filterMatrix, &margin)) { + return false; + } + } + + // (possibly) trim the bounds to reflect the clip + // (plus whatever slop the filter needs) + if (clipBounds) { + // Ugh. Guard against gigantic margins from wacky filters. Without this + // check we can request arbitrary amounts of slop beyond our visible + // clip, and bring down the renderer (at least on finite RAM machines + // like handsets, etc.). Need to balance this invented value between + // quality of large filters like blurs, and the corresponding memory + // requests. + static const int MAX_MARGIN = 128; + if (!bounds->intersect(clipBounds->makeOutset(SkMin32(margin.fX, MAX_MARGIN), + SkMin32(margin.fY, MAX_MARGIN)))) { + return false; + } + } + + return true; +} + +static void draw_into_mask(const SkMask& mask, const SkPath& devPath, + SkStrokeRec::InitStyle style) { + SkDraw draw; + if (!draw.fDst.reset(mask)) { + return; + } + + SkRasterClip clip; + SkMatrix matrix; + SkPaint paint; + + clip.setRect(SkIRect::MakeWH(mask.fBounds.width(), mask.fBounds.height())); + matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft), + -SkIntToScalar(mask.fBounds.fTop)); + + draw.fRC = &clip; + draw.fMatrix = &matrix; + paint.setAntiAlias(true); + switch (style) { + case SkStrokeRec::kHairline_InitStyle: + SkASSERT(!paint.getStrokeWidth()); + paint.setStyle(SkPaint::kStroke_Style); + break; + case SkStrokeRec::kFill_InitStyle: + SkASSERT(paint.getStyle() == SkPaint::kFill_Style); + break; + + } + draw.drawPath(devPath, paint); +} + +bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect* clipBounds, + const SkMaskFilter* filter, const SkMatrix* filterMatrix, + SkMask* mask, SkMask::CreateMode mode, + SkStrokeRec::InitStyle style) { + if (devPath.isEmpty()) { + return false; + } + + if (SkMask::kJustRenderImage_CreateMode != mode) { + if (!ComputeMaskBounds(devPath.getBounds(), clipBounds, filter, + filterMatrix, &mask->fBounds)) + return false; + } + + if (SkMask::kComputeBoundsAndRenderImage_CreateMode == mode) { + mask->fFormat = SkMask::kA8_Format; + mask->fRowBytes = mask->fBounds.width(); + size_t size = mask->computeImageSize(); + if (0 == size) { + // we're too big to allocate the mask, abort + return false; + } + mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc); + } + + if (SkMask::kJustComputeBounds_CreateMode != mode) { + draw_into_mask(*mask, devPath, style); + } + + return true; +} diff --git a/gfx/skia/skia/src/core/SkDraw.h b/gfx/skia/skia/src/core/SkDraw.h new file mode 100644 index 0000000000..69149f30da --- /dev/null +++ b/gfx/skia/skia/src/core/SkDraw.h @@ -0,0 +1,170 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkDraw_DEFINED +#define SkDraw_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkStrokeRec.h" +#include "include/core/SkVertices.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkMask.h" + +class SkBitmap; +class SkClipStack; +class SkBaseDevice; +class SkBlitter; +class SkMatrix; +class SkPath; +class SkRegion; +class SkRasterClip; +struct SkRect; +class SkRRect; + +class SkDraw : public SkGlyphRunListPainter::BitmapDevicePainter { +public: + SkDraw(); + + void drawPaint(const SkPaint&) const; + void drawPoints(SkCanvas::PointMode, size_t count, const SkPoint[], + const SkPaint&, SkBaseDevice*) const; + void drawRect(const SkRect& prePaintRect, const SkPaint&, const SkMatrix* paintMatrix, + const SkRect* postPaintRect) const; + void drawRect(const SkRect& rect, const SkPaint& paint) const { + this->drawRect(rect, paint, nullptr, nullptr); + } + void drawRRect(const SkRRect&, const SkPaint&) const; + /** + * To save on mallocs, we allow a flag that tells us that srcPath is + * mutable, so that we don't have to make copies of it as we transform it. + * + * If prePathMatrix is not null, it should logically be applied before any + * stroking or other effects. If there are no effects on the paint that + * affect the geometry/rasterization, then the pre matrix can just be + * pre-concated with the current matrix. + */ + void drawPath(const SkPath& path, const SkPaint& paint, + const SkMatrix* prePathMatrix = nullptr, bool pathIsMutable = false) const { + this->drawPath(path, paint, prePathMatrix, pathIsMutable, false); + } + + /* If dstOrNull is null, computes a dst by mapping the bitmap's bounds through the matrix. */ + void drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect* dstOrNull, + const SkPaint&) const; + void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) const; + void drawGlyphRunList(const SkGlyphRunList& glyphRunList, + SkGlyphRunListPainter* glyphPainter) const; + void drawVertices(SkVertices::VertexMode mode, int vertexCount, + const SkPoint vertices[], const SkPoint textures[], + const SkColor colors[], const SkVertices::BoneIndices boneIndices[], + const SkVertices::BoneWeights boneWeights[], SkBlendMode bmode, + const uint16_t indices[], int ptCount, + const SkPaint& paint, const SkVertices::Bone bones[], int boneCount) const; + void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int count, + SkBlendMode, const SkPaint&); + + /** + * Overwrite the target with the path's coverage (i.e. its mask). + * Will overwrite the entire device, so it need not be zero'd first. + * + * Only device A8 is supported right now. + */ + void drawPathCoverage(const SkPath& src, const SkPaint& paint, + SkBlitter* customBlitter = nullptr) const { + bool isHairline = paint.getStyle() == SkPaint::kStroke_Style && + paint.getStrokeWidth() > 0; + this->drawPath(src, paint, nullptr, false, !isHairline, customBlitter); + } + + void paintPaths(SkDrawableGlyphBuffer* drawables, + SkScalar scale, + const SkPaint& paint) const override; + + void paintMasks(SkDrawableGlyphBuffer* drawables, const SkPaint& paint) const override; + + static bool ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect* clipBounds, + const SkMaskFilter* filter, const SkMatrix* filterMatrix, + SkIRect* bounds); + + /** Helper function that creates a mask from a path and an optional maskfilter. + Note however, that the resulting mask will not have been actually filtered, + that must be done afterwards (by calling filterMask). The maskfilter is provided + solely to assist in computing the mask's bounds (if the mode requests that). + */ + static bool DrawToMask(const SkPath& devPath, const SkIRect* clipBounds, + const SkMaskFilter*, const SkMatrix* filterMatrix, + SkMask* mask, SkMask::CreateMode mode, + SkStrokeRec::InitStyle style); + + void drawDevMask(const SkMask& mask, const SkPaint&) const; + + enum RectType { + kHair_RectType, + kFill_RectType, + kStroke_RectType, + kPath_RectType + }; + + /** + * Based on the paint's style, strokeWidth, and the matrix, classify how + * to draw the rect. If no special-case is available, returns + * kPath_RectType. + * + * Iff RectType == kStroke_RectType, then strokeSize is set to the device + * width and height of the stroke. + */ + static RectType ComputeRectType(const SkPaint&, const SkMatrix&, + SkPoint* strokeSize); + + static SkScalar ComputeResScaleForStroking(const SkMatrix& matrix); +private: + void drawBitmapAsMask(const SkBitmap&, const SkPaint&) const; + + void drawPath(const SkPath&, + const SkPaint&, + const SkMatrix* preMatrix, + bool pathIsMutable, + bool drawCoverage, + SkBlitter* customBlitter = nullptr) const; + + void drawLine(const SkPoint[2], const SkPaint&) const; + + void drawDevPath(const SkPath& devPath, + const SkPaint& paint, + bool drawCoverage, + SkBlitter* customBlitter, + bool doFill) const; + /** + * Return the current clip bounds, in local coordinates, with slop to account + * for antialiasing or hairlines (i.e. device-bounds outset by 1, and then + * run through the inverse of the matrix). + * + * If the matrix cannot be inverted, or the current clip is empty, return + * false and ignore bounds parameter. + */ + bool SK_WARN_UNUSED_RESULT computeConservativeLocalClipBounds(SkRect* bounds) const; + +public: + SkPixmap fDst; + const SkMatrix* fMatrix{nullptr}; // required + const SkRasterClip* fRC{nullptr}; // required + + // optional, will be same dimensions as fDst if present + const SkPixmap* fCoverage{nullptr}; + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkDrawLooper.cpp b/gfx/skia/skia/src/core/SkDrawLooper.cpp new file mode 100644 index 0000000000..9de4d43673 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDrawLooper.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkDrawLooper.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkRect.h" +#include "src/core/SkArenaAlloc.h" + +void SkDrawLooper::Context::Info::applyToCTM(SkMatrix* ctm) const { + if (fApplyPostCTM) { + ctm->postTranslate(fTranslate.fX, fTranslate.fY); + } else { + ctm->preTranslate(fTranslate.fX, fTranslate.fY); + } +} + +void SkDrawLooper::Context::Info::applyToCanvas(SkCanvas* canvas) const { + if (fApplyPostCTM) { + SkMatrix ctm = canvas->getTotalMatrix(); + ctm.postTranslate(fTranslate.fX, fTranslate.fY); + canvas->setMatrix(ctm); + } else { + canvas->translate(fTranslate.fX, fTranslate.fY); + } +} + +bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) const { + SkSTArenaAlloc<48> alloc; + + SkDrawLooper::Context* context = this->makeContext(&alloc); + for (;;) { + SkPaint p(paint); + SkDrawLooper::Context::Info info; + if (context->next(&info, &p)) { + if (!p.canComputeFastBounds()) { + return false; + } + } else { + break; + } + } + return true; +} + +void SkDrawLooper::computeFastBounds(const SkPaint& paint, const SkRect& s, + SkRect* dst) const { + // src and dst rects may alias and we need to keep the original src, so copy it. + const SkRect src = s; + + SkSTArenaAlloc<48> alloc; + + *dst = src; // catch case where there are no loops + SkDrawLooper::Context* context = this->makeContext(&alloc); + + for (bool firstTime = true;; firstTime = false) { + SkPaint p(paint); + SkDrawLooper::Context::Info info; + if (context->next(&info, &p)) { + SkRect r(src); + + p.computeFastBounds(r, &r); + r.offset(info.fTranslate.fX, info.fTranslate.fY); + + if (firstTime) { + *dst = r; + } else { + dst->join(r); + } + } else { + break; + } + } +} + +bool SkDrawLooper::asABlurShadow(BlurShadowRec*) const { + return false; +} + +void SkDrawLooper::apply(SkCanvas* canvas, const SkPaint& paint, + std::function proc) { + SkSTArenaAlloc<256> alloc; + Context* ctx = this->makeContext(&alloc); + if (ctx) { + Context::Info info; + for (;;) { + SkPaint p = paint; + if (!ctx->next(&info, &p)) { + break; + } + canvas->save(); + if (info.fApplyPostCTM) { + SkMatrix ctm = canvas->getTotalMatrix(); + ctm.postTranslate(info.fTranslate.fX, info.fTranslate.fY); + canvas->setMatrix(ctm); + } else { + canvas->translate(info.fTranslate.fX, info.fTranslate.fY); + } + proc(canvas, p); + canvas->restore(); + } + } +} diff --git a/gfx/skia/skia/src/core/SkDrawProcs.h b/gfx/skia/skia/src/core/SkDrawProcs.h new file mode 100644 index 0000000000..c07e181e9c --- /dev/null +++ b/gfx/skia/skia/src/core/SkDrawProcs.h @@ -0,0 +1,42 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDrawProcs_DEFINED +#define SkDrawProcs_DEFINED + +#include "src/core/SkDraw.h" +#include "src/core/SkGlyph.h" + +bool SkDrawTreatAAStrokeAsHairline(SkScalar strokeWidth, const SkMatrix&, + SkScalar* coverage); + +/** + * If the current paint is set to stroke and the stroke-width when applied to + * the matrix is <= 1.0, then this returns true, and sets coverage (simulating + * a stroke by drawing a hairline with partial coverage). If any of these + * conditions are false, then this returns false and coverage is ignored. + */ +inline bool SkDrawTreatAsHairline(const SkPaint& paint, const SkMatrix& matrix, + SkScalar* coverage) { + if (SkPaint::kStroke_Style != paint.getStyle()) { + return false; + } + + SkScalar strokeWidth = paint.getStrokeWidth(); + if (0 == strokeWidth) { + *coverage = SK_Scalar1; + return true; + } + + if (!paint.isAntiAlias()) { + return false; + } + + return SkDrawTreatAAStrokeAsHairline(strokeWidth, matrix, coverage); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkDrawShadowInfo.cpp b/gfx/skia/skia/src/core/SkDrawShadowInfo.cpp new file mode 100644 index 0000000000..8912046f26 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDrawShadowInfo.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkRect.h" +#include "src/core/SkDrawShadowInfo.h" +#include "src/utils/SkPolyUtils.h" + +namespace SkDrawShadowMetrics { + +static SkScalar compute_z(SkScalar x, SkScalar y, const SkPoint3& params) { + return x*params.fX + y*params.fY + params.fZ; +} + +bool GetSpotShadowTransform(const SkPoint3& lightPos, SkScalar lightRadius, + const SkMatrix& ctm, const SkPoint3& zPlaneParams, + const SkRect& pathBounds, SkMatrix* shadowTransform, SkScalar* radius) { + auto heightFunc = [zPlaneParams] (SkScalar x, SkScalar y) { + return zPlaneParams.fX*x + zPlaneParams.fY*y + zPlaneParams.fZ; + }; + SkScalar occluderHeight = heightFunc(pathBounds.centerX(), pathBounds.centerY()); + + if (!ctm.hasPerspective()) { + SkScalar scale; + SkVector translate; + SkDrawShadowMetrics::GetSpotParams(occluderHeight, lightPos.fX, lightPos.fY, lightPos.fZ, + lightRadius, radius, &scale, &translate); + shadowTransform->setScaleTranslate(scale, scale, translate.fX, translate.fY); + shadowTransform->preConcat(ctm); + } else { + if (SkScalarNearlyZero(pathBounds.width()) || SkScalarNearlyZero(pathBounds.height())) { + return false; + } + + // get rotated quad in 3D + SkPoint pts[4]; + ctm.mapRectToQuad(pts, pathBounds); + // No shadows for bowties or other degenerate cases + if (!SkIsConvexPolygon(pts, 4)) { + return false; + } + SkPoint3 pts3D[4]; + SkScalar z = heightFunc(pathBounds.fLeft, pathBounds.fTop); + pts3D[0].set(pts[0].fX, pts[0].fY, z); + z = heightFunc(pathBounds.fRight, pathBounds.fTop); + pts3D[1].set(pts[1].fX, pts[1].fY, z); + z = heightFunc(pathBounds.fRight, pathBounds.fBottom); + pts3D[2].set(pts[2].fX, pts[2].fY, z); + z = heightFunc(pathBounds.fLeft, pathBounds.fBottom); + pts3D[3].set(pts[3].fX, pts[3].fY, z); + + // project from light through corners to z=0 plane + for (int i = 0; i < 4; ++i) { + SkScalar dz = lightPos.fZ - pts3D[i].fZ; + // light shouldn't be below or at a corner's z-location + if (dz <= SK_ScalarNearlyZero) { + return false; + } + SkScalar zRatio = pts3D[i].fZ / dz; + pts3D[i].fX -= (lightPos.fX - pts3D[i].fX)*zRatio; + pts3D[i].fY -= (lightPos.fY - pts3D[i].fY)*zRatio; + pts3D[i].fZ = SK_Scalar1; + } + + // Generate matrix that projects from [-1,1]x[-1,1] square to projected quad + SkPoint3 h0, h1, h2; + // Compute homogenous crossing point between top and bottom edges (gives new x-axis). + h0 = (pts3D[1].cross(pts3D[0])).cross(pts3D[2].cross(pts3D[3])); + // Compute homogenous crossing point between left and right edges (gives new y-axis). + h1 = (pts3D[0].cross(pts3D[3])).cross(pts3D[1].cross(pts3D[2])); + // Compute homogenous crossing point between diagonals (gives new origin). + h2 = (pts3D[0].cross(pts3D[2])).cross(pts3D[1].cross(pts3D[3])); + // If h2 is a vector (z=0 in 2D homogeneous space), that means that at least + // two of the quad corners are coincident and we don't have a realistic projection + if (SkScalarNearlyZero(h2.fZ)) { + return false; + } + // In some cases the crossing points are in the wrong direction + // to map (-1,-1) to pts3D[0], so we need to correct for that. + // Want h0 to be to the right of the left edge. + SkVector3 v = pts3D[3] - pts3D[0]; + SkVector3 w = h0 - pts3D[0]; + SkScalar perpDot = v.fX*w.fY - v.fY*w.fX; + if (perpDot > 0) { + h0 = -h0; + } + // Want h1 to be above the bottom edge. + v = pts3D[1] - pts3D[0]; + perpDot = v.fX*w.fY - v.fY*w.fX; + if (perpDot < 0) { + h1 = -h1; + } + shadowTransform->setAll(h0.fX / h2.fZ, h1.fX / h2.fZ, h2.fX / h2.fZ, + h0.fY / h2.fZ, h1.fY / h2.fZ, h2.fY / h2.fZ, + h0.fZ / h2.fZ, h1.fZ / h2.fZ, 1); + // generate matrix that transforms from bounds to [-1,1]x[-1,1] square + SkMatrix toHomogeneous; + SkScalar xScale = 2/(pathBounds.fRight - pathBounds.fLeft); + SkScalar yScale = 2/(pathBounds.fBottom - pathBounds.fTop); + toHomogeneous.setAll(xScale, 0, -xScale*pathBounds.fLeft - 1, + 0, yScale, -yScale*pathBounds.fTop - 1, + 0, 0, 1); + shadowTransform->preConcat(toHomogeneous); + + *radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius); + } + + return true; +} + +void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm, + SkRect* bounds) { + SkRect ambientBounds = path.getBounds(); + SkScalar occluderZ; + if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) { + occluderZ = rec.fZPlaneParams.fZ; + } else { + occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams); + occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop, + rec.fZPlaneParams)); + occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom, + rec.fZPlaneParams)); + occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom, + rec.fZPlaneParams)); + } + SkScalar ambientBlur; + SkScalar spotBlur; + SkScalar spotScale; + SkPoint spotOffset; + if (ctm.hasPerspective()) { + // transform ambient and spot bounds into device space + ctm.mapRect(&ambientBounds); + + // get ambient blur (in device space) + ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); + + // get spot params (in device space) + SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY); + ctm.mapPoints(&devLightPos, 1); + SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY, + rec.fLightPos.fZ, rec.fLightRadius, + &spotBlur, &spotScale, &spotOffset); + } else { + SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale()); + + // get ambient blur (in local space) + SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); + ambientBlur = devSpaceAmbientBlur*devToSrcScale; + + // get spot params (in local space) + SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY, + rec.fLightPos.fZ, rec.fLightRadius, + &spotBlur, &spotScale, &spotOffset); + + // convert spot blur to local space + spotBlur *= devToSrcScale; + } + + // in both cases, adjust ambient and spot bounds + SkRect spotBounds = ambientBounds; + ambientBounds.outset(ambientBlur, ambientBlur); + spotBounds.fLeft *= spotScale; + spotBounds.fTop *= spotScale; + spotBounds.fRight *= spotScale; + spotBounds.fBottom *= spotScale; + spotBounds.offset(spotOffset.fX, spotOffset.fY); + spotBounds.outset(spotBlur, spotBlur); + + // merge bounds + *bounds = ambientBounds; + bounds->join(spotBounds); + // outset a bit to account for floating point error + bounds->outset(1, 1); + + // if perspective, transform back to src space + if (ctm.hasPerspective()) { + // TODO: create tighter mapping from dev rect back to src rect + SkMatrix inverse; + if (ctm.invert(&inverse)) { + inverse.mapRect(bounds); + } + } +} + + +} + diff --git a/gfx/skia/skia/src/core/SkDrawShadowInfo.h b/gfx/skia/skia/src/core/SkDrawShadowInfo.h new file mode 100644 index 0000000000..c89c8f2046 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDrawShadowInfo.h @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDrawShadowInfo_DEFINED +#define SkDrawShadowInfo_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkPoint.h" +#include "include/core/SkPoint3.h" + +class SkMatrix; +class SkPath; +struct SkRect; + +struct SkDrawShadowRec { + SkPoint3 fZPlaneParams; + SkPoint3 fLightPos; + SkScalar fLightRadius; + SkColor fAmbientColor; + SkColor fSpotColor; + uint32_t fFlags; +}; + +namespace SkDrawShadowMetrics { + +static constexpr auto kAmbientHeightFactor = 1.0f / 128.0f; +static constexpr auto kAmbientGeomFactor = 64.0f; +// Assuming that we have a light height of 600 for the spot shadow, +// the spot values will reach their maximum at a height of approximately 292.3077. +// We'll round up to 300 to keep it simple. +static constexpr auto kMaxAmbientRadius = 300*kAmbientHeightFactor*kAmbientGeomFactor; + +static inline float divide_and_pin(float numer, float denom, float min, float max) { + float result = SkTPin(sk_ieee_float_divide(numer, denom), min, max); + // ensure that SkTPin handled non-finites correctly + SkASSERT(result >= min && result <= max); + return result; +} + +inline SkScalar AmbientBlurRadius(SkScalar height) { + return SkTMin(height*kAmbientHeightFactor*kAmbientGeomFactor, kMaxAmbientRadius); +} + +inline SkScalar AmbientRecipAlpha(SkScalar height) { + return 1.0f + SkTMax(height*kAmbientHeightFactor, 0.0f); +} + +inline SkScalar SpotBlurRadius(SkScalar occluderZ, SkScalar lightZ, SkScalar lightRadius) { + return lightRadius*divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f); +} + +inline void GetSpotParams(SkScalar occluderZ, SkScalar lightX, SkScalar lightY, SkScalar lightZ, + SkScalar lightRadius, + SkScalar* blurRadius, SkScalar* scale, SkVector* translate) { + SkScalar zRatio = divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f); + *blurRadius = lightRadius*zRatio; + *scale = divide_and_pin(lightZ, lightZ - occluderZ, 1.0f, 1.95f); + *translate = SkVector::Make(-zRatio * lightX, -zRatio * lightY); +} + +// Create the transformation to apply to a path to get its base shadow outline, given the light +// parameters and the path's 3D transformation (given by ctm and zPlaneParams). +// Also computes the blur radius to apply the transformed outline. +bool GetSpotShadowTransform(const SkPoint3& lightPos, SkScalar lightRadius, + const SkMatrix& ctm, const SkPoint3& zPlaneParams, + const SkRect& pathBounds, SkMatrix* shadowTransform, SkScalar* radius); + +// get bounds prior to the ctm being applied +void GetLocalBounds(const SkPath&, const SkDrawShadowRec&, const SkMatrix& ctm, SkRect* bounds); + +} + +#endif diff --git a/gfx/skia/skia/src/core/SkDraw_atlas.cpp b/gfx/skia/skia/src/core/SkDraw_atlas.cpp new file mode 100644 index 0000000000..b3d86061be --- /dev/null +++ b/gfx/skia/skia/src/core/SkDraw_atlas.cpp @@ -0,0 +1,120 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkRSXform.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkDraw.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkScan.h" +#include "src/shaders/SkShaderBase.h" + +#include "include/core/SkMatrix.h" +#include "src/core/SkScan.h" + +static void fill_rect(const SkMatrix& ctm, const SkRasterClip& rc, + const SkRect& r, SkBlitter* blitter, SkPath* scratchPath) { + if (ctm.rectStaysRect()) { + SkRect dr; + ctm.mapRect(&dr, r); + SkScan::FillRect(dr, rc, blitter); + } else { + SkPoint pts[4]; + r.toQuad(pts); + ctm.mapPoints(pts, pts, 4); + + scratchPath->rewind(); + scratchPath->addPoly(pts, 4, true); + SkScan::FillPath(*scratchPath, rc, blitter); + } +} + +static void load_color(SkRasterPipeline_UniformColorCtx* ctx, const float rgba[]) { + // only need one of these. can I query the pipeline to know if its lowp or highp? + ctx->rgba[0] = SkScalarRoundToInt(rgba[0]*255); ctx->r = rgba[0]; + ctx->rgba[1] = SkScalarRoundToInt(rgba[1]*255); ctx->g = rgba[1]; + ctx->rgba[2] = SkScalarRoundToInt(rgba[2]*255); ctx->b = rgba[2]; + ctx->rgba[3] = SkScalarRoundToInt(rgba[3]*255); ctx->a = rgba[3]; +} + +void SkDraw::drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect textures[], + const SkColor colors[], int count, SkBlendMode bmode, const SkPaint& paint) { + sk_sp atlasShader = atlas->makeShader(); + if (!atlasShader) { + return; + } + + SkPaint p(paint); + p.setAntiAlias(false); // we never respect this for drawAtlas(or drawVertices) + p.setStyle(SkPaint::kFill_Style); + p.setShader(nullptr); + p.setMaskFilter(nullptr); + + SkSTArenaAlloc<256> alloc; + SkRasterPipeline pipeline(&alloc); + SkStageRec rec = { + &pipeline, &alloc, fDst.colorType(), fDst.colorSpace(), p, nullptr, *fMatrix + }; + + SkStageUpdater* updator = as_SB(atlasShader.get())->appendUpdatableStages(rec); + if (!updator) { + SkDraw draw(*this); + + p.setShader(atlasShader); + for (int i = 0; i < count; ++i) { + if (colors) { + p.setShader(SkShaders::Blend(bmode, SkShaders::Color(colors[i]), atlasShader)); + } + SkMatrix mx; + mx.setRSXform(xform[i]); + mx.preTranslate(-textures[i].fLeft, -textures[i].fTop); + mx.postConcat(*fMatrix); + draw.fMatrix = &mx; + draw.drawRect(textures[i], p); + } + return; + } + + SkRasterPipeline_UniformColorCtx* uniformCtx = nullptr; + SkColorSpaceXformSteps steps(sk_srgb_singleton(), kUnpremul_SkAlphaType, + rec.fDstCS, kUnpremul_SkAlphaType); + + if (colors) { + // we will late-bind the values in ctx, once for each color in the loop + uniformCtx = alloc.make(); + rec.fPipeline->append(SkRasterPipeline::uniform_color_dst, uniformCtx); + SkBlendMode_AppendStages(bmode, rec.fPipeline); + } + + bool isOpaque = !colors && atlasShader->isOpaque(); + if (p.getAlphaf() != 1) { + rec.fPipeline->append(SkRasterPipeline::scale_1_float, alloc.make(p.getAlphaf())); + isOpaque = false; + } + + auto blitter = SkCreateRasterPipelineBlitter(fDst, p, pipeline, isOpaque, &alloc); + SkPath scratchPath; + + for (int i = 0; i < count; ++i) { + if (colors) { + SkColor4f c4 = SkColor4f::FromColor(colors[i]); + steps.apply(c4.vec()); + load_color(uniformCtx, c4.premul().vec()); + } + + SkMatrix mx; + mx.setRSXform(xform[i]); + mx.preTranslate(-textures[i].fLeft, -textures[i].fTop); + mx.postConcat(*fMatrix); + + updator->update(mx, nullptr); + fill_rect(mx, *fRC, textures[i], blitter, &scratchPath); + } +} diff --git a/gfx/skia/skia/src/core/SkDraw_text.cpp b/gfx/skia/skia/src/core/SkDraw_text.cpp new file mode 100644 index 0000000000..a3540818e4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDraw_text.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDraw.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkStrike.h" +#include "src/core/SkUtils.h" +#include + +// disable warning : local variable used without having been initialized +#if defined _WIN32 +#pragma warning ( push ) +#pragma warning ( disable : 4701 ) +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool check_glyph_position(SkPoint position) { + // Prevent glyphs from being drawn outside of or straddling the edge of device space. + // Comparisons written a little weirdly so that NaN coordinates are treated safely. + auto gt = [](float a, int b) { return !(a <= (float)b); }; + auto lt = [](float a, int b) { return !(a >= (float)b); }; + return !(gt(position.fX, INT_MAX - (INT16_MAX + SkTo(UINT16_MAX))) || + lt(position.fX, INT_MIN - (INT16_MIN + 0 /*UINT16_MIN*/)) || + gt(position.fY, INT_MAX - (INT16_MAX + SkTo(UINT16_MAX))) || + lt(position.fY, INT_MIN - (INT16_MIN + 0 /*UINT16_MIN*/))); +} + +void SkDraw::paintMasks(SkDrawableGlyphBuffer* drawables, const SkPaint& paint) const { + + // The size used for a typical blitter. + SkSTArenaAlloc<3308> alloc; + SkBlitter* blitter = SkBlitter::Choose(fDst, *fMatrix, paint, &alloc, false); + if (fCoverage) { + blitter = alloc.make( + blitter, + SkBlitter::Choose(*fCoverage, *fMatrix, SkPaint(), &alloc, true)); + } + + SkAAClipBlitterWrapper wrapper{*fRC, blitter}; + blitter = wrapper.getBlitter(); + + bool useRegion = fRC->isBW() && !fRC->isRect(); + + if (useRegion) { + for (auto t : drawables->drawable()) { + SkGlyphVariant glyph; SkPoint pos; + std::tie(glyph, pos) = t; + if (check_glyph_position(pos)) { + SkMask mask = glyph.glyph()->mask(pos); + + SkRegion::Cliperator clipper(fRC->bwRgn(), mask.fBounds); + + if (!clipper.done()) { + if (SkMask::kARGB32_Format == mask.fFormat) { + SkBitmap bm; + bm.installPixels(SkImageInfo::MakeN32Premul(mask.fBounds.size()), + mask.fImage, + mask.fRowBytes); + this->drawSprite(bm, mask.fBounds.x(), mask.fBounds.y(), paint); + } else { + const SkIRect& cr = clipper.rect(); + do { + blitter->blitMask(mask, cr); + clipper.next(); + } while (!clipper.done()); + } + } + } + } + } else { + SkIRect clipBounds = fRC->isBW() ? fRC->bwRgn().getBounds() + : fRC->aaRgn().getBounds(); + for (auto t : drawables->drawable()) { + SkGlyphVariant glyph; SkPoint pos; + std::tie(glyph, pos) = t; + if (check_glyph_position(pos)) { + SkMask mask = glyph.glyph()->mask(pos); + SkIRect storage; + const SkIRect* bounds = &mask.fBounds; + + // this extra test is worth it, assuming that most of the time it succeeds + // since we can avoid writing to storage + if (!clipBounds.containsNoEmptyCheck(mask.fBounds)) { + if (!storage.intersect(mask.fBounds, clipBounds)) { + continue; + } + bounds = &storage; + } + + if (SkMask::kARGB32_Format == mask.fFormat) { + SkBitmap bm; + bm.installPixels(SkImageInfo::MakeN32Premul(mask.fBounds.size()), + mask.fImage, + mask.fRowBytes); + this->drawSprite(bm, mask.fBounds.x(), mask.fBounds.y(), paint); + } else { + blitter->blitMask(mask, *bounds); + } + } + } + } +} + +void SkDraw::paintPaths(SkDrawableGlyphBuffer* drawables, + SkScalar scale, + const SkPaint& paint) const { + for (auto t : drawables->drawable()) { + SkGlyphVariant path; SkPoint pos; + std::tie(path, pos) = t; + SkMatrix m; + m.setScaleTranslate(scale, scale, pos.x(), pos.y()); + this->drawPath(*path.path(), paint, &m, false); + } +} + +void SkDraw::drawGlyphRunList(const SkGlyphRunList& glyphRunList, + SkGlyphRunListPainter* glyphPainter) const { + + SkDEBUGCODE(this->validate();) + + if (fRC->isEmpty()) { + return; + } + + glyphPainter->drawForBitmapDevice(glyphRunList, *fMatrix, this); +} + +#if defined _WIN32 +#pragma warning ( pop ) +#endif + diff --git a/gfx/skia/skia/src/core/SkDraw_vertices.cpp b/gfx/skia/skia/src/core/SkDraw_vertices.cpp new file mode 100644 index 0000000000..bed4c2aa76 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDraw_vertices.cpp @@ -0,0 +1,376 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/private/SkNx.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkAutoBlitterChoose.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkDraw.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkScan.h" +#include "src/core/SkVertState.h" +#include "src/shaders/SkComposeShader.h" +#include "src/shaders/SkShaderBase.h" + +struct Matrix43 { + float fMat[12]; // column major + + Sk4f map(float x, float y) const { + return Sk4f::Load(&fMat[0]) * x + Sk4f::Load(&fMat[4]) * y + Sk4f::Load(&fMat[8]); + } + + void setConcat(const Matrix43& a, const SkMatrix& b) { + fMat[ 0] = a.dot(0, b.getScaleX(), b.getSkewY()); + fMat[ 1] = a.dot(1, b.getScaleX(), b.getSkewY()); + fMat[ 2] = a.dot(2, b.getScaleX(), b.getSkewY()); + fMat[ 3] = a.dot(3, b.getScaleX(), b.getSkewY()); + + fMat[ 4] = a.dot(0, b.getSkewX(), b.getScaleY()); + fMat[ 5] = a.dot(1, b.getSkewX(), b.getScaleY()); + fMat[ 6] = a.dot(2, b.getSkewX(), b.getScaleY()); + fMat[ 7] = a.dot(3, b.getSkewX(), b.getScaleY()); + + fMat[ 8] = a.dot(0, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 8]; + fMat[ 9] = a.dot(1, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 9]; + fMat[10] = a.dot(2, b.getTranslateX(), b.getTranslateY()) + a.fMat[10]; + fMat[11] = a.dot(3, b.getTranslateX(), b.getTranslateY()) + a.fMat[11]; + } + +private: + float dot(int index, float x, float y) const { + return fMat[index + 0] * x + fMat[index + 4] * y; + } +}; + +static SkScan::HairRCProc ChooseHairProc(bool doAntiAlias) { + return doAntiAlias ? SkScan::AntiHairLine : SkScan::HairLine; +} + +static bool SK_WARN_UNUSED_RESULT +texture_to_matrix(const VertState& state, const SkPoint verts[], const SkPoint texs[], + SkMatrix* matrix) { + SkPoint src[3], dst[3]; + + src[0] = texs[state.f0]; + src[1] = texs[state.f1]; + src[2] = texs[state.f2]; + dst[0] = verts[state.f0]; + dst[1] = verts[state.f1]; + dst[2] = verts[state.f2]; + return matrix->setPolyToPoly(src, dst, 3); +} + +class SkTriColorShader : public SkShaderBase { +public: + SkTriColorShader(bool isOpaque) : fIsOpaque(isOpaque) {} + + bool update(const SkMatrix& ctmInv, const SkPoint pts[], const SkPMColor4f colors[], + int index0, int index1, int index2); + +protected: +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const override { + return nullptr; + } +#endif + bool onAppendStages(const SkStageRec& rec) const override { + rec.fPipeline->append(SkRasterPipeline::seed_shader); + rec.fPipeline->append(SkRasterPipeline::matrix_4x3, &fM43); + return true; + } + +private: + bool isOpaque() const override { return fIsOpaque; } + // For serialization. This will never be called. + Factory getFactory() const override { return nullptr; } + const char* getTypeName() const override { return nullptr; } + + Matrix43 fM43; // we overwrite this for each triangle + const bool fIsOpaque; + + typedef SkShaderBase INHERITED; +}; + +bool SkTriColorShader::update(const SkMatrix& ctmInv, const SkPoint pts[], + const SkPMColor4f colors[], int index0, int index1, int index2) { + SkMatrix m, im; + m.reset(); + m.set(0, pts[index1].fX - pts[index0].fX); + m.set(1, pts[index2].fX - pts[index0].fX); + m.set(2, pts[index0].fX); + m.set(3, pts[index1].fY - pts[index0].fY); + m.set(4, pts[index2].fY - pts[index0].fY); + m.set(5, pts[index0].fY); + if (!m.invert(&im)) { + return false; + } + + SkMatrix dstToUnit; + dstToUnit.setConcat(im, ctmInv); + + Sk4f c0 = Sk4f::Load(colors[index0].vec()), + c1 = Sk4f::Load(colors[index1].vec()), + c2 = Sk4f::Load(colors[index2].vec()); + + Matrix43 colorm; + (c1 - c0).store(&colorm.fMat[0]); + (c2 - c0).store(&colorm.fMat[4]); + c0.store(&colorm.fMat[8]); + fM43.setConcat(colorm, dstToUnit); + return true; +} + +// Convert the SkColors into float colors. The conversion depends on some conditions: +// - If the pixmap has a dst colorspace, we have to be "color-correct". +// Do we map into dst-colorspace before or after we interpolate? +// - We have to decide when to apply per-color alpha (before or after we interpolate) +// +// For now, we will take a simple approach, but recognize this is just a start: +// - convert colors into dst colorspace before interpolation (matches gradients) +// - apply per-color alpha before interpolation (matches old version of vertices) +// +static SkPMColor4f* convert_colors(const SkColor src[], int count, SkColorSpace* deviceCS, + SkArenaAlloc* alloc) { + SkPMColor4f* dst = alloc->makeArray(count); + SkImageInfo srcInfo = SkImageInfo::Make(count, 1, kBGRA_8888_SkColorType, + kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB()); + SkImageInfo dstInfo = SkImageInfo::Make(count, 1, kRGBA_F32_SkColorType, + kPremul_SkAlphaType, sk_ref_sp(deviceCS)); + SkConvertPixels(dstInfo, dst, 0, srcInfo, src, 0); + return dst; +} + +static bool compute_is_opaque(const SkColor colors[], int count) { + uint32_t c = ~0; + for (int i = 0; i < count; ++i) { + c &= colors[i]; + } + return SkColorGetA(c) == 0xFF; +} + +void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint textures[], + const SkColor colors[], const SkVertices::BoneIndices boneIndices[], + const SkVertices::BoneWeights boneWeights[], SkBlendMode bmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint, const SkVertices::Bone bones[], + int boneCount) const { + SkASSERT(0 == vertexCount || vertices); + + // abort early if there is nothing to draw + if (vertexCount < 3 || (indices && indexCount < 3) || fRC->isEmpty()) { + return; + } + SkMatrix ctmInv; + if (!fMatrix->invert(&ctmInv)) { + return; + } + + // make textures and shader mutually consistent + SkShader* shader = paint.getShader(); + if (!(shader && textures)) { + shader = nullptr; + textures = nullptr; + } + + // We can simplify things for certain blendmodes. This is for speed, and SkComposeShader + // itself insists we don't pass kSrc or kDst to it. + // + if (colors && textures) { + switch (bmode) { + case SkBlendMode::kSrc: + colors = nullptr; + break; + case SkBlendMode::kDst: + textures = nullptr; + break; + default: break; + } + } + + // we don't use the shader if there are no textures + if (!textures) { + shader = nullptr; + } + + constexpr size_t kDefVertexCount = 16; + constexpr size_t kOuterSize = sizeof(SkTriColorShader) + + sizeof(SkShader_Blend) + + (2 * sizeof(SkPoint) + sizeof(SkColor4f)) * kDefVertexCount; + SkSTArenaAlloc outerAlloc; + + // deform vertices using the skeleton if it is passed in + if (bones && boneCount) { + // allocate space for the deformed vertices + SkPoint* deformed = outerAlloc.makeArray(vertexCount); + + // deform the vertices + if (boneIndices && boneWeights) { + for (int i = 0; i < vertexCount; i ++) { + const SkVertices::BoneIndices& indices = boneIndices[i]; + const SkVertices::BoneWeights& weights = boneWeights[i]; + + // apply the world transform + SkPoint worldPoint = bones[0].mapPoint(vertices[i]); + + // apply bone deformations + deformed[i] = SkPoint::Make(0.0f, 0.0f); + for (uint32_t j = 0; j < 4; j ++) { + // get the attachment data + uint32_t index = indices[j]; + float weight = weights[j]; + + // skip the bone if there is no weight + if (weight == 0.0f) { + continue; + } + SkASSERT(index != 0); + + // deformed += M * v * w + deformed[i] += bones[index].mapPoint(worldPoint) * weight; + } + } + } else { + // no bones, so only apply world transform + SkMatrix worldTransform = SkMatrix::I(); + worldTransform.setAffine(bones[0].values); + worldTransform.mapPoints(deformed, vertices, vertexCount); + } + + // change the vertices to point to deformed + vertices = deformed; + } + + SkPoint* devVerts = outerAlloc.makeArray(vertexCount); + fMatrix->mapPoints(devVerts, vertices, vertexCount); + + { + SkRect bounds; + // this also sets bounds to empty if we see a non-finite value + bounds.setBounds(devVerts, vertexCount); + if (bounds.isEmpty()) { + return; + } + } + + VertState state(vertexCount, indices, indexCount); + VertState::Proc vertProc = state.chooseProc(vmode); + + if (!(colors || textures)) { + // no colors[] and no texture, stroke hairlines with paint's color. + SkPaint p; + p.setStyle(SkPaint::kStroke_Style); + SkAutoBlitterChoose blitter(*this, nullptr, p); + // Abort early if we failed to create a shader context. + if (blitter->isNullBlitter()) { + return; + } + SkScan::HairRCProc hairProc = ChooseHairProc(paint.isAntiAlias()); + const SkRasterClip& clip = *fRC; + while (vertProc(&state)) { + SkPoint array[] = { + devVerts[state.f0], devVerts[state.f1], devVerts[state.f2], devVerts[state.f0] + }; + hairProc(array, 4, clip, blitter.get()); + } + return; + } + + SkTriColorShader* triShader = nullptr; + SkPMColor4f* dstColors = nullptr; + + if (colors) { + dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), &outerAlloc); + triShader = outerAlloc.make(compute_is_opaque(colors, vertexCount)); + if (shader) { + shader = outerAlloc.make(bmode, + sk_ref_sp(triShader), sk_ref_sp(shader), + nullptr); + } else { + shader = triShader; + } + } + + SkPaint p(paint); + p.setShader(sk_ref_sp(shader)); + + if (!textures) { // only tricolor shader + auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *fMatrix, &outerAlloc); + while (vertProc(&state)) { + if (!triShader->update(ctmInv, vertices, dstColors, state.f0, state.f1, state.f2)) { + continue; + } + + SkPoint tmp[] = { + devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] + }; + SkScan::FillTriangle(tmp, *fRC, blitter); + } + return; + } + + SkRasterPipeline pipeline(&outerAlloc); + SkStageRec rec = { + &pipeline, &outerAlloc, fDst.colorType(), fDst.colorSpace(), p, nullptr, *fMatrix + }; + if (auto updater = as_SB(shader)->appendUpdatableStages(rec)) { + bool isOpaque = shader->isOpaque(); + if (triShader) { + isOpaque = false; // unless we want to walk all the colors, and see if they are + // all opaque (and the blendmode will keep them that way + } + + auto blitter = SkCreateRasterPipelineBlitter(fDst, p, pipeline, isOpaque, &outerAlloc); + while (vertProc(&state)) { + if (triShader && !triShader->update(ctmInv, vertices, dstColors, + state.f0, state.f1, state.f2)) { + continue; + } + + SkMatrix localM; + if (!texture_to_matrix(state, vertices, textures, &localM) || + !updater->update(*fMatrix, &localM)) { + continue; + } + + SkPoint tmp[] = { + devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] + }; + SkScan::FillTriangle(tmp, *fRC, blitter); + } + } else { + // must rebuild pipeline for each triangle, to pass in the computed ctm + while (vertProc(&state)) { + if (triShader && !triShader->update(ctmInv, vertices, dstColors, + state.f0, state.f1, state.f2)) { + continue; + } + + SkSTArenaAlloc<2048> innerAlloc; + + const SkMatrix* ctm = fMatrix; + SkMatrix tmpCtm; + if (textures) { + SkMatrix localM; + if (!texture_to_matrix(state, vertices, textures, &localM)) { + continue; + } + tmpCtm = SkMatrix::Concat(*fMatrix, localM); + ctm = &tmpCtm; + } + + SkPoint tmp[] = { + devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] + }; + auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *ctm, &innerAlloc); + SkScan::FillTriangle(tmp, *fRC, blitter); + } + } +} diff --git a/gfx/skia/skia/src/core/SkDrawable.cpp b/gfx/skia/skia/src/core/SkDrawable.cpp new file mode 100644 index 0000000000..62b0272972 --- /dev/null +++ b/gfx/skia/skia/src/core/SkDrawable.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkDrawable.h" +#include + +static int32_t next_generation_id() { + static std::atomic nextID{1}; + + int32_t id; + do { + id = nextID++; + } while (id == 0); + return id; +} + +SkDrawable::SkDrawable() : fGenerationID(0) {} + +static void draw_bbox(SkCanvas* canvas, const SkRect& r) { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(0xFFFF7088); + canvas->drawRect(r, paint); + canvas->drawLine(r.left(), r.top(), r.right(), r.bottom(), paint); + canvas->drawLine(r.left(), r.bottom(), r.right(), r.top(), paint); +} + +void SkDrawable::draw(SkCanvas* canvas, const SkMatrix* matrix) { + SkAutoCanvasRestore acr(canvas, true); + if (matrix) { + canvas->concat(*matrix); + } + this->onDraw(canvas); + + if (false) { + draw_bbox(canvas, this->getBounds()); + } +} + +void SkDrawable::draw(SkCanvas* canvas, SkScalar x, SkScalar y) { + SkMatrix matrix = SkMatrix::MakeTrans(x, y); + this->draw(canvas, &matrix); +} + +SkPicture* SkDrawable::newPictureSnapshot() { + return this->onNewPictureSnapshot(); +} + +uint32_t SkDrawable::getGenerationID() { + if (0 == fGenerationID) { + fGenerationID = next_generation_id(); + } + return fGenerationID; +} + +SkRect SkDrawable::getBounds() { + return this->onGetBounds(); +} + +void SkDrawable::notifyDrawingChanged() { + fGenerationID = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkPictureRecorder.h" + +SkPicture* SkDrawable::onNewPictureSnapshot() { + SkPictureRecorder recorder; + + const SkRect bounds = this->getBounds(); + SkCanvas* canvas = recorder.beginRecording(bounds, nullptr, 0); + this->draw(canvas); + if (false) { + draw_bbox(canvas, bounds); + } + return recorder.finishRecordingAsPicture().release(); +} diff --git a/gfx/skia/skia/src/core/SkEdge.cpp b/gfx/skia/skia/src/core/SkEdge.cpp new file mode 100644 index 0000000000..0aa3b618ad --- /dev/null +++ b/gfx/skia/skia/src/core/SkEdge.cpp @@ -0,0 +1,503 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkEdge.h" + +#include "include/private/SkTo.h" +#include "src/core/SkFDot6.h" +#include "src/core/SkMathPriv.h" + +#include + +/* + In setLine, setQuadratic, setCubic, the first thing we do is to convert + the points into FDot6. This is modulated by the shift parameter, which + will either be 0, or something like 2 for antialiasing. + + In the float case, we want to turn the float into .6 by saying pt * 64, + or pt * 256 for antialiasing. This is implemented as 1 << (shift + 6). + + In the fixed case, we want to turn the fixed into .6 by saying pt >> 10, + or pt >> 8 for antialiasing. This is implemented as pt >> (10 - shift). +*/ + +static inline SkFixed SkFDot6ToFixedDiv2(SkFDot6 value) { + // we want to return SkFDot6ToFixed(value >> 1), but we don't want to throw + // away data in value, so just perform a modify up-shift + return SkLeftShift(value, 16 - 6 - 1); +} + +///////////////////////////////////////////////////////////////////////// + +int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, const SkIRect* clip, + int shift) { + SkFDot6 x0, y0, x1, y1; + + { +#ifdef SK_RASTERIZE_EVEN_ROUNDING + x0 = SkScalarRoundToFDot6(p0.fX, shift); + y0 = SkScalarRoundToFDot6(p0.fY, shift); + x1 = SkScalarRoundToFDot6(p1.fX, shift); + y1 = SkScalarRoundToFDot6(p1.fY, shift); +#else + float scale = float(1 << (shift + 6)); + x0 = int(p0.fX * scale); + y0 = int(p0.fY * scale); + x1 = int(p1.fX * scale); + y1 = int(p1.fY * scale); +#endif + } + + int winding = 1; + + if (y0 > y1) { + using std::swap; + swap(x0, x1); + swap(y0, y1); + winding = -1; + } + + int top = SkFDot6Round(y0); + int bot = SkFDot6Round(y1); + + // are we a zero-height line? + if (top == bot) { + return 0; + } + // are we completely above or below the clip? + if (clip && (top >= clip->fBottom || bot <= clip->fTop)) { + return 0; + } + + SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0); + const SkFDot6 dy = SkEdge_Compute_DY(top, y0); + + fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2 + fDX = slope; + fFirstY = top; + fLastY = bot - 1; + fCurveCount = 0; + fWinding = SkToS8(winding); + fCurveShift = 0; + + if (clip) { + this->chopLineWithClip(*clip); + } + return 1; +} + +// called from a curve subclass +int SkEdge::updateLine(SkFixed x0, SkFixed y0, SkFixed x1, SkFixed y1) +{ + SkASSERT(fWinding == 1 || fWinding == -1); + SkASSERT(fCurveCount != 0); +// SkASSERT(fCurveShift != 0); + + y0 >>= 10; + y1 >>= 10; + + SkASSERT(y0 <= y1); + + int top = SkFDot6Round(y0); + int bot = SkFDot6Round(y1); + +// SkASSERT(top >= fFirstY); + + // are we a zero-height line? + if (top == bot) + return 0; + + x0 >>= 10; + x1 >>= 10; + + SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0); + const SkFDot6 dy = SkEdge_Compute_DY(top, y0); + + fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2 + fDX = slope; + fFirstY = top; + fLastY = bot - 1; + + return 1; +} + +void SkEdge::chopLineWithClip(const SkIRect& clip) +{ + int top = fFirstY; + + SkASSERT(top < clip.fBottom); + + // clip the line to the top + if (top < clip.fTop) + { + SkASSERT(fLastY >= clip.fTop); + fX += fDX * (clip.fTop - top); + fFirstY = clip.fTop; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +/* We store 1< dy) + dx += dy >> 1; + else + dx = dy + (dx >> 1); + return dx; +} + +static inline int diff_to_shift(SkFDot6 dx, SkFDot6 dy, int shiftAA = 2) +{ + // cheap calc of distance from center of p0-p2 to the center of the curve + SkFDot6 dist = cheap_distance(dx, dy); + + // shift down dist (it is currently in dot6) + // down by 3 should give us 1/8 pixel accuracy (assuming our dist is accurate...) + // this is chosen by heuristic: make it as big as possible (to minimize segments) + // ... but small enough so that our curves still look smooth + // When shift > 0, we're using AA and everything is scaled up so we can + // lower the accuracy. + dist = (dist + (1 << 4)) >> (3 + shiftAA); + + // each subdivision (shift value) cuts this dist (error) by 1/4 + return (32 - SkCLZ(dist)) >> 1; +} + +bool SkQuadraticEdge::setQuadraticWithoutUpdate(const SkPoint pts[3], int shift) { + SkFDot6 x0, y0, x1, y1, x2, y2; + + { +#ifdef SK_RASTERIZE_EVEN_ROUNDING + x0 = SkScalarRoundToFDot6(pts[0].fX, shift); + y0 = SkScalarRoundToFDot6(pts[0].fY, shift); + x1 = SkScalarRoundToFDot6(pts[1].fX, shift); + y1 = SkScalarRoundToFDot6(pts[1].fY, shift); + x2 = SkScalarRoundToFDot6(pts[2].fX, shift); + y2 = SkScalarRoundToFDot6(pts[2].fY, shift); +#else + float scale = float(1 << (shift + 6)); + x0 = int(pts[0].fX * scale); + y0 = int(pts[0].fY * scale); + x1 = int(pts[1].fX * scale); + y1 = int(pts[1].fY * scale); + x2 = int(pts[2].fX * scale); + y2 = int(pts[2].fY * scale); +#endif + } + + int winding = 1; + if (y0 > y2) + { + using std::swap; + swap(x0, x2); + swap(y0, y2); + winding = -1; + } + SkASSERT(y0 <= y1 && y1 <= y2); + + int top = SkFDot6Round(y0); + int bot = SkFDot6Round(y2); + + // are we a zero-height quad (line)? + if (top == bot) + return 0; + + // compute number of steps needed (1 << shift) + { + SkFDot6 dx = (SkLeftShift(x1, 1) - x0 - x2) >> 2; + SkFDot6 dy = (SkLeftShift(y1, 1) - y0 - y2) >> 2; + // This is a little confusing: + // before this line, shift is the scale up factor for AA; + // after this line, shift is the fCurveShift. + shift = diff_to_shift(dx, dy, shift); + SkASSERT(shift >= 0); + } + // need at least 1 subdivision for our bias trick + if (shift == 0) { + shift = 1; + } else if (shift > MAX_COEFF_SHIFT) { + shift = MAX_COEFF_SHIFT; + } + + fWinding = SkToS8(winding); + //fCubicDShift only set for cubics + fCurveCount = SkToS8(1 << shift); + + /* + * We want to reformulate into polynomial form, to make it clear how we + * should forward-difference. + * + * p0 (1 - t)^2 + p1 t(1 - t) + p2 t^2 ==> At^2 + Bt + C + * + * A = p0 - 2p1 + p2 + * B = 2(p1 - p0) + * C = p0 + * + * Our caller must have constrained our inputs (p0..p2) to all fit into + * 16.16. However, as seen above, we sometimes compute values that can be + * larger (e.g. B = 2*(p1 - p0)). To guard against overflow, we will store + * A and B at 1/2 of their actual value, and just apply a 2x scale during + * application in updateQuadratic(). Hence we store (shift - 1) in + * fCurveShift. + */ + + fCurveShift = SkToU8(shift - 1); + + SkFixed A = SkFDot6ToFixedDiv2(x0 - x1 - x1 + x2); // 1/2 the real value + SkFixed B = SkFDot6ToFixed(x1 - x0); // 1/2 the real value + + fQx = SkFDot6ToFixed(x0); + fQDx = B + (A >> shift); // biased by shift + fQDDx = A >> (shift - 1); // biased by shift + + A = SkFDot6ToFixedDiv2(y0 - y1 - y1 + y2); // 1/2 the real value + B = SkFDot6ToFixed(y1 - y0); // 1/2 the real value + + fQy = SkFDot6ToFixed(y0); + fQDy = B + (A >> shift); // biased by shift + fQDDy = A >> (shift - 1); // biased by shift + + fQLastX = SkFDot6ToFixed(x2); + fQLastY = SkFDot6ToFixed(y2); + + return true; +} + +int SkQuadraticEdge::setQuadratic(const SkPoint pts[3], int shift) { + if (!setQuadraticWithoutUpdate(pts, shift)) { + return 0; + } + return this->updateQuadratic(); +} + +int SkQuadraticEdge::updateQuadratic() +{ + int success; + int count = fCurveCount; + SkFixed oldx = fQx; + SkFixed oldy = fQy; + SkFixed dx = fQDx; + SkFixed dy = fQDy; + SkFixed newx, newy; + int shift = fCurveShift; + + SkASSERT(count > 0); + + do { + if (--count > 0) + { + newx = oldx + (dx >> shift); + dx += fQDDx; + newy = oldy + (dy >> shift); + dy += fQDDy; + } + else // last segment + { + newx = fQLastX; + newy = fQLastY; + } + success = this->updateLine(oldx, oldy, newx, newy); + oldx = newx; + oldy = newy; + } while (count > 0 && !success); + + fQx = newx; + fQy = newy; + fQDx = dx; + fQDy = dy; + fCurveCount = SkToS8(count); + return success; +} + +///////////////////////////////////////////////////////////////////////// + +static inline int SkFDot6UpShift(SkFDot6 x, int upShift) { + SkASSERT((SkLeftShift(x, upShift) >> upShift) == x); + return SkLeftShift(x, upShift); +} + +/* f(1/3) = (8a + 12b + 6c + d) / 27 + f(2/3) = (a + 6b + 12c + 8d) / 27 + + f(1/3)-b = (8a - 15b + 6c + d) / 27 + f(2/3)-c = (a + 6b - 15c + 8d) / 27 + + use 16/512 to approximate 1/27 +*/ +static SkFDot6 cubic_delta_from_line(SkFDot6 a, SkFDot6 b, SkFDot6 c, SkFDot6 d) +{ + // since our parameters may be negative, we don't use << to avoid ASAN warnings + SkFDot6 oneThird = (a*8 - b*15 + 6*c + d) * 19 >> 9; + SkFDot6 twoThird = (a + 6*b - c*15 + d*8) * 19 >> 9; + + return SkMax32(SkAbs32(oneThird), SkAbs32(twoThird)); +} + +bool SkCubicEdge::setCubicWithoutUpdate(const SkPoint pts[4], int shift, bool sortY) { + SkFDot6 x0, y0, x1, y1, x2, y2, x3, y3; + + { +#ifdef SK_RASTERIZE_EVEN_ROUNDING + x0 = SkScalarRoundToFDot6(pts[0].fX, shift); + y0 = SkScalarRoundToFDot6(pts[0].fY, shift); + x1 = SkScalarRoundToFDot6(pts[1].fX, shift); + y1 = SkScalarRoundToFDot6(pts[1].fY, shift); + x2 = SkScalarRoundToFDot6(pts[2].fX, shift); + y2 = SkScalarRoundToFDot6(pts[2].fY, shift); + x3 = SkScalarRoundToFDot6(pts[3].fX, shift); + y3 = SkScalarRoundToFDot6(pts[3].fY, shift); +#else + float scale = float(1 << (shift + 6)); + x0 = int(pts[0].fX * scale); + y0 = int(pts[0].fY * scale); + x1 = int(pts[1].fX * scale); + y1 = int(pts[1].fY * scale); + x2 = int(pts[2].fX * scale); + y2 = int(pts[2].fY * scale); + x3 = int(pts[3].fX * scale); + y3 = int(pts[3].fY * scale); +#endif + } + + int winding = 1; + if (sortY && y0 > y3) + { + using std::swap; + swap(x0, x3); + swap(x1, x2); + swap(y0, y3); + swap(y1, y2); + winding = -1; + } + + int top = SkFDot6Round(y0); + int bot = SkFDot6Round(y3); + + // are we a zero-height cubic (line)? + if (sortY && top == bot) + return 0; + + // compute number of steps needed (1 << shift) + { + // Can't use (center of curve - center of baseline), since center-of-curve + // need not be the max delta from the baseline (it could even be coincident) + // so we try just looking at the two off-curve points + SkFDot6 dx = cubic_delta_from_line(x0, x1, x2, x3); + SkFDot6 dy = cubic_delta_from_line(y0, y1, y2, y3); + // add 1 (by observation) + shift = diff_to_shift(dx, dy) + 1; + } + // need at least 1 subdivision for our bias trick + SkASSERT(shift > 0); + if (shift > MAX_COEFF_SHIFT) { + shift = MAX_COEFF_SHIFT; + } + + /* Since our in coming data is initially shifted down by 10 (or 8 in + antialias). That means the most we can shift up is 8. However, we + compute coefficients with a 3*, so the safest upshift is really 6 + */ + int upShift = 6; // largest safe value + int downShift = shift + upShift - 10; + if (downShift < 0) { + downShift = 0; + upShift = 10 - shift; + } + + fWinding = SkToS8(winding); + fCurveCount = SkToS8(SkLeftShift(-1, shift)); + fCurveShift = SkToU8(shift); + fCubicDShift = SkToU8(downShift); + + SkFixed B = SkFDot6UpShift(3 * (x1 - x0), upShift); + SkFixed C = SkFDot6UpShift(3 * (x0 - x1 - x1 + x2), upShift); + SkFixed D = SkFDot6UpShift(x3 + 3 * (x1 - x2) - x0, upShift); + + fCx = SkFDot6ToFixed(x0); + fCDx = B + (C >> shift) + (D >> 2*shift); // biased by shift + fCDDx = 2*C + (3*D >> (shift - 1)); // biased by 2*shift + fCDDDx = 3*D >> (shift - 1); // biased by 2*shift + + B = SkFDot6UpShift(3 * (y1 - y0), upShift); + C = SkFDot6UpShift(3 * (y0 - y1 - y1 + y2), upShift); + D = SkFDot6UpShift(y3 + 3 * (y1 - y2) - y0, upShift); + + fCy = SkFDot6ToFixed(y0); + fCDy = B + (C >> shift) + (D >> 2*shift); // biased by shift + fCDDy = 2*C + (3*D >> (shift - 1)); // biased by 2*shift + fCDDDy = 3*D >> (shift - 1); // biased by 2*shift + + fCLastX = SkFDot6ToFixed(x3); + fCLastY = SkFDot6ToFixed(y3); + + return true; +} + +int SkCubicEdge::setCubic(const SkPoint pts[4], int shift) { + if (!this->setCubicWithoutUpdate(pts, shift)) { + return 0; + } + return this->updateCubic(); +} + +int SkCubicEdge::updateCubic() +{ + int success; + int count = fCurveCount; + SkFixed oldx = fCx; + SkFixed oldy = fCy; + SkFixed newx, newy; + const int ddshift = fCurveShift; + const int dshift = fCubicDShift; + + SkASSERT(count < 0); + + do { + if (++count < 0) + { + newx = oldx + (fCDx >> dshift); + fCDx += fCDDx >> ddshift; + fCDDx += fCDDDx; + + newy = oldy + (fCDy >> dshift); + fCDy += fCDDy >> ddshift; + fCDDy += fCDDDy; + } + else // last segment + { + // SkDebugf("LastX err=%d, LastY err=%d\n", (oldx + (fCDx >> shift) - fLastX), (oldy + (fCDy >> shift) - fLastY)); + newx = fCLastX; + newy = fCLastY; + } + + // we want to say SkASSERT(oldy <= newy), but our finite fixedpoint + // doesn't always achieve that, so we have to explicitly pin it here. + if (newy < oldy) { + newy = oldy; + } + + success = this->updateLine(oldx, oldy, newx, newy); + oldx = newx; + oldy = newy; + } while (count < 0 && !success); + + fCx = newx; + fCy = newy; + fCurveCount = SkToS8(count); + return success; +} diff --git a/gfx/skia/skia/src/core/SkEdge.h b/gfx/skia/skia/src/core/SkEdge.h new file mode 100644 index 0000000000..c0c26ca0c7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEdge.h @@ -0,0 +1,138 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEdge_DEFINED +#define SkEdge_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkRect.h" +#include "include/private/SkTo.h" +#include "src/core/SkFDot6.h" + +#include + +// This correctly favors the lower-pixel when y0 is on a 1/2 pixel boundary +#define SkEdge_Compute_DY(top, y0) (SkLeftShift(top, 6) + 32 - (y0)) + +struct SkEdge { + enum Type { + kLine_Type, + kQuad_Type, + kCubic_Type + }; + + SkEdge* fNext; + SkEdge* fPrev; + + SkFixed fX; + SkFixed fDX; + int32_t fFirstY; + int32_t fLastY; + int8_t fCurveCount; // only used by kQuad(+) and kCubic(-) + uint8_t fCurveShift; // appled to all Dx/DDx/DDDx except for fCubicDShift exception + uint8_t fCubicDShift; // applied to fCDx and fCDy only in cubic + int8_t fWinding; // 1 or -1 + + int setLine(const SkPoint& p0, const SkPoint& p1, const SkIRect* clip, int shiftUp); + // call this version if you know you don't have a clip + inline int setLine(const SkPoint& p0, const SkPoint& p1, int shiftUp); + inline int updateLine(SkFixed ax, SkFixed ay, SkFixed bx, SkFixed by); + void chopLineWithClip(const SkIRect& clip); + + inline bool intersectsClip(const SkIRect& clip) const { + SkASSERT(fFirstY < clip.fBottom); + return fLastY >= clip.fTop; + } + +#ifdef SK_DEBUG + void dump() const { + SkDebugf("edge: firstY:%d lastY:%d x:%g dx:%g w:%d\n", fFirstY, fLastY, SkFixedToFloat(fX), SkFixedToFloat(fDX), fWinding); + } + + void validate() const { + SkASSERT(fPrev && fNext); + SkASSERT(fPrev->fNext == this); + SkASSERT(fNext->fPrev == this); + + SkASSERT(fFirstY <= fLastY); + SkASSERT(SkAbs32(fWinding) == 1); + } +#endif +}; + +struct SkQuadraticEdge : public SkEdge { + SkFixed fQx, fQy; + SkFixed fQDx, fQDy; + SkFixed fQDDx, fQDDy; + SkFixed fQLastX, fQLastY; + + bool setQuadraticWithoutUpdate(const SkPoint pts[3], int shiftUp); + int setQuadratic(const SkPoint pts[3], int shiftUp); + int updateQuadratic(); +}; + +struct SkCubicEdge : public SkEdge { + SkFixed fCx, fCy; + SkFixed fCDx, fCDy; + SkFixed fCDDx, fCDDy; + SkFixed fCDDDx, fCDDDy; + SkFixed fCLastX, fCLastY; + + bool setCubicWithoutUpdate(const SkPoint pts[4], int shiftUp, bool sortY = true); + int setCubic(const SkPoint pts[4], int shiftUp); + int updateCubic(); +}; + +int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, int shift) { + SkFDot6 x0, y0, x1, y1; + + { +#ifdef SK_RASTERIZE_EVEN_ROUNDING + x0 = SkScalarRoundToFDot6(p0.fX, shift); + y0 = SkScalarRoundToFDot6(p0.fY, shift); + x1 = SkScalarRoundToFDot6(p1.fX, shift); + y1 = SkScalarRoundToFDot6(p1.fY, shift); +#else + float scale = float(1 << (shift + 6)); + x0 = int(p0.fX * scale); + y0 = int(p0.fY * scale); + x1 = int(p1.fX * scale); + y1 = int(p1.fY * scale); +#endif + } + + int winding = 1; + + if (y0 > y1) { + using std::swap; + swap(x0, x1); + swap(y0, y1); + winding = -1; + } + + int top = SkFDot6Round(y0); + int bot = SkFDot6Round(y1); + + // are we a zero-height line? + if (top == bot) { + return 0; + } + + SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0); + const SkFDot6 dy = SkEdge_Compute_DY(top, y0); + + fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2 + fDX = slope; + fFirstY = top; + fLastY = bot - 1; + fCurveCount = 0; + fWinding = SkToS8(winding); + fCurveShift = 0; + return 1; +} + +#endif diff --git a/gfx/skia/skia/src/core/SkEdgeBuilder.cpp b/gfx/skia/skia/src/core/SkEdgeBuilder.cpp new file mode 100644 index 0000000000..721217d905 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEdgeBuilder.cpp @@ -0,0 +1,405 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "include/private/SkTo.h" +#include "src/core/SkAnalyticEdge.h" +#include "src/core/SkEdge.h" +#include "src/core/SkEdgeBuilder.h" +#include "src/core/SkEdgeClipper.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkLineClipper.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkSafeMath.h" + +SkEdgeBuilder::Combine SkBasicEdgeBuilder::combineVertical(const SkEdge* edge, SkEdge* last) { + if (last->fCurveCount || last->fDX || edge->fX != last->fX) { + return kNo_Combine; + } + if (edge->fWinding == last->fWinding) { + if (edge->fLastY + 1 == last->fFirstY) { + last->fFirstY = edge->fFirstY; + return kPartial_Combine; + } + if (edge->fFirstY == last->fLastY + 1) { + last->fLastY = edge->fLastY; + return kPartial_Combine; + } + return kNo_Combine; + } + if (edge->fFirstY == last->fFirstY) { + if (edge->fLastY == last->fLastY) { + return kTotal_Combine; + } + if (edge->fLastY < last->fLastY) { + last->fFirstY = edge->fLastY + 1; + return kPartial_Combine; + } + last->fFirstY = last->fLastY + 1; + last->fLastY = edge->fLastY; + last->fWinding = edge->fWinding; + return kPartial_Combine; + } + if (edge->fLastY == last->fLastY) { + if (edge->fFirstY > last->fFirstY) { + last->fLastY = edge->fFirstY - 1; + return kPartial_Combine; + } + last->fLastY = last->fFirstY - 1; + last->fFirstY = edge->fFirstY; + last->fWinding = edge->fWinding; + return kPartial_Combine; + } + return kNo_Combine; +} + +SkEdgeBuilder::Combine SkAnalyticEdgeBuilder::combineVertical(const SkAnalyticEdge* edge, + SkAnalyticEdge* last) { + auto approximately_equal = [](SkFixed a, SkFixed b) { + return SkAbs32(a - b) < 0x100; + }; + + if (last->fCurveCount || last->fDX || edge->fX != last->fX) { + return kNo_Combine; + } + if (edge->fWinding == last->fWinding) { + if (edge->fLowerY == last->fUpperY) { + last->fUpperY = edge->fUpperY; + last->fY = last->fUpperY; + return kPartial_Combine; + } + if (approximately_equal(edge->fUpperY, last->fLowerY)) { + last->fLowerY = edge->fLowerY; + return kPartial_Combine; + } + return kNo_Combine; + } + if (approximately_equal(edge->fUpperY, last->fUpperY)) { + if (approximately_equal(edge->fLowerY, last->fLowerY)) { + return kTotal_Combine; + } + if (edge->fLowerY < last->fLowerY) { + last->fUpperY = edge->fLowerY; + last->fY = last->fUpperY; + return kPartial_Combine; + } + last->fUpperY = last->fLowerY; + last->fY = last->fUpperY; + last->fLowerY = edge->fLowerY; + last->fWinding = edge->fWinding; + return kPartial_Combine; + } + if (approximately_equal(edge->fLowerY, last->fLowerY)) { + if (edge->fUpperY > last->fUpperY) { + last->fLowerY = edge->fUpperY; + return kPartial_Combine; + } + last->fLowerY = last->fUpperY; + last->fUpperY = edge->fUpperY; + last->fY = last->fUpperY; + last->fWinding = edge->fWinding; + return kPartial_Combine; + } + return kNo_Combine; +} + +template +static bool is_vertical(const Edge* edge) { + return edge->fDX == 0 + && edge->fCurveCount == 0; +} + +// TODO: we can deallocate the edge if edge->setFoo() fails +// or when we don't use it (kPartial_Combine or kTotal_Combine). + +void SkBasicEdgeBuilder::addLine(const SkPoint pts[]) { + SkEdge* edge = fAlloc.make(); + if (edge->setLine(pts[0], pts[1], fClipShift)) { + Combine combine = is_vertical(edge) && !fList.empty() + ? this->combineVertical(edge, (SkEdge*)fList.top()) + : kNo_Combine; + + switch (combine) { + case kTotal_Combine: fList.pop(); break; + case kPartial_Combine: break; + case kNo_Combine: fList.push_back(edge); break; + } + } +} +void SkAnalyticEdgeBuilder::addLine(const SkPoint pts[]) { + SkAnalyticEdge* edge = fAlloc.make(); + if (edge->setLine(pts[0], pts[1])) { + + Combine combine = is_vertical(edge) && !fList.empty() + ? this->combineVertical(edge, (SkAnalyticEdge*)fList.top()) + : kNo_Combine; + + switch (combine) { + case kTotal_Combine: fList.pop(); break; + case kPartial_Combine: break; + case kNo_Combine: fList.push_back(edge); break; + } + } +} +void SkBasicEdgeBuilder::addQuad(const SkPoint pts[]) { + SkQuadraticEdge* edge = fAlloc.make(); + if (edge->setQuadratic(pts, fClipShift)) { + fList.push_back(edge); + } +} +void SkAnalyticEdgeBuilder::addQuad(const SkPoint pts[]) { + SkAnalyticQuadraticEdge* edge = fAlloc.make(); + if (edge->setQuadratic(pts)) { + fList.push_back(edge); + } +} + +void SkBasicEdgeBuilder::addCubic(const SkPoint pts[]) { + SkCubicEdge* edge = fAlloc.make(); + if (edge->setCubic(pts, fClipShift)) { + fList.push_back(edge); + } +} +void SkAnalyticEdgeBuilder::addCubic(const SkPoint pts[]) { + SkAnalyticCubicEdge* edge = fAlloc.make(); + if (edge->setCubic(pts)) { + fList.push_back(edge); + } +} + +// TODO: merge addLine() and addPolyLine()? + +SkEdgeBuilder::Combine SkBasicEdgeBuilder::addPolyLine(const SkPoint pts[], + char* arg_edge, char** arg_edgePtr) { + auto edge = (SkEdge*) arg_edge; + auto edgePtr = (SkEdge**)arg_edgePtr; + + if (edge->setLine(pts[0], pts[1], fClipShift)) { + return is_vertical(edge) && edgePtr > (SkEdge**)fEdgeList + ? this->combineVertical(edge, edgePtr[-1]) + : kNo_Combine; + } + return SkEdgeBuilder::kPartial_Combine; // A convenient lie. Same do-nothing behavior. +} +SkEdgeBuilder::Combine SkAnalyticEdgeBuilder::addPolyLine(const SkPoint pts[], + char* arg_edge, char** arg_edgePtr) { + auto edge = (SkAnalyticEdge*) arg_edge; + auto edgePtr = (SkAnalyticEdge**)arg_edgePtr; + + if (edge->setLine(pts[0], pts[1])) { + return is_vertical(edge) && edgePtr > (SkAnalyticEdge**)fEdgeList + ? this->combineVertical(edge, edgePtr[-1]) + : kNo_Combine; + } + return SkEdgeBuilder::kPartial_Combine; // As above. +} + +SkRect SkBasicEdgeBuilder::recoverClip(const SkIRect& src) const { + return { SkIntToScalar(src.fLeft >> fClipShift), + SkIntToScalar(src.fTop >> fClipShift), + SkIntToScalar(src.fRight >> fClipShift), + SkIntToScalar(src.fBottom >> fClipShift), }; +} +SkRect SkAnalyticEdgeBuilder::recoverClip(const SkIRect& src) const { + return SkRect::Make(src); +} + +char* SkBasicEdgeBuilder::allocEdges(size_t n, size_t* size) { + *size = sizeof(SkEdge); + return (char*)fAlloc.makeArrayDefault(n); +} +char* SkAnalyticEdgeBuilder::allocEdges(size_t n, size_t* size) { + *size = sizeof(SkAnalyticEdge); + return (char*)fAlloc.makeArrayDefault(n); +} + +// TODO: maybe get rid of buildPoly() entirely? +int SkEdgeBuilder::buildPoly(const SkPath& path, const SkIRect* iclip, bool canCullToTheRight) { + size_t maxEdgeCount = path.countPoints(); + if (iclip) { + // clipping can turn 1 line into (up to) kMaxClippedLineSegments, since + // we turn portions that are clipped out on the left/right into vertical + // segments. + SkSafeMath safe; + maxEdgeCount = safe.mul(maxEdgeCount, SkLineClipper::kMaxClippedLineSegments); + if (!safe) { + return 0; + } + } + + size_t edgeSize; + char* edge = this->allocEdges(maxEdgeCount, &edgeSize); + + SkDEBUGCODE(char* edgeStart = edge); + char** edgePtr = fAlloc.makeArrayDefault(maxEdgeCount); + fEdgeList = (void**)edgePtr; + + SkPathEdgeIter iter(path); + if (iclip) { + SkRect clip = this->recoverClip(*iclip); + + while (auto e = iter.next()) { + switch (e.fEdge) { + case SkPathEdgeIter::Edge::kLine: { + SkPoint lines[SkLineClipper::kMaxPoints]; + int lineCount = SkLineClipper::ClipLine(e.fPts, clip, lines, canCullToTheRight); + SkASSERT(lineCount <= SkLineClipper::kMaxClippedLineSegments); + for (int i = 0; i < lineCount; i++) { + switch( this->addPolyLine(lines + i, edge, edgePtr) ) { + case kTotal_Combine: edgePtr--; break; + case kPartial_Combine: break; + case kNo_Combine: *edgePtr++ = edge; + edge += edgeSize; + } + } + break; + } + default: + SkDEBUGFAIL("unexpected verb"); + break; + } + } + } else { + while (auto e = iter.next()) { + switch (e.fEdge) { + case SkPathEdgeIter::Edge::kLine: { + switch( this->addPolyLine(e.fPts, edge, edgePtr) ) { + case kTotal_Combine: edgePtr--; break; + case kPartial_Combine: break; + case kNo_Combine: *edgePtr++ = edge; + edge += edgeSize; + } + break; + } + default: + SkDEBUGFAIL("unexpected verb"); + break; + } + } + } + SkASSERT((size_t)(edge - edgeStart) <= maxEdgeCount * edgeSize); + SkASSERT((size_t)(edgePtr - (char**)fEdgeList) <= maxEdgeCount); + return SkToInt(edgePtr - (char**)fEdgeList); +} + +int SkEdgeBuilder::build(const SkPath& path, const SkIRect* iclip, bool canCullToTheRight) { + SkAutoConicToQuads quadder; + const SkScalar conicTol = SK_Scalar1 / 4; + bool is_finite = true; + + SkPathEdgeIter iter(path); + if (iclip) { + SkRect clip = this->recoverClip(*iclip); + SkEdgeClipper clipper(canCullToTheRight); + + auto apply_clipper = [this, &clipper, &is_finite] { + SkPoint pts[4]; + SkPath::Verb verb; + + while ((verb = clipper.next(pts)) != SkPath::kDone_Verb) { + const int count = SkPathPriv::PtsInIter(verb); + if (!SkScalarsAreFinite(&pts[0].fX, count*2)) { + is_finite = false; + return; + } + switch (verb) { + case SkPath::kLine_Verb: this->addLine (pts); break; + case SkPath::kQuad_Verb: this->addQuad (pts); break; + case SkPath::kCubic_Verb: this->addCubic(pts); break; + default: break; + } + } + }; + + while (auto e = iter.next()) { + switch (e.fEdge) { + case SkPathEdgeIter::Edge::kLine: + if (clipper.clipLine(e.fPts[0], e.fPts[1], clip)) { + apply_clipper(); + } + break; + case SkPathEdgeIter::Edge::kQuad: + if (clipper.clipQuad(e.fPts, clip)) { + apply_clipper(); + } + break; + case SkPathEdgeIter::Edge::kConic: { + const SkPoint* quadPts = quadder.computeQuads( + e.fPts, iter.conicWeight(), conicTol); + for (int i = 0; i < quadder.countQuads(); ++i) { + if (clipper.clipQuad(quadPts, clip)) { + apply_clipper(); + } + quadPts += 2; + } + } break; + case SkPathEdgeIter::Edge::kCubic: + if (clipper.clipCubic(e.fPts, clip)) { + apply_clipper(); + } + break; + } + } + } else { + auto handle_quad = [this](const SkPoint pts[3]) { + SkPoint monoX[5]; + int n = SkChopQuadAtYExtrema(pts, monoX); + for (int i = 0; i <= n; i++) { + this->addQuad(&monoX[i * 2]); + } + }; + while (auto e = iter.next()) { + switch (e.fEdge) { + case SkPathEdgeIter::Edge::kLine: + this->addLine(e.fPts); + break; + case SkPathEdgeIter::Edge::kQuad: { + handle_quad(e.fPts); + break; + } + case SkPathEdgeIter::Edge::kConic: { + const SkPoint* quadPts = quadder.computeQuads( + e.fPts, iter.conicWeight(), conicTol); + for (int i = 0; i < quadder.countQuads(); ++i) { + handle_quad(quadPts); + quadPts += 2; + } + } break; + case SkPathEdgeIter::Edge::kCubic: { + SkPoint monoY[10]; + int n = SkChopCubicAtYExtrema(e.fPts, monoY); + for (int i = 0; i <= n; i++) { + this->addCubic(&monoY[i * 3]); + } + break; + } + } + } + } + fEdgeList = fList.begin(); + return is_finite ? fList.count() : 0; +} + +int SkEdgeBuilder::buildEdges(const SkPath& path, + const SkIRect* shiftedClip) { + // If we're convex, then we need both edges, even if the right edge is past the clip. + const bool canCullToTheRight = !path.isConvex(); + + // We can use our buildPoly() optimization if all the segments are lines. + // (Edges are homogenous and stored contiguously in memory, no need for indirection.) + const int count = SkPath::kLine_SegmentMask == path.getSegmentMasks() + ? this->buildPoly(path, shiftedClip, canCullToTheRight) + : this->build (path, shiftedClip, canCullToTheRight); + + SkASSERT(count >= 0); + + // If we can't cull to the right, we should have count > 1 (or 0). + if (!canCullToTheRight) { + SkASSERT(count != 1); + } + return count; +} diff --git a/gfx/skia/skia/src/core/SkEdgeBuilder.h b/gfx/skia/skia/src/core/SkEdgeBuilder.h new file mode 100644 index 0000000000..5fb7c62522 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEdgeBuilder.h @@ -0,0 +1,89 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkEdgeBuilder_DEFINED +#define SkEdgeBuilder_DEFINED + +#include "include/core/SkRect.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkAnalyticEdge.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkEdge.h" + +class SkPath; + +class SkEdgeBuilder { +public: + int buildEdges(const SkPath& path, + const SkIRect* shiftedClip); + +protected: + SkEdgeBuilder() = default; + virtual ~SkEdgeBuilder() = default; + + // In general mode we allocate pointers in fList and fEdgeList points to its head. + // In polygon mode we preallocated edges contiguously in fAlloc and fEdgeList points there. + void** fEdgeList = nullptr; + SkTDArray fList; + SkSTArenaAlloc<512> fAlloc; + + enum Combine { + kNo_Combine, + kPartial_Combine, + kTotal_Combine + }; + +private: + int build (const SkPath& path, const SkIRect* clip, bool clipToTheRight); + int buildPoly(const SkPath& path, const SkIRect* clip, bool clipToTheRight); + + virtual char* allocEdges(size_t n, size_t* sizeof_edge) = 0; + virtual SkRect recoverClip(const SkIRect&) const = 0; + + virtual void addLine (const SkPoint pts[]) = 0; + virtual void addQuad (const SkPoint pts[]) = 0; + virtual void addCubic(const SkPoint pts[]) = 0; + virtual Combine addPolyLine(const SkPoint pts[], char* edge, char** edgePtr) = 0; +}; + +class SkBasicEdgeBuilder final : public SkEdgeBuilder { +public: + explicit SkBasicEdgeBuilder(int clipShift) : fClipShift(clipShift) {} + + SkEdge** edgeList() { return (SkEdge**)fEdgeList; } + +private: + Combine combineVertical(const SkEdge* edge, SkEdge* last); + + char* allocEdges(size_t, size_t*) override; + SkRect recoverClip(const SkIRect&) const override; + + void addLine (const SkPoint pts[]) override; + void addQuad (const SkPoint pts[]) override; + void addCubic(const SkPoint pts[]) override; + Combine addPolyLine(const SkPoint pts[], char* edge, char** edgePtr) override; + + const int fClipShift; +}; + +class SkAnalyticEdgeBuilder final : public SkEdgeBuilder { +public: + SkAnalyticEdgeBuilder() {} + + SkAnalyticEdge** analyticEdgeList() { return (SkAnalyticEdge**)fEdgeList; } + +private: + Combine combineVertical(const SkAnalyticEdge* edge, SkAnalyticEdge* last); + + char* allocEdges(size_t, size_t*) override; + SkRect recoverClip(const SkIRect&) const override; + + void addLine (const SkPoint pts[]) override; + void addQuad (const SkPoint pts[]) override; + void addCubic(const SkPoint pts[]) override; + Combine addPolyLine(const SkPoint pts[], char* edge, char** edgePtr) override; +}; +#endif diff --git a/gfx/skia/skia/src/core/SkEdgeClipper.cpp b/gfx/skia/skia/src/core/SkEdgeClipper.cpp new file mode 100644 index 0000000000..fcadd3e986 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEdgeClipper.cpp @@ -0,0 +1,557 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMacros.h" +#include "src/core/SkEdgeClipper.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkLineClipper.h" + +#include + +static bool quick_reject(const SkRect& bounds, const SkRect& clip) { + return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop; +} + +static inline void clamp_le(SkScalar& value, SkScalar max) { + if (value > max) { + value = max; + } +} + +static inline void clamp_ge(SkScalar& value, SkScalar min) { + if (value < min) { + value = min; + } +} + +/* src[] must be monotonic in Y. This routine copies src into dst, and sorts + it to be increasing in Y. If it had to reverse the order of the points, + it returns true, otherwise it returns false + */ +static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) { + // we need the data to be monotonically increasing in Y + if (src[0].fY > src[count - 1].fY) { + for (int i = 0; i < count; i++) { + dst[i] = src[count - i - 1]; + } + return true; + } else { + memcpy(dst, src, count * sizeof(SkPoint)); + return false; + } +} + +bool SkEdgeClipper::clipLine(SkPoint p0, SkPoint p1, const SkRect& clip) { + fCurrPoint = fPoints; + fCurrVerb = fVerbs; + + SkPoint lines[SkLineClipper::kMaxPoints]; + const SkPoint pts[] = { p0, p1 }; + int lineCount = SkLineClipper::ClipLine(pts, clip, lines, fCanCullToTheRight); + for (int i = 0; i < lineCount; i++) { + this->appendLine(lines[i], lines[i + 1]); + } + + *fCurrVerb = SkPath::kDone_Verb; + fCurrPoint = fPoints; + fCurrVerb = fVerbs; + return SkPath::kDone_Verb != fVerbs[0]; +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2, + SkScalar target, SkScalar* t) { + /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2 + * We solve for t, using quadratic equation, hence we have to rearrange + * our cooefficents to look like At^2 + Bt + C + */ + SkScalar A = c0 - c1 - c1 + c2; + SkScalar B = 2*(c1 - c0); + SkScalar C = c0 - target; + + SkScalar roots[2]; // we only expect one, but make room for 2 for safety + int count = SkFindUnitQuadRoots(A, B, C, roots); + if (count) { + *t = roots[0]; + return true; + } + return false; +} + +static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) { + return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t); +} + +static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) { + return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t); +} + +// Modify pts[] in place so that it is clipped in Y to the clip rect +static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) { + SkScalar t; + SkPoint tmp[5]; // for SkChopQuadAt + + // are we partially above + if (pts[0].fY < clip.fTop) { + if (chopMonoQuadAtY(pts, clip.fTop, &t)) { + // take the 2nd chopped quad + SkChopQuadAt(pts, tmp, t); + // clamp to clean up imprecise numerics in the chop + tmp[2].fY = clip.fTop; + clamp_ge(tmp[3].fY, clip.fTop); + + pts[0] = tmp[2]; + pts[1] = tmp[3]; + } else { + // if chopMonoQuadAtY failed, then we may have hit inexact numerics + // so we just clamp against the top + for (int i = 0; i < 3; i++) { + if (pts[i].fY < clip.fTop) { + pts[i].fY = clip.fTop; + } + } + } + } + + // are we partially below + if (pts[2].fY > clip.fBottom) { + if (chopMonoQuadAtY(pts, clip.fBottom, &t)) { + SkChopQuadAt(pts, tmp, t); + // clamp to clean up imprecise numerics in the chop + clamp_le(tmp[1].fY, clip.fBottom); + tmp[2].fY = clip.fBottom; + + pts[1] = tmp[1]; + pts[2] = tmp[2]; + } else { + // if chopMonoQuadAtY failed, then we may have hit inexact numerics + // so we just clamp against the bottom + for (int i = 0; i < 3; i++) { + if (pts[i].fY > clip.fBottom) { + pts[i].fY = clip.fBottom; + } + } + } + } +} + +// srcPts[] must be monotonic in X and Y +void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) { + SkPoint pts[3]; + bool reverse = sort_increasing_Y(pts, srcPts, 3); + + // are we completely above or below + if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { + return; + } + + // Now chop so that pts is contained within clip in Y + chop_quad_in_Y(pts, clip); + + if (pts[0].fX > pts[2].fX) { + using std::swap; + swap(pts[0], pts[2]); + reverse = !reverse; + } + SkASSERT(pts[0].fX <= pts[1].fX); + SkASSERT(pts[1].fX <= pts[2].fX); + + // Now chop in X has needed, and record the segments + + if (pts[2].fX <= clip.fLeft) { // wholly to the left + this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); + return; + } + if (pts[0].fX >= clip.fRight) { // wholly to the right + if (!this->canCullToTheRight()) { + this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); + } + return; + } + + SkScalar t; + SkPoint tmp[5]; // for SkChopQuadAt + + // are we partially to the left + if (pts[0].fX < clip.fLeft) { + if (chopMonoQuadAtX(pts, clip.fLeft, &t)) { + SkChopQuadAt(pts, tmp, t); + this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse); + // clamp to clean up imprecise numerics in the chop + tmp[2].fX = clip.fLeft; + clamp_ge(tmp[3].fX, clip.fLeft); + + pts[0] = tmp[2]; + pts[1] = tmp[3]; + } else { + // if chopMonoQuadAtY failed, then we may have hit inexact numerics + // so we just clamp against the left + this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); + return; + } + } + + // are we partially to the right + if (pts[2].fX > clip.fRight) { + if (chopMonoQuadAtX(pts, clip.fRight, &t)) { + SkChopQuadAt(pts, tmp, t); + // clamp to clean up imprecise numerics in the chop + clamp_le(tmp[1].fX, clip.fRight); + tmp[2].fX = clip.fRight; + + this->appendQuad(tmp, reverse); + this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse); + } else { + // if chopMonoQuadAtY failed, then we may have hit inexact numerics + // so we just clamp against the right + this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); + } + } else { // wholly inside the clip + this->appendQuad(pts, reverse); + } +} + +bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) { + fCurrPoint = fPoints; + fCurrVerb = fVerbs; + + SkRect bounds; + bounds.setBounds(srcPts, 3); + + if (!quick_reject(bounds, clip)) { + SkPoint monoY[5]; + int countY = SkChopQuadAtYExtrema(srcPts, monoY); + for (int y = 0; y <= countY; y++) { + SkPoint monoX[5]; + int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX); + for (int x = 0; x <= countX; x++) { + this->clipMonoQuad(&monoX[x * 2], clip); + SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); + SkASSERT(fCurrPoint - fPoints <= kMaxPoints); + } + } + } + + *fCurrVerb = SkPath::kDone_Verb; + fCurrPoint = fPoints; + fCurrVerb = fVerbs; + return SkPath::kDone_Verb != fVerbs[0]; +} + +/////////////////////////////////////////////////////////////////////////////// + +static SkScalar mono_cubic_closestT(const SkScalar src[], SkScalar x) { + SkScalar t = 0.5f; + SkScalar lastT; + SkScalar bestT SK_INIT_TO_AVOID_WARNING; + SkScalar step = 0.25f; + SkScalar D = src[0]; + SkScalar A = src[6] + 3*(src[2] - src[4]) - D; + SkScalar B = 3*(src[4] - src[2] - src[2] + D); + SkScalar C = 3*(src[2] - D); + x -= D; + SkScalar closest = SK_ScalarMax; + do { + SkScalar loc = ((A * t + B) * t + C) * t; + SkScalar dist = SkScalarAbs(loc - x); + if (closest > dist) { + closest = dist; + bestT = t; + } + lastT = t; + t += loc < x ? step : -step; + step *= 0.5f; + } while (closest > 0.25f && lastT != t); + return bestT; +} + +static void chop_mono_cubic_at_y(SkPoint src[4], SkScalar y, SkPoint dst[7]) { + if (SkChopMonoCubicAtY(src, y, dst)) { + return; + } + SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fY, y)); +} + +// Modify pts[] in place so that it is clipped in Y to the clip rect +static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) { + + // are we partially above + if (pts[0].fY < clip.fTop) { + SkPoint tmp[7]; + chop_mono_cubic_at_y(pts, clip.fTop, tmp); + + /* + * For a large range in the points, we can do a poor job of chopping, such that the t + * we computed resulted in the lower cubic still being partly above the clip. + * + * If just the first or first 2 Y values are above the fTop, we can just smash them + * down. If the first 3 Ys are above fTop, we can't smash all 3, as that can really + * distort the cubic. In this case, we take the first output (tmp[3..6] and treat it as + * a guess, and re-chop against fTop. Then we fall through to checking if we need to + * smash the first 1 or 2 Y values. + */ + if (tmp[3].fY < clip.fTop && tmp[4].fY < clip.fTop && tmp[5].fY < clip.fTop) { + SkPoint tmp2[4]; + memcpy(tmp2, &tmp[3].fX, 4 * sizeof(SkPoint)); + chop_mono_cubic_at_y(tmp2, clip.fTop, tmp); + } + + // tmp[3, 4].fY should all be to the below clip.fTop. + // Since we can't trust the numerics of the chopper, we force those conditions now + tmp[3].fY = clip.fTop; + clamp_ge(tmp[4].fY, clip.fTop); + + pts[0] = tmp[3]; + pts[1] = tmp[4]; + pts[2] = tmp[5]; + } + + // are we partially below + if (pts[3].fY > clip.fBottom) { + SkPoint tmp[7]; + chop_mono_cubic_at_y(pts, clip.fBottom, tmp); + tmp[3].fY = clip.fBottom; + clamp_le(tmp[2].fY, clip.fBottom); + + pts[1] = tmp[1]; + pts[2] = tmp[2]; + pts[3] = tmp[3]; + } +} + +static void chop_mono_cubic_at_x(SkPoint src[4], SkScalar x, SkPoint dst[7]) { + if (SkChopMonoCubicAtX(src, x, dst)) { + return; + } + SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fX, x)); +} + +// srcPts[] must be monotonic in X and Y +void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) { + SkPoint pts[4]; + bool reverse = sort_increasing_Y(pts, src, 4); + + // are we completely above or below + if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { + return; + } + + // Now chop so that pts is contained within clip in Y + chop_cubic_in_Y(pts, clip); + + if (pts[0].fX > pts[3].fX) { + using std::swap; + swap(pts[0], pts[3]); + swap(pts[1], pts[2]); + reverse = !reverse; + } + + // Now chop in X has needed, and record the segments + + if (pts[3].fX <= clip.fLeft) { // wholly to the left + this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse); + return; + } + if (pts[0].fX >= clip.fRight) { // wholly to the right + if (!this->canCullToTheRight()) { + this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse); + } + return; + } + + // are we partially to the left + if (pts[0].fX < clip.fLeft) { + SkPoint tmp[7]; + chop_mono_cubic_at_x(pts, clip.fLeft, tmp); + this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse); + + // tmp[3, 4].fX should all be to the right of clip.fLeft. + // Since we can't trust the numerics of + // the chopper, we force those conditions now + tmp[3].fX = clip.fLeft; + clamp_ge(tmp[4].fX, clip.fLeft); + + pts[0] = tmp[3]; + pts[1] = tmp[4]; + pts[2] = tmp[5]; + } + + // are we partially to the right + if (pts[3].fX > clip.fRight) { + SkPoint tmp[7]; + chop_mono_cubic_at_x(pts, clip.fRight, tmp); + tmp[3].fX = clip.fRight; + clamp_le(tmp[2].fX, clip.fRight); + + this->appendCubic(tmp, reverse); + this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse); + } else { // wholly inside the clip + this->appendCubic(pts, reverse); + } +} + +static SkRect compute_cubic_bounds(const SkPoint pts[4]) { + SkRect r; + r.setBounds(pts, 4); + return r; +} + +static bool too_big_for_reliable_float_math(const SkRect& r) { + // limit set as the largest float value for which we can still reliably compute things like + // - chopping at XY extrema + // - chopping at Y or X values for clipping + // + // Current value chosen just by experiment. Larger (and still succeeds) is always better. + // + const SkScalar limit = 1 << 22; + return r.fLeft < -limit || r.fTop < -limit || r.fRight > limit || r.fBottom > limit; +} + +bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) { + fCurrPoint = fPoints; + fCurrVerb = fVerbs; + + const SkRect bounds = compute_cubic_bounds(srcPts); + // check if we're clipped out vertically + if (bounds.fBottom > clip.fTop && bounds.fTop < clip.fBottom) { + if (too_big_for_reliable_float_math(bounds)) { + // can't safely clip the cubic, so we give up and draw a line (which we can safely clip) + // + // If we rewrote chopcubicat*extrema and chopmonocubic using doubles, we could very + // likely always handle the cubic safely, but (it seems) at a big loss in speed, so + // we'd only want to take that alternate impl if needed. Perhaps a TODO to try it. + // + return this->clipLine(srcPts[0], srcPts[3], clip); + } else { + SkPoint monoY[10]; + int countY = SkChopCubicAtYExtrema(srcPts, monoY); + for (int y = 0; y <= countY; y++) { + SkPoint monoX[10]; + int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX); + for (int x = 0; x <= countX; x++) { + this->clipMonoCubic(&monoX[x * 3], clip); + SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); + SkASSERT(fCurrPoint - fPoints <= kMaxPoints); + } + } + } + } + + *fCurrVerb = SkPath::kDone_Verb; + fCurrPoint = fPoints; + fCurrVerb = fVerbs; + return SkPath::kDone_Verb != fVerbs[0]; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) { + *fCurrVerb++ = SkPath::kLine_Verb; + fCurrPoint[0] = p0; + fCurrPoint[1] = p1; + fCurrPoint += 2; +} + +void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse) { + *fCurrVerb++ = SkPath::kLine_Verb; + + if (reverse) { + using std::swap; + swap(y0, y1); + } + fCurrPoint[0].set(x, y0); + fCurrPoint[1].set(x, y1); + fCurrPoint += 2; +} + +void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) { + *fCurrVerb++ = SkPath::kQuad_Verb; + + if (reverse) { + fCurrPoint[0] = pts[2]; + fCurrPoint[2] = pts[0]; + } else { + fCurrPoint[0] = pts[0]; + fCurrPoint[2] = pts[2]; + } + fCurrPoint[1] = pts[1]; + fCurrPoint += 3; +} + +void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) { + *fCurrVerb++ = SkPath::kCubic_Verb; + + if (reverse) { + for (int i = 0; i < 4; i++) { + fCurrPoint[i] = pts[3 - i]; + } + } else { + memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint)); + } + fCurrPoint += 4; +} + +SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) { + SkPath::Verb verb = *fCurrVerb; + + switch (verb) { + case SkPath::kLine_Verb: + memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint)); + fCurrPoint += 2; + fCurrVerb += 1; + break; + case SkPath::kQuad_Verb: + memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint)); + fCurrPoint += 3; + fCurrVerb += 1; + break; + case SkPath::kCubic_Verb: + memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint)); + fCurrPoint += 4; + fCurrVerb += 1; + break; + case SkPath::kDone_Verb: + break; + default: + SkDEBUGFAIL("unexpected verb in quadclippper2 iter"); + break; + } + return verb; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +static void assert_monotonic(const SkScalar coord[], int count) { + if (coord[0] > coord[(count - 1) * 2]) { + for (int i = 1; i < count; i++) { + SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]); + } + } else if (coord[0] < coord[(count - 1) * 2]) { + for (int i = 1; i < count; i++) { + SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]); + } + } else { + for (int i = 1; i < count; i++) { + SkASSERT(coord[2 * (i - 1)] == coord[i * 2]); + } + } +} + +void sk_assert_monotonic_y(const SkPoint pts[], int count) { + if (count > 1) { + assert_monotonic(&pts[0].fY, count); + } +} + +void sk_assert_monotonic_x(const SkPoint pts[], int count) { + if (count > 1) { + assert_monotonic(&pts[0].fX, count); + } +} +#endif diff --git a/gfx/skia/skia/src/core/SkEdgeClipper.h b/gfx/skia/skia/src/core/SkEdgeClipper.h new file mode 100644 index 0000000000..2718ed774c --- /dev/null +++ b/gfx/skia/skia/src/core/SkEdgeClipper.h @@ -0,0 +1,57 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkEdgeClipper_DEFINED +#define SkEdgeClipper_DEFINED + +#include "include/core/SkPath.h" + +/** This is basically an iterator. It is initialized with an edge and a clip, + and then next() is called until it returns kDone_Verb. + */ +class SkEdgeClipper { +public: + SkEdgeClipper(bool canCullToTheRight) : fCanCullToTheRight(canCullToTheRight) {} + + bool clipLine(SkPoint p0, SkPoint p1, const SkRect& clip); + bool clipQuad(const SkPoint pts[3], const SkRect& clip); + bool clipCubic(const SkPoint pts[4], const SkRect& clip); + + SkPath::Verb next(SkPoint pts[]); + + bool canCullToTheRight() const { return fCanCullToTheRight; } + +private: + SkPoint* fCurrPoint; + SkPath::Verb* fCurrVerb; + const bool fCanCullToTheRight; + + enum { + kMaxVerbs = 18, // max curvature in X and Y split cubic into 9 pieces, * (line + cubic) + kMaxPoints = 54 // 2 lines + 1 cubic require 6 points; times 9 pieces + }; + SkPoint fPoints[kMaxPoints]; + SkPath::Verb fVerbs[kMaxVerbs]; + + void clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip); + void clipMonoCubic(const SkPoint srcPts[4], const SkRect& clip); + void appendLine(SkPoint p0, SkPoint p1); + void appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse); + void appendQuad(const SkPoint pts[3], bool reverse); + void appendCubic(const SkPoint pts[4], bool reverse); +}; + +#ifdef SK_DEBUG + void sk_assert_monotonic_x(const SkPoint pts[], int count); + void sk_assert_monotonic_y(const SkPoint pts[], int count); +#else + #define sk_assert_monotonic_x(pts, count) + #define sk_assert_monotonic_y(pts, count) +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkEffectPriv.h b/gfx/skia/skia/src/core/SkEffectPriv.h new file mode 100644 index 0000000000..965435e330 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEffectPriv.h @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEffectPriv_DEFINED +#define SkEffectPriv_DEFINED + +#include "include/core/SkImageInfo.h" + +class SkArenaAlloc; +class SkColorSpace; +class SkMatrix; +class SkPaint; +class SkRasterPipeline; + +// Passed to effects that will add stages to rasterpipeline +struct SkStageRec { + SkRasterPipeline* fPipeline; + SkArenaAlloc* fAlloc; + SkColorType fDstColorType; + SkColorSpace* fDstCS; // may be nullptr + const SkPaint& fPaint; + const SkMatrix* fLocalM; // may be nullptr + const SkMatrix fCTM; +}; + +#endif // SkEffectPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkEndian.h b/gfx/skia/skia/src/core/SkEndian.h new file mode 100644 index 0000000000..04b10d72a4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEndian.h @@ -0,0 +1,194 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEndian_DEFINED +#define SkEndian_DEFINED + +#include "include/core/SkTypes.h" + +/** \file SkEndian.h + + Macros and helper functions for handling 16 and 32 bit values in + big and little endian formats. +*/ + +#if defined(SK_CPU_LENDIAN) && defined(SK_CPU_BENDIAN) + #error "can't have both LENDIAN and BENDIAN defined" +#endif + +#if !defined(SK_CPU_LENDIAN) && !defined(SK_CPU_BENDIAN) + #error "need either LENDIAN or BENDIAN defined" +#endif + +/** Swap the two bytes in the low 16bits of the parameters. + e.g. 0x1234 -> 0x3412 +*/ +static inline uint16_t SkEndianSwap16(uint16_t value) { + return static_cast((value >> 8) | ((value & 0xFF) << 8)); +} + +template struct SkTEndianSwap16 { + static const uint16_t value = static_cast((N >> 8) | ((N & 0xFF) << 8)); +}; + +/** Vector version of SkEndianSwap16(), which swaps the + low two bytes of each value in the array. +*/ +static inline void SkEndianSwap16s(uint16_t array[], int count) { + SkASSERT(count == 0 || array != nullptr); + + while (--count >= 0) { + *array = SkEndianSwap16(*array); + array += 1; + } +} + +/** Reverse all 4 bytes in a 32bit value. + e.g. 0x12345678 -> 0x78563412 +*/ +static constexpr uint32_t SkEndianSwap32(uint32_t value) { + return ((value & 0xFF) << 24) | + ((value & 0xFF00) << 8) | + ((value & 0xFF0000) >> 8) | + (value >> 24); +} + +template struct SkTEndianSwap32 { + static const uint32_t value = ((N & 0xFF) << 24) | + ((N & 0xFF00) << 8) | + ((N & 0xFF0000) >> 8) | + (N >> 24); +}; + +/** Vector version of SkEndianSwap32(), which swaps the + bytes of each value in the array. +*/ +static inline void SkEndianSwap32s(uint32_t array[], int count) { + SkASSERT(count == 0 || array != nullptr); + + while (--count >= 0) { + *array = SkEndianSwap32(*array); + array += 1; + } +} + +/** Reverse all 8 bytes in a 64bit value. + e.g. 0x1122334455667788 -> 0x8877665544332211 +*/ +static inline uint64_t SkEndianSwap64(uint64_t value) { + return (((value & 0x00000000000000FFULL) << (8*7)) | + ((value & 0x000000000000FF00ULL) << (8*5)) | + ((value & 0x0000000000FF0000ULL) << (8*3)) | + ((value & 0x00000000FF000000ULL) << (8*1)) | + ((value & 0x000000FF00000000ULL) >> (8*1)) | + ((value & 0x0000FF0000000000ULL) >> (8*3)) | + ((value & 0x00FF000000000000ULL) >> (8*5)) | + ((value) >> (8*7))); +} +template struct SkTEndianSwap64 { + static const uint64_t value = (((N & 0x00000000000000FFULL) << (8*7)) | + ((N & 0x000000000000FF00ULL) << (8*5)) | + ((N & 0x0000000000FF0000ULL) << (8*3)) | + ((N & 0x00000000FF000000ULL) << (8*1)) | + ((N & 0x000000FF00000000ULL) >> (8*1)) | + ((N & 0x0000FF0000000000ULL) >> (8*3)) | + ((N & 0x00FF000000000000ULL) >> (8*5)) | + ((N) >> (8*7))); +}; + +/** Vector version of SkEndianSwap64(), which swaps the + bytes of each value in the array. +*/ +static inline void SkEndianSwap64s(uint64_t array[], int count) { + SkASSERT(count == 0 || array != nullptr); + + while (--count >= 0) { + *array = SkEndianSwap64(*array); + array += 1; + } +} + +#ifdef SK_CPU_LENDIAN + #define SkEndian_SwapBE16(n) SkEndianSwap16(n) + #define SkEndian_SwapBE32(n) SkEndianSwap32(n) + #define SkEndian_SwapBE64(n) SkEndianSwap64(n) + #define SkEndian_SwapLE16(n) (n) + #define SkEndian_SwapLE32(n) (n) + #define SkEndian_SwapLE64(n) (n) + + #define SkTEndian_SwapBE16(n) SkTEndianSwap16::value + #define SkTEndian_SwapBE32(n) SkTEndianSwap32::value + #define SkTEndian_SwapBE64(n) SkTEndianSwap64::value + #define SkTEndian_SwapLE16(n) (n) + #define SkTEndian_SwapLE32(n) (n) + #define SkTEndian_SwapLE64(n) (n) +#else // SK_CPU_BENDIAN + #define SkEndian_SwapBE16(n) (n) + #define SkEndian_SwapBE32(n) (n) + #define SkEndian_SwapBE64(n) (n) + #define SkEndian_SwapLE16(n) SkEndianSwap16(n) + #define SkEndian_SwapLE32(n) SkEndianSwap32(n) + #define SkEndian_SwapLE64(n) SkEndianSwap64(n) + + #define SkTEndian_SwapBE16(n) (n) + #define SkTEndian_SwapBE32(n) (n) + #define SkTEndian_SwapBE64(n) (n) + #define SkTEndian_SwapLE16(n) SkTEndianSwap16::value + #define SkTEndian_SwapLE32(n) SkTEndianSwap32::value + #define SkTEndian_SwapLE64(n) SkTEndianSwap64::value +#endif + +// When a bytestream is embedded in a 32-bit word, how far we need to +// shift the word to extract each byte from the low 8 bits by anding with 0xff. +#ifdef SK_CPU_LENDIAN + #define SkEndian_Byte0Shift 0 + #define SkEndian_Byte1Shift 8 + #define SkEndian_Byte2Shift 16 + #define SkEndian_Byte3Shift 24 +#else // SK_CPU_BENDIAN + #define SkEndian_Byte0Shift 24 + #define SkEndian_Byte1Shift 16 + #define SkEndian_Byte2Shift 8 + #define SkEndian_Byte3Shift 0 +#endif + + +#if defined(SK_UINT8_BITFIELD_LENDIAN) && defined(SK_UINT8_BITFIELD_BENDIAN) + #error "can't have both bitfield LENDIAN and BENDIAN defined" +#endif + +#if !defined(SK_UINT8_BITFIELD_LENDIAN) && !defined(SK_UINT8_BITFIELD_BENDIAN) + #ifdef SK_CPU_LENDIAN + #define SK_UINT8_BITFIELD_LENDIAN + #else + #define SK_UINT8_BITFIELD_BENDIAN + #endif +#endif + +#ifdef SK_UINT8_BITFIELD_LENDIAN + #define SK_UINT8_BITFIELD(f0, f1, f2, f3, f4, f5, f6, f7) \ + SK_OT_BYTE f0 : 1; \ + SK_OT_BYTE f1 : 1; \ + SK_OT_BYTE f2 : 1; \ + SK_OT_BYTE f3 : 1; \ + SK_OT_BYTE f4 : 1; \ + SK_OT_BYTE f5 : 1; \ + SK_OT_BYTE f6 : 1; \ + SK_OT_BYTE f7 : 1; +#else + #define SK_UINT8_BITFIELD(f0, f1, f2, f3, f4, f5, f6, f7) \ + SK_OT_BYTE f7 : 1; \ + SK_OT_BYTE f6 : 1; \ + SK_OT_BYTE f5 : 1; \ + SK_OT_BYTE f4 : 1; \ + SK_OT_BYTE f3 : 1; \ + SK_OT_BYTE f2 : 1; \ + SK_OT_BYTE f1 : 1; \ + SK_OT_BYTE f0 : 1; +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkEnumerate.h b/gfx/skia/skia/src/core/SkEnumerate.h new file mode 100644 index 0000000000..801aa54946 --- /dev/null +++ b/gfx/skia/skia/src/core/SkEnumerate.h @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkIota_DEFINED +#define SkIota_DEFINED + +#include +#include +#include + +#include "include/private/SkTLogic.h" + +// SkEnumerate returns a tuple with an index and the value returned by the iterator. The index always +// starts at 0. +template +class SkEnumerate { + using Result = std::tuple())>; + + class Iterator { + public: + using value_type = Result; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + constexpr Iterator(ptrdiff_t index, Iter it) : fIndex{index}, fIt{it} { } + constexpr Iterator(const Iterator&) = default; + constexpr Iterator operator++() { ++fIndex; ++fIt; return *this; } + constexpr Iterator operator++(int) { Iterator tmp(*this); operator++(); return tmp; } + constexpr bool operator==(const Iterator& rhs) const { return fIt == rhs.fIt; } + constexpr bool operator!=(const Iterator& rhs) const { return fIt != rhs.fIt; } + constexpr reference operator*() { return std::forward_as_tuple(fIndex, *fIt); } + + private: + ptrdiff_t fIndex; + Iter fIt; + }; + +public: + constexpr SkEnumerate(Iter begin, Iter end) : fBegin{begin}, fEnd{end} { } + explicit constexpr SkEnumerate(C&& c) + : fCollection{std::move(c)} + , fBegin{std::begin(fCollection)} + , fEnd{std::end(fCollection)} { } + constexpr SkEnumerate(const SkEnumerate& that) = default; + constexpr SkEnumerate& operator=(const SkEnumerate& that) { + fBegin = that.fBegin; + fEnd = that.fEnd; return *this; + } + constexpr Iterator begin() const { return Iterator{0, fBegin}; } + constexpr Iterator end() const { return Iterator{fEnd - fBegin, fEnd}; } + +private: + C fCollection; + Iter fBegin; + Iter fEnd; +}; + +template ()))> +inline constexpr SkEnumerate SkMakeEnumerate(C& c) { + return SkEnumerate{std::begin(c), std::end(c)}; +} +template ()))> +inline constexpr SkEnumerate SkMakeEnumerate(C&& c) { + return SkEnumerate{std::forward(c)}; +} + +template ()))> +inline constexpr SkEnumerate SkMakeEnumerate(T (&a)[N]) { + return SkEnumerate{std::begin(a), std::end(a)}; +} +#endif // SkIota_DEFINED diff --git a/gfx/skia/skia/src/core/SkExchange.h b/gfx/skia/skia/src/core/SkExchange.h new file mode 100644 index 0000000000..50e2fe983d --- /dev/null +++ b/gfx/skia/skia/src/core/SkExchange.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkExchange_DEFINED +#define SkExchange_DEFINED + +#include + +namespace skstd { + +// std::exchange is in C++14 +template +inline static T exchange(T& obj, U&& new_val) { + T old_val = std::move(obj); + obj = std::forward(new_val); + return old_val; +} + +} + +#endif // SkExchange_DEFINED diff --git a/gfx/skia/skia/src/core/SkExecutor.cpp b/gfx/skia/skia/src/core/SkExecutor.cpp new file mode 100644 index 0000000000..ce2ded28c7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkExecutor.cpp @@ -0,0 +1,151 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkExecutor.h" +#include "include/private/SkMutex.h" +#include "include/private/SkSemaphore.h" +#include "include/private/SkSpinlock.h" +#include "include/private/SkTArray.h" +#include "src/core/SkMakeUnique.h" +#include +#include + +#if defined(SK_BUILD_FOR_WIN) + #include "src/core/SkLeanWindows.h" + static int num_cores() { + SYSTEM_INFO sysinfo; + GetNativeSystemInfo(&sysinfo); + return (int)sysinfo.dwNumberOfProcessors; + } +#else + #include + static int num_cores() { + return (int)sysconf(_SC_NPROCESSORS_ONLN); + } +#endif + +SkExecutor::~SkExecutor() {} + +// The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away. +class SkTrivialExecutor final : public SkExecutor { + void add(std::function work) override { + work(); + } +}; + +static SkExecutor* gDefaultExecutor = nullptr; + +void SetDefaultTrivialExecutor() { + static SkTrivialExecutor *gTrivial = new SkTrivialExecutor(); + gDefaultExecutor = gTrivial; +} +SkExecutor& SkExecutor::GetDefault() { + if (!gDefaultExecutor) { + SetDefaultTrivialExecutor(); + } + return *gDefaultExecutor; +} +void SkExecutor::SetDefault(SkExecutor* executor) { + if (executor) { + gDefaultExecutor = executor; + } else { + SetDefaultTrivialExecutor(); + } +} + +// We'll always push_back() new work, but pop from the front of deques or the back of SkTArray. +static inline std::function pop(std::deque>* list) { + std::function fn = std::move(list->front()); + list->pop_front(); + return fn; +} +static inline std::function pop(SkTArray>* list) { + std::function fn = std::move(list->back()); + list->pop_back(); + return fn; +} + +// An SkThreadPool is an executor that runs work on a fixed pool of OS threads. +template +class SkThreadPool final : public SkExecutor { +public: + explicit SkThreadPool(int threads) { + for (int i = 0; i < threads; i++) { + fThreads.emplace_back(&Loop, this); + } + } + + ~SkThreadPool() override { + // Signal each thread that it's time to shut down. + for (int i = 0; i < fThreads.count(); i++) { + this->add(nullptr); + } + // Wait for each thread to shut down. + for (int i = 0; i < fThreads.count(); i++) { + fThreads[i].join(); + } + } + + virtual void add(std::function work) override { + // Add some work to our pile of work to do. + { + SkAutoMutexExclusive lock(fWorkLock); + fWork.emplace_back(std::move(work)); + } + // Tell the Loop() threads to pick it up. + fWorkAvailable.signal(1); + } + + virtual void borrow() override { + // If there is work waiting, do it. + if (fWorkAvailable.try_wait()) { + SkAssertResult(this->do_work()); + } + } + +private: + // This method should be called only when fWorkAvailable indicates there's work to do. + bool do_work() { + std::function work; + { + SkAutoMutexExclusive lock(fWorkLock); + SkASSERT(!fWork.empty()); // TODO: if (fWork.empty()) { return true; } ? + work = pop(&fWork); + } + + if (!work) { + return false; // This is Loop()'s signal to shut down. + } + + work(); + return true; + } + + static void Loop(void* ctx) { + auto pool = (SkThreadPool*)ctx; + do { + pool->fWorkAvailable.wait(); + } while (pool->do_work()); + } + + // Both SkMutex and SkSpinlock can work here. + using Lock = SkMutex; + + SkTArray fThreads; + WorkList fWork; + Lock fWorkLock; + SkSemaphore fWorkAvailable; +}; + +std::unique_ptr SkExecutor::MakeFIFOThreadPool(int threads) { + using WorkList = std::deque>; + return skstd::make_unique>(threads > 0 ? threads : num_cores()); +} +std::unique_ptr SkExecutor::MakeLIFOThreadPool(int threads) { + using WorkList = SkTArray>; + return skstd::make_unique>(threads > 0 ? threads : num_cores()); +} diff --git a/gfx/skia/skia/src/core/SkFDot6.h b/gfx/skia/skia/src/core/SkFDot6.h new file mode 100644 index 0000000000..037a434fd3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFDot6.h @@ -0,0 +1,78 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFDot6_DEFINED +#define SkFDot6_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkScalar.h" +#include "include/private/SkFixed.h" +#include "include/private/SkTo.h" + +typedef int32_t SkFDot6; + +/* This uses the magic number approach suggested here: + * http://stereopsis.com/sree/fpu2006.html and used in + * _cairo_fixed_from_double. It does banker's rounding + * (i.e. round to nearest even) + */ +inline SkFDot6 SkScalarRoundToFDot6(SkScalar x, int shift = 0) +{ + union { + double fDouble; + int32_t fBits[2]; + } tmp; + int fractionalBits = 6 + shift; + double magic = (1LL << (52 - (fractionalBits))) * 1.5; + + tmp.fDouble = SkScalarToDouble(x) + magic; +#ifdef SK_CPU_BENDIAN + return tmp.fBits[1]; +#else + return tmp.fBits[0]; +#endif +} + +#define SK_FDot6One (64) +#define SK_FDot6Half (32) + +#ifdef SK_DEBUG + inline SkFDot6 SkIntToFDot6(int x) { + SkASSERT(SkToS16(x) == x); + return x << 6; + } +#else + #define SkIntToFDot6(x) ((x) << 6) +#endif + +#define SkFDot6Floor(x) ((x) >> 6) +#define SkFDot6Ceil(x) (((x) + 63) >> 6) +#define SkFDot6Round(x) (((x) + 32) >> 6) + +#define SkFixedToFDot6(x) ((x) >> 10) + +inline SkFixed SkFDot6ToFixed(SkFDot6 x) { + SkASSERT((SkLeftShift(x, 10) >> 10) == x); + + return SkLeftShift(x, 10); +} + +#define SkScalarToFDot6(x) (SkFDot6)((x) * 64) +#define SkFDot6ToScalar(x) ((SkScalar)(x) * 0.015625f) +#define SkFDot6ToFloat SkFDot6ToScalar + +inline SkFixed SkFDot6Div(SkFDot6 a, SkFDot6 b) { + SkASSERT(b != 0); + + if (SkTFitsIn(a)) { + return SkLeftShift(a, 16) / b; + } else { + return SkFixedDiv(a, b); + } +} + +#endif diff --git a/gfx/skia/skia/src/core/SkFixed15.h b/gfx/skia/skia/src/core/SkFixed15.h new file mode 100644 index 0000000000..d50337f243 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFixed15.h @@ -0,0 +1,77 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFixed15_DEFINED +#define SkFixed15_DEFINED + +#include "include/core/SkTypes.h" + +// SkFixed15 is a fixed point value that represents values in [0,1] as [0x0000, 0x8000]. +// This mapping allows us to implement most operations in tightly packed 16-bit SIMD, +// most notably multiplying using Q15 multiplication instructions (and a little fixup). + +class SkFixed15 { +public: + SkFixed15() = default; + + SkFixed15(float val) : fVal(val * 32768) { SkASSERT(0.0f <= val && val <= 1.0f); } + explicit operator float() const { return fVal * (1/32768.0f); } + + static SkFixed15 Load(uint16_t val) { + SkASSERT(val <= 32768); + return val; + } + uint16_t store() const { return fVal; } + + static SkFixed15 FromU8(uint8_t val) { + return val*128 + (val>>1) // 32768/255 == 128.50196..., which is very close to 128 + 0.5. + + ((val+1)>>8); // All val but 255 are correct. +1 if val == 255 to get 32768. + } + + uint8_t to_u8() const { + // FromU8() and to_u8() roundtrip all bytes. + // There is still much room to tweak this towards the ideal, a rounding scale by 255/32768. + return (fVal - (fVal>>8))>>7; + } + + SkFixed15 operator +(SkFixed15 o) const { return fVal + o.fVal; } + SkFixed15 operator -(SkFixed15 o) const { return fVal - o.fVal; } + SkFixed15 operator *(SkFixed15 o) const { return (fVal * o.fVal + (1<<14)) >> 15; } + SkFixed15 operator<<(int bits) const { return fVal << bits; } + SkFixed15 operator>>(int bits) const { return fVal >> bits; } + + SkFixed15& operator +=(SkFixed15 o) { return (*this = *this + o); } + SkFixed15& operator -=(SkFixed15 o) { return (*this = *this - o); } + SkFixed15& operator *=(SkFixed15 o) { return (*this = *this * o); } + SkFixed15& operator<<=(int bits) { return (*this = *this << bits); } + SkFixed15& operator>>=(int bits) { return (*this = *this >> bits); } + + bool operator==(SkFixed15 o) const { return fVal == o.fVal; } + bool operator!=(SkFixed15 o) const { return fVal != o.fVal; } + bool operator<=(SkFixed15 o) const { return fVal <= o.fVal; } + bool operator>=(SkFixed15 o) const { return fVal >= o.fVal; } + bool operator< (SkFixed15 o) const { return fVal < o.fVal; } + bool operator> (SkFixed15 o) const { return fVal > o.fVal; } + +private: + SkFixed15(int val) : fVal(val) {} + + uint16_t fVal; +}; + +// Notes +// - SSSE3+ multiply is _mm_abs_epi16(_mm_mulhrs_epi16(x, y)); +// - NEON multipy is vsraq_n_u16(vabsq_s16(vqrdmulhq_s16(x,y)), +// vandq_s16(x,y), 15); +// - Conversion to and from float can be done manually with bit masks and float add/subtract, +// rather than the naive version here involving int<->float conversion and float multiply. +// - On x86, conversion to float is _mm_sub_ps(_mm_unpacklo_epi16(x, _mm_set1_epi16(0x4380)), +// _mm_set1_ps(256.0f)). // 0x43800000 +// - On ARM, we can use the vcvtq_n_f32_u32(vmovl_u16(x), 15) to convert to float, +// and vcvtq_n_u32_f32(..., 15) for the other way around. + +#endif//SkFixed15_DEFINED diff --git a/gfx/skia/skia/src/core/SkFlattenable.cpp b/gfx/skia/skia/src/core/SkFlattenable.cpp new file mode 100644 index 0000000000..9cff53af08 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFlattenable.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFlattenable.h" +#include "src/core/SkPtrRecorder.h" +#include "src/core/SkReadBuffer.h" + +#include + +SkNamedFactorySet::SkNamedFactorySet() : fNextAddedFactory(0) {} + +uint32_t SkNamedFactorySet::find(SkFlattenable::Factory factory) { + uint32_t index = fFactorySet.find(factory); + if (index > 0) { + return index; + } + const char* name = SkFlattenable::FactoryToName(factory); + if (nullptr == name) { + return 0; + } + *fNames.append() = name; + return fFactorySet.add(factory); +} + +const char* SkNamedFactorySet::getNextAddedFactoryName() { + if (fNextAddedFactory < fNames.count()) { + return fNames[fNextAddedFactory++]; + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkRefCntSet::~SkRefCntSet() { + // call this now, while our decPtr() is sill in scope + this->reset(); +} + +void SkRefCntSet::incPtr(void* ptr) { + ((SkRefCnt*)ptr)->ref(); +} + +void SkRefCntSet::decPtr(void* ptr) { + ((SkRefCnt*)ptr)->unref(); +} + +/////////////////////////////////////////////////////////////////////////////// + +namespace { + +struct Entry { + const char* fName; + SkFlattenable::Factory fFactory; +}; + +struct EntryComparator { + bool operator()(const Entry& a, const Entry& b) const { + return strcmp(a.fName, b.fName) < 0; + } + bool operator()(const Entry& a, const char* b) const { + return strcmp(a.fName, b) < 0; + } + bool operator()(const char* a, const Entry& b) const { + return strcmp(a, b.fName) < 0; + } +}; + +int gCount = 0; +Entry gEntries[128]; + +} // namespace + +void SkFlattenable::Finalize() { + std::sort(gEntries, gEntries + gCount, EntryComparator()); +} + +void SkFlattenable::Register(const char name[], Factory factory) { + SkASSERT(name); + SkASSERT(factory); + SkASSERT(gCount < (int)SK_ARRAY_COUNT(gEntries)); + + gEntries[gCount].fName = name; + gEntries[gCount].fFactory = factory; + gCount += 1; +} + +SkFlattenable::Factory SkFlattenable::NameToFactory(const char name[]) { + RegisterFlattenablesIfNeeded(); + + SkASSERT(std::is_sorted(gEntries, gEntries + gCount, EntryComparator())); + auto pair = std::equal_range(gEntries, gEntries + gCount, name, EntryComparator()); + if (pair.first == pair.second) { + return nullptr; + } + return pair.first->fFactory; +} + +const char* SkFlattenable::FactoryToName(Factory fact) { + RegisterFlattenablesIfNeeded(); + + const Entry* entries = gEntries; + for (int i = gCount - 1; i >= 0; --i) { + if (entries[i].fFactory == fact) { + return entries[i].fName; + } + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkFlattenable::serialize(const SkSerialProcs* procs) const { + SkBinaryWriteBuffer writer; + if (procs) { + writer.setSerialProcs(*procs); + } + writer.writeFlattenable(this); + size_t size = writer.bytesWritten(); + auto data = SkData::MakeUninitialized(size); + writer.writeToMemory(data->writable_data()); + return data; +} + +size_t SkFlattenable::serialize(void* memory, size_t memory_size, + const SkSerialProcs* procs) const { + SkBinaryWriteBuffer writer(memory, memory_size); + if (procs) { + writer.setSerialProcs(*procs); + } + writer.writeFlattenable(this); + return writer.usingInitialStorage() ? writer.bytesWritten() : 0u; +} + +sk_sp SkFlattenable::Deserialize(SkFlattenable::Type type, const void* data, + size_t size, const SkDeserialProcs* procs) { + SkReadBuffer buffer(data, size); + if (procs) { + buffer.setDeserialProcs(*procs); + } + return sk_sp(buffer.readFlattenable(type)); +} diff --git a/gfx/skia/skia/src/core/SkFont.cpp b/gfx/skia/skia/src/core/SkFont.cpp new file mode 100644 index 0000000000..fdfbf66a96 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFont.cpp @@ -0,0 +1,543 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkDraw.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkPaintDefaults.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkStrikeSpec.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkUtils.h" +#include "src/utils/SkUTF.h" + +#define kDefault_Size SkPaintDefaults_TextSize +#define kDefault_Flags SkFont::kBaselineSnap_PrivFlag +#define kDefault_Edging SkFont::Edging::kAntiAlias +#define kDefault_Hinting SkPaintDefaults_Hinting + +static inline SkScalar valid_size(SkScalar size) { + return SkTMax(0, size); +} + +SkFont::SkFont(sk_sp face, SkScalar size, SkScalar scaleX, SkScalar skewX) + : fTypeface(std::move(face)) + , fSize(valid_size(size)) + , fScaleX(scaleX) + , fSkewX(skewX) + , fFlags(kDefault_Flags) + , fEdging(static_cast(kDefault_Edging)) + , fHinting(static_cast(kDefault_Hinting)) +{} + +SkFont::SkFont(sk_sp face, SkScalar size) : SkFont(std::move(face), size, 1, 0) {} + +SkFont::SkFont(sk_sp face) : SkFont(std::move(face), kDefault_Size, 1, 0) {} + +SkFont::SkFont() : SkFont(nullptr, kDefault_Size) {} + +bool SkFont::operator==(const SkFont& b) const { + return fTypeface.get() == b.fTypeface.get() && + fSize == b.fSize && + fScaleX == b.fScaleX && + fSkewX == b.fSkewX && + fFlags == b.fFlags && + fEdging == b.fEdging && + fHinting == b.fHinting; +} + +void SkFont::dump() const { + SkDebugf("typeface %p\n", fTypeface.get()); + SkDebugf("size %g\n", fSize); + SkDebugf("skewx %g\n", fSkewX); + SkDebugf("scalex %g\n", fScaleX); + SkDebugf("flags 0x%X\n", fFlags); + SkDebugf("edging %d\n", (unsigned)fEdging); + SkDebugf("hinting %d\n", (unsigned)fHinting); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static inline uint32_t set_clear_mask(uint32_t bits, bool cond, uint32_t mask) { + return cond ? bits | mask : bits & ~mask; +} + +void SkFont::setForceAutoHinting(bool predicate) { + fFlags = set_clear_mask(fFlags, predicate, kForceAutoHinting_PrivFlag); +} +void SkFont::setEmbeddedBitmaps(bool predicate) { + fFlags = set_clear_mask(fFlags, predicate, kEmbeddedBitmaps_PrivFlag); +} +void SkFont::setSubpixel(bool predicate) { + fFlags = set_clear_mask(fFlags, predicate, kSubpixel_PrivFlag); +} +void SkFont::setLinearMetrics(bool predicate) { + fFlags = set_clear_mask(fFlags, predicate, kLinearMetrics_PrivFlag); +} +void SkFont::setEmbolden(bool predicate) { + fFlags = set_clear_mask(fFlags, predicate, kEmbolden_PrivFlag); +} +void SkFont::setBaselineSnap(bool predicate) { + fFlags = set_clear_mask(fFlags, predicate, kBaselineSnap_PrivFlag); +} +void SkFont::setEdging(Edging e) { + fEdging = SkToU8(e); +} + +void SkFont::setHinting(SkFontHinting h) { + fHinting = SkToU8(h); +} + +void SkFont::setSize(SkScalar size) { + fSize = valid_size(size); +} +void SkFont::setScaleX(SkScalar scale) { + fScaleX = scale; +} +void SkFont::setSkewX(SkScalar skew) { + fSkewX = skew; +} + +SkFont SkFont::makeWithSize(SkScalar newSize) const { + SkFont font = *this; + font.setSize(newSize); + return font; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkScalar SkFont::setupForAsPaths(SkPaint* paint) { + constexpr uint32_t flagsToIgnore = kEmbeddedBitmaps_PrivFlag | + kForceAutoHinting_PrivFlag; + + fFlags = (fFlags & ~flagsToIgnore) | kSubpixel_PrivFlag; + this->setHinting(SkFontHinting::kNone); + + if (this->getEdging() == Edging::kSubpixelAntiAlias) { + this->setEdging(Edging::kAntiAlias); + } + + if (paint) { + paint->setStyle(SkPaint::kFill_Style); + paint->setPathEffect(nullptr); + } + SkScalar textSize = fSize; + this->setSize(SkIntToScalar(SkFontPriv::kCanonicalTextSizeForPaths)); + return textSize / SkFontPriv::kCanonicalTextSizeForPaths; +} + +bool SkFont::hasSomeAntiAliasing() const { + Edging edging = this->getEdging(); + return edging == SkFont::Edging::kAntiAlias + || edging == SkFont::Edging::kSubpixelAntiAlias; +} + +SkGlyphID SkFont::unicharToGlyph(SkUnichar uni) const { + return this->getTypefaceOrDefault()->unicharToGlyph(uni); +} + +void SkFont::unicharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const { + this->getTypefaceOrDefault()->unicharsToGlyphs(uni, count, glyphs); +} + +class SkConvertToUTF32 { +public: + SkConvertToUTF32() {} + + const SkUnichar* convert(const void* text, size_t byteLength, SkTextEncoding encoding) { + const SkUnichar* uni; + switch (encoding) { + case SkTextEncoding::kUTF8: { + uni = fStorage.reset(byteLength); + const char* ptr = (const char*)text; + const char* end = ptr + byteLength; + for (int i = 0; ptr < end; ++i) { + fStorage[i] = SkUTF::NextUTF8(&ptr, end); + } + } break; + case SkTextEncoding::kUTF16: { + uni = fStorage.reset(byteLength); + const uint16_t* ptr = (const uint16_t*)text; + const uint16_t* end = ptr + (byteLength >> 1); + for (int i = 0; ptr < end; ++i) { + fStorage[i] = SkUTF::NextUTF16(&ptr, end); + } + } break; + case SkTextEncoding::kUTF32: + uni = (const SkUnichar*)text; + break; + default: + SK_ABORT("unexpected enum"); + } + return uni; + } + +private: + SkAutoSTMalloc<256, SkUnichar> fStorage; +}; + +int SkFont::textToGlyphs(const void* text, size_t byteLength, SkTextEncoding encoding, + SkGlyphID glyphs[], int maxGlyphCount) const { + if (0 == byteLength) { + return 0; + } + + SkASSERT(text); + + int count = SkFontPriv::CountTextElements(text, byteLength, encoding); + if (!glyphs || count > maxGlyphCount) { + return count; + } + + if (encoding == SkTextEncoding::kGlyphID) { + memcpy(glyphs, text, count << 1); + return count; + } + + SkConvertToUTF32 storage; + const SkUnichar* uni = storage.convert(text, byteLength, encoding); + + this->getTypefaceOrDefault()->unicharsToGlyphs(uni, count, glyphs); + return count; +} + +SkScalar SkFont::measureText(const void* text, size_t length, SkTextEncoding encoding, + SkRect* bounds, const SkPaint* paint) const { + + SkAutoToGlyphs atg(*this, text, length, encoding); + const int glyphCount = atg.count(); + if (glyphCount == 0) { + if (bounds) { + bounds->setEmpty(); + } + return 0; + } + const SkGlyphID* glyphIDs = atg.glyphs(); + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeCanonicalized(*this, paint); + SkBulkGlyphMetrics metrics{strikeSpec}; + SkSpan glyphs = metrics.glyphs(SkMakeSpan(glyphIDs, glyphCount)); + + SkScalar width = 0; + if (bounds) { + *bounds = glyphs[0]->rect(); + width = glyphs[0]->advanceX(); + for (int i = 1; i < glyphCount; ++i) { + SkRect r = glyphs[i]->rect(); + r.offset(width, 0); + bounds->join(r); + width += glyphs[i]->advanceX(); + } + } else { + for (auto glyph : glyphs) { + width += glyph->advanceX(); + } + } + + const SkScalar scale = strikeSpec.strikeToSourceRatio(); + if (scale != 1) { + width *= scale; + if (bounds) { + bounds->fLeft *= scale; + bounds->fTop *= scale; + bounds->fRight *= scale; + bounds->fBottom *= scale; + } + } + + return width; +} + +void SkFont::getWidthsBounds(const SkGlyphID glyphIDs[], + int count, + SkScalar widths[], + SkRect bounds[], + const SkPaint* paint) const { + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeCanonicalized(*this, paint); + SkBulkGlyphMetrics metrics{strikeSpec}; + SkSpan glyphs = metrics.glyphs(SkMakeSpan(glyphIDs, count)); + + SkScalar scale = strikeSpec.strikeToSourceRatio(); + + if (bounds) { + SkMatrix scaleMat = SkMatrix::MakeScale(scale); + SkRect* cursor = bounds; + for (auto glyph : glyphs) { + scaleMat.mapRectScaleTranslate(cursor++, glyph->rect()); + } + } + + if (widths) { + SkScalar* cursor = widths; + for (auto glyph : glyphs) { + *cursor++ = glyph->advanceX() * scale; + } + } +} + +void SkFont::getPos(const SkGlyphID glyphIDs[], int count, SkPoint pos[], SkPoint origin) const { + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeCanonicalized(*this); + SkBulkGlyphMetrics metrics{strikeSpec}; + SkSpan glyphs = metrics.glyphs(SkMakeSpan(glyphIDs, count)); + + SkPoint sum = origin; + for (auto glyph : glyphs) { + *pos++ = sum; + sum += glyph->advanceVector() * strikeSpec.strikeToSourceRatio(); + } +} + +void SkFont::getXPos( + const SkGlyphID glyphIDs[], int count, SkScalar xpos[], SkScalar origin) const { + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeCanonicalized(*this); + SkBulkGlyphMetrics metrics{strikeSpec}; + SkSpan glyphs = metrics.glyphs(SkMakeSpan(glyphIDs, count)); + + SkScalar loc = origin; + SkScalar* cursor = xpos; + for (auto glyph : glyphs) { + *cursor++ = loc; + loc += glyph->advanceX() * strikeSpec.strikeToSourceRatio(); + } +} + +void SkFont::getPaths(const SkGlyphID glyphIDs[], int count, + void (*proc)(const SkPath*, const SkMatrix&, void*), void* ctx) const { + SkFont font(*this); + SkScalar scale = font.setupForAsPaths(nullptr); + const SkMatrix mx = SkMatrix::MakeScale(scale); + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(font); + SkBulkGlyphMetricsAndPaths paths{strikeSpec}; + SkSpan glyphs = paths.glyphs(SkMakeSpan(glyphIDs, count)); + + for (auto glyph : glyphs) { + proc(glyph->path(), mx, ctx); + } +} + +bool SkFont::getPath(SkGlyphID glyphID, SkPath* path) const { + struct Pair { + SkPath* fPath; + bool fWasSet; + } pair = { path, false }; + + this->getPaths(&glyphID, 1, [](const SkPath* orig, const SkMatrix& mx, void* ctx) { + Pair* pair = static_cast(ctx); + if (orig) { + orig->transform(mx, pair->fPath); + pair->fWasSet = true; + } + }, &pair); + return pair.fWasSet; +} + +SkScalar SkFont::getMetrics(SkFontMetrics* metrics) const { + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeCanonicalized(*this, nullptr); + + SkFontMetrics storage; + if (nullptr == metrics) { + metrics = &storage; + } + + auto cache = strikeSpec.findOrCreateExclusiveStrike(); + *metrics = cache->getFontMetrics(); + + if (strikeSpec.strikeToSourceRatio() != 1) { + SkFontPriv::ScaleFontMetrics(metrics, strikeSpec.strikeToSourceRatio()); + } + return metrics->fDescent - metrics->fAscent + metrics->fLeading; +} + +SkTypeface* SkFont::getTypefaceOrDefault() const { + return fTypeface ? fTypeface.get() : SkTypeface::GetDefaultTypeface(); +} + +sk_sp SkFont::refTypefaceOrDefault() const { + return fTypeface ? fTypeface : SkTypeface::MakeDefault(); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkFontPriv::ScaleFontMetrics(SkFontMetrics* metrics, SkScalar scale) { + metrics->fTop *= scale; + metrics->fAscent *= scale; + metrics->fDescent *= scale; + metrics->fBottom *= scale; + metrics->fLeading *= scale; + metrics->fAvgCharWidth *= scale; + metrics->fMaxCharWidth *= scale; + metrics->fXMin *= scale; + metrics->fXMax *= scale; + metrics->fXHeight *= scale; + metrics->fCapHeight *= scale; + metrics->fUnderlineThickness *= scale; + metrics->fUnderlinePosition *= scale; + metrics->fStrikeoutThickness *= scale; + metrics->fStrikeoutPosition *= scale; +} + +SkRect SkFontPriv::GetFontBounds(const SkFont& font) { + SkMatrix m; + m.setScale(font.getSize() * font.getScaleX(), font.getSize()); + m.postSkew(font.getSkewX(), 0); + + SkTypeface* typeface = font.getTypefaceOrDefault(); + + SkRect bounds; + m.mapRect(&bounds, typeface->getBounds()); + return bounds; +} + +int SkFontPriv::CountTextElements(const void* text, size_t byteLength, SkTextEncoding encoding) { + switch (encoding) { + case SkTextEncoding::kUTF8: + return SkUTF::CountUTF8(reinterpret_cast(text), byteLength); + case SkTextEncoding::kUTF16: + return SkUTF::CountUTF16(reinterpret_cast(text), byteLength); + case SkTextEncoding::kUTF32: + return byteLength >> 2; + case SkTextEncoding::kGlyphID: + return byteLength >> 1; + } + SkASSERT(false); + return 0; +} + +void SkFontPriv::GlyphsToUnichars(const SkFont& font, const SkGlyphID glyphs[], int count, + SkUnichar text[]) { + if (count <= 0) { + return; + } + + auto typeface = font.getTypefaceOrDefault(); + const unsigned numGlyphsInTypeface = typeface->countGlyphs(); + SkAutoTArray unichars(numGlyphsInTypeface); + typeface->getGlyphToUnicodeMap(unichars.get()); + + for (int i = 0; i < count; ++i) { + unsigned id = glyphs[i]; + text[i] = (id < numGlyphsInTypeface) ? unichars[id] : 0xFFFD; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +// packed int at the beginning of the serialized font: +// +// control_bits:8 size_as_byte:8 flags:12 edging:2 hinting:2 + +enum { + kSize_Is_Byte_Bit = 1 << 31, + kHas_ScaleX_Bit = 1 << 30, + kHas_SkewX_Bit = 1 << 29, + kHas_Typeface_Bit = 1 << 28, + + kShift_for_Size = 16, + kMask_For_Size = 0xFF, + + kShift_For_Flags = 4, + kMask_For_Flags = 0xFFF, + + kShift_For_Edging = 2, + kMask_For_Edging = 0x3, + + kShift_For_Hinting = 0, + kMask_For_Hinting = 0x3 +}; + +static bool scalar_is_byte(SkScalar x) { + int ix = (int)x; + return ix == x && ix >= 0 && ix <= kMask_For_Size; +} + +void SkFontPriv::Flatten(const SkFont& font, SkWriteBuffer& buffer) { + SkASSERT(font.fFlags <= SkFont::kAllFlags); + SkASSERT((font.fFlags & ~kMask_For_Flags) == 0); + SkASSERT((font.fEdging & ~kMask_For_Edging) == 0); + SkASSERT((font.fHinting & ~kMask_For_Hinting) == 0); + + uint32_t packed = 0; + packed |= font.fFlags << kShift_For_Flags; + packed |= font.fEdging << kShift_For_Edging; + packed |= font.fHinting << kShift_For_Hinting; + + if (scalar_is_byte(font.fSize)) { + packed |= kSize_Is_Byte_Bit; + packed |= (int)font.fSize << kShift_for_Size; + } + if (font.fScaleX != 1) { + packed |= kHas_ScaleX_Bit; + } + if (font.fSkewX != 0) { + packed |= kHas_SkewX_Bit; + } + if (font.fTypeface) { + packed |= kHas_Typeface_Bit; + } + + buffer.write32(packed); + if (!(packed & kSize_Is_Byte_Bit)) { + buffer.writeScalar(font.fSize); + } + if (packed & kHas_ScaleX_Bit) { + buffer.writeScalar(font.fScaleX); + } + if (packed & kHas_SkewX_Bit) { + buffer.writeScalar(font.fSkewX); + } + if (packed & kHas_Typeface_Bit) { + buffer.writeTypeface(font.fTypeface.get()); + } +} + +bool SkFontPriv::Unflatten(SkFont* font, SkReadBuffer& buffer) { + const uint32_t packed = buffer.read32(); + + if (packed & kSize_Is_Byte_Bit) { + font->fSize = (packed >> kShift_for_Size) & kMask_For_Size; + } else { + font->fSize = buffer.readScalar(); + } + if (packed & kHas_ScaleX_Bit) { + font->fScaleX = buffer.readScalar(); + } + if (packed & kHas_SkewX_Bit) { + font->fSkewX = buffer.readScalar(); + } + if (packed & kHas_Typeface_Bit) { + font->fTypeface = buffer.readTypeface(); + } + + SkASSERT(SkFont::kAllFlags <= kMask_For_Flags); + // we & with kAllFlags, to clear out any unknown flag bits + font->fFlags = SkToU8((packed >> kShift_For_Flags) & SkFont::kAllFlags); + + unsigned edging = (packed >> kShift_For_Edging) & kMask_For_Edging; + if (edging > (unsigned)SkFont::Edging::kSubpixelAntiAlias) { + edging = 0; + } + font->fEdging = SkToU8(edging); + + unsigned hinting = (packed >> kShift_For_Hinting) & kMask_For_Hinting; + if (hinting > (unsigned)SkFontHinting::kFull) { + hinting = 0; + } + font->fHinting = SkToU8(hinting); + + return buffer.isValid(); +} diff --git a/gfx/skia/skia/src/core/SkFontDescriptor.cpp b/gfx/skia/skia/src/core/SkFontDescriptor.cpp new file mode 100644 index 0000000000..c47781e450 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontDescriptor.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" + +enum { + kInvalid = 0x00, + + // these must match the sfnt 'name' enums + kFontFamilyName = 0x01, + kFullName = 0x04, + kPostscriptName = 0x06, + + // These count backwards from 0xFF, so as not to collide with the SFNT + // defines for names in its 'name' table. + kFontAxes = 0xFB, + kFontAxes_bad = 0xFC, // Broken negative axes, remove when MIN_PICTURE_VERSION > 62. + kFontIndex = 0xFD, + kSentinel = 0xFF, +}; + +SkFontDescriptor::SkFontDescriptor() { } + +static bool SK_WARN_UNUSED_RESULT read_string(SkStream* stream, SkString* string) { + size_t length; + if (!stream->readPackedUInt(&length)) { return false; } + if (length > 0) { + string->resize(length); + if (stream->read(string->writable_str(), length) != length) { return false; } + } + return true; +} + +static bool write_string(SkWStream* stream, const SkString& string, uint32_t id) { + if (string.isEmpty()) { return true; } + return stream->writePackedUInt(id) && + stream->writePackedUInt(string.size()) && + stream->write(string.c_str(), string.size()); +} + +static bool write_uint(SkWStream* stream, size_t n, uint32_t id) { + return stream->writePackedUInt(id) && + stream->writePackedUInt(n); +} + +static size_t SK_WARN_UNUSED_RESULT read_id(SkStream* stream) { + size_t i; + if (!stream->readPackedUInt(&i)) { return kInvalid; } + return i; +} + +bool SkFontDescriptor::Deserialize(SkStream* stream, SkFontDescriptor* result) { + size_t styleBits; + if (!stream->readPackedUInt(&styleBits)) { return false; } + result->fStyle = SkFontStyle((styleBits >> 16) & 0xFFFF, + (styleBits >> 8 ) & 0xFF, + static_cast(styleBits & 0xFF)); + + SkAutoSTMalloc<4, SkFixed> axis; + size_t axisCount = 0; + size_t index = 0; + for (size_t id; (id = read_id(stream)) != kSentinel;) { + switch (id) { + case kFontFamilyName: + if (!read_string(stream, &result->fFamilyName)) { return false; } + break; + case kFullName: + if (!read_string(stream, &result->fFullName)) { return false; } + break; + case kPostscriptName: + if (!read_string(stream, &result->fPostscriptName)) { return false; } + break; + case kFontAxes: + if (!stream->readPackedUInt(&axisCount)) { return false; } + axis.reset(axisCount); + for (size_t i = 0; i < axisCount; ++i) { + if (!stream->readS32(&axis[i])) { return false; } + } + break; + case kFontAxes_bad: + if (!stream->readPackedUInt(&axisCount)) { return false; } + axis.reset(axisCount); + for (size_t i = 0; i < axisCount; ++i) { + size_t packedAxis; + if (!stream->readPackedUInt(&packedAxis)) { return false; } + axis[i] = packedAxis; + } + break; + case kFontIndex: + if (!stream->readPackedUInt(&index)) { return false; } + break; + default: + SkDEBUGFAIL("Unknown id used by a font descriptor"); + return false; + } + } + + size_t length; + if (!stream->readPackedUInt(&length)) { return false; } + if (length > 0) { + sk_sp data(SkData::MakeUninitialized(length)); + if (stream->read(data->writable_data(), length) != length) { + SkDEBUGFAIL("Could not read font data"); + return false; + } + result->fFontData = skstd::make_unique( + SkMemoryStream::Make(std::move(data)), index, axis, axisCount); + } + return true; +} + +void SkFontDescriptor::serialize(SkWStream* stream) const { + uint32_t styleBits = (fStyle.weight() << 16) | (fStyle.width() << 8) | (fStyle.slant()); + stream->writePackedUInt(styleBits); + + write_string(stream, fFamilyName, kFontFamilyName); + write_string(stream, fFullName, kFullName); + write_string(stream, fPostscriptName, kPostscriptName); + if (fFontData.get()) { + if (fFontData->getIndex()) { + write_uint(stream, fFontData->getIndex(), kFontIndex); + } + if (fFontData->getAxisCount()) { + write_uint(stream, fFontData->getAxisCount(), kFontAxes); + for (int i = 0; i < fFontData->getAxisCount(); ++i) { + stream->write32(fFontData->getAxis()[i]); + } + } + } + + stream->writePackedUInt(kSentinel); + + if (fFontData.get() && fFontData->hasStream()) { + std::unique_ptr fontStream = fFontData->detachStream(); + size_t length = fontStream->getLength(); + stream->writePackedUInt(length); + stream->writeStream(fontStream.get(), length); + } else { + stream->writePackedUInt(0); + } +} diff --git a/gfx/skia/skia/src/core/SkFontDescriptor.h b/gfx/skia/skia/src/core/SkFontDescriptor.h new file mode 100644 index 0000000000..8f16996aad --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontDescriptor.h @@ -0,0 +1,84 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontDescriptor_DEFINED +#define SkFontDescriptor_DEFINED + +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkFixed.h" +#include "include/private/SkNoncopyable.h" + +class SkFontData { +public: + /** Makes a copy of the data in 'axis'. */ + SkFontData(std::unique_ptr stream, int index, const SkFixed axis[],int axisCount) + : fStream(std::move(stream)), fIndex(index), fAxisCount(axisCount), fAxis(axisCount) + { + for (int i = 0; i < axisCount; ++i) { + fAxis[i] = axis[i]; + } + } + SkFontData(const SkFontData& that) + : fStream(that.fStream->duplicate()) + , fIndex(that.fIndex) + , fAxisCount(that.fAxisCount) + , fAxis(fAxisCount) + { + for (int i = 0; i < fAxisCount; ++i) { + fAxis[i] = that.fAxis[i]; + } + } + bool hasStream() const { return fStream.get() != nullptr; } + std::unique_ptr detachStream() { return std::move(fStream); } + SkStreamAsset* getStream() { return fStream.get(); } + SkStreamAsset const* getStream() const { return fStream.get(); } + int getIndex() const { return fIndex; } + int getAxisCount() const { return fAxisCount; } + const SkFixed* getAxis() const { return fAxis.get(); } + +private: + std::unique_ptr fStream; + int fIndex; + int fAxisCount; + SkAutoSTMalloc<4, SkFixed> fAxis; +}; + +class SkFontDescriptor : SkNoncopyable { +public: + SkFontDescriptor(); + // Does not affect ownership of SkStream. + static bool Deserialize(SkStream*, SkFontDescriptor* result); + + void serialize(SkWStream*) const; + + SkFontStyle getStyle() const { return fStyle; } + void setStyle(SkFontStyle style) { fStyle = style; } + + const char* getFamilyName() const { return fFamilyName.c_str(); } + const char* getFullName() const { return fFullName.c_str(); } + const char* getPostscriptName() const { return fPostscriptName.c_str(); } + bool hasFontData() const { return fFontData.get() != nullptr; } + std::unique_ptr detachFontData() { return std::move(fFontData); } + + void setFamilyName(const char* name) { fFamilyName.set(name); } + void setFullName(const char* name) { fFullName.set(name); } + void setPostscriptName(const char* name) { fPostscriptName.set(name); } + /** Set the font data only if it is necessary for serialization. */ + void setFontData(std::unique_ptr data) { fFontData = std::move(data); } + +private: + SkString fFamilyName; + SkString fFullName; + SkString fPostscriptName; + std::unique_ptr fFontData; + + SkFontStyle fStyle; +}; + +#endif // SkFontDescriptor_DEFINED diff --git a/gfx/skia/skia/src/core/SkFontLCDConfig.cpp b/gfx/skia/skia/src/core/SkFontLCDConfig.cpp new file mode 100644 index 0000000000..f902e2c7ae --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontLCDConfig.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontLCDConfig.h" + +static SkFontLCDConfig::LCDOrientation gLCDOrientation = SkFontLCDConfig::kHorizontal_LCDOrientation; +static SkFontLCDConfig::LCDOrder gLCDOrder = SkFontLCDConfig::kRGB_LCDOrder; + +SkFontLCDConfig::LCDOrientation SkFontLCDConfig::GetSubpixelOrientation() { + return gLCDOrientation; +} + +void SkFontLCDConfig::SetSubpixelOrientation(LCDOrientation orientation) { + gLCDOrientation = orientation; +} + +SkFontLCDConfig::LCDOrder SkFontLCDConfig::GetSubpixelOrder() { + return gLCDOrder; +} + +void SkFontLCDConfig::SetSubpixelOrder(LCDOrder order) { + gLCDOrder = order; +} diff --git a/gfx/skia/skia/src/core/SkFontMgr.cpp b/gfx/skia/skia/src/core/SkFontMgr.cpp new file mode 100644 index 0000000000..d42b07d384 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontMgr.cpp @@ -0,0 +1,308 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkOnce.h" +#include "src/core/SkFontDescriptor.h" + +class SkFontStyle; +class SkTypeface; + +class SkEmptyFontStyleSet : public SkFontStyleSet { +public: + int count() override { return 0; } + void getStyle(int, SkFontStyle*, SkString*) override { + SkDEBUGFAIL("SkFontStyleSet::getStyle called on empty set"); + } + SkTypeface* createTypeface(int index) override { + SkDEBUGFAIL("SkFontStyleSet::createTypeface called on empty set"); + return nullptr; + } + SkTypeface* matchStyle(const SkFontStyle&) override { + return nullptr; + } +}; + +SkFontStyleSet* SkFontStyleSet::CreateEmpty() { return new SkEmptyFontStyleSet; } + +/////////////////////////////////////////////////////////////////////////////// + +class SkEmptyFontMgr : public SkFontMgr { +protected: + int onCountFamilies() const override { + return 0; + } + void onGetFamilyName(int index, SkString* familyName) const override { + SkDEBUGFAIL("onGetFamilyName called with bad index"); + } + SkFontStyleSet* onCreateStyleSet(int index) const override { + SkDEBUGFAIL("onCreateStyleSet called with bad index"); + return nullptr; + } + SkFontStyleSet* onMatchFamily(const char[]) const override { + return SkFontStyleSet::CreateEmpty(); + } + + SkTypeface* onMatchFamilyStyle(const char[], const SkFontStyle&) const override { + return nullptr; + } + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const override { + return nullptr; + } + SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override { + return nullptr; + } + + sk_sp onMakeFromData(sk_sp, int) const override { + return nullptr; + } + sk_sp onMakeFromStreamIndex(std::unique_ptr, int) const override { + return nullptr; + } + sk_sp onMakeFromStreamArgs(std::unique_ptr, + const SkFontArguments&) const override { + return nullptr; + } + sk_sp onMakeFromFontData(std::unique_ptr) const override { + return nullptr; + } + sk_sp onMakeFromFile(const char[], int) const override { + return nullptr; + } + sk_sp onLegacyMakeTypeface(const char [], SkFontStyle) const override { + return nullptr; + } +}; + +static SkFontStyleSet* emptyOnNull(SkFontStyleSet* fsset) { + if (nullptr == fsset) { + fsset = SkFontStyleSet::CreateEmpty(); + } + return fsset; +} + +int SkFontMgr::countFamilies() const { + return this->onCountFamilies(); +} + +void SkFontMgr::getFamilyName(int index, SkString* familyName) const { + this->onGetFamilyName(index, familyName); +} + +SkFontStyleSet* SkFontMgr::createStyleSet(int index) const { + return emptyOnNull(this->onCreateStyleSet(index)); +} + +SkFontStyleSet* SkFontMgr::matchFamily(const char familyName[]) const { + return emptyOnNull(this->onMatchFamily(familyName)); +} + +SkTypeface* SkFontMgr::matchFamilyStyle(const char familyName[], + const SkFontStyle& fs) const { + return this->onMatchFamilyStyle(familyName, fs); +} + +SkTypeface* SkFontMgr::matchFamilyStyleCharacter(const char familyName[], const SkFontStyle& style, + const char* bcp47[], int bcp47Count, + SkUnichar character) const { + return this->onMatchFamilyStyleCharacter(familyName, style, bcp47, bcp47Count, character); +} + +SkTypeface* SkFontMgr::matchFaceStyle(const SkTypeface* face, + const SkFontStyle& fs) const { + return this->onMatchFaceStyle(face, fs); +} + +sk_sp SkFontMgr::makeFromData(sk_sp data, int ttcIndex) const { + if (nullptr == data) { + return nullptr; + } + return this->onMakeFromData(std::move(data), ttcIndex); +} + +sk_sp SkFontMgr::makeFromStream(std::unique_ptr stream, + int ttcIndex) const { + if (nullptr == stream) { + return nullptr; + } + return this->onMakeFromStreamIndex(std::move(stream), ttcIndex); +} + +sk_sp SkFontMgr::makeFromStream(std::unique_ptr stream, + const SkFontArguments& args) const { + if (nullptr == stream) { + return nullptr; + } + return this->onMakeFromStreamArgs(std::move(stream), args); +} + +sk_sp SkFontMgr::makeFromFontData(std::unique_ptr data) const { + if (nullptr == data) { + return nullptr; + } + return this->onMakeFromFontData(std::move(data)); +} + +sk_sp SkFontMgr::makeFromFile(const char path[], int ttcIndex) const { + if (nullptr == path) { + return nullptr; + } + return this->onMakeFromFile(path, ttcIndex); +} + +sk_sp SkFontMgr::legacyMakeTypeface(const char familyName[], SkFontStyle style) const { + return this->onLegacyMakeTypeface(familyName, style); +} + +sk_sp SkFontMgr::onMakeFromStreamArgs(std::unique_ptr stream, + const SkFontArguments& args) const { + return this->makeFromStream(std::move(stream), args.getCollectionIndex()); +} +sk_sp SkFontMgr::onMakeFromFontData(std::unique_ptr data) const { + return this->makeFromStream(data->detachStream(), data->getIndex()); +} + +// A global function pointer that's not declared, but can be overriden at startup by test tools. +sk_sp (*gSkFontMgr_DefaultFactory)() = nullptr; + +sk_sp SkFontMgr::RefDefault() { + static SkOnce once; + static sk_sp singleton; + + once([]{ + sk_sp fm = gSkFontMgr_DefaultFactory ? gSkFontMgr_DefaultFactory() + : SkFontMgr::Factory(); + singleton = fm ? std::move(fm) : sk_make_sp(); + }); + return singleton; +} + +/** +* Width has the greatest priority. +* If the value of pattern.width is 5 (normal) or less, +* narrower width values are checked first, then wider values. +* If the value of pattern.width is greater than 5 (normal), +* wider values are checked first, followed by narrower values. +* +* Italic/Oblique has the next highest priority. +* If italic requested and there is some italic font, use it. +* If oblique requested and there is some oblique font, use it. +* If italic requested and there is some oblique font, use it. +* If oblique requested and there is some italic font, use it. +* +* Exact match. +* If pattern.weight < 400, weights below pattern.weight are checked +* in descending order followed by weights above pattern.weight +* in ascending order until a match is found. +* If pattern.weight > 500, weights above pattern.weight are checked +* in ascending order followed by weights below pattern.weight +* in descending order until a match is found. +* If pattern.weight is 400, 500 is checked first +* and then the rule for pattern.weight < 400 is used. +* If pattern.weight is 500, 400 is checked first +* and then the rule for pattern.weight < 400 is used. +*/ +SkTypeface* SkFontStyleSet::matchStyleCSS3(const SkFontStyle& pattern) { + int count = this->count(); + if (0 == count) { + return nullptr; + } + + struct Score { + int score; + int index; + Score& operator +=(int rhs) { this->score += rhs; return *this; } + Score& operator <<=(int rhs) { this->score <<= rhs; return *this; } + bool operator <(const Score& that) { return this->score < that.score; } + }; + + Score maxScore = { 0, 0 }; + for (int i = 0; i < count; ++i) { + SkFontStyle current; + this->getStyle(i, ¤t, nullptr); + Score currentScore = { 0, i }; + + // CSS stretch / SkFontStyle::Width + // Takes priority over everything else. + if (pattern.width() <= SkFontStyle::kNormal_Width) { + if (current.width() <= pattern.width()) { + currentScore += 10 - pattern.width() + current.width(); + } else { + currentScore += 10 - current.width(); + } + } else { + if (current.width() > pattern.width()) { + currentScore += 10 + pattern.width() - current.width(); + } else { + currentScore += current.width(); + } + } + currentScore <<= 8; + + // CSS style (normal, italic, oblique) / SkFontStyle::Slant (upright, italic, oblique) + // Takes priority over all valid weights. + static_assert(SkFontStyle::kUpright_Slant == 0 && + SkFontStyle::kItalic_Slant == 1 && + SkFontStyle::kOblique_Slant == 2, + "SkFontStyle::Slant values not as required."); + SkASSERT(0 <= pattern.slant() && pattern.slant() <= 2 && + 0 <= current.slant() && current.slant() <= 2); + static const int score[3][3] = { + /* Upright Italic Oblique [current]*/ + /* Upright */ { 3 , 1 , 2 }, + /* Italic */ { 1 , 3 , 2 }, + /* Oblique */ { 1 , 2 , 3 }, + /* [pattern] */ + }; + currentScore += score[pattern.slant()][current.slant()]; + currentScore <<= 8; + + // Synthetics (weight, style) [no stretch synthetic?] + + // CSS weight / SkFontStyle::Weight + // The 'closer' to the target weight, the higher the score. + // 1000 is the 'heaviest' recognized weight + if (pattern.weight() == current.weight()) { + currentScore += 1000; + // less than 400 prefer lighter weights + } else if (pattern.weight() < 400) { + if (current.weight() <= pattern.weight()) { + currentScore += 1000 - pattern.weight() + current.weight(); + } else { + currentScore += 1000 - current.weight(); + } + // between 400 and 500 prefer heavier up to 500, then lighter weights + } else if (pattern.weight() <= 500) { + if (current.weight() >= pattern.weight() && current.weight() <= 500) { + currentScore += 1000 + pattern.weight() - current.weight(); + } else if (current.weight() <= pattern.weight()) { + currentScore += 500 + current.weight(); + } else { + currentScore += 1000 - current.weight(); + } + // greater than 500 prefer heavier weights + } else if (pattern.weight() > 500) { + if (current.weight() > pattern.weight()) { + currentScore += 1000 + pattern.weight() - current.weight(); + } else { + currentScore += current.weight(); + } + } + + if (maxScore < currentScore) { + maxScore = currentScore; + } + } + + return this->createTypeface(maxScore.index); +} diff --git a/gfx/skia/skia/src/core/SkFontMgrPriv.h b/gfx/skia/skia/src/core/SkFontMgrPriv.h new file mode 100644 index 0000000000..40cf264037 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontMgrPriv.h @@ -0,0 +1,14 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkFontMgrPriv_DEFINED +#define SkFontMgrPriv_DEFINED + +#include "include/core/SkFontMgr.h" + +extern sk_sp (*gSkFontMgr_DefaultFactory)(); + +#endif // SkFontMgrPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkFontPriv.h b/gfx/skia/skia/src/core/SkFontPriv.h new file mode 100644 index 0000000000..42fdc29688 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontPriv.h @@ -0,0 +1,104 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontPriv_DEFINED +#define SkFontPriv_DEFINED + +#include "include/core/SkFont.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkTypeface.h" + +class SkReadBuffer; +class SkWriteBuffer; + +class SkFontPriv { +public: + /* This is the size we use when we ask for a glyph's path. We then + * post-transform it as we draw to match the request. + * This is done to try to re-use cache entries for the path. + * + * This value is somewhat arbitrary. In theory, it could be 1, since + * we store paths as floats. However, we get the path from the font + * scaler, and it may represent its paths as fixed-point (or 26.6), + * so we shouldn't ask for something too big (might overflow 16.16) + * or too small (underflow 26.6). + * + * This value could track kMaxSizeForGlyphCache, assuming the above + * constraints, but since we ask for unhinted paths, the two values + * need not match per-se. + */ + static constexpr int kCanonicalTextSizeForPaths = 64; + + /** + * Return a matrix that applies the paint's text values: size, scale, skew + */ + static SkMatrix MakeTextMatrix(SkScalar size, SkScalar scaleX, SkScalar skewX) { + SkMatrix m = SkMatrix::MakeScale(size * scaleX, size); + if (skewX) { + m.postSkew(skewX, 0); + } + return m; + } + + static SkMatrix MakeTextMatrix(const SkFont& font) { + return MakeTextMatrix(font.getSize(), font.getScaleX(), font.getSkewX()); + } + + static void ScaleFontMetrics(SkFontMetrics*, SkScalar); + + /** + Returns the union of bounds of all glyphs. + Returned dimensions are computed by font manager from font data, + ignoring SkPaint::Hinting. Includes font metrics, but not fake bold or SkPathEffect. + + If text size is large, text scale is one, and text skew is zero, + returns the bounds as: + { SkFontMetrics::fXMin, SkFontMetrics::fTop, SkFontMetrics::fXMax, SkFontMetrics::fBottom }. + + @return union of bounds of all glyphs + */ + static SkRect GetFontBounds(const SkFont&); + + static bool IsFinite(const SkFont& font) { + return SkScalarIsFinite(font.getSize()) && + SkScalarIsFinite(font.getScaleX()) && + SkScalarIsFinite(font.getSkewX()); + } + + // Returns the number of elements (characters or glyphs) in the array. + static int CountTextElements(const void* text, size_t byteLength, SkTextEncoding); + + static void GlyphsToUnichars(const SkFont&, const uint16_t glyphs[], int count, SkUnichar[]); + + static void Flatten(const SkFont&, SkWriteBuffer& buffer); + static bool Unflatten(SkFont*, SkReadBuffer& buffer); +}; + +class SkAutoToGlyphs { +public: + SkAutoToGlyphs(const SkFont& font, const void* text, size_t length, SkTextEncoding encoding) { + if (encoding == SkTextEncoding::kGlyphID || length == 0) { + fGlyphs = reinterpret_cast(text); + fCount = length >> 1; + } else { + fCount = font.countText(text, length, encoding); + fStorage.reset(fCount); + font.textToGlyphs(text, length, encoding, fStorage.get(), fCount); + fGlyphs = fStorage.get(); + } + } + + int count() const { return fCount; } + const uint16_t* glyphs() const { return fGlyphs; } + +private: + SkAutoSTArray<32, uint16_t> fStorage; + const uint16_t* fGlyphs; + int fCount; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkFontStream.cpp b/gfx/skia/skia/src/core/SkFontStream.cpp new file mode 100644 index 0000000000..54c8cabf2f --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontStream.cpp @@ -0,0 +1,211 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkEndian.h" +#include "src/core/SkFontStream.h" + +struct SkSFNTHeader { + uint32_t fVersion; + uint16_t fNumTables; + uint16_t fSearchRange; + uint16_t fEntrySelector; + uint16_t fRangeShift; +}; + +struct SkTTCFHeader { + uint32_t fTag; + uint32_t fVersion; + uint32_t fNumOffsets; + uint32_t fOffset0; // the first of N (fNumOffsets) +}; + +union SkSharedTTHeader { + SkSFNTHeader fSingle; + SkTTCFHeader fCollection; +}; + +struct SkSFNTDirEntry { + uint32_t fTag; + uint32_t fChecksum; + uint32_t fOffset; + uint32_t fLength; +}; + +static bool read(SkStream* stream, void* buffer, size_t amount) { + return stream->read(buffer, amount) == amount; +} + +static bool skip(SkStream* stream, size_t amount) { + return stream->skip(amount) == amount; +} + +/** Return the number of tables, or if this is a TTC (collection), return the + number of tables in the first element of the collection. In either case, + if offsetToDir is not-null, set it to the offset to the beginning of the + table headers (SkSFNTDirEntry), relative to the start of the stream. + + On an error, return 0 for number of tables, and ignore offsetToDir + */ +static int count_tables(SkStream* stream, int ttcIndex, size_t* offsetToDir) { + SkASSERT(ttcIndex >= 0); + + SkAutoSMalloc<1024> storage(sizeof(SkSharedTTHeader)); + SkSharedTTHeader* header = (SkSharedTTHeader*)storage.get(); + + if (!read(stream, header, sizeof(SkSharedTTHeader))) { + return 0; + } + + // by default, SkSFNTHeader is at the start of the stream + size_t offset = 0; + + // if we're really a collection, the first 4-bytes will be 'ttcf' + uint32_t tag = SkEndian_SwapBE32(header->fCollection.fTag); + if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) { + unsigned count = SkEndian_SwapBE32(header->fCollection.fNumOffsets); + if ((unsigned)ttcIndex >= count) { + return 0; + } + + if (ttcIndex > 0) { // need to read more of the shared header + stream->rewind(); + size_t amount = sizeof(SkSharedTTHeader) + ttcIndex * sizeof(uint32_t); + header = (SkSharedTTHeader*)storage.reset(amount); + if (!read(stream, header, amount)) { + return 0; + } + } + // this is the offset to the local SkSFNTHeader + offset = SkEndian_SwapBE32((&header->fCollection.fOffset0)[ttcIndex]); + stream->rewind(); + if (!skip(stream, offset)) { + return 0; + } + if (!read(stream, header, sizeof(SkSFNTHeader))) { + return 0; + } + } + + if (offsetToDir) { + // add the size of the header, so we will point to the DirEntries + *offsetToDir = offset + sizeof(SkSFNTHeader); + } + return SkEndian_SwapBE16(header->fSingle.fNumTables); +} + +/////////////////////////////////////////////////////////////////////////////// + +struct SfntHeader { + SfntHeader() : fCount(0), fDir(nullptr) {} + ~SfntHeader() { sk_free(fDir); } + + /** If it returns true, then fCount and fDir are properly initialized. + Note: fDir will point to the raw array of SkSFNTDirEntry values, + meaning they will still be in the file's native endianness (BE). + + fDir will be automatically freed when this object is destroyed + */ + bool init(SkStream* stream, int ttcIndex) { + stream->rewind(); + + size_t offsetToDir; + fCount = count_tables(stream, ttcIndex, &offsetToDir); + if (0 == fCount) { + return false; + } + + stream->rewind(); + if (!skip(stream, offsetToDir)) { + return false; + } + + size_t size = fCount * sizeof(SkSFNTDirEntry); + fDir = reinterpret_cast(sk_malloc_throw(size)); + return read(stream, fDir, size); + } + + int fCount; + SkSFNTDirEntry* fDir; +}; + +/////////////////////////////////////////////////////////////////////////////// + +int SkFontStream::CountTTCEntries(SkStream* stream) { + stream->rewind(); + + SkSharedTTHeader shared; + if (!read(stream, &shared, sizeof(shared))) { + return 0; + } + + // if we're really a collection, the first 4-bytes will be 'ttcf' + uint32_t tag = SkEndian_SwapBE32(shared.fCollection.fTag); + if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) { + return SkEndian_SwapBE32(shared.fCollection.fNumOffsets); + } else { + return 1; // normal 'sfnt' has 1 dir entry + } +} + +int SkFontStream::GetTableTags(SkStream* stream, int ttcIndex, + SkFontTableTag tags[]) { + SfntHeader header; + if (!header.init(stream, ttcIndex)) { + return 0; + } + + if (tags) { + for (int i = 0; i < header.fCount; i++) { + tags[i] = SkEndian_SwapBE32(header.fDir[i].fTag); + } + } + return header.fCount; +} + +size_t SkFontStream::GetTableData(SkStream* stream, int ttcIndex, + SkFontTableTag tag, + size_t offset, size_t length, void* data) { + SfntHeader header; + if (!header.init(stream, ttcIndex)) { + return 0; + } + + for (int i = 0; i < header.fCount; i++) { + if (SkEndian_SwapBE32(header.fDir[i].fTag) == tag) { + size_t realOffset = SkEndian_SwapBE32(header.fDir[i].fOffset); + size_t realLength = SkEndian_SwapBE32(header.fDir[i].fLength); + // now sanity check the caller's offset/length + if (offset >= realLength) { + return 0; + } + // if the caller is trusting the length from the file, then a + // hostile file might choose a value which would overflow offset + + // length. + if (offset + length < offset) { + return 0; + } + if (length > realLength - offset) { + length = realLength - offset; + } + if (data) { + // skip the stream to the part of the table we want to copy from + stream->rewind(); + size_t bytesToSkip = realOffset + offset; + if (!skip(stream, bytesToSkip)) { + return 0; + } + if (!read(stream, data, length)) { + return 0; + } + } + return length; + } + } + return 0; +} diff --git a/gfx/skia/skia/src/core/SkFontStream.h b/gfx/skia/skia/src/core/SkFontStream.h new file mode 100644 index 0000000000..57f0e85137 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFontStream.h @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontStream_DEFINED +#define SkFontStream_DEFINED + +class SkStream; + +#include "include/core/SkTypeface.h" + +class SkFontStream { +public: + /** + * Return the number of shared directories inside a TTC sfnt, or return 1 + * if the stream is a normal sfnt (ttf). If there is an error or + * no directory is found, return 0. + * + * Note: the stream is rewound initially, but is returned at an arbitrary + * read offset. + */ + static int CountTTCEntries(SkStream*); + + /** + * @param ttcIndex 0 for normal sfnts, or the index within a TTC sfnt. + * + * Note: the stream is rewound initially, but is returned at an arbitrary + * read offset. + */ + static int GetTableTags(SkStream*, int ttcIndex, SkFontTableTag tags[]); + + /** + * @param ttcIndex 0 for normal sfnts, or the index within a TTC sfnt. + * + * Note: the stream is rewound initially, but is returned at an arbitrary + * read offset. + */ + static size_t GetTableData(SkStream*, int ttcIndex, SkFontTableTag tag, + size_t offset, size_t length, void* data); + + static size_t GetTableSize(SkStream* stream, int ttcIndex, SkFontTableTag tag) { + return GetTableData(stream, ttcIndex, tag, 0, ~0U, nullptr); + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkForceCPlusPlusLinking.cpp b/gfx/skia/skia/src/core/SkForceCPlusPlusLinking.cpp new file mode 100644 index 0000000000..829d0d3fe6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkForceCPlusPlusLinking.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// This file is intentionally empty. We add it to the dependencies of skia_lib +// so that GYP detects that libskia is a C++ library (implicitly depending on +// the standard library, -lm, etc.) from its file extension. +// +// If we didn't do this, GYP would link libskia.so as a C library and we'd get +// link-time failures for simple binaries that don't themselves depend on the +// C++ standard library. +// +// Even if we try hard not to depend on the standard library, say, never +// calling new or delete, the compiler can still insert calls on our behalf +// that make us depend on it anyway: a handler when we call a for a pure +// virtual, thread-safety guards around statics, probably other similar +// language constructs. diff --git a/gfx/skia/skia/src/core/SkFuzzLogging.h b/gfx/skia/skia/src/core/SkFuzzLogging.h new file mode 100644 index 0000000000..8e546e3a06 --- /dev/null +++ b/gfx/skia/skia/src/core/SkFuzzLogging.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFuzzLogging_DEFINED +#define SkFuzzLogging_DEFINED + +// Utilities for Skia's fuzzer + +// When SK_FUZZ_LOGGING is defined SkDebugfs relevant to image filter fuzzing +// will be enabled. This allows the filter fuzzing code to white list fuzzer +// failures based on the output logs. +// Define this flag in your SkUserConfig.h or in your Make/Build system. +#ifdef SK_FUZZ_LOGGING + #define SkFUZZF(args) SkDebugf("SkFUZZ: "); SkDebugf args +#else + #define SkFUZZF(args) +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkGaussFilter.cpp b/gfx/skia/skia/src/core/SkGaussFilter.cpp new file mode 100644 index 0000000000..e80092e6e1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGaussFilter.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkTypes.h" +#include "include/private/SkFloatingPoint.h" +#include "src/core/SkGaussFilter.h" +#include + +// The value when we can stop expanding the filter. The spec implies that 3% is acceptable, but +// we just use 1%. +static constexpr double kGoodEnough = 1.0 / 100.0; + +// Normalize the values of gauss to 1.0, and make sure they add to one. +// NB if n == 1, then this will force gauss[0] == 1. +static void normalize(int n, double* gauss) { + // Carefully add from smallest to largest to calculate the normalizing sum. + double sum = 0; + for (int i = n-1; i >= 1; i--) { + sum += 2 * gauss[i]; + } + sum += gauss[0]; + + // Normalize gauss. + for (int i = 0; i < n; i++) { + gauss[i] /= sum; + } + + // The factors should sum to 1. Take any remaining slop, and add it to gauss[0]. Add the + // values in such a way to maintain the most accuracy. + sum = 0; + for (int i = n - 1; i >= 1; i--) { + sum += 2 * gauss[i]; + } + + gauss[0] = 1 - sum; +} + +static int calculate_bessel_factors(double sigma, double *gauss) { + auto var = sigma * sigma; + + // The two functions below come from the equations in "Handbook of Mathematical Functions" + // by Abramowitz and Stegun. Specifically, equation 9.6.10 on page 375. Bessel0 is given + // explicitly as 9.6.12 + // BesselI_0 for 0 <= sigma < 2. + // NB the k = 0 factor is just sum = 1.0. + auto besselI_0 = [](double t) -> double { + auto tSquaredOver4 = t * t / 4.0; + auto sum = 1.0; + auto factor = 1.0; + auto k = 1; + // Use a variable number of loops. When sigma is small, this only requires 3-4 loops, but + // when sigma is near 2, it could require 10 loops. The same holds for BesselI_1. + while(factor > 1.0/1000000.0) { + factor *= tSquaredOver4 / (k * k); + sum += factor; + k += 1; + } + return sum; + }; + // BesselI_1 for 0 <= sigma < 2. + auto besselI_1 = [](double t) -> double { + auto tSquaredOver4 = t * t / 4.0; + auto sum = t / 2.0; + auto factor = sum; + auto k = 1; + while (factor > 1.0/1000000.0) { + factor *= tSquaredOver4 / (k * (k + 1)); + sum += factor; + k += 1; + } + return sum; + }; + + // The following formula for calculating the Gaussian kernel is from + // "Scale-Space for Discrete Signals" by Tony Lindeberg. + // gauss(n; var) = besselI_n(var) / (e^var) + auto d = std::exp(var); + double b[SkGaussFilter::kGaussArrayMax] = {besselI_0(var), besselI_1(var)}; + gauss[0] = b[0]/d; + gauss[1] = b[1]/d; + + // The code below is tricky, and written to mirror the recursive equations from the book. + // The maximum spread for sigma == 2 is guass[4], but in order to know to stop guass[5] + // is calculated. At this point n == 5 meaning that gauss[0..4] are the factors, but a 6th + // element was used to calculate them. + int n = 1; + // The recurrence relation below is from "Numerical Recipes" 3rd Edition. + // Equation 6.5.16 p.282 + while (gauss[n] > kGoodEnough) { + b[n+1] = -(2*n/var) * b[n] + b[n-1]; + gauss[n+1] = b[n+1] / d; + n += 1; + } + + normalize(n, gauss); + + return n; +} + +SkGaussFilter::SkGaussFilter(double sigma) { + SkASSERT(0 <= sigma && sigma < 2); + + fN = calculate_bessel_factors(sigma, fBasis); +} diff --git a/gfx/skia/skia/src/core/SkGaussFilter.h b/gfx/skia/skia/src/core/SkGaussFilter.h new file mode 100644 index 0000000000..1cf6eee1bc --- /dev/null +++ b/gfx/skia/skia/src/core/SkGaussFilter.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGaussFilter_DEFINED +#define SkGaussFilter_DEFINED + +#include + +// Define gaussian filters for values of sigma < 2. Produce values good to 1 part in 1,000,000. +// Produces values as defined in "Scale-Space for Discrete Signals" by Tony Lindeberg. +class SkGaussFilter { +public: + static constexpr int kGaussArrayMax = 6; + + explicit SkGaussFilter(double sigma); + + size_t size() const { return fN; } + int radius() const { return fN - 1; } + int width() const { return 2 * this->radius() + 1; } + + // Allow a filter to be used in a C++ ranged-for loop. + const double* begin() const { return &fBasis[0]; } + const double* end() const { return &fBasis[fN]; } + +private: + double fBasis[kGaussArrayMax]; + int fN; +}; + +#endif // SkGaussFilter_DEFINED diff --git a/gfx/skia/skia/src/core/SkGeometry.cpp b/gfx/skia/skia/src/core/SkGeometry.cpp new file mode 100644 index 0000000000..daed95579f --- /dev/null +++ b/gfx/skia/skia/src/core/SkGeometry.cpp @@ -0,0 +1,1491 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "include/core/SkPoint3.h" +#include "include/private/SkNx.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPointPriv.h" + +#include + +static SkVector to_vector(const Sk2s& x) { + SkVector vector; + x.store(&vector); + return vector; +} + +//////////////////////////////////////////////////////////////////////// + +static int is_not_monotonic(SkScalar a, SkScalar b, SkScalar c) { + SkScalar ab = a - b; + SkScalar bc = b - c; + if (ab < 0) { + bc = -bc; + } + return ab == 0 || bc < 0; +} + +//////////////////////////////////////////////////////////////////////// + +static int valid_unit_divide(SkScalar numer, SkScalar denom, SkScalar* ratio) { + SkASSERT(ratio); + + if (numer < 0) { + numer = -numer; + denom = -denom; + } + + if (denom == 0 || numer == 0 || numer >= denom) { + return 0; + } + + SkScalar r = numer / denom; + if (SkScalarIsNaN(r)) { + return 0; + } + SkASSERTF(r >= 0 && r < SK_Scalar1, "numer %f, denom %f, r %f", numer, denom, r); + if (r == 0) { // catch underflow if numer <<<< denom + return 0; + } + *ratio = r; + return 1; +} + +// Just returns its argument, but makes it easy to set a break-point to know when +// SkFindUnitQuadRoots is going to return 0 (an error). +static int return_check_zero(int value) { + if (value == 0) { + return 0; + } + return value; +} + +/** From Numerical Recipes in C. + + Q = -1/2 (B + sign(B) sqrt[B*B - 4*A*C]) + x1 = Q / A + x2 = C / Q +*/ +int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2]) { + SkASSERT(roots); + + if (A == 0) { + return return_check_zero(valid_unit_divide(-C, B, roots)); + } + + SkScalar* r = roots; + + // use doubles so we don't overflow temporarily trying to compute R + double dr = (double)B * B - 4 * (double)A * C; + if (dr < 0) { + return return_check_zero(0); + } + dr = sqrt(dr); + SkScalar R = SkDoubleToScalar(dr); + if (!SkScalarIsFinite(R)) { + return return_check_zero(0); + } + + SkScalar Q = (B < 0) ? -(B-R)/2 : -(B+R)/2; + r += valid_unit_divide(Q, A, r); + r += valid_unit_divide(C, Q, r); + if (r - roots == 2) { + if (roots[0] > roots[1]) { + using std::swap; + swap(roots[0], roots[1]); + } else if (roots[0] == roots[1]) { // nearly-equal? + r -= 1; // skip the double root + } + } + return return_check_zero((int)(r - roots)); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent) { + SkASSERT(src); + SkASSERT(t >= 0 && t <= SK_Scalar1); + + if (pt) { + *pt = SkEvalQuadAt(src, t); + } + if (tangent) { + *tangent = SkEvalQuadTangentAt(src, t); + } +} + +SkPoint SkEvalQuadAt(const SkPoint src[3], SkScalar t) { + return to_point(SkQuadCoeff(src).eval(t)); +} + +SkVector SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t) { + // The derivative equation is 2(b - a +(a - 2b +c)t). This returns a + // zero tangent vector when t is 0 or 1, and the control point is equal + // to the end point. In this case, use the quad end points to compute the tangent. + if ((t == 0 && src[0] == src[1]) || (t == 1 && src[1] == src[2])) { + return src[2] - src[0]; + } + SkASSERT(src); + SkASSERT(t >= 0 && t <= SK_Scalar1); + + Sk2s P0 = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); + + Sk2s B = P1 - P0; + Sk2s A = P2 - P1 - B; + Sk2s T = A * Sk2s(t) + B; + + return to_vector(T + T); +} + +static inline Sk2s interp(const Sk2s& v0, const Sk2s& v1, const Sk2s& t) { + return v0 + (v1 - v0) * t; +} + +void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t) { + SkASSERT(t > 0 && t < SK_Scalar1); + + Sk2s p0 = from_point(src[0]); + Sk2s p1 = from_point(src[1]); + Sk2s p2 = from_point(src[2]); + Sk2s tt(t); + + Sk2s p01 = interp(p0, p1, tt); + Sk2s p12 = interp(p1, p2, tt); + + dst[0] = to_point(p0); + dst[1] = to_point(p01); + dst[2] = to_point(interp(p01, p12, tt)); + dst[3] = to_point(p12); + dst[4] = to_point(p2); +} + +void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) { + SkChopQuadAt(src, dst, 0.5f); +} + +/** Quad'(t) = At + B, where + A = 2(a - 2b + c) + B = 2(b - a) + Solve for t, only if it fits between 0 < t < 1 +*/ +int SkFindQuadExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar tValue[1]) { + /* At + B == 0 + t = -B / A + */ + return valid_unit_divide(a - b, a - b - b + c, tValue); +} + +static inline void flatten_double_quad_extrema(SkScalar coords[14]) { + coords[2] = coords[6] = coords[4]; +} + +/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is + stored in dst[]. Guarantees that the 1/2 quads will be monotonic. + */ +int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5]) { + SkASSERT(src); + SkASSERT(dst); + + SkScalar a = src[0].fY; + SkScalar b = src[1].fY; + SkScalar c = src[2].fY; + + if (is_not_monotonic(a, b, c)) { + SkScalar tValue; + if (valid_unit_divide(a - b, a - b - b + c, &tValue)) { + SkChopQuadAt(src, dst, tValue); + flatten_double_quad_extrema(&dst[0].fY); + return 1; + } + // if we get here, we need to force dst to be monotonic, even though + // we couldn't compute a unit_divide value (probably underflow). + b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c; + } + dst[0].set(src[0].fX, a); + dst[1].set(src[1].fX, b); + dst[2].set(src[2].fX, c); + return 0; +} + +/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is + stored in dst[]. Guarantees that the 1/2 quads will be monotonic. + */ +int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5]) { + SkASSERT(src); + SkASSERT(dst); + + SkScalar a = src[0].fX; + SkScalar b = src[1].fX; + SkScalar c = src[2].fX; + + if (is_not_monotonic(a, b, c)) { + SkScalar tValue; + if (valid_unit_divide(a - b, a - b - b + c, &tValue)) { + SkChopQuadAt(src, dst, tValue); + flatten_double_quad_extrema(&dst[0].fX); + return 1; + } + // if we get here, we need to force dst to be monotonic, even though + // we couldn't compute a unit_divide value (probably underflow). + b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c; + } + dst[0].set(a, src[0].fY); + dst[1].set(b, src[1].fY); + dst[2].set(c, src[2].fY); + return 0; +} + +// F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2 +// F'(t) = 2 (b - a) + 2 (a - 2b + c) t +// F''(t) = 2 (a - 2b + c) +// +// A = 2 (b - a) +// B = 2 (a - 2b + c) +// +// Maximum curvature for a quadratic means solving +// Fx' Fx'' + Fy' Fy'' = 0 +// +// t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2) +// +SkScalar SkFindQuadMaxCurvature(const SkPoint src[3]) { + SkScalar Ax = src[1].fX - src[0].fX; + SkScalar Ay = src[1].fY - src[0].fY; + SkScalar Bx = src[0].fX - src[1].fX - src[1].fX + src[2].fX; + SkScalar By = src[0].fY - src[1].fY - src[1].fY + src[2].fY; + + SkScalar numer = -(Ax * Bx + Ay * By); + SkScalar denom = Bx * Bx + By * By; + if (denom < 0) { + numer = -numer; + denom = -denom; + } + if (numer <= 0) { + return 0; + } + if (numer >= denom) { // Also catches denom=0. + return 1; + } + SkScalar t = numer / denom; + SkASSERT((0 <= t && t < 1) || SkScalarIsNaN(t)); + return t; +} + +int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5]) { + SkScalar t = SkFindQuadMaxCurvature(src); + if (t == 0 || t == 1) { + memcpy(dst, src, 3 * sizeof(SkPoint)); + return 1; + } else { + SkChopQuadAt(src, dst, t); + return 2; + } +} + +void SkConvertQuadToCubic(const SkPoint src[3], SkPoint dst[4]) { + Sk2s scale(SkDoubleToScalar(2.0 / 3.0)); + Sk2s s0 = from_point(src[0]); + Sk2s s1 = from_point(src[1]); + Sk2s s2 = from_point(src[2]); + + dst[0] = to_point(s0); + dst[1] = to_point(s0 + (s1 - s0) * scale); + dst[2] = to_point(s2 + (s1 - s2) * scale); + dst[3] = to_point(s2); +} + +////////////////////////////////////////////////////////////////////////////// +///// CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS ///// +////////////////////////////////////////////////////////////////////////////// + +static SkVector eval_cubic_derivative(const SkPoint src[4], SkScalar t) { + SkQuadCoeff coeff; + Sk2s P0 = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); + Sk2s P3 = from_point(src[3]); + + coeff.fA = P3 + Sk2s(3) * (P1 - P2) - P0; + coeff.fB = times_2(P2 - times_2(P1) + P0); + coeff.fC = P1 - P0; + return to_vector(coeff.eval(t)); +} + +static SkVector eval_cubic_2ndDerivative(const SkPoint src[4], SkScalar t) { + Sk2s P0 = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); + Sk2s P3 = from_point(src[3]); + Sk2s A = P3 + Sk2s(3) * (P1 - P2) - P0; + Sk2s B = P2 - times_2(P1) + P0; + + return to_vector(A * Sk2s(t) + B); +} + +void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint* loc, + SkVector* tangent, SkVector* curvature) { + SkASSERT(src); + SkASSERT(t >= 0 && t <= SK_Scalar1); + + if (loc) { + *loc = to_point(SkCubicCoeff(src).eval(t)); + } + if (tangent) { + // The derivative equation returns a zero tangent vector when t is 0 or 1, and the + // adjacent control point is equal to the end point. In this case, use the + // next control point or the end points to compute the tangent. + if ((t == 0 && src[0] == src[1]) || (t == 1 && src[2] == src[3])) { + if (t == 0) { + *tangent = src[2] - src[0]; + } else { + *tangent = src[3] - src[1]; + } + if (!tangent->fX && !tangent->fY) { + *tangent = src[3] - src[0]; + } + } else { + *tangent = eval_cubic_derivative(src, t); + } + } + if (curvature) { + *curvature = eval_cubic_2ndDerivative(src, t); + } +} + +/** Cubic'(t) = At^2 + Bt + C, where + A = 3(-a + 3(b - c) + d) + B = 6(a - 2b + c) + C = 3(b - a) + Solve for t, keeping only those that fit betwee 0 < t < 1 +*/ +int SkFindCubicExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar d, + SkScalar tValues[2]) { + // we divide A,B,C by 3 to simplify + SkScalar A = d - a + 3*(b - c); + SkScalar B = 2*(a - b - b + c); + SkScalar C = b - a; + + return SkFindUnitQuadRoots(A, B, C, tValues); +} + +void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t) { + SkASSERT(t > 0 && t < SK_Scalar1); + + Sk2s p0 = from_point(src[0]); + Sk2s p1 = from_point(src[1]); + Sk2s p2 = from_point(src[2]); + Sk2s p3 = from_point(src[3]); + Sk2s tt(t); + + Sk2s ab = interp(p0, p1, tt); + Sk2s bc = interp(p1, p2, tt); + Sk2s cd = interp(p2, p3, tt); + Sk2s abc = interp(ab, bc, tt); + Sk2s bcd = interp(bc, cd, tt); + Sk2s abcd = interp(abc, bcd, tt); + + dst[0] = to_point(p0); + dst[1] = to_point(ab); + dst[2] = to_point(abc); + dst[3] = to_point(abcd); + dst[4] = to_point(bcd); + dst[5] = to_point(cd); + dst[6] = to_point(p3); +} + +/* http://code.google.com/p/skia/issues/detail?id=32 + + This test code would fail when we didn't check the return result of + valid_unit_divide in SkChopCubicAt(... tValues[], int roots). The reason is + that after the first chop, the parameters to valid_unit_divide are equal + (thanks to finite float precision and rounding in the subtracts). Thus + even though the 2nd tValue looks < 1.0, after we renormalize it, we end + up with 1.0, hence the need to check and just return the last cubic as + a degenerate clump of 4 points in the sampe place. + + static void test_cubic() { + SkPoint src[4] = { + { 556.25000, 523.03003 }, + { 556.23999, 522.96002 }, + { 556.21997, 522.89001 }, + { 556.21997, 522.82001 } + }; + SkPoint dst[10]; + SkScalar tval[] = { 0.33333334f, 0.99999994f }; + SkChopCubicAt(src, dst, tval, 2); + } + */ + +void SkChopCubicAt(const SkPoint src[4], SkPoint dst[], + const SkScalar tValues[], int roots) { +#ifdef SK_DEBUG + { + for (int i = 0; i < roots - 1; i++) + { + SkASSERT(0 < tValues[i] && tValues[i] < 1); + SkASSERT(0 < tValues[i+1] && tValues[i+1] < 1); + SkASSERT(tValues[i] < tValues[i+1]); + } + } +#endif + + if (dst) { + if (roots == 0) { // nothing to chop + memcpy(dst, src, 4*sizeof(SkPoint)); + } else { + SkScalar t = tValues[0]; + SkPoint tmp[4]; + + for (int i = 0; i < roots; i++) { + SkChopCubicAt(src, dst, t); + if (i == roots - 1) { + break; + } + + dst += 3; + // have src point to the remaining cubic (after the chop) + memcpy(tmp, dst, 4 * sizeof(SkPoint)); + src = tmp; + + // watch out in case the renormalized t isn't in range + if (!valid_unit_divide(tValues[i+1] - tValues[i], + SK_Scalar1 - tValues[i], &t)) { + // if we can't, just create a degenerate cubic + dst[4] = dst[5] = dst[6] = src[3]; + break; + } + } + } + } +} + +void SkChopCubicAtHalf(const SkPoint src[4], SkPoint dst[7]) { + SkChopCubicAt(src, dst, 0.5f); +} + +static void flatten_double_cubic_extrema(SkScalar coords[14]) { + coords[4] = coords[8] = coords[6]; +} + +/** Given 4 points on a cubic bezier, chop it into 1, 2, 3 beziers such that + the resulting beziers are monotonic in Y. This is called by the scan + converter. Depending on what is returned, dst[] is treated as follows: + 0 dst[0..3] is the original cubic + 1 dst[0..3] and dst[3..6] are the two new cubics + 2 dst[0..3], dst[3..6], dst[6..9] are the three new cubics + If dst == null, it is ignored and only the count is returned. +*/ +int SkChopCubicAtYExtrema(const SkPoint src[4], SkPoint dst[10]) { + SkScalar tValues[2]; + int roots = SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, + src[3].fY, tValues); + + SkChopCubicAt(src, dst, tValues, roots); + if (dst && roots > 0) { + // we do some cleanup to ensure our Y extrema are flat + flatten_double_cubic_extrema(&dst[0].fY); + if (roots == 2) { + flatten_double_cubic_extrema(&dst[3].fY); + } + } + return roots; +} + +int SkChopCubicAtXExtrema(const SkPoint src[4], SkPoint dst[10]) { + SkScalar tValues[2]; + int roots = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, + src[3].fX, tValues); + + SkChopCubicAt(src, dst, tValues, roots); + if (dst && roots > 0) { + // we do some cleanup to ensure our Y extrema are flat + flatten_double_cubic_extrema(&dst[0].fX); + if (roots == 2) { + flatten_double_cubic_extrema(&dst[3].fX); + } + } + return roots; +} + +/** http://www.faculty.idc.ac.il/arik/quality/appendixA.html + + Inflection means that curvature is zero. + Curvature is [F' x F''] / [F'^3] + So we solve F'x X F''y - F'y X F''y == 0 + After some canceling of the cubic term, we get + A = b - a + B = c - 2b + a + C = d - 3c + 3b - a + (BxCy - ByCx)t^2 + (AxCy - AyCx)t + AxBy - AyBx == 0 +*/ +int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[]) { + SkScalar Ax = src[1].fX - src[0].fX; + SkScalar Ay = src[1].fY - src[0].fY; + SkScalar Bx = src[2].fX - 2 * src[1].fX + src[0].fX; + SkScalar By = src[2].fY - 2 * src[1].fY + src[0].fY; + SkScalar Cx = src[3].fX + 3 * (src[1].fX - src[2].fX) - src[0].fX; + SkScalar Cy = src[3].fY + 3 * (src[1].fY - src[2].fY) - src[0].fY; + + return SkFindUnitQuadRoots(Bx*Cy - By*Cx, + Ax*Cy - Ay*Cx, + Ax*By - Ay*Bx, + tValues); +} + +int SkChopCubicAtInflections(const SkPoint src[], SkPoint dst[10]) { + SkScalar tValues[2]; + int count = SkFindCubicInflections(src, tValues); + + if (dst) { + if (count == 0) { + memcpy(dst, src, 4 * sizeof(SkPoint)); + } else { + SkChopCubicAt(src, dst, tValues, count); + } + } + return count + 1; +} + +// Assumes the third component of points is 1. +// Calcs p0 . (p1 x p2) +static double calc_dot_cross_cubic(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) { + const double xComp = (double) p0.fX * ((double) p1.fY - (double) p2.fY); + const double yComp = (double) p0.fY * ((double) p2.fX - (double) p1.fX); + const double wComp = (double) p1.fX * (double) p2.fY - (double) p1.fY * (double) p2.fX; + return (xComp + yComp + wComp); +} + +// Returns a positive power of 2 that, when multiplied by n, and excepting the two edge cases listed +// below, shifts the exponent of n to yield a magnitude somewhere inside [1..2). +// Returns 2^1023 if abs(n) < 2^-1022 (including 0). +// Returns NaN if n is Inf or NaN. +inline static double previous_inverse_pow2(double n) { + uint64_t bits; + memcpy(&bits, &n, sizeof(double)); + bits = ((1023llu*2 << 52) + ((1llu << 52) - 1)) - bits; // exp=-exp + bits &= (0x7ffllu) << 52; // mantissa=1.0, sign=0 + memcpy(&n, &bits, sizeof(double)); + return n; +} + +inline static void write_cubic_inflection_roots(double t0, double s0, double t1, double s1, + double* t, double* s) { + t[0] = t0; + s[0] = s0; + + // This copysign/abs business orients the implicit function so positive values are always on the + // "left" side of the curve. + t[1] = -copysign(t1, t1 * s1); + s[1] = -fabs(s1); + + // Ensure t[0]/s[0] <= t[1]/s[1] (s[1] is negative from above). + if (copysign(s[1], s[0]) * t[0] > -fabs(s[0]) * t[1]) { + using std::swap; + swap(t[0], t[1]); + swap(s[0], s[1]); + } +} + +SkCubicType SkClassifyCubic(const SkPoint P[4], double t[2], double s[2], double d[4]) { + // Find the cubic's inflection function, I = [T^3 -3T^2 3T -1] dot D. (D0 will always be 0 + // for integral cubics.) + // + // See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", + // 4.2 Curve Categorization: + // + // https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + double A1 = calc_dot_cross_cubic(P[0], P[3], P[2]); + double A2 = calc_dot_cross_cubic(P[1], P[0], P[3]); + double A3 = calc_dot_cross_cubic(P[2], P[1], P[0]); + + double D3 = 3 * A3; + double D2 = D3 - A2; + double D1 = D2 - A2 + A1; + + // Shift the exponents in D so the largest magnitude falls somewhere in 1..2. This protects us + // from overflow down the road while solving for roots and KLM functionals. + double Dmax = std::max(std::max(fabs(D1), fabs(D2)), fabs(D3)); + double norm = previous_inverse_pow2(Dmax); + D1 *= norm; + D2 *= norm; + D3 *= norm; + + if (d) { + d[3] = D3; + d[2] = D2; + d[1] = D1; + d[0] = 0; + } + + // Now use the inflection function to classify the cubic. + // + // See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", + // 4.4 Integral Cubics: + // + // https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + if (0 != D1) { + double discr = 3*D2*D2 - 4*D1*D3; + if (discr > 0) { // Serpentine. + if (t && s) { + double q = 3*D2 + copysign(sqrt(3*discr), D2); + write_cubic_inflection_roots(q, 6*D1, 2*D3, q, t, s); + } + return SkCubicType::kSerpentine; + } else if (discr < 0) { // Loop. + if (t && s) { + double q = D2 + copysign(sqrt(-discr), D2); + write_cubic_inflection_roots(q, 2*D1, 2*(D2*D2 - D3*D1), D1*q, t, s); + } + return SkCubicType::kLoop; + } else { // Cusp. + if (t && s) { + write_cubic_inflection_roots(D2, 2*D1, D2, 2*D1, t, s); + } + return SkCubicType::kLocalCusp; + } + } else { + if (0 != D2) { // Cusp at T=infinity. + if (t && s) { + write_cubic_inflection_roots(D3, 3*D2, 1, 0, t, s); // T1=infinity. + } + return SkCubicType::kCuspAtInfinity; + } else { // Degenerate. + if (t && s) { + write_cubic_inflection_roots(1, 0, 1, 0, t, s); // T0=T1=infinity. + } + return 0 != D3 ? SkCubicType::kQuadratic : SkCubicType::kLineOrPoint; + } + } +} + +template void bubble_sort(T array[], int count) { + for (int i = count - 1; i > 0; --i) + for (int j = i; j > 0; --j) + if (array[j] < array[j-1]) + { + T tmp(array[j]); + array[j] = array[j-1]; + array[j-1] = tmp; + } +} + +/** + * Given an array and count, remove all pair-wise duplicates from the array, + * keeping the existing sorting, and return the new count + */ +static int collaps_duplicates(SkScalar array[], int count) { + for (int n = count; n > 1; --n) { + if (array[0] == array[1]) { + for (int i = 1; i < n; ++i) { + array[i - 1] = array[i]; + } + count -= 1; + } else { + array += 1; + } + } + return count; +} + +#ifdef SK_DEBUG + +#define TEST_COLLAPS_ENTRY(array) array, SK_ARRAY_COUNT(array) + +static void test_collaps_duplicates() { + static bool gOnce; + if (gOnce) { return; } + gOnce = true; + const SkScalar src0[] = { 0 }; + const SkScalar src1[] = { 0, 0 }; + const SkScalar src2[] = { 0, 1 }; + const SkScalar src3[] = { 0, 0, 0 }; + const SkScalar src4[] = { 0, 0, 1 }; + const SkScalar src5[] = { 0, 1, 1 }; + const SkScalar src6[] = { 0, 1, 2 }; + const struct { + const SkScalar* fData; + int fCount; + int fCollapsedCount; + } data[] = { + { TEST_COLLAPS_ENTRY(src0), 1 }, + { TEST_COLLAPS_ENTRY(src1), 1 }, + { TEST_COLLAPS_ENTRY(src2), 2 }, + { TEST_COLLAPS_ENTRY(src3), 1 }, + { TEST_COLLAPS_ENTRY(src4), 2 }, + { TEST_COLLAPS_ENTRY(src5), 2 }, + { TEST_COLLAPS_ENTRY(src6), 3 }, + }; + for (size_t i = 0; i < SK_ARRAY_COUNT(data); ++i) { + SkScalar dst[3]; + memcpy(dst, data[i].fData, data[i].fCount * sizeof(dst[0])); + int count = collaps_duplicates(dst, data[i].fCount); + SkASSERT(data[i].fCollapsedCount == count); + for (int j = 1; j < count; ++j) { + SkASSERT(dst[j-1] < dst[j]); + } + } +} +#endif + +static SkScalar SkScalarCubeRoot(SkScalar x) { + return SkScalarPow(x, 0.3333333f); +} + +/* Solve coeff(t) == 0, returning the number of roots that + lie withing 0 < t < 1. + coeff[0]t^3 + coeff[1]t^2 + coeff[2]t + coeff[3] + + Eliminates repeated roots (so that all tValues are distinct, and are always + in increasing order. +*/ +static int solve_cubic_poly(const SkScalar coeff[4], SkScalar tValues[3]) { + if (SkScalarNearlyZero(coeff[0])) { // we're just a quadratic + return SkFindUnitQuadRoots(coeff[1], coeff[2], coeff[3], tValues); + } + + SkScalar a, b, c, Q, R; + + { + SkASSERT(coeff[0] != 0); + + SkScalar inva = SkScalarInvert(coeff[0]); + a = coeff[1] * inva; + b = coeff[2] * inva; + c = coeff[3] * inva; + } + Q = (a*a - b*3) / 9; + R = (2*a*a*a - 9*a*b + 27*c) / 54; + + SkScalar Q3 = Q * Q * Q; + SkScalar R2MinusQ3 = R * R - Q3; + SkScalar adiv3 = a / 3; + + if (R2MinusQ3 < 0) { // we have 3 real roots + // the divide/root can, due to finite precisions, be slightly outside of -1...1 + SkScalar theta = SkScalarACos(SkScalarPin(R / SkScalarSqrt(Q3), -1, 1)); + SkScalar neg2RootQ = -2 * SkScalarSqrt(Q); + + tValues[0] = SkScalarPin(neg2RootQ * SkScalarCos(theta/3) - adiv3, 0, 1); + tValues[1] = SkScalarPin(neg2RootQ * SkScalarCos((theta + 2*SK_ScalarPI)/3) - adiv3, 0, 1); + tValues[2] = SkScalarPin(neg2RootQ * SkScalarCos((theta - 2*SK_ScalarPI)/3) - adiv3, 0, 1); + SkDEBUGCODE(test_collaps_duplicates();) + + // now sort the roots + bubble_sort(tValues, 3); + return collaps_duplicates(tValues, 3); + } else { // we have 1 real root + SkScalar A = SkScalarAbs(R) + SkScalarSqrt(R2MinusQ3); + A = SkScalarCubeRoot(A); + if (R > 0) { + A = -A; + } + if (A != 0) { + A += Q / A; + } + tValues[0] = SkScalarPin(A - adiv3, 0, 1); + return 1; + } +} + +/* Looking for F' dot F'' == 0 + + A = b - a + B = c - 2b + a + C = d - 3c + 3b - a + + F' = 3Ct^2 + 6Bt + 3A + F'' = 6Ct + 6B + + F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB +*/ +static void formulate_F1DotF2(const SkScalar src[], SkScalar coeff[4]) { + SkScalar a = src[2] - src[0]; + SkScalar b = src[4] - 2 * src[2] + src[0]; + SkScalar c = src[6] + 3 * (src[2] - src[4]) - src[0]; + + coeff[0] = c * c; + coeff[1] = 3 * b * c; + coeff[2] = 2 * b * b + c * a; + coeff[3] = a * b; +} + +/* Looking for F' dot F'' == 0 + + A = b - a + B = c - 2b + a + C = d - 3c + 3b - a + + F' = 3Ct^2 + 6Bt + 3A + F'' = 6Ct + 6B + + F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB +*/ +int SkFindCubicMaxCurvature(const SkPoint src[4], SkScalar tValues[3]) { + SkScalar coeffX[4], coeffY[4]; + int i; + + formulate_F1DotF2(&src[0].fX, coeffX); + formulate_F1DotF2(&src[0].fY, coeffY); + + for (i = 0; i < 4; i++) { + coeffX[i] += coeffY[i]; + } + + int numRoots = solve_cubic_poly(coeffX, tValues); + // now remove extrema where the curvature is zero (mins) + // !!!! need a test for this !!!! + return numRoots; +} + +int SkChopCubicAtMaxCurvature(const SkPoint src[4], SkPoint dst[13], + SkScalar tValues[3]) { + SkScalar t_storage[3]; + + if (tValues == nullptr) { + tValues = t_storage; + } + + SkScalar roots[3]; + int rootCount = SkFindCubicMaxCurvature(src, roots); + + // Throw out values not inside 0..1. + int count = 0; + for (int i = 0; i < rootCount; ++i) { + if (0 < roots[i] && roots[i] < 1) { + tValues[count++] = roots[i]; + } + } + + if (dst) { + if (count == 0) { + memcpy(dst, src, 4 * sizeof(SkPoint)); + } else { + SkChopCubicAt(src, dst, tValues, count); + } + } + return count + 1; +} + +// Returns a constant proportional to the dimensions of the cubic. +// Constant found through experimentation -- maybe there's a better way.... +static SkScalar calc_cubic_precision(const SkPoint src[4]) { + return (SkPointPriv::DistanceToSqd(src[1], src[0]) + SkPointPriv::DistanceToSqd(src[2], src[1]) + + SkPointPriv::DistanceToSqd(src[3], src[2])) * 1e-8f; +} + +// Returns true if both points src[testIndex], src[testIndex+1] are in the same half plane defined +// by the line segment src[lineIndex], src[lineIndex+1]. +static bool on_same_side(const SkPoint src[4], int testIndex, int lineIndex) { + SkPoint origin = src[lineIndex]; + SkVector line = src[lineIndex + 1] - origin; + SkScalar crosses[2]; + for (int index = 0; index < 2; ++index) { + SkVector testLine = src[testIndex + index] - origin; + crosses[index] = line.cross(testLine); + } + return crosses[0] * crosses[1] >= 0; +} + +// Return location (in t) of cubic cusp, if there is one. +// Note that classify cubic code does not reliably return all cusp'd cubics, so +// it is not called here. +SkScalar SkFindCubicCusp(const SkPoint src[4]) { + // When the adjacent control point matches the end point, it behaves as if + // the cubic has a cusp: there's a point of max curvature where the derivative + // goes to zero. Ideally, this would be where t is zero or one, but math + // error makes not so. It is not uncommon to create cubics this way; skip them. + if (src[0] == src[1]) { + return -1; + } + if (src[2] == src[3]) { + return -1; + } + // Cubics only have a cusp if the line segments formed by the control and end points cross. + // Detect crossing if line ends are on opposite sides of plane formed by the other line. + if (on_same_side(src, 0, 2) || on_same_side(src, 2, 0)) { + return -1; + } + // Cubics may have multiple points of maximum curvature, although at most only + // one is a cusp. + SkScalar maxCurvature[3]; + int roots = SkFindCubicMaxCurvature(src, maxCurvature); + for (int index = 0; index < roots; ++index) { + SkScalar testT = maxCurvature[index]; + if (0 >= testT || testT >= 1) { // no need to consider max curvature on the end + continue; + } + // A cusp is at the max curvature, and also has a derivative close to zero. + // Choose the 'close to zero' meaning by comparing the derivative length + // with the overall cubic size. + SkVector dPt = eval_cubic_derivative(src, testT); + SkScalar dPtMagnitude = SkPointPriv::LengthSqd(dPt); + SkScalar precision = calc_cubic_precision(src); + if (dPtMagnitude < precision) { + // All three max curvature t values may be close to the cusp; + // return the first one. + return testT; + } + } + return -1; +} + +#include "src/pathops/SkPathOpsCubic.h" + +typedef int (SkDCubic::*InterceptProc)(double intercept, double roots[3]) const; + +static bool cubic_dchop_at_intercept(const SkPoint src[4], SkScalar intercept, SkPoint dst[7], + InterceptProc method) { + SkDCubic cubic; + double roots[3]; + int count = (cubic.set(src).*method)(intercept, roots); + if (count > 0) { + SkDCubicPair pair = cubic.chopAt(roots[0]); + for (int i = 0; i < 7; ++i) { + dst[i] = pair.pts[i].asSkPoint(); + } + return true; + } + return false; +} + +bool SkChopMonoCubicAtY(SkPoint src[4], SkScalar y, SkPoint dst[7]) { + return cubic_dchop_at_intercept(src, y, dst, &SkDCubic::horizontalIntersect); +} + +bool SkChopMonoCubicAtX(SkPoint src[4], SkScalar x, SkPoint dst[7]) { + return cubic_dchop_at_intercept(src, x, dst, &SkDCubic::verticalIntersect); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// NURB representation for conics. Helpful explanations at: +// +// http://citeseerx.ist.psu.edu/viewdoc/ +// download?doi=10.1.1.44.5740&rep=rep1&type=ps +// and +// http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS/RB-conics.html +// +// F = (A (1 - t)^2 + C t^2 + 2 B (1 - t) t w) +// ------------------------------------------ +// ((1 - t)^2 + t^2 + 2 (1 - t) t w) +// +// = {t^2 (P0 + P2 - 2 P1 w), t (-2 P0 + 2 P1 w), P0} +// ------------------------------------------------ +// {t^2 (2 - 2 w), t (-2 + 2 w), 1} +// + +// F' = 2 (C t (1 + t (-1 + w)) - A (-1 + t) (t (-1 + w) - w) + B (1 - 2 t) w) +// +// t^2 : (2 P0 - 2 P2 - 2 P0 w + 2 P2 w) +// t^1 : (-2 P0 + 2 P2 + 4 P0 w - 4 P1 w) +// t^0 : -2 P0 w + 2 P1 w +// +// We disregard magnitude, so we can freely ignore the denominator of F', and +// divide the numerator by 2 +// +// coeff[0] for t^2 +// coeff[1] for t^1 +// coeff[2] for t^0 +// +static void conic_deriv_coeff(const SkScalar src[], + SkScalar w, + SkScalar coeff[3]) { + const SkScalar P20 = src[4] - src[0]; + const SkScalar P10 = src[2] - src[0]; + const SkScalar wP10 = w * P10; + coeff[0] = w * P20 - P20; + coeff[1] = P20 - 2 * wP10; + coeff[2] = wP10; +} + +static bool conic_find_extrema(const SkScalar src[], SkScalar w, SkScalar* t) { + SkScalar coeff[3]; + conic_deriv_coeff(src, w, coeff); + + SkScalar tValues[2]; + int roots = SkFindUnitQuadRoots(coeff[0], coeff[1], coeff[2], tValues); + SkASSERT(0 == roots || 1 == roots); + + if (1 == roots) { + *t = tValues[0]; + return true; + } + return false; +} + +// We only interpolate one dimension at a time (the first, at +0, +3, +6). +static void p3d_interp(const SkScalar src[7], SkScalar dst[7], SkScalar t) { + SkScalar ab = SkScalarInterp(src[0], src[3], t); + SkScalar bc = SkScalarInterp(src[3], src[6], t); + dst[0] = ab; + dst[3] = SkScalarInterp(ab, bc, t); + dst[6] = bc; +} + +static void ratquad_mapTo3D(const SkPoint src[3], SkScalar w, SkPoint3 dst[3]) { + dst[0].set(src[0].fX * 1, src[0].fY * 1, 1); + dst[1].set(src[1].fX * w, src[1].fY * w, w); + dst[2].set(src[2].fX * 1, src[2].fY * 1, 1); +} + +static SkPoint project_down(const SkPoint3& src) { + return {src.fX / src.fZ, src.fY / src.fZ}; +} + +// return false if infinity or NaN is generated; caller must check +bool SkConic::chopAt(SkScalar t, SkConic dst[2]) const { + SkPoint3 tmp[3], tmp2[3]; + + ratquad_mapTo3D(fPts, fW, tmp); + + p3d_interp(&tmp[0].fX, &tmp2[0].fX, t); + p3d_interp(&tmp[0].fY, &tmp2[0].fY, t); + p3d_interp(&tmp[0].fZ, &tmp2[0].fZ, t); + + dst[0].fPts[0] = fPts[0]; + dst[0].fPts[1] = project_down(tmp2[0]); + dst[0].fPts[2] = project_down(tmp2[1]); dst[1].fPts[0] = dst[0].fPts[2]; + dst[1].fPts[1] = project_down(tmp2[2]); + dst[1].fPts[2] = fPts[2]; + + // to put in "standard form", where w0 and w2 are both 1, we compute the + // new w1 as sqrt(w1*w1/w0*w2) + // or + // w1 /= sqrt(w0*w2) + // + // However, in our case, we know that for dst[0]: + // w0 == 1, and for dst[1], w2 == 1 + // + SkScalar root = SkScalarSqrt(tmp2[1].fZ); + dst[0].fW = tmp2[0].fZ / root; + dst[1].fW = tmp2[2].fZ / root; + SkASSERT(sizeof(dst[0]) == sizeof(SkScalar) * 7); + SkASSERT(0 == offsetof(SkConic, fPts[0].fX)); + return SkScalarsAreFinite(&dst[0].fPts[0].fX, 7 * 2); +} + +void SkConic::chopAt(SkScalar t1, SkScalar t2, SkConic* dst) const { + if (0 == t1 || 1 == t2) { + if (0 == t1 && 1 == t2) { + *dst = *this; + return; + } else { + SkConic pair[2]; + if (this->chopAt(t1 ? t1 : t2, pair)) { + *dst = pair[SkToBool(t1)]; + return; + } + } + } + SkConicCoeff coeff(*this); + Sk2s tt1(t1); + Sk2s aXY = coeff.fNumer.eval(tt1); + Sk2s aZZ = coeff.fDenom.eval(tt1); + Sk2s midTT((t1 + t2) / 2); + Sk2s dXY = coeff.fNumer.eval(midTT); + Sk2s dZZ = coeff.fDenom.eval(midTT); + Sk2s tt2(t2); + Sk2s cXY = coeff.fNumer.eval(tt2); + Sk2s cZZ = coeff.fDenom.eval(tt2); + Sk2s bXY = times_2(dXY) - (aXY + cXY) * Sk2s(0.5f); + Sk2s bZZ = times_2(dZZ) - (aZZ + cZZ) * Sk2s(0.5f); + dst->fPts[0] = to_point(aXY / aZZ); + dst->fPts[1] = to_point(bXY / bZZ); + dst->fPts[2] = to_point(cXY / cZZ); + Sk2s ww = bZZ / (aZZ * cZZ).sqrt(); + dst->fW = ww[0]; +} + +SkPoint SkConic::evalAt(SkScalar t) const { + return to_point(SkConicCoeff(*this).eval(t)); +} + +SkVector SkConic::evalTangentAt(SkScalar t) const { + // The derivative equation returns a zero tangent vector when t is 0 or 1, + // and the control point is equal to the end point. + // In this case, use the conic endpoints to compute the tangent. + if ((t == 0 && fPts[0] == fPts[1]) || (t == 1 && fPts[1] == fPts[2])) { + return fPts[2] - fPts[0]; + } + Sk2s p0 = from_point(fPts[0]); + Sk2s p1 = from_point(fPts[1]); + Sk2s p2 = from_point(fPts[2]); + Sk2s ww(fW); + + Sk2s p20 = p2 - p0; + Sk2s p10 = p1 - p0; + + Sk2s C = ww * p10; + Sk2s A = ww * p20 - p20; + Sk2s B = p20 - C - C; + + return to_vector(SkQuadCoeff(A, B, C).eval(t)); +} + +void SkConic::evalAt(SkScalar t, SkPoint* pt, SkVector* tangent) const { + SkASSERT(t >= 0 && t <= SK_Scalar1); + + if (pt) { + *pt = this->evalAt(t); + } + if (tangent) { + *tangent = this->evalTangentAt(t); + } +} + +static SkScalar subdivide_w_value(SkScalar w) { + return SkScalarSqrt(SK_ScalarHalf + w * SK_ScalarHalf); +} + +void SkConic::chop(SkConic * SK_RESTRICT dst) const { + Sk2s scale = Sk2s(SkScalarInvert(SK_Scalar1 + fW)); + SkScalar newW = subdivide_w_value(fW); + + Sk2s p0 = from_point(fPts[0]); + Sk2s p1 = from_point(fPts[1]); + Sk2s p2 = from_point(fPts[2]); + Sk2s ww(fW); + + Sk2s wp1 = ww * p1; + Sk2s m = (p0 + times_2(wp1) + p2) * scale * Sk2s(0.5f); + SkPoint mPt = to_point(m); + if (!mPt.isFinite()) { + double w_d = fW; + double w_2 = w_d * 2; + double scale_half = 1 / (1 + w_d) * 0.5; + mPt.fX = SkDoubleToScalar((fPts[0].fX + w_2 * fPts[1].fX + fPts[2].fX) * scale_half); + mPt.fY = SkDoubleToScalar((fPts[0].fY + w_2 * fPts[1].fY + fPts[2].fY) * scale_half); + } + dst[0].fPts[0] = fPts[0]; + dst[0].fPts[1] = to_point((p0 + wp1) * scale); + dst[0].fPts[2] = dst[1].fPts[0] = mPt; + dst[1].fPts[1] = to_point((wp1 + p2) * scale); + dst[1].fPts[2] = fPts[2]; + + dst[0].fW = dst[1].fW = newW; +} + +/* + * "High order approximation of conic sections by quadratic splines" + * by Michael Floater, 1993 + */ +#define AS_QUAD_ERROR_SETUP \ + SkScalar a = fW - 1; \ + SkScalar k = a / (4 * (2 + a)); \ + SkScalar x = k * (fPts[0].fX - 2 * fPts[1].fX + fPts[2].fX); \ + SkScalar y = k * (fPts[0].fY - 2 * fPts[1].fY + fPts[2].fY); + +void SkConic::computeAsQuadError(SkVector* err) const { + AS_QUAD_ERROR_SETUP + err->set(x, y); +} + +bool SkConic::asQuadTol(SkScalar tol) const { + AS_QUAD_ERROR_SETUP + return (x * x + y * y) <= tol * tol; +} + +// Limit the number of suggested quads to approximate a conic +#define kMaxConicToQuadPOW2 5 + +int SkConic::computeQuadPOW2(SkScalar tol) const { + if (tol < 0 || !SkScalarIsFinite(tol) || !SkPointPriv::AreFinite(fPts, 3)) { + return 0; + } + + AS_QUAD_ERROR_SETUP + + SkScalar error = SkScalarSqrt(x * x + y * y); + int pow2; + for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) { + if (error <= tol) { + break; + } + error *= 0.25f; + } + // float version -- using ceil gives the same results as the above. + if (false) { + SkScalar err = SkScalarSqrt(x * x + y * y); + if (err <= tol) { + return 0; + } + SkScalar tol2 = tol * tol; + if (tol2 == 0) { + return kMaxConicToQuadPOW2; + } + SkScalar fpow2 = SkScalarLog2((x * x + y * y) / tol2) * 0.25f; + int altPow2 = SkScalarCeilToInt(fpow2); + if (altPow2 != pow2) { + SkDebugf("pow2 %d altPow2 %d fbits %g err %g tol %g\n", pow2, altPow2, fpow2, err, tol); + } + pow2 = altPow2; + } + return pow2; +} + +// This was originally developed and tested for pathops: see SkOpTypes.h +// returns true if (a <= b <= c) || (a >= b >= c) +static bool between(SkScalar a, SkScalar b, SkScalar c) { + return (a - b) * (c - b) <= 0; +} + +static SkPoint* subdivide(const SkConic& src, SkPoint pts[], int level) { + SkASSERT(level >= 0); + + if (0 == level) { + memcpy(pts, &src.fPts[1], 2 * sizeof(SkPoint)); + return pts + 2; + } else { + SkConic dst[2]; + src.chop(dst); + const SkScalar startY = src.fPts[0].fY; + SkScalar endY = src.fPts[2].fY; + if (between(startY, src.fPts[1].fY, endY)) { + // If the input is monotonic and the output is not, the scan converter hangs. + // Ensure that the chopped conics maintain their y-order. + SkScalar midY = dst[0].fPts[2].fY; + if (!between(startY, midY, endY)) { + // If the computed midpoint is outside the ends, move it to the closer one. + SkScalar closerY = SkTAbs(midY - startY) < SkTAbs(midY - endY) ? startY : endY; + dst[0].fPts[2].fY = dst[1].fPts[0].fY = closerY; + } + if (!between(startY, dst[0].fPts[1].fY, dst[0].fPts[2].fY)) { + // If the 1st control is not between the start and end, put it at the start. + // This also reduces the quad to a line. + dst[0].fPts[1].fY = startY; + } + if (!between(dst[1].fPts[0].fY, dst[1].fPts[1].fY, endY)) { + // If the 2nd control is not between the start and end, put it at the end. + // This also reduces the quad to a line. + dst[1].fPts[1].fY = endY; + } + // Verify that all five points are in order. + SkASSERT(between(startY, dst[0].fPts[1].fY, dst[0].fPts[2].fY)); + SkASSERT(between(dst[0].fPts[1].fY, dst[0].fPts[2].fY, dst[1].fPts[1].fY)); + SkASSERT(between(dst[0].fPts[2].fY, dst[1].fPts[1].fY, endY)); + } + --level; + pts = subdivide(dst[0], pts, level); + return subdivide(dst[1], pts, level); + } +} + +int SkConic::chopIntoQuadsPOW2(SkPoint pts[], int pow2) const { + SkASSERT(pow2 >= 0); + *pts = fPts[0]; + SkDEBUGCODE(SkPoint* endPts); + if (pow2 == kMaxConicToQuadPOW2) { // If an extreme weight generates many quads ... + SkConic dst[2]; + this->chop(dst); + // check to see if the first chop generates a pair of lines + if (SkPointPriv::EqualsWithinTolerance(dst[0].fPts[1], dst[0].fPts[2]) && + SkPointPriv::EqualsWithinTolerance(dst[1].fPts[0], dst[1].fPts[1])) { + pts[1] = pts[2] = pts[3] = dst[0].fPts[1]; // set ctrl == end to make lines + pts[4] = dst[1].fPts[2]; + pow2 = 1; + SkDEBUGCODE(endPts = &pts[5]); + goto commonFinitePtCheck; + } + } + SkDEBUGCODE(endPts = ) subdivide(*this, pts + 1, pow2); +commonFinitePtCheck: + const int quadCount = 1 << pow2; + const int ptCount = 2 * quadCount + 1; + SkASSERT(endPts - pts == ptCount); + if (!SkPointPriv::AreFinite(pts, ptCount)) { + // if we generated a non-finite, pin ourselves to the middle of the hull, + // as our first and last are already on the first/last pts of the hull. + for (int i = 1; i < ptCount - 1; ++i) { + pts[i] = fPts[1]; + } + } + return 1 << pow2; +} + +bool SkConic::findXExtrema(SkScalar* t) const { + return conic_find_extrema(&fPts[0].fX, fW, t); +} + +bool SkConic::findYExtrema(SkScalar* t) const { + return conic_find_extrema(&fPts[0].fY, fW, t); +} + +bool SkConic::chopAtXExtrema(SkConic dst[2]) const { + SkScalar t; + if (this->findXExtrema(&t)) { + if (!this->chopAt(t, dst)) { + // if chop can't return finite values, don't chop + return false; + } + // now clean-up the middle, since we know t was meant to be at + // an X-extrema + SkScalar value = dst[0].fPts[2].fX; + dst[0].fPts[1].fX = value; + dst[1].fPts[0].fX = value; + dst[1].fPts[1].fX = value; + return true; + } + return false; +} + +bool SkConic::chopAtYExtrema(SkConic dst[2]) const { + SkScalar t; + if (this->findYExtrema(&t)) { + if (!this->chopAt(t, dst)) { + // if chop can't return finite values, don't chop + return false; + } + // now clean-up the middle, since we know t was meant to be at + // an Y-extrema + SkScalar value = dst[0].fPts[2].fY; + dst[0].fPts[1].fY = value; + dst[1].fPts[0].fY = value; + dst[1].fPts[1].fY = value; + return true; + } + return false; +} + +void SkConic::computeTightBounds(SkRect* bounds) const { + SkPoint pts[4]; + pts[0] = fPts[0]; + pts[1] = fPts[2]; + int count = 2; + + SkScalar t; + if (this->findXExtrema(&t)) { + this->evalAt(t, &pts[count++]); + } + if (this->findYExtrema(&t)) { + this->evalAt(t, &pts[count++]); + } + bounds->setBounds(pts, count); +} + +void SkConic::computeFastBounds(SkRect* bounds) const { + bounds->setBounds(fPts, 3); +} + +#if 0 // unimplemented +bool SkConic::findMaxCurvature(SkScalar* t) const { + // TODO: Implement me + return false; +} +#endif + +SkScalar SkConic::TransformW(const SkPoint pts[], SkScalar w, const SkMatrix& matrix) { + if (!matrix.hasPerspective()) { + return w; + } + + SkPoint3 src[3], dst[3]; + + ratquad_mapTo3D(pts, w, src); + + matrix.mapHomogeneousPoints(dst, src, 3); + + // w' = sqrt(w1*w1/w0*w2) + // use doubles temporarily, to handle small numer/denom + double w0 = dst[0].fZ; + double w1 = dst[1].fZ; + double w2 = dst[2].fZ; + return sk_double_to_float(sqrt(sk_ieee_double_divide(w1 * w1, w0 * w2))); +} + +int SkConic::BuildUnitArc(const SkVector& uStart, const SkVector& uStop, SkRotationDirection dir, + const SkMatrix* userMatrix, SkConic dst[kMaxConicsForArc]) { + // rotate by x,y so that uStart is (1.0) + SkScalar x = SkPoint::DotProduct(uStart, uStop); + SkScalar y = SkPoint::CrossProduct(uStart, uStop); + + SkScalar absY = SkScalarAbs(y); + + // check for (effectively) coincident vectors + // this can happen if our angle is nearly 0 or nearly 180 (y == 0) + // ... we use the dot-prod to distinguish between 0 and 180 (x > 0) + if (absY <= SK_ScalarNearlyZero && x > 0 && ((y >= 0 && kCW_SkRotationDirection == dir) || + (y <= 0 && kCCW_SkRotationDirection == dir))) { + return 0; + } + + if (dir == kCCW_SkRotationDirection) { + y = -y; + } + + // We decide to use 1-conic per quadrant of a circle. What quadrant does [xy] lie in? + // 0 == [0 .. 90) + // 1 == [90 ..180) + // 2 == [180..270) + // 3 == [270..360) + // + int quadrant = 0; + if (0 == y) { + quadrant = 2; // 180 + SkASSERT(SkScalarAbs(x + SK_Scalar1) <= SK_ScalarNearlyZero); + } else if (0 == x) { + SkASSERT(absY - SK_Scalar1 <= SK_ScalarNearlyZero); + quadrant = y > 0 ? 1 : 3; // 90 : 270 + } else { + if (y < 0) { + quadrant += 2; + } + if ((x < 0) != (y < 0)) { + quadrant += 1; + } + } + + const SkPoint quadrantPts[] = { + { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 } + }; + const SkScalar quadrantWeight = SK_ScalarRoot2Over2; + + int conicCount = quadrant; + for (int i = 0; i < conicCount; ++i) { + dst[i].set(&quadrantPts[i * 2], quadrantWeight); + } + + // Now compute any remaing (sub-90-degree) arc for the last conic + const SkPoint finalP = { x, y }; + const SkPoint& lastQ = quadrantPts[quadrant * 2]; // will already be a unit-vector + const SkScalar dot = SkVector::DotProduct(lastQ, finalP); + if (!SkScalarIsFinite(dot)) { + return 0; + } + SkASSERT(0 <= dot && dot <= SK_Scalar1 + SK_ScalarNearlyZero); + + if (dot < 1) { + SkVector offCurve = { lastQ.x() + x, lastQ.y() + y }; + // compute the bisector vector, and then rescale to be the off-curve point. + // we compute its length from cos(theta/2) = length / 1, using half-angle identity we get + // length = sqrt(2 / (1 + cos(theta)). We already have cos() when to computed the dot. + // This is nice, since our computed weight is cos(theta/2) as well! + // + const SkScalar cosThetaOver2 = SkScalarSqrt((1 + dot) / 2); + offCurve.setLength(SkScalarInvert(cosThetaOver2)); + if (!SkPointPriv::EqualsWithinTolerance(lastQ, offCurve)) { + dst[conicCount].set(lastQ, offCurve, finalP, cosThetaOver2); + conicCount += 1; + } + } + + // now handle counter-clockwise and the initial unitStart rotation + SkMatrix matrix; + matrix.setSinCos(uStart.fY, uStart.fX); + if (dir == kCCW_SkRotationDirection) { + matrix.preScale(SK_Scalar1, -SK_Scalar1); + } + if (userMatrix) { + matrix.postConcat(*userMatrix); + } + for (int i = 0; i < conicCount; ++i) { + matrix.mapPoints(dst[i].fPts, 3); + } + return conicCount; +} diff --git a/gfx/skia/skia/src/core/SkGeometry.h b/gfx/skia/skia/src/core/SkGeometry.h new file mode 100644 index 0000000000..23bdbc1c42 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGeometry.h @@ -0,0 +1,453 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGeometry_DEFINED +#define SkGeometry_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/private/SkNx.h" + +static inline Sk2s from_point(const SkPoint& point) { + return Sk2s::Load(&point); +} + +static inline SkPoint to_point(const Sk2s& x) { + SkPoint point; + x.store(&point); + return point; +} + +static Sk2s times_2(const Sk2s& value) { + return value + value; +} + +/** Given a quadratic equation Ax^2 + Bx + C = 0, return 0, 1, 2 roots for the + equation. +*/ +int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2]); + +/////////////////////////////////////////////////////////////////////////////// + +SkPoint SkEvalQuadAt(const SkPoint src[3], SkScalar t); +SkPoint SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t); + +/** Set pt to the point on the src quadratic specified by t. t must be + 0 <= t <= 1.0 +*/ +void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent = nullptr); + +/** Given a src quadratic bezier, chop it at the specified t value, + where 0 < t < 1, and return the two new quadratics in dst: + dst[0..2] and dst[2..4] +*/ +void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t); + +/** Given a src quadratic bezier, chop it at the specified t == 1/2, + The new quads are returned in dst[0..2] and dst[2..4] +*/ +void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]); + +/** Given the 3 coefficients for a quadratic bezier (either X or Y values), look + for extrema, and return the number of t-values that are found that represent + these extrema. If the quadratic has no extrema betwee (0..1) exclusive, the + function returns 0. + Returned count tValues[] + 0 ignored + 1 0 < tValues[0] < 1 +*/ +int SkFindQuadExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar tValues[1]); + +/** Given 3 points on a quadratic bezier, chop it into 1, 2 beziers such that + the resulting beziers are monotonic in Y. This is called by the scan converter. + Depending on what is returned, dst[] is treated as follows + 0 dst[0..2] is the original quad + 1 dst[0..2] and dst[2..4] are the two new quads +*/ +int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5]); +int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5]); + +/** Given 3 points on a quadratic bezier, if the point of maximum + curvature exists on the segment, returns the t value for this + point along the curve. Otherwise it will return a value of 0. +*/ +SkScalar SkFindQuadMaxCurvature(const SkPoint src[3]); + +/** Given 3 points on a quadratic bezier, divide it into 2 quadratics + if the point of maximum curvature exists on the quad segment. + Depending on what is returned, dst[] is treated as follows + 1 dst[0..2] is the original quad + 2 dst[0..2] and dst[2..4] are the two new quads + If dst == null, it is ignored and only the count is returned. +*/ +int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5]); + +/** Given 3 points on a quadratic bezier, use degree elevation to + convert it into the cubic fitting the same curve. The new cubic + curve is returned in dst[0..3]. +*/ +SK_API void SkConvertQuadToCubic(const SkPoint src[3], SkPoint dst[4]); + +/////////////////////////////////////////////////////////////////////////////// + +/** Set pt to the point on the src cubic specified by t. t must be + 0 <= t <= 1.0 +*/ +void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint* locOrNull, + SkVector* tangentOrNull, SkVector* curvatureOrNull); + +/** Given a src cubic bezier, chop it at the specified t value, + where 0 < t < 1, and return the two new cubics in dst: + dst[0..3] and dst[3..6] +*/ +void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t); + +/** Given a src cubic bezier, chop it at the specified t values, + where 0 < t < 1, and return the new cubics in dst: + dst[0..3],dst[3..6],...,dst[3*t_count..3*(t_count+1)] +*/ +void SkChopCubicAt(const SkPoint src[4], SkPoint dst[], const SkScalar t[], + int t_count); + +/** Given a src cubic bezier, chop it at the specified t == 1/2, + The new cubics are returned in dst[0..3] and dst[3..6] +*/ +void SkChopCubicAtHalf(const SkPoint src[4], SkPoint dst[7]); + +/** Given the 4 coefficients for a cubic bezier (either X or Y values), look + for extrema, and return the number of t-values that are found that represent + these extrema. If the cubic has no extrema betwee (0..1) exclusive, the + function returns 0. + Returned count tValues[] + 0 ignored + 1 0 < tValues[0] < 1 + 2 0 < tValues[0] < tValues[1] < 1 +*/ +int SkFindCubicExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar d, + SkScalar tValues[2]); + +/** Given 4 points on a cubic bezier, chop it into 1, 2, 3 beziers such that + the resulting beziers are monotonic in Y. This is called by the scan converter. + Depending on what is returned, dst[] is treated as follows + 0 dst[0..3] is the original cubic + 1 dst[0..3] and dst[3..6] are the two new cubics + 2 dst[0..3], dst[3..6], dst[6..9] are the three new cubics + If dst == null, it is ignored and only the count is returned. +*/ +int SkChopCubicAtYExtrema(const SkPoint src[4], SkPoint dst[10]); +int SkChopCubicAtXExtrema(const SkPoint src[4], SkPoint dst[10]); + +/** Given a cubic bezier, return 0, 1, or 2 t-values that represent the + inflection points. +*/ +int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2]); + +/** Return 1 for no chop, 2 for having chopped the cubic at a single + inflection point, 3 for having chopped at 2 inflection points. + dst will hold the resulting 1, 2, or 3 cubics. +*/ +int SkChopCubicAtInflections(const SkPoint src[4], SkPoint dst[10]); + +int SkFindCubicMaxCurvature(const SkPoint src[4], SkScalar tValues[3]); +int SkChopCubicAtMaxCurvature(const SkPoint src[4], SkPoint dst[13], + SkScalar tValues[3] = nullptr); +/** Returns t value of cusp if cubic has one; returns -1 otherwise. + */ +SkScalar SkFindCubicCusp(const SkPoint src[4]); + +bool SkChopMonoCubicAtX(SkPoint src[4], SkScalar y, SkPoint dst[7]); +bool SkChopMonoCubicAtY(SkPoint src[4], SkScalar x, SkPoint dst[7]); + +enum class SkCubicType { + kSerpentine, + kLoop, + kLocalCusp, // Cusp at a non-infinite parameter value with an inflection at t=infinity. + kCuspAtInfinity, // Cusp with a cusp at t=infinity and a local inflection. + kQuadratic, + kLineOrPoint +}; + +static inline bool SkCubicIsDegenerate(SkCubicType type) { + switch (type) { + case SkCubicType::kSerpentine: + case SkCubicType::kLoop: + case SkCubicType::kLocalCusp: + case SkCubicType::kCuspAtInfinity: + return false; + case SkCubicType::kQuadratic: + case SkCubicType::kLineOrPoint: + return true; + } + SK_ABORT("Invalid SkCubicType"); +} + +static inline const char* SkCubicTypeName(SkCubicType type) { + switch (type) { + case SkCubicType::kSerpentine: return "kSerpentine"; + case SkCubicType::kLoop: return "kLoop"; + case SkCubicType::kLocalCusp: return "kLocalCusp"; + case SkCubicType::kCuspAtInfinity: return "kCuspAtInfinity"; + case SkCubicType::kQuadratic: return "kQuadratic"; + case SkCubicType::kLineOrPoint: return "kLineOrPoint"; + } + SK_ABORT("Invalid SkCubicType"); +} + +/** Returns the cubic classification. + + t[],s[] are set to the two homogeneous parameter values at which points the lines L & M + intersect with K, sorted from smallest to largest and oriented so positive values of the + implicit are on the "left" side. For a serpentine curve they are the inflection points. For a + loop they are the double point. For a local cusp, they are both equal and denote the cusp point. + For a cusp at an infinite parameter value, one will be the local inflection point and the other + +inf (t,s = 1,0). If the curve is degenerate (i.e. quadratic or linear) they are both set to a + parameter value of +inf (t,s = 1,0). + + d[] is filled with the cubic inflection function coefficients. See "Resolution Independent + Curve Rendering using Programmable Graphics Hardware", 4.2 Curve Categorization: + + If the input points contain infinities or NaN, the return values are undefined. + + https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf +*/ +SkCubicType SkClassifyCubic(const SkPoint p[4], double t[2] = nullptr, double s[2] = nullptr, + double d[4] = nullptr); + +/////////////////////////////////////////////////////////////////////////////// + +enum SkRotationDirection { + kCW_SkRotationDirection, + kCCW_SkRotationDirection +}; + +struct SkConic { + SkConic() {} + SkConic(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, SkScalar w) { + fPts[0] = p0; + fPts[1] = p1; + fPts[2] = p2; + fW = w; + } + SkConic(const SkPoint pts[3], SkScalar w) { + memcpy(fPts, pts, sizeof(fPts)); + fW = w; + } + + SkPoint fPts[3]; + SkScalar fW; + + void set(const SkPoint pts[3], SkScalar w) { + memcpy(fPts, pts, 3 * sizeof(SkPoint)); + fW = w; + } + + void set(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, SkScalar w) { + fPts[0] = p0; + fPts[1] = p1; + fPts[2] = p2; + fW = w; + } + + /** + * Given a t-value [0...1] return its position and/or tangent. + * If pos is not null, return its position at the t-value. + * If tangent is not null, return its tangent at the t-value. NOTE the + * tangent value's length is arbitrary, and only its direction should + * be used. + */ + void evalAt(SkScalar t, SkPoint* pos, SkVector* tangent = nullptr) const; + bool SK_WARN_UNUSED_RESULT chopAt(SkScalar t, SkConic dst[2]) const; + void chopAt(SkScalar t1, SkScalar t2, SkConic* dst) const; + void chop(SkConic dst[2]) const; + + SkPoint evalAt(SkScalar t) const; + SkVector evalTangentAt(SkScalar t) const; + + void computeAsQuadError(SkVector* err) const; + bool asQuadTol(SkScalar tol) const; + + /** + * return the power-of-2 number of quads needed to approximate this conic + * with a sequence of quads. Will be >= 0. + */ + int SK_API computeQuadPOW2(SkScalar tol) const; + + /** + * Chop this conic into N quads, stored continguously in pts[], where + * N = 1 << pow2. The amount of storage needed is (1 + 2 * N) + */ + int SK_API SK_WARN_UNUSED_RESULT chopIntoQuadsPOW2(SkPoint pts[], int pow2) const; + + bool findXExtrema(SkScalar* t) const; + bool findYExtrema(SkScalar* t) const; + bool chopAtXExtrema(SkConic dst[2]) const; + bool chopAtYExtrema(SkConic dst[2]) const; + + void computeTightBounds(SkRect* bounds) const; + void computeFastBounds(SkRect* bounds) const; + + /** Find the parameter value where the conic takes on its maximum curvature. + * + * @param t output scalar for max curvature. Will be unchanged if + * max curvature outside 0..1 range. + * + * @return true if max curvature found inside 0..1 range, false otherwise + */ +// bool findMaxCurvature(SkScalar* t) const; // unimplemented + + static SkScalar TransformW(const SkPoint[3], SkScalar w, const SkMatrix&); + + enum { + kMaxConicsForArc = 5 + }; + static int BuildUnitArc(const SkVector& start, const SkVector& stop, SkRotationDirection, + const SkMatrix*, SkConic conics[kMaxConicsForArc]); +}; + +// inline helpers are contained in a namespace to avoid external leakage to fragile SkNx members +namespace { // NOLINT(google-build-namespaces) + +/** + * use for : eval(t) == A * t^2 + B * t + C + */ +struct SkQuadCoeff { + SkQuadCoeff() {} + + SkQuadCoeff(const Sk2s& A, const Sk2s& B, const Sk2s& C) + : fA(A) + , fB(B) + , fC(C) + { + } + + SkQuadCoeff(const SkPoint src[3]) { + fC = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); + fB = times_2(P1 - fC); + fA = P2 - times_2(P1) + fC; + } + + Sk2s eval(SkScalar t) { + Sk2s tt(t); + return eval(tt); + } + + Sk2s eval(const Sk2s& tt) { + return (fA * tt + fB) * tt + fC; + } + + Sk2s fA; + Sk2s fB; + Sk2s fC; +}; + +struct SkConicCoeff { + SkConicCoeff(const SkConic& conic) { + Sk2s p0 = from_point(conic.fPts[0]); + Sk2s p1 = from_point(conic.fPts[1]); + Sk2s p2 = from_point(conic.fPts[2]); + Sk2s ww(conic.fW); + + Sk2s p1w = p1 * ww; + fNumer.fC = p0; + fNumer.fA = p2 - times_2(p1w) + p0; + fNumer.fB = times_2(p1w - p0); + + fDenom.fC = Sk2s(1); + fDenom.fB = times_2(ww - fDenom.fC); + fDenom.fA = Sk2s(0) - fDenom.fB; + } + + Sk2s eval(SkScalar t) { + Sk2s tt(t); + Sk2s numer = fNumer.eval(tt); + Sk2s denom = fDenom.eval(tt); + return numer / denom; + } + + SkQuadCoeff fNumer; + SkQuadCoeff fDenom; +}; + +struct SkCubicCoeff { + SkCubicCoeff(const SkPoint src[4]) { + Sk2s P0 = from_point(src[0]); + Sk2s P1 = from_point(src[1]); + Sk2s P2 = from_point(src[2]); + Sk2s P3 = from_point(src[3]); + Sk2s three(3); + fA = P3 + three * (P1 - P2) - P0; + fB = three * (P2 - times_2(P1) + P0); + fC = three * (P1 - P0); + fD = P0; + } + + Sk2s eval(SkScalar t) { + Sk2s tt(t); + return eval(tt); + } + + Sk2s eval(const Sk2s& t) { + return ((fA * t + fB) * t + fC) * t + fD; + } + + Sk2s fA; + Sk2s fB; + Sk2s fC; + Sk2s fD; +}; + +} + +#include "include/private/SkTemplates.h" + +/** + * Help class to allocate storage for approximating a conic with N quads. + */ +class SkAutoConicToQuads { +public: + SkAutoConicToQuads() : fQuadCount(0) {} + + /** + * Given a conic and a tolerance, return the array of points for the + * approximating quad(s). Call countQuads() to know the number of quads + * represented in these points. + * + * The quads are allocated to share end-points. e.g. if there are 4 quads, + * there will be 9 points allocated as follows + * quad[0] == pts[0..2] + * quad[1] == pts[2..4] + * quad[2] == pts[4..6] + * quad[3] == pts[6..8] + */ + const SkPoint* computeQuads(const SkConic& conic, SkScalar tol) { + int pow2 = conic.computeQuadPOW2(tol); + fQuadCount = 1 << pow2; + SkPoint* pts = fStorage.reset(1 + 2 * fQuadCount); + fQuadCount = conic.chopIntoQuadsPOW2(pts, pow2); + return pts; + } + + const SkPoint* computeQuads(const SkPoint pts[3], SkScalar weight, + SkScalar tol) { + SkConic conic; + conic.set(pts, weight); + return computeQuads(conic, tol); + } + + int countQuads() const { return fQuadCount; } + +private: + enum { + kQuadCount = 8, // should handle most conics + kPointCount = 1 + 2 * kQuadCount, + }; + SkAutoSTMalloc fStorage; + int fQuadCount; // #quads for current usage +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkGlobalInitialization_core.cpp b/gfx/skia/skia/src/core/SkGlobalInitialization_core.cpp new file mode 100644 index 0000000000..0c60c91169 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlobalInitialization_core.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFlattenable.h" +#include "include/private/SkOnce.h" + +void SkFlattenable::RegisterFlattenablesIfNeeded() { + static SkOnce once; + once([]{ + SkFlattenable::PrivateInitializer::InitEffects(); + SkFlattenable::PrivateInitializer::InitImageFilters(); + SkFlattenable::Finalize(); + }); +} diff --git a/gfx/skia/skia/src/core/SkGlyph.cpp b/gfx/skia/skia/src/core/SkGlyph.cpp new file mode 100644 index 0000000000..35218f4e5a --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyph.cpp @@ -0,0 +1,353 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkGlyph.h" + +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkScalerContext.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsQuad.h" + +SkMask SkGlyph::mask() const { + // getMetrics had to be called. + SkASSERT(fMaskFormat != MASK_FORMAT_UNKNOWN); + + SkMask mask; + mask.fImage = (uint8_t*)fImage; + mask.fBounds.setXYWH(fLeft, fTop, fWidth, fHeight); + mask.fRowBytes = this->rowBytes(); + mask.fFormat = static_cast(fMaskFormat); + return mask; +} + +SkMask SkGlyph::mask(SkPoint position) const { + SkMask answer = this->mask(); + answer.fBounds.offset(SkScalarFloorToInt(position.x()), SkScalarFloorToInt(position.y())); + return answer; +} + +void SkGlyph::zeroMetrics() { + fAdvanceX = 0; + fAdvanceY = 0; + fWidth = 0; + fHeight = 0; + fTop = 0; + fLeft = 0; +} + +static size_t bits_to_bytes(size_t bits) { + return (bits + 7) >> 3; +} + +static size_t format_alignment(SkMask::Format format) { + switch (format) { + case SkMask::kBW_Format: + case SkMask::kA8_Format: + case SkMask::k3D_Format: + case SkMask::kSDF_Format: + return alignof(uint8_t); + case SkMask::kARGB32_Format: + return alignof(uint32_t); + case SkMask::kLCD16_Format: + return alignof(uint16_t); + default: + SK_ABORT("Unknown mask format."); + break; + } + return 0; +} + +static size_t format_rowbytes(int width, SkMask::Format format) { + return format == SkMask::kBW_Format ? bits_to_bytes(width) + : width * format_alignment(format); +} + +SkGlyph::SkGlyph(const SkGlyphPrototype& p) + : fWidth{p.width} + , fHeight{p.height} + , fTop{p.top} + , fLeft{p.left} + , fAdvanceX{p.advanceX} + , fAdvanceY{p.advanceY} + , fMaskFormat{(uint8_t)p.maskFormat} + , fForceBW{p.forceBW} + , fID{p.id} + {} + +size_t SkGlyph::formatAlignment() const { + return format_alignment(this->maskFormat()); +} + +size_t SkGlyph::allocImage(SkArenaAlloc* alloc) { + SkASSERT(!this->isEmpty()); + auto size = this->imageSize(); + fImage = alloc->makeBytesAlignedTo(size, this->formatAlignment()); + + return size; +} + +bool SkGlyph::setImage(SkArenaAlloc* alloc, SkScalerContext* scalerContext) { + if (!this->setImageHasBeenCalled()) { + // It used to be that getImage() could change the fMaskFormat. Extra checking to make + // sure there are no regressions. + SkDEBUGCODE(SkMask::Format oldFormat = this->maskFormat()); + this->allocImage(alloc); + scalerContext->getImage(*this); + SkASSERT(oldFormat == this->maskFormat()); + return true; + } + return false; +} + +bool SkGlyph::setImage(SkArenaAlloc* alloc, const void* image) { + if (!this->setImageHasBeenCalled()) { + this->allocImage(alloc); + memcpy(fImage, image, this->imageSize()); + return true; + } + return false; +} + +bool SkGlyph::setMetricsAndImage(SkArenaAlloc* alloc, const SkGlyph& from) { + if (fImage == nullptr) { + fAdvanceX = from.fAdvanceX; + fAdvanceY = from.fAdvanceY; + fWidth = from.fWidth; + fHeight = from.fHeight; + fTop = from.fTop; + fLeft = from.fLeft; + fForceBW = from.fForceBW; + fMaskFormat = from.fMaskFormat; + return this->setImage(alloc, from.image()); + } + return false; +} + +size_t SkGlyph::rowBytes() const { + return format_rowbytes(fWidth, (SkMask::Format)fMaskFormat); +} + +size_t SkGlyph::rowBytesUsingFormat(SkMask::Format format) const { + return format_rowbytes(fWidth, format); +} + +size_t SkGlyph::imageSize() const { + if (this->isEmpty() || this->imageTooLarge()) { return 0; } + + size_t size = this->rowBytes() * fHeight; + + if (fMaskFormat == SkMask::k3D_Format) { + size *= 3; + } + + return size; +} + +void SkGlyph::installPath(SkArenaAlloc* alloc, const SkPath* path) { + SkASSERT(fPathData == nullptr); + SkASSERT(!this->setPathHasBeenCalled()); + fPathData = alloc->make(); + if (path != nullptr) { + fPathData->fPath = *path; + fPathData->fPath.updateBoundsCache(); + fPathData->fPath.getGenerationID(); + fPathData->fHasPath = true; + } +} + +bool SkGlyph::setPath(SkArenaAlloc* alloc, SkScalerContext* scalerContext) { + if (!this->setPathHasBeenCalled()) { + SkPath path; + if (scalerContext->getPath(this->getPackedID(), &path)) { + this->installPath(alloc, &path); + } else { + this->installPath(alloc, nullptr); + } + return this->path() != nullptr; + } + + return false; +} + +bool SkGlyph::setPath(SkArenaAlloc* alloc, const SkPath* path) { + if (!this->setPathHasBeenCalled()) { + this->installPath(alloc, path); + return this->path() != nullptr; + } + return false; +} + +const SkPath* SkGlyph::path() const { + // setPath must have been called previously. + SkASSERT(this->setPathHasBeenCalled()); + if (fPathData->fHasPath) { + return &fPathData->fPath; + } + return nullptr; +} + +static std::tuple calculate_path_gap( + SkScalar topOffset, SkScalar bottomOffset, const SkPath& path) { + + // Left and Right of an ever expanding gap around the path. + SkScalar left = SK_ScalarMax, + right = SK_ScalarMin; + auto expandGap = [&left, &right](SkScalar v) { + left = SkTMin(left, v); + right = SkTMax(right, v); + }; + + // Handle all the different verbs for the path. + SkPoint pts[4]; + auto addLine = [&expandGap, &pts](SkScalar offset) { + SkScalar t = sk_ieee_float_divide(offset - pts[0].fY, pts[1].fY - pts[0].fY); + if (0 <= t && t < 1) { // this handles divide by zero above + expandGap(pts[0].fX + t * (pts[1].fX - pts[0].fX)); + } + }; + + auto addQuad = [&expandGap, &pts](SkScalar offset) { + SkDQuad quad; + quad.set(pts); + double roots[2]; + int count = quad.horizontalIntersect(offset, roots); + while (--count >= 0) { + expandGap(quad.ptAtT(roots[count]).asSkPoint().fX); + } + }; + + auto addCubic = [&expandGap, &pts](SkScalar offset) { + SkDCubic cubic; + cubic.set(pts); + double roots[3]; + int count = cubic.horizontalIntersect(offset, roots); + while (--count >= 0) { + expandGap(cubic.ptAtT(roots[count]).asSkPoint().fX); + } + }; + + // Handle when a verb's points are in the gap between top and bottom. + auto addPts = [&expandGap, &pts, topOffset, bottomOffset](int ptCount) { + for (int i = 0; i < ptCount; ++i) { + if (topOffset < pts[i].fY && pts[i].fY < bottomOffset) { + expandGap(pts[i].fX); + } + } + }; + + SkPath::Iter iter(path, false); + SkPath::Verb verb; + while (SkPath::kDone_Verb != (verb = iter.next(pts))) { + switch (verb) { + case SkPath::kMove_Verb: { + break; + } + case SkPath::kLine_Verb: { + addLine(topOffset); + addLine(bottomOffset); + addPts(2); + break; + } + case SkPath::kQuad_Verb: { + SkScalar quadTop = SkTMin(SkTMin(pts[0].fY, pts[1].fY), pts[2].fY); + if (bottomOffset < quadTop) { break; } + SkScalar quadBottom = SkTMax(SkTMax(pts[0].fY, pts[1].fY), pts[2].fY); + if (topOffset > quadBottom) { break; } + addQuad(topOffset); + addQuad(bottomOffset); + addPts(3); + break; + } + case SkPath::kConic_Verb: { + SkASSERT(0); // no support for text composed of conics + break; + } + case SkPath::kCubic_Verb: { + SkScalar quadTop = + SkTMin(SkTMin(SkTMin(pts[0].fY, pts[1].fY), pts[2].fY), pts[3].fY); + if (bottomOffset < quadTop) { break; } + SkScalar quadBottom = + SkTMax(SkTMax(SkTMax(pts[0].fY, pts[1].fY), pts[2].fY), pts[3].fY); + if (topOffset > quadBottom) { break; } + addCubic(topOffset); + addCubic(bottomOffset); + addPts(4); + break; + } + case SkPath::kClose_Verb: { + break; + } + default: { + SkASSERT(0); + break; + } + } + } + + return std::tie(left, right); +} + +void SkGlyph::ensureIntercepts(const SkScalar* bounds, SkScalar scale, SkScalar xPos, + SkScalar* array, int* count, SkArenaAlloc* alloc) { + + auto offsetResults = [scale, xPos]( + const SkGlyph::Intercept* intercept,SkScalar* array, int* count) { + if (array) { + array += *count; + for (int index = 0; index < 2; index++) { + *array++ = intercept->fInterval[index] * scale + xPos; + } + } + *count += 2; + }; + + const SkGlyph::Intercept* match = + [this](const SkScalar bounds[2]) -> const SkGlyph::Intercept* { + if (!fPathData) { + return nullptr; + } + const SkGlyph::Intercept* intercept = fPathData->fIntercept; + while (intercept) { + if (bounds[0] == intercept->fBounds[0] && bounds[1] == intercept->fBounds[1]) { + return intercept; + } + intercept = intercept->fNext; + } + return nullptr; + }(bounds); + + if (match) { + if (match->fInterval[0] < match->fInterval[1]) { + offsetResults(match, array, count); + } + return; + } + + SkGlyph::Intercept* intercept = alloc->make(); + intercept->fNext = fPathData->fIntercept; + intercept->fBounds[0] = bounds[0]; + intercept->fBounds[1] = bounds[1]; + intercept->fInterval[0] = SK_ScalarMax; + intercept->fInterval[1] = SK_ScalarMin; + fPathData->fIntercept = intercept; + const SkPath* path = &(fPathData->fPath); + const SkRect& pathBounds = path->getBounds(); + if (pathBounds.fBottom < bounds[0] || bounds[1] < pathBounds.fTop) { + return; + } + + std::tie(intercept->fInterval[0], intercept->fInterval[1]) + = calculate_path_gap(bounds[0], bounds[1], *path); + + if (intercept->fInterval[0] >= intercept->fInterval[1]) { + intercept->fInterval[0] = SK_ScalarMax; + intercept->fInterval[1] = SK_ScalarMin; + return; + } + offsetResults(intercept, array, count); +} diff --git a/gfx/skia/skia/src/core/SkGlyph.h b/gfx/skia/skia/src/core/SkGlyph.h new file mode 100644 index 0000000000..46ba710897 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyph.h @@ -0,0 +1,320 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGlyph_DEFINED +#define SkGlyph_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkTypes.h" +#include "include/private/SkChecksum.h" +#include "include/private/SkFixed.h" +#include "include/private/SkTo.h" +#include "src/core/SkMask.h" + +class SkArenaAlloc; +class SkStrike; +class SkScalerContext; + +// needs to be != to any valid SkMask::Format +#define MASK_FORMAT_UNKNOWN (0xFF) +#define MASK_FORMAT_JUST_ADVANCE MASK_FORMAT_UNKNOWN + +// A combination of SkGlyphID and sub-pixel position information. +struct SkPackedGlyphID { + static constexpr uint32_t kImpossibleID = ~0u; + enum { + // Lengths + kGlyphIDLen = 16u, + kSubPixelPosLen = 2u, + + // Bit positions + kGlyphID = 0u, + kSubPixelY = kGlyphIDLen, + kSubPixelX = kGlyphIDLen + kSubPixelPosLen, + kEndData = kGlyphIDLen + 2 * kSubPixelPosLen, + + // Masks + kGlyphIDMask = (1u << kGlyphIDLen) - 1, + kSubPixelPosMask = (1u << kSubPixelPosLen) - 1, + kMaskAll = (1u << kEndData) - 1, + + // Location of sub pixel info in a fixed pointer number. + kFixedPointBinaryPointPos = 16u, + kFixedPointSubPixelPosBits = kFixedPointBinaryPointPos - kSubPixelPosLen, + }; + + constexpr explicit SkPackedGlyphID(SkGlyphID glyphID) + : fID{glyphID} { } + + constexpr SkPackedGlyphID(SkGlyphID glyphID, SkFixed x, SkFixed y) + : fID {PackIDXY(glyphID, x, y)} { + SkASSERT(fID != kImpossibleID); + } + + constexpr SkPackedGlyphID(SkGlyphID code, SkIPoint pt) + : SkPackedGlyphID(code, pt.fX, pt.fY) { } + + constexpr SkPackedGlyphID() : fID{kImpossibleID} {} + + bool operator==(const SkPackedGlyphID& that) const { + return fID == that.fID; + } + bool operator!=(const SkPackedGlyphID& that) const { + return !(*this == that); + } + bool operator<(SkPackedGlyphID that) const { + return this->fID < that.fID; + } + + uint32_t code() const { + return fID & kGlyphIDMask; + } + + uint32_t value() const { + return fID; + } + + SkFixed getSubXFixed() const { + return this->subToFixed(kSubPixelX); + } + + SkFixed getSubYFixed() const { + return this->subToFixed(kSubPixelY); + } + + uint32_t hash() const { + return SkChecksum::CheapMix(fID); + } + + SkString dump() const { + SkString str; + str.appendf("code: %d, x: %d, y:%d", code(), getSubXFixed(), getSubYFixed()); + return str; + } + +private: + static constexpr uint32_t PackIDXY(SkGlyphID glyphID, SkFixed x, SkFixed y) { + return (FixedToSub(x) << kSubPixelX) + | (FixedToSub(y) << kSubPixelY) + | glyphID; + } + + static constexpr uint32_t FixedToSub(SkFixed n) { + return ((uint32_t)n >> kFixedPointSubPixelPosBits) & kSubPixelPosMask; + } + + constexpr SkFixed subToFixed(uint32_t subPixelPosBit) const { + uint32_t subPixelPosition = (fID >> subPixelPosBit) & kSubPixelPosMask; + return subPixelPosition << kFixedPointSubPixelPosBits; + } + + uint32_t fID; +}; + +struct SkGlyphPrototype; + +class SkGlyph { +public: + static constexpr SkFixed kSubpixelRound = SK_FixedHalf >> SkPackedGlyphID::kSubPixelPosLen; + + // SkGlyph() is used for testing. + constexpr SkGlyph() : fID{SkPackedGlyphID()} { } + constexpr explicit SkGlyph(SkPackedGlyphID id) : fID{id} { } + explicit SkGlyph(const SkGlyphPrototype& p); + + SkVector advanceVector() const { return SkVector{fAdvanceX, fAdvanceY}; } + SkScalar advanceX() const { return fAdvanceX; } + SkScalar advanceY() const { return fAdvanceY; } + + SkGlyphID getGlyphID() const { return fID.code(); } + SkPackedGlyphID getPackedID() const { return fID; } + SkFixed getSubXFixed() const { return fID.getSubXFixed(); } + SkFixed getSubYFixed() const { return fID.getSubYFixed(); } + + size_t rowBytes() const; + size_t rowBytesUsingFormat(SkMask::Format format) const; + + // Call this to set all of the metrics fields to 0 (e.g. if the scaler + // encounters an error measuring a glyph). Note: this does not alter the + // fImage, fPath, fID, fMaskFormat fields. + void zeroMetrics(); + + SkMask mask() const; + + SkMask mask(SkPoint position) const; + + // Image + // If we haven't already tried to associate an image with this glyph + // (i.e. setImageHasBeenCalled() returns false), then use the + // SkScalerContext or const void* argument to set the image. + bool setImage(SkArenaAlloc* alloc, SkScalerContext* scalerContext); + bool setImage(SkArenaAlloc* alloc, const void* image); + + // Merge the from glyph into this glyph using alloc to allocate image data. Return true if + // image data was allocated. If the image for this glyph has not been initialized, then copy + // the width, height, top, left, format, and image into this glyph making a copy of the image + // using the alloc. + bool setMetricsAndImage(SkArenaAlloc* alloc, const SkGlyph& from); + + // Returns true if the image has been set. + bool setImageHasBeenCalled() const { + return fImage != nullptr || this->isEmpty() || this->imageTooLarge(); + } + + // Return a pointer to the path if the image exists, otherwise return nullptr. + const void* image() const { SkASSERT(this->setImageHasBeenCalled()); return fImage; } + + // Return the size of the image. + size_t imageSize() const; + + // Path + // If we haven't already tried to associate a path to this glyph + // (i.e. setPathHasBeenCalled() returns false), then use the + // SkScalerContext or SkPath argument to try to do so. N.B. this + // may still result in no path being associated with this glyph, + // e.g. if you pass a null SkPath or the typeface is bitmap-only. + // + // This setPath() call is sticky... once you call it, the glyph + // stays in its state permanently, ignoring any future calls. + // + // Returns true if this is the first time you called setPath() + // and there actually is a path; call path() to get it. + bool setPath(SkArenaAlloc* alloc, SkScalerContext* scalerContext); + bool setPath(SkArenaAlloc* alloc, const SkPath* path); + + // Returns true if that path has been set. + bool setPathHasBeenCalled() const { return fPathData != nullptr; } + + // Return a pointer to the path if it exists, otherwise return nullptr. Only works if the + // path was previously set. + const SkPath* path() const; + + // Format + bool isColor() const { return fMaskFormat == SkMask::kARGB32_Format; } + SkMask::Format maskFormat() const { return static_cast(fMaskFormat); } + size_t formatAlignment() const; + + // Bounds + int maxDimension() const { return std::max(fWidth, fHeight); } + SkIRect iRect() const { return SkIRect::MakeXYWH(fLeft, fTop, fWidth, fHeight); } + SkRect rect() const { return SkRect::MakeXYWH(fLeft, fTop, fWidth, fHeight); } + int left() const { return fLeft; } + int top() const { return fTop; } + int width() const { return fWidth; } + int height() const { return fHeight; } + bool isEmpty() const { + // fHeight == 0 -> fWidth == 0; + SkASSERT(fHeight != 0 || fWidth == 0); + return fWidth == 0; + } + bool imageTooLarge() const { return fWidth >= kMaxGlyphWidth; } + + // Make sure that the intercept information is on the glyph and return it, or return it if it + // already exists. + // * bounds - either end of the gap for the character. + // * scale, xPos - information about how wide the gap is. + // * array - accumulated gaps for many characters if not null. + // * count - the number of gaps. + void ensureIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos, + SkScalar* array, int* count, SkArenaAlloc* alloc); + +private: + // There are two sides to an SkGlyph, the scaler side (things that create glyph data) have + // access to all the fields. Scalers are assumed to maintain all the SkGlyph invariants. The + // consumer side has a tighter interface. + friend class RandomScalerContext; + friend class SkScalerContext; + friend class SkScalerContextProxy; + friend class SkScalerContext_Empty; + friend class SkScalerContext_FreeType; + friend class SkScalerContext_FreeType_Base; + friend class SkScalerContext_CairoFT; + friend class SkScalerContext_DW; + friend class SkScalerContext_GDI; + friend class SkScalerContext_Mac; + friend class SkStrikeClient; + friend class SkStrikeServer; + friend class SkTestScalerContext; + friend class SkTestSVGScalerContext; + friend class TestSVGTypeface; + friend class TestTypeface; + + static constexpr uint16_t kMaxGlyphWidth = 1u << 13u; + + // Support horizontal and vertical skipping strike-through / underlines. + // The caller walks the linked list looking for a match. For a horizontal underline, + // the fBounds contains the top and bottom of the underline. The fInterval pair contains the + // beginning and end of of the intersection of the bounds and the glyph's path. + // If interval[0] >= interval[1], no intersection was found. + struct Intercept { + Intercept* fNext; + SkScalar fBounds[2]; // for horz underlines, the boundaries in Y + SkScalar fInterval[2]; // the outside intersections of the axis and the glyph + }; + + struct PathData { + Intercept* fIntercept{nullptr}; + SkPath fPath; + bool fHasPath{false}; + }; + + size_t allocImage(SkArenaAlloc* alloc); + + // path == nullptr indicates that there is no path. + void installPath(SkArenaAlloc* alloc, const SkPath* path); + + // The width and height of the glyph mask. + uint16_t fWidth = 0, + fHeight = 0; + + // The offset from the glyphs origin on the baseline to the top left of the glyph mask. + int16_t fTop = 0, + fLeft = 0; + + // fImage must remain null if the glyph is empty or if width > kMaxGlyphWidth. + void* fImage = nullptr; + + // Path data has tricky state. If the glyph isEmpty, then fPathData should always be nullptr, + // else if fPathData is not null, then a path has been requested. The fPath field of fPathData + // may still be null after the request meaning that there is no path for this glyph. + PathData* fPathData = nullptr; + + // The advance for this glyph. + float fAdvanceX = 0, + fAdvanceY = 0; + + // This is a combination of SkMask::Format and SkGlyph state. The SkGlyph can be in one of two + // states, just the advances have been calculated, and all the metrics are available. The + // illegal mask format is used to signal that only the advances are available. + uint8_t fMaskFormat = MASK_FORMAT_UNKNOWN; + + // Used by the DirectWrite scaler to track state. + int8_t fForceBW = 0; + + const SkPackedGlyphID fID; +}; + +struct SkGlyphPrototype { + SkPackedGlyphID id; + + float advanceX = 0, + advanceY = 0; + + // The width and height of the glyph mask. + uint16_t width = 0, + height = 0; + + // The offset from the glyphs origin on the baseline to the top left of the glyph mask. + int16_t left = 0, + top = 0; + + SkMask::Format maskFormat = SkMask::kBW_Format; + + bool forceBW = false; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkGlyphBuffer.cpp b/gfx/skia/skia/src/core/SkGlyphBuffer.cpp new file mode 100644 index 0000000000..a3ef7fb804 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyphBuffer.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkGlyphBuffer.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkStrikeForGPU.h" + +void SkDrawableGlyphBuffer::ensureSize(size_t size) { + if (size > fMaxSize) { + fMultiBuffer.reset(size); + fPositions.reset(size); + fMaxSize = size; + } + + fInputSize = 0; + fDrawableSize = 0; +} + +void SkDrawableGlyphBuffer::startSource( + const SkZip& source, SkPoint origin) { + fInputSize = source.size(); + fDrawableSize = 0; + + // Map all the positions. + auto positions = source.get<1>(); + SkMatrix::MakeTrans(origin.x(), origin.y()).mapPoints( + fPositions, positions.data(), positions.size()); + + // Convert from SkGlyphIDs to SkPackedGlyphIDs. + SkGlyphVariant* packedIDCursor = fMultiBuffer; + for (auto t : source) { + *packedIDCursor++ = SkPackedGlyphID{std::get<0>(t)}; + } + SkDEBUGCODE(fPhase = kInput); +} + +void SkDrawableGlyphBuffer::startDevice( + const SkZip& source, + SkPoint origin, const SkMatrix& viewMatrix, + const SkGlyphPositionRoundingSpec& roundingSpec) { + fInputSize = source.size(); + fDrawableSize = 0; + + // Map the positions including subpixel position. + auto positions = source.get<1>(); + SkMatrix matrix = viewMatrix; + matrix.preTranslate(origin.x(), origin.y()); + SkPoint halfSampleFreq = roundingSpec.halfAxisSampleFreq; + matrix.postTranslate(halfSampleFreq.x(), halfSampleFreq.y()); + matrix.mapPoints(fPositions, positions.data(), positions.size()); + + // Mask for controlling axis alignment. + SkIPoint mask = roundingSpec.ignorePositionMask; + + // Convert glyph ids and positions to packed glyph ids. + SkZip withMappedPos = + SkMakeZip(source.get<0>(), fPositions.get()); + SkGlyphVariant* packedIDCursor = fMultiBuffer; + for (auto t : withMappedPos) { + SkGlyphID glyphID; SkPoint pos; + std::tie(glyphID, pos) = t; + SkFixed subX = SkScalarToFixed(pos.x()) & mask.x(), + subY = SkScalarToFixed(pos.y()) & mask.y(); + *packedIDCursor++ = SkPackedGlyphID{glyphID, subX, subY}; + } + SkDEBUGCODE(fPhase = kInput); +} + +void SkDrawableGlyphBuffer::reset() { + SkDEBUGCODE(fPhase = kReset); + if (fMaxSize > 200) { + fMultiBuffer.reset(); + fPositions.reset(); + fMaxSize = 0; + } + fInputSize = 0; + fDrawableSize = 0; +} + diff --git a/gfx/skia/skia/src/core/SkGlyphBuffer.h b/gfx/skia/skia/src/core/SkGlyphBuffer.h new file mode 100644 index 0000000000..ca7541a623 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyphBuffer.h @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGlyphBuffer_DEFINED +#define SkGlyphBuffer_DEFINED + +#include "src/core/SkGlyph.h" +#include "src/core/SkZip.h" + +class SkStrikeForGPU; +struct SkGlyphPositionRoundingSpec; + +// A memory format that allows an SkPackedGlyphID, SkGlyph*, and SkPath* to occupy the same +// memory. This allows SkPackedGlyphIDs as input, and SkGlyph*/SkPath* as output using the same +// memory. +class SkGlyphVariant { +public: + SkGlyphVariant() : fV{nullptr} { } + SkGlyphVariant& operator= (SkPackedGlyphID packedID) { + fV.packedID = packedID; + SkDEBUGCODE(fTag = kPackedID); + return *this; + } + SkGlyphVariant& operator= (SkGlyph* glyph) { + fV.glyph = glyph; + SkDEBUGCODE(fTag = kGlyph); + return *this; + + } + SkGlyphVariant& operator= (const SkPath* path) { + fV.path = path; + SkDEBUGCODE(fTag = kPath); + return *this; + } + + SkGlyph* glyph() const { + SkASSERT(fTag == kGlyph); + return fV.glyph; + } + const SkPath* path() const { + SkASSERT(fTag == kPath); + return fV.path; + } + SkPackedGlyphID packedID() const { + SkASSERT(fTag == kPackedID); + return fV.packedID; + } + + operator SkPackedGlyphID() const { return this->packedID(); } + operator SkGlyph*() const { return this->glyph(); } + operator const SkPath*() const { return this->path(); } + +private: + union { + SkGlyph* glyph; + const SkPath* path; + SkPackedGlyphID packedID; + } fV; + +#ifdef SK_DEBUG + enum { + kEmpty, + kPackedID, + kGlyph, + kPath + } fTag{kEmpty}; +#endif +}; + +// A buffer for converting SkPackedGlyph to SkGlyph* or SkPath*. Initially the buffer contains +// SkPackedGlyphIDs, but those are used to lookup SkGlyph*/SkPath* which are then copied over the +// SkPackedGlyphIDs. +class SkDrawableGlyphBuffer { +public: + void ensureSize(size_t size); + + // Load the buffer with SkPackedGlyphIDs and positions in source space. + void startSource(const SkZip& source, SkPoint origin); + + // Load the buffer with SkPackedGlyphIDs and positions using the device transform. + void startDevice( + const SkZip& source, + SkPoint origin, const SkMatrix& viewMatrix, + const SkGlyphPositionRoundingSpec& roundingSpec); + + // The input of SkPackedGlyphIDs + SkZip input() { + SkASSERT(fPhase == kInput); + SkDEBUGCODE(fPhase = kProcess); + return SkZip{fInputSize, fMultiBuffer, fPositions}; + } + + // Store the glyph in the next drawable slot, using the position information located at index + // from. + void push_back(SkGlyph* glyph, size_t from) { + SkASSERT(fPhase == kProcess); + SkASSERT(fDrawableSize <= from); + fPositions[fDrawableSize] = fPositions[from]; + fMultiBuffer[fDrawableSize] = glyph; + fDrawableSize++; + } + + // Store the path in the next drawable slot, using the position information located at index + // from. + void push_back(const SkPath* path, size_t from) { + SkASSERT(fPhase == kProcess); + SkASSERT(fDrawableSize <= from); + fPositions[fDrawableSize] = fPositions[from]; + fMultiBuffer[fDrawableSize] = path; + fDrawableSize++; + } + + // The result after a series of push_backs of drawable SkGlyph* or SkPath*. + SkZip drawable() { + SkASSERT(fPhase == kProcess); + SkDEBUGCODE(fPhase = kDraw); + return SkZip{fDrawableSize, fMultiBuffer, fPositions}; + } + + void reset(); + +private: + size_t fMaxSize{0}; + size_t fInputSize{0}; + size_t fDrawableSize{0}; + SkAutoTMalloc fMultiBuffer; + SkAutoTMalloc fPositions; + +#ifdef SK_DEBUG + enum { + kReset, + kInput, + kProcess, + kDraw + } fPhase{kReset}; +#endif +}; +#endif // SkGlyphBuffer_DEFINED diff --git a/gfx/skia/skia/src/core/SkGlyphRun.cpp b/gfx/skia/skia/src/core/SkGlyphRun.cpp new file mode 100644 index 0000000000..e85fb3742c --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyphRun.cpp @@ -0,0 +1,378 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkGlyphRun.h" + +#include "include/core/SkFont.h" +#include "include/core/SkPaint.h" +#include "include/core/SkTextBlob.h" +#include "include/private/SkTo.h" +#include "src/core/SkDevice.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkStrikeSpec.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/core/SkUtils.h" + +// -- SkGlyphRun ----------------------------------------------------------------------------------- +SkGlyphRun::SkGlyphRun(const SkFont& font, + SkSpan positions, + SkSpan glyphIDs, + SkSpan text, + SkSpan clusters) + : fSource{SkMakeZip(glyphIDs, positions)} + , fText{text} + , fClusters{clusters} + , fFont{font} {} + +SkGlyphRun::SkGlyphRun(const SkGlyphRun& that, const SkFont& font) + : fSource{that.fSource} + , fText{that.fText} + , fClusters{that.fClusters} + , fFont{font} {} + +// -- SkGlyphRunList ------------------------------------------------------------------------------- +SkGlyphRunList::SkGlyphRunList() = default; +SkGlyphRunList::SkGlyphRunList( + const SkPaint& paint, + const SkTextBlob* blob, + SkPoint origin, + SkSpan glyphRunList) + : fGlyphRuns{glyphRunList} + , fOriginalPaint{&paint} + , fOriginalTextBlob{blob} + , fOrigin{origin} { } + +SkGlyphRunList::SkGlyphRunList(const SkGlyphRun& glyphRun, const SkPaint& paint) + : fGlyphRuns{SkSpan{&glyphRun, 1}} + , fOriginalPaint{&paint} + , fOriginalTextBlob{nullptr} + , fOrigin{SkPoint::Make(0, 0)} {} + +uint64_t SkGlyphRunList::uniqueID() const { + return fOriginalTextBlob != nullptr ? fOriginalTextBlob->uniqueID() + : SK_InvalidUniqueID; +} + +bool SkGlyphRunList::anyRunsLCD() const { + for (const auto& r : fGlyphRuns) { + if (r.font().getEdging() == SkFont::Edging::kSubpixelAntiAlias) { + return true; + } + } + return false; +} + +bool SkGlyphRunList::anyRunsSubpixelPositioned() const { + for (const auto& r : fGlyphRuns) { + if (r.font().isSubpixel()) { + return true; + } + } + return false; +} + +bool SkGlyphRunList::allFontsFinite() const { + for (const auto& r : fGlyphRuns) { + if (!SkFontPriv::IsFinite(r.font())) { + return false; + } + } + return true; +} + +void SkGlyphRunList::temporaryShuntBlobNotifyAddedToCache(uint32_t cacheID) const { + SkASSERT(fOriginalTextBlob != nullptr); + fOriginalTextBlob->notifyAddedToCache(cacheID); +} + +// -- SkGlyphIDSet --------------------------------------------------------------------------------- +// A faster set implementation that does not need any initialization, and reading the set items +// is order the number of items, and not the size of the universe. +// This implementation is based on the paper by Briggs and Torczon, "An Efficient Representation +// for Sparse Sets" +// +// This implementation assumes that the unique glyphs added are appended to a vector that may +// already have unique glyph from a previous computation. This allows the packing of multiple +// UniqueID sequences in a single vector. +SkSpan SkGlyphIDSet::uniquifyGlyphIDs( + uint32_t universeSize, + SkSpan glyphIDs, + SkGlyphID* uniqueGlyphIDs, + uint16_t* denseIndices) { + static constexpr SkGlyphID kUndefGlyph{0}; + + if (universeSize > fUniverseToUniqueSize) { + fUniverseToUnique.reset(universeSize); + fUniverseToUniqueSize = universeSize; + // If the following bzero becomes a performance problem, the memory can be marked as + // initialized for valgrind and msan. + // valgrind = VALGRIND_MAKE_MEM_DEFINED(fUniverseToUnique, universeSize * sizeof(SkGlyphID)) + // msan = sk_msan_mark_initialized(fUniverseToUnique, universeSize * sizeof(SkGlyphID)) + sk_bzero(fUniverseToUnique, universeSize * sizeof(SkGlyphID)); + } + + // No need to clear fUniverseToUnique here... the set insertion algorithm is designed to work + // correctly even when the fUniverseToUnique buffer is uninitialized! + + size_t uniqueSize = 0; + size_t denseIndicesCursor = 0; + for (auto glyphID : glyphIDs) { + + // If the glyphID is not in range then it is the undefined glyph. + if (glyphID >= universeSize) { + glyphID = kUndefGlyph; + } + + // The index into the unique ID vector. + auto uniqueIndex = fUniverseToUnique[glyphID]; + + if (uniqueIndex >= uniqueSize || uniqueGlyphIDs[uniqueIndex] != glyphID) { + uniqueIndex = SkTo(uniqueSize); + uniqueGlyphIDs[uniqueSize] = glyphID; + fUniverseToUnique[glyphID] = uniqueIndex; + uniqueSize += 1; + } + + denseIndices[denseIndicesCursor++] = uniqueIndex; + } + + // If we're hanging onto these arrays for a long time, we don't want their size to drift + // endlessly upwards. It's unusual to see a typeface with more than 4096 possible glyphs. + if (fUniverseToUniqueSize > 4096) { + fUniverseToUnique.reset(4096); + sk_bzero(fUniverseToUnique, 4096 * sizeof(SkGlyphID)); + fUniverseToUniqueSize = 4096; + } + + return SkSpan(uniqueGlyphIDs, uniqueSize); +} + +// -- SkGlyphRunBuilder ---------------------------------------------------------------------------- +void SkGlyphRunBuilder::drawTextUTF8(const SkPaint& paint, const SkFont& font, const void* bytes, + size_t byteLength, SkPoint origin) { + auto glyphIDs = textToGlyphIDs(font, bytes, byteLength, SkTextEncoding::kUTF8); + if (!glyphIDs.empty()) { + this->initialize(glyphIDs.size()); + this->simplifyDrawText(font, glyphIDs, origin, fPositions); + } + + this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0)); +} + +void SkGlyphRunBuilder::drawTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin, + SkBaseDevice* device) { + // Figure out all the storage needed to pre-size everything below. + size_t totalGlyphs = 0; + for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) { + totalGlyphs += it.glyphCount(); + } + + // Pre-size all the buffers so they don't move during processing. + this->initialize(totalGlyphs); + + SkPoint* positions = fPositions; + + for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) { + if (it.positioning() != SkTextBlobRunIterator::kRSXform_Positioning) { + simplifyTextBlobIgnoringRSXForm(it, positions); + } else { + // Handle kRSXform_Positioning + if (!this->empty()) { + this->makeGlyphRunList(paint, &blob, origin); + device->drawGlyphRunList(this->useGlyphRunList()); + } + + device->drawGlyphRunRSXform(it.font(), it.glyphs(), (const SkRSXform*)it.pos(), + it.glyphCount(), origin, paint); + + // re-init in case we keep looping and need the builder again + this->initialize(totalGlyphs); + } + positions += it.glyphCount(); + } + + if (!this->empty()) { + this->makeGlyphRunList(paint, &blob, origin); + device->drawGlyphRunList(this->useGlyphRunList()); + } +} + +void SkGlyphRunBuilder::textBlobToGlyphRunListIgnoringRSXForm( + const SkPaint& paint, const SkTextBlob& blob, SkPoint origin) { + // Figure out all the storage needed to pre-size everything below. + size_t totalGlyphs = 0; + for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) { + totalGlyphs += it.glyphCount(); + } + + // Pre-size all the buffers so they don't move during processing. + this->initialize(totalGlyphs); + + SkPoint* positions = fPositions; + + for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) { + simplifyTextBlobIgnoringRSXForm(it, positions); + positions += it.glyphCount(); + } + + if (!this->empty()) { + this->makeGlyphRunList(paint, &blob, origin); + } +} + +void SkGlyphRunBuilder::simplifyTextBlobIgnoringRSXForm(const SkTextBlobRunIterator& it, + SkPoint* positions) { + size_t runSize = it.glyphCount(); + + auto text = SkSpan(it.text(), it.textSize()); + auto clusters = SkSpan(it.clusters(), runSize); + const SkPoint& offset = it.offset(); + auto glyphIDs = SkSpan{it.glyphs(), runSize}; + + switch (it.positioning()) { + case SkTextBlobRunIterator::kDefault_Positioning: { + this->simplifyDrawText( + it.font(), glyphIDs, offset, positions, text, clusters); + break; + } + case SkTextBlobRunIterator::kHorizontal_Positioning: { + auto constY = offset.y(); + this->simplifyDrawPosTextH( + it.font(), glyphIDs, it.pos(), constY, positions, text, clusters); + break; + } + case SkTextBlobRunIterator::kFull_Positioning: { + this->simplifyDrawPosText( + it.font(), glyphIDs, (const SkPoint*) it.pos(), text, clusters); + break; + } + case SkTextBlobRunIterator::kRSXform_Positioning: break; + } +} + +void SkGlyphRunBuilder::drawGlyphsWithPositions(const SkPaint& paint, const SkFont& font, + SkSpan glyphIDs, const SkPoint* pos) { + if (!glyphIDs.empty()) { + this->initialize(glyphIDs.size()); + this->simplifyDrawPosText(font, glyphIDs, pos); + this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0)); + } +} + +const SkGlyphRunList& SkGlyphRunBuilder::useGlyphRunList() { + return fGlyphRunList; +} + +void SkGlyphRunBuilder::initialize(size_t totalRunSize) { + + if (totalRunSize > fMaxTotalRunSize) { + fMaxTotalRunSize = totalRunSize; + fPositions.reset(fMaxTotalRunSize); + } + + fGlyphRunListStorage.clear(); +} + +SkSpan SkGlyphRunBuilder::textToGlyphIDs( + const SkFont& font, const void* bytes, size_t byteLength, SkTextEncoding encoding) { + if (encoding != SkTextEncoding::kGlyphID) { + int count = font.countText(bytes, byteLength, encoding); + if (count > 0) { + fScratchGlyphIDs.resize(count); + font.textToGlyphs(bytes, byteLength, encoding, fScratchGlyphIDs.data(), count); + return SkMakeSpan(fScratchGlyphIDs); + } else { + return SkSpan(); + } + } else { + return SkSpan((const SkGlyphID*)bytes, byteLength / 2); + } +} + +void SkGlyphRunBuilder::makeGlyphRun( + const SkFont& font, + SkSpan glyphIDs, + SkSpan positions, + SkSpan text, + SkSpan clusters) { + + // Ignore empty runs. + if (!glyphIDs.empty()) { + fGlyphRunListStorage.emplace_back( + font, + positions, + glyphIDs, + text, + clusters); + } +} + +void SkGlyphRunBuilder::makeGlyphRunList( + const SkPaint& paint, const SkTextBlob* blob, SkPoint origin) { + + fGlyphRunList.~SkGlyphRunList(); + new (&fGlyphRunList) SkGlyphRunList{ + paint, blob, origin, SkMakeSpan(fGlyphRunListStorage)}; +} + +void SkGlyphRunBuilder::simplifyDrawText( + const SkFont& font, SkSpan glyphIDs, + SkPoint origin, SkPoint* positions, + SkSpan text, SkSpan clusters) { + SkASSERT(!glyphIDs.empty()); + + auto runSize = glyphIDs.size(); + + if (!glyphIDs.empty()) { + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(font); + SkBulkGlyphMetrics storage{strikeSpec}; + auto glyphs = storage.glyphs(glyphIDs); + + SkPoint endOfLastGlyph = origin; + SkPoint* cursor = positions; + for (auto glyph : glyphs) { + *cursor++ = endOfLastGlyph; + endOfLastGlyph += glyph->advanceVector(); + } + + this->makeGlyphRun( + font, + glyphIDs, + SkSpan{positions, runSize}, + text, + clusters); + } +} + +void SkGlyphRunBuilder::simplifyDrawPosTextH( + const SkFont& font, SkSpan glyphIDs, + const SkScalar* xpos, SkScalar constY, SkPoint* positions, + SkSpan text, SkSpan clusters) { + + auto posCursor = positions; + for (auto x : SkSpan{xpos, glyphIDs.size()}) { + *posCursor++ = SkPoint::Make(x, constY); + } + + simplifyDrawPosText(font, glyphIDs, positions, text, clusters); +} + +void SkGlyphRunBuilder::simplifyDrawPosText( + const SkFont& font, SkSpan glyphIDs, + const SkPoint* pos, + SkSpan text, SkSpan clusters) { + auto runSize = glyphIDs.size(); + + this->makeGlyphRun( + font, + glyphIDs, + SkSpan{pos, runSize}, + text, + clusters); +} diff --git a/gfx/skia/skia/src/core/SkGlyphRun.h b/gfx/skia/skia/src/core/SkGlyphRun.h new file mode 100644 index 0000000000..3d9c918d40 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyphRun.h @@ -0,0 +1,175 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGlyphRun_DEFINED +#define SkGlyphRun_DEFINED + +#include +#include + +#include "include/core/SkFont.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPoint.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkSpan.h" +#include "src/core/SkZip.h" + +class SkBaseDevice; +class SkGlyph; +class SkTextBlob; +class SkTextBlobRunIterator; + +class SkGlyphRun { +public: + SkGlyphRun() = default; + SkGlyphRun(const SkFont& font, + SkSpan positions, + SkSpan glyphIDs, + SkSpan text, + SkSpan clusters); + SkGlyphRun(const SkGlyphRun& glyphRun, const SkFont& font); + + size_t runSize() const { return fSource.size(); } + SkSpan positions() const { return fSource.get<1>(); } + SkSpan glyphsIDs() const { return fSource.get<0>(); } + SkZip source() const { return fSource; } + const SkFont& font() const { return fFont; } + SkSpan clusters() const { return fClusters; } + SkSpan text() const { return fText; } + +private: + // GlyphIDs and positions. + const SkZip fSource; + // Original text from SkTextBlob if present. Will be empty of not present. + const SkSpan fText; + // Original clusters from SkTextBlob if present. Will be empty if not present. + const SkSpan fClusters; + // Paint for this run modified to have glyph encoding and left alignment. + SkFont fFont; +}; + +class SkGlyphRunList { + SkSpan fGlyphRuns; + +public: + SkGlyphRunList(); + // Blob maybe null. + SkGlyphRunList( + const SkPaint& paint, + const SkTextBlob* blob, + SkPoint origin, + SkSpan glyphRunList); + + SkGlyphRunList(const SkGlyphRun& glyphRun, const SkPaint& paint); + + uint64_t uniqueID() const; + bool anyRunsLCD() const; + bool anyRunsSubpixelPositioned() const; + void temporaryShuntBlobNotifyAddedToCache(uint32_t cacheID) const; + + bool canCache() const { return fOriginalTextBlob != nullptr; } + size_t runCount() const { return fGlyphRuns.size(); } + size_t totalGlyphCount() const { + size_t glyphCount = 0; + for(const auto& run : fGlyphRuns) { + glyphCount += run.runSize(); + } + return glyphCount; + } + bool allFontsFinite() const; + + SkPoint origin() const { return fOrigin; } + const SkPaint& paint() const { return *fOriginalPaint; } + const SkTextBlob* blob() const { return fOriginalTextBlob; } + + auto begin() -> decltype(fGlyphRuns.begin()) { return fGlyphRuns.begin(); } + auto end() -> decltype(fGlyphRuns.end()) { return fGlyphRuns.end(); } + auto begin() const -> decltype(fGlyphRuns.cbegin()) { return fGlyphRuns.cbegin(); } + auto end() const -> decltype(fGlyphRuns.cend()) { return fGlyphRuns.cend(); } + auto size() const -> decltype(fGlyphRuns.size()) { return fGlyphRuns.size(); } + auto empty() const -> decltype(fGlyphRuns.empty()) { return fGlyphRuns.empty(); } + auto operator [] (size_t i) const -> decltype(fGlyphRuns[i]) { return fGlyphRuns[i]; } + +private: + const SkPaint* fOriginalPaint{nullptr}; // This should be deleted soon. + // The text blob is needed to hookup the call back that the SkTextBlob destructor calls. It + // should be used for nothing else + const SkTextBlob* fOriginalTextBlob{nullptr}; + SkPoint fOrigin = {0, 0}; +}; + +class SkGlyphIDSet { +public: + SkSpan uniquifyGlyphIDs( + uint32_t universeSize, SkSpan glyphIDs, + SkGlyphID* uniqueGlyphIDs, uint16_t* denseindices); +private: + size_t fUniverseToUniqueSize{0}; + SkAutoTMalloc fUniverseToUnique; +}; + +class SkGlyphRunBuilder { +public: + void drawTextUTF8( + const SkPaint& paint, const SkFont&, const void* bytes, size_t byteLength, SkPoint origin); + void drawGlyphsWithPositions( + const SkPaint&, const SkFont&, SkSpan glyphIDs, const SkPoint* pos); + void drawTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin, SkBaseDevice*); + + void textBlobToGlyphRunListIgnoringRSXForm( + const SkPaint& paint, const SkTextBlob& blob, SkPoint origin); + + const SkGlyphRunList& useGlyphRunList(); + + bool empty() const { return fGlyphRunListStorage.empty(); } + +private: + void initialize(size_t totalRunSize); + SkSpan textToGlyphIDs( + const SkFont& font, const void* bytes, size_t byteLength, SkTextEncoding); + + void makeGlyphRun( + const SkFont& font, + SkSpan glyphIDs, + SkSpan positions, + SkSpan text, + SkSpan clusters); + + void makeGlyphRunList(const SkPaint& paint, const SkTextBlob* blob, SkPoint origin); + + void simplifyDrawText( + const SkFont& font, SkSpan glyphIDs, + SkPoint origin, SkPoint* positions, + SkSpan text = SkSpan{}, + SkSpan clusters = SkSpan{}); + void simplifyDrawPosTextH( + const SkFont& font, SkSpan glyphIDs, + const SkScalar* xpos, SkScalar constY, SkPoint* positions, + SkSpan text = SkSpan{}, + SkSpan clusters = SkSpan{}); + void simplifyDrawPosText( + const SkFont& font, SkSpan glyphIDs, + const SkPoint* pos, + SkSpan text = SkSpan{}, + SkSpan clusters = SkSpan{}); + void simplifyTextBlobIgnoringRSXForm( + const SkTextBlobRunIterator& it, + SkPoint* positions); + + size_t fMaxTotalRunSize{0}; + SkAutoTMalloc fPositions; + + std::vector fGlyphRunListStorage; + SkGlyphRunList fGlyphRunList; + + // Used as a temporary for preparing using utfN text. This implies that only one run of + // glyph ids will ever be needed because blobs are already glyph based. + std::vector fScratchGlyphIDs; +}; + +#endif // SkGlyphRun_DEFINED diff --git a/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp b/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp new file mode 100644 index 0000000000..cb63a0f916 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp @@ -0,0 +1,932 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkGlyphRunPainter.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/text/GrTextBlobCache.h" +#include "src/gpu/text/GrTextContext.h" +#endif + +#include "include/core/SkColorFilter.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkPathEffect.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkDevice.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/core/SkDraw.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkStrikeForGPU.h" +#include "src/core/SkStrikeSpec.h" +#include "src/core/SkTraceEvent.h" + +#include + +// -- SkGlyphRunListPainter ------------------------------------------------------------------------ +SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, + SkColorType colorType, + SkScalerContextFlags flags, + SkStrikeForGPUCacheInterface* strikeCache) + : fDeviceProps{props} + , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}} + , fColorType{colorType}, fScalerContextFlags{flags} + , fStrikeCache{strikeCache} {} + +// TODO: unify with code in GrTextContext.cpp +static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) { + // If we're doing linear blending, then we can disable the gamma hacks. + // Otherwise, leave them on. In either case, we still want the contrast boost: + // TODO: Can we be even smarter about mask gamma based on the dest transfer function? + if (cs && cs->gammaIsLinear()) { + return SkScalerContextFlags::kBoostContrast; + } else { + return SkScalerContextFlags::kFakeGammaAndBoostContrast; + } +} + +SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, + SkColorType colorType, + SkColorSpace* cs, + SkStrikeForGPUCacheInterface* strikeCache) + : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {} + +#if SK_SUPPORT_GPU +SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, const GrColorInfo& csi) + : SkGlyphRunListPainter(props, + kUnknown_SkColorType, + compute_scaler_context_flags(csi.colorSpace()), + SkStrikeCache::GlobalStrikeCache()) {} + +SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc) + : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorInfo()} {} + +#endif + +SkSpan SkGlyphRunListPainter::DeviceSpacePackedGlyphIDs( + const SkGlyphPositionRoundingSpec& roundingSpec, + const SkMatrix& viewMatrix, + const SkPoint& origin, + int n, + const SkGlyphID* glyphIDs, + const SkPoint* positions, + SkPoint* mappedPositions, + SkPackedGlyphID* results) { + // Add rounding and origin. + SkMatrix matrix = viewMatrix; + matrix.preTranslate(origin.x(), origin.y()); + SkPoint rounding = roundingSpec.halfAxisSampleFreq; + matrix.postTranslate(rounding.x(), rounding.y()); + matrix.mapPoints(mappedPositions, positions, n); + + SkIPoint mask = roundingSpec.ignorePositionMask; + + for (int i = 0; i < n; i++) { + SkFixed subX = SkScalarToFixed(mappedPositions[i].x()) & mask.x(), + subY = SkScalarToFixed(mappedPositions[i].y()) & mask.y(); + results[i] = SkPackedGlyphID{glyphIDs[i], subX, subY}; + } + + return SkSpan{results, SkTo(n)}; +} + +SkSpan SkGlyphRunListPainter::SourceSpacePackedGlyphIDs( + const SkPoint& origin, + int n, + const SkGlyphID* glyphIDs, + const SkPoint* positions, + SkPoint* mappedPositions, + SkPackedGlyphID* results) { + + SkMatrix::MakeTrans(origin.x(), origin.y()).mapPoints( + mappedPositions, positions, n); + + SkPackedGlyphID* cursor = results; + for (int i = 0; i < n; i++) { + *cursor++ = SkPackedGlyphID{glyphIDs[i]}; + } + + return SkSpan{results, SkTo(n)}; +} + +void SkGlyphRunListPainter::drawForBitmapDevice( + const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix, + const BitmapDevicePainter* bitmapDevice) { + ScopedBuffers _ = this->ensureBuffers(glyphRunList); + + const SkPaint& runPaint = glyphRunList.paint(); + // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise, + // convert the lcd text into A8 text. The props communicates this to the scaler. + auto& props = (kN32_SkColorType == fColorType && runPaint.isSrcOver()) + ? fDeviceProps + : fBitmapFallbackProps; + + SkPoint origin = glyphRunList.origin(); + for (auto& glyphRun : glyphRunList) { + const SkFont& runFont = glyphRun.font(); + + if (SkStrikeSpec::ShouldDrawAsPath(runPaint, runFont, deviceMatrix)) { + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakePath( + runFont, runPaint, props, fScalerContextFlags); + + auto strike = strikeSpec.findOrCreateExclusiveStrike(); + + fDrawable.startSource(glyphRun.source(), origin); + strike->prepareForDrawingPathsCPU(&fDrawable); + + // The paint we draw paths with must have the same anti-aliasing state as the runFont + // allowing the paths to have the same edging as the glyph masks. + SkPaint pathPaint = runPaint; + pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing()); + + bitmapDevice->paintPaths(&fDrawable, strikeSpec.strikeToSourceRatio(), pathPaint); + } else { + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask( + runFont, runPaint, props, fScalerContextFlags, deviceMatrix); + + auto strike = strikeSpec.findOrCreateExclusiveStrike(); + + fDrawable.startDevice(glyphRun.source(), origin, deviceMatrix, strike->roundingSpec()); + strike->prepareForDrawingMasksCPU(&fDrawable); + bitmapDevice->paintMasks(&fDrawable, runPaint); + } + } +} + +// Getting glyphs to the screen in a fallback situation can be complex. Here is the set of +// transformations that have to happen. Normally, they would all be accommodated by the font +// scaler, but the atlas has an upper limit to the glyphs it can handle. So the GPU is used to +// make up the difference from the smaller atlas size to the larger size needed by the final +// transform. Here are the transformations that are applied. +// +// final transform = [view matrix] * [text scale] * [text size] +// +// There are three cases: +// * Go Fast - view matrix is scale and translate, and all the glyphs are small enough +// Just scale the positions, and have the glyph cache handle the view matrix transformation. +// The text scale is 1. +// * It's complicated - view matrix is not scale and translate, and the glyphs are small enough +// The glyph cache does not handle the view matrix, but stores the glyphs at the text size +// specified by the run paint. The GPU handles the rotation, etc. specified by the view matrix. +// The text scale is 1. +// * Too big - The glyphs are too big to fit in the atlas +// Reduce the text size so the glyphs will fit in the atlas, but don't apply any +// transformations from the view matrix. Calculate a text scale based on that reduction. This +// scale factor is used to increase the size of the destination rectangles. The destination +// rectangles are then scaled, rotated, etc. by the GPU using the view matrix. +void SkGlyphRunListPainter::processARGBFallback(SkScalar maxSourceGlyphDimension, + const SkPaint& runPaint, + const SkFont& runFont, + const SkMatrix& viewMatrix, + SkGlyphRunPainterInterface* process) { + SkASSERT(!fARGBGlyphsIDs.empty()); + + // if maxSourceGlyphDimension then no pixels will change. + if (maxSourceGlyphDimension == 0) { return; } + + SkScalar maxScale = viewMatrix.getMaxScale(); + + // This is a linear estimate of the longest dimension among all the glyph widths and heights. + SkScalar conservativeMaxGlyphDimension = maxSourceGlyphDimension * maxScale; + + // If the situation that the matrix is simple, and all the glyphs are small enough. Go fast! + // N.B. If the matrix has scale, that will be reflected in the strike through the viewMatrix + // in the useFastPath case. + bool useDeviceCache = + viewMatrix.isScaleTranslate() + && conservativeMaxGlyphDimension <= SkStrikeCommon::kSkSideTooBigForAtlas; + + // A scaled and translated transform is the common case, and is handled directly in fallback. + // Even if the transform is scale and translate, fallback must be careful to use glyphs that + // fit in the atlas. If a glyph will not fit in the atlas, then the general transform case is + // used to render the glyphs. + if (useDeviceCache) { + // Translate the positions to device space. + // TODO: this code is dubious + viewMatrix.mapPoints(fARGBPositions.data(), fARGBPositions.size()); + for (SkPoint& point : fARGBPositions) { + point.fX = SkScalarFloorToScalar(point.fX); + point.fY = SkScalarFloorToScalar(point.fY); + } + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask( + runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix); + + SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache); + + SkPackedGlyphID* cursor = fPackedGlyphIDs; + for (auto glyphID : fARGBGlyphsIDs) { + *cursor++ = SkPackedGlyphID{glyphID}; + } + + SkSpan glyphPosSpan = strike->prepareForDrawingRemoveEmpty( + fPackedGlyphIDs, + fARGBPositions.data(), + fARGBGlyphsIDs.size(), + SkStrikeCommon::kSkSideTooBigForAtlas, + fGlyphPos); + + if (process) { + process->processDeviceFallback(glyphPosSpan, strikeSpec); + } + + } else { + // If the matrix is complicated or if scaling is used to fit the glyphs in the cache, + // then this case is used. + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeSourceFallback( + runFont, runPaint, fDeviceProps, fScalerContextFlags, maxSourceGlyphDimension); + + SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache); + + SkPackedGlyphID* cursor = fPackedGlyphIDs; + for (auto glyphID : fARGBGlyphsIDs) { + *cursor++ = SkPackedGlyphID{glyphID}; + } + + auto glyphPosSpan = strike->prepareForDrawingRemoveEmpty( + fPackedGlyphIDs, + fARGBPositions.data(), + fARGBGlyphsIDs.size(), + SkStrikeCommon::kSkSideTooBigForAtlas, + fGlyphPos); + + if (process) { + process->processSourceFallback( + glyphPosSpan, + strikeSpec, + viewMatrix.hasPerspective()); + } + } +} + +#if SK_SUPPORT_GPU +void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunList, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + bool contextSupportsDistanceFieldText, + const GrTextContext::Options& options, + SkGlyphRunPainterInterface* process) { + + SkPoint origin = glyphRunList.origin(); + const SkPaint& runPaint = glyphRunList.paint(); + + for (const auto& glyphRun : glyphRunList) { + SkScalar maxFallbackDimension{-SK_ScalarInfinity}; + ScopedBuffers _ = this->ensureBuffers(glyphRun); + + auto addFallback = [this, &maxFallbackDimension] + (const SkGlyph& glyph, SkPoint sourcePosition) { + maxFallbackDimension = std::max(maxFallbackDimension, + SkIntToScalar(glyph.maxDimension())); + fARGBGlyphsIDs.push_back(glyph.getGlyphID()); + fARGBPositions.push_back(sourcePosition); + }; + + const SkFont& runFont = glyphRun.font(); + + bool useSDFT = GrTextContext::CanDrawAsDistanceFields( + runPaint, runFont, viewMatrix, props, contextSupportsDistanceFieldText, options); + if (process) { + process->startRun(glyphRun, useSDFT); + } + + if (useSDFT) { + SkScalar minScale, maxScale; + SkStrikeSpec strikeSpec; + std::tie(strikeSpec, minScale, maxScale) = + SkStrikeSpec::MakeSDFT( + runFont, runPaint,fDeviceProps, viewMatrix, options); + + SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache); + + auto packedGlyphIDs = SourceSpacePackedGlyphIDs( + origin, + glyphRun.runSize(), + glyphRun.glyphsIDs().data(), + glyphRun.positions().data(), + fPositions, + fPackedGlyphIDs); + + SkSpan glyphPosSpan = strike->prepareForDrawingRemoveEmpty( + packedGlyphIDs.data(), + fPositions, + glyphRun.runSize(), + SkStrikeCommon::kSkSideTooBigForAtlas, + fGlyphPos); + + size_t glyphsWithMaskCount = 0; + for (const SkGlyphPos& glyphPos : glyphPosSpan) { + const SkGlyph& glyph = *glyphPos.glyph; + SkPoint position = glyphPos.position; + + // The SDF scaler context system ensures that a glyph is empty, kSDF_Format, or + // kARGB32_Format. The following if statements use this assumption. + SkASSERT(glyph.maskFormat() == SkMask::kSDF_Format || glyph.isColor()); + + if (SkStrikeForGPU::CanDrawAsSDFT(glyph)) { + // SDF mask will work. + fGlyphPos[glyphsWithMaskCount++] = glyphPos; + } else if (SkStrikeForGPU::CanDrawAsPath(glyph)) { + // If not color but too big, use a path. + fPaths.push_back(glyphPos); + } else { + // If no path, or it is color, then fallback. + addFallback(glyph, position); + } + } + + if (process) { + bool hasWCoord = + viewMatrix.hasPerspective() || options.fDistanceFieldVerticesAlwaysHaveW; + + // processSourceSDFT must be called even if there are no glyphs to make sure runs + // are set correctly. + process->processSourceSDFT( + SkSpan{fGlyphPos, glyphsWithMaskCount}, + strikeSpec, + runFont, + minScale, + maxScale, + hasWCoord); + + if (!fPaths.empty()) { + process->processSourcePaths( + SkMakeSpan(fPaths), + strikeSpec); + } + } + + // fGlyphPos will be reused here. + if (!fARGBGlyphsIDs.empty()) { + this->processARGBFallback(maxFallbackDimension * strikeSpec.strikeToSourceRatio(), + runPaint, runFont, viewMatrix, process); + } + } else if (SkStrikeSpec::ShouldDrawAsPath(runPaint, runFont, viewMatrix)) { + SkStrikeSpec strikeSpec = SkStrikeSpec::MakePath( + runFont, runPaint, fDeviceProps, fScalerContextFlags); + + SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache); + + auto packedGlyphIDs = SourceSpacePackedGlyphIDs( + origin, + glyphRun.runSize(), + glyphRun.glyphsIDs().data(), + glyphRun.positions().data(), + fPositions, + fPackedGlyphIDs); + + SkSpan glyphPosSpan = strike->prepareForDrawingRemoveEmpty( + packedGlyphIDs.data(), + fPositions, + glyphRun.runSize(), + 0, + fGlyphPos); + + // As opposed to SDF and mask, path handling puts paths in fGlyphPos instead of fPaths. + size_t glyphsWithPathCount = 0; + for (const SkGlyphPos& glyphPos : glyphPosSpan) { + const SkGlyph& glyph = *glyphPos.glyph; + SkPoint position = glyphPos.position; + if (SkStrikeForGPU::CanDrawAsPath(glyph)) { + // Place paths in fGlyphPos + fGlyphPos[glyphsWithPathCount++] = glyphPos; + } else { + addFallback(glyph, position); + } + } + + if (process) { + // processSourcePaths must be called even if there are no glyphs to make sure runs + // are set correctly. + process->processSourcePaths( + SkSpan{fGlyphPos, glyphsWithPathCount}, + strikeSpec); + } + + // fGlyphPos will be reused here. + if (!fARGBGlyphsIDs.empty()) { + this->processARGBFallback(maxFallbackDimension * strikeSpec.strikeToSourceRatio(), + runPaint, runFont, viewMatrix, process); + } + } else { + SkStrikeSpec strikeSpec = + SkStrikeSpec::MakeMask(runFont, runPaint, + fDeviceProps, fScalerContextFlags, viewMatrix); + + SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache); + + auto packedGlyphIDs = DeviceSpacePackedGlyphIDs( + strike->roundingSpec(), + viewMatrix, + origin, + glyphRun.runSize(), + glyphRun.glyphsIDs().data(), + glyphRun.positions().data(), + fPositions, + fPackedGlyphIDs); + + // Lookup all the glyphs from the cache. Strip empty glyphs. + SkSpan glyphPosSpan = strike->prepareForDrawingRemoveEmpty( + packedGlyphIDs.data(), + fPositions, + glyphRun.runSize(), + SkStrikeCommon::kSkSideTooBigForAtlas, + fGlyphPos); + + // Sort glyphs into the three bins: mask (fGlyphPos), path (fPaths), and fallback. + size_t glyphsWithMaskCount = 0; + for (const SkGlyphPos& glyphPos : glyphPosSpan) { + const SkGlyph& glyph = *glyphPos.glyph; + const SkPoint position = glyphPos.position; + + // Does the glyph have work to do or is the code able to position the glyph? + if (!SkScalarsAreFinite(position.x(), position.y())) { + // Do nothing; + } else if (SkStrikeForGPU::CanDrawAsMask(glyph)) { + fGlyphPos[glyphsWithMaskCount++] = glyphPos; + } else if (SkStrikeForGPU::CanDrawAsPath(glyph)) { + fPaths.push_back(glyphPos); + } else { + addFallback(glyph, origin + glyphRun.positions()[glyphPos.index]); + } + } + + if (process) { + // processDeviceMasks must be called even if there are no glyphs to make sure runs + // are set correctly. + process->processDeviceMasks( + SkSpan{fGlyphPos, glyphsWithMaskCount}, strikeSpec); + if (!fPaths.empty()) { + process->processDevicePaths(SkMakeSpan(fPaths)); + } + } + + // fGlyphPos will be reused here. + if (!fARGBGlyphsIDs.empty()) { + this->processARGBFallback(maxFallbackDimension / viewMatrix.getMaxScale(), + runPaint, runFont, viewMatrix, process); + } + } // Mask case + } // For all glyph runs +} +#endif // SK_SUPPORT_GPU + +auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers { + size_t size = 0; + for (const SkGlyphRun& run : glyphRunList) { + size = std::max(run.runSize(), size); + } + return ScopedBuffers(this, size); +} + +SkGlyphRunListPainter::ScopedBuffers +SkGlyphRunListPainter::ensureBuffers(const SkGlyphRun& glyphRun) { + return ScopedBuffers(this, glyphRun.runSize()); +} + +#if SK_SUPPORT_GPU +// -- GrTextContext -------------------------------------------------------------------------------- +SkPMColor4f generate_filtered_color(const SkPaint& paint, const GrColorInfo& colorInfo) { + SkColor4f filteredColor = paint.getColor4f(); + if (auto* xform = colorInfo.colorSpaceXformFromSRGB()) { + filteredColor = xform->apply(filteredColor); + } + if (paint.getColorFilter() != nullptr) { + filteredColor = paint.getColorFilter()->filterColor4f(filteredColor, colorInfo.colorSpace(), + colorInfo.colorSpace()); + } + return filteredColor.premul(); +} + +void GrTextContext::drawGlyphRunList( + GrRecordingContext* context, GrTextTarget* target, const GrClip& clip, + const SkMatrix& viewMatrix, const SkSurfaceProps& props, + const SkGlyphRunList& glyphRunList) { + SkPoint origin = glyphRunList.origin(); + + // Get the first paint to use as the key paint. + const SkPaint& listPaint = glyphRunList.paint(); + + SkPMColor4f filteredColor = generate_filtered_color(listPaint, target->colorInfo()); + GrColor color = generate_filtered_color(listPaint, target->colorInfo()).toBytes_RGBA(); + + // If we have been abandoned, then don't draw + if (context->priv().abandoned()) { + return; + } + + SkMaskFilterBase::BlurRec blurRec; + // It might be worth caching these things, but its not clear at this time + // TODO for animated mask filters, this will fill up our cache. We need a safeguard here + const SkMaskFilter* mf = listPaint.getMaskFilter(); + bool canCache = glyphRunList.canCache() && !(listPaint.getPathEffect() || + (mf && !as_MFB(mf)->asABlur(&blurRec))); + SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorInfo()); + + auto grStrikeCache = context->priv().getGrStrikeCache(); + GrTextBlobCache* textBlobCache = context->priv().getTextBlobCache(); + + sk_sp cacheBlob; + GrTextBlob::Key key; + if (canCache) { + bool hasLCD = glyphRunList.anyRunsLCD(); + + // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry + SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() : + kUnknown_SkPixelGeometry; + + // TODO we want to figure out a way to be able to use the canonical color on LCD text, + // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to + // ensure we always match the same key + GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT : + ComputeCanonicalColor(listPaint, hasLCD); + + key.fPixelGeometry = pixelGeometry; + key.fUniqueID = glyphRunList.uniqueID(); + key.fStyle = listPaint.getStyle(); + key.fHasBlur = SkToBool(mf); + key.fCanonicalColor = canonicalColor; + key.fScalerContextFlags = scalerContextFlags; + cacheBlob = textBlobCache->find(key); + } + + if (cacheBlob) { + if (cacheBlob->mustRegenerate(listPaint, glyphRunList.anyRunsSubpixelPositioned(), + blurRec, viewMatrix, origin.x(),origin.y())) { + // We have to remake the blob because changes may invalidate our masks. + // TODO we could probably get away reuse most of the time if the pointer is unique, + // but we'd have to clear the subrun information + textBlobCache->remove(cacheBlob.get()); + cacheBlob = textBlobCache->makeCachedBlob( + glyphRunList, key, blurRec, listPaint, color, grStrikeCache); + cacheBlob->generateFromGlyphRunList( + *context->priv().caps()->shaderCaps(), fOptions, + listPaint, scalerContextFlags, viewMatrix, props, + glyphRunList, target->glyphPainter()); + } else { + textBlobCache->makeMRU(cacheBlob.get()); + + if (CACHE_SANITY_CHECK) { + sk_sp sanityBlob(textBlobCache->makeBlob( + glyphRunList, color, grStrikeCache)); + sanityBlob->setupKey(key, blurRec, listPaint); + cacheBlob->generateFromGlyphRunList( + *context->priv().caps()->shaderCaps(), fOptions, + listPaint, scalerContextFlags, viewMatrix, props, glyphRunList, + target->glyphPainter()); + GrTextBlob::AssertEqual(*sanityBlob, *cacheBlob); + } + } + } else { + if (canCache) { + cacheBlob = textBlobCache->makeCachedBlob( + glyphRunList, key, blurRec, listPaint, color, grStrikeCache); + } else { + cacheBlob = textBlobCache->makeBlob(glyphRunList, color, grStrikeCache); + } + cacheBlob->generateFromGlyphRunList( + *context->priv().caps()->shaderCaps(), fOptions, listPaint, + scalerContextFlags, viewMatrix, props, glyphRunList, + target->glyphPainter()); + } + + cacheBlob->flush(target, props, fDistanceAdjustTable.get(), listPaint, filteredColor, + clip, viewMatrix, origin.x(), origin.y()); +} + +void GrTextBlob::SubRun::appendGlyph(GrGlyph* glyph, SkRect dstRect) { + + this->joinGlyphBounds(dstRect); + + GrTextBlob* blob = fRun->fBlob; + + bool hasW = this->hasWCoord(); + // glyphs drawn in perspective must always have a w coord. + SkASSERT(hasW || !blob->fInitialViewMatrix.hasPerspective()); + auto maskFormat = this->maskFormat(); + size_t vertexStride = GetVertexStride(maskFormat, hasW); + + intptr_t vertex = reinterpret_cast(blob->fVertices + fVertexEndIndex); + + // We always write the third position component used by SDFs. If it is unused it gets + // overwritten. Similarly, we always write the color and the blob will later overwrite it + // with texture coords if it is unused. + size_t colorOffset = hasW ? sizeof(SkPoint3) : sizeof(SkPoint); + // V0 + *reinterpret_cast(vertex) = {dstRect.fLeft, dstRect.fTop, 1.f}; + *reinterpret_cast(vertex + colorOffset) = fColor; + vertex += vertexStride; + + // V1 + *reinterpret_cast(vertex) = {dstRect.fLeft, dstRect.fBottom, 1.f}; + *reinterpret_cast(vertex + colorOffset) = fColor; + vertex += vertexStride; + + // V2 + *reinterpret_cast(vertex) = {dstRect.fRight, dstRect.fTop, 1.f}; + *reinterpret_cast(vertex + colorOffset) = fColor; + vertex += vertexStride; + + // V3 + *reinterpret_cast(vertex) = {dstRect.fRight, dstRect.fBottom, 1.f}; + *reinterpret_cast(vertex + colorOffset) = fColor; + + fVertexEndIndex += vertexStride * kVerticesPerGlyph; + blob->fGlyphs[fGlyphEndIndex++] = glyph; +} + +void GrTextBlob::Run::switchSubRunIfNeededAndAppendGlyph(GrGlyph* glyph, + const sk_sp& strike, + const SkRect& destRect, + bool needsTransform) { + GrMaskFormat format = glyph->fMaskFormat; + + SubRun* subRun = &fSubRunInfo.back(); + if (fInitialized && subRun->maskFormat() != format) { + subRun = pushBackSubRun(fStrikeSpec, fColor); + subRun->setStrike(strike); + } else if (!fInitialized) { + subRun->setStrike(strike); + } + + fInitialized = true; + subRun->setMaskFormat(format); + subRun->setNeedsTransform(needsTransform); + subRun->appendGlyph(glyph, destRect); +} + +void GrTextBlob::Run::appendDeviceSpaceGlyph(const sk_sp& strike, + const SkGlyph& skGlyph, SkPoint origin) { + if (GrGlyph* glyph = strike->getGlyph(skGlyph)) { + + SkRect glyphRect = glyph->destRect(origin); + + if (!glyphRect.isEmpty()) { + this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, false); + } + } +} + +void GrTextBlob::Run::appendSourceSpaceGlyph(const sk_sp& strike, + const SkGlyph& skGlyph, + SkPoint origin, + SkScalar textScale) { + if (GrGlyph* glyph = strike->getGlyph(skGlyph)) { + + SkRect glyphRect = glyph->destRect(origin, textScale); + + if (!glyphRect.isEmpty()) { + this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, true); + } + } +} + +void GrTextBlob::generateFromGlyphRunList(const GrShaderCaps& shaderCaps, + const GrTextContext::Options& options, + const SkPaint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + const SkGlyphRunList& glyphRunList, + SkGlyphRunListPainter* glyphPainter) { + SkPoint origin = glyphRunList.origin(); + const SkPaint& runPaint = glyphRunList.paint(); + this->initReusableBlob(SkPaintPriv::ComputeLuminanceColor(runPaint), viewMatrix, + origin.x(), origin.y()); + + glyphPainter->processGlyphRunList(glyphRunList, + viewMatrix, + props, + shaderCaps.supportsDistanceFieldText(), + options, + this); +} + +GrTextBlob::Run* GrTextBlob::currentRun() { + return &fRuns[fRunCount - 1]; +} + +void GrTextBlob::startRun(const SkGlyphRun& glyphRun, bool useSDFT) { + if (useSDFT) { + this->setHasDistanceField(); + } + Run* run = this->pushBackRun(); + run->setRunFontAntiAlias(glyphRun.font().hasSomeAntiAliasing()); +} + +void GrTextBlob::processDeviceMasks(SkSpan masks, + const SkStrikeSpec& strikeSpec) { + Run* run = this->currentRun(); + this->setHasBitmap(); + run->setupFont(strikeSpec); + sk_sp currStrike = strikeSpec.findOrCreateGrStrike(fStrikeCache); + for (const auto& mask : masks) { + SkPoint pt{SkScalarFloorToScalar(mask.position.fX), + SkScalarFloorToScalar(mask.position.fY)}; + run->appendDeviceSpaceGlyph(currStrike, *mask.glyph, pt); + } +} + +void GrTextBlob::processSourcePaths(SkSpan paths, + const SkStrikeSpec& strikeSpec) { + Run* run = this->currentRun(); + this->setHasBitmap(); + run->setupFont(strikeSpec); + for (const auto& path : paths) { + if (const SkPath* glyphPath = path.glyph->path()) { + run->appendPathGlyph(*glyphPath, path.position, strikeSpec.strikeToSourceRatio(), + false); + } + } +} + +void GrTextBlob::processDevicePaths(SkSpan paths) { + Run* run = this->currentRun(); + this->setHasBitmap(); + for (const auto& path : paths) { + SkPoint pt{SkScalarFloorToScalar(path.position.fX), + SkScalarFloorToScalar(path.position.fY)}; + // TODO: path should always be set. Remove when proven. + if (const SkPath* glyphPath = path.glyph->path()) { + run->appendPathGlyph(*glyphPath, pt, SK_Scalar1, true); + } + } +} + +void GrTextBlob::processSourceSDFT(SkSpan masks, + const SkStrikeSpec& strikeSpec, + const SkFont& runFont, + SkScalar minScale, + SkScalar maxScale, + bool hasWCoord) { + + Run* run = this->currentRun(); + run->setSubRunHasDistanceFields( + runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias, + runFont.hasSomeAntiAliasing(), + hasWCoord); + this->setMinAndMaxScale(minScale, maxScale); + run->setupFont(strikeSpec); + sk_sp currStrike = strikeSpec.findOrCreateGrStrike(fStrikeCache); + for (const auto& mask : masks) { + run->appendSourceSpaceGlyph( + currStrike, *mask.glyph, mask.position, strikeSpec.strikeToSourceRatio()); + } +} + +void GrTextBlob::processSourceFallback(SkSpan masks, + const SkStrikeSpec& strikeSpec, + bool hasW) { + Run* run = this->currentRun(); + + auto subRun = run->initARGBFallback(); + sk_sp grStrike = strikeSpec.findOrCreateGrStrike(fStrikeCache); + subRun->setStrike(grStrike); + subRun->setHasWCoord(hasW); + + this->setHasBitmap(); + run->setupFont(strikeSpec); + for (const auto& mask : masks) { + run->appendSourceSpaceGlyph + (grStrike, *mask.glyph, mask.position, strikeSpec.strikeToSourceRatio()); + } +} + +void GrTextBlob::processDeviceFallback(SkSpan masks, + const SkStrikeSpec& strikeSpec) { + Run* run = this->currentRun(); + this->setHasBitmap(); + sk_sp grStrike = strikeSpec.findOrCreateGrStrike(fStrikeCache); + auto subRun = run->initARGBFallback(); + run->setupFont(strikeSpec); + subRun->setStrike(grStrike); + for (const auto& mask : masks) { + run->appendDeviceSpaceGlyph(grStrike, *mask.glyph, mask.position); + } +} + +#if GR_TEST_UTILS + +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" + +std::unique_ptr GrTextContext::createOp_TestingOnly(GrRecordingContext* context, + GrTextContext* textContext, + GrRenderTargetContext* rtc, + const SkPaint& skPaint, + const SkFont& font, + const SkMatrix& viewMatrix, + const char* text, + int x, + int y) { + auto direct = context->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + auto strikeCache = direct->priv().getGrStrikeCache(); + + static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType); + + size_t textLen = (int)strlen(text); + + SkPMColor4f filteredColor = generate_filtered_color(skPaint, rtc->colorInfo()); + GrColor color = filteredColor.toBytes_RGBA(); + + auto origin = SkPoint::Make(x, y); + SkGlyphRunBuilder builder; + builder.drawTextUTF8(skPaint, font, text, textLen, origin); + + auto glyphRunList = builder.useGlyphRunList(); + sk_sp blob; + if (!glyphRunList.empty()) { + blob = direct->priv().getTextBlobCache()->makeBlob(glyphRunList, color, strikeCache); + // Use the text and textLen below, because we don't want to mess with the paint. + SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(rtc->colorInfo()); + blob->generateFromGlyphRunList( + *context->priv().caps()->shaderCaps(), textContext->fOptions, + skPaint, scalerContextFlags, viewMatrix, surfaceProps, + glyphRunList, rtc->textTarget()->glyphPainter()); + } + + return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, skPaint, filteredColor, surfaceProps, + textContext->dfAdjustTable(), rtc->textTarget()); +} + +#endif // GR_TEST_UTILS +#endif // SK_SUPPORT_GPU + +SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, size_t size) + : fPainter{painter} { + fPainter->fDrawable.ensureSize(size); + if (fPainter->fMaxRunSize < size) { + fPainter->fMaxRunSize = size; + + fPainter->fPositions.reset(size); + fPainter->fPackedGlyphIDs.reset(size); + fPainter->fGlyphPos.reset(size); + } +} + +SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() { + fPainter->fDrawable.reset(); + fPainter->fPaths.clear(); + fPainter->fARGBGlyphsIDs.clear(); + fPainter->fARGBPositions.clear(); + + if (fPainter->fMaxRunSize > 200) { + fPainter->fMaxRunSize = 0; + fPainter->fPositions.reset(); + fPainter->fPackedGlyphIDs.reset(); + fPainter->fGlyphPos.reset(); + fPainter->fPaths.shrink_to_fit(); + fPainter->fARGBGlyphsIDs.shrink_to_fit(); + fPainter->fARGBPositions.shrink_to_fit(); + } +} + +SkVector SkGlyphPositionRoundingSpec::HalfAxisSampleFreq(bool isSubpixel, SkAxisAlignment axisAlignment) { + if (!isSubpixel) { + return {SK_ScalarHalf, SK_ScalarHalf}; + } else { + static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound); + switch (axisAlignment) { + case kX_SkAxisAlignment: + return {kSubpixelRounding, SK_ScalarHalf}; + case kY_SkAxisAlignment: + return {SK_ScalarHalf, kSubpixelRounding}; + case kNone_SkAxisAlignment: + return {kSubpixelRounding, kSubpixelRounding}; + } + } + + // Some compilers need this. + return {0, 0}; +} + +SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionMask( + bool isSubpixel, SkAxisAlignment axisAlignment) { + return SkIPoint::Make((!isSubpixel || axisAlignment == kY_SkAxisAlignment) ? 0 : ~0, + (!isSubpixel || axisAlignment == kX_SkAxisAlignment) ? 0 : ~0); +} + +SkGlyphPositionRoundingSpec::SkGlyphPositionRoundingSpec(bool isSubpixel, + SkAxisAlignment axisAlignment) + : halfAxisSampleFreq{HalfAxisSampleFreq(isSubpixel, axisAlignment)} + , ignorePositionMask{IgnorePositionMask(isSubpixel, axisAlignment)} { +} diff --git a/gfx/skia/skia/src/core/SkGlyphRunPainter.h b/gfx/skia/skia/src/core/SkGlyphRunPainter.h new file mode 100644 index 0000000000..a5aff15cd5 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGlyphRunPainter.h @@ -0,0 +1,202 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGlyphRunPainter_DEFINED +#define SkGlyphRunPainter_DEFINED + +#include "include/core/SkSurfaceProps.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/core/SkGlyphBuffer.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkTextBlobPriv.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/text/GrTextContext.h" +class GrColorInfo; +class GrRenderTargetContext; +#endif + +class SkGlyphRunPainterInterface; +class SkStrikeSpec; + +// round and ignorePositionMask are used to calculate the subpixel position of a glyph. +// The per component (x or y) calculation is: +// +// subpixelOffset = (floor((viewportPosition + rounding) & mask) >> 14) & 3 +// +// where mask is either 0 or ~0, and rounding is either +// 1/2 for non-subpixel or 1/8 for subpixel. +struct SkGlyphPositionRoundingSpec { + SkGlyphPositionRoundingSpec(bool isSubpixel, SkAxisAlignment axisAlignment); + const SkVector halfAxisSampleFreq; + const SkIPoint ignorePositionMask; + +private: + static SkVector HalfAxisSampleFreq(bool isSubpixel, SkAxisAlignment axisAlignment); + static SkIPoint IgnorePositionMask(bool isSubpixel, SkAxisAlignment axisAlignment); +}; + +class SkStrikeCommon { +public: + // An atlas consists of plots, and plots hold glyphs. The minimum a plot can be is 256x256. + // This means that the maximum size a glyph can be is 256x256. + static constexpr uint16_t kSkSideTooBigForAtlas = 256; +}; + +class SkGlyphRunListPainter { +public: + // Constructor for SkBitmpapDevice. + SkGlyphRunListPainter(const SkSurfaceProps& props, + SkColorType colorType, + SkColorSpace* cs, + SkStrikeForGPUCacheInterface* strikeCache); + +#if SK_SUPPORT_GPU + // The following two ctors are used exclusively by the GPU, and will always use the global + // strike cache. + SkGlyphRunListPainter(const SkSurfaceProps&, const GrColorInfo&); + explicit SkGlyphRunListPainter(const GrRenderTargetContext& renderTargetContext); +#endif // SK_SUPPORT_GPU + + class BitmapDevicePainter { + public: + virtual ~BitmapDevicePainter() = default; + + virtual void paintPaths( + SkDrawableGlyphBuffer* drawables, SkScalar scale, const SkPaint& paint) const = 0; + + virtual void paintMasks(SkDrawableGlyphBuffer* drawables, const SkPaint& paint) const = 0; + }; + + void drawForBitmapDevice( + const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix, + const BitmapDevicePainter* bitmapDevice); + +#if SK_SUPPORT_GPU + // A nullptr for process means that the calls to the cache will be performed, but none of the + // callbacks will be called. + void processGlyphRunList(const SkGlyphRunList& glyphRunList, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + bool contextSupportsDistanceFieldText, + const GrTextContext::Options& options, + SkGlyphRunPainterInterface* process); +#endif // SK_SUPPORT_GPU + +private: + SkGlyphRunListPainter(const SkSurfaceProps& props, SkColorType colorType, + SkScalerContextFlags flags, SkStrikeForGPUCacheInterface* strikeCache); + + struct ScopedBuffers { + ScopedBuffers(SkGlyphRunListPainter* painter, size_t size); + ~ScopedBuffers(); + SkGlyphRunListPainter* fPainter; + }; + + ScopedBuffers SK_WARN_UNUSED_RESULT ensureBuffers(const SkGlyphRunList& glyphRunList); + + // TODO: Remove once I can hoist ensureBuffers above the list for loop in all cases. + ScopedBuffers SK_WARN_UNUSED_RESULT ensureBuffers(const SkGlyphRun& glyphRun); + + /** + * @param fARGBPositions in source space + * @param fARGBGlyphsIDs the glyphs to process + * @param fGlyphPos used as scratch space + * @param maxSourceGlyphDimension the longest dimension of any glyph as if all fARGBGlyphsIDs + * were drawn in source space (as if viewMatrix were identity) + */ + void processARGBFallback(SkScalar maxSourceGlyphDimension, + const SkPaint& runPaint, + const SkFont& runFont, + const SkMatrix& viewMatrix, + SkGlyphRunPainterInterface* process); + + static SkSpan DeviceSpacePackedGlyphIDs( + const SkGlyphPositionRoundingSpec& roundingSpec, + const SkMatrix& viewMatrix, + const SkPoint& origin, + int n, + const SkGlyphID* glyphIDs, + const SkPoint* positions, + SkPoint* mappedPositions, + SkPackedGlyphID* results); + + static SkSpan SourceSpacePackedGlyphIDs( + const SkPoint& origin, + int n, + const SkGlyphID* glyphIDs, + const SkPoint* positions, + SkPoint* mappedPositions, + SkPackedGlyphID* results); + + // The props as on the actual device. + const SkSurfaceProps fDeviceProps; + // The props for when the bitmap device can't draw LCD text. + const SkSurfaceProps fBitmapFallbackProps; + const SkColorType fColorType; + const SkScalerContextFlags fScalerContextFlags; + + SkStrikeForGPUCacheInterface* const fStrikeCache; + + SkDrawableGlyphBuffer fDrawable; + + size_t fMaxRunSize{0}; + SkAutoTMalloc fPositions; + SkAutoTMalloc fPackedGlyphIDs; + SkAutoTMalloc fGlyphPos; + + std::vector fPaths; + + // Vectors for tracking ARGB fallback information. + std::vector fARGBGlyphsIDs; + std::vector fARGBPositions; +}; + +// SkGlyphRunPainterInterface are all the ways that Ganesh generates glyphs. The first +// distinction is between Device and Source. +// * Device - the data in the cache is scaled to the device. There is no transformation from the +// cache to the screen. +// * Source - the data in the cache needs to be scaled from the cache to source space using the +// factor cacheToSourceScale. When drawn the system must combine cacheToSourceScale and the +// deviceView matrix to transform the cache data onto the screen. This allows zooming and +// simple animation to reuse the same glyph data by just changing the transform. +// +// In addition to transformation type above, Masks, Paths, SDFT, and Fallback (or really the +// rendering method of last resort) are the different +// formats of data used from the cache. +class SkGlyphRunPainterInterface { +public: + virtual ~SkGlyphRunPainterInterface() = default; + + virtual void startRun(const SkGlyphRun& glyphRun, bool useSDFT) = 0; + + virtual void processDeviceMasks(SkSpan masks, + const SkStrikeSpec& strikeSpec) = 0; + + virtual void processSourcePaths(SkSpan paths, + const SkStrikeSpec& strikeSpec) = 0; + + virtual void processDevicePaths(SkSpan paths) = 0; + + virtual void processSourceSDFT(SkSpan masks, + const SkStrikeSpec& strikeSpec, + const SkFont& runFont, + SkScalar minScale, + SkScalar maxScale, + bool hasWCoord) = 0; + + virtual void processSourceFallback(SkSpan masks, + const SkStrikeSpec& strikeSpec, + bool hasW) = 0; + + virtual void processDeviceFallback(SkSpan masks, + const SkStrikeSpec& strikeSpec) = 0; + +}; + +#endif // SkGlyphRunPainter_DEFINED diff --git a/gfx/skia/skia/src/core/SkGpuBlurUtils.cpp b/gfx/skia/skia/src/core/SkGpuBlurUtils.cpp new file mode 100644 index 0000000000..c863908aa3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGpuBlurUtils.cpp @@ -0,0 +1,556 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkGpuBlurUtils.h" + +#include "include/core/SkRect.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h" +#include "src/gpu/effects/GrMatrixConvolutionEffect.h" + +#include "src/gpu/SkGr.h" + +#define MAX_BLUR_SIGMA 4.0f + +using Direction = GrGaussianConvolutionFragmentProcessor::Direction; + +static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) { + rect->fLeft = SkScalarFloorToInt(rect->fLeft * xScale); + rect->fTop = SkScalarFloorToInt(rect->fTop * yScale); + rect->fRight = SkScalarCeilToInt(rect->fRight * xScale); + rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale); +} + +static void scale_irect(SkIRect* rect, int xScale, int yScale) { + rect->fLeft *= xScale; + rect->fTop *= yScale; + rect->fRight *= xScale; + rect->fBottom *= yScale; +} + +#ifdef SK_DEBUG +static inline int is_even(int x) { return !(x & 1); } +#endif + +static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) { + if (xAxis) { + SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight)); + rect->fLeft /= 2; + rect->fRight /= 2; + } + if (yAxis) { + SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom)); + rect->fTop /= 2; + rect->fBottom /= 2; + } +} + +static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { + *scaleFactor = 1; + while (sigma > MAX_BLUR_SIGMA) { + *scaleFactor *= 2; + sigma *= 0.5f; + if (*scaleFactor > maxTextureSize) { + *scaleFactor = maxTextureSize; + sigma = MAX_BLUR_SIGMA; + } + } + *radius = static_cast(ceilf(sigma * 3.0f)); + SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius); + return sigma; +} + +static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + const SkIRect& dstRect, + const SkIPoint& srcOffset, + sk_sp proxy, + GrColorType srcColorType, + Direction direction, + int radius, + float sigma, + GrTextureDomain::Mode mode, + int bounds[2]) { + GrPaint paint; + std::unique_ptr conv(GrGaussianConvolutionFragmentProcessor::Make( + std::move(proxy), srcColorType, direction, radius, sigma, mode, bounds)); + paint.addColorFragmentProcessor(std::move(conv)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), + -SkIntToScalar(srcOffset.y())); + renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::Make(dstRect), localMatrix); +} + +static std::unique_ptr convolve_gaussian_2d(GrRecordingContext* context, + sk_sp srcProxy, + GrColorType srcColorType, + const SkIRect& srcBounds, + const SkIPoint& srcOffset, + int radiusX, + int radiusY, + SkScalar sigmaX, + SkScalar sigmaY, + GrTextureDomain::Mode mode, + int finalW, + int finalH, + sk_sp finalCS, + SkBackingFit dstFit) { + + auto renderTargetContext = context->priv().makeDeferredRenderTargetContext( + dstFit, + finalW, + finalH, + srcColorType, + std::move(finalCS), + 1, + GrMipMapped::kNo, + srcProxy->origin(), + nullptr, + SkBudgeted::kYes, + srcProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo); + if (!renderTargetContext) { + return nullptr; + } + + SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), + -SkIntToScalar(srcOffset.y())); + SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); + SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); + GrPaint paint; + auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(srcProxy), srcBounds, size, + 1.0, 0.0, kernelOffset, mode, true, + sigmaX, sigmaY); + paint.addColorFragmentProcessor(std::move(conv)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + GrFixedClip clip(SkIRect::MakeWH(finalW, finalH)); + + renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::MakeWH(finalW, finalH), localMatrix); + + return renderTargetContext; +} + +// NOTE: Both convolve_gaussian or decimate accept a proxyOffset. This is separate from the +// srcBounds and srcOffset, which are relative to the content rect of the image, whereas proxyOffset +// maps from the content rect to the proxy's coordinate space. Due to how the destination bounds are +// calculated, it is more convenient to have the proxy offset kept separate from the logical bounds +// (which do impact destination decisions). Both functions incorporate the proxy offset into the +// geometry they submit or before calling convolve_gaussian_1d. + +static std::unique_ptr convolve_gaussian(GrRecordingContext* context, + sk_sp srcProxy, + GrColorType srcColorType, + const SkIPoint& proxyOffset, + const SkIRect& srcRect, + const SkIPoint& srcOffset, + Direction direction, + int radius, + float sigma, + SkIRect* contentRect, + GrTextureDomain::Mode mode, + int finalW, + int finalH, + sk_sp finalCS, + SkBackingFit fit) { + SkASSERT(srcRect.width() <= finalW && srcRect.height() <= finalH); + + auto dstRenderTargetContext = context->priv().makeDeferredRenderTargetContext( + fit, + srcRect.width(), + srcRect.height(), + srcColorType, + std::move(finalCS), + 1, + GrMipMapped::kNo, + srcProxy->origin(), + nullptr, + SkBudgeted::kYes, + srcProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo); + if (!dstRenderTargetContext) { + return nullptr; + } + + GrFixedClip clip(SkIRect::MakeWH(finalW, finalH)); + + int bounds[2] = { 0, 0 }; + SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height()); + SkIPoint netOffset = srcOffset - proxyOffset; + if (GrTextureDomain::kIgnore_Mode == mode) { + *contentRect = dstRect; + convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, netOffset, + std::move(srcProxy), srcColorType, direction, radius, sigma, + GrTextureDomain::kIgnore_Mode, bounds); + return dstRenderTargetContext; + } + // These destination rects need to be adjusted by srcOffset, but should *not* be adjusted by + // the proxyOffset, which is why keeping them separate is convenient. + SkIRect midRect = *contentRect, leftRect, rightRect; + midRect.offset(srcOffset); + SkIRect topRect, bottomRect; + if (Direction::kX == direction) { + bounds[0] = contentRect->left() + proxyOffset.x(); + bounds[1] = contentRect->right() + proxyOffset.x(); + topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top()); + bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom()); + midRect.inset(radius, 0); + leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom()); + rightRect = + SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom()); + dstRect.fTop = midRect.top(); + dstRect.fBottom = midRect.bottom(); + + contentRect->fLeft = dstRect.fLeft; + contentRect->fTop = midRect.fTop; + contentRect->fRight = dstRect.fRight; + contentRect->fBottom = midRect.fBottom; + } else { + bounds[0] = contentRect->top() + proxyOffset.y(); + bounds[1] = contentRect->bottom() + proxyOffset.y(); + topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom()); + bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom()); + midRect.inset(0, radius); + leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top()); + rightRect = + SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height()); + dstRect.fLeft = midRect.left(); + dstRect.fRight = midRect.right(); + + contentRect->fLeft = midRect.fLeft; + contentRect->fTop = dstRect.fTop; + contentRect->fRight = midRect.fRight; + contentRect->fBottom = dstRect.fBottom; + } + if (!topRect.isEmpty()) { + dstRenderTargetContext->clear(&topRect, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); + } + + if (!bottomRect.isEmpty()) { + dstRenderTargetContext->clear(&bottomRect, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); + } + + if (midRect.isEmpty()) { + // Blur radius covers srcBounds; use bounds over entire draw + convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, netOffset, + std::move(srcProxy), srcColorType, direction, radius, sigma, mode, + bounds); + } else { + // Draw right and left margins with bounds; middle without. + convolve_gaussian_1d(dstRenderTargetContext.get(), clip, leftRect, netOffset, + srcProxy, srcColorType, direction, radius, sigma, mode, bounds); + convolve_gaussian_1d(dstRenderTargetContext.get(), clip, rightRect, netOffset, + srcProxy, srcColorType, direction, radius, sigma, mode, bounds); + convolve_gaussian_1d(dstRenderTargetContext.get(), clip, midRect, netOffset, + std::move(srcProxy), srcColorType, direction, radius, sigma, + GrTextureDomain::kIgnore_Mode, bounds); + } + + return dstRenderTargetContext; +} + +// Returns a high quality scaled-down version of src. This is used to create an intermediate, +// shrunken version of the source image in the event that the requested blur sigma exceeds +// MAX_BLUR_SIGMA. +static sk_sp decimate(GrRecordingContext* context, + sk_sp srcProxy, + GrColorType srcColorType, + const SkIPoint& proxyOffset, + SkIPoint* srcOffset, + SkIRect* contentRect, + int scaleFactorX, int scaleFactorY, + int radiusX, int radiusY, + GrTextureDomain::Mode mode, + int finalW, + int finalH, + sk_sp finalCS) { + SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); + SkASSERT(scaleFactorX > 1 || scaleFactorY > 1); + + SkIRect srcRect; + if (GrTextureDomain::kIgnore_Mode == mode) { + srcRect = SkIRect::MakeWH(finalW, finalH); + } else { + srcRect = *contentRect; + srcRect.offset(*srcOffset); + } + + scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); + scale_irect(&srcRect, scaleFactorX, scaleFactorY); + + SkIRect dstRect(srcRect); + + // Map the src rect into proxy space, this only has to happen once since subsequent loops + // to decimate will have created a new proxy that has its origin at (0, 0). + srcRect.offset(proxyOffset.x(), proxyOffset.y()); + std::unique_ptr dstRenderTargetContext; + + for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { + shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY); + + // We know this will not be the final draw so we are free to make it an approx match. + dstRenderTargetContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, + dstRect.fRight, + dstRect.fBottom, + srcColorType, + finalCS, + 1, + GrMipMapped::kNo, + srcProxy->origin(), + nullptr, + SkBudgeted::kYes, + srcProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo); + if (!dstRenderTargetContext) { + return nullptr; + } + + GrPaint paint; + if (GrTextureDomain::kIgnore_Mode != mode && i == 1) { + // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter. + GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode + ? GrTextureDomain::kDecal_Mode + : mode; + + SkRect domain = SkRect::Make(*contentRect); + domain.inset((i < scaleFactorX) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f, + (i < scaleFactorY) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f); + // Ensure that the insetting doesn't invert the domain rectangle. + if (domain.fRight < domain.fLeft) { + domain.fLeft = domain.fRight = SkScalarAve(domain.fLeft, domain.fRight); + } + if (domain.fBottom < domain.fTop) { + domain.fTop = domain.fBottom = SkScalarAve(domain.fTop, domain.fBottom); + } + domain.offset(proxyOffset.x(), proxyOffset.y()); + auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), + srcColorType, + SkMatrix::I(), + domain, + modeForScaling, + GrSamplerState::Filter::kBilerp); + paint.addColorFragmentProcessor(std::move(fp)); + srcRect.offset(-(*srcOffset)); + // TODO: consume the srcOffset in both first draws and always set it to zero + // back in GaussianBlur + srcOffset->set(0, 0); + } else { + paint.addColorTextureProcessor(std::move(srcProxy), srcColorType, SkMatrix::I(), + GrSamplerState::ClampBilerp()); + } + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + dstRenderTargetContext->fillRectToRect(GrFixedClip::Disabled(), std::move(paint), GrAA::kNo, + SkMatrix::I(), SkRect::Make(dstRect), + SkRect::Make(srcRect)); + + srcProxy = dstRenderTargetContext->asTextureProxyRef(); + if (!srcProxy) { + return nullptr; + } + srcRect = dstRect; + } + + *contentRect = dstRect; + + SkASSERT(dstRenderTargetContext); + + return dstRenderTargetContext->asTextureProxyRef(); +} + +// Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are +// expanding an intermediate image, so there's no need to account for a proxy offset from the +// original input. +static std::unique_ptr reexpand( + GrRecordingContext* context, + std::unique_ptr srcRenderTargetContext, + const SkIRect& localSrcBounds, + int scaleFactorX, int scaleFactorY, + int finalW, + int finalH, + sk_sp finalCS, + SkBackingFit fit) { + const SkIRect srcRect = SkIRect::MakeWH(srcRenderTargetContext->width(), + srcRenderTargetContext->height()); + + sk_sp srcProxy = srcRenderTargetContext->asTextureProxyRef(); + if (!srcProxy) { + return nullptr; + } + + GrColorType srcColorType = srcRenderTargetContext->colorInfo().colorType(); + + srcRenderTargetContext = nullptr; // no longer needed + + auto dstRenderTargetContext = context->priv().makeDeferredRenderTargetContext( + fit, finalW, finalH, srcColorType, std::move(finalCS), 1, GrMipMapped::kNo, + srcProxy->origin()); + if (!dstRenderTargetContext) { + return nullptr; + } + + GrPaint paint; + SkRect domain = GrTextureDomain::MakeTexelDomain(localSrcBounds, GrTextureDomain::kClamp_Mode, + GrTextureDomain::kClamp_Mode); + auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), srcColorType, SkMatrix::I(), domain, + GrTextureDomain::kClamp_Mode, + GrSamplerState::Filter::kBilerp); + paint.addColorFragmentProcessor(std::move(fp)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + GrFixedClip clip(SkIRect::MakeWH(finalW, finalH)); + + // TODO: using dstII as dstRect results in some image diffs - why? + SkIRect dstRect(srcRect); + scale_irect(&dstRect, scaleFactorX, scaleFactorY); + + dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::Make(dstRect), SkRect::Make(srcRect)); + + return dstRenderTargetContext; +} + +namespace SkGpuBlurUtils { + +std::unique_ptr GaussianBlur(GrRecordingContext* context, + sk_sp srcProxy, + GrColorType srcColorType, + SkAlphaType srcAT, + const SkIPoint& proxyOffset, + sk_sp colorSpace, + const SkIRect& dstBounds, + const SkIRect& srcBounds, + float sigmaX, + float sigmaY, + GrTextureDomain::Mode mode, + SkBackingFit fit) { + SkASSERT(context); + + TRACE_EVENT2("skia.gpu", "GaussianBlur", "sigmaX", sigmaX, "sigmaY", sigmaY); + + int finalW = dstBounds.width(); + int finalH = dstBounds.height(); + + int scaleFactorX, radiusX; + int scaleFactorY, radiusY; + int maxTextureSize = context->priv().caps()->maxTextureSize(); + sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); + sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); + SkASSERT(sigmaX || sigmaY); + + SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y()); + SkIRect localSrcBounds = srcBounds; + SkIPoint localProxyOffset = proxyOffset; + + // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just + // launch a single non separable kernel vs two launches + if (sigmaX > 0.0f && sigmaY > 0.0f && + (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { + // We shouldn't be scaling because this is a small size blur + SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); + // Apply the proxy offset to src bounds and offset directly + srcOffset -= proxyOffset; + localSrcBounds.offset(proxyOffset); + return convolve_gaussian_2d(context, std::move(srcProxy), srcColorType, localSrcBounds, + srcOffset, radiusX, radiusY, sigmaX, sigmaY, mode, + finalW, finalH, colorSpace, fit); + } + + // Only the last rendered renderTargetContext needs to match the supplied 'fit' + SkBackingFit xFit = fit, yFit = fit; + if (scaleFactorX > 1 || scaleFactorY > 1) { + xFit = yFit = SkBackingFit::kApprox; // reexpand will be last + } else if (sigmaY > 0.0f) { + xFit = SkBackingFit::kApprox; // the y-pass will be last + } + + GrTextureDomain::Mode currDomainMode = mode; + if (scaleFactorX > 1 || scaleFactorY > 1) { + srcProxy = decimate(context, std::move(srcProxy), srcColorType, localProxyOffset, + &srcOffset, &localSrcBounds, scaleFactorX, scaleFactorY, radiusX, + radiusY, currDomainMode, finalW, finalH, colorSpace); + if (!srcProxy) { + return nullptr; + } + localProxyOffset.set(0, 0); + if (GrTextureDomain::kIgnore_Mode == currDomainMode) { + // decimate() always returns an approx texture, possibly with garbage after the image. + // We can't ignore the domain anymore. + currDomainMode = GrTextureDomain::kClamp_Mode; + } + } + + std::unique_ptr dstRenderTargetContext; + + auto srcRect = SkIRect::MakeWH(finalW, finalH); + scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); + if (sigmaX > 0.0f) { + dstRenderTargetContext = convolve_gaussian( + context, std::move(srcProxy), srcColorType, localProxyOffset, srcRect, srcOffset, + Direction::kX, radiusX, sigmaX, &localSrcBounds, currDomainMode, finalW, finalH, + colorSpace, xFit); + if (!dstRenderTargetContext) { + return nullptr; + } + + srcProxy = dstRenderTargetContext->asTextureProxyRef(); + if (!srcProxy) { + return nullptr; + } + + srcRect.offsetTo(0, 0); + srcOffset.set(0, 0); + localProxyOffset.set(0, 0); + if (SkBackingFit::kApprox == xFit && GrTextureDomain::kIgnore_Mode == currDomainMode) { + // srcProxy is now an approx texture, possibly with garbage after the image. We can't + // ignore the domain anymore. + currDomainMode = GrTextureDomain::kClamp_Mode; + } + } + + if (sigmaY > 0.0f) { + dstRenderTargetContext = convolve_gaussian( + context, std::move(srcProxy), srcColorType, localProxyOffset, srcRect, srcOffset, + Direction::kY, radiusY, sigmaY, &localSrcBounds, currDomainMode, finalW, finalH, + colorSpace, yFit); + if (!dstRenderTargetContext) { + return nullptr; + } + + srcProxy = dstRenderTargetContext->asTextureProxyRef(); + if (!srcProxy) { + return nullptr; + } + + srcRect.offsetTo(0, 0); + srcOffset.set(0, 0); + localProxyOffset.set(0, 0); + } + + SkASSERT(dstRenderTargetContext); + SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy()); + SkASSERT(localProxyOffset.x() == 0 && localProxyOffset.y() == 0); + + if (scaleFactorX > 1 || scaleFactorY > 1) { + dstRenderTargetContext = + reexpand(context, std::move(dstRenderTargetContext), localSrcBounds, scaleFactorX, + scaleFactorY, finalW, finalH, colorSpace, fit); + } + + SkASSERT(!dstRenderTargetContext || dstRenderTargetContext->origin() == srcProxy->origin()); + return dstRenderTargetContext; +} +} + +#endif diff --git a/gfx/skia/skia/src/core/SkGpuBlurUtils.h b/gfx/skia/skia/src/core/SkGpuBlurUtils.h new file mode 100644 index 0000000000..9143518cd1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkGpuBlurUtils.h @@ -0,0 +1,62 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGpuBlurUtils_DEFINED +#define SkGpuBlurUtils_DEFINED + +#if SK_SUPPORT_GPU +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/effects/GrTextureDomain.h" + +class GrContext; +class GrTexture; + +struct SkRect; + +namespace SkGpuBlurUtils { + /** + * Applies a 2D Gaussian blur to a given texture. The blurred result is returned + * as a renderTargetContext in case the caller wishes to draw into the result. + * + * The 'proxyOffset' is kept separate form 'srcBounds' because they exist in different + * coordinate spaces. 'srcBounds' exists in the content space of the special image, and + * 'proxyOffset' maps from the content space to the proxy's space. + * + * Note: one of sigmaX and sigmaY should be non-zero! + * @param context The GPU context + * @param srcProxy The source to be blurred. + * @param srcColorType The colorType of srcProxy + * @param srcAlphaType The alphaType of srcProxy + * @param proxyOffset The offset from the top-left corner to valid texels in 'srcProxy', + which should come from the subset of the owning SkSpecialImage. + * @param colorSpace Color space of the source (used for the renderTargetContext result, + * too). + * @param dstBounds The destination bounds, relative to the source texture. + * @param srcBounds The source bounds, relative to the source texture's offset. No pixels + * will be sampled outside of this rectangle. + * @param sigmaX The blur's standard deviation in X. + * @param sigmaY The blur's standard deviation in Y. + * @param mode The mode to handle samples outside bounds. + * @param fit backing fit for the returned render target context + * @return The renderTargetContext containing the blurred result. + */ +std::unique_ptr GaussianBlur(GrRecordingContext* context, + sk_sp srcProxy, + GrColorType srcColorType, + SkAlphaType srcAlphaType, + const SkIPoint& proxyOffset, + sk_sp colorSpace, + const SkIRect& dstBounds, + const SkIRect& srcBounds, + float sigmaX, + float sigmaY, + GrTextureDomain::Mode mode, + SkBackingFit fit = SkBackingFit::kApprox); +}; + +#endif +#endif diff --git a/gfx/skia/skia/src/core/SkGraphics.cpp b/gfx/skia/skia/src/core/SkGraphics.cpp new file mode 100644 index 0000000000..5db7daa1fe --- /dev/null +++ b/gfx/skia/skia/src/core/SkGraphics.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkGraphics.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkMath.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkShader.h" +#include "include/core/SkStream.h" +#include "include/core/SkTime.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkCpu.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkOpts.h" +#include "src/core/SkResourceCache.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTSearch.h" +#include "src/core/SkTypefaceCache.h" +#include "src/utils/SkUTF.h" + +#include + +void SkGraphics::Init() { + // SkGraphics::Init() must be thread-safe and idempotent. + SkCpu::CacheRuntimeFeatures(); + SkOpts::Init(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGraphics::DumpMemoryStatistics(SkTraceMemoryDump* dump) { + SkResourceCache::DumpMemoryStatistics(dump); + SkStrikeCache::DumpMemoryStatistics(dump); +} + +void SkGraphics::PurgeAllCaches() { + SkGraphics::PurgeFontCache(); + SkGraphics::PurgeResourceCache(); + SkImageFilter_Base::PurgeCache(); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const char kFontCacheLimitStr[] = "font-cache-limit"; +static const size_t kFontCacheLimitLen = sizeof(kFontCacheLimitStr) - 1; + +static const struct { + const char* fStr; + size_t fLen; + size_t (*fFunc)(size_t); +} gFlags[] = { + { kFontCacheLimitStr, kFontCacheLimitLen, SkGraphics::SetFontCacheLimit } +}; + +/* flags are of the form param; or param=value; */ +void SkGraphics::SetFlags(const char* flags) { + if (!flags) { + return; + } + const char* nextSemi; + do { + size_t len = strlen(flags); + const char* paramEnd = flags + len; + const char* nextEqual = strchr(flags, '='); + if (nextEqual && paramEnd > nextEqual) { + paramEnd = nextEqual; + } + nextSemi = strchr(flags, ';'); + if (nextSemi && paramEnd > nextSemi) { + paramEnd = nextSemi; + } + size_t paramLen = paramEnd - flags; + for (int i = 0; i < (int)SK_ARRAY_COUNT(gFlags); ++i) { + if (paramLen != gFlags[i].fLen) { + continue; + } + if (strncmp(flags, gFlags[i].fStr, paramLen) == 0) { + size_t val = 0; + if (nextEqual) { + val = (size_t) atoi(nextEqual + 1); + } + (gFlags[i].fFunc)(val); + break; + } + } + flags = nextSemi + 1; + } while (nextSemi); +} + +size_t SkGraphics::GetFontCacheLimit() { + return SkStrikeCache::GlobalStrikeCache()->getCacheSizeLimit(); +} + +size_t SkGraphics::SetFontCacheLimit(size_t bytes) { + return SkStrikeCache::GlobalStrikeCache()->setCacheSizeLimit(bytes); +} + +size_t SkGraphics::GetFontCacheUsed() { + return SkStrikeCache::GlobalStrikeCache()->getTotalMemoryUsed(); +} + +int SkGraphics::GetFontCacheCountLimit() { + return SkStrikeCache::GlobalStrikeCache()->getCacheCountLimit(); +} + +int SkGraphics::SetFontCacheCountLimit(int count) { + return SkStrikeCache::GlobalStrikeCache()->setCacheCountLimit(count); +} + +int SkGraphics::GetFontCacheCountUsed() { + return SkStrikeCache::GlobalStrikeCache()->getCacheCountUsed(); +} + +int SkGraphics::GetFontCachePointSizeLimit() { + return SkStrikeCache::GlobalStrikeCache()->getCachePointSizeLimit(); +} + +int SkGraphics::SetFontCachePointSizeLimit(int limit) { + return SkStrikeCache::GlobalStrikeCache()->setCachePointSizeLimit(limit); +} + +void SkGraphics::PurgeFontCache() { + SkStrikeCache::GlobalStrikeCache()->purgeAll(); + SkTypefaceCache::PurgeAll(); +} diff --git a/gfx/skia/skia/src/core/SkHalf.cpp b/gfx/skia/skia/src/core/SkHalf.cpp new file mode 100644 index 0000000000..49ce7efbe2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkHalf.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkFloatBits.h" +#include "include/private/SkHalf.h" + +uint16_t halfMantissa(SkHalf h) { + return h & 0x03ff; +} + +uint16_t halfExponent(SkHalf h) { + return (h >> 10) & 0x001f; +} + +uint16_t halfSign(SkHalf h) { + return h >> 15; +} + +union FloatUIntUnion { + uint32_t fUInt; // this must come first for the initializations below to work + float fFloat; +}; + +// based on Fabien Giesen's float_to_half_fast3() +// see https://gist.github.com/rygorous/2156668 +SkHalf SkFloatToHalf(float f) { + static const uint32_t f32infty = { 255 << 23 }; + static const uint32_t f16infty = { 31 << 23 }; + static const FloatUIntUnion magic = { 15 << 23 }; + static const uint32_t sign_mask = 0x80000000u; + static const uint32_t round_mask = ~0xfffu; + SkHalf o = 0; + + FloatUIntUnion floatUnion; + floatUnion.fFloat = f; + + uint32_t sign = floatUnion.fUInt & sign_mask; + floatUnion.fUInt ^= sign; + + // NOTE all the integer compares in this function can be safely + // compiled into signed compares since all operands are below + // 0x80000000. Important if you want fast straight SSE2 code + // (since there's no unsigned PCMPGTD). + + // Inf or NaN (all exponent bits set) + if (floatUnion.fUInt >= f32infty) + // NaN->qNaN and Inf->Inf + o = (floatUnion.fUInt > f32infty) ? 0x7e00 : 0x7c00; + // (De)normalized number or zero + else { + floatUnion.fUInt &= round_mask; + floatUnion.fFloat *= magic.fFloat; + floatUnion.fUInt -= round_mask; + // Clamp to signed infinity if overflowed + if (floatUnion.fUInt > f16infty) { + floatUnion.fUInt = f16infty; + } + + o = floatUnion.fUInt >> 13; // Take the bits! + } + + o |= sign >> 16; + return o; +} + +// based on Fabien Giesen's half_to_float_fast2() +// see https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ +float SkHalfToFloat(SkHalf h) { + static const FloatUIntUnion magic = { 126 << 23 }; + FloatUIntUnion o; + + if (halfExponent(h) == 0) + { + // Zero / Denormal + o.fUInt = magic.fUInt + halfMantissa(h); + o.fFloat -= magic.fFloat; + } + else + { + // Set mantissa + o.fUInt = halfMantissa(h) << 13; + // Set exponent + if (halfExponent(h) == 0x1f) + // Inf/NaN + o.fUInt |= (255 << 23); + else + o.fUInt |= ((127 - 15 + halfExponent(h)) << 23); + } + + // Set sign + o.fUInt |= (halfSign(h) << 31); + return o.fFloat; +} diff --git a/gfx/skia/skia/src/core/SkICC.cpp b/gfx/skia/skia/src/core/SkICC.cpp new file mode 100644 index 0000000000..78e4a90f06 --- /dev/null +++ b/gfx/skia/skia/src/core/SkICC.cpp @@ -0,0 +1,350 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkICC.h" +#include "include/private/SkFixed.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkEndian.h" +#include "src/core/SkICCPriv.h" +#include "src/core/SkMD5.h" +#include "src/core/SkUtils.h" + +static constexpr char kDescriptionTagBodyPrefix[12] = + { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'}; + +static constexpr size_t kICCDescriptionTagSize = 44; + +static_assert(kICCDescriptionTagSize == + sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), ""); +static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2; // ascii->utf16be + +static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes."); +static constexpr uint32_t kDescriptionTagHeader[7] { + SkEndian_SwapBE32(kTAG_TextType), // Type signature + 0, // Reserved + SkEndian_SwapBE32(1), // Number of records + SkEndian_SwapBE32(12), // Record size (must be 12) + SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA + SkEndian_SwapBE32(kDescriptionTagBodySize), // Length of string + SkEndian_SwapBE32(28), // Offset of string +}; + +static constexpr uint32_t kWhitePointTag[5] { + SkEndian_SwapBE32(kXYZ_PCSSpace), + 0, + SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50) + SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50) + SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50) +}; + +// Google Inc. 2016 (UTF-16) +static constexpr uint8_t kCopyrightTagBody[] = { + 0x00, 0x47, 0x00, 0x6f, + 0x00, 0x6f, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x65, + 0x00, 0x20, 0x00, 0x49, + 0x00, 0x6e, 0x00, 0x63, + 0x00, 0x2e, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x30, + 0x00, 0x31, 0x00, 0x36, +}; +static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes."); +static constexpr uint32_t kCopyrightTagHeader[7] { + SkEndian_SwapBE32(kTAG_TextType), // Type signature + 0, // Reserved + SkEndian_SwapBE32(1), // Number of records + SkEndian_SwapBE32(12), // Record size (must be 12) + SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA + SkEndian_SwapBE32(sizeof(kCopyrightTagBody)), // Length of string + SkEndian_SwapBE32(28), // Offset of string +}; + +// We will write a profile with the minimum nine required tags. +static constexpr uint32_t kICCNumEntries = 9; + +static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c'); +static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) + + kDescriptionTagBodySize; +static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + + kICCNumEntries * kICCTagTableEntrySize; + +static constexpr uint32_t kTAG_XYZ_Bytes = 20; +static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes; +static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes; +static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes; + +static constexpr uint32_t kTAG_TRC_Bytes = 40; +static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes; +static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset; +static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset; + +static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't'); +static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes; + +static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't'); +static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) + + sizeof(kCopyrightTagBody); +static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes; + +static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes; + +static constexpr uint32_t kICCHeader[kICCHeaderSize / 4] { + SkEndian_SwapBE32(kICCProfileSize), // Size of the profile + 0, // Preferred CMM type (ignored) + SkEndian_SwapBE32(0x02100000), // Version 2.1 + SkEndian_SwapBE32(kDisplay_Profile), // Display device profile + SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space + SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space + 0, 0, 0, // Date and time (ignored) + SkEndian_SwapBE32(kACSP_Signature), // Profile signature + 0, // Platform target (ignored) + 0x00000000, // Flags: not embedded, can be used independently + 0, // Device manufacturer (ignored) + 0, // Device model (ignored) + 0, 0, // Device attributes (ignored) + SkEndian_SwapBE32(1), // Relative colorimetric rendering intent + SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X) + SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y) + SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z) + 0, // Profile creator (ignored) + 0, 0, 0, 0, // Profile id checksum (ignored) + 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored) + SkEndian_SwapBE32(kICCNumEntries), // Number of tags +}; + +static constexpr uint32_t kICCTagTable[3 * kICCNumEntries] { + // Profile description + SkEndian_SwapBE32(kTAG_desc), + SkEndian_SwapBE32(kTAG_desc_Offset), + SkEndian_SwapBE32(kTAG_desc_Bytes), + + // rXYZ + SkEndian_SwapBE32(kTAG_rXYZ), + SkEndian_SwapBE32(kTAG_rXYZ_Offset), + SkEndian_SwapBE32(kTAG_XYZ_Bytes), + + // gXYZ + SkEndian_SwapBE32(kTAG_gXYZ), + SkEndian_SwapBE32(kTAG_gXYZ_Offset), + SkEndian_SwapBE32(kTAG_XYZ_Bytes), + + // bXYZ + SkEndian_SwapBE32(kTAG_bXYZ), + SkEndian_SwapBE32(kTAG_bXYZ_Offset), + SkEndian_SwapBE32(kTAG_XYZ_Bytes), + + // rTRC + SkEndian_SwapBE32(kTAG_rTRC), + SkEndian_SwapBE32(kTAG_rTRC_Offset), + SkEndian_SwapBE32(kTAG_TRC_Bytes), + + // gTRC + SkEndian_SwapBE32(kTAG_gTRC), + SkEndian_SwapBE32(kTAG_gTRC_Offset), + SkEndian_SwapBE32(kTAG_TRC_Bytes), + + // bTRC + SkEndian_SwapBE32(kTAG_bTRC), + SkEndian_SwapBE32(kTAG_bTRC_Offset), + SkEndian_SwapBE32(kTAG_TRC_Bytes), + + // White point + SkEndian_SwapBE32(kTAG_wtpt), + SkEndian_SwapBE32(kTAG_wtpt_Offset), + SkEndian_SwapBE32(kTAG_XYZ_Bytes), + + // Copyright + SkEndian_SwapBE32(kTAG_cprt), + SkEndian_SwapBE32(kTAG_cprt_Offset), + SkEndian_SwapBE32(kTAG_cprt_Bytes), +}; + +// This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible +// when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed). +// The use of double is necessary to accomodate the full potential 32-bit mantissa of the 16.16 +// SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h. +static SkFixed float_round_to_fixed(float x) { + return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5)); +} + +static void write_xyz_tag(uint32_t* ptr, const skcms_Matrix3x3& toXYZD50, int col) { + ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); + ptr[1] = 0; + ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[0][col])); + ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[1][col])); + ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[2][col])); +} + +static void write_trc_tag(uint32_t* ptr, const skcms_TransferFunction& fn) { + ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType); + ptr[1] = 0; + ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType)); + ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(fn.g)); + ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(fn.a)); + ptr[5] = SkEndian_SwapBE32(float_round_to_fixed(fn.b)); + ptr[6] = SkEndian_SwapBE32(float_round_to_fixed(fn.c)); + ptr[7] = SkEndian_SwapBE32(float_round_to_fixed(fn.d)); + ptr[8] = SkEndian_SwapBE32(float_round_to_fixed(fn.e)); + ptr[9] = SkEndian_SwapBE32(float_round_to_fixed(fn.f)); +} + +static bool nearly_equal(float x, float y) { + // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a + // tolerance of 0.001f, which doesn't seem to be enough to distinguish + // between similar transfer functions, for example: gamma2.2 and sRGB. + // + // If the tolerance is 0.0f, then this we can't distinguish between two + // different encodings of what is clearly the same colorspace. Some + // experimentation with example files lead to this number: + static constexpr float kTolerance = 1.0f / (1 << 11); + return ::fabsf(x - y) <= kTolerance; +} + +static bool nearly_equal(const skcms_TransferFunction& u, + const skcms_TransferFunction& v) { + return nearly_equal(u.g, v.g) + && nearly_equal(u.a, v.a) + && nearly_equal(u.b, v.b) + && nearly_equal(u.c, v.c) + && nearly_equal(u.d, v.d) + && nearly_equal(u.e, v.e) + && nearly_equal(u.f, v.f); +} + +static bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) { + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) { + if (!nearly_equal(u.vals[r][c], v.vals[r][c])) { + return false; + } + } + } + return true; +} + +// Return nullptr if the color profile doen't have a special name. +const char* get_color_profile_description(const skcms_TransferFunction& fn, + const skcms_Matrix3x3& toXYZD50) { + bool srgb_xfer = nearly_equal(fn, SkNamedTransferFn::kSRGB); + bool srgb_gamut = nearly_equal(toXYZD50, SkNamedGamut::kSRGB); + if (srgb_xfer && srgb_gamut) { + return "sRGB"; + } + bool line_xfer = nearly_equal(fn, SkNamedTransferFn::kLinear); + if (line_xfer && srgb_gamut) { + return "Linear Transfer with sRGB Gamut"; + } + bool twoDotTwo = nearly_equal(fn, SkNamedTransferFn::k2Dot2); + if (twoDotTwo && srgb_gamut) { + return "2.2 Transfer with sRGB Gamut"; + } + if (twoDotTwo && nearly_equal(toXYZD50, SkNamedGamut::kAdobeRGB)) { + return "AdobeRGB"; + } + bool dcip3_gamut = nearly_equal(toXYZD50, SkNamedGamut::kDCIP3); + if (srgb_xfer || line_xfer) { + if (srgb_xfer && dcip3_gamut) { + return "sRGB Transfer with DCI-P3 Gamut"; + } + if (line_xfer && dcip3_gamut) { + return "Linear Transfer with DCI-P3 Gamut"; + } + bool rec2020 = nearly_equal(toXYZD50, SkNamedGamut::kRec2020); + if (srgb_xfer && rec2020) { + return "sRGB Transfer with Rec-BT-2020 Gamut"; + } + if (line_xfer && rec2020) { + return "Linear Transfer with Rec-BT-2020 Gamut"; + } + } + return nullptr; +} + +static void get_color_profile_tag(char dst[kICCDescriptionTagSize], + const skcms_TransferFunction& fn, + const skcms_Matrix3x3& toXYZD50) { + SkASSERT(dst); + if (const char* description = get_color_profile_description(fn, toXYZD50)) { + SkASSERT(strlen(description) < kICCDescriptionTagSize); + strncpy(dst, description, kICCDescriptionTagSize); + // "If the length of src is less than n, strncpy() writes additional + // null bytes to dest to ensure that a total of n bytes are written." + } else { + strncpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix)); + SkMD5 md5; + md5.write(&toXYZD50, sizeof(toXYZD50)); + static_assert(sizeof(fn) == sizeof(float) * 7, "packed"); + md5.write(&fn, sizeof(fn)); + SkMD5::Digest digest = md5.finish(); + char* ptr = dst + sizeof(kDescriptionTagBodyPrefix); + for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) { + uint8_t byte = digest.data[i]; + *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4]; + *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF]; + } + SkASSERT(ptr == dst + kICCDescriptionTagSize); + } +} + +sk_sp SkWriteICCProfile(const skcms_TransferFunction& fn, + const skcms_Matrix3x3& toXYZD50) { + // We can't encode HDR transfer functions in ICC + if (classify_transfer_fn(fn) != sRGBish_TF) { + return nullptr; + } + + SkAutoMalloc profile(kICCProfileSize); + uint8_t* ptr = (uint8_t*) profile.get(); + + // Write profile header + memcpy(ptr, kICCHeader, sizeof(kICCHeader)); + ptr += sizeof(kICCHeader); + + // Write tag table + memcpy(ptr, kICCTagTable, sizeof(kICCTagTable)); + ptr += sizeof(kICCTagTable); + + // Write profile description tag + memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader)); + ptr += sizeof(kDescriptionTagHeader); + { + char colorProfileTag[kICCDescriptionTagSize]; + get_color_profile_tag(colorProfileTag, fn, toXYZD50); + + // ASCII --> big-endian UTF-16. + for (size_t i = 0; i < kICCDescriptionTagSize; i++) { + *ptr++ = 0; + *ptr++ = colorProfileTag[i]; + } + } + + // Write XYZ tags + write_xyz_tag((uint32_t*) ptr, toXYZD50, 0); + ptr += kTAG_XYZ_Bytes; + write_xyz_tag((uint32_t*) ptr, toXYZD50, 1); + ptr += kTAG_XYZ_Bytes; + write_xyz_tag((uint32_t*) ptr, toXYZD50, 2); + ptr += kTAG_XYZ_Bytes; + + // Write TRC tag + write_trc_tag((uint32_t*) ptr, fn); + ptr += kTAG_TRC_Bytes; + + // Write white point tag (must be D50) + memcpy(ptr, kWhitePointTag, sizeof(kWhitePointTag)); + ptr += sizeof(kWhitePointTag); + + // Write copyright tag + memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader)); + ptr += sizeof(kCopyrightTagHeader); + memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody)); + ptr += sizeof(kCopyrightTagBody); + + SkASSERT(kICCProfileSize == ptr - (uint8_t*) profile.get()); + return SkData::MakeFromMalloc(profile.release(), kICCProfileSize); +} diff --git a/gfx/skia/skia/src/core/SkICCPriv.h b/gfx/skia/skia/src/core/SkICCPriv.h new file mode 100644 index 0000000000..1ed2ca2dbc --- /dev/null +++ b/gfx/skia/skia/src/core/SkICCPriv.h @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkICCPriv_DEFINED +#define SkICCPriv_DEFINED + +#include "include/core/SkColorSpace.h" +#include "include/core/SkTypes.h" + +// This is equal to the header size according to the ICC specification (128) +// plus the size of the tag count (4). We include the tag count since we +// always require it to be present anyway. +static constexpr size_t kICCHeaderSize = 132; + +// Contains a signature (4), offset (4), and size (4). +static constexpr size_t kICCTagTableEntrySize = 12; + +static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '); +static constexpr uint32_t kCMYK_ColorSpace = SkSetFourByteTag('C', 'M', 'Y', 'K'); +static constexpr uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y'); +static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'); +static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'); +static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); +static constexpr uint32_t kColorSpace_Profile = SkSetFourByteTag('s', 'p', 'a', 'c'); +static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '); +static constexpr uint32_t kLAB_PCSSpace = SkSetFourByteTag('L', 'a', 'b', ' '); +static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p'); + +static constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); +static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); +static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); +static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_kTRC = SkSetFourByteTag('k', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0'); + +static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v'); +static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a'); +static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c'); + +enum ParaCurveType { + kExponential_ParaCurveType = 0, + kGAB_ParaCurveType = 1, + kGABC_ParaCurveType = 2, + kGABDE_ParaCurveType = 3, + kGABCDEF_ParaCurveType = 4, +}; + +#endif // SkICCPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkIPoint16.h b/gfx/skia/skia/src/core/SkIPoint16.h new file mode 100644 index 0000000000..c66465f720 --- /dev/null +++ b/gfx/skia/skia/src/core/SkIPoint16.h @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkIPoint16_DEFINED +#define SkIPoint16_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkTo.h" + +/** \struct SkIPoint16 + SkIPoint16 holds two 16 bit integer coordinates. + */ +struct SkIPoint16 { + int16_t fX; //!< x-axis value used by SkIPoint16 + + int16_t fY; //!< y-axis value used by SkIPoint16 + + /** Sets fX to x, fY to y. If SK_DEBUG is defined, asserts + if x or y does not fit in 16 bits. + + @param x integer x-axis value of constructed SkIPoint + @param y integer y-axis value of constructed SkIPoint + @return SkIPoint16 (x, y) + */ + static constexpr SkIPoint16 Make(int x, int y) { + return {SkToS16(x), SkToS16(y)}; + } + + /** Returns x-axis value of SkIPoint16. + + @return fX + */ + int16_t x() const { return fX; } + + /** Returns y-axis value of SkIPoint. + + @return fY + */ + int16_t y() const { return fY; } + + /** Sets fX to x and fY to y. + + @param x new value for fX + @param y new value for fY + */ + void set(int x, int y) { + fX = SkToS16(x); + fY = SkToS16(y); + } +}; + +#endif + diff --git a/gfx/skia/skia/src/core/SkImageFilter.cpp b/gfx/skia/skia/src/core/SkImageFilter.cpp new file mode 100644 index 0000000000..107566459b --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageFilter.cpp @@ -0,0 +1,713 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkRect.h" +#include "include/effects/SkComposeImageFilter.h" +#include "include/private/SkSafe32.h" +#include "src/core/SkFuzzLogging.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkLocalMatrixImageFilter.h" +#include "src/core/SkMatrixImageFilter.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkValidationUtils.h" +#include "src/core/SkWriteBuffer.h" +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#endif +#include + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// SkImageFilter - A number of the public APIs on SkImageFilter downcast to SkImageFilter_Base +// in order to perform their actual work. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the number of inputs this filter will accept (some inputs can + * be NULL). + */ +int SkImageFilter::countInputs() const { return as_IFB(this)->fInputs.count(); } + +/** + * Returns the input filter at a given index, or NULL if no input is + * connected. The indices used are filter-specific. + */ +const SkImageFilter* SkImageFilter::getInput(int i) const { + SkASSERT(i < this->countInputs()); + return as_IFB(this)->fInputs[i].get(); +} + +bool SkImageFilter::isColorFilterNode(SkColorFilter** filterPtr) const { + return as_IFB(this)->onIsColorFilterNode(filterPtr); +} + +SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection direction, const SkIRect* inputRect) const { + // The old filterBounds() function uses SkIRects that are defined in layer space so, while + // we still are supporting it, bypass SkIF_B's new public filter bounds functions and go right + // to the internal layer-space calculations. + skif::Mapping mapping(SkMatrix::I(), ctm); + if (kReverse_MapDirection == direction) { + skif::LayerSpace targetOutput(src); + skif::LayerSpace content(inputRect ? *inputRect : src); + return SkIRect(as_IFB(this)->onGetInputLayerBounds(mapping, targetOutput, content)); + } else { + SkASSERT(!inputRect); + skif::LayerSpace content(src); + skif::LayerSpace output = as_IFB(this)->onGetOutputLayerBounds(mapping, content); + // Manually apply the crop rect for now, until cropping is performed by a dedicated SkIF. + SkIRect dst; + as_IFB(this)->getCropRect().applyTo( + SkIRect(output), ctm, as_IFB(this)->affectsTransparentBlack(), &dst); + return dst; + } +} + +SkRect SkImageFilter::computeFastBounds(const SkRect& src) const { + if (0 == this->countInputs()) { + return src; + } + SkRect combinedBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; + for (int i = 1; i < this->countInputs(); i++) { + const SkImageFilter* input = this->getInput(i); + if (input) { + combinedBounds.join(input->computeFastBounds(src)); + } else { + combinedBounds.join(src); + } + } + return combinedBounds; +} + +bool SkImageFilter::canComputeFastBounds() const { + if (as_IFB(this)->affectsTransparentBlack()) { + return false; + } + for (int i = 0; i < this->countInputs(); i++) { + const SkImageFilter* input = this->getInput(i); + if (input && !input->canComputeFastBounds()) { + return false; + } + } + return true; +} + +bool SkImageFilter::asAColorFilter(SkColorFilter** filterPtr) const { + SkASSERT(nullptr != filterPtr); + if (!this->isColorFilterNode(filterPtr)) { + return false; + } + if (nullptr != this->getInput(0) || (*filterPtr)->affectsTransparentBlack()) { + (*filterPtr)->unref(); + return false; + } + return true; +} + +sk_sp SkImageFilter::MakeMatrixFilter(const SkMatrix& matrix, + SkFilterQuality filterQuality, + sk_sp input) { + return SkMatrixImageFilter::Make(matrix, filterQuality, std::move(input)); +} + +sk_sp SkImageFilter::makeWithLocalMatrix(const SkMatrix& matrix) const { + return SkLocalMatrixImageFilter::Make(matrix, this->refMe()); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// SkImageFilter_Base +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SK_USE_FLUENT_IMAGE_FILTER_TYPES + +static int32_t next_image_filter_unique_id() { + static std::atomic nextID{1}; + + int32_t id; + do { + id = nextID++; + } while (id == 0); + return id; +} + +SkImageFilter_Base::SkImageFilter_Base(sk_sp const* inputs, + int inputCount, const CropRect* cropRect) + : fUsesSrcInput(false) + , fUniqueID(next_image_filter_unique_id()) { + fCropRect = cropRect ? *cropRect : CropRect(SkRect(), 0x0); + + fInputs.reset(inputCount); + + for (int i = 0; i < inputCount; ++i) { + if (!inputs[i] || as_IFB(inputs[i])->fUsesSrcInput) { + fUsesSrcInput = true; + } + fInputs[i] = inputs[i]; + } +} + +SkImageFilter_Base::~SkImageFilter_Base() { + SkImageFilterCache::Get()->purgeByImageFilter(this); +} + +bool SkImageFilter_Base::Common::unflatten(SkReadBuffer& buffer, int expectedCount) { + const int count = buffer.readInt(); + if (!buffer.validate(count >= 0)) { + return false; + } + if (!buffer.validate(expectedCount < 0 || count == expectedCount)) { + return false; + } + + SkASSERT(fInputs.empty()); + for (int i = 0; i < count; i++) { + fInputs.push_back(buffer.readBool() ? buffer.readImageFilter() : nullptr); + if (!buffer.isValid()) { + return false; + } + } + SkRect rect; + buffer.readRect(&rect); + if (!buffer.isValid() || !buffer.validate(SkIsValidRect(rect))) { + return false; + } + + uint32_t flags = buffer.readUInt(); + fCropRect = CropRect(rect, flags); + return buffer.isValid(); +} + +void SkImageFilter_Base::flatten(SkWriteBuffer& buffer) const { + buffer.writeInt(fInputs.count()); + for (int i = 0; i < fInputs.count(); i++) { + const SkImageFilter* input = this->getInput(i); + buffer.writeBool(input != nullptr); + if (input != nullptr) { + buffer.writeFlattenable(input); + } + } + buffer.writeRect(fCropRect.rect()); + buffer.writeUInt(fCropRect.flags()); +} + +skif::FilterResult SkImageFilter_Base::filterImage(const skif::Context& context) const { + // TODO (michaelludwig) - Old filters have an implicit assumption that the source image + // (originally passed separately) has an origin of (0, 0). SkComposeImageFilter makes an effort + // to ensure that remains the case. Once everyone uses the new type systems for bounds, non + // (0, 0) source origins will be easy to support. + SkASSERT(context.source().layerOrigin().x() == 0 && context.source().layerOrigin().y() == 0); + + skif::FilterResult result; + if (!context.isValid()) { + return result; + } + + uint32_t srcGenID = fUsesSrcInput ? context.sourceImage()->uniqueID() : 0; + const SkIRect srcSubset = fUsesSrcInput ? context.sourceImage()->subset() + : SkIRect::MakeWH(0, 0); + + SkImageFilterCacheKey key(fUniqueID, context.mapping().layerMatrix(), context.clipBounds(), + srcGenID, srcSubset); + if (context.cache() && context.cache()->get(key, &result)) { + return result; + } + + result = this->onFilterImage(context); + +#if SK_SUPPORT_GPU + if (context.gpuBacked() && result.image() && !result.image()->isTextureBacked()) { + // Keep the result on the GPU - this is still required for some + // image filters that don't support GPU in all cases + auto asTexture = result.image()->makeTextureImage(context.getContext()); + result = skif::FilterResult(std::move(asTexture), result.layerOrigin()); + } +#endif + + if (context.cache()) { + context.cache()->set(key, this, result); + } + + return result; +} + +skif::LayerSpace SkImageFilter_Base::getInputBounds( + const skif::Mapping& mapping, const skif::DeviceSpace& desiredOutput, + const skif::ParameterSpace* knownContentBounds) const { + // Map both the device-space desired coverage area and the known content bounds to layer space + skif::LayerSpace desiredBounds = mapping.deviceToLayer(desiredOutput).roundOut(); + // If we have no known content bounds use the desired coverage area, because that is the most + // conservative possibility. + skif::LayerSpace contentBounds = + knownContentBounds ? mapping.paramToLayer(*knownContentBounds).roundOut() + : desiredBounds; + + // Process the layer-space desired output with the filter DAG to determine required input + skif::LayerSpace requiredInput = this->onGetInputLayerBounds( + mapping, desiredBounds, contentBounds); + // If we know what's actually going to be drawn into the layer, and we don't change transparent + // black, then we can further restrict the layer to what the known content is + if (knownContentBounds && !this->affectsTransparentBlack()) { + if (!requiredInput.intersect(contentBounds)) { + // Nothing would be output by the filter, so return empty rect + return skif::LayerSpace(SkIRect::MakeEmpty()); + } + } + return requiredInput; +} + +skif::DeviceSpace SkImageFilter_Base::getOutputBounds( + const skif::Mapping& mapping, const skif::ParameterSpace& contentBounds) const { + // Map the input content into the layer space where filtering will occur + skif::LayerSpace layerContent = mapping.paramToLayer(contentBounds); + // Determine the filter DAGs output bounds in layer space + skif::LayerSpace filterOutput = this->onGetOutputLayerBounds( + mapping, layerContent.roundOut()); + // FIXME (michaelludwig) - To be removed once cropping is isolated, but remain consistent with + // old filterBounds(kForward) behavior. + SkIRect dst; + as_IFB(this)->getCropRect().applyTo( + SkIRect(filterOutput), mapping.layerMatrix(), + as_IFB(this)->affectsTransparentBlack(), &dst); + + // Map all the way to device space + return mapping.layerToDevice(skif::LayerSpace(dst)); +} + +// TODO (michaelludwig) - Default to using the old onFilterImage, as filters are updated one by one. +// Once the old function is gone, this onFilterImage() will be made a pure virtual. +skif::FilterResult SkImageFilter_Base::onFilterImage(const skif::Context& context) const { + SkIPoint origin; + auto image = this->onFilterImage(context, &origin); + return skif::FilterResult(std::move(image), skif::LayerSpace(origin)); +} + +bool SkImageFilter_Base::canHandleComplexCTM() const { + // CropRects need to apply in the source coordinate system, but are not aware of complex CTMs + // when performing clipping. For a simple fix, any filter with a crop rect set cannot support + // complex CTMs until that's updated. + if (this->cropRectIsSet() || !this->onCanHandleComplexCTM()) { + return false; + } + const int count = this->countInputs(); + for (int i = 0; i < count; ++i) { + const SkImageFilter_Base* input = as_IFB(this->getInput(i)); + if (input && !input->canHandleComplexCTM()) { + return false; + } + } + return true; +} + +void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds, const SkMatrix& ctm, + bool embiggen, SkIRect* cropped) const { + *cropped = imageBounds; + if (fFlags) { + SkRect devCropR; + ctm.mapRect(&devCropR, fRect); + SkIRect devICropR = devCropR.roundOut(); + + // Compute the left/top first, in case we need to modify the right/bottom for a missing edge + if (fFlags & kHasLeft_CropEdge) { + if (embiggen || devICropR.fLeft > cropped->fLeft) { + cropped->fLeft = devICropR.fLeft; + } + } else { + devICropR.fRight = Sk32_sat_add(cropped->fLeft, devICropR.width()); + } + if (fFlags & kHasTop_CropEdge) { + if (embiggen || devICropR.fTop > cropped->fTop) { + cropped->fTop = devICropR.fTop; + } + } else { + devICropR.fBottom = Sk32_sat_add(cropped->fTop, devICropR.height()); + } + if (fFlags & kHasWidth_CropEdge) { + if (embiggen || devICropR.fRight < cropped->fRight) { + cropped->fRight = devICropR.fRight; + } + } + if (fFlags & kHasHeight_CropEdge) { + if (embiggen || devICropR.fBottom < cropped->fBottom) { + cropped->fBottom = devICropR.fBottom; + } + } + } +} + +bool SkImageFilter_Base::applyCropRect(const Context& ctx, const SkIRect& srcBounds, + SkIRect* dstBounds) const { + SkIRect tmpDst = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection, nullptr); + fCropRect.applyTo(tmpDst, ctx.ctm(), this->affectsTransparentBlack(), dstBounds); + // Intersect against the clip bounds, in case the crop rect has + // grown the bounds beyond the original clip. This can happen for + // example in tiling, where the clip is much smaller than the filtered + // primitive. If we didn't do this, we would be processing the filter + // at the full crop rect size in every tile. + return dstBounds->intersect(ctx.clipBounds()); +} + +// Return a larger (newWidth x newHeight) copy of 'src' with black padding +// around it. +static sk_sp pad_image(SkSpecialImage* src, const SkImageFilter_Base::Context& ctx, + int newWidth, int newHeight, int offX, int offY) { + // We would like to operate in the source's color space (so that we return an "identical" + // image, other than the padding. To achieve that, we'd create a new context using + // src->getColorSpace() to replace ctx.colorSpace(). + + // That fails in at least two ways. For formats that are texturable but not renderable (like + // F16 on some ES implementations), we can't create a surface to do the work. For sRGB, images + // may be tagged with an sRGB color space (which leads to an sRGB config in makeSurface). But + // the actual config of that sRGB image on a device with no sRGB support is non-sRGB. + // + // Rather than try to special case these situations, we execute the image padding in the + // destination color space. This should not affect the output of the DAG in (almost) any case, + // because the result of this call is going to be used as an input, where it would have been + // switched to the destination space anyway. The one exception would be a filter that expected + // to consume unclamped F16 data, but the padded version of the image is pre-clamped to 8888. + // We can revisit this logic if that ever becomes an actual problem. + sk_sp surf(ctx.makeSurface(SkISize::Make(newWidth, newHeight))); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); + + src->draw(canvas, offX, offY, nullptr); + + return surf->makeImageSnapshot(); +} + +sk_sp SkImageFilter_Base::applyCropRectAndPad(const Context& ctx, + SkSpecialImage* src, + SkIPoint* srcOffset, + SkIRect* bounds) const { + const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset->x(), srcOffset->y(), + src->width(), src->height()); + + if (!this->applyCropRect(ctx, srcBounds, bounds)) { + return nullptr; + } + + if (srcBounds.contains(*bounds)) { + return sk_sp(SkRef(src)); + } else { + sk_sp img(pad_image(src, ctx, bounds->width(), bounds->height(), + Sk32_sat_sub(srcOffset->x(), bounds->x()), + Sk32_sat_sub(srcOffset->y(), bounds->y()))); + *srcOffset = SkIPoint::Make(bounds->x(), bounds->y()); + return img; + } +} + +// NOTE: The new onGetOutputLayerBounds() and onGetInputLayerBounds() default to calling into the +// deprecated onFilterBounds and onFilterNodeBounds. While these functions are not tagged, they do +// match the documented default behavior for the new bounds functions. +SkIRect SkImageFilter_Base::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection dir, const SkIRect* inputRect) const { + if (this->countInputs() < 1) { + return src; + } + + SkIRect totalBounds; + for (int i = 0; i < this->countInputs(); ++i) { + const SkImageFilter* filter = this->getInput(i); + SkIRect rect = filter ? filter->filterBounds(src, ctm, dir, inputRect) : src; + if (0 == i) { + totalBounds = rect; + } else { + totalBounds.join(rect); + } + } + + return totalBounds; +} + +SkIRect SkImageFilter_Base::onFilterNodeBounds(const SkIRect& src, const SkMatrix&, + MapDirection, const SkIRect*) const { + return src; +} + +skif::LayerSpace SkImageFilter_Base::visitInputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& desiredOutput, + const skif::LayerSpace& contentBounds) const { + if (this->countInputs() < 1) { + // TODO (michaelludwig) - if a filter doesn't have any inputs, it doesn't need any + // implicit source image, so arguably we could return an empty rect here. 'desiredOutput' is + // consistent with original behavior, so empty bounds may have unintended side effects + // but should be explored later. + return desiredOutput; + } + + skif::LayerSpace netInput; + for (int i = 0; i < this->countInputs(); ++i) { + const SkImageFilter* filter = this->getInput(i); + // The required input for this input filter, or 'targetOutput' if the filter is null and + // the source image is used (so must be sized to cover 'targetOutput'). + skif::LayerSpace requiredInput = + filter ? as_IFB(filter)->onGetInputLayerBounds(mapping, desiredOutput, + contentBounds) + : desiredOutput; + // Accumulate with all other filters + if (i == 0) { + netInput = requiredInput; + } else { + netInput.join(requiredInput); + } + } + return netInput; +} + +skif::LayerSpace SkImageFilter_Base::visitOutputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& contentBounds) const { + if (this->countInputs() < 1) { + // TODO (michaelludwig) - if a filter doesn't have any inputs, it presumably is determining + // its output size from something other than the implicit source contentBounds, in which + // case it shouldn't be calling this helper function, so explore adding an unreachable test + return contentBounds; + } + + skif::LayerSpace netOutput; + for (int i = 0; i < this->countInputs(); ++i) { + const SkImageFilter* filter = this->getInput(i); + // The output for just this input filter, or 'contentBounds' if the filter is null and + // the source image is used (i.e. the identity filter applied to the source). + skif::LayerSpace output = + filter ? as_IFB(filter)->onGetOutputLayerBounds(mapping, contentBounds) + : contentBounds; + // Accumulate with all other filters + if (i == 0) { + netOutput = output; + } else { + netOutput.join(output); + } + } + return netOutput; +} + +skif::LayerSpace SkImageFilter_Base::onGetInputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& desiredOutput, + const skif::LayerSpace& contentBounds, VisitChildren recurse) const { + // Call old functions for now since they may have been overridden by a subclass that's not been + // updated yet; normally this would just default to visitInputLayerBounds() + SkIRect content = SkIRect(contentBounds); + SkIRect input = this->onFilterNodeBounds(SkIRect(desiredOutput), mapping.layerMatrix(), + kReverse_MapDirection, &content); + if (recurse == VisitChildren::kYes) { + SkIRect aggregate = this->onFilterBounds(input, mapping.layerMatrix(), + kReverse_MapDirection, &input); + return skif::LayerSpace(aggregate); + } else { + return skif::LayerSpace(input); + } +} + +skif::LayerSpace SkImageFilter_Base::onGetOutputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& contentBounds) const { + // Call old functions for now; normally this would default to visitOutputLayerBounds() + SkIRect aggregate = this->onFilterBounds(SkIRect(contentBounds), mapping.layerMatrix(), + kForward_MapDirection, nullptr); + SkIRect output = this->onFilterNodeBounds(aggregate, mapping.layerMatrix(), + kForward_MapDirection, nullptr); + return skif::LayerSpace(output); +} + +template +skif::FilterResult SkImageFilter_Base::filterInput(int index, const skif::Context& ctx) const { + // Sanity checks for the index-specific input usages + SkASSERT(kU != skif::Usage::kInput0 || index == 0); + SkASSERT(kU != skif::Usage::kInput1 || index == 1); + + const SkImageFilter* input = this->getInput(index); + if (!input) { + // Convert from the generic kInput of the source image to kU + return static_cast>(ctx.source()); + } + + skif::FilterResult result = as_IFB(input)->filterImage(this->mapContext(ctx)); + SkASSERT(!result.image() || ctx.gpuBacked() == result.image()->isTextureBacked()); + + // Map the output result of the input image filter to the input usage requested for this filter + return static_cast>(std::move(result)); +} +// Instantiate filterInput() for kInput, kInput0, and kInput1. This does not provide a definition +// for kOutput, which should never be used anyways, and this way the linker will fail for us then. +template skif::FilterResult SkImageFilter_Base::filterInput(int, const skif::Context&) const; +template skif::FilterResult SkImageFilter_Base::filterInput(int, const skif::Context&) const; +template skif::FilterResult SkImageFilter_Base::filterInput(int, const skif::Context&) const; + +SkImageFilter_Base::Context SkImageFilter_Base::mapContext(const Context& ctx) const { + // We don't recurse through the child input filters because that happens automatically + // as part of the filterImage() evaluation. In this case, we want the bounds for the + // edge from this node to its children, without the effects of the child filters. + skif::LayerSpace childOutput = this->onGetInputLayerBounds( + ctx.mapping(), ctx.desiredOutput(), ctx.desiredOutput(), VisitChildren::kNo); + return ctx.withNewDesiredOutput(childOutput); +} + +#if SK_SUPPORT_GPU +sk_sp SkImageFilter_Base::DrawWithFP(GrRecordingContext* context, + std::unique_ptr fp, + const SkIRect& bounds, + SkColorType colorType, + const SkColorSpace* colorSpace, + GrProtected isProtected) { + GrPaint paint; + paint.addColorFragmentProcessor(std::move(fp)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + auto renderTargetContext = + context->priv().makeDeferredRenderTargetContext(SkBackingFit::kApprox, + bounds.width(), + bounds.height(), + SkColorTypeToGrColorType(colorType), + sk_ref_sp(colorSpace), + 1, + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + isProtected); + if (!renderTargetContext) { + return nullptr; + } + + SkIRect dstIRect = SkIRect::MakeWH(bounds.width(), bounds.height()); + SkRect srcRect = SkRect::Make(bounds); + SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); + GrFixedClip clip(dstIRect); + renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect, + srcRect); + + return SkSpecialImage::MakeDeferredFromGpu( + context, dstIRect, kNeedNewImageUniqueID_SpecialImage, + renderTargetContext->asTextureProxyRef(), renderTargetContext->colorInfo().colorType(), + renderTargetContext->colorInfo().refColorSpace()); +} + +sk_sp SkImageFilter_Base::ImageToColorSpace(SkSpecialImage* src, + SkColorType colorType, + SkColorSpace* colorSpace) { + // There are several conditions that determine if we actually need to convert the source to the + // destination's color space. Rather than duplicate that logic here, just try to make an xform + // object. If that produces something, then both are tagged, and the source is in a different + // gamut than the dest. There is some overhead to making the xform, but those are cached, and + // if we get one back, that means we're about to use it during the conversion anyway. + auto colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(), src->alphaType(), + colorSpace, kPremul_SkAlphaType); + + if (!colorSpaceXform) { + // No xform needed, just return the original image + return sk_ref_sp(src); + } + + sk_sp surf(src->makeSurface(colorType, colorSpace, + SkISize::Make(src->width(), src->height()))); + if (!surf) { + return sk_ref_sp(src); + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + SkPaint p; + p.setBlendMode(SkBlendMode::kSrc); + src->draw(canvas, 0, 0, &p); + return surf->makeImageSnapshot(); +} +#endif + +// In repeat mode, when we are going to sample off one edge of the srcBounds we require the +// opposite side be preserved. +SkIRect SkImageFilter_Base::DetermineRepeatedSrcBound(const SkIRect& srcBounds, + const SkIVector& filterOffset, + const SkISize& filterSize, + const SkIRect& originalSrcBounds) { + SkIRect tmp = srcBounds; + tmp.adjust(-filterOffset.fX, -filterOffset.fY, + filterSize.fWidth - filterOffset.fX, filterSize.fHeight - filterOffset.fY); + + if (tmp.fLeft < originalSrcBounds.fLeft || tmp.fRight > originalSrcBounds.fRight) { + tmp.fLeft = originalSrcBounds.fLeft; + tmp.fRight = originalSrcBounds.fRight; + } + if (tmp.fTop < originalSrcBounds.fTop || tmp.fBottom > originalSrcBounds.fBottom) { + tmp.fTop = originalSrcBounds.fTop; + tmp.fBottom = originalSrcBounds.fBottom; + } + + return tmp; +} + +void SkImageFilter_Base::PurgeCache() { + SkImageFilterCache::Get()->purge(); +} + +static sk_sp apply_ctm_to_filter(sk_sp input, const SkMatrix& ctm, + SkMatrix* remainder) { + if (ctm.isScaleTranslate() || as_IFB(input)->canHandleComplexCTM()) { + // The filter supports the CTM, so leave it as-is and 'remainder' stores the whole CTM + *remainder = ctm; + return input; + } + + // We have a complex CTM and a filter that can't support them, so it needs to use the matrix + // transform filter that resamples the image contents. Decompose the simple portion of the ctm + // into 'remainder' + SkMatrix ctmToEmbed; + SkSize scale; + if (ctm.decomposeScale(&scale, &ctmToEmbed)) { + // decomposeScale splits ctm into scale * ctmToEmbed, so bake ctmToEmbed into DAG + // with a matrix filter and return scale as the remaining matrix for the real CTM. + remainder->setScale(scale.fWidth, scale.fHeight); + + // ctmToEmbed is passed to SkMatrixImageFilter, which performs its transforms as if it were + // a pre-transformation before applying the image-filter context's CTM. In this case, we + // need ctmToEmbed to be a post-transformation (i.e. after the scale matrix since + // decomposeScale produces ctm = ctmToEmbed * scale). Giving scale^-1 * ctmToEmbed * scale + // to the matrix filter achieves this effect. + // TODO (michaelludwig) - When the original root node of a filter can be drawn directly to a + // device using ctmToEmbed, this abuse of SkMatrixImageFilter can go away. + ctmToEmbed.preScale(scale.fWidth, scale.fHeight); + ctmToEmbed.postScale(1.f / scale.fWidth, 1.f / scale.fHeight); + } else { + // Unable to decompose + // FIXME Ideally we'd embed the entire CTM as part of the matrix image filter, but + // the device <-> src bounds calculations for filters are very brittle under perspective, + // and can easily run into precision issues (wrong bounds that clip), or performance issues + // (producing large source-space images where 80% of the image is compressed into a few + // device pixels). A longer term solution for perspective-space image filtering is needed + // see skbug.com/9074 + if (ctm.hasPerspective()) { + *remainder = ctm; + return input; + } + + ctmToEmbed = ctm; + remainder->setIdentity(); + } + + return SkMatrixImageFilter::Make(ctmToEmbed, kLow_SkFilterQuality, input); +} + +sk_sp SkImageFilter_Base::applyCTM(const SkMatrix& ctm, SkMatrix* remainder) const { + return apply_ctm_to_filter(this->refMe(), ctm, remainder); +} diff --git a/gfx/skia/skia/src/core/SkImageFilterCache.cpp b/gfx/skia/skia/src/core/SkImageFilterCache.cpp new file mode 100644 index 0000000000..c92338b457 --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageFilterCache.cpp @@ -0,0 +1,170 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkImageFilterCache.h" + +#include + +#include "include/core/SkImageFilter.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkMutex.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTHash.h" +#include "src/core/SkOpts.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkTDynamicHash.h" +#include "src/core/SkTInternalLList.h" + +#ifdef SK_BUILD_FOR_IOS + enum { kDefaultCacheSize = 2 * 1024 * 1024 }; +#else + enum { kDefaultCacheSize = 128 * 1024 * 1024 }; +#endif + +namespace { + +class CacheImpl : public SkImageFilterCache { +public: + typedef SkImageFilterCacheKey Key; + CacheImpl(size_t maxBytes) : fMaxBytes(maxBytes), fCurrentBytes(0) { } + ~CacheImpl() override { + SkTDynamicHash::Iter iter(&fLookup); + + while (!iter.done()) { + Value* v = &*iter; + ++iter; + delete v; + } + } + struct Value { + Value(const Key& key, const skif::FilterResult& image, + const SkImageFilter* filter) + : fKey(key), fImage(image), fFilter(filter) {} + + Key fKey; + skif::FilterResult fImage; + const SkImageFilter* fFilter; + static const Key& GetKey(const Value& v) { + return v.fKey; + } + static uint32_t Hash(const Key& key) { + return SkOpts::hash(reinterpret_cast(&key), sizeof(Key)); + } + SK_DECLARE_INTERNAL_LLIST_INTERFACE(Value); + }; + + bool get(const Key& key, skif::FilterResult* result) const override { + SkASSERT(result); + + SkAutoMutexExclusive mutex(fMutex); + if (Value* v = fLookup.find(key)) { + if (v != fLRU.head()) { + fLRU.remove(v); + fLRU.addToHead(v); + } + + *result = v->fImage; + return true; + } + return false; + } + + void set(const Key& key, const SkImageFilter* filter, + const skif::FilterResult& result) override { + SkAutoMutexExclusive mutex(fMutex); + if (Value* v = fLookup.find(key)) { + this->removeInternal(v); + } + Value* v = new Value(key, result, filter); + fLookup.add(v); + fLRU.addToHead(v); + fCurrentBytes += result.image() ? result.image()->getSize() : 0; + if (auto* values = fImageFilterValues.find(filter)) { + values->push_back(v); + } else { + fImageFilterValues.set(filter, {v}); + } + + while (fCurrentBytes > fMaxBytes) { + Value* tail = fLRU.tail(); + SkASSERT(tail); + if (tail == v) { + break; + } + this->removeInternal(tail); + } + } + + void purge() override { + SkAutoMutexExclusive mutex(fMutex); + while (fCurrentBytes > 0) { + Value* tail = fLRU.tail(); + SkASSERT(tail); + this->removeInternal(tail); + } + } + + void purgeByImageFilter(const SkImageFilter* filter) override { + SkAutoMutexExclusive mutex(fMutex); + auto* values = fImageFilterValues.find(filter); + if (!values) { + return; + } + for (Value* v : *values) { + // We set the filter to be null so that removeInternal() won't delete from values while + // we're iterating over it. + v->fFilter = nullptr; + this->removeInternal(v); + } + fImageFilterValues.remove(filter); + } + + SkDEBUGCODE(int count() const override { return fLookup.count(); }) +private: + void removeInternal(Value* v) { + if (v->fFilter) { + if (auto* values = fImageFilterValues.find(v->fFilter)) { + if (values->size() == 1 && (*values)[0] == v) { + fImageFilterValues.remove(v->fFilter); + } else { + for (auto it = values->begin(); it != values->end(); ++it) { + if (*it == v) { + values->erase(it); + break; + } + } + } + } + } + fCurrentBytes -= v->fImage.image() ? v->fImage.image()->getSize() : 0; + fLRU.remove(v); + fLookup.remove(v->fKey); + delete v; + } +private: + SkTDynamicHash fLookup; + mutable SkTInternalLList fLRU; + // Value* always points to an item in fLookup. + SkTHashMap> fImageFilterValues; + size_t fMaxBytes; + size_t fCurrentBytes; + mutable SkMutex fMutex; +}; + +} // namespace + +SkImageFilterCache* SkImageFilterCache::Create(size_t maxBytes) { + return new CacheImpl(maxBytes); +} + +SkImageFilterCache* SkImageFilterCache::Get() { + static SkOnce once; + static SkImageFilterCache* cache; + + once([]{ cache = SkImageFilterCache::Create(kDefaultCacheSize); }); + return cache; +} diff --git a/gfx/skia/skia/src/core/SkImageFilterCache.h b/gfx/skia/skia/src/core/SkImageFilterCache.h new file mode 100644 index 0000000000..b82f4daf29 --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageFilterCache.h @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageFilterCache_DEFINED +#define SkImageFilterCache_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkRefCnt.h" +#include "src/core/SkImageFilterTypes.h" + +struct SkIPoint; +class SkImageFilter; + +struct SkImageFilterCacheKey { + SkImageFilterCacheKey(const uint32_t uniqueID, const SkMatrix& matrix, + const SkIRect& clipBounds, uint32_t srcGenID, const SkIRect& srcSubset) + : fUniqueID(uniqueID) + , fMatrix(matrix) + , fClipBounds(clipBounds) + , fSrcGenID(srcGenID) + , fSrcSubset(srcSubset) { + // Assert that Key is tightly-packed, since it is hashed. + static_assert(sizeof(SkImageFilterCacheKey) == sizeof(uint32_t) + sizeof(SkMatrix) + + sizeof(SkIRect) + sizeof(uint32_t) + 4 * sizeof(int32_t), + "image_filter_key_tight_packing"); + fMatrix.getType(); // force initialization of type, so hashes match + SkASSERT(fMatrix.isFinite()); // otherwise we can't rely on == self when comparing keys + } + + uint32_t fUniqueID; + SkMatrix fMatrix; + SkIRect fClipBounds; + uint32_t fSrcGenID; + SkIRect fSrcSubset; + + bool operator==(const SkImageFilterCacheKey& other) const { + return fUniqueID == other.fUniqueID && + fMatrix == other.fMatrix && + fClipBounds == other.fClipBounds && + fSrcGenID == other.fSrcGenID && + fSrcSubset == other.fSrcSubset; + } +}; + +// This cache maps from (filter's unique ID + CTM + clipBounds + src bitmap generation ID) to result +// NOTE: this is the _specific_ unique ID of the image filter, so refiltering the same image with a +// copy of the image filter (with exactly the same parameters) will not yield a cache hit. +class SkImageFilterCache : public SkRefCnt { +public: + SK_USE_FLUENT_IMAGE_FILTER_TYPES_IN_CLASS + + enum { kDefaultTransientSize = 32 * 1024 * 1024 }; + + virtual ~SkImageFilterCache() {} + static SkImageFilterCache* Create(size_t maxBytes); + static SkImageFilterCache* Get(); + + // Returns true on cache hit and updates 'result' to be the cached result. Returns false when + // not in the cache, in which case 'result' is not modified. + virtual bool get(const SkImageFilterCacheKey& key, + skif::FilterResult* result) const = 0; + // 'filter' is included in the caching to allow the purging of all of an image filter's cached + // results when it is destroyed. + virtual void set(const SkImageFilterCacheKey& key, const SkImageFilter* filter, + const skif::FilterResult& result) = 0; + virtual void purge() = 0; + virtual void purgeByImageFilter(const SkImageFilter*) = 0; + SkDEBUGCODE(virtual int count() const = 0;) +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkImageFilterTypes.cpp b/gfx/skia/skia/src/core/SkImageFilterTypes.cpp new file mode 100644 index 0000000000..d372fbf0b6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageFilterTypes.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkImageFilterTypes.h" +#include "src/core/SkImageFilter_Base.h" + +// Both [I]Vectors and Sk[I]Sizes are transformed as non-positioned values, i.e. go through +// mapVectors() not mapPoints(). +static SkIVector map_as_vector(int32_t x, int32_t y, const SkMatrix& matrix) { + SkVector v = SkVector::Make(SkIntToScalar(x), SkIntToScalar(y)); + matrix.mapVectors(&v, 1); + return SkIVector::Make(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY)); +} + +static SkVector map_as_vector(SkScalar x, SkScalar y, const SkMatrix& matrix) { + SkVector v = SkVector::Make(x, y); + matrix.mapVectors(&v, 1); + return v; +} + +namespace skif { + +Mapping Mapping::Make(const SkMatrix& ctm, const SkImageFilter* filter) { + SkMatrix remainder, layer; + SkSize scale; + if (ctm.isScaleTranslate() || as_IFB(filter)->canHandleComplexCTM()) { + // It doesn't matter what type of matrix ctm is, we can have layer space be equivalent to + // device space. + remainder = SkMatrix::I(); + layer = ctm; + } else if (ctm.decomposeScale(&scale, &remainder)) { + // TODO (michaelludwig) - Should maybe strip out any fractional part of the translation in + // 'ctm' so that can be incorporated during regular drawing, instead of by resampling the + // filtered image. + layer = SkMatrix::MakeScale(scale.fWidth, scale.fHeight); + } else { + // Perspective + // TODO (michaelludwig) - Should investigate choosing a scale factor for the layer matrix + // that minimizes the aliasing in the final draw. + remainder = ctm; + layer = SkMatrix::I(); + } + return Mapping(remainder, layer); +} + +// Instantiate map specializations for the 6 geometric types used during filtering +template<> +SkIRect Mapping::map(const SkIRect& geom, const SkMatrix& matrix) { + return matrix.mapRect(SkRect::Make(geom)).roundOut(); +} + +template<> +SkRect Mapping::map(const SkRect& geom, const SkMatrix& matrix) { + return matrix.mapRect(geom); +} + +template<> +SkIPoint Mapping::map(const SkIPoint& geom, const SkMatrix& matrix) { + SkPoint p = SkPoint::Make(SkIntToScalar(geom.fX), SkIntToScalar(geom.fY)); + matrix.mapPoints(&p, 1); + return SkIPoint::Make(SkScalarRoundToInt(p.fX), SkScalarRoundToInt(p.fY)); +} + +template<> +SkPoint Mapping::map(const SkPoint& geom, const SkMatrix& matrix) { + SkPoint p; + matrix.mapPoints(&p, &geom, 1); + return p; +} + +template<> +IVector Mapping::map(const IVector& geom, const SkMatrix& matrix) { + return IVector(map_as_vector(geom.fX, geom.fY, matrix)); +} + +template<> +Vector Mapping::map(const Vector& geom, const SkMatrix& matrix) { + return Vector(map_as_vector(geom.fX, geom.fY, matrix)); +} + +template<> +SkISize Mapping::map(const SkISize& geom, const SkMatrix& matrix) { + SkIVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix); + return SkISize::Make(v.fX, v.fY); +} + +template<> +SkSize Mapping::map(const SkSize& geom, const SkMatrix& matrix) { + SkVector v = map_as_vector(geom.fWidth, geom.fHeight, matrix); + return SkSize::Make(v.fX, v.fY); +} + +} // end namespace skif diff --git a/gfx/skia/skia/src/core/SkImageFilterTypes.h b/gfx/skia/skia/src/core/SkImageFilterTypes.h new file mode 100644 index 0000000000..918ec7f54b --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageFilterTypes.h @@ -0,0 +1,702 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageFilterTypes_DEFINED +#define SkImageFilterTypes_DEFINED + +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" + +class GrRecordingContext; +class SkImageFilter; +class SkImageFilterCache; +class SkSpecialSurface; +class SkSurfaceProps; + +// The skif (SKI[mage]F[ilter]) namespace contains types that are used for filter implementations. +// The defined types come in two groups: users of internal Skia types, and templates to help with +// readability. Image filters cannot be implemented without access to key internal types, such as +// SkSpecialImage. It is possible to avoid the use of the readability templates, although they are +// strongly encouraged. +namespace skif { + +// skif::IVector and skif::Vector represent plain-old-data types for storing direction vectors, so +// that the coordinate-space templating system defined below can have a separate type id for +// directions vs. points, and specialize appropriately. As such, all operations with direction +// vectors are defined on the LayerSpace specialization, since that is the intended point of use. +struct IVector { + int32_t fX; + int32_t fY; + + IVector() = default; + IVector(int32_t x, int32_t y) : fX(x), fY(y) {} + explicit IVector(const SkIVector& v) : fX(v.fX), fY(v.fY) {} +}; + +struct Vector { + SkScalar fX; + SkScalar fY; + + Vector() = default; + Vector(SkScalar x, SkScalar y) : fX(x), fY(y) {} + explicit Vector(const SkVector& v) : fX(v.fX), fY(v.fY) {} +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Coordinate Space Tagging +// - In order to enforce correct coordinate spaces in image filter implementations and use, +// geometry is wrapped by templated structs to declare in the type system what coordinate space +// the coordinates are defined in. +// - Currently there is ParameterSpace and DeviceSpace that are data-only wrappers around +// coordinates, and the primary LayerSpace that provides all operative functionality for image +// filters. It is intended that all logic about image bounds and access be conducted in the shared +// layer space. +// - The LayerSpace struct has type-safe specializations for SkIRect, SkRect, SkIPoint, SkPoint, +// skif::IVector (to distinguish SkIVector from SkIPoint), skif::Vector, SkISize, and SkSize. +// - A Mapping object provides type safe coordinate conversions between these spaces, and +// automatically does the "right thing" for each geometric type. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// ParameterSpace is a data-only wrapper around Skia's geometric types such as SkIPoint, and SkRect. +// Parameter space is the same as the local coordinate space of an SkShader, or the coordinates +// passed into SkCanvas::drawX calls, but "local" is avoided due to the alliteration with layer +// space. SkImageFilters are defined in terms of ParameterSpace geometry and must use the Mapping +// on Context to transform the parameters into LayerSpace to evaluate the filter in the shared +// coordinate space of the entire filter DAG. +// +// A value of ParameterSpace implies that its wrapped SkIRect is defined in the local +// parameter space. +template +class ParameterSpace { +public: + explicit ParameterSpace(const T& data) : fData(data) {} + explicit ParameterSpace(T&& data) : fData(std::move(data)) {} + + explicit operator const T&() const { return fData; } + +private: + T fData; +}; + +// DeviceSpace is a data-only wrapper around Skia's geometric types. It is similar to +// 'ParameterSpace' except that it is used to represent geometry that has been transformed or +// defined in the root device space (i.e. the final pixels of drawn content). Much of what SkCanvas +// tracks, such as its clip bounds are defined in this space and DeviceSpace provides a +// type-enforced mechanism for the canvas to pass that information into the image filtering system, +// using the Mapping of the filtering context. +template +class DeviceSpace { +public: + explicit DeviceSpace(const T& data) : fData(data) {} + explicit DeviceSpace(T&& data) : fData(std::move(data)) {} + + explicit operator const T&() const { return fData; } + +private: + T fData; +}; + +// LayerSpace is a geometric wrapper that specifies the geometry is defined in the shared layer +// space where image filters are evaluated. For a given Context (and its Mapping), the image filter +// DAG operates in the same coordinate space. This space may be different from the local coordinate +// space that defined the image filter parameters (such as blur sigma), and it may be different +// from the total CTM of the SkCanvas. +// +// To encourage correct filter use and implementation, the bulk of filter logic should be performed +// in layer space (e.g. determining what portion of an input image to read, or what the output +// region is). LayerSpace specializations for the six common Skia math types (Sk[I]Rect, Sk[I]Point, +// and Sk[I]Size), and skif::[I]Vector (to allow vectors to be specialized separately from points)) +// are provided that mimic their APIs but preserve the coordinate space and enforce type semantics. +template +class LayerSpace {}; + +// Layer-space specialization for integerized direction vectors. +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const IVector& geometry) : fData(geometry) {} + explicit LayerSpace(IVector&& geometry) : fData(std::move(geometry)) {} + explicit operator const IVector&() const { return fData; } + + explicit operator SkIVector() const { return SkIVector::Make(fData.fX, fData.fY); } + + int32_t x() const { return fData.fX; } + int32_t y() const { return fData.fY; } + + LayerSpace operator-() const { return LayerSpace({-fData.fX, -fData.fY}); } + + LayerSpace operator+(const LayerSpace& v) const { + LayerSpace sum = *this; + sum += v; + return sum; + } + LayerSpace operator-(const LayerSpace& v) const { + LayerSpace diff = *this; + diff -= v; + return diff; + } + + void operator+=(const LayerSpace& v) { + fData.fX += v.fData.fX; + fData.fY += v.fData.fY; + } + void operator-=(const LayerSpace& v) { + fData.fX -= v.fData.fX; + fData.fY -= v.fData.fY; + } + +private: + IVector fData; +}; + +// Layer-space specialization for floating point direction vectors. +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const Vector& geometry) : fData(geometry) {} + explicit LayerSpace(Vector&& geometry) : fData(std::move(geometry)) {} + explicit operator const Vector&() const { return fData; } + + explicit operator SkVector() const { return SkVector::Make(fData.fX, fData.fY); } + + SkScalar x() const { return fData.fX; } + SkScalar y() const { return fData.fY; } + + SkScalar length() const { return SkVector::Length(fData.fX, fData.fY); } + + LayerSpace operator-() const { return LayerSpace({-fData.fX, -fData.fY}); } + + LayerSpace operator*(SkScalar s) const { + LayerSpace scaled = *this; + scaled *= s; + return scaled; + } + + LayerSpace operator+(const LayerSpace& v) const { + LayerSpace sum = *this; + sum += v; + return sum; + } + LayerSpace operator-(const LayerSpace& v) const { + LayerSpace diff = *this; + diff -= v; + return diff; + } + + void operator*=(SkScalar s) { + fData.fX *= s; + fData.fY *= s; + } + void operator+=(const LayerSpace& v) { + fData.fX += v.fData.fX; + fData.fY += v.fData.fY; + } + void operator-=(const LayerSpace& v) { + fData.fX -= v.fData.fX; + fData.fY -= v.fData.fY; + } + + friend LayerSpace operator*(SkScalar s, const LayerSpace& b) { + return b * s; + } + +private: + Vector fData; +}; + +// Layer-space specialization for integer 2D coordinates (treated as positions, not directions). +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const SkIPoint& geometry) : fData(geometry) {} + explicit LayerSpace(SkIPoint&& geometry) : fData(std::move(geometry)) {} + explicit operator const SkIPoint&() const { return fData; } + + // Parrot the SkIPoint API while preserving coordinate space. + int32_t x() const { return fData.fX; } + int32_t y() const { return fData.fY; } + + // Offsetting by direction vectors produce more points + LayerSpace operator+(const LayerSpace& v) { + return LayerSpace(fData + SkIVector(v)); + } + LayerSpace operator-(const LayerSpace& v) { + return LayerSpace(fData - SkIVector(v)); + } + + void operator+=(const LayerSpace& v) { + fData += SkIVector(v); + } + void operator-=(const LayerSpace& v) { + fData -= SkIVector(v); + } + + // Subtracting another point makes a direction between them + LayerSpace operator-(const LayerSpace& p) { + return LayerSpace(IVector(fData - p.fData)); + } + +private: + SkIPoint fData; +}; + +// Layer-space specialization for floating point 2D coordinates (treated as positions) +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const SkPoint& geometry) : fData(geometry) {} + explicit LayerSpace(SkPoint&& geometry) : fData(std::move(geometry)) {} + explicit operator const SkPoint&() const { return fData; } + + // Parrot the SkPoint API while preserving coordinate space. + SkScalar x() const { return fData.fX; } + SkScalar y() const { return fData.fY; } + + SkScalar distanceToOrigin() const { return fData.distanceToOrigin(); } + + // Offsetting by direction vectors produce more points + LayerSpace operator+(const LayerSpace& v) { + return LayerSpace(fData + SkVector(v)); + } + LayerSpace operator-(const LayerSpace& v) { + return LayerSpace(fData - SkVector(v)); + } + + void operator+=(const LayerSpace& v) { + fData += SkVector(v); + } + void operator-=(const LayerSpace& v) { + fData -= SkVector(v); + } + + // Subtracting another point makes a direction between them + LayerSpace operator-(const LayerSpace& p) { + return LayerSpace(Vector(fData - p.fData)); + } + +private: + SkPoint fData; +}; + +// Layer-space specialization for integer dimensions +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const SkISize& geometry) : fData(geometry) {} + explicit LayerSpace(SkISize&& geometry) : fData(std::move(geometry)) {} + explicit operator const SkISize&() const { return fData; } + + int32_t width() const { return fData.width(); } + int32_t height() const { return fData.height(); } + + bool isEmpty() const { return fData.isEmpty(); } + +private: + SkISize fData; +}; + +// Layer-space specialization for floating point dimensions +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const SkSize& geometry) : fData(geometry) {} + explicit LayerSpace(SkSize&& geometry) : fData(std::move(geometry)) {} + explicit operator const SkSize&() const { return fData; } + + SkScalar width() const { return fData.width(); } + SkScalar height() const { return fData.height(); } + + bool isEmpty() const { return fData.isEmpty(); } + bool isZero() const { return fData.isZero(); } + + LayerSpace round() const { return LayerSpace(fData.toRound()); } + LayerSpace ceil() const { return LayerSpace(fData.toCeil()); } + LayerSpace floor() const { return LayerSpace(fData.toFloor()); } + +private: + SkSize fData; +}; + +// Layer-space specialization for axis-aligned integer bounding boxes. +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const SkIRect& geometry) : fData(geometry) {} + explicit LayerSpace(SkIRect&& geometry) : fData(std::move(geometry)) {} + explicit operator const SkIRect&() const { return fData; } + + // Parrot the SkIRect API while preserving coord space + int32_t left() const { return fData.fLeft; } + int32_t top() const { return fData.fTop; } + int32_t right() const { return fData.fRight; } + int32_t bottom() const { return fData.fBottom; } + + int32_t width() const { return fData.width(); } + int32_t height() const { return fData.height(); } + + LayerSpace topLeft() const { return LayerSpace(fData.topLeft()); } + LayerSpace size() const { return LayerSpace(fData.size()); } + + bool intersect(const LayerSpace& r) { return fData.intersect(r.fData); } + void join(const LayerSpace& r) { fData.join(r.fData); } + void offset(const LayerSpace& v) { fData.offset(SkIVector(v)); } + void outset(const LayerSpace& delta) { fData.outset(delta.width(), delta.height()); } + +private: + SkIRect fData; +}; + +// Layer-space specialization for axis-aligned float bounding boxes. +template<> +class LayerSpace { +public: + LayerSpace() = default; + explicit LayerSpace(const SkRect& geometry) : fData(geometry) {} + explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {} + explicit operator const SkRect&() const { return fData; } + + // Parrot the SkRect API while preserving coord space and usage + SkScalar left() const { return fData.fLeft; } + SkScalar top() const { return fData.fTop; } + SkScalar right() const { return fData.fRight; } + SkScalar bottom() const { return fData.fBottom; } + + SkScalar width() const { return fData.width(); } + SkScalar height() const { return fData.height(); } + + LayerSpace topLeft() const { + return LayerSpace(SkPoint::Make(fData.fLeft, fData.fTop)); + } + LayerSpace size() const { + return LayerSpace(SkSize::Make(fData.width(), fData.height())); + } + LayerSpace roundOut() const { return LayerSpace(fData.roundOut()); } + + bool intersect(const LayerSpace& r) { return fData.intersect(r.fData); } + void join(const LayerSpace& r) { fData.join(r.fData); } + void offset(const LayerSpace& v) { fData.offset(SkVector(v)); } + void outset(const LayerSpace& delta) { fData.outset(delta.width(), delta.height()); } + +private: + SkRect fData; +}; + +// Mapping is the primary definition of the shared layer space used when evaluating an image filter +// DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix +// (that filters use to map their parameters to the layer space), and the layer-to-device matrix +// (that canvas uses to map the output layer-space image into its root device space). Mapping +// defines functions to transform ParameterSpace and DeviceSpace types to and from their LayerSpace +// variants, which can then be used and reasoned about by SkImageFilter implementations. +class Mapping { +public: + // This constructor allows the decomposition to be explicitly provided + Mapping(const SkMatrix& layerToDev, const SkMatrix& paramToLayer) + : fLayerToDevMatrix(layerToDev) + , fParamToLayerMatrix(paramToLayer) {} + + // Make the default decomposition Mapping, given the total CTM and the root image filter. + static Mapping Make(const SkMatrix& ctm, const SkImageFilter* filter); + + // Return a new Mapping object whose parameter-to-layer matrix is equal to this->layerMatrix() * + // local, but both share the same layer-to-device matrix. + Mapping concatLocal(const SkMatrix& local) const { + return Mapping(fLayerToDevMatrix, SkMatrix::Concat(fParamToLayerMatrix, local)); + } + + const SkMatrix& deviceMatrix() const { return fLayerToDevMatrix; } + const SkMatrix& layerMatrix() const { return fParamToLayerMatrix; } + SkMatrix totalMatrix() const { + return SkMatrix::Concat(fLayerToDevMatrix, fParamToLayerMatrix); + } + + template + LayerSpace paramToLayer(const ParameterSpace& paramGeometry) const { + return LayerSpace(map(static_cast(paramGeometry), fParamToLayerMatrix)); + } + + template + LayerSpace deviceToLayer(const DeviceSpace& devGeometry) const { + // The mapping from device space to layer space is defined by the inverse of the + // layer-to-device matrix + SkMatrix devToLayerMatrix; + if (!fLayerToDevMatrix.invert(&devToLayerMatrix)) { + // Punt and just pass through the geometry unmodified... + return LayerSpace(static_cast(devGeometry)); + } else { + return LayerSpace(map(static_cast(devGeometry), devToLayerMatrix)); + } + } + + template + DeviceSpace layerToDevice(const LayerSpace& layerGeometry) const { + return DeviceSpace(map(static_cast(layerGeometry), fLayerToDevMatrix)); + } + +private: + // The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the + // param-to-layer matrix to define the layer-space coordinate system. Depending on how it's + // decomposed, either the layer matrix or the device matrix could be the identity matrix (but + // sometimes neither). + SkMatrix fLayerToDevMatrix; + SkMatrix fParamToLayerMatrix; + + // Actual geometric mapping operations that work on coordinates and matrices w/o the type + // safety of the coordinate space wrappers (hence these are private). + template + static T map(const T& geom, const SkMatrix& matrix); +}; + +// Usage is a template tag to improve the readability of filter implementations. It is attached to +// images and geometry to group a collection of related variables and ensure that moving from one +// use case to another is explicit. +// NOTE: This can be aliased as 'For' when using the fluent type names. +// TODO (michaelludwig) - If the primary motivation for Usage--enforcing layer to image space +// transformations safely when multiple images are involved--can be handled entirely by helper +// functions on FilterResult, then Usage can go away and FilterResult will not need to be templated +enum class Usage { + // Designates the semantic purpose of the bounds, coordinate, or image as being an input + // to the image filter calculations. When this usage is used, it denotes a generic input, + // such as the current input in a dynamic loop, or some aggregate of all inputs. Because + // most image filters consume 1 or 2 filters only, the related kInput0 and kInput1 are + // also defined. + kInput, + // A more specific version of kInput, this marks the tagged variable as attached to the + // image filter of SkImageFilter_Base::getInput(0). + kInput0, + // A more specific version of kInput, this marks the tagged variable as attached to the + // image filter of SkImageFilter_Base::getInput(1). + kInput1, + // Designates the purpose of the bounds, coordinate, or image as being the output of the + // current image filter calculation. There is only ever one output for an image filter. + kOutput, +}; + +// Convenience macros to add 'using' declarations that rename the above enums to provide a more +// fluent and readable API. This should only be used in a private or local scope to prevent leakage +// of the names. Use the IN_CLASS variant at the start of a class declaration in those scenarios. +// These macros enable the following simpler type names: +// skif::Image -> Image +#define SK_USE_FLUENT_IMAGE_FILTER_TYPES \ + using For = skif::Usage; + +#define SK_USE_FLUENT_IMAGE_FILTER_TYPES_IN_CLASS \ + protected: SK_USE_FLUENT_IMAGE_FILTER_TYPES public: + +// Wraps an SkSpecialImage and tags it with a corresponding usage, either as generic input (e.g. the +// source image), or a specific input image from a filter's connected inputs. It also includes the +// origin of the image in the layer space. This origin is used to draw the image in the correct +// location. The 'layerBounds' rectangle of the filtered image is the layer-space bounding box of +// the image. It has its top left corner at 'origin' and has the same dimensions as the underlying +// special image subset. Transforming 'layerBounds' by the Context's layer matrix and painting it +// with the subset rectangle will display the filtered results in the appropriate device-space +// region. +// +// When filter implementations are processing intermediate FilterResult results, it can be assumed +// that all FilterResult' layerBounds are in the same layer coordinate space defined by the shared +// skif::Context. +// +// NOTE: This is named FilterResult since most instances will represent the output of an image +// filter (even if that is then cast to be the input to the next filter). The main exception is the +// source input used when an input filter is null, but from a data-standpoint it is the same since +// it is equivalent to the result of an identity filter. +template +class FilterResult { +public: + FilterResult() : fImage(nullptr), fOrigin(SkIPoint::Make(0, 0)) {} + + FilterResult(sk_sp image, const LayerSpace& origin) + : fImage(std::move(image)) + , fOrigin(origin) {} + + // Allow explicit moves/copies in order to cast from one use type to another, except kInput0 + // and kInput1 can only be cast to kOutput (e.g. as part of a noop image filter). + template + explicit FilterResult(FilterResult&& image) + : fImage(std::move(image.fImage)) + , fOrigin(image.fOrigin) { + static_assert((kU != Usage::kInput) || (kI != Usage::kInput0 && kI != Usage::kInput1), + "kInput0 and kInput1 cannot be moved to more generic kInput usage."); + static_assert((kU != Usage::kInput0 && kU != Usage::kInput1) || + (kI == kU || kI == Usage::kInput || kI == Usage::kOutput), + "Can only move to specific input from the generic kInput or kOutput usage."); + } + + template + explicit FilterResult(const FilterResult& image) + : fImage(image.fImage) + , fOrigin(image.fOrigin) { + static_assert((kU != Usage::kInput) || (kI != Usage::kInput0 && kI != Usage::kInput1), + "kInput0 and kInput1 cannot be copied to more generic kInput usage."); + static_assert((kU != Usage::kInput0 && kU != Usage::kInput1) || + (kI == kU || kI == Usage::kInput || kI == Usage::kOutput), + "Can only copy to specific input from the generic kInput usage."); + } + + const SkSpecialImage* image() const { return fImage.get(); } + sk_sp refImage() const { return fImage; } + + // Get the layer-space bounds of the result. This will have the same dimensions as the + // image and its top left corner will be 'origin()'. + LayerSpace layerBounds() const { + return LayerSpace(SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), + fImage->width(), fImage->height())); + } + + // Get the layer-space coordinate of this image's top left pixel. + const LayerSpace& layerOrigin() const { return fOrigin; } + + // Extract image and origin, safely when the image is null. + // TODO (michaelludwig) - This is intended for convenience until all call sites of + // SkImageFilter_Base::filterImage() have been updated to work in the new type system + // (which comes later as SkDevice, SkCanvas, etc. need to be modified, and coordinate space + // tagging needs to be added). + sk_sp imageAndOffset(SkIPoint* offset) const { + if (fImage) { + *offset = SkIPoint(fOrigin); + return fImage; + } else { + *offset = {0, 0}; + return nullptr; + } + } + +private: + // Allow all FilterResult templates access to each others members + template + friend class FilterResult; + + sk_sp fImage; + LayerSpace fOrigin; +}; + +// The context contains all necessary information to describe how the image filter should be +// computed (i.e. the current layer matrix and clip), and the color information of the output of a +// filter DAG. For now, this is just the color space (of the original requesting device). This is +// used when constructing intermediate rendering surfaces, so that we ensure we land in a surface +// that's similar/compatible to the final consumer of the DAG's output. +class Context { +public: + SK_USE_FLUENT_IMAGE_FILTER_TYPES_IN_CLASS + + // Creates a context with the given layer matrix and destination clip, reading from 'source' + // with an origin of (0,0). + Context(const SkMatrix& layerMatrix, const SkIRect& clipBounds, SkImageFilterCache* cache, + SkColorType colorType, SkColorSpace* colorSpace, const SkSpecialImage* source) + : fMapping(SkMatrix::I(), layerMatrix) + , fDesiredOutput(clipBounds) + , fCache(cache) + , fColorType(colorType) + , fColorSpace(colorSpace) + , fSource(sk_ref_sp(source), LayerSpace({0, 0})) {} + + Context(const Mapping& mapping, const LayerSpace& desiredOutput, + SkImageFilterCache* cache, SkColorType colorType, SkColorSpace* colorSpace, + const FilterResult& source) + : fMapping(mapping) + , fDesiredOutput(desiredOutput) + , fCache(cache) + , fColorType(colorType) + , fColorSpace(colorSpace) + , fSource(source) {} + + // The mapping that defines the transformation from local parameter space of the filters to the + // layer space where the image filters are evaluated, as well as the remaining transformation + // from the layer space to the final device space. The layer space defined by the returned + // Mapping may be the same as the root device space, or be an intermediate space that is + // supported by the image filter DAG (depending on what it returns from canHandleComplexCTM()). + // If a node returns false from canHandleComplexCTM(), the layer matrix of the mapping will be + // at most a scale + translate, and the remaining matrix will be appropriately set to transform + // the layer space to the final device space (applied by the SkCanvas when filtering is + // finished). + const Mapping& mapping() const { return fMapping; } + // DEPRECATED: Use mapping() and its coordinate-space types instead + const SkMatrix& ctm() const { return fMapping.layerMatrix(); } + // The bounds, in the layer space, that the filtered image will be clipped to. The output + // from filterImage() must cover these clip bounds, except in areas where it will just be + // transparent black, in which case a smaller output image can be returned. + const LayerSpace& desiredOutput() const { return fDesiredOutput; } + // DEPRECATED: Use desiredOutput() instead + const SkIRect& clipBounds() const { return static_cast(fDesiredOutput); } + // The cache to use when recursing through the filter DAG, in order to avoid repeated + // calculations of the same image. + SkImageFilterCache* cache() const { return fCache; } + // The output device's color type, which can be used for intermediate images to be + // compatible with the eventual target of the filtered result. + SkColorType colorType() const { return fColorType; } +#if SK_SUPPORT_GPU + GrColorType grColorType() const { return SkColorTypeToGrColorType(fColorType); } +#endif + // The output device's color space, so intermediate images can match, and so filtering can + // be performed in the destination color space. + SkColorSpace* colorSpace() const { return fColorSpace; } + sk_sp refColorSpace() const { return sk_ref_sp(fColorSpace); } + // The default surface properties to use when making transient surfaces during filtering. + const SkSurfaceProps* surfaceProps() const { return &fSource.image()->props(); } + + // This is the image to use whenever an expected input filter has been set to null. In the + // majority of cases, this is the original source image for the image filter DAG so it comes + // from the SkDevice that holds either the saveLayer or the temporary rendered result. The + // exception is composing two image filters (via SkImageFilters::Compose), which must use + // the output of the inner DAG as the "source" for the outer DAG. + const FilterResult& source() const { return fSource; } + // DEPRECATED: Use source() instead to get both the image and its origin. + const SkSpecialImage* sourceImage() const { return fSource.image(); } + + // True if image filtering should occur on the GPU if possible. + bool gpuBacked() const { return fSource.image()->isTextureBacked(); } + // The recording context to use when computing the filter with the GPU. + GrRecordingContext* getContext() const { return fSource.image()->getContext(); } + + /** + * Since a context can be built directly, its constructor has no chance to "return null" if + * it's given invalid or unsupported inputs. Call this to know of the the context can be + * used. + * + * The SkImageFilterCache Key, for example, requires a finite ctm (no infinities or NaN), + * so that test is part of isValid. + */ + bool isValid() const { return fSource.image() != nullptr && fMapping.layerMatrix().isFinite(); } + + // Create a surface of the given size, that matches the context's color type and color space + // as closely as possible, and uses the same backend of the device that produced the source + // image. + sk_sp makeSurface(const SkISize& size, + const SkSurfaceProps* props = nullptr) const { + return fSource.image()->makeSurface(fColorType, fColorSpace, size, + kPremul_SkAlphaType, props); + } + + // Create a new context that matches this context, but with an overridden layer space. + Context withNewMapping(const Mapping& mapping) const { + return Context(mapping, fDesiredOutput, fCache, fColorType, fColorSpace, fSource); + } + // Create a new context that matches this context, but with an overridden desired output rect. + Context withNewDesiredOutput(const LayerSpace& desiredOutput) const { + return Context(fMapping, desiredOutput, fCache, fColorType, fColorSpace, fSource); + } + +private: + Mapping fMapping; + LayerSpace fDesiredOutput; + SkImageFilterCache* fCache; + SkColorType fColorType; + // The pointed-to object is owned by the device controlling the filter process, and our lifetime + // is bounded by the device, so this can be a bare pointer. + SkColorSpace* fColorSpace; + FilterResult fSource; +}; + +} // end namespace skif + +#endif // SkImageFilterTypes_DEFINED diff --git a/gfx/skia/skia/src/core/SkImageFilter_Base.h b/gfx/skia/skia/src/core/SkImageFilter_Base.h new file mode 100644 index 0000000000..5837e5e9f3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageFilter_Base.h @@ -0,0 +1,442 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageFilter_Base_DEFINED +#define SkImageFilter_Base_DEFINED + +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkImageInfo.h" +#include "include/private/SkTArray.h" + +#include "src/core/SkImageFilterTypes.h" + +class GrFragmentProcessor; +class GrRecordingContext; + +// True base class that all SkImageFilter implementations need to extend from. This provides the +// actual API surface that Skia will use to compute the filtered images. +class SkImageFilter_Base : public SkImageFilter { +public: + SK_USE_FLUENT_IMAGE_FILTER_TYPES_IN_CLASS + + // DEPRECATED - Use skif::Context directly. + using Context = skif::Context; + + /** + * Request a new filtered image to be created from the src image. The returned skif::Image + * provides both the pixel data and the origin point that it should be drawn at, relative to + * the layer space defined by the provided context. + * + * If the result image cannot be created, or the result would be transparent black, returns + * a skif::Image that has a null special image, in which its origin should be ignored. + * + * TODO: Right now the imagefilters sometimes return empty result bitmaps/ + * specialimages. That doesn't seem quite right. + */ + skif::FilterResult filterImage(const skif::Context& context) const; + + /** + * Calculate the smallest-possible required layer bounds that would provide sufficient + * information to correctly compute the image filter for every pixel in the desired output + * bounds. The 'desiredOutput' is intended to represent either the root render target bounds, + * or the device-space bounds of the current clip. If the bounds of the content that will be + * drawn into the layer is known, 'knownContentBounds' should be provided, since it can be + * used to restrict the size of the layer if the image filter DAG does not affect transparent + * black. + * + * The returned rect is in the layer space defined by 'mapping', so it directly represents + * the size and location of the SkDevice created to rasterize the content prior to invoking the + * image filter (assuming its CTM and basis matrix are configured to match 'mapping'). + * + * While this operation transforms an device-space output bounds to a layer-space input bounds, + * it is not necessarily the inverse of getOutputBounds(). For instance, a blur needs to have + * an outset margin when reading pixels at the edge (to satisfy its kernel), thus it expands + * its required input rect to include every pixel that contributes to the desired output rect. + + * @param mapping The coordinate space mapping that defines both the transformation + * between local and layer, and layer to root device space, that will be + * used when the filter is later invoked. + * @param desiredOutput The desired output boundary that needs to be covered by the filter's + * output (assuming that the filter is then invoked with a suitable input) + * @param knownContentBounds + * Optional, the known layer-space bounds of the non-transparent content + * that would be rasterized in the source input image. + * + * @return The layer-space bounding box to use for an SkDevice when drawing the source image. + */ + skif::LayerSpace getInputBounds( + const skif::Mapping& mapping, const skif::DeviceSpace& desiredOutput, + const skif::ParameterSpace* knownContentBounds) const; + + /** + * Calculate the device-space bounds of the output of this filter DAG, if it were to process + * an image layer covering the 'contentBounds'. The 'mapping' defines how the content will be + * transformed to layer space when it is drawn, and how the output filter image is then + * transformed to the final device space (i.e. it specifies the mapping between the root device + * space and the parameter space of the initially provided content). + * + * While this operation transforms a parameter-space input bounds to an device-space output + * bounds, it is not necessarily the inverse of getInputBounds(). For instance, a blur needs to + * have an outset margin when reading pixels at the edge (to satisfy its kernel), so it will + * generate a result larger than its input (so that the blur is visible) and, thus, expands its + * output to include every pixel that it will touch. + * + * @param mapping The coordinate space mapping that defines both the transformation + * between local and layer, and layer to root device space, that will be + * used when the filter is later invoked. + * @param contentBounds The local-space bounds of the non-transparent content that would be + * drawn into the source image prior to filtering with this DAG, i.e. + * the same as 'knownContentBounds' in getInputBounds(). + * + * @return The root device-space bounding box of the filtered image, were it applied to + * content contained by 'contentBounds' and then drawn with 'mapping' to the root + * device (w/o any additional clipping). + */ + skif::DeviceSpace getOutputBounds( + const skif::Mapping& mapping, const skif::ParameterSpace& contentBounds) const; + + /** + * Returns whether any edges of the crop rect have been set. The crop + * rect is set at construction time, and determines which pixels from the + * input image will be processed, and which pixels in the output image will be allowed. + * The size of the crop rect should be + * used as the size of the destination image. The origin of this rect + * should be used to offset access to the input images, and should also + * be added to the "offset" parameter in onFilterImage. + * + * DEPRECATED - Remove once cropping is handled by a separate filter + */ + bool cropRectIsSet() const { return fCropRect.flags() != 0x0; } + + // DEPRECATED - Remove once cropping is handled by a separate filter + CropRect getCropRect() const { return fCropRect; } + + // Expose isolated node bounds behavior for SampleImageFilterDAG and debugging + SkIRect filterNodeBounds(const SkIRect& srcRect, const SkMatrix& ctm, + MapDirection dir, const SkIRect* inputRect) const { + return this->onFilterNodeBounds(srcRect, ctm, dir, inputRect); + } + + /** + * ImageFilters can natively handle scaling and translate components in the CTM. Only some of + * them can handle affine (or more complex) matrices. This call returns true iff the filter + * and all of its (non-null) inputs can handle these more complex matrices. + */ + bool canHandleComplexCTM() const; + + /** + * Return an image filter representing this filter applied with the given ctm. This will modify + * the DAG as needed if this filter does not support complex CTMs and 'ctm' is not simple. The + * ctm matrix will be decomposed such that ctm = A*B; B will be incorporated directly into the + * DAG and A must be the ctm set on the context passed to filterImage(). 'remainder' will be set + * to A. + * + * If this filter supports complex ctms, or 'ctm' is not complex, then A = ctm and B = I. When + * the filter does not support complex ctms, and the ctm is complex, then A represents the + * extracted simple portion of the ctm, and the complex portion is baked into a new DAG using a + * matrix filter. + * + * This will never return null. + * + * DEPRECATED - Should draw the results of filterImage() directly with the remainder matrix. + */ + sk_sp applyCTM(const SkMatrix& ctm, SkMatrix* remainder) const; + + uint32_t uniqueID() const { return fUniqueID; } + +protected: + class Common { + public: + /** + * Attempt to unflatten the cropRect and the expected number of input filters. + * If any number of input filters is valid, pass -1. + * If this fails (i.e. corrupt buffer or contents) then return false and common will + * be left uninitialized. + * If this returns true, then inputCount() is the number of found input filters, each + * of which may be NULL or a valid imagefilter. + */ + bool unflatten(SkReadBuffer&, int expectedInputs); + + const CropRect& cropRect() const { return fCropRect; } + int inputCount() const { return fInputs.count(); } + sk_sp* inputs() { return fInputs.begin(); } + + sk_sp getInput(int index) { return fInputs[index]; } + + private: + CropRect fCropRect; + // most filters accept at most 2 input-filters + SkSTArray<2, sk_sp, true> fInputs; + }; + + // Whether or not to recurse to child input filters for certain operations that walk the DAG. + enum class VisitChildren : bool { + kNo = false, + kYes = true + }; + + SkImageFilter_Base(sk_sp const* inputs, int inputCount, + const CropRect* cropRect); + + ~SkImageFilter_Base() override; + + void flatten(SkWriteBuffer&) const override; + + // DEPRECATED - Use the private context-only variant + virtual sk_sp onFilterImage(const Context&, SkIPoint* offset) const = 0; + + // DEPRECATED - Override onGetOutputLayerBounds and onGetInputLayerBounds instead. The + // node-specific and aggregation functions are no longer separated in the current API. A helper + // function is provided to do the default recursion for the common filter case. + virtual SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const; + virtual SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const; + + // DEPRECRATED - Call the Context-only getInputFilteredImage() + sk_sp filterInput(int index, const Context& ctx, SkIPoint* offset) const { + return this->getInputFilteredImage(index, ctx).imageAndOffset(offset); + } + + // Helper function to visit each of this filter's child filters and call their + // onGetInputLayerBounds with the provided 'desiredOutput' and 'contentBounds'. Automatically + // handles null input filters. Returns the union of all of the children's input bounds. + skif::LayerSpace visitInputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& desiredOutput, + const skif::LayerSpace& contentBounds) const; + // Helper function to visit each of this filter's child filters and call their + // onGetOutputLayerBounds with the provided 'contentBounds'. Automatically handles null input + // filters. + skif::LayerSpace visitOutputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& contentBounds) const; + + // Helper function to help with recursing through the filter DAG. It invokes filter processing + // set to null, it returns the dynamic source image on the Context instead. + // + // Implementations must handle cases when the input filter was unable to compute an image and + // the returned skif::Image has a null SkSpecialImage. If the filter affect transparent black + // should explicitly handle nullptr results and press on. In the error case this behavior will + // produce a better result than nothing and is necessary for the clipped out case. + skif::FilterResult getInputFilteredImage(int index, + const skif::Context& context) const { + return this->filterInput(index, context); + } + // Convenience that calls filterInput with index = 0 and the most specific usage. + skif::FilterResult getInputFilteredImage0(const skif::Context& context) const { + return this->filterInput(0, context); + } + // Convenience that calls filterInput with index = 1 and the most specific usage. + skif::FilterResult getInputFilteredImage1(const skif::Context& context) const { + return this->filterInput(1, context); + } + + // DEPRECATED - Remove once cropping is handled by a separate filter + const CropRect* getCropRectIfSet() const { + return this->cropRectIsSet() ? &fCropRect : nullptr; + } + + /** Given a "srcBounds" rect, computes destination bounds for this filter. + * "dstBounds" are computed by transforming the crop rect by the context's + * CTM, applying it to the initial bounds, and intersecting the result with + * the context's clip bounds. "srcBounds" (if non-null) are computed by + * intersecting the initial bounds with "dstBounds", to ensure that we never + * sample outside of the crop rect (this restriction may be relaxed in the + * future). + * + * DEPRECATED - Remove once cropping is handled by a separate filter, although it may be + * necessary to provide a similar convenience function to compute the output bounds given the + * images returned by filterInput(). + */ + bool applyCropRect(const Context&, const SkIRect& srcBounds, SkIRect* dstBounds) const; + + /** A variant of the above call which takes the original source bitmap and + * source offset. If the resulting crop rect is not entirely contained by + * the source bitmap's bounds, it creates a new bitmap in "result" and + * pads the edges with transparent black. In that case, the srcOffset is + * modified to be the same as the bounds, since no further adjustment is + * needed by the caller. This version should only be used by filters + * which are not capable of processing a smaller source bitmap into a + * larger destination. + * + * DEPRECATED - Remove once cropping is handled by a separate filter. + */ + sk_sp applyCropRectAndPad(const Context&, SkSpecialImage* src, + SkIPoint* srcOffset, SkIRect* bounds) const; + + /** + * Creates a modified Context for use when recursing up the image filter DAG. + * The clip bounds are adjusted to accommodate any margins that this + * filter requires by calling this node's + * onFilterNodeBounds(..., kReverse_MapDirection). + */ + // TODO (michaelludwig) - I don't think this is necessary to keep as protected. Other than the + // real use case in recursing through the DAG for filterInput(), it feels wrong for blur and + // other filters to need to call it. + Context mapContext(const Context& ctx) const; + +#if SK_SUPPORT_GPU + static sk_sp DrawWithFP(GrRecordingContext* context, + std::unique_ptr fp, + const SkIRect& bounds, + SkColorType colorType, + const SkColorSpace* colorSpace, + GrProtected isProtected = GrProtected::kNo); + + /** + * Returns a version of the passed-in image (possibly the original), that is in a colorspace + * with the same gamut as the one from the OutputProperties. This allows filters that do many + * texture samples to guarantee that any color space conversion has happened before running. + */ + static sk_sp ImageToColorSpace(SkSpecialImage* src, + SkColorType colorType, + SkColorSpace* colorSpace); +#endif + + // If 'srcBounds' will sample outside the border of 'originalSrcBounds' (i.e., the sample + // will wrap around to the other side) we must preserve the far side of the src along that + // axis (e.g., if we will sample beyond the left edge of the src, the right side must be + // preserved for the repeat sampling to work). + // DEPRECATED - Remove once cropping is handled by a separate filter, that can also handle all + // tile modes (including repeat) properly + static SkIRect DetermineRepeatedSrcBound(const SkIRect& srcBounds, + const SkIVector& filterOffset, + const SkISize& filterSize, + const SkIRect& originalSrcBounds); + +private: + friend class SkImageFilter; + // For PurgeCache() + friend class SkGraphics; + + static void PurgeCache(); + + void init(sk_sp const* inputs, int inputCount, const CropRect* cropRect); + + // Configuration points for the filter implementation, marked private since they should not + // need to be invoked by the subclasses. These refer to the node's specific behavior and are + // not responsible for aggregating the behavior of the entire filter DAG. + + /** + * Return true (and returns a ref'd colorfilter) if this node in the DAG is just a colorfilter + * w/o CropRect constraints. + */ + virtual bool onIsColorFilterNode(SkColorFilter** /*filterPtr*/) const { return false; } + + /** + * Return true if this filter can map from its parameter space to a layer space described by an + * arbitrary transformation matrix. If this returns false, the filter only needs to worry about + * mapping from parameter to layer using a scale+translate matrix. + */ + virtual bool onCanHandleComplexCTM() const { return false; } + + /** + * Return true if this filter would transform transparent black pixels to a color other than + * transparent black. When false, optimizations can be taken to discard regions known to be + * transparent black and thus process fewer pixels. + */ + virtual bool affectsTransparentBlack() const { return false; } + + /** + * This is the virtual which should be overridden by the derived class to perform image + * filtering. Subclasses are responsible for recursing to their input filters, although the + * getFilteredInputX() functions are provided to handle all necessary details of this. If the + * filter has a fixed number of inputs, the getFilterInput0() and getFilteredInput1() functions + * ensure the returned filtered Images have the most specific input usage. + * + * If the image cannot be created (either because of an error or if the result would be empty + * because it was clipped out), this should return a filtered Image with a null SkSpecialImage. + * In these situations, callers that do not affect transparent black can end early, since the + * "transparent" implicit image would be unchanged. Callers that affect transparent black need + * to safely handle these null and empty images and return an image filling the context's clip + * bounds as if its input filtered image were transparent black. + */ + virtual skif::FilterResult onFilterImage(const skif::Context& context) const; + + /** + * Calculates the necessary input layer size in order for the final output of the filter to + * cover the desired output bounds. The provided 'desiredOutput' represents the requested + * input bounds for this node's parent filter node, i.e. this function answers "what does this + * node require for input in order to satisfy (as its own output), the input needs of its + * parent?". + * + * If 'recurse' is true, this function is responsible for recursing to its child image filters + * and accounting for what they require to meet this filter's input requirements. It is up to + * the filter to determine how to aggregate these inputs, but a helper function is provided for + * the common case where the final required layer size is the union of the child filters' + * required inputs, evaluated on what this filter requires for itself. 'recurse' is kNo + * when mapping Contexts while actually filtering images, since the child recursion is + * happening at a higher level. + * + * Unlike the public getInputBounds(), all internal bounds calculations are done in the shared + * layer space defined by 'mapping'. + * + * The default implementation assumes that current filter requires an input equal to + * 'desiredOutputBounds', and passes this down to its child filters, and returns the union of + * their required inputs. + */ + virtual skif::LayerSpace onGetInputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& desiredOutput, + const skif::LayerSpace& contentBounds, + VisitChildren recurse = VisitChildren::kYes) const; + + /** + * Calculates the output bounds that this filter node would touch when processing an input + * sized to 'contentBounds'. This function is responsible for recursing to its child image + * filters and accounting for what they output. It is up to the filter to determine how to + * aggregate the outputs of its children, but a helper function is provided for the common + * case where the filter output is the union of its child outputs. + * + * Unlike the public getOutputBounds(), all internal bounds calculations are done in the + * shared layer space defined by 'mapping'. + * + * The default implementation assumes that the output of this filter is equal to the union of + * the outputs of its child filters evaluated with 'contentBounds'. + */ + // TODO (michaelludwig) - When layerMatrix = I, this function could be used to implement + // onComputeFastBounds() instead of making filters implement the essentially the same calcs x2 + virtual skif::LayerSpace onGetOutputLayerBounds( + const skif::Mapping& mapping, const skif::LayerSpace& contentBounds) const; + + // The actual implementation of the protected getFilterInputX() functions, but don't expose the + // flexible templating to subclasses so it can't be abused. + template + skif::FilterResult filterInput(int index, const skif::Context& ctx) const; + + SkAutoSTArray<2, sk_sp> fInputs; + + bool fUsesSrcInput; + CropRect fCropRect; + uint32_t fUniqueID; // Globally unique + + typedef SkImageFilter INHERITED; +}; + +static inline SkImageFilter_Base* as_IFB(SkImageFilter* filter) { + return static_cast(filter); +} + +static inline SkImageFilter_Base* as_IFB(const sk_sp& filter) { + return static_cast(filter.get()); +} + +static inline const SkImageFilter_Base* as_IFB(const SkImageFilter* filter) { + return static_cast(filter); +} + +/** + * Helper to unflatten the common data, and return nullptr if we fail. + */ +#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount) \ + Common localVar; \ + do { \ + if (!localVar.unflatten(buffer, expectedCount)) { \ + return nullptr; \ + } \ + } while (0) + +#endif // SkImageFilter_Base_DEFINED diff --git a/gfx/skia/skia/src/core/SkImageGenerator.cpp b/gfx/skia/skia/src/core/SkImageGenerator.cpp new file mode 100644 index 0000000000..c51c1b0c56 --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageGenerator.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImage.h" +#include "include/core/SkImageGenerator.h" +#include "include/core/SkYUVAIndex.h" +#include "src/core/SkNextID.h" + +SkImageGenerator::SkImageGenerator(const SkImageInfo& info, uint32_t uniqueID) + : fInfo(info) + , fUniqueID(kNeedNewImageUniqueID == uniqueID ? SkNextID::ImageID() : uniqueID) +{} + +bool SkImageGenerator::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { + if (kUnknown_SkColorType == info.colorType()) { + return false; + } + if (nullptr == pixels) { + return false; + } + if (rowBytes < info.minRowBytes()) { + return false; + } + + Options defaultOpts; + return this->onGetPixels(info, pixels, rowBytes, defaultOpts); +} + +bool SkImageGenerator::queryYUVA8(SkYUVASizeInfo* sizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace) const { + SkASSERT(sizeInfo); + + return this->onQueryYUVA8(sizeInfo, yuvaIndices, colorSpace); +} + +bool SkImageGenerator::getYUVA8Planes(const SkYUVASizeInfo& sizeInfo, + const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + void* planes[SkYUVASizeInfo::kMaxCount]) { + + for (int i = 0; i < SkYUVASizeInfo::kMaxCount; ++i) { + SkASSERT(sizeInfo.fSizes[i].fWidth >= 0); + SkASSERT(sizeInfo.fSizes[i].fHeight >= 0); + SkASSERT(sizeInfo.fWidthBytes[i] >= (size_t) sizeInfo.fSizes[i].fWidth); + } + + int numPlanes = 0; + SkASSERT(SkYUVAIndex::AreValidIndices(yuvaIndices, &numPlanes)); + SkASSERT(planes); + for (int i = 0; i < numPlanes; ++i) { + SkASSERT(planes[i]); + } + + return this->onGetYUVA8Planes(sizeInfo, yuvaIndices, planes); +} + +#if SK_SUPPORT_GPU +#include "src/gpu/GrTextureProxy.h" + +sk_sp SkImageGenerator::generateTexture(GrRecordingContext* ctx, + const SkImageInfo& info, + const SkIPoint& origin, + bool willNeedMipMaps) { + SkIRect srcRect = SkIRect::MakeXYWH(origin.x(), origin.y(), info.width(), info.height()); + if (!SkIRect::MakeWH(fInfo.width(), fInfo.height()).contains(srcRect)) { + return nullptr; + } + return this->onGenerateTexture(ctx, info, origin, willNeedMipMaps); +} + +sk_sp SkImageGenerator::onGenerateTexture(GrRecordingContext*, + const SkImageInfo&, + const SkIPoint&, + bool willNeedMipMaps) { + return nullptr; +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkBitmap.h" +#include "src/codec/SkColorTable.h" + +#include "include/core/SkGraphics.h" + +static SkGraphics::ImageGeneratorFromEncodedDataFactory gFactory; + +SkGraphics::ImageGeneratorFromEncodedDataFactory +SkGraphics::SetImageGeneratorFromEncodedDataFactory(ImageGeneratorFromEncodedDataFactory factory) +{ + ImageGeneratorFromEncodedDataFactory prev = gFactory; + gFactory = factory; + return prev; +} + +std::unique_ptr SkImageGenerator::MakeFromEncoded(sk_sp data) { + if (!data) { + return nullptr; + } + if (gFactory) { + if (std::unique_ptr generator = gFactory(data)) { + return generator; + } + } + return SkImageGenerator::MakeFromEncodedImpl(std::move(data)); +} diff --git a/gfx/skia/skia/src/core/SkImageInfo.cpp b/gfx/skia/skia/src/core/SkImageInfo.cpp new file mode 100644 index 0000000000..e6e12bccd9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkImageInfo.cpp @@ -0,0 +1,194 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkWriteBuffer.h" + +int SkColorTypeBytesPerPixel(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return 0; + case kAlpha_8_SkColorType: return 1; + case kRGB_565_SkColorType: return 2; + case kARGB_4444_SkColorType: return 2; + case kRGBA_8888_SkColorType: return 4; + case kBGRA_8888_SkColorType: return 4; + case kRGB_888x_SkColorType: return 4; + case kRGBA_1010102_SkColorType: return 4; + case kRGB_101010x_SkColorType: return 4; + case kGray_8_SkColorType: return 1; + case kRGBA_F16Norm_SkColorType: return 8; + case kRGBA_F16_SkColorType: return 8; + case kRGBA_F32_SkColorType: return 16; + case kR8G8_unorm_SkColorType: return 2; + case kA16_unorm_SkColorType: return 2; + case kR16G16_unorm_SkColorType: return 4; + case kA16_float_SkColorType: return 2; + case kR16G16_float_SkColorType: return 4; + case kR16G16B16A16_unorm_SkColorType: return 8; + } + SkUNREACHABLE; +} + +bool SkColorTypeIsAlwaysOpaque(SkColorType ct) { + return !(kAlpha_SkColorTypeComponentFlag & SkColorTypeComponentFlags(ct)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +int SkColorInfo::bytesPerPixel() const { return SkColorTypeBytesPerPixel(fColorType); } + +int SkColorInfo::shiftPerPixel() const { return SkColorTypeShiftPerPixel(fColorType); } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +size_t SkImageInfo::computeOffset(int x, int y, size_t rowBytes) const { + SkASSERT((unsigned)x < (unsigned)this->width()); + SkASSERT((unsigned)y < (unsigned)this->height()); + return SkColorTypeComputeOffset(this->colorType(), x, y, rowBytes); +} + +size_t SkImageInfo::computeByteSize(size_t rowBytes) const { + if (0 == this->height()) { + return 0; + } + SkSafeMath safe; + size_t bytes = safe.add(safe.mul(safe.addInt(this->height(), -1), rowBytes), + safe.mul(this->width(), this->bytesPerPixel())); + return safe.ok() ? bytes : SIZE_MAX; +} + +SkImageInfo SkImageInfo::MakeS32(int width, int height, SkAlphaType at) { + return SkImageInfo({width, height}, {kN32_SkColorType, at, SkColorSpace::MakeSRGB()}); +} + +#ifdef SK_DEBUG +void SkImageInfo::validate() const { + SkASSERT(fDimensions.width() >= 0); + SkASSERT(fDimensions.height() >= 0); + SkASSERT(SkColorTypeIsValid(this->colorType())); + SkASSERT(SkAlphaTypeIsValid(this->alphaType())); +} +#endif + +bool SkColorTypeValidateAlphaType(SkColorType colorType, SkAlphaType alphaType, + SkAlphaType* canonical) { + switch (colorType) { + case kUnknown_SkColorType: + alphaType = kUnknown_SkAlphaType; + break; + case kAlpha_8_SkColorType: // fall-through + case kA16_unorm_SkColorType: // fall-through + case kA16_float_SkColorType: + if (kUnpremul_SkAlphaType == alphaType) { + alphaType = kPremul_SkAlphaType; + } + // fall-through + case kARGB_4444_SkColorType: + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + case kRGBA_1010102_SkColorType: + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: + case kRGBA_F32_SkColorType: + case kR16G16B16A16_unorm_SkColorType: + if (kUnknown_SkAlphaType == alphaType) { + return false; + } + break; + case kGray_8_SkColorType: + case kR8G8_unorm_SkColorType: + case kR16G16_unorm_SkColorType: + case kR16G16_float_SkColorType: + case kRGB_565_SkColorType: + case kRGB_888x_SkColorType: + case kRGB_101010x_SkColorType: + alphaType = kOpaque_SkAlphaType; + break; + default: + return false; + } + if (canonical) { + *canonical = alphaType; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "src/image/SkReadPixelsRec.h" + +bool SkReadPixelsRec::trim(int srcWidth, int srcHeight) { + if (nullptr == fPixels || fRowBytes < fInfo.minRowBytes()) { + return false; + } + if (0 >= fInfo.width() || 0 >= fInfo.height()) { + return false; + } + + int x = fX; + int y = fY; + SkIRect srcR = SkIRect::MakeXYWH(x, y, fInfo.width(), fInfo.height()); + if (!srcR.intersect({0, 0, srcWidth, srcHeight})) { + return false; + } + + // if x or y are negative, then we have to adjust pixels + if (x > 0) { + x = 0; + } + if (y > 0) { + y = 0; + } + // here x,y are either 0 or negative + // we negate and add them so UBSAN (pointer-overflow) doesn't get confused. + fPixels = ((char*)fPixels + -y*fRowBytes + -x*fInfo.bytesPerPixel()); + // the intersect may have shrunk info's logical size + fInfo = fInfo.makeDimensions(srcR.size()); + fX = srcR.x(); + fY = srcR.y(); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkWritePixelsRec.h" + +bool SkWritePixelsRec::trim(int dstWidth, int dstHeight) { + if (nullptr == fPixels || fRowBytes < fInfo.minRowBytes()) { + return false; + } + if (0 >= fInfo.width() || 0 >= fInfo.height()) { + return false; + } + + int x = fX; + int y = fY; + SkIRect dstR = SkIRect::MakeXYWH(x, y, fInfo.width(), fInfo.height()); + if (!dstR.intersect({0, 0, dstWidth, dstHeight})) { + return false; + } + + // if x or y are negative, then we have to adjust pixels + if (x > 0) { + x = 0; + } + if (y > 0) { + y = 0; + } + // here x,y are either 0 or negative + // we negate and add them so UBSAN (pointer-overflow) doesn't get confused. + fPixels = ((const char*)fPixels + -y*fRowBytes + -x*fInfo.bytesPerPixel()); + // the intersect may have shrunk info's logical size + fInfo = fInfo.makeDimensions(dstR.size()); + fX = dstR.x(); + fY = dstR.y(); + + return true; +} diff --git a/gfx/skia/skia/src/core/SkImagePriv.h b/gfx/skia/skia/src/core/SkImagePriv.h new file mode 100644 index 0000000000..0fd44338df --- /dev/null +++ b/gfx/skia/skia/src/core/SkImagePriv.h @@ -0,0 +1,94 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImagePriv_DEFINED +#define SkImagePriv_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkSurface.h" +#include "include/core/SkTileMode.h" + +enum SkCopyPixelsMode { + kIfMutable_SkCopyPixelsMode, //!< only copy src pixels if they are marked mutable + kAlways_SkCopyPixelsMode, //!< always copy src pixels (even if they are marked immutable) + kNever_SkCopyPixelsMode, //!< never copy src pixels (even if they are marked mutable) +}; + +// A good size for creating shader contexts on the stack. +enum {kSkBlitterContextSize = 3332}; + +// If alloc is non-nullptr, it will be used to allocate the returned SkShader, and MUST outlive +// the SkShader. +sk_sp SkMakeBitmapShader(const SkBitmap& src, SkTileMode, SkTileMode, + const SkMatrix* localMatrix, SkCopyPixelsMode); + +// Convenience function to return a shader that implements the shader+image behavior defined for +// drawImage/Bitmap where the paint's shader is ignored when the bitmap is a color image, but +// properly compose them together when it is an alpha image. This allows the returned paint to +// be assigned to a paint clone without discarding the original behavior. +sk_sp SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src, + SkTileMode, SkTileMode, + const SkMatrix* localMatrix, SkCopyPixelsMode); + +/** + * Examines the bitmap to decide if it can share the existing pixelRef, or + * if it needs to make a deep-copy of the pixels. + * + * The bitmap's pixelref will be shared if either the bitmap is marked as + * immutable, or CopyPixelsMode allows it. Shared pixel refs are also + * locked when kLocked_SharedPixelRefMode is specified. + * + * Passing kLocked_SharedPixelRefMode allows the image's peekPixels() method + * to succeed, but it will force any lazy decodes/generators to execute if + * they exist on the pixelref. + * + * It is illegal to call this with a texture-backed bitmap. + * + * If the bitmap's colortype cannot be converted into a corresponding + * SkImageInfo, or the bitmap's pixels cannot be accessed, this will return + * nullptr. + */ +extern SK_API sk_sp SkMakeImageFromRasterBitmap(const SkBitmap&, SkCopyPixelsMode); + +// Given an image created from SkNewImageFromBitmap, return its pixelref. This +// may be called to see if the surface and the image share the same pixelref, +// in which case the surface may need to perform a copy-on-write. +extern const SkPixelRef* SkBitmapImageGetPixelRef(const SkImage* rasterImage); + +/** + * Will attempt to upload and lock the contents of the image as a texture, so that subsequent + * draws to a gpu-target will come from that texture (and not by looking at the original image + * src). In particular this is intended to use the texture even if the image's original content + * changes subsequent to this call (i.e. the src is mutable!). + * + * All successful calls must be balanced by an equal number of calls to SkImage_unpinAsTexture(). + * + * Once in this "pinned" state, the image has all of the same thread restrictions that exist + * for a natively created gpu image (e.g. SkImage::MakeFromTexture) + * - all drawing, pinning, unpinning must happen in the same thread as the GrContext. + * + * @return true if the image was successfully uploaded and locked into a texture + */ +bool SkImage_pinAsTexture(const SkImage*, GrContext*); + +/** + * The balancing call to a successful invokation of SkImage_pinAsTexture. When a balanced number of + * calls have been made, then the "pinned" texture is free to be purged, etc. This also means that a + * subsequent "pin" call will look at the original content again, and if its uniqueID/generationID + * has changed, then a newer texture will be uploaded/pinned. + * + * The context passed to unpin must match the one passed to pin. + */ +void SkImage_unpinAsTexture(const SkImage*, GrContext*); + +/** + * Returns the bounds of the image relative to its encoded buffer. For all non-lazy images, + * this returns (0,0,width,height). For a lazy-image, it may return a subset of that rect. + */ +SkIRect SkImage_getSubset(const SkImage*); + +#endif diff --git a/gfx/skia/skia/src/core/SkLRUCache.h b/gfx/skia/skia/src/core/SkLRUCache.h new file mode 100644 index 0000000000..9c5e671684 --- /dev/null +++ b/gfx/skia/skia/src/core/SkLRUCache.h @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLRUCache_DEFINED +#define SkLRUCache_DEFINED + +#include "include/private/SkChecksum.h" +#include "include/private/SkTHash.h" +#include "src/core/SkTInternalLList.h" + +/** + * A generic LRU cache. + */ +template +class SkLRUCache : public SkNoncopyable { +private: + struct Entry { + Entry(const K& key, V&& value) + : fKey(key) + , fValue(std::move(value)) {} + + K fKey; + V fValue; + + SK_DECLARE_INTERNAL_LLIST_INTERFACE(Entry); + }; + +public: + explicit SkLRUCache(int maxCount) + : fMaxCount(maxCount) {} + + ~SkLRUCache() { + Entry* node = fLRU.head(); + while (node) { + fLRU.remove(node); + delete node; + node = fLRU.head(); + } + } + + V* find(const K& key) { + Entry** value = fMap.find(key); + if (!value) { + return nullptr; + } + Entry* entry = *value; + if (entry != fLRU.head()) { + fLRU.remove(entry); + fLRU.addToHead(entry); + } // else it's already at head position, don't need to do anything + return &entry->fValue; + } + + V* insert(const K& key, V value) { + Entry* entry = new Entry(key, std::move(value)); + fMap.set(entry); + fLRU.addToHead(entry); + while (fMap.count() > fMaxCount) { + this->remove(fLRU.tail()->fKey); + } + return &entry->fValue; + } + + int count() { + return fMap.count(); + } + + template // f(V*) + void foreach(Fn&& fn) { + typename SkTInternalLList::Iter iter; + for (Entry* e = iter.init(fLRU, SkTInternalLList::Iter::kHead_IterStart); e; + e = iter.next()) { + fn(&e->fValue); + } + } + + void reset() { + fMap.reset(); + for (Entry* e = fLRU.head(); e; e = fLRU.head()) { + fLRU.remove(e); + delete e; + } + } + +private: + struct Traits { + static const K& GetKey(Entry* e) { + return e->fKey; + } + + static uint32_t Hash(const K& k) { + return HashK()(k); + } + }; + + void remove(const K& key) { + Entry** value = fMap.find(key); + SkASSERT(value); + Entry* entry = *value; + SkASSERT(key == entry->fKey); + fMap.remove(key); + fLRU.remove(entry); + delete entry; + } + + int fMaxCount; + SkTHashTable fMap; + SkTInternalLList fLRU; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkLatticeIter.cpp b/gfx/skia/skia/src/core/SkLatticeIter.cpp new file mode 100644 index 0000000000..0195031506 --- /dev/null +++ b/gfx/skia/skia/src/core/SkLatticeIter.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRect.h" +#include "src/core/SkLatticeIter.h" + +/** + * Divs must be in increasing order with no duplicates. + */ +static bool valid_divs(const int* divs, int count, int start, int end) { + int prev = start - 1; + for (int i = 0; i < count; i++) { + if (prev >= divs[i] || divs[i] >= end) { + return false; + } + prev = divs[i]; + } + + return true; +} + +bool SkLatticeIter::Valid(int width, int height, const SkCanvas::Lattice& lattice) { + SkIRect totalBounds = SkIRect::MakeWH(width, height); + SkASSERT(lattice.fBounds); + const SkIRect latticeBounds = *lattice.fBounds; + if (!totalBounds.contains(latticeBounds)) { + return false; + } + + bool zeroXDivs = lattice.fXCount <= 0 || (1 == lattice.fXCount && + latticeBounds.fLeft == lattice.fXDivs[0]); + bool zeroYDivs = lattice.fYCount <= 0 || (1 == lattice.fYCount && + latticeBounds.fTop == lattice.fYDivs[0]); + if (zeroXDivs && zeroYDivs) { + return false; + } + + return valid_divs(lattice.fXDivs, lattice.fXCount, latticeBounds.fLeft, latticeBounds.fRight) + && valid_divs(lattice.fYDivs, lattice.fYCount, latticeBounds.fTop, latticeBounds.fBottom); +} + +/** + * Count the number of pixels that are in "scalable" patches. + */ +static int count_scalable_pixels(const int32_t* divs, int numDivs, bool firstIsScalable, + int start, int end) { + if (0 == numDivs) { + return firstIsScalable ? end - start : 0; + } + + int i; + int count; + if (firstIsScalable) { + count = divs[0] - start; + i = 1; + } else { + count = 0; + i = 0; + } + + for (; i < numDivs; i += 2) { + // Alternatively, we could use |top| and |bottom| as variable names, instead of + // |left| and |right|. + int left = divs[i]; + int right = (i + 1 < numDivs) ? divs[i + 1] : end; + count += right - left; + } + + return count; +} + +/** + * Set points for the src and dst rects on subsequent draw calls. + */ +static void set_points(float* dst, int* src, const int* divs, int divCount, int srcFixed, + int srcScalable, int srcStart, int srcEnd, float dstStart, float dstEnd, + bool isScalable) { + float dstLen = dstEnd - dstStart; + float scale; + if (srcFixed <= dstLen) { + // This is the "normal" case, where we scale the "scalable" patches and leave + // the other patches fixed. + scale = (dstLen - ((float) srcFixed)) / ((float) srcScalable); + } else { + // In this case, we eliminate the "scalable" patches and scale the "fixed" patches. + scale = dstLen / ((float) srcFixed); + } + + src[0] = srcStart; + dst[0] = dstStart; + for (int i = 0; i < divCount; i++) { + src[i + 1] = divs[i]; + int srcDelta = src[i + 1] - src[i]; + float dstDelta; + if (srcFixed <= dstLen) { + dstDelta = isScalable ? scale * srcDelta : srcDelta; + } else { + dstDelta = isScalable ? 0.0f : scale * srcDelta; + } + dst[i + 1] = dst[i] + dstDelta; + + // Alternate between "scalable" and "fixed" patches. + isScalable = !isScalable; + } + + src[divCount + 1] = srcEnd; + dst[divCount + 1] = dstEnd; +} + +SkLatticeIter::SkLatticeIter(const SkCanvas::Lattice& lattice, const SkRect& dst) { + const int* xDivs = lattice.fXDivs; + const int origXCount = lattice.fXCount; + const int* yDivs = lattice.fYDivs; + const int origYCount = lattice.fYCount; + SkASSERT(lattice.fBounds); + const SkIRect src = *lattice.fBounds; + + // In the x-dimension, the first rectangle always starts at x = 0 and is "scalable". + // If xDiv[0] is 0, it indicates that the first rectangle is degenerate, so the + // first real rectangle "scalable" in the x-direction. + // + // The same interpretation applies to the y-dimension. + // + // As we move left to right across the image, alternating patches will be "fixed" or + // "scalable" in the x-direction. Similarly, as move top to bottom, alternating + // patches will be "fixed" or "scalable" in the y-direction. + int xCount = origXCount; + int yCount = origYCount; + bool xIsScalable = (xCount > 0 && src.fLeft == xDivs[0]); + if (xIsScalable) { + // Once we've decided that the first patch is "scalable", we don't need the + // xDiv. It is always implied that we start at the edge of the bounds. + xDivs++; + xCount--; + } + bool yIsScalable = (yCount > 0 && src.fTop == yDivs[0]); + if (yIsScalable) { + // Once we've decided that the first patch is "scalable", we don't need the + // yDiv. It is always implied that we start at the edge of the bounds. + yDivs++; + yCount--; + } + + // Count "scalable" and "fixed" pixels in each dimension. + int xCountScalable = count_scalable_pixels(xDivs, xCount, xIsScalable, src.fLeft, src.fRight); + int xCountFixed = src.width() - xCountScalable; + int yCountScalable = count_scalable_pixels(yDivs, yCount, yIsScalable, src.fTop, src.fBottom); + int yCountFixed = src.height() - yCountScalable; + + fSrcX.reset(xCount + 2); + fDstX.reset(xCount + 2); + set_points(fDstX.begin(), fSrcX.begin(), xDivs, xCount, xCountFixed, xCountScalable, + src.fLeft, src.fRight, dst.fLeft, dst.fRight, xIsScalable); + + fSrcY.reset(yCount + 2); + fDstY.reset(yCount + 2); + set_points(fDstY.begin(), fSrcY.begin(), yDivs, yCount, yCountFixed, yCountScalable, + src.fTop, src.fBottom, dst.fTop, dst.fBottom, yIsScalable); + + fCurrX = fCurrY = 0; + fNumRectsInLattice = (xCount + 1) * (yCount + 1); + fNumRectsToDraw = fNumRectsInLattice; + + if (lattice.fRectTypes) { + fRectTypes.push_back_n(fNumRectsInLattice); + fColors.push_back_n(fNumRectsInLattice); + + const SkCanvas::Lattice::RectType* flags = lattice.fRectTypes; + const SkColor* colors = lattice.fColors; + + bool hasPadRow = (yCount != origYCount); + bool hasPadCol = (xCount != origXCount); + if (hasPadRow) { + // The first row of rects are all empty, skip the first row of flags. + flags += origXCount + 1; + colors += origXCount + 1; + } + + int i = 0; + for (int y = 0; y < yCount + 1; y++) { + for (int x = 0; x < origXCount + 1; x++) { + if (0 == x && hasPadCol) { + // The first column of rects are all empty. Skip a rect. + flags++; + colors++; + continue; + } + + fRectTypes[i] = *flags; + fColors[i] = SkCanvas::Lattice::kFixedColor == *flags ? *colors : 0; + flags++; + colors++; + i++; + } + } + + for (int j = 0; j < fRectTypes.count(); j++) { + if (SkCanvas::Lattice::kTransparent == fRectTypes[j]) { + fNumRectsToDraw--; + } + } + } +} + +bool SkLatticeIter::Valid(int width, int height, const SkIRect& center) { + return !center.isEmpty() && SkIRect::MakeWH(width, height).contains(center); +} + +SkLatticeIter::SkLatticeIter(int w, int h, const SkIRect& c, const SkRect& dst) { + SkASSERT(SkIRect::MakeWH(w, h).contains(c)); + + fSrcX.reset(4); + fSrcY.reset(4); + fDstX.reset(4); + fDstY.reset(4); + + fSrcX[0] = 0; + fSrcX[1] = SkIntToScalar(c.fLeft); + fSrcX[2] = SkIntToScalar(c.fRight); + fSrcX[3] = SkIntToScalar(w); + + fSrcY[0] = 0; + fSrcY[1] = SkIntToScalar(c.fTop); + fSrcY[2] = SkIntToScalar(c.fBottom); + fSrcY[3] = SkIntToScalar(h); + + fDstX[0] = dst.fLeft; + fDstX[1] = dst.fLeft + SkIntToScalar(c.fLeft); + fDstX[2] = dst.fRight - SkIntToScalar(w - c.fRight); + fDstX[3] = dst.fRight; + + fDstY[0] = dst.fTop; + fDstY[1] = dst.fTop + SkIntToScalar(c.fTop); + fDstY[2] = dst.fBottom - SkIntToScalar(h - c.fBottom); + fDstY[3] = dst.fBottom; + + if (fDstX[1] > fDstX[2]) { + fDstX[1] = fDstX[0] + (fDstX[3] - fDstX[0]) * c.fLeft / (w - c.width()); + fDstX[2] = fDstX[1]; + } + + if (fDstY[1] > fDstY[2]) { + fDstY[1] = fDstY[0] + (fDstY[3] - fDstY[0]) * c.fTop / (h - c.height()); + fDstY[2] = fDstY[1]; + } + + fCurrX = fCurrY = 0; + fNumRectsInLattice = 9; + fNumRectsToDraw = 9; +} + +bool SkLatticeIter::next(SkIRect* src, SkRect* dst, bool* isFixedColor, SkColor* fixedColor) { + int currRect = fCurrX + fCurrY * (fSrcX.count() - 1); + if (currRect == fNumRectsInLattice) { + return false; + } + + const int x = fCurrX; + const int y = fCurrY; + SkASSERT(x >= 0 && x < fSrcX.count() - 1); + SkASSERT(y >= 0 && y < fSrcY.count() - 1); + + if (fSrcX.count() - 1 == ++fCurrX) { + fCurrX = 0; + fCurrY += 1; + } + + if (fRectTypes.count() > 0 + && SkToBool(SkCanvas::Lattice::kTransparent == fRectTypes[currRect])) { + return this->next(src, dst, isFixedColor, fixedColor); + } + + src->setLTRB(fSrcX[x], fSrcY[y], fSrcX[x + 1], fSrcY[y + 1]); + dst->setLTRB(fDstX[x], fDstY[y], fDstX[x + 1], fDstY[y + 1]); + if (isFixedColor && fixedColor) { + *isFixedColor = fRectTypes.count() > 0 + && SkToBool(SkCanvas::Lattice::kFixedColor == fRectTypes[currRect]); + if (*isFixedColor) { + *fixedColor = fColors[currRect]; + } + } + return true; +} + +void SkLatticeIter::mapDstScaleTranslate(const SkMatrix& matrix) { + SkASSERT(matrix.isScaleTranslate()); + SkScalar tx = matrix.getTranslateX(); + SkScalar sx = matrix.getScaleX(); + for (int i = 0; i < fDstX.count(); i++) { + fDstX[i] = fDstX[i] * sx + tx; + } + + SkScalar ty = matrix.getTranslateY(); + SkScalar sy = matrix.getScaleY(); + for (int i = 0; i < fDstY.count(); i++) { + fDstY[i] = fDstY[i] * sy + ty; + } +} diff --git a/gfx/skia/skia/src/core/SkLatticeIter.h b/gfx/skia/skia/src/core/SkLatticeIter.h new file mode 100644 index 0000000000..16eef21b7a --- /dev/null +++ b/gfx/skia/skia/src/core/SkLatticeIter.h @@ -0,0 +1,77 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLatticeIter_DEFINED +#define SkLatticeIter_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkScalar.h" +#include "include/private/SkTArray.h" + +struct SkIRect; +struct SkRect; + +/** + * Disect a lattice request into an sequence of src-rect / dst-rect pairs + */ +class SK_API SkLatticeIter { +public: + + static bool Valid(int imageWidth, int imageHeight, const SkCanvas::Lattice& lattice); + + SkLatticeIter(const SkCanvas::Lattice& lattice, const SkRect& dst); + + static bool Valid(int imageWidth, int imageHeight, const SkIRect& center); + + SkLatticeIter(int imageWidth, int imageHeight, const SkIRect& center, const SkRect& dst); + + /** + * While it returns true, use src/dst to draw the image/bitmap. Optional parameters + * isFixedColor and fixedColor specify if the rectangle is filled with a fixed color. + * If (*isFixedColor) is true, then (*fixedColor) contains the rectangle color. + */ + bool next(SkIRect* src, SkRect* dst, bool* isFixedColor = nullptr, + SkColor* fixedColor = nullptr); + + /** Version of above that converts the integer src rect to a scalar rect. */ + bool next(SkRect* src, SkRect* dst, bool* isFixedColor = nullptr, + SkColor* fixedColor = nullptr) { + SkIRect isrcR; + if (this->next(&isrcR, dst, isFixedColor, fixedColor)) { + *src = SkRect::Make(isrcR); + return true; + } + return false; + } + + /** + * Apply a matrix to the dst points. + */ + void mapDstScaleTranslate(const SkMatrix& matrix); + + /** + * Returns the number of rects that will actually be drawn. + */ + int numRectsToDraw() const { + return fNumRectsToDraw; + } + +private: + SkTArray fSrcX; + SkTArray fSrcY; + SkTArray fDstX; + SkTArray fDstY; + SkTArray fRectTypes; + SkTArray fColors; + + int fCurrX; + int fCurrY; + int fNumRectsInLattice; + int fNumRectsToDraw; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkLeanWindows.h b/gfx/skia/skia/src/core/SkLeanWindows.h new file mode 100644 index 0000000000..ec8dd58cdc --- /dev/null +++ b/gfx/skia/skia/src/core/SkLeanWindows.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkLeanWindows_DEFINED +#define SkLeanWindows_DEFINED + +#include "include/core/SkTypes.h" + +#ifdef SK_BUILD_FOR_WIN +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# define WIN32_IS_MEAN_WAS_LOCALLY_DEFINED +# endif +# ifndef NOMINMAX +# define NOMINMAX +# define NOMINMAX_WAS_LOCALLY_DEFINED +# endif +# +# include +# +# ifdef WIN32_IS_MEAN_WAS_LOCALLY_DEFINED +# undef WIN32_IS_MEAN_WAS_LOCALLY_DEFINED +# undef WIN32_LEAN_AND_MEAN +# endif +# ifdef NOMINMAX_WAS_LOCALLY_DEFINED +# undef NOMINMAX_WAS_LOCALLY_DEFINED +# undef NOMINMAX +# endif +#endif + +#endif // SkLeanWindows_DEFINED diff --git a/gfx/skia/skia/src/core/SkLineClipper.cpp b/gfx/skia/skia/src/core/SkLineClipper.cpp new file mode 100644 index 0000000000..6a71a03028 --- /dev/null +++ b/gfx/skia/skia/src/core/SkLineClipper.cpp @@ -0,0 +1,276 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkTo.h" +#include "src/core/SkLineClipper.h" + +#include + +template T pin_unsorted(T value, T limit0, T limit1) { + if (limit1 < limit0) { + using std::swap; + swap(limit0, limit1); + } + // now the limits are sorted + SkASSERT(limit0 <= limit1); + + if (value < limit0) { + value = limit0; + } else if (value > limit1) { + value = limit1; + } + return value; +} + +// return X coordinate of intersection with horizontal line at Y +static SkScalar sect_with_horizontal(const SkPoint src[2], SkScalar Y) { + SkScalar dy = src[1].fY - src[0].fY; + if (SkScalarNearlyZero(dy)) { + return SkScalarAve(src[0].fX, src[1].fX); + } else { + // need the extra precision so we don't compute a value that exceeds + // our original limits + double X0 = src[0].fX; + double Y0 = src[0].fY; + double X1 = src[1].fX; + double Y1 = src[1].fY; + double result = X0 + ((double)Y - Y0) * (X1 - X0) / (Y1 - Y0); + + // The computed X value might still exceed [X0..X1] due to quantum flux + // when the doubles were added and subtracted, so we have to pin the + // answer :( + return (float)pin_unsorted(result, X0, X1); + } +} + +// return Y coordinate of intersection with vertical line at X +static SkScalar sect_with_vertical(const SkPoint src[2], SkScalar X) { + SkScalar dx = src[1].fX - src[0].fX; + if (SkScalarNearlyZero(dx)) { + return SkScalarAve(src[0].fY, src[1].fY); + } else { + // need the extra precision so we don't compute a value that exceeds + // our original limits + double X0 = src[0].fX; + double Y0 = src[0].fY; + double X1 = src[1].fX; + double Y1 = src[1].fY; + double result = Y0 + ((double)X - X0) * (Y1 - Y0) / (X1 - X0); + return (float)result; + } +} + +static SkScalar sect_clamp_with_vertical(const SkPoint src[2], SkScalar x) { + SkScalar y = sect_with_vertical(src, x); + // Our caller expects y to be between src[0].fY and src[1].fY (unsorted), but due to the + // numerics of floats/doubles, we might have computed a value slightly outside of that, + // so we have to manually clamp afterwards. + // See skbug.com/7491 + return pin_unsorted(y, src[0].fY, src[1].fY); +} + +/////////////////////////////////////////////////////////////////////////////// + +static inline bool nestedLT(SkScalar a, SkScalar b, SkScalar dim) { + return a <= b && (a < b || dim > 0); +} + +// returns true if outer contains inner, even if inner is empty. +// note: outer.contains(inner) always returns false if inner is empty. +static inline bool containsNoEmptyCheck(const SkRect& outer, + const SkRect& inner) { + return outer.fLeft <= inner.fLeft && outer.fTop <= inner.fTop && + outer.fRight >= inner.fRight && outer.fBottom >= inner.fBottom; +} + +bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip, + SkPoint dst[2]) { + SkRect bounds; + + bounds.set(src[0], src[1]); + if (containsNoEmptyCheck(clip, bounds)) { + if (src != dst) { + memcpy(dst, src, 2 * sizeof(SkPoint)); + } + return true; + } + // check for no overlap, and only permit coincident edges if the line + // and the edge are colinear + if (nestedLT(bounds.fRight, clip.fLeft, bounds.width()) || + nestedLT(clip.fRight, bounds.fLeft, bounds.width()) || + nestedLT(bounds.fBottom, clip.fTop, bounds.height()) || + nestedLT(clip.fBottom, bounds.fTop, bounds.height())) { + return false; + } + + int index0, index1; + + if (src[0].fY < src[1].fY) { + index0 = 0; + index1 = 1; + } else { + index0 = 1; + index1 = 0; + } + + SkPoint tmp[2]; + memcpy(tmp, src, sizeof(tmp)); + + // now compute Y intersections + if (tmp[index0].fY < clip.fTop) { + tmp[index0].set(sect_with_horizontal(src, clip.fTop), clip.fTop); + } + if (tmp[index1].fY > clip.fBottom) { + tmp[index1].set(sect_with_horizontal(src, clip.fBottom), clip.fBottom); + } + + if (tmp[0].fX < tmp[1].fX) { + index0 = 0; + index1 = 1; + } else { + index0 = 1; + index1 = 0; + } + + // check for quick-reject in X again, now that we may have been chopped + if ((tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight)) { + // usually we will return false, but we don't if the line is vertical and coincident + // with the clip. + if (tmp[0].fX != tmp[1].fX || tmp[0].fX < clip.fLeft || tmp[0].fX > clip.fRight) { + return false; + } + } + + if (tmp[index0].fX < clip.fLeft) { + tmp[index0].set(clip.fLeft, sect_with_vertical(src, clip.fLeft)); + } + if (tmp[index1].fX > clip.fRight) { + tmp[index1].set(clip.fRight, sect_with_vertical(src, clip.fRight)); + } +#ifdef SK_DEBUG + bounds.set(tmp[0], tmp[1]); + SkASSERT(containsNoEmptyCheck(clip, bounds)); +#endif + memcpy(dst, tmp, sizeof(tmp)); + return true; +} + +#ifdef SK_DEBUG +// return value between the two limits, where the limits are either ascending +// or descending. +static bool is_between_unsorted(SkScalar value, + SkScalar limit0, SkScalar limit1) { + if (limit0 < limit1) { + return limit0 <= value && value <= limit1; + } else { + return limit1 <= value && value <= limit0; + } +} +#endif + +int SkLineClipper::ClipLine(const SkPoint pts[], const SkRect& clip, SkPoint lines[], + bool canCullToTheRight) { + int index0, index1; + + if (pts[0].fY < pts[1].fY) { + index0 = 0; + index1 = 1; + } else { + index0 = 1; + index1 = 0; + } + + // Check if we're completely clipped out in Y (above or below + + if (pts[index1].fY <= clip.fTop) { // we're above the clip + return 0; + } + if (pts[index0].fY >= clip.fBottom) { // we're below the clip + return 0; + } + + // Chop in Y to produce a single segment, stored in tmp[0..1] + + SkPoint tmp[2]; + memcpy(tmp, pts, sizeof(tmp)); + + // now compute intersections + if (pts[index0].fY < clip.fTop) { + tmp[index0].set(sect_with_horizontal(pts, clip.fTop), clip.fTop); + SkASSERT(is_between_unsorted(tmp[index0].fX, pts[0].fX, pts[1].fX)); + } + if (tmp[index1].fY > clip.fBottom) { + tmp[index1].set(sect_with_horizontal(pts, clip.fBottom), clip.fBottom); + SkASSERT(is_between_unsorted(tmp[index1].fX, pts[0].fX, pts[1].fX)); + } + + // Chop it into 1..3 segments that are wholly within the clip in X. + + // temp storage for up to 3 segments + SkPoint resultStorage[kMaxPoints]; + SkPoint* result; // points to our results, either tmp or resultStorage + int lineCount = 1; + bool reverse; + + if (pts[0].fX < pts[1].fX) { + index0 = 0; + index1 = 1; + reverse = false; + } else { + index0 = 1; + index1 = 0; + reverse = true; + } + + if (tmp[index1].fX <= clip.fLeft) { // wholly to the left + tmp[0].fX = tmp[1].fX = clip.fLeft; + result = tmp; + reverse = false; + } else if (tmp[index0].fX >= clip.fRight) { // wholly to the right + if (canCullToTheRight) { + return 0; + } + tmp[0].fX = tmp[1].fX = clip.fRight; + result = tmp; + reverse = false; + } else { + result = resultStorage; + SkPoint* r = result; + + if (tmp[index0].fX < clip.fLeft) { + r->set(clip.fLeft, tmp[index0].fY); + r += 1; + r->set(clip.fLeft, sect_clamp_with_vertical(tmp, clip.fLeft)); + SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY)); + } else { + *r = tmp[index0]; + } + r += 1; + + if (tmp[index1].fX > clip.fRight) { + r->set(clip.fRight, sect_clamp_with_vertical(tmp, clip.fRight)); + SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY)); + r += 1; + r->set(clip.fRight, tmp[index1].fY); + } else { + *r = tmp[index1]; + } + + lineCount = SkToInt(r - result); + } + + // Now copy the results into the caller's lines[] parameter + if (reverse) { + // copy the pts in reverse order to maintain winding order + for (int i = 0; i <= lineCount; i++) { + lines[lineCount - i] = result[i]; + } + } else { + memcpy(lines, result, (lineCount + 1) * sizeof(SkPoint)); + } + return lineCount; +} diff --git a/gfx/skia/skia/src/core/SkLineClipper.h b/gfx/skia/skia/src/core/SkLineClipper.h new file mode 100644 index 0000000000..5c2a7dc376 --- /dev/null +++ b/gfx/skia/skia/src/core/SkLineClipper.h @@ -0,0 +1,45 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkLineClipper_DEFINED +#define SkLineClipper_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" + +class SkLineClipper { +public: + enum { + kMaxPoints = 4, + kMaxClippedLineSegments = kMaxPoints - 1 + }; + + /* Clip the line pts[0]...pts[1] against clip, ignoring segments that + lie completely above or below the clip. For portions to the left or + right, turn those into vertical line segments that are aligned to the + edge of the clip. + + Return the number of line segments that result, and store the end-points + of those segments sequentially in lines as follows: + 1st segment: lines[0]..lines[1] + 2nd segment: lines[1]..lines[2] + 3rd segment: lines[2]..lines[3] + */ + static int ClipLine(const SkPoint pts[2], const SkRect& clip, + SkPoint lines[kMaxPoints], bool canCullToTheRight); + + /* Intersect the line segment against the rect. If there is a non-empty + resulting segment, return true and set dst[] to that segment. If not, + return false and ignore dst[]. + + ClipLine is specialized for scan-conversion, as it adds vertical + segments on the sides to show where the line extended beyond the + left or right sides. IntersectLine does not. + */ + static bool IntersectLine(const SkPoint src[2], const SkRect& clip, SkPoint dst[2]); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkLocalMatrixImageFilter.cpp b/gfx/skia/skia/src/core/SkLocalMatrixImageFilter.cpp new file mode 100644 index 0000000000..bb6dc3f13f --- /dev/null +++ b/gfx/skia/skia/src/core/SkLocalMatrixImageFilter.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "src/core/SkLocalMatrixImageFilter.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" + +sk_sp SkLocalMatrixImageFilter::Make(const SkMatrix& localM, + sk_sp input) { + if (!input) { + return nullptr; + } + if (localM.isIdentity()) { + return input; + } + if (!as_IFB(input)->canHandleComplexCTM() && !localM.isScaleTranslate()) { + // Nothing we can do at this point + return nullptr; + } + return sk_sp(new SkLocalMatrixImageFilter(localM, input)); +} + +SkLocalMatrixImageFilter::SkLocalMatrixImageFilter(const SkMatrix& localM, + sk_sp input) + : INHERITED(&input, 1, nullptr) + , fLocalM(localM) { +} + +sk_sp SkLocalMatrixImageFilter::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkMatrix lm; + buffer.readMatrix(&lm); + return SkLocalMatrixImageFilter::Make(lm, common.getInput(0)); +} + +void SkLocalMatrixImageFilter::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeMatrix(fLocalM); +} + +sk_sp SkLocalMatrixImageFilter::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + Context localCtx = ctx.withNewMapping(ctx.mapping().concatLocal(fLocalM)); + return this->filterInput(0, localCtx, offset); +} + +SkIRect SkLocalMatrixImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection dir, const SkIRect* inputRect) const { + return this->getInput(0)->filterBounds(src, SkMatrix::Concat(ctm, fLocalM), dir, inputRect); +} diff --git a/gfx/skia/skia/src/core/SkLocalMatrixImageFilter.h b/gfx/skia/skia/src/core/SkLocalMatrixImageFilter.h new file mode 100644 index 0000000000..f20773ca0a --- /dev/null +++ b/gfx/skia/skia/src/core/SkLocalMatrixImageFilter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLocalMatrixImageFilter_DEFINED +#define SkLocalMatrixImageFilter_DEFINED + +#include "include/core/SkFlattenable.h" +#include "src/core/SkImageFilter_Base.h" + +/** + * Wraps another imagefilter + matrix, such that using this filter will give the same result + * as using the wrapped filter with the matrix applied to its context. + */ +class SkLocalMatrixImageFilter : public SkImageFilter_Base { +public: + static sk_sp Make(const SkMatrix& localM, sk_sp input); + +protected: + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + + bool onCanHandleComplexCTM() const override { return true; } + +private: + SK_FLATTENABLE_HOOKS(SkLocalMatrixImageFilter) + + SkLocalMatrixImageFilter(const SkMatrix& localM, sk_sp input); + + SkMatrix fLocalM; + + typedef SkImageFilter_Base INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkMD5.cpp b/gfx/skia/skia/src/core/SkMD5.cpp new file mode 100644 index 0000000000..158bb63b74 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMD5.cpp @@ -0,0 +1,260 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * The following code is based on the description in RFC 1321. + * http://www.ietf.org/rfc/rfc1321.txt + */ + +//The following macros can be defined to affect the MD5 code generated. +//SK_MD5_CLEAR_DATA causes all intermediate state to be overwritten with 0's. +//SK_CPU_LENDIAN allows 32 bit <=> 8 bit conversions without copies (if alligned). +//SK_CPU_FAST_UNALIGNED_ACCESS allows 32 bit <=> 8 bit conversions without copies if SK_CPU_LENDIAN. + +#include "src/core/SkMD5.h" +#include + +/** MD5 basic transformation. Transforms state based on block. */ +static void transform(uint32_t state[4], const uint8_t block[64]); + +/** Encodes input into output (4 little endian 32 bit values). */ +static void encode(uint8_t output[16], const uint32_t input[4]); + +/** Encodes input into output (little endian 64 bit value). */ +static void encode(uint8_t output[8], const uint64_t input); + +/** Decodes input (4 little endian 32 bit values) into storage, if required. */ +static const uint32_t* decode(uint32_t storage[16], const uint8_t input[64]); + +SkMD5::SkMD5() : byteCount(0) { + // These are magic numbers from the specification. + this->state[0] = 0x67452301; + this->state[1] = 0xefcdab89; + this->state[2] = 0x98badcfe; + this->state[3] = 0x10325476; +} + +bool SkMD5::write(const void* buf, size_t inputLength) { + const uint8_t* input = reinterpret_cast(buf); + unsigned int bufferIndex = (unsigned int)(this->byteCount & 0x3F); + unsigned int bufferAvailable = 64 - bufferIndex; + + unsigned int inputIndex; + if (inputLength >= bufferAvailable) { + if (bufferIndex) { + memcpy(&this->buffer[bufferIndex], input, bufferAvailable); + transform(this->state, this->buffer); + inputIndex = bufferAvailable; + } else { + inputIndex = 0; + } + + for (; inputIndex + 63 < inputLength; inputIndex += 64) { + transform(this->state, &input[inputIndex]); + } + + bufferIndex = 0; + } else { + inputIndex = 0; + } + + memcpy(&this->buffer[bufferIndex], &input[inputIndex], inputLength - inputIndex); + + this->byteCount += inputLength; + return true; +} + +SkMD5::Digest SkMD5::finish() { + SkMD5::Digest digest; + // Get the number of bits before padding. + uint8_t bits[8]; + encode(bits, this->byteCount << 3); + + // Pad out to 56 mod 64. + unsigned int bufferIndex = (unsigned int)(this->byteCount & 0x3F); + unsigned int paddingLength = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); + static uint8_t PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + (void)this->write(PADDING, paddingLength); + + // Append length (length before padding, will cause final update). + (void)this->write(bits, 8); + + // Write out digest. + encode(digest.data, this->state); + +#if defined(SK_MD5_CLEAR_DATA) + // Clear state. + memset(this, 0, sizeof(*this)); +#endif + return digest; +} + +struct F { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) { + //return (x & y) | ((~x) & z); + return ((y ^ z) & x) ^ z; //equivelent but faster +}}; + +struct G { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) { + return (x & z) | (y & (~z)); + //return ((x ^ y) & z) ^ y; //equivelent but slower +}}; + +struct H { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) { + return x ^ y ^ z; +}}; + +struct I { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) { + return y ^ (x | (~z)); +}}; + +/** Rotates x left n bits. */ +static inline uint32_t rotate_left(uint32_t x, uint8_t n) { + return (x << n) | (x >> (32 - n)); +} + +template +static inline void operation(T operation, uint32_t& a, uint32_t b, uint32_t c, uint32_t d, + uint32_t x, uint8_t s, uint32_t t) { + a = b + rotate_left(a + operation(b, c, d) + x + t, s); +} + +static void transform(uint32_t state[4], const uint8_t block[64]) { + uint32_t a = state[0], b = state[1], c = state[2], d = state[3]; + + uint32_t storage[16]; + const uint32_t* X = decode(storage, block); + + // Round 1 + operation(F(), a, b, c, d, X[ 0], 7, 0xd76aa478); // 1 + operation(F(), d, a, b, c, X[ 1], 12, 0xe8c7b756); // 2 + operation(F(), c, d, a, b, X[ 2], 17, 0x242070db); // 3 + operation(F(), b, c, d, a, X[ 3], 22, 0xc1bdceee); // 4 + operation(F(), a, b, c, d, X[ 4], 7, 0xf57c0faf); // 5 + operation(F(), d, a, b, c, X[ 5], 12, 0x4787c62a); // 6 + operation(F(), c, d, a, b, X[ 6], 17, 0xa8304613); // 7 + operation(F(), b, c, d, a, X[ 7], 22, 0xfd469501); // 8 + operation(F(), a, b, c, d, X[ 8], 7, 0x698098d8); // 9 + operation(F(), d, a, b, c, X[ 9], 12, 0x8b44f7af); // 10 + operation(F(), c, d, a, b, X[10], 17, 0xffff5bb1); // 11 + operation(F(), b, c, d, a, X[11], 22, 0x895cd7be); // 12 + operation(F(), a, b, c, d, X[12], 7, 0x6b901122); // 13 + operation(F(), d, a, b, c, X[13], 12, 0xfd987193); // 14 + operation(F(), c, d, a, b, X[14], 17, 0xa679438e); // 15 + operation(F(), b, c, d, a, X[15], 22, 0x49b40821); // 16 + + // Round 2 + operation(G(), a, b, c, d, X[ 1], 5, 0xf61e2562); // 17 + operation(G(), d, a, b, c, X[ 6], 9, 0xc040b340); // 18 + operation(G(), c, d, a, b, X[11], 14, 0x265e5a51); // 19 + operation(G(), b, c, d, a, X[ 0], 20, 0xe9b6c7aa); // 20 + operation(G(), a, b, c, d, X[ 5], 5, 0xd62f105d); // 21 + operation(G(), d, a, b, c, X[10], 9, 0x2441453); // 22 + operation(G(), c, d, a, b, X[15], 14, 0xd8a1e681); // 23 + operation(G(), b, c, d, a, X[ 4], 20, 0xe7d3fbc8); // 24 + operation(G(), a, b, c, d, X[ 9], 5, 0x21e1cde6); // 25 + operation(G(), d, a, b, c, X[14], 9, 0xc33707d6); // 26 + operation(G(), c, d, a, b, X[ 3], 14, 0xf4d50d87); // 27 + operation(G(), b, c, d, a, X[ 8], 20, 0x455a14ed); // 28 + operation(G(), a, b, c, d, X[13], 5, 0xa9e3e905); // 29 + operation(G(), d, a, b, c, X[ 2], 9, 0xfcefa3f8); // 30 + operation(G(), c, d, a, b, X[ 7], 14, 0x676f02d9); // 31 + operation(G(), b, c, d, a, X[12], 20, 0x8d2a4c8a); // 32 + + // Round 3 + operation(H(), a, b, c, d, X[ 5], 4, 0xfffa3942); // 33 + operation(H(), d, a, b, c, X[ 8], 11, 0x8771f681); // 34 + operation(H(), c, d, a, b, X[11], 16, 0x6d9d6122); // 35 + operation(H(), b, c, d, a, X[14], 23, 0xfde5380c); // 36 + operation(H(), a, b, c, d, X[ 1], 4, 0xa4beea44); // 37 + operation(H(), d, a, b, c, X[ 4], 11, 0x4bdecfa9); // 38 + operation(H(), c, d, a, b, X[ 7], 16, 0xf6bb4b60); // 39 + operation(H(), b, c, d, a, X[10], 23, 0xbebfbc70); // 40 + operation(H(), a, b, c, d, X[13], 4, 0x289b7ec6); // 41 + operation(H(), d, a, b, c, X[ 0], 11, 0xeaa127fa); // 42 + operation(H(), c, d, a, b, X[ 3], 16, 0xd4ef3085); // 43 + operation(H(), b, c, d, a, X[ 6], 23, 0x4881d05); // 44 + operation(H(), a, b, c, d, X[ 9], 4, 0xd9d4d039); // 45 + operation(H(), d, a, b, c, X[12], 11, 0xe6db99e5); // 46 + operation(H(), c, d, a, b, X[15], 16, 0x1fa27cf8); // 47 + operation(H(), b, c, d, a, X[ 2], 23, 0xc4ac5665); // 48 + + // Round 4 + operation(I(), a, b, c, d, X[ 0], 6, 0xf4292244); // 49 + operation(I(), d, a, b, c, X[ 7], 10, 0x432aff97); // 50 + operation(I(), c, d, a, b, X[14], 15, 0xab9423a7); // 51 + operation(I(), b, c, d, a, X[ 5], 21, 0xfc93a039); // 52 + operation(I(), a, b, c, d, X[12], 6, 0x655b59c3); // 53 + operation(I(), d, a, b, c, X[ 3], 10, 0x8f0ccc92); // 54 + operation(I(), c, d, a, b, X[10], 15, 0xffeff47d); // 55 + operation(I(), b, c, d, a, X[ 1], 21, 0x85845dd1); // 56 + operation(I(), a, b, c, d, X[ 8], 6, 0x6fa87e4f); // 57 + operation(I(), d, a, b, c, X[15], 10, 0xfe2ce6e0); // 58 + operation(I(), c, d, a, b, X[ 6], 15, 0xa3014314); // 59 + operation(I(), b, c, d, a, X[13], 21, 0x4e0811a1); // 60 + operation(I(), a, b, c, d, X[ 4], 6, 0xf7537e82); // 61 + operation(I(), d, a, b, c, X[11], 10, 0xbd3af235); // 62 + operation(I(), c, d, a, b, X[ 2], 15, 0x2ad7d2bb); // 63 + operation(I(), b, c, d, a, X[ 9], 21, 0xeb86d391); // 64 + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + +#if defined(SK_MD5_CLEAR_DATA) + // Clear sensitive information. + if (X == &storage) { + memset(storage, 0, sizeof(storage)); + } +#endif +} + +static void encode(uint8_t output[16], const uint32_t input[4]) { + for (size_t i = 0, j = 0; i < 4; i++, j += 4) { + output[j ] = (uint8_t) (input[i] & 0xff); + output[j+1] = (uint8_t)((input[i] >> 8) & 0xff); + output[j+2] = (uint8_t)((input[i] >> 16) & 0xff); + output[j+3] = (uint8_t)((input[i] >> 24) & 0xff); + } +} + +static void encode(uint8_t output[8], const uint64_t input) { + output[0] = (uint8_t) (input & 0xff); + output[1] = (uint8_t)((input >> 8) & 0xff); + output[2] = (uint8_t)((input >> 16) & 0xff); + output[3] = (uint8_t)((input >> 24) & 0xff); + output[4] = (uint8_t)((input >> 32) & 0xff); + output[5] = (uint8_t)((input >> 40) & 0xff); + output[6] = (uint8_t)((input >> 48) & 0xff); + output[7] = (uint8_t)((input >> 56) & 0xff); +} + +static inline bool is_aligned(const void *pointer, size_t byte_count) { + return reinterpret_cast(pointer) % byte_count == 0; +} + +static const uint32_t* decode(uint32_t storage[16], const uint8_t input[64]) { +#if defined(SK_CPU_LENDIAN) && defined(SK_CPU_FAST_UNALIGNED_ACCESS) + return reinterpret_cast(input); +#else +#if defined(SK_CPU_LENDIAN) + if (is_aligned(input, 4)) { + return reinterpret_cast(input); + } +#endif + for (size_t i = 0, j = 0; j < 64; i++, j += 4) { + storage[i] = ((uint32_t)input[j ]) | + (((uint32_t)input[j+1]) << 8) | + (((uint32_t)input[j+2]) << 16) | + (((uint32_t)input[j+3]) << 24); + } + return storage; +#endif +} diff --git a/gfx/skia/skia/src/core/SkMD5.h b/gfx/skia/skia/src/core/SkMD5.h new file mode 100644 index 0000000000..2d04eae578 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMD5.h @@ -0,0 +1,42 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMD5_DEFINED +#define SkMD5_DEFINED + +#include "include/core/SkStream.h" +#include "include/private/SkTo.h" + +/* Calculate a 128-bit MD5 message-digest of the bytes sent to this stream. */ +class SkMD5 : public SkWStream { +public: + SkMD5(); + + /** Processes input, adding it to the digest. + Calling this after finish is undefined. */ + bool write(const void* buffer, size_t size) final; + + size_t bytesWritten() const final { return SkToSizeT(this->byteCount); } + + struct Digest { + uint8_t data[16]; + bool operator ==(Digest const& other) const { + return 0 == memcmp(data, other.data, sizeof(data)); + } + bool operator !=(Digest const& other) const { return !(*this == other); } + }; + + /** Computes and returns the digest. */ + Digest finish(); + +private: + uint64_t byteCount; // number of bytes, modulo 2^64 + uint32_t state[4]; // state (ABCD) + uint8_t buffer[64]; // input buffer +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkMSAN.h b/gfx/skia/skia/src/core/SkMSAN.h new file mode 100644 index 0000000000..acde144016 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMSAN.h @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMSAN_DEFINED +#define SkMSAN_DEFINED + +#include "include/core/SkTypes.h" + +#include + +// Typically declared in LLVM's msan_interface.h. Easier for us to just re-declare. +extern "C" { + void __msan_check_mem_is_initialized(const volatile void*, size_t); + void __msan_unpoison (const volatile void*, size_t); +} + +// Code that requires initialized inputs can call this to make it clear that +// the blame for use of uninitialized data belongs further up the call stack. +static inline void sk_msan_assert_initialized(const void* begin, const void* end) { +#if defined(__has_feature) + #if __has_feature(memory_sanitizer) + __msan_check_mem_is_initialized(begin, (const char*)end - (const char*)begin); + #endif +#endif +} + +// Lie to MSAN that this range of memory is initialized. +// This can hide serious problems if overused. Every use of this should refer to a bug. +static inline void sk_msan_mark_initialized(const void* begin, const void* end, const char* skbug) { + SkASSERT(skbug && 0 != strcmp(skbug, "")); +#if defined(__has_feature) + #if __has_feature(memory_sanitizer) + __msan_unpoison(begin, (const char*)end - (const char*)begin); + #endif +#endif +} + +#endif//SkMSAN_DEFINED diff --git a/gfx/skia/skia/src/core/SkMakeUnique.h b/gfx/skia/skia/src/core/SkMakeUnique.h new file mode 100644 index 0000000000..860ea2e8a7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMakeUnique.h @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMakeUnique_DEFINED +#define SkMakeUnique_DEFINED + +#include + +namespace skstd { + +// std::make_unique is in C++14 +template +std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +std::unique_ptr make_unique_default(size_t n) { + return std::unique_ptr(new typename std::remove_extent::type[n]); +} + +} + +#endif // SkMakeUnique_DEFINED diff --git a/gfx/skia/skia/src/core/SkMallocPixelRef.cpp b/gfx/skia/skia/src/core/SkMallocPixelRef.cpp new file mode 100644 index 0000000000..a9e14780cf --- /dev/null +++ b/gfx/skia/skia/src/core/SkMallocPixelRef.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMallocPixelRef.h" + +#include "include/core/SkData.h" +#include "include/core/SkImageInfo.h" +#include "include/private/SkMalloc.h" +#include "src/core/SkSafeMath.h" + +void* sk_calloc_throw(size_t count, size_t elemSize) { + return sk_calloc_throw(SkSafeMath::Mul(count, elemSize)); +} + +void* sk_malloc_throw(size_t count, size_t elemSize) { + return sk_malloc_throw(SkSafeMath::Mul(count, elemSize)); +} + +void* sk_realloc_throw(void* buffer, size_t count, size_t elemSize) { + return sk_realloc_throw(buffer, SkSafeMath::Mul(count, elemSize)); +} + +void* sk_malloc_canfail(size_t count, size_t elemSize) { + return sk_malloc_canfail(SkSafeMath::Mul(count, elemSize)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool is_valid(const SkImageInfo& info) { + if (info.width() < 0 || info.height() < 0 || + (unsigned)info.colorType() > (unsigned)kLastEnum_SkColorType || + (unsigned)info.alphaType() > (unsigned)kLastEnum_SkAlphaType) + { + return false; + } + return true; +} + +sk_sp SkMallocPixelRef::MakeAllocate(const SkImageInfo& info, size_t rowBytes) { + if (rowBytes == 0) { + rowBytes = info.minRowBytes(); + // rowBytes can still be zero, if it overflowed (width * bytesPerPixel > size_t) + // or if colortype is unknown + } + if (!is_valid(info) || !info.validRowBytes(rowBytes)) { + return nullptr; + } + size_t size = 0; + if (!info.isEmpty() && rowBytes) { + size = info.computeByteSize(rowBytes); + if (SkImageInfo::ByteSizeOverflowed(size)) { + return nullptr; + } + } + void* addr = sk_calloc_canfail(size); + if (nullptr == addr) { + return nullptr; + } + + struct PixelRef final : public SkPixelRef { + PixelRef(int w, int h, void* s, size_t r) : SkPixelRef(w, h, s, r) {} + ~PixelRef() override { sk_free(this->pixels()); } + }; + return sk_sp(new PixelRef(info.width(), info.height(), addr, rowBytes)); +} + +sk_sp SkMallocPixelRef::MakeWithData(const SkImageInfo& info, + size_t rowBytes, + sk_sp data) { + SkASSERT(data != nullptr); + if (!is_valid(info)) { + return nullptr; + } + // TODO: what should we return if computeByteSize returns 0? + // - the info was empty? + // - we overflowed computing the size? + if ((rowBytes < info.minRowBytes()) || (data->size() < info.computeByteSize(rowBytes))) { + return nullptr; + } + struct PixelRef final : public SkPixelRef { + sk_sp fData; + PixelRef(int w, int h, void* s, size_t r, sk_sp d) + : SkPixelRef(w, h, s, r), fData(std::move(d)) {} + }; + void* pixels = const_cast(data->data()); + sk_sp pr(new PixelRef(info.width(), info.height(), pixels, rowBytes, + std::move(data))); + pr->setImmutable(); // since we were created with (immutable) data + return pr; +} diff --git a/gfx/skia/skia/src/core/SkMask.cpp b/gfx/skia/skia/src/core/SkMask.cpp new file mode 100644 index 0000000000..76f093f982 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMask.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMask.h" + +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" +#include "src/core/SkSafeMath.h" + +/** returns the product if it is positive and fits in 31 bits. Otherwise this + returns 0. + */ +static int32_t safeMul32(int32_t a, int32_t b) { + int64_t size = sk_64_mul(a, b); + if (size > 0 && SkTFitsIn(size)) { + return size; + } + return 0; +} + +size_t SkMask::computeImageSize() const { + return safeMul32(fBounds.height(), fRowBytes); +} + +size_t SkMask::computeTotalImageSize() const { + size_t size = this->computeImageSize(); + if (fFormat == SkMask::k3D_Format) { + size = safeMul32(SkToS32(size), 3); + } + return size; +} + +/** We explicitly use this allocator for SkBimap pixels, so that we can + freely assign memory allocated by one class to the other. +*/ +uint8_t* SkMask::AllocImage(size_t size, AllocType at) { + size_t aligned_size = SkSafeMath::Align4(size); + unsigned flags = SK_MALLOC_THROW; + if (at == kZeroInit_Alloc) { + flags |= SK_MALLOC_ZERO_INITIALIZE; + } + return static_cast(sk_malloc_flags(aligned_size, flags)); +} + +/** We explicitly use this allocator for SkBimap pixels, so that we can + freely assign memory allocated by one class to the other. +*/ +void SkMask::FreeImage(void* image) { + sk_free(image); +} + +SkMask SkMask::PrepareDestination(int radiusX, int radiusY, const SkMask& src) { + SkSafeMath safe; + + SkMask dst; + // dstW = srcW + 2 * radiusX; + size_t dstW = safe.add(src.fBounds.width(), safe.add(radiusX, radiusX)); + // dstH = srcH + 2 * radiusY; + size_t dstH = safe.add(src.fBounds.height(), safe.add(radiusY, radiusY)); + + if (!SkTFitsIn(dstW) || !SkTFitsIn(dstH)) { + dst.fBounds.setEmpty(); + dst.fRowBytes = 0; + } else { + dst.fBounds.setWH(SkTo(dstW), SkTo(dstH)); + dst.fBounds.offset(src.fBounds.x(), src.fBounds.y()); + dst.fBounds.offset(-radiusX, -radiusY); + dst.fRowBytes = SkTo(dstW); + } + + dst.fImage = nullptr; + dst.fFormat = SkMask::kA8_Format; + + size_t toAlloc = safe.mul(dstW, dstH); + + if (safe && src.fImage != nullptr) { + dst.fImage = SkMask::AllocImage(toAlloc); + } + + return dst; +} + + +/////////////////////////////////////////////////////////////////////////////// + +static const int gMaskFormatToShift[] = { + ~0, // BW -- not supported + 0, // A8 + 0, // 3D + 2, // ARGB32 + 1, // LCD16 + 0, // SDF +}; + +static int maskFormatToShift(SkMask::Format format) { + SkASSERT((unsigned)format < SK_ARRAY_COUNT(gMaskFormatToShift)); + SkASSERT(SkMask::kBW_Format != format); + return gMaskFormatToShift[format]; +} + +void* SkMask::getAddr(int x, int y) const { + SkASSERT(kBW_Format != fFormat); + SkASSERT(fBounds.contains(x, y)); + SkASSERT(fImage); + + char* addr = (char*)fImage; + addr += (y - fBounds.fTop) * fRowBytes; + addr += (x - fBounds.fLeft) << maskFormatToShift(fFormat); + return addr; +} diff --git a/gfx/skia/skia/src/core/SkMask.h b/gfx/skia/skia/src/core/SkMask.h new file mode 100644 index 0000000000..29ae48e6ed --- /dev/null +++ b/gfx/skia/skia/src/core/SkMask.h @@ -0,0 +1,243 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMask_DEFINED +#define SkMask_DEFINED + +#include "include/core/SkRect.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTemplates.h" + +#include + +/** \class SkMask + SkMask is used to describe alpha bitmaps, either 1bit, 8bit, or + the 3-channel 3D format. These are passed to SkMaskFilter objects. +*/ +struct SkMask { + SkMask() : fImage(nullptr) {} + + enum Format { + kBW_Format, //!< 1bit per pixel mask (e.g. monochrome) + kA8_Format, //!< 8bits per pixel mask (e.g. antialiasing) + k3D_Format, //!< 3 8bit per pixl planes: alpha, mul, add + kARGB32_Format, //!< SkPMColor + kLCD16_Format, //!< 565 alpha for r/g/b + kSDF_Format, //!< 8bits representing signed distance field + }; + + enum { + kCountMaskFormats = kSDF_Format + 1 + }; + + uint8_t* fImage; + SkIRect fBounds; + uint32_t fRowBytes; + Format fFormat; + + static bool IsValidFormat(uint8_t format) { return format < kCountMaskFormats; } + + /** Returns true if the mask is empty: i.e. it has an empty bounds. + */ + bool isEmpty() const { return fBounds.isEmpty(); } + + /** Return the byte size of the mask, assuming only 1 plane. + Does not account for k3D_Format. For that, use computeTotalImageSize(). + If there is an overflow of 32bits, then returns 0. + */ + size_t computeImageSize() const; + + /** Return the byte size of the mask, taking into account + any extra planes (e.g. k3D_Format). + If there is an overflow of 32bits, then returns 0. + */ + size_t computeTotalImageSize() const; + + /** Returns the address of the byte that holds the specified bit. + Asserts that the mask is kBW_Format, and that x,y are in range. + x,y are in the same coordiate space as fBounds. + */ + uint8_t* getAddr1(int x, int y) const { + SkASSERT(kBW_Format == fFormat); + SkASSERT(fBounds.contains(x, y)); + SkASSERT(fImage != nullptr); + return fImage + ((x - fBounds.fLeft) >> 3) + (y - fBounds.fTop) * fRowBytes; + } + + /** Returns the address of the specified byte. + Asserts that the mask is kA8_Format, and that x,y are in range. + x,y are in the same coordiate space as fBounds. + */ + uint8_t* getAddr8(int x, int y) const { + SkASSERT(kA8_Format == fFormat || kSDF_Format == fFormat); + SkASSERT(fBounds.contains(x, y)); + SkASSERT(fImage != nullptr); + return fImage + x - fBounds.fLeft + (y - fBounds.fTop) * fRowBytes; + } + + /** + * Return the address of the specified 16bit mask. In the debug build, + * this asserts that the mask's format is kLCD16_Format, and that (x,y) + * are contained in the mask's fBounds. + */ + uint16_t* getAddrLCD16(int x, int y) const { + SkASSERT(kLCD16_Format == fFormat); + SkASSERT(fBounds.contains(x, y)); + SkASSERT(fImage != nullptr); + uint16_t* row = (uint16_t*)(fImage + (y - fBounds.fTop) * fRowBytes); + return row + (x - fBounds.fLeft); + } + + /** + * Return the address of the specified 32bit mask. In the debug build, + * this asserts that the mask's format is 32bits, and that (x,y) + * are contained in the mask's fBounds. + */ + uint32_t* getAddr32(int x, int y) const { + SkASSERT(kARGB32_Format == fFormat); + SkASSERT(fBounds.contains(x, y)); + SkASSERT(fImage != nullptr); + uint32_t* row = (uint32_t*)(fImage + (y - fBounds.fTop) * fRowBytes); + return row + (x - fBounds.fLeft); + } + + /** + * Returns the address of the specified pixel, computing the pixel-size + * at runtime based on the mask format. This will be slightly slower than + * using one of the routines where the format is implied by the name + * e.g. getAddr8 or getAddr32. + * + * x,y must be contained by the mask's bounds (this is asserted in the + * debug build, but not checked in the release build.) + * + * This should not be called with kBW_Format, as it will give unspecified + * results (and assert in the debug build). + */ + void* getAddr(int x, int y) const; + + enum AllocType { + kUninit_Alloc, + kZeroInit_Alloc, + }; + static uint8_t* AllocImage(size_t bytes, AllocType = kUninit_Alloc); + static void FreeImage(void* image); + + enum CreateMode { + kJustComputeBounds_CreateMode, //!< compute bounds and return + kJustRenderImage_CreateMode, //!< render into preallocate mask + kComputeBoundsAndRenderImage_CreateMode //!< compute bounds, alloc image and render into it + }; + + /** Iterates over the coverage values along a scanline in a given SkMask::Format. Provides + * constructor, copy constructor for creating + * operator++, operator-- for iterating over the coverage values on a scanline + * operator>>= to add row bytes + * operator* to get the coverage value at the current location + * operator< to compare two iterators + */ + template struct AlphaIter; + + /** + * Returns initial destination mask data padded by radiusX and radiusY + */ + static SkMask PrepareDestination(int radiusX, int radiusY, const SkMask& src); +}; + +template <> struct SkMask::AlphaIter { + AlphaIter(const uint8_t* ptr, int offset) : fPtr(ptr), fOffset(7 - offset) {} + AlphaIter(const AlphaIter& that) : fPtr(that.fPtr), fOffset(that.fOffset) {} + AlphaIter& operator++() { + if (0 < fOffset ) { + --fOffset; + } else { + ++fPtr; + fOffset = 7; + } + return *this; + } + AlphaIter& operator--() { + if (fOffset < 7) { + ++fOffset; + } else { + --fPtr; + fOffset = 0; + } + return *this; + } + AlphaIter& operator>>=(uint32_t rb) { + fPtr = SkTAddOffset(fPtr, rb); + return *this; + } + uint8_t operator*() const { return ((*fPtr) >> fOffset) & 1 ? 0xFF : 0; } + bool operator<(const AlphaIter& that) const { + return fPtr < that.fPtr || (fPtr == that.fPtr && fOffset > that.fOffset); + } + const uint8_t* fPtr; + int fOffset; +}; + +template <> struct SkMask::AlphaIter { + AlphaIter(const uint8_t* ptr) : fPtr(ptr) {} + AlphaIter(const AlphaIter& that) : fPtr(that.fPtr) {} + AlphaIter& operator++() { ++fPtr; return *this; } + AlphaIter& operator--() { --fPtr; return *this; } + AlphaIter& operator>>=(uint32_t rb) { + fPtr = SkTAddOffset(fPtr, rb); + return *this; + } + uint8_t operator*() const { return *fPtr; } + bool operator<(const AlphaIter& that) const { return fPtr < that.fPtr; } + const uint8_t* fPtr; +}; + +template <> struct SkMask::AlphaIter { + AlphaIter(const uint32_t* ptr) : fPtr(ptr) {} + AlphaIter(const AlphaIter& that) : fPtr(that.fPtr) {} + AlphaIter& operator++() { ++fPtr; return *this; } + AlphaIter& operator--() { --fPtr; return *this; } + AlphaIter& operator>>=(uint32_t rb) { + fPtr = SkTAddOffset(fPtr, rb); + return *this; + } + uint8_t operator*() const { return SkGetPackedA32(*fPtr); } + bool operator<(const AlphaIter& that) const { return fPtr < that.fPtr; } + const uint32_t* fPtr; +}; + +template <> struct SkMask::AlphaIter { + AlphaIter(const uint16_t* ptr) : fPtr(ptr) {} + AlphaIter(const AlphaIter& that) : fPtr(that.fPtr) {} + AlphaIter& operator++() { ++fPtr; return *this; } + AlphaIter& operator--() { --fPtr; return *this; } + AlphaIter& operator>>=(uint32_t rb) { + fPtr = SkTAddOffset(fPtr, rb); + return *this; + } + uint8_t operator*() const { + unsigned packed = *fPtr; + unsigned r = SkPacked16ToR32(packed); + unsigned g = SkPacked16ToG32(packed); + unsigned b = SkPacked16ToB32(packed); + return (r + g + b) / 3; + } + bool operator<(const AlphaIter& that) const { return fPtr < that.fPtr; } + const uint16_t* fPtr; +}; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * \using SkAutoMaskImage + * + * Stack class used to manage the fImage buffer in a SkMask. + * When this object loses scope, the buffer is freed with SkMask::FreeImage(). + */ +using SkAutoMaskFreeImage = std::unique_ptr>; +#define SkAutoMaskFreeImage(...) SK_REQUIRE_LOCAL_VAR(SkAutoMaskFreeImage) + +#endif diff --git a/gfx/skia/skia/src/core/SkMaskBlurFilter.cpp b/gfx/skia/skia/src/core/SkMaskBlurFilter.cpp new file mode 100644 index 0000000000..66cc1147db --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskBlurFilter.cpp @@ -0,0 +1,1051 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMaskBlurFilter.h" + +#include "include/core/SkColorPriv.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkNx.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkGaussFilter.h" + +#include +#include + +namespace { +static const double kPi = 3.14159265358979323846264338327950288; + +class PlanGauss final { +public: + explicit PlanGauss(double sigma) { + auto possibleWindow = static_cast(floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5)); + auto window = std::max(1, possibleWindow); + + fPass0Size = window - 1; + fPass1Size = window - 1; + fPass2Size = (window & 1) == 1 ? window - 1 : window; + + // Calculating the border is tricky. I will go through the odd case which is simpler, and + // then through the even case. Given a stack of filters seven wide for the odd case of + // three passes. + // + // S + // aaaAaaa + // bbbBbbb + // cccCccc + // D + // + // The furthest changed pixel is when the filters are in the following configuration. + // + // S + // aaaAaaa + // bbbBbbb + // cccCccc + // D + // + // The A pixel is calculated using the value S, the B uses A, and the C uses B, and + // finally D is C. So, with a window size of seven the border is nine. In general, the + // border is 3*((window - 1)/2). + // + // For even cases the filter stack is more complicated. The spec specifies two passes + // of even filters and a final pass of odd filters. A stack for a width of six looks like + // this. + // + // S + // aaaAaa + // bbBbbb + // cccCccc + // D + // + // The furthest pixel looks like this. + // + // S + // aaaAaa + // bbBbbb + // cccCccc + // D + // + // For a window of size, the border value is seven. In general the border is 3 * + // (window/2) -1. + fBorder = (window & 1) == 1 ? 3 * ((window - 1) / 2) : 3 * (window / 2) - 1; + fSlidingWindow = 2 * fBorder + 1; + + // If the window is odd then the divisor is just window ^ 3 otherwise, + // it is window * window * (window + 1) = window ^ 2 + window ^ 3; + auto window2 = window * window; + auto window3 = window2 * window; + auto divisor = (window & 1) == 1 ? window3 : window3 + window2; + + fWeight = static_cast(round(1.0 / divisor * (1ull << 32))); + } + + size_t bufferSize() const { return fPass0Size + fPass1Size + fPass2Size; } + + int border() const { return fBorder; } + +public: + class Scan { + public: + Scan(uint64_t weight, int noChangeCount, + uint32_t* buffer0, uint32_t* buffer0End, + uint32_t* buffer1, uint32_t* buffer1End, + uint32_t* buffer2, uint32_t* buffer2End) + : fWeight{weight} + , fNoChangeCount{noChangeCount} + , fBuffer0{buffer0} + , fBuffer0End{buffer0End} + , fBuffer1{buffer1} + , fBuffer1End{buffer1End} + , fBuffer2{buffer2} + , fBuffer2End{buffer2End} + { } + + template void blur(const AlphaIter srcBegin, const AlphaIter srcEnd, + uint8_t* dst, int dstStride, uint8_t* dstEnd) const { + auto buffer0Cursor = fBuffer0; + auto buffer1Cursor = fBuffer1; + auto buffer2Cursor = fBuffer2; + + memset(fBuffer0, 0x00, (fBuffer2End - fBuffer0) * sizeof(*fBuffer0)); + + uint32_t sum0 = 0; + uint32_t sum1 = 0; + uint32_t sum2 = 0; + + // Consume the source generating pixels. + for (AlphaIter src = srcBegin; src < srcEnd; ++src, dst += dstStride) { + uint32_t leadingEdge = *src; + sum0 += leadingEdge; + sum1 += sum0; + sum2 += sum1; + + *dst = this->finalScale(sum2); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < fBuffer2End ? buffer2Cursor + 1 : fBuffer2; + + sum1 -= *buffer1Cursor; + *buffer1Cursor = sum0; + buffer1Cursor = (buffer1Cursor + 1) < fBuffer1End ? buffer1Cursor + 1 : fBuffer1; + + sum0 -= *buffer0Cursor; + *buffer0Cursor = leadingEdge; + buffer0Cursor = (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : fBuffer0; + } + + // The leading edge is off the right side of the mask. + for (int i = 0; i < fNoChangeCount; i++) { + uint32_t leadingEdge = 0; + sum0 += leadingEdge; + sum1 += sum0; + sum2 += sum1; + + *dst = this->finalScale(sum2); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < fBuffer2End ? buffer2Cursor + 1 : fBuffer2; + + sum1 -= *buffer1Cursor; + *buffer1Cursor = sum0; + buffer1Cursor = (buffer1Cursor + 1) < fBuffer1End ? buffer1Cursor + 1 : fBuffer1; + + sum0 -= *buffer0Cursor; + *buffer0Cursor = leadingEdge; + buffer0Cursor = (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : fBuffer0; + + dst += dstStride; + } + + // Starting from the right, fill in the rest of the buffer. + memset(fBuffer0, 0, (fBuffer2End - fBuffer0) * sizeof(*fBuffer0)); + + sum0 = sum1 = sum2 = 0; + + uint8_t* dstCursor = dstEnd; + AlphaIter src = srcEnd; + while (dstCursor > dst) { + dstCursor -= dstStride; + uint32_t leadingEdge = *(--src); + sum0 += leadingEdge; + sum1 += sum0; + sum2 += sum1; + + *dstCursor = this->finalScale(sum2); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < fBuffer2End ? buffer2Cursor + 1 : fBuffer2; + + sum1 -= *buffer1Cursor; + *buffer1Cursor = sum0; + buffer1Cursor = (buffer1Cursor + 1) < fBuffer1End ? buffer1Cursor + 1 : fBuffer1; + + sum0 -= *buffer0Cursor; + *buffer0Cursor = leadingEdge; + buffer0Cursor = (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : fBuffer0; + } + } + + private: + static constexpr uint64_t kHalf = static_cast(1) << 31; + + uint8_t finalScale(uint32_t sum) const { + return SkTo((fWeight * sum + kHalf) >> 32); + } + + uint64_t fWeight; + int fNoChangeCount; + uint32_t* fBuffer0; + uint32_t* fBuffer0End; + uint32_t* fBuffer1; + uint32_t* fBuffer1End; + uint32_t* fBuffer2; + uint32_t* fBuffer2End; + }; + + Scan makeBlurScan(int width, uint32_t* buffer) const { + uint32_t* buffer0, *buffer0End, *buffer1, *buffer1End, *buffer2, *buffer2End; + buffer0 = buffer; + buffer0End = buffer1 = buffer0 + fPass0Size; + buffer1End = buffer2 = buffer1 + fPass1Size; + buffer2End = buffer2 + fPass2Size; + int noChangeCount = fSlidingWindow > width ? fSlidingWindow - width : 0; + + return Scan( + fWeight, noChangeCount, + buffer0, buffer0End, + buffer1, buffer1End, + buffer2, buffer2End); + } + + uint64_t fWeight; + int fBorder; + int fSlidingWindow; + int fPass0Size; + int fPass1Size; + int fPass2Size; +}; + +} // namespace + +// NB 135 is the largest sigma that will not cause a buffer full of 255 mask values to overflow +// using the Gauss filter. It also limits the size of buffers used hold intermediate values. The +// additional + 1 added to window represents adding one more leading element before subtracting the +// trailing element. +// Explanation of maximums: +// sum0 = (window + 1) * 255 +// sum1 = (window + 1) * sum0 -> (window + 1) * (window + 1) * 255 +// sum2 = (window + 1) * sum1 -> (window + 1) * (window + 1) * (window + 1) * 255 -> window^3 * 255 +// +// The value (window + 1)^3 * 255 must fit in a uint32_t. So, +// (window + 1)^3 * 255 < 2^32. window = 255. +// +// window = floor(sigma * 3 * sqrt(2 * kPi) / 4) +// For window <= 255, the largest value for sigma is 135. +SkMaskBlurFilter::SkMaskBlurFilter(double sigmaW, double sigmaH) + : fSigmaW{SkTPin(sigmaW, 0.0, 135.0)} + , fSigmaH{SkTPin(sigmaH, 0.0, 135.0)} +{ + SkASSERT(sigmaW >= 0); + SkASSERT(sigmaH >= 0); +} + +bool SkMaskBlurFilter::hasNoBlur() const { + return (3 * fSigmaW <= 1) && (3 * fSigmaH <= 1); +} + +// We favor A8 masks, and if we need to work with another format, we'll convert to A8 first. +// Each of these converts width (up to 8) mask values to A8. +static void bw_to_a8(uint8_t* a8, const uint8_t* from, int width) { + SkASSERT(0 < width && width <= 8); + + uint8_t masks = *from; + for (int i = 0; i < width; ++i) { + a8[i] = (masks >> (7 - i)) & 1 ? 0xFF + : 0x00; + } +} +static void lcd_to_a8(uint8_t* a8, const uint8_t* from, int width) { + SkASSERT(0 < width && width <= 8); + + for (int i = 0; i < width; ++i) { + unsigned rgb = reinterpret_cast(from)[i], + r = SkPacked16ToR32(rgb), + g = SkPacked16ToG32(rgb), + b = SkPacked16ToB32(rgb); + a8[i] = (r + g + b) / 3; + } +} +static void argb32_to_a8(uint8_t* a8, const uint8_t* from, int width) { + SkASSERT(0 < width && width <= 8); + for (int i = 0; i < width; ++i) { + uint32_t rgba = reinterpret_cast(from)[i]; + a8[i] = SkGetPackedA32(rgba); + } +} +using ToA8 = decltype(bw_to_a8); + +static Sk8h load(const uint8_t* from, int width, ToA8* toA8) { + // Our fast path is a full 8-byte load of A8. + // So we'll conditionally handle the two slow paths using tmp: + // - if we have a function to convert another mask to A8, use it; + // - if not but we have less than 8 bytes to load, load them one at a time. + uint8_t tmp[8] = {0,0,0,0, 0,0,0,0}; + if (toA8) { + toA8(tmp, from, width); + from = tmp; + } else if (width < 8) { + for (int i = 0; i < width; ++i) { + tmp[i] = from[i]; + } + from = tmp; + } + + // Load A8 and convert to 8.8 fixed-point. + return SkNx_cast(Sk8b::Load(from)) << 8; +} + +static void store(uint8_t* to, const Sk8h& v, int width) { + Sk8b b = SkNx_cast(v >> 8); + if (width == 8) { + b.store(to); + } else { + uint8_t buffer[8]; + b.store(buffer); + for (int i = 0; i < width; i++) { + to[i] = buffer[i]; + } + } +}; + +static constexpr uint16_t _____ = 0u; +static constexpr uint16_t kHalf = 0x80u; + +// In all the blur_x_radius_N and blur_y_radius_N functions the gaussian values are encoded +// in 0.16 format, none of the values is greater than one. The incoming mask values are in 8.8 +// format. The resulting multiply has a 8.24 format, by the mulhi truncates the lower 16 bits +// resulting in a 8.8 format. +// +// The blur_x_radius_N function below blur along a row of pixels using a kernel with radius N. This +// system is setup to minimize the number of multiplies needed. +// +// Explanation: +// Blurring a specific mask value is given by the following equation where D_n is the resulting +// mask value and S_n is the source value. The example below is for a filter with a radius of 1 +// and a width of 3 (radius == (width-1)/2). The indexes for the source and destination are +// aligned. The filter is given by G_n where n is the symmetric filter value. +// +// D[n] = S[n-1]*G[1] + S[n]*G[0] + S[n+1]*G[1]. +// +// We can start the source index at an offset relative to the destination separated by the +// radius. This results in a non-traditional restating of the above filter. +// +// D[n] = S[n]*G[1] + S[n+1]*G[0] + S[n+2]*G[1] +// +// If we look at three specific consecutive destinations the following equations result: +// +// D[5] = S[5]*G[1] + S[6]*G[0] + S[7]*G[1] +// D[7] = S[6]*G[1] + S[7]*G[0] + S[8]*G[1] +// D[8] = S[7]*G[1] + S[8]*G[0] + S[9]*G[1]. +// +// In the above equations, notice that S[7] is used in all three. In particular, two values are +// used: S[7]*G[0] and S[7]*G[1]. So, S[7] is only multiplied twice, but used in D[5], D[6] and +// D[7]. +// +// From the point of view of a source value we end up with the following three equations. +// +// Given S[7]: +// D[5] += S[7]*G[1] +// D[6] += S[7]*G[0] +// D[7] += S[7]*G[1] +// +// In General: +// D[n] += S[n]*G[1] +// D[n+1] += S[n]*G[0] +// D[n+2] += S[n]*G[1] +// +// Now these equations can be ganged using SIMD to form: +// D[n..n+7] += S[n..n+7]*G[1] +// D[n+1..n+8] += S[n..n+7]*G[0] +// D[n+2..n+9] += S[n..n+7]*G[1] +// The next set of values becomes. +// D[n+8..n+15] += S[n+8..n+15]*G[1] +// D[n+9..n+16] += S[n+8..n+15]*G[0] +// D[n+10..n+17] += S[n+8..n+15]*G[1] +// You can see that the D[n+8] and D[n+9] values overlap the two sets, using parts of both +// S[n..7] and S[n+8..n+15]. +// +// Just one more transformation allows the code to maintain all working values in +// registers. I introduce the notation {0, S[n..n+7] * G[k]} to mean that the value where 0 is +// prepended to the array of values to form {0, S[n] * G[k], ..., S[n+7]*G[k]}. +// +// D[n..n+7] += S[n..n+7] * G[1] +// D[n..n+8] += {0, S[n..n+7] * G[0]} +// D[n..n+9] += {0, 0, S[n..n+7] * G[1]} +// +// Now we can encode D[n..n+7] in a single Sk8h register called d0, and D[n+8..n+15] in a +// register d8. In addition, S[0..n+7] becomes s0. +// +// The translation of the {0, S[n..n+7] * G[k]} is translated in the following way below. +// +// Sk8h v0 = s0*G[0] +// Sk8h v1 = s0*G[1] +// /* D[n..n+7] += S[n..n+7] * G[1] */ +// d0 += v1; +// /* D[n..n+8] += {0, S[n..n+7] * G[0]} */ +// d0 += {_____, v0[0], v0[1], v0[2], v0[3], v0[4], v0[5], v0[6]} +// d1 += {v0[7], _____, _____, _____, _____, _____, _____, _____} +// /* D[n..n+9] += {0, 0, S[n..n+7] * G[1]} */ +// d0 += {_____, _____, v1[0], v1[1], v1[2], v1[3], v1[4], v1[5]} +// d1 += {v1[6], v1[7], _____, _____, _____, _____, _____, _____} +// Where we rely on the compiler to generate efficient code for the {____, n, ....} notation. + +static void blur_x_radius_1( + const Sk8h& s0, + const Sk8h& g0, const Sk8h& g1, const Sk8h&, const Sk8h&, const Sk8h&, + Sk8h* d0, Sk8h* d8) { + + auto v1 = s0.mulHi(g1); + auto v0 = s0.mulHi(g0); + + // D[n..n+7] += S[n..n+7] * G[1] + *d0 += v1; + + //D[n..n+8] += {0, S[n..n+7] * G[0]} + *d0 += Sk8h{_____, v0[0], v0[1], v0[2], v0[3], v0[4], v0[5], v0[6]}; + *d8 += Sk8h{v0[7], _____, _____, _____, _____, _____, _____, _____}; + + // D[n..n+9] += {0, 0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, _____, v1[0], v1[1], v1[2], v1[3], v1[4], v1[5]}; + *d8 += Sk8h{v1[6], v1[7], _____, _____, _____, _____, _____, _____}; + +} + +static void blur_x_radius_2( + const Sk8h& s0, + const Sk8h& g0, const Sk8h& g1, const Sk8h& g2, const Sk8h&, const Sk8h&, + Sk8h* d0, Sk8h* d8) { + auto v0 = s0.mulHi(g0); + auto v1 = s0.mulHi(g1); + auto v2 = s0.mulHi(g2); + + // D[n..n+7] += S[n..n+7] * G[2] + *d0 += v2; + + // D[n..n+8] += {0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, v1[0], v1[1], v1[2], v1[3], v1[4], v1[5], v1[6]}; + *d8 += Sk8h{v1[7], _____, _____, _____, _____, _____, _____, _____}; + + // D[n..n+9] += {0, 0, S[n..n+7] * G[0]} + *d0 += Sk8h{_____, _____, v0[0], v0[1], v0[2], v0[3], v0[4], v0[5]}; + *d8 += Sk8h{v0[6], v0[7], _____, _____, _____, _____, _____, _____}; + + // D[n..n+10] += {0, 0, 0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, _____, _____, v1[0], v1[1], v1[2], v1[3], v1[4]}; + *d8 += Sk8h{v1[5], v1[6], v1[7], _____, _____, _____, _____, _____}; + + // D[n..n+11] += {0, 0, 0, 0, S[n..n+7] * G[2]} + *d0 += Sk8h{_____, _____, _____, _____, v2[0], v2[1], v2[2], v2[3]}; + *d8 += Sk8h{v2[4], v2[5], v2[6], v2[7], _____, _____, _____, _____}; +} + +static void blur_x_radius_3( + const Sk8h& s0, + const Sk8h& gauss0, const Sk8h& gauss1, const Sk8h& gauss2, const Sk8h& gauss3, const Sk8h&, + Sk8h* d0, Sk8h* d8) { + auto v0 = s0.mulHi(gauss0); + auto v1 = s0.mulHi(gauss1); + auto v2 = s0.mulHi(gauss2); + auto v3 = s0.mulHi(gauss3); + + // D[n..n+7] += S[n..n+7] * G[3] + *d0 += v3; + + // D[n..n+8] += {0, S[n..n+7] * G[2]} + *d0 += Sk8h{_____, v2[0], v2[1], v2[2], v2[3], v2[4], v2[5], v2[6]}; + *d8 += Sk8h{v2[7], _____, _____, _____, _____, _____, _____, _____}; + + // D[n..n+9] += {0, 0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, _____, v1[0], v1[1], v1[2], v1[3], v1[4], v1[5]}; + *d8 += Sk8h{v1[6], v1[7], _____, _____, _____, _____, _____, _____}; + + // D[n..n+10] += {0, 0, 0, S[n..n+7] * G[0]} + *d0 += Sk8h{_____, _____, _____, v0[0], v0[1], v0[2], v0[3], v0[4]}; + *d8 += Sk8h{v0[5], v0[6], v0[7], _____, _____, _____, _____, _____}; + + // D[n..n+11] += {0, 0, 0, 0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, _____, _____, _____, v1[0], v1[1], v1[2], v1[3]}; + *d8 += Sk8h{v1[4], v1[5], v1[6], v1[7], _____, _____, _____, _____}; + + // D[n..n+12] += {0, 0, 0, 0, 0, S[n..n+7] * G[2]} + *d0 += Sk8h{_____, _____, _____, _____, _____, v2[0], v2[1], v2[2]}; + *d8 += Sk8h{v2[3], v2[4], v2[5], v2[6], v2[7], _____, _____, _____}; + + // D[n..n+13] += {0, 0, 0, 0, 0, 0, S[n..n+7] * G[3]} + *d0 += Sk8h{_____, _____, _____, _____, _____, _____, v3[0], v3[1]}; + *d8 += Sk8h{v3[2], v3[3], v3[4], v3[5], v3[6], v3[7], _____, _____}; +} + +static void blur_x_radius_4( + const Sk8h& s0, + const Sk8h& gauss0, + const Sk8h& gauss1, + const Sk8h& gauss2, + const Sk8h& gauss3, + const Sk8h& gauss4, + Sk8h* d0, Sk8h* d8) { + auto v0 = s0.mulHi(gauss0); + auto v1 = s0.mulHi(gauss1); + auto v2 = s0.mulHi(gauss2); + auto v3 = s0.mulHi(gauss3); + auto v4 = s0.mulHi(gauss4); + + // D[n..n+7] += S[n..n+7] * G[4] + *d0 += v4; + + // D[n..n+8] += {0, S[n..n+7] * G[3]} + *d0 += Sk8h{_____, v3[0], v3[1], v3[2], v3[3], v3[4], v3[5], v3[6]}; + *d8 += Sk8h{v3[7], _____, _____, _____, _____, _____, _____, _____}; + + // D[n..n+9] += {0, 0, S[n..n+7] * G[2]} + *d0 += Sk8h{_____, _____, v2[0], v2[1], v2[2], v2[3], v2[4], v2[5]}; + *d8 += Sk8h{v2[6], v2[7], _____, _____, _____, _____, _____, _____}; + + // D[n..n+10] += {0, 0, 0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, _____, _____, v1[0], v1[1], v1[2], v1[3], v1[4]}; + *d8 += Sk8h{v1[5], v1[6], v1[7], _____, _____, _____, _____, _____}; + + // D[n..n+11] += {0, 0, 0, 0, S[n..n+7] * G[0]} + *d0 += Sk8h{_____, _____, _____, _____, v0[0], v0[1], v0[2], v0[3]}; + *d8 += Sk8h{v0[4], v0[5], v0[6], v0[7], _____, _____, _____, _____}; + + // D[n..n+12] += {0, 0, 0, 0, 0, S[n..n+7] * G[1]} + *d0 += Sk8h{_____, _____, _____, _____, _____, v1[0], v1[1], v1[2]}; + *d8 += Sk8h{v1[3], v1[4], v1[5], v1[6], v1[7], _____, _____, _____}; + + // D[n..n+13] += {0, 0, 0, 0, 0, 0, S[n..n+7] * G[2]} + *d0 += Sk8h{_____, _____, _____, _____, _____, _____, v2[0], v2[1]}; + *d8 += Sk8h{v2[2], v2[3], v2[4], v2[5], v2[6], v2[7], _____, _____}; + + // D[n..n+14] += {0, 0, 0, 0, 0, 0, 0, S[n..n+7] * G[3]} + *d0 += Sk8h{_____, _____, _____, _____, _____, _____, _____, v3[0]}; + *d8 += Sk8h{v3[1], v3[2], v3[3], v3[4], v3[5], v3[6], v3[7], _____}; + + // D[n..n+15] += {0, 0, 0, 0, 0, 0, 0, 0, S[n..n+7] * G[4]} + *d8 += v4; +} + +using BlurX = decltype(blur_x_radius_1); + +// BlurX will only be one of the functions blur_x_radius_(1|2|3|4). +static void blur_row( + BlurX blur, + const Sk8h& g0, const Sk8h& g1, const Sk8h& g2, const Sk8h& g3, const Sk8h& g4, + const uint8_t* src, int srcW, + uint8_t* dst, int dstW) { + // Clear the buffer to handle summing wider than source. + Sk8h d0{kHalf}, d8{kHalf}; + + // Go by multiples of 8 in src. + int x = 0; + for (; x <= srcW - 8; x += 8) { + blur(load(src, 8, nullptr), g0, g1, g2, g3, g4, &d0, &d8); + + store(dst, d0, 8); + + d0 = d8; + d8 = Sk8h{kHalf}; + + src += 8; + dst += 8; + } + + // There are src values left, but the remainder of src values is not a multiple of 8. + int srcTail = srcW - x; + if (srcTail > 0) { + + blur(load(src, srcTail, nullptr), g0, g1, g2, g3, g4, &d0, &d8); + + int dstTail = std::min(8, dstW - x); + store(dst, d0, dstTail); + + d0 = d8; + dst += dstTail; + x += dstTail; + } + + // There are dst mask values to complete. + int dstTail = dstW - x; + if (dstTail > 0) { + store(dst, d0, dstTail); + } +} + +// BlurX will only be one of the functions blur_x_radius_(1|2|3|4). +static void blur_x_rect(BlurX blur, + uint16_t* gauss, + const uint8_t* src, size_t srcStride, int srcW, + uint8_t* dst, size_t dstStride, int dstW, int dstH) { + + Sk8h g0{gauss[0]}, + g1{gauss[1]}, + g2{gauss[2]}, + g3{gauss[3]}, + g4{gauss[4]}; + + // Blur *ALL* the rows. + for (int y = 0; y < dstH; y++) { + blur_row(blur, g0, g1, g2, g3, g4, src, srcW, dst, dstW); + src += srcStride; + dst += dstStride; + } +} + +static void direct_blur_x(int radius, uint16_t* gauss, + const uint8_t* src, size_t srcStride, int srcW, + uint8_t* dst, size_t dstStride, int dstW, int dstH) { + + switch (radius) { + case 1: + blur_x_rect(blur_x_radius_1, gauss, src, srcStride, srcW, dst, dstStride, dstW, dstH); + break; + + case 2: + blur_x_rect(blur_x_radius_2, gauss, src, srcStride, srcW, dst, dstStride, dstW, dstH); + break; + + case 3: + blur_x_rect(blur_x_radius_3, gauss, src, srcStride, srcW, dst, dstStride, dstW, dstH); + break; + + case 4: + blur_x_rect(blur_x_radius_4, gauss, src, srcStride, srcW, dst, dstStride, dstW, dstH); + break; + + default: + SkASSERTF(false, "The radius %d is not handled\n", radius); + } +} + +// The operations of the blur_y_radius_N functions work on a theme similar to the blur_x_radius_N +// functions, but end up being simpler because there is no complicated shift of registers. We +// start with the non-traditional form of the gaussian filter. In the following r is the value +// when added generates the next value in the column. +// +// D[n+0r] = S[n+0r]*G[1] +// + S[n+1r]*G[0] +// + S[n+2r]*G[1] +// +// Expanding out in a way similar to blur_x_radius_N for specific values of n. +// +// D[n+0r] = S[n-2r]*G[1] + S[n-1r]*G[0] + S[n+0r]*G[1] +// D[n+1r] = S[n-1r]*G[1] + S[n+0r]*G[0] + S[n+1r]*G[1] +// D[n+2r] = S[n+0r]*G[1] + S[n+1r]*G[0] + S[n+2r]*G[1] +// +// We can see that S[n+0r] is in all three D[] equations, but is only multiplied twice. Now we +// can look at the calculation form the point of view of a source value. +// +// Given S[n+0r]: +// D[n+0r] += S[n+0r]*G[1]; +// /* D[n+0r] is done and can be stored now. */ +// D[n+1r] += S[n+0r]*G[0]; +// D[n+2r] = S[n+0r]*G[1]; +// +// Remember, by induction, that D[n+0r] == S[n-2r]*G[1] + S[n-1r]*G[0] before adding in +// S[n+0r]*G[1]. So, after the addition D[n+0r] has finished calculation and can be stored. Also, +// notice that D[n+2r] is receiving its first value from S[n+0r]*G[1] and is not added in. Notice +// how values flow in the following two iterations in source. +// +// D[n+0r] += S[n+0r]*G[1] +// D[n+1r] += S[n+0r]*G[0] +// D[n+2r] = S[n+0r]*G[1] +// /* ------- */ +// D[n+1r] += S[n+1r]*G[1] +// D[n+2r] += S[n+1r]*G[0] +// D[n+3r] = S[n+1r]*G[1] +// +// Instead of using memory we can introduce temporaries d01 and d12. The update step changes +// to the following. +// +// answer = d01 + S[n+0r]*G[1] +// d01 = d12 + S[n+0r]*G[0] +// d12 = S[n+0r]*G[1] +// return answer +// +// Finally, this can be ganged into SIMD style. +// answer[0..7] = d01[0..7] + S[n+0r..n+0r+7]*G[1] +// d01[0..7] = d12[0..7] + S[n+0r..n+0r+7]*G[0] +// d12[0..7] = S[n+0r..n+0r+7]*G[1] +// return answer[0..7] +static Sk8h blur_y_radius_1( + const Sk8h& s0, + const Sk8h& g0, const Sk8h& g1, const Sk8h&, const Sk8h&, const Sk8h&, + Sk8h* d01, Sk8h* d12, Sk8h*, Sk8h*, Sk8h*, Sk8h*, Sk8h*, Sk8h*) { + auto v0 = s0.mulHi(g0); + auto v1 = s0.mulHi(g1); + + Sk8h answer = *d01 + v1; + *d01 = *d12 + v0; + *d12 = v1 + kHalf; + + return answer; +} + +static Sk8h blur_y_radius_2( + const Sk8h& s0, + const Sk8h& g0, const Sk8h& g1, const Sk8h& g2, const Sk8h&, const Sk8h&, + Sk8h* d01, Sk8h* d12, Sk8h* d23, Sk8h* d34, Sk8h*, Sk8h*, Sk8h*, Sk8h*) { + auto v0 = s0.mulHi(g0); + auto v1 = s0.mulHi(g1); + auto v2 = s0.mulHi(g2); + + Sk8h answer = *d01 + v2; + *d01 = *d12 + v1; + *d12 = *d23 + v0; + *d23 = *d34 + v1; + *d34 = v2 + kHalf; + + return answer; +} + +static Sk8h blur_y_radius_3( + const Sk8h& s0, + const Sk8h& g0, const Sk8h& g1, const Sk8h& g2, const Sk8h& g3, const Sk8h&, + Sk8h* d01, Sk8h* d12, Sk8h* d23, Sk8h* d34, Sk8h* d45, Sk8h* d56, Sk8h*, Sk8h*) { + auto v0 = s0.mulHi(g0); + auto v1 = s0.mulHi(g1); + auto v2 = s0.mulHi(g2); + auto v3 = s0.mulHi(g3); + + Sk8h answer = *d01 + v3; + *d01 = *d12 + v2; + *d12 = *d23 + v1; + *d23 = *d34 + v0; + *d34 = *d45 + v1; + *d45 = *d56 + v2; + *d56 = v3 + kHalf; + + return answer; +} + +static Sk8h blur_y_radius_4( + const Sk8h& s0, + const Sk8h& g0, const Sk8h& g1, const Sk8h& g2, const Sk8h& g3, const Sk8h& g4, + Sk8h* d01, Sk8h* d12, Sk8h* d23, Sk8h* d34, Sk8h* d45, Sk8h* d56, Sk8h* d67, Sk8h* d78) { + auto v0 = s0.mulHi(g0); + auto v1 = s0.mulHi(g1); + auto v2 = s0.mulHi(g2); + auto v3 = s0.mulHi(g3); + auto v4 = s0.mulHi(g4); + + Sk8h answer = *d01 + v4; + *d01 = *d12 + v3; + *d12 = *d23 + v2; + *d23 = *d34 + v1; + *d34 = *d45 + v0; + *d45 = *d56 + v1; + *d56 = *d67 + v2; + *d67 = *d78 + v3; + *d78 = v4 + kHalf; + + return answer; +} + +using BlurY = decltype(blur_y_radius_1); + +// BlurY will be one of blur_y_radius_(1|2|3|4). +static void blur_column( + ToA8 toA8, + BlurY blur, int radius, int width, + const Sk8h& g0, const Sk8h& g1, const Sk8h& g2, const Sk8h& g3, const Sk8h& g4, + const uint8_t* src, size_t srcRB, int srcH, + uint8_t* dst, size_t dstRB) { + Sk8h d01{kHalf}, d12{kHalf}, d23{kHalf}, d34{kHalf}, + d45{kHalf}, d56{kHalf}, d67{kHalf}, d78{kHalf}; + + auto flush = [&](uint8_t* to, const Sk8h& v0, const Sk8h& v1) { + store(to, v0, width); + to += dstRB; + store(to, v1, width); + return to + dstRB; + }; + + for (int y = 0; y < srcH; y += 1) { + auto s = load(src, width, toA8); + auto b = blur(s, + g0, g1, g2, g3, g4, + &d01, &d12, &d23, &d34, &d45, &d56, &d67, &d78); + store(dst, b, width); + src += srcRB; + dst += dstRB; + } + + if (radius >= 1) { + dst = flush(dst, d01, d12); + } + if (radius >= 2) { + dst = flush(dst, d23, d34); + } + if (radius >= 3) { + dst = flush(dst, d45, d56); + } + if (radius >= 4) { + flush(dst, d67, d78); + } +} + +// BlurY will be one of blur_y_radius_(1|2|3|4). +static void blur_y_rect(ToA8 toA8, const int strideOf8, + BlurY blur, int radius, uint16_t *gauss, + const uint8_t *src, size_t srcRB, int srcW, int srcH, + uint8_t *dst, size_t dstRB) { + + Sk8h g0{gauss[0]}, + g1{gauss[1]}, + g2{gauss[2]}, + g3{gauss[3]}, + g4{gauss[4]}; + + int x = 0; + for (; x <= srcW - 8; x += 8) { + blur_column(toA8, blur, radius, 8, + g0, g1, g2, g3, g4, + src, srcRB, srcH, + dst, dstRB); + src += strideOf8; + dst += 8; + } + + int xTail = srcW - x; + if (xTail > 0) { + blur_column(toA8, blur, radius, xTail, + g0, g1, g2, g3, g4, + src, srcRB, srcH, + dst, dstRB); + } +} + +static void direct_blur_y(ToA8 toA8, const int strideOf8, + int radius, uint16_t* gauss, + const uint8_t* src, size_t srcRB, int srcW, int srcH, + uint8_t* dst, size_t dstRB) { + + switch (radius) { + case 1: + blur_y_rect(toA8, strideOf8, blur_y_radius_1, 1, gauss, + src, srcRB, srcW, srcH, + dst, dstRB); + break; + + case 2: + blur_y_rect(toA8, strideOf8, blur_y_radius_2, 2, gauss, + src, srcRB, srcW, srcH, + dst, dstRB); + break; + + case 3: + blur_y_rect(toA8, strideOf8, blur_y_radius_3, 3, gauss, + src, srcRB, srcW, srcH, + dst, dstRB); + break; + + case 4: + blur_y_rect(toA8, strideOf8, blur_y_radius_4, 4, gauss, + src, srcRB, srcW, srcH, + dst, dstRB); + break; + + default: + SkASSERTF(false, "The radius %d is not handled\n", radius); + } +} + +static SkIPoint small_blur(double sigmaX, double sigmaY, const SkMask& src, SkMask* dst) { + SkASSERT(sigmaX == sigmaY); // TODO + SkASSERT(0.01 <= sigmaX && sigmaX < 2); + SkASSERT(0.01 <= sigmaY && sigmaY < 2); + + SkGaussFilter filterX{sigmaX}, + filterY{sigmaY}; + + int radiusX = filterX.radius(), + radiusY = filterY.radius(); + + SkASSERT(radiusX <= 4 && radiusY <= 4); + + auto prepareGauss = [](const SkGaussFilter& filter, uint16_t* factors) { + int i = 0; + for (double d : filter) { + factors[i++] = static_cast(round(d * (1 << 16))); + } + }; + + uint16_t gaussFactorsX[SkGaussFilter::kGaussArrayMax], + gaussFactorsY[SkGaussFilter::kGaussArrayMax]; + + prepareGauss(filterX, gaussFactorsX); + prepareGauss(filterY, gaussFactorsY); + + *dst = SkMask::PrepareDestination(radiusX, radiusY, src); + if (src.fImage == nullptr) { + return {SkTo(radiusX), SkTo(radiusY)}; + } + if (dst->fImage == nullptr) { + dst->fBounds.setEmpty(); + return {0, 0}; + } + + int srcW = src.fBounds.width(), + srcH = src.fBounds.height(); + + int dstW = dst->fBounds.width(), + dstH = dst->fBounds.height(); + + size_t srcRB = src.fRowBytes, + dstRB = dst->fRowBytes; + + //TODO: handle bluring in only one direction. + + // Blur vertically and copy to destination. + switch (src.fFormat) { + case SkMask::kBW_Format: + direct_blur_y(bw_to_a8, 1, + radiusY, gaussFactorsY, + src.fImage, srcRB, srcW, srcH, + dst->fImage + radiusX, dstRB); + break; + case SkMask::kA8_Format: + direct_blur_y(nullptr, 8, + radiusY, gaussFactorsY, + src.fImage, srcRB, srcW, srcH, + dst->fImage + radiusX, dstRB); + break; + case SkMask::kARGB32_Format: + direct_blur_y(argb32_to_a8, 32, + radiusY, gaussFactorsY, + src.fImage, srcRB, srcW, srcH, + dst->fImage + radiusX, dstRB); + break; + case SkMask::kLCD16_Format: + direct_blur_y(lcd_to_a8, 16, radiusY, gaussFactorsY, + src.fImage, srcRB, srcW, srcH, + dst->fImage + radiusX, dstRB); + break; + default: + SK_ABORT("Unhandled format."); + } + + // Blur horizontally in place. + direct_blur_x(radiusX, gaussFactorsX, + dst->fImage + radiusX, dstRB, srcW, + dst->fImage, dstRB, dstW, dstH); + + return {radiusX, radiusY}; +} + +// TODO: assuming sigmaW = sigmaH. Allow different sigmas. Right now the +// API forces the sigmas to be the same. +SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const { + + if (fSigmaW < 2.0 && fSigmaH < 2.0) { + return small_blur(fSigmaW, fSigmaH, src, dst); + } + + // 1024 is a place holder guess until more analysis can be done. + SkSTArenaAlloc<1024> alloc; + + PlanGauss planW(fSigmaW); + PlanGauss planH(fSigmaH); + + int borderW = planW.border(), + borderH = planH.border(); + SkASSERT(borderH >= 0 && borderW >= 0); + + *dst = SkMask::PrepareDestination(borderW, borderH, src); + if (src.fImage == nullptr) { + return {SkTo(borderW), SkTo(borderH)}; + } + if (dst->fImage == nullptr) { + dst->fBounds.setEmpty(); + return {0, 0}; + } + + int srcW = src.fBounds.width(), + srcH = src.fBounds.height(), + dstW = dst->fBounds.width(), + dstH = dst->fBounds.height(); + SkASSERT(srcW >= 0 && srcH >= 0 && dstW >= 0 && dstH >= 0); + + auto bufferSize = std::max(planW.bufferSize(), planH.bufferSize()); + auto buffer = alloc.makeArrayDefault(bufferSize); + + // Blur both directions. + int tmpW = srcH, + tmpH = dstW; + + auto tmp = alloc.makeArrayDefault(tmpW * tmpH); + + // Blur horizontally, and transpose. + const PlanGauss::Scan& scanW = planW.makeBlurScan(srcW, buffer); + switch (src.fFormat) { + case SkMask::kBW_Format: { + const uint8_t* bwStart = src.fImage; + auto start = SkMask::AlphaIter(bwStart, 0); + auto end = SkMask::AlphaIter(bwStart + (srcW / 8), srcW % 8); + for (int y = 0; y < srcH; ++y, start >>= src.fRowBytes, end >>= src.fRowBytes) { + auto tmpStart = &tmp[y]; + scanW.blur(start, end, tmpStart, tmpW, tmpStart + tmpW * tmpH); + } + } break; + case SkMask::kA8_Format: { + const uint8_t* a8Start = src.fImage; + auto start = SkMask::AlphaIter(a8Start); + auto end = SkMask::AlphaIter(a8Start + srcW); + for (int y = 0; y < srcH; ++y, start >>= src.fRowBytes, end >>= src.fRowBytes) { + auto tmpStart = &tmp[y]; + scanW.blur(start, end, tmpStart, tmpW, tmpStart + tmpW * tmpH); + } + } break; + case SkMask::kARGB32_Format: { + const uint32_t* argbStart = reinterpret_cast(src.fImage); + auto start = SkMask::AlphaIter(argbStart); + auto end = SkMask::AlphaIter(argbStart + srcW); + for (int y = 0; y < srcH; ++y, start >>= src.fRowBytes, end >>= src.fRowBytes) { + auto tmpStart = &tmp[y]; + scanW.blur(start, end, tmpStart, tmpW, tmpStart + tmpW * tmpH); + } + } break; + case SkMask::kLCD16_Format: { + const uint16_t* lcdStart = reinterpret_cast(src.fImage); + auto start = SkMask::AlphaIter(lcdStart); + auto end = SkMask::AlphaIter(lcdStart + srcW); + for (int y = 0; y < srcH; ++y, start >>= src.fRowBytes, end >>= src.fRowBytes) { + auto tmpStart = &tmp[y]; + scanW.blur(start, end, tmpStart, tmpW, tmpStart + tmpW * tmpH); + } + } break; + default: + SK_ABORT("Unhandled format."); + } + + // Blur vertically (scan in memory order because of the transposition), + // and transpose back to the original orientation. + const PlanGauss::Scan& scanH = planH.makeBlurScan(tmpW, buffer); + for (int y = 0; y < tmpH; y++) { + auto tmpStart = &tmp[y * tmpW]; + auto dstStart = &dst->fImage[y]; + + scanH.blur(tmpStart, tmpStart + tmpW, + dstStart, dst->fRowBytes, dstStart + dst->fRowBytes * dstH); + } + + return {SkTo(borderW), SkTo(borderH)}; +} diff --git a/gfx/skia/skia/src/core/SkMaskBlurFilter.h b/gfx/skia/skia/src/core/SkMaskBlurFilter.h new file mode 100644 index 0000000000..fe10cf4abb --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskBlurFilter.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMaskBlurFilter_DEFINED +#define SkMaskBlurFilter_DEFINED + +#include +#include +#include + +#include "include/core/SkTypes.h" +#include "src/core/SkMask.h" + +// Implement a single channel Gaussian blur. The specifics for implementation are taken from: +// https://drafts.fxtf.org/filters/#feGaussianBlurElement +class SkMaskBlurFilter { +public: + // Create an object suitable for filtering an SkMask using a filter with width sigmaW and + // height sigmaH. + SkMaskBlurFilter(double sigmaW, double sigmaH); + + // returns true iff the sigmas will result in an identity mask (no blurring) + bool hasNoBlur() const; + + // Given a src SkMask, generate dst SkMask returning the border width and height. + SkIPoint blur(const SkMask& src, SkMask* dst) const; + +private: + const double fSigmaW; + const double fSigmaH; +}; + +#endif // SkBlurMaskFilter_DEFINED diff --git a/gfx/skia/skia/src/core/SkMaskCache.cpp b/gfx/skia/skia/src/core/SkMaskCache.cpp new file mode 100644 index 0000000000..f08f4d7ee0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskCache.cpp @@ -0,0 +1,185 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMaskCache.h" + +#define CHECK_LOCAL(localCache, localName, globalName, ...) \ + ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__)) + +struct MaskValue { + SkMask fMask; + SkCachedData* fData; +}; + +namespace { +static unsigned gRRectBlurKeyNamespaceLabel; + +struct RRectBlurKey : public SkResourceCache::Key { +public: + RRectBlurKey(SkScalar sigma, const SkRRect& rrect, SkBlurStyle style) + : fSigma(sigma) + , fStyle(style) + , fRRect(rrect) + { + this->init(&gRRectBlurKeyNamespaceLabel, 0, + sizeof(fSigma) + sizeof(fStyle) + sizeof(fRRect)); + } + + SkScalar fSigma; + int32_t fStyle; + SkRRect fRRect; +}; + +struct RRectBlurRec : public SkResourceCache::Rec { + RRectBlurRec(RRectBlurKey key, const SkMask& mask, SkCachedData* data) + : fKey(key) + { + fValue.fMask = mask; + fValue.fData = data; + fValue.fData->attachToCacheAndRef(); + } + ~RRectBlurRec() override { + fValue.fData->detachFromCacheAndUnref(); + } + + RRectBlurKey fKey; + MaskValue fValue; + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { return sizeof(*this) + fValue.fData->size(); } + const char* getCategory() const override { return "rrect-blur"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { + return fValue.fData->diagnostic_only_getDiscardable(); + } + + static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextData) { + const RRectBlurRec& rec = static_cast(baseRec); + MaskValue* result = (MaskValue*)contextData; + + SkCachedData* tmpData = rec.fValue.fData; + tmpData->ref(); + if (nullptr == tmpData->data()) { + tmpData->unref(); + return false; + } + *result = rec.fValue; + return true; + } +}; +} // namespace + +SkCachedData* SkMaskCache::FindAndRef(SkScalar sigma, SkBlurStyle style, + const SkRRect& rrect, SkMask* mask, SkResourceCache* localCache) { + MaskValue result; + RRectBlurKey key(sigma, rrect, style); + if (!CHECK_LOCAL(localCache, find, Find, key, RRectBlurRec::Visitor, &result)) { + return nullptr; + } + + *mask = result.fMask; + mask->fImage = (uint8_t*)(result.fData->data()); + return result.fData; +} + +void SkMaskCache::Add(SkScalar sigma, SkBlurStyle style, + const SkRRect& rrect, const SkMask& mask, SkCachedData* data, + SkResourceCache* localCache) { + RRectBlurKey key(sigma, rrect, style); + return CHECK_LOCAL(localCache, add, Add, new RRectBlurRec(key, mask, data)); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +namespace { +static unsigned gRectsBlurKeyNamespaceLabel; + +struct RectsBlurKey : public SkResourceCache::Key { +public: + RectsBlurKey(SkScalar sigma, SkBlurStyle style, const SkRect rects[], int count) + : fSigma(sigma) + , fStyle(style) + { + SkASSERT(1 == count || 2 == count); + SkIRect ir; + rects[0].roundOut(&ir); + fSizes[0] = SkSize{rects[0].width(), rects[0].height()}; + if (2 == count) { + fSizes[1] = SkSize{rects[1].width(), rects[1].height()}; + fSizes[2] = SkSize{rects[0].x() - rects[1].x(), rects[0].y() - rects[1].y()}; + } else { + fSizes[1] = SkSize{0, 0}; + fSizes[2] = SkSize{0, 0}; + } + fSizes[3] = SkSize{rects[0].x() - ir.x(), rects[0].y() - ir.y()}; + + this->init(&gRectsBlurKeyNamespaceLabel, 0, + sizeof(fSigma) + sizeof(fStyle) + sizeof(fSizes)); + } + + SkScalar fSigma; + int32_t fStyle; + SkSize fSizes[4]; +}; + +struct RectsBlurRec : public SkResourceCache::Rec { + RectsBlurRec(RectsBlurKey key, const SkMask& mask, SkCachedData* data) + : fKey(key) + { + fValue.fMask = mask; + fValue.fData = data; + fValue.fData->attachToCacheAndRef(); + } + ~RectsBlurRec() override { + fValue.fData->detachFromCacheAndUnref(); + } + + RectsBlurKey fKey; + MaskValue fValue; + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { return sizeof(*this) + fValue.fData->size(); } + const char* getCategory() const override { return "rects-blur"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { + return fValue.fData->diagnostic_only_getDiscardable(); + } + + static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextData) { + const RectsBlurRec& rec = static_cast(baseRec); + MaskValue* result = static_cast(contextData); + + SkCachedData* tmpData = rec.fValue.fData; + tmpData->ref(); + if (nullptr == tmpData->data()) { + tmpData->unref(); + return false; + } + *result = rec.fValue; + return true; + } +}; +} // namespace + +SkCachedData* SkMaskCache::FindAndRef(SkScalar sigma, SkBlurStyle style, + const SkRect rects[], int count, SkMask* mask, + SkResourceCache* localCache) { + MaskValue result; + RectsBlurKey key(sigma, style, rects, count); + if (!CHECK_LOCAL(localCache, find, Find, key, RectsBlurRec::Visitor, &result)) { + return nullptr; + } + + *mask = result.fMask; + mask->fImage = (uint8_t*)(result.fData->data()); + return result.fData; +} + +void SkMaskCache::Add(SkScalar sigma, SkBlurStyle style, + const SkRect rects[], int count, const SkMask& mask, SkCachedData* data, + SkResourceCache* localCache) { + RectsBlurKey key(sigma, style, rects, count); + return CHECK_LOCAL(localCache, add, Add, new RectsBlurRec(key, mask, data)); +} diff --git a/gfx/skia/skia/src/core/SkMaskCache.h b/gfx/skia/skia/src/core/SkMaskCache.h new file mode 100644 index 0000000000..d22a5d1be0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskCache.h @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMaskCache_DEFINED +#define SkMaskCache_DEFINED + +#include "include/core/SkBlurTypes.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRect.h" +#include "src/core/SkCachedData.h" +#include "src/core/SkMask.h" +#include "src/core/SkResourceCache.h" + +class SkMaskCache { +public: + /** + * On success, return a ref to the SkCachedData that holds the pixels, and have mask + * already point to that memory. + * + * On failure, return nullptr. + */ + static SkCachedData* FindAndRef(SkScalar sigma, SkBlurStyle style, + const SkRRect& rrect, SkMask* mask, + SkResourceCache* localCache = nullptr); + static SkCachedData* FindAndRef(SkScalar sigma, SkBlurStyle style, + const SkRect rects[], int count, SkMask* mask, + SkResourceCache* localCache = nullptr); + + /** + * Add a mask and its pixel-data to the cache. + */ + static void Add(SkScalar sigma, SkBlurStyle style, + const SkRRect& rrect, const SkMask& mask, SkCachedData* data, + SkResourceCache* localCache = nullptr); + static void Add(SkScalar sigma, SkBlurStyle style, + const SkRect rects[], int count, const SkMask& mask, SkCachedData* data, + SkResourceCache* localCache = nullptr); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkMaskFilter.cpp b/gfx/skia/skia/src/core/SkMaskFilter.cpp new file mode 100644 index 0000000000..573fe3cabb --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskFilter.cpp @@ -0,0 +1,720 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMaskFilterBase.h" + +#include "include/core/SkPath.h" +#include "include/core/SkRRect.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkBlurPriv.h" +#include "src/core/SkCachedData.h" +#include "src/core/SkCoverageModePriv.h" +#include "src/core/SkDraw.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" +#include "src/gpu/text/GrSDFMaskFilter.h" +#endif + +SkMaskFilterBase::NinePatch::~NinePatch() { + if (fCache) { + SkASSERT((const void*)fMask.fImage == fCache->data()); + fCache->unref(); + } else { + SkMask::FreeImage(fMask.fImage); + } +} + +bool SkMaskFilterBase::asABlur(BlurRec*) const { + return false; +} + +static void extractMaskSubset(const SkMask& src, SkMask* dst) { + SkASSERT(src.fBounds.contains(dst->fBounds)); + + const int dx = dst->fBounds.left() - src.fBounds.left(); + const int dy = dst->fBounds.top() - src.fBounds.top(); + dst->fImage = src.fImage + dy * src.fRowBytes + dx; + dst->fRowBytes = src.fRowBytes; + dst->fFormat = src.fFormat; +} + +static void blitClippedMask(SkBlitter* blitter, const SkMask& mask, + const SkIRect& bounds, const SkIRect& clipR) { + SkIRect r; + if (r.intersect(bounds, clipR)) { + blitter->blitMask(mask, r); + } +} + +static void blitClippedRect(SkBlitter* blitter, const SkIRect& rect, const SkIRect& clipR) { + SkIRect r; + if (r.intersect(rect, clipR)) { + blitter->blitRect(r.left(), r.top(), r.width(), r.height()); + } +} + +#if 0 +static void dump(const SkMask& mask) { + for (int y = mask.fBounds.top(); y < mask.fBounds.bottom(); ++y) { + for (int x = mask.fBounds.left(); x < mask.fBounds.right(); ++x) { + SkDebugf("%02X", *mask.getAddr8(x, y)); + } + SkDebugf("\n"); + } + SkDebugf("\n"); +} +#endif + +static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR, + const SkIPoint& center, bool fillCenter, + const SkIRect& clipR, SkBlitter* blitter) { + int cx = center.x(); + int cy = center.y(); + SkMask m; + + // top-left + m.fBounds = mask.fBounds; + m.fBounds.fRight = cx; + m.fBounds.fBottom = cy; + if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { + extractMaskSubset(mask, &m); + m.fBounds.offsetTo(outerR.left(), outerR.top()); + blitClippedMask(blitter, m, m.fBounds, clipR); + } + + // top-right + m.fBounds = mask.fBounds; + m.fBounds.fLeft = cx + 1; + m.fBounds.fBottom = cy; + if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { + extractMaskSubset(mask, &m); + m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.top()); + blitClippedMask(blitter, m, m.fBounds, clipR); + } + + // bottom-left + m.fBounds = mask.fBounds; + m.fBounds.fRight = cx; + m.fBounds.fTop = cy + 1; + if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { + extractMaskSubset(mask, &m); + m.fBounds.offsetTo(outerR.left(), outerR.bottom() - m.fBounds.height()); + blitClippedMask(blitter, m, m.fBounds, clipR); + } + + // bottom-right + m.fBounds = mask.fBounds; + m.fBounds.fLeft = cx + 1; + m.fBounds.fTop = cy + 1; + if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { + extractMaskSubset(mask, &m); + m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), + outerR.bottom() - m.fBounds.height()); + blitClippedMask(blitter, m, m.fBounds, clipR); + } + + SkIRect innerR; + innerR.setLTRB(outerR.left() + cx - mask.fBounds.left(), + outerR.top() + cy - mask.fBounds.top(), + outerR.right() + (cx + 1 - mask.fBounds.right()), + outerR.bottom() + (cy + 1 - mask.fBounds.bottom())); + if (fillCenter) { + blitClippedRect(blitter, innerR, clipR); + } + + const int innerW = innerR.width(); + size_t storageSize = (innerW + 1) * (sizeof(int16_t) + sizeof(uint8_t)); + SkAutoSMalloc<4*1024> storage(storageSize); + int16_t* runs = (int16_t*)storage.get(); + uint8_t* alpha = (uint8_t*)(runs + innerW + 1); + + SkIRect r; + // top + r.setLTRB(innerR.left(), outerR.top(), innerR.right(), innerR.top()); + if (r.intersect(clipR)) { + int startY = SkMax32(0, r.top() - outerR.top()); + int stopY = startY + r.height(); + int width = r.width(); + for (int y = startY; y < stopY; ++y) { + runs[0] = width; + runs[width] = 0; + alpha[0] = *mask.getAddr8(cx, mask.fBounds.top() + y); + blitter->blitAntiH(r.left(), outerR.top() + y, alpha, runs); + } + } + // bottom + r.setLTRB(innerR.left(), innerR.bottom(), innerR.right(), outerR.bottom()); + if (r.intersect(clipR)) { + int startY = outerR.bottom() - r.bottom(); + int stopY = startY + r.height(); + int width = r.width(); + for (int y = startY; y < stopY; ++y) { + runs[0] = width; + runs[width] = 0; + alpha[0] = *mask.getAddr8(cx, mask.fBounds.bottom() - y - 1); + blitter->blitAntiH(r.left(), outerR.bottom() - y - 1, alpha, runs); + } + } + // left + r.setLTRB(outerR.left(), innerR.top(), innerR.left(), innerR.bottom()); + if (r.intersect(clipR)) { + SkMask m; + m.fImage = mask.getAddr8(mask.fBounds.left() + r.left() - outerR.left(), + mask.fBounds.top() + cy); + m.fBounds = r; + m.fRowBytes = 0; // so we repeat the scanline for our height + m.fFormat = SkMask::kA8_Format; + blitter->blitMask(m, r); + } + // right + r.setLTRB(innerR.right(), innerR.top(), outerR.right(), innerR.bottom()); + if (r.intersect(clipR)) { + SkMask m; + m.fImage = mask.getAddr8(mask.fBounds.right() - outerR.right() + r.left(), + mask.fBounds.top() + cy); + m.fBounds = r; + m.fRowBytes = 0; // so we repeat the scanline for our height + m.fFormat = SkMask::kA8_Format; + blitter->blitMask(m, r); + } +} + +static void draw_nine(const SkMask& mask, const SkIRect& outerR, const SkIPoint& center, + bool fillCenter, const SkRasterClip& clip, SkBlitter* blitter) { + // if we get here, we need to (possibly) resolve the clip and blitter + SkAAClipBlitterWrapper wrapper(clip, blitter); + blitter = wrapper.getBlitter(); + + SkRegion::Cliperator clipper(wrapper.getRgn(), outerR); + + if (!clipper.done()) { + const SkIRect& cr = clipper.rect(); + do { + draw_nine_clipped(mask, outerR, center, fillCenter, cr, blitter); + clipper.next(); + } while (!clipper.done()); + } +} + +static int countNestedRects(const SkPath& path, SkRect rects[2]) { + if (SkPathPriv::IsNestedFillRects(path, rects)) { + return 2; + } + return path.isRect(&rects[0]); +} + +bool SkMaskFilterBase::filterRRect(const SkRRect& devRRect, const SkMatrix& matrix, + const SkRasterClip& clip, SkBlitter* blitter) const { + // Attempt to speed up drawing by creating a nine patch. If a nine patch + // cannot be used, return false to allow our caller to recover and perform + // the drawing another way. + NinePatch patch; + patch.fMask.fImage = nullptr; + if (kTrue_FilterReturn != this->filterRRectToNine(devRRect, matrix, + clip.getBounds(), + &patch)) { + SkASSERT(nullptr == patch.fMask.fImage); + return false; + } + draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter, true, clip, blitter); + return true; +} + +bool SkMaskFilterBase::filterPath(const SkPath& devPath, const SkMatrix& matrix, + const SkRasterClip& clip, SkBlitter* blitter, + SkStrokeRec::InitStyle style) const { + SkRect rects[2]; + int rectCount = 0; + if (SkStrokeRec::kFill_InitStyle == style) { + rectCount = countNestedRects(devPath, rects); + } + if (rectCount > 0) { + NinePatch patch; + + switch (this->filterRectsToNine(rects, rectCount, matrix, clip.getBounds(), &patch)) { + case kFalse_FilterReturn: + SkASSERT(nullptr == patch.fMask.fImage); + return false; + + case kTrue_FilterReturn: + draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter, 1 == rectCount, clip, + blitter); + return true; + + case kUnimplemented_FilterReturn: + SkASSERT(nullptr == patch.fMask.fImage); + // fall through + break; + } + } + + SkMask srcM, dstM; + + if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), this, &matrix, &srcM, + SkMask::kComputeBoundsAndRenderImage_CreateMode, + style)) { + return false; + } + SkAutoMaskFreeImage autoSrc(srcM.fImage); + + if (!this->filterMask(&dstM, srcM, matrix, nullptr)) { + return false; + } + SkAutoMaskFreeImage autoDst(dstM.fImage); + + // if we get here, we need to (possibly) resolve the clip and blitter + SkAAClipBlitterWrapper wrapper(clip, blitter); + blitter = wrapper.getBlitter(); + + SkRegion::Cliperator clipper(wrapper.getRgn(), dstM.fBounds); + + if (!clipper.done()) { + const SkIRect& cr = clipper.rect(); + do { + blitter->blitMask(dstM, cr); + clipper.next(); + } while (!clipper.done()); + } + + return true; +} + +SkMaskFilterBase::FilterReturn +SkMaskFilterBase::filterRRectToNine(const SkRRect&, const SkMatrix&, + const SkIRect& clipBounds, NinePatch*) const { + return kUnimplemented_FilterReturn; +} + +SkMaskFilterBase::FilterReturn +SkMaskFilterBase::filterRectsToNine(const SkRect[], int count, const SkMatrix&, + const SkIRect& clipBounds, NinePatch*) const { + return kUnimplemented_FilterReturn; +} + +#if SK_SUPPORT_GPU +std::unique_ptr +SkMaskFilterBase::asFragmentProcessor(const GrFPArgs& args) const { + auto fp = this->onAsFragmentProcessor(args); + if (fp) { + SkASSERT(this->hasFragmentProcessor()); + } else { + SkASSERT(!this->hasFragmentProcessor()); + } + return fp; +} +bool SkMaskFilterBase::hasFragmentProcessor() const { + return this->onHasFragmentProcessor(); +} + +std::unique_ptr +SkMaskFilterBase::onAsFragmentProcessor(const GrFPArgs&) const { + return nullptr; +} +bool SkMaskFilterBase::onHasFragmentProcessor() const { return false; } + +bool SkMaskFilterBase::canFilterMaskGPU(const GrShape& shape, + const SkIRect& devSpaceShapeBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkIRect* maskRect) const { + return false; +} + +bool SkMaskFilterBase::directFilterMaskGPU(GrRecordingContext*, + GrRenderTargetContext*, + GrPaint&&, + const GrClip&, + const SkMatrix& viewMatrix, + const GrShape&) const { + return false; +} + +sk_sp SkMaskFilterBase::filterMaskGPU(GrRecordingContext*, + sk_sp srcProxy, + GrColorType srcColorType, + SkAlphaType srcAlphaType, + const SkMatrix& ctm, + const SkIRect& maskRect) const { + return nullptr; +} +#endif + +void SkMaskFilterBase::computeFastBounds(const SkRect& src, SkRect* dst) const { + SkMask srcM, dstM; + + srcM.fBounds = src.roundOut(); + srcM.fRowBytes = 0; + srcM.fFormat = SkMask::kA8_Format; + + SkIPoint margin; // ignored + if (this->filterMask(&dstM, srcM, SkMatrix::I(), &margin)) { + dst->set(dstM.fBounds); + } else { + dst->set(srcM.fBounds); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +template static inline T join(const T& a, const T& b) { + T r = a; + r.join(b); + return r; +} +template static inline T sect(const T& a, const T& b) { + T r = a; + return r.intersect(b) ? r : T::MakeEmpty(); +} + +class SkComposeMF : public SkMaskFilterBase { +public: + SkComposeMF(sk_sp outer, sk_sp inner) + : fOuter(std::move(outer)) + , fInner(std::move(inner)) + { + SkASSERT(as_MFB(fOuter)->getFormat() == SkMask::kA8_Format); + SkASSERT(as_MFB(fInner)->getFormat() == SkMask::kA8_Format); + } + + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, SkIPoint*) const override; + + void computeFastBounds(const SkRect& src, SkRect* dst) const override { + SkRect tmp; + as_MFB(fInner)->computeFastBounds(src, &tmp); + as_MFB(fOuter)->computeFastBounds(tmp, dst); + } + + SkMask::Format getFormat() const override { return SkMask::kA8_Format; } + +protected: +#if SK_SUPPORT_GPU + std::unique_ptr onAsFragmentProcessor(const GrFPArgs& args) const override{ + std::unique_ptr array[2] = { + as_MFB(fInner)->asFragmentProcessor(args), + as_MFB(fOuter)->asFragmentProcessor(args), + }; + if (!array[0] || !array[1]) { + return nullptr; + } + return GrFragmentProcessor::RunInSeries(array, 2); + } + + bool onHasFragmentProcessor() const override { + return as_MFB(fInner)->hasFragmentProcessor() && as_MFB(fOuter)->hasFragmentProcessor(); + } +#endif + +private: + SK_FLATTENABLE_HOOKS(SkComposeMF) + + sk_sp fOuter; + sk_sp fInner; + + void flatten(SkWriteBuffer&) const override; + + friend class SkMaskFilter; + + typedef SkMaskFilterBase INHERITED; +}; + +bool SkComposeMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm, + SkIPoint* margin) const { + SkIPoint innerMargin; + SkMask innerMask; + + if (!as_MFB(fInner)->filterMask(&innerMask, src, ctm, &innerMargin)) { + return false; + } + if (!as_MFB(fOuter)->filterMask(dst, innerMask, ctm, margin)) { + return false; + } + if (margin) { + margin->fX += innerMargin.fX; + margin->fY += innerMargin.fY; + } + sk_free(innerMask.fImage); + return true; +} + +void SkComposeMF::flatten(SkWriteBuffer & buffer) const { + buffer.writeFlattenable(fOuter.get()); + buffer.writeFlattenable(fInner.get()); +} + +sk_sp SkComposeMF::CreateProc(SkReadBuffer& buffer) { + auto outer = buffer.readMaskFilter(); + auto inner = buffer.readMaskFilter(); + if (!buffer.validate(outer && inner)) { + return nullptr; + } + return SkMaskFilter::MakeCompose(std::move(outer), std::move(inner)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkCombineMF : public SkMaskFilterBase { +public: + SkCombineMF(sk_sp dst, sk_sp src, SkCoverageMode mode) + : fDst(std::move(dst)) + , fSrc(std::move(src)) + , fMode(mode) + { + SkASSERT(as_MFB(fSrc)->getFormat() == SkMask::kA8_Format); + SkASSERT(as_MFB(fDst)->getFormat() == SkMask::kA8_Format); + } + + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, SkIPoint*) const override; + + void computeFastBounds(const SkRect& src, SkRect* dst) const override { + SkRect srcR, dstR; + as_MFB(fSrc)->computeFastBounds(src, &srcR); + as_MFB(fDst)->computeFastBounds(src, &dstR); + *dst = join(srcR, dstR); + } + + SkMask::Format getFormat() const override { return SkMask::kA8_Format; } + + SK_FLATTENABLE_HOOKS(SkCombineMF) + +protected: +#if SK_SUPPORT_GPU + std::unique_ptr onAsFragmentProcessor(const GrFPArgs& args) const override{ + auto src = as_MFB(fSrc)->asFragmentProcessor(args); + auto dst = as_MFB(fDst)->asFragmentProcessor(args); + if (!src || !dst) { + return nullptr; + } + return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(src), std::move(dst), + SkUncorrelatedCoverageModeToBlendMode(fMode)); + } + + bool onHasFragmentProcessor() const override { + return as_MFB(fSrc)->hasFragmentProcessor() && as_MFB(fDst)->hasFragmentProcessor(); + } +#endif + +private: + sk_sp fDst; + sk_sp fSrc; + SkCoverageMode fMode; + + void flatten(SkWriteBuffer&) const override; + + friend class SkMaskFilter; + + typedef SkMaskFilterBase INHERITED; +}; + +#include "src/core/SkSafeMath.h" + +class DrawIntoMask : public SkDraw { +public: + // we ignore the offset of the mask->fBounds + DrawIntoMask(SkMask* mask) { + int w = mask->fBounds.width(); + int h = mask->fBounds.height(); + size_t size = SkSafeMath::Mul(w, h); + mask->fFormat = SkMask::kA8_Format; + mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc); + mask->fRowBytes = w; + + SkAssertResult(fDst.reset(*mask)); + + fMatrixStorage.reset(); + fMatrix = &fMatrixStorage; + + fRCStorage.setRect({ 0, 0, w, h }); + fRC = &fRCStorage; + } + + void drawAsBitmap(const SkMask& m, const SkPaint& p) { + SkBitmap b; + b.installMaskPixels(m); + this->drawSprite(b, m.fBounds.fLeft, m.fBounds.fTop, p); + } + +private: + SkMatrix fMatrixStorage; + SkRasterClip fRCStorage; +}; + +static SkIRect join(const SkIRect& src, const SkIRect& dst, SkCoverageMode mode) { + switch (mode) { + case SkCoverageMode::kUnion: return join(src, dst); + case SkCoverageMode::kIntersect: return sect(src, dst); + case SkCoverageMode::kDifference: return src; + case SkCoverageMode::kReverseDifference: return dst; + case SkCoverageMode::kXor: return join(src, dst); + } + // not reached + return { 0, 0, 0, 0 }; +} + +bool SkCombineMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm, + SkIPoint* margin) const { + SkIPoint srcP, dstP; + SkMask srcM, dstM; + + if (!as_MFB(fSrc)->filterMask(&srcM, src, ctm, &srcP)) { + return false; + } + if (!as_MFB(fDst)->filterMask(&dstM, src, ctm, &dstP)) { + return false; + } + + dst->fBounds = join(srcM.fBounds, dstM.fBounds, fMode); + dst->fFormat = SkMask::kA8_Format; + if (src.fImage == nullptr) { + dst->fImage = nullptr; + return true; + } + + DrawIntoMask md(dst); + SkPaint p; + + p.setBlendMode(SkBlendMode::kSrc); + dstM.fBounds.offset(-dst->fBounds.fLeft, -dst->fBounds.fTop); + md.drawAsBitmap(dstM, p); + p.setBlendMode(SkUncorrelatedCoverageModeToBlendMode(fMode)); + srcM.fBounds.offset(-dst->fBounds.fLeft, -dst->fBounds.fTop); + md.drawAsBitmap(srcM, p); + + sk_free(srcM.fImage); + sk_free(dstM.fImage); + return true; +} + +void SkCombineMF::flatten(SkWriteBuffer & buffer) const { + buffer.writeFlattenable(fDst.get()); + buffer.writeFlattenable(fSrc.get()); + buffer.write32(static_cast(fMode)); +} + +sk_sp SkCombineMF::CreateProc(SkReadBuffer& buffer) { + auto dst = buffer.readMaskFilter(); + auto src = buffer.readMaskFilter(); + SkCoverageMode mode = buffer.read32LE(SkCoverageMode::kLast); + if (!buffer.validate(dst && src)) { + return nullptr; + } + return SkMaskFilter::MakeCombine(std::move(dst), std::move(src), mode); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkMatrixMF : public SkMaskFilterBase { +public: + SkMatrixMF(sk_sp filter, const SkMatrix& lm) + : fFilter(std::move(filter)) + , fLM(lm) + {} + + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm, + SkIPoint* margin) const override { + return as_MFB(fFilter)->filterMask(dst, src, SkMatrix::Concat(ctm, fLM), margin); + } + + void computeFastBounds(const SkRect& src, SkRect* dst) const override { + *dst = src; + SkRect tmp; + fLM.mapRect(&tmp, src); + as_MFB(fFilter)->computeFastBounds(tmp, dst); + } + + SkMask::Format getFormat() const override { return as_MFB(fFilter)->getFormat(); } + + SK_FLATTENABLE_HOOKS(SkMatrixMF) + +protected: +#if SK_SUPPORT_GPU + std::unique_ptr onAsFragmentProcessor(const GrFPArgs& args) const override{ + return as_MFB(fFilter)->asFragmentProcessor(GrFPArgs::WithPostLocalMatrix(args, fLM)); + } + + bool onHasFragmentProcessor() const override { + return as_MFB(fFilter)->hasFragmentProcessor(); + } +#endif + +private: + sk_sp fFilter; + const SkMatrix fLM; + + void flatten(SkWriteBuffer& buffer) const override { + buffer.writeMatrix(fLM); + buffer.writeFlattenable(fFilter.get()); + } + + friend class SkMaskFilter; + typedef SkMaskFilterBase INHERITED; +}; + +sk_sp SkMatrixMF::CreateProc(SkReadBuffer& buffer) { + SkMatrix m; + buffer.readMatrix(&m); + auto filter = buffer.readMaskFilter(); + return filter ? filter->makeWithMatrix(m) : nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkMaskFilter::MakeCompose(sk_sp outer, + sk_sp inner) { + if (!outer) { + return inner; + } + if (!inner) { + return outer; + } + if (as_MFB(inner)->getFormat() != SkMask::kA8_Format || + as_MFB(outer)->getFormat() != SkMask::kA8_Format) { + return nullptr; + } + return sk_sp(new SkComposeMF(std::move(outer), std::move(inner))); +} + +sk_sp SkMaskFilter::MakeCombine(sk_sp dst, sk_sp src, + SkCoverageMode mode) { + if (!dst) { + return src; + } + if (!src) { + return dst; + } + + if (as_MFB(dst)->getFormat() != SkMask::kA8_Format || + as_MFB(src)->getFormat() != SkMask::kA8_Format) { + return nullptr; + } + return sk_sp(new SkCombineMF(std::move(dst), std::move(src), mode)); +} + +sk_sp SkMaskFilter::makeWithMatrix(const SkMatrix& lm) const { + sk_sp me = sk_ref_sp(const_cast(this)); + if (lm.isIdentity()) { + return me; + } + return sk_sp(new SkMatrixMF(std::move(me), lm)); +} + +void SkMaskFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkMatrixMF); + SK_REGISTER_FLATTENABLE(SkComposeMF); + SK_REGISTER_FLATTENABLE(SkCombineMF); + sk_register_blur_maskfilter_createproc(); +#if SK_SUPPORT_GPU + gr_register_sdf_maskfilter_createproc(); +#endif +} diff --git a/gfx/skia/skia/src/core/SkMaskFilterBase.h b/gfx/skia/skia/src/core/SkMaskFilterBase.h new file mode 100644 index 0000000000..3c06f1bd34 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskFilterBase.h @@ -0,0 +1,245 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMaskFilterBase_DEFINED +#define SkMaskFilterBase_DEFINED + +#include "include/core/SkBlurTypes.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkPaint.h" +#include "include/core/SkStrokeRec.h" +#include "include/private/SkNoncopyable.h" +#include "src/core/SkMask.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrTypesPriv.h" +#endif + +class GrClip; +struct GrFPArgs; +class GrFragmentProcessor; +class GrPaint; +class GrRecordingContext; +class GrRenderTarget; +class GrRenderTargetContext; +class GrResourceProvider; +class GrShape; +class GrTexture; +class GrTextureProxy; + +class SkBitmap; +class SkBlitter; +class SkCachedData; +class SkMatrix; +class SkPath; +class SkRasterClip; +class SkRRect; + +class SkMaskFilterBase : public SkMaskFilter { +public: + /** Returns the format of the resulting mask that this subclass will return + when its filterMask() method is called. + */ + virtual SkMask::Format getFormat() const = 0; + + /** Create a new mask by filter the src mask. + If src.fImage == null, then do not allocate or create the dst image + but do fill out the other fields in dstMask. + If you do allocate a dst image, use SkMask::AllocImage() + If this returns false, dst mask is ignored. + @param dst the result of the filter. If src.fImage == null, dst should not allocate its image + @param src the original image to be filtered. + @param matrix the CTM + @param margin if not null, return the buffer dx/dy need when calculating the effect. Used when + drawing a clipped object to know how much larger to allocate the src before + applying the filter. If returning false, ignore this parameter. + @return true if the dst mask was correctly created. + */ + virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, + SkIPoint* margin) const = 0; + +#if SK_SUPPORT_GPU + /** + * Returns a processor if the filter can be expressed a single-pass GrProcessor without + * requiring an explicit input mask. Per-pixel, the effect receives the incoming mask's + * coverage as the input color and outputs the filtered covereage value. This means that each + * pixel's filtered coverage must only depend on the unfiltered mask value for that pixel and + * not on surrounding values. + */ + std::unique_ptr asFragmentProcessor(const GrFPArgs& args) const; + + /** + * Returns true iff asFragmentProcessor() will return a processor + */ + bool hasFragmentProcessor() const; + + /** + * If asFragmentProcessor() fails the filter may be implemented on the GPU by a subclass + * overriding filterMaskGPU (declared below). That code path requires constructing a + * src mask as input. Since that is a potentially expensive operation, the subclass must also + * override this function to indicate whether filterTextureMaskGPU would succeeed if the mask + * were to be created. + * + * 'maskRect' returns the device space portion of the mask that the filter needs. The mask + * passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be + * translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop) + * appears at (0, 0) in the mask). + * + * Logically, how this works is: + * canFilterMaskGPU is called + * if (it returns true) + * the returned mask rect is used for quick rejecting + * the mask rect is used to generate the mask + * filterMaskGPU is called to filter the mask + * + * TODO: this should work as: + * if (canFilterMaskGPU(devShape, ...)) // rect, rrect, drrect, path + * filterMaskGPU(devShape, ...) + * this would hide the RRect special case and the mask generation + */ + virtual bool canFilterMaskGPU(const GrShape&, + const SkIRect& devSpaceShapeBounds, + const SkIRect& clipBounds, + const SkMatrix& ctm, + SkIRect* maskRect) const; + + /** + * Try to directly render the mask filter into the target. Returns true if drawing was + * successful. If false is returned then paint is unmodified. + */ + virtual bool directFilterMaskGPU(GrRecordingContext*, + GrRenderTargetContext*, + GrPaint&& paint, + const GrClip&, + const SkMatrix& viewMatrix, + const GrShape& shape) const; + + /** + * This function is used to implement filters that require an explicit src mask. It should only + * be called if canFilterMaskGPU returned true and the maskRect param should be the output from + * that call. + * Implementations are free to get the GrContext from the src texture in order to create + * additional textures and perform multiple passes. + */ + virtual sk_sp filterMaskGPU(GrRecordingContext*, + sk_sp srcProxy, + GrColorType srcColorType, + SkAlphaType srcAlphaType, + const SkMatrix& ctm, + const SkIRect& maskRect) const; +#endif + + /** + * The fast bounds function is used to enable the paint to be culled early + * in the drawing pipeline. This function accepts the current bounds of the + * paint as its src param and the filter adjust those bounds using its + * current mask and returns the result using the dest param. Callers are + * allowed to provide the same struct for both src and dest so each + * implementation must accomodate that behavior. + * + * The default impl calls filterMask with the src mask having no image, + * but subclasses may override this if they can compute the rect faster. + */ + virtual void computeFastBounds(const SkRect& src, SkRect* dest) const; + + struct BlurRec { + SkScalar fSigma; + SkBlurStyle fStyle; + }; + /** + * If this filter can be represented by a BlurRec, return true and (if not null) fill in the + * provided BlurRec parameter. If this effect cannot be represented as a BlurRec, return false + * and ignore the BlurRec parameter. + */ + virtual bool asABlur(BlurRec*) const; + +protected: + SkMaskFilterBase() {} + +#if SK_SUPPORT_GPU + virtual std::unique_ptr onAsFragmentProcessor(const GrFPArgs&) const; + virtual bool onHasFragmentProcessor() const; +#endif + + enum FilterReturn { + kFalse_FilterReturn, + kTrue_FilterReturn, + kUnimplemented_FilterReturn + }; + + class NinePatch : ::SkNoncopyable { + public: + NinePatch() : fCache(nullptr) { } + ~NinePatch(); + + SkMask fMask; // fBounds must have [0,0] in its top-left + SkIRect fOuterRect; // width/height must be >= fMask.fBounds' + SkIPoint fCenter; // identifies center row/col for stretching + SkCachedData* fCache; + }; + + /** + * Override if your subclass can filter a rect, and return the answer as + * a ninepatch mask to be stretched over the returned outerRect. On success + * return kTrue_FilterReturn. On failure (e.g. out of memory) return + * kFalse_FilterReturn. If the normal filterMask() entry-point should be + * called (the default) return kUnimplemented_FilterReturn. + * + * By convention, the caller will take the center rol/col from the returned + * mask as the slice it can replicate horizontally and vertically as we + * stretch the mask to fit inside outerRect. It is an error for outerRect + * to be smaller than the mask's bounds. This would imply that the width + * and height of the mask should be odd. This is not required, just that + * the caller will call mask.fBounds.centerX() and centerY() to find the + * strips that will be replicated. + */ + virtual FilterReturn filterRectsToNine(const SkRect[], int count, + const SkMatrix&, + const SkIRect& clipBounds, + NinePatch*) const; + /** + * Similar to filterRectsToNine, except it performs the work on a round rect. + */ + virtual FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&, + const SkIRect& clipBounds, + NinePatch*) const; + +private: + friend class SkDraw; + + /** Helper method that, given a path in device space, will rasterize it into a kA8_Format mask + and then call filterMask(). If this returns true, the specified blitter will be called + to render that mask. Returns false if filterMask() returned false. + This method is not exported to java. + */ + bool filterPath(const SkPath& devPath, const SkMatrix& ctm, const SkRasterClip&, SkBlitter*, + SkStrokeRec::InitStyle) const; + + /** Helper method that, given a roundRect in device space, will rasterize it into a kA8_Format + mask and then call filterMask(). If this returns true, the specified blitter will be called + to render that mask. Returns false if filterMask() returned false. + */ + bool filterRRect(const SkRRect& devRRect, const SkMatrix& ctm, const SkRasterClip&, + SkBlitter*) const; + + typedef SkFlattenable INHERITED; +}; + +inline SkMaskFilterBase* as_MFB(SkMaskFilter* mf) { + return static_cast(mf); +} + +inline const SkMaskFilterBase* as_MFB(const SkMaskFilter* mf) { + return static_cast(mf); +} + +inline const SkMaskFilterBase* as_MFB(const sk_sp& mf) { + return static_cast(mf.get()); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkMaskGamma.cpp b/gfx/skia/skia/src/core/SkMaskGamma.cpp new file mode 100644 index 0000000000..b5b51a6286 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskGamma.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMaskGamma.h" + +#include "include/core/SkColor.h" +#include "include/core/SkTypes.h" +#include "include/private/SkFloatingPoint.h" +#include "include/private/SkTo.h" + +class SkLinearColorSpaceLuminance : public SkColorSpaceLuminance { + SkScalar toLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luminance) const override { + SkASSERT(SK_Scalar1 == gamma); + return luminance; + } + SkScalar fromLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luma) const override { + SkASSERT(SK_Scalar1 == gamma); + return luma; + } +}; + +class SkGammaColorSpaceLuminance : public SkColorSpaceLuminance { + SkScalar toLuma(SkScalar gamma, SkScalar luminance) const override { + return SkScalarPow(luminance, gamma); + } + SkScalar fromLuma(SkScalar gamma, SkScalar luma) const override { + return SkScalarPow(luma, SkScalarInvert(gamma)); + } +}; + +class SkSRGBColorSpaceLuminance : public SkColorSpaceLuminance { + SkScalar toLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luminance) const override { + SkASSERT(0 == gamma); + //The magic numbers are derived from the sRGB specification. + //See http://www.color.org/chardata/rgb/srgb.xalter . + if (luminance <= 0.04045f) { + return luminance / 12.92f; + } + return SkScalarPow((luminance + 0.055f) / 1.055f, + 2.4f); + } + SkScalar fromLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luma) const override { + SkASSERT(0 == gamma); + //The magic numbers are derived from the sRGB specification. + //See http://www.color.org/chardata/rgb/srgb.xalter . + if (luma <= 0.0031308f) { + return luma * 12.92f; + } + return 1.055f * SkScalarPow(luma, SkScalarInvert(2.4f)) + - 0.055f; + } +}; + +/*static*/ const SkColorSpaceLuminance& SkColorSpaceLuminance::Fetch(SkScalar gamma) { + static SkLinearColorSpaceLuminance gSkLinearColorSpaceLuminance; + static SkGammaColorSpaceLuminance gSkGammaColorSpaceLuminance; + static SkSRGBColorSpaceLuminance gSkSRGBColorSpaceLuminance; + + if (0 == gamma) { + return gSkSRGBColorSpaceLuminance; + } else if (SK_Scalar1 == gamma) { + return gSkLinearColorSpaceLuminance; + } else { + return gSkGammaColorSpaceLuminance; + } +} + +static float apply_contrast(float srca, float contrast) { + return srca + ((1.0f - srca) * contrast * srca); +} + +void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast, + const SkColorSpaceLuminance& srcConvert, SkScalar srcGamma, + const SkColorSpaceLuminance& dstConvert, SkScalar dstGamma) { + const float src = (float)srcI / 255.0f; + const float linSrc = srcConvert.toLuma(srcGamma, src); + //Guess at the dst. The perceptual inverse provides smaller visual + //discontinuities when slight changes to desaturated colors cause a channel + //to map to a different correcting lut with neighboring srcI. + //See https://code.google.com/p/chromium/issues/detail?id=141425#c59 . + const float dst = 1.0f - src; + const float linDst = dstConvert.toLuma(dstGamma, dst); + + //Contrast value tapers off to 0 as the src luminance becomes white + const float adjustedContrast = SkScalarToFloat(contrast) * linDst; + + //Remove discontinuity and instability when src is close to dst. + //The value 1/256 is arbitrary and appears to contain the instability. + if (fabs(src - dst) < (1.0f / 256.0f)) { + float ii = 0.0f; + for (int i = 0; i < 256; ++i, ii += 1.0f) { + float rawSrca = ii / 255.0f; + float srca = apply_contrast(rawSrca, adjustedContrast); + table[i] = SkToU8(sk_float_round2int(255.0f * srca)); + } + } else { + // Avoid slow int to float conversion. + float ii = 0.0f; + for (int i = 0; i < 256; ++i, ii += 1.0f) { + // 'rawSrca += 1.0f / 255.0f' and even + // 'rawSrca = i * (1.0f / 255.0f)' can add up to more than 1.0f. + // When this happens the table[255] == 0x0 instead of 0xff. + // See http://code.google.com/p/chromium/issues/detail?id=146466 + float rawSrca = ii / 255.0f; + float srca = apply_contrast(rawSrca, adjustedContrast); + SkASSERT(srca <= 1.0f); + float dsta = 1.0f - srca; + + //Calculate the output we want. + float linOut = (linSrc * srca + dsta * linDst); + SkASSERT(linOut <= 1.0f); + float out = dstConvert.fromLuma(dstGamma, linOut); + + //Undo what the blit blend will do. + float result = (out - dst) / (src - dst); + SkASSERT(sk_float_round2int(255.0f * result) <= 255); + + table[i] = SkToU8(sk_float_round2int(255.0f * result)); + } + } +} diff --git a/gfx/skia/skia/src/core/SkMaskGamma.h b/gfx/skia/skia/src/core/SkMaskGamma.h new file mode 100644 index 0000000000..643deedd7e --- /dev/null +++ b/gfx/skia/skia/src/core/SkMaskGamma.h @@ -0,0 +1,232 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMaskGamma_DEFINED +#define SkMaskGamma_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkNoncopyable.h" + +/** + * SkColorSpaceLuminance is used to convert luminances to and from linear and + * perceptual color spaces. + * + * Luma is used to specify a linear luminance value [0.0, 1.0]. + * Luminance is used to specify a luminance value in an arbitrary color space [0.0, 1.0]. + */ +class SkColorSpaceLuminance : SkNoncopyable { +public: + virtual ~SkColorSpaceLuminance() { } + + /** Converts a color component luminance in the color space to a linear luma. */ + virtual SkScalar toLuma(SkScalar gamma, SkScalar luminance) const = 0; + /** Converts a linear luma to a color component luminance in the color space. */ + virtual SkScalar fromLuma(SkScalar gamma, SkScalar luma) const = 0; + + /** Converts a color to a luminance value. */ + static U8CPU computeLuminance(SkScalar gamma, SkColor c) { + const SkColorSpaceLuminance& luminance = Fetch(gamma); + SkScalar r = luminance.toLuma(gamma, SkIntToScalar(SkColorGetR(c)) / 255); + SkScalar g = luminance.toLuma(gamma, SkIntToScalar(SkColorGetG(c)) / 255); + SkScalar b = luminance.toLuma(gamma, SkIntToScalar(SkColorGetB(c)) / 255); + SkScalar luma = r * SK_LUM_COEFF_R + + g * SK_LUM_COEFF_G + + b * SK_LUM_COEFF_B; + SkASSERT(luma <= SK_Scalar1); + return SkScalarRoundToInt(luminance.fromLuma(gamma, luma) * 255); + } + + /** Retrieves the SkColorSpaceLuminance for the given gamma. */ + static const SkColorSpaceLuminance& Fetch(SkScalar gamma); +}; + +///@{ +/** + * Scales base <= 2^N-1 to 2^8-1 + * @param N [1, 8] the number of bits used by base. + * @param base the number to be scaled to [0, 255]. + */ +template static inline U8CPU sk_t_scale255(U8CPU base) { + base <<= (8 - N); + U8CPU lum = base; + for (unsigned int i = N; i < 8; i += N) { + lum |= base >> i; + } + return lum; +} +template<> /*static*/ inline U8CPU sk_t_scale255<1>(U8CPU base) { + return base * 0xFF; +} +template<> /*static*/ inline U8CPU sk_t_scale255<2>(U8CPU base) { + return base * 0x55; +} +template<> /*static*/ inline U8CPU sk_t_scale255<4>(U8CPU base) { + return base * 0x11; +} +template<> /*static*/ inline U8CPU sk_t_scale255<8>(U8CPU base) { + return base; +} +///@} + +template class SkTMaskPreBlend; + +void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast, + const SkColorSpaceLuminance& srcConvert, SkScalar srcGamma, + const SkColorSpaceLuminance& dstConvert, SkScalar dstGamma); + +/** + * A regular mask contains linear alpha values. A gamma correcting mask + * contains non-linear alpha values in an attempt to create gamma correct blits + * in the presence of a gamma incorrect (linear) blend in the blitter. + * + * SkMaskGamma creates and maintains tables which convert linear alpha values + * to gamma correcting alpha values. + * @param R The number of luminance bits to use [1, 8] from the red channel. + * @param G The number of luminance bits to use [1, 8] from the green channel. + * @param B The number of luminance bits to use [1, 8] from the blue channel. + */ +template class SkTMaskGamma : public SkRefCnt { + +public: + + /** Creates a linear SkTMaskGamma. */ + SkTMaskGamma() : fIsLinear(true) { } + + /** + * Creates tables to convert linear alpha values to gamma correcting alpha + * values. + * + * @param contrast A value in the range [0.0, 1.0] which indicates the + * amount of artificial contrast to add. + * @param paint The color space in which the paint color was chosen. + * @param device The color space of the target device. + */ + SkTMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) : fIsLinear(false) { + const SkColorSpaceLuminance& paintConvert = SkColorSpaceLuminance::Fetch(paintGamma); + const SkColorSpaceLuminance& deviceConvert = SkColorSpaceLuminance::Fetch(deviceGamma); + for (U8CPU i = 0; i < (1 << MAX_LUM_BITS); ++i) { + U8CPU lum = sk_t_scale255(i); + SkTMaskGamma_build_correcting_lut(fGammaTables[i], lum, contrast, + paintConvert, paintGamma, + deviceConvert, deviceGamma); + } + } + + /** Given a color, returns the closest canonical color. */ + static SkColor CanonicalColor(SkColor color) { + return SkColorSetRGB( + sk_t_scale255(SkColorGetR(color) >> (8 - R_LUM_BITS)), + sk_t_scale255(SkColorGetG(color) >> (8 - G_LUM_BITS)), + sk_t_scale255(SkColorGetB(color) >> (8 - B_LUM_BITS))); + } + + /** The type of the mask pre-blend which will be returned from preBlend(SkColor). */ + typedef SkTMaskPreBlend PreBlend; + + /** + * Provides access to the tables appropriate for converting linear alpha + * values into gamma correcting alpha values when drawing the given color + * through the mask. The destination color will be approximated. + */ + PreBlend preBlend(SkColor color) const; + + /** + * Get dimensions for the full table set, so it can be allocated as a block. + */ + void getGammaTableDimensions(int* tableWidth, int* numTables) const { + *tableWidth = 256; + *numTables = (1 << MAX_LUM_BITS); + } + + /** + * Provides direct access to the full table set, so it can be uploaded + * into a texture or analyzed in other ways. + * Returns nullptr if fGammaTables hasn't been initialized. + */ + const uint8_t* getGammaTables() const { + return fIsLinear ? nullptr : (const uint8_t*) fGammaTables; + } + +private: + static const int MAX_LUM_BITS = + B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS) + ? B_LUM_BITS : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS); + uint8_t fGammaTables[1 << MAX_LUM_BITS][256]; + bool fIsLinear; + + typedef SkRefCnt INHERITED; +}; + + +/** + * SkTMaskPreBlend is a tear-off of SkTMaskGamma. It provides the tables to + * convert a linear alpha value for a given channel to a gamma correcting alpha + * value for that channel. This class is immutable. + * + * If fR, fG, or fB is nullptr, all of them will be. This indicates that no mask + * pre blend should be applied. SkTMaskPreBlend::isApplicable() is provided as + * a convenience function to test for the absence of this case. + */ +template class SkTMaskPreBlend { +private: + SkTMaskPreBlend(sk_sp> parent, + const uint8_t* r, const uint8_t* g, const uint8_t* b) + : fParent(std::move(parent)), fR(r), fG(g), fB(b) { } + + sk_sp> fParent; + friend class SkTMaskGamma; +public: + /** Creates a non applicable SkTMaskPreBlend. */ + SkTMaskPreBlend() : fParent(), fR(nullptr), fG(nullptr), fB(nullptr) { } + + /** + * This copy contructor exists for correctness, but should never be called + * when return value optimization is enabled. + */ + SkTMaskPreBlend(const SkTMaskPreBlend& that) + : fParent(that.fParent), fR(that.fR), fG(that.fG), fB(that.fB) { } + + ~SkTMaskPreBlend() { } + + /** True if this PreBlend should be applied. When false, fR, fG, and fB are nullptr. */ + bool isApplicable() const { return SkToBool(this->fG); } + + const uint8_t* fR; + const uint8_t* fG; + const uint8_t* fB; +}; + +template +SkTMaskPreBlend +SkTMaskGamma::preBlend(SkColor color) const { + return fIsLinear ? SkTMaskPreBlend() + : SkTMaskPreBlend(sk_ref_sp(this), + fGammaTables[SkColorGetR(color) >> (8 - MAX_LUM_BITS)], + fGammaTables[SkColorGetG(color) >> (8 - MAX_LUM_BITS)], + fGammaTables[SkColorGetB(color) >> (8 - MAX_LUM_BITS)]); +} + +///@{ +/** + * If APPLY_LUT is false, returns component unchanged. + * If APPLY_LUT is true, returns lut[component]. + * @param APPLY_LUT whether or not the look-up table should be applied to component. + * @component the initial component. + * @lut a look-up table which transforms the component. + */ +template static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) { + return component; +} +template<> /*static*/ inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t* lut) { + return lut[component]; +} +///@} + +#endif diff --git a/gfx/skia/skia/src/core/SkMath.cpp b/gfx/skia/skia/src/core/SkMath.cpp new file mode 100644 index 0000000000..8b2df0e640 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMath.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkScalar.h" +#include "include/private/SkFixed.h" +#include "include/private/SkFloatBits.h" +#include "include/private/SkFloatingPoint.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkSafeMath.h" + +#define sub_shift(zeros, x, n) \ + zeros -= n; \ + x >>= n + +int SkCLZ_portable(uint32_t x) { + if (x == 0) { + return 32; + } + + int zeros = 31; + if (x & 0xFFFF0000) { + sub_shift(zeros, x, 16); + } + if (x & 0xFF00) { + sub_shift(zeros, x, 8); + } + if (x & 0xF0) { + sub_shift(zeros, x, 4); + } + if (x & 0xC) { + sub_shift(zeros, x, 2); + } + if (x & 0x2) { + sub_shift(zeros, x, 1); + } + + return zeros; +} + +/////////////////////////////////////////////////////////////////////////////// + +/* www.worldserver.com/turk/computergraphics/FixedSqrt.pdf +*/ +int32_t SkSqrtBits(int32_t x, int count) { + SkASSERT(x >= 0 && count > 0 && (unsigned)count <= 30); + + uint32_t root = 0; + uint32_t remHi = 0; + uint32_t remLo = x; + + do { + root <<= 1; + + remHi = (remHi<<2) | (remLo>>30); + remLo <<= 2; + + uint32_t testDiv = (root << 1) + 1; + if (remHi >= testDiv) { + remHi -= testDiv; + root++; + } + } while (--count >= 0); + + return root; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +size_t SkSafeMath::Add(size_t x, size_t y) { + SkSafeMath tmp; + size_t sum = tmp.add(x, y); + return tmp.ok() ? sum : SIZE_MAX; +} + +size_t SkSafeMath::Mul(size_t x, size_t y) { + SkSafeMath tmp; + size_t prod = tmp.mul(x, y); + return tmp.ok() ? prod : SIZE_MAX; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool sk_floats_are_unit(const float array[], size_t count) { + bool is_unit = true; + for (size_t i = 0; i < count; ++i) { + is_unit &= (array[i] >= 0) & (array[i] <= 1); + } + return is_unit; +} diff --git a/gfx/skia/skia/src/core/SkMathPriv.h b/gfx/skia/skia/src/core/SkMathPriv.h new file mode 100644 index 0000000000..d0ce3b2be8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMathPriv.h @@ -0,0 +1,251 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMathPriv_DEFINED +#define SkMathPriv_DEFINED + +#include "include/core/SkMath.h" + +/** + * Return the integer square root of value, with a bias of bitBias + */ +int32_t SkSqrtBits(int32_t value, int bitBias); + +/** Return the integer square root of n, treated as a SkFixed (16.16) + */ +static inline int32_t SkSqrt32(int32_t n) { return SkSqrtBits(n, 15); } + +/** + * Returns (value < 0 ? 0 : value) efficiently (i.e. no compares or branches) + */ +static inline int SkClampPos(int value) { + return value & ~(value >> 31); +} + +/** + * Stores numer/denom and numer%denom into div and mod respectively. + */ +template +inline void SkTDivMod(In numer, In denom, Out* div, Out* mod) { +#ifdef SK_CPU_ARM32 + // If we wrote this as in the else branch, GCC won't fuse the two into one + // divmod call, but rather a div call followed by a divmod. Silly! This + // version is just as fast as calling __aeabi_[u]idivmod manually, but with + // prettier code. + // + // This benches as around 2x faster than the code in the else branch. + const In d = numer/denom; + *div = static_cast(d); + *mod = static_cast(numer-d*denom); +#else + // On x86 this will just be a single idiv. + *div = static_cast(numer/denom); + *mod = static_cast(numer%denom); +#endif +} + +/** Returns -1 if n < 0, else returns 0 + */ +#define SkExtractSign(n) ((int32_t)(n) >> 31) + +/** If sign == -1, returns -n, else sign must be 0, and returns n. + Typically used in conjunction with SkExtractSign(). + */ +static inline int32_t SkApplySign(int32_t n, int32_t sign) { + SkASSERT(sign == 0 || sign == -1); + return (n ^ sign) - sign; +} + +/** Return x with the sign of y */ +static inline int32_t SkCopySign32(int32_t x, int32_t y) { + return SkApplySign(x, SkExtractSign(x ^ y)); +} + +/** Given a positive value and a positive max, return the value + pinned against max. + Note: only works as long as max - value doesn't wrap around + @return max if value >= max, else value + */ +static inline unsigned SkClampUMax(unsigned value, unsigned max) { + if (value > max) { + value = max; + } + return value; +} + +// If a signed int holds min_int (e.g. 0x80000000) it is undefined what happens when +// we negate it (even though we *know* we're 2's complement and we'll get the same +// value back). So we create this helper function that casts to size_t (unsigned) first, +// to avoid the complaint. +static inline size_t sk_negate_to_size_t(int32_t value) { +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4146) // Thanks MSVC, we know what we're negating an unsigned +#endif + return -static_cast(value); +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +/** Return a*b/255, truncating away any fractional bits. Only valid if both + a and b are 0..255 + */ +static inline U8CPU SkMulDiv255Trunc(U8CPU a, U8CPU b) { + SkASSERT((uint8_t)a == a); + SkASSERT((uint8_t)b == b); + unsigned prod = a*b + 1; + return (prod + (prod >> 8)) >> 8; +} + +/** Return (a*b)/255, taking the ceiling of any fractional bits. Only valid if + both a and b are 0..255. The expected result equals (a * b + 254) / 255. + */ +static inline U8CPU SkMulDiv255Ceiling(U8CPU a, U8CPU b) { + SkASSERT((uint8_t)a == a); + SkASSERT((uint8_t)b == b); + unsigned prod = a*b + 255; + return (prod + (prod >> 8)) >> 8; +} + +/** Just the rounding step in SkDiv255Round: round(value / 255) + */ +static inline unsigned SkDiv255Round(unsigned prod) { + prod += 128; + return (prod + (prod >> 8)) >> 8; +} + +static inline float SkPinToUnitFloat(float x) { + return SkTMin(SkTMax(x, 0.0f), 1.0f); +} + +/** + * Swap byte order of a 4-byte value, e.g. 0xaarrggbb -> 0xbbggrraa. + */ +#if defined(_MSC_VER) + #include + static inline uint32_t SkBSwap32(uint32_t v) { return _byteswap_ulong(v); } +#else + static inline uint32_t SkBSwap32(uint32_t v) { return __builtin_bswap32(v); } +#endif + +//! Returns the number of leading zero bits (0...32) +int SkCLZ_portable(uint32_t); + +#ifndef SkCLZ + #if defined(SK_BUILD_FOR_WIN) + #include + + static inline int SkCLZ(uint32_t mask) { + if (mask) { + unsigned long index; + _BitScanReverse(&index, mask); + // Suppress this bogus /analyze warning. The check for non-zero + // guarantees that _BitScanReverse will succeed. +#pragma warning(suppress : 6102) // Using 'index' from failed function call + return index ^ 0x1F; + } else { + return 32; + } + } + #elif defined(SK_CPU_ARM32) || defined(__GNUC__) || defined(__clang__) + static inline int SkCLZ(uint32_t mask) { + // __builtin_clz(0) is undefined, so we have to detect that case. + return mask ? __builtin_clz(mask) : 32; + } + #else + #define SkCLZ(x) SkCLZ_portable(x) + #endif +#endif + +/** + * Returns the smallest power-of-2 that is >= the specified value. If value + * is already a power of 2, then it is returned unchanged. It is undefined + * if value is <= 0. + */ +static inline int SkNextPow2(int value) { + SkASSERT(value > 0); + return 1 << (32 - SkCLZ(value - 1)); +} + +/** +* Returns the largest power-of-2 that is <= the specified value. If value +* is already a power of 2, then it is returned unchanged. It is undefined +* if value is <= 0. +*/ +static inline int SkPrevPow2(int value) { + SkASSERT(value > 0); + return 1 << (32 - SkCLZ(value >> 1)); +} + +/** + * Returns the log2 of the specified value, were that value to be rounded up + * to the next power of 2. It is undefined to pass 0. Examples: + * SkNextLog2(1) -> 0 + * SkNextLog2(2) -> 1 + * SkNextLog2(3) -> 2 + * SkNextLog2(4) -> 2 + * SkNextLog2(5) -> 3 + */ +static inline int SkNextLog2(uint32_t value) { + SkASSERT(value != 0); + return 32 - SkCLZ(value - 1); +} + +/** +* Returns the log2 of the specified value, were that value to be rounded down +* to the previous power of 2. It is undefined to pass 0. Examples: +* SkPrevLog2(1) -> 0 +* SkPrevLog2(2) -> 1 +* SkPrevLog2(3) -> 1 +* SkPrevLog2(4) -> 2 +* SkPrevLog2(5) -> 2 +*/ +static inline int SkPrevLog2(uint32_t value) { + SkASSERT(value != 0); + return 32 - SkCLZ(value >> 1); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Return the smallest power-of-2 >= n. + */ +static inline uint32_t GrNextPow2(uint32_t n) { + return n ? (1 << (32 - SkCLZ(n - 1))) : 1; +} + +/** + * Returns the next power of 2 >= n or n if the next power of 2 can't be represented by size_t. + */ +static inline size_t GrNextSizePow2(size_t n) { + constexpr int kNumSizeTBits = 8 * sizeof(size_t); + constexpr size_t kHighBitSet = size_t(1) << (kNumSizeTBits - 1); + + if (!n) { + return 1; + } else if (n >= kHighBitSet) { + return n; + } + + n--; + uint32_t shift = 1; + while (shift < kNumSizeTBits) { + n |= n >> shift; + shift <<= 1; + } + return n + 1; +} + +// conservative check. will return false for very large values that "could" fit +template static inline bool SkFitsInFixed(T x) { + return SkTAbs(x) <= 32767.0f; +} + +#endif diff --git a/gfx/skia/skia/src/core/SkMatrix.cpp b/gfx/skia/skia/src/core/SkMatrix.cpp new file mode 100644 index 0000000000..268308ffd4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMatrix.cpp @@ -0,0 +1,1833 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" + +#include "include/core/SkPaint.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkRSXform.h" +#include "include/core/SkString.h" +#include "include/private/SkFloatBits.h" +#include "include/private/SkNx.h" +#include "include/private/SkTo.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkMatrixPriv.h" + +#include +#include + +static void normalize_perspective(SkScalar mat[9]) { + // If it was interesting to never store the last element, we could divide all 8 other + // elements here by the 9th, making it 1.0... + // + // When SkScalar was SkFixed, we would sometimes rescale the entire matrix to keep its + // component values from getting too large. This is not a concern when using floats/doubles, + // so we do nothing now. + + // Disable this for now, but it could be enabled. +#if 0 + if (0 == mat[SkMatrix::kMPersp0] && 0 == mat[SkMatrix::kMPersp1]) { + SkScalar p2 = mat[SkMatrix::kMPersp2]; + if (p2 != 0 && p2 != 1) { + double inv = 1.0 / p2; + for (int i = 0; i < 6; ++i) { + mat[i] = SkDoubleToScalar(mat[i] * inv); + } + mat[SkMatrix::kMPersp2] = 1; + } + } +#endif +} + +// In a few places, we performed the following +// a * b + c * d + e +// as +// a * b + (c * d + e) +// +// sdot and scross are indended to capture these compound operations into a +// function, with an eye toward considering upscaling the intermediates to +// doubles for more precision (as we do in concat and invert). +// +// However, these few lines that performed the last add before the "dot", cause +// tiny image differences, so we guard that change until we see the impact on +// chrome's layouttests. +// +#define SK_LEGACY_MATRIX_MATH_ORDER + +/* [scale-x skew-x trans-x] [X] [X'] + [skew-y scale-y trans-y] * [Y] = [Y'] + [persp-0 persp-1 persp-2] [1] [1 ] +*/ + +SkMatrix& SkMatrix::reset() { *this = SkMatrix(); return *this; } + +SkMatrix& SkMatrix::set9(const SkScalar buffer[]) { + memcpy(fMat, buffer, 9 * sizeof(SkScalar)); + normalize_perspective(fMat); + this->setTypeMask(kUnknown_Mask); + return *this; +} + +SkMatrix& SkMatrix::setAffine(const SkScalar buffer[]) { + fMat[kMScaleX] = buffer[kAScaleX]; + fMat[kMSkewX] = buffer[kASkewX]; + fMat[kMTransX] = buffer[kATransX]; + fMat[kMSkewY] = buffer[kASkewY]; + fMat[kMScaleY] = buffer[kAScaleY]; + fMat[kMTransY] = buffer[kATransY]; + fMat[kMPersp0] = 0; + fMat[kMPersp1] = 0; + fMat[kMPersp2] = 1; + this->setTypeMask(kUnknown_Mask); + return *this; +} + +// this guy aligns with the masks, so we can compute a mask from a varaible 0/1 +enum { + kTranslate_Shift, + kScale_Shift, + kAffine_Shift, + kPerspective_Shift, + kRectStaysRect_Shift +}; + +static const int32_t kScalar1Int = 0x3f800000; + +uint8_t SkMatrix::computePerspectiveTypeMask() const { + // Benchmarking suggests that replacing this set of SkScalarAs2sCompliment + // is a win, but replacing those below is not. We don't yet understand + // that result. + if (fMat[kMPersp0] != 0 || fMat[kMPersp1] != 0 || fMat[kMPersp2] != 1) { + // If this is a perspective transform, we return true for all other + // transform flags - this does not disable any optimizations, respects + // the rule that the type mask must be conservative, and speeds up + // type mask computation. + return SkToU8(kORableMasks); + } + + return SkToU8(kOnlyPerspectiveValid_Mask | kUnknown_Mask); +} + +uint8_t SkMatrix::computeTypeMask() const { + unsigned mask = 0; + + if (fMat[kMPersp0] != 0 || fMat[kMPersp1] != 0 || fMat[kMPersp2] != 1) { + // Once it is determined that that this is a perspective transform, + // all other flags are moot as far as optimizations are concerned. + return SkToU8(kORableMasks); + } + + if (fMat[kMTransX] != 0 || fMat[kMTransY] != 0) { + mask |= kTranslate_Mask; + } + + int m00 = SkScalarAs2sCompliment(fMat[SkMatrix::kMScaleX]); + int m01 = SkScalarAs2sCompliment(fMat[SkMatrix::kMSkewX]); + int m10 = SkScalarAs2sCompliment(fMat[SkMatrix::kMSkewY]); + int m11 = SkScalarAs2sCompliment(fMat[SkMatrix::kMScaleY]); + + if (m01 | m10) { + // The skew components may be scale-inducing, unless we are dealing + // with a pure rotation. Testing for a pure rotation is expensive, + // so we opt for being conservative by always setting the scale bit. + // along with affine. + // By doing this, we are also ensuring that matrices have the same + // type masks as their inverses. + mask |= kAffine_Mask | kScale_Mask; + + // For rectStaysRect, in the affine case, we only need check that + // the primary diagonal is all zeros and that the secondary diagonal + // is all non-zero. + + // map non-zero to 1 + m01 = m01 != 0; + m10 = m10 != 0; + + int dp0 = 0 == (m00 | m11) ; // true if both are 0 + int ds1 = m01 & m10; // true if both are 1 + + mask |= (dp0 & ds1) << kRectStaysRect_Shift; + } else { + // Only test for scale explicitly if not affine, since affine sets the + // scale bit. + if ((m00 ^ kScalar1Int) | (m11 ^ kScalar1Int)) { + mask |= kScale_Mask; + } + + // Not affine, therefore we already know secondary diagonal is + // all zeros, so we just need to check that primary diagonal is + // all non-zero. + + // map non-zero to 1 + m00 = m00 != 0; + m11 = m11 != 0; + + // record if the (p)rimary diagonal is all non-zero + mask |= (m00 & m11) << kRectStaysRect_Shift; + } + + return SkToU8(mask); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool operator==(const SkMatrix& a, const SkMatrix& b) { + const SkScalar* SK_RESTRICT ma = a.fMat; + const SkScalar* SK_RESTRICT mb = b.fMat; + + return ma[0] == mb[0] && ma[1] == mb[1] && ma[2] == mb[2] && + ma[3] == mb[3] && ma[4] == mb[4] && ma[5] == mb[5] && + ma[6] == mb[6] && ma[7] == mb[7] && ma[8] == mb[8]; +} + +/////////////////////////////////////////////////////////////////////////////// + +// helper function to determine if upper-left 2x2 of matrix is degenerate +static inline bool is_degenerate_2x2(SkScalar scaleX, SkScalar skewX, + SkScalar skewY, SkScalar scaleY) { + SkScalar perp_dot = scaleX*scaleY - skewX*skewY; + return SkScalarNearlyZero(perp_dot, SK_ScalarNearlyZero*SK_ScalarNearlyZero); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkMatrix::isSimilarity(SkScalar tol) const { + // if identity or translate matrix + TypeMask mask = this->getType(); + if (mask <= kTranslate_Mask) { + return true; + } + if (mask & kPerspective_Mask) { + return false; + } + + SkScalar mx = fMat[kMScaleX]; + SkScalar my = fMat[kMScaleY]; + // if no skew, can just compare scale factors + if (!(mask & kAffine_Mask)) { + return !SkScalarNearlyZero(mx) && SkScalarNearlyEqual(SkScalarAbs(mx), SkScalarAbs(my)); + } + SkScalar sx = fMat[kMSkewX]; + SkScalar sy = fMat[kMSkewY]; + + if (is_degenerate_2x2(mx, sx, sy, my)) { + return false; + } + + // upper 2x2 is rotation/reflection + uniform scale if basis vectors + // are 90 degree rotations of each other + return (SkScalarNearlyEqual(mx, my, tol) && SkScalarNearlyEqual(sx, -sy, tol)) + || (SkScalarNearlyEqual(mx, -my, tol) && SkScalarNearlyEqual(sx, sy, tol)); +} + +bool SkMatrix::preservesRightAngles(SkScalar tol) const { + TypeMask mask = this->getType(); + + if (mask <= kTranslate_Mask) { + // identity, translate and/or scale + return true; + } + if (mask & kPerspective_Mask) { + return false; + } + + SkASSERT(mask & (kAffine_Mask | kScale_Mask)); + + SkScalar mx = fMat[kMScaleX]; + SkScalar my = fMat[kMScaleY]; + SkScalar sx = fMat[kMSkewX]; + SkScalar sy = fMat[kMSkewY]; + + if (is_degenerate_2x2(mx, sx, sy, my)) { + return false; + } + + // upper 2x2 is scale + rotation/reflection if basis vectors are orthogonal + SkVector vec[2]; + vec[0].set(mx, sy); + vec[1].set(sx, my); + + return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)); +} + +/////////////////////////////////////////////////////////////////////////////// + +static inline SkScalar sdot(SkScalar a, SkScalar b, SkScalar c, SkScalar d) { + return a * b + c * d; +} + +static inline SkScalar sdot(SkScalar a, SkScalar b, SkScalar c, SkScalar d, + SkScalar e, SkScalar f) { + return a * b + c * d + e * f; +} + +static inline SkScalar scross(SkScalar a, SkScalar b, SkScalar c, SkScalar d) { + return a * b - c * d; +} + +SkMatrix& SkMatrix::setTranslate(SkScalar dx, SkScalar dy) { + *this = SkMatrix(1, 0, dx, + 0, 1, dy, + 0, 0, 1, + (dx != 0 || dy != 0) ? kTranslate_Mask | kRectStaysRect_Mask + : kIdentity_Mask | kRectStaysRect_Mask); + return *this; +} + +SkMatrix& SkMatrix::preTranslate(SkScalar dx, SkScalar dy) { + const unsigned mask = this->getType(); + + if (mask <= kTranslate_Mask) { + fMat[kMTransX] += dx; + fMat[kMTransY] += dy; + } else if (mask & kPerspective_Mask) { + SkMatrix m; + m.setTranslate(dx, dy); + return this->preConcat(m); + } else { + fMat[kMTransX] += sdot(fMat[kMScaleX], dx, fMat[kMSkewX], dy); + fMat[kMTransY] += sdot(fMat[kMSkewY], dx, fMat[kMScaleY], dy); + } + this->updateTranslateMask(); + return *this; +} + +SkMatrix& SkMatrix::postTranslate(SkScalar dx, SkScalar dy) { + if (this->hasPerspective()) { + SkMatrix m; + m.setTranslate(dx, dy); + this->postConcat(m); + } else { + fMat[kMTransX] += dx; + fMat[kMTransY] += dy; + this->updateTranslateMask(); + } + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkMatrix& SkMatrix::setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { + if (1 == sx && 1 == sy) { + this->reset(); + } else { + this->setScaleTranslate(sx, sy, px - sx * px, py - sy * py); + } + return *this; +} + +SkMatrix& SkMatrix::setScale(SkScalar sx, SkScalar sy) { + *this = SkMatrix(sx, 0, 0, + 0, sy, 0, + 0, 0, 1, + (sx == 1 && sy == 1) ? kIdentity_Mask | kRectStaysRect_Mask + : kScale_Mask | kRectStaysRect_Mask); + return *this; +} + +SkMatrix& SkMatrix::preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { + if (1 == sx && 1 == sy) { + return *this; + } + + SkMatrix m; + m.setScale(sx, sy, px, py); + return this->preConcat(m); +} + +SkMatrix& SkMatrix::preScale(SkScalar sx, SkScalar sy) { + if (1 == sx && 1 == sy) { + return *this; + } + + // the assumption is that these multiplies are very cheap, and that + // a full concat and/or just computing the matrix type is more expensive. + // Also, the fixed-point case checks for overflow, but the float doesn't, + // so we can get away with these blind multiplies. + + fMat[kMScaleX] *= sx; + fMat[kMSkewY] *= sx; + fMat[kMPersp0] *= sx; + + fMat[kMSkewX] *= sy; + fMat[kMScaleY] *= sy; + fMat[kMPersp1] *= sy; + + // Attempt to simplify our type when applying an inverse scale. + // TODO: The persp/affine preconditions are in place to keep the mask consistent with + // what computeTypeMask() would produce (persp/skew always implies kScale). + // We should investigate whether these flag dependencies are truly needed. + if (fMat[kMScaleX] == 1 && fMat[kMScaleY] == 1 + && !(fTypeMask & (kPerspective_Mask | kAffine_Mask))) { + this->clearTypeMask(kScale_Mask); + } else { + this->orTypeMask(kScale_Mask); + } + return *this; +} + +SkMatrix& SkMatrix::postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { + if (1 == sx && 1 == sy) { + return *this; + } + SkMatrix m; + m.setScale(sx, sy, px, py); + return this->postConcat(m); +} + +SkMatrix& SkMatrix::postScale(SkScalar sx, SkScalar sy) { + if (1 == sx && 1 == sy) { + return *this; + } + SkMatrix m; + m.setScale(sx, sy); + return this->postConcat(m); +} + +// this guy perhaps can go away, if we have a fract/high-precision way to +// scale matrices +bool SkMatrix::postIDiv(int divx, int divy) { + if (divx == 0 || divy == 0) { + return false; + } + + const float invX = 1.f / divx; + const float invY = 1.f / divy; + + fMat[kMScaleX] *= invX; + fMat[kMSkewX] *= invX; + fMat[kMTransX] *= invX; + + fMat[kMScaleY] *= invY; + fMat[kMSkewY] *= invY; + fMat[kMTransY] *= invY; + + this->setTypeMask(kUnknown_Mask); + return true; +} + +//////////////////////////////////////////////////////////////////////////////////// + +SkMatrix& SkMatrix::setSinCos(SkScalar sinV, SkScalar cosV, SkScalar px, SkScalar py) { + const SkScalar oneMinusCosV = 1 - cosV; + + fMat[kMScaleX] = cosV; + fMat[kMSkewX] = -sinV; + fMat[kMTransX] = sdot(sinV, py, oneMinusCosV, px); + + fMat[kMSkewY] = sinV; + fMat[kMScaleY] = cosV; + fMat[kMTransY] = sdot(-sinV, px, oneMinusCosV, py); + + fMat[kMPersp0] = fMat[kMPersp1] = 0; + fMat[kMPersp2] = 1; + + this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); + return *this; +} + +SkMatrix& SkMatrix::setRSXform(const SkRSXform& xform) { + fMat[kMScaleX] = xform.fSCos; + fMat[kMSkewX] = -xform.fSSin; + fMat[kMTransX] = xform.fTx; + + fMat[kMSkewY] = xform.fSSin; + fMat[kMScaleY] = xform.fSCos; + fMat[kMTransY] = xform.fTy; + + fMat[kMPersp0] = fMat[kMPersp1] = 0; + fMat[kMPersp2] = 1; + + this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); + return *this; +} + +SkMatrix& SkMatrix::setSinCos(SkScalar sinV, SkScalar cosV) { + fMat[kMScaleX] = cosV; + fMat[kMSkewX] = -sinV; + fMat[kMTransX] = 0; + + fMat[kMSkewY] = sinV; + fMat[kMScaleY] = cosV; + fMat[kMTransY] = 0; + + fMat[kMPersp0] = fMat[kMPersp1] = 0; + fMat[kMPersp2] = 1; + + this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); + return *this; +} + +SkMatrix& SkMatrix::setRotate(SkScalar degrees, SkScalar px, SkScalar py) { + SkScalar rad = SkDegreesToRadians(degrees); + return this->setSinCos(SkScalarSinSnapToZero(rad), SkScalarCosSnapToZero(rad), px, py); +} + +SkMatrix& SkMatrix::setRotate(SkScalar degrees) { + SkScalar rad = SkDegreesToRadians(degrees); + return this->setSinCos(SkScalarSinSnapToZero(rad), SkScalarCosSnapToZero(rad)); +} + +SkMatrix& SkMatrix::preRotate(SkScalar degrees, SkScalar px, SkScalar py) { + SkMatrix m; + m.setRotate(degrees, px, py); + return this->preConcat(m); +} + +SkMatrix& SkMatrix::preRotate(SkScalar degrees) { + SkMatrix m; + m.setRotate(degrees); + return this->preConcat(m); +} + +SkMatrix& SkMatrix::postRotate(SkScalar degrees, SkScalar px, SkScalar py) { + SkMatrix m; + m.setRotate(degrees, px, py); + return this->postConcat(m); +} + +SkMatrix& SkMatrix::postRotate(SkScalar degrees) { + SkMatrix m; + m.setRotate(degrees); + return this->postConcat(m); +} + +//////////////////////////////////////////////////////////////////////////////////// + +SkMatrix& SkMatrix::setSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { + *this = SkMatrix(1, sx, -sx * py, + sy, 1, -sy * px, + 0, 0, 1, + kUnknown_Mask | kOnlyPerspectiveValid_Mask); + return *this; +} + +SkMatrix& SkMatrix::setSkew(SkScalar sx, SkScalar sy) { + fMat[kMScaleX] = 1; + fMat[kMSkewX] = sx; + fMat[kMTransX] = 0; + + fMat[kMSkewY] = sy; + fMat[kMScaleY] = 1; + fMat[kMTransY] = 0; + + fMat[kMPersp0] = fMat[kMPersp1] = 0; + fMat[kMPersp2] = 1; + + this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); + return *this; +} + +SkMatrix& SkMatrix::preSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { + SkMatrix m; + m.setSkew(sx, sy, px, py); + return this->preConcat(m); +} + +SkMatrix& SkMatrix::preSkew(SkScalar sx, SkScalar sy) { + SkMatrix m; + m.setSkew(sx, sy); + return this->preConcat(m); +} + +SkMatrix& SkMatrix::postSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { + SkMatrix m; + m.setSkew(sx, sy, px, py); + return this->postConcat(m); +} + +SkMatrix& SkMatrix::postSkew(SkScalar sx, SkScalar sy) { + SkMatrix m; + m.setSkew(sx, sy); + return this->postConcat(m); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkMatrix::setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit align) { + if (src.isEmpty()) { + this->reset(); + return false; + } + + if (dst.isEmpty()) { + sk_bzero(fMat, 8 * sizeof(SkScalar)); + fMat[kMPersp2] = 1; + this->setTypeMask(kScale_Mask | kRectStaysRect_Mask); + } else { + SkScalar tx, sx = dst.width() / src.width(); + SkScalar ty, sy = dst.height() / src.height(); + bool xLarger = false; + + if (align != kFill_ScaleToFit) { + if (sx > sy) { + xLarger = true; + sx = sy; + } else { + sy = sx; + } + } + + tx = dst.fLeft - src.fLeft * sx; + ty = dst.fTop - src.fTop * sy; + if (align == kCenter_ScaleToFit || align == kEnd_ScaleToFit) { + SkScalar diff; + + if (xLarger) { + diff = dst.width() - src.width() * sy; + } else { + diff = dst.height() - src.height() * sy; + } + + if (align == kCenter_ScaleToFit) { + diff = SkScalarHalf(diff); + } + + if (xLarger) { + tx += diff; + } else { + ty += diff; + } + } + + this->setScaleTranslate(sx, sy, tx, ty); + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +static inline float muladdmul(float a, float b, float c, float d) { + return sk_double_to_float((double)a * b + (double)c * d); +} + +static inline float rowcol3(const float row[], const float col[]) { + return row[0] * col[0] + row[1] * col[3] + row[2] * col[6]; +} + +static bool only_scale_and_translate(unsigned mask) { + return 0 == (mask & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)); +} + +SkMatrix& SkMatrix::setConcat(const SkMatrix& a, const SkMatrix& b) { + TypeMask aType = a.getType(); + TypeMask bType = b.getType(); + + if (a.isTriviallyIdentity()) { + *this = b; + } else if (b.isTriviallyIdentity()) { + *this = a; + } else if (only_scale_and_translate(aType | bType)) { + this->setScaleTranslate(a.fMat[kMScaleX] * b.fMat[kMScaleX], + a.fMat[kMScaleY] * b.fMat[kMScaleY], + a.fMat[kMScaleX] * b.fMat[kMTransX] + a.fMat[kMTransX], + a.fMat[kMScaleY] * b.fMat[kMTransY] + a.fMat[kMTransY]); + } else { + SkMatrix tmp; + + if ((aType | bType) & kPerspective_Mask) { + tmp.fMat[kMScaleX] = rowcol3(&a.fMat[0], &b.fMat[0]); + tmp.fMat[kMSkewX] = rowcol3(&a.fMat[0], &b.fMat[1]); + tmp.fMat[kMTransX] = rowcol3(&a.fMat[0], &b.fMat[2]); + tmp.fMat[kMSkewY] = rowcol3(&a.fMat[3], &b.fMat[0]); + tmp.fMat[kMScaleY] = rowcol3(&a.fMat[3], &b.fMat[1]); + tmp.fMat[kMTransY] = rowcol3(&a.fMat[3], &b.fMat[2]); + tmp.fMat[kMPersp0] = rowcol3(&a.fMat[6], &b.fMat[0]); + tmp.fMat[kMPersp1] = rowcol3(&a.fMat[6], &b.fMat[1]); + tmp.fMat[kMPersp2] = rowcol3(&a.fMat[6], &b.fMat[2]); + + normalize_perspective(tmp.fMat); + tmp.setTypeMask(kUnknown_Mask); + } else { + tmp.fMat[kMScaleX] = muladdmul(a.fMat[kMScaleX], + b.fMat[kMScaleX], + a.fMat[kMSkewX], + b.fMat[kMSkewY]); + + tmp.fMat[kMSkewX] = muladdmul(a.fMat[kMScaleX], + b.fMat[kMSkewX], + a.fMat[kMSkewX], + b.fMat[kMScaleY]); + + tmp.fMat[kMTransX] = muladdmul(a.fMat[kMScaleX], + b.fMat[kMTransX], + a.fMat[kMSkewX], + b.fMat[kMTransY]) + a.fMat[kMTransX]; + + tmp.fMat[kMSkewY] = muladdmul(a.fMat[kMSkewY], + b.fMat[kMScaleX], + a.fMat[kMScaleY], + b.fMat[kMSkewY]); + + tmp.fMat[kMScaleY] = muladdmul(a.fMat[kMSkewY], + b.fMat[kMSkewX], + a.fMat[kMScaleY], + b.fMat[kMScaleY]); + + tmp.fMat[kMTransY] = muladdmul(a.fMat[kMSkewY], + b.fMat[kMTransX], + a.fMat[kMScaleY], + b.fMat[kMTransY]) + a.fMat[kMTransY]; + + tmp.fMat[kMPersp0] = 0; + tmp.fMat[kMPersp1] = 0; + tmp.fMat[kMPersp2] = 1; + //SkDebugf("Concat mat non-persp type: %d\n", tmp.getType()); + //SkASSERT(!(tmp.getType() & kPerspective_Mask)); + tmp.setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); + } + *this = tmp; + } + return *this; +} + +SkMatrix& SkMatrix::preConcat(const SkMatrix& mat) { + // check for identity first, so we don't do a needless copy of ourselves + // to ourselves inside setConcat() + if(!mat.isIdentity()) { + this->setConcat(*this, mat); + } + return *this; +} + +SkMatrix& SkMatrix::postConcat(const SkMatrix& mat) { + // check for identity first, so we don't do a needless copy of ourselves + // to ourselves inside setConcat() + if (!mat.isIdentity()) { + this->setConcat(mat, *this); + } + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// + +/* Matrix inversion is very expensive, but also the place where keeping + precision may be most important (here and matrix concat). Hence to avoid + bitmap blitting artifacts when walking the inverse, we use doubles for + the intermediate math, even though we know that is more expensive. + */ + +static inline SkScalar scross_dscale(SkScalar a, SkScalar b, + SkScalar c, SkScalar d, double scale) { + return SkDoubleToScalar(scross(a, b, c, d) * scale); +} + +static inline double dcross(double a, double b, double c, double d) { + return a * b - c * d; +} + +static inline SkScalar dcross_dscale(double a, double b, + double c, double d, double scale) { + return SkDoubleToScalar(dcross(a, b, c, d) * scale); +} + +static double sk_inv_determinant(const float mat[9], int isPerspective) { + double det; + + if (isPerspective) { + det = mat[SkMatrix::kMScaleX] * + dcross(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2], + mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1]) + + + mat[SkMatrix::kMSkewX] * + dcross(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0], + mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2]) + + + mat[SkMatrix::kMTransX] * + dcross(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1], + mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]); + } else { + det = dcross(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY], + mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]); + } + + // Since the determinant is on the order of the cube of the matrix members, + // compare to the cube of the default nearly-zero constant (although an + // estimate of the condition number would be better if it wasn't so expensive). + if (SkScalarNearlyZero(sk_double_to_float(det), + SK_ScalarNearlyZero * SK_ScalarNearlyZero * SK_ScalarNearlyZero)) { + return 0; + } + return 1.0 / det; +} + +void SkMatrix::SetAffineIdentity(SkScalar affine[6]) { + affine[kAScaleX] = 1; + affine[kASkewY] = 0; + affine[kASkewX] = 0; + affine[kAScaleY] = 1; + affine[kATransX] = 0; + affine[kATransY] = 0; +} + +bool SkMatrix::asAffine(SkScalar affine[6]) const { + if (this->hasPerspective()) { + return false; + } + if (affine) { + affine[kAScaleX] = this->fMat[kMScaleX]; + affine[kASkewY] = this->fMat[kMSkewY]; + affine[kASkewX] = this->fMat[kMSkewX]; + affine[kAScaleY] = this->fMat[kMScaleY]; + affine[kATransX] = this->fMat[kMTransX]; + affine[kATransY] = this->fMat[kMTransY]; + } + return true; +} + +void SkMatrix::mapPoints(SkPoint dst[], const SkPoint src[], int count) const { + SkASSERT((dst && src && count > 0) || 0 == count); + // no partial overlap + SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]); + this->getMapPtsProc()(*this, dst, src, count); +} + +void SkMatrix::mapXY(SkScalar x, SkScalar y, SkPoint* result) const { + SkASSERT(result); + this->getMapXYProc()(*this, x, y, result); +} + +void SkMatrix::ComputeInv(SkScalar dst[9], const SkScalar src[9], double invDet, bool isPersp) { + SkASSERT(src != dst); + SkASSERT(src && dst); + + if (isPersp) { + dst[kMScaleX] = scross_dscale(src[kMScaleY], src[kMPersp2], src[kMTransY], src[kMPersp1], invDet); + dst[kMSkewX] = scross_dscale(src[kMTransX], src[kMPersp1], src[kMSkewX], src[kMPersp2], invDet); + dst[kMTransX] = scross_dscale(src[kMSkewX], src[kMTransY], src[kMTransX], src[kMScaleY], invDet); + + dst[kMSkewY] = scross_dscale(src[kMTransY], src[kMPersp0], src[kMSkewY], src[kMPersp2], invDet); + dst[kMScaleY] = scross_dscale(src[kMScaleX], src[kMPersp2], src[kMTransX], src[kMPersp0], invDet); + dst[kMTransY] = scross_dscale(src[kMTransX], src[kMSkewY], src[kMScaleX], src[kMTransY], invDet); + + dst[kMPersp0] = scross_dscale(src[kMSkewY], src[kMPersp1], src[kMScaleY], src[kMPersp0], invDet); + dst[kMPersp1] = scross_dscale(src[kMSkewX], src[kMPersp0], src[kMScaleX], src[kMPersp1], invDet); + dst[kMPersp2] = scross_dscale(src[kMScaleX], src[kMScaleY], src[kMSkewX], src[kMSkewY], invDet); + } else { // not perspective + dst[kMScaleX] = SkDoubleToScalar(src[kMScaleY] * invDet); + dst[kMSkewX] = SkDoubleToScalar(-src[kMSkewX] * invDet); + dst[kMTransX] = dcross_dscale(src[kMSkewX], src[kMTransY], src[kMScaleY], src[kMTransX], invDet); + + dst[kMSkewY] = SkDoubleToScalar(-src[kMSkewY] * invDet); + dst[kMScaleY] = SkDoubleToScalar(src[kMScaleX] * invDet); + dst[kMTransY] = dcross_dscale(src[kMSkewY], src[kMTransX], src[kMScaleX], src[kMTransY], invDet); + + dst[kMPersp0] = 0; + dst[kMPersp1] = 0; + dst[kMPersp2] = 1; + } +} + +bool SkMatrix::invertNonIdentity(SkMatrix* inv) const { + SkASSERT(!this->isIdentity()); + + TypeMask mask = this->getType(); + + if (0 == (mask & ~(kScale_Mask | kTranslate_Mask))) { + bool invertible = true; + if (inv) { + if (mask & kScale_Mask) { + SkScalar invX = fMat[kMScaleX]; + SkScalar invY = fMat[kMScaleY]; + if (0 == invX || 0 == invY) { + return false; + } + invX = SkScalarInvert(invX); + invY = SkScalarInvert(invY); + + // Must be careful when writing to inv, since it may be the + // same memory as this. + + inv->fMat[kMSkewX] = inv->fMat[kMSkewY] = + inv->fMat[kMPersp0] = inv->fMat[kMPersp1] = 0; + + inv->fMat[kMScaleX] = invX; + inv->fMat[kMScaleY] = invY; + inv->fMat[kMPersp2] = 1; + inv->fMat[kMTransX] = -fMat[kMTransX] * invX; + inv->fMat[kMTransY] = -fMat[kMTransY] * invY; + + inv->setTypeMask(mask | kRectStaysRect_Mask); + } else { + // translate only + inv->setTranslate(-fMat[kMTransX], -fMat[kMTransY]); + } + } else { // inv is nullptr, just check if we're invertible + if (!fMat[kMScaleX] || !fMat[kMScaleY]) { + invertible = false; + } + } + return invertible; + } + + int isPersp = mask & kPerspective_Mask; + double invDet = sk_inv_determinant(fMat, isPersp); + + if (invDet == 0) { // underflow + return false; + } + + bool applyingInPlace = (inv == this); + + SkMatrix* tmp = inv; + + SkMatrix storage; + if (applyingInPlace || nullptr == tmp) { + tmp = &storage; // we either need to avoid trampling memory or have no memory + } + + ComputeInv(tmp->fMat, fMat, invDet, isPersp); + if (!tmp->isFinite()) { + return false; + } + + tmp->setTypeMask(fTypeMask); + + if (applyingInPlace) { + *inv = storage; // need to copy answer back + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix::Identity_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { + SkASSERT(m.getType() == 0); + + if (dst != src && count > 0) { + memcpy(dst, src, count * sizeof(SkPoint)); + } +} + +void SkMatrix::Trans_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { + SkASSERT(m.getType() <= SkMatrix::kTranslate_Mask); + if (count > 0) { + SkScalar tx = m.getTranslateX(); + SkScalar ty = m.getTranslateY(); + if (count & 1) { + dst->fX = src->fX + tx; + dst->fY = src->fY + ty; + src += 1; + dst += 1; + } + Sk4s trans4(tx, ty, tx, ty); + count >>= 1; + if (count & 1) { + (Sk4s::Load(src) + trans4).store(dst); + src += 2; + dst += 2; + } + count >>= 1; + for (int i = 0; i < count; ++i) { + (Sk4s::Load(src+0) + trans4).store(dst+0); + (Sk4s::Load(src+2) + trans4).store(dst+2); + src += 4; + dst += 4; + } + } +} + +void SkMatrix::Scale_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { + SkASSERT(m.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)); + if (count > 0) { + SkScalar tx = m.getTranslateX(); + SkScalar ty = m.getTranslateY(); + SkScalar sx = m.getScaleX(); + SkScalar sy = m.getScaleY(); + if (count & 1) { + dst->fX = src->fX * sx + tx; + dst->fY = src->fY * sy + ty; + src += 1; + dst += 1; + } + Sk4s trans4(tx, ty, tx, ty); + Sk4s scale4(sx, sy, sx, sy); + count >>= 1; + if (count & 1) { + (Sk4s::Load(src) * scale4 + trans4).store(dst); + src += 2; + dst += 2; + } + count >>= 1; + for (int i = 0; i < count; ++i) { + (Sk4s::Load(src+0) * scale4 + trans4).store(dst+0); + (Sk4s::Load(src+2) * scale4 + trans4).store(dst+2); + src += 4; + dst += 4; + } + } +} + +void SkMatrix::Persp_pts(const SkMatrix& m, SkPoint dst[], + const SkPoint src[], int count) { + SkASSERT(m.hasPerspective()); + + if (count > 0) { + do { + SkScalar sy = src->fY; + SkScalar sx = src->fX; + src += 1; + + SkScalar x = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; + SkScalar y = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; +#ifdef SK_LEGACY_MATRIX_MATH_ORDER + SkScalar z = sx * m.fMat[kMPersp0] + (sy * m.fMat[kMPersp1] + m.fMat[kMPersp2]); +#else + SkScalar z = sdot(sx, m.fMat[kMPersp0], sy, m.fMat[kMPersp1]) + m.fMat[kMPersp2]; +#endif + if (z) { + z = 1 / z; + } + + dst->fY = y * z; + dst->fX = x * z; + dst += 1; + } while (--count); + } +} + +void SkMatrix::Affine_vpts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { + SkASSERT(m.getType() != SkMatrix::kPerspective_Mask); + if (count > 0) { + SkScalar tx = m.getTranslateX(); + SkScalar ty = m.getTranslateY(); + SkScalar sx = m.getScaleX(); + SkScalar sy = m.getScaleY(); + SkScalar kx = m.getSkewX(); + SkScalar ky = m.getSkewY(); + if (count & 1) { + dst->set(src->fX * sx + src->fY * kx + tx, + src->fX * ky + src->fY * sy + ty); + src += 1; + dst += 1; + } + Sk4s trans4(tx, ty, tx, ty); + Sk4s scale4(sx, sy, sx, sy); + Sk4s skew4(kx, ky, kx, ky); // applied to swizzle of src4 + count >>= 1; + for (int i = 0; i < count; ++i) { + Sk4s src4 = Sk4s::Load(src); + Sk4s swz4 = SkNx_shuffle<1,0,3,2>(src4); // y0 x0, y1 x1 + (src4 * scale4 + swz4 * skew4 + trans4).store(dst); + src += 2; + dst += 2; + } + } +} + +const SkMatrix::MapPtsProc SkMatrix::gMapPtsProcs[] = { + SkMatrix::Identity_pts, SkMatrix::Trans_pts, + SkMatrix::Scale_pts, SkMatrix::Scale_pts, + SkMatrix::Affine_vpts, SkMatrix::Affine_vpts, + SkMatrix::Affine_vpts, SkMatrix::Affine_vpts, + // repeat the persp proc 8 times + SkMatrix::Persp_pts, SkMatrix::Persp_pts, + SkMatrix::Persp_pts, SkMatrix::Persp_pts, + SkMatrix::Persp_pts, SkMatrix::Persp_pts, + SkMatrix::Persp_pts, SkMatrix::Persp_pts +}; + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrixPriv::MapHomogeneousPointsWithStride(const SkMatrix& mx, SkPoint3 dst[], + size_t dstStride, const SkPoint3 src[], + size_t srcStride, int count) { + SkASSERT((dst && src && count > 0) || 0 == count); + // no partial overlap + SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]); + + if (count > 0) { + if (mx.isIdentity()) { + if (src != dst) { + if (srcStride == sizeof(SkPoint3) && dstStride == sizeof(SkPoint3)) { + memcpy(dst, src, count * sizeof(SkPoint3)); + } else { + for (int i = 0; i < count; ++i) { + *dst = *src; + dst = reinterpret_cast(reinterpret_cast(dst) + dstStride); + src = reinterpret_cast(reinterpret_cast(src) + + srcStride); + } + } + } + return; + } + do { + SkScalar sx = src->fX; + SkScalar sy = src->fY; + SkScalar sw = src->fZ; + src = reinterpret_cast(reinterpret_cast(src) + srcStride); + const SkScalar* mat = mx.fMat; + typedef SkMatrix M; + SkScalar x = sdot(sx, mat[M::kMScaleX], sy, mat[M::kMSkewX], sw, mat[M::kMTransX]); + SkScalar y = sdot(sx, mat[M::kMSkewY], sy, mat[M::kMScaleY], sw, mat[M::kMTransY]); + SkScalar w = sdot(sx, mat[M::kMPersp0], sy, mat[M::kMPersp1], sw, mat[M::kMPersp2]); + + dst->set(x, y, w); + dst = reinterpret_cast(reinterpret_cast(dst) + dstStride); + } while (--count); + } +} + +void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const { + SkMatrixPriv::MapHomogeneousPointsWithStride(*this, dst, sizeof(SkPoint3), src, + sizeof(SkPoint3), count); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix::mapVectors(SkPoint dst[], const SkPoint src[], int count) const { + if (this->hasPerspective()) { + SkPoint origin; + + MapXYProc proc = this->getMapXYProc(); + proc(*this, 0, 0, &origin); + + for (int i = count - 1; i >= 0; --i) { + SkPoint tmp; + + proc(*this, src[i].fX, src[i].fY, &tmp); + dst[i].set(tmp.fX - origin.fX, tmp.fY - origin.fY); + } + } else { + SkMatrix tmp = *this; + + tmp.fMat[kMTransX] = tmp.fMat[kMTransY] = 0; + tmp.clearTypeMask(kTranslate_Mask); + tmp.mapPoints(dst, src, count); + } +} + +static Sk4f sort_as_rect(const Sk4f& ltrb) { + Sk4f rblt(ltrb[2], ltrb[3], ltrb[0], ltrb[1]); + Sk4f min = Sk4f::Min(ltrb, rblt); + Sk4f max = Sk4f::Max(ltrb, rblt); + // We can extract either pair [0,1] or [2,3] from min and max and be correct, but on + // ARM this sequence generates the fastest (a single instruction). + return Sk4f(min[2], min[3], max[0], max[1]); +} + +void SkMatrix::mapRectScaleTranslate(SkRect* dst, const SkRect& src) const { + SkASSERT(dst); + SkASSERT(this->isScaleTranslate()); + + SkScalar sx = fMat[kMScaleX]; + SkScalar sy = fMat[kMScaleY]; + SkScalar tx = fMat[kMTransX]; + SkScalar ty = fMat[kMTransY]; + Sk4f scale(sx, sy, sx, sy); + Sk4f trans(tx, ty, tx, ty); + sort_as_rect(Sk4f::Load(&src.fLeft) * scale + trans).store(&dst->fLeft); +} + +bool SkMatrix::mapRect(SkRect* dst, const SkRect& src) const { + SkASSERT(dst); + + if (this->getType() <= kTranslate_Mask) { + SkScalar tx = fMat[kMTransX]; + SkScalar ty = fMat[kMTransY]; + Sk4f trans(tx, ty, tx, ty); + sort_as_rect(Sk4f::Load(&src.fLeft) + trans).store(&dst->fLeft); + return true; + } + if (this->isScaleTranslate()) { + this->mapRectScaleTranslate(dst, src); + return true; + } else { + SkPoint quad[4]; + + src.toQuad(quad); + this->mapPoints(quad, quad, 4); + dst->setBoundsNoCheck(quad, 4); + return this->rectStaysRect(); // might still return true if rotated by 90, etc. + } +} + +SkScalar SkMatrix::mapRadius(SkScalar radius) const { + SkVector vec[2]; + + vec[0].set(radius, 0); + vec[1].set(0, radius); + this->mapVectors(vec, 2); + + SkScalar d0 = vec[0].length(); + SkScalar d1 = vec[1].length(); + + // return geometric mean + return SkScalarSqrt(d0 * d1); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix::Persp_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT(m.hasPerspective()); + + SkScalar x = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; + SkScalar y = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; + SkScalar z = sdot(sx, m.fMat[kMPersp0], sy, m.fMat[kMPersp1]) + m.fMat[kMPersp2]; + if (z) { + z = 1 / z; + } + pt->fX = x * z; + pt->fY = y * z; +} + +void SkMatrix::RotTrans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT((m.getType() & (kAffine_Mask | kPerspective_Mask)) == kAffine_Mask); + +#ifdef SK_LEGACY_MATRIX_MATH_ORDER + pt->fX = sx * m.fMat[kMScaleX] + (sy * m.fMat[kMSkewX] + m.fMat[kMTransX]); + pt->fY = sx * m.fMat[kMSkewY] + (sy * m.fMat[kMScaleY] + m.fMat[kMTransY]); +#else + pt->fX = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; + pt->fY = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; +#endif +} + +void SkMatrix::Rot_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT((m.getType() & (kAffine_Mask | kPerspective_Mask))== kAffine_Mask); + SkASSERT(0 == m.fMat[kMTransX]); + SkASSERT(0 == m.fMat[kMTransY]); + +#ifdef SK_LEGACY_MATRIX_MATH_ORDER + pt->fX = sx * m.fMat[kMScaleX] + (sy * m.fMat[kMSkewX] + m.fMat[kMTransX]); + pt->fY = sx * m.fMat[kMSkewY] + (sy * m.fMat[kMScaleY] + m.fMat[kMTransY]); +#else + pt->fX = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; + pt->fY = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; +#endif +} + +void SkMatrix::ScaleTrans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT((m.getType() & (kScale_Mask | kAffine_Mask | kPerspective_Mask)) + == kScale_Mask); + + pt->fX = sx * m.fMat[kMScaleX] + m.fMat[kMTransX]; + pt->fY = sy * m.fMat[kMScaleY] + m.fMat[kMTransY]; +} + +void SkMatrix::Scale_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT((m.getType() & (kScale_Mask | kAffine_Mask | kPerspective_Mask)) + == kScale_Mask); + SkASSERT(0 == m.fMat[kMTransX]); + SkASSERT(0 == m.fMat[kMTransY]); + + pt->fX = sx * m.fMat[kMScaleX]; + pt->fY = sy * m.fMat[kMScaleY]; +} + +void SkMatrix::Trans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT(m.getType() == kTranslate_Mask); + + pt->fX = sx + m.fMat[kMTransX]; + pt->fY = sy + m.fMat[kMTransY]; +} + +void SkMatrix::Identity_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, + SkPoint* pt) { + SkASSERT(0 == m.getType()); + + pt->fX = sx; + pt->fY = sy; +} + +const SkMatrix::MapXYProc SkMatrix::gMapXYProcs[] = { + SkMatrix::Identity_xy, SkMatrix::Trans_xy, + SkMatrix::Scale_xy, SkMatrix::ScaleTrans_xy, + SkMatrix::Rot_xy, SkMatrix::RotTrans_xy, + SkMatrix::Rot_xy, SkMatrix::RotTrans_xy, + // repeat the persp proc 8 times + SkMatrix::Persp_xy, SkMatrix::Persp_xy, + SkMatrix::Persp_xy, SkMatrix::Persp_xy, + SkMatrix::Persp_xy, SkMatrix::Persp_xy, + SkMatrix::Persp_xy, SkMatrix::Persp_xy +}; + +/////////////////////////////////////////////////////////////////////////////// + +// if its nearly zero (just made up 26, perhaps it should be bigger or smaller) +#define PerspNearlyZero(x) SkScalarNearlyZero(x, (1.0f / (1 << 26))) + +bool SkMatrix::isFixedStepInX() const { + return PerspNearlyZero(fMat[kMPersp0]); +} + +SkVector SkMatrix::fixedStepInX(SkScalar y) const { + SkASSERT(PerspNearlyZero(fMat[kMPersp0])); + if (PerspNearlyZero(fMat[kMPersp1]) && + PerspNearlyZero(fMat[kMPersp2] - 1)) { + return SkVector::Make(fMat[kMScaleX], fMat[kMSkewY]); + } else { + SkScalar z = y * fMat[kMPersp1] + fMat[kMPersp2]; + return SkVector::Make(fMat[kMScaleX] / z, fMat[kMSkewY] / z); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +static inline bool checkForZero(float x) { + return x*x == 0; +} + +bool SkMatrix::Poly2Proc(const SkPoint srcPt[], SkMatrix* dst) { + dst->fMat[kMScaleX] = srcPt[1].fY - srcPt[0].fY; + dst->fMat[kMSkewY] = srcPt[0].fX - srcPt[1].fX; + dst->fMat[kMPersp0] = 0; + + dst->fMat[kMSkewX] = srcPt[1].fX - srcPt[0].fX; + dst->fMat[kMScaleY] = srcPt[1].fY - srcPt[0].fY; + dst->fMat[kMPersp1] = 0; + + dst->fMat[kMTransX] = srcPt[0].fX; + dst->fMat[kMTransY] = srcPt[0].fY; + dst->fMat[kMPersp2] = 1; + dst->setTypeMask(kUnknown_Mask); + return true; +} + +bool SkMatrix::Poly3Proc(const SkPoint srcPt[], SkMatrix* dst) { + dst->fMat[kMScaleX] = srcPt[2].fX - srcPt[0].fX; + dst->fMat[kMSkewY] = srcPt[2].fY - srcPt[0].fY; + dst->fMat[kMPersp0] = 0; + + dst->fMat[kMSkewX] = srcPt[1].fX - srcPt[0].fX; + dst->fMat[kMScaleY] = srcPt[1].fY - srcPt[0].fY; + dst->fMat[kMPersp1] = 0; + + dst->fMat[kMTransX] = srcPt[0].fX; + dst->fMat[kMTransY] = srcPt[0].fY; + dst->fMat[kMPersp2] = 1; + dst->setTypeMask(kUnknown_Mask); + return true; +} + +bool SkMatrix::Poly4Proc(const SkPoint srcPt[], SkMatrix* dst) { + float a1, a2; + float x0, y0, x1, y1, x2, y2; + + x0 = srcPt[2].fX - srcPt[0].fX; + y0 = srcPt[2].fY - srcPt[0].fY; + x1 = srcPt[2].fX - srcPt[1].fX; + y1 = srcPt[2].fY - srcPt[1].fY; + x2 = srcPt[2].fX - srcPt[3].fX; + y2 = srcPt[2].fY - srcPt[3].fY; + + /* check if abs(x2) > abs(y2) */ + if ( x2 > 0 ? y2 > 0 ? x2 > y2 : x2 > -y2 : y2 > 0 ? -x2 > y2 : x2 < y2) { + float denom = sk_ieee_float_divide(x1 * y2, x2) - y1; + if (checkForZero(denom)) { + return false; + } + a1 = (((x0 - x1) * y2 / x2) - y0 + y1) / denom; + } else { + float denom = x1 - sk_ieee_float_divide(y1 * x2, y2); + if (checkForZero(denom)) { + return false; + } + a1 = (x0 - x1 - sk_ieee_float_divide((y0 - y1) * x2, y2)) / denom; + } + + /* check if abs(x1) > abs(y1) */ + if ( x1 > 0 ? y1 > 0 ? x1 > y1 : x1 > -y1 : y1 > 0 ? -x1 > y1 : x1 < y1) { + float denom = y2 - sk_ieee_float_divide(x2 * y1, x1); + if (checkForZero(denom)) { + return false; + } + a2 = (y0 - y2 - sk_ieee_float_divide((x0 - x2) * y1, x1)) / denom; + } else { + float denom = sk_ieee_float_divide(y2 * x1, y1) - x2; + if (checkForZero(denom)) { + return false; + } + a2 = (sk_ieee_float_divide((y0 - y2) * x1, y1) - x0 + x2) / denom; + } + + dst->fMat[kMScaleX] = a2 * srcPt[3].fX + srcPt[3].fX - srcPt[0].fX; + dst->fMat[kMSkewY] = a2 * srcPt[3].fY + srcPt[3].fY - srcPt[0].fY; + dst->fMat[kMPersp0] = a2; + + dst->fMat[kMSkewX] = a1 * srcPt[1].fX + srcPt[1].fX - srcPt[0].fX; + dst->fMat[kMScaleY] = a1 * srcPt[1].fY + srcPt[1].fY - srcPt[0].fY; + dst->fMat[kMPersp1] = a1; + + dst->fMat[kMTransX] = srcPt[0].fX; + dst->fMat[kMTransY] = srcPt[0].fY; + dst->fMat[kMPersp2] = 1; + dst->setTypeMask(kUnknown_Mask); + return true; +} + +typedef bool (*PolyMapProc)(const SkPoint[], SkMatrix*); + +/* Adapted from Rob Johnson's original sample code in QuickDraw GX +*/ +bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[], int count) { + if ((unsigned)count > 4) { + SkDebugf("--- SkMatrix::setPolyToPoly count out of range %d\n", count); + return false; + } + + if (0 == count) { + this->reset(); + return true; + } + if (1 == count) { + this->setTranslate(dst[0].fX - src[0].fX, dst[0].fY - src[0].fY); + return true; + } + + const PolyMapProc gPolyMapProcs[] = { + SkMatrix::Poly2Proc, SkMatrix::Poly3Proc, SkMatrix::Poly4Proc + }; + PolyMapProc proc = gPolyMapProcs[count - 2]; + + SkMatrix tempMap, result; + + if (!proc(src, &tempMap)) { + return false; + } + if (!tempMap.invert(&result)) { + return false; + } + if (!proc(dst, &tempMap)) { + return false; + } + this->setConcat(tempMap, result); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +enum MinMaxOrBoth { + kMin_MinMaxOrBoth, + kMax_MinMaxOrBoth, + kBoth_MinMaxOrBoth +}; + +template bool get_scale_factor(SkMatrix::TypeMask typeMask, + const SkScalar m[9], + SkScalar results[/*1 or 2*/]) { + if (typeMask & SkMatrix::kPerspective_Mask) { + return false; + } + if (SkMatrix::kIdentity_Mask == typeMask) { + results[0] = SK_Scalar1; + if (kBoth_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[1] = SK_Scalar1; + } + return true; + } + if (!(typeMask & SkMatrix::kAffine_Mask)) { + if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[0] = SkMinScalar(SkScalarAbs(m[SkMatrix::kMScaleX]), + SkScalarAbs(m[SkMatrix::kMScaleY])); + } else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[0] = SkMaxScalar(SkScalarAbs(m[SkMatrix::kMScaleX]), + SkScalarAbs(m[SkMatrix::kMScaleY])); + } else { + results[0] = SkScalarAbs(m[SkMatrix::kMScaleX]); + results[1] = SkScalarAbs(m[SkMatrix::kMScaleY]); + if (results[0] > results[1]) { + using std::swap; + swap(results[0], results[1]); + } + } + return true; + } + // ignore the translation part of the matrix, just look at 2x2 portion. + // compute singular values, take largest or smallest abs value. + // [a b; b c] = A^T*A + SkScalar a = sdot(m[SkMatrix::kMScaleX], m[SkMatrix::kMScaleX], + m[SkMatrix::kMSkewY], m[SkMatrix::kMSkewY]); + SkScalar b = sdot(m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewX], + m[SkMatrix::kMScaleY], m[SkMatrix::kMSkewY]); + SkScalar c = sdot(m[SkMatrix::kMSkewX], m[SkMatrix::kMSkewX], + m[SkMatrix::kMScaleY], m[SkMatrix::kMScaleY]); + // eigenvalues of A^T*A are the squared singular values of A. + // characteristic equation is det((A^T*A) - l*I) = 0 + // l^2 - (a + c)l + (ac-b^2) + // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff + // and roots are guaranteed to be pos and real). + SkScalar bSqd = b * b; + // if upper left 2x2 is orthogonal save some math + if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { + if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[0] = SkMinScalar(a, c); + } else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[0] = SkMaxScalar(a, c); + } else { + results[0] = a; + results[1] = c; + if (results[0] > results[1]) { + using std::swap; + swap(results[0], results[1]); + } + } + } else { + SkScalar aminusc = a - c; + SkScalar apluscdiv2 = SkScalarHalf(a + c); + SkScalar x = SkScalarHalf(SkScalarSqrt(aminusc * aminusc + 4 * bSqd)); + if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[0] = apluscdiv2 - x; + } else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + results[0] = apluscdiv2 + x; + } else { + results[0] = apluscdiv2 - x; + results[1] = apluscdiv2 + x; + } + } + if (!SkScalarIsFinite(results[0])) { + return false; + } + // Due to the floating point inaccuracy, there might be an error in a, b, c + // calculated by sdot, further deepened by subsequent arithmetic operations + // on them. Therefore, we allow and cap the nearly-zero negative values. + SkASSERT(results[0] >= -SK_ScalarNearlyZero); + if (results[0] < 0) { + results[0] = 0; + } + results[0] = SkScalarSqrt(results[0]); + if (kBoth_MinMaxOrBoth == MIN_MAX_OR_BOTH) { + if (!SkScalarIsFinite(results[1])) { + return false; + } + SkASSERT(results[1] >= -SK_ScalarNearlyZero); + if (results[1] < 0) { + results[1] = 0; + } + results[1] = SkScalarSqrt(results[1]); + } + return true; +} + +SkScalar SkMatrix::getMinScale() const { + SkScalar factor; + if (get_scale_factor(this->getType(), fMat, &factor)) { + return factor; + } else { + return -1; + } +} + +SkScalar SkMatrix::getMaxScale() const { + SkScalar factor; + if (get_scale_factor(this->getType(), fMat, &factor)) { + return factor; + } else { + return -1; + } +} + +bool SkMatrix::getMinMaxScales(SkScalar scaleFactors[2]) const { + return get_scale_factor(this->getType(), fMat, scaleFactors); +} + +const SkMatrix& SkMatrix::I() { + static constexpr SkMatrix identity; + SkASSERT(identity.isIdentity()); + return identity; +} + +const SkMatrix& SkMatrix::InvalidMatrix() { + static constexpr SkMatrix invalid(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, + SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, + SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, + kTranslate_Mask | kScale_Mask | + kAffine_Mask | kPerspective_Mask); + return invalid; +} + +bool SkMatrix::decomposeScale(SkSize* scale, SkMatrix* remaining) const { + if (this->hasPerspective()) { + return false; + } + + const SkScalar sx = SkVector::Length(this->getScaleX(), this->getSkewY()); + const SkScalar sy = SkVector::Length(this->getSkewX(), this->getScaleY()); + if (!SkScalarIsFinite(sx) || !SkScalarIsFinite(sy) || + SkScalarNearlyZero(sx) || SkScalarNearlyZero(sy)) { + return false; + } + + if (scale) { + scale->set(sx, sy); + } + if (remaining) { + *remaining = *this; + remaining->preScale(SkScalarInvert(sx), SkScalarInvert(sy)); + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +size_t SkMatrix::writeToMemory(void* buffer) const { + // TODO write less for simple matrices + static const size_t sizeInMemory = 9 * sizeof(SkScalar); + if (buffer) { + memcpy(buffer, fMat, sizeInMemory); + } + return sizeInMemory; +} + +size_t SkMatrix::readFromMemory(const void* buffer, size_t length) { + static const size_t sizeInMemory = 9 * sizeof(SkScalar); + if (length < sizeInMemory) { + return 0; + } + memcpy(fMat, buffer, sizeInMemory); + this->setTypeMask(kUnknown_Mask); + return sizeInMemory; +} + +void SkMatrix::dump() const { + SkString str; + str.appendf("[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]", + fMat[0], fMat[1], fMat[2], fMat[3], fMat[4], fMat[5], + fMat[6], fMat[7], fMat[8]); + SkDebugf("%s\n", str.c_str()); +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkMatrixUtils.h" + +bool SkTreatAsSprite(const SkMatrix& mat, const SkISize& size, const SkPaint& paint) { + // Our path aa is 2-bits, and our rect aa is 8, so we could use 8, + // but in practice 4 seems enough (still looks smooth) and allows + // more slightly fractional cases to fall into the fast (sprite) case. + static const unsigned kAntiAliasSubpixelBits = 4; + + const unsigned subpixelBits = paint.isAntiAlias() ? kAntiAliasSubpixelBits : 0; + + // quick reject on affine or perspective + if (mat.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) { + return false; + } + + // quick success check + if (!subpixelBits && !(mat.getType() & ~SkMatrix::kTranslate_Mask)) { + return true; + } + + // mapRect supports negative scales, so we eliminate those first + if (mat.getScaleX() < 0 || mat.getScaleY() < 0) { + return false; + } + + SkRect dst; + SkIRect isrc = SkIRect::MakeSize(size); + + { + SkRect src; + src.set(isrc); + mat.mapRect(&dst, src); + } + + // just apply the translate to isrc + isrc.offset(SkScalarRoundToInt(mat.getTranslateX()), + SkScalarRoundToInt(mat.getTranslateY())); + + if (subpixelBits) { + isrc.fLeft = SkLeftShift(isrc.fLeft, subpixelBits); + isrc.fTop = SkLeftShift(isrc.fTop, subpixelBits); + isrc.fRight = SkLeftShift(isrc.fRight, subpixelBits); + isrc.fBottom = SkLeftShift(isrc.fBottom, subpixelBits); + + const float scale = 1 << subpixelBits; + dst.fLeft *= scale; + dst.fTop *= scale; + dst.fRight *= scale; + dst.fBottom *= scale; + } + + SkIRect idst; + dst.round(&idst); + return isrc == idst; +} + +// A square matrix M can be decomposed (via polar decomposition) into two matrices -- +// an orthogonal matrix Q and a symmetric matrix S. In turn we can decompose S into U*W*U^T, +// where U is another orthogonal matrix and W is a scale matrix. These can be recombined +// to give M = (Q*U)*W*U^T, i.e., the product of two orthogonal matrices and a scale matrix. +// +// The one wrinkle is that traditionally Q may contain a reflection -- the +// calculation has been rejiggered to put that reflection into W. +bool SkDecomposeUpper2x2(const SkMatrix& matrix, + SkPoint* rotation1, + SkPoint* scale, + SkPoint* rotation2) { + + SkScalar A = matrix[SkMatrix::kMScaleX]; + SkScalar B = matrix[SkMatrix::kMSkewX]; + SkScalar C = matrix[SkMatrix::kMSkewY]; + SkScalar D = matrix[SkMatrix::kMScaleY]; + + if (is_degenerate_2x2(A, B, C, D)) { + return false; + } + + double w1, w2; + SkScalar cos1, sin1; + SkScalar cos2, sin2; + + // do polar decomposition (M = Q*S) + SkScalar cosQ, sinQ; + double Sa, Sb, Sd; + // if M is already symmetric (i.e., M = I*S) + if (SkScalarNearlyEqual(B, C)) { + cosQ = 1; + sinQ = 0; + + Sa = A; + Sb = B; + Sd = D; + } else { + cosQ = A + D; + sinQ = C - B; + SkScalar reciplen = SkScalarInvert(SkScalarSqrt(cosQ*cosQ + sinQ*sinQ)); + cosQ *= reciplen; + sinQ *= reciplen; + + // S = Q^-1*M + // we don't calc Sc since it's symmetric + Sa = A*cosQ + C*sinQ; + Sb = B*cosQ + D*sinQ; + Sd = -B*sinQ + D*cosQ; + } + + // Now we need to compute eigenvalues of S (our scale factors) + // and eigenvectors (bases for our rotation) + // From this, should be able to reconstruct S as U*W*U^T + if (SkScalarNearlyZero(SkDoubleToScalar(Sb))) { + // already diagonalized + cos1 = 1; + sin1 = 0; + w1 = Sa; + w2 = Sd; + cos2 = cosQ; + sin2 = sinQ; + } else { + double diff = Sa - Sd; + double discriminant = sqrt(diff*diff + 4.0*Sb*Sb); + double trace = Sa + Sd; + if (diff > 0) { + w1 = 0.5*(trace + discriminant); + w2 = 0.5*(trace - discriminant); + } else { + w1 = 0.5*(trace - discriminant); + w2 = 0.5*(trace + discriminant); + } + + cos1 = SkDoubleToScalar(Sb); sin1 = SkDoubleToScalar(w1 - Sa); + SkScalar reciplen = SkScalarInvert(SkScalarSqrt(cos1*cos1 + sin1*sin1)); + cos1 *= reciplen; + sin1 *= reciplen; + + // rotation 2 is composition of Q and U + cos2 = cos1*cosQ - sin1*sinQ; + sin2 = sin1*cosQ + cos1*sinQ; + + // rotation 1 is U^T + sin1 = -sin1; + } + + if (scale) { + scale->fX = SkDoubleToScalar(w1); + scale->fY = SkDoubleToScalar(w2); + } + if (rotation1) { + rotation1->fX = cos1; + rotation1->fY = sin1; + } + if (rotation2) { + rotation2->fX = cos2; + rotation2->fY = sin2; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkRSXform::toQuad(SkScalar width, SkScalar height, SkPoint quad[4]) const { +#if 0 + // This is the slow way, but it documents what we're doing + quad[0].set(0, 0); + quad[1].set(width, 0); + quad[2].set(width, height); + quad[3].set(0, height); + SkMatrix m; + m.setRSXform(*this).mapPoints(quad, quad, 4); +#else + const SkScalar m00 = fSCos; + const SkScalar m01 = -fSSin; + const SkScalar m02 = fTx; + const SkScalar m10 = -m01; + const SkScalar m11 = m00; + const SkScalar m12 = fTy; + + quad[0].set(m02, m12); + quad[1].set(m00 * width + m02, m10 * width + m12); + quad[2].set(m00 * width + m01 * height + m02, m10 * width + m11 * height + m12); + quad[3].set(m01 * height + m02, m11 * height + m12); +#endif +} + +void SkRSXform::toTriStrip(SkScalar width, SkScalar height, SkPoint strip[4]) const { + const SkScalar m00 = fSCos; + const SkScalar m01 = -fSSin; + const SkScalar m02 = fTx; + const SkScalar m10 = -m01; + const SkScalar m11 = m00; + const SkScalar m12 = fTy; + + strip[0].set(m02, m12); + strip[1].set(m01 * height + m02, m11 * height + m12); + strip[2].set(m00 * width + m02, m10 * width + m12); + strip[3].set(m00 * width + m01 * height + m02, m10 * width + m11 * height + m12); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkFilterQuality SkMatrixPriv::AdjustHighQualityFilterLevel(const SkMatrix& matrix, + bool matrixIsInverse) { + if (matrix.isIdentity()) { + return kNone_SkFilterQuality; + } + + auto is_minimizing = [&](SkScalar scale) { + return matrixIsInverse ? scale > 1 : scale < 1; + }; + + SkScalar scales[2]; + if (!matrix.getMinMaxScales(scales) || is_minimizing(scales[0])) { + // Bicubic doesn't handle arbitrary minimization well, as src texels can be skipped + // entirely, + return kMedium_SkFilterQuality; + } + + // At this point if scales[1] == SK_Scalar1 then the matrix doesn't do any scaling. + if (scales[1] == SK_Scalar1) { + if (matrix.rectStaysRect() && SkScalarIsInt(matrix.getTranslateX()) && + SkScalarIsInt(matrix.getTranslateY())) { + return kNone_SkFilterQuality; + } else { + // Use bilerp to handle rotation or fractional translation. + return kLow_SkFilterQuality; + } + } + + return kHigh_SkFilterQuality; +} diff --git a/gfx/skia/skia/src/core/SkMatrix44.cpp b/gfx/skia/skia/src/core/SkMatrix44.cpp new file mode 100644 index 0000000000..67e710bf0e --- /dev/null +++ b/gfx/skia/skia/src/core/SkMatrix44.cpp @@ -0,0 +1,1032 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix44.h" +#include +#include + +// Copying SkMatrix44 byte-wise is performance-critical to Blink. This class is +// contained in several Transform classes, which are copied multiple times +// during the rendering life cycle. See crbug.com/938563 for reference. +#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC) +// std::is_trivially_copyable is not supported for some older clang versions, +// which (at least as of this patch) are in use for Chromecast. +static_assert(std::is_trivially_copyable::value, + "SkMatrix44 must be trivially copyable"); +#endif + +static inline bool eq4(const SkMScalar* SK_RESTRICT a, + const SkMScalar* SK_RESTRICT b) { + return (a[0] == b[0]) & (a[1] == b[1]) & (a[2] == b[2]) & (a[3] == b[3]); +} + +bool SkMatrix44::operator==(const SkMatrix44& other) const { + if (this == &other) { + return true; + } + + if (this->isIdentity() && other.isIdentity()) { + return true; + } + + const SkMScalar* SK_RESTRICT a = &fMat[0][0]; + const SkMScalar* SK_RESTRICT b = &other.fMat[0][0]; + +#if 0 + for (int i = 0; i < 16; ++i) { + if (a[i] != b[i]) { + return false; + } + } + return true; +#else + // to reduce branch instructions, we compare 4 at a time. + // see bench/Matrix44Bench.cpp for test. + if (!eq4(&a[0], &b[0])) { + return false; + } + if (!eq4(&a[4], &b[4])) { + return false; + } + if (!eq4(&a[8], &b[8])) { + return false; + } + return eq4(&a[12], &b[12]); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +void SkMatrix44::recomputeTypeMask() { + if (0 != perspX() || 0 != perspY() || 0 != perspZ() || 1 != fMat[3][3]) { + fTypeMask = kTranslate_Mask | kScale_Mask | kAffine_Mask | kPerspective_Mask; + return; + } + + TypeMask mask = kIdentity_Mask; + if (0 != transX() || 0 != transY() || 0 != transZ()) { + mask |= kTranslate_Mask; + } + + if (1 != scaleX() || 1 != scaleY() || 1 != scaleZ()) { + mask |= kScale_Mask; + } + + if (0 != fMat[1][0] || 0 != fMat[0][1] || 0 != fMat[0][2] || + 0 != fMat[2][0] || 0 != fMat[1][2] || 0 != fMat[2][1]) { + mask |= kAffine_Mask; + } + fTypeMask = mask; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::asColMajorf(float dst[]) const { + const SkMScalar* src = &fMat[0][0]; +#ifdef SK_MSCALAR_IS_DOUBLE + for (int i = 0; i < 16; ++i) { + dst[i] = SkMScalarToFloat(src[i]); + } +#elif defined SK_MSCALAR_IS_FLOAT + memcpy(dst, src, 16 * sizeof(float)); +#endif +} + +void SkMatrix44::as3x4RowMajorf(float dst[]) const { + dst[0] = fMat[0][0]; dst[1] = fMat[1][0]; dst[2] = fMat[2][0]; dst[3] = fMat[3][0]; + dst[4] = fMat[0][1]; dst[5] = fMat[1][1]; dst[6] = fMat[2][1]; dst[7] = fMat[3][1]; + dst[8] = fMat[0][2]; dst[9] = fMat[1][2]; dst[10] = fMat[2][2]; dst[11] = fMat[3][2]; +} + +void SkMatrix44::asColMajord(double dst[]) const { + const SkMScalar* src = &fMat[0][0]; +#ifdef SK_MSCALAR_IS_DOUBLE + memcpy(dst, src, 16 * sizeof(double)); +#elif defined SK_MSCALAR_IS_FLOAT + for (int i = 0; i < 16; ++i) { + dst[i] = SkMScalarToDouble(src[i]); + } +#endif +} + +void SkMatrix44::asRowMajorf(float dst[]) const { + const SkMScalar* src = &fMat[0][0]; + for (int i = 0; i < 4; ++i) { + dst[0] = SkMScalarToFloat(src[0]); + dst[4] = SkMScalarToFloat(src[1]); + dst[8] = SkMScalarToFloat(src[2]); + dst[12] = SkMScalarToFloat(src[3]); + src += 4; + dst += 1; + } +} + +void SkMatrix44::asRowMajord(double dst[]) const { + const SkMScalar* src = &fMat[0][0]; + for (int i = 0; i < 4; ++i) { + dst[0] = SkMScalarToDouble(src[0]); + dst[4] = SkMScalarToDouble(src[1]); + dst[8] = SkMScalarToDouble(src[2]); + dst[12] = SkMScalarToDouble(src[3]); + src += 4; + dst += 1; + } +} + +void SkMatrix44::setColMajorf(const float src[]) { + SkMScalar* dst = &fMat[0][0]; +#ifdef SK_MSCALAR_IS_DOUBLE + for (int i = 0; i < 16; ++i) { + dst[i] = SkMScalarToFloat(src[i]); + } +#elif defined SK_MSCALAR_IS_FLOAT + memcpy(dst, src, 16 * sizeof(float)); +#endif + + this->recomputeTypeMask(); +} + +void SkMatrix44::setColMajord(const double src[]) { + SkMScalar* dst = &fMat[0][0]; +#ifdef SK_MSCALAR_IS_DOUBLE + memcpy(dst, src, 16 * sizeof(double)); +#elif defined SK_MSCALAR_IS_FLOAT + for (int i = 0; i < 16; ++i) { + dst[i] = SkDoubleToMScalar(src[i]); + } +#endif + + this->recomputeTypeMask(); +} + +void SkMatrix44::setRowMajorf(const float src[]) { + SkMScalar* dst = &fMat[0][0]; + for (int i = 0; i < 4; ++i) { + dst[0] = SkMScalarToFloat(src[0]); + dst[4] = SkMScalarToFloat(src[1]); + dst[8] = SkMScalarToFloat(src[2]); + dst[12] = SkMScalarToFloat(src[3]); + src += 4; + dst += 1; + } + this->recomputeTypeMask(); +} + +void SkMatrix44::setRowMajord(const double src[]) { + SkMScalar* dst = &fMat[0][0]; + for (int i = 0; i < 4; ++i) { + dst[0] = SkDoubleToMScalar(src[0]); + dst[4] = SkDoubleToMScalar(src[1]); + dst[8] = SkDoubleToMScalar(src[2]); + dst[12] = SkDoubleToMScalar(src[3]); + src += 4; + dst += 1; + } + this->recomputeTypeMask(); +} + +/////////////////////////////////////////////////////////////////////////////// + +const SkMatrix44& SkMatrix44::I() { + static constexpr SkMatrix44 gIdentity44(kIdentity_Constructor); + return gIdentity44; +} + +void SkMatrix44::setIdentity() { + fMat[0][0] = 1; + fMat[0][1] = 0; + fMat[0][2] = 0; + fMat[0][3] = 0; + fMat[1][0] = 0; + fMat[1][1] = 1; + fMat[1][2] = 0; + fMat[1][3] = 0; + fMat[2][0] = 0; + fMat[2][1] = 0; + fMat[2][2] = 1; + fMat[2][3] = 0; + fMat[3][0] = 0; + fMat[3][1] = 0; + fMat[3][2] = 0; + fMat[3][3] = 1; + this->setTypeMask(kIdentity_Mask); +} + +void SkMatrix44::set3x3(SkMScalar m_00, SkMScalar m_10, SkMScalar m_20, + SkMScalar m_01, SkMScalar m_11, SkMScalar m_21, + SkMScalar m_02, SkMScalar m_12, SkMScalar m_22) { + fMat[0][0] = m_00; fMat[0][1] = m_10; fMat[0][2] = m_20; fMat[0][3] = 0; + fMat[1][0] = m_01; fMat[1][1] = m_11; fMat[1][2] = m_21; fMat[1][3] = 0; + fMat[2][0] = m_02; fMat[2][1] = m_12; fMat[2][2] = m_22; fMat[2][3] = 0; + fMat[3][0] = 0; fMat[3][1] = 0; fMat[3][2] = 0; fMat[3][3] = 1; + this->recomputeTypeMask(); +} + +void SkMatrix44::set3x3RowMajorf(const float src[]) { + fMat[0][0] = src[0]; fMat[0][1] = src[3]; fMat[0][2] = src[6]; fMat[0][3] = 0; + fMat[1][0] = src[1]; fMat[1][1] = src[4]; fMat[1][2] = src[7]; fMat[1][3] = 0; + fMat[2][0] = src[2]; fMat[2][1] = src[5]; fMat[2][2] = src[8]; fMat[2][3] = 0; + fMat[3][0] = 0; fMat[3][1] = 0; fMat[3][2] = 0; fMat[3][3] = 1; + this->recomputeTypeMask(); +} + +void SkMatrix44::set3x4RowMajorf(const float src[]) { + fMat[0][0] = src[0]; fMat[1][0] = src[1]; fMat[2][0] = src[2]; fMat[3][0] = src[3]; + fMat[0][1] = src[4]; fMat[1][1] = src[5]; fMat[2][1] = src[6]; fMat[3][1] = src[7]; + fMat[0][2] = src[8]; fMat[1][2] = src[9]; fMat[2][2] = src[10]; fMat[3][2] = src[11]; + fMat[0][3] = 0; fMat[1][3] = 0; fMat[2][3] = 0; fMat[3][3] = 1; + this->recomputeTypeMask(); +} + +void SkMatrix44::set4x4(SkMScalar m_00, SkMScalar m_10, SkMScalar m_20, SkMScalar m_30, + SkMScalar m_01, SkMScalar m_11, SkMScalar m_21, SkMScalar m_31, + SkMScalar m_02, SkMScalar m_12, SkMScalar m_22, SkMScalar m_32, + SkMScalar m_03, SkMScalar m_13, SkMScalar m_23, SkMScalar m_33) { + fMat[0][0] = m_00; fMat[0][1] = m_10; fMat[0][2] = m_20; fMat[0][3] = m_30; + fMat[1][0] = m_01; fMat[1][1] = m_11; fMat[1][2] = m_21; fMat[1][3] = m_31; + fMat[2][0] = m_02; fMat[2][1] = m_12; fMat[2][2] = m_22; fMat[2][3] = m_32; + fMat[3][0] = m_03; fMat[3][1] = m_13; fMat[3][2] = m_23; fMat[3][3] = m_33; + this->recomputeTypeMask(); +} + + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::setTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) { + this->setIdentity(); + + if (!dx && !dy && !dz) { + return; + } + + fMat[3][0] = dx; + fMat[3][1] = dy; + fMat[3][2] = dz; + this->setTypeMask(kTranslate_Mask); +} + +void SkMatrix44::preTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) { + if (!dx && !dy && !dz) { + return; + } + + for (int i = 0; i < 4; ++i) { + fMat[3][i] = fMat[0][i] * dx + fMat[1][i] * dy + fMat[2][i] * dz + fMat[3][i]; + } + this->recomputeTypeMask(); +} + +void SkMatrix44::postTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) { + if (!dx && !dy && !dz) { + return; + } + + if (this->getType() & kPerspective_Mask) { + for (int i = 0; i < 4; ++i) { + fMat[i][0] += fMat[i][3] * dx; + fMat[i][1] += fMat[i][3] * dy; + fMat[i][2] += fMat[i][3] * dz; + } + } else { + fMat[3][0] += dx; + fMat[3][1] += dy; + fMat[3][2] += dz; + this->recomputeTypeMask(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::setScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) { + this->setIdentity(); + + if (1 == sx && 1 == sy && 1 == sz) { + return; + } + + fMat[0][0] = sx; + fMat[1][1] = sy; + fMat[2][2] = sz; + this->setTypeMask(kScale_Mask); +} + +void SkMatrix44::preScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) { + if (1 == sx && 1 == sy && 1 == sz) { + return; + } + + // The implementation matrix * pureScale can be shortcut + // by knowing that pureScale components effectively scale + // the columns of the original matrix. + for (int i = 0; i < 4; i++) { + fMat[0][i] *= sx; + fMat[1][i] *= sy; + fMat[2][i] *= sz; + } + this->recomputeTypeMask(); +} + +void SkMatrix44::postScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) { + if (1 == sx && 1 == sy && 1 == sz) { + return; + } + + for (int i = 0; i < 4; i++) { + fMat[i][0] *= sx; + fMat[i][1] *= sy; + fMat[i][2] *= sz; + } + this->recomputeTypeMask(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::setRotateAbout(SkMScalar x, SkMScalar y, SkMScalar z, + SkMScalar radians) { + double len2 = (double)x * x + (double)y * y + (double)z * z; + if (1 != len2) { + if (0 == len2) { + this->setIdentity(); + return; + } + double scale = 1 / sqrt(len2); + x = SkDoubleToMScalar(x * scale); + y = SkDoubleToMScalar(y * scale); + z = SkDoubleToMScalar(z * scale); + } + this->setRotateAboutUnit(x, y, z, radians); +} + +void SkMatrix44::setRotateAboutUnit(SkMScalar x, SkMScalar y, SkMScalar z, + SkMScalar radians) { + double c = cos(radians); + double s = sin(radians); + double C = 1 - c; + double xs = x * s; + double ys = y * s; + double zs = z * s; + double xC = x * C; + double yC = y * C; + double zC = z * C; + double xyC = x * yC; + double yzC = y * zC; + double zxC = z * xC; + + // if you're looking at wikipedia, remember that we're column major. + this->set3x3(SkDoubleToMScalar(x * xC + c), // scale x + SkDoubleToMScalar(xyC + zs), // skew x + SkDoubleToMScalar(zxC - ys), // trans x + + SkDoubleToMScalar(xyC - zs), // skew y + SkDoubleToMScalar(y * yC + c), // scale y + SkDoubleToMScalar(yzC + xs), // trans y + + SkDoubleToMScalar(zxC + ys), // persp x + SkDoubleToMScalar(yzC - xs), // persp y + SkDoubleToMScalar(z * zC + c)); // persp 2 +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool bits_isonly(int value, int mask) { + return 0 == (value & ~mask); +} + +void SkMatrix44::setConcat(const SkMatrix44& a, const SkMatrix44& b) { + const SkMatrix44::TypeMask a_mask = a.getType(); + const SkMatrix44::TypeMask b_mask = b.getType(); + + if (kIdentity_Mask == a_mask) { + *this = b; + return; + } + if (kIdentity_Mask == b_mask) { + *this = a; + return; + } + + bool useStorage = (this == &a || this == &b); + SkMScalar storage[16]; + SkMScalar* result = useStorage ? storage : &fMat[0][0]; + + // Both matrices are at most scale+translate + if (bits_isonly(a_mask | b_mask, kScale_Mask | kTranslate_Mask)) { + result[0] = a.fMat[0][0] * b.fMat[0][0]; + result[1] = result[2] = result[3] = result[4] = 0; + result[5] = a.fMat[1][1] * b.fMat[1][1]; + result[6] = result[7] = result[8] = result[9] = 0; + result[10] = a.fMat[2][2] * b.fMat[2][2]; + result[11] = 0; + result[12] = a.fMat[0][0] * b.fMat[3][0] + a.fMat[3][0]; + result[13] = a.fMat[1][1] * b.fMat[3][1] + a.fMat[3][1]; + result[14] = a.fMat[2][2] * b.fMat[3][2] + a.fMat[3][2]; + result[15] = 1; + } else { + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + double value = 0; + for (int k = 0; k < 4; k++) { + value += SkMScalarToDouble(a.fMat[k][i]) * b.fMat[j][k]; + } + *result++ = SkDoubleToMScalar(value); + } + } + } + + if (useStorage) { + memcpy(fMat, storage, sizeof(storage)); + } + this->recomputeTypeMask(); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** We always perform the calculation in doubles, to avoid prematurely losing + precision along the way. This relies on the compiler automatically + promoting our SkMScalar values to double (if needed). + */ +double SkMatrix44::determinant() const { + if (this->isIdentity()) { + return 1; + } + if (this->isScaleTranslate()) { + return fMat[0][0] * fMat[1][1] * fMat[2][2] * fMat[3][3]; + } + + double a00 = fMat[0][0]; + double a01 = fMat[0][1]; + double a02 = fMat[0][2]; + double a03 = fMat[0][3]; + double a10 = fMat[1][0]; + double a11 = fMat[1][1]; + double a12 = fMat[1][2]; + double a13 = fMat[1][3]; + double a20 = fMat[2][0]; + double a21 = fMat[2][1]; + double a22 = fMat[2][2]; + double a23 = fMat[2][3]; + double a30 = fMat[3][0]; + double a31 = fMat[3][1]; + double a32 = fMat[3][2]; + double a33 = fMat[3][3]; + + double b00 = a00 * a11 - a01 * a10; + double b01 = a00 * a12 - a02 * a10; + double b02 = a00 * a13 - a03 * a10; + double b03 = a01 * a12 - a02 * a11; + double b04 = a01 * a13 - a03 * a11; + double b05 = a02 * a13 - a03 * a12; + double b06 = a20 * a31 - a21 * a30; + double b07 = a20 * a32 - a22 * a30; + double b08 = a20 * a33 - a23 * a30; + double b09 = a21 * a32 - a22 * a31; + double b10 = a21 * a33 - a23 * a31; + double b11 = a22 * a33 - a23 * a32; + + // Calculate the determinant + return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool is_matrix_finite(const SkMatrix44& matrix) { + SkMScalar accumulator = 0; + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + accumulator *= matrix.get(row, col); + } + } + return accumulator == 0; +} + +bool SkMatrix44::invert(SkMatrix44* storage) const { + if (this->isIdentity()) { + if (storage) { + storage->setIdentity(); + } + return true; + } + + if (this->isTranslate()) { + if (storage) { + storage->setTranslate(-fMat[3][0], -fMat[3][1], -fMat[3][2]); + } + return true; + } + + SkMatrix44 tmp; + // Use storage if it's available and distinct from this matrix. + SkMatrix44* inverse = (storage && storage != this) ? storage : &tmp; + if (this->isScaleTranslate()) { + if (0 == fMat[0][0] * fMat[1][1] * fMat[2][2]) { + return false; + } + + double invXScale = 1 / fMat[0][0]; + double invYScale = 1 / fMat[1][1]; + double invZScale = 1 / fMat[2][2]; + + inverse->fMat[0][0] = SkDoubleToMScalar(invXScale); + inverse->fMat[0][1] = 0; + inverse->fMat[0][2] = 0; + inverse->fMat[0][3] = 0; + + inverse->fMat[1][0] = 0; + inverse->fMat[1][1] = SkDoubleToMScalar(invYScale); + inverse->fMat[1][2] = 0; + inverse->fMat[1][3] = 0; + + inverse->fMat[2][0] = 0; + inverse->fMat[2][1] = 0; + inverse->fMat[2][2] = SkDoubleToMScalar(invZScale); + inverse->fMat[2][3] = 0; + + inverse->fMat[3][0] = SkDoubleToMScalar(-fMat[3][0] * invXScale); + inverse->fMat[3][1] = SkDoubleToMScalar(-fMat[3][1] * invYScale); + inverse->fMat[3][2] = SkDoubleToMScalar(-fMat[3][2] * invZScale); + inverse->fMat[3][3] = 1; + + inverse->setTypeMask(this->getType()); + + if (!is_matrix_finite(*inverse)) { + return false; + } + if (storage && inverse != storage) { + *storage = *inverse; + } + return true; + } + + double a00 = fMat[0][0]; + double a01 = fMat[0][1]; + double a02 = fMat[0][2]; + double a03 = fMat[0][3]; + double a10 = fMat[1][0]; + double a11 = fMat[1][1]; + double a12 = fMat[1][2]; + double a13 = fMat[1][3]; + double a20 = fMat[2][0]; + double a21 = fMat[2][1]; + double a22 = fMat[2][2]; + double a23 = fMat[2][3]; + double a30 = fMat[3][0]; + double a31 = fMat[3][1]; + double a32 = fMat[3][2]; + double a33 = fMat[3][3]; + + if (!(this->getType() & kPerspective_Mask)) { + // If we know the matrix has no perspective, then the perspective + // component is (0, 0, 0, 1). We can use this information to save a lot + // of arithmetic that would otherwise be spent to compute the inverse + // of a general matrix. + + SkASSERT(a03 == 0); + SkASSERT(a13 == 0); + SkASSERT(a23 == 0); + SkASSERT(a33 == 1); + + double b00 = a00 * a11 - a01 * a10; + double b01 = a00 * a12 - a02 * a10; + double b03 = a01 * a12 - a02 * a11; + double b06 = a20 * a31 - a21 * a30; + double b07 = a20 * a32 - a22 * a30; + double b08 = a20; + double b09 = a21 * a32 - a22 * a31; + double b10 = a21; + double b11 = a22; + + // Calculate the determinant + double det = b00 * b11 - b01 * b10 + b03 * b08; + + double invdet = sk_ieee_double_divide(1.0, det); + // If det is zero, we want to return false. However, we also want to return false + // if 1/det overflows to infinity (i.e. det is denormalized). Both of these are + // handled by checking that 1/det is finite. + if (!sk_float_isfinite(sk_double_to_float(invdet))) { + return false; + } + + b00 *= invdet; + b01 *= invdet; + b03 *= invdet; + b06 *= invdet; + b07 *= invdet; + b08 *= invdet; + b09 *= invdet; + b10 *= invdet; + b11 *= invdet; + + inverse->fMat[0][0] = SkDoubleToMScalar(a11 * b11 - a12 * b10); + inverse->fMat[0][1] = SkDoubleToMScalar(a02 * b10 - a01 * b11); + inverse->fMat[0][2] = SkDoubleToMScalar(b03); + inverse->fMat[0][3] = 0; + inverse->fMat[1][0] = SkDoubleToMScalar(a12 * b08 - a10 * b11); + inverse->fMat[1][1] = SkDoubleToMScalar(a00 * b11 - a02 * b08); + inverse->fMat[1][2] = SkDoubleToMScalar(-b01); + inverse->fMat[1][3] = 0; + inverse->fMat[2][0] = SkDoubleToMScalar(a10 * b10 - a11 * b08); + inverse->fMat[2][1] = SkDoubleToMScalar(a01 * b08 - a00 * b10); + inverse->fMat[2][2] = SkDoubleToMScalar(b00); + inverse->fMat[2][3] = 0; + inverse->fMat[3][0] = SkDoubleToMScalar(a11 * b07 - a10 * b09 - a12 * b06); + inverse->fMat[3][1] = SkDoubleToMScalar(a00 * b09 - a01 * b07 + a02 * b06); + inverse->fMat[3][2] = SkDoubleToMScalar(a31 * b01 - a30 * b03 - a32 * b00); + inverse->fMat[3][3] = 1; + + inverse->setTypeMask(this->getType()); + if (!is_matrix_finite(*inverse)) { + return false; + } + if (storage && inverse != storage) { + *storage = *inverse; + } + return true; + } + + double b00 = a00 * a11 - a01 * a10; + double b01 = a00 * a12 - a02 * a10; + double b02 = a00 * a13 - a03 * a10; + double b03 = a01 * a12 - a02 * a11; + double b04 = a01 * a13 - a03 * a11; + double b05 = a02 * a13 - a03 * a12; + double b06 = a20 * a31 - a21 * a30; + double b07 = a20 * a32 - a22 * a30; + double b08 = a20 * a33 - a23 * a30; + double b09 = a21 * a32 - a22 * a31; + double b10 = a21 * a33 - a23 * a31; + double b11 = a22 * a33 - a23 * a32; + + // Calculate the determinant + double det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + double invdet = sk_ieee_double_divide(1.0, det); + // If det is zero, we want to return false. However, we also want to return false + // if 1/det overflows to infinity (i.e. det is denormalized). Both of these are + // handled by checking that 1/det is finite. + if (!sk_float_isfinite(sk_double_to_float(invdet))) { + return false; + } + + b00 *= invdet; + b01 *= invdet; + b02 *= invdet; + b03 *= invdet; + b04 *= invdet; + b05 *= invdet; + b06 *= invdet; + b07 *= invdet; + b08 *= invdet; + b09 *= invdet; + b10 *= invdet; + b11 *= invdet; + + inverse->fMat[0][0] = SkDoubleToMScalar(a11 * b11 - a12 * b10 + a13 * b09); + inverse->fMat[0][1] = SkDoubleToMScalar(a02 * b10 - a01 * b11 - a03 * b09); + inverse->fMat[0][2] = SkDoubleToMScalar(a31 * b05 - a32 * b04 + a33 * b03); + inverse->fMat[0][3] = SkDoubleToMScalar(a22 * b04 - a21 * b05 - a23 * b03); + inverse->fMat[1][0] = SkDoubleToMScalar(a12 * b08 - a10 * b11 - a13 * b07); + inverse->fMat[1][1] = SkDoubleToMScalar(a00 * b11 - a02 * b08 + a03 * b07); + inverse->fMat[1][2] = SkDoubleToMScalar(a32 * b02 - a30 * b05 - a33 * b01); + inverse->fMat[1][3] = SkDoubleToMScalar(a20 * b05 - a22 * b02 + a23 * b01); + inverse->fMat[2][0] = SkDoubleToMScalar(a10 * b10 - a11 * b08 + a13 * b06); + inverse->fMat[2][1] = SkDoubleToMScalar(a01 * b08 - a00 * b10 - a03 * b06); + inverse->fMat[2][2] = SkDoubleToMScalar(a30 * b04 - a31 * b02 + a33 * b00); + inverse->fMat[2][3] = SkDoubleToMScalar(a21 * b02 - a20 * b04 - a23 * b00); + inverse->fMat[3][0] = SkDoubleToMScalar(a11 * b07 - a10 * b09 - a12 * b06); + inverse->fMat[3][1] = SkDoubleToMScalar(a00 * b09 - a01 * b07 + a02 * b06); + inverse->fMat[3][2] = SkDoubleToMScalar(a31 * b01 - a30 * b03 - a32 * b00); + inverse->fMat[3][3] = SkDoubleToMScalar(a20 * b03 - a21 * b01 + a22 * b00); + inverse->setTypeMask(this->getType()); + if (!is_matrix_finite(*inverse)) { + return false; + } + if (storage && inverse != storage) { + *storage = *inverse; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::transpose() { + if (!this->isIdentity()) { + using std::swap; + swap(fMat[0][1], fMat[1][0]); + swap(fMat[0][2], fMat[2][0]); + swap(fMat[0][3], fMat[3][0]); + swap(fMat[1][2], fMat[2][1]); + swap(fMat[1][3], fMat[3][1]); + swap(fMat[2][3], fMat[3][2]); + this->recomputeTypeMask(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::mapScalars(const SkScalar src[4], SkScalar dst[4]) const { + SkScalar storage[4]; + SkScalar* result = (src == dst) ? storage : dst; + + for (int i = 0; i < 4; i++) { + SkMScalar value = 0; + for (int j = 0; j < 4; j++) { + value += fMat[j][i] * src[j]; + } + result[i] = SkMScalarToScalar(value); + } + + if (storage == result) { + memcpy(dst, storage, sizeof(storage)); + } +} + +#ifdef SK_MSCALAR_IS_DOUBLE + +void SkMatrix44::mapMScalars(const SkMScalar src[4], SkMScalar dst[4]) const { + SkMScalar storage[4]; + SkMScalar* result = (src == dst) ? storage : dst; + + for (int i = 0; i < 4; i++) { + SkMScalar value = 0; + for (int j = 0; j < 4; j++) { + value += fMat[j][i] * src[j]; + } + result[i] = value; + } + + if (storage == result) { + memcpy(dst, storage, sizeof(storage)); + } +} + +#endif + +typedef void (*Map2Procf)(const SkMScalar mat[][4], const float src2[], int count, float dst4[]); +typedef void (*Map2Procd)(const SkMScalar mat[][4], const double src2[], int count, double dst4[]); + +static void map2_if(const SkMScalar mat[][4], const float* SK_RESTRICT src2, + int count, float* SK_RESTRICT dst4) { + for (int i = 0; i < count; ++i) { + dst4[0] = src2[0]; + dst4[1] = src2[1]; + dst4[2] = 0; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_id(const SkMScalar mat[][4], const double* SK_RESTRICT src2, + int count, double* SK_RESTRICT dst4) { + for (int i = 0; i < count; ++i) { + dst4[0] = src2[0]; + dst4[1] = src2[1]; + dst4[2] = 0; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_tf(const SkMScalar mat[][4], const float* SK_RESTRICT src2, + int count, float* SK_RESTRICT dst4) { + const float mat30 = SkMScalarToFloat(mat[3][0]); + const float mat31 = SkMScalarToFloat(mat[3][1]); + const float mat32 = SkMScalarToFloat(mat[3][2]); + for (int n = 0; n < count; ++n) { + dst4[0] = src2[0] + mat30; + dst4[1] = src2[1] + mat31; + dst4[2] = mat32; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_td(const SkMScalar mat[][4], const double* SK_RESTRICT src2, + int count, double* SK_RESTRICT dst4) { + for (int n = 0; n < count; ++n) { + dst4[0] = src2[0] + mat[3][0]; + dst4[1] = src2[1] + mat[3][1]; + dst4[2] = mat[3][2]; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_sf(const SkMScalar mat[][4], const float* SK_RESTRICT src2, + int count, float* SK_RESTRICT dst4) { + const float mat32 = SkMScalarToFloat(mat[3][2]); + for (int n = 0; n < count; ++n) { + dst4[0] = SkMScalarToFloat(mat[0][0] * src2[0] + mat[3][0]); + dst4[1] = SkMScalarToFloat(mat[1][1] * src2[1] + mat[3][1]); + dst4[2] = mat32; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_sd(const SkMScalar mat[][4], const double* SK_RESTRICT src2, + int count, double* SK_RESTRICT dst4) { + for (int n = 0; n < count; ++n) { + dst4[0] = mat[0][0] * src2[0] + mat[3][0]; + dst4[1] = mat[1][1] * src2[1] + mat[3][1]; + dst4[2] = mat[3][2]; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_af(const SkMScalar mat[][4], const float* SK_RESTRICT src2, + int count, float* SK_RESTRICT dst4) { + SkMScalar r; + for (int n = 0; n < count; ++n) { + SkMScalar sx = SkFloatToMScalar(src2[0]); + SkMScalar sy = SkFloatToMScalar(src2[1]); + r = mat[0][0] * sx + mat[1][0] * sy + mat[3][0]; + dst4[0] = SkMScalarToFloat(r); + r = mat[0][1] * sx + mat[1][1] * sy + mat[3][1]; + dst4[1] = SkMScalarToFloat(r); + r = mat[0][2] * sx + mat[1][2] * sy + mat[3][2]; + dst4[2] = SkMScalarToFloat(r); + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_ad(const SkMScalar mat[][4], const double* SK_RESTRICT src2, + int count, double* SK_RESTRICT dst4) { + for (int n = 0; n < count; ++n) { + double sx = src2[0]; + double sy = src2[1]; + dst4[0] = mat[0][0] * sx + mat[1][0] * sy + mat[3][0]; + dst4[1] = mat[0][1] * sx + mat[1][1] * sy + mat[3][1]; + dst4[2] = mat[0][2] * sx + mat[1][2] * sy + mat[3][2]; + dst4[3] = 1; + src2 += 2; + dst4 += 4; + } +} + +static void map2_pf(const SkMScalar mat[][4], const float* SK_RESTRICT src2, + int count, float* SK_RESTRICT dst4) { + SkMScalar r; + for (int n = 0; n < count; ++n) { + SkMScalar sx = SkFloatToMScalar(src2[0]); + SkMScalar sy = SkFloatToMScalar(src2[1]); + for (int i = 0; i < 4; i++) { + r = mat[0][i] * sx + mat[1][i] * sy + mat[3][i]; + dst4[i] = SkMScalarToFloat(r); + } + src2 += 2; + dst4 += 4; + } +} + +static void map2_pd(const SkMScalar mat[][4], const double* SK_RESTRICT src2, + int count, double* SK_RESTRICT dst4) { + for (int n = 0; n < count; ++n) { + double sx = src2[0]; + double sy = src2[1]; + for (int i = 0; i < 4; i++) { + dst4[i] = mat[0][i] * sx + mat[1][i] * sy + mat[3][i]; + } + src2 += 2; + dst4 += 4; + } +} + +void SkMatrix44::map2(const float src2[], int count, float dst4[]) const { + static const Map2Procf gProc[] = { + map2_if, map2_tf, map2_sf, map2_sf, map2_af, map2_af, map2_af, map2_af + }; + + TypeMask mask = this->getType(); + Map2Procf proc = (mask & kPerspective_Mask) ? map2_pf : gProc[mask]; + proc(fMat, src2, count, dst4); +} + +void SkMatrix44::map2(const double src2[], int count, double dst4[]) const { + static const Map2Procd gProc[] = { + map2_id, map2_td, map2_sd, map2_sd, map2_ad, map2_ad, map2_ad, map2_ad + }; + + TypeMask mask = this->getType(); + Map2Procd proc = (mask & kPerspective_Mask) ? map2_pd : gProc[mask]; + proc(fMat, src2, count, dst4); +} + +bool SkMatrix44::preserves2dAxisAlignment (SkMScalar epsilon) const { + + // Can't check (mask & kPerspective_Mask) because Z isn't relevant here. + if (0 != perspX() || 0 != perspY()) return false; + + // A matrix with two non-zeroish values in any of the upper right + // rows or columns will skew. If only one value in each row or + // column is non-zeroish, we get a scale plus perhaps a 90-degree + // rotation. + int col0 = 0; + int col1 = 0; + int row0 = 0; + int row1 = 0; + + // Must test against epsilon, not 0, because we can get values + // around 6e-17 in the matrix that "should" be 0. + + if (SkMScalarAbs(fMat[0][0]) > epsilon) { + col0++; + row0++; + } + if (SkMScalarAbs(fMat[0][1]) > epsilon) { + col1++; + row0++; + } + if (SkMScalarAbs(fMat[1][0]) > epsilon) { + col0++; + row1++; + } + if (SkMScalarAbs(fMat[1][1]) > epsilon) { + col1++; + row1++; + } + if (col0 > 1 || col1 > 1 || row0 > 1 || row1 > 1) { + return false; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix44::dump() const { + static const char* format = "|%g %g %g %g|\n" + "|%g %g %g %g|\n" + "|%g %g %g %g|\n" + "|%g %g %g %g|\n"; + SkDebugf(format, + fMat[0][0], fMat[1][0], fMat[2][0], fMat[3][0], + fMat[0][1], fMat[1][1], fMat[2][1], fMat[3][1], + fMat[0][2], fMat[1][2], fMat[2][2], fMat[3][2], + fMat[0][3], fMat[1][3], fMat[2][3], fMat[3][3]); +} + +/////////////////////////////////////////////////////////////////////////////// + +static void initFromMatrix(SkMScalar dst[4][4], const SkMatrix& src) { + dst[0][0] = SkScalarToMScalar(src[SkMatrix::kMScaleX]); + dst[1][0] = SkScalarToMScalar(src[SkMatrix::kMSkewX]); + dst[2][0] = 0; + dst[3][0] = SkScalarToMScalar(src[SkMatrix::kMTransX]); + dst[0][1] = SkScalarToMScalar(src[SkMatrix::kMSkewY]); + dst[1][1] = SkScalarToMScalar(src[SkMatrix::kMScaleY]); + dst[2][1] = 0; + dst[3][1] = SkScalarToMScalar(src[SkMatrix::kMTransY]); + dst[0][2] = 0; + dst[1][2] = 0; + dst[2][2] = 1; + dst[3][2] = 0; + dst[0][3] = SkScalarToMScalar(src[SkMatrix::kMPersp0]); + dst[1][3] = SkScalarToMScalar(src[SkMatrix::kMPersp1]); + dst[2][3] = 0; + dst[3][3] = SkScalarToMScalar(src[SkMatrix::kMPersp2]); +} + +SkMatrix44::SkMatrix44(const SkMatrix& src) { + this->operator=(src); +} + +SkMatrix44& SkMatrix44::operator=(const SkMatrix& src) { + initFromMatrix(fMat, src); + + if (src.isIdentity()) { + this->setTypeMask(kIdentity_Mask); + } else { + this->recomputeTypeMask(); + } + return *this; +} + +SkMatrix44::operator SkMatrix() const { + SkMatrix dst; + + dst[SkMatrix::kMScaleX] = SkMScalarToScalar(fMat[0][0]); + dst[SkMatrix::kMSkewX] = SkMScalarToScalar(fMat[1][0]); + dst[SkMatrix::kMTransX] = SkMScalarToScalar(fMat[3][0]); + + dst[SkMatrix::kMSkewY] = SkMScalarToScalar(fMat[0][1]); + dst[SkMatrix::kMScaleY] = SkMScalarToScalar(fMat[1][1]); + dst[SkMatrix::kMTransY] = SkMScalarToScalar(fMat[3][1]); + + dst[SkMatrix::kMPersp0] = SkMScalarToScalar(fMat[0][3]); + dst[SkMatrix::kMPersp1] = SkMScalarToScalar(fMat[1][3]); + dst[SkMatrix::kMPersp2] = SkMScalarToScalar(fMat[3][3]); + + return dst; +} diff --git a/gfx/skia/skia/src/core/SkMatrixImageFilter.cpp b/gfx/skia/skia/src/core/SkMatrixImageFilter.cpp new file mode 100644 index 0000000000..9fb877851f --- /dev/null +++ b/gfx/skia/skia/src/core/SkMatrixImageFilter.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMatrixImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkRect.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" + +SkMatrixImageFilter::SkMatrixImageFilter(const SkMatrix& transform, + SkFilterQuality filterQuality, + sk_sp input) + : INHERITED(&input, 1, nullptr) + , fTransform(transform) + , fFilterQuality(filterQuality) { +} + +sk_sp SkMatrixImageFilter::Make(const SkMatrix& transform, + SkFilterQuality filterQuality, + sk_sp input) { + return sk_sp(new SkMatrixImageFilter(transform, + filterQuality, + std::move(input))); +} + +sk_sp SkMatrixImageFilter::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkMatrix matrix; + buffer.readMatrix(&matrix); + + return Make(matrix, buffer.read32LE(kLast_SkFilterQuality), common.getInput(0)); +} + +void SkMatrixImageFilter::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeMatrix(fTransform); + buffer.writeInt(fFilterQuality); +} + +sk_sp SkMatrixImageFilter::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + SkMatrix matrix; + if (!ctx.ctm().invert(&matrix)) { + return nullptr; + } + matrix.postConcat(fTransform); + matrix.postConcat(ctx.ctm()); + + const SkIRect srcBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + const SkRect srcRect = SkRect::Make(srcBounds); + + SkRect dstRect; + matrix.mapRect(&dstRect, srcRect); + SkIRect dstBounds; + dstRect.roundOut(&dstBounds); + + sk_sp surf(ctx.makeSurface(dstBounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); + + canvas->translate(-SkIntToScalar(dstBounds.x()), -SkIntToScalar(dstBounds.y())); + canvas->concat(matrix); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setBlendMode(SkBlendMode::kSrc); + paint.setFilterQuality(fFilterQuality); + + input->draw(canvas, srcRect.x(), srcRect.y(), &paint); + + offset->fX = dstBounds.fLeft; + offset->fY = dstBounds.fTop; + return surf->makeImageSnapshot(); +} + +SkRect SkMatrixImageFilter::computeFastBounds(const SkRect& src) const { + SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; + SkRect dst; + fTransform.mapRect(&dst, bounds); + return dst; +} + +SkIRect SkMatrixImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection dir, const SkIRect* inputRect) const { + SkMatrix matrix; + if (!ctm.invert(&matrix)) { + return src; + } + if (kForward_MapDirection == dir) { + matrix.postConcat(fTransform); + } else { + SkMatrix transformInverse; + if (!fTransform.invert(&transformInverse)) { + return src; + } + matrix.postConcat(transformInverse); + } + matrix.postConcat(ctm); + SkRect floatBounds; + matrix.mapRect(&floatBounds, SkRect::Make(src)); + SkIRect result = floatBounds.roundOut(); + + if (kReverse_MapDirection == dir && kNone_SkFilterQuality != fFilterQuality) { + // When filtering we might need some pixels in the source that might be otherwise + // clipped off. + result.outset(1, 1); + } + + return result; +} diff --git a/gfx/skia/skia/src/core/SkMatrixImageFilter.h b/gfx/skia/skia/src/core/SkMatrixImageFilter.h new file mode 100644 index 0000000000..12ed13ed41 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMatrixImageFilter.h @@ -0,0 +1,53 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMatrixImageFilter_DEFINED +#define SkMatrixImageFilter_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkMatrix.h" +#include "src/core/SkImageFilter_Base.h" + +/*! \class SkMatrixImageFilter + Matrix transformation image filter. This filter draws its source + input transformed by the given matrix. + */ + +class SkMatrixImageFilter : public SkImageFilter_Base { +public: + /** Construct a 2D transformation image filter. + * @param transform The matrix to apply when drawing the src bitmap + * @param filterQuality The quality of filtering to apply when scaling. + * @param input The input image filter. If nullptr, the src bitmap + * passed to filterImage() is used instead. + */ + + static sk_sp Make(const SkMatrix& transform, + SkFilterQuality filterQuality, + sk_sp input); + + SkRect computeFastBounds(const SkRect&) const override; + +protected: + SkMatrixImageFilter(const SkMatrix& transform, + SkFilterQuality, + sk_sp input); + void flatten(SkWriteBuffer&) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +private: + SK_FLATTENABLE_HOOKS(SkMatrixImageFilter) + + SkMatrix fTransform; + SkFilterQuality fFilterQuality; + typedef SkImageFilter_Base INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkMatrixPriv.h b/gfx/skia/skia/src/core/SkMatrixPriv.h new file mode 100644 index 0000000000..3ac856b0e1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMatrixPriv.h @@ -0,0 +1,155 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMatrixPriv_DEFINE +#define SkMatrixPriv_DEFINE + +#include "include/core/SkFilterQuality.h" +#include "include/core/SkMatrix.h" +#include "include/private/SkNx.h" +#include "src/core/SkPointPriv.h" + +class SkMatrixPriv { +public: + enum { + // writeTo/readFromMemory will never return a value larger than this + kMaxFlattenSize = 9 * sizeof(SkScalar) + sizeof(uint32_t), + }; + + static size_t WriteToMemory(const SkMatrix& matrix, void* buffer) { + return matrix.writeToMemory(buffer); + } + + static size_t ReadFromMemory(SkMatrix* matrix, const void* buffer, size_t length) { + return matrix->readFromMemory(buffer, length); + } + + typedef SkMatrix::MapXYProc MapXYProc; + typedef SkMatrix::MapPtsProc MapPtsProc; + + + static MapPtsProc GetMapPtsProc(const SkMatrix& matrix) { + return SkMatrix::GetMapPtsProc(matrix.getType()); + } + + static MapXYProc GetMapXYProc(const SkMatrix& matrix) { + return SkMatrix::GetMapXYProc(matrix.getType()); + } + + /** + * Attempt to map the rect through the inverse of the matrix. If it is not invertible, + * then this returns false and dst is unchanged. + */ + static bool SK_WARN_UNUSED_RESULT InverseMapRect(const SkMatrix& mx, + SkRect* dst, const SkRect& src) { + if (mx.getType() <= SkMatrix::kTranslate_Mask) { + SkScalar tx = mx.getTranslateX(); + SkScalar ty = mx.getTranslateY(); + Sk4f trans(tx, ty, tx, ty); + (Sk4f::Load(&src.fLeft) - trans).store(&dst->fLeft); + return true; + } + // Insert other special-cases here (e.g. scale+translate) + + // general case + SkMatrix inverse; + if (mx.invert(&inverse)) { + inverse.mapRect(dst, src); + return true; + } + return false; + } + + /** Maps count pts, skipping stride bytes to advance from one SkPoint to the next. + Points are mapped by multiplying each SkPoint by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, pt = | y | + | G H I | | 1 | + + each resulting pts SkPoint is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param mx matrix used to map the points + @param pts storage for mapped points + @param stride size of record starting with SkPoint, in bytes + @param count number of points to transform + */ + static void MapPointsWithStride(const SkMatrix& mx, SkPoint pts[], size_t stride, int count) { + SkASSERT(stride >= sizeof(SkPoint)); + SkASSERT(0 == stride % sizeof(SkScalar)); + + SkMatrix::TypeMask tm = mx.getType(); + + if (SkMatrix::kIdentity_Mask == tm) { + return; + } + if (SkMatrix::kTranslate_Mask == tm) { + const SkScalar tx = mx.getTranslateX(); + const SkScalar ty = mx.getTranslateY(); + Sk2s trans(tx, ty); + for (int i = 0; i < count; ++i) { + (Sk2s::Load(&pts->fX) + trans).store(&pts->fX); + pts = (SkPoint*)((intptr_t)pts + stride); + } + return; + } + // Insert other special-cases here (e.g. scale+translate) + + // general case + SkMatrix::MapXYProc proc = mx.getMapXYProc(); + for (int i = 0; i < count; ++i) { + proc(mx, pts->fX, pts->fY, pts); + pts = (SkPoint*)((intptr_t)pts + stride); + } + } + + /** Maps src SkPoint array of length count to dst SkPoint array, skipping stride bytes + to advance from one SkPoint to the next. + Points are mapped by multiplying each SkPoint by SkMatrix. Given: + + | A B C | | x | + Matrix = | D E F |, src = | y | + | G H I | | 1 | + + each resulting dst SkPoint is computed as: + + |A B C| |x| Ax+By+C Dx+Ey+F + Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- + |G H I| |1| Gx+Hy+I Gx+Hy+I + + @param mx matrix used to map the points + @param dst storage for mapped points + @param src points to transform + @param stride size of record starting with SkPoint, in bytes + @param count number of points to transform + */ + static void MapPointsWithStride(const SkMatrix& mx, SkPoint dst[], size_t dstStride, + const SkPoint src[], size_t srcStride, int count) { + SkASSERT(srcStride >= sizeof(SkPoint)); + SkASSERT(dstStride >= sizeof(SkPoint)); + SkASSERT(0 == srcStride % sizeof(SkScalar)); + SkASSERT(0 == dstStride % sizeof(SkScalar)); + for (int i = 0; i < count; ++i) { + mx.mapPoints(dst, src, 1); + src = (SkPoint*)((intptr_t)src + srcStride); + dst = (SkPoint*)((intptr_t)dst + dstStride); + } + } + + static void MapHomogeneousPointsWithStride(const SkMatrix& mx, SkPoint3 dst[], size_t dstStride, + const SkPoint3 src[], size_t srcStride, int count); + + // Returns the recommended filterquality, assuming the caller originally wanted kHigh (bicubic) + static SkFilterQuality AdjustHighQualityFilterLevel(const SkMatrix&, + bool matrixIsInverse = false); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkMatrixUtils.h b/gfx/skia/skia/src/core/SkMatrixUtils.h new file mode 100644 index 0000000000..2a17ec2fbb --- /dev/null +++ b/gfx/skia/skia/src/core/SkMatrixUtils.h @@ -0,0 +1,38 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMatrixUtils_DEFINED +#define SkMatrixUtils_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkSize.h" + +class SkMatrix; +class SkPaint; + +/** + * Given a matrix, size and paint, return true if the computed dst-rect would + * align such that there is a 1-to-1 coorspondence between src and dst pixels. + * This can be called by drawing code to see if drawBitmap can be turned into + * drawSprite (which is faster). + * + * The src-rect is defined to be { 0, 0, size.width(), size.height() } + */ +bool SkTreatAsSprite(const SkMatrix&, const SkISize& size, const SkPaint& paint); + +/** Decomposes the upper-left 2x2 of the matrix into a rotation (represented by + the cosine and sine of the rotation angle), followed by a non-uniform scale, + followed by another rotation. If there is a reflection, one of the scale + factors will be negative. + Returns true if successful. Returns false if the matrix is degenerate. + */ +bool SkDecomposeUpper2x2(const SkMatrix& matrix, + SkPoint* rotation1, + SkPoint* scale, + SkPoint* rotation2); + +#endif diff --git a/gfx/skia/skia/src/core/SkMessageBus.h b/gfx/skia/skia/src/core/SkMessageBus.h new file mode 100644 index 0000000000..2a40b28777 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMessageBus.h @@ -0,0 +1,125 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMessageBus_DEFINED +#define SkMessageBus_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMutex.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" + +/** + * The following method must have a specialization for type 'Message': + * + * bool SkShouldPostMessageToBus(const Message&, uint32_t msgBusUniqueID) + * + * We may want to consider providing a default template implementation, to avoid this requirement by + * sending to all inboxes when the specialization for type 'Message' is not present. + */ +template +class SkMessageBus : SkNoncopyable { +public: + // Post a message to be received by Inboxes for this Message type. Checks + // SkShouldPostMessageToBus() for each inbox. Threadsafe. + static void Post(const Message& m); + + class Inbox { + public: + Inbox(uint32_t uniqueID = SK_InvalidUniqueID); + ~Inbox(); + + uint32_t uniqueID() const { return fUniqueID; } + + // Overwrite out with all the messages we've received since the last call. Threadsafe. + void poll(SkTArray* out); + + private: + SkTArray fMessages; + SkMutex fMessagesMutex; + uint32_t fUniqueID; + + friend class SkMessageBus; + void receive(const Message& m); // SkMessageBus is a friend only to call this. + }; + +private: + SkMessageBus(); + static SkMessageBus* Get(); + + SkTDArray fInboxes; + SkMutex fInboxesMutex; +}; + +// This must go in a single .cpp file, not some .h, or we risk creating more than one global +// SkMessageBus per type when using shared libraries. NOTE: at most one per file will compile. +#define DECLARE_SKMESSAGEBUS_MESSAGE(Message) \ + template <> \ + SkMessageBus* SkMessageBus::Get() { \ + static SkOnce once; \ + static SkMessageBus* bus; \ + once([] { bus = new SkMessageBus(); }); \ + return bus; \ + } + +// ----------------------- Implementation of SkMessageBus::Inbox ----------------------- + +template +SkMessageBus::Inbox::Inbox(uint32_t uniqueID) : fUniqueID(uniqueID) { + // Register ourselves with the corresponding message bus. + SkMessageBus* bus = SkMessageBus::Get(); + SkAutoMutexExclusive lock(bus->fInboxesMutex); + bus->fInboxes.push_back(this); +} + +template +SkMessageBus::Inbox::~Inbox() { + // Remove ourselves from the corresponding message bus. + SkMessageBus* bus = SkMessageBus::Get(); + SkAutoMutexExclusive lock(bus->fInboxesMutex); + // This is a cheaper fInboxes.remove(fInboxes.find(this)) when order doesn't matter. + for (int i = 0; i < bus->fInboxes.count(); i++) { + if (this == bus->fInboxes[i]) { + bus->fInboxes.removeShuffle(i); + break; + } + } +} + +template +void SkMessageBus::Inbox::receive(const Message& m) { + SkAutoMutexExclusive lock(fMessagesMutex); + fMessages.push_back(m); +} + +template +void SkMessageBus::Inbox::poll(SkTArray* messages) { + SkASSERT(messages); + messages->reset(); + SkAutoMutexExclusive lock(fMessagesMutex); + fMessages.swap(*messages); +} + +// ----------------------- Implementation of SkMessageBus ----------------------- + +template +SkMessageBus::SkMessageBus() {} + +template +/*static*/ void SkMessageBus::Post(const Message& m) { + SkMessageBus* bus = SkMessageBus::Get(); + SkAutoMutexExclusive lock(bus->fInboxesMutex); + for (int i = 0; i < bus->fInboxes.count(); i++) { + if (SkShouldPostMessageToBus(m, bus->fInboxes[i]->fUniqueID)) { + bus->fInboxes[i]->receive(m); + } + } +} + +#endif // SkMessageBus_DEFINED diff --git a/gfx/skia/skia/src/core/SkMiniRecorder.cpp b/gfx/skia/skia/src/core/SkMiniRecorder.cpp new file mode 100644 index 0000000000..e75ecd04c3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMiniRecorder.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkPicture.h" +#include "include/core/SkTextBlob.h" +#include "include/private/SkOnce.h" +#include "src/core/SkMiniRecorder.h" +#include "src/core/SkPictureCommon.h" +#include "src/core/SkRecordDraw.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkTLazy.h" +#include + +using namespace SkRecords; + +class SkEmptyPicture final : public SkPicture { +public: + void playback(SkCanvas*, AbortCallback*) const override { } + + size_t approximateBytesUsed() const override { return sizeof(*this); } + int approximateOpCount() const override { return 0; } + SkRect cullRect() const override { return SkRect::MakeEmpty(); } +}; + +// Calculate conservative bounds for each type of draw op that can be its own mini picture. +// These are fairly easy because we know they can't be affected by any matrix or saveLayers. +static SkRect adjust_for_paint(SkRect bounds, const SkPaint& paint) { + return paint.canComputeFastBounds() ? paint.computeFastBounds(bounds, &bounds) + : SkRectPriv::MakeLargest(); +} +static SkRect bounds(const DrawRect& op) { + return adjust_for_paint(op.rect, op.paint); +} +static SkRect bounds(const DrawPath& op) { + return op.path.isInverseFillType() ? SkRectPriv::MakeLargest() + : adjust_for_paint(op.path.getBounds(), op.paint); +} +static SkRect bounds(const DrawTextBlob& op) { + return adjust_for_paint(op.blob->bounds().makeOffset(op.x, op.y), op.paint); +} + +template +class SkMiniPicture final : public SkPicture { +public: + SkMiniPicture(const SkRect* cull, T* op) : fCull(cull ? *cull : bounds(*op)) { + memcpy(&fOp, op, sizeof(fOp)); // We take ownership of op's guts. + } + + void playback(SkCanvas* c, AbortCallback*) const override { + SkRecords::Draw(c, nullptr, nullptr, 0, nullptr)(fOp); + } + + size_t approximateBytesUsed() const override { return sizeof(*this); } + int approximateOpCount() const override { return 1; } + SkRect cullRect() const override { return fCull; } + +private: + SkRect fCull; + T fOp; +}; + + +SkMiniRecorder::SkMiniRecorder() : fState(State::kEmpty) {} +SkMiniRecorder::~SkMiniRecorder() { + if (fState != State::kEmpty) { + // We have internal state pending. + // Detaching then deleting a picture is an easy way to clean up. + (void)this->detachAsPicture(nullptr); + } + SkASSERT(fState == State::kEmpty); +} + +#define TRY_TO_STORE(Type, ...) \ + if (fState != State::kEmpty) { return false; } \ + fState = State::k##Type; \ + new (fBuffer.get()) Type{__VA_ARGS__}; \ + return true + +bool SkMiniRecorder::drawRect(const SkRect& rect, const SkPaint& paint) { + TRY_TO_STORE(DrawRect, paint, rect); +} + +bool SkMiniRecorder::drawPath(const SkPath& path, const SkPaint& paint) { + TRY_TO_STORE(DrawPath, paint, path); +} + +bool SkMiniRecorder::drawTextBlob(const SkTextBlob* b, SkScalar x, SkScalar y, const SkPaint& p) { + TRY_TO_STORE(DrawTextBlob, p, sk_ref_sp(b), x, y); +} +#undef TRY_TO_STORE + + +sk_sp SkMiniRecorder::detachAsPicture(const SkRect* cull) { +#define CASE(Type) \ + case State::k##Type: \ + fState = State::kEmpty; \ + return sk_make_sp>(cull, reinterpret_cast(fBuffer.get())) + + static SkOnce once; + static SkPicture* empty; + + switch (fState) { + case State::kEmpty: + once([]{ empty = new SkEmptyPicture; }); + return sk_ref_sp(empty); + CASE(DrawPath); + CASE(DrawRect); + CASE(DrawTextBlob); + } + SkASSERT(false); + return nullptr; +#undef CASE +} + +void SkMiniRecorder::flushAndReset(SkCanvas* canvas) { +#define CASE(Type) \ + case State::k##Type: { \ + fState = State::kEmpty; \ + Type* op = reinterpret_cast(fBuffer.get()); \ + SkRecords::Draw(canvas, nullptr, nullptr, 0, nullptr)(*op); \ + op->~Type(); \ + } return + + switch (fState) { + case State::kEmpty: return; + CASE(DrawPath); + CASE(DrawRect); + CASE(DrawTextBlob); + } + SkASSERT(false); +#undef CASE +} diff --git a/gfx/skia/skia/src/core/SkMiniRecorder.h b/gfx/skia/skia/src/core/SkMiniRecorder.h new file mode 100644 index 0000000000..0e5c143624 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMiniRecorder.h @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMiniRecorder_DEFINED +#define SkMiniRecorder_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "include/private/SkNoncopyable.h" +#include "src/core/SkRecords.h" +class SkCanvas; + +// Records small pictures, but only a limited subset of the canvas API, and may fail. +class SkMiniRecorder : SkNoncopyable { +public: + SkMiniRecorder(); + ~SkMiniRecorder(); + + // Try to record an op. Returns false on failure. + bool drawPath(const SkPath&, const SkPaint&); + bool drawRect(const SkRect&, const SkPaint&); + bool drawTextBlob(const SkTextBlob*, SkScalar x, SkScalar y, const SkPaint&); + + // Detach anything we've recorded as a picture, resetting this SkMiniRecorder. + // If cull is nullptr we'll calculate it. + sk_sp detachAsPicture(const SkRect* cull); + + // Flush anything we've recorded to the canvas, resetting this SkMiniRecorder. + // This is logically the same as but rather more efficient than: + // sk_sp pic(this->detachAsPicture(nullptr)); + // pic->playback(canvas); + void flushAndReset(SkCanvas*); + +private: + enum class State { + kEmpty, + kDrawPath, + kDrawRect, + kDrawTextBlob, + }; + + State fState; + + template + struct Max { static const size_t val = A > B ? A : B; }; + + static const size_t kInlineStorage = + Max::val>::val; + SkAlignedSStorage fBuffer; +}; + +#endif//SkMiniRecorder_DEFINED diff --git a/gfx/skia/skia/src/core/SkMipMap.cpp b/gfx/skia/skia/src/core/SkMipMap.cpp new file mode 100644 index 0000000000..748b7eaf70 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMipMap.cpp @@ -0,0 +1,778 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMipMap.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkNx.h" +#include "include/private/SkTo.h" +#include "include/private/SkVx.h" +#include "src/core/SkMathPriv.h" +#include + +// +// ColorTypeFilter is the "Type" we pass to some downsample template functions. +// It controls how we expand a pixel into a large type, with space between each component, +// so we can then perform our simple filter (either box or triangle) and store the intermediates +// in the expanded type. +// + +struct ColorTypeFilter_8888 { + typedef uint32_t Type; + static Sk4h Expand(uint32_t x) { + return SkNx_cast(Sk4b::Load(&x)); + } + static uint32_t Compact(const Sk4h& x) { + uint32_t r; + SkNx_cast(x).store(&r); + return r; + } +}; + +struct ColorTypeFilter_565 { + typedef uint16_t Type; + static uint32_t Expand(uint16_t x) { + return (x & ~SK_G16_MASK_IN_PLACE) | ((x & SK_G16_MASK_IN_PLACE) << 16); + } + static uint16_t Compact(uint32_t x) { + return ((x & ~SK_G16_MASK_IN_PLACE) & 0xFFFF) | ((x >> 16) & SK_G16_MASK_IN_PLACE); + } +}; + +struct ColorTypeFilter_4444 { + typedef uint16_t Type; + static uint32_t Expand(uint16_t x) { + return (x & 0xF0F) | ((x & ~0xF0F) << 12); + } + static uint16_t Compact(uint32_t x) { + return (x & 0xF0F) | ((x >> 12) & ~0xF0F); + } +}; + +struct ColorTypeFilter_8 { + typedef uint8_t Type; + static unsigned Expand(unsigned x) { + return x; + } + static uint8_t Compact(unsigned x) { + return (uint8_t)x; + } +}; + +struct ColorTypeFilter_Alpha_F16 { + typedef uint16_t Type; + static Sk4f Expand(uint16_t x) { + return SkHalfToFloat_finite_ftz((uint64_t) x); // expand out to four lanes + + } + static uint16_t Compact(const Sk4f& x) { + uint64_t r; + SkFloatToHalf_finite_ftz(x).store(&r); + return r & 0xFFFF; // but ignore the extra 3 here + } +}; + +struct ColorTypeFilter_RGBA_F16 { + typedef uint64_t Type; // SkHalf x4 + static Sk4f Expand(uint64_t x) { + return SkHalfToFloat_finite_ftz(x); + } + static uint64_t Compact(const Sk4f& x) { + uint64_t r; + SkFloatToHalf_finite_ftz(x).store(&r); + return r; + } +}; + +struct ColorTypeFilter_88 { + typedef uint16_t Type; + static uint32_t Expand(uint16_t x) { + return (x & 0xFF) | ((x & ~0xFF) << 8); + } + static uint16_t Compact(uint32_t x) { + return (x & 0xFF) | ((x >> 8) & ~0xFF); + } +}; + +struct ColorTypeFilter_1616 { + typedef uint32_t Type; + static uint64_t Expand(uint32_t x) { + return (x & 0xFFFF) | ((x & ~0xFFFF) << 16); + } + static uint16_t Compact(uint64_t x) { + return (x & 0xFFFF) | ((x >> 16) & ~0xFFFF); + } +}; + +struct ColorTypeFilter_F16F16 { + typedef uint32_t Type; + static Sk4f Expand(uint32_t x) { + return SkHalfToFloat_finite_ftz((uint64_t) x); // expand out to four lanes + } + static uint32_t Compact(const Sk4f& x) { + uint64_t r; + SkFloatToHalf_finite_ftz(x).store(&r); + return (uint32_t) (r & 0xFFFFFFFF); // but ignore the extra 2 here + } +}; + +struct ColorTypeFilter_16161616 { + typedef uint64_t Type; + static skvx::Vec<4, uint32_t> Expand(uint64_t x) { + return skvx::cast(skvx::Vec<4, uint16_t>::Load(&x)); + } + static uint64_t Compact(const skvx::Vec<4, uint32_t>& x) { + uint64_t r; + skvx::cast(x).store(&r); + return r; + } +}; + +struct ColorTypeFilter_16 { + typedef uint16_t Type; + static uint32_t Expand(uint16_t x) { + return x; + } + static uint16_t Compact(uint32_t x) { + return (uint16_t) x; + } +}; + +struct ColorTypeFilter_1010102 { + typedef uint32_t Type; + static uint64_t Expand(uint64_t x) { + return (((x ) & 0x3ff) ) | + (((x >> 10) & 0x3ff) << 20) | + (((x >> 20) & 0x3ff) << 40) | + (((x >> 30) & 0x3 ) << 60); + } + static uint32_t Compact(uint64_t x) { + return (((x ) & 0x3ff) ) | + (((x >> 20) & 0x3ff) << 10) | + (((x >> 40) & 0x3ff) << 20) | + (((x >> 60) & 0x3 ) << 30); + } +}; + +template T add_121(const T& a, const T& b, const T& c) { + return a + b + b + c; +} + +template T shift_right(const T& x, int bits) { + return x >> bits; +} + +Sk4f shift_right(const Sk4f& x, int bits) { + return x * (1.0f / (1 << bits)); +} + +template T shift_left(const T& x, int bits) { + return x << bits; +} + +Sk4f shift_left(const Sk4f& x, int bits) { + return x * (1 << bits); +} + +// +// To produce each mip level, we need to filter down by 1/2 (e.g. 100x100 -> 50,50) +// If the starting dimension is odd, we floor the size of the lower level (e.g. 101 -> 50) +// In those (odd) cases, we use a triangle filter, with 1-pixel overlap between samplings, +// else for even cases, we just use a 2x box filter. +// +// This produces 4 possible isotropic filters: 2x2 2x3 3x2 3x3 where WxH indicates the number of +// src pixels we need to sample in each dimension to produce 1 dst pixel. +// +// OpenGL expects a full mipmap stack to contain anisotropic space as well. +// This means a 100x1 image would continue down to a 50x1 image, 25x1 image... +// Because of this, we need 4 more anisotropic filters: 1x2, 1x3, 2x1, 3x1. + +template void downsample_1_2(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto p1 = (const typename F::Type*)((const char*)p0 + srcRB); + auto d = static_cast(dst); + + for (int i = 0; i < count; ++i) { + auto c00 = F::Expand(p0[0]); + auto c10 = F::Expand(p1[0]); + + auto c = c00 + c10; + d[i] = F::Compact(shift_right(c, 1)); + p0 += 2; + p1 += 2; + } +} + +template void downsample_1_3(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto p1 = (const typename F::Type*)((const char*)p0 + srcRB); + auto p2 = (const typename F::Type*)((const char*)p1 + srcRB); + auto d = static_cast(dst); + + for (int i = 0; i < count; ++i) { + auto c00 = F::Expand(p0[0]); + auto c10 = F::Expand(p1[0]); + auto c20 = F::Expand(p2[0]); + + auto c = add_121(c00, c10, c20); + d[i] = F::Compact(shift_right(c, 2)); + p0 += 2; + p1 += 2; + p2 += 2; + } +} + +template void downsample_2_1(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto d = static_cast(dst); + + for (int i = 0; i < count; ++i) { + auto c00 = F::Expand(p0[0]); + auto c01 = F::Expand(p0[1]); + + auto c = c00 + c01; + d[i] = F::Compact(shift_right(c, 1)); + p0 += 2; + } +} + +template void downsample_2_2(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto p1 = (const typename F::Type*)((const char*)p0 + srcRB); + auto d = static_cast(dst); + + for (int i = 0; i < count; ++i) { + auto c00 = F::Expand(p0[0]); + auto c01 = F::Expand(p0[1]); + auto c10 = F::Expand(p1[0]); + auto c11 = F::Expand(p1[1]); + + auto c = c00 + c10 + c01 + c11; + d[i] = F::Compact(shift_right(c, 2)); + p0 += 2; + p1 += 2; + } +} + +template void downsample_2_3(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto p1 = (const typename F::Type*)((const char*)p0 + srcRB); + auto p2 = (const typename F::Type*)((const char*)p1 + srcRB); + auto d = static_cast(dst); + + for (int i = 0; i < count; ++i) { + auto c00 = F::Expand(p0[0]); + auto c01 = F::Expand(p0[1]); + auto c10 = F::Expand(p1[0]); + auto c11 = F::Expand(p1[1]); + auto c20 = F::Expand(p2[0]); + auto c21 = F::Expand(p2[1]); + + auto c = add_121(c00, c10, c20) + add_121(c01, c11, c21); + d[i] = F::Compact(shift_right(c, 3)); + p0 += 2; + p1 += 2; + p2 += 2; + } +} + +template void downsample_3_1(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto d = static_cast(dst); + + auto c02 = F::Expand(p0[0]); + for (int i = 0; i < count; ++i) { + auto c00 = c02; + auto c01 = F::Expand(p0[1]); + c02 = F::Expand(p0[2]); + + auto c = add_121(c00, c01, c02); + d[i] = F::Compact(shift_right(c, 2)); + p0 += 2; + } +} + +template void downsample_3_2(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto p1 = (const typename F::Type*)((const char*)p0 + srcRB); + auto d = static_cast(dst); + + // Given pixels: + // a0 b0 c0 d0 e0 ... + // a1 b1 c1 d1 e1 ... + // We want: + // (a0 + 2*b0 + c0 + a1 + 2*b1 + c1) / 8 + // (c0 + 2*d0 + e0 + c1 + 2*d1 + e1) / 8 + // ... + + auto c0 = F::Expand(p0[0]); + auto c1 = F::Expand(p1[0]); + auto c = c0 + c1; + for (int i = 0; i < count; ++i) { + auto a = c; + + auto b0 = F::Expand(p0[1]); + auto b1 = F::Expand(p1[1]); + auto b = b0 + b0 + b1 + b1; + + c0 = F::Expand(p0[2]); + c1 = F::Expand(p1[2]); + c = c0 + c1; + + auto sum = a + b + c; + d[i] = F::Compact(shift_right(sum, 3)); + p0 += 2; + p1 += 2; + } +} + +template void downsample_3_3(void* dst, const void* src, size_t srcRB, int count) { + SkASSERT(count > 0); + auto p0 = static_cast(src); + auto p1 = (const typename F::Type*)((const char*)p0 + srcRB); + auto p2 = (const typename F::Type*)((const char*)p1 + srcRB); + auto d = static_cast(dst); + + // Given pixels: + // a0 b0 c0 d0 e0 ... + // a1 b1 c1 d1 e1 ... + // a2 b2 c2 d2 e2 ... + // We want: + // (a0 + 2*b0 + c0 + 2*a1 + 4*b1 + 2*c1 + a2 + 2*b2 + c2) / 16 + // (c0 + 2*d0 + e0 + 2*c1 + 4*d1 + 2*e1 + c2 + 2*d2 + e2) / 16 + // ... + + auto c0 = F::Expand(p0[0]); + auto c1 = F::Expand(p1[0]); + auto c2 = F::Expand(p2[0]); + auto c = add_121(c0, c1, c2); + for (int i = 0; i < count; ++i) { + auto a = c; + + auto b0 = F::Expand(p0[1]); + auto b1 = F::Expand(p1[1]); + auto b2 = F::Expand(p2[1]); + auto b = shift_left(add_121(b0, b1, b2), 1); + + c0 = F::Expand(p0[2]); + c1 = F::Expand(p1[2]); + c2 = F::Expand(p2[2]); + c = add_121(c0, c1, c2); + + auto sum = a + b + c; + d[i] = F::Compact(shift_right(sum, 4)); + p0 += 2; + p1 += 2; + p2 += 2; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +size_t SkMipMap::AllocLevelsSize(int levelCount, size_t pixelSize) { + if (levelCount < 0) { + return 0; + } + int64_t size = sk_64_mul(levelCount + 1, sizeof(Level)) + pixelSize; + if (!SkTFitsIn(size)) { + return 0; + } + return SkTo(size); +} + +SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) { + typedef void FilterProc(void*, const void* srcPtr, size_t srcRB, int count); + + FilterProc* proc_1_2 = nullptr; + FilterProc* proc_1_3 = nullptr; + FilterProc* proc_2_1 = nullptr; + FilterProc* proc_2_2 = nullptr; + FilterProc* proc_2_3 = nullptr; + FilterProc* proc_3_1 = nullptr; + FilterProc* proc_3_2 = nullptr; + FilterProc* proc_3_3 = nullptr; + + const SkColorType ct = src.colorType(); + const SkAlphaType at = src.alphaType(); + + switch (ct) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kRGB_565_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kARGB_4444_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kAlpha_8_SkColorType: + case kGray_8_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kR8G8_unorm_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kR16G16_unorm_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kA16_unorm_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kRGBA_1010102_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kA16_float_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kR16G16_float_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + case kR16G16B16A16_unorm_SkColorType: + proc_1_2 = downsample_1_2; + proc_1_3 = downsample_1_3; + proc_2_1 = downsample_2_1; + proc_2_2 = downsample_2_2; + proc_2_3 = downsample_2_3; + proc_3_1 = downsample_3_1; + proc_3_2 = downsample_3_2; + proc_3_3 = downsample_3_3; + break; + default: + return nullptr; + } + + if (src.width() <= 1 && src.height() <= 1) { + return nullptr; + } + // whip through our loop to compute the exact size needed + size_t size = 0; + int countLevels = ComputeLevelCount(src.width(), src.height()); + for (int currentMipLevel = countLevels; currentMipLevel >= 0; currentMipLevel--) { + SkISize mipSize = ComputeLevelSize(src.width(), src.height(), currentMipLevel); + size += SkColorTypeMinRowBytes(ct, mipSize.fWidth) * mipSize.fHeight; + } + + size_t storageSize = SkMipMap::AllocLevelsSize(countLevels, size); + if (0 == storageSize) { + return nullptr; + } + + SkMipMap* mipmap; + if (fact) { + SkDiscardableMemory* dm = fact(storageSize); + if (nullptr == dm) { + return nullptr; + } + mipmap = new SkMipMap(storageSize, dm); + } else { + mipmap = new SkMipMap(sk_malloc_throw(storageSize), storageSize); + } + + // init + mipmap->fCS = sk_ref_sp(src.info().colorSpace()); + mipmap->fCount = countLevels; + mipmap->fLevels = (Level*)mipmap->writable_data(); + SkASSERT(mipmap->fLevels); + + Level* levels = mipmap->fLevels; + uint8_t* baseAddr = (uint8_t*)&levels[countLevels]; + uint8_t* addr = baseAddr; + int width = src.width(); + int height = src.height(); + uint32_t rowBytes; + SkPixmap srcPM(src); + + // Depending on architecture and other factors, the pixel data alignment may need to be as + // large as 8 (for F16 pixels). See the comment on SkMipMap::Level. + SkASSERT(SkIsAlign8((uintptr_t)addr)); + + for (int i = 0; i < countLevels; ++i) { + FilterProc* proc; + if (height & 1) { + if (height == 1) { // src-height is 1 + if (width & 1) { // src-width is 3 + proc = proc_3_1; + } else { // src-width is 2 + proc = proc_2_1; + } + } else { // src-height is 3 + if (width & 1) { + if (width == 1) { // src-width is 1 + proc = proc_1_3; + } else { // src-width is 3 + proc = proc_3_3; + } + } else { // src-width is 2 + proc = proc_2_3; + } + } + } else { // src-height is 2 + if (width & 1) { + if (width == 1) { // src-width is 1 + proc = proc_1_2; + } else { // src-width is 3 + proc = proc_3_2; + } + } else { // src-width is 2 + proc = proc_2_2; + } + } + width = SkTMax(1, width >> 1); + height = SkTMax(1, height >> 1); + rowBytes = SkToU32(SkColorTypeMinRowBytes(ct, width)); + + // We make the Info w/o any colorspace, since that storage is not under our control, and + // will not be deleted in a controlled fashion. When the caller is given the pixmap for + // a given level, we augment this pixmap with fCS (which we do manage). + new (&levels[i].fPixmap) SkPixmap(SkImageInfo::Make(width, height, ct, at), addr, rowBytes); + levels[i].fScale = SkSize::Make(SkIntToScalar(width) / src.width(), + SkIntToScalar(height) / src.height()); + + const SkPixmap& dstPM = levels[i].fPixmap; + const void* srcBasePtr = srcPM.addr(); + void* dstBasePtr = dstPM.writable_addr(); + + const size_t srcRB = srcPM.rowBytes(); + for (int y = 0; y < height; y++) { + proc(dstBasePtr, srcBasePtr, srcRB, width); + srcBasePtr = (char*)srcBasePtr + srcRB * 2; // jump two rows + dstBasePtr = (char*)dstBasePtr + dstPM.rowBytes(); + } + srcPM = dstPM; + addr += height * rowBytes; + } + SkASSERT(addr == baseAddr + size); + + SkASSERT(mipmap->fLevels); + return mipmap; +} + +int SkMipMap::ComputeLevelCount(int baseWidth, int baseHeight) { + if (baseWidth < 1 || baseHeight < 1) { + return 0; + } + + // OpenGL's spec requires that each mipmap level have height/width equal to + // max(1, floor(original_height / 2^i) + // (or original_width) where i is the mipmap level. + // Continue scaling down until both axes are size 1. + + const int largestAxis = SkTMax(baseWidth, baseHeight); + if (largestAxis < 2) { + // SkMipMap::Build requires a minimum size of 2. + return 0; + } + const int leadingZeros = SkCLZ(static_cast(largestAxis)); + // If the value 00011010 has 3 leading 0s then it has 5 significant bits + // (the bits which are not leading zeros) + const int significantBits = (sizeof(uint32_t) * 8) - leadingZeros; + // This is making the assumption that the size of a byte is 8 bits + // and that sizeof(uint32_t)'s implementation-defined behavior is 4. + int mipLevelCount = significantBits; + + // SkMipMap does not include the base mip level. + // For example, it contains levels 1-x instead of 0-x. + // This is because the image used to create SkMipMap is the base level. + // So subtract 1 from the mip level count. + if (mipLevelCount > 0) { + --mipLevelCount; + } + + return mipLevelCount; +} + +SkISize SkMipMap::ComputeLevelSize(int baseWidth, int baseHeight, int level) { + if (baseWidth < 1 || baseHeight < 1) { + return SkISize::Make(0, 0); + } + + int maxLevelCount = ComputeLevelCount(baseWidth, baseHeight); + if (level >= maxLevelCount || level < 0) { + return SkISize::Make(0, 0); + } + // OpenGL's spec requires that each mipmap level have height/width equal to + // max(1, floor(original_height / 2^i) + // (or original_width) where i is the mipmap level. + + // SkMipMap does not include the base mip level. + // For example, it contains levels 1-x instead of 0-x. + // This is because the image used to create SkMipMap is the base level. + // So subtract 1 from the mip level to get the index stored by SkMipMap. + int width = SkTMax(1, baseWidth >> (level + 1)); + int height = SkTMax(1, baseHeight >> (level + 1)); + + return SkISize::Make(width, height); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkMipMap::extractLevel(const SkSize& scaleSize, Level* levelPtr) const { + if (nullptr == fLevels) { + return false; + } + + SkASSERT(scaleSize.width() >= 0 && scaleSize.height() >= 0); + +#ifndef SK_SUPPORT_LEGACY_ANISOTROPIC_MIPMAP_SCALE + // Use the smallest scale to match the GPU impl. + const SkScalar scale = SkTMin(scaleSize.width(), scaleSize.height()); +#else + // Ideally we'd pick the smaller scale, to match Ganesh. But ignoring one of the + // scales can produce some atrocious results, so for now we use the geometric mean. + // (https://bugs.chromium.org/p/skia/issues/detail?id=4863) + const SkScalar scale = SkScalarSqrt(scaleSize.width() * scaleSize.height()); +#endif + + if (scale >= SK_Scalar1 || scale <= 0 || !SkScalarIsFinite(scale)) { + return false; + } + + SkScalar L = -SkScalarLog2(scale); + if (!SkScalarIsFinite(L)) { + return false; + } + SkASSERT(L >= 0); + int level = SkScalarFloorToInt(L); + + SkASSERT(level >= 0); + if (level <= 0) { + return false; + } + + if (level > fCount) { + level = fCount; + } + if (levelPtr) { + *levelPtr = fLevels[level - 1]; + // need to augment with our colorspace + levelPtr->fPixmap.setColorSpace(fCS); + } + return true; +} + +// Helper which extracts a pixmap from the src bitmap +// +SkMipMap* SkMipMap::Build(const SkBitmap& src, SkDiscardableFactoryProc fact) { + SkPixmap srcPixmap; + if (!src.peekPixels(&srcPixmap)) { + return nullptr; + } + return Build(srcPixmap, fact); +} + +int SkMipMap::countLevels() const { + return fCount; +} + +bool SkMipMap::getLevel(int index, Level* levelPtr) const { + if (nullptr == fLevels) { + return false; + } + if (index < 0) { + return false; + } + if (index > fCount - 1) { + return false; + } + if (levelPtr) { + *levelPtr = fLevels[index]; + } + return true; +} diff --git a/gfx/skia/skia/src/core/SkMipMap.h b/gfx/skia/skia/src/core/SkMipMap.h new file mode 100644 index 0000000000..9aa43d265e --- /dev/null +++ b/gfx/skia/skia/src/core/SkMipMap.h @@ -0,0 +1,82 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMipMap_DEFINED +#define SkMipMap_DEFINED + +#include "include/core/SkPixmap.h" +#include "include/core/SkScalar.h" +#include "include/core/SkSize.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkCachedData.h" +#include "src/shaders/SkShaderBase.h" + +class SkBitmap; +class SkDiscardableMemory; + +typedef SkDiscardableMemory* (*SkDiscardableFactoryProc)(size_t bytes); + +/* + * SkMipMap will generate mipmap levels when given a base mipmap level image. + * + * Any function which deals with mipmap levels indices will start with index 0 + * being the first mipmap level which was generated. Said another way, it does + * not include the base level in its range. + */ +class SkMipMap : public SkCachedData { +public: + static SkMipMap* Build(const SkPixmap& src, SkDiscardableFactoryProc); + static SkMipMap* Build(const SkBitmap& src, SkDiscardableFactoryProc); + + // Determines how many levels a SkMipMap will have without creating that mipmap. + // This does not include the base mipmap level that the user provided when + // creating the SkMipMap. + static int ComputeLevelCount(int baseWidth, int baseHeight); + + // Determines the size of a given mipmap level. + // |level| is an index into the generated mipmap levels. It does not include + // the base level. So index 0 represents mipmap level 1. + static SkISize ComputeLevelSize(int baseWidth, int baseHeight, int level); + + // We use a block of (possibly discardable) memory to hold an array of Level structs, followed + // by the pixel data for each level. On 32-bit platforms, Level would naturally be 4 byte + // aligned, so the pixel data could end up with 4 byte alignment. If the pixel data is F16, + // it must be 8 byte aligned. To ensure this, keep the Level struct 8 byte aligned as well. + struct alignas(8) Level { + SkPixmap fPixmap; + SkSize fScale; // < 1.0 + }; + + bool extractLevel(const SkSize& scale, Level*) const; + + // countLevels returns the number of mipmap levels generated (which does not + // include the base mipmap level). + int countLevels() const; + + // |index| is an index into the generated mipmap levels. It does not include + // the base level. So index 0 represents mipmap level 1. + bool getLevel(int index, Level*) const; + +protected: + void onDataChange(void* oldData, void* newData) override { + fLevels = (Level*)newData; // could be nullptr + } + +private: + sk_sp fCS; + Level* fLevels; // managed by the baseclass, may be null due to onDataChanged. + int fCount; + + SkMipMap(void* malloc, size_t size) : INHERITED(malloc, size) {} + SkMipMap(size_t size, SkDiscardableMemory* dm) : INHERITED(size, dm) {} + + static size_t AllocLevelsSize(int levelCount, size_t pixelSize); + + typedef SkCachedData INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkModeColorFilter.cpp b/gfx/skia/skia/src/core/SkModeColorFilter.cpp new file mode 100644 index 0000000000..bad042be1c --- /dev/null +++ b/gfx/skia/skia/src/core/SkModeColorFilter.cpp @@ -0,0 +1,141 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkString.h" +#include "include/private/SkColorData.h" +#include "include/utils/SkRandom.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkBlitRow.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkModeColorFilter.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkValidationUtils.h" +#include "src/core/SkWriteBuffer.h" +#include "src/utils/SkUTF.h" + +////////////////////////////////////////////////////////////////////////////////////////////////// + +SkModeColorFilter::SkModeColorFilter(SkColor color, SkBlendMode mode) { + fColor = color; + fMode = mode; +} + +bool SkModeColorFilter::onAsAColorMode(SkColor* color, SkBlendMode* mode) const { + if (color) { + *color = fColor; + } + if (mode) { + *mode = fMode; + } + return true; +} + +uint32_t SkModeColorFilter::getFlags() const { + uint32_t flags = 0; + switch (fMode) { + case SkBlendMode::kDst: //!< [Da, Dc] + case SkBlendMode::kSrcATop: //!< [Da, Sc * Da + (1 - Sa) * Dc] + flags |= kAlphaUnchanged_Flag; + default: + break; + } + return flags; +} + +void SkModeColorFilter::flatten(SkWriteBuffer& buffer) const { + buffer.writeColor(fColor); + buffer.writeUInt((int)fMode); +} + +sk_sp SkModeColorFilter::CreateProc(SkReadBuffer& buffer) { + SkColor color = buffer.readColor(); + SkBlendMode mode = (SkBlendMode)buffer.readUInt(); + return SkColorFilters::Blend(color, mode); +} + +bool SkModeColorFilter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const { + rec.fPipeline->append(SkRasterPipeline::move_src_dst); + SkColor4f color = SkColor4f::FromColor(fColor); + SkColorSpaceXformSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType, + rec.fDstCS, kUnpremul_SkAlphaType).apply(color.vec()); + rec.fPipeline->append_constant_color(rec.fAlloc, color.premul().vec()); + SkBlendMode_AppendStages(fMode, rec.fPipeline); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +#if SK_SUPPORT_GPU +#include "src/gpu/GrBlend.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" + +std::unique_ptr SkModeColorFilter::asFragmentProcessor( + GrRecordingContext*, const GrColorInfo& dstColorInfo) const { + if (SkBlendMode::kDst == fMode) { + return nullptr; + } + + auto constFP = GrConstColorProcessor::Make(SkColorToPMColor4f(fColor, dstColorInfo), + GrConstColorProcessor::InputMode::kIgnore); + auto fp = GrXfermodeFragmentProcessor::MakeFromSrcProcessor(std::move(constFP), fMode); + if (!fp) { + return nullptr; + } +#ifdef SK_DEBUG + // With a solid color input this should always be able to compute the blended color + // (at least for coeff modes) + if ((unsigned)fMode <= (unsigned)SkBlendMode::kLastCoeffMode) { + SkASSERT(fp->hasConstantOutputForConstantInput()); + } +#endif + return fp; +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkColorFilters::Blend(SkColor color, SkBlendMode mode) { + if (!SkIsValidMode(mode)) { + return nullptr; + } + + unsigned alpha = SkColorGetA(color); + + // first collaps some modes if possible + + if (SkBlendMode::kClear == mode) { + color = 0; + mode = SkBlendMode::kSrc; + } else if (SkBlendMode::kSrcOver == mode) { + if (0 == alpha) { + mode = SkBlendMode::kDst; + } else if (255 == alpha) { + mode = SkBlendMode::kSrc; + } + // else just stay srcover + } + + // weed out combinations that are noops, and just return null + if (SkBlendMode::kDst == mode || + (0 == alpha && (SkBlendMode::kSrcOver == mode || + SkBlendMode::kDstOver == mode || + SkBlendMode::kDstOut == mode || + SkBlendMode::kSrcATop == mode || + SkBlendMode::kXor == mode || + SkBlendMode::kDarken == mode)) || + (0xFF == alpha && SkBlendMode::kDstIn == mode)) { + return nullptr; + } + + return SkModeColorFilter::Make(color, mode); +} diff --git a/gfx/skia/skia/src/core/SkModeColorFilter.h b/gfx/skia/skia/src/core/SkModeColorFilter.h new file mode 100644 index 0000000000..ba107590e6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkModeColorFilter.h @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkModeColorFilter_DEFINED +#define SkModeColorFilter_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkFlattenable.h" + +class SkModeColorFilter : public SkColorFilter { +public: + static sk_sp Make(SkColor color, SkBlendMode mode) { + return sk_sp(new SkModeColorFilter(color, mode)); + } + + uint32_t getFlags() const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override; +#endif + +protected: + SkModeColorFilter(SkColor color, SkBlendMode mode); + + void flatten(SkWriteBuffer&) const override; + bool onAsAColorMode(SkColor*, SkBlendMode*) const override; + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override; + +private: + SK_FLATTENABLE_HOOKS(SkModeColorFilter) + + SkColor fColor; + SkBlendMode fMode; + + friend class SkColorFilter; + + typedef SkColorFilter INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkMultiPictureDraw.cpp b/gfx/skia/skia/src/core/SkMultiPictureDraw.cpp new file mode 100644 index 0000000000..837aa600b9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkMultiPictureDraw.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkMultiPictureDraw.h" +#include "include/core/SkPicture.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkTaskGroup.h" + +void SkMultiPictureDraw::DrawData::draw() { + fCanvas->drawPicture(fPicture, &fMatrix, fPaint); +} + +void SkMultiPictureDraw::DrawData::init(SkCanvas* canvas, const SkPicture* picture, + const SkMatrix* matrix, const SkPaint* paint) { + fPicture = SkRef(picture); + fCanvas = canvas; + if (matrix) { + fMatrix = *matrix; + } else { + fMatrix.setIdentity(); + } + if (paint) { + fPaint = new SkPaint(*paint); + } else { + fPaint = nullptr; + } +} + +void SkMultiPictureDraw::DrawData::Reset(SkTDArray& data) { + for (int i = 0; i < data.count(); ++i) { + data[i].fPicture->unref(); + delete data[i].fPaint; + } + data.rewind(); +} + +////////////////////////////////////////////////////////////////////////////////////// + +SkMultiPictureDraw::SkMultiPictureDraw(int reserve) { + if (reserve > 0) { + fGPUDrawData.setReserve(reserve); + fThreadSafeDrawData.setReserve(reserve); + } +} + +void SkMultiPictureDraw::reset() { + DrawData::Reset(fGPUDrawData); + DrawData::Reset(fThreadSafeDrawData); +} + +void SkMultiPictureDraw::add(SkCanvas* canvas, + const SkPicture* picture, + const SkMatrix* matrix, + const SkPaint* paint) { + if (nullptr == canvas || nullptr == picture) { + SkDEBUGFAIL("parameters to SkMultiPictureDraw::add should be non-nullptr"); + return; + } + + SkTDArray& array = canvas->getGrContext() ? fGPUDrawData : fThreadSafeDrawData; + array.append()->init(canvas, picture, matrix, paint); +} + +class AutoMPDReset : SkNoncopyable { + SkMultiPictureDraw* fMPD; +public: + AutoMPDReset(SkMultiPictureDraw* mpd) : fMPD(mpd) {} + ~AutoMPDReset() { fMPD->reset(); } +}; + +//#define FORCE_SINGLE_THREAD_DRAWING_FOR_TESTING + +void SkMultiPictureDraw::draw(bool flush) { + AutoMPDReset mpdreset(this); + +#ifdef FORCE_SINGLE_THREAD_DRAWING_FOR_TESTING + for (int i = 0; i < fThreadSafeDrawData.count(); ++i) { + fThreadSafeDrawData[i].draw(); + } +#else + SkTaskGroup().batch(fThreadSafeDrawData.count(), [&](int i) { + fThreadSafeDrawData[i].draw(); + }); +#endif + + // N.B. we could get going on any GPU work from this main thread while the CPU work runs. + // But in practice, we've either got GPU work or CPU work, not both. + + const int count = fGPUDrawData.count(); + if (0 == count) { + return; + } + + for (int i = 0; i < count; ++i) { + const DrawData& data = fGPUDrawData[i]; + SkCanvas* canvas = data.fCanvas; + const SkPicture* picture = data.fPicture; + + canvas->drawPicture(picture, &data.fMatrix, data.fPaint); + if (flush) { + canvas->flush(); + } + } +} diff --git a/gfx/skia/skia/src/core/SkNextID.h b/gfx/skia/skia/src/core/SkNextID.h new file mode 100644 index 0000000000..395c9a27a6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkNextID.h @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNextID_DEFINED +#define SkNextID_DEFINED + +#include "include/core/SkTypes.h" + +class SkNextID { +public: + /** + * Shared between SkPixelRef's generationID and SkImage's uniqueID + */ + static uint32_t ImageID(); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkNormalFlatSource.cpp b/gfx/skia/skia/src/core/SkNormalFlatSource.cpp new file mode 100644 index 0000000000..13fa2af488 --- /dev/null +++ b/gfx/skia/skia/src/core/SkNormalFlatSource.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkNormalFlatSource.h" + +#include "include/core/SkPoint3.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkNormalSource.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +class NormalFlatFP : public GrFragmentProcessor { +public: + static std::unique_ptr Make() { + return std::unique_ptr(new NormalFlatFP()); + } + + const char* name() const override { return "NormalFlatFP"; } + + std::unique_ptr clone() const override { return Make(); } + +private: + class GLSLNormalFlatFP : public GrGLSLFragmentProcessor { + public: + GLSLNormalFlatFP() {} + + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + fragBuilder->codeAppendf("%s = half4(0, 0, 1, 0);", args.fOutputColor); + } + + private: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override {} + }; + + NormalFlatFP() + : INHERITED(kFlatNormalsFP_ClassID, kConstantOutputForConstantInput_OptimizationFlag) { + } + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {} + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f&) const override { + return { 0, 0, 1, 0 }; + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalFlatFP; } + + bool onIsEqual(const GrFragmentProcessor&) const override { return true; } + + typedef GrFragmentProcessor INHERITED; +}; + +std::unique_ptr SkNormalFlatSourceImpl::asFragmentProcessor( + const GrFPArgs&) const { + return NormalFlatFP::Make(); +} + +#endif // SK_SUPPORT_GPU + +//////////////////////////////////////////////////////////////////////////// + +SkNormalFlatSourceImpl::Provider::Provider() {} + +SkNormalFlatSourceImpl::Provider::~Provider() {} + +SkNormalSource::Provider* SkNormalFlatSourceImpl::asProvider(const SkShaderBase::ContextRec &rec, + SkArenaAlloc *alloc) const { + return alloc->make(); +} + +void SkNormalFlatSourceImpl::Provider::fillScanLine(int x, int y, SkPoint3 output[], + int count) const { + for (int i = 0; i < count; i++) { + output[i] = {0.0f, 0.0f, 1.0f}; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalFlatSourceImpl::CreateProc(SkReadBuffer& buf) { + return sk_make_sp(); +} + +void SkNormalFlatSourceImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); +} + +//////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalSource::MakeFlat() { + return sk_make_sp(); +} diff --git a/gfx/skia/skia/src/core/SkNormalFlatSource.h b/gfx/skia/skia/src/core/SkNormalFlatSource.h new file mode 100644 index 0000000000..a7960b0ac6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkNormalFlatSource.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNormalFlatSource_DEFINED +#define SkNormalFlatSource_DEFINED + +#include "src/core/SkNormalSource.h" + +class SK_API SkNormalFlatSourceImpl : public SkNormalSource { +public: + SkNormalFlatSourceImpl(){} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs& args) const override; +#endif + + SkNormalSource::Provider* asProvider(const SkShaderBase::ContextRec& rec, + SkArenaAlloc* alloc) const override; + +protected: + void flatten(SkWriteBuffer& buf) const override; + +private: + SK_FLATTENABLE_HOOKS(SkNormalFlatSourceImpl) + + class Provider : public SkNormalSource::Provider { + public: + Provider(); + + ~Provider() override; + + void fillScanLine(int x, int y, SkPoint3 output[], int count) const override; + + private: + typedef SkNormalSource::Provider INHERITED; + }; + + friend class SkNormalSource; + + typedef SkNormalSource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkNormalMapSource.cpp b/gfx/skia/skia/src/core/SkNormalMapSource.cpp new file mode 100644 index 0000000000..b84e726934 --- /dev/null +++ b/gfx/skia/skia/src/core/SkNormalMapSource.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkNormalMapSource.h" + +#include "include/core/SkMatrix.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkNormalSource.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkLightingShader.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +class NormalMapFP : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr mapFP, + const SkMatrix& invCTM) { + return std::unique_ptr(new NormalMapFP(std::move(mapFP), invCTM)); + } + + const char* name() const override { return "NormalMapFP"; } + + const SkMatrix& invCTM() const { return fInvCTM; } + + std::unique_ptr clone() const override { + return Make(this->childProcessor(0).clone(), fInvCTM); + } + +private: + class GLSLNormalMapFP : public GrGLSLFragmentProcessor { + public: + GLSLNormalMapFP() : fColumnMajorInvCTM22{0.0f} {} + + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // add uniform + const char* xformUniName = nullptr; + fXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2x2_GrSLType, + "Xform", &xformUniName); + + SkString dstNormalColorName("dstNormalColor"); + this->invokeChild(0, &dstNormalColorName, args); + fragBuilder->codeAppendf("float3 normal = normalize(%s.rgb - float3(0.5));", + dstNormalColorName.c_str()); + + // If there's no x & y components, return (0, 0, +/- 1) instead to avoid division by 0 + fragBuilder->codeAppend( "if (abs(normal.z) > 0.999) {"); + fragBuilder->codeAppendf(" %s = normalize(half4(0.0, 0.0, half(normal.z), 0.0));", + args.fOutputColor); + // Else, Normalizing the transformed X and Y, while keeping constant both Z and the + // vector's angle in the XY plane. This maintains the "slope" for the surface while + // appropriately rotating the normal regardless of any anisotropic scaling that occurs. + // Here, we call 'scaling factor' the number that must divide the transformed X and Y so + // that the normal's length remains equal to 1. + fragBuilder->codeAppend( "} else {"); + fragBuilder->codeAppendf(" float2 transformed = %s * normal.xy;", + xformUniName); + fragBuilder->codeAppend( " float scalingFactorSquared = " + "( (transformed.x * transformed.x) " + "+ (transformed.y * transformed.y) )" + "/(1.0 - (normal.z * normal.z));"); + fragBuilder->codeAppendf(" %s = half4(half2(transformed * " + "inversesqrt(scalingFactorSquared))," + "half(normal.z), 0.0);", + args.fOutputColor); + fragBuilder->codeAppend( "}"); + } + + static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(0x0); + } + + private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) override { + const NormalMapFP& normalMapFP = proc.cast(); + + const SkMatrix& invCTM = normalMapFP.invCTM(); + fColumnMajorInvCTM22[0] = invCTM.get(SkMatrix::kMScaleX); + fColumnMajorInvCTM22[1] = invCTM.get(SkMatrix::kMSkewY); + fColumnMajorInvCTM22[2] = invCTM.get(SkMatrix::kMSkewX); + fColumnMajorInvCTM22[3] = invCTM.get(SkMatrix::kMScaleY); + pdman.setMatrix2f(fXformUni, fColumnMajorInvCTM22); + } + + private: + // Upper-right 2x2 corner of the inverse of the CTM in column-major form + float fColumnMajorInvCTM22[4]; + GrGLSLProgramDataManager::UniformHandle fXformUni; + }; + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLNormalMapFP::GenKey(*this, caps, b); + } + NormalMapFP(std::unique_ptr mapFP, const SkMatrix& invCTM) + : INHERITED(kMappedNormalsFP_ClassID, kNone_OptimizationFlags) + , fInvCTM(invCTM) { + this->registerChildProcessor(std::move(mapFP)); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalMapFP; } + + bool onIsEqual(const GrFragmentProcessor& proc) const override { + const NormalMapFP& normalMapFP = proc.cast(); + return fInvCTM == normalMapFP.fInvCTM; + } + + SkMatrix fInvCTM; + + typedef GrFragmentProcessor INHERITED; +}; + +std::unique_ptr SkNormalMapSourceImpl::asFragmentProcessor( + const GrFPArgs& args) const { + std::unique_ptr mapFP = as_SB(fMapShader)->asFragmentProcessor(args); + if (!mapFP) { + return nullptr; + } + + return NormalMapFP::Make(std::move(mapFP), fInvCTM); +} + +#endif // SK_SUPPORT_GPU + +//////////////////////////////////////////////////////////////////////////// + +SkNormalMapSourceImpl::Provider::Provider(const SkNormalMapSourceImpl& source, + SkShaderBase::Context* mapContext) + : fSource(source) + , fMapContext(mapContext) {} + +SkNormalSource::Provider* SkNormalMapSourceImpl::asProvider(const SkShaderBase::ContextRec &rec, + SkArenaAlloc* alloc) const { + SkMatrix normTotalInv; + if (!this->computeNormTotalInverse(rec, &normTotalInv)) { + return nullptr; + } + + // Normals really aren't colors, so to ensure we can always make the context, we ignore + // the rec's colorspace + SkColorSpace* dstColorSpace = nullptr; + + // Overriding paint's alpha because we need the normal map's RGB channels to be unpremul'd + SkPaint overridePaint {*(rec.fPaint)}; + overridePaint.setAlpha(0xFF); + SkShaderBase::ContextRec overrideRec(overridePaint, *(rec.fMatrix), rec.fLocalMatrix, + rec.fDstColorType, dstColorSpace); + + auto* context = as_SB(fMapShader)->makeContext(overrideRec, alloc); + if (!context) { + return nullptr; + } + + return alloc->make(*this, context); +} + +bool SkNormalMapSourceImpl::computeNormTotalInverse(const SkShaderBase::ContextRec& rec, + SkMatrix* normTotalInverse) const { + SkMatrix total = SkMatrix::Concat(*rec.fMatrix, as_SB(fMapShader)->getLocalMatrix()); + if (rec.fLocalMatrix) { + total.preConcat(*rec.fLocalMatrix); + } + + return total.invert(normTotalInverse); +} + +#define BUFFER_MAX 16 +void SkNormalMapSourceImpl::Provider::fillScanLine(int x, int y, SkPoint3 output[], + int count) const { + SkPMColor tmpNormalColors[BUFFER_MAX]; + + do { + int n = SkTMin(count, BUFFER_MAX); + + fMapContext->shadeSpan(x, y, tmpNormalColors, n); + + for (int i = 0; i < n; i++) { + SkPoint3 tempNorm; + + tempNorm.set(SkIntToScalar(SkGetPackedR32(tmpNormalColors[i])) - 127.0f, + SkIntToScalar(SkGetPackedG32(tmpNormalColors[i])) - 127.0f, + SkIntToScalar(SkGetPackedB32(tmpNormalColors[i])) - 127.0f); + + tempNorm.normalize(); + + + if (!SkScalarNearlyEqual(SkScalarAbs(tempNorm.fZ), 1.0f)) { + SkVector transformed = fSource.fInvCTM.mapVector(tempNorm.fX, tempNorm.fY); + + // Normalizing the transformed X and Y, while keeping constant both Z and the + // vector's angle in the XY plane. This maintains the "slope" for the surface while + // appropriately rotating the normal for any anisotropic scaling that occurs. + // Here, we call scaling factor the number that must divide the transformed X and Y + // so that the normal's length remains equal to 1. + SkScalar scalingFactorSquared = + (SkScalarSquare(transformed.fX) + SkScalarSquare(transformed.fY)) + / (1.0f - SkScalarSquare(tempNorm.fZ)); + SkScalar invScalingFactor = SkScalarInvert(SkScalarSqrt(scalingFactorSquared)); + + output[i].fX = transformed.fX * invScalingFactor; + output[i].fY = transformed.fY * invScalingFactor; + output[i].fZ = tempNorm.fZ; + } else { + output[i] = {0.0f, 0.0f, tempNorm.fZ}; + output[i].normalize(); + } + + SkASSERT(SkScalarNearlyEqual(output[i].length(), 1.0f)); + } + + output += n; + x += n; + count -= n; + } while (count > 0); +} + +//////////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalMapSourceImpl::CreateProc(SkReadBuffer& buf) { + + sk_sp mapShader = buf.readFlattenable(); + + SkMatrix invCTM; + buf.readMatrix(&invCTM); + + return sk_make_sp(std::move(mapShader), invCTM); +} + +void SkNormalMapSourceImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); + + buf.writeFlattenable(fMapShader.get()); + buf.writeMatrix(fInvCTM); +} + +//////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalSource::MakeFromNormalMap(sk_sp map, const SkMatrix& ctm) { + SkMatrix invCTM; + + if (!ctm.invert(&invCTM) || !map) { + return nullptr; + } + + return sk_make_sp(std::move(map), invCTM); +} diff --git a/gfx/skia/skia/src/core/SkNormalMapSource.h b/gfx/skia/skia/src/core/SkNormalMapSource.h new file mode 100644 index 0000000000..435cb0312a --- /dev/null +++ b/gfx/skia/skia/src/core/SkNormalMapSource.h @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNormalMapSource_DEFINED +#define SkNormalMapSource_DEFINED + +#include "src/core/SkNormalSource.h" + +class SkNormalMapSourceImpl : public SkNormalSource { +public: + SkNormalMapSourceImpl(sk_sp mapShader, const SkMatrix& invCTM) + : fMapShader(std::move(mapShader)) + , fInvCTM(invCTM) {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs& args) const override; +#endif + + SkNormalSource::Provider* asProvider(const SkShaderBase::ContextRec& rec, + SkArenaAlloc* alloc) const override; + +protected: + void flatten(SkWriteBuffer& buf) const override; + + bool computeNormTotalInverse(const SkShaderBase::ContextRec& rec, + SkMatrix* normTotalInverse) const; + +private: + SK_FLATTENABLE_HOOKS(SkNormalMapSourceImpl) + + class Provider : public SkNormalSource::Provider { + public: + Provider(const SkNormalMapSourceImpl& source, SkShaderBase::Context* mapContext); + + void fillScanLine(int x, int y, SkPoint3 output[], int count) const override; + + private: + const SkNormalMapSourceImpl& fSource; + SkShaderBase::Context* fMapContext; + + typedef SkNormalSource::Provider INHERITED; + }; + + sk_sp fMapShader; + SkMatrix fInvCTM; // Inverse of the canvas total matrix, used for rotating normals. + + friend class SkNormalSource; + + typedef SkNormalSource INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/core/SkNormalSource.cpp b/gfx/skia/skia/src/core/SkNormalSource.cpp new file mode 100644 index 0000000000..9bcab1e7dc --- /dev/null +++ b/gfx/skia/skia/src/core/SkNormalSource.cpp @@ -0,0 +1,19 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkNormalFlatSource.h" +#include "src/core/SkNormalMapSource.h" +#include "src/core/SkNormalSource.h" + +// Generating vtable +SkNormalSource::~SkNormalSource() {} + +void SkNormalSource::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkNormalMapSourceImpl); + SK_REGISTER_FLATTENABLE(SkNormalFlatSourceImpl); +} + diff --git a/gfx/skia/skia/src/core/SkNormalSource.h b/gfx/skia/skia/src/core/SkNormalSource.h new file mode 100644 index 0000000000..49a5257779 --- /dev/null +++ b/gfx/skia/skia/src/core/SkNormalSource.h @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNormalSource_DEFINED +#define SkNormalSource_DEFINED + +#include "include/core/SkFlattenable.h" +#include "src/shaders/SkShaderBase.h" + +class SkMatrix; +struct SkPoint3; + +#if SK_SUPPORT_GPU +class GrFragmentProcessor; +#endif + +/** Abstract class that generates or reads in normals for use by SkLightingShader. +*/ +class SK_API SkNormalSource : public SkFlattenable { +public: + virtual ~SkNormalSource() override; + +#if SK_SUPPORT_GPU + /** Returns a fragment processor that takes no input and outputs a normal (already rotated) + as its output color. To be used as a child fragment processor. + */ + virtual std::unique_ptr asFragmentProcessor(const GrFPArgs&) const = 0; +#endif + + class Provider { + public: + virtual ~Provider() {} + + /** Called for each span of the object being drawn on the CPU. Your subclass should set + the appropriate normals that correspond to the specified device coordinates. + */ + virtual void fillScanLine(int x, int y, SkPoint3 output[], int count) const = 0; + }; + + /** Returns an instance of 'Provider' that provides normals for the CPU pipeline. The + necessary data will be initialized in place at 'storage'. + */ + virtual Provider* asProvider(const SkShaderBase::ContextRec&, SkArenaAlloc*) const = 0; + + /** Returns a normal source that provides normals sourced from the the normal map argument. + + @param map a shader that outputs the normal map + @param ctm the current canvas' total matrix, used to rotate normals when necessary. + + nullptr will be returned if 'map' is null + + The normal map is currently assumed to be an 8888 image where the normal at a texel + is retrieved by: + N.x = R-127; + N.y = G-127; + N.z = B-127; + N.normalize(); + The +Z axis is thus encoded in RGB as (127, 127, 255) while the -Z axis is + (127, 127, 0). + */ + static sk_sp MakeFromNormalMap(sk_sp map, const SkMatrix& ctm); + + /** Returns a normal source that provides straight-up normals only <0, 0, 1>. + */ + static sk_sp MakeFlat(); + + static Type GetFlattenableType() { return kSkNormalSource_Type; } + Type getFlattenableType() const override { return GetFlattenableType(); } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize(GetFlattenableType(), data, size, procs).release())); + } + static void RegisterFlattenables(); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkOSFile.h b/gfx/skia/skia/src/core/SkOSFile.h new file mode 100644 index 0000000000..330b77bff4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkOSFile.h @@ -0,0 +1,99 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +// TODO: add unittests for all these operations + +#ifndef SkOSFile_DEFINED +#define SkOSFile_DEFINED + +#include + +#include "include/core/SkString.h" + +enum SkFILE_Flags { + kRead_SkFILE_Flag = 0x01, + kWrite_SkFILE_Flag = 0x02 +}; + +FILE* sk_fopen(const char path[], SkFILE_Flags); +void sk_fclose(FILE*); + +size_t sk_fgetsize(FILE*); + +size_t sk_fwrite(const void* buffer, size_t byteCount, FILE*); + +void sk_fflush(FILE*); +void sk_fsync(FILE*); + +size_t sk_ftell(FILE*); + +/** Maps a file into memory. Returns the address and length on success, NULL otherwise. + * The mapping is read only. + * When finished with the mapping, free the returned pointer with sk_fmunmap. + */ +void* sk_fmmap(FILE* f, size_t* length); + +/** Maps a file descriptor into memory. Returns the address and length on success, NULL otherwise. + * The mapping is read only. + * When finished with the mapping, free the returned pointer with sk_fmunmap. + */ +void* sk_fdmmap(int fd, size_t* length); + +/** Unmaps a file previously mapped by sk_fmmap or sk_fdmmap. + * The length parameter must be the same as returned from sk_fmmap. + */ +void sk_fmunmap(const void* addr, size_t length); + +/** Returns true if the two point at the exact same filesystem object. */ +bool sk_fidentical(FILE* a, FILE* b); + +/** Returns the underlying file descriptor for the given file. + * The return value will be < 0 on failure. + */ +int sk_fileno(FILE* f); + +/** Returns true if something (file, directory, ???) exists at this path, + * and has the specified access flags. + */ +bool sk_exists(const char *path, SkFILE_Flags = (SkFILE_Flags)0); + +// Returns true if a directory exists at this path. +bool sk_isdir(const char *path); + +// Like pread, but may affect the file position marker. +// Returns the number of bytes read or SIZE_MAX if failed. +size_t sk_qread(FILE*, void* buffer, size_t count, size_t offset); + + +// Create a new directory at this path; returns true if successful. +// If the directory already existed, this will return true. +// Description of the error, if any, will be written to stderr. +bool sk_mkdir(const char* path); + +class SkOSFile { +public: + class Iter { + public: + Iter(); + Iter(const char path[], const char suffix[] = nullptr); + ~Iter(); + + void reset(const char path[], const char suffix[] = nullptr); + /** If getDir is true, only returns directories. + Results are undefined if true and false calls are + interleaved on a single iterator. + */ + bool next(SkString* name, bool getDir = false); + + static const size_t kStorageSize = 40; + private: + SkAlignedSStorage fSelf; + }; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkOpts.cpp b/gfx/skia/skia/src/core/SkOpts.cpp new file mode 100644 index 0000000000..7228c30712 --- /dev/null +++ b/gfx/skia/skia/src/core/SkOpts.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkHalf.h" +#include "include/private/SkOnce.h" +#include "src/core/SkCpu.h" +#include "src/core/SkOpts.h" + +#if defined(SK_ARM_HAS_NEON) + #if defined(SK_ARM_HAS_CRC32) + #define SK_OPTS_NS neon_and_crc32 + #else + #define SK_OPTS_NS neon + #endif +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + #define SK_OPTS_NS avx2 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX + #define SK_OPTS_NS avx +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE42 + #define SK_OPTS_NS sse42 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + #define SK_OPTS_NS sse41 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + #define SK_OPTS_NS ssse3 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE3 + #define SK_OPTS_NS sse3 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #define SK_OPTS_NS sse2 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 + #define SK_OPTS_NS sse +#else + #define SK_OPTS_NS portable +#endif + +#include "src/opts/SkBitmapFilter_opts.h" +#include "src/opts/SkBitmapProcState_opts.h" +#include "src/opts/SkBlitMask_opts.h" +#include "src/opts/SkBlitRow_opts.h" +#include "src/opts/SkChecksum_opts.h" +#include "src/opts/SkRasterPipeline_opts.h" +#include "src/opts/SkSwizzler_opts.h" +#include "src/opts/SkUtils_opts.h" +#include "src/opts/SkXfermode_opts.h" + +#include "src/core/SkCubicSolver.h" + +namespace SkOpts { + // Define default function pointer values here... + // If our global compile options are set high enough, these defaults might even be + // CPU-specialized, e.g. a typical x86-64 machine might start with SSE2 defaults. + // They'll still get a chance to be replaced with even better ones, e.g. using SSE4.1. +#define DEFINE_DEFAULT(name) decltype(name) name = SK_OPTS_NS::name + DEFINE_DEFAULT(create_xfermode); + + DEFINE_DEFAULT(blit_mask_d32_a8); + + DEFINE_DEFAULT(blit_row_color32); + DEFINE_DEFAULT(blit_row_s32a_opaque); + + DEFINE_DEFAULT(RGBA_to_BGRA); + DEFINE_DEFAULT(RGBA_to_rgbA); + DEFINE_DEFAULT(RGBA_to_bgrA); + DEFINE_DEFAULT(RGB_to_RGB1); + DEFINE_DEFAULT(RGB_to_BGR1); + DEFINE_DEFAULT(gray_to_RGB1); + DEFINE_DEFAULT(grayA_to_RGBA); + DEFINE_DEFAULT(grayA_to_rgbA); + DEFINE_DEFAULT(inverted_CMYK_to_RGB1); + DEFINE_DEFAULT(inverted_CMYK_to_BGR1); + + DEFINE_DEFAULT(memset16); + DEFINE_DEFAULT(memset32); + DEFINE_DEFAULT(memset64); + + DEFINE_DEFAULT(rect_memset16); + DEFINE_DEFAULT(rect_memset32); + DEFINE_DEFAULT(rect_memset64); + + DEFINE_DEFAULT(cubic_solver); + + DEFINE_DEFAULT(hash_fn); + + DEFINE_DEFAULT(S32_alpha_D32_filter_DX); + + DEFINE_DEFAULT(convolve_vertically); + DEFINE_DEFAULT(convolve_horizontally); + DEFINE_DEFAULT(convolve_4_rows_horizontally); + +#undef DEFINE_DEFAULT + +#define M(st) (StageFn)SK_OPTS_NS::st, + StageFn stages_highp[] = { SK_RASTER_PIPELINE_STAGES(M) }; + StageFn just_return_highp = (StageFn)SK_OPTS_NS::just_return; + void (*start_pipeline_highp)(size_t,size_t,size_t,size_t,void**) + = SK_OPTS_NS::start_pipeline; +#undef M + +#define M(st) (StageFn)SK_OPTS_NS::lowp::st, + StageFn stages_lowp[] = { SK_RASTER_PIPELINE_STAGES(M) }; + StageFn just_return_lowp = (StageFn)SK_OPTS_NS::lowp::just_return; + void (*start_pipeline_lowp)(size_t,size_t,size_t,size_t,void**) + = SK_OPTS_NS::lowp::start_pipeline; +#undef M + + // Each Init_foo() is defined in src/opts/SkOpts_foo.cpp. + void Init_ssse3(); + void Init_sse41(); + void Init_sse42(); + void Init_avx(); + void Init_hsw(); + void Init_crc32(); + + static void init() { +#if !defined(SK_BUILD_NO_OPTS) + #if defined(SK_CPU_X86) + #if SK_CPU_SSE_LEVEL < SK_CPU_SSE_LEVEL_SSSE3 + if (SkCpu::Supports(SkCpu::SSSE3)) { Init_ssse3(); } + #endif + + #if SK_CPU_SSE_LEVEL < SK_CPU_SSE_LEVEL_SSE41 + if (SkCpu::Supports(SkCpu::SSE41)) { Init_sse41(); } + #endif + + #if SK_CPU_SSE_LEVEL < SK_CPU_SSE_LEVEL_SSE42 + if (SkCpu::Supports(SkCpu::SSE42)) { Init_sse42(); } + #endif + + #if SK_CPU_SSE_LEVEL < SK_CPU_SSE_LEVEL_AVX + if (SkCpu::Supports(SkCpu::AVX)) { Init_avx(); } + if (SkCpu::Supports(SkCpu::HSW)) { Init_hsw(); } + #endif + + if (SkCpu::Supports(SkCpu::HSW )) { Init_hsw(); } + + #elif defined(SK_CPU_ARM64) + if (SkCpu::Supports(SkCpu::CRC32)) { Init_crc32(); } + + #endif +#endif + } + + void Init() { + static SkOnce once; + once(init); + } +} // namespace SkOpts diff --git a/gfx/skia/skia/src/core/SkOpts.h b/gfx/skia/skia/src/core/SkOpts.h new file mode 100644 index 0000000000..2a717c0bf8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkOpts.h @@ -0,0 +1,90 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOpts_DEFINED +#define SkOpts_DEFINED + +#include "include/core/SkTypes.h" +#include "src/core/SkConvolver.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkXfermodePriv.h" + +struct SkBitmapProcState; + +namespace SkOpts { + // Call to replace pointers to portable functions with pointers to CPU-specific functions. + // Thread-safe and idempotent. + // Called by SkGraphics::Init(). + void Init(); + + // Declare function pointers here... + + // May return nullptr if we haven't specialized the given Mode. + extern SkXfermode* (*create_xfermode)(SkBlendMode); + + extern void (*blit_mask_d32_a8)(SkPMColor*, size_t, const SkAlpha*, size_t, SkColor, int, int); + extern void (*blit_row_color32)(SkPMColor*, const SkPMColor*, int, SkPMColor); + extern void (*blit_row_s32a_opaque)(SkPMColor*, const SkPMColor*, int, U8CPU); + + // Swizzle input into some sort of 8888 pixel, {premul,unpremul} x {rgba,bgra}. + typedef void (*Swizzle_8888_u32)(uint32_t*, const uint32_t*, int); + extern Swizzle_8888_u32 RGBA_to_BGRA, // i.e. just swap RB + RGBA_to_rgbA, // i.e. just premultiply + RGBA_to_bgrA, // i.e. swap RB and premultiply + inverted_CMYK_to_RGB1, // i.e. convert color space + inverted_CMYK_to_BGR1; // i.e. convert color space + + typedef void (*Swizzle_8888_u8)(uint32_t*, const uint8_t*, int); + extern Swizzle_8888_u8 RGB_to_RGB1, // i.e. insert an opaque alpha + RGB_to_BGR1, // i.e. swap RB and insert an opaque alpha + gray_to_RGB1, // i.e. expand to color channels + an opaque alpha + grayA_to_RGBA, // i.e. expand to color channels + grayA_to_rgbA; // i.e. expand to color channels and premultiply + + extern void (*memset16)(uint16_t[], uint16_t, int); + extern void SK_API (*memset32)(uint32_t[], uint32_t, int); + extern void (*memset64)(uint64_t[], uint64_t, int); + + extern void (*rect_memset16)(uint16_t[], uint16_t, int, size_t, int); + extern void (*rect_memset32)(uint32_t[], uint32_t, int, size_t, int); + extern void (*rect_memset64)(uint64_t[], uint64_t, int, size_t, int); + + extern float (*cubic_solver)(float, float, float, float); + + // The fastest high quality 32-bit hash we can provide on this platform. + extern uint32_t (*hash_fn)(const void*, size_t, uint32_t seed); + static inline uint32_t hash(const void* data, size_t bytes, uint32_t seed=0) { + return hash_fn(data, bytes, seed); + } + + // SkBitmapProcState optimized Shader, Sample, or Matrix procs. + // This is the only one that can use anything past SSE2/NEON. + extern void (*S32_alpha_D32_filter_DX)(const SkBitmapProcState&, + const uint32_t* xy, int count, SkPMColor*); + +#define M(st) +1 + // We can't necessarily express the type of SkJumper stage functions here, + // so we just use this void(*)(void) as a stand-in. + using StageFn = void(*)(void); + extern StageFn stages_highp[SK_RASTER_PIPELINE_STAGES(M)], just_return_highp; + extern StageFn stages_lowp [SK_RASTER_PIPELINE_STAGES(M)], just_return_lowp; + + extern void (*start_pipeline_highp)(size_t,size_t,size_t,size_t, void**); + extern void (*start_pipeline_lowp )(size_t,size_t,size_t,size_t, void**); +#undef M + + extern void (*convolve_vertically)(const SkConvolutionFilter1D::ConvolutionFixed* filter_values, + int filter_length, unsigned char* const* source_data_rows, + int pixel_width, unsigned char* out_row, bool has_alpha); + extern void (*convolve_4_rows_horizontally)(const unsigned char* src_data[4], + const SkConvolutionFilter1D& filter, + unsigned char* out_row[4], size_t out_row_bytes); + extern void (*convolve_horizontally)(const unsigned char* src_data, const SkConvolutionFilter1D& filter, + unsigned char* out_row, bool has_alpha); +} + +#endif//SkOpts_DEFINED diff --git a/gfx/skia/skia/src/core/SkOrderedReadBuffer.h b/gfx/skia/skia/src/core/SkOrderedReadBuffer.h new file mode 100644 index 0000000000..239d8b68c2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkOrderedReadBuffer.h @@ -0,0 +1,9 @@ +// Temporary shim to keep a couple dependencies working in Chromium. +#ifndef SkOrderedReadBuffer_DEFINED +#define SkOrderedReadBuffer_DEFINED + +#include "src/core/SkReadBuffer.h" + +typedef SkReadBuffer SkOrderedReadBuffer; + +#endif//SkOrderedReadBuffer_DEFINED diff --git a/gfx/skia/skia/src/core/SkOverdrawCanvas.cpp b/gfx/skia/skia/src/core/SkOverdrawCanvas.cpp new file mode 100644 index 0000000000..3a7aaeb4e7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkOverdrawCanvas.cpp @@ -0,0 +1,274 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkOverdrawCanvas.h" + +#include "include/core/SkColorFilter.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkPath.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRSXform.h" +#include "include/core/SkTextBlob.h" +#include "include/private/SkTo.h" +#include "src/core/SkDevice.h" +#include "src/core/SkDrawShadowInfo.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkLatticeIter.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/utils/SkPatchUtils.h" + +SkOverdrawCanvas::SkOverdrawCanvas(SkCanvas* canvas) + : INHERITED(canvas->onImageInfo().width(), canvas->onImageInfo().height()) +{ + // Non-drawing calls that SkOverdrawCanvas does not override (translate, save, etc.) + // will pass through to the input canvas. + this->addCanvas(canvas); + + static constexpr float kIncrementAlpha[] = { + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f/255, + }; + + fPaint.setAntiAlias(false); + fPaint.setBlendMode(SkBlendMode::kPlus); + fPaint.setColorFilter(SkColorFilters::Matrix(kIncrementAlpha)); +} + +namespace { +class TextDevice : public SkNoPixelsDevice, public SkGlyphRunListPainter::BitmapDevicePainter { +public: + TextDevice(SkCanvas* overdrawCanvas, const SkSurfaceProps& props) + : SkNoPixelsDevice{SkIRect::MakeWH(32767, 32767), props}, + fOverdrawCanvas{overdrawCanvas}, + fPainter{props, kN32_SkColorType, nullptr, SkStrikeCache::GlobalStrikeCache()} {} + + void paintPaths(SkDrawableGlyphBuffer*, SkScalar scale, const SkPaint& paint) const override {} + + void paintMasks(SkDrawableGlyphBuffer* drawables, const SkPaint& paint) const override { + for (auto t : drawables->drawable()) { + SkGlyphVariant glyph; SkPoint pos; + std::tie(glyph, pos) = t; + SkMask mask = glyph.glyph()->mask(pos); + fOverdrawCanvas->drawRect(SkRect::Make(mask.fBounds), SkPaint()); + } + } + +protected: + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override { + fPainter.drawForBitmapDevice(glyphRunList, fOverdrawCanvas->getTotalMatrix(), this); + } + +private: + SkCanvas* const fOverdrawCanvas; + SkGlyphRunListPainter fPainter; +}; +} // namespace + +void SkOverdrawCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + SkGlyphRunBuilder b; + SkSurfaceProps props{0, kUnknown_SkPixelGeometry}; + this->getProps(&props); + TextDevice device{this, props}; + + b.drawTextBlob(paint, *blob, {x, y}, &device); +} + +void SkOverdrawCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode blendMode, + const SkPaint&) { + fList[0]->onDrawPatch(cubics, colors, texCoords, blendMode, fPaint); +} + +void SkOverdrawCanvas::onDrawPaint(const SkPaint& paint) { + if (0 == paint.getColor() && !paint.getColorFilter() && !paint.getShader()) { + // This is a clear, ignore it. + } else { + fList[0]->onDrawPaint(this->overdrawPaint(paint)); + } +} + +void SkOverdrawCanvas::onDrawBehind(const SkPaint& paint) { + fList[0]->onDrawBehind(this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) { + fList[0]->onDrawRect(rect, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + fList[0]->onDrawRegion(region, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) { + fList[0]->onDrawOval(oval, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawArc(const SkRect& arc, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) { + fList[0]->onDrawArc(arc, startAngle, sweepAngle, useCenter, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + fList[0]->onDrawDRRect(outer, inner, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawRRect(const SkRRect& rect, const SkPaint& paint) { + fList[0]->onDrawRRect(rect, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint points[], + const SkPaint& paint) { + fList[0]->onDrawPoints(mode, count, points, this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawVerticesObject(const SkVertices* vertices, + const SkVertices::Bone bones[], int boneCount, + SkBlendMode blendMode, const SkPaint& paint) { + fList[0]->onDrawVerticesObject(vertices, + bones, + boneCount, + blendMode, + this->overdrawPaint(paint)); +} + +void SkOverdrawCanvas::onDrawAtlas(const SkImage* image, const SkRSXform xform[], + const SkRect texs[], const SkColor colors[], int count, + SkBlendMode mode, const SkRect* cull, const SkPaint* paint) { + SkPaint* paintPtr = &fPaint; + SkPaint storage; + if (paint) { + storage = this->overdrawPaint(*paint); + paintPtr = &storage; + } + + fList[0]->onDrawAtlas(image, xform, texs, colors, count, mode, cull, paintPtr); +} + +void SkOverdrawCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { + fList[0]->onDrawPath(path, fPaint); +} + +void SkOverdrawCanvas::onDrawImage(const SkImage* image, SkScalar x, SkScalar y, const SkPaint*) { + fList[0]->onDrawRect(SkRect::MakeXYWH(x, y, image->width(), image->height()), fPaint); +} + +void SkOverdrawCanvas::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) { + fList[0]->onDrawRect(dst, fPaint); +} + +void SkOverdrawCanvas::onDrawImageNine(const SkImage*, const SkIRect&, const SkRect& dst, + const SkPaint*) { + fList[0]->onDrawRect(dst, fPaint); +} + +void SkOverdrawCanvas::onDrawImageLattice(const SkImage* image, const Lattice& lattice, + const SkRect& dst, const SkPaint*) { + SkIRect bounds; + Lattice latticePlusBounds = lattice; + if (!latticePlusBounds.fBounds) { + bounds = SkIRect::MakeWH(image->width(), image->height()); + latticePlusBounds.fBounds = &bounds; + } + + if (SkLatticeIter::Valid(image->width(), image->height(), latticePlusBounds)) { + SkLatticeIter iter(latticePlusBounds, dst); + + SkRect dummy, iterDst; + while (iter.next(&dummy, &iterDst)) { + fList[0]->onDrawRect(iterDst, fPaint); + } + } else { + fList[0]->onDrawRect(dst, fPaint); + } +} + +void SkOverdrawCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y, + const SkPaint*) { + fList[0]->onDrawRect(SkRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()), fPaint); +} + +void SkOverdrawCanvas::onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect& dst, + const SkPaint*, SrcRectConstraint) { + fList[0]->onDrawRect(dst, fPaint); +} + +void SkOverdrawCanvas::onDrawBitmapNine(const SkBitmap&, const SkIRect&, const SkRect& dst, + const SkPaint*) { + fList[0]->onDrawRect(dst, fPaint); +} + +void SkOverdrawCanvas::onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + sk_sp image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); + this->onDrawImageLattice(image.get(), lattice, dst, paint); +} + +void SkOverdrawCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { + drawable->draw(this, matrix); +} + +void SkOverdrawCanvas::onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) { + SkASSERT(false); + return; +} + +void SkOverdrawCanvas::onDrawAnnotation(const SkRect&, const char[], SkData*) {} + +void SkOverdrawCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + SkRect bounds; + SkDrawShadowMetrics::GetLocalBounds(path, rec, this->getTotalMatrix(), &bounds); + fList[0]->onDrawRect(bounds, fPaint); +} + +void SkOverdrawCanvas::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + QuadAAFlags aa, const SkColor4f& color, SkBlendMode mode) { + if (clip) { + SkPath path; + path.addPoly(clip, 4, true); + fList[0]->onDrawPath(path, fPaint); + } else { + fList[0]->onDrawRect(rect, fPaint); + } +} + +void SkOverdrawCanvas::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count, + const SkPoint dstClips[], + const SkMatrix preViewMatrices[], const SkPaint* paint, + SrcRectConstraint constraint) { + int clipIndex = 0; + for (int i = 0; i < count; ++i) { + if (set[i].fMatrixIndex >= 0) { + fList[0]->save(); + fList[0]->concat(preViewMatrices[set[i].fMatrixIndex]); + } + if (set[i].fHasClip) { + SkPath path; + path.addPoly(dstClips + clipIndex, 4, true); + clipIndex += 4; + fList[0]->onDrawPath(path, fPaint); + } else { + fList[0]->onDrawRect(set[i].fDstRect, fPaint); + } + if (set[i].fMatrixIndex >= 0) { + fList[0]->restore(); + } + } +} + +inline SkPaint SkOverdrawCanvas::overdrawPaint(const SkPaint& paint) { + SkPaint newPaint = fPaint; + newPaint.setStyle(paint.getStyle()); + newPaint.setStrokeWidth(paint.getStrokeWidth()); + return newPaint; +} diff --git a/gfx/skia/skia/src/core/SkPaint.cpp b/gfx/skia/skia/src/core/SkPaint.cpp new file mode 100644 index 0000000000..f294dbe4a0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPaint.cpp @@ -0,0 +1,552 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" + +#include "include/core/SkColorFilter.h" +#include "include/core/SkData.h" +#include "include/core/SkGraphics.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkScalar.h" +#include "include/core/SkShader.h" +#include "include/core/SkStrokeRec.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTo.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkDraw.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkOpts.h" +#include "src/core/SkPaintDefaults.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSafeRange.h" +#include "src/core/SkStringUtils.h" +#include "src/core/SkStroke.h" +#include "src/core/SkSurfacePriv.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkShaderBase.h" + +// define this to get a printf for out-of-range parameter in setters +// e.g. setTextSize(-1) +//#define SK_REPORT_API_RANGE_CHECK + + +SkPaint::SkPaint() + : fColor4f{0, 0, 0, 1} // opaque black + , fWidth{0} + , fMiterLimit{SkPaintDefaults_MiterLimit} + , fBitfields{(unsigned)false, // fAntiAlias + (unsigned)false, // fDither + (unsigned)SkPaint::kDefault_Cap, // fCapType + (unsigned)SkPaint::kDefault_Join, // fJoinType + (unsigned)SkPaint::kFill_Style, // fStyle + (unsigned)kNone_SkFilterQuality, // fFilterQuality + (unsigned)SkBlendMode::kSrcOver, // fBlendMode + 0} // fPadding +{ + static_assert(sizeof(fBitfields) == sizeof(fBitfieldsUInt), ""); +} + +SkPaint::SkPaint(const SkColor4f& color, SkColorSpace* colorSpace) : SkPaint() { + this->setColor(color, colorSpace); +} + +SkPaint::SkPaint(const SkPaint& src) = default; + +SkPaint::SkPaint(SkPaint&& src) = default; + +SkPaint::~SkPaint() = default; + +SkPaint& SkPaint::operator=(const SkPaint& src) = default; + +SkPaint& SkPaint::operator=(SkPaint&& src) = default; + +bool operator==(const SkPaint& a, const SkPaint& b) { +#define EQUAL(field) (a.field == b.field) + return EQUAL(fPathEffect) + && EQUAL(fShader) + && EQUAL(fMaskFilter) + && EQUAL(fColorFilter) + && EQUAL(fImageFilter) + && EQUAL(fColor4f) + && EQUAL(fWidth) + && EQUAL(fMiterLimit) + && EQUAL(fBitfieldsUInt) + ; +#undef EQUAL +} + +#define DEFINE_REF_FOO(type) sk_sp SkPaint::ref##type() const { return f##type; } +DEFINE_REF_FOO(ColorFilter) +DEFINE_REF_FOO(ImageFilter) +DEFINE_REF_FOO(MaskFilter) +DEFINE_REF_FOO(PathEffect) +DEFINE_REF_FOO(Shader) +#undef DEFINE_REF_FOO + +void SkPaint::reset() { *this = SkPaint(); } + +void SkPaint::setFilterQuality(SkFilterQuality quality) { + fBitfields.fFilterQuality = quality; +} + +void SkPaint::setStyle(Style style) { + if ((unsigned)style < kStyleCount) { + fBitfields.fStyle = style; + } else { +#ifdef SK_REPORT_API_RANGE_CHECK + SkDebugf("SkPaint::setStyle(%d) out of range\n", style); +#endif + } +} + +void SkPaint::setColor(SkColor color) { + fColor4f = SkColor4f::FromColor(color); +} + +void SkPaint::setColor(const SkColor4f& color, SkColorSpace* colorSpace) { + SkASSERT(fColor4f.fA >= 0 && fColor4f.fA <= 1.0f); + + SkColorSpaceXformSteps steps{colorSpace, kUnpremul_SkAlphaType, + sk_srgb_singleton(), kUnpremul_SkAlphaType}; + fColor4f = color; + steps.apply(fColor4f.vec()); +} + +void SkPaint::setAlphaf(float a) { + SkASSERT(a >= 0 && a <= 1.0f); + fColor4f.fA = a; +} + +void SkPaint::setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + this->setColor(SkColorSetARGB(a, r, g, b)); +} + +void SkPaint::setStrokeWidth(SkScalar width) { + if (width >= 0) { + fWidth = width; + } else { +#ifdef SK_REPORT_API_RANGE_CHECK + SkDebugf("SkPaint::setStrokeWidth() called with negative value\n"); +#endif + } +} + +void SkPaint::setStrokeMiter(SkScalar limit) { + if (limit >= 0) { + fMiterLimit = limit; + } else { +#ifdef SK_REPORT_API_RANGE_CHECK + SkDebugf("SkPaint::setStrokeMiter() called with negative value\n"); +#endif + } +} + +void SkPaint::setStrokeCap(Cap ct) { + if ((unsigned)ct < kCapCount) { + fBitfields.fCapType = SkToU8(ct); + } else { +#ifdef SK_REPORT_API_RANGE_CHECK + SkDebugf("SkPaint::setStrokeCap(%d) out of range\n", ct); +#endif + } +} + +void SkPaint::setStrokeJoin(Join jt) { + if ((unsigned)jt < kJoinCount) { + fBitfields.fJoinType = SkToU8(jt); + } else { +#ifdef SK_REPORT_API_RANGE_CHECK + SkDebugf("SkPaint::setStrokeJoin(%d) out of range\n", jt); +#endif + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#define MOVE_FIELD(Field) void SkPaint::set##Field(sk_sp f) { f##Field = std::move(f); } +MOVE_FIELD(ImageFilter) +MOVE_FIELD(Shader) +MOVE_FIELD(ColorFilter) +MOVE_FIELD(PathEffect) +MOVE_FIELD(MaskFilter) +#undef MOVE_FIELD + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkStream.h" + +#ifdef SK_DEBUG + static void ASSERT_FITS_IN(uint32_t value, int bitCount) { + SkASSERT(bitCount > 0 && bitCount <= 32); + uint32_t mask = ~0U; + mask >>= (32 - bitCount); + SkASSERT(0 == (value & ~mask)); + } +#else + #define ASSERT_FITS_IN(value, bitcount) +#endif + +enum FlatFlags { + kHasTypeface_FlatFlag = 0x1, + kHasEffects_FlatFlag = 0x2, + + kFlatFlagMask = 0x3, +}; + +enum BitsPerField { + kFlags_BPF = 16, + kHint_BPF = 2, + kFilter_BPF = 2, + kFlatFlags_BPF = 3, +}; + +static inline int BPF_Mask(int bits) { + return (1 << bits) - 1; +} + +// SkPaint originally defined flags, some of which now apply to SkFont. These are renames +// of those flags, split into categories depending on which objects they (now) apply to. + +enum PaintFlagsForPaint { + kAA_PaintFlagForPaint = 0x01, + kDither_PaintFlagForPaint = 0x04, +}; + +enum PaintFlagsForFont { + kFakeBold_PaintFlagForFont = 0x20, + kLinear_PaintFlagForFont = 0x40, + kSubpixel_PaintFlagForFont = 0x80, + kLCD_PaintFlagForFont = 0x200, + kEmbeddedBitmap_PaintFlagForFont = 0x400, + kAutoHinting_PaintFlagForFont = 0x800, +}; + +static FlatFlags unpack_paint_flags(SkPaint* paint, uint32_t packed, SkFont* font) { + uint32_t f = packed >> 16; + paint->setAntiAlias((f & kAA_PaintFlagForPaint) != 0); + paint->setDither((f & kDither_PaintFlagForPaint) != 0); + if (font) { + font->setEmbolden((f & kFakeBold_PaintFlagForFont) != 0); + font->setLinearMetrics((f & kLinear_PaintFlagForFont) != 0); + font->setSubpixel((f & kSubpixel_PaintFlagForFont) != 0); + font->setEmbeddedBitmaps((f & kEmbeddedBitmap_PaintFlagForFont) != 0); + font->setForceAutoHinting((f & kAutoHinting_PaintFlagForFont) != 0); + + font->setHinting((SkFontHinting)((packed >> 14) & BPF_Mask(kHint_BPF))); + + if (f & kAA_PaintFlagForPaint) { + if (f & kLCD_PaintFlagForFont) { + font->setEdging(SkFont::Edging::kSubpixelAntiAlias); + } else { + font->setEdging(SkFont::Edging::kAntiAlias); + } + } else { + font->setEdging(SkFont::Edging::kAlias); + } + } + + paint->setFilterQuality((SkFilterQuality)((packed >> 10) & BPF_Mask(kFilter_BPF))); + return (FlatFlags)(packed & kFlatFlagMask); +} + +template uint32_t shift_bits(T value, unsigned shift, unsigned bits) { + SkASSERT(shift + bits <= 32); + uint32_t v = static_cast(value); + ASSERT_FITS_IN(v, bits); + return v << shift; +} + +/* Packing the paint + flags : 8 // 2... + blend : 8 // 30+ + cap : 2 // 3 + join : 2 // 3 + style : 2 // 3 + filter: 2 // 4 + flat : 8 // 1... + total : 32 + */ +static uint32_t pack_v68(const SkPaint& paint, unsigned flatFlags) { + uint32_t packed = 0; + packed |= shift_bits(((unsigned)paint.isDither() << 1) | + (unsigned)paint.isAntiAlias(), 0, 8); + packed |= shift_bits(paint.getBlendMode(), 8, 8); + packed |= shift_bits(paint.getStrokeCap(), 16, 2); + packed |= shift_bits(paint.getStrokeJoin(), 18, 2); + packed |= shift_bits(paint.getStyle(), 20, 2); + packed |= shift_bits(paint.getFilterQuality(), 22, 2); + packed |= shift_bits(flatFlags, 24, 8); + return packed; +} + +static uint32_t unpack_v68(SkPaint* paint, uint32_t packed, SkSafeRange& safe) { + paint->setAntiAlias((packed & 1) != 0); + paint->setDither((packed & 2) != 0); + packed >>= 8; + paint->setBlendMode(safe.checkLE(packed & 0xFF, SkBlendMode::kLastMode)); + packed >>= 8; + paint->setStrokeCap(safe.checkLE(packed & 0x3, SkPaint::kLast_Cap)); + packed >>= 2; + paint->setStrokeJoin(safe.checkLE(packed & 0x3, SkPaint::kLast_Join)); + packed >>= 2; + paint->setStyle(safe.checkLE(packed & 0x3, SkPaint::kStrokeAndFill_Style)); + packed >>= 2; + paint->setFilterQuality(safe.checkLE(packed & 0x3, kLast_SkFilterQuality)); + packed >>= 2; + return packed; +} + +/* To save space/time, we analyze the paint, and write a truncated version of + it if there are not tricky elements like shaders, etc. + */ +void SkPaintPriv::Flatten(const SkPaint& paint, SkWriteBuffer& buffer) { + uint8_t flatFlags = 0; + + if (paint.getPathEffect() || + paint.getShader() || + paint.getMaskFilter() || + paint.getColorFilter() || + paint.getImageFilter()) { + flatFlags |= kHasEffects_FlatFlag; + } + + buffer.writeScalar(paint.getStrokeWidth()); + buffer.writeScalar(paint.getStrokeMiter()); + buffer.writeColor4f(paint.getColor4f()); + + buffer.write32(pack_v68(paint, flatFlags)); + + if (flatFlags & kHasEffects_FlatFlag) { + buffer.writeFlattenable(paint.getPathEffect()); + buffer.writeFlattenable(paint.getShader()); + buffer.writeFlattenable(paint.getMaskFilter()); + buffer.writeFlattenable(paint.getColorFilter()); + buffer.write32(0); // legacy, was drawlooper + buffer.writeFlattenable(paint.getImageFilter()); + } +} + +SkReadPaintResult SkPaintPriv::Unflatten_PreV68(SkPaint* paint, SkReadBuffer& buffer, SkFont* font) { + SkSafeRange safe; + + { + SkScalar sz = buffer.readScalar(); + SkScalar sx = buffer.readScalar(); + SkScalar kx = buffer.readScalar(); + if (font) { + font->setSize(sz); + font->setScaleX(sx); + font->setSkewX(kx); + } + } + + paint->setStrokeWidth(buffer.readScalar()); + paint->setStrokeMiter(buffer.readScalar()); + if (buffer.isVersionLT(SkPicturePriv::kFloat4PaintColor_Version)) { + paint->setColor(buffer.readColor()); + } else { + SkColor4f color; + buffer.readColor4f(&color); + paint->setColor(color, sk_srgb_singleton()); + } + + unsigned flatFlags = unpack_paint_flags(paint, buffer.readUInt(), font); + + uint32_t tmp = buffer.readUInt(); + paint->setStrokeCap(safe.checkLE((tmp >> 24) & 0xFF, SkPaint::kLast_Cap)); + paint->setStrokeJoin(safe.checkLE((tmp >> 16) & 0xFF, SkPaint::kLast_Join)); + paint->setStyle(safe.checkLE((tmp >> 12) & 0xF, SkPaint::kStrokeAndFill_Style)); + paint->setBlendMode(safe.checkLE(tmp & 0xFF, SkBlendMode::kLastMode)); + + sk_sp tf; + if (flatFlags & kHasTypeface_FlatFlag) { + tf = buffer.readTypeface(); + } + if (font) { + font->setTypeface(tf); + } + + if (flatFlags & kHasEffects_FlatFlag) { + paint->setPathEffect(buffer.readPathEffect()); + paint->setShader(buffer.readShader()); + paint->setMaskFilter(buffer.readMaskFilter()); + paint->setColorFilter(buffer.readColorFilter()); + (void)buffer.read32(); // use to be SkRasterizer + (void)buffer.read32(); // used to be drawlooper + paint->setImageFilter(buffer.readImageFilter()); + } else { + paint->setPathEffect(nullptr); + paint->setShader(nullptr); + paint->setMaskFilter(nullptr); + paint->setColorFilter(nullptr); + paint->setImageFilter(nullptr); + } + + if (!buffer.validate(safe)) { + paint->reset(); + return kFailed_ReadPaint; + } + return kSuccess_PaintAndFont; +} + +SkReadPaintResult SkPaintPriv::Unflatten(SkPaint* paint, SkReadBuffer& buffer, SkFont* font) { + if (buffer.isVersionLT(SkPicturePriv::kPaintDoesntSerializeFonts_Version)) { + return Unflatten_PreV68(paint, buffer, font); + } + + SkSafeRange safe; + + paint->setStrokeWidth(buffer.readScalar()); + paint->setStrokeMiter(buffer.readScalar()); + { + SkColor4f color; + buffer.readColor4f(&color); + paint->setColor(color, sk_srgb_singleton()); + } + + unsigned flatFlags = unpack_v68(paint, buffer.readUInt(), safe); + + if (flatFlags & kHasEffects_FlatFlag) { + paint->setPathEffect(buffer.readPathEffect()); + paint->setShader(buffer.readShader()); + paint->setMaskFilter(buffer.readMaskFilter()); + paint->setColorFilter(buffer.readColorFilter()); + (void)buffer.readDrawLooper(); + paint->setImageFilter(buffer.readImageFilter()); + } else { + paint->setPathEffect(nullptr); + paint->setShader(nullptr); + paint->setMaskFilter(nullptr); + paint->setColorFilter(nullptr); + paint->setImageFilter(nullptr); + } + + if (!buffer.validate(safe)) { + paint->reset(); + return kFailed_ReadPaint; + } + return kSuccess_JustPaint; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkPaint::getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect, + SkScalar resScale) const { + if (!src.isFinite()) { + dst->reset(); + return false; + } + + SkStrokeRec rec(*this, resScale); + + const SkPath* srcPtr = &src; + SkPath tmpPath; + + if (fPathEffect && fPathEffect->filterPath(&tmpPath, src, &rec, cullRect)) { + srcPtr = &tmpPath; + } + + if (!rec.applyToPath(dst, *srcPtr)) { + if (srcPtr == &tmpPath) { + // If path's were copy-on-write, this trick would not be needed. + // As it is, we want to save making a deep-copy from tmpPath -> dst + // since we know we're just going to delete tmpPath when we return, + // so the swap saves that copy. + dst->swap(tmpPath); + } else { + *dst = *srcPtr; + } + } + + if (!dst->isFinite()) { + dst->reset(); + return false; + } + return !rec.isHairlineStyle(); +} + +bool SkPaint::canComputeFastBounds() const { + if (this->getImageFilter() && !this->getImageFilter()->canComputeFastBounds()) { + return false; + } + return true; +} + +const SkRect& SkPaint::doComputeFastBounds(const SkRect& origSrc, + SkRect* storage, + Style style) const { + SkASSERT(storage); + + const SkRect* src = &origSrc; + + SkRect tmpSrc; + if (this->getPathEffect()) { + this->getPathEffect()->computeFastBounds(&tmpSrc, origSrc); + src = &tmpSrc; + } + + SkScalar radius = SkStrokeRec::GetInflationRadius(*this, style); + *storage = src->makeOutset(radius, radius); + + if (this->getMaskFilter()) { + as_MFB(this->getMaskFilter())->computeFastBounds(*storage, storage); + } + + if (this->getImageFilter()) { + *storage = this->getImageFilter()->computeFastBounds(*storage); + } + + return *storage; +} + +/////////////////////////////////////////////////////////////////////////////// + +// return true if the filter exists, and may affect alpha +static bool affects_alpha(const SkColorFilter* cf) { + return cf && !(cf->getFlags() & SkColorFilter::kAlphaUnchanged_Flag); +} + +// return true if the filter exists, and may affect alpha +static bool affects_alpha(const SkImageFilter* imf) { + // TODO: check if we should allow imagefilters to broadcast that they don't affect alpha + // ala colorfilters + return imf != nullptr; +} + +bool SkPaint::nothingToDraw() const { + switch (this->getBlendMode()) { + case SkBlendMode::kSrcOver: + case SkBlendMode::kSrcATop: + case SkBlendMode::kDstOut: + case SkBlendMode::kDstOver: + case SkBlendMode::kPlus: + if (0 == this->getAlpha()) { + return !affects_alpha(fColorFilter.get()) && !affects_alpha(fImageFilter.get()); + } + break; + case SkBlendMode::kDst: + return true; + default: + break; + } + return false; +} + +uint32_t SkPaint::getHash() const { + // We're going to hash 5 pointers and 6 floats, finishing up with fBitfields, + // so fBitfields should be 5 pointers and 6 floats from the start. + static_assert(offsetof(SkPaint, fBitfieldsUInt) == 5 * sizeof(void*) + 6 * sizeof(float), + "SkPaint_notPackedTightly"); + return SkOpts::hash(reinterpret_cast(this), + offsetof(SkPaint, fBitfieldsUInt) + sizeof(fBitfieldsUInt)); +} diff --git a/gfx/skia/skia/src/core/SkPaintDefaults.h b/gfx/skia/skia/src/core/SkPaintDefaults.h new file mode 100644 index 0000000000..ce90fd1803 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPaintDefaults.h @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPaintDefaults_DEFINED +#define SkPaintDefaults_DEFINED + +#include "include/core/SkFontTypes.h" + +/** + * Any of these can be specified by the build system (or SkUserConfig.h) + * to change the default values for a SkPaint. This file should not be + * edited directly. + */ + +#ifndef SkPaintDefaults_TextSize + #define SkPaintDefaults_TextSize SkIntToScalar(12) +#endif + +#ifndef SkPaintDefaults_Hinting + #define SkPaintDefaults_Hinting SkFontHinting::kNormal +#endif + +#ifndef SkPaintDefaults_MiterLimit + #define SkPaintDefaults_MiterLimit SkIntToScalar(4) +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkPaintPriv.cpp b/gfx/skia/skia/src/core/SkPaintPriv.cpp new file mode 100644 index 0000000000..cce22aad79 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPaintPriv.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkPaint.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkXfermodePriv.h" +#include "src/shaders/SkShaderBase.h" + +static bool changes_alpha(const SkPaint& paint) { + SkColorFilter* cf = paint.getColorFilter(); + return cf && !(cf->getFlags() & SkColorFilter::kAlphaUnchanged_Flag); +} + +bool SkPaintPriv::Overwrites(const SkPaint* paint, ShaderOverrideOpacity overrideOpacity) { + if (!paint) { + // No paint means we default to SRC_OVER, so we overwrite iff our shader-override + // is opaque, or we don't have one. + return overrideOpacity != kNotOpaque_ShaderOverrideOpacity; + } + + SkXfermode::SrcColorOpacity opacityType = SkXfermode::kUnknown_SrcColorOpacity; + + if (!changes_alpha(*paint)) { + const unsigned paintAlpha = paint->getAlpha(); + if (0xff == paintAlpha && overrideOpacity != kNotOpaque_ShaderOverrideOpacity && + (!paint->getShader() || paint->getShader()->isOpaque())) + { + opacityType = SkXfermode::kOpaque_SrcColorOpacity; + } else if (0 == paintAlpha) { + if (overrideOpacity == kNone_ShaderOverrideOpacity && !paint->getShader()) { + opacityType = SkXfermode::kTransparentBlack_SrcColorOpacity; + } else { + opacityType = SkXfermode::kTransparentAlpha_SrcColorOpacity; + } + } + } + + return SkXfermode::IsOpaque(paint->getBlendMode(), opacityType); +} + +bool SkPaintPriv::ShouldDither(const SkPaint& p, SkColorType dstCT) { + // The paint dither flag can veto. + if (!p.isDither()) { + return false; + } + + // We always dither 565 or 4444 when requested. + if (dstCT == kRGB_565_SkColorType || dstCT == kARGB_4444_SkColorType) { + return true; + } + + // Otherwise, dither is only needed for non-const paints. + return p.getImageFilter() || p.getMaskFilter() + || !p.getShader() || !as_SB(p.getShader())->isConstant(); +} + +// return true if the paint is just a single color (i.e. not a shader). If its +// a shader, then we can't compute a const luminance for it :( +static bool just_a_color(const SkPaint& paint, SkColor* color) { + SkColor c = paint.getColor(); + + const auto* shader = as_SB(paint.getShader()); + if (shader && !shader->asLuminanceColor(&c)) { + return false; + } + if (paint.getColorFilter()) { + c = paint.getColorFilter()->filterColor(c); + } + if (color) { + *color = c; + } + return true; +} + +SkColor SkPaintPriv::ComputeLuminanceColor(const SkPaint& paint) { + SkColor c; + if (!just_a_color(paint, &c)) { + c = SkColorSetRGB(0x7F, 0x80, 0x7F); + } + return c; +} diff --git a/gfx/skia/skia/src/core/SkPaintPriv.h b/gfx/skia/skia/src/core/SkPaintPriv.h new file mode 100644 index 0000000000..1befb699d1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPaintPriv.h @@ -0,0 +1,74 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPaintPriv_DEFINED +#define SkPaintPriv_DEFINED + +#include "include/core/SkPaint.h" + +class SkFont; +class SkReadBuffer; +class SkWriteBuffer; + +enum SkReadPaintResult { + kFailed_ReadPaint, + kSuccess_JustPaint, + kSuccess_PaintAndFont, +}; + +class SkPaintPriv { +public: + enum ShaderOverrideOpacity { + kNone_ShaderOverrideOpacity, //!< there is no overriding shader (bitmap or image) + kOpaque_ShaderOverrideOpacity, //!< the overriding shader is opaque + kNotOpaque_ShaderOverrideOpacity, //!< the overriding shader may not be opaque + }; + + /** + * Returns true if drawing with this paint (or nullptr) will ovewrite all affected pixels. + * + * Note: returns conservative true, meaning it may return false even though the paint might + * in fact overwrite its pixels. + */ + static bool Overwrites(const SkPaint* paint, ShaderOverrideOpacity); + + static bool ShouldDither(const SkPaint&, SkColorType); + + /* + * The luminance color is used to determine which Gamma Canonical color to map to. This is + * really only used by backends which want to cache glyph masks, and need some way to know if + * they need to generate new masks based off a given color. + */ + static SkColor ComputeLuminanceColor(const SkPaint&); + + /** Serializes SkPaint into a buffer. A companion unflatten() call + can reconstitute the paint at a later time. + + @param buffer SkWriteBuffer receiving the flattened SkPaint data + */ + static void Flatten(const SkPaint& paint, SkWriteBuffer& buffer); + + /** Populates SkPaint, typically from a serialized stream, created by calling + flatten() at an earlier time. + + SkReadBuffer class is not public, so unflatten() cannot be meaningfully called + by the client. + + Older formats also stored font info in the serialized data. On success, this + returns if it deserialized just a paint, or both a font and paint. The font + param is optional. + + @param buffer serialized data describing SkPaint content + @return false if the buffer contains invalid data + */ + static SkReadPaintResult Unflatten(SkPaint* paint, SkReadBuffer& buffer, SkFont* font); + +private: + static SkReadPaintResult Unflatten_PreV68(SkPaint* paint, SkReadBuffer& buffer, SkFont*); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPath.cpp b/gfx/skia/skia/src/core/SkPath.cpp new file mode 100644 index 0000000000..43ce47a979 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPath.cpp @@ -0,0 +1,3739 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" + +#include "include/core/SkData.h" +#include "include/core/SkMath.h" +#include "include/core/SkRRect.h" +#include "include/private/SkMacros.h" +#include "include/private/SkPathRef.h" +#include "include/private/SkTo.h" +#include "src/core/SkBuffer.h" +#include "src/core/SkCubicClipper.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkPathMakers.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkTLazy.h" +// need SkDVector +#include "src/pathops/SkPathOpsPoint.h" + +#include +#include + +struct SkPath_Storage_Equivalent { + void* fPtr; + int32_t fIndex; + uint32_t fFlags; +}; + +static_assert(sizeof(SkPath) == sizeof(SkPath_Storage_Equivalent), + "Please keep an eye on SkPath packing."); + +static float poly_eval(float A, float B, float C, float t) { + return (A * t + B) * t + C; +} + +static float poly_eval(float A, float B, float C, float D, float t) { + return ((A * t + B) * t + C) * t + D; +} + +//////////////////////////////////////////////////////////////////////////// + +/** + * Path.bounds is defined to be the bounds of all the control points. + * If we called bounds.join(r) we would skip r if r was empty, which breaks + * our promise. Hence we have a custom joiner that doesn't look at emptiness + */ +static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) { + dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft); + dst->fTop = SkMinScalar(dst->fTop, src.fTop); + dst->fRight = SkMaxScalar(dst->fRight, src.fRight); + dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom); +} + +static bool is_degenerate(const SkPath& path) { + return path.countVerbs() <= 1; +} + +class SkAutoDisableDirectionCheck { +public: + SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) { + fSaved = static_cast(fPath->getFirstDirection()); + } + + ~SkAutoDisableDirectionCheck() { + fPath->setFirstDirection(fSaved); + } + +private: + SkPath* fPath; + SkPathPriv::FirstDirection fSaved; +}; +#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck) + +/* This guy's constructor/destructor bracket a path editing operation. It is + used when we know the bounds of the amount we are going to add to the path + (usually a new contour, but not required). + + It captures some state about the path up front (i.e. if it already has a + cached bounds), and then if it can, it updates the cache bounds explicitly, + avoiding the need to revisit all of the points in getBounds(). + + It also notes if the path was originally degenerate, and if so, sets + isConvex to true. Thus it can only be used if the contour being added is + convex. + */ +class SkAutoPathBoundsUpdate { +public: + SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fPath(path), fRect(r) { + // Cannot use fRect for our bounds unless we know it is sorted + fRect.sort(); + // Mark the path's bounds as dirty if (1) they are, or (2) the path + // is non-finite, and therefore its bounds are not meaningful + fHasValidBounds = path->hasComputedBounds() && path->isFinite(); + fEmpty = path->isEmpty(); + if (fHasValidBounds && !fEmpty) { + joinNoEmptyChecks(&fRect, fPath->getBounds()); + } + fDegenerate = is_degenerate(*path); + } + + ~SkAutoPathBoundsUpdate() { + fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity + : SkPath::kUnknown_Convexity); + if ((fEmpty || fHasValidBounds) && fRect.isFinite()) { + fPath->setBounds(fRect); + } + } + +private: + SkPath* fPath; + SkRect fRect; + bool fHasValidBounds; + bool fDegenerate; + bool fEmpty; +}; +#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate) + +//////////////////////////////////////////////////////////////////////////// + +/* + Stores the verbs and points as they are given to us, with exceptions: + - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic + - we insert a Move(0,0) if Line | Quad | Cubic is our first command + + The iterator does more cleanup, especially if forceClose == true + 1. If we encounter degenerate segments, remove them + 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt) + 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2 + 4. if we encounter Line | Quad | Cubic after Close, cons up a Move +*/ + +//////////////////////////////////////////////////////////////////////////// + +// flag to require a moveTo if we begin with something else, like lineTo etc. +#define INITIAL_LASTMOVETOINDEX_VALUE ~0 + +SkPath::SkPath() + : fPathRef(SkPathRef::CreateEmpty()) { + this->resetFields(); + fIsVolatile = false; +} + +void SkPath::resetFields() { + //fPathRef is assumed to have been emptied by the caller. + fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE; + fFillType = kWinding_FillType; + this->setConvexity(kUnknown_Convexity); + this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); + + // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we + // don't want to muck with it if it's been set to something non-nullptr. +} + +SkPath::SkPath(const SkPath& that) + : fPathRef(SkRef(that.fPathRef.get())) { + this->copyFields(that); + SkDEBUGCODE(that.validate();) +} + +SkPath::~SkPath() { + SkDEBUGCODE(this->validate();) +} + +SkPath& SkPath::operator=(const SkPath& that) { + SkDEBUGCODE(that.validate();) + + if (this != &that) { + fPathRef.reset(SkRef(that.fPathRef.get())); + this->copyFields(that); + } + SkDEBUGCODE(this->validate();) + return *this; +} + +void SkPath::copyFields(const SkPath& that) { + //fPathRef is assumed to have been set by the caller. + fLastMoveToIndex = that.fLastMoveToIndex; + fFillType = that.fFillType; + fIsVolatile = that.fIsVolatile; + + // Non-atomic assignment of atomic values. + this->setConvexity(that.getConvexityOrUnknown()); + this->setFirstDirection(that.getFirstDirection()); +} + +bool operator==(const SkPath& a, const SkPath& b) { + // note: don't need to look at isConvex or bounds, since just comparing the + // raw data is sufficient. + return &a == &b || + (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get()); +} + +void SkPath::swap(SkPath& that) { + if (this != &that) { + fPathRef.swap(that.fPathRef); + std::swap(fLastMoveToIndex, that.fLastMoveToIndex); + + const auto ft = fFillType; + fFillType = that.fFillType; + that.fFillType = ft; + + const auto iv = fIsVolatile; + fIsVolatile = that.fIsVolatile; + that.fIsVolatile = iv; + + // Non-atomic swaps of atomic values. + Convexity c = this->getConvexityOrUnknown(); + this->setConvexity(that.getConvexityOrUnknown()); + that.setConvexity(c); + + uint8_t fd = this->getFirstDirection(); + this->setFirstDirection(that.getFirstDirection()); + that.setFirstDirection(fd); + } +} + +bool SkPath::isInterpolatable(const SkPath& compare) const { + // need the same structure (verbs, conicweights) and same point-count + return fPathRef->fPoints.count() == compare.fPathRef->fPoints.count() && + fPathRef->fVerbs == compare.fPathRef->fVerbs && + fPathRef->fConicWeights == compare.fPathRef->fConicWeights; +} + +bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const { + int pointCount = fPathRef->countPoints(); + if (pointCount != ending.fPathRef->countPoints()) { + return false; + } + if (!pointCount) { + return true; + } + out->reset(); + out->addPath(*this); + fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get()); + return true; +} + +static inline bool check_edge_against_rect(const SkPoint& p0, + const SkPoint& p1, + const SkRect& rect, + SkPathPriv::FirstDirection dir) { + const SkPoint* edgeBegin; + SkVector v; + if (SkPathPriv::kCW_FirstDirection == dir) { + v = p1 - p0; + edgeBegin = &p0; + } else { + v = p0 - p1; + edgeBegin = &p1; + } + if (v.fX || v.fY) { + // check the cross product of v with the vec from edgeBegin to each rect corner + SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX); + SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY); + SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX); + SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY); + if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) { + return false; + } + } + return true; +} + +bool SkPath::conservativelyContainsRect(const SkRect& rect) const { + // This only handles non-degenerate convex paths currently. + if (kConvex_Convexity != this->getConvexity()) { + return false; + } + + SkPathPriv::FirstDirection direction; + if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) { + return false; + } + + SkPoint firstPt; + SkPoint prevPt; + SkPath::Iter iter(*this, true); + SkPath::Verb verb; + SkPoint pts[4]; + int segmentCount = 0; + SkDEBUGCODE(int moveCnt = 0;) + SkDEBUGCODE(int closeCount = 0;) + + while ((verb = iter.next(pts)) != kDone_Verb) { + int nextPt = -1; + switch (verb) { + case kMove_Verb: + SkASSERT(!segmentCount && !closeCount); + SkDEBUGCODE(++moveCnt); + firstPt = prevPt = pts[0]; + break; + case kLine_Verb: + if (!SkPathPriv::AllPointsEq(pts, 2)) { + nextPt = 1; + SkASSERT(moveCnt && !closeCount); + ++segmentCount; + } + break; + case kQuad_Verb: + case kConic_Verb: + if (!SkPathPriv::AllPointsEq(pts, 3)) { + SkASSERT(moveCnt && !closeCount); + ++segmentCount; + nextPt = 2; + } + break; + case kCubic_Verb: + if (!SkPathPriv::AllPointsEq(pts, 4)) { + SkASSERT(moveCnt && !closeCount); + ++segmentCount; + nextPt = 3; + } + break; + case kClose_Verb: + SkDEBUGCODE(++closeCount;) + break; + default: + SkDEBUGFAIL("unknown verb"); + } + if (-1 != nextPt) { + if (SkPath::kConic_Verb == verb) { + SkConic orig; + orig.set(pts, iter.conicWeight()); + SkPoint quadPts[5]; + int count = orig.chopIntoQuadsPOW2(quadPts, 1); + SkASSERT_RELEASE(2 == count); + + if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) { + return false; + } + if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) { + return false; + } + } else { + if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) { + return false; + } + } + prevPt = pts[nextPt]; + } + } + + if (segmentCount) { + return check_edge_against_rect(prevPt, firstPt, rect, direction); + } + return false; +} + +uint32_t SkPath::getGenerationID() const { + uint32_t genID = fPathRef->genID(); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt))); + genID |= static_cast(fFillType) << SkPathPriv::kPathRefGenIDBitCnt; +#endif + return genID; +} + +SkPath& SkPath::reset() { + SkDEBUGCODE(this->validate();) + + fPathRef.reset(SkPathRef::CreateEmpty()); + this->resetFields(); + return *this; +} + +SkPath& SkPath::rewind() { + SkDEBUGCODE(this->validate();) + + SkPathRef::Rewind(&fPathRef); + this->resetFields(); + return *this; +} + +bool SkPath::isLastContourClosed() const { + int verbCount = fPathRef->countVerbs(); + if (0 == verbCount) { + return false; + } + return kClose_Verb == fPathRef->atVerb(verbCount - 1); +} + +bool SkPath::isLine(SkPoint line[2]) const { + int verbCount = fPathRef->countVerbs(); + + if (2 == verbCount) { + SkASSERT(kMove_Verb == fPathRef->atVerb(0)); + if (kLine_Verb == fPathRef->atVerb(1)) { + SkASSERT(2 == fPathRef->countPoints()); + if (line) { + const SkPoint* pts = fPathRef->points(); + line[0] = pts[0]; + line[1] = pts[1]; + } + return true; + } + } + return false; +} + +/* + Determines if path is a rect by keeping track of changes in direction + and looking for a loop either clockwise or counterclockwise. + + The direction is computed such that: + 0: vertical up + 1: horizontal left + 2: vertical down + 3: horizontal right + +A rectangle cycles up/right/down/left or up/left/down/right. + +The test fails if: + The path is closed, and followed by a line. + A second move creates a new endpoint. + A diagonal line is parsed. + There's more than four changes of direction. + There's a discontinuity on the line (e.g., a move in the middle) + The line reverses direction. + The path contains a quadratic or cubic. + The path contains fewer than four points. + *The rectangle doesn't complete a cycle. + *The final point isn't equal to the first point. + + *These last two conditions we relax if we have a 3-edge path that would + form a rectangle if it were closed (as we do when we fill a path) + +It's OK if the path has: + Several colinear line segments composing a rectangle side. + Single points on the rectangle side. + +The direction takes advantage of the corners found since opposite sides +must travel in opposite directions. + +FIXME: Allow colinear quads and cubics to be treated like lines. +FIXME: If the API passes fill-only, return true if the filled stroke + is a rectangle, though the caller failed to close the path. + + directions values: + 0x1 is set if the segment is horizontal + 0x2 is set if the segment is moving to the right or down + thus: + two directions are opposites iff (dirA ^ dirB) == 0x2 + two directions are perpendicular iff (dirA ^ dirB) == 0x1 + + */ +static int rect_make_dir(SkScalar dx, SkScalar dy) { + return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1); +} + +bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const { + SkDEBUGCODE(this->validate();) + int currVerb = 0; + const SkPoint* pts = fPathRef->points(); + return SkPathPriv::IsRectContour(*this, false, &currVerb, &pts, isClosed, direction, rect); +} + +bool SkPath::isOval(SkRect* bounds) const { + return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr); +} + +bool SkPath::isRRect(SkRRect* rrect) const { + return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr); +} + +int SkPath::countPoints() const { + return fPathRef->countPoints(); +} + +int SkPath::getPoints(SkPoint dst[], int max) const { + SkDEBUGCODE(this->validate();) + + SkASSERT(max >= 0); + SkASSERT(!max || dst); + int count = SkMin32(max, fPathRef->countPoints()); + sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint)); + return fPathRef->countPoints(); +} + +SkPoint SkPath::getPoint(int index) const { + if ((unsigned)index < (unsigned)fPathRef->countPoints()) { + return fPathRef->atPoint(index); + } + return SkPoint::Make(0, 0); +} + +int SkPath::countVerbs() const { + return fPathRef->countVerbs(); +} + +int SkPath::getVerbs(uint8_t dst[], int max) const { + SkDEBUGCODE(this->validate();) + + SkASSERT(max >= 0); + SkASSERT(!max || dst); + int count = SkMin32(max, fPathRef->countVerbs()); + if (count) { + memcpy(dst, fPathRef->verbsBegin(), count); + } + return fPathRef->countVerbs(); +} + +size_t SkPath::approximateBytesUsed() const { + size_t size = sizeof (SkPath); + if (fPathRef != nullptr) { + size += fPathRef->countPoints() * sizeof(SkPoint) + + fPathRef->countVerbs() + + fPathRef->countWeights() * sizeof(SkScalar); + } + + return size; +} + +bool SkPath::getLastPt(SkPoint* lastPt) const { + SkDEBUGCODE(this->validate();) + + int count = fPathRef->countPoints(); + if (count > 0) { + if (lastPt) { + *lastPt = fPathRef->atPoint(count - 1); + } + return true; + } + if (lastPt) { + lastPt->set(0, 0); + } + return false; +} + +void SkPath::setPt(int index, SkScalar x, SkScalar y) { + SkDEBUGCODE(this->validate();) + + int count = fPathRef->countPoints(); + if (count <= index) { + return; + } else { + SkPathRef::Editor ed(&fPathRef); + ed.atPoint(index)->set(x, y); + } +} + +void SkPath::setLastPt(SkScalar x, SkScalar y) { + SkDEBUGCODE(this->validate();) + + int count = fPathRef->countPoints(); + if (count == 0) { + this->moveTo(x, y); + } else { + SkPathRef::Editor ed(&fPathRef); + ed.atPoint(count-1)->set(x, y); + } +} + +// This is the public-facing non-const setConvexity(). +void SkPath::setConvexity(Convexity c) { + fConvexity.store(c, std::memory_order_relaxed); +} + +// Const hooks for working with fConvexity and fFirstDirection from const methods. +void SkPath::setConvexity(Convexity c) const { + fConvexity.store(c, std::memory_order_relaxed); +} +void SkPath::setFirstDirection(uint8_t d) const { + fFirstDirection.store(d, std::memory_order_relaxed); +} +uint8_t SkPath::getFirstDirection() const { + return fFirstDirection.load(std::memory_order_relaxed); +} + +////////////////////////////////////////////////////////////////////////////// +// Construction methods + +#define DIRTY_AFTER_EDIT \ + do { \ + this->setConvexity(kUnknown_Convexity); \ + this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); \ + } while (0) + +void SkPath::incReserve(int inc) { + SkDEBUGCODE(this->validate();) + if (inc > 0) { + SkPathRef::Editor(&fPathRef, inc, inc); + } + SkDEBUGCODE(this->validate();) +} + +SkPath& SkPath::moveTo(SkScalar x, SkScalar y) { + SkDEBUGCODE(this->validate();) + + SkPathRef::Editor ed(&fPathRef); + + // remember our index + fLastMoveToIndex = fPathRef->countPoints(); + + ed.growForVerb(kMove_Verb)->set(x, y); + + DIRTY_AFTER_EDIT; + return *this; +} + +SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) { + SkPoint pt; + this->getLastPt(&pt); + return this->moveTo(pt.fX + x, pt.fY + y); +} + +void SkPath::injectMoveToIfNeeded() { + if (fLastMoveToIndex < 0) { + SkScalar x, y; + if (fPathRef->countVerbs() == 0) { + x = y = 0; + } else { + const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex); + x = pt.fX; + y = pt.fY; + } + this->moveTo(x, y); + } +} + +SkPath& SkPath::lineTo(SkScalar x, SkScalar y) { + SkDEBUGCODE(this->validate();) + + this->injectMoveToIfNeeded(); + + SkPathRef::Editor ed(&fPathRef); + ed.growForVerb(kLine_Verb)->set(x, y); + + DIRTY_AFTER_EDIT; + return *this; +} + +SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) { + this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). + SkPoint pt; + this->getLastPt(&pt); + return this->lineTo(pt.fX + x, pt.fY + y); +} + +SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { + SkDEBUGCODE(this->validate();) + + this->injectMoveToIfNeeded(); + + SkPathRef::Editor ed(&fPathRef); + SkPoint* pts = ed.growForVerb(kQuad_Verb); + pts[0].set(x1, y1); + pts[1].set(x2, y2); + + DIRTY_AFTER_EDIT; + return *this; +} + +SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { + this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). + SkPoint pt; + this->getLastPt(&pt); + return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2); +} + +SkPath& SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, + SkScalar w) { + // check for <= 0 or NaN with this test + if (!(w > 0)) { + this->lineTo(x2, y2); + } else if (!SkScalarIsFinite(w)) { + this->lineTo(x1, y1); + this->lineTo(x2, y2); + } else if (SK_Scalar1 == w) { + this->quadTo(x1, y1, x2, y2); + } else { + SkDEBUGCODE(this->validate();) + + this->injectMoveToIfNeeded(); + + SkPathRef::Editor ed(&fPathRef); + SkPoint* pts = ed.growForVerb(kConic_Verb, w); + pts[0].set(x1, y1); + pts[1].set(x2, y2); + + DIRTY_AFTER_EDIT; + } + return *this; +} + +SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2, + SkScalar w) { + this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). + SkPoint pt; + this->getLastPt(&pt); + return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w); +} + +SkPath& SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, + SkScalar x3, SkScalar y3) { + SkDEBUGCODE(this->validate();) + + this->injectMoveToIfNeeded(); + + SkPathRef::Editor ed(&fPathRef); + SkPoint* pts = ed.growForVerb(kCubic_Verb); + pts[0].set(x1, y1); + pts[1].set(x2, y2); + pts[2].set(x3, y3); + + DIRTY_AFTER_EDIT; + return *this; +} + +SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, + SkScalar x3, SkScalar y3) { + this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). + SkPoint pt; + this->getLastPt(&pt); + return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2, + pt.fX + x3, pt.fY + y3); +} + +SkPath& SkPath::close() { + SkDEBUGCODE(this->validate();) + + int count = fPathRef->countVerbs(); + if (count > 0) { + switch (fPathRef->atVerb(count - 1)) { + case kLine_Verb: + case kQuad_Verb: + case kConic_Verb: + case kCubic_Verb: + case kMove_Verb: { + SkPathRef::Editor ed(&fPathRef); + ed.growForVerb(kClose_Verb); + break; + } + case kClose_Verb: + // don't add a close if it's the first verb or a repeat + break; + default: + SkDEBUGFAIL("unexpected verb"); + break; + } + } + + // signal that we need a moveTo to follow us (unless we're done) +#if 0 + if (fLastMoveToIndex >= 0) { + fLastMoveToIndex = ~fLastMoveToIndex; + } +#else + fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1); +#endif + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// + +static void assert_known_direction(int dir) { + SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir); +} + +SkPath& SkPath::addRect(const SkRect& rect, Direction dir) { + return this->addRect(rect, dir, 0); +} + +SkPath& SkPath::addRect(SkScalar left, SkScalar top, SkScalar right, + SkScalar bottom, Direction dir) { + return this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0); +} + +SkPath& SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) { + assert_known_direction(dir); + this->setFirstDirection(this->hasOnlyMoveTos() ? (SkPathPriv::FirstDirection)dir + : SkPathPriv::kUnknown_FirstDirection); + SkAutoDisableDirectionCheck addc(this); + SkAutoPathBoundsUpdate apbu(this, rect); + + SkDEBUGCODE(int initialVerbCount = this->countVerbs()); + + const int kVerbs = 5; // moveTo + 3x lineTo + close + this->incReserve(kVerbs); + + SkPath_RectPointIterator iter(rect, dir, startIndex); + + this->moveTo(iter.current()); + this->lineTo(iter.next()); + this->lineTo(iter.next()); + this->lineTo(iter.next()); + this->close(); + + SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); + return *this; +} + +SkPath& SkPath::addPoly(const SkPoint pts[], int count, bool close) { + SkDEBUGCODE(this->validate();) + if (count <= 0) { + return *this; + } + + fLastMoveToIndex = fPathRef->countPoints(); + + // +close makes room for the extra kClose_Verb + SkPathRef::Editor ed(&fPathRef, count+close, count); + + ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY); + if (count > 1) { + SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1); + memcpy(p, &pts[1], (count-1) * sizeof(SkPoint)); + } + + if (close) { + ed.growForVerb(kClose_Verb); + fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1); + } + + DIRTY_AFTER_EDIT; + SkDEBUGCODE(this->validate();) + return *this; +} + +#include "src/core/SkGeometry.h" + +static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + SkPoint* pt) { + if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) { + // Chrome uses this path to move into and out of ovals. If not + // treated as a special case the moves can distort the oval's + // bounding box (and break the circle special case). + pt->set(oval.fRight, oval.centerY()); + return true; + } else if (0 == oval.width() && 0 == oval.height()) { + // Chrome will sometimes create 0 radius round rects. Having degenerate + // quad segments in the path prevents the path from being recognized as + // a rect. + // TODO: optimizing the case where only one of width or height is zero + // should also be considered. This case, however, doesn't seem to be + // as common as the single point case. + pt->set(oval.fRight, oval.fTop); + return true; + } + return false; +} + +// Return the unit vectors pointing at the start/stop points for the given start/sweep angles +// +static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle, + SkVector* startV, SkVector* stopV, SkRotationDirection* dir) { + SkScalar startRad = SkDegreesToRadians(startAngle), + stopRad = SkDegreesToRadians(startAngle + sweepAngle); + + startV->fY = SkScalarSinSnapToZero(startRad); + startV->fX = SkScalarCosSnapToZero(startRad); + stopV->fY = SkScalarSinSnapToZero(stopRad); + stopV->fX = SkScalarCosSnapToZero(stopRad); + + /* If the sweep angle is nearly (but less than) 360, then due to precision + loss in radians-conversion and/or sin/cos, we may end up with coincident + vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead + of drawing a nearly complete circle (good). + e.g. canvas.drawArc(0, 359.99, ...) + -vs- canvas.drawArc(0, 359.9, ...) + We try to detect this edge case, and tweak the stop vector + */ + if (*startV == *stopV) { + SkScalar sw = SkScalarAbs(sweepAngle); + if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) { + // make a guess at a tiny angle (in radians) to tweak by + SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle); + // not sure how much will be enough, so we use a loop + do { + stopRad -= deltaRad; + stopV->fY = SkScalarSinSnapToZero(stopRad); + stopV->fX = SkScalarCosSnapToZero(stopRad); + } while (*startV == *stopV); + } + } + *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection; +} + +/** + * If this returns 0, then the caller should just line-to the singlePt, else it should + * ignore singlePt and append the specified number of conics. + */ +static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop, + SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc], + SkPoint* singlePt) { + SkMatrix matrix; + + matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height())); + matrix.postTranslate(oval.centerX(), oval.centerY()); + + int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics); + if (0 == count) { + matrix.mapXY(stop.x(), stop.y(), singlePt); + } + return count; +} + +SkPath& SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[], + Direction dir) { + SkRRect rrect; + rrect.setRectRadii(rect, (const SkVector*) radii); + return this->addRRect(rrect, dir); +} + +SkPath& SkPath::addRRect(const SkRRect& rrect, Direction dir) { + // legacy start indices: 6 (CW) and 7(CCW) + return this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7); +} + +SkPath& SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) { + assert_known_direction(dir); + + bool isRRect = hasOnlyMoveTos(); + const SkRect& bounds = rrect.getBounds(); + + if (rrect.isRect() || rrect.isEmpty()) { + // degenerate(rect) => radii points are collapsing + this->addRect(bounds, dir, (startIndex + 1) / 2); + } else if (rrect.isOval()) { + // degenerate(oval) => line points are collapsing + this->addOval(bounds, dir, startIndex / 2); + } else { + this->setFirstDirection(this->hasOnlyMoveTos() ? (SkPathPriv::FirstDirection)dir + : SkPathPriv::kUnknown_FirstDirection); + + SkAutoPathBoundsUpdate apbu(this, bounds); + SkAutoDisableDirectionCheck addc(this); + + // we start with a conic on odd indices when moving CW vs. even indices when moving CCW + const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction)); + const SkScalar weight = SK_ScalarRoot2Over2; + + SkDEBUGCODE(int initialVerbCount = this->countVerbs()); + const int kVerbs = startsWithConic + ? 9 // moveTo + 4x conicTo + 3x lineTo + close + : 10; // moveTo + 4x lineTo + 4x conicTo + close + this->incReserve(kVerbs); + + SkPath_RRectPointIterator rrectIter(rrect, dir, startIndex); + // Corner iterator indices follow the collapsed radii model, + // adjusted such that the start pt is "behind" the radii start pt. + const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1); + SkPath_RectPointIterator rectIter(bounds, dir, rectStartIndex); + + this->moveTo(rrectIter.current()); + if (startsWithConic) { + for (unsigned i = 0; i < 3; ++i) { + this->conicTo(rectIter.next(), rrectIter.next(), weight); + this->lineTo(rrectIter.next()); + } + this->conicTo(rectIter.next(), rrectIter.next(), weight); + // final lineTo handled by close(). + } else { + for (unsigned i = 0; i < 4; ++i) { + this->lineTo(rrectIter.next()); + this->conicTo(rectIter.next(), rrectIter.next(), weight); + } + } + this->close(); + + SkPathRef::Editor ed(&fPathRef); + ed.setIsRRect(isRRect, dir, startIndex % 8); + + SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); + } + + SkDEBUGCODE(fPathRef->validate();) + return *this; +} + +bool SkPath::hasOnlyMoveTos() const { + int count = fPathRef->countVerbs(); + const uint8_t* verbs = fPathRef->verbsBegin(); + for (int i = 0; i < count; ++i) { + if (*verbs == kLine_Verb || + *verbs == kQuad_Verb || + *verbs == kConic_Verb || + *verbs == kCubic_Verb) { + return false; + } + ++verbs; + } + return true; +} + +bool SkPath::isZeroLengthSincePoint(int startPtIndex) const { + int count = fPathRef->countPoints() - startPtIndex; + if (count < 2) { + return true; + } + const SkPoint* pts = fPathRef.get()->points() + startPtIndex; + const SkPoint& first = *pts; + for (int index = 1; index < count; ++index) { + if (first != pts[index]) { + return false; + } + } + return true; +} + +SkPath& SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, + Direction dir) { + assert_known_direction(dir); + + if (rx < 0 || ry < 0) { + return *this; + } + + SkRRect rrect; + rrect.setRectXY(rect, rx, ry); + return this->addRRect(rrect, dir); +} + +SkPath& SkPath::addOval(const SkRect& oval, Direction dir) { + // legacy start index: 1 + return this->addOval(oval, dir, 1); +} + +SkPath& SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) { + assert_known_direction(dir); + + /* If addOval() is called after previous moveTo(), + this path is still marked as an oval. This is used to + fit into WebKit's calling sequences. + We can't simply check isEmpty() in this case, as additional + moveTo() would mark the path non empty. + */ + bool isOval = hasOnlyMoveTos(); + if (isOval) { + this->setFirstDirection((SkPathPriv::FirstDirection)dir); + } else { + this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); + } + + SkAutoDisableDirectionCheck addc(this); + SkAutoPathBoundsUpdate apbu(this, oval); + + SkDEBUGCODE(int initialVerbCount = this->countVerbs()); + const int kVerbs = 6; // moveTo + 4x conicTo + close + this->incReserve(kVerbs); + + SkPath_OvalPointIterator ovalIter(oval, dir, startPointIndex); + // The corner iterator pts are tracking "behind" the oval/radii pts. + SkPath_RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1)); + const SkScalar weight = SK_ScalarRoot2Over2; + + this->moveTo(ovalIter.current()); + for (unsigned i = 0; i < 4; ++i) { + this->conicTo(rectIter.next(), ovalIter.next(), weight); + } + this->close(); + + SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); + + SkPathRef::Editor ed(&fPathRef); + + ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4); + return *this; +} + +SkPath& SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) { + if (r > 0) { + this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir); + } + return *this; +} + +SkPath& SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool forceMoveTo) { + if (oval.width() < 0 || oval.height() < 0) { + return *this; + } + + if (fPathRef->countVerbs() == 0) { + forceMoveTo = true; + } + + SkPoint lonePt; + if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) { + return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt); + } + + SkVector startV, stopV; + SkRotationDirection dir; + angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir); + + SkPoint singlePt; + + // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently + // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous + // arcs from the same oval. + auto addPt = [&forceMoveTo, this](const SkPoint& pt) { + SkPoint lastPt; + if (forceMoveTo) { + this->moveTo(pt); + } else if (!this->getLastPt(&lastPt) || + !SkScalarNearlyEqual(lastPt.fX, pt.fX) || + !SkScalarNearlyEqual(lastPt.fY, pt.fY)) { + this->lineTo(pt); + } + }; + + // At this point, we know that the arc is not a lone point, but startV == stopV + // indicates that the sweepAngle is too small such that angles_to_unit_vectors + // cannot handle it. + if (startV == stopV) { + SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle); + SkScalar radiusX = oval.width() / 2; + SkScalar radiusY = oval.height() / 2; + // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle + // is very small and radius is huge, the expected behavior here is to draw a line. But + // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot. + singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle), + oval.centerY() + radiusY * SkScalarSin(endAngle)); + addPt(singlePt); + return *this; + } + + SkConic conics[SkConic::kMaxConicsForArc]; + int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt); + if (count) { + this->incReserve(count * 2 + 1); + const SkPoint& pt = conics[0].fPts[0]; + addPt(pt); + for (int i = 0; i < count; ++i) { + this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW); + } + } else { + addPt(singlePt); + } + return *this; +} + +// This converts the SVG arc to conics. +// Partly adapted from Niko's code in kdelibs/kdecore/svgicons. +// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic() +// See also SVG implementation notes: +// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter +// Note that arcSweep bool value is flipped from the original implementation. +SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge, + SkPath::Direction arcSweep, SkScalar x, SkScalar y) { + this->injectMoveToIfNeeded(); + SkPoint srcPts[2]; + this->getLastPt(&srcPts[0]); + // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") + // joining the endpoints. + // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters + if (!rx || !ry) { + return this->lineTo(x, y); + } + // If the current point and target point for the arc are identical, it should be treated as a + // zero length path. This ensures continuity in animations. + srcPts[1].set(x, y); + if (srcPts[0] == srcPts[1]) { + return this->lineTo(x, y); + } + rx = SkScalarAbs(rx); + ry = SkScalarAbs(ry); + SkVector midPointDistance = srcPts[0] - srcPts[1]; + midPointDistance *= 0.5f; + + SkMatrix pointTransform; + pointTransform.setRotate(-angle); + + SkPoint transformedMidPoint; + pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1); + SkScalar squareRx = rx * rx; + SkScalar squareRy = ry * ry; + SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX; + SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY; + + // Check if the radii are big enough to draw the arc, scale radii if not. + // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii + SkScalar radiiScale = squareX / squareRx + squareY / squareRy; + if (radiiScale > 1) { + radiiScale = SkScalarSqrt(radiiScale); + rx *= radiiScale; + ry *= radiiScale; + } + + pointTransform.setScale(1 / rx, 1 / ry); + pointTransform.preRotate(-angle); + + SkPoint unitPts[2]; + pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts)); + SkVector delta = unitPts[1] - unitPts[0]; + + SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY; + SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f); + + SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared); + if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation + scaleFactor = -scaleFactor; + } + delta.scale(scaleFactor); + SkPoint centerPoint = unitPts[0] + unitPts[1]; + centerPoint *= 0.5f; + centerPoint.offset(-delta.fY, delta.fX); + unitPts[0] -= centerPoint; + unitPts[1] -= centerPoint; + SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX); + SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX); + SkScalar thetaArc = theta2 - theta1; + if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation + thetaArc += SK_ScalarPI * 2; + } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation + thetaArc -= SK_ScalarPI * 2; + } + + // Very tiny angles cause our subsequent math to go wonky (skbug.com/9272) + // so we do a quick check here. The precise tolerance amount is just made up. + // PI/million happens to fix the bug in 9272, but a larger value is probably + // ok too. + if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) { + return this->lineTo(x, y); + } + + pointTransform.setRotate(angle); + pointTransform.preScale(rx, ry); + + // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd + int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3))); + SkScalar thetaWidth = thetaArc / segments; + SkScalar t = SkScalarTan(0.5f * thetaWidth); + if (!SkScalarIsFinite(t)) { + return *this; + } + SkScalar startTheta = theta1; + SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf); + auto scalar_is_integer = [](SkScalar scalar) -> bool { + return scalar == SkScalarFloorToScalar(scalar); + }; + bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) && + scalar_is_integer(rx) && scalar_is_integer(ry) && + scalar_is_integer(x) && scalar_is_integer(y); + + for (int i = 0; i < segments; ++i) { + SkScalar endTheta = startTheta + thetaWidth, + sinEndTheta = SkScalarSinSnapToZero(endTheta), + cosEndTheta = SkScalarCosSnapToZero(endTheta); + + unitPts[1].set(cosEndTheta, sinEndTheta); + unitPts[1] += centerPoint; + unitPts[0] = unitPts[1]; + unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta); + SkPoint mapped[2]; + pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts)); + /* + Computing the arc width introduces rounding errors that cause arcs to start + outside their marks. A round rect may lose convexity as a result. If the input + values are on integers, place the conic on integers as well. + */ + if (expectIntegers) { + for (SkPoint& point : mapped) { + point.fX = SkScalarRoundToScalar(point.fX); + point.fY = SkScalarRoundToScalar(point.fY); + } + } + this->conicTo(mapped[0], mapped[1], w); + startTheta = endTheta; + } + return *this; +} + +SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc, + SkPath::Direction sweep, SkScalar dx, SkScalar dy) { + SkPoint currentPoint; + this->getLastPt(¤tPoint); + return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, + currentPoint.fX + dx, currentPoint.fY + dy); +} + +SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) { + if (oval.isEmpty() || 0 == sweepAngle) { + return *this; + } + + const SkScalar kFullCircleAngle = SkIntToScalar(360); + + if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) { + // We can treat the arc as an oval if it begins at one of our legal starting positions. + // See SkPath::addOval() docs. + SkScalar startOver90 = startAngle / 90.f; + SkScalar startOver90I = SkScalarRoundToScalar(startOver90); + SkScalar error = startOver90 - startOver90I; + if (SkScalarNearlyEqual(error, 0)) { + // Index 1 is at startAngle == 0. + SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f); + startIndex = startIndex < 0 ? startIndex + 4.f : startIndex; + return this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction, + (unsigned) startIndex); + } + } + return this->arcTo(oval, startAngle, sweepAngle, true); +} + +/* + Need to handle the case when the angle is sharp, and our computed end-points + for the arc go behind pt1 and/or p2... +*/ +SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) { + if (radius == 0) { + return this->lineTo(x1, y1); + } + + // need to know our prev pt so we can construct tangent vectors + SkPoint start; + this->getLastPt(&start); + + // need double precision for these calcs. + SkDVector befored, afterd; + befored.set({x1 - start.fX, y1 - start.fY}).normalize(); + afterd.set({x2 - x1, y2 - y1}).normalize(); + double cosh = befored.dot(afterd); + double sinh = befored.cross(afterd); + + if (!befored.isFinite() || !afterd.isFinite() || SkScalarNearlyZero(SkDoubleToScalar(sinh))) { + return this->lineTo(x1, y1); + } + + // safe to convert back to floats now + SkVector before = befored.asSkVector(); + SkVector after = afterd.asSkVector(); + SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh)); + SkScalar xx = x1 - dist * before.fX; + SkScalar yy = y1 - dist * before.fY; + after.setLength(dist); + this->lineTo(xx, yy); + SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5)); + return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) { + SkMatrix matrix; + + matrix.setTranslate(dx, dy); + return this->addPath(path, matrix, mode); +} + +SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) { + // Detect if we're trying to add ourself + const SkPath* src = &srcPath; + SkTLazy tmp; + if (this == src) { + src = tmp.set(srcPath); + } + + SkPathRef::Editor(&fPathRef, src->countVerbs(), src->countPoints()); + + RawIter iter(*src); + SkPoint pts[4]; + Verb verb; + + SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix); + bool firstVerb = true; + while ((verb = iter.next(pts)) != kDone_Verb) { + switch (verb) { + case kMove_Verb: + proc(matrix, &pts[0], &pts[0], 1); + if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) { + injectMoveToIfNeeded(); // In case last contour is closed + SkPoint lastPt; + // don't add lineTo if it is degenerate + if (fLastMoveToIndex < 0 || !this->getLastPt(&lastPt) || lastPt != pts[0]) { + this->lineTo(pts[0]); + } + } else { + this->moveTo(pts[0]); + } + break; + case kLine_Verb: + proc(matrix, &pts[1], &pts[1], 1); + this->lineTo(pts[1]); + break; + case kQuad_Verb: + proc(matrix, &pts[1], &pts[1], 2); + this->quadTo(pts[1], pts[2]); + break; + case kConic_Verb: + proc(matrix, &pts[1], &pts[1], 2); + this->conicTo(pts[1], pts[2], iter.conicWeight()); + break; + case kCubic_Verb: + proc(matrix, &pts[1], &pts[1], 3); + this->cubicTo(pts[1], pts[2], pts[3]); + break; + case kClose_Verb: + this->close(); + break; + default: + SkDEBUGFAIL("unknown verb"); + } + firstVerb = false; + } + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// + +static int pts_in_verb(unsigned verb) { + static const uint8_t gPtsInVerb[] = { + 1, // kMove + 1, // kLine + 2, // kQuad + 2, // kConic + 3, // kCubic + 0, // kClose + 0 // kDone + }; + + SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb)); + return gPtsInVerb[verb]; +} + +// ignore the last point of the 1st contour +SkPath& SkPath::reversePathTo(const SkPath& path) { + if (path.fPathRef->fVerbs.count() == 0) { + return *this; + } + + const uint8_t* verbs = path.fPathRef->verbsEnd(); + const uint8_t* verbsBegin = path.fPathRef->verbsBegin(); + SkASSERT(verbsBegin[0] == kMove_Verb); + const SkPoint* pts = path.fPathRef->pointsEnd() - 1; + const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd(); + + while (verbs > verbsBegin) { + uint8_t v = *--verbs; + pts -= pts_in_verb(v); + switch (v) { + case kMove_Verb: + // if the path has multiple contours, stop after reversing the last + return *this; + case kLine_Verb: + this->lineTo(pts[0]); + break; + case kQuad_Verb: + this->quadTo(pts[1], pts[0]); + break; + case kConic_Verb: + this->conicTo(pts[1], pts[0], *--conicWeights); + break; + case kCubic_Verb: + this->cubicTo(pts[2], pts[1], pts[0]); + break; + case kClose_Verb: + break; + default: + SkDEBUGFAIL("bad verb"); + break; + } + } + return *this; +} + +SkPath& SkPath::reverseAddPath(const SkPath& srcPath) { + // Detect if we're trying to add ourself + const SkPath* src = &srcPath; + SkTLazy tmp; + if (this == src) { + src = tmp.set(srcPath); + } + + SkPathRef::Editor ed(&fPathRef, src->countVerbs(), src->countPoints()); + + const uint8_t* verbsBegin = src->fPathRef->verbsBegin(); + const uint8_t* verbs = src->fPathRef->verbsEnd(); + const SkPoint* pts = src->fPathRef->pointsEnd(); + const SkScalar* conicWeights = src->fPathRef->conicWeightsEnd(); + + bool needMove = true; + bool needClose = false; + while (verbs > verbsBegin) { + uint8_t v = *--verbs; + int n = pts_in_verb(v); + + if (needMove) { + --pts; + this->moveTo(pts->fX, pts->fY); + needMove = false; + } + pts -= n; + switch (v) { + case kMove_Verb: + if (needClose) { + this->close(); + needClose = false; + } + needMove = true; + pts += 1; // so we see the point in "if (needMove)" above + break; + case kLine_Verb: + this->lineTo(pts[0]); + break; + case kQuad_Verb: + this->quadTo(pts[1], pts[0]); + break; + case kConic_Verb: + this->conicTo(pts[1], pts[0], *--conicWeights); + break; + case kCubic_Verb: + this->cubicTo(pts[2], pts[1], pts[0]); + break; + case kClose_Verb: + needClose = true; + break; + default: + SkDEBUGFAIL("unexpected verb"); + } + } + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const { + SkMatrix matrix; + + matrix.setTranslate(dx, dy); + this->transform(matrix, dst); +} + +static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4], + int level = 2) { + if (--level >= 0) { + SkPoint tmp[7]; + + SkChopCubicAtHalf(pts, tmp); + subdivide_cubic_to(path, &tmp[0], level); + subdivide_cubic_to(path, &tmp[3], level); + } else { + path->cubicTo(pts[1], pts[2], pts[3]); + } +} + +void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const { + if (matrix.isIdentity()) { + if (dst != nullptr && dst != this) { + *dst = *this; + } + return; + } + + SkDEBUGCODE(this->validate();) + if (dst == nullptr) { + dst = (SkPath*)this; + } + + if (matrix.hasPerspective()) { + SkPath tmp; + tmp.fFillType = fFillType; + + SkPath::Iter iter(*this, false); + SkPoint pts[4]; + SkPath::Verb verb; + + while ((verb = iter.next(pts)) != kDone_Verb) { + switch (verb) { + case kMove_Verb: + tmp.moveTo(pts[0]); + break; + case kLine_Verb: + tmp.lineTo(pts[1]); + break; + case kQuad_Verb: + // promote the quad to a conic + tmp.conicTo(pts[1], pts[2], + SkConic::TransformW(pts, SK_Scalar1, matrix)); + break; + case kConic_Verb: + tmp.conicTo(pts[1], pts[2], + SkConic::TransformW(pts, iter.conicWeight(), matrix)); + break; + case kCubic_Verb: + subdivide_cubic_to(&tmp, pts); + break; + case kClose_Verb: + tmp.close(); + break; + default: + SkDEBUGFAIL("unknown verb"); + break; + } + } + + dst->swap(tmp); + SkPathRef::Editor ed(&dst->fPathRef); + matrix.mapPoints(ed.writablePoints(), ed.pathRef()->countPoints()); + dst->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); + } else { + Convexity convexity = this->getConvexityOrUnknown(); + + SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix); + + if (this != dst) { + dst->fLastMoveToIndex = fLastMoveToIndex; + dst->fFillType = fFillType; + dst->fIsVolatile = fIsVolatile; + } + + // Due to finite/fragile float numerics, we can't assume that a convex path remains + // convex after a transformation, so mark it as unknown here. + // However, some transformations are thought to be safe: + // axis-aligned values under scale/translate. + // + // See skbug.com/8606 + // If we can land a robust convex scan-converter, we may be able to relax/remove this + // check, and keep convex paths marked as such after a general transform... + // + if (matrix.isScaleTranslate() && SkPathPriv::IsAxisAligned(*this)) { + dst->setConvexity(convexity); + } else { + dst->setConvexity(kUnknown_Convexity); + } + + if (this->getFirstDirection() == SkPathPriv::kUnknown_FirstDirection) { + dst->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); + } else { + SkScalar det2x2 = + matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) - + matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY); + if (det2x2 < 0) { + dst->setFirstDirection( + SkPathPriv::OppositeFirstDirection( + (SkPathPriv::FirstDirection)this->getFirstDirection())); + } else if (det2x2 > 0) { + dst->setFirstDirection(this->getFirstDirection()); + } else { + dst->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); + } + } + + SkDEBUGCODE(dst->validate();) + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SkPath::Iter::Iter() { +#ifdef SK_DEBUG + fPts = nullptr; + fConicWeights = nullptr; + fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0; + fForceClose = fCloseLine = false; + fSegmentState = kEmptyContour_SegmentState; +#endif + // need to init enough to make next() harmlessly return kDone_Verb + fVerbs = nullptr; + fVerbStop = nullptr; + fNeedClose = false; +} + +SkPath::Iter::Iter(const SkPath& path, bool forceClose) { + this->setPath(path, forceClose); +} + +void SkPath::Iter::setPath(const SkPath& path, bool forceClose) { + fPts = path.fPathRef->points(); + fVerbs = path.fPathRef->verbsBegin(); + fVerbStop = path.fPathRef->verbsEnd(); + fConicWeights = path.fPathRef->conicWeights(); + if (fConicWeights) { + fConicWeights -= 1; // begin one behind + } + fLastPt.fX = fLastPt.fY = 0; + fMoveTo.fX = fMoveTo.fY = 0; + fForceClose = SkToU8(forceClose); + fNeedClose = false; + fSegmentState = kEmptyContour_SegmentState; +} + +bool SkPath::Iter::isClosedContour() const { + if (fVerbs == nullptr || fVerbs == fVerbStop) { + return false; + } + if (fForceClose) { + return true; + } + + const uint8_t* verbs = fVerbs; + const uint8_t* stop = fVerbStop; + + if (kMove_Verb == *verbs) { + verbs += 1; // skip the initial moveto + } + + while (verbs < stop) { + // verbs points one beyond the current verb, decrement first. + unsigned v = *verbs++; + if (kMove_Verb == v) { + break; + } + if (kClose_Verb == v) { + return true; + } + } + return false; +} + +SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) { + SkASSERT(pts); + if (fLastPt != fMoveTo) { + // A special case: if both points are NaN, SkPoint::operation== returns + // false, but the iterator expects that they are treated as the same. + // (consider SkPoint is a 2-dimension float point). + if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) || + SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) { + return kClose_Verb; + } + + pts[0] = fLastPt; + pts[1] = fMoveTo; + fLastPt = fMoveTo; + fCloseLine = true; + return kLine_Verb; + } else { + pts[0] = fMoveTo; + return kClose_Verb; + } +} + +const SkPoint& SkPath::Iter::cons_moveTo() { + if (fSegmentState == kAfterMove_SegmentState) { + // Set the first return pt to the move pt + fSegmentState = kAfterPrimitive_SegmentState; + return fMoveTo; + } + + SkASSERT(fSegmentState == kAfterPrimitive_SegmentState); + // Set the first return pt to the last pt of the previous primitive. + return fPts[-1]; +} + +SkPath::Verb SkPath::Iter::next(SkPoint ptsParam[4]) { + SkASSERT(ptsParam); + + if (fVerbs == fVerbStop) { + // Close the curve if requested and if there is some curve to close + if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) { + if (kLine_Verb == this->autoClose(ptsParam)) { + return kLine_Verb; + } + fNeedClose = false; + return kClose_Verb; + } + return kDone_Verb; + } + + unsigned verb = *fVerbs++; + const SkPoint* SK_RESTRICT srcPts = fPts; + SkPoint* SK_RESTRICT pts = ptsParam; + + switch (verb) { + case kMove_Verb: + if (fNeedClose) { + fVerbs--; // move back one verb + verb = this->autoClose(pts); + if (verb == kClose_Verb) { + fNeedClose = false; + } + return (Verb)verb; + } + if (fVerbs == fVerbStop) { // might be a trailing moveto + return kDone_Verb; + } + fMoveTo = *srcPts; + pts[0] = *srcPts; + srcPts += 1; + fSegmentState = kAfterMove_SegmentState; + fLastPt = fMoveTo; + fNeedClose = fForceClose; + break; + case kLine_Verb: + pts[0] = this->cons_moveTo(); + pts[1] = srcPts[0]; + fLastPt = srcPts[0]; + fCloseLine = false; + srcPts += 1; + break; + case kConic_Verb: + fConicWeights += 1; + // fall-through + case kQuad_Verb: + pts[0] = this->cons_moveTo(); + memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint)); + fLastPt = srcPts[1]; + srcPts += 2; + break; + case kCubic_Verb: + pts[0] = this->cons_moveTo(); + memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint)); + fLastPt = srcPts[2]; + srcPts += 3; + break; + case kClose_Verb: + verb = this->autoClose(pts); + if (verb == kLine_Verb) { + fVerbs--; // move back one verb + } else { + fNeedClose = false; + fSegmentState = kEmptyContour_SegmentState; + } + fLastPt = fMoveTo; + break; + } + fPts = srcPts; + return (Verb)verb; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "src/core/SkStringUtils.h" + +static void append_params(SkString* str, const char label[], const SkPoint pts[], + int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) { + str->append(label); + str->append("("); + + const SkScalar* values = &pts[0].fX; + count *= 2; + + for (int i = 0; i < count; ++i) { + SkAppendScalar(str, values[i], strType); + if (i < count - 1) { + str->append(", "); + } + } + if (conicWeight != -12345) { + str->append(", "); + SkAppendScalar(str, conicWeight, strType); + } + str->append(");"); + if (kHex_SkScalarAsStringType == strType) { + str->append(" // "); + for (int i = 0; i < count; ++i) { + SkAppendScalarDec(str, values[i]); + if (i < count - 1) { + str->append(", "); + } + } + if (conicWeight >= 0) { + str->append(", "); + SkAppendScalarDec(str, conicWeight); + } + } + str->append("\n"); +} + +void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const { + SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType; + Iter iter(*this, forceClose); + SkPoint pts[4]; + Verb verb; + + SkString builder; + char const * const gFillTypeStrs[] = { + "Winding", + "EvenOdd", + "InverseWinding", + "InverseEvenOdd", + }; + builder.printf("path.setFillType(SkPath::k%s_FillType);\n", + gFillTypeStrs[(int) this->getFillType()]); + while ((verb = iter.next(pts)) != kDone_Verb) { + switch (verb) { + case kMove_Verb: + append_params(&builder, "path.moveTo", &pts[0], 1, asType); + break; + case kLine_Verb: + append_params(&builder, "path.lineTo", &pts[1], 1, asType); + break; + case kQuad_Verb: + append_params(&builder, "path.quadTo", &pts[1], 2, asType); + break; + case kConic_Verb: + append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight()); + break; + case kCubic_Verb: + append_params(&builder, "path.cubicTo", &pts[1], 3, asType); + break; + case kClose_Verb: + builder.append("path.close();\n"); + break; + default: + SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb); + verb = kDone_Verb; // stop the loop + break; + } + if (!wStream && builder.size()) { + SkDebugf("%s", builder.c_str()); + builder.reset(); + } + } + if (wStream) { + wStream->writeText(builder.c_str()); + } +} + +void SkPath::dump() const { + this->dump(nullptr, false, false); +} + +void SkPath::dumpHex() const { + this->dump(nullptr, false, true); +} + + +bool SkPath::isValidImpl() const { + if ((fFillType & ~3) != 0) { + return false; + } + +#ifdef SK_DEBUG_PATH + if (!fBoundsIsDirty) { + SkRect bounds; + + bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get()); + if (SkToBool(fIsFinite) != isFinite) { + return false; + } + + if (fPathRef->countPoints() <= 1) { + // if we're empty, fBounds may be empty but translated, so we can't + // necessarily compare to bounds directly + // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will + // be [2, 2, 2, 2] + if (!bounds.isEmpty() || !fBounds.isEmpty()) { + return false; + } + } else { + if (bounds.isEmpty()) { + if (!fBounds.isEmpty()) { + return false; + } + } else { + if (!fBounds.isEmpty()) { + if (!fBounds.contains(bounds)) { + return false; + } + } + } + } + } +#endif // SK_DEBUG_PATH + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_LEGACY_PATH_CONVEXITY // for rebaselining Chrome + +static int sign(SkScalar x) { return x < 0; } +#define kValueNeverReturnedBySign 2 + +enum DirChange { + kLeft_DirChange, + kRight_DirChange, + kStraight_DirChange, + kBackwards_DirChange, + + kInvalid_DirChange +}; + + +static bool almost_equal(SkScalar compA, SkScalar compB) { + // The error epsilon was empirically derived; worse case round rects + // with a mid point outset by 2x float epsilon in tests had an error + // of 12. + const int epsilon = 16; + if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) { + return false; + } + // no need to check for small numbers because SkPath::Iter has removed degenerate values + int aBits = SkFloatAs2sCompliment(compA); + int bBits = SkFloatAs2sCompliment(compB); + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +// only valid for a single contour +struct Convexicator { + Convexicator() + : fPtCount(0) + , fConvexity(SkPath::kConvex_Convexity) + , fFirstDirection(SkPathPriv::kUnknown_FirstDirection) + , fIsFinite(true) + , fIsCurve(false) + , fBackwards(false) { + fExpectedDir = kInvalid_DirChange; + // warnings + fPriorPt.set(0,0); + fLastPt.set(0, 0); + fCurrPt.set(0, 0); + fLastVec.set(0, 0); + fFirstVec.set(0, 0); + + fDx = fDy = 0; + fSx = fSy = kValueNeverReturnedBySign; + } + + SkPath::Convexity getConvexity() const { return fConvexity; } + + /** The direction returned is only valid if the path is determined convex */ + SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; } + + void addPt(const SkPoint& pt) { + if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) { + return; + } + + if (0 == fPtCount) { + fCurrPt = pt; + ++fPtCount; + } else { + SkVector vec = pt - fCurrPt; + SkScalar lengthSqd = SkPointPriv::LengthSqd(vec); + if (!SkScalarIsFinite(lengthSqd)) { + fIsFinite = false; + } else if (lengthSqd) { + fPriorPt = fLastPt; + fLastPt = fCurrPt; + fCurrPt = pt; + if (++fPtCount == 2) { + fFirstVec = fLastVec = vec; + } else { + SkASSERT(fPtCount > 2); + this->addVec(vec); + } + + int sx = sign(vec.fX); + int sy = sign(vec.fY); + fDx += (sx != fSx); + fDy += (sy != fSy); + fSx = sx; + fSy = sy; + + if (fDx > 3 || fDy > 3) { + fConvexity = SkPath::kConcave_Convexity; + } + } + } + } + + void close() { + if (fPtCount > 2) { + this->addVec(fFirstVec); + } + } + + DirChange directionChange(const SkVector& curVec) { + SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec); + + SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY))); + SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY))); + largest = SkTMax(largest, -smallest); + + if (!almost_equal(largest, largest + cross)) { + int sign = SkScalarSignAsInt(cross); + if (sign) { + return (1 == sign) ? kRight_DirChange : kLeft_DirChange; + } + } + + if (cross) { + double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX); + double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY); + double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX); + double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY); + double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX; + if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) { + int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross)); + if (sign) { + return (1 == sign) ? kRight_DirChange : kLeft_DirChange; + } + } + } + + if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec), + SK_ScalarNearlyZero*SK_ScalarNearlyZero) && + !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec), + SK_ScalarNearlyZero*SK_ScalarNearlyZero) && + fLastVec.dot(curVec) < 0.0f) { + return kBackwards_DirChange; + } + + return kStraight_DirChange; + } + + bool hasBackwards() const { + return fBackwards; + } + + bool isFinite() const { + return fIsFinite; + } + + void setCurve(bool isCurve) { + fIsCurve = isCurve; + } + +private: + void addVec(const SkVector& vec) { + SkASSERT(vec.fX || vec.fY); + DirChange dir = this->directionChange(vec); + switch (dir) { + case kLeft_DirChange: // fall through + case kRight_DirChange: + if (kInvalid_DirChange == fExpectedDir) { + fExpectedDir = dir; + fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection + : SkPathPriv::kCCW_FirstDirection; + } else if (dir != fExpectedDir) { + fConvexity = SkPath::kConcave_Convexity; + fFirstDirection = SkPathPriv::kUnknown_FirstDirection; + } + fLastVec = vec; + break; + case kStraight_DirChange: + break; + case kBackwards_DirChange: + if (fIsCurve) { + // If any of the subsequent dir is non-backward, it'll be concave. + // Otherwise, it's still convex. + fExpectedDir = dir; + } + fLastVec = vec; + fBackwards = true; + break; + case kInvalid_DirChange: + SK_ABORT("Use of invalid direction change flag"); + break; + } + } + + SkPoint fPriorPt; + SkPoint fLastPt; + SkPoint fCurrPt; + // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product + // value with the current vec is deemed to be of a significant value. + SkVector fLastVec, fFirstVec; + int fPtCount; // non-degenerate points + DirChange fExpectedDir; + SkPath::Convexity fConvexity; + SkPathPriv::FirstDirection fFirstDirection; + int fDx, fDy, fSx, fSy; + bool fIsFinite; + bool fIsCurve; + bool fBackwards; +}; + +SkPath::Convexity SkPath::internalGetConvexity() const { + // Sometimes we think we need to calculate convexity but another thread already did. + auto c = this->getConvexityOrUnknown(); + if (c != kUnknown_Convexity) { + return c; + } + + SkPoint pts[4]; + SkPath::Verb verb; + SkPath::Iter iter(*this, true); + + int contourCount = 0; + int count; + Convexicator state; + + if (!isFinite()) { + return kUnknown_Convexity; + } + while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) { + switch (verb) { + case kMove_Verb: + if (++contourCount > 1) { + this->setConvexity(kConcave_Convexity); + return kConcave_Convexity; + } + pts[1] = pts[0]; + // fall through + case kLine_Verb: + count = 1; + state.setCurve(false); + break; + case kQuad_Verb: + // fall through + case kConic_Verb: + // fall through + case kCubic_Verb: + count = 2 + (kCubic_Verb == verb); + // As an additional enhancement, this could set curve true only + // if the curve is nonlinear + state.setCurve(true); + break; + case kClose_Verb: + state.setCurve(false); + state.close(); + count = 0; + break; + default: + SkDEBUGFAIL("bad verb"); + this->setConvexity(kConcave_Convexity); + return kConcave_Convexity; + } + + for (int i = 1; i <= count; i++) { + state.addPt(pts[i]); + } + // early exit + if (!state.isFinite()) { + return kUnknown_Convexity; + } + if (kConcave_Convexity == state.getConvexity()) { + this->setConvexity(kConcave_Convexity); + return kConcave_Convexity; + } + } + this->setConvexity(state.getConvexity()); + + if (this->getConvexityOrUnknown() == kConvex_Convexity && + this->getFirstDirection() == SkPathPriv::kUnknown_FirstDirection) { + + if (state.getFirstDirection() == SkPathPriv::kUnknown_FirstDirection + && !this->getBounds().isEmpty() + && !state.hasBackwards()) { + this->setConvexity(Convexity::kConcave_Convexity); + } else { + this->setFirstDirection(state.getFirstDirection()); + } + } + return this->getConvexityOrUnknown(); +} + +#else + +static int sign(SkScalar x) { return x < 0; } +#define kValueNeverReturnedBySign 2 + +enum DirChange { + kUnknown_DirChange, + kLeft_DirChange, + kRight_DirChange, + kStraight_DirChange, + kBackwards_DirChange, // if double back, allow simple lines to be convex + kInvalid_DirChange +}; + + +static bool almost_equal(SkScalar compA, SkScalar compB) { + // The error epsilon was empirically derived; worse case round rects + // with a mid point outset by 2x float epsilon in tests had an error + // of 12. + const int epsilon = 16; + if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) { + return false; + } + // no need to check for small numbers because SkPath::Iter has removed degenerate values + int aBits = SkFloatAs2sCompliment(compA); + int bBits = SkFloatAs2sCompliment(compB); + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +// only valid for a single contour +struct Convexicator { + + /** The direction returned is only valid if the path is determined convex */ + SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; } + + void setMovePt(const SkPoint& pt) { + fPriorPt = fLastPt = fCurrPt = pt; + } + + bool addPt(const SkPoint& pt) { + if (fCurrPt == pt) { + return true; + } + fCurrPt = pt; + if (fPriorPt == fLastPt) { // should only be true for first non-zero vector + fLastVec = fCurrPt - fLastPt; + fFirstPt = pt; + } else if (!this->addVec(fCurrPt - fLastPt)) { + return false; + } + fPriorPt = fLastPt; + fLastPt = fCurrPt; + return true; + } + + static SkPath::Convexity BySign(const SkPoint points[], int count) { + const SkPoint* last = points + count; + SkPoint currPt = *points++; + SkPoint firstPt = currPt; + int dxes = 0; + int dyes = 0; + int lastSx = kValueNeverReturnedBySign; + int lastSy = kValueNeverReturnedBySign; + for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) { + while (points != last) { + SkVector vec = *points - currPt; + if (!vec.isZero()) { + // give up if vector construction failed + if (!vec.isFinite()) { + return SkPath::kUnknown_Convexity; + } + int sx = sign(vec.fX); + int sy = sign(vec.fY); + dxes += (sx != lastSx); + dyes += (sy != lastSy); + if (dxes > 3 || dyes > 3) { + return SkPath::kConcave_Convexity; + } + lastSx = sx; + lastSy = sy; + } + currPt = *points++; + if (outerLoop) { + break; + } + } + points = &firstPt; + } + return SkPath::kConvex_Convexity; // that is, it may be convex, don't know yet + } + + bool close() { + return this->addPt(fFirstPt); + } + + bool isFinite() const { + return fIsFinite; + } + + int reversals() const { + return fReversals; + } + +private: + DirChange directionChange(const SkVector& curVec) { + SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec); + if (!SkScalarIsFinite(cross)) { + return kUnknown_DirChange; + } + SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY))); + SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY))); + largest = SkTMax(largest, -smallest); + + if (almost_equal(largest, largest + cross)) { + constexpr SkScalar nearlyZeroSqd = SK_ScalarNearlyZero * SK_ScalarNearlyZero; + if (SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec), nearlyZeroSqd) || + SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec), nearlyZeroSqd)) { + return kUnknown_DirChange; + } + return fLastVec.dot(curVec) < 0 ? kBackwards_DirChange : kStraight_DirChange; + } + return 1 == SkScalarSignAsInt(cross) ? kRight_DirChange : kLeft_DirChange; + } + + bool addVec(const SkVector& curVec) { + DirChange dir = this->directionChange(curVec); + switch (dir) { + case kLeft_DirChange: // fall through + case kRight_DirChange: + if (kInvalid_DirChange == fExpectedDir) { + fExpectedDir = dir; + fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection + : SkPathPriv::kCCW_FirstDirection; + } else if (dir != fExpectedDir) { + fFirstDirection = SkPathPriv::kUnknown_FirstDirection; + return false; + } + fLastVec = curVec; + break; + case kStraight_DirChange: + break; + case kBackwards_DirChange: + // allow path to reverse direction twice + // Given path.moveTo(0, 0); path.lineTo(1, 1); + // - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0) + // - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1) + fLastVec = curVec; + return ++fReversals < 3; + case kUnknown_DirChange: + return (fIsFinite = false); + case kInvalid_DirChange: + SK_ABORT("Use of invalid direction change flag"); + break; + } + return true; + } + + SkPoint fFirstPt {0, 0}; + SkPoint fPriorPt {0, 0}; + SkPoint fLastPt {0, 0}; + SkPoint fCurrPt {0, 0}; + SkVector fLastVec {0, 0}; + DirChange fExpectedDir { kInvalid_DirChange }; + SkPathPriv::FirstDirection fFirstDirection { SkPathPriv::kUnknown_FirstDirection }; + int fReversals { 0 }; + bool fIsFinite { true }; +}; + +SkPath::Convexity SkPath::internalGetConvexity() const { + SkPoint pts[4]; + SkPath::Verb verb; + SkPath::Iter iter(*this, true); + auto setComputedConvexity = [=](Convexity convexity){ + SkASSERT(kUnknown_Convexity != convexity); + this->setConvexity(convexity); + return convexity; + }; + + // Check to see if path changes direction more than three times as quick concave test + int pointCount = this->countPoints(); + // last moveTo index may exceed point count if data comes from fuzzer (via SkImageFilter) + if (0 < fLastMoveToIndex && fLastMoveToIndex < pointCount) { + pointCount = fLastMoveToIndex; + } + if (pointCount > 3) { + const SkPoint* points = fPathRef->points(); + const SkPoint* last = &points[pointCount]; + // only consider the last of the initial move tos + while (SkPath::kMove_Verb == iter.next(pts)) { + ++points; + } + --points; + SkPath::Convexity convexity = Convexicator::BySign(points, (int) (last - points)); + if (SkPath::kConcave_Convexity == convexity) { + return setComputedConvexity(SkPath::kConcave_Convexity); + } else if (SkPath::kUnknown_Convexity == convexity) { + return SkPath::kUnknown_Convexity; + } + iter.setPath(*this, true); + } else if (!this->isFinite()) { + return kUnknown_Convexity; + } + + int contourCount = 0; + int count; + Convexicator state; + auto setFail = [=](){ + if (!state.isFinite()) { + return SkPath::kUnknown_Convexity; + } + return setComputedConvexity(SkPath::kConcave_Convexity); + }; + + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + switch (verb) { + case kMove_Verb: + if (++contourCount > 1) { + return setComputedConvexity(kConcave_Convexity); + } + state.setMovePt(pts[0]); + count = 0; + break; + case kLine_Verb: + count = 1; + break; + case kQuad_Verb: + // fall through + case kConic_Verb: + count = 2; + break; + case kCubic_Verb: + count = 3; + break; + case kClose_Verb: + if (!state.close()) { + return setFail(); + } + count = 0; + break; + default: + SkDEBUGFAIL("bad verb"); + return setComputedConvexity(kConcave_Convexity); + } + for (int i = 1; i <= count; i++) { + if (!state.addPt(pts[i])) { + return setFail(); + } + } + } + + if (this->getFirstDirection() == SkPathPriv::kUnknown_FirstDirection) { + if (state.getFirstDirection() == SkPathPriv::kUnknown_FirstDirection + && !this->getBounds().isEmpty()) { + return setComputedConvexity(state.reversals() < 3 ? + kConvex_Convexity : kConcave_Convexity); + } + this->setFirstDirection(state.getFirstDirection()); + } + return setComputedConvexity(kConvex_Convexity); +} + +bool SkPathPriv::IsConvex(const SkPoint points[], int count) { + SkPath::Convexity convexity = Convexicator::BySign(points, count); + if (SkPath::kConvex_Convexity != convexity) { + return false; + } + Convexicator state; + state.setMovePt(points[0]); + for (int i = 1; i < count; i++) { + if (!state.addPt(points[i])) { + return false; + } + } + if (!state.addPt(points[0])) { + return false; + } + if (!state.close()) { + return false; + } + return state.getFirstDirection() != SkPathPriv::kUnknown_FirstDirection + || state.reversals() < 3; +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +class ContourIter { +public: + ContourIter(const SkPathRef& pathRef); + + bool done() const { return fDone; } + // if !done() then these may be called + int count() const { return fCurrPtCount; } + const SkPoint* pts() const { return fCurrPt; } + void next(); + +private: + int fCurrPtCount; + const SkPoint* fCurrPt; + const uint8_t* fCurrVerb; + const uint8_t* fStopVerbs; + const SkScalar* fCurrConicWeight; + bool fDone; + SkDEBUGCODE(int fContourCounter;) +}; + +ContourIter::ContourIter(const SkPathRef& pathRef) { + fStopVerbs = pathRef.verbsEnd(); + fDone = false; + fCurrPt = pathRef.points(); + fCurrVerb = pathRef.verbsBegin(); + fCurrConicWeight = pathRef.conicWeights(); + fCurrPtCount = 0; + SkDEBUGCODE(fContourCounter = 0;) + this->next(); +} + +void ContourIter::next() { + if (fCurrVerb >= fStopVerbs) { + fDone = true; + } + if (fDone) { + return; + } + + // skip pts of prev contour + fCurrPt += fCurrPtCount; + + SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]); + int ptCount = 1; // moveTo + const uint8_t* verbs = fCurrVerb; + + for (verbs++; verbs < fStopVerbs; verbs++) { + switch (*verbs) { + case SkPath::kMove_Verb: + goto CONTOUR_END; + case SkPath::kLine_Verb: + ptCount += 1; + break; + case SkPath::kConic_Verb: + fCurrConicWeight += 1; + // fall-through + case SkPath::kQuad_Verb: + ptCount += 2; + break; + case SkPath::kCubic_Verb: + ptCount += 3; + break; + case SkPath::kClose_Verb: + break; + default: + SkDEBUGFAIL("unexpected verb"); + break; + } + } +CONTOUR_END: + fCurrPtCount = ptCount; + fCurrVerb = verbs; + SkDEBUGCODE(++fContourCounter;) +} + +// returns cross product of (p1 - p0) and (p2 - p0) +static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) { + SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0); + // We may get 0 when the above subtracts underflow. We expect this to be + // very rare and lazily promote to double. + if (0 == cross) { + double p0x = SkScalarToDouble(p0.fX); + double p0y = SkScalarToDouble(p0.fY); + + double p1x = SkScalarToDouble(p1.fX); + double p1y = SkScalarToDouble(p1.fY); + + double p2x = SkScalarToDouble(p2.fX); + double p2y = SkScalarToDouble(p2.fY); + + cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) - + (p1y - p0y) * (p2x - p0x)); + + } + return cross; +} + +// Returns the first pt with the maximum Y coordinate +static int find_max_y(const SkPoint pts[], int count) { + SkASSERT(count > 0); + SkScalar max = pts[0].fY; + int firstIndex = 0; + for (int i = 1; i < count; ++i) { + SkScalar y = pts[i].fY; + if (y > max) { + max = y; + firstIndex = i; + } + } + return firstIndex; +} + +static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) { + int i = index; + for (;;) { + i = (i + inc) % n; + if (i == index) { // we wrapped around, so abort + break; + } + if (pts[index] != pts[i]) { // found a different point, success! + break; + } + } + return i; +} + +/** + * Starting at index, and moving forward (incrementing), find the xmin and + * xmax of the contiguous points that have the same Y. + */ +static int find_min_max_x_at_y(const SkPoint pts[], int index, int n, + int* maxIndexPtr) { + const SkScalar y = pts[index].fY; + SkScalar min = pts[index].fX; + SkScalar max = min; + int minIndex = index; + int maxIndex = index; + for (int i = index + 1; i < n; ++i) { + if (pts[i].fY != y) { + break; + } + SkScalar x = pts[i].fX; + if (x < min) { + min = x; + minIndex = i; + } else if (x > max) { + max = x; + maxIndex = i; + } + } + *maxIndexPtr = maxIndex; + return minIndex; +} + +static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) { + *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection; +} + +/* + * We loop through all contours, and keep the computed cross-product of the + * contour that contained the global y-max. If we just look at the first + * contour, we may find one that is wound the opposite way (correctly) since + * it is the interior of a hole (e.g. 'o'). Thus we must find the contour + * that is outer most (or at least has the global y-max) before we can consider + * its cross product. + */ +bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) { + auto d = path.getFirstDirection(); + if (d != kUnknown_FirstDirection) { + *dir = static_cast(d); + return true; + } + + // We don't want to pay the cost for computing convexity if it is unknown, + // so we call getConvexityOrUnknown() instead of isConvex(). + if (path.getConvexityOrUnknown() == SkPath::kConvex_Convexity) { + SkASSERT(path.getFirstDirection() == kUnknown_FirstDirection); + *dir = static_cast(path.getFirstDirection()); + return false; + } + + ContourIter iter(*path.fPathRef.get()); + + // initialize with our logical y-min + SkScalar ymax = path.getBounds().fTop; + SkScalar ymaxCross = 0; + + for (; !iter.done(); iter.next()) { + int n = iter.count(); + if (n < 3) { + continue; + } + + const SkPoint* pts = iter.pts(); + SkScalar cross = 0; + int index = find_max_y(pts, n); + if (pts[index].fY < ymax) { + continue; + } + + // If there is more than 1 distinct point at the y-max, we take the + // x-min and x-max of them and just subtract to compute the dir. + if (pts[(index + 1) % n].fY == pts[index].fY) { + int maxIndex; + int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex); + if (minIndex == maxIndex) { + goto TRY_CROSSPROD; + } + SkASSERT(pts[minIndex].fY == pts[index].fY); + SkASSERT(pts[maxIndex].fY == pts[index].fY); + SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX); + // we just subtract the indices, and let that auto-convert to + // SkScalar, since we just want - or + to signal the direction. + cross = minIndex - maxIndex; + } else { + TRY_CROSSPROD: + // Find a next and prev index to use for the cross-product test, + // but we try to find pts that form non-zero vectors from pts[index] + // + // Its possible that we can't find two non-degenerate vectors, so + // we have to guard our search (e.g. all the pts could be in the + // same place). + + // we pass n - 1 instead of -1 so we don't foul up % operator by + // passing it a negative LH argument. + int prev = find_diff_pt(pts, index, n, n - 1); + if (prev == index) { + // completely degenerate, skip to next contour + continue; + } + int next = find_diff_pt(pts, index, n, 1); + SkASSERT(next != index); + cross = cross_prod(pts[prev], pts[index], pts[next]); + // if we get a zero and the points are horizontal, then we look at the spread in + // x-direction. We really should continue to walk away from the degeneracy until + // there is a divergence. + if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) { + // construct the subtract so we get the correct Direction below + cross = pts[index].fX - pts[next].fX; + } + } + + if (cross) { + // record our best guess so far + ymax = pts[index].fY; + ymaxCross = cross; + } + } + if (ymaxCross) { + crossToDir(ymaxCross, dir); + path.setFirstDirection(*dir); + return true; + } else { + return false; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool between(SkScalar a, SkScalar b, SkScalar c) { + SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0) + || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c))); + return (a - b) * (c - b) <= 0; +} + +static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3, + SkScalar t) { + SkScalar A = c3 + 3*(c1 - c2) - c0; + SkScalar B = 3*(c2 - c1 - c1 + c0); + SkScalar C = 3*(c1 - c0); + SkScalar D = c0; + return poly_eval(A, B, C, D, t); +} + +template static void find_minmax(const SkPoint pts[], + SkScalar* minPtr, SkScalar* maxPtr) { + SkScalar min, max; + min = max = pts[0].fX; + for (size_t i = 1; i < N; ++i) { + min = SkMinScalar(min, pts[i].fX); + max = SkMaxScalar(max, pts[i].fX); + } + *minPtr = min; + *maxPtr = max; +} + +static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) { + if (start.fY == end.fY) { + return between(start.fX, x, end.fX) && x != end.fX; + } else { + return x == start.fX && y == start.fY; + } +} + +static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { + SkScalar y0 = pts[0].fY; + SkScalar y3 = pts[3].fY; + + int dir = 1; + if (y0 > y3) { + using std::swap; + swap(y0, y3); + dir = -1; + } + if (y < y0 || y > y3) { + return 0; + } + if (checkOnCurve(x, y, pts[0], pts[3])) { + *onCurveCount += 1; + return 0; + } + if (y == y3) { + return 0; + } + + // quickreject or quickaccept + SkScalar min, max; + find_minmax<4>(pts, &min, &max); + if (x < min) { + return 0; + } + if (x > max) { + return dir; + } + + // compute the actual x(t) value + SkScalar t; + if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) { + return 0; + } + SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t); + if (SkScalarNearlyEqual(xt, x)) { + if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points + *onCurveCount += 1; + return 0; + } + } + return xt < x ? dir : 0; +} + +static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { + SkPoint dst[10]; + int n = SkChopCubicAtYExtrema(pts, dst); + int w = 0; + for (int i = 0; i <= n; ++i) { + w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount); + } + return w; +} + +static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) { + SkASSERT(src); + SkASSERT(t >= 0 && t <= 1); + SkScalar src2w = src[2] * w; + SkScalar C = src[0]; + SkScalar A = src[4] - 2 * src2w + C; + SkScalar B = 2 * (src2w - C); + return poly_eval(A, B, C, t); +} + + +static double conic_eval_denominator(SkScalar w, SkScalar t) { + SkScalar B = 2 * (w - 1); + SkScalar C = 1; + SkScalar A = -B; + return poly_eval(A, B, C, t); +} + +static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) { + const SkPoint* pts = conic.fPts; + SkScalar y0 = pts[0].fY; + SkScalar y2 = pts[2].fY; + + int dir = 1; + if (y0 > y2) { + using std::swap; + swap(y0, y2); + dir = -1; + } + if (y < y0 || y > y2) { + return 0; + } + if (checkOnCurve(x, y, pts[0], pts[2])) { + *onCurveCount += 1; + return 0; + } + if (y == y2) { + return 0; + } + + SkScalar roots[2]; + SkScalar A = pts[2].fY; + SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y; + SkScalar C = pts[0].fY; + A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept) + B -= C; // B = b*w - w * yCept + yCept - a + C -= y; + int n = SkFindUnitQuadRoots(A, 2 * B, C, roots); + SkASSERT(n <= 1); + SkScalar xt; + if (0 == n) { + // zero roots are returned only when y0 == y + // Need [0] if dir == 1 + // and [2] if dir == -1 + xt = pts[1 - dir].fX; + } else { + SkScalar t = roots[0]; + xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t); + } + if (SkScalarNearlyEqual(xt, x)) { + if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points + *onCurveCount += 1; + return 0; + } + } + return xt < x ? dir : 0; +} + +static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) { + // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0; + if (y0 == y1) { + return true; + } + if (y0 < y1) { + return y1 <= y2; + } else { + return y1 >= y2; + } +} + +static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight, + int* onCurveCount) { + SkConic conic(pts, weight); + SkConic chopped[2]; + // If the data points are very large, the conic may not be monotonic but may also + // fail to chop. Then, the chopper does not split the original conic in two. + bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped); + int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount); + if (!isMono) { + w += winding_mono_conic(chopped[1], x, y, onCurveCount); + } + return w; +} + +static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { + SkScalar y0 = pts[0].fY; + SkScalar y2 = pts[2].fY; + + int dir = 1; + if (y0 > y2) { + using std::swap; + swap(y0, y2); + dir = -1; + } + if (y < y0 || y > y2) { + return 0; + } + if (checkOnCurve(x, y, pts[0], pts[2])) { + *onCurveCount += 1; + return 0; + } + if (y == y2) { + return 0; + } + // bounds check on X (not required. is it faster?) +#if 0 + if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) { + return 0; + } +#endif + + SkScalar roots[2]; + int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY, + 2 * (pts[1].fY - pts[0].fY), + pts[0].fY - y, + roots); + SkASSERT(n <= 1); + SkScalar xt; + if (0 == n) { + // zero roots are returned only when y0 == y + // Need [0] if dir == 1 + // and [2] if dir == -1 + xt = pts[1 - dir].fX; + } else { + SkScalar t = roots[0]; + SkScalar C = pts[0].fX; + SkScalar A = pts[2].fX - 2 * pts[1].fX + C; + SkScalar B = 2 * (pts[1].fX - C); + xt = poly_eval(A, B, C, t); + } + if (SkScalarNearlyEqual(xt, x)) { + if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points + *onCurveCount += 1; + return 0; + } + } + return xt < x ? dir : 0; +} + +static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { + SkPoint dst[5]; + int n = 0; + + if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) { + n = SkChopQuadAtYExtrema(pts, dst); + pts = dst; + } + int w = winding_mono_quad(pts, x, y, onCurveCount); + if (n > 0) { + w += winding_mono_quad(&pts[2], x, y, onCurveCount); + } + return w; +} + +static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { + SkScalar x0 = pts[0].fX; + SkScalar y0 = pts[0].fY; + SkScalar x1 = pts[1].fX; + SkScalar y1 = pts[1].fY; + + SkScalar dy = y1 - y0; + + int dir = 1; + if (y0 > y1) { + using std::swap; + swap(y0, y1); + dir = -1; + } + if (y < y0 || y > y1) { + return 0; + } + if (checkOnCurve(x, y, pts[0], pts[1])) { + *onCurveCount += 1; + return 0; + } + if (y == y1) { + return 0; + } + SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0); + + if (!cross) { + // zero cross means the point is on the line, and since the case where + // y of the query point is at the end point is handled above, we can be + // sure that we're on the line (excluding the end point) here + if (x != x1 || y != pts[1].fY) { + *onCurveCount += 1; + } + dir = 0; + } else if (SkScalarSignAsInt(cross) == dir) { + dir = 0; + } + return dir; +} + +static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y, + SkTDArray* tangents) { + if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY) + && !between(pts[2].fY, y, pts[3].fY)) { + return; + } + if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX) + && !between(pts[2].fX, x, pts[3].fX)) { + return; + } + SkPoint dst[10]; + int n = SkChopCubicAtYExtrema(pts, dst); + for (int i = 0; i <= n; ++i) { + SkPoint* c = &dst[i * 3]; + SkScalar t; + if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) { + continue; + } + SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t); + if (!SkScalarNearlyEqual(x, xt)) { + continue; + } + SkVector tangent; + SkEvalCubicAt(c, t, nullptr, &tangent, nullptr); + tangents->push_back(tangent); + } +} + +static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w, + SkTDArray* tangents) { + if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) { + return; + } + if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) { + return; + } + SkScalar roots[2]; + SkScalar A = pts[2].fY; + SkScalar B = pts[1].fY * w - y * w + y; + SkScalar C = pts[0].fY; + A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept) + B -= C; // B = b*w - w * yCept + yCept - a + C -= y; + int n = SkFindUnitQuadRoots(A, 2 * B, C, roots); + for (int index = 0; index < n; ++index) { + SkScalar t = roots[index]; + SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t); + if (!SkScalarNearlyEqual(x, xt)) { + continue; + } + SkConic conic(pts, w); + tangents->push_back(conic.evalTangentAt(t)); + } +} + +static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y, + SkTDArray* tangents) { + if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) { + return; + } + if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) { + return; + } + SkScalar roots[2]; + int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY, + 2 * (pts[1].fY - pts[0].fY), + pts[0].fY - y, + roots); + for (int index = 0; index < n; ++index) { + SkScalar t = roots[index]; + SkScalar C = pts[0].fX; + SkScalar A = pts[2].fX - 2 * pts[1].fX + C; + SkScalar B = 2 * (pts[1].fX - C); + SkScalar xt = poly_eval(A, B, C, t); + if (!SkScalarNearlyEqual(x, xt)) { + continue; + } + tangents->push_back(SkEvalQuadTangentAt(pts, t)); + } +} + +static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y, + SkTDArray* tangents) { + SkScalar y0 = pts[0].fY; + SkScalar y1 = pts[1].fY; + if (!between(y0, y, y1)) { + return; + } + SkScalar x0 = pts[0].fX; + SkScalar x1 = pts[1].fX; + if (!between(x0, x, x1)) { + return; + } + SkScalar dx = x1 - x0; + SkScalar dy = y1 - y0; + if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) { + return; + } + SkVector v; + v.set(dx, dy); + tangents->push_back(v); +} + +static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) { + return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom; +} + +bool SkPath::contains(SkScalar x, SkScalar y) const { + bool isInverse = this->isInverseFillType(); + if (this->isEmpty()) { + return isInverse; + } + + if (!contains_inclusive(this->getBounds(), x, y)) { + return isInverse; + } + + SkPath::Iter iter(*this, true); + bool done = false; + int w = 0; + int onCurveCount = 0; + do { + SkPoint pts[4]; + switch (iter.next(pts)) { + case SkPath::kMove_Verb: + case SkPath::kClose_Verb: + break; + case SkPath::kLine_Verb: + w += winding_line(pts, x, y, &onCurveCount); + break; + case SkPath::kQuad_Verb: + w += winding_quad(pts, x, y, &onCurveCount); + break; + case SkPath::kConic_Verb: + w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount); + break; + case SkPath::kCubic_Verb: + w += winding_cubic(pts, x, y, &onCurveCount); + break; + case SkPath::kDone_Verb: + done = true; + break; + } + } while (!done); + bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType() + || SkPath::kInverseEvenOdd_FillType == this->getFillType(); + if (evenOddFill) { + w &= 1; + } + if (w) { + return !isInverse; + } + if (onCurveCount <= 1) { + return SkToBool(onCurveCount) ^ isInverse; + } + if ((onCurveCount & 1) || evenOddFill) { + return SkToBool(onCurveCount & 1) ^ isInverse; + } + // If the point touches an even number of curves, and the fill is winding, check for + // coincidence. Count coincidence as places where the on curve points have identical tangents. + iter.setPath(*this, true); + done = false; + SkTDArray tangents; + do { + SkPoint pts[4]; + int oldCount = tangents.count(); + switch (iter.next(pts)) { + case SkPath::kMove_Verb: + case SkPath::kClose_Verb: + break; + case SkPath::kLine_Verb: + tangent_line(pts, x, y, &tangents); + break; + case SkPath::kQuad_Verb: + tangent_quad(pts, x, y, &tangents); + break; + case SkPath::kConic_Verb: + tangent_conic(pts, x, y, iter.conicWeight(), &tangents); + break; + case SkPath::kCubic_Verb: + tangent_cubic(pts, x, y, &tangents); + break; + case SkPath::kDone_Verb: + done = true; + break; + } + if (tangents.count() > oldCount) { + int last = tangents.count() - 1; + const SkVector& tangent = tangents[last]; + if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) { + tangents.remove(last); + } else { + for (int index = 0; index < last; ++index) { + const SkVector& test = tangents[index]; + if (SkScalarNearlyZero(test.cross(tangent)) + && SkScalarSignAsInt(tangent.fX * test.fX) <= 0 + && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) { + tangents.remove(last); + tangents.removeShuffle(index); + break; + } + } + } + } + } while (!done); + return SkToBool(tangents.count()) ^ isInverse; +} + +int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, + SkScalar w, SkPoint pts[], int pow2) { + const SkConic conic(p0, p1, p2, w); + return conic.chopIntoQuadsPOW2(pts, pow2); +} + +bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction, + unsigned* start) { + if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) { + return false; + } + SkPath::RawIter iter(path); + SkPoint verbPts[4]; + SkPath::Verb v; + SkPoint rectPts[5]; + int rectPtCnt = 0; + while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) { + switch (v) { + case SkPath::kMove_Verb: + if (0 != rectPtCnt) { + return false; + } + rectPts[0] = verbPts[0]; + ++rectPtCnt; + break; + case SkPath::kLine_Verb: + if (5 == rectPtCnt) { + return false; + } + rectPts[rectPtCnt] = verbPts[1]; + ++rectPtCnt; + break; + case SkPath::kClose_Verb: + if (4 == rectPtCnt) { + rectPts[4] = rectPts[0]; + rectPtCnt = 5; + } + break; + default: + return false; + } + } + if (rectPtCnt < 5) { + return false; + } + if (rectPts[0] != rectPts[4]) { + return false; + } + // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge ( + // and pts 1 and 2 the opposite vertical or horizontal edge). + bool vec03IsVertical; + if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX && + rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) { + // Make sure it has non-zero width and height + if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) { + return false; + } + vec03IsVertical = true; + } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY && + rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) { + // Make sure it has non-zero width and height + if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) { + return false; + } + vec03IsVertical = false; + } else { + return false; + } + // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit + // set if it is on the bottom edge. + unsigned sortFlags = + ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) | + ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10); + switch (sortFlags) { + case 0b00: + rect->setLTRB(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY); + *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction; + *start = 0; + break; + case 0b01: + rect->setLTRB(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY); + *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction; + *start = 1; + break; + case 0b10: + rect->setLTRB(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY); + *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction; + *start = 3; + break; + case 0b11: + rect->setLTRB(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY); + *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction; + *start = 2; + break; + } + return true; +} + +bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) { + if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) { + // This gets converted to an oval. + return true; + } + if (useCenter) { + // This is a pie wedge. It's convex if the angle is <= 180. + return SkScalarAbs(sweepAngle) <= 180.f; + } + // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped + // to a secant, i.e. convex. + return SkScalarAbs(sweepAngle) <= 360.f; +} + +void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) { + SkASSERT(!oval.isEmpty()); + SkASSERT(sweepAngle); + + path->reset(); + path->setIsVolatile(true); + path->setFillType(SkPath::kWinding_FillType); + if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) { + path->addOval(oval); + SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect)); + return; + } + if (useCenter) { + path->moveTo(oval.centerX(), oval.centerY()); + } + auto firstDir = + sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection; + bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect); + // Arc to mods at 360 and drawArc is not supposed to. + bool forceMoveTo = !useCenter; + while (sweepAngle <= -360.f) { + path->arcTo(oval, startAngle, -180.f, forceMoveTo); + startAngle -= 180.f; + path->arcTo(oval, startAngle, -180.f, false); + startAngle -= 180.f; + forceMoveTo = false; + sweepAngle += 360.f; + } + while (sweepAngle >= 360.f) { + path->arcTo(oval, startAngle, 180.f, forceMoveTo); + startAngle += 180.f; + path->arcTo(oval, startAngle, 180.f, false); + startAngle += 180.f; + forceMoveTo = false; + sweepAngle -= 360.f; + } + path->arcTo(oval, startAngle, sweepAngle, forceMoveTo); + if (useCenter) { + path->close(); + } + path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity); + path->setFirstDirection(firstDir); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "include/private/SkNx.h" + +static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) { + SkScalar ts[2]; + int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts); + n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]); + SkASSERT(n >= 0 && n <= 2); + for (int i = 0; i < n; ++i) { + extremas[i] = SkEvalQuadAt(src, ts[i]); + } + extremas[n] = src[2]; + return n + 1; +} + +static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) { + SkConic conic(src[0], src[1], src[2], w); + SkScalar ts[2]; + int n = conic.findXExtrema(ts); + n += conic.findYExtrema(&ts[n]); + SkASSERT(n >= 0 && n <= 2); + for (int i = 0; i < n; ++i) { + extremas[i] = conic.evalAt(ts[i]); + } + extremas[n] = src[2]; + return n + 1; +} + +static int compute_cubic_extremas(const SkPoint src[4], SkPoint extremas[5]) { + SkScalar ts[4]; + int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts); + n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]); + SkASSERT(n >= 0 && n <= 4); + for (int i = 0; i < n; ++i) { + SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr); + } + extremas[n] = src[3]; + return n + 1; +} + +SkRect SkPath::computeTightBounds() const { + if (0 == this->countVerbs()) { + return SkRect::MakeEmpty(); + } + + if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) { + return this->getBounds(); + } + + SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1 + SkPoint pts[4]; + SkPath::RawIter iter(*this); + + // initial with the first MoveTo, so we don't have to check inside the switch + Sk2s min, max; + min = max = from_point(this->getPoint(0)); + for (;;) { + int count = 0; + switch (iter.next(pts)) { + case SkPath::kMove_Verb: + extremas[0] = pts[0]; + count = 1; + break; + case SkPath::kLine_Verb: + extremas[0] = pts[1]; + count = 1; + break; + case SkPath::kQuad_Verb: + count = compute_quad_extremas(pts, extremas); + break; + case SkPath::kConic_Verb: + count = compute_conic_extremas(pts, iter.conicWeight(), extremas); + break; + case SkPath::kCubic_Verb: + count = compute_cubic_extremas(pts, extremas); + break; + case SkPath::kClose_Verb: + break; + case SkPath::kDone_Verb: + goto DONE; + } + for (int i = 0; i < count; ++i) { + Sk2s tmp = from_point(extremas[i]); + min = Sk2s::Min(min, tmp); + max = Sk2s::Max(max, tmp); + } + } +DONE: + SkRect bounds; + min.store((SkPoint*)&bounds.fLeft); + max.store((SkPoint*)&bounds.fRight); + return bounds; +} + +bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) { + return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2); +} + +bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2, + const SkPoint& p3, bool exact) { + return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) && + SkPointPriv::EqualsWithinTolerance(p2, p3); +} + +bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2, + const SkPoint& p3, const SkPoint& p4, bool exact) { + return exact ? p1 == p2 && p2 == p3 && p3 == p4 : + SkPointPriv::EqualsWithinTolerance(p1, p2) && + SkPointPriv::EqualsWithinTolerance(p2, p3) && + SkPointPriv::EqualsWithinTolerance(p3, p4); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkPathPriv::IsRectContour(const SkPath& path, bool allowPartial, int* currVerb, + const SkPoint** ptsPtr, bool* isClosed, SkPath::Direction* direction, + SkRect* rect) { + int corners = 0; + SkPoint closeXY; // used to determine if final line falls on a diagonal + SkPoint lineStart; // used to construct line from previous point + const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves) + const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed) + SkPoint firstCorner; + SkPoint thirdCorner; + const SkPoint* pts = *ptsPtr; + const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects + lineStart.set(0, 0); + signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized + bool closedOrMoved = false; + bool autoClose = false; + bool insertClose = false; + int verbCnt = path.fPathRef->countVerbs(); + while (*currVerb < verbCnt && (!allowPartial || !autoClose)) { + uint8_t verb = insertClose ? (uint8_t) SkPath::kClose_Verb : path.fPathRef->atVerb(*currVerb); + switch (verb) { + case SkPath::kClose_Verb: + savePts = pts; + autoClose = true; + insertClose = false; + case SkPath::kLine_Verb: { + if (SkPath::kClose_Verb != verb) { + lastPt = pts; + } + SkPoint lineEnd = SkPath::kClose_Verb == verb ? *firstPt : *pts++; + SkVector lineDelta = lineEnd - lineStart; + if (lineDelta.fX && lineDelta.fY) { + return false; // diagonal + } + if (!lineDelta.isFinite()) { + return false; // path contains infinity or NaN + } + if (lineStart == lineEnd) { + break; // single point on side OK + } + int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3 + if (0 == corners) { + directions[0] = nextDirection; + corners = 1; + closedOrMoved = false; + lineStart = lineEnd; + break; + } + if (closedOrMoved) { + return false; // closed followed by a line + } + if (autoClose && nextDirection == directions[0]) { + break; // colinear with first + } + closedOrMoved = autoClose; + if (directions[corners - 1] == nextDirection) { + if (3 == corners && SkPath::kLine_Verb == verb) { + thirdCorner = lineEnd; + } + lineStart = lineEnd; + break; // colinear segment + } + directions[corners++] = nextDirection; + // opposite lines must point in opposite directions; xoring them should equal 2 + switch (corners) { + case 2: + firstCorner = lineStart; + break; + case 3: + if ((directions[0] ^ directions[2]) != 2) { + return false; + } + thirdCorner = lineEnd; + break; + case 4: + if ((directions[1] ^ directions[3]) != 2) { + return false; + } + break; + default: + return false; // too many direction changes + } + lineStart = lineEnd; + break; + } + case SkPath::kQuad_Verb: + case SkPath::kConic_Verb: + case SkPath::kCubic_Verb: + return false; // quadratic, cubic not allowed + case SkPath::kMove_Verb: + if (allowPartial && !autoClose && directions[0] >= 0) { + insertClose = true; + *currVerb -= 1; // try move again afterwards + goto addMissingClose; + } + if (pts != *ptsPtr) { + return false; + } + if (!corners) { + firstPt = pts; + } else { + closeXY = *firstPt - *lastPt; + if (closeXY.fX && closeXY.fY) { + return false; // we're diagonal, abort + } + } + lineStart = *pts++; + closedOrMoved = true; + break; + default: + SkDEBUGFAIL("unexpected verb"); + break; + } + *currVerb += 1; + addMissingClose: + ; + } + // Success if 4 corners and first point equals last + if (corners < 3 || corners > 4) { + return false; + } + if (savePts) { + *ptsPtr = savePts; + } + // check if close generates diagonal + closeXY = *firstPt - *lastPt; + if (closeXY.fX && closeXY.fY) { + return false; + } + if (rect) { + rect->set(firstCorner, thirdCorner); + } + if (isClosed) { + *isClosed = autoClose; + } + if (direction) { + *direction = directions[0] == ((directions[1] + 1) & 3) ? + SkPath::kCW_Direction : SkPath::kCCW_Direction; + } + return true; +} + + +bool SkPathPriv::IsNestedFillRects(const SkPath& path, SkRect rects[2], SkPath::Direction dirs[2]) { + SkDEBUGCODE(path.validate();) + int currVerb = 0; + const SkPoint* pts = path.fPathRef->points(); + SkPath::Direction testDirs[2]; + SkRect testRects[2]; + if (!IsRectContour(path, true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) { + return false; + } + if (IsRectContour(path, false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) { + if (testRects[0].contains(testRects[1])) { + if (rects) { + rects[0] = testRects[0]; + rects[1] = testRects[1]; + } + if (dirs) { + dirs[0] = testDirs[0]; + dirs[1] = testDirs[1]; + } + return true; + } + if (testRects[1].contains(testRects[0])) { + if (rects) { + rects[0] = testRects[1]; + rects[1] = testRects[0]; + } + if (dirs) { + dirs[0] = testDirs[1]; + dirs[1] = testDirs[0]; + } + return true; + } + } + return false; +} diff --git a/gfx/skia/skia/src/core/SkPathEffect.cpp b/gfx/skia/skia/src/core/SkPathEffect.cpp new file mode 100644 index 0000000000..f5cb185e23 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPathEffect.cpp @@ -0,0 +1,195 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +/////////////////////////////////////////////////////////////////////////////// + +bool SkPathEffect::filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* bounds) const { + SkPath tmp, *tmpDst = dst; + if (dst == &src) { + tmpDst = &tmp; + } + if (this->onFilterPath(tmpDst, src, rec, bounds)) { + if (dst == &src) { + *dst = tmp; + } + return true; + } + return false; +} + +void SkPathEffect::computeFastBounds(SkRect* dst, const SkRect& src) const { + *dst = this->onComputeFastBounds(src); +} + +bool SkPathEffect::asPoints(PointData* results, const SkPath& src, + const SkStrokeRec& rec, const SkMatrix& mx, const SkRect* rect) const { + return this->onAsPoints(results, src, rec, mx, rect); +} + +SkPathEffect::DashType SkPathEffect::asADash(DashInfo* info) const { + return this->onAsADash(info); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** \class SkPairPathEffect + + Common baseclass for Compose and Sum. This subclass manages two pathEffects, + including flattening them. It does nothing in filterPath, and is only useful + for managing the lifetimes of its two arguments. + */ +class SkPairPathEffect : public SkPathEffect { +protected: + SkPairPathEffect(sk_sp pe0, sk_sp pe1) + : fPE0(std::move(pe0)), fPE1(std::move(pe1)) + { + SkASSERT(fPE0.get()); + SkASSERT(fPE1.get()); + } + + void flatten(SkWriteBuffer& buffer) const override { + buffer.writeFlattenable(fPE0.get()); + buffer.writeFlattenable(fPE1.get()); + } + + // these are visible to our subclasses + sk_sp fPE0; + sk_sp fPE1; + +private: + typedef SkPathEffect INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/** \class SkComposePathEffect + + This subclass of SkPathEffect composes its two arguments, to create + a compound pathEffect. + */ +class SkComposePathEffect : public SkPairPathEffect { +public: + /** Construct a pathEffect whose effect is to apply first the inner pathEffect + and the the outer pathEffect (e.g. outer(inner(path))) + The reference counts for outer and inner are both incremented in the constructor, + and decremented in the destructor. + */ + static sk_sp Make(sk_sp outer, sk_sp inner) { + if (!outer) { + return inner; + } + if (!inner) { + return outer; + } + return sk_sp(new SkComposePathEffect(outer, inner)); + } + +protected: + SkComposePathEffect(sk_sp outer, sk_sp inner) + : INHERITED(outer, inner) {} + + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect) const override { + SkPath tmp; + const SkPath* ptr = &src; + + if (fPE1->filterPath(&tmp, src, rec, cullRect)) { + ptr = &tmp; + } + return fPE0->filterPath(dst, *ptr, rec, cullRect); + } + +private: + SK_FLATTENABLE_HOOKS(SkComposePathEffect) + + // illegal + SkComposePathEffect(const SkComposePathEffect&); + SkComposePathEffect& operator=(const SkComposePathEffect&); + friend class SkPathEffect; + + typedef SkPairPathEffect INHERITED; +}; + +sk_sp SkComposePathEffect::CreateProc(SkReadBuffer& buffer) { + sk_sp pe0(buffer.readPathEffect()); + sk_sp pe1(buffer.readPathEffect()); + return SkComposePathEffect::Make(std::move(pe0), std::move(pe1)); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** \class SkSumPathEffect + + This subclass of SkPathEffect applies two pathEffects, one after the other. + Its filterPath() returns true if either of the effects succeeded. + */ +class SkSumPathEffect : public SkPairPathEffect { +public: + /** Construct a pathEffect whose effect is to apply two effects, in sequence. + (e.g. first(path) + second(path)) + The reference counts for first and second are both incremented in the constructor, + and decremented in the destructor. + */ + static sk_sp Make(sk_sp first, sk_sp second) { + if (!first) { + return second; + } + if (!second) { + return first; + } + return sk_sp(new SkSumPathEffect(first, second)); + } + + SK_FLATTENABLE_HOOKS(SkSumPathEffect) + +protected: + SkSumPathEffect(sk_sp first, sk_sp second) + : INHERITED(first, second) {} + + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect) const override { + // use bit-or so that we always call both, even if the first one succeeds + return fPE0->filterPath(dst, src, rec, cullRect) | + fPE1->filterPath(dst, src, rec, cullRect); + } + +private: + // illegal + SkSumPathEffect(const SkSumPathEffect&); + SkSumPathEffect& operator=(const SkSumPathEffect&); + friend class SkPathEffect; + + typedef SkPairPathEffect INHERITED; +}; + +sk_sp SkSumPathEffect::CreateProc(SkReadBuffer& buffer) { + sk_sp pe0(buffer.readPathEffect()); + sk_sp pe1(buffer.readPathEffect()); + return SkSumPathEffect::Make(pe0, pe1); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkPathEffect::MakeSum(sk_sp first, sk_sp second) { + return SkSumPathEffect::Make(std::move(first), std::move(second)); +} + +sk_sp SkPathEffect::MakeCompose(sk_sp outer, + sk_sp inner) { + return SkComposePathEffect::Make(std::move(outer), std::move(inner)); +} + +void SkPathEffect::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkComposePathEffect); + SK_REGISTER_FLATTENABLE(SkSumPathEffect); +} diff --git a/gfx/skia/skia/src/core/SkPathMakers.h b/gfx/skia/skia/src/core/SkPathMakers.h new file mode 100644 index 0000000000..a4ef7e1215 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPathMakers.h @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathMakers_DEFINED +#define SkPathMakers_DEFINED + +#include "include/core/SkPath.h" // just for direction +#include "include/core/SkPoint.h" +#include "include/core/SkRRect.h" + +template class SkPath_PointIterator { +public: + SkPath_PointIterator(SkPath::Direction dir, unsigned startIndex) + : fCurrent(startIndex % N) + , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { } + + const SkPoint& current() const { + SkASSERT(fCurrent < N); + return fPts[fCurrent]; + } + + const SkPoint& next() { + fCurrent = (fCurrent + fAdvance) % N; + return this->current(); + } + + protected: + SkPoint fPts[N]; + + private: + unsigned fCurrent; + unsigned fAdvance; +}; + +class SkPath_RectPointIterator : public SkPath_PointIterator<4> { +public: + SkPath_RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex) + : SkPath_PointIterator(dir, startIndex) { + + fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop); + fPts[1] = SkPoint::Make(rect.fRight, rect.fTop); + fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom); + fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom); + } +}; + +class SkPath_OvalPointIterator : public SkPath_PointIterator<4> { +public: + SkPath_OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex) + : SkPath_PointIterator(dir, startIndex) { + + const SkScalar cx = oval.centerX(); + const SkScalar cy = oval.centerY(); + + fPts[0] = SkPoint::Make(cx, oval.fTop); + fPts[1] = SkPoint::Make(oval.fRight, cy); + fPts[2] = SkPoint::Make(cx, oval.fBottom); + fPts[3] = SkPoint::Make(oval.fLeft, cy); + } +}; + +class SkPath_RRectPointIterator : public SkPath_PointIterator<8> { +public: + SkPath_RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex) + : SkPath_PointIterator(dir, startIndex) { + + const SkRect& bounds = rrect.getBounds(); + const SkScalar L = bounds.fLeft; + const SkScalar T = bounds.fTop; + const SkScalar R = bounds.fRight; + const SkScalar B = bounds.fBottom; + + fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T); + fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T); + fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY); + fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY); + fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B); + fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B); + fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY); + fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY); + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPathMeasure.cpp b/gfx/skia/skia/src/core/SkPathMeasure.cpp new file mode 100644 index 0000000000..445e6a12ba --- /dev/null +++ b/gfx/skia/skia/src/core/SkPathMeasure.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkContourMeasure.h" +#include "include/core/SkPathMeasure.h" + +SkPathMeasure::SkPathMeasure() {} + +SkPathMeasure::SkPathMeasure(const SkPath& path, bool forceClosed, SkScalar resScale) + : fIter(path, forceClosed, resScale) +{ + fContour = fIter.next(); +} + +SkPathMeasure::~SkPathMeasure() {} + +void SkPathMeasure::setPath(const SkPath* path, bool forceClosed) { + fIter.reset(path ? *path : SkPath(), forceClosed); + fContour = fIter.next(); +} + +SkScalar SkPathMeasure::getLength() { + return fContour ? fContour->length() : 0; +} + +bool SkPathMeasure::getPosTan(SkScalar distance, SkPoint* position, SkVector* tangent) { + return fContour && fContour->getPosTan(distance, position, tangent); +} + +bool SkPathMeasure::getMatrix(SkScalar distance, SkMatrix* matrix, MatrixFlags flags) { + return fContour && fContour->getMatrix(distance, matrix, (SkContourMeasure::MatrixFlags)flags); +} + +bool SkPathMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst, bool startWithMoveTo) { + return fContour && fContour->getSegment(startD, stopD, dst, startWithMoveTo); +} + +bool SkPathMeasure::isClosed() { + return fContour && fContour->isClosed(); +} + +bool SkPathMeasure::nextContour() { + fContour = fIter.next(); + return !!fContour; +} + +#ifdef SK_DEBUG +void SkPathMeasure::dump() {} +#endif diff --git a/gfx/skia/skia/src/core/SkPathMeasurePriv.h b/gfx/skia/skia/src/core/SkPathMeasurePriv.h new file mode 100644 index 0000000000..dbad22b622 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPathMeasurePriv.h @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathMeasurePriv_DEFINED +#define SkPathMeasurePriv_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "src/core/SkGeometry.h" + +// Used in the Segment struct defined in SkPathMeasure.h +// It is used as a 2-bit field so if you add to this +// you must increase the size of the bitfield there. +enum SkSegType { + kLine_SegType, + kQuad_SegType, + kCubic_SegType, + kConic_SegType, +}; + + +void SkPathMeasure_segTo(const SkPoint pts[], unsigned segType, + SkScalar startT, SkScalar stopT, SkPath* dst); + +#endif // SkPathMeasurePriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkPathPriv.h b/gfx/skia/skia/src/core/SkPathPriv.h new file mode 100644 index 0000000000..52d4685ba6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPathPriv.h @@ -0,0 +1,400 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathPriv_DEFINED +#define SkPathPriv_DEFINED + +#include "include/core/SkPath.h" + +class SkPathPriv { +public: +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + static const int kPathRefGenIDBitCnt = 30; // leave room for the fill type (skbug.com/1762) +#else + static const int kPathRefGenIDBitCnt = 32; +#endif + + enum FirstDirection : int { + kCW_FirstDirection, // == SkPath::kCW_Direction + kCCW_FirstDirection, // == SkPath::kCCW_Direction + kUnknown_FirstDirection, + }; + + static FirstDirection AsFirstDirection(SkPath::Direction dir) { + // since we agree numerically for the values in Direction, we can just cast. + return (FirstDirection)dir; + } + + /** + * Return the opposite of the specified direction. kUnknown is its own + * opposite. + */ + static FirstDirection OppositeFirstDirection(FirstDirection dir) { + static const FirstDirection gOppositeDir[] = { + kCCW_FirstDirection, kCW_FirstDirection, kUnknown_FirstDirection, + }; + return gOppositeDir[dir]; + } + + /** + * Tries to quickly compute the direction of the first non-degenerate + * contour. If it can be computed, return true and set dir to that + * direction. If it cannot be (quickly) determined, return false and ignore + * the dir parameter. If the direction was determined, it is cached to make + * subsequent calls return quickly. + */ + static bool CheapComputeFirstDirection(const SkPath&, FirstDirection* dir); + + /** + * Returns true if the path's direction can be computed via + * cheapComputDirection() and if that computed direction matches the + * specified direction. If dir is kUnknown, returns true if the direction + * cannot be computed. + */ + static bool CheapIsFirstDirection(const SkPath& path, FirstDirection dir) { + FirstDirection computedDir = kUnknown_FirstDirection; + (void)CheapComputeFirstDirection(path, &computedDir); + return computedDir == dir; + } + + static bool IsClosedSingleContour(const SkPath& path) { + int verbCount = path.countVerbs(); + if (verbCount == 0) + return false; + int moveCount = 0; + auto verbs = path.fPathRef->verbsBegin(); + for (int i = 0; i < verbCount; i++) { + switch (verbs[i]) { + case SkPath::Verb::kMove_Verb: + moveCount += 1; + if (moveCount > 1) { + return false; + } + break; + case SkPath::Verb::kClose_Verb: + if (i == verbCount - 1) { + return true; + } + return false; + default: break; + } + } + return false; + } + + static void AddGenIDChangeListener(const SkPath& path, + sk_sp listener) { + path.fPathRef->addGenIDChangeListener(std::move(listener)); + } + + /** + * This returns true for a rect that begins and ends at the same corner and has either a move + * followed by four lines or a move followed by 3 lines and a close. None of the parameters are + * optional. This does not permit degenerate line or point rectangles. + */ + static bool IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction, + unsigned* start); + + /** + * Creates a path from arc params using the semantics of SkCanvas::drawArc. This function + * assumes empty ovals and zero sweeps have already been filtered out. + */ + static void CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect); + + /** + * Determines whether an arc produced by CreateDrawArcPath will be convex. Assumes a non-empty + * oval. + */ + static bool DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect); + + /** + * Returns a C++11-iterable object that traverses a path's verbs in order. e.g: + * + * for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { + * ... + * } + */ + struct Verbs { + public: + Verbs(const SkPath& path) : fPathRef(path.fPathRef.get()) {} + struct Iter { + void operator++() { fVerb++; } + bool operator!=(const Iter& b) { return fVerb != b.fVerb; } + SkPath::Verb operator*() { return static_cast(*fVerb); } + const uint8_t* fVerb; + }; + Iter begin() { return Iter{fPathRef->verbsBegin()}; } + Iter end() { return Iter{fPathRef->verbsEnd()}; } + private: + Verbs(const Verbs&) = delete; + Verbs& operator=(const Verbs&) = delete; + SkPathRef* fPathRef; + }; + + /** + * Returns a pointer to the verb data. + */ + static const uint8_t* VerbData(const SkPath& path) { + return path.fPathRef->verbsBegin(); + } + + /** Returns a raw pointer to the path points */ + static const SkPoint* PointData(const SkPath& path) { + return path.fPathRef->points(); + } + + /** Returns the number of conic weights in the path */ + static int ConicWeightCnt(const SkPath& path) { + return path.fPathRef->countWeights(); + } + + /** Returns a raw pointer to the path conic weights. */ + static const SkScalar* ConicWeightData(const SkPath& path) { + return path.fPathRef->conicWeights(); + } + +#ifndef SK_LEGACY_PATH_CONVEXITY + /** Returns true if path formed by pts is convex. + + @param pts SkPoint array of path + @param count number of entries in array + + @return true if pts represent a convex geometry + */ + static bool IsConvex(const SkPoint pts[], int count); +#endif + + /** Returns true if the underlying SkPathRef has one single owner. */ + static bool TestingOnly_unique(const SkPath& path) { + return path.fPathRef->unique(); + } + + /** Returns true if constructed by addCircle(), addOval(); and in some cases, + addRoundRect(), addRRect(). SkPath constructed with conicTo() or rConicTo() will not + return true though SkPath draws oval. + + rect receives bounds of oval. + dir receives SkPath::Direction of oval: kCW_Direction if clockwise, kCCW_Direction if + counterclockwise. + start receives start of oval: 0 for top, 1 for right, 2 for bottom, 3 for left. + + rect, dir, and start are unmodified if oval is not found. + + Triggers performance optimizations on some GPU surface implementations. + + @param rect storage for bounding SkRect of oval; may be nullptr + @param dir storage for SkPath::Direction; may be nullptr + @param start storage for start of oval; may be nullptr + @return true if SkPath was constructed by method that reduces to oval + */ + static bool IsOval(const SkPath& path, SkRect* rect, SkPath::Direction* dir, unsigned* start) { + bool isCCW = false; + bool result = path.fPathRef->isOval(rect, &isCCW, start); + if (dir && result) { + *dir = isCCW ? SkPath::kCCW_Direction : SkPath::kCW_Direction; + } + return result; + } + + /** Returns true if constructed by addRoundRect(), addRRect(); and if construction + is not empty, not SkRect, and not oval. SkPath constructed with other calls + will not return true though SkPath draws SkRRect. + + rrect receives bounds of SkRRect. + dir receives SkPath::Direction of oval: kCW_Direction if clockwise, kCCW_Direction if + counterclockwise. + start receives start of SkRRect: 0 for top, 1 for right, 2 for bottom, 3 for left. + + rrect, dir, and start are unmodified if SkRRect is not found. + + Triggers performance optimizations on some GPU surface implementations. + + @param rrect storage for bounding SkRect of SkRRect; may be nullptr + @param dir storage for SkPath::Direction; may be nullptr + @param start storage for start of SkRRect; may be nullptr + @return true if SkPath contains only SkRRect + */ + static bool IsRRect(const SkPath& path, SkRRect* rrect, SkPath::Direction* dir, + unsigned* start) { + bool isCCW = false; + bool result = path.fPathRef->isRRect(rrect, &isCCW, start); + if (dir && result) { + *dir = isCCW ? SkPath::kCCW_Direction : SkPath::kCW_Direction; + } + return result; + } + + /** + * Sometimes in the drawing pipeline, we have to perform math on path coordinates, even after + * the path is in device-coordinates. Tessellation and clipping are two examples. Usually this + * is pretty modest, but it can involve subtracting/adding coordinates, or multiplying by + * small constants (e.g. 2,3,4). To try to preflight issues where these optionations could turn + * finite path values into infinities (or NaNs), we allow the upper drawing code to reject + * the path if its bounds (in device coordinates) is too close to max float. + */ + static bool TooBigForMath(const SkRect& bounds) { + // This value is just a guess. smaller is safer, but we don't want to reject largish paths + // that we don't have to. + constexpr SkScalar scale_down_to_allow_for_small_multiplies = 0.25f; + constexpr SkScalar max = SK_ScalarMax * scale_down_to_allow_for_small_multiplies; + + // use ! expression so we return true if bounds contains NaN + return !(bounds.fLeft >= -max && bounds.fTop >= -max && + bounds.fRight <= max && bounds.fBottom <= max); + } + static bool TooBigForMath(const SkPath& path) { + return TooBigForMath(path.getBounds()); + } + + // Returns number of valid points for each SkPath::Iter verb + static int PtsInIter(unsigned verb) { + static const uint8_t gPtsInVerb[] = { + 1, // kMove pts[0] + 2, // kLine pts[0..1] + 3, // kQuad pts[0..2] + 3, // kConic pts[0..2] + 4, // kCubic pts[0..3] + 0, // kClose + 0 // kDone + }; + + SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb)); + return gPtsInVerb[verb]; + } + + static bool IsAxisAligned(const SkPath& path) { + SkRect tmp; + return (path.fPathRef->fIsRRect | path.fPathRef->fIsOval) || path.isRect(&tmp); + } + + static bool AllPointsEq(const SkPoint pts[], int count) { + for (int i = 1; i < count; ++i) { + if (pts[0] != pts[i]) { + return false; + } + } + return true; + } + + static bool IsRectContour(const SkPath&, bool allowPartial, int* currVerb, + const SkPoint** ptsPtr, bool* isClosed, SkPath::Direction* direction, + SkRect* rect); + + /** Returns true if SkPath is equivalent to nested SkRect pair when filled. + If false, rect and dirs are unchanged. + If true, rect and dirs are written to if not nullptr: + setting rect[0] to outer SkRect, and rect[1] to inner SkRect; + setting dirs[0] to SkPath::Direction of outer SkRect, and dirs[1] to SkPath::Direction of + inner SkRect. + + @param rect storage for SkRect pair; may be nullptr + @param dirs storage for SkPath::Direction pair; may be nullptr + @return true if SkPath contains nested SkRect pair + */ + static bool IsNestedFillRects(const SkPath&, SkRect rect[2], + SkPath::Direction dirs[2] = nullptr); +}; + +// Lightweight variant of SkPath::Iter that only returns segments (e.g. lines/conics). +// Does not return kMove or kClose. +// Always "auto-closes" each contour. +// Roughly the same as SkPath::Iter(path, true), but does not return moves or closes +// +class SkPathEdgeIter { + const uint8_t* fVerbs; + const uint8_t* fVerbsStop; + const SkPoint* fPts; + const SkPoint* fMoveToPtr; + const SkScalar* fConicWeights; + SkPoint fScratch[2]; // for auto-close lines + bool fNeedsCloseLine; + SkDEBUGCODE(bool fIsConic); + + enum { + kIllegalEdgeValue = 99 + }; + +public: + SkPathEdgeIter(const SkPath& path); + + SkScalar conicWeight() const { + SkASSERT(fIsConic); + return *fConicWeights; + } + + enum class Edge { + kLine = SkPath::kLine_Verb, + kQuad = SkPath::kQuad_Verb, + kConic = SkPath::kConic_Verb, + kCubic = SkPath::kCubic_Verb, + }; + + static SkPath::Verb EdgeToVerb(Edge e) { + return SkPath::Verb(e); + } + + struct Result { + const SkPoint* fPts; // points for the segment, or null if done + Edge fEdge; + + // Returns true when it holds an Edge, false when the path is done. + MOZ_IMPLICIT operator bool() { return fPts != nullptr; } + }; + + Result next() { + auto closeline = [&]() { + fScratch[0] = fPts[-1]; + fScratch[1] = *fMoveToPtr; + fNeedsCloseLine = false; + return Result{ fScratch, Edge::kLine }; + }; + + for (;;) { + SkASSERT(fVerbs <= fVerbsStop); + if (fVerbs == fVerbsStop) { + return fNeedsCloseLine + ? closeline() + : Result{ nullptr, Edge(kIllegalEdgeValue) }; + } + + SkDEBUGCODE(fIsConic = false;) + + const auto v = *fVerbs++; + switch (v) { + case SkPath::kMove_Verb: { + if (fNeedsCloseLine) { + auto res = closeline(); + fMoveToPtr = fPts++; + return res; + } + fMoveToPtr = fPts++; + } break; + case SkPath::kClose_Verb: + if (fNeedsCloseLine) return closeline(); + break; + default: { + // Actual edge. + const int pts_count = (v+2) / 2, + cws_count = (v & (v-1)) / 2; + SkASSERT(pts_count == SkPathPriv::PtsInIter(v) - 1); + + fNeedsCloseLine = true; + fPts += pts_count; + fConicWeights += cws_count; + + SkDEBUGCODE(fIsConic = (v == SkPath::kConic_Verb);) + SkASSERT(fIsConic == (cws_count > 0)); + + return { &fPts[-(pts_count + 1)], Edge(v) }; + } + } + } + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPathRef.cpp b/gfx/skia/skia/src/core/SkPathRef.cpp new file mode 100644 index 0000000000..561fed7961 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPathRef.cpp @@ -0,0 +1,702 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkPathRef.h" + +#include "include/core/SkPath.h" +#include "include/private/SkNx.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTo.h" +#include "src/core/SkBuffer.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkSafeMath.h" + +////////////////////////////////////////////////////////////////////////////// +SkPathRef::Editor::Editor(sk_sp* pathRef, + int incReserveVerbs, + int incReservePoints) +{ + SkASSERT(incReserveVerbs >= 0); + SkASSERT(incReservePoints >= 0); + + if ((*pathRef)->unique()) { + (*pathRef)->incReserve(incReserveVerbs, incReservePoints); + } else { + SkPathRef* copy = new SkPathRef; + copy->copy(**pathRef, incReserveVerbs, incReservePoints); + pathRef->reset(copy); + } + fPathRef = pathRef->get(); + fPathRef->callGenIDChangeListeners(); + fPathRef->fGenerationID = 0; + fPathRef->fBoundsIsDirty = true; + SkDEBUGCODE(fPathRef->fEditorsAttached++;) +} + +// Sort of like makeSpace(0) but the the additional requirement that we actively shrink the +// allocations to just fit the current needs. makeSpace() will only grow, but never shrinks. +// +void SkPath::shrinkToFit() { + fPathRef->fPoints.shrinkToFit(); + fPathRef->fVerbs.shrinkToFit(); + fPathRef->fConicWeights.shrinkToFit(); + SkDEBUGCODE(fPathRef->validate();) +} + +////////////////////////////////////////////////////////////////////////////// + +SkPathRef::~SkPathRef() { + // Deliberately don't validate() this path ref, otherwise there's no way + // to read one that's not valid and then free its memory without asserting. + this->callGenIDChangeListeners(); + SkASSERT(fGenIDChangeListeners.empty()); // These are raw ptrs. + SkDEBUGCODE(fGenerationID = 0xEEEEEEEE;) + SkDEBUGCODE(fEditorsAttached.store(0x7777777);) +} + +static SkPathRef* gEmpty = nullptr; + +SkPathRef* SkPathRef::CreateEmpty() { + static SkOnce once; + once([]{ + gEmpty = new SkPathRef; + gEmpty->computeBounds(); // Avoids races later to be the first to do this. + }); + return SkRef(gEmpty); +} + +static void transform_dir_and_start(const SkMatrix& matrix, bool isRRect, bool* isCCW, + unsigned* start) { + int inStart = *start; + int rm = 0; + if (isRRect) { + // Degenerate rrect indices to oval indices and remember the remainder. + // Ovals have one index per side whereas rrects have two. + rm = inStart & 0b1; + inStart /= 2; + } + // Is the antidiagonal non-zero (otherwise the diagonal is zero) + int antiDiag; + // Is the non-zero value in the top row (either kMScaleX or kMSkewX) negative + int topNeg; + // Are the two non-zero diagonal or antidiagonal values the same sign. + int sameSign; + if (matrix.get(SkMatrix::kMScaleX) != 0) { + antiDiag = 0b00; + if (matrix.get(SkMatrix::kMScaleX) > 0) { + topNeg = 0b00; + sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b01 : 0b00; + } else { + topNeg = 0b10; + sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b00 : 0b01; + } + } else { + antiDiag = 0b01; + if (matrix.get(SkMatrix::kMSkewX) > 0) { + topNeg = 0b00; + sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b01 : 0b00; + } else { + topNeg = 0b10; + sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b00 : 0b01; + } + } + if (sameSign != antiDiag) { + // This is a rotation (and maybe scale). The direction is unchanged. + // Trust me on the start computation (or draw yourself some pictures) + *start = (inStart + 4 - (topNeg | antiDiag)) % 4; + SkASSERT(*start < 4); + if (isRRect) { + *start = 2 * *start + rm; + } + } else { + // This is a mirror (and maybe scale). The direction is reversed. + *isCCW = !*isCCW; + // Trust me on the start computation (or draw yourself some pictures) + *start = (6 + (topNeg | antiDiag) - inStart) % 4; + SkASSERT(*start < 4); + if (isRRect) { + *start = 2 * *start + (rm ? 0 : 1); + } + } +} + +void SkPathRef::CreateTransformedCopy(sk_sp* dst, + const SkPathRef& src, + const SkMatrix& matrix) { + SkDEBUGCODE(src.validate();) + if (matrix.isIdentity()) { + if (dst->get() != &src) { + src.ref(); + dst->reset(const_cast(&src)); + SkDEBUGCODE((*dst)->validate();) + } + return; + } + + if (!(*dst)->unique()) { + dst->reset(new SkPathRef); + } + + if (dst->get() != &src) { + (*dst)->fPoints = src.fPoints; + (*dst)->fVerbs = src.fVerbs; + (*dst)->fConicWeights = src.fConicWeights; + (*dst)->callGenIDChangeListeners(); + (*dst)->fGenerationID = 0; // mark as dirty + } + + // Need to check this here in case (&src == dst) + bool canXformBounds = !src.fBoundsIsDirty && matrix.rectStaysRect() && src.countPoints() > 1; + + matrix.mapPoints((*dst)->fPoints.begin(), src.fPoints.begin(), src.fPoints.count()); + + /* + * Here we optimize the bounds computation, by noting if the bounds are + * already known, and if so, we just transform those as well and mark + * them as "known", rather than force the transformed path to have to + * recompute them. + * + * Special gotchas if the path is effectively empty (<= 1 point) or + * if it is non-finite. In those cases bounds need to stay empty, + * regardless of the matrix. + */ + if (canXformBounds) { + (*dst)->fBoundsIsDirty = false; + if (src.fIsFinite) { + matrix.mapRect(&(*dst)->fBounds, src.fBounds); + if (!((*dst)->fIsFinite = (*dst)->fBounds.isFinite())) { + (*dst)->fBounds.setEmpty(); + } else if (src.countPoints() & 1) { + /* Matrix optimizations may cause the first point to use slightly different + * math for its transform, which can lead to it being outside the transformed + * bounds. Include it in the bounds just in case. + */ + SkPoint p = (*dst)->fPoints[0]; + SkRect& r = (*dst)->fBounds; + r.fLeft = SkMinScalar(r.fLeft, p.fX); + r.fTop = SkMinScalar(r.fTop, p.fY); + r.fRight = SkMaxScalar(r.fRight, p.fX); + r.fBottom = SkMaxScalar(r.fBottom, p.fY); + } + } else { + (*dst)->fIsFinite = false; + (*dst)->fBounds.setEmpty(); + } + } else { + (*dst)->fBoundsIsDirty = true; + } + + (*dst)->fSegmentMask = src.fSegmentMask; + + // It's an oval only if it stays a rect. + bool rectStaysRect = matrix.rectStaysRect(); + (*dst)->fIsOval = src.fIsOval && rectStaysRect; + (*dst)->fIsRRect = src.fIsRRect && rectStaysRect; + if ((*dst)->fIsOval || (*dst)->fIsRRect) { + unsigned start = src.fRRectOrOvalStartIdx; + bool isCCW = SkToBool(src.fRRectOrOvalIsCCW); + transform_dir_and_start(matrix, (*dst)->fIsRRect, &isCCW, &start); + (*dst)->fRRectOrOvalIsCCW = isCCW; + (*dst)->fRRectOrOvalStartIdx = start; + } + + if (dst->get() == &src) { + (*dst)->callGenIDChangeListeners(); + (*dst)->fGenerationID = 0; + } + + SkDEBUGCODE((*dst)->validate();) +} + +void SkPathRef::Rewind(sk_sp* pathRef) { + if ((*pathRef)->unique()) { + SkDEBUGCODE((*pathRef)->validate();) + (*pathRef)->callGenIDChangeListeners(); + (*pathRef)->fBoundsIsDirty = true; // this also invalidates fIsFinite + (*pathRef)->fGenerationID = 0; + (*pathRef)->fPoints.rewind(); + (*pathRef)->fVerbs.rewind(); + (*pathRef)->fConicWeights.rewind(); + (*pathRef)->fSegmentMask = 0; + (*pathRef)->fIsOval = false; + (*pathRef)->fIsRRect = false; + SkDEBUGCODE((*pathRef)->validate();) + } else { + int oldVCnt = (*pathRef)->countVerbs(); + int oldPCnt = (*pathRef)->countPoints(); + pathRef->reset(new SkPathRef); + (*pathRef)->resetToSize(0, 0, 0, oldVCnt, oldPCnt); + } +} + +bool SkPathRef::operator== (const SkPathRef& ref) const { + SkDEBUGCODE(this->validate();) + SkDEBUGCODE(ref.validate();) + + // We explicitly check fSegmentMask as a quick-reject. We could skip it, + // since it is only a cache of info in the fVerbs, but its a fast way to + // notice a difference + if (fSegmentMask != ref.fSegmentMask) { + return false; + } + + bool genIDMatch = fGenerationID && fGenerationID == ref.fGenerationID; +#ifdef SK_RELEASE + if (genIDMatch) { + return true; + } +#endif + if (fPoints != ref.fPoints || fConicWeights != ref.fConicWeights || fVerbs != ref.fVerbs) { + SkASSERT(!genIDMatch); + return false; + } + if (ref.fVerbs.count() == 0) { + SkASSERT(ref.fPoints.count() == 0); + } + return true; +} + +void SkPathRef::writeToBuffer(SkWBuffer* buffer) const { + SkDEBUGCODE(this->validate();) + SkDEBUGCODE(size_t beforePos = buffer->pos();) + + // Call getBounds() to ensure (as a side-effect) that fBounds + // and fIsFinite are computed. + const SkRect& bounds = this->getBounds(); + + // We store fSegmentMask for older readers, but current readers can't trust it, so they + // don't read it. + int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) | + (fSegmentMask << kSegmentMask_SerializationShift); + buffer->write32(packed); + + // TODO: write gen ID here. Problem: We don't know if we're cross process or not from + // SkWBuffer. Until this is fixed we write 0. + buffer->write32(0); + buffer->write32(fVerbs.count()); + buffer->write32(fPoints.count()); + buffer->write32(fConicWeights.count()); + buffer->write(fVerbs.begin(), fVerbs.bytes()); + buffer->write(fPoints.begin(), fVerbs.bytes()); + buffer->write(fConicWeights.begin(), fConicWeights.bytes()); + buffer->write(&bounds, sizeof(bounds)); + + SkASSERT(buffer->pos() - beforePos == (size_t) this->writeSize()); +} + +uint32_t SkPathRef::writeSize() const { + return uint32_t(5 * sizeof(uint32_t) + + fVerbs.bytes() + fPoints.bytes() + fConicWeights.bytes() + + sizeof(SkRect)); +} + +void SkPathRef::copy(const SkPathRef& ref, + int additionalReserveVerbs, + int additionalReservePoints) { + SkDEBUGCODE(this->validate();) + this->resetToSize(ref.fVerbs.count(), ref.fPoints.count(), ref.fConicWeights.count(), + additionalReserveVerbs, additionalReservePoints); + fVerbs = ref.fVerbs; + fPoints = ref.fPoints; + fConicWeights = ref.fConicWeights; + fBoundsIsDirty = ref.fBoundsIsDirty; + if (!fBoundsIsDirty) { + fBounds = ref.fBounds; + fIsFinite = ref.fIsFinite; + } + fSegmentMask = ref.fSegmentMask; + fIsOval = ref.fIsOval; + fIsRRect = ref.fIsRRect; + fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW; + fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx; + SkDEBUGCODE(this->validate();) +} + +unsigned SkPathRef::computeSegmentMask() const { + const uint8_t* verbs = fVerbs.begin(); + unsigned mask = 0; + for (int i = 0; i < fVerbs.count(); ++i) { + switch (verbs[i]) { + case SkPath::kLine_Verb: mask |= SkPath::kLine_SegmentMask; break; + case SkPath::kQuad_Verb: mask |= SkPath::kQuad_SegmentMask; break; + case SkPath::kConic_Verb: mask |= SkPath::kConic_SegmentMask; break; + case SkPath::kCubic_Verb: mask |= SkPath::kCubic_SegmentMask; break; + default: break; + } + } + return mask; +} + +void SkPathRef::interpolate(const SkPathRef& ending, SkScalar weight, SkPathRef* out) const { + const SkScalar* inValues = &ending.getPoints()->fX; + SkScalar* outValues = &out->getWritablePoints()->fX; + int count = out->countPoints() * 2; + for (int index = 0; index < count; ++index) { + outValues[index] = outValues[index] * weight + inValues[index] * (1 - weight); + } + out->fBoundsIsDirty = true; + out->fIsOval = false; + out->fIsRRect = false; +} + +SkPoint* SkPathRef::growForRepeatedVerb(int /*SkPath::Verb*/ verb, + int numVbs, + SkScalar** weights) { + SkDEBUGCODE(this->validate();) + int pCnt; + switch (verb) { + case SkPath::kMove_Verb: + pCnt = numVbs; + break; + case SkPath::kLine_Verb: + fSegmentMask |= SkPath::kLine_SegmentMask; + pCnt = numVbs; + break; + case SkPath::kQuad_Verb: + fSegmentMask |= SkPath::kQuad_SegmentMask; + pCnt = 2 * numVbs; + break; + case SkPath::kConic_Verb: + fSegmentMask |= SkPath::kConic_SegmentMask; + pCnt = 2 * numVbs; + break; + case SkPath::kCubic_Verb: + fSegmentMask |= SkPath::kCubic_SegmentMask; + pCnt = 3 * numVbs; + break; + case SkPath::kClose_Verb: + SkDEBUGFAIL("growForRepeatedVerb called for kClose_Verb"); + pCnt = 0; + break; + case SkPath::kDone_Verb: + SkDEBUGFAIL("growForRepeatedVerb called for kDone"); + // fall through + default: + SkDEBUGFAIL("default should not be reached"); + pCnt = 0; + } + + fBoundsIsDirty = true; // this also invalidates fIsFinite + fIsOval = false; + fIsRRect = false; + + memset(fVerbs.append(numVbs), verb, numVbs); + if (SkPath::kConic_Verb == verb) { + SkASSERT(weights); + *weights = fConicWeights.append(numVbs); + } + SkPoint* pts = fPoints.append(pCnt); + + SkDEBUGCODE(this->validate();) + return pts; +} + +SkPoint* SkPathRef::growForVerb(int /* SkPath::Verb*/ verb, SkScalar weight) { + SkDEBUGCODE(this->validate();) + int pCnt; + unsigned mask = 0; + switch (verb) { + case SkPath::kMove_Verb: + pCnt = 1; + break; + case SkPath::kLine_Verb: + mask = SkPath::kLine_SegmentMask; + pCnt = 1; + break; + case SkPath::kQuad_Verb: + mask = SkPath::kQuad_SegmentMask; + pCnt = 2; + break; + case SkPath::kConic_Verb: + mask = SkPath::kConic_SegmentMask; + pCnt = 2; + break; + case SkPath::kCubic_Verb: + mask = SkPath::kCubic_SegmentMask; + pCnt = 3; + break; + case SkPath::kClose_Verb: + pCnt = 0; + break; + case SkPath::kDone_Verb: + SkDEBUGFAIL("growForVerb called for kDone"); + // fall through + default: + SkDEBUGFAIL("default is not reached"); + pCnt = 0; + } + + fSegmentMask |= mask; + fBoundsIsDirty = true; // this also invalidates fIsFinite + fIsOval = false; + fIsRRect = false; + + *fVerbs.append() = verb; + if (SkPath::kConic_Verb == verb) { + *fConicWeights.append() = weight; + } + SkPoint* pts = fPoints.append(pCnt); + + SkDEBUGCODE(this->validate();) + return pts; +} + +uint32_t SkPathRef::genID() const { + SkASSERT(fEditorsAttached.load() == 0); + static const uint32_t kMask = (static_cast(1) << SkPathPriv::kPathRefGenIDBitCnt) - 1; + + if (fGenerationID == 0) { + if (fPoints.count() == 0 && fVerbs.count() == 0) { + fGenerationID = kEmptyGenID; + } else { + static std::atomic nextID{kEmptyGenID + 1}; + do { + fGenerationID = nextID.fetch_add(1, std::memory_order_relaxed) & kMask; + } while (fGenerationID == 0 || fGenerationID == kEmptyGenID); + } + } + return fGenerationID; +} + +void SkPathRef::addGenIDChangeListener(sk_sp listener) { + if (nullptr == listener || this == gEmpty) { + return; + } + + SkAutoMutexExclusive lock(fGenIDChangeListenersMutex); + + // Clean out any stale listeners before we append the new one. + for (int i = 0; i < fGenIDChangeListeners.count(); ++i) { + if (fGenIDChangeListeners[i]->shouldUnregisterFromPath()) { + fGenIDChangeListeners[i]->unref(); + fGenIDChangeListeners.removeShuffle(i--); // No need to preserve the order after i. + } + } + + SkASSERT(!listener->shouldUnregisterFromPath()); + *fGenIDChangeListeners.append() = listener.release(); +} + +// we need to be called *before* the genID gets changed or zerod +void SkPathRef::callGenIDChangeListeners() { + auto visit = [this]() { + for (GenIDChangeListener* listener : fGenIDChangeListeners) { + if (!listener->shouldUnregisterFromPath()) { + listener->onChange(); + } + // Listeners get at most one shot, so whether these triggered or not, blow them away. + listener->unref(); + } + fGenIDChangeListeners.reset(); + }; + + // Acquiring the mutex is relatively expensive, compared to operations like moveTo, etc. + // Thus we want to skip it if we're unique. This is safe because the only purpose of the + // mutex is to keep the listener-list intact while we iterate/edit it, and if we're unique, + // no one else can modify fGenIDChangeListeners. + + if (this->unique()) { + visit(); + } else { + SkAutoMutexExclusive lock(fGenIDChangeListenersMutex); + visit(); + } +} + +SkRRect SkPathRef::getRRect() const { + const SkRect& bounds = this->getBounds(); + SkVector radii[4] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; + Iter iter(*this); + SkPoint pts[4]; + uint8_t verb = iter.next(pts); + SkASSERT(SkPath::kMove_Verb == verb); + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + if (SkPath::kConic_Verb == verb) { + SkVector v1_0 = pts[1] - pts[0]; + SkVector v2_1 = pts[2] - pts[1]; + SkVector dxdy; + if (v1_0.fX) { + SkASSERT(!v2_1.fX && !v1_0.fY); + dxdy.set(SkScalarAbs(v1_0.fX), SkScalarAbs(v2_1.fY)); + } else if (!v1_0.fY) { + SkASSERT(!v2_1.fX || !v2_1.fY); + dxdy.set(SkScalarAbs(v2_1.fX), SkScalarAbs(v2_1.fY)); + } else { + SkASSERT(!v2_1.fY); + dxdy.set(SkScalarAbs(v2_1.fX), SkScalarAbs(v1_0.fY)); + } + SkRRect::Corner corner = + pts[1].fX == bounds.fLeft ? + pts[1].fY == bounds.fTop ? + SkRRect::kUpperLeft_Corner : SkRRect::kLowerLeft_Corner : + pts[1].fY == bounds.fTop ? + SkRRect::kUpperRight_Corner : SkRRect::kLowerRight_Corner; + SkASSERT(!radii[corner].fX && !radii[corner].fY); + radii[corner] = dxdy; + } else { + SkASSERT((verb == SkPath::kLine_Verb + && (!(pts[1].fX - pts[0].fX) || !(pts[1].fY - pts[0].fY))) + || verb == SkPath::kClose_Verb); + } + } + SkRRect rrect; + rrect.setRectRadii(bounds, radii); + return rrect; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPathRef::Iter::Iter() { +#ifdef SK_DEBUG + fPts = nullptr; + fConicWeights = nullptr; +#endif + // need to init enough to make next() harmlessly return kDone_Verb + fVerbs = nullptr; + fVerbStop = nullptr; +} + +SkPathRef::Iter::Iter(const SkPathRef& path) { + this->setPathRef(path); +} + +void SkPathRef::Iter::setPathRef(const SkPathRef& path) { + fPts = path.points(); + fVerbs = path.verbsBegin(); + fVerbStop = path.verbsEnd(); + fConicWeights = path.conicWeights(); + if (fConicWeights) { + fConicWeights -= 1; // begin one behind + } + + // Don't allow iteration through non-finite points. + if (!path.isFinite()) { + fVerbStop = fVerbs; + } +} + +uint8_t SkPathRef::Iter::next(SkPoint pts[4]) { + SkASSERT(pts); + + SkDEBUGCODE(unsigned peekResult = this->peek();) + + if (fVerbs == fVerbStop) { + SkASSERT(peekResult == SkPath::kDone_Verb); + return (uint8_t) SkPath::kDone_Verb; + } + + // fVerbs points one beyond next verb so decrement first. + unsigned verb = *fVerbs++; + const SkPoint* srcPts = fPts; + + switch (verb) { + case SkPath::kMove_Verb: + pts[0] = srcPts[0]; + srcPts += 1; + break; + case SkPath::kLine_Verb: + pts[0] = srcPts[-1]; + pts[1] = srcPts[0]; + srcPts += 1; + break; + case SkPath::kConic_Verb: + fConicWeights += 1; + // fall-through + case SkPath::kQuad_Verb: + pts[0] = srcPts[-1]; + pts[1] = srcPts[0]; + pts[2] = srcPts[1]; + srcPts += 2; + break; + case SkPath::kCubic_Verb: + pts[0] = srcPts[-1]; + pts[1] = srcPts[0]; + pts[2] = srcPts[1]; + pts[3] = srcPts[2]; + srcPts += 3; + break; + case SkPath::kClose_Verb: + break; + case SkPath::kDone_Verb: + SkASSERT(fVerbs == fVerbStop); + break; + } + fPts = srcPts; + SkASSERT(peekResult == verb); + return (uint8_t) verb; +} + +uint8_t SkPathRef::Iter::peek() const { + return fVerbs < fVerbStop ? *fVerbs : (uint8_t) SkPath::kDone_Verb; +} + + +bool SkPathRef::isValid() const { + if (fIsOval || fIsRRect) { + // Currently we don't allow both of these to be set, even though ovals are ro + if (fIsOval == fIsRRect) { + return false; + } + if (fIsOval) { + if (fRRectOrOvalStartIdx >= 4) { + return false; + } + } else { + if (fRRectOrOvalStartIdx >= 8) { + return false; + } + } + } + + if (!fBoundsIsDirty && !fBounds.isEmpty()) { + bool isFinite = true; + Sk2s leftTop = Sk2s(fBounds.fLeft, fBounds.fTop); + Sk2s rightBot = Sk2s(fBounds.fRight, fBounds.fBottom); + for (int i = 0; i < fPoints.count(); ++i) { + Sk2s point = Sk2s(fPoints[i].fX, fPoints[i].fY); +#ifdef SK_DEBUG + if (fPoints[i].isFinite() && + ((point < leftTop).anyTrue() || (point > rightBot).anyTrue())) { + SkDebugf("bad SkPathRef bounds: %g %g %g %g\n", + fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom); + for (int j = 0; j < fPoints.count(); ++j) { + if (i == j) { + SkDebugf("*** bounds do not contain: "); + } + SkDebugf("%g %g\n", fPoints[j].fX, fPoints[j].fY); + } + return false; + } +#endif + + if (fPoints[i].isFinite() && (point < leftTop).anyTrue() && !(point > rightBot).anyTrue()) + return false; + if (!fPoints[i].isFinite()) { + isFinite = false; + } + } + if (SkToBool(fIsFinite) != isFinite) { + return false; + } + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +SkPathEdgeIter::SkPathEdgeIter(const SkPath& path) { + fMoveToPtr = fPts = path.fPathRef->points(); + fVerbs = path.fPathRef->verbsBegin(); + fVerbsStop = path.fPathRef->verbsEnd(); + fConicWeights = path.fPathRef->conicWeights(); + if (fConicWeights) { + fConicWeights -= 1; // begin one behind + } + + fNeedsCloseLine = false; + SkDEBUGCODE(fIsConic = false;) +} diff --git a/gfx/skia/skia/src/core/SkPath_serial.cpp b/gfx/skia/skia/src/core/SkPath_serial.cpp new file mode 100644 index 0000000000..44fd146bac --- /dev/null +++ b/gfx/skia/skia/src/core/SkPath_serial.cpp @@ -0,0 +1,292 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkMath.h" +#include "include/private/SkPathRef.h" +#include "include/private/SkTo.h" +#include "src/core/SkBuffer.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkRRectPriv.h" +#include "src/core/SkSafeMath.h" + +#include + +enum SerializationOffsets { + kType_SerializationShift = 28, // requires 4 bits + kDirection_SerializationShift = 26, // requires 2 bits + kFillType_SerializationShift = 8, // requires 8 bits + // low-8-bits are version + kVersion_SerializationMask = 0xFF, +}; + +enum SerializationVersions { + // kPathPrivFirstDirection_Version = 1, + // kPathPrivLastMoveToIndex_Version = 2, + // kPathPrivTypeEnumVersion = 3, + kJustPublicData_Version = 4, // introduced Feb/2018 + kVerbsAreStoredForward_Version = 5, // introduced Sept/2019 + + kMin_Version = kJustPublicData_Version, + kCurrent_Version = kVerbsAreStoredForward_Version +}; + +enum SerializationType { + kGeneral = 0, + kRRect = 1 +}; + +static unsigned extract_version(uint32_t packed) { + return packed & kVersion_SerializationMask; +} + +static SkPath::FillType extract_filltype(uint32_t packed) { + return static_cast((packed >> kFillType_SerializationShift) & 0x3); +} + +static SerializationType extract_serializationtype(uint32_t packed) { + return static_cast((packed >> kType_SerializationShift) & 0xF); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +size_t SkPath::writeToMemoryAsRRect(void* storage) const { + SkRect oval; + SkRRect rrect; + bool isCCW; + unsigned start; + if (fPathRef->isOval(&oval, &isCCW, &start)) { + rrect.setOval(oval); + // Convert to rrect start indices. + start *= 2; + } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) { + return 0; + } + + // packed header, rrect, start index. + const size_t sizeNeeded = sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t); + if (!storage) { + return sizeNeeded; + } + + int firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection; + int32_t packed = (fFillType << kFillType_SerializationShift) | + (firstDir << kDirection_SerializationShift) | + (SerializationType::kRRect << kType_SerializationShift) | + kCurrent_Version; + + SkWBuffer buffer(storage); + buffer.write32(packed); + SkRRectPriv::WriteToBuffer(rrect, &buffer); + buffer.write32(SkToS32(start)); + buffer.padToAlign4(); + SkASSERT(sizeNeeded == buffer.pos()); + return buffer.pos(); +} + +size_t SkPath::writeToMemory(void* storage) const { + SkDEBUGCODE(this->validate();) + + if (size_t bytes = this->writeToMemoryAsRRect(storage)) { + return bytes; + } + + int32_t packed = (fFillType << kFillType_SerializationShift) | + (SerializationType::kGeneral << kType_SerializationShift) | + kCurrent_Version; + + int32_t pts = fPathRef->countPoints(); + int32_t cnx = fPathRef->countWeights(); + int32_t vbs = fPathRef->countVerbs(); + + SkSafeMath safe; + size_t size = 4 * sizeof(int32_t); + size = safe.add(size, safe.mul(pts, sizeof(SkPoint))); + size = safe.add(size, safe.mul(cnx, sizeof(SkScalar))); + size = safe.add(size, safe.mul(vbs, sizeof(uint8_t))); + size = safe.alignUp(size, 4); + if (!safe) { + return 0; + } + if (!storage) { + return size; + } + + SkWBuffer buffer(storage); + buffer.write32(packed); + buffer.write32(pts); + buffer.write32(cnx); + buffer.write32(vbs); + buffer.write(fPathRef->points(), pts * sizeof(SkPoint)); + buffer.write(fPathRef->conicWeights(), cnx * sizeof(SkScalar)); + buffer.write(fPathRef->verbsBegin(), vbs * sizeof(uint8_t)); + buffer.padToAlign4(); + + SkASSERT(buffer.pos() == size); + return size; +} + +sk_sp SkPath::serialize() const { + size_t size = this->writeToMemory(nullptr); + sk_sp data = SkData::MakeUninitialized(size); + this->writeToMemory(data->writable_data()); + return data; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// reading + +size_t SkPath::readFromMemory(const void* storage, size_t length) { + SkRBuffer buffer(storage, length); + uint32_t packed; + if (!buffer.readU32(&packed)) { + return 0; + } + unsigned version = extract_version(packed); + if (version < kMin_Version || version > kCurrent_Version) { + return 0; + } + + if (version == kJustPublicData_Version || version == kVerbsAreStoredForward_Version) { + return this->readFromMemory_EQ4Or5(storage, length); + } + return 0; +} + +size_t SkPath::readAsRRect(const void* storage, size_t length) { + SkRBuffer buffer(storage, length); + uint32_t packed; + if (!buffer.readU32(&packed)) { + return 0; + } + + SkASSERT(extract_serializationtype(packed) == SerializationType::kRRect); + + uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3; + FillType fillType = extract_filltype(packed); + + Direction rrectDir; + SkRRect rrect; + int32_t start; + switch (dir) { + case SkPathPriv::kCW_FirstDirection: + rrectDir = kCW_Direction; + break; + case SkPathPriv::kCCW_FirstDirection: + rrectDir = kCCW_Direction; + break; + default: + return 0; + } + if (!SkRRectPriv::ReadFromBuffer(&buffer, &rrect)) { + return 0; + } + if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) { + return 0; + } + this->reset(); + this->addRRect(rrect, rrectDir, SkToUInt(start)); + this->setFillType(fillType); + buffer.skipToAlign4(); + return buffer.pos(); +} + +size_t SkPath::readFromMemory_EQ4Or5(const void* storage, size_t length) { + SkRBuffer buffer(storage, length); + uint32_t packed; + if (!buffer.readU32(&packed)) { + return 0; + } + + bool verbsAreReversed = true; + if (extract_version(packed) == kVerbsAreStoredForward_Version) { + verbsAreReversed = false; + } + + switch (extract_serializationtype(packed)) { + case SerializationType::kRRect: + return this->readAsRRect(storage, length); + case SerializationType::kGeneral: + break; // fall through + default: + return 0; + } + + int32_t pts, cnx, vbs; + if (!buffer.readS32(&pts) || !buffer.readS32(&cnx) || !buffer.readS32(&vbs)) { + return 0; + } + + const SkPoint* points = buffer.skipCount(pts); + const SkScalar* conics = buffer.skipCount(cnx); + const uint8_t* verbs = buffer.skipCount(vbs); + buffer.skipToAlign4(); + if (!buffer.isValid()) { + return 0; + } + SkASSERT(buffer.pos() <= length); + +#define CHECK_POINTS_CONICS(p, c) \ + do { \ + if (p && ((pts -= p) < 0)) { \ + return 0; \ + } \ + if (c && ((cnx -= c) < 0)) { \ + return 0; \ + } \ + } while (0) + + int verbsStep = 1; + if (verbsAreReversed) { + verbs += vbs - 1; + verbsStep = -1; + } + + SkPath tmp; + tmp.setFillType(extract_filltype(packed)); + tmp.incReserve(pts); + for (int i = 0; i < vbs; ++i) { + switch (*verbs) { + case kMove_Verb: + CHECK_POINTS_CONICS(1, 0); + tmp.moveTo(*points++); + break; + case kLine_Verb: + CHECK_POINTS_CONICS(1, 0); + tmp.lineTo(*points++); + break; + case kQuad_Verb: + CHECK_POINTS_CONICS(2, 0); + tmp.quadTo(points[0], points[1]); + points += 2; + break; + case kConic_Verb: + CHECK_POINTS_CONICS(2, 1); + tmp.conicTo(points[0], points[1], *conics++); + points += 2; + break; + case kCubic_Verb: + CHECK_POINTS_CONICS(3, 0); + tmp.cubicTo(points[0], points[1], points[2]); + points += 3; + break; + case kClose_Verb: + tmp.close(); + break; + default: + return 0; // bad verb + } + verbs += verbsStep; + } +#undef CHECK_POINTS_CONICS + if (pts || cnx) { + return 0; // leftover points and/or conics + } + + *this = std::move(tmp); + return buffer.pos(); +} diff --git a/gfx/skia/skia/src/core/SkPicture.cpp b/gfx/skia/skia/src/core/SkPicture.cpp new file mode 100644 index 0000000000..e041ba64f0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPicture.cpp @@ -0,0 +1,333 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPicture.h" + +#include "include/core/SkImageGenerator.h" +#include "include/core/SkPictureRecorder.h" +#include "include/core/SkSerialProcs.h" +#include "include/private/SkTo.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkPictureCommon.h" +#include "src/core/SkPictureData.h" +#include "src/core/SkPicturePlayback.h" +#include "src/core/SkPicturePriv.h" +#include "src/core/SkPictureRecord.h" +#include + +// When we read/write the SkPictInfo via a stream, we have a sentinel byte right after the info. +// Note: in the read/write buffer versions, we have a slightly different convention: +// We have a sentinel int32_t: +// 0 : failure +// 1 : PictureData +// <0 : -size of the custom data +enum { + kFailure_TrailingStreamByteAfterPictInfo = 0, // nothing follows + kPictureData_TrailingStreamByteAfterPictInfo = 1, // SkPictureData follows + kCustom_TrailingStreamByteAfterPictInfo = 2, // -size32 follows +}; + +/* SkPicture impl. This handles generic responsibilities like unique IDs and serialization. */ + +SkPicture::SkPicture() { + static std::atomic nextID{1}; + do { + fUniqueID = nextID.fetch_add(+1, std::memory_order_relaxed); + } while (fUniqueID == 0); +} + +static const char kMagic[] = { 's', 'k', 'i', 'a', 'p', 'i', 'c', 't' }; + +SkPictInfo SkPicture::createHeader() const { + SkPictInfo info; + // Copy magic bytes at the beginning of the header + static_assert(sizeof(kMagic) == 8, ""); + static_assert(sizeof(kMagic) == sizeof(info.fMagic), ""); + memcpy(info.fMagic, kMagic, sizeof(kMagic)); + + // Set picture info after magic bytes in the header + info.setVersion(SkPicturePriv::kCurrent_Version); + info.fCullRect = this->cullRect(); + return info; +} + +bool SkPicture::IsValidPictInfo(const SkPictInfo& info) { + if (0 != memcmp(info.fMagic, kMagic, sizeof(kMagic))) { + return false; + } + if (info.getVersion() < SkPicturePriv::kMin_Version || + info.getVersion() > SkPicturePriv::kCurrent_Version) { + return false; + } + return true; +} + +bool SkPicture::StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) { + if (!stream) { + return false; + } + + SkPictInfo info; + SkASSERT(sizeof(kMagic) == sizeof(info.fMagic)); + if (stream->read(&info.fMagic, sizeof(kMagic)) != sizeof(kMagic)) { + return false; + } + + uint32_t version; + if (!stream->readU32(&version)) { return false; } + info.setVersion(version); + if (!stream->readScalar(&info.fCullRect.fLeft )) { return false; } + if (!stream->readScalar(&info.fCullRect.fTop )) { return false; } + if (!stream->readScalar(&info.fCullRect.fRight )) { return false; } + if (!stream->readScalar(&info.fCullRect.fBottom)) { return false; } + if (info.getVersion() < SkPicturePriv::kRemoveHeaderFlags_Version) { + if (!stream->readU32(nullptr)) { return false; } + } + + if (!IsValidPictInfo(info)) { return false; } + + if (pInfo) { *pInfo = info; } + return true; +} +bool SkPicture_StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) { + return SkPicture::StreamIsSKP(stream, pInfo); +} + +bool SkPicture::BufferIsSKP(SkReadBuffer* buffer, SkPictInfo* pInfo) { + SkPictInfo info; + SkASSERT(sizeof(kMagic) == sizeof(info.fMagic)); + if (!buffer->readByteArray(&info.fMagic, sizeof(kMagic))) { + return false; + } + + info.setVersion(buffer->readUInt()); + buffer->readRect(&info.fCullRect); + if (info.getVersion() < SkPicturePriv::kRemoveHeaderFlags_Version) { + (void)buffer->readUInt(); // used to be flags + } + + if (IsValidPictInfo(info)) { + if (pInfo) { *pInfo = info; } + return true; + } + return false; +} + +sk_sp SkPicture::Forwardport(const SkPictInfo& info, + const SkPictureData* data, + SkReadBuffer* buffer) { + if (!data) { + return nullptr; + } + if (!data->opData()) { + return nullptr; + } + SkPicturePlayback playback(data); + SkPictureRecorder r; + playback.draw(r.beginRecording(info.fCullRect), nullptr/*no callback*/, buffer); + return r.finishRecordingAsPicture(); +} + +sk_sp SkPicture::MakeFromStream(SkStream* stream, const SkDeserialProcs* procs) { + return MakeFromStream(stream, procs, nullptr); +} + +sk_sp SkPicture::MakeFromData(const void* data, size_t size, + const SkDeserialProcs* procs) { + if (!data) { + return nullptr; + } + SkMemoryStream stream(data, size); + return MakeFromStream(&stream, procs, nullptr); +} + +sk_sp SkPicture::MakeFromData(const SkData* data, const SkDeserialProcs* procs) { + if (!data) { + return nullptr; + } + SkMemoryStream stream(data->data(), data->size()); + return MakeFromStream(&stream, procs, nullptr); +} + +sk_sp SkPicture::MakeFromStream(SkStream* stream, const SkDeserialProcs* procsPtr, + SkTypefacePlayback* typefaces) { + SkPictInfo info; + if (!StreamIsSKP(stream, &info)) { + return nullptr; + } + + SkDeserialProcs procs; + if (procsPtr) { + procs = *procsPtr; + } + + uint8_t trailingStreamByteAfterPictInfo; + if (!stream->readU8(&trailingStreamByteAfterPictInfo)) { return nullptr; } + switch (trailingStreamByteAfterPictInfo) { + case kPictureData_TrailingStreamByteAfterPictInfo: { + std::unique_ptr data( + SkPictureData::CreateFromStream(stream, info, procs, typefaces)); + return Forwardport(info, data.get(), nullptr); + } + case kCustom_TrailingStreamByteAfterPictInfo: { + int32_t ssize; + if (!stream->readS32(&ssize) || ssize >= 0 || !procs.fPictureProc) { + return nullptr; + } + size_t size = sk_negate_to_size_t(ssize); + auto data = SkData::MakeUninitialized(size); + if (stream->read(data->writable_data(), size) != size) { + return nullptr; + } + return procs.fPictureProc(data->data(), size, procs.fPictureCtx); + } + default: // fall through to error return + break; + } + return nullptr; +} + +sk_sp SkPicturePriv::MakeFromBuffer(SkReadBuffer& buffer) { + SkPictInfo info; + if (!SkPicture::BufferIsSKP(&buffer, &info)) { + return nullptr; + } + // size should be 0, 1, or negative + int32_t ssize = buffer.read32(); + if (ssize < 0) { + const SkDeserialProcs& procs = buffer.getDeserialProcs(); + if (!procs.fPictureProc) { + return nullptr; + } + size_t size = sk_negate_to_size_t(ssize); + return procs.fPictureProc(buffer.skip(size), size, procs.fPictureCtx); + } + if (ssize != 1) { + // 1 is the magic 'size' that means SkPictureData follows + return nullptr; + } + std::unique_ptr data(SkPictureData::CreateFromBuffer(buffer, info)); + return SkPicture::Forwardport(info, data.get(), &buffer); +} + +SkPictureData* SkPicture::backport() const { + SkPictInfo info = this->createHeader(); + SkPictureRecord rec(info.fCullRect.roundOut(), 0/*flags*/); + rec.beginRecording(); + this->playback(&rec); + rec.endRecording(); + return new SkPictureData(rec, info); +} + +void SkPicture::serialize(SkWStream* stream, const SkSerialProcs* procs) const { + this->serialize(stream, procs, nullptr); +} + +sk_sp SkPicture::serialize(const SkSerialProcs* procs) const { + SkDynamicMemoryWStream stream; + this->serialize(&stream, procs, nullptr); + return stream.detachAsData(); +} + +static sk_sp custom_serialize(const SkPicture* picture, const SkSerialProcs& procs) { + if (procs.fPictureProc) { + auto data = procs.fPictureProc(const_cast(picture), procs.fPictureCtx); + if (data) { + size_t size = data->size(); + if (!SkTFitsIn(size) || size <= 1) { + return SkData::MakeEmpty(); + } + return data; + } + } + return nullptr; +} + +static bool write_pad32(SkWStream* stream, const void* data, size_t size) { + if (!stream->write(data, size)) { + return false; + } + if (size & 3) { + uint32_t zero = 0; + return stream->write(&zero, 4 - (size & 3)); + } + return true; +} + +// Private serialize. +// SkPictureData::serialize makes a first pass on all subpictures, indicatewd by textBlobsOnly=true, +// to fill typefaceSet. +void SkPicture::serialize(SkWStream* stream, const SkSerialProcs* procsPtr, + SkRefCntSet* typefaceSet, bool textBlobsOnly) const { + SkSerialProcs procs; + if (procsPtr) { + procs = *procsPtr; + } + + SkPictInfo info = this->createHeader(); + stream->write(&info, sizeof(info)); + + if (auto custom = custom_serialize(this, procs)) { + int32_t size = SkToS32(custom->size()); + if (size == 0) { + stream->write8(kFailure_TrailingStreamByteAfterPictInfo); + return; + } + stream->write8(kCustom_TrailingStreamByteAfterPictInfo); + stream->write32(-size); // negative for custom format + write_pad32(stream, custom->data(), size); + return; + } + + std::unique_ptr data(this->backport()); + if (data) { + stream->write8(kPictureData_TrailingStreamByteAfterPictInfo); + data->serialize(stream, procs, typefaceSet, textBlobsOnly); + } else { + stream->write8(kFailure_TrailingStreamByteAfterPictInfo); + } +} + +void SkPicturePriv::Flatten(const sk_sp picture, SkWriteBuffer& buffer) { + SkPictInfo info = picture->createHeader(); + std::unique_ptr data(picture->backport()); + + buffer.writeByteArray(&info.fMagic, sizeof(info.fMagic)); + buffer.writeUInt(info.getVersion()); + buffer.writeRect(info.fCullRect); + + if (auto custom = custom_serialize(picture.get(), buffer.fProcs)) { + int32_t size = SkToS32(custom->size()); + buffer.write32(-size); // negative for custom format + buffer.writePad32(custom->data(), size); + return; + } + + if (data) { + buffer.write32(1); // special size meaning SkPictureData + data->flatten(buffer); + } else { + buffer.write32(0); // signal no content + } +} + +sk_sp SkPicture::MakePlaceholder(SkRect cull) { + struct Placeholder : public SkPicture { + explicit Placeholder(SkRect cull) : fCull(cull) {} + + void playback(SkCanvas*, AbortCallback*) const override { } + + // approximateOpCount() needs to be greater than kMaxPictureOpsToUnrollInsteadOfRef + // in SkCanvas.cpp to avoid that unrolling. SK_MaxS32 can't not be big enough! + int approximateOpCount() const override { return SK_MaxS32; } + size_t approximateBytesUsed() const override { return sizeof(*this); } + SkRect cullRect() const override { return fCull; } + + SkRect fCull; + }; + return sk_make_sp(cull); +} diff --git a/gfx/skia/skia/src/core/SkPictureCommon.h b/gfx/skia/skia/src/core/SkPictureCommon.h new file mode 100644 index 0000000000..2a10699ca6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureCommon.h @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureCommon_DEFINED +#define SkPictureCommon_DEFINED + +// Some shared code used by both SkBigPicture and SkMiniPicture. +// SkTextHunter -- SkRecord visitor that returns true when the op draws text. +// SkPathCounter -- SkRecord visitor that counts paths that draw slowly on the GPU. + +#include "include/core/SkPathEffect.h" +#include "include/core/SkShader.h" +#include "include/private/SkTLogic.h" +#include "src/core/SkRecords.h" + +// TODO: might be nicer to have operator() return an int (the number of slow paths) ? +struct SkPathCounter { + // Some ops have a paint, some have an optional paint. Either way, get back a pointer. + static const SkPaint* AsPtr(const SkPaint& p) { return &p; } + static const SkPaint* AsPtr(const SkRecords::Optional& p) { return p; } + + SkPathCounter() : fNumSlowPathsAndDashEffects(0) {} + + void checkPaint(const SkPaint* paint) { + if (paint && paint->getPathEffect()) { + // Initially assume it's slow. + fNumSlowPathsAndDashEffects++; + } + } + + void operator()(const SkRecords::DrawPoints& op) { + this->checkPaint(&op.paint); + const SkPathEffect* effect = op.paint.getPathEffect(); + if (effect) { + SkPathEffect::DashInfo info; + SkPathEffect::DashType dashType = effect->asADash(&info); + if (2 == op.count && SkPaint::kRound_Cap != op.paint.getStrokeCap() && + SkPathEffect::kDash_DashType == dashType && 2 == info.fCount) { + fNumSlowPathsAndDashEffects--; + } + } + } + + void operator()(const SkRecords::DrawPath& op) { + this->checkPaint(&op.paint); + if (op.paint.isAntiAlias() && !op.path.isConvex()) { + SkPaint::Style paintStyle = op.paint.getStyle(); + const SkRect& pathBounds = op.path.getBounds(); + if (SkPaint::kStroke_Style == paintStyle && + 0 == op.paint.getStrokeWidth()) { + // AA hairline concave path is not slow. + } else if (SkPaint::kFill_Style == paintStyle && pathBounds.width() < 64.f && + pathBounds.height() < 64.f && !op.path.isVolatile()) { + // AADF eligible concave path is not slow. + } else { + fNumSlowPathsAndDashEffects++; + } + } + } + + void operator()(const SkRecords::ClipPath& op) { + // TODO: does the SkRegion op matter? + if (op.opAA.aa() && !op.path.isConvex()) { + fNumSlowPathsAndDashEffects++; + } + } + + void operator()(const SkRecords::SaveLayer& op) { + this->checkPaint(AsPtr(op.paint)); + } + + template + SK_WHEN(T::kTags & SkRecords::kHasPaint_Tag, void) operator()(const T& op) { + this->checkPaint(AsPtr(op.paint)); + } + + template + SK_WHEN(!(T::kTags & SkRecords::kHasPaint_Tag), void) + operator()(const T& op) { /* do nothing */ } + + int fNumSlowPathsAndDashEffects; +}; + +sk_sp ImageDeserializer_SkDeserialImageProc(const void*, size_t, void* imagedeserializer); + +bool SkPicture_StreamIsSKP(SkStream*, SkPictInfo*); + +#endif // SkPictureCommon_DEFINED diff --git a/gfx/skia/skia/src/core/SkPictureData.cpp b/gfx/skia/skia/src/core/SkPictureData.cpp new file mode 100644 index 0000000000..9fd2768d6e --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureData.cpp @@ -0,0 +1,536 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkPictureData.h" + +#include "include/core/SkImageGenerator.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkTo.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkPicturePriv.h" +#include "src/core/SkPictureRecord.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/core/SkWriteBuffer.h" + +#include + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#endif + +template int SafeCount(const T* obj) { + return obj ? obj->count() : 0; +} + +SkPictureData::SkPictureData(const SkPictInfo& info) + : fInfo(info) {} + +void SkPictureData::initForPlayback() const { + // ensure that the paths bounds are pre-computed + for (int i = 0; i < fPaths.count(); i++) { + fPaths[i].updateBoundsCache(); + } +} + +SkPictureData::SkPictureData(const SkPictureRecord& record, + const SkPictInfo& info) + : fPictures(record.getPictures()) + , fDrawables(record.getDrawables()) + , fTextBlobs(record.getTextBlobs()) + , fVertices(record.getVertices()) + , fImages(record.getImages()) + , fInfo(info) { + + fOpData = record.opData(); + + fPaints = record.fPaints; + + fPaths.reset(record.fPaths.count()); + record.fPaths.foreach([this](const SkPath& path, int n) { + // These indices are logically 1-based, but we need to serialize them + // 0-based to keep the deserializing SkPictureData::getPath() working. + fPaths[n-1] = path; + }); + + this->initForPlayback(); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkStream.h" + +static size_t compute_chunk_size(SkFlattenable::Factory* array, int count) { + size_t size = 4; // for 'count' + + for (int i = 0; i < count; i++) { + const char* name = SkFlattenable::FactoryToName(array[i]); + if (nullptr == name || 0 == *name) { + size += SkWStream::SizeOfPackedUInt(0); + } else { + size_t len = strlen(name); + size += SkWStream::SizeOfPackedUInt(len); + size += len; + } + } + + return size; +} + +static void write_tag_size(SkWriteBuffer& buffer, uint32_t tag, size_t size) { + buffer.writeUInt(tag); + buffer.writeUInt(SkToU32(size)); +} + +static void write_tag_size(SkWStream* stream, uint32_t tag, size_t size) { + stream->write32(tag); + stream->write32(SkToU32(size)); +} + +void SkPictureData::WriteFactories(SkWStream* stream, const SkFactorySet& rec) { + int count = rec.count(); + + SkAutoSTMalloc<16, SkFlattenable::Factory> storage(count); + SkFlattenable::Factory* array = (SkFlattenable::Factory*)storage.get(); + rec.copyToArray(array); + + size_t size = compute_chunk_size(array, count); + + // TODO: write_tag_size should really take a size_t + write_tag_size(stream, SK_PICT_FACTORY_TAG, (uint32_t) size); + SkDEBUGCODE(size_t start = stream->bytesWritten()); + stream->write32(count); + + for (int i = 0; i < count; i++) { + const char* name = SkFlattenable::FactoryToName(array[i]); + if (nullptr == name || 0 == *name) { + stream->writePackedUInt(0); + } else { + size_t len = strlen(name); + stream->writePackedUInt(len); + stream->write(name, len); + } + } + + SkASSERT(size == (stream->bytesWritten() - start)); +} + +void SkPictureData::WriteTypefaces(SkWStream* stream, const SkRefCntSet& rec, + const SkSerialProcs& procs) { + int count = rec.count(); + + write_tag_size(stream, SK_PICT_TYPEFACE_TAG, count); + + SkAutoSTMalloc<16, SkTypeface*> storage(count); + SkTypeface** array = (SkTypeface**)storage.get(); + rec.copyToArray((SkRefCnt**)array); + + for (int i = 0; i < count; i++) { + SkTypeface* tf = array[i]; + if (procs.fTypefaceProc) { + auto data = procs.fTypefaceProc(tf, procs.fTypefaceCtx); + if (data) { + stream->write(data->data(), data->size()); + continue; + } + } + array[i]->serialize(stream); + } +} + +void SkPictureData::flattenToBuffer(SkWriteBuffer& buffer, bool textBlobsOnly) const { + int i, n; + + if (!textBlobsOnly) { + if ((n = fPaints.count()) > 0) { + write_tag_size(buffer, SK_PICT_PAINT_BUFFER_TAG, n); + for (i = 0; i < n; i++) { + buffer.writePaint(fPaints[i]); + } + } + + if ((n = fPaths.count()) > 0) { + write_tag_size(buffer, SK_PICT_PATH_BUFFER_TAG, n); + buffer.writeInt(n); + for (int i = 0; i < n; i++) { + buffer.writePath(fPaths[i]); + } + } + } + + if (!fTextBlobs.empty()) { + write_tag_size(buffer, SK_PICT_TEXTBLOB_BUFFER_TAG, fTextBlobs.count()); + for (const auto& blob : fTextBlobs) { + SkTextBlobPriv::Flatten(*blob, buffer); + } + } + + if (!textBlobsOnly) { + if (!fVertices.empty()) { + write_tag_size(buffer, SK_PICT_VERTICES_BUFFER_TAG, fVertices.count()); + for (const auto& vert : fVertices) { + buffer.writeDataAsByteArray(vert->encode().get()); + } + } + + if (!fImages.empty()) { + write_tag_size(buffer, SK_PICT_IMAGE_BUFFER_TAG, fImages.count()); + for (const auto& img : fImages) { + buffer.writeImage(img.get()); + } + } + } +} + +// SkPictureData::serialize() will write out paints, and then write out an array of typefaces +// (unique set). However, paint's serializer will respect SerialProcs, which can cause us to +// call that custom typefaceproc on *every* typeface, not just on the unique ones. To avoid this, +// we ignore the custom proc (here) when we serialize the paints, and then do respect it when +// we serialize the typefaces. +static SkSerialProcs skip_typeface_proc(const SkSerialProcs& procs) { + SkSerialProcs newProcs = procs; + newProcs.fTypefaceProc = nullptr; + newProcs.fTypefaceCtx = nullptr; + return newProcs; +} + +// topLevelTypeFaceSet is null only on the top level call. +// This method is called recursively on every subpicture in two passes. +// textBlobsOnly serves to indicate that we are on the first pass and skip as much work as +// possible that is not relevant to collecting text blobs in topLevelTypeFaceSet +// TODO(nifong): dedupe typefaces and all other shared resources in a faster and more readable way. +void SkPictureData::serialize(SkWStream* stream, const SkSerialProcs& procs, + SkRefCntSet* topLevelTypeFaceSet, bool textBlobsOnly) const { + // This can happen at pretty much any time, so might as well do it first. + write_tag_size(stream, SK_PICT_READER_TAG, fOpData->size()); + stream->write(fOpData->bytes(), fOpData->size()); + + // We serialize all typefaces into the typeface section of the top-level picture. + SkRefCntSet localTypefaceSet; + SkRefCntSet* typefaceSet = topLevelTypeFaceSet ? topLevelTypeFaceSet : &localTypefaceSet; + + // We delay serializing the bulk of our data until after we've serialized + // factories and typefaces by first serializing to an in-memory write buffer. + SkFactorySet factSet; // buffer refs factSet, so factSet must come first. + SkBinaryWriteBuffer buffer; + buffer.setFactoryRecorder(sk_ref_sp(&factSet)); + buffer.setSerialProcs(skip_typeface_proc(procs)); + buffer.setTypefaceRecorder(sk_ref_sp(typefaceSet)); + this->flattenToBuffer(buffer, textBlobsOnly); + + // Dummy serialize our sub-pictures for the side effect of filling typefaceSet + // with typefaces from sub-pictures. + struct DevNull: public SkWStream { + DevNull() : fBytesWritten(0) {} + size_t fBytesWritten; + bool write(const void*, size_t size) override { fBytesWritten += size; return true; } + size_t bytesWritten() const override { return fBytesWritten; } + } devnull; + for (const auto& pic : fPictures) { + pic->serialize(&devnull, nullptr, typefaceSet, /*textBlobsOnly=*/ true); + } + if (textBlobsOnly) { return; } // return early from dummy serialize + + // We need to write factories before we write the buffer. + // We need to write typefaces before we write the buffer or any sub-picture. + WriteFactories(stream, factSet); + // Pass the original typefaceproc (if any) now that we're ready to actually serialize the + // typefaces. We skipped this proc before, when we were serializing paints, so that the + // paints would just write indices into our typeface set. + WriteTypefaces(stream, *typefaceSet, procs); + + // Write the buffer. + write_tag_size(stream, SK_PICT_BUFFER_SIZE_TAG, buffer.bytesWritten()); + buffer.writeToStream(stream); + + // Write sub-pictures by calling serialize again. + if (!fPictures.empty()) { + write_tag_size(stream, SK_PICT_PICTURE_TAG, fPictures.count()); + for (const auto& pic : fPictures) { + pic->serialize(stream, &procs, typefaceSet, /*textBlobsOnly=*/ false); + } + } + + stream->write32(SK_PICT_EOF_TAG); +} + +void SkPictureData::flatten(SkWriteBuffer& buffer) const { + write_tag_size(buffer, SK_PICT_READER_TAG, fOpData->size()); + buffer.writeByteArray(fOpData->bytes(), fOpData->size()); + + if (!fPictures.empty()) { + write_tag_size(buffer, SK_PICT_PICTURE_TAG, fPictures.count()); + for (const auto& pic : fPictures) { + SkPicturePriv::Flatten(pic, buffer); + } + } + + if (!fDrawables.empty()) { + write_tag_size(buffer, SK_PICT_DRAWABLE_TAG, fDrawables.count()); + for (const auto& draw : fDrawables) { + buffer.writeFlattenable(draw.get()); + } + } + + // Write this picture playback's data into a writebuffer + this->flattenToBuffer(buffer, false); + buffer.write32(SK_PICT_EOF_TAG); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkPictureData::parseStreamTag(SkStream* stream, + uint32_t tag, + uint32_t size, + const SkDeserialProcs& procs, + SkTypefacePlayback* topLevelTFPlayback) { + switch (tag) { + case SK_PICT_READER_TAG: + SkASSERT(nullptr == fOpData); + fOpData = SkData::MakeFromStream(stream, size); + if (!fOpData) { + return false; + } + break; + case SK_PICT_FACTORY_TAG: { + if (!stream->readU32(&size)) { return false; } + fFactoryPlayback = skstd::make_unique(size); + for (size_t i = 0; i < size; i++) { + SkString str; + size_t len; + if (!stream->readPackedUInt(&len)) { return false; } + str.resize(len); + if (stream->read(str.writable_str(), len) != len) { + return false; + } + fFactoryPlayback->base()[i] = SkFlattenable::NameToFactory(str.c_str()); + } + } break; + case SK_PICT_TYPEFACE_TAG: { + fTFPlayback.setCount(size); + for (uint32_t i = 0; i < size; ++i) { + sk_sp tf(SkTypeface::MakeDeserialize(stream)); + if (!tf.get()) { // failed to deserialize + // fTFPlayback asserts it never has a null, so we plop in + // the default here. + tf = SkTypeface::MakeDefault(); + } + fTFPlayback[i] = std::move(tf); + } + } break; + case SK_PICT_PICTURE_TAG: { + SkASSERT(fPictures.empty()); + fPictures.reserve(SkToInt(size)); + + for (uint32_t i = 0; i < size; i++) { + auto pic = SkPicture::MakeFromStream(stream, &procs, topLevelTFPlayback); + if (!pic) { + return false; + } + fPictures.push_back(std::move(pic)); + } + } break; + case SK_PICT_BUFFER_SIZE_TAG: { + SkAutoMalloc storage(size); + if (stream->read(storage.get(), size) != size) { + return false; + } + + SkReadBuffer buffer(storage.get(), size); + buffer.setVersion(fInfo.getVersion()); + + if (!fFactoryPlayback) { + return false; + } + fFactoryPlayback->setupBuffer(buffer); + buffer.setDeserialProcs(procs); + + if (fTFPlayback.count() > 0) { + // .skp files <= v43 have typefaces serialized with each sub picture. + fTFPlayback.setupBuffer(buffer); + } else { + // Newer .skp files serialize all typefaces with the top picture. + topLevelTFPlayback->setupBuffer(buffer); + } + + while (!buffer.eof() && buffer.isValid()) { + tag = buffer.readUInt(); + size = buffer.readUInt(); + this->parseBufferTag(buffer, tag, size); + } + if (!buffer.isValid()) { + return false; + } + } break; + } + return true; // success +} + +static sk_sp create_image_from_buffer(SkReadBuffer& buffer) { + return buffer.readImage(); +} +static sk_sp create_vertices_from_buffer(SkReadBuffer& buffer) { + auto data = buffer.readByteArrayAsData(); + return data ? SkVertices::Decode(data->data(), data->size()) : nullptr; +} + +static sk_sp create_drawable_from_buffer(SkReadBuffer& buffer) { + return sk_sp((SkDrawable*)buffer.readFlattenable(SkFlattenable::kSkDrawable_Type)); +} + +// We need two types 'cause SkDrawable is const-variant. +template +bool new_array_from_buffer(SkReadBuffer& buffer, uint32_t inCount, + SkTArray>& array, sk_sp (*factory)(SkReadBuffer&)) { + if (!buffer.validate(array.empty() && SkTFitsIn(inCount))) { + return false; + } + if (0 == inCount) { + return true; + } + + for (uint32_t i = 0; i < inCount; ++i) { + auto obj = factory(buffer); + + if (!buffer.validate(obj != nullptr)) { + array.reset(); + return false; + } + + array.push_back(std::move(obj)); + } + + return true; +} + +void SkPictureData::parseBufferTag(SkReadBuffer& buffer, uint32_t tag, uint32_t size) { + switch (tag) { + case SK_PICT_PAINT_BUFFER_TAG: { + if (!buffer.validate(SkTFitsIn(size))) { + return; + } + const int count = SkToInt(size); + + for (int i = 0; i < count; ++i) { + // Do we need to keep an array of fFonts for legacy draws? + if (!buffer.readPaint(&fPaints.push_back(), nullptr)) { + return; + } + } + } break; + case SK_PICT_PATH_BUFFER_TAG: + if (size > 0) { + const int count = buffer.readInt(); + if (!buffer.validate(count >= 0)) { + return; + } + for (int i = 0; i < count; i++) { + buffer.readPath(&fPaths.push_back()); + if (!buffer.isValid()) { + return; + } + } + } break; + case SK_PICT_TEXTBLOB_BUFFER_TAG: + new_array_from_buffer(buffer, size, fTextBlobs, SkTextBlobPriv::MakeFromBuffer); + break; + case SK_PICT_VERTICES_BUFFER_TAG: + new_array_from_buffer(buffer, size, fVertices, create_vertices_from_buffer); + break; + case SK_PICT_IMAGE_BUFFER_TAG: + new_array_from_buffer(buffer, size, fImages, create_image_from_buffer); + break; + case SK_PICT_READER_TAG: { + // Preflight check that we can initialize all data from the buffer + // before allocating it. + if (!buffer.validateCanReadN(size)) { + return; + } + auto data(SkData::MakeUninitialized(size)); + if (!buffer.readByteArray(data->writable_data(), size) || + !buffer.validate(nullptr == fOpData)) { + return; + } + SkASSERT(nullptr == fOpData); + fOpData = std::move(data); + } break; + case SK_PICT_PICTURE_TAG: + new_array_from_buffer(buffer, size, fPictures, SkPicturePriv::MakeFromBuffer); + break; + case SK_PICT_DRAWABLE_TAG: + new_array_from_buffer(buffer, size, fDrawables, create_drawable_from_buffer); + break; + default: + buffer.validate(false); // The tag was invalid. + break; + } +} + +SkPictureData* SkPictureData::CreateFromStream(SkStream* stream, + const SkPictInfo& info, + const SkDeserialProcs& procs, + SkTypefacePlayback* topLevelTFPlayback) { + std::unique_ptr data(new SkPictureData(info)); + if (!topLevelTFPlayback) { + topLevelTFPlayback = &data->fTFPlayback; + } + + if (!data->parseStream(stream, procs, topLevelTFPlayback)) { + return nullptr; + } + return data.release(); +} + +SkPictureData* SkPictureData::CreateFromBuffer(SkReadBuffer& buffer, + const SkPictInfo& info) { + std::unique_ptr data(new SkPictureData(info)); + buffer.setVersion(info.getVersion()); + + if (!data->parseBuffer(buffer)) { + return nullptr; + } + return data.release(); +} + +bool SkPictureData::parseStream(SkStream* stream, + const SkDeserialProcs& procs, + SkTypefacePlayback* topLevelTFPlayback) { + for (;;) { + uint32_t tag; + if (!stream->readU32(&tag)) { return false; } + if (SK_PICT_EOF_TAG == tag) { + break; + } + + uint32_t size; + if (!stream->readU32(&size)) { return false; } + if (!this->parseStreamTag(stream, tag, size, procs, topLevelTFPlayback)) { + return false; // we're invalid + } + } + return true; +} + +bool SkPictureData::parseBuffer(SkReadBuffer& buffer) { + while (buffer.isValid()) { + uint32_t tag = buffer.readUInt(); + if (SK_PICT_EOF_TAG == tag) { + break; + } + this->parseBufferTag(buffer, tag, buffer.readUInt()); + } + + // Check that we encountered required tags + if (!buffer.validate(this->opData() != nullptr)) { + // If we didn't build any opData, we are invalid. Even an EmptyPicture allocates the + // SkData for the ops (though its length may be zero). + return false; + } + return true; +} diff --git a/gfx/skia/skia/src/core/SkPictureData.h b/gfx/skia/skia/src/core/SkPictureData.h new file mode 100644 index 0000000000..25c9a7acc4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureData.h @@ -0,0 +1,170 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureData_DEFINED +#define SkPictureData_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkPicture.h" +#include "include/private/SkTArray.h" +#include "src/core/SkPictureFlat.h" + +#include + +class SkData; +class SkPictureRecord; +class SkReader32; +struct SkSerialProcs; +class SkStream; +class SkWStream; +class SkBBoxHierarchy; +class SkMatrix; +class SkPaint; +class SkPath; +class SkReadBuffer; +class SkTextBlob; + +struct SkPictInfo { + SkPictInfo() : fVersion(~0U) {} + + uint32_t getVersion() const { + SkASSERT(fVersion != ~0U); + return fVersion; + } + + void setVersion(uint32_t version) { + SkASSERT(version != ~0U); + fVersion = version; + } + +public: + char fMagic[8]; +private: + uint32_t fVersion; +public: + SkRect fCullRect; +}; + +#define SK_PICT_READER_TAG SkSetFourByteTag('r', 'e', 'a', 'd') +#define SK_PICT_FACTORY_TAG SkSetFourByteTag('f', 'a', 'c', 't') +#define SK_PICT_TYPEFACE_TAG SkSetFourByteTag('t', 'p', 'f', 'c') +#define SK_PICT_PICTURE_TAG SkSetFourByteTag('p', 'c', 't', 'r') +#define SK_PICT_DRAWABLE_TAG SkSetFourByteTag('d', 'r', 'a', 'w') + +// This tag specifies the size of the ReadBuffer, needed for the following tags +#define SK_PICT_BUFFER_SIZE_TAG SkSetFourByteTag('a', 'r', 'a', 'y') +// these are all inside the ARRAYS tag +#define SK_PICT_PAINT_BUFFER_TAG SkSetFourByteTag('p', 'n', 't', ' ') +#define SK_PICT_PATH_BUFFER_TAG SkSetFourByteTag('p', 't', 'h', ' ') +#define SK_PICT_TEXTBLOB_BUFFER_TAG SkSetFourByteTag('b', 'l', 'o', 'b') +#define SK_PICT_VERTICES_BUFFER_TAG SkSetFourByteTag('v', 'e', 'r', 't') +#define SK_PICT_IMAGE_BUFFER_TAG SkSetFourByteTag('i', 'm', 'a', 'g') + +// Always write this guy last (with no length field afterwards) +#define SK_PICT_EOF_TAG SkSetFourByteTag('e', 'o', 'f', ' ') + +template +T* read_index_base_1_or_null(SkReadBuffer* reader, const SkTArray>& array) { + int index = reader->readInt(); + return reader->validate(index > 0 && index <= array.count()) ? array[index - 1].get() : nullptr; +} + +class SkPictureData { +public: + SkPictureData(const SkPictureRecord& record, const SkPictInfo&); + // Does not affect ownership of SkStream. + static SkPictureData* CreateFromStream(SkStream*, + const SkPictInfo&, + const SkDeserialProcs&, + SkTypefacePlayback*); + static SkPictureData* CreateFromBuffer(SkReadBuffer&, const SkPictInfo&); + + void serialize(SkWStream*, const SkSerialProcs&, SkRefCntSet*, bool textBlobsOnly=false) const; + void flatten(SkWriteBuffer&) const; + + const sk_sp& opData() const { return fOpData; } + +protected: + explicit SkPictureData(const SkPictInfo& info); + + // Does not affect ownership of SkStream. + bool parseStream(SkStream*, const SkDeserialProcs&, SkTypefacePlayback*); + bool parseBuffer(SkReadBuffer& buffer); + +public: + const SkImage* getImage(SkReadBuffer* reader) const { + // images are written base-0, unlike paths, pictures, drawables, etc. + const int index = reader->readInt(); + return reader->validateIndex(index, fImages.count()) ? fImages[index].get() : nullptr; + } + + const SkPath& getPath(SkReadBuffer* reader) const { + int index = reader->readInt(); + return reader->validate(index > 0 && index <= fPaths.count()) ? + fPaths[index - 1] : fEmptyPath; + } + + const SkPicture* getPicture(SkReadBuffer* reader) const { + return read_index_base_1_or_null(reader, fPictures); + } + + SkDrawable* getDrawable(SkReadBuffer* reader) const { + return read_index_base_1_or_null(reader, fDrawables); + } + + const SkPaint* getPaint(SkReadBuffer* reader) const { + int index = reader->readInt(); + if (index == 0) { + return nullptr; // recorder wrote a zero for no paint (likely drawimage) + } + return reader->validate(index > 0 && index <= fPaints.count()) ? + &fPaints[index - 1] : nullptr; + } + + const SkTextBlob* getTextBlob(SkReadBuffer* reader) const { + return read_index_base_1_or_null(reader, fTextBlobs); + } + + const SkVertices* getVertices(SkReadBuffer* reader) const { + return read_index_base_1_or_null(reader, fVertices); + } + +private: + // these help us with reading/writing + // Does not affect ownership of SkStream. + bool parseStreamTag(SkStream*, uint32_t tag, uint32_t size, + const SkDeserialProcs&, SkTypefacePlayback*); + void parseBufferTag(SkReadBuffer&, uint32_t tag, uint32_t size); + void flattenToBuffer(SkWriteBuffer&, bool textBlobsOnly) const; + + SkTArray fPaints; + SkTArray fPaths; + + sk_sp fOpData; // opcodes and parameters + + const SkPath fEmptyPath; + const SkBitmap fEmptyBitmap; + + SkTArray> fPictures; + SkTArray> fDrawables; + SkTArray> fTextBlobs; + SkTArray> fVertices; + SkTArray> fImages; + + SkTypefacePlayback fTFPlayback; + std::unique_ptr fFactoryPlayback; + + const SkPictInfo fInfo; + + static void WriteFactories(SkWStream* stream, const SkFactorySet& rec); + static void WriteTypefaces(SkWStream* stream, const SkRefCntSet& rec, const SkSerialProcs&); + + void initForPlayback() const; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPictureFlat.cpp b/gfx/skia/skia/src/core/SkPictureFlat.cpp new file mode 100644 index 0000000000..a15f2e703c --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureFlat.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkShader.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkChecksum.h" +#include "src/core/SkPictureFlat.h" + +/////////////////////////////////////////////////////////////////////////////// + +void SkTypefacePlayback::setCount(size_t count) { + fCount = count; + fArray.reset(new sk_sp[count]); +} diff --git a/gfx/skia/skia/src/core/SkPictureFlat.h b/gfx/skia/skia/src/core/SkPictureFlat.h new file mode 100644 index 0000000000..715d131f97 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureFlat.h @@ -0,0 +1,208 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPictureFlat_DEFINED +#define SkPictureFlat_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPicture.h" +#include "include/private/SkChecksum.h" +#include "src/core/SkPtrRecorder.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkTDynamicHash.h" +#include "src/core/SkWriteBuffer.h" + +/* + * Note: While adding new DrawTypes, it is necessary to add to the end of this list + * and update LAST_DRAWTYPE_ENUM to avoid having the code read older skps wrong. + * (which can cause segfaults) + * + * Reordering can be done during version updates. + */ +enum DrawType { + UNUSED, + CLIP_PATH, + CLIP_REGION, + CLIP_RECT, + CLIP_RRECT, + CONCAT, + DRAW_BITMAP_RETIRED_2016_REMOVED_2018, + DRAW_BITMAP_MATRIX_RETIRED_2016_REMOVED_2018, + DRAW_BITMAP_NINE_RETIRED_2016_REMOVED_2018, + DRAW_BITMAP_RECT_RETIRED_2016_REMOVED_2018, + DRAW_CLEAR, + DRAW_DATA, + DRAW_OVAL, + DRAW_PAINT, + DRAW_PATH, + DRAW_PICTURE, + DRAW_POINTS, + DRAW_POS_TEXT_REMOVED_1_2019, + DRAW_POS_TEXT_TOP_BOTTOM_REMOVED_1_2019, + DRAW_POS_TEXT_H_REMOVED_1_2019, + DRAW_POS_TEXT_H_TOP_BOTTOM_REMOVED_1_2019, + DRAW_RECT, + DRAW_RRECT, + DRAW_SPRITE_RETIRED_2015_REMOVED_2018, + DRAW_TEXT_REMOVED_1_2019, + DRAW_TEXT_ON_PATH_RETIRED_08_2018_REMOVED_10_2018, + DRAW_TEXT_TOP_BOTTOM_REMOVED_1_2019, + DRAW_VERTICES_RETIRED_03_2017_REMOVED_01_2018, + RESTORE, + ROTATE, + SAVE, + SAVE_LAYER_SAVEFLAGS_DEPRECATED, + SCALE, + SET_MATRIX, + SKEW, + TRANSLATE, + NOOP, + BEGIN_COMMENT_GROUP_obsolete, + COMMENT_obsolete, + END_COMMENT_GROUP_obsolete, + + // new ops -- feel free to re-alphabetize on next version bump + DRAW_DRRECT, + PUSH_CULL, // deprecated, M41 was last Chromium version to write this to an .skp + POP_CULL, // deprecated, M41 was last Chromium version to write this to an .skp + + DRAW_PATCH, // could not add in aphabetical order + DRAW_PICTURE_MATRIX_PAINT, + DRAW_TEXT_BLOB, + DRAW_IMAGE, + DRAW_IMAGE_RECT_STRICT_obsolete, + DRAW_ATLAS, + DRAW_IMAGE_NINE, + DRAW_IMAGE_RECT, + + SAVE_LAYER_SAVELAYERFLAGS_DEPRECATED_JAN_2016_REMOVED_01_2018, + SAVE_LAYER_SAVELAYERREC, + + DRAW_ANNOTATION, + DRAW_DRAWABLE, + DRAW_DRAWABLE_MATRIX, + DRAW_TEXT_RSXFORM_DEPRECATED_DEC_2018, + + TRANSLATE_Z, // deprecated (M60) + + DRAW_SHADOW_REC, + DRAW_IMAGE_LATTICE, + DRAW_ARC, + DRAW_REGION, + DRAW_VERTICES_OBJECT, + + FLUSH, + + DRAW_EDGEAA_IMAGE_SET, + + SAVE_BEHIND, + + DRAW_EDGEAA_QUAD, + + DRAW_BEHIND_PAINT, + + LAST_DRAWTYPE_ENUM = DRAW_BEHIND_PAINT, +}; + +enum DrawVertexFlags { + DRAW_VERTICES_HAS_TEXS = 0x01, + DRAW_VERTICES_HAS_COLORS = 0x02, + DRAW_VERTICES_HAS_INDICES = 0x04, + DRAW_VERTICES_HAS_XFER = 0x08, +}; + +enum DrawAtlasFlags { + DRAW_ATLAS_HAS_COLORS = 1 << 0, + DRAW_ATLAS_HAS_CULL = 1 << 1, +}; + +enum DrawTextRSXformFlags { + DRAW_TEXT_RSXFORM_HAS_CULL = 1 << 0, +}; + +enum SaveLayerRecFlatFlags { + SAVELAYERREC_HAS_BOUNDS = 1 << 0, + SAVELAYERREC_HAS_PAINT = 1 << 1, + SAVELAYERREC_HAS_BACKDROP = 1 << 2, + SAVELAYERREC_HAS_FLAGS = 1 << 3, + SAVELAYERREC_HAS_CLIPMASK = 1 << 4, + SAVELAYERREC_HAS_CLIPMATRIX = 1 << 5, +}; + +enum SaveBehindFlatFlags { + SAVEBEHIND_HAS_SUBSET = 1 << 0, +}; + +/////////////////////////////////////////////////////////////////////////////// +// clipparams are packed in 5 bits +// doAA:1 | clipOp:4 + +static inline uint32_t ClipParams_pack(SkClipOp op, bool doAA) { + unsigned doAABit = doAA ? 1 : 0; + return (doAABit << 4) | static_cast(op); +} + +template T asValidEnum(SkReadBuffer* buffer, uint32_t candidate) { + + if (buffer->validate(candidate <= static_cast(T::kMax_EnumValue))) { + return static_cast(candidate); + } + + return T::kMax_EnumValue; +} + +static inline SkClipOp ClipParams_unpackRegionOp(SkReadBuffer* buffer, uint32_t packed) { + return asValidEnum(buffer, packed & 0xF); +} + +static inline bool ClipParams_unpackDoAA(uint32_t packed) { + return SkToBool((packed >> 4) & 1); +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkTypefacePlayback { +public: + SkTypefacePlayback() : fCount(0), fArray(nullptr) {} + ~SkTypefacePlayback() = default; + + void setCount(size_t count); + + size_t count() const { return fCount; } + + sk_sp& operator[](size_t index) { + SkASSERT(index < fCount); + return fArray[index]; + } + + void setupBuffer(SkReadBuffer& buffer) const { + buffer.setTypefaceArray(fArray.get(), fCount); + } + +protected: + size_t fCount; + std::unique_ptr[]> fArray; +}; + +class SkFactoryPlayback { +public: + SkFactoryPlayback(int count) : fCount(count) { fArray = new SkFlattenable::Factory[count]; } + + ~SkFactoryPlayback() { delete[] fArray; } + + SkFlattenable::Factory* base() const { return fArray; } + + void setupBuffer(SkReadBuffer& buffer) const { + buffer.setFactoryPlayback(fArray, fCount); + } + +private: + int fCount; + SkFlattenable::Factory* fArray; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPictureImageGenerator.cpp b/gfx/skia/skia/src/core/SkPictureImageGenerator.cpp new file mode 100644 index 0000000000..21987f10df --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureImageGenerator.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkImageGenerator.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPicture.h" +#include "include/core/SkSurface.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTLazy.h" +#include "src/image/SkImage_Base.h" + +class SkPictureImageGenerator : public SkImageGenerator { +public: + SkPictureImageGenerator(const SkImageInfo& info, sk_sp, const SkMatrix*, + const SkPaint*); + +protected: + bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options& opts) + override; + +#if SK_SUPPORT_GPU + TexGenType onCanGenerateTexture() const override { return TexGenType::kExpensive; } + sk_sp onGenerateTexture(GrRecordingContext*, const SkImageInfo&, + const SkIPoint&, bool willNeedMipMaps) override; +#endif + +private: + sk_sp fPicture; + SkMatrix fMatrix; + SkTLazy fPaint; + + typedef SkImageGenerator INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr +SkImageGenerator::MakeFromPicture(const SkISize& size, sk_sp picture, + const SkMatrix* matrix, const SkPaint* paint, + SkImage::BitDepth bitDepth, sk_sp colorSpace) { + if (!picture || !colorSpace || size.isEmpty()) { + return nullptr; + } + + SkColorType colorType = kN32_SkColorType; + if (SkImage::BitDepth::kF16 == bitDepth) { + colorType = kRGBA_F16_SkColorType; + } + + SkImageInfo info = + SkImageInfo::Make(size, colorType, kPremul_SkAlphaType, std::move(colorSpace)); + return std::unique_ptr( + new SkPictureImageGenerator(info, std::move(picture), matrix, paint)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkPictureImageGenerator::SkPictureImageGenerator(const SkImageInfo& info, sk_sp picture, + const SkMatrix* matrix, const SkPaint* paint) + : INHERITED(info) + , fPicture(std::move(picture)) { + + if (matrix) { + fMatrix = *matrix; + } else { + fMatrix.reset(); + } + + if (paint) { + fPaint.set(*paint); + } +} + +bool SkPictureImageGenerator::onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const Options& opts) { + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + std::unique_ptr canvas = SkCanvas::MakeRasterDirect(info, pixels, rowBytes, &props); + if (!canvas) { + return false; + } + canvas->clear(0); + canvas->drawPicture(fPicture, &fMatrix, fPaint.getMaybeNull()); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrRecordingContextPriv.h" + +sk_sp SkPictureImageGenerator::onGenerateTexture( + GrRecordingContext* ctx, const SkImageInfo& info, + const SkIPoint& origin, bool willNeedMipMaps) { + SkASSERT(ctx); + + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + + // CONTEXT TODO: remove this use of 'backdoor' to create an SkSkSurface + sk_sp surface(SkSurface::MakeRenderTarget(ctx->priv().backdoor(), + SkBudgeted::kYes, info, 0, + kTopLeft_GrSurfaceOrigin, &props, + willNeedMipMaps)); + if (!surface) { + return nullptr; + } + + SkMatrix matrix = fMatrix; + matrix.postTranslate(-origin.x(), -origin.y()); + surface->getCanvas()->clear(0); + surface->getCanvas()->drawPicture(fPicture.get(), &matrix, fPaint.getMaybeNull()); + sk_sp image(surface->makeImageSnapshot()); + if (!image) { + return nullptr; + } + sk_sp proxy = as_IB(image)->asTextureProxyRef(ctx); + SkASSERT(!willNeedMipMaps || GrMipMapped::kYes == proxy->mipMapped()); + return proxy; +} +#endif diff --git a/gfx/skia/skia/src/core/SkPicturePlayback.cpp b/gfx/skia/skia/src/core/SkPicturePlayback.cpp new file mode 100644 index 0000000000..da793f0837 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPicturePlayback.cpp @@ -0,0 +1,662 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkRSXform.h" +#include "include/core/SkTextBlob.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkDrawShadowInfo.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkPictureData.h" +#include "src/core/SkPicturePlayback.h" +#include "src/core/SkPictureRecord.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSafeMath.h" +#include "src/utils/SkPatchUtils.h" + +// matches old SkCanvas::SaveFlags +enum LegacySaveFlags { + kClipToLayer_LegacySaveFlags = 0x10, +}; + +SkCanvas::SaveLayerFlags SkCanvasPriv::LegacySaveFlagsToSaveLayerFlags(uint32_t flags) { + uint32_t layerFlags = 0; + + if (0 == (flags & kClipToLayer_LegacySaveFlags)) { + layerFlags |= kDontClipToLayer_SaveLayerFlag; + } + return layerFlags; +} + +/* + * Read the next op code and chunk size from 'reader'. The returned size + * is the entire size of the chunk (including the opcode). Thus, the + * offset just prior to calling ReadOpAndSize + 'size' is the offset + * to the next chunk's op code. This also means that the size of a chunk + * with no arguments (just an opcode) will be 4. + */ +DrawType SkPicturePlayback::ReadOpAndSize(SkReadBuffer* reader, uint32_t* size) { + uint32_t temp = reader->readInt(); + uint32_t op; + if ((temp & 0xFF) == temp) { + // old skp file - no size information + op = temp; + *size = 0; + } else { + UNPACK_8_24(temp, op, *size); + if (MASK_24 == *size) { + *size = reader->readInt(); + } + } + return (DrawType)op; +} + + +static const SkRect* get_rect_ptr(SkReadBuffer* reader, SkRect* storage) { + if (reader->readBool()) { + reader->readRect(storage); + return storage; + } else { + return nullptr; + } +} + +void SkPicturePlayback::draw(SkCanvas* canvas, + SkPicture::AbortCallback* callback, + SkReadBuffer* buffer) { + AutoResetOpID aroi(this); + SkASSERT(0 == fCurOffset); + + SkReadBuffer reader(fPictureData->opData()->bytes(), + fPictureData->opData()->size()); + + // Record this, so we can concat w/ it if we encounter a setMatrix() + SkMatrix initialMatrix = canvas->getTotalMatrix(); + + SkAutoCanvasRestore acr(canvas, false); + + while (!reader.eof()) { + if (callback && callback->abort()) { + return; + } + + fCurOffset = reader.offset(); + uint32_t size; + DrawType op = ReadOpAndSize(&reader, &size); + if (!reader.validate(op > UNUSED && op <= LAST_DRAWTYPE_ENUM)) { + return; + } + + this->handleOp(&reader, op, size, canvas, initialMatrix); + } + + // need to propagate invalid state to the parent reader + if (buffer) { + buffer->validate(reader.isValid()); + } +} + +static void validate_offsetToRestore(SkReadBuffer* reader, size_t offsetToRestore) { + if (offsetToRestore) { + reader->validate(SkIsAlign4(offsetToRestore) && offsetToRestore >= reader->offset()); + } +} + +void SkPicturePlayback::handleOp(SkReadBuffer* reader, + DrawType op, + uint32_t size, + SkCanvas* canvas, + const SkMatrix& initialMatrix) { +#define BREAK_ON_READ_ERROR(r) if (!r->isValid()) break + + switch (op) { + case NOOP: { + SkASSERT(size >= 4); + reader->skip(size - 4); + } break; + case FLUSH: + canvas->flush(); + break; + case CLIP_PATH: { + const SkPath& path = fPictureData->getPath(reader); + uint32_t packed = reader->readInt(); + SkClipOp clipOp = ClipParams_unpackRegionOp(reader, packed); + bool doAA = ClipParams_unpackDoAA(packed); + size_t offsetToRestore = reader->readInt(); + validate_offsetToRestore(reader, offsetToRestore); + BREAK_ON_READ_ERROR(reader); + + canvas->clipPath(path, clipOp, doAA); + if (canvas->isClipEmpty() && offsetToRestore) { + reader->skip(offsetToRestore - reader->offset()); + } + } break; + case CLIP_REGION: { + SkRegion region; + reader->readRegion(®ion); + uint32_t packed = reader->readInt(); + SkClipOp clipOp = ClipParams_unpackRegionOp(reader, packed); + size_t offsetToRestore = reader->readInt(); + validate_offsetToRestore(reader, offsetToRestore); + BREAK_ON_READ_ERROR(reader); + + canvas->clipRegion(region, clipOp); + if (canvas->isClipEmpty() && offsetToRestore) { + reader->skip(offsetToRestore - reader->offset()); + } + } break; + case CLIP_RECT: { + SkRect rect; + reader->readRect(&rect); + uint32_t packed = reader->readInt(); + SkClipOp clipOp = ClipParams_unpackRegionOp(reader, packed); + bool doAA = ClipParams_unpackDoAA(packed); + size_t offsetToRestore = reader->readInt(); + validate_offsetToRestore(reader, offsetToRestore); + BREAK_ON_READ_ERROR(reader); + + canvas->clipRect(rect, clipOp, doAA); + if (canvas->isClipEmpty() && offsetToRestore) { + reader->skip(offsetToRestore - reader->offset()); + } + } break; + case CLIP_RRECT: { + SkRRect rrect; + reader->readRRect(&rrect); + uint32_t packed = reader->readInt(); + SkClipOp clipOp = ClipParams_unpackRegionOp(reader, packed); + bool doAA = ClipParams_unpackDoAA(packed); + size_t offsetToRestore = reader->readInt(); + validate_offsetToRestore(reader, offsetToRestore); + BREAK_ON_READ_ERROR(reader); + + canvas->clipRRect(rrect, clipOp, doAA); + if (canvas->isClipEmpty() && offsetToRestore) { + reader->skip(offsetToRestore - reader->offset()); + } + } break; + case PUSH_CULL: break; // Deprecated, safe to ignore both push and pop. + case POP_CULL: break; + case CONCAT: { + SkMatrix matrix; + reader->readMatrix(&matrix); + BREAK_ON_READ_ERROR(reader); + + canvas->concat(matrix); + break; + } + case DRAW_ANNOTATION: { + SkRect rect; + reader->readRect(&rect); + SkString key; + reader->readString(&key); + sk_sp data = reader->readByteArrayAsData(); + BREAK_ON_READ_ERROR(reader); + SkASSERT(data); + + canvas->drawAnnotation(rect, key.c_str(), data.get()); + } break; + case DRAW_ARC: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkRect rect; + reader->readRect(&rect); + SkScalar startAngle = reader->readScalar(); + SkScalar sweepAngle = reader->readScalar(); + int useCenter = reader->readInt(); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawArc(rect, startAngle, sweepAngle, SkToBool(useCenter), *paint); + } + } break; + case DRAW_ATLAS: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkImage* atlas = fPictureData->getImage(reader); + const uint32_t flags = reader->readUInt(); + const int count = reader->readUInt(); + const SkRSXform* xform = (const SkRSXform*)reader->skip(count, sizeof(SkRSXform)); + const SkRect* tex = (const SkRect*)reader->skip(count, sizeof(SkRect)); + const SkColor* colors = nullptr; + SkBlendMode mode = SkBlendMode::kDst; + if (flags & DRAW_ATLAS_HAS_COLORS) { + colors = (const SkColor*)reader->skip(count, sizeof(SkColor)); + mode = (SkBlendMode)reader->readUInt(); + } + const SkRect* cull = nullptr; + if (flags & DRAW_ATLAS_HAS_CULL) { + cull = (const SkRect*)reader->skip(sizeof(SkRect)); + } + BREAK_ON_READ_ERROR(reader); + + canvas->drawAtlas(atlas, xform, tex, colors, count, mode, cull, paint); + } break; + case DRAW_CLEAR: { + auto c = reader->readInt(); + BREAK_ON_READ_ERROR(reader); + + canvas->clear(c); + } break; + case DRAW_DATA: { + // This opcode is now dead, just need to skip it for backwards compatibility + size_t length = reader->readInt(); + (void)reader->skip(length); + // skip handles padding the read out to a multiple of 4 + } break; + case DRAW_DRAWABLE: { + auto* d = fPictureData->getDrawable(reader); + BREAK_ON_READ_ERROR(reader); + + canvas->drawDrawable(d); + } break; + case DRAW_DRAWABLE_MATRIX: { + SkMatrix matrix; + reader->readMatrix(&matrix); + SkDrawable* drawable = fPictureData->getDrawable(reader); + BREAK_ON_READ_ERROR(reader); + + canvas->drawDrawable(drawable, &matrix); + } break; + case DRAW_DRRECT: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkRRect outer, inner; + reader->readRRect(&outer); + reader->readRRect(&inner); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawDRRect(outer, inner, *paint); + } + } break; + case DRAW_EDGEAA_QUAD: { + SkRect rect; + reader->readRect(&rect); + SkCanvas::QuadAAFlags aaFlags = static_cast(reader->read32()); + SkColor4f color; + if (reader->isVersionLT(SkPicturePriv::kEdgeAAQuadColor4f_Version)) { + // Old version stored color as 8888 + color = SkColor4f::FromColor(reader->read32()); + } else { + reader->readColor4f(&color); + } + SkBlendMode blend = static_cast(reader->read32()); + bool hasClip = reader->readInt(); + SkPoint* clip = nullptr; + if (hasClip) { + clip = (SkPoint*) reader->skip(4, sizeof(SkPoint)); + } + BREAK_ON_READ_ERROR(reader); + canvas->experimental_DrawEdgeAAQuad(rect, clip, aaFlags, color, blend); + } break; + case DRAW_EDGEAA_IMAGE_SET: { + static const size_t kEntryReadSize = + 4 * sizeof(uint32_t) + 2 * sizeof(SkRect) + sizeof(SkScalar); + static const size_t kMatrixSize = 9 * sizeof(SkScalar); // != sizeof(SkMatrix) + + int cnt = reader->readInt(); + if (!reader->validate(cnt >= 0)) { + break; + } + const SkPaint* paint = fPictureData->getPaint(reader); + SkCanvas::SrcRectConstraint constraint = + static_cast(reader->readInt()); + + if (!reader->validate(SkSafeMath::Mul(cnt, kEntryReadSize) <= reader->available())) { + break; + } + + // Track minimum necessary clip points and matrices that must be provided to satisfy + // the entries. + int expectedClips = 0; + int maxMatrixIndex = -1; + SkAutoTArray set(cnt); + for (int i = 0; i < cnt && reader->isValid(); ++i) { + set[i].fImage = sk_ref_sp(fPictureData->getImage(reader)); + reader->readRect(&set[i].fSrcRect); + reader->readRect(&set[i].fDstRect); + set[i].fMatrixIndex = reader->readInt(); + set[i].fAlpha = reader->readScalar(); + set[i].fAAFlags = reader->readUInt(); + set[i].fHasClip = reader->readInt(); + + expectedClips += set[i].fHasClip ? 1 : 0; + if (set[i].fMatrixIndex > maxMatrixIndex) { + maxMatrixIndex = set[i].fMatrixIndex; + } + } + + int dstClipCount = reader->readInt(); + SkPoint* dstClips = nullptr; + if (!reader->validate(expectedClips <= dstClipCount)) { + // Entries request more dstClip points than are provided in the buffer + break; + } else if (dstClipCount > 0) { + dstClips = (SkPoint*) reader->skip(dstClipCount, sizeof(SkPoint)); + if (dstClips == nullptr) { + // Not enough bytes remaining so the reader has been invalidated + break; + } + } + int matrixCount = reader->readInt(); + if (!reader->validate((maxMatrixIndex + 1) <= matrixCount) || + !reader->validate( + SkSafeMath::Mul(matrixCount, kMatrixSize) <= reader->available())) { + // Entries access out-of-bound matrix indices, given provided matrices or + // there aren't enough bytes to provide that many matrices + break; + } + SkTArray matrices(matrixCount); + for (int i = 0; i < matrixCount && reader->isValid(); ++i) { + reader->readMatrix(&matrices.push_back()); + } + BREAK_ON_READ_ERROR(reader); + + canvas->experimental_DrawEdgeAAImageSet(set.get(), cnt, dstClips, matrices.begin(), + paint, constraint); + } break; + case DRAW_IMAGE: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkImage* image = fPictureData->getImage(reader); + SkPoint loc; + reader->readPoint(&loc); + BREAK_ON_READ_ERROR(reader); + + canvas->drawImage(image, loc.fX, loc.fY, paint); + } break; + case DRAW_IMAGE_LATTICE: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkImage* image = fPictureData->getImage(reader); + SkCanvas::Lattice lattice; + (void)SkCanvasPriv::ReadLattice(*reader, &lattice); + const SkRect* dst = reader->skipT(); + BREAK_ON_READ_ERROR(reader); + + canvas->drawImageLattice(image, lattice, *dst, paint); + } break; + case DRAW_IMAGE_NINE: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkImage* image = fPictureData->getImage(reader); + SkIRect center; + reader->readIRect(¢er); + SkRect dst; + reader->readRect(&dst); + BREAK_ON_READ_ERROR(reader); + + canvas->drawImageNine(image, center, dst, paint); + } break; + case DRAW_IMAGE_RECT: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkImage* image = fPictureData->getImage(reader); + SkRect storage; + const SkRect* src = get_rect_ptr(reader, &storage); // may be null + SkRect dst; + reader->readRect(&dst); // required + // DRAW_IMAGE_RECT_STRICT assumes this constraint, and doesn't store it + SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint; + if (DRAW_IMAGE_RECT == op) { + // newer op-code stores the constraint explicitly + constraint = (SkCanvas::SrcRectConstraint)reader->readInt(); + } + BREAK_ON_READ_ERROR(reader); + + canvas->legacy_drawImageRect(image, src, dst, paint, constraint); + } break; + case DRAW_OVAL: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkRect rect; + reader->readRect(&rect); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawOval(rect, *paint); + } + } break; + case DRAW_PAINT: { + const SkPaint* paint = fPictureData->getPaint(reader); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawPaint(*paint); + } + } break; + case DRAW_BEHIND_PAINT: { + const SkPaint* paint = fPictureData->getPaint(reader); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + SkCanvasPriv::DrawBehind(canvas, *paint); + } + } break; + case DRAW_PATCH: { + const SkPaint* paint = fPictureData->getPaint(reader); + + const SkPoint* cubics = (const SkPoint*)reader->skip(SkPatchUtils::kNumCtrlPts, + sizeof(SkPoint)); + uint32_t flag = reader->readInt(); + const SkColor* colors = nullptr; + if (flag & DRAW_VERTICES_HAS_COLORS) { + colors = (const SkColor*)reader->skip(SkPatchUtils::kNumCorners, sizeof(SkColor)); + } + const SkPoint* texCoords = nullptr; + if (flag & DRAW_VERTICES_HAS_TEXS) { + texCoords = (const SkPoint*)reader->skip(SkPatchUtils::kNumCorners, + sizeof(SkPoint)); + } + SkBlendMode bmode = SkBlendMode::kModulate; + if (flag & DRAW_VERTICES_HAS_XFER) { + unsigned mode = reader->readInt(); + if (mode <= (unsigned)SkBlendMode::kLastMode) { + bmode = (SkBlendMode)mode; + } + } + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawPatch(cubics, colors, texCoords, bmode, *paint); + } + } break; + case DRAW_PATH: { + const SkPaint* paint = fPictureData->getPaint(reader); + const auto& path = fPictureData->getPath(reader); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawPath(path, *paint); + } + } break; + case DRAW_PICTURE: { + const auto* pic = fPictureData->getPicture(reader); + BREAK_ON_READ_ERROR(reader); + + canvas->drawPicture(pic); + } break; + case DRAW_PICTURE_MATRIX_PAINT: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkMatrix matrix; + reader->readMatrix(&matrix); + const SkPicture* pic = fPictureData->getPicture(reader); + BREAK_ON_READ_ERROR(reader); + + canvas->drawPicture(pic, &matrix, paint); + } break; + case DRAW_POINTS: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkCanvas::PointMode mode = (SkCanvas::PointMode)reader->readInt(); + size_t count = reader->readInt(); + const SkPoint* pts = (const SkPoint*)reader->skip(count, sizeof(SkPoint)); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawPoints(mode, count, pts, *paint); + } + } break; + case DRAW_RECT: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkRect rect; + reader->readRect(&rect); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawRect(rect, *paint); + } + } break; + case DRAW_REGION: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkRegion region; + reader->readRegion(®ion); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawRegion(region, *paint); + } + } break; + case DRAW_RRECT: { + const SkPaint* paint = fPictureData->getPaint(reader); + SkRRect rrect; + reader->readRRect(&rrect); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawRRect(rrect, *paint); + } + } break; + case DRAW_SHADOW_REC: { + const auto& path = fPictureData->getPath(reader); + SkDrawShadowRec rec; + reader->readPoint3(&rec.fZPlaneParams); + reader->readPoint3(&rec.fLightPos); + rec.fLightRadius = reader->readScalar(); + if (reader->isVersionLT(SkPicturePriv::kTwoColorDrawShadow_Version)) { + SkScalar ambientAlpha = reader->readScalar(); + SkScalar spotAlpha = reader->readScalar(); + SkColor color = reader->read32(); + rec.fAmbientColor = SkColorSetA(color, SkColorGetA(color)*ambientAlpha); + rec.fSpotColor = SkColorSetA(color, SkColorGetA(color)*spotAlpha); + } else { + rec.fAmbientColor = reader->read32(); + rec.fSpotColor = reader->read32(); + } + rec.fFlags = reader->read32(); + BREAK_ON_READ_ERROR(reader); + + canvas->private_draw_shadow_rec(path, rec); + } break; + case DRAW_TEXT_BLOB: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkTextBlob* blob = fPictureData->getTextBlob(reader); + SkScalar x = reader->readScalar(); + SkScalar y = reader->readScalar(); + BREAK_ON_READ_ERROR(reader); + + if (paint) { + canvas->drawTextBlob(blob, x, y, *paint); + } + } break; + case DRAW_VERTICES_OBJECT: { + const SkPaint* paint = fPictureData->getPaint(reader); + const SkVertices* vertices = fPictureData->getVertices(reader); + const int boneCount = reader->readInt(); + const SkVertices::Bone* bones = boneCount ? + (const SkVertices::Bone*) reader->skip(boneCount, sizeof(SkVertices::Bone)) : + nullptr; + SkBlendMode bmode = reader->read32LE(SkBlendMode::kLastMode); + BREAK_ON_READ_ERROR(reader); + + if (paint && vertices) { + canvas->drawVertices(vertices, bones, boneCount, bmode, *paint); + } + } break; + case RESTORE: + canvas->restore(); + break; + case ROTATE: { + auto deg = reader->readScalar(); + canvas->rotate(deg); + } break; + case SAVE: + canvas->save(); + break; + case SAVE_BEHIND: { + uint32_t flags = reader->readInt(); + const SkRect* subset = nullptr; + SkRect storage; + if (flags & SAVEBEHIND_HAS_SUBSET) { + reader->readRect(&storage); + subset = &storage; + } + SkCanvasPriv::SaveBehind(canvas, subset); + } break; + case SAVE_LAYER_SAVEFLAGS_DEPRECATED: { + SkRect storage; + const SkRect* boundsPtr = get_rect_ptr(reader, &storage); + const SkPaint* paint = fPictureData->getPaint(reader); + auto flags = SkCanvasPriv::LegacySaveFlagsToSaveLayerFlags(reader->readInt()); + BREAK_ON_READ_ERROR(reader); + + canvas->saveLayer(SkCanvas::SaveLayerRec(boundsPtr, paint, flags)); + } break; + case SAVE_LAYER_SAVELAYERREC: { + SkCanvas::SaveLayerRec rec(nullptr, nullptr, nullptr, nullptr, nullptr, 0); + SkMatrix clipMatrix; + const uint32_t flatFlags = reader->readInt(); + SkRect bounds; + if (flatFlags & SAVELAYERREC_HAS_BOUNDS) { + reader->readRect(&bounds); + rec.fBounds = &bounds; + } + if (flatFlags & SAVELAYERREC_HAS_PAINT) { + rec.fPaint = fPictureData->getPaint(reader); + } + if (flatFlags & SAVELAYERREC_HAS_BACKDROP) { + if (const auto* paint = fPictureData->getPaint(reader)) { + rec.fBackdrop = paint->getImageFilter(); + } + } + if (flatFlags & SAVELAYERREC_HAS_FLAGS) { + rec.fSaveLayerFlags = reader->readInt(); + } + if (flatFlags & SAVELAYERREC_HAS_CLIPMASK) { + rec.fClipMask = fPictureData->getImage(reader); + } + if (flatFlags & SAVELAYERREC_HAS_CLIPMATRIX) { + reader->readMatrix(&clipMatrix); + rec.fClipMatrix = &clipMatrix; + } + BREAK_ON_READ_ERROR(reader); + + canvas->saveLayer(rec); + } break; + case SCALE: { + SkScalar sx = reader->readScalar(); + SkScalar sy = reader->readScalar(); + canvas->scale(sx, sy); + } break; + case SET_MATRIX: { + SkMatrix matrix; + reader->readMatrix(&matrix); + matrix.postConcat(initialMatrix); + canvas->setMatrix(matrix); + } break; + case SKEW: { + SkScalar sx = reader->readScalar(); + SkScalar sy = reader->readScalar(); + canvas->skew(sx, sy); + } break; + case TRANSLATE: { + SkScalar dx = reader->readScalar(); + SkScalar dy = reader->readScalar(); + canvas->translate(dx, dy); + } break; + default: + reader->validate(false); // unknown op + break; + } + +#undef BREAK_ON_READ_ERROR +} diff --git a/gfx/skia/skia/src/core/SkPicturePlayback.h b/gfx/skia/skia/src/core/SkPicturePlayback.h new file mode 100644 index 0000000000..76f7cad6b9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPicturePlayback.h @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPicturePlayback_DEFINED +#define SkPicturePlayback_DEFINED + +#include "src/core/SkPictureFlat.h" + +class SkBitmap; +class SkCanvas; +class SkPaint; +class SkPictureData; + +// The basic picture playback class replays the provided picture into a canvas. +class SkPicturePlayback final : SkNoncopyable { +public: + SkPicturePlayback(const SkPictureData* data) + : fPictureData(data) + , fCurOffset(0) { + } + + void draw(SkCanvas* canvas, SkPicture::AbortCallback*, SkReadBuffer* buffer); + + // TODO: remove the curOp calls after cleaning up GrGatherDevice + // Return the ID of the operation currently being executed when playing + // back. 0 indicates no call is active. + size_t curOpID() const { return fCurOffset; } + void resetOpID() { fCurOffset = 0; } + +protected: + const SkPictureData* fPictureData; + + // The offset of the current operation when within the draw method + size_t fCurOffset; + + void handleOp(SkReadBuffer* reader, + DrawType op, + uint32_t size, + SkCanvas* canvas, + const SkMatrix& initialMatrix); + + static DrawType ReadOpAndSize(SkReadBuffer* reader, uint32_t* size); + + class AutoResetOpID { + public: + AutoResetOpID(SkPicturePlayback* playback) : fPlayback(playback) { } + ~AutoResetOpID() { + if (fPlayback) { + fPlayback->resetOpID(); + } + } + + private: + SkPicturePlayback* fPlayback; + }; + +private: + typedef SkNoncopyable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPicturePriv.h b/gfx/skia/skia/src/core/SkPicturePriv.h new file mode 100644 index 0000000000..7c95675ffb --- /dev/null +++ b/gfx/skia/skia/src/core/SkPicturePriv.h @@ -0,0 +1,106 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPicturePriv_DEFINED +#define SkPicturePriv_DEFINED + +#include "include/core/SkPicture.h" + +class SkReadBuffer; +class SkWriteBuffer; + +class SkPicturePriv { +public: + /** + * Recreate a picture that was serialized into a buffer. If the creation requires bitmap + * decoding, the decoder must be set on the SkReadBuffer parameter by calling + * SkReadBuffer::setBitmapDecoder() before calling SkPicture::MakeFromBuffer(). + * @param buffer Serialized picture data. + * @return A new SkPicture representing the serialized data, or NULL if the buffer is + * invalid. + */ + static sk_sp MakeFromBuffer(SkReadBuffer& buffer); + + /** + * Serialize to a buffer. + */ + static void Flatten(const sk_sp , SkWriteBuffer& buffer); + + // Returns NULL if this is not an SkBigPicture. + static const SkBigPicture* AsSkBigPicture(const sk_sp picture) { + return picture->asSkBigPicture(); + } + + // V35: Store SkRect (rather then width & height) in header + // V36: Remove (obsolete) alphatype from SkColorTable + // V37: Added shadow only option to SkDropShadowImageFilter (last version to record CLEAR) + // V38: Added PictureResolution option to SkPictureImageFilter + // V39: Added FilterLevel option to SkPictureImageFilter + // V40: Remove UniqueID serialization from SkImageFilter. + // V41: Added serialization of SkBitmapSource's filterQuality parameter + // V42: Added a bool to SkPictureShader serialization to indicate did-we-serialize-a-picture? + // V43: Added DRAW_IMAGE and DRAW_IMAGE_RECT opt codes to serialized data + // V44: Move annotations from paint to drawAnnotation + // V45: Add invNormRotation to SkLightingShader. + // V46: Add drawTextRSXform + // V47: Add occluder rect to SkBlurMaskFilter + // V48: Read and write extended SkTextBlobs. + // V49: Gradients serialized as SkColor4f + SkColorSpace + // V50: SkXfermode -> SkBlendMode + // V51: more SkXfermode -> SkBlendMode + // V52: Remove SkTextBlob::fRunCount + // V53: SaveLayerRec clip mask + // V54: ComposeShader can use a Mode or a Lerp + // V55: Drop blendmode[] from MergeImageFilter + // V56: Add TileMode in SkBlurImageFilter. + // V57: Sweep tiling info. + // V58: No more 2pt conical flipping. + // V59: No more LocalSpace option on PictureImageFilter + // V60: Remove flags in picture header + // V61: Change SkDrawPictureRec to take two colors rather than two alphas + // V62: Don't negate size of custom encoded images (don't write origin x,y either) + // V63: Store image bounds (including origin) instead of just width/height to support subsets + // V64: Remove occluder feature from blur maskFilter + // V65: Float4 paint color + // V66: Add saveBehind + // V67: Blobs serialize fonts instead of paints + // V68: Paint doesn't serialize font-related stuff + // V69: Clean up duplicated and redundant SkImageFilter related enums + // V70: Image filters definitions hidden, registered names updated to include "Impl" + // V71: Unify erode and dilate image filters + // V72: SkColorFilter_Matrix domain (rgba vs. hsla) + // V73: Use SkColor4f in per-edge AA quad API + + enum Version { + kTileModeInBlurImageFilter_Version = 56, + kTileInfoInSweepGradient_Version = 57, + k2PtConicalNoFlip_Version = 58, + kRemovePictureImageFilterLocalSpace = 59, + kRemoveHeaderFlags_Version = 60, + kTwoColorDrawShadow_Version = 61, + kDontNegateImageSize_Version = 62, + kStoreImageBounds_Version = 63, + kRemoveOccluderFromBlurMaskFilter = 64, + kFloat4PaintColor_Version = 65, + kSaveBehind_Version = 66, + kSerializeFonts_Version = 67, + kPaintDoesntSerializeFonts_Version = 68, + kCleanupImageFilterEnums_Version = 69, + kHideImageFilterImpls_Version = 70, + kUnifyErodeDilateImpls_Version = 71, + kMatrixColorFilterDomain_Version = 72, + kEdgeAAQuadColor4f_Version = 73, + + // Only SKPs within the min/current picture version range (inclusive) can be read. + kMin_Version = kTileModeInBlurImageFilter_Version, + kCurrent_Version = kEdgeAAQuadColor4f_Version + }; + + static_assert(kMin_Version <= 62, "Remove kFontAxes_bad from SkFontDescriptor.cpp"); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPictureRecord.cpp b/gfx/skia/skia/src/core/SkPictureRecord.cpp new file mode 100644 index 0000000000..46ba4a42d2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureRecord.cpp @@ -0,0 +1,932 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkPictureRecord.h" + +#include "include/core/SkRRect.h" +#include "include/core/SkRSXform.h" +#include "include/core/SkTextBlob.h" +#include "include/private/SkTo.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkDrawShadowInfo.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkTSearch.h" +#include "src/image/SkImage_Base.h" +#include "src/utils/SkPatchUtils.h" + +#define HEAP_BLOCK_SIZE 4096 + +enum { + // just need a value that save or getSaveCount would never return + kNoInitialSave = -1, +}; + +// A lot of basic types get stored as a uint32_t: bools, ints, paint indices, etc. +static int const kUInt32Size = 4; + +SkPictureRecord::SkPictureRecord(const SkIRect& dimensions, uint32_t flags) + : INHERITED(dimensions) + , fRecordFlags(flags) + , fInitialSaveCount(kNoInitialSave) { +} + +SkPictureRecord::SkPictureRecord(const SkISize& dimensions, uint32_t flags) + : SkPictureRecord(SkIRect::MakeSize(dimensions), flags) {} + +/////////////////////////////////////////////////////////////////////////////// + +void SkPictureRecord::onFlush() { + size_t size = sizeof(kUInt32Size); + size_t initialOffset = this->addDraw(FLUSH, &size); + this->validate(initialOffset, size); +} + +void SkPictureRecord::willSave() { + // record the offset to us, making it non-positive to distinguish a save + // from a clip entry. + fRestoreOffsetStack.push_back(-(int32_t)fWriter.bytesWritten()); + this->recordSave(); + + this->INHERITED::willSave(); +} + +void SkPictureRecord::recordSave() { + // op only + size_t size = sizeof(kUInt32Size); + size_t initialOffset = this->addDraw(SAVE, &size); + + this->validate(initialOffset, size); +} + +SkCanvas::SaveLayerStrategy SkPictureRecord::getSaveLayerStrategy(const SaveLayerRec& rec) { + // record the offset to us, making it non-positive to distinguish a save + // from a clip entry. + fRestoreOffsetStack.push_back(-(int32_t)fWriter.bytesWritten()); + this->recordSaveLayer(rec); + + (void)this->INHERITED::getSaveLayerStrategy(rec); + /* No need for a (potentially very big) layer which we don't actually need + at this time (and may not be able to afford since during record our + clip starts out the size of the picture, which is often much larger + than the size of the actual device we'll use during playback). + */ + return kNoLayer_SaveLayerStrategy; +} + +bool SkPictureRecord::onDoSaveBehind(const SkRect* subset) { + fRestoreOffsetStack.push_back(-(int32_t)fWriter.bytesWritten()); + + size_t size = sizeof(kUInt32Size) + sizeof(uint32_t); // op + flags + uint32_t flags = 0; + if (subset) { + flags |= SAVEBEHIND_HAS_SUBSET; + size += sizeof(*subset); + } + + size_t initialOffset = this->addDraw(SAVE_BEHIND, &size); + this->addInt(flags); + if (subset) { + this->addRect(*subset); + } + + this->validate(initialOffset, size); + return false; +} + +void SkPictureRecord::recordSaveLayer(const SaveLayerRec& rec) { + // op + flatflags + size_t size = 2 * kUInt32Size; + uint32_t flatFlags = 0; + + if (rec.fBounds) { + flatFlags |= SAVELAYERREC_HAS_BOUNDS; + size += sizeof(*rec.fBounds); + } + if (rec.fPaint) { + flatFlags |= SAVELAYERREC_HAS_PAINT; + size += sizeof(uint32_t); // index + } + if (rec.fBackdrop) { + flatFlags |= SAVELAYERREC_HAS_BACKDROP; + size += sizeof(uint32_t); // (paint) index + } + if (rec.fSaveLayerFlags) { + flatFlags |= SAVELAYERREC_HAS_FLAGS; + size += sizeof(uint32_t); + } + if (rec.fClipMask) { + flatFlags |= SAVELAYERREC_HAS_CLIPMASK; + size += sizeof(uint32_t); // clip image index + } + if (rec.fClipMatrix) { + flatFlags |= SAVELAYERREC_HAS_CLIPMATRIX; + size += SkMatrixPriv::WriteToMemory(*rec.fClipMatrix, nullptr); + } + + const size_t initialOffset = this->addDraw(SAVE_LAYER_SAVELAYERREC, &size); + this->addInt(flatFlags); + if (flatFlags & SAVELAYERREC_HAS_BOUNDS) { + this->addRect(*rec.fBounds); + } + if (flatFlags & SAVELAYERREC_HAS_PAINT) { + this->addPaintPtr(rec.fPaint); + } + if (flatFlags & SAVELAYERREC_HAS_BACKDROP) { + // overkill, but we didn't already track single flattenables, so using a paint for that + SkPaint paint; + paint.setImageFilter(sk_ref_sp(const_cast(rec.fBackdrop))); + this->addPaint(paint); + } + if (flatFlags & SAVELAYERREC_HAS_FLAGS) { + this->addInt(rec.fSaveLayerFlags); + } + if (flatFlags & SAVELAYERREC_HAS_CLIPMASK) { + this->addImage(rec.fClipMask); + } + if (flatFlags & SAVELAYERREC_HAS_CLIPMATRIX) { + this->addMatrix(*rec.fClipMatrix); + } + this->validate(initialOffset, size); +} + +#ifdef SK_DEBUG +/* + * Read the op code from 'offset' in 'writer' and extract the size too. + */ +static DrawType peek_op_and_size(SkWriter32* writer, size_t offset, uint32_t* size) { + uint32_t peek = writer->readTAt(offset); + + uint32_t op; + UNPACK_8_24(peek, op, *size); + if (MASK_24 == *size) { + // size required its own slot right after the op code + *size = writer->readTAt(offset + kUInt32Size); + } + return (DrawType) op; +} +#endif//SK_DEBUG + +void SkPictureRecord::willRestore() { +#if 0 + SkASSERT(fRestoreOffsetStack.count() > 1); +#endif + + // check for underflow + if (fRestoreOffsetStack.count() == 0) { + return; + } + + this->recordRestore(); + + fRestoreOffsetStack.pop(); + + this->INHERITED::willRestore(); +} + +void SkPictureRecord::recordRestore(bool fillInSkips) { + if (fillInSkips) { + this->fillRestoreOffsetPlaceholdersForCurrentStackLevel((uint32_t)fWriter.bytesWritten()); + } + size_t size = 1 * kUInt32Size; // RESTORE consists solely of 1 op code + size_t initialOffset = this->addDraw(RESTORE, &size); + this->validate(initialOffset, size); +} + +void SkPictureRecord::recordTranslate(const SkMatrix& m) { + SkASSERT(SkMatrix::kTranslate_Mask == m.getType()); + + // op + dx + dy + size_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar); + size_t initialOffset = this->addDraw(TRANSLATE, &size); + this->addScalar(m.getTranslateX()); + this->addScalar(m.getTranslateY()); + this->validate(initialOffset, size); +} + +void SkPictureRecord::recordScale(const SkMatrix& m) { + SkASSERT(SkMatrix::kScale_Mask == m.getType()); + + // op + sx + sy + size_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar); + size_t initialOffset = this->addDraw(SCALE, &size); + this->addScalar(m.getScaleX()); + this->addScalar(m.getScaleY()); + this->validate(initialOffset, size); +} + +void SkPictureRecord::didConcat(const SkMatrix& matrix) { + switch (matrix.getType()) { + case SkMatrix::kTranslate_Mask: + this->recordTranslate(matrix); + break; + case SkMatrix::kScale_Mask: + this->recordScale(matrix); + break; + default: + this->recordConcat(matrix); + break; + } + this->INHERITED::didConcat(matrix); +} + +void SkPictureRecord::recordConcat(const SkMatrix& matrix) { + this->validate(fWriter.bytesWritten(), 0); + // op + matrix + size_t size = kUInt32Size + SkMatrixPriv::WriteToMemory(matrix, nullptr); + size_t initialOffset = this->addDraw(CONCAT, &size); + this->addMatrix(matrix); + this->validate(initialOffset, size); +} + +void SkPictureRecord::didSetMatrix(const SkMatrix& matrix) { + this->validate(fWriter.bytesWritten(), 0); + // op + matrix + size_t size = kUInt32Size + SkMatrixPriv::WriteToMemory(matrix, nullptr); + size_t initialOffset = this->addDraw(SET_MATRIX, &size); + this->addMatrix(matrix); + this->validate(initialOffset, size); + this->INHERITED::didSetMatrix(matrix); +} + +static bool clipOpExpands(SkClipOp op) { + switch (op) { + case kUnion_SkClipOp: + case kXOR_SkClipOp: + case kReverseDifference_SkClipOp: + case kReplace_SkClipOp: + return true; + case kIntersect_SkClipOp: + case kDifference_SkClipOp: + return false; + default: + SkDEBUGFAIL("unknown clipop"); + return false; + } +} + +void SkPictureRecord::fillRestoreOffsetPlaceholdersForCurrentStackLevel(uint32_t restoreOffset) { + int32_t offset = fRestoreOffsetStack.top(); + while (offset > 0) { + uint32_t peek = fWriter.readTAt(offset); + fWriter.overwriteTAt(offset, restoreOffset); + offset = peek; + } + +#ifdef SK_DEBUG + // offset of 0 has been disabled, so we skip it + if (offset > 0) { + // assert that the final offset value points to a save verb + uint32_t opSize; + DrawType drawOp = peek_op_and_size(&fWriter, -offset, &opSize); + SkASSERT(SAVE == drawOp || SAVE_LAYER_SAVELAYERREC == drawOp); + } +#endif +} + +void SkPictureRecord::beginRecording() { + // we have to call this *after* our constructor, to ensure that it gets + // recorded. This is balanced by restoreToCount() call from endRecording, + // which in-turn calls our overridden restore(), so those get recorded too. + fInitialSaveCount = this->save(); +} + +void SkPictureRecord::endRecording() { + SkASSERT(kNoInitialSave != fInitialSaveCount); + this->restoreToCount(fInitialSaveCount); +} + +size_t SkPictureRecord::recordRestoreOffsetPlaceholder(SkClipOp op) { + if (fRestoreOffsetStack.isEmpty()) { + return -1; + } + + // The RestoreOffset field is initially filled with a placeholder + // value that points to the offset of the previous RestoreOffset + // in the current stack level, thus forming a linked list so that + // the restore offsets can be filled in when the corresponding + // restore command is recorded. + int32_t prevOffset = fRestoreOffsetStack.top(); + + if (clipOpExpands(op)) { + // Run back through any previous clip ops, and mark their offset to + // be 0, disabling their ability to trigger a jump-to-restore, otherwise + // they could hide this clips ability to expand the clip (i.e. go from + // empty to non-empty). + this->fillRestoreOffsetPlaceholdersForCurrentStackLevel(0); + + // Reset the pointer back to the previous clip so that subsequent + // restores don't overwrite the offsets we just cleared. + prevOffset = 0; + } + + size_t offset = fWriter.bytesWritten(); + this->addInt(prevOffset); + fRestoreOffsetStack.top() = SkToU32(offset); + return offset; +} + +void SkPictureRecord::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) { + this->recordClipRect(rect, op, kSoft_ClipEdgeStyle == edgeStyle); + this->INHERITED::onClipRect(rect, op, edgeStyle); +} + +size_t SkPictureRecord::recordClipRect(const SkRect& rect, SkClipOp op, bool doAA) { + // id + rect + clip params + size_t size = 1 * kUInt32Size + sizeof(rect) + 1 * kUInt32Size; + // recordRestoreOffsetPlaceholder doesn't always write an offset + if (!fRestoreOffsetStack.isEmpty()) { + // + restore offset + size += kUInt32Size; + } + size_t initialOffset = this->addDraw(CLIP_RECT, &size); + this->addRect(rect); + this->addInt(ClipParams_pack(op, doAA)); + size_t offset = this->recordRestoreOffsetPlaceholder(op); + + this->validate(initialOffset, size); + return offset; +} + +void SkPictureRecord::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) { + this->recordClipRRect(rrect, op, kSoft_ClipEdgeStyle == edgeStyle); + this->INHERITED::onClipRRect(rrect, op, edgeStyle); +} + +size_t SkPictureRecord::recordClipRRect(const SkRRect& rrect, SkClipOp op, bool doAA) { + // op + rrect + clip params + size_t size = 1 * kUInt32Size + SkRRect::kSizeInMemory + 1 * kUInt32Size; + // recordRestoreOffsetPlaceholder doesn't always write an offset + if (!fRestoreOffsetStack.isEmpty()) { + // + restore offset + size += kUInt32Size; + } + size_t initialOffset = this->addDraw(CLIP_RRECT, &size); + this->addRRect(rrect); + this->addInt(ClipParams_pack(op, doAA)); + size_t offset = recordRestoreOffsetPlaceholder(op); + this->validate(initialOffset, size); + return offset; +} + +void SkPictureRecord::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) { + int pathID = this->addPathToHeap(path); + this->recordClipPath(pathID, op, kSoft_ClipEdgeStyle == edgeStyle); + this->INHERITED::onClipPath(path, op, edgeStyle); +} + +size_t SkPictureRecord::recordClipPath(int pathID, SkClipOp op, bool doAA) { + // op + path index + clip params + size_t size = 3 * kUInt32Size; + // recordRestoreOffsetPlaceholder doesn't always write an offset + if (!fRestoreOffsetStack.isEmpty()) { + // + restore offset + size += kUInt32Size; + } + size_t initialOffset = this->addDraw(CLIP_PATH, &size); + this->addInt(pathID); + this->addInt(ClipParams_pack(op, doAA)); + size_t offset = recordRestoreOffsetPlaceholder(op); + this->validate(initialOffset, size); + return offset; +} + +void SkPictureRecord::onClipRegion(const SkRegion& region, SkClipOp op) { + this->recordClipRegion(region, op); + this->INHERITED::onClipRegion(region, op); +} + +size_t SkPictureRecord::recordClipRegion(const SkRegion& region, SkClipOp op) { + // op + clip params + region + size_t size = 2 * kUInt32Size + region.writeToMemory(nullptr); + // recordRestoreOffsetPlaceholder doesn't always write an offset + if (!fRestoreOffsetStack.isEmpty()) { + // + restore offset + size += kUInt32Size; + } + size_t initialOffset = this->addDraw(CLIP_REGION, &size); + this->addRegion(region); + this->addInt(ClipParams_pack(op, false)); + size_t offset = this->recordRestoreOffsetPlaceholder(op); + + this->validate(initialOffset, size); + return offset; +} + +void SkPictureRecord::onDrawPaint(const SkPaint& paint) { + // op + paint index + size_t size = 2 * kUInt32Size; + size_t initialOffset = this->addDraw(DRAW_PAINT, &size); + this->addPaint(paint); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawBehind(const SkPaint& paint) { + // logically the same as drawPaint, but with a diff enum + // op + paint index + size_t size = 2 * kUInt32Size; + size_t initialOffset = this->addDraw(DRAW_BEHIND_PAINT, &size); + this->addPaint(paint); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + // op + paint index + mode + count + point data + size_t size = 4 * kUInt32Size + count * sizeof(SkPoint); + size_t initialOffset = this->addDraw(DRAW_POINTS, &size); + this->addPaint(paint); + + this->addInt(mode); + this->addInt(SkToInt(count)); + fWriter.writeMul4(pts, count * sizeof(SkPoint)); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawOval(const SkRect& oval, const SkPaint& paint) { + // op + paint index + rect + size_t size = 2 * kUInt32Size + sizeof(oval); + size_t initialOffset = this->addDraw(DRAW_OVAL, &size); + this->addPaint(paint); + this->addRect(oval); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) { + // op + paint index + rect + start + sweep + bool (as int) + size_t size = 2 * kUInt32Size + sizeof(oval) + sizeof(startAngle) + sizeof(sweepAngle) + + sizeof(int); + size_t initialOffset = this->addDraw(DRAW_ARC, &size); + this->addPaint(paint); + this->addRect(oval); + this->addScalar(startAngle); + this->addScalar(sweepAngle); + this->addInt(useCenter); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawRect(const SkRect& rect, const SkPaint& paint) { + // op + paint index + rect + size_t size = 2 * kUInt32Size + sizeof(rect); + size_t initialOffset = this->addDraw(DRAW_RECT, &size); + this->addPaint(paint); + this->addRect(rect); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + // op + paint index + region + size_t regionBytes = region.writeToMemory(nullptr); + size_t size = 2 * kUInt32Size + regionBytes; + size_t initialOffset = this->addDraw(DRAW_REGION, &size); + this->addPaint(paint); + fWriter.writeRegion(region); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { + // op + paint index + rrect + size_t size = 2 * kUInt32Size + SkRRect::kSizeInMemory; + size_t initialOffset = this->addDraw(DRAW_RRECT, &size); + this->addPaint(paint); + this->addRRect(rrect); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + // op + paint index + rrects + size_t size = 2 * kUInt32Size + SkRRect::kSizeInMemory * 2; + size_t initialOffset = this->addDraw(DRAW_DRRECT, &size); + this->addPaint(paint); + this->addRRect(outer); + this->addRRect(inner); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawPath(const SkPath& path, const SkPaint& paint) { + // op + paint index + path index + size_t size = 3 * kUInt32Size; + size_t initialOffset = this->addDraw(DRAW_PATH, &size); + this->addPaint(paint); + this->addPath(path); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawImage(const SkImage* image, SkScalar x, SkScalar y, + const SkPaint* paint) { + // op + paint_index + image_index + x + y + size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar); + size_t initialOffset = this->addDraw(DRAW_IMAGE, &size); + this->addPaintPtr(paint); + this->addImage(image); + this->addScalar(x); + this->addScalar(y); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + // id + paint_index + image_index + bool_for_src + constraint + size_t size = 5 * kUInt32Size; + if (src) { + size += sizeof(*src); // + rect + } + size += sizeof(dst); // + rect + + size_t initialOffset = this->addDraw(DRAW_IMAGE_RECT, &size); + this->addPaintPtr(paint); + this->addImage(image); + this->addRectPtr(src); // may be null + this->addRect(dst); + this->addInt(constraint); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawImageNine(const SkImage* img, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + // id + paint_index + image_index + center + dst + size_t size = 3 * kUInt32Size + sizeof(SkIRect) + sizeof(SkRect); + + size_t initialOffset = this->addDraw(DRAW_IMAGE_NINE, &size); + this->addPaintPtr(paint); + this->addImage(img); + this->addIRect(center); + this->addRect(dst); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawImageLattice(const SkImage* image, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + size_t latticeSize = SkCanvasPriv::WriteLattice(nullptr, lattice); + // op + paint index + image index + lattice + dst rect + size_t size = 3 * kUInt32Size + latticeSize + sizeof(dst); + size_t initialOffset = this->addDraw(DRAW_IMAGE_LATTICE, &size); + this->addPaintPtr(paint); + this->addImage(image); + (void)SkCanvasPriv::WriteLattice(fWriter.reservePad(latticeSize), lattice); + this->addRect(dst); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + + // op + paint index + blob index + x/y + size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar); + size_t initialOffset = this->addDraw(DRAW_TEXT_BLOB, &size); + + this->addPaint(paint); + this->addTextBlob(blob); + this->addScalar(x); + this->addScalar(y); + + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) { + // op + picture index + size_t size = 2 * kUInt32Size; + size_t initialOffset; + + if (nullptr == matrix && nullptr == paint) { + initialOffset = this->addDraw(DRAW_PICTURE, &size); + this->addPicture(picture); + } else { + const SkMatrix& m = matrix ? *matrix : SkMatrix::I(); + size += SkMatrixPriv::WriteToMemory(m, nullptr) + kUInt32Size; // matrix + paint + initialOffset = this->addDraw(DRAW_PICTURE_MATRIX_PAINT, &size); + this->addPaintPtr(paint); + this->addMatrix(m); + this->addPicture(picture); + } + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { + // op + drawable index + size_t size = 2 * kUInt32Size; + size_t initialOffset; + + if (nullptr == matrix) { + initialOffset = this->addDraw(DRAW_DRAWABLE, &size); + this->addDrawable(drawable); + } else { + size += SkMatrixPriv::WriteToMemory(*matrix, nullptr); // matrix + initialOffset = this->addDraw(DRAW_DRAWABLE_MATRIX, &size); + this->addMatrix(*matrix); + this->addDrawable(drawable); + } + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawVerticesObject(const SkVertices* vertices, + const SkVertices::Bone bones[], int boneCount, + SkBlendMode mode, const SkPaint& paint) { + // op + paint index + vertices index + number of bones + bone matrices + mode + size_t size = 5 * kUInt32Size + boneCount * sizeof(SkVertices::Bone); + size_t initialOffset = this->addDraw(DRAW_VERTICES_OBJECT, &size); + + this->addPaint(paint); + this->addVertices(vertices); + this->addInt(boneCount); + fWriter.write(bones, boneCount * sizeof(SkVertices::Bone)); + this->addInt(static_cast(mode)); + + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode bmode, + const SkPaint& paint) { + // op + paint index + patch 12 control points + flag + patch 4 colors + 4 texture coordinates + size_t size = 2 * kUInt32Size + SkPatchUtils::kNumCtrlPts * sizeof(SkPoint) + kUInt32Size; + uint32_t flag = 0; + if (colors) { + flag |= DRAW_VERTICES_HAS_COLORS; + size += SkPatchUtils::kNumCorners * sizeof(SkColor); + } + if (texCoords) { + flag |= DRAW_VERTICES_HAS_TEXS; + size += SkPatchUtils::kNumCorners * sizeof(SkPoint); + } + if (SkBlendMode::kModulate != bmode) { + flag |= DRAW_VERTICES_HAS_XFER; + size += kUInt32Size; + } + + size_t initialOffset = this->addDraw(DRAW_PATCH, &size); + this->addPaint(paint); + this->addPatch(cubics); + this->addInt(flag); + + // write optional parameters + if (colors) { + fWriter.write(colors, SkPatchUtils::kNumCorners * sizeof(SkColor)); + } + if (texCoords) { + fWriter.write(texCoords, SkPatchUtils::kNumCorners * sizeof(SkPoint)); + } + if (flag & DRAW_VERTICES_HAS_XFER) { + this->addInt((int)bmode); + } + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], + const SkColor colors[], int count, SkBlendMode mode, + const SkRect* cull, const SkPaint* paint) { + // [op + paint-index + atlas-index + flags + count] + [xform] + [tex] + [*colors + mode] + cull + size_t size = 5 * kUInt32Size + count * sizeof(SkRSXform) + count * sizeof(SkRect); + uint32_t flags = 0; + if (colors) { + flags |= DRAW_ATLAS_HAS_COLORS; + size += count * sizeof(SkColor); + size += sizeof(uint32_t); // xfermode::mode + } + if (cull) { + flags |= DRAW_ATLAS_HAS_CULL; + size += sizeof(SkRect); + } + + size_t initialOffset = this->addDraw(DRAW_ATLAS, &size); + this->addPaintPtr(paint); + this->addImage(atlas); + this->addInt(flags); + this->addInt(count); + fWriter.write(xform, count * sizeof(SkRSXform)); + fWriter.write(tex, count * sizeof(SkRect)); + + // write optional parameters + if (colors) { + fWriter.write(colors, count * sizeof(SkColor)); + this->addInt((int)mode); + } + if (cull) { + fWriter.write(cull, sizeof(SkRect)); + } + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + // op + path index + zParams + lightPos + lightRadius + spot/ambient alphas + color + flags + size_t size = 2 * kUInt32Size + 2 * sizeof(SkPoint3) + 1 * sizeof(SkScalar) + 3 * kUInt32Size; + size_t initialOffset = this->addDraw(DRAW_SHADOW_REC, &size); + + this->addPath(path); + + fWriter.writePoint3(rec.fZPlaneParams); + fWriter.writePoint3(rec.fLightPos); + fWriter.writeScalar(rec.fLightRadius); + fWriter.write32(rec.fAmbientColor); + fWriter.write32(rec.fSpotColor); + fWriter.write32(rec.fFlags); + + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) { + size_t keyLen = fWriter.WriteStringSize(key); + size_t valueLen = fWriter.WriteDataSize(value); + size_t size = 4 + sizeof(SkRect) + keyLen + valueLen; + + size_t initialOffset = this->addDraw(DRAW_ANNOTATION, &size); + this->addRect(rect); + fWriter.writeString(key); + fWriter.writeData(value); + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + SkCanvas::QuadAAFlags aa, const SkColor4f& color, + SkBlendMode mode) { + + // op + rect + aa flags + color + mode + hasClip(as int) + clipCount*points + size_t size = 4 * kUInt32Size + sizeof(SkColor4f) + sizeof(rect) + + (clip ? 4 : 0) * sizeof(SkPoint); + size_t initialOffset = this->addDraw(DRAW_EDGEAA_QUAD, &size); + this->addRect(rect); + this->addInt((int) aa); + fWriter.write(&color, sizeof(SkColor4f)); + this->addInt((int) mode); + this->addInt(clip != nullptr); + if (clip) { + this->addPoints(clip, 4); + } + this->validate(initialOffset, size); +} + +void SkPictureRecord::onDrawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count, + const SkPoint dstClips[], + const SkMatrix preViewMatrices[], + const SkPaint* paint, + SkCanvas::SrcRectConstraint constraint) { + static constexpr size_t kMatrixSize = 9 * sizeof(SkScalar); // *not* sizeof(SkMatrix) + // op + count + paint + constraint + (image index, src rect, dst rect, alpha, aa flags, + // hasClip(int), matrixIndex) * cnt + totalClipCount + dstClips + totalMatrixCount + matrices + int totalDstClipCount, totalMatrixCount; + SkCanvasPriv::GetDstClipAndMatrixCounts(set, count, &totalDstClipCount, &totalMatrixCount); + + size_t size = 6 * kUInt32Size + sizeof(SkPoint) * totalDstClipCount + + kMatrixSize * totalMatrixCount + + (4 * kUInt32Size + 2 * sizeof(SkRect) + sizeof(SkScalar)) * count; + size_t initialOffset = this->addDraw(DRAW_EDGEAA_IMAGE_SET, &size); + this->addInt(count); + this->addPaintPtr(paint); + this->addInt((int) constraint); + for (int i = 0; i < count; ++i) { + this->addImage(set[i].fImage.get()); + this->addRect(set[i].fSrcRect); + this->addRect(set[i].fDstRect); + this->addInt(set[i].fMatrixIndex); + this->addScalar(set[i].fAlpha); + this->addInt((int)set[i].fAAFlags); + this->addInt(set[i].fHasClip); + } + this->addInt(totalDstClipCount); + this->addPoints(dstClips, totalDstClipCount); + this->addInt(totalMatrixCount); + for (int i = 0; i < totalMatrixCount; ++i) { + this->addMatrix(preViewMatrices[i]); + } + this->validate(initialOffset, size); +} + +/////////////////////////////////////////////////////////////////////////////// + +// De-duping helper. + +template +static bool equals(T* a, T* b) { return a->uniqueID() == b->uniqueID(); } + +template <> +bool equals(SkDrawable* a, SkDrawable* b) { + // SkDrawable's generationID is not a stable unique identifier. + return a == b; +} + +template +static int find_or_append(SkTArray>& array, T* obj) { + for (int i = 0; i < array.count(); i++) { + if (equals(array[i].get(), obj)) { + return i; + } + } + + array.push_back(sk_ref_sp(obj)); + + return array.count() - 1; +} + +sk_sp SkPictureRecord::onNewSurface(const SkImageInfo& info, const SkSurfaceProps&) { + return nullptr; +} + +void SkPictureRecord::addImage(const SkImage* image) { + // convention for images is 0-based index + this->addInt(find_or_append(fImages, image)); +} + +void SkPictureRecord::addMatrix(const SkMatrix& matrix) { + fWriter.writeMatrix(matrix); +} + +void SkPictureRecord::addPaintPtr(const SkPaint* paint) { + if (paint) { + fPaints.push_back(*paint); + this->addInt(fPaints.count()); + } else { + this->addInt(0); + } +} + +int SkPictureRecord::addPathToHeap(const SkPath& path) { + if (int* n = fPaths.find(path)) { + return *n; + } + int n = fPaths.count() + 1; // 0 is reserved for null / error. + fPaths.set(path, n); + return n; +} + +void SkPictureRecord::addPath(const SkPath& path) { + this->addInt(this->addPathToHeap(path)); +} + +void SkPictureRecord::addPatch(const SkPoint cubics[12]) { + fWriter.write(cubics, SkPatchUtils::kNumCtrlPts * sizeof(SkPoint)); +} + +void SkPictureRecord::addPicture(const SkPicture* picture) { + // follow the convention of recording a 1-based index + this->addInt(find_or_append(fPictures, picture) + 1); +} + +void SkPictureRecord::addDrawable(SkDrawable* drawable) { + // follow the convention of recording a 1-based index + this->addInt(find_or_append(fDrawables, drawable) + 1); +} + +void SkPictureRecord::addPoint(const SkPoint& point) { + fWriter.writePoint(point); +} + +void SkPictureRecord::addPoints(const SkPoint pts[], int count) { + fWriter.writeMul4(pts, count * sizeof(SkPoint)); +} + +void SkPictureRecord::addNoOp() { + size_t size = kUInt32Size; // op + this->addDraw(NOOP, &size); +} + +void SkPictureRecord::addRect(const SkRect& rect) { + fWriter.writeRect(rect); +} + +void SkPictureRecord::addRectPtr(const SkRect* rect) { + if (fWriter.writeBool(rect != nullptr)) { + fWriter.writeRect(*rect); + } +} + +void SkPictureRecord::addIRect(const SkIRect& rect) { + fWriter.write(&rect, sizeof(rect)); +} + +void SkPictureRecord::addIRectPtr(const SkIRect* rect) { + if (fWriter.writeBool(rect != nullptr)) { + *(SkIRect*)fWriter.reserve(sizeof(SkIRect)) = *rect; + } +} + +void SkPictureRecord::addRRect(const SkRRect& rrect) { + fWriter.writeRRect(rrect); +} + +void SkPictureRecord::addRegion(const SkRegion& region) { + fWriter.writeRegion(region); +} + +void SkPictureRecord::addText(const void* text, size_t byteLength) { + addInt(SkToInt(byteLength)); + fWriter.writePad(text, byteLength); +} + +void SkPictureRecord::addTextBlob(const SkTextBlob* blob) { + // follow the convention of recording a 1-based index + this->addInt(find_or_append(fTextBlobs, blob) + 1); +} + +void SkPictureRecord::addVertices(const SkVertices* vertices) { + // follow the convention of recording a 1-based index + this->addInt(find_or_append(fVertices, vertices) + 1); +} + +/////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/skia/skia/src/core/SkPictureRecord.h b/gfx/skia/skia/src/core/SkPictureRecord.h new file mode 100644 index 0000000000..b83eec2893 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureRecord.h @@ -0,0 +1,271 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureRecord_DEFINED +#define SkPictureRecord_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkCanvasVirtualEnforcer.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkPicture.h" +#include "include/core/SkVertices.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTHash.h" +#include "include/private/SkTo.h" +#include "src/core/SkPictureData.h" +#include "src/core/SkWriter32.h" + +// These macros help with packing and unpacking a single byte value and +// a 3 byte value into/out of a uint32_t +#define MASK_24 0x00FFFFFF +#define UNPACK_8_24(combined, small, large) \ + small = (combined >> 24) & 0xFF; \ + large = combined & MASK_24 +#define PACK_8_24(small, large) ((small << 24) | large) + + +class SkPictureRecord : public SkCanvasVirtualEnforcer { +public: + SkPictureRecord(const SkISize& dimensions, uint32_t recordFlags); + + SkPictureRecord(const SkIRect& dimensions, uint32_t recordFlags); + + const SkTArray>& getPictures() const { + return fPictures; + } + + const SkTArray>& getDrawables() const { + return fDrawables; + } + + const SkTArray>& getTextBlobs() const { + return fTextBlobs; + } + + const SkTArray>& getVertices() const { + return fVertices; + } + + const SkTArray>& getImages() const { + return fImages; + } + + sk_sp opData() const { + this->validate(fWriter.bytesWritten(), 0); + + if (fWriter.bytesWritten() == 0) { + return SkData::MakeEmpty(); + } + return fWriter.snapshotAsData(); + } + + void setFlags(uint32_t recordFlags) { + fRecordFlags = recordFlags; + } + + const SkWriter32& writeStream() const { + return fWriter; + } + + void beginRecording(); + void endRecording(); + +protected: + void addNoOp(); + +private: + void handleOptimization(int opt); + size_t recordRestoreOffsetPlaceholder(SkClipOp); + void fillRestoreOffsetPlaceholdersForCurrentStackLevel(uint32_t restoreOffset); + + SkTDArray fRestoreOffsetStack; + + SkTDArray fCullOffsetStack; + + /* + * Write the 'drawType' operation and chunk size to the skp. 'size' + * can potentially be increased if the chunk size needs its own storage + * location (i.e., it overflows 24 bits). + * Returns the start offset of the chunk. This is the location at which + * the opcode & size are stored. + * TODO: since we are handing the size into here we could call reserve + * and then return a pointer to the memory storage. This could decrease + * allocation overhead but could lead to more wasted space (the tail + * end of blocks could go unused). Possibly add a second addDraw that + * operates in this manner. + */ + size_t addDraw(DrawType drawType, size_t* size) { + size_t offset = fWriter.bytesWritten(); + + this->predrawNotify(); + + SkASSERT(0 != *size); + SkASSERT(((uint8_t) drawType) == drawType); + + if (0 != (*size & ~MASK_24) || *size == MASK_24) { + fWriter.writeInt(PACK_8_24(drawType, MASK_24)); + *size += 1; + fWriter.writeInt(SkToU32(*size)); + } else { + fWriter.writeInt(PACK_8_24(drawType, SkToU32(*size))); + } + + return offset; + } + + void addInt(int value) { + fWriter.writeInt(value); + } + void addScalar(SkScalar scalar) { + fWriter.writeScalar(scalar); + } + + void addImage(const SkImage*); + void addMatrix(const SkMatrix& matrix); + void addPaint(const SkPaint& paint) { this->addPaintPtr(&paint); } + void addPaintPtr(const SkPaint* paint); + void addPatch(const SkPoint cubics[12]); + void addPath(const SkPath& path); + void addPicture(const SkPicture* picture); + void addDrawable(SkDrawable* picture); + void addPoint(const SkPoint& point); + void addPoints(const SkPoint pts[], int count); + void addRect(const SkRect& rect); + void addRectPtr(const SkRect* rect); + void addIRect(const SkIRect& rect); + void addIRectPtr(const SkIRect* rect); + void addRRect(const SkRRect&); + void addRegion(const SkRegion& region); + void addText(const void* text, size_t byteLength); + void addTextBlob(const SkTextBlob* blob); + void addVertices(const SkVertices*); + + int find(const SkBitmap& bitmap); + +protected: + void validate(size_t initialOffset, size_t size) const { + SkASSERT(fWriter.bytesWritten() == initialOffset + size); + } + + sk_sp onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; + bool onPeekPixels(SkPixmap*) override { return false; } + + void onFlush() override; + + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + bool onDoSaveBehind(const SkRect*) override; + void willRestore() override; + + void didConcat(const SkMatrix&) override; + void didSetMatrix(const SkMatrix&) override; + + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode, const SkPaint& paint) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int, + SkBlendMode, const SkRect*, const SkPaint*) override; + + void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) override; + void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const SkCanvas::Lattice& lattice, const SkRect& dst, + const SkPaint*) override; + + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; + + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + void onDrawAnnotation(const SkRect&, const char[], SkData*) override; + + void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, const SkColor4f&, + SkBlendMode) override; + void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[], + const SkPaint*, SrcRectConstraint) override; + + int addPathToHeap(const SkPath& path); // does not write to ops stream + + // These entry points allow the writing of matrices, clips, saves & + // restores to be deferred (e.g., if the MC state is being collapsed and + // only written out as needed). + void recordConcat(const SkMatrix& matrix); + void recordTranslate(const SkMatrix& matrix); + void recordScale(const SkMatrix& matrix); + size_t recordClipRect(const SkRect& rect, SkClipOp op, bool doAA); + size_t recordClipRRect(const SkRRect& rrect, SkClipOp op, bool doAA); + size_t recordClipPath(int pathID, SkClipOp op, bool doAA); + size_t recordClipRegion(const SkRegion& region, SkClipOp op); + void recordSave(); + void recordSaveLayer(const SaveLayerRec&); + void recordRestore(bool fillInSkips = true); + + // SHOULD NEVER BE CALLED + void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override { + SK_ABORT("not reached"); + } + void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*, + SrcRectConstraint) override { + SK_ABORT("not reached"); + } + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) override { + SK_ABORT("not reached"); + } + void onDrawBitmapLattice(const SkBitmap&, const SkCanvas::Lattice& lattice, const SkRect& dst, + const SkPaint*) override { + SK_ABORT("not reached"); + } + +private: + SkTArray fPaints; + + struct PathHash { + uint32_t operator()(const SkPath& p) { return p.getGenerationID(); } + }; + SkTHashMap fPaths; + + SkWriter32 fWriter; + + SkTArray> fImages; + SkTArray> fPictures; + SkTArray> fDrawables; + SkTArray> fTextBlobs; + SkTArray> fVertices; + + uint32_t fRecordFlags; + int fInitialSaveCount; + + friend class SkPictureData; // for SkPictureData's SkPictureRecord-based constructor + + typedef SkCanvasVirtualEnforcer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPictureRecorder.cpp b/gfx/skia/skia/src/core/SkPictureRecorder.cpp new file mode 100644 index 0000000000..20cc7d8e32 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPictureRecorder.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkPictureRecorder.h" +#include "include/core/SkTypes.h" +#include "src/core/SkBigPicture.h" +#include "src/core/SkMiniRecorder.h" +#include "src/core/SkRecord.h" +#include "src/core/SkRecordDraw.h" +#include "src/core/SkRecordOpts.h" +#include "src/core/SkRecordedDrawable.h" +#include "src/core/SkRecorder.h" + +SkPictureRecorder::SkPictureRecorder() { + fActivelyRecording = false; + fMiniRecorder.reset(new SkMiniRecorder); + fRecorder.reset(new SkRecorder(nullptr, SkRect::MakeEmpty(), fMiniRecorder.get())); +} + +SkPictureRecorder::~SkPictureRecorder() {} + +SkCanvas* SkPictureRecorder::beginRecording(const SkRect& userCullRect, + SkBBHFactory* bbhFactory /* = nullptr */, + uint32_t recordFlags /* = 0 */) { + const SkRect cullRect = userCullRect.isEmpty() ? SkRect::MakeEmpty() : userCullRect; + + fCullRect = cullRect; + fFlags = recordFlags; + + if (bbhFactory) { + fBBH.reset((*bbhFactory)()); + SkASSERT(fBBH.get()); + } + + if (!fRecord) { + fRecord.reset(new SkRecord); + } + SkRecorder::DrawPictureMode dpm = (recordFlags & kPlaybackDrawPicture_RecordFlag) + ? SkRecorder::Playback_DrawPictureMode + : SkRecorder::Record_DrawPictureMode; + fRecorder->reset(fRecord.get(), cullRect, dpm, fMiniRecorder.get()); + fActivelyRecording = true; + return this->getRecordingCanvas(); +} + +SkCanvas* SkPictureRecorder::getRecordingCanvas() { + return fActivelyRecording ? fRecorder.get() : nullptr; +} + +sk_sp SkPictureRecorder::finishRecordingAsPicture(uint32_t finishFlags) { + fActivelyRecording = false; + fRecorder->restoreToCount(1); // If we were missing any restores, add them now. + + if (fRecord->count() == 0) { + auto pic = fMiniRecorder->detachAsPicture(fBBH ? nullptr : &fCullRect); + fBBH.reset(nullptr); + return pic; + } + + // TODO: delay as much of this work until just before first playback? + SkRecordOptimize(fRecord.get()); + + SkDrawableList* drawableList = fRecorder->getDrawableList(); + SkBigPicture::SnapshotArray* pictList = + drawableList ? drawableList->newDrawableSnapshot() : nullptr; + + if (fBBH.get()) { + SkAutoTMalloc bounds(fRecord->count()); + SkRecordFillBounds(fCullRect, *fRecord, bounds); + fBBH->insert(bounds, fRecord->count()); + + // Now that we've calculated content bounds, we can update fCullRect, often trimming it. + // TODO: get updated fCullRect from bounds instead of forcing the BBH to return it? + SkRect bbhBound = fBBH->getRootBound(); + SkASSERT((bbhBound.isEmpty() || fCullRect.contains(bbhBound)) + || (bbhBound.isEmpty() && fCullRect.isEmpty())); + fCullRect = bbhBound; + } + + size_t subPictureBytes = fRecorder->approxBytesUsedBySubPictures(); + for (int i = 0; pictList && i < pictList->count(); i++) { + subPictureBytes += pictList->begin()[i]->approximateBytesUsed(); + } + return sk_make_sp(fCullRect, fRecord.release(), pictList, fBBH.release(), + subPictureBytes); +} + +sk_sp SkPictureRecorder::finishRecordingAsPictureWithCull(const SkRect& cullRect, + uint32_t finishFlags) { + fCullRect = cullRect; + return this->finishRecordingAsPicture(finishFlags); +} + + +void SkPictureRecorder::partialReplay(SkCanvas* canvas) const { + if (nullptr == canvas) { + return; + } + + int drawableCount = 0; + SkDrawable* const* drawables = nullptr; + SkDrawableList* drawableList = fRecorder->getDrawableList(); + if (drawableList) { + drawableCount = drawableList->count(); + drawables = drawableList->begin(); + } + SkRecordDraw(*fRecord, canvas, nullptr, drawables, drawableCount, nullptr/*bbh*/, nullptr/*callback*/); +} + +sk_sp SkPictureRecorder::finishRecordingAsDrawable(uint32_t finishFlags) { + fActivelyRecording = false; + fRecorder->flushMiniRecorder(); + fRecorder->restoreToCount(1); // If we were missing any restores, add them now. + + SkRecordOptimize(fRecord.get()); + + if (fBBH.get()) { + SkAutoTMalloc bounds(fRecord->count()); + SkRecordFillBounds(fCullRect, *fRecord, bounds); + fBBH->insert(bounds, fRecord->count()); + } + + sk_sp drawable = + sk_make_sp(std::move(fRecord), std::move(fBBH), + fRecorder->detachDrawableList(), fCullRect); + + return drawable; +} diff --git a/gfx/skia/skia/src/core/SkPixelRef.cpp b/gfx/skia/skia/src/core/SkPixelRef.cpp new file mode 100644 index 0000000000..ac37f725a0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPixelRef.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPixelRef.h" +#include "include/private/SkMutex.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkNextID.h" +#include "src/core/SkPixelRefPriv.h" +#include "src/core/SkTraceEvent.h" + +#include + +uint32_t SkNextID::ImageID() { + // We never set the low bit.... see SkPixelRef::genIDIsUnique(). + static std::atomic nextID{2}; + + uint32_t id; + do { + id = nextID.fetch_add(2); + } while (id == 0); + return id; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPixelRef::SkPixelRef(int width, int height, void* pixels, size_t rowBytes) + : fWidth(width) + , fHeight(height) + , fPixels(pixels) + , fRowBytes(rowBytes) + , fAddedToCache(false) +{ + this->needsNewGenID(); + fMutability = kMutable; +} + +SkPixelRef::~SkPixelRef() { + this->callGenIDChangeListeners(); +} + +// This is undefined if there are clients in-flight trying to use us +void SkPixelRef::android_only_reset(int width, int height, size_t rowBytes) { + fWidth = width; + fHeight = height; + fRowBytes = rowBytes; + // note: we do not change fPixels + + // conservative, since its possible the "new" settings are the same as the old. + this->notifyPixelsChanged(); +} + +void SkPixelRef::needsNewGenID() { + fTaggedGenID.store(0); + SkASSERT(!this->genIDIsUnique()); // This method isn't threadsafe, so the assert should be fine. +} + +uint32_t SkPixelRef::getGenerationID() const { + uint32_t id = fTaggedGenID.load(); + if (0 == id) { + uint32_t next = SkNextID::ImageID() | 1u; + if (fTaggedGenID.compare_exchange_strong(id, next)) { + id = next; // There was no race or we won the race. fTaggedGenID is next now. + } else { + // We lost a race to set fTaggedGenID. compare_exchange() filled id with the winner. + } + // We can't quite SkASSERT(this->genIDIsUnique()). It could be non-unique + // if we got here via the else path (pretty unlikely, but possible). + } + return id & ~1u; // Mask off bottom unique bit. +} + +void SkPixelRef::addGenIDChangeListener(GenIDChangeListener* listener) { + if (nullptr == listener || !this->genIDIsUnique()) { + // No point in tracking this if we're not going to call it. + delete listener; + return; + } + SkAutoMutexExclusive lock(fGenIDChangeListenersMutex); + *fGenIDChangeListeners.append() = listener; +} + +// we need to be called *before* the genID gets changed or zerod +void SkPixelRef::callGenIDChangeListeners() { + SkAutoMutexExclusive lock(fGenIDChangeListenersMutex); + // We don't invalidate ourselves if we think another SkPixelRef is sharing our genID. + if (this->genIDIsUnique()) { + for (int i = 0; i < fGenIDChangeListeners.count(); i++) { + fGenIDChangeListeners[i]->onChange(); + } + + if (fAddedToCache.exchange(false)) { + SkNotifyBitmapGenIDIsStale(this->getGenerationID()); + } + } + // Listeners get at most one shot, so whether these triggered or not, blow them away. + fGenIDChangeListeners.deleteAll(); +} + +void SkPixelRef::notifyPixelsChanged() { +#ifdef SK_DEBUG + if (this->isImmutable()) { + SkDebugf("========== notifyPixelsChanged called on immutable pixelref"); + } +#endif + this->callGenIDChangeListeners(); + this->needsNewGenID(); +} + +void SkPixelRef::setImmutable() { + fMutability = kImmutable; +} + +void SkPixelRef::setImmutableWithID(uint32_t genID) { + /* + * We are forcing the genID to match an external value. The caller must ensure that this + * value does not conflict with other content. + * + * One use is to force this pixelref's id to match an SkImage's id + */ + fMutability = kImmutable; + fTaggedGenID.store(genID); +} + +void SkPixelRef::setTemporarilyImmutable() { + SkASSERT(fMutability != kImmutable); + fMutability = kTemporarilyImmutable; +} + +void SkPixelRef::restoreMutability() { + SkASSERT(fMutability != kImmutable); + fMutability = kMutable; +} + +sk_sp SkMakePixelRefWithProc(int width, int height, size_t rowBytes, void* addr, + void (*releaseProc)(void* addr, void* ctx), void* ctx) { + SkASSERT(width >= 0 && height >= 0); + if (nullptr == releaseProc) { + return sk_make_sp(width, height, addr, rowBytes); + } + struct PixelRef final : public SkPixelRef { + void (*fReleaseProc)(void*, void*); + void* fReleaseProcContext; + PixelRef(int w, int h, void* s, size_t r, void (*proc)(void*, void*), void* ctx) + : SkPixelRef(w, h, s, r), fReleaseProc(proc), fReleaseProcContext(ctx) {} + ~PixelRef() override { fReleaseProc(this->pixels(), fReleaseProcContext); } + }; + return sk_sp(new PixelRef(width, height, addr, rowBytes, releaseProc, ctx)); +} diff --git a/gfx/skia/skia/src/core/SkPixelRefPriv.h b/gfx/skia/skia/src/core/SkPixelRefPriv.h new file mode 100644 index 0000000000..a227b06daa --- /dev/null +++ b/gfx/skia/skia/src/core/SkPixelRefPriv.h @@ -0,0 +1,20 @@ +// Copyright 2019 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +#ifndef SkPixelRefPriv_DEFINED +#define SkPixelRefPriv_DEFINED +/** + * Return a new SkMallocPixelRef with the provided pixel storage and + * rowBytes. On destruction, ReleaseProc will be called. + * + * If ReleaseProc is NULL, the pixels will never be released. This + * can be useful if the pixels were stack allocated. However, such an + * SkMallocPixelRef must not live beyond its pixels (e.g. by copying + * an SkBitmap pointing to it, or drawing to an SkPicture). + * + * Returns NULL on failure. + */ +sk_sp SkMakePixelRefWithProc(int w, int h, size_t rowBytes, void* addr, + void (*releaseProc)(void* addr, void* ctx), void* ctx); + +#endif // SkPixelRefPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkPixmap.cpp b/gfx/skia/skia/src/core/SkPixmap.cpp new file mode 100644 index 0000000000..c8a09ff7c7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPixmap.cpp @@ -0,0 +1,594 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPixmap.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkSurface.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkNx.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkDraw.h" +#include "src/core/SkMask.h" +#include "src/core/SkPixmapPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkUtils.h" +#include "src/image/SkReadPixelsRec.h" +#include "src/shaders/SkImageShader.h" + +#include + +///////////////////////////////////////////////////////////////////////////////////////////////// + +void SkPixmap::reset() { + fPixels = nullptr; + fRowBytes = 0; + fInfo = SkImageInfo::MakeUnknown(); +} + +void SkPixmap::reset(const SkImageInfo& info, const void* addr, size_t rowBytes) { + if (addr) { + SkASSERT(info.validRowBytes(rowBytes)); + } + fPixels = addr; + fRowBytes = rowBytes; + fInfo = info; +} + +bool SkPixmap::reset(const SkMask& src) { + if (SkMask::kA8_Format == src.fFormat) { + this->reset(SkImageInfo::MakeA8(src.fBounds.width(), src.fBounds.height()), + src.fImage, src.fRowBytes); + return true; + } + this->reset(); + return false; +} + +void SkPixmap::setColorSpace(sk_sp cs) { + fInfo = fInfo.makeColorSpace(std::move(cs)); +} + +bool SkPixmap::extractSubset(SkPixmap* result, const SkIRect& subset) const { + SkIRect srcRect, r; + srcRect.setWH(this->width(), this->height()); + if (!r.intersect(srcRect, subset)) { + return false; // r is empty (i.e. no intersection) + } + + // If the upper left of the rectangle was outside the bounds of this SkBitmap, we should have + // exited above. + SkASSERT(static_cast(r.fLeft) < static_cast(this->width())); + SkASSERT(static_cast(r.fTop) < static_cast(this->height())); + + const void* pixels = nullptr; + if (fPixels) { + const size_t bpp = fInfo.bytesPerPixel(); + pixels = (const uint8_t*)fPixels + r.fTop * fRowBytes + r.fLeft * bpp; + } + result->reset(fInfo.makeDimensions(r.size()), pixels, fRowBytes); + return true; +} + +// This is the same as SkPixmap::addr(x,y), but this version gets inlined, while the public +// method does not. Perhaps we could bloat it so it can be inlined, but that would grow code-size +// everywhere, instead of just here (on behalf of getAlphaf()). +static const void* fast_getaddr(const SkPixmap& pm, int x, int y) { + x <<= SkColorTypeShiftPerPixel(pm.colorType()); + return static_cast(pm.addr()) + y * pm.rowBytes() + x; +} + +float SkPixmap::getAlphaf(int x, int y) const { + SkASSERT(this->addr()); + SkASSERT((unsigned)x < (unsigned)this->width()); + SkASSERT((unsigned)y < (unsigned)this->height()); + + float value = 0; + const void* srcPtr = fast_getaddr(*this, x, y); + + switch (this->colorType()) { + case kUnknown_SkColorType: + return 0; + case kGray_8_SkColorType: + case kR8G8_unorm_SkColorType: + case kR16G16_unorm_SkColorType: + case kR16G16_float_SkColorType: + case kRGB_565_SkColorType: + case kRGB_888x_SkColorType: + case kRGB_101010x_SkColorType: + return 1; + case kAlpha_8_SkColorType: + value = static_cast(srcPtr)[0] * (1.0f/255); + break; + case kA16_unorm_SkColorType: + value = static_cast(srcPtr)[0] * (1.0f/65535); + break; + case kA16_float_SkColorType: { + SkHalf half = static_cast(srcPtr)[0]; + value = SkHalfToFloat(half); + break; + } + case kARGB_4444_SkColorType: { + uint16_t u16 = static_cast(srcPtr)[0]; + value = SkGetPackedA4444(u16) * (1.0f/15); + break; + } + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + value = static_cast(srcPtr)[3] * (1.0f/255); + break; + case kRGBA_1010102_SkColorType: { + uint32_t u32 = static_cast(srcPtr)[0]; + value = (u32 >> 30) * (1.0f/3); + break; + } + case kR16G16B16A16_unorm_SkColorType: { + uint64_t u64 = static_cast(srcPtr)[0]; + value = (u64 >> 48) * (1.0f/65535); + break; + } + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: { + uint64_t px; + memcpy(&px, srcPtr, sizeof(px)); + value = SkHalfToFloat_finite_ftz(px)[3]; + break; + } + case kRGBA_F32_SkColorType: + value = static_cast(srcPtr)[3]; + break; + } + return value; +} + +bool SkPixmap::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + int x, int y) const { + if (!SkImageInfoValidConversion(dstInfo, fInfo)) { + return false; + } + + SkReadPixelsRec rec(dstInfo, dstPixels, dstRB, x, y); + if (!rec.trim(fInfo.width(), fInfo.height())) { + return false; + } + + const void* srcPixels = this->addr(rec.fX, rec.fY); + const SkImageInfo srcInfo = fInfo.makeDimensions(rec.fInfo.dimensions()); + SkConvertPixels(rec.fInfo, rec.fPixels, rec.fRowBytes, srcInfo, srcPixels, this->rowBytes()); + return true; +} + +bool SkPixmap::erase(SkColor color, const SkIRect& subset) const { + return this->erase(SkColor4f::FromColor(color), &subset); +} + +bool SkPixmap::erase(const SkColor4f& color, const SkIRect* subset) const { + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + paint.setColor4f(color, this->colorSpace()); + + SkIRect clip = this->bounds(); + if (subset && !clip.intersect(*subset)) { + return false; + } + SkRasterClip rc{clip}; + + SkDraw draw; + draw.fDst = *this; + draw.fMatrix = &SkMatrix::I(); + draw.fRC = &rc; + + draw.drawPaint(paint); + return true; +} + +bool SkPixmap::scalePixels(const SkPixmap& actualDst, SkFilterQuality quality) const { + // We may need to tweak how we interpret these just a little below, so we make copies. + SkPixmap src = *this, + dst = actualDst; + + // Can't do anthing with empty src or dst + if (src.width() <= 0 || src.height() <= 0 || + dst.width() <= 0 || dst.height() <= 0) { + return false; + } + + // no scaling involved? + if (src.width() == dst.width() && src.height() == dst.height()) { + return src.readPixels(dst); + } + + // If src and dst are both unpremul, we'll fake the source out to appear as if premul, + // and mark the destination as opaque. This odd combination allows us to scale unpremul + // pixels without ever premultiplying them (perhaps losing information in the color channels). + // This is an idiosyncratic feature of scalePixels(), and is tested by scalepixels_unpremul GM. + bool clampAsIfUnpremul = false; + if (src.alphaType() == kUnpremul_SkAlphaType && + dst.alphaType() == kUnpremul_SkAlphaType) { + src.reset(src.info().makeAlphaType(kPremul_SkAlphaType), src.addr(), src.rowBytes()); + dst.reset(dst.info().makeAlphaType(kOpaque_SkAlphaType), dst.addr(), dst.rowBytes()); + + // We'll need to tell the image shader to clamp to [0,1] instead of the + // usual [0,a] when using a bicubic scaling (kHigh_SkFilterQuality). + clampAsIfUnpremul = true; + } + + SkBitmap bitmap; + if (!bitmap.installPixels(src)) { + return false; + } + bitmap.setImmutable(); // Don't copy when we create an image. + bitmap.setIsVolatile(true); // Disable any caching. + + SkMatrix scale = SkMatrix::MakeRectToRect(SkRect::Make(src.bounds()), + SkRect::Make(dst.bounds()), + SkMatrix::kFill_ScaleToFit); + + // We'll create a shader to do this draw so we have control over the bicubic clamp. + sk_sp shader = SkImageShader::Make(SkImage::MakeFromBitmap(bitmap), + SkTileMode::kClamp, + SkTileMode::kClamp, + &scale, + clampAsIfUnpremul); + + sk_sp surface = SkSurface::MakeRasterDirect(dst.info(), + dst.writable_addr(), + dst.rowBytes()); + if (!shader || !surface) { + return false; + } + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + paint.setFilterQuality(quality); + paint.setShader(std::move(shader)); + surface->getCanvas()->drawPaint(paint); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +SkColor SkPixmap::getColor(int x, int y) const { + SkASSERT(this->addr()); + SkASSERT((unsigned)x < (unsigned)this->width()); + SkASSERT((unsigned)y < (unsigned)this->height()); + + const bool needsUnpremul = (kPremul_SkAlphaType == fInfo.alphaType()); + auto toColor = [needsUnpremul](uint32_t maybePremulColor) { + return needsUnpremul ? SkUnPreMultiply::PMColorToColor(maybePremulColor) + : SkSwizzle_BGRA_to_PMColor(maybePremulColor); + }; + + switch (this->colorType()) { + case kGray_8_SkColorType: { + uint8_t value = *this->addr8(x, y); + return SkColorSetRGB(value, value, value); + } + case kAlpha_8_SkColorType: { + return SkColorSetA(0, *this->addr8(x, y)); + } + case kA16_unorm_SkColorType: { + uint16_t value = *this->addr16(x, y); + return SkColorSetA(0, value * (255 / 65535.0f)); + } + case kA16_float_SkColorType: { + SkHalf value = *this->addr16(x, y); + return SkColorSetA(0, 255 * SkHalfToFloat(value)); + } + case kRGB_565_SkColorType: { + return SkPixel16ToColor(*this->addr16(x, y)); + } + case kARGB_4444_SkColorType: { + uint16_t value = *this->addr16(x, y); + SkPMColor c = SkPixel4444ToPixel32(value); + return toColor(c); + } + case kR8G8_unorm_SkColorType: { + uint16_t value = *this->addr16(x, y); + return (uint32_t)( ((value >> 0) & 0xff) ) << 16 + | (uint32_t)( ((value >> 8) & 0xff) ) << 8 + | 0xff000000; + } + case kR16G16_unorm_SkColorType: { + uint32_t value = *this->addr32(x, y); + return (uint32_t)( ((value >> 0) & 0xffff) * (255/65535.0f) ) << 16 + | (uint32_t)( ((value >> 16) & 0xffff) * (255/65535.0f) ) << 8 + | 0xff000000; + } + case kR16G16_float_SkColorType: { + uint32_t value = *this->addr32(x, y); + uint32_t r = 255 * SkHalfToFloat((value >> 0) & 0xffff); + uint32_t g = 255 * SkHalfToFloat((value >> 16) & 0xffff); + return (r << 16) | (g << 8) | 0xff000000; + } + case kRGB_888x_SkColorType: { + uint32_t value = *this->addr32(x, y); + return SkSwizzle_RB(value | 0xff000000); + } + case kBGRA_8888_SkColorType: { + uint32_t value = *this->addr32(x, y); + SkPMColor c = SkSwizzle_BGRA_to_PMColor(value); + return toColor(c); + } + case kRGBA_8888_SkColorType: { + uint32_t value = *this->addr32(x, y); + SkPMColor c = SkSwizzle_RGBA_to_PMColor(value); + return toColor(c); + } + case kRGB_101010x_SkColorType: { + uint32_t value = *this->addr32(x, y); + // Convert 10-bit rgb to 8-bit bgr, and mask in 0xff alpha at the top. + return (uint32_t)( ((value >> 0) & 0x3ff) * (255/1023.0f) ) << 16 + | (uint32_t)( ((value >> 10) & 0x3ff) * (255/1023.0f) ) << 8 + | (uint32_t)( ((value >> 20) & 0x3ff) * (255/1023.0f) ) << 0 + | 0xff000000; + } + case kRGBA_1010102_SkColorType: { + uint32_t value = *this->addr32(x, y); + + float r = ((value >> 0) & 0x3ff) * (1/1023.0f), + g = ((value >> 10) & 0x3ff) * (1/1023.0f), + b = ((value >> 20) & 0x3ff) * (1/1023.0f), + a = ((value >> 30) & 0x3 ) * (1/ 3.0f); + if (a != 0 && needsUnpremul) { + r = SkTPin(r/a, 0.0f, 1.0f); + g = SkTPin(g/a, 0.0f, 1.0f); + b = SkTPin(b/a, 0.0f, 1.0f); + } + return (uint32_t)( r * 255.0f ) << 16 + | (uint32_t)( g * 255.0f ) << 8 + | (uint32_t)( b * 255.0f ) << 0 + | (uint32_t)( a * 255.0f ) << 24; + } + case kR16G16B16A16_unorm_SkColorType: { + uint64_t value = *this->addr64(x, y); + + float r = ((value ) & 0xffff) * (1/65535.0f), + g = ((value >> 16) & 0xffff) * (1/65535.0f), + b = ((value >> 32) & 0xffff) * (1/65535.0f), + a = ((value >> 48) & 0xffff) * (1/65535.0f); + if (a != 0 && needsUnpremul) { + r *= (1.0f/a); + g *= (1.0f/a); + b *= (1.0f/a); + } + return (uint32_t)( r * 255.0f ) << 16 + | (uint32_t)( g * 255.0f ) << 8 + | (uint32_t)( b * 255.0f ) << 0 + | (uint32_t)( a * 255.0f ) << 24; + } + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: { + const uint64_t* addr = + (const uint64_t*)fPixels + y * (fRowBytes >> 3) + x; + Sk4f p4 = SkHalfToFloat_finite_ftz(*addr); + if (p4[3] && needsUnpremul) { + float inva = 1 / p4[3]; + p4 = p4 * Sk4f(inva, inva, inva, 1); + } + SkColor c; + SkNx_cast(p4 * Sk4f(255) + Sk4f(0.5f)).store(&c); + // p4 is RGBA, but we want BGRA, so we need to swap next + return SkSwizzle_RB(c); + } + case kRGBA_F32_SkColorType: { + const float* rgba = + (const float*)fPixels + 4*y*(fRowBytes >> 4) + 4*x; + Sk4f p4 = Sk4f::Load(rgba); + // From here on, just like F16: + if (p4[3] && needsUnpremul) { + float inva = 1 / p4[3]; + p4 = p4 * Sk4f(inva, inva, inva, 1); + } + SkColor c; + SkNx_cast(p4 * Sk4f(255) + Sk4f(0.5f)).store(&c); + // p4 is RGBA, but we want BGRA, so we need to swap next + return SkSwizzle_RB(c); + } + default: + SkDEBUGFAIL(""); + return SkColorSetARGB(0, 0, 0, 0); + } +} + +bool SkPixmap::computeIsOpaque() const { + const int height = this->height(); + const int width = this->width(); + + switch (this->colorType()) { + case kAlpha_8_SkColorType: { + unsigned a = 0xFF; + for (int y = 0; y < height; ++y) { + const uint8_t* row = this->addr8(0, y); + for (int x = 0; x < width; ++x) { + a &= row[x]; + } + if (0xFF != a) { + return false; + } + } + return true; + } + case kA16_unorm_SkColorType: { + unsigned a = 0xFFFF; + for (int y = 0; y < height; ++y) { + const uint16_t* row = this->addr16(0, y); + for (int x = 0; x < width; ++x) { + a &= row[x]; + } + if (0xFFFF != a) { + return false; + } + } + return true; + } + case kA16_float_SkColorType: { + for (int y = 0; y < height; ++y) { + const SkHalf* row = this->addr16(0, y); + for (int x = 0; x < width; ++x) { + if (row[x] < SK_Half1) { + return false; + } + } + } + return true; + } + case kRGB_565_SkColorType: + case kGray_8_SkColorType: + case kR8G8_unorm_SkColorType: + case kR16G16_unorm_SkColorType: + case kR16G16_float_SkColorType: + case kRGB_888x_SkColorType: + case kRGB_101010x_SkColorType: + return true; + break; + case kARGB_4444_SkColorType: { + unsigned c = 0xFFFF; + for (int y = 0; y < height; ++y) { + const SkPMColor16* row = this->addr16(0, y); + for (int x = 0; x < width; ++x) { + c &= row[x]; + } + if (0xF != SkGetPackedA4444(c)) { + return false; + } + } + return true; + } + case kBGRA_8888_SkColorType: + case kRGBA_8888_SkColorType: { + SkPMColor c = (SkPMColor)~0; + for (int y = 0; y < height; ++y) { + const SkPMColor* row = this->addr32(0, y); + for (int x = 0; x < width; ++x) { + c &= row[x]; + } + if (0xFF != SkGetPackedA32(c)) { + return false; + } + } + return true; + } + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: { + const SkHalf* row = (const SkHalf*)this->addr(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (row[4 * x + 3] < SK_Half1) { + return false; + } + } + row += this->rowBytes() >> 1; + } + return true; + } + case kRGBA_F32_SkColorType: { + const float* row = (const float*)this->addr(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (row[4 * x + 3] < 1.0f) { + return false; + } + } + row += this->rowBytes() >> 2; + } + return true; + } + case kRGBA_1010102_SkColorType: { + uint32_t c = ~0; + for (int y = 0; y < height; ++y) { + const uint32_t* row = this->addr32(0, y); + for (int x = 0; x < width; ++x) { + c &= row[x]; + } + if (0b11 != c >> 30) { + return false; + } + } + return true; + } + case kR16G16B16A16_unorm_SkColorType: { + uint16_t acc = 0xFFFF; + for (int y = 0; y < height; ++y) { + const uint64_t* row = this->addr64(0, y); + for (int x = 0; x < width; ++x) { + acc &= (row[x] >> 48); + } + if (0xFFFF != acc) { + return false; + } + } + return true; + } + case kUnknown_SkColorType: + SkDEBUGFAIL(""); + break; + } + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool draw_orientation(const SkPixmap& dst, const SkPixmap& src, SkEncodedOrigin origin) { + auto surf = SkSurface::MakeRasterDirect(dst.info(), dst.writable_addr(), dst.rowBytes()); + if (!surf) { + return false; + } + + SkBitmap bm; + bm.installPixels(src); + + SkMatrix m = SkEncodedOriginToMatrix(origin, src.width(), src.height()); + + SkPaint p; + p.setBlendMode(SkBlendMode::kSrc); + surf->getCanvas()->concat(m); + surf->getCanvas()->drawBitmap(bm, 0, 0, &p); + return true; +} + +bool SkPixmapPriv::Orient(const SkPixmap& dst, const SkPixmap& src, SkEncodedOrigin origin) { + if (src.colorType() != dst.colorType()) { + return false; + } + // note: we just ignore alphaType and colorSpace for this transformation + + int w = src.width(); + int h = src.height(); + if (ShouldSwapWidthHeight(origin)) { + using std::swap; + swap(w, h); + } + if (dst.width() != w || dst.height() != h) { + return false; + } + if (w == 0 || h == 0) { + return true; + } + + // check for aliasing to self + if (src.addr() == dst.addr()) { + return kTopLeft_SkEncodedOrigin == origin; + } + return draw_orientation(dst, src, origin); +} + +bool SkPixmapPriv::ShouldSwapWidthHeight(SkEncodedOrigin origin) { + // The last four SkEncodedOrigin values involve 90 degree rotations + return origin >= kLeftTop_SkEncodedOrigin; +} + +SkImageInfo SkPixmapPriv::SwapWidthHeight(const SkImageInfo& info) { + return info.makeWH(info.height(), info.width()); +} + diff --git a/gfx/skia/skia/src/core/SkPixmapPriv.h b/gfx/skia/skia/src/core/SkPixmapPriv.h new file mode 100644 index 0000000000..f3d643bfa4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPixmapPriv.h @@ -0,0 +1,65 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPixmapPriv_DEFINED +#define SkPixmapPriv_DEFINED + +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkPixmap.h" +#include "src/core/SkAutoPixmapStorage.h" + +class SkPixmapPriv { +public: + /** + * Copy the pixels in this pixmap into dst, applying the orientation transformations specified + * by the flags. If the inputs are invalid, this returns false and no copy is made. + */ + static bool Orient(const SkPixmap& dst, const SkPixmap& src, SkEncodedOrigin); + + static bool ShouldSwapWidthHeight(SkEncodedOrigin o); + static SkImageInfo SwapWidthHeight(const SkImageInfo& info); + + /** + * Decode an image and then copy into dst, applying origin. + * + * @param dst SkPixmap to write the final image, after + * applying the origin. + * @param origin SkEncodedOrigin to apply to the raw pixels. + * @param decode Function for decoding into a pixmap without + * applying the origin. + */ + + template + static bool Orient(const SkPixmap& dst, SkEncodedOrigin origin, Fn&& decode) { + SkAutoPixmapStorage storage; + const SkPixmap* tmp = &dst; + if (origin != kTopLeft_SkEncodedOrigin) { + auto info = dst.info(); + if (ShouldSwapWidthHeight(origin)) { + info = SwapWidthHeight(info); + } + if (!storage.tryAlloc(info)) { + return false; + } + tmp = &storage; + } + if (!decode(*tmp)) { + return false; + } + if (tmp != &dst) { + return Orient(dst, *tmp, origin); + } + return true; + } + + static void ResetPixmapKeepInfo(SkPixmap* pm, const void* address, size_t rowBytes) { + pm->fRowBytes = rowBytes; + pm->fPixels = address; + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPoint.cpp b/gfx/skia/skia/src/core/SkPoint.cpp new file mode 100644 index 0000000000..ee902be772 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPoint.cpp @@ -0,0 +1,164 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMathPriv.h" +#include "src/core/SkPointPriv.h" + +/////////////////////////////////////////////////////////////////////////////// + +void SkPoint::scale(SkScalar scale, SkPoint* dst) const { + SkASSERT(dst); + dst->set(fX * scale, fY * scale); +} + +bool SkPoint::normalize() { + return this->setLength(fX, fY, SK_Scalar1); +} + +bool SkPoint::setNormalize(SkScalar x, SkScalar y) { + return this->setLength(x, y, SK_Scalar1); +} + +bool SkPoint::setLength(SkScalar length) { + return this->setLength(fX, fY, length); +} + +/* + * We have to worry about 2 tricky conditions: + * 1. underflow of mag2 (compared against nearlyzero^2) + * 2. overflow of mag2 (compared w/ isfinite) + * + * If we underflow, we return false. If we overflow, we compute again using + * doubles, which is much slower (3x in a desktop test) but will not overflow. + */ +template bool set_point_length(SkPoint* pt, float x, float y, float length, + float* orig_length = nullptr) { + SkASSERT(!use_rsqrt || (orig_length == nullptr)); + + // our mag2 step overflowed to infinity, so use doubles instead. + // much slower, but needed when x or y are very large, other wise we + // divide by inf. and return (0,0) vector. + double xx = x; + double yy = y; + double dmag = sqrt(xx * xx + yy * yy); + double dscale = sk_ieee_double_divide(length, dmag); + x *= dscale; + y *= dscale; + // check if we're not finite, or we're zero-length + if (!sk_float_isfinite(x) || !sk_float_isfinite(y) || (x == 0 && y == 0)) { + pt->set(0, 0); + return false; + } + float mag = 0; + if (orig_length) { + mag = sk_double_to_float(dmag); + } + pt->set(x, y); + if (orig_length) { + *orig_length = mag; + } + return true; +} + +SkScalar SkPoint::Normalize(SkPoint* pt) { + float mag; + if (set_point_length(pt, pt->fX, pt->fY, 1.0f, &mag)) { + return mag; + } + return 0; +} + +SkScalar SkPoint::Length(SkScalar dx, SkScalar dy) { + float mag2 = dx * dx + dy * dy; + if (SkScalarIsFinite(mag2)) { + return sk_float_sqrt(mag2); + } else { + double xx = dx; + double yy = dy; + return sk_double_to_float(sqrt(xx * xx + yy * yy)); + } +} + +bool SkPoint::setLength(float x, float y, float length) { + return set_point_length(this, x, y, length); +} + +bool SkPointPriv::SetLengthFast(SkPoint* pt, float length) { + return set_point_length(pt, pt->fX, pt->fY, length); +} + + +/////////////////////////////////////////////////////////////////////////////// + +SkScalar SkPointPriv::DistanceToLineBetweenSqd(const SkPoint& pt, const SkPoint& a, + const SkPoint& b, + Side* side) { + + SkVector u = b - a; + SkVector v = pt - a; + + SkScalar uLengthSqd = LengthSqd(u); + SkScalar det = u.cross(v); + if (side) { + SkASSERT(-1 == kLeft_Side && + 0 == kOn_Side && + 1 == kRight_Side); + *side = (Side) SkScalarSignAsInt(det); + } + SkScalar temp = sk_ieee_float_divide(det, uLengthSqd); + temp *= det; + // It's possible we have a degenerate line vector, or we're so far away it looks degenerate + // In this case, return squared distance to point A. + if (!SkScalarIsFinite(temp)) { + return LengthSqd(v); + } + return temp; +} + +SkScalar SkPointPriv::DistanceToLineSegmentBetweenSqd(const SkPoint& pt, const SkPoint& a, + const SkPoint& b) { + // See comments to distanceToLineBetweenSqd. If the projection of c onto + // u is between a and b then this returns the same result as that + // function. Otherwise, it returns the distance to the closer of a and + // b. Let the projection of v onto u be v'. There are three cases: + // 1. v' points opposite to u. c is not between a and b and is closer + // to a than b. + // 2. v' points along u and has magnitude less than y. c is between + // a and b and the distance to the segment is the same as distance + // to the line ab. + // 3. v' points along u and has greater magnitude than u. c is not + // not between a and b and is closer to b than a. + // v' = (u dot v) * u / |u|. So if (u dot v)/|u| is less than zero we're + // in case 1. If (u dot v)/|u| is > |u| we are in case 3. Otherwise + // we're in case 2. We actually compare (u dot v) to 0 and |u|^2 to + // avoid a sqrt to compute |u|. + + SkVector u = b - a; + SkVector v = pt - a; + + SkScalar uLengthSqd = LengthSqd(u); + SkScalar uDotV = SkPoint::DotProduct(u, v); + + // closest point is point A + if (uDotV <= 0) { + return LengthSqd(v); + // closest point is point B + } else if (uDotV > uLengthSqd) { + return DistanceToSqd(b, pt); + // closest point is inside segment + } else { + SkScalar det = u.cross(v); + SkScalar temp = sk_ieee_float_divide(det, uLengthSqd); + temp *= det; + // It's possible we have a degenerate segment, or we're so far away it looks degenerate + // In this case, return squared distance to point A. + if (!SkScalarIsFinite(temp)) { + return LengthSqd(v); + } + return temp; + } +} diff --git a/gfx/skia/skia/src/core/SkPoint3.cpp b/gfx/skia/skia/src/core/SkPoint3.cpp new file mode 100644 index 0000000000..901e90ee6f --- /dev/null +++ b/gfx/skia/skia/src/core/SkPoint3.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPoint3.h" + +// Returns the square of the Euclidian distance to (x,y,z). +static inline float get_length_squared(float x, float y, float z) { + return x * x + y * y + z * z; +} + +// Calculates the square of the Euclidian distance to (x,y,z) and stores it in +// *lengthSquared. Returns true if the distance is judged to be "nearly zero". +// +// This logic is encapsulated in a helper method to make it explicit that we +// always perform this check in the same manner, to avoid inconsistencies +// (see http://code.google.com/p/skia/issues/detail?id=560 ). +static inline bool is_length_nearly_zero(float x, float y, float z, float *lengthSquared) { + *lengthSquared = get_length_squared(x, y, z); + return *lengthSquared <= (SK_ScalarNearlyZero * SK_ScalarNearlyZero); +} + +SkScalar SkPoint3::Length(SkScalar x, SkScalar y, SkScalar z) { + float magSq = get_length_squared(x, y, z); + if (SkScalarIsFinite(magSq)) { + return sk_float_sqrt(magSq); + } else { + double xx = x; + double yy = y; + double zz = z; + return (float)sqrt(xx * xx + yy * yy + zz * zz); + } +} + +/* + * We have to worry about 2 tricky conditions: + * 1. underflow of magSq (compared against nearlyzero^2) + * 2. overflow of magSq (compared w/ isfinite) + * + * If we underflow, we return false. If we overflow, we compute again using + * doubles, which is much slower (3x in a desktop test) but will not overflow. + */ +bool SkPoint3::normalize() { + float magSq; + if (is_length_nearly_zero(fX, fY, fZ, &magSq)) { + this->set(0, 0, 0); + return false; + } + // sqrtf does not provide enough precision; since sqrt takes a double, + // there's no additional penalty to storing invScale in a double + double invScale; + if (sk_float_isfinite(magSq)) { + invScale = magSq; + } else { + // our magSq step overflowed to infinity, so use doubles instead. + // much slower, but needed when x, y or z is very large, otherwise we + // divide by inf. and return (0,0,0) vector. + double xx = fX; + double yy = fY; + double zz = fZ; + invScale = xx * xx + yy * yy + zz * zz; + } + // using a float instead of a double for scale loses too much precision + double scale = 1 / sqrt(invScale); + fX *= scale; + fY *= scale; + fZ *= scale; + if (!sk_float_isfinite(fX) || !sk_float_isfinite(fY) || !sk_float_isfinite(fZ)) { + this->set(0, 0, 0); + return false; + } + return true; +} diff --git a/gfx/skia/skia/src/core/SkPointPriv.h b/gfx/skia/skia/src/core/SkPointPriv.h new file mode 100644 index 0000000000..c8a6d520e0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPointPriv.h @@ -0,0 +1,127 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPointPriv_DEFINED +#define SkPointPriv_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" + +class SkPointPriv { +public: + enum Side { + kLeft_Side = -1, + kOn_Side = 0, + kRight_Side = 1, + }; + + static bool AreFinite(const SkPoint array[], int count) { + return SkScalarsAreFinite(&array[0].fX, count << 1); + } + + static const SkScalar* AsScalars(const SkPoint& pt) { return &pt.fX; } + + static bool CanNormalize(SkScalar dx, SkScalar dy) { + return SkScalarsAreFinite(dx, dy) && (dx || dy); + } + + static SkScalar DistanceToLineBetweenSqd(const SkPoint& pt, const SkPoint& a, + const SkPoint& b, Side* side = nullptr); + + static SkScalar DistanceToLineBetween(const SkPoint& pt, const SkPoint& a, + const SkPoint& b, Side* side = nullptr) { + return SkScalarSqrt(DistanceToLineBetweenSqd(pt, a, b, side)); + } + + static SkScalar DistanceToLineSegmentBetweenSqd(const SkPoint& pt, const SkPoint& a, + const SkPoint& b); + + static SkScalar DistanceToLineSegmentBetween(const SkPoint& pt, const SkPoint& a, + const SkPoint& b) { + return SkScalarSqrt(DistanceToLineSegmentBetweenSqd(pt, a, b)); + } + + static SkScalar DistanceToSqd(const SkPoint& pt, const SkPoint& a) { + SkScalar dx = pt.fX - a.fX; + SkScalar dy = pt.fY - a.fY; + return dx * dx + dy * dy; + } + + static bool EqualsWithinTolerance(const SkPoint& p1, const SkPoint& p2) { + return !CanNormalize(p1.fX - p2.fX, p1.fY - p2.fY); + } + + static bool EqualsWithinTolerance(const SkPoint& pt, const SkPoint& p, SkScalar tol) { + return SkScalarNearlyZero(pt.fX - p.fX, tol) + && SkScalarNearlyZero(pt.fY - p.fY, tol); + } + + static SkScalar LengthSqd(const SkPoint& pt) { + return SkPoint::DotProduct(pt, pt); + } + + static void Negate(SkIPoint& pt) { + pt.fX = -pt.fX; + pt.fY = -pt.fY; + } + + static void RotateCCW(const SkPoint& src, SkPoint* dst) { + // use a tmp in case src == dst + SkScalar tmp = src.fX; + dst->fX = src.fY; + dst->fY = -tmp; + } + + static void RotateCCW(SkPoint* pt) { + RotateCCW(*pt, pt); + } + + static void RotateCW(const SkPoint& src, SkPoint* dst) { + // use a tmp in case src == dst + SkScalar tmp = src.fX; + dst->fX = -src.fY; + dst->fY = tmp; + } + + static void RotateCW(SkPoint* pt) { + RotateCW(*pt, pt); + } + + static bool SetLengthFast(SkPoint* pt, float length); + + static SkPoint MakeOrthog(const SkPoint& vec, Side side = kLeft_Side) { + SkASSERT(side == kRight_Side || side == kLeft_Side); + return (side == kRight_Side) ? SkPoint{-vec.fY, vec.fX} : SkPoint{vec.fY, -vec.fX}; + } + + // counter-clockwise fan + static void SetRectFan(SkPoint v[], SkScalar l, SkScalar t, SkScalar r, SkScalar b, + size_t stride) { + SkASSERT(stride >= sizeof(SkPoint)); + + ((SkPoint*)((intptr_t)v + 0 * stride))->set(l, t); + ((SkPoint*)((intptr_t)v + 1 * stride))->set(l, b); + ((SkPoint*)((intptr_t)v + 2 * stride))->set(r, b); + ((SkPoint*)((intptr_t)v + 3 * stride))->set(r, t); + } + + // tri strip with two counter-clockwise triangles + static void SetRectTriStrip(SkPoint v[], SkScalar l, SkScalar t, SkScalar r, SkScalar b, + size_t stride) { + SkASSERT(stride >= sizeof(SkPoint)); + + ((SkPoint*)((intptr_t)v + 0 * stride))->set(l, t); + ((SkPoint*)((intptr_t)v + 1 * stride))->set(l, b); + ((SkPoint*)((intptr_t)v + 2 * stride))->set(r, t); + ((SkPoint*)((intptr_t)v + 3 * stride))->set(r, b); + } + static void SetRectTriStrip(SkPoint v[], const SkRect& rect, size_t stride) { + SetRectTriStrip(v, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, stride); + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkPromiseImageTexture.cpp b/gfx/skia/skia/src/core/SkPromiseImageTexture.cpp new file mode 100644 index 0000000000..d8d8dfe4e2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPromiseImageTexture.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPromiseImageTexture.h" +#include "src/core/SkMessageBus.h" + +#if SK_SUPPORT_GPU + +std::atomic SkPromiseImageTexture::gUniqueID{1}; + +SkPromiseImageTexture::SkPromiseImageTexture(const GrBackendTexture& backendTexture) { + SkASSERT(backendTexture.isValid()); + fBackendTexture = backendTexture; + fUniqueID = gUniqueID++; +} + +SkPromiseImageTexture::~SkPromiseImageTexture() { + for (const auto& msg : fMessages) { + SkMessageBus::Post(msg); + } +} + +void SkPromiseImageTexture::addKeyToInvalidate(uint32_t contextID, const GrUniqueKey& key) { + SkASSERT(contextID != SK_InvalidUniqueID); + SkASSERT(key.isValid()); + for (const auto& msg : fMessages) { + if (msg.contextID() == contextID && msg.key() == key) { + return; + } + } + fMessages.emplace_back(key, contextID); +} + +#if GR_TEST_UTILS +SkTArray SkPromiseImageTexture::testingOnly_uniqueKeysToInvalidate() const { + SkTArray results; + for (const auto& msg : fMessages) { + results.push_back(msg.key()); + } + return results; +} +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkPtrRecorder.cpp b/gfx/skia/skia/src/core/SkPtrRecorder.cpp new file mode 100644 index 0000000000..7c34879fc3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkPtrRecorder.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkPtrRecorder.h" +#include "src/core/SkTSearch.h" + +void SkPtrSet::reset() { + Pair* p = fList.begin(); + Pair* stop = fList.end(); + while (p < stop) { + this->decPtr(p->fPtr); + p += 1; + } + fList.reset(); +} + +bool SkPtrSet::Less(const Pair& a, const Pair& b) { + return (char*)a.fPtr < (char*)b.fPtr; +} + +uint32_t SkPtrSet::find(void* ptr) const { + if (nullptr == ptr) { + return 0; + } + + int count = fList.count(); + Pair pair; + pair.fPtr = ptr; + + int index = SkTSearch(fList.begin(), count, pair, sizeof(pair)); + if (index < 0) { + return 0; + } + return fList[index].fIndex; +} + +uint32_t SkPtrSet::add(void* ptr) { + if (nullptr == ptr) { + return 0; + } + + int count = fList.count(); + Pair pair; + pair.fPtr = ptr; + + int index = SkTSearch(fList.begin(), count, pair, sizeof(pair)); + if (index < 0) { + index = ~index; // turn it back into an index for insertion + this->incPtr(ptr); + pair.fIndex = count + 1; + *fList.insert(index) = pair; + return count + 1; + } else { + return fList[index].fIndex; + } +} + +void SkPtrSet::copyToArray(void* array[]) const { + int count = fList.count(); + if (count > 0) { + SkASSERT(array); + const Pair* p = fList.begin(); + // p->fIndex is base-1, so we need to subtract to find its slot + for (int i = 0; i < count; i++) { + int index = p[i].fIndex - 1; + SkASSERT((unsigned)index < (unsigned)count); + array[index] = p[i].fPtr; + } + } +} diff --git a/gfx/skia/skia/src/core/SkPtrRecorder.h b/gfx/skia/skia/src/core/SkPtrRecorder.h new file mode 100644 index 0000000000..2ea550f6ad --- /dev/null +++ b/gfx/skia/skia/src/core/SkPtrRecorder.h @@ -0,0 +1,171 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkPtrSet_DEFINED +#define SkPtrSet_DEFINED + +#include "include/core/SkFlattenable.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkTDArray.h" + +/** + * Maintains a set of ptrs, assigning each a unique ID [1...N]. Duplicate ptrs + * return the same ID (since its a set). Subclasses can override inPtr() + * and decPtr(). incPtr() is called each time a unique ptr is added ot the + * set. decPtr() is called on each ptr when the set is destroyed or reset. + */ +class SkPtrSet : public SkRefCnt { +public: + + + /** + * Search for the specified ptr in the set. If it is found, return its + * 32bit ID [1..N], or if not found, return 0. Always returns 0 for nullptr. + */ + uint32_t find(void*) const; + + /** + * Add the specified ptr to the set, returning a unique 32bit ID for it + * [1...N]. Duplicate ptrs will return the same ID. + * + * If the ptr is nullptr, it is not added, and 0 is returned. + */ + uint32_t add(void*); + + /** + * Return the number of (non-null) ptrs in the set. + */ + int count() const { return fList.count(); } + + /** + * Copy the ptrs in the set into the specified array (allocated by the + * caller). The ptrs are assgined to the array based on their corresponding + * ID. e.g. array[ptr.ID - 1] = ptr. + * + * incPtr() and decPtr() are not called during this operation. + */ + void copyToArray(void* array[]) const; + + /** + * Call decPtr() on each ptr in the set, and the reset the size of the set + * to 0. + */ + void reset(); + + /** + * Set iterator. + */ + class Iter { + public: + Iter(const SkPtrSet& set) + : fSet(set) + , fIndex(0) {} + + /** + * Return the next ptr in the set or null if the end was reached. + */ + void* next() { + return fIndex < fSet.fList.count() ? fSet.fList[fIndex++].fPtr : nullptr; + } + + private: + const SkPtrSet& fSet; + int fIndex; + }; + +protected: + virtual void incPtr(void*) {} + virtual void decPtr(void*) {} + +private: + struct Pair { + void* fPtr; // never nullptr + uint32_t fIndex; // 1...N + }; + + // we store the ptrs in sorted-order (using Cmp) so that we can efficiently + // detect duplicates when add() is called. Hence we need to store the + // ptr and its ID/fIndex explicitly, since the ptr's position in the array + // is not related to its "index". + SkTDArray fList; + + static bool Less(const Pair& a, const Pair& b); + + typedef SkRefCnt INHERITED; +}; + +/** + * Templated wrapper for SkPtrSet, just meant to automate typecasting + * parameters to and from void* (which the base class expects). + */ +template class SkTPtrSet : public SkPtrSet { +public: + uint32_t find(T ptr) { + return this->INHERITED::find((void*)ptr); + } + uint32_t add(T ptr) { + return this->INHERITED::add((void*)ptr); + } + + void copyToArray(T* array) const { + this->INHERITED::copyToArray((void**)array); + } + +private: + typedef SkPtrSet INHERITED; +}; + +/** + * Subclass of SkTPtrSet specialed to call ref() and unref() when the + * base class's incPtr() and decPtr() are called. This makes it a valid owner + * of each ptr, which is released when the set is reset or destroyed. + */ +class SkRefCntSet : public SkTPtrSet { +public: + virtual ~SkRefCntSet(); + +protected: + // overrides + virtual void incPtr(void*); + virtual void decPtr(void*); +}; + +class SkFactorySet : public SkTPtrSet {}; + +/** + * Similar to SkFactorySet, but only allows Factorys that have registered names. + * Also has a function to return the next added Factory's name. + */ +class SkNamedFactorySet : public SkRefCnt { +public: + + + SkNamedFactorySet(); + + /** + * Find the specified Factory in the set. If it is not already in the set, + * and has registered its name, add it to the set, and return its index. + * If the Factory has no registered name, return 0. + */ + uint32_t find(SkFlattenable::Factory); + + /** + * If new Factorys have been added to the set, return the name of the first + * Factory added after the Factory name returned by the last call to this + * function. + */ + const char* getNextAddedFactoryName(); +private: + int fNextAddedFactory; + SkFactorySet fFactorySet; + SkTDArray fNames; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkQuadClipper.cpp b/gfx/skia/skia/src/core/SkQuadClipper.cpp new file mode 100644 index 0000000000..d265635b48 --- /dev/null +++ b/gfx/skia/skia/src/core/SkQuadClipper.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkGeometry.h" +#include "src/core/SkQuadClipper.h" + +#include + +SkQuadClipper::SkQuadClipper() { + fClip.setEmpty(); +} + +void SkQuadClipper::setClip(const SkIRect& clip) { + // conver to scalars, since that's where we'll see the points + fClip.set(clip); +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2, + SkScalar target, SkScalar* t) { + /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2 + * We solve for t, using quadratic equation, hence we have to rearrange + * our cooefficents to look like At^2 + Bt + C + */ + SkScalar A = c0 - c1 - c1 + c2; + SkScalar B = 2*(c1 - c0); + SkScalar C = c0 - target; + + SkScalar roots[2]; // we only expect one, but make room for 2 for safety + int count = SkFindUnitQuadRoots(A, B, C, roots); + if (count) { + *t = roots[0]; + return true; + } + return false; +} + +static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) { + return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t); +} + +/////////////////////////////////////////////////////////////////////////////// + +/* If we somehow returned the fact that we had to flip the pts in Y, we could + communicate that to setQuadratic, and then avoid having to flip it back + here (only to have setQuadratic do the flip again) + */ +bool SkQuadClipper::clipQuad(const SkPoint srcPts[3], SkPoint dst[3]) { + bool reverse; + + // we need the data to be monotonically increasing in Y + if (srcPts[0].fY > srcPts[2].fY) { + dst[0] = srcPts[2]; + dst[1] = srcPts[1]; + dst[2] = srcPts[0]; + reverse = true; + } else { + memcpy(dst, srcPts, 3 * sizeof(SkPoint)); + reverse = false; + } + + // are we completely above or below + const SkScalar ctop = fClip.fTop; + const SkScalar cbot = fClip.fBottom; + if (dst[2].fY <= ctop || dst[0].fY >= cbot) { + return false; + } + + SkScalar t; + SkPoint tmp[5]; // for SkChopQuadAt + + // are we partially above + if (dst[0].fY < ctop) { + if (chopMonoQuadAtY(dst, ctop, &t)) { + // take the 2nd chopped quad + SkChopQuadAt(dst, tmp, t); + dst[0] = tmp[2]; + dst[1] = tmp[3]; + } else { + // if chopMonoQuadAtY failed, then we may have hit inexact numerics + // so we just clamp against the top + for (int i = 0; i < 3; i++) { + if (dst[i].fY < ctop) { + dst[i].fY = ctop; + } + } + } + } + + // are we partially below + if (dst[2].fY > cbot) { + if (chopMonoQuadAtY(dst, cbot, &t)) { + SkChopQuadAt(dst, tmp, t); + dst[1] = tmp[1]; + dst[2] = tmp[2]; + } else { + // if chopMonoQuadAtY failed, then we may have hit inexact numerics + // so we just clamp against the bottom + for (int i = 0; i < 3; i++) { + if (dst[i].fY > cbot) { + dst[i].fY = cbot; + } + } + } + } + + if (reverse) { + using std::swap; + swap(dst[0], dst[2]); + } + return true; +} diff --git a/gfx/skia/skia/src/core/SkQuadClipper.h b/gfx/skia/skia/src/core/SkQuadClipper.h new file mode 100644 index 0000000000..c3f5d63c8a --- /dev/null +++ b/gfx/skia/skia/src/core/SkQuadClipper.h @@ -0,0 +1,69 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkQuadClipper_DEFINED +#define SkQuadClipper_DEFINED + +#include "include/core/SkPath.h" + +/** This class is initialized with a clip rectangle, and then can be fed quads, + which must already be monotonic in Y. + + In the future, it might return a series of segments, allowing it to clip + also in X, to ensure that all segments fit in a finite coordinate system. + */ +class SkQuadClipper { +public: + SkQuadClipper(); + + void setClip(const SkIRect& clip); + + bool clipQuad(const SkPoint src[3], SkPoint dst[3]); + +private: + SkRect fClip; +}; + +/** Iterator that returns the clipped segements of a quad clipped to a rect. + The segments will be either lines or quads (based on SkPath::Verb), and + will all be monotonic in Y + */ +class SkQuadClipper2 { +public: + bool clipQuad(const SkPoint pts[3], const SkRect& clip); + bool clipCubic(const SkPoint pts[4], const SkRect& clip); + + SkPath::Verb next(SkPoint pts[]); + +private: + SkPoint* fCurrPoint; + SkPath::Verb* fCurrVerb; + + enum { + kMaxVerbs = 13, + kMaxPoints = 32 + }; + SkPoint fPoints[kMaxPoints]; + SkPath::Verb fVerbs[kMaxVerbs]; + + void clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip); + void clipMonoCubic(const SkPoint srcPts[4], const SkRect& clip); + void appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse); + void appendQuad(const SkPoint pts[3], bool reverse); + void appendCubic(const SkPoint pts[4], bool reverse); +}; + +#ifdef SK_DEBUG + void sk_assert_monotonic_x(const SkPoint pts[], int count); + void sk_assert_monotonic_y(const SkPoint pts[], int count); +#else + #define sk_assert_monotonic_x(pts, count) + #define sk_assert_monotonic_y(pts, count) +#endif + +#endif diff --git a/gfx/skia/skia/src/core/SkRRect.cpp b/gfx/skia/skia/src/core/SkRRect.cpp new file mode 100644 index 0000000000..df5b2d4908 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRRect.cpp @@ -0,0 +1,688 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "include/private/SkMalloc.h" +#include "src/core/SkBuffer.h" +#include "src/core/SkRRectPriv.h" +#include "src/core/SkScaleToSides.h" + +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { + if (!this->initializeRect(rect)) { + return; + } + + if (!SkScalarsAreFinite(xRad, yRad)) { + xRad = yRad = 0; // devolve into a simple rect + } + + if (fRect.width() < xRad+xRad || fRect.height() < yRad+yRad) { + // At most one of these two divides will be by zero, and neither numerator is zero. + SkScalar scale = SkMinScalar(sk_ieee_float_divide(fRect. width(), xRad + xRad), + sk_ieee_float_divide(fRect.height(), yRad + yRad)); + SkASSERT(scale < SK_Scalar1); + xRad *= scale; + yRad *= scale; + } + + if (xRad <= 0 || yRad <= 0) { + // all corners are square in this case + this->setRect(rect); + return; + } + + for (int i = 0; i < 4; ++i) { + fRadii[i].set(xRad, yRad); + } + fType = kSimple_Type; + if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) { + fType = kOval_Type; + // TODO: assert that all the x&y radii are already W/2 & H/2 + } + + SkASSERT(this->isValid()); +} + +void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, + SkScalar rightRad, SkScalar bottomRad) { + if (!this->initializeRect(rect)) { + return; + } + + const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad }; + if (!SkScalarsAreFinite(array, 4)) { + this->setRect(rect); // devolve into a simple rect + return; + } + + leftRad = SkMaxScalar(leftRad, 0); + topRad = SkMaxScalar(topRad, 0); + rightRad = SkMaxScalar(rightRad, 0); + bottomRad = SkMaxScalar(bottomRad, 0); + + SkScalar scale = SK_Scalar1; + if (leftRad + rightRad > fRect.width()) { + scale = fRect.width() / (leftRad + rightRad); + } + if (topRad + bottomRad > fRect.height()) { + scale = SkMinScalar(scale, fRect.height() / (topRad + bottomRad)); + } + + if (scale < SK_Scalar1) { + leftRad *= scale; + topRad *= scale; + rightRad *= scale; + bottomRad *= scale; + } + + if (leftRad == rightRad && topRad == bottomRad) { + if (leftRad >= SkScalarHalf(fRect.width()) && topRad >= SkScalarHalf(fRect.height())) { + fType = kOval_Type; + } else if (0 == leftRad || 0 == topRad) { + // If the left and (by equality check above) right radii are zero then it is a rect. + // Same goes for top/bottom. + fType = kRect_Type; + leftRad = 0; + topRad = 0; + rightRad = 0; + bottomRad = 0; + } else { + fType = kSimple_Type; + } + } else { + fType = kNinePatch_Type; + } + + fRadii[kUpperLeft_Corner].set(leftRad, topRad); + fRadii[kUpperRight_Corner].set(rightRad, topRad); + fRadii[kLowerRight_Corner].set(rightRad, bottomRad); + fRadii[kLowerLeft_Corner].set(leftRad, bottomRad); + + SkASSERT(this->isValid()); +} + +// These parameters intentionally double. Apropos crbug.com/463920, if one of the +// radii is huge while the other is small, single precision math can completely +// miss the fact that a scale is required. +static double compute_min_scale(double rad1, double rad2, double limit, double curMin) { + if ((rad1 + rad2) > limit) { + return SkTMin(curMin, limit / (rad1 + rad2)); + } + return curMin; +} + +static bool clamp_to_zero(SkVector radii[4]) { + bool allCornersSquare = true; + + // Clamp negative radii to zero + for (int i = 0; i < 4; ++i) { + if (radii[i].fX <= 0 || radii[i].fY <= 0) { + // In this case we are being a little fast & loose. Since one of + // the radii is 0 the corner is square. However, the other radii + // could still be non-zero and play in the global scale factor + // computation. + radii[i].fX = 0; + radii[i].fY = 0; + } else { + allCornersSquare = false; + } + } + + return allCornersSquare; +} + +void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) { + if (!this->initializeRect(rect)) { + return; + } + + if (!SkScalarsAreFinite(&radii[0].fX, 8)) { + this->setRect(rect); // devolve into a simple rect + return; + } + + memcpy(fRadii, radii, sizeof(fRadii)); + + if (clamp_to_zero(fRadii)) { + this->setRect(rect); + return; + } + + this->scaleRadii(rect); +} + +bool SkRRect::initializeRect(const SkRect& rect) { + // Check this before sorting because sorting can hide nans. + if (!rect.isFinite()) { + *this = SkRRect(); + return false; + } + fRect = rect.makeSorted(); + if (fRect.isEmpty()) { + memset(fRadii, 0, sizeof(fRadii)); + fType = kEmpty_Type; + return false; + } + return true; +} + +// If we can't distinguish one of the radii relative to the other, force it to zero so it +// doesn't confuse us later. See crbug.com/850350 +// +static void flush_to_zero(SkScalar& a, SkScalar& b) { + SkASSERT(a >= 0); + SkASSERT(b >= 0); + if (a + b == a) { + b = 0; + } else if (a + b == b) { + a = 0; + } +} + +void SkRRect::scaleRadii(const SkRect& rect) { + // Proportionally scale down all radii to fit. Find the minimum ratio + // of a side and the radii on that side (for all four sides) and use + // that to scale down _all_ the radii. This algorithm is from the + // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping + // Curves: + // "Let f = min(Li/Si), where i is one of { top, right, bottom, left }, + // Si is the sum of the two corresponding radii of the corners on side i, + // and Ltop = Lbottom = the width of the box, + // and Lleft = Lright = the height of the box. + // If f < 1, then all corner radii are reduced by multiplying them by f." + double scale = 1.0; + + // The sides of the rectangle may be larger than a float. + double width = (double)fRect.fRight - (double)fRect.fLeft; + double height = (double)fRect.fBottom - (double)fRect.fTop; + scale = compute_min_scale(fRadii[0].fX, fRadii[1].fX, width, scale); + scale = compute_min_scale(fRadii[1].fY, fRadii[2].fY, height, scale); + scale = compute_min_scale(fRadii[2].fX, fRadii[3].fX, width, scale); + scale = compute_min_scale(fRadii[3].fY, fRadii[0].fY, height, scale); + + flush_to_zero(fRadii[0].fX, fRadii[1].fX); + flush_to_zero(fRadii[1].fY, fRadii[2].fY); + flush_to_zero(fRadii[2].fX, fRadii[3].fX); + flush_to_zero(fRadii[3].fY, fRadii[0].fY); + + if (scale < 1.0) { + SkScaleToSides::AdjustRadii(width, scale, &fRadii[0].fX, &fRadii[1].fX); + SkScaleToSides::AdjustRadii(height, scale, &fRadii[1].fY, &fRadii[2].fY); + SkScaleToSides::AdjustRadii(width, scale, &fRadii[2].fX, &fRadii[3].fX); + SkScaleToSides::AdjustRadii(height, scale, &fRadii[3].fY, &fRadii[0].fY); + } + + // adjust radii may set x or y to zero; set companion to zero as well + if (clamp_to_zero(fRadii)) { + this->setRect(rect); + return; + } + + // At this point we're either oval, simple, or complex (not empty or rect). + this->computeType(); + + SkASSERT(this->isValid()); +} + +// This method determines if a point known to be inside the RRect's bounds is +// inside all the corners. +bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const { + SkPoint canonicalPt; // (x,y) translated to one of the quadrants + int index; + + if (kOval_Type == this->type()) { + canonicalPt.set(x - fRect.centerX(), y - fRect.centerY()); + index = kUpperLeft_Corner; // any corner will do in this case + } else { + if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX && + y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) { + // UL corner + index = kUpperLeft_Corner; + canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX), + y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY)); + SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0); + } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX && + y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) { + // LL corner + index = kLowerLeft_Corner; + canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX), + y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY)); + SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0); + } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX && + y < fRect.fTop + fRadii[kUpperRight_Corner].fY) { + // UR corner + index = kUpperRight_Corner; + canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX), + y - (fRect.fTop + fRadii[kUpperRight_Corner].fY)); + SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0); + } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX && + y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) { + // LR corner + index = kLowerRight_Corner; + canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX), + y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY)); + SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0); + } else { + // not in any of the corners + return true; + } + } + + // A point is in an ellipse (in standard position) if: + // x^2 y^2 + // ----- + ----- <= 1 + // a^2 b^2 + // or : + // b^2*x^2 + a^2*y^2 <= (ab)^2 + SkScalar dist = SkScalarSquare(canonicalPt.fX) * SkScalarSquare(fRadii[index].fY) + + SkScalarSquare(canonicalPt.fY) * SkScalarSquare(fRadii[index].fX); + return dist <= SkScalarSquare(fRadii[index].fX * fRadii[index].fY); +} + +bool SkRRectPriv::AllCornersCircular(const SkRRect& rr, SkScalar tolerance) { + return SkScalarNearlyEqual(rr.fRadii[0].fX, rr.fRadii[0].fY, tolerance) && + SkScalarNearlyEqual(rr.fRadii[1].fX, rr.fRadii[1].fY, tolerance) && + SkScalarNearlyEqual(rr.fRadii[2].fX, rr.fRadii[2].fY, tolerance) && + SkScalarNearlyEqual(rr.fRadii[3].fX, rr.fRadii[3].fY, tolerance); +} + +bool SkRRect::contains(const SkRect& rect) const { + if (!this->getBounds().contains(rect)) { + // If 'rect' isn't contained by the RR's bounds then the + // RR definitely doesn't contain it + return false; + } + + if (this->isRect()) { + // the prior test was sufficient + return true; + } + + // At this point we know all four corners of 'rect' are inside the + // bounds of of this RR. Check to make sure all the corners are inside + // all the curves + return this->checkCornerContainment(rect.fLeft, rect.fTop) && + this->checkCornerContainment(rect.fRight, rect.fTop) && + this->checkCornerContainment(rect.fRight, rect.fBottom) && + this->checkCornerContainment(rect.fLeft, rect.fBottom); +} + +static bool radii_are_nine_patch(const SkVector radii[4]) { + return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX && + radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY && + radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX && + radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY; +} + +// There is a simplified version of this method in setRectXY +void SkRRect::computeType() { + if (fRect.isEmpty()) { + SkASSERT(fRect.isSorted()); + for (size_t i = 0; i < SK_ARRAY_COUNT(fRadii); ++i) { + SkASSERT((fRadii[i] == SkVector{0, 0})); + } + fType = kEmpty_Type; + SkASSERT(this->isValid()); + return; + } + + bool allRadiiEqual = true; // are all x radii equal and all y radii? + bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY; + + for (int i = 1; i < 4; ++i) { + if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { + // if either radius is zero the corner is square so both have to + // be non-zero to have a rounded corner + allCornersSquare = false; + } + if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { + allRadiiEqual = false; + } + } + + if (allCornersSquare) { + fType = kRect_Type; + SkASSERT(this->isValid()); + return; + } + + if (allRadiiEqual) { + if (fRadii[0].fX >= SkScalarHalf(fRect.width()) && + fRadii[0].fY >= SkScalarHalf(fRect.height())) { + fType = kOval_Type; + } else { + fType = kSimple_Type; + } + SkASSERT(this->isValid()); + return; + } + + if (radii_are_nine_patch(fRadii)) { + fType = kNinePatch_Type; + } else { + fType = kComplex_Type; + } + SkASSERT(this->isValid()); +} + +bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const { + if (nullptr == dst) { + return false; + } + + // Assert that the caller is not trying to do this in place, which + // would violate const-ness. Do not return false though, so that + // if they know what they're doing and want to violate it they can. + SkASSERT(dst != this); + + if (matrix.isIdentity()) { + *dst = *this; + return true; + } + + if (!matrix.preservesAxisAlignment()) { + return false; + } + + SkRect newRect; + if (!matrix.mapRect(&newRect, fRect)) { + return false; + } + + // The matrix may have scaled us to zero (or due to float madness, we now have collapsed + // some dimension of the rect, so we need to check for that. Note that matrix must be + // scale and translate and mapRect() produces a sorted rect. So an empty rect indicates + // loss of precision. + if (!newRect.isFinite() || newRect.isEmpty()) { + return false; + } + + // At this point, this is guaranteed to succeed, so we can modify dst. + dst->fRect = newRect; + + // Since the only transforms that were allowed are axis aligned, the type + // remains unchanged. + dst->fType = fType; + + if (kRect_Type == fType) { + SkASSERT(dst->isValid()); + return true; + } + if (kOval_Type == fType) { + for (int i = 0; i < 4; ++i) { + dst->fRadii[i].fX = SkScalarHalf(newRect.width()); + dst->fRadii[i].fY = SkScalarHalf(newRect.height()); + } + SkASSERT(dst->isValid()); + return true; + } + + // Now scale each corner + SkScalar xScale = matrix.getScaleX(); + SkScalar yScale = matrix.getScaleY(); + + // There is a rotation of 90 (Clockwise 90) or 270 (Counter clockwise 90). + // 180 degrees rotations are simply flipX with a flipY and would come under + // a scale transform. + if (!matrix.isScaleTranslate()) { + const bool isClockwise = matrix.getSkewX() < 0; + + // The matrix location for scale changes if there is a rotation. + xScale = matrix.getSkewY() * (isClockwise ? 1 : -1); + yScale = matrix.getSkewX() * (isClockwise ? -1 : 1); + + const int dir = isClockwise ? 3 : 1; + for (int i = 0; i < 4; ++i) { + const int src = (i + dir) >= 4 ? (i + dir) % 4 : (i + dir); + // Swap X and Y axis for the radii. + dst->fRadii[i].fX = fRadii[src].fY; + dst->fRadii[i].fY = fRadii[src].fX; + } + } else { + for (int i = 0; i < 4; ++i) { + dst->fRadii[i].fX = fRadii[i].fX; + dst->fRadii[i].fY = fRadii[i].fY; + } + } + + const bool flipX = xScale < 0; + if (flipX) { + xScale = -xScale; + } + + const bool flipY = yScale < 0; + if (flipY) { + yScale = -yScale; + } + + // Scale the radii without respecting the flip. + for (int i = 0; i < 4; ++i) { + dst->fRadii[i].fX *= xScale; + dst->fRadii[i].fY *= yScale; + } + + // Now swap as necessary. + using std::swap; + if (flipX) { + if (flipY) { + // Swap with opposite corners + swap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]); + swap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]); + } else { + // Only swap in x + swap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]); + swap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]); + } + } else if (flipY) { + // Only swap in y + swap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]); + swap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]); + } + + if (!AreRectAndRadiiValid(dst->fRect, dst->fRadii)) { + return false; + } + + dst->scaleRadii(dst->fRect); + dst->isValid(); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const { + SkRect r = fRect.makeInset(dx, dy); + bool degenerate = false; + if (r.fRight <= r.fLeft) { + degenerate = true; + r.fLeft = r.fRight = SkScalarAve(r.fLeft, r.fRight); + } + if (r.fBottom <= r.fTop) { + degenerate = true; + r.fTop = r.fBottom = SkScalarAve(r.fTop, r.fBottom); + } + if (degenerate) { + dst->fRect = r; + memset(dst->fRadii, 0, sizeof(dst->fRadii)); + dst->fType = kEmpty_Type; + return; + } + if (!r.isFinite()) { + *dst = SkRRect(); + return; + } + + SkVector radii[4]; + memcpy(radii, fRadii, sizeof(radii)); + for (int i = 0; i < 4; ++i) { + if (radii[i].fX) { + radii[i].fX -= dx; + } + if (radii[i].fY) { + radii[i].fY -= dy; + } + } + dst->setRectRadii(r, radii); +} + +/////////////////////////////////////////////////////////////////////////////// + +size_t SkRRect::writeToMemory(void* buffer) const { + // Serialize only the rect and corners, but not the derived type tag. + memcpy(buffer, this, kSizeInMemory); + return kSizeInMemory; +} + +void SkRRectPriv::WriteToBuffer(const SkRRect& rr, SkWBuffer* buffer) { + // Serialize only the rect and corners, but not the derived type tag. + buffer->write(&rr, SkRRect::kSizeInMemory); +} + +size_t SkRRect::readFromMemory(const void* buffer, size_t length) { + if (length < kSizeInMemory) { + return 0; + } + + SkRRect raw; + memcpy(&raw, buffer, kSizeInMemory); + this->setRectRadii(raw.fRect, raw.fRadii); + return kSizeInMemory; +} + +bool SkRRectPriv::ReadFromBuffer(SkRBuffer* buffer, SkRRect* rr) { + if (buffer->available() < SkRRect::kSizeInMemory) { + return false; + } + SkRRect storage; + return buffer->read(&storage, SkRRect::kSizeInMemory) && + (rr->readFromMemory(&storage, SkRRect::kSizeInMemory) == SkRRect::kSizeInMemory); +} + +#include "include/core/SkString.h" +#include "src/core/SkStringUtils.h" + +void SkRRect::dump(bool asHex) const { + SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType; + + fRect.dump(asHex); + SkString line("const SkPoint corners[] = {\n"); + for (int i = 0; i < 4; ++i) { + SkString strX, strY; + SkAppendScalar(&strX, fRadii[i].x(), asType); + SkAppendScalar(&strY, fRadii[i].y(), asType); + line.appendf(" { %s, %s },", strX.c_str(), strY.c_str()); + if (asHex) { + line.appendf(" /* %f %f */", fRadii[i].x(), fRadii[i].y()); + } + line.append("\n"); + } + line.append("};"); + SkDebugf("%s\n", line.c_str()); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * We need all combinations of predicates to be true to have a "safe" radius value. + */ +static bool are_radius_check_predicates_valid(SkScalar rad, SkScalar min, SkScalar max) { + return (min <= max) && (rad <= max - min) && (min + rad <= max) && (max - rad >= min) && + rad >= 0; +} + +bool SkRRect::isValid() const { + if (!AreRectAndRadiiValid(fRect, fRadii)) { + return false; + } + + bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY); + bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY); + bool allRadiiSame = true; + + for (int i = 1; i < 4; ++i) { + if (0 != fRadii[i].fX || 0 != fRadii[i].fY) { + allRadiiZero = false; + } + + if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { + allRadiiSame = false; + } + + if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { + allCornersSquare = false; + } + } + bool patchesOfNine = radii_are_nine_patch(fRadii); + + if (fType < 0 || fType > kLastType) { + return false; + } + + switch (fType) { + case kEmpty_Type: + if (!fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) { + return false; + } + break; + case kRect_Type: + if (fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) { + return false; + } + break; + case kOval_Type: + if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) { + return false; + } + + for (int i = 0; i < 4; ++i) { + if (!SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())) || + !SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))) { + return false; + } + } + break; + case kSimple_Type: + if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) { + return false; + } + break; + case kNinePatch_Type: + if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare || + !patchesOfNine) { + return false; + } + break; + case kComplex_Type: + if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare || + patchesOfNine) { + return false; + } + break; + } + + return true; +} + +bool SkRRect::AreRectAndRadiiValid(const SkRect& rect, const SkVector radii[4]) { + if (!rect.isFinite() || !rect.isSorted()) { + return false; + } + for (int i = 0; i < 4; ++i) { + if (!are_radius_check_predicates_valid(radii[i].fX, rect.fLeft, rect.fRight) || + !are_radius_check_predicates_valid(radii[i].fY, rect.fTop, rect.fBottom)) { + return false; + } + } + return true; +} +/////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/skia/skia/src/core/SkRRectPriv.h b/gfx/skia/skia/src/core/SkRRectPriv.h new file mode 100644 index 0000000000..4cf529569b --- /dev/null +++ b/gfx/skia/skia/src/core/SkRRectPriv.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRRectPriv_DEFINED +#define SkRRectPriv_DEFINED + +#include "include/core/SkRRect.h" + +class SkRBuffer; +class SkWBuffer; + +class SkRRectPriv { +public: + static bool IsCircle(const SkRRect& rr) { + return rr.isOval() && SkScalarNearlyEqual(rr.fRadii[0].fX, rr.fRadii[0].fY); + } + + static SkVector GetSimpleRadii(const SkRRect& rr) { + SkASSERT(!rr.isComplex()); + return rr.fRadii[0]; + } + + static bool IsSimpleCircular(const SkRRect& rr) { + return rr.isSimple() && SkScalarNearlyEqual(rr.fRadii[0].fX, rr.fRadii[0].fY); + } + + static bool EqualRadii(const SkRRect& rr) { + return rr.isRect() || SkRRectPriv::IsCircle(rr) || SkRRectPriv::IsSimpleCircular(rr); + } + + static const SkVector* GetRadiiArray(const SkRRect& rr) { return rr.fRadii; } + + static bool AllCornersCircular(const SkRRect& rr, SkScalar tolerance = SK_ScalarNearlyZero); + + static bool ReadFromBuffer(SkRBuffer* buffer, SkRRect* rr); + + static void WriteToBuffer(const SkRRect& rr, SkWBuffer* buffer); +}; + +#endif + diff --git a/gfx/skia/skia/src/core/SkRTree.cpp b/gfx/skia/skia/src/core/SkRTree.cpp new file mode 100644 index 0000000000..5688350bbc --- /dev/null +++ b/gfx/skia/skia/src/core/SkRTree.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkRTree.h" + +SkRTree::SkRTree() : fCount(0) {} + +SkRect SkRTree::getRootBound() const { + if (fCount) { + return fRoot.fBounds; + } else { + return SkRect::MakeEmpty(); + } +} + +void SkRTree::insert(const SkRect boundsArray[], int N) { + SkASSERT(0 == fCount); + + SkTDArray branches; + branches.setReserve(N); + + for (int i = 0; i < N; i++) { + const SkRect& bounds = boundsArray[i]; + if (bounds.isEmpty()) { + continue; + } + + Branch* b = branches.push(); + b->fBounds = bounds; + b->fOpIndex = i; + } + + fCount = branches.count(); + if (fCount) { + if (1 == fCount) { + fNodes.setReserve(1); + Node* n = this->allocateNodeAtLevel(0); + n->fNumChildren = 1; + n->fChildren[0] = branches[0]; + fRoot.fSubtree = n; + fRoot.fBounds = branches[0].fBounds; + } else { + fNodes.setReserve(CountNodes(fCount)); + fRoot = this->bulkLoad(&branches); + } + } +} + +SkRTree::Node* SkRTree::allocateNodeAtLevel(uint16_t level) { + SkDEBUGCODE(Node* p = fNodes.begin()); + Node* out = fNodes.push(); + SkASSERT(fNodes.begin() == p); // If this fails, we didn't setReserve() enough. + out->fNumChildren = 0; + out->fLevel = level; + return out; +} + +// This function parallels bulkLoad, but just counts how many nodes bulkLoad would allocate. +int SkRTree::CountNodes(int branches) { + if (branches == 1) { + return 1; + } + int numBranches = branches / kMaxChildren; + int remainder = branches % kMaxChildren; + if (remainder > 0) { + numBranches++; + if (remainder >= kMinChildren) { + remainder = 0; + } else { + remainder = kMinChildren - remainder; + } + } + int currentBranch = 0; + int nodes = 0; + while (currentBranch < branches) { + int incrementBy = kMaxChildren; + if (remainder != 0) { + if (remainder <= kMaxChildren - kMinChildren) { + incrementBy -= remainder; + remainder = 0; + } else { + incrementBy = kMinChildren; + remainder -= kMaxChildren - kMinChildren; + } + } + nodes++; + currentBranch++; + for (int k = 1; k < incrementBy && currentBranch < branches; ++k) { + currentBranch++; + } + } + return nodes + CountNodes(nodes); +} + +SkRTree::Branch SkRTree::bulkLoad(SkTDArray* branches, int level) { + if (branches->count() == 1) { // Only one branch. It will be the root. + return (*branches)[0]; + } + + // We might sort our branches here, but we expect Blink gives us a reasonable x,y order. + // Skipping a call to sort (in Y) here resulted in a 17% win for recording with negligible + // difference in playback speed. + int numBranches = branches->count() / kMaxChildren; + int remainder = branches->count() % kMaxChildren; + int newBranches = 0; + + if (remainder > 0) { + ++numBranches; + // If the remainder isn't enough to fill a node, we'll add fewer nodes to other branches. + if (remainder >= kMinChildren) { + remainder = 0; + } else { + remainder = kMinChildren - remainder; + } + } + + int currentBranch = 0; + while (currentBranch < branches->count()) { + int incrementBy = kMaxChildren; + if (remainder != 0) { + // if need be, omit some nodes to make up for remainder + if (remainder <= kMaxChildren - kMinChildren) { + incrementBy -= remainder; + remainder = 0; + } else { + incrementBy = kMinChildren; + remainder -= kMaxChildren - kMinChildren; + } + } + Node* n = allocateNodeAtLevel(level); + n->fNumChildren = 1; + n->fChildren[0] = (*branches)[currentBranch]; + Branch b; + b.fBounds = (*branches)[currentBranch].fBounds; + b.fSubtree = n; + ++currentBranch; + for (int k = 1; k < incrementBy && currentBranch < branches->count(); ++k) { + b.fBounds.join((*branches)[currentBranch].fBounds); + n->fChildren[k] = (*branches)[currentBranch]; + ++n->fNumChildren; + ++currentBranch; + } + (*branches)[newBranches] = b; + ++newBranches; + } + branches->setCount(newBranches); + return this->bulkLoad(branches, level + 1); +} + +void SkRTree::search(const SkRect& query, SkTDArray* results) const { + if (fCount > 0 && SkRect::Intersects(fRoot.fBounds, query)) { + this->search(fRoot.fSubtree, query, results); + } +} + +void SkRTree::search(Node* node, const SkRect& query, SkTDArray* results) const { + for (int i = 0; i < node->fNumChildren; ++i) { + if (SkRect::Intersects(node->fChildren[i].fBounds, query)) { + if (0 == node->fLevel) { + results->push_back(node->fChildren[i].fOpIndex); + } else { + this->search(node->fChildren[i].fSubtree, query, results); + } + } + } +} + +size_t SkRTree::bytesUsed() const { + size_t byteCount = sizeof(SkRTree); + + byteCount += fNodes.reserved() * sizeof(Node); + + return byteCount; +} diff --git a/gfx/skia/skia/src/core/SkRTree.h b/gfx/skia/skia/src/core/SkRTree.h new file mode 100644 index 0000000000..021a58b450 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRTree.h @@ -0,0 +1,89 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRTree_DEFINED +#define SkRTree_DEFINED + +#include "include/core/SkRect.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkBBoxHierarchy.h" + +/** + * An R-Tree implementation. In short, it is a balanced n-ary tree containing a hierarchy of + * bounding rectangles. + * + * It only supports bulk-loading, i.e. creation from a batch of bounding rectangles. + * This performs a bottom-up bulk load using the STR (sort-tile-recursive) algorithm. + * + * TODO: Experiment with other bulk-load algorithms (in particular the Hilbert pack variant, + * which groups rects by position on the Hilbert curve, is probably worth a look). There also + * exist top-down bulk load variants (VAMSplit, TopDownGreedy, etc). + * + * For more details see: + * + * Beckmann, N.; Kriegel, H. P.; Schneider, R.; Seeger, B. (1990). "The R*-tree: + * an efficient and robust access method for points and rectangles" + */ +class SkRTree : public SkBBoxHierarchy { +public: + SkRTree(); + ~SkRTree() override {} + + void insert(const SkRect[], int N) override; + void search(const SkRect& query, SkTDArray* results) const override; + size_t bytesUsed() const override; + + // Methods and constants below here are only public for tests. + + // Return the depth of the tree structure. + int getDepth() const { return fCount ? fRoot.fSubtree->fLevel + 1 : 0; } + // Insertion count (not overall node count, which may be greater). + int getCount() const { return fCount; } + + // Get the root bound. + SkRect getRootBound() const override; + + // These values were empirically determined to produce reasonable performance in most cases. + static const int kMinChildren = 6, + kMaxChildren = 11; + +private: + struct Node; + + struct Branch { + union { + Node* fSubtree; + int fOpIndex; + }; + SkRect fBounds; + }; + + struct Node { + uint16_t fNumChildren; + uint16_t fLevel; + Branch fChildren[kMaxChildren]; + }; + + void search(Node* root, const SkRect& query, SkTDArray* results) const; + + // Consumes the input array. + Branch bulkLoad(SkTDArray* branches, int level = 0); + + // How many times will bulkLoad() call allocateNodeAtLevel()? + static int CountNodes(int branches); + + Node* allocateNodeAtLevel(uint16_t level); + + // This is the count of data elements (rather than total nodes in the tree) + int fCount; + Branch fRoot; + SkTDArray fNodes; + + typedef SkBBoxHierarchy INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkRWBuffer.cpp b/gfx/skia/skia/src/core/SkRWBuffer.cpp new file mode 100644 index 0000000000..84e56e4748 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRWBuffer.cpp @@ -0,0 +1,364 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRWBuffer.h" + +#include "include/core/SkStream.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" +#include "src/core/SkMakeUnique.h" + +#include +#include + +// Force small chunks to be a page's worth +static const size_t kMinAllocSize = 4096; + +struct SkBufferBlock { + SkBufferBlock* fNext; // updated by the writer + size_t fUsed; // updated by the writer + const size_t fCapacity; + + SkBufferBlock(size_t capacity) : fNext(nullptr), fUsed(0), fCapacity(capacity) {} + + const void* startData() const { return this + 1; } + + size_t avail() const { return fCapacity - fUsed; } + void* availData() { return (char*)this->startData() + fUsed; } + + static SkBufferBlock* Alloc(size_t length) { + size_t capacity = LengthToCapacity(length); + void* buffer = sk_malloc_throw(sizeof(SkBufferBlock) + capacity); + return new (buffer) SkBufferBlock(capacity); + } + + // Return number of bytes actually appended. Important that we always completely this block + // before spilling into the next, since the reader uses fCapacity to know how many it can read. + // + size_t append(const void* src, size_t length) { + this->validate(); + size_t amount = SkTMin(this->avail(), length); + memcpy(this->availData(), src, amount); + fUsed += amount; + this->validate(); + return amount; + } + + // Do not call in the reader thread, since the writer may be updating fUsed. + // (The assertion is still true, but TSAN still may complain about its raciness.) + void validate() const { +#ifdef SK_DEBUG + SkASSERT(fCapacity > 0); + SkASSERT(fUsed <= fCapacity); +#endif + } + +private: + static size_t LengthToCapacity(size_t length) { + const size_t minSize = kMinAllocSize - sizeof(SkBufferBlock); + return SkTMax(length, minSize); + } +}; + +struct SkBufferHead { + mutable std::atomic fRefCnt; + SkBufferBlock fBlock; + + SkBufferHead(size_t capacity) : fRefCnt(1), fBlock(capacity) {} + + static size_t LengthToCapacity(size_t length) { + const size_t minSize = kMinAllocSize - sizeof(SkBufferHead); + return SkTMax(length, minSize); + } + + static SkBufferHead* Alloc(size_t length) { + size_t capacity = LengthToCapacity(length); + size_t size = sizeof(SkBufferHead) + capacity; + void* buffer = sk_malloc_throw(size); + return new (buffer) SkBufferHead(capacity); + } + + void ref() const { + SkAssertResult(fRefCnt.fetch_add(+1, std::memory_order_relaxed)); + } + + void unref() const { + // A release here acts in place of all releases we "should" have been doing in ref(). + int32_t oldRefCnt = fRefCnt.fetch_add(-1, std::memory_order_acq_rel); + SkASSERT(oldRefCnt); + if (1 == oldRefCnt) { + // Like unique(), the acquire is only needed on success. + SkBufferBlock* block = fBlock.fNext; + sk_free((void*)this); + while (block) { + SkBufferBlock* next = block->fNext; + sk_free(block); + block = next; + } + } + } + + void validate(size_t minUsed, const SkBufferBlock* tail = nullptr) const { +#ifdef SK_DEBUG + SkASSERT(fRefCnt.load(std::memory_order_relaxed) > 0); + size_t totalUsed = 0; + const SkBufferBlock* block = &fBlock; + const SkBufferBlock* lastBlock = block; + while (block) { + block->validate(); + totalUsed += block->fUsed; + lastBlock = block; + block = block->fNext; + } + SkASSERT(minUsed <= totalUsed); + if (tail) { + SkASSERT(tail == lastBlock); + } +#endif + } +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// The reader can only access block.fCapacity (which never changes), and cannot access +// block.fUsed, which may be updated by the writer. +// +SkROBuffer::SkROBuffer(const SkBufferHead* head, size_t available, const SkBufferBlock* tail) + : fHead(head), fAvailable(available), fTail(tail) +{ + if (head) { + fHead->ref(); + SkASSERT(available > 0); + head->validate(available, tail); + } else { + SkASSERT(0 == available); + SkASSERT(!tail); + } +} + +SkROBuffer::~SkROBuffer() { + if (fHead) { + fHead->unref(); + } +} + +SkROBuffer::Iter::Iter(const SkROBuffer* buffer) { + this->reset(buffer); +} + +SkROBuffer::Iter::Iter(const sk_sp& buffer) { + this->reset(buffer.get()); +} + +void SkROBuffer::Iter::reset(const SkROBuffer* buffer) { + fBuffer = buffer; + if (buffer && buffer->fHead) { + fBlock = &buffer->fHead->fBlock; + fRemaining = buffer->fAvailable; + } else { + fBlock = nullptr; + fRemaining = 0; + } +} + +const void* SkROBuffer::Iter::data() const { + return fRemaining ? fBlock->startData() : nullptr; +} + +size_t SkROBuffer::Iter::size() const { + if (!fBlock) { + return 0; + } + return SkTMin(fBlock->fCapacity, fRemaining); +} + +bool SkROBuffer::Iter::next() { + if (fRemaining) { + fRemaining -= this->size(); + if (fBuffer->fTail == fBlock) { + // There are more blocks, but fBuffer does not know about them. + SkASSERT(0 == fRemaining); + fBlock = nullptr; + } else { + fBlock = fBlock->fNext; + } + } + return fRemaining != 0; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkRWBuffer::SkRWBuffer(size_t initialCapacity) : fHead(nullptr), fTail(nullptr), fTotalUsed(0) { + if (initialCapacity) { + fHead = SkBufferHead::Alloc(initialCapacity); + fTail = &fHead->fBlock; + } +} + +SkRWBuffer::~SkRWBuffer() { + this->validate(); + if (fHead) { + fHead->unref(); + } +} + +// It is important that we always completely fill the current block before spilling over to the +// next, since our reader will be using fCapacity (min'd against its total available) to know how +// many bytes to read from a given block. +// +void SkRWBuffer::append(const void* src, size_t length, size_t reserve) { + this->validate(); + if (0 == length) { + return; + } + + fTotalUsed += length; + + if (nullptr == fHead) { + fHead = SkBufferHead::Alloc(length + reserve); + fTail = &fHead->fBlock; + } + + size_t written = fTail->append(src, length); + SkASSERT(written <= length); + src = (const char*)src + written; + length -= written; + + if (length) { + SkBufferBlock* block = SkBufferBlock::Alloc(length + reserve); + fTail->fNext = block; + fTail = block; + written = fTail->append(src, length); + SkASSERT(written == length); + } + this->validate(); +} + +#ifdef SK_DEBUG +void SkRWBuffer::validate() const { + if (fHead) { + fHead->validate(fTotalUsed, fTail); + } else { + SkASSERT(nullptr == fTail); + SkASSERT(0 == fTotalUsed); + } +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkROBufferStreamAsset : public SkStreamAsset { + void validate() const { +#ifdef SK_DEBUG + SkASSERT(fGlobalOffset <= fBuffer->size()); + SkASSERT(fLocalOffset <= fIter.size()); + SkASSERT(fLocalOffset <= fGlobalOffset); +#endif + } + +#ifdef SK_DEBUG + class AutoValidate { + SkROBufferStreamAsset* fStream; + public: + AutoValidate(SkROBufferStreamAsset* stream) : fStream(stream) { stream->validate(); } + ~AutoValidate() { fStream->validate(); } + }; + #define AUTO_VALIDATE AutoValidate av(this); +#else + #define AUTO_VALIDATE +#endif + +public: + SkROBufferStreamAsset(sk_sp buffer) : fBuffer(std::move(buffer)), fIter(fBuffer) { + fGlobalOffset = fLocalOffset = 0; + } + + size_t getLength() const override { return fBuffer->size(); } + + bool rewind() override { + AUTO_VALIDATE + fIter.reset(fBuffer.get()); + fGlobalOffset = fLocalOffset = 0; + return true; + } + + size_t read(void* dst, size_t request) override { + AUTO_VALIDATE + size_t bytesRead = 0; + for (;;) { + size_t size = fIter.size(); + SkASSERT(fLocalOffset <= size); + size_t avail = SkTMin(size - fLocalOffset, request - bytesRead); + if (dst) { + memcpy(dst, (const char*)fIter.data() + fLocalOffset, avail); + dst = (char*)dst + avail; + } + bytesRead += avail; + fLocalOffset += avail; + SkASSERT(bytesRead <= request); + if (bytesRead == request) { + break; + } + // If we get here, we've exhausted the current iter + SkASSERT(fLocalOffset == size); + fLocalOffset = 0; + if (!fIter.next()) { + break; // ran out of data + } + } + fGlobalOffset += bytesRead; + SkASSERT(fGlobalOffset <= fBuffer->size()); + return bytesRead; + } + + bool isAtEnd() const override { + return fBuffer->size() == fGlobalOffset; + } + + size_t getPosition() const override { + return fGlobalOffset; + } + + bool seek(size_t position) override { + AUTO_VALIDATE + if (position < fGlobalOffset) { + this->rewind(); + } + (void)this->skip(position - fGlobalOffset); + return true; + } + + bool move(long offset) override{ + AUTO_VALIDATE + offset += fGlobalOffset; + if (offset <= 0) { + this->rewind(); + } else { + (void)this->seek(SkToSizeT(offset)); + } + return true; + } + +private: + SkStreamAsset* onDuplicate() const override { + return new SkROBufferStreamAsset(fBuffer); + } + + SkStreamAsset* onFork() const override { + auto clone = this->duplicate(); + clone->seek(this->getPosition()); + return clone.release(); + } + + sk_sp fBuffer; + SkROBuffer::Iter fIter; + size_t fLocalOffset; + size_t fGlobalOffset; +}; + +std::unique_ptr SkRWBuffer::makeStreamSnapshot() const { + return skstd::make_unique(this->makeROBufferSnapshot()); +} diff --git a/gfx/skia/skia/src/core/SkRasterClip.cpp b/gfx/skia/skia/src/core/SkRasterClip.cpp new file mode 100644 index 0000000000..a2f796f057 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRasterClip.cpp @@ -0,0 +1,507 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkRegionPriv.h" + +enum MutateResult { + kDoNothing_MutateResult, + kReplaceClippedAgainstGlobalBounds_MutateResult, + kContinue_MutateResult, +}; + +static MutateResult mutate_conservative_op(SkRegion::Op* op, bool inverseFilled) { + if (inverseFilled) { + switch (*op) { + case SkRegion::kIntersect_Op: + case SkRegion::kDifference_Op: + // These ops can only shrink the current clip. So leaving + // the clip unchanged conservatively respects the contract. + return kDoNothing_MutateResult; + case SkRegion::kUnion_Op: + case SkRegion::kReplace_Op: + case SkRegion::kReverseDifference_Op: + case SkRegion::kXOR_Op: { + // These ops can grow the current clip up to the extents of + // the input clip, which is inverse filled, so we just set + // the current clip to the device bounds. + *op = SkRegion::kReplace_Op; + return kReplaceClippedAgainstGlobalBounds_MutateResult; + } + } + } else { + // Not inverse filled + switch (*op) { + case SkRegion::kIntersect_Op: + case SkRegion::kUnion_Op: + case SkRegion::kReplace_Op: + return kContinue_MutateResult; + case SkRegion::kDifference_Op: + // Difference can only shrink the current clip. + // Leaving clip unchanged conservatively fullfills the contract. + return kDoNothing_MutateResult; + case SkRegion::kReverseDifference_Op: + // To reverse, we swap in the bounds with a replace op. + // As with difference, leave it unchanged. + *op = SkRegion::kReplace_Op; + return kContinue_MutateResult; + case SkRegion::kXOR_Op: + // Be conservative, based on (A XOR B) always included in (A union B), + // which is always included in (bounds(A) union bounds(B)) + *op = SkRegion::kUnion_Op; + return kContinue_MutateResult; + } + } + SkASSERT(false); // unknown op + return kDoNothing_MutateResult; +} + +void SkConservativeClip::opRect(const SkRect& localRect, const SkMatrix& ctm, + const SkIRect& devBounds, SkRegion::Op op, bool doAA) { + SkIRect ir; + switch (mutate_conservative_op(&op, false)) { + case kDoNothing_MutateResult: + return; + case kReplaceClippedAgainstGlobalBounds_MutateResult: + ir = devBounds; + break; + case kContinue_MutateResult: { + SkRect devRect; + ctm.mapRect(&devRect, localRect); + ir = doAA ? devRect.roundOut() : devRect.round(); + } break; + } + this->opIRect(ir, op); +} + +void SkConservativeClip::opRRect(const SkRRect& rrect, const SkMatrix& ctm, + const SkIRect& devBounds, SkRegion::Op op, bool doAA) { + this->opRect(rrect.getBounds(), ctm, devBounds, op, doAA); +} + +void SkConservativeClip::opPath(const SkPath& path, const SkMatrix& ctm, const SkIRect& devBounds, + SkRegion::Op op, bool doAA) { + SkIRect ir; + switch (mutate_conservative_op(&op, path.isInverseFillType())) { + case kDoNothing_MutateResult: + return; + case kReplaceClippedAgainstGlobalBounds_MutateResult: + ir = devBounds; + break; + case kContinue_MutateResult: { + SkRect bounds = path.getBounds(); + ctm.mapRect(&bounds); + ir = bounds.roundOut(); + break; + } + } + return this->opIRect(ir, op); +} + +void SkConservativeClip::opRegion(const SkRegion& rgn, SkRegion::Op op) { + this->opIRect(rgn.getBounds(), op); +} + +void SkConservativeClip::opIRect(const SkIRect& devRect, SkRegion::Op op) { + if (SkRegion::kIntersect_Op == op) { + if (!fBounds.intersect(devRect)) { + fBounds.setEmpty(); + } + return; + } + + // This may still create a complex region (which we would then take the bounds + // Perhaps we should inline the op-logic directly to never create the rgn... + SkRegion result; + result.op(SkRegion(fBounds), SkRegion(devRect), op); + fBounds = result.getBounds(); + this->applyClipRestriction(op, &fBounds); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkRasterClip::SkRasterClip(const SkRasterClip& src) { + AUTO_RASTERCLIP_VALIDATE(src); + + fIsBW = src.fIsBW; + if (fIsBW) { + fBW = src.fBW; + } else { + fAA = src.fAA; + } + + fIsEmpty = src.isEmpty(); + fIsRect = src.isRect(); + fClipRestrictionRect = src.fClipRestrictionRect; + SkDEBUGCODE(this->validate();) +} + +SkRasterClip::SkRasterClip(const SkRegion& rgn) : fBW(rgn) { + fIsBW = true; + fIsEmpty = this->computeIsEmpty(); // bounds might be empty, so compute + fIsRect = !fIsEmpty; + SkDEBUGCODE(this->validate();) +} + +SkRasterClip::SkRasterClip(const SkIRect& bounds) : fBW(bounds) { + fIsBW = true; + fIsEmpty = this->computeIsEmpty(); // bounds might be empty, so compute + fIsRect = !fIsEmpty; + SkDEBUGCODE(this->validate();) +} + +SkRasterClip::SkRasterClip() { + fIsBW = true; + fIsEmpty = true; + fIsRect = false; + SkDEBUGCODE(this->validate();) +} + +SkRasterClip::~SkRasterClip() { + SkDEBUGCODE(this->validate();) +} + +bool SkRasterClip::operator==(const SkRasterClip& other) const { + if (fIsBW != other.fIsBW) { + return false; + } + bool isEqual = fIsBW ? fBW == other.fBW : fAA == other.fAA; +#ifdef SK_DEBUG + if (isEqual) { + SkASSERT(fIsEmpty == other.fIsEmpty); + SkASSERT(fIsRect == other.fIsRect); + } +#endif + return isEqual; +} + +bool SkRasterClip::isComplex() const { + return fIsBW ? fBW.isComplex() : !fAA.isEmpty(); +} + +const SkIRect& SkRasterClip::getBounds() const { + return fIsBW ? fBW.getBounds() : fAA.getBounds(); +} + +bool SkRasterClip::setEmpty() { + AUTO_RASTERCLIP_VALIDATE(*this); + + fIsBW = true; + fBW.setEmpty(); + fAA.setEmpty(); + fIsEmpty = true; + fIsRect = false; + return false; +} + +bool SkRasterClip::setRect(const SkIRect& rect) { + AUTO_RASTERCLIP_VALIDATE(*this); + + fIsBW = true; + fAA.setEmpty(); + fIsRect = fBW.setRect(rect); + fIsEmpty = !fIsRect; + return fIsRect; +} + +///////////////////////////////////////////////////////////////////////////////////// + +bool SkRasterClip::setConservativeRect(const SkRect& r, const SkIRect& clipR, bool isInverse) { + SkRegion::Op op; + if (isInverse) { + op = SkRegion::kDifference_Op; + } else { + op = SkRegion::kIntersect_Op; + } + fBW.setRect(clipR); + fBW.op(r.roundOut(), op); + return this->updateCacheAndReturnNonEmpty(); +} + +///////////////////////////////////////////////////////////////////////////////////// + +bool SkRasterClip::setPath(const SkPath& path, const SkRegion& clip, bool doAA) { + AUTO_RASTERCLIP_VALIDATE(*this); + + if (this->isBW() && !doAA) { + (void)fBW.setPath(path, clip); + } else { + // TODO: since we are going to over-write fAA completely (aren't we?) + // we should just clear our BW data (if any) and set fIsAA=true + if (this->isBW()) { + this->convertToAA(); + } + (void)fAA.setPath(path, &clip, doAA); + } + return this->updateCacheAndReturnNonEmpty(); +} + +bool SkRasterClip::op(const SkRRect& rrect, const SkMatrix& matrix, const SkIRect& devBounds, + SkRegion::Op op, bool doAA) { + SkIRect bounds(devBounds); + this->applyClipRestriction(op, &bounds); + + SkPath path; + path.addRRect(rrect); + + return this->op(path, matrix, bounds, op, doAA); +} + +bool SkRasterClip::op(const SkPath& path, const SkMatrix& matrix, const SkIRect& devBounds, + SkRegion::Op op, bool doAA) { + AUTO_RASTERCLIP_VALIDATE(*this); + SkIRect bounds(devBounds); + this->applyClipRestriction(op, &bounds); + + // base is used to limit the size (and therefore memory allocation) of the + // region that results from scan converting devPath. + SkRegion base; + + SkPath devPath; + if (matrix.isIdentity()) { + devPath = path; + } else { + path.transform(matrix, &devPath); + devPath.setIsVolatile(true); + } + if (SkRegion::kIntersect_Op == op) { + // since we are intersect, we can do better (tighter) with currRgn's + // bounds, than just using the device. However, if currRgn is complex, + // our region blitter may hork, so we do that case in two steps. + if (this->isRect()) { + // FIXME: we should also be able to do this when this->isBW(), + // but relaxing the test above triggers GM asserts in + // SkRgnBuilder::blitH(). We need to investigate what's going on. + return this->setPath(devPath, this->bwRgn(), doAA); + } else { + base.setRect(this->getBounds()); + SkRasterClip clip; + clip.setPath(devPath, base, doAA); + return this->op(clip, op); + } + } else { + base.setRect(bounds); + + if (SkRegion::kReplace_Op == op) { + return this->setPath(devPath, base, doAA); + } else { + SkRasterClip clip; + clip.setPath(devPath, base, doAA); + return this->op(clip, op); + } + } +} + +bool SkRasterClip::setPath(const SkPath& path, const SkIRect& clip, bool doAA) { + SkRegion tmp; + tmp.setRect(clip); + return this->setPath(path, tmp, doAA); +} + +bool SkRasterClip::op(const SkIRect& rect, SkRegion::Op op) { + AUTO_RASTERCLIP_VALIDATE(*this); + + fIsBW ? fBW.op(rect, op) : fAA.op(rect, op); + return this->updateCacheAndReturnNonEmpty(); +} + +bool SkRasterClip::op(const SkRegion& rgn, SkRegion::Op op) { + AUTO_RASTERCLIP_VALIDATE(*this); + + if (fIsBW) { + (void)fBW.op(rgn, op); + } else { + SkAAClip tmp; + tmp.setRegion(rgn); + (void)fAA.op(tmp, op); + } + return this->updateCacheAndReturnNonEmpty(); +} + +bool SkRasterClip::op(const SkRasterClip& clip, SkRegion::Op op) { + AUTO_RASTERCLIP_VALIDATE(*this); + clip.validate(); + + if (this->isBW() && clip.isBW()) { + (void)fBW.op(clip.fBW, op); + } else { + SkAAClip tmp; + const SkAAClip* other; + + if (this->isBW()) { + this->convertToAA(); + } + if (clip.isBW()) { + tmp.setRegion(clip.bwRgn()); + other = &tmp; + } else { + other = &clip.aaRgn(); + } + (void)fAA.op(*other, op); + } + return this->updateCacheAndReturnNonEmpty(); +} + +/** + * Our antialiasing currently has a granularity of 1/4 of a pixel along each + * axis. Thus we can treat an axis coordinate as an integer if it differs + * from its nearest int by < half of that value (1.8 in this case). + */ +static bool nearly_integral(SkScalar x) { + static const SkScalar domain = SK_Scalar1 / 4; + static const SkScalar halfDomain = domain / 2; + + x += halfDomain; + return x - SkScalarFloorToScalar(x) < domain; +} + +bool SkRasterClip::op(const SkRect& localRect, const SkMatrix& matrix, const SkIRect& devBounds, + SkRegion::Op op, bool doAA) { + AUTO_RASTERCLIP_VALIDATE(*this); + SkRect devRect; + + const bool isScaleTrans = matrix.isScaleTranslate(); + if (!isScaleTrans) { + SkPath path; + path.addRect(localRect); + path.setIsVolatile(true); + return this->op(path, matrix, devBounds, op, doAA); + } + + matrix.mapRect(&devRect, localRect); + + if (fIsBW && doAA) { + // check that the rect really needs aa, or is it close enought to + // integer boundaries that we can just treat it as a BW rect? + if (nearly_integral(devRect.fLeft) && nearly_integral(devRect.fTop) && + nearly_integral(devRect.fRight) && nearly_integral(devRect.fBottom)) { + doAA = false; + } + } + + if (fIsBW && !doAA) { + SkIRect ir; + devRect.round(&ir); + this->applyClipRestriction(op, &ir); + (void)fBW.op(ir, op); + } else { + if (fIsBW) { + this->convertToAA(); + } + this->applyClipRestriction(op, &devRect); + (void)fAA.op(devRect, op, doAA); + } + return this->updateCacheAndReturnNonEmpty(); +} + +void SkRasterClip::translate(int dx, int dy, SkRasterClip* dst) const { + if (nullptr == dst) { + return; + } + + AUTO_RASTERCLIP_VALIDATE(*this); + + if (this->isEmpty()) { + dst->setEmpty(); + return; + } + if (0 == (dx | dy)) { + *dst = *this; + return; + } + + dst->fIsBW = fIsBW; + if (fIsBW) { + fBW.translate(dx, dy, &dst->fBW); + dst->fAA.setEmpty(); + } else { + fAA.translate(dx, dy, &dst->fAA); + dst->fBW.setEmpty(); + } + dst->updateCacheAndReturnNonEmpty(); +} + +bool SkRasterClip::quickContains(const SkIRect& ir) const { + return fIsBW ? fBW.quickContains(ir) : fAA.quickContains(ir); +} + +/////////////////////////////////////////////////////////////////////////////// + +const SkRegion& SkRasterClip::forceGetBW() { + AUTO_RASTERCLIP_VALIDATE(*this); + + if (!fIsBW) { + fBW.setRect(fAA.getBounds()); + } + return fBW; +} + +void SkRasterClip::convertToAA() { + AUTO_RASTERCLIP_VALIDATE(*this); + + SkASSERT(fIsBW); + fAA.setRegion(fBW); + fIsBW = false; + + // since we are being explicitly asked to convert-to-aa, we pass false so we don't "optimize" + // ourselves back to BW. + (void)this->updateCacheAndReturnNonEmpty(false); +} + +#ifdef SK_DEBUG +void SkRasterClip::validate() const { + // can't ever assert that fBW is empty, since we may have called forceGetBW + if (fIsBW) { + SkASSERT(fAA.isEmpty()); + } + + SkRegionPriv::Validate(fBW); + fAA.validate(); + + SkASSERT(this->computeIsEmpty() == fIsEmpty); + SkASSERT(this->computeIsRect() == fIsRect); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +SkAAClipBlitterWrapper::SkAAClipBlitterWrapper() { + SkDEBUGCODE(fClipRgn = nullptr;) + SkDEBUGCODE(fBlitter = nullptr;) +} + +SkAAClipBlitterWrapper::SkAAClipBlitterWrapper(const SkRasterClip& clip, + SkBlitter* blitter) { + this->init(clip, blitter); +} + +SkAAClipBlitterWrapper::SkAAClipBlitterWrapper(const SkAAClip* aaclip, + SkBlitter* blitter) { + SkASSERT(blitter); + SkASSERT(aaclip); + fBWRgn.setRect(aaclip->getBounds()); + fAABlitter.init(blitter, aaclip); + // now our return values + fClipRgn = &fBWRgn; + fBlitter = &fAABlitter; +} + +void SkAAClipBlitterWrapper::init(const SkRasterClip& clip, SkBlitter* blitter) { + SkASSERT(blitter); + if (clip.isBW()) { + fClipRgn = &clip.bwRgn(); + fBlitter = blitter; + } else { + const SkAAClip& aaclip = clip.aaRgn(); + fBWRgn.setRect(aaclip.getBounds()); + fAABlitter.init(blitter, &aaclip); + // now our return values + fClipRgn = &fBWRgn; + fBlitter = &fAABlitter; + } +} diff --git a/gfx/skia/skia/src/core/SkRasterClip.h b/gfx/skia/skia/src/core/SkRasterClip.h new file mode 100644 index 0000000000..994efd6273 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRasterClip.h @@ -0,0 +1,249 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRasterClip_DEFINED +#define SkRasterClip_DEFINED + +#include "include/core/SkRegion.h" +#include "include/private/SkMacros.h" +#include "src/core/SkAAClip.h" + +class SkRRect; + +class SkConservativeClip { + SkIRect fBounds; + const SkIRect* fClipRestrictionRect; + + inline void applyClipRestriction(SkRegion::Op op, SkIRect* bounds) { + if (op >= SkRegion::kUnion_Op && fClipRestrictionRect + && !fClipRestrictionRect->isEmpty()) { + if (!bounds->intersect(*fClipRestrictionRect)) { + bounds->setEmpty(); + } + } + } + +public: + SkConservativeClip() : fBounds(SkIRect::MakeEmpty()), fClipRestrictionRect(nullptr) {} + + bool isEmpty() const { return fBounds.isEmpty(); } + bool isRect() const { return true; } + const SkIRect& getBounds() const { return fBounds; } + + void setEmpty() { fBounds.setEmpty(); } + void setRect(const SkIRect& r) { fBounds = r; } + void setDeviceClipRestriction(const SkIRect* rect) { + fClipRestrictionRect = rect; + } + + void opRect(const SkRect&, const SkMatrix&, const SkIRect& limit, SkRegion::Op, bool isAA); + void opRRect(const SkRRect&, const SkMatrix&, const SkIRect& limit, SkRegion::Op, bool isAA); + void opPath(const SkPath&, const SkMatrix&, const SkIRect& limit, SkRegion::Op, bool isAA); + void opRegion(const SkRegion&, SkRegion::Op); + void opIRect(const SkIRect&, SkRegion::Op); +}; + +/** + * Wraps a SkRegion and SkAAClip, so we have a single object that can represent either our + * BW or antialiased clips. + * + * This class is optimized for the raster backend of canvas, but can be expense to keep up2date, + * so it supports a runtime option (force-conservative-rects) to turn it into a super-fast + * rect-only tracker. The gpu backend uses this since it does not need the result (it uses + * SkClipStack instead). + */ +class SkRasterClip { +public: + SkRasterClip(); + SkRasterClip(const SkIRect&); + SkRasterClip(const SkRegion&); + SkRasterClip(const SkRasterClip&); + ~SkRasterClip(); + + // Only compares the current state. Does not compare isForceConservativeRects(), so that field + // could be different but this could still return true. + bool operator==(const SkRasterClip&) const; + bool operator!=(const SkRasterClip& other) const { + return !(*this == other); + } + + bool isBW() const { return fIsBW; } + bool isAA() const { return !fIsBW; } + const SkRegion& bwRgn() const { SkASSERT(fIsBW); return fBW; } + const SkAAClip& aaRgn() const { SkASSERT(!fIsBW); return fAA; } + + bool isEmpty() const { + SkASSERT(this->computeIsEmpty() == fIsEmpty); + return fIsEmpty; + } + + bool isRect() const { + SkASSERT(this->computeIsRect() == fIsRect); + return fIsRect; + } + + bool isComplex() const; + const SkIRect& getBounds() const; + + bool setEmpty(); + bool setRect(const SkIRect&); + + bool op(const SkIRect&, SkRegion::Op); + bool op(const SkRegion&, SkRegion::Op); + bool op(const SkRect&, const SkMatrix& matrix, const SkIRect&, SkRegion::Op, bool doAA); + bool op(const SkRRect&, const SkMatrix& matrix, const SkIRect&, SkRegion::Op, bool doAA); + bool op(const SkPath&, const SkMatrix& matrix, const SkIRect&, SkRegion::Op, bool doAA); + + void translate(int dx, int dy, SkRasterClip* dst) const; + void translate(int dx, int dy) { + this->translate(dx, dy, this); + } + + bool quickContains(const SkIRect& rect) const; + bool quickContains(int left, int top, int right, int bottom) const { + return quickContains(SkIRect::MakeLTRB(left, top, right, bottom)); + } + + /** + * Return true if this region is empty, or if the specified rectangle does + * not intersect the region. Returning false is not a guarantee that they + * intersect, but returning true is a guarantee that they do not. + */ + bool quickReject(const SkIRect& rect) const { + return !SkIRect::Intersects(this->getBounds(), rect); + } + + // hack for SkCanvas::getTotalClip + const SkRegion& forceGetBW(); + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + void setDeviceClipRestriction(const SkIRect* rect) { + fClipRestrictionRect = rect; + } + +private: + SkRegion fBW; + SkAAClip fAA; + bool fIsBW; + // these 2 are caches based on querying the right obj based on fIsBW + bool fIsEmpty; + bool fIsRect; + const SkIRect* fClipRestrictionRect = nullptr; + + bool computeIsEmpty() const { + return fIsBW ? fBW.isEmpty() : fAA.isEmpty(); + } + + bool computeIsRect() const { + return fIsBW ? fBW.isRect() : fAA.isRect(); + } + + bool updateCacheAndReturnNonEmpty(bool detectAARect = true) { + fIsEmpty = this->computeIsEmpty(); + + // detect that our computed AA is really just a (hard-edged) rect + if (detectAARect && !fIsEmpty && !fIsBW && fAA.isRect()) { + fBW.setRect(fAA.getBounds()); + fAA.setEmpty(); // don't need this guy anymore + fIsBW = true; + } + + fIsRect = this->computeIsRect(); + return !fIsEmpty; + } + + void convertToAA(); + + bool setPath(const SkPath& path, const SkRegion& clip, bool doAA); + bool setPath(const SkPath& path, const SkIRect& clip, bool doAA); + bool op(const SkRasterClip&, SkRegion::Op); + bool setConservativeRect(const SkRect& r, const SkIRect& clipR, bool isInverse); + + inline void applyClipRestriction(SkRegion::Op op, SkIRect* bounds) { + if (op >= SkRegion::kUnion_Op && fClipRestrictionRect + && !fClipRestrictionRect->isEmpty()) { + if (!bounds->intersect(*fClipRestrictionRect)) { + bounds->setEmpty(); + } + } + } + + inline void applyClipRestriction(SkRegion::Op op, SkRect* bounds) { + if (op >= SkRegion::kUnion_Op && fClipRestrictionRect + && !fClipRestrictionRect->isEmpty()) { + if (!bounds->intersect(SkRect::Make(*fClipRestrictionRect))) { + bounds->setEmpty(); + } + } + } +}; + +class SkAutoRasterClipValidate : SkNoncopyable { +public: + SkAutoRasterClipValidate(const SkRasterClip& rc) : fRC(rc) { + fRC.validate(); + } + ~SkAutoRasterClipValidate() { + fRC.validate(); + } +private: + const SkRasterClip& fRC; +}; +#define SkAutoRasterClipValidate(...) SK_REQUIRE_LOCAL_VAR(SkAutoRasterClipValidate) + +#ifdef SK_DEBUG + #define AUTO_RASTERCLIP_VALIDATE(rc) SkAutoRasterClipValidate arcv(rc) +#else + #define AUTO_RASTERCLIP_VALIDATE(rc) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Encapsulates the logic of deciding if we need to change/wrap the blitter + * for aaclipping. If so, getRgn and getBlitter return modified values. If + * not, they return the raw blitter and (bw) clip region. + * + * We need to keep the constructor/destructor cost as small as possible, so we + * can freely put this guy on the stack, and not pay too much for the case when + * we're really BW anyways. + */ +class SkAAClipBlitterWrapper { +public: + SkAAClipBlitterWrapper(); + SkAAClipBlitterWrapper(const SkRasterClip&, SkBlitter*); + SkAAClipBlitterWrapper(const SkAAClip*, SkBlitter*); + + void init(const SkRasterClip&, SkBlitter*); + + const SkIRect& getBounds() const { + SkASSERT(fClipRgn); + return fClipRgn->getBounds(); + } + const SkRegion& getRgn() const { + SkASSERT(fClipRgn); + return *fClipRgn; + } + SkBlitter* getBlitter() { + SkASSERT(fBlitter); + return fBlitter; + } + +private: + SkRegion fBWRgn; + SkAAClipBlitter fAABlitter; + // what we return + const SkRegion* fClipRgn; + SkBlitter* fBlitter; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkRasterClipStack.h b/gfx/skia/skia/src/core/SkRasterClipStack.h new file mode 100644 index 0000000000..3698e256c0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRasterClipStack.h @@ -0,0 +1,174 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRasterClipStack_DEFINED +#define SkRasterClipStack_DEFINED + +#include "include/core/SkClipOp.h" +#include "include/core/SkDeque.h" +#include "src/core/SkRasterClip.h" +#include + +template class SkTStack { +public: + SkTStack(void* storage, size_t size) : fDeque(sizeof(T), storage, size), fTop(nullptr) {} + ~SkTStack() { + while (!fDeque.empty()) { + ((T*)fDeque.back())->~T(); + fDeque.pop_back(); + } + } + + bool empty() const { return fDeque.empty(); } + + int count() const { return fDeque.count(); } + + const T& top() const { + SkASSERT(fTop); + return *fTop; + } + + T& top() { + SkASSERT(fTop); + return *fTop; + } + + T* push_raw() { return (T*)fDeque.push_back(); } + T& push() { + fTop = this->push_raw(); + new (fTop) T(); + return *fTop; + } + T& push(const T& src) { + fTop = this->push_raw(); + new (fTop) T(src); + return *fTop; + } + + void pop() { + fTop->~T(); + fDeque.pop_back(); + fTop = fDeque.empty() ? nullptr : (T*)fDeque.back(); + } + +private: + SkDeque fDeque; + T* fTop; +}; + +class SkRasterClipStack : SkNoncopyable { + int fCounter = 0; +public: + SkRasterClipStack(int width, int height) + : fStack(fStorage, sizeof(fStorage)) + , fRootBounds(SkIRect::MakeWH(width, height)) + { + Rec& rec = fStack.push(); + rec.fRC.setRect(fRootBounds); + rec.fDeferredCount = 0; + SkASSERT(fStack.count() == 1); + } + + void setNewSize(int w, int h) { + fRootBounds.setXYWH(0, 0, w, h); + + SkASSERT(fStack.count() == 1); + Rec& rec = fStack.top(); + SkASSERT(rec.fDeferredCount == 0); + rec.fRC.setRect(fRootBounds); + } + + const SkRasterClip& rc() const { return fStack.top().fRC; } + + void save() { + fCounter += 1; + SkASSERT(fStack.top().fDeferredCount >= 0); + fStack.top().fDeferredCount += 1; + } + + void restore() { + fCounter -= 1; SkASSERT(fCounter >= 0); + if (--fStack.top().fDeferredCount < 0) { + SkASSERT(fStack.top().fDeferredCount == -1); + SkASSERT(fStack.count() > 1); + fStack.pop(); + } + } + + void clipRect(const SkMatrix& ctm, const SkRect& rect, SkClipOp op, bool aa) { + this->writable_rc().op(rect, ctm, fRootBounds, (SkRegion::Op)op, aa); + this->trimIfExpanding(op); + this->validate(); + } + + void clipRRect(const SkMatrix& ctm, const SkRRect& rrect, SkClipOp op, bool aa) { + this->writable_rc().op(rrect, ctm, fRootBounds, (SkRegion::Op)op, aa); + this->trimIfExpanding(op); + this->validate(); + } + + void clipPath(const SkMatrix& ctm, const SkPath& path, SkClipOp op, bool aa) { + this->writable_rc().op(path, ctm, fRootBounds, (SkRegion::Op)op, aa); + this->trimIfExpanding(op); + this->validate(); + } + + void clipRegion(const SkRegion& rgn, SkClipOp op) { + this->writable_rc().op(rgn, (SkRegion::Op)op); + this->trimIfExpanding(op); + this->validate(); + } + + void setDeviceClipRestriction(SkIRect* mutableClipRestriction) { + this->writable_rc().setDeviceClipRestriction(mutableClipRestriction); + } + + void validate() const { +#ifdef SK_DEBUG + const SkRasterClip& clip = this->rc(); + if (fRootBounds.isEmpty()) { + SkASSERT(clip.isEmpty()); + } else if (!clip.isEmpty()) { + SkASSERT(fRootBounds.contains(clip.getBounds())); + } +#endif + } + +private: + struct Rec { + SkRasterClip fRC; + int fDeferredCount; // 0 for a "normal" entry + }; + + enum { + ELEM_COUNT = 16, + PTR_COUNT = ELEM_COUNT * sizeof(Rec) / sizeof(void*) + }; + void* fStorage[PTR_COUNT]; + SkTStack fStack; + SkIRect fRootBounds; + + SkRasterClip& writable_rc() { + SkASSERT(fStack.top().fDeferredCount >= 0); + if (fStack.top().fDeferredCount > 0) { + fStack.top().fDeferredCount -= 1; + fStack.push(fStack.top()); + fStack.top().fDeferredCount = 0; + } + return fStack.top().fRC; + } + + void trimIfExpanding(SkClipOp op) { + if ((int)op > (int)SkClipOp::kIntersect) { + Rec& rec = fStack.top(); + SkASSERT(rec.fDeferredCount == 0); + rec.fRC.op(fRootBounds, SkRegion::kIntersect_Op); + } + } +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkRasterPipeline.cpp b/gfx/skia/skia/src/core/SkRasterPipeline.cpp new file mode 100644 index 0000000000..4f73643552 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRasterPipeline.cpp @@ -0,0 +1,358 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRasterPipeline.h" +#include + +SkRasterPipeline::SkRasterPipeline(SkArenaAlloc* alloc) : fAlloc(alloc) { + this->reset(); +} +void SkRasterPipeline::reset() { + fStages = nullptr; + fNumStages = 0; + fSlotsNeeded = 1; // We always need one extra slot for just_return(). +} + +void SkRasterPipeline::append(StockStage stage, void* ctx) { + SkASSERT(stage != uniform_color); // Please use append_constant_color(). + SkASSERT(stage != unbounded_uniform_color); // Please use append_constant_color(). + SkASSERT(stage != set_rgb); // Please use append_set_rgb(). + SkASSERT(stage != unbounded_set_rgb); // Please use append_set_rgb(). + SkASSERT(stage != clamp_gamut); // Please use append_gamut_clamp_if_normalized(). + SkASSERT(stage != parametric); // Please use append_transfer_function(). + SkASSERT(stage != gamma_); // Please use append_transfer_function(). + SkASSERT(stage != PQish); // Please use append_transfer_function(). + SkASSERT(stage != HLGish); // Please use append_transfer_function(). + SkASSERT(stage != HLGinvish); // Please use append_transfer_function(). + this->unchecked_append(stage, ctx); +} +void SkRasterPipeline::unchecked_append(StockStage stage, void* ctx) { + fStages = fAlloc->make( StageList{fStages, stage, ctx} ); + fNumStages += 1; + fSlotsNeeded += ctx ? 2 : 1; +} +void SkRasterPipeline::append(StockStage stage, uintptr_t ctx) { + void* ptrCtx; + memcpy(&ptrCtx, &ctx, sizeof(ctx)); + this->append(stage, ptrCtx); +} + +void SkRasterPipeline::extend(const SkRasterPipeline& src) { + if (src.empty()) { + return; + } + auto stages = fAlloc->makeArrayDefault(src.fNumStages); + + int n = src.fNumStages; + const StageList* st = src.fStages; + while (n --> 1) { + stages[n] = *st; + stages[n].prev = &stages[n-1]; + st = st->prev; + } + stages[0] = *st; + stages[0].prev = fStages; + + fStages = &stages[src.fNumStages - 1]; + fNumStages += src.fNumStages; + fSlotsNeeded += src.fSlotsNeeded - 1; // Don't double count just_returns(). +} + +void SkRasterPipeline::dump() const { + SkDebugf("SkRasterPipeline, %d stages\n", fNumStages); + std::vector stages; + for (auto st = fStages; st; st = st->prev) { + const char* name = ""; + switch (st->stage) { + #define M(x) case x: name = #x; break; + SK_RASTER_PIPELINE_STAGES(M) + #undef M + } + stages.push_back(name); + } + std::reverse(stages.begin(), stages.end()); + for (const char* name : stages) { + SkDebugf("\t%s\n", name); + } + SkDebugf("\n"); +} + +void SkRasterPipeline::append_set_rgb(SkArenaAlloc* alloc, const float rgb[3]) { + auto arg = alloc->makeArrayDefault(3); + arg[0] = rgb[0]; + arg[1] = rgb[1]; + arg[2] = rgb[2]; + + auto stage = unbounded_set_rgb; + if (0 <= rgb[0] && rgb[0] <= 1 && + 0 <= rgb[1] && rgb[1] <= 1 && + 0 <= rgb[2] && rgb[2] <= 1) + { + stage = set_rgb; + } + + this->unchecked_append(stage, arg); +} + +void SkRasterPipeline::append_constant_color(SkArenaAlloc* alloc, const float rgba[4]) { + // r,g,b might be outside [0,1], but alpha should probably always be in [0,1]. + SkASSERT(0 <= rgba[3] && rgba[3] <= 1); + + if (rgba[0] == 0 && rgba[1] == 0 && rgba[2] == 0 && rgba[3] == 1) { + this->append(black_color); + } else if (rgba[0] == 1 && rgba[1] == 1 && rgba[2] == 1 && rgba[3] == 1) { + this->append(white_color); + } else { + auto ctx = alloc->make(); + Sk4f color = Sk4f::Load(rgba); + color.store(&ctx->r); + + // uniform_color requires colors in range and can go lowp, + // while unbounded_uniform_color supports out-of-range colors too but not lowp. + if (0 <= rgba[0] && rgba[0] <= rgba[3] && + 0 <= rgba[1] && rgba[1] <= rgba[3] && + 0 <= rgba[2] && rgba[2] <= rgba[3]) { + // To make loads more direct, we store 8-bit values in 16-bit slots. + color = color * 255.0f + 0.5f; + ctx->rgba[0] = (uint16_t)color[0]; + ctx->rgba[1] = (uint16_t)color[1]; + ctx->rgba[2] = (uint16_t)color[2]; + ctx->rgba[3] = (uint16_t)color[3]; + this->unchecked_append(uniform_color, ctx); + } else { + this->unchecked_append(unbounded_uniform_color, ctx); + } + } +} + +void SkRasterPipeline::append_matrix(SkArenaAlloc* alloc, const SkMatrix& matrix) { + SkMatrix::TypeMask mt = matrix.getType(); + + if (mt == SkMatrix::kIdentity_Mask) { + return; + } + if (mt == SkMatrix::kTranslate_Mask) { + float* trans = alloc->makeArrayDefault(2); + trans[0] = matrix.getTranslateX(); + trans[1] = matrix.getTranslateY(); + this->append(SkRasterPipeline::matrix_translate, trans); + } else if ((mt | (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) == + (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) { + float* scaleTrans = alloc->makeArrayDefault(4); + scaleTrans[0] = matrix.getScaleX(); + scaleTrans[1] = matrix.getScaleY(); + scaleTrans[2] = matrix.getTranslateX(); + scaleTrans[3] = matrix.getTranslateY(); + this->append(SkRasterPipeline::matrix_scale_translate, scaleTrans); + } else { + float* storage = alloc->makeArrayDefault(9); + if (matrix.asAffine(storage)) { + // note: asAffine and the 2x3 stage really only need 6 entries + this->append(SkRasterPipeline::matrix_2x3, storage); + } else { + matrix.get9(storage); + this->append(SkRasterPipeline::matrix_perspective, storage); + } + } +} + +void SkRasterPipeline::append_load(SkColorType ct, const SkRasterPipeline_MemoryCtx* ctx) { + switch (ct) { + case kUnknown_SkColorType: SkASSERT(false); break; + + case kAlpha_8_SkColorType: this->append(load_a8, ctx); break; + case kA16_unorm_SkColorType: this->append(load_a16, ctx); break; + case kA16_float_SkColorType: this->append(load_af16, ctx); break; + case kRGB_565_SkColorType: this->append(load_565, ctx); break; + case kARGB_4444_SkColorType: this->append(load_4444, ctx); break; + case kR8G8_unorm_SkColorType: this->append(load_rg88, ctx); break; + case kR16G16_unorm_SkColorType: this->append(load_rg1616, ctx); break; + case kR16G16_float_SkColorType: this->append(load_rgf16, ctx); break; + case kRGBA_8888_SkColorType: this->append(load_8888, ctx); break; + case kRGBA_1010102_SkColorType: this->append(load_1010102, ctx); break; + case kR16G16B16A16_unorm_SkColorType:this->append(load_16161616,ctx); break; + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: this->append(load_f16, ctx); break; + case kRGBA_F32_SkColorType: this->append(load_f32, ctx); break; + + case kGray_8_SkColorType: this->append(load_a8, ctx); + this->append(alpha_to_gray); + break; + + case kRGB_888x_SkColorType: this->append(load_8888, ctx); + this->append(force_opaque); + break; + + case kRGB_101010x_SkColorType: this->append(load_1010102, ctx); + this->append(force_opaque); + break; + + case kBGRA_8888_SkColorType: this->append(load_8888, ctx); + this->append(swap_rb); + break; + } +} + +void SkRasterPipeline::append_load_dst(SkColorType ct, const SkRasterPipeline_MemoryCtx* ctx) { + switch (ct) { + case kUnknown_SkColorType: SkASSERT(false); break; + + case kAlpha_8_SkColorType: this->append(load_a8_dst, ctx); break; + case kA16_unorm_SkColorType: this->append(load_a16_dst, ctx); break; + case kA16_float_SkColorType: this->append(load_af16_dst, ctx); break; + case kRGB_565_SkColorType: this->append(load_565_dst, ctx); break; + case kARGB_4444_SkColorType: this->append(load_4444_dst, ctx); break; + case kR8G8_unorm_SkColorType: this->append(load_rg88_dst, ctx); break; + case kR16G16_unorm_SkColorType: this->append(load_rg1616_dst, ctx); break; + case kR16G16_float_SkColorType: this->append(load_rgf16_dst, ctx); break; + case kRGBA_8888_SkColorType: this->append(load_8888_dst, ctx); break; + case kRGBA_1010102_SkColorType: this->append(load_1010102_dst, ctx); break; + case kR16G16B16A16_unorm_SkColorType: this->append(load_16161616_dst,ctx); break; + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: this->append(load_f16_dst, ctx); break; + case kRGBA_F32_SkColorType: this->append(load_f32_dst, ctx); break; + + case kGray_8_SkColorType: this->append(load_a8_dst, ctx); + this->append(alpha_to_gray_dst); + break; + + case kRGB_888x_SkColorType: this->append(load_8888_dst, ctx); + this->append(force_opaque_dst); + break; + + case kRGB_101010x_SkColorType: this->append(load_1010102_dst, ctx); + this->append(force_opaque_dst); + break; + + case kBGRA_8888_SkColorType: this->append(load_8888_dst, ctx); + this->append(swap_rb_dst); + break; + } +} + +void SkRasterPipeline::append_store(SkColorType ct, const SkRasterPipeline_MemoryCtx* ctx) { + switch (ct) { + case kUnknown_SkColorType: SkASSERT(false); break; + + case kAlpha_8_SkColorType: this->append(store_a8, ctx); break; + case kA16_unorm_SkColorType: this->append(store_a16, ctx); break; + case kA16_float_SkColorType: this->append(store_af16, ctx); break; + case kRGB_565_SkColorType: this->append(store_565, ctx); break; + case kARGB_4444_SkColorType: this->append(store_4444, ctx); break; + case kR8G8_unorm_SkColorType: this->append(store_rg88, ctx); break; + case kR16G16_unorm_SkColorType: this->append(store_rg1616, ctx); break; + case kR16G16_float_SkColorType: this->append(store_rgf16, ctx); break; + case kRGBA_8888_SkColorType: this->append(store_8888, ctx); break; + case kRGBA_1010102_SkColorType: this->append(store_1010102, ctx); break; + case kR16G16B16A16_unorm_SkColorType: this->append(store_16161616,ctx); break; + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: this->append(store_f16, ctx); break; + case kRGBA_F32_SkColorType: this->append(store_f32, ctx); break; + + case kRGB_888x_SkColorType: this->append(force_opaque); + this->append(store_8888, ctx); + break; + + case kRGB_101010x_SkColorType: this->append(force_opaque); + this->append(store_1010102, ctx); + break; + + case kGray_8_SkColorType: this->append(bt709_luminance_or_luma_to_alpha); + this->append(store_a8, ctx); + break; + + case kBGRA_8888_SkColorType: this->append(swap_rb); + this->append(store_8888, ctx); + break; + } +} + +void SkRasterPipeline::append_transfer_function(const skcms_TransferFunction& tf) { + void* ctx = const_cast(static_cast(&tf)); + switch (classify_transfer_fn(tf)) { + case Bad_TF: SkASSERT(false); break; + + case TFKind::sRGBish_TF: + if (tf.a == 1 && tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) { + this->unchecked_append(gamma_, ctx); + } else { + this->unchecked_append(parametric, ctx); + } + break; + case PQish_TF: this->unchecked_append(PQish, ctx); break; + case HLGish_TF: this->unchecked_append(HLGish, ctx); break; + case HLGinvish_TF: this->unchecked_append(HLGinvish, ctx); break; + } +} + +void SkRasterPipeline::append_gamut_clamp_if_normalized(const SkImageInfo& dstInfo) { + // N.B. we _do_ clamp for kRGBA_F16Norm_SkColorType... because it's normalized. + if (dstInfo.colorType() != kRGBA_F16_SkColorType && + dstInfo.colorType() != kRGBA_F32_SkColorType && + dstInfo.alphaType() == kPremul_SkAlphaType) + { + this->unchecked_append(SkRasterPipeline::clamp_gamut, nullptr); + } +} + +SkRasterPipeline::StartPipelineFn SkRasterPipeline::build_pipeline(void** ip) const { + // We'll try to build a lowp pipeline, but if that fails fallback to a highp float pipeline. + void** reset_point = ip; + + // Stages are stored backwards in fStages, so we reverse here, back to front. + *--ip = (void*)SkOpts::just_return_lowp; + for (const StageList* st = fStages; st; st = st->prev) { + if (auto fn = SkOpts::stages_lowp[st->stage]) { + if (st->ctx) { + *--ip = st->ctx; + } + *--ip = (void*)fn; + } else { + ip = reset_point; + break; + } + } + if (ip != reset_point) { + return SkOpts::start_pipeline_lowp; + } + + *--ip = (void*)SkOpts::just_return_highp; + for (const StageList* st = fStages; st; st = st->prev) { + if (st->ctx) { + *--ip = st->ctx; + } + *--ip = (void*)SkOpts::stages_highp[st->stage]; + } + return SkOpts::start_pipeline_highp; +} + +void SkRasterPipeline::run(size_t x, size_t y, size_t w, size_t h) const { + if (this->empty()) { + return; + } + + // Best to not use fAlloc here... we can't bound how often run() will be called. + SkAutoSTMalloc<64, void*> program(fSlotsNeeded); + + auto start_pipeline = this->build_pipeline(program.get() + fSlotsNeeded); + start_pipeline(x,y,x+w,y+h, program.get()); +} + +std::function SkRasterPipeline::compile() const { + if (this->empty()) { + return [](size_t, size_t, size_t, size_t) {}; + } + + void** program = fAlloc->makeArray(fSlotsNeeded); + + auto start_pipeline = this->build_pipeline(program + fSlotsNeeded); + return [=](size_t x, size_t y, size_t w, size_t h) { + start_pipeline(x,y,x+w,y+h, program); + }; +} diff --git a/gfx/skia/skia/src/core/SkRasterPipeline.h b/gfx/skia/skia/src/core/SkRasterPipeline.h new file mode 100644 index 0000000000..d13a8f0a60 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRasterPipeline.h @@ -0,0 +1,298 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRasterPipeline_DEFINED +#define SkRasterPipeline_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTileMode.h" +#include "include/core/SkTypes.h" +#include "include/private/SkNx.h" +#include "include/private/SkTArray.h" +#include "src/core/SkArenaAlloc.h" +#include +#include // TODO: unused + +/** + * SkRasterPipeline provides a cheap way to chain together a pixel processing pipeline. + * + * It's particularly designed for situations where the potential pipeline is extremely + * combinatoric: {N dst formats} x {M source formats} x {K mask formats} x {C transfer modes} ... + * No one wants to write specialized routines for all those combinations, and if we did, we'd + * end up bloating our code size dramatically. SkRasterPipeline stages can be chained together + * at runtime, so we can scale this problem linearly rather than combinatorically. + * + * Each stage is represented by a function conforming to a common interface and by an + * arbitrary context pointer. The stage funciton arguments and calling convention are + * designed to maximize the amount of data we can pass along the pipeline cheaply, and + * vary depending on CPU feature detection. + */ + +#define SK_RASTER_PIPELINE_STAGES(M) \ + M(callback) M(interpreter) \ + M(move_src_dst) M(move_dst_src) \ + M(clamp_0) M(clamp_1) M(clamp_a) M(clamp_gamut) \ + M(unpremul) M(premul) M(premul_dst) \ + M(force_opaque) M(force_opaque_dst) \ + M(set_rgb) M(unbounded_set_rgb) M(swap_rb) M(swap_rb_dst) \ + M(from_srgb) M(to_srgb) \ + M(black_color) M(white_color) \ + M(uniform_color) M(unbounded_uniform_color) M(uniform_color_dst) \ + M(seed_shader) M(dither) \ + M(load_a8) M(load_a8_dst) M(store_a8) M(gather_a8) \ + M(load_565) M(load_565_dst) M(store_565) M(gather_565) \ + M(load_4444) M(load_4444_dst) M(store_4444) M(gather_4444) \ + M(load_f16) M(load_f16_dst) M(store_f16) M(gather_f16) \ + M(load_af16) M(load_af16_dst) M(store_af16) M(gather_af16) \ + M(load_rgf16) M(load_rgf16_dst) M(store_rgf16) M(gather_rgf16) \ + M(load_f32) M(load_f32_dst) M(store_f32) M(gather_f32) \ + M(load_rgf32) M(store_rgf32) \ + M(load_8888) M(load_8888_dst) M(store_8888) M(gather_8888) \ + M(load_rg88) M(load_rg88_dst) M(store_rg88) M(gather_rg88) \ + M(load_a16) M(load_a16_dst) M(store_a16) M(gather_a16) \ + M(load_rg1616) M(load_rg1616_dst) M(store_rg1616) M(gather_rg1616) \ + M(load_16161616) M(load_16161616_dst) M(store_16161616) M(gather_16161616) \ + M(load_1010102) M(load_1010102_dst) M(store_1010102) M(gather_1010102) \ + M(alpha_to_gray) M(alpha_to_gray_dst) M(bt709_luminance_or_luma_to_alpha) \ + M(bilerp_clamp_8888) M(bicubic_clamp_8888) \ + M(store_u16_be) \ + M(load_src) M(store_src) M(load_dst) M(store_dst) \ + M(scale_u8) M(scale_565) M(scale_1_float) \ + M( lerp_u8) M( lerp_565) M( lerp_1_float) M(lerp_native) \ + M(dstatop) M(dstin) M(dstout) M(dstover) \ + M(srcatop) M(srcin) M(srcout) M(srcover) \ + M(clear) M(modulate) M(multiply) M(plus_) M(screen) M(xor_) \ + M(colorburn) M(colordodge) M(darken) M(difference) \ + M(exclusion) M(hardlight) M(lighten) M(overlay) M(softlight) \ + M(hue) M(saturation) M(color) M(luminosity) \ + M(srcover_rgba_8888) \ + M(matrix_translate) M(matrix_scale_translate) \ + M(matrix_2x3) M(matrix_3x3) M(matrix_3x4) M(matrix_4x5) M(matrix_4x3) \ + M(matrix_perspective) \ + M(parametric) M(gamma_) M(PQish) M(HLGish) M(HLGinvish) \ + M(mirror_x) M(repeat_x) \ + M(mirror_y) M(repeat_y) \ + M(decal_x) M(decal_y) M(decal_x_and_y) \ + M(check_decal_mask) \ + M(negate_x) \ + M(bilinear) M(bicubic) \ + M(bilinear_nx) M(bilinear_px) M(bilinear_ny) M(bilinear_py) \ + M(bicubic_n3x) M(bicubic_n1x) M(bicubic_p1x) M(bicubic_p3x) \ + M(bicubic_n3y) M(bicubic_n1y) M(bicubic_p1y) M(bicubic_p3y) \ + M(save_xy) M(accumulate) \ + M(clamp_x_1) M(mirror_x_1) M(repeat_x_1) \ + M(evenly_spaced_gradient) \ + M(gradient) \ + M(evenly_spaced_2_stop_gradient) \ + M(xy_to_unit_angle) \ + M(xy_to_radius) \ + M(xy_to_2pt_conical_strip) \ + M(xy_to_2pt_conical_focal_on_circle) \ + M(xy_to_2pt_conical_well_behaved) \ + M(xy_to_2pt_conical_smaller) \ + M(xy_to_2pt_conical_greater) \ + M(alter_2pt_conical_compensate_focal) \ + M(alter_2pt_conical_unswap) \ + M(mask_2pt_conical_nan) \ + M(mask_2pt_conical_degenerates) M(apply_vector_mask) \ + M(byte_tables) \ + M(rgb_to_hsl) M(hsl_to_rgb) \ + M(gauss_a_to_rgba) \ + M(emboss) \ + M(swizzle) + +// The largest number of pixels we handle at a time. +static const int SkRasterPipeline_kMaxStride = 16; + +// Structs representing the arguments to some common stages. + +struct SkRasterPipeline_MemoryCtx { + void* pixels; + int stride; +}; + +struct SkRasterPipeline_GatherCtx { + const void* pixels; + int stride; + float width; + float height; +}; + +// State shared by save_xy, accumulate, and bilinear_* / bicubic_*. +struct SkRasterPipeline_SamplerCtx { + float x[SkRasterPipeline_kMaxStride]; + float y[SkRasterPipeline_kMaxStride]; + float fx[SkRasterPipeline_kMaxStride]; + float fy[SkRasterPipeline_kMaxStride]; + float scalex[SkRasterPipeline_kMaxStride]; + float scaley[SkRasterPipeline_kMaxStride]; +}; + +struct SkRasterPipeline_TileCtx { + float scale; + float invScale; // cache of 1/scale +}; + +struct SkRasterPipeline_DecalTileCtx { + uint32_t mask[SkRasterPipeline_kMaxStride]; + float limit_x; + float limit_y; +}; + +struct SkRasterPipeline_SamplerCtx2 : public SkRasterPipeline_GatherCtx { + SkColorType ct; + SkTileMode tileX, tileY; + float invWidth, invHeight; +}; + +struct SkRasterPipeline_CallbackCtx { + void (*fn)(SkRasterPipeline_CallbackCtx* self, int active_pixels/*<= SkRasterPipeline_kMaxStride*/); + + // When called, fn() will have our active pixels available in rgba. + // When fn() returns, the pipeline will read back those active pixels from read_from. + float rgba[4*SkRasterPipeline_kMaxStride]; + float* read_from = rgba; +}; + +namespace SkSL { +class ByteCode; +class ByteCodeFunction; +} + +struct SkRasterPipeline_InterpreterCtx { + const SkSL::ByteCode* byteCode; + const SkSL::ByteCodeFunction* fn; + + SkColor4f paintColor; + const void* inputs; + int ninputs; + bool shaderConvention; // if false, we're a colorfilter +}; + +struct SkRasterPipeline_GradientCtx { + size_t stopCount; + float* fs[4]; + float* bs[4]; + float* ts; + bool interpolatedInPremul; +}; + +struct SkRasterPipeline_EvenlySpaced2StopGradientCtx { + float f[4]; + float b[4]; + bool interpolatedInPremul; +}; + +struct SkRasterPipeline_2PtConicalCtx { + uint32_t fMask[SkRasterPipeline_kMaxStride]; + float fP0, + fP1; +}; + +struct SkRasterPipeline_UniformColorCtx { + float r,g,b,a; + uint16_t rgba[4]; // [0,255] in a 16-bit lane. +}; + +struct SkRasterPipeline_EmbossCtx { + SkRasterPipeline_MemoryCtx mul, + add; +}; + +class SkRasterPipeline { +public: + explicit SkRasterPipeline(SkArenaAlloc*); + + SkRasterPipeline(const SkRasterPipeline&) = delete; + SkRasterPipeline(SkRasterPipeline&&) = default; + + SkRasterPipeline& operator=(const SkRasterPipeline&) = delete; + SkRasterPipeline& operator=(SkRasterPipeline&&) = default; + + void reset(); + + enum StockStage { + #define M(stage) stage, + SK_RASTER_PIPELINE_STAGES(M) + #undef M + }; + void append(StockStage, void* = nullptr); + void append(StockStage stage, const void* ctx) { this->append(stage, const_cast(ctx)); } + void append(StockStage, uintptr_t ctx); + + // Append all stages to this pipeline. + void extend(const SkRasterPipeline&); + + // Runs the pipeline in 2d from (x,y) inclusive to (x+w,y+h) exclusive. + void run(size_t x, size_t y, size_t w, size_t h) const; + + // Allocates a thunk which amortizes run() setup cost in alloc. + std::function compile() const; + + void dump() const; + + // Appends a stage for the specified matrix. + // Tries to optimize the stage by analyzing the type of matrix. + void append_matrix(SkArenaAlloc*, const SkMatrix&); + + // Appends a stage for a constant uniform color. + // Tries to optimize the stage based on the color. + void append_constant_color(SkArenaAlloc*, const float rgba[4]); + + void append_constant_color(SkArenaAlloc* alloc, const SkColor4f& color) { + this->append_constant_color(alloc, color.vec()); + } + + // Like append_constant_color() but only affecting r,g,b, ignoring the alpha channel. + void append_set_rgb(SkArenaAlloc*, const float rgb[3]); + + void append_set_rgb(SkArenaAlloc* alloc, const SkColor4f& color) { + this->append_set_rgb(alloc, color.vec()); + } + + void append_load (SkColorType, const SkRasterPipeline_MemoryCtx*); + void append_load_dst(SkColorType, const SkRasterPipeline_MemoryCtx*); + void append_store (SkColorType, const SkRasterPipeline_MemoryCtx*); + + void append_gamut_clamp_if_normalized(const SkImageInfo&); + + void append_transfer_function(const skcms_TransferFunction&); + + bool empty() const { return fStages == nullptr; } + +private: + struct StageList { + StageList* prev; + StockStage stage; + void* ctx; + }; + + using StartPipelineFn = void(*)(size_t,size_t,size_t,size_t, void** program); + StartPipelineFn build_pipeline(void**) const; + + void unchecked_append(StockStage, void*); + + // Used by old single-program void** style execution. + SkArenaAlloc* fAlloc; + StageList* fStages; + int fNumStages; + int fSlotsNeeded; +}; + +template +class SkRasterPipeline_ : public SkRasterPipeline { +public: + SkRasterPipeline_() + : SkRasterPipeline(&fBuiltinAlloc) {} + +private: + SkSTArenaAlloc fBuiltinAlloc; +}; + + +#endif//SkRasterPipeline_DEFINED diff --git a/gfx/skia/skia/src/core/SkRasterPipelineBlitter.cpp b/gfx/skia/skia/src/core/SkRasterPipelineBlitter.cpp new file mode 100644 index 0000000000..b983d3b70d --- /dev/null +++ b/gfx/skia/skia/src/core/SkRasterPipelineBlitter.cpp @@ -0,0 +1,472 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColor.h" +#include "include/core/SkColorFilter.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" +#include "include/private/SkTo.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkUtils.h" +#include "src/shaders/SkShaderBase.h" + +class SkRasterPipelineBlitter final : public SkBlitter { +public: + // This is our common entrypoint for creating the blitter once we've sorted out shaders. + static SkBlitter* Create(const SkPixmap&, const SkPaint&, SkArenaAlloc*, + const SkRasterPipeline& shaderPipeline, + bool is_opaque, bool is_constant); + + SkRasterPipelineBlitter(SkPixmap dst, + SkBlendMode blend, + SkArenaAlloc* alloc) + : fDst(dst) + , fBlend(blend) + , fAlloc(alloc) + , fColorPipeline(alloc) + {} + + void blitH (int x, int y, int w) override; + void blitAntiH (int x, int y, const SkAlpha[], const int16_t[]) override; + void blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) override; + void blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) override; + void blitMask (const SkMask&, const SkIRect& clip) override; + void blitRect (int x, int y, int width, int height) override; + void blitV (int x, int y, int height, SkAlpha alpha) override; + +private: + void append_load_dst (SkRasterPipeline*) const; + void append_store (SkRasterPipeline*) const; + + SkPixmap fDst; + SkBlendMode fBlend; + SkArenaAlloc* fAlloc; + SkRasterPipeline fColorPipeline; + + SkRasterPipeline_MemoryCtx + fDstPtr = {nullptr,0}, // Always points to the top-left of fDst. + fMaskPtr = {nullptr,0}; // Updated each call to blitMask(). + SkRasterPipeline_EmbossCtx fEmbossCtx; // Used only for k3D_Format masks. + + // We may be able to specialize blitH() or blitRect() into a memset. + void (*fMemset2D)(SkPixmap*, int x,int y, int w,int h, uint64_t color) = nullptr; + uint64_t fMemsetColor = 0; // Big enough for largest memsettable dst format, F16. + + // Built lazily on first use. + std::function fBlitRect, + fBlitAntiH, + fBlitMaskA8, + fBlitMaskLCD16, + fBlitMask3D; + + // These values are pointed to by the blit pipelines above, + // which allows us to adjust them from call to call. + float fCurrentCoverage = 0.0f; + float fDitherRate = 0.0f; + + typedef SkBlitter INHERITED; +}; + +SkBlitter* SkCreateRasterPipelineBlitter(const SkPixmap& dst, + const SkPaint& paint, + const SkMatrix& ctm, + SkArenaAlloc* alloc) { + // For legacy to keep working, we need to sometimes still distinguish null dstCS from sRGB. +#if 0 + SkColorSpace* dstCS = dst.colorSpace() ? dst.colorSpace() + : sk_srgb_singleton(); +#else + SkColorSpace* dstCS = dst.colorSpace(); +#endif + SkColorType dstCT = dst.colorType(); + SkColor4f paintColor = paint.getColor4f(); + SkColorSpaceXformSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType, + dstCS, kUnpremul_SkAlphaType).apply(paintColor.vec()); + + auto shader = as_SB(paint.getShader()); + + SkRasterPipeline_<256> shaderPipeline; + if (!shader) { + // Having no shader makes things nice and easy... just use the paint color. + shaderPipeline.append_constant_color(alloc, paintColor.premul().vec()); + bool is_opaque = paintColor.fA == 1.0f, + is_constant = true; + return SkRasterPipelineBlitter::Create(dst, paint, alloc, + shaderPipeline, is_opaque, is_constant); + } + + bool is_opaque = shader->isOpaque() && paintColor.fA == 1.0f; + bool is_constant = shader->isConstant(); + + if (shader->appendStages({&shaderPipeline, alloc, dstCT, dstCS, paint, nullptr, ctm})) { + if (paintColor.fA != 1.0f) { + shaderPipeline.append(SkRasterPipeline::scale_1_float, + alloc->make(paintColor.fA)); + } + return SkRasterPipelineBlitter::Create(dst, paint, alloc, + shaderPipeline, is_opaque, is_constant); + } + + // The shader has opted out of drawing anything. + return alloc->make(); +} + +SkBlitter* SkCreateRasterPipelineBlitter(const SkPixmap& dst, + const SkPaint& paint, + const SkRasterPipeline& shaderPipeline, + bool is_opaque, + SkArenaAlloc* alloc) { + bool is_constant = false; // If this were the case, it'd be better to just set a paint color. + return SkRasterPipelineBlitter::Create(dst, paint, alloc, + shaderPipeline, is_opaque, is_constant); +} + +SkBlitter* SkRasterPipelineBlitter::Create(const SkPixmap& dst, + const SkPaint& paint, + SkArenaAlloc* alloc, + const SkRasterPipeline& shaderPipeline, + bool is_opaque, + bool is_constant) { + auto blitter = alloc->make(dst, + paint.getBlendMode(), + alloc); + + // Our job in this factory is to fill out the blitter's color pipeline. + // This is the common front of the full blit pipelines, each constructed lazily on first use. + // The full blit pipelines handle reading and writing the dst, blending, coverage, dithering. + auto colorPipeline = &blitter->fColorPipeline; + + // Let's get the shader in first. + colorPipeline->extend(shaderPipeline); + + // If there's a color filter it comes next. + if (auto colorFilter = paint.getColorFilter()) { + SkStageRec rec = { + colorPipeline, alloc, dst.colorType(), dst.colorSpace(), paint, nullptr, SkMatrix::I() + }; + colorFilter->appendStages(rec, is_opaque); + is_opaque = is_opaque && (colorFilter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag); + } + + // Not all formats make sense to dither (think, F16). We set their dither rate + // to zero. We need to decide if we're going to dither now to keep is_constant accurate. + if (paint.isDither()) { + switch (dst.info().colorType()) { + default: blitter->fDitherRate = 0.0f; break; + case kARGB_4444_SkColorType: blitter->fDitherRate = 1/15.0f; break; + case kRGB_565_SkColorType: blitter->fDitherRate = 1/63.0f; break; + case kGray_8_SkColorType: + case kRGB_888x_SkColorType: + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: blitter->fDitherRate = 1/255.0f; break; + case kRGB_101010x_SkColorType: + case kRGBA_1010102_SkColorType: blitter->fDitherRate = 1/1023.0f; break; + } + // TODO: for constant colors, we could try to measure the effect of dithering, and if + // it has no value (i.e. all variations result in the same 32bit color, then we + // could disable it (for speed, by not adding the stage). + } + is_constant = is_constant && (blitter->fDitherRate == 0.0f); + + // We're logically done here. The code between here and return blitter is all optimization. + + // A pipeline that's still constant here can collapse back into a constant color. + if (is_constant) { + SkColor4f constantColor; + SkRasterPipeline_MemoryCtx constantColorPtr = { &constantColor, 0 }; + colorPipeline->append_gamut_clamp_if_normalized(dst.info()); + colorPipeline->append(SkRasterPipeline::store_f32, &constantColorPtr); + colorPipeline->run(0,0,1,1); + colorPipeline->reset(); + colorPipeline->append_constant_color(alloc, constantColor); + + is_opaque = constantColor.fA == 1.0f; + } + + // We can strength-reduce SrcOver into Src when opaque. + if (is_opaque && blitter->fBlend == SkBlendMode::kSrcOver) { + blitter->fBlend = SkBlendMode::kSrc; + } + + // When we're drawing a constant color in Src mode, we can sometimes just memset. + // (The previous two optimizations help find more opportunities for this one.) + if (is_constant && blitter->fBlend == SkBlendMode::kSrc) { + // Run our color pipeline all the way through to produce what we'd memset when we can. + // Not all blits can memset, so we need to keep colorPipeline too. + SkRasterPipeline_<256> p; + p.extend(*colorPipeline); + p.append_gamut_clamp_if_normalized(dst.info()); + blitter->fDstPtr = SkRasterPipeline_MemoryCtx{&blitter->fMemsetColor, 0}; + blitter->append_store(&p); + p.run(0,0,1,1); + + switch (blitter->fDst.shiftPerPixel()) { + case 0: blitter->fMemset2D = [](SkPixmap* dst, int x,int y, int w,int h, uint64_t c) { + void* p = dst->writable_addr(x,y); + while (h --> 0) { + memset(p, c, w); + p = SkTAddOffset(p, dst->rowBytes()); + } + }; break; + + case 1: blitter->fMemset2D = [](SkPixmap* dst, int x,int y, int w,int h, uint64_t c) { + SkOpts::rect_memset16(dst->writable_addr16(x,y), c, w, dst->rowBytes(), h); + }; break; + + case 2: blitter->fMemset2D = [](SkPixmap* dst, int x,int y, int w,int h, uint64_t c) { + SkOpts::rect_memset32(dst->writable_addr32(x,y), c, w, dst->rowBytes(), h); + }; break; + + case 3: blitter->fMemset2D = [](SkPixmap* dst, int x,int y, int w,int h, uint64_t c) { + SkOpts::rect_memset64(dst->writable_addr64(x,y), c, w, dst->rowBytes(), h); + }; break; + + // TODO(F32)? + } + } + + blitter->fDstPtr = SkRasterPipeline_MemoryCtx{ + blitter->fDst.writable_addr(), + blitter->fDst.rowBytesAsPixels(), + }; + + return blitter; +} + +void SkRasterPipelineBlitter::append_load_dst(SkRasterPipeline* p) const { + p->append_load_dst(fDst.info().colorType(), &fDstPtr); + if (fDst.info().alphaType() == kUnpremul_SkAlphaType) { + p->append(SkRasterPipeline::premul_dst); + } +} + +void SkRasterPipelineBlitter::append_store(SkRasterPipeline* p) const { + if (fDst.info().alphaType() == kUnpremul_SkAlphaType) { + p->append(SkRasterPipeline::unpremul); + } + if (fDitherRate > 0.0f) { + p->append(SkRasterPipeline::dither, &fDitherRate); + } + + p->append_store(fDst.info().colorType(), &fDstPtr); +} + +void SkRasterPipelineBlitter::blitH(int x, int y, int w) { + this->blitRect(x,y,w,1); +} + +void SkRasterPipelineBlitter::blitRect(int x, int y, int w, int h) { + if (fMemset2D) { + fMemset2D(&fDst, x,y, w,h, fMemsetColor); + return; + } + + if (!fBlitRect) { + SkRasterPipeline p(fAlloc); + p.extend(fColorPipeline); + p.append_gamut_clamp_if_normalized(fDst.info()); + if (fBlend == SkBlendMode::kSrcOver + && (fDst.info().colorType() == kRGBA_8888_SkColorType || + fDst.info().colorType() == kBGRA_8888_SkColorType) + && !fDst.colorSpace() + && fDst.info().alphaType() != kUnpremul_SkAlphaType + && fDitherRate == 0.0f) { + if (fDst.info().colorType() == kBGRA_8888_SkColorType) { + p.append(SkRasterPipeline::swap_rb); + } + p.append(SkRasterPipeline::srcover_rgba_8888, &fDstPtr); + } else { + if (fBlend != SkBlendMode::kSrc) { + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + } + this->append_store(&p); + } + fBlitRect = p.compile(); + } + + fBlitRect(x,y,w,h); +} + +void SkRasterPipelineBlitter::blitAntiH(int x, int y, const SkAlpha aa[], const int16_t runs[]) { + if (!fBlitAntiH) { + SkRasterPipeline p(fAlloc); + p.extend(fColorPipeline); + p.append_gamut_clamp_if_normalized(fDst.info()); + if (SkBlendMode_ShouldPreScaleCoverage(fBlend, /*rgb_coverage=*/false)) { + p.append(SkRasterPipeline::scale_1_float, &fCurrentCoverage); + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + } else { + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + p.append(SkRasterPipeline::lerp_1_float, &fCurrentCoverage); + } + + this->append_store(&p); + fBlitAntiH = p.compile(); + } + + for (int16_t run = *runs; run > 0; run = *runs) { + switch (*aa) { + case 0x00: break; + case 0xff: this->blitH(x,y,run); break; + default: + fCurrentCoverage = *aa * (1/255.0f); + fBlitAntiH(x,y,run,1); + } + x += run; + runs += run; + aa += run; + } +} + +void SkRasterPipelineBlitter::blitAntiH2(int x, int y, U8CPU a0, U8CPU a1) { + SkIRect clip = {x,y, x+2,y+1}; + uint8_t coverage[] = { (uint8_t)a0, (uint8_t)a1 }; + + SkMask mask; + mask.fImage = coverage; + mask.fBounds = clip; + mask.fRowBytes = 2; + mask.fFormat = SkMask::kA8_Format; + + this->blitMask(mask, clip); +} + +void SkRasterPipelineBlitter::blitAntiV2(int x, int y, U8CPU a0, U8CPU a1) { + SkIRect clip = {x,y, x+1,y+2}; + uint8_t coverage[] = { (uint8_t)a0, (uint8_t)a1 }; + + SkMask mask; + mask.fImage = coverage; + mask.fBounds = clip; + mask.fRowBytes = 1; + mask.fFormat = SkMask::kA8_Format; + + this->blitMask(mask, clip); +} + +void SkRasterPipelineBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + SkIRect clip = {x,y, x+1,y+height}; + + SkMask mask; + mask.fImage = α + mask.fBounds = clip; + mask.fRowBytes = 0; // so we reuse the 1 "row" for all of height + mask.fFormat = SkMask::kA8_Format; + + this->blitMask(mask, clip); +} + +void SkRasterPipelineBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { + if (mask.fFormat == SkMask::kBW_Format) { + // TODO: native BW masks? + return INHERITED::blitMask(mask, clip); + } + + // ARGB and SDF masks shouldn't make it here. + SkASSERT(mask.fFormat == SkMask::kA8_Format + || mask.fFormat == SkMask::kLCD16_Format + || mask.fFormat == SkMask::k3D_Format); + + auto extract_mask_plane = [&mask](int plane, SkRasterPipeline_MemoryCtx* ctx) { + // LCD is 16-bit per pixel; A8 and 3D are 8-bit per pixel. + size_t bpp = mask.fFormat == SkMask::kLCD16_Format ? 2 : 1; + + // Select the right mask plane. Usually plane == 0 and this is just mask.fImage. + auto ptr = (uintptr_t)mask.fImage + + plane * mask.computeImageSize(); + + // Update ctx to point "into" this current mask, but lined up with fDstPtr at (0,0). + // This sort of trickery upsets UBSAN (pointer-overflow) so our ptr must be a uintptr_t. + // mask.fRowBytes is a uint32_t, which would break our addressing math on 64-bit builds. + size_t rowBytes = mask.fRowBytes; + ctx->stride = rowBytes / bpp; + ctx->pixels = (void*)(ptr - mask.fBounds.left() * bpp + - mask.fBounds.top() * rowBytes); + }; + + extract_mask_plane(0, &fMaskPtr); + if (mask.fFormat == SkMask::k3D_Format) { + extract_mask_plane(1, &fEmbossCtx.mul); + extract_mask_plane(2, &fEmbossCtx.add); + } + + // Lazily build whichever pipeline we need, specialized for each mask format. + if (mask.fFormat == SkMask::kA8_Format && !fBlitMaskA8) { + SkRasterPipeline p(fAlloc); + p.extend(fColorPipeline); + p.append_gamut_clamp_if_normalized(fDst.info()); + if (SkBlendMode_ShouldPreScaleCoverage(fBlend, /*rgb_coverage=*/false)) { + p.append(SkRasterPipeline::scale_u8, &fMaskPtr); + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + } else { + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + p.append(SkRasterPipeline::lerp_u8, &fMaskPtr); + } + this->append_store(&p); + fBlitMaskA8 = p.compile(); + } + if (mask.fFormat == SkMask::kLCD16_Format && !fBlitMaskLCD16) { + SkRasterPipeline p(fAlloc); + p.extend(fColorPipeline); + p.append_gamut_clamp_if_normalized(fDst.info()); + if (SkBlendMode_ShouldPreScaleCoverage(fBlend, /*rgb_coverage=*/true)) { + // Somewhat unusually, scale_565 needs dst loaded first. + this->append_load_dst(&p); + p.append(SkRasterPipeline::scale_565, &fMaskPtr); + SkBlendMode_AppendStages(fBlend, &p); + } else { + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + p.append(SkRasterPipeline::lerp_565, &fMaskPtr); + } + this->append_store(&p); + fBlitMaskLCD16 = p.compile(); + } + if (mask.fFormat == SkMask::k3D_Format && !fBlitMask3D) { + SkRasterPipeline p(fAlloc); + p.extend(fColorPipeline); + // This bit is where we differ from kA8_Format: + p.append(SkRasterPipeline::emboss, &fEmbossCtx); + // Now onward just as kA8. + p.append_gamut_clamp_if_normalized(fDst.info()); + if (SkBlendMode_ShouldPreScaleCoverage(fBlend, /*rgb_coverage=*/false)) { + p.append(SkRasterPipeline::scale_u8, &fMaskPtr); + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + } else { + this->append_load_dst(&p); + SkBlendMode_AppendStages(fBlend, &p); + p.append(SkRasterPipeline::lerp_u8, &fMaskPtr); + } + this->append_store(&p); + fBlitMask3D = p.compile(); + } + + std::function* blitter = nullptr; + switch (mask.fFormat) { + case SkMask::kA8_Format: blitter = &fBlitMaskA8; break; + case SkMask::kLCD16_Format: blitter = &fBlitMaskLCD16; break; + case SkMask::k3D_Format: blitter = &fBlitMask3D; break; + default: + SkASSERT(false); + return; + } + + SkASSERT(blitter); + (*blitter)(clip.left(),clip.top(), clip.width(),clip.height()); +} diff --git a/gfx/skia/skia/src/core/SkReadBuffer.cpp b/gfx/skia/skia/src/core/SkReadBuffer.cpp new file mode 100644 index 0000000000..ff78b74a34 --- /dev/null +++ b/gfx/skia/skia/src/core/SkReadBuffer.cpp @@ -0,0 +1,465 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageGenerator.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSafeMath.h" + +#ifndef SK_DISABLE_READBUFFER + +namespace { + // This generator intentionally should always fail on all attempts to get its pixels, + // simulating a bad or empty codec stream. + class EmptyImageGenerator final : public SkImageGenerator { + public: + EmptyImageGenerator(const SkImageInfo& info) : INHERITED(info) { } + + private: + typedef SkImageGenerator INHERITED; + }; + + static sk_sp MakeEmptyImage(int width, int height) { + return SkImage::MakeFromGenerator( + skstd::make_unique(SkImageInfo::MakeN32Premul(width, height))); + } + +} // anonymous namespace + + +SkReadBuffer::SkReadBuffer() { + fVersion = 0; + + fTFArray = nullptr; + fTFCount = 0; + + fFactoryArray = nullptr; + fFactoryCount = 0; +} + +SkReadBuffer::SkReadBuffer(const void* data, size_t size) { + fVersion = 0; + this->setMemory(data, size); + + fTFArray = nullptr; + fTFCount = 0; + + fFactoryArray = nullptr; + fFactoryCount = 0; +} + +void SkReadBuffer::setMemory(const void* data, size_t size) { + this->validate(IsPtrAlign4(data) && (SkAlign4(size) == size)); + if (!fError) { + fReader.setMemory(data, size); + } +} +void SkReadBuffer::setInvalid() { + if (!fError) { + // When an error is found, send the read cursor to the end of the stream + fReader.skip(fReader.available()); + fError = true; + } +} + +const void* SkReadBuffer::skip(size_t size) { + size_t inc = SkAlign4(size); + this->validate(inc >= size); + const void* addr = fReader.peek(); + this->validate(IsPtrAlign4(addr) && fReader.isAvailable(inc)); + if (fError) { + return nullptr; + } + + fReader.skip(size); + return addr; +} + +const void* SkReadBuffer::skip(size_t count, size_t size) { + return this->skip(SkSafeMath::Mul(count, size)); +} + +void SkReadBuffer::setDeserialProcs(const SkDeserialProcs& procs) { + fProcs = procs; +} + +bool SkReadBuffer::readBool() { + uint32_t value = this->readUInt(); + // Boolean value should be either 0 or 1 + this->validate(!(value & ~1)); + return value != 0; +} + +SkColor SkReadBuffer::readColor() { + return this->readUInt(); +} + +int32_t SkReadBuffer::readInt() { + const size_t inc = sizeof(int32_t); + this->validate(IsPtrAlign4(fReader.peek()) && fReader.isAvailable(inc)); + return fError ? 0 : fReader.readInt(); +} + +SkScalar SkReadBuffer::readScalar() { + const size_t inc = sizeof(SkScalar); + this->validate(IsPtrAlign4(fReader.peek()) && fReader.isAvailable(inc)); + return fError ? 0 : fReader.readScalar(); +} + +uint32_t SkReadBuffer::readUInt() { + return this->readInt(); +} + +int32_t SkReadBuffer::read32() { + return this->readInt(); +} + +uint8_t SkReadBuffer::peekByte() { + if (fReader.available() <= 0) { + fError = true; + return 0; + } + return *((uint8_t*) fReader.peek()); +} + +bool SkReadBuffer::readPad32(void* buffer, size_t bytes) { + if (const void* src = this->skip(bytes)) { + memcpy(buffer, src, bytes); + return true; + } + return false; +} + +const char* SkReadBuffer::readString(size_t* len) { + *len = this->readUInt(); + + // The string is len characters and a terminating \0. + const char* c_str = this->skipT(*len+1); + + if (this->validate(c_str && c_str[*len] == '\0')) { + return c_str; + } + return nullptr; +} + +void SkReadBuffer::readString(SkString* string) { + size_t len; + if (const char* c_str = this->readString(&len)) { + string->set(c_str, len); + return; + } + string->reset(); +} + +void SkReadBuffer::readColor4f(SkColor4f* color) { + if (!this->readPad32(color, sizeof(SkColor4f))) { + *color = {0, 0, 0, 0}; + } +} + +void SkReadBuffer::readPoint(SkPoint* point) { + point->fX = this->readScalar(); + point->fY = this->readScalar(); +} + +void SkReadBuffer::readPoint3(SkPoint3* point) { + this->readPad32(point, sizeof(SkPoint3)); +} + +void SkReadBuffer::readMatrix(SkMatrix* matrix) { + size_t size = 0; + if (this->isValid()) { + size = SkMatrixPriv::ReadFromMemory(matrix, fReader.peek(), fReader.available()); + (void)this->validate((SkAlign4(size) == size) && (0 != size)); + } + if (!this->isValid()) { + matrix->reset(); + } + (void)this->skip(size); +} + +void SkReadBuffer::readIRect(SkIRect* rect) { + if (!this->readPad32(rect, sizeof(SkIRect))) { + rect->setEmpty(); + } +} + +void SkReadBuffer::readRect(SkRect* rect) { + if (!this->readPad32(rect, sizeof(SkRect))) { + rect->setEmpty(); + } +} + +void SkReadBuffer::readRRect(SkRRect* rrect) { + if (!this->validate(fReader.readRRect(rrect))) { + rrect->setEmpty(); + } +} + +void SkReadBuffer::readRegion(SkRegion* region) { + size_t size = 0; + if (!fError) { + size = region->readFromMemory(fReader.peek(), fReader.available()); + if (!this->validate((SkAlign4(size) == size) && (0 != size))) { + region->setEmpty(); + } + } + (void)this->skip(size); +} + +void SkReadBuffer::readPath(SkPath* path) { + size_t size = 0; + if (!fError) { + size = path->readFromMemory(fReader.peek(), fReader.available()); + if (!this->validate((SkAlign4(size) == size) && (0 != size))) { + path->reset(); + } + } + (void)this->skip(size); +} + +bool SkReadBuffer::readArray(void* value, size_t size, size_t elementSize) { + const uint32_t count = this->readUInt(); + return this->validate(size == count) && + this->readPad32(value, SkSafeMath::Mul(size, elementSize)); +} + +bool SkReadBuffer::readByteArray(void* value, size_t size) { + return this->readArray(value, size, sizeof(uint8_t)); +} + +bool SkReadBuffer::readColorArray(SkColor* colors, size_t size) { + return this->readArray(colors, size, sizeof(SkColor)); +} + +bool SkReadBuffer::readColor4fArray(SkColor4f* colors, size_t size) { + return this->readArray(colors, size, sizeof(SkColor4f)); +} + +bool SkReadBuffer::readIntArray(int32_t* values, size_t size) { + return this->readArray(values, size, sizeof(int32_t)); +} + +bool SkReadBuffer::readPointArray(SkPoint* points, size_t size) { + return this->readArray(points, size, sizeof(SkPoint)); +} + +bool SkReadBuffer::readScalarArray(SkScalar* values, size_t size) { + return this->readArray(values, size, sizeof(SkScalar)); +} + +sk_sp SkReadBuffer::readByteArrayAsData() { + size_t numBytes = this->getArrayCount(); + if (!this->validate(fReader.isAvailable(numBytes))) { + return nullptr; + } + + SkAutoMalloc buffer(numBytes); + if (!this->readByteArray(buffer.get(), numBytes)) { + return nullptr; + } + + return SkData::MakeFromMalloc(buffer.release(), numBytes); +} + +uint32_t SkReadBuffer::getArrayCount() { + const size_t inc = sizeof(uint32_t); + fError = fError || !IsPtrAlign4(fReader.peek()) || !fReader.isAvailable(inc); + return fError ? 0 : *(uint32_t*)fReader.peek(); +} + +/* Format: + * (subset) width, height + * (subset) origin x, y + * size (31bits) + * data [ encoded, with raw width/height ] + */ +sk_sp SkReadBuffer::readImage() { + SkIRect bounds; + if (this->isVersionLT(SkPicturePriv::kStoreImageBounds_Version)) { + bounds.fLeft = bounds.fTop = 0; + bounds.fRight = this->read32(); + bounds.fBottom = this->read32(); + } else { + this->readIRect(&bounds); + } + const int width = bounds.width(); + const int height = bounds.height(); + if (width <= 0 || height <= 0) { // SkImage never has a zero dimension + this->validate(false); + return nullptr; + } + + int32_t size = this->read32(); + if (size == SK_NaN32) { + // 0x80000000 is never valid, since it cannot be passed to abs(). + this->validate(false); + return nullptr; + } + if (size == 0) { + // The image could not be encoded at serialization time - return an empty placeholder. + return MakeEmptyImage(width, height); + } + + // we used to negate the size for "custom" encoded images -- ignore that signal (Dec-2017) + size = SkAbs32(size); + if (size == 1) { + // legacy check (we stopped writing this for "raw" images Nov-2017) + this->validate(false); + return nullptr; + } + + // Preflight check to make sure there's enough stuff in the buffer before + // we allocate the memory. This helps the fuzzer avoid OOM when it creates + // bad/corrupt input. + if (!this->validateCanReadN(size)) { + return nullptr; + } + + sk_sp data = SkData::MakeUninitialized(size); + if (!this->readPad32(data->writable_data(), size)) { + this->validate(false); + return nullptr; + } + if (this->isVersionLT(SkPicturePriv::kDontNegateImageSize_Version)) { + (void)this->read32(); // originX + (void)this->read32(); // originY + } + + sk_sp image; + if (fProcs.fImageProc) { + image = fProcs.fImageProc(data->data(), data->size(), fProcs.fImageCtx); + } + if (!image) { + image = SkImage::MakeFromEncoded(std::move(data)); + } + if (image) { + if (bounds.x() || bounds.y() || width < image->width() || height < image->height()) { + image = image->makeSubset(bounds); + } + } + // Question: are we correct to return an "empty" image instead of nullptr, if the decoder + // failed for some reason? + return image ? image : MakeEmptyImage(width, height); +} + +sk_sp SkReadBuffer::readTypeface() { + // Read 32 bits (signed) + // 0 -- return null (default font) + // >0 -- index + // <0 -- custom (serial procs) : negative size in bytes + + int32_t index = this->read32(); + if (index == 0) { + return nullptr; + } else if (index > 0) { + if (!this->validate(index <= fTFCount)) { + return nullptr; + } + return fTFArray[index - 1]; + } else { // custom + size_t size = sk_negate_to_size_t(index); + const void* data = this->skip(size); + if (!this->validate(data != nullptr && fProcs.fTypefaceProc)) { + return nullptr; + } + return fProcs.fTypefaceProc(data, size, fProcs.fTypefaceCtx); + } +} + +SkFlattenable* SkReadBuffer::readFlattenable(SkFlattenable::Type ft) { + SkFlattenable::Factory factory = nullptr; + + if (fFactoryCount > 0) { + int32_t index = this->read32(); + if (0 == index || !this->isValid()) { + return nullptr; // writer failed to give us the flattenable + } + index -= 1; // we stored the index-base-1 + if ((unsigned)index >= (unsigned)fFactoryCount) { + this->validate(false); + return nullptr; + } + factory = fFactoryArray[index]; + } else { + if (this->peekByte() != 0) { + // If the first byte is non-zero, the flattenable is specified by a string. + size_t ignored_length; + if (const char* name = this->readString(&ignored_length)) { + factory = SkFlattenable::NameToFactory(name); + fFlattenableDict.set(fFlattenableDict.count() + 1, factory); + } + } else { + // Read the index. We are guaranteed that the first byte + // is zeroed, so we must shift down a byte. + uint32_t index = this->readUInt() >> 8; + if (index == 0) { + return nullptr; // writer failed to give us the flattenable + } + + if (SkFlattenable::Factory* found = fFlattenableDict.find(index)) { + factory = *found; + } + } + + if (!this->validate(factory != nullptr)) { + return nullptr; + } + } + + // if we get here, factory may still be null, but if that is the case, the + // failure was ours, not the writer. + sk_sp obj; + uint32_t sizeRecorded = this->read32(); + if (factory) { + size_t offset = fReader.offset(); + obj = (*factory)(*this); + // check that we read the amount we expected + size_t sizeRead = fReader.offset() - offset; + if (sizeRecorded != sizeRead) { + this->validate(false); + return nullptr; + } + if (obj && obj->getFlattenableType() != ft) { + this->validate(false); + return nullptr; + } + } else { + // we must skip the remaining data + fReader.skip(sizeRecorded); + } + if (!this->isValid()) { + return nullptr; + } + return obj.release(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +int32_t SkReadBuffer::checkInt(int32_t min, int32_t max) { + SkASSERT(min <= max); + int32_t value = this->read32(); + if (value < min || value > max) { + this->validate(false); + value = min; + } + return value; +} + +SkFilterQuality SkReadBuffer::checkFilterQuality() { + return this->checkRange(kNone_SkFilterQuality, kLast_SkFilterQuality); +} + +#endif // #ifndef SK_DISABLE_READBUFFER diff --git a/gfx/skia/skia/src/core/SkReadBuffer.h b/gfx/skia/skia/src/core/SkReadBuffer.h new file mode 100644 index 0000000000..0bc91a09e0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkReadBuffer.h @@ -0,0 +1,321 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkReadBuffer_DEFINED +#define SkReadBuffer_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkDrawLooper.h" +#include "include/core/SkFont.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSerialProcs.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkPicturePriv.h" +#include "src/core/SkReader32.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkShaderBase.h" + +class SkData; +class SkImage; + +#ifndef SK_DISABLE_READBUFFER + +class SkReadBuffer { +public: + SkReadBuffer(); + SkReadBuffer(const void* data, size_t size); + + /** + * Returns true IFF the version is older than the specified version. + */ + bool isVersionLT(SkPicturePriv::Version targetVersion) const { + SkASSERT(targetVersion > 0); + return fVersion > 0 && fVersion < targetVersion; + } + + uint32_t getVersion() const { return fVersion; } + + /** This may be called at most once; most clients of SkReadBuffer should not mess with it. */ + void setVersion(int version) { + SkASSERT(0 == fVersion || version == fVersion); + fVersion = version; + } + + size_t size() const { return fReader.size(); } + size_t offset() const { return fReader.offset(); } + bool eof() { return fReader.eof(); } + const void* skip(size_t size); + const void* skip(size_t count, size_t size); // does safe multiply + size_t available() const { return fReader.available(); } + + template const T* skipT() { + return static_cast(this->skip(sizeof(T))); + } + template const T* skipT(size_t count) { + return static_cast(this->skip(count, sizeof(T))); + } + + // primitives + bool readBool(); + SkColor readColor(); + int32_t readInt(); + SkScalar readScalar(); + uint32_t readUInt(); + int32_t read32(); + + template T read32LE(T max) { + uint32_t value = this->readUInt(); + if (!this->validate(value <= static_cast(max))) { + value = 0; + } + return static_cast(value); + } + + // peek + uint8_t peekByte(); + + void readString(SkString* string); + + // common data structures + void readColor4f(SkColor4f* color); + void readPoint(SkPoint* point); + SkPoint readPoint() { SkPoint p; this->readPoint(&p); return p; } + void readPoint3(SkPoint3* point); + void readMatrix(SkMatrix* matrix); + void readIRect(SkIRect* rect); + void readRect(SkRect* rect); + void readRRect(SkRRect* rrect); + void readRegion(SkRegion* region); + + void readPath(SkPath* path); + + SkReadPaintResult readPaint(SkPaint* paint, SkFont* font) { + return SkPaintPriv::Unflatten(paint, *this, font); + } + + SkFlattenable* readFlattenable(SkFlattenable::Type); + template sk_sp readFlattenable() { + return sk_sp((T*)this->readFlattenable(T::GetFlattenableType())); + } + sk_sp readColorFilter() { return this->readFlattenable(); } + sk_sp readDrawLooper() { return this->readFlattenable(); } + sk_sp readImageFilter() { return this->readFlattenable(); } + sk_sp readMaskFilter() { return this->readFlattenable(); } + sk_sp readPathEffect() { return this->readFlattenable(); } + sk_sp readShader() { return this->readFlattenable(); } + + // Reads SkAlign4(bytes), but will only copy bytes into the buffer. + bool readPad32(void* buffer, size_t bytes); + + // binary data and arrays + bool readByteArray(void* value, size_t size); + bool readColorArray(SkColor* colors, size_t size); + bool readColor4fArray(SkColor4f* colors, size_t size); + bool readIntArray(int32_t* values, size_t size); + bool readPointArray(SkPoint* points, size_t size); + bool readScalarArray(SkScalar* values, size_t size); + + sk_sp readByteArrayAsData(); + + // helpers to get info about arrays and binary data + uint32_t getArrayCount(); + + // If there is a real error (e.g. data is corrupted) this returns null. If the image cannot + // be created (e.g. it was not originally encoded) then this returns an image that doesn't + // draw. + sk_sp readImage(); + sk_sp readTypeface(); + + void setTypefaceArray(sk_sp array[], int count) { + fTFArray = array; + fTFCount = count; + } + + /** + * Call this with a pre-loaded array of Factories, in the same order as + * were created/written by the writer. SkPicture uses this. + */ + void setFactoryPlayback(SkFlattenable::Factory array[], int count) { + fFactoryArray = array; + fFactoryCount = count; + } + + void setDeserialProcs(const SkDeserialProcs& procs); + const SkDeserialProcs& getDeserialProcs() const { return fProcs; } + + /** + * If isValid is false, sets the buffer to be "invalid". Returns true if the buffer + * is still valid. + */ + bool validate(bool isValid) { + if (!isValid) { + this->setInvalid(); + } + return !fError; + } + + /** + * Helper function to do a preflight check before a large allocation or read. + * Returns true if there is enough bytes in the buffer to read n elements of T. + * If not, the buffer will be "invalid" and false will be returned. + */ + template + bool validateCanReadN(size_t n) { + return this->validate(n <= (fReader.available() / sizeof(T))); + } + + bool isValid() const { return !fError; } + bool validateIndex(int index, int count) { + return this->validate(index >= 0 && index < count); + } + + // Utilities that mark the buffer invalid if the requested value is out-of-range + + // If the read value is outside of the range, validate(false) is called, and min + // is returned, else the value is returned. + int32_t checkInt(int min, int max); + + template T checkRange(T min, T max) { + return static_cast(this->checkInt(static_cast(min), + static_cast(max))); + } + + SkFilterQuality checkFilterQuality(); + +private: + const char* readString(size_t* length); + + void setInvalid(); + bool readArray(void* value, size_t size, size_t elementSize); + void setMemory(const void*, size_t); + + SkReader32 fReader; + + // Only used if we do not have an fFactoryArray. + SkTHashMap fFlattenableDict; + + int fVersion; + + sk_sp* fTFArray; + int fTFCount; + + SkFlattenable::Factory* fFactoryArray; + int fFactoryCount; + + SkDeserialProcs fProcs; + + static bool IsPtrAlign4(const void* ptr) { + return SkIsAlign4((uintptr_t)ptr); + } + + bool fError = false; +}; + +#else // #ifndef SK_DISABLE_READBUFFER + +class SkReadBuffer { +public: + SkReadBuffer() {} + SkReadBuffer(const void*, size_t) {} + + bool isVersionLT(SkPicturePriv::Version) const { return false; } + uint32_t getVersion() const { return 0xffffffff; } + void setVersion(int) {} + + size_t size() const { return 0; } + size_t offset() const { return 0; } + bool eof() { return true; } + size_t available() const { return 0; } + + const void* skip(size_t) { return nullptr; } + const void* skip(size_t, size_t) { return nullptr; } + template const T* skipT() { return nullptr; } + template const T* skipT(size_t) { return nullptr; } + + bool readBool() { return 0; } + SkColor readColor() { return 0; } + int32_t readInt() { return 0; } + SkScalar readScalar() { return 0; } + uint32_t readUInt() { return 0; } + int32_t read32() { return 0; } + + template T read32LE(T max) { return max; } + + uint8_t peekByte() { return 0; } + + void readColor4f(SkColor4f* out) { *out = SkColor4f{0,0,0,0}; } + void readPoint (SkPoint* out) { *out = SkPoint{0,0}; } + void readPoint3 (SkPoint3* out) { *out = SkPoint3{0,0,0}; } + void readMatrix (SkMatrix* out) { *out = SkMatrix::I(); } + void readIRect (SkIRect* out) { *out = SkIRect{0,0,0,0}; } + void readRect (SkRect* out) { *out = SkRect{0,0,0,0}; } + void readRRect (SkRRect* out) { *out = SkRRect(); } + void readRegion (SkRegion* out) { *out = SkRegion(); } + void readString (SkString* out) { *out = SkString(); } + void readPath (SkPath* out) { *out = SkPath(); } + SkReadPaintResult readPaint (SkPaint* out, SkFont* font) { + *out = SkPaint(); + if (font) { + *font = SkFont(); + } + return kFailed_ReadPaint; + } + + SkPoint readPoint() { return {0,0}; } + + SkFlattenable* readFlattenable(SkFlattenable::Type) { return nullptr; } + + template sk_sp readFlattenable() { return nullptr; } + sk_sp readColorFilter() { return nullptr; } + sk_sp readDrawLooper() { return nullptr; } + sk_sp readImageFilter() { return nullptr; } + sk_sp readMaskFilter() { return nullptr; } + sk_sp readPathEffect() { return nullptr; } + sk_sp readShader() { return nullptr; } + + bool readPad32 (void*, size_t) { return false; } + bool readByteArray (void*, size_t) { return false; } + bool readColorArray (SkColor*, size_t) { return false; } + bool readColor4fArray(SkColor4f*, size_t) { return false; } + bool readIntArray (int32_t*, size_t) { return false; } + bool readPointArray (SkPoint*, size_t) { return false; } + bool readScalarArray (SkScalar*, size_t) { return false; } + + sk_sp readByteArrayAsData() { return nullptr; } + uint32_t getArrayCount() { return 0; } + + sk_sp readImage() { return nullptr; } + sk_sp readTypeface() { return nullptr; } + + bool validate(bool) { return false; } + template bool validateCanReadN(size_t) { return false; } + bool isValid() const { return false; } + bool validateIndex(int, int) { return false; } + + int32_t checkInt(int min, int) { return min; } + template T checkRange(T min, T) { return min; } + + SkFilterQuality checkFilterQuality() { return SkFilterQuality::kNone_SkFilterQuality; } + + void setTypefaceArray(sk_sp[], int) {} + void setFactoryPlayback(SkFlattenable::Factory[], int) {} + void setDeserialProcs(const SkDeserialProcs&) {} + + const SkDeserialProcs& getDeserialProcs() const { + static const SkDeserialProcs procs; + return procs; + } +}; + +#endif // #ifndef SK_DISABLE_READBUFFER + +#endif // SkReadBuffer_DEFINED diff --git a/gfx/skia/skia/src/core/SkReader32.h b/gfx/skia/skia/src/core/SkReader32.h new file mode 100644 index 0000000000..ff2b820de6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkReader32.h @@ -0,0 +1,169 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkReader32_DEFINED +#define SkReader32_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRegion.h" +#include "include/core/SkScalar.h" +#include "include/private/SkNoncopyable.h" + +class SkString; + +class SkReader32 : SkNoncopyable { +public: + SkReader32() : fCurr(nullptr), fStop(nullptr), fBase(nullptr) {} + SkReader32(const void* data, size_t size) { + this->setMemory(data, size); + } + + void setMemory(const void* data, size_t size) { + SkASSERT(ptr_align_4(data)); + SkASSERT(SkAlign4(size) == size); + + fBase = fCurr = (const char*)data; + fStop = (const char*)data + size; + } + + size_t size() const { return fStop - fBase; } + size_t offset() const { return fCurr - fBase; } + bool eof() const { return fCurr >= fStop; } + const void* base() const { return fBase; } + const void* peek() const { return fCurr; } + + size_t available() const { return fStop - fCurr; } + bool isAvailable(size_t size) const { return size <= this->available(); } + + void rewind() { fCurr = fBase; } + + void setOffset(size_t offset) { + SkASSERT(SkAlign4(offset) == offset); + SkASSERT(offset <= this->size()); + fCurr = fBase + offset; + } + + bool readBool() { return this->readInt() != 0; } + + int32_t readInt() { + SkASSERT(ptr_align_4(fCurr)); + int32_t value = *(const int32_t*)fCurr; + fCurr += sizeof(value); + SkASSERT(fCurr <= fStop); + return value; + } + + void* readPtr() { + void* ptr; + // we presume this "if" is resolved at compile-time + if (4 == sizeof(void*)) { + ptr = *(void**)fCurr; + } else { + memcpy(&ptr, fCurr, sizeof(void*)); + } + fCurr += sizeof(void*); + return ptr; + } + + SkScalar readScalar() { + SkASSERT(ptr_align_4(fCurr)); + SkScalar value = *(const SkScalar*)fCurr; + fCurr += sizeof(value); + SkASSERT(fCurr <= fStop); + return value; + } + + const void* skip(size_t size) { + SkASSERT(ptr_align_4(fCurr)); + const void* addr = fCurr; + fCurr += SkAlign4(size); + SkASSERT(fCurr <= fStop); + return addr; + } + + template const T& skipT() { + SkASSERT(SkAlign4(sizeof(T)) == sizeof(T)); + return *(const T*)this->skip(sizeof(T)); + } + + void read(void* dst, size_t size) { + SkASSERT(0 == size || dst != nullptr); + SkASSERT(ptr_align_4(fCurr)); + sk_careful_memcpy(dst, fCurr, size); + fCurr += SkAlign4(size); + SkASSERT(fCurr <= fStop); + } + + uint8_t readU8() { return (uint8_t)this->readInt(); } + uint16_t readU16() { return (uint16_t)this->readInt(); } + int32_t readS32() { return this->readInt(); } + uint32_t readU32() { return this->readInt(); } + + bool readPath(SkPath* path) { + return this->readObjectFromMemory(path); + } + + bool readMatrix(SkMatrix* matrix) { + return this->readObjectFromMemory(matrix); + } + + bool readRRect(SkRRect* rrect) { + return this->readObjectFromMemory(rrect); + } + + bool readRegion(SkRegion* rgn) { + return this->readObjectFromMemory(rgn); + } + + /** + * Read the length of a string (written by SkWriter32::writeString) into + * len (if len is not nullptr) and return the null-ternimated address of the + * string within the reader's buffer. + */ + const char* readString(size_t* len = nullptr); + + /** + * Read the string (written by SkWriter32::writeString) and return it in + * copy (if copy is not null). Return the length of the string. + */ + size_t readIntoString(SkString* copy); + + sk_sp readData() { + uint32_t byteLength = this->readU32(); + if (0 == byteLength) { + return SkData::MakeEmpty(); + } + return SkData::MakeWithCopy(this->skip(byteLength), byteLength); + } + +private: + template bool readObjectFromMemory(T* obj) { + size_t size = obj->readFromMemory(this->peek(), this->available()); + // If readFromMemory() fails (which means that available() was too small), it returns 0 + bool success = (size > 0) && (size <= this->available()) && (SkAlign4(size) == size); + // In case of failure, we want to skip to the end + (void)this->skip(success ? size : this->available()); + return success; + } + + // these are always 4-byte aligned + const char* fCurr; // current position within buffer + const char* fStop; // end of buffer + const char* fBase; // beginning of buffer + +#ifdef SK_DEBUG + static bool ptr_align_4(const void* ptr) { + return (((const char*)ptr - (const char*)nullptr) & 3) == 0; + } +#endif +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkRecord.cpp b/gfx/skia/skia/src/core/SkRecord.cpp new file mode 100644 index 0000000000..6f93944b36 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecord.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImage.h" +#include "src/core/SkRecord.h" +#include + +SkRecord::~SkRecord() { + Destroyer destroyer; + for (int i = 0; i < this->count(); i++) { + this->mutate(i, destroyer); + } +} + +void SkRecord::grow() { + SkASSERT(fCount == fReserved); + fReserved = fReserved ? fReserved * 2 : 4; + fRecords.realloc(fReserved); +} + +size_t SkRecord::bytesUsed() const { + size_t bytes = fApproxBytesAllocated + sizeof(SkRecord); + return bytes; +} + +void SkRecord::defrag() { + // Remove all the NoOps, preserving the order of other ops, e.g. + // Save, ClipRect, NoOp, DrawRect, NoOp, NoOp, Restore + // -> Save, ClipRect, DrawRect, Restore + Record* noops = std::remove_if(fRecords.get(), fRecords.get() + fCount, + [](Record op) { return op.type() == SkRecords::NoOp_Type; }); + fCount = noops - fRecords.get(); +} diff --git a/gfx/skia/skia/src/core/SkRecord.h b/gfx/skia/skia/src/core/SkRecord.h new file mode 100644 index 0000000000..68c63492ba --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecord.h @@ -0,0 +1,194 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecord_DEFINED +#define SkRecord_DEFINED + +#include "include/private/SkTLogic.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkRecords.h" + +// SkRecord represents a sequence of SkCanvas calls, saved for future use. +// These future uses may include: replay, optimization, serialization, or combinations of those. +// +// Though an enterprising user may find calling alloc(), append(), visit(), and mutate() enough to +// work with SkRecord, you probably want to look at SkRecorder which presents an SkCanvas interface +// for creating an SkRecord, and SkRecordDraw which plays an SkRecord back into another SkCanvas. +// +// SkRecord often looks like it's compatible with any type T, but really it's compatible with any +// type T which has a static const SkRecords::Type kType. That is to say, SkRecord is compatible +// only with SkRecords::* structs defined in SkRecords.h. Your compiler will helpfully yell if you +// get this wrong. + +class SkRecord : public SkRefCnt { +public: + SkRecord() = default; + ~SkRecord(); + + // Returns the number of canvas commands in this SkRecord. + int count() const { return fCount; } + + // Visit the i-th canvas command with a functor matching this interface: + // template + // R operator()(const T& record) { ... } + // This operator() must be defined for at least all SkRecords::*. + template + auto visit(int i, F&& f) const -> decltype(f(SkRecords::NoOp())) { + return fRecords[i].visit(f); + } + + // Mutate the i-th canvas command with a functor matching this interface: + // template + // R operator()(T* record) { ... } + // This operator() must be defined for at least all SkRecords::*. + template + auto mutate(int i, F&& f) -> decltype(f((SkRecords::NoOp*)nullptr)) { + return fRecords[i].mutate(f); + } + + // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed. + // Here T can be any class, not just those from SkRecords. Throws on failure. + template + T* alloc(size_t count = 1) { + struct RawBytes { + alignas(T) char data[sizeof(T)]; + }; + fApproxBytesAllocated += count * sizeof(T) + alignof(T); + return (T*)fAlloc.makeArrayDefault(count); + } + + // Add a new command of type T to the end of this SkRecord. + // You are expected to placement new an object of type T onto this pointer. + template + T* append() { + if (fCount == fReserved) { + this->grow(); + } + return fRecords[fCount++].set(this->allocCommand()); + } + + // Replace the i-th command with a new command of type T. + // You are expected to placement new an object of type T onto this pointer. + // References to the original command are invalidated. + template + T* replace(int i) { + SkASSERT(i < this->count()); + + Destroyer destroyer; + this->mutate(i, destroyer); + + return fRecords[i].set(this->allocCommand()); + } + + // Replace the i-th command with a new command of type T. + // You are expected to placement new an object of type T onto this pointer. + // You must show proof that you've already adopted the existing command. + template + T* replace(int i, const SkRecords::Adopted& proofOfAdoption) { + SkASSERT(i < this->count()); + + SkASSERT(Existing::kType == fRecords[i].type()); + SkASSERT(proofOfAdoption == fRecords[i].ptr()); + + return fRecords[i].set(this->allocCommand()); + } + + // Does not return the bytes in any pointers embedded in the Records; callers + // need to iterate with a visitor to measure those they care for. + size_t bytesUsed() const; + + // Rearrange and resize this record to eliminate any NoOps. + // May change count() and the indices of ops, but preserves their order. + void defrag(); + +private: + // An SkRecord is structured as an array of pointers into a big chunk of memory where + // records representing each canvas draw call are stored: + // + // fRecords: [*][*][*]... + // | | | + // | | | + // | | +---------------------------------------+ + // | +-----------------+ | + // | | | + // v v v + // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::DrawRect]... + // + // We store the types of each of the pointers alongside the pointer. + // The cost to append a T to this structure is 8 + sizeof(T) bytes. + + // A mutator that can be used with replace to destroy canvas commands. + struct Destroyer { + template + void operator()(T* record) { record->~T(); } + }; + + template + SK_WHEN(std::is_empty::value, T*) allocCommand() { + static T singleton = {}; + return &singleton; + } + + template + SK_WHEN(!std::is_empty::value, T*) allocCommand() { return this->alloc(); } + + void grow(); + + // A typed pointer to some bytes in fAlloc. visit() and mutate() allow polymorphic dispatch. + struct Record { + SkRecords::Type fType; + void* fPtr; + + // Point this record to its data in fAlloc. Returns ptr for convenience. + template + T* set(T* ptr) { + fType = T::kType; + fPtr = ptr; + SkASSERT(this->ptr() == ptr && this->type() == T::kType); + return ptr; + } + + SkRecords::Type type() const { return fType; } + void* ptr() const { return fPtr; } + + // Visit this record with functor F (see public API above). + template + auto visit(F&& f) const -> decltype(f(SkRecords::NoOp())) { + #define CASE(T) case SkRecords::T##_Type: return f(*(const SkRecords::T*)this->ptr()); + switch(this->type()) { SK_RECORD_TYPES(CASE) } + #undef CASE + SkDEBUGFAIL("Unreachable"); + static const SkRecords::NoOp noop{}; + return f(noop); + } + + // Mutate this record with functor F (see public API above). + template + auto mutate(F&& f) -> decltype(f((SkRecords::NoOp*)nullptr)) { + #define CASE(T) case SkRecords::T##_Type: return f((SkRecords::T*)this->ptr()); + switch(this->type()) { SK_RECORD_TYPES(CASE) } + #undef CASE + SkDEBUGFAIL("Unreachable"); + static const SkRecords::NoOp noop{}; + return f(const_cast(&noop)); + } + }; + + // fRecords needs to be a data structure that can append fixed length data, and need to + // support efficient random access and forward iteration. (It doesn't need to be contiguous.) + int fCount{0}, + fReserved{0}; + SkAutoTMalloc fRecords; + + // fAlloc needs to be a data structure which can append variable length data in contiguous + // chunks, returning a stable handle to that data for later retrieval. + SkArenaAlloc fAlloc{256}; + size_t fApproxBytesAllocated{0}; +}; + +#endif//SkRecord_DEFINED diff --git a/gfx/skia/skia/src/core/SkRecordDraw.cpp b/gfx/skia/skia/src/core/SkRecordDraw.cpp new file mode 100644 index 0000000000..16827f07a4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordDraw.cpp @@ -0,0 +1,529 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImage.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkRecordDraw.h" +#include "src/utils/SkPatchUtils.h" + +void SkRecordDraw(const SkRecord& record, + SkCanvas* canvas, + SkPicture const* const drawablePicts[], + SkDrawable* const drawables[], + int drawableCount, + const SkBBoxHierarchy* bbh, + SkPicture::AbortCallback* callback) { + SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/); + + if (bbh) { + // Draw only ops that affect pixels in the canvas's current clip. + // The SkRecord and BBH were recorded in identity space. This canvas + // is not necessarily in that same space. getLocalClipBounds() returns us + // this canvas' clip bounds transformed back into identity space, which + // lets us query the BBH. + SkRect query = canvas->getLocalClipBounds(); + + SkTDArray ops; + bbh->search(query, &ops); + + SkRecords::Draw draw(canvas, drawablePicts, drawables, drawableCount); + for (int i = 0; i < ops.count(); i++) { + if (callback && callback->abort()) { + return; + } + // This visit call uses the SkRecords::Draw::operator() to call + // methods on the |canvas|, wrapped by methods defined with the + // DRAW() macro. + record.visit(ops[i], draw); + } + } else { + // Draw all ops. + SkRecords::Draw draw(canvas, drawablePicts, drawables, drawableCount); + for (int i = 0; i < record.count(); i++) { + if (callback && callback->abort()) { + return; + } + // This visit call uses the SkRecords::Draw::operator() to call + // methods on the |canvas|, wrapped by methods defined with the + // DRAW() macro. + record.visit(i, draw); + } + } +} + +void SkRecordPartialDraw(const SkRecord& record, SkCanvas* canvas, + SkPicture const* const drawablePicts[], int drawableCount, + int start, int stop, + const SkMatrix& initialCTM) { + SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/); + + stop = SkTMin(stop, record.count()); + SkRecords::Draw draw(canvas, drawablePicts, nullptr, drawableCount, &initialCTM); + for (int i = start; i < stop; i++) { + record.visit(i, draw); + } +} + +namespace SkRecords { + +// NoOps draw nothing. +template <> void Draw::draw(const NoOp&) {} + +#define DRAW(T, call) template <> void Draw::draw(const T& r) { fCanvas->call; } +DRAW(Flush, flush()); +DRAW(Restore, restore()); +DRAW(Save, save()); +DRAW(SaveLayer, saveLayer(SkCanvas::SaveLayerRec(r.bounds, + r.paint, + r.backdrop.get(), + r.clipMask.get(), + r.clipMatrix, + r.saveLayerFlags))); + +template <> void Draw::draw(const SaveBehind& r) { + SkCanvasPriv::SaveBehind(fCanvas, r.subset); +} + +template <> void Draw::draw(const DrawBehind& r) { + SkCanvasPriv::DrawBehind(fCanvas, r.paint); +} + +DRAW(SetMatrix, setMatrix(SkMatrix::Concat(fInitialCTM, r.matrix))); +DRAW(Concat, concat(r.matrix)); +DRAW(Translate, translate(r.dx, r.dy)); + +DRAW(ClipPath, clipPath(r.path, r.opAA.op(), r.opAA.aa())); +DRAW(ClipRRect, clipRRect(r.rrect, r.opAA.op(), r.opAA.aa())); +DRAW(ClipRect, clipRect(r.rect, r.opAA.op(), r.opAA.aa())); +DRAW(ClipRegion, clipRegion(r.region, r.op)); + +DRAW(DrawArc, drawArc(r.oval, r.startAngle, r.sweepAngle, r.useCenter, r.paint)); +DRAW(DrawDRRect, drawDRRect(r.outer, r.inner, r.paint)); +DRAW(DrawImage, drawImage(r.image.get(), r.left, r.top, r.paint)); + +template <> void Draw::draw(const DrawImageLattice& r) { + SkCanvas::Lattice lattice; + lattice.fXCount = r.xCount; + lattice.fXDivs = r.xDivs; + lattice.fYCount = r.yCount; + lattice.fYDivs = r.yDivs; + lattice.fRectTypes = (0 == r.flagCount) ? nullptr : r.flags; + lattice.fColors = (0 == r.flagCount) ? nullptr : r.colors; + lattice.fBounds = &r.src; + fCanvas->drawImageLattice(r.image.get(), lattice, r.dst, r.paint); +} + +DRAW(DrawImageRect, legacy_drawImageRect(r.image.get(), r.src, r.dst, r.paint, r.constraint)); +DRAW(DrawImageNine, drawImageNine(r.image.get(), r.center, r.dst, r.paint)); +DRAW(DrawOval, drawOval(r.oval, r.paint)); +DRAW(DrawPaint, drawPaint(r.paint)); +DRAW(DrawPath, drawPath(r.path, r.paint)); +DRAW(DrawPatch, drawPatch(r.cubics, r.colors, r.texCoords, r.bmode, r.paint)); +DRAW(DrawPicture, drawPicture(r.picture.get(), &r.matrix, r.paint)); +DRAW(DrawPoints, drawPoints(r.mode, r.count, r.pts, r.paint)); +DRAW(DrawRRect, drawRRect(r.rrect, r.paint)); +DRAW(DrawRect, drawRect(r.rect, r.paint)); +DRAW(DrawRegion, drawRegion(r.region, r.paint)); +DRAW(DrawTextBlob, drawTextBlob(r.blob.get(), r.x, r.y, r.paint)); +DRAW(DrawAtlas, drawAtlas(r.atlas.get(), + r.xforms, r.texs, r.colors, r.count, r.mode, r.cull, r.paint)); +DRAW(DrawVertices, drawVertices(r.vertices, r.bones, r.boneCount, r.bmode, r.paint)); +DRAW(DrawShadowRec, private_draw_shadow_rec(r.path, r.rec)); +DRAW(DrawAnnotation, drawAnnotation(r.rect, r.key.c_str(), r.value.get())); + +DRAW(DrawEdgeAAQuad, experimental_DrawEdgeAAQuad( + r.rect, r.clip, r.aa, r.color, r.mode)); +DRAW(DrawEdgeAAImageSet, experimental_DrawEdgeAAImageSet( + r.set.get(), r.count, r.dstClips, r.preViewMatrices, r.paint, r.constraint)); + +#undef DRAW + +template <> void Draw::draw(const DrawDrawable& r) { + SkASSERT(r.index >= 0); + SkASSERT(r.index < fDrawableCount); + if (fDrawables) { + SkASSERT(nullptr == fDrawablePicts); + fCanvas->drawDrawable(fDrawables[r.index], r.matrix); + } else { + fCanvas->drawPicture(fDrawablePicts[r.index], r.matrix, nullptr); + } +} + +// This is an SkRecord visitor that fills an SkBBoxHierarchy. +// +// The interesting part here is how to calculate bounds for ops which don't +// have intrinsic bounds. What is the bounds of a Save or a Translate? +// +// We answer this by thinking about a particular definition of bounds: if I +// don't execute this op, pixels in this rectangle might draw incorrectly. So +// the bounds of a Save, a Translate, a Restore, etc. are the union of the +// bounds of Draw* ops that they might have an effect on. For any given +// Save/Restore block, the bounds of the Save, the Restore, and any other +// non-drawing ("control") ops inside are exactly the union of the bounds of +// the drawing ops inside that block. +// +// To implement this, we keep a stack of active Save blocks. As we consume ops +// inside the Save/Restore block, drawing ops are unioned with the bounds of +// the block, and control ops are stashed away for later. When we finish the +// block with a Restore, our bounds are complete, and we go back and fill them +// in for all the control ops we stashed away. +class FillBounds : SkNoncopyable { +public: + FillBounds(const SkRect& cullRect, const SkRecord& record, SkRect bounds[]) + : fCullRect(cullRect) + , fBounds(bounds) { + fCTM = SkMatrix::I(); + + // We push an extra save block to track the bounds of any top-level control operations. + fSaveStack.push_back({ 0, Bounds::MakeEmpty(), nullptr, fCTM }); + } + + ~FillBounds() { + // If we have any lingering unpaired Saves, simulate restores to make + // sure all ops in those Save blocks have their bounds calculated. + while (!fSaveStack.isEmpty()) { + this->popSaveBlock(); + } + + // Any control ops not part of any Save/Restore block draw everywhere. + while (!fControlIndices.isEmpty()) { + this->popControl(fCullRect); + } + } + + void setCurrentOp(int currentOp) { fCurrentOp = currentOp; } + + + template void operator()(const T& op) { + this->updateCTM(op); + this->trackBounds(op); + } + + // In this file, SkRect are in local coordinates, Bounds are translated back to identity space. + typedef SkRect Bounds; + + // Adjust rect for all paints that may affect its geometry, then map it to identity space. + Bounds adjustAndMap(SkRect rect, const SkPaint* paint) const { + // Inverted rectangles really confuse our BBHs. + rect.sort(); + + // Adjust the rect for its own paint. + if (!AdjustForPaint(paint, &rect)) { + // The paint could do anything to our bounds. The only safe answer is the cull. + return fCullRect; + } + + // Adjust rect for all the paints from the SaveLayers we're inside. + if (!this->adjustForSaveLayerPaints(&rect)) { + // Same deal as above. + return fCullRect; + } + + // Map the rect back to identity space. + fCTM.mapRect(&rect); + + // Nothing can draw outside the cull rect. + if (!rect.intersect(fCullRect)) { + return Bounds::MakeEmpty(); + } + + return rect; + } + +private: + struct SaveBounds { + int controlOps; // Number of control ops in this Save block, including the Save. + Bounds bounds; // Bounds of everything in the block. + const SkPaint* paint; // Unowned. If set, adjusts the bounds of all ops in this block. + SkMatrix ctm; + }; + + // Only Restore, SetMatrix, Concat, and Translate change the CTM. + template void updateCTM(const T&) {} + void updateCTM(const Restore& op) { fCTM = op.matrix; } + void updateCTM(const SetMatrix& op) { fCTM = op.matrix; } + void updateCTM(const Concat& op) { fCTM.preConcat(op.matrix); } + void updateCTM(const Translate& op) { fCTM.preTranslate(op.dx, op.dy); } + + // The bounds of these ops must be calculated when we hit the Restore + // from the bounds of the ops in the same Save block. + void trackBounds(const Save&) { this->pushSaveBlock(nullptr); } + void trackBounds(const SaveLayer& op) { this->pushSaveBlock(op.paint); } + void trackBounds(const SaveBehind&) { this->pushSaveBlock(nullptr); } + void trackBounds(const Restore&) { fBounds[fCurrentOp] = this->popSaveBlock(); } + + void trackBounds(const SetMatrix&) { this->pushControl(); } + void trackBounds(const Concat&) { this->pushControl(); } + void trackBounds(const Translate&) { this->pushControl(); } + void trackBounds(const ClipRect&) { this->pushControl(); } + void trackBounds(const ClipRRect&) { this->pushControl(); } + void trackBounds(const ClipPath&) { this->pushControl(); } + void trackBounds(const ClipRegion&) { this->pushControl(); } + + + // For all other ops, we can calculate and store the bounds directly now. + template void trackBounds(const T& op) { + fBounds[fCurrentOp] = this->bounds(op); + this->updateSaveBounds(fBounds[fCurrentOp]); + } + + void pushSaveBlock(const SkPaint* paint) { + // Starting a new Save block. Push a new entry to represent that. + SaveBounds sb; + sb.controlOps = 0; + // If the paint affects transparent black, + // the bound shouldn't be smaller than the cull. + sb.bounds = + PaintMayAffectTransparentBlack(paint) ? fCullRect : Bounds::MakeEmpty(); + sb.paint = paint; + sb.ctm = this->fCTM; + + fSaveStack.push_back(sb); + this->pushControl(); + } + + static bool PaintMayAffectTransparentBlack(const SkPaint* paint) { + if (paint) { + // FIXME: this is very conservative + if (paint->getImageFilter() || paint->getColorFilter()) { + return true; + } + + // Unusual blendmodes require us to process a saved layer + // even with operations outisde the clip. + // For example, DstIn is used by masking layers. + // https://code.google.com/p/skia/issues/detail?id=1291 + // https://crbug.com/401593 + switch (paint->getBlendMode()) { + // For each of the following transfer modes, if the source + // alpha is zero (our transparent black), the resulting + // blended alpha is not necessarily equal to the original + // destination alpha. + case SkBlendMode::kClear: + case SkBlendMode::kSrc: + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: + case SkBlendMode::kSrcOut: + case SkBlendMode::kDstATop: + case SkBlendMode::kModulate: + return true; + break; + default: + break; + } + } + return false; + } + + Bounds popSaveBlock() { + // We're done the Save block. Apply the block's bounds to all control ops inside it. + SaveBounds sb; + fSaveStack.pop(&sb); + + while (sb.controlOps --> 0) { + this->popControl(sb.bounds); + } + + // This whole Save block may be part another Save block. + this->updateSaveBounds(sb.bounds); + + // If called from a real Restore (not a phony one for balance), it'll need the bounds. + return sb.bounds; + } + + void pushControl() { + fControlIndices.push_back(fCurrentOp); + if (!fSaveStack.isEmpty()) { + fSaveStack.top().controlOps++; + } + } + + void popControl(const Bounds& bounds) { + fBounds[fControlIndices.top()] = bounds; + fControlIndices.pop(); + } + + void updateSaveBounds(const Bounds& bounds) { + // If we're in a Save block, expand its bounds to cover these bounds too. + if (!fSaveStack.isEmpty()) { + fSaveStack.top().bounds.join(bounds); + } + } + + Bounds bounds(const Flush&) const { return fCullRect; } + + Bounds bounds(const DrawPaint&) const { return fCullRect; } + Bounds bounds(const DrawBehind&) const { return fCullRect; } + Bounds bounds(const NoOp&) const { return Bounds::MakeEmpty(); } // NoOps don't draw. + + Bounds bounds(const DrawRect& op) const { return this->adjustAndMap(op.rect, &op.paint); } + Bounds bounds(const DrawRegion& op) const { + SkRect rect = SkRect::Make(op.region.getBounds()); + return this->adjustAndMap(rect, &op.paint); + } + Bounds bounds(const DrawOval& op) const { return this->adjustAndMap(op.oval, &op.paint); } + // Tighter arc bounds? + Bounds bounds(const DrawArc& op) const { return this->adjustAndMap(op.oval, &op.paint); } + Bounds bounds(const DrawRRect& op) const { + return this->adjustAndMap(op.rrect.rect(), &op.paint); + } + Bounds bounds(const DrawDRRect& op) const { + return this->adjustAndMap(op.outer.rect(), &op.paint); + } + Bounds bounds(const DrawImage& op) const { + const SkImage* image = op.image.get(); + SkRect rect = SkRect::MakeXYWH(op.left, op.top, image->width(), image->height()); + + return this->adjustAndMap(rect, op.paint); + } + Bounds bounds(const DrawImageLattice& op) const { + return this->adjustAndMap(op.dst, op.paint); + } + Bounds bounds(const DrawImageRect& op) const { + return this->adjustAndMap(op.dst, op.paint); + } + Bounds bounds(const DrawImageNine& op) const { + return this->adjustAndMap(op.dst, op.paint); + } + Bounds bounds(const DrawPath& op) const { + return op.path.isInverseFillType() ? fCullRect + : this->adjustAndMap(op.path.getBounds(), &op.paint); + } + Bounds bounds(const DrawPoints& op) const { + SkRect dst; + dst.setBounds(op.pts, op.count); + + // Pad the bounding box a little to make sure hairline points' bounds aren't empty. + SkScalar stroke = SkMaxScalar(op.paint.getStrokeWidth(), 0.01f); + dst.outset(stroke/2, stroke/2); + + return this->adjustAndMap(dst, &op.paint); + } + Bounds bounds(const DrawPatch& op) const { + SkRect dst; + dst.setBounds(op.cubics, SkPatchUtils::kNumCtrlPts); + return this->adjustAndMap(dst, &op.paint); + } + Bounds bounds(const DrawVertices& op) const { + return this->adjustAndMap(op.vertices->bounds(), &op.paint); + } + + Bounds bounds(const DrawAtlas& op) const { + if (op.cull) { + // TODO: can we pass nullptr for the paint? Isn't cull already "correct" + // for the paint (by the caller)? + return this->adjustAndMap(*op.cull, op.paint); + } else { + return fCullRect; + } + } + + Bounds bounds(const DrawShadowRec& op) const { + SkRect bounds; + SkDrawShadowMetrics::GetLocalBounds(op.path, op.rec, fCTM, &bounds); + return this->adjustAndMap(bounds, nullptr); + } + + Bounds bounds(const DrawPicture& op) const { + SkRect dst = op.picture->cullRect(); + op.matrix.mapRect(&dst); + return this->adjustAndMap(dst, op.paint); + } + + Bounds bounds(const DrawTextBlob& op) const { + SkRect dst = op.blob->bounds(); + dst.offset(op.x, op.y); + return this->adjustAndMap(dst, &op.paint); + } + + Bounds bounds(const DrawDrawable& op) const { + return this->adjustAndMap(op.worstCaseBounds, nullptr); + } + + Bounds bounds(const DrawAnnotation& op) const { + return this->adjustAndMap(op.rect, nullptr); + } + Bounds bounds(const DrawEdgeAAQuad& op) const { + SkRect bounds = op.rect; + if (op.clip) { + bounds.setBounds(op.clip, 4); + } + return this->adjustAndMap(bounds, nullptr); + } + Bounds bounds(const DrawEdgeAAImageSet& op) const { + SkRect rect = SkRect::MakeEmpty(); + int clipIndex = 0; + for (int i = 0; i < op.count; ++i) { + SkRect entryBounds = op.set[i].fDstRect; + if (op.set[i].fHasClip) { + entryBounds.setBounds(op.dstClips + clipIndex, 4); + clipIndex += 4; + } + if (op.set[i].fMatrixIndex >= 0) { + op.preViewMatrices[op.set[i].fMatrixIndex].mapRect(&entryBounds); + } + rect.join(this->adjustAndMap(entryBounds, nullptr)); + } + return rect; + } + + // Returns true if rect was meaningfully adjusted for the effects of paint, + // false if the paint could affect the rect in unknown ways. + static bool AdjustForPaint(const SkPaint* paint, SkRect* rect) { + if (paint) { + if (paint->canComputeFastBounds()) { + *rect = paint->computeFastBounds(*rect, rect); + return true; + } + return false; + } + return true; + } + + bool adjustForSaveLayerPaints(SkRect* rect, int savesToIgnore = 0) const { + for (int i = fSaveStack.count() - 1 - savesToIgnore; i >= 0; i--) { + SkMatrix inverse; + if (!fSaveStack[i].ctm.invert(&inverse)) { + return false; + } + inverse.mapRect(rect); + if (!AdjustForPaint(fSaveStack[i].paint, rect)) { + return false; + } + fSaveStack[i].ctm.mapRect(rect); + } + return true; + } + + // We do not guarantee anything for operations outside of the cull rect + const SkRect fCullRect; + + // Conservative identity-space bounds for each op in the SkRecord. + Bounds* fBounds; + + // We walk fCurrentOp through the SkRecord, + // as we go using updateCTM() to maintain the exact CTM (fCTM). + int fCurrentOp; + SkMatrix fCTM; + + // Used to track the bounds of Save/Restore blocks and the control ops inside them. + SkTDArray fSaveStack; + SkTDArray fControlIndices; +}; + +} // namespace SkRecords + +void SkRecordFillBounds(const SkRect& cullRect, const SkRecord& record, SkRect bounds[]) { + { + SkRecords::FillBounds visitor(cullRect, record, bounds); + for (int i = 0; i < record.count(); i++) { + visitor.setCurrentOp(i); + record.visit(i, visitor); + } + } +} + diff --git a/gfx/skia/skia/src/core/SkRecordDraw.h b/gfx/skia/skia/src/core/SkRecordDraw.h new file mode 100644 index 0000000000..baffab5572 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordDraw.h @@ -0,0 +1,82 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecordDraw_DEFINED +#define SkRecordDraw_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkMatrix.h" +#include "src/core/SkBBoxHierarchy.h" +#include "src/core/SkBigPicture.h" +#include "src/core/SkRecord.h" + +class SkDrawable; +class SkLayerInfo; + +// Calculate conservative identity space bounds for each op in the record. +void SkRecordFillBounds(const SkRect& cullRect, const SkRecord&, SkRect bounds[]); + +// SkRecordFillBounds(), and gathers information about saveLayers and stores it for later +// use (e.g., layer hoisting). The gathered information is sufficient to determine +// where each saveLayer will land and which ops in the picture it represents. +void SkRecordComputeLayers(const SkRect& cullRect, const SkRecord&, SkRect bounds[], + const SkBigPicture::SnapshotArray*, SkLayerInfo* data); + +// Draw an SkRecord into an SkCanvas. A convenience wrapper around SkRecords::Draw. +void SkRecordDraw(const SkRecord&, SkCanvas*, SkPicture const* const drawablePicts[], + SkDrawable* const drawables[], int drawableCount, + const SkBBoxHierarchy*, SkPicture::AbortCallback*); + +// Draw a portion of an SkRecord into an SkCanvas. +// When drawing a portion of an SkRecord the CTM on the passed in canvas must be +// the composition of the replay matrix with the record-time CTM (for the portion +// of the record that is being replayed). For setMatrix calls to behave correctly +// the initialCTM parameter must set to just the replay matrix. +void SkRecordPartialDraw(const SkRecord&, SkCanvas*, + SkPicture const* const drawablePicts[], int drawableCount, + int start, int stop, const SkMatrix& initialCTM); + +namespace SkRecords { + +// This is an SkRecord visitor that will draw that SkRecord to an SkCanvas. +class Draw : SkNoncopyable { +public: + explicit Draw(SkCanvas* canvas, SkPicture const* const drawablePicts[], + SkDrawable* const drawables[], int drawableCount, + const SkMatrix* initialCTM = nullptr) + : fInitialCTM(initialCTM ? *initialCTM : canvas->getTotalMatrix()) + , fCanvas(canvas) + , fDrawablePicts(drawablePicts) + , fDrawables(drawables) + , fDrawableCount(drawableCount) + {} + + // This operator calls methods on the |canvas|. The various draw() wrapper + // methods around SkCanvas are defined by the DRAW() macro in + // SkRecordDraw.cpp. + template void operator()(const T& r) { + this->draw(r); + } + +protected: + SkPicture const* const* drawablePicts() const { return fDrawablePicts; } + int drawableCount() const { return fDrawableCount; } + +private: + // No base case, so we'll be compile-time checked that we implement all possibilities. + template void draw(const T&); + + const SkMatrix fInitialCTM; + SkCanvas* fCanvas; + SkPicture const* const* fDrawablePicts; + SkDrawable* const* fDrawables; + int fDrawableCount; +}; + +} // namespace SkRecords + +#endif//SkRecordDraw_DEFINED diff --git a/gfx/skia/skia/src/core/SkRecordOpts.cpp b/gfx/skia/skia/src/core/SkRecordOpts.cpp new file mode 100644 index 0000000000..127a9a861e --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordOpts.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkRecordOpts.h" + +#include "include/private/SkTDArray.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkRecordPattern.h" +#include "src/core/SkRecords.h" + +using namespace SkRecords; + +// Most of the optimizations in this file are pattern-based. These are all defined as structs with: +// - a Match typedef +// - a bool onMatch(SkRceord*, Match*, int begin, int end) method, +// which returns true if it made changes and false if not. + +// Run a pattern-based optimization once across the SkRecord, returning true if it made any changes. +// It looks for spans which match Pass::Match, and when found calls onMatch() with that pattern, +// record, and [begin,end) span of the commands that matched. +template +static bool apply(Pass* pass, SkRecord* record) { + typename Pass::Match match; + bool changed = false; + int begin, end = 0; + + while (match.search(record, &begin, &end)) { + changed |= pass->onMatch(record, &match, begin, end); + } + return changed; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static void multiple_set_matrices(SkRecord* record) { + struct { + typedef Pattern, + Greedy>, + Is > + Match; + + bool onMatch(SkRecord* record, Match* pattern, int begin, int end) { + record->replace(begin); // first SetMatrix + return true; + } + } pass; + while (apply(&pass, record)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if 0 // experimental, but needs knowledge of previous matrix to operate correctly +static void apply_matrix_to_draw_params(SkRecord* record) { + struct { + typedef Pattern, + Greedy>, + Is > + Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, int begin, int end) { + record->replace(begin); // first SetMatrix + return true; + } + } pass; + // No need to loop, as we never "open up" opportunities for more of this type of optimization. + apply(&pass, record); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// Turns the logical NoOp Save and Restore in Save-Draw*-Restore patterns into actual NoOps. +struct SaveOnlyDrawsRestoreNooper { + typedef Pattern, + Greedy, IsDraw>>, + Is> + Match; + + bool onMatch(SkRecord* record, Match*, int begin, int end) { + record->replace(begin); // Save + record->replace(end-1); // Restore + return true; + } +}; + +static bool fold_opacity_layer_color_to_paint(const SkPaint* layerPaint, + bool isSaveLayer, + SkPaint* paint) { + // We assume layerPaint is always from a saveLayer. If isSaveLayer is + // true, we assume paint is too. + + // The alpha folding can proceed if the filter layer paint does not have properties which cause + // the resulting filter layer to be "blended" in complex ways to the parent layer. + // TODO: most likely only some xfer modes are the hard constraints + if (!paint->isSrcOver()) { + return false; + } + + if (!isSaveLayer && paint->getImageFilter()) { + // For normal draws, the paint color is used as one input for the color for the draw. Image + // filter will operate on the result, and thus we can not change the input. + // For layer saves, the image filter is applied to the layer contents. The layer is then + // modulated with the paint color, so it's fine to proceed with the fold for saveLayer + // paints with image filters. + return false; + } + + if (paint->getColorFilter()) { + // Filter input depends on the paint color. + + // Here we could filter the color if we knew the draw is going to be uniform color. This + // should be detectable as drawPath/drawRect/.. without a shader being uniform, while + // drawBitmap/drawSprite or a shader being non-uniform. However, current matchers don't + // give the type out easily, so just do not optimize that at the moment. + return false; + } + + if (layerPaint) { + const uint32_t layerColor = layerPaint->getColor(); + // The layer paint color must have only alpha component. + if (SK_ColorTRANSPARENT != SkColorSetA(layerColor, SK_AlphaTRANSPARENT)) { + return false; + } + + // The layer paint can not have any effects. + if (layerPaint->getPathEffect() || + layerPaint->getShader() || + !layerPaint->isSrcOver() || + layerPaint->getMaskFilter() || + layerPaint->getColorFilter() || + layerPaint->getImageFilter()) { + return false; + } + paint->setAlpha(SkMulDiv255Round(paint->getAlpha(), SkColorGetA(layerColor))); + } + + return true; +} + +// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. +struct SaveNoDrawsRestoreNooper { + // Greedy matches greedily, so we also have to exclude Save and Restore. + // Nested SaveLayers need to be excluded, or we'll match their Restore! + typedef Pattern, + Greedy, + Is, + Is, + IsDraw>>>, + Is> + Match; + + bool onMatch(SkRecord* record, Match*, int begin, int end) { + // The entire span between Save and Restore (inclusively) does nothing. + for (int i = begin; i < end; i++) { + record->replace(i); + } + return true; + } +}; +void SkRecordNoopSaveRestores(SkRecord* record) { + SaveOnlyDrawsRestoreNooper onlyDraws; + SaveNoDrawsRestoreNooper noDraws; + + // Run until they stop changing things. + while (apply(&onlyDraws, record) || apply(&noDraws, record)); +} + +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK +static bool effectively_srcover(const SkPaint* paint) { + if (!paint || paint->isSrcOver()) { + return true; + } + // src-mode with opaque and no effects (which might change opaqueness) is ok too. + return !paint->getShader() && !paint->getColorFilter() && !paint->getImageFilter() && + 0xFF == paint->getAlpha() && paint->getBlendMode() == SkBlendMode::kSrc; +} + +// For some SaveLayer-[drawing command]-Restore patterns, merge the SaveLayer's alpha into the +// draw, and no-op the SaveLayer and Restore. +struct SaveLayerDrawRestoreNooper { + typedef Pattern, IsDraw, Is> Match; + + bool onMatch(SkRecord* record, Match* match, int begin, int end) { + if (match->first()->backdrop || match->first()->clipMask) { + // can't throw away the layer if we have a backdrop or clip mask + return false; + } + + if (match->first()->saveLayerFlags & + SkCanvasPriv::kDontClipToLayer_SaveLayerFlag) { + // can't throw away the layer if set + return false; + } + + // A SaveLayer's bounds field is just a hint, so we should be free to ignore it. + SkPaint* layerPaint = match->first()->paint; + SkPaint* drawPaint = match->second(); + + if (nullptr == layerPaint && effectively_srcover(drawPaint)) { + // There wasn't really any point to this SaveLayer at all. + return KillSaveLayerAndRestore(record, begin); + } + + if (drawPaint == nullptr) { + // We can just give the draw the SaveLayer's paint. + // TODO(mtklein): figure out how to do this clearly + return false; + } + + if (!fold_opacity_layer_color_to_paint(layerPaint, false /*isSaveLayer*/, drawPaint)) { + return false; + } + + return KillSaveLayerAndRestore(record, begin); + } + + static bool KillSaveLayerAndRestore(SkRecord* record, int saveLayerIndex) { + record->replace(saveLayerIndex); // SaveLayer + record->replace(saveLayerIndex+2); // Restore + return true; + } +}; +void SkRecordNoopSaveLayerDrawRestores(SkRecord* record) { + SaveLayerDrawRestoreNooper pass; + apply(&pass, record); +} +#endif + +/* For SVG generated: + SaveLayer (non-opaque, typically for CSS opacity) + Save + ClipRect + SaveLayer (typically for SVG filter) + Restore + Restore + Restore +*/ +struct SvgOpacityAndFilterLayerMergePass { + typedef Pattern, Is, Is, Is, + Is, Is, Is> Match; + + bool onMatch(SkRecord* record, Match* match, int begin, int end) { + if (match->first()->backdrop) { + // can't throw away the layer if we have a backdrop + return false; + } + + SkPaint* opacityPaint = match->first()->paint; + if (nullptr == opacityPaint) { + // There wasn't really any point to this SaveLayer at all. + return KillSaveLayerAndRestore(record, begin); + } + + // This layer typically contains a filter, but this should work for layers with for other + // purposes too. + SkPaint* filterLayerPaint = match->fourth()->paint; + if (filterLayerPaint == nullptr) { + // We can just give the inner SaveLayer the paint of the outer SaveLayer. + // TODO(mtklein): figure out how to do this clearly + return false; + } + + if (!fold_opacity_layer_color_to_paint(opacityPaint, true /*isSaveLayer*/, + filterLayerPaint)) { + return false; + } + + return KillSaveLayerAndRestore(record, begin); + } + + static bool KillSaveLayerAndRestore(SkRecord* record, int saveLayerIndex) { + record->replace(saveLayerIndex); // SaveLayer + record->replace(saveLayerIndex + 6); // Restore + return true; + } +}; + +void SkRecordMergeSvgOpacityAndFilterLayers(SkRecord* record) { + SvgOpacityAndFilterLayerMergePass pass; + apply(&pass, record); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkRecordOptimize(SkRecord* record) { + // This might be useful as a first pass in the future if we want to weed + // out junk for other optimization passes. Right now, nothing needs it, + // and the bounding box hierarchy will do the work of skipping no-op + // Save-NoDraw-Restore sequences better than we can here. + // As there is a known problem with this peephole and drawAnnotation, disable this. + // If we want to enable this we must first fix this bug: + // https://bugs.chromium.org/p/skia/issues/detail?id=5548 +// SkRecordNoopSaveRestores(record); + + // Turn off this optimization completely for Android framework + // because it makes the following Android CTS test fail: + // android.uirendering.cts.testclasses.LayerTests#testSaveLayerClippedWithAlpha +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkRecordNoopSaveLayerDrawRestores(record); +#endif + SkRecordMergeSvgOpacityAndFilterLayers(record); + + record->defrag(); +} + +void SkRecordOptimize2(SkRecord* record) { + multiple_set_matrices(record); + SkRecordNoopSaveRestores(record); + // See why we turn this off in SkRecordOptimize above. +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkRecordNoopSaveLayerDrawRestores(record); +#endif + SkRecordMergeSvgOpacityAndFilterLayers(record); + + record->defrag(); +} diff --git a/gfx/skia/skia/src/core/SkRecordOpts.h b/gfx/skia/skia/src/core/SkRecordOpts.h new file mode 100644 index 0000000000..a1e3c245a0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordOpts.h @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecordOpts_DEFINED +#define SkRecordOpts_DEFINED + +#include "src/core/SkRecord.h" + +// Run all optimizations in recommended order. +void SkRecordOptimize(SkRecord*); + +// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. +void SkRecordNoopSaveRestores(SkRecord*); + +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK +// For some SaveLayer-[drawing command]-Restore patterns, merge the SaveLayer's alpha into the +// draw, and no-op the SaveLayer and Restore. +void SkRecordNoopSaveLayerDrawRestores(SkRecord*); +#endif + +// For SVG generated SaveLayer-Save-ClipRect-SaveLayer-3xRestore patterns, merge +// the alpha of the first SaveLayer to the second SaveLayer. +void SkRecordMergeSvgOpacityAndFilterLayers(SkRecord*); + +// Experimental optimizers +void SkRecordOptimize2(SkRecord*); + +#endif//SkRecordOpts_DEFINED diff --git a/gfx/skia/skia/src/core/SkRecordPattern.h b/gfx/skia/skia/src/core/SkRecordPattern.h new file mode 100644 index 0000000000..7e96592c70 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordPattern.h @@ -0,0 +1,176 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecordPattern_DEFINED +#define SkRecordPattern_DEFINED + +#include "include/private/SkTLogic.h" +#include "src/core/SkRecord.h" + +namespace SkRecords { + +// First, some matchers. These match a single command in the SkRecord, +// and may hang onto some data from it. If so, you can get the data by calling .get(). + +// Matches a command of type T, and stores that command. +template +class Is { +public: + Is() : fPtr(nullptr) {} + + typedef T type; + type* get() { return fPtr; } + + bool operator()(T* ptr) { + fPtr = ptr; + return true; + } + + template + bool operator()(U*) { + fPtr = nullptr; + return false; + } + +private: + type* fPtr; +}; + +// Matches any command that draws, and stores its paint. +class IsDraw { +public: + IsDraw() : fPaint(nullptr) {} + + typedef SkPaint type; + type* get() { return fPaint; } + + template + SK_WHEN((T::kTags & kDrawWithPaint_Tag) == kDrawWithPaint_Tag, bool) operator()(T* draw) { + fPaint = AsPtr(draw->paint); + return true; + } + + template + SK_WHEN((T::kTags & kDrawWithPaint_Tag) == kDraw_Tag, bool) operator()(T* draw) { + fPaint = nullptr; + return true; + } + + template + SK_WHEN(!(T::kTags & kDraw_Tag), bool) operator()(T* draw) { + fPaint = nullptr; + return false; + } + +private: + // Abstracts away whether the paint is always part of the command or optional. + template static T* AsPtr(SkRecords::Optional& x) { return x; } + template static T* AsPtr(T& x) { return &x; } + + type* fPaint; +}; + +// Matches if Matcher doesn't. Stores nothing. +template +struct Not { + template + bool operator()(T* ptr) { return !Matcher()(ptr); } +}; + +// Matches if any of First or Rest... does. Stores nothing. +template +struct Or { + template + bool operator()(T* ptr) { return First()(ptr) || Or()(ptr); } +}; +template +struct Or { + template + bool operator()(T* ptr) { return First()(ptr); } +}; + + +// Greedy is a special matcher that greedily matches Matcher 0 or more times. Stores nothing. +template +struct Greedy { + template + bool operator()(T* ptr) { return Matcher()(ptr); } +}; + +// Pattern matches each of its matchers in order. +// +// This is the main entry point to pattern matching, and so provides a couple of extra API bits: +// - search scans through the record to look for matches; +// - first, second, third, ... return the data stored by their respective matchers in the pattern. + +template class Pattern; + +template <> class Pattern<> { +public: + // Bottoms out recursion. Just return whatever i the front decided on. + int match(SkRecord*, int i) { return i; } +}; + +template +class Pattern { +public: + // If this pattern matches the SkRecord starting from i, + // return the index just past the end of the pattern, otherwise return 0. + SK_ALWAYS_INLINE int match(SkRecord* record, int i) { + i = this->matchFirst(&fFirst, record, i); + return i > 0 ? fRest.match(record, i) : 0; + } + + // Starting from *end, walk through the SkRecord to find the first span matching this pattern. + // If there is no such span, return false. If there is, return true and set [*begin, *end). + SK_ALWAYS_INLINE bool search(SkRecord* record, int* begin, int* end) { + for (*begin = *end; *begin < record->count(); ++(*begin)) { + *end = this->match(record, *begin); + if (*end != 0) { + return true; + } + } + return false; + } + + // TODO: some sort of smart get() + template T* first() { return fFirst.get(); } + template T* second() { return fRest.template first(); } + template T* third() { return fRest.template second(); } + template T* fourth() { return fRest.template third(); } + +private: + // If first isn't a Greedy, try to match at i once. + template + int matchFirst(T* first, SkRecord* record, int i) { + if (i < record->count()) { + if (record->mutate(i, *first)) { + return i+1; + } + } + return 0; + } + + // If first is a Greedy, walk i until it doesn't match. + template + int matchFirst(Greedy* first, SkRecord* record, int i) { + while (i < record->count()) { + if (!record->mutate(i, *first)) { + return i; + } + i++; + } + return 0; + } + + First fFirst; + Pattern fRest; +}; + +} // namespace SkRecords + +#endif//SkRecordPattern_DEFINED diff --git a/gfx/skia/skia/src/core/SkRecordedDrawable.cpp b/gfx/skia/skia/src/core/SkRecordedDrawable.cpp new file mode 100644 index 0000000000..c9af8a2c08 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordedDrawable.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "include/core/SkPictureRecorder.h" +#include "src/core/SkPictureData.h" +#include "src/core/SkPicturePlayback.h" +#include "src/core/SkPictureRecord.h" +#include "src/core/SkRecordDraw.h" +#include "src/core/SkRecordedDrawable.h" + +void SkRecordedDrawable::onDraw(SkCanvas* canvas) { + SkDrawable* const* drawables = nullptr; + int drawableCount = 0; + if (fDrawableList) { + drawables = fDrawableList->begin(); + drawableCount = fDrawableList->count(); + } + SkRecordDraw(*fRecord, canvas, nullptr, drawables, drawableCount, fBBH.get(), nullptr); +} + +SkPicture* SkRecordedDrawable::onNewPictureSnapshot() { + SkBigPicture::SnapshotArray* pictList = nullptr; + if (fDrawableList) { + // TODO: should we plumb-down the BBHFactory and recordFlags from our host + // PictureRecorder? + pictList = fDrawableList->newDrawableSnapshot(); + } + + size_t subPictureBytes = 0; + for (int i = 0; pictList && i < pictList->count(); i++) { + subPictureBytes += pictList->begin()[i]->approximateBytesUsed(); + } + // SkBigPicture will take ownership of a ref on both fRecord and fBBH. + // We're not willing to give up our ownership, so we must ref them for SkPicture. + return new SkBigPicture(fBounds, SkRef(fRecord.get()), pictList, SkSafeRef(fBBH.get()), + subPictureBytes); +} + +void SkRecordedDrawable::flatten(SkWriteBuffer& buffer) const { + // Write the bounds. + buffer.writeRect(fBounds); + + // Create an SkPictureRecord to record the draw commands. + SkPictInfo info; + SkPictureRecord pictureRecord(SkISize::Make(fBounds.width(), fBounds.height()), 0); + + // If the query contains the whole picture, don't bother with the bounding box hierarchy. + SkBBoxHierarchy* bbh; + if (pictureRecord.getLocalClipBounds().contains(fBounds)) { + bbh = nullptr; + } else { + bbh = fBBH.get(); + } + + // Record the draw commands. + pictureRecord.beginRecording(); + SkRecordDraw(*fRecord, &pictureRecord, nullptr, fDrawableList->begin(), fDrawableList->count(), + bbh, nullptr); + pictureRecord.endRecording(); + + // Flatten the recorded commands and drawables. + SkPictureData pictureData(pictureRecord, info); + pictureData.flatten(buffer); +} + +sk_sp SkRecordedDrawable::CreateProc(SkReadBuffer& buffer) { + // Read the bounds. + SkRect bounds; + buffer.readRect(&bounds); + + // Unflatten into a SkPictureData. + SkPictInfo info; + info.setVersion(buffer.getVersion()); + info.fCullRect = bounds; + std::unique_ptr pictureData(SkPictureData::CreateFromBuffer(buffer, info)); + if (!pictureData) { + return nullptr; + } + + // Create a drawable. + SkPicturePlayback playback(pictureData.get()); + SkPictureRecorder recorder; + playback.draw(recorder.beginRecording(bounds), nullptr, &buffer); + return recorder.finishRecordingAsDrawable(); +} diff --git a/gfx/skia/skia/src/core/SkRecordedDrawable.h b/gfx/skia/skia/src/core/SkRecordedDrawable.h new file mode 100644 index 0000000000..75502cd862 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecordedDrawable.h @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkRecordedDrawable_DEFINED +#define SkRecordedDrawable_DEFINED + +#include "include/core/SkDrawable.h" +#include "src/core/SkBBoxHierarchy.h" +#include "src/core/SkRecord.h" +#include "src/core/SkRecorder.h" + +class SkRecordedDrawable : public SkDrawable { +public: + SkRecordedDrawable(sk_sp record, sk_sp bbh, + std::unique_ptr drawableList, const SkRect& bounds) + : fRecord(std::move(record)) + , fBBH(std::move(bbh)) + , fDrawableList(std::move(drawableList)) + , fBounds(bounds) + {} + + void flatten(SkWriteBuffer& buffer) const override; + +protected: + SkRect onGetBounds() override { return fBounds; } + + void onDraw(SkCanvas* canvas) override; + + SkPicture* onNewPictureSnapshot() override; + +private: + SK_FLATTENABLE_HOOKS(SkRecordedDrawable) + + sk_sp fRecord; + sk_sp fBBH; + std::unique_ptr fDrawableList; + const SkRect fBounds; +}; +#endif // SkRecordedDrawable_DEFINED diff --git a/gfx/skia/skia/src/core/SkRecorder.cpp b/gfx/skia/skia/src/core/SkRecorder.cpp new file mode 100644 index 0000000000..df584499bb --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecorder.cpp @@ -0,0 +1,410 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkRecorder.h" + +#include "include/core/SkImage.h" +#include "include/core/SkPicture.h" +#include "include/core/SkSurface.h" +#include "include/private/SkTo.h" +#include "src/core/SkBigPicture.h" +#include "src/core/SkCanvasPriv.h" +#include "src/utils/SkPatchUtils.h" + +#include + +SkDrawableList::~SkDrawableList() { + fArray.unrefAll(); +} + +SkBigPicture::SnapshotArray* SkDrawableList::newDrawableSnapshot() { + const int count = fArray.count(); + if (0 == count) { + return nullptr; + } + SkAutoTMalloc pics(count); + for (int i = 0; i < count; ++i) { + pics[i] = fArray[i]->newPictureSnapshot(); + } + return new SkBigPicture::SnapshotArray(pics.release(), count); +} + +void SkDrawableList::append(SkDrawable* drawable) { + *fArray.append() = SkRef(drawable); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +SkRecorder::SkRecorder(SkRecord* record, int width, int height, SkMiniRecorder* mr) + : SkCanvasVirtualEnforcer(width, height) + , fDrawPictureMode(Record_DrawPictureMode) + , fApproxBytesUsedBySubPictures(0) + , fRecord(record) + , fMiniRecorder(mr) {} + +SkRecorder::SkRecorder(SkRecord* record, const SkRect& bounds, SkMiniRecorder* mr) + : SkCanvasVirtualEnforcer(bounds.roundOut()) + , fDrawPictureMode(Record_DrawPictureMode) + , fApproxBytesUsedBySubPictures(0) + , fRecord(record) + , fMiniRecorder(mr) {} + +void SkRecorder::reset(SkRecord* record, const SkRect& bounds, + DrawPictureMode dpm, SkMiniRecorder* mr) { + this->forgetRecord(); + fDrawPictureMode = dpm; + fRecord = record; + this->resetCanvas(bounds.roundOut()); + fMiniRecorder = mr; +} + +void SkRecorder::forgetRecord() { + fDrawableList.reset(nullptr); + fApproxBytesUsedBySubPictures = 0; + fRecord = nullptr; +} + +// To make appending to fRecord a little less verbose. +template +void SkRecorder::append(Args&&... args) { + if (fMiniRecorder) { + this->flushMiniRecorder(); + } + new (fRecord->append()) T{std::forward(args)...}; +} + +#define TRY_MINIRECORDER(method, ...) \ + if (fMiniRecorder && fMiniRecorder->method(__VA_ARGS__)) return + +// For methods which must call back into SkNoDrawCanvas. +#define INHERITED(method, ...) this->SkNoDrawCanvas::method(__VA_ARGS__) + +// Use copy() only for optional arguments, to be copied if present or skipped if not. +// (For most types we just pass by value and let copy constructors do their thing.) +template +T* SkRecorder::copy(const T* src) { + if (nullptr == src) { + return nullptr; + } + return new (fRecord->alloc()) T(*src); +} + +// This copy() is for arrays. +// It will work with POD or non-POD, though currently we only use it for POD. +template +T* SkRecorder::copy(const T src[], size_t count) { + if (nullptr == src) { + return nullptr; + } + T* dst = fRecord->alloc(count); + for (size_t i = 0; i < count; i++) { + new (dst + i) T(src[i]); + } + return dst; +} + +// Specialization for copying strings, using memcpy. +// This measured around 2x faster for copying code points, +// but I found no corresponding speedup for other arrays. +template <> +char* SkRecorder::copy(const char src[], size_t count) { + if (nullptr == src) { + return nullptr; + } + char* dst = fRecord->alloc(count); + memcpy(dst, src, count); + return dst; +} + +// As above, assuming and copying a terminating \0. +template <> +char* SkRecorder::copy(const char* src) { + return this->copy(src, strlen(src)+1); +} + +void SkRecorder::flushMiniRecorder() { + if (fMiniRecorder) { + SkMiniRecorder* mr = fMiniRecorder; + fMiniRecorder = nullptr; // Needs to happen before flushAndReset() or we recurse forever. + mr->flushAndReset(this); + } +} + +void SkRecorder::onDrawPaint(const SkPaint& paint) { + this->append(paint); +} + +void SkRecorder::onDrawBehind(const SkPaint& paint) { + this->append(paint); +} + +void SkRecorder::onDrawPoints(PointMode mode, + size_t count, + const SkPoint pts[], + const SkPaint& paint) { + this->append(paint, mode, SkToUInt(count), this->copy(pts, count)); +} + +void SkRecorder::onDrawRect(const SkRect& rect, const SkPaint& paint) { + TRY_MINIRECORDER(drawRect, rect, paint); + this->append(paint, rect); +} + +void SkRecorder::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + this->append(paint, region); +} + +void SkRecorder::onDrawOval(const SkRect& oval, const SkPaint& paint) { + this->append(paint, oval); +} + +void SkRecorder::onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) { + this->append(paint, oval, startAngle, sweepAngle, useCenter); +} + +void SkRecorder::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { + this->append(paint, rrect); +} + +void SkRecorder::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + this->append(paint, outer, inner); +} + +void SkRecorder::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { + if (fDrawPictureMode == Record_DrawPictureMode) { + if (!fDrawableList) { + fDrawableList.reset(new SkDrawableList); + } + fDrawableList->append(drawable); + this->append(this->copy(matrix), drawable->getBounds(), fDrawableList->count() - 1); + } else { + SkASSERT(fDrawPictureMode == Playback_DrawPictureMode); + drawable->draw(this, matrix); + } +} + +void SkRecorder::onDrawPath(const SkPath& path, const SkPaint& paint) { + TRY_MINIRECORDER(drawPath, path, paint); + this->append(paint, path); +} + +void SkRecorder::onDrawBitmap(const SkBitmap& bitmap, + SkScalar left, + SkScalar top, + const SkPaint* paint) { + sk_sp image = SkImage::MakeFromBitmap(bitmap); + if (image) { + this->onDrawImage(image.get(), left, top, paint); + } +} + +void SkRecorder::onDrawBitmapRect(const SkBitmap& bitmap, + const SkRect* src, + const SkRect& dst, + const SkPaint* paint, + SrcRectConstraint constraint) { + sk_sp image = SkImage::MakeFromBitmap(bitmap); + if (image) { + this->onDrawImageRect(image.get(), src, dst, paint, constraint); + } +} + +void SkRecorder::onDrawBitmapNine(const SkBitmap& bitmap, + const SkIRect& center, + const SkRect& dst, + const SkPaint* paint) { + sk_sp image = SkImage::MakeFromBitmap(bitmap); + if (image) { + this->onDrawImageNine(image.get(), center, dst, paint); + } +} + +void SkRecorder::onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + sk_sp image = SkImage::MakeFromBitmap(bitmap); + this->onDrawImageLattice(image.get(), lattice, dst, paint); +} + +void SkRecorder::onDrawImage(const SkImage* image, SkScalar left, SkScalar top, + const SkPaint* paint) { + this->append(this->copy(paint), sk_ref_sp(image), left, top); +} + +void SkRecorder::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + this->append(this->copy(paint), sk_ref_sp(image), this->copy(src), dst, constraint); +} + +void SkRecorder::onDrawImageNine(const SkImage* image, const SkIRect& center, + const SkRect& dst, const SkPaint* paint) { + this->append(this->copy(paint), sk_ref_sp(image), center, dst); +} + +void SkRecorder::onDrawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint) { + int flagCount = lattice.fRectTypes ? (lattice.fXCount + 1) * (lattice.fYCount + 1) : 0; + SkASSERT(lattice.fBounds); + this->append(this->copy(paint), sk_ref_sp(image), + lattice.fXCount, this->copy(lattice.fXDivs, lattice.fXCount), + lattice.fYCount, this->copy(lattice.fYDivs, lattice.fYCount), + flagCount, this->copy(lattice.fRectTypes, flagCount), + this->copy(lattice.fColors, flagCount), *lattice.fBounds, dst); +} + +void SkRecorder::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + TRY_MINIRECORDER(drawTextBlob, blob, x, y, paint); + this->append(paint, sk_ref_sp(blob), x, y); +} + +void SkRecorder::onDrawPicture(const SkPicture* pic, const SkMatrix* matrix, const SkPaint* paint) { + if (fDrawPictureMode == Record_DrawPictureMode) { + fApproxBytesUsedBySubPictures += pic->approximateBytesUsed(); + this->append(this->copy(paint), sk_ref_sp(pic), matrix ? *matrix : SkMatrix::I()); + } else if (fDrawPictureMode == PlaybackTop_DrawPictureMode) { + // temporarily change the mode of this recorder to Record, + fDrawPictureMode = Record_DrawPictureMode; + // play back the top level picture + SkAutoCanvasMatrixPaint acmp(this, matrix, paint, pic->cullRect()); + pic->playback(this); + // restore the mode + fDrawPictureMode = PlaybackTop_DrawPictureMode; + } else { + SkASSERT(fDrawPictureMode == Playback_DrawPictureMode); + SkAutoCanvasMatrixPaint acmp(this, matrix, paint, pic->cullRect()); + pic->playback(this); + } +} + +void SkRecorder::onDrawVerticesObject(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode bmode, const SkPaint& paint) { + this->append(paint, + sk_ref_sp(const_cast(vertices)), + this->copy(bones, boneCount), + boneCount, + bmode); +} + +void SkRecorder::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode bmode, + const SkPaint& paint) { + this->append(paint, + cubics ? this->copy(cubics, SkPatchUtils::kNumCtrlPts) : nullptr, + colors ? this->copy(colors, SkPatchUtils::kNumCorners) : nullptr, + texCoords ? this->copy(texCoords, SkPatchUtils::kNumCorners) : nullptr, + bmode); +} + +void SkRecorder::onDrawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], + const SkColor colors[], int count, SkBlendMode mode, + const SkRect* cull, const SkPaint* paint) { + this->append(this->copy(paint), + sk_ref_sp(atlas), + this->copy(xform, count), + this->copy(tex, count), + this->copy(colors, count), + count, + mode, + this->copy(cull)); +} + +void SkRecorder::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + this->append(path, rec); +} + +void SkRecorder::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) { + this->append(rect, SkString(key), sk_ref_sp(value)); +} + +void SkRecorder::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + QuadAAFlags aa, const SkColor4f& color, SkBlendMode mode) { + this->append( + rect, this->copy(clip, 4), aa, color, mode); +} + +void SkRecorder::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint* paint, SrcRectConstraint constraint) { + int totalDstClipCount, totalMatrixCount; + SkCanvasPriv::GetDstClipAndMatrixCounts(set, count, &totalDstClipCount, &totalMatrixCount); + + SkAutoTArray setCopy(count); + for (int i = 0; i < count; ++i) { + setCopy[i] = set[i]; + } + + this->append(this->copy(paint), std::move(setCopy), count, + this->copy(dstClips, totalDstClipCount), + this->copy(preViewMatrices, totalMatrixCount), constraint); +} + +void SkRecorder::onFlush() { + this->append(); +} + +void SkRecorder::willSave() { + this->append(); +} + +SkCanvas::SaveLayerStrategy SkRecorder::getSaveLayerStrategy(const SaveLayerRec& rec) { + this->append(this->copy(rec.fBounds) + , this->copy(rec.fPaint) + , sk_ref_sp(rec.fBackdrop) + , sk_ref_sp(rec.fClipMask) + , this->copy(rec.fClipMatrix) + , rec.fSaveLayerFlags); + return SkCanvas::kNoLayer_SaveLayerStrategy; +} + +bool SkRecorder::onDoSaveBehind(const SkRect* subset) { + this->append(this->copy(subset)); + return false; +} + +void SkRecorder::didRestore() { + this->append(this->getTotalMatrix()); +} + +void SkRecorder::didConcat(const SkMatrix& matrix) { + this->append(matrix); +} + +void SkRecorder::didSetMatrix(const SkMatrix& matrix) { + this->append(matrix); +} + +void SkRecorder::didTranslate(SkScalar dx, SkScalar dy) { + this->append(dx, dy); +} + +void SkRecorder::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) { + INHERITED(onClipRect, rect, op, edgeStyle); + SkRecords::ClipOpAndAA opAA(op, kSoft_ClipEdgeStyle == edgeStyle); + this->append(rect, opAA); +} + +void SkRecorder::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) { + INHERITED(onClipRRect, rrect, op, edgeStyle); + SkRecords::ClipOpAndAA opAA(op, kSoft_ClipEdgeStyle == edgeStyle); + this->append(rrect, opAA); +} + +void SkRecorder::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) { + INHERITED(onClipPath, path, op, edgeStyle); + SkRecords::ClipOpAndAA opAA(op, kSoft_ClipEdgeStyle == edgeStyle); + this->append(path, opAA); +} + +void SkRecorder::onClipRegion(const SkRegion& deviceRgn, SkClipOp op) { + INHERITED(onClipRegion, deviceRgn, op); + this->append(deviceRgn, op); +} + +sk_sp SkRecorder::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) { + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkRecorder.h b/gfx/skia/skia/src/core/SkRecorder.h new file mode 100644 index 0000000000..c20f84cf8c --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecorder.h @@ -0,0 +1,149 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecorder_DEFINED +#define SkRecorder_DEFINED + +#include "include/core/SkCanvasVirtualEnforcer.h" +#include "include/private/SkTDArray.h" +#include "include/utils/SkNoDrawCanvas.h" +#include "src/core/SkBigPicture.h" +#include "src/core/SkMiniRecorder.h" +#include "src/core/SkRecord.h" +#include "src/core/SkRecords.h" + +class SkBBHFactory; + +class SkDrawableList : SkNoncopyable { +public: + SkDrawableList() {} + ~SkDrawableList(); + + int count() const { return fArray.count(); } + SkDrawable* const* begin() const { return fArray.begin(); } + + void append(SkDrawable* drawable); + + // Return a new or ref'd array of pictures that were snapped from our drawables. + SkBigPicture::SnapshotArray* newDrawableSnapshot(); + +private: + SkTDArray fArray; +}; + +// SkRecorder provides an SkCanvas interface for recording into an SkRecord. + +class SkRecorder final : public SkCanvasVirtualEnforcer { +public: + // Does not take ownership of the SkRecord. + SkRecorder(SkRecord*, int width, int height, SkMiniRecorder* = nullptr); // TODO: remove + SkRecorder(SkRecord*, const SkRect& bounds, SkMiniRecorder* = nullptr); + + enum DrawPictureMode { + Record_DrawPictureMode, + Playback_DrawPictureMode, + // Plays back top level drawPicture calls only, but records pictures within those. + PlaybackTop_DrawPictureMode, + }; + void reset(SkRecord*, const SkRect& bounds, DrawPictureMode, SkMiniRecorder* = nullptr); + + size_t approxBytesUsedBySubPictures() const { return fApproxBytesUsedBySubPictures; } + + SkDrawableList* getDrawableList() const { return fDrawableList.get(); } + std::unique_ptr detachDrawableList() { return std::move(fDrawableList); } + + // Make SkRecorder forget entirely about its SkRecord*; all calls to SkRecorder will fail. + void forgetRecord(); + + void onFlush() override; + + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + bool onDoSaveBehind(const SkRect*) override; + void willRestore() override {} + void didRestore() override; + + void didConcat(const SkMatrix&) override; + void didSetMatrix(const SkMatrix&) override; + void didTranslate(SkScalar, SkScalar) override; + + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + void onDrawTextBlob(const SkTextBlob* blob, + SkScalar x, + SkScalar y, + const SkPaint& paint) override; + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode, + const SkPaint& paint) override; + + void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*, + SrcRectConstraint) override; + void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) override; + void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst, + const SkPaint*) override; + void onDrawBitmapLattice(const SkBitmap&, const Lattice& lattice, const SkRect& dst, + const SkPaint*) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], + int count, SkBlendMode, const SkRect* cull, const SkPaint*) override; + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + + void onClipRect(const SkRect& rect, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect& rrect, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath& path, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion& deviceRgn, SkClipOp) override; + + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + + void onDrawAnnotation(const SkRect&, const char[], SkData*) override; + + void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, const SkColor4f&, + SkBlendMode) override; + void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[], + const SkPaint*, SrcRectConstraint) override; + + sk_sp onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + void flushMiniRecorder(); + +private: + template + T* copy(const T*); + + template + T* copy(const T[], size_t count); + + template + void append(Args&&...); + + DrawPictureMode fDrawPictureMode; + size_t fApproxBytesUsedBySubPictures; + SkRecord* fRecord; + std::unique_ptr fDrawableList; + + SkMiniRecorder* fMiniRecorder; +}; + +#endif//SkRecorder_DEFINED diff --git a/gfx/skia/skia/src/core/SkRecords.cpp b/gfx/skia/skia/src/core/SkRecords.cpp new file mode 100644 index 0000000000..1b27660b78 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecords.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkPathPriv.h" +#include "src/core/SkRecords.h" + +namespace SkRecords { + PreCachedPath::PreCachedPath(const SkPath& path) : SkPath(path) { + this->updateBoundsCache(); + (void)this->getGenerationID(); +#if 0 // Disabled to see if we ever really race on this. It costs time, chromium:496982. + SkPathPriv::FirstDirection junk; + (void)SkPathPriv::CheapComputeFirstDirection(*this, &junk); +#endif + } + + TypedMatrix::TypedMatrix(const SkMatrix& matrix) : SkMatrix(matrix) { + (void)this->getType(); + } +} diff --git a/gfx/skia/skia/src/core/SkRecords.h b/gfx/skia/skia/src/core/SkRecords.h new file mode 100644 index 0000000000..2c246961bc --- /dev/null +++ b/gfx/skia/skia/src/core/SkRecords.h @@ -0,0 +1,346 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecords_DEFINED +#define SkRecords_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRSXform.h" +#include "include/core/SkRect.h" +#include "include/core/SkRegion.h" +#include "include/core/SkString.h" +#include "include/core/SkTextBlob.h" +#include "include/core/SkVertices.h" +#include "src/core/SkDrawShadowInfo.h" + +namespace SkRecords { + +// A list of all the types of canvas calls we can record. +// Each of these is reified into a struct below. +// +// (We're using the macro-of-macro trick here to do several different things with the same list.) +// +// We leave this SK_RECORD_TYPES macro defined for use by code that wants to operate on SkRecords +// types polymorphically. (See SkRecord::Record::{visit,mutate} for an example.) +// +// Order doesn't technically matter here, but the compiler can generally generate better code if +// you keep them semantically grouped, especially the Draws. It's also nice to leave NoOp at 0. +#define SK_RECORD_TYPES(M) \ + M(NoOp) \ + M(Flush) \ + M(Restore) \ + M(Save) \ + M(SaveLayer) \ + M(SaveBehind) \ + M(SetMatrix) \ + M(Translate) \ + M(Concat) \ + M(ClipPath) \ + M(ClipRRect) \ + M(ClipRect) \ + M(ClipRegion) \ + M(DrawArc) \ + M(DrawDrawable) \ + M(DrawImage) \ + M(DrawImageLattice) \ + M(DrawImageRect) \ + M(DrawImageNine) \ + M(DrawDRRect) \ + M(DrawOval) \ + M(DrawBehind) \ + M(DrawPaint) \ + M(DrawPath) \ + M(DrawPatch) \ + M(DrawPicture) \ + M(DrawPoints) \ + M(DrawRRect) \ + M(DrawRect) \ + M(DrawRegion) \ + M(DrawTextBlob) \ + M(DrawAtlas) \ + M(DrawVertices) \ + M(DrawShadowRec) \ + M(DrawAnnotation) \ + M(DrawEdgeAAQuad) \ + M(DrawEdgeAAImageSet) + + +// Defines SkRecords::Type, an enum of all record types. +#define ENUM(T) T##_Type, +enum Type { SK_RECORD_TYPES(ENUM) }; +#undef ENUM + +#define ACT_AS_PTR(ptr) \ + operator T*() const { return ptr; } \ + T* operator->() const { return ptr; } + +// An Optional doesn't own the pointer's memory, but may need to destroy non-POD data. +template +class Optional { +public: + Optional() : fPtr(nullptr) {} + Optional(T* ptr) : fPtr(ptr) {} + Optional(Optional&& o) : fPtr(o.fPtr) { + o.fPtr = nullptr; + } + ~Optional() { if (fPtr) fPtr->~T(); } + + ACT_AS_PTR(fPtr) +private: + T* fPtr; + Optional(const Optional&) = delete; + Optional& operator=(const Optional&) = delete; +}; + +// Like Optional, but ptr must not be NULL. +template +class Adopted { +public: + Adopted(T* ptr) : fPtr(ptr) { SkASSERT(fPtr); } + Adopted(Adopted* source) { + // Transfer ownership from source to this. + fPtr = source->fPtr; + source->fPtr = NULL; + } + ~Adopted() { if (fPtr) fPtr->~T(); } + + ACT_AS_PTR(fPtr) +private: + T* fPtr; + Adopted(const Adopted&) = delete; + Adopted& operator=(const Adopted&) = delete; +}; + +// PODArray doesn't own the pointer's memory, and we assume the data is POD. +template +class PODArray { +public: + PODArray() {} + PODArray(T* ptr) : fPtr(ptr) {} + // Default copy and assign. + + ACT_AS_PTR(fPtr) +private: + T* fPtr; +}; + +#undef ACT_AS_PTR + +// SkPath::getBounds() isn't thread safe unless we precache the bounds in a singlethreaded context. +// SkPath::cheapComputeDirection() is similar. +// Recording is a convenient time to cache these, or we can delay it to between record and playback. +struct PreCachedPath : public SkPath { + PreCachedPath() {} + PreCachedPath(const SkPath& path); +}; + +// Like SkPath::getBounds(), SkMatrix::getType() isn't thread safe unless we precache it. +// This may not cover all SkMatrices used by the picture (e.g. some could be hiding in a shader). +struct TypedMatrix : public SkMatrix { + TypedMatrix() {} + TypedMatrix(const SkMatrix& matrix); +}; + +enum Tags { + kDraw_Tag = 1, // May draw something (usually named DrawFoo). + kHasImage_Tag = 2, // Contains an SkImage or SkBitmap. + kHasText_Tag = 4, // Contains text. + kHasPaint_Tag = 8, // May have an SkPaint field, at least optionally. + + kDrawWithPaint_Tag = kDraw_Tag | kHasPaint_Tag, +}; + +// A macro to make it a little easier to define a struct that can be stored in SkRecord. +#define RECORD(T, tags, ...) \ +struct T { \ + static const Type kType = T##_Type; \ + static const int kTags = tags; \ + __VA_ARGS__; \ +}; + +RECORD(NoOp, 0); +RECORD(Flush, 0); +RECORD(Restore, 0, + TypedMatrix matrix); +RECORD(Save, 0); + +RECORD(SaveLayer, kHasPaint_Tag, + Optional bounds; + Optional paint; + sk_sp backdrop; + sk_sp clipMask; + Optional clipMatrix; + SkCanvas::SaveLayerFlags saveLayerFlags); + +RECORD(SaveBehind, 0, + Optional subset); + +RECORD(SetMatrix, 0, + TypedMatrix matrix); +RECORD(Concat, 0, + TypedMatrix matrix); + +RECORD(Translate, 0, + SkScalar dx; + SkScalar dy); + +struct ClipOpAndAA { + ClipOpAndAA() {} + ClipOpAndAA(SkClipOp op, bool aa) : fOp(static_cast(op)), fAA(aa) {} + + SkClipOp op() const { return static_cast(fOp); } + bool aa() const { return fAA != 0; } + +private: + unsigned fOp : 31; // This really only needs to be 3, but there's no win today to do so. + unsigned fAA : 1; // MSVC won't pack an enum with an bool, so we call this an unsigned. +}; +static_assert(sizeof(ClipOpAndAA) == 4, "ClipOpAndAASize"); + +RECORD(ClipPath, 0, + PreCachedPath path; + ClipOpAndAA opAA); +RECORD(ClipRRect, 0, + SkRRect rrect; + ClipOpAndAA opAA); +RECORD(ClipRect, 0, + SkRect rect; + ClipOpAndAA opAA); +RECORD(ClipRegion, 0, + SkRegion region; + SkClipOp op); + +// While not strictly required, if you have an SkPaint, it's fastest to put it first. +RECORD(DrawArc, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkRect oval; + SkScalar startAngle; + SkScalar sweepAngle; + unsigned useCenter); +RECORD(DrawDRRect, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkRRect outer; + SkRRect inner); +RECORD(DrawDrawable, kDraw_Tag, + Optional matrix; + SkRect worstCaseBounds; + int32_t index); +RECORD(DrawImage, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag, + Optional paint; + sk_sp image; + SkScalar left; + SkScalar top); +RECORD(DrawImageLattice, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag, + Optional paint; + sk_sp image; + int xCount; + PODArray xDivs; + int yCount; + PODArray yDivs; + int flagCount; + PODArray flags; + PODArray colors; + SkIRect src; + SkRect dst); +RECORD(DrawImageRect, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag, + Optional paint; + sk_sp image; + Optional src; + SkRect dst; + SkCanvas::SrcRectConstraint constraint); +RECORD(DrawImageNine, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag, + Optional paint; + sk_sp image; + SkIRect center; + SkRect dst); +RECORD(DrawOval, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkRect oval); +RECORD(DrawPaint, kDraw_Tag|kHasPaint_Tag, + SkPaint paint); +RECORD(DrawBehind, kDraw_Tag|kHasPaint_Tag, + SkPaint paint); +RECORD(DrawPath, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + PreCachedPath path); +RECORD(DrawPicture, kDraw_Tag|kHasPaint_Tag, + Optional paint; + sk_sp picture; + TypedMatrix matrix); +RECORD(DrawPoints, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkCanvas::PointMode mode; + unsigned count; + SkPoint* pts); +RECORD(DrawRRect, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkRRect rrect); +RECORD(DrawRect, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkRect rect); +RECORD(DrawRegion, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + SkRegion region); +RECORD(DrawTextBlob, kDraw_Tag|kHasText_Tag|kHasPaint_Tag, + SkPaint paint; + sk_sp blob; + SkScalar x; + SkScalar y); +RECORD(DrawPatch, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + PODArray cubics; + PODArray colors; + PODArray texCoords; + SkBlendMode bmode); +RECORD(DrawAtlas, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag, + Optional paint; + sk_sp atlas; + PODArray xforms; + PODArray texs; + PODArray colors; + int count; + SkBlendMode mode; + Optional cull); +RECORD(DrawVertices, kDraw_Tag|kHasPaint_Tag, + SkPaint paint; + sk_sp vertices; + PODArray bones; + int boneCount; + SkBlendMode bmode); +RECORD(DrawShadowRec, kDraw_Tag, + PreCachedPath path; + SkDrawShadowRec rec); +RECORD(DrawAnnotation, 0, // TODO: kDraw_Tag, skia:5548 + SkRect rect; + SkString key; + sk_sp value); +RECORD(DrawEdgeAAQuad, kDraw_Tag, + SkRect rect; + PODArray clip; + SkCanvas::QuadAAFlags aa; + SkColor4f color; + SkBlendMode mode); +RECORD(DrawEdgeAAImageSet, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag, + Optional paint; + SkAutoTArray set; + int count; + PODArray dstClips; + PODArray preViewMatrices; + SkCanvas::SrcRectConstraint constraint); +#undef RECORD + +} // namespace SkRecords + +#endif//SkRecords_DEFINED diff --git a/gfx/skia/skia/src/core/SkRect.cpp b/gfx/skia/skia/src/core/SkRect.cpp new file mode 100644 index 0000000000..37f054a053 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRect.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRect.h" + +#include "include/private/SkMalloc.h" + +bool SkIRect::intersect(const SkIRect& a, const SkIRect& b) { + SkIRect tmp = { + SkMax32(a.fLeft, b.fLeft), + SkMax32(a.fTop, b.fTop), + SkMin32(a.fRight, b.fRight), + SkMin32(a.fBottom, b.fBottom) + }; + if (tmp.isEmpty()) { + return false; + } + *this = tmp; + return true; +} + +void SkIRect::join(const SkIRect& r) { + // do nothing if the params are empty + if (r.fLeft >= r.fRight || r.fTop >= r.fBottom) { + return; + } + + // if we are empty, just assign + if (fLeft >= fRight || fTop >= fBottom) { + *this = r; + } else { + if (r.fLeft < fLeft) fLeft = r.fLeft; + if (r.fTop < fTop) fTop = r.fTop; + if (r.fRight > fRight) fRight = r.fRight; + if (r.fBottom > fBottom) fBottom = r.fBottom; + } +} + +///////////////////////////////////////////////////////////////////////////// + +void SkRect::toQuad(SkPoint quad[4]) const { + SkASSERT(quad); + + quad[0].set(fLeft, fTop); + quad[1].set(fRight, fTop); + quad[2].set(fRight, fBottom); + quad[3].set(fLeft, fBottom); +} + +#include "include/private/SkNx.h" + +bool SkRect::setBoundsCheck(const SkPoint pts[], int count) { + SkASSERT((pts && count > 0) || count == 0); + + if (count <= 0) { + this->setEmpty(); + return true; + } + + Sk4s min, max; + if (count & 1) { + min = max = Sk4s(pts->fX, pts->fY, + pts->fX, pts->fY); + pts += 1; + count -= 1; + } else { + min = max = Sk4s::Load(pts); + pts += 2; + count -= 2; + } + + Sk4s accum = min * 0; + while (count) { + Sk4s xy = Sk4s::Load(pts); + accum = accum * xy; + min = Sk4s::Min(min, xy); + max = Sk4s::Max(max, xy); + pts += 2; + count -= 2; + } + + bool all_finite = (accum * 0 == 0).allTrue(); + if (all_finite) { + this->setLTRB(SkTMin(min[0], min[2]), SkTMin(min[1], min[3]), + SkTMax(max[0], max[2]), SkTMax(max[1], max[3])); + } else { + this->setEmpty(); + } + return all_finite; +} + +void SkRect::setBoundsNoCheck(const SkPoint pts[], int count) { + if (!this->setBoundsCheck(pts, count)) { + this->setLTRB(SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN); + } +} + +#define CHECK_INTERSECT(al, at, ar, ab, bl, bt, br, bb) \ + SkScalar L = SkMaxScalar(al, bl); \ + SkScalar R = SkMinScalar(ar, br); \ + SkScalar T = SkMaxScalar(at, bt); \ + SkScalar B = SkMinScalar(ab, bb); \ + do { if (!(L < R && T < B)) return false; } while (0) + // do the !(opposite) check so we return false if either arg is NaN + +bool SkRect::intersect(const SkRect& r) { + CHECK_INTERSECT(r.fLeft, r.fTop, r.fRight, r.fBottom, fLeft, fTop, fRight, fBottom); + this->setLTRB(L, T, R, B); + return true; +} + +bool SkRect::intersect(const SkRect& a, const SkRect& b) { + CHECK_INTERSECT(a.fLeft, a.fTop, a.fRight, a.fBottom, b.fLeft, b.fTop, b.fRight, b.fBottom); + this->setLTRB(L, T, R, B); + return true; +} + +void SkRect::join(const SkRect& r) { + if (r.isEmpty()) { + return; + } + + if (this->isEmpty()) { + *this = r; + } else { + fLeft = SkMinScalar(fLeft, r.fLeft); + fTop = SkMinScalar(fTop, r.fTop); + fRight = SkMaxScalar(fRight, r.fRight); + fBottom = SkMaxScalar(fBottom, r.fBottom); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkString.h" +#include "src/core/SkStringUtils.h" + +static const char* set_scalar(SkString* storage, SkScalar value, SkScalarAsStringType asType) { + storage->reset(); + SkAppendScalar(storage, value, asType); + return storage->c_str(); +} + +void SkRect::dump(bool asHex) const { + SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType; + + SkString line; + if (asHex) { + SkString tmp; + line.printf( "SkRect::MakeLTRB(%s, /* %f */\n", set_scalar(&tmp, fLeft, asType), fLeft); + line.appendf(" %s, /* %f */\n", set_scalar(&tmp, fTop, asType), fTop); + line.appendf(" %s, /* %f */\n", set_scalar(&tmp, fRight, asType), fRight); + line.appendf(" %s /* %f */);", set_scalar(&tmp, fBottom, asType), fBottom); + } else { + SkString strL, strT, strR, strB; + SkAppendScalarDec(&strL, fLeft); + SkAppendScalarDec(&strT, fTop); + SkAppendScalarDec(&strR, fRight); + SkAppendScalarDec(&strB, fBottom); + line.printf("SkRect::MakeLTRB(%s, %s, %s, %s);", + strL.c_str(), strT.c_str(), strR.c_str(), strB.c_str()); + } + SkDebugf("%s\n", line.c_str()); +} diff --git a/gfx/skia/skia/src/core/SkRectPriv.h b/gfx/skia/skia/src/core/SkRectPriv.h new file mode 100644 index 0000000000..e812f5ef36 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRectPriv.h @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRectPriv_DEFINED +#define SkRectPriv_DEFINED + +#include "include/core/SkRect.h" +#include "src/core/SkMathPriv.h" + +class SkRectPriv { +public: + // Returns an irect that is very large, and can be safely round-trip with SkRect and still + // be considered non-empty (i.e. width/height > 0) even if we round-out the SkRect. + static SkIRect MakeILarge() { + // SK_MaxS32 >> 1 seemed better, but it did not survive round-trip with SkRect and rounding. + // Also, 1 << 29 can be perfectly represented in float, while SK_MaxS32 >> 1 cannot. + const int32_t large = 1 << 29; + return { -large, -large, large, large }; + } + + static SkIRect MakeILargestInverted() { + return { SK_MaxS32, SK_MaxS32, SK_MinS32, SK_MinS32 }; + } + + static SkRect MakeLargeS32() { + SkRect r; + r.set(MakeILarge()); + return r; + } + + static SkRect MakeLargest() { + return { SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax }; + } + + static constexpr SkRect MakeLargestInverted() { + return { SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin }; + } + + static void GrowToInclude(SkRect* r, const SkPoint& pt) { + r->fLeft = SkMinScalar(pt.fX, r->fLeft); + r->fRight = SkMaxScalar(pt.fX, r->fRight); + r->fTop = SkMinScalar(pt.fY, r->fTop); + r->fBottom = SkMaxScalar(pt.fY, r->fBottom); + } + + // Conservative check if r can be expressed in fixed-point. + // Will return false for very large values that might have fit + static bool FitsInFixed(const SkRect& r) { + return SkFitsInFixed(r.fLeft) && SkFitsInFixed(r.fTop) && + SkFitsInFixed(r.fRight) && SkFitsInFixed(r.fBottom); + } + + static bool Is16Bit(const SkIRect& r) { + return SkTFitsIn(r.fLeft) && SkTFitsIn(r.fTop) && + SkTFitsIn(r.fRight) && SkTFitsIn(r.fBottom); + } +}; + + +#endif diff --git a/gfx/skia/skia/src/core/SkRegion.cpp b/gfx/skia/skia/src/core/SkRegion.cpp new file mode 100644 index 0000000000..efa50bc539 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRegion.cpp @@ -0,0 +1,1582 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRegion.h" + +#include "include/private/SkMacros.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkRegionPriv.h" +#include "src/core/SkSafeMath.h" +#include "src/utils/SkUTF.h" + +#include + +/* Region Layout + * + * TOP + * + * [ Bottom, X-Intervals, [Left, Right]..., X-Sentinel ] + * ... + * + * Y-Sentinel + */ + +///////////////////////////////////////////////////////////////////////////////////////////////// + +#define SkRegion_gEmptyRunHeadPtr ((SkRegionPriv::RunHead*)-1) +#define SkRegion_gRectRunHeadPtr nullptr + +constexpr int kRunArrayStackCount = 256; + +// This is a simple data structure which is like a SkSTArray, except that: +// - It does not initialize memory. +// - It does not distinguish between reserved space and initialized space. +// - resizeToAtLeast() instead of resize() +// - Uses sk_realloc_throw() +// - Can never be made smaller. +// Measurement: for the `region_union_16` benchmark, this is 6% faster. +class RunArray { +public: + RunArray() { fPtr = fStack; } + #ifdef SK_DEBUG + int count() const { return fCount; } + #endif + SkRegionPriv::RunType& operator[](int i) { + SkASSERT((unsigned)i < (unsigned)fCount); + return fPtr[i]; + } + /** Resize the array to a size greater-than-or-equal-to count. */ + void resizeToAtLeast(int count) { + if (count > fCount) { + // leave at least 50% extra space for future growth. + count += count >> 1; + fMalloc.realloc(count); + if (fPtr == fStack) { + memcpy(fMalloc.get(), fStack, fCount * sizeof(SkRegionPriv::RunType)); + } + fPtr = fMalloc.get(); + fCount = count; + } + } +private: + SkRegionPriv::RunType fStack[kRunArrayStackCount]; + SkAutoTMalloc fMalloc; + int fCount = kRunArrayStackCount; + SkRegionPriv::RunType* fPtr; // non-owning pointer +}; + +/* Pass in the beginning with the intervals. + * We back up 1 to read the interval-count. + * Return the beginning of the next scanline (i.e. the next Y-value) + */ +static SkRegionPriv::RunType* skip_intervals(const SkRegionPriv::RunType runs[]) { + int intervals = runs[-1]; +#ifdef SK_DEBUG + if (intervals > 0) { + SkASSERT(runs[0] < runs[1]); + SkASSERT(runs[1] < SkRegion_kRunTypeSentinel); + } else { + SkASSERT(0 == intervals); + SkASSERT(SkRegion_kRunTypeSentinel == runs[0]); + } +#endif + runs += intervals * 2 + 1; + return const_cast(runs); +} + +bool SkRegion::RunsAreARect(const SkRegion::RunType runs[], int count, + SkIRect* bounds) { + assert_sentinel(runs[0], false); // top + SkASSERT(count >= kRectRegionRuns); + + if (count == kRectRegionRuns) { + assert_sentinel(runs[1], false); // bottom + SkASSERT(1 == runs[2]); + assert_sentinel(runs[3], false); // left + assert_sentinel(runs[4], false); // right + assert_sentinel(runs[5], true); + assert_sentinel(runs[6], true); + + SkASSERT(runs[0] < runs[1]); // valid height + SkASSERT(runs[3] < runs[4]); // valid width + + bounds->setLTRB(runs[3], runs[0], runs[4], runs[1]); + return true; + } + return false; +} + +////////////////////////////////////////////////////////////////////////// + +SkRegion::SkRegion() { + fBounds.setEmpty(); + fRunHead = SkRegion_gEmptyRunHeadPtr; +} + +SkRegion::SkRegion(const SkRegion& src) { + fRunHead = SkRegion_gEmptyRunHeadPtr; // just need a value that won't trigger sk_free(fRunHead) + this->setRegion(src); +} + +SkRegion::SkRegion(const SkIRect& rect) { + fRunHead = SkRegion_gEmptyRunHeadPtr; // just need a value that won't trigger sk_free(fRunHead) + this->setRect(rect); +} + +SkRegion::~SkRegion() { + this->freeRuns(); +} + +void SkRegion::freeRuns() { + if (this->isComplex()) { + SkASSERT(fRunHead->fRefCnt >= 1); + if (--fRunHead->fRefCnt == 0) { + sk_free(fRunHead); + } + } +} + +void SkRegion::allocateRuns(int count, int ySpanCount, int intervalCount) { + fRunHead = RunHead::Alloc(count, ySpanCount, intervalCount); +} + +void SkRegion::allocateRuns(int count) { + fRunHead = RunHead::Alloc(count); +} + +void SkRegion::allocateRuns(const RunHead& head) { + fRunHead = RunHead::Alloc(head.fRunCount, + head.getYSpanCount(), + head.getIntervalCount()); +} + +SkRegion& SkRegion::operator=(const SkRegion& src) { + (void)this->setRegion(src); + return *this; +} + +void SkRegion::swap(SkRegion& other) { + using std::swap; + swap(fBounds, other.fBounds); + swap(fRunHead, other.fRunHead); +} + +int SkRegion::computeRegionComplexity() const { + if (this->isEmpty()) { + return 0; + } else if (this->isRect()) { + return 1; + } + return fRunHead->getIntervalCount(); +} + +bool SkRegion::setEmpty() { + this->freeRuns(); + fBounds.setEmpty(); + fRunHead = SkRegion_gEmptyRunHeadPtr; + return false; +} + +bool SkRegion::setRect(const SkIRect& r) { + if (r.isEmpty() || + SkRegion_kRunTypeSentinel == r.right() || + SkRegion_kRunTypeSentinel == r.bottom()) { + return this->setEmpty(); + } + this->freeRuns(); + fBounds = r; + fRunHead = SkRegion_gRectRunHeadPtr; + return true; +} + +bool SkRegion::setRegion(const SkRegion& src) { + if (this != &src) { + this->freeRuns(); + + fBounds = src.fBounds; + fRunHead = src.fRunHead; + if (this->isComplex()) { + fRunHead->fRefCnt++; + } + } + return fRunHead != SkRegion_gEmptyRunHeadPtr; +} + +bool SkRegion::op(const SkIRect& rect, const SkRegion& rgn, Op op) { + SkRegion tmp(rect); + + return this->op(tmp, rgn, op); +} + +bool SkRegion::op(const SkRegion& rgn, const SkIRect& rect, Op op) { + SkRegion tmp(rect); + + return this->op(rgn, tmp, op); +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK +#include +char* SkRegion::toString() { + Iterator iter(*this); + int count = 0; + while (!iter.done()) { + count++; + iter.next(); + } + // 4 ints, up to 10 digits each plus sign, 3 commas, '(', ')', SkRegion() and '\0' + const int max = (count*((11*4)+5))+11+1; + char* result = (char*)sk_malloc_throw(max); + if (result == nullptr) { + return nullptr; + } + count = snprintf(result, max, "SkRegion("); + iter.reset(*this); + while (!iter.done()) { + const SkIRect& r = iter.rect(); + count += snprintf(result+count, max - count, + "(%d,%d,%d,%d)", r.fLeft, r.fTop, r.fRight, r.fBottom); + iter.next(); + } + count += snprintf(result+count, max - count, ")"); + return result; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +int SkRegion::count_runtype_values(int* itop, int* ibot) const { + int maxT; + + if (this->isRect()) { + maxT = 2; + } else { + SkASSERT(this->isComplex()); + maxT = fRunHead->getIntervalCount() * 2; + } + *itop = fBounds.fTop; + *ibot = fBounds.fBottom; + return maxT; +} + +static bool isRunCountEmpty(int count) { + return count <= 2; +} + +bool SkRegion::setRuns(RunType runs[], int count) { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + SkASSERT(count > 0); + + if (isRunCountEmpty(count)) { + // SkDEBUGF("setRuns: empty\n"); + assert_sentinel(runs[count-1], true); + return this->setEmpty(); + } + + // trim off any empty spans from the top and bottom + // weird I should need this, perhaps op() could be smarter... + if (count > kRectRegionRuns) { + RunType* stop = runs + count; + assert_sentinel(runs[0], false); // top + assert_sentinel(runs[1], false); // bottom + // runs[2] is uncomputed intervalCount + + if (runs[3] == SkRegion_kRunTypeSentinel) { // should be first left... + runs += 3; // skip empty initial span + runs[0] = runs[-2]; // set new top to prev bottom + assert_sentinel(runs[1], false); // bot: a sentinal would mean two in a row + assert_sentinel(runs[2], false); // intervalcount + assert_sentinel(runs[3], false); // left + assert_sentinel(runs[4], false); // right + } + + assert_sentinel(stop[-1], true); + assert_sentinel(stop[-2], true); + + // now check for a trailing empty span + if (stop[-5] == SkRegion_kRunTypeSentinel) { // eek, stop[-4] was a bottom with no x-runs + stop[-4] = SkRegion_kRunTypeSentinel; // kill empty last span + stop -= 3; + assert_sentinel(stop[-1], true); // last y-sentinel + assert_sentinel(stop[-2], true); // last x-sentinel + assert_sentinel(stop[-3], false); // last right + assert_sentinel(stop[-4], false); // last left + assert_sentinel(stop[-5], false); // last interval-count + assert_sentinel(stop[-6], false); // last bottom + } + count = (int)(stop - runs); + } + + SkASSERT(count >= kRectRegionRuns); + + if (SkRegion::RunsAreARect(runs, count, &fBounds)) { + return this->setRect(fBounds); + } + + // if we get here, we need to become a complex region + + if (!this->isComplex() || fRunHead->fRunCount != count) { + this->freeRuns(); + this->allocateRuns(count); + SkASSERT(this->isComplex()); + } + + // must call this before we can write directly into runs() + // in case we are sharing the buffer with another region (copy on write) + fRunHead = fRunHead->ensureWritable(); + memcpy(fRunHead->writable_runs(), runs, count * sizeof(RunType)); + fRunHead->computeRunBounds(&fBounds); + + // Our computed bounds might be too large, so we have to check here. + if (fBounds.isEmpty()) { + return this->setEmpty(); + } + + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + + return true; +} + +void SkRegion::BuildRectRuns(const SkIRect& bounds, + RunType runs[kRectRegionRuns]) { + runs[0] = bounds.fTop; + runs[1] = bounds.fBottom; + runs[2] = 1; // 1 interval for this scanline + runs[3] = bounds.fLeft; + runs[4] = bounds.fRight; + runs[5] = SkRegion_kRunTypeSentinel; + runs[6] = SkRegion_kRunTypeSentinel; +} + +bool SkRegion::contains(int32_t x, int32_t y) const { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + + if (!fBounds.contains(x, y)) { + return false; + } + if (this->isRect()) { + return true; + } + SkASSERT(this->isComplex()); + + const RunType* runs = fRunHead->findScanline(y); + + // Skip the Bottom and IntervalCount + runs += 2; + + // Just walk this scanline, checking each interval. The X-sentinel will + // appear as a left-inteval (runs[0]) and should abort the search. + // + // We could do a bsearch, using interval-count (runs[1]), but need to time + // when that would be worthwhile. + // + for (;;) { + if (x < runs[0]) { + break; + } + if (x < runs[1]) { + return true; + } + runs += 2; + } + return false; +} + +static SkRegionPriv::RunType scanline_bottom(const SkRegionPriv::RunType runs[]) { + return runs[0]; +} + +static const SkRegionPriv::RunType* scanline_next(const SkRegionPriv::RunType runs[]) { + // skip [B N [L R]... S] + return runs + 2 + runs[1] * 2 + 1; +} + +static bool scanline_contains(const SkRegionPriv::RunType runs[], + SkRegionPriv::RunType L, SkRegionPriv::RunType R) { + runs += 2; // skip Bottom and IntervalCount + for (;;) { + if (L < runs[0]) { + break; + } + if (R <= runs[1]) { + return true; + } + runs += 2; + } + return false; +} + +bool SkRegion::contains(const SkIRect& r) const { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + + if (!fBounds.contains(r)) { + return false; + } + if (this->isRect()) { + return true; + } + SkASSERT(this->isComplex()); + + const RunType* scanline = fRunHead->findScanline(r.fTop); + for (;;) { + if (!scanline_contains(scanline, r.fLeft, r.fRight)) { + return false; + } + if (r.fBottom <= scanline_bottom(scanline)) { + break; + } + scanline = scanline_next(scanline); + } + return true; +} + +bool SkRegion::contains(const SkRegion& rgn) const { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + SkDEBUGCODE(SkRegionPriv::Validate(rgn)); + + if (this->isEmpty() || rgn.isEmpty() || !fBounds.contains(rgn.fBounds)) { + return false; + } + if (this->isRect()) { + return true; + } + if (rgn.isRect()) { + return this->contains(rgn.getBounds()); + } + + /* + * A contains B is equivalent to + * B - A == 0 + */ + return !Oper(rgn, *this, kDifference_Op, nullptr); +} + +const SkRegion::RunType* SkRegion::getRuns(RunType tmpStorage[], + int* intervals) const { + SkASSERT(tmpStorage && intervals); + const RunType* runs = tmpStorage; + + if (this->isEmpty()) { + tmpStorage[0] = SkRegion_kRunTypeSentinel; + *intervals = 0; + } else if (this->isRect()) { + BuildRectRuns(fBounds, tmpStorage); + *intervals = 1; + } else { + runs = fRunHead->readonly_runs(); + *intervals = fRunHead->getIntervalCount(); + } + return runs; +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool scanline_intersects(const SkRegionPriv::RunType runs[], + SkRegionPriv::RunType L, SkRegionPriv::RunType R) { + runs += 2; // skip Bottom and IntervalCount + for (;;) { + if (R <= runs[0]) { + break; + } + if (L < runs[1]) { + return true; + } + runs += 2; + } + return false; +} + +bool SkRegion::intersects(const SkIRect& r) const { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + + if (this->isEmpty() || r.isEmpty()) { + return false; + } + + SkIRect sect; + if (!sect.intersect(fBounds, r)) { + return false; + } + if (this->isRect()) { + return true; + } + SkASSERT(this->isComplex()); + + const RunType* scanline = fRunHead->findScanline(sect.fTop); + for (;;) { + if (scanline_intersects(scanline, sect.fLeft, sect.fRight)) { + return true; + } + if (sect.fBottom <= scanline_bottom(scanline)) { + break; + } + scanline = scanline_next(scanline); + } + return false; +} + +bool SkRegion::intersects(const SkRegion& rgn) const { + if (this->isEmpty() || rgn.isEmpty()) { + return false; + } + + if (!SkIRect::Intersects(fBounds, rgn.fBounds)) { + return false; + } + + bool weAreARect = this->isRect(); + bool theyAreARect = rgn.isRect(); + + if (weAreARect && theyAreARect) { + return true; + } + if (weAreARect) { + return rgn.intersects(this->getBounds()); + } + if (theyAreARect) { + return this->intersects(rgn.getBounds()); + } + + // both of us are complex + return Oper(*this, rgn, kIntersect_Op, nullptr); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkRegion::operator==(const SkRegion& b) const { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + SkDEBUGCODE(SkRegionPriv::Validate(b)); + + if (this == &b) { + return true; + } + if (fBounds != b.fBounds) { + return false; + } + + const SkRegion::RunHead* ah = fRunHead; + const SkRegion::RunHead* bh = b.fRunHead; + + // this catches empties and rects being equal + if (ah == bh) { + return true; + } + // now we insist that both are complex (but different ptrs) + if (!this->isComplex() || !b.isComplex()) { + return false; + } + return ah->fRunCount == bh->fRunCount && + !memcmp(ah->readonly_runs(), bh->readonly_runs(), + ah->fRunCount * sizeof(SkRegion::RunType)); +} + +// Return a (new) offset such that when applied (+=) to min and max, we don't overflow/underflow +static int32_t pin_offset_s32(int32_t min, int32_t max, int32_t offset) { + SkASSERT(min <= max); + const int32_t lo = -SK_MaxS32-1, + hi = +SK_MaxS32; + if ((int64_t)min + offset < lo) { offset = lo - min; } + if ((int64_t)max + offset > hi) { offset = hi - max; } + return offset; +} + +void SkRegion::translate(int dx, int dy, SkRegion* dst) const { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + + if (nullptr == dst) { + return; + } + if (this->isEmpty()) { + dst->setEmpty(); + return; + } + // pin dx and dy so we don't overflow our existing bounds + dx = pin_offset_s32(fBounds.fLeft, fBounds.fRight, dx); + dy = pin_offset_s32(fBounds.fTop, fBounds.fBottom, dy); + + if (this->isRect()) { + dst->setRect(fBounds.makeOffset(dx, dy)); + } else { + if (this == dst) { + dst->fRunHead = dst->fRunHead->ensureWritable(); + } else { + SkRegion tmp; + tmp.allocateRuns(*fRunHead); + SkASSERT(tmp.isComplex()); + tmp.fBounds = fBounds; + dst->swap(tmp); + } + + dst->fBounds.offset(dx, dy); + + const RunType* sruns = fRunHead->readonly_runs(); + RunType* druns = dst->fRunHead->writable_runs(); + + *druns++ = (SkRegion::RunType)(*sruns++ + dy); // top + for (;;) { + int bottom = *sruns++; + if (bottom == SkRegion_kRunTypeSentinel) { + break; + } + *druns++ = (SkRegion::RunType)(bottom + dy); // bottom; + *druns++ = *sruns++; // copy intervalCount; + for (;;) { + int x = *sruns++; + if (x == SkRegion_kRunTypeSentinel) { + break; + } + *druns++ = (SkRegion::RunType)(x + dx); + *druns++ = (SkRegion::RunType)(*sruns++ + dx); + } + *druns++ = SkRegion_kRunTypeSentinel; // x sentinel + } + *druns++ = SkRegion_kRunTypeSentinel; // y sentinel + + SkASSERT(sruns - fRunHead->readonly_runs() == fRunHead->fRunCount); + SkASSERT(druns - dst->fRunHead->readonly_runs() == dst->fRunHead->fRunCount); + } + + SkDEBUGCODE(SkRegionPriv::Validate(*this)); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkRegion::setRects(const SkIRect rects[], int count) { + if (0 == count) { + this->setEmpty(); + } else { + this->setRect(rects[0]); + for (int i = 1; i < count; i++) { + this->op(rects[i], kUnion_Op); + } + } + return !this->isEmpty(); +} + +/////////////////////////////////////////////////////////////////////////////// + +#if defined _WIN32 // disable warning : local variable used without having been initialized +#pragma warning ( push ) +#pragma warning ( disable : 4701 ) +#endif + +#ifdef SK_DEBUG +static void assert_valid_pair(int left, int rite) +{ + SkASSERT(left == SkRegion_kRunTypeSentinel || left < rite); +} +#else + #define assert_valid_pair(left, rite) +#endif + +struct spanRec { + const SkRegionPriv::RunType* fA_runs; + const SkRegionPriv::RunType* fB_runs; + int fA_left, fA_rite, fB_left, fB_rite; + int fLeft, fRite, fInside; + + void init(const SkRegionPriv::RunType a_runs[], + const SkRegionPriv::RunType b_runs[]) { + fA_left = *a_runs++; + fA_rite = *a_runs++; + fB_left = *b_runs++; + fB_rite = *b_runs++; + + fA_runs = a_runs; + fB_runs = b_runs; + } + + bool done() const { + SkASSERT(fA_left <= SkRegion_kRunTypeSentinel); + SkASSERT(fB_left <= SkRegion_kRunTypeSentinel); + return fA_left == SkRegion_kRunTypeSentinel && + fB_left == SkRegion_kRunTypeSentinel; + } + + void next() { + assert_valid_pair(fA_left, fA_rite); + assert_valid_pair(fB_left, fB_rite); + + int inside, left, rite SK_INIT_TO_AVOID_WARNING; + bool a_flush = false; + bool b_flush = false; + + int a_left = fA_left; + int a_rite = fA_rite; + int b_left = fB_left; + int b_rite = fB_rite; + + if (a_left < b_left) { + inside = 1; + left = a_left; + if (a_rite <= b_left) { // [...] <...> + rite = a_rite; + a_flush = true; + } else { // [...<..]...> or [...<...>...] + rite = a_left = b_left; + } + } else if (b_left < a_left) { + inside = 2; + left = b_left; + if (b_rite <= a_left) { // [...] <...> + rite = b_rite; + b_flush = true; + } else { // [...<..]...> or [...<...>...] + rite = b_left = a_left; + } + } else { // a_left == b_left + inside = 3; + left = a_left; // or b_left + if (a_rite <= b_rite) { + rite = b_left = a_rite; + a_flush = true; + } + if (b_rite <= a_rite) { + rite = a_left = b_rite; + b_flush = true; + } + } + + if (a_flush) { + a_left = *fA_runs++; + a_rite = *fA_runs++; + } + if (b_flush) { + b_left = *fB_runs++; + b_rite = *fB_runs++; + } + + SkASSERT(left <= rite); + + // now update our state + fA_left = a_left; + fA_rite = a_rite; + fB_left = b_left; + fB_rite = b_rite; + + fLeft = left; + fRite = rite; + fInside = inside; + } +}; + +static int distance_to_sentinel(const SkRegionPriv::RunType* runs) { + const SkRegionPriv::RunType* ptr = runs; + while (*ptr != SkRegion_kRunTypeSentinel) { ptr += 2; } + return ptr - runs; +} + +static int operate_on_span(const SkRegionPriv::RunType a_runs[], + const SkRegionPriv::RunType b_runs[], + RunArray* array, int dstOffset, + int min, int max) { + // This is a worst-case for this span plus two for TWO terminating sentinels. + array->resizeToAtLeast( + dstOffset + distance_to_sentinel(a_runs) + distance_to_sentinel(b_runs) + 2); + SkRegionPriv::RunType* dst = &(*array)[dstOffset]; // get pointer AFTER resizing. + + spanRec rec; + bool firstInterval = true; + + rec.init(a_runs, b_runs); + + while (!rec.done()) { + rec.next(); + + int left = rec.fLeft; + int rite = rec.fRite; + + // add left,rite to our dst buffer (checking for coincidence + if ((unsigned)(rec.fInside - min) <= (unsigned)(max - min) && + left < rite) { // skip if equal + if (firstInterval || *(dst - 1) < left) { + *dst++ = (SkRegionPriv::RunType)(left); + *dst++ = (SkRegionPriv::RunType)(rite); + firstInterval = false; + } else { + // update the right edge + *(dst - 1) = (SkRegionPriv::RunType)(rite); + } + } + } + SkASSERT(dst < &(*array)[array->count() - 1]); + *dst++ = SkRegion_kRunTypeSentinel; + return dst - &(*array)[0]; +} + +#if defined _WIN32 +#pragma warning ( pop ) +#endif + +static const struct { + uint8_t fMin; + uint8_t fMax; +} gOpMinMax[] = { + { 1, 1 }, // Difference + { 3, 3 }, // Intersection + { 1, 3 }, // Union + { 1, 2 } // XOR +}; +// need to ensure that the op enum lines up with our minmax array +static_assert(0 == SkRegion::kDifference_Op, ""); +static_assert(1 == SkRegion::kIntersect_Op, ""); +static_assert(2 == SkRegion::kUnion_Op, ""); +static_assert(3 == SkRegion::kXOR_Op, ""); + +class RgnOper { +public: + RgnOper(int top, RunArray* array, SkRegion::Op op) + : fMin(gOpMinMax[op].fMin) + , fMax(gOpMinMax[op].fMax) + , fArray(array) + , fTop((SkRegionPriv::RunType)top) // just a first guess, we might update this + { SkASSERT((unsigned)op <= 3); } + + void addSpan(int bottom, const SkRegionPriv::RunType a_runs[], + const SkRegionPriv::RunType b_runs[]) { + // skip X values and slots for the next Y+intervalCount + int start = fPrevDst + fPrevLen + 2; + // start points to beginning of dst interval + int stop = operate_on_span(a_runs, b_runs, fArray, start, fMin, fMax); + size_t len = SkToSizeT(stop - start); + SkASSERT(len >= 1 && (len & 1) == 1); + SkASSERT(SkRegion_kRunTypeSentinel == (*fArray)[stop - 1]); + + // Assert memcmp won't exceed fArray->count(). + SkASSERT(fArray->count() >= SkToInt(start + len - 1)); + if (fPrevLen == len && + (1 == len || !memcmp(&(*fArray)[fPrevDst], + &(*fArray)[start], + (len - 1) * sizeof(SkRegionPriv::RunType)))) { + // update Y value + (*fArray)[fPrevDst - 2] = (SkRegionPriv::RunType)bottom; + } else { // accept the new span + if (len == 1 && fPrevLen == 0) { + fTop = (SkRegionPriv::RunType)bottom; // just update our bottom + } else { + (*fArray)[start - 2] = (SkRegionPriv::RunType)bottom; + (*fArray)[start - 1] = SkToS32(len >> 1); + fPrevDst = start; + fPrevLen = len; + } + } + } + + int flush() { + (*fArray)[fStartDst] = fTop; + // Previously reserved enough for TWO sentinals. + SkASSERT(fArray->count() > SkToInt(fPrevDst + fPrevLen)); + (*fArray)[fPrevDst + fPrevLen] = SkRegion_kRunTypeSentinel; + return (int)(fPrevDst - fStartDst + fPrevLen + 1); + } + + bool isEmpty() const { return 0 == fPrevLen; } + + uint8_t fMin, fMax; + +private: + RunArray* fArray; + int fStartDst = 0; + int fPrevDst = 1; + size_t fPrevLen = 0; // will never match a length from operate_on_span + SkRegionPriv::RunType fTop; +}; + +// want a unique value to signal that we exited due to quickExit +#define QUICK_EXIT_TRUE_COUNT (-1) + +static int operate(const SkRegionPriv::RunType a_runs[], + const SkRegionPriv::RunType b_runs[], + RunArray* dst, + SkRegion::Op op, + bool quickExit) { + const SkRegionPriv::RunType gEmptyScanline[] = { + 0, // dummy bottom value + 0, // zero intervals + SkRegion_kRunTypeSentinel, + // just need a 2nd value, since spanRec.init() reads 2 values, even + // though if the first value is the sentinel, it ignores the 2nd value. + // w/o the 2nd value here, we might read uninitialized memory. + // This happens when we are using gSentinel, which is pointing at + // our sentinel value. + 0 + }; + const SkRegionPriv::RunType* const gSentinel = &gEmptyScanline[2]; + + int a_top = *a_runs++; + int a_bot = *a_runs++; + int b_top = *b_runs++; + int b_bot = *b_runs++; + + a_runs += 1; // skip the intervalCount; + b_runs += 1; // skip the intervalCount; + + // Now a_runs and b_runs to their intervals (or sentinel) + + assert_sentinel(a_top, false); + assert_sentinel(a_bot, false); + assert_sentinel(b_top, false); + assert_sentinel(b_bot, false); + + RgnOper oper(SkMin32(a_top, b_top), dst, op); + + int prevBot = SkRegion_kRunTypeSentinel; // so we fail the first test + + while (a_bot < SkRegion_kRunTypeSentinel || + b_bot < SkRegion_kRunTypeSentinel) { + int top, bot SK_INIT_TO_AVOID_WARNING; + const SkRegionPriv::RunType* run0 = gSentinel; + const SkRegionPriv::RunType* run1 = gSentinel; + bool a_flush = false; + bool b_flush = false; + + if (a_top < b_top) { + top = a_top; + run0 = a_runs; + if (a_bot <= b_top) { // [...] <...> + bot = a_bot; + a_flush = true; + } else { // [...<..]...> or [...<...>...] + bot = a_top = b_top; + } + } else if (b_top < a_top) { + top = b_top; + run1 = b_runs; + if (b_bot <= a_top) { // [...] <...> + bot = b_bot; + b_flush = true; + } else { // [...<..]...> or [...<...>...] + bot = b_top = a_top; + } + } else { // a_top == b_top + top = a_top; // or b_top + run0 = a_runs; + run1 = b_runs; + if (a_bot <= b_bot) { + bot = b_top = a_bot; + a_flush = true; + } + if (b_bot <= a_bot) { + bot = a_top = b_bot; + b_flush = true; + } + } + + if (top > prevBot) { + oper.addSpan(top, gSentinel, gSentinel); + } + oper.addSpan(bot, run0, run1); + + if (quickExit && !oper.isEmpty()) { + return QUICK_EXIT_TRUE_COUNT; + } + + if (a_flush) { + a_runs = skip_intervals(a_runs); + a_top = a_bot; + a_bot = *a_runs++; + a_runs += 1; // skip uninitialized intervalCount + if (a_bot == SkRegion_kRunTypeSentinel) { + a_top = a_bot; + } + } + if (b_flush) { + b_runs = skip_intervals(b_runs); + b_top = b_bot; + b_bot = *b_runs++; + b_runs += 1; // skip uninitialized intervalCount + if (b_bot == SkRegion_kRunTypeSentinel) { + b_top = b_bot; + } + } + + prevBot = bot; + } + return oper.flush(); +} + +/////////////////////////////////////////////////////////////////////////////// + +/* Given count RunTypes in a complex region, return the worst case number of + logical intervals that represents (i.e. number of rects that would be + returned from the iterator). + + We could just return count/2, since there must be at least 2 values per + interval, but we can first trim off the const overhead of the initial TOP + value, plus the final BOTTOM + 2 sentinels. + */ +#if 0 // UNUSED +static int count_to_intervals(int count) { + SkASSERT(count >= 6); // a single rect is 6 values + return (count - 4) >> 1; +} +#endif + +static bool setEmptyCheck(SkRegion* result) { + return result ? result->setEmpty() : false; +} + +static bool setRectCheck(SkRegion* result, const SkIRect& rect) { + return result ? result->setRect(rect) : !rect.isEmpty(); +} + +static bool setRegionCheck(SkRegion* result, const SkRegion& rgn) { + return result ? result->setRegion(rgn) : !rgn.isEmpty(); +} + +bool SkRegion::Oper(const SkRegion& rgnaOrig, const SkRegion& rgnbOrig, Op op, + SkRegion* result) { + SkASSERT((unsigned)op < kOpCount); + + if (kReplace_Op == op) { + return setRegionCheck(result, rgnbOrig); + } + + // swith to using pointers, so we can swap them as needed + const SkRegion* rgna = &rgnaOrig; + const SkRegion* rgnb = &rgnbOrig; + // after this point, do not refer to rgnaOrig or rgnbOrig!!! + + // collaps difference and reverse-difference into just difference + if (kReverseDifference_Op == op) { + using std::swap; + swap(rgna, rgnb); + op = kDifference_Op; + } + + SkIRect bounds; + bool a_empty = rgna->isEmpty(); + bool b_empty = rgnb->isEmpty(); + bool a_rect = rgna->isRect(); + bool b_rect = rgnb->isRect(); + + switch (op) { + case kDifference_Op: + if (a_empty) { + return setEmptyCheck(result); + } + if (b_empty || !SkIRect::Intersects(rgna->fBounds, rgnb->fBounds)) { + return setRegionCheck(result, *rgna); + } + if (b_rect && rgnb->fBounds.containsNoEmptyCheck(rgna->fBounds)) { + return setEmptyCheck(result); + } + break; + + case kIntersect_Op: + if ((a_empty | b_empty) + || !bounds.intersect(rgna->fBounds, rgnb->fBounds)) { + return setEmptyCheck(result); + } + if (a_rect & b_rect) { + return setRectCheck(result, bounds); + } + if (a_rect && rgna->fBounds.contains(rgnb->fBounds)) { + return setRegionCheck(result, *rgnb); + } + if (b_rect && rgnb->fBounds.contains(rgna->fBounds)) { + return setRegionCheck(result, *rgna); + } + break; + + case kUnion_Op: + if (a_empty) { + return setRegionCheck(result, *rgnb); + } + if (b_empty) { + return setRegionCheck(result, *rgna); + } + if (a_rect && rgna->fBounds.contains(rgnb->fBounds)) { + return setRegionCheck(result, *rgna); + } + if (b_rect && rgnb->fBounds.contains(rgna->fBounds)) { + return setRegionCheck(result, *rgnb); + } + break; + + case kXOR_Op: + if (a_empty) { + return setRegionCheck(result, *rgnb); + } + if (b_empty) { + return setRegionCheck(result, *rgna); + } + break; + default: + SkDEBUGFAIL("unknown region op"); + return false; + } + + RunType tmpA[kRectRegionRuns]; + RunType tmpB[kRectRegionRuns]; + + int a_intervals, b_intervals; + const RunType* a_runs = rgna->getRuns(tmpA, &a_intervals); + const RunType* b_runs = rgnb->getRuns(tmpB, &b_intervals); + + RunArray array; + int count = operate(a_runs, b_runs, &array, op, nullptr == result); + SkASSERT(count <= array.count()); + + if (result) { + SkASSERT(count >= 0); + return result->setRuns(&array[0], count); + } else { + return (QUICK_EXIT_TRUE_COUNT == count) || !isRunCountEmpty(count); + } +} + +bool SkRegion::op(const SkRegion& rgna, const SkRegion& rgnb, Op op) { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + return SkRegion::Oper(rgna, rgnb, op, this); +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkBuffer.h" + +size_t SkRegion::writeToMemory(void* storage) const { + if (nullptr == storage) { + size_t size = sizeof(int32_t); // -1 (empty), 0 (rect), runCount + if (!this->isEmpty()) { + size += sizeof(fBounds); + if (this->isComplex()) { + size += 2 * sizeof(int32_t); // ySpanCount + intervalCount + size += fRunHead->fRunCount * sizeof(RunType); + } + } + return size; + } + + SkWBuffer buffer(storage); + + if (this->isEmpty()) { + buffer.write32(-1); + } else { + bool isRect = this->isRect(); + + buffer.write32(isRect ? 0 : fRunHead->fRunCount); + buffer.write(&fBounds, sizeof(fBounds)); + + if (!isRect) { + buffer.write32(fRunHead->getYSpanCount()); + buffer.write32(fRunHead->getIntervalCount()); + buffer.write(fRunHead->readonly_runs(), + fRunHead->fRunCount * sizeof(RunType)); + } + } + return buffer.pos(); +} + +static bool validate_run_count(int ySpanCount, int intervalCount, int runCount) { + // return 2 + 3 * ySpanCount + 2 * intervalCount; + if (ySpanCount < 1 || intervalCount < 2) { + return false; + } + SkSafeMath safeMath; + int sum = 2; + sum = safeMath.addInt(sum, ySpanCount); + sum = safeMath.addInt(sum, ySpanCount); + sum = safeMath.addInt(sum, ySpanCount); + sum = safeMath.addInt(sum, intervalCount); + sum = safeMath.addInt(sum, intervalCount); + return safeMath && sum == runCount; +} + +// Validate that a memory sequence is a valid region. +// Try to check all possible errors. +// never read beyond &runs[runCount-1]. +static bool validate_run(const int32_t* runs, + int runCount, + const SkIRect& givenBounds, + int32_t ySpanCount, + int32_t intervalCount) { + // Region Layout: + // Top ( Bottom Span_Interval_Count ( Left Right )* Sentinel )+ Sentinel + if (!validate_run_count(SkToInt(ySpanCount), SkToInt(intervalCount), runCount)) { + return false; + } + SkASSERT(runCount >= 7); // 7==SkRegion::kRectRegionRuns + // quick sanity check: + if (runs[runCount - 1] != SkRegion_kRunTypeSentinel || + runs[runCount - 2] != SkRegion_kRunTypeSentinel) { + return false; + } + const int32_t* const end = runs + runCount; + SkIRect bounds = {0, 0, 0 ,0}; // calulated bounds + SkIRect rect = {0, 0, 0, 0}; // current rect + rect.fTop = *runs++; + if (rect.fTop == SkRegion_kRunTypeSentinel) { + return false; // no rect can contain SkRegion_kRunTypeSentinel + } + if (rect.fTop != givenBounds.fTop) { + return false; // Must not begin with empty span that does not contribute to bounds. + } + do { + --ySpanCount; + if (ySpanCount < 0) { + return false; // too many yspans + } + rect.fBottom = *runs++; + if (rect.fBottom == SkRegion_kRunTypeSentinel) { + return false; + } + if (rect.fBottom > givenBounds.fBottom) { + return false; // Must not end with empty span that does not contribute to bounds. + } + if (rect.fBottom <= rect.fTop) { + return false; // y-intervals must be ordered; rects must be non-empty. + } + + int32_t xIntervals = *runs++; + SkASSERT(runs < end); + if (xIntervals < 0 || xIntervals > intervalCount || runs + 1 + 2 * xIntervals > end) { + return false; + } + intervalCount -= xIntervals; + bool firstInterval = true; + int32_t lastRight = 0; // check that x-intervals are distinct and ordered. + while (xIntervals-- > 0) { + rect.fLeft = *runs++; + rect.fRight = *runs++; + if (rect.fLeft == SkRegion_kRunTypeSentinel || + rect.fRight == SkRegion_kRunTypeSentinel || + rect.fLeft >= rect.fRight || // check non-empty rect + (!firstInterval && rect.fLeft <= lastRight)) { + return false; + } + lastRight = rect.fRight; + firstInterval = false; + bounds.join(rect); + } + if (*runs++ != SkRegion_kRunTypeSentinel) { + return false; // required check sentinal. + } + rect.fTop = rect.fBottom; + SkASSERT(runs < end); + } while (*runs != SkRegion_kRunTypeSentinel); + ++runs; + if (ySpanCount != 0 || intervalCount != 0 || givenBounds != bounds) { + return false; + } + SkASSERT(runs == end); // if ySpanCount && intervalCount are right, must be correct length. + return true; +} +size_t SkRegion::readFromMemory(const void* storage, size_t length) { + SkRBuffer buffer(storage, length); + SkRegion tmp; + int32_t count; + + // Serialized Region Format: + // Empty: + // -1 + // Simple Rect: + // 0 LEFT TOP RIGHT BOTTOM + // Complex Region: + // COUNT LEFT TOP RIGHT BOTTOM Y_SPAN_COUNT TOTAL_INTERVAL_COUNT [RUNS....] + if (!buffer.readS32(&count) || count < -1) { + return 0; + } + if (count >= 0) { + if (!buffer.read(&tmp.fBounds, sizeof(tmp.fBounds)) || tmp.fBounds.isEmpty()) { + return 0; // Short buffer or bad bounds for non-empty region; report failure. + } + if (count == 0) { + tmp.fRunHead = SkRegion_gRectRunHeadPtr; + } else { + int32_t ySpanCount, intervalCount; + if (!buffer.readS32(&ySpanCount) || + !buffer.readS32(&intervalCount) || + buffer.available() < count * sizeof(int32_t)) { + return 0; + } + if (!validate_run((const int32_t*)((const char*)storage + buffer.pos()), count, + tmp.fBounds, ySpanCount, intervalCount)) { + return 0; // invalid runs, don't even allocate + } + tmp.allocateRuns(count, ySpanCount, intervalCount); + SkASSERT(tmp.isComplex()); + SkAssertResult(buffer.read(tmp.fRunHead->writable_runs(), count * sizeof(int32_t))); + } + } + SkASSERT(tmp.isValid()); + SkASSERT(buffer.isValid()); + this->swap(tmp); + return buffer.pos(); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkRegion::isValid() const { + if (this->isEmpty()) { + return fBounds == SkIRect{0, 0, 0, 0}; + } + if (fBounds.isEmpty()) { + return false; + } + if (this->isRect()) { + return true; + } + return fRunHead && fRunHead->fRefCnt > 0 && + validate_run(fRunHead->readonly_runs(), fRunHead->fRunCount, fBounds, + fRunHead->getYSpanCount(), fRunHead->getIntervalCount()); +} + +#ifdef SK_DEBUG +void SkRegionPriv::Validate(const SkRegion& rgn) { SkASSERT(rgn.isValid()); } + +void SkRegion::dump() const { + if (this->isEmpty()) { + SkDebugf(" rgn: empty\n"); + } else { + SkDebugf(" rgn: [%d %d %d %d]", fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom); + if (this->isComplex()) { + const RunType* runs = fRunHead->readonly_runs(); + for (int i = 0; i < fRunHead->fRunCount; i++) + SkDebugf(" %d", runs[i]); + } + SkDebugf("\n"); + } +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +SkRegion::Iterator::Iterator(const SkRegion& rgn) { + this->reset(rgn); +} + +bool SkRegion::Iterator::rewind() { + if (fRgn) { + this->reset(*fRgn); + return true; + } + return false; +} + +void SkRegion::Iterator::reset(const SkRegion& rgn) { + fRgn = &rgn; + if (rgn.isEmpty()) { + fDone = true; + } else { + fDone = false; + if (rgn.isRect()) { + fRect = rgn.fBounds; + fRuns = nullptr; + } else { + fRuns = rgn.fRunHead->readonly_runs(); + fRect.setLTRB(fRuns[3], fRuns[0], fRuns[4], fRuns[1]); + fRuns += 5; + // Now fRuns points to the 2nd interval (or x-sentinel) + } + } +} + +void SkRegion::Iterator::next() { + if (fDone) { + return; + } + + if (fRuns == nullptr) { // rect case + fDone = true; + return; + } + + const RunType* runs = fRuns; + + if (runs[0] < SkRegion_kRunTypeSentinel) { // valid X value + fRect.fLeft = runs[0]; + fRect.fRight = runs[1]; + runs += 2; + } else { // we're at the end of a line + runs += 1; + if (runs[0] < SkRegion_kRunTypeSentinel) { // valid Y value + int intervals = runs[1]; + if (0 == intervals) { // empty line + fRect.fTop = runs[0]; + runs += 3; + } else { + fRect.fTop = fRect.fBottom; + } + + fRect.fBottom = runs[0]; + assert_sentinel(runs[2], false); + assert_sentinel(runs[3], false); + fRect.fLeft = runs[2]; + fRect.fRight = runs[3]; + runs += 4; + } else { // end of rgn + fDone = true; + } + } + fRuns = runs; +} + +SkRegion::Cliperator::Cliperator(const SkRegion& rgn, const SkIRect& clip) + : fIter(rgn), fClip(clip), fDone(true) { + const SkIRect& r = fIter.rect(); + + while (!fIter.done()) { + if (r.fTop >= clip.fBottom) { + break; + } + if (fRect.intersect(clip, r)) { + fDone = false; + break; + } + fIter.next(); + } +} + +void SkRegion::Cliperator::next() { + if (fDone) { + return; + } + + const SkIRect& r = fIter.rect(); + + fDone = true; + fIter.next(); + while (!fIter.done()) { + if (r.fTop >= fClip.fBottom) { + break; + } + if (fRect.intersect(fClip, r)) { + fDone = false; + break; + } + fIter.next(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +SkRegion::Spanerator::Spanerator(const SkRegion& rgn, int y, int left, + int right) { + SkDEBUGCODE(SkRegionPriv::Validate(rgn)); + + const SkIRect& r = rgn.getBounds(); + + fDone = true; + if (!rgn.isEmpty() && y >= r.fTop && y < r.fBottom && + right > r.fLeft && left < r.fRight) { + if (rgn.isRect()) { + if (left < r.fLeft) { + left = r.fLeft; + } + if (right > r.fRight) { + right = r.fRight; + } + fLeft = left; + fRight = right; + fRuns = nullptr; // means we're a rect, not a rgn + fDone = false; + } else { + const SkRegion::RunType* runs = rgn.fRunHead->findScanline(y); + runs += 2; // skip Bottom and IntervalCount + for (;;) { + // runs[0..1] is to the right of the span, so we're done + if (runs[0] >= right) { + break; + } + // runs[0..1] is to the left of the span, so continue + if (runs[1] <= left) { + runs += 2; + continue; + } + // runs[0..1] intersects the span + fRuns = runs; + fLeft = left; + fRight = right; + fDone = false; + break; + } + } + } +} + +bool SkRegion::Spanerator::next(int* left, int* right) { + if (fDone) { + return false; + } + + if (fRuns == nullptr) { // we're a rect + fDone = true; // ok, now we're done + if (left) { + *left = fLeft; + } + if (right) { + *right = fRight; + } + return true; // this interval is legal + } + + const SkRegion::RunType* runs = fRuns; + + if (runs[0] >= fRight) { + fDone = true; + return false; + } + + SkASSERT(runs[1] > fLeft); + + if (left) { + *left = SkMax32(fLeft, runs[0]); + } + if (right) { + *right = SkMin32(fRight, runs[1]); + } + fRuns = runs + 2; + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static void visit_pairs(int pairCount, int y, const int32_t pairs[], + const std::function& visitor) { + for (int i = 0; i < pairCount; ++i) { + visitor({ pairs[0], y, pairs[1], y + 1 }); + pairs += 2; + } +} + +void SkRegionPriv::VisitSpans(const SkRegion& rgn, + const std::function& visitor) { + if (rgn.isEmpty()) { + return; + } + if (rgn.isRect()) { + visitor(rgn.getBounds()); + } else { + const int32_t* p = rgn.fRunHead->readonly_runs(); + int32_t top = *p++; + int32_t bot = *p++; + do { + int pairCount = *p++; + if (pairCount == 1) { + visitor({ p[0], top, p[1], bot }); + p += 2; + } else if (pairCount > 1) { + // we have to loop repeated in Y, sending each interval in Y -> X order + for (int y = top; y < bot; ++y) { + visit_pairs(pairCount, y, p, visitor); + } + p += pairCount * 2; + } + assert_sentinel(*p, true); + p += 1; // skip sentinel + + // read next bottom or sentinel + top = bot; + bot = *p++; + } while (!SkRegionValueIsSentinel(bot)); + } +} + diff --git a/gfx/skia/skia/src/core/SkRegionPriv.h b/gfx/skia/skia/src/core/SkRegionPriv.h new file mode 100644 index 0000000000..2d0559ca0d --- /dev/null +++ b/gfx/skia/skia/src/core/SkRegionPriv.h @@ -0,0 +1,259 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRegionPriv_DEFINED +#define SkRegionPriv_DEFINED + +#include "include/core/SkRegion.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" +#include +#include + +class SkRegionPriv { +public: + static constexpr int kRunTypeSentinel = 0x7FFFFFFF; + typedef SkRegion::RunType RunType; + typedef SkRegion::RunHead RunHead; + + // Call the function with each span, in Y -> X ascending order. + // We pass a rect, but we will still ensure the span Y->X ordering, so often the height + // of the rect may be 1. It should never be empty. + static void VisitSpans(const SkRegion& rgn, const std::function&); + +#ifdef SK_DEBUG + static void Validate(const SkRegion& rgn); +#endif +}; + +static constexpr int SkRegion_kRunTypeSentinel = 0x7FFFFFFF; + +inline bool SkRegionValueIsSentinel(int32_t value) { + return value == (int32_t)SkRegion_kRunTypeSentinel; +} + +#define assert_sentinel(value, isSentinel) \ + SkASSERT(SkRegionValueIsSentinel(value) == isSentinel) + +#ifdef SK_DEBUG +// Given the first interval (just past the interval-count), compute the +// interval count, by search for the x-sentinel +// +static int compute_intervalcount(const SkRegionPriv::RunType runs[]) { + const SkRegionPriv::RunType* curr = runs; + while (*curr < SkRegion_kRunTypeSentinel) { + SkASSERT(curr[0] < curr[1]); + SkASSERT(curr[1] < SkRegion_kRunTypeSentinel); + curr += 2; + } + return SkToInt((curr - runs) >> 1); +} +#endif + +struct SkRegion::RunHead { +private: + +public: + std::atomic fRefCnt; + int32_t fRunCount; + + /** + * Number of spans with different Y values. This does not count the initial + * Top value, nor does it count the final Y-Sentinel value. In the logical + * case of a rectangle, this would return 1, and an empty region would + * return 0. + */ + int getYSpanCount() const { + return fYSpanCount; + } + + /** + * Number of intervals in the entire region. This equals the number of + * rects that would be returned by the Iterator. In the logical case of + * a rect, this would return 1, and an empty region would return 0. + */ + int getIntervalCount() const { + return fIntervalCount; + } + + static RunHead* Alloc(int count) { + if (count < SkRegion::kRectRegionRuns) { + return nullptr; + } + + const int64_t size = sk_64_mul(count, sizeof(RunType)) + sizeof(RunHead); + if (count < 0 || !SkTFitsIn(size)) { SK_ABORT("Invalid Size"); } + + RunHead* head = (RunHead*)sk_malloc_throw(size); + head->fRefCnt = 1; + head->fRunCount = count; + // these must be filled in later, otherwise we will be invalid + head->fYSpanCount = 0; + head->fIntervalCount = 0; + return head; + } + + static RunHead* Alloc(int count, int yspancount, int intervalCount) { + if (yspancount <= 0 || intervalCount <= 1) { + return nullptr; + } + + RunHead* head = Alloc(count); + if (!head) { + return nullptr; + } + head->fYSpanCount = yspancount; + head->fIntervalCount = intervalCount; + return head; + } + + SkRegion::RunType* writable_runs() { + SkASSERT(fRefCnt == 1); + return (SkRegion::RunType*)(this + 1); + } + + const SkRegion::RunType* readonly_runs() const { + return (const SkRegion::RunType*)(this + 1); + } + + RunHead* ensureWritable() { + RunHead* writable = this; + if (fRefCnt > 1) { + // We need to alloc & copy the current region before decrease + // the refcount because it could be freed in the meantime. + writable = Alloc(fRunCount, fYSpanCount, fIntervalCount); + memcpy(writable->writable_runs(), this->readonly_runs(), + fRunCount * sizeof(RunType)); + + // fRefCount might have changed since we last checked. + // If we own the last reference at this point, we need to + // free the memory. + if (--fRefCnt == 0) { + sk_free(this); + } + } + return writable; + } + + /** + * Given a scanline (including its Bottom value at runs[0]), return the next + * scanline. Asserts that there is one (i.e. runs[0] < Sentinel) + */ + static SkRegion::RunType* SkipEntireScanline(const SkRegion::RunType runs[]) { + // we are not the Y Sentinel + SkASSERT(runs[0] < SkRegion_kRunTypeSentinel); + + const int intervals = runs[1]; + SkASSERT(runs[2 + intervals * 2] == SkRegion_kRunTypeSentinel); +#ifdef SK_DEBUG + { + int n = compute_intervalcount(&runs[2]); + SkASSERT(n == intervals); + } +#endif + + // skip the entire line [B N [L R] S] + runs += 1 + 1 + intervals * 2 + 1; + return const_cast(runs); + } + + + /** + * Return the scanline that contains the Y value. This requires that the Y + * value is already known to be contained within the bounds of the region, + * and so this routine never returns nullptr. + * + * It returns the beginning of the scanline, starting with its Bottom value. + */ + SkRegion::RunType* findScanline(int y) const { + const RunType* runs = this->readonly_runs(); + + // if the top-check fails, we didn't do a quick check on the bounds + SkASSERT(y >= runs[0]); + + runs += 1; // skip top-Y + for (;;) { + int bottom = runs[0]; + // If we hit this, we've walked off the region, and our bounds check + // failed. + SkASSERT(bottom < SkRegion_kRunTypeSentinel); + if (y < bottom) { + break; + } + runs = SkipEntireScanline(runs); + } + return const_cast(runs); + } + + // Copy src runs into us, computing interval counts and bounds along the way + void computeRunBounds(SkIRect* bounds) { + RunType* runs = this->writable_runs(); + bounds->fTop = *runs++; + + int bot; + int ySpanCount = 0; + int intervalCount = 0; + int left = SK_MaxS32; + int rite = SK_MinS32; + + do { + bot = *runs++; + SkASSERT(bot < SkRegion_kRunTypeSentinel); + ySpanCount += 1; + + const int intervals = *runs++; + SkASSERT(intervals >= 0); + SkASSERT(intervals < SkRegion_kRunTypeSentinel); + + if (intervals > 0) { +#ifdef SK_DEBUG + { + int n = compute_intervalcount(runs); + SkASSERT(n == intervals); + } +#endif + RunType L = runs[0]; + SkASSERT(L < SkRegion_kRunTypeSentinel); + if (left > L) { + left = L; + } + + runs += intervals * 2; + RunType R = runs[-1]; + SkASSERT(R < SkRegion_kRunTypeSentinel); + if (rite < R) { + rite = R; + } + + intervalCount += intervals; + } + SkASSERT(SkRegion_kRunTypeSentinel == *runs); + runs += 1; // skip x-sentinel + + // test Y-sentinel + } while (SkRegion_kRunTypeSentinel > *runs); + +#ifdef SK_DEBUG + // +1 to skip the last Y-sentinel + int runCount = SkToInt(runs - this->writable_runs() + 1); + SkASSERT(runCount == fRunCount); +#endif + + fYSpanCount = ySpanCount; + fIntervalCount = intervalCount; + + bounds->fLeft = left; + bounds->fRight = rite; + bounds->fBottom = bot; + } + +private: + int32_t fYSpanCount; + int32_t fIntervalCount; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkRegion_path.cpp b/gfx/skia/skia/src/core/SkRegion_path.cpp new file mode 100644 index 0000000000..ff07c466c5 --- /dev/null +++ b/gfx/skia/skia/src/core/SkRegion_path.cpp @@ -0,0 +1,549 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTo.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkRegionPriv.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkScan.h" +#include "src/core/SkTSort.h" + +// The rgnbuilder caller *seems* to pass short counts, possible often seens early failure, so +// we may not want to promote this to a "std" routine just yet. +static bool sk_memeq32(const int32_t* SK_RESTRICT a, const int32_t* SK_RESTRICT b, int count) { + for (int i = 0; i < count; ++i) { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +class SkRgnBuilder : public SkBlitter { +public: + SkRgnBuilder(); + ~SkRgnBuilder() override; + + // returns true if it could allocate the working storage needed + bool init(int maxHeight, int maxTransitions, bool pathIsInverse); + + void done() { + if (fCurrScanline != nullptr) { + fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX())); + if (!this->collapsWithPrev()) { // flush the last line + fCurrScanline = fCurrScanline->nextScanline(); + } + } + } + + int computeRunCount() const; + void copyToRect(SkIRect*) const; + void copyToRgn(SkRegion::RunType runs[]) const; + + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override { + SkDEBUGFAIL("blitAntiH not implemented"); + } + +#ifdef SK_DEBUG + void dump() const { + SkDebugf("SkRgnBuilder: Top = %d\n", fTop); + const Scanline* line = (Scanline*)fStorage; + while (line < fCurrScanline) { + SkDebugf("SkRgnBuilder::Scanline: LastY=%d, fXCount=%d", line->fLastY, line->fXCount); + for (int i = 0; i < line->fXCount; i++) { + SkDebugf(" %d", line->firstX()[i]); + } + SkDebugf("\n"); + + line = line->nextScanline(); + } + } +#endif +private: + /* + * Scanline mimics a row in the region, nearly. A row in a region is: + * [Bottom IntervalCount [L R]... Sentinel] + * while a Scanline is + * [LastY XCount [L R]... uninitialized] + * The two are the same length (which is good), but we have to transmute + * the scanline a little when we convert it to a region-row. + * + * Potentially we could recode this to exactly match the row format, in + * which case copyToRgn() could be a single memcpy. Not sure that is worth + * the effort. + */ + struct Scanline { + SkRegion::RunType fLastY; + SkRegion::RunType fXCount; + + SkRegion::RunType* firstX() const { return (SkRegion::RunType*)(this + 1); } + Scanline* nextScanline() const { + // add final +1 for the x-sentinel + return (Scanline*)((SkRegion::RunType*)(this + 1) + fXCount + 1); + } + }; + SkRegion::RunType* fStorage; + Scanline* fCurrScanline; + Scanline* fPrevScanline; + // points at next avialable x[] in fCurrScanline + SkRegion::RunType* fCurrXPtr; + SkRegion::RunType fTop; // first Y value + + int fStorageCount; + + bool collapsWithPrev() { + if (fPrevScanline != nullptr && + fPrevScanline->fLastY + 1 == fCurrScanline->fLastY && + fPrevScanline->fXCount == fCurrScanline->fXCount && + sk_memeq32(fPrevScanline->firstX(), fCurrScanline->firstX(), fCurrScanline->fXCount)) + { + // update the height of fPrevScanline + fPrevScanline->fLastY = fCurrScanline->fLastY; + return true; + } + return false; + } +}; + +SkRgnBuilder::SkRgnBuilder() + : fStorage(nullptr) { +} + +SkRgnBuilder::~SkRgnBuilder() { + sk_free(fStorage); +} + +bool SkRgnBuilder::init(int maxHeight, int maxTransitions, bool pathIsInverse) { + if ((maxHeight | maxTransitions) < 0) { + return false; + } + + SkSafeMath safe; + + if (pathIsInverse) { + // allow for additional X transitions to "invert" each scanline + // [ L' ... normal transitions ... R' ] + // + maxTransitions = safe.addInt(maxTransitions, 2); + } + + // compute the count with +1 and +3 slop for the working buffer + size_t count = safe.mul(safe.addInt(maxHeight, 1), safe.addInt(3, maxTransitions)); + + if (pathIsInverse) { + // allow for two "empty" rows for the top and bottom + // [ Y, 1, L, R, S] == 5 (*2 for top and bottom) + count = safe.add(count, 10); + } + + if (!safe || !SkTFitsIn(count)) { + return false; + } + fStorageCount = SkToS32(count); + + fStorage = (SkRegion::RunType*)sk_malloc_canfail(fStorageCount, sizeof(SkRegion::RunType)); + if (nullptr == fStorage) { + return false; + } + + fCurrScanline = nullptr; // signal empty collection + fPrevScanline = nullptr; // signal first scanline + return true; +} + +void SkRgnBuilder::blitH(int x, int y, int width) { + if (fCurrScanline == nullptr) { // first time + fTop = (SkRegion::RunType)(y); + fCurrScanline = (Scanline*)fStorage; + fCurrScanline->fLastY = (SkRegion::RunType)(y); + fCurrXPtr = fCurrScanline->firstX(); + } else { + SkASSERT(y >= fCurrScanline->fLastY); + + if (y > fCurrScanline->fLastY) { + // if we get here, we're done with fCurrScanline + fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX())); + + int prevLastY = fCurrScanline->fLastY; + if (!this->collapsWithPrev()) { + fPrevScanline = fCurrScanline; + fCurrScanline = fCurrScanline->nextScanline(); + + } + if (y - 1 > prevLastY) { // insert empty run + fCurrScanline->fLastY = (SkRegion::RunType)(y - 1); + fCurrScanline->fXCount = 0; + fCurrScanline = fCurrScanline->nextScanline(); + } + // setup for the new curr line + fCurrScanline->fLastY = (SkRegion::RunType)(y); + fCurrXPtr = fCurrScanline->firstX(); + } + } + // check if we should extend the current run, or add a new one + if (fCurrXPtr > fCurrScanline->firstX() && fCurrXPtr[-1] == x) { + fCurrXPtr[-1] = (SkRegion::RunType)(x + width); + } else { + fCurrXPtr[0] = (SkRegion::RunType)(x); + fCurrXPtr[1] = (SkRegion::RunType)(x + width); + fCurrXPtr += 2; + } + SkASSERT(fCurrXPtr - fStorage < fStorageCount); +} + +int SkRgnBuilder::computeRunCount() const { + if (fCurrScanline == nullptr) { + return 0; + } + + const SkRegion::RunType* line = fStorage; + const SkRegion::RunType* stop = (const SkRegion::RunType*)fCurrScanline; + + return 2 + (int)(stop - line); +} + +void SkRgnBuilder::copyToRect(SkIRect* r) const { + SkASSERT(fCurrScanline != nullptr); + // A rect's scanline is [bottom intervals left right sentinel] == 5 + SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage == 5); + + const Scanline* line = (const Scanline*)fStorage; + SkASSERT(line->fXCount == 2); + + r->setLTRB(line->firstX()[0], fTop, line->firstX()[1], line->fLastY + 1); +} + +void SkRgnBuilder::copyToRgn(SkRegion::RunType runs[]) const { + SkASSERT(fCurrScanline != nullptr); + SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage > 4); + + const Scanline* line = (const Scanline*)fStorage; + const Scanline* stop = fCurrScanline; + + *runs++ = fTop; + do { + *runs++ = (SkRegion::RunType)(line->fLastY + 1); + int count = line->fXCount; + *runs++ = count >> 1; // intervalCount + if (count) { + memcpy(runs, line->firstX(), count * sizeof(SkRegion::RunType)); + runs += count; + } + *runs++ = SkRegion_kRunTypeSentinel; + line = line->nextScanline(); + } while (line < stop); + SkASSERT(line == stop); + *runs = SkRegion_kRunTypeSentinel; +} + +static unsigned verb_to_initial_last_index(unsigned verb) { + static const uint8_t gPathVerbToInitialLastIndex[] = { + 0, // kMove_Verb + 1, // kLine_Verb + 2, // kQuad_Verb + 2, // kConic_Verb + 3, // kCubic_Verb + 0, // kClose_Verb + 0 // kDone_Verb + }; + SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToInitialLastIndex)); + return gPathVerbToInitialLastIndex[verb]; +} + +static unsigned verb_to_max_edges(unsigned verb) { + static const uint8_t gPathVerbToMaxEdges[] = { + 0, // kMove_Verb + 1, // kLine_Verb + 2, // kQuad_VerbB + 2, // kConic_VerbB + 3, // kCubic_Verb + 0, // kClose_Verb + 0 // kDone_Verb + }; + SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToMaxEdges)); + return gPathVerbToMaxEdges[verb]; +} + +// If returns 0, ignore itop and ibot +static int count_path_runtype_values(const SkPath& path, int* itop, int* ibot) { + SkPath::Iter iter(path, true); + SkPoint pts[4]; + SkPath::Verb verb; + + int maxEdges = 0; + SkScalar top = SkIntToScalar(SK_MaxS16); + SkScalar bot = SkIntToScalar(SK_MinS16); + + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + maxEdges += verb_to_max_edges(verb); + + int lastIndex = verb_to_initial_last_index(verb); + if (lastIndex > 0) { + for (int i = 1; i <= lastIndex; i++) { + if (top > pts[i].fY) { + top = pts[i].fY; + } else if (bot < pts[i].fY) { + bot = pts[i].fY; + } + } + } else if (SkPath::kMove_Verb == verb) { + if (top > pts[0].fY) { + top = pts[0].fY; + } else if (bot < pts[0].fY) { + bot = pts[0].fY; + } + } + } + if (0 == maxEdges) { + return 0; // we have only moves+closes + } + + SkASSERT(top <= bot); + *itop = SkScalarRoundToInt(top); + *ibot = SkScalarRoundToInt(bot); + return maxEdges; +} + +static bool check_inverse_on_empty_return(SkRegion* dst, const SkPath& path, const SkRegion& clip) { + if (path.isInverseFillType()) { + return dst->set(clip); + } else { + return dst->setEmpty(); + } +} + +bool SkRegion::setPath(const SkPath& path, const SkRegion& clip) { + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + + if (clip.isEmpty() || !path.isFinite()) { + return this->setEmpty(); + } + + if (path.isEmpty()) { + return check_inverse_on_empty_return(this, path, clip); + } + + // Our builder is very fragile, and can't be called with spans/rects out of Y->X order. + // To ensure this, we only "fill" clipped to a rect (the clip's bounds), and if the + // clip is more complex than that, we just post-intersect the result with the clip. + if (clip.isComplex()) { + if (!this->setPath(path, SkRegion(clip.getBounds()))) { + return false; + } + return this->op(clip, kIntersect_Op); + } + + // compute worst-case rgn-size for the path + int pathTop, pathBot; + int pathTransitions = count_path_runtype_values(path, &pathTop, &pathBot); + if (0 == pathTransitions) { + return check_inverse_on_empty_return(this, path, clip); + } + + int clipTop, clipBot; + int clipTransitions = clip.count_runtype_values(&clipTop, &clipBot); + + int top = SkMax32(pathTop, clipTop); + int bot = SkMin32(pathBot, clipBot); + if (top >= bot) { + return check_inverse_on_empty_return(this, path, clip); + } + + SkRgnBuilder builder; + + if (!builder.init(bot - top, + SkMax32(pathTransitions, clipTransitions), + path.isInverseFillType())) { + // can't allocate working space, so return false + return this->setEmpty(); + } + + SkScan::FillPath(path, clip, &builder); + builder.done(); + + int count = builder.computeRunCount(); + if (count == 0) { + return this->setEmpty(); + } else if (count == kRectRegionRuns) { + builder.copyToRect(&fBounds); + this->setRect(fBounds); + } else { + SkRegion tmp; + + tmp.fRunHead = RunHead::Alloc(count); + builder.copyToRgn(tmp.fRunHead->writable_runs()); + tmp.fRunHead->computeRunBounds(&tmp.fBounds); + this->swap(tmp); + } + SkDEBUGCODE(SkRegionPriv::Validate(*this)); + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////// + +struct Edge { + enum { + kY0Link = 0x01, + kY1Link = 0x02, + + kCompleteLink = (kY0Link | kY1Link) + }; + + SkRegionPriv::RunType fX; + SkRegionPriv::RunType fY0, fY1; + uint8_t fFlags; + Edge* fNext; + + void set(int x, int y0, int y1) { + SkASSERT(y0 != y1); + + fX = (SkRegionPriv::RunType)(x); + fY0 = (SkRegionPriv::RunType)(y0); + fY1 = (SkRegionPriv::RunType)(y1); + fFlags = 0; + SkDEBUGCODE(fNext = nullptr;) + } + + int top() const { + return SkMin32(fY0, fY1); + } +}; + +static void find_link(Edge* base, Edge* stop) { + SkASSERT(base < stop); + + if (base->fFlags == Edge::kCompleteLink) { + SkASSERT(base->fNext); + return; + } + + SkASSERT(base + 1 < stop); + + int y0 = base->fY0; + int y1 = base->fY1; + + Edge* e = base; + if ((base->fFlags & Edge::kY0Link) == 0) { + for (;;) { + e += 1; + if ((e->fFlags & Edge::kY1Link) == 0 && y0 == e->fY1) { + SkASSERT(nullptr == e->fNext); + e->fNext = base; + e->fFlags = SkToU8(e->fFlags | Edge::kY1Link); + break; + } + } + } + + e = base; + if ((base->fFlags & Edge::kY1Link) == 0) { + for (;;) { + e += 1; + if ((e->fFlags & Edge::kY0Link) == 0 && y1 == e->fY0) { + SkASSERT(nullptr == base->fNext); + base->fNext = e; + e->fFlags = SkToU8(e->fFlags | Edge::kY0Link); + break; + } + } + } + + base->fFlags = Edge::kCompleteLink; +} + +static int extract_path(Edge* edge, Edge* stop, SkPath* path) { + while (0 == edge->fFlags) { + edge++; // skip over "used" edges + } + + SkASSERT(edge < stop); + + Edge* base = edge; + Edge* prev = edge; + edge = edge->fNext; + SkASSERT(edge != base); + + int count = 1; + path->moveTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY0)); + prev->fFlags = 0; + do { + if (prev->fX != edge->fX || prev->fY1 != edge->fY0) { // skip collinear + path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1)); // V + path->lineTo(SkIntToScalar(edge->fX), SkIntToScalar(edge->fY0)); // H + } + prev = edge; + edge = edge->fNext; + count += 1; + prev->fFlags = 0; + } while (edge != base); + path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1)); // V + path->close(); + return count; +} + +struct EdgeLT { + bool operator()(const Edge& a, const Edge& b) const { + return (a.fX == b.fX) ? a.top() < b.top() : a.fX < b.fX; + } +}; + +bool SkRegion::getBoundaryPath(SkPath* path) const { + // path could safely be nullptr if we're empty, but the caller shouldn't + // *know* that + SkASSERT(path); + + if (this->isEmpty()) { + return false; + } + + const SkIRect& bounds = this->getBounds(); + + if (this->isRect()) { + SkRect r; + r.set(bounds); // this converts the ints to scalars + path->addRect(r); + return true; + } + + SkRegion::Iterator iter(*this); + SkTDArray edges; + + for (const SkIRect& r = iter.rect(); !iter.done(); iter.next()) { + Edge* edge = edges.append(2); + edge[0].set(r.fLeft, r.fBottom, r.fTop); + edge[1].set(r.fRight, r.fTop, r.fBottom); + } + + int count = edges.count(); + Edge* start = edges.begin(); + Edge* stop = start + count; + SkTQSort(start, stop - 1, EdgeLT()); + + Edge* e; + for (e = start; e != stop; e++) { + find_link(e, stop); + } + +#ifdef SK_DEBUG + for (e = start; e != stop; e++) { + SkASSERT(e->fNext != nullptr); + SkASSERT(e->fFlags == Edge::kCompleteLink); + } +#endif + + path->incReserve(count << 1); + do { + SkASSERT(count > 1); + count -= extract_path(start, stop, path); + } while (count > 0); + + return true; +} diff --git a/gfx/skia/skia/src/core/SkRemoteGlyphCache.cpp b/gfx/skia/skia/src/core/SkRemoteGlyphCache.cpp new file mode 100644 index 0000000000..aea005506c --- /dev/null +++ b/gfx/skia/skia/src/core/SkRemoteGlyphCache.cpp @@ -0,0 +1,891 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkRemoteGlyphCache.h" + +#include +#include +#include +#include +#include + +#include "src/core/SkDevice.h" +#include "src/core/SkDraw.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkTraceEvent.h" +#include "src/core/SkTypeface_remote.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/text/GrTextContext.h" +#endif + +static SkDescriptor* auto_descriptor_from_desc(const SkDescriptor* source_desc, + SkFontID font_id, + SkAutoDescriptor* ad) { + ad->reset(source_desc->getLength()); + auto* desc = ad->getDesc(); + desc->init(); + + // Rec. + { + uint32_t size; + auto ptr = source_desc->findEntry(kRec_SkDescriptorTag, &size); + SkScalerContextRec rec; + memcpy(&rec, ptr, size); + rec.fFontID = font_id; + desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec); + } + + // Effects. + { + uint32_t size; + auto ptr = source_desc->findEntry(kEffects_SkDescriptorTag, &size); + if (ptr) { desc->addEntry(kEffects_SkDescriptorTag, size, ptr); } + } + + desc->computeChecksum(); + return desc; +} + +static const SkDescriptor* create_descriptor( + const SkPaint& paint, const SkFont& font, const SkMatrix& m, + const SkSurfaceProps& props, SkScalerContextFlags flags, + SkAutoDescriptor* ad, SkScalerContextEffects* effects) { + SkScalerContextRec rec; + SkScalerContext::MakeRecAndEffects(font, paint, props, flags, m, &rec, effects); + return SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, *effects, ad); +} + +// -- Serializer ----------------------------------------------------------------------------------- +size_t pad(size_t size, size_t alignment) { return (size + (alignment - 1)) & ~(alignment - 1); } + +// Alignment between x86 and x64 differs for some types, in particular +// int64_t and doubles have 4 and 8-byte alignment, respectively. +// Be consistent even when writing and reading across different architectures. +template +size_t serialization_alignment() { + return sizeof(T) == 8 ? 8 : alignof(T); +} + +class Serializer { +public: + Serializer(std::vector* buffer) : fBuffer{buffer} { } + + template + T* emplace(Args&&... args) { + auto result = allocate(sizeof(T), serialization_alignment()); + return new (result) T{std::forward(args)...}; + } + + template + void write(const T& data) { + T* result = (T*)allocate(sizeof(T), serialization_alignment()); + memcpy(result, &data, sizeof(T)); + } + + template + T* allocate() { + T* result = (T*)allocate(sizeof(T), serialization_alignment()); + return result; + } + + void writeDescriptor(const SkDescriptor& desc) { + write(desc.getLength()); + auto result = allocate(desc.getLength(), alignof(SkDescriptor)); + memcpy(result, &desc, desc.getLength()); + } + + void* allocate(size_t size, size_t alignment) { + size_t aligned = pad(fBuffer->size(), alignment); + fBuffer->resize(aligned + size); + return &(*fBuffer)[aligned]; + } + +private: + std::vector* fBuffer; +}; + +// -- Deserializer ------------------------------------------------------------------------------- +// Note that the Deserializer is reading untrusted data, we need to guard against invalid data. +class Deserializer { +public: + Deserializer(const volatile char* memory, size_t memorySize) + : fMemory(memory), fMemorySize(memorySize) {} + + template + bool read(T* val) { + auto* result = this->ensureAtLeast(sizeof(T), serialization_alignment()); + if (!result) return false; + + memcpy(val, const_cast(result), sizeof(T)); + return true; + } + + bool readDescriptor(SkAutoDescriptor* ad) { + uint32_t descLength = 0u; + if (!read(&descLength)) return false; + if (descLength < sizeof(SkDescriptor)) return false; + if (descLength != SkAlign4(descLength)) return false; + + auto* result = this->ensureAtLeast(descLength, alignof(SkDescriptor)); + if (!result) return false; + + ad->reset(descLength); + memcpy(ad->getDesc(), const_cast(result), descLength); + + if (ad->getDesc()->getLength() > descLength) return false; + return ad->getDesc()->isValid(); + } + + const volatile void* read(size_t size, size_t alignment) { + return this->ensureAtLeast(size, alignment); + } + + size_t bytesRead() const { return fBytesRead; } + +private: + const volatile char* ensureAtLeast(size_t size, size_t alignment) { + size_t padded = pad(fBytesRead, alignment); + + // Not enough data. + if (padded > fMemorySize) return nullptr; + if (size > fMemorySize - padded) return nullptr; + + auto* result = fMemory + padded; + fBytesRead = padded + size; + return result; + } + + // Note that we read each piece of memory only once to guard against TOCTOU violations. + const volatile char* fMemory; + size_t fMemorySize; + size_t fBytesRead = 0u; +}; + +// Paths use a SkWriter32 which requires 4 byte alignment. +static const size_t kPathAlignment = 4u; + +// -- StrikeSpec ----------------------------------------------------------------------------------- +struct StrikeSpec { + StrikeSpec() = default; + StrikeSpec(SkFontID typefaceID_, SkDiscardableHandleId discardableHandleId_) + : typefaceID{typefaceID_}, discardableHandleId(discardableHandleId_) {} + SkFontID typefaceID = 0u; + SkDiscardableHandleId discardableHandleId = 0u; + /* desc */ + /* n X (glyphs ids) */ +}; + +// -- RemoteStrike ---------------------------------------------------------------------------- +class SkStrikeServer::RemoteStrike : public SkStrikeForGPU { +public: + // N.B. RemoteStrike is not valid until ensureScalerContext is called. + RemoteStrike(const SkDescriptor& descriptor, + std::unique_ptr context, + SkDiscardableHandleId discardableHandleId); + ~RemoteStrike() override; + + void addGlyph(SkPackedGlyphID, bool asPath); + void writePendingGlyphs(Serializer* serializer); + SkDiscardableHandleId discardableHandleId() const { return fDiscardableHandleId; } + + const SkDescriptor& getDescriptor() const override { + return *fDescriptor.getDesc(); + } + + void setTypefaceAndEffects(const SkTypeface* typeface, SkScalerContextEffects effects); + + const SkGlyphPositionRoundingSpec& roundingSpec() const override { + return fRoundingSpec; + } + + SkSpan + prepareForDrawingRemoveEmpty( + const SkPackedGlyphID packedGlyphIDs[], + const SkPoint positions[], size_t n, + int maxDimension, + SkGlyphPos results[]) override; + + void onAboutToExitScope() override {} + + bool hasPendingGlyphs() const { + return !fPendingGlyphImages.empty() || !fPendingGlyphPaths.empty(); + } + + void resetScalerContext(); + +private: + void writeGlyphPath(const SkPackedGlyphID& glyphID, Serializer* serializer) const; + + void ensureScalerContext(); + + // The set of glyphs cached on the remote client. + SkTHashSet fCachedGlyphImages; + SkTHashSet fCachedGlyphPaths; + + // The set of glyphs which has not yet been serialized and sent to the + // remote client. + std::vector fPendingGlyphImages; + std::vector fPendingGlyphPaths; + + const SkAutoDescriptor fDescriptor; + + const SkDiscardableHandleId fDiscardableHandleId; + + const SkGlyphPositionRoundingSpec fRoundingSpec; + + // The context built using fDescriptor + std::unique_ptr fContext; + + // These fields are set every time getOrCreateCache. This allows the code to maintain the + // fContext as lazy as possible. + const SkTypeface* fTypeface{nullptr}; + SkScalerContextEffects fEffects; + + bool fHaveSentFontMetrics{false}; + + class GlyphMapHashTraits { + public: + static SkPackedGlyphID GetKey(const SkGlyph* glyph) { + return glyph->getPackedID(); + } + static uint32_t Hash(SkPackedGlyphID glyphId) { + return glyphId.hash(); + } + }; + + // FallbackTextHelper cases require glyph metrics when analyzing a glyph run, in which case + // we cache them here. + SkTHashTable fGlyphMap; + + SkArenaAlloc fAlloc{256}; +}; + +SkStrikeServer::RemoteStrike::RemoteStrike( + const SkDescriptor& descriptor, + std::unique_ptr context, + uint32_t discardableHandleId) + : fDescriptor{descriptor} + , fDiscardableHandleId(discardableHandleId) + , fRoundingSpec{context->isSubpixel(), context->computeAxisAlignmentForHText()} + // N.B. context must come last because it is used above. + , fContext{std::move(context)} { + SkASSERT(fDescriptor.getDesc() != nullptr); + SkASSERT(fContext != nullptr); +} + +SkStrikeServer::RemoteStrike::~RemoteStrike() = default; + +void SkStrikeServer::RemoteStrike::addGlyph(SkPackedGlyphID glyph, bool asPath) { + auto* cache = asPath ? &fCachedGlyphPaths : &fCachedGlyphImages; + auto* pending = asPath ? &fPendingGlyphPaths : &fPendingGlyphImages; + + // Already cached. + if (cache->contains(glyph)) { + return; + } + + // A glyph is going to be sent. Make sure we have a scaler context to send it. + this->ensureScalerContext(); + + // Serialize and cache. Also create the scalar context to use when serializing + // this glyph. + cache->add(glyph); + pending->push_back(glyph); +} + +size_t SkStrikeServer::MapOps::operator()(const SkDescriptor* key) const { + return key->getChecksum(); +} + +bool SkStrikeServer::MapOps::operator()(const SkDescriptor* lhs, const SkDescriptor* rhs) const { + return *lhs == *rhs; +} + + +// -- TrackLayerDevice ----------------------------------------------------------------------------- +class SkTextBlobCacheDiffCanvas::TrackLayerDevice final : public SkNoPixelsDevice { +public: + TrackLayerDevice( + const SkIRect& bounds, const SkSurfaceProps& props, SkStrikeServer* server, + sk_sp colorSpace, bool DFTSupport) + : SkNoPixelsDevice(bounds, props, std::move(colorSpace)) + , fStrikeServer(server) + , fDFTSupport(DFTSupport) + , fPainter{props, kUnknown_SkColorType, imageInfo().colorSpace(), fStrikeServer} { + SkASSERT(fStrikeServer != nullptr); + } + + SkBaseDevice* onCreateDevice(const CreateInfo& cinfo, const SkPaint*) override { + const SkSurfaceProps surfaceProps(this->surfaceProps().flags(), cinfo.fPixelGeometry); + return new TrackLayerDevice(this->getGlobalBounds(), surfaceProps, fStrikeServer, + cinfo.fInfo.refColorSpace(), fDFTSupport); + } + +protected: + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override { +#if SK_SUPPORT_GPU + GrTextContext::Options options; + GrTextContext::SanitizeOptions(&options); + + fPainter.processGlyphRunList(glyphRunList, + this->ctm(), + this->surfaceProps(), + fDFTSupport, + options, + nullptr); +#endif // SK_SUPPORT_GPU + } + +private: + SkStrikeServer* const fStrikeServer; + const bool fDFTSupport{false}; + SkGlyphRunListPainter fPainter; +}; + +// -- SkTextBlobCacheDiffCanvas ------------------------------------------------------------------- +SkTextBlobCacheDiffCanvas::SkTextBlobCacheDiffCanvas(int width, int height, + const SkSurfaceProps& props, + SkStrikeServer* strikeServer, + bool DFTSupport) + : SkTextBlobCacheDiffCanvas{width, height, props, strikeServer, nullptr, DFTSupport} { } + +SkTextBlobCacheDiffCanvas::SkTextBlobCacheDiffCanvas(int width, int height, + const SkSurfaceProps& props, + SkStrikeServer* strikeServer, + sk_sp colorSpace, + bool DFTSupport) + : SkNoDrawCanvas{sk_make_sp(SkIRect::MakeWH(width, height), + props, + strikeServer, + std::move(colorSpace), + DFTSupport)} { } + +SkTextBlobCacheDiffCanvas::~SkTextBlobCacheDiffCanvas() = default; + +SkCanvas::SaveLayerStrategy SkTextBlobCacheDiffCanvas::getSaveLayerStrategy( + const SaveLayerRec& rec) { + return kFullLayer_SaveLayerStrategy; +} + +bool SkTextBlobCacheDiffCanvas::onDoSaveBehind(const SkRect*) { + return false; +} + +void SkTextBlobCacheDiffCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + SkCanvas::onDrawTextBlob(blob, x, y, paint); +} + +// -- WireTypeface --------------------------------------------------------------------------------- +struct WireTypeface { + WireTypeface() = default; + WireTypeface(SkFontID typeface_id, int glyph_count, SkFontStyle style, bool is_fixed) + : typefaceID(typeface_id), glyphCount(glyph_count), style(style), isFixed(is_fixed) {} + + SkFontID typefaceID{0}; + int glyphCount{0}; + SkFontStyle style; + bool isFixed{false}; +}; + +// SkStrikeServer ---------------------------------------------------------------------------------- +SkStrikeServer::SkStrikeServer(DiscardableHandleManager* discardableHandleManager) + : fDiscardableHandleManager(discardableHandleManager) { + SkASSERT(fDiscardableHandleManager); +} + +SkStrikeServer::~SkStrikeServer() = default; + +sk_sp SkStrikeServer::serializeTypeface(SkTypeface* tf) { + auto* data = fSerializedTypefaces.find(SkTypeface::UniqueID(tf)); + if (data) { + return *data; + } + + WireTypeface wire(SkTypeface::UniqueID(tf), tf->countGlyphs(), tf->fontStyle(), + tf->isFixedPitch()); + data = fSerializedTypefaces.set(SkTypeface::UniqueID(tf), + SkData::MakeWithCopy(&wire, sizeof(wire))); + return *data; +} + +void SkStrikeServer::writeStrikeData(std::vector* memory) { + size_t strikesToSend = 0; + fRemoteStrikesToSend.foreach ([&strikesToSend](RemoteStrike* strike) { + if (strike->hasPendingGlyphs()) { + strikesToSend++; + } else { + strike->resetScalerContext(); + } + }); + + if (strikesToSend == 0 && fTypefacesToSend.empty()) { + fRemoteStrikesToSend.reset(); + return; + } + + Serializer serializer(memory); + serializer.emplace(fTypefacesToSend.size()); + for (const auto& tf : fTypefacesToSend) { + serializer.write(tf); + } + fTypefacesToSend.clear(); + + serializer.emplace(SkTo(strikesToSend)); + fRemoteStrikesToSend.foreach ( +#ifdef SK_DEBUG + [&serializer, this](RemoteStrike* strike) { + if (strike->hasPendingGlyphs()) { + strike->writePendingGlyphs(&serializer); + strike->resetScalerContext(); + } + auto it = fDescToRemoteStrike.find(&strike->getDescriptor()); + SkASSERT(it != fDescToRemoteStrike.end()); + SkASSERT(it->second.get() == strike); + } + +#else + [&serializer](RemoteStrike* strike) { + if (strike->hasPendingGlyphs()) { + strike->writePendingGlyphs(&serializer); + strike->resetScalerContext(); + } + } +#endif + ); + fRemoteStrikesToSend.reset(); +} + +SkStrikeServer::RemoteStrike* SkStrikeServer::getOrCreateCache( + const SkPaint& paint, + const SkFont& font, + const SkSurfaceProps& props, + const SkMatrix& matrix, + SkScalerContextFlags flags, + SkScalerContextEffects* effects) { + SkAutoDescriptor descStorage; + auto desc = create_descriptor(paint, font, matrix, props, flags, &descStorage, effects); + + return this->getOrCreateCache(*desc, *font.getTypefaceOrDefault(), *effects); +} + +SkScopedStrikeForGPU SkStrikeServer::findOrCreateScopedStrike(const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) { + return SkScopedStrikeForGPU{this->getOrCreateCache(desc, typeface, effects)}; +} + +void SkStrikeServer::AddGlyphForTesting( + RemoteStrike* cache, SkPackedGlyphID glyphID, bool asPath) { + cache->addGlyph(glyphID, asPath); +} + +void SkStrikeServer::checkForDeletedEntries() { + auto it = fDescToRemoteStrike.begin(); + while (fDescToRemoteStrike.size() > fMaxEntriesInDescriptorMap && + it != fDescToRemoteStrike.end()) { + RemoteStrike* strike = it->second.get(); + if (fDiscardableHandleManager->isHandleDeleted(strike->discardableHandleId())) { + // If we are removing the strike, we better not be trying to send it at the same time. + SkASSERT(!fRemoteStrikesToSend.contains(strike)); + it = fDescToRemoteStrike.erase(it); + } else { + ++it; + } + } +} + +SkStrikeServer::RemoteStrike* SkStrikeServer::getOrCreateCache( + const SkDescriptor& desc, const SkTypeface& typeface, SkScalerContextEffects effects) { + + // In cases where tracing is turned off, make sure not to get an unused function warning. + // Lambdaize the function. + TRACE_EVENT1("skia", "RecForDesc", "rec", + TRACE_STR_COPY( + [&desc](){ + auto ptr = desc.findEntry(kRec_SkDescriptorTag, nullptr); + SkScalerContextRec rec; + std::memcpy(&rec, ptr, sizeof(rec)); + return rec.dump(); + }().c_str() + ) + ); + + auto it = fDescToRemoteStrike.find(&desc); + if (it != fDescToRemoteStrike.end()) { + // We have processed the RemoteStrike before. Reuse it. + RemoteStrike* strike = it->second.get(); + strike->setTypefaceAndEffects(&typeface, effects); + if (fRemoteStrikesToSend.contains(strike)) { + // Already tracking + return strike; + } + + // Strike is in unknown state on GPU. Start tracking strike on GPU by locking it. + bool locked = fDiscardableHandleManager->lockHandle(it->second->discardableHandleId()); + if (locked) { + fRemoteStrikesToSend.add(strike); + return strike; + } + + fDescToRemoteStrike.erase(it); + } + + // Create a new RemoteStrike. Start by processing the typeface. + const SkFontID typefaceId = typeface.uniqueID(); + if (!fCachedTypefaces.contains(typefaceId)) { + fCachedTypefaces.add(typefaceId); + fTypefacesToSend.emplace_back(typefaceId, typeface.countGlyphs(), + typeface.fontStyle(), + typeface.isFixedPitch()); + } + + auto context = typeface.createScalerContext(effects, &desc); + auto newHandle = fDiscardableHandleManager->createHandle(); // Locked on creation + auto remoteStrike = skstd::make_unique(desc, std::move(context), newHandle); + remoteStrike->setTypefaceAndEffects(&typeface, effects); + auto remoteStrikePtr = remoteStrike.get(); + fRemoteStrikesToSend.add(remoteStrikePtr); + auto d = &remoteStrike->getDescriptor(); + fDescToRemoteStrike[d] = std::move(remoteStrike); + + checkForDeletedEntries(); + + // Be sure we can build glyphs with this RemoteStrike. + remoteStrikePtr->setTypefaceAndEffects(&typeface, effects); + return remoteStrikePtr; +} + +// No need to write fForceBW because it is a flag private to SkScalerContext_DW, which will never +// be called on the GPU side. +static void writeGlyph(SkGlyph* glyph, Serializer* serializer) { + serializer->write(glyph->getPackedID()); + serializer->write(glyph->advanceX()); + serializer->write(glyph->advanceY()); + serializer->write(glyph->width()); + serializer->write(glyph->height()); + serializer->write(glyph->top()); + serializer->write(glyph->left()); + serializer->write(glyph->maskFormat()); +} + +void SkStrikeServer::RemoteStrike::writePendingGlyphs(Serializer* serializer) { + SkASSERT(this->hasPendingGlyphs()); + + // Write the desc. + serializer->emplace(fContext->getTypeface()->uniqueID(), fDiscardableHandleId); + serializer->writeDescriptor(*fDescriptor.getDesc()); + + serializer->emplace(fHaveSentFontMetrics); + if (!fHaveSentFontMetrics) { + // Write FontMetrics if not sent before. + SkFontMetrics fontMetrics; + fContext->getFontMetrics(&fontMetrics); + serializer->write(fontMetrics); + fHaveSentFontMetrics = true; + } + + // Write glyphs images. + serializer->emplace(fPendingGlyphImages.size()); + for (const auto& glyphID : fPendingGlyphImages) { + SkGlyph glyph{glyphID}; + fContext->getMetrics(&glyph); + SkASSERT(SkMask::IsValidFormat(glyph.fMaskFormat)); + + writeGlyph(&glyph, serializer); + auto imageSize = glyph.imageSize(); + if (imageSize == 0u) continue; + + glyph.fImage = serializer->allocate(imageSize, glyph.formatAlignment()); + fContext->getImage(glyph); + // TODO: Generating the image can change the mask format, do we need to update it in the + // serialized glyph? + } + fPendingGlyphImages.clear(); + + // Write glyphs paths. + serializer->emplace(fPendingGlyphPaths.size()); + for (const auto& glyphID : fPendingGlyphPaths) { + SkGlyph glyph{glyphID}; + fContext->getMetrics(&glyph); + SkASSERT(SkMask::IsValidFormat(glyph.fMaskFormat)); + + writeGlyph(&glyph, serializer); + writeGlyphPath(glyphID, serializer); + } + fPendingGlyphPaths.clear(); +} + +void SkStrikeServer::RemoteStrike::ensureScalerContext() { + if (fContext == nullptr) { + fContext = fTypeface->createScalerContext(fEffects, fDescriptor.getDesc()); + } +} + +void SkStrikeServer::RemoteStrike::resetScalerContext() { + fContext.reset(); + fTypeface = nullptr; +} + +void SkStrikeServer::RemoteStrike::setTypefaceAndEffects( + const SkTypeface* typeface, SkScalerContextEffects effects) { + fTypeface = typeface; + fEffects = effects; +} + +void SkStrikeServer::RemoteStrike::writeGlyphPath(const SkPackedGlyphID& glyphID, + Serializer* serializer) const { + SkPath path; + if (!fContext->getPath(glyphID, &path)) { + serializer->write(0u); + return; + } + + size_t pathSize = path.writeToMemory(nullptr); + serializer->write(pathSize); + path.writeToMemory(serializer->allocate(pathSize, kPathAlignment)); +} + + +// Be sure to read and understand the comment for prepareForDrawingRemoveEmpty in +// SkStrikeForGPU.h before working on this code. +SkSpan +SkStrikeServer::RemoteStrike::prepareForDrawingRemoveEmpty( + const SkPackedGlyphID packedGlyphIDs[], + const SkPoint positions[], size_t n, + int maxDimension, + SkGlyphPos results[]) { + size_t drawableGlyphCount = 0; + for (size_t i = 0; i < n; i++) { + SkPoint glyphPos = positions[i]; + + // Check the cache for the glyph. + SkGlyph* glyphPtr = fGlyphMap.findOrNull(packedGlyphIDs[i]); + + // Has this glyph ever been seen before? + if (glyphPtr == nullptr) { + + // Never seen before. Make a new glyph. + glyphPtr = fAlloc.make(packedGlyphIDs[i]); + fGlyphMap.set(glyphPtr); + this->ensureScalerContext(); + fContext->getMetrics(glyphPtr); + + if (glyphPtr->maxDimension() <= maxDimension) { + // do nothing + } else if (!glyphPtr->isColor()) { + // The glyph is too big for the atlas, but it is not color, so it is handled + // with a path. + if (glyphPtr->setPath(&fAlloc, fContext.get())) { + // Always send the path data, even if its not available, to make sure empty + // paths are not incorrectly assumed to be cache misses. + fCachedGlyphPaths.add(glyphPtr->getPackedID()); + fPendingGlyphPaths.push_back(glyphPtr->getPackedID()); + } + } else { + // This will be handled by the fallback strike. + SkASSERT(glyphPtr->maxDimension() > maxDimension && glyphPtr->isColor()); + } + + // Make sure to send the glyph to the GPU because we always send the image for a glyph. + fCachedGlyphImages.add(packedGlyphIDs[i]); + fPendingGlyphImages.push_back(packedGlyphIDs[i]); + } + + // Each non-empty glyph needs to be added as per the contract for + // prepareForDrawingRemoveEmpty. + // TODO(herb): Change the code to only send the glyphs for fallback? + if (!glyphPtr->isEmpty()) { + results[drawableGlyphCount++] = {i, glyphPtr, glyphPos}; + } + } + return SkMakeSpan(results, drawableGlyphCount); +} + +// SkStrikeClient ---------------------------------------------------------------------------------- +class SkStrikeClient::DiscardableStrikePinner : public SkStrikePinner { +public: + DiscardableStrikePinner(SkDiscardableHandleId discardableHandleId, + sk_sp manager) + : fDiscardableHandleId(discardableHandleId), fManager(std::move(manager)) {} + + ~DiscardableStrikePinner() override = default; + bool canDelete() override { return fManager->deleteHandle(fDiscardableHandleId); } + +private: + const SkDiscardableHandleId fDiscardableHandleId; + sk_sp fManager; +}; + +SkStrikeClient::SkStrikeClient(sk_sp discardableManager, + bool isLogging, + SkStrikeCache* strikeCache) + : fDiscardableHandleManager(std::move(discardableManager)) + , fStrikeCache{strikeCache ? strikeCache : SkStrikeCache::GlobalStrikeCache()} + , fIsLogging{isLogging} {} + +SkStrikeClient::~SkStrikeClient() = default; + +#define READ_FAILURE \ + { \ + SkDebugf("Bad font data serialization line: %d", __LINE__); \ + DiscardableHandleManager::ReadFailureData data = { \ + memorySize, deserializer.bytesRead(), typefaceSize, \ + strikeCount, glyphImagesCount, glyphPathsCount}; \ + fDiscardableHandleManager->notifyReadFailure(data); \ + return false; \ + } + +// No need to read fForceBW because it is a flag private to SkScalerContext_DW, which will never +// be called on the GPU side. +bool SkStrikeClient::ReadGlyph(SkTLazy& glyph, Deserializer* deserializer) { + SkPackedGlyphID glyphID; + if (!deserializer->read(&glyphID)) return false; + glyph.init(glyphID); + if (!deserializer->read(&glyph->fAdvanceX)) return false; + if (!deserializer->read(&glyph->fAdvanceY)) return false; + if (!deserializer->read(&glyph->fWidth)) return false; + if (!deserializer->read(&glyph->fHeight)) return false; + if (!deserializer->read(&glyph->fTop)) return false; + if (!deserializer->read(&glyph->fLeft)) return false; + if (!deserializer->read(&glyph->fMaskFormat)) return false; + if (!SkMask::IsValidFormat(glyph->fMaskFormat)) return false; + + return true; +} + +bool SkStrikeClient::readStrikeData(const volatile void* memory, size_t memorySize) { + SkASSERT(memorySize != 0u); + Deserializer deserializer(static_cast(memory), memorySize); + + uint64_t typefaceSize = 0u; + uint64_t strikeCount = 0u; + uint64_t glyphImagesCount = 0u; + uint64_t glyphPathsCount = 0u; + + if (!deserializer.read(&typefaceSize)) READ_FAILURE + + for (size_t i = 0; i < typefaceSize; ++i) { + WireTypeface wire; + if (!deserializer.read(&wire)) READ_FAILURE + + // TODO(khushalsagar): The typeface no longer needs a reference to the + // SkStrikeClient, since all needed glyphs must have been pushed before + // raster. + addTypeface(wire); + } + + if (!deserializer.read(&strikeCount)) READ_FAILURE + + for (size_t i = 0; i < strikeCount; ++i) { + StrikeSpec spec; + if (!deserializer.read(&spec)) READ_FAILURE + + SkAutoDescriptor sourceAd; + if (!deserializer.readDescriptor(&sourceAd)) READ_FAILURE + + bool fontMetricsInitialized; + if (!deserializer.read(&fontMetricsInitialized)) READ_FAILURE + + SkFontMetrics fontMetrics{}; + if (!fontMetricsInitialized) { + if (!deserializer.read(&fontMetrics)) READ_FAILURE + } + + // Get the local typeface from remote fontID. + auto* tfPtr = fRemoteFontIdToTypeface.find(spec.typefaceID); + // Received strikes for a typeface which doesn't exist. + if (!tfPtr) READ_FAILURE + auto* tf = tfPtr->get(); + + // Replace the ContextRec in the desc from the server to create the client + // side descriptor. + // TODO: Can we do this in-place and re-compute checksum? Instead of a complete copy. + SkAutoDescriptor ad; + auto* client_desc = auto_descriptor_from_desc(sourceAd.getDesc(), tf->uniqueID(), &ad); + + auto strike = fStrikeCache->findStrikeExclusive(*client_desc); + // Metrics are only sent the first time. If the metrics are not initialized, there must + // be an existing strike. + if (fontMetricsInitialized && strike == nullptr) READ_FAILURE + if (strike == nullptr) { + // Note that we don't need to deserialize the effects since we won't be generating any + // glyphs here anyway, and the desc is still correct since it includes the serialized + // effects. + SkScalerContextEffects effects; + auto scaler = SkStrikeCache::CreateScalerContext(*client_desc, effects, *tf); + strike = fStrikeCache->createStrikeExclusive( + *client_desc, std::move(scaler), &fontMetrics, + skstd::make_unique(spec.discardableHandleId, + fDiscardableHandleManager)); + auto proxyContext = static_cast(strike->getScalerContext()); + proxyContext->initCache(strike.get(), fStrikeCache); + } + + if (!deserializer.read(&glyphImagesCount)) READ_FAILURE + for (size_t j = 0; j < glyphImagesCount; j++) { + SkTLazy glyph; + if (!ReadGlyph(glyph, &deserializer)) READ_FAILURE + + if (!glyph->isEmpty()) { + const volatile void* image = + deserializer.read(glyph->imageSize(), glyph->formatAlignment()); + if (!image) READ_FAILURE + glyph->fImage = (void*)image; + } + + strike->mergeGlyphAndImage(glyph->getPackedID(), *glyph); + } + + if (!deserializer.read(&glyphPathsCount)) READ_FAILURE + for (size_t j = 0; j < glyphPathsCount; j++) { + SkTLazy glyph; + if (!ReadGlyph(glyph, &deserializer)) READ_FAILURE + + SkGlyph* allocatedGlyph = strike->mergeGlyphAndImage(glyph->getPackedID(), *glyph); + + SkPath* pathPtr = nullptr; + SkPath path; + uint64_t pathSize = 0u; + if (!deserializer.read(&pathSize)) READ_FAILURE + + if (pathSize > 0) { + auto* pathData = deserializer.read(pathSize, kPathAlignment); + if (!pathData) READ_FAILURE + if (!path.readFromMemory(const_cast(pathData), pathSize)) READ_FAILURE + pathPtr = &path; + } + + strike->preparePath(allocatedGlyph, pathPtr); + } + } + + return true; +} + +sk_sp SkStrikeClient::deserializeTypeface(const void* buf, size_t len) { + WireTypeface wire; + if (len != sizeof(wire)) return nullptr; + memcpy(&wire, buf, sizeof(wire)); + return this->addTypeface(wire); +} + +sk_sp SkStrikeClient::addTypeface(const WireTypeface& wire) { + auto* typeface = fRemoteFontIdToTypeface.find(wire.typefaceID); + if (typeface) return *typeface; + + auto newTypeface = sk_make_sp( + wire.typefaceID, wire.glyphCount, wire.style, wire.isFixed, + fDiscardableHandleManager, fIsLogging); + fRemoteFontIdToTypeface.set(wire.typefaceID, newTypeface); + return newTypeface; +} diff --git a/gfx/skia/skia/src/core/SkRemoteGlyphCache.h b/gfx/skia/skia/src/core/SkRemoteGlyphCache.h new file mode 100644 index 0000000000..d81413f55c --- /dev/null +++ b/gfx/skia/skia/src/core/SkRemoteGlyphCache.h @@ -0,0 +1,224 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRemoteGlyphCache_DEFINED +#define SkRemoteGlyphCache_DEFINED + +#include +#include +#include +#include +#include + +#include "include/core/SkData.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSerialProcs.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkTHash.h" +#include "include/utils/SkNoDrawCanvas.h" +#include "src/core/SkDevice.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkStrikeForGPU.h" +#include "src/core/SkTLazy.h" + +class Deserializer; +class Serializer; +enum SkAxisAlignment : uint32_t; +class SkDescriptor; +class SkStrike; +struct SkPackedGlyphID; +enum SkScalerContextFlags : uint32_t; +class SkStrikeCache; +class SkTypefaceProxy; +struct WireTypeface; + +class SkStrikeServer; + +// A SkTextBlobCacheDiffCanvas is used to populate the SkStrikeServer with ops +// which will be serialized and rendered using the SkStrikeClient. +class SkTextBlobCacheDiffCanvas : public SkNoDrawCanvas { +public: + + // For testing use only + SkTextBlobCacheDiffCanvas(int width, int height, const SkSurfaceProps& props, + SkStrikeServer* strikeServer, bool DFTSupport = true); + + SK_API SkTextBlobCacheDiffCanvas(int width, int height, const SkSurfaceProps& props, + SkStrikeServer* strikeServer, sk_sp colorSpace, + bool DFTSupport); + + SK_API ~SkTextBlobCacheDiffCanvas() override; + +protected: + SkCanvas::SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override; + bool onDoSaveBehind(const SkRect*) override; + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + +private: + class TrackLayerDevice; +}; + +using SkDiscardableHandleId = uint32_t; + +// This class is not thread-safe. +class SkStrikeServer final : public SkStrikeForGPUCacheInterface { +public: + // An interface used by the server to create handles for pinning SkStrike + // entries on the remote client. + class DiscardableHandleManager { + public: + SK_API virtual ~DiscardableHandleManager() = default; + + // Creates a new *locked* handle and returns a unique ID that can be used to identify + // it on the remote client. + SK_API virtual SkDiscardableHandleId createHandle() = 0; + + // Returns true if the handle could be successfully locked. The server can + // assume it will remain locked until the next set of serialized entries is + // pulled from the SkStrikeServer. + // If returns false, the cache entry mapped to the handle has been deleted + // on the client. Any subsequent attempts to lock the same handle are not + // allowed. + SK_API virtual bool lockHandle(SkDiscardableHandleId) = 0; + + // Returns true if a handle has been deleted on the remote client. It is + // invalid to use a handle id again with this manager once this returns true. + // TODO(khushalsagar): Make pure virtual once chrome implementation lands. + SK_API virtual bool isHandleDeleted(SkDiscardableHandleId) { return false; } + }; + + SK_API explicit SkStrikeServer(DiscardableHandleManager* discardableHandleManager); + SK_API ~SkStrikeServer() override; + + // Serializes the typeface to be transmitted using this server. + SK_API sk_sp serializeTypeface(SkTypeface*); + + // Serializes the strike data captured using a SkTextBlobCacheDiffCanvas. Any + // handles locked using the DiscardableHandleManager will be assumed to be + // unlocked after this call. + SK_API void writeStrikeData(std::vector* memory); + + // Methods used internally in Skia ------------------------------------------ + class RemoteStrike; + + RemoteStrike* getOrCreateCache(const SkPaint&, + const SkFont& font, + const SkSurfaceProps&, + const SkMatrix&, + SkScalerContextFlags flags, + SkScalerContextEffects* effects); + + SkScopedStrikeForGPU findOrCreateScopedStrike(const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) override; + + static void AddGlyphForTesting( + RemoteStrike* cache, SkPackedGlyphID glyphID, bool asPath); + + void setMaxEntriesInDescriptorMapForTesting(size_t count) { + fMaxEntriesInDescriptorMap = count; + } + size_t remoteStrikeMapSizeForTesting() const { return fDescToRemoteStrike.size(); } + +private: + static constexpr size_t kMaxEntriesInDescriptorMap = 2000u; + + void checkForDeletedEntries(); + + RemoteStrike* getOrCreateCache(const SkDescriptor& desc, + const SkTypeface& typeface, + SkScalerContextEffects effects); + + struct MapOps { + size_t operator()(const SkDescriptor* key) const; + bool operator()(const SkDescriptor* lhs, const SkDescriptor* rhs) const; + }; + using DescToRemoteStrike = + std::unordered_map, MapOps, MapOps>; + DescToRemoteStrike fDescToRemoteStrike; + + DiscardableHandleManager* const fDiscardableHandleManager; + SkTHashSet fCachedTypefaces; + size_t fMaxEntriesInDescriptorMap = kMaxEntriesInDescriptorMap; + + // Cached serialized typefaces. + SkTHashMap> fSerializedTypefaces; + + // State cached until the next serialization. + SkTHashSet fRemoteStrikesToSend; + std::vector fTypefacesToSend; +}; + +class SkStrikeClient { +public: + // This enum is used in histogram reporting in chromium. Please don't re-order the list of + // entries, and consider it to be append-only. + enum CacheMissType : uint32_t { + // Hard failures where no fallback could be found. + kFontMetrics = 0, + kGlyphMetrics = 1, + kGlyphImage = 2, + kGlyphPath = 3, + + // The original glyph could not be found and a fallback was used. + kGlyphMetricsFallback = 4, + kGlyphPathFallback = 5, + + kLast = kGlyphPathFallback + }; + + // An interface to delete handles that may be pinned by the remote server. + class DiscardableHandleManager : public SkRefCnt { + public: + ~DiscardableHandleManager() override = default; + + // Returns true if the handle was unlocked and can be safely deleted. Once + // successful, subsequent attempts to delete the same handle are invalid. + virtual bool deleteHandle(SkDiscardableHandleId) = 0; + + virtual void notifyCacheMiss(CacheMissType) {} + + struct ReadFailureData { + size_t memorySize; + size_t bytesRead; + uint64_t typefaceSize; + uint64_t strikeCount; + uint64_t glyphImagesCount; + uint64_t glyphPathsCount; + }; + virtual void notifyReadFailure(const ReadFailureData& data) {} + }; + + SK_API explicit SkStrikeClient(sk_sp, + bool isLogging = true, + SkStrikeCache* strikeCache = nullptr); + SK_API ~SkStrikeClient(); + + // Deserializes the typeface previously serialized using the SkStrikeServer. Returns null if the + // data is invalid. + SK_API sk_sp deserializeTypeface(const void* data, size_t length); + + // Deserializes the strike data from a SkStrikeServer. All messages generated + // from a server when serializing the ops must be deserialized before the op + // is rasterized. + // Returns false if the data is invalid. + SK_API bool readStrikeData(const volatile void* memory, size_t memorySize); + +private: + class DiscardableStrikePinner; + + static bool ReadGlyph(SkTLazy& glyph, Deserializer* deserializer); + sk_sp addTypeface(const WireTypeface& wire); + + SkTHashMap> fRemoteFontIdToTypeface; + sk_sp fDiscardableHandleManager; + SkStrikeCache* const fStrikeCache; + const bool fIsLogging; +}; + +#endif // SkRemoteGlyphCache_DEFINED diff --git a/gfx/skia/skia/src/core/SkResourceCache.cpp b/gfx/skia/skia/src/core/SkResourceCache.cpp new file mode 100644 index 0000000000..61197f85cc --- /dev/null +++ b/gfx/skia/skia/src/core/SkResourceCache.cpp @@ -0,0 +1,605 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkResourceCache.h" + +#include "include/core/SkTraceMemoryDump.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTo.h" +#include "src/core/SkDiscardableMemory.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkMessageBus.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkOpts.h" + +#include +#include + +DECLARE_SKMESSAGEBUS_MESSAGE(SkResourceCache::PurgeSharedIDMessage) + +static inline bool SkShouldPostMessageToBus( + const SkResourceCache::PurgeSharedIDMessage&, uint32_t) { + // SkResourceCache is typically used as a singleton and we don't label Inboxes so all messages + // go to all inboxes. + return true; +} + +// This can be defined by the caller's build system +//#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE + +#ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT +# define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 1024 +#endif + +#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT + #define SK_DEFAULT_IMAGE_CACHE_LIMIT (32 * 1024 * 1024) +#endif + +void SkResourceCache::Key::init(void* nameSpace, uint64_t sharedID, size_t dataSize) { + SkASSERT(SkAlign4(dataSize) == dataSize); + + // fCount32 and fHash are not hashed + static const int kUnhashedLocal32s = 2; // fCache32 + fHash + static const int kSharedIDLocal32s = 2; // fSharedID_lo + fSharedID_hi + static const int kHashedLocal32s = kSharedIDLocal32s + (sizeof(fNamespace) >> 2); + static const int kLocal32s = kUnhashedLocal32s + kHashedLocal32s; + + static_assert(sizeof(Key) == (kLocal32s << 2), "unaccounted_key_locals"); + static_assert(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace), + "namespace_field_must_be_last"); + + fCount32 = SkToS32(kLocal32s + (dataSize >> 2)); + fSharedID_lo = (uint32_t)(sharedID & 0xFFFFFFFF); + fSharedID_hi = (uint32_t)(sharedID >> 32); + fNamespace = nameSpace; + // skip unhashed fields when computing the hash + fHash = SkOpts::hash(this->as32() + kUnhashedLocal32s, + (fCount32 - kUnhashedLocal32s) << 2); +} + +#include "include/private/SkTHash.h" + +namespace { + struct HashTraits { + static uint32_t Hash(const SkResourceCache::Key& key) { return key.hash(); } + static const SkResourceCache::Key& GetKey(const SkResourceCache::Rec* rec) { + return rec->getKey(); + } + }; +} + +class SkResourceCache::Hash : + public SkTHashTable {}; + + +/////////////////////////////////////////////////////////////////////////////// + +void SkResourceCache::init() { + fHead = nullptr; + fTail = nullptr; + fHash = new Hash; + fTotalBytesUsed = 0; + fCount = 0; + fSingleAllocationByteLimit = 0; + + // One of these should be explicit set by the caller after we return. + fTotalByteLimit = 0; + fDiscardableFactory = nullptr; +} + +SkResourceCache::SkResourceCache(DiscardableFactory factory) { + this->init(); + fDiscardableFactory = factory; +} + +SkResourceCache::SkResourceCache(size_t byteLimit) { + this->init(); + fTotalByteLimit = byteLimit; +} + +SkResourceCache::~SkResourceCache() { + Rec* rec = fHead; + while (rec) { + Rec* next = rec->fNext; + delete rec; + rec = next; + } + delete fHash; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) { + this->checkMessages(); + + if (auto found = fHash->find(key)) { + Rec* rec = *found; + if (visitor(*rec, context)) { + this->moveToHead(rec); // for our LRU + return true; + } else { + this->remove(rec); // stale + return false; + } + } + return false; +} + +static void make_size_str(size_t size, SkString* str) { + const char suffix[] = { 'b', 'k', 'm', 'g', 't', 0 }; + int i = 0; + while (suffix[i] && (size > 1024)) { + i += 1; + size >>= 10; + } + str->printf("%zu%c", size, suffix[i]); +} + +static bool gDumpCacheTransactions; + +void SkResourceCache::add(Rec* rec, void* payload) { + this->checkMessages(); + + SkASSERT(rec); + // See if we already have this key (racy inserts, etc.) + if (Rec** preexisting = fHash->find(rec->getKey())) { + Rec* prev = *preexisting; + if (prev->canBePurged()) { + // if it can be purged, the install may fail, so we have to remove it + this->remove(prev); + } else { + // if it cannot be purged, we reuse it and delete the new one + prev->postAddInstall(payload); + delete rec; + return; + } + } + + this->addToHead(rec); + fHash->set(rec); + rec->postAddInstall(payload); + + if (gDumpCacheTransactions) { + SkString bytesStr, totalStr; + make_size_str(rec->bytesUsed(), &bytesStr); + make_size_str(fTotalBytesUsed, &totalStr); + SkDebugf("RC: add %5s %12p key %08x -- total %5s, count %d\n", + bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount); + } + + // since the new rec may push us over-budget, we perform a purge check now + this->purgeAsNeeded(); +} + +void SkResourceCache::remove(Rec* rec) { + SkASSERT(rec->canBePurged()); + size_t used = rec->bytesUsed(); + SkASSERT(used <= fTotalBytesUsed); + + this->release(rec); + fHash->remove(rec->getKey()); + + fTotalBytesUsed -= used; + fCount -= 1; + + //SkDebugf("-RC count [%3d] bytes %d\n", fCount, fTotalBytesUsed); + + if (gDumpCacheTransactions) { + SkString bytesStr, totalStr; + make_size_str(used, &bytesStr); + make_size_str(fTotalBytesUsed, &totalStr); + SkDebugf("RC: remove %5s %12p key %08x -- total %5s, count %d\n", + bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount); + } + + delete rec; +} + +void SkResourceCache::purgeAsNeeded(bool forcePurge) { + size_t byteLimit; + int countLimit; + + if (fDiscardableFactory) { + countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT; + byteLimit = UINT32_MAX; // no limit based on bytes + } else { + countLimit = SK_MaxS32; // no limit based on count + byteLimit = fTotalByteLimit; + } + + Rec* rec = fTail; + while (rec) { + if (!forcePurge && fTotalBytesUsed < byteLimit && fCount < countLimit) { + break; + } + + Rec* prev = rec->fPrev; + if (rec->canBePurged()) { + this->remove(rec); + } + rec = prev; + } +} + +//#define SK_TRACK_PURGE_SHAREDID_HITRATE + +#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE +static int gPurgeCallCounter; +static int gPurgeHitCounter; +#endif + +void SkResourceCache::purgeSharedID(uint64_t sharedID) { + if (0 == sharedID) { + return; + } + +#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE + gPurgeCallCounter += 1; + bool found = false; +#endif + // go backwards, just like purgeAsNeeded, just to make the code similar. + // could iterate either direction and still be correct. + Rec* rec = fTail; + while (rec) { + Rec* prev = rec->fPrev; + if (rec->getKey().getSharedID() == sharedID) { + // even though the "src" is now dead, caches could still be in-flight, so + // we have to check if it can be removed. + if (rec->canBePurged()) { + this->remove(rec); + } +#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE + found = true; +#endif + } + rec = prev; + } + +#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE + if (found) { + gPurgeHitCounter += 1; + } + + SkDebugf("PurgeShared calls=%d hits=%d rate=%g\n", gPurgeCallCounter, gPurgeHitCounter, + gPurgeHitCounter * 100.0 / gPurgeCallCounter); +#endif +} + +void SkResourceCache::visitAll(Visitor visitor, void* context) { + // go backwards, just like purgeAsNeeded, just to make the code similar. + // could iterate either direction and still be correct. + Rec* rec = fTail; + while (rec) { + visitor(*rec, context); + rec = rec->fPrev; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +size_t SkResourceCache::setTotalByteLimit(size_t newLimit) { + size_t prevLimit = fTotalByteLimit; + fTotalByteLimit = newLimit; + if (newLimit < prevLimit) { + this->purgeAsNeeded(); + } + return prevLimit; +} + +SkCachedData* SkResourceCache::newCachedData(size_t bytes) { + this->checkMessages(); + + if (fDiscardableFactory) { + SkDiscardableMemory* dm = fDiscardableFactory(bytes); + return dm ? new SkCachedData(bytes, dm) : nullptr; + } else { + return new SkCachedData(sk_malloc_throw(bytes), bytes); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkResourceCache::release(Rec* rec) { + Rec* prev = rec->fPrev; + Rec* next = rec->fNext; + + if (!prev) { + SkASSERT(fHead == rec); + fHead = next; + } else { + prev->fNext = next; + } + + if (!next) { + fTail = prev; + } else { + next->fPrev = prev; + } + + rec->fNext = rec->fPrev = nullptr; +} + +void SkResourceCache::moveToHead(Rec* rec) { + if (fHead == rec) { + return; + } + + SkASSERT(fHead); + SkASSERT(fTail); + + this->validate(); + + this->release(rec); + + fHead->fPrev = rec; + rec->fNext = fHead; + fHead = rec; + + this->validate(); +} + +void SkResourceCache::addToHead(Rec* rec) { + this->validate(); + + rec->fPrev = nullptr; + rec->fNext = fHead; + if (fHead) { + fHead->fPrev = rec; + } + fHead = rec; + if (!fTail) { + fTail = rec; + } + fTotalBytesUsed += rec->bytesUsed(); + fCount += 1; + + this->validate(); +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +void SkResourceCache::validate() const { + if (nullptr == fHead) { + SkASSERT(nullptr == fTail); + SkASSERT(0 == fTotalBytesUsed); + return; + } + + if (fHead == fTail) { + SkASSERT(nullptr == fHead->fPrev); + SkASSERT(nullptr == fHead->fNext); + SkASSERT(fHead->bytesUsed() == fTotalBytesUsed); + return; + } + + SkASSERT(nullptr == fHead->fPrev); + SkASSERT(fHead->fNext); + SkASSERT(nullptr == fTail->fNext); + SkASSERT(fTail->fPrev); + + size_t used = 0; + int count = 0; + const Rec* rec = fHead; + while (rec) { + count += 1; + used += rec->bytesUsed(); + SkASSERT(used <= fTotalBytesUsed); + rec = rec->fNext; + } + SkASSERT(fCount == count); + + rec = fTail; + while (rec) { + SkASSERT(count > 0); + count -= 1; + SkASSERT(used >= rec->bytesUsed()); + used -= rec->bytesUsed(); + rec = rec->fPrev; + } + + SkASSERT(0 == count); + SkASSERT(0 == used); +} +#endif + +void SkResourceCache::dump() const { + this->validate(); + + SkDebugf("SkResourceCache: count=%d bytes=%d %s\n", + fCount, fTotalBytesUsed, fDiscardableFactory ? "discardable" : "malloc"); +} + +size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) { + size_t oldLimit = fSingleAllocationByteLimit; + fSingleAllocationByteLimit = newLimit; + return oldLimit; +} + +size_t SkResourceCache::getSingleAllocationByteLimit() const { + return fSingleAllocationByteLimit; +} + +size_t SkResourceCache::getEffectiveSingleAllocationByteLimit() const { + // fSingleAllocationByteLimit == 0 means the caller is asking for our default + size_t limit = fSingleAllocationByteLimit; + + // if we're not discardable (i.e. we are fixed-budget) then cap the single-limit + // to our budget. + if (nullptr == fDiscardableFactory) { + if (0 == limit) { + limit = fTotalByteLimit; + } else { + limit = SkTMin(limit, fTotalByteLimit); + } + } + return limit; +} + +void SkResourceCache::checkMessages() { + SkTArray msgs; + fPurgeSharedIDInbox.poll(&msgs); + for (int i = 0; i < msgs.count(); ++i) { + this->purgeSharedID(msgs[i].fSharedID); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +static SkResourceCache* gResourceCache = nullptr; +static SkMutex& resource_cache_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} + +/** Must hold resource_cache_mutex() when calling. */ +static SkResourceCache* get_cache() { + // resource_cache_mutex() is always held when this is called, so we don't need to be fancy in here. + resource_cache_mutex().assertHeld(); + if (nullptr == gResourceCache) { +#ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE + gResourceCache = new SkResourceCache(SkDiscardableMemory::Create); +#else + gResourceCache = new SkResourceCache(SK_DEFAULT_IMAGE_CACHE_LIMIT); +#endif + } + return gResourceCache; +} + +size_t SkResourceCache::GetTotalBytesUsed() { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->getTotalBytesUsed(); +} + +size_t SkResourceCache::GetTotalByteLimit() { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->getTotalByteLimit(); +} + +size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->setTotalByteLimit(newLimit); +} + +SkResourceCache::DiscardableFactory SkResourceCache::GetDiscardableFactory() { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->discardableFactory(); +} + +SkCachedData* SkResourceCache::NewCachedData(size_t bytes) { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->newCachedData(bytes); +} + +void SkResourceCache::Dump() { + SkAutoMutexExclusive am(resource_cache_mutex()); + get_cache()->dump(); +} + +size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->setSingleAllocationByteLimit(size); +} + +size_t SkResourceCache::GetSingleAllocationByteLimit() { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->getSingleAllocationByteLimit(); +} + +size_t SkResourceCache::GetEffectiveSingleAllocationByteLimit() { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->getEffectiveSingleAllocationByteLimit(); +} + +void SkResourceCache::PurgeAll() { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->purgeAll(); +} + +bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) { + SkAutoMutexExclusive am(resource_cache_mutex()); + return get_cache()->find(key, visitor, context); +} + +void SkResourceCache::Add(Rec* rec, void* payload) { + SkAutoMutexExclusive am(resource_cache_mutex()); + get_cache()->add(rec, payload); +} + +void SkResourceCache::VisitAll(Visitor visitor, void* context) { + SkAutoMutexExclusive am(resource_cache_mutex()); + get_cache()->visitAll(visitor, context); +} + +void SkResourceCache::PostPurgeSharedID(uint64_t sharedID) { + if (sharedID) { + SkMessageBus::Post(PurgeSharedIDMessage(sharedID)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkGraphics.h" +#include "include/core/SkImageFilter.h" + +size_t SkGraphics::GetResourceCacheTotalBytesUsed() { + return SkResourceCache::GetTotalBytesUsed(); +} + +size_t SkGraphics::GetResourceCacheTotalByteLimit() { + return SkResourceCache::GetTotalByteLimit(); +} + +size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) { + return SkResourceCache::SetTotalByteLimit(newLimit); +} + +size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() { + return SkResourceCache::GetSingleAllocationByteLimit(); +} + +size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) { + return SkResourceCache::SetSingleAllocationByteLimit(newLimit); +} + +void SkGraphics::PurgeResourceCache() { + SkImageFilter_Base::PurgeCache(); + return SkResourceCache::PurgeAll(); +} + +///////////// + +static void dump_visitor(const SkResourceCache::Rec& rec, void*) { + SkDebugf("RC: %12s bytes %9lu discardable %p\n", + rec.getCategory(), rec.bytesUsed(), rec.diagnostic_only_getDiscardable()); +} + +void SkResourceCache::TestDumpMemoryStatistics() { + VisitAll(dump_visitor, nullptr); +} + +static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) { + SkTraceMemoryDump* dump = static_cast(context); + SkString dumpName = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec); + SkDiscardableMemory* discardable = rec.diagnostic_only_getDiscardable(); + if (discardable) { + dump->setDiscardableMemoryBacking(dumpName.c_str(), *discardable); + + // The discardable memory size will be calculated by dumper, but we also dump what we think + // the size of object in memory is irrespective of whether object is live or dead. + dump->dumpNumericValue(dumpName.c_str(), "discardable_size", "bytes", rec.bytesUsed()); + } else { + dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", rec.bytesUsed()); + dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); + } +} + +void SkResourceCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) { + // Since resource could be backed by malloc or discardable, the cache always dumps detailed + // stats to be accurate. + VisitAll(sk_trace_dump_visitor, dump); +} diff --git a/gfx/skia/skia/src/core/SkResourceCache.h b/gfx/skia/skia/src/core/SkResourceCache.h new file mode 100644 index 0000000000..4805b28d87 --- /dev/null +++ b/gfx/skia/skia/src/core/SkResourceCache.h @@ -0,0 +1,292 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkResourceCache_DEFINED +#define SkResourceCache_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkMessageBus.h" + +class SkCachedData; +class SkDiscardableMemory; +class SkTraceMemoryDump; + +/** + * Cache object for bitmaps (with possible scale in X Y as part of the key). + * + * Multiple caches can be instantiated, but each instance is not implicitly + * thread-safe, so if a given instance is to be shared across threads, the + * caller must manage the access itself (e.g. via a mutex). + * + * As a convenience, a global instance is also defined, which can be safely + * access across threads via the static methods (e.g. FindAndLock, etc.). + */ +class SkResourceCache { +public: + struct Key { + /** Key subclasses must call this after their own fields and data are initialized. + * All fields and data must be tightly packed. + * @param nameSpace must be unique per Key subclass. + * @param sharedID == 0 means ignore this field, does not support group purging. + * @param dataSize is size of fields and data of the subclass, must be a multiple of 4. + */ + void init(void* nameSpace, uint64_t sharedID, size_t dataSize); + + /** Returns the size of this key. */ + size_t size() const { + return fCount32 << 2; + } + + void* getNamespace() const { return fNamespace; } + uint64_t getSharedID() const { return ((uint64_t)fSharedID_hi << 32) | fSharedID_lo; } + + // This is only valid after having called init(). + uint32_t hash() const { return fHash; } + + bool operator==(const Key& other) const { + const uint32_t* a = this->as32(); + const uint32_t* b = other.as32(); + for (int i = 0; i < fCount32; ++i) { // (This checks fCount == other.fCount first.) + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + private: + int32_t fCount32; // local + user contents count32 + uint32_t fHash; + // split uint64_t into hi and lo so we don't force ourselves to pad on 32bit machines. + uint32_t fSharedID_lo; + uint32_t fSharedID_hi; + void* fNamespace; // A unique namespace tag. This is hashed. + /* uint32_t fContents32[] */ + + const uint32_t* as32() const { return (const uint32_t*)this; } + }; + + struct Rec { + typedef SkResourceCache::Key Key; + + Rec() {} + virtual ~Rec() {} + + uint32_t getHash() const { return this->getKey().hash(); } + + virtual const Key& getKey() const = 0; + virtual size_t bytesUsed() const = 0; + + // Called if the cache needs to purge/remove/delete the Rec. Default returns true. + // Subclass may return false if there are outstanding references to it (e.g. bitmaps). + // Will only be deleted/removed-from-the-cache when this returns true. + virtual bool canBePurged() { return true; } + + // A rec is first created/initialized, and then added to the cache. As part of the add(), + // the cache will callback into the rec with postAddInstall, passing in whatever payload + // was passed to add/Add. + // + // This late-install callback exists because the process of add-ing might end up deleting + // the new rec (if an existing rec in the cache has the same key and cannot be purged). + // If the new rec will be deleted during add, the pre-existing one (with the same key) + // will have postAddInstall() called on it instead, so that either way an "install" will + // happen during the add. + virtual void postAddInstall(void*) {} + + // for memory usage diagnostics + virtual const char* getCategory() const = 0; + virtual SkDiscardableMemory* diagnostic_only_getDiscardable() const { return nullptr; } + + private: + Rec* fNext; + Rec* fPrev; + + friend class SkResourceCache; + }; + + // Used with SkMessageBus + struct PurgeSharedIDMessage { + PurgeSharedIDMessage(uint64_t sharedID) : fSharedID(sharedID) {} + uint64_t fSharedID; + }; + + typedef const Rec* ID; + + /** + * Callback function for find(). If called, the cache will have found a match for the + * specified Key, and will pass in the corresponding Rec, along with a caller-specified + * context. The function can read the data in Rec, and copy whatever it likes into context + * (casting context to whatever it really is). + * + * The return value determines what the cache will do with the Rec. If the function returns + * true, then the Rec is considered "valid". If false is returned, the Rec will be considered + * "stale" and will be purged from the cache. + */ + typedef bool (*FindVisitor)(const Rec&, void* context); + + /** + * Returns a locked/pinned SkDiscardableMemory instance for the specified + * number of bytes, or nullptr on failure. + */ + typedef SkDiscardableMemory* (*DiscardableFactory)(size_t bytes); + + /* + * The following static methods are thread-safe wrappers around a global + * instance of this cache. + */ + + /** + * Returns true if the visitor was called on a matching Key, and the visitor returned true. + * + * Find() will search the cache for the specified Key. If no match is found, return false and + * do not call the FindVisitor. If a match is found, return whatever the visitor returns. + * Its return value is interpreted to mean: + * true : Rec is valid + * false : Rec is "stale" -- the cache will purge it. + */ + static bool Find(const Key& key, FindVisitor, void* context); + static void Add(Rec*, void* payload = nullptr); + + typedef void (*Visitor)(const Rec&, void* context); + // Call the visitor for every Rec in the cache. + static void VisitAll(Visitor, void* context); + + static size_t GetTotalBytesUsed(); + static size_t GetTotalByteLimit(); + static size_t SetTotalByteLimit(size_t newLimit); + + static size_t SetSingleAllocationByteLimit(size_t); + static size_t GetSingleAllocationByteLimit(); + static size_t GetEffectiveSingleAllocationByteLimit(); + + static void PurgeAll(); + + static void TestDumpMemoryStatistics(); + + /** Dump memory usage statistics of every Rec in the cache using the + SkTraceMemoryDump interface. + */ + static void DumpMemoryStatistics(SkTraceMemoryDump* dump); + + /** + * Returns the DiscardableFactory used by the global cache, or nullptr. + */ + static DiscardableFactory GetDiscardableFactory(); + + static SkCachedData* NewCachedData(size_t bytes); + + static void PostPurgeSharedID(uint64_t sharedID); + + /** + * Call SkDebugf() with diagnostic information about the state of the cache + */ + static void Dump(); + + /////////////////////////////////////////////////////////////////////////// + + /** + * Construct the cache to call DiscardableFactory when it + * allocates memory for the pixels. In this mode, the cache has + * not explicit budget, and so methods like getTotalBytesUsed() + * and getTotalByteLimit() will return 0, and setTotalByteLimit + * will ignore its argument and return 0. + */ + SkResourceCache(DiscardableFactory); + + /** + * Construct the cache, allocating memory with malloc, and respect the + * byteLimit, purging automatically when a new image is added to the cache + * that pushes the total bytesUsed over the limit. Note: The limit can be + * changed at runtime with setTotalByteLimit. + */ + explicit SkResourceCache(size_t byteLimit); + ~SkResourceCache(); + + /** + * Returns true if the visitor was called on a matching Key, and the visitor returned true. + * + * find() will search the cache for the specified Key. If no match is found, return false and + * do not call the FindVisitor. If a match is found, return whatever the visitor returns. + * Its return value is interpreted to mean: + * true : Rec is valid + * false : Rec is "stale" -- the cache will purge it. + */ + bool find(const Key&, FindVisitor, void* context); + void add(Rec*, void* payload = nullptr); + void visitAll(Visitor, void* context); + + size_t getTotalBytesUsed() const { return fTotalBytesUsed; } + size_t getTotalByteLimit() const { return fTotalByteLimit; } + + /** + * This is respected by SkBitmapProcState::possiblyScaleImage. + * 0 is no maximum at all; this is the default. + * setSingleAllocationByteLimit() returns the previous value. + */ + size_t setSingleAllocationByteLimit(size_t maximumAllocationSize); + size_t getSingleAllocationByteLimit() const; + // returns the logical single allocation size (pinning against the budget when the cache + // is not backed by discardable memory. + size_t getEffectiveSingleAllocationByteLimit() const; + + /** + * Set the maximum number of bytes available to this cache. If the current + * cache exceeds this new value, it will be purged to try to fit within + * this new limit. + */ + size_t setTotalByteLimit(size_t newLimit); + + void purgeSharedID(uint64_t sharedID); + + void purgeAll() { + this->purgeAsNeeded(true); + } + + DiscardableFactory discardableFactory() const { return fDiscardableFactory; } + + SkCachedData* newCachedData(size_t bytes); + + /** + * Call SkDebugf() with diagnostic information about the state of the cache + */ + void dump() const; + +private: + Rec* fHead; + Rec* fTail; + + class Hash; + Hash* fHash; + + DiscardableFactory fDiscardableFactory; + + size_t fTotalBytesUsed; + size_t fTotalByteLimit; + size_t fSingleAllocationByteLimit; + int fCount; + + SkMessageBus::Inbox fPurgeSharedIDInbox; + + void checkMessages(); + void purgeAsNeeded(bool forcePurge = false); + + // linklist management + void moveToHead(Rec*); + void addToHead(Rec*); + void release(Rec*); + void remove(Rec*); + + void init(); // called by constructors + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif +}; +#endif diff --git a/gfx/skia/skia/src/core/SkSafeMath.h b/gfx/skia/skia/src/core/SkSafeMath.h new file mode 100644 index 0000000000..ba64f12bf8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSafeMath.h @@ -0,0 +1,108 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSafeMath_DEFINED +#define SkSafeMath_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkTFitsIn.h" +#include + +// SkSafeMath always check that a series of operations do not overflow. +// This must be correct for all platforms, because this is a check for safety at runtime. + +class SkSafeMath { +public: + SkSafeMath() = default; + + bool ok() const { return fOK; } + explicit operator bool() const { return fOK; } + + size_t mul(size_t x, size_t y) { + return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y); + } + + size_t add(size_t x, size_t y) { + size_t result = x + y; + fOK &= result >= x; + return result; + } + + /** + * Return a + b, unless this result is an overflow/underflow. In those cases, fOK will + * be set to false, and it is undefined what this returns. + */ + int addInt(int a, int b) { + if (b < 0 && a < std::numeric_limits::min() - b) { + fOK = false; + return a; + } else if (b > 0 && a > std::numeric_limits::max() - b) { + fOK = false; + return a; + } + return a + b; + } + + size_t alignUp(size_t x, size_t alignment) { + SkASSERT(alignment && !(alignment & (alignment - 1))); + return add(x, alignment - 1) & ~(alignment - 1); + } + + template T castTo(size_t value) { + if (!SkTFitsIn(value)) { + fOK = false; + } + return static_cast(value); + } + + // These saturate to their results + static size_t Add(size_t x, size_t y); + static size_t Mul(size_t x, size_t y); + static size_t Align4(size_t x) { + SkSafeMath safe; + return safe.alignUp(x, 4); + } + +private: + uint32_t mul32(uint32_t x, uint32_t y) { + uint64_t bx = x; + uint64_t by = y; + uint64_t result = bx * by; + fOK &= result >> 32 == 0; + return result; + } + + uint64_t mul64(uint64_t x, uint64_t y) { + if (x <= std::numeric_limits::max() >> 32 + && y <= std::numeric_limits::max() >> 32) { + return x * y; + } else { + auto hi = [](uint64_t x) { return x >> 32; }; + auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; + + uint64_t lx_ly = lo(x) * lo(y); + uint64_t hx_ly = hi(x) * lo(y); + uint64_t lx_hy = lo(x) * hi(y); + uint64_t hx_hy = hi(x) * hi(y); + uint64_t result = 0; + result = this->add(lx_ly, (hx_ly << 32)); + result = this->add(result, (lx_hy << 32)); + fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0; + + #if defined(SK_DEBUG) && defined(__clang__) && defined(__x86_64__) + auto double_check = (unsigned __int128)x * y; + SkASSERT(result == (double_check & 0xFFFFFFFFFFFFFFFF)); + SkASSERT(!fOK || (double_check >> 64 == 0)); + #endif + + return result; + } + } + bool fOK = true; +}; + +#endif//SkSafeMath_DEFINED diff --git a/gfx/skia/skia/src/core/SkSafeRange.h b/gfx/skia/skia/src/core/SkSafeRange.h new file mode 100644 index 0000000000..b207cff8b5 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSafeRange.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSafeRange_DEFINED +#define SkSafeRange_DEFINED + +#include "include/core/SkTypes.h" + +#include + +// SkSafeRange always check that a series of operations are in-range. +// This check is sticky, so that if any one operation fails, the object will remember that and +// return false from ok(). + +class SkSafeRange { +public: + MOZ_IMPLICIT operator bool() const { return fOK; } + + bool ok() const { return fOK; } + + // checks 0 <= value <= max. + // On success, returns value + // On failure, returns 0 and sets ok() to false + template T checkLE(uint64_t value, T max) { + SkASSERT(static_cast(max) >= 0); + if (value > static_cast(max)) { + fOK = false; + value = 0; + } + return static_cast(value); + } + + int checkGE(int value, int min) { + if (value < min) { + fOK = false; + value = min; + } + return value; + } + +private: + bool fOK = true; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkScalar.cpp b/gfx/skia/skia/src/core/SkScalar.cpp new file mode 100644 index 0000000000..66f875e8a3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScalar.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkMath.h" +#include "include/core/SkScalar.h" + +SkScalar SkScalarInterpFunc(SkScalar searchKey, const SkScalar keys[], + const SkScalar values[], int length) { + SkASSERT(length > 0); + SkASSERT(keys != nullptr); + SkASSERT(values != nullptr); +#ifdef SK_DEBUG + for (int i = 1; i < length; i++) + SkASSERT(keys[i] >= keys[i-1]); +#endif + int right = 0; + while (right < length && searchKey > keys[right]) + right++; + // Could use sentinel values to eliminate conditionals, but since the + // tables are taken as input, a simpler format is better. + if (length == right) + return values[length-1]; + if (0 == right) + return values[0]; + // Otherwise, interpolate between right - 1 and right. + SkScalar rightKey = keys[right]; + SkScalar leftKey = keys[right-1]; + SkScalar fract = (searchKey - leftKey) / (rightKey - leftKey); + return SkScalarInterp(values[right-1], values[right], fract); +} diff --git a/gfx/skia/skia/src/core/SkScaleToSides.h b/gfx/skia/skia/src/core/SkScaleToSides.h new file mode 100644 index 0000000000..e48d542e2e --- /dev/null +++ b/gfx/skia/skia/src/core/SkScaleToSides.h @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkScaleToSides_DEFINED +#define SkScaleToSides_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" + +#include +#include + +class SkScaleToSides { +public: + // This code assumes that a and b fit in a float, and therefore the resulting smaller value + // of a and b will fit in a float. The side of the rectangle may be larger than a float. + // Scale must be less than or equal to the ratio limit / (*a + *b). + // This code assumes that NaN and Inf are never passed in. + static void AdjustRadii(double limit, double scale, SkScalar* a, SkScalar* b) { + SkASSERTF(scale < 1.0 && scale > 0.0, "scale: %g", scale); + + *a = (float)((double)*a * scale); + *b = (float)((double)*b * scale); + + if (*a + *b > limit) { + float* minRadius = a; + float* maxRadius = b; + + // Force minRadius to be the smaller of the two. + if (*minRadius > *maxRadius) { + using std::swap; + swap(minRadius, maxRadius); + } + + // newMinRadius must be float in order to give the actual value of the radius. + // The newMinRadius will always be smaller than limit. The largest that minRadius can be + // is 1/2 the ratio of minRadius : (minRadius + maxRadius), therefore in the resulting + // division, minRadius can be no larger than 1/2 limit + ULP. The newMinRadius can be + // 1/2 a ULP off at this point. + float newMinRadius = *minRadius; + + // Because newMaxRadius is the result of a double to float conversion, it can be larger + // than limit, but only by one ULP. + float newMaxRadius = (float)(limit - newMinRadius); + + // The total sum of newMinRadius and newMaxRadius can be upto 1.5 ULPs off. If the + // sum is greater than the limit then newMaxRadius may have to be reduced twice. + // Note: nextafterf is a c99 call and should be std::nextafter, but this is not + // implemented in the GCC ARM compiler. + if (newMaxRadius + newMinRadius > limit) { + newMaxRadius = nextafterf(newMaxRadius, 0.0f); + if (newMaxRadius + newMinRadius > limit) { + newMaxRadius = nextafterf(newMaxRadius, 0.0f); + } + } + *maxRadius = newMaxRadius; + } + + SkASSERTF(*a >= 0.0f && *b >= 0.0f, "a: %g, b: %g, limit: %g, scale: %g", *a, *b, limit, + scale); + + SkASSERTF(*a + *b <= limit, + "\nlimit: %.17f, sum: %.17f, a: %.10f, b: %.10f, scale: %.20f", + limit, *a + *b, *a, *b, scale); + } +}; +#endif // ScaleToSides_DEFINED diff --git a/gfx/skia/skia/src/core/SkScalerContext.cpp b/gfx/skia/skia/src/core/SkScalerContext.cpp new file mode 100644 index 0000000000..fb783c965f --- /dev/null +++ b/gfx/skia/skia/src/core/SkScalerContext.cpp @@ -0,0 +1,1205 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "src/core/SkScalerContext.h" + +#include "include/core/SkFontMetrics.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkStrokeRec.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkDraw.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkStroke.h" +#include "src/core/SkSurfacePriv.h" +#include "src/core/SkTextFormatParams.h" +#include "src/core/SkWriteBuffer.h" +#include "src/utils/SkMatrix22.h" +#include + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + #define DUMP_RECx +#endif + +SkScalerContextRec SkScalerContext::PreprocessRec(const SkTypeface& typeface, + const SkScalerContextEffects& effects, + const SkDescriptor& desc) { + SkScalerContextRec rec = + *static_cast(desc.findEntry(kRec_SkDescriptorTag, nullptr)); + + // Allow the typeface to adjust the rec. + typeface.onFilterRec(&rec); + + if (effects.fMaskFilter) { + // Pre-blend is not currently applied to filtered text. + // The primary filter is blur, for which contrast makes no sense, + // and for which the destination guess error is more visible. + // Also, all existing users of blur have calibrated for linear. + rec.ignorePreBlend(); + } + + SkColor lumColor = rec.getLuminanceColor(); + + if (rec.fMaskFormat == SkMask::kA8_Format) { + U8CPU lum = SkComputeLuminance(SkColorGetR(lumColor), + SkColorGetG(lumColor), + SkColorGetB(lumColor)); + lumColor = SkColorSetRGB(lum, lum, lum); + } + + // TODO: remove CanonicalColor when we to fix up Chrome layout tests. + rec.setLuminanceColor(lumColor); + + return rec; +} + +SkScalerContext::SkScalerContext(sk_sp typeface, const SkScalerContextEffects& effects, + const SkDescriptor* desc) + : fRec(PreprocessRec(*typeface, effects, *desc)) + , fTypeface(std::move(typeface)) + , fPathEffect(sk_ref_sp(effects.fPathEffect)) + , fMaskFilter(sk_ref_sp(effects.fMaskFilter)) + // Initialize based on our settings. Subclasses can also force this. + , fGenerateImageFromPath(fRec.fFrameWidth > 0 || fPathEffect != nullptr) + + , fPreBlend(fMaskFilter ? SkMaskGamma::PreBlend() : SkScalerContext::GetMaskPreBlend(fRec)) +{ +#ifdef DUMP_REC + SkDebugf("SkScalerContext checksum %x count %d length %d\n", + desc->getChecksum(), desc->getCount(), desc->getLength()); + SkDebugf("%s", fRec.dump().c_str()); + SkDebugf(" effects %x\n", desc->findEntry(kEffects_SkDescriptorTag, nullptr)); +#endif +} + +SkScalerContext::~SkScalerContext() {} + +/** + * In order to call cachedDeviceLuminance, cachedPaintLuminance, or + * cachedMaskGamma the caller must hold the mask_gamma_cache_mutex and continue + * to hold it until the returned pointer is refed or forgotten. + */ +static SkMutex& mask_gamma_cache_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} + +static SkMaskGamma* gLinearMaskGamma = nullptr; +static SkMaskGamma* gMaskGamma = nullptr; +static SkScalar gContrast = SK_ScalarMin; +static SkScalar gPaintGamma = SK_ScalarMin; +static SkScalar gDeviceGamma = SK_ScalarMin; + +/** + * The caller must hold the mask_gamma_cache_mutex() and continue to hold it until + * the returned SkMaskGamma pointer is refed or forgotten. + */ +static const SkMaskGamma& cached_mask_gamma(SkScalar contrast, SkScalar paintGamma, + SkScalar deviceGamma) { + mask_gamma_cache_mutex().assertHeld(); + if (0 == contrast && SK_Scalar1 == paintGamma && SK_Scalar1 == deviceGamma) { + if (nullptr == gLinearMaskGamma) { + gLinearMaskGamma = new SkMaskGamma; + } + return *gLinearMaskGamma; + } + if (gContrast != contrast || gPaintGamma != paintGamma || gDeviceGamma != deviceGamma) { + SkSafeUnref(gMaskGamma); + gMaskGamma = new SkMaskGamma(contrast, paintGamma, deviceGamma); + gContrast = contrast; + gPaintGamma = paintGamma; + gDeviceGamma = deviceGamma; + } + return *gMaskGamma; +} + +/** + * Expands fDeviceGamma, fPaintGamma, fContrast, and fLumBits into a mask pre-blend. + */ +SkMaskGamma::PreBlend SkScalerContext::GetMaskPreBlend(const SkScalerContextRec& rec) { + SkAutoMutexExclusive ama(mask_gamma_cache_mutex()); + + const SkMaskGamma& maskGamma = cached_mask_gamma(rec.getContrast(), + rec.getPaintGamma(), + rec.getDeviceGamma()); + + // TODO: remove CanonicalColor when we to fix up Chrome layout tests. + return maskGamma.preBlend(rec.getLuminanceColor()); +} + +size_t SkScalerContext::GetGammaLUTSize(SkScalar contrast, SkScalar paintGamma, + SkScalar deviceGamma, int* width, int* height) { + SkAutoMutexExclusive ama(mask_gamma_cache_mutex()); + const SkMaskGamma& maskGamma = cached_mask_gamma(contrast, + paintGamma, + deviceGamma); + + maskGamma.getGammaTableDimensions(width, height); + size_t size = (*width)*(*height)*sizeof(uint8_t); + + return size; +} + +bool SkScalerContext::GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma, + uint8_t* data) { + SkAutoMutexExclusive ama(mask_gamma_cache_mutex()); + const SkMaskGamma& maskGamma = cached_mask_gamma(contrast, + paintGamma, + deviceGamma); + const uint8_t* gammaTables = maskGamma.getGammaTables(); + if (!gammaTables) { + return false; + } + + int width, height; + maskGamma.getGammaTableDimensions(&width, &height); + size_t size = width*height * sizeof(uint8_t); + memcpy(data, gammaTables, size); + return true; +} + +void SkScalerContext::getAdvance(SkGlyph* glyph) { + if (generateAdvance(glyph)) { + glyph->fMaskFormat = MASK_FORMAT_JUST_ADVANCE; + } else { + this->getMetrics(glyph); + SkASSERT(glyph->fMaskFormat != MASK_FORMAT_UNKNOWN); + } +} + +void SkScalerContext::getMetrics(SkGlyph* glyph) { + bool generatingImageFromPath = fGenerateImageFromPath; + if (!generatingImageFromPath) { + generateMetrics(glyph); + SkASSERT(glyph->fMaskFormat != MASK_FORMAT_UNKNOWN); + } else { + SkPath devPath; + generatingImageFromPath = this->internalGetPath(glyph->getPackedID(), &devPath); + if (!generatingImageFromPath) { + generateMetrics(glyph); + SkASSERT(glyph->fMaskFormat != MASK_FORMAT_UNKNOWN); + } else { + uint8_t originMaskFormat = glyph->fMaskFormat; + if (!generateAdvance(glyph)) { + generateMetrics(glyph); + } + + if (originMaskFormat != MASK_FORMAT_UNKNOWN) { + glyph->fMaskFormat = originMaskFormat; + } else { + glyph->fMaskFormat = fRec.fMaskFormat; + } + + // If we are going to create the mask, then we cannot keep the color + if (SkMask::kARGB32_Format == glyph->fMaskFormat) { + glyph->fMaskFormat = SkMask::kA8_Format; + } + + const SkIRect ir = devPath.getBounds().roundOut(); + if (ir.isEmpty() || !SkRectPriv::Is16Bit(ir)) { + goto SK_ERROR; + } + glyph->fLeft = ir.fLeft; + glyph->fTop = ir.fTop; + glyph->fWidth = SkToU16(ir.width()); + glyph->fHeight = SkToU16(ir.height()); + + if (glyph->fWidth > 0) { + switch (glyph->fMaskFormat) { + case SkMask::kLCD16_Format: + if (fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag) { + glyph->fHeight += 2; + glyph->fTop -= 1; + } else { + glyph->fWidth += 2; + glyph->fLeft -= 1; + } + break; + default: + break; + } + } + } + } + + // if either dimension is empty, zap the image bounds of the glyph + if (0 == glyph->fWidth || 0 == glyph->fHeight) { + glyph->fWidth = 0; + glyph->fHeight = 0; + glyph->fTop = 0; + glyph->fLeft = 0; + glyph->fMaskFormat = 0; + return; + } + + if (fMaskFilter) { + SkMask src = glyph->mask(), + dst; + SkMatrix matrix; + + fRec.getMatrixFrom2x2(&matrix); + + src.fImage = nullptr; // only want the bounds from the filter + if (as_MFB(fMaskFilter)->filterMask(&dst, src, matrix, nullptr)) { + if (dst.fBounds.isEmpty() || !SkRectPriv::Is16Bit(dst.fBounds)) { + goto SK_ERROR; + } + SkASSERT(dst.fImage == nullptr); + glyph->fLeft = dst.fBounds.fLeft; + glyph->fTop = dst.fBounds.fTop; + glyph->fWidth = SkToU16(dst.fBounds.width()); + glyph->fHeight = SkToU16(dst.fBounds.height()); + glyph->fMaskFormat = dst.fFormat; + } + } + return; + +SK_ERROR: + // draw nothing 'cause we failed + glyph->fLeft = 0; + glyph->fTop = 0; + glyph->fWidth = 0; + glyph->fHeight = 0; + // put a valid value here, in case it was earlier set to + // MASK_FORMAT_JUST_ADVANCE + glyph->fMaskFormat = fRec.fMaskFormat; +} + +#define SK_SHOW_TEXT_BLIT_COVERAGE 0 + +static void applyLUTToA8Mask(const SkMask& mask, const uint8_t* lut) { + uint8_t* SK_RESTRICT dst = (uint8_t*)mask.fImage; + unsigned rowBytes = mask.fRowBytes; + + for (int y = mask.fBounds.height() - 1; y >= 0; --y) { + for (int x = mask.fBounds.width() - 1; x >= 0; --x) { + dst[x] = lut[dst[x]]; + } + dst += rowBytes; + } +} + +static void pack4xHToLCD16(const SkPixmap& src, const SkMask& dst, + const SkMaskGamma::PreBlend& maskPreBlend, + const bool doBGR, const bool doVert) { +#define SAMPLES_PER_PIXEL 4 +#define LCD_PER_PIXEL 3 + SkASSERT(kAlpha_8_SkColorType == src.colorType()); + SkASSERT(SkMask::kLCD16_Format == dst.fFormat); + + // doVert in this function means swap x and y when writing to dst. + if (doVert) { + SkASSERT(src.width() == (dst.fBounds.height() - 2) * 4); + SkASSERT(src.height() == dst.fBounds.width()); + } else { + SkASSERT(src.width() == (dst.fBounds.width() - 2) * 4); + SkASSERT(src.height() == dst.fBounds.height()); + } + + const int sample_width = src.width(); + const int height = src.height(); + + uint16_t* dstImage = (uint16_t*)dst.fImage; + size_t dstRB = dst.fRowBytes; + // An N tap FIR is defined by + // out[n] = coeff[0]*x[n] + coeff[1]*x[n-1] + ... + coeff[N]*x[n-N] + // or + // out[n] = sum(i, 0, N, coeff[i]*x[n-i]) + + // The strategy is to use one FIR (different coefficients) for each of r, g, and b. + // This means using every 4th FIR output value of each FIR and discarding the rest. + // The FIRs are aligned, and the coefficients reach 5 samples to each side of their 'center'. + // (For r and b this is technically incorrect, but the coeffs outside round to zero anyway.) + + // These are in some fixed point repesentation. + // Adding up to more than one simulates ink spread. + // For implementation reasons, these should never add up to more than two. + + // Coefficients determined by a gausian where 5 samples = 3 std deviations (0x110 'contrast'). + // Calculated using tools/generate_fir_coeff.py + // With this one almost no fringing is ever seen, but it is imperceptibly blurry. + // The lcd smoothed text is almost imperceptibly different from gray, + // but is still sharper on small stems and small rounded corners than gray. + // This also seems to be about as wide as one can get and only have a three pixel kernel. + // TODO: calculate these at runtime so parameters can be adjusted (esp contrast). + static const unsigned int coefficients[LCD_PER_PIXEL][SAMPLES_PER_PIXEL*3] = { + //The red subpixel is centered inside the first sample (at 1/6 pixel), and is shifted. + { 0x03, 0x0b, 0x1c, 0x33, 0x40, 0x39, 0x24, 0x10, 0x05, 0x01, 0x00, 0x00, }, + //The green subpixel is centered between two samples (at 1/2 pixel), so is symetric + { 0x00, 0x02, 0x08, 0x16, 0x2b, 0x3d, 0x3d, 0x2b, 0x16, 0x08, 0x02, 0x00, }, + //The blue subpixel is centered inside the last sample (at 5/6 pixel), and is shifted. + { 0x00, 0x00, 0x01, 0x05, 0x10, 0x24, 0x39, 0x40, 0x33, 0x1c, 0x0b, 0x03, }, + }; + + for (int y = 0; y < height; ++y) { + uint16_t* dstP; + size_t dstPDelta; + if (doVert) { + dstP = dstImage + y; + dstPDelta = dstRB; + } else { + dstP = SkTAddOffset(dstImage, dstRB * y); + dstPDelta = sizeof(uint16_t); + } + + const uint8_t* srcP = src.addr8(0, y); + + // TODO: this fir filter implementation is straight forward, but slow. + // It should be possible to make it much faster. + for (int sample_x = -4; sample_x < sample_width + 4; sample_x += 4) { + int fir[LCD_PER_PIXEL] = { 0 }; + for (int sample_index = SkMax32(0, sample_x - 4), coeff_index = sample_index - (sample_x - 4) + ; sample_index < SkMin32(sample_x + 8, sample_width) + ; ++sample_index, ++coeff_index) + { + int sample_value = srcP[sample_index]; + for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) { + fir[subpxl_index] += coefficients[subpxl_index][coeff_index] * sample_value; + } + } + for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) { + fir[subpxl_index] /= 0x100; + fir[subpxl_index] = SkMin32(fir[subpxl_index], 255); + } + + U8CPU r, g, b; + if (doBGR) { + r = fir[2]; + g = fir[1]; + b = fir[0]; + } else { + r = fir[0]; + g = fir[1]; + b = fir[2]; + } + if (maskPreBlend.isApplicable()) { + r = maskPreBlend.fR[r]; + g = maskPreBlend.fG[g]; + b = maskPreBlend.fB[b]; + } +#if SK_SHOW_TEXT_BLIT_COVERAGE + r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10); +#endif + *dstP = SkPack888ToRGB16(r, g, b); + dstP = SkTAddOffset(dstP, dstPDelta); + } + } +} + +static inline int convert_8_to_1(unsigned byte) { + SkASSERT(byte <= 0xFF); + return byte >> 7; +} + +static uint8_t pack_8_to_1(const uint8_t alpha[8]) { + unsigned bits = 0; + for (int i = 0; i < 8; ++i) { + bits <<= 1; + bits |= convert_8_to_1(alpha[i]); + } + return SkToU8(bits); +} + +static void packA8ToA1(const SkMask& mask, const uint8_t* src, size_t srcRB) { + const int height = mask.fBounds.height(); + const int width = mask.fBounds.width(); + const int octs = width >> 3; + const int leftOverBits = width & 7; + + uint8_t* dst = mask.fImage; + const int dstPad = mask.fRowBytes - SkAlign8(width)/8; + SkASSERT(dstPad >= 0); + + SkASSERT(width >= 0); + SkASSERT(srcRB >= (size_t)width); + const size_t srcPad = srcRB - width; + + for (int y = 0; y < height; ++y) { + for (int i = 0; i < octs; ++i) { + *dst++ = pack_8_to_1(src); + src += 8; + } + if (leftOverBits > 0) { + unsigned bits = 0; + int shift = 7; + for (int i = 0; i < leftOverBits; ++i, --shift) { + bits |= convert_8_to_1(*src++) << shift; + } + *dst++ = bits; + } + src += srcPad; + dst += dstPad; + } +} + +static void generateMask(const SkMask& mask, const SkPath& path, + const SkMaskGamma::PreBlend& maskPreBlend, + bool doBGR, bool doVert) { + SkPaint paint; + + int srcW = mask.fBounds.width(); + int srcH = mask.fBounds.height(); + int dstW = srcW; + int dstH = srcH; + int dstRB = mask.fRowBytes; + + SkMatrix matrix; + matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft), + -SkIntToScalar(mask.fBounds.fTop)); + + paint.setAntiAlias(SkMask::kBW_Format != mask.fFormat); + switch (mask.fFormat) { + case SkMask::kBW_Format: + dstRB = 0; // signals we need a copy + break; + case SkMask::kA8_Format: + break; + case SkMask::kLCD16_Format: + if (doVert) { + dstW = 4*dstH - 8; + dstH = srcW; + matrix.setAll(0, 4, -SkIntToScalar(mask.fBounds.fTop + 1) * 4, + 1, 0, -SkIntToScalar(mask.fBounds.fLeft), + 0, 0, 1); + } else { + dstW = 4*dstW - 8; + matrix.setAll(4, 0, -SkIntToScalar(mask.fBounds.fLeft + 1) * 4, + 0, 1, -SkIntToScalar(mask.fBounds.fTop), + 0, 0, 1); + } + dstRB = 0; // signals we need a copy + break; + default: + SkDEBUGFAIL("unexpected mask format"); + } + + SkRasterClip clip; + clip.setRect(SkIRect::MakeWH(dstW, dstH)); + + const SkImageInfo info = SkImageInfo::MakeA8(dstW, dstH); + SkAutoPixmapStorage dst; + + if (0 == dstRB) { + if (!dst.tryAlloc(info)) { + // can't allocate offscreen, so empty the mask and return + sk_bzero(mask.fImage, mask.computeImageSize()); + return; + } + } else { + dst.reset(info, mask.fImage, dstRB); + } + sk_bzero(dst.writable_addr(), dst.computeByteSize()); + + SkDraw draw; + draw.fDst = dst; + draw.fRC = &clip; + draw.fMatrix = &matrix; + draw.drawPath(path, paint); + + switch (mask.fFormat) { + case SkMask::kBW_Format: + packA8ToA1(mask, dst.addr8(0, 0), dst.rowBytes()); + break; + case SkMask::kA8_Format: + if (maskPreBlend.isApplicable()) { + applyLUTToA8Mask(mask, maskPreBlend.fG); + } + break; + case SkMask::kLCD16_Format: + pack4xHToLCD16(dst, mask, maskPreBlend, doBGR, doVert); + break; + default: + break; + } +} + +void SkScalerContext::getImage(const SkGlyph& origGlyph) { + const SkGlyph* glyph = &origGlyph; + SkGlyph tmpGlyph{origGlyph.getPackedID()}; + + // in case we need to call generateImage on a mask-format that is different + // (i.e. larger) than what our caller allocated by looking at origGlyph. + SkAutoMalloc tmpGlyphImageStorage; + + if (fMaskFilter) { // restore the prefilter bounds + + // need the original bounds, sans our maskfilter + sk_sp mf = std::move(fMaskFilter); + this->getMetrics(&tmpGlyph); + fMaskFilter = std::move(mf); + + // we need the prefilter bounds to be <= filter bounds + SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth); + SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight); + + if (tmpGlyph.fMaskFormat == origGlyph.fMaskFormat) { + tmpGlyph.fImage = origGlyph.fImage; + } else { + tmpGlyphImageStorage.reset(tmpGlyph.imageSize()); + tmpGlyph.fImage = tmpGlyphImageStorage.get(); + } + glyph = &tmpGlyph; + } + + if (!fGenerateImageFromPath) { + generateImage(*glyph); + } else { + SkPath devPath; + SkMask mask = glyph->mask(); + + if (!this->internalGetPath(glyph->getPackedID(), &devPath)) { + generateImage(*glyph); + } else { + SkASSERT(SkMask::kARGB32_Format != origGlyph.fMaskFormat); + SkASSERT(SkMask::kARGB32_Format != mask.fFormat); + const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); + const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); + generateMask(mask, devPath, fPreBlend, doBGR, doVert); + } + } + + if (fMaskFilter) { + // the src glyph image shouldn't be 3D + SkASSERT(SkMask::k3D_Format != glyph->fMaskFormat); + + SkMask srcM = glyph->mask(), + dstM; + SkMatrix matrix; + + fRec.getMatrixFrom2x2(&matrix); + + if (as_MFB(fMaskFilter)->filterMask(&dstM, srcM, matrix, nullptr)) { + int width = SkMin32(origGlyph.fWidth, dstM.fBounds.width()); + int height = SkMin32(origGlyph.fHeight, dstM.fBounds.height()); + int dstRB = origGlyph.rowBytes(); + int srcRB = dstM.fRowBytes; + + const uint8_t* src = (const uint8_t*)dstM.fImage; + uint8_t* dst = (uint8_t*)origGlyph.fImage; + + if (SkMask::k3D_Format == dstM.fFormat) { + // we have to copy 3 times as much + height *= 3; + } + + // clean out our glyph, since it may be larger than dstM + //sk_bzero(dst, height * dstRB); + + while (--height >= 0) { + memcpy(dst, src, width); + src += srcRB; + dst += dstRB; + } + SkMask::FreeImage(dstM.fImage); + } + } +} + +bool SkScalerContext::getPath(SkPackedGlyphID glyphID, SkPath* path) { + return this->internalGetPath(glyphID, path); +} + +void SkScalerContext::getFontMetrics(SkFontMetrics* fm) { + SkASSERT(fm); + this->generateFontMetrics(fm); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath) { + SkPath path; + if (!generatePath(glyphID.code(), &path)) { + return false; + } + + if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { + SkFixed dx = glyphID.getSubXFixed(); + SkFixed dy = glyphID.getSubYFixed(); + if (dx | dy) { + path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy)); + } + } + + if (fRec.fFrameWidth > 0 || fPathEffect != nullptr) { + // need the path in user-space, with only the point-size applied + // so that our stroking and effects will operate the same way they + // would if the user had extracted the path themself, and then + // called drawPath + SkPath localPath; + SkMatrix matrix, inverse; + + fRec.getMatrixFrom2x2(&matrix); + if (!matrix.invert(&inverse)) { + // assume devPath is already empty. + return true; + } + path.transform(inverse, &localPath); + // now localPath is only affected by the paint settings, and not the canvas matrix + + SkStrokeRec rec(SkStrokeRec::kFill_InitStyle); + + if (fRec.fFrameWidth > 0) { + rec.setStrokeStyle(fRec.fFrameWidth, + SkToBool(fRec.fFlags & kFrameAndFill_Flag)); + // glyphs are always closed contours, so cap type is ignored, + // so we just pass something. + rec.setStrokeParams((SkPaint::Cap)fRec.fStrokeCap, + (SkPaint::Join)fRec.fStrokeJoin, + fRec.fMiterLimit); + } + + if (fPathEffect) { + SkPath effectPath; + if (fPathEffect->filterPath(&effectPath, localPath, &rec, nullptr)) { + localPath.swap(effectPath); + } + } + + if (rec.needToApply()) { + SkPath strokePath; + if (rec.applyToPath(&strokePath, localPath)) { + localPath.swap(strokePath); + } + } + + // now return stuff to the caller + if (devPath) { + localPath.transform(matrix, devPath); + } + } else { // nothing tricky to do + if (devPath) { + devPath->swap(path); + } + } + + if (devPath) { + devPath->updateBoundsCache(); + } + return true; +} + + +void SkScalerContextRec::getMatrixFrom2x2(SkMatrix* dst) const { + dst->setAll(fPost2x2[0][0], fPost2x2[0][1], 0, + fPost2x2[1][0], fPost2x2[1][1], 0, + 0, 0, 1); +} + +void SkScalerContextRec::getLocalMatrix(SkMatrix* m) const { + *m = SkFontPriv::MakeTextMatrix(fTextSize, fPreScaleX, fPreSkewX); +} + +void SkScalerContextRec::getSingleMatrix(SkMatrix* m) const { + this->getLocalMatrix(m); + + // now concat the device matrix + SkMatrix deviceMatrix; + this->getMatrixFrom2x2(&deviceMatrix); + m->postConcat(deviceMatrix); +} + +bool SkScalerContextRec::computeMatrices(PreMatrixScale preMatrixScale, SkVector* s, SkMatrix* sA, + SkMatrix* GsA, SkMatrix* G_inv, SkMatrix* A_out) +{ + // A is the 'total' matrix. + SkMatrix A; + this->getSingleMatrix(&A); + + // The caller may find the 'total' matrix useful when dealing directly with EM sizes. + if (A_out) { + *A_out = A; + } + + // GA is the matrix A with rotation removed. + SkMatrix GA; + bool skewedOrFlipped = A.getSkewX() || A.getSkewY() || A.getScaleX() < 0 || A.getScaleY() < 0; + if (skewedOrFlipped) { + // QR by Givens rotations. G is Q^T and GA is R. G is rotational (no reflections). + // h is where A maps the horizontal baseline. + SkPoint h = SkPoint::Make(SK_Scalar1, 0); + A.mapPoints(&h, 1); + + // G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0). + SkMatrix G; + SkComputeGivensRotation(h, &G); + + GA = G; + GA.preConcat(A); + + // The 'remainingRotation' is G inverse, which is fairly simple since G is 2x2 rotational. + if (G_inv) { + G_inv->setAll( + G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX), + -G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY), + G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2)); + } + } else { + GA = A; + if (G_inv) { + G_inv->reset(); + } + } + + // If the 'total' matrix is singular, set the 'scale' to something finite and zero the matrices. + // All underlying ports have issues with zero text size, so use the matricies to zero. + // If one of the scale factors is less than 1/256 then an EM filling square will + // never affect any pixels. + // If there are any nonfinite numbers in the matrix, bail out and set the matrices to zero. + if (SkScalarAbs(GA.get(SkMatrix::kMScaleX)) <= SK_ScalarNearlyZero || + SkScalarAbs(GA.get(SkMatrix::kMScaleY)) <= SK_ScalarNearlyZero || + !GA.isFinite()) + { + s->fX = SK_Scalar1; + s->fY = SK_Scalar1; + sA->setScale(0, 0); + if (GsA) { + GsA->setScale(0, 0); + } + if (G_inv) { + G_inv->reset(); + } + return false; + } + + // At this point, given GA, create s. + switch (preMatrixScale) { + case kFull_PreMatrixScale: + s->fX = SkScalarAbs(GA.get(SkMatrix::kMScaleX)); + s->fY = SkScalarAbs(GA.get(SkMatrix::kMScaleY)); + break; + case kVertical_PreMatrixScale: { + SkScalar yScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY)); + s->fX = yScale; + s->fY = yScale; + break; + } + case kVerticalInteger_PreMatrixScale: { + SkScalar realYScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY)); + SkScalar intYScale = SkScalarRoundToScalar(realYScale); + if (intYScale == 0) { + intYScale = SK_Scalar1; + } + s->fX = intYScale; + s->fY = intYScale; + break; + } + } + + // The 'remaining' matrix sA is the total matrix A without the scale. + if (!skewedOrFlipped && ( + (kFull_PreMatrixScale == preMatrixScale) || + (kVertical_PreMatrixScale == preMatrixScale && A.getScaleX() == A.getScaleY()))) + { + // If GA == A and kFull_PreMatrixScale, sA is identity. + // If GA == A and kVertical_PreMatrixScale and A.scaleX == A.scaleY, sA is identity. + sA->reset(); + } else if (!skewedOrFlipped && kVertical_PreMatrixScale == preMatrixScale) { + // If GA == A and kVertical_PreMatrixScale, sA.scaleY is SK_Scalar1. + sA->reset(); + sA->setScaleX(A.getScaleX() / s->fY); + } else { + // TODO: like kVertical_PreMatrixScale, kVerticalInteger_PreMatrixScale with int scales. + *sA = A; + sA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY)); + } + + // The 'remainingWithoutRotation' matrix GsA is the non-rotational part of A without the scale. + if (GsA) { + *GsA = GA; + // G is rotational so reorders with the scale. + GsA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY)); + } + + return true; +} + +SkAxisAlignment SkScalerContext::computeAxisAlignmentForHText() const { + return fRec.computeAxisAlignmentForHText(); +} + +SkAxisAlignment SkScalerContextRec::computeAxisAlignmentForHText() const { + // Why fPost2x2 can be used here. + // getSingleMatrix multiplies in getLocalMatrix, which consists of + // * fTextSize (a scale, which has no effect) + // * fPreScaleX (a scale in x, which has no effect) + // * fPreSkewX (has no effect, but would on vertical text alignment). + // In other words, making the text bigger, stretching it along the + // horizontal axis, or fake italicizing it does not move the baseline. + if (!SkToBool(fFlags & SkScalerContext::kBaselineSnap_Flag)) { + return kNone_SkAxisAlignment; + } + + if (0 == fPost2x2[1][0]) { + // The x axis is mapped onto the x axis. + return kX_SkAxisAlignment; + } + if (0 == fPost2x2[0][0]) { + // The x axis is mapped onto the y axis. + return kY_SkAxisAlignment; + } + return kNone_SkAxisAlignment; +} + +void SkScalerContextRec::setLuminanceColor(SkColor c) { + fLumBits = SkMaskGamma::CanonicalColor( + SkColorSetRGB(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c))); +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkScalerContext_Empty : public SkScalerContext { +public: + SkScalerContext_Empty(sk_sp typeface, const SkScalerContextEffects& effects, + const SkDescriptor* desc) + : SkScalerContext(std::move(typeface), effects, desc) {} + +protected: + unsigned generateGlyphCount() override { + return 0; + } + bool generateAdvance(SkGlyph* glyph) override { + glyph->zeroMetrics(); + return true; + } + void generateMetrics(SkGlyph* glyph) override { + glyph->fMaskFormat = fRec.fMaskFormat; + glyph->zeroMetrics(); + } + void generateImage(const SkGlyph& glyph) override {} + bool generatePath(SkGlyphID glyph, SkPath* path) override { + path->reset(); + return false; + } + void generateFontMetrics(SkFontMetrics* metrics) override { + if (metrics) { + sk_bzero(metrics, sizeof(*metrics)); + } + } +}; + +extern SkScalerContext* SkCreateColorScalerContext(const SkDescriptor* desc); + +std::unique_ptr SkTypeface::createScalerContext( + const SkScalerContextEffects& effects, const SkDescriptor* desc, bool allowFailure) const +{ + std::unique_ptr c(this->onCreateScalerContext(effects, desc)); + if (!c && !allowFailure) { + c = skstd::make_unique(sk_ref_sp(const_cast(this)), + effects, desc); + } + + // !allowFailure implies c != nullptr + SkASSERT(c || allowFailure); + + return c; +} + +/* + * Return the scalar with only limited fractional precision. Used to consolidate matrices + * that vary only slightly when we create our key into the font cache, since the font scaler + * typically returns the same looking resuts for tiny changes in the matrix. + */ +static SkScalar sk_relax(SkScalar x) { + SkScalar n = SkScalarRoundToScalar(x * 1024); + return n / 1024.0f; +} + +static SkMask::Format compute_mask_format(const SkFont& font) { + switch (font.getEdging()) { + case SkFont::Edging::kAlias: + return SkMask::kBW_Format; + case SkFont::Edging::kAntiAlias: + return SkMask::kA8_Format; + case SkFont::Edging::kSubpixelAntiAlias: + return SkMask::kLCD16_Format; + } + SkASSERT(false); + return SkMask::kA8_Format; +} + +// Beyond this size, LCD doesn't appreciably improve quality, but it always +// cost more RAM and draws slower, so we set a cap. +#ifndef SK_MAX_SIZE_FOR_LCDTEXT + #define SK_MAX_SIZE_FOR_LCDTEXT 48 +#endif + +const SkScalar gMaxSize2ForLCDText = SK_MAX_SIZE_FOR_LCDTEXT * SK_MAX_SIZE_FOR_LCDTEXT; + +static bool too_big_for_lcd(const SkScalerContextRec& rec, bool checkPost2x2) { + if (checkPost2x2) { + SkScalar area = rec.fPost2x2[0][0] * rec.fPost2x2[1][1] - + rec.fPost2x2[1][0] * rec.fPost2x2[0][1]; + area *= rec.fTextSize * rec.fTextSize; + return area > gMaxSize2ForLCDText; + } else { + return rec.fTextSize > SK_MAX_SIZE_FOR_LCDTEXT; + } +} + +// The only reason this is not file static is because it needs the context of SkScalerContext to +// access SkPaint::computeLuminanceColor. +void SkScalerContext::MakeRecAndEffects(const SkFont& font, const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix, + SkScalerContextRec* rec, + SkScalerContextEffects* effects) { + SkASSERT(!deviceMatrix.hasPerspective()); + + sk_bzero(rec, sizeof(SkScalerContextRec)); + + SkTypeface* typeface = font.getTypefaceOrDefault(); + + rec->fFontID = typeface->uniqueID(); + rec->fTextSize = font.getSize(); + rec->fPreScaleX = font.getScaleX(); + rec->fPreSkewX = font.getSkewX(); + + bool checkPost2x2 = false; + + const SkMatrix::TypeMask mask = deviceMatrix.getType(); + if (mask & SkMatrix::kScale_Mask) { + rec->fPost2x2[0][0] = sk_relax(deviceMatrix.getScaleX()); + rec->fPost2x2[1][1] = sk_relax(deviceMatrix.getScaleY()); + checkPost2x2 = true; + } else { + rec->fPost2x2[0][0] = rec->fPost2x2[1][1] = SK_Scalar1; + } + if (mask & SkMatrix::kAffine_Mask) { + rec->fPost2x2[0][1] = sk_relax(deviceMatrix.getSkewX()); + rec->fPost2x2[1][0] = sk_relax(deviceMatrix.getSkewY()); + checkPost2x2 = true; + } else { + rec->fPost2x2[0][1] = rec->fPost2x2[1][0] = 0; + } + + SkPaint::Style style = paint.getStyle(); + SkScalar strokeWidth = paint.getStrokeWidth(); + + unsigned flags = 0; + + if (font.isEmbolden()) { +#ifdef SK_USE_FREETYPE_EMBOLDEN + flags |= SkScalerContext::kEmbolden_Flag; +#else + SkScalar fakeBoldScale = SkScalarInterpFunc(font.getSize(), + kStdFakeBoldInterpKeys, + kStdFakeBoldInterpValues, + kStdFakeBoldInterpLength); + SkScalar extra = font.getSize() * fakeBoldScale; + + if (style == SkPaint::kFill_Style) { + style = SkPaint::kStrokeAndFill_Style; + strokeWidth = extra; // ignore paint's strokeWidth if it was "fill" + } else { + strokeWidth += extra; + } +#endif + } + + if (style != SkPaint::kFill_Style && strokeWidth > 0) { + rec->fFrameWidth = strokeWidth; + rec->fMiterLimit = paint.getStrokeMiter(); + rec->fStrokeJoin = SkToU8(paint.getStrokeJoin()); + rec->fStrokeCap = SkToU8(paint.getStrokeCap()); + + if (style == SkPaint::kStrokeAndFill_Style) { + flags |= SkScalerContext::kFrameAndFill_Flag; + } + } else { + rec->fFrameWidth = 0; + rec->fMiterLimit = 0; + rec->fStrokeJoin = 0; + rec->fStrokeCap = 0; + } + + rec->fMaskFormat = SkToU8(compute_mask_format(font)); + + if (SkMask::kLCD16_Format == rec->fMaskFormat) { + if (too_big_for_lcd(*rec, checkPost2x2)) { + rec->fMaskFormat = SkMask::kA8_Format; + flags |= SkScalerContext::kGenA8FromLCD_Flag; + } else { + SkPixelGeometry geometry = surfaceProps.pixelGeometry(); + + switch (geometry) { + case kUnknown_SkPixelGeometry: + // eeek, can't support LCD + rec->fMaskFormat = SkMask::kA8_Format; + flags |= SkScalerContext::kGenA8FromLCD_Flag; + break; + case kRGB_H_SkPixelGeometry: + // our default, do nothing. + break; + case kBGR_H_SkPixelGeometry: + flags |= SkScalerContext::kLCD_BGROrder_Flag; + break; + case kRGB_V_SkPixelGeometry: + flags |= SkScalerContext::kLCD_Vertical_Flag; + break; + case kBGR_V_SkPixelGeometry: + flags |= SkScalerContext::kLCD_Vertical_Flag; + flags |= SkScalerContext::kLCD_BGROrder_Flag; + break; + } + } + } + + if (font.isEmbeddedBitmaps()) { + flags |= SkScalerContext::kEmbeddedBitmapText_Flag; + } + if (font.isSubpixel()) { + flags |= SkScalerContext::kSubpixelPositioning_Flag; + } + if (font.isForceAutoHinting()) { + flags |= SkScalerContext::kForceAutohinting_Flag; + } + if (font.isLinearMetrics()) { + flags |= SkScalerContext::kLinearMetrics_Flag; + } + if (font.isBaselineSnap()) { + flags |= SkScalerContext::kBaselineSnap_Flag; + } + rec->fFlags = SkToU16(flags); + + // these modify fFlags, so do them after assigning fFlags + rec->setHinting(font.getHinting()); + rec->setLuminanceColor(SkPaintPriv::ComputeLuminanceColor(paint)); + + // For now always set the paint gamma equal to the device gamma. + // The math in SkMaskGamma can handle them being different, + // but it requires superluminous masks when + // Ex : deviceGamma(x) < paintGamma(x) and x is sufficiently large. + rec->setDeviceGamma(SK_GAMMA_EXPONENT); + rec->setPaintGamma(SK_GAMMA_EXPONENT); + +#ifdef SK_GAMMA_CONTRAST + rec->setContrast(SK_GAMMA_CONTRAST); +#else + // A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise. + // With lower values small text appears washed out (though correctly so). + // With higher values lcd fringing is worse and the smoothing effect of + // partial coverage is diminished. + rec->setContrast(0.5f); +#endif + + if (!SkToBool(scalerContextFlags & SkScalerContextFlags::kFakeGamma)) { + rec->ignoreGamma(); + } + if (!SkToBool(scalerContextFlags & SkScalerContextFlags::kBoostContrast)) { + rec->setContrast(0); + } + + new (effects) SkScalerContextEffects{paint}; +} + +SkDescriptor* SkScalerContext::MakeDescriptorForPaths(SkFontID typefaceID, + SkAutoDescriptor* ad) { + SkScalerContextRec rec; + memset(&rec, 0, sizeof(rec)); + rec.fFontID = typefaceID; + rec.fTextSize = SkFontPriv::kCanonicalTextSizeForPaths; + rec.fPreScaleX = rec.fPost2x2[0][0] = rec.fPost2x2[1][1] = SK_Scalar1; + return AutoDescriptorGivenRecAndEffects(rec, SkScalerContextEffects(), ad); +} + +SkDescriptor* SkScalerContext::CreateDescriptorAndEffectsUsingPaint( + const SkFont& font, const SkPaint& paint, const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, const SkMatrix& deviceMatrix, SkAutoDescriptor* ad, + SkScalerContextEffects* effects) +{ + SkScalerContextRec rec; + MakeRecAndEffects(font, paint, surfaceProps, scalerContextFlags, deviceMatrix, &rec, effects); + return AutoDescriptorGivenRecAndEffects(rec, *effects, ad); +} + +static size_t calculate_size_and_flatten(const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkBinaryWriteBuffer* effectBuffer) { + size_t descSize = sizeof(rec); + int entryCount = 1; + + if (effects.fPathEffect || effects.fMaskFilter) { + if (effects.fPathEffect) { effectBuffer->writeFlattenable(effects.fPathEffect); } + if (effects.fMaskFilter) { effectBuffer->writeFlattenable(effects.fMaskFilter); } + entryCount += 1; + descSize += effectBuffer->bytesWritten(); + } + + descSize += SkDescriptor::ComputeOverhead(entryCount); + return descSize; +} + +static void generate_descriptor(const SkScalerContextRec& rec, + const SkBinaryWriteBuffer& effectBuffer, + SkDescriptor* desc) { + desc->init(); + desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec); + + if (effectBuffer.bytesWritten() > 0) { + effectBuffer.writeToMemory(desc->addEntry(kEffects_SkDescriptorTag, + effectBuffer.bytesWritten(), + nullptr)); + } + + desc->computeChecksum(); +} + +SkDescriptor* SkScalerContext::AutoDescriptorGivenRecAndEffects( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkAutoDescriptor* ad) +{ + SkBinaryWriteBuffer buf; + + ad->reset(calculate_size_and_flatten(rec, effects, &buf)); + generate_descriptor(rec, buf, ad->getDesc()); + + return ad->getDesc(); +} + +std::unique_ptr SkScalerContext::DescriptorGivenRecAndEffects( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects) +{ + SkBinaryWriteBuffer buf; + + auto desc = SkDescriptor::Alloc(calculate_size_and_flatten(rec, effects, &buf)); + generate_descriptor(rec, buf, desc.get()); + + return desc; +} + +void SkScalerContext::DescriptorBufferGiveRec(const SkScalerContextRec& rec, void* buffer) { + generate_descriptor(rec, SkBinaryWriteBuffer{}, (SkDescriptor*)buffer); +} + +bool SkScalerContext::CheckBufferSizeForRec(const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + size_t size) { + SkBinaryWriteBuffer buf; + return size >= calculate_size_and_flatten(rec, effects, &buf); +} + + + + diff --git a/gfx/skia/skia/src/core/SkScalerContext.h b/gfx/skia/skia/src/core/SkScalerContext.h new file mode 100644 index 0000000000..72b45de166 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScalerContext.h @@ -0,0 +1,447 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkScalerContext_DEFINED +#define SkScalerContext_DEFINED + +#include + +#include "include/core/SkFont.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkMacros.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkMask.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkStrikeForGPU.h" +#include "src/core/SkSurfacePriv.h" +#include "src/core/SkWriteBuffer.h" + +class SkAutoDescriptor; +class SkDescriptor; +class SkMaskFilter; +class SkPathEffect; +class SkScalerContext; +class SkScalerContext_DW; + +enum SkScalerContextFlags : uint32_t { + kNone = 0, + kFakeGamma = 1 << 0, + kBoostContrast = 1 << 1, + kFakeGammaAndBoostContrast = kFakeGamma | kBoostContrast, +}; + +enum SkAxisAlignment : uint32_t { + kNone_SkAxisAlignment, + kX_SkAxisAlignment, + kY_SkAxisAlignment +}; + +/* + * To allow this to be forward-declared, it must be its own typename, rather + * than a nested struct inside SkScalerContext (where it started). + * + * SkScalerContextRec must be dense, and all bytes must be set to a know quantity because this + * structure is used to calculate a checksum. + */ +SK_BEGIN_REQUIRE_DENSE +struct SkScalerContextRec { + uint32_t fFontID; + SkScalar fTextSize, fPreScaleX, fPreSkewX; + SkScalar fPost2x2[2][2]; + SkScalar fFrameWidth, fMiterLimit; + +private: + //These describe the parameters to create (uniquely identify) the pre-blend. + uint32_t fLumBits; + uint8_t fDeviceGamma; //2.6, (0.0, 4.0) gamma, 0.0 for sRGB + uint8_t fPaintGamma; //2.6, (0.0, 4.0) gamma, 0.0 for sRGB + uint8_t fContrast; //0.8+1, [0.0, 1.0] artificial contrast + const uint8_t fReservedAlign{0}; + +public: + + SkScalar getDeviceGamma() const { + return SkIntToScalar(fDeviceGamma) / (1 << 6); + } + void setDeviceGamma(SkScalar dg) { + SkASSERT(0 <= dg && dg < SkIntToScalar(4)); + fDeviceGamma = SkScalarFloorToInt(dg * (1 << 6)); + } + + SkScalar getPaintGamma() const { + return SkIntToScalar(fPaintGamma) / (1 << 6); + } + void setPaintGamma(SkScalar pg) { + SkASSERT(0 <= pg && pg < SkIntToScalar(4)); + fPaintGamma = SkScalarFloorToInt(pg * (1 << 6)); + } + + SkScalar getContrast() const { + sk_ignore_unused_variable(fReservedAlign); + return SkIntToScalar(fContrast) / ((1 << 8) - 1); + } + void setContrast(SkScalar c) { + SkASSERT(0 <= c && c <= SK_Scalar1); + fContrast = SkScalarRoundToInt(c * ((1 << 8) - 1)); + } + + /** + * Causes the luminance color to be ignored, and the paint and device + * gamma to be effectively 1.0 + */ + void ignoreGamma() { + setLuminanceColor(SK_ColorTRANSPARENT); + setPaintGamma(SK_Scalar1); + setDeviceGamma(SK_Scalar1); + } + + /** + * Causes the luminance color and contrast to be ignored, and the + * paint and device gamma to be effectively 1.0. + */ + void ignorePreBlend() { + ignoreGamma(); + setContrast(0); + } + + uint8_t fMaskFormat; +private: + uint8_t fStrokeJoin : 4; + uint8_t fStrokeCap : 4; + +public: + uint16_t fFlags; + + // Warning: when adding members note that the size of this structure + // must be a multiple of 4. SkDescriptor requires that its arguments be + // multiples of four and this structure is put in an SkDescriptor in + // SkPaint::MakeRecAndEffects. + + SkString dump() const { + SkString msg; + msg.appendf("Rec\n"); + msg.appendf(" textsize %g prescale %g preskew %g post [%g %g %g %g]\n", + fTextSize, fPreScaleX, fPreSkewX, fPost2x2[0][0], + fPost2x2[0][1], fPost2x2[1][0], fPost2x2[1][1]); + msg.appendf(" frame %g miter %g format %d join %d cap %d flags %#hx\n", + fFrameWidth, fMiterLimit, fMaskFormat, fStrokeJoin, fStrokeCap, fFlags); + msg.appendf(" lum bits %x, device gamma %d, paint gamma %d contrast %d\n", fLumBits, + fDeviceGamma, fPaintGamma, fContrast); + return msg; + } + + void getMatrixFrom2x2(SkMatrix*) const; + void getLocalMatrix(SkMatrix*) const; + void getSingleMatrix(SkMatrix*) const; + + /** The kind of scale which will be applied by the underlying port (pre-matrix). */ + enum PreMatrixScale { + kFull_PreMatrixScale, // The underlying port can apply both x and y scale. + kVertical_PreMatrixScale, // The underlying port can only apply a y scale. + kVerticalInteger_PreMatrixScale // The underlying port can only apply an integer y scale. + }; + /** + * Compute useful matrices for use with sizing in underlying libraries. + * + * There are two kinds of text size, a 'requested/logical size' which is like asking for size + * '12' and a 'real' size which is the size after the matrix is applied. The matrices produced + * by this method are based on the 'real' size. This method effectively finds the total device + * matrix and decomposes it in various ways. + * + * The most useful decomposition is into 'scale' and 'remaining'. The 'scale' is applied first + * and then the 'remaining' to fully apply the total matrix. This decomposition is useful when + * the text size ('scale') may have meaning apart from the total matrix. This is true when + * hinting, and sometimes true for other properties as well. + * + * The second (optional) decomposition is of 'remaining' into a non-rotational part + * 'remainingWithoutRotation' and a rotational part 'remainingRotation'. The 'scale' is applied + * first, then 'remainingWithoutRotation', then 'remainingRotation' to fully apply the total + * matrix. This decomposition is helpful when only horizontal metrics can be trusted, so the + * 'scale' and 'remainingWithoutRotation' will be handled by the underlying library, but + * the final rotation 'remainingRotation' will be handled manually. + * + * The 'total' matrix is also (optionally) available. This is useful in cases where the + * underlying library will not be used, often when working directly with font data. + * + * The parameters 'scale' and 'remaining' are required, the other pointers may be nullptr. + * + * @param preMatrixScale the kind of scale to extract from the total matrix. + * @param scale the scale extracted from the total matrix (both values positive). + * @param remaining apply after scale to apply the total matrix. + * @param remainingWithoutRotation apply after scale to apply the total matrix sans rotation. + * @param remainingRotation apply after remainingWithoutRotation to apply the total matrix. + * @param total the total matrix. + * @return false if the matrix was singular. The output will be valid but not invertible. + */ + bool computeMatrices(PreMatrixScale preMatrixScale, + SkVector* scale, SkMatrix* remaining, + SkMatrix* remainingWithoutRotation = nullptr, + SkMatrix* remainingRotation = nullptr, + SkMatrix* total = nullptr); + + SkAxisAlignment computeAxisAlignmentForHText() const; + + inline SkFontHinting getHinting() const; + inline void setHinting(SkFontHinting); + + SkMask::Format getFormat() const { + return static_cast(fMaskFormat); + } + + SkColor getLuminanceColor() const { + return fLumBits; + } + + // setLuminanceColor forces the alpha to be 0xFF because the blitter that draws the glyph + // will apply the alpha from the paint. Don't apply the alpha twice. + void setLuminanceColor(SkColor c); + +private: + // TODO: remove + friend class SkScalerContext; +}; +SK_END_REQUIRE_DENSE + +// TODO: rename SkScalerContextEffects -> SkStrikeEffects +struct SkScalerContextEffects { + SkScalerContextEffects() : fPathEffect(nullptr), fMaskFilter(nullptr) {} + SkScalerContextEffects(SkPathEffect* pe, SkMaskFilter* mf) + : fPathEffect(pe), fMaskFilter(mf) {} + explicit SkScalerContextEffects(const SkPaint& paint) + : fPathEffect(paint.getPathEffect()) + , fMaskFilter(paint.getMaskFilter()) {} + + SkPathEffect* fPathEffect; + SkMaskFilter* fMaskFilter; +}; + +//The following typedef hides from the rest of the implementation the number of +//most significant bits to consider when creating mask gamma tables. Two bits +//per channel was chosen as a balance between fidelity (more bits) and cache +//sizes (fewer bits). Three bits per channel was chosen when #303942; (used by +//the Chrome UI) turned out too green. +typedef SkTMaskGamma<3, 3, 3> SkMaskGamma; + +class SkScalerContext { +public: + enum Flags { + kFrameAndFill_Flag = 0x0001, + kUnused = 0x0002, + kEmbeddedBitmapText_Flag = 0x0004, + kEmbolden_Flag = 0x0008, + kSubpixelPositioning_Flag = 0x0010, + kForceAutohinting_Flag = 0x0020, // Use auto instead of bytcode hinting if hinting. + + // together, these two flags resulting in a two bit value which matches + // up with the SkPaint::Hinting enum. + kHinting_Shift = 7, // to shift into the other flags above + kHintingBit1_Flag = 0x0080, + kHintingBit2_Flag = 0x0100, + + // Pixel geometry information. + // only meaningful if fMaskFormat is kLCD16 + kLCD_Vertical_Flag = 0x0200, // else Horizontal + kLCD_BGROrder_Flag = 0x0400, // else RGB order + + // Generate A8 from LCD source (for GDI and CoreGraphics). + // only meaningful if fMaskFormat is kA8 + kGenA8FromLCD_Flag = 0x0800, // could be 0x200 (bit meaning dependent on fMaskFormat) + kLinearMetrics_Flag = 0x1000, + kBaselineSnap_Flag = 0x2000, + + kLightOnDark_Flag = 0x8000, // Moz + Mac only, used to distinguish different mask dilations + }; + + // computed values + enum { + kHinting_Mask = kHintingBit1_Flag | kHintingBit2_Flag, + }; + + SkScalerContext(sk_sp, const SkScalerContextEffects&, const SkDescriptor*); + virtual ~SkScalerContext(); + + SkTypeface* getTypeface() const { return fTypeface.get(); } + + SkMask::Format getMaskFormat() const { + return (SkMask::Format)fRec.fMaskFormat; + } + + bool isSubpixel() const { + return SkToBool(fRec.fFlags & kSubpixelPositioning_Flag); + } + + bool isLinearMetrics() const { + return SkToBool(fRec.fFlags & kLinearMetrics_Flag); + } + + // DEPRECATED + bool isVertical() const { return false; } + + unsigned getGlyphCount() { return this->generateGlyphCount(); } + void getAdvance(SkGlyph*); + void getMetrics(SkGlyph*); + void getImage(const SkGlyph&); + bool SK_WARN_UNUSED_RESULT getPath(SkPackedGlyphID, SkPath*); + void getFontMetrics(SkFontMetrics*); + + /** Return the size in bytes of the associated gamma lookup table + */ + static size_t GetGammaLUTSize(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma, + int* width, int* height); + + /** Get the associated gamma lookup table. The 'data' pointer must point to pre-allocated + * memory, with size in bytes greater than or equal to the return value of getGammaLUTSize(). + * + * If the lookup table hasn't been initialized (e.g., it's linear), this will return false. + */ + static bool GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma, + uint8_t* data); + + static void MakeRecAndEffects(const SkFont& font, const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix, + SkScalerContextRec* rec, + SkScalerContextEffects* effects); + + // If we are creating rec and effects from a font only, then there is no device around either. + static void MakeRecAndEffectsFromFont(const SkFont& font, + SkScalerContextRec* rec, + SkScalerContextEffects* effects) { + SkPaint paint; + return MakeRecAndEffects( + font, paint, SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType), + SkScalerContextFlags::kNone, SkMatrix::I(), rec, effects); + } + + static SkDescriptor* MakeDescriptorForPaths(SkFontID fontID, + SkAutoDescriptor* ad); + + static SkDescriptor* AutoDescriptorGivenRecAndEffects( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkAutoDescriptor* ad); + + static std::unique_ptr DescriptorGivenRecAndEffects( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects); + + static void DescriptorBufferGiveRec(const SkScalerContextRec& rec, void* buffer); + static bool CheckBufferSizeForRec(const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + size_t size); + + static SkMaskGamma::PreBlend GetMaskPreBlend(const SkScalerContextRec& rec); + + const SkScalerContextRec& getRec() const { return fRec; } + + SkScalerContextEffects getEffects() const { + return { fPathEffect.get(), fMaskFilter.get() }; + } + + /** + * Return the axis (if any) that the baseline for horizontal text should land on. + * As an example, the identity matrix will return kX_SkAxisAlignment + */ + SkAxisAlignment computeAxisAlignmentForHText() const; + + static SkDescriptor* CreateDescriptorAndEffectsUsingPaint( + const SkFont&, const SkPaint&, const SkSurfaceProps&, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix, SkAutoDescriptor* ad, + SkScalerContextEffects* effects); + +protected: + SkScalerContextRec fRec; + + /** Generates the contents of glyph.fAdvanceX and glyph.fAdvanceY if it can do so quickly. + * Returns true if it could, false otherwise. + */ + virtual bool generateAdvance(SkGlyph* glyph) = 0; + + /** Generates the contents of glyph.fWidth, fHeight, fTop, fLeft, + * as well as fAdvanceX and fAdvanceY if not already set. + * + * TODO: fMaskFormat is set by getMetrics later; cannot be set here. + */ + virtual void generateMetrics(SkGlyph* glyph) = 0; + + /** Generates the contents of glyph.fImage. + * When called, glyph.fImage will be pointing to a pre-allocated, + * uninitialized region of memory of size glyph.imageSize(). + * This method may change glyph.fMaskFormat if the new image size is + * less than or equal to the old image size. + * + * Because glyph.imageSize() will determine the size of fImage, + * generateMetrics will be called before generateImage. + */ + virtual void generateImage(const SkGlyph& glyph) = 0; + + /** Sets the passed path to the glyph outline. + * If this cannot be done the path is set to empty; + * @return false if this glyph does not have any path. + */ + virtual bool SK_WARN_UNUSED_RESULT generatePath(SkGlyphID glyphId, SkPath* path) = 0; + + /** Retrieves font metrics. */ + virtual void generateFontMetrics(SkFontMetrics*) = 0; + + /** Returns the number of glyphs in the font. */ + virtual unsigned generateGlyphCount() = 0; + + void forceGenerateImageFromPath() { fGenerateImageFromPath = true; } + void forceOffGenerateImageFromPath() { fGenerateImageFromPath = false; } + +private: + friend class RandomScalerContext; // For debug purposes + + static SkScalerContextRec PreprocessRec(const SkTypeface& typeface, + const SkScalerContextEffects& effects, + const SkDescriptor& desc); + + // never null + sk_sp fTypeface; + + // optional objects, which may be null + sk_sp fPathEffect; + sk_sp fMaskFilter; + + // if this is set, we draw the image from a path, rather than + // calling generateImage. + bool fGenerateImageFromPath; + + /** Returns false if the glyph has no path at all. */ + bool internalGetPath(SkPackedGlyphID id, SkPath* devPath); + + // SkMaskGamma::PreBlend converts linear masks to gamma correcting masks. +protected: + // Visible to subclasses so that generateImage can apply the pre-blend directly. + const SkMaskGamma::PreBlend fPreBlend; +}; + +#define kRec_SkDescriptorTag SkSetFourByteTag('s', 'r', 'e', 'c') +#define kEffects_SkDescriptorTag SkSetFourByteTag('e', 'f', 'c', 't') + +/////////////////////////////////////////////////////////////////////////////// + +SkFontHinting SkScalerContextRec::getHinting() const { + unsigned hint = (fFlags & SkScalerContext::kHinting_Mask) >> + SkScalerContext::kHinting_Shift; + return static_cast(hint); +} + +void SkScalerContextRec::setHinting(SkFontHinting hinting) { + fFlags = (fFlags & ~SkScalerContext::kHinting_Mask) | + (static_cast(hinting) << SkScalerContext::kHinting_Shift); +} + + +#endif diff --git a/gfx/skia/skia/src/core/SkScan.cpp b/gfx/skia/skia/src/core/SkScan.cpp new file mode 100644 index 0000000000..b93f5f4f07 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/core/SkBlitter.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkScan.h" + +std::atomic gSkUseAnalyticAA{true}; +std::atomic gSkForceAnalyticAA{false}; + +static inline void blitrect(SkBlitter* blitter, const SkIRect& r) { + blitter->blitRect(r.fLeft, r.fTop, r.width(), r.height()); +} + +void SkScan::FillIRect(const SkIRect& r, const SkRegion* clip, + SkBlitter* blitter) { + if (!r.isEmpty()) { + if (clip) { + if (clip->isRect()) { + const SkIRect& clipBounds = clip->getBounds(); + + if (clipBounds.contains(r)) { + blitrect(blitter, r); + } else { + SkIRect rr = r; + if (rr.intersect(clipBounds)) { + blitrect(blitter, rr); + } + } + } else { + SkRegion::Cliperator cliper(*clip, r); + const SkIRect& rr = cliper.rect(); + + while (!cliper.done()) { + blitrect(blitter, rr); + cliper.next(); + } + } + } else { + blitrect(blitter, r); + } + } +} + +void SkScan::FillXRect(const SkXRect& xr, const SkRegion* clip, + SkBlitter* blitter) { + SkIRect r; + + XRect_round(xr, &r); + SkScan::FillIRect(r, clip, blitter); +} + +void SkScan::FillRect(const SkRect& r, const SkRegion* clip, + SkBlitter* blitter) { + SkIRect ir; + + r.round(&ir); + SkScan::FillIRect(ir, clip, blitter); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkScan::FillIRect(const SkIRect& r, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isEmpty() || r.isEmpty()) { + return; + } + + if (clip.isBW()) { + FillIRect(r, &clip.bwRgn(), blitter); + return; + } + + SkAAClipBlitterWrapper wrapper(clip, blitter); + FillIRect(r, &wrapper.getRgn(), wrapper.getBlitter()); +} + +void SkScan::FillXRect(const SkXRect& xr, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isEmpty() || xr.isEmpty()) { + return; + } + + if (clip.isBW()) { + FillXRect(xr, &clip.bwRgn(), blitter); + return; + } + + SkAAClipBlitterWrapper wrapper(clip, blitter); + FillXRect(xr, &wrapper.getRgn(), wrapper.getBlitter()); +} + +void SkScan::FillRect(const SkRect& r, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isEmpty() || r.isEmpty()) { + return; + } + + if (clip.isBW()) { + FillRect(r, &clip.bwRgn(), blitter); + return; + } + + SkAAClipBlitterWrapper wrapper(clip, blitter); + FillRect(r, &wrapper.getRgn(), wrapper.getBlitter()); +} diff --git a/gfx/skia/skia/src/core/SkScan.h b/gfx/skia/skia/src/core/SkScan.h new file mode 100644 index 0000000000..a1db6e5e6d --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan.h @@ -0,0 +1,136 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkScan_DEFINED +#define SkScan_DEFINED + +#include "include/core/SkRect.h" +#include "include/private/SkFixed.h" +#include + +class SkRasterClip; +class SkRegion; +class SkBlitter; +class SkPath; + +/** Defines a fixed-point rectangle, identical to the integer SkIRect, but its + coordinates are treated as SkFixed rather than int32_t. +*/ +typedef SkIRect SkXRect; + +extern std::atomic gSkUseAnalyticAA; +extern std::atomic gSkForceAnalyticAA; + +class AdditiveBlitter; + +class SkScan { +public: + /* + * Draws count-1 line segments, one at a time: + * line(pts[0], pts[1]) + * line(pts[1], pts[2]) + * line(......, pts[count - 1]) + */ + typedef void (*HairRgnProc)(const SkPoint[], int count, const SkRegion*, SkBlitter*); + typedef void (*HairRCProc)(const SkPoint[], int count, const SkRasterClip&, SkBlitter*); + + static void FillPath(const SkPath&, const SkIRect&, SkBlitter*); + + /////////////////////////////////////////////////////////////////////////// + // rasterclip + + static void FillIRect(const SkIRect&, const SkRasterClip&, SkBlitter*); + static void FillXRect(const SkXRect&, const SkRasterClip&, SkBlitter*); + static void FillRect(const SkRect&, const SkRasterClip&, SkBlitter*); + static void AntiFillRect(const SkRect&, const SkRasterClip&, SkBlitter*); + static void AntiFillXRect(const SkXRect&, const SkRasterClip&, SkBlitter*); + static void FillPath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void AntiFillPath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void FrameRect(const SkRect&, const SkPoint& strokeSize, + const SkRasterClip&, SkBlitter*); + static void AntiFrameRect(const SkRect&, const SkPoint& strokeSize, + const SkRasterClip&, SkBlitter*); + static void FillTriangle(const SkPoint pts[], const SkRasterClip&, SkBlitter*); + static void HairLine(const SkPoint[], int count, const SkRasterClip&, SkBlitter*); + static void AntiHairLine(const SkPoint[], int count, const SkRasterClip&, SkBlitter*); + static void HairRect(const SkRect&, const SkRasterClip&, SkBlitter*); + static void AntiHairRect(const SkRect&, const SkRasterClip&, SkBlitter*); + static void HairPath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void AntiHairPath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void HairSquarePath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void AntiHairSquarePath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void HairRoundPath(const SkPath&, const SkRasterClip&, SkBlitter*); + static void AntiHairRoundPath(const SkPath&, const SkRasterClip&, SkBlitter*); + + // Needed by do_fill_path in SkScanPriv.h + static void FillPath(const SkPath&, const SkRegion& clip, SkBlitter*); + +private: + friend class SkAAClip; + friend class SkRegion; + + static void FillIRect(const SkIRect&, const SkRegion* clip, SkBlitter*); + static void FillXRect(const SkXRect&, const SkRegion* clip, SkBlitter*); + static void FillRect(const SkRect&, const SkRegion* clip, SkBlitter*); + static void AntiFillRect(const SkRect&, const SkRegion* clip, SkBlitter*); + static void AntiFillXRect(const SkXRect&, const SkRegion*, SkBlitter*); + static void AntiFillPath(const SkPath&, const SkRegion& clip, SkBlitter*, bool forceRLE); + static void FillTriangle(const SkPoint pts[], const SkRegion*, SkBlitter*); + + static void AntiFrameRect(const SkRect&, const SkPoint& strokeSize, + const SkRegion*, SkBlitter*); + static void HairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*); + static void AntiHairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*); + static void AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR, + const SkIRect& clipBounds, bool forceRLE); + static void SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR, + const SkIRect& clipBounds, bool forceRLE); +}; + +/** Assign an SkXRect from a SkIRect, by promoting the src rect's coordinates + from int to SkFixed. Does not check for overflow if the src coordinates + exceed 32K +*/ +static inline void XRect_set(SkXRect* xr, const SkIRect& src) { + xr->fLeft = SkIntToFixed(src.fLeft); + xr->fTop = SkIntToFixed(src.fTop); + xr->fRight = SkIntToFixed(src.fRight); + xr->fBottom = SkIntToFixed(src.fBottom); +} + +/** Assign an SkXRect from a SkRect, by promoting the src rect's coordinates + from SkScalar to SkFixed. Does not check for overflow if the src coordinates + exceed 32K +*/ +static inline void XRect_set(SkXRect* xr, const SkRect& src) { + xr->fLeft = SkScalarToFixed(src.fLeft); + xr->fTop = SkScalarToFixed(src.fTop); + xr->fRight = SkScalarToFixed(src.fRight); + xr->fBottom = SkScalarToFixed(src.fBottom); +} + +/** Round the SkXRect coordinates, and store the result in the SkIRect. +*/ +static inline void XRect_round(const SkXRect& xr, SkIRect* dst) { + dst->fLeft = SkFixedRoundToInt(xr.fLeft); + dst->fTop = SkFixedRoundToInt(xr.fTop); + dst->fRight = SkFixedRoundToInt(xr.fRight); + dst->fBottom = SkFixedRoundToInt(xr.fBottom); +} + +/** Round the SkXRect coordinates out (i.e. use floor for left/top, and ceiling + for right/bottom), and store the result in the SkIRect. +*/ +static inline void XRect_roundOut(const SkXRect& xr, SkIRect* dst) { + dst->fLeft = SkFixedFloorToInt(xr.fLeft); + dst->fTop = SkFixedFloorToInt(xr.fTop); + dst->fRight = SkFixedCeilToInt(xr.fRight); + dst->fBottom = SkFixedCeilToInt(xr.fBottom); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkScanPriv.h b/gfx/skia/skia/src/core/SkScanPriv.h new file mode 100644 index 0000000000..2bec85f7b8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScanPriv.h @@ -0,0 +1,100 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkScanPriv_DEFINED +#define SkScanPriv_DEFINED + +#include "include/core/SkPath.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkScan.h" + +// controls how much we super-sample (when we use that scan convertion) +#define SK_SUPERSAMPLE_SHIFT 2 + +class SkScanClipper { +public: + SkScanClipper(SkBlitter* blitter, const SkRegion* clip, const SkIRect& bounds, + bool skipRejectTest = false, bool boundsPreClipped = false); + + SkBlitter* getBlitter() const { return fBlitter; } + const SkIRect* getClipRect() const { return fClipRect; } + +private: + SkRectClipBlitter fRectBlitter; + SkRgnClipBlitter fRgnBlitter; +#ifdef SK_DEBUG + SkRectClipCheckBlitter fRectClipCheckBlitter; +#endif + SkBlitter* fBlitter; + const SkIRect* fClipRect; +}; + +void sk_fill_path(const SkPath& path, const SkIRect& clipRect, + SkBlitter* blitter, int start_y, int stop_y, int shiftEdgesUp, + bool pathContainedInClip); + +// blit the rects above and below avoid, clipped to clip +void sk_blit_above(SkBlitter*, const SkIRect& avoid, const SkRegion& clip); +void sk_blit_below(SkBlitter*, const SkIRect& avoid, const SkRegion& clip); + +template +static inline void remove_edge(EdgeType* edge) { + edge->fPrev->fNext = edge->fNext; + edge->fNext->fPrev = edge->fPrev; +} + +template +static inline void insert_edge_after(EdgeType* edge, EdgeType* afterMe) { + edge->fPrev = afterMe; + edge->fNext = afterMe->fNext; + afterMe->fNext->fPrev = edge; + afterMe->fNext = edge; +} + +template +static void backward_insert_edge_based_on_x(EdgeType* edge) { + SkFixed x = edge->fX; + EdgeType* prev = edge->fPrev; + while (prev->fPrev && prev->fX > x) { + prev = prev->fPrev; + } + if (prev->fNext != edge) { + remove_edge(edge); + insert_edge_after(edge, prev); + } +} + +// Start from the right side, searching backwards for the point to begin the new edge list +// insertion, marching forwards from here. The implementation could have started from the left +// of the prior insertion, and search to the right, or with some additional caching, binary +// search the starting point. More work could be done to determine optimal new edge insertion. +template +static EdgeType* backward_insert_start(EdgeType* prev, SkFixed x) { + while (prev->fPrev && prev->fX > x) { + prev = prev->fPrev; + } + return prev; +} + +// Check if the path is a rect and fat enough after clipping; if so, blit it. +static inline bool TryBlitFatAntiRect(SkBlitter* blitter, const SkPath& path, const SkIRect& clip) { + SkRect rect; + if (!path.isRect(&rect)) { + return false; // not rect + } + if (!rect.intersect(SkRect::Make(clip))) { + return true; // The intersection is empty. Hence consider it done. + } + SkIRect bounds = rect.roundOut(); + if (bounds.width() < 3) { + return false; // not fat + } + blitter->blitFatAntiRect(rect); + return true; +} + +#endif diff --git a/gfx/skia/skia/src/core/SkScan_AAAPath.cpp b/gfx/skia/skia/src/core/SkScan_AAAPath.cpp new file mode 100644 index 0000000000..a9c0af4173 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan_AAAPath.cpp @@ -0,0 +1,2011 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "include/core/SkRegion.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkAnalyticEdge.h" +#include "src/core/SkAntiRun.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkEdge.h" +#include "src/core/SkEdgeBuilder.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkQuadClipper.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkScan.h" +#include "src/core/SkScanPriv.h" +#include "src/core/SkTSort.h" +#include "src/utils/SkUTF.h" + +#include + +#if defined(SK_DISABLE_AAA) +void SkScan::AAAFillPath(const SkPath&, SkBlitter*, const SkIRect&, const SkIRect&, bool) { + SkDEBUGFAIL("AAA Disabled"); + return; +} +#else + +/* + +The following is a high-level overview of our analytic anti-aliasing +algorithm. We consider a path as a collection of line segments, as +quadratic/cubic curves are converted to small line segments. Without loss of +generality, let's assume that the draw region is [0, W] x [0, H]. + +Our algorithm is based on horizontal scan lines (y = c_i) as the previous +sampling-based algorithm did. However, our algorithm uses non-equal-spaced +scan lines, while the previous method always uses equal-spaced scan lines, +such as (y = 1/2 + 0, 1/2 + 1, 1/2 + 2, ...) in the previous non-AA algorithm, +and (y = 1/8 + 1/4, 1/8 + 2/4, 1/8 + 3/4, ...) in the previous +16-supersampling AA algorithm. + +Our algorithm contains scan lines y = c_i for c_i that is either: + +1. an integer between [0, H] + +2. the y value of a line segment endpoint + +3. the y value of an intersection of two line segments + +For two consecutive scan lines y = c_i, y = c_{i+1}, we analytically computes +the coverage of this horizontal strip of our path on each pixel. This can be +done very efficiently because the strip of our path now only consists of +trapezoids whose top and bottom edges are y = c_i, y = c_{i+1} (this includes +rectangles and triangles as special cases). + +We now describe how the coverage of single pixel is computed against such a +trapezoid. That coverage is essentially the intersection area of a rectangle +(e.g., [0, 1] x [c_i, c_{i+1}]) and our trapezoid. However, that intersection +could be complicated, as shown in the example region A below: + ++-----------\----+ +| \ C| +| \ | +\ \ | +|\ A \| +| \ \ +| \ | +| B \ | ++----\-----------+ + +However, we don't have to compute the area of A directly. Instead, we can +compute the excluded area, which are B and C, quite easily, because they're +just triangles. In fact, we can prove that an excluded region (take B as an +example) is either itself a simple trapezoid (including rectangles, triangles, +and empty regions), or its opposite (the opposite of B is A + C) is a simple +trapezoid. In any case, we can compute its area efficiently. + +In summary, our algorithm has a higher quality because it generates ground- +truth coverages analytically. It is also faster because it has much fewer +unnessasary horizontal scan lines. For example, given a triangle path, the +number of scan lines in our algorithm is only about 3 + H while the +16-supersampling algorithm has about 4H scan lines. + +*/ + +static void add_alpha(SkAlpha* alpha, SkAlpha delta) { + SkASSERT(*alpha + delta <= 256); + *alpha = SkAlphaRuns::CatchOverflow(*alpha + delta); +} + +static void safely_add_alpha(SkAlpha* alpha, SkAlpha delta) { + *alpha = SkTMin(0xFF, *alpha + delta); +} + +class AdditiveBlitter : public SkBlitter { +public: + ~AdditiveBlitter() override {} + + virtual SkBlitter* getRealBlitter(bool forceRealBlitter = false) = 0; + + virtual void blitAntiH(int x, int y, const SkAlpha antialias[], int len) = 0; + virtual void blitAntiH(int x, int y, const SkAlpha alpha) = 0; + virtual void blitAntiH(int x, int y, int width, const SkAlpha alpha) = 0; + + void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override { + SkDEBUGFAIL("Please call real blitter's blitAntiH instead."); + } + + void blitV(int x, int y, int height, SkAlpha alpha) override { + SkDEBUGFAIL("Please call real blitter's blitV instead."); + } + + void blitH(int x, int y, int width) override { + SkDEBUGFAIL("Please call real blitter's blitH instead."); + } + + void blitRect(int x, int y, int width, int height) override { + SkDEBUGFAIL("Please call real blitter's blitRect instead."); + } + + void blitAntiRect(int x, int y, int width, int height, SkAlpha leftAlpha, SkAlpha rightAlpha) + override { + SkDEBUGFAIL("Please call real blitter's blitAntiRect instead."); + } + + virtual int getWidth() = 0; + + // Flush the additive alpha cache if floor(y) and floor(nextY) is different + // (i.e., we'll start working on a new pixel row). + virtual void flush_if_y_changed(SkFixed y, SkFixed nextY) = 0; +}; + +// We need this mask blitter because it significantly accelerates small path filling. +class MaskAdditiveBlitter : public AdditiveBlitter { +public: + MaskAdditiveBlitter(SkBlitter* realBlitter, + const SkIRect& ir, + const SkIRect& clipBounds, + bool isInverse); + ~MaskAdditiveBlitter() override { fRealBlitter->blitMask(fMask, fClipRect); } + + // Most of the time, we still consider this mask blitter as the real blitter + // so we can accelerate blitRect and others. But sometimes we want to return + // the absolute real blitter (e.g., when we fall back to the old code path). + SkBlitter* getRealBlitter(bool forceRealBlitter) override { + return forceRealBlitter ? fRealBlitter : this; + } + + // Virtual function is slow. So don't use this. Directly add alpha to the mask instead. + void blitAntiH(int x, int y, const SkAlpha antialias[], int len) override; + + // Allowing following methods are used to blit rectangles during aaa_walk_convex_edges + // Since there aren't many rectangles, we can still bear the slow speed of virtual functions. + void blitAntiH(int x, int y, const SkAlpha alpha) override; + void blitAntiH(int x, int y, int width, const SkAlpha alpha) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitRect(int x, int y, int width, int height) override; + void blitAntiRect(int x, int y, int width, int height, SkAlpha leftAlpha, SkAlpha rightAlpha) + override; + + // The flush is only needed for RLE (RunBasedAdditiveBlitter) + void flush_if_y_changed(SkFixed y, SkFixed nextY) override {} + + int getWidth() override { return fClipRect.width(); } + + static bool CanHandleRect(const SkIRect& bounds) { + int width = bounds.width(); + if (width > MaskAdditiveBlitter::kMAX_WIDTH) { + return false; + } + int64_t rb = SkAlign4(width); + // use 64bits to detect overflow + int64_t storage = rb * bounds.height(); + + return (width <= MaskAdditiveBlitter::kMAX_WIDTH) && + (storage <= MaskAdditiveBlitter::kMAX_STORAGE); + } + + // Return a pointer where pointer[x] corresonds to the alpha of (x, y) + uint8_t* getRow(int y) { + if (y != fY) { + fY = y; + fRow = fMask.fImage + (y - fMask.fBounds.fTop) * fMask.fRowBytes - fMask.fBounds.fLeft; + } + return fRow; + } + +private: + // so we don't try to do very wide things, where the RLE blitter would be faster + static const int kMAX_WIDTH = 32; + static const int kMAX_STORAGE = 1024; + + SkBlitter* fRealBlitter; + SkMask fMask; + SkIRect fClipRect; + // we add 2 because we can write 1 extra byte at either end due to precision error + uint32_t fStorage[(kMAX_STORAGE >> 2) + 2]; + + uint8_t* fRow; + int fY; +}; + +MaskAdditiveBlitter::MaskAdditiveBlitter(SkBlitter* realBlitter, + const SkIRect& ir, + const SkIRect& clipBounds, + bool isInverse) { + SkASSERT(CanHandleRect(ir)); + SkASSERT(!isInverse); + + fRealBlitter = realBlitter; + + fMask.fImage = (uint8_t*)fStorage + 1; // There's 1 extra byte at either end of fStorage + fMask.fBounds = ir; + fMask.fRowBytes = ir.width(); + fMask.fFormat = SkMask::kA8_Format; + + fY = ir.fTop - 1; + fRow = nullptr; + + fClipRect = ir; + if (!fClipRect.intersect(clipBounds)) { + SkASSERT(0); + fClipRect.setEmpty(); + } + + memset(fStorage, 0, fMask.fBounds.height() * fMask.fRowBytes + 2); +} + +void MaskAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], int len) { + SK_ABORT("Don't use this; directly add alphas to the mask."); +} + +void MaskAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha alpha) { + SkASSERT(x >= fMask.fBounds.fLeft - 1); + add_alpha(&this->getRow(y)[x], alpha); +} + +void MaskAdditiveBlitter::blitAntiH(int x, int y, int width, const SkAlpha alpha) { + SkASSERT(x >= fMask.fBounds.fLeft - 1); + uint8_t* row = this->getRow(y); + for (int i = 0; i < width; ++i) { + add_alpha(&row[x + i], alpha); + } +} + +void MaskAdditiveBlitter::blitV(int x, int y, int height, SkAlpha alpha) { + if (alpha == 0) { + return; + } + SkASSERT(x >= fMask.fBounds.fLeft - 1); + // This must be called as if this is a real blitter. + // So we directly set alpha rather than adding it. + uint8_t* row = this->getRow(y); + for (int i = 0; i < height; ++i) { + row[x] = alpha; + row += fMask.fRowBytes; + } +} + +void MaskAdditiveBlitter::blitRect(int x, int y, int width, int height) { + SkASSERT(x >= fMask.fBounds.fLeft - 1); + // This must be called as if this is a real blitter. + // So we directly set alpha rather than adding it. + uint8_t* row = this->getRow(y); + for (int i = 0; i < height; ++i) { + memset(row + x, 0xFF, width); + row += fMask.fRowBytes; + } +} + +void MaskAdditiveBlitter::blitAntiRect(int x, + int y, + int width, + int height, + SkAlpha leftAlpha, + SkAlpha rightAlpha) { + blitV(x, y, height, leftAlpha); + blitV(x + 1 + width, y, height, rightAlpha); + blitRect(x + 1, y, width, height); +} + +class RunBasedAdditiveBlitter : public AdditiveBlitter { +public: + RunBasedAdditiveBlitter(SkBlitter* realBlitter, + const SkIRect& ir, + const SkIRect& clipBounds, + bool isInverse); + + ~RunBasedAdditiveBlitter() override { this->flush(); } + + SkBlitter* getRealBlitter(bool forceRealBlitter) override { return fRealBlitter; } + + void blitAntiH(int x, int y, const SkAlpha antialias[], int len) override; + void blitAntiH(int x, int y, const SkAlpha alpha) override; + void blitAntiH(int x, int y, int width, const SkAlpha alpha) override; + + int getWidth() override { return fWidth; } + + void flush_if_y_changed(SkFixed y, SkFixed nextY) override { + if (SkFixedFloorToInt(y) != SkFixedFloorToInt(nextY)) { + this->flush(); + } + } + +protected: + SkBlitter* fRealBlitter; + + int fCurrY; // Current y coordinate. + int fWidth; // Widest row of region to be blitted + int fLeft; // Leftmost x coordinate in any row + int fTop; // Initial y coordinate (top of bounds) + + // The next three variables are used to track a circular buffer that + // contains the values used in SkAlphaRuns. These variables should only + // ever be updated in advanceRuns(), and fRuns should always point to + // a valid SkAlphaRuns... + int fRunsToBuffer; + void* fRunsBuffer; + int fCurrentRun; + SkAlphaRuns fRuns; + + int fOffsetX; + + bool check(int x, int width) const { return x >= 0 && x + width <= fWidth; } + + // extra one to store the zero at the end + int getRunsSz() const { return (fWidth + 1 + (fWidth + 2) / 2) * sizeof(int16_t); } + + // This function updates the fRuns variable to point to the next buffer space + // with adequate storage for a SkAlphaRuns. It mostly just advances fCurrentRun + // and resets fRuns to point to an empty scanline. + void advanceRuns() { + const size_t kRunsSz = this->getRunsSz(); + fCurrentRun = (fCurrentRun + 1) % fRunsToBuffer; + fRuns.fRuns = reinterpret_cast(reinterpret_cast(fRunsBuffer) + + fCurrentRun * kRunsSz); + fRuns.fAlpha = reinterpret_cast(fRuns.fRuns + fWidth + 1); + fRuns.reset(fWidth); + } + + // Blitting 0xFF and 0 is much faster so we snap alphas close to them + SkAlpha snapAlpha(SkAlpha alpha) { return alpha > 247 ? 0xFF : alpha < 8 ? 0x00 : alpha; } + + void flush() { + if (fCurrY >= fTop) { + SkASSERT(fCurrentRun < fRunsToBuffer); + for (int x = 0; fRuns.fRuns[x]; x += fRuns.fRuns[x]) { + // It seems that blitting 255 or 0 is much faster than blitting 254 or 1 + fRuns.fAlpha[x] = snapAlpha(fRuns.fAlpha[x]); + } + if (!fRuns.empty()) { + // SkDEBUGCODE(fRuns.dump();) + fRealBlitter->blitAntiH(fLeft, fCurrY, fRuns.fAlpha, fRuns.fRuns); + this->advanceRuns(); + fOffsetX = 0; + } + fCurrY = fTop - 1; + } + } + + void checkY(int y) { + if (y != fCurrY) { + this->flush(); + fCurrY = y; + } + } +}; + +RunBasedAdditiveBlitter::RunBasedAdditiveBlitter(SkBlitter* realBlitter, + const SkIRect& ir, + const SkIRect& clipBounds, + bool isInverse) { + fRealBlitter = realBlitter; + + SkIRect sectBounds; + if (isInverse) { + // We use the clip bounds instead of the ir, since we may be asked to + // draw outside of the rect when we're a inverse filltype + sectBounds = clipBounds; + } else { + if (!sectBounds.intersect(ir, clipBounds)) { + sectBounds.setEmpty(); + } + } + + const int left = sectBounds.left(); + const int right = sectBounds.right(); + + fLeft = left; + fWidth = right - left; + fTop = sectBounds.top(); + fCurrY = fTop - 1; + + fRunsToBuffer = realBlitter->requestRowsPreserved(); + fRunsBuffer = realBlitter->allocBlitMemory(fRunsToBuffer * this->getRunsSz()); + fCurrentRun = -1; + + this->advanceRuns(); + + fOffsetX = 0; +} + +void RunBasedAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], int len) { + checkY(y); + x -= fLeft; + + if (x < 0) { + len += x; + antialias -= x; + x = 0; + } + len = SkTMin(len, fWidth - x); + SkASSERT(check(x, len)); + + if (x < fOffsetX) { + fOffsetX = 0; + } + + fOffsetX = fRuns.add(x, 0, len, 0, 0, fOffsetX); // Break the run + for (int i = 0; i < len; i += fRuns.fRuns[x + i]) { + for (int j = 1; j < fRuns.fRuns[x + i]; j++) { + fRuns.fRuns[x + i + j] = 1; + fRuns.fAlpha[x + i + j] = fRuns.fAlpha[x + i]; + } + fRuns.fRuns[x + i] = 1; + } + for (int i = 0; i < len; ++i) { + add_alpha(&fRuns.fAlpha[x + i], antialias[i]); + } +} + +void RunBasedAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha alpha) { + checkY(y); + x -= fLeft; + + if (x < fOffsetX) { + fOffsetX = 0; + } + + if (this->check(x, 1)) { + fOffsetX = fRuns.add(x, 0, 1, 0, alpha, fOffsetX); + } +} + +void RunBasedAdditiveBlitter::blitAntiH(int x, int y, int width, const SkAlpha alpha) { + checkY(y); + x -= fLeft; + + if (x < fOffsetX) { + fOffsetX = 0; + } + + if (this->check(x, width)) { + fOffsetX = fRuns.add(x, 0, width, 0, alpha, fOffsetX); + } +} + +// This exists specifically for concave path filling. +// In those cases, we can easily accumulate alpha greater than 0xFF. +class SafeRLEAdditiveBlitter : public RunBasedAdditiveBlitter { +public: + SafeRLEAdditiveBlitter(SkBlitter* realBlitter, + const SkIRect& ir, + const SkIRect& clipBounds, + bool isInverse) + : RunBasedAdditiveBlitter(realBlitter, ir, clipBounds, isInverse) {} + + void blitAntiH(int x, int y, const SkAlpha antialias[], int len) override; + void blitAntiH(int x, int y, const SkAlpha alpha) override; + void blitAntiH(int x, int y, int width, const SkAlpha alpha) override; +}; + +void SafeRLEAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], int len) { + checkY(y); + x -= fLeft; + + if (x < 0) { + len += x; + antialias -= x; + x = 0; + } + len = SkTMin(len, fWidth - x); + SkASSERT(check(x, len)); + + if (x < fOffsetX) { + fOffsetX = 0; + } + + fOffsetX = fRuns.add(x, 0, len, 0, 0, fOffsetX); // Break the run + for (int i = 0; i < len; i += fRuns.fRuns[x + i]) { + for (int j = 1; j < fRuns.fRuns[x + i]; j++) { + fRuns.fRuns[x + i + j] = 1; + fRuns.fAlpha[x + i + j] = fRuns.fAlpha[x + i]; + } + fRuns.fRuns[x + i] = 1; + } + for (int i = 0; i < len; ++i) { + safely_add_alpha(&fRuns.fAlpha[x + i], antialias[i]); + } +} + +void SafeRLEAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha alpha) { + checkY(y); + x -= fLeft; + + if (x < fOffsetX) { + fOffsetX = 0; + } + + if (check(x, 1)) { + // Break the run + fOffsetX = fRuns.add(x, 0, 1, 0, 0, fOffsetX); + safely_add_alpha(&fRuns.fAlpha[x], alpha); + } +} + +void SafeRLEAdditiveBlitter::blitAntiH(int x, int y, int width, const SkAlpha alpha) { + checkY(y); + x -= fLeft; + + if (x < fOffsetX) { + fOffsetX = 0; + } + + if (check(x, width)) { + // Break the run + fOffsetX = fRuns.add(x, 0, width, 0, 0, fOffsetX); + for (int i = x; i < x + width; i += fRuns.fRuns[i]) { + safely_add_alpha(&fRuns.fAlpha[i], alpha); + } + } +} + +// Return the alpha of a trapezoid whose height is 1 +static SkAlpha trapezoid_to_alpha(SkFixed l1, SkFixed l2) { + SkASSERT(l1 >= 0 && l2 >= 0); + SkFixed area = (l1 + l2) / 2; + return SkTo(area >> 8); +} + +// The alpha of right-triangle (a, a*b) +static SkAlpha partial_triangle_to_alpha(SkFixed a, SkFixed b) { + SkASSERT(a <= SK_Fixed1); +#if 0 + // TODO(mtklein): skia:8877 + SkASSERT(b <= SK_Fixed1); +#endif + + // Approximating... + // SkFixed area = SkFixedMul(a, SkFixedMul(a,b)) / 2; + SkFixed area = (a >> 11) * (a >> 11) * (b >> 11); + +#if 0 + // TODO(mtklein): skia:8877 + return SkTo(area >> 8); +#else + return SkTo((area >> 8) & 0xFF); +#endif +} + +static SkAlpha get_partial_alpha(SkAlpha alpha, SkFixed partialHeight) { + return SkToU8(SkFixedRoundToInt(alpha * partialHeight)); +} + +static SkAlpha get_partial_alpha(SkAlpha alpha, SkAlpha fullAlpha) { + return (alpha * fullAlpha) >> 8; +} + +// For SkFixed that's close to SK_Fixed1, we can't convert it to alpha by just shifting right. +// For example, when f = SK_Fixed1, right shifting 8 will get 256, but we need 255. +// This is rarely the problem so we'll only use this for blitting rectangles. +static SkAlpha fixed_to_alpha(SkFixed f) { + SkASSERT(f <= SK_Fixed1); + return get_partial_alpha(0xFF, f); +} + +// Suppose that line (l1, y)-(r1, y+1) intersects with (l2, y)-(r2, y+1), +// approximate (very coarsely) the x coordinate of the intersection. +static SkFixed approximate_intersection(SkFixed l1, SkFixed r1, SkFixed l2, SkFixed r2) { + if (l1 > r1) { + std::swap(l1, r1); + } + if (l2 > r2) { + std::swap(l2, r2); + } + return (SkTMax(l1, l2) + SkTMin(r1, r2)) / 2; +} + +// Here we always send in l < SK_Fixed1, and the first alpha we want to compute is alphas[0] +static void compute_alpha_above_line(SkAlpha* alphas, + SkFixed l, + SkFixed r, + SkFixed dY, + SkAlpha fullAlpha) { + SkASSERT(l <= r); + SkASSERT(l >> 16 == 0); + int R = SkFixedCeilToInt(r); + if (R == 0) { + return; + } else if (R == 1) { + alphas[0] = get_partial_alpha(((R << 17) - l - r) >> 9, fullAlpha); + } else { + SkFixed first = SK_Fixed1 - l; // horizontal edge length of the left-most triangle + SkFixed last = r - ((R - 1) << 16); // horizontal edge length of the right-most triangle + SkFixed firstH = SkFixedMul(first, dY); // vertical edge of the left-most triangle + alphas[0] = SkFixedMul(first, firstH) >> 9; // triangle alpha + SkFixed alpha16 = firstH + (dY >> 1); // rectangle plus triangle + for (int i = 1; i < R - 1; ++i) { + alphas[i] = alpha16 >> 8; + alpha16 += dY; + } + alphas[R - 1] = fullAlpha - partial_triangle_to_alpha(last, dY); + } +} + +// Here we always send in l < SK_Fixed1, and the first alpha we want to compute is alphas[0] +static void compute_alpha_below_line(SkAlpha* alphas, + SkFixed l, + SkFixed r, + SkFixed dY, + SkAlpha fullAlpha) { + SkASSERT(l <= r); + SkASSERT(l >> 16 == 0); + int R = SkFixedCeilToInt(r); + if (R == 0) { + return; + } else if (R == 1) { + alphas[0] = get_partial_alpha(trapezoid_to_alpha(l, r), fullAlpha); + } else { + SkFixed first = SK_Fixed1 - l; // horizontal edge length of the left-most triangle + SkFixed last = r - ((R - 1) << 16); // horizontal edge length of the right-most triangle + SkFixed lastH = SkFixedMul(last, dY); // vertical edge of the right-most triangle + alphas[R - 1] = SkFixedMul(last, lastH) >> 9; // triangle alpha + SkFixed alpha16 = lastH + (dY >> 1); // rectangle plus triangle + for (int i = R - 2; i > 0; i--) { + alphas[i] = (alpha16 >> 8) & 0xFF; + alpha16 += dY; + } + alphas[0] = fullAlpha - partial_triangle_to_alpha(first, dY); + } +} + +// Note that if fullAlpha != 0xFF, we'll multiply alpha by fullAlpha +static SK_ALWAYS_INLINE void blit_single_alpha(AdditiveBlitter* blitter, + int y, + int x, + SkAlpha alpha, + SkAlpha fullAlpha, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter, + bool needSafeCheck) { + if (isUsingMask) { + if (fullAlpha == 0xFF && !noRealBlitter) { // noRealBlitter is needed for concave paths + maskRow[x] = alpha; + } else if (needSafeCheck) { + safely_add_alpha(&maskRow[x], get_partial_alpha(alpha, fullAlpha)); + } else { + add_alpha(&maskRow[x], get_partial_alpha(alpha, fullAlpha)); + } + } else { + if (fullAlpha == 0xFF && !noRealBlitter) { + blitter->getRealBlitter()->blitV(x, y, 1, alpha); + } else { + blitter->blitAntiH(x, y, get_partial_alpha(alpha, fullAlpha)); + } + } +} + +static SK_ALWAYS_INLINE void blit_two_alphas(AdditiveBlitter* blitter, + int y, + int x, + SkAlpha a1, + SkAlpha a2, + SkAlpha fullAlpha, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter, + bool needSafeCheck) { + if (isUsingMask) { + if (needSafeCheck) { + safely_add_alpha(&maskRow[x], a1); + safely_add_alpha(&maskRow[x + 1], a2); + } else { + add_alpha(&maskRow[x], a1); + add_alpha(&maskRow[x + 1], a2); + } + } else { + if (fullAlpha == 0xFF && !noRealBlitter) { + blitter->getRealBlitter()->blitAntiH2(x, y, a1, a2); + } else { + blitter->blitAntiH(x, y, a1); + blitter->blitAntiH(x + 1, y, a2); + } + } +} + +static SK_ALWAYS_INLINE void blit_full_alpha(AdditiveBlitter* blitter, + int y, + int x, + int len, + SkAlpha fullAlpha, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter, + bool needSafeCheck) { + if (isUsingMask) { + for (int i = 0; i < len; ++i) { + if (needSafeCheck) { + safely_add_alpha(&maskRow[x + i], fullAlpha); + } else { + add_alpha(&maskRow[x + i], fullAlpha); + } + } + } else { + if (fullAlpha == 0xFF && !noRealBlitter) { + blitter->getRealBlitter()->blitH(x, y, len); + } else { + blitter->blitAntiH(x, y, len, fullAlpha); + } + } +} + +static void blit_aaa_trapezoid_row(AdditiveBlitter* blitter, + int y, + SkFixed ul, + SkFixed ur, + SkFixed ll, + SkFixed lr, + SkFixed lDY, + SkFixed rDY, + SkAlpha fullAlpha, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter, + bool needSafeCheck) { + int L = SkFixedFloorToInt(ul), R = SkFixedCeilToInt(lr); + int len = R - L; + + if (len == 1) { + SkAlpha alpha = trapezoid_to_alpha(ur - ul, lr - ll); + blit_single_alpha(blitter, + y, + L, + alpha, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + return; + } + + const int kQuickLen = 31; + char quickMemory[(sizeof(SkAlpha) * 2 + sizeof(int16_t)) * (kQuickLen + 1)]; + SkAlpha* alphas; + + if (len <= kQuickLen) { + alphas = (SkAlpha*)quickMemory; + } else { + alphas = new SkAlpha[(len + 1) * (sizeof(SkAlpha) * 2 + sizeof(int16_t))]; + } + + SkAlpha* tempAlphas = alphas + len + 1; + int16_t* runs = (int16_t*)(alphas + (len + 1) * 2); + + for (int i = 0; i < len; ++i) { + runs[i] = 1; + alphas[i] = fullAlpha; + } + runs[len] = 0; + + int uL = SkFixedFloorToInt(ul); + int lL = SkFixedCeilToInt(ll); + if (uL + 2 == lL) { // We only need to compute two triangles, accelerate this special case + SkFixed first = SkIntToFixed(uL) + SK_Fixed1 - ul; + SkFixed second = ll - ul - first; + SkAlpha a1 = fullAlpha - partial_triangle_to_alpha(first, lDY); + SkAlpha a2 = partial_triangle_to_alpha(second, lDY); + alphas[0] = alphas[0] > a1 ? alphas[0] - a1 : 0; + alphas[1] = alphas[1] > a2 ? alphas[1] - a2 : 0; + } else { + compute_alpha_below_line( + tempAlphas + uL - L, ul - SkIntToFixed(uL), ll - SkIntToFixed(uL), lDY, fullAlpha); + for (int i = uL; i < lL; ++i) { + if (alphas[i - L] > tempAlphas[i - L]) { + alphas[i - L] -= tempAlphas[i - L]; + } else { + alphas[i - L] = 0; + } + } + } + + int uR = SkFixedFloorToInt(ur); + int lR = SkFixedCeilToInt(lr); + if (uR + 2 == lR) { // We only need to compute two triangles, accelerate this special case + SkFixed first = SkIntToFixed(uR) + SK_Fixed1 - ur; + SkFixed second = lr - ur - first; + SkAlpha a1 = partial_triangle_to_alpha(first, rDY); + SkAlpha a2 = fullAlpha - partial_triangle_to_alpha(second, rDY); + alphas[len - 2] = alphas[len - 2] > a1 ? alphas[len - 2] - a1 : 0; + alphas[len - 1] = alphas[len - 1] > a2 ? alphas[len - 1] - a2 : 0; + } else { + compute_alpha_above_line( + tempAlphas + uR - L, ur - SkIntToFixed(uR), lr - SkIntToFixed(uR), rDY, fullAlpha); + for (int i = uR; i < lR; ++i) { + if (alphas[i - L] > tempAlphas[i - L]) { + alphas[i - L] -= tempAlphas[i - L]; + } else { + alphas[i - L] = 0; + } + } + } + + if (isUsingMask) { + for (int i = 0; i < len; ++i) { + if (needSafeCheck) { + safely_add_alpha(&maskRow[L + i], alphas[i]); + } else { + add_alpha(&maskRow[L + i], alphas[i]); + } + } + } else { + if (fullAlpha == 0xFF && !noRealBlitter) { + // Real blitter is faster than RunBasedAdditiveBlitter + blitter->getRealBlitter()->blitAntiH(L, y, alphas, runs); + } else { + blitter->blitAntiH(L, y, alphas, len); + } + } + + if (len > kQuickLen) { + delete[] alphas; + } +} + +static SK_ALWAYS_INLINE void blit_trapezoid_row(AdditiveBlitter* blitter, + int y, + SkFixed ul, + SkFixed ur, + SkFixed ll, + SkFixed lr, + SkFixed lDY, + SkFixed rDY, + SkAlpha fullAlpha, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter = false, + bool needSafeCheck = false) { + SkASSERT(lDY >= 0 && rDY >= 0); // We should only send in the absolte value + + if (ul > ur) { + return; + } + + // Edge crosses. Approximate it. This should only happend due to precision limit, + // so the approximation could be very coarse. + if (ll > lr) { + ll = lr = approximate_intersection(ul, ll, ur, lr); + } + + if (ul == ur && ll == lr) { + return; // empty trapzoid + } + + // We're going to use the left line ul-ll and the rite line ur-lr + // to exclude the area that's not covered by the path. + // Swapping (ul, ll) or (ur, lr) won't affect that exclusion + // so we'll do that for simplicity. + if (ul > ll) { + std::swap(ul, ll); + } + if (ur > lr) { + std::swap(ur, lr); + } + + SkFixed joinLeft = SkFixedCeilToFixed(ll); + SkFixed joinRite = SkFixedFloorToFixed(ur); + if (joinLeft <= joinRite) { // There's a rect from joinLeft to joinRite that we can blit + if (ul < joinLeft) { + int len = SkFixedCeilToInt(joinLeft - ul); + if (len == 1) { + SkAlpha alpha = trapezoid_to_alpha(joinLeft - ul, joinLeft - ll); + blit_single_alpha(blitter, + y, + ul >> 16, + alpha, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } else if (len == 2) { + SkFixed first = joinLeft - SK_Fixed1 - ul; + SkFixed second = ll - ul - first; + SkAlpha a1 = partial_triangle_to_alpha(first, lDY); + SkAlpha a2 = fullAlpha - partial_triangle_to_alpha(second, lDY); + blit_two_alphas(blitter, + y, + ul >> 16, + a1, + a2, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } else { + blit_aaa_trapezoid_row(blitter, + y, + ul, + joinLeft, + ll, + joinLeft, + lDY, + SK_MaxS32, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } + } + // SkAAClip requires that we blit from left to right. + // Hence we must blit [ul, joinLeft] before blitting [joinLeft, joinRite] + if (joinLeft < joinRite) { + blit_full_alpha(blitter, + y, + SkFixedFloorToInt(joinLeft), + SkFixedFloorToInt(joinRite - joinLeft), + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } + if (lr > joinRite) { + int len = SkFixedCeilToInt(lr - joinRite); + if (len == 1) { + SkAlpha alpha = trapezoid_to_alpha(ur - joinRite, lr - joinRite); + blit_single_alpha(blitter, + y, + joinRite >> 16, + alpha, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } else if (len == 2) { + SkFixed first = joinRite + SK_Fixed1 - ur; + SkFixed second = lr - ur - first; + SkAlpha a1 = fullAlpha - partial_triangle_to_alpha(first, rDY); + SkAlpha a2 = partial_triangle_to_alpha(second, rDY); + blit_two_alphas(blitter, + y, + joinRite >> 16, + a1, + a2, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } else { + blit_aaa_trapezoid_row(blitter, + y, + joinRite, + ur, + joinRite, + lr, + SK_MaxS32, + rDY, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } + } + } else { + blit_aaa_trapezoid_row(blitter, + y, + ul, + ur, + ll, + lr, + lDY, + rDY, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter, + needSafeCheck); + } +} + +static bool operator<(const SkAnalyticEdge& a, const SkAnalyticEdge& b) { + int valuea = a.fUpperY; + int valueb = b.fUpperY; + + if (valuea == valueb) { + valuea = a.fX; + valueb = b.fX; + } + + if (valuea == valueb) { + valuea = a.fDX; + valueb = b.fDX; + } + + return valuea < valueb; +} + +static SkAnalyticEdge* sort_edges(SkAnalyticEdge* list[], int count, SkAnalyticEdge** last) { + SkTQSort(list, list + count - 1); + + // now make the edges linked in sorted order + for (int i = 1; i < count; ++i) { + list[i - 1]->fNext = list[i]; + list[i]->fPrev = list[i - 1]; + } + + *last = list[count - 1]; + return list[0]; +} + +static void validate_sort(const SkAnalyticEdge* edge) { +#ifdef SK_DEBUG + SkFixed y = SkIntToFixed(-32768); + + while (edge->fUpperY != SK_MaxS32) { + edge->validate(); + SkASSERT(y <= edge->fUpperY); + + y = edge->fUpperY; + edge = (SkAnalyticEdge*)edge->fNext; + } +#endif +} + +// For an edge, we consider it smooth if the Dx doesn't change much, and Dy is large enough +// For curves that are updating, the Dx is not changing much if fQDx/fCDx and fQDy/fCDy are +// relatively large compared to fQDDx/QCDDx and fQDDy/fCDDy +static bool is_smooth_enough(SkAnalyticEdge* thisEdge, SkAnalyticEdge* nextEdge, int stop_y) { + if (thisEdge->fCurveCount < 0) { + const SkCubicEdge& cEdge = static_cast(thisEdge)->fCEdge; + int ddshift = cEdge.fCurveShift; + return SkAbs32(cEdge.fCDx) >> 1 >= SkAbs32(cEdge.fCDDx) >> ddshift && + SkAbs32(cEdge.fCDy) >> 1 >= SkAbs32(cEdge.fCDDy) >> ddshift && + // current Dy is (fCDy - (fCDDy >> ddshift)) >> dshift + (cEdge.fCDy - (cEdge.fCDDy >> ddshift)) >> cEdge.fCubicDShift >= SK_Fixed1; + } else if (thisEdge->fCurveCount > 0) { + const SkQuadraticEdge& qEdge = static_cast(thisEdge)->fQEdge; + return SkAbs32(qEdge.fQDx) >> 1 >= SkAbs32(qEdge.fQDDx) && + SkAbs32(qEdge.fQDy) >> 1 >= SkAbs32(qEdge.fQDDy) && + // current Dy is (fQDy - fQDDy) >> shift + (qEdge.fQDy - qEdge.fQDDy) >> qEdge.fCurveShift >= SK_Fixed1; + } + return SkAbs32(nextEdge->fDX - thisEdge->fDX) <= SK_Fixed1 && // DDx should be small + nextEdge->fLowerY - nextEdge->fUpperY >= SK_Fixed1; // Dy should be large +} + +// Check if the leftE and riteE are changing smoothly in terms of fDX. +// If yes, we can later skip the fractional y and directly jump to integer y. +static bool is_smooth_enough(SkAnalyticEdge* leftE, + SkAnalyticEdge* riteE, + SkAnalyticEdge* currE, + int stop_y) { + if (currE->fUpperY >= SkLeftShift(stop_y, 16)) { + return false; // We're at the end so we won't skip anything + } + if (leftE->fLowerY + SK_Fixed1 < riteE->fLowerY) { + return is_smooth_enough(leftE, currE, stop_y); // Only leftE is changing + } else if (leftE->fLowerY > riteE->fLowerY + SK_Fixed1) { + return is_smooth_enough(riteE, currE, stop_y); // Only riteE is changing + } + + // Now both edges are changing, find the second next edge + SkAnalyticEdge* nextCurrE = currE->fNext; + if (nextCurrE->fUpperY >= stop_y << 16) { // Check if we're at the end + return false; + } + // Ensure that currE is the next left edge and nextCurrE is the next right edge. Swap if not. + if (nextCurrE->fUpperX < currE->fUpperX) { + std::swap(currE, nextCurrE); + } + return is_smooth_enough(leftE, currE, stop_y) && is_smooth_enough(riteE, nextCurrE, stop_y); +} + +static void aaa_walk_convex_edges(SkAnalyticEdge* prevHead, + AdditiveBlitter* blitter, + int start_y, + int stop_y, + SkFixed leftBound, + SkFixed riteBound, + bool isUsingMask) { + validate_sort((SkAnalyticEdge*)prevHead->fNext); + + SkAnalyticEdge* leftE = (SkAnalyticEdge*)prevHead->fNext; + SkAnalyticEdge* riteE = (SkAnalyticEdge*)leftE->fNext; + SkAnalyticEdge* currE = (SkAnalyticEdge*)riteE->fNext; + + SkFixed y = SkTMax(leftE->fUpperY, riteE->fUpperY); + + for (;;) { + // We have to check fLowerY first because some edges might be alone (e.g., there's only + // a left edge but no right edge in a given y scan line) due to precision limit. + while (leftE->fLowerY <= y) { // Due to smooth jump, we may pass multiple short edges + if (!leftE->update(y)) { + if (SkFixedFloorToInt(currE->fUpperY) >= stop_y) { + goto END_WALK; + } + leftE = currE; + currE = (SkAnalyticEdge*)currE->fNext; + } + } + while (riteE->fLowerY <= y) { // Due to smooth jump, we may pass multiple short edges + if (!riteE->update(y)) { + if (SkFixedFloorToInt(currE->fUpperY) >= stop_y) { + goto END_WALK; + } + riteE = currE; + currE = (SkAnalyticEdge*)currE->fNext; + } + } + + SkASSERT(leftE); + SkASSERT(riteE); + + // check our bottom clip + if (SkFixedFloorToInt(y) >= stop_y) { + break; + } + + SkASSERT(SkFixedFloorToInt(leftE->fUpperY) <= stop_y); + SkASSERT(SkFixedFloorToInt(riteE->fUpperY) <= stop_y); + + leftE->goY(y); + riteE->goY(y); + + if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX && leftE->fDX > riteE->fDX)) { + std::swap(leftE, riteE); + } + + SkFixed local_bot_fixed = SkMin32(leftE->fLowerY, riteE->fLowerY); + if (is_smooth_enough(leftE, riteE, currE, stop_y)) { + local_bot_fixed = SkFixedCeilToFixed(local_bot_fixed); + } + local_bot_fixed = SkMin32(local_bot_fixed, SkIntToFixed(stop_y)); + + SkFixed left = SkTMax(leftBound, leftE->fX); + SkFixed dLeft = leftE->fDX; + SkFixed rite = SkTMin(riteBound, riteE->fX); + SkFixed dRite = riteE->fDX; + if (0 == (dLeft | dRite)) { + int fullLeft = SkFixedCeilToInt(left); + int fullRite = SkFixedFloorToInt(rite); + SkFixed partialLeft = SkIntToFixed(fullLeft) - left; + SkFixed partialRite = rite - SkIntToFixed(fullRite); + int fullTop = SkFixedCeilToInt(y); + int fullBot = SkFixedFloorToInt(local_bot_fixed); + SkFixed partialTop = SkIntToFixed(fullTop) - y; + SkFixed partialBot = local_bot_fixed - SkIntToFixed(fullBot); + if (fullTop > fullBot) { // The rectangle is within one pixel height... + partialTop -= (SK_Fixed1 - partialBot); + partialBot = 0; + } + + if (fullRite >= fullLeft) { + if (partialTop > 0) { // blit first partial row + if (partialLeft > 0) { + blitter->blitAntiH(fullLeft - 1, + fullTop - 1, + fixed_to_alpha(SkFixedMul(partialTop, partialLeft))); + } + blitter->blitAntiH( + fullLeft, fullTop - 1, fullRite - fullLeft, fixed_to_alpha(partialTop)); + if (partialRite > 0) { + blitter->blitAntiH(fullRite, + fullTop - 1, + fixed_to_alpha(SkFixedMul(partialTop, partialRite))); + } + blitter->flush_if_y_changed(y, y + partialTop); + } + + // Blit all full-height rows from fullTop to fullBot + if (fullBot > fullTop && + // SkAAClip cannot handle the empty rect so check the non-emptiness here + // (bug chromium:662800) + (fullRite > fullLeft || fixed_to_alpha(partialLeft) > 0 || + fixed_to_alpha(partialRite) > 0)) { + blitter->getRealBlitter()->blitAntiRect(fullLeft - 1, + fullTop, + fullRite - fullLeft, + fullBot - fullTop, + fixed_to_alpha(partialLeft), + fixed_to_alpha(partialRite)); + } + + if (partialBot > 0) { // blit last partial row + if (partialLeft > 0) { + blitter->blitAntiH(fullLeft - 1, + fullBot, + fixed_to_alpha(SkFixedMul(partialBot, partialLeft))); + } + blitter->blitAntiH( + fullLeft, fullBot, fullRite - fullLeft, fixed_to_alpha(partialBot)); + if (partialRite > 0) { + blitter->blitAntiH(fullRite, + fullBot, + fixed_to_alpha(SkFixedMul(partialBot, partialRite))); + } + } + } else { // left and rite are within the same pixel + if (partialTop > 0) { + blitter->blitAntiH(fullLeft - 1, + fullTop - 1, + 1, + fixed_to_alpha(SkFixedMul(partialTop, rite - left))); + blitter->flush_if_y_changed(y, y + partialTop); + } + if (fullBot > fullTop) { + blitter->getRealBlitter()->blitV( + fullLeft - 1, fullTop, fullBot - fullTop, fixed_to_alpha(rite - left)); + } + if (partialBot > 0) { + blitter->blitAntiH(fullLeft - 1, + fullBot, + 1, + fixed_to_alpha(SkFixedMul(partialBot, rite - left))); + } + } + + y = local_bot_fixed; + } else { + // The following constant are used to snap X + // We snap X mainly for speedup (no tiny triangle) and + // avoiding edge cases caused by precision errors + const SkFixed kSnapDigit = SK_Fixed1 >> 4; + const SkFixed kSnapHalf = kSnapDigit >> 1; + const SkFixed kSnapMask = (-1 ^ (kSnapDigit - 1)); + left += kSnapHalf; + rite += kSnapHalf; // For fast rounding + + // Number of blit_trapezoid_row calls we'll have + int count = SkFixedCeilToInt(local_bot_fixed) - SkFixedFloorToInt(y); + + // If we're using mask blitter, we advance the mask row in this function + // to save some "if" condition checks. + SkAlpha* maskRow = nullptr; + if (isUsingMask) { + maskRow = static_cast(blitter)->getRow(y >> 16); + } + + // Instead of writing one loop that handles both partial-row blit_trapezoid_row + // and full-row trapezoid_row together, we use the following 3-stage flow to + // handle partial-row blit and full-row blit separately. It will save us much time + // on changing y, left, and rite. + if (count > 1) { + if ((int)(y & 0xFFFF0000) != y) { // There's a partial-row on the top + count--; + SkFixed nextY = SkFixedCeilToFixed(y + 1); + SkFixed dY = nextY - y; + SkFixed nextLeft = left + SkFixedMul(dLeft, dY); + SkFixed nextRite = rite + SkFixedMul(dRite, dY); + SkASSERT((left & kSnapMask) >= leftBound && (rite & kSnapMask) <= riteBound && + (nextLeft & kSnapMask) >= leftBound && + (nextRite & kSnapMask) <= riteBound); + blit_trapezoid_row(blitter, + y >> 16, + left & kSnapMask, + rite & kSnapMask, + nextLeft & kSnapMask, + nextRite & kSnapMask, + leftE->fDY, + riteE->fDY, + get_partial_alpha(0xFF, dY), + maskRow, + isUsingMask); + blitter->flush_if_y_changed(y, nextY); + left = nextLeft; + rite = nextRite; + y = nextY; + } + + while (count > 1) { // Full rows in the middle + count--; + if (isUsingMask) { + maskRow = static_cast(blitter)->getRow(y >> 16); + } + SkFixed nextY = y + SK_Fixed1, nextLeft = left + dLeft, nextRite = rite + dRite; + SkASSERT((left & kSnapMask) >= leftBound && (rite & kSnapMask) <= riteBound && + (nextLeft & kSnapMask) >= leftBound && + (nextRite & kSnapMask) <= riteBound); + blit_trapezoid_row(blitter, + y >> 16, + left & kSnapMask, + rite & kSnapMask, + nextLeft & kSnapMask, + nextRite & kSnapMask, + leftE->fDY, + riteE->fDY, + 0xFF, + maskRow, + isUsingMask); + blitter->flush_if_y_changed(y, nextY); + left = nextLeft; + rite = nextRite; + y = nextY; + } + } + + if (isUsingMask) { + maskRow = static_cast(blitter)->getRow(y >> 16); + } + + SkFixed dY = local_bot_fixed - y; // partial-row on the bottom + SkASSERT(dY <= SK_Fixed1); + // Smooth jumping to integer y may make the last nextLeft/nextRite out of bound. + // Take them back into the bound here. + // Note that we substract kSnapHalf later so we have to add them to leftBound/riteBound + SkFixed nextLeft = SkTMax(left + SkFixedMul(dLeft, dY), leftBound + kSnapHalf); + SkFixed nextRite = SkTMin(rite + SkFixedMul(dRite, dY), riteBound + kSnapHalf); + SkASSERT((left & kSnapMask) >= leftBound && (rite & kSnapMask) <= riteBound && + (nextLeft & kSnapMask) >= leftBound && (nextRite & kSnapMask) <= riteBound); + blit_trapezoid_row(blitter, + y >> 16, + left & kSnapMask, + rite & kSnapMask, + nextLeft & kSnapMask, + nextRite & kSnapMask, + leftE->fDY, + riteE->fDY, + get_partial_alpha(0xFF, dY), + maskRow, + isUsingMask); + blitter->flush_if_y_changed(y, local_bot_fixed); + left = nextLeft; + rite = nextRite; + y = local_bot_fixed; + left -= kSnapHalf; + rite -= kSnapHalf; + } + + leftE->fX = left; + riteE->fX = rite; + leftE->fY = riteE->fY = y; + } + +END_WALK:; +} + +static void update_next_next_y(SkFixed y, SkFixed nextY, SkFixed* nextNextY) { + *nextNextY = y > nextY && y < *nextNextY ? y : *nextNextY; +} + +static void check_intersection(const SkAnalyticEdge* edge, SkFixed nextY, SkFixed* nextNextY) { + if (edge->fPrev->fPrev && edge->fPrev->fX + edge->fPrev->fDX > edge->fX + edge->fDX) { + *nextNextY = nextY + (SK_Fixed1 >> SkAnalyticEdge::kDefaultAccuracy); + } +} + +static void insert_new_edges(SkAnalyticEdge* newEdge, SkFixed y, SkFixed* nextNextY) { + if (newEdge->fUpperY > y) { + update_next_next_y(newEdge->fUpperY, y, nextNextY); + return; + } + SkAnalyticEdge* prev = newEdge->fPrev; + if (prev->fX <= newEdge->fX) { + while (newEdge->fUpperY <= y) { + check_intersection(newEdge, y, nextNextY); + update_next_next_y(newEdge->fLowerY, y, nextNextY); + newEdge = newEdge->fNext; + } + update_next_next_y(newEdge->fUpperY, y, nextNextY); + return; + } + // find first x pos to insert + SkAnalyticEdge* start = backward_insert_start(prev, newEdge->fX); + // insert the lot, fixing up the links as we go + do { + SkAnalyticEdge* next = newEdge->fNext; + do { + if (start->fNext == newEdge) { + goto nextEdge; + } + SkAnalyticEdge* after = start->fNext; + if (after->fX >= newEdge->fX) { + break; + } + SkASSERT(start != after); + start = after; + } while (true); + remove_edge(newEdge); + insert_edge_after(newEdge, start); + nextEdge: + check_intersection(newEdge, y, nextNextY); + update_next_next_y(newEdge->fLowerY, y, nextNextY); + start = newEdge; + newEdge = next; + } while (newEdge->fUpperY <= y); + update_next_next_y(newEdge->fUpperY, y, nextNextY); +} + +static void validate_edges_for_y(const SkAnalyticEdge* edge, SkFixed y) { +#ifdef SK_DEBUG + while (edge->fUpperY <= y) { + SkASSERT(edge->fPrev && edge->fNext); + SkASSERT(edge->fPrev->fNext == edge); + SkASSERT(edge->fNext->fPrev == edge); + SkASSERT(edge->fUpperY <= edge->fLowerY); + SkASSERT(edge->fPrev->fPrev == nullptr || edge->fPrev->fX <= edge->fX); + edge = edge->fNext; + } +#endif +} + +// Return true if prev->fX, next->fX are too close in the current pixel row. +static bool edges_too_close(SkAnalyticEdge* prev, SkAnalyticEdge* next, SkFixed lowerY) { + // When next->fDX == 0, prev->fX >= next->fX - SkAbs32(next->fDX) would be false + // even if prev->fX and next->fX are close and within one pixel (e.g., prev->fX == 0.1, + // next->fX == 0.9). Adding SLACK = 1 to the formula would guarantee it to be true if two + // edges prev and next are within one pixel. + constexpr SkFixed SLACK = SK_Fixed1; + + // Note that even if the following test failed, the edges might still be very close to each + // other at some point within the current pixel row because of prev->fDX and next->fDX. + // However, to handle that case, we have to sacrafice more performance. + // I think the current quality is good enough (mainly by looking at Nebraska-StateSeal.svg) + // so I'll ignore fDX for performance tradeoff. + return next && prev && next->fUpperY < lowerY && + prev->fX + SLACK >= next->fX - SkAbs32(next->fDX); + // The following is more accurate but also slower. + // return (prev && prev->fPrev && next && next->fNext != nullptr && next->fUpperY < lowerY && + // prev->fX + SkAbs32(prev->fDX) + SLACK >= next->fX - SkAbs32(next->fDX)); +} + +// This function exists for the case where the previous rite edge is removed because +// its fLowerY <= nextY +static bool edges_too_close(int prevRite, SkFixed ul, SkFixed ll) { + return prevRite > SkFixedFloorToInt(ul) || prevRite > SkFixedFloorToInt(ll); +} + +static void blit_saved_trapezoid(SkAnalyticEdge* leftE, + SkFixed lowerY, + SkFixed lowerLeft, + SkFixed lowerRite, + AdditiveBlitter* blitter, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter, + SkFixed leftClip, + SkFixed rightClip) { + SkAnalyticEdge* riteE = leftE->fRiteE; + SkASSERT(riteE); + SkASSERT(riteE->fNext == nullptr || leftE->fSavedY == riteE->fSavedY); + SkASSERT(SkFixedFloorToInt(lowerY - 1) == SkFixedFloorToInt(leftE->fSavedY)); + int y = SkFixedFloorToInt(leftE->fSavedY); + // Instead of using fixed_to_alpha(lowerY - leftE->fSavedY), we use the following fullAlpha + // to elimiate cumulative error: if there are many fractional y scan lines within the + // same row, the former may accumulate the rounding error while the later won't. + SkAlpha fullAlpha = fixed_to_alpha(lowerY - SkIntToFixed(y)) - + fixed_to_alpha(leftE->fSavedY - SkIntToFixed(y)); + // We need fSavedDY because the (quad or cubic) edge might be updated + blit_trapezoid_row( + blitter, + y, + SkTMax(leftE->fSavedX, leftClip), + SkTMin(riteE->fSavedX, rightClip), + SkTMax(lowerLeft, leftClip), + SkTMin(lowerRite, rightClip), + leftE->fSavedDY, + riteE->fSavedDY, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter || (fullAlpha == 0xFF && (edges_too_close(leftE->fPrev, leftE, lowerY) || + edges_too_close(riteE, riteE->fNext, lowerY))), + true); + leftE->fRiteE = nullptr; +} + +static void deferred_blit(SkAnalyticEdge* leftE, + SkAnalyticEdge* riteE, + SkFixed left, + SkFixed leftDY, // don't save leftE->fX/fDY as they may have been updated + SkFixed y, + SkFixed nextY, + bool isIntegralNextY, + bool leftEnds, + bool riteEnds, + AdditiveBlitter* blitter, + SkAlpha* maskRow, + bool isUsingMask, + bool noRealBlitter, + SkFixed leftClip, + SkFixed rightClip, + int yShift) { + if (leftE->fRiteE && leftE->fRiteE != riteE) { + // leftE's right edge changed. Blit the saved trapezoid. + SkASSERT(leftE->fRiteE->fNext == nullptr || leftE->fRiteE->fY == y); + blit_saved_trapezoid(leftE, + y, + left, + leftE->fRiteE->fX, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip); + } + if (!leftE->fRiteE) { + // Save and defer blitting the trapezoid + SkASSERT(riteE->fRiteE == nullptr); + SkASSERT(leftE->fPrev == nullptr || leftE->fY == nextY); + SkASSERT(riteE->fNext == nullptr || riteE->fY == y); + leftE->saveXY(left, y, leftDY); + riteE->saveXY(riteE->fX, y, riteE->fDY); + leftE->fRiteE = riteE; + } + SkASSERT(leftE->fPrev == nullptr || leftE->fY == nextY); + riteE->goY(nextY, yShift); + // Always blit when edges end or nextY is integral + if (isIntegralNextY || leftEnds || riteEnds) { + blit_saved_trapezoid(leftE, + nextY, + leftE->fX, + riteE->fX, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip); + } +} + +static void aaa_walk_edges(SkAnalyticEdge* prevHead, + SkAnalyticEdge* nextTail, + SkPath::FillType fillType, + AdditiveBlitter* blitter, + int start_y, + int stop_y, + SkFixed leftClip, + SkFixed rightClip, + bool isUsingMask, + bool forceRLE, + bool useDeferred, + bool skipIntersect) { + prevHead->fX = prevHead->fUpperX = leftClip; + nextTail->fX = nextTail->fUpperX = rightClip; + SkFixed y = SkTMax(prevHead->fNext->fUpperY, SkIntToFixed(start_y)); + SkFixed nextNextY = SK_MaxS32; + + { + SkAnalyticEdge* edge; + for (edge = prevHead->fNext; edge->fUpperY <= y; edge = edge->fNext) { + edge->goY(y); + update_next_next_y(edge->fLowerY, y, &nextNextY); + } + update_next_next_y(edge->fUpperY, y, &nextNextY); + } + + // returns 1 for evenodd, -1 for winding, regardless of inverse-ness + int windingMask = (fillType & 1) ? 1 : -1; + + bool isInverse = SkPath::IsInverseFillType(fillType); + + if (isInverse && SkIntToFixed(start_y) != y) { + int width = SkFixedFloorToInt(rightClip - leftClip); + if (SkFixedFloorToInt(y) != start_y) { + blitter->getRealBlitter()->blitRect( + SkFixedFloorToInt(leftClip), start_y, width, SkFixedFloorToInt(y) - start_y); + start_y = SkFixedFloorToInt(y); + } + SkAlpha* maskRow = + isUsingMask ? static_cast(blitter)->getRow(start_y) : nullptr; + blit_full_alpha(blitter, + start_y, + SkFixedFloorToInt(leftClip), + width, + fixed_to_alpha(y - SkIntToFixed(start_y)), + maskRow, + isUsingMask, + false, + false); + } + + while (true) { + int w = 0; + bool in_interval = isInverse; + SkFixed prevX = prevHead->fX; + SkFixed nextY = SkTMin(nextNextY, SkFixedCeilToFixed(y + 1)); + bool isIntegralNextY = (nextY & (SK_Fixed1 - 1)) == 0; + SkAnalyticEdge* currE = prevHead->fNext; + SkAnalyticEdge* leftE = prevHead; + SkFixed left = leftClip; + SkFixed leftDY = 0; + bool leftEnds = false; + int prevRite = SkFixedFloorToInt(leftClip); + + nextNextY = SK_MaxS32; + + SkASSERT((nextY & ((SK_Fixed1 >> 2) - 1)) == 0); + int yShift = 0; + if ((nextY - y) & (SK_Fixed1 >> 2)) { + yShift = 2; + nextY = y + (SK_Fixed1 >> 2); + } else if ((nextY - y) & (SK_Fixed1 >> 1)) { + yShift = 1; + SkASSERT(nextY == y + (SK_Fixed1 >> 1)); + } + + SkAlpha fullAlpha = fixed_to_alpha(nextY - y); + + // If we're using mask blitter, we advance the mask row in this function + // to save some "if" condition checks. + SkAlpha* maskRow = nullptr; + if (isUsingMask) { + maskRow = static_cast(blitter)->getRow(SkFixedFloorToInt(y)); + } + + SkASSERT(currE->fPrev == prevHead); + validate_edges_for_y(currE, y); + + // Even if next - y == SK_Fixed1, we can still break the left-to-right order requirement + // of the SKAAClip: |\| (two trapezoids with overlapping middle wedges) + bool noRealBlitter = forceRLE; // forceRLE && (nextY - y != SK_Fixed1); + + while (currE->fUpperY <= y) { + SkASSERT(currE->fLowerY >= nextY); + SkASSERT(currE->fY == y); + + w += currE->fWinding; + bool prev_in_interval = in_interval; + in_interval = !(w & windingMask) == isInverse; + + bool isLeft = in_interval && !prev_in_interval; + bool isRite = !in_interval && prev_in_interval; + bool currEnds = currE->fLowerY == nextY; + + if (useDeferred) { + if (currE->fRiteE && !isLeft) { + // currE is a left edge previously, but now it's not. + // Blit the trapezoid between fSavedY and y. + SkASSERT(currE->fRiteE->fY == y); + blit_saved_trapezoid(currE, + y, + currE->fX, + currE->fRiteE->fX, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip); + } + if (leftE->fRiteE == currE && !isRite) { + // currE is a right edge previously, but now it's not. + // Moreover, its corresponding leftE doesn't change (otherwise we'll handle it + // in the previous if clause). Hence we blit the trapezoid. + blit_saved_trapezoid(leftE, + y, + left, + currE->fX, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip); + } + } + + if (isRite) { + if (useDeferred) { + deferred_blit(leftE, + currE, + left, + leftDY, + y, + nextY, + isIntegralNextY, + leftEnds, + currEnds, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip, + yShift); + } else { + SkFixed rite = currE->fX; + currE->goY(nextY, yShift); + SkFixed nextLeft = SkTMax(leftClip, leftE->fX); + rite = SkTMin(rightClip, rite); + SkFixed nextRite = SkTMin(rightClip, currE->fX); + blit_trapezoid_row( + blitter, + y >> 16, + left, + rite, + nextLeft, + nextRite, + leftDY, + currE->fDY, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter || (fullAlpha == 0xFF && + (edges_too_close(prevRite, left, leftE->fX) || + edges_too_close(currE, currE->fNext, nextY))), + true); + prevRite = SkFixedCeilToInt(SkTMax(rite, currE->fX)); + } + } else { + if (isLeft) { + left = SkTMax(currE->fX, leftClip); + leftDY = currE->fDY; + leftE = currE; + leftEnds = leftE->fLowerY == nextY; + } + currE->goY(nextY, yShift); + } + + SkAnalyticEdge* next = currE->fNext; + SkFixed newX; + + while (currE->fLowerY <= nextY) { + if (currE->fCurveCount < 0) { + SkAnalyticCubicEdge* cubicEdge = (SkAnalyticCubicEdge*)currE; + cubicEdge->keepContinuous(); + if (!cubicEdge->updateCubic()) { + break; + } + } else if (currE->fCurveCount > 0) { + SkAnalyticQuadraticEdge* quadEdge = (SkAnalyticQuadraticEdge*)currE; + quadEdge->keepContinuous(); + if (!quadEdge->updateQuadratic()) { + break; + } + } else { + break; + } + } + SkASSERT(currE->fY == nextY); + + if (currE->fLowerY <= nextY) { + remove_edge(currE); + } else { + update_next_next_y(currE->fLowerY, nextY, &nextNextY); + newX = currE->fX; + SkASSERT(currE->fLowerY > nextY); + if (newX < prevX) { // ripple currE backwards until it is x-sorted + // If the crossing edge is a right edge, blit the saved trapezoid. + if (leftE->fRiteE == currE && useDeferred) { + SkASSERT(leftE->fY == nextY && currE->fY == nextY); + blit_saved_trapezoid(leftE, + nextY, + leftE->fX, + currE->fX, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip); + } + backward_insert_edge_based_on_x(currE); + } else { + prevX = newX; + } + if (!skipIntersect) { + check_intersection(currE, nextY, &nextNextY); + } + } + + currE = next; + SkASSERT(currE); + } + + // was our right-edge culled away? + if (in_interval) { + if (useDeferred) { + deferred_blit(leftE, + nextTail, + left, + leftDY, + y, + nextY, + isIntegralNextY, + leftEnds, + false, + blitter, + maskRow, + isUsingMask, + noRealBlitter, + leftClip, + rightClip, + yShift); + } else { + blit_trapezoid_row(blitter, + y >> 16, + left, + rightClip, + SkTMax(leftClip, leftE->fX), + rightClip, + leftDY, + 0, + fullAlpha, + maskRow, + isUsingMask, + noRealBlitter || (fullAlpha == 0xFF && + edges_too_close(leftE->fPrev, leftE, nextY)), + true); + } + } + + if (forceRLE) { + ((RunBasedAdditiveBlitter*)blitter)->flush_if_y_changed(y, nextY); + } + + y = nextY; + if (y >= SkIntToFixed(stop_y)) { + break; + } + + // now currE points to the first edge with a fUpperY larger than the previous y + insert_new_edges(currE, y, &nextNextY); + } +} + +static SK_ALWAYS_INLINE void aaa_fill_path( + const SkPath& path, + const SkIRect& clipRect, + AdditiveBlitter* blitter, + int start_y, + int stop_y, + bool pathContainedInClip, + bool isUsingMask, + bool forceRLE) { // forceRLE implies that SkAAClip is calling us + SkASSERT(blitter); + + SkAnalyticEdgeBuilder builder; + int count = builder.buildEdges(path, pathContainedInClip ? nullptr : &clipRect); + SkAnalyticEdge** list = builder.analyticEdgeList(); + + SkIRect rect = clipRect; + if (0 == count) { + if (path.isInverseFillType()) { + /* + * Since we are in inverse-fill, our caller has already drawn above + * our top (start_y) and will draw below our bottom (stop_y). Thus + * we need to restrict our drawing to the intersection of the clip + * and those two limits. + */ + if (rect.fTop < start_y) { + rect.fTop = start_y; + } + if (rect.fBottom > stop_y) { + rect.fBottom = stop_y; + } + if (!rect.isEmpty()) { + blitter->getRealBlitter()->blitRect( + rect.fLeft, rect.fTop, rect.width(), rect.height()); + } + } + return; + } + + SkAnalyticEdge headEdge, tailEdge, *last; + // this returns the first and last edge after they're sorted into a dlink list + SkAnalyticEdge* edge = sort_edges(list, count, &last); + + headEdge.fRiteE = nullptr; + headEdge.fPrev = nullptr; + headEdge.fNext = edge; + headEdge.fUpperY = headEdge.fLowerY = SK_MinS32; + headEdge.fX = SK_MinS32; + headEdge.fDX = 0; + headEdge.fDY = SK_MaxS32; + headEdge.fUpperX = SK_MinS32; + edge->fPrev = &headEdge; + + tailEdge.fRiteE = nullptr; + tailEdge.fPrev = last; + tailEdge.fNext = nullptr; + tailEdge.fUpperY = tailEdge.fLowerY = SK_MaxS32; + tailEdge.fX = SK_MaxS32; + tailEdge.fDX = 0; + tailEdge.fDY = SK_MaxS32; + tailEdge.fUpperX = SK_MaxS32; + last->fNext = &tailEdge; + + // now edge is the head of the sorted linklist + + if (!pathContainedInClip && start_y < clipRect.fTop) { + start_y = clipRect.fTop; + } + if (!pathContainedInClip && stop_y > clipRect.fBottom) { + stop_y = clipRect.fBottom; + } + + SkFixed leftBound = SkIntToFixed(rect.fLeft); + SkFixed rightBound = SkIntToFixed(rect.fRight); + if (isUsingMask) { + // If we're using mask, then we have to limit the bound within the path bounds. + // Otherwise, the edge drift may access an invalid address inside the mask. + SkIRect ir; + path.getBounds().roundOut(&ir); + leftBound = SkTMax(leftBound, SkIntToFixed(ir.fLeft)); + rightBound = SkTMin(rightBound, SkIntToFixed(ir.fRight)); + } + + if (!path.isInverseFillType() && path.isConvex() && count >= 2) { + aaa_walk_convex_edges( + &headEdge, blitter, start_y, stop_y, leftBound, rightBound, isUsingMask); + } else { + // Only use deferred blitting if there are many edges. + bool useDeferred = + count > + (SkFixedFloorToInt(tailEdge.fPrev->fLowerY - headEdge.fNext->fUpperY) + 1) * 4; + + // We skip intersection computation if there are many points which probably already + // give us enough fractional scan lines. + bool skipIntersect = path.countPoints() > (stop_y - start_y) * 2; + + aaa_walk_edges(&headEdge, + &tailEdge, + path.getFillType(), + blitter, + start_y, + stop_y, + leftBound, + rightBound, + isUsingMask, + forceRLE, + useDeferred, + skipIntersect); + } +} + +void SkScan::AAAFillPath(const SkPath& path, + SkBlitter* blitter, + const SkIRect& ir, + const SkIRect& clipBounds, + bool forceRLE) { + bool containedInClip = clipBounds.contains(ir); + bool isInverse = path.isInverseFillType(); + + // The mask blitter (where we store intermediate alpha values directly in a mask, and then call + // the real blitter once in the end to blit the whole mask) is faster than the RLE blitter when + // the blit region is small enough (i.e., CanHandleRect(ir)). When isInverse is true, the blit + // region is no longer the rectangle ir so we won't use the mask blitter. The caller may also + // use the forceRLE flag to force not using the mask blitter. Also, when the path is a simple + // rect, preparing a mask and blitting it might have too much overhead. Hence we'll use + // blitFatAntiRect to avoid the mask and its overhead. + if (MaskAdditiveBlitter::CanHandleRect(ir) && !isInverse && !forceRLE) { + // blitFatAntiRect is slower than the normal AAA flow without MaskAdditiveBlitter. + // Hence only tryBlitFatAntiRect when MaskAdditiveBlitter would have been used. + if (!TryBlitFatAntiRect(blitter, path, clipBounds)) { + MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse); + aaa_fill_path(path, + clipBounds, + &additiveBlitter, + ir.fTop, + ir.fBottom, + containedInClip, + true, + forceRLE); + } + } else if (!isInverse && path.isConvex()) { + // If the filling area is convex (i.e., path.isConvex && !isInverse), our simpler + // aaa_walk_convex_edges won't generate alphas above 255. Hence we don't need + // SafeRLEAdditiveBlitter (which is slow due to clamping). The basic RLE blitter + // RunBasedAdditiveBlitter would suffice. + RunBasedAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse); + aaa_fill_path(path, + clipBounds, + &additiveBlitter, + ir.fTop, + ir.fBottom, + containedInClip, + false, + forceRLE); + } else { + // If the filling area might not be convex, the more involved aaa_walk_edges would + // be called and we have to clamp the alpha downto 255. The SafeRLEAdditiveBlitter + // does that at a cost of performance. + SafeRLEAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse); + aaa_fill_path(path, + clipBounds, + &additiveBlitter, + ir.fTop, + ir.fBottom, + containedInClip, + false, + forceRLE); + } +} +#endif // defined(SK_DISABLE_AAA) diff --git a/gfx/skia/skia/src/core/SkScan_AntiPath.cpp b/gfx/skia/skia/src/core/SkScan_AntiPath.cpp new file mode 100644 index 0000000000..9b21e4b43b --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan_AntiPath.cpp @@ -0,0 +1,838 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkScanPriv.h" + +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkRegion.h" +#include "include/private/SkTo.h" +#include "src/core/SkAntiRun.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkPathPriv.h" + +#define SHIFT SK_SUPERSAMPLE_SHIFT +#define SCALE (1 << SHIFT) +#define MASK (SCALE - 1) + +/** @file + We have two techniques for capturing the output of the supersampler: + - SUPERMASK, which records a large mask-bitmap + this is often faster for small, complex objects + - RLE, which records a rle-encoded scanline + this is often faster for large objects with big spans + + These blitters use two coordinate systems: + - destination coordinates, scale equal to the output - often + abbreviated with 'i' or 'I' in variable names + - supersampled coordinates, scale equal to the output * SCALE + */ + +//#define FORCE_SUPERMASK +//#define FORCE_RLE + +/////////////////////////////////////////////////////////////////////////////// + +/// Base class for a single-pass supersampled blitter. +class BaseSuperBlitter : public SkBlitter { +public: + BaseSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir, + const SkIRect& clipBounds, bool isInverse); + + /// Must be explicitly defined on subclasses. + virtual void blitAntiH(int x, int y, const SkAlpha antialias[], + const int16_t runs[]) override { + SkDEBUGFAIL("How did I get here?"); + } + /// May not be called on BaseSuperBlitter because it blits out of order. + void blitV(int x, int y, int height, SkAlpha alpha) override { + SkDEBUGFAIL("How did I get here?"); + } + +protected: + SkBlitter* fRealBlitter; + /// Current y coordinate, in destination coordinates. + int fCurrIY; + /// Widest row of region to be blitted, in destination coordinates. + int fWidth; + /// Leftmost x coordinate in any row, in destination coordinates. + int fLeft; + /// Leftmost x coordinate in any row, in supersampled coordinates. + int fSuperLeft; + + SkDEBUGCODE(int fCurrX;) + /// Current y coordinate in supersampled coordinates. + int fCurrY; + /// Initial y coordinate (top of bounds). + int fTop; + + SkIRect fSectBounds; +}; + +BaseSuperBlitter::BaseSuperBlitter(SkBlitter* realBlit, const SkIRect& ir, + const SkIRect& clipBounds, bool isInverse) { + fRealBlitter = realBlit; + + SkIRect sectBounds; + if (isInverse) { + // We use the clip bounds instead of the ir, since we may be asked to + //draw outside of the rect when we're a inverse filltype + sectBounds = clipBounds; + } else { + if (!sectBounds.intersect(ir, clipBounds)) { + sectBounds.setEmpty(); + } + } + + const int left = sectBounds.left(); + const int right = sectBounds.right(); + + fLeft = left; + fSuperLeft = SkLeftShift(left, SHIFT); + fWidth = right - left; + fTop = sectBounds.top(); + fCurrIY = fTop - 1; + fCurrY = SkLeftShift(fTop, SHIFT) - 1; + + SkDEBUGCODE(fCurrX = -1;) +} + +/// Run-length-encoded supersampling antialiased blitter. +class SuperBlitter : public BaseSuperBlitter { +public: + SuperBlitter(SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds, + bool isInverse); + + ~SuperBlitter() override { + this->flush(); + } + + /// Once fRuns contains a complete supersampled row, flush() blits + /// it out through the wrapped blitter. + void flush(); + + /// Blits a row of pixels, with location and width specified + /// in supersampled coordinates. + void blitH(int x, int y, int width) override; + /// Blits a rectangle of pixels, with location and size specified + /// in supersampled coordinates. + void blitRect(int x, int y, int width, int height) override; + +private: + // The next three variables are used to track a circular buffer that + // contains the values used in SkAlphaRuns. These variables should only + // ever be updated in advanceRuns(), and fRuns should always point to + // a valid SkAlphaRuns... + int fRunsToBuffer; + void* fRunsBuffer; + int fCurrentRun; + SkAlphaRuns fRuns; + + // extra one to store the zero at the end + int getRunsSz() const { return (fWidth + 1 + (fWidth + 2)/2) * sizeof(int16_t); } + + // This function updates the fRuns variable to point to the next buffer space + // with adequate storage for a SkAlphaRuns. It mostly just advances fCurrentRun + // and resets fRuns to point to an empty scanline. + void advanceRuns() { + const size_t kRunsSz = this->getRunsSz(); + fCurrentRun = (fCurrentRun + 1) % fRunsToBuffer; + fRuns.fRuns = reinterpret_cast( + reinterpret_cast(fRunsBuffer) + fCurrentRun * kRunsSz); + fRuns.fAlpha = reinterpret_cast(fRuns.fRuns + fWidth + 1); + fRuns.reset(fWidth); + } + + int fOffsetX; +}; + +SuperBlitter::SuperBlitter(SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds, + bool isInverse) + : BaseSuperBlitter(realBlitter, ir, clipBounds, isInverse) +{ + fRunsToBuffer = realBlitter->requestRowsPreserved(); + fRunsBuffer = realBlitter->allocBlitMemory(fRunsToBuffer * this->getRunsSz()); + fCurrentRun = -1; + + this->advanceRuns(); + + fOffsetX = 0; +} + +void SuperBlitter::flush() { + if (fCurrIY >= fTop) { + + SkASSERT(fCurrentRun < fRunsToBuffer); + if (!fRuns.empty()) { + // SkDEBUGCODE(fRuns.dump();) + fRealBlitter->blitAntiH(fLeft, fCurrIY, fRuns.fAlpha, fRuns.fRuns); + this->advanceRuns(); + fOffsetX = 0; + } + + fCurrIY = fTop - 1; + SkDEBUGCODE(fCurrX = -1;) + } +} + +/** coverage_to_partial_alpha() is being used by SkAlphaRuns, which + *accumulates* SCALE pixels worth of "alpha" in [0,(256/SCALE)] + to produce a final value in [0, 255] and handles clamping 256->255 + itself, with the same (alpha - (alpha >> 8)) correction as + coverage_to_exact_alpha(). +*/ +static inline int coverage_to_partial_alpha(int aa) { + aa <<= 8 - 2*SHIFT; + return aa; +} + +/** coverage_to_exact_alpha() is being used by our blitter, which wants + a final value in [0, 255]. +*/ +static inline int coverage_to_exact_alpha(int aa) { + int alpha = (256 >> SHIFT) * aa; + // clamp 256->255 + return alpha - (alpha >> 8); +} + +void SuperBlitter::blitH(int x, int y, int width) { + SkASSERT(width > 0); + + int iy = y >> SHIFT; + SkASSERT(iy >= fCurrIY); + + x -= fSuperLeft; + // hack, until I figure out why my cubics (I think) go beyond the bounds + if (x < 0) { + width += x; + x = 0; + } + +#ifdef SK_DEBUG + SkASSERT(y != fCurrY || x >= fCurrX); +#endif + SkASSERT(y >= fCurrY); + if (fCurrY != y) { + fOffsetX = 0; + fCurrY = y; + } + + if (iy != fCurrIY) { // new scanline + this->flush(); + fCurrIY = iy; + } + + int start = x; + int stop = x + width; + + SkASSERT(start >= 0 && stop > start); + // integer-pixel-aligned ends of blit, rounded out + int fb = start & MASK; + int fe = stop & MASK; + int n = (stop >> SHIFT) - (start >> SHIFT) - 1; + + if (n < 0) { + fb = fe - fb; + n = 0; + fe = 0; + } else { + if (fb == 0) { + n += 1; + } else { + fb = SCALE - fb; + } + } + + fOffsetX = fRuns.add(x >> SHIFT, coverage_to_partial_alpha(fb), + n, coverage_to_partial_alpha(fe), + (1 << (8 - SHIFT)) - (((y & MASK) + 1) >> SHIFT), + fOffsetX); + +#ifdef SK_DEBUG + fRuns.assertValid(y & MASK, (1 << (8 - SHIFT))); + fCurrX = x + width; +#endif +} + +#if 0 // UNUSED +static void set_left_rite_runs(SkAlphaRuns& runs, int ileft, U8CPU leftA, + int n, U8CPU riteA) { + SkASSERT(leftA <= 0xFF); + SkASSERT(riteA <= 0xFF); + + int16_t* run = runs.fRuns; + uint8_t* aa = runs.fAlpha; + + if (ileft > 0) { + run[0] = ileft; + aa[0] = 0; + run += ileft; + aa += ileft; + } + + SkASSERT(leftA < 0xFF); + if (leftA > 0) { + *run++ = 1; + *aa++ = leftA; + } + + if (n > 0) { + run[0] = n; + aa[0] = 0xFF; + run += n; + aa += n; + } + + SkASSERT(riteA < 0xFF); + if (riteA > 0) { + *run++ = 1; + *aa++ = riteA; + } + run[0] = 0; +} +#endif + +void SuperBlitter::blitRect(int x, int y, int width, int height) { + SkASSERT(width > 0); + SkASSERT(height > 0); + + // blit leading rows + while ((y & MASK)) { + this->blitH(x, y++, width); + if (--height <= 0) { + return; + } + } + SkASSERT(height > 0); + + // Since this is a rect, instead of blitting supersampled rows one at a + // time and then resolving to the destination canvas, we can blit + // directly to the destintion canvas one row per SCALE supersampled rows. + int start_y = y >> SHIFT; + int stop_y = (y + height) >> SHIFT; + int count = stop_y - start_y; + if (count > 0) { + y += count << SHIFT; + height -= count << SHIFT; + + // save original X for our tail blitH() loop at the bottom + int origX = x; + + x -= fSuperLeft; + // hack, until I figure out why my cubics (I think) go beyond the bounds + if (x < 0) { + width += x; + x = 0; + } + + // There is always a left column, a middle, and a right column. + // ileft is the destination x of the first pixel of the entire rect. + // xleft is (SCALE - # of covered supersampled pixels) in that + // destination pixel. + int ileft = x >> SHIFT; + int xleft = x & MASK; + // irite is the destination x of the last pixel of the OPAQUE section. + // xrite is the number of supersampled pixels extending beyond irite; + // xrite/SCALE should give us alpha. + int irite = (x + width) >> SHIFT; + int xrite = (x + width) & MASK; + if (!xrite) { + xrite = SCALE; + irite--; + } + + // Need to call flush() to clean up pending draws before we + // even consider blitV(), since otherwise it can look nonmonotonic. + SkASSERT(start_y > fCurrIY); + this->flush(); + + int n = irite - ileft - 1; + if (n < 0) { + // If n < 0, we'll only have a single partially-transparent column + // of pixels to render. + xleft = xrite - xleft; + SkASSERT(xleft <= SCALE); + SkASSERT(xleft > 0); + fRealBlitter->blitV(ileft + fLeft, start_y, count, + coverage_to_exact_alpha(xleft)); + } else { + // With n = 0, we have two possibly-transparent columns of pixels + // to render; with n > 0, we have opaque columns between them. + + xleft = SCALE - xleft; + + // Using coverage_to_exact_alpha is not consistent with blitH() + const int coverageL = coverage_to_exact_alpha(xleft); + const int coverageR = coverage_to_exact_alpha(xrite); + + SkASSERT(coverageL > 0 || n > 0 || coverageR > 0); + SkASSERT((coverageL != 0) + n + (coverageR != 0) <= fWidth); + + fRealBlitter->blitAntiRect(ileft + fLeft, start_y, n, count, + coverageL, coverageR); + } + + // preamble for our next call to blitH() + fCurrIY = stop_y - 1; + fOffsetX = 0; + fCurrY = y - 1; + fRuns.reset(fWidth); + x = origX; + } + + // catch any remaining few rows + SkASSERT(height <= MASK); + while (--height >= 0) { + this->blitH(x, y++, width); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +/// Masked supersampling antialiased blitter. +class MaskSuperBlitter : public BaseSuperBlitter { +public: + MaskSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir, const SkIRect&, bool isInverse); + ~MaskSuperBlitter() override { + fRealBlitter->blitMask(fMask, fClipRect); + } + + void blitH(int x, int y, int width) override; + + static bool CanHandleRect(const SkIRect& bounds) { +#ifdef FORCE_RLE + return false; +#endif + int width = bounds.width(); + int64_t rb = SkAlign4(width); + // use 64bits to detect overflow + int64_t storage = rb * bounds.height(); + + return (width <= MaskSuperBlitter::kMAX_WIDTH) && + (storage <= MaskSuperBlitter::kMAX_STORAGE); + } + +private: + enum { +#ifdef FORCE_SUPERMASK + kMAX_WIDTH = 2048, + kMAX_STORAGE = 1024 * 1024 * 2 +#else + kMAX_WIDTH = 32, // so we don't try to do very wide things, where the RLE blitter would be faster + kMAX_STORAGE = 1024 +#endif + }; + + SkMask fMask; + SkIRect fClipRect; + // we add 1 because add_aa_span can write (unchanged) 1 extra byte at the end, rather than + // perform a test to see if stopAlpha != 0 + uint32_t fStorage[(kMAX_STORAGE >> 2) + 1]; +}; + +MaskSuperBlitter::MaskSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir, + const SkIRect& clipBounds, bool isInverse) + : BaseSuperBlitter(realBlitter, ir, clipBounds, isInverse) +{ + SkASSERT(CanHandleRect(ir)); + SkASSERT(!isInverse); + + fMask.fImage = (uint8_t*)fStorage; + fMask.fBounds = ir; + fMask.fRowBytes = ir.width(); + fMask.fFormat = SkMask::kA8_Format; + + fClipRect = ir; + if (!fClipRect.intersect(clipBounds)) { + SkASSERT(0); + fClipRect.setEmpty(); + } + + // For valgrind, write 1 extra byte at the end so we don't read + // uninitialized memory. See comment in add_aa_span and fStorage[]. + memset(fStorage, 0, fMask.fBounds.height() * fMask.fRowBytes + 1); +} + +static void add_aa_span(uint8_t* alpha, U8CPU startAlpha) { + /* I should be able to just add alpha[x] + startAlpha. + However, if the trailing edge of the previous span and the leading + edge of the current span round to the same super-sampled x value, + I might overflow to 256 with this add, hence the funny subtract. + */ + unsigned tmp = *alpha + startAlpha; + SkASSERT(tmp <= 256); + *alpha = SkToU8(tmp - (tmp >> 8)); +} + +static inline uint32_t quadplicate_byte(U8CPU value) { + uint32_t pair = (value << 8) | value; + return (pair << 16) | pair; +} + +// Perform this tricky subtract, to avoid overflowing to 256. Our caller should +// only ever call us with at most enough to hit 256 (never larger), so it is +// enough to just subtract the high-bit. Actually clamping with a branch would +// be slower (e.g. if (tmp > 255) tmp = 255;) +// +static inline void saturated_add(uint8_t* ptr, U8CPU add) { + unsigned tmp = *ptr + add; + SkASSERT(tmp <= 256); + *ptr = SkToU8(tmp - (tmp >> 8)); +} + +// minimum count before we want to setup an inner loop, adding 4-at-a-time +#define MIN_COUNT_FOR_QUAD_LOOP 16 + +static void add_aa_span(uint8_t* alpha, U8CPU startAlpha, int middleCount, + U8CPU stopAlpha, U8CPU maxValue) { + SkASSERT(middleCount >= 0); + + saturated_add(alpha, startAlpha); + alpha += 1; + + if (middleCount >= MIN_COUNT_FOR_QUAD_LOOP) { + // loop until we're quad-byte aligned + while (reinterpret_cast(alpha) & 0x3) { + alpha[0] = SkToU8(alpha[0] + maxValue); + alpha += 1; + middleCount -= 1; + } + + int bigCount = middleCount >> 2; + uint32_t* qptr = reinterpret_cast(alpha); + uint32_t qval = quadplicate_byte(maxValue); + do { + *qptr++ += qval; + } while (--bigCount > 0); + + middleCount &= 3; + alpha = reinterpret_cast (qptr); + // fall through to the following while-loop + } + + while (--middleCount >= 0) { + alpha[0] = SkToU8(alpha[0] + maxValue); + alpha += 1; + } + + // potentially this can be off the end of our "legal" alpha values, but that + // only happens if stopAlpha is also 0. Rather than test for stopAlpha != 0 + // every time (slow), we just do it, and ensure that we've allocated extra space + // (see the + 1 comment in fStorage[] + saturated_add(alpha, stopAlpha); +} + +void MaskSuperBlitter::blitH(int x, int y, int width) { + int iy = (y >> SHIFT); + + SkASSERT(iy >= fMask.fBounds.fTop && iy < fMask.fBounds.fBottom); + iy -= fMask.fBounds.fTop; // make it relative to 0 + + // This should never happen, but it does. Until the true cause is + // discovered, let's skip this span instead of crashing. + // See http://crbug.com/17569. + if (iy < 0) { + return; + } + +#ifdef SK_DEBUG + { + int ix = x >> SHIFT; + SkASSERT(ix >= fMask.fBounds.fLeft && ix < fMask.fBounds.fRight); + } +#endif + + x -= SkLeftShift(fMask.fBounds.fLeft, SHIFT); + + // hack, until I figure out why my cubics (I think) go beyond the bounds + if (x < 0) { + width += x; + x = 0; + } + + uint8_t* row = fMask.fImage + iy * fMask.fRowBytes + (x >> SHIFT); + + int start = x; + int stop = x + width; + + SkASSERT(start >= 0 && stop > start); + int fb = start & MASK; + int fe = stop & MASK; + int n = (stop >> SHIFT) - (start >> SHIFT) - 1; + + + if (n < 0) { + SkASSERT(row >= fMask.fImage); + SkASSERT(row < fMask.fImage + kMAX_STORAGE + 1); + add_aa_span(row, coverage_to_partial_alpha(fe - fb)); + } else { + fb = SCALE - fb; + SkASSERT(row >= fMask.fImage); + SkASSERT(row + n + 1 < fMask.fImage + kMAX_STORAGE + 1); + add_aa_span(row, coverage_to_partial_alpha(fb), + n, coverage_to_partial_alpha(fe), + (1 << (8 - SHIFT)) - (((y & MASK) + 1) >> SHIFT)); + } + +#ifdef SK_DEBUG + fCurrX = x + width; +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +static SkIRect safeRoundOut(const SkRect& src) { + // roundOut will pin huge floats to max/min int + SkIRect dst = src.roundOut(); + + // intersect with a smaller huge rect, so the rect will not be considered empty for being + // too large. e.g. { -SK_MaxS32 ... SK_MaxS32 } is considered empty because its width + // exceeds signed 32bit. + const int32_t limit = SK_MaxS32 >> SK_SUPERSAMPLE_SHIFT; + (void)dst.intersect({ -limit, -limit, limit, limit}); + + return dst; +} + +constexpr int kSampleSize = 8; +#if !defined(SK_DISABLE_AAA) + constexpr SkScalar kComplexityThreshold = 0.25; +#endif + +static void compute_complexity(const SkPath& path, SkScalar& avgLength, SkScalar& complexity) { + int n = path.countPoints(); + if (n < kSampleSize || path.getBounds().isEmpty()) { + // set to invalid value to indicate that we failed to compute + avgLength = complexity = -1; + return; + } + + SkScalar sumLength = 0; + SkPoint lastPoint = path.getPoint(0); + for(int i = 1; i < kSampleSize; ++i) { + SkPoint point = path.getPoint(i); + sumLength += SkPoint::Distance(lastPoint, point); + lastPoint = point; + } + avgLength = sumLength / (kSampleSize - 1); + + auto sqr = [](SkScalar x) { return x*x; }; + + SkScalar diagonalSqr = sqr(path.getBounds().width()) + sqr(path.getBounds().height()); + + // If the path consists of random line segments, the number of intersections should be + // proportional to this. + SkScalar intersections = sk_ieee_float_divide(sqr(n) * sqr(avgLength), diagonalSqr); + + // The number of intersections per scanline should be proportional to this number. + complexity = sk_ieee_float_divide(intersections, path.getBounds().height()); + + if (sk_float_isnan(complexity)) { // it may be possible to have 0.0 / 0.0; inf is fine for us. + complexity = -1; + } +} + +static bool ShouldUseAAA(const SkPath& path, SkScalar avgLength, SkScalar complexity) { +#if defined(SK_DISABLE_AAA) + return false; +#else + if (gSkForceAnalyticAA) { + return true; + } + if (!gSkUseAnalyticAA) { + return false; + } + if (path.isRect(nullptr)) { + return true; + } + + #ifdef SK_SUPPORT_LEGACY_AAA_CHOICE + const SkRect& bounds = path.getBounds(); + // When the path have so many points compared to the size of its + // bounds/resolution, it indicates that the path is not quite smooth in + // the current resolution: the expected number of turning points in + // every pixel row/column is significantly greater than zero. Hence + // Aanlytic AA is not likely to produce visible quality improvements, + // and Analytic AA might be slower than supersampling. + return path.countPoints() < SkTMax(bounds.width(), bounds.height()) / 2 - 10; + #else + if (path.countPoints() >= path.getBounds().height()) { + // SAA is faster than AAA in this case even if there are no + // intersections because AAA will have too many scan lines. See + // skbug.com/8272 + return false; + } + // We will use AAA if the number of verbs < kSampleSize and therefore complexity < 0 + return complexity < kComplexityThreshold; + #endif +#endif +} + +void SkScan::SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir, + const SkIRect& clipBounds, bool forceRLE) { + bool containedInClip = clipBounds.contains(ir); + bool isInverse = path.isInverseFillType(); + + // MaskSuperBlitter can't handle drawing outside of ir, so we can't use it + // if we're an inverse filltype + if (!isInverse && MaskSuperBlitter::CanHandleRect(ir) && !forceRLE) { + MaskSuperBlitter superBlit(blitter, ir, clipBounds, isInverse); + SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop); + sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip); + } else { + SuperBlitter superBlit(blitter, ir, clipBounds, isInverse); + sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip); + } +} + +static int overflows_short_shift(int value, int shift) { + const int s = 16 + shift; + return (SkLeftShift(value, s) >> s) - value; +} + +/** + Would any of the coordinates of this rectangle not fit in a short, + when left-shifted by shift? +*/ +static int rect_overflows_short_shift(SkIRect rect, int shift) { + SkASSERT(!overflows_short_shift(8191, shift)); + SkASSERT(overflows_short_shift(8192, shift)); + SkASSERT(!overflows_short_shift(32767, 0)); + SkASSERT(overflows_short_shift(32768, 0)); + + // Since we expect these to succeed, we bit-or together + // for a tiny extra bit of speed. + return overflows_short_shift(rect.fLeft, shift) | + overflows_short_shift(rect.fRight, shift) | + overflows_short_shift(rect.fTop, shift) | + overflows_short_shift(rect.fBottom, shift); +} + +void SkScan::AntiFillPath(const SkPath& path, const SkRegion& origClip, + SkBlitter* blitter, bool forceRLE) { + if (origClip.isEmpty()) { + return; + } + + const bool isInverse = path.isInverseFillType(); + SkIRect ir = safeRoundOut(path.getBounds()); + if (ir.isEmpty()) { + if (isInverse) { + blitter->blitRegion(origClip); + } + return; + } + + // If the intersection of the path bounds and the clip bounds + // will overflow 32767 when << by SHIFT, we can't supersample, + // so draw without antialiasing. + SkIRect clippedIR; + if (isInverse) { + // If the path is an inverse fill, it's going to fill the entire + // clip, and we care whether the entire clip exceeds our limits. + clippedIR = origClip.getBounds(); + } else { + if (!clippedIR.intersect(ir, origClip.getBounds())) { + return; + } + } + if (rect_overflows_short_shift(clippedIR, SHIFT)) { + SkScan::FillPath(path, origClip, blitter); + return; + } + + // Our antialiasing can't handle a clip larger than 32767, so we restrict + // the clip to that limit here. (the runs[] uses int16_t for its index). + // + // A more general solution (one that could also eliminate the need to + // disable aa based on ir bounds (see overflows_short_shift) would be + // to tile the clip/target... + SkRegion tmpClipStorage; + const SkRegion* clipRgn = &origClip; + { + static const int32_t kMaxClipCoord = 32767; + const SkIRect& bounds = origClip.getBounds(); + if (bounds.fRight > kMaxClipCoord || bounds.fBottom > kMaxClipCoord) { + SkIRect limit = { 0, 0, kMaxClipCoord, kMaxClipCoord }; + tmpClipStorage.op(origClip, limit, SkRegion::kIntersect_Op); + clipRgn = &tmpClipStorage; + } + } + // for here down, use clipRgn, not origClip + + SkScanClipper clipper(blitter, clipRgn, ir); + + if (clipper.getBlitter() == nullptr) { // clipped out + if (isInverse) { + blitter->blitRegion(*clipRgn); + } + return; + } + + SkASSERT(clipper.getClipRect() == nullptr || + *clipper.getClipRect() == clipRgn->getBounds()); + + // now use the (possibly wrapped) blitter + blitter = clipper.getBlitter(); + + if (isInverse) { + sk_blit_above(blitter, ir, *clipRgn); + } + + SkScalar avgLength, complexity; + compute_complexity(path, avgLength, complexity); + + if (ShouldUseAAA(path, avgLength, complexity)) { + // Do not use AAA if path is too complicated: + // there won't be any speedup or significant visual improvement. + SkScan::AAAFillPath(path, blitter, ir, clipRgn->getBounds(), forceRLE); + } else { + SkScan::SAAFillPath(path, blitter, ir, clipRgn->getBounds(), forceRLE); + } + + if (isInverse) { + sk_blit_below(blitter, ir, *clipRgn); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkRasterClip.h" + +void SkScan::FillPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + if (clip.isEmpty() || !path.isFinite()) { + return; + } + + if (clip.isBW()) { + FillPath(path, clip.bwRgn(), blitter); + } else { + SkRegion tmp; + SkAAClipBlitter aaBlitter; + + tmp.setRect(clip.getBounds()); + aaBlitter.init(blitter, &clip.aaRgn()); + SkScan::FillPath(path, tmp, &aaBlitter); + } +} + +void SkScan::AntiFillPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + if (clip.isEmpty() || !path.isFinite()) { + return; + } + + if (clip.isBW()) { + AntiFillPath(path, clip.bwRgn(), blitter, false); + } else { + SkRegion tmp; + SkAAClipBlitter aaBlitter; + + tmp.setRect(clip.getBounds()); + aaBlitter.init(blitter, &clip.aaRgn()); + AntiFillPath(path, tmp, &aaBlitter, true); // SkAAClipBlitter can blitMask, why forceRLE? + } +} diff --git a/gfx/skia/skia/src/core/SkScan_Antihair.cpp b/gfx/skia/skia/src/core/SkScan_Antihair.cpp new file mode 100644 index 0000000000..feb96f2470 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan_Antihair.cpp @@ -0,0 +1,1009 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkScan.h" + +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkFDot6.h" +#include "src/core/SkLineClipper.h" +#include "src/core/SkRasterClip.h" + +#include + +/* Our attempt to compute the worst case "bounds" for the horizontal and + vertical cases has some numerical bug in it, and we sometimes undervalue + our extends. The bug is that when this happens, we will set the clip to + nullptr (for speed), and thus draw outside of the clip by a pixel, which might + only look bad, but it might also access memory outside of the valid range + allcoated for the device bitmap. + + This define enables our fix to outset our "bounds" by 1, thus avoiding the + chance of the bug, but at the cost of sometimes taking the rectblitter + case (i.e. not setting the clip to nullptr) when we might not actually need + to. If we can improve/fix the actual calculations, then we can remove this + step. + */ +#define OUTSET_BEFORE_CLIP_TEST true + +#define HLINE_STACK_BUFFER 100 + +static inline int SmallDot6Scale(int value, int dot6) { + SkASSERT((int16_t)value == value); + SkASSERT((unsigned)dot6 <= 64); + return (value * dot6) >> 6; +} + +//#define TEST_GAMMA + +#ifdef TEST_GAMMA + static uint8_t gGammaTable[256]; + #define ApplyGamma(table, alpha) (table)[alpha] + + static void build_gamma_table() { + static bool gInit = false; + + if (gInit == false) { + for (int i = 0; i < 256; i++) { + SkFixed n = i * 257; + n += n >> 15; + SkASSERT(n >= 0 && n <= SK_Fixed1); + n = SkFixedSqrt(n); + n = n * 255 >> 16; + // SkDebugf("morph %d -> %d\n", i, n); + gGammaTable[i] = SkToU8(n); + } + gInit = true; + } + } +#else + #define ApplyGamma(table, alpha) SkToU8(alpha) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +static void call_hline_blitter(SkBlitter* blitter, int x, int y, int count, + U8CPU alpha) { + SkASSERT(count > 0); + + int16_t runs[HLINE_STACK_BUFFER + 1]; + uint8_t aa[HLINE_STACK_BUFFER]; + + aa[0] = ApplyGamma(gGammaTable, alpha); + do { + int n = count; + if (n > HLINE_STACK_BUFFER) { + n = HLINE_STACK_BUFFER; + } + runs[0] = SkToS16(n); + runs[n] = 0; + blitter->blitAntiH(x, y, aa, runs); + x += n; + count -= n; + } while (count > 0); +} + +class SkAntiHairBlitter { +public: + SkAntiHairBlitter() : fBlitter(nullptr) {} + virtual ~SkAntiHairBlitter() {} + + SkBlitter* getBlitter() const { return fBlitter; } + + void setup(SkBlitter* blitter) { + fBlitter = blitter; + } + + virtual SkFixed drawCap(int x, SkFixed fy, SkFixed slope, int mod64) = 0; + virtual SkFixed drawLine(int x, int stopx, SkFixed fy, SkFixed slope) = 0; + +private: + SkBlitter* fBlitter; +}; + +class HLine_SkAntiHairBlitter : public SkAntiHairBlitter { +public: + SkFixed drawCap(int x, SkFixed fy, SkFixed slope, int mod64) override { + fy += SK_Fixed1/2; + + int y = fy >> 16; + uint8_t a = (uint8_t)((fy >> 8) & 0xFF); + + // lower line + unsigned ma = SmallDot6Scale(a, mod64); + if (ma) { + call_hline_blitter(this->getBlitter(), x, y, 1, ma); + } + + // upper line + ma = SmallDot6Scale(255 - a, mod64); + if (ma) { + call_hline_blitter(this->getBlitter(), x, y - 1, 1, ma); + } + + return fy - SK_Fixed1/2; + } + + virtual SkFixed drawLine(int x, int stopx, SkFixed fy, + SkFixed slope) override { + SkASSERT(x < stopx); + int count = stopx - x; + fy += SK_Fixed1/2; + + int y = fy >> 16; + uint8_t a = (uint8_t)((fy >> 8) & 0xFF); + + // lower line + if (a) { + call_hline_blitter(this->getBlitter(), x, y, count, a); + } + + // upper line + a = 255 - a; + if (a) { + call_hline_blitter(this->getBlitter(), x, y - 1, count, a); + } + + return fy - SK_Fixed1/2; + } +}; + +class Horish_SkAntiHairBlitter : public SkAntiHairBlitter { +public: + SkFixed drawCap(int x, SkFixed fy, SkFixed dy, int mod64) override { + fy += SK_Fixed1/2; + + int lower_y = fy >> 16; + uint8_t a = (uint8_t)((fy >> 8) & 0xFF); + unsigned a0 = SmallDot6Scale(255 - a, mod64); + unsigned a1 = SmallDot6Scale(a, mod64); + this->getBlitter()->blitAntiV2(x, lower_y - 1, a0, a1); + + return fy + dy - SK_Fixed1/2; + } + + SkFixed drawLine(int x, int stopx, SkFixed fy, SkFixed dy) override { + SkASSERT(x < stopx); + + fy += SK_Fixed1/2; + SkBlitter* blitter = this->getBlitter(); + do { + int lower_y = fy >> 16; + uint8_t a = (uint8_t)((fy >> 8) & 0xFF); + blitter->blitAntiV2(x, lower_y - 1, 255 - a, a); + fy += dy; + } while (++x < stopx); + + return fy - SK_Fixed1/2; + } +}; + +class VLine_SkAntiHairBlitter : public SkAntiHairBlitter { +public: + SkFixed drawCap(int y, SkFixed fx, SkFixed dx, int mod64) override { + SkASSERT(0 == dx); + fx += SK_Fixed1/2; + + int x = fx >> 16; + int a = (uint8_t)((fx >> 8) & 0xFF); + + unsigned ma = SmallDot6Scale(a, mod64); + if (ma) { + this->getBlitter()->blitV(x, y, 1, ma); + } + ma = SmallDot6Scale(255 - a, mod64); + if (ma) { + this->getBlitter()->blitV(x - 1, y, 1, ma); + } + + return fx - SK_Fixed1/2; + } + + SkFixed drawLine(int y, int stopy, SkFixed fx, SkFixed dx) override { + SkASSERT(y < stopy); + SkASSERT(0 == dx); + fx += SK_Fixed1/2; + + int x = fx >> 16; + int a = (uint8_t)((fx >> 8) & 0xFF); + + if (a) { + this->getBlitter()->blitV(x, y, stopy - y, a); + } + a = 255 - a; + if (a) { + this->getBlitter()->blitV(x - 1, y, stopy - y, a); + } + + return fx - SK_Fixed1/2; + } +}; + +class Vertish_SkAntiHairBlitter : public SkAntiHairBlitter { +public: + SkFixed drawCap(int y, SkFixed fx, SkFixed dx, int mod64) override { + fx += SK_Fixed1/2; + + int x = fx >> 16; + uint8_t a = (uint8_t)((fx >> 8) & 0xFF); + this->getBlitter()->blitAntiH2(x - 1, y, + SmallDot6Scale(255 - a, mod64), SmallDot6Scale(a, mod64)); + + return fx + dx - SK_Fixed1/2; + } + + SkFixed drawLine(int y, int stopy, SkFixed fx, SkFixed dx) override { + SkASSERT(y < stopy); + fx += SK_Fixed1/2; + do { + int x = fx >> 16; + uint8_t a = (uint8_t)((fx >> 8) & 0xFF); + this->getBlitter()->blitAntiH2(x - 1, y, 255 - a, a); + fx += dx; + } while (++y < stopy); + + return fx - SK_Fixed1/2; + } +}; + +static inline SkFixed fastfixdiv(SkFDot6 a, SkFDot6 b) { + SkASSERT((SkLeftShift(a, 16) >> 16) == a); + SkASSERT(b != 0); + return SkLeftShift(a, 16) / b; +} + +#define SkBITCOUNT(x) (sizeof(x) << 3) + +#if 1 +// returns high-bit set iff x==0x8000... +static inline int bad_int(int x) { + return x & -x; +} + +static int any_bad_ints(int a, int b, int c, int d) { + return (bad_int(a) | bad_int(b) | bad_int(c) | bad_int(d)) >> (SkBITCOUNT(int) - 1); +} +#else +static inline int good_int(int x) { + return x ^ (1 << (SkBITCOUNT(x) - 1)); +} + +static int any_bad_ints(int a, int b, int c, int d) { + return !(good_int(a) & good_int(b) & good_int(c) & good_int(d)); +} +#endif + +#ifdef SK_DEBUG +static bool canConvertFDot6ToFixed(SkFDot6 x) { + const int maxDot6 = SK_MaxS32 >> (16 - 6); + return SkAbs32(x) <= maxDot6; +} +#endif + +/* + * We want the fractional part of ordinate, but we want multiples of 64 to + * return 64, not 0, so we can't just say (ordinate & 63). + * We basically want to compute those bits, and if they're 0, return 64. + * We can do that w/o a branch with an extra sub and add. + */ +static int contribution_64(SkFDot6 ordinate) { +#if 0 + int result = ordinate & 63; + if (0 == result) { + result = 64; + } +#else + int result = ((ordinate - 1) & 63) + 1; +#endif + SkASSERT(result > 0 && result <= 64); + return result; +} + +static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1, + const SkIRect* clip, SkBlitter* blitter) { + // check for integer NaN (0x80000000) which we can't handle (can't negate it) + // It appears typically from a huge float (inf or nan) being converted to int. + // If we see it, just don't draw. + if (any_bad_ints(x0, y0, x1, y1)) { + return; + } + + // The caller must clip the line to [-32767.0 ... 32767.0] ahead of time + // (in dot6 format) + SkASSERT(canConvertFDot6ToFixed(x0)); + SkASSERT(canConvertFDot6ToFixed(y0)); + SkASSERT(canConvertFDot6ToFixed(x1)); + SkASSERT(canConvertFDot6ToFixed(y1)); + + if (SkAbs32(x1 - x0) > SkIntToFDot6(511) || SkAbs32(y1 - y0) > SkIntToFDot6(511)) { + /* instead of (x0 + x1) >> 1, we shift each separately. This is less + precise, but avoids overflowing the intermediate result if the + values are huge. A better fix might be to clip the original pts + directly (i.e. do the divide), so we don't spend time subdividing + huge lines at all. + */ + int hx = (x0 >> 1) + (x1 >> 1); + int hy = (y0 >> 1) + (y1 >> 1); + do_anti_hairline(x0, y0, hx, hy, clip, blitter); + do_anti_hairline(hx, hy, x1, y1, clip, blitter); + return; + } + + int scaleStart, scaleStop; + int istart, istop; + SkFixed fstart, slope; + + HLine_SkAntiHairBlitter hline_blitter; + Horish_SkAntiHairBlitter horish_blitter; + VLine_SkAntiHairBlitter vline_blitter; + Vertish_SkAntiHairBlitter vertish_blitter; + SkAntiHairBlitter* hairBlitter = nullptr; + + if (SkAbs32(x1 - x0) > SkAbs32(y1 - y0)) { // mostly horizontal + if (x0 > x1) { // we want to go left-to-right + using std::swap; + swap(x0, x1); + swap(y0, y1); + } + + istart = SkFDot6Floor(x0); + istop = SkFDot6Ceil(x1); + fstart = SkFDot6ToFixed(y0); + if (y0 == y1) { // completely horizontal, take fast case + slope = 0; + hairBlitter = &hline_blitter; + } else { + slope = fastfixdiv(y1 - y0, x1 - x0); + SkASSERT(slope >= -SK_Fixed1 && slope <= SK_Fixed1); + fstart += (slope * (32 - (x0 & 63)) + 32) >> 6; + hairBlitter = &horish_blitter; + } + + SkASSERT(istop > istart); + if (istop - istart == 1) { + // we are within a single pixel + scaleStart = x1 - x0; + SkASSERT(scaleStart >= 0 && scaleStart <= 64); + scaleStop = 0; + } else { + scaleStart = 64 - (x0 & 63); + scaleStop = x1 & 63; + } + + if (clip){ + if (istart >= clip->fRight || istop <= clip->fLeft) { + return; + } + if (istart < clip->fLeft) { + fstart += slope * (clip->fLeft - istart); + istart = clip->fLeft; + scaleStart = 64; + if (istop - istart == 1) { + // we are within a single pixel + scaleStart = contribution_64(x1); + scaleStop = 0; + } + } + if (istop > clip->fRight) { + istop = clip->fRight; + scaleStop = 0; // so we don't draw this last column + } + + SkASSERT(istart <= istop); + if (istart == istop) { + return; + } + // now test if our Y values are completely inside the clip + int top, bottom; + if (slope >= 0) { // T2B + top = SkFixedFloorToInt(fstart - SK_FixedHalf); + bottom = SkFixedCeilToInt(fstart + (istop - istart - 1) * slope + SK_FixedHalf); + } else { // B2T + bottom = SkFixedCeilToInt(fstart + SK_FixedHalf); + top = SkFixedFloorToInt(fstart + (istop - istart - 1) * slope - SK_FixedHalf); + } +#ifdef OUTSET_BEFORE_CLIP_TEST + top -= 1; + bottom += 1; +#endif + if (top >= clip->fBottom || bottom <= clip->fTop) { + return; + } + if (clip->fTop <= top && clip->fBottom >= bottom) { + clip = nullptr; + } + } + } else { // mostly vertical + if (y0 > y1) { // we want to go top-to-bottom + using std::swap; + swap(x0, x1); + swap(y0, y1); + } + + istart = SkFDot6Floor(y0); + istop = SkFDot6Ceil(y1); + fstart = SkFDot6ToFixed(x0); + if (x0 == x1) { + if (y0 == y1) { // are we zero length? + return; // nothing to do + } + slope = 0; + hairBlitter = &vline_blitter; + } else { + slope = fastfixdiv(x1 - x0, y1 - y0); + SkASSERT(slope <= SK_Fixed1 && slope >= -SK_Fixed1); + fstart += (slope * (32 - (y0 & 63)) + 32) >> 6; + hairBlitter = &vertish_blitter; + } + + SkASSERT(istop > istart); + if (istop - istart == 1) { + // we are within a single pixel + scaleStart = y1 - y0; + SkASSERT(scaleStart >= 0 && scaleStart <= 64); + scaleStop = 0; + } else { + scaleStart = 64 - (y0 & 63); + scaleStop = y1 & 63; + } + + if (clip) { + if (istart >= clip->fBottom || istop <= clip->fTop) { + return; + } + if (istart < clip->fTop) { + fstart += slope * (clip->fTop - istart); + istart = clip->fTop; + scaleStart = 64; + if (istop - istart == 1) { + // we are within a single pixel + scaleStart = contribution_64(y1); + scaleStop = 0; + } + } + if (istop > clip->fBottom) { + istop = clip->fBottom; + scaleStop = 0; // so we don't draw this last row + } + + SkASSERT(istart <= istop); + if (istart == istop) + return; + + // now test if our X values are completely inside the clip + int left, right; + if (slope >= 0) { // L2R + left = SkFixedFloorToInt(fstart - SK_FixedHalf); + right = SkFixedCeilToInt(fstart + (istop - istart - 1) * slope + SK_FixedHalf); + } else { // R2L + right = SkFixedCeilToInt(fstart + SK_FixedHalf); + left = SkFixedFloorToInt(fstart + (istop - istart - 1) * slope - SK_FixedHalf); + } +#ifdef OUTSET_BEFORE_CLIP_TEST + left -= 1; + right += 1; +#endif + if (left >= clip->fRight || right <= clip->fLeft) { + return; + } + if (clip->fLeft <= left && clip->fRight >= right) { + clip = nullptr; + } + } + } + + SkRectClipBlitter rectClipper; + if (clip) { + rectClipper.init(blitter, *clip); + blitter = &rectClipper; + } + + SkASSERT(hairBlitter); + hairBlitter->setup(blitter); + +#ifdef SK_DEBUG + if (scaleStart > 0 && scaleStop > 0) { + // be sure we don't draw twice in the same pixel + SkASSERT(istart < istop - 1); + } +#endif + + fstart = hairBlitter->drawCap(istart, fstart, slope, scaleStart); + istart += 1; + int fullSpans = istop - istart - (scaleStop > 0); + if (fullSpans > 0) { + fstart = hairBlitter->drawLine(istart, istart + fullSpans, fstart, slope); + } + if (scaleStop > 0) { + hairBlitter->drawCap(istop - 1, fstart, slope, scaleStop); + } +} + +void SkScan::AntiHairLineRgn(const SkPoint array[], int arrayCount, const SkRegion* clip, + SkBlitter* blitter) { + if (clip && clip->isEmpty()) { + return; + } + + SkASSERT(clip == nullptr || !clip->getBounds().isEmpty()); + +#ifdef TEST_GAMMA + build_gamma_table(); +#endif + + const SkScalar max = SkIntToScalar(32767); + const SkRect fixedBounds = SkRect::MakeLTRB(-max, -max, max, max); + + SkRect clipBounds; + if (clip) { + clipBounds.set(clip->getBounds()); + /* We perform integral clipping later on, but we do a scalar clip first + to ensure that our coordinates are expressible in fixed/integers. + + antialiased hairlines can draw up to 1/2 of a pixel outside of + their bounds, so we need to outset the clip before calling the + clipper. To make the numerics safer, we outset by a whole pixel, + since the 1/2 pixel boundary is important to the antihair blitter, + we don't want to risk numerical fate by chopping on that edge. + */ + clipBounds.outset(SK_Scalar1, SK_Scalar1); + } + + for (int i = 0; i < arrayCount - 1; ++i) { + SkPoint pts[2]; + + // We have to pre-clip the line to fit in a SkFixed, so we just chop + // the line. TODO find a way to actually draw beyond that range. + if (!SkLineClipper::IntersectLine(&array[i], fixedBounds, pts)) { + continue; + } + + if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) { + continue; + } + + SkFDot6 x0 = SkScalarToFDot6(pts[0].fX); + SkFDot6 y0 = SkScalarToFDot6(pts[0].fY); + SkFDot6 x1 = SkScalarToFDot6(pts[1].fX); + SkFDot6 y1 = SkScalarToFDot6(pts[1].fY); + + if (clip) { + SkFDot6 left = SkMin32(x0, x1); + SkFDot6 top = SkMin32(y0, y1); + SkFDot6 right = SkMax32(x0, x1); + SkFDot6 bottom = SkMax32(y0, y1); + SkIRect ir; + + ir.setLTRB(SkFDot6Floor(left) - 1, + SkFDot6Floor(top) - 1, + SkFDot6Ceil(right) + 1, + SkFDot6Ceil(bottom) + 1); + + if (clip->quickReject(ir)) { + continue; + } + if (!clip->quickContains(ir)) { + SkRegion::Cliperator iter(*clip, ir); + const SkIRect* r = &iter.rect(); + + while (!iter.done()) { + do_anti_hairline(x0, y0, x1, y1, r, blitter); + iter.next(); + } + continue; + } + // fall through to no-clip case + } + do_anti_hairline(x0, y0, x1, y1, nullptr, blitter); + } +} + +void SkScan::AntiHairRect(const SkRect& rect, const SkRasterClip& clip, + SkBlitter* blitter) { + SkPoint pts[5]; + + pts[0].set(rect.fLeft, rect.fTop); + pts[1].set(rect.fRight, rect.fTop); + pts[2].set(rect.fRight, rect.fBottom); + pts[3].set(rect.fLeft, rect.fBottom); + pts[4] = pts[0]; + SkScan::AntiHairLine(pts, 5, clip, blitter); +} + +/////////////////////////////////////////////////////////////////////////////// + +typedef int FDot8; // 24.8 integer fixed point + +static inline FDot8 SkFixedToFDot8(SkFixed x) { + return (x + 0x80) >> 8; +} + +static void do_scanline(FDot8 L, int top, FDot8 R, U8CPU alpha, + SkBlitter* blitter) { + SkASSERT(L < R); + + if ((L >> 8) == ((R - 1) >> 8)) { // 1x1 pixel + blitter->blitV(L >> 8, top, 1, SkAlphaMul(alpha, R - L)); + return; + } + + int left = L >> 8; + + if (L & 0xFF) { + blitter->blitV(left, top, 1, SkAlphaMul(alpha, 256 - (L & 0xFF))); + left += 1; + } + + int rite = R >> 8; + int width = rite - left; + if (width > 0) { + call_hline_blitter(blitter, left, top, width, alpha); + } + if (R & 0xFF) { + blitter->blitV(rite, top, 1, SkAlphaMul(alpha, R & 0xFF)); + } +} + +static void antifilldot8(FDot8 L, FDot8 T, FDot8 R, FDot8 B, SkBlitter* blitter, + bool fillInner) { + // check for empty now that we're in our reduced precision space + if (L >= R || T >= B) { + return; + } + int top = T >> 8; + if (top == ((B - 1) >> 8)) { // just one scanline high + do_scanline(L, top, R, B - T - 1, blitter); + return; + } + + if (T & 0xFF) { + do_scanline(L, top, R, 256 - (T & 0xFF), blitter); + top += 1; + } + + int bot = B >> 8; + int height = bot - top; + if (height > 0) { + int left = L >> 8; + if (left == ((R - 1) >> 8)) { // just 1-pixel wide + blitter->blitV(left, top, height, R - L - 1); + } else { + if (L & 0xFF) { + blitter->blitV(left, top, height, 256 - (L & 0xFF)); + left += 1; + } + int rite = R >> 8; + int width = rite - left; + if (width > 0 && fillInner) { + blitter->blitRect(left, top, width, height); + } + if (R & 0xFF) { + blitter->blitV(rite, top, height, R & 0xFF); + } + } + } + + if (B & 0xFF) { + do_scanline(L, bot, R, B & 0xFF, blitter); + } +} + +static void antifillrect(const SkXRect& xr, SkBlitter* blitter) { + antifilldot8(SkFixedToFDot8(xr.fLeft), SkFixedToFDot8(xr.fTop), + SkFixedToFDot8(xr.fRight), SkFixedToFDot8(xr.fBottom), + blitter, true); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkScan::AntiFillXRect(const SkXRect& xr, const SkRegion* clip, + SkBlitter* blitter) { + if (nullptr == clip) { + antifillrect(xr, blitter); + } else { + SkIRect outerBounds; + XRect_roundOut(xr, &outerBounds); + + if (clip->isRect()) { + const SkIRect& clipBounds = clip->getBounds(); + + if (clipBounds.contains(outerBounds)) { + antifillrect(xr, blitter); + } else { + SkXRect tmpR; + // this keeps our original edges fractional + XRect_set(&tmpR, clipBounds); + if (tmpR.intersect(xr)) { + antifillrect(tmpR, blitter); + } + } + } else { + SkRegion::Cliperator clipper(*clip, outerBounds); + const SkIRect& rr = clipper.rect(); + + while (!clipper.done()) { + SkXRect tmpR; + + // this keeps our original edges fractional + XRect_set(&tmpR, rr); + if (tmpR.intersect(xr)) { + antifillrect(tmpR, blitter); + } + clipper.next(); + } + } + } +} + +void SkScan::AntiFillXRect(const SkXRect& xr, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isBW()) { + AntiFillXRect(xr, &clip.bwRgn(), blitter); + } else { + SkIRect outerBounds; + XRect_roundOut(xr, &outerBounds); + + if (clip.quickContains(outerBounds)) { + AntiFillXRect(xr, nullptr, blitter); + } else { + SkAAClipBlitterWrapper wrapper(clip, blitter); + AntiFillXRect(xr, &wrapper.getRgn(), wrapper.getBlitter()); + } + } +} + +/* This guy takes a float-rect, but with the key improvement that it has + already been clipped, so we know that it is safe to convert it into a + XRect (fixedpoint), as it won't overflow. +*/ +static void antifillrect(const SkRect& r, SkBlitter* blitter) { + SkXRect xr; + + XRect_set(&xr, r); + antifillrect(xr, blitter); +} + +/* We repeat the clipping logic of AntiFillXRect because the float rect might + overflow if we blindly converted it to an XRect. This sucks that we have to + repeat the clipping logic, but I don't see how to share the code/logic. + + We clip r (as needed) into one or more (smaller) float rects, and then pass + those to our version of antifillrect, which converts it into an XRect and + then calls the blit. +*/ +void SkScan::AntiFillRect(const SkRect& origR, const SkRegion* clip, + SkBlitter* blitter) { + if (clip) { + SkRect newR; + newR.set(clip->getBounds()); + if (!newR.intersect(origR)) { + return; + } + + const SkIRect outerBounds = newR.roundOut(); + + if (clip->isRect()) { + antifillrect(newR, blitter); + } else { + SkRegion::Cliperator clipper(*clip, outerBounds); + while (!clipper.done()) { + newR.set(clipper.rect()); + if (newR.intersect(origR)) { + antifillrect(newR, blitter); + } + clipper.next(); + } + } + } else { + antifillrect(origR, blitter); + } +} + +void SkScan::AntiFillRect(const SkRect& r, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isBW()) { + AntiFillRect(r, &clip.bwRgn(), blitter); + } else { + SkAAClipBlitterWrapper wrap(clip, blitter); + AntiFillRect(r, &wrap.getRgn(), wrap.getBlitter()); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#define SkAlphaMulRound(a, b) SkMulDiv255Round(a, b) + +// calls blitRect() if the rectangle is non-empty +static void fillcheckrect(int L, int T, int R, int B, SkBlitter* blitter) { + if (L < R && T < B) { + blitter->blitRect(L, T, R - L, B - T); + } +} + +static inline FDot8 SkScalarToFDot8(SkScalar x) { + return (int)(x * 256); +} + +static inline int FDot8Floor(FDot8 x) { + return x >> 8; +} + +static inline int FDot8Ceil(FDot8 x) { + return (x + 0xFF) >> 8; +} + +// 1 - (1 - a)*(1 - b) +static inline U8CPU InvAlphaMul(U8CPU a, U8CPU b) { + // need precise rounding (not just SkAlphaMul) so that values like + // a=228, b=252 don't overflow the result + return SkToU8(a + b - SkAlphaMulRound(a, b)); +} + +static void inner_scanline(FDot8 L, int top, FDot8 R, U8CPU alpha, + SkBlitter* blitter) { + SkASSERT(L < R); + + if ((L >> 8) == ((R - 1) >> 8)) { // 1x1 pixel + FDot8 widClamp = R - L; + // border case clamp 256 to 255 instead of going through call_hline_blitter + // see skbug/4406 + widClamp = widClamp - (widClamp >> 8); + blitter->blitV(L >> 8, top, 1, InvAlphaMul(alpha, widClamp)); + return; + } + + int left = L >> 8; + if (L & 0xFF) { + blitter->blitV(left, top, 1, InvAlphaMul(alpha, L & 0xFF)); + left += 1; + } + + int rite = R >> 8; + int width = rite - left; + if (width > 0) { + call_hline_blitter(blitter, left, top, width, alpha); + } + + if (R & 0xFF) { + blitter->blitV(rite, top, 1, InvAlphaMul(alpha, ~R & 0xFF)); + } +} + +static void innerstrokedot8(FDot8 L, FDot8 T, FDot8 R, FDot8 B, + SkBlitter* blitter) { + SkASSERT(L < R && T < B); + + int top = T >> 8; + if (top == ((B - 1) >> 8)) { // just one scanline high + // We want the inverse of B-T, since we're the inner-stroke + int alpha = 256 - (B - T); + if (alpha) { + inner_scanline(L, top, R, alpha, blitter); + } + return; + } + + if (T & 0xFF) { + inner_scanline(L, top, R, T & 0xFF, blitter); + top += 1; + } + + int bot = B >> 8; + int height = bot - top; + if (height > 0) { + if (L & 0xFF) { + blitter->blitV(L >> 8, top, height, L & 0xFF); + } + if (R & 0xFF) { + blitter->blitV(R >> 8, top, height, ~R & 0xFF); + } + } + + if (B & 0xFF) { + inner_scanline(L, bot, R, ~B & 0xFF, blitter); + } +} + +static inline void align_thin_stroke(FDot8& edge1, FDot8& edge2) { + SkASSERT(edge1 <= edge2); + + if (FDot8Floor(edge1) == FDot8Floor(edge2)) { + edge2 -= (edge1 & 0xFF); + edge1 &= ~0xFF; + } +} + +void SkScan::AntiFrameRect(const SkRect& r, const SkPoint& strokeSize, + const SkRegion* clip, SkBlitter* blitter) { + SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0); + + SkScalar rx = SkScalarHalf(strokeSize.fX); + SkScalar ry = SkScalarHalf(strokeSize.fY); + + // outset by the radius + FDot8 outerL = SkScalarToFDot8(r.fLeft - rx); + FDot8 outerT = SkScalarToFDot8(r.fTop - ry); + FDot8 outerR = SkScalarToFDot8(r.fRight + rx); + FDot8 outerB = SkScalarToFDot8(r.fBottom + ry); + + SkIRect outer; + // set outer to the outer rect of the outer section + outer.setLTRB(FDot8Floor(outerL), FDot8Floor(outerT), FDot8Ceil(outerR), FDot8Ceil(outerB)); + + SkBlitterClipper clipper; + if (clip) { + if (clip->quickReject(outer)) { + return; + } + if (!clip->contains(outer)) { + blitter = clipper.apply(blitter, clip, &outer); + } + // now we can ignore clip for the rest of the function + } + + // in case we lost a bit with diameter/2 + rx = strokeSize.fX - rx; + ry = strokeSize.fY - ry; + + // inset by the radius + FDot8 innerL = SkScalarToFDot8(r.fLeft + rx); + FDot8 innerT = SkScalarToFDot8(r.fTop + ry); + FDot8 innerR = SkScalarToFDot8(r.fRight - rx); + FDot8 innerB = SkScalarToFDot8(r.fBottom - ry); + + // For sub-unit strokes, tweak the hulls such that one of the edges coincides with the pixel + // edge. This ensures that the general rect stroking logic below + // a) doesn't blit the same scanline twice + // b) computes the correct coverage when both edges fall within the same pixel + if (strokeSize.fX < 1 || strokeSize.fY < 1) { + align_thin_stroke(outerL, innerL); + align_thin_stroke(outerT, innerT); + align_thin_stroke(innerR, outerR); + align_thin_stroke(innerB, outerB); + } + + // stroke the outer hull + antifilldot8(outerL, outerT, outerR, outerB, blitter, false); + + // set outer to the outer rect of the middle section + outer.setLTRB(FDot8Ceil(outerL), FDot8Ceil(outerT), FDot8Floor(outerR), FDot8Floor(outerB)); + + if (innerL >= innerR || innerT >= innerB) { + fillcheckrect(outer.fLeft, outer.fTop, outer.fRight, outer.fBottom, + blitter); + } else { + SkIRect inner; + // set inner to the inner rect of the middle section + inner.setLTRB(FDot8Floor(innerL), FDot8Floor(innerT), FDot8Ceil(innerR), FDot8Ceil(innerB)); + + // draw the frame in 4 pieces + fillcheckrect(outer.fLeft, outer.fTop, outer.fRight, inner.fTop, + blitter); + fillcheckrect(outer.fLeft, inner.fTop, inner.fLeft, inner.fBottom, + blitter); + fillcheckrect(inner.fRight, inner.fTop, outer.fRight, inner.fBottom, + blitter); + fillcheckrect(outer.fLeft, inner.fBottom, outer.fRight, outer.fBottom, + blitter); + + // now stroke the inner rect, which is similar to antifilldot8() except that + // it treats the fractional coordinates with the inverse bias (since its + // inner). + innerstrokedot8(innerL, innerT, innerR, innerB, blitter); + } +} + +void SkScan::AntiFrameRect(const SkRect& r, const SkPoint& strokeSize, + const SkRasterClip& clip, SkBlitter* blitter) { + if (clip.isBW()) { + AntiFrameRect(r, strokeSize, &clip.bwRgn(), blitter); + } else { + SkAAClipBlitterWrapper wrap(clip, blitter); + AntiFrameRect(r, strokeSize, &wrap.getRgn(), wrap.getBlitter()); + } +} diff --git a/gfx/skia/skia/src/core/SkScan_Hairline.cpp b/gfx/skia/skia/src/core/SkScan_Hairline.cpp new file mode 100644 index 0000000000..f9365cecb2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan_Hairline.cpp @@ -0,0 +1,732 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkFDot6.h" +#include "src/core/SkLineClipper.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkScan.h" + +#include + +static void horiline(int x, int stopx, SkFixed fy, SkFixed dy, + SkBlitter* blitter) { + SkASSERT(x < stopx); + + do { + blitter->blitH(x, fy >> 16, 1); + fy += dy; + } while (++x < stopx); +} + +static void vertline(int y, int stopy, SkFixed fx, SkFixed dx, + SkBlitter* blitter) { + SkASSERT(y < stopy); + + do { + blitter->blitH(fx >> 16, y, 1); + fx += dx; + } while (++y < stopy); +} + +#ifdef SK_DEBUG +static bool canConvertFDot6ToFixed(SkFDot6 x) { + const int maxDot6 = SK_MaxS32 >> (16 - 6); + return SkAbs32(x) <= maxDot6; +} +#endif + +void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion* clip, + SkBlitter* origBlitter) { + SkBlitterClipper clipper; + SkIRect clipR, ptsR; + + const SkScalar max = SkIntToScalar(32767); + const SkRect fixedBounds = SkRect::MakeLTRB(-max, -max, max, max); + + SkRect clipBounds; + if (clip) { + clipBounds.set(clip->getBounds()); + } + + for (int i = 0; i < arrayCount - 1; ++i) { + SkBlitter* blitter = origBlitter; + + SkPoint pts[2]; + + // We have to pre-clip the line to fit in a SkFixed, so we just chop + // the line. TODO find a way to actually draw beyond that range. + if (!SkLineClipper::IntersectLine(&array[i], fixedBounds, pts)) { + continue; + } + + // Perform a clip in scalar space, so we catch huge values which might + // be missed after we convert to SkFDot6 (overflow) + if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) { + continue; + } + + SkFDot6 x0 = SkScalarToFDot6(pts[0].fX); + SkFDot6 y0 = SkScalarToFDot6(pts[0].fY); + SkFDot6 x1 = SkScalarToFDot6(pts[1].fX); + SkFDot6 y1 = SkScalarToFDot6(pts[1].fY); + + SkASSERT(canConvertFDot6ToFixed(x0)); + SkASSERT(canConvertFDot6ToFixed(y0)); + SkASSERT(canConvertFDot6ToFixed(x1)); + SkASSERT(canConvertFDot6ToFixed(y1)); + + if (clip) { + // now perform clipping again, as the rounding to dot6 can wiggle us + // our rects are really dot6 rects, but since we've already used + // lineclipper, we know they will fit in 32bits (26.6) + const SkIRect& bounds = clip->getBounds(); + + clipR.setLTRB(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop), + SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom)); + ptsR.setLTRB(x0, y0, x1, y1); + ptsR.sort(); + + // outset the right and bottom, to account for how hairlines are + // actually drawn, which may hit the pixel to the right or below of + // the coordinate + ptsR.fRight += SK_FDot6One; + ptsR.fBottom += SK_FDot6One; + + if (!SkIRect::Intersects(ptsR, clipR)) { + continue; + } + if (!clip->isRect() || !clipR.contains(ptsR)) { + blitter = clipper.apply(origBlitter, clip); + } + } + + SkFDot6 dx = x1 - x0; + SkFDot6 dy = y1 - y0; + + if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal + if (x0 > x1) { // we want to go left-to-right + using std::swap; + swap(x0, x1); + swap(y0, y1); + } + int ix0 = SkFDot6Round(x0); + int ix1 = SkFDot6Round(x1); + if (ix0 == ix1) {// too short to draw + continue; + } + + SkFixed slope = SkFixedDiv(dy, dx); + SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6); + + horiline(ix0, ix1, startY, slope, blitter); + } else { // mostly vertical + if (y0 > y1) { // we want to go top-to-bottom + using std::swap; + swap(x0, x1); + swap(y0, y1); + } + int iy0 = SkFDot6Round(y0); + int iy1 = SkFDot6Round(y1); + if (iy0 == iy1) { // too short to draw + continue; + } + + SkFixed slope = SkFixedDiv(dx, dy); + SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6); + + vertline(iy0, iy1, startX, slope, blitter); + } + } +} + +// we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right +// and double-hit the top-left. +void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip, SkBlitter* blitter) { + SkAAClipBlitterWrapper wrapper; + SkBlitterClipper clipper; + // Create the enclosing bounds of the hairrect. i.e. we will stroke the interior of r. + SkIRect r = SkIRect::MakeLTRB(SkScalarFloorToInt(rect.fLeft), + SkScalarFloorToInt(rect.fTop), + SkScalarFloorToInt(rect.fRight + 1), + SkScalarFloorToInt(rect.fBottom + 1)); + + // Note: r might be crazy big, if rect was huge, possibly getting pinned to max/min s32. + // We need to trim it back to something reasonable before we can query its width etc. + // since r.fRight - r.fLeft might wrap around to negative even if fRight > fLeft. + // + // We outset the clip bounds by 1 before intersecting, since r is being stroked and not filled + // so we don't want to pin an edge of it to the clip. The intersect's job is mostly to just + // get the actual edge values into a reasonable range (e.g. so width() can't overflow). + if (!r.intersect(clip.getBounds().makeOutset(1, 1))) { + return; + } + + if (clip.quickReject(r)) { + return; + } + if (!clip.quickContains(r)) { + const SkRegion* clipRgn; + if (clip.isBW()) { + clipRgn = &clip.bwRgn(); + } else { + wrapper.init(clip, blitter); + clipRgn = &wrapper.getRgn(); + blitter = wrapper.getBlitter(); + } + blitter = clipper.apply(blitter, clipRgn); + } + + int width = r.width(); + int height = r.height(); + + if ((width | height) == 0) { + return; + } + if (width <= 2 || height <= 2) { + blitter->blitRect(r.fLeft, r.fTop, width, height); + return; + } + // if we get here, we know we have 4 segments to draw + blitter->blitH(r.fLeft, r.fTop, width); // top + blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2); // left + blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right + blitter->blitH(r.fLeft, r.fBottom - 1, width); // bottom +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkPath.h" +#include "include/private/SkNx.h" +#include "src/core/SkGeometry.h" + +#define kMaxCubicSubdivideLevel 9 +#define kMaxQuadSubdivideLevel 5 + +static uint32_t compute_int_quad_dist(const SkPoint pts[3]) { + // compute the vector between the control point ([1]) and the middle of the + // line connecting the start and end ([0] and [2]) + SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX; + SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY; + // we want everyone to be positive + dx = SkScalarAbs(dx); + dy = SkScalarAbs(dy); + // convert to whole pixel values (use ceiling to be conservative). + // assign to unsigned so we can safely add 1/2 of the smaller and still fit in + // uint32_t, since SkScalarCeilToInt() returns 31 bits at most. + uint32_t idx = SkScalarCeilToInt(dx); + uint32_t idy = SkScalarCeilToInt(dy); + // use the cheap approx for distance + if (idx > idy) { + return idx + (idy >> 1); + } else { + return idy + (idx >> 1); + } +} + +static void hair_quad(const SkPoint pts[3], const SkRegion* clip, + SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) { + SkASSERT(level <= kMaxQuadSubdivideLevel); + + SkQuadCoeff coeff(pts); + + const int lines = 1 << level; + Sk2s t(0); + Sk2s dt(SK_Scalar1 / lines); + + SkPoint tmp[(1 << kMaxQuadSubdivideLevel) + 1]; + SkASSERT((unsigned)lines < SK_ARRAY_COUNT(tmp)); + + tmp[0] = pts[0]; + Sk2s A = coeff.fA; + Sk2s B = coeff.fB; + Sk2s C = coeff.fC; + for (int i = 1; i < lines; ++i) { + t = t + dt; + ((A * t + B) * t + C).store(&tmp[i]); + } + tmp[lines] = pts[2]; + lineproc(tmp, lines + 1, clip, blitter); +} + +static SkRect compute_nocheck_quad_bounds(const SkPoint pts[3]) { + SkASSERT(SkScalarsAreFinite(&pts[0].fX, 6)); + + Sk2s min = Sk2s::Load(pts); + Sk2s max = min; + for (int i = 1; i < 3; ++i) { + Sk2s pair = Sk2s::Load(pts+i); + min = Sk2s::Min(min, pair); + max = Sk2s::Max(max, pair); + } + return { min[0], min[1], max[0], max[1] }; +} + +static bool is_inverted(const SkRect& r) { + return r.fLeft > r.fRight || r.fTop > r.fBottom; +} + +// Can't call SkRect::intersects, since it cares about empty, and we don't (since we tracking +// something to be stroked, so empty can still draw something (e.g. horizontal line) +static bool geometric_overlap(const SkRect& a, const SkRect& b) { + SkASSERT(!is_inverted(a) && !is_inverted(b)); + return a.fLeft < b.fRight && b.fLeft < a.fRight && + a.fTop < b.fBottom && b.fTop < a.fBottom; +} + +// Can't call SkRect::contains, since it cares about empty, and we don't (since we tracking +// something to be stroked, so empty can still draw something (e.g. horizontal line) +static bool geometric_contains(const SkRect& outer, const SkRect& inner) { + SkASSERT(!is_inverted(outer) && !is_inverted(inner)); + return inner.fRight <= outer.fRight && inner.fLeft >= outer.fLeft && + inner.fBottom <= outer.fBottom && inner.fTop >= outer.fTop; +} + +static inline void hairquad(const SkPoint pts[3], const SkRegion* clip, const SkRect* insetClip, const SkRect* outsetClip, + SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) { + if (insetClip) { + SkASSERT(outsetClip); + SkRect bounds = compute_nocheck_quad_bounds(pts); + if (!geometric_overlap(*outsetClip, bounds)) { + return; + } else if (geometric_contains(*insetClip, bounds)) { + clip = nullptr; + } + } + + hair_quad(pts, clip, blitter, level, lineproc); +} + +static inline Sk2s abs(const Sk2s& value) { + return Sk2s::Max(value, Sk2s(0)-value); +} + +static inline SkScalar max_component(const Sk2s& value) { + SkScalar components[2]; + value.store(components); + return SkTMax(components[0], components[1]); +} + +static inline int compute_cubic_segs(const SkPoint pts[4]) { + Sk2s p0 = from_point(pts[0]); + Sk2s p1 = from_point(pts[1]); + Sk2s p2 = from_point(pts[2]); + Sk2s p3 = from_point(pts[3]); + + const Sk2s oneThird(1.0f / 3.0f); + const Sk2s twoThird(2.0f / 3.0f); + + Sk2s p13 = oneThird * p3 + twoThird * p0; + Sk2s p23 = oneThird * p0 + twoThird * p3; + + SkScalar diff = max_component(Sk2s::Max(abs(p1 - p13), abs(p2 - p23))); + SkScalar tol = SK_Scalar1 / 8; + + for (int i = 0; i < kMaxCubicSubdivideLevel; ++i) { + if (diff < tol) { + return 1 << i; + } + tol *= 4; + } + return 1 << kMaxCubicSubdivideLevel; +} + +static bool lt_90(SkPoint p0, SkPoint pivot, SkPoint p2) { + return SkVector::DotProduct(p0 - pivot, p2 - pivot) >= 0; +} + +// The off-curve points are "inside" the limits of the on-curve pts +static bool quick_cubic_niceness_check(const SkPoint pts[4]) { + return lt_90(pts[1], pts[0], pts[3]) && + lt_90(pts[2], pts[0], pts[3]) && + lt_90(pts[1], pts[3], pts[0]) && + lt_90(pts[2], pts[3], pts[0]); +} + +typedef SkNx<2, uint32_t> Sk2x32; + +static inline Sk2x32 sk2s_is_finite(const Sk2s& x) { + const Sk2x32 exp_mask = Sk2x32(0xFF << 23); + return (Sk2x32::Load(&x) & exp_mask) != exp_mask; +} + +static void hair_cubic(const SkPoint pts[4], const SkRegion* clip, SkBlitter* blitter, + SkScan::HairRgnProc lineproc) { + const int lines = compute_cubic_segs(pts); + SkASSERT(lines > 0); + if (1 == lines) { + SkPoint tmp[2] = { pts[0], pts[3] }; + lineproc(tmp, 2, clip, blitter); + return; + } + + SkCubicCoeff coeff(pts); + + const Sk2s dt(SK_Scalar1 / lines); + Sk2s t(0); + + SkPoint tmp[(1 << kMaxCubicSubdivideLevel) + 1]; + SkASSERT((unsigned)lines < SK_ARRAY_COUNT(tmp)); + + tmp[0] = pts[0]; + Sk2s A = coeff.fA; + Sk2s B = coeff.fB; + Sk2s C = coeff.fC; + Sk2s D = coeff.fD; + Sk2x32 is_finite(~0); // start out as true + for (int i = 1; i < lines; ++i) { + t = t + dt; + Sk2s p = ((A * t + B) * t + C) * t + D; + is_finite &= sk2s_is_finite(p); + p.store(&tmp[i]); + } + if (is_finite.allTrue()) { + tmp[lines] = pts[3]; + lineproc(tmp, lines + 1, clip, blitter); + } // else some point(s) are non-finite, so don't draw +} + +static SkRect compute_nocheck_cubic_bounds(const SkPoint pts[4]) { + SkASSERT(SkScalarsAreFinite(&pts[0].fX, 8)); + + Sk2s min = Sk2s::Load(pts); + Sk2s max = min; + for (int i = 1; i < 4; ++i) { + Sk2s pair = Sk2s::Load(pts+i); + min = Sk2s::Min(min, pair); + max = Sk2s::Max(max, pair); + } + return { min[0], min[1], max[0], max[1] }; +} + +static inline void haircubic(const SkPoint pts[4], const SkRegion* clip, const SkRect* insetClip, const SkRect* outsetClip, + SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) { + if (insetClip) { + SkASSERT(outsetClip); + SkRect bounds = compute_nocheck_cubic_bounds(pts); + if (!geometric_overlap(*outsetClip, bounds)) { + return; + } else if (geometric_contains(*insetClip, bounds)) { + clip = nullptr; + } + } + + if (quick_cubic_niceness_check(pts)) { + hair_cubic(pts, clip, blitter, lineproc); + } else { + SkPoint tmp[13]; + SkScalar tValues[3]; + + int count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); + for (int i = 0; i < count; i++) { + hair_cubic(&tmp[i * 3], clip, blitter, lineproc); + } + } +} + +static int compute_quad_level(const SkPoint pts[3]) { + uint32_t d = compute_int_quad_dist(pts); + /* quadratics approach the line connecting their start and end points + 4x closer with each subdivision, so we compute the number of + subdivisions to be the minimum need to get that distance to be less + than a pixel. + */ + int level = (33 - SkCLZ(d)) >> 1; + // sanity check on level (from the previous version) + if (level > kMaxQuadSubdivideLevel) { + level = kMaxQuadSubdivideLevel; + } + return level; +} + +/* Extend the points in the direction of the starting or ending tangent by 1/2 unit to + account for a round or square cap. If there's no distance between the end point and + the control point, use the next control point to create a tangent. If the curve + is degenerate, move the cap out 1/2 unit horizontally. */ +template +void extend_pts(SkPath::Verb prevVerb, SkPath::Verb nextVerb, SkPoint* pts, int ptCount) { + SkASSERT(SkPaint::kSquare_Cap == capStyle || SkPaint::kRound_Cap == capStyle); + // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that. + const SkScalar capOutset = SkPaint::kSquare_Cap == capStyle ? 0.5f : SK_ScalarPI / 8; + if (SkPath::kMove_Verb == prevVerb) { + SkPoint* first = pts; + SkPoint* ctrl = first; + int controls = ptCount - 1; + SkVector tangent; + do { + tangent = *first - *++ctrl; + } while (tangent.isZero() && --controls > 0); + if (tangent.isZero()) { + tangent.set(1, 0); + controls = ptCount - 1; // If all points are equal, move all but one + } else { + tangent.normalize(); + } + do { // If the end point and control points are equal, loop to move them in tandem. + first->fX += tangent.fX * capOutset; + first->fY += tangent.fY * capOutset; + ++first; + } while (++controls < ptCount); + } + if (SkPath::kMove_Verb == nextVerb || SkPath::kDone_Verb == nextVerb + || SkPath::kClose_Verb == nextVerb) { + SkPoint* last = &pts[ptCount - 1]; + SkPoint* ctrl = last; + int controls = ptCount - 1; + SkVector tangent; + do { + tangent = *last - *--ctrl; + } while (tangent.isZero() && --controls > 0); + if (tangent.isZero()) { + tangent.set(-1, 0); + controls = ptCount - 1; + } else { + tangent.normalize(); + } + do { + last->fX += tangent.fX * capOutset; + last->fY += tangent.fY * capOutset; + --last; + } while (++controls < ptCount); + } +} + +template +void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter, + SkScan::HairRgnProc lineproc) { + if (path.isEmpty()) { + return; + } + + SkAAClipBlitterWrapper wrap; + const SkRegion* clip = nullptr; + SkRect insetStorage, outsetStorage; + const SkRect* insetClip = nullptr; + const SkRect* outsetClip = nullptr; + + { + const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2; + const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut); + if (rclip.quickReject(ibounds)) { + return; + } + if (!rclip.quickContains(ibounds)) { + if (rclip.isBW()) { + clip = &rclip.bwRgn(); + } else { + wrap.init(rclip, blitter); + blitter = wrap.getBlitter(); + clip = &wrap.getRgn(); + } + + /* + * We now cache two scalar rects, to use for culling per-segment (e.g. cubic). + * Since we're hairlining, the "bounds" of the control points isn't necessairly the + * limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). + * + * Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust + * the culling bounds so we can just do a straight compare per segment. + * + * insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset + * it from the clip-bounds (since segment bounds can be off by 1). + * + * outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we + * outset it from the clip-bounds. + */ + insetStorage.set(clip->getBounds()); + outsetStorage = insetStorage.makeOutset(1, 1); + insetStorage.inset(1, 1); + if (is_inverted(insetStorage)) { + /* + * our bounds checks assume the rects are never inverted. If insetting has + * created that, we assume that the area is too small to safely perform a + * quick-accept, so we just mark the rect as empty (so the quick-accept check + * will always fail. + */ + insetStorage.setEmpty(); // just so we don't pass an inverted rect + } + if (rclip.isRect()) { + insetClip = &insetStorage; + } + outsetClip = &outsetStorage; + } + } + + SkPath::RawIter iter(path); + SkPoint pts[4], firstPt, lastPt; + SkPath::Verb verb, prevVerb; + SkAutoConicToQuads converter; + + if (SkPaint::kButt_Cap != capStyle) { + prevVerb = SkPath::kDone_Verb; + } + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + firstPt = lastPt = pts[0]; + break; + case SkPath::kLine_Verb: + if (SkPaint::kButt_Cap != capStyle) { + extend_pts(prevVerb, iter.peek(), pts, 2); + } + lineproc(pts, 2, clip, blitter); + lastPt = pts[1]; + break; + case SkPath::kQuad_Verb: + if (SkPaint::kButt_Cap != capStyle) { + extend_pts(prevVerb, iter.peek(), pts, 3); + } + hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc); + lastPt = pts[2]; + break; + case SkPath::kConic_Verb: { + if (SkPaint::kButt_Cap != capStyle) { + extend_pts(prevVerb, iter.peek(), pts, 3); + } + // how close should the quads be to the original conic? + const SkScalar tol = SK_Scalar1 / 4; + const SkPoint* quadPts = converter.computeQuads(pts, + iter.conicWeight(), tol); + for (int i = 0; i < converter.countQuads(); ++i) { + int level = compute_quad_level(quadPts); + hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc); + quadPts += 2; + } + lastPt = pts[2]; + break; + } + case SkPath::kCubic_Verb: { + if (SkPaint::kButt_Cap != capStyle) { + extend_pts(prevVerb, iter.peek(), pts, 4); + } + haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc); + lastPt = pts[3]; + } break; + case SkPath::kClose_Verb: + pts[0] = lastPt; + pts[1] = firstPt; + if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) { + // cap moveTo/close to match svg expectations for degenerate segments + extend_pts(prevVerb, iter.peek(), pts, 2); + } + lineproc(pts, 2, clip, blitter); + break; + case SkPath::kDone_Verb: + break; + } + if (SkPaint::kButt_Cap != capStyle) { + if (prevVerb == SkPath::kMove_Verb && + verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { + firstPt = pts[0]; // the curve moved the initial point, so close to it instead + } + prevVerb = verb; + } + } +} + +void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + hair_path(path, clip, blitter, SkScan::HairLineRgn); +} + +void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + hair_path(path, clip, blitter, SkScan::AntiHairLineRgn); +} + +void SkScan::HairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + hair_path(path, clip, blitter, SkScan::HairLineRgn); +} + +void SkScan::AntiHairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + hair_path(path, clip, blitter, SkScan::AntiHairLineRgn); +} + +void SkScan::HairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + hair_path(path, clip, blitter, SkScan::HairLineRgn); +} + +void SkScan::AntiHairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { + hair_path(path, clip, blitter, SkScan::AntiHairLineRgn); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize, + const SkRasterClip& clip, SkBlitter* blitter) { + SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0); + + if (strokeSize.fX < 0 || strokeSize.fY < 0) { + return; + } + + const SkScalar dx = strokeSize.fX; + const SkScalar dy = strokeSize.fY; + SkScalar rx = SkScalarHalf(dx); + SkScalar ry = SkScalarHalf(dy); + SkRect outer, tmp; + + outer.setLTRB(r.fLeft - rx, r.fTop - ry, r.fRight + rx, r.fBottom + ry); + + if (r.width() <= dx || r.height() <= dy) { + SkScan::FillRect(outer, clip, blitter); + return; + } + + tmp.setLTRB(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy); + SkScan::FillRect(tmp, clip, blitter); + tmp.fTop = outer.fBottom - dy; + tmp.fBottom = outer.fBottom; + SkScan::FillRect(tmp, clip, blitter); + + tmp.setLTRB(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy); + SkScan::FillRect(tmp, clip, blitter); + tmp.fLeft = outer.fRight - dx; + tmp.fRight = outer.fRight; + SkScan::FillRect(tmp, clip, blitter); +} + +void SkScan::HairLine(const SkPoint pts[], int count, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isBW()) { + HairLineRgn(pts, count, &clip.bwRgn(), blitter); + } else { + const SkRegion* clipRgn = nullptr; + + SkRect r; + r.setBounds(pts, count); + r.outset(SK_ScalarHalf, SK_ScalarHalf); + + SkAAClipBlitterWrapper wrap; + if (!clip.quickContains(r.roundOut())) { + wrap.init(clip, blitter); + blitter = wrap.getBlitter(); + clipRgn = &wrap.getRgn(); + } + HairLineRgn(pts, count, clipRgn, blitter); + } +} + +void SkScan::AntiHairLine(const SkPoint pts[], int count, const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isBW()) { + AntiHairLineRgn(pts, count, &clip.bwRgn(), blitter); + } else { + const SkRegion* clipRgn = nullptr; + + SkRect r; + r.setBounds(pts, count); + + SkAAClipBlitterWrapper wrap; + if (!clip.quickContains(r.roundOut().makeOutset(1, 1))) { + wrap.init(clip, blitter); + blitter = wrap.getBlitter(); + clipRgn = &wrap.getRgn(); + } + AntiHairLineRgn(pts, count, clipRgn, blitter); + } +} diff --git a/gfx/skia/skia/src/core/SkScan_Path.cpp b/gfx/skia/skia/src/core/SkScan_Path.cpp new file mode 100644 index 0000000000..4271f4e4b4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkScan_Path.cpp @@ -0,0 +1,780 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "include/core/SkRegion.h" +#include "include/private/SkMacros.h" +#include "include/private/SkSafe32.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkBlitter.h" +#include "src/core/SkEdge.h" +#include "src/core/SkEdgeBuilder.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkQuadClipper.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkScanPriv.h" +#include "src/core/SkTSort.h" + +#include + +#define kEDGE_HEAD_Y SK_MinS32 +#define kEDGE_TAIL_Y SK_MaxS32 + +#ifdef SK_DEBUG + static void validate_sort(const SkEdge* edge) { + int y = kEDGE_HEAD_Y; + + while (edge->fFirstY != SK_MaxS32) { + edge->validate(); + SkASSERT(y <= edge->fFirstY); + + y = edge->fFirstY; + edge = edge->fNext; + } + } +#else + #define validate_sort(edge) +#endif + +static void insert_new_edges(SkEdge* newEdge, int curr_y) { + if (newEdge->fFirstY != curr_y) { + return; + } + SkEdge* prev = newEdge->fPrev; + if (prev->fX <= newEdge->fX) { + return; + } + // find first x pos to insert + SkEdge* start = backward_insert_start(prev, newEdge->fX); + // insert the lot, fixing up the links as we go + do { + SkEdge* next = newEdge->fNext; + do { + if (start->fNext == newEdge) { + goto nextEdge; + } + SkEdge* after = start->fNext; + if (after->fX >= newEdge->fX) { + break; + } + start = after; + } while (true); + remove_edge(newEdge); + insert_edge_after(newEdge, start); +nextEdge: + start = newEdge; + newEdge = next; + } while (newEdge->fFirstY == curr_y); +} + +#ifdef SK_DEBUG +static void validate_edges_for_y(const SkEdge* edge, int curr_y) { + while (edge->fFirstY <= curr_y) { + SkASSERT(edge->fPrev && edge->fNext); + SkASSERT(edge->fPrev->fNext == edge); + SkASSERT(edge->fNext->fPrev == edge); + SkASSERT(edge->fFirstY <= edge->fLastY); + + SkASSERT(edge->fPrev->fX <= edge->fX); + edge = edge->fNext; + } +} +#else + #define validate_edges_for_y(edge, curr_y) +#endif + +#if defined _WIN32 // disable warning : local variable used without having been initialized +#pragma warning ( push ) +#pragma warning ( disable : 4701 ) +#endif + +typedef void (*PrePostProc)(SkBlitter* blitter, int y, bool isStartOfScanline); +#define PREPOST_START true +#define PREPOST_END false + +static void walk_edges(SkEdge* prevHead, SkPath::FillType fillType, + SkBlitter* blitter, int start_y, int stop_y, + PrePostProc proc, int rightClip) { + validate_sort(prevHead->fNext); + + int curr_y = start_y; + // returns 1 for evenodd, -1 for winding, regardless of inverse-ness + int windingMask = (fillType & 1) ? 1 : -1; + + for (;;) { + int w = 0; + int left SK_INIT_TO_AVOID_WARNING; + SkEdge* currE = prevHead->fNext; + SkFixed prevX = prevHead->fX; + + validate_edges_for_y(currE, curr_y); + + if (proc) { + proc(blitter, curr_y, PREPOST_START); // pre-proc + } + + while (currE->fFirstY <= curr_y) { + SkASSERT(currE->fLastY >= curr_y); + + int x = SkFixedRoundToInt(currE->fX); + + if ((w & windingMask) == 0) { // we're starting interval + left = x; + } + + w += currE->fWinding; + + if ((w & windingMask) == 0) { // we finished an interval + int width = x - left; + SkASSERT(width >= 0); + if (width > 0) { + blitter->blitH(left, curr_y, width); + } + } + + SkEdge* next = currE->fNext; + SkFixed newX; + + if (currE->fLastY == curr_y) { // are we done with this edge? + if (currE->fCurveCount > 0) { + if (((SkQuadraticEdge*)currE)->updateQuadratic()) { + newX = currE->fX; + goto NEXT_X; + } + } else if (currE->fCurveCount < 0) { + if (((SkCubicEdge*)currE)->updateCubic()) { + SkASSERT(currE->fFirstY == curr_y + 1); + + newX = currE->fX; + goto NEXT_X; + } + } + remove_edge(currE); + } else { + SkASSERT(currE->fLastY > curr_y); + newX = currE->fX + currE->fDX; + currE->fX = newX; + NEXT_X: + if (newX < prevX) { // ripple currE backwards until it is x-sorted + backward_insert_edge_based_on_x(currE); + } else { + prevX = newX; + } + } + currE = next; + SkASSERT(currE); + } + + if ((w & windingMask) != 0) { // was our right-edge culled away? + int width = rightClip - left; + if (width > 0) { + blitter->blitH(left, curr_y, width); + } + } + + if (proc) { + proc(blitter, curr_y, PREPOST_END); // post-proc + } + + curr_y += 1; + if (curr_y >= stop_y) { + break; + } + // now currE points to the first edge with a Yint larger than curr_y + insert_new_edges(currE, curr_y); + } +} + +// return true if we're NOT done with this edge +static bool update_edge(SkEdge* edge, int last_y) { + SkASSERT(edge->fLastY >= last_y); + if (last_y == edge->fLastY) { + if (edge->fCurveCount < 0) { + if (((SkCubicEdge*)edge)->updateCubic()) { + SkASSERT(edge->fFirstY == last_y + 1); + return true; + } + } else if (edge->fCurveCount > 0) { + if (((SkQuadraticEdge*)edge)->updateQuadratic()) { + SkASSERT(edge->fFirstY == last_y + 1); + return true; + } + } + return false; + } + return true; +} + +// Unexpected conditions for which we need to return +#define ASSERT_RETURN(cond) \ + do { \ + if (!(cond)) { \ + SkASSERT(false); \ + return; \ + } \ + } while (0) + +// Needs Y to only change once (looser than convex in X) +static void walk_simple_edges(SkEdge* prevHead, SkBlitter* blitter, int start_y, int stop_y) { + validate_sort(prevHead->fNext); + + SkEdge* leftE = prevHead->fNext; + SkEdge* riteE = leftE->fNext; + SkEdge* currE = riteE->fNext; + + // our edge choppers for curves can result in the initial edges + // not lining up, so we take the max. + int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY); + ASSERT_RETURN(local_top >= start_y); + + while (local_top < stop_y) { + SkASSERT(leftE->fFirstY <= stop_y); + SkASSERT(riteE->fFirstY <= stop_y); + + int local_bot = SkMin32(leftE->fLastY, riteE->fLastY); + local_bot = SkMin32(local_bot, stop_y - 1); + ASSERT_RETURN(local_top <= local_bot); + + SkFixed left = leftE->fX; + SkFixed dLeft = leftE->fDX; + SkFixed rite = riteE->fX; + SkFixed dRite = riteE->fDX; + int count = local_bot - local_top; + ASSERT_RETURN(count >= 0); + + if (0 == (dLeft | dRite)) { + int L = SkFixedRoundToInt(left); + int R = SkFixedRoundToInt(rite); + if (L > R) { + std::swap(L, R); + } + if (L < R) { + count += 1; + blitter->blitRect(L, local_top, R - L, count); + } + local_top = local_bot + 1; + } else { + do { + int L = SkFixedRoundToInt(left); + int R = SkFixedRoundToInt(rite); + if (L > R) { + std::swap(L, R); + } + if (L < R) { + blitter->blitH(L, local_top, R - L); + } + // Either/both of these might overflow, since we perform this step even if + // (later) we determine that we are done with the edge, and so the computed + // left or rite edge will not be used (see update_edge). Use this helper to + // silence UBSAN when we perform the add. + left = Sk32_can_overflow_add(left, dLeft); + rite = Sk32_can_overflow_add(rite, dRite); + local_top += 1; + } while (--count >= 0); + } + + leftE->fX = left; + riteE->fX = rite; + + if (!update_edge(leftE, local_bot)) { + if (currE->fFirstY >= stop_y) { + return; // we're done + } + leftE = currE; + currE = currE->fNext; + ASSERT_RETURN(leftE->fFirstY == local_top); + } + if (!update_edge(riteE, local_bot)) { + if (currE->fFirstY >= stop_y) { + return; // we're done + } + riteE = currE; + currE = currE->fNext; + ASSERT_RETURN(riteE->fFirstY == local_top); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// this guy overrides blitH, and will call its proxy blitter with the inverse +// of the spans it is given (clipped to the left/right of the cliprect) +// +// used to implement inverse filltypes on paths +// +class InverseBlitter : public SkBlitter { +public: + void setBlitter(SkBlitter* blitter, const SkIRect& clip, int shift) { + fBlitter = blitter; + fFirstX = clip.fLeft << shift; + fLastX = clip.fRight << shift; + } + void prepost(int y, bool isStart) { + if (isStart) { + fPrevX = fFirstX; + } else { + int invWidth = fLastX - fPrevX; + if (invWidth > 0) { + fBlitter->blitH(fPrevX, y, invWidth); + } + } + } + + // overrides + void blitH(int x, int y, int width) override { + int invWidth = x - fPrevX; + if (invWidth > 0) { + fBlitter->blitH(fPrevX, y, invWidth); + } + fPrevX = x + width; + } + + // we do not expect to get called with these entrypoints + void blitAntiH(int, int, const SkAlpha[], const int16_t runs[]) override { + SkDEBUGFAIL("blitAntiH unexpected"); + } + void blitV(int x, int y, int height, SkAlpha alpha) override { + SkDEBUGFAIL("blitV unexpected"); + } + void blitRect(int x, int y, int width, int height) override { + SkDEBUGFAIL("blitRect unexpected"); + } + void blitMask(const SkMask&, const SkIRect& clip) override { + SkDEBUGFAIL("blitMask unexpected"); + } + const SkPixmap* justAnOpaqueColor(uint32_t* value) override { + SkDEBUGFAIL("justAnOpaqueColor unexpected"); + return nullptr; + } + +private: + SkBlitter* fBlitter; + int fFirstX, fLastX, fPrevX; +}; + +static void PrePostInverseBlitterProc(SkBlitter* blitter, int y, bool isStart) { + ((InverseBlitter*)blitter)->prepost(y, isStart); +} + +/////////////////////////////////////////////////////////////////////////////// + +#if defined _WIN32 +#pragma warning ( pop ) +#endif + +static bool operator<(const SkEdge& a, const SkEdge& b) { + int valuea = a.fFirstY; + int valueb = b.fFirstY; + + if (valuea == valueb) { + valuea = a.fX; + valueb = b.fX; + } + + return valuea < valueb; +} + +static SkEdge* sort_edges(SkEdge* list[], int count, SkEdge** last) { + SkTQSort(list, list + count - 1); + + // now make the edges linked in sorted order + for (int i = 1; i < count; i++) { + list[i - 1]->fNext = list[i]; + list[i]->fPrev = list[i - 1]; + } + + *last = list[count - 1]; + return list[0]; +} + +// clipRect has not been shifted up +void sk_fill_path(const SkPath& path, const SkIRect& clipRect, SkBlitter* blitter, + int start_y, int stop_y, int shiftEdgesUp, bool pathContainedInClip) { + SkASSERT(blitter); + + SkIRect shiftedClip = clipRect; + shiftedClip.fLeft = SkLeftShift(shiftedClip.fLeft, shiftEdgesUp); + shiftedClip.fRight = SkLeftShift(shiftedClip.fRight, shiftEdgesUp); + shiftedClip.fTop = SkLeftShift(shiftedClip.fTop, shiftEdgesUp); + shiftedClip.fBottom = SkLeftShift(shiftedClip.fBottom, shiftEdgesUp); + + SkBasicEdgeBuilder builder(shiftEdgesUp); + int count = builder.buildEdges(path, pathContainedInClip ? nullptr : &shiftedClip); + SkEdge** list = builder.edgeList(); + + if (0 == count) { + if (path.isInverseFillType()) { + /* + * Since we are in inverse-fill, our caller has already drawn above + * our top (start_y) and will draw below our bottom (stop_y). Thus + * we need to restrict our drawing to the intersection of the clip + * and those two limits. + */ + SkIRect rect = clipRect; + if (rect.fTop < start_y) { + rect.fTop = start_y; + } + if (rect.fBottom > stop_y) { + rect.fBottom = stop_y; + } + if (!rect.isEmpty()) { + blitter->blitRect(rect.fLeft << shiftEdgesUp, + rect.fTop << shiftEdgesUp, + rect.width() << shiftEdgesUp, + rect.height() << shiftEdgesUp); + } + } + return; + } + + SkEdge headEdge, tailEdge, *last; + // this returns the first and last edge after they're sorted into a dlink list + SkEdge* edge = sort_edges(list, count, &last); + + headEdge.fPrev = nullptr; + headEdge.fNext = edge; + headEdge.fFirstY = kEDGE_HEAD_Y; + headEdge.fX = SK_MinS32; + edge->fPrev = &headEdge; + + tailEdge.fPrev = last; + tailEdge.fNext = nullptr; + tailEdge.fFirstY = kEDGE_TAIL_Y; + last->fNext = &tailEdge; + + // now edge is the head of the sorted linklist + + start_y = SkLeftShift(start_y, shiftEdgesUp); + stop_y = SkLeftShift(stop_y, shiftEdgesUp); + if (!pathContainedInClip && start_y < shiftedClip.fTop) { + start_y = shiftedClip.fTop; + } + if (!pathContainedInClip && stop_y > shiftedClip.fBottom) { + stop_y = shiftedClip.fBottom; + } + + InverseBlitter ib; + PrePostProc proc = nullptr; + + if (path.isInverseFillType()) { + ib.setBlitter(blitter, clipRect, shiftEdgesUp); + blitter = &ib; + proc = PrePostInverseBlitterProc; + } + + // count >= 2 is required as the convex walker does not handle missing right edges + if (path.isConvex() && (nullptr == proc) && count >= 2) { + walk_simple_edges(&headEdge, blitter, start_y, stop_y); + } else { + walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc, + shiftedClip.right()); + } +} + +void sk_blit_above(SkBlitter* blitter, const SkIRect& ir, const SkRegion& clip) { + const SkIRect& cr = clip.getBounds(); + SkIRect tmp; + + tmp.fLeft = cr.fLeft; + tmp.fRight = cr.fRight; + tmp.fTop = cr.fTop; + tmp.fBottom = ir.fTop; + if (!tmp.isEmpty()) { + blitter->blitRectRegion(tmp, clip); + } +} + +void sk_blit_below(SkBlitter* blitter, const SkIRect& ir, const SkRegion& clip) { + const SkIRect& cr = clip.getBounds(); + SkIRect tmp; + + tmp.fLeft = cr.fLeft; + tmp.fRight = cr.fRight; + tmp.fTop = ir.fBottom; + tmp.fBottom = cr.fBottom; + if (!tmp.isEmpty()) { + blitter->blitRectRegion(tmp, clip); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * If the caller is drawing an inverse-fill path, then it pass true for + * skipRejectTest, so we don't abort drawing just because the src bounds (ir) + * is outside of the clip. + */ +SkScanClipper::SkScanClipper(SkBlitter* blitter, const SkRegion* clip, + const SkIRect& ir, bool skipRejectTest, bool irPreClipped) { + fBlitter = nullptr; // null means blit nothing + fClipRect = nullptr; + + if (clip) { + fClipRect = &clip->getBounds(); + if (!skipRejectTest && !SkIRect::Intersects(*fClipRect, ir)) { // completely clipped out + return; + } + + if (clip->isRect()) { + if (!irPreClipped && fClipRect->contains(ir)) { +#ifdef SK_DEBUG + fRectClipCheckBlitter.init(blitter, *fClipRect); + blitter = &fRectClipCheckBlitter; +#endif + fClipRect = nullptr; + } else { + // only need a wrapper blitter if we're horizontally clipped + if (irPreClipped || + fClipRect->fLeft > ir.fLeft || fClipRect->fRight < ir.fRight) { + fRectBlitter.init(blitter, *fClipRect); + blitter = &fRectBlitter; + } else { +#ifdef SK_DEBUG + fRectClipCheckBlitter.init(blitter, *fClipRect); + blitter = &fRectClipCheckBlitter; +#endif + } + } + } else { + fRgnBlitter.init(blitter, clip); + blitter = &fRgnBlitter; + } + } + fBlitter = blitter; +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool clip_to_limit(const SkRegion& orig, SkRegion* reduced) { + // need to limit coordinates such that the width/height of our rect can be represented + // in SkFixed (16.16). See skbug.com/7998 + const int32_t limit = 32767 >> 1; + + SkIRect limitR; + limitR.setLTRB(-limit, -limit, limit, limit); + if (limitR.contains(orig.getBounds())) { + return false; + } + reduced->op(orig, limitR, SkRegion::kIntersect_Op); + return true; +} + +// Bias used for conservative rounding of float rects to int rects, to nudge the irects a little +// larger, so we don't "think" a path's bounds are inside a clip, when (due to numeric drift in +// the scan-converter) we might walk beyond the predicted limits. +// +// This value has been determined trial and error: pick the smallest value (after the 0.5) that +// fixes any problematic cases (e.g. crbug.com/844457) +// NOTE: cubics appear to be the main reason for needing this slop. If we could (perhaps) have a +// more accurate walker for cubics, we may be able to reduce this fudge factor. +static const double kConservativeRoundBias = 0.5 + 1.5 / SK_FDot6One; + +/** + * Round the value down. This is used to round the top and left of a rectangle, + * and corresponds to the way the scan converter treats the top and left edges. + * It has a slight bias to make the "rounded" int smaller than a normal round, to create a more + * conservative int-bounds (larger) from a float rect. + */ +static inline int round_down_to_int(SkScalar x) { + double xx = x; + xx -= kConservativeRoundBias; + return sk_double_saturate2int(ceil(xx)); +} + +/** + * Round the value up. This is used to round the right and bottom of a rectangle. + * It has a slight bias to make the "rounded" int smaller than a normal round, to create a more + * conservative int-bounds (larger) from a float rect. + */ +static inline int round_up_to_int(SkScalar x) { + double xx = x; + xx += kConservativeRoundBias; + return sk_double_saturate2int(floor(xx)); +} + +/* + * Conservative rounding function, which effectively nudges the int-rect to be slightly larger + * than SkRect::round() might have produced. This is a safety-net for the scan-converter, which + * inspects the returned int-rect, and may disable clipping (for speed) if it thinks all of the + * edges will fit inside the clip's bounds. The scan-converter introduces slight numeric errors + * due to accumulated += of the slope, so this function is used to return a conservatively large + * int-bounds, and thus we will only disable clipping if we're sure the edges will stay in-bounds. + */ +static SkIRect conservative_round_to_int(const SkRect& src) { + return { + round_down_to_int(src.fLeft), + round_down_to_int(src.fTop), + round_up_to_int(src.fRight), + round_up_to_int(src.fBottom), + }; +} + +void SkScan::FillPath(const SkPath& path, const SkRegion& origClip, + SkBlitter* blitter) { + if (origClip.isEmpty()) { + return; + } + + // Our edges are fixed-point, and don't like the bounds of the clip to + // exceed that. Here we trim the clip just so we don't overflow later on + const SkRegion* clipPtr = &origClip; + SkRegion finiteClip; + if (clip_to_limit(origClip, &finiteClip)) { + if (finiteClip.isEmpty()) { + return; + } + clipPtr = &finiteClip; + } + // don't reference "origClip" any more, just use clipPtr + + + SkRect bounds = path.getBounds(); + bool irPreClipped = false; + if (!SkRectPriv::MakeLargeS32().contains(bounds)) { + if (!bounds.intersect(SkRectPriv::MakeLargeS32())) { + bounds.setEmpty(); + } + irPreClipped = true; + } + + SkIRect ir = conservative_round_to_int(bounds); + if (ir.isEmpty()) { + if (path.isInverseFillType()) { + blitter->blitRegion(*clipPtr); + } + return; + } + + SkScanClipper clipper(blitter, clipPtr, ir, path.isInverseFillType(), irPreClipped); + + blitter = clipper.getBlitter(); + if (blitter) { + // we have to keep our calls to blitter in sorted order, so we + // must blit the above section first, then the middle, then the bottom. + if (path.isInverseFillType()) { + sk_blit_above(blitter, ir, *clipPtr); + } + SkASSERT(clipper.getClipRect() == nullptr || + *clipper.getClipRect() == clipPtr->getBounds()); + sk_fill_path(path, clipPtr->getBounds(), blitter, ir.fTop, ir.fBottom, + 0, clipper.getClipRect() == nullptr); + if (path.isInverseFillType()) { + sk_blit_below(blitter, ir, *clipPtr); + } + } else { + // what does it mean to not have a blitter if path.isInverseFillType??? + } +} + +void SkScan::FillPath(const SkPath& path, const SkIRect& ir, + SkBlitter* blitter) { + SkRegion rgn(ir); + FillPath(path, rgn, blitter); +} + +/////////////////////////////////////////////////////////////////////////////// + +static int build_tri_edges(SkEdge edge[], const SkPoint pts[], + const SkIRect* clipRect, SkEdge* list[]) { + SkEdge** start = list; + + if (edge->setLine(pts[0], pts[1], clipRect, 0)) { + *list++ = edge; + edge = (SkEdge*)((char*)edge + sizeof(SkEdge)); + } + if (edge->setLine(pts[1], pts[2], clipRect, 0)) { + *list++ = edge; + edge = (SkEdge*)((char*)edge + sizeof(SkEdge)); + } + if (edge->setLine(pts[2], pts[0], clipRect, 0)) { + *list++ = edge; + } + return (int)(list - start); +} + + +static void sk_fill_triangle(const SkPoint pts[], const SkIRect* clipRect, + SkBlitter* blitter, const SkIRect& ir) { + SkASSERT(pts && blitter); + + SkEdge edgeStorage[3]; + SkEdge* list[3]; + + int count = build_tri_edges(edgeStorage, pts, clipRect, list); + if (count < 2) { + return; + } + + SkEdge headEdge, tailEdge, *last; + + // this returns the first and last edge after they're sorted into a dlink list + SkEdge* edge = sort_edges(list, count, &last); + + headEdge.fPrev = nullptr; + headEdge.fNext = edge; + headEdge.fFirstY = kEDGE_HEAD_Y; + headEdge.fX = SK_MinS32; + edge->fPrev = &headEdge; + + tailEdge.fPrev = last; + tailEdge.fNext = nullptr; + tailEdge.fFirstY = kEDGE_TAIL_Y; + last->fNext = &tailEdge; + + // now edge is the head of the sorted linklist + int stop_y = ir.fBottom; + if (clipRect && stop_y > clipRect->fBottom) { + stop_y = clipRect->fBottom; + } + int start_y = ir.fTop; + if (clipRect && start_y < clipRect->fTop) { + start_y = clipRect->fTop; + } + walk_simple_edges(&headEdge, blitter, start_y, stop_y); +} + +void SkScan::FillTriangle(const SkPoint pts[], const SkRasterClip& clip, + SkBlitter* blitter) { + if (clip.isEmpty()) { + return; + } + + SkRect r; + r.setBounds(pts, 3); + // If r is too large (larger than can easily fit in SkFixed) then we need perform geometric + // clipping. This is a bit of work, so we just call the general FillPath() to handle it. + // Use FixedMax/2 as the limit so we can subtract two edges and still store that in Fixed. + const SkScalar limit = SK_MaxS16 >> 1; + if (!SkRect::MakeLTRB(-limit, -limit, limit, limit).contains(r)) { + SkPath path; + path.addPoly(pts, 3, false); + FillPath(path, clip, blitter); + return; + } + + SkIRect ir = conservative_round_to_int(r); + if (ir.isEmpty() || !SkIRect::Intersects(ir, clip.getBounds())) { + return; + } + + SkAAClipBlitterWrapper wrap; + const SkRegion* clipRgn; + if (clip.isBW()) { + clipRgn = &clip.bwRgn(); + } else { + wrap.init(clip, blitter); + clipRgn = &wrap.getRgn(); + blitter = wrap.getBlitter(); + } + + SkScanClipper clipper(blitter, clipRgn, ir); + blitter = clipper.getBlitter(); + if (blitter) { + sk_fill_triangle(pts, clipper.getClipRect(), blitter, ir); + } +} diff --git a/gfx/skia/skia/src/core/SkScopeExit.h b/gfx/skia/skia/src/core/SkScopeExit.h new file mode 100644 index 0000000000..d4705abc5d --- /dev/null +++ b/gfx/skia/skia/src/core/SkScopeExit.h @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkScopeExit_DEFINED +#define SkScopeExit_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMacros.h" + +#include + +/** SkScopeExit calls a std:::function in its destructor. */ +class SkScopeExit { +public: + SkScopeExit() = default; + SkScopeExit(std::function f) : fFn(std::move(f)) {} + SkScopeExit(SkScopeExit&& that) : fFn(std::move(that.fFn)) {} + + ~SkScopeExit() { + if (fFn) { + fFn(); + } + } + + void clear() { fFn = {}; } + + SkScopeExit& operator=(SkScopeExit&& that) { + fFn = std::move(that.fFn); + return *this; + } + +private: + std::function fFn; + + SkScopeExit( const SkScopeExit& ) = delete; + SkScopeExit& operator=(const SkScopeExit& ) = delete; +}; + +/** + * SK_AT_SCOPE_EXIT(stmt) evaluates stmt when the current scope ends. + * + * E.g. + * { + * int x = 5; + * { + * SK_AT_SCOPE_EXIT(x--); + * SkASSERT(x == 5); + * } + * SkASSERT(x == 4); + * } + */ +#define SK_AT_SCOPE_EXIT(stmt) \ + SkScopeExit SK_MACRO_APPEND_LINE(at_scope_exit_)([&]() { stmt; }) + +#endif // SkScopeExit_DEFINED diff --git a/gfx/skia/skia/src/core/SkSemaphore.cpp b/gfx/skia/skia/src/core/SkSemaphore.cpp new file mode 100644 index 0000000000..692cbfc15c --- /dev/null +++ b/gfx/skia/skia/src/core/SkSemaphore.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSemaphore.h" +#include "src/core/SkLeanWindows.h" + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + #include + + struct SkSemaphore::OSSemaphore { + dispatch_semaphore_t fSemaphore; + + OSSemaphore() { fSemaphore = dispatch_semaphore_create(0/*initial count*/); } + ~OSSemaphore() { dispatch_release(fSemaphore); } + + void signal(int n) { while (n --> 0) { dispatch_semaphore_signal(fSemaphore); } } + void wait() { dispatch_semaphore_wait(fSemaphore, DISPATCH_TIME_FOREVER); } + }; +#elif defined(SK_BUILD_FOR_WIN) + struct SkSemaphore::OSSemaphore { + HANDLE fSemaphore; + + OSSemaphore() { + fSemaphore = CreateSemaphore(nullptr /*security attributes, optional*/, + 0 /*initial count*/, + MAXLONG /*max count*/, + nullptr /*name, optional*/); + } + ~OSSemaphore() { CloseHandle(fSemaphore); } + + void signal(int n) { + ReleaseSemaphore(fSemaphore, n, nullptr/*returns previous count, optional*/); + } + void wait() { WaitForSingleObject(fSemaphore, INFINITE/*timeout in ms*/); } + }; +#else + // It's important we test for Mach before this. This code will compile but not work there. + #include + #include + struct SkSemaphore::OSSemaphore { + sem_t fSemaphore; + + OSSemaphore() { sem_init(&fSemaphore, 0/*cross process?*/, 0/*initial count*/); } + ~OSSemaphore() { sem_destroy(&fSemaphore); } + + void signal(int n) { while (n --> 0) { sem_post(&fSemaphore); } } + void wait() { + // Try until we're not interrupted. + while(sem_wait(&fSemaphore) == -1 && errno == EINTR); + } + }; +#endif + +/////////////////////////////////////////////////////////////////////////////// + +SkSemaphore::~SkSemaphore() { + delete fOSSemaphore; +} + +void SkSemaphore::osSignal(int n) { + fOSSemaphoreOnce([this] { fOSSemaphore = new OSSemaphore; }); + fOSSemaphore->signal(n); +} + +void SkSemaphore::osWait() { + fOSSemaphoreOnce([this] { fOSSemaphore = new OSSemaphore; }); + fOSSemaphore->wait(); +} + +bool SkSemaphore::try_wait() { + int count = fCount.load(std::memory_order_relaxed); + if (count > 0) { + return fCount.compare_exchange_weak(count, count-1, std::memory_order_acquire); + } + return false; +} diff --git a/gfx/skia/skia/src/core/SkSharedMutex.cpp b/gfx/skia/skia/src/core/SkSharedMutex.cpp new file mode 100644 index 0000000000..22de1a1357 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSharedMutex.cpp @@ -0,0 +1,366 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkSharedMutex.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSemaphore.h" + +#if !defined(__has_feature) + #define __has_feature(x) 0 +#endif + +#if __has_feature(thread_sanitizer) + + /* Report that a lock has been created at address "lock". */ + #define ANNOTATE_RWLOCK_CREATE(lock) \ + AnnotateRWLockCreate(__FILE__, __LINE__, lock) + + /* Report that the lock at address "lock" is about to be destroyed. */ + #define ANNOTATE_RWLOCK_DESTROY(lock) \ + AnnotateRWLockDestroy(__FILE__, __LINE__, lock) + + /* Report that the lock at address "lock" has been acquired. + is_w=1 for writer lock, is_w=0 for reader lock. */ + #define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \ + AnnotateRWLockAcquired(__FILE__, __LINE__, lock, is_w) + + /* Report that the lock at address "lock" is about to be released. */ + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \ + AnnotateRWLockReleased(__FILE__, __LINE__, lock, is_w) + + #if defined(DYNAMIC_ANNOTATIONS_WANT_ATTRIBUTE_WEAK) + #if defined(__GNUC__) + #define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK __attribute__((weak)) + #else + /* TODO(glider): for Windows support we may want to change this macro in order + to prepend __declspec(selectany) to the annotations' declarations. */ + #error weak annotations are not supported for your compiler + #endif + #else + #define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK + #endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif + + extern "C" { + void AnnotateRWLockCreate( + const char *file, int line, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; + void AnnotateRWLockDestroy( + const char *file, int line, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; + void AnnotateRWLockAcquired( + const char *file, int line, + const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; + void AnnotateRWLockReleased( + const char *file, int line, + const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; + } + +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#else + + #define ANNOTATE_RWLOCK_CREATE(lock) + #define ANNOTATE_RWLOCK_DESTROY(lock) + #define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) + +#endif + +#ifdef SK_DEBUG + + #include "include/private/SkTDArray.h" + #include "include/private/SkThreadID.h" + + class SkSharedMutex::ThreadIDSet { + public: + // Returns true if threadID is in the set. + bool find(SkThreadID threadID) const { + for (auto& t : fThreadIDs) { + if (t == threadID) return true; + } + return false; + } + + // Returns true if did not already exist. + bool tryAdd(SkThreadID threadID) { + for (auto& t : fThreadIDs) { + if (t == threadID) return false; + } + fThreadIDs.append(1, &threadID); + return true; + } + // Returns true if already exists in Set. + bool tryRemove(SkThreadID threadID) { + for (int i = 0; i < fThreadIDs.count(); ++i) { + if (fThreadIDs[i] == threadID) { + fThreadIDs.remove(i); + return true; + } + } + return false; + } + + void swap(ThreadIDSet& other) { + fThreadIDs.swap(other.fThreadIDs); + } + + int count() const { + return fThreadIDs.count(); + } + + private: + SkTDArray fThreadIDs; + }; + + SkSharedMutex::SkSharedMutex() + : fCurrentShared(new ThreadIDSet) + , fWaitingExclusive(new ThreadIDSet) + , fWaitingShared(new ThreadIDSet){ + ANNOTATE_RWLOCK_CREATE(this); + } + + SkSharedMutex::~SkSharedMutex() { ANNOTATE_RWLOCK_DESTROY(this); } + + void SkSharedMutex::acquire() { + SkThreadID threadID(SkGetThreadID()); + int currentSharedCount; + int waitingExclusiveCount; + { + SkAutoMutexExclusive l(fMu); + + SkASSERTF(!fCurrentShared->find(threadID), + "Thread %lx already has an shared lock\n", threadID); + + if (!fWaitingExclusive->tryAdd(threadID)) { + SkDEBUGFAILF("Thread %lx already has an exclusive lock\n", threadID); + } + + currentSharedCount = fCurrentShared->count(); + waitingExclusiveCount = fWaitingExclusive->count(); + } + + if (currentSharedCount > 0 || waitingExclusiveCount > 1) { + fExclusiveQueue.wait(); + } + + ANNOTATE_RWLOCK_ACQUIRED(this, 1); + } + + // Implementation Detail: + // The shared threads need two seperate queues to keep the threads that were added after the + // exclusive lock separate from the threads added before. + void SkSharedMutex::release() { + ANNOTATE_RWLOCK_RELEASED(this, 1); + SkThreadID threadID(SkGetThreadID()); + int sharedWaitingCount; + int exclusiveWaitingCount; + int sharedQueueSelect; + { + SkAutoMutexExclusive l(fMu); + SkASSERT(0 == fCurrentShared->count()); + if (!fWaitingExclusive->tryRemove(threadID)) { + SkDEBUGFAILF("Thread %lx did not have the lock held.\n", threadID); + } + exclusiveWaitingCount = fWaitingExclusive->count(); + sharedWaitingCount = fWaitingShared->count(); + fWaitingShared.swap(fCurrentShared); + sharedQueueSelect = fSharedQueueSelect; + if (sharedWaitingCount > 0) { + fSharedQueueSelect = 1 - fSharedQueueSelect; + } + } + + if (sharedWaitingCount > 0) { + fSharedQueue[sharedQueueSelect].signal(sharedWaitingCount); + } else if (exclusiveWaitingCount > 0) { + fExclusiveQueue.signal(); + } + } + + void SkSharedMutex::assertHeld() const { + SkThreadID threadID(SkGetThreadID()); + SkAutoMutexExclusive l(fMu); + SkASSERT(0 == fCurrentShared->count()); + SkASSERT(fWaitingExclusive->find(threadID)); + } + + void SkSharedMutex::acquireShared() { + SkThreadID threadID(SkGetThreadID()); + int exclusiveWaitingCount; + int sharedQueueSelect; + { + SkAutoMutexExclusive l(fMu); + exclusiveWaitingCount = fWaitingExclusive->count(); + if (exclusiveWaitingCount > 0) { + if (!fWaitingShared->tryAdd(threadID)) { + SkDEBUGFAILF("Thread %lx was already waiting!\n", threadID); + } + } else { + if (!fCurrentShared->tryAdd(threadID)) { + SkDEBUGFAILF("Thread %lx already holds a shared lock!\n", threadID); + } + } + sharedQueueSelect = fSharedQueueSelect; + } + + if (exclusiveWaitingCount > 0) { + fSharedQueue[sharedQueueSelect].wait(); + } + + ANNOTATE_RWLOCK_ACQUIRED(this, 0); + } + + void SkSharedMutex::releaseShared() { + ANNOTATE_RWLOCK_RELEASED(this, 0); + SkThreadID threadID(SkGetThreadID()); + + int currentSharedCount; + int waitingExclusiveCount; + { + SkAutoMutexExclusive l(fMu); + if (!fCurrentShared->tryRemove(threadID)) { + SkDEBUGFAILF("Thread %lx does not hold a shared lock.\n", threadID); + } + currentSharedCount = fCurrentShared->count(); + waitingExclusiveCount = fWaitingExclusive->count(); + } + + if (0 == currentSharedCount && waitingExclusiveCount > 0) { + fExclusiveQueue.signal(); + } + } + + void SkSharedMutex::assertHeldShared() const { + SkThreadID threadID(SkGetThreadID()); + SkAutoMutexExclusive l(fMu); + SkASSERT(fCurrentShared->find(threadID)); + } + +#else + + // The fQueueCounts fields holds many counts in an int32_t in order to make managing them atomic. + // These three counts must be the same size, so each gets 10 bits. The 10 bits represent + // the log of the count which is 1024. + // + // The three counts held in fQueueCounts are: + // * Shared - the number of shared lock holders currently running. + // * WaitingExclusive - the number of threads waiting for an exclusive lock. + // * WaitingShared - the number of threads waiting to run while waiting for an exclusive thread + // to finish. + static const int kLogThreadCount = 10; + + enum { + kSharedOffset = (0 * kLogThreadCount), + kWaitingExlusiveOffset = (1 * kLogThreadCount), + kWaitingSharedOffset = (2 * kLogThreadCount), + kSharedMask = ((1 << kLogThreadCount) - 1) << kSharedOffset, + kWaitingExclusiveMask = ((1 << kLogThreadCount) - 1) << kWaitingExlusiveOffset, + kWaitingSharedMask = ((1 << kLogThreadCount) - 1) << kWaitingSharedOffset, + }; + + SkSharedMutex::SkSharedMutex() : fQueueCounts(0) { ANNOTATE_RWLOCK_CREATE(this); } + SkSharedMutex::~SkSharedMutex() { ANNOTATE_RWLOCK_DESTROY(this); } + void SkSharedMutex::acquire() { + // Increment the count of exclusive queue waiters. + int32_t oldQueueCounts = fQueueCounts.fetch_add(1 << kWaitingExlusiveOffset, + std::memory_order_acquire); + + // If there are no other exclusive waiters and no shared threads are running then run + // else wait. + if ((oldQueueCounts & kWaitingExclusiveMask) > 0 || (oldQueueCounts & kSharedMask) > 0) { + fExclusiveQueue.wait(); + } + ANNOTATE_RWLOCK_ACQUIRED(this, 1); + } + + void SkSharedMutex::release() { + ANNOTATE_RWLOCK_RELEASED(this, 1); + + int32_t oldQueueCounts = fQueueCounts.load(std::memory_order_relaxed); + int32_t waitingShared; + int32_t newQueueCounts; + do { + newQueueCounts = oldQueueCounts; + + // Decrement exclusive waiters. + newQueueCounts -= 1 << kWaitingExlusiveOffset; + + // The number of threads waiting to acquire a shared lock. + waitingShared = (oldQueueCounts & kWaitingSharedMask) >> kWaitingSharedOffset; + + // If there are any move the counts of all the shared waiters to actual shared. They are + // going to run next. + if (waitingShared > 0) { + + // Set waiting shared to zero. + newQueueCounts &= ~kWaitingSharedMask; + + // Because this is the exclusive release, then there are zero readers. So, the bits + // for shared locks should be zero. Since those bits are zero, we can just |= in the + // waitingShared count instead of clearing with an &= and then |= the count. + newQueueCounts |= waitingShared << kSharedOffset; + } + + } while (!fQueueCounts.compare_exchange_strong(oldQueueCounts, newQueueCounts, + std::memory_order_release, + std::memory_order_relaxed)); + + if (waitingShared > 0) { + // Run all the shared. + fSharedQueue.signal(waitingShared); + } else if ((newQueueCounts & kWaitingExclusiveMask) > 0) { + // Run a single exclusive waiter. + fExclusiveQueue.signal(); + } + } + + void SkSharedMutex::acquireShared() { + int32_t oldQueueCounts = fQueueCounts.load(std::memory_order_relaxed); + int32_t newQueueCounts; + do { + newQueueCounts = oldQueueCounts; + // If there are waiting exclusives then this shared lock waits else it runs. + if ((newQueueCounts & kWaitingExclusiveMask) > 0) { + newQueueCounts += 1 << kWaitingSharedOffset; + } else { + newQueueCounts += 1 << kSharedOffset; + } + } while (!fQueueCounts.compare_exchange_strong(oldQueueCounts, newQueueCounts, + std::memory_order_acquire, + std::memory_order_relaxed)); + + // If there are waiting exclusives, then this shared waits until after it runs. + if ((newQueueCounts & kWaitingExclusiveMask) > 0) { + fSharedQueue.wait(); + } + ANNOTATE_RWLOCK_ACQUIRED(this, 0); + + } + + void SkSharedMutex::releaseShared() { + ANNOTATE_RWLOCK_RELEASED(this, 0); + + // Decrement the shared count. + int32_t oldQueueCounts = fQueueCounts.fetch_sub(1 << kSharedOffset, + std::memory_order_release); + + // If shared count is going to zero (because the old count == 1) and there are exclusive + // waiters, then run a single exclusive waiter. + if (((oldQueueCounts & kSharedMask) >> kSharedOffset) == 1 + && (oldQueueCounts & kWaitingExclusiveMask) > 0) { + fExclusiveQueue.signal(); + } + } + +#endif diff --git a/gfx/skia/skia/src/core/SkSharedMutex.h b/gfx/skia/skia/src/core/SkSharedMutex.h new file mode 100644 index 0000000000..2544ee605a --- /dev/null +++ b/gfx/skia/skia/src/core/SkSharedMutex.h @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSharedLock_DEFINED +#define SkSharedLock_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMacros.h" +#include "include/private/SkSemaphore.h" +#include "include/private/SkThreadAnnotations.h" +#include + +#ifdef SK_DEBUG + #include "include/private/SkMutex.h" + #include +#endif // SK_DEBUG + +// There are two shared lock implementations one debug the other is high performance. They implement +// an interface similar to pthread's rwlocks. +// This is a shared lock implementation similar to pthreads rwlocks. The high performance +// implementation is cribbed from Preshing's article: +// http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ +// +// This lock does not obey strict queue ordering. It will always alternate between readers and +// a single writer. +class SK_CAPABILITY("mutex") SkSharedMutex { +public: + SkSharedMutex(); + ~SkSharedMutex(); + // Acquire lock for exclusive use. + void acquire() SK_ACQUIRE(); + + // Release lock for exclusive use. + void release() SK_RELEASE_CAPABILITY(); + + // Fail if exclusive is not held. + void assertHeld() const SK_ASSERT_CAPABILITY(this); + + // Acquire lock for shared use. + void acquireShared() SK_ACQUIRE_SHARED(); + + // Release lock for shared use. + void releaseShared() SK_RELEASE_SHARED_CAPABILITY(); + + // Fail if shared lock not held. + void assertHeldShared() const SK_ASSERT_SHARED_CAPABILITY(this); + +private: +#ifdef SK_DEBUG + class ThreadIDSet; + std::unique_ptr fCurrentShared; + std::unique_ptr fWaitingExclusive; + std::unique_ptr fWaitingShared; + int fSharedQueueSelect{0}; + mutable SkMutex fMu; + SkSemaphore fSharedQueue[2]; + SkSemaphore fExclusiveQueue; +#else + std::atomic fQueueCounts; + SkSemaphore fSharedQueue; + SkSemaphore fExclusiveQueue; +#endif // SK_DEBUG +}; + +#ifndef SK_DEBUG +inline void SkSharedMutex::assertHeld() const {}; +inline void SkSharedMutex::assertHeldShared() const {}; +#endif // SK_DEBUG + +class SK_SCOPED_CAPABILITY SkAutoSharedMutexExclusive { +public: + explicit SkAutoSharedMutexExclusive(SkSharedMutex& lock) SK_ACQUIRE(lock) + : fLock(lock) { + lock.acquire(); + } + ~SkAutoSharedMutexExclusive() SK_RELEASE_CAPABILITY() { fLock.release(); } + +private: + SkSharedMutex& fLock; +}; + +#define SkAutoSharedMutexExclusive(...) SK_REQUIRE_LOCAL_VAR(SkAutoSharedMutexExclusive) + +class SK_SCOPED_CAPABILITY SkAutoSharedMutexShared { +public: + explicit SkAutoSharedMutexShared(SkSharedMutex& lock) SK_ACQUIRE_SHARED(lock) + : fLock(lock) { + lock.acquireShared(); + } + + // You would think this should be SK_RELEASE_SHARED_CAPABILITY, but SK_SCOPED_CAPABILITY + // doesn't fully understand the difference between shared and exclusive. + // Please review https://reviews.llvm.org/D52578 for more information. + ~SkAutoSharedMutexShared() SK_RELEASE_CAPABILITY() { fLock.releaseShared(); } + +private: + SkSharedMutex& fLock; +}; + +#define SkAutoSharedMutexShared(...) SK_REQUIRE_LOCAL_VAR(SkAutoSharedMutexShared) + +#endif // SkSharedLock_DEFINED diff --git a/gfx/skia/skia/src/core/SkSpan.h b/gfx/skia/skia/src/core/SkSpan.h new file mode 100644 index 0000000000..8db1866c79 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpan.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSpan_DEFINED +#define SkSpan_DEFINED + +#include +#include "include/private/SkTo.h" + +template +class SkSpan { +public: + constexpr SkSpan() : fPtr{nullptr}, fSize{0} {} + constexpr SkSpan(T* ptr, size_t size) : fPtr{ptr}, fSize{size} {} + template ::value>::type> + constexpr SkSpan(const SkSpan& that) : fPtr(that.data()), fSize{that.size()} {} + constexpr SkSpan(const SkSpan& o) = default; + constexpr SkSpan& operator=(const SkSpan& that) { + fPtr = that.fPtr; + fSize = that.fSize; + return *this; + } + constexpr T& operator [] (size_t i) const { return fPtr[i]; } + constexpr T& front() const { return fPtr[0]; } + constexpr T& back() const { return fPtr[fSize - 1]; } + constexpr T* begin() const { return fPtr; } + constexpr T* end() const { return fPtr + fSize; } + constexpr const T* cbegin() const { return fPtr; } + constexpr const T* cend() const { return fPtr + fSize; } + constexpr T* data() const { return fPtr; } + constexpr size_t size() const { return fSize; } + constexpr bool empty() const { return fSize == 0; } + constexpr size_t size_bytes() const { return fSize * sizeof(T); } + constexpr SkSpan first(size_t prefixLen) { return SkSpan{fPtr, prefixLen}; } + +private: + T* fPtr; + size_t fSize; +}; + +template +inline constexpr SkSpan SkMakeSpan(T* p, S s) { return SkSpan{p, SkTo(s)}; } + +template +inline constexpr SkSpan SkMakeSpan(T(&a)[N]) { return SkSpan{a, N}; } + +template +inline auto SkMakeSpan(Container& c) + -> SkSpan::type> { + return {c.data(), c.size()}; +} +#endif // SkSpan_DEFINED diff --git a/gfx/skia/skia/src/core/SkSpecialImage.cpp b/gfx/skia/skia/src/core/SkSpecialImage.cpp new file mode 100644 index 0000000000..3c72e616bb --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpecialImage.cpp @@ -0,0 +1,560 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file + */ + +#include "src/core/SkSpecialImage.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkImage.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkSurfacePriv.h" +#include "src/image/SkImage_Base.h" +#include + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/image/SkImage_Gpu.h" +#endif + +// Currently the raster imagefilters can only handle certain imageinfos. Call this to know if +// a given info is supported. +static bool valid_for_imagefilters(const SkImageInfo& info) { + // no support for other swizzles/depths yet + return info.colorType() == kN32_SkColorType; +} + +/////////////////////////////////////////////////////////////////////////////// +class SkSpecialImage_Base : public SkSpecialImage { +public: + SkSpecialImage_Base(const SkIRect& subset, uint32_t uniqueID, const SkSurfaceProps* props) + : INHERITED(subset, uniqueID, props) { + } + ~SkSpecialImage_Base() override { } + + virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) const = 0; + + virtual bool onGetROPixels(SkBitmap*) const = 0; + + virtual GrRecordingContext* onGetContext() const { return nullptr; } + + virtual SkColorSpace* onGetColorSpace() const = 0; + +#if SK_SUPPORT_GPU + virtual sk_sp onAsTextureProxyRef(GrRecordingContext* context) const = 0; +#endif + + // This subset is relative to the backing store's coordinate frame, it has already been mapped + // from the content rect by the non-virtual makeSubset(). + virtual sk_sp onMakeSubset(const SkIRect& subset) const = 0; + + virtual sk_sp onMakeSurface( + SkColorType colorType, const SkColorSpace* colorSpace, const SkISize& size, + SkAlphaType at, const SkSurfaceProps* = nullptr) const = 0; + + // This subset (when not null) is relative to the backing store's coordinate frame, it has + // already been mapped from the content rect by the non-virtual asImage(). + virtual sk_sp onAsImage(const SkIRect* subset) const = 0; + + virtual sk_sp onMakeTightSurface( + SkColorType colorType, const SkColorSpace* colorSpace, + const SkISize& size, SkAlphaType at) const = 0; + +private: + typedef SkSpecialImage INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// +static inline const SkSpecialImage_Base* as_SIB(const SkSpecialImage* image) { + return static_cast(image); +} + +SkSpecialImage::SkSpecialImage(const SkIRect& subset, + uint32_t uniqueID, + const SkSurfaceProps* props) + : fProps(SkSurfacePropsCopyOrDefault(props)) + , fSubset(subset) + , fUniqueID(kNeedNewImageUniqueID_SpecialImage == uniqueID ? SkNextID::ImageID() : uniqueID) { +} + +sk_sp SkSpecialImage::makeTextureImage(GrRecordingContext* context) const { +#if SK_SUPPORT_GPU + if (!context) { + return nullptr; + } + if (GrRecordingContext* curContext = as_SIB(this)->onGetContext()) { + return curContext->priv().matches(context) ? sk_ref_sp(this) : nullptr; + } + + auto proxyProvider = context->priv().proxyProvider(); + SkBitmap bmp; + // At this point, we are definitely not texture-backed, so we must be raster or generator + // backed. If we remove the special-wrapping-an-image subclass, we may be able to assert that + // we are strictly raster-backed (i.e. generator images become raster when they are specialized) + // in which case getROPixels could turn into peekPixels... + if (!this->getROPixels(&bmp)) { + return nullptr; + } + + if (bmp.empty()) { + return SkSpecialImage::MakeFromRaster(SkIRect::MakeEmpty(), bmp, &this->props()); + } + + // TODO: this is a tight copy of 'bmp' but it doesn't have to be (given SkSpecialImage's + // semantics). Since this is cached though we would have to bake the fit into the cache key. + sk_sp proxy = GrMakeCachedBitmapProxy(proxyProvider, bmp); + if (!proxy) { + return nullptr; + } + + const SkIRect rect = SkIRect::MakeWH(proxy->width(), proxy->height()); + + // GrMakeCachedBitmapProxy has uploaded only the specified subset of 'bmp' so we need not + // bother with SkBitmap::getSubset + return SkSpecialImage::MakeDeferredFromGpu(context, + rect, + this->uniqueID(), + std::move(proxy), + SkColorTypeToGrColorType(bmp.colorType()), + sk_ref_sp(this->getColorSpace()), + &this->props(), + this->alphaType()); +#else + return nullptr; +#endif +} + +void SkSpecialImage::draw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) const { + return as_SIB(this)->onDraw(canvas, x, y, paint); +} + +bool SkSpecialImage::getROPixels(SkBitmap* bm) const { + return as_SIB(this)->onGetROPixels(bm); +} + +bool SkSpecialImage::isTextureBacked() const { + return SkToBool(as_SIB(this)->onGetContext()); +} + +GrRecordingContext* SkSpecialImage::getContext() const { + return as_SIB(this)->onGetContext(); +} + +SkColorSpace* SkSpecialImage::getColorSpace() const { + return as_SIB(this)->onGetColorSpace(); +} + +#if SK_SUPPORT_GPU +sk_sp SkSpecialImage::asTextureProxyRef(GrRecordingContext* context) const { + return as_SIB(this)->onAsTextureProxyRef(context); +} +#endif + +sk_sp SkSpecialImage::makeSurface( + SkColorType colorType, const SkColorSpace* colorSpace, const SkISize& size, + SkAlphaType at, const SkSurfaceProps* props) const { + return as_SIB(this)->onMakeSurface(colorType, colorSpace, size, at, props); +} + +sk_sp SkSpecialImage::makeTightSurface( + SkColorType colorType, const SkColorSpace* colorSpace, const SkISize& size, + SkAlphaType at) const { + return as_SIB(this)->onMakeTightSurface(colorType, colorSpace, size, at); +} + +sk_sp SkSpecialImage::makeSubset(const SkIRect& subset) const { + SkIRect absolute = subset.makeOffset(this->subset().topLeft()); + return as_SIB(this)->onMakeSubset(absolute); +} + +sk_sp SkSpecialImage::asImage(const SkIRect* subset) const { + if (subset) { + SkIRect absolute = subset->makeOffset(this->subset().topLeft()); + return as_SIB(this)->onAsImage(&absolute); + } else { + return as_SIB(this)->onAsImage(nullptr); + } +} + +#if defined(SK_DEBUG) || SK_SUPPORT_GPU +static bool rect_fits(const SkIRect& rect, int width, int height) { + if (0 == width && 0 == height) { + SkASSERT(0 == rect.fLeft && 0 == rect.fRight && 0 == rect.fTop && 0 == rect.fBottom); + return true; + } + + return rect.fLeft >= 0 && rect.fLeft < width && rect.fLeft < rect.fRight && + rect.fRight >= 0 && rect.fRight <= width && + rect.fTop >= 0 && rect.fTop < height && rect.fTop < rect.fBottom && + rect.fBottom >= 0 && rect.fBottom <= height; +} +#endif + +sk_sp SkSpecialImage::MakeFromImage(GrRecordingContext* context, + const SkIRect& subset, + sk_sp image, + const SkSurfaceProps* props) { + SkASSERT(rect_fits(subset, image->width(), image->height())); + +#if SK_SUPPORT_GPU + if (sk_sp proxy = as_IB(image)->asTextureProxyRef(context)) { + if (!as_IB(image)->context()->priv().matches(context)) { + return nullptr; + } + + return MakeDeferredFromGpu(context, subset, image->uniqueID(), std::move(proxy), + SkColorTypeToGrColorType(image->colorType()), + image->refColorSpace(), props); + } else +#endif + { + SkBitmap bm; + if (as_IB(image)->getROPixels(&bm)) { + return MakeFromRaster(subset, bm, props); + } + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkSpecialImage_Raster : public SkSpecialImage_Base { +public: + SkSpecialImage_Raster(const SkIRect& subset, const SkBitmap& bm, const SkSurfaceProps* props) + : INHERITED(subset, bm.getGenerationID(), props) + , fBitmap(bm) + { + SkASSERT(bm.pixelRef()); + SkASSERT(fBitmap.getPixels()); + } + + SkAlphaType alphaType() const override { return fBitmap.alphaType(); } + + SkColorType colorType() const override { return fBitmap.colorType(); } + + size_t getSize() const override { return fBitmap.computeByteSize(); } + + void onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) const override { + SkRect dst = SkRect::MakeXYWH(x, y, + this->subset().width(), this->subset().height()); + + canvas->drawBitmapRect(fBitmap, this->subset(), + dst, paint, SkCanvas::kStrict_SrcRectConstraint); + } + + bool onGetROPixels(SkBitmap* bm) const override { + return fBitmap.extractSubset(bm, this->subset()); + } + + SkColorSpace* onGetColorSpace() const override { + return fBitmap.colorSpace(); + } + +#if SK_SUPPORT_GPU + sk_sp onAsTextureProxyRef(GrRecordingContext* context) const override { + if (context) { + return GrMakeCachedBitmapProxy(context->priv().proxyProvider(), fBitmap); + } + + return nullptr; + } +#endif + + sk_sp onMakeSurface(SkColorType colorType, const SkColorSpace* colorSpace, + const SkISize& size, SkAlphaType at, + const SkSurfaceProps* props) const override { + // Ignore the requested color type, the raster backend currently only supports N32 + colorType = kN32_SkColorType; // TODO: find ways to allow f16 + SkImageInfo info = SkImageInfo::Make(size, colorType, at, sk_ref_sp(colorSpace)); + return SkSpecialSurface::MakeRaster(info, props); + } + + sk_sp onMakeSubset(const SkIRect& subset) const override { + // No need to extract subset, onGetROPixels handles that when needed + return SkSpecialImage::MakeFromRaster(subset, fBitmap, &this->props()); + } + + sk_sp onAsImage(const SkIRect* subset) const override { + if (subset) { + SkBitmap subsetBM; + + if (!fBitmap.extractSubset(&subsetBM, *subset)) { + return nullptr; + } + + return SkImage::MakeFromBitmap(subsetBM); + } + + return SkImage::MakeFromBitmap(fBitmap); + } + +sk_sp onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace, + const SkISize& size, SkAlphaType at) const override { + // Ignore the requested color type, the raster backend currently only supports N32 + colorType = kN32_SkColorType; // TODO: find ways to allow f16 + SkImageInfo info = SkImageInfo::Make(size, colorType, at, sk_ref_sp(colorSpace)); + return SkSurface::MakeRaster(info); + } + +private: + SkBitmap fBitmap; + + typedef SkSpecialImage_Base INHERITED; +}; + +sk_sp SkSpecialImage::MakeFromRaster(const SkIRect& subset, + const SkBitmap& bm, + const SkSurfaceProps* props) { + SkASSERT(rect_fits(subset, bm.width(), bm.height())); + + if (!bm.pixelRef()) { + return nullptr; + } + + const SkBitmap* srcBM = &bm; + SkBitmap tmp; + // ImageFilters only handle N32 at the moment, so force our src to be that + if (!valid_for_imagefilters(bm.info())) { + if (!tmp.tryAllocPixels(bm.info().makeColorType(kN32_SkColorType)) || + !bm.readPixels(tmp.info(), tmp.getPixels(), tmp.rowBytes(), 0, 0)) + { + return nullptr; + } + srcBM = &tmp; + } + return sk_make_sp(subset, *srcBM, props); +} + +sk_sp SkSpecialImage::CopyFromRaster(const SkIRect& subset, + const SkBitmap& bm, + const SkSurfaceProps* props) { + SkASSERT(rect_fits(subset, bm.width(), bm.height())); + + if (!bm.pixelRef()) { + return nullptr; + } + + SkBitmap tmp; + SkImageInfo info = bm.info().makeDimensions(subset.size()); + // As in MakeFromRaster, must force src to N32 for ImageFilters + if (!valid_for_imagefilters(bm.info())) { + info = info.makeColorType(kN32_SkColorType); + } + if (!tmp.tryAllocPixels(info)) { + return nullptr; + } + if (!bm.readPixels(tmp.info(), tmp.getPixels(), tmp.rowBytes(), subset.x(), subset.y())) { + return nullptr; + } + + // Since we're making a copy of the raster, the resulting special image is the exact size + // of the requested subset of the original and no longer needs to be offset by subset's left + // and top, since those were relative to the original's buffer. + return sk_make_sp( + SkIRect::MakeWH(subset.width(), subset.height()), tmp, props); +} + +#if SK_SUPPORT_GPU +/////////////////////////////////////////////////////////////////////////////// +static sk_sp wrap_proxy_in_image(GrRecordingContext* context, sk_sp proxy, + SkAlphaType alphaType, sk_sp colorSpace) { + // CONTEXT TODO: remove this use of 'backdoor' to create an SkImage + return sk_make_sp(sk_ref_sp(context->priv().backdoor()), + kNeedNewImageUniqueID, alphaType, + std::move(proxy), std::move(colorSpace)); +} + +class SkSpecialImage_Gpu : public SkSpecialImage_Base { +public: + SkSpecialImage_Gpu(GrRecordingContext* context, const SkIRect& subset, + uint32_t uniqueID, sk_sp proxy, GrColorType ct, + SkAlphaType at, sk_sp colorSpace, const SkSurfaceProps* props) + : INHERITED(subset, uniqueID, props) + , fContext(context) + , fTextureProxy(std::move(proxy)) + , fColorType(ct) + , fAlphaType(at) + , fColorSpace(std::move(colorSpace)) + , fAddedRasterVersionToCache(false) { + } + + ~SkSpecialImage_Gpu() override { + if (fAddedRasterVersionToCache.load()) { + SkNotifyBitmapGenIDIsStale(this->uniqueID()); + } + } + + SkAlphaType alphaType() const override { return fAlphaType; } + + SkColorType colorType() const override { return GrColorTypeToSkColorType(fColorType); } + + size_t getSize() const override { + return fTextureProxy->gpuMemorySize(*fContext->priv().caps()); + } + + void onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) const override { + SkRect dst = SkRect::MakeXYWH(x, y, + this->subset().width(), this->subset().height()); + + // TODO: In this instance we know we're going to draw a sub-portion of the backing + // texture into the canvas so it is okay to wrap it in an SkImage. This poses + // some problems for full deferral however in that when the deferred SkImage_Gpu + // instantiates itself it is going to have to either be okay with having a larger + // than expected backing texture (unlikely) or the 'fit' of the SurfaceProxy needs + // to be tightened (if it is deferred). + sk_sp img = + sk_sp(new SkImage_Gpu(sk_ref_sp(canvas->getGrContext()), this->uniqueID(), + fAlphaType, fTextureProxy, fColorSpace)); + + canvas->drawImageRect(img, this->subset(), + dst, paint, SkCanvas::kStrict_SrcRectConstraint); + } + + GrRecordingContext* onGetContext() const override { return fContext; } + + sk_sp onAsTextureProxyRef(GrRecordingContext*) const override { + return fTextureProxy; + } + + bool onGetROPixels(SkBitmap* dst) const override { + const auto desc = SkBitmapCacheDesc::Make(this->uniqueID(), this->subset()); + if (SkBitmapCache::Find(desc, dst)) { + SkASSERT(dst->getGenerationID() == this->uniqueID()); + SkASSERT(dst->isImmutable()); + SkASSERT(dst->getPixels()); + return true; + } + + SkPixmap pmap; + SkImageInfo info = SkImageInfo::MakeN32(this->width(), this->height(), + this->alphaType(), fColorSpace); + auto rec = SkBitmapCache::Alloc(desc, info, &pmap); + if (!rec) { + return false; + } + auto sContext = fContext->priv().makeWrappedSurfaceContext(fTextureProxy, fColorType, + this->alphaType(), fColorSpace); + if (!sContext) { + return false; + } + + if (!sContext->readPixels(info, pmap.writable_addr(), pmap.rowBytes(), + {this->subset().left(), this->subset().top()})) { + return false; + } + + SkBitmapCache::Add(std::move(rec), dst); + fAddedRasterVersionToCache.store(true); + return true; + } + + SkColorSpace* onGetColorSpace() const override { + return fColorSpace.get(); + } + + sk_sp onMakeSurface(SkColorType colorType, const SkColorSpace* colorSpace, + const SkISize& size, SkAlphaType at, + const SkSurfaceProps* props) const override { + if (!fContext) { + return nullptr; + } + + return SkSpecialSurface::MakeRenderTarget(fContext, size.width(), size.height(), + SkColorTypeToGrColorType(colorType), + sk_ref_sp(colorSpace), props); + } + + sk_sp onMakeSubset(const SkIRect& subset) const override { + return SkSpecialImage::MakeDeferredFromGpu(fContext, + subset, + this->uniqueID(), + fTextureProxy, + fColorType, + fColorSpace, + &this->props(), + fAlphaType); + } + + // TODO: move all the logic here into the subset-flavor GrSurfaceProxy::copy? + sk_sp onAsImage(const SkIRect* subset) const override { + if (subset) { + // TODO: if this becomes a bottle neck we could base this logic on what the size + // will be when it is finally instantiated - but that is more fraught. + if (GrProxyProvider::IsFunctionallyExact(fTextureProxy.get()) && + 0 == subset->fLeft && 0 == subset->fTop && + fTextureProxy->width() == subset->width() && + fTextureProxy->height() == subset->height()) { + fTextureProxy->priv().exactify(false); + // The existing GrTexture is already tight so reuse it in the SkImage + return wrap_proxy_in_image(fContext, fTextureProxy, fAlphaType, fColorSpace); + } + + sk_sp subsetProxy( + GrSurfaceProxy::Copy(fContext, fTextureProxy.get(), fColorType, + GrMipMapped::kNo, *subset, SkBackingFit::kExact, + SkBudgeted::kYes)); + if (!subsetProxy) { + return nullptr; + } + + SkASSERT(subsetProxy->priv().isExact()); + // MDB: this is acceptable (wrapping subsetProxy in an SkImage) bc Copy will + // return a kExact-backed proxy + return wrap_proxy_in_image(fContext, std::move(subsetProxy), fAlphaType, fColorSpace); + } + + fTextureProxy->priv().exactify(true); + + return wrap_proxy_in_image(fContext, fTextureProxy, fAlphaType, fColorSpace); + } + + sk_sp onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace, + const SkISize& size, SkAlphaType at) const override { + // TODO (michaelludwig): Why does this ignore colorType but onMakeSurface doesn't ignore it? + // Once makeTightSurface() goes away, should this type overriding behavior be moved into + // onMakeSurface() or is this unnecessary? + colorType = colorSpace && colorSpace->gammaIsLinear() + ? kRGBA_F16_SkColorType : kRGBA_8888_SkColorType; + SkImageInfo info = SkImageInfo::Make(size, colorType, at, sk_ref_sp(colorSpace)); + // CONTEXT TODO: remove this use of 'backdoor' to create an SkSurface + return SkSurface::MakeRenderTarget(fContext->priv().backdoor(), SkBudgeted::kYes, info); + } + +private: + GrRecordingContext* fContext; + sk_sp fTextureProxy; + const GrColorType fColorType; + const SkAlphaType fAlphaType; + sk_sp fColorSpace; + mutable std::atomic fAddedRasterVersionToCache; + + typedef SkSpecialImage_Base INHERITED; +}; + +sk_sp SkSpecialImage::MakeDeferredFromGpu(GrRecordingContext* context, + const SkIRect& subset, + uint32_t uniqueID, + sk_sp proxy, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props, + SkAlphaType at) { + if (!context || context->priv().abandoned() || !proxy) { + return nullptr; + } + SkASSERT_RELEASE(rect_fits(subset, proxy->width(), proxy->height())); + return sk_make_sp(context, subset, uniqueID, std::move(proxy), colorType, + at, std::move(colorSpace), props); +} +#endif diff --git a/gfx/skia/skia/src/core/SkSpecialImage.h b/gfx/skia/skia/src/core/SkSpecialImage.h new file mode 100644 index 0000000000..d68b966c31 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpecialImage.h @@ -0,0 +1,171 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file + */ + +#ifndef SkSpecialImage_DEFINED +#define SkSpecialImage_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceProps.h" +#include "src/core/SkNextID.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrTypesPriv.h" +#endif + +class GrRecordingContext; +class GrTextureProxy; +class SkBitmap; +class SkCanvas; +class SkImage; +struct SkImageInfo; +class SkPaint; +class SkPixmap; +class SkSpecialSurface; +class SkSurface; + +enum { + kNeedNewImageUniqueID_SpecialImage = 0 +}; + +/** + * This is a restricted form of SkImage solely intended for internal use. It + * differs from SkImage in that: + * - it can only be backed by raster or gpu (no generators) + * - it can be backed by a GrTextureProxy larger than its nominal bounds + * - it can't be drawn tiled + * - it can't be drawn with MIPMAPs + * It is similar to SkImage in that it abstracts how the pixels are stored/represented. + * + * Note: the contents of the backing storage outside of the subset rect are undefined. + */ +class SkSpecialImage : public SkRefCnt { +public: + typedef void* ReleaseContext; + typedef void(*RasterReleaseProc)(void* pixels, ReleaseContext); + + const SkSurfaceProps& props() const { return fProps; } + + int width() const { return fSubset.width(); } + int height() const { return fSubset.height(); } + const SkIRect& subset() const { return fSubset; } + SkColorSpace* getColorSpace() const; + + uint32_t uniqueID() const { return fUniqueID; } + virtual SkAlphaType alphaType() const = 0; + virtual SkColorType colorType() const = 0; + virtual size_t getSize() const = 0; + + /** + * Ensures that a special image is backed by a texture (when GrRecordingContext is non-null). + * If no transformation is required, the returned image may be the same as this special image. + * If this special image is from a different GrRecordingContext, this will fail. + */ + sk_sp makeTextureImage(GrRecordingContext*) const; + + /** + * Draw this SpecialImage into the canvas, automatically taking into account the image's subset + */ + void draw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) const; + + static sk_sp MakeFromImage(GrRecordingContext*, + const SkIRect& subset, + sk_sp, + const SkSurfaceProps* = nullptr); + static sk_sp MakeFromRaster(const SkIRect& subset, + const SkBitmap&, + const SkSurfaceProps* = nullptr); + static sk_sp CopyFromRaster(const SkIRect& subset, + const SkBitmap&, + const SkSurfaceProps* = nullptr); +#if SK_SUPPORT_GPU + static sk_sp MakeDeferredFromGpu(GrRecordingContext*, + const SkIRect& subset, + uint32_t uniqueID, + sk_sp, + GrColorType, + sk_sp, + const SkSurfaceProps* = nullptr, + SkAlphaType at = kPremul_SkAlphaType); +#endif + + /** + * Create a new special surface with a backend that is compatible with this special image. + */ + sk_sp makeSurface(SkColorType colorType, + const SkColorSpace* colorSpace, + const SkISize& size, + SkAlphaType at = kPremul_SkAlphaType, + const SkSurfaceProps* props = nullptr) const; + + /** + * Create a new surface with a backend that is compatible with this special image. + * TODO: switch this to makeSurface once we resolved the naming issue + * TODO (michaelludwig) - This is only used by SkTileImageFilter, which appears should be + * updated to work correctly with subsets and then makeTightSurface() can go away entirely. + */ + sk_sp makeTightSurface(SkColorType colorType, + const SkColorSpace* colorSpace, + const SkISize& size, + SkAlphaType at = kPremul_SkAlphaType) const; + + /** + * Extract a subset of this special image and return it as a special image. + * It may or may not point to the same backing memory. The input 'subset' is relative to the + * special image's content rect. + */ + sk_sp makeSubset(const SkIRect& subset) const; + + /** + * Create an SkImage from the contents of this special image optionally extracting a subset. + * It may or may not point to the same backing memory. + * Note: when no 'subset' parameter is specified the the entire SkSpecialImage will be + * returned - including whatever extra padding may have resulted from a loose fit! + * When the 'subset' parameter is specified the returned image will be tight even if that + * entails a copy! The 'subset' is relative to this special image's content rect. + */ + sk_sp asImage(const SkIRect* subset = nullptr) const; + + /** + * If the SpecialImage is backed by a gpu texture, return true. + */ + bool isTextureBacked() const; + + /** + * Return the GrRecordingContext if the SkSpecialImage is GrTexture-backed + */ + GrRecordingContext* getContext() const; + +#if SK_SUPPORT_GPU + /** + * Regardless of how the underlying backing data is stored, returns the contents as a + * GrTextureProxy. The returned proxy represents the entire backing image, so texture + * coordinates must be mapped from the content rect (e.g. relative to 'subset()') to the proxy's + * space (offset by subset().topLeft()). + */ + sk_sp asTextureProxyRef(GrRecordingContext*) const; +#endif + + /** + * Regardless of the underlying backing store, return the contents as an SkBitmap. + * The returned bitmap represents the subset accessed by this image, thus (0,0) refers to the + * top-left corner of 'subset'. + */ + bool getROPixels(SkBitmap*) const; + +protected: + SkSpecialImage(const SkIRect& subset, uint32_t uniqueID, const SkSurfaceProps*); + +private: + const SkSurfaceProps fProps; + const SkIRect fSubset; + const uint32_t fUniqueID; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkSpecialSurface.cpp b/gfx/skia/skia/src/core/SkSpecialSurface.cpp new file mode 100644 index 0000000000..2f34fa03d0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpecialSurface.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file + */ + +#include "include/core/SkCanvas.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkSurfacePriv.h" + + /////////////////////////////////////////////////////////////////////////////// +class SkSpecialSurface_Base : public SkSpecialSurface { +public: + SkSpecialSurface_Base(const SkIRect& subset, const SkSurfaceProps* props) + : INHERITED(subset, props) + , fCanvas(nullptr) { + } + + virtual ~SkSpecialSurface_Base() { } + + // reset is called after an SkSpecialImage has been snapped + void reset() { fCanvas.reset(); } + + // This can return nullptr if reset has already been called or something when wrong in the ctor + SkCanvas* onGetCanvas() { return fCanvas.get(); } + + virtual sk_sp onMakeImageSnapshot() = 0; + +protected: + std::unique_ptr fCanvas; // initialized by derived classes in ctors + +private: + typedef SkSpecialSurface INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// +static SkSpecialSurface_Base* as_SB(SkSpecialSurface* surface) { + return static_cast(surface); +} + +SkSpecialSurface::SkSpecialSurface(const SkIRect& subset, + const SkSurfaceProps* props) + : fProps(SkSurfacePropsCopyOrDefault(props).flags(), kUnknown_SkPixelGeometry) + , fSubset(subset) { + SkASSERT(fSubset.width() > 0); + SkASSERT(fSubset.height() > 0); +} + +SkCanvas* SkSpecialSurface::getCanvas() { + return as_SB(this)->onGetCanvas(); +} + +sk_sp SkSpecialSurface::makeImageSnapshot() { + sk_sp image(as_SB(this)->onMakeImageSnapshot()); + as_SB(this)->reset(); + return image; // the caller gets the creation ref +} + +/////////////////////////////////////////////////////////////////////////////// +#include "include/core/SkMallocPixelRef.h" + +class SkSpecialSurface_Raster : public SkSpecialSurface_Base { +public: + SkSpecialSurface_Raster(const SkImageInfo& info, + sk_sp pr, + const SkIRect& subset, + const SkSurfaceProps* props) + : INHERITED(subset, props) { + SkASSERT(info.width() == pr->width() && info.height() == pr->height()); + fBitmap.setInfo(info, info.minRowBytes()); + fBitmap.setPixelRef(std::move(pr), 0, 0); + + fCanvas.reset(new SkCanvas(fBitmap, this->props())); + fCanvas->clipRect(SkRect::Make(subset)); +#ifdef SK_IS_BOT + fCanvas->clear(SK_ColorRED); // catch any imageFilter sloppiness +#endif + } + + ~SkSpecialSurface_Raster() override { } + + sk_sp onMakeImageSnapshot() override { + return SkSpecialImage::MakeFromRaster(this->subset(), fBitmap, &this->props()); + } + +private: + SkBitmap fBitmap; + + typedef SkSpecialSurface_Base INHERITED; +}; + +sk_sp SkSpecialSurface::MakeFromBitmap(const SkIRect& subset, SkBitmap& bm, + const SkSurfaceProps* props) { + if (subset.isEmpty() || !SkSurfaceValidateRasterInfo(bm.info(), bm.rowBytes())) { + return nullptr; + } + return sk_make_sp(bm.info(), sk_ref_sp(bm.pixelRef()), subset, props); +} + +sk_sp SkSpecialSurface::MakeRaster(const SkImageInfo& info, + const SkSurfaceProps* props) { + if (!SkSurfaceValidateRasterInfo(info)) { + return nullptr; + } + + sk_sp pr = SkMallocPixelRef::MakeAllocate(info, 0); + if (!pr) { + return nullptr; + } + + const SkIRect subset = SkIRect::MakeWH(info.width(), info.height()); + + return sk_make_sp(info, std::move(pr), subset, props); +} + +#if SK_SUPPORT_GPU +/////////////////////////////////////////////////////////////////////////////// +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMakeUnique.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGpuDevice.h" + +class SkSpecialSurface_Gpu : public SkSpecialSurface_Base { +public: + SkSpecialSurface_Gpu(GrRecordingContext* context, + std::unique_ptr renderTargetContext, + int width, int height, const SkIRect& subset) + : INHERITED(subset, &renderTargetContext->surfaceProps()) + , fProxy(renderTargetContext->asTextureProxyRef()) { + // CONTEXT TODO: remove this use of 'backdoor' to create an SkGpuDevice + auto device = SkGpuDevice::Make(context->priv().backdoor(), std::move(renderTargetContext), + SkGpuDevice::kUninit_InitContents); + if (!device) { + return; + } + + fCanvas.reset(new SkCanvas(std::move(device))); + fCanvas->clipRect(SkRect::Make(subset)); +#ifdef SK_IS_BOT + fCanvas->clear(SK_ColorRED); // catch any imageFilter sloppiness +#endif + } + + sk_sp onMakeImageSnapshot() override { + if (!fProxy) { + return nullptr; + } + GrColorType ct = SkColorTypeToGrColorType(fCanvas->imageInfo().colorType()); + + return SkSpecialImage::MakeDeferredFromGpu(fCanvas->getGrContext(), + this->subset(), + kNeedNewImageUniqueID_SpecialImage, + std::move(fProxy), ct, + fCanvas->imageInfo().refColorSpace(), + &this->props()); + } + +private: + sk_sp fProxy; + typedef SkSpecialSurface_Base INHERITED; +}; + +sk_sp SkSpecialSurface::MakeRenderTarget(GrRecordingContext* context, + int width, int height, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props) { + if (!context) { + return nullptr; + } + auto renderTargetContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, width, height, colorType, std::move(colorSpace), 1, + GrMipMapped::kNo, kBottomLeft_GrSurfaceOrigin, props); + if (!renderTargetContext) { + return nullptr; + } + + const SkIRect subset = SkIRect::MakeWH(width, height); + + return sk_make_sp(context, std::move(renderTargetContext), + width, height, subset); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkSpecialSurface.h b/gfx/skia/skia/src/core/SkSpecialSurface.h new file mode 100644 index 0000000000..f890e1e0ac --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpecialSurface.h @@ -0,0 +1,99 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file + */ + +#ifndef SkSpecialSurface_DEFINED +#define SkSpecialSurface_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceProps.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrTypesPriv.h" +#endif + +class GrBackendFormat; +class GrContext; +class GrRecordingContext; +class SkBitmap; +class SkCanvas; +class SkSpecialImage; + +/** + * SkSpecialSurface is a restricted form of SkSurface solely for internal use. It differs + * from SkSurface in that: + * - it can be backed by GrTextures larger than [ fWidth, fHeight ] + * - it can't be used for tiling + * - it becomes inactive once a snapshot of it is taken (i.e., no copy-on-write) + * - it has no generation ID + */ +class SkSpecialSurface : public SkRefCnt { +public: + const SkSurfaceProps& props() const { return fProps; } + + int width() const { return fSubset.width(); } + int height() const { return fSubset.height(); } + + /** + * Return a canvas that will draw into this surface. This will always + * return the same canvas for a given surface, and is managed/owned by the + * surface. + * + * The canvas will be invalid after 'newImageSnapshot' is called. + */ + SkCanvas* getCanvas(); + + /** + * Returns an image of the current state of the surface pixels up to this + * point. The canvas returned by 'getCanvas' becomes invalidated by this + * call and no more drawing to this surface is allowed. + * + * Note: the caller inherits a ref from this call that must be balanced + */ + sk_sp makeImageSnapshot(); + +#if SK_SUPPORT_GPU + /** + * Allocate a new GPU-backed SkSpecialSurface. If the requested surface cannot + * be created, nullptr will be returned. + */ + static sk_sp MakeRenderTarget(GrRecordingContext*, int width, int height, + GrColorType, sk_sp colorSpace, + const SkSurfaceProps* = nullptr); +#endif + + /** + * Use and existing SkBitmap as the backing store. + */ + static sk_sp MakeFromBitmap(const SkIRect& subset, SkBitmap& bm, + const SkSurfaceProps* = nullptr); + + /** + * Return a new CPU-backed surface, with the memory for the pixels automatically + * allocated. + * + * If the requested surface cannot be created, or the request is not a + * supported configuration, nullptr will be returned. + */ + static sk_sp MakeRaster(const SkImageInfo&, + const SkSurfaceProps* = nullptr); + +protected: + SkSpecialSurface(const SkIRect& subset, const SkSurfaceProps*); + + // For testing only + friend class TestingSpecialSurfaceAccess; + const SkIRect& subset() const { return fSubset; } + +private: + const SkSurfaceProps fProps; + const SkIRect fSubset; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkSpinlock.cpp b/gfx/skia/skia/src/core/SkSpinlock.cpp new file mode 100644 index 0000000000..59ea2b7cb9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpinlock.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSpinlock.h" + +#if 0 + #include "include/private/SkMutex.h" + #include + #include + + static void debug_trace() { + void* stack[64]; + int len = backtrace(stack, SK_ARRAY_COUNT(stack)); + + // As you might imagine, we can't use an SkSpinlock here... + static SkMutex lock; + { + SkAutoMutexExclusive locked(lock); + fprintf(stderr, "\n"); + backtrace_symbols_fd(stack, len, 2/*stderr*/); + fprintf(stderr, "\n"); + } + } +#else + static void debug_trace() {} +#endif + +// Renamed from "pause" to avoid conflict with function defined in unistd.h +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include + static void do_pause() { _mm_pause(); } +#else + static void do_pause() { /*spin*/ } +#endif + +void SkSpinlock::contendedAcquire() { + debug_trace(); + + // To act as a mutex, we need an acquire barrier when we acquire the lock. + while (fLocked.exchange(true, std::memory_order_acquire)) { + do_pause(); + } +} diff --git a/gfx/skia/skia/src/core/SkSpriteBlitter.h b/gfx/skia/skia/src/core/SkSpriteBlitter.h new file mode 100644 index 0000000000..48a873a5ff --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpriteBlitter.h @@ -0,0 +1,49 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSpriteBlitter_DEFINED +#define SkSpriteBlitter_DEFINED + +#include "include/core/SkPixmap.h" +#include "include/core/SkShader.h" +#include "src/core/SkBlitter.h" + +class SkPaint; + +// SkSpriteBlitter specializes SkBlitter in a way to move large rectangles of pixels around. +// Because of this use, the main primitive shifts from blitH style things to the more efficient +// blitRect. +class SkSpriteBlitter : public SkBlitter { +public: + SkSpriteBlitter(const SkPixmap& source); + + virtual void setup(const SkPixmap& dst, int left, int top, const SkPaint&); + + // blitH, blitAntiH, blitV and blitMask should not be called on an SkSpriteBlitter. + void blitH(int x, int y, int width) override; + void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override; + void blitV(int x, int y, int height, SkAlpha alpha) override; + void blitMask(const SkMask&, const SkIRect& clip) override; + + // A SkSpriteBlitter must implement blitRect. + void blitRect(int x, int y, int width, int height) override = 0; + + static SkSpriteBlitter* ChooseL32(const SkPixmap& source, const SkPaint&, SkArenaAlloc*); + static SkSpriteBlitter* ChooseL565(const SkPixmap& source, const SkPaint&, SkArenaAlloc*); + static SkSpriteBlitter* ChooseLA8(const SkPixmap& source, const SkPaint&, SkArenaAlloc*); + +protected: + SkPixmap fDst; + const SkPixmap fSource; + int fLeft, fTop; + const SkPaint* fPaint; + +private: + typedef SkBlitter INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkSpriteBlitter_ARGB32.cpp b/gfx/skia/skia/src/core/SkSpriteBlitter_ARGB32.cpp new file mode 100644 index 0000000000..457929dc32 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpriteBlitter_ARGB32.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkPaint.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlitRow.h" +#include "src/core/SkSpriteBlitter.h" +#include "src/core/SkXfermodePriv.h" +#include "src/utils/SkUTF.h" + +/////////////////////////////////////////////////////////////////////////////// + +class Sprite_D32_S32 : public SkSpriteBlitter { +public: + Sprite_D32_S32(const SkPixmap& src, U8CPU alpha) : INHERITED(src) { + SkASSERT(src.colorType() == kN32_SkColorType); + + unsigned flags32 = 0; + if (255 != alpha) { + flags32 |= SkBlitRow::kGlobalAlpha_Flag32; + } + if (!src.isOpaque()) { + flags32 |= SkBlitRow::kSrcPixelAlpha_Flag32; + } + + fProc32 = SkBlitRow::Factory32(flags32); + fAlpha = alpha; + } + + void blitRect(int x, int y, int width, int height) override { + SkASSERT(width > 0 && height > 0); + uint32_t* SK_RESTRICT dst = fDst.writable_addr32(x, y); + const uint32_t* SK_RESTRICT src = fSource.addr32(x - fLeft, y - fTop); + size_t dstRB = fDst.rowBytes(); + size_t srcRB = fSource.rowBytes(); + SkBlitRow::Proc32 proc = fProc32; + U8CPU alpha = fAlpha; + + do { + proc(dst, src, width, alpha); + dst = (uint32_t* SK_RESTRICT)((char*)dst + dstRB); + src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB); + } while (--height != 0); + } + +private: + SkBlitRow::Proc32 fProc32; + U8CPU fAlpha; + + typedef SkSpriteBlitter INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class Sprite_D32_S32A_Xfer: public SkSpriteBlitter { +public: + Sprite_D32_S32A_Xfer(const SkPixmap& source, const SkPaint& paint) : SkSpriteBlitter(source) { + fXfermode = SkXfermode::Peek(paint.getBlendMode()); + SkASSERT(fXfermode); + } + + void blitRect(int x, int y, int width, int height) override { + SkASSERT(width > 0 && height > 0); + uint32_t* SK_RESTRICT dst = fDst.writable_addr32(x, y); + const uint32_t* SK_RESTRICT src = fSource.addr32(x - fLeft, y - fTop); + size_t dstRB = fDst.rowBytes(); + size_t srcRB = fSource.rowBytes(); + SkXfermode* xfermode = fXfermode; + + do { + xfermode->xfer32(dst, src, width, nullptr); + + dst = (uint32_t* SK_RESTRICT)((char*)dst + dstRB); + src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB); + } while (--height != 0); + } + +protected: + SkXfermode* fXfermode; + +private: + typedef SkSpriteBlitter INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +SkSpriteBlitter* SkSpriteBlitter::ChooseL32(const SkPixmap& source, const SkPaint& paint, + SkArenaAlloc* allocator) { + SkASSERT(allocator != nullptr); + + if (paint.getColorFilter() != nullptr) { + return nullptr; + } + if (paint.getMaskFilter() != nullptr) { + return nullptr; + } + + U8CPU alpha = paint.getAlpha(); + + if (source.colorType() == kN32_SkColorType) { + if (paint.isSrcOver()) { + // this can handle alpha, but not xfermode + return allocator->make(source, alpha); + } + if (255 == alpha) { + // this can handle an xfermode, but not alpha + return allocator->make(source, paint); + } + } + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkSpriteBlitter_RGB565.cpp b/gfx/skia/skia/src/core/SkSpriteBlitter_RGB565.cpp new file mode 100644 index 0000000000..d0d5a99c97 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSpriteBlitter_RGB565.cpp @@ -0,0 +1,180 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkPaint.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlitRow.h" +#include "src/core/SkSpriteBlitter.h" +#include "src/core/SkXfermodePriv.h" +#include "src/utils/SkUTF.h" + +/////////////////////////////////////////////////////////////////////////////// + +static void S32_src(uint16_t dst[], const SkPMColor src[], int count) { + for (int i = 0; i < count; ++i) { + dst[i] = SkPixel32ToPixel16(src[i]); + } +} + +static void S32_srcover(uint16_t dst[], const SkPMColor src[], int count) { + for (int i = 0; i < count; ++i) { + dst[i] = SkSrcOver32To16(src[i], dst[i]); + } +} + +class Sprite_D16_S32 : public SkSpriteBlitter { +public: + Sprite_D16_S32(const SkPixmap& src, SkBlendMode mode) : INHERITED(src) { + SkASSERT(src.colorType() == kN32_SkColorType); + SkASSERT(mode == SkBlendMode::kSrc || mode == SkBlendMode::kSrcOver); + + fUseSrcOver = (mode == SkBlendMode::kSrcOver) && !src.isOpaque(); + } + + void blitRect(int x, int y, int width, int height) override { + SkASSERT(width > 0 && height > 0); + uint16_t* SK_RESTRICT dst = fDst.writable_addr16(x, y); + const uint32_t* SK_RESTRICT src = fSource.addr32(x - fLeft, y - fTop); + size_t dstRB = fDst.rowBytes(); + size_t srcRB = fSource.rowBytes(); + + do { + if (fUseSrcOver) { + S32_srcover(dst, src, width); + } else { + S32_src(dst, src, width); + } + + dst = (uint16_t* SK_RESTRICT)((char*)dst + dstRB); + src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB); + } while (--height != 0); + } + +private: + bool fUseSrcOver; + + typedef SkSpriteBlitter INHERITED; +}; + +SkSpriteBlitter* SkSpriteBlitter::ChooseL565(const SkPixmap& source, const SkPaint& paint, + SkArenaAlloc* allocator) { + SkASSERT(allocator != nullptr); + + if (paint.getColorFilter() != nullptr) { + return nullptr; + } + if (paint.getMaskFilter() != nullptr) { + return nullptr; + } + + U8CPU alpha = paint.getAlpha(); + if (alpha != 0xFF) { + return nullptr; + } + + if (source.colorType() == kN32_SkColorType) { + switch (paint.getBlendMode()) { + case SkBlendMode::kSrc: + case SkBlendMode::kSrcOver: + return allocator->make(source, paint.getBlendMode()); + default: + break; + } + } + return nullptr; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +static unsigned div255(unsigned a, unsigned b) { + return (a * b * 257 + 127) >> 16; +} + +static void S32_src_da8(uint8_t dst[], const SkPMColor src[], int count) { + for (int i = 0; i < count; ++i) { + dst[i] = SkGetPackedA32(src[i]); + } +} + +static void S32_srcover_da8(uint8_t dst[], const SkPMColor src[], int count) { + for (int i = 0; i < count; ++i) { + SkPMColor c = src[i]; + if (c) { + unsigned a = SkGetPackedA32(c); + if (a == 0xFF) { + dst[i] = 0xFF; + } else { + dst[i] = a + div255(255 - a, dst[i]); + } + } + } +} + +class Sprite_D8_S32 : public SkSpriteBlitter { +public: + Sprite_D8_S32(const SkPixmap& src, SkBlendMode mode) : INHERITED(src) { + SkASSERT(src.colorType() == kN32_SkColorType); + SkASSERT(mode == SkBlendMode::kSrc || mode == SkBlendMode::kSrcOver); + + fUseSrcOver = (mode == SkBlendMode::kSrcOver) && !src.isOpaque(); + } + + void blitRect(int x, int y, int width, int height) override { + SkASSERT(width > 0 && height > 0); + uint8_t* SK_RESTRICT dst = fDst.writable_addr8(x, y); + const uint32_t* SK_RESTRICT src = fSource.addr32(x - fLeft, y - fTop); + size_t dstRB = fDst.rowBytes(); + size_t srcRB = fSource.rowBytes(); + + do { + if (fUseSrcOver) { + S32_srcover_da8(dst, src, width); + } else { + S32_src_da8(dst, src, width); + } + + dst = (uint8_t* SK_RESTRICT)((char*)dst + dstRB); + src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB); + } while (--height != 0); + } + +private: + bool fUseSrcOver; + + typedef SkSpriteBlitter INHERITED; +}; + +SkSpriteBlitter* SkSpriteBlitter::ChooseLA8(const SkPixmap& source, const SkPaint& paint, + SkArenaAlloc* allocator) { + SkASSERT(allocator != nullptr); + + if (paint.getColorFilter() != nullptr) { + return nullptr; + } + if (paint.getMaskFilter() != nullptr) { + return nullptr; + } + + U8CPU alpha = paint.getAlpha(); + if (alpha != 0xFF) { + return nullptr; + } + + if (source.colorType() == kN32_SkColorType) { + switch (paint.getBlendMode()) { + case SkBlendMode::kSrc: + case SkBlendMode::kSrcOver: + return allocator->make(source, paint.getBlendMode()); + default: + break; + } + } + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkStream.cpp b/gfx/skia/skia/src/core/SkStream.cpp new file mode 100644 index 0000000000..2bdd47feb4 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStream.cpp @@ -0,0 +1,948 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" + +#include "include/core/SkData.h" +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/private/SkFixed.h" +#include "include/private/SkTFitsIn.h" +#include "include/private/SkTo.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkOSFile.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkStreamPriv.h" + +#include + +/////////////////////////////////////////////////////////////////////////////// + +bool SkStream::readS8(int8_t* i) { + return this->read(i, sizeof(*i)) == sizeof(*i); +} + +bool SkStream::readS16(int16_t* i) { + return this->read(i, sizeof(*i)) == sizeof(*i); +} + +bool SkStream::readS32(int32_t* i) { + return this->read(i, sizeof(*i)) == sizeof(*i); +} + +bool SkStream::readScalar(SkScalar* i) { + return this->read(i, sizeof(*i)) == sizeof(*i); +} + +#define SK_MAX_BYTE_FOR_U8 0xFD +#define SK_BYTE_SENTINEL_FOR_U16 0xFE +#define SK_BYTE_SENTINEL_FOR_U32 0xFF + +bool SkStream::readPackedUInt(size_t* i) { + uint8_t byte; + if (!this->read(&byte, 1)) { + return false; + } + if (SK_BYTE_SENTINEL_FOR_U16 == byte) { + uint16_t i16; + if (!this->readU16(&i16)) { return false; } + *i = i16; + } else if (SK_BYTE_SENTINEL_FOR_U32 == byte) { + uint32_t i32; + if (!this->readU32(&i32)) { return false; } + *i = i32; + } else { + *i = byte; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////// + +SkWStream::~SkWStream() +{ +} + +void SkWStream::flush() +{ +} + +bool SkWStream::writeDecAsText(int32_t dec) +{ + char buffer[SkStrAppendS32_MaxSize]; + char* stop = SkStrAppendS32(buffer, dec); + return this->write(buffer, stop - buffer); +} + +bool SkWStream::writeBigDecAsText(int64_t dec, int minDigits) +{ + char buffer[SkStrAppendU64_MaxSize]; + char* stop = SkStrAppendU64(buffer, dec, minDigits); + return this->write(buffer, stop - buffer); +} + +bool SkWStream::writeHexAsText(uint32_t hex, int digits) +{ + SkString tmp; + tmp.appendHex(hex, digits); + return this->write(tmp.c_str(), tmp.size()); +} + +bool SkWStream::writeScalarAsText(SkScalar value) +{ + char buffer[SkStrAppendScalar_MaxSize]; + char* stop = SkStrAppendScalar(buffer, value); + return this->write(buffer, stop - buffer); +} + +bool SkWStream::writeScalar(SkScalar value) { + return this->write(&value, sizeof(value)); +} + +int SkWStream::SizeOfPackedUInt(size_t value) { + if (value <= SK_MAX_BYTE_FOR_U8) { + return 1; + } else if (value <= 0xFFFF) { + return 3; + } + return 5; +} + +bool SkWStream::writePackedUInt(size_t value) { + uint8_t data[5]; + size_t len = 1; + if (value <= SK_MAX_BYTE_FOR_U8) { + data[0] = value; + len = 1; + } else if (value <= 0xFFFF) { + uint16_t value16 = value; + data[0] = SK_BYTE_SENTINEL_FOR_U16; + memcpy(&data[1], &value16, 2); + len = 3; + } else { + uint32_t value32 = SkToU32(value); + data[0] = SK_BYTE_SENTINEL_FOR_U32; + memcpy(&data[1], &value32, 4); + len = 5; + } + return this->write(data, len); +} + +bool SkWStream::writeStream(SkStream* stream, size_t length) { + char scratch[1024]; + const size_t MAX = sizeof(scratch); + + while (length != 0) { + size_t n = length; + if (n > MAX) { + n = MAX; + } + stream->read(scratch, n); + if (!this->write(scratch, n)) { + return false; + } + length -= n; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkFILEStream::SkFILEStream(std::shared_ptr file, size_t size, + size_t offset, size_t originalOffset) + : fFILE(std::move(file)) + , fSize(size) + , fOffset(SkTMin(offset, fSize)) + , fOriginalOffset(SkTMin(originalOffset, fSize)) +{ } + +SkFILEStream::SkFILEStream(std::shared_ptr file, size_t size, size_t offset) + : SkFILEStream(std::move(file), size, offset, offset) +{ } + +SkFILEStream::SkFILEStream(FILE* file) + : SkFILEStream(std::shared_ptr(file, sk_fclose), + file ? sk_fgetsize(file) : 0, + file ? sk_ftell(file) : 0) +{ } + + +SkFILEStream::SkFILEStream(const char path[]) + : SkFILEStream(path ? sk_fopen(path, kRead_SkFILE_Flag) : nullptr) +{ } + +SkFILEStream::~SkFILEStream() { + this->close(); +} + +void SkFILEStream::close() { + fFILE.reset(); + fSize = 0; + fOffset = 0; +} + +size_t SkFILEStream::read(void* buffer, size_t size) { + if (size > fSize - fOffset) { + size = fSize - fOffset; + } + size_t bytesRead = size; + if (buffer) { + bytesRead = sk_qread(fFILE.get(), buffer, size, fOffset); + } + if (bytesRead == SIZE_MAX) { + return 0; + } + fOffset += bytesRead; + return bytesRead; +} + +bool SkFILEStream::isAtEnd() const { + if (fOffset == fSize) { + return true; + } + return fOffset >= sk_fgetsize(fFILE.get()); +} + +bool SkFILEStream::rewind() { + fOffset = fOriginalOffset; + return true; +} + +SkStreamAsset* SkFILEStream::onDuplicate() const { + return new SkFILEStream(fFILE, fSize, fOriginalOffset, fOriginalOffset); +} + +size_t SkFILEStream::getPosition() const { + SkASSERT(fOffset >= fOriginalOffset); + return fOffset - fOriginalOffset; +} + +bool SkFILEStream::seek(size_t position) { + fOffset = SkTMin(SkSafeMath::Add(position, fOriginalOffset), fSize); + return true; +} + +bool SkFILEStream::move(long offset) { + if (offset < 0) { + if (offset == std::numeric_limits::min() + || !SkTFitsIn(-offset) + || (size_t) (-offset) >= this->getPosition()) { + fOffset = fOriginalOffset; + } else { + fOffset += offset; + } + } else if (!SkTFitsIn(offset)) { + fOffset = fSize; + } else { + fOffset = SkTMin(SkSafeMath::Add(fOffset, (size_t) offset), fSize); + } + + SkASSERT(fOffset >= fOriginalOffset && fOffset <= fSize); + return true; +} + +SkStreamAsset* SkFILEStream::onFork() const { + return new SkFILEStream(fFILE, fSize, fOffset, fOriginalOffset); +} + +size_t SkFILEStream::getLength() const { + return fSize - fOriginalOffset; +} + +/////////////////////////////////////////////////////////////////////////////// + +static sk_sp newFromParams(const void* src, size_t size, bool copyData) { + if (copyData) { + return SkData::MakeWithCopy(src, size); + } else { + return SkData::MakeWithoutCopy(src, size); + } +} + +SkMemoryStream::SkMemoryStream() { + fData = SkData::MakeEmpty(); + fOffset = 0; +} + +SkMemoryStream::SkMemoryStream(size_t size) { + fData = SkData::MakeUninitialized(size); + fOffset = 0; +} + +SkMemoryStream::SkMemoryStream(const void* src, size_t size, bool copyData) { + fData = newFromParams(src, size, copyData); + fOffset = 0; +} + +SkMemoryStream::SkMemoryStream(sk_sp data) : fData(std::move(data)) { + if (nullptr == fData) { + fData = SkData::MakeEmpty(); + } + fOffset = 0; +} + +std::unique_ptr SkMemoryStream::MakeCopy(const void* data, size_t length) { + return skstd::make_unique(data, length, true); +} + +std::unique_ptr SkMemoryStream::MakeDirect(const void* data, size_t length) { + return skstd::make_unique(data, length, false); +} + +std::unique_ptr SkMemoryStream::Make(sk_sp data) { + return skstd::make_unique(std::move(data)); +} + +void SkMemoryStream::setMemoryOwned(const void* src, size_t size) { + fData = SkData::MakeFromMalloc(src, size); + fOffset = 0; +} + +void SkMemoryStream::setMemory(const void* src, size_t size, bool copyData) { + fData = newFromParams(src, size, copyData); + fOffset = 0; +} + +void SkMemoryStream::setData(sk_sp data) { + if (nullptr == data) { + fData = SkData::MakeEmpty(); + } else { + fData = data; + } + fOffset = 0; +} + +void SkMemoryStream::skipToAlign4() { + // cast to remove unary-minus warning + fOffset += -(int)fOffset & 0x03; +} + +size_t SkMemoryStream::read(void* buffer, size_t size) { + size_t dataSize = fData->size(); + + if (size > dataSize - fOffset) { + size = dataSize - fOffset; + } + if (buffer) { + memcpy(buffer, fData->bytes() + fOffset, size); + } + fOffset += size; + return size; +} + +size_t SkMemoryStream::peek(void* buffer, size_t size) const { + SkASSERT(buffer != nullptr); + + const size_t currentOffset = fOffset; + SkMemoryStream* nonConstThis = const_cast(this); + const size_t bytesRead = nonConstThis->read(buffer, size); + nonConstThis->fOffset = currentOffset; + return bytesRead; +} + +bool SkMemoryStream::isAtEnd() const { + return fOffset == fData->size(); +} + +bool SkMemoryStream::rewind() { + fOffset = 0; + return true; +} + +SkMemoryStream* SkMemoryStream::onDuplicate() const { + return new SkMemoryStream(fData); +} + +size_t SkMemoryStream::getPosition() const { + return fOffset; +} + +bool SkMemoryStream::seek(size_t position) { + fOffset = position > fData->size() + ? fData->size() + : position; + return true; +} + +bool SkMemoryStream::move(long offset) { + return this->seek(fOffset + offset); +} + +SkMemoryStream* SkMemoryStream::onFork() const { + std::unique_ptr that(this->duplicate()); + that->seek(fOffset); + return that.release(); +} + +size_t SkMemoryStream::getLength() const { + return fData->size(); +} + +const void* SkMemoryStream::getMemoryBase() { + return fData->data(); +} + +const void* SkMemoryStream::getAtPos() { + return fData->bytes() + fOffset; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +SkFILEWStream::SkFILEWStream(const char path[]) +{ + fFILE = sk_fopen(path, kWrite_SkFILE_Flag); +} + +SkFILEWStream::~SkFILEWStream() +{ + if (fFILE) { + sk_fclose(fFILE); + } +} + +size_t SkFILEWStream::bytesWritten() const { + return sk_ftell(fFILE); +} + +bool SkFILEWStream::write(const void* buffer, size_t size) +{ + if (fFILE == nullptr) { + return false; + } + + if (sk_fwrite(buffer, size, fFILE) != size) + { + SkDEBUGCODE(SkDebugf("SkFILEWStream failed writing %d bytes\n", size);) + sk_fclose(fFILE); + fFILE = nullptr; + return false; + } + return true; +} + +void SkFILEWStream::flush() +{ + if (fFILE) { + sk_fflush(fFILE); + } +} + +void SkFILEWStream::fsync() +{ + flush(); + if (fFILE) { + sk_fsync(fFILE); + } +} + +//////////////////////////////////////////////////////////////////////// + +static inline void sk_memcpy_4bytes(void* dst, const void* src, size_t size) { + if (size == 4) { + memcpy(dst, src, 4); + } else { + memcpy(dst, src, size); + } +} + +#define SkDynamicMemoryWStream_MinBlockSize 4096 + +struct SkDynamicMemoryWStream::Block { + Block* fNext; + char* fCurr; + char* fStop; + + const char* start() const { return (const char*)(this + 1); } + char* start() { return (char*)(this + 1); } + size_t avail() const { return fStop - fCurr; } + size_t written() const { return fCurr - this->start(); } + + void init(size_t size) { + fNext = nullptr; + fCurr = this->start(); + fStop = this->start() + size; + } + + const void* append(const void* data, size_t size) { + SkASSERT((size_t)(fStop - fCurr) >= size); + sk_memcpy_4bytes(fCurr, data, size); + fCurr += size; + return (const void*)((const char*)data + size); + } +}; + +SkDynamicMemoryWStream::SkDynamicMemoryWStream(SkDynamicMemoryWStream&& other) + : fHead(other.fHead) + , fTail(other.fTail) + , fBytesWrittenBeforeTail(other.fBytesWrittenBeforeTail) +{ + other.fHead = nullptr; + other.fTail = nullptr; + other.fBytesWrittenBeforeTail = 0; +} + +SkDynamicMemoryWStream& SkDynamicMemoryWStream::operator=(SkDynamicMemoryWStream&& other) { + if (this != &other) { + this->~SkDynamicMemoryWStream(); + new (this) SkDynamicMemoryWStream(std::move(other)); + } + return *this; +} + +SkDynamicMemoryWStream::~SkDynamicMemoryWStream() { + this->reset(); +} + +void SkDynamicMemoryWStream::reset() { + Block* block = fHead; + while (block != nullptr) { + Block* next = block->fNext; + sk_free(block); + block = next; + } + fHead = fTail = nullptr; + fBytesWrittenBeforeTail = 0; +} + +size_t SkDynamicMemoryWStream::bytesWritten() const { + this->validate(); + + if (fTail) { + return fBytesWrittenBeforeTail + fTail->written(); + } + return 0; +} + +bool SkDynamicMemoryWStream::write(const void* buffer, size_t count) { + if (count > 0) { + SkASSERT(buffer); + size_t size; + + if (fTail) { + if (fTail->avail() > 0) { + size = SkTMin(fTail->avail(), count); + buffer = fTail->append(buffer, size); + SkASSERT(count >= size); + count -= size; + if (count == 0) { + return true; + } + } + // If we get here, we've just exhausted fTail, so update our tracker + fBytesWrittenBeforeTail += fTail->written(); + } + + size = SkTMax(count, SkDynamicMemoryWStream_MinBlockSize - sizeof(Block)); + size = SkAlign4(size); // ensure we're always a multiple of 4 (see padToAlign4()) + + Block* block = (Block*)sk_malloc_throw(sizeof(Block) + size); + block->init(size); + block->append(buffer, count); + + if (fTail != nullptr) { + fTail->fNext = block; + } else { + fHead = fTail = block; + } + fTail = block; + this->validate(); + } + return true; +} + +bool SkDynamicMemoryWStream::writeToAndReset(SkDynamicMemoryWStream* dst) { + SkASSERT(dst); + SkASSERT(dst != this); + if (0 == this->bytesWritten()) { + return true; + } + if (0 == dst->bytesWritten()) { + *dst = std::move(*this); + return true; + } + dst->fTail->fNext = fHead; + dst->fBytesWrittenBeforeTail += fBytesWrittenBeforeTail + dst->fTail->written(); + dst->fTail = fTail; + fHead = fTail = nullptr; + fBytesWrittenBeforeTail = 0; + return true; +} + +void SkDynamicMemoryWStream::prependToAndReset(SkDynamicMemoryWStream* dst) { + SkASSERT(dst); + SkASSERT(dst != this); + if (0 == this->bytesWritten()) { + return; + } + if (0 == dst->bytesWritten()) { + *dst = std::move(*this); + return; + } + fTail->fNext = dst->fHead; + dst->fHead = fHead; + dst->fBytesWrittenBeforeTail += fBytesWrittenBeforeTail + fTail->written(); + fHead = fTail = nullptr; + fBytesWrittenBeforeTail = 0; + return; +} + + +bool SkDynamicMemoryWStream::read(void* buffer, size_t offset, size_t count) { + if (offset + count > this->bytesWritten()) { + return false; // test does not partially modify + } + Block* block = fHead; + while (block != nullptr) { + size_t size = block->written(); + if (offset < size) { + size_t part = offset + count > size ? size - offset : count; + memcpy(buffer, block->start() + offset, part); + if (count <= part) { + return true; + } + count -= part; + buffer = (void*) ((char* ) buffer + part); + } + offset = offset > size ? offset - size : 0; + block = block->fNext; + } + return false; +} + +void SkDynamicMemoryWStream::copyTo(void* dst) const { + SkASSERT(dst); + Block* block = fHead; + while (block != nullptr) { + size_t size = block->written(); + memcpy(dst, block->start(), size); + dst = (void*)((char*)dst + size); + block = block->fNext; + } +} + +bool SkDynamicMemoryWStream::writeToStream(SkWStream* dst) const { + SkASSERT(dst); + for (Block* block = fHead; block != nullptr; block = block->fNext) { + if (!dst->write(block->start(), block->written())) { + return false; + } + } + return true; +} + +void SkDynamicMemoryWStream::padToAlign4() { + // The contract is to write zeros until the entire stream has written a multiple of 4 bytes. + // Our Blocks are guaranteed always be (a) full (except the tail) and (b) a multiple of 4 + // so it is sufficient to just examine the tail (if present). + + if (fTail) { + // cast to remove unary-minus warning + int padBytes = -(int)fTail->written() & 0x03; + if (padBytes) { + int zero = 0; + fTail->append(&zero, padBytes); + } + } +} + + +void SkDynamicMemoryWStream::copyToAndReset(void* ptr) { + if (!ptr) { + this->reset(); + return; + } + // By looping through the source and freeing as we copy, we + // can reduce real memory use with large streams. + char* dst = reinterpret_cast(ptr); + Block* block = fHead; + while (block != nullptr) { + size_t len = block->written(); + memcpy(dst, block->start(), len); + dst += len; + Block* next = block->fNext; + sk_free(block); + block = next; + } + fHead = fTail = nullptr; + fBytesWrittenBeforeTail = 0; +} + +bool SkDynamicMemoryWStream::writeToAndReset(SkWStream* dst) { + SkASSERT(dst); + // By looping through the source and freeing as we copy, we + // can reduce real memory use with large streams. + bool dstStreamGood = true; + for (Block* block = fHead; block != nullptr; ) { + if (dstStreamGood && !dst->write(block->start(), block->written())) { + dstStreamGood = false; + } + Block* next = block->fNext; + sk_free(block); + block = next; + } + fHead = fTail = nullptr; + fBytesWrittenBeforeTail = 0; + return dstStreamGood; +} + +sk_sp SkDynamicMemoryWStream::detachAsData() { + const size_t size = this->bytesWritten(); + if (0 == size) { + return SkData::MakeEmpty(); + } + sk_sp data = SkData::MakeUninitialized(size); + this->copyToAndReset(data->writable_data()); + return data; +} + +#ifdef SK_DEBUG +void SkDynamicMemoryWStream::validate() const { + if (!fHead) { + SkASSERT(!fTail); + SkASSERT(fBytesWrittenBeforeTail == 0); + return; + } + SkASSERT(fTail); + + size_t bytes = 0; + const Block* block = fHead; + while (block) { + if (block->fNext) { + bytes += block->written(); + } + block = block->fNext; + } + SkASSERT(bytes == fBytesWrittenBeforeTail); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////// + +class SkBlockMemoryRefCnt : public SkRefCnt { +public: + explicit SkBlockMemoryRefCnt(SkDynamicMemoryWStream::Block* head) : fHead(head) { } + + virtual ~SkBlockMemoryRefCnt() { + SkDynamicMemoryWStream::Block* block = fHead; + while (block != nullptr) { + SkDynamicMemoryWStream::Block* next = block->fNext; + sk_free(block); + block = next; + } + } + + SkDynamicMemoryWStream::Block* const fHead; +}; + +class SkBlockMemoryStream : public SkStreamAsset { +public: + SkBlockMemoryStream(sk_sp headRef, size_t size) + : fBlockMemory(std::move(headRef)), fCurrent(fBlockMemory->fHead) + , fSize(size) , fOffset(0), fCurrentOffset(0) { } + + size_t read(void* buffer, size_t rawCount) override { + size_t count = rawCount; + if (fOffset + count > fSize) { + count = fSize - fOffset; + } + size_t bytesLeftToRead = count; + while (fCurrent != nullptr) { + size_t bytesLeftInCurrent = fCurrent->written() - fCurrentOffset; + size_t bytesFromCurrent = SkTMin(bytesLeftToRead, bytesLeftInCurrent); + if (buffer) { + memcpy(buffer, fCurrent->start() + fCurrentOffset, bytesFromCurrent); + buffer = SkTAddOffset(buffer, bytesFromCurrent); + } + if (bytesLeftToRead <= bytesFromCurrent) { + fCurrentOffset += bytesFromCurrent; + fOffset += count; + return count; + } + bytesLeftToRead -= bytesFromCurrent; + fCurrent = fCurrent->fNext; + fCurrentOffset = 0; + } + SkASSERT(false); + return 0; + } + + bool isAtEnd() const override { + return fOffset == fSize; + } + + size_t peek(void* buff, size_t bytesToPeek) const override { + SkASSERT(buff != nullptr); + + bytesToPeek = SkTMin(bytesToPeek, fSize - fOffset); + + size_t bytesLeftToPeek = bytesToPeek; + char* buffer = static_cast(buff); + const SkDynamicMemoryWStream::Block* current = fCurrent; + size_t currentOffset = fCurrentOffset; + while (bytesLeftToPeek) { + SkASSERT(current); + size_t bytesFromCurrent = SkTMin(current->written() - currentOffset, bytesLeftToPeek); + memcpy(buffer, current->start() + currentOffset, bytesFromCurrent); + bytesLeftToPeek -= bytesFromCurrent; + buffer += bytesFromCurrent; + current = current->fNext; + currentOffset = 0; + } + return bytesToPeek; + } + + bool rewind() override { + fCurrent = fBlockMemory->fHead; + fOffset = 0; + fCurrentOffset = 0; + return true; + } + + SkBlockMemoryStream* onDuplicate() const override { + return new SkBlockMemoryStream(fBlockMemory, fSize); + } + + size_t getPosition() const override { + return fOffset; + } + + bool seek(size_t position) override { + // If possible, skip forward. + if (position >= fOffset) { + size_t skipAmount = position - fOffset; + return this->skip(skipAmount) == skipAmount; + } + // If possible, move backward within the current block. + size_t moveBackAmount = fOffset - position; + if (moveBackAmount <= fCurrentOffset) { + fCurrentOffset -= moveBackAmount; + fOffset -= moveBackAmount; + return true; + } + // Otherwise rewind and move forward. + return this->rewind() && this->skip(position) == position; + } + + bool move(long offset) override { + return seek(fOffset + offset); + } + + SkBlockMemoryStream* onFork() const override { + SkBlockMemoryStream* that = this->onDuplicate(); + that->fCurrent = this->fCurrent; + that->fOffset = this->fOffset; + that->fCurrentOffset = this->fCurrentOffset; + return that; + } + + size_t getLength() const override { + return fSize; + } + + const void* getMemoryBase() override { + if (fBlockMemory->fHead && !fBlockMemory->fHead->fNext) { + return fBlockMemory->fHead->start(); + } + return nullptr; + } + +private: + sk_sp const fBlockMemory; + SkDynamicMemoryWStream::Block const * fCurrent; + size_t const fSize; + size_t fOffset; + size_t fCurrentOffset; +}; + +std::unique_ptr SkDynamicMemoryWStream::detachAsStream() { + if (nullptr == fHead) { + // no need to reset. + return SkMemoryStream::Make(nullptr); + } + if (fHead == fTail) { // one block, may be worth shrinking. + ptrdiff_t used = fTail->fCurr - (char*)fTail; + fHead = fTail = (SkDynamicMemoryWStream::Block*)sk_realloc_throw(fTail, SkToSizeT(used)); + fTail->fStop = fTail->fCurr = (char*)fTail + used; // Update pointers. + SkASSERT(nullptr == fTail->fNext); + SkASSERT(0 == fBytesWrittenBeforeTail); + } + std::unique_ptr stream + = skstd::make_unique(sk_make_sp(fHead), + this->bytesWritten()); + fHead = nullptr; // signal reset() to not free anything + this->reset(); + return stream; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static sk_sp mmap_filename(const char path[]) { + FILE* file = sk_fopen(path, kRead_SkFILE_Flag); + if (nullptr == file) { + return nullptr; + } + + auto data = SkData::MakeFromFILE(file); + sk_fclose(file); + return data; +} + +std::unique_ptr SkStream::MakeFromFile(const char path[]) { + auto data(mmap_filename(path)); + if (data) { + return skstd::make_unique(std::move(data)); + } + + // If we get here, then our attempt at using mmap failed, so try normal file access. + auto stream = skstd::make_unique(path); + if (!stream->isValid()) { + return nullptr; + } + return stream; +} + +// Declared in SkStreamPriv.h: +sk_sp SkCopyStreamToData(SkStream* stream) { + SkASSERT(stream != nullptr); + + if (stream->hasLength()) { + return SkData::MakeFromStream(stream, stream->getLength()); + } + + SkDynamicMemoryWStream tempStream; + const size_t bufferSize = 4096; + char buffer[bufferSize]; + do { + size_t bytesRead = stream->read(buffer, bufferSize); + tempStream.write(buffer, bytesRead); + } while (!stream->isAtEnd()); + return tempStream.detachAsData(); +} + +bool SkStreamCopy(SkWStream* out, SkStream* input) { + const char* base = static_cast(input->getMemoryBase()); + if (base && input->hasPosition() && input->hasLength()) { + // Shortcut that avoids the while loop. + size_t position = input->getPosition(); + size_t length = input->getLength(); + SkASSERT(length >= position); + return out->write(&base[position], length - position); + } + char scratch[4096]; + size_t count; + while (true) { + count = input->read(scratch, sizeof(scratch)); + if (0 == count) { + return true; + } + if (!out->write(scratch, count)) { + return false; + } + } +} diff --git a/gfx/skia/skia/src/core/SkStreamPriv.h b/gfx/skia/skia/src/core/SkStreamPriv.h new file mode 100644 index 0000000000..d9b6cc6b4f --- /dev/null +++ b/gfx/skia/skia/src/core/SkStreamPriv.h @@ -0,0 +1,34 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStreamPriv_DEFINED +#define SkStreamPriv_DEFINED + +#include "include/core/SkRefCnt.h" + +class SkData; +class SkStream; +class SkWStream; + +/** + * Copy the provided stream to an SkData variable. + * + * Note: Assumes the stream is at the beginning. If it has a length, + * but is not at the beginning, this call will fail (return NULL). + * + * @param stream SkStream to be copied into data. + * @return The resulting SkData after the copy, nullptr on failure. + */ +sk_sp SkCopyStreamToData(SkStream* stream); + +/** + * Copies the input stream from the current position to the end. + * Does not rewind the input stream. + */ +bool SkStreamCopy(SkWStream* out, SkStream* input); + +#endif // SkStreamPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkStrike.cpp b/gfx/skia/skia/src/core/SkStrike.cpp new file mode 100644 index 0000000000..1c444c8d04 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrike.cpp @@ -0,0 +1,291 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkStrike.h" + +#include "include/core/SkGraphics.h" +#include "include/core/SkPath.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkMutex.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkEnumerate.h" +#include "src/core/SkMakeUnique.h" +#include + +SkStrike::SkStrike( + const SkDescriptor& desc, + std::unique_ptr scaler, + const SkFontMetrics& fontMetrics) + : fDesc{desc} + , fScalerContext{std::move(scaler)} + , fFontMetrics{fontMetrics} + , fRoundingSpec{fScalerContext->isSubpixel(), + fScalerContext->computeAxisAlignmentForHText()} { + SkASSERT(fScalerContext != nullptr); + fMemoryUsed = sizeof(*this); +} + +#ifdef SK_DEBUG +#define VALIDATE() AutoValidate av(this) +#else +#define VALIDATE() +#endif + +// -- glyph creation ------------------------------------------------------------------------------- +SkGlyph* SkStrike::makeGlyph(SkPackedGlyphID packedGlyphID) { + fMemoryUsed += sizeof(SkGlyph); + SkGlyph* glyph = fAlloc.make(packedGlyphID); + fGlyphMap.set(glyph); + return glyph; +} + +SkGlyph* SkStrike::glyph(SkPackedGlyphID packedGlyphID) { + VALIDATE(); + SkGlyph* glyph = fGlyphMap.findOrNull(packedGlyphID); + if (glyph == nullptr) { + glyph = this->makeGlyph(packedGlyphID); + fScalerContext->getMetrics(glyph); + } + return glyph; +} + +SkGlyph* SkStrike::glyph(SkGlyphID glyphID) { + return this->glyph(SkPackedGlyphID{glyphID}); +} + +SkGlyph* SkStrike::glyph(SkGlyphID glyphID, SkPoint position) { + SkIPoint mask = fRoundingSpec.ignorePositionMask; + SkFixed subX = SkScalarToFixed(position.x()) & mask.x(), + subY = SkScalarToFixed(position.y()) & mask.y(); + return this->glyph(SkPackedGlyphID{glyphID, subX, subY}); +} + +SkGlyph* SkStrike::glyphFromPrototype(const SkGlyphPrototype& p, void* image) { + SkGlyph* glyph = fGlyphMap.findOrNull(p.id); + if (glyph == nullptr) { + fMemoryUsed += sizeof(SkGlyph); + glyph = fAlloc.make(p); + fGlyphMap.set(glyph); + } + if (glyph->setImage(&fAlloc, image)) { + fMemoryUsed += glyph->imageSize(); + } + return glyph; +} + +SkGlyph* SkStrike::glyphOrNull(SkPackedGlyphID id) const { + return fGlyphMap.findOrNull(id); +} + +const SkPath* SkStrike::preparePath(SkGlyph* glyph) { + if (glyph->setPath(&fAlloc, fScalerContext.get())) { + fMemoryUsed += glyph->path()->approximateBytesUsed(); + } + return glyph->path(); +} + +const SkPath* SkStrike::preparePath(SkGlyph* glyph, const SkPath* path) { + if (glyph->setPath(&fAlloc, path)) { + fMemoryUsed += glyph->path()->approximateBytesUsed(); + } + return glyph->path(); +} + +const SkDescriptor& SkStrike::getDescriptor() const { + return *fDesc.getDesc(); +} + +unsigned SkStrike::getGlyphCount() const { + return fScalerContext->getGlyphCount(); +} + +int SkStrike::countCachedGlyphs() const { + return fGlyphMap.count(); +} + +SkSpan SkStrike::internalPrepare( + SkSpan glyphIDs, PathDetail pathDetail, const SkGlyph** results) { + const SkGlyph** cursor = results; + for (auto glyphID : glyphIDs) { + SkGlyph* glyphPtr = this->glyph(glyphID); + if (pathDetail == kMetricsAndPath) { + this->preparePath(glyphPtr); + } + *cursor++ = glyphPtr; + } + + return {results, glyphIDs.size()}; +} + +const void* SkStrike::prepareImage(SkGlyph* glyph) { + if (glyph->setImage(&fAlloc, fScalerContext.get())) { + fMemoryUsed += glyph->imageSize(); + } + return glyph->image(); +} + +SkGlyph* SkStrike::mergeGlyphAndImage(SkPackedGlyphID toID, const SkGlyph& from) { + SkGlyph* glyph = fGlyphMap.findOrNull(toID); + if (glyph == nullptr) { + glyph = this->makeGlyph(toID); + } + if (glyph->setMetricsAndImage(&fAlloc, from)) { + fMemoryUsed += glyph->imageSize(); + } + return glyph; +} + +bool SkStrike::belongsToCache(const SkGlyph* glyph) const { + return glyph && fGlyphMap.findOrNull(glyph->getPackedID()) == glyph; +} + +const SkGlyph* SkStrike::getCachedGlyphAnySubPix(SkGlyphID glyphID, + SkPackedGlyphID vetoID) const { + for (SkFixed subY = 0; subY < SK_Fixed1; subY += SK_FixedQuarter) { + for (SkFixed subX = 0; subX < SK_Fixed1; subX += SK_FixedQuarter) { + SkPackedGlyphID packedGlyphID{glyphID, subX, subY}; + if (packedGlyphID == vetoID) continue; + if (SkGlyph* glyphPtr = fGlyphMap.findOrNull(packedGlyphID)) { + return glyphPtr; + } + } + } + + return nullptr; +} + +SkSpan SkStrike::metrics(SkSpan glyphIDs, + const SkGlyph* results[]) { + return this->internalPrepare(glyphIDs, kMetricsOnly, results); +} + +SkSpan SkStrike::preparePaths(SkSpan glyphIDs, + const SkGlyph* results[]) { + return this->internalPrepare(glyphIDs, kMetricsAndPath, results); +} + +SkSpan +SkStrike::prepareImages(SkSpan glyphIDs, const SkGlyph* results[]) { + const SkGlyph** cursor = results; + for (auto glyphID : glyphIDs) { + SkGlyph* glyphPtr = this->glyph(glyphID); + (void)this->prepareImage(glyphPtr); + *cursor++ = glyphPtr; + } + + return {results, glyphIDs.size()}; +} + +void SkStrike::prepareForDrawingMasksCPU(SkDrawableGlyphBuffer* drawables) { + for (auto t : SkMakeEnumerate(drawables->input())) { + size_t i; SkGlyphVariant packedID; + std::forward_as_tuple(i, std::tie(packedID, std::ignore)) = t; + SkGlyph* glyph = this->glyph(packedID); + if (!glyph->isEmpty()) { + const void* image = this->prepareImage(glyph); + // If the glyph is too large, then no image is created. + if (image != nullptr) { + drawables->push_back(glyph, i); + } + } + } +} + +void SkStrike::prepareForDrawingPathsCPU(SkDrawableGlyphBuffer* drawables) { + for (auto t : SkMakeEnumerate(drawables->input())) { + size_t i; SkGlyphVariant packedID; + std::forward_as_tuple(i, std::tie(packedID, std::ignore)) = t; + SkGlyph* glyph = this->glyph(packedID); + if (!glyph->isEmpty()) { + const SkPath* path = this->preparePath(glyph); + // The glyph my not have a path. + if (path != nullptr) { + drawables->push_back(path, i); + } + } + } +} + +// N.B. This glyphMetrics call culls all the glyphs which will not display based on a non-finite +// position or that there are no mask pixels. +SkSpan +SkStrike::prepareForDrawingRemoveEmpty(const SkPackedGlyphID packedGlyphIDs[], + const SkPoint positions[], + size_t n, + int maxDimension, + SkGlyphPos results[]) { + size_t drawableGlyphCount = 0; + for (size_t i = 0; i < n; i++) { + SkPoint pos = positions[i]; + if (SkScalarsAreFinite(pos.x(), pos.y())) { + SkGlyph* glyphPtr = this->glyph(packedGlyphIDs[i]); + if (!glyphPtr->isEmpty()) { + results[drawableGlyphCount++] = {i, glyphPtr, pos}; + if (glyphPtr->maxDimension() <= maxDimension) { + // The glyph fits. Prepare image later. + } else if (!glyphPtr->isColor()) { + // The out of atlas glyph is not color so we can draw it using paths. + this->preparePath(glyphPtr); + } else { + // This will be handled by the fallback strike. + SkASSERT(glyphPtr->maxDimension() > maxDimension && glyphPtr->isColor()); + } + } + } + } + + return SkSpan{results, drawableGlyphCount}; +} + +void SkStrike::findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos, + SkGlyph* glyph, SkScalar* array, int* count) { + glyph->ensureIntercepts(bounds, scale, xPos, array, count, &fAlloc); +} + +void SkStrike::dump() const { + const SkTypeface* face = fScalerContext->getTypeface(); + const SkScalerContextRec& rec = fScalerContext->getRec(); + SkMatrix matrix; + rec.getSingleMatrix(&matrix); + matrix.preScale(SkScalarInvert(rec.fTextSize), SkScalarInvert(rec.fTextSize)); + SkString name; + face->getFamilyName(&name); + + SkString msg; + SkFontStyle style = face->fontStyle(); + msg.printf("cache typeface:%x %25s:(%d,%d,%d)\n %s glyphs:%3d", + face->uniqueID(), name.c_str(), style.weight(), style.width(), style.slant(), + rec.dump().c_str(), fGlyphMap.count()); + SkDebugf("%s\n", msg.c_str()); +} + +void SkStrike::onAboutToExitScope() { } + +#ifdef SK_DEBUG +void SkStrike::forceValidate() const { + size_t memoryUsed = sizeof(*this); + fGlyphMap.foreach ([&memoryUsed](const SkGlyph* glyphPtr) { + memoryUsed += sizeof(SkGlyph); + if (glyphPtr->setImageHasBeenCalled()) { + memoryUsed += glyphPtr->imageSize(); + } + if (glyphPtr->setPathHasBeenCalled() && glyphPtr->path() != nullptr) { + memoryUsed += glyphPtr->path()->approximateBytesUsed(); + } + }); + SkASSERT(fMemoryUsed == memoryUsed); +} + +void SkStrike::validate() const { +#ifdef SK_DEBUG_GLYPH_CACHE + forceValidate(); +#endif +} +#endif // SK_DEBUG + + diff --git a/gfx/skia/skia/src/core/SkStrike.h b/gfx/skia/skia/src/core/SkStrike.h new file mode 100644 index 0000000000..f893835cc6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrike.h @@ -0,0 +1,205 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + */ + +#ifndef SkStrike_DEFINED +#define SkStrike_DEFINED + +#include "include/core/SkFontMetrics.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkPaint.h" +#include "include/private/SkTHash.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkStrikeForGPU.h" +#include + +/** \class SkGlyphCache + + This class represents a strike: a specific combination of typeface, size, matrix, etc., and + holds the glyphs for that strike. Calling any of the getGlyphID... methods will + return the requested glyph, either instantly if it is already cached, or by first generating + it and then adding it to the strike. + + The strikes are held in a global list, available to all threads. To interact with one, call + either Find{OrCreate}Exclusive(). + + The Find*Exclusive() method returns SkExclusiveStrikePtr, which releases exclusive ownership + when they go out of scope. +*/ +class SkStrike final : public SkStrikeForGPU { +public: + SkStrike(const SkDescriptor& desc, + std::unique_ptr scaler, + const SkFontMetrics&); + + // Return a glyph. Create it if it doesn't exist, and initialize the glyph with metrics and + // advances using a scaler. + SkGlyph* glyph(SkPackedGlyphID packedID); + SkGlyph* glyph(SkGlyphID glyphID); + SkGlyph* glyph(SkGlyphID, SkPoint); + + // Return a glyph. Create it if it doesn't exist, and initialize with the prototype. + SkGlyph* glyphFromPrototype(const SkGlyphPrototype& p, void* image = nullptr); + + // Return a glyph or nullptr if it does not exits in the strike. + SkGlyph* glyphOrNull(SkPackedGlyphID id) const; + + const void* prepareImage(SkGlyph* glyph); + + // Lookup (or create if needed) the toGlyph using toID. If that glyph is not initialized with + // an image, then use the information in from to initialize the width, height top, left, + // format and image of the toGlyph. This is mainly used preserving the glyph if it was + // created by a search of desperation. + SkGlyph* mergeGlyphAndImage(SkPackedGlyphID toID, const SkGlyph& from); + + // If the path has never been set, then use the scaler context to add the glyph. + const SkPath* preparePath(SkGlyph*); + + // If the path has never been set, then add a path to glyph. + const SkPath* preparePath(SkGlyph* glyph, const SkPath* path); + + /** Returns the number of glyphs for this strike. + */ + unsigned getGlyphCount() const; + + /** Return the number of glyphs currently cached. */ + int countCachedGlyphs() const; + + /** If the advance axis intersects the glyph's path, append the positions scaled and offset + to the array (if non-null), and set the count to the updated array length. + */ + void findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos, + SkGlyph* , SkScalar* array, int* count); + + /** Fallback glyphs used during font remoting if the original glyph can't be found. + */ + bool belongsToCache(const SkGlyph* glyph) const; + /** Find any glyph in this cache with the given ID, regardless of subpixel positioning. + * If set and present, skip over the glyph with vetoID. + */ + const SkGlyph* getCachedGlyphAnySubPix(SkGlyphID, + SkPackedGlyphID vetoID = SkPackedGlyphID()) const; + + /** Return the vertical metrics for this strike. + */ + const SkFontMetrics& getFontMetrics() const { + return fFontMetrics; + } + + SkMask::Format getMaskFormat() const { + return fScalerContext->getMaskFormat(); + } + + const SkGlyphPositionRoundingSpec& roundingSpec() const override { + return fRoundingSpec; + } + + const SkDescriptor& getDescriptor() const override; + + SkSpan metrics(SkSpan glyphIDs, + const SkGlyph* results[]); + + SkSpan preparePaths(SkSpan glyphIDs, + const SkGlyph* results[]); + + SkSpan prepareImages(SkSpan glyphIDs, + const SkGlyph* results[]); + + void prepareForDrawingMasksCPU(SkDrawableGlyphBuffer* drawables); + + void prepareForDrawingPathsCPU(SkDrawableGlyphBuffer* drawables); + SkSpan prepareForDrawingRemoveEmpty(const SkPackedGlyphID packedGlyphIDs[], + const SkPoint positions[], + size_t n, + int maxDimension, + SkGlyphPos results[]) override; + + void onAboutToExitScope() override; + + /** Return the approx RAM usage for this cache. */ + size_t getMemoryUsed() const { return fMemoryUsed; } + + void dump() const; + + SkScalerContext* getScalerContext() const { return fScalerContext.get(); } + +#ifdef SK_DEBUG + void forceValidate() const; + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate : SkNoncopyable { + public: + AutoValidate(const SkStrike* cache) : fCache(cache) { + if (fCache) { + fCache->validate(); + } + } + ~AutoValidate() { + if (fCache) { + fCache->validate(); + } + } + void forget() { + fCache = nullptr; + } + private: + const SkStrike* fCache; + }; + +private: + class GlyphMapHashTraits { + public: + static SkPackedGlyphID GetKey(const SkGlyph* glyph) { + return glyph->getPackedID(); + } + static uint32_t Hash(SkPackedGlyphID glyphId) { + return glyphId.hash(); + } + }; + + SkGlyph* makeGlyph(SkPackedGlyphID); + + enum PathDetail { + kMetricsOnly, + kMetricsAndPath + }; + + // internalPrepare will only be called with a mutex already held. + SkSpan internalPrepare( + SkSpan glyphIDs, + PathDetail pathDetail, + const SkGlyph** results); + + const SkAutoDescriptor fDesc; + const std::unique_ptr fScalerContext; + SkFontMetrics fFontMetrics; + + // Map from a combined GlyphID and sub-pixel position to a SkGlyph*. + // The actual glyph is stored in the fAlloc. This structure provides an + // unchanging pointer as long as the strike is alive. + SkTHashTable fGlyphMap; + + // so we don't grow our arrays a lot + static constexpr size_t kMinGlyphCount = 8; + static constexpr size_t kMinGlyphImageSize = 16 /* height */ * 8 /* width */; + static constexpr size_t kMinAllocAmount = kMinGlyphImageSize * kMinGlyphCount; + + SkArenaAlloc fAlloc {kMinAllocAmount}; + + // Tracks (approx) how much ram is tied-up in this strike. + size_t fMemoryUsed; + + const SkGlyphPositionRoundingSpec fRoundingSpec; +}; + +#endif // SkStrike_DEFINED diff --git a/gfx/skia/skia/src/core/SkStrikeCache.cpp b/gfx/skia/skia/src/core/SkStrikeCache.cpp new file mode 100644 index 0000000000..bdef140897 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrikeCache.cpp @@ -0,0 +1,581 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkStrikeCache.h" + +#include + +#include "include/core/SkGraphics.h" +#include "include/core/SkTraceMemoryDump.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkStrike.h" + +class SkStrikeCache::Node final : public SkStrikeForGPU { +public: + Node(SkStrikeCache* strikeCache, + const SkDescriptor& desc, + std::unique_ptr scaler, + const SkFontMetrics& metrics, + std::unique_ptr pinner) + : fStrikeCache{strikeCache} + , fStrike{desc, std::move(scaler), metrics} + , fPinner{std::move(pinner)} {} + + const SkGlyphPositionRoundingSpec& roundingSpec() const override { + return fStrike.roundingSpec(); + } + + SkSpan + prepareForDrawingRemoveEmpty(const SkPackedGlyphID packedGlyphIDs[], + const SkPoint positions[], + size_t n, + int maxDimension, + SkGlyphPos results[]) override { + return fStrike.prepareForDrawingRemoveEmpty(packedGlyphIDs, + positions, + n, + maxDimension, + results); + } + + const SkDescriptor& getDescriptor() const override { + return fStrike.getDescriptor(); + } + + void onAboutToExitScope() override { + fStrikeCache->attachNode(this); + } + + SkStrikeCache* const fStrikeCache; + Node* fNext{nullptr}; + Node* fPrev{nullptr}; + SkStrike fStrike; + std::unique_ptr fPinner; +}; + +bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false; + +SkStrikeCache* SkStrikeCache::GlobalStrikeCache() { +#if !defined(SK_BUILD_FOR_IOS) + if (gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental) { + static thread_local auto* cache = new SkStrikeCache; + return cache; + } +#endif + static auto* cache = new SkStrikeCache; + return cache; +} + +SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(SkStrikeCache::Node* node) + : fNode{node} {} + +SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr() + : fNode{nullptr} {} + +SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(ExclusiveStrikePtr&& o) + : fNode{o.fNode} { + o.fNode = nullptr; +} + +SkStrikeCache::ExclusiveStrikePtr& +SkStrikeCache::ExclusiveStrikePtr::operator = (ExclusiveStrikePtr&& o) { + if (fNode != nullptr) { + fNode->fStrikeCache->attachNode(fNode); + } + fNode = o.fNode; + o.fNode = nullptr; + return *this; +} + +SkStrikeCache::ExclusiveStrikePtr::~ExclusiveStrikePtr() { + if (fNode != nullptr) { + fNode->fStrikeCache->attachNode(fNode); + } +} + +SkStrike* SkStrikeCache::ExclusiveStrikePtr::get() const { + return &fNode->fStrike; +} + +SkStrike* SkStrikeCache::ExclusiveStrikePtr::operator -> () const { + return this->get(); +} + +SkStrike& SkStrikeCache::ExclusiveStrikePtr::operator * () const { + return *this->get(); +} + +bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, + const SkStrikeCache::ExclusiveStrikePtr& rhs) { + return lhs.fNode == rhs.fNode; +} + +bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, decltype(nullptr)) { + return lhs.fNode == nullptr; +} + +bool operator == (decltype(nullptr), const SkStrikeCache::ExclusiveStrikePtr& rhs) { + return nullptr == rhs.fNode; +} + +SkStrikeCache::~SkStrikeCache() { + Node* node = fHead; + while (node) { + Node* next = node->fNext; + delete node; + node = next; + } +} + +std::unique_ptr SkStrikeCache::CreateScalerContext( + const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) { + auto scaler = typeface.createScalerContext(effects, &desc, true /* can fail */); + + // Check if we can create a scaler-context before creating the glyphcache. + // If not, we may have exhausted OS/font resources, so try purging the + // cache once and try again + // pass true the first time, to notice if the scalercontext failed, + if (scaler == nullptr) { + PurgeAll(); + scaler = typeface.createScalerContext(effects, &desc, false /* must succeed */); + } + return scaler; +} + +SkExclusiveStrikePtr SkStrikeCache::findOrCreateStrikeExclusive( + const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface) +{ + return SkExclusiveStrikePtr(this->findOrCreateStrike(desc, effects, typeface)); +} + +auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) -> Node* { + Node* node = this->findAndDetachStrike(desc); + if (node == nullptr) { + auto scaler = CreateScalerContext(desc, effects, typeface); + node = this->createStrike(desc, std::move(scaler)); + } + return node; +} + +SkScopedStrikeForGPU SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) { + return SkScopedStrikeForGPU{this->findOrCreateStrike(desc, effects, typeface)}; +} + +void SkStrikeCache::PurgeAll() { + GlobalStrikeCache()->purgeAll(); +} + +void SkStrikeCache::Dump() { + SkDebugf("GlyphCache [ used budget ]\n"); + SkDebugf(" bytes [ %8zu %8zu ]\n", + SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit()); + SkDebugf(" count [ %8zu %8zu ]\n", + SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit()); + + int counter = 0; + + auto visitor = [&counter](const SkStrike& cache) { + const SkScalerContextRec& rec = cache.getScalerContext()->getRec(); + + SkDebugf("index %d\n", counter); + SkDebugf("%s", rec.dump().c_str()); + counter += 1; + }; + + GlobalStrikeCache()->forEachStrike(visitor); +} + +namespace { + const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache"; +} // namespace + +void SkStrikeCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) { + dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed()); + dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes", + SkGraphics::GetFontCacheLimit()); + dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects", + SkGraphics::GetFontCacheCountUsed()); + dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects", + SkGraphics::GetFontCacheCountLimit()); + + if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) { + dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr); + return; + } + + auto visitor = [&dump](const SkStrike& cache) { + const SkTypeface* face = cache.getScalerContext()->getTypeface(); + const SkScalerContextRec& rec = cache.getScalerContext()->getRec(); + + SkString fontName; + face->getFamilyName(&fontName); + // Replace all special characters with '_'. + for (size_t index = 0; index < fontName.size(); ++index) { + if (!std::isalnum(fontName[index])) { + fontName[index] = '_'; + } + } + + SkString dumpName = SkStringPrintf( + "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &cache); + + dump->dumpNumericValue(dumpName.c_str(), + "size", "bytes", cache.getMemoryUsed()); + dump->dumpNumericValue(dumpName.c_str(), + "glyph_count", "objects", cache.countCachedGlyphs()); + dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); + }; + + GlobalStrikeCache()->forEachStrike(visitor); +} + + +void SkStrikeCache::attachNode(Node* node) { + if (node == nullptr) { + return; + } + SkAutoSpinlock ac(fLock); + + this->validate(); + node->fStrike.validate(); + + this->internalAttachToHead(node); + this->internalPurge(); +} + +SkExclusiveStrikePtr SkStrikeCache::findStrikeExclusive(const SkDescriptor& desc) { + return SkExclusiveStrikePtr(this->findAndDetachStrike(desc)); +} + +auto SkStrikeCache::findAndDetachStrike(const SkDescriptor& desc) -> Node* { + SkAutoSpinlock ac(fLock); + + for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) { + if (node->fStrike.getDescriptor() == desc) { + this->internalDetachCache(node); + return node; + } + } + + return nullptr; +} + + +static bool loose_compare(const SkDescriptor& lhs, const SkDescriptor& rhs) { + uint32_t size; + auto ptr = lhs.findEntry(kRec_SkDescriptorTag, &size); + SkScalerContextRec lhsRec; + memcpy(&lhsRec, ptr, size); + + ptr = rhs.findEntry(kRec_SkDescriptorTag, &size); + SkScalerContextRec rhsRec; + memcpy(&rhsRec, ptr, size); + + // If these don't match, there's no way we can use these strikes interchangeably. + // Note that a typeface from each renderer maps to a unique proxy typeface on the GPU, + // keyed in the glyph cache using fontID in the SkDescriptor. By limiting this search + // to descriptors with the same fontID, we ensure that a renderer never uses glyphs + // generated by a different renderer. + return + lhsRec.fFontID == rhsRec.fFontID && + lhsRec.fTextSize == rhsRec.fTextSize && + lhsRec.fPreScaleX == rhsRec.fPreScaleX && + lhsRec.fPreSkewX == rhsRec.fPreSkewX && + lhsRec.fPost2x2[0][0] == rhsRec.fPost2x2[0][0] && + lhsRec.fPost2x2[0][1] == rhsRec.fPost2x2[0][1] && + lhsRec.fPost2x2[1][0] == rhsRec.fPost2x2[1][0] && + lhsRec.fPost2x2[1][1] == rhsRec.fPost2x2[1][1]; +} + +bool SkStrikeCache::desperationSearchForImage(const SkDescriptor& desc, SkGlyph* glyph, + SkStrike* targetCache) { + SkAutoSpinlock ac(fLock); + + SkGlyphID glyphID = glyph->getGlyphID(); + for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) { + if (loose_compare(node->fStrike.getDescriptor(), desc)) { + if (SkGlyph *fallback = node->fStrike.glyphOrNull(glyph->getPackedID())) { + // This desperate-match node may disappear as soon as we drop fLock, so we + // need to copy the glyph from node into this strike, including a + // deep copy of the mask. + targetCache->mergeGlyphAndImage(glyph->getPackedID(), *fallback); + return true; + } + + // Look for any sub-pixel pos for this glyph, in case there is a pos mismatch. + if (const auto* fallback = node->fStrike.getCachedGlyphAnySubPix(glyphID)) { + targetCache->mergeGlyphAndImage(glyph->getPackedID(), *fallback); + return true; + } + } + } + + return false; +} + +bool SkStrikeCache::desperationSearchForPath( + const SkDescriptor& desc, SkGlyphID glyphID, SkPath* path) { + SkAutoSpinlock ac(fLock); + + // The following is wrong there is subpixel positioning with paths... + // Paths are only ever at sub-pixel position (0,0), so we can just try that directly rather + // than try our packed position first then search all others on failure like for masks. + // + // This will have to search the sub-pixel positions too. + // There is also a problem with accounting for cache size with shared path data. + for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) { + if (loose_compare(node->fStrike.getDescriptor(), desc)) { + if (SkGlyph *from = node->fStrike.glyphOrNull(SkPackedGlyphID{glyphID})) { + if (from->setPathHasBeenCalled() && from->path() != nullptr) { + // We can just copy the path out by value here, so no need to worry + // about the lifetime of this desperate-match node. + *path = *from->path(); + return true; + } + } + } + } + return false; +} + +SkExclusiveStrikePtr SkStrikeCache::createStrikeExclusive( + const SkDescriptor& desc, + std::unique_ptr scaler, + SkFontMetrics* maybeMetrics, + std::unique_ptr pinner) +{ + return SkExclusiveStrikePtr( + this->createStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner))); +} + +auto SkStrikeCache::createStrike( + const SkDescriptor& desc, + std::unique_ptr scaler, + SkFontMetrics* maybeMetrics, + std::unique_ptr pinner) -> Node* { + SkFontMetrics fontMetrics; + if (maybeMetrics != nullptr) { + fontMetrics = *maybeMetrics; + } else { + scaler->getFontMetrics(&fontMetrics); + } + + return new Node{this, desc, std::move(scaler), fontMetrics, std::move(pinner)}; +} + +void SkStrikeCache::purgeAll() { + SkAutoSpinlock ac(fLock); + this->internalPurge(fTotalMemoryUsed); +} + +size_t SkStrikeCache::getTotalMemoryUsed() const { + SkAutoSpinlock ac(fLock); + return fTotalMemoryUsed; +} + +int SkStrikeCache::getCacheCountUsed() const { + SkAutoSpinlock ac(fLock); + return fCacheCount; +} + +int SkStrikeCache::getCacheCountLimit() const { + SkAutoSpinlock ac(fLock); + return fCacheCountLimit; +} + +size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) { + static const size_t minLimit = 256 * 1024; + if (newLimit < minLimit) { + newLimit = minLimit; + } + + SkAutoSpinlock ac(fLock); + + size_t prevLimit = fCacheSizeLimit; + fCacheSizeLimit = newLimit; + this->internalPurge(); + return prevLimit; +} + +size_t SkStrikeCache::getCacheSizeLimit() const { + SkAutoSpinlock ac(fLock); + return fCacheSizeLimit; +} + +int SkStrikeCache::setCacheCountLimit(int newCount) { + if (newCount < 0) { + newCount = 0; + } + + SkAutoSpinlock ac(fLock); + + int prevCount = fCacheCountLimit; + fCacheCountLimit = newCount; + this->internalPurge(); + return prevCount; +} + +int SkStrikeCache::getCachePointSizeLimit() const { + SkAutoSpinlock ac(fLock); + return fPointSizeLimit; +} + +int SkStrikeCache::setCachePointSizeLimit(int newLimit) { + if (newLimit < 0) { + newLimit = 0; + } + + SkAutoSpinlock ac(fLock); + + int prevLimit = fPointSizeLimit; + fPointSizeLimit = newLimit; + return prevLimit; +} + +void SkStrikeCache::forEachStrike(std::function visitor) const { + SkAutoSpinlock ac(fLock); + + this->validate(); + + for (Node* node = this->internalGetHead(); node != nullptr; node = node->fNext) { + visitor(node->fStrike); + } +} + +size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) { + this->validate(); + + size_t bytesNeeded = 0; + if (fTotalMemoryUsed > fCacheSizeLimit) { + bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit; + } + bytesNeeded = SkTMax(bytesNeeded, minBytesNeeded); + if (bytesNeeded) { + // no small purges! + bytesNeeded = SkTMax(bytesNeeded, fTotalMemoryUsed >> 2); + } + + int countNeeded = 0; + if (fCacheCount > fCacheCountLimit) { + countNeeded = fCacheCount - fCacheCountLimit; + // no small purges! + countNeeded = SkMax32(countNeeded, fCacheCount >> 2); + } + + // early exit + if (!countNeeded && !bytesNeeded) { + return 0; + } + + size_t bytesFreed = 0; + int countFreed = 0; + + // Start at the tail and proceed backwards deleting; the list is in LRU + // order, with unimportant entries at the tail. + Node* node = this->internalGetTail(); + while (node != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) { + Node* prev = node->fPrev; + + // Only delete if the strike is not pinned. + if (node->fPinner == nullptr || node->fPinner->canDelete()) { + bytesFreed += node->fStrike.getMemoryUsed(); + countFreed += 1; + this->internalDetachCache(node); + delete node; + } + node = prev; + } + + this->validate(); + +#ifdef SPEW_PURGE_STATUS + if (countFreed) { + SkDebugf("purging %dK from font cache [%d entries]\n", + (int)(bytesFreed >> 10), countFreed); + } +#endif + + return bytesFreed; +} + +void SkStrikeCache::internalAttachToHead(Node* node) { + SkASSERT(nullptr == node->fPrev && nullptr == node->fNext); + if (fHead) { + fHead->fPrev = node; + node->fNext = fHead; + } + fHead = node; + + if (fTail == nullptr) { + fTail = node; + } + + fCacheCount += 1; + fTotalMemoryUsed += node->fStrike.getMemoryUsed(); +} + +void SkStrikeCache::internalDetachCache(Node* node) { + SkASSERT(fCacheCount > 0); + fCacheCount -= 1; + fTotalMemoryUsed -= node->fStrike.getMemoryUsed(); + + if (node->fPrev) { + node->fPrev->fNext = node->fNext; + } else { + fHead = node->fNext; + } + if (node->fNext) { + node->fNext->fPrev = node->fPrev; + } else { + fTail = node->fPrev; + } + node->fPrev = node->fNext = nullptr; +} + +void SkStrikeCache::ValidateGlyphCacheDataSize() { +#ifdef SK_DEBUG + GlobalStrikeCache()->validateGlyphCacheDataSize(); +#endif +} + +#ifdef SK_DEBUG +void SkStrikeCache::validateGlyphCacheDataSize() const { + this->forEachStrike( + [](const SkStrike& cache) { cache.forceValidate(); + }); +} +#endif + +#ifdef SK_DEBUG +void SkStrikeCache::validate() const { + size_t computedBytes = 0; + int computedCount = 0; + + const Node* node = fHead; + while (node != nullptr) { + computedBytes += node->fStrike.getMemoryUsed(); + computedCount += 1; + node = node->fNext; + } + + SkASSERTF(fCacheCount == computedCount, "fCacheCount: %d, computedCount: %d", fCacheCount, + computedCount); + SkASSERTF(fTotalMemoryUsed == computedBytes, "fTotalMemoryUsed: %d, computedBytes: %d", + fTotalMemoryUsed, computedBytes); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/skia/skia/src/core/SkStrikeCache.h b/gfx/skia/skia/src/core/SkStrikeCache.h new file mode 100644 index 0000000000..189da86506 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrikeCache.h @@ -0,0 +1,168 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStrikeCache_DEFINED +#define SkStrikeCache_DEFINED + +#include +#include + +#include "include/private/SkSpinlock.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkStrike.h" + +class SkTraceMemoryDump; + +#ifndef SK_DEFAULT_FONT_CACHE_COUNT_LIMIT + #define SK_DEFAULT_FONT_CACHE_COUNT_LIMIT 2048 +#endif + +#ifndef SK_DEFAULT_FONT_CACHE_LIMIT + #define SK_DEFAULT_FONT_CACHE_LIMIT (2 * 1024 * 1024) +#endif + +#ifndef SK_DEFAULT_FONT_CACHE_POINT_SIZE_LIMIT + #define SK_DEFAULT_FONT_CACHE_POINT_SIZE_LIMIT 256 +#endif + +/////////////////////////////////////////////////////////////////////////////// + +class SkStrikePinner { +public: + virtual ~SkStrikePinner() = default; + virtual bool canDelete() = 0; +}; + +class SkStrikeCache final : public SkStrikeForGPUCacheInterface { + class Node; + +public: + SkStrikeCache() = default; + ~SkStrikeCache() override; + + class ExclusiveStrikePtr { + public: + explicit ExclusiveStrikePtr(Node*); + ExclusiveStrikePtr(); + ExclusiveStrikePtr(const ExclusiveStrikePtr&) = delete; + ExclusiveStrikePtr& operator = (const ExclusiveStrikePtr&) = delete; + ExclusiveStrikePtr(ExclusiveStrikePtr&&); + ExclusiveStrikePtr& operator = (ExclusiveStrikePtr&&); + ~ExclusiveStrikePtr(); + + SkStrike* get() const; + SkStrike* operator -> () const; + SkStrike& operator * () const; + explicit operator bool () const { return fNode != nullptr; } + friend bool operator == (const ExclusiveStrikePtr&, const ExclusiveStrikePtr&); + friend bool operator == (const ExclusiveStrikePtr&, decltype(nullptr)); + friend bool operator == (decltype(nullptr), const ExclusiveStrikePtr&); + + private: + Node* fNode; + }; + + static SkStrikeCache* GlobalStrikeCache(); + + ExclusiveStrikePtr findStrikeExclusive(const SkDescriptor&); + + ExclusiveStrikePtr createStrikeExclusive( + const SkDescriptor& desc, + std::unique_ptr scaler, + SkFontMetrics* maybeMetrics = nullptr, + std::unique_ptr = nullptr); + + ExclusiveStrikePtr findOrCreateStrikeExclusive( + const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface); + + // Routines to find suitable data when working in a remote cache situation. These are + // suitable as substitutes for similar calls in SkScalerContext. + bool desperationSearchForImage(const SkDescriptor& desc, + SkGlyph* glyph, + SkStrike* targetCache); + bool desperationSearchForPath(const SkDescriptor& desc, SkGlyphID glyphID, SkPath* path); + + SkScopedStrikeForGPU findOrCreateScopedStrike(const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) override; + + static std::unique_ptr CreateScalerContext( + const SkDescriptor&, const SkScalerContextEffects&, const SkTypeface&); + + static void PurgeAll(); + static void ValidateGlyphCacheDataSize(); + static void Dump(); + + // Dump memory usage statistics of all the attaches caches in the process using the + // SkTraceMemoryDump interface. + static void DumpMemoryStatistics(SkTraceMemoryDump* dump); + + void purgeAll(); // does not change budget + + int getCacheCountLimit() const; + int setCacheCountLimit(int limit); + int getCacheCountUsed() const; + + size_t getCacheSizeLimit() const; + size_t setCacheSizeLimit(size_t limit); + size_t getTotalMemoryUsed() const; + + int getCachePointSizeLimit() const; + int setCachePointSizeLimit(int limit); + +#ifdef SK_DEBUG + // A simple accounting of what each glyph cache reports and the strike cache total. + void validate() const SK_REQUIRES(fLock); + // Make sure that each glyph cache's memory tracking and actual memory used are in sync. + void validateGlyphCacheDataSize() const; +#else + void validate() const {} + void validateGlyphCacheDataSize() const {} +#endif + +private: + Node* findAndDetachStrike(const SkDescriptor&); + Node* createStrike( + const SkDescriptor& desc, + std::unique_ptr scaler, + SkFontMetrics* maybeMetrics = nullptr, + std::unique_ptr = nullptr); + Node* findOrCreateStrike( + const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface); + void attachNode(Node* node); + + // The following methods can only be called when mutex is already held. + Node* internalGetHead() const SK_REQUIRES(fLock) { return fHead; } + Node* internalGetTail() const SK_REQUIRES(fLock) { return fTail; } + void internalDetachCache(Node*) SK_REQUIRES(fLock); + void internalAttachToHead(Node*) SK_REQUIRES(fLock); + + // Checkout budgets, modulated by the specified min-bytes-needed-to-purge, + // and attempt to purge caches to match. + // Returns number of bytes freed. + size_t internalPurge(size_t minBytesNeeded = 0) SK_REQUIRES(fLock); + + void forEachStrike(std::function visitor) const; + + mutable SkSpinlock fLock; + Node* fHead SK_GUARDED_BY(fLock) {nullptr}; + Node* fTail SK_GUARDED_BY(fLock) {nullptr}; + size_t fTotalMemoryUsed{0}; + size_t fCacheSizeLimit{SK_DEFAULT_FONT_CACHE_LIMIT}; + int32_t fCacheCountLimit{SK_DEFAULT_FONT_CACHE_COUNT_LIMIT}; + int32_t fCacheCount{0}; + int32_t fPointSizeLimit{SK_DEFAULT_FONT_CACHE_POINT_SIZE_LIMIT}; +}; + +using SkExclusiveStrikePtr = SkStrikeCache::ExclusiveStrikePtr; + +#endif // SkStrikeCache_DEFINED diff --git a/gfx/skia/skia/src/core/SkStrikeForGPU.cpp b/gfx/skia/skia/src/core/SkStrikeForGPU.cpp new file mode 100644 index 0000000000..1c2da44b41 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrikeForGPU.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#include "src/core/SkStrikeForGPU.h" + +#include "src/core/SkGlyphRunPainter.h" + +bool SkStrikeForGPU::CanDrawAsMask(const SkGlyph& glyph) { + return glyph.maxDimension() <= SkStrikeCommon::kSkSideTooBigForAtlas; +} + +bool SkStrikeForGPU::CanDrawAsSDFT(const SkGlyph& glyph) { + return glyph.maxDimension() <= SkStrikeCommon::kSkSideTooBigForAtlas + && glyph.maskFormat() == SkMask::kSDF_Format; +} + +bool SkStrikeForGPU::CanDrawAsPath(const SkGlyph& glyph) { + SkASSERT(glyph.isColor() || glyph.setPathHasBeenCalled()); + return !glyph.isColor() && glyph.path() != nullptr; +} + diff --git a/gfx/skia/skia/src/core/SkStrikeForGPU.h b/gfx/skia/skia/src/core/SkStrikeForGPU.h new file mode 100644 index 0000000000..a72ea355e5 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrikeForGPU.h @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStrikeInterface_DEFINED +#define SkStrikeInterface_DEFINED + +#include "include/core/SkPaint.h" +#include "include/core/SkPoint.h" +#include "include/core/SkTypes.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkSpan.h" + +#include + +class SkDescriptor; +class SkGlyph; +class SkMaskFilter; +class SkPathEffect; +class SkTypeface; +struct SkGlyphPositionRoundingSpec; +struct SkScalerContextEffects; + +struct SkGlyphPos { + size_t index; + const SkGlyph* glyph; + SkPoint position; +}; + +struct SkPathPos { + const SkPath* path; + SkPoint position; +}; + +class SkStrikeForGPU { +public: + virtual ~SkStrikeForGPU() = default; + virtual const SkDescriptor& getDescriptor() const = 0; + + // prepareForDrawingRemoveEmpty takes glyphIDs, and position, and returns a list of SkGlyphs + // and positions where all the data to draw the glyph has been created. The maxDimension + // parameter determines if the mask/SDF version will be created, or an alternate drawing + // format should be used. For path-only drawing set maxDimension to 0, and for bitmap-device + // drawing (where there is no upper limit to the glyph in the cache) use INT_MAX. + // prepareForDrawingRemoveEmpty should remove all empty glyphs from the returned span. + virtual SkSpan + prepareForDrawingRemoveEmpty(const SkPackedGlyphID packedGlyphIDs[], + const SkPoint positions[], + size_t n, + int maxDimension, + SkGlyphPos results[]) = 0; + + virtual const SkGlyphPositionRoundingSpec& roundingSpec() const = 0; + + // Used with SkScopedStrikeForGPU to take action at the end of a scope. + virtual void onAboutToExitScope() = 0; + + // Common categories for glyph types used by GPU. + static bool CanDrawAsMask(const SkGlyph& glyph); + static bool CanDrawAsSDFT(const SkGlyph& glyph); + static bool CanDrawAsPath(const SkGlyph& glyph); + + + struct Deleter { + void operator()(SkStrikeForGPU* ptr) const { + ptr->onAboutToExitScope(); + } + }; +}; + +using SkScopedStrikeForGPU = std::unique_ptr; + +class SkStrikeForGPUCacheInterface { +public: + virtual ~SkStrikeForGPUCacheInterface() = default; + virtual SkScopedStrikeForGPU findOrCreateScopedStrike(const SkDescriptor& desc, + const SkScalerContextEffects& effects, + const SkTypeface& typeface) = 0; +}; +#endif //SkStrikeInterface_DEFINED diff --git a/gfx/skia/skia/src/core/SkStrikeSpec.cpp b/gfx/skia/skia/src/core/SkStrikeSpec.cpp new file mode 100644 index 0000000000..731c259dc2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrikeSpec.cpp @@ -0,0 +1,259 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkStrikeSpec.h" + +#include "include/core/SkGraphics.h" +#include "src/core/SkDraw.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTLazy.h" + +SkStrikeSpec SkStrikeSpec::MakeMask(const SkFont& font, const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix) { + SkStrikeSpec storage; + + storage.commonSetup(font, paint, surfaceProps, scalerContextFlags, deviceMatrix); + + return storage; +} + +SkStrikeSpec SkStrikeSpec::MakePath(const SkFont& font, const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags) { + SkStrikeSpec storage; + + // setup our std runPaint, in hopes of getting hits in the cache + SkPaint pathPaint{paint}; + SkFont pathFont{font}; + + // The factor to get from the size stored in the strike to the size needed for + // the source. + storage.fStrikeToSourceRatio = pathFont.setupForAsPaths(&pathPaint); + + // The sub-pixel position will always happen when transforming to the screen. + pathFont.setSubpixel(false); + + storage.commonSetup(pathFont, pathPaint, surfaceProps, scalerContextFlags, SkMatrix::I()); + + return storage; +} + +SkStrikeSpec SkStrikeSpec::MakeSourceFallback( + const SkFont& font, + const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + SkScalar maxSourceGlyphDimension) { + SkStrikeSpec storage; + + // Subtract 2 to account for the bilerp pad around the glyph + SkScalar maxAtlasDimension = SkStrikeCommon::kSkSideTooBigForAtlas - 2; + + SkScalar runFontTextSize = font.getSize(); + + // Scale the text size down so the long side of all the glyphs will fit in the atlas. + SkScalar fallbackTextSize = SkScalarFloorToScalar( + (maxAtlasDimension / maxSourceGlyphDimension) * runFontTextSize); + + SkFont fallbackFont{font}; + fallbackFont.setSize(fallbackTextSize); + + // No sub-pixel needed. The transform to the screen will take care of sub-pixel positioning. + fallbackFont.setSubpixel(false); + + // The scale factor to go from strike size to the source size for glyphs. + storage.fStrikeToSourceRatio = runFontTextSize / fallbackTextSize; + + storage.commonSetup(fallbackFont, paint, surfaceProps, scalerContextFlags, SkMatrix::I()); + + return storage; +} + +SkStrikeSpec SkStrikeSpec::MakeCanonicalized(const SkFont& font, const SkPaint* paint) { + SkStrikeSpec storage; + + SkPaint canonicalizedPaint; + if (paint != nullptr) { + canonicalizedPaint = *paint; + } + + const SkFont* canonicalizedFont = &font; + SkTLazy pathFont; + if (ShouldDrawAsPath(canonicalizedPaint, font, SkMatrix::I())) { + canonicalizedFont = pathFont.set(font); + storage.fStrikeToSourceRatio = pathFont->setupForAsPaths(nullptr); + canonicalizedPaint.reset(); + } + + storage.commonSetup(*canonicalizedFont, + canonicalizedPaint, + SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType), + kFakeGammaAndBoostContrast, + SkMatrix::I()); + return storage; +} + +SkStrikeSpec SkStrikeSpec::MakeWithNoDevice(const SkFont& font, const SkPaint* paint) { + SkStrikeSpec storage; + + SkPaint setupPaint; + if (paint != nullptr) { + setupPaint = *paint; + } + + storage.commonSetup(font, + setupPaint, + SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType), + kFakeGammaAndBoostContrast, + SkMatrix::I()); + + return storage; + +} + +SkStrikeSpec SkStrikeSpec::MakeDefault() { + SkFont defaultFont; + return MakeCanonicalized(defaultFont); +} + +bool SkStrikeSpec::ShouldDrawAsPath( + const SkPaint& paint, const SkFont& font, const SkMatrix& viewMatrix) { + + // hairline glyphs are fast enough so we don't need to cache them + if (SkPaint::kStroke_Style == paint.getStyle() && 0 == paint.getStrokeWidth()) { + return true; + } + + // we don't cache perspective + if (viewMatrix.hasPerspective()) { + return true; + } + + // Glyphs like Emojis can't be rendered as a path. + if (font.getTypeface() && font.getTypeface()->hasColorGlyphs()) { + return false; + } + + SkMatrix textMatrix = SkFontPriv::MakeTextMatrix(font); + textMatrix.postConcat(viewMatrix); + + // we have a self-imposed maximum, just for memory-usage sanity + SkScalar limit = SkMinScalar(SkGraphics::GetFontCachePointSizeLimit(), 1024); + SkScalar maxSizeSquared = limit * limit; + + auto distance = [&textMatrix](int XIndex, int YIndex) { + return textMatrix[XIndex] * textMatrix[XIndex] + textMatrix[YIndex] * textMatrix[YIndex]; + }; + + return distance(SkMatrix::kMScaleX, SkMatrix::kMSkewY ) > maxSizeSquared + || distance(SkMatrix::kMSkewX, SkMatrix::kMScaleY) > maxSizeSquared; +} + +SkStrikeSpec SkStrikeSpec::MakePDFVector(const SkTypeface& typeface, int* size) { + SkFont font; + font.setHinting(SkFontHinting::kNone); + font.setEdging(SkFont::Edging::kAlias); + font.setTypeface(sk_ref_sp(&typeface)); + int unitsPerEm = typeface.getUnitsPerEm(); + if (unitsPerEm <= 0) { + unitsPerEm = 1024; + } + if (size) { + *size = unitsPerEm; + } + font.setSize((SkScalar)unitsPerEm); + + SkStrikeSpec storage; + storage.commonSetup(font, + SkPaint(), + SkSurfaceProps(0, kUnknown_SkPixelGeometry), + kFakeGammaAndBoostContrast, + SkMatrix::I()); + + return storage; +} + +#if SK_SUPPORT_GPU +std::tuple +SkStrikeSpec::MakeSDFT(const SkFont& font, const SkPaint& paint, + const SkSurfaceProps& surfaceProps, const SkMatrix& deviceMatrix, + const GrTextContext::Options& options) { + SkStrikeSpec storage; + + SkPaint dfPaint = GrTextContext::InitDistanceFieldPaint(paint); + SkFont dfFont = GrTextContext::InitDistanceFieldFont( + font, deviceMatrix, options, &storage.fStrikeToSourceRatio); + + // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the + // passed-in scaler context flags. (It's only used when we fall-back to bitmap text). + SkScalerContextFlags flags = SkScalerContextFlags::kNone; + + SkScalar minScale, maxScale; + std::tie(minScale, maxScale) = GrTextContext::InitDistanceFieldMinMaxScale( + font.getSize(), deviceMatrix, options); + + storage.commonSetup(dfFont, dfPaint, surfaceProps, flags, SkMatrix::I()); + + return std::tie(storage, minScale, maxScale); +} + +sk_sp SkStrikeSpec::findOrCreateGrStrike(GrStrikeCache* cache) const { + return cache->getStrike(*fAutoDescriptor.getDesc()); +} +#endif + +void SkStrikeSpec::commonSetup(const SkFont& font, const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix) { + SkScalerContextEffects effects; + + SkScalerContext::CreateDescriptorAndEffectsUsingPaint( + font, paint, surfaceProps, scalerContextFlags, deviceMatrix, + &fAutoDescriptor, &effects); + + fMaskFilter = sk_ref_sp(effects.fMaskFilter); + fPathEffect = sk_ref_sp(effects.fPathEffect); + fTypeface = font.refTypefaceOrDefault(); +} + +SkScopedStrikeForGPU SkStrikeSpec::findOrCreateScopedStrike(SkStrikeForGPUCacheInterface* cache) const { + SkScalerContextEffects effects{fPathEffect.get(), fMaskFilter.get()}; + return cache->findOrCreateScopedStrike(*fAutoDescriptor.getDesc(), effects, *fTypeface); +} + +SkExclusiveStrikePtr SkStrikeSpec::findOrCreateExclusiveStrike(SkStrikeCache* cache) const { + SkScalerContextEffects effects{fPathEffect.get(), fMaskFilter.get()}; + return cache->findOrCreateStrikeExclusive(*fAutoDescriptor.getDesc(), effects, *fTypeface); +} + +SkBulkGlyphMetrics::SkBulkGlyphMetrics(const SkStrikeSpec& spec) + : fStrike{spec.findOrCreateExclusiveStrike()} { } + +SkSpan SkBulkGlyphMetrics::glyphs(SkSpan glyphIDs) { + fGlyphs.reset(glyphIDs.size()); + return fStrike->metrics(glyphIDs, fGlyphs.get()); +} + +SkBulkGlyphMetricsAndPaths::SkBulkGlyphMetricsAndPaths(const SkStrikeSpec& spec) + : fStrike{spec.findOrCreateExclusiveStrike()} { } + +SkSpan SkBulkGlyphMetricsAndPaths::glyphs(SkSpan glyphIDs) { + fGlyphs.reset(glyphIDs.size()); + return fStrike->preparePaths(glyphIDs, fGlyphs.get()); +} + +SkBulkGlyphMetricsAndImages::SkBulkGlyphMetricsAndImages(const SkStrikeSpec& spec) + : fStrike{spec.findOrCreateExclusiveStrike()} { } + +SkSpan SkBulkGlyphMetricsAndImages::glyphs(SkSpan glyphIDs) { + fGlyphs.reset(glyphIDs.size()); + return fStrike->prepareImages(glyphIDs, fGlyphs.get()); +} diff --git a/gfx/skia/skia/src/core/SkStrikeSpec.h b/gfx/skia/skia/src/core/SkStrikeSpec.h new file mode 100644 index 0000000000..d34e711661 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrikeSpec.h @@ -0,0 +1,131 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStrikeSpec_DEFINED +#define SkStrikeSpec_DEFINED + +#include "src/core/SkDescriptor.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkStrikeForGPU.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/text/GrStrikeCache.h" +#include "src/gpu/text/GrTextContext.h" +#endif + +class SkFont; +class SkPaint; +class SkStrikeCache; +class SkSurfaceProps; + +class SkStrikeSpec { +public: + // Create a strike spec for mask style cache entries. + static SkStrikeSpec MakeMask( + const SkFont& font, + const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix); + + // Create a strike spec for path style cache entries. + static SkStrikeSpec MakePath( + const SkFont& font, + const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags); + + static SkStrikeSpec MakeSourceFallback(const SkFont& font, + const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + SkScalar maxSourceGlyphDimension); + + // Create a canonical strike spec for device-less measurements. + static SkStrikeSpec MakeCanonicalized( + const SkFont& font, const SkPaint* paint = nullptr); + + // Create a strike spec without a device, and does not switch over to path for large sizes. + // This means that strikeToSourceRatio() is always 1. + static SkStrikeSpec MakeWithNoDevice(const SkFont& font, const SkPaint* paint = nullptr); + + // Make a canonical strike spec for device-less measurements using default typeface and size. + static SkStrikeSpec MakeDefault(); + + // Make a strike spec for PDF Vector strikes + static SkStrikeSpec MakePDFVector(const SkTypeface& typeface, int* size); + +#if SK_SUPPORT_GPU + // Create a strike spec for scaled distance field text. + static std::tuple MakeSDFT( + const SkFont& font, + const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + const SkMatrix& deviceMatrix, + const GrTextContext::Options& options); + + sk_sp findOrCreateGrStrike(GrStrikeCache* cache) const; +#endif + + SkScopedStrikeForGPU findOrCreateScopedStrike(SkStrikeForGPUCacheInterface* cache) const; + + SkExclusiveStrikePtr findOrCreateExclusiveStrike( + SkStrikeCache* cache = SkStrikeCache::GlobalStrikeCache()) const; + + SkScalar strikeToSourceRatio() const { return fStrikeToSourceRatio; } + const SkDescriptor& descriptor() const { return *fAutoDescriptor.getDesc(); } + static bool ShouldDrawAsPath(const SkPaint& paint, const SkFont& font, const SkMatrix& matrix); + +private: + void commonSetup( + const SkFont& font, + const SkPaint& paint, + const SkSurfaceProps& surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& deviceMatrix); + + SkAutoDescriptor fAutoDescriptor; + sk_sp fMaskFilter; + sk_sp fPathEffect; + sk_sp fTypeface; + SkScalar fStrikeToSourceRatio{1.0f}; +}; + +class SkBulkGlyphMetrics { +public: + explicit SkBulkGlyphMetrics(const SkStrikeSpec& spec); + SkSpan glyphs(SkSpan glyphIDs); + +private: + static constexpr int kTypicalGlyphCount = 20; + SkAutoSTArray fGlyphs; + SkExclusiveStrikePtr fStrike; +}; + +class SkBulkGlyphMetricsAndPaths { +public: + explicit SkBulkGlyphMetricsAndPaths(const SkStrikeSpec& spec); + SkSpan glyphs(SkSpan glyphIDs); + +private: + static constexpr int kTypicalGlyphCount = 20; + SkAutoSTArray fGlyphs; + SkExclusiveStrikePtr fStrike; +}; + +class SkBulkGlyphMetricsAndImages { +public: + explicit SkBulkGlyphMetricsAndImages(const SkStrikeSpec& spec); + SkSpan glyphs(SkSpan glyphIDs); + +private: + static constexpr int kTypicalGlyphCount = 64; + SkAutoSTArray fGlyphs; + SkExclusiveStrikePtr fStrike; +}; + +#endif // SkStrikeSpec_DEFINED diff --git a/gfx/skia/skia/src/core/SkString.cpp b/gfx/skia/skia/src/core/SkString.cpp new file mode 100644 index 0000000000..d42ee6f07e --- /dev/null +++ b/gfx/skia/skia/src/core/SkString.cpp @@ -0,0 +1,604 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/private/SkTo.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkUtils.h" +#include "src/utils/SkUTF.h" + +#include +#include +#include +#include + +// number of bytes (on the stack) to receive the printf result +static const size_t kBufferSize = 1024; + +static const char* apply_format_string(const char* format, va_list args, char* stackBuffer, + size_t stackBufferSize, int* length, SkString* heapBuffer) { + va_list argsCopy; + va_copy(argsCopy, args); + *length = std::vsnprintf(stackBuffer, stackBufferSize, format, args); + if (*length < 0) { + SkDebugf("SkString: vsnprintf reported error."); + va_end(argsCopy); + *length = 0; + return stackBuffer; + } + if (*length < SkToInt(stackBufferSize)) { + va_end(argsCopy); + return stackBuffer; + } + heapBuffer->resize(*length); + SkDEBUGCODE(int check =) + std::vsnprintf(heapBuffer->writable_str(), *length + 1, format, argsCopy); + SkASSERT(check == *length); + va_end(argsCopy); + return heapBuffer->c_str(); +} + +#define ARGS_TO_BUFFER(format, buffer, size, written, result) \ + SkString overflow; \ + do { \ + va_list args; \ + va_start(args, format); \ + result = apply_format_string(format, args, buffer, size, &written, &overflow); \ + va_end(args); \ + } while (0) + +#define V_SKSTRING_PRINTF(output, format) \ + do { \ + char buffer[kBufferSize]; \ + va_list args; \ + va_start(args, format); \ + int length; \ + auto result = apply_format_string(format, args, buffer, kBufferSize, &length, &output); \ + SkASSERT(result == output.c_str() || result == buffer); \ + if (result == buffer) { \ + output.set(buffer, length); \ + } \ + } while (0) + +/////////////////////////////////////////////////////////////////////////////// + +bool SkStrEndsWith(const char string[], const char suffixStr[]) { + SkASSERT(string); + SkASSERT(suffixStr); + size_t strLen = strlen(string); + size_t suffixLen = strlen(suffixStr); + return strLen >= suffixLen && + !strncmp(string + strLen - suffixLen, suffixStr, suffixLen); +} + +bool SkStrEndsWith(const char string[], const char suffixChar) { + SkASSERT(string); + size_t strLen = strlen(string); + if (0 == strLen) { + return false; + } else { + return (suffixChar == string[strLen-1]); + } +} + +int SkStrStartsWithOneOf(const char string[], const char prefixes[]) { + int index = 0; + do { + const char* limit = strchr(prefixes, '\0'); + if (!strncmp(string, prefixes, limit - prefixes)) { + return index; + } + prefixes = limit + 1; + index++; + } while (prefixes[0]); + return -1; +} + +char* SkStrAppendU32(char string[], uint32_t dec) { + SkDEBUGCODE(char* start = string;) + + char buffer[SkStrAppendU32_MaxSize]; + char* p = buffer + sizeof(buffer); + + do { + *--p = SkToU8('0' + dec % 10); + dec /= 10; + } while (dec != 0); + + SkASSERT(p >= buffer); + char* stop = buffer + sizeof(buffer); + while (p < stop) { + *string++ = *p++; + } + SkASSERT(string - start <= SkStrAppendU32_MaxSize); + return string; +} + +char* SkStrAppendS32(char string[], int32_t dec) { + uint32_t udec = dec; + if (dec < 0) { + *string++ = '-'; + udec = ~udec + 1; // udec = -udec, but silences some warnings that are trying to be helpful + } + return SkStrAppendU32(string, udec); +} + +char* SkStrAppendU64(char string[], uint64_t dec, int minDigits) { + SkDEBUGCODE(char* start = string;) + + char buffer[SkStrAppendU64_MaxSize]; + char* p = buffer + sizeof(buffer); + + do { + *--p = SkToU8('0' + (int32_t) (dec % 10)); + dec /= 10; + minDigits--; + } while (dec != 0); + + while (minDigits > 0) { + *--p = '0'; + minDigits--; + } + + SkASSERT(p >= buffer); + size_t cp_len = buffer + sizeof(buffer) - p; + memcpy(string, p, cp_len); + string += cp_len; + + SkASSERT(string - start <= SkStrAppendU64_MaxSize); + return string; +} + +char* SkStrAppendS64(char string[], int64_t dec, int minDigits) { + uint64_t udec = dec; + if (dec < 0) { + *string++ = '-'; + udec = ~udec + 1; // udec = -udec, but silences some warnings that are trying to be helpful + } + return SkStrAppendU64(string, udec, minDigits); +} + +char* SkStrAppendFloat(char string[], float value) { + // since floats have at most 8 significant digits, we limit our %g to that. + static const char gFormat[] = "%.8g"; + // make it 1 larger for the terminating 0 + char buffer[SkStrAppendScalar_MaxSize + 1]; + int len = snprintf(buffer, sizeof(buffer), gFormat, value); + memcpy(string, buffer, len); + SkASSERT(len <= SkStrAppendScalar_MaxSize); + return string + len; +} + +/////////////////////////////////////////////////////////////////////////////// + +const SkString::Rec SkString::gEmptyRec(0, 0); + +#define SizeOfRec() (gEmptyRec.data() - (const char*)&gEmptyRec) + +static uint32_t trim_size_t_to_u32(size_t value) { + if (sizeof(size_t) > sizeof(uint32_t)) { + if (value > UINT32_MAX) { + value = UINT32_MAX; + } + } + return (uint32_t)value; +} + +static size_t check_add32(size_t base, size_t extra) { + SkASSERT(base <= UINT32_MAX); + if (sizeof(size_t) > sizeof(uint32_t)) { + if (base + extra > UINT32_MAX) { + extra = UINT32_MAX - base; + } + } + return extra; +} + +sk_sp SkString::Rec::Make(const char text[], size_t len) { + if (0 == len) { + return sk_sp(const_cast(&gEmptyRec)); + } + + SkSafeMath safe; + // We store a 32bit version of the length + uint32_t stringLen = safe.castTo(len); + // Add SizeOfRec() for our overhead and 1 for null-termination + size_t allocationSize = safe.add(len, SizeOfRec() + sizeof(char)); + // Align up to a multiple of 4 + allocationSize = safe.alignUp(allocationSize, 4); + + SkASSERT_RELEASE(safe.ok()); + + void* storage = ::operator new (allocationSize); + sk_sp rec(new (storage) Rec(stringLen, 1)); + if (text) { + memcpy(rec->data(), text, len); + } + rec->data()[len] = 0; + return rec; +} + +void SkString::Rec::ref() const { + if (this == &SkString::gEmptyRec) { + return; + } + SkAssertResult(this->fRefCnt.fetch_add(+1, std::memory_order_relaxed)); +} + +void SkString::Rec::unref() const { + if (this == &SkString::gEmptyRec) { + return; + } + int32_t oldRefCnt = this->fRefCnt.fetch_add(-1, std::memory_order_acq_rel); + SkASSERT(oldRefCnt); + if (1 == oldRefCnt) { + delete this; + } +} + +bool SkString::Rec::unique() const { + return fRefCnt.load(std::memory_order_acquire) == 1; +} + +#ifdef SK_DEBUG +const SkString& SkString::validate() const { + // make sure know one has written over our global + SkASSERT(0 == gEmptyRec.fLength); + SkASSERT(0 == gEmptyRec.fRefCnt.load(std::memory_order_relaxed)); + SkASSERT(0 == gEmptyRec.data()[0]); + + if (fRec.get() != &gEmptyRec) { + SkASSERT(fRec->fLength > 0); + SkASSERT(fRec->fRefCnt.load(std::memory_order_relaxed) > 0); + SkASSERT(0 == fRec->data()[fRec->fLength]); + } + return *this; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +SkString::SkString() : fRec(const_cast(&gEmptyRec)) { +} + +SkString::SkString(size_t len) { + fRec = Rec::Make(nullptr, len); +} + +SkString::SkString(const char text[]) { + size_t len = text ? strlen(text) : 0; + + fRec = Rec::Make(text, len); +} + +SkString::SkString(const char text[], size_t len) { + fRec = Rec::Make(text, len); +} + +SkString::SkString(const SkString& src) : fRec(src.validate().fRec) {} + +SkString::SkString(SkString&& src) : fRec(std::move(src.validate().fRec)) { + src.fRec.reset(const_cast(&gEmptyRec)); +} + +SkString::~SkString() { + this->validate(); +} + +bool SkString::equals(const SkString& src) const { + return fRec == src.fRec || this->equals(src.c_str(), src.size()); +} + +bool SkString::equals(const char text[]) const { + return this->equals(text, text ? strlen(text) : 0); +} + +bool SkString::equals(const char text[], size_t len) const { + SkASSERT(len == 0 || text != nullptr); + + return fRec->fLength == len && !memcmp(fRec->data(), text, len); +} + +SkString& SkString::operator=(const SkString& src) { + this->validate(); + fRec = src.fRec; // sk_sp::operator=(const sk_sp&) checks for self-assignment. + return *this; +} + +SkString& SkString::operator=(SkString&& src) { + this->validate(); + + if (fRec != src.fRec) { + this->swap(src); + } + return *this; +} + +SkString& SkString::operator=(const char text[]) { + this->validate(); + return *this = SkString(text); +} + +void SkString::reset() { + this->validate(); + fRec.reset(const_cast(&gEmptyRec)); +} + +char* SkString::writable_str() { + this->validate(); + + if (fRec->fLength) { + if (!fRec->unique()) { + fRec = Rec::Make(fRec->data(), fRec->fLength); + } + } + return fRec->data(); +} + +void SkString::set(const char text[]) { + this->set(text, text ? strlen(text) : 0); +} + +void SkString::set(const char text[], size_t len) { + len = trim_size_t_to_u32(len); + bool unique = fRec->unique(); + if (0 == len) { + this->reset(); + } else if (unique && len <= fRec->fLength) { + // should we resize if len <<<< fLength, to save RAM? (e.g. len < (fLength>>1))? + // just use less of the buffer without allocating a smaller one + char* p = this->writable_str(); + if (text) { + memcpy(p, text, len); + } + p[len] = 0; + fRec->fLength = SkToU32(len); + } else if (unique && (fRec->fLength >> 2) == (len >> 2)) { + // we have spare room in the current allocation, so don't alloc a larger one + char* p = this->writable_str(); + if (text) { + memcpy(p, text, len); + } + p[len] = 0; + fRec->fLength = SkToU32(len); + } else { + SkString tmp(text, len); + this->swap(tmp); + } +} + +void SkString::insert(size_t offset, const char text[]) { + this->insert(offset, text, text ? strlen(text) : 0); +} + +void SkString::insert(size_t offset, const char text[], size_t len) { + if (len) { + size_t length = fRec->fLength; + if (offset > length) { + offset = length; + } + + // Check if length + len exceeds 32bits, we trim len + len = check_add32(length, len); + if (0 == len) { + return; + } + + /* If we're the only owner, and we have room in our allocation for the insert, + do it in place, rather than allocating a new buffer. + + To know we have room, compare the allocated sizes + beforeAlloc = SkAlign4(length + 1) + afterAlloc = SkAligh4(length + 1 + len) + but SkAlign4(x) is (x + 3) >> 2 << 2 + which is equivalent for testing to (length + 1 + 3) >> 2 == (length + 1 + 3 + len) >> 2 + and we can then eliminate the +1+3 since that doesn't affec the answer + */ + if (fRec->unique() && (length >> 2) == ((length + len) >> 2)) { + char* dst = this->writable_str(); + + if (offset < length) { + memmove(dst + offset + len, dst + offset, length - offset); + } + memcpy(dst + offset, text, len); + + dst[length + len] = 0; + fRec->fLength = SkToU32(length + len); + } else { + /* Seems we should use realloc here, since that is safe if it fails + (we have the original data), and might be faster than alloc/copy/free. + */ + SkString tmp(fRec->fLength + len); + char* dst = tmp.writable_str(); + + if (offset > 0) { + memcpy(dst, fRec->data(), offset); + } + memcpy(dst + offset, text, len); + if (offset < fRec->fLength) { + memcpy(dst + offset + len, fRec->data() + offset, + fRec->fLength - offset); + } + + this->swap(tmp); + } + } +} + +void SkString::insertUnichar(size_t offset, SkUnichar uni) { + char buffer[SkUTF::kMaxBytesInUTF8Sequence]; + size_t len = SkUTF::ToUTF8(uni, buffer); + + if (len) { + this->insert(offset, buffer, len); + } +} + +void SkString::insertS32(size_t offset, int32_t dec) { + char buffer[SkStrAppendS32_MaxSize]; + char* stop = SkStrAppendS32(buffer, dec); + this->insert(offset, buffer, stop - buffer); +} + +void SkString::insertS64(size_t offset, int64_t dec, int minDigits) { + char buffer[SkStrAppendS64_MaxSize]; + char* stop = SkStrAppendS64(buffer, dec, minDigits); + this->insert(offset, buffer, stop - buffer); +} + +void SkString::insertU32(size_t offset, uint32_t dec) { + char buffer[SkStrAppendU32_MaxSize]; + char* stop = SkStrAppendU32(buffer, dec); + this->insert(offset, buffer, stop - buffer); +} + +void SkString::insertU64(size_t offset, uint64_t dec, int minDigits) { + char buffer[SkStrAppendU64_MaxSize]; + char* stop = SkStrAppendU64(buffer, dec, minDigits); + this->insert(offset, buffer, stop - buffer); +} + +void SkString::insertHex(size_t offset, uint32_t hex, int minDigits) { + minDigits = SkTPin(minDigits, 0, 8); + + char buffer[8]; + char* p = buffer + sizeof(buffer); + + do { + *--p = SkHexadecimalDigits::gUpper[hex & 0xF]; + hex >>= 4; + minDigits -= 1; + } while (hex != 0); + + while (--minDigits >= 0) { + *--p = '0'; + } + + SkASSERT(p >= buffer); + this->insert(offset, p, buffer + sizeof(buffer) - p); +} + +void SkString::insertScalar(size_t offset, SkScalar value) { + char buffer[SkStrAppendScalar_MaxSize]; + char* stop = SkStrAppendScalar(buffer, value); + this->insert(offset, buffer, stop - buffer); +} + +void SkString::printf(const char format[], ...) { + V_SKSTRING_PRINTF((*this), format); +} + +void SkString::appendf(const char format[], ...) { + char buffer[kBufferSize]; + int length; + const char* result; + ARGS_TO_BUFFER(format, buffer, kBufferSize, length, result); + + this->append(result, length); +} + +void SkString::appendVAList(const char format[], va_list args) { + char buffer[kBufferSize]; + int length = vsnprintf(buffer, kBufferSize, format, args); + SkASSERT(length >= 0 && length < SkToInt(kBufferSize)); + + this->append(buffer, length); +} + +void SkString::prependf(const char format[], ...) { + char buffer[kBufferSize]; + int length; + const char* result; + ARGS_TO_BUFFER(format, buffer, kBufferSize, length, result); + + this->prepend(result, length); +} + +void SkString::prependVAList(const char format[], va_list args) { + char buffer[kBufferSize]; + int length = vsnprintf(buffer, kBufferSize, format, args); + SkASSERT(length >= 0 && length < SkToInt(kBufferSize)); + + this->prepend(buffer, length); +} + + +/////////////////////////////////////////////////////////////////////////////// + +void SkString::remove(size_t offset, size_t length) { + size_t size = this->size(); + + if (offset < size) { + if (length > size - offset) { + length = size - offset; + } + SkASSERT(length <= size); + SkASSERT(offset <= size - length); + if (length > 0) { + SkString tmp(size - length); + char* dst = tmp.writable_str(); + const char* src = this->c_str(); + + if (offset) { + memcpy(dst, src, offset); + } + size_t tail = size - (offset + length); + if (tail) { + memcpy(dst + offset, src + (offset + length), tail); + } + SkASSERT(dst[tmp.size()] == 0); + this->swap(tmp); + } + } +} + +void SkString::swap(SkString& other) { + this->validate(); + other.validate(); + + using std::swap; + swap(fRec, other.fRec); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkString SkStringPrintf(const char* format, ...) { + SkString formattedOutput; + V_SKSTRING_PRINTF(formattedOutput, format); + return formattedOutput; +} + +void SkStrSplit(const char* str, const char* delimiters, SkStrSplitMode splitMode, + SkTArray* out) { + if (splitMode == kCoalesce_SkStrSplitMode) { + // Skip any delimiters. + str += strspn(str, delimiters); + } + if (!*str) { + return; + } + + while (true) { + // Find a token. + const size_t len = strcspn(str, delimiters); + if (splitMode == kStrict_SkStrSplitMode || len > 0) { + out->push_back().set(str, len); + str += len; + } + + if (!*str) { + return; + } + if (splitMode == kCoalesce_SkStrSplitMode) { + // Skip any delimiters. + str += strspn(str, delimiters); + } else { + // Skip one delimiter. + str += 1; + } + } +} diff --git a/gfx/skia/skia/src/core/SkStringUtils.cpp b/gfx/skia/skia/src/core/SkStringUtils.cpp new file mode 100644 index 0000000000..efb9d13096 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStringUtils.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "src/core/SkStringUtils.h" +#include "src/utils/SkUTF.h" + +void SkAppendScalar(SkString* str, SkScalar value, SkScalarAsStringType asType) { + switch (asType) { + case kHex_SkScalarAsStringType: + str->appendf("SkBits2Float(0x%08x)", SkFloat2Bits(value)); + break; + case kDec_SkScalarAsStringType: { + SkString tmp; + tmp.printf("%g", value); + if (tmp.contains('.')) { + tmp.appendUnichar('f'); + } + str->append(tmp); + break; + } + } +} + +SkString SkTabString(const SkString& string, int tabCnt) { + if (tabCnt <= 0) { + return string; + } + SkString tabs; + for (int i = 0; i < tabCnt; ++i) { + tabs.append("\t"); + } + SkString result; + static const char newline[] = "\n"; + const char* input = string.c_str(); + int nextNL = SkStrFind(input, newline); + while (nextNL >= 0) { + if (nextNL > 0) { + result.append(tabs); + } + result.append(input, nextNL + 1); + input += nextNL + 1; + nextNL = SkStrFind(input, newline); + } + if (*input != '\0') { + result.append(tabs); + result.append(input); + } + return result; +} + +SkString SkStringFromUTF16(const uint16_t* src, size_t count) { + SkString ret; + const uint16_t* stop = src + count; + if (count > 0) { + SkASSERT(src); + size_t n = 0; + const uint16_t* end = src + count; + for (const uint16_t* ptr = src; ptr < end;) { + const uint16_t* last = ptr; + SkUnichar u = SkUTF::NextUTF16(&ptr, stop); + size_t s = SkUTF::ToUTF8(u); + if (n > UINT32_MAX - s) { + end = last; // truncate input string + break; + } + n += s; + } + ret = SkString(n); + char* out = ret.writable_str(); + for (const uint16_t* ptr = src; ptr < end;) { + out += SkUTF::ToUTF8(SkUTF::NextUTF16(&ptr, stop), out); + } + SkASSERT(out == ret.writable_str() + n); + } + return ret; +} diff --git a/gfx/skia/skia/src/core/SkStringUtils.h b/gfx/skia/skia/src/core/SkStringUtils.h new file mode 100644 index 0000000000..184ca6e2d5 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStringUtils.h @@ -0,0 +1,35 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStringUtils_DEFINED +#define SkStringUtils_DEFINED + +#include "include/core/SkScalar.h" + +class SkString; + +enum SkScalarAsStringType { + kDec_SkScalarAsStringType, + kHex_SkScalarAsStringType, +}; + +void SkAppendScalar(SkString*, SkScalar, SkScalarAsStringType); + +static inline void SkAppendScalarDec(SkString* str, SkScalar value) { + SkAppendScalar(str, value, kDec_SkScalarAsStringType); +} + +static inline void SkAppendScalarHex(SkString* str, SkScalar value) { + SkAppendScalar(str, value, kHex_SkScalarAsStringType); +} + +/** Indents every non-empty line of the string by tabCnt tabs */ +SkString SkTabString(const SkString& string, int tabCnt); + +SkString SkStringFromUTF16(const uint16_t* src, size_t count); + +#endif diff --git a/gfx/skia/skia/src/core/SkStroke.cpp b/gfx/skia/skia/src/core/SkStroke.cpp new file mode 100644 index 0000000000..ecfefdf686 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStroke.cpp @@ -0,0 +1,1579 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkStrokerPriv.h" + +#include "include/private/SkMacros.h" +#include "include/private/SkTo.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkPointPriv.h" + +#include + +enum { + kTangent_RecursiveLimit, + kCubic_RecursiveLimit, + kConic_RecursiveLimit, + kQuad_RecursiveLimit +}; + +// quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure +// largest seen for normal cubics : 5, 26 +// largest seen for normal quads : 11 +static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3, 11*3 }; // 3x limits seen in practice + +static_assert(0 == kTangent_RecursiveLimit, "cubic_stroke_relies_on_tangent_equalling_zero"); +static_assert(1 == kCubic_RecursiveLimit, "cubic_stroke_relies_on_cubic_equalling_one"); +static_assert(SK_ARRAY_COUNT(kRecursiveLimits) == kQuad_RecursiveLimit + 1, + "recursive_limits_mismatch"); + +#if defined SK_DEBUG && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING + int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 }; +#endif +#ifndef DEBUG_QUAD_STROKER + #define DEBUG_QUAD_STROKER 0 +#endif + +#if DEBUG_QUAD_STROKER + /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting + stroke has more than the optimal number of quadratics and lines */ + #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \ + SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \ + SkDebugf(" " #resultType " t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \ + resultType + #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__ +#else + #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \ + resultType + #define STROKER_DEBUG_PARAMS(...) +#endif + +static inline bool degenerate_vector(const SkVector& v) { + return !SkPointPriv::CanNormalize(v.fX, v.fY); +} + +static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, SkScalar scale, + SkScalar radius, + SkVector* normal, SkVector* unitNormal) { + if (!unitNormal->setNormalize((after.fX - before.fX) * scale, + (after.fY - before.fY) * scale)) { + return false; + } + SkPointPriv::RotateCCW(unitNormal); + unitNormal->scale(radius, normal); + return true; +} + +static bool set_normal_unitnormal(const SkVector& vec, + SkScalar radius, + SkVector* normal, SkVector* unitNormal) { + if (!unitNormal->setNormalize(vec.fX, vec.fY)) { + return false; + } + SkPointPriv::RotateCCW(unitNormal); + unitNormal->scale(radius, normal); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +struct SkQuadConstruct { // The state of the quad stroke under construction. + SkPoint fQuad[3]; // the stroked quad parallel to the original curve + SkPoint fTangentStart; // a point tangent to fQuad[0] + SkPoint fTangentEnd; // a point tangent to fQuad[2] + SkScalar fStartT; // a segment of the original curve + SkScalar fMidT; // " + SkScalar fEndT; // " + bool fStartSet; // state to share common points across structs + bool fEndSet; // " + bool fOppositeTangents; // set if coincident tangents have opposite directions + + // return false if start and end are too close to have a unique middle + bool init(SkScalar start, SkScalar end) { + fStartT = start; + fMidT = (start + end) * SK_ScalarHalf; + fEndT = end; + fStartSet = fEndSet = false; + return fStartT < fMidT && fMidT < fEndT; + } + + bool initWithStart(SkQuadConstruct* parent) { + if (!init(parent->fStartT, parent->fMidT)) { + return false; + } + fQuad[0] = parent->fQuad[0]; + fTangentStart = parent->fTangentStart; + fStartSet = true; + return true; + } + + bool initWithEnd(SkQuadConstruct* parent) { + if (!init(parent->fMidT, parent->fEndT)) { + return false; + } + fQuad[2] = parent->fQuad[2]; + fTangentEnd = parent->fTangentEnd; + fEndSet = true; + return true; + } +}; + +class SkPathStroker { +public: + SkPathStroker(const SkPath& src, + SkScalar radius, SkScalar miterLimit, SkPaint::Cap, + SkPaint::Join, SkScalar resScale, + bool canIgnoreCenter); + + bool hasOnlyMoveTo() const { return 0 == fSegmentCount; } + SkPoint moveToPt() const { return fFirstPt; } + + void moveTo(const SkPoint&); + void lineTo(const SkPoint&, const SkPath::Iter* iter = nullptr); + void quadTo(const SkPoint&, const SkPoint&); + void conicTo(const SkPoint&, const SkPoint&, SkScalar weight); + void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); + void close(bool isLine) { this->finishContour(true, isLine); } + + void done(SkPath* dst, bool isLine) { + this->finishContour(false, isLine); + dst->swap(fOuter); + } + + SkScalar getResScale() const { return fResScale; } + + bool isCurrentContourEmpty() const { + return fInner.isZeroLengthSincePoint(0) && + fOuter.isZeroLengthSincePoint(fFirstOuterPtIndexInContour); + } + +private: + SkScalar fRadius; + SkScalar fInvMiterLimit; + SkScalar fResScale; + SkScalar fInvResScale; + SkScalar fInvResScaleSquared; + + SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; + SkPoint fFirstPt, fPrevPt; // on original path + SkPoint fFirstOuterPt; + int fFirstOuterPtIndexInContour; + int fSegmentCount; + bool fPrevIsLine; + bool fCanIgnoreCenter; + + SkStrokerPriv::CapProc fCapper; + SkStrokerPriv::JoinProc fJoiner; + + SkPath fInner, fOuter, fCusper; // outer is our working answer, inner is temp + + enum StrokeType { + kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis + kInner_StrokeType = -1 + } fStrokeType; + + enum ResultType { + kSplit_ResultType, // the caller should split the quad stroke in two + kDegenerate_ResultType, // the caller should add a line + kQuad_ResultType, // the caller should (continue to try to) add a quad stroke + }; + + enum ReductionType { + kPoint_ReductionType, // all curve points are practically identical + kLine_ReductionType, // the control point is on the line between the ends + kQuad_ReductionType, // the control point is outside the line between the ends + kDegenerate_ReductionType, // the control point is on the line but outside the ends + kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic) + kDegenerate3_ReductionType, // three areas of max curvature found (for cubic) + }; + + enum IntersectRayType { + kCtrlPt_RayType, + kResultType_RayType, + }; + + int fRecursionDepth; // track stack depth to abort if numerics run amok + bool fFoundTangents; // do less work until tangents meet (cubic) + bool fJoinCompleted; // previous join was not degenerate + + void addDegenerateLine(const SkQuadConstruct* ); + static ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction); + static ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3], + const SkPoint** tanPtPtr); + static ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction); + ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ) const; + ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* ); + ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* ); + void conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const; + void conicQuadEnds(const SkConic& , SkQuadConstruct* ) const; + bool conicStroke(const SkConic& , SkQuadConstruct* ); + bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const; + void cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const; + void cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* ); + void cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* , SkPoint* mid) const; + bool cubicStroke(const SkPoint cubic[4], SkQuadConstruct* ); + void init(StrokeType strokeType, SkQuadConstruct* , SkScalar tStart, SkScalar tEnd); + ResultType intersectRay(SkQuadConstruct* , IntersectRayType STROKER_DEBUG_PARAMS(int) ) const; + bool ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const; + void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const; + bool quadStroke(const SkPoint quad[3], SkQuadConstruct* ); + void setConicEndNormal(const SkConic& , + const SkVector& normalAB, const SkVector& unitNormalAB, + SkVector* normalBC, SkVector* unitNormalBC); + void setCubicEndNormal(const SkPoint cubic[4], + const SkVector& normalAB, const SkVector& unitNormalAB, + SkVector* normalCD, SkVector* unitNormalCD); + void setQuadEndNormal(const SkPoint quad[3], + const SkVector& normalAB, const SkVector& unitNormalAB, + SkVector* normalBC, SkVector* unitNormalBC); + void setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, SkPoint* tangent) const; + static bool SlightAngle(SkQuadConstruct* ); + ResultType strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2], + SkQuadConstruct* STROKER_DEBUG_PARAMS(int depth) ) const; + ResultType tangentsMeet(const SkPoint cubic[4], SkQuadConstruct* ); + + void finishContour(bool close, bool isLine); + bool preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal, + bool isLine); + void postJoinTo(const SkPoint&, const SkVector& normal, + const SkVector& unitNormal); + + void line_to(const SkPoint& currPt, const SkVector& normal); +}; + +/////////////////////////////////////////////////////////////////////////////// + +bool SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal, + SkVector* unitNormal, bool currIsLine) { + SkASSERT(fSegmentCount >= 0); + + SkScalar prevX = fPrevPt.fX; + SkScalar prevY = fPrevPt.fY; + + if (!set_normal_unitnormal(fPrevPt, currPt, fResScale, fRadius, normal, unitNormal)) { + if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper) { + return false; + } + /* Square caps and round caps draw even if the segment length is zero. + Since the zero length segment has no direction, set the orientation + to upright as the default orientation */ + normal->set(fRadius, 0); + unitNormal->set(1, 0); + } + + if (fSegmentCount == 0) { + fFirstNormal = *normal; + fFirstUnitNormal = *unitNormal; + fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY); + + fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY); + fInner.moveTo(prevX - normal->fX, prevY - normal->fY); + } else { // we have a previous segment + fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal, + fRadius, fInvMiterLimit, fPrevIsLine, currIsLine); + } + fPrevIsLine = currIsLine; + return true; +} + +void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal, + const SkVector& unitNormal) { + fJoinCompleted = true; + fPrevPt = currPt; + fPrevUnitNormal = unitNormal; + fPrevNormal = normal; + fSegmentCount += 1; +} + +void SkPathStroker::finishContour(bool close, bool currIsLine) { + if (fSegmentCount > 0) { + SkPoint pt; + + if (close) { + fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, + fFirstUnitNormal, fRadius, fInvMiterLimit, + fPrevIsLine, currIsLine); + fOuter.close(); + + if (fCanIgnoreCenter) { + // If we can ignore the center just make sure the larger of the two paths + // is preserved and don't add the smaller one. + if (fInner.getBounds().contains(fOuter.getBounds())) { + fInner.swap(fOuter); + } + } else { + // now add fInner as its own contour + fInner.getLastPt(&pt); + fOuter.moveTo(pt.fX, pt.fY); + fOuter.reversePathTo(fInner); + fOuter.close(); + } + } else { // add caps to start and end + // cap the end + fInner.getLastPt(&pt); + fCapper(&fOuter, fPrevPt, fPrevNormal, pt, + currIsLine ? &fInner : nullptr); + fOuter.reversePathTo(fInner); + // cap the start + fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt, + fPrevIsLine ? &fInner : nullptr); + fOuter.close(); + } + if (!fCusper.isEmpty()) { + fOuter.addPath(fCusper); + fCusper.rewind(); + } + } + // since we may re-use fInner, we rewind instead of reset, to save on + // reallocating its internal storage. + fInner.rewind(); + fSegmentCount = -1; + fFirstOuterPtIndexInContour = fOuter.countPoints(); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPathStroker::SkPathStroker(const SkPath& src, + SkScalar radius, SkScalar miterLimit, + SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale, + bool canIgnoreCenter) + : fRadius(radius) + , fResScale(resScale) + , fCanIgnoreCenter(canIgnoreCenter) { + + /* This is only used when join is miter_join, but we initialize it here + so that it is always defined, to fis valgrind warnings. + */ + fInvMiterLimit = 0; + + if (join == SkPaint::kMiter_Join) { + if (miterLimit <= SK_Scalar1) { + join = SkPaint::kBevel_Join; + } else { + fInvMiterLimit = SkScalarInvert(miterLimit); + } + } + fCapper = SkStrokerPriv::CapFactory(cap); + fJoiner = SkStrokerPriv::JoinFactory(join); + fSegmentCount = -1; + fFirstOuterPtIndexInContour = 0; + fPrevIsLine = false; + + // Need some estimate of how large our final result (fOuter) + // and our per-contour temp (fInner) will be, so we don't spend + // extra time repeatedly growing these arrays. + // + // 3x for result == inner + outer + join (swag) + // 1x for inner == 'wag' (worst contour length would be better guess) + fOuter.incReserve(src.countPoints() * 3); + fOuter.setIsVolatile(true); + fInner.incReserve(src.countPoints()); + fInner.setIsVolatile(true); + // TODO : write a common error function used by stroking and filling + // The '4' below matches the fill scan converter's error term + fInvResScale = SkScalarInvert(resScale * 4); + fInvResScaleSquared = fInvResScale * fInvResScale; + fRecursionDepth = 0; +} + +void SkPathStroker::moveTo(const SkPoint& pt) { + if (fSegmentCount > 0) { + this->finishContour(false, false); + } + fSegmentCount = 0; + fFirstPt = fPrevPt = pt; + fJoinCompleted = false; +} + +void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) { + fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY); + fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY); +} + +static bool has_valid_tangent(const SkPath::Iter* iter) { + SkPath::Iter copy = *iter; + SkPath::Verb verb; + SkPoint pts[4]; + while ((verb = copy.next(pts))) { + switch (verb) { + case SkPath::kMove_Verb: + return false; + case SkPath::kLine_Verb: + if (pts[0] == pts[1]) { + continue; + } + return true; + case SkPath::kQuad_Verb: + case SkPath::kConic_Verb: + if (pts[0] == pts[1] && pts[0] == pts[2]) { + continue; + } + return true; + case SkPath::kCubic_Verb: + if (pts[0] == pts[1] && pts[0] == pts[2] && pts[0] == pts[3]) { + continue; + } + return true; + case SkPath::kClose_Verb: + case SkPath::kDone_Verb: + return false; + } + } + return false; +} + +void SkPathStroker::lineTo(const SkPoint& currPt, const SkPath::Iter* iter) { + bool teenyLine = SkPointPriv::EqualsWithinTolerance(fPrevPt, currPt, SK_ScalarNearlyZero * fInvResScale); + if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper && teenyLine) { + return; + } + if (teenyLine && (fJoinCompleted || (iter && has_valid_tangent(iter)))) { + return; + } + SkVector normal, unitNormal; + + if (!this->preJoinTo(currPt, &normal, &unitNormal, true)) { + return; + } + this->line_to(currPt, normal); + this->postJoinTo(currPt, normal, unitNormal); +} + +void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB, + const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) { + if (!set_normal_unitnormal(quad[1], quad[2], fResScale, fRadius, normalBC, unitNormalBC)) { + *normalBC = normalAB; + *unitNormalBC = unitNormalAB; + } +} + +void SkPathStroker::setConicEndNormal(const SkConic& conic, const SkVector& normalAB, + const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) { + setQuadEndNormal(conic.fPts, normalAB, unitNormalAB, normalBC, unitNormalBC); +} + +void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB, + const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) { + SkVector ab = cubic[1] - cubic[0]; + SkVector cd = cubic[3] - cubic[2]; + + bool degenerateAB = degenerate_vector(ab); + bool degenerateCD = degenerate_vector(cd); + + if (degenerateAB && degenerateCD) { + goto DEGENERATE_NORMAL; + } + + if (degenerateAB) { + ab = cubic[2] - cubic[0]; + degenerateAB = degenerate_vector(ab); + } + if (degenerateCD) { + cd = cubic[3] - cubic[1]; + degenerateCD = degenerate_vector(cd); + } + if (degenerateAB || degenerateCD) { +DEGENERATE_NORMAL: + *normalCD = normalAB; + *unitNormalCD = unitNormalAB; + return; + } + SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); +} + +void SkPathStroker::init(StrokeType strokeType, SkQuadConstruct* quadPts, SkScalar tStart, + SkScalar tEnd) { + fStrokeType = strokeType; + fFoundTangents = false; + quadPts->init(tStart, tEnd); +} + +// returns the distance squared from the point to the line +static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) { + SkVector dxy = lineEnd - lineStart; + SkVector ab0 = pt - lineStart; + SkScalar numer = dxy.dot(ab0); + SkScalar denom = dxy.dot(dxy); + SkScalar t = sk_ieee_float_divide(numer, denom); + if (t >= 0 && t <= 1) { + SkPoint hit; + hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t; + hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t; + return SkPointPriv::DistanceToSqd(hit, pt); + } else { + return SkPointPriv::DistanceToSqd(pt, lineStart); + } +} + +/* Given a cubic, determine if all four points are in a line. + Return true if the inner points is close to a line connecting the outermost points. + + Find the outermost point by looking for the largest difference in X or Y. + Given the indices of the outermost points, and that outer_1 is greater than outer_2, + this table shows the index of the smaller of the remaining points: + + outer_2 + 0 1 2 3 + outer_1 ---------------- + 0 | - 2 1 1 + 1 | - - 0 0 + 2 | - - - 0 + 3 | - - - - + + If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2. + + This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1 + + Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is: + + mid_2 == (outer_1 ^ outer_2 ^ mid_1) + */ +static bool cubic_in_line(const SkPoint cubic[4]) { + SkScalar ptMax = -1; + int outer1 SK_INIT_TO_AVOID_WARNING; + int outer2 SK_INIT_TO_AVOID_WARNING; + for (int index = 0; index < 3; ++index) { + for (int inner = index + 1; inner < 4; ++inner) { + SkVector testDiff = cubic[inner] - cubic[index]; + SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY)); + if (ptMax < testMax) { + outer1 = index; + outer2 = inner; + ptMax = testMax; + } + } + } + SkASSERT(outer1 >= 0 && outer1 <= 2); + SkASSERT(outer2 >= 1 && outer2 <= 3); + SkASSERT(outer1 < outer2); + int mid1 = (1 + (2 >> outer2)) >> outer1; + SkASSERT(mid1 >= 0 && mid1 <= 2); + SkASSERT(outer1 != mid1 && outer2 != mid1); + int mid2 = outer1 ^ outer2 ^ mid1; + SkASSERT(mid2 >= 1 && mid2 <= 3); + SkASSERT(mid2 != outer1 && mid2 != outer2 && mid2 != mid1); + SkASSERT(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f); + SkScalar lineSlop = ptMax * ptMax * 0.00001f; // this multiplier is pulled out of the air + return pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= lineSlop + && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= lineSlop; +} + +/* Given quad, see if all there points are in a line. + Return true if the inside point is close to a line connecting the outermost points. + + Find the outermost point by looking for the largest difference in X or Y. + Since the XOR of the indices is 3 (0 ^ 1 ^ 2) + the missing index equals: outer_1 ^ outer_2 ^ 3 + */ +static bool quad_in_line(const SkPoint quad[3]) { + SkScalar ptMax = -1; + int outer1 SK_INIT_TO_AVOID_WARNING; + int outer2 SK_INIT_TO_AVOID_WARNING; + for (int index = 0; index < 2; ++index) { + for (int inner = index + 1; inner < 3; ++inner) { + SkVector testDiff = quad[inner] - quad[index]; + SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY)); + if (ptMax < testMax) { + outer1 = index; + outer2 = inner; + ptMax = testMax; + } + } + } + SkASSERT(outer1 >= 0 && outer1 <= 1); + SkASSERT(outer2 >= 1 && outer2 <= 2); + SkASSERT(outer1 < outer2); + int mid = outer1 ^ outer2 ^ 3; + const float kCurvatureSlop = 0.000005f; // this multiplier is pulled out of the air + SkScalar lineSlop = ptMax * ptMax * kCurvatureSlop; + return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop; +} + +static bool conic_in_line(const SkConic& conic) { + return quad_in_line(conic.fPts); +} + +SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4], + SkPoint reduction[3], const SkPoint** tangentPtPtr) { + bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]); + bool degenerateBC = degenerate_vector(cubic[2] - cubic[1]); + bool degenerateCD = degenerate_vector(cubic[3] - cubic[2]); + if (degenerateAB & degenerateBC & degenerateCD) { + return kPoint_ReductionType; + } + if (degenerateAB + degenerateBC + degenerateCD == 2) { + return kLine_ReductionType; + } + if (!cubic_in_line(cubic)) { + *tangentPtPtr = degenerateAB ? &cubic[2] : &cubic[1]; + return kQuad_ReductionType; + } + SkScalar tValues[3]; + int count = SkFindCubicMaxCurvature(cubic, tValues); + int rCount = 0; + // Now loop over the t-values, and reject any that evaluate to either end-point + for (int index = 0; index < count; ++index) { + SkScalar t = tValues[index]; + if (0 >= t || t >= 1) { + continue; + } + SkEvalCubicAt(cubic, t, &reduction[rCount], nullptr, nullptr); + if (reduction[rCount] != cubic[0] && reduction[rCount] != cubic[3]) { + ++rCount; + } + } + if (rCount == 0) { + return kLine_ReductionType; + } + static_assert(kQuad_ReductionType + 1 == kDegenerate_ReductionType, "enum_out_of_whack"); + static_assert(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, "enum_out_of_whack"); + static_assert(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, "enum_out_of_whack"); + + return (ReductionType) (kQuad_ReductionType + rCount); +} + +SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic, + SkPoint* reduction) { + bool degenerateAB = degenerate_vector(conic.fPts[1] - conic.fPts[0]); + bool degenerateBC = degenerate_vector(conic.fPts[2] - conic.fPts[1]); + if (degenerateAB & degenerateBC) { + return kPoint_ReductionType; + } + if (degenerateAB | degenerateBC) { + return kLine_ReductionType; + } + if (!conic_in_line(conic)) { + return kQuad_ReductionType; + } + // SkFindConicMaxCurvature would be a better solution, once we know how to + // implement it. Quad curvature is a reasonable substitute + SkScalar t = SkFindQuadMaxCurvature(conic.fPts); + if (0 == t) { + return kLine_ReductionType; + } + conic.evalAt(t, reduction, nullptr); + return kDegenerate_ReductionType; +} + +SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3], + SkPoint* reduction) { + bool degenerateAB = degenerate_vector(quad[1] - quad[0]); + bool degenerateBC = degenerate_vector(quad[2] - quad[1]); + if (degenerateAB & degenerateBC) { + return kPoint_ReductionType; + } + if (degenerateAB | degenerateBC) { + return kLine_ReductionType; + } + if (!quad_in_line(quad)) { + return kQuad_ReductionType; + } + SkScalar t = SkFindQuadMaxCurvature(quad); + if (0 == t || 1 == t) { + return kLine_ReductionType; + } + *reduction = SkEvalQuadAt(quad, t); + return kDegenerate_ReductionType; +} + +void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) { + const SkConic conic(fPrevPt, pt1, pt2, weight); + SkPoint reduction; + ReductionType reductionType = CheckConicLinear(conic, &reduction); + if (kPoint_ReductionType == reductionType) { + /* If the stroke consists of a moveTo followed by a degenerate curve, treat it + as if it were followed by a zero-length line. Lines without length + can have square and round end caps. */ + this->lineTo(pt2); + return; + } + if (kLine_ReductionType == reductionType) { + this->lineTo(pt2); + return; + } + if (kDegenerate_ReductionType == reductionType) { + this->lineTo(reduction); + SkStrokerPriv::JoinProc saveJoiner = fJoiner; + fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join); + this->lineTo(pt2); + fJoiner = saveJoiner; + return; + } + SkASSERT(kQuad_ReductionType == reductionType); + SkVector normalAB, unitAB, normalBC, unitBC; + if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) { + this->lineTo(pt2); + return; + } + SkQuadConstruct quadPts; + this->init(kOuter_StrokeType, &quadPts, 0, 1); + (void) this->conicStroke(conic, &quadPts); + this->init(kInner_StrokeType, &quadPts, 0, 1); + (void) this->conicStroke(conic, &quadPts); + this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC); + this->postJoinTo(pt2, normalBC, unitBC); +} + +void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { + const SkPoint quad[3] = { fPrevPt, pt1, pt2 }; + SkPoint reduction; + ReductionType reductionType = CheckQuadLinear(quad, &reduction); + if (kPoint_ReductionType == reductionType) { + /* If the stroke consists of a moveTo followed by a degenerate curve, treat it + as if it were followed by a zero-length line. Lines without length + can have square and round end caps. */ + this->lineTo(pt2); + return; + } + if (kLine_ReductionType == reductionType) { + this->lineTo(pt2); + return; + } + if (kDegenerate_ReductionType == reductionType) { + this->lineTo(reduction); + SkStrokerPriv::JoinProc saveJoiner = fJoiner; + fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join); + this->lineTo(pt2); + fJoiner = saveJoiner; + return; + } + SkASSERT(kQuad_ReductionType == reductionType); + SkVector normalAB, unitAB, normalBC, unitBC; + if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) { + this->lineTo(pt2); + return; + } + SkQuadConstruct quadPts; + this->init(kOuter_StrokeType, &quadPts, 0, 1); + (void) this->quadStroke(quad, &quadPts); + this->init(kInner_StrokeType, &quadPts, 0, 1); + (void) this->quadStroke(quad, &quadPts); + this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC); + + this->postJoinTo(pt2, normalBC, unitBC); +} + +// Given a point on the curve and its derivative, scale the derivative by the radius, and +// compute the perpendicular point and its tangent. +void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, + SkPoint* tangent) const { + if (!dxy->setLength(fRadius)) { + dxy->set(fRadius, 0); + } + SkScalar axisFlip = SkIntToScalar(fStrokeType); // go opposite ways for outer, inner + onPt->fX = tPt.fX + axisFlip * dxy->fY; + onPt->fY = tPt.fY - axisFlip * dxy->fX; + if (tangent) { + tangent->fX = onPt->fX + dxy->fX; + tangent->fY = onPt->fY + dxy->fY; + } +} + +// Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent. +// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0) +void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const { + SkVector dxy; + conic.evalAt(t, tPt, &dxy); + if (dxy.fX == 0 && dxy.fY == 0) { + dxy = conic.fPts[2] - conic.fPts[0]; + } + this->setRayPts(*tPt, &dxy, onPt, tangent); +} + +// Given a conic and a t range, find the start and end if they haven't been found already. +void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const { + if (!quadPts->fStartSet) { + SkPoint conicStartPt; + this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0], + &quadPts->fTangentStart); + quadPts->fStartSet = true; + } + if (!quadPts->fEndSet) { + SkPoint conicEndPt; + this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2], + &quadPts->fTangentEnd); + quadPts->fEndSet = true; + } +} + + +// Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent. +void SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const { + SkVector dxy; + SkPoint chopped[7]; + SkEvalCubicAt(cubic, t, tPt, &dxy, nullptr); + if (dxy.fX == 0 && dxy.fY == 0) { + const SkPoint* cPts = cubic; + if (SkScalarNearlyZero(t)) { + dxy = cubic[2] - cubic[0]; + } else if (SkScalarNearlyZero(1 - t)) { + dxy = cubic[3] - cubic[1]; + } else { + // If the cubic inflection falls on the cusp, subdivide the cubic + // to find the tangent at that point. + SkChopCubicAt(cubic, chopped, t); + dxy = chopped[3] - chopped[2]; + if (dxy.fX == 0 && dxy.fY == 0) { + dxy = chopped[3] - chopped[1]; + cPts = chopped; + } + } + if (dxy.fX == 0 && dxy.fY == 0) { + dxy = cPts[3] - cPts[0]; + } + } + setRayPts(*tPt, &dxy, onPt, tangent); +} + +// Given a cubic and a t range, find the start and end if they haven't been found already. +void SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) { + if (!quadPts->fStartSet) { + SkPoint cubicStartPt; + this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0], + &quadPts->fTangentStart); + quadPts->fStartSet = true; + } + if (!quadPts->fEndSet) { + SkPoint cubicEndPt; + this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2], + &quadPts->fTangentEnd); + quadPts->fEndSet = true; + } +} + +void SkPathStroker::cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* quadPts, + SkPoint* mid) const { + SkPoint cubicMidPt; + this->cubicPerpRay(cubic, quadPts->fMidT, &cubicMidPt, mid, nullptr); +} + +// Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent. +void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const { + SkVector dxy; + SkEvalQuadAt(quad, t, tPt, &dxy); + if (dxy.fX == 0 && dxy.fY == 0) { + dxy = quad[2] - quad[0]; + } + setRayPts(*tPt, &dxy, onPt, tangent); +} + +// Find the intersection of the stroke tangents to construct a stroke quad. +// Return whether the stroke is a degenerate (a line), a quad, or must be split. +// Optionally compute the quad's control point. +SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts, + IntersectRayType intersectRayType STROKER_DEBUG_PARAMS(int depth)) const { + const SkPoint& start = quadPts->fQuad[0]; + const SkPoint& end = quadPts->fQuad[2]; + SkVector aLen = quadPts->fTangentStart - start; + SkVector bLen = quadPts->fTangentEnd - end; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen ( == denom ) + */ + SkScalar denom = aLen.cross(bLen); + if (denom == 0 || !SkScalarIsFinite(denom)) { + quadPts->fOppositeTangents = aLen.dot(bLen) < 0; + return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0"); + } + quadPts->fOppositeTangents = false; + SkVector ab0 = start - end; + SkScalar numerA = bLen.cross(ab0); + SkScalar numerB = aLen.cross(ab0); + if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends + // if the perpendicular distances from the quad points to the opposite tangent line + // are small, a straight line is good enough + SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd); + SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart); + if (SkTMax(dist1, dist2) <= fInvResScaleSquared) { + return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, + "SkTMax(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2); + } + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, + "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB); + } + // check to see if the denominator is teeny relative to the numerator + // if the offset by one will be lost, the ratio is too large + numerA /= denom; + bool validDivide = numerA > numerA - 1; + if (validDivide) { + if (kCtrlPt_RayType == intersectRayType) { + SkPoint* ctrlPt = &quadPts->fQuad[1]; + // the intersection of the tangents need not be on the tangent segment + // so 0 <= numerA <= 1 is not necessarily true + ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA; + ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA; + } + return STROKER_RESULT(kQuad_ResultType, depth, quadPts, + "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB); + } + quadPts->fOppositeTangents = aLen.dot(bLen) < 0; + // if the lines are parallel, straight line is good enough + return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, + "SkScalarNearlyZero(denom=%g)", denom); +} + +// Given a cubic and a t-range, determine if the stroke can be described by a quadratic. +SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4], + SkQuadConstruct* quadPts) { + this->cubicQuadEnds(cubic, quadPts); + return this->intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth)); +} + +// Intersect the line with the quad and return the t values on the quad where the line crosses. +static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) { + SkVector vec = line[1] - line[0]; + SkScalar r[3]; + for (int n = 0; n < 3; ++n) { + r[n] = (quad[n].fY - line[0].fY) * vec.fX - (quad[n].fX - line[0].fX) * vec.fY; + } + SkScalar A = r[2]; + SkScalar B = r[1]; + SkScalar C = r[0]; + A += C - 2 * B; // A = a - 2*b + c + B -= C; // B = -(b - c) + return SkFindUnitQuadRoots(A, 2 * B, C, roots); +} + +// Return true if the point is close to the bounds of the quad. This is used as a quick reject. +bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const { + SkScalar xMin = SkTMin(SkTMin(quad[0].fX, quad[1].fX), quad[2].fX); + if (pt.fX + fInvResScale < xMin) { + return false; + } + SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX); + if (pt.fX - fInvResScale > xMax) { + return false; + } + SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY); + if (pt.fY + fInvResScale < yMin) { + return false; + } + SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY); + if (pt.fY - fInvResScale > yMax) { + return false; + } + return true; +} + +static bool points_within_dist(const SkPoint& nearPt, const SkPoint& farPt, SkScalar limit) { + return SkPointPriv::DistanceToSqd(nearPt, farPt) <= limit * limit; +} + +static bool sharp_angle(const SkPoint quad[3]) { + SkVector smaller = quad[1] - quad[0]; + SkVector larger = quad[1] - quad[2]; + SkScalar smallerLen = SkPointPriv::LengthSqd(smaller); + SkScalar largerLen = SkPointPriv::LengthSqd(larger); + if (smallerLen > largerLen) { + using std::swap; + swap(smaller, larger); + largerLen = smallerLen; + } + if (!smaller.setLength(largerLen)) { + return false; + } + SkScalar dot = smaller.dot(larger); + return dot > 0; +} + +SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3], + const SkPoint ray[2], SkQuadConstruct* quadPts STROKER_DEBUG_PARAMS(int depth)) const { + SkPoint strokeMid = SkEvalQuadAt(stroke, SK_ScalarHalf); + // measure the distance from the curve to the quad-stroke midpoint, compare to radius + if (points_within_dist(ray[0], strokeMid, fInvResScale)) { // if the difference is small + if (sharp_angle(quadPts->fQuad)) { + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, + "sharp_angle (1) =%g,%g, %g,%g, %g,%g", + quadPts->fQuad[0].fX, quadPts->fQuad[0].fY, + quadPts->fQuad[1].fX, quadPts->fQuad[1].fY, + quadPts->fQuad[2].fX, quadPts->fQuad[2].fY); + } + return STROKER_RESULT(kQuad_ResultType, depth, quadPts, + "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScale=%g)", + ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScale); + } + // measure the distance to quad's bounds (quick reject) + // an alternative : look for point in triangle + if (!ptInQuadBounds(stroke, ray[0])) { // if far, subdivide + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, + "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)", + stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY, + ray[0].fX, ray[0].fY); + } + // measure the curve ray distance to the quad-stroke + SkScalar roots[2]; + int rootCount = intersect_quad_ray(ray, stroke, roots); + if (rootCount != 1) { + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, + "rootCount=%d != 1", rootCount); + } + SkPoint quadPt = SkEvalQuadAt(stroke, roots[0]); + SkScalar error = fInvResScale * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2); + if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done + if (sharp_angle(quadPts->fQuad)) { + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, + "sharp_angle (2) =%g,%g, %g,%g, %g,%g", + quadPts->fQuad[0].fX, quadPts->fQuad[0].fY, + quadPts->fQuad[1].fX, quadPts->fQuad[1].fY, + quadPts->fQuad[2].fX, quadPts->fQuad[2].fY); + } + return STROKER_RESULT(kQuad_ResultType, depth, quadPts, + "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)", + ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error); + } + // otherwise, subdivide + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through"); +} + +SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4], + SkQuadConstruct* quadPts) { + // get the quadratic approximation of the stroke + this->cubicQuadEnds(cubic, quadPts); + ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType + STROKER_DEBUG_PARAMS(fRecursionDepth) ); + if (resultType != kQuad_ResultType) { + return resultType; + } + // project a ray from the curve to the stroke + SkPoint ray[2]; // points near midpoint on quad, midpoint on cubic + this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], nullptr); + return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts + STROKER_DEBUG_PARAMS(fRecursionDepth)); +} + +SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic, + SkQuadConstruct* quadPts) const { + // get the quadratic approximation of the stroke + this->conicQuadEnds(conic, quadPts); + ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType + STROKER_DEBUG_PARAMS(fRecursionDepth) ); + if (resultType != kQuad_ResultType) { + return resultType; + } + // project a ray from the curve to the stroke + SkPoint ray[2]; // points near midpoint on quad, midpoint on conic + this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], nullptr); + return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts + STROKER_DEBUG_PARAMS(fRecursionDepth)); +} + +SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3], + SkQuadConstruct* quadPts) { + // get the quadratic approximation of the stroke + if (!quadPts->fStartSet) { + SkPoint quadStartPt; + this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0], + &quadPts->fTangentStart); + quadPts->fStartSet = true; + } + if (!quadPts->fEndSet) { + SkPoint quadEndPt; + this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2], + &quadPts->fTangentEnd); + quadPts->fEndSet = true; + } + ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType + STROKER_DEBUG_PARAMS(fRecursionDepth)); + if (resultType != kQuad_ResultType) { + return resultType; + } + // project a ray from the curve to the stroke + SkPoint ray[2]; + this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], nullptr); + return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts + STROKER_DEBUG_PARAMS(fRecursionDepth)); +} + +void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) { + const SkPoint* quad = quadPts->fQuad; + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner; + path->lineTo(quad[2].fX, quad[2].fY); +} + +bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* quadPts) const { + SkPoint strokeMid; + this->cubicQuadMid(cubic, quadPts, &strokeMid); + SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]); + return dist < fInvResScaleSquared; +} + +bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) { + if (!fFoundTangents) { + ResultType resultType = this->tangentsMeet(cubic, quadPts); + if (kQuad_ResultType != resultType) { + if ((kDegenerate_ResultType == resultType + || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], + fInvResScale)) && cubicMidOnLine(cubic, quadPts)) { + addDegenerateLine(quadPts); + return true; + } + } else { + fFoundTangents = true; + } + } + if (fFoundTangents) { + ResultType resultType = this->compareQuadCubic(cubic, quadPts); + if (kQuad_ResultType == resultType) { + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner; + const SkPoint* stroke = quadPts->fQuad; + path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY); + return true; + } + if (kDegenerate_ResultType == resultType) { + if (!quadPts->fOppositeTangents) { + addDegenerateLine(quadPts); + return true; + } + } + } + if (!SkScalarIsFinite(quadPts->fQuad[2].fX) || !SkScalarIsFinite(quadPts->fQuad[2].fY)) { + return false; // just abort if projected quad isn't representable + } +#if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING + SkDEBUGCODE(gMaxRecursion[fFoundTangents] = SkTMax(gMaxRecursion[fFoundTangents], + fRecursionDepth + 1)); +#endif + if (++fRecursionDepth > kRecursiveLimits[fFoundTangents]) { + return false; // just abort if projected quad isn't representable + } + SkQuadConstruct half; + if (!half.initWithStart(quadPts)) { + addDegenerateLine(quadPts); + --fRecursionDepth; + return true; + } + if (!this->cubicStroke(cubic, &half)) { + return false; + } + if (!half.initWithEnd(quadPts)) { + addDegenerateLine(quadPts); + --fRecursionDepth; + return true; + } + if (!this->cubicStroke(cubic, &half)) { + return false; + } + --fRecursionDepth; + return true; +} + +bool SkPathStroker::conicStroke(const SkConic& conic, SkQuadConstruct* quadPts) { + ResultType resultType = this->compareQuadConic(conic, quadPts); + if (kQuad_ResultType == resultType) { + const SkPoint* stroke = quadPts->fQuad; + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner; + path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY); + return true; + } + if (kDegenerate_ResultType == resultType) { + addDegenerateLine(quadPts); + return true; + } +#if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING + SkDEBUGCODE(gMaxRecursion[kConic_RecursiveLimit] = SkTMax(gMaxRecursion[kConic_RecursiveLimit], + fRecursionDepth + 1)); +#endif + if (++fRecursionDepth > kRecursiveLimits[kConic_RecursiveLimit]) { + return false; // just abort if projected quad isn't representable + } + SkQuadConstruct half; + (void) half.initWithStart(quadPts); + if (!this->conicStroke(conic, &half)) { + return false; + } + (void) half.initWithEnd(quadPts); + if (!this->conicStroke(conic, &half)) { + return false; + } + --fRecursionDepth; + return true; +} + +bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) { + ResultType resultType = this->compareQuadQuad(quad, quadPts); + if (kQuad_ResultType == resultType) { + const SkPoint* stroke = quadPts->fQuad; + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner; + path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY); + return true; + } + if (kDegenerate_ResultType == resultType) { + addDegenerateLine(quadPts); + return true; + } +#if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING + SkDEBUGCODE(gMaxRecursion[kQuad_RecursiveLimit] = SkTMax(gMaxRecursion[kQuad_RecursiveLimit], + fRecursionDepth + 1)); +#endif + if (++fRecursionDepth > kRecursiveLimits[kQuad_RecursiveLimit]) { + return false; // just abort if projected quad isn't representable + } + SkQuadConstruct half; + (void) half.initWithStart(quadPts); + if (!this->quadStroke(quad, &half)) { + return false; + } + (void) half.initWithEnd(quadPts); + if (!this->quadStroke(quad, &half)) { + return false; + } + --fRecursionDepth; + return true; +} + +void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, + const SkPoint& pt3) { + const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 }; + SkPoint reduction[3]; + const SkPoint* tangentPt; + ReductionType reductionType = CheckCubicLinear(cubic, reduction, &tangentPt); + if (kPoint_ReductionType == reductionType) { + /* If the stroke consists of a moveTo followed by a degenerate curve, treat it + as if it were followed by a zero-length line. Lines without length + can have square and round end caps. */ + this->lineTo(pt3); + return; + } + if (kLine_ReductionType == reductionType) { + this->lineTo(pt3); + return; + } + if (kDegenerate_ReductionType <= reductionType && kDegenerate3_ReductionType >= reductionType) { + this->lineTo(reduction[0]); + SkStrokerPriv::JoinProc saveJoiner = fJoiner; + fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join); + if (kDegenerate2_ReductionType <= reductionType) { + this->lineTo(reduction[1]); + } + if (kDegenerate3_ReductionType == reductionType) { + this->lineTo(reduction[2]); + } + this->lineTo(pt3); + fJoiner = saveJoiner; + return; + } + SkASSERT(kQuad_ReductionType == reductionType); + SkVector normalAB, unitAB, normalCD, unitCD; + if (!this->preJoinTo(*tangentPt, &normalAB, &unitAB, false)) { + this->lineTo(pt3); + return; + } + SkScalar tValues[2]; + int count = SkFindCubicInflections(cubic, tValues); + SkScalar lastT = 0; + for (int index = 0; index <= count; ++index) { + SkScalar nextT = index < count ? tValues[index] : 1; + SkQuadConstruct quadPts; + this->init(kOuter_StrokeType, &quadPts, lastT, nextT); + (void) this->cubicStroke(cubic, &quadPts); + this->init(kInner_StrokeType, &quadPts, lastT, nextT); + (void) this->cubicStroke(cubic, &quadPts); + lastT = nextT; + } + SkScalar cusp = SkFindCubicCusp(cubic); + if (cusp > 0) { + SkPoint cuspLoc; + SkEvalCubicAt(cubic, cusp, &cuspLoc, nullptr, nullptr); + fCusper.addCircle(cuspLoc.fX, cuspLoc.fY, fRadius); + } + // emit the join even if one stroke succeeded but the last one failed + // this avoids reversing an inner stroke with a partial path followed by another moveto + this->setCubicEndNormal(cubic, normalAB, unitAB, &normalCD, &unitCD); + + this->postJoinTo(pt3, normalCD, unitCD); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkPaintDefaults.h" + +SkStroke::SkStroke() { + fWidth = SK_Scalar1; + fMiterLimit = SkPaintDefaults_MiterLimit; + fResScale = 1; + fCap = SkPaint::kDefault_Cap; + fJoin = SkPaint::kDefault_Join; + fDoFill = false; +} + +SkStroke::SkStroke(const SkPaint& p) { + fWidth = p.getStrokeWidth(); + fMiterLimit = p.getStrokeMiter(); + fResScale = 1; + fCap = (uint8_t)p.getStrokeCap(); + fJoin = (uint8_t)p.getStrokeJoin(); + fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); +} + +SkStroke::SkStroke(const SkPaint& p, SkScalar width) { + fWidth = width; + fMiterLimit = p.getStrokeMiter(); + fResScale = 1; + fCap = (uint8_t)p.getStrokeCap(); + fJoin = (uint8_t)p.getStrokeJoin(); + fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); +} + +void SkStroke::setWidth(SkScalar width) { + SkASSERT(width >= 0); + fWidth = width; +} + +void SkStroke::setMiterLimit(SkScalar miterLimit) { + SkASSERT(miterLimit >= 0); + fMiterLimit = miterLimit; +} + +void SkStroke::setCap(SkPaint::Cap cap) { + SkASSERT((unsigned)cap < SkPaint::kCapCount); + fCap = SkToU8(cap); +} + +void SkStroke::setJoin(SkPaint::Join join) { + SkASSERT((unsigned)join < SkPaint::kJoinCount); + fJoin = SkToU8(join); +} + +/////////////////////////////////////////////////////////////////////////////// + +// If src==dst, then we use a tmp path to record the stroke, and then swap +// its contents with src when we're done. +class AutoTmpPath { +public: + AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { + if (&src == *dst) { + *dst = &fTmpDst; + fSwapWithSrc = true; + } else { + (*dst)->reset(); + fSwapWithSrc = false; + } + } + + ~AutoTmpPath() { + if (fSwapWithSrc) { + fTmpDst.swap(*const_cast(&fSrc)); + } + } + +private: + SkPath fTmpDst; + const SkPath& fSrc; + bool fSwapWithSrc; +}; + +void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { + SkASSERT(dst); + + SkScalar radius = SkScalarHalf(fWidth); + + AutoTmpPath tmp(src, &dst); + + if (radius <= 0) { + return; + } + + // If src is really a rect, call our specialty strokeRect() method + { + SkRect rect; + bool isClosed; + SkPath::Direction dir; + if (src.isRect(&rect, &isClosed, &dir) && isClosed) { + this->strokeRect(rect, dst, dir); + // our answer should preserve the inverseness of the src + if (src.isInverseFillType()) { + SkASSERT(!dst->isInverseFillType()); + dst->toggleInverseFillType(); + } + return; + } + } + + // We can always ignore centers for stroke and fill convex line-only paths + // TODO: remove the line-only restriction + bool ignoreCenter = fDoFill && (src.getSegmentMasks() == SkPath::kLine_SegmentMask) && + src.isLastContourClosed() && src.isConvex(); + + SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), + fResScale, ignoreCenter); + SkPath::Iter iter(src, false); + SkPath::Verb lastSegment = SkPath::kMove_Verb; + + for (;;) { + SkPoint pts[4]; + switch (iter.next(pts)) { + case SkPath::kMove_Verb: + stroker.moveTo(pts[0]); + break; + case SkPath::kLine_Verb: + stroker.lineTo(pts[1], &iter); + lastSegment = SkPath::kLine_Verb; + break; + case SkPath::kQuad_Verb: + stroker.quadTo(pts[1], pts[2]); + lastSegment = SkPath::kQuad_Verb; + break; + case SkPath::kConic_Verb: { + stroker.conicTo(pts[1], pts[2], iter.conicWeight()); + lastSegment = SkPath::kConic_Verb; + break; + } break; + case SkPath::kCubic_Verb: + stroker.cubicTo(pts[1], pts[2], pts[3]); + lastSegment = SkPath::kCubic_Verb; + break; + case SkPath::kClose_Verb: + if (SkPaint::kButt_Cap != this->getCap()) { + /* If the stroke consists of a moveTo followed by a close, treat it + as if it were followed by a zero-length line. Lines without length + can have square and round end caps. */ + if (stroker.hasOnlyMoveTo()) { + stroker.lineTo(stroker.moveToPt()); + goto ZERO_LENGTH; + } + /* If the stroke consists of a moveTo followed by one or more zero-length + verbs, then followed by a close, treat is as if it were followed by a + zero-length line. Lines without length can have square & round end caps. */ + if (stroker.isCurrentContourEmpty()) { + ZERO_LENGTH: + lastSegment = SkPath::kLine_Verb; + break; + } + } + stroker.close(lastSegment == SkPath::kLine_Verb); + break; + case SkPath::kDone_Verb: + goto DONE; + } + } +DONE: + stroker.done(dst, lastSegment == SkPath::kLine_Verb); + + if (fDoFill && !ignoreCenter) { + if (SkPathPriv::CheapIsFirstDirection(src, SkPathPriv::kCCW_FirstDirection)) { + dst->reverseAddPath(src); + } else { + dst->addPath(src); + } + } else { + // Seems like we can assume that a 2-point src would always result in + // a convex stroke, but testing has proved otherwise. + // TODO: fix the stroker to make this assumption true (without making + // it slower that the work that will be done in computeConvexity()) +#if 0 + // this test results in a non-convex stroke :( + static void test(SkCanvas* canvas) { + SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; + SkPaint paint; + paint.setStrokeWidth(7); + paint.setStrokeCap(SkPaint::kRound_Cap); + canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); + } +#endif +#if 0 + if (2 == src.countPoints()) { + dst->setIsConvex(true); + } +#endif + } + + // our answer should preserve the inverseness of the src + if (src.isInverseFillType()) { + SkASSERT(!dst->isInverseFillType()); + dst->toggleInverseFillType(); + } +} + +static SkPath::Direction reverse_direction(SkPath::Direction dir) { + static const SkPath::Direction gOpposite[] = { SkPath::kCCW_Direction, SkPath::kCW_Direction }; + return gOpposite[dir]; +} + +static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { + SkPoint pts[8]; + + if (SkPath::kCW_Direction == dir) { + pts[0].set(r.fLeft, outer.fTop); + pts[1].set(r.fRight, outer.fTop); + pts[2].set(outer.fRight, r.fTop); + pts[3].set(outer.fRight, r.fBottom); + pts[4].set(r.fRight, outer.fBottom); + pts[5].set(r.fLeft, outer.fBottom); + pts[6].set(outer.fLeft, r.fBottom); + pts[7].set(outer.fLeft, r.fTop); + } else { + pts[7].set(r.fLeft, outer.fTop); + pts[6].set(r.fRight, outer.fTop); + pts[5].set(outer.fRight, r.fTop); + pts[4].set(outer.fRight, r.fBottom); + pts[3].set(r.fRight, outer.fBottom); + pts[2].set(r.fLeft, outer.fBottom); + pts[1].set(outer.fLeft, r.fBottom); + pts[0].set(outer.fLeft, r.fTop); + } + path->addPoly(pts, 8, true); +} + +void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, + SkPath::Direction dir) const { + SkASSERT(dst != nullptr); + dst->reset(); + + SkScalar radius = SkScalarHalf(fWidth); + if (radius <= 0) { + return; + } + + SkScalar rw = origRect.width(); + SkScalar rh = origRect.height(); + if ((rw < 0) ^ (rh < 0)) { + dir = reverse_direction(dir); + } + SkRect rect(origRect); + rect.sort(); + // reassign these, now that we know they'll be >= 0 + rw = rect.width(); + rh = rect.height(); + + SkRect r(rect); + r.outset(radius, radius); + + SkPaint::Join join = (SkPaint::Join)fJoin; + if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { + join = SkPaint::kBevel_Join; + } + + switch (join) { + case SkPaint::kMiter_Join: + dst->addRect(r, dir); + break; + case SkPaint::kBevel_Join: + addBevel(dst, rect, r, dir); + break; + case SkPaint::kRound_Join: + dst->addRoundRect(r, radius, radius, dir); + break; + default: + break; + } + + if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { + r = rect; + r.inset(radius, radius); + dst->addRect(r, reverse_direction(dir)); + } +} diff --git a/gfx/skia/skia/src/core/SkStroke.h b/gfx/skia/skia/src/core/SkStroke.h new file mode 100644 index 0000000000..66edf3d92f --- /dev/null +++ b/gfx/skia/skia/src/core/SkStroke.h @@ -0,0 +1,79 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStroke_DEFINED +#define SkStroke_DEFINED + +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "include/private/SkTo.h" +#include "src/core/SkStrokerPriv.h" + +#ifdef SK_DEBUG +extern bool gDebugStrokerErrorSet; +extern SkScalar gDebugStrokerError; +extern int gMaxRecursion[]; +#endif + +/** \class SkStroke + SkStroke is the utility class that constructs paths by stroking + geometries (lines, rects, ovals, roundrects, paths). This is + invoked when a geometry or text is drawn in a canvas with the + kStroke_Mask bit set in the paint. +*/ +class SkStroke { +public: + SkStroke(); + SkStroke(const SkPaint&); + SkStroke(const SkPaint&, SkScalar width); // width overrides paint.getStrokeWidth() + + SkPaint::Cap getCap() const { return (SkPaint::Cap)fCap; } + void setCap(SkPaint::Cap); + + SkPaint::Join getJoin() const { return (SkPaint::Join)fJoin; } + void setJoin(SkPaint::Join); + + void setMiterLimit(SkScalar); + void setWidth(SkScalar); + + bool getDoFill() const { return SkToBool(fDoFill); } + void setDoFill(bool doFill) { fDoFill = SkToU8(doFill); } + + /** + * ResScale is the "intended" resolution for the output. + * Default is 1.0. + * Larger values (res > 1) indicate that the result should be more precise, since it will + * be zoomed up, and small errors will be magnified. + * Smaller values (0 < res < 1) indicate that the result can be less precise, since it will + * be zoomed down, and small errors may be invisible. + */ + SkScalar getResScale() const { return fResScale; } + void setResScale(SkScalar rs) { + SkASSERT(rs > 0 && SkScalarIsFinite(rs)); + fResScale = rs; + } + + /** + * Stroke the specified rect, winding it in the specified direction.. + */ + void strokeRect(const SkRect& rect, SkPath* result, + SkPath::Direction = SkPath::kCW_Direction) const; + void strokePath(const SkPath& path, SkPath*) const; + + //////////////////////////////////////////////////////////////// + +private: + SkScalar fWidth, fMiterLimit; + SkScalar fResScale; + uint8_t fCap, fJoin; + bool fDoFill; + + friend class SkPaint; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkStrokeRec.cpp b/gfx/skia/skia/src/core/SkStrokeRec.cpp new file mode 100644 index 0000000000..a668dab2d0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrokeRec.cpp @@ -0,0 +1,170 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStrokeRec.h" +#include "src/core/SkPaintDefaults.h" + +// must be < 0, since ==0 means hairline, and >0 means normal stroke +#define kStrokeRec_FillStyleWidth (-SK_Scalar1) + +SkStrokeRec::SkStrokeRec(InitStyle s) { + fResScale = 1; + fWidth = (kFill_InitStyle == s) ? kStrokeRec_FillStyleWidth : 0; + fMiterLimit = SkPaintDefaults_MiterLimit; + fCap = SkPaint::kDefault_Cap; + fJoin = SkPaint::kDefault_Join; + fStrokeAndFill = false; +} + +SkStrokeRec::SkStrokeRec(const SkPaint& paint, SkScalar resScale) { + this->init(paint, paint.getStyle(), resScale); +} + +SkStrokeRec::SkStrokeRec(const SkPaint& paint, SkPaint::Style styleOverride, SkScalar resScale) { + this->init(paint, styleOverride, resScale); +} + +void SkStrokeRec::init(const SkPaint& paint, SkPaint::Style style, SkScalar resScale) { + fResScale = resScale; + + switch (style) { + case SkPaint::kFill_Style: + fWidth = kStrokeRec_FillStyleWidth; + fStrokeAndFill = false; + break; + case SkPaint::kStroke_Style: + fWidth = paint.getStrokeWidth(); + fStrokeAndFill = false; + break; + case SkPaint::kStrokeAndFill_Style: + if (0 == paint.getStrokeWidth()) { + // hairline+fill == fill + fWidth = kStrokeRec_FillStyleWidth; + fStrokeAndFill = false; + } else { + fWidth = paint.getStrokeWidth(); + fStrokeAndFill = true; + } + break; + default: + SkDEBUGFAIL("unknown paint style"); + // fall back on just fill + fWidth = kStrokeRec_FillStyleWidth; + fStrokeAndFill = false; + break; + } + + // copy these from the paint, regardless of our "style" + fMiterLimit = paint.getStrokeMiter(); + fCap = paint.getStrokeCap(); + fJoin = paint.getStrokeJoin(); +} + +SkStrokeRec::Style SkStrokeRec::getStyle() const { + if (fWidth < 0) { + return kFill_Style; + } else if (0 == fWidth) { + return kHairline_Style; + } else { + return fStrokeAndFill ? kStrokeAndFill_Style : kStroke_Style; + } +} + +void SkStrokeRec::setFillStyle() { + fWidth = kStrokeRec_FillStyleWidth; + fStrokeAndFill = false; +} + +void SkStrokeRec::setHairlineStyle() { + fWidth = 0; + fStrokeAndFill = false; +} + +void SkStrokeRec::setStrokeStyle(SkScalar width, bool strokeAndFill) { + if (strokeAndFill && (0 == width)) { + // hairline+fill == fill + this->setFillStyle(); + } else { + fWidth = width; + fStrokeAndFill = strokeAndFill; + } +} + +#include "src/core/SkStroke.h" + +#ifdef SK_DEBUG + // enables tweaking these values at runtime from Viewer + bool gDebugStrokerErrorSet = false; + SkScalar gDebugStrokerError; +#endif + +bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const { + if (fWidth <= 0) { // hairline or fill + return false; + } + + SkStroke stroker; + stroker.setCap((SkPaint::Cap)fCap); + stroker.setJoin((SkPaint::Join)fJoin); + stroker.setMiterLimit(fMiterLimit); + stroker.setWidth(fWidth); + stroker.setDoFill(fStrokeAndFill); +#ifdef SK_DEBUG + stroker.setResScale(gDebugStrokerErrorSet ? gDebugStrokerError : fResScale); +#else + stroker.setResScale(fResScale); +#endif + stroker.strokePath(src, dst); + return true; +} + +void SkStrokeRec::applyToPaint(SkPaint* paint) const { + if (fWidth < 0) { // fill + paint->setStyle(SkPaint::kFill_Style); + return; + } + + paint->setStyle(fStrokeAndFill ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style); + paint->setStrokeWidth(fWidth); + paint->setStrokeMiter(fMiterLimit); + paint->setStrokeCap((SkPaint::Cap)fCap); + paint->setStrokeJoin((SkPaint::Join)fJoin); +} + +SkScalar SkStrokeRec::getInflationRadius() const { + return GetInflationRadius((SkPaint::Join)fJoin, fMiterLimit, (SkPaint::Cap)fCap, fWidth); +} + +SkScalar SkStrokeRec::GetInflationRadius(const SkPaint& paint, SkPaint::Style style) { + SkScalar width = SkPaint::kFill_Style == style ? -SK_Scalar1 : paint.getStrokeWidth(); + return GetInflationRadius(paint.getStrokeJoin(), paint.getStrokeMiter(), paint.getStrokeCap(), + width); + +} + +SkScalar SkStrokeRec::GetInflationRadius(SkPaint::Join join, SkScalar miterLimit, SkPaint::Cap cap, + SkScalar strokeWidth) { + if (strokeWidth < 0) { // fill + return 0; + } else if (0 == strokeWidth) { + // FIXME: We need a "matrixScale" parameter here in order to properly handle hairlines. + // Their with is determined in device space, unlike other strokes. + // http://skbug.com/8157 + return SK_Scalar1; + } + + // since we're stroked, outset the rect by the radius (and join type, caps) + SkScalar multiplier = SK_Scalar1; + if (SkPaint::kMiter_Join == join) { + multiplier = SkTMax(multiplier, miterLimit); + } + if (SkPaint::kSquare_Cap == cap) { + multiplier = SkTMax(multiplier, SK_ScalarSqrt2); + } + return strokeWidth/2 * multiplier; +} + diff --git a/gfx/skia/skia/src/core/SkStrokerPriv.cpp b/gfx/skia/skia/src/core/SkStrokerPriv.cpp new file mode 100644 index 0000000000..32cf9ecb4e --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrokerPriv.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkStrokerPriv.h" + +#include + +static void ButtCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal, + const SkPoint& stop, SkPath*) { + path->lineTo(stop.fX, stop.fY); +} + +static void RoundCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal, + const SkPoint& stop, SkPath*) { + SkVector parallel; + SkPointPriv::RotateCW(normal, ¶llel); + + SkPoint projectedCenter = pivot + parallel; + + path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2); + path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2); +} + +static void SquareCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal, + const SkPoint& stop, SkPath* otherPath) { + SkVector parallel; + SkPointPriv::RotateCW(normal, ¶llel); + + if (otherPath) { + path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY); + path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY); + } else { + path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY); + path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY); + path->lineTo(stop.fX, stop.fY); + } +} + +///////////////////////////////////////////////////////////////////////////// + +static bool is_clockwise(const SkVector& before, const SkVector& after) { + return before.fX * after.fY > before.fY * after.fX; +} + +enum AngleType { + kNearly180_AngleType, + kSharp_AngleType, + kShallow_AngleType, + kNearlyLine_AngleType +}; + +static AngleType Dot2AngleType(SkScalar dot) { +// need more precise fixed normalization +// SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero); + + if (dot >= 0) { // shallow or line + return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType; + } else { // sharp or 180 + return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType; + } +} + +static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after) { +#if 1 + /* In the degenerate case that the stroke radius is larger than our segments + just connecting the two inner segments may "show through" as a funny + diagonal. To pseudo-fix this, we go through the pivot point. This adds + an extra point/edge, but I can't see a cheap way to know when this is + not needed :( + */ + inner->lineTo(pivot.fX, pivot.fY); +#endif + + inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY); +} + +static void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, + const SkPoint& pivot, const SkVector& afterUnitNormal, + SkScalar radius, SkScalar invMiterLimit, bool, bool) { + SkVector after; + afterUnitNormal.scale(radius, &after); + + if (!is_clockwise(beforeUnitNormal, afterUnitNormal)) { + using std::swap; + swap(outer, inner); + after.negate(); + } + + outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY); + HandleInnerJoin(inner, pivot, after); +} + +static void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, + const SkPoint& pivot, const SkVector& afterUnitNormal, + SkScalar radius, SkScalar invMiterLimit, bool, bool) { + SkScalar dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal); + AngleType angleType = Dot2AngleType(dotProd); + + if (angleType == kNearlyLine_AngleType) + return; + + SkVector before = beforeUnitNormal; + SkVector after = afterUnitNormal; + SkRotationDirection dir = kCW_SkRotationDirection; + + if (!is_clockwise(before, after)) { + using std::swap; + swap(outer, inner); + before.negate(); + after.negate(); + dir = kCCW_SkRotationDirection; + } + + SkMatrix matrix; + matrix.setScale(radius, radius); + matrix.postTranslate(pivot.fX, pivot.fY); + SkConic conics[SkConic::kMaxConicsForArc]; + int count = SkConic::BuildUnitArc(before, after, dir, &matrix, conics); + if (count > 0) { + for (int i = 0; i < count; ++i) { + outer->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW); + } + after.scale(radius); + HandleInnerJoin(inner, pivot, after); + } +} + +#define kOneOverSqrt2 (0.707106781f) + +static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, + const SkPoint& pivot, const SkVector& afterUnitNormal, + SkScalar radius, SkScalar invMiterLimit, + bool prevIsLine, bool currIsLine) { + // negate the dot since we're using normals instead of tangents + SkScalar dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal); + AngleType angleType = Dot2AngleType(dotProd); + SkVector before = beforeUnitNormal; + SkVector after = afterUnitNormal; + SkVector mid; + SkScalar sinHalfAngle; + bool ccw; + + if (angleType == kNearlyLine_AngleType) { + return; + } + if (angleType == kNearly180_AngleType) { + currIsLine = false; + goto DO_BLUNT; + } + + ccw = !is_clockwise(before, after); + if (ccw) { + using std::swap; + swap(outer, inner); + before.negate(); + after.negate(); + } + + /* Before we enter the world of square-roots and divides, + check if we're trying to join an upright right angle + (common case for stroking rectangles). If so, special case + that (for speed an accuracy). + Note: we only need to check one normal if dot==0 + */ + if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) { + mid = (before + after) * radius; + goto DO_MITER; + } + + /* midLength = radius / sinHalfAngle + if (midLength > miterLimit * radius) abort + if (radius / sinHalf > miterLimit * radius) abort + if (1 / sinHalf > miterLimit) abort + if (1 / miterLimit > sinHalf) abort + My dotProd is opposite sign, since it is built from normals and not tangents + hence 1 + dot instead of 1 - dot in the formula + */ + sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd)); + if (sinHalfAngle < invMiterLimit) { + currIsLine = false; + goto DO_BLUNT; + } + + // choose the most accurate way to form the initial mid-vector + if (angleType == kSharp_AngleType) { + mid.set(after.fY - before.fY, before.fX - after.fX); + if (ccw) { + mid.negate(); + } + } else { + mid.set(before.fX + after.fX, before.fY + after.fY); + } + + mid.setLength(radius / sinHalfAngle); +DO_MITER: + if (prevIsLine) { + outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY); + } else { + outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY); + } + +DO_BLUNT: + after.scale(radius); + if (!currIsLine) { + outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY); + } + HandleInnerJoin(inner, pivot, after); +} + +///////////////////////////////////////////////////////////////////////////// + +SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap) { + const SkStrokerPriv::CapProc gCappers[] = { + ButtCapper, RoundCapper, SquareCapper + }; + + SkASSERT((unsigned)cap < SkPaint::kCapCount); + return gCappers[cap]; +} + +SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) { + const SkStrokerPriv::JoinProc gJoiners[] = { + MiterJoiner, RoundJoiner, BluntJoiner + }; + + SkASSERT((unsigned)join < SkPaint::kJoinCount); + return gJoiners[join]; +} diff --git a/gfx/skia/skia/src/core/SkStrokerPriv.h b/gfx/skia/skia/src/core/SkStrokerPriv.h new file mode 100644 index 0000000000..a7294f7e27 --- /dev/null +++ b/gfx/skia/skia/src/core/SkStrokerPriv.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkStrokerPriv_DEFINED +#define SkStrokerPriv_DEFINED + +#include "src/core/SkStroke.h" + +#define CWX(x, y) (-y) +#define CWY(x, y) (x) +#define CCWX(x, y) (y) +#define CCWY(x, y) (-x) + +#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3) + +// this enables a global which is not thread-safe; doing so triggers a TSAN error in Chrome tests. +#define QUAD_STROKE_APPROX_EXTENDED_DEBUGGING 0 // set to 1 to enable debugging in StrokerTest.cpp + +class SkStrokerPriv { +public: + typedef void (*CapProc)(SkPath* path, + const SkPoint& pivot, + const SkVector& normal, + const SkPoint& stop, + SkPath* otherPath); + + typedef void (*JoinProc)(SkPath* outer, SkPath* inner, + const SkVector& beforeUnitNormal, + const SkPoint& pivot, + const SkVector& afterUnitNormal, + SkScalar radius, SkScalar invMiterLimit, + bool prevIsLine, bool currIsLine); + + static CapProc CapFactory(SkPaint::Cap); + static JoinProc JoinFactory(SkPaint::Join); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkSurfaceCharacterization.cpp b/gfx/skia/skia/src/core/SkSurfaceCharacterization.cpp new file mode 100644 index 0000000000..0d9b5e8266 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSurfaceCharacterization.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkSurfaceCharacterization.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" + +#ifdef SK_DEBUG +void SkSurfaceCharacterization::validate() const { + const GrCaps* caps = fContextInfo->priv().caps(); + + GrColorType grCT = SkColorTypeToGrColorType(this->colorType()); + SkASSERT(fSampleCnt && caps->isFormatAsColorTypeRenderable(grCT, fBackendFormat, fSampleCnt)); + + SkASSERT(caps->areColorTypeAndFormatCompatible(grCT, fBackendFormat)); +} +#endif + + +bool SkSurfaceCharacterization::operator==(const SkSurfaceCharacterization& other) const { + if (!this->isValid() || !other.isValid()) { + return false; + } + + if (fContextInfo != other.fContextInfo) { + return false; + } + + return fCacheMaxResourceBytes == other.fCacheMaxResourceBytes && + fOrigin == other.fOrigin && + fImageInfo == other.fImageInfo && + fBackendFormat == other.fBackendFormat && + fSampleCnt == other.fSampleCnt && + fIsTextureable == other.fIsTextureable && + fIsMipMapped == other.fIsMipMapped && + fUsesGLFBO0 == other.fUsesGLFBO0 && + fVulkanSecondaryCBCompatible == other.fVulkanSecondaryCBCompatible && + fIsProtected == other.fIsProtected && + fSurfaceProps == other.fSurfaceProps; +} + +SkSurfaceCharacterization SkSurfaceCharacterization::createResized(int width, int height) const { + const GrCaps* caps = fContextInfo->priv().caps(); + if (!caps) { + return SkSurfaceCharacterization(); + } + + if (width <= 0 || height <= 0 || width > caps->maxRenderTargetSize() || + height > caps->maxRenderTargetSize()) { + return SkSurfaceCharacterization(); + } + + return SkSurfaceCharacterization(fContextInfo, fCacheMaxResourceBytes, + fImageInfo.makeWH(width, height), fBackendFormat, fOrigin, + fSampleCnt, fIsTextureable, fIsMipMapped, fUsesGLFBO0, + fVulkanSecondaryCBCompatible, fIsProtected, fSurfaceProps); +} + +SkSurfaceCharacterization SkSurfaceCharacterization::createColorSpace( + sk_sp cs) const { + if (!this->isValid()) { + return SkSurfaceCharacterization(); + } + + return SkSurfaceCharacterization(fContextInfo, fCacheMaxResourceBytes, + fImageInfo.makeColorSpace(std::move(cs)), fBackendFormat, + fOrigin, fSampleCnt, fIsTextureable, fIsMipMapped, fUsesGLFBO0, + fVulkanSecondaryCBCompatible, fIsProtected, fSurfaceProps); +} + + +bool SkSurfaceCharacterization::isCompatible(const GrBackendTexture& backendTex) const { + if (!this->isValid() || !backendTex.isValid()) { + return false; + } + + if (fBackendFormat != backendTex.getBackendFormat()) { + return false; + } + + if (this->usesGLFBO0()) { + // It is a backend texture so can't be wrapping FBO0 + return false; + } + + if (this->vulkanSecondaryCBCompatible()) { + return false; + } + + if (this->isMipMapped() && !backendTex.hasMipMaps()) { + // backend texture is allowed to have mipmaps even if the characterization doesn't require + // them. + return false; + } + + if (this->width() != backendTex.width() || this->height() != backendTex.height()) { + return false; + } + + if (this->isProtected() != GrProtected(backendTex.isProtected())) { + return false; + } + + return true; +} + + +#endif diff --git a/gfx/skia/skia/src/core/SkSurfacePriv.h b/gfx/skia/skia/src/core/SkSurfacePriv.h new file mode 100644 index 0000000000..6e5ff0962f --- /dev/null +++ b/gfx/skia/skia/src/core/SkSurfacePriv.h @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSurfacePriv_DEFINED +#define SkSurfacePriv_DEFINED + +#include "include/core/SkSurfaceProps.h" + +struct SkImageInfo; + +static inline SkSurfaceProps SkSurfacePropsCopyOrDefault(const SkSurfaceProps* props) { + if (props) { + return *props; + } else { + return SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType); + } +} + +constexpr size_t kIgnoreRowBytesValue = static_cast(~0); + +bool SkSurfaceValidateRasterInfo(const SkImageInfo&, size_t rb = kIgnoreRowBytesValue); + +#endif diff --git a/gfx/skia/skia/src/core/SkSwizzle.cpp b/gfx/skia/skia/src/core/SkSwizzle.cpp new file mode 100644 index 0000000000..301b0184f1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkSwizzle.cpp @@ -0,0 +1,14 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkSwizzle.h" + +#include "src/core/SkOpts.h" + +void SkSwapRB(uint32_t* dest, const uint32_t* src, int count) { + SkOpts::RGBA_to_BGRA(dest, src, count); +} diff --git a/gfx/skia/skia/src/core/SkTDPQueue.h b/gfx/skia/skia/src/core/SkTDPQueue.h new file mode 100644 index 0000000000..569bbcbb45 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTDPQueue.h @@ -0,0 +1,219 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTDPQueue_DEFINED +#define SkTDPQueue_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/core/SkTSort.h" + +#include + +/** + * This class implements a priority queue. T is the type of the elements in the queue. LESS is a + * function that compares two Ts and returns true if the first is higher priority than the second. + * + * Optionally objects may know their index into the priority queue. The queue will update the index + * as the objects move through the queue. This is enabled by using a non-nullptr function for INDEX. + * When an INDEX function is provided random deletes from the queue are allowed using remove(). + * Additionally, the * priority is allowed to change as long as priorityDidChange() is called + * afterwards. In debug builds the index will be set to -1 before an element is removed from the + * queue. + */ +template +class SkTDPQueue { +public: + SkTDPQueue() {} + SkTDPQueue(int reserve) { fArray.setReserve(reserve); } + + SkTDPQueue(SkTDPQueue&&) = default; + SkTDPQueue& operator =(SkTDPQueue&&) = default; + + SkTDPQueue(const SkTDPQueue&) = delete; + SkTDPQueue& operator=(const SkTDPQueue&) = delete; + + /** Number of items in the queue. */ + int count() const { return fArray.count(); } + + /** Gets the next item in the queue without popping it. */ + const T& peek() const { return fArray[0]; } + T& peek() { return fArray[0]; } + + /** Removes the next item. */ + void pop() { + this->validate(); + SkDEBUGCODE(if (SkToBool(INDEX)) { *INDEX(fArray[0]) = -1; }) + if (1 == fArray.count()) { + fArray.pop(); + return; + } + + fArray[0] = fArray[fArray.count() - 1]; + this->setIndex(0); + fArray.pop(); + this->percolateDownIfNecessary(0); + + this->validate(); + } + + /** Inserts a new item in the queue based on its priority. */ + void insert(T entry) { + this->validate(); + int index = fArray.count(); + *fArray.append() = entry; + this->setIndex(fArray.count() - 1); + this->percolateUpIfNecessary(index); + this->validate(); + } + + /** Random access removal. This requires that the INDEX function is non-nullptr. */ + void remove(T entry) { + SkASSERT(nullptr != INDEX); + int index = *INDEX(entry); + SkASSERT(index >= 0 && index < fArray.count()); + this->validate(); + SkDEBUGCODE(*INDEX(fArray[index]) = -1;) + if (index == fArray.count() - 1) { + fArray.pop(); + return; + } + fArray[index] = fArray[fArray.count() - 1]; + fArray.pop(); + this->setIndex(index); + this->percolateUpOrDown(index); + this->validate(); + } + + /** Notification that the priority of an entry has changed. This must be called after an + item's priority is changed to maintain correct ordering. Changing the priority is only + allowed if an INDEX function is provided. */ + void priorityDidChange(T entry) { + SkASSERT(nullptr != INDEX); + int index = *INDEX(entry); + SkASSERT(index >= 0 && index < fArray.count()); + this->validate(index); + this->percolateUpOrDown(index); + this->validate(); + } + + /** Gets the item at index i in the priority queue (for i < this->count()). at(0) is equivalent + to peek(). Otherwise, there is no guarantee about ordering of elements in the queue. */ + T at(int i) const { return fArray[i]; } + + /** Sorts the queue into priority order. The queue is only guarenteed to remain in sorted order + * until any other operation, other than at(), is performed. + */ + void sort() { + if (fArray.count() > 1) { + SkTQSort(fArray.begin(), fArray.end() - 1, LESS); + for (int i = 0; i < fArray.count(); i++) { + this->setIndex(i); + } + this->validate(); + } + } + +private: + static int LeftOf(int x) { SkASSERT(x >= 0); return 2 * x + 1; } + static int ParentOf(int x) { SkASSERT(x > 0); return (x - 1) >> 1; } + + void percolateUpOrDown(int index) { + SkASSERT(index >= 0); + if (!percolateUpIfNecessary(index)) { + this->validate(index); + this->percolateDownIfNecessary(index); + } + } + + bool percolateUpIfNecessary(int index) { + SkASSERT(index >= 0); + bool percolated = false; + do { + if (0 == index) { + this->setIndex(index); + return percolated; + } + int p = ParentOf(index); + if (LESS(fArray[index], fArray[p])) { + using std::swap; + swap(fArray[index], fArray[p]); + this->setIndex(index); + index = p; + percolated = true; + } else { + this->setIndex(index); + return percolated; + } + this->validate(index); + } while (true); + } + + void percolateDownIfNecessary(int index) { + SkASSERT(index >= 0); + do { + int child = LeftOf(index); + + if (child >= fArray.count()) { + // We're a leaf. + this->setIndex(index); + return; + } + + if (child + 1 >= fArray.count()) { + // We only have a left child. + if (LESS(fArray[child], fArray[index])) { + using std::swap; + swap(fArray[child], fArray[index]); + this->setIndex(child); + this->setIndex(index); + return; + } + } else if (LESS(fArray[child + 1], fArray[child])) { + // The right child is the one we should swap with, if we swap. + child++; + } + + // Check if we need to swap. + if (LESS(fArray[child], fArray[index])) { + using std::swap; + swap(fArray[child], fArray[index]); + this->setIndex(index); + index = child; + } else { + // We're less than both our children. + this->setIndex(index); + return; + } + this->validate(index); + } while (true); + } + + void setIndex(int index) { + SkASSERT(index < fArray.count()); + if (SkToBool(INDEX)) { + *INDEX(fArray[index]) = index; + } + } + + void validate(int excludedIndex = -1) const { +#ifdef SK_DEBUG + for (int i = 1; i < fArray.count(); ++i) { + int p = ParentOf(i); + if (excludedIndex != p && excludedIndex != i) { + SkASSERT(!(LESS(fArray[i], fArray[p]))); + SkASSERT(!SkToBool(INDEX) || *INDEX(fArray[i]) == i); + } + } +#endif + } + + SkTDArray fArray; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTDynamicHash.h b/gfx/skia/skia/src/core/SkTDynamicHash.h new file mode 100644 index 0000000000..381d2129a8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTDynamicHash.h @@ -0,0 +1,298 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTDynamicHash_DEFINED +#define SkTDynamicHash_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTemplates.h" + +// Traits requires: +// static const Key& GetKey(const T&) { ... } +// static uint32_t Hash(const Key&) { ... } +// We'll look on T for these by default, or you can pass a custom Traits type. +template // Larger -> more memory efficient, but slower. +class SkTDynamicHash { +public: + SkTDynamicHash() : fCount(0), fDeleted(0), fCapacity(0), fArray(nullptr) { + SkASSERT(this->validate()); + } + + ~SkTDynamicHash() { + sk_free(fArray); + } + + class Iter { + public: + explicit Iter(SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) { + SkASSERT(hash); + ++(*this); + } + bool done() const { + SkASSERT(fCurrentIndex <= fHash->fCapacity); + return fCurrentIndex == fHash->fCapacity; + } + T& operator*() const { + SkASSERT(!this->done()); + return *this->current(); + } + void operator++() { + do { + fCurrentIndex++; + } while (!this->done() && (this->current() == Empty() || this->current() == Deleted())); + } + + private: + T* current() const { return fHash->fArray[fCurrentIndex]; } + + SkTDynamicHash* fHash; + int fCurrentIndex; + }; + + class ConstIter { + public: + explicit ConstIter(const SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) { + SkASSERT(hash); + ++(*this); + } + bool done() const { + SkASSERT(fCurrentIndex <= fHash->fCapacity); + return fCurrentIndex == fHash->fCapacity; + } + const T& operator*() const { + SkASSERT(!this->done()); + return *this->current(); + } + void operator++() { + do { + fCurrentIndex++; + } while (!this->done() && (this->current() == Empty() || this->current() == Deleted())); + } + + private: + const T* current() const { return fHash->fArray[fCurrentIndex]; } + + const SkTDynamicHash* fHash; + int fCurrentIndex; + }; + + int count() const { return fCount; } + + // Return the entry with this key if we have it, otherwise nullptr. + T* find(const Key& key) const { + int index = this->firstIndex(key); + for (int round = 0; round < fCapacity; round++) { + SkASSERT(index >= 0 && index < fCapacity); + T* candidate = fArray[index]; + if (Empty() == candidate) { + return nullptr; + } + if (Deleted() != candidate && GetKey(*candidate) == key) { + return candidate; + } + index = this->nextIndex(index, round); + } + SkASSERT(fCapacity == 0); + return nullptr; + } + + // Add an entry with this key. We require that no entry with newEntry's key is already present. + void add(T* newEntry) { + SkASSERT(nullptr == this->find(GetKey(*newEntry))); + this->maybeGrow(); + this->innerAdd(newEntry); + SkASSERT(this->validate()); + } + + // Remove the entry with this key. We require that an entry with this key is present. + void remove(const Key& key) { + SkASSERT(this->find(key)); + this->innerRemove(key); + SkASSERT(this->validate()); + } + + void rewind() { + if (fArray) { + sk_bzero(fArray, sizeof(T*)* fCapacity); + } + fCount = 0; + fDeleted = 0; + } + + void reset() { + fCount = 0; + fDeleted = 0; + fCapacity = 0; + sk_free(fArray); + fArray = nullptr; + } + +protected: + // These methods are used by tests only. + + int capacity() const { return fCapacity; } + + // How many collisions do we go through before finding where this entry should be inserted? + int countCollisions(const Key& key) const { + int index = this->firstIndex(key); + for (int round = 0; round < fCapacity; round++) { + SkASSERT(index >= 0 && index < fCapacity); + const T* candidate = fArray[index]; + if (Empty() == candidate || Deleted() == candidate || GetKey(*candidate) == key) { + return round; + } + index = this->nextIndex(index, round); + } + SkASSERT(fCapacity == 0); + return 0; + } + +private: + // We have two special values to indicate an empty or deleted entry. + static T* Empty() { return reinterpret_cast(0); } // i.e. nullptr + static T* Deleted() { return reinterpret_cast(1); } // Also an invalid pointer. + + bool validate() const { + #define SKTDYNAMICHASH_CHECK(x) SkASSERT(x); if (!(x)) return false + static const int kLarge = 50; // Arbitrary, tweak to suit your patience. + + // O(1) checks, always done. + // Is capacity sane? + SKTDYNAMICHASH_CHECK(SkIsPow2(fCapacity)); + + // O(N) checks, skipped when very large. + if (fCount < kLarge * kLarge) { + // Are fCount and fDeleted correct, and are all elements findable? + int count = 0, deleted = 0; + for (int i = 0; i < fCapacity; i++) { + if (Deleted() == fArray[i]) { + deleted++; + } else if (Empty() != fArray[i]) { + count++; + SKTDYNAMICHASH_CHECK(this->find(GetKey(*fArray[i]))); + } + } + SKTDYNAMICHASH_CHECK(count == fCount); + SKTDYNAMICHASH_CHECK(deleted == fDeleted); + } + + // O(N^2) checks, skipped when large. + if (fCount < kLarge) { + // Are all entries unique? + for (int i = 0; i < fCapacity; i++) { + if (Empty() == fArray[i] || Deleted() == fArray[i]) { + continue; + } + for (int j = i+1; j < fCapacity; j++) { + if (Empty() == fArray[j] || Deleted() == fArray[j]) { + continue; + } + SKTDYNAMICHASH_CHECK(fArray[i] != fArray[j]); + SKTDYNAMICHASH_CHECK(!(GetKey(*fArray[i]) == GetKey(*fArray[j]))); + } + } + } + #undef SKTDYNAMICHASH_CHECK + return true; + } + + void innerAdd(T* newEntry) { + const Key& key = GetKey(*newEntry); + int index = this->firstIndex(key); + for (int round = 0; round < fCapacity; round++) { + SkASSERT(index >= 0 && index < fCapacity); + const T* candidate = fArray[index]; + if (Empty() == candidate || Deleted() == candidate) { + if (Deleted() == candidate) { + fDeleted--; + } + fCount++; + fArray[index] = newEntry; + return; + } + index = this->nextIndex(index, round); + } + SkASSERT(fCapacity == 0); + } + + void innerRemove(const Key& key) { + const int firstIndex = this->firstIndex(key); + int index = firstIndex; + for (int round = 0; round < fCapacity; round++) { + SkASSERT(index >= 0 && index < fCapacity); + const T* candidate = fArray[index]; + if (Deleted() != candidate && GetKey(*candidate) == key) { + fDeleted++; + fCount--; + fArray[index] = Deleted(); + return; + } + index = this->nextIndex(index, round); + } + SkASSERT(fCapacity == 0); + } + + void maybeGrow() { + if (100 * (int64_t(fCount + fDeleted) + 1) > int64_t(fCapacity) * kGrowPercent) { + auto newCapacity = fCapacity > 0 ? fCapacity : 4; + + // Only grow the storage when most non-empty entries are + // in active use. Otherwise, just purge the tombstones. + if (fCount > fDeleted) { + SkASSERT_RELEASE(newCapacity <= std::numeric_limits::max() / 2); + newCapacity *= 2; + } + SkASSERT(newCapacity > fCount + 1); + this->resize(newCapacity); + } + } + + void resize(int newCapacity) { + SkDEBUGCODE(int oldCount = fCount;) + int oldCapacity = fCapacity; + SkAutoTMalloc oldArray(fArray); + + fCount = fDeleted = 0; + fCapacity = newCapacity; + fArray = (T**)sk_calloc_throw(fCapacity, sizeof(T*)); + + for (int i = 0; i < oldCapacity; i++) { + T* entry = oldArray[i]; + if (Empty() != entry && Deleted() != entry) { + this->innerAdd(entry); + } + } + SkASSERT(oldCount == fCount); + } + + // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash. + uint32_t hashMask() const { return fCapacity - 1; } + + int firstIndex(const Key& key) const { + return Hash(key) & this->hashMask(); + } + + // Given index at round N, what is the index to check at N+1? round should start at 0. + int nextIndex(int index, int round) const { + // This will search a power-of-two array fully without repeating an index. + return (index + round + 1) & this->hashMask(); + } + + static const Key& GetKey(const T& t) { return Traits::GetKey(t); } + static uint32_t Hash(const Key& key) { return Traits::Hash(key); } + + int fCount; // Number of non Empty(), non Deleted() entries in fArray. + int fDeleted; // Number of Deleted() entries in fArray. + int fCapacity; // Number of entries in fArray. Always a power of 2. + T** fArray; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTInternalLList.h b/gfx/skia/skia/src/core/SkTInternalLList.h new file mode 100644 index 0000000000..f435c0c4f0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTInternalLList.h @@ -0,0 +1,302 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTInternalLList_DEFINED +#define SkTInternalLList_DEFINED + +#include "include/core/SkTypes.h" + +/** + * This macro creates the member variables required by the SkTInternalLList class. It should be + * placed in the private section of any class that will be stored in a double linked list. + */ +#define SK_DECLARE_INTERNAL_LLIST_INTERFACE(ClassName) \ + friend class SkTInternalLList; \ + /* back pointer to the owning list - for debugging */ \ + SkDEBUGCODE(SkTInternalLList* fList = nullptr;) \ + ClassName* fPrev = nullptr; \ + ClassName* fNext = nullptr + +/** + * This class implements a templated internal doubly linked list data structure. + */ +template class SkTInternalLList { +public: + SkTInternalLList() {} + + void reset() { + fHead = nullptr; + fTail = nullptr; + } + + void remove(T* entry) { + SkASSERT(fHead && fTail); + SkASSERT(this->isInList(entry)); + + T* prev = entry->fPrev; + T* next = entry->fNext; + + if (prev) { + prev->fNext = next; + } else { + fHead = next; + } + if (next) { + next->fPrev = prev; + } else { + fTail = prev; + } + + entry->fPrev = nullptr; + entry->fNext = nullptr; + +#ifdef SK_DEBUG + entry->fList = nullptr; +#endif + } + + void addToHead(T* entry) { + SkASSERT(nullptr == entry->fPrev && nullptr == entry->fNext); + SkASSERT(nullptr == entry->fList); + + entry->fPrev = nullptr; + entry->fNext = fHead; + if (fHead) { + fHead->fPrev = entry; + } + fHead = entry; + if (nullptr == fTail) { + fTail = entry; + } + +#ifdef SK_DEBUG + entry->fList = this; +#endif + } + + void addToTail(T* entry) { + SkASSERT(nullptr == entry->fPrev && nullptr == entry->fNext); + SkASSERT(nullptr == entry->fList); + + entry->fPrev = fTail; + entry->fNext = nullptr; + if (fTail) { + fTail->fNext = entry; + } + fTail = entry; + if (nullptr == fHead) { + fHead = entry; + } + +#ifdef SK_DEBUG + entry->fList = this; +#endif + } + + /** + * Inserts a new list entry before an existing list entry. The new entry must not already be + * a member of this or any other list. If existingEntry is NULL then the new entry is added + * at the tail. + */ + void addBefore(T* newEntry, T* existingEntry) { + SkASSERT(newEntry); + + if (nullptr == existingEntry) { + this->addToTail(newEntry); + return; + } + + SkASSERT(this->isInList(existingEntry)); + newEntry->fNext = existingEntry; + T* prev = existingEntry->fPrev; + existingEntry->fPrev = newEntry; + newEntry->fPrev = prev; + if (nullptr == prev) { + SkASSERT(fHead == existingEntry); + fHead = newEntry; + } else { + prev->fNext = newEntry; + } +#ifdef SK_DEBUG + newEntry->fList = this; +#endif + } + + /** + * Inserts a new list entry after an existing list entry. The new entry must not already be + * a member of this or any other list. If existingEntry is NULL then the new entry is added + * at the head. + */ + void addAfter(T* newEntry, T* existingEntry) { + SkASSERT(newEntry); + + if (nullptr == existingEntry) { + this->addToHead(newEntry); + return; + } + + SkASSERT(this->isInList(existingEntry)); + newEntry->fPrev = existingEntry; + T* next = existingEntry->fNext; + existingEntry->fNext = newEntry; + newEntry->fNext = next; + if (nullptr == next) { + SkASSERT(fTail == existingEntry); + fTail = newEntry; + } else { + next->fPrev = newEntry; + } +#ifdef SK_DEBUG + newEntry->fList = this; +#endif + } + + void concat(SkTInternalLList&& list) { + if (list.isEmpty()) { + return; + } + + list.fHead->fPrev = fTail; + if (!fHead) { + SkASSERT(!list.fHead->fPrev); + fHead = list.fHead; + } else { + SkASSERT(fTail); + fTail->fNext = list.fHead; + } + fTail = list.fTail; + +#ifdef SK_DEBUG + for (T* node = list.fHead; node; node = node->fNext) { + SkASSERT(node->fList == &list); + node->fList = this; + } +#endif + + list.fHead = list.fTail = nullptr; + } + + bool isEmpty() const { + SkASSERT(SkToBool(fHead) == SkToBool(fTail)); + return !fHead; + } + + T* head() { return fHead; } + T* tail() { return fTail; } + + class Iter { + public: + enum IterStart { + kHead_IterStart, + kTail_IterStart + }; + + Iter() : fCurr(nullptr) {} + Iter(const Iter& iter) : fCurr(iter.fCurr) {} + Iter& operator= (const Iter& iter) { fCurr = iter.fCurr; return *this; } + + T* init(const SkTInternalLList& list, IterStart startLoc) { + if (kHead_IterStart == startLoc) { + fCurr = list.fHead; + } else { + SkASSERT(kTail_IterStart == startLoc); + fCurr = list.fTail; + } + + return fCurr; + } + + T* get() { return fCurr; } + + /** + * Return the next/previous element in the list or NULL if at the end. + */ + T* next() { + if (nullptr == fCurr) { + return nullptr; + } + + fCurr = fCurr->fNext; + return fCurr; + } + + T* prev() { + if (nullptr == fCurr) { + return nullptr; + } + + fCurr = fCurr->fPrev; + return fCurr; + } + + /** + * C++11 range-for interface. + */ + bool operator!=(const Iter& that) { return fCurr != that.fCurr; } + T* operator*() { return this->get(); } + void operator++() { this->next(); } + + private: + T* fCurr; + }; + + Iter begin() const { + Iter iter; + iter.init(*this, Iter::kHead_IterStart); + return iter; + } + + Iter end() const { return Iter(); } + +#ifdef SK_DEBUG + void validate() const { + SkASSERT(!fHead == !fTail); + Iter iter; + for (T* item = iter.init(*this, Iter::kHead_IterStart); item; item = iter.next()) { + SkASSERT(this->isInList(item)); + if (nullptr == item->fPrev) { + SkASSERT(fHead == item); + } else { + SkASSERT(item->fPrev->fNext == item); + } + if (nullptr == item->fNext) { + SkASSERT(fTail == item); + } else { + SkASSERT(item->fNext->fPrev == item); + } + } + } + + /** + * Debugging-only method that uses the list back pointer to check if 'entry' is indeed in 'this' + * list. + */ + bool isInList(const T* entry) const { + return entry->fList == this; + } + + /** + * Debugging-only method that laboriously counts the list entries. + */ + int countEntries() const { + int count = 0; + for (T* entry = fHead; entry; entry = entry->fNext) { + ++count; + } + return count; + } +#endif // SK_DEBUG + +private: + T* fHead = nullptr; + T* fTail = nullptr; + + SkTInternalLList(const SkTInternalLList&) = delete; + SkTInternalLList& operator=(const SkTInternalLList&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTLList.h b/gfx/skia/skia/src/core/SkTLList.h new file mode 100644 index 0000000000..496ee9e9de --- /dev/null +++ b/gfx/skia/skia/src/core/SkTLList.h @@ -0,0 +1,354 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTLList_DEFINED +#define SkTLList_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkTInternalLList.h" +#include +#include + +/** Doubly-linked list of objects. The objects' lifetimes are controlled by the list. I.e. the + the list creates the objects and they are deleted upon removal. This class block-allocates + space for entries based on a param passed to the constructor. + + Elements of the list can be constructed in place using the following macros: + SkNEW_INSERT_IN_LLIST_BEFORE(list, location, type_name, args) + SkNEW_INSERT_IN_LLIST_AFTER(list, location, type_name, args) + where list is a SkTLList*, location is an iterator, and args is the paren-surrounded + constructor arguments for type_name. These macros behave like addBefore() and addAfter(). + + allocCnt is the number of objects to allocate as a group. In the worst case fragmentation + each object is using the space required for allocCnt unfragmented objects. +*/ +template class SkTLList { +private: + struct Block; + struct Node { + SkAlignedSTStorage<1, T> fObj; + SK_DECLARE_INTERNAL_LLIST_INTERFACE(Node); + Block* fBlock; // owning block. + }; + typedef SkTInternalLList NodeList; + +public: + class Iter; + + // Having fCount initialized to -1 indicates that the first time we attempt to grab a free node + // all the nodes in the pre-allocated first block need to be inserted into the free list. This + // allows us to skip that loop in instances when the list is never populated. + SkTLList() : fCount(-1) {} + + ~SkTLList() { + this->validate(); + typename NodeList::Iter iter; + Node* node = iter.init(fList, Iter::kHead_IterStart); + while (node) { + reinterpret_cast(node->fObj.get())->~T(); + Block* block = node->fBlock; + node = iter.next(); + if (0 == --block->fNodesInUse) { + for (unsigned int i = 0; i < N; ++i) { + block->fNodes[i].~Node(); + } + if (block != &fFirstBlock) { + sk_free(block); + } + } + } + } + + /** Adds a new element to the list at the head. */ + template T* addToHead(Args&&... args) { + this->validate(); + Node* node = this->createNode(); + fList.addToHead(node); + this->validate(); + return new (node->fObj.get()) T(std::forward(args)...); + } + + /** Adds a new element to the list at the tail. */ + template T* addToTail(Args&&... args) { + this->validate(); + Node* node = this->createNode(); + fList.addToTail(node); + this->validate(); + return new (node->fObj.get()) T(std::forward(args)...); + } + + /** Adds a new element to the list before the location indicated by the iterator. If the + iterator refers to a nullptr location then the new element is added at the tail */ + template T* addBefore(Iter location, Args&&... args) { + this->validate(); + Node* node = this->createNode(); + fList.addBefore(node, location.getNode()); + this->validate(); + return new (node->fObj.get()) T(std::forward(args)...); + } + + /** Adds a new element to the list after the location indicated by the iterator. If the + iterator refers to a nullptr location then the new element is added at the head */ + template T* addAfter(Iter location, Args&&... args) { + this->validate(); + Node* node = this->createNode(); + fList.addAfter(node, location.getNode()); + this->validate(); + return new (node->fObj.get()) T(std::forward(args)...); + } + + /** Convenience methods for getting an iterator initialized to the head/tail of the list. */ + Iter headIter() const { return Iter(*this, Iter::kHead_IterStart); } + Iter tailIter() const { return Iter(*this, Iter::kTail_IterStart); } + + T* head() { return Iter(*this, Iter::kHead_IterStart).get(); } + T* tail() { return Iter(*this, Iter::kTail_IterStart).get(); } + const T* head() const { return Iter(*this, Iter::kHead_IterStart).get(); } + const T* tail() const { return Iter(*this, Iter::kTail_IterStart).get(); } + + void popHead() { + this->validate(); + Node* node = fList.head(); + if (node) { + this->removeNode(node); + } + this->validate(); + } + + void popTail() { + this->validate(); + Node* node = fList.head(); + if (node) { + this->removeNode(node); + } + this->validate(); + } + + void remove(T* t) { + this->validate(); + Node* node = reinterpret_cast(t); + SkASSERT(reinterpret_cast(node->fObj.get()) == t); + this->removeNode(node); + this->validate(); + } + + void reset() { + this->validate(); + Iter iter(*this, Iter::kHead_IterStart); + while (iter.get()) { + Iter next = iter; + next.next(); + this->remove(iter.get()); + iter = next; + } + SkASSERT(0 == fCount || -1 == fCount); + this->validate(); + } + + int count() const { return SkTMax(fCount ,0); } + bool isEmpty() const { this->validate(); return 0 == fCount || -1 == fCount; } + + bool operator== (const SkTLList& list) const { + if (this == &list) { + return true; + } + // Call count() rather than use fCount because an empty list may have fCount = 0 or -1. + if (this->count() != list.count()) { + return false; + } + for (Iter a(*this, Iter::kHead_IterStart), b(list, Iter::kHead_IterStart); + a.get(); + a.next(), b.next()) { + SkASSERT(b.get()); // already checked that counts match. + if (!(*a.get() == *b.get())) { + return false; + } + } + return true; + } + bool operator!= (const SkTLList& list) const { return !(*this == list); } + + /** The iterator becomes invalid if the element it refers to is removed from the list. */ + class Iter : private NodeList::Iter { + private: + typedef typename NodeList::Iter INHERITED; + + public: + typedef typename INHERITED::IterStart IterStart; + //!< Start the iterator at the head of the list. + static const IterStart kHead_IterStart = INHERITED::kHead_IterStart; + //!< Start the iterator at the tail of the list. + static const IterStart kTail_IterStart = INHERITED::kTail_IterStart; + + Iter() {} + + Iter(const SkTLList& list, IterStart start = kHead_IterStart) { + INHERITED::init(list.fList, start); + } + + T* init(const SkTLList& list, IterStart start = kHead_IterStart) { + return this->nodeToObj(INHERITED::init(list.fList, start)); + } + + T* get() { return this->nodeToObj(INHERITED::get()); } + + T* next() { return this->nodeToObj(INHERITED::next()); } + + T* prev() { return this->nodeToObj(INHERITED::prev()); } + + Iter& operator= (const Iter& iter) { INHERITED::operator=(iter); return *this; } + + private: + friend class SkTLList; + Node* getNode() { return INHERITED::get(); } + + T* nodeToObj(Node* node) { + if (node) { + return reinterpret_cast(node->fObj.get()); + } else { + return nullptr; + } + } + }; + +private: + struct Block { + int fNodesInUse; + Node fNodes[N]; + }; + + void delayedInit() { + SkASSERT(-1 == fCount); + fFirstBlock.fNodesInUse = 0; + for (unsigned int i = 0; i < N; ++i) { + fFreeList.addToHead(fFirstBlock.fNodes + i); + fFirstBlock.fNodes[i].fBlock = &fFirstBlock; + } + fCount = 0; + this->validate(); + } + + Node* createNode() { + if (-1 == fCount) { + this->delayedInit(); + } + Node* node = fFreeList.head(); + if (node) { + fFreeList.remove(node); + ++node->fBlock->fNodesInUse; + } else { + // Should not get here when count == 0 because we always have the preallocated first + // block. + SkASSERT(fCount > 0); + Block* block = reinterpret_cast(sk_malloc_throw(sizeof(Block))); + node = &block->fNodes[0]; + new (node) Node; + node->fBlock = block; + block->fNodesInUse = 1; + for (unsigned int i = 1; i < N; ++i) { + new (block->fNodes + i) Node; + fFreeList.addToHead(block->fNodes + i); + block->fNodes[i].fBlock = block; + } + } + ++fCount; + return node; + } + + void removeNode(Node* node) { + SkASSERT(node); + fList.remove(node); + reinterpret_cast(node->fObj.get())->~T(); + Block* block = node->fBlock; + // Don't ever elease the first block, just add its nodes to the free list + if (0 == --block->fNodesInUse && block != &fFirstBlock) { + for (unsigned int i = 0; i < N; ++i) { + if (block->fNodes + i != node) { + fFreeList.remove(block->fNodes + i); + } + block->fNodes[i].~Node(); + } + sk_free(block); + } else { + fFreeList.addToHead(node); + } + --fCount; + this->validate(); + } + + void validate() const { +#ifdef SK_DEBUG + bool isEmpty = false; + if (-1 == fCount) { + // We should not yet have initialized the free list. + SkASSERT(fFreeList.isEmpty()); + isEmpty = true; + } else if (0 == fCount) { + // Should only have the nodes from the first block in the free list. + SkASSERT(fFreeList.countEntries() == N); + isEmpty = true; + } + SkASSERT(isEmpty == fList.isEmpty()); + fList.validate(); + fFreeList.validate(); + typename NodeList::Iter iter; + Node* freeNode = iter.init(fFreeList, Iter::kHead_IterStart); + while (freeNode) { + SkASSERT(fFreeList.isInList(freeNode)); + Block* block = freeNode->fBlock; + // Only the first block is allowed to have all its nodes in the free list. + SkASSERT(block->fNodesInUse > 0 || block == &fFirstBlock); + SkASSERT((unsigned)block->fNodesInUse < N); + int activeCnt = 0; + int freeCnt = 0; + for (unsigned int i = 0; i < N; ++i) { + bool free = fFreeList.isInList(block->fNodes + i); + bool active = fList.isInList(block->fNodes + i); + SkASSERT(free != active); + activeCnt += active; + freeCnt += free; + } + SkASSERT(activeCnt == block->fNodesInUse); + freeNode = iter.next(); + } + + int count = 0; + Node* activeNode = iter.init(fList, Iter::kHead_IterStart); + while (activeNode) { + ++count; + SkASSERT(fList.isInList(activeNode)); + Block* block = activeNode->fBlock; + SkASSERT(block->fNodesInUse > 0 && (unsigned)block->fNodesInUse <= N); + + int activeCnt = 0; + int freeCnt = 0; + for (unsigned int i = 0; i < N; ++i) { + bool free = fFreeList.isInList(block->fNodes + i); + bool active = fList.isInList(block->fNodes + i); + SkASSERT(free != active); + activeCnt += active; + freeCnt += free; + } + SkASSERT(activeCnt == block->fNodesInUse); + activeNode = iter.next(); + } + SkASSERT(count == fCount || (0 == count && -1 == fCount)); +#endif + } + + NodeList fList; + NodeList fFreeList; + Block fFirstBlock; + int fCount; + + SkTLList(const SkTLList&) = delete; + SkTLList& operator=(const SkTLList&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTLS.cpp b/gfx/skia/skia/src/core/SkTLS.cpp new file mode 100644 index 0000000000..e781a539b1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTLS.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkTLS.h" + +struct SkTLSRec { + SkTLSRec* fNext; + void* fData; + SkTLS::CreateProc fCreateProc; + SkTLS::DeleteProc fDeleteProc; + + ~SkTLSRec() { + if (fDeleteProc) { + fDeleteProc(fData); + } + // else we leak fData, or it will be managed by the caller + } +}; + +void SkTLS::Destructor(void* ptr) { + SkTLSRec* rec = (SkTLSRec*)ptr; + do { + SkTLSRec* next = rec->fNext; + delete rec; + rec = next; + } while (rec); +} + +void* SkTLS::Get(CreateProc createProc, DeleteProc deleteProc) { + if (nullptr == createProc) { + return nullptr; + } + + void* ptr = SkTLS::PlatformGetSpecific(true); + + if (ptr) { + const SkTLSRec* rec = (const SkTLSRec*)ptr; + do { + if (rec->fCreateProc == createProc) { + SkASSERT(rec->fDeleteProc == deleteProc); + return rec->fData; + } + } while ((rec = rec->fNext) != nullptr); + // not found, so create a new one + } + + // add a new head of our change + SkTLSRec* rec = new SkTLSRec; + rec->fNext = (SkTLSRec*)ptr; + + SkTLS::PlatformSetSpecific(rec); + + rec->fData = createProc(); + rec->fCreateProc = createProc; + rec->fDeleteProc = deleteProc; + return rec->fData; +} + +void* SkTLS::Find(CreateProc createProc) { + if (nullptr == createProc) { + return nullptr; + } + + void* ptr = SkTLS::PlatformGetSpecific(false); + + if (ptr) { + const SkTLSRec* rec = (const SkTLSRec*)ptr; + do { + if (rec->fCreateProc == createProc) { + return rec->fData; + } + } while ((rec = rec->fNext) != nullptr); + } + return nullptr; +} + +void SkTLS::Delete(CreateProc createProc) { + if (nullptr == createProc) { + return; + } + + void* ptr = SkTLS::PlatformGetSpecific(false); + + SkTLSRec* curr = (SkTLSRec*)ptr; + SkTLSRec* prev = nullptr; + while (curr) { + SkTLSRec* next = curr->fNext; + if (curr->fCreateProc == createProc) { + if (prev) { + prev->fNext = next; + } else { + // we have a new head of our chain + SkTLS::PlatformSetSpecific(next); + } + delete curr; + break; + } + prev = curr; + curr = next; + } +} diff --git a/gfx/skia/skia/src/core/SkTLS.h b/gfx/skia/skia/src/core/SkTLS.h new file mode 100644 index 0000000000..3d86a26433 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTLS.h @@ -0,0 +1,83 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTLS_DEFINED +#define SkTLS_DEFINED + +#include "include/core/SkTypes.h" + +/** + * Maintains a per-thread cache, using a CreateProc as the key into that cache. + */ +class SkTLS { +public: + typedef void* (*CreateProc)(); + typedef void (*DeleteProc)(void*); + + /** + * If Get() has previously been called with this CreateProc, then this + * returns its cached data, otherwise it returns nullptr. The CreateProc is + * never invoked in Find, it is only used as a key for searching the + * cache. + */ + static void* Find(CreateProc); + + /** + * Return the cached data that was returned by the CreateProc. This proc + * is only called the first time Get is called, and there after it is + * cached (per-thread), using the CreateProc as a key to look it up. + * + * When this thread, or Delete is called, the cached data is removed, and + * if a DeleteProc was specified, it is passed the pointer to the cached + * data. + */ + static void* Get(CreateProc, DeleteProc); + + /** + * Remove (optionally calling the DeleteProc if it was specificed in Get) + * the cached data associated with this CreateProc. If no associated cached + * data is found, do nothing. + */ + static void Delete(CreateProc); + +private: + // Our implementation requires only 1 TLS slot, as we manage multiple values + // ourselves in a list, with the platform specific value as our head. + + /** + * Implemented by the platform, to return the value of our (one) slot per-thread + * + * If forceCreateTheSlot is true, then we must have created the "slot" for + * our TLS, even though we know that the return value will be nullptr in that + * case (i.e. no-slot and first-time-slot both return nullptr). This ensures + * that after calling GetSpecific, we know that we can legally call + * SetSpecific. + * + * If forceCreateTheSlot is false, then the impl can either create the + * slot or not. + */ + static void* PlatformGetSpecific(bool forceCreateTheSlot); + + /** + * Implemented by the platform, to set the value for our (one) slot per-thread + * + * The implementation can rely on GetSpecific(true) having been previously + * called before SetSpecific is called. + */ + static void PlatformSetSpecific(void*); + +public: + /** + * Will delete our internal list. To be called by the platform if/when its + * TLS slot is deleted (often at thread shutdown). + * + * Public *only* for the platform's use, not to be called by a client. + */ + static void Destructor(void* ptr); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTLazy.h b/gfx/skia/skia/src/core/SkTLazy.h new file mode 100644 index 0000000000..a6533cbcc2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTLazy.h @@ -0,0 +1,202 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTLazy_DEFINED +#define SkTLazy_DEFINED + +#include "include/core/SkTypes.h" +#include +#include +#include + +/** + * Efficient way to defer allocating/initializing a class until it is needed + * (if ever). + */ +template class SkTLazy { +public: + SkTLazy() = default; + explicit SkTLazy(const T* src) : fPtr(src ? new (&fStorage) T(*src) : nullptr) {} + SkTLazy(const SkTLazy& that) : fPtr(that.fPtr ? new (&fStorage) T(*that.fPtr) : nullptr) {} + SkTLazy(SkTLazy&& that) : fPtr(that.fPtr ? new (&fStorage) T(std::move(*that.fPtr)) : nullptr){} + + ~SkTLazy() { this->reset(); } + + SkTLazy& operator=(const SkTLazy& that) { + if (that.isValid()) { + this->set(*that); + } else { + this->reset(); + } + return *this; + } + + SkTLazy& operator=(SkTLazy&& that) { + if (that.isValid()) { + this->set(std::move(*that)); + } else { + this->reset(); + } + return *this; + } + + /** + * Return a pointer to an instance of the class initialized with 'args'. + * If a previous instance had been initialized (either from init() or + * set()) it will first be destroyed, so that a freshly initialized + * instance is always returned. + */ + template T* init(Args&&... args) { + this->reset(); + fPtr = new (&fStorage) T(std::forward(args)...); + return fPtr; + } + + /** + * Copy src into this, and return a pointer to a copy of it. Note this + * will always return the same pointer, so if it is called on a lazy that + * has already been initialized, then this will copy over the previous + * contents. + */ + T* set(const T& src) { + if (this->isValid()) { + *fPtr = src; + } else { + fPtr = new (&fStorage) T(src); + } + return fPtr; + } + + T* set(T&& src) { + if (this->isValid()) { + *fPtr = std::move(src); + } else { + fPtr = new (&fStorage) T(std::move(src)); + } + return fPtr; + } + + /** + * Destroy the lazy object (if it was created via init() or set()) + */ + void reset() { + if (this->isValid()) { + fPtr->~T(); + fPtr = nullptr; + } + } + + /** + * Returns true if a valid object has been initialized in the SkTLazy, + * false otherwise. + */ + bool isValid() const { return SkToBool(fPtr); } + + /** + * Returns the object. This version should only be called when the caller + * knows that the object has been initialized. + */ + T* get() const { SkASSERT(this->isValid()); return fPtr; } + T* operator->() const { return this->get(); } + T& operator*() const { return *this->get(); } + + /** + * Like above but doesn't assert if object isn't initialized (in which case + * nullptr is returned). + */ + T* getMaybeNull() const { return fPtr; } + +private: + typename std::aligned_storage::type fStorage; + T* fPtr{nullptr}; // nullptr or fStorage +}; + +/** + * A helper built on top of SkTLazy to do copy-on-first-write. The object is initialized + * with a const pointer but provides a non-const pointer accessor. The first time the + * accessor is called (if ever) the object is cloned. + * + * In the following example at most one copy of constThing is made: + * + * SkTCopyOnFirstWrite thing(&constThing); + * ... + * function_that_takes_a_const_thing_ptr(thing); // constThing is passed + * ... + * if (need_to_modify_thing()) { + * thing.writable()->modifyMe(); // makes a copy of constThing + * } + * ... + * x = thing->readSomething(); + * ... + * if (need_to_modify_thing_now()) { + * thing.writable()->changeMe(); // makes a copy of constThing if we didn't call modifyMe() + * } + * + * consume_a_thing(thing); // could be constThing or a modified copy. + */ +template +class SkTCopyOnFirstWrite { +public: + explicit SkTCopyOnFirstWrite(const T& initial) : fObj(&initial) {} + + explicit SkTCopyOnFirstWrite(const T* initial) : fObj(initial) {} + + // Constructor for delayed initialization. + SkTCopyOnFirstWrite() : fObj(nullptr) {} + + SkTCopyOnFirstWrite(const SkTCopyOnFirstWrite& that) { *this = that; } + SkTCopyOnFirstWrite( SkTCopyOnFirstWrite&& that) { *this = std::move(that); } + + SkTCopyOnFirstWrite& operator=(const SkTCopyOnFirstWrite& that) { + fLazy = that.fLazy; + fObj = fLazy.isValid() ? fLazy.get() : that.fObj; + return *this; + } + + SkTCopyOnFirstWrite& operator=(SkTCopyOnFirstWrite&& that) { + fLazy = std::move(that.fLazy); + fObj = fLazy.isValid() ? fLazy.get() : that.fObj; + return *this; + } + + // Should only be called once, and only if the default constructor was used. + void init(const T& initial) { + SkASSERT(nullptr == fObj); + SkASSERT(!fLazy.isValid()); + fObj = &initial; + } + + /** + * Returns a writable T*. The first time this is called the initial object is cloned. + */ + T* writable() { + SkASSERT(fObj); + if (!fLazy.isValid()) { + fLazy.set(*fObj); + fObj = fLazy.get(); + } + return const_cast(fObj); + } + + const T* get() const { return fObj; } + + /** + * Operators for treating this as though it were a const pointer. + */ + + const T *operator->() const { return fObj; } + + operator const T*() const { return fObj; } + + const T& operator *() const { return *fObj; } + +private: + const T* fObj; + SkTLazy fLazy; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTMultiMap.h b/gfx/skia/skia/src/core/SkTMultiMap.h new file mode 100644 index 0000000000..d14aa97b0f --- /dev/null +++ b/gfx/skia/skia/src/core/SkTMultiMap.h @@ -0,0 +1,210 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTMultiMap_DEFINED +#define SkTMultiMap_DEFINED + +#include "include/gpu/GrTypes.h" +#include "src/core/SkTDynamicHash.h" + +/** A set that contains pointers to instances of T. Instances can be looked up with key Key. + * Multiple (possibly same) values can have the same key. + */ +template +class SkTMultiMap { + struct ValueList { + explicit ValueList(T* value) : fValue(value), fNext(nullptr) {} + + static const Key& GetKey(const ValueList& e) { return HashTraits::GetKey(*e.fValue); } + static uint32_t Hash(const Key& key) { return HashTraits::Hash(key); } + T* fValue; + ValueList* fNext; + }; +public: + SkTMultiMap() : fCount(0) {} + + ~SkTMultiMap() { + typename SkTDynamicHash::Iter iter(&fHash); + for ( ; !iter.done(); ++iter) { + ValueList* next; + for (ValueList* cur = &(*iter); cur; cur = next) { + HashTraits::OnFree(cur->fValue); + next = cur->fNext; + delete cur; + } + } + } + + void insert(const Key& key, T* value) { + ValueList* list = fHash.find(key); + if (list) { + // The new ValueList entry is inserted as the second element in the + // linked list, and it will contain the value of the first element. + ValueList* newEntry = new ValueList(list->fValue); + newEntry->fNext = list->fNext; + // The existing first ValueList entry is updated to contain the + // inserted value. + list->fNext = newEntry; + list->fValue = value; + } else { + fHash.add(new ValueList(value)); + } + + ++fCount; + } + + void remove(const Key& key, const T* value) { + ValueList* list = fHash.find(key); + // Temporarily making this safe for remove entries not in the map because of + // crbug.com/877915. +#if 0 + // Since we expect the caller to be fully aware of what is stored, just + // assert that the caller removes an existing value. + SkASSERT(list); + ValueList* prev = nullptr; + while (list->fValue != value) { + prev = list; + list = list->fNext; + } + this->internalRemove(prev, list, key); +#else + ValueList* prev = nullptr; + while (list && list->fValue != value) { + prev = list; + list = list->fNext; + } + // Crash in Debug since it'd be great to detect a repro of 877915. + SkASSERT(list); + if (list) { + this->internalRemove(prev, list, key); + } +#endif + } + + T* find(const Key& key) const { + ValueList* list = fHash.find(key); + if (list) { + return list->fValue; + } + return nullptr; + } + + template + T* find(const Key& key, const FindPredicate f) { + ValueList* list = fHash.find(key); + while (list) { + if (f(list->fValue)){ + return list->fValue; + } + list = list->fNext; + } + return nullptr; + } + + template + T* findAndRemove(const Key& key, const FindPredicate f) { + ValueList* list = fHash.find(key); + + ValueList* prev = nullptr; + while (list) { + if (f(list->fValue)){ + T* value = list->fValue; + this->internalRemove(prev, list, key); + return value; + } + prev = list; + list = list->fNext; + } + return nullptr; + } + + int count() const { return fCount; } + +#ifdef SK_DEBUG + class ConstIter { + public: + explicit ConstIter(const SkTMultiMap* mmap) + : fIter(&(mmap->fHash)) + , fList(nullptr) { + if (!fIter.done()) { + fList = &(*fIter); + } + } + + bool done() const { + return fIter.done(); + } + + const T* operator*() { + SkASSERT(fList); + return fList->fValue; + } + + void operator++() { + if (fList) { + fList = fList->fNext; + } + if (!fList) { + ++fIter; + if (!fIter.done()) { + fList = &(*fIter); + } + } + } + + private: + typename SkTDynamicHash::ConstIter fIter; + const ValueList* fList; + }; + + bool has(const T* value, const Key& key) const { + for (ValueList* list = fHash.find(key); list; list = list->fNext) { + if (list->fValue == value) { + return true; + } + } + return false; + } + + // This is not particularly fast and only used for validation, so debug only. + int countForKey(const Key& key) const { + int count = 0; + ValueList* list = fHash.find(key); + while (list) { + list = list->fNext; + ++count; + } + return count; + } +#endif + +private: + SkTDynamicHash fHash; + int fCount; + + void internalRemove(ValueList* prev, ValueList* elem, const Key& key) { + if (elem->fNext) { + ValueList* next = elem->fNext; + elem->fValue = next->fValue; + elem->fNext = next->fNext; + delete next; + } else if (prev) { + prev->fNext = nullptr; + delete elem; + } else { + fHash.remove(key); + delete elem; + } + + --fCount; + } + +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTSearch.cpp b/gfx/skia/skia/src/core/SkTSearch.cpp new file mode 100644 index 0000000000..dbcd83a88c --- /dev/null +++ b/gfx/skia/skia/src/core/SkTSearch.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/core/SkTSearch.h" + +#include "include/private/SkMalloc.h" + +#include + +static inline const char* index_into_base(const char*const* base, int index, + size_t elemSize) +{ + return *(const char*const*)((const char*)base + index * elemSize); +} + +int SkStrSearch(const char*const* base, int count, const char target[], + size_t target_len, size_t elemSize) +{ + if (count <= 0) + return ~0; + + SkASSERT(base != nullptr); + + int lo = 0; + int hi = count - 1; + + while (lo < hi) + { + int mid = (hi + lo) >> 1; + const char* elem = index_into_base(base, mid, elemSize); + + int cmp = strncmp(elem, target, target_len); + if (cmp < 0) + lo = mid + 1; + else if (cmp > 0 || strlen(elem) > target_len) + hi = mid; + else + return mid; + } + + const char* elem = index_into_base(base, hi, elemSize); + int cmp = strncmp(elem, target, target_len); + if (cmp || strlen(elem) > target_len) + { + if (cmp < 0) + hi += 1; + hi = ~hi; + } + return hi; +} + +int SkStrSearch(const char*const* base, int count, const char target[], + size_t elemSize) +{ + return SkStrSearch(base, count, target, strlen(target), elemSize); +} + +int SkStrLCSearch(const char*const* base, int count, const char target[], + size_t len, size_t elemSize) +{ + SkASSERT(target); + + SkAutoAsciiToLC tolc(target, len); + + return SkStrSearch(base, count, tolc.lc(), len, elemSize); +} + +int SkStrLCSearch(const char*const* base, int count, const char target[], + size_t elemSize) +{ + return SkStrLCSearch(base, count, target, strlen(target), elemSize); +} + +////////////////////////////////////////////////////////////////////////////// + +SkAutoAsciiToLC::SkAutoAsciiToLC(const char str[], size_t len) +{ + // see if we need to compute the length + if ((long)len < 0) { + len = strlen(str); + } + fLength = len; + + // assign lc to our preallocated storage if len is small enough, or allocate + // it on the heap + char* lc; + if (len <= STORAGE) { + lc = fStorage; + } else { + lc = (char*)sk_malloc_throw(len + 1); + } + fLC = lc; + + // convert any asii to lower-case. we let non-ascii (utf8) chars pass + // through unchanged + for (int i = (int)(len - 1); i >= 0; --i) { + int c = str[i]; + if ((c & 0x80) == 0) { // is just ascii + c = tolower(c); + } + lc[i] = c; + } + lc[len] = 0; +} + +SkAutoAsciiToLC::~SkAutoAsciiToLC() +{ + if (fLC != fStorage) { + sk_free(fLC); + } +} diff --git a/gfx/skia/skia/src/core/SkTSearch.h b/gfx/skia/skia/src/core/SkTSearch.h new file mode 100644 index 0000000000..0466917a27 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTSearch.h @@ -0,0 +1,146 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkTSearch_DEFINED +#define SkTSearch_DEFINED + +#include "include/core/SkTypes.h" + +/** + * All of the SkTSearch variants want to return the index (0...N-1) of the + * found element, or the bit-not of where to insert the element. + * + * At a simple level, if the return value is negative, it was not found. + * + * For clients that want to insert the new element if it was not found, use + * the following logic: + * + * int index = SkTSearch(...); + * if (index >= 0) { + * // found at index + * } else { + * index = ~index; // now we are positive + * // insert at index + * } + */ + + +// The most general form of SkTSearch takes an array of T and a key of type K. A functor, less, is +// used to perform comparisons. It has two function operators: +// bool operator() (const T& t, const K& k) +// bool operator() (const K& t, const T& k) +template +int SkTSearch(const T base[], int count, const K& key, size_t elemSize, LESS& less) +{ + SkASSERT(count >= 0); + if (count <= 0) { + return ~0; + } + + SkASSERT(base != nullptr); // base may be nullptr if count is zero + + int lo = 0; + int hi = count - 1; + + while (lo < hi) { + int mid = lo + ((hi - lo) >> 1); + const T* elem = (const T*)((const char*)base + mid * elemSize); + + if (less(*elem, key)) + lo = mid + 1; + else + hi = mid; + } + + const T* elem = (const T*)((const char*)base + hi * elemSize); + if (less(*elem, key)) { + hi += 1; + hi = ~hi; + } else if (less(key, *elem)) { + hi = ~hi; + } + return hi; +} + +// Adapts a less-than function to a functor. +template struct SkTLessFunctionToFunctorAdaptor { + bool operator()(const T& a, const T& b) { return LESS(a, b); } +}; + +// Specialization for case when T==K and the caller wants to use a function rather than functor. +template +int SkTSearch(const T base[], int count, const T& target, size_t elemSize) { + static SkTLessFunctionToFunctorAdaptor functor; + return SkTSearch(base, count, target, elemSize, functor); +} + +// Adapts operator < to a functor. +template struct SkTLessFunctor { + bool operator()(const T& a, const T& b) { return a < b; } +}; + +// Specialization for T==K, compare using op <. +template +int SkTSearch(const T base[], int count, const T& target, size_t elemSize) { + static SkTLessFunctor functor; + return SkTSearch(base, count, target, elemSize, functor); +} + +// Similar to SkLessFunctionToFunctorAdaptor but makes the functor interface take T* rather than T. +template struct SkTLessFunctionToPtrFunctorAdaptor { + bool operator() (const T* t, const T* k) { return LESS(*t, *k); } +}; + +// Specialization for case where domain is an array of T* and the key value is a T*, and you want +// to compare the T objects, not the pointers. +template +int SkTSearch(T* base[], int count, T* target, size_t elemSize) { + static SkTLessFunctionToPtrFunctorAdaptor functor; + return SkTSearch(base, count, target, elemSize, functor); +} + +int SkStrSearch(const char*const* base, int count, const char target[], + size_t target_len, size_t elemSize); +int SkStrSearch(const char*const* base, int count, const char target[], + size_t elemSize); + +/** Like SkStrSearch, but treats target as if it were all lower-case. Assumes that + base points to a table of lower-case strings. +*/ +int SkStrLCSearch(const char*const* base, int count, const char target[], + size_t target_len, size_t elemSize); +int SkStrLCSearch(const char*const* base, int count, const char target[], + size_t elemSize); + +/** Helper class to convert a string to lower-case, but only modifying the ascii + characters. This makes the routine very fast and never changes the string + length, but it is not suitable for linguistic purposes. Normally this is + used for buiding and searching string tables. +*/ +class SkAutoAsciiToLC { +public: + SkAutoAsciiToLC(const char str[], size_t len = (size_t)-1); + ~SkAutoAsciiToLC(); + + const char* lc() const { return fLC; } + size_t length() const { return fLength; } + +private: + char* fLC; // points to either the heap or fStorage + size_t fLength; + enum { + STORAGE = 64 + }; + char fStorage[STORAGE+1]; +}; + +// Helper when calling qsort with a compare proc that has typed its arguments +#define SkCastForQSort(compare) reinterpret_cast(compare) + +#endif diff --git a/gfx/skia/skia/src/core/SkTSort.h b/gfx/skia/skia/src/core/SkTSort.h new file mode 100644 index 0000000000..1e853b0da7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTSort.h @@ -0,0 +1,216 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTSort_DEFINED +#define SkTSort_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkTo.h" +#include "src/core/SkMathPriv.h" + +#include + +/* A comparison functor which performs the comparison 'a < b'. */ +template struct SkTCompareLT { + bool operator()(const T a, const T b) const { return a < b; } +}; + +/* A comparison functor which performs the comparison '*a < *b'. */ +template struct SkTPointerCompareLT { + bool operator()(const T* a, const T* b) const { return *a < *b; } +}; + +/////////////////////////////////////////////////////////////////////////////// + +/* Sifts a broken heap. The input array is a heap from root to bottom + * except that the root entry may be out of place. + * + * Sinks a hole from array[root] to leaf and then sifts the original array[root] element + * from the leaf level up. + * + * This version does extra work, in that it copies child to parent on the way down, + * then copies parent to child on the way back up. When copies are inexpensive, + * this is an optimization as this sift variant should only be used when + * the potentially out of place root entry value is expected to be small. + * + * @param root the one based index into array of the out-of-place root of the heap. + * @param bottom the one based index in the array of the last entry in the heap. + */ +template +void SkTHeapSort_SiftUp(T array[], size_t root, size_t bottom, C lessThan) { + T x = array[root-1]; + size_t start = root; + size_t j = root << 1; + while (j <= bottom) { + if (j < bottom && lessThan(array[j-1], array[j])) { + ++j; + } + array[root-1] = array[j-1]; + root = j; + j = root << 1; + } + j = root >> 1; + while (j >= start) { + if (lessThan(array[j-1], x)) { + array[root-1] = array[j-1]; + root = j; + j = root >> 1; + } else { + break; + } + } + array[root-1] = x; +} + +/* Sifts a broken heap. The input array is a heap from root to bottom + * except that the root entry may be out of place. + * + * Sifts the array[root] element from the root down. + * + * @param root the one based index into array of the out-of-place root of the heap. + * @param bottom the one based index in the array of the last entry in the heap. + */ +template +void SkTHeapSort_SiftDown(T array[], size_t root, size_t bottom, C lessThan) { + T x = array[root-1]; + size_t child = root << 1; + while (child <= bottom) { + if (child < bottom && lessThan(array[child-1], array[child])) { + ++child; + } + if (lessThan(x, array[child-1])) { + array[root-1] = array[child-1]; + root = child; + child = root << 1; + } else { + break; + } + } + array[root-1] = x; +} + +/** Sorts the array of size count using comparator lessThan using a Heap Sort algorithm. Be sure to + * specialize swap if T has an efficient swap operation. + * + * @param array the array to be sorted. + * @param count the number of elements in the array. + * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b. + */ +template void SkTHeapSort(T array[], size_t count, C lessThan) { + for (size_t i = count >> 1; i > 0; --i) { + SkTHeapSort_SiftDown(array, i, count, lessThan); + } + + for (size_t i = count - 1; i > 0; --i) { + using std::swap; + swap(array[0], array[i]); + SkTHeapSort_SiftUp(array, 1, i, lessThan); + } +} + +/** Sorts the array of size count using comparator '<' using a Heap Sort algorithm. */ +template void SkTHeapSort(T array[], size_t count) { + SkTHeapSort(array, count, SkTCompareLT()); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** Sorts the array of size count using comparator lessThan using an Insertion Sort algorithm. */ +template static void SkTInsertionSort(T* left, T* right, C lessThan) { + for (T* next = left + 1; next <= right; ++next) { + if (!lessThan(*next, *(next - 1))) { + continue; + } + T insert = std::move(*next); + T* hole = next; + do { + *hole = std::move(*(hole - 1)); + --hole; + } while (left < hole && lessThan(insert, *(hole - 1))); + *hole = std::move(insert); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +template +static T* SkTQSort_Partition(T* left, T* right, T* pivot, C lessThan) { + using std::swap; + T pivotValue = *pivot; + swap(*pivot, *right); + T* newPivot = left; + while (left < right) { + if (lessThan(*left, pivotValue)) { + swap(*left, *newPivot); + newPivot += 1; + } + left += 1; + } + swap(*newPivot, *right); + return newPivot; +} + +/* Intro Sort is a modified Quick Sort. + * When the region to be sorted is a small constant size it uses Insertion Sort. + * When depth becomes zero, it switches over to Heap Sort. + * This implementation recurses on the left region after pivoting and loops on the right, + * we already limit the stack depth by switching to heap sort, + * and cache locality on the data appears more important than saving a few stack frames. + * + * @param depth at this recursion depth, switch to Heap Sort. + * @param left the beginning of the region to be sorted. + * @param right the end of the region to be sorted (inclusive). + * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b. + */ +template void SkTIntroSort(int depth, T* left, T* right, C lessThan) { + while (true) { + if (right - left < 32) { + SkTInsertionSort(left, right, lessThan); + return; + } + + if (depth == 0) { + SkTHeapSort(left, right - left + 1, lessThan); + return; + } + --depth; + + T* pivot = left + ((right - left) >> 1); + pivot = SkTQSort_Partition(left, right, pivot, lessThan); + + SkTIntroSort(depth, left, pivot - 1, lessThan); + left = pivot + 1; + } +} + +/** Sorts the region from left to right using comparator lessThan using a Quick Sort algorithm. Be + * sure to specialize swap if T has an efficient swap operation. + * + * @param left the beginning of the region to be sorted. + * @param right the end of the region to be sorted (inclusive). + * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b. + */ +template void SkTQSort(T* left, T* right, C lessThan) { + if (left >= right) { + return; + } + // Limit Intro Sort recursion depth to no more than 2 * ceil(log2(n)). + int depth = 2 * SkNextLog2(SkToU32(right - left)); + SkTIntroSort(depth, left, right, lessThan); +} + +/** Sorts the region from left to right using comparator '<' using a Quick Sort algorithm. */ +template void SkTQSort(T* left, T* right) { + SkTQSort(left, right, SkTCompareLT()); +} + +/** Sorts the region from left to right using comparator '* < *' using a Quick Sort algorithm. */ +template void SkTQSort(T** left, T** right) { + SkTQSort(left, right, SkTPointerCompareLT()); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkTTopoSort.h b/gfx/skia/skia/src/core/SkTTopoSort.h new file mode 100644 index 0000000000..bc1b07ef59 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTTopoSort.h @@ -0,0 +1,113 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTTopoSort_DEFINED +#define SkTTopoSort_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/SkTArray.h" + +#ifdef SK_DEBUG +template +void SkTTopoSort_CheckAllUnmarked(const SkTArray>& graph) { + for (int i = 0; i < graph.count(); ++i) { + SkASSERT(!Traits::IsTempMarked(graph[i].get())); + SkASSERT(!Traits::WasOutput(graph[i].get())); + } +} + +template +void SkTTopoSort_CleanExit(const SkTArray>& graph) { + for (int i = 0; i < graph.count(); ++i) { + SkASSERT(!Traits::IsTempMarked(graph[i].get())); + SkASSERT(Traits::WasOutput(graph[i].get())); + } +} +#endif + +// Recursively visit a node and all the other nodes it depends on. +// Return false if there is a loop. +template +bool SkTTopoSort_Visit(T* node, SkTArray>* result) { + if (Traits::IsTempMarked(node)) { + // There is a loop. + return false; + } + + // If the node under consideration has been already been output it means it + // (and all the nodes it depends on) are already in 'result'. + if (!Traits::WasOutput(node)) { + // This node hasn't been output yet. Recursively assess all the + // nodes it depends on outputing them first. + Traits::SetTempMark(node); + for (int i = 0; i < Traits::NumDependencies(node); ++i) { + if (!SkTTopoSort_Visit(Traits::Dependency(node, i), result)) { + return false; + } + } + Traits::Output(node, result->count()); // mark this node as output + Traits::ResetTempMark(node); + + result->push_back(sk_ref_sp(node)); + } + + return true; +} + +// Topologically sort the nodes in 'graph'. For this sort, when node 'i' depends +// on node 'j' it means node 'j' must appear in the result before node 'i'. +// A false return value means there was a loop and the contents of 'graph' will +// be in some arbitrary state. +// +// Traits requires: +// static void Output(T* t, int index) { ... } // 'index' is 't's position in the result +// static bool WasOutput(const T* t) { ... } +// +// static void SetTempMark(T* t) { ... } // transiently used during toposort +// static void ResetTempMark(T* t) { ... } +// static bool IsTempMarked(const T* t) { ... } +// +// static int NumDependencies(const T* t) { ... } // 't' will be output after all the other - +// static T* Dependency(T* t, int index) { ... } // nodes on which it depends +// We'll look on T for these by default, or you can pass a custom Traits type. +// +// TODO: potentially add a version that takes a seed node and just outputs that +// node and all the nodes on which it depends. This could be used to partially +// flush a GrRenderTask DAG. +template +bool SkTTopoSort(SkTArray>* graph) { + SkTArray> result; + +#ifdef SK_DEBUG + SkTTopoSort_CheckAllUnmarked(*graph); +#endif + + result.reserve(graph->count()); + + for (int i = 0; i < graph->count(); ++i) { + if (Traits::WasOutput((*graph)[i].get())) { + // This node was depended on by some earlier node and has already + // been output + continue; + } + + // Output this node after all the nodes it depends on have been output. + if (!SkTTopoSort_Visit((*graph)[i].get(), &result)) { + return false; + } + } + + SkASSERT(graph->count() == result.count()); + graph->swap(result); + +#ifdef SK_DEBUG + SkTTopoSort_CleanExit(*graph); +#endif + return true; +} + +#endif diff --git a/gfx/skia/skia/src/core/SkTaskGroup.cpp b/gfx/skia/skia/src/core/SkTaskGroup.cpp new file mode 100644 index 0000000000..5425821c31 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTaskGroup.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkExecutor.h" +#include "src/core/SkTaskGroup.h" + +SkTaskGroup::SkTaskGroup(SkExecutor& executor) : fPending(0), fExecutor(executor) {} + +void SkTaskGroup::add(std::function fn) { + fPending.fetch_add(+1, std::memory_order_relaxed); + fExecutor.add([=] { + fn(); + fPending.fetch_add(-1, std::memory_order_release); + }); +} + +void SkTaskGroup::batch(int N, std::function fn) { + // TODO: I really thought we had some sort of more clever chunking logic. + fPending.fetch_add(+N, std::memory_order_relaxed); + for (int i = 0; i < N; i++) { + fExecutor.add([=] { + fn(i); + fPending.fetch_add(-1, std::memory_order_release); + }); + } +} + +bool SkTaskGroup::done() const { + return fPending.load(std::memory_order_acquire) == 0; +} + +void SkTaskGroup::wait() { + // Actively help the executor do work until our task group is done. + // This lets SkTaskGroups nest arbitrarily deep on a single SkExecutor: + // no thread ever blocks waiting for others to do its work. + // (We may end up doing work that's not part of our task group. That's fine.) + while (!this->done()) { + fExecutor.borrow(); + } +} + +SkTaskGroup::Enabler::Enabler(int threads) { + if (threads) { + fThreadPool = SkExecutor::MakeLIFOThreadPool(threads); + SkExecutor::SetDefault(fThreadPool.get()); + } +} diff --git a/gfx/skia/skia/src/core/SkTaskGroup.h b/gfx/skia/skia/src/core/SkTaskGroup.h new file mode 100644 index 0000000000..8d0da7c64c --- /dev/null +++ b/gfx/skia/skia/src/core/SkTaskGroup.h @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTaskGroup_DEFINED +#define SkTaskGroup_DEFINED + +#include "include/core/SkExecutor.h" +#include "include/core/SkTypes.h" +#include "include/private/SkNoncopyable.h" +#include +#include + +class SkTaskGroup : SkNoncopyable { +public: + // Tasks added to this SkTaskGroup will run on its executor. + explicit SkTaskGroup(SkExecutor& executor = SkExecutor::GetDefault()); + ~SkTaskGroup() { this->wait(); } + + // Add a task to this SkTaskGroup. + void add(std::function fn); + + // Add a batch of N tasks, all calling fn with different arguments. + void batch(int N, std::function fn); + + // Returns true if all Tasks previously add()ed to this SkTaskGroup have run. + // It is safe to reuse this SkTaskGroup once done(). + bool done() const; + + // Block until done(). + void wait(); + + // A convenience for testing tools. + // Creates and owns a thread pool, and passes it to SkExecutor::SetDefault(). + struct Enabler { + explicit Enabler(int threads = -1); // -1 -> num_cores, 0 -> noop + std::unique_ptr fThreadPool; + }; + +private: + std::atomic fPending; + SkExecutor& fExecutor; +}; + +#endif//SkTaskGroup_DEFINED diff --git a/gfx/skia/skia/src/core/SkTextBlob.cpp b/gfx/skia/skia/src/core/SkTextBlob.cpp new file mode 100644 index 0000000000..3c84bda6f8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTextBlob.cpp @@ -0,0 +1,947 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkRSXform.h" +#include "include/core/SkTextBlob.h" +#include "include/core/SkTypeface.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkStrikeSpec.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/core/SkWriteBuffer.h" + +#include +#include +#include + +#if SK_SUPPORT_GPU +#include "src/gpu/text/GrTextBlobCache.h" +#endif + +namespace { +struct RunFontStorageEquivalent { + SkScalar fSize, fScaleX; + void* fTypeface; + SkScalar fSkewX; + uint32_t fFlags; +}; +static_assert(sizeof(SkFont) == sizeof(RunFontStorageEquivalent), "runfont_should_stay_packed"); +} + +size_t SkTextBlob::RunRecord::StorageSize(uint32_t glyphCount, uint32_t textSize, + SkTextBlob::GlyphPositioning positioning, + SkSafeMath* safe) { + static_assert(SkIsAlign4(sizeof(SkScalar)), "SkScalar size alignment"); + + auto glyphSize = safe->mul(glyphCount, sizeof(uint16_t)), + posSize = safe->mul(PosCount(glyphCount, positioning, safe), sizeof(SkScalar)); + + // RunRecord object + (aligned) glyph buffer + position buffer + auto size = sizeof(SkTextBlob::RunRecord); + size = safe->add(size, safe->alignUp(glyphSize, 4)); + size = safe->add(size, posSize); + + if (textSize) { // Extended run. + size = safe->add(size, sizeof(uint32_t)); + size = safe->add(size, safe->mul(glyphCount, sizeof(uint32_t))); + size = safe->add(size, textSize); + } + + return safe->alignUp(size, sizeof(void*)); +} + +const SkTextBlob::RunRecord* SkTextBlob::RunRecord::First(const SkTextBlob* blob) { + // The first record (if present) is stored following the blob object. + // (aligned up to make the RunRecord aligned too) + return reinterpret_cast(SkAlignPtr((uintptr_t)(blob + 1))); +} + +const SkTextBlob::RunRecord* SkTextBlob::RunRecord::Next(const RunRecord* run) { + return SkToBool(run->fFlags & kLast_Flag) ? nullptr : NextUnchecked(run); +} + +namespace { +struct RunRecordStorageEquivalent { + SkFont fFont; + SkPoint fOffset; + uint32_t fCount; + uint32_t fFlags; + SkDEBUGCODE(unsigned fMagic;) +}; +} + +void SkTextBlob::RunRecord::validate(const uint8_t* storageTop) const { + SkASSERT(kRunRecordMagic == fMagic); + SkASSERT((uint8_t*)NextUnchecked(this) <= storageTop); + + SkASSERT(glyphBuffer() + fCount <= (uint16_t*)posBuffer()); + SkASSERT(posBuffer() + fCount * ScalarsPerGlyph(positioning()) + <= (SkScalar*)NextUnchecked(this)); + if (isExtended()) { + SkASSERT(textSize() > 0); + SkASSERT(textSizePtr() < (uint32_t*)NextUnchecked(this)); + SkASSERT(clusterBuffer() < (uint32_t*)NextUnchecked(this)); + SkASSERT(textBuffer() + textSize() <= (char*)NextUnchecked(this)); + } + static_assert(sizeof(SkTextBlob::RunRecord) == sizeof(RunRecordStorageEquivalent), + "runrecord_should_stay_packed"); +} + +const SkTextBlob::RunRecord* SkTextBlob::RunRecord::NextUnchecked(const RunRecord* run) { + SkSafeMath safe; + auto res = reinterpret_cast( + reinterpret_cast(run) + + StorageSize(run->glyphCount(), run->textSize(), run->positioning(), &safe)); + SkASSERT(safe); + return res; +} + +size_t SkTextBlob::RunRecord::PosCount(uint32_t glyphCount, + SkTextBlob::GlyphPositioning positioning, + SkSafeMath* safe) { + return safe->mul(glyphCount, ScalarsPerGlyph(positioning)); +} + +uint32_t* SkTextBlob::RunRecord::textSizePtr() const { + // textSize follows the position buffer. + SkASSERT(isExtended()); + SkSafeMath safe; + auto res = (uint32_t*)(&this->posBuffer()[PosCount(fCount, positioning(), &safe)]); + SkASSERT(safe); + return res; +} + +void SkTextBlob::RunRecord::grow(uint32_t count) { + SkScalar* initialPosBuffer = posBuffer(); + uint32_t initialCount = fCount; + fCount += count; + + // Move the initial pos scalars to their new location. + size_t copySize = initialCount * sizeof(SkScalar) * ScalarsPerGlyph(positioning()); + SkASSERT((uint8_t*)posBuffer() + copySize <= (uint8_t*)NextUnchecked(this)); + + // memmove, as the buffers may overlap + memmove(posBuffer(), initialPosBuffer, copySize); +} + +static int32_t next_id() { + static std::atomic nextID{1}; + int32_t id; + do { + id = nextID++; + } while (id == SK_InvalidGenID); + return id; +} + +SkTextBlob::SkTextBlob(const SkRect& bounds) + : fBounds(bounds) + , fUniqueID(next_id()) + , fCacheID(SK_InvalidUniqueID) {} + +SkTextBlob::~SkTextBlob() { +#if SK_SUPPORT_GPU + if (SK_InvalidUniqueID != fCacheID.load()) { + GrTextBlobCache::PostPurgeBlobMessage(fUniqueID, fCacheID); + } +#endif + + const auto* run = RunRecord::First(this); + do { + const auto* nextRun = RunRecord::Next(run); + SkDEBUGCODE(run->validate((uint8_t*)this + fStorageSize);) + run->~RunRecord(); + run = nextRun; + } while (run); +} + +namespace { + +union PositioningAndExtended { + int32_t intValue; + struct { + uint8_t positioning; + uint8_t extended; + uint16_t padding; + }; +}; + +static_assert(sizeof(PositioningAndExtended) == sizeof(int32_t), ""); + +} // namespace + +enum SkTextBlob::GlyphPositioning : uint8_t { + kDefault_Positioning = 0, // Default glyph advances -- zero scalars per glyph. + kHorizontal_Positioning = 1, // Horizontal positioning -- one scalar per glyph. + kFull_Positioning = 2, // Point positioning -- two scalars per glyph. + kRSXform_Positioning = 3, // RSXform positioning -- four scalars per glyph. +}; + +unsigned SkTextBlob::ScalarsPerGlyph(GlyphPositioning pos) { + const uint8_t gScalarsPerPositioning[] = { + 0, // kDefault_Positioning + 1, // kHorizontal_Positioning + 2, // kFull_Positioning + 4, // kRSXform_Positioning + }; + SkASSERT((unsigned)pos <= 3); + return gScalarsPerPositioning[pos]; +} + +void SkTextBlob::operator delete(void* p) { + sk_free(p); +} + +void* SkTextBlob::operator new(size_t) { + SK_ABORT("All blobs are created by placement new."); +} + +void* SkTextBlob::operator new(size_t, void* p) { + return p; +} + +SkTextBlobRunIterator::SkTextBlobRunIterator(const SkTextBlob* blob) + : fCurrentRun(SkTextBlob::RunRecord::First(blob)) { + SkDEBUGCODE(fStorageTop = (uint8_t*)blob + blob->fStorageSize;) +} + +void SkTextBlobRunIterator::next() { + SkASSERT(!this->done()); + + if (!this->done()) { + SkDEBUGCODE(fCurrentRun->validate(fStorageTop);) + fCurrentRun = SkTextBlob::RunRecord::Next(fCurrentRun); + } +} + +SkTextBlobRunIterator::GlyphPositioning SkTextBlobRunIterator::positioning() const { + SkASSERT(!this->done()); + static_assert(static_cast(SkTextBlob::kDefault_Positioning) == + kDefault_Positioning, ""); + static_assert(static_cast(SkTextBlob::kHorizontal_Positioning) == + kHorizontal_Positioning, ""); + static_assert(static_cast(SkTextBlob::kFull_Positioning) == + kFull_Positioning, ""); + static_assert(static_cast(SkTextBlob::kRSXform_Positioning) == + kRSXform_Positioning, ""); + + return SkTo(fCurrentRun->positioning()); +} + +bool SkTextBlobRunIterator::isLCD() const { + return fCurrentRun->font().getEdging() == SkFont::Edging::kSubpixelAntiAlias; +} + +SkTextBlobBuilder::SkTextBlobBuilder() + : fStorageSize(0) + , fStorageUsed(0) + , fRunCount(0) + , fDeferredBounds(false) + , fLastRun(0) { + fBounds.setEmpty(); +} + +SkTextBlobBuilder::~SkTextBlobBuilder() { + if (nullptr != fStorage.get()) { + // We are abandoning runs and must destruct the associated font data. + // The easiest way to accomplish that is to use the blob destructor. + this->make(); + } +} + +SkRect SkTextBlobBuilder::TightRunBounds(const SkTextBlob::RunRecord& run) { + const SkFont& font = run.font(); + SkRect bounds; + + if (SkTextBlob::kDefault_Positioning == run.positioning()) { + font.measureText(run.glyphBuffer(), run.glyphCount() * sizeof(uint16_t), + SkTextEncoding::kGlyphID, &bounds); + return bounds.makeOffset(run.offset().x(), run.offset().y()); + } + + SkAutoSTArray<16, SkRect> glyphBounds(run.glyphCount()); + font.getBounds(run.glyphBuffer(), run.glyphCount(), glyphBounds.get(), nullptr); + + SkASSERT(SkTextBlob::kFull_Positioning == run.positioning() || + SkTextBlob::kHorizontal_Positioning == run.positioning()); + // kFull_Positioning => [ x, y, x, y... ] + // kHorizontal_Positioning => [ x, x, x... ] + // (const y applied by runBounds.offset(run->offset()) later) + const SkScalar horizontalConstY = 0; + const SkScalar* glyphPosX = run.posBuffer(); + const SkScalar* glyphPosY = (run.positioning() == SkTextBlob::kFull_Positioning) ? + glyphPosX + 1 : &horizontalConstY; + const unsigned posXInc = SkTextBlob::ScalarsPerGlyph(run.positioning()); + const unsigned posYInc = (run.positioning() == SkTextBlob::kFull_Positioning) ? + posXInc : 0; + + bounds.setEmpty(); + for (unsigned i = 0; i < run.glyphCount(); ++i) { + bounds.join(glyphBounds[i].makeOffset(*glyphPosX, *glyphPosY)); + glyphPosX += posXInc; + glyphPosY += posYInc; + } + + SkASSERT((void*)glyphPosX <= SkTextBlob::RunRecord::Next(&run)); + + return bounds.makeOffset(run.offset().x(), run.offset().y()); +} + +static SkRect map_quad_to_rect(const SkRSXform& xform, const SkRect& rect) { + return SkMatrix().setRSXform(xform).mapRect(rect); +} + +SkRect SkTextBlobBuilder::ConservativeRunBounds(const SkTextBlob::RunRecord& run) { + SkASSERT(run.glyphCount() > 0); + SkASSERT(SkTextBlob::kFull_Positioning == run.positioning() || + SkTextBlob::kHorizontal_Positioning == run.positioning() || + SkTextBlob::kRSXform_Positioning == run.positioning()); + + const SkRect fontBounds = SkFontPriv::GetFontBounds(run.font()); + if (fontBounds.isEmpty()) { + // Empty font bounds are likely a font bug. TightBounds has a better chance of + // producing useful results in this case. + return TightRunBounds(run); + } + + // Compute the glyph position bbox. + SkRect bounds; + switch (run.positioning()) { + case SkTextBlob::kHorizontal_Positioning: { + const SkScalar* glyphPos = run.posBuffer(); + SkASSERT((void*)(glyphPos + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run)); + + SkScalar minX = *glyphPos; + SkScalar maxX = *glyphPos; + for (unsigned i = 1; i < run.glyphCount(); ++i) { + SkScalar x = glyphPos[i]; + minX = SkMinScalar(x, minX); + maxX = SkMaxScalar(x, maxX); + } + + bounds.setLTRB(minX, 0, maxX, 0); + } break; + case SkTextBlob::kFull_Positioning: { + const SkPoint* glyphPosPts = run.pointBuffer(); + SkASSERT((void*)(glyphPosPts + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run)); + + bounds.setBounds(glyphPosPts, run.glyphCount()); + } break; + case SkTextBlob::kRSXform_Positioning: { + const SkRSXform* xform = run.xformBuffer(); + SkASSERT((void*)(xform + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run)); + bounds = map_quad_to_rect(xform[0], fontBounds); + for (unsigned i = 1; i < run.glyphCount(); ++i) { + bounds.join(map_quad_to_rect(xform[i], fontBounds)); + } + } break; + default: + SK_ABORT("unsupported positioning mode"); + } + + if (run.positioning() != SkTextBlob::kRSXform_Positioning) { + // Expand by typeface glyph bounds. + bounds.fLeft += fontBounds.left(); + bounds.fTop += fontBounds.top(); + bounds.fRight += fontBounds.right(); + bounds.fBottom += fontBounds.bottom(); + } + + // Offset by run position. + return bounds.makeOffset(run.offset().x(), run.offset().y()); +} + +void SkTextBlobBuilder::updateDeferredBounds() { + SkASSERT(!fDeferredBounds || fRunCount > 0); + + if (!fDeferredBounds) { + return; + } + + SkASSERT(fLastRun >= SkAlignPtr(sizeof(SkTextBlob))); + SkTextBlob::RunRecord* run = reinterpret_cast(fStorage.get() + + fLastRun); + + // FIXME: we should also use conservative bounds for kDefault_Positioning. + SkRect runBounds = SkTextBlob::kDefault_Positioning == run->positioning() ? + TightRunBounds(*run) : ConservativeRunBounds(*run); + fBounds.join(runBounds); + fDeferredBounds = false; +} + +void SkTextBlobBuilder::reserve(size_t size) { + SkSafeMath safe; + + // We don't currently pre-allocate, but maybe someday... + if (safe.add(fStorageUsed, size) <= fStorageSize && safe) { + return; + } + + if (0 == fRunCount) { + SkASSERT(nullptr == fStorage.get()); + SkASSERT(0 == fStorageSize); + SkASSERT(0 == fStorageUsed); + + // the first allocation also includes blob storage + // aligned up to a pointer alignment so SkTextBlob::RunRecords after it stay aligned. + fStorageUsed = SkAlignPtr(sizeof(SkTextBlob)); + } + + fStorageSize = safe.add(fStorageUsed, size); + + // FYI: This relies on everything we store being relocatable, particularly SkPaint. + // Also, this is counting on the underlying realloc to throw when passed max(). + fStorage.realloc(safe ? fStorageSize : std::numeric_limits::max()); +} + +bool SkTextBlobBuilder::mergeRun(const SkFont& font, SkTextBlob::GlyphPositioning positioning, + uint32_t count, SkPoint offset) { + if (0 == fLastRun) { + SkASSERT(0 == fRunCount); + return false; + } + + SkASSERT(fLastRun >= SkAlignPtr(sizeof(SkTextBlob))); + SkTextBlob::RunRecord* run = reinterpret_cast(fStorage.get() + + fLastRun); + SkASSERT(run->glyphCount() > 0); + + if (run->textSize() != 0) { + return false; + } + + if (run->positioning() != positioning + || run->font() != font + || (run->glyphCount() + count < run->glyphCount())) { + return false; + } + + // we can merge same-font/same-positioning runs in the following cases: + // * fully positioned run following another fully positioned run + // * horizontally postioned run following another horizontally positioned run with the same + // y-offset + if (SkTextBlob::kFull_Positioning != positioning + && (SkTextBlob::kHorizontal_Positioning != positioning + || run->offset().y() != offset.y())) { + return false; + } + + SkSafeMath safe; + size_t sizeDelta = + SkTextBlob::RunRecord::StorageSize(run->glyphCount() + count, 0, positioning, &safe) - + SkTextBlob::RunRecord::StorageSize(run->glyphCount() , 0, positioning, &safe); + if (!safe) { + return false; + } + + this->reserve(sizeDelta); + + // reserve may have realloced + run = reinterpret_cast(fStorage.get() + fLastRun); + uint32_t preMergeCount = run->glyphCount(); + run->grow(count); + + // Callers expect the buffers to point at the newly added slice, ant not at the beginning. + fCurrentRunBuffer.glyphs = run->glyphBuffer() + preMergeCount; + fCurrentRunBuffer.pos = run->posBuffer() + + preMergeCount * SkTextBlob::ScalarsPerGlyph(positioning); + + fStorageUsed += sizeDelta; + + SkASSERT(fStorageUsed <= fStorageSize); + run->validate(fStorage.get() + fStorageUsed); + + return true; +} + +void SkTextBlobBuilder::allocInternal(const SkFont& font, + SkTextBlob::GlyphPositioning positioning, + int count, int textSize, SkPoint offset, + const SkRect* bounds) { + if (count <= 0 || textSize < 0) { + fCurrentRunBuffer = { nullptr, nullptr, nullptr, nullptr }; + return; + } + + if (textSize != 0 || !this->mergeRun(font, positioning, count, offset)) { + this->updateDeferredBounds(); + + SkSafeMath safe; + size_t runSize = SkTextBlob::RunRecord::StorageSize(count, textSize, positioning, &safe); + if (!safe) { + fCurrentRunBuffer = { nullptr, nullptr, nullptr, nullptr }; + return; + } + + this->reserve(runSize); + + SkASSERT(fStorageUsed >= SkAlignPtr(sizeof(SkTextBlob))); + SkASSERT(fStorageUsed + runSize <= fStorageSize); + + SkTextBlob::RunRecord* run = new (fStorage.get() + fStorageUsed) + SkTextBlob::RunRecord(count, textSize, offset, font, positioning); + fCurrentRunBuffer.glyphs = run->glyphBuffer(); + fCurrentRunBuffer.pos = run->posBuffer(); + fCurrentRunBuffer.utf8text = run->textBuffer(); + fCurrentRunBuffer.clusters = run->clusterBuffer(); + + fLastRun = fStorageUsed; + fStorageUsed += runSize; + fRunCount++; + + SkASSERT(fStorageUsed <= fStorageSize); + run->validate(fStorage.get() + fStorageUsed); + } + SkASSERT(textSize > 0 || nullptr == fCurrentRunBuffer.utf8text); + SkASSERT(textSize > 0 || nullptr == fCurrentRunBuffer.clusters); + if (!fDeferredBounds) { + if (bounds) { + fBounds.join(*bounds); + } else { + fDeferredBounds = true; + } + } +} + +// SkFont versions + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRun(const SkFont& font, int count, + SkScalar x, SkScalar y, + const SkRect* bounds) { + this->allocInternal(font, SkTextBlob::kDefault_Positioning, count, 0, {x, y}, bounds); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPosH(const SkFont& font, int count, + SkScalar y, + const SkRect* bounds) { + this->allocInternal(font, SkTextBlob::kHorizontal_Positioning, count, 0, {0, y}, bounds); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPos(const SkFont& font, int count, + const SkRect* bounds) { + this->allocInternal(font, SkTextBlob::kFull_Positioning, count, 0, {0, 0}, bounds); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& +SkTextBlobBuilder::allocRunRSXform(const SkFont& font, int count) { + this->allocInternal(font, SkTextBlob::kRSXform_Positioning, count, 0, {0, 0}, nullptr); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunText(const SkFont& font, int count, + SkScalar x, SkScalar y, + int textByteCount, + SkString lang, + const SkRect* bounds) { + this->allocInternal(font, + SkTextBlob::kDefault_Positioning, + count, + textByteCount, + SkPoint::Make(x, y), + bounds); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPosH(const SkFont& font, int count, + SkScalar y, + int textByteCount, + SkString lang, + const SkRect* bounds) { + this->allocInternal(font, + SkTextBlob::kHorizontal_Positioning, + count, + textByteCount, + SkPoint::Make(0, y), + bounds); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPos(const SkFont& font, int count, + int textByteCount, + SkString lang, + const SkRect *bounds) { + this->allocInternal(font, + SkTextBlob::kFull_Positioning, + count, textByteCount, + SkPoint::Make(0, 0), + bounds); + return fCurrentRunBuffer; +} + +const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunRSXform(const SkFont& font, int count, + int textByteCount, + SkString lang, + const SkRect* bounds) { + this->allocInternal(font, + SkTextBlob::kRSXform_Positioning, + count, + textByteCount, + {0, 0}, + bounds); + return fCurrentRunBuffer; +} + +sk_sp SkTextBlobBuilder::make() { + if (!fRunCount) { + // We don't instantiate empty blobs. + SkASSERT(!fStorage.get()); + SkASSERT(fStorageUsed == 0); + SkASSERT(fStorageSize == 0); + SkASSERT(fLastRun == 0); + SkASSERT(fBounds.isEmpty()); + return nullptr; + } + + this->updateDeferredBounds(); + + // Tag the last run as such. + auto* lastRun = reinterpret_cast(fStorage.get() + fLastRun); + lastRun->fFlags |= SkTextBlob::RunRecord::kLast_Flag; + + SkTextBlob* blob = new (fStorage.release()) SkTextBlob(fBounds); + SkDEBUGCODE(const_cast(blob)->fStorageSize = fStorageSize;) + + SkDEBUGCODE( + SkSafeMath safe; + size_t validateSize = SkAlignPtr(sizeof(SkTextBlob)); + for (const auto* run = SkTextBlob::RunRecord::First(blob); run; + run = SkTextBlob::RunRecord::Next(run)) { + validateSize += SkTextBlob::RunRecord::StorageSize( + run->fCount, run->textSize(), run->positioning(), &safe); + run->validate(reinterpret_cast(blob) + fStorageUsed); + fRunCount--; + } + SkASSERT(validateSize == fStorageUsed); + SkASSERT(fRunCount == 0); + SkASSERT(safe); + ) + + fStorageUsed = 0; + fStorageSize = 0; + fRunCount = 0; + fLastRun = 0; + fBounds.setEmpty(); + + return sk_sp(blob); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkTextBlobPriv::Flatten(const SkTextBlob& blob, SkWriteBuffer& buffer) { + // seems like we could skip this, and just recompute bounds in unflatten, but + // some cc_unittests fail if we remove this... + buffer.writeRect(blob.bounds()); + + SkTextBlobRunIterator it(&blob); + while (!it.done()) { + SkASSERT(it.glyphCount() > 0); + + buffer.write32(it.glyphCount()); + PositioningAndExtended pe; + pe.intValue = 0; + pe.positioning = it.positioning(); + SkASSERT((int32_t)it.positioning() == pe.intValue); // backwards compat. + + uint32_t textSize = it.textSize(); + pe.extended = textSize > 0; + buffer.write32(pe.intValue); + if (pe.extended) { + buffer.write32(textSize); + } + buffer.writePoint(it.offset()); + + SkFontPriv::Flatten(it.font(), buffer); + + buffer.writeByteArray(it.glyphs(), it.glyphCount() * sizeof(uint16_t)); + buffer.writeByteArray(it.pos(), + it.glyphCount() * sizeof(SkScalar) * + SkTextBlob::ScalarsPerGlyph( + SkTo(it.positioning()))); + if (pe.extended) { + buffer.writeByteArray(it.clusters(), sizeof(uint32_t) * it.glyphCount()); + buffer.writeByteArray(it.text(), it.textSize()); + } + + it.next(); + } + + // Marker for the last run (0 is not a valid glyph count). + buffer.write32(0); +} + +sk_sp SkTextBlobPriv::MakeFromBuffer(SkReadBuffer& reader) { + SkRect bounds; + reader.readRect(&bounds); + + SkTextBlobBuilder blobBuilder; + SkSafeMath safe; + for (;;) { + int glyphCount = reader.read32(); + if (glyphCount == 0) { + // End-of-runs marker. + break; + } + + PositioningAndExtended pe; + pe.intValue = reader.read32(); + const auto pos = SkTo(pe.positioning); + if (glyphCount <= 0 || pos > SkTextBlob::kRSXform_Positioning) { + return nullptr; + } + int textSize = pe.extended ? reader.read32() : 0; + if (textSize < 0) { + return nullptr; + } + + SkPoint offset; + reader.readPoint(&offset); + SkFont font; + if (reader.isVersionLT(SkPicturePriv::kSerializeFonts_Version)) { + SkPaint paint; + reader.readPaint(&paint, &font); + } else { + SkFontPriv::Unflatten(&font, reader); + } + + // Compute the expected size of the buffer and ensure we have enough to deserialize + // a run before allocating it. + const size_t glyphSize = safe.mul(glyphCount, sizeof(uint16_t)), + posSize = + safe.mul(glyphCount, safe.mul(sizeof(SkScalar), + SkTextBlob::ScalarsPerGlyph(pos))), + clusterSize = pe.extended ? safe.mul(glyphCount, sizeof(uint32_t)) : 0; + const size_t totalSize = + safe.add(safe.add(glyphSize, posSize), safe.add(clusterSize, textSize)); + + if (!reader.isValid() || !safe || totalSize > reader.available()) { + return nullptr; + } + + const SkTextBlobBuilder::RunBuffer* buf = nullptr; + switch (pos) { + case SkTextBlob::kDefault_Positioning: + buf = &blobBuilder.allocRunText(font, glyphCount, offset.x(), offset.y(), + textSize, SkString(), &bounds); + break; + case SkTextBlob::kHorizontal_Positioning: + buf = &blobBuilder.allocRunTextPosH(font, glyphCount, offset.y(), + textSize, SkString(), &bounds); + break; + case SkTextBlob::kFull_Positioning: + buf = &blobBuilder.allocRunTextPos(font, glyphCount, textSize, SkString(), &bounds); + break; + case SkTextBlob::kRSXform_Positioning: + buf = &blobBuilder.allocRunRSXform(font, glyphCount, textSize, SkString(), &bounds); + break; + } + + if (!buf->glyphs || + !buf->pos || + (pe.extended && (!buf->clusters || !buf->utf8text))) { + return nullptr; + } + + if (!reader.readByteArray(buf->glyphs, glyphSize) || + !reader.readByteArray(buf->pos, posSize)) { + return nullptr; + } + + if (pe.extended) { + if (!reader.readByteArray(buf->clusters, clusterSize) || + !reader.readByteArray(buf->utf8text, textSize)) { + return nullptr; + } + } + } + + return blobBuilder.make(); +} + +sk_sp SkTextBlob::MakeFromText(const void* text, size_t byteLength, const SkFont& font, + SkTextEncoding encoding) { + // Note: we deliberately promote this to fully positioned blobs, since we'd have to pay the + // same cost down stream (i.e. computing bounds), so its cheaper to pay the cost once now. + const int count = font.countText(text, byteLength, encoding); + SkTextBlobBuilder builder; + auto buffer = builder.allocRunPos(font, count); + font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count); + font.getPos(buffer.glyphs, count, buffer.points(), {0, 0}); + return builder.make(); +} + +sk_sp SkTextBlob::MakeFromPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkFont& font, + SkTextEncoding encoding) { + const int count = font.countText(text, byteLength, encoding); + SkTextBlobBuilder builder; + auto buffer = builder.allocRunPos(font, count); + font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count); + memcpy(buffer.points(), pos, count * sizeof(SkPoint)); + return builder.make(); +} + +sk_sp SkTextBlob::MakeFromPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkFont& font, SkTextEncoding encoding) { + const int count = font.countText(text, byteLength, encoding); + SkTextBlobBuilder builder; + auto buffer = builder.allocRunPosH(font, count, constY); + font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count); + memcpy(buffer.pos, xpos, count * sizeof(SkScalar)); + return builder.make(); +} + +sk_sp SkTextBlob::MakeFromRSXform(const void* text, size_t byteLength, + const SkRSXform xform[], const SkFont& font, + SkTextEncoding encoding) { + const int count = font.countText(text, byteLength, encoding); + SkTextBlobBuilder builder; + auto buffer = builder.allocRunRSXform(font, count); + font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count); + memcpy(buffer.xforms(), xform, count * sizeof(SkRSXform)); + return builder.make(); +} + +sk_sp SkTextBlob::serialize(const SkSerialProcs& procs) const { + SkBinaryWriteBuffer buffer; + buffer.setSerialProcs(procs); + SkTextBlobPriv::Flatten(*this, buffer); + + size_t total = buffer.bytesWritten(); + sk_sp data = SkData::MakeUninitialized(total); + buffer.writeToMemory(data->writable_data()); + return data; +} + +sk_sp SkTextBlob::Deserialize(const void* data, size_t length, + const SkDeserialProcs& procs) { + SkReadBuffer buffer(data, length); + buffer.setDeserialProcs(procs); + return SkTextBlobPriv::MakeFromBuffer(buffer); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +size_t SkTextBlob::serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const { + SkBinaryWriteBuffer buffer(memory, memory_size); + buffer.setSerialProcs(procs); + SkTextBlobPriv::Flatten(*this, buffer); + return buffer.usingInitialStorage() ? buffer.bytesWritten() : 0u; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace { +int get_glyph_run_intercepts(const SkGlyphRun& glyphRun, + const SkPaint& paint, + const SkScalar bounds[2], + SkScalar intervals[], + int* intervalCount) { + SkScalar scale = SK_Scalar1; + SkPaint interceptPaint{paint}; + SkFont interceptFont{glyphRun.font()}; + + interceptPaint.setMaskFilter(nullptr); // don't want this affecting our path-cache lookup + + // can't use our canonical size if we need to apply path effects + if (interceptPaint.getPathEffect() == nullptr) { + // If the wrong size is going to be used, don't hint anything. + interceptFont.setHinting(SkFontHinting::kNone); + interceptFont.setSubpixel(true); + scale = interceptFont.getSize() / SkFontPriv::kCanonicalTextSizeForPaths; + interceptFont.setSize(SkIntToScalar(SkFontPriv::kCanonicalTextSizeForPaths)); + // Note: fScale can be zero here (even if it wasn't before the divide). It can also + // be very very small. We call sk_ieee_float_divide below to ensure IEEE divide behavior, + // since downstream we will check for the resulting coordinates being non-finite anyway. + // Thus we don't need to check for zero here. + if (interceptPaint.getStrokeWidth() > 0 + && interceptPaint.getStyle() != SkPaint::kFill_Style) { + interceptPaint.setStrokeWidth( + sk_ieee_float_divide(interceptPaint.getStrokeWidth(), scale)); + } + } + + interceptPaint.setStyle(SkPaint::kFill_Style); + interceptPaint.setPathEffect(nullptr); + + SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(interceptFont, &interceptPaint); + auto cache = strikeSpec.findOrCreateExclusiveStrike(); + + SkScalar xOffset = 0; + SkScalar xPos = xOffset; + SkScalar prevAdvance = 0; + + const SkPoint* posCursor = glyphRun.positions().begin(); + for (auto glyphID : glyphRun.glyphsIDs()) { + SkPoint pos = *posCursor++; + + SkGlyph* glyph = cache->glyph(glyphID); + xPos += prevAdvance * scale; + prevAdvance = glyph->advanceX(); + if (cache->preparePath(glyph) != nullptr) { + // The typeface is scaled, so un-scale the bounds to be in the space of the typeface. + // Also ensure the bounds are properly offset by the vertical positioning of the glyph. + SkScalar scaledBounds[2] = { + (bounds[0] - pos.y()) / scale, + (bounds[1] - pos.y()) / scale + }; + cache->findIntercepts(scaledBounds, scale, pos.x(), glyph, intervals, intervalCount); + } + } + return *intervalCount; +} +} // namespace + +int SkTextBlob::getIntercepts(const SkScalar bounds[2], SkScalar intervals[], + const SkPaint* paint) const { + + SkTLazy defaultPaint; + if (paint == nullptr) { + defaultPaint.init(); + paint = defaultPaint.get(); + } + + SkGlyphRunBuilder builder; + builder.textBlobToGlyphRunListIgnoringRSXForm(*paint, *this, SkPoint{0, 0}); + auto glyphRunList = builder.useGlyphRunList(); + + int intervalCount = 0; + for (const SkGlyphRun& glyphRun : glyphRunList) { + intervalCount = get_glyph_run_intercepts(glyphRun, *paint, bounds, intervals, &intervalCount); + } + + return intervalCount; +} + +//////// + +SkTextBlob::Iter::Iter(const SkTextBlob& blob) { + fRunRecord = RunRecord::First(&blob); +} + +bool SkTextBlob::Iter::next(Run* rec) { + if (fRunRecord) { + if (rec) { + rec->fTypeface = fRunRecord->font().getTypeface(); + rec->fGlyphCount = fRunRecord->glyphCount(); + rec->fGlyphIndices = fRunRecord->glyphBuffer(); + } + if (fRunRecord->isLastRun()) { + fRunRecord = nullptr; + } else { + fRunRecord = RunRecord::Next(fRunRecord); + } + return true; + } + return false; +} diff --git a/gfx/skia/skia/src/core/SkTextBlobPriv.h b/gfx/skia/skia/src/core/SkTextBlobPriv.h new file mode 100644 index 0000000000..5394b50d5d --- /dev/null +++ b/gfx/skia/skia/src/core/SkTextBlobPriv.h @@ -0,0 +1,268 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTextBlobPriv_DEFINED +#define SkTextBlobPriv_DEFINED + +#include "include/core/SkColorFilter.h" +#include "include/core/SkFont.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkShader.h" +#include "include/core/SkTextBlob.h" +#include "include/core/SkTypeface.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkSafeMath.h" + +class SkReadBuffer; +class SkWriteBuffer; + +class SkTextBlobPriv { +public: + /** + * Serialize to a buffer. + */ + static void Flatten(const SkTextBlob& , SkWriteBuffer&); + + /** + * Recreate an SkTextBlob that was serialized into a buffer. + * + * @param SkReadBuffer Serialized blob data. + * @return A new SkTextBlob representing the serialized data, or NULL if the buffer is + * invalid. + */ + static sk_sp MakeFromBuffer(SkReadBuffer&); +}; + +class SkTextBlobBuilderPriv { +public: + static const SkTextBlobBuilder::RunBuffer& AllocRunText(SkTextBlobBuilder* builder, + const SkFont& font, int count, SkScalar x, SkScalar y, int textByteCount, + SkString lang, const SkRect* bounds = nullptr) { + return builder->allocRunText(font, count, x, y, textByteCount, lang, bounds); + } + static const SkTextBlobBuilder::RunBuffer& AllocRunTextPosH(SkTextBlobBuilder* builder, + const SkFont& font, int count, SkScalar y, int textByteCount, SkString lang, + const SkRect* bounds = nullptr) { + return builder->allocRunTextPosH(font, count, y, textByteCount, lang, bounds); + } + static const SkTextBlobBuilder::RunBuffer& AllocRunTextPos(SkTextBlobBuilder* builder, + const SkFont& font, int count, int textByteCount, SkString lang, + const SkRect* bounds = nullptr) { + return builder->allocRunTextPos(font, count, textByteCount, lang, bounds); + } +}; + +// +// Textblob data is laid out into externally-managed storage as follows: +// +// ----------------------------------------------------------------------------- +// | SkTextBlob | RunRecord | Glyphs[] | Pos[] | RunRecord | Glyphs[] | Pos[] | ... +// ----------------------------------------------------------------------------- +// +// Each run record describes a text blob run, and can be used to determine the (implicit) +// location of the following record. +// +// Extended Textblob runs have more data after the Pos[] array: +// +// ------------------------------------------------------------------------- +// ... | RunRecord | Glyphs[] | Pos[] | TextSize | Clusters[] | Text[] | ... +// ------------------------------------------------------------------------- +// +// To determine the length of the extended run data, the TextSize must be read. +// +// Extended Textblob runs may be mixed with non-extended runs. + +SkDEBUGCODE(static const unsigned kRunRecordMagic = 0xb10bcafe;) + +class SkTextBlob::RunRecord { +public: + RunRecord(uint32_t count, uint32_t textSize, const SkPoint& offset, const SkFont& font, GlyphPositioning pos) + : fFont(font) + , fCount(count) + , fOffset(offset) + , fFlags(pos) { + SkASSERT(static_cast(pos) <= Flags::kPositioning_Mask); + + SkDEBUGCODE(fMagic = kRunRecordMagic); + if (textSize > 0) { + fFlags |= kExtended_Flag; + *this->textSizePtr() = textSize; + } + } + + uint32_t glyphCount() const { + return fCount; + } + + const SkPoint& offset() const { + return fOffset; + } + + const SkFont& font() const { + return fFont; + } + + GlyphPositioning positioning() const { + return static_cast(fFlags & kPositioning_Mask); + } + + uint16_t* glyphBuffer() const { + static_assert(SkIsAlignPtr(sizeof(RunRecord)), ""); + // Glyphs are stored immediately following the record. + return reinterpret_cast(const_cast(this) + 1); + } + + // can be aliased with pointBuffer() or xformBuffer() + SkScalar* posBuffer() const { + // Position scalars follow the (aligned) glyph buffer. + return reinterpret_cast(reinterpret_cast(this->glyphBuffer()) + + SkAlign4(fCount * sizeof(uint16_t))); + } + + // alias for posBuffer() + SkPoint* pointBuffer() const { + SkASSERT(this->positioning() == (GlyphPositioning)2); + return reinterpret_cast(this->posBuffer()); + } + + // alias for posBuffer() + SkRSXform* xformBuffer() const { + SkASSERT(this->positioning() == (GlyphPositioning)3); + return reinterpret_cast(this->posBuffer()); + } + + uint32_t textSize() const { return isExtended() ? *this->textSizePtr() : 0; } + + uint32_t* clusterBuffer() const { + // clusters follow the textSize. + return isExtended() ? 1 + this->textSizePtr() : nullptr; + } + + char* textBuffer() const { + return isExtended() + ? reinterpret_cast(this->clusterBuffer() + fCount) + : nullptr; + } + + bool isLastRun() const { return SkToBool(fFlags & kLast_Flag); } + + static size_t StorageSize(uint32_t glyphCount, uint32_t textSize, + SkTextBlob::GlyphPositioning positioning, + SkSafeMath* safe); + + static const RunRecord* First(const SkTextBlob* blob); + + static const RunRecord* Next(const RunRecord* run); + + void validate(const uint8_t* storageTop) const; + +private: + friend class SkTextBlobBuilder; + + enum Flags { + kPositioning_Mask = 0x03, // bits 0-1 reserved for positioning + kLast_Flag = 0x04, // set for the last blob run + kExtended_Flag = 0x08, // set for runs with text/cluster info + }; + + static const RunRecord* NextUnchecked(const RunRecord* run); + + static size_t PosCount(uint32_t glyphCount, + SkTextBlob::GlyphPositioning positioning, + SkSafeMath* safe); + + uint32_t* textSizePtr() const; + + void grow(uint32_t count); + + bool isExtended() const { + return fFlags & kExtended_Flag; + } + + SkFont fFont; + uint32_t fCount; + SkPoint fOffset; + uint32_t fFlags; + + SkDEBUGCODE(unsigned fMagic;) +}; + +/** + * Iterate through all of the text runs of the text blob. For example: + * for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) { + * ..... + * } + */ +class SkTextBlobRunIterator { +public: + SkTextBlobRunIterator(const SkTextBlob* blob); + + enum GlyphPositioning : uint8_t { + kDefault_Positioning = 0, // Default glyph advances -- zero scalars per glyph. + kHorizontal_Positioning = 1, // Horizontal positioning -- one scalar per glyph. + kFull_Positioning = 2, // Point positioning -- two scalars per glyph. + kRSXform_Positioning = 3, // RSXform positioning -- four scalars per glyph. + }; + + bool done() const { + return !fCurrentRun; + } + void next(); + + uint32_t glyphCount() const { + SkASSERT(!this->done()); + return fCurrentRun->glyphCount(); + } + const uint16_t* glyphs() const { + SkASSERT(!this->done()); + return fCurrentRun->glyphBuffer(); + } + const SkScalar* pos() const { + SkASSERT(!this->done()); + return fCurrentRun->posBuffer(); + } + // alias for pos() + const SkPoint* points() const { + return fCurrentRun->pointBuffer(); + } + // alias for pos() + const SkRSXform* xforms() const { + return fCurrentRun->xformBuffer(); + } + const SkPoint& offset() const { + SkASSERT(!this->done()); + return fCurrentRun->offset(); + } + const SkFont& font() const { + SkASSERT(!this->done()); + return fCurrentRun->font(); + } + GlyphPositioning positioning() const; + uint32_t* clusters() const { + SkASSERT(!this->done()); + return fCurrentRun->clusterBuffer(); + } + uint32_t textSize() const { + SkASSERT(!this->done()); + return fCurrentRun->textSize(); + } + char* text() const { + SkASSERT(!this->done()); + return fCurrentRun->textBuffer(); + } + + bool isLCD() const; + +private: + const SkTextBlob::RunRecord* fCurrentRun; + + SkDEBUGCODE(uint8_t* fStorageTop;) +}; + +#endif // SkTextBlobPriv_DEFINED diff --git a/gfx/skia/skia/src/core/SkTextFormatParams.h b/gfx/skia/skia/src/core/SkTextFormatParams.h new file mode 100644 index 0000000000..a4f1c0eb78 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTextFormatParams.h @@ -0,0 +1,38 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkTextFormatParams_DEFINES +#define SkTextFormatParams_DEFINES + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" + +// Fraction of the text size to lower a strike through line below the baseline. +#define kStdStrikeThru_Offset (-SK_Scalar1 * 6 / 21) +// Fraction of the text size to lower a underline below the baseline. +#define kStdUnderline_Offset (SK_Scalar1 / 9) +// Fraction of the text size to use for a strike through or under-line. +#define kStdUnderline_Thickness (SK_Scalar1 / 18) + +// The fraction of text size to embolden fake bold text scales with text size. +// At 9 points or below, the stroke width is increased by text size / 24. +// At 36 points and above, it is increased by text size / 32. In between, +// it is interpolated between those values. +static const SkScalar kStdFakeBoldInterpKeys[] = { + SK_Scalar1*9, + SK_Scalar1*36, +}; +static const SkScalar kStdFakeBoldInterpValues[] = { + SK_Scalar1/24, + SK_Scalar1/32, +}; +static_assert(SK_ARRAY_COUNT(kStdFakeBoldInterpKeys) == SK_ARRAY_COUNT(kStdFakeBoldInterpValues), + "mismatched_array_size"); +static const int kStdFakeBoldInterpLength = SK_ARRAY_COUNT(kStdFakeBoldInterpKeys); + +#endif //SkTextFormatParams_DEFINES diff --git a/gfx/skia/skia/src/core/SkThreadID.cpp b/gfx/skia/skia/src/core/SkThreadID.cpp new file mode 100644 index 0000000000..e882834e72 --- /dev/null +++ b/gfx/skia/skia/src/core/SkThreadID.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkThreadID.h" + +#ifdef SK_BUILD_FOR_WIN + #include "src/core/SkLeanWindows.h" + SkThreadID SkGetThreadID() { return GetCurrentThreadId(); } +#else + #include + SkThreadID SkGetThreadID() { return (int64_t)pthread_self(); } +#endif diff --git a/gfx/skia/skia/src/core/SkTime.cpp b/gfx/skia/skia/src/core/SkTime.cpp new file mode 100644 index 0000000000..47151f91ec --- /dev/null +++ b/gfx/skia/skia/src/core/SkTime.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTime.h" + +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTo.h" +#include "src/core/SkLeanWindows.h" + +void SkTime::DateTime::toISO8601(SkString* dst) const { + if (dst) { + int timeZoneMinutes = SkToInt(fTimeZoneMinutes); + char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; + int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; + timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; + dst->printf("%04u-%02u-%02uT%02u:%02u:%02u%c%02d:%02d", + static_cast(fYear), static_cast(fMonth), + static_cast(fDay), static_cast(fHour), + static_cast(fMinute), + static_cast(fSecond), timezoneSign, timeZoneHours, + timeZoneMinutes); + } +} + +#ifdef SK_BUILD_FOR_WIN + +void SkTime::GetDateTime(DateTime* dt) { + if (dt) { + SYSTEMTIME st; + GetSystemTime(&st); + dt->fTimeZoneMinutes = 0; + dt->fYear = st.wYear; + dt->fMonth = SkToU8(st.wMonth); + dt->fDayOfWeek = SkToU8(st.wDayOfWeek); + dt->fDay = SkToU8(st.wDay); + dt->fHour = SkToU8(st.wHour); + dt->fMinute = SkToU8(st.wMinute); + dt->fSecond = SkToU8(st.wSecond); + } +} + +#else // SK_BUILD_FOR_WIN + +#include +void SkTime::GetDateTime(DateTime* dt) { + if (dt) { + time_t m_time; + time(&m_time); + struct tm tstruct; + gmtime_r(&m_time, &tstruct); + dt->fTimeZoneMinutes = 0; + dt->fYear = tstruct.tm_year + 1900; + dt->fMonth = SkToU8(tstruct.tm_mon + 1); + dt->fDayOfWeek = SkToU8(tstruct.tm_wday); + dt->fDay = SkToU8(tstruct.tm_mday); + dt->fHour = SkToU8(tstruct.tm_hour); + dt->fMinute = SkToU8(tstruct.tm_min); + dt->fSecond = SkToU8(tstruct.tm_sec); + } +} +#endif // SK_BUILD_FOR_WIN + +#if !defined(__has_feature) + #define __has_feature(x) 0 +#endif + +#if __has_feature(memory_sanitizer) || defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_ANDROID) +#include +double SkTime::GetNSecs() { + // See skia:6504 + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec * 1e9 + tp.tv_nsec; +} +#else +#include +double SkTime::GetNSecs() { + auto now = std::chrono::steady_clock::now(); + std::chrono::duration ns = now.time_since_epoch(); + return ns.count(); +} +#endif diff --git a/gfx/skia/skia/src/core/SkTraceEvent.h b/gfx/skia/skia/src/core/SkTraceEvent.h new file mode 100644 index 0000000000..b4548ddf01 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTraceEvent.h @@ -0,0 +1,366 @@ +// Copyright (c) 2014 Google Inc. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header file defines implementation details of how the trace macros in +// SkTraceEventCommon.h collect and store trace events. Anything not +// implementation-specific should go in SkTraceEventCommon.h instead of here. + +#ifndef SkTraceEvent_DEFINED +#define SkTraceEvent_DEFINED + +#include "include/utils/SkEventTracer.h" +#include "src/core/SkTraceEventCommon.h" +#include + +//////////////////////////////////////////////////////////////////////////////// +// Implementation specific tracing API definitions. + +// Makes it easier to add traces with a simple TRACE_EVENT0("skia", TRACE_FUNC). +#if defined(_MSC_VER) + #define TRACE_FUNC __FUNCSIG__ +#else + #define TRACE_FUNC __PRETTY_FUNCTION__ +#endif + +// By default, const char* argument values are assumed to have long-lived scope +// and will not be copied. Use this macro to force a const char* to be copied. +#define TRACE_STR_COPY(str) \ + skia::tracing_internals::TraceStringWithCopy(str) + + +#define INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE() \ + *INTERNAL_TRACE_EVENT_UID(category_group_enabled) & \ + (SkEventTracer::kEnabledForRecording_CategoryGroupEnabledFlags | \ + SkEventTracer::kEnabledForEventCallback_CategoryGroupEnabledFlags) + +// Get a pointer to the enabled state of the given trace category. Only +// long-lived literal strings should be given as the category group. The +// returned pointer can be held permanently in a local static for example. If +// the unsigned char is non-zero, tracing is enabled. If tracing is enabled, +// TRACE_EVENT_API_ADD_TRACE_EVENT can be called. It's OK if tracing is disabled +// between the load of the tracing state and the call to +// TRACE_EVENT_API_ADD_TRACE_EVENT, because this flag only provides an early out +// for best performance when tracing is disabled. +// const uint8_t* +// TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(const char* category_group) +#define TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED \ + SkEventTracer::GetInstance()->getCategoryGroupEnabled + +// Add a trace event to the platform tracing system. +// SkEventTracer::Handle TRACE_EVENT_API_ADD_TRACE_EVENT( +// char phase, +// const uint8_t* category_group_enabled, +// const char* name, +// uint64_t id, +// int num_args, +// const char** arg_names, +// const uint8_t* arg_types, +// const uint64_t* arg_values, +// unsigned char flags) +#define TRACE_EVENT_API_ADD_TRACE_EVENT \ + SkEventTracer::GetInstance()->addTraceEvent + +// Set the duration field of a COMPLETE trace event. +// void TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( +// const uint8_t* category_group_enabled, +// const char* name, +// SkEventTracer::Handle id) +#define TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION \ + SkEventTracer::GetInstance()->updateTraceEventDuration + +// Defines visibility for classes in trace_event.h +#define TRACE_EVENT_API_CLASS_EXPORT SK_API + +// We prepend this string to all category names, so that ALL Skia trace events are +// disabled by default when tracing in Chrome. +#define TRACE_CATEGORY_PREFIX "disabled-by-default-" + +//////////////////////////////////////////////////////////////////////////////// + +// Implementation detail: trace event macros create temporary variables +// to keep instrumentation overhead low. These macros give each temporary +// variable a unique name based on the line number to prevent name collisions. +#define INTERNAL_TRACE_EVENT_UID3(a,b) \ + trace_event_unique_##a##b +#define INTERNAL_TRACE_EVENT_UID2(a,b) \ + INTERNAL_TRACE_EVENT_UID3(a,b) +#define INTERNAL_TRACE_EVENT_UID(name_prefix) \ + INTERNAL_TRACE_EVENT_UID2(name_prefix, __LINE__) + +// Implementation detail: internal macro to create static category. +// No barriers are needed, because this code is designed to operate safely +// even when the unsigned char* points to garbage data (which may be the case +// on processors without cache coherency). +#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ + category_group, atomic, category_group_enabled) \ + category_group_enabled = \ + reinterpret_cast(atomic.load(std::memory_order_relaxed)); \ + if (!category_group_enabled) { \ + category_group_enabled = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_group); \ + atomic.store(reinterpret_cast(category_group_enabled), \ + std::memory_order_relaxed); \ + } + +#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \ + static std::atomic INTERNAL_TRACE_EVENT_UID(atomic){0}; \ + const uint8_t* INTERNAL_TRACE_EVENT_UID(category_group_enabled); \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ + TRACE_CATEGORY_PREFIX category_group, \ + INTERNAL_TRACE_EVENT_UID(atomic), \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled)); + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD(phase, category_group, name, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + skia::tracing_internals::AddTraceEvent( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + skia::tracing_internals::kNoEventId, flags, ##__VA_ARGS__); \ + } \ + } while (0) + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category_group, name, id, \ + flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + unsigned char trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ + skia::tracing_internals::TraceID trace_event_trace_id( \ + id, &trace_event_flags); \ + skia::tracing_internals::AddTraceEvent( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ + name, trace_event_trace_id.data(), trace_event_flags, \ + ##__VA_ARGS__); \ + } \ + } while (0) + +// Implementation detail: internal macro to create static category and add begin +// event if the category is enabled. Also adds the end event when the scope +// ends. +#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, ...) \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + skia::tracing_internals::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer); \ + do { \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + SkEventTracer::Handle h = skia::tracing_internals::AddTraceEvent( \ + TRACE_EVENT_PHASE_COMPLETE, \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ + name, skia::tracing_internals::kNoEventId, \ + TRACE_EVENT_FLAG_NONE, ##__VA_ARGS__); \ + INTERNAL_TRACE_EVENT_UID(tracer).Initialize( \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, h); \ + } \ + } while (0) + +namespace skia { +namespace tracing_internals { + +// Specify these values when the corresponding argument of AddTraceEvent is not +// used. +const int kZeroNumArgs = 0; +const uint64_t kNoEventId = 0; + +// TraceID encapsulates an ID that can either be an integer or pointer. Pointers +// are by default mangled with the Process ID so that they are unlikely to +// collide when the same pointer is used on different processes. +class TraceID { +public: + TraceID(const void* id, unsigned char* flags) + : data_(static_cast(reinterpret_cast(id))) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + TraceID(uint64_t id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned int id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned short id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(unsigned char id, unsigned char* flags) + : data_(id) { (void)flags; } + TraceID(long long id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(long id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(int id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(short id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + TraceID(signed char id, unsigned char* flags) + : data_(static_cast(id)) { (void)flags; } + + uint64_t data() const { return data_; } + +private: + uint64_t data_; +}; + +// Simple union to store various types as uint64_t. +union TraceValueUnion { + bool as_bool; + uint64_t as_uint; + long long as_int; + double as_double; + const void* as_pointer; + const char* as_string; +}; + +// Simple container for const char* that should be copied instead of retained. +class TraceStringWithCopy { + public: + explicit TraceStringWithCopy(const char* str) : str_(str) {} + operator const char* () const { return str_; } + private: + const char* str_; +}; + +// Define SetTraceValue for each allowed type. It stores the type and +// value in the return arguments. This allows this API to avoid declaring any +// structures so that it is portable to third_party libraries. +#define INTERNAL_DECLARE_SET_TRACE_VALUE(actual_type, \ + union_member, \ + value_type_id) \ + static inline void SetTraceValue( \ + actual_type arg, \ + unsigned char* type, \ + uint64_t* value) { \ + TraceValueUnion type_value; \ + type_value.union_member = arg; \ + *type = value_type_id; \ + *value = type_value.as_uint; \ + } +// Simpler form for int types that can be safely casted. +#define INTERNAL_DECLARE_SET_TRACE_VALUE_INT(actual_type, \ + value_type_id) \ + static inline void SetTraceValue( \ + actual_type arg, \ + unsigned char* type, \ + uint64_t* value) { \ + *type = value_type_id; \ + *value = static_cast(arg); \ + } + +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(uint64_t, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned int, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned short, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned char, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(long long, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(long, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(short, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(signed char, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE(bool, as_bool, TRACE_VALUE_TYPE_BOOL) +INTERNAL_DECLARE_SET_TRACE_VALUE(double, as_double, TRACE_VALUE_TYPE_DOUBLE) +INTERNAL_DECLARE_SET_TRACE_VALUE(const void*, as_pointer, TRACE_VALUE_TYPE_POINTER) +INTERNAL_DECLARE_SET_TRACE_VALUE(const char*, as_string, TRACE_VALUE_TYPE_STRING) +INTERNAL_DECLARE_SET_TRACE_VALUE(const TraceStringWithCopy&, as_string, + TRACE_VALUE_TYPE_COPY_STRING) + +#undef INTERNAL_DECLARE_SET_TRACE_VALUE +#undef INTERNAL_DECLARE_SET_TRACE_VALUE_INT + +// These AddTraceEvent and AddTraceEvent template +// functions are defined here instead of in the macro, because the arg_values +// could be temporary objects, such as std::string. In order to store +// pointers to the internal c_str and pass through to the tracing API, +// the arg_values must live throughout these procedures. + +static inline SkEventTracer::Handle +AddTraceEvent( + char phase, + const uint8_t* category_group_enabled, + const char* name, + uint64_t id, + unsigned char flags) { + return TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_group_enabled, name, id, + kZeroNumArgs, nullptr, nullptr, nullptr, flags); +} + +template +static inline SkEventTracer::Handle +AddTraceEvent( + char phase, + const uint8_t* category_group_enabled, + const char* name, + uint64_t id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val) { + const int num_args = 1; + uint8_t arg_types[1]; + uint64_t arg_values[1]; + SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + return TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_group_enabled, name, id, + num_args, &arg1_name, arg_types, arg_values, flags); +} + +template +static inline SkEventTracer::Handle +AddTraceEvent( + char phase, + const uint8_t* category_group_enabled, + const char* name, + uint64_t id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val, + const char* arg2_name, + const ARG2_TYPE& arg2_val) { + const int num_args = 2; + const char* arg_names[2] = { arg1_name, arg2_name }; + unsigned char arg_types[2]; + uint64_t arg_values[2]; + SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); + return TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_group_enabled, name, id, + num_args, arg_names, arg_types, arg_values, flags); +} + +// Used by TRACE_EVENTx macros. Do not use directly. +class TRACE_EVENT_API_CLASS_EXPORT ScopedTracer { + public: + // Note: members of data_ intentionally left uninitialized. See Initialize. + ScopedTracer() : p_data_(nullptr) {} + + ~ScopedTracer() { + if (p_data_ && *data_.category_group_enabled) + TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( + data_.category_group_enabled, data_.name, data_.event_handle); + } + + void Initialize(const uint8_t* category_group_enabled, + const char* name, + SkEventTracer::Handle event_handle) { + data_.category_group_enabled = category_group_enabled; + data_.name = name; + data_.event_handle = event_handle; + p_data_ = &data_; + } + + private: + // This Data struct workaround is to avoid initializing all the members + // in Data during construction of this object, since this object is always + // constructed, even when tracing is disabled. If the members of Data were + // members of this class instead, compiler warnings occur about potential + // uninitialized accesses. + struct Data { + const uint8_t* category_group_enabled; + const char* name; + SkEventTracer::Handle event_handle; + }; + Data* p_data_; + Data data_; +}; + +} // namespace tracing_internals +} // namespace skia + +#endif diff --git a/gfx/skia/skia/src/core/SkTraceEventCommon.h b/gfx/skia/skia/src/core/SkTraceEventCommon.h new file mode 100644 index 0000000000..2bf5c826d3 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTraceEventCommon.h @@ -0,0 +1,291 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef SkTraceEventCommon_DEFINED +#define SkTraceEventCommon_DEFINED + +#include "include/core/SkTypes.h" +#include "include/utils/SkTraceEventPhase.h" + +// Trace events are for tracking application performance and resource usage. +// Macros are provided to track: +// Duration of scoped regions +// Instantaneous events +// Counters +// +// The first two arguments to all TRACE macros are the category and name. Both are strings, and +// must have application lifetime (statics or literals). The same applies to arg_names, and string +// argument values. However, you can force a copy of a string argument value with TRACE_STR_COPY: +// TRACE_EVENT1("category", "name", "arg1", "literal string is only referenced"); +// TRACE_EVENT1("category", "name", "arg1", TRACE_STR_COPY("string will be copied")); +// +// +// Categories are used to group events, and +// can be enabled or disabled by the tracing framework. The trace system will automatically add the +// process id, thread id, and microsecond timestamp to all events. +// +// +// The TRACE_EVENT[0-2] macros trace the duration of entire scopes: +// void doSomethingCostly() { +// TRACE_EVENT0("MY_SUBSYSTEM", "doSomethingCostly"); +// ... +// } +// +// Additional parameters can be associated with an event: +// void doSomethingCostly2(int howMuch) { +// TRACE_EVENT1("MY_SUBSYSTEM", "doSomethingCostly", "howMuch", howMuch); +// ... +// } +// +// +// Trace event also supports counters, which is a way to track a quantity as it varies over time. +// Counters are created with the following macro: +// TRACE_COUNTER1("MY_SUBSYSTEM", "myCounter", g_myCounterValue); +// +// Counters are process-specific. The macro itself can be issued from any thread, however. +// +// Sometimes, you want to track two counters at once. You can do this with two counter macros: +// TRACE_COUNTER1("MY_SUBSYSTEM", "myCounter0", g_myCounterValue[0]); +// TRACE_COUNTER1("MY_SUBSYSTEM", "myCounter1", g_myCounterValue[1]); +// Or you can do it with a combined macro: +// TRACE_COUNTER2("MY_SUBSYSTEM", "myCounter", +// "bytesPinned", g_myCounterValue[0], +// "bytesAllocated", g_myCounterValue[1]); +// The tracing UI will show these counters in a single graph, as a summed area chart. + +#if defined(TRACE_EVENT0) +#error "Another copy of this file has already been included." +#endif + +#define TRACE_EMPTY do {} while (0) + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + +#include +#include + +class SkAndroidFrameworkTraceUtil { +public: + SkAndroidFrameworkTraceUtil(const char* name) { + if (CC_UNLIKELY(gEnableAndroidTracing)) { + ATRACE_BEGIN(name); + } + } + SkAndroidFrameworkTraceUtil(bool, const char* fmt, ...) { + if (CC_LIKELY((!gEnableAndroidTracing) || (!ATRACE_ENABLED()))) return; + + const int BUFFER_SIZE = 256; + va_list ap; + char buf[BUFFER_SIZE]; + + va_start(ap, fmt); + vsnprintf(buf, BUFFER_SIZE, fmt, ap); + va_end(ap); + + ATRACE_BEGIN(buf); + } + ~SkAndroidFrameworkTraceUtil() { + if (CC_UNLIKELY(gEnableAndroidTracing)) { + ATRACE_END(); + } + } + + static void setEnableTracing(bool enableAndroidTracing) { + gEnableAndroidTracing = enableAndroidTracing; + } + + static bool getEnableTracing() { + return gEnableAndroidTracing; + } + +private: + static bool gEnableAndroidTracing; +}; + +#define ATRACE_ANDROID_FRAMEWORK(fmt, ...) SkAndroidFrameworkTraceUtil __trace(true, fmt, ##__VA_ARGS__) + +// Records a pair of begin and end events called "name" for the current scope, with 0, 1 or 2 +// associated arguments. In the framework, the arguments are ignored. +#define TRACE_EVENT0(category_group, name) \ + SkAndroidFrameworkTraceUtil __trace(name) +#define TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \ + SkAndroidFrameworkTraceUtil __trace(name) +#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) \ + SkAndroidFrameworkTraceUtil __trace(name) + +// Records a single event called "name" immediately, with 0, 1 or 2 associated arguments. If the +// category is not enabled, then this does nothing. +#define TRACE_EVENT_INSTANT0(category_group, name, scope) \ + do { SkAndroidFrameworkTraceUtil __trace(name); } while(0) + +#define TRACE_EVENT_INSTANT1(category_group, name, scope, arg1_name, arg1_val) \ + do { SkAndroidFrameworkTraceUtil __trace(name); } while(0) + +#define TRACE_EVENT_INSTANT2(category_group, name, scope, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + do { SkAndroidFrameworkTraceUtil __trace(name); } while(0) + +// Records the value of a counter called "name" immediately. Value +// must be representable as a 32 bit integer. +#define TRACE_COUNTER1(category_group, name, value) \ + if (CC_UNLIKELY(SkAndroidFrameworkTraceUtil::getEnableTracing())) { \ + ATRACE_INT(name, value); \ + } + +// Records the values of a multi-parted counter called "name" immediately. +// In Chrome, this macro produces a stacked bar chart. ATrace doesn't support +// that, so this just produces two separate counters. +#define TRACE_COUNTER2(category_group, name, value1_name, value1_val, value2_name, value2_val) \ + do { \ + if (CC_UNLIKELY(SkAndroidFrameworkTraceUtil::getEnableTracing())) { \ + ATRACE_INT(name "-" value1_name, value1_val); \ + ATRACE_INT(name "-" value2_name, value2_val); \ + } \ + } while (0) + +// ATrace has no object tracking +#define TRACE_EVENT_OBJECT_CREATED_WITH_ID(category_group, name, id) TRACE_EMPTY +#define TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(category_group, name, id, snapshot) TRACE_EMPTY +#define TRACE_EVENT_OBJECT_DELETED_WITH_ID(category_group, name, id) TRACE_EMPTY + +// Macro to efficiently determine if a given category group is enabled. +// This is only used for some shader text logging that isn't supported in ATrace anyway. +#define TRACE_EVENT_CATEGORY_GROUP_ENABLED(category_group, ret) \ + do { *ret = false; } while (0) + +#else // !SK_BUILD_FOR_ANDROID_FRAMEWORK + +#define ATRACE_ANDROID_FRAMEWORK(fmt, ...) TRACE_EMPTY + +// Records a pair of begin and end events called "name" for the current scope, with 0, 1 or 2 +// associated arguments. If the category is not enabled, then this does nothing. +#define TRACE_EVENT0(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name) + +#define TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, arg1_name, arg1_val) + +#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) + +// Records a single event called "name" immediately, with 0, 1 or 2 associated arguments. If the +// category is not enabled, then this does nothing. +#define TRACE_EVENT_INSTANT0(category_group, name, scope) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, category_group, name, \ + TRACE_EVENT_FLAG_NONE | scope) + +#define TRACE_EVENT_INSTANT1(category_group, name, scope, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, category_group, name, \ + TRACE_EVENT_FLAG_NONE | scope, arg1_name, arg1_val) + +#define TRACE_EVENT_INSTANT2(category_group, name, scope, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, category_group, name, \ + TRACE_EVENT_FLAG_NONE | scope, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Records the value of a counter called "name" immediately. Value +// must be representable as a 32 bit integer. +#define TRACE_COUNTER1(category_group, name, value) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, category_group, name, \ + TRACE_EVENT_FLAG_NONE, "value", \ + static_cast(value)) + +// Records the values of a multi-parted counter called "name" immediately. +// The UI will treat value1 and value2 as parts of a whole, displaying their +// values as a stacked-bar chart. +#define TRACE_COUNTER2(category_group, name, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, category_group, name, \ + TRACE_EVENT_FLAG_NONE, value1_name, \ + static_cast(value1_val), value2_name, \ + static_cast(value2_val)) + +#define TRACE_EVENT_ASYNC_BEGIN0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID( \ + TRACE_EVENT_PHASE_ASYNC_BEGIN, category, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_ASYNC_BEGIN1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_ASYNC_BEGIN2(category, name, id, arg1_name, arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, arg2_name, arg2_val) + +#define TRACE_EVENT_ASYNC_END0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_ASYNC_END1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_ASYNC_END2(category, name, id, arg1_name, arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, arg2_name, arg2_val) + +// Macros to track the life time and value of arbitrary client objects. +#define TRACE_EVENT_OBJECT_CREATED_WITH_ID(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID( \ + TRACE_EVENT_PHASE_CREATE_OBJECT, category_group, name, id, \ + TRACE_EVENT_FLAG_NONE) + +#define TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(category_group, name, id, \ + snapshot) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID( \ + TRACE_EVENT_PHASE_SNAPSHOT_OBJECT, category_group, name, \ + id, TRACE_EVENT_FLAG_NONE, "snapshot", snapshot) + +#define TRACE_EVENT_OBJECT_DELETED_WITH_ID(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID( \ + TRACE_EVENT_PHASE_DELETE_OBJECT, category_group, name, id, \ + TRACE_EVENT_FLAG_NONE) + +// Macro to efficiently determine if a given category group is enabled. +#define TRACE_EVENT_CATEGORY_GROUP_ENABLED(category_group, ret) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + *ret = true; \ + } else { \ + *ret = false; \ + } \ + } while (0) + +#endif + +// Flags for changing the behavior of TRACE_EVENT_API_ADD_TRACE_EVENT. +#define TRACE_EVENT_FLAG_NONE (static_cast(0)) +#define TRACE_EVENT_FLAG_COPY (static_cast(1 << 0)) +#define TRACE_EVENT_FLAG_HAS_ID (static_cast(1 << 1)) +#define TRACE_EVENT_FLAG_MANGLE_ID (static_cast(1 << 2)) +#define TRACE_EVENT_FLAG_SCOPE_OFFSET (static_cast(1 << 3)) +#define TRACE_EVENT_FLAG_SCOPE_EXTRA (static_cast(1 << 4)) +#define TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP (static_cast(1 << 5)) +#define TRACE_EVENT_FLAG_ASYNC_TTS (static_cast(1 << 6)) +#define TRACE_EVENT_FLAG_BIND_TO_ENCLOSING (static_cast(1 << 7)) +#define TRACE_EVENT_FLAG_FLOW_IN (static_cast(1 << 8)) +#define TRACE_EVENT_FLAG_FLOW_OUT (static_cast(1 << 9)) +#define TRACE_EVENT_FLAG_HAS_CONTEXT_ID (static_cast(1 << 10)) + +#define TRACE_EVENT_FLAG_SCOPE_MASK \ + (static_cast(TRACE_EVENT_FLAG_SCOPE_OFFSET | \ + TRACE_EVENT_FLAG_SCOPE_EXTRA)) + +// Type values for identifying types in the TraceValue union. +#define TRACE_VALUE_TYPE_BOOL (static_cast(1)) +#define TRACE_VALUE_TYPE_UINT (static_cast(2)) +#define TRACE_VALUE_TYPE_INT (static_cast(3)) +#define TRACE_VALUE_TYPE_DOUBLE (static_cast(4)) +#define TRACE_VALUE_TYPE_POINTER (static_cast(5)) +#define TRACE_VALUE_TYPE_STRING (static_cast(6)) +#define TRACE_VALUE_TYPE_COPY_STRING (static_cast(7)) +#define TRACE_VALUE_TYPE_CONVERTABLE (static_cast(8)) + +// Enum reflecting the scope of an INSTANT event. Must fit within TRACE_EVENT_FLAG_SCOPE_MASK. +#define TRACE_EVENT_SCOPE_GLOBAL (static_cast(0 << 3)) +#define TRACE_EVENT_SCOPE_PROCESS (static_cast(1 << 3)) +#define TRACE_EVENT_SCOPE_THREAD (static_cast(2 << 3)) + +#define TRACE_EVENT_SCOPE_NAME_GLOBAL ('g') +#define TRACE_EVENT_SCOPE_NAME_PROCESS ('p') +#define TRACE_EVENT_SCOPE_NAME_THREAD ('t') + +#endif // SkTraceEventCommon_DEFINED diff --git a/gfx/skia/skia/src/core/SkTypeface.cpp b/gfx/skia/skia/src/core/SkTypeface.cpp new file mode 100644 index 0000000000..17159bebf9 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTypeface.cpp @@ -0,0 +1,420 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMetrics.h" +#include "include/core/SkFontMgr.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkMutex.h" +#include "include/private/SkOnce.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkEndian.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkSurfacePriv.h" +#include "src/core/SkTypefaceCache.h" +#include "src/sfnt/SkOTTable_OS_2.h" + +SkTypeface::SkTypeface(const SkFontStyle& style, bool isFixedPitch) + : fUniqueID(SkTypefaceCache::NewFontID()), fStyle(style), fIsFixedPitch(isFixedPitch) { } + +SkTypeface::~SkTypeface() { } + +#ifdef SK_WHITELIST_SERIALIZED_TYPEFACES +extern void WhitelistSerializeTypeface(const SkTypeface*, SkWStream* ); +#define SK_TYPEFACE_DELEGATE WhitelistSerializeTypeface +#else +#define SK_TYPEFACE_DELEGATE nullptr +#endif + +void (*gSerializeTypefaceDelegate)(const SkTypeface*, SkWStream* ) = SK_TYPEFACE_DELEGATE; +sk_sp (*gDeserializeTypefaceDelegate)(SkStream* ) = nullptr; + +/////////////////////////////////////////////////////////////////////////////// + +namespace { + +class SkEmptyTypeface : public SkTypeface { +public: + static sk_sp Make() { return sk_sp(new SkEmptyTypeface); } +protected: + SkEmptyTypeface() : SkTypeface(SkFontStyle(), true) { } + + std::unique_ptr onOpenStream(int* ttcIndex) const override { return nullptr; } + sk_sp onMakeClone(const SkFontArguments& args) const override { + return sk_ref_sp(this); + } + SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&, + const SkDescriptor*) const override { + return nullptr; + } + void onFilterRec(SkScalerContextRec*) const override { } + std::unique_ptr onGetAdvancedMetrics() const override { + return nullptr; + } + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override { } + void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override { + sk_bzero(glyphs, count * sizeof(glyphs[0])); + } + int onCountGlyphs() const override { return 0; } + void getPostScriptGlyphNames(SkString*) const override {} + void getGlyphToUnicodeMap(SkUnichar*) const override {} + int onGetUPEM() const override { return 0; } + class EmptyLocalizedStrings : public SkTypeface::LocalizedStrings { + public: + bool next(SkTypeface::LocalizedString*) override { return false; } + }; + void onGetFamilyName(SkString* familyName) const override { + familyName->reset(); + } + SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override { + return new EmptyLocalizedStrings; + } + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override + { + return 0; + } + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override + { + return 0; + } + int onGetTableTags(SkFontTableTag tags[]) const override { return 0; } + size_t onGetTableData(SkFontTableTag, size_t, size_t, void*) const override { + return 0; + } +}; + +} // namespace + +SkFontStyle SkTypeface::FromOldStyle(Style oldStyle) { + return SkFontStyle((oldStyle & SkTypeface::kBold) ? SkFontStyle::kBold_Weight + : SkFontStyle::kNormal_Weight, + SkFontStyle::kNormal_Width, + (oldStyle & SkTypeface::kItalic) ? SkFontStyle::kItalic_Slant + : SkFontStyle::kUpright_Slant); +} + +SkTypeface* SkTypeface::GetDefaultTypeface(Style style) { + static SkOnce once[4]; + static sk_sp defaults[4]; + + SkASSERT((int)style < 4); + once[style]([style] { + sk_sp fm(SkFontMgr::RefDefault()); + auto t = fm->legacyMakeTypeface(nullptr, FromOldStyle(style)); + defaults[style] = t ? t : SkEmptyTypeface::Make(); + }); + return defaults[style].get(); +} + +sk_sp SkTypeface::MakeDefault() { + return sk_ref_sp(GetDefaultTypeface()); +} + +uint32_t SkTypeface::UniqueID(const SkTypeface* face) { + if (nullptr == face) { + face = GetDefaultTypeface(); + } + return face->uniqueID(); +} + +bool SkTypeface::Equal(const SkTypeface* facea, const SkTypeface* faceb) { + return facea == faceb || SkTypeface::UniqueID(facea) == SkTypeface::UniqueID(faceb); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkTypeface::MakeFromName(const char name[], + SkFontStyle fontStyle) { + if (nullptr == name && (fontStyle.slant() == SkFontStyle::kItalic_Slant || + fontStyle.slant() == SkFontStyle::kUpright_Slant) && + (fontStyle.weight() == SkFontStyle::kBold_Weight || + fontStyle.weight() == SkFontStyle::kNormal_Weight)) { + return sk_ref_sp(GetDefaultTypeface(static_cast( + (fontStyle.slant() == SkFontStyle::kItalic_Slant ? SkTypeface::kItalic : + SkTypeface::kNormal) | + (fontStyle.weight() == SkFontStyle::kBold_Weight ? SkTypeface::kBold : + SkTypeface::kNormal)))); + } + return SkFontMgr::RefDefault()->legacyMakeTypeface(name, fontStyle); +} + +sk_sp SkTypeface::MakeFromStream(std::unique_ptr stream, int index) { + if (!stream) { + return nullptr; + } + return SkFontMgr::RefDefault()->makeFromStream(std::move(stream), index); +} + +sk_sp SkTypeface::MakeFromData(sk_sp data, int index) { + if (!data) { + return nullptr; + } + return SkFontMgr::RefDefault()->makeFromData(std::move(data), index); +} + +sk_sp SkTypeface::MakeFromFontData(std::unique_ptr data) { + return SkFontMgr::RefDefault()->makeFromFontData(std::move(data)); +} + +sk_sp SkTypeface::MakeFromFile(const char path[], int index) { + return SkFontMgr::RefDefault()->makeFromFile(path, index); +} + +sk_sp SkTypeface::makeClone(const SkFontArguments& args) const { + return this->onMakeClone(args); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkTypeface::serialize(SkWStream* wstream, SerializeBehavior behavior) const { + if (gSerializeTypefaceDelegate) { + (*gSerializeTypefaceDelegate)(this, wstream); + return; + } + + bool isLocalData = false; + SkFontDescriptor desc; + this->onGetFontDescriptor(&desc, &isLocalData); + + bool shouldSerializeData = false; + switch (behavior) { + case SerializeBehavior::kDoIncludeData: shouldSerializeData = true; break; + case SerializeBehavior::kDontIncludeData: shouldSerializeData = false; break; + case SerializeBehavior::kIncludeDataIfLocal: shouldSerializeData = isLocalData; break; + } + + // TODO: why do we check hasFontData() and allow the data to pass through even if the caller + // has said they don't want the fontdata? Does this actually happen (getDescriptor returns + // fontdata as well?) + if (shouldSerializeData && !desc.hasFontData()) { + desc.setFontData(this->onMakeFontData()); + } + desc.serialize(wstream); +} + +sk_sp SkTypeface::serialize(SerializeBehavior behavior) const { + SkDynamicMemoryWStream stream; + this->serialize(&stream, behavior); + return stream.detachAsData(); +} + +sk_sp SkTypeface::MakeDeserialize(SkStream* stream) { + if (gDeserializeTypefaceDelegate) { + return (*gDeserializeTypefaceDelegate)(stream); + } + + SkFontDescriptor desc; + if (!SkFontDescriptor::Deserialize(stream, &desc)) { + return nullptr; + } + + std::unique_ptr data = desc.detachFontData(); + if (data) { + sk_sp typeface(SkTypeface::MakeFromFontData(std::move(data))); + if (typeface) { + return typeface; + } + } + + return SkTypeface::MakeFromName(desc.getFamilyName(), desc.getStyle()); +} + +/////////////////////////////////////////////////////////////////////////////// + +int SkTypeface::getVariationDesignPosition( + SkFontArguments::VariationPosition::Coordinate coordinates[], int coordinateCount) const +{ + return this->onGetVariationDesignPosition(coordinates, coordinateCount); +} + +int SkTypeface::getVariationDesignParameters( + SkFontParameters::Variation::Axis parameters[], int parameterCount) const +{ + return this->onGetVariationDesignParameters(parameters, parameterCount); +} + +int SkTypeface::countTables() const { + return this->onGetTableTags(nullptr); +} + +int SkTypeface::getTableTags(SkFontTableTag tags[]) const { + return this->onGetTableTags(tags); +} + +size_t SkTypeface::getTableSize(SkFontTableTag tag) const { + return this->onGetTableData(tag, 0, ~0U, nullptr); +} + +size_t SkTypeface::getTableData(SkFontTableTag tag, size_t offset, size_t length, + void* data) const { + return this->onGetTableData(tag, offset, length, data); +} + +sk_sp SkTypeface::copyTableData(SkFontTableTag tag) const { + return this->onCopyTableData(tag); +} + +sk_sp SkTypeface::onCopyTableData(SkFontTableTag tag) const { + size_t size = this->getTableSize(tag); + if (size) { + sk_sp data = SkData::MakeUninitialized(size); + (void)this->getTableData(tag, 0, size, data->writable_data()); + return data; + } + return nullptr; +} + +std::unique_ptr SkTypeface::openStream(int* ttcIndex) const { + int ttcIndexStorage; + if (nullptr == ttcIndex) { + // So our subclasses don't need to check for null param + ttcIndex = &ttcIndexStorage; + } + return this->onOpenStream(ttcIndex); +} + +std::unique_ptr SkTypeface::makeFontData() const { + return this->onMakeFontData(); +} + +// This implementation is temporary until this method can be made pure virtual. +std::unique_ptr SkTypeface::onMakeFontData() const { + int index; + std::unique_ptr stream(this->onOpenStream(&index)); + if (!stream) { + return nullptr; + } + return skstd::make_unique(std::move(stream), index, nullptr, 0); +}; + +void SkTypeface::unicharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const { + if (count > 0 && glyphs && uni) { + this->onCharsToGlyphs(uni, count, glyphs); + } +} + +SkGlyphID SkTypeface::unicharToGlyph(SkUnichar uni) const { + SkGlyphID glyphs[1] = { 0 }; + this->onCharsToGlyphs(&uni, 1, glyphs); + return glyphs[0]; +} + +int SkTypeface::countGlyphs() const { + return this->onCountGlyphs(); +} + +int SkTypeface::getUnitsPerEm() const { + // should we try to cache this in the base-class? + return this->onGetUPEM(); +} + +bool SkTypeface::getKerningPairAdjustments(const uint16_t glyphs[], int count, + int32_t adjustments[]) const { + SkASSERT(count >= 0); + // check for the only legal way to pass a nullptr.. everything is 0 + // in which case they just want to know if this face can possibly support + // kerning (true) or never (false). + if (nullptr == glyphs || nullptr == adjustments) { + SkASSERT(nullptr == glyphs); + SkASSERT(0 == count); + SkASSERT(nullptr == adjustments); + } + return this->onGetKerningPairAdjustments(glyphs, count, adjustments); +} + +SkTypeface::LocalizedStrings* SkTypeface::createFamilyNameIterator() const { + return this->onCreateFamilyNameIterator(); +} + +void SkTypeface::getFamilyName(SkString* name) const { + SkASSERT(name); + this->onGetFamilyName(name); +} + +void SkTypeface::getGlyphToUnicodeMap(SkUnichar* dst) const { + sk_bzero(dst, sizeof(SkUnichar) * this->countGlyphs()); +} + +std::unique_ptr SkTypeface::getAdvancedMetrics() const { + std::unique_ptr result = this->onGetAdvancedMetrics(); + if (result && result->fPostScriptName.isEmpty()) { + result->fPostScriptName = result->fFontName; + } + if (result && result->fType == SkAdvancedTypefaceMetrics::kTrueType_Font) { + SkOTTableOS2::Version::V2::Type::Field fsType; + constexpr SkFontTableTag os2Tag = SkTEndian_SwapBE32(SkOTTableOS2::TAG); + constexpr size_t fsTypeOffset = offsetof(SkOTTableOS2::Version::V2, fsType); + if (this->getTableData(os2Tag, fsTypeOffset, sizeof(fsType), &fsType) == sizeof(fsType)) { + if (fsType.Bitmap || (fsType.Restricted && !(fsType.PreviewPrint || fsType.Editable))) { + result->fFlags |= SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag; + } + if (fsType.NoSubsetting) { + result->fFlags |= SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag; + } + } + } + return result; +} + +bool SkTypeface::onGetKerningPairAdjustments(const uint16_t glyphs[], int count, + int32_t adjustments[]) const { + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkPaint.h" +#include "src/core/SkDescriptor.h" + +SkRect SkTypeface::getBounds() const { + fBoundsOnce([this] { + if (!this->onComputeBounds(&fBounds)) { + fBounds.setEmpty(); + } + }); + return fBounds; +} + +bool SkTypeface::onComputeBounds(SkRect* bounds) const { + // we use a big size to ensure lots of significant bits from the scalercontext. + // then we scale back down to return our final answer (at 1-pt) + const SkScalar textSize = 2048; + const SkScalar invTextSize = 1 / textSize; + + SkFont font; + font.setTypeface(sk_ref_sp(const_cast(this))); + font.setSize(textSize); + font.setLinearMetrics(true); + + SkScalerContextRec rec; + SkScalerContextEffects effects; + + SkScalerContext::MakeRecAndEffectsFromFont(font, &rec, &effects); + + SkAutoDescriptor ad; + SkScalerContextEffects noeffects; + SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, noeffects, &ad); + + std::unique_ptr ctx = this->createScalerContext(noeffects, ad.getDesc(), true); + if (!ctx) { + return false; + } + + SkFontMetrics fm; + ctx->getFontMetrics(&fm); + bounds->setLTRB(fm.fXMin * invTextSize, fm.fTop * invTextSize, + fm.fXMax * invTextSize, fm.fBottom * invTextSize); + return true; +} + +std::unique_ptr SkTypeface::onGetAdvancedMetrics() const { + SkDEBUGFAIL("Typefaces that need to work with PDF backend must override this."); + return nullptr; +} diff --git a/gfx/skia/skia/src/core/SkTypefaceCache.cpp b/gfx/skia/skia/src/core/SkTypefaceCache.cpp new file mode 100644 index 0000000000..0d05bac7fe --- /dev/null +++ b/gfx/skia/skia/src/core/SkTypefaceCache.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMutex.h" +#include "src/core/SkTypefaceCache.h" +#include + +#define TYPEFACE_CACHE_LIMIT 1024 + +SkTypefaceCache::SkTypefaceCache() {} + +void SkTypefaceCache::add(sk_sp face) { + if (fTypefaces.count() >= TYPEFACE_CACHE_LIMIT) { + this->purge(TYPEFACE_CACHE_LIMIT >> 2); + } + + fTypefaces.emplace_back(std::move(face)); +} + +sk_sp SkTypefaceCache::findByProcAndRef(FindProc proc, void* ctx) const { + for (const sk_sp& typeface : fTypefaces) { + if (proc(typeface.get(), ctx)) { + return typeface; + } + } + return nullptr; +} + +void SkTypefaceCache::purge(int numToPurge) { + int count = fTypefaces.count(); + int i = 0; + while (i < count) { + if (fTypefaces[i]->unique()) { + fTypefaces.removeShuffle(i); + --count; + if (--numToPurge == 0) { + return; + } + } else { + ++i; + } + } +} + +void SkTypefaceCache::purgeAll() { + this->purge(fTypefaces.count()); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkTypefaceCache& SkTypefaceCache::Get() { + static SkTypefaceCache gCache; + return gCache; +} + +SkFontID SkTypefaceCache::NewFontID() { + static std::atomic nextID{1}; + return nextID++; +} + +static SkMutex& typeface_cache_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} + +void SkTypefaceCache::Add(sk_sp face) { +#ifndef SK_DISABLE_TYPEFACE_CACHE + SkAutoMutexExclusive ama(typeface_cache_mutex()); + Get().add(std::move(face)); +#endif +} + +sk_sp SkTypefaceCache::FindByProcAndRef(FindProc proc, void* ctx) { +#ifndef SK_DISABLE_TYPEFACE_CACHE + SkAutoMutexExclusive ama(typeface_cache_mutex()); + return Get().findByProcAndRef(proc, ctx); +#else + return nullptr; +#endif +} + +void SkTypefaceCache::PurgeAll() { +#ifndef SK_DISABLE_TYPEFACE_CACHE + SkAutoMutexExclusive ama(typeface_cache_mutex()); + Get().purgeAll(); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +static bool DumpProc(SkTypeface* face, void* ctx) { + SkString n; + face->getFamilyName(&n); + SkFontStyle s = face->fontStyle(); + SkFontID id = face->uniqueID(); + SkDebugf("SkTypefaceCache: face %p fontID %d weight %d width %d style %d name %s\n", + face, id, s.weight(), s.width(), s.slant(), n.c_str()); + return false; +} +#endif + +void SkTypefaceCache::Dump() { +#ifdef SK_DEBUG + (void)Get().findByProcAndRef(DumpProc, nullptr); +#endif +} diff --git a/gfx/skia/skia/src/core/SkTypefaceCache.h b/gfx/skia/skia/src/core/SkTypefaceCache.h new file mode 100644 index 0000000000..ac3faafec0 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTypefaceCache.h @@ -0,0 +1,74 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#ifndef SkTypefaceCache_DEFINED +#define SkTypefaceCache_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkTArray.h" + +class SkTypefaceCache { +public: + SkTypefaceCache(); + + /** + * Callback for FindByProc. Returns true if the given typeface is a match + * for the given context. The passed typeface is owned by the cache and is + * not additionally ref()ed. The typeface may be in the disposed state. + */ + typedef bool(*FindProc)(SkTypeface*, void* context); + + /** + * Add a typeface to the cache. Later, if we need to purge the cache, + * typefaces uniquely owned by the cache will be unref()ed. + */ + void add(sk_sp); + + /** + * Iterate through the cache, calling proc(typeface, ctx) for each typeface. + * If proc returns true, then return that typeface. + * If it never returns true, return nullptr. + */ + sk_sp findByProcAndRef(FindProc proc, void* ctx) const; + + /** + * This will unref all of the typefaces in the cache for which the cache + * is the only owner. Normally this is handled automatically as needed. + * This function is exposed for clients that explicitly want to purge the + * cache (e.g. to look for leaks). + */ + void purgeAll(); + + /** + * Helper: returns a unique fontID to pass to the constructor of + * your subclass of SkTypeface + */ + static SkFontID NewFontID(); + + // These are static wrappers around a global instance of a cache. + + static void Add(sk_sp); + static sk_sp FindByProcAndRef(FindProc proc, void* ctx); + static void PurgeAll(); + + /** + * Debugging only: dumps the status of the typefaces in the cache + */ + static void Dump(); + +private: + static SkTypefaceCache& Get(); + + void purge(int count); + + SkTArray> fTypefaces; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTypefacePriv.h b/gfx/skia/skia/src/core/SkTypefacePriv.h new file mode 100644 index 0000000000..cc25b843af --- /dev/null +++ b/gfx/skia/skia/src/core/SkTypefacePriv.h @@ -0,0 +1,38 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTypefacePriv_DEFINED +#define SkTypefacePriv_DEFINED + +#include "include/core/SkTypeface.h" + +/** + * Return a ref'd typeface, which must later be unref'd + * + * If the parameter is non-null, it will be ref'd and returned, otherwise + * it will be the default typeface. + */ +static inline sk_sp ref_or_default(SkTypeface* face) { + return face ? sk_ref_sp(face) : SkTypeface::MakeDefault(); +} + +/** + * Always resolves to a non-null typeface, either the value passed to its + * constructor, or the default typeface if null was passed. + */ +class SkAutoResolveDefaultTypeface : public sk_sp { +public: + SkAutoResolveDefaultTypeface() : INHERITED(SkTypeface::MakeDefault()) {} + + SkAutoResolveDefaultTypeface(SkTypeface* face) + : INHERITED(ref_or_default(face)) {} + +private: + typedef sk_sp INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkTypeface_remote.cpp b/gfx/skia/skia/src/core/SkTypeface_remote.cpp new file mode 100644 index 0000000000..838a783313 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTypeface_remote.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "src/core/SkRemoteGlyphCache.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkTraceEvent.h" +#include "src/core/SkTypeface_remote.h" + +SkScalerContextProxy::SkScalerContextProxy(sk_sp tf, + const SkScalerContextEffects& effects, + const SkDescriptor* desc, + sk_sp manager) + : SkScalerContext{std::move(tf), effects, desc} + , fDiscardableManager{std::move(manager)} {} + +void SkScalerContextProxy::initCache(SkStrike* cache, SkStrikeCache* strikeCache) { + SkASSERT(fCache == nullptr); + SkASSERT(cache != nullptr); + + fCache = cache; + fStrikeCache = strikeCache; +} + +unsigned SkScalerContextProxy::generateGlyphCount() { + SK_ABORT("Should never be called."); +} + +bool SkScalerContextProxy::generateAdvance(SkGlyph* glyph) { + return false; +} + +void SkScalerContextProxy::generateMetrics(SkGlyph* glyph) { + TRACE_EVENT1("skia", "generateMetrics", "rec", TRACE_STR_COPY(this->getRec().dump().c_str())); + if (this->getProxyTypeface()->isLogging()) { + SkDebugf("GlyphCacheMiss generateMetrics: %s\n", this->getRec().dump().c_str()); + } + + glyph->fMaskFormat = fRec.fMaskFormat; + + // Since the scaler context is being called, we don't have the needed data. Try to find a + // fallback before failing. + if (fCache && fCache->belongsToCache(glyph)) { + // First check the original cache, in case there is a sub-pixel pos mismatch. + if (const SkGlyph* from = + fCache->getCachedGlyphAnySubPix(glyph->getGlyphID(), glyph->getPackedID())) { + fCache->mergeGlyphAndImage(glyph->getPackedID(), *from); + fDiscardableManager->notifyCacheMiss( + SkStrikeClient::CacheMissType::kGlyphMetricsFallback); + return; + } + + // Now check other caches for a desc mismatch. + if (fStrikeCache->desperationSearchForImage(fCache->getDescriptor(), glyph, fCache)) { + fDiscardableManager->notifyCacheMiss( + SkStrikeClient::CacheMissType::kGlyphMetricsFallback); + return; + } + } + + glyph->zeroMetrics(); + fDiscardableManager->notifyCacheMiss(SkStrikeClient::CacheMissType::kGlyphMetrics); +} + +void SkScalerContextProxy::generateImage(const SkGlyph& glyph) { + TRACE_EVENT1("skia", "generateImage", "rec", TRACE_STR_COPY(this->getRec().dump().c_str())); + if (this->getProxyTypeface()->isLogging()) { + SkDebugf("GlyphCacheMiss generateImage: %s\n", this->getRec().dump().c_str()); + } + + // There is no desperation search here, because if there was an image to be found it was + // copied over with the metrics search. + fDiscardableManager->notifyCacheMiss(SkStrikeClient::CacheMissType::kGlyphImage); +} + +bool SkScalerContextProxy::generatePath(SkGlyphID glyphID, SkPath* path) { + TRACE_EVENT1("skia", "generatePath", "rec", TRACE_STR_COPY(this->getRec().dump().c_str())); + if (this->getProxyTypeface()->isLogging()) { + SkDebugf("GlyphCacheMiss generatePath: %s\n", this->getRec().dump().c_str()); + } + + // Since the scaler context is being called, we don't have the needed data. Try to find a + // fallback before failing. + auto desc = SkScalerContext::DescriptorGivenRecAndEffects(this->getRec(), this->getEffects()); + bool foundPath = fStrikeCache && fStrikeCache->desperationSearchForPath(*desc, glyphID, path); + fDiscardableManager->notifyCacheMiss(foundPath + ? SkStrikeClient::CacheMissType::kGlyphPathFallback + : SkStrikeClient::CacheMissType::kGlyphPath); + return foundPath; +} + +void SkScalerContextProxy::generateFontMetrics(SkFontMetrics* metrics) { + TRACE_EVENT1( + "skia", "generateFontMetrics", "rec", TRACE_STR_COPY(this->getRec().dump().c_str())); + if (this->getProxyTypeface()->isLogging()) { + SkDebugf("GlyphCacheMiss generateFontMetrics: %s\n", this->getRec().dump().c_str()); + SkDEBUGCODE(SkStrikeCache::Dump()); + } + + // Font metrics aren't really used for render, so just zero out the data and return. + fDiscardableManager->notifyCacheMiss(SkStrikeClient::CacheMissType::kFontMetrics); + sk_bzero(metrics, sizeof(*metrics)); +} + +SkTypefaceProxy* SkScalerContextProxy::getProxyTypeface() const { + return (SkTypefaceProxy*)this->getTypeface(); +} diff --git a/gfx/skia/skia/src/core/SkTypeface_remote.h b/gfx/skia/skia/src/core/SkTypeface_remote.h new file mode 100644 index 0000000000..3d454cf760 --- /dev/null +++ b/gfx/skia/skia/src/core/SkTypeface_remote.h @@ -0,0 +1,141 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRemoteTypeface_DEFINED +#define SkRemoteTypeface_DEFINED + +#include "include/core/SkFontStyle.h" +#include "include/core/SkPaint.h" +#include "include/core/SkTypeface.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkRemoteGlyphCache.h" +#include "src/core/SkScalerContext.h" + +class SkTypefaceProxy; +class SkStrikeCache; + +class SkScalerContextProxy : public SkScalerContext { +public: + SkScalerContextProxy(sk_sp tf, + const SkScalerContextEffects& effects, + const SkDescriptor* desc, + sk_sp manager); + + void initCache(SkStrike*, SkStrikeCache*); + +protected: + unsigned generateGlyphCount() override; + bool generateAdvance(SkGlyph* glyph) override; + void generateMetrics(SkGlyph* glyph) override; + void generateImage(const SkGlyph& glyph) override; + bool generatePath(SkGlyphID glyphID, SkPath* path) override; + void generateFontMetrics(SkFontMetrics* metrics) override; + SkTypefaceProxy* getProxyTypeface() const; + +private: + sk_sp fDiscardableManager; + SkStrike* fCache = nullptr; + SkStrikeCache* fStrikeCache = nullptr; + typedef SkScalerContext INHERITED; +}; + +class SkTypefaceProxy : public SkTypeface { +public: + SkTypefaceProxy(SkFontID fontId, + int glyphCount, + const SkFontStyle& style, + bool isFixed, + sk_sp manager, + bool isLogging = true) + : INHERITED{style, false} + , fFontId{fontId} + , fGlyphCount{glyphCount} + , fIsLogging{isLogging} + , fDiscardableManager{std::move(manager)} {} + SkFontID remoteTypefaceID() const {return fFontId;} + int glyphCount() const {return fGlyphCount;} + bool isLogging() const {return fIsLogging;} + +protected: + int onGetUPEM() const override { SK_ABORT("Should never be called."); } + std::unique_ptr onOpenStream(int* ttcIndex) const override { + SK_ABORT("Should never be called."); + } + std::unique_ptr onMakeFontData() const override { + SK_ABORT("Should never be called."); + } + sk_sp onMakeClone(const SkFontArguments& args) const override { + SK_ABORT("Should never be called."); + } + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override { + SK_ABORT("Should never be called."); + } + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override { + SK_ABORT("Should never be called."); + } + void onGetFamilyName(SkString* familyName) const override { + // Used by SkStrikeCache::DumpMemoryStatistics. + *familyName = ""; + } + SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override { + SK_ABORT("Should never be called."); + } + int onGetTableTags(SkFontTableTag tags[]) const override { + SK_ABORT("Should never be called."); + } + size_t onGetTableData(SkFontTableTag, size_t offset, size_t length, void* data) const override { + SK_ABORT("Should never be called."); + } + SkScalerContext* onCreateScalerContext(const SkScalerContextEffects& effects, + const SkDescriptor* desc) const override { + return new SkScalerContextProxy(sk_ref_sp(const_cast(this)), effects, + desc, fDiscardableManager); + } + void onFilterRec(SkScalerContextRec* rec) const override { + // The rec filtering is already applied by the server when generating + // the glyphs. + } + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override { + SK_ABORT("Should never be called."); + } + void getGlyphToUnicodeMap(SkUnichar*) const override { + SK_ABORT("Should never be called."); + } + + void getPostScriptGlyphNames(SkString*) const override { + SK_ABORT("Should never be called."); + } + + std::unique_ptr onGetAdvancedMetrics() const override { + SK_ABORT("Should never be called."); + } + void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override { + SK_ABORT("Should never be called."); + } + int onCountGlyphs() const override { + return this->glyphCount(); + } + + void* onGetCTFontRef() const override { + SK_ABORT("Should never be called."); + } + +private: + const SkFontID fFontId; + const int fGlyphCount; + const bool fIsLogging; + sk_sp fDiscardableManager; + + + typedef SkTypeface INHERITED; +}; + +#endif // SkRemoteTypeface_DEFINED diff --git a/gfx/skia/skia/src/core/SkUnPreMultiply.cpp b/gfx/skia/skia/src/core/SkUnPreMultiply.cpp new file mode 100644 index 0000000000..2b999190ce --- /dev/null +++ b/gfx/skia/skia/src/core/SkUnPreMultiply.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" + +SkColor SkUnPreMultiply::PMColorToColor(SkPMColor c) { + const unsigned a = SkGetPackedA32(c); + const Scale scale = GetScale(a); + return SkColorSetARGB(a, + ApplyScale(scale, SkGetPackedR32(c)), + ApplyScale(scale, SkGetPackedG32(c)), + ApplyScale(scale, SkGetPackedB32(c))); +} + +const uint32_t SkUnPreMultiply::gTable[] = { + 0x00000000, 0xFF000000, 0x7F800000, 0x55000000, 0x3FC00000, 0x33000000, 0x2A800000, 0x246DB6DB, + 0x1FE00000, 0x1C555555, 0x19800000, 0x172E8BA3, 0x15400000, 0x139D89D9, 0x1236DB6E, 0x11000000, + 0x0FF00000, 0x0F000000, 0x0E2AAAAB, 0x0D6BCA1B, 0x0CC00000, 0x0C249249, 0x0B9745D1, 0x0B1642C8, + 0x0AA00000, 0x0A333333, 0x09CEC4EC, 0x0971C71C, 0x091B6DB7, 0x08CB08D4, 0x08800000, 0x0839CE74, + 0x07F80000, 0x07BA2E8C, 0x07800000, 0x07492492, 0x07155555, 0x06E45307, 0x06B5E50D, 0x0689D89E, + 0x06600000, 0x063831F4, 0x06124925, 0x05EE23B9, 0x05CBA2E9, 0x05AAAAAB, 0x058B2164, 0x056CEFA9, + 0x05500000, 0x05343EB2, 0x0519999A, 0x05000000, 0x04E76276, 0x04CFB2B8, 0x04B8E38E, 0x04A2E8BA, + 0x048DB6DB, 0x0479435E, 0x0465846A, 0x045270D0, 0x04400000, 0x042E29F8, 0x041CE73A, 0x040C30C3, + 0x03FC0000, 0x03EC4EC5, 0x03DD1746, 0x03CE540F, 0x03C00000, 0x03B21643, 0x03A49249, 0x03976FC6, + 0x038AAAAB, 0x037E3F20, 0x03722983, 0x03666666, 0x035AF287, 0x034FCACE, 0x0344EC4F, 0x033A5441, + 0x03300000, 0x0325ED09, 0x031C18FA, 0x0312818B, 0x03092492, 0x03000000, 0x02F711DC, 0x02EE5847, + 0x02E5D174, 0x02DD7BAF, 0x02D55555, 0x02CD5CD6, 0x02C590B2, 0x02BDEF7C, 0x02B677D4, 0x02AF286C, + 0x02A80000, 0x02A0FD5C, 0x029A1F59, 0x029364D9, 0x028CCCCD, 0x0286562E, 0x02800000, 0x0279C952, + 0x0273B13B, 0x026DB6DB, 0x0267D95C, 0x026217ED, 0x025C71C7, 0x0256E62A, 0x0251745D, 0x024C1BAD, + 0x0246DB6E, 0x0241B2F9, 0x023CA1AF, 0x0237A6F5, 0x0232C235, 0x022DF2DF, 0x02293868, 0x02249249, + 0x02200000, 0x021B810F, 0x021714FC, 0x0212BB51, 0x020E739D, 0x020A3D71, 0x02061862, 0x02020408, + 0x01FE0000, 0x01FA0BE8, 0x01F62762, 0x01F25214, 0x01EE8BA3, 0x01EAD3BB, 0x01E72A08, 0x01E38E39, + 0x01E00000, 0x01DC7F11, 0x01D90B21, 0x01D5A3EA, 0x01D24925, 0x01CEFA8E, 0x01CBB7E3, 0x01C880E5, + 0x01C55555, 0x01C234F7, 0x01BF1F90, 0x01BC14E6, 0x01B914C2, 0x01B61EED, 0x01B33333, 0x01B05161, + 0x01AD7943, 0x01AAAAAB, 0x01A7E567, 0x01A5294A, 0x01A27627, 0x019FCBD2, 0x019D2A20, 0x019A90E8, + 0x01980000, 0x01957741, 0x0192F685, 0x01907DA5, 0x018E0C7D, 0x018BA2E9, 0x018940C5, 0x0186E5F1, + 0x01849249, 0x018245AE, 0x01800000, 0x017DC11F, 0x017B88EE, 0x0179574E, 0x01772C23, 0x01750750, + 0x0172E8BA, 0x0170D045, 0x016EBDD8, 0x016CB157, 0x016AAAAB, 0x0168A9B9, 0x0166AE6B, 0x0164B8A8, + 0x0162C859, 0x0160DD68, 0x015EF7BE, 0x015D1746, 0x015B3BEA, 0x01596596, 0x01579436, 0x0155C7B5, + 0x01540000, 0x01523D04, 0x01507EAE, 0x014EC4EC, 0x014D0FAC, 0x014B5EDD, 0x0149B26D, 0x01480A4B, + 0x01466666, 0x0144C6B0, 0x01432B17, 0x0141938C, 0x01400000, 0x013E7064, 0x013CE4A9, 0x013B5CC1, + 0x0139D89E, 0x01385831, 0x0136DB6E, 0x01356246, 0x0133ECAE, 0x01327A97, 0x01310BF6, 0x012FA0BF, + 0x012E38E4, 0x012CD45A, 0x012B7315, 0x012A150B, 0x0128BA2F, 0x01276276, 0x01260DD6, 0x0124BC45, + 0x01236DB7, 0x01222222, 0x0120D97D, 0x011F93BC, 0x011E50D8, 0x011D10C5, 0x011BD37A, 0x011A98EF, + 0x0119611A, 0x01182BF3, 0x0116F970, 0x0115C988, 0x01149C34, 0x0113716B, 0x01124925, 0x01112359, + 0x01100000, 0x010EDF12, 0x010DC087, 0x010CA458, 0x010B8A7E, 0x010A72F0, 0x01095DA9, 0x01084AA0, + 0x010739CE, 0x01062B2E, 0x01051EB8, 0x01041466, 0x01030C31, 0x01020612, 0x01010204, 0x01000000 +}; + +#ifdef BUILD_DIVIDE_TABLE +void SkUnPreMultiply_BuildTable() { + for (unsigned i = 0; i <= 255; i++) { + uint32_t scale; + + if (0 == i) { + scale = 0; + } else { + scale = ((255 << 24) + (i >> 1)) / i; + } + + SkDebugf(" 0x%08X,", scale); + if ((i & 7) == 7) { + SkDebugf("\n"); + } + + // test the result + for (int j = 1; j <= i; j++) { + uint32_t test = (j * scale + (1 << 23)) >> 24; + uint32_t div = roundf(j * 255.0f / i); + int diff = SkAbs32(test - div); + SkASSERT(diff <= 1 && test <= 255); + } + } +} +#endif diff --git a/gfx/skia/skia/src/core/SkUtils.cpp b/gfx/skia/skia/src/core/SkUtils.cpp new file mode 100644 index 0000000000..d369d509b8 --- /dev/null +++ b/gfx/skia/skia/src/core/SkUtils.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkUtils.h" + +template +static SkUnichar next(const T** srcPtr, unsigned N, SkUnichar (*fn)(const T**, const T*)) { + SkASSERT(srcPtr); + const T* ptr = *srcPtr; + SkUnichar c = fn(&ptr, ptr + N); + if (c == -1) { + SkASSERT(false); + ++(*srcPtr); + return 0xFFFD; // REPLACEMENT CHARACTER + } + *srcPtr = ptr; + return c; +} +SkUnichar SkUTF8_NextUnichar(const char** p) { + return next(p, SkUTF::kMaxBytesInUTF8Sequence, SkUTF::NextUTF8); +} +SkUnichar SkUTF16_NextUnichar(const uint16_t** p) { + return next(p, 2, SkUTF::NextUTF16); +} + +/////////////////////////////////////////////////////////////////////////////// + +const char SkHexadecimalDigits::gUpper[16] = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; +const char SkHexadecimalDigits::gLower[16] = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; diff --git a/gfx/skia/skia/src/core/SkUtils.h b/gfx/skia/skia/src/core/SkUtils.h new file mode 100644 index 0000000000..cf2ee10914 --- /dev/null +++ b/gfx/skia/skia/src/core/SkUtils.h @@ -0,0 +1,98 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkUtils_DEFINED +#define SkUtils_DEFINED + +#include "include/core/SkFontTypes.h" +#include "src/core/SkOpts.h" +#include "src/utils/SkUTF.h" + +/** Similar to memset(), but it assigns a 16, 32, or 64-bit value into the buffer. + @param buffer The memory to have value copied into it + @param value The value to be copied into buffer + @param count The number of times value should be copied into the buffer. +*/ +static inline void sk_memset16(uint16_t buffer[], uint16_t value, int count) { + SkOpts::memset16(buffer, value, count); +} +static inline void sk_memset32(uint32_t buffer[], uint32_t value, int count) { + SkOpts::memset32(buffer, value, count); +} +static inline void sk_memset64(uint64_t buffer[], uint64_t value, int count) { + SkOpts::memset64(buffer, value, count); +} + +/////////////////////////////////////////////////////////////////////////////// + +// Unlike the functions in SkUTF.h, these two functions do not take an array +// length parameter. When possible, use SkUTF::NextUTF{8,16} instead. +SkUnichar SkUTF8_NextUnichar(const char**); +SkUnichar SkUTF16_NextUnichar(const uint16_t**); + +/////////////////////////////////////////////////////////////////////////////// + +static inline bool SkUTF16_IsLeadingSurrogate(uint16_t c) { return ((c) & 0xFC00) == 0xD800; } + +static inline bool SkUTF16_IsTrailingSurrogate (uint16_t c) { return ((c) & 0xFC00) == 0xDC00; } + +/////////////////////////////////////////////////////////////////////////////// + +static inline int SkUTFN_CountUnichars(SkTextEncoding enc, const void* utfN, size_t bytes) { + switch (enc) { + case SkTextEncoding::kUTF8: return SkUTF::CountUTF8((const char*)utfN, bytes); + case SkTextEncoding::kUTF16: return SkUTF::CountUTF16((const uint16_t*)utfN, bytes); + case SkTextEncoding::kUTF32: return SkUTF::CountUTF32((const int32_t*)utfN, bytes); + default: SkDEBUGFAIL("unknown text encoding"); return -1; + } +} + +static inline SkUnichar SkUTFN_Next(SkTextEncoding enc, const void** ptr, const void* stop) { + switch (enc) { + case SkTextEncoding::kUTF8: + return SkUTF::NextUTF8((const char**)ptr, (const char*)stop); + case SkTextEncoding::kUTF16: + return SkUTF::NextUTF16((const uint16_t**)ptr, (const uint16_t*)stop); + case SkTextEncoding::kUTF32: + return SkUTF::NextUTF32((const int32_t**)ptr, (const int32_t*)stop); + default: SkDEBUGFAIL("unknown text encoding"); return -1; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +namespace SkHexadecimalDigits { + extern const char gUpper[16]; // 0-9A-F + extern const char gLower[16]; // 0-9a-f +} + +/////////////////////////////////////////////////////////////////////////////// + +// If T is an 8-byte GCC or Clang vector extension type, it would naturally +// pass or return in the MMX mm0 register on 32-bit x86 builds. This has the +// fun side effect of clobbering any state in the x87 st0 register. (There is +// no ABI governing who should preserve mm?/st? registers, so no one does!) +// +// We force-inline sk_unaligned_load() and sk_unaligned_store() to avoid that, +// making them safe to use for all types on all platforms, thus solving the +// problem once and for all! + +template +static SK_ALWAYS_INLINE T sk_unaligned_load(const P* ptr) { + // TODO: static_assert desirable things about T here so as not to be totally abused. + T val; + memcpy(&val, ptr, sizeof(val)); + return val; +} + +template +static SK_ALWAYS_INLINE void sk_unaligned_store(P* ptr, T val) { + // TODO: ditto + memcpy(ptr, &val, sizeof(val)); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkUtilsArm.cpp b/gfx/skia/skia/src/core/SkUtilsArm.cpp new file mode 100644 index 0000000000..c29938fdfc --- /dev/null +++ b/gfx/skia/skia/src/core/SkUtilsArm.cpp @@ -0,0 +1,8 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// This file no longer needs to exist, but it's still referenced by Chrome's GYP / GN builds. diff --git a/gfx/skia/skia/src/core/SkVM.cpp b/gfx/skia/skia/src/core/SkVM.cpp new file mode 100644 index 0000000000..cd2bc3ae2b --- /dev/null +++ b/gfx/skia/skia/src/core/SkVM.cpp @@ -0,0 +1,2221 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkSpinlock.h" +#include "include/private/SkTFitsIn.h" +#include "include/private/SkThreadID.h" +#include "include/private/SkVx.h" +#include "src/core/SkCpu.h" +#include "src/core/SkVM.h" +#include +#if defined(SKVM_JIT) + #include +#endif +#if defined(SKVM_PERF_DUMPS) + #include + #include +#endif + + +namespace skvm { + + // Debugging tools, mostly for printing various data structures out to a stream. + + namespace { + class SkDebugfStream final : public SkWStream { + size_t fBytesWritten = 0; + + bool write(const void* buffer, size_t size) override { + SkDebugf("%.*s", size, buffer); + fBytesWritten += size; + return true; + } + + size_t bytesWritten() const override { + return fBytesWritten; + } + }; + + struct V { Val id; }; + struct R { Reg id; }; + struct Shift { int bits; }; + struct Splat { int bits; }; + struct Hex { int bits; }; + + static void write(SkWStream* o, const char* s) { + o->writeText(s); + } + + static void write(SkWStream* o, Arg a) { + write(o, "arg("); + o->writeDecAsText(a.ix); + write(o, ")"); + } + static void write(SkWStream* o, V v) { + write(o, "v"); + o->writeDecAsText(v.id); + } + static void write(SkWStream* o, R r) { + write(o, "r"); + o->writeDecAsText(r.id); + } + static void write(SkWStream* o, Shift s) { + o->writeDecAsText(s.bits); + } + static void write(SkWStream* o, Splat s) { + float f; + memcpy(&f, &s.bits, 4); + o->writeHexAsText(s.bits); + write(o, " ("); + o->writeScalarAsText(f); + write(o, ")"); + } + static void write(SkWStream* o, Hex h) { + o->writeHexAsText(h.bits); + } + + template + static void write(SkWStream* o, T first, Ts... rest) { + write(o, first); + write(o, " "); + write(o, rest...); + } + } + + static void dump_builder_program(const std::vector& program, + SkWStream* o) { + for (Val id = 0; id < (Val)program.size(); id++) { + const Builder::Instruction& inst = program[id]; + Op op = inst.op; + Val x = inst.x, + y = inst.y, + z = inst.z; + int imm = inst.imm; + write(o, inst.death == 0 ? "☠️ " : + !inst.can_hoist ? " " : + inst.used_in_loop ? "↑ " : + "↟ "); + switch (op) { + case Op::store8: write(o, "store8" , Arg{imm}, V{x}); break; + case Op::store16: write(o, "store16", Arg{imm}, V{x}); break; + case Op::store32: write(o, "store32", Arg{imm}, V{x}); break; + + case Op::load8: write(o, V{id}, "= load8" , Arg{imm}); break; + case Op::load16: write(o, V{id}, "= load16", Arg{imm}); break; + case Op::load32: write(o, V{id}, "= load32", Arg{imm}); break; + + case Op::gather8: write(o, V{id}, "= gather8" , Arg{imm}, V{x}); break; + case Op::gather16: write(o, V{id}, "= gather16", Arg{imm}, V{x}); break; + case Op::gather32: write(o, V{id}, "= gather32", Arg{imm}, V{x}); break; + + case Op::uniform8: write(o, V{id}, "= uniform8" , Arg{imm & 0xffff}, Hex{imm>>16}); break; + case Op::uniform16: write(o, V{id}, "= uniform16", Arg{imm & 0xffff}, Hex{imm>>16}); break; + case Op::uniform32: write(o, V{id}, "= uniform32", Arg{imm & 0xffff}, Hex{imm>>16}); break; + + case Op::splat: write(o, V{id}, "= splat", Splat{imm}); break; + + + case Op::add_f32: write(o, V{id}, "= add_f32", V{x}, V{y} ); break; + case Op::sub_f32: write(o, V{id}, "= sub_f32", V{x}, V{y} ); break; + case Op::mul_f32: write(o, V{id}, "= mul_f32", V{x}, V{y} ); break; + case Op::div_f32: write(o, V{id}, "= div_f32", V{x}, V{y} ); break; + case Op::mad_f32: write(o, V{id}, "= mad_f32", V{x}, V{y}, V{z}); break; + + case Op:: eq_f32: write(o, V{id}, "= eq_f32", V{x}, V{y}); break; + case Op::neq_f32: write(o, V{id}, "= neq_f32", V{x}, V{y}); break; + case Op:: lt_f32: write(o, V{id}, "= lt_f32", V{x}, V{y}); break; + case Op::lte_f32: write(o, V{id}, "= lte_f32", V{x}, V{y}); break; + case Op:: gt_f32: write(o, V{id}, "= gt_f32", V{x}, V{y}); break; + case Op::gte_f32: write(o, V{id}, "= gte_f32", V{x}, V{y}); break; + + + case Op::add_i32: write(o, V{id}, "= add_i32", V{x}, V{y}); break; + case Op::sub_i32: write(o, V{id}, "= sub_i32", V{x}, V{y}); break; + case Op::mul_i32: write(o, V{id}, "= mul_i32", V{x}, V{y}); break; + + case Op::shl_i32: write(o, V{id}, "= shl_i32", V{x}, Shift{imm}); break; + case Op::shr_i32: write(o, V{id}, "= shr_i32", V{x}, Shift{imm}); break; + case Op::sra_i32: write(o, V{id}, "= sra_i32", V{x}, Shift{imm}); break; + + case Op:: eq_i32: write(o, V{id}, "= eq_i32", V{x}, V{y}); break; + case Op::neq_i32: write(o, V{id}, "= neq_i32", V{x}, V{y}); break; + case Op:: lt_i32: write(o, V{id}, "= lt_i32", V{x}, V{y}); break; + case Op::lte_i32: write(o, V{id}, "= lte_i32", V{x}, V{y}); break; + case Op:: gt_i32: write(o, V{id}, "= gt_i32", V{x}, V{y}); break; + case Op::gte_i32: write(o, V{id}, "= gte_i32", V{x}, V{y}); break; + + case Op::add_i16x2: write(o, V{id}, "= add_i16x2", V{x}, V{y}); break; + case Op::sub_i16x2: write(o, V{id}, "= sub_i16x2", V{x}, V{y}); break; + case Op::mul_i16x2: write(o, V{id}, "= mul_i16x2", V{x}, V{y}); break; + + case Op::shl_i16x2: write(o, V{id}, "= shl_i16x2", V{x}, Shift{imm}); break; + case Op::shr_i16x2: write(o, V{id}, "= shr_i16x2", V{x}, Shift{imm}); break; + case Op::sra_i16x2: write(o, V{id}, "= sra_i16x2", V{x}, Shift{imm}); break; + + case Op:: eq_i16x2: write(o, V{id}, "= eq_i16x2", V{x}, V{y}); break; + case Op::neq_i16x2: write(o, V{id}, "= neq_i16x2", V{x}, V{y}); break; + case Op:: lt_i16x2: write(o, V{id}, "= lt_i16x2", V{x}, V{y}); break; + case Op::lte_i16x2: write(o, V{id}, "= lte_i16x2", V{x}, V{y}); break; + case Op:: gt_i16x2: write(o, V{id}, "= gt_i16x2", V{x}, V{y}); break; + case Op::gte_i16x2: write(o, V{id}, "= gte_i16x2", V{x}, V{y}); break; + + case Op::bit_and : write(o, V{id}, "= bit_and" , V{x}, V{y} ); break; + case Op::bit_or : write(o, V{id}, "= bit_or" , V{x}, V{y} ); break; + case Op::bit_xor : write(o, V{id}, "= bit_xor" , V{x}, V{y} ); break; + case Op::bit_clear: write(o, V{id}, "= bit_clear", V{x}, V{y} ); break; + case Op::select : write(o, V{id}, "= select" , V{x}, V{y}, V{z}); break; + + case Op::bytes: write(o, V{id}, "= bytes", V{x}, Hex{imm}); break; + case Op::extract: write(o, V{id}, "= extract", V{x}, Shift{imm}, V{y}); break; + case Op::pack: write(o, V{id}, "= pack", V{x}, V{y}, Shift{imm}); break; + + case Op::to_f32: write(o, V{id}, "= to_f32", V{x}); break; + case Op::to_i32: write(o, V{id}, "= to_i32", V{x}); break; + } + + write(o, "\n"); + } + } + + void Builder::dump(SkWStream* o) const { + SkDebugfStream debug; + if (!o) { o = &debug; } + + o->writeDecAsText(fProgram.size()); + o->writeText(" values:\n"); + dump_builder_program(fProgram, o); + } + + void Program::dump(SkWStream* o) const { + SkDebugfStream debug; + if (!o) { o = &debug; } + + o->writeDecAsText(fRegs); + o->writeText(" registers, "); + o->writeDecAsText(fInstructions.size()); + o->writeText(" instructions:\n"); + for (int i = 0; i < (int)fInstructions.size(); i++) { + if (i == fLoop) { + write(o, "loop:\n"); + } + const Program::Instruction& inst = fInstructions[i]; + Op op = inst.op; + Reg d = inst.d, + x = inst.x, + y = inst.y, + z = inst.z; + int imm = inst.imm; + switch (op) { + case Op::store8: write(o, "store8" , Arg{imm}, R{x}); break; + case Op::store16: write(o, "store16", Arg{imm}, R{x}); break; + case Op::store32: write(o, "store32", Arg{imm}, R{x}); break; + + case Op::load8: write(o, R{d}, "= load8" , Arg{imm}); break; + case Op::load16: write(o, R{d}, "= load16", Arg{imm}); break; + case Op::load32: write(o, R{d}, "= load32", Arg{imm}); break; + + case Op::gather8: write(o, R{d}, "= gather8" , Arg{imm}, R{x}); break; + case Op::gather16: write(o, R{d}, "= gather16", Arg{imm}, R{x}); break; + case Op::gather32: write(o, R{d}, "= gather32", Arg{imm}, R{x}); break; + + case Op::uniform8: write(o, R{d}, "= uniform8" , Arg{imm & 0xffff}, Hex{imm>>16}); break; + case Op::uniform16: write(o, R{d}, "= uniform16", Arg{imm & 0xffff}, Hex{imm>>16}); break; + case Op::uniform32: write(o, R{d}, "= uniform32", Arg{imm & 0xffff}, Hex{imm>>16}); break; + + case Op::splat: write(o, R{d}, "= splat", Splat{imm}); break; + + + case Op::add_f32: write(o, R{d}, "= add_f32", R{x}, R{y} ); break; + case Op::sub_f32: write(o, R{d}, "= sub_f32", R{x}, R{y} ); break; + case Op::mul_f32: write(o, R{d}, "= mul_f32", R{x}, R{y} ); break; + case Op::div_f32: write(o, R{d}, "= div_f32", R{x}, R{y} ); break; + case Op::mad_f32: write(o, R{d}, "= mad_f32", R{x}, R{y}, R{z}); break; + + case Op:: eq_f32: write(o, R{d}, "= eq_f32", R{x}, R{y}); break; + case Op::neq_f32: write(o, R{d}, "= neq_f32", R{x}, R{y}); break; + case Op:: lt_f32: write(o, R{d}, "= lt_f32", R{x}, R{y}); break; + case Op::lte_f32: write(o, R{d}, "= lte_f32", R{x}, R{y}); break; + case Op:: gt_f32: write(o, R{d}, "= gt_f32", R{x}, R{y}); break; + case Op::gte_f32: write(o, R{d}, "= gte_f32", R{x}, R{y}); break; + + + case Op::add_i32: write(o, R{d}, "= add_i32", R{x}, R{y}); break; + case Op::sub_i32: write(o, R{d}, "= sub_i32", R{x}, R{y}); break; + case Op::mul_i32: write(o, R{d}, "= mul_i32", R{x}, R{y}); break; + + case Op::shl_i32: write(o, R{d}, "= shl_i32", R{x}, Shift{imm}); break; + case Op::shr_i32: write(o, R{d}, "= shr_i32", R{x}, Shift{imm}); break; + case Op::sra_i32: write(o, R{d}, "= sra_i32", R{x}, Shift{imm}); break; + + case Op:: eq_i32: write(o, R{d}, "= eq_i32", R{x}, R{y}); break; + case Op::neq_i32: write(o, R{d}, "= neq_i32", R{x}, R{y}); break; + case Op:: lt_i32: write(o, R{d}, "= lt_i32", R{x}, R{y}); break; + case Op::lte_i32: write(o, R{d}, "= lte_i32", R{x}, R{y}); break; + case Op:: gt_i32: write(o, R{d}, "= gt_i32", R{x}, R{y}); break; + case Op::gte_i32: write(o, R{d}, "= gte_i32", R{x}, R{y}); break; + + + case Op::add_i16x2: write(o, R{d}, "= add_i16x2", R{x}, R{y}); break; + case Op::sub_i16x2: write(o, R{d}, "= sub_i16x2", R{x}, R{y}); break; + case Op::mul_i16x2: write(o, R{d}, "= mul_i16x2", R{x}, R{y}); break; + + case Op::shl_i16x2: write(o, R{d}, "= shl_i16x2", R{x}, Shift{imm}); break; + case Op::shr_i16x2: write(o, R{d}, "= shr_i16x2", R{x}, Shift{imm}); break; + case Op::sra_i16x2: write(o, R{d}, "= sra_i16x2", R{x}, Shift{imm}); break; + + case Op:: eq_i16x2: write(o, R{d}, "= eq_i16x2", R{x}, R{y}); break; + case Op::neq_i16x2: write(o, R{d}, "= neq_i16x2", R{x}, R{y}); break; + case Op:: lt_i16x2: write(o, R{d}, "= lt_i16x2", R{x}, R{y}); break; + case Op::lte_i16x2: write(o, R{d}, "= lte_i16x2", R{x}, R{y}); break; + case Op:: gt_i16x2: write(o, R{d}, "= gt_i16x2", R{x}, R{y}); break; + case Op::gte_i16x2: write(o, R{d}, "= gte_i16x2", R{x}, R{y}); break; + + + case Op::bit_and : write(o, R{d}, "= bit_and" , R{x}, R{y} ); break; + case Op::bit_or : write(o, R{d}, "= bit_or" , R{x}, R{y} ); break; + case Op::bit_xor : write(o, R{d}, "= bit_xor" , R{x}, R{y} ); break; + case Op::bit_clear: write(o, R{d}, "= bit_clear", R{x}, R{y} ); break; + case Op::select : write(o, R{d}, "= select" , R{x}, R{y}, R{z}); break; + + case Op::bytes: write(o, R{d}, "= bytes", R{x}, Hex{imm}); break; + case Op::extract: write(o, R{d}, "= extract", R{x}, Shift{imm}, R{y}); break; + case Op::pack: write(o, R{d}, "= pack", R{x}, R{y}, Shift{imm}); break; + + case Op::to_f32: write(o, R{d}, "= to_f32", R{x}); break; + case Op::to_i32: write(o, R{d}, "= to_i32", R{x}); break; + } + write(o, "\n"); + } + } + + // Builder -> Program, with liveness and loop hoisting analysis. + + Program Builder::done(const char* debug_name) { + // Basic liveness analysis: + // an instruction is live until all live instructions that need its input have retired. + for (Val id = fProgram.size(); id --> 0; ) { + Instruction& inst = fProgram[id]; + // All side-effect-only instructions (stores) are live. + if (inst.op <= Op::store32) { + inst.death = id; + } + // The arguments of a live instruction must live until at least that instruction. + if (inst.death != 0) { + // Notice how we're walking backward, storing the latest instruction in death. + if (inst.x != NA && fProgram[inst.x].death == 0) { fProgram[inst.x].death = id; } + if (inst.y != NA && fProgram[inst.y].death == 0) { fProgram[inst.y].death = id; } + if (inst.z != NA && fProgram[inst.z].death == 0) { fProgram[inst.z].death = id; } + } + } + + // Mark which values don't depend on the loop and can be hoisted. + for (Val id = 0; id < (Val)fProgram.size(); id++) { + Builder::Instruction& inst = fProgram[id]; + + // Varying loads (and gathers) and stores cannot be hoisted out of the loop. + if (inst.op <= Op::gather32) { + inst.can_hoist = false; + } + + // If any of an instruction's inputs can't be hoisted, it can't be hoisted itself. + if (inst.can_hoist) { + if (inst.x != NA) { inst.can_hoist &= fProgram[inst.x].can_hoist; } + if (inst.y != NA) { inst.can_hoist &= fProgram[inst.y].can_hoist; } + if (inst.z != NA) { inst.can_hoist &= fProgram[inst.z].can_hoist; } + } + + // We'll want to know if hoisted values are used in the loop; + // if not, we can recycle their registers like we do loop values. + if (!inst.can_hoist /*i.e. we're in the loop, so the arguments are used_in_loop*/) { + if (inst.x != NA) { fProgram[inst.x].used_in_loop = true; } + if (inst.y != NA) { fProgram[inst.y].used_in_loop = true; } + if (inst.z != NA) { fProgram[inst.z].used_in_loop = true; } + } + } + + return {fProgram, fStrides, debug_name}; + } + + // TODO: it's probably not important that we include post-Builder::done() fields like + // death, can_hoist, and used_in_loop in operator==() and InstructionHash::operator(). + // They'll always have the same, initial values as set in Builder::push(). + + static bool operator==(const Builder::Instruction& a, const Builder::Instruction& b) { + return a.op == b.op + && a.x == b.x + && a.y == b.y + && a.z == b.z + && a.imm == b.imm + && a.death == b.death + && a.can_hoist == b.can_hoist + && a.used_in_loop == b.used_in_loop; + } + + // TODO: replace with SkOpts::hash()? + size_t Builder::InstructionHash::operator()(const Instruction& inst) const { + return Hash((uint8_t)inst.op) + ^ Hash(inst.x) + ^ Hash(inst.y) + ^ Hash(inst.z) + ^ Hash(inst.imm) + ^ Hash(inst.death) + ^ Hash(inst.can_hoist) + ^ Hash(inst.used_in_loop); + } + + // Most instructions produce a value and return it by ID, + // the value-producing instruction's own index in the program vector. + Val Builder::push(Op op, Val x, Val y, Val z, int imm) { + Instruction inst{op, x, y, z, imm, + /*death=*/0, /*can_hoist=*/true, /*used_in_loop=*/false}; + + // Basic common subexpression elimination: + // if we've already seen this exact Instruction, use it instead of creating a new one. + if (Val* id = fIndex.find(inst)) { + return *id; + } + Val id = static_cast(fProgram.size()); + fProgram.push_back(inst); + fIndex.set(inst, id); + return id; + } + + bool Builder::isZero(Val id) const { + return fProgram[id].op == Op::splat + && fProgram[id].imm == 0; + } + + Arg Builder::arg(int stride) { + int ix = (int)fStrides.size(); + fStrides.push_back(stride); + return {ix}; + } + + void Builder::store8 (Arg ptr, I32 val) { (void)this->push(Op::store8 , val.id,NA,NA, ptr.ix); } + void Builder::store16(Arg ptr, I32 val) { (void)this->push(Op::store16, val.id,NA,NA, ptr.ix); } + void Builder::store32(Arg ptr, I32 val) { (void)this->push(Op::store32, val.id,NA,NA, ptr.ix); } + + I32 Builder::load8 (Arg ptr) { return {this->push(Op::load8 , NA,NA,NA, ptr.ix) }; } + I32 Builder::load16(Arg ptr) { return {this->push(Op::load16, NA,NA,NA, ptr.ix) }; } + I32 Builder::load32(Arg ptr) { return {this->push(Op::load32, NA,NA,NA, ptr.ix) }; } + + I32 Builder::gather8 (Arg ptr, I32 offset) { + return {this->push(Op::gather8 , offset.id,NA,NA, ptr.ix)}; + } + I32 Builder::gather16(Arg ptr, I32 offset) { + return {this->push(Op::gather16, offset.id,NA,NA, ptr.ix)}; + } + I32 Builder::gather32(Arg ptr, I32 offset) { + return {this->push(Op::gather32, offset.id,NA,NA, ptr.ix)}; + } + + I32 Builder::uniform8(Arg ptr, int offset) { + return {this->push(Op::uniform8, NA,NA,NA, ptr.ix | (offset<<16))}; + } + I32 Builder::uniform16(Arg ptr, int offset) { + return {this->push(Op::uniform16, NA,NA,NA, ptr.ix | (offset<<16))}; + } + I32 Builder::uniform32(Arg ptr, int offset) { + return {this->push(Op::uniform32, NA,NA,NA, ptr.ix | (offset<<16))}; + } + + // The two splat() functions are just syntax sugar over splatting a 4-byte bit pattern. + I32 Builder::splat(int n) { return {this->push(Op::splat, NA,NA,NA, n) }; } + F32 Builder::splat(float f) { + int bits; + memcpy(&bits, &f, 4); + return {this->push(Op::splat, NA,NA,NA, bits)}; + } + + F32 Builder::add(F32 x, F32 y ) { return {this->push(Op::add_f32, x.id, y.id)}; } + F32 Builder::sub(F32 x, F32 y ) { return {this->push(Op::sub_f32, x.id, y.id)}; } + F32 Builder::mul(F32 x, F32 y ) { return {this->push(Op::mul_f32, x.id, y.id)}; } + F32 Builder::div(F32 x, F32 y ) { return {this->push(Op::div_f32, x.id, y.id)}; } + F32 Builder::mad(F32 x, F32 y, F32 z) { + if (this->isZero(z.id)) { + return this->mul(x,y); + } + return {this->push(Op::mad_f32, x.id, y.id, z.id)}; + } + + I32 Builder::add(I32 x, I32 y) { return {this->push(Op::add_i32, x.id, y.id)}; } + I32 Builder::sub(I32 x, I32 y) { return {this->push(Op::sub_i32, x.id, y.id)}; } + I32 Builder::mul(I32 x, I32 y) { return {this->push(Op::mul_i32, x.id, y.id)}; } + + I32 Builder::add_16x2(I32 x, I32 y) { return {this->push(Op::add_i16x2, x.id, y.id)}; } + I32 Builder::sub_16x2(I32 x, I32 y) { return {this->push(Op::sub_i16x2, x.id, y.id)}; } + I32 Builder::mul_16x2(I32 x, I32 y) { return {this->push(Op::mul_i16x2, x.id, y.id)}; } + + I32 Builder::shl(I32 x, int bits) { return {this->push(Op::shl_i32, x.id,NA,NA, bits)}; } + I32 Builder::shr(I32 x, int bits) { return {this->push(Op::shr_i32, x.id,NA,NA, bits)}; } + I32 Builder::sra(I32 x, int bits) { return {this->push(Op::sra_i32, x.id,NA,NA, bits)}; } + + I32 Builder::shl_16x2(I32 x, int bits) { return {this->push(Op::shl_i16x2, x.id,NA,NA, bits)}; } + I32 Builder::shr_16x2(I32 x, int bits) { return {this->push(Op::shr_i16x2, x.id,NA,NA, bits)}; } + I32 Builder::sra_16x2(I32 x, int bits) { return {this->push(Op::sra_i16x2, x.id,NA,NA, bits)}; } + + I32 Builder:: eq(F32 x, F32 y) { return {this->push(Op:: eq_f32, x.id, y.id)}; } + I32 Builder::neq(F32 x, F32 y) { return {this->push(Op::neq_f32, x.id, y.id)}; } + I32 Builder:: lt(F32 x, F32 y) { return {this->push(Op:: lt_f32, x.id, y.id)}; } + I32 Builder::lte(F32 x, F32 y) { return {this->push(Op::lte_f32, x.id, y.id)}; } + I32 Builder:: gt(F32 x, F32 y) { return {this->push(Op:: gt_f32, x.id, y.id)}; } + I32 Builder::gte(F32 x, F32 y) { return {this->push(Op::gte_f32, x.id, y.id)}; } + + I32 Builder:: eq(I32 x, I32 y) { return {this->push(Op:: eq_i32, x.id, y.id)}; } + I32 Builder::neq(I32 x, I32 y) { return {this->push(Op::neq_i32, x.id, y.id)}; } + I32 Builder:: lt(I32 x, I32 y) { return {this->push(Op:: lt_i32, x.id, y.id)}; } + I32 Builder::lte(I32 x, I32 y) { return {this->push(Op::lte_i32, x.id, y.id)}; } + I32 Builder:: gt(I32 x, I32 y) { return {this->push(Op:: gt_i32, x.id, y.id)}; } + I32 Builder::gte(I32 x, I32 y) { return {this->push(Op::gte_i32, x.id, y.id)}; } + + I32 Builder:: eq_16x2(I32 x, I32 y) { return {this->push(Op:: eq_i16x2, x.id, y.id)}; } + I32 Builder::neq_16x2(I32 x, I32 y) { return {this->push(Op::neq_i16x2, x.id, y.id)}; } + I32 Builder:: lt_16x2(I32 x, I32 y) { return {this->push(Op:: lt_i16x2, x.id, y.id)}; } + I32 Builder::lte_16x2(I32 x, I32 y) { return {this->push(Op::lte_i16x2, x.id, y.id)}; } + I32 Builder:: gt_16x2(I32 x, I32 y) { return {this->push(Op:: gt_i16x2, x.id, y.id)}; } + I32 Builder::gte_16x2(I32 x, I32 y) { return {this->push(Op::gte_i16x2, x.id, y.id)}; } + + I32 Builder::bit_and (I32 x, I32 y) { return {this->push(Op::bit_and , x.id, y.id)}; } + I32 Builder::bit_or (I32 x, I32 y) { return {this->push(Op::bit_or , x.id, y.id)}; } + I32 Builder::bit_xor (I32 x, I32 y) { return {this->push(Op::bit_xor , x.id, y.id)}; } + I32 Builder::bit_clear(I32 x, I32 y) { return {this->push(Op::bit_clear, x.id, y.id)}; } + I32 Builder::select(I32 x, I32 y, I32 z) { return {this->push(Op::select, x.id, y.id, z.id)}; } + + + I32 Builder::extract(I32 x, int bits, I32 y) { + return {this->push(Op::extract, x.id,y.id,NA, bits)}; + } + + I32 Builder::pack(I32 x, I32 y, int bits) { + return {this->push(Op::pack, x.id,y.id,NA, bits)}; + } + + I32 Builder::bytes(I32 x, int control) { + return {this->push(Op::bytes, x.id,NA,NA, control)}; + } + + F32 Builder::to_f32(I32 x) { return {this->push(Op::to_f32, x.id)}; } + I32 Builder::to_i32(F32 x) { return {this->push(Op::to_i32, x.id)}; } + + // ~~~~ Program::eval() and co. ~~~~ // + + // Handy references for x86-64 instruction encoding: + // https://wiki.osdev.org/X86-64_Instruction_Encoding + // https://www-user.tu-chemnitz.de/~heha/viewchm.php/hs/x86.chm/x64.htm + // https://www-user.tu-chemnitz.de/~heha/viewchm.php/hs/x86.chm/x86.htm + // http://ref.x86asm.net/coder64.html + + // Used for ModRM / immediate instruction encoding. + static uint8_t _233(int a, int b, int c) { + return (a & 3) << 6 + | (b & 7) << 3 + | (c & 7) << 0; + } + + // ModRM byte encodes the arguments of an opcode. + enum class Mod { Indirect, OneByteImm, FourByteImm, Direct }; + static uint8_t mod_rm(Mod mod, int reg, int rm) { + return _233((int)mod, reg, rm); + } + + static Mod mod(int imm) { + if (imm == 0) { return Mod::Indirect; } + if (SkTFitsIn(imm)) { return Mod::OneByteImm; } + return Mod::FourByteImm; + } + + static int imm_bytes(Mod mod) { + switch (mod) { + case Mod::Indirect: return 0; + case Mod::OneByteImm: return 1; + case Mod::FourByteImm: return 4; + case Mod::Direct: SkUNREACHABLE; + } + SkUNREACHABLE; + } + +#if 0 + // SIB byte encodes a memory address, base + (index * scale). + enum class Scale { One, Two, Four, Eight }; + static uint8_t sib(Scale scale, int index, int base) { + return _233((int)scale, index, base); + } +#endif + + // The REX prefix is used to extend most old 32-bit instructions to 64-bit. + static uint8_t rex(bool W, // If set, operation is 64-bit, otherwise default, usually 32-bit. + bool R, // Extra top bit to select ModRM reg, registers 8-15. + bool X, // Extra top bit for SIB index register. + bool B) { // Extra top bit for SIB base or ModRM rm register. + return 0b01000000 // Fixed 0100 for top four bits. + | (W << 3) + | (R << 2) + | (X << 1) + | (B << 0); + } + + + // The VEX prefix extends SSE operations to AVX. Used generally, even with XMM. + struct VEX { + int len; + uint8_t bytes[3]; + }; + + static VEX vex(bool WE, // Like REX W for int operations, or opcode extension for float? + bool R, // Same as REX R. Pass high bit of dst register, dst>>3. + bool X, // Same as REX X. + bool B, // Same as REX B. Pass y>>3 for 3-arg ops, x>>3 for 2-arg. + int map, // SSE opcode map selector: 0x0f, 0x380f, 0x3a0f. + int vvvv, // 4-bit second operand register. Pass our x for 3-arg ops. + bool L, // Set for 256-bit ymm operations, off for 128-bit xmm. + int pp) { // SSE mandatory prefix: 0x66, 0xf3, 0xf2, else none. + + // Pack x86 opcode map selector to 5-bit VEX encoding. + map = [map]{ + switch (map) { + case 0x0f: return 0b00001; + case 0x380f: return 0b00010; + case 0x3a0f: return 0b00011; + // Several more cases only used by XOP / TBM. + } + SkUNREACHABLE; + }(); + + // Pack mandatory SSE opcode prefix byte to 2-bit VEX encoding. + pp = [pp]{ + switch (pp) { + case 0x66: return 0b01; + case 0xf3: return 0b10; + case 0xf2: return 0b11; + } + return 0b00; + }(); + + VEX vex = {0, {0,0,0}}; + if (X == 0 && B == 0 && WE == 0 && map == 0b00001) { + // With these conditions met, we can optionally compress VEX to 2-byte. + vex.len = 2; + vex.bytes[0] = 0xc5; + vex.bytes[1] = (pp & 3) << 0 + | (L & 1) << 2 + | (~vvvv & 15) << 3 + | (~(int)R & 1) << 7; + } else { + // We could use this 3-byte VEX prefix all the time if we like. + vex.len = 3; + vex.bytes[0] = 0xc4; + vex.bytes[1] = (map & 31) << 0 + | (~(int)B & 1) << 5 + | (~(int)X & 1) << 6 + | (~(int)R & 1) << 7; + vex.bytes[2] = (pp & 3) << 0 + | (L & 1) << 2 + | (~vvvv & 15) << 3 + | (WE & 1) << 7; + } + return vex; + } + + Assembler::Assembler(void* buf) : fCode((uint8_t*)buf), fCurr(fCode), fSize(0) {} + + size_t Assembler::size() const { return fSize; } + + void Assembler::bytes(const void* p, int n) { + if (fCurr) { + memcpy(fCurr, p, n); + fCurr += n; + } + fSize += n; + } + + void Assembler::byte(uint8_t b) { this->bytes(&b, 1); } + void Assembler::word(uint32_t w) { this->bytes(&w, 4); } + + void Assembler::align(int mod) { + while (this->size() % mod) { + this->byte(0x00); + } + } + + void Assembler::vzeroupper() { + this->byte(0xc5); + this->byte(0xf8); + this->byte(0x77); + } + void Assembler::ret() { this->byte(0xc3); } + + // Common instruction building for 64-bit opcodes with an immediate argument. + void Assembler::op(int opcode, int opcode_ext, GP64 dst, int imm) { + opcode |= 0b0000'0001; // low bit set for 64-bit operands + opcode |= 0b1000'0000; // top bit set for instructions with any immediate + + int imm_bytes = 4; + if (SkTFitsIn(imm)) { + imm_bytes = 1; + opcode |= 0b0000'0010; // second bit set for 8-bit immediate, else 32-bit. + } + + this->byte(rex(1,0,0,dst>>3)); + this->byte(opcode); + this->byte(mod_rm(Mod::Direct, opcode_ext, dst&7)); + this->bytes(&imm, imm_bytes); + } + + void Assembler::add(GP64 dst, int imm) { this->op(0,0b000, dst,imm); } + void Assembler::sub(GP64 dst, int imm) { this->op(0,0b101, dst,imm); } + void Assembler::cmp(GP64 reg, int imm) { this->op(0,0b111, reg,imm); } + + void Assembler::op(int prefix, int map, int opcode, Ymm dst, Ymm x, Ymm y, bool W/*=false*/) { + VEX v = vex(W, dst>>3, 0, y>>3, + map, x, 1/*ymm, not xmm*/, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Direct, dst&7, y&7)); + } + + void Assembler::vpaddd (Ymm dst, Ymm x, Ymm y) { this->op(0x66, 0x0f,0xfe, dst,x,y); } + void Assembler::vpsubd (Ymm dst, Ymm x, Ymm y) { this->op(0x66, 0x0f,0xfa, dst,x,y); } + void Assembler::vpmulld(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x380f,0x40, dst,x,y); } + + void Assembler::vpsubw (Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0xf9, dst,x,y); } + void Assembler::vpmullw(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0xd5, dst,x,y); } + + void Assembler::vpand (Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0xdb, dst,x,y); } + void Assembler::vpor (Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0xeb, dst,x,y); } + void Assembler::vpxor (Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0xef, dst,x,y); } + void Assembler::vpandn(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0xdf, dst,x,y); } + + void Assembler::vaddps(Ymm dst, Ymm x, Ymm y) { this->op(0,0x0f,0x58, dst,x,y); } + void Assembler::vsubps(Ymm dst, Ymm x, Ymm y) { this->op(0,0x0f,0x5c, dst,x,y); } + void Assembler::vmulps(Ymm dst, Ymm x, Ymm y) { this->op(0,0x0f,0x59, dst,x,y); } + void Assembler::vdivps(Ymm dst, Ymm x, Ymm y) { this->op(0,0x0f,0x5e, dst,x,y); } + + void Assembler::vfmadd132ps(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x380f,0x98, dst,x,y); } + void Assembler::vfmadd213ps(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x380f,0xa8, dst,x,y); } + void Assembler::vfmadd231ps(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x380f,0xb8, dst,x,y); } + + void Assembler::vpackusdw(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x380f,0x2b, dst,x,y); } + void Assembler::vpackuswb(Ymm dst, Ymm x, Ymm y) { this->op(0x66, 0x0f,0x67, dst,x,y); } + + void Assembler::vpcmpeqd(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0x76, dst,x,y); } + void Assembler::vpcmpgtd(Ymm dst, Ymm x, Ymm y) { this->op(0x66,0x0f,0x66, dst,x,y); } + + void Assembler::vpblendvb(Ymm dst, Ymm x, Ymm y, Ymm z) { + int prefix = 0x66, + map = 0x3a0f, + opcode = 0x4c; + VEX v = vex(0, dst>>3, 0, y>>3, + map, x, /*ymm?*/1, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Direct, dst&7, y&7)); + this->byte(z << 4); + } + + // dst = x op /opcode_ext imm + void Assembler::op(int prefix, int map, int opcode, int opcode_ext, Ymm dst, Ymm x, int imm) { + // This is a little weird, but if we pass the opcode_ext as if it were the dst register, + // the dst register as if x, and the x register as if y, all the bits end up where we want. + this->op(prefix, map, opcode, (Ymm)opcode_ext,dst,x); + this->byte(imm); + } + + void Assembler::vpslld(Ymm dst, Ymm x, int imm) { this->op(0x66,0x0f,0x72,6, dst,x,imm); } + void Assembler::vpsrld(Ymm dst, Ymm x, int imm) { this->op(0x66,0x0f,0x72,2, dst,x,imm); } + void Assembler::vpsrad(Ymm dst, Ymm x, int imm) { this->op(0x66,0x0f,0x72,4, dst,x,imm); } + + void Assembler::vpsrlw(Ymm dst, Ymm x, int imm) { this->op(0x66,0x0f,0x71,2, dst,x,imm); } + + + void Assembler::vpermq(Ymm dst, Ymm x, int imm) { + // A bit unusual among the instructions we use, this is 64-bit operation, so we set W. + bool W = true; + this->op(0x66,0x3a0f,0x00, dst,x,W); + this->byte(imm); + } + + void Assembler::vmovdqa(Ymm dst, Ymm src) { this->op(0x66,0x0f,0x6f, dst,src); } + + void Assembler::vcvtdq2ps (Ymm dst, Ymm x) { this->op(0, 0x0f,0x5b, dst,x); } + void Assembler::vcvttps2dq(Ymm dst, Ymm x) { this->op(0xf3,0x0f,0x5b, dst,x); } + + Assembler::Label Assembler::here() { + return { (int)this->size(), Label::None, {} }; + } + + int Assembler::disp19(Label* l) { + SkASSERT(l->kind == Label::None || + l->kind == Label::ARMDisp19); + l->kind = Label::ARMDisp19; + l->references.push_back(here().offset); + // ARM 19-bit instruction count, from the beginning of this instruction. + return (l->offset - here().offset) / 4; + } + + int Assembler::disp32(Label* l) { + SkASSERT(l->kind == Label::None || + l->kind == Label::X86Disp32); + l->kind = Label::X86Disp32; + l->references.push_back(here().offset); + // x86 32-bit byte count, from the end of this instruction. + return l->offset - (here().offset + 4); + } + + void Assembler::op(int prefix, int map, int opcode, Ymm dst, Ymm x, Label* l) { + // IP-relative addressing uses Mod::Indirect with the R/M encoded as-if rbp or r13. + const int rip = rbp; + + VEX v = vex(0, dst>>3, 0, rip>>3, + map, x, /*ymm?*/1, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, dst&7, rip&7)); + this->word(this->disp32(l)); + } + + void Assembler::vpshufb(Ymm dst, Ymm x, Label* l) { this->op(0x66,0x380f,0x00, dst,x,l); } + + void Assembler::vbroadcastss(Ymm dst, Label* l) { this->op(0x66,0x380f,0x18, dst, (Ymm)0, l); } + void Assembler::vbroadcastss(Ymm dst, Xmm src) { this->op(0x66,0x380f,0x18, dst, (Ymm)src); } + void Assembler::vbroadcastss(Ymm dst, GP64 ptr, int off) { + int prefix = 0x66, + map = 0x380f, + opcode = 0x18; + VEX v = vex(0, dst>>3, 0, ptr>>3, + map, 0, /*ymm?*/1, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + + this->byte(mod_rm(mod(off), dst&7, ptr&7)); + this->bytes(&off, imm_bytes(mod(off))); + } + + void Assembler::jump(uint8_t condition, Label* l) { + // These conditional jumps can be either 2 bytes (short) or 6 bytes (near): + // 7? one-byte-disp + // 0F 8? four-byte-disp + // We always use the near displacement to make updating labels simpler (no resizing). + this->byte(0x0f); + this->byte(condition); + this->word(this->disp32(l)); + } + void Assembler::je (Label* l) { this->jump(0x84, l); } + void Assembler::jne(Label* l) { this->jump(0x85, l); } + void Assembler::jl (Label* l) { this->jump(0x8c, l); } + + void Assembler::jmp(Label* l) { + // Like above in jump(), we could use 8-bit displacement here, but always use 32-bit. + this->byte(0xe9); + this->word(this->disp32(l)); + } + + void Assembler::load_store(int prefix, int map, int opcode, Ymm ymm, GP64 ptr) { + VEX v = vex(0, ymm>>3, 0, ptr>>3, + map, 0, /*ymm?*/1, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, ymm&7, ptr&7)); + } + + void Assembler::vmovups (Ymm dst, GP64 src) { this->load_store(0 , 0x0f,0x10, dst,src); } + void Assembler::vpmovzxwd(Ymm dst, GP64 src) { this->load_store(0x66,0x380f,0x33, dst,src); } + void Assembler::vpmovzxbd(Ymm dst, GP64 src) { this->load_store(0x66,0x380f,0x31, dst,src); } + + void Assembler::vmovups (GP64 dst, Ymm src) { this->load_store(0 , 0x0f,0x11, src,dst); } + void Assembler::vmovups (GP64 dst, Xmm src) { + // Same as vmovups(GP64,YMM) and load_store() except ymm? is 0. + int prefix = 0, + map = 0x0f, + opcode = 0x11; + VEX v = vex(0, src>>3, 0, dst>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, src&7, dst&7)); + } + + void Assembler::vmovq(GP64 dst, Xmm src) { + int prefix = 0x66, + map = 0x0f, + opcode = 0xd6; + VEX v = vex(0, src>>3, 0, dst>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, src&7, dst&7)); + } + + void Assembler::vmovd(GP64 dst, Xmm src) { + int prefix = 0x66, + map = 0x0f, + opcode = 0x7e; + VEX v = vex(0, src>>3, 0, dst>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, src&7, dst&7)); + } + + void Assembler::vmovd_direct(GP64 dst, Xmm src) { + int prefix = 0x66, + map = 0x0f, + opcode = 0x7e; + VEX v = vex(0, src>>3, 0, dst>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Direct, src&7, dst&7)); + } + + void Assembler::vmovd(Xmm dst, GP64 src) { + int prefix = 0x66, + map = 0x0f, + opcode = 0x6e; + VEX v = vex(0, dst>>3, 0, src>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, dst&7, src&7)); + } + + void Assembler::vmovd_direct(Xmm dst, GP64 src) { + int prefix = 0x66, + map = 0x0f, + opcode = 0x6e; + VEX v = vex(0, dst>>3, 0, src>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Direct, dst&7, src&7)); + } + + void Assembler::movzbl(GP64 dst, GP64 src, int off) { + if ((dst>>3) || (src>>3)) { + this->byte(rex(0,dst>>3,0,src>>3)); + } + this->byte(0x0f); + this->byte(0xb6); + this->byte(mod_rm(mod(off), dst&7, src&7)); + this->bytes(&off, imm_bytes(mod(off))); + } + + + void Assembler::movb(GP64 dst, GP64 src) { + if ((dst>>3) || (src>>3)) { + this->byte(rex(0,src>>3,0,dst>>3)); + } + this->byte(0x88); + this->byte(mod_rm(Mod::Indirect, src&7, dst&7)); + } + + void Assembler::vpinsrw(Xmm dst, Xmm src, GP64 ptr, int imm) { + int prefix = 0x66, + map = 0x0f, + opcode = 0xc4; + VEX v = vex(0, dst>>3, 0, ptr>>3, + map, src, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, dst&7, ptr&7)); + this->byte(imm); + } + + void Assembler::vpinsrb(Xmm dst, Xmm src, GP64 ptr, int imm) { + int prefix = 0x66, + map = 0x3a0f, + opcode = 0x20; + VEX v = vex(0, dst>>3, 0, ptr>>3, + map, src, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, dst&7, ptr&7)); + this->byte(imm); + } + + void Assembler::vpextrw(GP64 ptr, Xmm src, int imm) { + int prefix = 0x66, + map = 0x3a0f, + opcode = 0x15; + + VEX v = vex(0, src>>3, 0, ptr>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, src&7, ptr&7)); + this->byte(imm); + } + void Assembler::vpextrb(GP64 ptr, Xmm src, int imm) { + int prefix = 0x66, + map = 0x3a0f, + opcode = 0x14; + + VEX v = vex(0, src>>3, 0, ptr>>3, + map, 0, /*ymm?*/0, prefix); + this->bytes(v.bytes, v.len); + this->byte(opcode); + this->byte(mod_rm(Mod::Indirect, src&7, ptr&7)); + this->byte(imm); + } + + // https://static.docs.arm.com/ddi0596/a/DDI_0596_ARM_a64_instruction_set_architecture.pdf + + static int operator"" _mask(unsigned long long bits) { return (1<<(int)bits)-1; } + + void Assembler::op(uint32_t hi, V m, uint32_t lo, V n, V d) { + this->word( (hi & 11_mask) << 21 + | (m & 5_mask) << 16 + | (lo & 6_mask) << 10 + | (n & 5_mask) << 5 + | (d & 5_mask) << 0); + } + + void Assembler::and16b(V d, V n, V m) { this->op(0b0'1'0'01110'00'1, m, 0b00011'1, n, d); } + void Assembler::orr16b(V d, V n, V m) { this->op(0b0'1'0'01110'10'1, m, 0b00011'1, n, d); } + void Assembler::eor16b(V d, V n, V m) { this->op(0b0'1'1'01110'00'1, m, 0b00011'1, n, d); } + void Assembler::bic16b(V d, V n, V m) { this->op(0b0'1'0'01110'01'1, m, 0b00011'1, n, d); } + void Assembler::bsl16b(V d, V n, V m) { this->op(0b0'1'1'01110'01'1, m, 0b00011'1, n, d); } + + void Assembler::add4s(V d, V n, V m) { this->op(0b0'1'0'01110'10'1, m, 0b10000'1, n, d); } + void Assembler::sub4s(V d, V n, V m) { this->op(0b0'1'1'01110'10'1, m, 0b10000'1, n, d); } + void Assembler::mul4s(V d, V n, V m) { this->op(0b0'1'0'01110'10'1, m, 0b10011'1, n, d); } + + void Assembler::cmeq4s(V d, V n, V m) { this->op(0b0'1'1'01110'10'1, m, 0b10001'1, n, d); } + void Assembler::cmgt4s(V d, V n, V m) { this->op(0b0'1'0'01110'10'1, m, 0b0011'0'1, n, d); } + + void Assembler::sub8h(V d, V n, V m) { this->op(0b0'1'1'01110'01'1, m, 0b10000'1, n, d); } + void Assembler::mul8h(V d, V n, V m) { this->op(0b0'1'0'01110'01'1, m, 0b10011'1, n, d); } + + void Assembler::fadd4s(V d, V n, V m) { this->op(0b0'1'0'01110'0'0'1, m, 0b11010'1, n, d); } + void Assembler::fsub4s(V d, V n, V m) { this->op(0b0'1'0'01110'1'0'1, m, 0b11010'1, n, d); } + void Assembler::fmul4s(V d, V n, V m) { this->op(0b0'1'1'01110'0'0'1, m, 0b11011'1, n, d); } + void Assembler::fdiv4s(V d, V n, V m) { this->op(0b0'1'1'01110'0'0'1, m, 0b11111'1, n, d); } + + void Assembler::fmla4s(V d, V n, V m) { this->op(0b0'1'0'01110'0'0'1, m, 0b11001'1, n, d); } + + void Assembler::tbl(V d, V n, V m) { this->op(0b0'1'001110'00'0, m, 0b0'00'0'00, n, d); } + + void Assembler::op(uint32_t op22, int imm, V n, V d) { + this->word( (op22 & 22_mask) << 10 + | imm << 16 // imm is embedded inside op, bit size depends on op + | (n & 5_mask) << 5 + | (d & 5_mask) << 0); + } + + void Assembler::sli4s(V d, V n, int imm) { + this->op(0b0'1'1'011110'0100'000'01010'1, ( imm&31), n, d); + } + void Assembler::shl4s(V d, V n, int imm) { + this->op(0b0'1'0'011110'0100'000'01010'1, ( imm&31), n, d); + } + void Assembler::sshr4s(V d, V n, int imm) { + this->op(0b0'1'0'011110'0100'000'00'0'0'0'1, (-imm&31), n, d); + } + void Assembler::ushr4s(V d, V n, int imm) { + this->op(0b0'1'1'011110'0100'000'00'0'0'0'1, (-imm&31), n, d); + } + void Assembler::ushr8h(V d, V n, int imm) { + this->op(0b0'1'1'011110'0010'000'00'0'0'0'1, (-imm&15), n, d); + } + + void Assembler::scvtf4s (V d, V n) { this->op(0b0'1'0'01110'0'0'10000'11101'10, n,d); } + void Assembler::fcvtzs4s(V d, V n) { this->op(0b0'1'0'01110'1'0'10000'1101'1'10, n,d); } + + void Assembler::xtns2h(V d, V n) { this->op(0b0'0'0'01110'01'10000'10010'10, n,d); } + void Assembler::xtnh2b(V d, V n) { this->op(0b0'0'0'01110'00'10000'10010'10, n,d); } + + void Assembler::uxtlb2h(V d, V n) { this->op(0b0'0'1'011110'0001'000'10100'1, n,d); } + void Assembler::uxtlh2s(V d, V n) { this->op(0b0'0'1'011110'0010'000'10100'1, n,d); } + + void Assembler::ret(X n) { + this->word(0b1101011'0'0'10'11111'0000'0'0 << 10 + | (n & 5_mask) << 5); + } + + void Assembler::add(X d, X n, int imm12) { + this->word(0b1'0'0'10001'00 << 22 + | (imm12 & 12_mask) << 10 + | (n & 5_mask) << 5 + | (d & 5_mask) << 0); + } + void Assembler::sub(X d, X n, int imm12) { + this->word( 0b1'1'0'10001'00 << 22 + | (imm12 & 12_mask) << 10 + | (n & 5_mask) << 5 + | (d & 5_mask) << 0); + } + void Assembler::subs(X d, X n, int imm12) { + this->word( 0b1'1'1'10001'00 << 22 + | (imm12 & 12_mask) << 10 + | (n & 5_mask) << 5 + | (d & 5_mask) << 0); + } + + void Assembler::b(Condition cond, Label* l) { + const int imm19 = this->disp19(l); + this->word( 0b0101010'0 << 24 + | (imm19 & 19_mask) << 5 + | ((int)cond & 4_mask) << 0); + } + void Assembler::cbz(X t, Label* l) { + const int imm19 = this->disp19(l); + this->word( 0b1'011010'0 << 24 + | (imm19 & 19_mask) << 5 + | (t & 5_mask) << 0); + } + void Assembler::cbnz(X t, Label* l) { + const int imm19 = this->disp19(l); + this->word( 0b1'011010'1 << 24 + | (imm19 & 19_mask) << 5 + | (t & 5_mask) << 0); + } + + void Assembler::ldrq(V dst, X src) { this->op(0b00'111'1'01'11'000000000000, src, dst); } + void Assembler::ldrs(V dst, X src) { this->op(0b10'111'1'01'01'000000000000, src, dst); } + void Assembler::ldrb(V dst, X src) { this->op(0b00'111'1'01'01'000000000000, src, dst); } + + void Assembler::strq(V src, X dst) { this->op(0b00'111'1'01'10'000000000000, dst, src); } + void Assembler::strs(V src, X dst) { this->op(0b10'111'1'01'00'000000000000, dst, src); } + void Assembler::strb(V src, X dst) { this->op(0b00'111'1'01'00'000000000000, dst, src); } + + void Assembler::ldrq(V dst, Label* l) { + const int imm19 = this->disp19(l); + this->word( 0b10'011'1'00 << 24 + | (imm19 & 19_mask) << 5 + | (dst & 5_mask) << 0); + } + + void Assembler::label(Label* l) { + if (fCode) { + // The instructions all currently point to l->offset. + // We'll want to add a delta to point them to here(). + int delta = here().offset - l->offset; + l->offset = here().offset; + + if (l->kind == Label::ARMDisp19) { + for (int ref : l->references) { + // ref points to a 32-bit instruction with 19-bit displacement in instructions. + uint32_t inst; + memcpy(&inst, fCode + ref, 4); + + // [ 8 bits to preserve] [ 19 bit signed displacement ] [ 5 bits to preserve ] + int disp = (int)(inst << 8) >> 13; + + disp += delta/4; // delta is in bytes, we want instructions. + + // Put it all back together, preserving the high 8 bits and low 5. + inst = ((disp << 5) & (19_mask << 5)) + | ((inst ) & ~(19_mask << 5)); + + memcpy(fCode + ref, &inst, 4); + } + } + + if (l->kind == Label::X86Disp32) { + for (int ref : l->references) { + // ref points to a 32-bit displacement in bytes. + int disp; + memcpy(&disp, fCode + ref, 4); + + disp += delta; + + memcpy(fCode + ref, &disp, 4); + } + } + } + } + + void Program::eval(int n, void* args[]) const { + const int nargs = (int)fStrides.size(); + + if (fJITBuf) { + void** a = args; + const void* b = fJITBuf; + switch (nargs) { + case 0: return ((void(*)(int ))b)(n ); + case 1: return ((void(*)(int,void* ))b)(n,a[0] ); + case 2: return ((void(*)(int,void*,void* ))b)(n,a[0],a[1] ); + case 3: return ((void(*)(int,void*,void*,void* ))b)(n,a[0],a[1],a[2] ); + case 4: return ((void(*)(int,void*,void*,void*,void*))b)(n,a[0],a[1],a[2],a[3]); + default: SkUNREACHABLE; // TODO + } + } + + // We'll operate in SIMT style, knocking off K-size chunks from n while possible. + constexpr int K = 16; + using I32 = skvx::Vec; + using F32 = skvx::Vec; + using U32 = skvx::Vec; + using U16 = skvx::Vec; + using U8 = skvx::Vec; + + using I16x2 = skvx::Vec<2*K, int16_t>; + using U16x2 = skvx::Vec<2*K, uint16_t>; + + union Slot { + F32 f32; + I32 i32; + U32 u32; + I16x2 i16x2; + U16x2 u16x2; + }; + + Slot few_regs[16]; + std::unique_ptr many_regs; + + Slot* regs = few_regs; + + if (fRegs > (int)SK_ARRAY_COUNT(few_regs)) { + // Annoyingly we can't trust that malloc() or new will work with Slot because + // the skvx::Vec types may have alignment greater than what they provide. + // We'll overallocate one extra register so we can align manually. + many_regs.reset(new char[ sizeof(Slot) * (fRegs + 1) ]); + + uintptr_t addr = (uintptr_t)many_regs.get(); + addr += alignof(Slot) - + (addr & (alignof(Slot) - 1)); + SkASSERT((addr & (alignof(Slot) - 1)) == 0); + regs = (Slot*)addr; + } + + + auto r = [&](Reg id) -> Slot& { + SkASSERT(0 <= id && id < fRegs); + return regs[id]; + }; + auto arg = [&](int ix) { + SkASSERT(0 <= ix && ix < nargs); + return args[ix]; + }; + + // Step each argument pointer ahead by its stride a number of times. + auto step_args = [&](int times) { + for (int i = 0; i < (int)fStrides.size(); i++) { + args[i] = (void*)( (char*)args[i] + times * fStrides[i] ); + } + }; + + int start = 0, + stride; + for ( ; n > 0; start = fLoop, n -= stride, step_args(stride)) { + stride = n >= K ? K : 1; + + for (int i = start; i < (int)fInstructions.size(); i++) { + Instruction inst = fInstructions[i]; + + // d = op(x,y,z/imm) + Reg d = inst.d, + x = inst.x, + y = inst.y, + z = inst.z; + int imm = inst.imm; + + // Ops that interact with memory need to know whether we're stride=1 or K, + // but all non-memory ops can run the same code no matter the stride. + switch (2*(int)inst.op + (stride == K ? 1 : 0)) { + default: SkUNREACHABLE; + + #define STRIDE_1(op) case 2*(int)op + #define STRIDE_K(op) case 2*(int)op + 1 + STRIDE_1(Op::store8 ): memcpy(arg(imm), &r(x).i32, 1); break; + STRIDE_1(Op::store16): memcpy(arg(imm), &r(x).i32, 2); break; + STRIDE_1(Op::store32): memcpy(arg(imm), &r(x).i32, 4); break; + + STRIDE_K(Op::store8 ): skvx::cast (r(x).i32).store(arg(imm)); break; + STRIDE_K(Op::store16): skvx::cast(r(x).i32).store(arg(imm)); break; + STRIDE_K(Op::store32): (r(x).i32).store(arg(imm)); break; + + STRIDE_1(Op::load8 ): r(d).i32 = 0; memcpy(&r(d).i32, arg(imm), 1); break; + STRIDE_1(Op::load16): r(d).i32 = 0; memcpy(&r(d).i32, arg(imm), 2); break; + STRIDE_1(Op::load32): r(d).i32 = 0; memcpy(&r(d).i32, arg(imm), 4); break; + + STRIDE_K(Op::load8 ): r(d).i32= skvx::cast(U8 ::Load(arg(imm))); break; + STRIDE_K(Op::load16): r(d).i32= skvx::cast(U16::Load(arg(imm))); break; + STRIDE_K(Op::load32): r(d).i32= I32::Load(arg(imm)) ; break; + + STRIDE_1(Op::gather8): + for (int i = 0; i < K; i++) { + r(d).i32[i] = (i == 0) ? ((const uint8_t* )arg(imm))[ r(x).i32[i] ] : 0; + } break; + STRIDE_1(Op::gather16): + for (int i = 0; i < K; i++) { + r(d).i32[i] = (i == 0) ? ((const uint16_t*)arg(imm))[ r(x).i32[i] ] : 0; + } break; + STRIDE_1(Op::gather32): + for (int i = 0; i < K; i++) { + r(d).i32[i] = (i == 0) ? ((const int* )arg(imm))[ r(x).i32[i] ] : 0; + } break; + + STRIDE_K(Op::gather8): + for (int i = 0; i < K; i++) { + r(d).i32[i] = ((const uint8_t* )arg(imm))[ r(x).i32[i] ]; + } break; + STRIDE_K(Op::gather16): + for (int i = 0; i < K; i++) { + r(d).i32[i] = ((const uint16_t*)arg(imm))[ r(x).i32[i] ]; + } break; + STRIDE_K(Op::gather32): + for (int i = 0; i < K; i++) { + r(d).i32[i] = ((const int* )arg(imm))[ r(x).i32[i] ]; + } break; + + #undef STRIDE_1 + #undef STRIDE_K + + // Ops that don't interact with memory should never care about the stride. + #define CASE(op) case 2*(int)op: /*fallthrough*/ case 2*(int)op+1 + + CASE(Op::uniform8): + r(d).i32 = *(const uint8_t* )( (const char*)arg(imm&0xffff) + (imm>>16) ); + break; + CASE(Op::uniform16): + r(d).i32 = *(const uint16_t*)( (const char*)arg(imm&0xffff) + (imm>>16) ); + break; + CASE(Op::uniform32): + r(d).i32 = *(const int* )( (const char*)arg(imm&0xffff) + (imm>>16) ); + break; + + CASE(Op::splat): r(d).i32 = imm; break; + + CASE(Op::add_f32): r(d).f32 = r(x).f32 + r(y).f32; break; + CASE(Op::sub_f32): r(d).f32 = r(x).f32 - r(y).f32; break; + CASE(Op::mul_f32): r(d).f32 = r(x).f32 * r(y).f32; break; + CASE(Op::div_f32): r(d).f32 = r(x).f32 / r(y).f32; break; + + CASE(Op::mad_f32): r(d).f32 = r(x).f32 * r(y).f32 + r(z).f32; break; + + CASE(Op::add_i32): r(d).i32 = r(x).i32 + r(y).i32; break; + CASE(Op::sub_i32): r(d).i32 = r(x).i32 - r(y).i32; break; + CASE(Op::mul_i32): r(d).i32 = r(x).i32 * r(y).i32; break; + + CASE(Op::add_i16x2): r(d).i16x2 = r(x).i16x2 + r(y).i16x2; break; + CASE(Op::sub_i16x2): r(d).i16x2 = r(x).i16x2 - r(y).i16x2; break; + CASE(Op::mul_i16x2): r(d).i16x2 = r(x).i16x2 * r(y).i16x2; break; + + CASE(Op::shl_i32): r(d).i32 = r(x).i32 << imm; break; + CASE(Op::sra_i32): r(d).i32 = r(x).i32 >> imm; break; + CASE(Op::shr_i32): r(d).u32 = r(x).u32 >> imm; break; + + CASE(Op::shl_i16x2): r(d).i16x2 = r(x).i16x2 << imm; break; + CASE(Op::sra_i16x2): r(d).i16x2 = r(x).i16x2 >> imm; break; + CASE(Op::shr_i16x2): r(d).u16x2 = r(x).u16x2 >> imm; break; + + CASE(Op:: eq_f32): r(d).i32 = r(x).f32 == r(y).f32; break; + CASE(Op::neq_f32): r(d).i32 = r(x).f32 != r(y).f32; break; + CASE(Op:: lt_f32): r(d).i32 = r(x).f32 < r(y).f32; break; + CASE(Op::lte_f32): r(d).i32 = r(x).f32 <= r(y).f32; break; + CASE(Op:: gt_f32): r(d).i32 = r(x).f32 > r(y).f32; break; + CASE(Op::gte_f32): r(d).i32 = r(x).f32 >= r(y).f32; break; + + CASE(Op:: eq_i32): r(d).i32 = r(x).i32 == r(y).i32; break; + CASE(Op::neq_i32): r(d).i32 = r(x).i32 != r(y).i32; break; + CASE(Op:: lt_i32): r(d).i32 = r(x).i32 < r(y).i32; break; + CASE(Op::lte_i32): r(d).i32 = r(x).i32 <= r(y).i32; break; + CASE(Op:: gt_i32): r(d).i32 = r(x).i32 > r(y).i32; break; + CASE(Op::gte_i32): r(d).i32 = r(x).i32 >= r(y).i32; break; + + CASE(Op:: eq_i16x2): r(d).i16x2 = r(x).i16x2 == r(y).i16x2; break; + CASE(Op::neq_i16x2): r(d).i16x2 = r(x).i16x2 != r(y).i16x2; break; + CASE(Op:: lt_i16x2): r(d).i16x2 = r(x).i16x2 < r(y).i16x2; break; + CASE(Op::lte_i16x2): r(d).i16x2 = r(x).i16x2 <= r(y).i16x2; break; + CASE(Op:: gt_i16x2): r(d).i16x2 = r(x).i16x2 > r(y).i16x2; break; + CASE(Op::gte_i16x2): r(d).i16x2 = r(x).i16x2 >= r(y).i16x2; break; + + CASE(Op::bit_and ): r(d).i32 = r(x).i32 & r(y).i32; break; + CASE(Op::bit_or ): r(d).i32 = r(x).i32 | r(y).i32; break; + CASE(Op::bit_xor ): r(d).i32 = r(x).i32 ^ r(y).i32; break; + CASE(Op::bit_clear): r(d).i32 = r(x).i32 & ~r(y).i32; break; + + CASE(Op::select): r(d).i32 = skvx::if_then_else(r(x).i32, r(y).i32, r(z).i32); + break; + + + CASE(Op::extract): r(d).u32 = (r(x).u32 >> imm) & r(y).u32; break; + CASE(Op::pack): r(d).u32 = r(x).u32 | (r(y).u32 << imm); break; + + CASE(Op::bytes): { + const U32 table[] = { + 0, + (r(x).u32 ) & 0xff, + (r(x).u32 >> 8) & 0xff, + (r(x).u32 >> 16) & 0xff, + (r(x).u32 >> 24) & 0xff, + }; + r(d).u32 = table[(imm >> 0) & 0xf] << 0 + | table[(imm >> 4) & 0xf] << 8 + | table[(imm >> 8) & 0xf] << 16 + | table[(imm >> 12) & 0xf] << 24; + } break; + + CASE(Op::to_f32): r(d).f32 = skvx::cast(r(x).i32); break; + CASE(Op::to_i32): r(d).i32 = skvx::cast (r(x).f32); break; + #undef CASE + } + } + } + } + + bool Program::hasJIT() const { + return fJITBuf != nullptr; + } + + void Program::dropJIT() { + #if defined(SKVM_JIT) + if (fJITBuf) { + munmap(fJITBuf, fJITSize); + } + #else + SkASSERT(!this->hasJIT()); + #endif + + fJITBuf = nullptr; + fJITSize = 0; + } + + Program::~Program() { this->dropJIT(); } + + Program::Program(Program&& other) { + fInstructions = std::move(other.fInstructions); + fRegs = other.fRegs; + fLoop = other.fLoop; + fStrides = std::move(other.fStrides); + fOriginalProgram = std::move(other.fOriginalProgram); + + std::swap(fJITBuf , other.fJITBuf); + std::swap(fJITSize , other.fJITSize); + } + + Program& Program::operator=(Program&& other) { + fInstructions = std::move(other.fInstructions); + fRegs = other.fRegs; + fLoop = other.fLoop; + fStrides = std::move(other.fStrides); + fOriginalProgram = std::move(other.fOriginalProgram); + + std::swap(fJITBuf , other.fJITBuf); + std::swap(fJITSize , other.fJITSize); + return *this; + } + + Program::Program() {} + + Program::Program(const std::vector& instructions, + const std::vector& strides, + const char* debug_name) + : fStrides(strides) + , fOriginalProgram(instructions) + { + this->setupInterpreter(instructions); + #if defined(SKVM_JIT) + this->setupJIT(instructions, debug_name); + #endif + } + + // Translate Builder::Instructions to Program::Instructions used by the interpreter. + void Program::setupInterpreter(const std::vector& instructions) { + // Register each instruction is assigned to. + std::vector reg(instructions.size()); + + // This next bit is a bit more complicated than strictly necessary; + // we could just assign every live instruction to its own register. + // + // But recycling registers is fairly cheap, and good practice for the + // JITs where minimizing register pressure really is important. + // + // Since we have effectively infinite registers, we hoist any value we can. + // (The JIT may choose a more complex policy to reduce register pressure.) + auto hoisted = [&](Val id) { return instructions[id].can_hoist; }; + + fRegs = 0; + int live_instructions = 0; + std::vector avail; + + // Assign this value to a register, recycling them where we can. + auto assign_register = [&](Val id) { + live_instructions++; + const Builder::Instruction& inst = instructions[id]; + + // If this is a real input and it's lifetime ends at this instruction, + // we can recycle the register it's occupying. + auto maybe_recycle_register = [&](Val input) { + if (input != NA + && instructions[input].death == id + && !(hoisted(input) && instructions[input].used_in_loop)) { + avail.push_back(reg[input]); + } + }; + + // Take care to not recycle the same register twice. + if (true ) { maybe_recycle_register(inst.x); } + if (inst.y != inst.x ) { maybe_recycle_register(inst.y); } + if (inst.z != inst.x && inst.z != inst.y) { maybe_recycle_register(inst.z); } + + // Allocate a register if we have to, preferring to reuse anything available. + if (avail.empty()) { + reg[id] = fRegs++; + } else { + reg[id] = avail.back(); + avail.pop_back(); + } + }; + + // Assign a register to each live hoisted instruction. + for (Val id = 0; id < (Val)instructions.size(); id++) { + if (instructions[id].death != 0 && hoisted(id)) { + assign_register(id); + } + } + + // Assign registers to each live loop instruction. + for (Val id = 0; id < (Val)instructions.size(); id++) { + if (instructions[id].death != 0 && !hoisted(id)) { + assign_register(id); + + } + } + + // Translate Builder::Instructions to Program::Instructions by mapping values to + // registers. This will be two passes, first hoisted instructions, then inside the loop. + + // The loop begins at the fLoop'th Instruction. + fLoop = 0; + fInstructions.reserve(live_instructions); + + // Add a dummy mapping for the N/A sentinel Val to any arbitrary register + // so lookups don't have to know which arguments are used by which Ops. + auto lookup_register = [&](Val id) { + return id == NA ? (Reg)0 + : reg[id]; + }; + + auto push_instruction = [&](Val id, const Builder::Instruction& inst) { + Program::Instruction pinst{ + inst.op, + lookup_register(id), + lookup_register(inst.x), + lookup_register(inst.y), + {lookup_register(inst.z)}, + }; + if (inst.z == NA) { pinst.imm = inst.imm; } + fInstructions.push_back(pinst); + }; + + for (Val id = 0; id < (Val)instructions.size(); id++) { + const Builder::Instruction& inst = instructions[id]; + if (inst.death != 0 && hoisted(id)) { + push_instruction(id, inst); + fLoop++; + } + } + for (Val id = 0; id < (Val)instructions.size(); id++) { + const Builder::Instruction& inst = instructions[id]; + if (inst.death != 0 && !hoisted(id)) { + push_instruction(id, inst); + } + } + } + +#if defined(SKVM_JIT) + + // Just so happens that we can translate the immediate control for our bytes() op + // to a single 128-bit mask that can be consumed by both AVX2 vpshufb and NEON tbl! + static void bytes_control(int imm, int mask[4]) { + auto nibble_to_vpshufb = [](uint8_t n) -> uint8_t { + // 0 -> 0xff, Fill with zero + // 1 -> 0x00, Select byte 0 + // 2 -> 0x01, " 1 + // 3 -> 0x02, " 2 + // 4 -> 0x03, " 3 + return n - 1; + }; + uint8_t control[] = { + nibble_to_vpshufb( (imm >> 0) & 0xf ), + nibble_to_vpshufb( (imm >> 4) & 0xf ), + nibble_to_vpshufb( (imm >> 8) & 0xf ), + nibble_to_vpshufb( (imm >> 12) & 0xf ), + }; + for (int i = 0; i < 4; i++) { + mask[i] = (int)control[0] << 0 + | (int)control[1] << 8 + | (int)control[2] << 16 + | (int)control[3] << 24; + + // Update each byte that refers to a byte index by 4 to + // point into the next 32-bit lane, but leave any 0xff + // that fills with zero alone. + control[0] += control[0] == 0xff ? 0 : 4; + control[1] += control[1] == 0xff ? 0 : 4; + control[2] += control[2] == 0xff ? 0 : 4; + control[3] += control[3] == 0xff ? 0 : 4; + } + } + + bool Program::jit(const std::vector& instructions, + const bool try_hoisting, + Assembler* a) const { + using A = Assembler; + + auto debug_dump = [&] { + #if 0 + SkDebugfStream stream; + this->dump(&stream); + dump_builder_program(fOriginalProgram, &stream); + return true; + #else + return false; + #endif + }; + + #if defined(__x86_64__) + if (!SkCpu::Supports(SkCpu::HSW)) { + return false; + } + A::GP64 N = A::rdi, + arg[] = { A::rsi, A::rdx, A::rcx, A::r8, A::r9 }; + + // All 16 ymm registers are available to use. + using Reg = A::Ymm; + uint32_t avail = 0xffff; + + #elif defined(__aarch64__) + A::X N = A::x0, + arg[] = { A::x1, A::x2, A::x3, A::x4, A::x5, A::x6, A::x7 }; + + // We can use v0-v7 and v16-v31 freely; we'd need to preserve v8-v15. + using Reg = A::V; + uint32_t avail = 0xffff00ff; + #endif + + if (SK_ARRAY_COUNT(arg) < fStrides.size()) { + return false; + } + + auto hoisted = [&](Val id) { return try_hoisting && instructions[id].can_hoist; }; + + std::vector r(instructions.size()); + + struct LabelAndReg { + A::Label label; + Reg reg; + }; + SkTHashMap splats, + bytes_masks; + + auto warmup = [&](Val id) { + const Builder::Instruction& inst = instructions[id]; + if (inst.death == 0) { + return true; + } + + Op op = inst.op; + int imm = inst.imm; + + switch (op) { + default: break; + + case Op::splat: if (!splats.find(imm)) { splats.set(imm, {}); } + break; + + case Op::bytes: if (!bytes_masks.find(imm)) { + bytes_masks.set(imm, {}); + if (try_hoisting) { + // vpshufb can always work with the mask from memory, + // but it helps to hoist the mask to a register for tbl. + #if defined(__aarch64__) + LabelAndReg* entry = bytes_masks.find(imm); + if (int found = __builtin_ffs(avail)) { + entry->reg = (Reg)(found-1); + avail ^= 1 << entry->reg; + a->ldrq(entry->reg, &entry->label); + } else { + return false; + } + #endif + } + } + break; + } + return true; + }; + + auto emit = [&](Val id, bool scalar) { + const Builder::Instruction& inst = instructions[id]; + + // No need to emit dead code instructions that produce values that are never used. + if (inst.death == 0) { + return true; + } + + Op op = inst.op; + Val x = inst.x, + y = inst.y, + z = inst.z; + int imm = inst.imm; + + // Most (but not all) ops create an output value and need a register to hold it, dst. + // We track each instruction's dst in r[] so we can thread it through as an input + // to any future instructions needing that value. + // + // And some ops may need a temporary scratch register, tmp. Some need both tmp and dst. + // + // tmp and dst are very similar and can and will often be assigned the same register, + // but tmp may never alias any of the instructions's inputs, while dst may when this + // instruction consumes that input, i.e. if the input reaches its end of life here. + // + // We'll assign both registers lazily to keep register pressure as low as possible. + bool tmp_is_set = false, + dst_is_set = false; + Reg tmp_reg = (Reg)0; // This initial value won't matter... anything legal is fine. + + bool ok = true; // Set to false if we need to assign a register and none's available. + + // First lock in how to choose tmp if we need to based on the registers + // available before this instruction, not including any of its input registers. + auto tmp = [&,avail/*important, closing over avail's current value*/]{ + if (!tmp_is_set) { + tmp_is_set = true; + if (int found = __builtin_ffs(avail)) { + // This is a scratch register just for this op, + // so we leave it marked available for future ops. + tmp_reg = (Reg)(found - 1); + } else { + // We needed a tmp register but couldn't find one available. :'( + // This will cause emit() to return false, in turn causing jit() to fail. + if (debug_dump()) { + SkDebugf("\nCould not find a register to hold tmp\n"); + } + ok = false; + } + } + return tmp_reg; + }; + + // Now make available any registers that are consumed by this instruction. + // (The register pool we can pick dst from is >= the pool for tmp, adding any of these.) + auto maybe_recycle_register = [&](Val input) { + if (input != NA + && instructions[input].death == id + && !(hoisted(input) && instructions[input].used_in_loop)) { + avail |= 1 << r[input]; + } + }; + maybe_recycle_register(x); + maybe_recycle_register(y); + maybe_recycle_register(z); + // set_dst() and dst() will work read/write with this perhaps-just-updated avail. + + // Some ops may decide dst on their own to best fit the instruction (see Op::mad_f32). + auto set_dst = [&](Reg reg){ + SkASSERT(dst_is_set == false); + dst_is_set = true; + + SkASSERT(avail & (1<vpextrb (arg[imm], (A::Xmm)r[x], 0); } + else { a->vpackusdw(tmp(), r[x], r[x]); + a->vpermq (tmp(), tmp(), 0xd8); + a->vpackuswb(tmp(), tmp(), tmp()); + a->vmovq (arg[imm], (A::Xmm)tmp()); } + break; + + case Op::store16: if (scalar) { a->vpextrw (arg[imm], (A::Xmm)r[x], 0); } + else { a->vpackusdw(tmp(), r[x], r[x]); + a->vpermq (tmp(), tmp(), 0xd8); + a->vmovups (arg[imm], (A::Xmm)tmp()); } + break; + + case Op::store32: if (scalar) { a->vmovd (arg[imm], (A::Xmm)r[x]); } + else { a->vmovups(arg[imm], r[x]); } + break; + + case Op::load8: if (scalar) { + a->vpxor (dst(), dst(), dst()); + a->vpinsrb((A::Xmm)dst(), (A::Xmm)dst(), arg[imm], 0); + } else { + a->vpmovzxbd(dst(), arg[imm]); + } break; + + case Op::load16: if (scalar) { + a->vpxor (dst(), dst(), dst()); + a->vpinsrw((A::Xmm)dst(), (A::Xmm)dst(), arg[imm], 0); + } else { + a->vpmovzxwd(dst(), arg[imm]); + } break; + + case Op::load32: if (scalar) { a->vmovd ((A::Xmm)dst(), arg[imm]); } + else { a->vmovups( dst(), arg[imm]); } + break; + + case Op::uniform8: a->movzbl(A::rax, arg[imm&0xffff], imm>>16); + a->vmovd_direct((A::Xmm)dst(), A::rax); + a->vbroadcastss(dst(), (A::Xmm)dst()); + break; + + case Op::uniform32: a->vbroadcastss(dst(), arg[imm&0xffff], imm>>16); + break; + + case Op::splat: a->vbroadcastss(dst(), &splats.find(imm)->label); + break; + // TODO: many of these instructions have variants that + // can read one of their arugments from 32-byte memory + // instead of a register. Find a way to avoid needing + // to splat most* constants out at all? + // (*Might work for x - 255 but not 255 - x, so will + // always need to be able to splat to a register.) + + case Op::add_f32: a->vaddps(dst(), r[x], r[y]); break; + case Op::sub_f32: a->vsubps(dst(), r[x], r[y]); break; + case Op::mul_f32: a->vmulps(dst(), r[x], r[y]); break; + case Op::div_f32: a->vdivps(dst(), r[x], r[y]); break; + + case Op::mad_f32: + if (avail & (1<vfmadd132ps(r[x], r[z], r[y]); } + else if (avail & (1<vfmadd213ps(r[y], r[x], r[z]); } + else if (avail & (1<vfmadd231ps(r[z], r[x], r[y]); } + else { SkASSERT(dst() == tmp()); + a->vmovdqa (dst(),r[x]); + a->vfmadd132ps(dst(),r[z], r[y]); } + break; + + case Op::add_i32: a->vpaddd (dst(), r[x], r[y]); break; + case Op::sub_i32: a->vpsubd (dst(), r[x], r[y]); break; + case Op::mul_i32: a->vpmulld(dst(), r[x], r[y]); break; + + case Op::sub_i16x2: a->vpsubw (dst(), r[x], r[y]); break; + case Op::mul_i16x2: a->vpmullw(dst(), r[x], r[y]); break; + case Op::shr_i16x2: a->vpsrlw (dst(), r[x], imm); break; + + case Op::bit_and : a->vpand (dst(), r[x], r[y]); break; + case Op::bit_or : a->vpor (dst(), r[x], r[y]); break; + case Op::bit_xor : a->vpxor (dst(), r[x], r[y]); break; + case Op::bit_clear: a->vpandn(dst(), r[y], r[x]); break; // N.B. Y then X. + case Op::select : a->vpblendvb(dst(), r[z], r[y], r[x]); break; + + case Op::shl_i32: a->vpslld(dst(), r[x], imm); break; + case Op::shr_i32: a->vpsrld(dst(), r[x], imm); break; + case Op::sra_i32: a->vpsrad(dst(), r[x], imm); break; + + case Op::eq_i32: a->vpcmpeqd(dst(), r[x], r[y]); break; + case Op::lt_i32: a->vpcmpgtd(dst(), r[y], r[x]); break; + case Op::gt_i32: a->vpcmpgtd(dst(), r[x], r[y]); break; + + case Op::extract: if (imm == 0) { a->vpand (dst(), r[x], r[y]); } + else { a->vpsrld(tmp(), r[x], imm); + a->vpand (dst(), tmp(), r[y]); } + break; + + case Op::pack: a->vpslld(tmp(), r[y], imm); + a->vpor (dst(), tmp(), r[x]); + break; + + case Op::to_f32: a->vcvtdq2ps (dst(), r[x]); break; + case Op::to_i32: a->vcvttps2dq(dst(), r[x]); break; + + case Op::bytes: a->vpshufb(dst(), r[x], &bytes_masks.find(imm)->label); + break; + + #elif defined(__aarch64__) + case Op::store8: a->xtns2h(tmp(), r[x]); + a->xtnh2b(tmp(), tmp()); + if (scalar) { a->strb (tmp(), arg[imm]); } + else { a->strs (tmp(), arg[imm]); } + break; + // TODO: another case where it'd be okay to alias r[x] and tmp if r[x] dies here. + + case Op::store32: if (scalar) { a->strs(r[x], arg[imm]); } + else { a->strq(r[x], arg[imm]); } + break; + + case Op::load8: if (scalar) { a->ldrb(tmp(), arg[imm]); } + else { a->ldrs(tmp(), arg[imm]); } + a->uxtlb2h(tmp(), tmp()); + a->uxtlh2s(dst(), tmp()); + break; + + case Op::load32: if (scalar) { a->ldrs(dst(), arg[imm]); } + else { a->ldrq(dst(), arg[imm]); } + break; + + case Op::splat: a->ldrq(dst(), &splats.find(imm)->label); + break; + // TODO: If we hoist these, pack 4 values in each register + // and use vector/lane operations, cutting the register + // pressure cost of hoisting by 4? + + case Op::add_f32: a->fadd4s(dst(), r[x], r[y]); break; + case Op::sub_f32: a->fsub4s(dst(), r[x], r[y]); break; + case Op::mul_f32: a->fmul4s(dst(), r[x], r[y]); break; + case Op::div_f32: a->fdiv4s(dst(), r[x], r[y]); break; + + case Op::mad_f32: // fmla4s is z += x*y + if (avail & (1<fmla4s( r[z], r[x], r[y]); } + else { a->orr16b(tmp(), r[z], r[z]); + a->fmla4s(tmp(), r[x], r[y]); + if(dst() != tmp()) { a->orr16b(dst(), tmp(), tmp()); } } + break; + + + case Op::add_i32: a->add4s(dst(), r[x], r[y]); break; + case Op::sub_i32: a->sub4s(dst(), r[x], r[y]); break; + case Op::mul_i32: a->mul4s(dst(), r[x], r[y]); break; + + case Op::sub_i16x2: a->sub8h (dst(), r[x], r[y]); break; + case Op::mul_i16x2: a->mul8h (dst(), r[x], r[y]); break; + case Op::shr_i16x2: a->ushr8h(dst(), r[x], imm); break; + + case Op::bit_and : a->and16b(dst(), r[x], r[y]); break; + case Op::bit_or : a->orr16b(dst(), r[x], r[y]); break; + case Op::bit_xor : a->eor16b(dst(), r[x], r[y]); break; + case Op::bit_clear: a->bic16b(dst(), r[x], r[y]); break; + + case Op::select: // bsl16b is x = x ? y : z + if (avail & (1<bsl16b( r[x], r[y], r[z]); } + else { a->orr16b(tmp(), r[x], r[x]); + a->bsl16b(tmp(), r[y], r[z]); + if(dst() != tmp()) { a->orr16b(dst(), tmp(), tmp()); } } + break; + + case Op::shl_i32: a-> shl4s(dst(), r[x], imm); break; + case Op::shr_i32: a->ushr4s(dst(), r[x], imm); break; + case Op::sra_i32: a->sshr4s(dst(), r[x], imm); break; + + case Op::eq_i32: a->cmeq4s(dst(), r[x], r[y]); break; + case Op::lt_i32: a->cmgt4s(dst(), r[y], r[x]); break; + case Op::gt_i32: a->cmgt4s(dst(), r[x], r[y]); break; + + case Op::extract: if (imm) { a->ushr4s(tmp(), r[x], imm); + a->and16b(dst(), tmp(), r[y]); } + else { a->and16b(dst(), r[x], r[y]); } + break; + + case Op::pack: + if (avail & (1<sli4s ( r[x], r[y], imm); } + else { a->shl4s (tmp(), r[y], imm); + a->orr16b(dst(), tmp(), r[x]); } + break; + + case Op::to_f32: a->scvtf4s (dst(), r[x]); break; + case Op::to_i32: a->fcvtzs4s(dst(), r[x]); break; + + case Op::bytes: + if (try_hoisting) { a->tbl (dst(), r[x], bytes_masks.find(imm)->reg); } + else { a->ldrq(tmp(), &bytes_masks.find(imm)->label); + a->tbl (dst(), r[x], tmp()); } + break; + #endif + } + + // Calls to tmp() or dst() might have flipped this false from its default true state. + return ok; + }; + + + #if defined(__x86_64__) + const int K = 8; + auto jump_if_less = [&](A::Label* l) { a->jl (l); }; + auto jump = [&](A::Label* l) { a->jmp(l); }; + + auto add = [&](A::GP64 gp, int imm) { a->add(gp, imm); }; + auto sub = [&](A::GP64 gp, int imm) { a->sub(gp, imm); }; + + auto exit = [&]{ a->vzeroupper(); a->ret(); }; + #elif defined(__aarch64__) + const int K = 4; + auto jump_if_less = [&](A::Label* l) { a->blt(l); }; + auto jump = [&](A::Label* l) { a->b (l); }; + + auto add = [&](A::X gp, int imm) { a->add(gp, gp, imm); }; + auto sub = [&](A::X gp, int imm) { a->sub(gp, gp, imm); }; + + auto exit = [&]{ a->ret(A::x30); }; + #endif + + A::Label body, + tail, + done; + + for (Val id = 0; id < (Val)instructions.size(); id++) { + if (!warmup(id)) { + return false; + } + if (hoisted(id) && !emit(id, /*scalar=*/false)) { + return false; + } + } + + a->label(&body); + { + a->cmp(N, K); + jump_if_less(&tail); + for (Val id = 0; id < (Val)instructions.size(); id++) { + if (!hoisted(id) && !emit(id, /*scalar=*/false)) { + return false; + } + } + for (int i = 0; i < (int)fStrides.size(); i++) { + if (fStrides[i]) { + add(arg[i], K*fStrides[i]); + } + } + sub(N, K); + jump(&body); + } + + a->label(&tail); + { + a->cmp(N, 1); + jump_if_less(&done); + for (Val id = 0; id < (Val)instructions.size(); id++) { + if (!hoisted(id) && !emit(id, /*scalar=*/true)) { + return false; + } + } + for (int i = 0; i < (int)fStrides.size(); i++) { + if (fStrides[i]) { + add(arg[i], 1*fStrides[i]); + } + } + sub(N, 1); + jump(&tail); + } + + a->label(&done); + { + exit(); + } + + bytes_masks.foreach([&](int imm, LabelAndReg* entry) { + // One 16-byte pattern for ARM tbl, that same pattern twice for x86-64 vpshufb. + #if defined(__x86_64__) + a->align(32); + #elif defined(__aarch64__) + a->align(4); + #endif + + a->label(&entry->label); + int mask[4]; + bytes_control(imm, mask); + a->bytes(mask, sizeof(mask)); + #if defined(__x86_64__) + a->bytes(mask, sizeof(mask)); + #endif + }); + + splats.foreach([&](int imm, LabelAndReg* entry) { + // vbroadcastss 4 bytes on x86-64, or simply load 16-bytes on aarch64. + a->align(4); + a->label(&entry->label); + a->word(imm); + #if defined(__aarch64__) + a->word(imm); + a->word(imm); + a->word(imm); + #endif + }); + + return true; + } + + void Program::setupJIT(const std::vector& instructions, + const char* debug_name) { + // Assemble with no buffer to determine a.size(), the number of bytes we'll assemble. + Assembler a{nullptr}; + + // First try allowing code hoisting (faster code) + // then again without if that fails (lower register pressure). + bool try_hoisting = true; + if (!this->jit(instructions, try_hoisting, &a)) { + try_hoisting = false; + if (!this->jit(instructions, try_hoisting, &a)) { + return; + } + } + + // Allocate space that we can remap as executable. + const size_t page = sysconf(_SC_PAGESIZE); + fJITSize = ((a.size() + page - 1) / page) * page; // mprotect works at page granularity. + fJITBuf = mmap(nullptr,fJITSize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1,0); + + // Assemble the program for real. + a = Assembler{fJITBuf}; + SkAssertResult(this->jit(instructions, try_hoisting, &a)); + SkASSERT(a.size() <= fJITSize); + + // Remap as executable, and flush caches on platforms that need that. + mprotect(fJITBuf, fJITSize, PROT_READ|PROT_EXEC); + __builtin___clear_cache((char*)fJITBuf, + (char*)fJITBuf + fJITSize); + #if defined(SKVM_PERF_DUMPS) + this->dumpJIT(debug_name, a.size()); + #endif + } +#endif + +#if defined(SKVM_PERF_DUMPS) + void Program::dumpJIT(const char* debug_name, size_t size) const { + #if 0 && defined(__aarch64__) + if (debug_name) { + SkDebugf("\n%s:", debug_name); + } + // cat | llvm-mc -arch aarch64 -disassemble + auto cur = (const uint8_t*)fJITBuf; + for (int i = 0; i < (int)size; i++) { + if (i % 4 == 0) { + SkDebugf("\n"); + } + SkDebugf("0x%02x ", *cur++); + } + SkDebugf("\n"); + #endif + + // We're doing some really stateful things below so one thread at a time please... + static SkSpinlock dump_lock; + SkAutoSpinlock lock(dump_lock); + + auto fnv1a = [](const void* vbuf, size_t n) { + uint32_t hash = 2166136261; + for (auto buf = (const uint8_t*)vbuf; n --> 0; buf++) { + hash ^= *buf; + hash *= 16777619; + } + return hash; + }; + + + char name[64]; + uint32_t hash = fnv1a(fJITBuf, size); + if (debug_name) { + sprintf(name, "skvm-jit-%s", debug_name); + } else { + sprintf(name, "skvm-jit-%u", hash); + } + + // Create a jit-.dump file that we can `perf inject -j` into a + // perf.data captured with `perf record -k 1`, letting us see each + // JIT'd Program as if a function named skvm-jit-. E.g. + // + // ninja -C out nanobench + // perf record -k 1 out/nanobench -m SkVM_4096_I32\$ + // perf inject -j -i perf.data -o perf.data.jit + // perf report -i perf.data.jit + // + // Running `perf inject -j` will also dump an .so for each JIT'd + // program, named jitted--.so. + // + // https://lwn.net/Articles/638566/ + // https://v8.dev/docs/linux-perf + // https://cs.chromium.org/chromium/src/v8/src/diagnostics/perf-jit.cc + // https://lore.kernel.org/patchwork/patch/622240/ + + + auto timestamp_ns = []() -> uint64_t { + // It's important to use CLOCK_MONOTONIC here so that perf can + // correlate our timestamps with those captured by `perf record + // -k 1`. That's also what `-k 1` does, by the way, tell perf + // record to use CLOCK_MONOTONIC. + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * (uint64_t)1e9 + ts.tv_nsec; + }; + + // We'll open the jit-.dump file and write a small header once, + // and just leave it open forever because we're lazy. + static FILE* jitdump = [&]{ + // Must map as w+ for the mmap() call below to work. + char path[64]; + sprintf(path, "jit-%d.dump", getpid()); + FILE* f = fopen(path, "w+"); + + // Calling mmap() on the file adds a "hey they mmap()'d this" record to + // the perf.data file that will point `perf inject -j` at this log file. + // Kind of a strange way to tell `perf inject` where the file is... + void* marker = mmap(nullptr, sysconf(_SC_PAGESIZE), + PROT_READ|PROT_EXEC, MAP_PRIVATE, + fileno(f), /*offset=*/0); + SkASSERT_RELEASE(marker != MAP_FAILED); + // Like never calling fclose(f), we'll also just always leave marker mmap()'d. + + #if defined(__x86_64__) + const uint32_t elf_mach = 62; + #elif defined(__aarch64__) + const uint32_t elf_mach = 183; + #endif + + struct Header { + uint32_t magic, version, header_size, elf_mach, reserved, pid; + uint64_t timestamp_us, flags; + } header = { + 0x4A695444, 1, sizeof(Header), elf_mach, 0, (uint32_t)getpid(), + timestamp_ns() / 1000, 0, + }; + fwrite(&header, sizeof(header), 1, f); + + return f; + }(); + + struct CodeLoad { + uint32_t event_type, event_size; + uint64_t timestamp_ns; + + uint32_t pid, tid; + uint64_t vma/*???*/, code_addr, code_size, id; + } load = { + 0/*code load*/, (uint32_t)(sizeof(CodeLoad) + strlen(name) + 1 + size), + timestamp_ns(), + + (uint32_t)getpid(), (uint32_t)SkGetThreadID(), + (uint64_t)fJITBuf, (uint64_t)fJITBuf, size, hash, + }; + + // Write the header, the JIT'd function name, and the JIT'd code itself. + fwrite(&load, sizeof(load), 1, jitdump); + fwrite(name, 1, strlen(name), jitdump); + fwrite("\0", 1, 1, jitdump); + fwrite(fJITBuf, 1, size, jitdump); + } +#endif + + +} // namespace skvm diff --git a/gfx/skia/skia/src/core/SkVM.h b/gfx/skia/skia/src/core/SkVM.h new file mode 100644 index 0000000000..06b189ac14 --- /dev/null +++ b/gfx/skia/skia/src/core/SkVM.h @@ -0,0 +1,521 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkVM_DEFINED +#define SkVM_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkTHash.h" +#include // std::hash +#include // std::vector + +class SkWStream; + +namespace skvm { + + class Assembler { + public: + explicit Assembler(void* buf); + + size_t size() const; + + // Order matters... GP64, Xmm, Ymm values match 4-bit register encoding for each. + enum GP64 { + rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, + r8 , r9 , r10, r11, r12, r13, r14, r15, + }; + enum Xmm { + xmm0, xmm1, xmm2 , xmm3 , xmm4 , xmm5 , xmm6 , xmm7 , + xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, + }; + enum Ymm { + ymm0, ymm1, ymm2 , ymm3 , ymm4 , ymm5 , ymm6 , ymm7 , + ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15, + }; + + // X and V values match 5-bit encoding for each (nothing tricky). + enum X { + x0 , x1 , x2 , x3 , x4 , x5 , x6 , x7 , + x8 , x9 , x10, x11, x12, x13, x14, x15, + x16, x17, x18, x19, x20, x21, x22, x23, + x24, x25, x26, x27, x28, x29, x30, xzr, + }; + enum V { + v0 , v1 , v2 , v3 , v4 , v5 , v6 , v7 , + v8 , v9 , v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, + }; + + void bytes(const void*, int); + void byte(uint8_t); + void word(uint32_t); + + // x86-64 + + void align(int mod); + + void vzeroupper(); + void ret(); + + void add(GP64, int imm); + void sub(GP64, int imm); + + // All dst = x op y. + using DstEqXOpY = void(Ymm dst, Ymm x, Ymm y); + DstEqXOpY vpand, vpor, vpxor, vpandn, + vpaddd, vpsubd, vpmulld, + vpsubw, vpmullw, + vaddps, vsubps, vmulps, vdivps, + vfmadd132ps, vfmadd213ps, vfmadd231ps, + vpackusdw, vpackuswb, + vpcmpeqd, vpcmpgtd; + + using DstEqXOpImm = void(Ymm dst, Ymm x, int imm); + DstEqXOpImm vpslld, vpsrld, vpsrad, + vpsrlw, + vpermq; + + using DstEqOpX = void(Ymm dst, Ymm x); + DstEqOpX vmovdqa, vcvtdq2ps, vcvttps2dq; + + void vpblendvb(Ymm dst, Ymm x, Ymm y, Ymm z); + + struct Label { + int offset = 0; + enum { None, ARMDisp19, X86Disp32 } kind = None; + std::vector references; + }; + + Label here(); + void label(Label*); + + void jmp(Label*); + void je (Label*); + void jne(Label*); + void jl (Label*); + void cmp(GP64, int imm); + + void vbroadcastss(Ymm dst, Label*); + void vbroadcastss(Ymm dst, Xmm src); + void vbroadcastss(Ymm dst, GP64 ptr, int off); // dst = *(ptr+off) + + void vpshufb(Ymm dst, Ymm x, Label*); + + void vmovups (Ymm dst, GP64 ptr); // dst = *ptr, 256-bit + void vpmovzxwd(Ymm dst, GP64 ptr); // dst = *ptr, 128-bit, each uint16_t expanded to int + void vpmovzxbd(Ymm dst, GP64 ptr); // dst = *ptr, 64-bit, each uint8_t expanded to int + void vmovd (Xmm dst, GP64 ptr); // dst = *ptr, 32-bit + + void vmovups(GP64 ptr, Ymm src); // *ptr = src, 256-bit + void vmovups(GP64 ptr, Xmm src); // *ptr = src, 128-bit + void vmovq (GP64 ptr, Xmm src); // *ptr = src, 64-bit + void vmovd (GP64 ptr, Xmm src); // *ptr = src, 32-bit + + void movzbl(GP64 dst, GP64 ptr, int off); // dst = *(ptr+off), uint8_t -> int + void movb (GP64 ptr, GP64 src); // *ptr = src, 8-bit + + void vmovd_direct(GP64 dst, Xmm src); // dst = src, 32-bit + void vmovd_direct(Xmm dst, GP64 src); // dst = src, 32-bit + + void vpinsrw(Xmm dst, Xmm src, GP64 ptr, int imm); // dst = src; dst[imm] = *ptr, 16-bit + void vpinsrb(Xmm dst, Xmm src, GP64 ptr, int imm); // dst = src; dst[imm] = *ptr, 8-bit + + void vpextrw(GP64 ptr, Xmm src, int imm); // *dst = src[imm] , 16-bit + void vpextrb(GP64 ptr, Xmm src, int imm); // *dst = src[imm] , 8-bit + + // aarch64 + + // d = op(n,m) + using DOpNM = void(V d, V n, V m); + DOpNM and16b, orr16b, eor16b, bic16b, bsl16b, + add4s, sub4s, mul4s, + cmeq4s, cmgt4s, + sub8h, mul8h, + fadd4s, fsub4s, fmul4s, fdiv4s, + tbl; + + // d += n*m + void fmla4s(V d, V n, V m); + + // d = op(n,imm) + using DOpNImm = void(V d, V n, int imm); + DOpNImm sli4s, + shl4s, sshr4s, ushr4s, + ushr8h; + + // d = op(n) + using DOpN = void(V d, V n); + DOpN scvtf4s, // int -> float + fcvtzs4s, // truncate float -> int + xtns2h, // u32 -> u16 + xtnh2b, // u16 -> u8 + uxtlb2h, // u8 -> u16 + uxtlh2s; // u16 -> u32 + + // TODO: both these platforms support rounding float->int (vcvtps2dq, fcvtns.4s)... use? + + void ret (X); + void add (X d, X n, int imm12); + void sub (X d, X n, int imm12); + void subs(X d, X n, int imm12); // subtract setting condition flags + + // There's another encoding for unconditional branches that can jump further, + // but this one encoded as b.al is simple to implement and should be fine. + void b (Label* l) { this->b(Condition::al, l); } + void bne(Label* l) { this->b(Condition::ne, l); } + void blt(Label* l) { this->b(Condition::lt, l); } + + // "cmp ..." is just an assembler mnemonic for "subs xzr, ..."! + void cmp(X n, int imm12) { this->subs(xzr, n, imm12); } + + // Compare and branch if zero/non-zero, as if + // cmp(t,0) + // beq/bne(l) + // but without setting condition flags. + void cbz (X t, Label* l); + void cbnz(X t, Label* l); + + void ldrq(V dst, Label*); // 128-bit PC-relative load + + void ldrq(V dst, X src); // 128-bit dst = *src + void ldrs(V dst, X src); // 32-bit dst = *src + void ldrb(V dst, X src); // 8-bit dst = *src + + void strq(V src, X dst); // 128-bit *dst = src + void strs(V src, X dst); // 32-bit *dst = src + void strb(V src, X dst); // 8-bit *dst = src + + private: + // dst = op(dst, imm) + void op(int opcode, int opcode_ext, GP64 dst, int imm); + + + // dst = op(x,y) or op(x) + void op(int prefix, int map, int opcode, Ymm dst, Ymm x, Ymm y, bool W=false); + void op(int prefix, int map, int opcode, Ymm dst, Ymm x, bool W=false) { + // Two arguments ops seem to pass them in dst and y, forcing x to 0 so VEX.vvvv == 1111. + this->op(prefix, map, opcode, dst,(Ymm)0,x, W); + } + + // dst = op(x,imm) + void op(int prefix, int map, int opcode, int opcode_ext, Ymm dst, Ymm x, int imm); + + // dst = op(x,label) or op(label) + void op(int prefix, int map, int opcode, Ymm dst, Ymm x, Label* l); + + // *ptr = ymm or ymm = *ptr, depending on opcode. + void load_store(int prefix, int map, int opcode, Ymm ymm, GP64 ptr); + + // Opcode for 3-arguments ops is split between hi and lo: + // [11 bits hi] [5 bits m] [6 bits lo] [5 bits n] [5 bits d] + void op(uint32_t hi, V m, uint32_t lo, V n, V d); + + // 2-argument ops, with or without an immediate. + void op(uint32_t op22, int imm, V n, V d); + void op(uint32_t op22, V n, V d) { this->op(op22,0,n,d); } + void op(uint32_t op22, X x, V v) { this->op(op22,0,(V)x,v); } + + // Order matters... value is 4-bit encoding for condition code. + enum class Condition { eq,ne,cs,cc,mi,pl,vs,vc,hi,ls,ge,lt,gt,le,al }; + void b(Condition, Label*); + + void jump(uint8_t condition, Label*); + + int disp19(Label*); + int disp32(Label*); + + uint8_t* fCode; + uint8_t* fCurr; + size_t fSize; + }; + + enum class Op : uint8_t { + store8, store16, store32, + // ↑ side effects / no side effects ↓ + + load8, load16, load32, + gather8, gather16, gather32, + // ↑ always varying / uniforms, constants, Just Math ↓ + + uniform8, uniform16, uniform32, + splat, + + add_f32, add_i32, add_i16x2, + sub_f32, sub_i32, sub_i16x2, + mul_f32, mul_i32, mul_i16x2, + div_f32, + mad_f32, + shl_i32, shl_i16x2, + shr_i32, shr_i16x2, + sra_i32, sra_i16x2, + + to_i32, to_f32, + + eq_f32, eq_i32, eq_i16x2, + neq_f32, neq_i32, neq_i16x2, + lt_f32, lt_i32, lt_i16x2, + lte_f32, lte_i32, lte_i16x2, + gt_f32, gt_i32, gt_i16x2, + gte_f32, gte_i32, gte_i16x2, + + bit_and, + bit_or, + bit_xor, + bit_clear, + select, + + bytes, extract, pack, + }; + + using Val = int; + // We reserve the last Val ID as a sentinel meaning none, n/a, null, nil, etc. + static const Val NA = ~0; + + struct Arg { int ix; }; + struct I32 { Val id; }; + struct F32 { Val id; }; + + class Program; + + class Builder { + public: + struct Instruction { + Op op; // v* = op(x,y,z,imm), where * == index of this Instruction. + Val x,y,z; // Enough arguments for mad(). + int imm; // Immediate bit pattern, shift count, argument index, etc. + + // Not populated until done() has been called. + int death; // Index of last live instruction taking this input; live if != 0. + bool can_hoist; // Value independent of all loop variables? + bool used_in_loop; // Is the value used in the loop (or only by hoisted values)? + }; + + Program done(const char* debug_name = nullptr); + + // Mostly for debugging, tests, etc. + std::vector program() const { return fProgram; } + + + // Declare an argument with given stride (use stride=0 for uniforms). + // TODO: different types for varying and uniforms? + Arg arg(int stride); + + // Convenience arg() wrappers for most common strides, sizeof(T) and 0. + template + Arg varying() { return this->arg(sizeof(T)); } + Arg uniform() { return this->arg(0); } + + // TODO: allow uniform (i.e. Arg) offsets to store* and load*? + // TODO: sign extension (signed types) for <32-bit loads? + // TODO: unsigned integer operations where relevant (just comparisons?)? + + // Store {8,16,32}-bit varying. + void store8 (Arg ptr, I32 val); + void store16(Arg ptr, I32 val); + void store32(Arg ptr, I32 val); + + // Load u8,u16,i32 varying. + I32 load8 (Arg ptr); + I32 load16(Arg ptr); + I32 load32(Arg ptr); + + // Gather u8,u16,i32 with varying element-count offset. + I32 gather8 (Arg ptr, I32 offset); + I32 gather16(Arg ptr, I32 offset); + I32 gather32(Arg ptr, I32 offset); + + // Load u8,u16,i32 uniform with optional byte-count offset. + I32 uniform8 (Arg ptr, int offset=0); + I32 uniform16(Arg ptr, int offset=0); + I32 uniform32(Arg ptr, int offset=0); + + // Load an immediate constant. + I32 splat(int n); + I32 splat(unsigned u) { return this->splat((int)u); } + F32 splat(float f); + + // float math, comparisons, etc. + F32 add(F32 x, F32 y); + F32 sub(F32 x, F32 y); + F32 mul(F32 x, F32 y); + F32 div(F32 x, F32 y); + F32 mad(F32 x, F32 y, F32 z); // x*y+z, often an FMA + + I32 eq (F32 x, F32 y); + I32 neq(F32 x, F32 y); + I32 lt (F32 x, F32 y); + I32 lte(F32 x, F32 y); + I32 gt (F32 x, F32 y); + I32 gte(F32 x, F32 y); + + I32 to_i32(F32 x); + I32 bit_cast(F32 x) { return {x.id}; } + + // int math, comparisons, etc. + I32 add(I32 x, I32 y); + I32 sub(I32 x, I32 y); + I32 mul(I32 x, I32 y); + + I32 shl(I32 x, int bits); + I32 shr(I32 x, int bits); + I32 sra(I32 x, int bits); + + I32 eq (I32 x, I32 y); + I32 neq(I32 x, I32 y); + I32 lt (I32 x, I32 y); + I32 lte(I32 x, I32 y); + I32 gt (I32 x, I32 y); + I32 gte(I32 x, I32 y); + + F32 to_f32(I32 x); + F32 bit_cast(I32 x) { return {x.id}; } + + // Treat each 32-bit lane as a pair of 16-bit ints. + I32 add_16x2(I32 x, I32 y); + I32 sub_16x2(I32 x, I32 y); + I32 mul_16x2(I32 x, I32 y); + + I32 shl_16x2(I32 x, int bits); + I32 shr_16x2(I32 x, int bits); + I32 sra_16x2(I32 x, int bits); + + I32 eq_16x2(I32 x, I32 y); + I32 neq_16x2(I32 x, I32 y); + I32 lt_16x2(I32 x, I32 y); + I32 lte_16x2(I32 x, I32 y); + I32 gt_16x2(I32 x, I32 y); + I32 gte_16x2(I32 x, I32 y); + + // Bitwise operations. + I32 bit_and (I32 x, I32 y); + I32 bit_or (I32 x, I32 y); + I32 bit_xor (I32 x, I32 y); + I32 bit_clear(I32 x, I32 y); // x & ~y + + I32 select(I32 cond, I32 t, I32 f); // cond ? t : f + F32 select(I32 cond, F32 t, F32 f) { + return this->bit_cast(this->select(cond, this->bit_cast(t) + , this->bit_cast(f))); + } + + // More complex operations... + + // Shuffle the bytes in x according to each nibble of control, as if + // + // uint8_t bytes[] = { + // 0, + // ((uint32_t)x ) & 0xff, + // ((uint32_t)x >> 8) & 0xff, + // ((uint32_t)x >> 16) & 0xff, + // ((uint32_t)x >> 24) & 0xff, + // }; + // return (uint32_t)bytes[(control >> 0) & 0xf] << 0 + // | (uint32_t)bytes[(control >> 4) & 0xf] << 8 + // | (uint32_t)bytes[(control >> 8) & 0xf] << 16 + // | (uint32_t)bytes[(control >> 12) & 0xf] << 24; + // + // So, e.g., + // - bytes(x, 0x1111) splats the low byte of x to all four bytes + // - bytes(x, 0x4321) is x, an identity + // - bytes(x, 0x0000) is 0 + // - bytes(x, 0x0404) transforms an RGBA pixel into an A0A0 bit pattern. + I32 bytes (I32 x, int control); + + I32 extract(I32 x, int bits, I32 y); // (x >> bits) & y + I32 pack (I32 x, I32 y, int bits); // x | (y << bits), assuming (x & (y << bits)) == 0 + + void dump(SkWStream* = nullptr) const; + + private: + struct InstructionHash { + template + static size_t Hash(T val) { + return std::hash{}(val); + } + size_t operator()(const Instruction& inst) const; + }; + + Val push(Op, Val x, Val y=NA, Val z=NA, int imm=0); + bool isZero(Val) const; + + SkTHashMap fIndex; + std::vector fProgram; + std::vector fStrides; + }; + + using Reg = int; + + class Program { + public: + struct Instruction { // d = op(x, y, z/imm) + Op op; + Reg d,x,y; + union { Reg z; int imm; }; + }; + + Program(const std::vector& instructions, + const std::vector & strides, + const char* debug_name); + + Program(); + ~Program(); + Program(Program&&); + Program& operator=(Program&&); + Program(const Program&) = delete; + Program& operator=(const Program&) = delete; + + void eval(int n, void* args[]) const; + + template + void eval(int n, T*... arg) const { + SkASSERT(sizeof...(arg) == fStrides.size()); + // This nullptr isn't important except that it makes args[] non-empty if you pass none. + void* args[] = { (void*)arg..., nullptr }; + this->eval(n, args); + } + + std::vector instructions() const { return fInstructions; } + int nregs() const { return fRegs; } + int loop() const { return fLoop; } + bool empty() const { return fInstructions.empty(); } + + bool hasJIT() const; // Has this Program been JITted? + void dropJIT(); // If hasJIT(), drop it, forcing interpreter fallback. + + void dump(SkWStream* = nullptr) const; + + private: + void setupInterpreter(const std::vector&); + void setupJIT (const std::vector&, const char* debug_name); + + bool jit(const std::vector&, + bool try_hoisting, + Assembler*) const; + + // Dump jit-*.dump files for perf inject. + void dumpJIT(const char* debug_name, size_t size) const; + + std::vector fInstructions; + int fRegs = 0; + int fLoop = 0; + std::vector fStrides; + + // We only hang onto these to help debugging. + std::vector fOriginalProgram; + + void* fJITBuf = nullptr; + size_t fJITSize = 0; + }; + + // TODO: control flow + // TODO: 64-bit values? + // TODO: SSE2/SSE4.1, AVX-512F, ARMv8.2 JITs? + // TODO: lower to LLVM or WebASM for comparison? +} + +#endif//SkVM_DEFINED diff --git a/gfx/skia/skia/src/core/SkVMBlitter.cpp b/gfx/skia/skia/src/core/SkVMBlitter.cpp new file mode 100644 index 0000000000..5b767afbe2 --- /dev/null +++ b/gfx/skia/skia/src/core/SkVMBlitter.cpp @@ -0,0 +1,446 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMacros.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkCoreBlitters.h" +#include "src/core/SkLRUCache.h" +#include "src/core/SkVM.h" + +namespace { + + enum class Coverage { Full, UniformA8, MaskA8, MaskLCD16, Mask3D }; + + SK_BEGIN_REQUIRE_DENSE; + struct Key { + SkColorType colorType; + SkAlphaType alphaType; + Coverage coverage; + SkBlendMode blendMode; + SkShader* shader; + SkColorFilter* colorFilter; + + Key withCoverage(Coverage c) const { + Key k = *this; + k.coverage = c; + return k; + } + }; + SK_END_REQUIRE_DENSE; + + static bool operator==(const Key& x, const Key& y) { + return x.colorType == y.colorType + && x.alphaType == y.alphaType + && x.coverage == y.coverage + && x.blendMode == y.blendMode + && x.shader == y.shader + && x.colorFilter == y.colorFilter; + } + + static SkString debug_name(const Key& key) { + return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-Shader%d-CF%d", + key.colorType, + key.alphaType, + key.coverage, + key.blendMode, + SkToBool(key.shader), + SkToBool(key.colorFilter)); + } + + static bool debug_dump(const Key& key) { + #if 0 + SkDebugf("%s\n", debug_name(key).c_str()); + return true; + #else + return false; + #endif + } + + static SkLRUCache* try_acquire_program_cache() { + #if defined(SK_BUILD_FOR_IOS) + // iOS doesn't support thread_local on versions less than 9.0. pthread + // based fallbacks must be used there. We could also use an SkSpinlock + // and tryAcquire()/release(), or... + return nullptr; // ... we could just not cache programs on those platforms. + #else + thread_local static auto* cache = new SkLRUCache{8}; + return cache; + #endif + } + + static void release_program_cache() { } + + + struct Uniforms { + uint32_t paint_color; + uint8_t coverage; // Used when Coverage::UniformA8. + }; + + struct Builder : public skvm::Builder { + //using namespace skvm; + + struct Color { skvm::I32 r,g,b,a; }; + + + skvm::I32 inv(skvm::I32 x) { + return sub(splat(255), x); + } + + // TODO: provide this in skvm::Builder, with a custom NEON impl. + skvm::I32 div255(skvm::I32 v) { + // This should be a bit-perfect version of (v+127)/255, + // implemented as (v + ((v+128)>>8) + 128)>>8. + skvm::I32 v128 = add(v, splat(128)); + return shr(add(v128, shr(v128, 8)), 8); + } + + skvm::I32 mix(skvm::I32 x, skvm::I32 y, skvm::I32 t) { + return div255(add(mul(x, inv(t)), + mul(y, t ))); + } + + Color unpack_8888(skvm::I32 rgba) { + return { + extract(rgba, 0, splat(0xff)), + extract(rgba, 8, splat(0xff)), + extract(rgba, 16, splat(0xff)), + extract(rgba, 24, splat(0xff)), + }; + } + + skvm::I32 pack_8888(Color c) { + return pack(pack(c.r, c.g, 8), + pack(c.b, c.a, 8), 16); + } + + Color unpack_565(skvm::I32 bgr) { + // N.B. kRGB_565_SkColorType is named confusingly; + // blue is in the low bits and red the high. + skvm::I32 r = extract(bgr, 11, splat(0b011'111)), + g = extract(bgr, 5, splat(0b111'111)), + b = extract(bgr, 0, splat(0b011'111)); + return { + // Scale 565 up to 888. + bit_or(shl(r, 3), shr(r, 2)), + bit_or(shl(g, 2), shr(g, 4)), + bit_or(shl(b, 3), shr(b, 2)), + splat(0xff), + }; + } + + skvm::I32 pack_565(Color c) { + skvm::I32 r = div255(mul(c.r, splat(31))), + g = div255(mul(c.g, splat(63))), + b = div255(mul(c.b, splat(31))); + return pack(pack(b, g,5), r,11); + } + + // TODO: add native min/max ops to skvm::Builder + skvm::I32 min(skvm::I32 x, skvm::I32 y) { return select(lt(x,y), x,y); } + skvm::I32 max(skvm::I32 x, skvm::I32 y) { return select(gt(x,y), x,y); } + + static bool CanBuild(const Key& key) { + // These checks parallel the TODOs in Builder::Builder(). + if (key.shader) { return false; } + if (key.colorFilter) { return false; } + + switch (key.colorType) { + default: return false; + case kRGB_565_SkColorType: break; + case kRGBA_8888_SkColorType: break; + case kBGRA_8888_SkColorType: break; + } + + if (key.alphaType == kUnpremul_SkAlphaType) { return false; } + + switch (key.blendMode) { + default: return false; + case SkBlendMode::kSrc: break; + case SkBlendMode::kSrcOver: break; + } + + return true; + } + + explicit Builder(const Key& key) { + #define TODO SkUNREACHABLE + SkASSERT(CanBuild(key)); + skvm::Arg uniforms = uniform(), + dst_ptr = arg(SkColorTypeBytesPerPixel(key.colorType)); + // When coverage is MaskA8 or MaskLCD16 there will be one more mask varying, + // and when coverage is Mask3D there will be three more mask varyings. + + + // When there's no shader and no color filter, the source color is the paint color. + if (key.shader) { TODO; } + if (key.colorFilter) { TODO; } + Color src = unpack_8888(uniform32(uniforms, offsetof(Uniforms, paint_color))); + + // There are several orderings here of when we load dst and coverage + // and how coverage is applied, and to complicate things, LCD coverage + // needs to know dst.a. We're careful to assert it's loaded in time. + Color dst; + SkDEBUGCODE(bool dst_loaded = false;) + + // load_coverage() returns false when there's no need to apply coverage. + auto load_coverage = [&](Color* cov) { + switch (key.coverage) { + case Coverage::Full: return false; + + case Coverage::UniformA8: cov->r = cov->g = cov->b = cov->a = + uniform8(uniforms, offsetof(Uniforms, coverage)); + return true; + + case Coverage::MaskA8: cov->r = cov->g = cov->b = cov->a = + load8(varying()); + return true; + + case Coverage::MaskLCD16: + SkASSERT(dst_loaded); + *cov = unpack_565(load16(varying())); + cov->a = select(lt(src.a, dst.a), min(cov->r, min(cov->g,cov->b)) + , max(cov->r, max(cov->g,cov->b))); + return true; + + case Coverage::Mask3D: TODO; + } + // GCC insists... + return false; + }; + + // The math for some blend modes lets us fold coverage into src before the blend, + // obviating the need for the lerp afterwards. This early-coverage strategy tends + // to be both faster and require fewer registers. + bool lerp_coverage_post_blend = true; + if (SkBlendMode_ShouldPreScaleCoverage(key.blendMode, + key.coverage == Coverage::MaskLCD16)) { + Color cov; + if (load_coverage(&cov)) { + src.r = div255(mul(src.r, cov.r)); + src.g = div255(mul(src.g, cov.g)); + src.b = div255(mul(src.b, cov.b)); + src.a = div255(mul(src.a, cov.a)); + } + lerp_coverage_post_blend = false; + } + + // Load up the destination color. + SkDEBUGCODE(dst_loaded = true;) + switch (key.colorType) { + default: TODO; + case kRGB_565_SkColorType: dst = unpack_565 (load16(dst_ptr)); break; + case kRGBA_8888_SkColorType: dst = unpack_8888(load32(dst_ptr)); break; + case kBGRA_8888_SkColorType: dst = unpack_8888(load32(dst_ptr)); + std::swap(dst.r, dst.b); + break; + } + + // When a destination is tagged opaque, we may assume it both starts and stays fully + // opaque, ignoring any math that disagrees. So anything involving force_opaque is + // optional, and sometimes helps cut a small amount of work in these programs. + const bool force_opaque = true && key.alphaType == kOpaque_SkAlphaType; + if (force_opaque) { dst.a = splat(0xff); } + + // We'd need to premul dst after loading and unpremul before storing. + if (key.alphaType == kUnpremul_SkAlphaType) { TODO; } + + // Blend src and dst. + switch (key.blendMode) { + default: TODO; + + case SkBlendMode::kSrc: break; + + case SkBlendMode::kSrcOver: { + src.r = add(src.r, div255(mul(dst.r, inv(src.a)))); + src.g = add(src.g, div255(mul(dst.g, inv(src.a)))); + src.b = add(src.b, div255(mul(dst.b, inv(src.a)))); + src.a = add(src.a, div255(mul(dst.a, inv(src.a)))); + } break; + } + + // Lerp with coverage post-blend if needed. + Color cov; + if (lerp_coverage_post_blend && load_coverage(&cov)) { + src.r = mix(dst.r, src.r, cov.r); + src.g = mix(dst.g, src.g, cov.g); + src.b = mix(dst.b, src.b, cov.b); + src.a = mix(dst.a, src.a, cov.a); + } + + if (force_opaque) { src.a = splat(0xff); } + + // Store back to the destination. + switch (key.colorType) { + default: SkUNREACHABLE; + + case kRGB_565_SkColorType: store16(dst_ptr, pack_565(src)); break; + + case kBGRA_8888_SkColorType: std::swap(src.r, src.b); // fallthrough + case kRGBA_8888_SkColorType: store32(dst_ptr, pack_8888(src)); break; + } + #undef TODO + } + }; + + class Blitter final : public SkBlitter { + public: + bool ok = false; + + Blitter(const SkPixmap& device, const SkPaint& paint) + : fDevice(device) + , fKey { + device.colorType(), + device.alphaType(), + Coverage::Full, + paint.getBlendMode(), + paint.getShader(), + paint.getColorFilter(), + } + { + SkColor4f color = paint.getColor4f(); + SkColorSpaceXformSteps{sk_srgb_singleton(), kUnpremul_SkAlphaType, + device.colorSpace(), kUnpremul_SkAlphaType}.apply(color.vec()); + + if (color.fitsInBytes() && Builder::CanBuild(fKey)) { + fUniforms.paint_color = color.premul().toBytes_RGBA(); + ok = true; + } + } + + ~Blitter() override { + if (SkLRUCache* cache = try_acquire_program_cache()) { + auto cache_program = [&](skvm::Program&& program, Coverage coverage) { + if (!program.empty()) { + Key key = fKey.withCoverage(coverage); + if (skvm::Program* found = cache->find(key)) { + *found = std::move(program); + } else { + cache->insert(key, std::move(program)); + } + } + }; + cache_program(std::move(fBlitH), Coverage::Full); + cache_program(std::move(fBlitAntiH), Coverage::UniformA8); + cache_program(std::move(fBlitMaskA8), Coverage::MaskA8); + cache_program(std::move(fBlitMaskLCD16), Coverage::MaskLCD16); + + release_program_cache(); + } + } + + private: + SkPixmap fDevice; // TODO: can this be const&? + const Key fKey; + Uniforms fUniforms; + skvm::Program fBlitH, + fBlitAntiH, + fBlitMaskA8, + fBlitMaskLCD16; + + skvm::Program buildProgram(Coverage coverage) { + Key key = fKey.withCoverage(coverage); + { + skvm::Program p; + if (SkLRUCache* cache = try_acquire_program_cache()) { + if (skvm::Program* found = cache->find(key)) { + p = std::move(*found); + } + release_program_cache(); + } + if (!p.empty()) { + return p; + } + } + #if 0 + static std::atomic done{0}; + if (0 == done++) { + atexit([]{ SkDebugf("%d calls to done\n", done.load()); }); + } + #endif + Builder builder{key}; + skvm::Program program = builder.done(debug_name(key).c_str()); + if (!program.hasJIT() && debug_dump(key)) { + SkDebugf("\nfalling back to interpreter for blitter with this key.\n"); + builder.dump(); + program.dump(); + } + return program; + } + + void blitH(int x, int y, int w) override { + if (fBlitH.empty()) { + fBlitH = this->buildProgram(Coverage::Full); + } + fBlitH.eval(w, &fUniforms, fDevice.addr(x,y)); + } + + void blitAntiH(int x, int y, const SkAlpha cov[], const int16_t runs[]) override { + if (fBlitAntiH.empty()) { + fBlitAntiH = this->buildProgram(Coverage::UniformA8); + } + for (int16_t run = *runs; run > 0; run = *runs) { + fUniforms.coverage = *cov; + fBlitAntiH.eval(run, &fUniforms, fDevice.addr(x,y)); + + x += run; + runs += run; + cov += run; + } + } + + void blitMask(const SkMask& mask, const SkIRect& clip) override { + if (mask.fFormat == SkMask::kBW_Format) { + // TODO: native BW masks? + return SkBlitter::blitMask(mask, clip); + } + + const skvm::Program* program = nullptr; + switch (mask.fFormat) { + default: SkUNREACHABLE; // ARGB and SDF masks shouldn't make it here. + + case SkMask::k3D_Format: // TODO: the mul and add 3D mask planes too + case SkMask::kA8_Format: + if (fBlitMaskA8.empty()) { + fBlitMaskA8 = this->buildProgram(Coverage::MaskA8); + } + program = &fBlitMaskA8; + break; + + case SkMask::kLCD16_Format: + if (fBlitMaskLCD16.empty()) { + fBlitMaskLCD16 = this->buildProgram(Coverage::MaskLCD16); + } + program = &fBlitMaskLCD16; + break; + } + + SkASSERT(program); + if (program) { + for (int y = clip.top(); y < clip.bottom(); y++) { + program->eval(clip.width(), + &fUniforms, + fDevice.addr(clip.left(), y), + mask.getAddr(clip.left(), y)); + } + } + } + }; + +} // namespace + + +SkBlitter* SkCreateSkVMBlitter(const SkPixmap& device, + const SkPaint& paint, + const SkMatrix& ctm, + SkArenaAlloc* alloc) { + auto blitter = alloc->make(device, paint); + return blitter->ok ? blitter + : nullptr; +} diff --git a/gfx/skia/skia/src/core/SkValidatingReadBuffer.h b/gfx/skia/skia/src/core/SkValidatingReadBuffer.h new file mode 100644 index 0000000000..887c621f84 --- /dev/null +++ b/gfx/skia/skia/src/core/SkValidatingReadBuffer.h @@ -0,0 +1,23 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkValidatingReadBuffer_DEFINED +#define SkValidatingReadBuffer_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRefCnt.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkReader32.h" +#include "src/core/SkWriteBuffer.h" + +class SkBitmap; + +// DEPRECATED -- just use SkReadBuffer (so we can delete this header) +typedef SkReadBuffer SkValidatingReadBuffer; + +#endif diff --git a/gfx/skia/skia/src/core/SkValidationUtils.h b/gfx/skia/skia/src/core/SkValidationUtils.h new file mode 100644 index 0000000000..16dfdb3199 --- /dev/null +++ b/gfx/skia/skia/src/core/SkValidationUtils.h @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkValidationUtils_DEFINED +#define SkValidationUtils_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkBlendMode.h" +#include "src/core/SkXfermodePriv.h" + +/** Returns true if mode's value is in the SkBlendMode enum. + */ +static inline bool SkIsValidMode(SkBlendMode mode) { + return (unsigned)mode <= (unsigned)SkBlendMode::kLastMode; +} + +/** Returns true if the rect's dimensions are between 0 and SK_MaxS32 + */ +static inline bool SkIsValidIRect(const SkIRect& rect) { + return rect.width() >= 0 && rect.height() >= 0; +} + +/** Returns true if the rect's dimensions are between 0 and SK_ScalarMax + */ +static inline bool SkIsValidRect(const SkRect& rect) { + return (rect.fLeft <= rect.fRight) && + (rect.fTop <= rect.fBottom) && + SkScalarIsFinite(rect.width()) && + SkScalarIsFinite(rect.height()); +} + +#endif diff --git a/gfx/skia/skia/src/core/SkVertState.cpp b/gfx/skia/skia/src/core/SkVertState.cpp new file mode 100644 index 0000000000..d10a23ddde --- /dev/null +++ b/gfx/skia/skia/src/core/SkVertState.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkVertState.h" + +bool VertState::Triangles(VertState* state) { + int index = state->fCurrIndex; + if (index + 3 > state->fCount) { + return false; + } + state->f0 = index + 0; + state->f1 = index + 1; + state->f2 = index + 2; + state->fCurrIndex = index + 3; + return true; +} + +bool VertState::TrianglesX(VertState* state) { + const uint16_t* indices = state->fIndices; + int index = state->fCurrIndex; + if (index + 3 > state->fCount) { + return false; + } + state->f0 = indices[index + 0]; + state->f1 = indices[index + 1]; + state->f2 = indices[index + 2]; + state->fCurrIndex = index + 3; + return true; +} + +bool VertState::TriangleStrip(VertState* state) { + int index = state->fCurrIndex; + if (index + 3 > state->fCount) { + return false; + } + state->f2 = index + 2; + if (index & 1) { + state->f0 = index + 1; + state->f1 = index + 0; + } else { + state->f0 = index + 0; + state->f1 = index + 1; + } + state->fCurrIndex = index + 1; + return true; +} + +bool VertState::TriangleStripX(VertState* state) { + const uint16_t* indices = state->fIndices; + int index = state->fCurrIndex; + if (index + 3 > state->fCount) { + return false; + } + state->f2 = indices[index + 2]; + if (index & 1) { + state->f0 = indices[index + 1]; + state->f1 = indices[index + 0]; + } else { + state->f0 = indices[index + 0]; + state->f1 = indices[index + 1]; + } + state->fCurrIndex = index + 1; + return true; +} + +bool VertState::TriangleFan(VertState* state) { + int index = state->fCurrIndex; + if (index + 3 > state->fCount) { + return false; + } + state->f0 = 0; + state->f1 = index + 1; + state->f2 = index + 2; + state->fCurrIndex = index + 1; + return true; +} + +bool VertState::TriangleFanX(VertState* state) { + const uint16_t* indices = state->fIndices; + int index = state->fCurrIndex; + if (index + 3 > state->fCount) { + return false; + } + state->f0 = indices[0]; + state->f1 = indices[index + 1]; + state->f2 = indices[index + 2]; + state->fCurrIndex = index + 1; + return true; +} + +VertState::Proc VertState::chooseProc(SkVertices::VertexMode mode) { + switch (mode) { + case SkVertices::kTriangles_VertexMode: + return fIndices ? TrianglesX : Triangles; + case SkVertices::kTriangleStrip_VertexMode: + return fIndices ? TriangleStripX : TriangleStrip; + case SkVertices::kTriangleFan_VertexMode: + return fIndices ? TriangleFanX : TriangleFan; + default: + return nullptr; + } +} diff --git a/gfx/skia/skia/src/core/SkVertState.h b/gfx/skia/skia/src/core/SkVertState.h new file mode 100644 index 0000000000..fb981b7c2e --- /dev/null +++ b/gfx/skia/skia/src/core/SkVertState.h @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkVertState_DEFINED +#define SkVertState_DEFINED + +#include "include/core/SkVertices.h" + +/** \struct VertState + This is a helper for drawVertices(). It is used to iterate over the triangles + that are to be rendered based on an SkCanvas::VertexMode and (optionally) an + index array. It does not copy the index array and the client must ensure it + remains valid for the lifetime of the VertState object. +*/ + +struct VertState { + int f0, f1, f2; + + /** + * Construct a VertState from a vertex count, index array, and index count. + * If the vertices are unindexed pass nullptr for indices. + */ + VertState(int vCount, const uint16_t indices[], int indexCount) + : fIndices(indices) { + fCurrIndex = 0; + if (indices) { + fCount = indexCount; + } else { + fCount = vCount; + } + } + + typedef bool (*Proc)(VertState*); + + /** + * Choose an appropriate function to traverse the vertices. + * @param mode Specifies the SkCanvas::VertexMode. + */ + Proc chooseProc(SkVertices::VertexMode mode); + +private: + int fCount; + int fCurrIndex; + const uint16_t* fIndices; + + static bool Triangles(VertState*); + static bool TrianglesX(VertState*); + static bool TriangleStrip(VertState*); + static bool TriangleStripX(VertState*); + static bool TriangleFan(VertState*); + static bool TriangleFanX(VertState*); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkVertices.cpp b/gfx/skia/skia/src/core/SkVertices.cpp new file mode 100644 index 0000000000..dc2070372c --- /dev/null +++ b/gfx/skia/skia/src/core/SkVertices.cpp @@ -0,0 +1,431 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkVertices.h" + +#include "include/core/SkData.h" +#include "include/private/SkTo.h" +#include "src/core/SkReader32.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkSafeRange.h" +#include "src/core/SkWriter32.h" +#include +#include + +static int32_t next_id() { + static std::atomic nextID{1}; + + int32_t id; + do { + id = nextID++; + } while (id == SK_InvalidGenID); + return id; +} + +struct SkVertices::Sizes { + Sizes(SkVertices::VertexMode mode, int vertexCount, int indexCount, bool hasTexs, + bool hasColors, bool hasBones) { + SkSafeMath safe; + + fVSize = safe.mul(vertexCount, sizeof(SkPoint)); + fTSize = hasTexs ? safe.mul(vertexCount, sizeof(SkPoint)) : 0; + fCSize = hasColors ? safe.mul(vertexCount, sizeof(SkColor)) : 0; + fBISize = hasBones ? safe.mul(vertexCount, sizeof(BoneIndices)) : 0; + fBWSize = hasBones ? safe.mul(vertexCount, sizeof(BoneWeights)) : 0; + + fBuilderTriFanISize = 0; + fISize = safe.mul(indexCount, sizeof(uint16_t)); + if (kTriangleFan_VertexMode == mode) { + int numFanTris = 0; + if (indexCount) { + fBuilderTriFanISize = fISize; + numFanTris = indexCount - 2; + } else { + numFanTris = vertexCount - 2; + // By forcing this to become indexed we are adding a constraint to the maximum + // number of vertices. + if (vertexCount > (SkTo(UINT16_MAX) + 1)) { + sk_bzero(this, sizeof(*this)); + return; + } + } + if (numFanTris <= 0) { + sk_bzero(this, sizeof(*this)); + return; + } + fISize = safe.mul(numFanTris, 3 * sizeof(uint16_t)); + } + + fTotal = safe.add(sizeof(SkVertices), + safe.add(fVSize, + safe.add(fTSize, + safe.add(fCSize, + safe.add(fBISize, + safe.add(fBWSize, + fISize)))))); + + if (safe.ok()) { + fArrays = fTotal - sizeof(SkVertices); // just the sum of the arrays + } else { + sk_bzero(this, sizeof(*this)); + } + } + + bool isValid() const { return fTotal != 0; } + + size_t fTotal; // size of entire SkVertices allocation (obj + arrays) + size_t fArrays; // size of all the arrays (V + T + C + BI + BW + I) + size_t fVSize; + size_t fTSize; + size_t fCSize; + size_t fBISize; + size_t fBWSize; + size_t fISize; + + // For indexed tri-fans this is the number of amount of space fo indices needed in the builder + // before conversion to indexed triangles (or zero if not indexed or not a triangle fan). + size_t fBuilderTriFanISize; +}; + +SkVertices::Builder::Builder(VertexMode mode, int vertexCount, int indexCount, + uint32_t builderFlags) { + bool hasTexs = SkToBool(builderFlags & SkVertices::kHasTexCoords_BuilderFlag); + bool hasColors = SkToBool(builderFlags & SkVertices::kHasColors_BuilderFlag); + bool hasBones = SkToBool(builderFlags & SkVertices::kHasBones_BuilderFlag); + bool isVolatile = !SkToBool(builderFlags & SkVertices::kIsNonVolatile_BuilderFlag); + this->init(mode, vertexCount, indexCount, isVolatile, + SkVertices::Sizes(mode, vertexCount, indexCount, hasTexs, hasColors, hasBones)); +} + +SkVertices::Builder::Builder(VertexMode mode, int vertexCount, int indexCount, bool isVolatile, + const SkVertices::Sizes& sizes) { + this->init(mode, vertexCount, indexCount, isVolatile, sizes); +} + +void SkVertices::Builder::init(VertexMode mode, int vertexCount, int indexCount, bool isVolatile, + const SkVertices::Sizes& sizes) { + if (!sizes.isValid()) { + return; // fVertices will already be null + } + + void* storage = ::operator new (sizes.fTotal); + if (sizes.fBuilderTriFanISize) { + fIntermediateFanIndices.reset(new uint8_t[sizes.fBuilderTriFanISize]); + } + + fVertices.reset(new (storage) SkVertices); + + // need to point past the object to store the arrays + char* ptr = (char*)storage + sizeof(SkVertices); + + fVertices->fPositions = (SkPoint*)ptr; ptr += sizes.fVSize; + fVertices->fTexs = sizes.fTSize ? (SkPoint*)ptr : nullptr; ptr += sizes.fTSize; + fVertices->fColors = sizes.fCSize ? (SkColor*)ptr : nullptr; ptr += sizes.fCSize; + fVertices->fBoneIndices = sizes.fBISize ? (BoneIndices*) ptr : nullptr; ptr += sizes.fBISize; + fVertices->fBoneWeights = sizes.fBWSize ? (BoneWeights*) ptr : nullptr; ptr += sizes.fBWSize; + fVertices->fIndices = sizes.fISize ? (uint16_t*)ptr : nullptr; + fVertices->fVertexCnt = vertexCount; + fVertices->fIndexCnt = indexCount; + fVertices->fIsVolatile = isVolatile; + fVertices->fMode = mode; + + // We defer assigning fBounds and fUniqueID until detach() is called +} + +sk_sp SkVertices::Builder::detach() { + if (fVertices) { + fVertices->fBounds.setBounds(fVertices->fPositions, fVertices->fVertexCnt); + if (fVertices->fMode == kTriangleFan_VertexMode) { + if (fIntermediateFanIndices.get()) { + SkASSERT(fVertices->fIndexCnt); + auto tempIndices = this->indices(); + for (int t = 0; t < fVertices->fIndexCnt - 2; ++t) { + fVertices->fIndices[3 * t + 0] = tempIndices[0]; + fVertices->fIndices[3 * t + 1] = tempIndices[t + 1]; + fVertices->fIndices[3 * t + 2] = tempIndices[t + 2]; + } + fVertices->fIndexCnt = 3 * (fVertices->fIndexCnt - 2); + } else { + SkASSERT(!fVertices->fIndexCnt); + for (int t = 0; t < fVertices->fVertexCnt - 2; ++t) { + fVertices->fIndices[3 * t + 0] = 0; + fVertices->fIndices[3 * t + 1] = SkToU16(t + 1); + fVertices->fIndices[3 * t + 2] = SkToU16(t + 2); + } + fVertices->fIndexCnt = 3 * (fVertices->fVertexCnt - 2); + } + fVertices->fMode = kTriangles_VertexMode; + } + fVertices->fUniqueID = next_id(); + return std::move(fVertices); // this will null fVertices after the return + } + return nullptr; +} + +int SkVertices::Builder::vertexCount() const { + return fVertices ? fVertices->vertexCount() : 0; +} + +int SkVertices::Builder::indexCount() const { + return fVertices ? fVertices->indexCount() : 0; +} + +bool SkVertices::Builder::isVolatile() const { + return fVertices ? fVertices->isVolatile() : true; +} + +SkPoint* SkVertices::Builder::positions() { + return fVertices ? const_cast(fVertices->positions()) : nullptr; +} + +SkPoint* SkVertices::Builder::texCoords() { + return fVertices ? const_cast(fVertices->texCoords()) : nullptr; +} + +SkColor* SkVertices::Builder::colors() { + return fVertices ? const_cast(fVertices->colors()) : nullptr; +} + +SkVertices::BoneIndices* SkVertices::Builder::boneIndices() { + return fVertices ? const_cast(fVertices->boneIndices()) : nullptr; +} + +SkVertices::BoneWeights* SkVertices::Builder::boneWeights() { + return fVertices ? const_cast(fVertices->boneWeights()) : nullptr; +} + +uint16_t* SkVertices::Builder::indices() { + if (!fVertices) { + return nullptr; + } + if (fIntermediateFanIndices) { + return reinterpret_cast(fIntermediateFanIndices.get()); + } + return const_cast(fVertices->indices()); +} + +/** Makes a copy of the SkVertices and applies a set of bones, then returns the deformed + vertices. + + @param bones The bones to apply. + @param boneCount The number of bones. + @return The transformed SkVertices. +*/ +sk_sp SkVertices::applyBones(const SkVertices::Bone bones[], int boneCount) const { + // If there aren't any bones, then nothing changes. + // We don't check if the SkVertices object has bone indices/weights because there is the case + // where the object can have no indices/weights but still have a world transform applied. + if (!bones || !boneCount) { + return sk_ref_sp(this); + } + SkASSERT(boneCount >= 1); + + // Copy the SkVertices. + sk_sp copy = SkVertices::MakeCopy(this->mode(), + this->vertexCount(), + this->positions(), + this->texCoords(), + this->colors(), + nullptr, + nullptr, + this->indexCount(), + this->indices()); + + // Transform the positions. + for (int i = 0; i < this->vertexCount(); i++) { + SkPoint& position = copy->fPositions[i]; + + // Apply the world transform. + position = bones[0].mapPoint(position); + + // Apply the bone deformations. + if (boneCount > 1) { + SkASSERT(this->boneIndices()); + SkASSERT(this->boneWeights()); + + SkPoint result = SkPoint::Make(0.0f, 0.0f); + const SkVertices::BoneIndices& indices = this->boneIndices()[i]; + const SkVertices::BoneWeights& weights = this->boneWeights()[i]; + for (int j = 0; j < 4; j++) { + int index = indices[j]; + float weight = weights[j]; + if (index == 0 || weight == 0.0f) { + continue; + } + SkASSERT(index < boneCount); + + // result += M * v * w. + result += bones[index].mapPoint(position) * weight; + } + position = result; + } + } + + // Recalculate the bounds. + copy->fBounds.setBounds(copy->fPositions, copy->fVertexCnt); + + return copy; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkVertices::MakeCopy(VertexMode mode, int vertexCount, + const SkPoint pos[], const SkPoint texs[], + const SkColor colors[], + const BoneIndices boneIndices[], + const BoneWeights boneWeights[], + int indexCount, const uint16_t indices[], + bool isVolatile) { + SkASSERT((!boneIndices && !boneWeights) || (boneIndices && boneWeights)); + Sizes sizes(mode, + vertexCount, + indexCount, + texs != nullptr, + colors != nullptr, + boneIndices != nullptr); + if (!sizes.isValid()) { + return nullptr; + } + + Builder builder(mode, vertexCount, indexCount, isVolatile, sizes); + SkASSERT(builder.isValid()); + + sk_careful_memcpy(builder.positions(), pos, sizes.fVSize); + sk_careful_memcpy(builder.texCoords(), texs, sizes.fTSize); + sk_careful_memcpy(builder.colors(), colors, sizes.fCSize); + sk_careful_memcpy(builder.boneIndices(), boneIndices, sizes.fBISize); + sk_careful_memcpy(builder.boneWeights(), boneWeights, sizes.fBWSize); + size_t isize = (mode == kTriangleFan_VertexMode) ? sizes.fBuilderTriFanISize : sizes.fISize; + sk_careful_memcpy(builder.indices(), indices, isize); + + return builder.detach(); +} + +size_t SkVertices::approximateSize() const { + Sizes sizes(fMode, + fVertexCnt, + fIndexCnt, + this->hasTexCoords(), + this->hasColors(), + this->hasBones()); + SkASSERT(sizes.isValid()); + return sizeof(SkVertices) + sizes.fArrays; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// storage = packed | vertex_count | index_count | pos[] | texs[] | colors[] | boneIndices[] | +// boneWeights[] | indices[] +// = header + arrays + +#define kMode_Mask 0x0FF +#define kHasTexs_Mask 0x100 +#define kHasColors_Mask 0x200 +#define kHasBones_Mask 0x400 +#define kIsNonVolatile_Mask 0x800 +#define kHeaderSize (3 * sizeof(uint32_t)) + +sk_sp SkVertices::encode() const { + // packed has room for addtional flags in the future (e.g. versioning) + uint32_t packed = static_cast(fMode); + SkASSERT((packed & ~kMode_Mask) == 0); // our mode fits in the mask bits + if (this->hasTexCoords()) { + packed |= kHasTexs_Mask; + } + if (this->hasColors()) { + packed |= kHasColors_Mask; + } + if (this->hasBones()) { + packed |= kHasBones_Mask; + } + if (!this->isVolatile()) { + packed |= kIsNonVolatile_Mask; + } + + Sizes sizes(fMode, + fVertexCnt, + fIndexCnt, + this->hasTexCoords(), + this->hasColors(), + this->hasBones()); + SkASSERT(sizes.isValid()); + SkASSERT(!sizes.fBuilderTriFanISize); + // need to force alignment to 4 for SkWriter32 -- will pad w/ 0s as needed + const size_t size = SkAlign4(kHeaderSize + sizes.fArrays); + + sk_sp data = SkData::MakeUninitialized(size); + SkWriter32 writer(data->writable_data(), data->size()); + + writer.write32(packed); + writer.write32(fVertexCnt); + writer.write32(fIndexCnt); + writer.write(fPositions, sizes.fVSize); + writer.write(fTexs, sizes.fTSize); + writer.write(fColors, sizes.fCSize); + writer.write(fBoneIndices, sizes.fBISize); + writer.write(fBoneWeights, sizes.fBWSize); + // if index-count is odd, we won't be 4-bytes aligned, so we call the pad version + writer.writePad(fIndices, sizes.fISize); + + return data; +} + +sk_sp SkVertices::Decode(const void* data, size_t length) { + if (length < kHeaderSize) { + return nullptr; + } + + SkReader32 reader(data, length); + SkSafeRange safe; + + const uint32_t packed = reader.readInt(); + const int vertexCount = safe.checkGE(reader.readInt(), 0); + const int indexCount = safe.checkGE(reader.readInt(), 0); + const VertexMode mode = safe.checkLE(packed & kMode_Mask, + SkVertices::kLast_VertexMode); + if (!safe) { + return nullptr; + } + const bool hasTexs = SkToBool(packed & kHasTexs_Mask); + const bool hasColors = SkToBool(packed & kHasColors_Mask); + const bool hasBones = SkToBool(packed & kHasBones_Mask); + const bool isVolatile = !SkToBool(packed & kIsNonVolatile_Mask); + Sizes sizes(mode, vertexCount, indexCount, hasTexs, hasColors, hasBones); + if (!sizes.isValid()) { + return nullptr; + } + // logically we can be only 2-byte aligned, but our buffer is always 4-byte aligned + if (SkAlign4(kHeaderSize + sizes.fArrays) != length) { + return nullptr; + } + + Builder builder(mode, vertexCount, indexCount, isVolatile, sizes); + + reader.read(builder.positions(), sizes.fVSize); + reader.read(builder.texCoords(), sizes.fTSize); + reader.read(builder.colors(), sizes.fCSize); + reader.read(builder.boneIndices(), sizes.fBISize); + reader.read(builder.boneWeights(), sizes.fBWSize); + size_t isize = (mode == kTriangleFan_VertexMode) ? sizes.fBuilderTriFanISize : sizes.fISize; + reader.read(builder.indices(), isize); + if (indexCount > 0) { + // validate that the indicies are in range + SkASSERT(indexCount == builder.indexCount()); + const uint16_t* indices = builder.indices(); + for (int i = 0; i < indexCount; ++i) { + if (indices[i] >= (unsigned)vertexCount) { + return nullptr; + } + } + } + return builder.detach(); +} + +void SkVertices::operator delete(void* p) +{ + ::operator delete(p); +} diff --git a/gfx/skia/skia/src/core/SkVptr.h b/gfx/skia/skia/src/core/SkVptr.h new file mode 100644 index 0000000000..2a33ad06c7 --- /dev/null +++ b/gfx/skia/skia/src/core/SkVptr.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkVptr_DEFINED +#define SkVptr_DEFINED + +#include +#include + +// Experimentally, see if we can get at the vptr of objects with one. + +template +static inline void* SkVptr(const T& object) { + static_assert(std::has_virtual_destructor::value, ""); + void* vptr; + memcpy(&vptr, (const void*)&object, sizeof(vptr)); + return vptr; +} + +#endif//SkVptr_DEFINED diff --git a/gfx/skia/skia/src/core/SkWriteBuffer.cpp b/gfx/skia/skia/src/core/SkWriteBuffer.cpp new file mode 100644 index 0000000000..f12099ab16 --- /dev/null +++ b/gfx/skia/skia/src/core/SkWriteBuffer.cpp @@ -0,0 +1,260 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkWriteBuffer.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkTo.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkPtrRecorder.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkBinaryWriteBuffer::SkBinaryWriteBuffer() + : fFactorySet(nullptr) + , fTFSet(nullptr) { +} + +SkBinaryWriteBuffer::SkBinaryWriteBuffer(void* storage, size_t storageSize) + : fFactorySet(nullptr) + , fTFSet(nullptr) + , fWriter(storage, storageSize) +{} + +SkBinaryWriteBuffer::~SkBinaryWriteBuffer() {} + +bool SkBinaryWriteBuffer::usingInitialStorage() const { + return fWriter.usingInitialStorage(); +} + +void SkBinaryWriteBuffer::writeByteArray(const void* data, size_t size) { + fWriter.write32(SkToU32(size)); + fWriter.writePad(data, size); +} + +void SkBinaryWriteBuffer::writeBool(bool value) { + fWriter.writeBool(value); +} + +void SkBinaryWriteBuffer::writeScalar(SkScalar value) { + fWriter.writeScalar(value); +} + +void SkBinaryWriteBuffer::writeScalarArray(const SkScalar* value, uint32_t count) { + fWriter.write32(count); + fWriter.write(value, count * sizeof(SkScalar)); +} + +void SkBinaryWriteBuffer::writeInt(int32_t value) { + fWriter.write32(value); +} + +void SkBinaryWriteBuffer::writeIntArray(const int32_t* value, uint32_t count) { + fWriter.write32(count); + fWriter.write(value, count * sizeof(int32_t)); +} + +void SkBinaryWriteBuffer::writeUInt(uint32_t value) { + fWriter.write32(value); +} + +void SkBinaryWriteBuffer::writeString(const char* value) { + fWriter.writeString(value); +} + +void SkBinaryWriteBuffer::writeColor(SkColor color) { + fWriter.write32(color); +} + +void SkBinaryWriteBuffer::writeColorArray(const SkColor* color, uint32_t count) { + fWriter.write32(count); + fWriter.write(color, count * sizeof(SkColor)); +} + +void SkBinaryWriteBuffer::writeColor4f(const SkColor4f& color) { + fWriter.write(&color, sizeof(SkColor4f)); +} + +void SkBinaryWriteBuffer::writeColor4fArray(const SkColor4f* color, uint32_t count) { + fWriter.write32(count); + fWriter.write(color, count * sizeof(SkColor4f)); +} + +void SkBinaryWriteBuffer::writePoint(const SkPoint& point) { + fWriter.writeScalar(point.fX); + fWriter.writeScalar(point.fY); +} + +void SkBinaryWriteBuffer::writePoint3(const SkPoint3& point) { + this->writePad32(&point, sizeof(SkPoint3)); +} + +void SkBinaryWriteBuffer::writePointArray(const SkPoint* point, uint32_t count) { + fWriter.write32(count); + fWriter.write(point, count * sizeof(SkPoint)); +} + +void SkBinaryWriteBuffer::writeMatrix(const SkMatrix& matrix) { + fWriter.writeMatrix(matrix); +} + +void SkBinaryWriteBuffer::writeIRect(const SkIRect& rect) { + fWriter.write(&rect, sizeof(SkIRect)); +} + +void SkBinaryWriteBuffer::writeRect(const SkRect& rect) { + fWriter.writeRect(rect); +} + +void SkBinaryWriteBuffer::writeRegion(const SkRegion& region) { + fWriter.writeRegion(region); +} + +void SkBinaryWriteBuffer::writePath(const SkPath& path) { + fWriter.writePath(path); +} + +size_t SkBinaryWriteBuffer::writeStream(SkStream* stream, size_t length) { + fWriter.write32(SkToU32(length)); + size_t bytesWritten = fWriter.readFromStream(stream, length); + if (bytesWritten < length) { + fWriter.reservePad(length - bytesWritten); + } + return bytesWritten; +} + +bool SkBinaryWriteBuffer::writeToStream(SkWStream* stream) const { + return fWriter.writeToStream(stream); +} + +/* Format: + * (subset) bounds + * size (31bits) + * data [ encoded, with raw width/height ] + */ +void SkBinaryWriteBuffer::writeImage(const SkImage* image) { + const SkIRect bounds = SkImage_getSubset(image); + this->writeIRect(bounds); + + sk_sp data; + if (fProcs.fImageProc) { + data = fProcs.fImageProc(const_cast(image), fProcs.fImageCtx); + } + if (!data) { + data = image->encodeToData(); + } + + size_t size = data ? data->size() : 0; + if (!SkTFitsIn(size)) { + size = 0; // too big to store + } + this->write32(SkToS32(size)); // writing 0 signals failure + if (size) { + this->writePad32(data->data(), size); + } +} + +void SkBinaryWriteBuffer::writeTypeface(SkTypeface* obj) { + // Write 32 bits (signed) + // 0 -- default font + // >0 -- index + // <0 -- custom (serial procs) + + if (obj == nullptr) { + fWriter.write32(0); + } else if (fProcs.fTypefaceProc) { + auto data = fProcs.fTypefaceProc(obj, fProcs.fTypefaceCtx); + if (data) { + size_t size = data->size(); + if (!SkTFitsIn(size)) { + size = 0; // fall back to default font + } + int32_t ssize = SkToS32(size); + fWriter.write32(-ssize); // negative to signal custom + if (size) { + this->writePad32(data->data(), size); + } + return; + } + // no data means fall through for std behavior + } + fWriter.write32(fTFSet ? fTFSet->add(obj) : 0); +} + +void SkBinaryWriteBuffer::writePaint(const SkPaint& paint) { + SkPaintPriv::Flatten(paint, *this); +} + +void SkBinaryWriteBuffer::setFactoryRecorder(sk_sp rec) { + fFactorySet = std::move(rec); +} + +void SkBinaryWriteBuffer::setTypefaceRecorder(sk_sp rec) { + fTFSet = std::move(rec); +} + +void SkBinaryWriteBuffer::writeFlattenable(const SkFlattenable* flattenable) { + if (nullptr == flattenable) { + this->write32(0); + return; + } + + /* + * We can write 1 of 2 versions of the flattenable: + * 1. index into fFactorySet : This assumes the writer will later + * resolve the function-ptrs into strings for its reader. SkPicture + * does exactly this, by writing a table of names (matching the indices) + * up front in its serialized form. + * 2. string name of the flattenable or index into fFlattenableDict: We + * store the string to allow the reader to specify its own factories + * after write time. In order to improve compression, if we have + * already written the string, we write its index instead. + */ + + SkFlattenable::Factory factory = flattenable->getFactory(); + SkASSERT(factory); + + if (fFactorySet) { + this->write32(fFactorySet->add(factory)); + } else { + + if (uint32_t* indexPtr = fFlattenableDict.find(factory)) { + // We will write the index as a 32-bit int. We want the first byte + // that we send to be zero - this will act as a sentinel that we + // have an index (not a string). This means that we will send the + // the index shifted left by 8. The remaining 24-bits should be + // plenty to store the index. Note that this strategy depends on + // being little endian. + SkASSERT(0 == *indexPtr >> 24); + this->write32(*indexPtr << 8); + } else { + const char* name = flattenable->getTypeName(); + SkASSERT(name); + // Otherwise write the string. Clients should not use the empty + // string as a name, or we will have a problem. + SkASSERT(0 != strcmp("", name)); + this->writeString(name); + + // Add key to dictionary. + fFlattenableDict.set(factory, fFlattenableDict.count() + 1); + } + } + + // make room for the size of the flattened object + (void)fWriter.reserve(sizeof(uint32_t)); + // record the current size, so we can subtract after the object writes. + size_t offset = fWriter.bytesWritten(); + // now flatten the object + flattenable->flatten(*this); + size_t objSize = fWriter.bytesWritten() - offset; + // record the obj's size + fWriter.overwriteTAt(offset - sizeof(uint32_t), SkToU32(objSize)); +} diff --git a/gfx/skia/skia/src/core/SkWriteBuffer.h b/gfx/skia/skia/src/core/SkWriteBuffer.h new file mode 100644 index 0000000000..5bd58e9156 --- /dev/null +++ b/gfx/skia/skia/src/core/SkWriteBuffer.h @@ -0,0 +1,139 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWriteBuffer_DEFINED +#define SkWriteBuffer_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkFlattenable.h" +#include "include/core/SkSerialProcs.h" +#include "include/private/SkTHash.h" +#include "src/core/SkWriter32.h" + +class SkFactorySet; +class SkImage; +class SkPath; +class SkRefCntSet; + +class SkWriteBuffer { +public: + SkWriteBuffer() {} + virtual ~SkWriteBuffer() {} + + virtual void writePad32(const void* buffer, size_t bytes) = 0; + + virtual void writeByteArray(const void* data, size_t size) = 0; + void writeDataAsByteArray(SkData* data) { + this->writeByteArray(data->data(), data->size()); + } + virtual void writeBool(bool value) = 0; + virtual void writeScalar(SkScalar value) = 0; + virtual void writeScalarArray(const SkScalar* value, uint32_t count) = 0; + virtual void writeInt(int32_t value) = 0; + virtual void writeIntArray(const int32_t* value, uint32_t count) = 0; + virtual void writeUInt(uint32_t value) = 0; + void write32(int32_t value) { + this->writeInt(value); + } + virtual void writeString(const char* value) = 0; + + virtual void writeFlattenable(const SkFlattenable* flattenable) = 0; + virtual void writeColor(SkColor color) = 0; + virtual void writeColorArray(const SkColor* color, uint32_t count) = 0; + virtual void writeColor4f(const SkColor4f& color) = 0; + virtual void writeColor4fArray(const SkColor4f* color, uint32_t count) = 0; + virtual void writePoint(const SkPoint& point) = 0; + virtual void writePointArray(const SkPoint* point, uint32_t count) = 0; + virtual void writePoint3(const SkPoint3& point) = 0; + virtual void writeMatrix(const SkMatrix& matrix) = 0; + virtual void writeIRect(const SkIRect& rect) = 0; + virtual void writeRect(const SkRect& rect) = 0; + virtual void writeRegion(const SkRegion& region) = 0; + virtual void writePath(const SkPath& path) = 0; + virtual size_t writeStream(SkStream* stream, size_t length) = 0; + virtual void writeImage(const SkImage*) = 0; + virtual void writeTypeface(SkTypeface* typeface) = 0; + virtual void writePaint(const SkPaint& paint) = 0; + + void setSerialProcs(const SkSerialProcs& procs) { fProcs = procs; } + +protected: + SkSerialProcs fProcs; + + friend class SkPicturePriv; // fProcs +}; + +/** + * Concrete implementation that serializes to a flat binary blob. + */ +class SkBinaryWriteBuffer : public SkWriteBuffer { +public: + SkBinaryWriteBuffer(); + SkBinaryWriteBuffer(void* initialStorage, size_t storageSize); + ~SkBinaryWriteBuffer() override; + + void write(const void* buffer, size_t bytes) { + fWriter.write(buffer, bytes); + } + void writePad32(const void* buffer, size_t bytes) override { + fWriter.writePad(buffer, bytes); + } + + void reset(void* storage = nullptr, size_t storageSize = 0) { + fWriter.reset(storage, storageSize); + } + + size_t bytesWritten() const { return fWriter.bytesWritten(); } + + // Returns true iff all of the bytes written so far are stored in the initial storage + // buffer provided in the constructor or the most recent call to reset. + bool usingInitialStorage() const; + + void writeByteArray(const void* data, size_t size) override; + void writeBool(bool value) override; + void writeScalar(SkScalar value) override; + void writeScalarArray(const SkScalar* value, uint32_t count) override; + void writeInt(int32_t value) override; + void writeIntArray(const int32_t* value, uint32_t count) override; + void writeUInt(uint32_t value) override; + void writeString(const char* value) override; + + void writeFlattenable(const SkFlattenable* flattenable) override; + void writeColor(SkColor color) override; + void writeColorArray(const SkColor* color, uint32_t count) override; + void writeColor4f(const SkColor4f& color) override; + void writeColor4fArray(const SkColor4f* color, uint32_t count) override; + void writePoint(const SkPoint& point) override; + void writePointArray(const SkPoint* point, uint32_t count) override; + void writePoint3(const SkPoint3& point) override; + void writeMatrix(const SkMatrix& matrix) override; + void writeIRect(const SkIRect& rect) override; + void writeRect(const SkRect& rect) override; + void writeRegion(const SkRegion& region) override; + void writePath(const SkPath& path) override; + size_t writeStream(SkStream* stream, size_t length) override; + void writeImage(const SkImage*) override; + void writeTypeface(SkTypeface* typeface) override; + void writePaint(const SkPaint& paint) override; + + bool writeToStream(SkWStream*) const; + void writeToMemory(void* dst) const { fWriter.flatten(dst); } + + void setFactoryRecorder(sk_sp); + void setTypefaceRecorder(sk_sp); + +private: + sk_sp fFactorySet; + sk_sp fTFSet; + + SkWriter32 fWriter; + + // Only used if we do not have an fFactorySet + SkTHashMap fFlattenableDict; +}; + +#endif // SkWriteBuffer_DEFINED diff --git a/gfx/skia/skia/src/core/SkWritePixelsRec.h b/gfx/skia/skia/src/core/SkWritePixelsRec.h new file mode 100644 index 0000000000..4f02c4e9de --- /dev/null +++ b/gfx/skia/skia/src/core/SkWritePixelsRec.h @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWritePixelsRec_DEFINED +#define SkWritePixelsRec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" + +/** + * Helper class to package and trim the parameters passed to writePixels() + */ +struct SkWritePixelsRec { + SkWritePixelsRec(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y) + : fPixels(pixels) + , fRowBytes(rowBytes) + , fInfo(info) + , fX(x) + , fY(y) + {} + + SkWritePixelsRec(const SkPixmap& pm, int x, int y) + : fPixels(pm.addr()) + , fRowBytes(pm.rowBytes()) + , fInfo(pm.info()) + , fX(x) + , fY(y) + {} + + const void* fPixels; + size_t fRowBytes; + SkImageInfo fInfo; + int fX; + int fY; + + /* + * On true, may have modified its fields (except fRowBytes) to make it a legal subset + * of the specified dst width/height. + * + * On false, leaves self unchanged, but indicates that it does not overlap dst, or + * is not valid (e.g. bad fInfo) for writePixels(). + */ + bool trim(int dstWidth, int dstHeight); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkWriter32.cpp b/gfx/skia/skia/src/core/SkWriter32.cpp new file mode 100644 index 0000000000..cab2812962 --- /dev/null +++ b/gfx/skia/skia/src/core/SkWriter32.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/private/SkTo.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkReader32.h" + +#include "src/core/SkWriter32.h" + +void SkWriter32::writeMatrix(const SkMatrix& matrix) { + size_t size = SkMatrixPriv::WriteToMemory(matrix, nullptr); + SkASSERT(SkAlign4(size) == size); + SkMatrixPriv::WriteToMemory(matrix, this->reserve(size)); +} + +/* + * Strings are stored as: length[4-bytes] + string_data + '\0' + pad_to_mul_4 + */ + +const char* SkReader32::readString(size_t* outLen) { + size_t len = this->readU32(); + const void* ptr = this->peek(); + + // skip over the string + '\0' and then pad to a multiple of 4 + size_t alignedSize = SkAlign4(len + 1); + this->skip(alignedSize); + + if (outLen) { + *outLen = len; + } + return (const char*)ptr; +} + +size_t SkReader32::readIntoString(SkString* copy) { + size_t len; + const char* ptr = this->readString(&len); + if (copy) { + copy->set(ptr, len); + } + return len; +} + +void SkWriter32::writeString(const char str[], size_t len) { + if (nullptr == str) { + str = ""; + len = 0; + } + if ((long)len < 0) { + len = strlen(str); + } + + // [ 4 byte len ] [ str ... ] [1 - 4 \0s] + uint32_t* ptr = this->reservePad(sizeof(uint32_t) + len + 1); + *ptr = SkToU32(len); + char* chars = (char*)(ptr + 1); + memcpy(chars, str, len); + chars[len] = '\0'; +} + +size_t SkWriter32::WriteStringSize(const char* str, size_t len) { + if ((long)len < 0) { + SkASSERT(str); + len = strlen(str); + } + const size_t lenBytes = 4; // we use 4 bytes to record the length + // add 1 since we also write a terminating 0 + return SkAlign4(lenBytes + len + 1); +} + +void SkWriter32::growToAtLeast(size_t size) { + const bool wasExternal = (fExternal != nullptr) && (fData == fExternal); + + fCapacity = 4096 + SkTMax(size, fCapacity + (fCapacity / 2)); + fInternal.realloc(fCapacity); + fData = fInternal.get(); + + if (wasExternal) { + // we were external, so copy in the data + memcpy(fData, fExternal, fUsed); + } +} + +sk_sp SkWriter32::snapshotAsData() const { + return SkData::MakeWithCopy(fData, fUsed); +} diff --git a/gfx/skia/skia/src/core/SkWriter32.h b/gfx/skia/skia/src/core/SkWriter32.h new file mode 100644 index 0000000000..3f7d305357 --- /dev/null +++ b/gfx/skia/skia/src/core/SkWriter32.h @@ -0,0 +1,282 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWriter32_DEFINED +#define SkWriter32_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRect.h" +#include "include/core/SkRegion.h" +#include "include/core/SkScalar.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" + +class SK_API SkWriter32 : SkNoncopyable { +public: + /** + * The caller can specify an initial block of storage, which the caller manages. + * + * SkWriter32 will try to back reserve and write calls with this external storage until the + * first time an allocation doesn't fit. From then it will use dynamically allocated storage. + * This used to be optional behavior, but pipe now relies on it. + */ + SkWriter32(void* external = nullptr, size_t externalBytes = 0) { + this->reset(external, externalBytes); + } + + // return the current offset (will always be a multiple of 4) + size_t bytesWritten() const { return fUsed; } + + // Returns true iff all of the bytes written so far are stored in the initial storage + // buffer provided in the constructor or the most recent call to reset. + bool usingInitialStorage() const { return fData == fExternal; } + + void reset(void* external = nullptr, size_t externalBytes = 0) { + // we cast this pointer to int* and float* at times, so assert that it is aligned. + SkASSERT(SkIsAlign4((uintptr_t)external)); + // we always write multiples of 4-bytes, so truncate down the size to match that + externalBytes &= ~3; + + fData = (uint8_t*)external; + fCapacity = externalBytes; + fUsed = 0; + fExternal = external; + } + + // size MUST be multiple of 4 + uint32_t* reserve(size_t size) { + SkASSERT(SkAlign4(size) == size); + size_t offset = fUsed; + size_t totalRequired = fUsed + size; + if (totalRequired > fCapacity) { + this->growToAtLeast(totalRequired); + } + fUsed = totalRequired; + return (uint32_t*)(fData + offset); + } + + /** + * Read a T record at offset, which must be a multiple of 4. Only legal if the record + * was written atomically using the write methods below. + */ + template + const T& readTAt(size_t offset) const { + SkASSERT(SkAlign4(offset) == offset); + SkASSERT(offset < fUsed); + return *(T*)(fData + offset); + } + + /** + * Overwrite a T record at offset, which must be a multiple of 4. Only legal if the record + * was written atomically using the write methods below. + */ + template + void overwriteTAt(size_t offset, const T& value) { + SkASSERT(SkAlign4(offset) == offset); + SkASSERT(offset < fUsed); + *(T*)(fData + offset) = value; + } + + bool writeBool(bool value) { + this->write32(value); + return value; + } + + void writeInt(int32_t value) { + this->write32(value); + } + + void write8(int32_t value) { + *(int32_t*)this->reserve(sizeof(value)) = value & 0xFF; + } + + void write16(int32_t value) { + *(int32_t*)this->reserve(sizeof(value)) = value & 0xFFFF; + } + + void write32(int32_t value) { + *(int32_t*)this->reserve(sizeof(value)) = value; + } + + void writePtr(void* value) { + // this->reserve() only returns 4-byte aligned pointers, + // so this may be an under-aligned write if we were to do this like the others. + memcpy(this->reserve(sizeof(value)), &value, sizeof(value)); + } + + void writeScalar(SkScalar value) { + *(SkScalar*)this->reserve(sizeof(value)) = value; + } + + void writePoint(const SkPoint& pt) { + *(SkPoint*)this->reserve(sizeof(pt)) = pt; + } + + void writePoint3(const SkPoint3& pt) { + *(SkPoint3*)this->reserve(sizeof(pt)) = pt; + } + + void writeRect(const SkRect& rect) { + *(SkRect*)this->reserve(sizeof(rect)) = rect; + } + + void writeIRect(const SkIRect& rect) { + *(SkIRect*)this->reserve(sizeof(rect)) = rect; + } + + void writeRRect(const SkRRect& rrect) { + rrect.writeToMemory(this->reserve(SkRRect::kSizeInMemory)); + } + + void writePath(const SkPath& path) { + size_t size = path.writeToMemory(nullptr); + SkASSERT(SkAlign4(size) == size); + path.writeToMemory(this->reserve(size)); + } + + void writeMatrix(const SkMatrix& matrix); + + void writeRegion(const SkRegion& rgn) { + size_t size = rgn.writeToMemory(nullptr); + SkASSERT(SkAlign4(size) == size); + rgn.writeToMemory(this->reserve(size)); + } + + // write count bytes (must be a multiple of 4) + void writeMul4(const void* values, size_t size) { + this->write(values, size); + } + + /** + * Write size bytes from values. size must be a multiple of 4, though + * values need not be 4-byte aligned. + */ + void write(const void* values, size_t size) { + SkASSERT(SkAlign4(size) == size); + sk_careful_memcpy(this->reserve(size), values, size); + } + + /** + * Reserve size bytes. Does not need to be 4 byte aligned. The remaining space (if any) will be + * filled in with zeroes. + */ + uint32_t* reservePad(size_t size) { + size_t alignedSize = SkAlign4(size); + uint32_t* p = this->reserve(alignedSize); + if (alignedSize != size) { + SkASSERT(alignedSize >= 4); + p[alignedSize / 4 - 1] = 0; + } + return p; + } + + /** + * Write size bytes from src, and pad to 4 byte alignment with zeroes. + */ + void writePad(const void* src, size_t size) { + sk_careful_memcpy(this->reservePad(size), src, size); + } + + /** + * Writes a string to the writer, which can be retrieved with + * SkReader32::readString(). + * The length can be specified, or if -1 is passed, it will be computed by + * calling strlen(). The length must be < max size_t. + * + * If you write NULL, it will be read as "". + */ + void writeString(const char* str, size_t len = (size_t)-1); + + /** + * Computes the size (aligned to multiple of 4) need to write the string + * in a call to writeString(). If the length is not specified, it will be + * computed by calling strlen(). + */ + static size_t WriteStringSize(const char* str, size_t len = (size_t)-1); + + void writeData(const SkData* data) { + uint32_t len = data ? SkToU32(data->size()) : 0; + this->write32(len); + if (data) { + this->writePad(data->data(), len); + } + } + + static size_t WriteDataSize(const SkData* data) { + return 4 + SkAlign4(data ? data->size() : 0); + } + + /** + * Move the cursor back to offset bytes from the beginning. + * offset must be a multiple of 4 no greater than size(). + */ + void rewindToOffset(size_t offset) { + SkASSERT(SkAlign4(offset) == offset); + SkASSERT(offset <= bytesWritten()); + fUsed = offset; + } + + // copy into a single buffer (allocated by caller). Must be at least size() + void flatten(void* dst) const { + memcpy(dst, fData, fUsed); + } + + bool writeToStream(SkWStream* stream) const { + return stream->write(fData, fUsed); + } + + // read from the stream, and write up to length bytes. Return the actual + // number of bytes written. + size_t readFromStream(SkStream* stream, size_t length) { + return stream->read(this->reservePad(length), length); + } + + /** + * Captures a snapshot of the data as it is right now, and return it. + */ + sk_sp snapshotAsData() const; +private: + void growToAtLeast(size_t size); + + uint8_t* fData; // Points to either fInternal or fExternal. + size_t fCapacity; // Number of bytes we can write to fData. + size_t fUsed; // Number of bytes written. + void* fExternal; // Unmanaged memory block. + SkAutoTMalloc fInternal; // Managed memory block. +}; + +/** + * Helper class to allocated SIZE bytes as part of the writer, and to provide + * that storage to the constructor as its initial storage buffer. + * + * This wrapper ensures proper alignment rules are met for the storage. + */ +template class SkSWriter32 : public SkWriter32 { +public: + SkSWriter32() { this->reset(); } + + void reset() {this->INHERITED::reset(fData.fStorage, SIZE); } + +private: + union { + void* fPtrAlignment; + double fDoubleAlignment; + char fStorage[SIZE]; + } fData; + + typedef SkWriter32 INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkXfermode.cpp b/gfx/skia/skia/src/core/SkXfermode.cpp new file mode 100644 index 0000000000..71a13dcc3a --- /dev/null +++ b/gfx/skia/skia/src/core/SkXfermode.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/private/SkColorData.h" +#include "include/private/SkOnce.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/core/SkXfermodePriv.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/effects/GrCustomXfermode.h" +#include "src/gpu/effects/GrPorterDuffXferProcessor.h" +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkProcCoeffXfermode : public SkXfermode { +public: + SkProcCoeffXfermode(SkBlendMode mode) : fMode(mode) {} + + void xfer32(SkPMColor dst[], const SkPMColor src[], int count, + const SkAlpha aa[]) const override { + SkASSERT(dst && src && count >= 0); + + SkRasterPipeline_<256> p; + + SkRasterPipeline_MemoryCtx dst_ctx = { (void*)dst, 0 }, + src_ctx = { (void*)src, 0 }, + aa_ctx = { (void*)aa, 0 }; + + p.append_load (kN32_SkColorType, &src_ctx); + p.append_load_dst(kN32_SkColorType, &dst_ctx); + + if (SkBlendMode_ShouldPreScaleCoverage(fMode, /*rgb_coverage=*/false)) { + if (aa) { + p.append(SkRasterPipeline::scale_u8, &aa_ctx); + } + SkBlendMode_AppendStages(fMode, &p); + } else { + SkBlendMode_AppendStages(fMode, &p); + if (aa) { + p.append(SkRasterPipeline::lerp_u8, &aa_ctx); + } + } + + p.append_store(kN32_SkColorType, &dst_ctx); + p.run(0, 0, count,1); + } + +private: + const SkBlendMode fMode; + + typedef SkXfermode INHERITED; +}; + +const char* SkBlendMode_Name(SkBlendMode mode) { + SkASSERT((unsigned) mode <= (unsigned)SkBlendMode::kLastMode); + const char* gModeStrings[] = { + "Clear", "Src", "Dst", "SrcOver", "DstOver", "SrcIn", "DstIn", + "SrcOut", "DstOut", "SrcATop", "DstATop", "Xor", "Plus", + "Modulate", "Screen", "Overlay", "Darken", "Lighten", "ColorDodge", + "ColorBurn", "HardLight", "SoftLight", "Difference", "Exclusion", + "Multiply", "Hue", "Saturation", "Color", "Luminosity" + }; + return gModeStrings[(int)mode]; + static_assert(SK_ARRAY_COUNT(gModeStrings) == (size_t)SkBlendMode::kLastMode + 1, "mode_count"); +} + +sk_sp SkXfermode::Make(SkBlendMode mode) { + if ((unsigned)mode > (unsigned)SkBlendMode::kLastMode) { + // report error + return nullptr; + } + + // Skia's "default" mode is srcover. nullptr in SkPaint is interpreted as srcover + // so we can just return nullptr from the factory. + if (SkBlendMode::kSrcOver == mode) { + return nullptr; + } + + const int COUNT_BLENDMODES = (int)SkBlendMode::kLastMode + 1; + + static SkOnce once[COUNT_BLENDMODES]; + static SkXfermode* cached[COUNT_BLENDMODES]; + + once[(int)mode]([mode] { + if (auto xfermode = SkOpts::create_xfermode(mode)) { + cached[(int)mode] = xfermode; + } else { + cached[(int)mode] = new SkProcCoeffXfermode(mode); + } + }); + return sk_ref_sp(cached[(int)mode]); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkXfermode::IsOpaque(SkBlendMode mode, SrcColorOpacity opacityType) { + SkBlendModeCoeff src, dst; + if (!SkBlendMode_AsCoeff(mode, &src, &dst)) { + return false; + } + + switch (src) { + case SkBlendModeCoeff::kDA: + case SkBlendModeCoeff::kDC: + case SkBlendModeCoeff::kIDA: + case SkBlendModeCoeff::kIDC: + return false; + default: + break; + } + + switch (dst) { + case SkBlendModeCoeff::kZero: + return true; + case SkBlendModeCoeff::kISA: + return kOpaque_SrcColorOpacity == opacityType; + case SkBlendModeCoeff::kSA: + return kTransparentBlack_SrcColorOpacity == opacityType || + kTransparentAlpha_SrcColorOpacity == opacityType; + case SkBlendModeCoeff::kSC: + return kTransparentBlack_SrcColorOpacity == opacityType; + default: + return false; + } + return false; +} + +#if SK_SUPPORT_GPU +const GrXPFactory* SkBlendMode_AsXPFactory(SkBlendMode mode) { + if (SkBlendMode_AsCoeff(mode, nullptr, nullptr)) { + const GrXPFactory* result = GrPorterDuffXPFactory::Get(mode); + SkASSERT(result); + return result; + } + + SkASSERT(GrCustomXfermode::IsSupportedMode(mode)); + return GrCustomXfermode::Get(mode); +} +#endif + diff --git a/gfx/skia/skia/src/core/SkXfermodeInterpretation.cpp b/gfx/skia/skia/src/core/SkXfermodeInterpretation.cpp new file mode 100644 index 0000000000..95675a5d5f --- /dev/null +++ b/gfx/skia/skia/src/core/SkXfermodeInterpretation.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "src/core/SkXfermodeInterpretation.h" + +static bool just_solid_color(const SkPaint& p) { + return SK_AlphaOPAQUE == p.getAlpha() && !p.getColorFilter() && !p.getShader(); +} + +SkXfermodeInterpretation SkInterpretXfermode(const SkPaint& paint, bool dstIsOpaque) { + switch (paint.getBlendMode()) { + case SkBlendMode::kSrcOver: + return kSrcOver_SkXfermodeInterpretation; + case SkBlendMode::kSrc: + if (just_solid_color(paint)) { + return kSrcOver_SkXfermodeInterpretation; + } + return kNormal_SkXfermodeInterpretation; + case SkBlendMode::kDst: + return kSkipDrawing_SkXfermodeInterpretation; + case SkBlendMode::kDstOver: + if (dstIsOpaque) { + return kSkipDrawing_SkXfermodeInterpretation; + } + return kNormal_SkXfermodeInterpretation; + case SkBlendMode::kSrcIn: + if (dstIsOpaque && just_solid_color(paint)) { + return kSrcOver_SkXfermodeInterpretation; + } + return kNormal_SkXfermodeInterpretation; + case SkBlendMode::kDstIn: + if (just_solid_color(paint)) { + return kSkipDrawing_SkXfermodeInterpretation; + } + return kNormal_SkXfermodeInterpretation; + default: + return kNormal_SkXfermodeInterpretation; + } +} diff --git a/gfx/skia/skia/src/core/SkXfermodeInterpretation.h b/gfx/skia/skia/src/core/SkXfermodeInterpretation.h new file mode 100644 index 0000000000..d0a420f383 --- /dev/null +++ b/gfx/skia/skia/src/core/SkXfermodeInterpretation.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkXfermodeInterpretation_DEFINED +#define SkXfermodeInterpretation_DEFINED + +class SkPaint; + +/** + * By analyzing the paint, we may decide we can take special + * action. This enum lists our possible actions. + */ +enum SkXfermodeInterpretation { + kNormal_SkXfermodeInterpretation, //< draw normally + kSrcOver_SkXfermodeInterpretation, //< draw as if in srcover mode + kSkipDrawing_SkXfermodeInterpretation //< draw nothing +}; + +/** + * Given a paint, determine whether the paint's transfer mode can be + * replaced with kSrcOver_Mode or not drawn at all. This is used by + * SkBlitter and SkPDFDevice. + */ +SkXfermodeInterpretation SkInterpretXfermode(const SkPaint&, bool dstIsOpaque); + +#endif // SkXfermodeInterpretation_DEFINED diff --git a/gfx/skia/skia/src/core/SkXfermodePriv.h b/gfx/skia/skia/src/core/SkXfermodePriv.h new file mode 100644 index 0000000000..0b7a920d8e --- /dev/null +++ b/gfx/skia/skia/src/core/SkXfermodePriv.h @@ -0,0 +1,62 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkXfermodePriv_DEFINED +#define SkXfermodePriv_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/core/SkRefCnt.h" + +class GrFragmentProcessor; +class GrTexture; +class GrXPFactory; +class SkRasterPipeline; +class SkString; + +class SkXfermode : public SkRefCnt { +public: + virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count, + const SkAlpha aa[]) const = 0; + + /** Return an SkXfermode object for the specified mode. + */ + static sk_sp Make(SkBlendMode); + + /** + * Skia maintains global xfermode objects corresponding to each BlendMode. This returns a + * ptr to that global xfermode (or null if the mode is srcover). Thus the caller may use + * the returned ptr, but it should leave its refcnt untouched. + */ + static SkXfermode* Peek(SkBlendMode mode) { + sk_sp xfer = Make(mode); + if (!xfer) { + SkASSERT(SkBlendMode::kSrcOver == mode); + return nullptr; + } + SkASSERT(!xfer->unique()); + return xfer.get(); + } + + enum SrcColorOpacity { + // The src color is known to be opaque (alpha == 255) + kOpaque_SrcColorOpacity = 0, + // The src color is known to be fully transparent (color == 0) + kTransparentBlack_SrcColorOpacity = 1, + // The src alpha is known to be fully transparent (alpha == 0) + kTransparentAlpha_SrcColorOpacity = 2, + // The src color opacity is unknown + kUnknown_SrcColorOpacity = 3 + }; + + static bool IsOpaque(SkBlendMode, SrcColorOpacity); + +protected: + SkXfermode() {} +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkYUVASizeInfo.cpp b/gfx/skia/skia/src/core/SkYUVASizeInfo.cpp new file mode 100644 index 0000000000..9df8e9cbe1 --- /dev/null +++ b/gfx/skia/skia/src/core/SkYUVASizeInfo.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkYUVASizeInfo.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkSafeMath.h" + +size_t SkYUVASizeInfo::computeTotalBytes() const { + SkSafeMath safe; + size_t totalBytes = 0; + + for (int i = 0; i < kMaxCount; ++i) { + SkASSERT((!fSizes[i].isEmpty() && fWidthBytes[i]) || + (fSizes[i].isEmpty() && !fWidthBytes[i])); + totalBytes = safe.add(totalBytes, safe.mul(fWidthBytes[i], fSizes[i].height())); + } + + return safe.ok() ? totalBytes : SIZE_MAX; +} + +void SkYUVASizeInfo::computePlanes(void* base, void* planes[SkYUVASizeInfo::kMaxCount]) const { + planes[0] = base; + int i = 1; + for (; i < SkYUVASizeInfo::kMaxCount; ++i) { + if (fSizes[i].isEmpty()) { + break; + } + planes[i] = SkTAddOffset(planes[i - 1], fWidthBytes[i - 1] * fSizes[i - 1].height()); + } + for (; i < SkYUVASizeInfo::kMaxCount; ++i) { + planes[i] = nullptr; + } +} diff --git a/gfx/skia/skia/src/core/SkYUVMath.cpp b/gfx/skia/skia/src/core/SkYUVMath.cpp new file mode 100644 index 0000000000..091e6948de --- /dev/null +++ b/gfx/skia/skia/src/core/SkYUVMath.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix44.h" +#include "src/core/SkYUVMath.h" + +// in SkColorMatrix order (row-major) +// Created by running SkColorMatrix_DumpYUVMatrixTables() + +const float Rec709_rgb_to_yuv[] = { + 0.182586f, 0.614231f, 0.062007f, 0.000000f, 0.062745f, + -0.100644f, -0.338572f, 0.439216f, 0.000000f, 0.501961f, + 0.439216f, -0.398942f, -0.040274f, 0.000000f, 0.501961f, + 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, +}; +const float Rec709_yuv_to_rgb[] = { + 1.164384f, 0.000000f, 1.792741f, 0.000000f, -0.972945f, + 1.164384f, -0.213249f, -0.532909f, 0.000000f, 0.301483f, + 1.164384f, 2.112402f, 0.000000f, 0.000000f, -1.133402f, + 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, +}; +const float Rec601_rgb_to_yuv[] = { + 0.256788f, 0.504129f, 0.097906f, 0.000000f, 0.062745f, + -0.148223f, -0.290993f, 0.439216f, 0.000000f, 0.501961f, + 0.439216f, -0.367788f, -0.071427f, 0.000000f, 0.501961f, + 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, +}; +const float Rec601_yuv_to_rgb[] = { + 1.164384f, 0.000000f, 1.596027f, 0.000000f, -0.874202f, + 1.164384f, -0.391762f, -0.812968f, 0.000000f, 0.531668f, + 1.164384f, 2.017232f, 0.000000f, 0.000000f, -1.085631f, + 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, +}; +const float JPEG_rgb_to_yuv[] = { + 0.299000f, 0.587000f, 0.114000f, 0.000000f, 0.000000f, + -0.168736f, -0.331264f, 0.500000f, 0.000000f, 0.501961f, + 0.500000f, -0.418688f, -0.081312f, 0.000000f, 0.501961f, + 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, +}; +const float JPEG_yuv_to_rgb[] = { + 1.000000f, 0.000000f, 1.402000f, 0.000000f, -0.703749f, + 1.000000f, -0.344136f, -0.714136f, 0.000000f, 0.531211f, + 1.000000f, 1.772000f, 0.000000f, 0.000000f, -0.889475f, + 0.000000f, 0.000000f, 0.000000f, 1.000000f, 0.000000f, +}; + +static_assert(kJPEG_SkYUVColorSpace == 0, ""); +static_assert(kRec601_SkYUVColorSpace == 1, ""); +static_assert(kRec709_SkYUVColorSpace == 2, ""); + +const float* yuv_to_rgb_array[] = { + JPEG_yuv_to_rgb, + Rec601_yuv_to_rgb, + Rec709_yuv_to_rgb, +}; + +const float* rgb_to_yuv_array[] = { + JPEG_rgb_to_yuv, + Rec601_rgb_to_yuv, + Rec709_rgb_to_yuv, +}; + +constexpr size_t kSizeOfColorMatrix = 20 * sizeof(float); + +void SkColorMatrix_RGB2YUV(SkYUVColorSpace cs, float m[20]) { + if ((unsigned)cs < (unsigned)kIdentity_SkYUVColorSpace) { + memcpy(m, rgb_to_yuv_array[(unsigned)cs], kSizeOfColorMatrix); + } else { + memset(m, 0, kSizeOfColorMatrix); + m[0] = m[6] = m[12] = m[18] = 1; + } +} + +void SkColorMatrix_YUV2RGB(SkYUVColorSpace cs, float m[20]) { + if ((unsigned)cs < (unsigned)kIdentity_SkYUVColorSpace) { + memcpy(m, yuv_to_rgb_array[(unsigned)cs], kSizeOfColorMatrix); + } else { + memset(m, 0, kSizeOfColorMatrix); + m[0] = m[6] = m[12] = m[18] = 1; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// we just drop the alpha rol/col from the colormatrix +// output is | tr | +// | 3x3 tg | +// | tb | +// | 0 0 0 1 | +static void colormatrix_to_matrix44(const float src[20], SkMatrix44* dst) { + for (int r = 0; r < 3; ++r) { + for (int c = 0; c < 3; ++c) { + dst->set(r, c, src[r*5 + c]); + } + dst->set(r, 3, src[r*5 + 4]); + } + dst->set(3, 0, 0); + dst->set(3, 1, 0); + dst->set(3, 2, 0); + dst->set(3, 3, 1); +} + +// input: ignore the bottom row +// output: inject identity row/column for alpha +static void matrix44_to_colormatrix(const SkMatrix44& src, float dst[20]) { + for (int r = 0; r < 3; ++r) { + for (int c = 0; c < 3; ++c) { + dst[r*5 + c] = src.get(r, c); + } + dst[r*5 + 3] = 0; // scale alpha + dst[r*5 + 4] = src.get(r, 3); // translate + } + dst[15] = dst[16] = dst[17] = dst[19] = 0; + dst[18] = 1; +} + +static void scale3(float m[], float s) { + for (int i = 0; i < 3; ++i) { + m[i] *= s; + } +} + +namespace { +struct YUVCoeff { + float Kr, Kb; + float Cr, Cb; + float scaleY, addY; + float scaleUV; +}; +} // namespace + +const YUVCoeff gCoeff[] = { + // kJPEG_SkYUVColorSpace + { 0.299f, 0.114f, 1/1.772f, 1/1.402f, 1, 0, 1, }, + + // kRec601_SkYUVColorSpace + { 0.299f, 0.114f, 1/1.772f, 1/1.402f, 219/255.f, 16/255.f, 224/255.f, }, + + // kRec709_SkYUVColorSpace + { 0.2126f, 0.0722f, 1/1.8556f, 1/1.5748f, 219/255.f, 16/255.f, 224/255.f, }, +}; + +static void make_rgb_to_yuv_matrix(float mx[20], const YUVCoeff& c) { + const float Kr = c.Kr; + const float Kb = c.Kb; + const float Kg = 1.0f - Kr - Kb; + + float m[20] = { + Kr, Kg, Kb, 0, c.addY, + -Kr, -Kg, 1-Kb, 0, 128/255.f, + 1-Kr, -Kg, -Kb, 0, 128/255.f, + 0, 0, 0, 1, 0, + }; + memcpy(mx, m, sizeof(m)); + scale3(mx + 0, c.scaleY); + scale3(mx + 5, c.Cr * c.scaleUV); + scale3(mx + 10, c.Cb * c.scaleUV); +} + +static void dump(const float m[20], SkYUVColorSpace cs, bool rgb2yuv) { + const char* names[] = { + "JPEG", "Rec601", "Rec709", + }; + const char* dirnames[] = { + "yuv_to_rgb", "rgb_to_yuv", + }; + SkDebugf("const float %s_%s[] = {\n", names[cs], dirnames[rgb2yuv]); + for (int i = 0; i < 4; ++i) { + SkDebugf(" "); + for (int j = 0; j < 5; ++j) { + SkDebugf(" %9.6ff,", m[i * 5 + j]); + } + SkDebugf("\n"); + } + SkDebugf("};\n"); +} + +// Used to create the prebuilt tables for each colorspace. +// Don't remove this function, in case we want to recompute those tables in the future. +void SkColorMatrix_DumpYUVMatrixTables() { + for (auto cs : {kRec709_SkYUVColorSpace, kRec601_SkYUVColorSpace, kJPEG_SkYUVColorSpace}) { + float m[20]; + make_rgb_to_yuv_matrix(m, gCoeff[(unsigned)cs]); + dump(m, cs, true); + SkMatrix44 m44, im44; + colormatrix_to_matrix44(m, &m44); + float im[20]; +#ifdef SK_DEBUG + // be sure our coversion between matrix44 and colormatrix is perfect + matrix44_to_colormatrix(m44, im); + SkASSERT(memcmp(m, im, sizeof(im)) == 0); +#endif + SkAssertResult(m44.invert(&im44)); + matrix44_to_colormatrix(im44, im); + dump(im, cs, false); + } +} diff --git a/gfx/skia/skia/src/core/SkYUVMath.h b/gfx/skia/skia/src/core/SkYUVMath.h new file mode 100644 index 0000000000..9ecd2c8366 --- /dev/null +++ b/gfx/skia/skia/src/core/SkYUVMath.h @@ -0,0 +1,19 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkYUVMath_DEFINED +#define SkYUVMath_DEFINED + +#include "include/core/SkImageInfo.h" + +void SkColorMatrix_RGB2YUV(SkYUVColorSpace, float m[20]); +void SkColorMatrix_YUV2RGB(SkYUVColorSpace, float m[20]); + +// Used to create the pre-compiled tables in SkYUVMath.cpp +void SkColorMatrix_DumpYUVMatrixTables(); + +#endif diff --git a/gfx/skia/skia/src/core/SkYUVPlanesCache.cpp b/gfx/skia/skia/src/core/SkYUVPlanesCache.cpp new file mode 100644 index 0000000000..756937f607 --- /dev/null +++ b/gfx/skia/skia/src/core/SkYUVPlanesCache.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBitmapCache.h" +#include "src/core/SkResourceCache.h" +#include "src/core/SkYUVPlanesCache.h" + +#define CHECK_LOCAL(localCache, localName, globalName, ...) \ + ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__)) + +namespace { +static unsigned gYUVPlanesKeyNamespaceLabel; + +struct YUVValue { + SkYUVPlanesCache::Info fInfo; + SkCachedData* fData; +}; + +struct YUVPlanesKey : public SkResourceCache::Key { + YUVPlanesKey(uint32_t genID) + : fGenID(genID) + { + this->init(&gYUVPlanesKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(genID), + sizeof(genID)); + } + + uint32_t fGenID; +}; + +struct YUVPlanesRec : public SkResourceCache::Rec { + YUVPlanesRec(YUVPlanesKey key, SkCachedData* data, SkYUVPlanesCache::Info* info) + : fKey(key) + { + fValue.fData = data; + fValue.fInfo = *info; + fValue.fData->attachToCacheAndRef(); + } + ~YUVPlanesRec() override { + fValue.fData->detachFromCacheAndUnref(); + } + + YUVPlanesKey fKey; + YUVValue fValue; + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { return sizeof(*this) + fValue.fData->size(); } + const char* getCategory() const override { return "yuv-planes"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { + return fValue.fData->diagnostic_only_getDiscardable(); + } + + static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextData) { + const YUVPlanesRec& rec = static_cast(baseRec); + YUVValue* result = static_cast(contextData); + + SkCachedData* tmpData = rec.fValue.fData; + tmpData->ref(); + if (nullptr == tmpData->data()) { + tmpData->unref(); + return false; + } + result->fData = tmpData; + result->fInfo = rec.fValue.fInfo; + return true; + } +}; +} // namespace + +SkCachedData* SkYUVPlanesCache::FindAndRef(uint32_t genID, Info* info, + SkResourceCache* localCache) { + YUVValue result; + YUVPlanesKey key(genID); + if (!CHECK_LOCAL(localCache, find, Find, key, YUVPlanesRec::Visitor, &result)) { + return nullptr; + } + + *info = result.fInfo; + return result.fData; +} + +void SkYUVPlanesCache::Add(uint32_t genID, SkCachedData* data, Info* info, + SkResourceCache* localCache) { + YUVPlanesKey key(genID); + return CHECK_LOCAL(localCache, add, Add, new YUVPlanesRec(key, data, info)); +} diff --git a/gfx/skia/skia/src/core/SkYUVPlanesCache.h b/gfx/skia/skia/src/core/SkYUVPlanesCache.h new file mode 100644 index 0000000000..18792305d6 --- /dev/null +++ b/gfx/skia/skia/src/core/SkYUVPlanesCache.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkYUVPlanesCache_DEFINED +#define SkYUVPlanesCache_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkYUVAIndex.h" +#include "include/core/SkYUVASizeInfo.h" +#include "src/core/SkCachedData.h" + +class SkResourceCache; + +class SkYUVPlanesCache { +public: + /** + * The Info struct contains data about the 4 Y, U, V, and A planes of memory stored + * contiguously, in that order, as a single block of memory within SkYUVPlanesCache. + * + * fSizeInfo: fWidth, fHeight, and fWidthBytes of each of the Y, U, V, and A planes. + * fColorSpace: color space that will be used for the YUV -> RGB conversion. + */ + struct Info { + SkYUVASizeInfo fSizeInfo; + SkYUVAIndex fYUVAIndices[SkYUVAIndex::kIndexCount]; + SkYUVColorSpace fColorSpace; + }; + /** + * On success, return a ref to the SkCachedData that holds the pixels. + * + * On failure, return nullptr. + */ + static SkCachedData* FindAndRef(uint32_t genID, Info* info, + SkResourceCache* localCache = nullptr); + + /** + * Add a pixelRef ID and its YUV planes data to the cache. + */ + static void Add(uint32_t genID, SkCachedData* data, Info* info, + SkResourceCache* localCache = nullptr); +}; + +#endif diff --git a/gfx/skia/skia/src/core/SkZip.h b/gfx/skia/skia/src/core/SkZip.h new file mode 100644 index 0000000000..1b38b41f33 --- /dev/null +++ b/gfx/skia/skia/src/core/SkZip.h @@ -0,0 +1,187 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkZip_DEFINED +#define SkZip_DEFINED + +#include +#include +#include +#include + +#include "include/core/SkTypes.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkSpan.h" + +// Take a list of things that can be pointers, and use them all in parallel. The iterators and +// accessor operator[] for the class produce a tuple of the items. +template +class SkZip { + using ReturnTuple = std::tuple; + + class Iterator { + public: + using value_type = ReturnTuple; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + constexpr Iterator(const SkZip* zip, size_t index) : fZip{zip}, fIndex{index} { } + constexpr Iterator(const Iterator& that) : Iterator{ that.fZip, that.fIndex } { } + constexpr Iterator& operator++() { ++fIndex; return *this; } + constexpr Iterator operator++(int) { Iterator tmp(*this); operator++(); return tmp; } + constexpr bool operator==(const Iterator& rhs) const { return fIndex == rhs.fIndex; } + constexpr bool operator!=(const Iterator& rhs) const { return fIndex != rhs.fIndex; } + constexpr reference operator*() { return (*fZip)[fIndex]; } + friend constexpr difference_type operator-(Iterator lhs, Iterator rhs) { + return lhs.fIndex - rhs.fIndex; + } + + private: + const SkZip* const fZip = nullptr; + size_t fIndex = 0; + }; + + template + using make_nullptr = std::integral_constant; + +public: + constexpr SkZip() : fPointers{make_nullptr::value...}, fSize{0} {} + constexpr SkZip(size_t) = delete; + constexpr SkZip(size_t size, Ts*... ts) + : fPointers{ts...} + , fSize{size} {} + constexpr SkZip(const SkZip& that) = default; + + // Check to see if U can be used for const T or is the same as T + template + using CanConvertToConst = typename std::integral_constant::value && sizeof(U) == sizeof(T)>::type; + + // Allow SkZip to be constructed from SkZip. + template...>::value>> + constexpr SkZip(const SkZip& that) + : fPointers(that.data()) + , fSize{that.size()} { } + + constexpr ReturnTuple operator[](size_t i) const { return this->index(i);} + constexpr size_t size() const { return fSize; } + constexpr bool empty() const { return this->size() == 0; } + constexpr ReturnTuple front() const { return this->index(0); } + constexpr ReturnTuple back() const { return this->index(this->size() - 1); } + constexpr Iterator begin() const { return Iterator{this, 0}; } + constexpr Iterator end() const { return Iterator{this, this->size()}; } + template constexpr auto get() const { + return SkMakeSpan(std::get(fPointers), fSize); + } + constexpr std::tuple data() const { return fPointers; } + constexpr SkZip first(size_t n) const { + SkASSERT(n <= this->size()); + return SkZip{n, fPointers}; + } + +private: + constexpr SkZip(size_t n, const std::tuple& pointers) + : fPointers{pointers} + , fSize{n} {} + + constexpr ReturnTuple index(size_t i) const { + SkASSERT(this->size() > 0); + SkASSERT(i < this->size()); + return indexDetail(i, skstd::make_index_sequence{}); + } + + template + constexpr ReturnTuple indexDetail(size_t i, skstd::index_sequence) const { + return ReturnTuple((std::get(fPointers))[i]...); + } + + std::tuple fPointers; + size_t fSize; +}; + +class SkMakeZipDetail { + template struct DecayPointer{ + using U = typename std::remove_cv::type>::type; + using type = typename std::conditional::value, U, T>::type; + }; + template using DecayPointerT = typename DecayPointer::type; + + template struct ContiguousMemory { }; + template struct ContiguousMemory { + using value_type = T; + static constexpr value_type* Data(T* t) { return t; } + static constexpr size_t Size(T* s) { return SIZE_MAX; } + }; + template struct ContiguousMemory { + using value_type = T; + static constexpr value_type* Data(T(&t)[N]) { return t; } + static constexpr size_t Size(T(&)[N]) { return N; } + }; + // In general, we don't want r-value collections, but SkSpans are ok, because they are a view + // onto an actual container. + template struct ContiguousMemory> { + using value_type = T; + static constexpr value_type* Data(SkSpan s) { return s.data(); } + static constexpr size_t Size(SkSpan s) { return s.size(); } + }; + // Only accept l-value references to collections. + template struct ContiguousMemory { + using value_type = typename std::remove_pointer().data())>::type; + static constexpr value_type* Data(C& c) { return c.data(); } + static constexpr size_t Size(C& c) { return c.size(); } + }; + template using Span = ContiguousMemory>; + template using ValueType = typename Span::value_type; + + template struct PickOneSize { }; + template struct PickOneSize { + static constexpr size_t Size(T* t, Ts... ts) { + return PickOneSize::Size(std::forward(ts)...); + } + }; + template struct PickOneSize { + static constexpr size_t Size(T(&)[N], Ts...) { return N; } + }; + template struct PickOneSize, Ts...> { + static constexpr size_t Size(SkSpan s, Ts...) { return s.size(); } + }; + template struct PickOneSize { + static constexpr size_t Size(C& c, Ts...) { return c.size(); } + }; + +public: + template + static constexpr auto MakeZip(Ts&& ... ts) { + + // Pick the first collection that has a size, and use that for the size. + size_t size = PickOneSize...>::Size(std::forward(ts)...); + +#ifdef SK_DEBUG + // Check that all sizes are the same. + size_t minSize = SIZE_MAX; + size_t maxSize = 0; + for (size_t s : {Span::Size(std::forward(ts))...}) { + if (s != SIZE_MAX) { + minSize = SkTMin(minSize, s); + maxSize = SkTMax(maxSize, s); + } + } + SkASSERT(minSize == maxSize); +#endif + + return SkZip...>{size, Span::Data(std::forward(ts))...}; + } +}; + +template +inline constexpr auto SkMakeZip(Ts&& ... ts) { + return SkMakeZipDetail::MakeZip(std::forward(ts)...); +} +#endif //SkZip_DEFINED diff --git a/gfx/skia/skia/src/effects/Sk1DPathEffect.cpp b/gfx/skia/skia/src/effects/Sk1DPathEffect.cpp new file mode 100644 index 0000000000..360179d663 --- /dev/null +++ b/gfx/skia/skia/src/effects/Sk1DPathEffect.cpp @@ -0,0 +1,214 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkPathMeasure.h" +#include "include/core/SkStrokeRec.h" +#include "include/effects/Sk1DPathEffect.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +// Since we are stepping by a float, the do/while loop might go on forever (or nearly so). +// Put in a governor to limit crash values from looping too long (and allocating too much ram). +#define MAX_REASONABLE_ITERATIONS 100000 + +bool Sk1DPathEffect::onFilterPath(SkPath* dst, const SkPath& src, + SkStrokeRec*, const SkRect*) const { + SkPathMeasure meas(src, false); + do { + int governor = MAX_REASONABLE_ITERATIONS; + SkScalar length = meas.getLength(); + SkScalar distance = this->begin(length); + while (distance < length && --governor >= 0) { + SkScalar delta = this->next(dst, distance, meas); + if (delta <= 0) { + break; + } + distance += delta; + } + } while (meas.nextContour()); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPath1DPathEffect::SkPath1DPathEffect(const SkPath& path, SkScalar advance, SkScalar phase, + Style style) : fPath(path) { + SkASSERT(advance > 0 && !path.isEmpty()); + SkASSERT((unsigned)style <= kMorph_Style); + + // Make the path thread-safe. + fPath.updateBoundsCache(); + (void)fPath.getGenerationID(); + + // cleanup their phase parameter, inverting it so that it becomes an + // offset along the path (to match the interpretation in PostScript) + if (phase < 0) { + phase = -phase; + if (phase > advance) { + phase = SkScalarMod(phase, advance); + } + } else { + if (phase > advance) { + phase = SkScalarMod(phase, advance); + } + phase = advance - phase; + } + // now catch the edge case where phase == advance (within epsilon) + if (phase >= advance) { + phase = 0; + } + SkASSERT(phase >= 0); + + fAdvance = advance; + fInitialOffset = phase; + + if ((unsigned)style > kMorph_Style) { + SkDEBUGF("SkPath1DPathEffect style enum out of range %d\n", style); + } + fStyle = style; +} + +bool SkPath1DPathEffect::onFilterPath(SkPath* dst, const SkPath& src, + SkStrokeRec* rec, const SkRect* cullRect) const { + rec->setFillStyle(); + return this->INHERITED::onFilterPath(dst, src, rec, cullRect); +} + +static bool morphpoints(SkPoint dst[], const SkPoint src[], int count, + SkPathMeasure& meas, SkScalar dist) { + for (int i = 0; i < count; i++) { + SkPoint pos; + SkVector tangent; + + SkScalar sx = src[i].fX; + SkScalar sy = src[i].fY; + + if (!meas.getPosTan(dist + sx, &pos, &tangent)) { + return false; + } + + SkMatrix matrix; + SkPoint pt; + + pt.set(sx, sy); + matrix.setSinCos(tangent.fY, tangent.fX, 0, 0); + matrix.preTranslate(-sx, 0); + matrix.postTranslate(pos.fX, pos.fY); + matrix.mapPoints(&dst[i], &pt, 1); + } + return true; +} + +/* TODO + +Need differentially more subdivisions when the follow-path is curvy. Not sure how to +determine that, but we need it. I guess a cheap answer is let the caller tell us, +but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out. +*/ +static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas, + SkScalar dist) { + SkPath::Iter iter(src, false); + SkPoint srcP[4], dstP[3]; + SkPath::Verb verb; + + while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + if (morphpoints(dstP, srcP, 1, meas, dist)) { + dst->moveTo(dstP[0]); + } + break; + case SkPath::kLine_Verb: + srcP[2] = srcP[1]; + srcP[1].set(SkScalarAve(srcP[0].fX, srcP[2].fX), + SkScalarAve(srcP[0].fY, srcP[2].fY)); + // fall through to quad + case SkPath::kQuad_Verb: + if (morphpoints(dstP, &srcP[1], 2, meas, dist)) { + dst->quadTo(dstP[0], dstP[1]); + } + break; + case SkPath::kConic_Verb: + if (morphpoints(dstP, &srcP[1], 2, meas, dist)) { + dst->conicTo(dstP[0], dstP[1], iter.conicWeight()); + } + break; + case SkPath::kCubic_Verb: + if (morphpoints(dstP, &srcP[1], 3, meas, dist)) { + dst->cubicTo(dstP[0], dstP[1], dstP[2]); + } + break; + case SkPath::kClose_Verb: + dst->close(); + break; + default: + SkDEBUGFAIL("unknown verb"); + break; + } + } +} + +SkScalar SkPath1DPathEffect::begin(SkScalar contourLength) const { + return fInitialOffset; +} + +sk_sp SkPath1DPathEffect::CreateProc(SkReadBuffer& buffer) { + SkScalar advance = buffer.readScalar(); + SkPath path; + buffer.readPath(&path); + SkScalar phase = buffer.readScalar(); + Style style = buffer.read32LE(kLastEnum_Style); + return buffer.isValid() ? SkPath1DPathEffect::Make(path, advance, phase, style) : nullptr; +} + +void SkPath1DPathEffect::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fAdvance); + buffer.writePath(fPath); + buffer.writeScalar(fInitialOffset); + buffer.writeUInt(fStyle); +} + +SkScalar SkPath1DPathEffect::next(SkPath* dst, SkScalar distance, + SkPathMeasure& meas) const { +#if defined(IS_FUZZING_WITH_LIBFUZZER) + if (dst->countPoints() > 100000) { + return fAdvance; + } +#endif + switch (fStyle) { + case kTranslate_Style: { + SkPoint pos; + if (meas.getPosTan(distance, &pos, nullptr)) { + dst->addPath(fPath, pos.fX, pos.fY); + } + } break; + case kRotate_Style: { + SkMatrix matrix; + if (meas.getMatrix(distance, &matrix)) { + dst->addPath(fPath, matrix); + } + } break; + case kMorph_Style: + morphpath(dst, fPath, meas, distance); + break; + default: + SkDEBUGFAIL("unknown Style enum"); + break; + } + return fAdvance; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkPath1DPathEffect::Make(const SkPath& path, SkScalar advance, SkScalar phase, + Style style) { + if (advance <= 0 || !SkScalarIsFinite(advance) || !SkScalarIsFinite(phase) || path.isEmpty()) { + return nullptr; + } + return sk_sp(new SkPath1DPathEffect(path, advance, phase, style)); +} diff --git a/gfx/skia/skia/src/effects/Sk2DPathEffect.cpp b/gfx/skia/skia/src/effects/Sk2DPathEffect.cpp new file mode 100644 index 0000000000..fa19bb96b8 --- /dev/null +++ b/gfx/skia/skia/src/effects/Sk2DPathEffect.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkPath.h" +#include "include/core/SkRegion.h" +#include "include/core/SkStrokeRec.h" +#include "include/effects/Sk2DPathEffect.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +Sk2DPathEffect::Sk2DPathEffect(const SkMatrix& mat) : fMatrix(mat) { + // Calling invert will set the type mask on both matrices, making them thread safe. + fMatrixIsInvertible = fMatrix.invert(&fInverse); +} + +bool Sk2DPathEffect::onFilterPath(SkPath* dst, const SkPath& src, + SkStrokeRec*, const SkRect*) const { + if (!fMatrixIsInvertible) { + return false; + } + + SkPath tmp; + SkIRect ir; + + src.transform(fInverse, &tmp); + tmp.getBounds().round(&ir); + if (!ir.isEmpty()) { + this->begin(ir, dst); + + SkRegion rgn; + rgn.setPath(tmp, SkRegion(ir)); + SkRegion::Iterator iter(rgn); + for (; !iter.done(); iter.next()) { + const SkIRect& rect = iter.rect(); + for (int y = rect.fTop; y < rect.fBottom; ++y) { + this->nextSpan(rect.fLeft, y, rect.width(), dst); + } + } + + this->end(dst); + } + return true; +} + +void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) const { + if (!fMatrixIsInvertible) { + return; + } + + const SkMatrix& mat = this->getMatrix(); + SkPoint src, dst; + + src.set(SkIntToScalar(x) + SK_ScalarHalf, SkIntToScalar(y) + SK_ScalarHalf); + do { + mat.mapPoints(&dst, &src, 1); + this->next(dst, x++, y, path); + src.fX += SK_Scalar1; + } while (--count > 0); +} + +void Sk2DPathEffect::begin(const SkIRect& uvBounds, SkPath* dst) const {} +void Sk2DPathEffect::next(const SkPoint& loc, int u, int v, SkPath* dst) const {} +void Sk2DPathEffect::end(SkPath* dst) const {} + +/////////////////////////////////////////////////////////////////////////////// + +void Sk2DPathEffect::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeMatrix(fMatrix); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkLine2DPathEffect::onFilterPath(SkPath* dst, const SkPath& src, + SkStrokeRec* rec, const SkRect* cullRect) const { + if (this->INHERITED::onFilterPath(dst, src, rec, cullRect)) { + rec->setStrokeStyle(fWidth); + return true; + } + return false; +} + +void SkLine2DPathEffect::nextSpan(int u, int v, int ucount, SkPath* dst) const { + if (ucount > 1) { + SkPoint src[2], dstP[2]; + + src[0].set(SkIntToScalar(u) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf); + src[1].set(SkIntToScalar(u+ucount) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf); + this->getMatrix().mapPoints(dstP, src, 2); + + dst->moveTo(dstP[0]); + dst->lineTo(dstP[1]); + } +} + +sk_sp SkLine2DPathEffect::CreateProc(SkReadBuffer& buffer) { + SkMatrix matrix; + buffer.readMatrix(&matrix); + SkScalar width = buffer.readScalar(); + return SkLine2DPathEffect::Make(width, matrix); +} + +void SkLine2DPathEffect::flatten(SkWriteBuffer &buffer) const { + buffer.writeMatrix(this->getMatrix()); + buffer.writeScalar(fWidth); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPath2DPathEffect::SkPath2DPathEffect(const SkMatrix& m, const SkPath& p) + : INHERITED(m), fPath(p) { +} + +sk_sp SkPath2DPathEffect::CreateProc(SkReadBuffer& buffer) { + SkMatrix matrix; + buffer.readMatrix(&matrix); + SkPath path; + buffer.readPath(&path); + return SkPath2DPathEffect::Make(matrix, path); +} + +void SkPath2DPathEffect::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(this->getMatrix()); + buffer.writePath(fPath); +} + +void SkPath2DPathEffect::next(const SkPoint& loc, int u, int v, + SkPath* dst) const { + dst->addPath(fPath, loc.fX, loc.fY); +} diff --git a/gfx/skia/skia/src/effects/SkColorMatrix.cpp b/gfx/skia/skia/src/effects/SkColorMatrix.cpp new file mode 100644 index 0000000000..f6d8eaeded --- /dev/null +++ b/gfx/skia/skia/src/effects/SkColorMatrix.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkColorMatrix.h" +#include "include/private/SkFloatingPoint.h" + +enum { + kR_Scale = 0, + kG_Scale = 6, + kB_Scale = 12, + kA_Scale = 18, + + kR_Trans = 4, + kG_Trans = 9, + kB_Trans = 14, + kA_Trans = 19, +}; + +static void set_concat(float result[20], const float outer[20], const float inner[20]) { + float tmp[20]; + float* target; + + if (outer == result || inner == result) { + target = tmp; // will memcpy answer when we're done into result + } else { + target = result; + } + + int index = 0; + for (int j = 0; j < 20; j += 5) { + for (int i = 0; i < 4; i++) { + target[index++] = outer[j + 0] * inner[i + 0] + + outer[j + 1] * inner[i + 5] + + outer[j + 2] * inner[i + 10] + + outer[j + 3] * inner[i + 15]; + } + target[index++] = outer[j + 0] * inner[4] + + outer[j + 1] * inner[9] + + outer[j + 2] * inner[14] + + outer[j + 3] * inner[19] + + outer[j + 4]; + } + + if (target != result) { + memcpy(result, target, 20 * sizeof(float)); + } +} + +void SkColorMatrix::setIdentity() { + memset(fMat, 0, sizeof(fMat)); + fMat[kR_Scale] = fMat[kG_Scale] = fMat[kB_Scale] = fMat[kA_Scale] = 1; +} + +void SkColorMatrix::setScale(float rScale, float gScale, float bScale, float aScale) { + memset(fMat, 0, sizeof(fMat)); + fMat[kR_Scale] = rScale; + fMat[kG_Scale] = gScale; + fMat[kB_Scale] = bScale; + fMat[kA_Scale] = aScale; +} + +void SkColorMatrix::postTranslate(float dr, float dg, float db, float da) { + fMat[kR_Trans] += dr; + fMat[kG_Trans] += dg; + fMat[kB_Trans] += db; + fMat[kA_Trans] += da; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkColorMatrix::setRotate(Axis axis, float degrees) { + float r = sk_float_degrees_to_radians(degrees); + this->setSinCos(axis, sk_float_sin(r), sk_float_cos(r)); +} + +void SkColorMatrix::setSinCos(Axis axis, float sine, float cosine) { + SkASSERT((unsigned)axis < 3); + + static const uint8_t gRotateIndex[] = { + 6, 7, 11, 12, + 0, 10, 2, 12, + 0, 1, 5, 6, + }; + const uint8_t* index = gRotateIndex + axis * 4; + + this->setIdentity(); + fMat[index[0]] = cosine; + fMat[index[1]] = sine; + fMat[index[2]] = -sine; + fMat[index[3]] = cosine; +} + +void SkColorMatrix::preRotate(Axis axis, float degrees) { + SkColorMatrix tmp; + tmp.setRotate(axis, degrees); + this->preConcat(tmp); +} + +void SkColorMatrix::postRotate(Axis axis, float degrees) { + SkColorMatrix tmp; + tmp.setRotate(axis, degrees); + this->postConcat(tmp); +} + +void SkColorMatrix::setConcat(const SkColorMatrix& matA, const SkColorMatrix& matB) { + set_concat(fMat, matA.fMat, matB.fMat); +} + +/////////////////////////////////////////////////////////////////////////////// + +static void setrow(float row[], float r, float g, float b) { + row[0] = r; + row[1] = g; + row[2] = b; +} + +static const float kHueR = 0.213f; +static const float kHueG = 0.715f; +static const float kHueB = 0.072f; + +void SkColorMatrix::setSaturation(float sat) { + memset(fMat, 0, sizeof(fMat)); + + const float R = kHueR * (1 - sat); + const float G = kHueG * (1 - sat); + const float B = kHueB * (1 - sat); + + setrow(fMat + 0, R + sat, G, B); + setrow(fMat + 5, R, G + sat, B); + setrow(fMat + 10, R, G, B + sat); + fMat[kA_Scale] = 1; +} + +static const float kR2Y = 0.299f; +static const float kG2Y = 0.587f; +static const float kB2Y = 0.114f; + +static const float kR2U = -0.16874f; +static const float kG2U = -0.33126f; +static const float kB2U = 0.5f; + +static const float kR2V = 0.5f; +static const float kG2V = -0.41869f; +static const float kB2V = -0.08131f; + +void SkColorMatrix::setRGB2YUV() { + memset(fMat, 0, sizeof(fMat)); + + setrow(fMat + 0, kR2Y, kG2Y, kB2Y); + setrow(fMat + 5, kR2U, kG2U, kB2U); + setrow(fMat + 10, kR2V, kG2V, kB2V); + fMat[kA_Scale] = 1; +} + +static const float kV2R = 1.402f; +static const float kU2G = -0.34414f; +static const float kV2G = -0.71414f; +static const float kU2B = 1.772f; + +void SkColorMatrix::setYUV2RGB() { + memset(fMat, 0, sizeof(fMat)); + + setrow(fMat + 0, 1, 0, kV2R); + setrow(fMat + 5, 1, kU2G, kV2G); + setrow(fMat + 10, 1, kU2B, 0); + fMat[kA_Scale] = 1; +} diff --git a/gfx/skia/skia/src/effects/SkColorMatrixFilter.cpp b/gfx/skia/skia/src/effects/SkColorMatrixFilter.cpp new file mode 100644 index 0000000000..91811a6d30 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkColorMatrixFilter.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkColorMatrixFilter.h" + +static SkScalar byte_to_unit_float(U8CPU byte) { + if (0xFF == byte) { + // want to get this exact + return 1; + } else { + return byte * 0.00392156862745f; + } +} + +sk_sp SkColorMatrixFilter::MakeLightingFilter(SkColor mul, SkColor add) { + const SkColor opaqueAlphaMask = SK_ColorBLACK; + // omit the alpha and compare only the RGB values + if (0 == (add & ~opaqueAlphaMask)) { + return SkColorFilters::Blend(mul | opaqueAlphaMask, SkBlendMode::kModulate); + } + + SkColorMatrix matrix; + matrix.setScale(byte_to_unit_float(SkColorGetR(mul)), + byte_to_unit_float(SkColorGetG(mul)), + byte_to_unit_float(SkColorGetB(mul)), + 1); + matrix.postTranslate(byte_to_unit_float(SkColorGetR(add)), + byte_to_unit_float(SkColorGetG(add)), + byte_to_unit_float(SkColorGetB(add)), + 0); + return SkColorFilters::Matrix(matrix); +} diff --git a/gfx/skia/skia/src/effects/SkCornerPathEffect.cpp b/gfx/skia/skia/src/effects/SkCornerPathEffect.cpp new file mode 100644 index 0000000000..16670e55e9 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkCornerPathEffect.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "include/effects/SkCornerPathEffect.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +SkCornerPathEffect::SkCornerPathEffect(SkScalar radius) { + // check with ! to catch NaNs + if (!(radius > 0)) { + radius = 0; + } + fRadius = radius; +} +SkCornerPathEffect::~SkCornerPathEffect() {} + +static bool ComputeStep(const SkPoint& a, const SkPoint& b, SkScalar radius, + SkPoint* step) { + SkScalar dist = SkPoint::Distance(a, b); + + *step = b - a; + if (dist <= radius * 2) { + *step *= SK_ScalarHalf; + return false; + } else { + *step *= radius / dist; + return true; + } +} + +bool SkCornerPathEffect::onFilterPath(SkPath* dst, const SkPath& src, + SkStrokeRec*, const SkRect*) const { + if (fRadius <= 0) { + return false; + } + + SkPath::Iter iter(src, false); + SkPath::Verb verb, prevVerb = SkPath::kDone_Verb; + SkPoint pts[4]; + + bool closed; + SkPoint moveTo, lastCorner; + SkVector firstStep, step; + bool prevIsValid = true; + + // to avoid warnings + step.set(0, 0); + moveTo.set(0, 0); + firstStep.set(0, 0); + lastCorner.set(0, 0); + + for (;;) { + switch (verb = iter.next(pts)) { + case SkPath::kMove_Verb: + // close out the previous (open) contour + if (SkPath::kLine_Verb == prevVerb) { + dst->lineTo(lastCorner); + } + closed = iter.isClosedContour(); + if (closed) { + moveTo = pts[0]; + prevIsValid = false; + } else { + dst->moveTo(pts[0]); + prevIsValid = true; + } + break; + case SkPath::kLine_Verb: { + bool drawSegment = ComputeStep(pts[0], pts[1], fRadius, &step); + // prev corner + if (!prevIsValid) { + dst->moveTo(moveTo + step); + prevIsValid = true; + } else { + dst->quadTo(pts[0].fX, pts[0].fY, pts[0].fX + step.fX, + pts[0].fY + step.fY); + } + if (drawSegment) { + dst->lineTo(pts[1].fX - step.fX, pts[1].fY - step.fY); + } + lastCorner = pts[1]; + prevIsValid = true; + break; + } + case SkPath::kQuad_Verb: + // TBD - just replicate the curve for now + if (!prevIsValid) { + dst->moveTo(pts[0]); + prevIsValid = true; + } + dst->quadTo(pts[1], pts[2]); + lastCorner = pts[2]; + firstStep.set(0, 0); + break; + case SkPath::kConic_Verb: + // TBD - just replicate the curve for now + if (!prevIsValid) { + dst->moveTo(pts[0]); + prevIsValid = true; + } + dst->conicTo(pts[1], pts[2], iter.conicWeight()); + lastCorner = pts[2]; + firstStep.set(0, 0); + break; + case SkPath::kCubic_Verb: + if (!prevIsValid) { + dst->moveTo(pts[0]); + prevIsValid = true; + } + // TBD - just replicate the curve for now + dst->cubicTo(pts[1], pts[2], pts[3]); + lastCorner = pts[3]; + firstStep.set(0, 0); + break; + case SkPath::kClose_Verb: + if (firstStep.fX || firstStep.fY) { + dst->quadTo(lastCorner.fX, lastCorner.fY, + lastCorner.fX + firstStep.fX, + lastCorner.fY + firstStep.fY); + } + dst->close(); + prevIsValid = false; + break; + case SkPath::kDone_Verb: + if (prevIsValid) { + dst->lineTo(lastCorner); + } + return true; + default: + SkDEBUGFAIL("default should not be reached"); + return false; + } + + if (SkPath::kMove_Verb == prevVerb) { + firstStep = step; + } + prevVerb = verb; + } + + return true; +} + +sk_sp SkCornerPathEffect::CreateProc(SkReadBuffer& buffer) { + return SkCornerPathEffect::Make(buffer.readScalar()); +} + +void SkCornerPathEffect::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fRadius); +} diff --git a/gfx/skia/skia/src/effects/SkDashImpl.h b/gfx/skia/skia/src/effects/SkDashImpl.h new file mode 100644 index 0000000000..1d509713f9 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkDashImpl.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDashImpl_DEFINED +#define SkDashImpl_DEFINED + +#include "include/core/SkPathEffect.h" + +class SkDashImpl : public SkPathEffect { +public: + SkDashImpl(const SkScalar intervals[], int count, SkScalar phase); + +protected: + ~SkDashImpl() override; + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + + bool onAsPoints(PointData* results, const SkPath& src, const SkStrokeRec&, const SkMatrix&, + const SkRect*) const override; + + DashType onAsADash(DashInfo* info) const override; + +private: + SK_FLATTENABLE_HOOKS(SkDashImpl) + + SkScalar* fIntervals; + int32_t fCount; + SkScalar fPhase; + // computed from phase + + SkScalar fInitialDashLength; + int32_t fInitialDashIndex; + SkScalar fIntervalLength; + + typedef SkPathEffect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/SkDashPathEffect.cpp b/gfx/skia/skia/src/effects/SkDashPathEffect.cpp new file mode 100644 index 0000000000..913d851c04 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkDashPathEffect.cpp @@ -0,0 +1,398 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkDashPathEffect.h" + +#include "include/core/SkStrokeRec.h" +#include "include/private/SkTo.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/effects/SkDashImpl.h" +#include "src/utils/SkDashPathPriv.h" + +#include + +SkDashImpl::SkDashImpl(const SkScalar intervals[], int count, SkScalar phase) + : fPhase(0) + , fInitialDashLength(-1) + , fInitialDashIndex(0) + , fIntervalLength(0) { + SkASSERT(intervals); + SkASSERT(count > 1 && SkIsAlign2(count)); + + fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); + fCount = count; + for (int i = 0; i < count; i++) { + fIntervals[i] = intervals[i]; + } + + // set the internal data members + SkDashPath::CalcDashParameters(phase, fIntervals, fCount, + &fInitialDashLength, &fInitialDashIndex, &fIntervalLength, &fPhase); +} + +SkDashImpl::~SkDashImpl() { + sk_free(fIntervals); +} + +bool SkDashImpl::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect) const { + return SkDashPath::InternalFilter(dst, src, rec, cullRect, fIntervals, fCount, + fInitialDashLength, fInitialDashIndex, fIntervalLength); +} + +static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) { + SkScalar radius = SkScalarHalf(rec.getWidth()); + if (0 == radius) { + radius = SK_Scalar1; // hairlines + } + if (SkPaint::kMiter_Join == rec.getJoin()) { + radius *= rec.getMiter(); + } + rect->outset(radius, radius); +} + +// Attempt to trim the line to minimally cover the cull rect (currently +// only works for horizontal and vertical lines). +// Return true if processing should continue; false otherwise. +static bool cull_line(SkPoint* pts, const SkStrokeRec& rec, + const SkMatrix& ctm, const SkRect* cullRect, + const SkScalar intervalLength) { + if (nullptr == cullRect) { + SkASSERT(false); // Shouldn't ever occur in practice + return false; + } + + SkScalar dx = pts[1].x() - pts[0].x(); + SkScalar dy = pts[1].y() - pts[0].y(); + + if ((dx && dy) || (!dx && !dy)) { + return false; + } + + SkRect bounds = *cullRect; + outset_for_stroke(&bounds, rec); + + // cullRect is in device space while pts are in the local coordinate system + // defined by the ctm. We want our answer in the local coordinate system. + + SkASSERT(ctm.rectStaysRect()); + SkMatrix inv; + if (!ctm.invert(&inv)) { + return false; + } + + inv.mapRect(&bounds); + + if (dx) { + SkASSERT(dx && !dy); + SkScalar minX = pts[0].fX; + SkScalar maxX = pts[1].fX; + + if (dx < 0) { + using std::swap; + swap(minX, maxX); + } + + SkASSERT(minX < maxX); + if (maxX <= bounds.fLeft || minX >= bounds.fRight) { + return false; + } + + // Now we actually perform the chop, removing the excess to the left and + // right of the bounds (keeping our new line "in phase" with the dash, + // hence the (mod intervalLength). + + if (minX < bounds.fLeft) { + minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, intervalLength); + } + if (maxX > bounds.fRight) { + maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, intervalLength); + } + + SkASSERT(maxX > minX); + if (dx < 0) { + using std::swap; + swap(minX, maxX); + } + pts[0].fX = minX; + pts[1].fX = maxX; + } else { + SkASSERT(dy && !dx); + SkScalar minY = pts[0].fY; + SkScalar maxY = pts[1].fY; + + if (dy < 0) { + using std::swap; + swap(minY, maxY); + } + + SkASSERT(minY < maxY); + if (maxY <= bounds.fTop || minY >= bounds.fBottom) { + return false; + } + + // Now we actually perform the chop, removing the excess to the top and + // bottom of the bounds (keeping our new line "in phase" with the dash, + // hence the (mod intervalLength). + + if (minY < bounds.fTop) { + minY = bounds.fTop - SkScalarMod(bounds.fTop - minY, intervalLength); + } + if (maxY > bounds.fBottom) { + maxY = bounds.fBottom + SkScalarMod(maxY - bounds.fBottom, intervalLength); + } + + SkASSERT(maxY > minY); + if (dy < 0) { + using std::swap; + swap(minY, maxY); + } + pts[0].fY = minY; + pts[1].fY = maxY; + } + + return true; +} + +// Currently asPoints is more restrictive then it needs to be. In the future +// we need to: +// allow kRound_Cap capping (could allow rotations in the matrix with this) +// allow paths to be returned +bool SkDashImpl::onAsPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec, + const SkMatrix& matrix, const SkRect* cullRect) const { + // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out + if (0 >= rec.getWidth()) { + return false; + } + + // TODO: this next test could be eased up. We could allow any number of + // intervals as long as all the ons match and all the offs match. + // Additionally, they do not necessarily need to be integers. + // We cannot allow arbitrary intervals since we want the returned points + // to be uniformly sized. + if (fCount != 2 || + !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) || + !SkScalarIsInt(fIntervals[0]) || + !SkScalarIsInt(fIntervals[1])) { + return false; + } + + SkPoint pts[2]; + + if (!src.isLine(pts)) { + return false; + } + + // TODO: this test could be eased up to allow circles + if (SkPaint::kButt_Cap != rec.getCap()) { + return false; + } + + // TODO: this test could be eased up for circles. Rotations could be allowed. + if (!matrix.rectStaysRect()) { + return false; + } + + // See if the line can be limited to something plausible. + if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) { + return false; + } + + SkScalar length = SkPoint::Distance(pts[1], pts[0]); + + SkVector tangent = pts[1] - pts[0]; + if (tangent.isZero()) { + return false; + } + + tangent.scale(SkScalarInvert(length)); + + // TODO: make this test for horizontal & vertical lines more robust + bool isXAxis = true; + if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) || + SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) { + results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth())); + } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) || + SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) { + results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0])); + isXAxis = false; + } else if (SkPaint::kRound_Cap != rec.getCap()) { + // Angled lines don't have axis-aligned boxes. + return false; + } + + if (results) { + results->fFlags = 0; + SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength); + + if (SkPaint::kRound_Cap == rec.getCap()) { + results->fFlags |= PointData::kCircles_PointFlag; + } + + results->fNumPoints = 0; + SkScalar len2 = length; + if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { + SkASSERT(len2 >= clampedInitialDashLength); + if (0 == fInitialDashIndex) { + if (clampedInitialDashLength > 0) { + if (clampedInitialDashLength >= fIntervals[0]) { + ++results->fNumPoints; // partial first dash + } + len2 -= clampedInitialDashLength; + } + len2 -= fIntervals[1]; // also skip first space + if (len2 < 0) { + len2 = 0; + } + } else { + len2 -= clampedInitialDashLength; // skip initial partial empty + } + } + // Too many midpoints can cause results->fNumPoints to overflow or + // otherwise cause the results->fPoints allocation below to OOM. + // Cap it to a sane value. + SkScalar numIntervals = len2 / fIntervalLength; + if (!SkScalarIsFinite(numIntervals) || numIntervals > SkDashPath::kMaxDashCount) { + return false; + } + int numMidPoints = SkScalarFloorToInt(numIntervals); + results->fNumPoints += numMidPoints; + len2 -= numMidPoints * fIntervalLength; + bool partialLast = false; + if (len2 > 0) { + if (len2 < fIntervals[0]) { + partialLast = true; + } else { + ++numMidPoints; + ++results->fNumPoints; + } + } + + results->fPoints = new SkPoint[results->fNumPoints]; + + SkScalar distance = 0; + int curPt = 0; + + if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { + SkASSERT(clampedInitialDashLength <= length); + + if (0 == fInitialDashIndex) { + if (clampedInitialDashLength > 0) { + // partial first block + SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles + SkScalar x = pts[0].fX + tangent.fX * SkScalarHalf(clampedInitialDashLength); + SkScalar y = pts[0].fY + tangent.fY * SkScalarHalf(clampedInitialDashLength); + SkScalar halfWidth, halfHeight; + if (isXAxis) { + halfWidth = SkScalarHalf(clampedInitialDashLength); + halfHeight = SkScalarHalf(rec.getWidth()); + } else { + halfWidth = SkScalarHalf(rec.getWidth()); + halfHeight = SkScalarHalf(clampedInitialDashLength); + } + if (clampedInitialDashLength < fIntervals[0]) { + // This one will not be like the others + results->fFirst.addRect(x - halfWidth, y - halfHeight, + x + halfWidth, y + halfHeight); + } else { + SkASSERT(curPt < results->fNumPoints); + results->fPoints[curPt].set(x, y); + ++curPt; + } + + distance += clampedInitialDashLength; + } + + distance += fIntervals[1]; // skip over the next blank block too + } else { + distance += clampedInitialDashLength; + } + } + + if (0 != numMidPoints) { + distance += SkScalarHalf(fIntervals[0]); + + for (int i = 0; i < numMidPoints; ++i) { + SkScalar x = pts[0].fX + tangent.fX * distance; + SkScalar y = pts[0].fY + tangent.fY * distance; + + SkASSERT(curPt < results->fNumPoints); + results->fPoints[curPt].set(x, y); + ++curPt; + + distance += fIntervalLength; + } + + distance -= SkScalarHalf(fIntervals[0]); + } + + if (partialLast) { + // partial final block + SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles + SkScalar temp = length - distance; + SkASSERT(temp < fIntervals[0]); + SkScalar x = pts[0].fX + tangent.fX * (distance + SkScalarHalf(temp)); + SkScalar y = pts[0].fY + tangent.fY * (distance + SkScalarHalf(temp)); + SkScalar halfWidth, halfHeight; + if (isXAxis) { + halfWidth = SkScalarHalf(temp); + halfHeight = SkScalarHalf(rec.getWidth()); + } else { + halfWidth = SkScalarHalf(rec.getWidth()); + halfHeight = SkScalarHalf(temp); + } + results->fLast.addRect(x - halfWidth, y - halfHeight, + x + halfWidth, y + halfHeight); + } + + SkASSERT(curPt == results->fNumPoints); + } + + return true; +} + +SkPathEffect::DashType SkDashImpl::onAsADash(DashInfo* info) const { + if (info) { + if (info->fCount >= fCount && info->fIntervals) { + memcpy(info->fIntervals, fIntervals, fCount * sizeof(SkScalar)); + } + info->fCount = fCount; + info->fPhase = fPhase; + } + return kDash_DashType; +} + +void SkDashImpl::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fPhase); + buffer.writeScalarArray(fIntervals, fCount); +} + +sk_sp SkDashImpl::CreateProc(SkReadBuffer& buffer) { + const SkScalar phase = buffer.readScalar(); + uint32_t count = buffer.getArrayCount(); + + // Don't allocate gigantic buffers if there's not data for them. + if (!buffer.validateCanReadN(count)) { + return nullptr; + } + + SkAutoSTArray<32, SkScalar> intervals(count); + if (buffer.readScalarArray(intervals.get(), count)) { + return SkDashPathEffect::Make(intervals.get(), SkToInt(count), phase); + } + return nullptr; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkDashPathEffect::Make(const SkScalar intervals[], int count, SkScalar phase) { + if (!SkDashPath::ValidDashPath(phase, intervals, count)) { + return nullptr; + } + return sk_sp(new SkDashImpl(intervals, count, phase)); +} diff --git a/gfx/skia/skia/src/effects/SkDiscretePathEffect.cpp b/gfx/skia/skia/src/effects/SkDiscretePathEffect.cpp new file mode 100644 index 0000000000..93ea43fa29 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkDiscretePathEffect.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkPathMeasure.h" +#include "include/core/SkStrokeRec.h" +#include "include/effects/SkDiscretePathEffect.h" +#include "include/private/SkFixed.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +sk_sp SkDiscretePathEffect::Make(SkScalar segLength, SkScalar deviation, + uint32_t seedAssist) { + if (!SkScalarIsFinite(segLength) || !SkScalarIsFinite(deviation)) { + return nullptr; + } + if (segLength <= SK_ScalarNearlyZero) { + return nullptr; + } + return sk_sp(new SkDiscretePathEffect(segLength, deviation, seedAssist)); +} + +static void Perterb(SkPoint* p, const SkVector& tangent, SkScalar scale) { + SkVector normal = tangent; + SkPointPriv::RotateCCW(&normal); + normal.setLength(scale); + *p += normal; +} + +SkDiscretePathEffect::SkDiscretePathEffect(SkScalar segLength, + SkScalar deviation, + uint32_t seedAssist) + : fSegLength(segLength), fPerterb(deviation), fSeedAssist(seedAssist) +{ +} + +/** \class LCGRandom + + Utility class that implements pseudo random 32bit numbers using a fast + linear equation. Unlike rand(), this class holds its own seed (initially + set to 0), so that multiple instances can be used with no side-effects. + + Copied from the original implementation of SkRandom. Only contains the + methods used by SkDiscretePathEffect::filterPath, with methods that were + not called directly moved to private. +*/ + +class LCGRandom { +public: + LCGRandom(uint32_t seed) : fSeed(seed) {} + + /** Return the next pseudo random number expressed as a SkScalar + in the range [-SK_Scalar1..SK_Scalar1). + */ + SkScalar nextSScalar1() { return SkFixedToScalar(this->nextSFixed1()); } + +private: + /** Return the next pseudo random number as an unsigned 32bit value. + */ + uint32_t nextU() { uint32_t r = fSeed * kMul + kAdd; fSeed = r; return r; } + + /** Return the next pseudo random number as a signed 32bit value. + */ + int32_t nextS() { return (int32_t)this->nextU(); } + + /** Return the next pseudo random number expressed as a signed SkFixed + in the range [-SK_Fixed1..SK_Fixed1). + */ + SkFixed nextSFixed1() { return this->nextS() >> 15; } + + // See "Numerical Recipes in C", 1992 page 284 for these constants + enum { + kMul = 1664525, + kAdd = 1013904223 + }; + uint32_t fSeed; +}; + +bool SkDiscretePathEffect::onFilterPath(SkPath* dst, const SkPath& src, + SkStrokeRec* rec, const SkRect*) const { + bool doFill = rec->isFillStyle(); + + SkPathMeasure meas(src, doFill); + + /* Caller may supply their own seed assist, which by default is 0 */ + uint32_t seed = fSeedAssist ^ SkScalarRoundToInt(meas.getLength()); + + LCGRandom rand(seed ^ ((seed << 16) | (seed >> 16))); + SkScalar scale = fPerterb; + SkPoint p; + SkVector v; + + do { + SkScalar length = meas.getLength(); + + if (fSegLength * (2 + doFill) > length) { + meas.getSegment(0, length, dst, true); // to short for us to mangle + } else { + int n = SkScalarRoundToInt(length / fSegLength); + constexpr int kMaxReasonableIterations = 100000; + n = SkTMin(n, kMaxReasonableIterations); + SkScalar delta = length / n; + SkScalar distance = 0; + + if (meas.isClosed()) { + n -= 1; + distance += delta/2; + } + + if (meas.getPosTan(distance, &p, &v)) { + Perterb(&p, v, rand.nextSScalar1() * scale); + dst->moveTo(p); + } + while (--n >= 0) { + distance += delta; + if (meas.getPosTan(distance, &p, &v)) { + Perterb(&p, v, rand.nextSScalar1() * scale); + dst->lineTo(p); + } + } + if (meas.isClosed()) { + dst->close(); + } + } + } while (meas.nextContour()); + return true; +} + +sk_sp SkDiscretePathEffect::CreateProc(SkReadBuffer& buffer) { + SkScalar segLength = buffer.readScalar(); + SkScalar perterb = buffer.readScalar(); + uint32_t seed = buffer.readUInt(); + return Make(segLength, perterb, seed); +} + +void SkDiscretePathEffect::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fSegLength); + buffer.writeScalar(fPerterb); + buffer.writeUInt(fSeedAssist); +} diff --git a/gfx/skia/skia/src/effects/SkEmbossMask.cpp b/gfx/skia/skia/src/effects/SkEmbossMask.cpp new file mode 100644 index 0000000000..dcac971c7d --- /dev/null +++ b/gfx/skia/skia/src/effects/SkEmbossMask.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/effects/SkEmbossMask.h" + +#include "include/core/SkMath.h" +#include "include/private/SkFixed.h" +#include "include/private/SkTo.h" +#include "src/core/SkMathPriv.h" + +static inline int nonzero_to_one(int x) { +#if 0 + return x != 0; +#else + return ((unsigned)(x | -x)) >> 31; +#endif +} + +static inline int neq_to_one(int x, int max) { +#if 0 + return x != max; +#else + SkASSERT(x >= 0 && x <= max); + return ((unsigned)(x - max)) >> 31; +#endif +} + +static inline int neq_to_mask(int x, int max) { +#if 0 + return -(x != max); +#else + SkASSERT(x >= 0 && x <= max); + return (x - max) >> 31; +#endif +} + +static inline unsigned div255(unsigned x) { + SkASSERT(x <= (255*255)); + return x * ((1 << 24) / 255) >> 24; +} + +#define kDelta 32 // small enough to show off angle differences + +void SkEmbossMask::Emboss(SkMask* mask, const SkEmbossMaskFilter::Light& light) { + SkASSERT(mask->fFormat == SkMask::k3D_Format); + + int specular = light.fSpecular; + int ambient = light.fAmbient; + SkFixed lx = SkScalarToFixed(light.fDirection[0]); + SkFixed ly = SkScalarToFixed(light.fDirection[1]); + SkFixed lz = SkScalarToFixed(light.fDirection[2]); + SkFixed lz_dot_nz = lz * kDelta; + int lz_dot8 = lz >> 8; + + size_t planeSize = mask->computeImageSize(); + uint8_t* alpha = mask->fImage; + uint8_t* multiply = (uint8_t*)alpha + planeSize; + uint8_t* additive = multiply + planeSize; + + int rowBytes = mask->fRowBytes; + int maxy = mask->fBounds.height() - 1; + int maxx = mask->fBounds.width() - 1; + + int prev_row = 0; + for (int y = 0; y <= maxy; y++) { + int next_row = neq_to_mask(y, maxy) & rowBytes; + + for (int x = 0; x <= maxx; x++) { + int nx = alpha[x + neq_to_one(x, maxx)] - alpha[x - nonzero_to_one(x)]; + int ny = alpha[x + next_row] - alpha[x - prev_row]; + + SkFixed numer = lx * nx + ly * ny + lz_dot_nz; + int mul = ambient; + int add = 0; + + if (numer > 0) { // preflight when numer/denom will be <= 0 + int denom = SkSqrt32(nx * nx + ny * ny + kDelta*kDelta); + SkFixed dot = numer / denom; + dot >>= 8; // now dot is 2^8 instead of 2^16 + mul = SkMin32(mul + dot, 255); + + // now for the reflection + + // R = 2 (Light * Normal) Normal - Light + // hilite = R * Eye(0, 0, 1) + + int hilite = (2 * dot - lz_dot8) * lz_dot8 >> 8; + if (hilite > 0) { + // pin hilite to 255, since our fast math is also a little sloppy + hilite = SkClampMax(hilite, 255); + + // specular is 4.4 + // would really like to compute the fractional part of this + // and then possibly cache a 256 table for a given specular + // value in the light, and just pass that in to this function. + add = hilite; + for (int i = specular >> 4; i > 0; --i) { + add = div255(add * hilite); + } + } + } + multiply[x] = SkToU8(mul); + additive[x] = SkToU8(add); + } + alpha += rowBytes; + multiply += rowBytes; + additive += rowBytes; + prev_row = rowBytes; + } +} diff --git a/gfx/skia/skia/src/effects/SkEmbossMask.h b/gfx/skia/skia/src/effects/SkEmbossMask.h new file mode 100644 index 0000000000..7185fc909e --- /dev/null +++ b/gfx/skia/skia/src/effects/SkEmbossMask.h @@ -0,0 +1,19 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkEmbossMask_DEFINED +#define SkEmbossMask_DEFINED + +#include "src/effects/SkEmbossMaskFilter.h" + +class SkEmbossMask { +public: + static void Emboss(SkMask* mask, const SkEmbossMaskFilter::Light&); +}; + +#endif diff --git a/gfx/skia/skia/src/effects/SkEmbossMaskFilter.cpp b/gfx/skia/skia/src/effects/SkEmbossMaskFilter.cpp new file mode 100644 index 0000000000..5592fc7cd3 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkEmbossMaskFilter.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorPriv.h" +#include "include/core/SkString.h" +#include "include/effects/SkBlurMaskFilter.h" +#include "src/core/SkBlurMask.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/effects/SkEmbossMask.h" +#include "src/effects/SkEmbossMaskFilter.h" + +static void normalize3(SkScalar dst[3], const SkScalar src[3]) { + SkScalar mag = SkScalarSquare(src[0]) + SkScalarSquare(src[1]) + SkScalarSquare(src[2]); + SkScalar scale = SkScalarInvert(SkScalarSqrt(mag)); + + for (int i = 0; i < 3; i++) { + dst[i] = src[i] * scale; + } +} + +sk_sp SkEmbossMaskFilter::Make(SkScalar blurSigma, const Light& light) { + if (!SkScalarIsFinite(blurSigma) || blurSigma <= 0) { + return nullptr; + } + + Light newLight = light; + normalize3(newLight.fDirection, light.fDirection); + if (!SkScalarsAreFinite(newLight.fDirection, 3)) { + return nullptr; + } + + return sk_sp(new SkEmbossMaskFilter(blurSigma, newLight)); +} + +#ifdef SK_SUPPORT_LEGACY_EMBOSSMASKFILTER +sk_sp SkBlurMaskFilter::MakeEmboss(SkScalar blurSigma, const SkScalar direction[3], + SkScalar ambient, SkScalar specular) { + if (direction == nullptr) { + return nullptr; + } + + SkEmbossMaskFilter::Light light; + + memcpy(light.fDirection, direction, sizeof(light.fDirection)); + // ambient should be 0...1 as a scalar + light.fAmbient = SkUnitScalarClampToByte(ambient); + // specular should be 0..15.99 as a scalar + static const SkScalar kSpecularMultiplier = SkIntToScalar(255) / 16; + light.fSpecular = static_cast(SkScalarPin(specular, 0, 16) * kSpecularMultiplier + 0.5); + + return SkEmbossMaskFilter::Make(blurSigma, light); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +SkEmbossMaskFilter::SkEmbossMaskFilter(SkScalar blurSigma, const Light& light) + : fLight(light), fBlurSigma(blurSigma) +{ + SkASSERT(fBlurSigma > 0); + SkASSERT(SkScalarsAreFinite(fLight.fDirection, 3)); +} + +SkMask::Format SkEmbossMaskFilter::getFormat() const { + return SkMask::k3D_Format; +} + +bool SkEmbossMaskFilter::filterMask(SkMask* dst, const SkMask& src, + const SkMatrix& matrix, SkIPoint* margin) const { + if (src.fFormat != SkMask::kA8_Format) { + return false; + } + + SkScalar sigma = matrix.mapRadius(fBlurSigma); + + if (!SkBlurMask::BoxBlur(dst, src, sigma, kInner_SkBlurStyle)) { + return false; + } + + dst->fFormat = SkMask::k3D_Format; + if (margin) { + margin->set(SkScalarCeilToInt(3*sigma), SkScalarCeilToInt(3*sigma)); + } + + if (src.fImage == nullptr) { + return true; + } + + // create a larger buffer for the other two channels (should force fBlur to do this for us) + + { + uint8_t* alphaPlane = dst->fImage; + size_t planeSize = dst->computeImageSize(); + if (0 == planeSize) { + return false; // too big to allocate, abort + } + dst->fImage = SkMask::AllocImage(planeSize * 3); + memcpy(dst->fImage, alphaPlane, planeSize); + SkMask::FreeImage(alphaPlane); + } + + // run the light direction through the matrix... + Light light = fLight; + matrix.mapVectors((SkVector*)(void*)light.fDirection, + (SkVector*)(void*)fLight.fDirection, 1); + + // now restore the length of the XY component + // cast to SkVector so we can call setLength (this double cast silences alias warnings) + SkVector* vec = (SkVector*)(void*)light.fDirection; + vec->setLength(light.fDirection[0], + light.fDirection[1], + SkPoint::Length(fLight.fDirection[0], fLight.fDirection[1])); + + SkEmbossMask::Emboss(dst, light); + + // restore original alpha + memcpy(dst->fImage, src.fImage, src.computeImageSize()); + + return true; +} + +sk_sp SkEmbossMaskFilter::CreateProc(SkReadBuffer& buffer) { + Light light; + if (buffer.readByteArray(&light, sizeof(Light))) { + light.fPad = 0; // for the font-cache lookup to be clean + const SkScalar sigma = buffer.readScalar(); + return Make(sigma, light); + } + return nullptr; +} + +void SkEmbossMaskFilter::flatten(SkWriteBuffer& buffer) const { + Light tmpLight = fLight; + tmpLight.fPad = 0; // for the font-cache lookup to be clean + buffer.writeByteArray(&tmpLight, sizeof(tmpLight)); + buffer.writeScalar(fBlurSigma); +} diff --git a/gfx/skia/skia/src/effects/SkEmbossMaskFilter.h b/gfx/skia/skia/src/effects/SkEmbossMaskFilter.h new file mode 100644 index 0000000000..d79e89b94e --- /dev/null +++ b/gfx/skia/skia/src/effects/SkEmbossMaskFilter.h @@ -0,0 +1,48 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEmbossMaskFilter_DEFINED +#define SkEmbossMaskFilter_DEFINED + +#include "src/core/SkMaskFilterBase.h" + +/** \class SkEmbossMaskFilter + + This mask filter creates a 3D emboss look, by specifying a light and blur amount. +*/ +class SK_API SkEmbossMaskFilter : public SkMaskFilterBase { +public: + struct Light { + SkScalar fDirection[3]; // x,y,z + uint16_t fPad; + uint8_t fAmbient; + uint8_t fSpecular; // exponent, 4.4 right now + }; + + static sk_sp Make(SkScalar blurSigma, const Light& light); + + // overrides from SkMaskFilter + // This method is not exported to java. + SkMask::Format getFormat() const override; + // This method is not exported to java. + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, + SkIPoint* margin) const override; + +protected: + SkEmbossMaskFilter(SkScalar blurSigma, const Light& light); + void flatten(SkWriteBuffer&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkEmbossMaskFilter) + + Light fLight; + SkScalar fBlurSigma; + + typedef SkMaskFilter INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/SkHighContrastFilter.cpp b/gfx/skia/skia/src/effects/SkHighContrastFilter.cpp new file mode 100644 index 0000000000..5382a037af --- /dev/null +++ b/gfx/skia/skia/src/effects/SkHighContrastFilter.cpp @@ -0,0 +1,361 @@ +/* +* Copyright 2017 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/core/SkString.h" +#include "include/effects/SkHighContrastFilter.h" +#include "include/private/SkColorData.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkEffectPriv.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#endif + +using InvertStyle = SkHighContrastConfig::InvertStyle; + +class SkHighContrast_Filter : public SkColorFilter { +public: + SkHighContrast_Filter(const SkHighContrastConfig& config) { + fConfig = config; + // Clamp contrast to just inside -1 to 1 to avoid division by zero. + fConfig.fContrast = SkScalarPin(fConfig.fContrast, + -1.0f + FLT_EPSILON, + 1.0f - FLT_EPSILON); + } + + ~SkHighContrast_Filter() override {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override; +#endif + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override; + +protected: + void flatten(SkWriteBuffer&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkHighContrast_Filter) + + SkHighContrastConfig fConfig; + + friend class SkHighContrastFilter; + + typedef SkColorFilter INHERITED; +}; + +bool SkHighContrast_Filter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const { + SkRasterPipeline* p = rec.fPipeline; + SkArenaAlloc* alloc = rec.fAlloc; + + if (!shaderIsOpaque) { + p->append(SkRasterPipeline::unpremul); + } + + // Linearize before applying high-contrast filter. + auto tf = alloc->make(); + if (rec.fDstCS) { + rec.fDstCS->transferFn(&tf->g); + } else { + // Historically we approximate untagged destinations as gamma 2. + // TODO: sRGB? + *tf = {2,1, 0,0,0,0,0}; + } + p->append_transfer_function(*tf); + + if (fConfig.fGrayscale) { + float r = SK_LUM_COEFF_R; + float g = SK_LUM_COEFF_G; + float b = SK_LUM_COEFF_B; + float* matrix = alloc->makeArray(12); + matrix[0] = matrix[1] = matrix[2] = r; + matrix[3] = matrix[4] = matrix[5] = g; + matrix[6] = matrix[7] = matrix[8] = b; + p->append(SkRasterPipeline::matrix_3x4, matrix); + } + + if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) { + float* matrix = alloc->makeArray(12); + matrix[0] = matrix[4] = matrix[8] = -1; + matrix[9] = matrix[10] = matrix[11] = 1; + p->append(SkRasterPipeline::matrix_3x4, matrix); + } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) { + p->append(SkRasterPipeline::rgb_to_hsl); + float* matrix = alloc->makeArray(12); + matrix[0] = matrix[4] = matrix[11] = 1; + matrix[8] = -1; + p->append(SkRasterPipeline::matrix_3x4, matrix); + p->append(SkRasterPipeline::hsl_to_rgb); + } + + if (fConfig.fContrast != 0.0) { + float* matrix = alloc->makeArray(12); + float c = fConfig.fContrast; + float m = (1 + c) / (1 - c); + float b = (-0.5f * m + 0.5f); + matrix[0] = matrix[4] = matrix[8] = m; + matrix[9] = matrix[10] = matrix[11] = b; + p->append(SkRasterPipeline::matrix_3x4, matrix); + } + + p->append(SkRasterPipeline::clamp_0); + p->append(SkRasterPipeline::clamp_1); + + // Re-encode back from linear. + auto invTF = alloc->make(); + if (rec.fDstCS) { + rec.fDstCS->invTransferFn(&invTF->g); + } else { + // See above... historically untagged == gamma 2 in this filter. + *invTF ={0.5f,1, 0,0,0,0,0}; + } + p->append_transfer_function(*invTF); + + if (!shaderIsOpaque) { + p->append(SkRasterPipeline::premul); + } + return true; +} + +void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const { + buffer.writeBool(fConfig.fGrayscale); + buffer.writeInt(static_cast(fConfig.fInvertStyle)); + buffer.writeScalar(fConfig.fContrast); +} + +sk_sp SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) { + SkHighContrastConfig config; + config.fGrayscale = buffer.readBool(); + config.fInvertStyle = buffer.read32LE(InvertStyle::kLast); + config.fContrast = buffer.readScalar(); + + return SkHighContrastFilter::Make(config); +} + +sk_sp SkHighContrastFilter::Make( + const SkHighContrastConfig& config) { + if (!config.isValid()) { + return nullptr; + } + return sk_make_sp(config); +} + +void SkHighContrastFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkHighContrast_Filter); +} + +#if SK_SUPPORT_GPU +class HighContrastFilterEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(const SkHighContrastConfig& config, + bool linearize) { + return std::unique_ptr(new HighContrastFilterEffect(config, + linearize)); + } + + const char* name() const override { return "HighContrastFilter"; } + + const SkHighContrastConfig& config() const { return fConfig; } + bool linearize() const { return fLinearize; } + + std::unique_ptr clone() const override { + return Make(fConfig, fLinearize); + } + +private: + HighContrastFilterEffect(const SkHighContrastConfig& config, bool linearize) + : INHERITED(kHighContrastFilterEffect_ClassID, kNone_OptimizationFlags) + , fConfig(config) + , fLinearize(linearize) {} + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + bool onIsEqual(const GrFragmentProcessor& other) const override { + const HighContrastFilterEffect& that = other.cast(); + return fConfig.fGrayscale == that.fConfig.fGrayscale && + fConfig.fInvertStyle == that.fConfig.fInvertStyle && + fConfig.fContrast == that.fConfig.fContrast && + fLinearize == that.fLinearize; + } + + SkHighContrastConfig fConfig; + bool fLinearize; + + typedef GrFragmentProcessor INHERITED; +}; + +class GLHighContrastFilterEffect : public GrGLSLFragmentProcessor { +public: + static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + void emitCode(EmitArgs& args) override; + +private: + UniformHandle fContrastUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +GrGLSLFragmentProcessor* HighContrastFilterEffect::onCreateGLSLInstance() const { + return new GLHighContrastFilterEffect(); +} + +void HighContrastFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLHighContrastFilterEffect::GenKey(*this, caps, b); +} + +void GLHighContrastFilterEffect::onSetData(const GrGLSLProgramDataManager& pdm, + const GrFragmentProcessor& proc) { + const HighContrastFilterEffect& hcfe = proc.cast(); + pdm.set1f(fContrastUni, hcfe.config().fContrast); +} + +void GLHighContrastFilterEffect::GenKey( + const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const HighContrastFilterEffect& hcfe = proc.cast(); + b->add32(static_cast(hcfe.config().fGrayscale)); + b->add32(static_cast(hcfe.config().fInvertStyle)); + b->add32(hcfe.linearize() ? 1 : 0); +} + +void GLHighContrastFilterEffect::emitCode(EmitArgs& args) { + const HighContrastFilterEffect& hcfe = args.fFp.cast(); + const SkHighContrastConfig& config = hcfe.config(); + + const char* contrast; + fContrastUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "contrast", &contrast); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + fragBuilder->codeAppendf("half4 color = %s;", args.fInputColor); + + // Unpremultiply. The max() is to guard against 0 / 0. + fragBuilder->codeAppendf("half nonZeroAlpha = max(color.a, 0.0001);"); + fragBuilder->codeAppendf("color = half4(color.rgb / nonZeroAlpha, nonZeroAlpha);"); + + if (hcfe.linearize()) { + fragBuilder->codeAppend("color.rgb = color.rgb * color.rgb;"); + } + + // Grayscale. + if (config.fGrayscale) { + fragBuilder->codeAppendf("half luma = dot(color, half4(%f, %f, %f, 0));", + SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B); + fragBuilder->codeAppendf("color = half4(luma, luma, luma, 0);"); + } + + if (config.fInvertStyle == InvertStyle::kInvertBrightness) { + fragBuilder->codeAppendf("color = half4(1, 1, 1, 1) - color;"); + } + + if (config.fInvertStyle == InvertStyle::kInvertLightness) { + // Convert from RGB to HSL. + fragBuilder->codeAppendf("half fmax = max(color.r, max(color.g, color.b));"); + fragBuilder->codeAppendf("half fmin = min(color.r, min(color.g, color.b));"); + fragBuilder->codeAppendf("half l = (fmax + fmin) / 2;"); + + fragBuilder->codeAppendf("half h;"); + fragBuilder->codeAppendf("half s;"); + + fragBuilder->codeAppendf("if (fmax == fmin) {"); + fragBuilder->codeAppendf(" h = 0;"); + fragBuilder->codeAppendf(" s = 0;"); + fragBuilder->codeAppendf("} else {"); + fragBuilder->codeAppendf(" half d = fmax - fmin;"); + fragBuilder->codeAppendf(" s = l > 0.5 ?"); + fragBuilder->codeAppendf(" d / (2 - fmax - fmin) :"); + fragBuilder->codeAppendf(" d / (fmax + fmin);"); + // We'd like to just write "if (color.r == fmax) { ... }". On many GPUs, running the + // angle_d3d9_es2 config, that failed. It seems that max(x, y) is not necessarily equal + // to either x or y. Tried several ways to fix it, but this was the only reasonable fix. + fragBuilder->codeAppendf(" if (color.r >= color.g && color.r >= color.b) {"); + fragBuilder->codeAppendf(" h = (color.g - color.b) / d + "); + fragBuilder->codeAppendf(" (color.g < color.b ? 6 : 0);"); + fragBuilder->codeAppendf(" } else if (color.g >= color.b) {"); + fragBuilder->codeAppendf(" h = (color.b - color.r) / d + 2;"); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" h = (color.r - color.g) / d + 4;"); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + fragBuilder->codeAppendf("h /= 6;"); + fragBuilder->codeAppendf("l = 1.0 - l;"); + // Convert back from HSL to RGB. + SkString hue2rgbFuncName; + const GrShaderVar gHue2rgbArgs[] = { + GrShaderVar("p", kHalf_GrSLType), + GrShaderVar("q", kHalf_GrSLType), + GrShaderVar("t", kHalf_GrSLType), + }; + fragBuilder->emitFunction(kHalf_GrSLType, + "hue2rgb", + SK_ARRAY_COUNT(gHue2rgbArgs), + gHue2rgbArgs, + "if (t < 0)" + " t += 1;" + "if (t > 1)" + " t -= 1;" + "if (t < 1/6.)" + " return p + (q - p) * 6 * t;" + "if (t < 1/2.)" + " return q;" + "if (t < 2/3.)" + " return p + (q - p) * (2/3. - t) * 6;" + "return p;", + &hue2rgbFuncName); + fragBuilder->codeAppendf("if (s == 0) {"); + fragBuilder->codeAppendf(" color = half4(l, l, l, 0);"); + fragBuilder->codeAppendf("} else {"); + fragBuilder->codeAppendf(" half q = l < 0.5 ? l * (1 + s) : l + s - l * s;"); + fragBuilder->codeAppendf(" half p = 2 * l - q;"); + fragBuilder->codeAppendf(" color.r = %s(p, q, h + 1/3.);", hue2rgbFuncName.c_str()); + fragBuilder->codeAppendf(" color.g = %s(p, q, h);", hue2rgbFuncName.c_str()); + fragBuilder->codeAppendf(" color.b = %s(p, q, h - 1/3.);", hue2rgbFuncName.c_str()); + fragBuilder->codeAppendf("}"); + } + + // Contrast. + fragBuilder->codeAppendf("if (%s != 0) {", contrast); + fragBuilder->codeAppendf(" half m = (1 + %s) / (1 - %s);", contrast, contrast); + fragBuilder->codeAppendf(" half off = (-0.5 * m + 0.5);"); + fragBuilder->codeAppendf(" color = m * color + off;"); + fragBuilder->codeAppendf("}"); + + // Clamp. + fragBuilder->codeAppendf("color = saturate(color);"); + + if (hcfe.linearize()) { + fragBuilder->codeAppend("color.rgb = sqrt(color.rgb);"); + } + + // Restore the original alpha and premultiply. + fragBuilder->codeAppendf("color.a = %s.a;", args.fInputColor); + fragBuilder->codeAppendf("color.rgb *= color.a;"); + + // Copy to the output color. + fragBuilder->codeAppendf("%s = color;", args.fOutputColor); +} + +std::unique_ptr SkHighContrast_Filter::asFragmentProcessor( + GrRecordingContext*, const GrColorInfo& csi) const { + bool linearize = !csi.isLinearlyBlended(); + return HighContrastFilterEffect::Make(fConfig, linearize); +} +#endif diff --git a/gfx/skia/skia/src/effects/SkLayerDrawLooper.cpp b/gfx/skia/skia/src/effects/SkLayerDrawLooper.cpp new file mode 100644 index 0000000000..42592211a9 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkLayerDrawLooper.cpp @@ -0,0 +1,328 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkMaskFilter.h" +#include "include/core/SkString.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/effects/SkBlurDrawLooper.h" +#include "include/effects/SkLayerDrawLooper.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkStringUtils.h" +#include "src/core/SkWriteBuffer.h" +#include "src/core/SkXfermodePriv.h" + +SkLayerDrawLooper::LayerInfo::LayerInfo() { + fPaintBits = 0; // ignore our paint fields + fColorMode = SkBlendMode::kDst; // ignore our color + fOffset.set(0, 0); + fPostTranslate = false; +} + +SkLayerDrawLooper::SkLayerDrawLooper() + : fRecs(nullptr), + fCount(0) { +} + +SkLayerDrawLooper::~SkLayerDrawLooper() { + Rec* rec = fRecs; + while (rec) { + Rec* next = rec->fNext; + delete rec; + rec = next; + } +} + +SkLayerDrawLooper::Context* +SkLayerDrawLooper::makeContext(SkArenaAlloc* alloc) const { + return alloc->make(this); +} + +static SkColor4f xferColor(const SkColor4f& src, const SkColor4f& dst, SkBlendMode mode) { + switch (mode) { + case SkBlendMode::kSrc: + return src; + case SkBlendMode::kDst: + return dst; + default: { + SkPMColor4f pmS = src.premul(); + SkPMColor4f pmD = dst.premul(); + return SkBlendMode_Apply(mode, pmS, pmD).unpremul(); + } + } +} + +// Even with kEntirePaint_Bits, we always ensure that the master paint's +// text-encoding is respected, since that controls how we interpret the +// text/length parameters of a draw[Pos]Text call. +void SkLayerDrawLooper::LayerDrawLooperContext::ApplyInfo( + SkPaint* dst, const SkPaint& src, const LayerInfo& info) { + SkColor4f srcColor = src.getColor4f(); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // The framework may respect the alpha value on the original paint. + // Match this legacy behavior. + if (src.getAlpha() == 255) { + srcColor.fA = dst->getColor4f().fA; + } +#endif + dst->setColor4f(xferColor(srcColor, dst->getColor4f(), (SkBlendMode)info.fColorMode), + sk_srgb_singleton()); + + BitFlags bits = info.fPaintBits; + + if (0 == bits) { + return; + } + if (kEntirePaint_Bits == bits) { + // we've already computed these, so save it from the assignment + bool aa = dst->isAntiAlias(); + bool di = dst->isDither(); + SkColor4f c = dst->getColor4f(); + *dst = src; + dst->setAntiAlias(aa); + dst->setDither(di); + dst->setColor4f(c, sk_srgb_singleton()); + return; + } + + if (bits & kStyle_Bit) { + dst->setStyle(src.getStyle()); + dst->setStrokeWidth(src.getStrokeWidth()); + dst->setStrokeMiter(src.getStrokeMiter()); + dst->setStrokeCap(src.getStrokeCap()); + dst->setStrokeJoin(src.getStrokeJoin()); + } + + if (bits & kPathEffect_Bit) { + dst->setPathEffect(src.refPathEffect()); + } + if (bits & kMaskFilter_Bit) { + dst->setMaskFilter(src.refMaskFilter()); + } + if (bits & kShader_Bit) { + dst->setShader(src.refShader()); + } + if (bits & kColorFilter_Bit) { + dst->setColorFilter(src.refColorFilter()); + } + if (bits & kXfermode_Bit) { + dst->setBlendMode(src.getBlendMode()); + } + + // we don't override these +#if 0 + dst->setTypeface(src.getTypeface()); + dst->setTextSize(src.getTextSize()); + dst->setTextScaleX(src.getTextScaleX()); + dst->setRasterizer(src.getRasterizer()); + dst->setLooper(src.getLooper()); + dst->setTextEncoding(src.getTextEncoding()); + dst->setHinting(src.getHinting()); +#endif +} + +SkLayerDrawLooper::LayerDrawLooperContext::LayerDrawLooperContext( + const SkLayerDrawLooper* looper) : fCurrRec(looper->fRecs) {} + +bool SkLayerDrawLooper::LayerDrawLooperContext::next(Info* info, SkPaint* paint) { + if (nullptr == fCurrRec) { + return false; + } + + ApplyInfo(paint, fCurrRec->fPaint, fCurrRec->fInfo); + + if (info) { + info->fTranslate = fCurrRec->fInfo.fOffset; + info->fApplyPostCTM = fCurrRec->fInfo.fPostTranslate; + } + fCurrRec = fCurrRec->fNext; + return true; +} + +bool SkLayerDrawLooper::asABlurShadow(BlurShadowRec* bsRec) const { + if (fCount != 2) { + return false; + } + const Rec* rec = fRecs; + + // bottom layer needs to be just blur(maskfilter) + if ((rec->fInfo.fPaintBits & ~kMaskFilter_Bit)) { + return false; + } + if (SkBlendMode::kSrc != (SkBlendMode)rec->fInfo.fColorMode) { + return false; + } + const SkMaskFilter* mf = rec->fPaint.getMaskFilter(); + if (nullptr == mf) { + return false; + } + SkMaskFilterBase::BlurRec maskBlur; + if (!as_MFB(mf)->asABlur(&maskBlur)) { + return false; + } + + rec = rec->fNext; + // top layer needs to be "plain" + if (rec->fInfo.fPaintBits) { + return false; + } + if (SkBlendMode::kDst != (SkBlendMode)rec->fInfo.fColorMode) { + return false; + } + if (!rec->fInfo.fOffset.equals(0, 0)) { + return false; + } + + if (bsRec) { + bsRec->fSigma = maskBlur.fSigma; + bsRec->fOffset = fRecs->fInfo.fOffset; + // TODO: Update BlurShadowRec to use SkColor4f? + bsRec->fColor = fRecs->fPaint.getColor(); + bsRec->fStyle = maskBlur.fStyle; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkLayerDrawLooper::flatten(SkWriteBuffer& buffer) const { + buffer.writeInt(fCount); + + Rec* rec = fRecs; + for (int i = 0; i < fCount; i++) { + // Legacy "flagsmask" field -- now ignored, remove when we bump version + buffer.writeInt(0); + + buffer.writeInt(rec->fInfo.fPaintBits); + buffer.writeInt((int)rec->fInfo.fColorMode); + buffer.writePoint(rec->fInfo.fOffset); + buffer.writeBool(rec->fInfo.fPostTranslate); + buffer.writePaint(rec->fPaint); + rec = rec->fNext; + } +} + +sk_sp SkLayerDrawLooper::CreateProc(SkReadBuffer& buffer) { + int count = buffer.readInt(); + + Builder builder; + for (int i = 0; i < count; i++) { + LayerInfo info; + // Legacy "flagsmask" field -- now ignored, remove when we bump version + (void)buffer.readInt(); + + info.fPaintBits = buffer.readInt(); + info.fColorMode = (SkBlendMode)buffer.readInt(); + buffer.readPoint(&info.fOffset); + info.fPostTranslate = buffer.readBool(); + buffer.readPaint(builder.addLayerOnTop(info), nullptr); + if (!buffer.isValid()) { + return nullptr; + } + } + return builder.detach(); +} + +SkLayerDrawLooper::Builder::Builder() + : fRecs(nullptr), + fTopRec(nullptr), + fCount(0) { +} + +SkLayerDrawLooper::Builder::~Builder() { + Rec* rec = fRecs; + while (rec) { + Rec* next = rec->fNext; + delete rec; + rec = next; + } +} + +SkPaint* SkLayerDrawLooper::Builder::addLayer(const LayerInfo& info) { + fCount += 1; + + Rec* rec = new Rec; + rec->fNext = fRecs; + rec->fInfo = info; + fRecs = rec; + if (nullptr == fTopRec) { + fTopRec = rec; + } + + return &rec->fPaint; +} + +void SkLayerDrawLooper::Builder::addLayer(SkScalar dx, SkScalar dy) { + LayerInfo info; + + info.fOffset.set(dx, dy); + (void)this->addLayer(info); +} + +SkPaint* SkLayerDrawLooper::Builder::addLayerOnTop(const LayerInfo& info) { + fCount += 1; + + Rec* rec = new Rec; + rec->fNext = nullptr; + rec->fInfo = info; + if (nullptr == fRecs) { + fRecs = rec; + } else { + SkASSERT(fTopRec); + fTopRec->fNext = rec; + } + fTopRec = rec; + + return &rec->fPaint; +} + +sk_sp SkLayerDrawLooper::Builder::detach() { + SkLayerDrawLooper* looper = new SkLayerDrawLooper; + looper->fCount = fCount; + looper->fRecs = fRecs; + + fCount = 0; + fRecs = nullptr; + fTopRec = nullptr; + + return sk_sp(looper); +} + +sk_sp SkBlurDrawLooper::Make(SkColor color, SkScalar sigma, SkScalar dx, SkScalar dy) +{ + return Make(SkColor4f::FromColor(color), sk_srgb_singleton(), sigma, dx, dy); +} + +sk_sp SkBlurDrawLooper::Make(SkColor4f color, SkColorSpace* cs, + SkScalar sigma, SkScalar dx, SkScalar dy) +{ + sk_sp blur = nullptr; + if (sigma > 0.0f) { + blur = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, true); + } + + SkLayerDrawLooper::Builder builder; + + // First layer + SkLayerDrawLooper::LayerInfo defaultLayer; + builder.addLayer(defaultLayer); + + // Blur layer + SkLayerDrawLooper::LayerInfo blurInfo; + blurInfo.fColorMode = SkBlendMode::kSrc; + blurInfo.fPaintBits = SkLayerDrawLooper::kMaskFilter_Bit; + blurInfo.fOffset = SkVector::Make(dx, dy); + SkPaint* paint = builder.addLayer(blurInfo); + paint->setMaskFilter(std::move(blur)); + paint->setColor4f(color, cs); + + return builder.detach(); +} diff --git a/gfx/skia/skia/src/effects/SkLumaColorFilter.cpp b/gfx/skia/skia/src/effects/SkLumaColorFilter.cpp new file mode 100644 index 0000000000..f164408caa --- /dev/null +++ b/gfx/skia/skia/src/effects/SkLumaColorFilter.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/effects/SkLumaColorFilter.h" +#include "include/private/SkColorData.h" +#include "src/core/SkEffectPriv.h" +#include "src/core/SkRasterPipeline.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/effects/generated/GrLumaColorFilterEffect.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#endif + +bool SkLumaColorFilter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const { + rec.fPipeline->append(SkRasterPipeline::bt709_luminance_or_luma_to_alpha); + rec.fPipeline->append(SkRasterPipeline::clamp_0); + rec.fPipeline->append(SkRasterPipeline::clamp_1); + return true; +} + +sk_sp SkLumaColorFilter::Make() { + return sk_sp(new SkLumaColorFilter); +} + +SkLumaColorFilter::SkLumaColorFilter() : INHERITED() {} + +sk_sp SkLumaColorFilter::CreateProc(SkReadBuffer&) { + return Make(); +} + +void SkLumaColorFilter::flatten(SkWriteBuffer&) const {} + +#if SK_SUPPORT_GPU +std::unique_ptr SkLumaColorFilter::asFragmentProcessor( + GrRecordingContext*, const GrColorInfo&) const { + return GrLumaColorFilterEffect::Make(); +} +#endif diff --git a/gfx/skia/skia/src/effects/SkOpPE.h b/gfx/skia/skia/src/effects/SkOpPE.h new file mode 100644 index 0000000000..1f51579ebf --- /dev/null +++ b/gfx/skia/skia/src/effects/SkOpPE.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOpPE_DEFINED +#define SkOpPE_DEFINED + +#include "include/effects/SkOpPathEffect.h" + +class SkOpPE : public SkPathEffect { +public: + SkOpPE(sk_sp one, sk_sp two, SkPathOp op); + + +protected: + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkOpPE) + + sk_sp fOne; + sk_sp fTwo; + SkPathOp fOp; + + typedef SkPathEffect INHERITED; +}; + +class SkMatrixPE : public SkPathEffect { +public: + SkMatrixPE(const SkMatrix&); + +protected: + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkMatrixPE) + + SkMatrix fMatrix; + + typedef SkPathEffect INHERITED; +}; + +class SkStrokePE : public SkPathEffect { +public: + SkStrokePE(SkScalar width, SkPaint::Join, SkPaint::Cap, SkScalar miter); + +protected: + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + // TODO: override onComputeFastBounds (I think) + +private: + SK_FLATTENABLE_HOOKS(SkStrokePE) + + SkScalar fWidth, + fMiter; + SkPaint::Join fJoin; + SkPaint::Cap fCap; + + typedef SkPathEffect INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/effects/SkOpPathEffect.cpp b/gfx/skia/skia/src/effects/SkOpPathEffect.cpp new file mode 100644 index 0000000000..e7b8a604bd --- /dev/null +++ b/gfx/skia/skia/src/effects/SkOpPathEffect.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStrokeRec.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/effects/SkOpPE.h" + +sk_sp SkMergePathEffect::Make(sk_sp one, sk_sp two, + SkPathOp op) { + return sk_sp(new SkOpPE(std::move(one), std::move(two), op)); +} + +SkOpPE::SkOpPE(sk_sp one, sk_sp two, SkPathOp op) + : fOne(std::move(one)), fTwo(std::move(two)), fOp(op) {} + +bool SkOpPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cull) const { + SkPath one, two; + if (fOne) { + if (!fOne->filterPath(&one, src, rec, cull)) { + return false; + } + } else { + one = src; + } + if (fTwo) { + if (!fTwo->filterPath(&two, src, rec, cull)) { + return false; + } + } else { + two = src; + } + return Op(one, two, fOp, dst); +} + +void SkOpPE::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fOne.get()); + buffer.writeFlattenable(fTwo.get()); + buffer.write32(fOp); +} + +sk_sp SkOpPE::CreateProc(SkReadBuffer& buffer) { + auto one = buffer.readPathEffect(); + auto two = buffer.readPathEffect(); + SkPathOp op = buffer.read32LE(kReverseDifference_SkPathOp); + return buffer.isValid() ? SkMergePathEffect::Make(std::move(one), std::move(two), op) : nullptr; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkMatrixPathEffect::MakeTranslate(SkScalar dx, SkScalar dy) { + if (!SkScalarsAreFinite(dx, dy)) { + return nullptr; + } + return sk_sp(new SkMatrixPE(SkMatrix::MakeTrans(dx, dy))); +} + +sk_sp SkMatrixPathEffect::Make(const SkMatrix& matrix) { + if (!matrix.isFinite()) { + return nullptr; + } + return sk_sp(new SkMatrixPE(matrix)); +} + +SkMatrixPE::SkMatrixPE(const SkMatrix& matrix) : fMatrix(matrix) { + SkASSERT(matrix.isFinite()); +} + +bool SkMatrixPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const { + src.transform(fMatrix, dst); + return true; +} + +void SkMatrixPE::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(fMatrix); +} + +sk_sp SkMatrixPE::CreateProc(SkReadBuffer& buffer) { + SkMatrix mx; + buffer.readMatrix(&mx); + return buffer.isValid() ? SkMatrixPathEffect::Make(mx) : nullptr; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkStrokePathEffect::Make(SkScalar width, SkPaint::Join join, SkPaint::Cap cap, + SkScalar miter) { + if (!SkScalarsAreFinite(width, miter) || width < 0 || miter < 0) { + return nullptr; + } + return sk_sp(new SkStrokePE(width, join, cap, miter)); +} + +SkStrokePE::SkStrokePE(SkScalar width, SkPaint::Join join, SkPaint::Cap cap, SkScalar miter) + : fWidth(width), fMiter(miter), fJoin(join), fCap(cap) {} + +bool SkStrokePE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const { + SkStrokeRec rec(SkStrokeRec::kFill_InitStyle); + rec.setStrokeStyle(fWidth); + rec.setStrokeParams(fCap, fJoin, fMiter); + return rec.applyToPath(dst, src); +} + +void SkStrokePE::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fWidth); + buffer.writeScalar(fMiter); + buffer.write32(fJoin); + buffer.write32(fCap); +} + +sk_sp SkStrokePE::CreateProc(SkReadBuffer& buffer) { + SkScalar width = buffer.readScalar(); + SkScalar miter = buffer.readScalar(); + SkPaint::Join join = buffer.read32LE(SkPaint::kLast_Join); + SkPaint::Cap cap = buffer.read32LE(SkPaint::kLast_Cap); + return buffer.isValid() ? SkStrokePathEffect::Make(width, join, cap, miter) : nullptr; +} + + diff --git a/gfx/skia/skia/src/effects/SkOverdrawColorFilter.cpp b/gfx/skia/skia/src/effects/SkOverdrawColorFilter.cpp new file mode 100644 index 0000000000..c91748eca6 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkOverdrawColorFilter.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkOverdrawColorFilter.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkEffectPriv.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/effects/GrSkSLFP.h" + +GR_FP_SRC_STRING SKSL_OVERDRAW_SRC = R"( +layout(ctype=SkPMColor) uniform half4 color0; +layout(ctype=SkPMColor) uniform half4 color1; +layout(ctype=SkPMColor) uniform half4 color2; +layout(ctype=SkPMColor) uniform half4 color3; +layout(ctype=SkPMColor) uniform half4 color4; +layout(ctype=SkPMColor) uniform half4 color5; + +void main(inout half4 color) { + half alpha = 255.0 * color.a; + if (alpha < 0.5) { + color = color0; + } else if (alpha < 1.5) { + color = color1; + } else if (alpha < 2.5) { + color = color2; + } else if (alpha < 3.5) { + color = color3; + } else if (alpha < 4.5) { + color = color4; + } else { + color = color5; + } +} +)"; +#endif + +bool SkOverdrawColorFilter::onAppendStages(const SkStageRec& rec, bool shader_is_opaque) const { + struct Ctx : public SkRasterPipeline_CallbackCtx { + const SkPMColor* colors; + }; + // TODO: do we care about transforming to dstCS? + auto ctx = rec.fAlloc->make(); + ctx->colors = fColors; + ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) { + auto ctx = (Ctx*)arg; + auto pixels = (SkPMColor4f*)ctx->rgba; + for (int i = 0; i < active_pixels; i++) { + uint8_t alpha = (int)(pixels[i].fA * 255); + if (alpha >= kNumColors) { + alpha = kNumColors - 1; + } + pixels[i] = SkPMColor4f::FromPMColor(ctx->colors[alpha]); + } + }; + rec.fPipeline->append(SkRasterPipeline::callback, ctx); + return true; +} + +void SkOverdrawColorFilter::flatten(SkWriteBuffer& buffer) const { + buffer.writeByteArray(fColors, kNumColors * sizeof(SkPMColor)); +} + +sk_sp SkOverdrawColorFilter::CreateProc(SkReadBuffer& buffer) { + SkPMColor colors[kNumColors]; + size_t size = buffer.getArrayCount(); + if (!buffer.validate(size == sizeof(colors))) { + return nullptr; + } + if (!buffer.readByteArray(colors, sizeof(colors))) { + return nullptr; + } + + return SkOverdrawColorFilter::Make(colors); +} + +void SkOverdrawColorFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkOverdrawColorFilter); +} +#if SK_SUPPORT_GPU + +#include "include/private/GrRecordingContext.h" + +std::unique_ptr SkOverdrawColorFilter::asFragmentProcessor( + GrRecordingContext* context, const GrColorInfo&) const { + static int overdrawIndex = GrSkSLFP::NewIndex(); + return GrSkSLFP::Make(context, overdrawIndex, "Overdraw", SKSL_OVERDRAW_SRC, fColors, + sizeof(fColors)); +} + +#endif diff --git a/gfx/skia/skia/src/effects/SkPackBits.cpp b/gfx/skia/skia/src/effects/SkPackBits.cpp new file mode 100644 index 0000000000..a590eb6c60 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkPackBits.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkTo.h" +#include "src/effects/SkPackBits.h" + +#include + +size_t SkPackBits::ComputeMaxSize8(size_t srcSize) { + // worst case is the number of 8bit values + 1 byte per (up to) 128 entries. + return ((srcSize + 127) >> 7) + srcSize; +} + +static uint8_t* flush_same8(uint8_t dst[], uint8_t value, size_t count) { + while (count > 0) { + size_t n = count > 128 ? 128 : count; + *dst++ = (uint8_t)(n - 1); + *dst++ = (uint8_t)value; + count -= n; + } + return dst; +} + +static uint8_t* flush_diff8(uint8_t* SK_RESTRICT dst, + const uint8_t* SK_RESTRICT src, size_t count) { + while (count > 0) { + size_t n = count > 128 ? 128 : count; + *dst++ = (uint8_t)(n + 127); + memcpy(dst, src, n); + src += n; + dst += n; + count -= n; + } + return dst; +} + +size_t SkPackBits::Pack8(const uint8_t* SK_RESTRICT src, size_t srcSize, + uint8_t* SK_RESTRICT dst, size_t dstSize) { + if (dstSize < ComputeMaxSize8(srcSize)) { + return 0; + } + + uint8_t* const origDst = dst; + const uint8_t* stop = src + srcSize; + + for (intptr_t count = stop - src; count > 0; count = stop - src) { + if (1 == count) { + *dst++ = 0; + *dst++ = *src; + break; + } + + unsigned value = *src; + const uint8_t* s = src + 1; + + if (*s == value) { // accumulate same values... + do { + s++; + if (s == stop) { + break; + } + } while (*s == value); + dst = flush_same8(dst, value, SkToInt(s - src)); + } else { // accumulate diff values... + do { + if (++s == stop) { + goto FLUSH_DIFF; + } + // only stop if we hit 3 in a row, + // otherwise we get bigger than compuatemax + } while (*s != s[-1] || s[-1] != s[-2]); + s -= 2; // back up so we don't grab the "same" values that follow + FLUSH_DIFF: + dst = flush_diff8(dst, src, SkToInt(s - src)); + } + src = s; + } + return dst - origDst; +} + +int SkPackBits::Unpack8(const uint8_t* SK_RESTRICT src, size_t srcSize, + uint8_t* SK_RESTRICT dst, size_t dstSize) { + uint8_t* const origDst = dst; + uint8_t* const endDst = dst + dstSize; + const uint8_t* stop = src + srcSize; + + while (src < stop) { + unsigned n = *src++; + if (n <= 127) { // repeat count (n + 1) + n += 1; + if (dst > (endDst - n) || src >= stop) { + return 0; + } + memset(dst, *src++, n); + } else { // same count (n - 127) + n -= 127; + if (dst > (endDst - n) || src > (stop - n)) { + return 0; + } + memcpy(dst, src, n); + src += n; + } + dst += n; + } + SkASSERT(src <= stop); + SkASSERT(dst <= endDst); + return SkToInt(dst - origDst); +} diff --git a/gfx/skia/skia/src/effects/SkPackBits.h b/gfx/skia/skia/src/effects/SkPackBits.h new file mode 100644 index 0000000000..b04e28d56c --- /dev/null +++ b/gfx/skia/skia/src/effects/SkPackBits.h @@ -0,0 +1,45 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkPackBits_DEFINED +#define SkPackBits_DEFINED + +#include "include/core/SkTypes.h" + +class SkPackBits { +public: + /** Given the number of 8bit values that will be passed to Pack8, + returns the worst-case size needed for the dst[] buffer. + */ + static size_t ComputeMaxSize8(size_t srcSize); + + /** Write the src array into a packed format. The packing process may end + up writing more bytes than it read, so dst[] must be large enough. + @param src Input array of 8bit values + @param srcSize Number of entries in src[] + @param dst Buffer (allocated by caller) to write the packed data + into + @param dstSize Number of bytes in the output buffer. + @return the number of bytes written to dst[] + */ + static size_t Pack8(const uint8_t src[], size_t srcSize, uint8_t dst[], + size_t dstSize); + + /** Unpack the data in src[], and expand it into dst[]. The src[] data was + written by a previous call to Pack8. + @param src Input data to unpack, previously created by Pack8. + @param srcSize Number of bytes of src to unpack + @param dst Buffer (allocated by caller) to expand the src[] into. + @param dstSize Number of bytes in the output buffer. + @return the number of bytes written into dst, or 0 if srcSize or dstSize are too small. + */ + static int Unpack8(const uint8_t src[], size_t srcSize, uint8_t dst[], + size_t dstSize); +}; + +#endif diff --git a/gfx/skia/skia/src/effects/SkShaderMaskFilter.cpp b/gfx/skia/skia/src/effects/SkShaderMaskFilter.cpp new file mode 100644 index 0000000000..1cae6a1a77 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkShaderMaskFilter.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkString.h" +#include "include/effects/SkShaderMaskFilter.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkReadBuffer.h" +#include "src/shaders/SkShaderBase.h" + +class SkShaderMF : public SkMaskFilterBase { +public: + SkShaderMF(sk_sp shader) : fShader(std::move(shader)) {} + + SkMask::Format getFormat() const override { return SkMask::kA8_Format; } + + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, + SkIPoint* margin) const override; + + void computeFastBounds(const SkRect& src, SkRect* dst) const override { + *dst = src; + } + + bool asABlur(BlurRec*) const override { return false; } + +protected: +#if SK_SUPPORT_GPU + std::unique_ptr onAsFragmentProcessor(const GrFPArgs&) const override; + bool onHasFragmentProcessor() const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkShaderMF) + + sk_sp fShader; + + SkShaderMF(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + + friend class SkShaderMaskFilter; + + typedef SkMaskFilter INHERITED; +}; + +sk_sp SkShaderMF::CreateProc(SkReadBuffer& buffer) { + return SkShaderMaskFilter::Make(buffer.readShader()); +} + +void SkShaderMF::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fShader.get()); +} + +static void rect_memcpy(void* dst, size_t dstRB, const void* src, size_t srcRB, + size_t copyBytes, int rows) { + for (int i = 0; i < rows; ++i) { + memcpy(dst, src, copyBytes); + dst = (char*)dst + dstRB; + src = (const char*)src + srcRB; + } +} + +bool SkShaderMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm, + SkIPoint* margin) const { + if (src.fFormat != SkMask::kA8_Format) { + return false; + } + + if (margin) { + margin->set(0, 0); + } + dst->fBounds = src.fBounds; + dst->fRowBytes = src.fBounds.width(); // need alignment? + dst->fFormat = SkMask::kA8_Format; + + if (src.fImage == nullptr) { + dst->fImage = nullptr; + return true; + } + size_t size = dst->computeImageSize(); + if (0 == size) { + return false; // too big to allocate, abort + } + + // Allocate and initialize dst image with a copy of the src image + dst->fImage = SkMask::AllocImage(size); + rect_memcpy(dst->fImage, dst->fRowBytes, src.fImage, src.fRowBytes, + src.fBounds.width() * sizeof(uint8_t), src.fBounds.height()); + + // Now we have a dst-mask, just need to setup a canvas and draw into it + SkBitmap bitmap; + if (!bitmap.installMaskPixels(*dst)) { + return false; + } + + SkPaint paint; + paint.setShader(fShader); + paint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality); + // this blendmode is the trick: we only draw the shader where the mask is + paint.setBlendMode(SkBlendMode::kSrcIn); + + SkCanvas canvas(bitmap); + canvas.translate(-SkIntToScalar(dst->fBounds.fLeft), -SkIntToScalar(dst->fBounds.fTop)); + canvas.concat(ctm); + canvas.drawPaint(paint); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" + +std::unique_ptr SkShaderMF::onAsFragmentProcessor(const GrFPArgs& args) const { + return GrFragmentProcessor::MulInputByChildAlpha(as_SB(fShader)->asFragmentProcessor(args)); +} + +bool SkShaderMF::onHasFragmentProcessor() const { + return true; +} + +#endif +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkShaderMaskFilter::Make(sk_sp shader) { + return shader ? sk_sp(new SkShaderMF(std::move(shader))) : nullptr; +} + +void SkShaderMaskFilter::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkShaderMF); } diff --git a/gfx/skia/skia/src/effects/SkTableColorFilter.cpp b/gfx/skia/skia/src/effects/SkTableColorFilter.cpp new file mode 100644 index 0000000000..601da8f149 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkTableColorFilter.cpp @@ -0,0 +1,432 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkTableColorFilter.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkString.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkEffectPriv.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +static const uint8_t gIdentityTable[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +class SkTable_ColorFilter : public SkColorFilter { +public: + SkTable_ColorFilter(const uint8_t tableA[], const uint8_t tableR[], + const uint8_t tableG[], const uint8_t tableB[]) { + fBitmap = nullptr; + fFlags = 0; + + uint8_t* dst = fStorage; + if (tableA) { + memcpy(dst, tableA, 256); + dst += 256; + fFlags |= kA_Flag; + } + if (tableR) { + memcpy(dst, tableR, 256); + dst += 256; + fFlags |= kR_Flag; + } + if (tableG) { + memcpy(dst, tableG, 256); + dst += 256; + fFlags |= kG_Flag; + } + if (tableB) { + memcpy(dst, tableB, 256); + fFlags |= kB_Flag; + } + } + + ~SkTable_ColorFilter() override { delete fBitmap; } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(GrRecordingContext*, + const GrColorInfo&) const override; +#endif + + enum { + kA_Flag = 1 << 0, + kR_Flag = 1 << 1, + kG_Flag = 1 << 2, + kB_Flag = 1 << 3, + }; + + bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override { + const uint8_t *r = gIdentityTable, + *g = gIdentityTable, + *b = gIdentityTable, + *a = gIdentityTable; + const uint8_t* ptr = fStorage; + if (fFlags & kA_Flag) { a = ptr; ptr += 256; } + if (fFlags & kR_Flag) { r = ptr; ptr += 256; } + if (fFlags & kG_Flag) { g = ptr; ptr += 256; } + if (fFlags & kB_Flag) { b = ptr; } + + SkRasterPipeline* p = rec.fPipeline; + if (!shaderIsOpaque) { + p->append(SkRasterPipeline::unpremul); + } + + struct Tables { const uint8_t *r, *g, *b, *a; }; + p->append(SkRasterPipeline::byte_tables, rec.fAlloc->make(Tables{r,g,b,a})); + + bool definitelyOpaque = shaderIsOpaque && a[0xff] == 0xff; + if (!definitelyOpaque) { + p->append(SkRasterPipeline::premul); + } + return true; + } + +protected: + void flatten(SkWriteBuffer&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkTable_ColorFilter) + + void getTableAsBitmap(SkBitmap* table) const; + + mutable const SkBitmap* fBitmap; // lazily allocated + + uint8_t fStorage[256 * 4]; + unsigned fFlags; + + friend class SkTableColorFilter; + + typedef SkColorFilter INHERITED; +}; + +static const uint8_t gCountNibBits[] = { + 0, 1, 1, 2, + 1, 2, 2, 3, + 1, 2, 2, 3, + 2, 3, 3, 4 +}; + +#include "src/effects/SkPackBits.h" + +void SkTable_ColorFilter::flatten(SkWriteBuffer& buffer) const { + uint8_t storage[5*256]; + int count = gCountNibBits[fFlags & 0xF]; + size_t size = SkPackBits::Pack8(fStorage, count * 256, storage, + sizeof(storage)); + + buffer.write32(fFlags); + buffer.writeByteArray(storage, size); +} + +sk_sp SkTable_ColorFilter::CreateProc(SkReadBuffer& buffer) { + const int flags = buffer.read32(); + const size_t count = gCountNibBits[flags & 0xF]; + SkASSERT(count <= 4); + + uint8_t packedStorage[5*256]; + size_t packedSize = buffer.getArrayCount(); + if (!buffer.validate(packedSize <= sizeof(packedStorage))) { + return nullptr; + } + if (!buffer.readByteArray(packedStorage, packedSize)) { + return nullptr; + } + + uint8_t unpackedStorage[4*256]; + size_t unpackedSize = SkPackBits::Unpack8(packedStorage, packedSize, + unpackedStorage, sizeof(unpackedStorage)); + // now check that we got the size we expected + if (!buffer.validate(unpackedSize == count*256)) { + return nullptr; + } + + const uint8_t* a = nullptr; + const uint8_t* r = nullptr; + const uint8_t* g = nullptr; + const uint8_t* b = nullptr; + const uint8_t* ptr = unpackedStorage; + + if (flags & kA_Flag) { + a = ptr; + ptr += 256; + } + if (flags & kR_Flag) { + r = ptr; + ptr += 256; + } + if (flags & kG_Flag) { + g = ptr; + ptr += 256; + } + if (flags & kB_Flag) { + b = ptr; + ptr += 256; + } + return SkTableColorFilter::MakeARGB(a, r, g, b); +} + +void SkTable_ColorFilter::getTableAsBitmap(SkBitmap* table) const { + if (table) { + if (nullptr == fBitmap) { + SkBitmap* bmp = new SkBitmap; + bmp->allocPixels(SkImageInfo::MakeA8(256, 4)); + uint8_t* bitmapPixels = bmp->getAddr8(0, 0); + int offset = 0; + static const unsigned kFlags[] = { kA_Flag, kR_Flag, kG_Flag, kB_Flag }; + + for (int x = 0; x < 4; ++x) { + if (!(fFlags & kFlags[x])) { + memcpy(bitmapPixels, gIdentityTable, sizeof(gIdentityTable)); + } else { + memcpy(bitmapPixels, fStorage + offset, 256); + offset += 256; + } + bitmapPixels += 256; + } + bmp->setImmutable(); + fBitmap = bmp; + } + *table = *fBitmap; + } +} + +#if SK_SUPPORT_GPU + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class ColorTableEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(GrRecordingContext* context, + const SkBitmap& bitmap); + + ~ColorTableEffect() override {} + + const char* name() const override { return "ColorTableEffect"; } + + std::unique_ptr clone() const override { + return std::unique_ptr( + new ColorTableEffect(sk_ref_sp(fTextureSampler.proxy()))); + } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override { return true; } + + ColorTableEffect(sk_sp proxy) + : INHERITED(kColorTableEffect_ClassID, + kNone_OptimizationFlags) // Not bothering with table-specific optimizations. + , fTextureSampler(std::move(proxy)) { + this->setTextureSamplerCnt(1); + } + + const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + TextureSampler fTextureSampler; + + typedef GrFragmentProcessor INHERITED; +}; + +class GLColorTableEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*) {} + +private: + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GLColorTableEffect::emitCode(EmitArgs& args) { + static const float kColorScaleFactor = 255.0f / 256.0f; + static const float kColorOffsetFactor = 1.0f / 512.0f; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + if (nullptr == args.fInputColor) { + // the input color is solid white (all ones). + static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor; + fragBuilder->codeAppendf("\t\thalf4 coord = half4(%f, %f, %f, %f);\n", + kMaxValue, kMaxValue, kMaxValue, kMaxValue); + + } else { + fragBuilder->codeAppendf("\t\thalf nonZeroAlpha = max(%s.a, .0001);\n", args.fInputColor); + fragBuilder->codeAppendf("\t\thalf4 coord = half4(%s.rgb / nonZeroAlpha, nonZeroAlpha);\n", + args.fInputColor); + fragBuilder->codeAppendf("\t\tcoord = coord * %f + half4(%f, %f, %f, %f);\n", + kColorScaleFactor, + kColorOffsetFactor, kColorOffsetFactor, + kColorOffsetFactor, kColorOffsetFactor); + } + + SkString coord; + + fragBuilder->codeAppendf("\t\t%s.a = ", args.fOutputColor); + coord.printf("half2(coord.a, 0.125)"); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], coord.c_str()); + fragBuilder->codeAppend(".a;\n"); + + fragBuilder->codeAppendf("\t\t%s.r = ", args.fOutputColor); + coord.printf("half2(coord.r, 0.375)"); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], coord.c_str()); + fragBuilder->codeAppend(".a;\n"); + + fragBuilder->codeAppendf("\t\t%s.g = ", args.fOutputColor); + coord.printf("half2(coord.g, 0.625)"); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], coord.c_str()); + fragBuilder->codeAppend(".a;\n"); + + fragBuilder->codeAppendf("\t\t%s.b = ", args.fOutputColor); + coord.printf("half2(coord.b, 0.875)"); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], coord.c_str()); + fragBuilder->codeAppend(".a;\n"); + + fragBuilder->codeAppendf("\t\t%s.rgb *= %s.a;\n", args.fOutputColor, args.fOutputColor); +} + +/////////////////////////////////////////////////////////////////////////////// +std::unique_ptr ColorTableEffect::Make(GrRecordingContext* context, + const SkBitmap& bitmap) { + SkASSERT(kPremul_SkAlphaType == bitmap.alphaType()); + SkASSERT(bitmap.isImmutable()); + + if (kUnknown_GrPixelConfig == SkColorType2GrPixelConfig(bitmap.colorType())) { + return nullptr; + } + + sk_sp srcImage = SkImage::MakeFromBitmap(bitmap); + if (!srcImage) { + return nullptr; + } + + sk_sp proxy = GrMakeCachedImageProxy(context->priv().proxyProvider(), + std::move(srcImage)); + if (!proxy) { + return nullptr; + } + + return std::unique_ptr(new ColorTableEffect(std::move(proxy))); +} + +void ColorTableEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLColorTableEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* ColorTableEffect::onCreateGLSLInstance() const { + return new GLColorTableEffect; +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ColorTableEffect); + +#if GR_TEST_UTILS + +#include "include/gpu/GrContext.h" + +std::unique_ptr ColorTableEffect::TestCreate(GrProcessorTestData* d) { + int flags = 0; + uint8_t luts[256][4]; + do { + for (int i = 0; i < 4; ++i) { + flags |= d->fRandom->nextBool() ? (1 << i): 0; + } + } while (!flags); + for (int i = 0; i < 4; ++i) { + if (flags & (1 << i)) { + for (int j = 0; j < 256; ++j) { + luts[j][i] = SkToU8(d->fRandom->nextBits(8)); + } + } + } + auto filter(SkTableColorFilter::MakeARGB( + (flags & (1 << 0)) ? luts[0] : nullptr, + (flags & (1 << 1)) ? luts[1] : nullptr, + (flags & (1 << 2)) ? luts[2] : nullptr, + (flags & (1 << 3)) ? luts[3] : nullptr + )); + sk_sp colorSpace = GrTest::TestColorSpace(d->fRandom); + auto fp = filter->asFragmentProcessor( + d->context(), + GrColorInfo(GrColorType::kRGBA_8888, kUnknown_SkAlphaType, std::move(colorSpace))); + SkASSERT(fp); + return fp; +} +#endif + +std::unique_ptr SkTable_ColorFilter::asFragmentProcessor( + GrRecordingContext* context, const GrColorInfo&) const { + SkBitmap bitmap; + this->getTableAsBitmap(&bitmap); + + return ColorTableEffect::Make(context, bitmap); +} + +#endif // SK_SUPPORT_GPU + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkTableColorFilter::Make(const uint8_t table[256]) { + return sk_make_sp(table, table, table, table); +} + +sk_sp SkTableColorFilter::MakeARGB(const uint8_t tableA[256], + const uint8_t tableR[256], + const uint8_t tableG[256], + const uint8_t tableB[256]) { + return sk_make_sp(tableA, tableR, tableG, tableB); +} + +void SkTableColorFilter::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkTable_ColorFilter); } diff --git a/gfx/skia/skia/src/effects/SkTableMaskFilter.cpp b/gfx/skia/skia/src/effects/SkTableMaskFilter.cpp new file mode 100644 index 0000000000..4fe0fb1319 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkTableMaskFilter.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/effects/SkTableMaskFilter.h" +#include "include/private/SkFixed.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +class SkTableMaskFilterImpl : public SkMaskFilterBase { +public: + explicit SkTableMaskFilterImpl(const uint8_t table[256]); + + SkMask::Format getFormat() const override; + bool filterMask(SkMask*, const SkMask&, const SkMatrix&, SkIPoint*) const override; + +protected: + ~SkTableMaskFilterImpl() override; + + void flatten(SkWriteBuffer&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkTableMaskFilterImpl) + + SkTableMaskFilterImpl(); + + uint8_t fTable[256]; + + typedef SkMaskFilter INHERITED; +}; + +SkTableMaskFilterImpl::SkTableMaskFilterImpl() { + for (int i = 0; i < 256; i++) { + fTable[i] = i; + } +} + +SkTableMaskFilterImpl::SkTableMaskFilterImpl(const uint8_t table[256]) { + memcpy(fTable, table, sizeof(fTable)); +} + +SkTableMaskFilterImpl::~SkTableMaskFilterImpl() {} + +bool SkTableMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, + const SkMatrix&, SkIPoint* margin) const { + if (src.fFormat != SkMask::kA8_Format) { + return false; + } + + dst->fBounds = src.fBounds; + dst->fRowBytes = SkAlign4(dst->fBounds.width()); + dst->fFormat = SkMask::kA8_Format; + dst->fImage = nullptr; + + if (src.fImage) { + dst->fImage = SkMask::AllocImage(dst->computeImageSize()); + + const uint8_t* srcP = src.fImage; + uint8_t* dstP = dst->fImage; + const uint8_t* table = fTable; + int dstWidth = dst->fBounds.width(); + int extraZeros = dst->fRowBytes - dstWidth; + + for (int y = dst->fBounds.height() - 1; y >= 0; --y) { + for (int x = dstWidth - 1; x >= 0; --x) { + dstP[x] = table[srcP[x]]; + } + srcP += src.fRowBytes; + // we can't just inc dstP by rowbytes, because if it has any + // padding between its width and its rowbytes, we need to zero those + // so that the bitters can read those safely if that is faster for + // them + dstP += dstWidth; + for (int i = extraZeros - 1; i >= 0; --i) { + *dstP++ = 0; + } + } + } + + if (margin) { + margin->set(0, 0); + } + return true; +} + +SkMask::Format SkTableMaskFilterImpl::getFormat() const { + return SkMask::kA8_Format; +} + +void SkTableMaskFilterImpl::flatten(SkWriteBuffer& wb) const { + wb.writeByteArray(fTable, 256); +} + +sk_sp SkTableMaskFilterImpl::CreateProc(SkReadBuffer& buffer) { + uint8_t table[256]; + if (!buffer.readByteArray(table, 256)) { + return nullptr; + } + return sk_sp(SkTableMaskFilter::Create(table)); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkMaskFilter* SkTableMaskFilter::Create(const uint8_t table[256]) { + return new SkTableMaskFilterImpl(table); +} + +SkMaskFilter* SkTableMaskFilter::CreateGamma(SkScalar gamma) { + uint8_t table[256]; + MakeGammaTable(table, gamma); + return new SkTableMaskFilterImpl(table); +} + +SkMaskFilter* SkTableMaskFilter::CreateClip(uint8_t min, uint8_t max) { + uint8_t table[256]; + MakeClipTable(table, min, max); + return new SkTableMaskFilterImpl(table); +} + +void SkTableMaskFilter::MakeGammaTable(uint8_t table[256], SkScalar gamma) { + const float dx = 1 / 255.0f; + const float g = SkScalarToFloat(gamma); + + float x = 0; + for (int i = 0; i < 256; i++) { + // float ee = powf(x, g) * 255; + table[i] = SkTPin(sk_float_round2int(powf(x, g) * 255), 0, 255); + x += dx; + } +} + +void SkTableMaskFilter::MakeClipTable(uint8_t table[256], uint8_t min, + uint8_t max) { + if (0 == max) { + max = 1; + } + if (min >= max) { + min = max - 1; + } + SkASSERT(min < max); + + SkFixed scale = (1 << 16) * 255 / (max - min); + memset(table, 0, min + 1); + for (int i = min + 1; i < max; i++) { + int value = SkFixedRoundToInt(scale * (i - min)); + SkASSERT(value <= 255); + table[i] = value; + } + memset(table + max, 255, 256 - max); + +#if 0 + int j; + for (j = 0; j < 256; j++) { + if (table[j]) { + break; + } + } + SkDebugf("%d %d start [%d]", min, max, j); + for (; j < 256; j++) { + SkDebugf(" %d", table[j]); + } + SkDebugf("\n\n"); +#endif +} diff --git a/gfx/skia/skia/src/effects/SkTrimPE.h b/gfx/skia/skia/src/effects/SkTrimPE.h new file mode 100644 index 0000000000..b457a38fe8 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkTrimPE.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTrimImpl_DEFINED +#define SkTrimImpl_DEFINED + +#include "include/core/SkPathEffect.h" + +#include "include/effects/SkTrimPathEffect.h" + +class SkTrimPE : public SkPathEffect { +public: + SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode); + +protected: + void flatten(SkWriteBuffer&) const override; + bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; + +private: + SK_FLATTENABLE_HOOKS(SkTrimPE) + + const SkScalar fStartT, + fStopT; + const SkTrimPathEffect::Mode fMode; + + typedef SkPathEffect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/SkTrimPathEffect.cpp b/gfx/skia/skia/src/effects/SkTrimPathEffect.cpp new file mode 100644 index 0000000000..d0a8806d40 --- /dev/null +++ b/gfx/skia/skia/src/effects/SkTrimPathEffect.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPathMeasure.h" +#include "include/effects/SkTrimPathEffect.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/effects/SkTrimPE.h" + +namespace { + +class Segmentator : public SkNoncopyable { +public: + Segmentator(const SkPath& src, SkPath* dst) + : fMeasure(src, false) + , fDst(dst) {} + + void add(SkScalar start, SkScalar stop) { + SkASSERT(start < stop); + + // TODO: we appear to skip zero-length contours. + do { + const auto nextOffset = fCurrentSegmentOffset + fMeasure.getLength(); + + if (start < nextOffset) { + fMeasure.getSegment(start - fCurrentSegmentOffset, + stop - fCurrentSegmentOffset, + fDst, true); + + if (stop < nextOffset) + break; + } + + fCurrentSegmentOffset = nextOffset; + } while (fMeasure.nextContour()); + } + +private: + SkPathMeasure fMeasure; + SkPath* fDst; + + SkScalar fCurrentSegmentOffset = 0; + + using INHERITED = SkNoncopyable; +}; + +} // namespace + +SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode mode) + : fStartT(startT), fStopT(stopT), fMode(mode) {} + +bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect) const { + if (fStartT >= fStopT) { + SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal); + return true; + } + + // First pass: compute the total len. + SkScalar len = 0; + SkPathMeasure meas(src, false); + do { + len += meas.getLength(); + } while (meas.nextContour()); + + const auto arcStart = len * fStartT, + arcStop = len * fStopT; + + // Second pass: actually add segments. + Segmentator segmentator(src, dst); + if (fMode == SkTrimPathEffect::Mode::kNormal) { + if (arcStart < arcStop) segmentator.add(arcStart, arcStop); + } else { + if (0 < arcStart) segmentator.add(0, arcStart); + if (arcStop < len) segmentator.add(arcStop, len); + } + + return true; +} + +void SkTrimPE::flatten(SkWriteBuffer& buffer) const { + buffer.writeScalar(fStartT); + buffer.writeScalar(fStopT); + buffer.writeUInt(static_cast(fMode)); +} + +sk_sp SkTrimPE::CreateProc(SkReadBuffer& buffer) { + const auto start = buffer.readScalar(), + stop = buffer.readScalar(); + const auto mode = buffer.readUInt(); + + return SkTrimPathEffect::Make(start, stop, + (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) { + if (!SkScalarsAreFinite(startT, stopT)) { + return nullptr; + } + + if (startT <= 0 && stopT >= 1 && mode == Mode::kNormal) { + return nullptr; + } + + startT = SkTPin(startT, 0.f, 1.f); + stopT = SkTPin(stopT, 0.f, 1.f); + + if (startT >= stopT && mode == Mode::kInverted) { + return nullptr; + } + + return sk_sp(new SkTrimPE(startT, stopT, mode)); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkAlphaThresholdFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkAlphaThresholdFilter.cpp new file mode 100644 index 0000000000..a059b6588a --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkAlphaThresholdFilter.cpp @@ -0,0 +1,266 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkAlphaThresholdFilter.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkRegion.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#endif + +namespace { + +class SkAlphaThresholdFilterImpl final : public SkImageFilter_Base { +public: + SkAlphaThresholdFilterImpl(const SkRegion& region, SkScalar innerThreshold, + SkScalar outerThreshold, sk_sp input, + const CropRect* cropRect = nullptr) + : INHERITED(&input, 1, cropRect) + , fRegion(region) + , fInnerThreshold(innerThreshold) + , fOuterThreshold(outerThreshold) {} + +protected: + void flatten(SkWriteBuffer&) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +#if SK_SUPPORT_GPU + sk_sp createMaskTexture(GrRecordingContext*, + const SkMatrix&, + const SkIRect& bounds) const; +#endif + +private: + friend void SkAlphaThresholdFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkAlphaThresholdFilterImpl) + + SkRegion fRegion; + SkScalar fInnerThreshold; + SkScalar fOuterThreshold; + + typedef SkImageFilter_Base INHERITED; +}; + +}; // end namespace + +sk_sp SkAlphaThresholdFilter::Make(const SkRegion& region, SkScalar innerThreshold, + SkScalar outerThreshold, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + innerThreshold = SkScalarPin(innerThreshold, 0.f, 1.f); + outerThreshold = SkScalarPin(outerThreshold, 0.f, 1.f); + if (!SkScalarIsFinite(innerThreshold) || !SkScalarIsFinite(outerThreshold)) { + return nullptr; + } + return sk_sp(new SkAlphaThresholdFilterImpl( + region, innerThreshold, outerThreshold, std::move(input), cropRect)); + } + +void SkAlphaThresholdFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkAlphaThresholdFilterImpl); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkAlphaThresholdFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkScalar inner = buffer.readScalar(); + SkScalar outer = buffer.readScalar(); + SkRegion rgn; + buffer.readRegion(&rgn); + return SkAlphaThresholdFilter::Make(rgn, inner, outer, common.getInput(0), + &common.cropRect()); +} + +void SkAlphaThresholdFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeScalar(fInnerThreshold); + buffer.writeScalar(fOuterThreshold); + buffer.writeRegion(fRegion); +} + +#if SK_SUPPORT_GPU +sk_sp SkAlphaThresholdFilterImpl::createMaskTexture(GrRecordingContext* context, + const SkMatrix& inMatrix, + const SkIRect& bounds) const { + auto rtContext = context->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kApprox, bounds.width(), bounds.height(), GrColorType::kAlpha_8, nullptr); + if (!rtContext) { + return nullptr; + } + + SkRegion::Iterator iter(fRegion); + rtContext->clear(nullptr, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); + + GrFixedClip clip(SkIRect::MakeWH(bounds.width(), bounds.height())); + while (!iter.done()) { + GrPaint paint; + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + SkRect rect = SkRect::Make(iter.rect()); + + rtContext->drawRect(clip, std::move(paint), GrAA::kNo, inMatrix, rect); + + iter.next(); + } + + return rtContext->asTextureProxyRef(); +} +#endif + +sk_sp SkAlphaThresholdFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + + SkIRect bounds; + if (!this->applyCropRect(ctx, inputBounds, &bounds)) { + return nullptr; + } + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + auto context = ctx.getContext(); + + sk_sp inputProxy(input->asTextureProxyRef(context)); + SkASSERT(inputProxy); + const bool isProtected = inputProxy->isProtected(); + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + + bounds.offset(-inputOffset); + + SkMatrix matrix(ctx.ctm()); + matrix.postTranslate(SkIntToScalar(-offset->fX), SkIntToScalar(-offset->fY)); + + sk_sp maskProxy(this->createMaskTexture(context, matrix, bounds)); + if (!maskProxy) { + return nullptr; + } + + GrColorType srcColorType = SkColorTypeToGrColorType(input->colorType()); + auto textureFP = GrSimpleTextureEffect::Make(std::move(inputProxy), srcColorType, + SkMatrix::MakeTrans(input->subset().x(), + input->subset().y())); + textureFP = GrColorSpaceXformEffect::Make(std::move(textureFP), input->getColorSpace(), + input->alphaType(), ctx.colorSpace()); + if (!textureFP) { + return nullptr; + } + + auto thresholdFP = GrAlphaThresholdFragmentProcessor::Make(std::move(maskProxy), + fInnerThreshold, + fOuterThreshold, + bounds); + if (!thresholdFP) { + return nullptr; + } + + std::unique_ptr fpSeries[] = { std::move(textureFP), + std::move(thresholdFP) }; + auto fp = GrFragmentProcessor::RunInSeries(fpSeries, 2); + + return DrawWithFP(context, std::move(fp), bounds, ctx.colorType(), ctx.colorSpace(), + isProtected ? GrProtected::kYes : GrProtected::kNo); + } +#endif + + SkBitmap inputBM; + + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) { + return nullptr; + } + + + SkMatrix localInverse; + if (!ctx.ctm().invert(&localInverse)) { + return nullptr; + } + + SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), + kPremul_SkAlphaType); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + U8CPU innerThreshold = (U8CPU)(fInnerThreshold * 0xFF); + U8CPU outerThreshold = (U8CPU)(fOuterThreshold * 0xFF); + SkColor* dptr = dst.getAddr32(0, 0); + int dstWidth = dst.width(), dstHeight = dst.height(); + SkIPoint srcOffset = { bounds.fLeft - inputOffset.fX, bounds.fTop - inputOffset.fY }; + for (int y = 0; y < dstHeight; ++y) { + const SkColor* sptr = inputBM.getAddr32(srcOffset.fX, srcOffset.fY+y); + + for (int x = 0; x < dstWidth; ++x) { + const SkColor& source = sptr[x]; + SkColor outputColor(source); + SkPoint position; + localInverse.mapXY((SkScalar)x + bounds.fLeft, (SkScalar)y + bounds.fTop, &position); + if (fRegion.contains((int32_t)position.x(), (int32_t)position.y())) { + if (SkColorGetA(source) < innerThreshold) { + U8CPU alpha = SkColorGetA(source); + if (alpha == 0) { + alpha = 1; + } + float scale = (float)innerThreshold / alpha; + outputColor = SkColorSetARGB(innerThreshold, + (U8CPU)(SkColorGetR(source) * scale), + (U8CPU)(SkColorGetG(source) * scale), + (U8CPU)(SkColorGetB(source) * scale)); + } + } else { + if (SkColorGetA(source) > outerThreshold) { + float scale = (float)outerThreshold / SkColorGetA(source); + outputColor = SkColorSetARGB(outerThreshold, + (U8CPU)(SkColorGetR(source) * scale), + (U8CPU)(SkColorGetG(source) * scale), + (U8CPU)(SkColorGetB(source) * scale)); + } + } + dptr[y * dstWidth + x] = outputColor; + } + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), + dst); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkArithmeticImageFilter.cpp new file mode 100644 index 0000000000..2b1aaaf6a5 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkArithmeticImageFilter.cpp @@ -0,0 +1,479 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkArithmeticImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/effects/SkXfermodeImageFilter.h" +#include "include/private/SkNx.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrSkSLFP.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +GR_FP_SRC_STRING SKSL_ARITHMETIC_SRC = R"( +uniform float4 k; +in bool enforcePMColor; +in fragmentProcessor child; + +void main(inout half4 color) { + half4 dst = sample(child); + color = saturate(half(k.x) * color * dst + half(k.y) * color + half(k.z) * dst + half(k.w)); + @if (enforcePMColor) { + color.rgb = min(color.rgb, color.a); + } +} +)"; +#endif + +namespace { + +class ArithmeticImageFilterImpl final : public SkImageFilter_Base { +public: + ArithmeticImageFilterImpl(float k1, float k2, float k3, float k4, bool enforcePMColor, + sk_sp inputs[2], const CropRect* cropRect) + : INHERITED(inputs, 2, cropRect), fK{k1, k2, k3, k4}, fEnforcePMColor(enforcePMColor) {} + +protected: + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + + SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +#if SK_SUPPORT_GPU + sk_sp filterImageGPU(const Context& ctx, + sk_sp background, + const SkIPoint& backgroundOffset, + sk_sp foreground, + const SkIPoint& foregroundOffset, + const SkIRect& bounds) const; +#endif + + void flatten(SkWriteBuffer& buffer) const override; + + void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const; + +private: + friend void SkArithmeticImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(ArithmeticImageFilterImpl) + + bool affectsTransparentBlack() const override { return !SkScalarNearlyZero(fK[3]); } + + const float fK[4]; + const bool fEnforcePMColor; + + typedef SkImageFilter_Base INHERITED; +}; + +}; // end namespace + +sk_sp SkArithmeticImageFilter::Make(float k1, float k2, float k3, float k4, + bool enforcePMColor, + sk_sp background, + sk_sp foreground, + const SkImageFilter::CropRect* crop) { + if (!SkScalarIsFinite(k1) || !SkScalarIsFinite(k2) || !SkScalarIsFinite(k3) || + !SkScalarIsFinite(k4)) { + return nullptr; + } + + // are we nearly some other "std" mode? + int mode = -1; // illegal mode + if (SkScalarNearlyZero(k1) && SkScalarNearlyEqual(k2, SK_Scalar1) && SkScalarNearlyZero(k3) && + SkScalarNearlyZero(k4)) { + mode = (int)SkBlendMode::kSrc; + } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) && + SkScalarNearlyEqual(k3, SK_Scalar1) && SkScalarNearlyZero(k4)) { + mode = (int)SkBlendMode::kDst; + } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) && SkScalarNearlyZero(k3) && + SkScalarNearlyZero(k4)) { + mode = (int)SkBlendMode::kClear; + } + if (mode >= 0) { + return SkXfermodeImageFilter::Make((SkBlendMode)mode, std::move(background), + std::move(foreground), crop); + } + + sk_sp inputs[2] = {std::move(background), std::move(foreground)}; + return sk_sp( + new ArithmeticImageFilterImpl(k1, k2, k3, k4, enforcePMColor, inputs, crop)); +} + +void SkArithmeticImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(ArithmeticImageFilterImpl); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp ArithmeticImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2); + float k[4]; + for (int i = 0; i < 4; ++i) { + k[i] = buffer.readScalar(); + } + const bool enforcePMColor = buffer.readBool(); + if (!buffer.isValid()) { + return nullptr; + } + return SkArithmeticImageFilter::Make(k[0], k[1], k[2], k[3], enforcePMColor, common.getInput(0), + common.getInput(1), &common.cropRect()); +} + +void ArithmeticImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + for (int i = 0; i < 4; ++i) { + buffer.writeScalar(fK[i]); + } + buffer.writeBool(fEnforcePMColor); +} + +static Sk4f pin(float min, const Sk4f& val, float max) { + return Sk4f::Max(min, Sk4f::Min(val, max)); +} + +template +void arith_span(const float k[], SkPMColor dst[], const SkPMColor src[], int count) { + const Sk4f k1 = k[0] * (1/255.0f), + k2 = k[1], + k3 = k[2], + k4 = k[3] * 255.0f + 0.5f; + + for (int i = 0; i < count; i++) { + Sk4f s = SkNx_cast(Sk4b::Load(src+i)), + d = SkNx_cast(Sk4b::Load(dst+i)), + r = pin(0, k1*s*d + k2*s + k3*d + k4, 255); + if (EnforcePMColor) { + Sk4f a = SkNx_shuffle<3,3,3,3>(r); + r = Sk4f::Min(a, r); + } + SkNx_cast(r).store(dst+i); + } +} + +// apply mode to src==transparent (0) +template void arith_transparent(const float k[], SkPMColor dst[], int count) { + const Sk4f k3 = k[2], + k4 = k[3] * 255.0f + 0.5f; + + for (int i = 0; i < count; i++) { + Sk4f d = SkNx_cast(Sk4b::Load(dst+i)), + r = pin(0, k3*d + k4, 255); + if (EnforcePMColor) { + Sk4f a = SkNx_shuffle<3,3,3,3>(r); + r = Sk4f::Min(a, r); + } + SkNx_cast(r).store(dst+i); + } +} + +static bool intersect(SkPixmap* dst, SkPixmap* src, int srcDx, int srcDy) { + SkIRect dstR = SkIRect::MakeWH(dst->width(), dst->height()); + SkIRect srcR = SkIRect::MakeXYWH(srcDx, srcDy, src->width(), src->height()); + SkIRect sect; + if (!sect.intersect(dstR, srcR)) { + return false; + } + *dst = SkPixmap(dst->info().makeDimensions(sect.size()), + dst->addr(sect.fLeft, sect.fTop), + dst->rowBytes()); + *src = SkPixmap(src->info().makeDimensions(sect.size()), + src->addr(SkTMax(0, -srcDx), SkTMax(0, -srcDy)), + src->rowBytes()); + return true; +} + +sk_sp ArithmeticImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint backgroundOffset = SkIPoint::Make(0, 0); + sk_sp background(this->filterInput(0, ctx, &backgroundOffset)); + + SkIPoint foregroundOffset = SkIPoint::Make(0, 0); + sk_sp foreground(this->filterInput(1, ctx, &foregroundOffset)); + + SkIRect foregroundBounds = SkIRect::EmptyIRect(); + if (foreground) { + foregroundBounds = SkIRect::MakeXYWH(foregroundOffset.x(), foregroundOffset.y(), + foreground->width(), foreground->height()); + } + + SkIRect srcBounds = SkIRect::EmptyIRect(); + if (background) { + srcBounds = SkIRect::MakeXYWH(backgroundOffset.x(), backgroundOffset.y(), + background->width(), background->height()); + } + + srcBounds.join(foregroundBounds); + if (srcBounds.isEmpty()) { + return nullptr; + } + + SkIRect bounds; + if (!this->applyCropRect(ctx, srcBounds, &bounds)) { + return nullptr; + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + return this->filterImageGPU(ctx, background, backgroundOffset, foreground, + foregroundOffset, bounds); + } +#endif + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); // can't count on background to fully clear the background + canvas->translate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); + + if (background) { + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + background->draw(canvas, SkIntToScalar(backgroundOffset.fX), + SkIntToScalar(backgroundOffset.fY), &paint); + } + + this->drawForeground(canvas, foreground.get(), foregroundBounds); + + return surf->makeImageSnapshot(); +} + +SkIRect ArithmeticImageFilterImpl::onFilterBounds(const SkIRect& src, + const SkMatrix& ctm, + MapDirection dir, + const SkIRect* inputRect) const { + if (kReverse_MapDirection == dir) { + return INHERITED::onFilterBounds(src, ctm, dir, inputRect); + } + + SkASSERT(2 == this->countInputs()); + + // result(i1,i2) = k1*i1*i2 + k2*i1 + k3*i2 + k4 + // Note that background (getInput(0)) is i2, and foreground (getInput(1)) is i1. + auto i2 = this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, dir, nullptr) : src; + auto i1 = this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, dir, nullptr) : src; + + // Arithmetic with non-zero k4 may influence the complete filter primitive + // region. [k4 > 0 => result(0,0) = k4 => result(i1,i2) >= k4] + if (!SkScalarNearlyZero(fK[3])) { + i1.join(i2); + return i1; + } + + // If both K2 or K3 are non-zero, both i1 and i2 appear. + if (!SkScalarNearlyZero(fK[1]) && !SkScalarNearlyZero(fK[2])) { + i1.join(i2); + return i1; + } + + // If k2 is non-zero, output can be produced whenever i1 is non-transparent. + // [k3 = k4 = 0 => result(i1,i2) = k1*i1*i2 + k2*i1 = (k1*i2 + k2)*i1] + if (!SkScalarNearlyZero(fK[1])) { + return i1; + } + + // If k3 is non-zero, output can be produced whenever i2 is non-transparent. + // [k2 = k4 = 0 => result(i1,i2) = k1*i1*i2 + k3*i2 = (k1*i1 + k3)*i2] + if (!SkScalarNearlyZero(fK[2])) { + return i2; + } + + // If just k1 is non-zero, output will only be produce where both inputs + // are non-transparent. Use intersection. + // [k1 > 0 and k2 = k3 = k4 = 0 => result(i1,i2) = k1*i1*i2] + if (!SkScalarNearlyZero(fK[0])) { + if (!i1.intersect(i2)) { + return SkIRect::MakeEmpty(); + } + return i1; + } + + // [k1 = k2 = k3 = k4 = 0 => result(i1,i2) = 0] + return SkIRect::MakeEmpty(); +} + +#if SK_SUPPORT_GPU + +sk_sp ArithmeticImageFilterImpl::filterImageGPU( + const Context& ctx, + sk_sp background, + const SkIPoint& backgroundOffset, + sk_sp foreground, + const SkIPoint& foregroundOffset, + const SkIRect& bounds) const { + SkASSERT(ctx.gpuBacked()); + + auto context = ctx.getContext(); + + sk_sp backgroundProxy, foregroundProxy; + + GrProtected isProtected = GrProtected::kNo; + if (background) { + backgroundProxy = background->asTextureProxyRef(context); + isProtected = backgroundProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo; + } + + if (foreground) { + foregroundProxy = foreground->asTextureProxyRef(context); + isProtected = foregroundProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo; + } + + GrPaint paint; + std::unique_ptr bgFP; + + if (backgroundProxy) { + SkIRect bgSubset = background->subset(); + SkMatrix backgroundMatrix = SkMatrix::MakeTrans( + SkIntToScalar(bgSubset.left() - backgroundOffset.fX), + SkIntToScalar(bgSubset.top() - backgroundOffset.fY)); + GrColorType bgColorType = SkColorTypeToGrColorType(background->colorType()); + bgFP = GrTextureDomainEffect::Make( + std::move(backgroundProxy), bgColorType, backgroundMatrix, + GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode), + GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); + bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(), + background->alphaType(), + ctx.colorSpace()); + } else { + bgFP = GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kIgnore); + } + + if (foregroundProxy) { + SkIRect fgSubset = foreground->subset(); + SkMatrix foregroundMatrix = SkMatrix::MakeTrans( + SkIntToScalar(fgSubset.left() - foregroundOffset.fX), + SkIntToScalar(fgSubset.top() - foregroundOffset.fY)); + GrColorType fgColorType = SkColorTypeToGrColorType(foreground->colorType()); + auto foregroundFP = GrTextureDomainEffect::Make( + std::move(foregroundProxy), fgColorType, foregroundMatrix, + GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode), + GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); + foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP), + foreground->getColorSpace(), + foreground->alphaType(), + ctx.colorSpace()); + paint.addColorFragmentProcessor(std::move(foregroundFP)); + + static int arithmeticIndex = GrSkSLFP::NewIndex(); + ArithmeticFPInputs inputs; + static_assert(sizeof(inputs.k) == sizeof(fK), "struct size mismatch"); + memcpy(inputs.k, fK, sizeof(inputs.k)); + inputs.enforcePMColor = fEnforcePMColor; + std::unique_ptr xferFP = GrSkSLFP::Make(context, + arithmeticIndex, + "Arithmetic", + SKSL_ARITHMETIC_SRC, + &inputs, + sizeof(inputs)); + if (xferFP) { + ((GrSkSLFP&) *xferFP).addChild(std::move(bgFP)); + paint.addColorFragmentProcessor(std::move(xferFP)); + } + } else { + paint.addColorFragmentProcessor(std::move(bgFP)); + } + + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + auto renderTargetContext = + context->priv().makeDeferredRenderTargetContext(SkBackingFit::kApprox, + bounds.width(), + bounds.height(), + ctx.grColorType(), + ctx.refColorSpace(), + 1, + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + isProtected); + if (!renderTargetContext) { + return nullptr; + } + + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, + SkRect::Make(bounds)); + + return SkSpecialImage::MakeDeferredFromGpu(context, + SkIRect::MakeWH(bounds.width(), bounds.height()), + kNeedNewImageUniqueID_SpecialImage, + renderTargetContext->asTextureProxyRef(), + renderTargetContext->colorInfo().colorType(), + renderTargetContext->colorInfo().refColorSpace()); +} +#endif + +void ArithmeticImageFilterImpl::drawForeground(SkCanvas* canvas, SkSpecialImage* img, + const SkIRect& fgBounds) const { + SkPixmap dst; + if (!canvas->peekPixels(&dst)) { + return; + } + + const SkMatrix& ctm = canvas->getTotalMatrix(); + SkASSERT(ctm.getType() <= SkMatrix::kTranslate_Mask); + const int dx = SkScalarRoundToInt(ctm.getTranslateX()); + const int dy = SkScalarRoundToInt(ctm.getTranslateY()); + // be sure to perform this offset using SkIRect, since it saturates to avoid overflows + const SkIRect fgoffset = fgBounds.makeOffset(dx, dy); + + if (img) { + SkBitmap srcBM; + SkPixmap src; + if (!img->getROPixels(&srcBM)) { + return; + } + if (!srcBM.peekPixels(&src)) { + return; + } + + auto proc = fEnforcePMColor ? arith_span : arith_span; + SkPixmap tmpDst = dst; + if (intersect(&tmpDst, &src, fgoffset.fLeft, fgoffset.fTop)) { + for (int y = 0; y < tmpDst.height(); ++y) { + proc(fK, tmpDst.writable_addr32(0, y), src.addr32(0, y), tmpDst.width()); + } + } + } + + // Now apply the mode with transparent-color to the outside of the fg image + SkRegion outside(SkIRect::MakeWH(dst.width(), dst.height())); + outside.op(fgoffset, SkRegion::kDifference_Op); + auto proc = fEnforcePMColor ? arith_transparent : arith_transparent; + for (SkRegion::Iterator iter(outside); !iter.done(); iter.next()) { + const SkIRect r = iter.rect(); + for (int y = r.fTop; y < r.fBottom; ++y) { + proc(fK, dst.writable_addr32(r.fLeft, y), r.width()); + } + } +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkBlurImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkBlurImageFilter.cpp new file mode 100644 index 0000000000..2dde5e2be7 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkBlurImageFilter.cpp @@ -0,0 +1,706 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkBlurImageFilter.h" + +#include + +#include "include/core/SkBitmap.h" +#include "include/core/SkTileMode.h" +#include "include/private/SkColorData.h" +#include "include/private/SkNx.h" +#include "include/private/SkTFitsIn.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkGpuBlurUtils.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkOpts.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#endif + +namespace { + +class SkBlurImageFilterImpl final : public SkImageFilter_Base { +public: + SkBlurImageFilterImpl(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, + sk_sp input, const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fSigma{sigmaX, sigmaY} + , fTileMode(tileMode) {} + + SkRect computeFastBounds(const SkRect&) const override; + +protected: + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +private: + friend void SkBlurImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkBlurImageFilterImpl) + +#if SK_SUPPORT_GPU + sk_sp gpuFilter( + const Context& ctx, SkVector sigma, + const sk_sp &input, + SkIRect inputBounds, SkIRect dstBounds, SkIPoint inputOffset, SkIPoint* offset) const; +#endif + + SkSize fSigma; + SkTileMode fTileMode; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +static SkTileMode to_sktilemode(SkBlurImageFilter::TileMode tileMode) { + switch(tileMode) { + case SkBlurImageFilter::kClamp_TileMode: + return SkTileMode::kClamp; + case SkBlurImageFilter::kRepeat_TileMode: + return SkTileMode::kRepeat; + case SkBlurImageFilter::kClampToBlack_TileMode: + // Fall through + default: + return SkTileMode::kDecal; + } +} + +sk_sp SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY, + sk_sp input, + const SkImageFilter::CropRect* cropRect, + TileMode tileMode) { + return Make(sigmaX, sigmaY, to_sktilemode(tileMode), std::move(input), cropRect); +} + +sk_sp SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + if (sigmaX < SK_ScalarNearlyZero && sigmaY < SK_ScalarNearlyZero && !cropRect) { + return input; + } + return sk_sp( + new SkBlurImageFilterImpl(sigmaX, sigmaY, tileMode, input, cropRect)); +} + +void SkBlurImageFilter::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkBlurImageFilterImpl); } + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkScalar sigmaX = buffer.readScalar(); + SkScalar sigmaY = buffer.readScalar(); + SkTileMode tileMode; + if (buffer.isVersionLT(SkPicturePriv::kTileModeInBlurImageFilter_Version)) { + tileMode = SkTileMode::kDecal; + } else if (buffer.isVersionLT(SkPicturePriv::kCleanupImageFilterEnums_Version)) { + tileMode = to_sktilemode(buffer.read32LE(SkBlurImageFilter::kLast_TileMode)); + } else { + tileMode = buffer.read32LE(SkTileMode::kLastTileMode); + } + + static_assert(SkBlurImageFilter::kLast_TileMode == 2, "CreateProc"); + + return SkBlurImageFilter::Make( + sigmaX, sigmaY, tileMode, common.getInput(0), &common.cropRect()); +} + +void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeScalar(fSigma.fWidth); + buffer.writeScalar(fSigma.fHeight); + + // Fuzzer sanity checks + static_assert((int) SkTileMode::kLastTileMode == 3 && SkBlurImageFilter::kLast_TileMode == 2, + "SkBlurImageFilterImpl::flatten"); + SkASSERT(fTileMode <= SkTileMode::kLastTileMode); + buffer.writeInt(static_cast(fTileMode)); +} + +#if SK_SUPPORT_GPU +static GrTextureDomain::Mode to_texture_domain_mode(SkTileMode tileMode) { + switch (tileMode) { + case SkTileMode::kClamp: + return GrTextureDomain::kClamp_Mode; + case SkTileMode::kDecal: + return GrTextureDomain::kDecal_Mode; + case SkTileMode::kMirror: + // TODO (michaelludwig) - Support mirror mode, treat as repeat for now + case SkTileMode::kRepeat: + return GrTextureDomain::kRepeat_Mode; + default: + SK_ABORT("Unsupported tile mode."); + } +} +#endif + +// This is defined by the SVG spec: +// https://drafts.fxtf.org/filter-effects/#feGaussianBlurElement +static int calculate_window(double sigma) { + // NB 136 is the largest sigma that will not cause a buffer full of 255 mask values to overflow + // using the Gauss filter. It also limits the size of buffers used hold intermediate values. + // Explanation of maximums: + // sum0 = window * 255 + // sum1 = window * sum0 -> window * window * 255 + // sum2 = window * sum1 -> window * window * window * 255 -> window^3 * 255 + // + // The value window^3 * 255 must fit in a uint32_t. So, + // window^3 < 2^32. window = 255. + // + // window = floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5) + // For window <= 255, the largest value for sigma is 136. + sigma = SkTPin(sigma, 0.0, 136.0); + auto possibleWindow = static_cast(floor(sigma * 3 * sqrt(2 * SK_DoublePI) / 4 + 0.5)); + return std::max(1, possibleWindow); +} + +// Calculating the border is tricky. The border is the distance in pixels between the first dst +// pixel and the first src pixel (or the last src pixel and the last dst pixel). +// I will go through the odd case which is simpler, and then through the even case. Given a +// stack of filters seven wide for the odd case of three passes. +// +// S +// aaaAaaa +// bbbBbbb +// cccCccc +// D +// +// The furthest changed pixel is when the filters are in the following configuration. +// +// S +// aaaAaaa +// bbbBbbb +// cccCccc +// D +// +// The A pixel is calculated using the value S, the B uses A, and the C uses B, and +// finally D is C. So, with a window size of seven the border is nine. In the odd case, the +// border is 3*((window - 1)/2). +// +// For even cases the filter stack is more complicated. The spec specifies two passes +// of even filters and a final pass of odd filters. A stack for a width of six looks like +// this. +// +// S +// aaaAaa +// bbBbbb +// cccCccc +// D +// +// The furthest pixel looks like this. +// +// S +// aaaAaa +// bbBbbb +// cccCccc +// D +// +// For a window of six, the border value is eight. In the even case the border is 3 * +// (window/2) - 1. +static int calculate_border(int window) { + return (window & 1) == 1 ? 3 * ((window - 1) / 2) : 3 * (window / 2) - 1; +} + +static int calculate_buffer(int window) { + int bufferSize = window - 1; + return (window & 1) == 1 ? 3 * bufferSize : 3 * bufferSize + 1; +} + +// blur_one_direction implements the common three pass box filter approximation of Gaussian blur, +// but combines all three passes into a single pass. This approach is facilitated by three circular +// buffers the width of the window which track values for trailing edges of each of the three +// passes. This allows the algorithm to use more precision in the calculation because the values +// are not rounded each pass. And this implementation also avoids a trap that's easy to fall +// into resulting in blending in too many zeroes near the edge. +// +// In general, a window sum has the form: +// sum_n+1 = sum_n + leading_edge - trailing_edge. +// If instead we do the subtraction at the end of the previous iteration, we can just +// calculate the sums instead of having to do the subtractions too. +// +// In previous iteration: +// sum_n+1 = sum_n - trailing_edge. +// +// In this iteration: +// sum_n+1 = sum_n + leading_edge. +// +// Now we can stack all three sums and do them at once. Sum0 gets its leading edge from the +// actual data. Sum1's leading edge is just Sum0, and Sum2's leading edge is Sum1. So, doing the +// three passes at the same time has the form: +// +// sum0_n+1 = sum0_n + leading edge +// sum1_n+1 = sum1_n + sum0_n+1 +// sum2_n+1 = sum2_n + sum1_n+1 +// +// sum2_n+1 / window^3 is the new value of the destination pixel. +// +// Reduce the sums by the trailing edges which were stored in the circular buffers, +// for the next go around. This is the case for odd sized windows, even windows the the third +// circular buffer is one larger then the first two circular buffers. +// +// sum2_n+2 = sum2_n+1 - buffer2[i]; +// buffer2[i] = sum1; +// sum1_n+2 = sum1_n+1 - buffer1[i]; +// buffer1[i] = sum0; +// sum0_n+2 = sum0_n+1 - buffer0[i]; +// buffer0[i] = leading edge +// +// This is all encapsulated in the processValue function below. +// +using Pass0And1 = Sk4u[2]; +// The would be dLeft parameter is assumed to be 0. +static void blur_one_direction(Sk4u* buffer, int window, + int srcLeft, int srcRight, int dstRight, + const uint32_t* src, int srcXStride, int srcYStride, int srcH, + uint32_t* dst, int dstXStride, int dstYStride) { + + // The circular buffers are one less than the window. + auto pass0Count = window - 1, + pass1Count = window - 1, + pass2Count = (window & 1) == 1 ? window - 1 : window; + + Pass0And1* buffer01Start = (Pass0And1*)buffer; + Sk4u* buffer2Start = buffer + pass0Count + pass1Count; + Pass0And1* buffer01End = (Pass0And1*)buffer2Start; + Sk4u* buffer2End = buffer2Start + pass2Count; + + // If the window is odd then the divisor is just window ^ 3 otherwise, + // it is window * window * (window + 1) = window ^ 3 + window ^ 2; + auto window2 = window * window; + auto window3 = window2 * window; + auto divisor = (window & 1) == 1 ? window3 : window3 + window2; + + // NB the sums in the blur code use the following technique to avoid + // adding 1/2 to round the divide. + // + // Sum/d + 1/2 == (Sum + h) / d + // Sum + d(1/2) == Sum + h + // h == (1/2)d + // + // But the d/2 it self should be rounded. + // h == d/2 + 1/2 == (d + 1) / 2 + // + // weight = 1 / d * 2 ^ 32 + auto weight = static_cast(round(1.0 / divisor * (1ull << 32))); + auto half = static_cast((divisor + 1) / 2); + + auto border = calculate_border(window); + + // Calculate the start and end of the source pixels with respect to the destination start. + auto srcStart = srcLeft - border, + srcEnd = srcRight - border, + dstEnd = dstRight; + + for (auto y = 0; y < srcH; y++) { + auto buffer01Cursor = buffer01Start; + auto buffer2Cursor = buffer2Start; + + Sk4u sum0{0u}; + Sk4u sum1{0u}; + Sk4u sum2{half}; + + sk_bzero(buffer01Start, (buffer2End - (Sk4u *) (buffer01Start)) * sizeof(*buffer2Start)); + + // Given an expanded input pixel, move the window ahead using the leadingEdge value. + auto processValue = [&](const Sk4u& leadingEdge) -> Sk4u { + sum0 += leadingEdge; + sum1 += sum0; + sum2 += sum1; + + Sk4u value = sum2.mulHi(weight); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < buffer2End ? buffer2Cursor + 1 : buffer2Start; + + sum1 -= (*buffer01Cursor)[1]; + (*buffer01Cursor)[1] = sum0; + sum0 -= (*buffer01Cursor)[0]; + (*buffer01Cursor)[0] = leadingEdge; + buffer01Cursor = + (buffer01Cursor + 1) < buffer01End ? buffer01Cursor + 1 : buffer01Start; + + return value; + }; + + auto srcIdx = srcStart; + auto dstIdx = 0; + const uint32_t* srcCursor = src; + uint32_t* dstCursor = dst; + + // The destination pixels are not effected by the src pixels, + // change to zero as per the spec. + // https://drafts.fxtf.org/filter-effects/#FilterPrimitivesOverviewIntro + while (dstIdx < srcIdx) { + *dstCursor = 0; + dstCursor += dstXStride; + SK_PREFETCH(dstCursor); + dstIdx++; + } + + // The edge of the source is before the edge of the destination. Calculate the sums for + // the pixels before the start of the destination. + while (dstIdx > srcIdx) { + Sk4u leadingEdge = srcIdx < srcEnd ? SkNx_cast(Sk4b::Load(srcCursor)) : 0; + (void) processValue(leadingEdge); + srcCursor += srcXStride; + srcIdx++; + } + + // The dstIdx and srcIdx are in sync now; the code just uses the dstIdx for both now. + // Consume the source generating pixels to dst. + auto loopEnd = std::min(dstEnd, srcEnd); + while (dstIdx < loopEnd) { + Sk4u leadingEdge = SkNx_cast(Sk4b::Load(srcCursor)); + SkNx_cast(processValue(leadingEdge)).store(dstCursor); + srcCursor += srcXStride; + dstCursor += dstXStride; + SK_PREFETCH(dstCursor); + dstIdx++; + } + + // The leading edge is beyond the end of the source. Assume that the pixels + // are now 0x0000 until the end of the destination. + loopEnd = dstEnd; + while (dstIdx < loopEnd) { + SkNx_cast(processValue(0u)).store(dstCursor); + dstCursor += dstXStride; + SK_PREFETCH(dstCursor); + dstIdx++; + } + + src += srcYStride; + dst += dstYStride; + } +} + +static sk_sp copy_image_with_bounds( + const SkImageFilter_Base::Context& ctx, const sk_sp &input, + SkIRect srcBounds, SkIRect dstBounds) { + SkBitmap inputBM; + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + SkBitmap src; + inputBM.extractSubset(&src, srcBounds); + + // Make everything relative to the destination bounds. + srcBounds.offset(-dstBounds.x(), -dstBounds.y()); + dstBounds.offset(-dstBounds.x(), -dstBounds.y()); + + auto srcW = srcBounds.width(), + dstW = dstBounds.width(), + dstH = dstBounds.height(); + + SkImageInfo dstInfo = SkImageInfo::Make(dstW, dstH, inputBM.colorType(), inputBM.alphaType()); + + SkBitmap dst; + if (!dst.tryAllocPixels(dstInfo)) { + return nullptr; + } + + // There is no blurring to do, but we still need to copy the source while accounting for the + // dstBounds. Remember that the src was intersected with the dst. + int y = 0; + size_t dstWBytes = dstW * sizeof(uint32_t); + for (;y < srcBounds.top(); y++) { + sk_bzero(dst.getAddr32(0, y), dstWBytes); + } + + for (;y < srcBounds.bottom(); y++) { + int x = 0; + uint32_t* dstPtr = dst.getAddr32(0, y); + for (;x < srcBounds.left(); x++) { + *dstPtr++ = 0; + } + + memcpy(dstPtr, src.getAddr32(x - srcBounds.left(), y - srcBounds.top()), + srcW * sizeof(uint32_t)); + + dstPtr += srcW; + x += srcW; + + for (;x < dstBounds.right(); x++) { + *dstPtr++ = 0; + } + } + + for (;y < dstBounds.bottom(); y++) { + sk_bzero(dst.getAddr32(0, y), dstWBytes); + } + + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), + dstBounds.height()), + dst, ctx.surfaceProps()); +} + +// TODO: Implement CPU backend for different fTileMode. +static sk_sp cpu_blur( + const SkImageFilter_Base::Context& ctx, + SkVector sigma, const sk_sp &input, + SkIRect srcBounds, SkIRect dstBounds) { + auto windowW = calculate_window(sigma.x()), + windowH = calculate_window(sigma.y()); + + if (windowW <= 1 && windowH <= 1) { + return copy_image_with_bounds(ctx, input, srcBounds, dstBounds); + } + + SkBitmap inputBM; + + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + SkBitmap src; + inputBM.extractSubset(&src, srcBounds); + + // Make everything relative to the destination bounds. + srcBounds.offset(-dstBounds.x(), -dstBounds.y()); + dstBounds.offset(-dstBounds.x(), -dstBounds.y()); + + auto srcW = srcBounds.width(), + srcH = srcBounds.height(), + dstW = dstBounds.width(), + dstH = dstBounds.height(); + + SkImageInfo dstInfo = inputBM.info().makeWH(dstW, dstH); + + SkBitmap dst; + if (!dst.tryAllocPixels(dstInfo)) { + return nullptr; + } + + auto bufferSizeW = calculate_buffer(windowW), + bufferSizeH = calculate_buffer(windowH); + + // The amount 1024 is enough for buffers up to 10 sigma. The tmp bitmap will be + // allocated on the heap. + SkSTArenaAlloc<1024> alloc; + Sk4u* buffer = alloc.makeArrayDefault(std::max(bufferSizeW, bufferSizeH)); + + // Basic Plan: The three cases to handle + // * Horizontal and Vertical - blur horizontally while copying values from the source to + // the destination. Then, do an in-place vertical blur. + // * Horizontal only - blur horizontally copying values from the source to the destination. + // * Vertical only - blur vertically copying values from the source to the destination. + + // Default to vertical only blur case. If a horizontal blur is needed, then these values + // will be adjusted while doing the horizontal blur. + auto intermediateSrc = static_cast(src.getPixels()); + auto intermediateRowBytesAsPixels = src.rowBytesAsPixels(); + auto intermediateWidth = srcW; + + // Because the border is calculated before the fork of the GPU/CPU path. The border is + // the maximum of the two rendering methods. In the case where sigma is zero, then the + // src and dst left values are the same. If sigma is small resulting in a window size of + // 1, then border calculations add some pixels which will always be zero. Inset the + // destination by those zero pixels. This case is very rare. + auto intermediateDst = dst.getAddr32(srcBounds.left(), 0); + + // The following code is executed very rarely, I have never seen it in a real web + // page. If sigma is small but not zero then shared GPU/CPU border calculation + // code adds extra pixels for the border. Just clear everything to clear those pixels. + // This solution is overkill, but very simple. + if (windowW == 1 || windowH == 1) { + dst.eraseColor(0); + } + + if (windowW > 1) { + // Make int64 to avoid overflow in multiplication below. + int64_t shift = srcBounds.top() - dstBounds.top(); + + // For the horizontal blur, starts part way down in anticipation of the vertical blur. + // For a vertical sigma of zero shift should be zero. But, for small sigma, + // shift may be > 0 but the vertical window could be 1. + intermediateSrc = static_cast(dst.getPixels()) + + (shift > 0 ? shift * dst.rowBytesAsPixels() : 0); + intermediateRowBytesAsPixels = dst.rowBytesAsPixels(); + intermediateWidth = dstW; + intermediateDst = static_cast(dst.getPixels()); + + blur_one_direction( + buffer, windowW, + srcBounds.left(), srcBounds.right(), dstBounds.right(), + static_cast(src.getPixels()), 1, src.rowBytesAsPixels(), srcH, + intermediateSrc, 1, intermediateRowBytesAsPixels); + } + + if (windowH > 1) { + blur_one_direction( + buffer, windowH, + srcBounds.top(), srcBounds.bottom(), dstBounds.bottom(), + intermediateSrc, intermediateRowBytesAsPixels, 1, intermediateWidth, + intermediateDst, dst.rowBytesAsPixels(), 1); + } + + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), + dstBounds.height()), + dst, ctx.surfaceProps()); +} + +// This rather arbitrary-looking value results in a maximum box blur kernel size +// of 1000 pixels on the raster path, which matches the WebKit and Firefox +// implementations. Since the GPU path does not compute a box blur, putting +// the limit on sigma ensures consistent behaviour between the GPU and +// raster paths. +#define MAX_SIGMA SkIntToScalar(532) + +static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) { + SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height()); + ctm.mapVectors(&sigma, 1); + sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA); + sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA); + return sigma; +} + +sk_sp SkBlurImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, + input->width(), input->height()); + + // Calculate the destination bounds. + SkIRect dstBounds; + if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) { + return nullptr; + } + if (!inputBounds.intersect(dstBounds)) { + return nullptr; + } + + // Save the offset in preparation to make all rectangles relative to the inputOffset. + SkIPoint resultOffset = SkIPoint::Make(dstBounds.fLeft, dstBounds.fTop); + + // Make all bounds relative to the inputOffset. + inputBounds.offset(-inputOffset); + dstBounds.offset(-inputOffset); + + SkVector sigma = map_sigma(fSigma, ctx.ctm()); + if (sigma.x() < 0 || sigma.y() < 0) { + return nullptr; + } + + sk_sp result; +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + // Ensure the input is in the destination's gamut. This saves us from having to do the + // xform during the filter itself. + input = ImageToColorSpace(input.get(), ctx.colorType(), ctx.colorSpace()); + result = this->gpuFilter(ctx, sigma, input, inputBounds, dstBounds, inputOffset, + &resultOffset); + } else +#endif + { + // NB 135 is the largest sigma that will not cause a buffer full of 255 mask values to overflow + // using the Gauss filter. It also limits the size of buffers used hold intermediate values. The + // additional + 1 added to window represents adding one more leading element before subtracting the + // trailing element. + // Explanation of maximums: + // sum0 = (window + 1) * 255 + // sum1 = (window + 1) * sum0 -> (window + 1) * (window + 1) * 255 + // sum2 = (window + 1) * sum1 -> (window + 1) * (window + 1) * (window + 1) * 255 -> window^3 * 255 + // + // The value (window + 1)^3 * 255 must fit in a uint32_t. So, + // (window + 1)^3 * 255 < 2^32. window = 255. + // + // window = floor(sigma * 3 * sqrt(2 * kPi) / 4) + // For window <= 255, the largest value for sigma is 135. + sigma.fX = SkTPin(sigma.fX, 0.0f, 135.0f); + sigma.fY = SkTPin(sigma.fY, 0.0f, 135.0f); + + result = cpu_blur(ctx, sigma, input, inputBounds, dstBounds); + } + + // Return the resultOffset if the blur succeeded. + if (result != nullptr) { + *offset = resultOffset; + } + return result; +} + +#if SK_SUPPORT_GPU +sk_sp SkBlurImageFilterImpl::gpuFilter( + const Context& ctx, SkVector sigma, const sk_sp &input, SkIRect inputBounds, + SkIRect dstBounds, SkIPoint inputOffset, SkIPoint* offset) const { + if (0 == sigma.x() && 0 == sigma.y()) { + offset->fX = inputBounds.x() + inputOffset.fX; + offset->fY = inputBounds.y() + inputOffset.fY; + return input->makeSubset(inputBounds); + } + + auto context = ctx.getContext(); + + sk_sp inputTexture(input->asTextureProxyRef(context)); + if (!inputTexture) { + return nullptr; + } + + // TODO (michaelludwig) - The color space choice is odd, should it just be ctx.refColorSpace()? + auto renderTargetContext = SkGpuBlurUtils::GaussianBlur( + context, + std::move(inputTexture), + SkColorTypeToGrColorType(input->colorType()), + input->alphaType(), + input->subset().topLeft(), + ctx.colorSpace() ? sk_ref_sp(input->getColorSpace()) : nullptr, + dstBounds, + inputBounds, + sigma.x(), + sigma.y(), + to_texture_domain_mode(fTileMode)); + if (!renderTargetContext) { + return nullptr; + } + + return SkSpecialImage::MakeDeferredFromGpu( + context, + SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), + kNeedNewImageUniqueID_SpecialImage, + renderTargetContext->asTextureProxyRef(), + renderTargetContext->colorInfo().colorType(), + sk_ref_sp(input->getColorSpace()), + ctx.surfaceProps()); +} +#endif + +SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const { + SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; + bounds.outset(fSigma.width() * 3, fSigma.height() * 3); + return bounds; +} + +SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const { + SkVector sigma = map_sigma(fSigma, ctm); + return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3)); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkColorFilterImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkColorFilterImageFilter.cpp new file mode 100644 index 0000000000..b9154431c6 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkColorFilterImageFilter.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkColorFilterImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkColorFilter.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkColorFilterImageFilterImpl final : public SkImageFilter_Base { +public: + SkColorFilterImageFilterImpl(sk_sp cf, sk_sp input, + const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fColorFilter(std::move(cf)) {} + +protected: + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + bool onIsColorFilterNode(SkColorFilter**) const override; + bool onCanHandleComplexCTM() const override { return true; } + bool affectsTransparentBlack() const override; + +private: + friend void SkColorFilterImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkColorFilterImageFilterImpl) + + sk_sp fColorFilter; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkColorFilterImageFilter::Make(sk_sp cf, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + if (!cf) { + return nullptr; + } + + SkColorFilter* inputCF; + if (input && input->isColorFilterNode(&inputCF)) { + // This is an optimization, as it collapses the hierarchy by just combining the two + // colorfilters into a single one, which the new imagefilter will wrap. + sk_sp newCF = cf->makeComposed(sk_sp(inputCF)); + if (newCF) { + return sk_sp(new SkColorFilterImageFilterImpl( + std::move(newCF), sk_ref_sp(input->getInput(0)), cropRect)); + } + } + + return sk_sp(new SkColorFilterImageFilterImpl( + std::move(cf), std::move(input), cropRect)); +} + +void SkColorFilterImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkColorFilterImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkColorFilterImageFilter", SkColorFilterImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkColorFilterImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + sk_sp cf(buffer.readColorFilter()); + return SkColorFilterImageFilter::Make(std::move(cf), common.getInput(0), &common.cropRect()); +} + +void SkColorFilterImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeFlattenable(fColorFilter.get()); +} + +sk_sp SkColorFilterImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + + SkIRect inputBounds; + if (fColorFilter->affectsTransparentBlack()) { + // If the color filter affects transparent black, the bounds are the entire clip. + inputBounds = ctx.clipBounds(); + } else if (!input) { + return nullptr; + } else { + inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + } + + SkIRect bounds; + if (!this->applyCropRect(ctx, inputBounds, &bounds)) { + return nullptr; + } + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + SkPaint paint; + + paint.setBlendMode(SkBlendMode::kSrc); + paint.setColorFilter(fColorFilter); + + // TODO: it may not be necessary to clear or drawPaint inside the input bounds + // (see skbug.com/5075) + if (fColorFilter->affectsTransparentBlack()) { + // The subsequent input->draw() call may not fill the entire canvas. For filters which + // affect transparent black, ensure that the filter is applied everywhere. + paint.setColor(SK_ColorTRANSPARENT); + canvas->drawPaint(paint); + paint.setColor(SK_ColorBLACK); + } else { + canvas->clear(0x0); + } + + if (input) { + input->draw(canvas, + SkIntToScalar(inputOffset.fX - bounds.fLeft), + SkIntToScalar(inputOffset.fY - bounds.fTop), + &paint); + } + + offset->fX = bounds.fLeft; + offset->fY = bounds.fTop; + return surf->makeImageSnapshot(); +} + +bool SkColorFilterImageFilterImpl::onIsColorFilterNode(SkColorFilter** filter) const { + SkASSERT(1 == this->countInputs()); + if (!this->cropRectIsSet()) { + if (filter) { + *filter = SkRef(fColorFilter.get()); + } + return true; + } + return false; +} + +bool SkColorFilterImageFilterImpl::affectsTransparentBlack() const { + return fColorFilter->affectsTransparentBlack(); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkComposeImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkComposeImageFilter.cpp new file mode 100644 index 0000000000..f83eefe9d0 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkComposeImageFilter.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkComposeImageFilter.h" + +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkComposeImageFilterImpl final : public SkImageFilter_Base { +public: + explicit SkComposeImageFilterImpl(sk_sp inputs[2]) + : INHERITED(inputs, 2, nullptr) { + SkASSERT(inputs[0].get()); + SkASSERT(inputs[1].get()); + } + + SkRect computeFastBounds(const SkRect& src) const override; + +protected: + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + bool onCanHandleComplexCTM() const override { return true; } + +private: + friend void SkComposeImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkComposeImageFilterImpl) + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkComposeImageFilter::Make(sk_sp outer, + sk_sp inner) { + if (!outer) { + return inner; + } + if (!inner) { + return outer; + } + sk_sp inputs[2] = { std::move(outer), std::move(inner) }; + return sk_sp(new SkComposeImageFilterImpl(inputs)); +} + +void SkComposeImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkComposeImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkComposeImageFilter", SkComposeImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkComposeImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2); + return SkComposeImageFilter::Make(common.getInput(0), common.getInput(1)); +} + +SkRect SkComposeImageFilterImpl::computeFastBounds(const SkRect& src) const { + const SkImageFilter* outer = this->getInput(0); + const SkImageFilter* inner = this->getInput(1); + + return outer->computeFastBounds(inner->computeFastBounds(src)); +} + +sk_sp SkComposeImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + // The bounds passed to the inner filter must be filtered by the outer + // filter, so that the inner filter produces the pixels that the outer + // filter requires as input. This matters if the outer filter moves pixels. + SkIRect innerClipBounds; + innerClipBounds = this->getInput(0)->filterBounds(ctx.clipBounds(), ctx.ctm(), + kReverse_MapDirection, &ctx.clipBounds()); + Context innerContext = ctx.withNewDesiredOutput(skif::LayerSpace(innerClipBounds)); + SkIPoint innerOffset = SkIPoint::Make(0, 0); + sk_sp inner(this->filterInput(1, innerContext, &innerOffset)); + if (!inner) { + return nullptr; + } + + // TODO (michaelludwig) - Once all filters are updated to process coordinate spaces more + // robustly, we can allow source images to have non-(0,0) origins, which will mean that the + // CTM/clipBounds modifications for the outerContext can go away. + SkMatrix outerMatrix(ctx.ctm()); + outerMatrix.postTranslate(SkIntToScalar(-innerOffset.x()), SkIntToScalar(-innerOffset.y())); + SkIRect clipBounds = ctx.clipBounds(); + clipBounds.offset(-innerOffset.x(), -innerOffset.y()); + // NOTE: This is the only spot in image filtering where the source image of the context + // is not constant for the entire DAG evaluation. Given that the inner and outer DAG branches + // were already created, there's no alternative way for the leaf nodes of the outer DAG to + // get the results of the inner DAG. Overriding the source image of the context has the correct + // effect, but means that the source image is not fixed for the entire filter process. + Context outerContext(outerMatrix, clipBounds, ctx.cache(), ctx.colorType(), ctx.colorSpace(), + inner.get()); + + SkIPoint outerOffset = SkIPoint::Make(0, 0); + sk_sp outer(this->filterInput(0, outerContext, &outerOffset)); + if (!outer) { + return nullptr; + } + + *offset = innerOffset + outerOffset; + return outer; +} + +SkIRect SkComposeImageFilterImpl::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection dir, const SkIRect* inputRect) const { + const SkImageFilter* outer = this->getInput(0); + const SkImageFilter* inner = this->getInput(1); + + const SkIRect innerRect = inner->filterBounds(src, ctm, dir, inputRect); + return outer->filterBounds(innerRect, ctm, dir, + kReverse_MapDirection == dir ? &innerRect : nullptr); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/gfx/skia/skia/src/effects/imagefilters/SkDisplacementMapEffect.cpp new file mode 100644 index 0000000000..ede0de438c --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkDisplacementMapEffect.cpp @@ -0,0 +1,675 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkDisplacementMapEffect.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" +#if SK_SUPPORT_GPU +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#endif + +namespace { + +class SkDisplacementMapEffectImpl final : public SkImageFilter_Base { +public: + SkDisplacementMapEffectImpl(SkColorChannel xChannelSelector, SkColorChannel yChannelSelector, + SkScalar scale, sk_sp inputs[2], + const CropRect* cropRect) + : INHERITED(inputs, 2, cropRect) + , fXChannelSelector(xChannelSelector) + , fYChannelSelector(yChannelSelector) + , fScale(scale) {} + + SkRect computeFastBounds(const SkRect& src) const override; + + virtual SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +protected: + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + + void flatten(SkWriteBuffer&) const override; + +private: + friend void SkDisplacementMapEffect::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkDisplacementMapEffectImpl) + + SkColorChannel fXChannelSelector; + SkColorChannel fYChannelSelector; + SkScalar fScale; + + const SkImageFilter* getDisplacementInput() const { return getInput(0); } + const SkImageFilter* getColorInput() const { return getInput(1); } + + typedef SkImageFilter_Base INHERITED; +}; + +// Shift values to extract channels from an SkColor (SkColorGetR, SkColorGetG, etc) +const uint8_t gChannelTypeToShift[] = { + 16, // R + 8, // G + 0, // B + 24, // A +}; +struct Extractor { + Extractor(SkColorChannel typeX, + SkColorChannel typeY) + : fShiftX(gChannelTypeToShift[static_cast(typeX)]) + , fShiftY(gChannelTypeToShift[static_cast(typeY)]) + {} + + unsigned fShiftX, fShiftY; + + unsigned getX(SkColor c) const { return (c >> fShiftX) & 0xFF; } + unsigned getY(SkColor c) const { return (c >> fShiftY) & 0xFF; } +}; + +static bool channel_selector_type_is_valid(SkColorChannel cst) { + switch (cst) { + case SkColorChannel::kR: + case SkColorChannel::kG: + case SkColorChannel::kB: + case SkColorChannel::kA: + return true; + default: + break; + } + return false; +} + +static SkColorChannel convert_channel_type(SkDisplacementMapEffect::ChannelSelectorType c) { + switch(c) { + case SkDisplacementMapEffect::kR_ChannelSelectorType: + return SkColorChannel::kR; + case SkDisplacementMapEffect::kG_ChannelSelectorType: + return SkColorChannel::kG; + case SkDisplacementMapEffect::kB_ChannelSelectorType: + return SkColorChannel::kB; + case SkDisplacementMapEffect::kA_ChannelSelectorType: + return SkColorChannel::kA; + case SkDisplacementMapEffect::kUnknown_ChannelSelectorType: + default: + // Raster backend historically treated this as B, GPU backend would fail when generating + // shader code. Just return B without aborting in debug-builds in order to keep fuzzers + // happy when they pass in the technically still valid kUnknown_ChannelSelectorType. + return SkColorChannel::kB; + } +} + +} // end namespace + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkDisplacementMapEffect::Make(ChannelSelectorType xChannelSelector, + ChannelSelectorType yChannelSelector, + SkScalar scale, + sk_sp displacement, + sk_sp color, + const SkImageFilter::CropRect* cropRect) { + return Make(convert_channel_type(xChannelSelector), convert_channel_type(yChannelSelector), + scale, std::move(displacement), std::move(color), cropRect); +} + +sk_sp SkDisplacementMapEffect::Make(SkColorChannel xChannelSelector, + SkColorChannel yChannelSelector, + SkScalar scale, + sk_sp displacement, + sk_sp color, + const SkImageFilter::CropRect* cropRect) { + if (!channel_selector_type_is_valid(xChannelSelector) || + !channel_selector_type_is_valid(yChannelSelector)) { + return nullptr; + } + + sk_sp inputs[2] = { std::move(displacement), std::move(color) }; + return sk_sp(new SkDisplacementMapEffectImpl(xChannelSelector, yChannelSelector, + scale, inputs, cropRect)); +} + +void SkDisplacementMapEffect::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkDisplacementMapEffectImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkDisplacementMapEffect", SkDisplacementMapEffectImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkDisplacementMapEffectImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2); + + SkColorChannel xsel, ysel; + if (buffer.isVersionLT(SkPicturePriv::kCleanupImageFilterEnums_Version)) { + xsel = convert_channel_type(buffer.read32LE( + SkDisplacementMapEffect::kLast_ChannelSelectorType)); + ysel = convert_channel_type(buffer.read32LE( + SkDisplacementMapEffect::kLast_ChannelSelectorType)); + } else { + xsel = buffer.read32LE(SkColorChannel::kLastEnum); + ysel = buffer.read32LE(SkColorChannel::kLastEnum); + } + + SkScalar scale = buffer.readScalar(); + + return SkDisplacementMapEffect::Make(xsel, ysel, scale, common.getInput(0), common.getInput(1), + &common.cropRect()); +} + +void SkDisplacementMapEffectImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeInt((int) fXChannelSelector); + buffer.writeInt((int) fYChannelSelector); + buffer.writeScalar(fScale); +} + +#if SK_SUPPORT_GPU +class GrDisplacementMapEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make( + SkColorChannel xChannelSelector, SkColorChannel yChannelSelector, SkVector scale, + sk_sp displacement, const SkIRect& displSubset, + const SkMatrix& offsetMatrix, sk_sp color, const SkIRect& colorSubset) { + return std::unique_ptr(new GrDisplacementMapEffect( + xChannelSelector, yChannelSelector, scale, std::move(displacement), displSubset, + offsetMatrix, std::move(color), colorSubset)); + } + + ~GrDisplacementMapEffect() override; + + SkColorChannel xChannelSelector() const { + return fXChannelSelector; + } + SkColorChannel yChannelSelector() const { + return fYChannelSelector; + } + const SkVector& scale() const { return fScale; } + + const char* name() const override { return "DisplacementMap"; } + const GrTextureDomain& domain() const { return fDomain; } + + std::unique_ptr clone() const override; + +private: + GrDisplacementMapEffect(const GrDisplacementMapEffect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + GrDisplacementMapEffect(SkColorChannel xChannelSelector, + SkColorChannel yChannelSelector, + const SkVector& scale, sk_sp displacement, + const SkIRect& displSubset, const SkMatrix& offsetMatrix, + sk_sp color, const SkIRect& colorSubset); + + const TextureSampler& onTextureSampler(int i) const override { + return IthTextureSampler(i, fDisplacementSampler, fColorSampler); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + GrCoordTransform fDisplacementTransform; + TextureSampler fDisplacementSampler; + GrCoordTransform fColorTransform; + GrTextureDomain fDomain; + TextureSampler fColorSampler; + SkColorChannel fXChannelSelector; + SkColorChannel fYChannelSelector; + SkVector fScale; + + typedef GrFragmentProcessor INHERITED; +}; +#endif + +static void compute_displacement(Extractor ex, const SkVector& scale, SkBitmap* dst, + const SkBitmap& displ, const SkIPoint& offset, + const SkBitmap& src, + const SkIRect& bounds) { + static const SkScalar Inv8bit = SkScalarInvert(255); + const int srcW = src.width(); + const int srcH = src.height(); + const SkVector scaleForColor = SkVector::Make(scale.fX * Inv8bit, scale.fY * Inv8bit); + const SkVector scaleAdj = SkVector::Make(SK_ScalarHalf - scale.fX * SK_ScalarHalf, + SK_ScalarHalf - scale.fY * SK_ScalarHalf); + SkPMColor* dstPtr = dst->getAddr32(0, 0); + for (int y = bounds.top(); y < bounds.bottom(); ++y) { + const SkPMColor* displPtr = displ.getAddr32(bounds.left() + offset.fX, y + offset.fY); + for (int x = bounds.left(); x < bounds.right(); ++x, ++displPtr) { + SkColor c = SkUnPreMultiply::PMColorToColor(*displPtr); + + SkScalar displX = scaleForColor.fX * ex.getX(c) + scaleAdj.fX; + SkScalar displY = scaleForColor.fY * ex.getY(c) + scaleAdj.fY; + // Truncate the displacement values + const int32_t srcX = Sk32_sat_add(x, SkScalarTruncToInt(displX)); + const int32_t srcY = Sk32_sat_add(y, SkScalarTruncToInt(displY)); + *dstPtr++ = ((srcX < 0) || (srcX >= srcW) || (srcY < 0) || (srcY >= srcH)) ? + 0 : *(src.getAddr32(srcX, srcY)); + } + } +} + +sk_sp SkDisplacementMapEffectImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint colorOffset = SkIPoint::Make(0, 0); + sk_sp color(this->filterInput(1, ctx, &colorOffset)); + if (!color) { + return nullptr; + } + + SkIPoint displOffset = SkIPoint::Make(0, 0); + // Creation of the displacement map should happen in a non-colorspace aware context. This + // texture is a purely mathematical construct, so we want to just operate on the stored + // values. Consider: + // User supplies an sRGB displacement map. If we're rendering to a wider gamut, then we could + // end up filtering the displacement map into that gamut, which has the effect of reducing + // the amount of displacement that it represents (as encoded values move away from the + // primaries). + // With a more complex DAG attached to this input, it's not clear that working in ANY specific + // color space makes sense, so we ignore color spaces (and gamma) entirely. This may not be + // ideal, but it's at least consistent and predictable. + Context displContext(ctx.mapping(), ctx.desiredOutput(), ctx.cache(), + kN32_SkColorType, nullptr, ctx.source()); + sk_sp displ(this->filterInput(0, displContext, &displOffset)); + if (!displ) { + return nullptr; + } + + const SkIRect srcBounds = SkIRect::MakeXYWH(colorOffset.x(), colorOffset.y(), + color->width(), color->height()); + + // Both paths do bounds checking on color pixel access, we don't need to + // pad the color bitmap to bounds here. + SkIRect bounds; + if (!this->applyCropRect(ctx, srcBounds, &bounds)) { + return nullptr; + } + + SkIRect displBounds; + displ = this->applyCropRectAndPad(ctx, displ.get(), &displOffset, &displBounds); + if (!displ) { + return nullptr; + } + + if (!bounds.intersect(displBounds)) { + return nullptr; + } + + const SkIRect colorBounds = bounds.makeOffset(-colorOffset); + // If the offset overflowed (saturated) then we have to abort, as we need their + // dimensions to be equal. See https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=7209 + if (colorBounds.size() != bounds.size()) { + return nullptr; + } + + SkVector scale = SkVector::Make(fScale, fScale); + ctx.ctm().mapVectors(&scale, 1); + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + auto context = ctx.getContext(); + + sk_sp colorProxy(color->asTextureProxyRef(context)); + sk_sp displProxy(displ->asTextureProxyRef(context)); + if (!colorProxy || !displProxy) { + return nullptr; + } + const auto isProtected = colorProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo; + + SkMatrix offsetMatrix = SkMatrix::MakeTrans(SkIntToScalar(colorOffset.fX - displOffset.fX), + SkIntToScalar(colorOffset.fY - displOffset.fY)); + + std::unique_ptr fp = + GrDisplacementMapEffect::Make(fXChannelSelector, + fYChannelSelector, + scale, + std::move(displProxy), + displ->subset(), + offsetMatrix, + std::move(colorProxy), + color->subset()); + fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), + color->alphaType(), ctx.colorSpace()); + + GrPaint paint; + paint.addColorFragmentProcessor(std::move(fp)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + SkMatrix matrix; + matrix.setTranslate(-SkIntToScalar(colorBounds.x()), -SkIntToScalar(colorBounds.y())); + + auto renderTargetContext = + context->priv().makeDeferredRenderTargetContext(SkBackingFit::kApprox, + bounds.width(), + bounds.height(), + ctx.grColorType(), + ctx.refColorSpace(), + 1, + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + isProtected); + if (!renderTargetContext) { + return nullptr; + } + + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, + SkRect::Make(colorBounds)); + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + return SkSpecialImage::MakeDeferredFromGpu( + context, + SkIRect::MakeWH(bounds.width(), bounds.height()), + kNeedNewImageUniqueID_SpecialImage, + renderTargetContext->asTextureProxyRef(), + renderTargetContext->colorInfo().colorType(), + renderTargetContext->colorInfo().refColorSpace()); + } +#endif + + SkBitmap colorBM, displBM; + + if (!color->getROPixels(&colorBM) || !displ->getROPixels(&displBM)) { + return nullptr; + } + + if ((colorBM.colorType() != kN32_SkColorType) || + (displBM.colorType() != kN32_SkColorType)) { + return nullptr; + } + + if (!colorBM.getPixels() || !displBM.getPixels()) { + return nullptr; + } + + SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), + colorBM.alphaType()); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + compute_displacement(Extractor(fXChannelSelector, fYChannelSelector), scale, &dst, + displBM, colorOffset - displOffset, colorBM, colorBounds); + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), + dst); +} + +SkRect SkDisplacementMapEffectImpl::computeFastBounds(const SkRect& src) const { + SkRect bounds = this->getColorInput() ? this->getColorInput()->computeFastBounds(src) : src; + bounds.outset(SkScalarAbs(fScale) * SK_ScalarHalf, SkScalarAbs(fScale) * SK_ScalarHalf); + return bounds; +} + +SkIRect SkDisplacementMapEffectImpl::onFilterNodeBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection, const SkIRect* inputRect) const { + SkVector scale = SkVector::Make(fScale, fScale); + ctm.mapVectors(&scale, 1); + return src.makeOutset(SkScalarCeilToInt(SkScalarAbs(scale.fX) * SK_ScalarHalf), + SkScalarCeilToInt(SkScalarAbs(scale.fY) * SK_ScalarHalf)); +} + +SkIRect SkDisplacementMapEffectImpl::onFilterBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const { + // Recurse only into color input. + if (this->getColorInput()) { + return this->getColorInput()->filterBounds(src, ctm, dir, inputRect); + } + return src; +} + +/////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU +class GrGLDisplacementMapEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; + + UniformHandle fScaleUni; + GrTextureDomain::GLDomain fGLDomain; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrDisplacementMapEffect::onCreateGLSLInstance() const { + return new GrGLDisplacementMapEffect; +} + +void GrDisplacementMapEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLDisplacementMapEffect::GenKey(*this, caps, b); +} + +GrDisplacementMapEffect::GrDisplacementMapEffect( + SkColorChannel xChannelSelector, + SkColorChannel yChannelSelector, + const SkVector& scale, + sk_sp displacement, + const SkIRect& displSubset, + const SkMatrix& offsetMatrix, + sk_sp color, + const SkIRect& colorSubset) + : INHERITED(kGrDisplacementMapEffect_ClassID, + GrFragmentProcessor::kNone_OptimizationFlags) + , fDisplacementTransform( + SkMatrix::Concat(SkMatrix::MakeTrans(displSubset.x(), displSubset.y()), + offsetMatrix), + displacement.get()) + , fDisplacementSampler(displacement) + , fColorTransform(SkMatrix::MakeTrans(colorSubset.x(), colorSubset.y()), color.get()) + , fDomain(color.get(), + GrTextureDomain::MakeTexelDomain(colorSubset, + GrTextureDomain::kDecal_Mode), + GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode) + , fColorSampler(color) + , fXChannelSelector(xChannelSelector) + , fYChannelSelector(yChannelSelector) + , fScale(scale) { + this->addCoordTransform(&fDisplacementTransform); + this->addCoordTransform(&fColorTransform); + this->setTextureSamplerCnt(2); +} + +GrDisplacementMapEffect::GrDisplacementMapEffect(const GrDisplacementMapEffect& that) + : INHERITED(kGrDisplacementMapEffect_ClassID, that.optimizationFlags()) + , fDisplacementTransform(that.fDisplacementTransform) + , fDisplacementSampler(that.fDisplacementSampler) + , fColorTransform(that.fColorTransform) + , fDomain(that.fDomain) + , fColorSampler(that.fColorSampler) + , fXChannelSelector(that.fXChannelSelector) + , fYChannelSelector(that.fYChannelSelector) + , fScale(that.fScale) { + this->addCoordTransform(&fDisplacementTransform); + this->addCoordTransform(&fColorTransform); + this->setTextureSamplerCnt(2); +} + +GrDisplacementMapEffect::~GrDisplacementMapEffect() {} + +std::unique_ptr GrDisplacementMapEffect::clone() const { + return std::unique_ptr(new GrDisplacementMapEffect(*this)); +} + +bool GrDisplacementMapEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrDisplacementMapEffect& s = sBase.cast(); + return fXChannelSelector == s.fXChannelSelector && + fYChannelSelector == s.fYChannelSelector && + fScale == s.fScale; +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDisplacementMapEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrDisplacementMapEffect::TestCreate(GrProcessorTestData* d) { + int texIdxDispl = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx : + GrProcessorUnitTest::kAlphaTextureIdx; + int texIdxColor = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx : + GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp dispProxy = d->textureProxy(texIdxDispl); + sk_sp colorProxy = d->textureProxy(texIdxColor); + static const int kMaxComponent = static_cast(SkColorChannel::kLastEnum); + SkColorChannel xChannelSelector = + static_cast(d->fRandom->nextRangeU(1, kMaxComponent)); + SkColorChannel yChannelSelector = + static_cast(d->fRandom->nextRangeU(1, kMaxComponent)); + SkVector scale = SkVector::Make(d->fRandom->nextRangeScalar(0, 100.0f), + d->fRandom->nextRangeScalar(0, 100.0f)); + SkISize colorDimensions; + colorDimensions.fWidth = d->fRandom->nextRangeU(0, colorProxy->width()); + colorDimensions.fHeight = d->fRandom->nextRangeU(0, colorProxy->height()); + SkIRect dispRect = SkIRect::MakeWH(dispProxy->width(), dispProxy->height()); + return GrDisplacementMapEffect::Make(xChannelSelector, yChannelSelector, scale, + std::move(dispProxy), + dispRect, + SkMatrix::I(), + std::move(colorProxy), SkIRect::MakeSize(colorDimensions)); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLDisplacementMapEffect::emitCode(EmitArgs& args) { + const GrDisplacementMapEffect& displacementMap = args.fFp.cast(); + const GrTextureDomain& domain = displacementMap.domain(); + + fScaleUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "Scale"); + const char* scaleUni = args.fUniformHandler->getUniformCStr(fScaleUni); + const char* dColor = "dColor"; + const char* cCoords = "cCoords"; + const char* nearZero = "1e-6"; // Since 6.10352e-5 is the smallest half float, use + // a number smaller than that to approximate 0, but + // leave room for 32-bit float GPU rounding errors. + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + fragBuilder->codeAppendf("\t\thalf4 %s = ", dColor); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], + args.fTransformedCoords[0].fVaryingPoint.c_str(), + args.fTransformedCoords[0].fVaryingPoint.getType()); + fragBuilder->codeAppend(";\n"); + + // Unpremultiply the displacement + fragBuilder->codeAppendf( + "\t\t%s.rgb = (%s.a < %s) ? half3(0.0) : saturate(%s.rgb / %s.a);", + dColor, dColor, nearZero, dColor, dColor); + SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[1].fVaryingPoint); + fragBuilder->codeAppendf("\t\tfloat2 %s = %s + %s*(%s.", + cCoords, coords2D.c_str(), scaleUni, dColor); + + switch (displacementMap.xChannelSelector()) { + case SkColorChannel::kR: + fragBuilder->codeAppend("r"); + break; + case SkColorChannel::kG: + fragBuilder->codeAppend("g"); + break; + case SkColorChannel::kB: + fragBuilder->codeAppend("b"); + break; + case SkColorChannel::kA: + fragBuilder->codeAppend("a"); + break; + default: + SkDEBUGFAIL("Unknown X channel selector"); + } + + switch (displacementMap.yChannelSelector()) { + case SkColorChannel::kR: + fragBuilder->codeAppend("r"); + break; + case SkColorChannel::kG: + fragBuilder->codeAppend("g"); + break; + case SkColorChannel::kB: + fragBuilder->codeAppend("b"); + break; + case SkColorChannel::kA: + fragBuilder->codeAppend("a"); + break; + default: + SkDEBUGFAIL("Unknown Y channel selector"); + } + fragBuilder->codeAppend("-half2(0.5));\t\t"); + + fGLDomain.sampleTexture(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + domain, + args.fOutputColor, + SkString(cCoords), + args.fTexSamplers[1]); + fragBuilder->codeAppend(";\n"); +} + +void GrGLDisplacementMapEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) { + const GrDisplacementMapEffect& displacementMap = proc.cast(); + GrTextureProxy* proxy = displacementMap.textureSampler(1).proxy(); + GrTexture* colorTex = proxy->peekTexture(); + + SkScalar scaleX = displacementMap.scale().fX / colorTex->width(); + SkScalar scaleY = displacementMap.scale().fY / colorTex->height(); + pdman.set2f(fScaleUni, SkScalarToFloat(scaleX), + proxy->origin() == kTopLeft_GrSurfaceOrigin ? + SkScalarToFloat(scaleY) : SkScalarToFloat(-scaleY)); + fGLDomain.setData(pdman, displacementMap.domain(), proxy, + displacementMap.textureSampler(1).samplerState()); +} + +void GrGLDisplacementMapEffect::GenKey(const GrProcessor& proc, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const GrDisplacementMapEffect& displacementMap = proc.cast(); + + static constexpr int kChannelSelectorKeyBits = 2; // Max value is 3, so 2 bits are required + + uint32_t xKey = static_cast(displacementMap.xChannelSelector()); + uint32_t yKey = static_cast(displacementMap.yChannelSelector()) + << kChannelSelectorKeyBits; + + b->add32(xKey | yKey); +} +#endif diff --git a/gfx/skia/skia/src/effects/imagefilters/SkDropShadowImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkDropShadowImageFilter.cpp new file mode 100644 index 0000000000..5f8fc19bad --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkDropShadowImageFilter.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkDropShadowImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/effects/SkBlurImageFilter.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkDropShadowImageFilterImpl final : public SkImageFilter_Base { +public: + SkDropShadowImageFilterImpl(SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, + SkColor color, bool shadowOnly, sk_sp input, + const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fDx(dx) + , fDy(dy) + , fSigmaX(sigmaX) + , fSigmaY(sigmaY) + , fColor(color) + , fShadowOnly(shadowOnly) {} + + SkRect computeFastBounds(const SkRect&) const override; + +protected: + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +private: + friend void SkDropShadowImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkDropShadowImageFilterImpl) + + SkScalar fDx, fDy, fSigmaX, fSigmaY; + SkColor fColor; + bool fShadowOnly; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkDropShadowImageFilter::Make(SkScalar dx, SkScalar dy, + SkScalar sigmaX, SkScalar sigmaY, + SkColor color, ShadowMode shadowMode, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + bool shadowOnly = shadowMode == SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode; + return sk_sp(new SkDropShadowImageFilterImpl( + dx, dy, sigmaX, sigmaY, color, shadowOnly, std::move(input), cropRect)); +} + +void SkDropShadowImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkDropShadowImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkDropShadowImageFilter", SkDropShadowImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkDropShadowImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkScalar dx = buffer.readScalar(); + SkScalar dy = buffer.readScalar(); + SkScalar sigmaX = buffer.readScalar(); + SkScalar sigmaY = buffer.readScalar(); + SkColor color = buffer.readColor(); + + // For backwards compatibility, the shadow mode had been saved as an enum cast to a 32LE int, + // where shadow-and-foreground was 0 and shadow-only was 1. Other than the number of bits, this + // is equivalent to the bool that SkDropShadowImageFilterImpl now uses. + bool shadowOnly = SkToBool(buffer.read32LE(1)); + // TODO (michaelludwig) - TODO: Call factory function once SkDropShadowImageFilter::Make no + // longer takes the old enum as its argument + return sk_sp(new SkDropShadowImageFilterImpl( + dx, dy, sigmaX, sigmaY, color, shadowOnly, common.getInput(0), &common.cropRect())); +} + +void SkDropShadowImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeScalar(fDx); + buffer.writeScalar(fDy); + buffer.writeScalar(fSigmaX); + buffer.writeScalar(fSigmaY); + buffer.writeColor(fColor); + // See CreateProc, but we save the bool as an int to match previous enum serialization. + buffer.writeInt(fShadowOnly); +} + +sk_sp SkDropShadowImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + SkIRect bounds; + if (!this->applyCropRect(ctx, inputBounds, &bounds)) { + return nullptr; + } + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); + + SkVector sigma = SkVector::Make(fSigmaX, fSigmaY); + ctx.ctm().mapVectors(&sigma, 1); + sigma.fX = SkMaxScalar(0, sigma.fX); + sigma.fY = SkMaxScalar(0, sigma.fY); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setImageFilter(SkBlurImageFilter::Make(sigma.fX, sigma.fY, nullptr)); + paint.setColorFilter(SkColorFilters::Blend(fColor, SkBlendMode::kSrcIn)); + + SkVector offsetVec = SkVector::Make(fDx, fDy); + ctx.ctm().mapVectors(&offsetVec, 1); + + canvas->translate(SkIntToScalar(inputOffset.fX - bounds.fLeft), + SkIntToScalar(inputOffset.fY - bounds.fTop)); + input->draw(canvas, offsetVec.fX, offsetVec.fY, &paint); + + if (!fShadowOnly) { + input->draw(canvas, 0, 0, nullptr); + } + offset->fX = bounds.fLeft; + offset->fY = bounds.fTop; + return surf->makeImageSnapshot(); +} + +SkRect SkDropShadowImageFilterImpl::computeFastBounds(const SkRect& src) const { + SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; + SkRect shadowBounds = bounds; + shadowBounds.offset(fDx, fDy); + shadowBounds.outset(fSigmaX * 3, fSigmaY * 3); + if (!fShadowOnly) { + bounds.join(shadowBounds); + } else { + bounds = shadowBounds; + } + return bounds; +} + +SkIRect SkDropShadowImageFilterImpl::onFilterNodeBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const { + SkVector offsetVec = SkVector::Make(fDx, fDy); + if (kReverse_MapDirection == dir) { + offsetVec.negate(); + } + ctm.mapVectors(&offsetVec, 1); + SkIRect dst = src.makeOffset(SkScalarCeilToInt(offsetVec.x()), + SkScalarCeilToInt(offsetVec.y())); + SkVector sigma = SkVector::Make(fSigmaX, fSigmaY); + ctm.mapVectors(&sigma, 1); + dst.outset( + SkScalarCeilToInt(SkScalarAbs(sigma.x() * 3)), + SkScalarCeilToInt(SkScalarAbs(sigma.y() * 3))); + if (!fShadowOnly) { + dst.join(src); + } + return dst; +} + diff --git a/gfx/skia/skia/src/effects/imagefilters/SkImageFilters.cpp b/gfx/skia/skia/src/effects/imagefilters/SkImageFilters.cpp new file mode 100644 index 0000000000..3121d7ac4b --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkImageFilters.cpp @@ -0,0 +1,260 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkImageFilters.h" + +// TODO (michaelludwig) - Right now there is a bit of a weird dependency where the implementations +// of the new, preferred filter factories depends on the per-filter headers in include/effects, +// which have themselves been marked as deprecated. But, once clients are updated to use the +// new factories implemented in this file, the per-filter headers can go into +// src/effects/imagefilters and will no longer be "deprecated" since they've been made fully +// internal at that point. +#include "include/effects/SkAlphaThresholdFilter.h" +#include "include/effects/SkArithmeticImageFilter.h" +#include "include/effects/SkBlurImageFilter.h" +#include "include/effects/SkColorFilterImageFilter.h" +#include "include/effects/SkComposeImageFilter.h" +#include "include/effects/SkDisplacementMapEffect.h" +#include "include/effects/SkDropShadowImageFilter.h" +#include "include/effects/SkImageSource.h" +#include "include/effects/SkLightingImageFilter.h" +#include "include/effects/SkMagnifierImageFilter.h" +#include "include/effects/SkMatrixConvolutionImageFilter.h" +#include "include/effects/SkMergeImageFilter.h" +#include "include/effects/SkMorphologyImageFilter.h" +#include "include/effects/SkOffsetImageFilter.h" +#include "include/effects/SkPaintImageFilter.h" +#include "include/effects/SkPictureImageFilter.h" +#include "include/effects/SkTileImageFilter.h" +#include "include/effects/SkXfermodeImageFilter.h" + +// TODO (michaelludwig) - Once SkCanvas can draw the results of a filter with any transform, this +// filter can be moved out of core +#include "src/core/SkMatrixImageFilter.h" + +// TODO (michaelludwig) - We are phasing out the use of SkImageFilter::CropRect since it does not +// appear as though edge flags are actually used and will move towards an explicit cropping filter. +// To assist with this, the new factory functions just take the basic SkIRect* even though the +// implementations have not been updated yet. +static SkImageFilter::CropRect make_crop_rect(const SkIRect* cropRect) { + return cropRect ? SkImageFilter::CropRect(SkRect::Make(*cropRect)) + : SkImageFilter::CropRect(SkRect::MakeEmpty(), 0x0); +} + +void SkImageFilters::RegisterFlattenables() { + SkAlphaThresholdFilter::RegisterFlattenables(); + SkArithmeticImageFilter::RegisterFlattenables(); + SkBlurImageFilter::RegisterFlattenables(); + SkColorFilterImageFilter::RegisterFlattenables(); + SkComposeImageFilter::RegisterFlattenables(); + SkDilateImageFilter::RegisterFlattenables(); + SkDisplacementMapEffect::RegisterFlattenables(); + SkDropShadowImageFilter::RegisterFlattenables(); + SkImageSource::RegisterFlattenables(); + SkLightingImageFilter::RegisterFlattenables(); + SkMagnifierImageFilter::RegisterFlattenables(); + SkMatrixConvolutionImageFilter::RegisterFlattenables(); + SkMergeImageFilter::RegisterFlattenables(); + SkOffsetImageFilter::RegisterFlattenables(); + SkPaintImageFilter::RegisterFlattenables(); + SkPictureImageFilter::RegisterFlattenables(); + SkTileImageFilter::RegisterFlattenables(); + SkXfermodeImageFilter::RegisterFlattenables(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImageFilters::AlphaThreshold( + const SkRegion& region, SkScalar innerMin, SkScalar outerMax, sk_sp input, + const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkAlphaThresholdFilter::Make(region, innerMin, outerMax, std::move(input), &r); +} + +sk_sp SkImageFilters::Arithmetic( + SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4, bool enforcePMColor, + sk_sp background, sk_sp foreground, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkArithmeticImageFilter::Make(k1, k2, k3, k4, enforcePMColor, std::move(background), + std::move(foreground), &r); +} + +sk_sp SkImageFilters::Blur( + SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, sk_sp input, + const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkBlurImageFilter::Make(sigmaX, sigmaY, tileMode, std::move(input), &r); +} + +sk_sp SkImageFilters::ColorFilter( + sk_sp cf, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkColorFilterImageFilter::Make(std::move(cf), std::move(input), &r); +} + +sk_sp SkImageFilters::Compose( + sk_sp outer, sk_sp inner) { + return SkComposeImageFilter::Make(std::move(outer), std::move(inner)); +} + +sk_sp SkImageFilters::DisplacementMap( + SkColorChannel xChannelSelector, SkColorChannel yChannelSelector, SkScalar scale, + sk_sp displacement, sk_sp color, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkDisplacementMapEffect::Make(xChannelSelector, yChannelSelector, scale, + std::move(displacement), std::move(color), &r); +} + +sk_sp SkImageFilters::DropShadow( + SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, SkColor color, + sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + // TODO (michaelludwig) - Once SkDropShadowImageFilter is fully hidden, this can be updated to + // pass a constant bool into the internal factory. + return SkDropShadowImageFilter::Make( + dx, dy, sigmaX, sigmaY, color, + SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, + std::move(input), &r); +} + +sk_sp SkImageFilters::DropShadowOnly( + SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, SkColor color, + sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + // TODO (michaelludwig) - Once SkDropShadowImageFilter is fully hidden, this can be updated to + // pass a constant bool into the internal factory. + return SkDropShadowImageFilter::Make(dx, dy, sigmaX, sigmaY, color, + SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, + std::move(input), &r); +} + +sk_sp SkImageFilters::Image( + sk_sp image, const SkRect& srcRect, const SkRect& dstRect, + SkFilterQuality filterQuality) { + return SkImageSource::Make(std::move(image), srcRect, dstRect, filterQuality); +} + +sk_sp SkImageFilters::Magnifier( + const SkRect& srcRect, SkScalar inset, sk_sp input,const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkMagnifierImageFilter::Make(srcRect, inset, std::move(input), &r); +} + +sk_sp SkImageFilters::MatrixConvolution( + const SkISize& kernelSize, const SkScalar kernel[], SkScalar gain, SkScalar bias, + const SkIPoint& kernelOffset, SkTileMode tileMode, bool convolveAlpha, + sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkMatrixConvolutionImageFilter::Make(kernelSize, kernel, gain, bias, kernelOffset, + tileMode, convolveAlpha, std::move(input), &r); +} + +sk_sp SkImageFilters::MatrixTransform( + const SkMatrix& transform, SkFilterQuality filterQuality, sk_sp input) { + return SkMatrixImageFilter::Make(transform, filterQuality, std::move(input)); +} + +sk_sp SkImageFilters::Merge( + sk_sp* const filters, int count, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkMergeImageFilter::Make(filters, count, &r); +} + +sk_sp SkImageFilters::Offset( + SkScalar dx, SkScalar dy, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkOffsetImageFilter::Make(dx, dy, std::move(input), &r); +} + +sk_sp SkImageFilters::Paint(const SkPaint& paint, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkPaintImageFilter::Make(paint, &r); +} + +sk_sp SkImageFilters::Picture(sk_sp pic, const SkRect& targetRect) { + return SkPictureImageFilter::Make(std::move(pic), targetRect); +} + +sk_sp SkImageFilters::Tile( + const SkRect& src, const SkRect& dst, sk_sp input) { + return SkTileImageFilter::Make(src, dst, std::move(input)); +} + +sk_sp SkImageFilters::Xfermode( + SkBlendMode mode, sk_sp background, sk_sp foreground, + const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkXfermodeImageFilter::Make(mode, std::move(background), std::move(foreground), &r); +} + +// Morphology filter effects + +sk_sp SkImageFilters::Dilate( + int radiusX, int radiusY, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkDilateImageFilter::Make(radiusX, radiusY, std::move(input), &r); +} + +sk_sp SkImageFilters::Erode( + int radiusX, int radiusY, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkErodeImageFilter::Make(radiusX, radiusY, std::move(input), &r); +} + +// Lighting filter effects + +sk_sp SkImageFilters::DistantLitDiffuse( + const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkLightingImageFilter::MakeDistantLitDiffuse(direction, lightColor, surfaceScale, kd, + std::move(input), &r); +} + +sk_sp SkImageFilters::PointLitDiffuse( + const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkLightingImageFilter::MakePointLitDiffuse(location, lightColor, surfaceScale, kd, + std::move(input), &r); +} + +sk_sp SkImageFilters::SpotLitDiffuse( + const SkPoint3& location, const SkPoint3& target, SkScalar falloffExponent, + SkScalar cutoffAngle, SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkLightingImageFilter::MakeSpotLitDiffuse(location, target, falloffExponent, cutoffAngle, + lightColor, surfaceScale, kd, + std::move(input), &r); +} + +sk_sp SkImageFilters::DistantLitSpecular( + const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkLightingImageFilter::MakeDistantLitSpecular(direction, lightColor, surfaceScale, + ks, shininess, std::move(input), &r); +} + +sk_sp SkImageFilters::PointLitSpecular( + const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkLightingImageFilter::MakePointLitSpecular(location, lightColor, surfaceScale, ks, + shininess, std::move(input), &r); +} + +sk_sp SkImageFilters::SpotLitSpecular( + const SkPoint3& location, const SkPoint3& target, SkScalar falloffExponent, + SkScalar cutoffAngle, SkColor lightColor, SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, const SkIRect* cropRect) { + SkImageFilter::CropRect r = make_crop_rect(cropRect); + return SkLightingImageFilter::MakeSpotLitSpecular(location, target, falloffExponent, + cutoffAngle, lightColor, surfaceScale, + ks, shininess, std::move(input), &r); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkImageSource.cpp b/gfx/skia/skia/src/effects/imagefilters/SkImageSource.cpp new file mode 100644 index 0000000000..7c4d1af053 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkImageSource.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkImageSource.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkImage.h" +#include "include/core/SkString.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkImageSourceImpl final : public SkImageFilter_Base { +public: + SkImageSourceImpl(sk_sp image, const SkRect& srcRect, const SkRect& dstRect, + SkFilterQuality filterQuality) + : INHERITED(nullptr, 0, nullptr) + , fImage(std::move(image)) + , fSrcRect(srcRect) + , fDstRect(dstRect) + , fFilterQuality(filterQuality) {} + + SkRect computeFastBounds(const SkRect& src) const override; + +protected: + void flatten(SkWriteBuffer&) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +private: + friend void SkImageSource::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkImageSourceImpl) + + sk_sp fImage; + SkRect fSrcRect, fDstRect; + SkFilterQuality fFilterQuality; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkImageSource::Make(sk_sp image) { + SkRect rect = image ? SkRect::MakeIWH(image->width(), image->height()) : SkRect::MakeEmpty(); + return SkImageSource::Make(std::move(image), rect, rect, kHigh_SkFilterQuality); +} + +sk_sp SkImageSource::Make(sk_sp image, + const SkRect& srcRect, + const SkRect& dstRect, + SkFilterQuality filterQuality) { + if (!image || srcRect.width() <= 0.0f || srcRect.height() <= 0.0f) { + return nullptr; + } + + return sk_sp(new SkImageSourceImpl( + std::move(image), srcRect, dstRect, filterQuality)); +} + +void SkImageSource::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkImageSourceImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkImageSourceImpl", SkImageSourceImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImageSourceImpl::CreateProc(SkReadBuffer& buffer) { + SkFilterQuality filterQuality = (SkFilterQuality)buffer.readInt(); + + SkRect src, dst; + buffer.readRect(&src); + buffer.readRect(&dst); + + sk_sp image(buffer.readImage()); + if (!image) { + return nullptr; + } + + return SkImageSource::Make(std::move(image), src, dst, filterQuality); +} + +void SkImageSourceImpl::flatten(SkWriteBuffer& buffer) const { + buffer.writeInt(fFilterQuality); + buffer.writeRect(fSrcRect); + buffer.writeRect(fDstRect); + buffer.writeImage(fImage.get()); +} + +sk_sp SkImageSourceImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkRect dstRect; + ctx.ctm().mapRect(&dstRect, fDstRect); + + SkRect bounds = SkRect::MakeIWH(fImage->width(), fImage->height()); + if (fSrcRect == bounds) { + int iLeft = dstRect.fLeft; + int iTop = dstRect.fTop; + // TODO: this seems to be a very noise-prone way to determine this (esp. the floating-point + // widths & heights). + if (dstRect.width() == bounds.width() && dstRect.height() == bounds.height() && + iLeft == dstRect.fLeft && iTop == dstRect.fTop) { + // The dest is just an un-scaled integer translation of the entire image; return it + offset->fX = iLeft; + offset->fY = iTop; + + return SkSpecialImage::MakeFromImage(ctx.getContext(), + SkIRect::MakeWH(fImage->width(), fImage->height()), + fImage, ctx.surfaceProps()); + } + } + + const SkIRect dstIRect = dstRect.roundOut(); + + sk_sp surf(ctx.makeSurface(dstIRect.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + // TODO: it seems like this clear shouldn't be necessary (see skbug.com/5075) + canvas->clear(0x0); + + SkPaint paint; + + // Subtract off the integer component of the translation (will be applied in offset, below). + dstRect.offset(-SkIntToScalar(dstIRect.fLeft), -SkIntToScalar(dstIRect.fTop)); + paint.setBlendMode(SkBlendMode::kSrc); + // FIXME: this probably shouldn't be necessary, but drawImageRect asserts + // None filtering when it's translate-only + paint.setFilterQuality( + fSrcRect.width() == dstRect.width() && fSrcRect.height() == dstRect.height() ? + kNone_SkFilterQuality : fFilterQuality); + canvas->drawImageRect(fImage.get(), fSrcRect, dstRect, &paint, + SkCanvas::kStrict_SrcRectConstraint); + + offset->fX = dstIRect.fLeft; + offset->fY = dstIRect.fTop; + return surf->makeImageSnapshot(); +} + +SkRect SkImageSourceImpl::computeFastBounds(const SkRect& src) const { + return fDstRect; +} + +SkIRect SkImageSourceImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection direction, + const SkIRect* inputRect) const { + if (kReverse_MapDirection == direction) { + return INHERITED::onFilterNodeBounds(src, ctm, direction, inputRect); + } + + SkRect dstRect = fDstRect; + ctm.mapRect(&dstRect); + return dstRect.roundOut(); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkLightingImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkLightingImageFilter.cpp new file mode 100644 index 0000000000..43d32dcc91 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkLightingImageFilter.cpp @@ -0,0 +1,2120 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkLightingImageFilter.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" + +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrGLDiffuseLightingEffect; +class GrGLSpecularLightingEffect; + +// For brevity +typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; +#endif + +const SkScalar gOneThird = SkIntToScalar(1) / 3; +const SkScalar gTwoThirds = SkIntToScalar(2) / 3; +const SkScalar gOneHalf = 0.5f; +const SkScalar gOneQuarter = 0.25f; + +#if SK_SUPPORT_GPU +static void setUniformPoint3(const GrGLSLProgramDataManager& pdman, UniformHandle uni, + const SkPoint3& point) { + GR_STATIC_ASSERT(sizeof(SkPoint3) == 3 * sizeof(float)); + pdman.set3fv(uni, 1, &point.fX); +} + +static void setUniformNormal3(const GrGLSLProgramDataManager& pdman, UniformHandle uni, + const SkPoint3& point) { + setUniformPoint3(pdman, uni, point); +} +#endif + +// Shift matrix components to the left, as we advance pixels to the right. +static inline void shiftMatrixLeft(int m[9]) { + m[0] = m[1]; + m[3] = m[4]; + m[6] = m[7]; + m[1] = m[2]; + m[4] = m[5]; + m[7] = m[8]; +} + +static inline void fast_normalize(SkPoint3* vector) { + // add a tiny bit so we don't have to worry about divide-by-zero + SkScalar magSq = vector->dot(*vector) + SK_ScalarNearlyZero; + SkScalar scale = sk_float_rsqrt(magSq); + vector->fX *= scale; + vector->fY *= scale; + vector->fZ *= scale; +} + +static SkPoint3 read_point3(SkReadBuffer& buffer) { + SkPoint3 point; + point.fX = buffer.readScalar(); + point.fY = buffer.readScalar(); + point.fZ = buffer.readScalar(); + buffer.validate(SkScalarIsFinite(point.fX) && + SkScalarIsFinite(point.fY) && + SkScalarIsFinite(point.fZ)); + return point; +}; + +static void write_point3(const SkPoint3& point, SkWriteBuffer& buffer) { + buffer.writeScalar(point.fX); + buffer.writeScalar(point.fY); + buffer.writeScalar(point.fZ); +}; + +class GrGLLight; +class SkImageFilterLight : public SkRefCnt { +public: + enum LightType { + kDistant_LightType, + kPoint_LightType, + kSpot_LightType, + + kLast_LightType = kSpot_LightType + }; + virtual LightType type() const = 0; + const SkPoint3& color() const { return fColor; } + virtual GrGLLight* createGLLight() const = 0; + virtual bool isEqual(const SkImageFilterLight& other) const { + return fColor == other.fColor; + } + virtual SkImageFilterLight* transform(const SkMatrix& matrix) const = 0; + + // Defined below SkLight's subclasses. + void flattenLight(SkWriteBuffer& buffer) const; + static SkImageFilterLight* UnflattenLight(SkReadBuffer& buffer); + + virtual SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const = 0; + virtual SkPoint3 lightColor(const SkPoint3& surfaceToLight) const = 0; + +protected: + SkImageFilterLight(SkColor color) { + fColor = SkPoint3::Make(SkIntToScalar(SkColorGetR(color)), + SkIntToScalar(SkColorGetG(color)), + SkIntToScalar(SkColorGetB(color))); + } + SkImageFilterLight(const SkPoint3& color) : fColor(color) {} + + SkImageFilterLight(SkReadBuffer& buffer) { + fColor = read_point3(buffer); + } + + virtual void onFlattenLight(SkWriteBuffer& buffer) const = 0; + + +private: + typedef SkRefCnt INHERITED; + SkPoint3 fColor; +}; + +class BaseLightingType { +public: + BaseLightingType() {} + virtual ~BaseLightingType() {} + + virtual SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, + const SkPoint3& lightColor) const= 0; +}; + +class DiffuseLightingType : public BaseLightingType { +public: + DiffuseLightingType(SkScalar kd) + : fKD(kd) {} + SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, + const SkPoint3& lightColor) const override { + SkScalar colorScale = fKD * normal.dot(surfaceTolight); + colorScale = SkScalarClampMax(colorScale, SK_Scalar1); + SkPoint3 color = lightColor.makeScale(colorScale); + return SkPackARGB32(255, + SkClampMax(SkScalarRoundToInt(color.fX), 255), + SkClampMax(SkScalarRoundToInt(color.fY), 255), + SkClampMax(SkScalarRoundToInt(color.fZ), 255)); + } +private: + SkScalar fKD; +}; + +static SkScalar max_component(const SkPoint3& p) { + return p.x() > p.y() ? (p.x() > p.z() ? p.x() : p.z()) : (p.y() > p.z() ? p.y() : p.z()); +} + +class SpecularLightingType : public BaseLightingType { +public: + SpecularLightingType(SkScalar ks, SkScalar shininess) + : fKS(ks), fShininess(shininess) {} + SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, + const SkPoint3& lightColor) const override { + SkPoint3 halfDir(surfaceTolight); + halfDir.fZ += SK_Scalar1; // eye position is always (0, 0, 1) + fast_normalize(&halfDir); + SkScalar colorScale = fKS * SkScalarPow(normal.dot(halfDir), fShininess); + colorScale = SkScalarClampMax(colorScale, SK_Scalar1); + SkPoint3 color = lightColor.makeScale(colorScale); + return SkPackARGB32(SkClampMax(SkScalarRoundToInt(max_component(color)), 255), + SkClampMax(SkScalarRoundToInt(color.fX), 255), + SkClampMax(SkScalarRoundToInt(color.fY), 255), + SkClampMax(SkScalarRoundToInt(color.fZ), 255)); + } +private: + SkScalar fKS; + SkScalar fShininess; +}; + +static inline SkScalar sobel(int a, int b, int c, int d, int e, int f, SkScalar scale) { + return (-a + b - 2 * c + 2 * d -e + f) * scale; +} + +static inline SkPoint3 pointToNormal(SkScalar x, SkScalar y, SkScalar surfaceScale) { + SkPoint3 vector = SkPoint3::Make(-x * surfaceScale, -y * surfaceScale, 1); + fast_normalize(&vector); + return vector; +} + +static inline SkPoint3 topLeftNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(0, 0, m[4], m[5], m[7], m[8], gTwoThirds), + sobel(0, 0, m[4], m[7], m[5], m[8], gTwoThirds), + surfaceScale); +} + +static inline SkPoint3 topNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel( 0, 0, m[3], m[5], m[6], m[8], gOneThird), + sobel(m[3], m[6], m[4], m[7], m[5], m[8], gOneHalf), + surfaceScale); +} + +static inline SkPoint3 topRightNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel( 0, 0, m[3], m[4], m[6], m[7], gTwoThirds), + sobel(m[3], m[6], m[4], m[7], 0, 0, gTwoThirds), + surfaceScale); +} + +static inline SkPoint3 leftNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(m[1], m[2], m[4], m[5], m[7], m[8], gOneHalf), + sobel( 0, 0, m[1], m[7], m[2], m[8], gOneThird), + surfaceScale); +} + + +static inline SkPoint3 interiorNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(m[0], m[2], m[3], m[5], m[6], m[8], gOneQuarter), + sobel(m[0], m[6], m[1], m[7], m[2], m[8], gOneQuarter), + surfaceScale); +} + +static inline SkPoint3 rightNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(m[0], m[1], m[3], m[4], m[6], m[7], gOneHalf), + sobel(m[0], m[6], m[1], m[7], 0, 0, gOneThird), + surfaceScale); +} + +static inline SkPoint3 bottomLeftNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(m[1], m[2], m[4], m[5], 0, 0, gTwoThirds), + sobel( 0, 0, m[1], m[4], m[2], m[5], gTwoThirds), + surfaceScale); +} + +static inline SkPoint3 bottomNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(m[0], m[2], m[3], m[5], 0, 0, gOneThird), + sobel(m[0], m[3], m[1], m[4], m[2], m[5], gOneHalf), + surfaceScale); +} + +static inline SkPoint3 bottomRightNormal(int m[9], SkScalar surfaceScale) { + return pointToNormal(sobel(m[0], m[1], m[3], m[4], 0, 0, gTwoThirds), + sobel(m[0], m[3], m[1], m[4], 0, 0, gTwoThirds), + surfaceScale); +} + + +class UncheckedPixelFetcher { +public: + static inline uint32_t Fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { + return SkGetPackedA32(*src.getAddr32(x, y)); + } +}; + +// The DecalPixelFetcher is used when the destination crop rect exceeds the input bitmap bounds. +class DecalPixelFetcher { +public: + static inline uint32_t Fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { + if (x < bounds.fLeft || x >= bounds.fRight || y < bounds.fTop || y >= bounds.fBottom) { + return 0; + } else { + return SkGetPackedA32(*src.getAddr32(x, y)); + } + } +}; + +template +static void lightBitmap(const BaseLightingType& lightingType, + const SkImageFilterLight* l, + const SkBitmap& src, + SkBitmap* dst, + SkScalar surfaceScale, + const SkIRect& bounds) { + SkASSERT(dst->width() == bounds.width() && dst->height() == bounds.height()); + int left = bounds.left(), right = bounds.right(); + int bottom = bounds.bottom(); + int y = bounds.top(); + SkIRect srcBounds = src.bounds(); + SkPMColor* dptr = dst->getAddr32(0, 0); + { + int x = left; + int m[9]; + m[4] = PixelFetcher::Fetch(src, x, y, srcBounds); + m[5] = PixelFetcher::Fetch(src, x + 1, y, srcBounds); + m[7] = PixelFetcher::Fetch(src, x, y + 1, srcBounds); + m[8] = PixelFetcher::Fetch(src, x + 1, y + 1, srcBounds); + SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(topLeftNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + for (++x; x < right - 1; ++x) + { + shiftMatrixLeft(m); + m[5] = PixelFetcher::Fetch(src, x + 1, y, srcBounds); + m[8] = PixelFetcher::Fetch(src, x + 1, y + 1, srcBounds); + surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(topNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + } + shiftMatrixLeft(m); + surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(topRightNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + } + + for (++y; y < bottom - 1; ++y) { + int x = left; + int m[9]; + m[1] = PixelFetcher::Fetch(src, x, y - 1, srcBounds); + m[2] = PixelFetcher::Fetch(src, x + 1, y - 1, srcBounds); + m[4] = PixelFetcher::Fetch(src, x, y, srcBounds); + m[5] = PixelFetcher::Fetch(src, x + 1, y, srcBounds); + m[7] = PixelFetcher::Fetch(src, x, y + 1, srcBounds); + m[8] = PixelFetcher::Fetch(src, x + 1, y + 1, srcBounds); + SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(leftNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + for (++x; x < right - 1; ++x) { + shiftMatrixLeft(m); + m[2] = PixelFetcher::Fetch(src, x + 1, y - 1, srcBounds); + m[5] = PixelFetcher::Fetch(src, x + 1, y, srcBounds); + m[8] = PixelFetcher::Fetch(src, x + 1, y + 1, srcBounds); + surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(interiorNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + } + shiftMatrixLeft(m); + surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(rightNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + } + + { + int x = left; + int m[9]; + m[1] = PixelFetcher::Fetch(src, x, bottom - 2, srcBounds); + m[2] = PixelFetcher::Fetch(src, x + 1, bottom - 2, srcBounds); + m[4] = PixelFetcher::Fetch(src, x, bottom - 1, srcBounds); + m[5] = PixelFetcher::Fetch(src, x + 1, bottom - 1, srcBounds); + SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(bottomLeftNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + for (++x; x < right - 1; ++x) + { + shiftMatrixLeft(m); + m[2] = PixelFetcher::Fetch(src, x + 1, bottom - 2, srcBounds); + m[5] = PixelFetcher::Fetch(src, x + 1, bottom - 1, srcBounds); + surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(bottomNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + } + shiftMatrixLeft(m); + surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale); + *dptr++ = lightingType.light(bottomRightNormal(m, surfaceScale), surfaceToLight, + l->lightColor(surfaceToLight)); + } +} + +static void lightBitmap(const BaseLightingType& lightingType, + const SkImageFilterLight* light, + const SkBitmap& src, + SkBitmap* dst, + SkScalar surfaceScale, + const SkIRect& bounds) { + if (src.bounds().contains(bounds)) { + lightBitmap( + lightingType, light, src, dst, surfaceScale, bounds); + } else { + lightBitmap( + lightingType, light, src, dst, surfaceScale, bounds); + } +} + +enum BoundaryMode { + kTopLeft_BoundaryMode, + kTop_BoundaryMode, + kTopRight_BoundaryMode, + kLeft_BoundaryMode, + kInterior_BoundaryMode, + kRight_BoundaryMode, + kBottomLeft_BoundaryMode, + kBottom_BoundaryMode, + kBottomRight_BoundaryMode, + + kBoundaryModeCount, +}; + +class SkLightingImageFilterInternal : public SkImageFilter_Base { +protected: + SkLightingImageFilterInternal(sk_sp light, + SkScalar surfaceScale, + sk_sp input, + const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fLight(std::move(light)) + , fSurfaceScale(surfaceScale / 255) {} + + void flatten(SkWriteBuffer& buffer) const override { + this->INHERITED::flatten(buffer); + fLight->flattenLight(buffer); + buffer.writeScalar(fSurfaceScale * 255); + } + + bool affectsTransparentBlack() const override { return true; } + + const SkImageFilterLight* light() const { return fLight.get(); } + inline sk_sp refLight() const { return fLight; } + SkScalar surfaceScale() const { return fSurfaceScale; } + +#if SK_SUPPORT_GPU + sk_sp filterImageGPU(const Context& ctx, + SkSpecialImage* input, + const SkIRect& bounds, + const SkMatrix& matrix) const; + virtual std::unique_ptr makeFragmentProcessor( + sk_sp, + const SkMatrix&, + const SkIRect* srcBounds, + BoundaryMode boundaryMode) const = 0; +#endif + +private: +#if SK_SUPPORT_GPU + void drawRect(GrRenderTargetContext*, + sk_sp srcProxy, + const SkMatrix& matrix, + const GrClip& clip, + const SkRect& dstRect, + BoundaryMode boundaryMode, + const SkIRect* srcBounds, + const SkIRect& bounds) const; +#endif + + sk_sp fLight; + SkScalar fSurfaceScale; + + typedef SkImageFilter_Base INHERITED; +}; + +#if SK_SUPPORT_GPU +void SkLightingImageFilterInternal::drawRect(GrRenderTargetContext* renderTargetContext, + sk_sp srcProxy, + const SkMatrix& matrix, + const GrClip& clip, + const SkRect& dstRect, + BoundaryMode boundaryMode, + const SkIRect* srcBounds, + const SkIRect& bounds) const { + SkRect srcRect = dstRect.makeOffset(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y())); + GrPaint paint; + auto fp = this->makeFragmentProcessor(std::move(srcProxy), matrix, srcBounds, boundaryMode); + paint.addColorFragmentProcessor(std::move(fp)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect, + srcRect); +} + +sk_sp SkLightingImageFilterInternal::filterImageGPU( + const Context& ctx, + SkSpecialImage* input, + const SkIRect& offsetBounds, + const SkMatrix& matrix) const { + SkASSERT(ctx.gpuBacked()); + + auto context = ctx.getContext(); + + sk_sp inputProxy(input->asTextureProxyRef(context)); + SkASSERT(inputProxy); + + auto renderTargetContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, + offsetBounds.width(), + offsetBounds.height(), + ctx.grColorType(), + ctx.refColorSpace(), + 1, + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + inputProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo); + if (!renderTargetContext) { + return nullptr; + } + + SkIRect dstIRect = SkIRect::MakeWH(offsetBounds.width(), offsetBounds.height()); + SkRect dstRect = SkRect::Make(dstIRect); + + // setup new clip + GrFixedClip clip(dstIRect); + + const SkIRect inputBounds = SkIRect::MakeWH(input->width(), input->height()); + SkRect topLeft = SkRect::MakeXYWH(0, 0, 1, 1); + SkRect top = SkRect::MakeXYWH(1, 0, dstRect.width() - 2, 1); + SkRect topRight = SkRect::MakeXYWH(dstRect.width() - 1, 0, 1, 1); + SkRect left = SkRect::MakeXYWH(0, 1, 1, dstRect.height() - 2); + SkRect interior = dstRect.makeInset(1, 1); + SkRect right = SkRect::MakeXYWH(dstRect.width() - 1, 1, 1, dstRect.height() - 2); + SkRect bottomLeft = SkRect::MakeXYWH(0, dstRect.height() - 1, 1, 1); + SkRect bottom = SkRect::MakeXYWH(1, dstRect.height() - 1, dstRect.width() - 2, 1); + SkRect bottomRight = SkRect::MakeXYWH(dstRect.width() - 1, dstRect.height() - 1, 1, 1); + + const SkIRect* pSrcBounds = inputBounds.contains(offsetBounds) ? nullptr : &inputBounds; + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, topLeft, + kTopLeft_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, top, + kTop_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, topRight, + kTopRight_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, left, + kLeft_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, interior, + kInterior_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, right, + kRight_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, bottomLeft, + kBottomLeft_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, bottom, + kBottom_BoundaryMode, pSrcBounds, offsetBounds); + this->drawRect(renderTargetContext.get(), inputProxy, matrix, clip, bottomRight, + kBottomRight_BoundaryMode, pSrcBounds, offsetBounds); + + return SkSpecialImage::MakeDeferredFromGpu( + context, + SkIRect::MakeWH(offsetBounds.width(), offsetBounds.height()), + kNeedNewImageUniqueID_SpecialImage, + renderTargetContext->asTextureProxyRef(), + renderTargetContext->colorInfo().colorType(), + renderTargetContext->colorInfo().refColorSpace()); +} +#endif + +class SkDiffuseLightingImageFilter : public SkLightingImageFilterInternal { +public: + static sk_sp Make(sk_sp light, + SkScalar surfaceScale, + SkScalar kd, + sk_sp, + const CropRect*); + + SkScalar kd() const { return fKD; } + +protected: + SkDiffuseLightingImageFilter(sk_sp light, SkScalar surfaceScale, + SkScalar kd, + sk_sp input, const CropRect* cropRect); + void flatten(SkWriteBuffer& buffer) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +#if SK_SUPPORT_GPU + std::unique_ptr makeFragmentProcessor(sk_sp, + const SkMatrix&, + const SkIRect* bounds, + BoundaryMode) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkDiffuseLightingImageFilter) + friend class SkLightingImageFilter; + SkScalar fKD; + + typedef SkLightingImageFilterInternal INHERITED; +}; + +class SkSpecularLightingImageFilter : public SkLightingImageFilterInternal { +public: + static sk_sp Make(sk_sp light, + SkScalar surfaceScale, + SkScalar ks, SkScalar shininess, + sk_sp, const CropRect*); + + SkScalar ks() const { return fKS; } + SkScalar shininess() const { return fShininess; } + +protected: + SkSpecularLightingImageFilter(sk_sp light, + SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, + sk_sp input, const CropRect*); + void flatten(SkWriteBuffer& buffer) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +#if SK_SUPPORT_GPU + std::unique_ptr makeFragmentProcessor(sk_sp, + const SkMatrix&, + const SkIRect* bounds, + BoundaryMode) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkSpecularLightingImageFilter) + + SkScalar fKS; + SkScalar fShininess; + friend class SkLightingImageFilter; + typedef SkLightingImageFilterInternal INHERITED; +}; + +#if SK_SUPPORT_GPU + +class GrLightingEffect : public GrFragmentProcessor { +public: + const SkImageFilterLight* light() const { return fLight.get(); } + SkScalar surfaceScale() const { return fSurfaceScale; } + const SkMatrix& filterMatrix() const { return fFilterMatrix; } + BoundaryMode boundaryMode() const { return fBoundaryMode; } + const GrTextureDomain& domain() const { return fDomain; } + +protected: + GrLightingEffect(ClassID classID, sk_sp, sk_sp light, + SkScalar surfaceScale, const SkMatrix& matrix, BoundaryMode boundaryMode, + const SkIRect* srcBounds); + + GrLightingEffect(const GrLightingEffect& that); + + bool onIsEqual(const GrFragmentProcessor&) const override; + +private: + const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; } + + GrCoordTransform fCoordTransform; + GrTextureDomain fDomain; + TextureSampler fTextureSampler; + sk_sp fLight; + SkScalar fSurfaceScale; + SkMatrix fFilterMatrix; + BoundaryMode fBoundaryMode; + + typedef GrFragmentProcessor INHERITED; +}; + +class GrDiffuseLightingEffect : public GrLightingEffect { +public: + static std::unique_ptr Make(sk_sp proxy, + sk_sp light, + SkScalar surfaceScale, + const SkMatrix& matrix, + SkScalar kd, + BoundaryMode boundaryMode, + const SkIRect* srcBounds) { + return std::unique_ptr( + new GrDiffuseLightingEffect(std::move(proxy), std::move(light), surfaceScale, + matrix, kd, boundaryMode, srcBounds)); + } + + const char* name() const override { return "DiffuseLighting"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrDiffuseLightingEffect(*this)); + } + + SkScalar kd() const { return fKD; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + GrDiffuseLightingEffect(sk_sp, + sk_sp light, + SkScalar surfaceScale, + const SkMatrix& matrix, + SkScalar kd, + BoundaryMode boundaryMode, + const SkIRect* srcBounds); + + explicit GrDiffuseLightingEffect(const GrDiffuseLightingEffect& that); + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + SkScalar fKD; + + typedef GrLightingEffect INHERITED; +}; + +class GrSpecularLightingEffect : public GrLightingEffect { +public: + static std::unique_ptr Make(sk_sp proxy, + sk_sp light, + SkScalar surfaceScale, + const SkMatrix& matrix, + SkScalar ks, + SkScalar shininess, + BoundaryMode boundaryMode, + const SkIRect* srcBounds) { + return std::unique_ptr( + new GrSpecularLightingEffect(std::move(proxy), std::move(light), surfaceScale, + matrix, ks, shininess, boundaryMode, srcBounds)); + } + + const char* name() const override { return "SpecularLighting"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrSpecularLightingEffect(*this)); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + SkScalar ks() const { return fKS; } + SkScalar shininess() const { return fShininess; } + +private: + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + GrSpecularLightingEffect(sk_sp, + sk_sp light, + SkScalar surfaceScale, + const SkMatrix& matrix, + SkScalar ks, + SkScalar shininess, + BoundaryMode boundaryMode, + const SkIRect* srcBounds); + + explicit GrSpecularLightingEffect(const GrSpecularLightingEffect&); + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + SkScalar fKS; + SkScalar fShininess; + + typedef GrLightingEffect INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLLight { +public: + virtual ~GrGLLight() {} + + /** + * This is called by GrGLLightingEffect::emitCode() before either of the two virtual functions + * below. It adds a half3 uniform visible in the FS that represents the constant light color. + */ + void emitLightColorUniform(GrGLSLUniformHandler*); + + /** + * These two functions are called from GrGLLightingEffect's emitCode() function. + * emitSurfaceToLight places an expression in param out that is the vector from the surface to + * the light. The expression will be used in the FS. emitLightColor writes an expression into + * the FS that is the color of the light. Either function may add functions and/or uniforms to + * the FS. The default of emitLightColor appends the name of the constant light color uniform + * and so this function only needs to be overridden if the light color varies spatially. + */ + virtual void emitSurfaceToLight(GrGLSLUniformHandler*, + GrGLSLFPFragmentBuilder*, + const char* z) = 0; + virtual void emitLightColor(GrGLSLUniformHandler*, + GrGLSLFPFragmentBuilder*, + const char *surfaceToLight); + + // This is called from GrGLLightingEffect's setData(). Subclasses of GrGLLight must call + // INHERITED::setData(). + virtual void setData(const GrGLSLProgramDataManager&, const SkImageFilterLight* light) const; + +protected: + /** + * Gets the constant light color uniform. Subclasses can use this in their emitLightColor + * function. + */ + UniformHandle lightColorUni() const { return fColorUni; } + +private: + UniformHandle fColorUni; + + typedef SkRefCnt INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLDistantLight : public GrGLLight { +public: + ~GrGLDistantLight() override {} + void setData(const GrGLSLProgramDataManager&, const SkImageFilterLight* light) const override; + void emitSurfaceToLight(GrGLSLUniformHandler*, GrGLSLFPFragmentBuilder*, const char* z) override; + +private: + typedef GrGLLight INHERITED; + UniformHandle fDirectionUni; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLPointLight : public GrGLLight { +public: + ~GrGLPointLight() override {} + void setData(const GrGLSLProgramDataManager&, const SkImageFilterLight* light) const override; + void emitSurfaceToLight(GrGLSLUniformHandler*, GrGLSLFPFragmentBuilder*, const char* z) override; + +private: + typedef GrGLLight INHERITED; + UniformHandle fLocationUni; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLSpotLight : public GrGLLight { +public: + ~GrGLSpotLight() override {} + void setData(const GrGLSLProgramDataManager&, const SkImageFilterLight* light) const override; + void emitSurfaceToLight(GrGLSLUniformHandler*, GrGLSLFPFragmentBuilder*, const char* z) override; + void emitLightColor(GrGLSLUniformHandler*, + GrGLSLFPFragmentBuilder*, + const char *surfaceToLight) override; + +private: + typedef GrGLLight INHERITED; + + SkString fLightColorFunc; + UniformHandle fLocationUni; + UniformHandle fExponentUni; + UniformHandle fCosOuterConeAngleUni; + UniformHandle fCosInnerConeAngleUni; + UniformHandle fConeScaleUni; + UniformHandle fSUni; +}; +#else + +class GrGLLight; + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// + +class SkDistantLight : public SkImageFilterLight { +public: + SkDistantLight(const SkPoint3& direction, SkColor color) + : INHERITED(color), fDirection(direction) { + } + + SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const override { + return fDirection; + } + SkPoint3 lightColor(const SkPoint3&) const override { return this->color(); } + LightType type() const override { return kDistant_LightType; } + const SkPoint3& direction() const { return fDirection; } + GrGLLight* createGLLight() const override { +#if SK_SUPPORT_GPU + return new GrGLDistantLight; +#else + SkDEBUGFAIL("Should not call in GPU-less build"); + return nullptr; +#endif + } + + bool isEqual(const SkImageFilterLight& other) const override { + if (other.type() != kDistant_LightType) { + return false; + } + + const SkDistantLight& o = static_cast(other); + return INHERITED::isEqual(other) && + fDirection == o.fDirection; + } + + SkDistantLight(SkReadBuffer& buffer) : INHERITED(buffer) { + fDirection = read_point3(buffer); + } + +protected: + SkDistantLight(const SkPoint3& direction, const SkPoint3& color) + : INHERITED(color), fDirection(direction) { + } + SkImageFilterLight* transform(const SkMatrix& matrix) const override { + return new SkDistantLight(direction(), color()); + } + void onFlattenLight(SkWriteBuffer& buffer) const override { + write_point3(fDirection, buffer); + } + +private: + SkPoint3 fDirection; + + typedef SkImageFilterLight INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkPointLight : public SkImageFilterLight { +public: + SkPointLight(const SkPoint3& location, SkColor color) + : INHERITED(color), fLocation(location) {} + + SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const override { + SkPoint3 direction = SkPoint3::Make(fLocation.fX - SkIntToScalar(x), + fLocation.fY - SkIntToScalar(y), + fLocation.fZ - SkIntToScalar(z) * surfaceScale); + fast_normalize(&direction); + return direction; + } + SkPoint3 lightColor(const SkPoint3&) const override { return this->color(); } + LightType type() const override { return kPoint_LightType; } + const SkPoint3& location() const { return fLocation; } + GrGLLight* createGLLight() const override { +#if SK_SUPPORT_GPU + return new GrGLPointLight; +#else + SkDEBUGFAIL("Should not call in GPU-less build"); + return nullptr; +#endif + } + + bool isEqual(const SkImageFilterLight& other) const override { + if (other.type() != kPoint_LightType) { + return false; + } + const SkPointLight& o = static_cast(other); + return INHERITED::isEqual(other) && + fLocation == o.fLocation; + } + SkImageFilterLight* transform(const SkMatrix& matrix) const override { + SkPoint location2 = SkPoint::Make(fLocation.fX, fLocation.fY); + matrix.mapPoints(&location2, 1); + // Use X scale and Y scale on Z and average the result + SkPoint locationZ = SkPoint::Make(fLocation.fZ, fLocation.fZ); + matrix.mapVectors(&locationZ, 1); + SkPoint3 location = SkPoint3::Make(location2.fX, + location2.fY, + SkScalarAve(locationZ.fX, locationZ.fY)); + return new SkPointLight(location, color()); + } + + SkPointLight(SkReadBuffer& buffer) : INHERITED(buffer) { + fLocation = read_point3(buffer); + } + +protected: + SkPointLight(const SkPoint3& location, const SkPoint3& color) + : INHERITED(color), fLocation(location) {} + void onFlattenLight(SkWriteBuffer& buffer) const override { + write_point3(fLocation, buffer); + } + +private: + SkPoint3 fLocation; + + typedef SkImageFilterLight INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkSpotLight : public SkImageFilterLight { +public: + SkSpotLight(const SkPoint3& location, + const SkPoint3& target, + SkScalar specularExponent, + SkScalar cutoffAngle, + SkColor color) + : INHERITED(color), + fLocation(location), + fTarget(target), + fSpecularExponent(SkScalarPin(specularExponent, kSpecularExponentMin, kSpecularExponentMax)) + { + fS = target - location; + fast_normalize(&fS); + fCosOuterConeAngle = SkScalarCos(SkDegreesToRadians(cutoffAngle)); + const SkScalar antiAliasThreshold = 0.016f; + fCosInnerConeAngle = fCosOuterConeAngle + antiAliasThreshold; + fConeScale = SkScalarInvert(antiAliasThreshold); + } + + SkImageFilterLight* transform(const SkMatrix& matrix) const override { + SkPoint location2 = SkPoint::Make(fLocation.fX, fLocation.fY); + matrix.mapPoints(&location2, 1); + // Use X scale and Y scale on Z and average the result + SkPoint locationZ = SkPoint::Make(fLocation.fZ, fLocation.fZ); + matrix.mapVectors(&locationZ, 1); + SkPoint3 location = SkPoint3::Make(location2.fX, location2.fY, + SkScalarAve(locationZ.fX, locationZ.fY)); + SkPoint target2 = SkPoint::Make(fTarget.fX, fTarget.fY); + matrix.mapPoints(&target2, 1); + SkPoint targetZ = SkPoint::Make(fTarget.fZ, fTarget.fZ); + matrix.mapVectors(&targetZ, 1); + SkPoint3 target = SkPoint3::Make(target2.fX, target2.fY, + SkScalarAve(targetZ.fX, targetZ.fY)); + SkPoint3 s = target - location; + fast_normalize(&s); + return new SkSpotLight(location, + target, + fSpecularExponent, + fCosOuterConeAngle, + fCosInnerConeAngle, + fConeScale, + s, + color()); + } + + SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const override { + SkPoint3 direction = SkPoint3::Make(fLocation.fX - SkIntToScalar(x), + fLocation.fY - SkIntToScalar(y), + fLocation.fZ - SkIntToScalar(z) * surfaceScale); + fast_normalize(&direction); + return direction; + } + SkPoint3 lightColor(const SkPoint3& surfaceToLight) const override { + SkScalar cosAngle = -surfaceToLight.dot(fS); + SkScalar scale = 0; + if (cosAngle >= fCosOuterConeAngle) { + scale = SkScalarPow(cosAngle, fSpecularExponent); + if (cosAngle < fCosInnerConeAngle) { + scale *= (cosAngle - fCosOuterConeAngle) * fConeScale; + } + } + return this->color().makeScale(scale); + } + GrGLLight* createGLLight() const override { +#if SK_SUPPORT_GPU + return new GrGLSpotLight; +#else + SkDEBUGFAIL("Should not call in GPU-less build"); + return nullptr; +#endif + } + LightType type() const override { return kSpot_LightType; } + const SkPoint3& location() const { return fLocation; } + const SkPoint3& target() const { return fTarget; } + SkScalar specularExponent() const { return fSpecularExponent; } + SkScalar cosInnerConeAngle() const { return fCosInnerConeAngle; } + SkScalar cosOuterConeAngle() const { return fCosOuterConeAngle; } + SkScalar coneScale() const { return fConeScale; } + const SkPoint3& s() const { return fS; } + + SkSpotLight(SkReadBuffer& buffer) : INHERITED(buffer) { + fLocation = read_point3(buffer); + fTarget = read_point3(buffer); + fSpecularExponent = buffer.readScalar(); + fCosOuterConeAngle = buffer.readScalar(); + fCosInnerConeAngle = buffer.readScalar(); + fConeScale = buffer.readScalar(); + fS = read_point3(buffer); + buffer.validate(SkScalarIsFinite(fSpecularExponent) && + SkScalarIsFinite(fCosOuterConeAngle) && + SkScalarIsFinite(fCosInnerConeAngle) && + SkScalarIsFinite(fConeScale)); + } +protected: + SkSpotLight(const SkPoint3& location, + const SkPoint3& target, + SkScalar specularExponent, + SkScalar cosOuterConeAngle, + SkScalar cosInnerConeAngle, + SkScalar coneScale, + const SkPoint3& s, + const SkPoint3& color) + : INHERITED(color), + fLocation(location), + fTarget(target), + fSpecularExponent(specularExponent), + fCosOuterConeAngle(cosOuterConeAngle), + fCosInnerConeAngle(cosInnerConeAngle), + fConeScale(coneScale), + fS(s) + { + } + void onFlattenLight(SkWriteBuffer& buffer) const override { + write_point3(fLocation, buffer); + write_point3(fTarget, buffer); + buffer.writeScalar(fSpecularExponent); + buffer.writeScalar(fCosOuterConeAngle); + buffer.writeScalar(fCosInnerConeAngle); + buffer.writeScalar(fConeScale); + write_point3(fS, buffer); + } + + bool isEqual(const SkImageFilterLight& other) const override { + if (other.type() != kSpot_LightType) { + return false; + } + + const SkSpotLight& o = static_cast(other); + return INHERITED::isEqual(other) && + fLocation == o.fLocation && + fTarget == o.fTarget && + fSpecularExponent == o.fSpecularExponent && + fCosOuterConeAngle == o.fCosOuterConeAngle; + } + +private: + static const SkScalar kSpecularExponentMin; + static const SkScalar kSpecularExponentMax; + + SkPoint3 fLocation; + SkPoint3 fTarget; + SkScalar fSpecularExponent; + SkScalar fCosOuterConeAngle; + SkScalar fCosInnerConeAngle; + SkScalar fConeScale; + SkPoint3 fS; + + typedef SkImageFilterLight INHERITED; +}; + +// According to the spec, the specular term should be in the range [1, 128] : +// http://www.w3.org/TR/SVG/filters.html#feSpecularLightingSpecularExponentAttribute +const SkScalar SkSpotLight::kSpecularExponentMin = 1.0f; +const SkScalar SkSpotLight::kSpecularExponentMax = 128.0f; + +/////////////////////////////////////////////////////////////////////////////// + +void SkImageFilterLight::flattenLight(SkWriteBuffer& buffer) const { + // Write type first, then baseclass, then subclass. + buffer.writeInt(this->type()); + write_point3(fColor, buffer); + this->onFlattenLight(buffer); +} + +/*static*/ SkImageFilterLight* SkImageFilterLight::UnflattenLight(SkReadBuffer& buffer) { + SkImageFilterLight::LightType type = buffer.read32LE(SkImageFilterLight::kLast_LightType); + + switch (type) { + // Each of these constructors must first call SkLight's, so we'll read the baseclass + // then subclass, same order as flattenLight. + case SkImageFilterLight::kDistant_LightType: + return new SkDistantLight(buffer); + case SkImageFilterLight::kPoint_LightType: + return new SkPointLight(buffer); + case SkImageFilterLight::kSpot_LightType: + return new SkSpotLight(buffer); + default: + // Should never get here due to prior check of SkSafeRange + SkDEBUGFAIL("Unknown LightType."); + return nullptr; + } +} +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkLightingImageFilter::MakeDistantLitDiffuse( + const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkImageFilter::CropRect* cropRect) { + sk_sp light(new SkDistantLight(direction, lightColor)); + return SkDiffuseLightingImageFilter::Make(std::move(light), surfaceScale, kd, + std::move(input), cropRect); +} + +sk_sp SkLightingImageFilter::MakePointLitDiffuse( + const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkImageFilter::CropRect* cropRect) { + sk_sp light(new SkPointLight(location, lightColor)); + return SkDiffuseLightingImageFilter::Make(std::move(light), surfaceScale, kd, + std::move(input), cropRect); +} + +sk_sp SkLightingImageFilter::MakeSpotLitDiffuse( + const SkPoint3& location, const SkPoint3& target, SkScalar specularExponent, + SkScalar cutoffAngle, SkColor lightColor, SkScalar surfaceScale, SkScalar kd, + sk_sp input, const SkImageFilter::CropRect* cropRect) { + sk_sp light( + new SkSpotLight(location, target, specularExponent, cutoffAngle, lightColor)); + return SkDiffuseLightingImageFilter::Make(std::move(light), surfaceScale, kd, + std::move(input), cropRect); +} + +sk_sp SkLightingImageFilter::MakeDistantLitSpecular( + const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, const SkImageFilter::CropRect* cropRect) { + sk_sp light(new SkDistantLight(direction, lightColor)); + return SkSpecularLightingImageFilter::Make(std::move(light), surfaceScale, ks, shininess, + std::move(input), cropRect); +} + +sk_sp SkLightingImageFilter::MakePointLitSpecular( + const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, const SkImageFilter::CropRect* cropRect) { + sk_sp light(new SkPointLight(location, lightColor)); + return SkSpecularLightingImageFilter::Make(std::move(light), surfaceScale, ks, shininess, + std::move(input), cropRect); +} + +sk_sp SkLightingImageFilter::MakeSpotLitSpecular( + const SkPoint3& location, const SkPoint3& target, SkScalar specularExponent, + SkScalar cutoffAngle, SkColor lightColor, SkScalar surfaceScale, SkScalar ks, + SkScalar shininess, sk_sp input, const SkImageFilter::CropRect* cropRect) { + sk_sp light( + new SkSpotLight(location, target, specularExponent, cutoffAngle, lightColor)); + return SkSpecularLightingImageFilter::Make(std::move(light), surfaceScale, ks, shininess, + std::move(input), cropRect); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkDiffuseLightingImageFilter::Make(sk_sp light, + SkScalar surfaceScale, + SkScalar kd, + sk_sp input, + const CropRect* cropRect) { + if (!light) { + return nullptr; + } + if (!SkScalarIsFinite(surfaceScale) || !SkScalarIsFinite(kd)) { + return nullptr; + } + // According to the spec, kd can be any non-negative number : + // http://www.w3.org/TR/SVG/filters.html#feDiffuseLightingElement + if (kd < 0) { + return nullptr; + } + return sk_sp(new SkDiffuseLightingImageFilter(std::move(light), surfaceScale, + kd, std::move(input), cropRect)); +} + +SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(sk_sp light, + SkScalar surfaceScale, + SkScalar kd, + sk_sp input, + const CropRect* cropRect) + : INHERITED(std::move(light), surfaceScale, std::move(input), cropRect) + , fKD(kd) { +} + +sk_sp SkDiffuseLightingImageFilter::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + + sk_sp light(SkImageFilterLight::UnflattenLight(buffer)); + SkScalar surfaceScale = buffer.readScalar(); + SkScalar kd = buffer.readScalar(); + + return Make(std::move(light), surfaceScale, kd, common.getInput(0), &common.cropRect()); +} + +void SkDiffuseLightingImageFilter::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeScalar(fKD); +} + +sk_sp SkDiffuseLightingImageFilter::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + SkIRect bounds; + if (!this->applyCropRect(ctx, inputBounds, &bounds)) { + return nullptr; + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + bounds.offset(-inputOffset); + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + SkMatrix matrix(ctx.ctm()); + matrix.postTranslate(SkIntToScalar(-offset->fX), SkIntToScalar(-offset->fY)); + + return this->filterImageGPU(ctx, input.get(), bounds, matrix); + } +#endif + + if (bounds.width() < 2 || bounds.height() < 2) { + return nullptr; + } + + SkBitmap inputBM; + + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + if (!inputBM.getPixels()) { + return nullptr; + } + + const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + SkMatrix matrix(ctx.ctm()); + matrix.postTranslate(SkIntToScalar(-inputOffset.x()), SkIntToScalar(-inputOffset.y())); + + sk_sp transformedLight(light()->transform(matrix)); + + DiffuseLightingType lightingType(fKD); + lightBitmap(lightingType, + transformedLight.get(), + inputBM, + &dst, + surfaceScale(), + bounds); + + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), + dst); +} + +#if SK_SUPPORT_GPU +std::unique_ptr SkDiffuseLightingImageFilter::makeFragmentProcessor( + sk_sp proxy, + const SkMatrix& matrix, + const SkIRect* srcBounds, + BoundaryMode boundaryMode) const { + SkScalar scale = this->surfaceScale() * 255; + return GrDiffuseLightingEffect::Make(std::move(proxy), this->refLight(), scale, matrix, + this->kd(), boundaryMode, srcBounds); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkSpecularLightingImageFilter::Make(sk_sp light, + SkScalar surfaceScale, + SkScalar ks, + SkScalar shininess, + sk_sp input, + const CropRect* cropRect) { + if (!light) { + return nullptr; + } + if (!SkScalarIsFinite(surfaceScale) || !SkScalarIsFinite(ks) || !SkScalarIsFinite(shininess)) { + return nullptr; + } + // According to the spec, ks can be any non-negative number : + // http://www.w3.org/TR/SVG/filters.html#feSpecularLightingElement + if (ks < 0) { + return nullptr; + } + return sk_sp(new SkSpecularLightingImageFilter(std::move(light), surfaceScale, + ks, shininess, + std::move(input), cropRect)); +} + +SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(sk_sp light, + SkScalar surfaceScale, + SkScalar ks, + SkScalar shininess, + sk_sp input, + const CropRect* cropRect) + : INHERITED(std::move(light), surfaceScale, std::move(input), cropRect) + , fKS(ks) + , fShininess(shininess) { +} + +sk_sp SkSpecularLightingImageFilter::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + sk_sp light(SkImageFilterLight::UnflattenLight(buffer)); + SkScalar surfaceScale = buffer.readScalar(); + SkScalar ks = buffer.readScalar(); + SkScalar shine = buffer.readScalar(); + + return Make(std::move(light), surfaceScale, ks, shine, common.getInput(0), + &common.cropRect()); +} + +void SkSpecularLightingImageFilter::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeScalar(fKS); + buffer.writeScalar(fShininess); +} + +sk_sp SkSpecularLightingImageFilter::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + SkIRect bounds; + if (!this->applyCropRect(ctx, inputBounds, &bounds)) { + return nullptr; + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + bounds.offset(-inputOffset); + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + SkMatrix matrix(ctx.ctm()); + matrix.postTranslate(SkIntToScalar(-offset->fX), SkIntToScalar(-offset->fY)); + + return this->filterImageGPU(ctx, input.get(), bounds, matrix); + } +#endif + + if (bounds.width() < 2 || bounds.height() < 2) { + return nullptr; + } + + SkBitmap inputBM; + + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + if (!inputBM.getPixels()) { + return nullptr; + } + + const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + SpecularLightingType lightingType(fKS, fShininess); + + SkMatrix matrix(ctx.ctm()); + matrix.postTranslate(SkIntToScalar(-inputOffset.x()), SkIntToScalar(-inputOffset.y())); + + sk_sp transformedLight(light()->transform(matrix)); + + lightBitmap(lightingType, + transformedLight.get(), + inputBM, + &dst, + surfaceScale(), + bounds); + + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), dst); +} + +#if SK_SUPPORT_GPU +std::unique_ptr SkSpecularLightingImageFilter::makeFragmentProcessor( + sk_sp proxy, + const SkMatrix& matrix, + const SkIRect* srcBounds, + BoundaryMode boundaryMode) const { + SkScalar scale = this->surfaceScale() * 255; + return GrSpecularLightingEffect::Make(std::move(proxy), this->refLight(), scale, matrix, + this->ks(), this->shininess(), boundaryMode, srcBounds); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +static SkString emitNormalFunc(BoundaryMode mode, + const char* pointToNormalName, + const char* sobelFuncName) { + SkString result; + switch (mode) { + case kTopLeft_BoundaryMode: + result.printf("\treturn %s(%s(0.0, 0.0, m[4], m[5], m[7], m[8], %g),\n" + "\t %s(0.0, 0.0, m[4], m[7], m[5], m[8], %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gTwoThirds, + sobelFuncName, gTwoThirds); + break; + case kTop_BoundaryMode: + result.printf("\treturn %s(%s(0.0, 0.0, m[3], m[5], m[6], m[8], %g),\n" + "\t %s(0.0, 0.0, m[4], m[7], m[5], m[8], %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gOneThird, + sobelFuncName, gOneHalf); + break; + case kTopRight_BoundaryMode: + result.printf("\treturn %s(%s( 0.0, 0.0, m[3], m[4], m[6], m[7], %g),\n" + "\t %s(m[3], m[6], m[4], m[7], 0.0, 0.0, %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gTwoThirds, + sobelFuncName, gTwoThirds); + break; + case kLeft_BoundaryMode: + result.printf("\treturn %s(%s(m[1], m[2], m[4], m[5], m[7], m[8], %g),\n" + "\t %s( 0.0, 0.0, m[1], m[7], m[2], m[8], %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gOneHalf, + sobelFuncName, gOneThird); + break; + case kInterior_BoundaryMode: + result.printf("\treturn %s(%s(m[0], m[2], m[3], m[5], m[6], m[8], %g),\n" + "\t %s(m[0], m[6], m[1], m[7], m[2], m[8], %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gOneQuarter, + sobelFuncName, gOneQuarter); + break; + case kRight_BoundaryMode: + result.printf("\treturn %s(%s(m[0], m[1], m[3], m[4], m[6], m[7], %g),\n" + "\t %s(m[0], m[6], m[1], m[7], 0.0, 0.0, %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gOneHalf, + sobelFuncName, gOneThird); + break; + case kBottomLeft_BoundaryMode: + result.printf("\treturn %s(%s(m[1], m[2], m[4], m[5], 0.0, 0.0, %g),\n" + "\t %s( 0.0, 0.0, m[1], m[4], m[2], m[5], %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gTwoThirds, + sobelFuncName, gTwoThirds); + break; + case kBottom_BoundaryMode: + result.printf("\treturn %s(%s(m[0], m[2], m[3], m[5], 0.0, 0.0, %g),\n" + "\t %s(m[0], m[3], m[1], m[4], m[2], m[5], %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gOneThird, + sobelFuncName, gOneHalf); + break; + case kBottomRight_BoundaryMode: + result.printf("\treturn %s(%s(m[0], m[1], m[3], m[4], 0.0, 0.0, %g),\n" + "\t %s(m[0], m[3], m[1], m[4], 0.0, 0.0, %g),\n" + "\t surfaceScale);\n", + pointToNormalName, sobelFuncName, gTwoThirds, + sobelFuncName, gTwoThirds); + break; + default: + SkASSERT(false); + break; + } + return result; +} + +class GrGLLightingEffect : public GrGLSLFragmentProcessor { +public: + GrGLLightingEffect() : fLight(nullptr) { } + ~GrGLLightingEffect() override { delete fLight; } + + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder* b); + +protected: + /** + * Subclasses of GrGLLightingEffect must call INHERITED::onSetData(); + */ + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + virtual void emitLightFunc(GrGLSLUniformHandler*, + GrGLSLFPFragmentBuilder*, + SkString* funcName) = 0; + +private: + typedef GrGLSLFragmentProcessor INHERITED; + + UniformHandle fImageIncrementUni; + UniformHandle fSurfaceScaleUni; + GrTextureDomain::GLDomain fDomain; + GrGLLight* fLight; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLDiffuseLightingEffect : public GrGLLightingEffect { +public: + void emitLightFunc(GrGLSLUniformHandler*, GrGLSLFPFragmentBuilder*, SkString* funcName) override; + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + typedef GrGLLightingEffect INHERITED; + + UniformHandle fKDUni; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLSpecularLightingEffect : public GrGLLightingEffect { +public: + void emitLightFunc(GrGLSLUniformHandler*, GrGLSLFPFragmentBuilder*, SkString* funcName) override; + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + typedef GrGLLightingEffect INHERITED; + + UniformHandle fKSUni; + UniformHandle fShininessUni; +}; + +/////////////////////////////////////////////////////////////////////////////// + +static GrTextureDomain create_domain(GrTextureProxy* proxy, const SkIRect* srcBounds, + GrTextureDomain::Mode mode) { + if (srcBounds) { + SkRect texelDomain = GrTextureDomain::MakeTexelDomain(*srcBounds, mode); + return GrTextureDomain(proxy, texelDomain, mode, mode); + } else { + return GrTextureDomain::IgnoredDomain(); + } +} + +GrLightingEffect::GrLightingEffect(ClassID classID, + sk_sp proxy, + sk_sp light, + SkScalar surfaceScale, + const SkMatrix& matrix, + BoundaryMode boundaryMode, + const SkIRect* srcBounds) + // Perhaps this could advertise the opaque or coverage-as-alpha optimizations? + : INHERITED(classID, kNone_OptimizationFlags) + , fCoordTransform(proxy.get()) + , fDomain(create_domain(proxy.get(), srcBounds, GrTextureDomain::kDecal_Mode)) + , fTextureSampler(std::move(proxy)) + , fLight(std::move(light)) + , fSurfaceScale(surfaceScale) + , fFilterMatrix(matrix) + , fBoundaryMode(boundaryMode) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); +} + +GrLightingEffect::GrLightingEffect(const GrLightingEffect& that) + : INHERITED(that.classID(), that.optimizationFlags()) + , fCoordTransform(that.fCoordTransform) + , fDomain(that.fDomain) + , fTextureSampler(that.fTextureSampler) + , fLight(that.fLight) + , fSurfaceScale(that.fSurfaceScale) + , fFilterMatrix(that.fFilterMatrix) + , fBoundaryMode(that.fBoundaryMode) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); +} + +bool GrLightingEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrLightingEffect& s = sBase.cast(); + return fLight->isEqual(*s.fLight) && + fSurfaceScale == s.fSurfaceScale && + fBoundaryMode == s.fBoundaryMode; +} + +/////////////////////////////////////////////////////////////////////////////// + +GrDiffuseLightingEffect::GrDiffuseLightingEffect(sk_sp proxy, + sk_splight, + SkScalar surfaceScale, + const SkMatrix& matrix, + SkScalar kd, + BoundaryMode boundaryMode, + const SkIRect* srcBounds) + : INHERITED(kGrDiffuseLightingEffect_ClassID, std::move(proxy), std::move(light), + surfaceScale, matrix, boundaryMode, srcBounds) + , fKD(kd) {} + +GrDiffuseLightingEffect::GrDiffuseLightingEffect(const GrDiffuseLightingEffect& that) + : INHERITED(that), fKD(that.fKD) {} + +bool GrDiffuseLightingEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrDiffuseLightingEffect& s = sBase.cast(); + return INHERITED::onIsEqual(sBase) && this->kd() == s.kd(); +} + +void GrDiffuseLightingEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLDiffuseLightingEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrDiffuseLightingEffect::onCreateGLSLInstance() const { + return new GrGLDiffuseLightingEffect; +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDiffuseLightingEffect); + +#if GR_TEST_UTILS + +static SkPoint3 random_point3(SkRandom* random) { + return SkPoint3::Make(SkScalarToFloat(random->nextSScalar1()), + SkScalarToFloat(random->nextSScalar1()), + SkScalarToFloat(random->nextSScalar1())); +} + +static SkImageFilterLight* create_random_light(SkRandom* random) { + int type = random->nextULessThan(3); + switch (type) { + case 0: { + return new SkDistantLight(random_point3(random), random->nextU()); + } + case 1: { + return new SkPointLight(random_point3(random), random->nextU()); + } + case 2: { + return new SkSpotLight(random_point3(random), random_point3(random), + random->nextUScalar1(), random->nextUScalar1(), random->nextU()); + } + default: + SK_ABORT("Unexpected value."); + } +} + +std::unique_ptr GrDiffuseLightingEffect::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + SkScalar surfaceScale = d->fRandom->nextSScalar1(); + SkScalar kd = d->fRandom->nextUScalar1(); + sk_sp light(create_random_light(d->fRandom)); + SkMatrix matrix; + for (int i = 0; i < 9; i++) { + matrix[i] = d->fRandom->nextUScalar1(); + } + SkIRect srcBounds = SkIRect::MakeXYWH(d->fRandom->nextRangeU(0, proxy->width()), + d->fRandom->nextRangeU(0, proxy->height()), + d->fRandom->nextRangeU(0, proxy->width()), + d->fRandom->nextRangeU(0, proxy->height())); + BoundaryMode mode = static_cast(d->fRandom->nextU() % kBoundaryModeCount); + return GrDiffuseLightingEffect::Make(std::move(proxy), std::move(light), surfaceScale, matrix, + kd, mode, &srcBounds); +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLLightingEffect::emitCode(EmitArgs& args) { + const GrLightingEffect& le = args.fFp.cast(); + if (!fLight) { + fLight = le.light()->createGLLight(); + } + + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf2_GrSLType, "ImageIncrement"); + fSurfaceScaleUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf_GrSLType, "SurfaceScale"); + fLight->emitLightColorUniform(uniformHandler); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString lightFunc; + this->emitLightFunc(uniformHandler, fragBuilder, &lightFunc); + const GrShaderVar gSobelArgs[] = { + GrShaderVar("a", kHalf_GrSLType), + GrShaderVar("b", kHalf_GrSLType), + GrShaderVar("c", kHalf_GrSLType), + GrShaderVar("d", kHalf_GrSLType), + GrShaderVar("e", kHalf_GrSLType), + GrShaderVar("f", kHalf_GrSLType), + GrShaderVar("scale", kHalf_GrSLType), + }; + SkString sobelFuncName; + SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + + fragBuilder->emitFunction(kHalf_GrSLType, + "sobel", + SK_ARRAY_COUNT(gSobelArgs), + gSobelArgs, + "\treturn (-a + b - 2.0 * c + 2.0 * d -e + f) * scale;\n", + &sobelFuncName); + const GrShaderVar gPointToNormalArgs[] = { + GrShaderVar("x", kHalf_GrSLType), + GrShaderVar("y", kHalf_GrSLType), + GrShaderVar("scale", kHalf_GrSLType), + }; + SkString pointToNormalName; + fragBuilder->emitFunction(kHalf3_GrSLType, + "pointToNormal", + SK_ARRAY_COUNT(gPointToNormalArgs), + gPointToNormalArgs, + "\treturn normalize(half3(-x * scale, -y * scale, 1));\n", + &pointToNormalName); + + const GrShaderVar gInteriorNormalArgs[] = { + GrShaderVar("m", kHalf_GrSLType, 9), + GrShaderVar("surfaceScale", kHalf_GrSLType), + }; + SkString normalBody = emitNormalFunc(le.boundaryMode(), + pointToNormalName.c_str(), + sobelFuncName.c_str()); + SkString normalName; + fragBuilder->emitFunction(kHalf3_GrSLType, + "normal", + SK_ARRAY_COUNT(gInteriorNormalArgs), + gInteriorNormalArgs, + normalBody.c_str(), + &normalName); + + fragBuilder->codeAppendf("\t\tfloat2 coord = %s;\n", coords2D.c_str()); + fragBuilder->codeAppend("\t\thalf m[9];\n"); + + const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni); + const char* surfScale = uniformHandler->getUniformCStr(fSurfaceScaleUni); + + int index = 0; + for (int dy = 1; dy >= -1; dy--) { + for (int dx = -1; dx <= 1; dx++) { + SkString texCoords; + texCoords.appendf("coord + half2(%d, %d) * %s", dx, dy, imgInc); + SkString temp; + temp.appendf("temp%d", index); + fragBuilder->codeAppendf("half4 %s;", temp.c_str()); + fDomain.sampleTexture(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + le.domain(), + temp.c_str(), + texCoords, + args.fTexSamplers[0]); + fragBuilder->codeAppendf("m[%d] = %s.a;", index, temp.c_str()); + index++; + } + } + fragBuilder->codeAppend("\t\thalf3 surfaceToLight = "); + SkString arg; + arg.appendf("%s * m[4]", surfScale); + fLight->emitSurfaceToLight(uniformHandler, fragBuilder, arg.c_str()); + fragBuilder->codeAppend(";\n"); + fragBuilder->codeAppendf("\t\t%s = %s(%s(m, %s), surfaceToLight, ", + args.fOutputColor, lightFunc.c_str(), normalName.c_str(), surfScale); + fLight->emitLightColor(uniformHandler, fragBuilder, "surfaceToLight"); + fragBuilder->codeAppend(");\n"); + fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor); +} + +void GrGLLightingEffect::GenKey(const GrProcessor& proc, + const GrShaderCaps& caps, GrProcessorKeyBuilder* b) { + const GrLightingEffect& lighting = proc.cast(); + b->add32(lighting.boundaryMode() << 2 | lighting.light()->type()); + b->add32(GrTextureDomain::GLDomain::DomainKey(lighting.domain())); +} + +void GrGLLightingEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) { + const GrLightingEffect& lighting = proc.cast(); + if (!fLight) { + fLight = lighting.light()->createGLLight(); + } + + GrTextureProxy* proxy = lighting.textureSampler(0).proxy(); + GrTexture* texture = proxy->peekTexture(); + + float ySign = proxy->origin() == kTopLeft_GrSurfaceOrigin ? -1.0f : 1.0f; + pdman.set2f(fImageIncrementUni, 1.0f / texture->width(), ySign / texture->height()); + pdman.set1f(fSurfaceScaleUni, lighting.surfaceScale()); + sk_sp transformedLight( + lighting.light()->transform(lighting.filterMatrix())); + fDomain.setData(pdman, lighting.domain(), proxy, lighting.textureSampler(0).samplerState()); + fLight->setData(pdman, transformedLight.get()); +} + +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLDiffuseLightingEffect::emitLightFunc(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + SkString* funcName) { + const char* kd; + fKDUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "KD", &kd); + + const GrShaderVar gLightArgs[] = { + GrShaderVar("normal", kHalf3_GrSLType), + GrShaderVar("surfaceToLight", kHalf3_GrSLType), + GrShaderVar("lightColor", kHalf3_GrSLType) + }; + SkString lightBody; + lightBody.appendf("\thalf colorScale = %s * dot(normal, surfaceToLight);\n", kd); + lightBody.appendf("\treturn half4(lightColor * saturate(colorScale), 1.0);\n"); + fragBuilder->emitFunction(kHalf4_GrSLType, + "light", + SK_ARRAY_COUNT(gLightArgs), + gLightArgs, + lightBody.c_str(), + funcName); +} + +void GrGLDiffuseLightingEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) { + INHERITED::onSetData(pdman, proc); + const GrDiffuseLightingEffect& diffuse = proc.cast(); + pdman.set1f(fKDUni, diffuse.kd()); +} + +/////////////////////////////////////////////////////////////////////////////// + +GrSpecularLightingEffect::GrSpecularLightingEffect(sk_sp proxy, + sk_sp light, + SkScalar surfaceScale, + const SkMatrix& matrix, + SkScalar ks, + SkScalar shininess, + BoundaryMode boundaryMode, + const SkIRect* srcBounds) + : INHERITED(kGrSpecularLightingEffect_ClassID, std::move(proxy), std::move(light), + surfaceScale, matrix, boundaryMode, srcBounds) + , fKS(ks) + , fShininess(shininess) {} + +GrSpecularLightingEffect::GrSpecularLightingEffect(const GrSpecularLightingEffect& that) + : INHERITED(that), fKS(that.fKS), fShininess(that.fShininess) {} + +bool GrSpecularLightingEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrSpecularLightingEffect& s = sBase.cast(); + return INHERITED::onIsEqual(sBase) && + this->ks() == s.ks() && + this->shininess() == s.shininess(); +} + +void GrSpecularLightingEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLSpecularLightingEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrSpecularLightingEffect::onCreateGLSLInstance() const { + return new GrGLSpecularLightingEffect; +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSpecularLightingEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrSpecularLightingEffect::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + SkScalar surfaceScale = d->fRandom->nextSScalar1(); + SkScalar ks = d->fRandom->nextUScalar1(); + SkScalar shininess = d->fRandom->nextUScalar1(); + sk_sp light(create_random_light(d->fRandom)); + SkMatrix matrix; + for (int i = 0; i < 9; i++) { + matrix[i] = d->fRandom->nextUScalar1(); + } + BoundaryMode mode = static_cast(d->fRandom->nextU() % kBoundaryModeCount); + SkIRect srcBounds = SkIRect::MakeXYWH(d->fRandom->nextRangeU(0, proxy->width()), + d->fRandom->nextRangeU(0, proxy->height()), + d->fRandom->nextRangeU(0, proxy->width()), + d->fRandom->nextRangeU(0, proxy->height())); + return GrSpecularLightingEffect::Make(std::move(proxy), std::move(light), surfaceScale, matrix, + ks, shininess, mode, &srcBounds); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLSpecularLightingEffect::emitLightFunc(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + SkString* funcName) { + const char* ks; + const char* shininess; + + fKSUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "KS", &ks); + fShininessUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf_GrSLType, + "Shininess", + &shininess); + + const GrShaderVar gLightArgs[] = { + GrShaderVar("normal", kHalf3_GrSLType), + GrShaderVar("surfaceToLight", kHalf3_GrSLType), + GrShaderVar("lightColor", kHalf3_GrSLType) + }; + SkString lightBody; + lightBody.appendf("\thalf3 halfDir = half3(normalize(surfaceToLight + half3(0, 0, 1)));\n"); + lightBody.appendf("\thalf colorScale = half(%s * pow(dot(normal, halfDir), %s));\n", + ks, shininess); + lightBody.appendf("\thalf3 color = lightColor * saturate(colorScale);\n"); + lightBody.appendf("\treturn half4(color, max(max(color.r, color.g), color.b));\n"); + fragBuilder->emitFunction(kHalf4_GrSLType, + "light", + SK_ARRAY_COUNT(gLightArgs), + gLightArgs, + lightBody.c_str(), + funcName); +} + +void GrGLSpecularLightingEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& effect) { + INHERITED::onSetData(pdman, effect); + const GrSpecularLightingEffect& spec = effect.cast(); + pdman.set1f(fKSUni, spec.ks()); + pdman.set1f(fShininessUni, spec.shininess()); +} + +/////////////////////////////////////////////////////////////////////////////// +void GrGLLight::emitLightColorUniform(GrGLSLUniformHandler* uniformHandler) { + fColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, "LightColor"); +} + +void GrGLLight::emitLightColor(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + const char *surfaceToLight) { + fragBuilder->codeAppend(uniformHandler->getUniformCStr(this->lightColorUni())); +} + +void GrGLLight::setData(const GrGLSLProgramDataManager& pdman, + const SkImageFilterLight* light) const { + setUniformPoint3(pdman, fColorUni, + light->color().makeScale(SkScalarInvert(SkIntToScalar(255)))); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLDistantLight::setData(const GrGLSLProgramDataManager& pdman, + const SkImageFilterLight* light) const { + INHERITED::setData(pdman, light); + SkASSERT(light->type() == SkImageFilterLight::kDistant_LightType); + const SkDistantLight* distantLight = static_cast(light); + setUniformNormal3(pdman, fDirectionUni, distantLight->direction()); +} + +void GrGLDistantLight::emitSurfaceToLight(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + const char* z) { + const char* dir; + fDirectionUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, + "LightDirection", &dir); + fragBuilder->codeAppend(dir); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLPointLight::setData(const GrGLSLProgramDataManager& pdman, + const SkImageFilterLight* light) const { + INHERITED::setData(pdman, light); + SkASSERT(light->type() == SkImageFilterLight::kPoint_LightType); + const SkPointLight* pointLight = static_cast(light); + setUniformPoint3(pdman, fLocationUni, pointLight->location()); +} + +void GrGLPointLight::emitSurfaceToLight(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + const char* z) { + const char* loc; + fLocationUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, + "LightLocation", &loc); + fragBuilder->codeAppendf("normalize(%s - half3(sk_FragCoord.xy, %s))", + loc, z); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLSpotLight::setData(const GrGLSLProgramDataManager& pdman, + const SkImageFilterLight* light) const { + INHERITED::setData(pdman, light); + SkASSERT(light->type() == SkImageFilterLight::kSpot_LightType); + const SkSpotLight* spotLight = static_cast(light); + setUniformPoint3(pdman, fLocationUni, spotLight->location()); + pdman.set1f(fExponentUni, spotLight->specularExponent()); + pdman.set1f(fCosInnerConeAngleUni, spotLight->cosInnerConeAngle()); + pdman.set1f(fCosOuterConeAngleUni, spotLight->cosOuterConeAngle()); + pdman.set1f(fConeScaleUni, spotLight->coneScale()); + setUniformNormal3(pdman, fSUni, spotLight->s()); +} + +void GrGLSpotLight::emitSurfaceToLight(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + const char* z) { + const char* location; + fLocationUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, + "LightLocation", &location); + + fragBuilder->codeAppendf("normalize(%s - half3(sk_FragCoord.xy, %s))", + location, z); +} + +void GrGLSpotLight::emitLightColor(GrGLSLUniformHandler* uniformHandler, + GrGLSLFPFragmentBuilder* fragBuilder, + const char *surfaceToLight) { + + const char* color = uniformHandler->getUniformCStr(this->lightColorUni()); // created by parent class. + + const char* exponent; + const char* cosInner; + const char* cosOuter; + const char* coneScale; + const char* s; + fExponentUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "Exponent", &exponent); + fCosInnerConeAngleUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "CosInnerConeAngle", &cosInner); + fCosOuterConeAngleUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "CosOuterConeAngle", &cosOuter); + fConeScaleUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "ConeScale", &coneScale); + fSUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, "S", &s); + + const GrShaderVar gLightColorArgs[] = { + GrShaderVar("surfaceToLight", kHalf3_GrSLType) + }; + SkString lightColorBody; + lightColorBody.appendf("\thalf cosAngle = -dot(surfaceToLight, %s);\n", s); + lightColorBody.appendf("\tif (cosAngle < %s) {\n", cosOuter); + lightColorBody.appendf("\t\treturn half3(0);\n"); + lightColorBody.appendf("\t}\n"); + lightColorBody.appendf("\thalf scale = pow(cosAngle, %s);\n", exponent); + lightColorBody.appendf("\tif (cosAngle < %s) {\n", cosInner); + lightColorBody.appendf("\t\treturn %s * scale * (cosAngle - %s) * %s;\n", + color, cosOuter, coneScale); + lightColorBody.appendf("\t}\n"); + lightColorBody.appendf("\treturn %s;\n", color); + fragBuilder->emitFunction(kHalf3_GrSLType, + "lightColor", + SK_ARRAY_COUNT(gLightColorArgs), + gLightColorArgs, + lightColorBody.c_str(), + &fLightColorFunc); + + fragBuilder->codeAppendf("%s(%s)", fLightColorFunc.c_str(), surfaceToLight); +} + +#endif + +void SkLightingImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkDiffuseLightingImageFilter); + SK_REGISTER_FLATTENABLE(SkSpecularLightingImageFilter); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkMagnifierImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkMagnifierImageFilter.cpp new file mode 100644 index 0000000000..8e360932ab --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkMagnifierImageFilter.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkMagnifierImageFilter.h" + +#include "include/core/SkBitmap.h" +#include "include/private/SkColorData.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkValidationUtils.h" +#include "src/core/SkWriteBuffer.h" + +//////////////////////////////////////////////////////////////////////////////// +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/effects/generated/GrMagnifierEffect.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#endif + +namespace { + +class SkMagnifierImageFilterImpl final : public SkImageFilter_Base { +public: + SkMagnifierImageFilterImpl(const SkRect& srcRect, SkScalar inset, sk_sp input, + const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fSrcRect(srcRect) + , fInset(inset) { + SkASSERT(srcRect.left() >= 0 && srcRect.top() >= 0 && inset >= 0); + } + +protected: + void flatten(SkWriteBuffer&) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +private: + friend void SkMagnifierImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkMagnifierImageFilterImpl) + + SkRect fSrcRect; + SkScalar fInset; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkMagnifierImageFilter::Make(const SkRect& srcRect, SkScalar inset, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + if (!SkScalarIsFinite(inset) || !SkIsValidRect(srcRect)) { + return nullptr; + } + if (inset < 0) { + return nullptr; + } + // Negative numbers in src rect are not supported + if (srcRect.fLeft < 0 || srcRect.fTop < 0) { + return nullptr; + } + return sk_sp(new SkMagnifierImageFilterImpl(srcRect, inset, std::move(input), + cropRect)); +} + +void SkMagnifierImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkMagnifierImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkMagnifierImageFilter", SkMagnifierImageFilterImpl::CreateProc); +} + +//////////////////////////////////////////////////////////////////////////////// + +sk_sp SkMagnifierImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkRect src; + buffer.readRect(&src); + return SkMagnifierImageFilter::Make(src, buffer.readScalar(), common.getInput(0), + &common.cropRect()); +} + +void SkMagnifierImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeRect(fSrcRect); + buffer.writeScalar(fInset); +} + +sk_sp SkMagnifierImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), + input->width(), input->height()); + + SkIRect bounds; + if (!this->applyCropRect(ctx, inputBounds, &bounds)) { + return nullptr; + } + + SkScalar invInset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; + + SkScalar invXZoom = fSrcRect.width() / bounds.width(); + SkScalar invYZoom = fSrcRect.height() / bounds.height(); + + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + auto context = ctx.getContext(); + + sk_sp inputProxy(input->asTextureProxyRef(context)); + SkASSERT(inputProxy); + + const auto isProtected = inputProxy->isProtected(); + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + bounds.offset(-inputOffset); + + // Map bounds and srcRect into the proxy space. Due to the zoom effect, + // it's not just an offset for fSrcRect. + bounds.offset(input->subset().x(), input->subset().y()); + SkRect srcRect = fSrcRect.makeOffset((1.f - invXZoom) * input->subset().x(), + (1.f - invYZoom) * input->subset().y()); + + auto fp = GrMagnifierEffect::Make(std::move(inputProxy), + bounds, + srcRect, + invXZoom, + invYZoom, + bounds.width() * invInset, + bounds.height() * invInset); + fp = GrColorSpaceXformEffect::Make(std::move(fp), input->getColorSpace(), + input->alphaType(), ctx.colorSpace()); + if (!fp) { + return nullptr; + } + + return DrawWithFP(context, std::move(fp), bounds, ctx.colorType(), ctx.colorSpace(), + isProtected ? GrProtected::kYes : GrProtected::kNo); + } +#endif + + SkBitmap inputBM; + + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if ((inputBM.colorType() != kN32_SkColorType) || + (fSrcRect.width() >= inputBM.width()) || (fSrcRect.height() >= inputBM.height())) { + return nullptr; + } + + SkASSERT(inputBM.getPixels()); + if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) { + return nullptr; + } + + const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + SkColor* dptr = dst.getAddr32(0, 0); + int dstWidth = dst.width(), dstHeight = dst.height(); + for (int y = 0; y < dstHeight; ++y) { + for (int x = 0; x < dstWidth; ++x) { + SkScalar x_dist = SkMin32(x, dstWidth - x - 1) * invInset; + SkScalar y_dist = SkMin32(y, dstHeight - y - 1) * invInset; + SkScalar weight = 0; + + static const SkScalar kScalar2 = SkScalar(2); + + // To create a smooth curve at the corners, we need to work on + // a square twice the size of the inset. + if (x_dist < kScalar2 && y_dist < kScalar2) { + x_dist = kScalar2 - x_dist; + y_dist = kScalar2 - y_dist; + + SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) + + SkScalarSquare(y_dist)); + dist = SkMaxScalar(kScalar2 - dist, 0); + weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1); + } else { + SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist), + SkScalarSquare(y_dist)); + weight = SkMinScalar(sqDist, SK_Scalar1); + } + + SkScalar x_interp = weight * (fSrcRect.x() + x * invXZoom) + (1 - weight) * x; + SkScalar y_interp = weight * (fSrcRect.y() + y * invYZoom) + (1 - weight) * y; + + int x_val = SkTPin(bounds.x() + SkScalarFloorToInt(x_interp), 0, inputBM.width() - 1); + int y_val = SkTPin(bounds.y() + SkScalarFloorToInt(y_interp), 0, inputBM.height() - 1); + + *dptr = *inputBM.getAddr32(x_val, y_val); + dptr++; + } + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), + dst); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp new file mode 100644 index 0000000000..611f02703b --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp @@ -0,0 +1,574 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkMatrixConvolutionImageFilter.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkRect.h" +#include "include/core/SkTileMode.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/GrMatrixConvolutionEffect.h" +#endif + +namespace { + +class SkMatrixConvolutionImageFilterImpl final : public SkImageFilter_Base { +public: + SkMatrixConvolutionImageFilterImpl(const SkISize& kernelSize, const SkScalar* kernel, + SkScalar gain, SkScalar bias, const SkIPoint& kernelOffset, + SkTileMode tileMode, bool convolveAlpha, + sk_sp input, const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fKernelSize(kernelSize) + , fGain(gain) + , fBias(bias) + , fKernelOffset(kernelOffset) + , fTileMode(tileMode) + , fConvolveAlpha(convolveAlpha) { + size_t size = (size_t) sk_64_mul(fKernelSize.width(), fKernelSize.height()); + fKernel = new SkScalar[size]; + memcpy(fKernel, kernel, size * sizeof(SkScalar)); + SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1); + SkASSERT(kernelOffset.fX >= 0 && kernelOffset.fX < kernelSize.fWidth); + SkASSERT(kernelOffset.fY >= 0 && kernelOffset.fY < kernelSize.fHeight); + } + + ~SkMatrixConvolutionImageFilterImpl() override { + delete[] fKernel; + } + +protected: + + void flatten(SkWriteBuffer&) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + bool affectsTransparentBlack() const override; + +private: + friend void SkMatrixConvolutionImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkMatrixConvolutionImageFilterImpl) + + SkISize fKernelSize; + SkScalar* fKernel; + SkScalar fGain; + SkScalar fBias; + SkIPoint fKernelOffset; + SkTileMode fTileMode; + bool fConvolveAlpha; + + template + void filterPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& bounds) const; + template + void filterPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& bounds) const; + void filterInteriorPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& bounds) const; + void filterBorderPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& bounds) const; + + typedef SkImageFilter_Base INHERITED; +}; + +class UncheckedPixelFetcher { +public: + static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { + return *src.getAddr32(x, y); + } +}; + +class ClampPixelFetcher { +public: + static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { + x = SkTPin(x, bounds.fLeft, bounds.fRight - 1); + y = SkTPin(y, bounds.fTop, bounds.fBottom - 1); + return *src.getAddr32(x, y); + } +}; + +class RepeatPixelFetcher { +public: + static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { + x = (x - bounds.left()) % bounds.width() + bounds.left(); + y = (y - bounds.top()) % bounds.height() + bounds.top(); + if (x < bounds.left()) { + x += bounds.width(); + } + if (y < bounds.top()) { + y += bounds.height(); + } + return *src.getAddr32(x, y); + } +}; + +class ClampToBlackPixelFetcher { +public: + static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { + if (x < bounds.fLeft || x >= bounds.fRight || y < bounds.fTop || y >= bounds.fBottom) { + return 0; + } else { + return *src.getAddr32(x, y); + } + } +}; + +} // end namespace + +static SkTileMode to_sktilemode(SkMatrixConvolutionImageFilter::TileMode tileMode) { + switch(tileMode) { + case SkMatrixConvolutionImageFilter::kClamp_TileMode: + return SkTileMode::kClamp; + case SkMatrixConvolutionImageFilter::kRepeat_TileMode: + return SkTileMode::kRepeat; + case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode: + // Fall through + default: + return SkTileMode::kDecal; + } +} + +sk_sp SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + TileMode tileMode, + bool convolveAlpha, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + return Make(kernelSize, kernel, gain, bias, kernelOffset, to_sktilemode(tileMode), + convolveAlpha, std::move(input), cropRect); +} + +sk_sp SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + SkTileMode tileMode, + bool convolveAlpha, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + // We need to be able to read at most SK_MaxS32 bytes, so divide that + // by the size of a scalar to know how many scalars we can read. + static constexpr int32_t kMaxKernelSize = SK_MaxS32 / sizeof(SkScalar); + + if (kernelSize.width() < 1 || kernelSize.height() < 1) { + return nullptr; + } + if (kMaxKernelSize / kernelSize.fWidth < kernelSize.fHeight) { + return nullptr; + } + if (!kernel) { + return nullptr; + } + if ((kernelOffset.fX < 0) || (kernelOffset.fX >= kernelSize.fWidth) || + (kernelOffset.fY < 0) || (kernelOffset.fY >= kernelSize.fHeight)) { + return nullptr; + } + return sk_sp(new SkMatrixConvolutionImageFilterImpl( + kernelSize, kernel, gain, bias, kernelOffset, tileMode, convolveAlpha, + std::move(input), cropRect)); +} + +void SkMatrixConvolutionImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkMatrixConvolutionImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkMatrixConvolutionImageFilter", + SkMatrixConvolutionImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkMatrixConvolutionImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + + SkISize kernelSize; + kernelSize.fWidth = buffer.readInt(); + kernelSize.fHeight = buffer.readInt(); + const int count = buffer.getArrayCount(); + + const int64_t kernelArea = sk_64_mul(kernelSize.width(), kernelSize.height()); + if (!buffer.validate(kernelArea == count)) { + return nullptr; + } + if (!buffer.validateCanReadN(count)) { + return nullptr; + } + SkAutoSTArray<16, SkScalar> kernel(count); + if (!buffer.readScalarArray(kernel.get(), count)) { + return nullptr; + } + SkScalar gain = buffer.readScalar(); + SkScalar bias = buffer.readScalar(); + SkIPoint kernelOffset; + kernelOffset.fX = buffer.readInt(); + kernelOffset.fY = buffer.readInt(); + + SkTileMode tileMode; + if (buffer.isVersionLT(SkPicturePriv::kCleanupImageFilterEnums_Version)) { + tileMode = to_sktilemode(buffer.read32LE(SkMatrixConvolutionImageFilter::kLast_TileMode)); + } else { + tileMode = buffer.read32LE(SkTileMode::kLastTileMode); + } + bool convolveAlpha = buffer.readBool(); + + if (!buffer.isValid()) { + return nullptr; + } + return SkMatrixConvolutionImageFilter::Make( + kernelSize, kernel.get(), gain, bias, kernelOffset, tileMode, + convolveAlpha, common.getInput(0), &common.cropRect()); +} + +void SkMatrixConvolutionImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeInt(fKernelSize.fWidth); + buffer.writeInt(fKernelSize.fHeight); + buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight); + buffer.writeScalar(fGain); + buffer.writeScalar(fBias); + buffer.writeInt(fKernelOffset.fX); + buffer.writeInt(fKernelOffset.fY); + buffer.writeInt((int) fTileMode); + buffer.writeBool(fConvolveAlpha); +} + +template +void SkMatrixConvolutionImageFilterImpl::filterPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& r, + const SkIRect& bounds) const { + SkIRect rect(r); + if (!rect.intersect(bounds)) { + return; + } + for (int y = rect.fTop; y < rect.fBottom; ++y) { + SkPMColor* dptr = result->getAddr32(rect.fLeft - offset.fX, y - offset.fY); + for (int x = rect.fLeft; x < rect.fRight; ++x) { + SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0; + for (int cy = 0; cy < fKernelSize.fHeight; cy++) { + for (int cx = 0; cx < fKernelSize.fWidth; cx++) { + SkPMColor s = PixelFetcher::fetch(src, + x + cx - fKernelOffset.fX, + y + cy - fKernelOffset.fY, + bounds); + SkScalar k = fKernel[cy * fKernelSize.fWidth + cx]; + if (convolveAlpha) { + sumA += SkGetPackedA32(s) * k; + } + sumR += SkGetPackedR32(s) * k; + sumG += SkGetPackedG32(s) * k; + sumB += SkGetPackedB32(s) * k; + } + } + int a = convolveAlpha + ? SkClampMax(SkScalarFloorToInt(sumA * fGain + fBias), 255) + : 255; + int r = SkClampMax(SkScalarFloorToInt(sumR * fGain + fBias), a); + int g = SkClampMax(SkScalarFloorToInt(sumG * fGain + fBias), a); + int b = SkClampMax(SkScalarFloorToInt(sumB * fGain + fBias), a); + if (!convolveAlpha) { + a = SkGetPackedA32(PixelFetcher::fetch(src, x, y, bounds)); + *dptr++ = SkPreMultiplyARGB(a, r, g, b); + } else { + *dptr++ = SkPackARGB32(a, r, g, b); + } + } + } +} + +template +void SkMatrixConvolutionImageFilterImpl::filterPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& bounds) const { + if (fConvolveAlpha) { + filterPixels(src, result, offset, rect, bounds); + } else { + filterPixels(src, result, offset, rect, bounds); + } +} + +void SkMatrixConvolutionImageFilterImpl::filterInteriorPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& bounds) const { + switch (fTileMode) { + case SkTileMode::kMirror: + // TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now. + case SkTileMode::kRepeat: + // In repeat mode, we still need to wrap the samples around the src + filterPixels(src, result, offset, rect, bounds); + break; + case SkTileMode::kClamp: + // Fall through + case SkTileMode::kDecal: + filterPixels(src, result, offset, rect, bounds); + break; + } +} + +void SkMatrixConvolutionImageFilterImpl::filterBorderPixels(const SkBitmap& src, + SkBitmap* result, + SkIVector& offset, + const SkIRect& rect, + const SkIRect& srcBounds) const { + switch (fTileMode) { + case SkTileMode::kClamp: + filterPixels(src, result, offset, rect, srcBounds); + break; + case SkTileMode::kMirror: + // TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now. + case SkTileMode::kRepeat: + filterPixels(src, result, offset, rect, srcBounds); + break; + case SkTileMode::kDecal: + filterPixels(src, result, offset, rect, srcBounds); + break; + } +} + +// FIXME: This should be refactored to SkImageFilterUtils for +// use by other filters. For now, we assume the input is always +// premultiplied and unpremultiply it +static SkBitmap unpremultiply_bitmap(const SkBitmap& src) { + if (!src.getPixels()) { + return SkBitmap(); + } + + const SkImageInfo info = SkImageInfo::MakeN32(src.width(), src.height(), src.alphaType()); + SkBitmap result; + if (!result.tryAllocPixels(info)) { + return SkBitmap(); + } + for (int y = 0; y < src.height(); ++y) { + const uint32_t* srcRow = src.getAddr32(0, y); + uint32_t* dstRow = result.getAddr32(0, y); + for (int x = 0; x < src.width(); ++x) { + dstRow[x] = SkUnPreMultiply::PMColorToColor(srcRow[x]); + } + } + return result; +} + +#if SK_SUPPORT_GPU + +static GrTextureDomain::Mode convert_tilemodes(SkTileMode tileMode) { + switch (tileMode) { + case SkTileMode::kClamp: + return GrTextureDomain::kClamp_Mode; + case SkTileMode::kMirror: + // TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now. + case SkTileMode::kRepeat: + return GrTextureDomain::kRepeat_Mode; + case SkTileMode::kDecal: + return GrTextureDomain::kDecal_Mode; + default: + SkASSERT(false); + } + return GrTextureDomain::kIgnore_Mode; +} +#endif + +sk_sp SkMatrixConvolutionImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + SkIRect dstBounds; + input = this->applyCropRectAndPad(this->mapContext(ctx), input.get(), &inputOffset, &dstBounds); + if (!input) { + return nullptr; + } + + const SkIRect originalSrcBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, + input->width(), input->height()); + + SkIRect srcBounds = this->onFilterNodeBounds(dstBounds, ctx.ctm(), kReverse_MapDirection, + &originalSrcBounds); + + if (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode) { + srcBounds = DetermineRepeatedSrcBound(srcBounds, fKernelOffset, + fKernelSize, originalSrcBounds); + } else { + if (!srcBounds.intersect(dstBounds)) { + return nullptr; + } + } + +#if SK_SUPPORT_GPU + // Note: if the kernel is too big, the GPU path falls back to SW + if (ctx.gpuBacked() && + fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) { + auto context = ctx.getContext(); + + // Ensure the input is in the destination color space. Typically applyCropRect will have + // called pad_image to account for our dilation of bounds, so the result will already be + // moved to the destination color space. If a filter DAG avoids that, then we use this + // fall-back, which saves us from having to do the xform during the filter itself. + input = ImageToColorSpace(input.get(), ctx.colorType(), ctx.colorSpace()); + + sk_sp inputProxy(input->asTextureProxyRef(context)); + SkASSERT(inputProxy); + + const auto isProtected = inputProxy->isProtected(); + + offset->fX = dstBounds.left(); + offset->fY = dstBounds.top(); + dstBounds.offset(-inputOffset); + srcBounds.offset(-inputOffset); + // Map srcBounds from input's logical image domain to that of the proxy + srcBounds.offset(input->subset().x(), input->subset().y()); + + auto fp = GrMatrixConvolutionEffect::Make(std::move(inputProxy), + srcBounds, + fKernelSize, + fKernel, + fGain, + fBias, + fKernelOffset, + convert_tilemodes(fTileMode), + fConvolveAlpha); + if (!fp) { + return nullptr; + } + + // FIXME (michaelludwig) - Clean this up as part of the imagefilter refactor, some filters + // instead require a coord transform on the FP. At very least, be consistent, at best make + // it so that filter impls don't need to worry about the subset origin. + + // Must also map the dstBounds since it is used as the src rect in DrawWithFP when + // evaluating the FP, and the dst rect just uses the size of dstBounds. + dstBounds.offset(input->subset().x(), input->subset().y()); + return DrawWithFP(context, std::move(fp), dstBounds, ctx.colorType(), ctx.colorSpace(), + isProtected ? GrProtected::kYes : GrProtected::kNo); + } +#endif + + SkBitmap inputBM; + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + if (!fConvolveAlpha && !inputBM.isOpaque()) { + inputBM = unpremultiply_bitmap(inputBM); + } + + if (!inputBM.getPixels()) { + return nullptr; + } + + const SkImageInfo info = SkImageInfo::MakeN32(dstBounds.width(), dstBounds.height(), + inputBM.alphaType()); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + offset->fX = dstBounds.fLeft; + offset->fY = dstBounds.fTop; + dstBounds.offset(-inputOffset); + srcBounds.offset(-inputOffset); + + SkIRect interior; + if (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode) { + // In repeat mode, the filterPixels calls will wrap around + // so we just need to render 'dstBounds' + interior = dstBounds; + } else { + interior = SkIRect::MakeXYWH(dstBounds.left() + fKernelOffset.fX, + dstBounds.top() + fKernelOffset.fY, + dstBounds.width() - fKernelSize.fWidth + 1, + dstBounds.height() - fKernelSize.fHeight + 1); + } + + SkIRect top = SkIRect::MakeLTRB(dstBounds.left(), dstBounds.top(), + dstBounds.right(), interior.top()); + SkIRect bottom = SkIRect::MakeLTRB(dstBounds.left(), interior.bottom(), + dstBounds.right(), dstBounds.bottom()); + SkIRect left = SkIRect::MakeLTRB(dstBounds.left(), interior.top(), + interior.left(), interior.bottom()); + SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(), + dstBounds.right(), interior.bottom()); + + SkIVector dstContentOffset = { offset->fX - inputOffset.fX, offset->fY - inputOffset.fY }; + + this->filterBorderPixels(inputBM, &dst, dstContentOffset, top, srcBounds); + this->filterBorderPixels(inputBM, &dst, dstContentOffset, left, srcBounds); + this->filterInteriorPixels(inputBM, &dst, dstContentOffset, interior, srcBounds); + this->filterBorderPixels(inputBM, &dst, dstContentOffset, right, srcBounds); + this->filterBorderPixels(inputBM, &dst, dstContentOffset, bottom, srcBounds); + + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), + dst); +} + +SkIRect SkMatrixConvolutionImageFilterImpl::onFilterNodeBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const { + if (kReverse_MapDirection == dir && inputRect && + (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode)) { + SkASSERT(inputRect); + return DetermineRepeatedSrcBound(src, fKernelOffset, fKernelSize, *inputRect); + } + + SkIRect dst = src; + int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1; + + if (kReverse_MapDirection == dir) { + dst.adjust(-fKernelOffset.fX, -fKernelOffset.fY, + w - fKernelOffset.fX, h - fKernelOffset.fY); + } else { + dst.adjust(fKernelOffset.fX - w, fKernelOffset.fY - h, fKernelOffset.fX, fKernelOffset.fY); + } + return dst; +} + +bool SkMatrixConvolutionImageFilterImpl::affectsTransparentBlack() const { + // It seems that the only rational way for repeat sample mode to work is if the caller + // explicitly restricts the input in which case the input range is explicitly known and + // specified. + // TODO: is seems that this should be true for clamp mode too. + + // For the other modes, because the kernel is applied in device-space, we have no idea what + // pixels it will affect in object-space. + return SkTileMode::kRepeat != fTileMode && SkTileMode::kMirror != fTileMode; +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkMergeImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkMergeImageFilter.cpp new file mode 100644 index 0000000000..d7925d78fd --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkMergeImageFilter.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkMergeImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkValidationUtils.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkMergeImageFilterImpl final : public SkImageFilter_Base { +public: + SkMergeImageFilterImpl(sk_sp* const filters, int count, + const CropRect* cropRect) + : INHERITED(filters, count, cropRect) { + SkASSERT(count >= 0); + } + +protected: + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + bool onCanHandleComplexCTM() const override { return true; } + +private: + friend void SkMergeImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkMergeImageFilterImpl) + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkMergeImageFilter::Make(sk_sp* const filters, int count, + const SkImageFilter::CropRect* cropRect) { + return sk_sp(new SkMergeImageFilterImpl(filters, count, cropRect)); +} + +void SkMergeImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkMergeImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkMergeImageFilter", SkMergeImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkMergeImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + Common common; + if (!common.unflatten(buffer, -1) || !buffer.isValid()) { + return nullptr; + } + return SkMergeImageFilter::Make(common.inputs(), common.inputCount(), &common.cropRect()); +} + +sk_sp SkMergeImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + int inputCount = this->countInputs(); + if (inputCount < 1) { + return nullptr; + } + + SkIRect bounds; + bounds.setEmpty(); + + std::unique_ptr[]> inputs(new sk_sp[inputCount]); + std::unique_ptr offsets(new SkIPoint[inputCount]); + + // Filter all of the inputs. + for (int i = 0; i < inputCount; ++i) { + offsets[i] = { 0, 0 }; + inputs[i] = this->filterInput(i, ctx, &offsets[i]); + if (!inputs[i]) { + continue; + } + const SkIRect inputBounds = SkIRect::MakeXYWH(offsets[i].fX, offsets[i].fY, + inputs[i]->width(), inputs[i]->height()); + bounds.join(inputBounds); + } + if (bounds.isEmpty()) { + return nullptr; + } + + // Apply the crop rect to the union of the inputs' bounds. + // Note that the crop rect can only reduce the bounds, since this + // filter does not affect transparent black. + bool embiggen = false; + this->getCropRect().applyTo(bounds, ctx.ctm(), embiggen, &bounds); + if (!bounds.intersect(ctx.clipBounds())) { + return nullptr; + } + + const int x0 = bounds.left(); + const int y0 = bounds.top(); + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); + + // Composite all of the filter inputs. + for (int i = 0; i < inputCount; ++i) { + if (!inputs[i]) { + continue; + } + + inputs[i]->draw(canvas, + SkIntToScalar(offsets[i].x() - x0), SkIntToScalar(offsets[i].y() - y0), + nullptr); + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + return surf->makeImageSnapshot(); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkMorphologyImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkMorphologyImageFilter.cpp new file mode 100644 index 0000000000..3fa7eb1aec --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkMorphologyImageFilter.cpp @@ -0,0 +1,853 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkMorphologyImageFilter.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkRect.h" +#include "include/private/SkColorData.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#endif + +namespace { + +enum class MorphType { + kErode, + kDilate, + kLastType = kDilate +}; + +enum class MorphDirection { kX, kY }; + +class SkMorphologyImageFilterImpl final : public SkImageFilter_Base { +public: + SkMorphologyImageFilterImpl(MorphType type, int radiusX, int radiusY, + sk_sp input, const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) + , fType(type) + , fRadius(SkISize::Make(radiusX, radiusY)) {} + + SkRect computeFastBounds(const SkRect& src) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + + /** + * All morphology procs have the same signature: src is the source buffer, dst the + * destination buffer, radius is the morphology radius, width and height are the bounds + * of the destination buffer (in pixels), and srcStride and dstStride are the + * number of pixels per row in each buffer. All buffers are 8888. + */ + + typedef void (*Proc)(const SkPMColor* src, SkPMColor* dst, int radius, + int width, int height, int srcStride, int dstStride); + +protected: + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + void flatten(SkWriteBuffer&) const override; + + SkISize radius() const { return fRadius; } + SkSize mappedRadius(const SkMatrix& ctm) const { + SkVector radiusVector = SkVector::Make(SkIntToScalar(fRadius.width()), + SkIntToScalar(fRadius.height())); + ctm.mapVectors(&radiusVector, 1); + radiusVector.setAbs(radiusVector); + return SkSize::Make(radiusVector.x(), radiusVector.y()); + } + +private: + friend void SkDilateImageFilter::RegisterFlattenables(); + + SK_FLATTENABLE_HOOKS(SkMorphologyImageFilterImpl) + // Historically the morphology op was implicitly encoded in the factory type used to decode + // the image filter, so provide backwards compatible functions for old SKPs. + static sk_sp CreateProcWithType(SkReadBuffer&, const MorphType*); + static sk_sp DilateCreateProc(SkReadBuffer& buffer) { + static const MorphType kType = MorphType::kDilate; + return CreateProcWithType(buffer, &kType); + } + static sk_sp ErodeCreateProc(SkReadBuffer& buffer) { + static const MorphType kType = MorphType::kErode; + return CreateProcWithType(buffer, &kType); + } + + MorphType fType; + SkISize fRadius; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkDilateImageFilter::Make(int radiusX, int radiusY, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + if (radiusX < 0 || radiusY < 0) { + return nullptr; + } + return sk_sp(new SkMorphologyImageFilterImpl( + MorphType::kDilate, radiusX, radiusY, std::move(input), cropRect)); +} + +sk_sp SkErodeImageFilter::Make(int radiusX, int radiusY, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + if (radiusX < 0 || radiusY < 0) { + return nullptr; + } + return sk_sp(new SkMorphologyImageFilterImpl( + MorphType::kErode, radiusX, radiusY, std::move(input), cropRect)); +} + +void SkDilateImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkMorphologyImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old names + SkFlattenable::Register("SkDilateImageFilter", SkMorphologyImageFilterImpl::DilateCreateProc); + SkFlattenable::Register( + "SkDilateImageFilterImpl", SkMorphologyImageFilterImpl::DilateCreateProc); + SkFlattenable::Register("SkErodeImageFilter", SkMorphologyImageFilterImpl::ErodeCreateProc); + SkFlattenable::Register("SkErodeImageFilterImpl", SkMorphologyImageFilterImpl::ErodeCreateProc); +} + +/////////////////////////////////////////////////////////////////////////////// + +// 'type' acts as a signal that old-style deserialization is required. It is temporary. +sk_sp SkMorphologyImageFilterImpl::CreateProcWithType(SkReadBuffer& buffer, + const MorphType* type) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + const int width = buffer.readInt(); + const int height = buffer.readInt(); + + MorphType filterType; + if (type) { + // The old create procs that have an associated op should only be used on old SKPs + SkASSERT(buffer.isVersionLT(SkPicturePriv::kUnifyErodeDilateImpls_Version)); + filterType = *type; + } else { + filterType = buffer.read32LE(MorphType::kLastType); + } + + if (filterType == MorphType::kDilate) { + return SkDilateImageFilter::Make(width, height, common.getInput(0), &common.cropRect()); + } else if (filterType == MorphType::kErode) { + return SkErodeImageFilter::Make(width, height, common.getInput(0), &common.cropRect()); + } else { + return nullptr; + } +} + +sk_sp SkMorphologyImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + // Pass null to have the create proc read the op from the buffer + return CreateProcWithType(buffer, nullptr); +} + +void SkMorphologyImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeInt(fRadius.fWidth); + buffer.writeInt(fRadius.fHeight); + buffer.writeInt(static_cast(fType)); +} + +static void call_proc_X(SkMorphologyImageFilterImpl::Proc procX, + const SkBitmap& src, SkBitmap* dst, + int radiusX, const SkIRect& bounds) { + procX(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0), + radiusX, bounds.width(), bounds.height(), + src.rowBytesAsPixels(), dst->rowBytesAsPixels()); +} + +static void call_proc_Y(SkMorphologyImageFilterImpl::Proc procY, + const SkPMColor* src, int srcRowBytesAsPixels, SkBitmap* dst, + int radiusY, const SkIRect& bounds) { + procY(src, dst->getAddr32(0, 0), + radiusY, bounds.height(), bounds.width(), + srcRowBytesAsPixels, dst->rowBytesAsPixels()); +} + +SkRect SkMorphologyImageFilterImpl::computeFastBounds(const SkRect& src) const { + SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; + bounds.outset(SkIntToScalar(fRadius.width()), SkIntToScalar(fRadius.height())); + return bounds; +} + +SkIRect SkMorphologyImageFilterImpl::onFilterNodeBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection, const SkIRect* inputRect) const { + SkSize radius = mappedRadius(ctm); + return src.makeOutset(SkScalarCeilToInt(radius.width()), SkScalarCeilToInt(radius.height())); +} + +#if SK_SUPPORT_GPU + +/////////////////////////////////////////////////////////////////////////////// +/** + * Morphology effects. Depending upon the type of morphology, either the + * component-wise min (Erode_Type) or max (Dilate_Type) of all pixels in the + * kernel is selected as the new color. The new color is modulated by the input + * color. + */ +class GrMorphologyEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + MorphDirection dir, + int radius, MorphType type) { + return std::unique_ptr( + new GrMorphologyEffect(std::move(proxy), srcColorType, dir, radius, type, nullptr)); + } + + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + MorphDirection dir, + int radius, MorphType type, + const float bounds[2]) { + return std::unique_ptr( + new GrMorphologyEffect(std::move(proxy), srcColorType, dir, radius, type, bounds)); + } + + MorphType type() const { return fType; } + bool useRange() const { return fUseRange; } + const float* range() const { return fRange; } + MorphDirection direction() const { return fDirection; } + int radius() const { return fRadius; } + int width() const { return 2 * fRadius + 1; } + + const char* name() const override { return "Morphology"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrMorphologyEffect(*this)); + } + +private: + GrCoordTransform fCoordTransform; + TextureSampler fTextureSampler; + MorphDirection fDirection; + int fRadius; + MorphType fType; + bool fUseRange; + float fRange[2]; + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const TextureSampler& onTextureSampler(int i) const override { return fTextureSampler; } + + GrMorphologyEffect(sk_sp, GrColorType srcColorType, MorphDirection, int radius, + MorphType, const float range[2]); + explicit GrMorphologyEffect(const GrMorphologyEffect&); + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLMorphologyEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fPixelSizeUni; + GrGLSLProgramDataManager::UniformHandle fRangeUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GrGLMorphologyEffect::emitCode(EmitArgs& args) { + const GrMorphologyEffect& me = args.fFp.cast(); + + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + fPixelSizeUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "PixelSize"); + const char* pixelSizeInc = uniformHandler->getUniformCStr(fPixelSizeUni); + fRangeUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2_GrSLType, "Range"); + const char* range = uniformHandler->getUniformCStr(fRangeUni); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + const char* func; + switch (me.type()) { + case MorphType::kErode: + fragBuilder->codeAppendf("\t\t%s = half4(1, 1, 1, 1);\n", args.fOutputColor); + func = "min"; + break; + case MorphType::kDilate: + fragBuilder->codeAppendf("\t\t%s = half4(0, 0, 0, 0);\n", args.fOutputColor); + func = "max"; + break; + default: + SK_ABORT("Unexpected type"); + func = ""; // suppress warning + break; + } + + const char* dir; + switch (me.direction()) { + case MorphDirection::kX: + dir = "x"; + break; + case MorphDirection::kY: + dir = "y"; + break; + default: + SK_ABORT("Unknown filter direction."); + dir = ""; // suppress warning + } + + int width = me.width(); + + // float2 coord = coord2D; + fragBuilder->codeAppendf("\t\tfloat2 coord = %s;\n", coords2D.c_str()); + // coord.x -= radius * pixelSize; + fragBuilder->codeAppendf("\t\tcoord.%s -= %d.0 * %s; \n", dir, me.radius(), pixelSizeInc); + if (me.useRange()) { + // highBound = min(highBound, coord.x + (width-1) * pixelSize); + fragBuilder->codeAppendf("\t\tfloat highBound = min(%s.y, coord.%s + %f * %s);", + range, dir, float(width - 1), pixelSizeInc); + // coord.x = max(lowBound, coord.x); + fragBuilder->codeAppendf("\t\tcoord.%s = max(%s.x, coord.%s);", dir, range, dir); + } + fragBuilder->codeAppendf("\t\tfor (int i = 0; i < %d; i++) {\n", width); + fragBuilder->codeAppendf("\t\t\t%s = %s(%s, ", args.fOutputColor, func, args.fOutputColor); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coord"); + fragBuilder->codeAppend(");\n"); + // coord.x += pixelSize; + fragBuilder->codeAppendf("\t\t\tcoord.%s += %s;\n", dir, pixelSizeInc); + if (me.useRange()) { + // coord.x = min(highBound, coord.x); + fragBuilder->codeAppendf("\t\t\tcoord.%s = min(highBound, coord.%s);", dir, dir); + } + fragBuilder->codeAppend("\t\t}\n"); + fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor); +} + +void GrGLMorphologyEffect::GenKey(const GrProcessor& proc, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const GrMorphologyEffect& m = proc.cast(); + uint32_t key = static_cast(m.radius()); + key |= (static_cast(m.type()) << 8); + key |= (static_cast(m.direction()) << 9); + if (m.useRange()) { + key |= 1 << 10; + } + b->add32(key); +} + +void GrGLMorphologyEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) { + const GrMorphologyEffect& m = proc.cast(); + GrSurfaceProxy* proxy = m.textureSampler(0).proxy(); + GrTexture& texture = *proxy->peekTexture(); + + float pixelSize = 0.0f; + switch (m.direction()) { + case MorphDirection::kX: + pixelSize = 1.0f / texture.width(); + break; + case MorphDirection::kY: + pixelSize = 1.0f / texture.height(); + break; + default: + SK_ABORT("Unknown filter direction."); + } + pdman.set1f(fPixelSizeUni, pixelSize); + + if (m.useRange()) { + const float* range = m.range(); + if (MorphDirection::kY == m.direction() && + proxy->origin() == kBottomLeft_GrSurfaceOrigin) { + pdman.set2f(fRangeUni, 1.0f - (range[1]*pixelSize), 1.0f - (range[0]*pixelSize)); + } else { + pdman.set2f(fRangeUni, range[0] * pixelSize, range[1] * pixelSize); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +GrMorphologyEffect::GrMorphologyEffect(sk_sp proxy, + GrColorType srcColorType, + MorphDirection direction, + int radius, + MorphType type, + const float range[2]) + : INHERITED(kGrMorphologyEffect_ClassID, + ModulateForClampedSamplerOptFlags(srcColorType)) + , fCoordTransform(proxy.get()) + , fTextureSampler(std::move(proxy)) + , fDirection(direction) + , fRadius(radius) + , fType(type) + , fUseRange(SkToBool(range)) { + // Make sure the sampler's ctor uses the clamp wrap mode + SkASSERT(fTextureSampler.samplerState().wrapModeX() == GrSamplerState::WrapMode::kClamp && + fTextureSampler.samplerState().wrapModeY() == GrSamplerState::WrapMode::kClamp); + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); + if (fUseRange) { + fRange[0] = range[0]; + fRange[1] = range[1]; + } +} + +GrMorphologyEffect::GrMorphologyEffect(const GrMorphologyEffect& that) + : INHERITED(kGrMorphologyEffect_ClassID, that.optimizationFlags()) + , fCoordTransform(that.fCoordTransform) + , fTextureSampler(that.fTextureSampler) + , fDirection(that.fDirection) + , fRadius(that.fRadius) + , fType(that.fType) + , fUseRange(that.fUseRange) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); + if (that.fUseRange) { + fRange[0] = that.fRange[0]; + fRange[1] = that.fRange[1]; + } +} + +void GrMorphologyEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLMorphologyEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrMorphologyEffect::onCreateGLSLInstance() const { + return new GrGLMorphologyEffect; +} +bool GrMorphologyEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrMorphologyEffect& s = sBase.cast(); + return (this->radius() == s.radius() && + this->direction() == s.direction() && + this->useRange() == s.useRange() && + this->type() == s.type()); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMorphologyEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrMorphologyEffect::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + + MorphDirection dir = d->fRandom->nextBool() ? MorphDirection::kX : MorphDirection::kY; + static const int kMaxRadius = 10; + int radius = d->fRandom->nextRangeU(1, kMaxRadius); + MorphType type = d->fRandom->nextBool() ? MorphType::kErode : MorphType::kDilate; + + return GrMorphologyEffect::Make(std::move(proxy), d->textureProxyColorType(texIdx), dir, radius, + type); +} +#endif + +static void apply_morphology_rect(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + sk_sp proxy, + GrColorType srcColorType, + const SkIRect& srcRect, + const SkIRect& dstRect, + int radius, + MorphType morphType, + const float bounds[2], + MorphDirection direction) { + GrPaint paint; + paint.addColorFragmentProcessor(GrMorphologyEffect::Make(std::move(proxy), srcColorType, + direction, radius, morphType, + bounds)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::Make(dstRect), SkRect::Make(srcRect)); +} + +static void apply_morphology_rect_no_bounds(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + sk_sp proxy, + GrColorType srcColorType, + const SkIRect& srcRect, + const SkIRect& dstRect, + int radius, + MorphType morphType, + MorphDirection direction) { + GrPaint paint; + paint.addColorFragmentProcessor(GrMorphologyEffect::Make(std::move(proxy), srcColorType, + direction, radius, morphType)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::Make(dstRect), SkRect::Make(srcRect)); +} + +static void apply_morphology_pass(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + sk_sp textureProxy, + GrColorType srcColorType, + const SkIRect& srcRect, + const SkIRect& dstRect, + int radius, + MorphType morphType, + MorphDirection direction) { + float bounds[2] = { 0.0f, 1.0f }; + SkIRect lowerSrcRect = srcRect, lowerDstRect = dstRect; + SkIRect middleSrcRect = srcRect, middleDstRect = dstRect; + SkIRect upperSrcRect = srcRect, upperDstRect = dstRect; + if (direction == MorphDirection::kX) { + bounds[0] = SkIntToScalar(srcRect.left()) + 0.5f; + bounds[1] = SkIntToScalar(srcRect.right()) - 0.5f; + lowerSrcRect.fRight = srcRect.left() + radius; + lowerDstRect.fRight = dstRect.left() + radius; + upperSrcRect.fLeft = srcRect.right() - radius; + upperDstRect.fLeft = dstRect.right() - radius; + middleSrcRect.inset(radius, 0); + middleDstRect.inset(radius, 0); + } else { + bounds[0] = SkIntToScalar(srcRect.top()) + 0.5f; + bounds[1] = SkIntToScalar(srcRect.bottom()) - 0.5f; + lowerSrcRect.fBottom = srcRect.top() + radius; + lowerDstRect.fBottom = dstRect.top() + radius; + upperSrcRect.fTop = srcRect.bottom() - radius; + upperDstRect.fTop = dstRect.bottom() - radius; + middleSrcRect.inset(0, radius); + middleDstRect.inset(0, radius); + } + if (middleSrcRect.width() <= 0) { + // radius covers srcRect; use bounds over entire draw + apply_morphology_rect(renderTargetContext, clip, std::move(textureProxy), srcColorType, + srcRect, dstRect, radius, morphType, bounds, direction); + } else { + // Draw upper and lower margins with bounds; middle without. + apply_morphology_rect(renderTargetContext, clip, textureProxy, srcColorType, + lowerSrcRect, lowerDstRect, radius, morphType, bounds, direction); + apply_morphology_rect(renderTargetContext, clip, textureProxy, srcColorType, + upperSrcRect, upperDstRect, radius, morphType, bounds, direction); + apply_morphology_rect_no_bounds(renderTargetContext, clip, std::move(textureProxy), + srcColorType, middleSrcRect, middleDstRect, radius, + morphType, direction); + } +} + +static sk_sp apply_morphology( + GrRecordingContext* context, SkSpecialImage* input, const SkIRect& rect, + MorphType morphType, SkISize radius, const SkImageFilter_Base::Context& ctx) { + sk_sp srcTexture(input->asTextureProxyRef(context)); + GrColorType srcColorType = SkColorTypeToGrColorType(input->colorType()); + SkASSERT(srcTexture); + sk_sp colorSpace = ctx.refColorSpace(); + GrColorType colorType = ctx.grColorType(); + + // setup new clip + const GrFixedClip clip(SkIRect::MakeWH(srcTexture->width(), srcTexture->height())); + + const SkIRect dstRect = SkIRect::MakeWH(rect.width(), rect.height()); + SkIRect srcRect = rect; + // Map into proxy space + srcRect.offset(input->subset().x(), input->subset().y()); + SkASSERT(radius.width() > 0 || radius.height() > 0); + + if (radius.fWidth > 0) { + auto dstRTContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, + rect.width(), + rect.height(), + colorType, + colorSpace, + 1, + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + srcTexture->isProtected() ? GrProtected::kYes : GrProtected::kNo); + if (!dstRTContext) { + return nullptr; + } + + apply_morphology_pass(dstRTContext.get(), clip, std::move(srcTexture), srcColorType, + srcRect, dstRect, radius.fWidth, morphType, MorphDirection::kX); + SkIRect clearRect = SkIRect::MakeXYWH(dstRect.fLeft, dstRect.fBottom, + dstRect.width(), radius.fHeight); + SkPMColor4f clearColor = MorphType::kErode == morphType + ? SK_PMColor4fWHITE : SK_PMColor4fTRANSPARENT; + dstRTContext->clear(&clearRect, clearColor, GrRenderTargetContext::CanClearFullscreen::kNo); + + srcTexture = dstRTContext->asTextureProxyRef(); + srcColorType = colorType; + srcRect = dstRect; + } + if (radius.fHeight > 0) { + auto dstRTContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, + rect.width(), + rect.height(), + colorType, + colorSpace, + 1, + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + srcTexture->isProtected() ? GrProtected::kYes : GrProtected::kNo); + if (!dstRTContext) { + return nullptr; + } + + apply_morphology_pass(dstRTContext.get(), clip, std::move(srcTexture), srcColorType, + srcRect, dstRect, radius.fHeight, morphType, MorphDirection::kY); + + srcTexture = dstRTContext->asTextureProxyRef(); + } + + return SkSpecialImage::MakeDeferredFromGpu(context, + SkIRect::MakeWH(rect.width(), rect.height()), + kNeedNewImageUniqueID_SpecialImage, + std::move(srcTexture), colorType, + std::move(colorSpace), &input->props()); +} +#endif + +namespace { + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + template + static void morph(const SkPMColor* src, SkPMColor* dst, + int radius, int width, int height, int srcStride, int dstStride) { + const int srcStrideX = direction == MorphDirection::kX ? 1 : srcStride; + const int dstStrideX = direction == MorphDirection::kX ? 1 : dstStride; + const int srcStrideY = direction == MorphDirection::kX ? srcStride : 1; + const int dstStrideY = direction == MorphDirection::kX ? dstStride : 1; + radius = SkMin32(radius, width - 1); + const SkPMColor* upperSrc = src + radius * srcStrideX; + for (int x = 0; x < width; ++x) { + const SkPMColor* lp = src; + const SkPMColor* up = upperSrc; + SkPMColor* dptr = dst; + for (int y = 0; y < height; ++y) { + __m128i extreme = (type == MorphType::kDilate) ? _mm_setzero_si128() + : _mm_set1_epi32(0xFFFFFFFF); + for (const SkPMColor* p = lp; p <= up; p += srcStrideX) { + __m128i src_pixel = _mm_cvtsi32_si128(*p); + extreme = (type == MorphType::kDilate) ? _mm_max_epu8(src_pixel, extreme) + : _mm_min_epu8(src_pixel, extreme); + } + *dptr = _mm_cvtsi128_si32(extreme); + dptr += dstStrideY; + lp += srcStrideY; + up += srcStrideY; + } + if (x >= radius) { src += srcStrideX; } + if (x + radius < width - 1) { upperSrc += srcStrideX; } + dst += dstStrideX; + } + } + +#elif defined(SK_ARM_HAS_NEON) + template + static void morph(const SkPMColor* src, SkPMColor* dst, + int radius, int width, int height, int srcStride, int dstStride) { + const int srcStrideX = direction == MorphDirection::kX ? 1 : srcStride; + const int dstStrideX = direction == MorphDirection::kX ? 1 : dstStride; + const int srcStrideY = direction == MorphDirection::kX ? srcStride : 1; + const int dstStrideY = direction == MorphDirection::kX ? dstStride : 1; + radius = SkMin32(radius, width - 1); + const SkPMColor* upperSrc = src + radius * srcStrideX; + for (int x = 0; x < width; ++x) { + const SkPMColor* lp = src; + const SkPMColor* up = upperSrc; + SkPMColor* dptr = dst; + for (int y = 0; y < height; ++y) { + uint8x8_t extreme = vdup_n_u8(type == MorphType::kDilate ? 0 : 255); + for (const SkPMColor* p = lp; p <= up; p += srcStrideX) { + uint8x8_t src_pixel = vreinterpret_u8_u32(vdup_n_u32(*p)); + extreme = (type == MorphType::kDilate) ? vmax_u8(src_pixel, extreme) + : vmin_u8(src_pixel, extreme); + } + *dptr = vget_lane_u32(vreinterpret_u32_u8(extreme), 0); + dptr += dstStrideY; + lp += srcStrideY; + up += srcStrideY; + } + if (x >= radius) src += srcStrideX; + if (x + radius < width - 1) upperSrc += srcStrideX; + dst += dstStrideX; + } + } + +#else + template + static void morph(const SkPMColor* src, SkPMColor* dst, + int radius, int width, int height, int srcStride, int dstStride) { + const int srcStrideX = direction == MorphDirection::kX ? 1 : srcStride; + const int dstStrideX = direction == MorphDirection::kX ? 1 : dstStride; + const int srcStrideY = direction == MorphDirection::kX ? srcStride : 1; + const int dstStrideY = direction == MorphDirection::kX ? dstStride : 1; + radius = SkMin32(radius, width - 1); + const SkPMColor* upperSrc = src + radius * srcStrideX; + for (int x = 0; x < width; ++x) { + const SkPMColor* lp = src; + const SkPMColor* up = upperSrc; + SkPMColor* dptr = dst; + for (int y = 0; y < height; ++y) { + // If we're maxing (dilate), start from 0; if minning (erode), start from 255. + const int start = (type == MorphType::kDilate) ? 0 : 255; + int B = start, G = start, R = start, A = start; + for (const SkPMColor* p = lp; p <= up; p += srcStrideX) { + int b = SkGetPackedB32(*p), + g = SkGetPackedG32(*p), + r = SkGetPackedR32(*p), + a = SkGetPackedA32(*p); + if (type == MorphType::kDilate) { + B = SkTMax(b, B); + G = SkTMax(g, G); + R = SkTMax(r, R); + A = SkTMax(a, A); + } else { + B = SkTMin(b, B); + G = SkTMin(g, G); + R = SkTMin(r, R); + A = SkTMin(a, A); + } + } + *dptr = SkPackARGB32(A, R, G, B); + dptr += dstStrideY; + lp += srcStrideY; + up += srcStrideY; + } + if (x >= radius) { src += srcStrideX; } + if (x + radius < width - 1) { upperSrc += srcStrideX; } + dst += dstStrideX; + } + } +#endif +} // namespace + +sk_sp SkMorphologyImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + SkIRect bounds; + input = this->applyCropRectAndPad(this->mapContext(ctx), input.get(), &inputOffset, &bounds); + if (!input) { + return nullptr; + } + + SkSize radius = mappedRadius(ctx.ctm()); + int width = SkScalarFloorToInt(radius.width()); + int height = SkScalarFloorToInt(radius.height()); + + if (width < 0 || height < 0) { + return nullptr; + } + + SkIRect srcBounds = bounds; + srcBounds.offset(-inputOffset); + + if (0 == width && 0 == height) { + offset->fX = bounds.left(); + offset->fY = bounds.top(); + return input->makeSubset(srcBounds); + } + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + auto context = ctx.getContext(); + + // Ensure the input is in the destination color space. Typically applyCropRect will have + // called pad_image to account for our dilation of bounds, so the result will already be + // moved to the destination color space. If a filter DAG avoids that, then we use this + // fall-back, which saves us from having to do the xform during the filter itself. + input = ImageToColorSpace(input.get(), ctx.colorType(), ctx.colorSpace()); + + sk_sp result(apply_morphology(context, input.get(), srcBounds, fType, + SkISize::Make(width, height), ctx)); + if (result) { + offset->fX = bounds.left(); + offset->fY = bounds.top(); + } + return result; + } +#endif + + SkBitmap inputBM; + + if (!input->getROPixels(&inputBM)) { + return nullptr; + } + + if (inputBM.colorType() != kN32_SkColorType) { + return nullptr; + } + + SkImageInfo info = SkImageInfo::Make(bounds.size(), inputBM.colorType(), inputBM.alphaType()); + + SkBitmap dst; + if (!dst.tryAllocPixels(info)) { + return nullptr; + } + + SkMorphologyImageFilterImpl::Proc procX, procY; + + if (MorphType::kDilate == fType) { + procX = &morph; + procY = &morph; + } else { + procX = &morph; + procY = &morph; + } + + if (width > 0 && height > 0) { + SkBitmap tmp; + if (!tmp.tryAllocPixels(info)) { + return nullptr; + } + + call_proc_X(procX, inputBM, &tmp, width, srcBounds); + SkIRect tmpBounds = SkIRect::MakeWH(srcBounds.width(), srcBounds.height()); + call_proc_Y(procY, + tmp.getAddr32(tmpBounds.left(), tmpBounds.top()), tmp.rowBytesAsPixels(), + &dst, height, tmpBounds); + } else if (width > 0) { + call_proc_X(procX, inputBM, &dst, width, srcBounds); + } else if (height > 0) { + call_proc_Y(procY, + inputBM.getAddr32(srcBounds.left(), srcBounds.top()), + inputBM.rowBytesAsPixels(), + &dst, height, srcBounds); + } + offset->fX = bounds.left(); + offset->fY = bounds.top(); + + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), + dst, ctx.surfaceProps()); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkOffsetImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkOffsetImageFilter.cpp new file mode 100644 index 0000000000..8a7de697f3 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkOffsetImageFilter.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkOffsetImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkOffsetImageFilterImpl final : public SkImageFilter_Base { +public: + SkOffsetImageFilterImpl(SkScalar dx, SkScalar dy, sk_sp input, + const CropRect* cropRect) + : INHERITED(&input, 1, cropRect) { + fOffset.set(dx, dy); + } + + SkRect computeFastBounds(const SkRect& src) const override; + +protected: + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +private: + friend void SkOffsetImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkOffsetImageFilterImpl) + + SkVector fOffset; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkOffsetImageFilter::Make(SkScalar dx, SkScalar dy, + sk_sp input, + const SkImageFilter::CropRect* cropRect) { + if (!SkScalarIsFinite(dx) || !SkScalarIsFinite(dy)) { + return nullptr; + } + + return sk_sp(new SkOffsetImageFilterImpl(dx, dy, std::move(input), cropRect)); +} + +void SkOffsetImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkOffsetImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkOffsetImageFilter", SkOffsetImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkOffsetImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkPoint offset; + buffer.readPoint(&offset); + return SkOffsetImageFilter::Make(offset.x(), offset.y(), common.getInput(0), + &common.cropRect()); +} + +void SkOffsetImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fOffset); +} + +static SkIPoint map_offset_vector(const SkMatrix& ctm, const SkVector& offset) { + SkVector vec = ctm.mapVector(offset.fX, offset.fY); + return SkIPoint::Make(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY)); +} + +sk_sp SkOffsetImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint srcOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &srcOffset)); + if (!input) { + return nullptr; + } + + SkIPoint vec = map_offset_vector(ctx.ctm(), fOffset); + + if (!this->cropRectIsSet()) { + offset->fX = Sk32_sat_add(srcOffset.fX, vec.fX); + offset->fY = Sk32_sat_add(srcOffset.fY, vec.fY); + return input; + } else { + SkIRect bounds; + const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset.fX, srcOffset.fY, + input->width(), input->height()); + if (!this->applyCropRect(ctx, srcBounds, &bounds)) { + return nullptr; + } + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + // TODO: it seems like this clear shouldn't be necessary (see skbug.com/5075) + canvas->clear(0x0); + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + canvas->translate(SkIntToScalar(srcOffset.fX - bounds.fLeft), + SkIntToScalar(srcOffset.fY - bounds.fTop)); + + input->draw(canvas, vec.fX, vec.fY, &paint); + + offset->fX = bounds.fLeft; + offset->fY = bounds.fTop; + return surf->makeImageSnapshot(); + } +} + +SkRect SkOffsetImageFilterImpl::computeFastBounds(const SkRect& src) const { + SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; + bounds.offset(fOffset.fX, fOffset.fY); + return bounds; +} + +SkIRect SkOffsetImageFilterImpl::onFilterNodeBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const { + SkIPoint vec = map_offset_vector(ctm, fOffset); + if (kReverse_MapDirection == dir) { + SkPointPriv::Negate(vec); + } + + return src.makeOffset(vec); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkPaintImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkPaintImageFilter.cpp new file mode 100644 index 0000000000..1aae4849d1 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkPaintImageFilter.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkPaintImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkPaint.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkPaintImageFilterImpl final : public SkImageFilter_Base { +public: + SkPaintImageFilterImpl(const SkPaint& paint, const CropRect* rect) + : INHERITED(nullptr, 0, rect) + , fPaint(paint) {} + + bool affectsTransparentBlack() const override; + +protected: + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +private: + friend void SkPaintImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkPaintImageFilterImpl) + + SkPaint fPaint; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkPaintImageFilter::Make(const SkPaint& paint, + const SkImageFilter::CropRect* cropRect) { + return sk_sp(new SkPaintImageFilterImpl(paint, cropRect)); +} + +void SkPaintImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkPaintImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkPaintImageFilter", SkPaintImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkPaintImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 0); + SkPaint paint; + buffer.readPaint(&paint, nullptr); + return SkPaintImageFilter::Make(paint, &common.cropRect()); +} + +void SkPaintImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePaint(fPaint); +} + +sk_sp SkPaintImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIRect bounds; + const SkIRect srcBounds = SkIRect::MakeWH(ctx.sourceImage()->width(), + ctx.sourceImage()->height()); + if (!this->applyCropRect(ctx, srcBounds, &bounds)) { + return nullptr; + } + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); + + SkMatrix matrix(ctx.ctm()); + matrix.postTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); + SkRect rect = SkRect::MakeIWH(bounds.width(), bounds.height()); + SkMatrix inverse; + if (matrix.invert(&inverse)) { + inverse.mapRect(&rect); + } + canvas->setMatrix(matrix); + if (rect.isFinite()) { + canvas->drawRect(rect, fPaint); + } + + offset->fX = bounds.fLeft; + offset->fY = bounds.fTop; + return surf->makeImageSnapshot(); +} + +bool SkPaintImageFilterImpl::affectsTransparentBlack() const { + return true; +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkPictureImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkPictureImageFilter.cpp new file mode 100644 index 0000000000..63a43c7f08 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkPictureImageFilter.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2013 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkPictureImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkPicture.h" +#include "include/effects/SkImageSource.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkPicturePriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkValidationUtils.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkPictureImageFilterImpl final : public SkImageFilter_Base { +public: + SkPictureImageFilterImpl(sk_sp picture, const SkRect& cropRect) + : INHERITED(nullptr, 0, nullptr) + , fPicture(std::move(picture)) + , fCropRect(cropRect) {} + +protected: + /* Constructs an SkPictureImageFilter object from an SkReadBuffer. + * Note: If the SkPictureImageFilter object construction requires bitmap + * decoding, the decoder must be set on the SkReadBuffer parameter by calling + * SkReadBuffer::setBitmapDecoder() before calling this constructor. + * @param SkReadBuffer Serialized picture data. + */ + void flatten(SkWriteBuffer&) const override; + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +private: + friend void SkPictureImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkPictureImageFilterImpl) + + sk_sp fPicture; + SkRect fCropRect; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkPictureImageFilter::Make(sk_sp picture) { + SkRect cropRect = picture ? picture->cullRect() : SkRect::MakeEmpty(); + return Make(std::move(picture), cropRect); +} + +sk_sp SkPictureImageFilter::Make(sk_sp picture, const SkRect& cropRect) { + return sk_sp(new SkPictureImageFilterImpl(std::move(picture), cropRect)); +} + +void SkPictureImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkPictureImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkPictureImageFilter", SkPictureImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +enum PictureResolution { + kDeviceSpace_PictureResolution, + kLocalSpace_PictureResolution +}; +static sk_sp make_localspace_filter(sk_sp pic, const SkRect& cropRect, + SkFilterQuality fq) { + SkISize dim = { SkScalarRoundToInt(cropRect.width()), SkScalarRoundToInt(cropRect.height()) }; + auto img = SkImage::MakeFromPicture(std::move(pic), dim, nullptr, nullptr, + SkImage::BitDepth::kU8, SkColorSpace::MakeSRGB()); + return SkImageSource::Make(img, cropRect, cropRect, fq); +} + +sk_sp SkPictureImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + sk_sp picture; + SkRect cropRect; + + if (buffer.readBool()) { + picture = SkPicturePriv::MakeFromBuffer(buffer); + } + buffer.readRect(&cropRect); + + if (buffer.isVersionLT(SkPicturePriv::kRemovePictureImageFilterLocalSpace)) { + PictureResolution pictureResolution = buffer.checkRange( + kDeviceSpace_PictureResolution, kLocalSpace_PictureResolution); + if (kLocalSpace_PictureResolution == pictureResolution) { + return make_localspace_filter(std::move(picture), cropRect, + buffer.checkFilterQuality()); + } + } + return SkPictureImageFilter::Make(std::move(picture), cropRect); +} + +void SkPictureImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + bool hasPicture = (fPicture != nullptr); + buffer.writeBool(hasPicture); + if (hasPicture) { + SkPicturePriv::Flatten(fPicture, buffer); + } + buffer.writeRect(fCropRect); +} + +sk_sp SkPictureImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + if (!fPicture) { + return nullptr; + } + + SkRect floatBounds; + ctx.ctm().mapRect(&floatBounds, fCropRect); + SkIRect bounds = floatBounds.roundOut(); + if (!bounds.intersect(ctx.clipBounds())) { + return nullptr; + } + + SkASSERT(!bounds.isEmpty()); + + // Given the standard usage of the picture image filter (i.e., to render content at a fixed + // resolution that, most likely, differs from the screen's) disable LCD text. + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + sk_sp surf(ctx.makeSurface(bounds.size(), &props)); + if (!surf) { + return nullptr; + } + + SkASSERT(kUnknown_SkPixelGeometry == surf->props().pixelGeometry()); + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + canvas->clear(0x0); + + canvas->translate(-SkIntToScalar(bounds.fLeft), -SkIntToScalar(bounds.fTop)); + canvas->concat(ctx.ctm()); + canvas->drawPicture(fPicture); + + offset->fX = bounds.fLeft; + offset->fY = bounds.fTop; + return surf->makeImageSnapshot(); +} diff --git a/gfx/skia/skia/src/effects/imagefilters/SkTileImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkTileImageFilter.cpp new file mode 100644 index 0000000000..987747dc00 --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkTileImageFilter.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkTileImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkImage.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" +#include "include/core/SkSurface.h" +#include "include/effects/SkOffsetImageFilter.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkValidationUtils.h" +#include "src/core/SkWriteBuffer.h" + +namespace { + +class SkTileImageFilterImpl final : public SkImageFilter_Base { +public: + SkTileImageFilterImpl(const SkRect& srcRect, const SkRect& dstRect, sk_sp input) + : INHERITED(&input, 1, nullptr) + , fSrcRect(srcRect) + , fDstRect(dstRect) {} + + SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + SkRect computeFastBounds(const SkRect& src) const override; + +protected: + void flatten(SkWriteBuffer& buffer) const override; + + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + +private: + friend void SkTileImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkTileImageFilterImpl) + + SkRect fSrcRect; + SkRect fDstRect; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkTileImageFilter::Make(const SkRect& srcRect, const SkRect& dstRect, + sk_sp input) { + if (!SkIsValidRect(srcRect) || !SkIsValidRect(dstRect)) { + return nullptr; + } + if (srcRect.width() == dstRect.width() && srcRect.height() == dstRect.height()) { + SkRect ir = dstRect; + if (!ir.intersect(srcRect)) { + return input; + } + SkImageFilter::CropRect cropRect(ir); + return SkOffsetImageFilter::Make(dstRect.x() - srcRect.x(), + dstRect.y() - srcRect.y(), + std::move(input), + &cropRect); + } + return sk_sp(new SkTileImageFilterImpl(srcRect, dstRect, std::move(input))); +} + +void SkTileImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkTileImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkTileImageFilter", SkTileImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkTileImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); + SkRect src, dst; + buffer.readRect(&src); + buffer.readRect(&dst); + return SkTileImageFilter::Make(src, dst, common.getInput(0)); +} + +void SkTileImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writeRect(fSrcRect); + buffer.writeRect(fDstRect); +} + +sk_sp SkTileImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint inputOffset = SkIPoint::Make(0, 0); + sk_sp input(this->filterInput(0, ctx, &inputOffset)); + if (!input) { + return nullptr; + } + + SkRect dstRect; + ctx.ctm().mapRect(&dstRect, fDstRect); + if (!dstRect.intersect(SkRect::Make(ctx.clipBounds()))) { + return nullptr; + } + + const SkIRect dstIRect = dstRect.roundOut(); + if (!fSrcRect.width() || !fSrcRect.height() || !dstIRect.width() || !dstIRect.height()) { + return nullptr; + } + + SkRect srcRect; + ctx.ctm().mapRect(&srcRect, fSrcRect); + SkIRect srcIRect; + srcRect.roundOut(&srcIRect); + srcIRect.offset(-inputOffset); + const SkIRect inputBounds = SkIRect::MakeWH(input->width(), input->height()); + + if (!SkIRect::Intersects(srcIRect, inputBounds)) { + return nullptr; + } + + // We create an SkImage here b.c. it needs to be a tight fit for the tiling + sk_sp subset; + if (inputBounds.contains(srcIRect)) { + subset = input->asImage(&srcIRect); + } else { + sk_sp surf(input->makeTightSurface(ctx.colorType(), ctx.colorSpace(), + srcIRect.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + + input->draw(canvas, + SkIntToScalar(inputOffset.x()), SkIntToScalar(inputOffset.y()), + &paint); + + subset = surf->makeImageSnapshot(); + } + if (!subset) { + return nullptr; + } + SkASSERT(subset->width() == srcIRect.width()); + SkASSERT(subset->height() == srcIRect.height()); + + sk_sp surf(ctx.makeSurface(dstIRect.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + paint.setShader(subset->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat)); + canvas->translate(-dstRect.fLeft, -dstRect.fTop); + canvas->drawRect(dstRect, paint); + offset->fX = dstIRect.fLeft; + offset->fY = dstIRect.fTop; + return surf->makeImageSnapshot(); +} + +SkIRect SkTileImageFilterImpl::onFilterNodeBounds( + const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const { + SkRect rect = kReverse_MapDirection == dir ? fSrcRect : fDstRect; + ctm.mapRect(&rect); + return rect.roundOut(); +} + +SkIRect SkTileImageFilterImpl::onFilterBounds(const SkIRect& src, const SkMatrix&, + MapDirection, const SkIRect* inputRect) const { + // Don't recurse into inputs. + return src; +} + +SkRect SkTileImageFilterImpl::computeFastBounds(const SkRect& src) const { + return fDstRect; +} + diff --git a/gfx/skia/skia/src/effects/imagefilters/SkXfermodeImageFilter.cpp b/gfx/skia/skia/src/effects/imagefilters/SkXfermodeImageFilter.cpp new file mode 100644 index 0000000000..14cf52feea --- /dev/null +++ b/gfx/skia/skia/src/effects/imagefilters/SkXfermodeImageFilter.cpp @@ -0,0 +1,335 @@ +/* + * Copyright 2013 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkXfermodeImageFilter.h" + +#include "include/core/SkCanvas.h" +#include "include/private/SkColorData.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkSpecialSurface.h" +#include "src/core/SkWriteBuffer.h" +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" + +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#endif +#include "src/core/SkClipOpPriv.h" + +namespace { + +class SkXfermodeImageFilterImpl : public SkImageFilter_Base { +public: + SkXfermodeImageFilterImpl(SkBlendMode mode, sk_sp inputs[2], + const CropRect* cropRect) + : INHERITED(inputs, 2, cropRect) + , fMode(mode) {} + +protected: + sk_sp onFilterImage(const Context&, SkIPoint* offset) const override; + + SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + +#if SK_SUPPORT_GPU + sk_sp filterImageGPU(const Context& ctx, + sk_sp background, + const SkIPoint& backgroundOffset, + sk_sp foreground, + const SkIPoint& foregroundOffset, + const SkIRect& bounds) const; +#endif + + void flatten(SkWriteBuffer&) const override; + + void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const; +#if SK_SUPPORT_GPU + std::unique_ptr makeFGFrag( + std::unique_ptr bgFP) const; +#endif + +private: + friend void SkXfermodeImageFilter::RegisterFlattenables(); + SK_FLATTENABLE_HOOKS(SkXfermodeImageFilterImpl) + + SkBlendMode fMode; + + typedef SkImageFilter_Base INHERITED; +}; + +} // end namespace + +sk_sp SkXfermodeImageFilter::Make(SkBlendMode mode, + sk_sp background, + sk_sp foreground, + const SkImageFilter::CropRect* cropRect) { + sk_sp inputs[2] = { std::move(background), std::move(foreground) }; + return sk_sp(new SkXfermodeImageFilterImpl(mode, inputs, cropRect)); +} + +void SkXfermodeImageFilter::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkXfermodeImageFilterImpl); + // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name + SkFlattenable::Register("SkXfermodeImageFilter_Base", SkXfermodeImageFilterImpl::CreateProc); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static unsigned unflatten_blendmode(SkReadBuffer& buffer) { + unsigned mode = buffer.read32(); + (void)buffer.validate(mode <= (unsigned)SkBlendMode::kLastMode); + return mode; +} + +sk_sp SkXfermodeImageFilterImpl::CreateProc(SkReadBuffer& buffer) { + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2); + unsigned mode = unflatten_blendmode(buffer); + if (!buffer.isValid()) { + return nullptr; + } + return SkXfermodeImageFilter::Make((SkBlendMode)mode, common.getInput(0), + common.getInput(1), &common.cropRect()); +} + +void SkXfermodeImageFilterImpl::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.write32((unsigned)fMode); +} + +sk_sp SkXfermodeImageFilterImpl::onFilterImage(const Context& ctx, + SkIPoint* offset) const { + SkIPoint backgroundOffset = SkIPoint::Make(0, 0); + sk_sp background(this->filterInput(0, ctx, &backgroundOffset)); + + SkIPoint foregroundOffset = SkIPoint::Make(0, 0); + sk_sp foreground(this->filterInput(1, ctx, &foregroundOffset)); + + SkIRect foregroundBounds = SkIRect::EmptyIRect(); + if (foreground) { + foregroundBounds = SkIRect::MakeXYWH(foregroundOffset.x(), foregroundOffset.y(), + foreground->width(), foreground->height()); + } + + SkIRect srcBounds = SkIRect::EmptyIRect(); + if (background) { + srcBounds = SkIRect::MakeXYWH(backgroundOffset.x(), backgroundOffset.y(), + background->width(), background->height()); + } + + srcBounds.join(foregroundBounds); + if (srcBounds.isEmpty()) { + return nullptr; + } + + SkIRect bounds; + if (!this->applyCropRect(ctx, srcBounds, &bounds)) { + return nullptr; + } + + offset->fX = bounds.left(); + offset->fY = bounds.top(); + +#if SK_SUPPORT_GPU + if (ctx.gpuBacked()) { + return this->filterImageGPU(ctx, background, backgroundOffset, + foreground, foregroundOffset, bounds); + } +#endif + + sk_sp surf(ctx.makeSurface(bounds.size())); + if (!surf) { + return nullptr; + } + + SkCanvas* canvas = surf->getCanvas(); + SkASSERT(canvas); + + canvas->clear(0x0); // can't count on background to fully clear the background + canvas->translate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); + + if (background) { + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + background->draw(canvas, + SkIntToScalar(backgroundOffset.fX), SkIntToScalar(backgroundOffset.fY), + &paint); + } + + this->drawForeground(canvas, foreground.get(), foregroundBounds); + + return surf->makeImageSnapshot(); +} + +SkIRect SkXfermodeImageFilterImpl::onFilterBounds(const SkIRect& src, + const SkMatrix& ctm, + MapDirection dir, + const SkIRect* inputRect) const { + if (kReverse_MapDirection == dir) { + return INHERITED::onFilterBounds(src, ctm, dir, inputRect); + } + + SkASSERT(!inputRect); + SkASSERT(2 == this->countInputs()); + auto getBackground = [&]() { + return this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, dir, inputRect) : src; + }; + auto getForeground = [&]() { + return this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, dir, inputRect) : src; + }; + switch (fMode) { + case SkBlendMode::kClear: + return SkIRect::MakeEmpty(); + + case SkBlendMode::kSrc: + case SkBlendMode::kDstATop: + return getForeground(); + + case SkBlendMode::kDst: + case SkBlendMode::kSrcATop: + return getBackground(); + + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: { + auto result = getBackground(); + if (!result.intersect(getForeground())) { + return SkIRect::MakeEmpty(); + } + return result; + } + + default: { + auto result = getBackground(); + result.join(getForeground()); + return result; + } + } +} + +void SkXfermodeImageFilterImpl::drawForeground(SkCanvas* canvas, SkSpecialImage* img, + const SkIRect& fgBounds) const { + SkPaint paint; + paint.setBlendMode(fMode); + if (img) { + img->draw(canvas, SkIntToScalar(fgBounds.fLeft), SkIntToScalar(fgBounds.fTop), &paint); + } + + SkAutoCanvasRestore acr(canvas, true); + canvas->clipRect(SkRect::Make(fgBounds), kDifference_SkClipOp); + paint.setColor(0); + canvas->drawPaint(paint); +} + +#if SK_SUPPORT_GPU + +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" + +sk_sp SkXfermodeImageFilterImpl::filterImageGPU( + const Context& ctx, + sk_sp background, + const SkIPoint& backgroundOffset, + sk_sp foreground, + const SkIPoint& foregroundOffset, + const SkIRect& bounds) const { + SkASSERT(ctx.gpuBacked()); + + auto context = ctx.getContext(); + + sk_sp backgroundProxy, foregroundProxy; + + if (background) { + backgroundProxy = background->asTextureProxyRef(context); + } + + if (foreground) { + foregroundProxy = foreground->asTextureProxyRef(context); + } + + GrPaint paint; + std::unique_ptr bgFP; + + if (backgroundProxy) { + SkIRect bgSubset = background->subset(); + SkMatrix bgMatrix = SkMatrix::MakeTrans( + SkIntToScalar(bgSubset.left() - backgroundOffset.fX), + SkIntToScalar(bgSubset.top() - backgroundOffset.fY)); + GrColorType bgColorType = SkColorTypeToGrColorType(background->colorType()); + bgFP = GrTextureDomainEffect::Make( + std::move(backgroundProxy), bgColorType, bgMatrix, + GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode), + GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); + bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(), + background->alphaType(), + ctx.colorSpace()); + } else { + bgFP = GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kIgnore); + } + + if (foregroundProxy) { + SkIRect fgSubset = foreground->subset(); + SkMatrix fgMatrix = SkMatrix::MakeTrans( + SkIntToScalar(fgSubset.left() - foregroundOffset.fX), + SkIntToScalar(fgSubset.top() - foregroundOffset.fY)); + GrColorType fgColorType = SkColorTypeToGrColorType(foreground->colorType()); + auto foregroundFP = GrTextureDomainEffect::Make( + std::move(foregroundProxy), fgColorType, fgMatrix, + GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode), + GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest); + foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP), + foreground->getColorSpace(), + foreground->alphaType(), + ctx.colorSpace()); + paint.addColorFragmentProcessor(std::move(foregroundFP)); + + std::unique_ptr xferFP = this->makeFGFrag(std::move(bgFP)); + + // A null 'xferFP' here means kSrc_Mode was used in which case we can just proceed + if (xferFP) { + paint.addColorFragmentProcessor(std::move(xferFP)); + } + } else { + paint.addColorFragmentProcessor(std::move(bgFP)); + } + + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + auto renderTargetContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, bounds.width(), bounds.height(), ctx.grColorType(), + ctx.refColorSpace()); + if (!renderTargetContext) { + return nullptr; + } + + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, + SkRect::Make(bounds)); + + return SkSpecialImage::MakeDeferredFromGpu(context, + SkIRect::MakeWH(bounds.width(), bounds.height()), + kNeedNewImageUniqueID_SpecialImage, + renderTargetContext->asTextureProxyRef(), + renderTargetContext->colorInfo().colorType(), + renderTargetContext->colorInfo().refColorSpace()); +} + +std::unique_ptr SkXfermodeImageFilterImpl::makeFGFrag( + std::unique_ptr bgFP) const { + return GrXfermodeFragmentProcessor::MakeFromDstProcessor(std::move(bgFP), fMode); +} + +#endif diff --git a/gfx/skia/skia/src/fonts/SkFontMgr_indirect.cpp b/gfx/skia/skia/src/fonts/SkFontMgr_indirect.cpp new file mode 100644 index 0000000000..da7678a725 --- /dev/null +++ b/gfx/skia/skia/src/fonts/SkFontMgr_indirect.cpp @@ -0,0 +1,187 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/ports/SkFontMgr_indirect.h" +#include "include/ports/SkRemotableFontMgr.h" +#include "include/private/SkMutex.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" + +class SkData; + +class SkStyleSet_Indirect : public SkFontStyleSet { +public: + /** Takes ownership of the SkRemotableFontIdentitySet. */ + SkStyleSet_Indirect(const SkFontMgr_Indirect* owner, int familyIndex, + SkRemotableFontIdentitySet* data) + : fOwner(SkRef(owner)), fFamilyIndex(familyIndex), fData(data) + { } + + int count() override { return fData->count(); } + + void getStyle(int index, SkFontStyle* fs, SkString* style) override { + if (fs) { + *fs = fData->at(index).fFontStyle; + } + if (style) { + // TODO: is this useful? Current locale? + style->reset(); + } + } + + SkTypeface* createTypeface(int index) override { + return fOwner->createTypefaceFromFontId(fData->at(index)); + } + + SkTypeface* matchStyle(const SkFontStyle& pattern) override { + if (fFamilyIndex >= 0) { + SkFontIdentity id = fOwner->fProxy->matchIndexStyle(fFamilyIndex, pattern); + return fOwner->createTypefaceFromFontId(id); + } + + return this->matchStyleCSS3(pattern); + } +private: + sk_sp fOwner; + int fFamilyIndex; + sk_sp fData; +}; + +int SkFontMgr_Indirect::onCountFamilies() const { + return 0; +} + +void SkFontMgr_Indirect::onGetFamilyName(int index, SkString* familyName) const { + SK_ABORT("Not implemented"); +} + +SkFontStyleSet* SkFontMgr_Indirect::onCreateStyleSet(int index) const { + SK_ABORT("Not implemented"); +} + +SkFontStyleSet* SkFontMgr_Indirect::onMatchFamily(const char familyName[]) const { + return new SkStyleSet_Indirect(this, -1, fProxy->matchName(familyName)); +} + +SkTypeface* SkFontMgr_Indirect::createTypefaceFromFontId(const SkFontIdentity& id) const { + if (id.fDataId == SkFontIdentity::kInvalidDataId) { + return nullptr; + } + + SkAutoMutexExclusive ama(fDataCacheMutex); + + sk_sp dataTypeface; + int dataTypefaceIndex = 0; + for (int i = 0; i < fDataCache.count(); ++i) { + const DataEntry& entry = fDataCache[i]; + if (entry.fDataId == id.fDataId) { + if (entry.fTtcIndex == id.fTtcIndex && + !entry.fTypeface->weak_expired() && entry.fTypeface->try_ref()) + { + return entry.fTypeface; + } + if (dataTypeface.get() == nullptr && + !entry.fTypeface->weak_expired() && entry.fTypeface->try_ref()) + { + dataTypeface.reset(entry.fTypeface); + dataTypefaceIndex = entry.fTtcIndex; + } + } + + if (entry.fTypeface->weak_expired()) { + fDataCache.removeShuffle(i); + --i; + } + } + + // No exact match, but did find a data match. + if (dataTypeface.get() != nullptr) { + std::unique_ptr stream(dataTypeface->openStream(nullptr)); + if (stream.get() != nullptr) { + return fImpl->makeFromStream(std::move(stream), dataTypefaceIndex).release(); + } + } + + // No data match, request data and add entry. + std::unique_ptr stream(fProxy->getData(id.fDataId)); + if (stream.get() == nullptr) { + return nullptr; + } + + sk_sp typeface(fImpl->makeFromStream(std::move(stream), id.fTtcIndex)); + if (typeface.get() == nullptr) { + return nullptr; + } + + DataEntry& newEntry = fDataCache.push_back(); + typeface->weak_ref(); + newEntry.fDataId = id.fDataId; + newEntry.fTtcIndex = id.fTtcIndex; + newEntry.fTypeface = typeface.get(); // weak reference passed to new entry. + + return typeface.release(); +} + +SkTypeface* SkFontMgr_Indirect::onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontStyle) const { + SkFontIdentity id = fProxy->matchNameStyle(familyName, fontStyle); + return this->createTypefaceFromFontId(id); +} + +SkTypeface* SkFontMgr_Indirect::onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const { + SkFontIdentity id = fProxy->matchNameStyleCharacter(familyName, style, bcp47, + bcp47Count, character); + return this->createTypefaceFromFontId(id); +} + +SkTypeface* SkFontMgr_Indirect::onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontStyle) const { + SkString familyName; + familyMember->getFamilyName(&familyName); + return this->matchFamilyStyle(familyName.c_str(), fontStyle); +} + +sk_sp SkFontMgr_Indirect::onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const { + return fImpl->makeFromStream(std::move(stream), ttcIndex); +} + +sk_sp SkFontMgr_Indirect::onMakeFromFile(const char path[], int ttcIndex) const { + return fImpl->makeFromFile(path, ttcIndex); +} + +sk_sp SkFontMgr_Indirect::onMakeFromData(sk_sp data, int ttcIndex) const { + return fImpl->makeFromData(std::move(data), ttcIndex); +} + +sk_sp SkFontMgr_Indirect::onLegacyMakeTypeface(const char familyName[], + SkFontStyle style) const { + sk_sp face(this->matchFamilyStyle(familyName, style)); + + if (nullptr == face.get()) { + face.reset(this->matchFamilyStyle(nullptr, style)); + } + + if (nullptr == face.get()) { + SkFontIdentity fontId = this->fProxy->matchIndexStyle(0, style); + face.reset(this->createTypefaceFromFontId(fontId)); + } + + return face; +} diff --git a/gfx/skia/skia/src/fonts/SkRemotableFontMgr.cpp b/gfx/skia/skia/src/fonts/SkRemotableFontMgr.cpp new file mode 100644 index 0000000000..9b89a53c2d --- /dev/null +++ b/gfx/skia/skia/src/fonts/SkRemotableFontMgr.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/ports/SkRemotableFontMgr.h" +#include "include/private/SkOnce.h" + +SkRemotableFontIdentitySet::SkRemotableFontIdentitySet(int count, SkFontIdentity** data) + : fCount(count), fData(count) +{ + SkASSERT(data); + *data = fData; +} + +SkRemotableFontIdentitySet* SkRemotableFontIdentitySet::NewEmpty() { + static SkOnce once; + static SkRemotableFontIdentitySet* empty; + once([]{ empty = new SkRemotableFontIdentitySet; }); + return SkRef(empty); +} diff --git a/gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.cpp b/gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.cpp new file mode 100644 index 0000000000..34b9c07717 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.cpp @@ -0,0 +1,230 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + + +#include "src/gpu/GrAHardwareBufferImageGenerator.h" + +#include + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/gpu/gl/GrGLTypes.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkExchange.h" +#include "src/core/SkMessageBus.h" +#include "src/gpu/GrAHardwareBufferUtils.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrResourceProviderPriv.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/gl/GrGLDefines.h" + +#include +#include +#include +#include + +#ifdef SK_VULKAN +#include "include/gpu/vk/GrVkExtensions.h" +#include "src/gpu/vk/GrVkGpu.h" +#endif + +#define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content" +#define EGL_PROTECTED_CONTENT_EXT 0x32C0 + +std::unique_ptr GrAHardwareBufferImageGenerator::Make( + AHardwareBuffer* graphicBuffer, SkAlphaType alphaType, sk_sp colorSpace, + GrSurfaceOrigin surfaceOrigin) { + AHardwareBuffer_Desc bufferDesc; + AHardwareBuffer_describe(graphicBuffer, &bufferDesc); + + SkColorType colorType = + GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(bufferDesc.format); + SkImageInfo info = SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType, + alphaType, std::move(colorSpace)); + + bool createProtectedImage = 0 != (bufferDesc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); + return std::unique_ptr(new GrAHardwareBufferImageGenerator( + info, graphicBuffer, alphaType, createProtectedImage, + bufferDesc.format, surfaceOrigin)); +} + +GrAHardwareBufferImageGenerator::GrAHardwareBufferImageGenerator(const SkImageInfo& info, + AHardwareBuffer* hardwareBuffer, SkAlphaType alphaType, bool isProtectedContent, + uint32_t bufferFormat, GrSurfaceOrigin surfaceOrigin) + : INHERITED(info) + , fHardwareBuffer(hardwareBuffer) + , fBufferFormat(bufferFormat) + , fIsProtectedContent(isProtectedContent) + , fSurfaceOrigin(surfaceOrigin) { + AHardwareBuffer_acquire(fHardwareBuffer); +} + +GrAHardwareBufferImageGenerator::~GrAHardwareBufferImageGenerator() { + AHardwareBuffer_release(fHardwareBuffer); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp GrAHardwareBufferImageGenerator::makeProxy(GrRecordingContext* context) { + if (context->priv().abandoned()) { + return nullptr; + } + + auto direct = context->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(direct, + fHardwareBuffer, + fBufferFormat, + false); + + GrColorType grColorType = SkColorTypeToGrColorType(this->getInfo().colorType()); + GrPixelConfig pixelConfig = context->priv().caps()->getConfigFromBackendFormat(backendFormat, + grColorType); + + if (pixelConfig == kUnknown_GrPixelConfig) { + return nullptr; + } + + int width = this->getInfo().width(); + int height = this->getInfo().height(); + + GrSurfaceDesc desc; + desc.fWidth = width; + desc.fHeight = height; + desc.fConfig = pixelConfig; + + GrTextureType textureType = GrTextureType::k2D; + if (context->backend() == GrBackendApi::kOpenGL) { + textureType = GrTextureType::kExternal; + } else if (context->backend() == GrBackendApi::kVulkan) { + VkFormat format; + SkAssertResult(backendFormat.asVkFormat(&format)); + if (format == VK_FORMAT_UNDEFINED) { + textureType = GrTextureType::kExternal; + } + } + + auto proxyProvider = context->priv().proxyProvider(); + + AHardwareBuffer* hardwareBuffer = fHardwareBuffer; + AHardwareBuffer_acquire(hardwareBuffer); + + const bool isProtectedContent = fIsProtectedContent; + + class AutoAHBRelease { + public: + AutoAHBRelease(AHardwareBuffer* ahb) : fAhb(ahb) {} + // std::function() must be CopyConstructible, but ours should never actually be copied. + AutoAHBRelease(const AutoAHBRelease&) { SkASSERT(0); } + AutoAHBRelease(AutoAHBRelease&& that) : fAhb(that.fAhb) { that.fAhb = nullptr; } + ~AutoAHBRelease() { fAhb ? AHardwareBuffer_release(fAhb) : void(); } + + AutoAHBRelease& operator=(AutoAHBRelease&& that) { + fAhb = skstd::exchange(that.fAhb, nullptr); + return *this; + } + AutoAHBRelease& operator=(const AutoAHBRelease&) = delete; + + AHardwareBuffer* get() const { return fAhb; } + + private: + AHardwareBuffer* fAhb; + }; + + sk_sp texProxy = proxyProvider->createLazyProxy( + [direct, buffer = AutoAHBRelease(hardwareBuffer), width, height, isProtectedContent, + backendFormat, grColorType]( + GrResourceProvider* resourceProvider) -> GrSurfaceProxy::LazyCallbackResult { + GrAHardwareBufferUtils::DeleteImageProc deleteImageProc = nullptr; + GrAHardwareBufferUtils::UpdateImageProc updateImageProc = nullptr; + GrAHardwareBufferUtils::TexImageCtx texImageCtx = nullptr; + + GrBackendTexture backendTex = + GrAHardwareBufferUtils::MakeBackendTexture(direct, buffer.get(), + width, height, + &deleteImageProc, + &updateImageProc, + &texImageCtx, + isProtectedContent, + backendFormat, + false); + if (!backendTex.isValid()) { + return {}; + } + SkASSERT(deleteImageProc && texImageCtx); + + // We make this texture cacheable to avoid recreating a GrTexture every time this + // is invoked. We know the owning SkIamge will send an invalidation message when the + // image is destroyed, so the texture will be removed at that time. + sk_sp tex = resourceProvider->wrapBackendTexture( + backendTex, grColorType, kBorrow_GrWrapOwnership, GrWrapCacheable::kYes, + kRead_GrIOType); + if (!tex) { + deleteImageProc(texImageCtx); + return {}; + } + + if (deleteImageProc) { + tex->setRelease(deleteImageProc, texImageCtx); + } + + return tex; + }, + backendFormat, desc, GrRenderable::kNo, 1, fSurfaceOrigin, GrMipMapped::kNo, + GrMipMapsStatus::kNotAllocated, GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, + SkBudgeted::kNo, GrProtected::kNo, GrSurfaceProxy::UseAllocator::kYes); + + return texProxy; +} + +sk_sp GrAHardwareBufferImageGenerator::onGenerateTexture( + GrRecordingContext* context, const SkImageInfo& info, + const SkIPoint& origin, bool willNeedMipMaps) { + sk_sp texProxy = this->makeProxy(context); + if (!texProxy) { + return nullptr; + } + + if (0 == origin.fX && 0 == origin.fY && + info.width() == this->getInfo().width() && info.height() == this->getInfo().height()) { + // If the caller wants the full texture we're done. The caller will handle making a copy for + // mip maps if that is required. + return texProxy; + } + // Otherwise, make a copy for the requested subset. + SkIRect subset = SkIRect::MakeXYWH(origin.fX, origin.fY, info.width(), info.height()); + + GrMipMapped mipMapped = willNeedMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo; + + GrColorType grColorType = SkColorTypeToGrColorType(this->getInfo().colorType()); + return GrSurfaceProxy::Copy(context, texProxy.get(), grColorType, mipMapped, subset, + SkBackingFit::kExact, SkBudgeted::kYes); +} + +bool GrAHardwareBufferImageGenerator::onIsValid(GrContext* context) const { + if (nullptr == context) { + return false; //CPU backend is not supported, because hardware buffer can be swizzled + } + return GrBackendApi::kOpenGL == context->backend() || + GrBackendApi::kVulkan == context->backend(); +} + +#endif //SK_BUILD_FOR_ANDROID_FRAMEWORK diff --git a/gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.h b/gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.h new file mode 100644 index 0000000000..f200d39073 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAHardwareBufferImageGenerator.h @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrAHardwareBufferImageGenerator_DEFINED +#define GrAHardwareBufferImageGenerator_DEFINED + +#include "include/core/SkImageGenerator.h" + +#include "include/private/GrTypesPriv.h" + +class GrGpuResource; + +extern "C" { + typedef struct AHardwareBuffer AHardwareBuffer; +} + +/** + * GrAHardwareBufferImageGenerator allows to create an SkImage attached to + * an existing android native hardware buffer. A hardware buffer has to be + * created with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE usage, because it is + * bound to an external texture using an EGLImage. The image generator will + * keep a reference to the hardware buffer for its lifetime. A hardware buffer + * can be shared between processes and same buffer can be used in multiple GPU + * contexts. + * To implement certain features like tiling, Skia may copy the texture to + * avoid OpenGL API limitations. + */ +class GrAHardwareBufferImageGenerator : public SkImageGenerator { +public: + static std::unique_ptr Make(AHardwareBuffer*, SkAlphaType, + sk_sp, GrSurfaceOrigin); + + ~GrAHardwareBufferImageGenerator() override; + + static void DeleteGLTexture(void* ctx); + +protected: + + bool onIsValid(GrContext*) const override; + + TexGenType onCanGenerateTexture() const override { return TexGenType::kCheap; } + sk_sp onGenerateTexture(GrRecordingContext*, const SkImageInfo&, + const SkIPoint&, bool willNeedMipMaps) override; + +private: + GrAHardwareBufferImageGenerator(const SkImageInfo&, AHardwareBuffer*, SkAlphaType, + bool isProtectedContent, uint32_t bufferFormat, + GrSurfaceOrigin surfaceOrigin); + sk_sp makeProxy(GrRecordingContext* context); + + void releaseTextureRef(); + + static void ReleaseRefHelper_TextureReleaseProc(void* ctx); + + AHardwareBuffer* fHardwareBuffer; + uint32_t fBufferFormat; + const bool fIsProtectedContent; + GrSurfaceOrigin fSurfaceOrigin; + + typedef SkImageGenerator INHERITED; +}; +#endif // GrAHardwareBufferImageGenerator_DEFINED diff --git a/gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.cpp b/gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.cpp new file mode 100644 index 0000000000..77a5ed067a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.cpp @@ -0,0 +1,556 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + +#include "src/gpu/GrAHardwareBufferUtils.h" + +#include +#include +#include +#include +#include + +#include "include/gpu/GrContext.h" +#include "include/gpu/gl/GrGLTypes.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/gl/GrGLDefines.h" +#include "src/gpu/gl/GrGLUtil.h" + +#ifdef SK_VULKAN +#include "src/gpu/vk/GrVkCaps.h" +#include "src/gpu/vk/GrVkGpu.h" +#endif + +#define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content" +#define EGL_PROTECTED_CONTENT_EXT 0x32C0 + +#define VK_CALL(X) gpu->vkInterface()->fFunctions.f##X; + +namespace GrAHardwareBufferUtils { + +SkColorType GetSkColorTypeFromBufferFormat(uint32_t bufferFormat) { + switch (bufferFormat) { + case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: + return kRGBA_8888_SkColorType; + case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: + return kRGB_888x_SkColorType; + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + return kRGBA_F16_SkColorType; + case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: + return kRGB_565_SkColorType; + case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: + return kRGB_888x_SkColorType; + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + return kRGBA_1010102_SkColorType; + default: + // Given that we only use this texture as a source, colorType will not impact how Skia + // uses the texture. The only potential affect this is anticipated to have is that for + // some format types if we are not bound as an OES texture we may get invalid results + // for SKP capture if we read back the texture. + return kRGBA_8888_SkColorType; + } +} + +GrBackendFormat GetBackendFormat(GrContext* context, AHardwareBuffer* hardwareBuffer, + uint32_t bufferFormat, bool requireKnownFormat) { + GrBackendApi backend = context->backend(); + + if (backend == GrBackendApi::kOpenGL) { + switch (bufferFormat) { + //TODO: find out if we can detect, which graphic buffers support GR_GL_TEXTURE_2D + case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: + case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: + return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + return GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: + return GrBackendFormat::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + return GrBackendFormat::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: + return GrBackendFormat::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL); + default: + if (requireKnownFormat) { + return GrBackendFormat(); + } else { + return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); + } + } + } else if (backend == GrBackendApi::kVulkan) { +#ifdef SK_VULKAN + switch (bufferFormat) { + case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: + return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM); + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + return GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT); + case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: + return GrBackendFormat::MakeVk(VK_FORMAT_R5G6B5_UNORM_PACK16); + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + return GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32); + case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: + return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM); + case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: + return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM); + default: { + if (requireKnownFormat) { + return GrBackendFormat(); + } else { + GrVkGpu* gpu = static_cast(context->priv().getGpu()); + SkASSERT(gpu); + VkDevice device = gpu->device(); + + if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) { + return GrBackendFormat(); + } + VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps; + hwbFormatProps.sType = + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID; + hwbFormatProps.pNext = nullptr; + + VkAndroidHardwareBufferPropertiesANDROID hwbProps; + hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID; + hwbProps.pNext = &hwbFormatProps; + + VkResult err = VK_CALL(GetAndroidHardwareBufferProperties(device, + hardwareBuffer, + &hwbProps)); + if (VK_SUCCESS != err) { + return GrBackendFormat(); + } + + if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) { + return GrBackendFormat(); + } + + GrVkYcbcrConversionInfo ycbcrConversion; + ycbcrConversion.fYcbcrModel = hwbFormatProps.suggestedYcbcrModel; + ycbcrConversion.fYcbcrRange = hwbFormatProps.suggestedYcbcrRange; + ycbcrConversion.fXChromaOffset = hwbFormatProps.suggestedXChromaOffset; + ycbcrConversion.fYChromaOffset = hwbFormatProps.suggestedYChromaOffset; + ycbcrConversion.fForceExplicitReconstruction = VK_FALSE; + ycbcrConversion.fExternalFormat = hwbFormatProps.externalFormat; + ycbcrConversion.fFormatFeatures = hwbFormatProps.formatFeatures; + if (VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT & + hwbFormatProps.formatFeatures) { + ycbcrConversion.fChromaFilter = VK_FILTER_LINEAR; + } else { + ycbcrConversion.fChromaFilter = VK_FILTER_NEAREST; + } + + return GrBackendFormat::MakeVk(ycbcrConversion); + } + } + } +#else + return GrBackendFormat(); +#endif + } + return GrBackendFormat(); +} + +class GLTextureHelper { +public: + GLTextureHelper(GrGLuint texID, EGLImageKHR image, EGLDisplay display, GrGLuint texTarget) + : fTexID(texID) + , fImage(image) + , fDisplay(display) + , fTexTarget(texTarget) { } + ~GLTextureHelper() { + glDeleteTextures(1, &fTexID); + // eglDestroyImageKHR will remove a ref from the AHardwareBuffer + eglDestroyImageKHR(fDisplay, fImage); + } + void rebind(GrContext* grContext); + +private: + GrGLuint fTexID; + EGLImageKHR fImage; + EGLDisplay fDisplay; + GrGLuint fTexTarget; +}; + +void GLTextureHelper::rebind(GrContext* grContext) { + glBindTexture(fTexTarget, fTexID); + GLenum status = GL_NO_ERROR; + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glBindTexture(%#x, %d) failed (%#x)", (int) fTexTarget, + (int) fTexID, (int) status); + return; + } + glEGLImageTargetTexture2DOES(fTexTarget, fImage); + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", (int) status); + return; + } + grContext->resetContext(kTextureBinding_GrGLBackendState); +} + +void delete_gl_texture(void* context) { + GLTextureHelper* cleanupHelper = static_cast(context); + delete cleanupHelper; +} + +void update_gl_texture(void* context, GrContext* grContext) { + GLTextureHelper* cleanupHelper = static_cast(context); + cleanupHelper->rebind(grContext); +} + +static GrBackendTexture make_gl_backend_texture( + GrContext* context, AHardwareBuffer* hardwareBuffer, + int width, int height, + DeleteImageProc* deleteProc, + UpdateImageProc* updateProc, + TexImageCtx* imageCtx, + bool isProtectedContent, + const GrBackendFormat& backendFormat, + bool isRenderable) { + while (GL_NO_ERROR != glGetError()) {} //clear GL errors + + EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer); + EGLint attribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + isProtectedContent ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE, + isProtectedContent ? EGL_TRUE : EGL_NONE, + EGL_NONE }; + EGLDisplay display = eglGetCurrentDisplay(); + // eglCreateImageKHR will add a ref to the AHardwareBuffer + EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, + clientBuffer, attribs); + if (EGL_NO_IMAGE_KHR == image) { + SkDebugf("Could not create EGL image, err = (%#x)", (int) eglGetError() ); + return GrBackendTexture(); + } + + GrGLuint texID; + glGenTextures(1, &texID); + if (!texID) { + eglDestroyImageKHR(display, image); + return GrBackendTexture(); + } + + GrGLuint target = isRenderable ? GR_GL_TEXTURE_2D : GR_GL_TEXTURE_EXTERNAL; + + glBindTexture(target, texID); + GLenum status = GL_NO_ERROR; + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glBindTexture failed (%#x)", (int) status); + glDeleteTextures(1, &texID); + eglDestroyImageKHR(display, image); + return GrBackendTexture(); + } + glEGLImageTargetTexture2DOES(target, image); + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", (int) status); + glDeleteTextures(1, &texID); + eglDestroyImageKHR(display, image); + return GrBackendTexture(); + } + context->resetContext(kTextureBinding_GrGLBackendState); + + GrGLTextureInfo textureInfo; + textureInfo.fID = texID; + SkASSERT(backendFormat.isValid()); + textureInfo.fTarget = target; + textureInfo.fFormat = GrGLFormatToEnum(backendFormat.asGLFormat()); + + *deleteProc = delete_gl_texture; + *updateProc = update_gl_texture; + *imageCtx = new GLTextureHelper(texID, image, display, target); + + return GrBackendTexture(width, height, GrMipMapped::kNo, textureInfo); +} + +#ifdef SK_VULKAN +class VulkanCleanupHelper { +public: + VulkanCleanupHelper(GrVkGpu* gpu, VkImage image, VkDeviceMemory memory) + : fDevice(gpu->device()) + , fImage(image) + , fMemory(memory) + , fDestroyImage(gpu->vkInterface()->fFunctions.fDestroyImage) + , fFreeMemory(gpu->vkInterface()->fFunctions.fFreeMemory) {} + ~VulkanCleanupHelper() { + fDestroyImage(fDevice, fImage, nullptr); + fFreeMemory(fDevice, fMemory, nullptr); + } +private: + VkDevice fDevice; + VkImage fImage; + VkDeviceMemory fMemory; + PFN_vkDestroyImage fDestroyImage; + PFN_vkFreeMemory fFreeMemory; +}; + +void delete_vk_image(void* context) { + VulkanCleanupHelper* cleanupHelper = static_cast(context); + delete cleanupHelper; +} + +void update_vk_image(void* context, GrContext* grContext) { + // no op +} + +static GrBackendTexture make_vk_backend_texture( + GrContext* context, AHardwareBuffer* hardwareBuffer, + int width, int height, + DeleteImageProc* deleteProc, + UpdateImageProc* updateProc, + TexImageCtx* imageCtx, + bool isProtectedContent, + const GrBackendFormat& backendFormat, + bool isRenderable) { + SkASSERT(context->backend() == GrBackendApi::kVulkan); + GrVkGpu* gpu = static_cast(context->priv().getGpu()); + + VkPhysicalDevice physicalDevice = gpu->physicalDevice(); + VkDevice device = gpu->device(); + + SkASSERT(gpu); + + if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) { + return GrBackendTexture(); + } + + VkFormat format; + SkAssertResult(backendFormat.asVkFormat(&format)); + + VkResult err; + + VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps; + hwbFormatProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID; + hwbFormatProps.pNext = nullptr; + + VkAndroidHardwareBufferPropertiesANDROID hwbProps; + hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID; + hwbProps.pNext = &hwbFormatProps; + + err = VK_CALL(GetAndroidHardwareBufferProperties(device, hardwareBuffer, &hwbProps)); + if (VK_SUCCESS != err) { + return GrBackendTexture(); + } + + VkExternalFormatANDROID externalFormat; + externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID; + externalFormat.pNext = nullptr; + externalFormat.externalFormat = 0; // If this is zero it is as if we aren't using this struct. + + const GrVkYcbcrConversionInfo* ycbcrConversion = backendFormat.getVkYcbcrConversionInfo(); + if (!ycbcrConversion) { + return GrBackendTexture(); + } + + if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) { + // TODO: We should not assume the transfer features here and instead should have a way for + // Ganesh's tracking of intenral images to report whether or not they support transfers. + SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures) && + SkToBool(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT & hwbFormatProps.formatFeatures) && + SkToBool(VK_FORMAT_FEATURE_TRANSFER_DST_BIT & hwbFormatProps.formatFeatures)); + SkASSERT(!ycbcrConversion->isValid()); + } else { + SkASSERT(ycbcrConversion->isValid()); + // We have an external only format + SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures)); + SkASSERT(format == VK_FORMAT_UNDEFINED); + SkASSERT(hwbFormatProps.externalFormat == ycbcrConversion->fExternalFormat); + externalFormat.externalFormat = hwbFormatProps.externalFormat; + } + SkASSERT(format == hwbFormatProps.format); + + const VkExternalMemoryImageCreateInfo externalMemoryImageInfo{ + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, // sType + &externalFormat, // pNext + VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, // handleTypes + }; + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; + if (format != VK_FORMAT_UNDEFINED) { + usageFlags = usageFlags | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (isRenderable) { + usageFlags = usageFlags | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + } + + // TODO: Check the supported tilings vkGetPhysicalDeviceImageFormatProperties2 to see if we have + // to use linear. Add better linear support throughout Ganesh. + VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; + + const VkImageCreateInfo imageCreateInfo = { + VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType + &externalMemoryImageInfo, // pNext + 0, // VkImageCreateFlags + VK_IMAGE_TYPE_2D, // VkImageType + format, // VkFormat + { (uint32_t)width, (uint32_t)height, 1 }, // VkExtent3D + 1, // mipLevels + 1, // arrayLayers + VK_SAMPLE_COUNT_1_BIT, // samples + tiling, // VkImageTiling + usageFlags, // VkImageUsageFlags + VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode + 0, // queueFamilyCount + 0, // pQueueFamilyIndices + VK_IMAGE_LAYOUT_UNDEFINED, // initialLayout + }; + + VkImage image; + err = VK_CALL(CreateImage(device, &imageCreateInfo, nullptr, &image)); + if (VK_SUCCESS != err) { + return GrBackendTexture(); + } + + VkPhysicalDeviceMemoryProperties2 phyDevMemProps; + phyDevMemProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2; + phyDevMemProps.pNext = nullptr; + + uint32_t typeIndex = 0; + uint32_t heapIndex = 0; + bool foundHeap = false; + VK_CALL(GetPhysicalDeviceMemoryProperties2(physicalDevice, &phyDevMemProps)); + uint32_t memTypeCnt = phyDevMemProps.memoryProperties.memoryTypeCount; + for (uint32_t i = 0; i < memTypeCnt && !foundHeap; ++i) { + if (hwbProps.memoryTypeBits & (1 << i)) { + const VkPhysicalDeviceMemoryProperties& pdmp = phyDevMemProps.memoryProperties; + uint32_t supportedFlags = pdmp.memoryTypes[i].propertyFlags & + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + if (supportedFlags == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { + typeIndex = i; + heapIndex = pdmp.memoryTypes[i].heapIndex; + foundHeap = true; + } + } + } + if (!foundHeap) { + VK_CALL(DestroyImage(device, image, nullptr)); + return GrBackendTexture(); + } + + VkImportAndroidHardwareBufferInfoANDROID hwbImportInfo; + hwbImportInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID; + hwbImportInfo.pNext = nullptr; + hwbImportInfo.buffer = hardwareBuffer; + + VkMemoryDedicatedAllocateInfo dedicatedAllocInfo; + dedicatedAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; + dedicatedAllocInfo.pNext = &hwbImportInfo; + dedicatedAllocInfo.image = image; + dedicatedAllocInfo.buffer = VK_NULL_HANDLE; + + VkMemoryAllocateInfo allocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType + &dedicatedAllocInfo, // pNext + hwbProps.allocationSize, // allocationSize + typeIndex, // memoryTypeIndex + }; + + VkDeviceMemory memory; + + err = VK_CALL(AllocateMemory(device, &allocInfo, nullptr, &memory)); + if (VK_SUCCESS != err) { + VK_CALL(DestroyImage(device, image, nullptr)); + return GrBackendTexture(); + } + + VkBindImageMemoryInfo bindImageInfo; + bindImageInfo.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + bindImageInfo.pNext = nullptr; + bindImageInfo.image = image; + bindImageInfo.memory = memory; + bindImageInfo.memoryOffset = 0; + + err = VK_CALL(BindImageMemory2(device, 1, &bindImageInfo)); + if (VK_SUCCESS != err) { + VK_CALL(DestroyImage(device, image, nullptr)); + VK_CALL(FreeMemory(device, memory, nullptr)); + return GrBackendTexture(); + } + + GrVkImageInfo imageInfo; + + imageInfo.fImage = image; + imageInfo.fAlloc = GrVkAlloc(memory, 0, hwbProps.allocationSize, 0); + imageInfo.fImageTiling = tiling; + imageInfo.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.fFormat = format; + imageInfo.fLevelCount = 1; + // TODO: This should possibly be VK_QUEUE_FAMILY_FOREIGN_EXT but current Adreno devices do not + // support that extension. Or if we know the source of the AHardwareBuffer is not from a + // "foreign" device we can leave them as external. + imageInfo.fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL; + imageInfo.fYcbcrConversionInfo = *ycbcrConversion; + + *deleteProc = delete_vk_image; + *updateProc = update_vk_image; + *imageCtx = new VulkanCleanupHelper(gpu, image, memory); + + return GrBackendTexture(width, height, imageInfo); +} +#endif + +static bool can_import_protected_content_eglimpl() { + EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + const char* exts = eglQueryString(dpy, EGL_EXTENSIONS); + size_t cropExtLen = strlen(PROT_CONTENT_EXT_STR); + size_t extsLen = strlen(exts); + bool equal = !strcmp(PROT_CONTENT_EXT_STR, exts); + bool atStart = !strncmp(PROT_CONTENT_EXT_STR " ", exts, cropExtLen+1); + bool atEnd = (cropExtLen+1) < extsLen + && !strcmp(" " PROT_CONTENT_EXT_STR, + exts + extsLen - (cropExtLen+1)); + bool inMiddle = strstr(exts, " " PROT_CONTENT_EXT_STR " "); + return equal || atStart || atEnd || inMiddle; +} + +static bool can_import_protected_content(GrContext* context) { + if (GrBackendApi::kOpenGL == context->backend()) { + // Only compute whether the extension is present once the first time this + // function is called. + static bool hasIt = can_import_protected_content_eglimpl(); + return hasIt; + } + return false; +} + +GrBackendTexture MakeBackendTexture(GrContext* context, AHardwareBuffer* hardwareBuffer, + int width, int height, + DeleteImageProc* deleteProc, + UpdateImageProc* updateProc, + TexImageCtx* imageCtx, + bool isProtectedContent, + const GrBackendFormat& backendFormat, + bool isRenderable) { + if (context->abandoned()) { + return GrBackendTexture(); + } + bool createProtectedImage = isProtectedContent && can_import_protected_content(context); + + if (GrBackendApi::kOpenGL == context->backend()) { + return make_gl_backend_texture(context, hardwareBuffer, width, height, deleteProc, + updateProc, imageCtx, createProtectedImage, backendFormat, + isRenderable); + } else { + SkASSERT(GrBackendApi::kVulkan == context->backend()); +#ifdef SK_VULKAN + // Currently we don't support protected images on vulkan + SkASSERT(!createProtectedImage); + return make_vk_backend_texture(context, hardwareBuffer, width, height, deleteProc, + updateProc, imageCtx, createProtectedImage, backendFormat, + isRenderable); +#else + return GrBackendTexture(); +#endif + } +} + +} // GrAHardwareBufferUtils + +#endif + diff --git a/gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.h b/gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.h new file mode 100644 index 0000000000..d0982f4bf2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAHardwareBufferUtils.h @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrAHardwareBufferUtils_DEFINED +#define GrAHardwareBufferUtils_DEFINED + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrTypes.h" + +class GrContext; + +extern "C" { + typedef struct AHardwareBuffer AHardwareBuffer; +} + +namespace GrAHardwareBufferUtils { + +SkColorType GetSkColorTypeFromBufferFormat(uint32_t bufferFormat); + +GrBackendFormat GetBackendFormat(GrContext* context, AHardwareBuffer* hardwareBuffer, + uint32_t bufferFormat, bool requireKnownFormat); + +typedef void* TexImageCtx; +typedef void (*DeleteImageProc)(TexImageCtx); +typedef void (*UpdateImageProc)(TexImageCtx, GrContext*); + +/** + * Create a GrBackendTexture from AHardwareBuffer + * + * @param context GPU context + * @param hardwareBuffer AHB + * @param width texture width + * @param height texture height + * @param deleteProc returns a function that deletes the texture and + * other GPU resources. Must be invoked on the same + * thread as MakeBackendTexture + * @param updateProc returns a function, that needs to be invoked, when + * AHB buffer content has changed. Must be invoked on + * the same thread as MakeBackendTexture + * @param imageCtx returns an opaque image context, that is passed as + * first argument to deleteProc and updateProc + * @param isProtectedContent if true, GL backend uses EXT_protected_content + * @param backendFormat backend format, usually created with helper + * function GetBackendFormat + * @param isRenderable true if GrBackendTexture can be used as a color + * attachment + * @return valid GrBackendTexture object on success + */ +GrBackendTexture MakeBackendTexture(GrContext* context, AHardwareBuffer* hardwareBuffer, + int width, int height, + DeleteImageProc* deleteProc, + UpdateImageProc* updateProc, + TexImageCtx* imageCtx, + bool isProtectedContent, + const GrBackendFormat& backendFormat, + bool isRenderable); + +} // GrAHardwareBufferUtils + + +#endif +#endif diff --git a/gfx/skia/skia/src/gpu/GrAllocator.h b/gfx/skia/skia/src/gpu/GrAllocator.h new file mode 100644 index 0000000000..b2945e8ab6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAllocator.h @@ -0,0 +1,439 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAllocator_DEFINED +#define GrAllocator_DEFINED + +#include "include/core/SkTypes.h" +#include "include/gpu/GrConfig.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTArray.h" +#include + +class GrAllocator : SkNoncopyable { +public: + ~GrAllocator() { this->reset(); } + + /** + * Create an allocator + * + * @param itemSize the size of each item to allocate + * @param itemsPerBlock the number of items to allocate at once + * @param initialBlock optional memory to use for the first block. + * Must be at least itemSize*itemsPerBlock sized. + * Caller is responsible for freeing this memory. + */ + GrAllocator(size_t itemSize, int itemsPerBlock, void* initialBlock) + : fItemSize(itemSize) + , fItemsPerBlock(itemsPerBlock) + , fOwnFirstBlock(nullptr == initialBlock) + , fCount(0) + , fInsertionIndexInBlock(0) { + SkASSERT(itemsPerBlock > 0); + fBlockSize = fItemSize * fItemsPerBlock; + if (fOwnFirstBlock) { + // This force us to allocate a new block on push_back(). + fInsertionIndexInBlock = fItemsPerBlock; + } else { + fBlocks.push_back() = initialBlock; + fInsertionIndexInBlock = 0; + } + } + + /** + * Adds an item and returns pointer to it. + * + * @return pointer to the added item. + */ + void* push_back() { + // we always have at least one block + if (fItemsPerBlock == fInsertionIndexInBlock) { + fBlocks.push_back() = sk_malloc_throw(fBlockSize); + fInsertionIndexInBlock = 0; + } + void* ret = (char*)fBlocks.back() + fItemSize * fInsertionIndexInBlock; + ++fCount; + ++fInsertionIndexInBlock; + return ret; + } + + /** + * Remove the last item, only call if count() != 0 + */ + void pop_back() { + SkASSERT(fCount); + SkASSERT(fInsertionIndexInBlock > 0); + --fInsertionIndexInBlock; + --fCount; + if (0 == fInsertionIndexInBlock) { + // Never delete the first block + if (fBlocks.count() > 1) { + sk_free(fBlocks.back()); + fBlocks.pop_back(); + fInsertionIndexInBlock = fItemsPerBlock; + } + } + } + + /** + * Removes all added items. + */ + void reset() { + int firstBlockToFree = fOwnFirstBlock ? 0 : 1; + for (int i = firstBlockToFree; i < fBlocks.count(); ++i) { + sk_free(fBlocks[i]); + } + if (fOwnFirstBlock) { + fBlocks.reset(); + // This force us to allocate a new block on push_back(). + fInsertionIndexInBlock = fItemsPerBlock; + } else { + fBlocks.pop_back_n(fBlocks.count() - 1); + fInsertionIndexInBlock = 0; + } + fCount = 0; + } + + /** + * Returns the item count. + */ + int count() const { + return fCount; + } + + /** + * Is the count 0? + */ + bool empty() const { return 0 == fCount; } + + /** + * Access first item, only call if count() != 0 + */ + void* front() { + SkASSERT(fCount); + SkASSERT(fInsertionIndexInBlock > 0); + return (char*)(fBlocks.front()); + } + + /** + * Access first item, only call if count() != 0 + */ + const void* front() const { + SkASSERT(fCount); + SkASSERT(fInsertionIndexInBlock > 0); + return (const char*)(fBlocks.front()); + } + + /** + * Access last item, only call if count() != 0 + */ + void* back() { + SkASSERT(fCount); + SkASSERT(fInsertionIndexInBlock > 0); + return (char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize; + } + + /** + * Access last item, only call if count() != 0 + */ + const void* back() const { + SkASSERT(fCount); + SkASSERT(fInsertionIndexInBlock > 0); + return (const char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize; + } + + /** + * Iterates through the allocator. This is faster than using operator[] when walking linearly + * through the allocator. + */ + class Iter { + public: + /** + * Initializes the iterator. next() must be called before get(). + */ + Iter(const GrAllocator* allocator) + : fAllocator(allocator) + , fBlockIndex(-1) + , fIndexInBlock(allocator->fItemsPerBlock - 1) + , fItemIndex(-1) {} + + /** + * Advances the iterator. Iteration is finished when next() returns false. + */ + bool next() { + ++fIndexInBlock; + ++fItemIndex; + if (fIndexInBlock == fAllocator->fItemsPerBlock) { + ++fBlockIndex; + fIndexInBlock = 0; + } + return fItemIndex < fAllocator->fCount; + } + + /** + * Gets the current iterator value. Call next() at least once before calling. Don't call + * after next() returns false. + */ + void* get() const { + SkASSERT(fItemIndex >= 0 && fItemIndex < fAllocator->fCount); + return (char*) fAllocator->fBlocks[fBlockIndex] + fIndexInBlock * fAllocator->fItemSize; + } + + private: + const GrAllocator* fAllocator; + int fBlockIndex; + int fIndexInBlock; + int fItemIndex; + }; + + /** + * Access item by index. + */ + void* operator[] (int i) { + SkASSERT(i >= 0 && i < fCount); + return (char*)fBlocks[i / fItemsPerBlock] + + fItemSize * (i % fItemsPerBlock); + } + + /** + * Access item by index. + */ + const void* operator[] (int i) const { + SkASSERT(i >= 0 && i < fCount); + return (const char*)fBlocks[i / fItemsPerBlock] + + fItemSize * (i % fItemsPerBlock); + } + +protected: + /** + * Set first block of memory to write into. Must be called before any other methods. + * This requires that you have passed nullptr in the constructor. + * + * @param initialBlock optional memory to use for the first block. + * Must be at least itemSize*itemsPerBlock sized. + * Caller is responsible for freeing this memory. + */ + void setInitialBlock(void* initialBlock) { + SkASSERT(0 == fCount); + SkASSERT(0 == fBlocks.count()); + SkASSERT(fItemsPerBlock == fInsertionIndexInBlock); + fOwnFirstBlock = false; + fBlocks.push_back() = initialBlock; + fInsertionIndexInBlock = 0; + } + + // For access to above function. + template friend class GrTAllocator; + +private: + static const int NUM_INIT_BLOCK_PTRS = 8; + + SkSTArray fBlocks; + size_t fBlockSize; + size_t fItemSize; + int fItemsPerBlock; + bool fOwnFirstBlock; + int fCount; + int fInsertionIndexInBlock; + + typedef SkNoncopyable INHERITED; +}; + +template class GrTAllocator; +template void* operator new(size_t, GrTAllocator*); + +template class GrTAllocator : SkNoncopyable { +public: + virtual ~GrTAllocator() { this->reset(); } + + /** + * Create an allocator + * + * @param itemsPerBlock the number of items to allocate at once + */ + explicit GrTAllocator(int itemsPerBlock) + : fAllocator(sizeof(T), itemsPerBlock, nullptr) {} + + /** + * Adds an item and returns it. + * + * @return the added item. + */ + T& push_back() { + void* item = fAllocator.push_back(); + SkASSERT(item); + new (item) T; + return *(T*)item; + } + + T& push_back(const T& t) { + void* item = fAllocator.push_back(); + SkASSERT(item); + new (item) T(t); + return *(T*)item; + } + + template T& emplace_back(Args&&... args) { + void* item = fAllocator.push_back(); + SkASSERT(item); + new (item) T(std::forward(args)...); + return *(T*)item; + } + + /** + * Remove the last item, only call if count() != 0 + */ + void pop_back() { + this->back().~T(); + fAllocator.pop_back(); + } + + /** + * Removes all added items. + */ + void reset() { + int c = fAllocator.count(); + for (int i = 0; i < c; ++i) { + ((T*)fAllocator[i])->~T(); + } + fAllocator.reset(); + } + + /** + * Returns the item count. + */ + int count() const { + return fAllocator.count(); + } + + /** + * Is the count 0? + */ + bool empty() const { return fAllocator.empty(); } + + /** + * Access first item, only call if count() != 0 + */ + T& front() { + return *(T*)fAllocator.front(); + } + + /** + * Access first item, only call if count() != 0 + */ + const T& front() const { + return *(T*)fAllocator.front(); + } + + /** + * Access last item, only call if count() != 0 + */ + T& back() { + return *(T*)fAllocator.back(); + } + + /** + * Access last item, only call if count() != 0 + */ + const T& back() const { + return *(const T*)fAllocator.back(); + } + + /** + * Iterates through the allocator. This is faster than using operator[] when walking linearly + * through the allocator. + */ + class Iter { + public: + /** + * Initializes the iterator. next() must be called before get() or ops * and ->. + */ + Iter(const GrTAllocator* allocator) : fImpl(&allocator->fAllocator) {} + + /** + * Advances the iterator. Iteration is finished when next() returns false. + */ + bool next() { return fImpl.next(); } + + /** + * Gets the current iterator value. Call next() at least once before calling. Don't call + * after next() returns false. + */ + T* get() const { return (T*) fImpl.get(); } + + /** + * Convenience operators. Same rules for calling apply as get(). + */ + T& operator*() const { return *this->get(); } + T* operator->() const { return this->get(); } + + private: + GrAllocator::Iter fImpl; + }; + + /** + * Access item by index. + */ + T& operator[] (int i) { + return *(T*)(fAllocator[i]); + } + + /** + * Access item by index. + */ + const T& operator[] (int i) const { + return *(const T*)(fAllocator[i]); + } + +protected: + /* + * Set first block of memory to write into. Must be called before any other methods. + * + * @param initialBlock optional memory to use for the first block. + * Must be at least size(T)*itemsPerBlock sized. + * Caller is responsible for freeing this memory. + */ + void setInitialBlock(void* initialBlock) { + fAllocator.setInitialBlock(initialBlock); + } + +private: + friend void* operator new(size_t, GrTAllocator*); + + GrAllocator fAllocator; + typedef SkNoncopyable INHERITED; +}; + +template class GrSTAllocator : public GrTAllocator { +private: + typedef GrTAllocator INHERITED; + +public: + GrSTAllocator() : INHERITED(N) { + this->setInitialBlock(fStorage.get()); + } + +private: + SkAlignedSTStorage fStorage; +}; + +template void* operator new(size_t size, GrTAllocator* allocator) { + return allocator->fAllocator.push_back(); +} + +// Skia doesn't use C++ exceptions but it may be compiled with them enabled. Having an op delete +// to match the op new silences warnings about missing op delete when a constructor throws an +// exception. +template void operator delete(void*, GrTAllocator*) { + SK_ABORT("Invalid Operation"); +} + +#define GrNEW_APPEND_TO_ALLOCATOR(allocator_ptr, type_name, args) \ + new (allocator_ptr) type_name args + +#endif diff --git a/gfx/skia/skia/src/gpu/GrAppliedClip.h b/gfx/skia/skia/src/gpu/GrAppliedClip.h new file mode 100644 index 0000000000..5e6e4cacce --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAppliedClip.h @@ -0,0 +1,141 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAppliedClip_DEFINED +#define GrAppliedClip_DEFINED + +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrScissorState.h" +#include "src/gpu/GrWindowRectsState.h" + +#include "src/core/SkClipStack.h" + + +/** + * Produced by GrHardClip. It provides a set of modifications to the hardware drawing state that + * implement the clip. + */ +class GrAppliedHardClip { +public: + GrAppliedHardClip() = default; + GrAppliedHardClip(GrAppliedHardClip&& that) = default; + GrAppliedHardClip(const GrAppliedHardClip&) = delete; + + const GrScissorState& scissorState() const { return fScissorState; } + const GrWindowRectsState& windowRectsState() const { return fWindowRectsState; } + uint32_t stencilStackID() const { return fStencilStackID; } + bool hasStencilClip() const { return SkClipStack::kInvalidGenID != fStencilStackID; } + + /** + * Intersects the applied clip with the provided rect. Returns false if the draw became empty. + * 'clippedDrawBounds' will be intersected with 'irect'. This returns false if the clip becomes + * empty or the draw no longer intersects the clip. In either case the draw can be skipped. + */ + bool addScissor(const SkIRect& irect, SkRect* clippedDrawBounds) { + return fScissorState.intersect(irect) && clippedDrawBounds->intersect(SkRect::Make(irect)); + } + + void addWindowRectangles(const GrWindowRectsState& windowState) { + SkASSERT(!fWindowRectsState.enabled()); + fWindowRectsState = windowState; + } + + void addWindowRectangles(const GrWindowRectangles& windows, GrWindowRectsState::Mode mode) { + SkASSERT(!fWindowRectsState.enabled()); + fWindowRectsState.set(windows, mode); + } + + void addStencilClip(uint32_t stencilStackID) { + SkASSERT(SkClipStack::kInvalidGenID == fStencilStackID); + fStencilStackID = stencilStackID; + } + + bool doesClip() const { + return fScissorState.enabled() || this->hasStencilClip() || fWindowRectsState.enabled(); + } + + bool operator==(const GrAppliedHardClip& that) const { + return fScissorState == that.fScissorState && + fWindowRectsState == that.fWindowRectsState && + fStencilStackID == that.fStencilStackID; + } + bool operator!=(const GrAppliedHardClip& that) const { return !(*this == that); } + +private: + GrScissorState fScissorState; + GrWindowRectsState fWindowRectsState; + uint32_t fStencilStackID = SkClipStack::kInvalidGenID; +}; + +/** + * Produced by GrClip. It provides a set of modifications to GrPipeline that implement the clip. + */ +class GrAppliedClip { +public: + GrAppliedClip() = default; + GrAppliedClip(GrAppliedClip&& that) = default; + GrAppliedClip(const GrAppliedClip&) = delete; + + const GrScissorState& scissorState() const { return fHardClip.scissorState(); } + const GrWindowRectsState& windowRectsState() const { return fHardClip.windowRectsState(); } + uint32_t stencilStackID() const { return fHardClip.stencilStackID(); } + bool hasStencilClip() const { return fHardClip.hasStencilClip(); } + int numClipCoverageFragmentProcessors() const { return fClipCoverageFPs.count(); } + const GrFragmentProcessor* clipCoverageFragmentProcessor(int i) const { + SkASSERT(fClipCoverageFPs[i]); + return fClipCoverageFPs[i].get(); + } + std::unique_ptr detachClipCoverageFragmentProcessor(int i) { + SkASSERT(fClipCoverageFPs[i]); + return std::move(fClipCoverageFPs[i]); + } + + GrAppliedHardClip& hardClip() { return fHardClip; } + + void addCoverageFP(std::unique_ptr fp) { + SkASSERT(fp); + fClipCoverageFPs.push_back(std::move(fp)); + } + + bool doesClip() const { + return fHardClip.doesClip() || !fClipCoverageFPs.empty(); + } + + bool operator==(const GrAppliedClip& that) const { + if (fHardClip != that.fHardClip || + fClipCoverageFPs.count() != that.fClipCoverageFPs.count()) { + return false; + } + for (int i = 0; i < fClipCoverageFPs.count(); ++i) { + if (!fClipCoverageFPs[i] || !that.fClipCoverageFPs[i]) { + if (fClipCoverageFPs[i] == that.fClipCoverageFPs[i]) { + continue; // Both are null. + } + return false; + } + if (!fClipCoverageFPs[i]->isEqual(*that.fClipCoverageFPs[i])) { + return false; + } + } + return true; + } + bool operator!=(const GrAppliedClip& that) const { return !(*this == that); } + + void visitProxies(const GrOp::VisitProxyFunc& func) const { + for (const std::unique_ptr& fp : fClipCoverageFPs) { + if (fp) { // This might be called after detach. + fp->visitProxies(func); + } + } + } + +private: + GrAppliedHardClip fHardClip; + SkSTArray<4, std::unique_ptr> fClipCoverageFPs; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrAuditTrail.cpp b/gfx/skia/skia/src/gpu/GrAuditTrail.cpp new file mode 100644 index 0000000000..9c72acf725 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAuditTrail.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/ops/GrOp.h" +#include "src/utils/SkJSONWriter.h" + +const int GrAuditTrail::kGrAuditTrailInvalidID = -1; + +void GrAuditTrail::addOp(const GrOp* op, GrRenderTargetProxy::UniqueID proxyID) { + SkASSERT(fEnabled); + Op* auditOp = new Op; + fOpPool.emplace_back(auditOp); + auditOp->fName = op->name(); + auditOp->fBounds = op->bounds(); + auditOp->fClientID = kGrAuditTrailInvalidID; + auditOp->fOpsTaskID = kGrAuditTrailInvalidID; + auditOp->fChildID = kGrAuditTrailInvalidID; + + // consume the current stack trace if any + auditOp->fStackTrace = fCurrentStackTrace; + fCurrentStackTrace.reset(); + + if (fClientID != kGrAuditTrailInvalidID) { + auditOp->fClientID = fClientID; + Ops** opsLookup = fClientIDLookup.find(fClientID); + Ops* ops = nullptr; + if (!opsLookup) { + ops = new Ops; + fClientIDLookup.set(fClientID, ops); + } else { + ops = *opsLookup; + } + + ops->push_back(auditOp); + } + + // Our algorithm doesn't bother to reorder inside of an OpNode so the ChildID will start at 0 + auditOp->fOpsTaskID = fOpsTask.count(); + auditOp->fChildID = 0; + + // We use the op pointer as a key to find the OpNode we are 'glomming' ops onto + fIDLookup.set(op->uniqueID(), auditOp->fOpsTaskID); + OpNode* opNode = new OpNode(proxyID); + opNode->fBounds = op->bounds(); + opNode->fChildren.push_back(auditOp); + fOpsTask.emplace_back(opNode); +} + +void GrAuditTrail::opsCombined(const GrOp* consumer, const GrOp* consumed) { + // Look up the op we are going to glom onto + int* indexPtr = fIDLookup.find(consumer->uniqueID()); + SkASSERT(indexPtr); + int index = *indexPtr; + SkASSERT(index < fOpsTask.count() && fOpsTask[index]); + OpNode& consumerOp = *fOpsTask[index]; + + // Look up the op which will be glommed + int* consumedPtr = fIDLookup.find(consumed->uniqueID()); + SkASSERT(consumedPtr); + int consumedIndex = *consumedPtr; + SkASSERT(consumedIndex < fOpsTask.count() && fOpsTask[consumedIndex]); + OpNode& consumedOp = *fOpsTask[consumedIndex]; + + // steal all of consumed's ops + for (int i = 0; i < consumedOp.fChildren.count(); i++) { + Op* childOp = consumedOp.fChildren[i]; + + // set the ids for the child op + childOp->fOpsTaskID = index; + childOp->fChildID = consumerOp.fChildren.count(); + consumerOp.fChildren.push_back(childOp); + } + + // Update the bounds for the combineWith node + consumerOp.fBounds = consumer->bounds(); + + // remove the old node from our opsTask and clear the combinee's lookup + // NOTE: because we can't change the shape of the oplist, we use a sentinel + fOpsTask[consumedIndex].reset(nullptr); + fIDLookup.remove(consumed->uniqueID()); +} + +void GrAuditTrail::copyOutFromOpsTask(OpInfo* outOpInfo, int opsTaskID) { + SkASSERT(opsTaskID < fOpsTask.count()); + const OpNode* bn = fOpsTask[opsTaskID].get(); + SkASSERT(bn); + outOpInfo->fBounds = bn->fBounds; + outOpInfo->fProxyUniqueID = bn->fProxyUniqueID; + for (int j = 0; j < bn->fChildren.count(); j++) { + OpInfo::Op& outOp = outOpInfo->fOps.push_back(); + const Op* currentOp = bn->fChildren[j]; + outOp.fBounds = currentOp->fBounds; + outOp.fClientID = currentOp->fClientID; + } +} + +void GrAuditTrail::getBoundsByClientID(SkTArray* outInfo, int clientID) { + Ops** opsLookup = fClientIDLookup.find(clientID); + if (opsLookup) { + // We track which oplistID we're currently looking at. If it changes, then we need to push + // back a new op info struct. We happen to know that ops are in sequential order in the + // oplist, otherwise we'd have to do more bookkeeping + int currentOpsTaskID = kGrAuditTrailInvalidID; + for (int i = 0; i < (*opsLookup)->count(); i++) { + const Op* op = (**opsLookup)[i]; + + // Because we will copy out all of the ops associated with a given op list id everytime + // the id changes, we only have to update our struct when the id changes. + if (kGrAuditTrailInvalidID == currentOpsTaskID || op->fOpsTaskID != currentOpsTaskID) { + OpInfo& outOpInfo = outInfo->push_back(); + + // copy out all of the ops so the client can display them even if they have a + // different clientID + this->copyOutFromOpsTask(&outOpInfo, op->fOpsTaskID); + } + } + } +} + +void GrAuditTrail::getBoundsByOpsTaskID(OpInfo* outInfo, int opsTaskID) { + this->copyOutFromOpsTask(outInfo, opsTaskID); +} + +void GrAuditTrail::fullReset() { + SkASSERT(fEnabled); + fOpsTask.reset(); + fIDLookup.reset(); + // free all client ops + fClientIDLookup.foreach ([](const int&, Ops** ops) { delete *ops; }); + fClientIDLookup.reset(); + fOpPool.reset(); // must be last, frees all of the memory +} + +template +void GrAuditTrail::JsonifyTArray(SkJSONWriter& writer, const char* name, const T& array) { + if (array.count()) { + writer.beginArray(name); + for (int i = 0; i < array.count(); i++) { + // Handle sentinel nullptrs + if (array[i]) { + array[i]->toJson(writer); + } + } + writer.endArray(); + } +} + +void GrAuditTrail::toJson(SkJSONWriter& writer) const { + writer.beginObject(); + JsonifyTArray(writer, "Ops", fOpsTask); + writer.endObject(); +} + +void GrAuditTrail::toJson(SkJSONWriter& writer, int clientID) const { + writer.beginObject(); + Ops** ops = fClientIDLookup.find(clientID); + if (ops) { + JsonifyTArray(writer, "Ops", **ops); + } + writer.endObject(); +} + +static void skrect_to_json(SkJSONWriter& writer, const char* name, const SkRect& rect) { + writer.beginObject(name); + writer.appendFloat("Left", rect.fLeft); + writer.appendFloat("Right", rect.fRight); + writer.appendFloat("Top", rect.fTop); + writer.appendFloat("Bottom", rect.fBottom); + writer.endObject(); +} + +void GrAuditTrail::Op::toJson(SkJSONWriter& writer) const { + writer.beginObject(); + writer.appendString("Name", fName.c_str()); + writer.appendS32("ClientID", fClientID); + writer.appendS32("OpsTaskID", fOpsTaskID); + writer.appendS32("ChildID", fChildID); + skrect_to_json(writer, "Bounds", fBounds); + if (fStackTrace.count()) { + writer.beginArray("Stack"); + for (int i = 0; i < fStackTrace.count(); i++) { + writer.appendString(fStackTrace[i].c_str()); + } + writer.endArray(); + } + writer.endObject(); +} + +void GrAuditTrail::OpNode::toJson(SkJSONWriter& writer) const { + writer.beginObject(); + writer.appendU32("ProxyID", fProxyUniqueID.asUInt()); + skrect_to_json(writer, "Bounds", fBounds); + JsonifyTArray(writer, "Ops", fChildren); + writer.endObject(); +} diff --git a/gfx/skia/skia/src/gpu/GrAuditTrail.h b/gfx/skia/skia/src/gpu/GrAuditTrail.h new file mode 100644 index 0000000000..dd6549ef6c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAuditTrail.h @@ -0,0 +1,181 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAuditTrail_DEFINED +#define GrAuditTrail_DEFINED + +#include "include/core/SkRect.h" +#include "include/core/SkString.h" +#include "include/gpu/GrConfig.h" +#include "include/gpu/GrGpuResource.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTHash.h" +#include "src/gpu/GrRenderTargetProxy.h" + +class GrOp; +class SkJSONWriter; + +/* + * GrAuditTrail collects a list of draw ops, detailed information about those ops, and can dump them + * to json. + * + * Capturing this information is expensive and consumes a lot of memory, therefore it is important + * to enable auditing only when required and disable it promptly. The AutoEnable class helps to + * ensure that the audit trail is disabled in a timely fashion. Once the information has been dealt + * with, be sure to call reset(), or the log will simply keep growing. + */ +class GrAuditTrail { +public: + GrAuditTrail() + : fClientID(kGrAuditTrailInvalidID) + , fEnabled(false) {} + + class AutoEnable { + public: + AutoEnable(GrAuditTrail* auditTrail) + : fAuditTrail(auditTrail) { + SkASSERT(!fAuditTrail->isEnabled()); + fAuditTrail->setEnabled(true); + } + + ~AutoEnable() { + SkASSERT(fAuditTrail->isEnabled()); + fAuditTrail->setEnabled(false); + } + + private: + GrAuditTrail* fAuditTrail; + }; + + class AutoManageOpsTask { + public: + AutoManageOpsTask(GrAuditTrail* auditTrail) + : fAutoEnable(auditTrail), fAuditTrail(auditTrail) {} + + ~AutoManageOpsTask() { fAuditTrail->fullReset(); } + + private: + AutoEnable fAutoEnable; + GrAuditTrail* fAuditTrail; + }; + + class AutoCollectOps { + public: + AutoCollectOps(GrAuditTrail* auditTrail, int clientID) + : fAutoEnable(auditTrail), fAuditTrail(auditTrail) { + fAuditTrail->setClientID(clientID); + } + + ~AutoCollectOps() { fAuditTrail->setClientID(kGrAuditTrailInvalidID); } + + private: + AutoEnable fAutoEnable; + GrAuditTrail* fAuditTrail; + }; + + void pushFrame(const char* framename) { + SkASSERT(fEnabled); + fCurrentStackTrace.push_back(SkString(framename)); + } + + void addOp(const GrOp*, GrRenderTargetProxy::UniqueID proxyID); + + void opsCombined(const GrOp* consumer, const GrOp* consumed); + + // Because op combining is heavily dependent on sequence of draw calls, these calls will only + // produce valid information for the given draw sequence which preceeded them. Specifically, ops + // of future draw calls may combine with previous ops and thus would invalidate the json. What + // this means is that for some sequence of draw calls N, the below toJson calls will only + // produce JSON which reflects N draw calls. This JSON may or may not be accurate for N + 1 or + // N - 1 draws depending on the actual combining algorithm used. + void toJson(SkJSONWriter& writer) const; + + // returns a json string of all of the ops associated with a given client id + void toJson(SkJSONWriter& writer, int clientID) const; + + bool isEnabled() { return fEnabled; } + void setEnabled(bool enabled) { fEnabled = enabled; } + + void setClientID(int clientID) { fClientID = clientID; } + + // We could just return our internal bookkeeping struct if copying the data out becomes + // a performance issue, but until then its nice to decouple + struct OpInfo { + struct Op { + int fClientID; + SkRect fBounds; + }; + + SkRect fBounds; + GrSurfaceProxy::UniqueID fProxyUniqueID; + SkTArray fOps; + }; + + void getBoundsByClientID(SkTArray* outInfo, int clientID); + void getBoundsByOpsTaskID(OpInfo* outInfo, int opsTaskID); + + void fullReset(); + + static const int kGrAuditTrailInvalidID; + +private: + // TODO if performance becomes an issue, we can move to using SkVarAlloc + struct Op { + void toJson(SkJSONWriter& writer) const; + SkString fName; + SkTArray fStackTrace; + SkRect fBounds; + int fClientID; + int fOpsTaskID; + int fChildID; + }; + typedef SkTArray, true> OpPool; + + typedef SkTArray Ops; + + struct OpNode { + OpNode(const GrSurfaceProxy::UniqueID& proxyID) : fProxyUniqueID(proxyID) { } + void toJson(SkJSONWriter& writer) const; + + SkRect fBounds; + Ops fChildren; + const GrSurfaceProxy::UniqueID fProxyUniqueID; + }; + typedef SkTArray, true> OpsTask; + + void copyOutFromOpsTask(OpInfo* outOpInfo, int opsTask); + + template + static void JsonifyTArray(SkJSONWriter& writer, const char* name, const T& array); + + OpPool fOpPool; + SkTHashMap fIDLookup; + SkTHashMap fClientIDLookup; + OpsTask fOpsTask; + SkTArray fCurrentStackTrace; + + // The client can pass in an optional client ID which we will use to mark the ops + int fClientID; + bool fEnabled; +}; + +#define GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, invoke, ...) \ + if (audit_trail->isEnabled()) audit_trail->invoke(__VA_ARGS__) + +#define GR_AUDIT_TRAIL_AUTO_FRAME(audit_trail, framename) \ + GR_AUDIT_TRAIL_INVOKE_GUARD((audit_trail), pushFrame, framename) + +#define GR_AUDIT_TRAIL_RESET(audit_trail) \ + //GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, fullReset); + +#define GR_AUDIT_TRAIL_ADD_OP(audit_trail, op, proxy_id) \ + GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, addOp, op, proxy_id) + +#define GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(audit_trail, combineWith, op) \ + GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, opsCombined, combineWith, op) + +#endif diff --git a/gfx/skia/skia/src/gpu/GrAutoLocaleSetter.h b/gfx/skia/skia/src/gpu/GrAutoLocaleSetter.h new file mode 100644 index 0000000000..aa24177728 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrAutoLocaleSetter.h @@ -0,0 +1,98 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAutoLocaleSetter_DEFINED +#define GrAutoLocaleSetter_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/private/SkNoncopyable.h" + +#if defined(SK_BUILD_FOR_WIN) +#include "include/core/SkString.h" +#endif + +#if !defined(SK_BUILD_FOR_ANDROID) +#include +#endif + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) +#include +#include +#define HAVE_XLOCALE 1 +#else +#define HAVE_XLOCALE 0 +#endif + +#if defined(SK_BUILD_FOR_ANDROID) || defined(__UCLIBC__) || defined(_NEWLIB_VERSION) +#define HAVE_LOCALE_T 0 +#else +#define HAVE_LOCALE_T 1 +#endif + +/** + * Helper class for ensuring that we don't use the wrong locale when building shaders. Android + * doesn't support locale in the NDK, so this is a no-op there. + */ +class GrAutoLocaleSetter : public SkNoncopyable { +public: + GrAutoLocaleSetter (const char* name) { +#if defined(SK_BUILD_FOR_WIN) + fOldPerThreadLocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + char* oldLocale = setlocale(LC_ALL, name); + if (oldLocale) { + fOldLocale = oldLocale; + fShouldRestoreLocale = true; + } else { + fShouldRestoreLocale = false; + } +#elif HAVE_LOCALE_T +#if HAVE_XLOCALE + // In xlocale nullptr means the C locale. + if (0 == strcmp(name, "C")) { + name = nullptr; + } +#endif + fLocale = newlocale(LC_ALL_MASK, name, nullptr); + if (fLocale) { + fOldLocale = uselocale(fLocale); + } else { + fOldLocale = static_cast(nullptr); + } +#else + (void) name; // suppress unused param warning. +#endif + } + + ~GrAutoLocaleSetter () { +#if defined(SK_BUILD_FOR_WIN) + if (fShouldRestoreLocale) { + setlocale(LC_ALL, fOldLocale.c_str()); + } + _configthreadlocale(fOldPerThreadLocale); +#elif HAVE_LOCALE_T + if (fLocale) { + uselocale(fOldLocale); + freelocale(fLocale); + } +#endif + } + +private: +#if defined(SK_BUILD_FOR_WIN) + int fOldPerThreadLocale; + bool fShouldRestoreLocale; + SkString fOldLocale; +#elif HAVE_LOCALE_T + locale_t fOldLocale; + locale_t fLocale; +#endif +}; + +#undef HAVE_LOCALE_T +#undef HAVE_XLOCALE + +#endif diff --git a/gfx/skia/skia/src/gpu/GrBackendSurface.cpp b/gfx/skia/skia/src/gpu/GrBackendSurface.cpp new file mode 100644 index 0000000000..fa95520811 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBackendSurface.cpp @@ -0,0 +1,924 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/gpu/GrBackendSurface.h" + +#include "src/gpu/gl/GrGLUtil.h" + +#ifdef SK_DAWN +#include "include/gpu/dawn/GrDawnTypes.h" +#include "src/gpu/dawn/GrDawnUtil.h" +#endif + +#ifdef SK_VULKAN +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkImageLayout.h" +#include "src/gpu/vk/GrVkUtil.h" +#endif +#ifdef SK_METAL +#include "include/gpu/mtl/GrMtlTypes.h" +#include "src/gpu/mtl/GrMtlCppUtil.h" +#endif + +GrBackendFormat::GrBackendFormat(const GrBackendFormat& that) + : fBackend(that.fBackend) + , fValid(that.fValid) + , fTextureType(that.fTextureType) { + if (!fValid) { + return; + } + + switch (fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + fGLFormat = that.fGLFormat; + break; +#endif +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: + fVk = that.fVk; + break; +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: + fMtlFormat = that.fMtlFormat; + break; +#endif +#ifdef SK_DAWN + case GrBackendApi::kDawn: + fDawnFormat = that.fDawnFormat; + break; +#endif + case GrBackendApi::kMock: + fMockColorType = that.fMockColorType; + break; + default: + SK_ABORT("Unknown GrBackend"); + } +} + +GrBackendFormat::GrBackendFormat(GrGLenum format, GrGLenum target) + : fBackend(GrBackendApi::kOpenGL) + , fValid(true) + , fGLFormat(format) { + switch (target) { + case GR_GL_TEXTURE_NONE: + fTextureType = GrTextureType::kNone; + break; + case GR_GL_TEXTURE_2D: + fTextureType = GrTextureType::k2D; + break; + case GR_GL_TEXTURE_RECTANGLE: + fTextureType = GrTextureType::kRectangle; + break; + case GR_GL_TEXTURE_EXTERNAL: + fTextureType = GrTextureType::kExternal; + break; + default: + SK_ABORT("Unexpected texture target"); + } +} + +GrGLFormat GrBackendFormat::asGLFormat() const { + if (this->isValid() && GrBackendApi::kOpenGL == fBackend) { + return GrGLFormatFromGLEnum(fGLFormat); + } + return GrGLFormat::kUnknown; +} + +GrBackendFormat GrBackendFormat::MakeVk(const GrVkYcbcrConversionInfo& ycbcrInfo) { + SkASSERT(ycbcrInfo.isValid()); + return GrBackendFormat(ycbcrInfo.fFormat, ycbcrInfo); +} + +GrBackendFormat::GrBackendFormat(VkFormat vkFormat, const GrVkYcbcrConversionInfo& ycbcrInfo) + : fBackend(GrBackendApi::kVulkan) +#ifdef SK_VULKAN + , fValid(true) +#else + , fValid(false) +#endif + , fTextureType(GrTextureType::k2D) { + fVk.fFormat = vkFormat; + fVk.fYcbcrConversionInfo = ycbcrInfo; + if (fVk.fYcbcrConversionInfo.isValid() && fVk.fYcbcrConversionInfo.fExternalFormat) { + fTextureType = GrTextureType::kExternal; + } +} + +bool GrBackendFormat::asVkFormat(VkFormat* format) const { + SkASSERT(format); + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + *format = fVk.fFormat; + return true; + } + return false; +} + +const GrVkYcbcrConversionInfo* GrBackendFormat::getVkYcbcrConversionInfo() const { + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + return &fVk.fYcbcrConversionInfo; + } + return nullptr; +} + +#ifdef SK_DAWN +GrBackendFormat::GrBackendFormat(dawn::TextureFormat format) + : fBackend(GrBackendApi::kDawn) + , fValid(true) + , fDawnFormat(format) + , fTextureType(GrTextureType::k2D) { +} + +bool GrBackendFormat::asDawnFormat(dawn::TextureFormat* format) const { + SkASSERT(format); + if (this->isValid() && GrBackendApi::kDawn == fBackend) { + *format = fDawnFormat; + return true; + } + return false; +} +#endif + +#ifdef SK_METAL +GrBackendFormat::GrBackendFormat(GrMTLPixelFormat mtlFormat) + : fBackend(GrBackendApi::kMetal) + , fValid(true) + , fMtlFormat(mtlFormat) + , fTextureType(GrTextureType::k2D) { +} + +GrMTLPixelFormat GrBackendFormat::asMtlFormat() const { + if (this->isValid() && GrBackendApi::kMetal == fBackend) { + return fMtlFormat; + } + // MTLPixelFormatInvalid == 0 + return GrMTLPixelFormat(0); +} +#endif + +GrBackendFormat::GrBackendFormat(GrColorType colorType) + : fBackend(GrBackendApi::kMock) + , fValid(true) + , fTextureType(GrTextureType::k2D) { + fMockColorType = colorType; +} + +GrColorType GrBackendFormat::asMockColorType() const { + if (this->isValid() && GrBackendApi::kMock == fBackend) { + return fMockColorType; + } + return GrColorType::kUnknown; +} + +GrBackendFormat GrBackendFormat::makeTexture2D() const { + GrBackendFormat copy = *this; + if (const GrVkYcbcrConversionInfo* ycbcrInfo = this->getVkYcbcrConversionInfo()) { + if (ycbcrInfo->isValid()) { + // If we have a ycbcr we remove it from the backend format and set the VkFormat to + // R8G8B8A8_UNORM + SkASSERT(copy.fBackend == GrBackendApi::kVulkan); + copy.fVk.fYcbcrConversionInfo = GrVkYcbcrConversionInfo(); + copy.fVk.fFormat = VK_FORMAT_R8G8B8A8_UNORM; + } + } + copy.fTextureType = GrTextureType::k2D; + return copy; +} + +bool GrBackendFormat::operator==(const GrBackendFormat& that) const { + // Invalid GrBackendFormats are never equal to anything. + if (!fValid || !that.fValid) { + return false; + } + + if (fBackend != that.fBackend) { + return false; + } + + switch (fBackend) { + case GrBackendApi::kOpenGL: + return fGLFormat == that.fGLFormat; + case GrBackendApi::kVulkan: +#ifdef SK_VULKAN + return fVk.fFormat == that.fVk.fFormat && + fVk.fYcbcrConversionInfo == that.fVk.fYcbcrConversionInfo; +#endif + break; +#ifdef SK_METAL + case GrBackendApi::kMetal: + return fMtlFormat == that.fMtlFormat; +#endif + break; + case GrBackendApi::kDawn: +#ifdef SK_DAWN + return fDawnFormat == that.fDawnFormat; +#endif + break; + case GrBackendApi::kMock: + return fMockColorType == that.fMockColorType; + default: + SK_ABORT("Unknown GrBackend"); + } + return false; +} + +#if GR_TEST_UTILS +#include "include/core/SkString.h" +#include "src/gpu/GrTestUtils.h" + +#ifdef SK_GL +#include "src/gpu/gl/GrGLUtil.h" +#endif +#ifdef SK_VULKAN +#include "src/gpu/vk/GrVkUtil.h" +#endif + +SkString GrBackendFormat::toStr() const { + SkString str; + + if (!fValid) { + str.append("invalid"); + return str; + } + + str.appendf("%s-", GrBackendApiToStr(fBackend)); + + switch (fBackend) { + case GrBackendApi::kOpenGL: +#ifdef SK_GL + str.append(GrGLFormatToStr(fGLFormat)); +#endif + break; + case GrBackendApi::kVulkan: +#ifdef SK_VULKAN + str.append(GrVkFormatToStr(fVk.fFormat)); +#endif + break; + case GrBackendApi::kMetal: +#ifdef SK_METAL + str.append(GrMtlFormatToStr(fMtlFormat)); +#endif + break; + case GrBackendApi::kDawn: +#ifdef SK_DAWN + str.append(GrDawnFormatToStr(fDawnFormat)); +#endif + break; + case GrBackendApi::kMock: + str.append(GrColorTypeToStr(fMockColorType)); + break; + } + + return str; +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef SK_DAWN +GrBackendTexture::GrBackendTexture(int width, + int height, + const GrDawnImageInfo& dawnInfo) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fMipMapped(GrMipMapped(dawnInfo.fLevelCount > 1)) + , fBackend(GrBackendApi::kDawn) + , fDawnInfo(dawnInfo) {} +#endif + +GrBackendTexture::GrBackendTexture(int width, int height, const GrVkImageInfo& vkInfo) +#ifdef SK_VULKAN + : GrBackendTexture(width, height, vkInfo, + sk_sp(new GrVkImageLayout(vkInfo.fImageLayout))) {} +#else + : fIsValid(false) {} +#endif + +#ifdef SK_GL +GrBackendTexture::GrBackendTexture(int width, + int height, + GrMipMapped mipMapped, + const GrGLTextureInfo glInfo, + sk_sp params) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fMipMapped(mipMapped) + , fBackend(GrBackendApi::kOpenGL) + , fGLInfo(glInfo, params.release()) {} + +sk_sp GrBackendTexture::getGLTextureParams() const { + if (fBackend != GrBackendApi::kOpenGL) { + return nullptr; + } + return fGLInfo.refParameters(); +} +#endif + +#ifdef SK_VULKAN +GrBackendTexture::GrBackendTexture(int width, + int height, + const GrVkImageInfo& vkInfo, + sk_sp layout) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fMipMapped(GrMipMapped(vkInfo.fLevelCount > 1)) + , fBackend(GrBackendApi::kVulkan) + , fVkInfo(vkInfo, layout.release()) {} +#endif + +#ifdef SK_METAL +GrBackendTexture::GrBackendTexture(int width, + int height, + GrMipMapped mipMapped, + const GrMtlTextureInfo& mtlInfo) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fMipMapped(mipMapped) + , fBackend(GrBackendApi::kMetal) + , fMtlInfo(mtlInfo) {} +#endif + +GrBackendTexture::GrBackendTexture(int width, + int height, + GrMipMapped mipMapped, + const GrGLTextureInfo& glInfo) + : GrBackendTexture(width, height, mipMapped, glInfo, sk_make_sp()) { + // Make no assumptions about client's texture's parameters. + this->glTextureParametersModified(); +} + +GrBackendTexture::GrBackendTexture(int width, + int height, + GrMipMapped mipMapped, + const GrMockTextureInfo& mockInfo) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fMipMapped(mipMapped) + , fBackend(GrBackendApi::kMock) + , fMockInfo(mockInfo) {} + +GrBackendTexture::~GrBackendTexture() { + this->cleanup(); +} + +void GrBackendTexture::cleanup() { +#ifdef SK_GL + if (this->isValid() && GrBackendApi::kOpenGL == fBackend) { + fGLInfo.cleanup(); + } +#endif +#ifdef SK_VULKAN + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + fVkInfo.cleanup(); + } +#endif +} + +GrBackendTexture::GrBackendTexture(const GrBackendTexture& that) : fIsValid(false) { + *this = that; +} + +GrBackendTexture& GrBackendTexture::operator=(const GrBackendTexture& that) { + if (!that.isValid()) { + this->cleanup(); + fIsValid = false; + return *this; + } else if (fIsValid && this->fBackend != that.fBackend) { + this->cleanup(); + fIsValid = false; + } + fWidth = that.fWidth; + fHeight = that.fHeight; + fMipMapped = that.fMipMapped; + fBackend = that.fBackend; + + switch (that.fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + fGLInfo.assign(that.fGLInfo, this->isValid()); + break; +#endif +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: + fVkInfo.assign(that.fVkInfo, this->isValid()); + break; +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: + fMtlInfo = that.fMtlInfo; + break; +#endif +#ifdef SK_DAWN + case GrBackendApi::kDawn: + fDawnInfo = that.fDawnInfo; + break; +#endif + case GrBackendApi::kMock: + fMockInfo = that.fMockInfo; + break; + default: + SK_ABORT("Unknown GrBackend"); + } + fIsValid = true; + return *this; +} + +#ifdef SK_DAWN +bool GrBackendTexture::getDawnImageInfo(GrDawnImageInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kDawn == fBackend) { + *outInfo = fDawnInfo; + return true; + } + return false; +} +#endif + +bool GrBackendTexture::getVkImageInfo(GrVkImageInfo* outInfo) const { +#ifdef SK_VULKAN + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + *outInfo = fVkInfo.snapImageInfo(); + return true; + } +#endif + return false; +} + +void GrBackendTexture::setVkImageLayout(VkImageLayout layout) { +#ifdef SK_VULKAN + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + fVkInfo.setImageLayout(layout); + } +#endif +} + +#ifdef SK_VULKAN +sk_sp GrBackendTexture::getGrVkImageLayout() const { + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + return fVkInfo.getGrVkImageLayout(); + } + return nullptr; +} +#endif + +#ifdef SK_METAL +bool GrBackendTexture::getMtlTextureInfo(GrMtlTextureInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kMetal == fBackend) { + *outInfo = fMtlInfo; + return true; + } + return false; +} +#endif + +bool GrBackendTexture::getGLTextureInfo(GrGLTextureInfo* outInfo) const { +#ifdef SK_GL + if (this->isValid() && GrBackendApi::kOpenGL == fBackend) { + *outInfo = fGLInfo.info(); + return true; + } else if (this->isValid() && GrBackendApi::kMock == fBackend) { + // Hack! This allows some blink unit tests to work when using the Mock GrContext. + // Specifically, tests that rely on CanvasResourceProviderTextureGpuMemoryBuffer. + // If that code ever goes away (or ideally becomes backend-agnostic), this can go away. + *outInfo = GrGLTextureInfo{ GR_GL_TEXTURE_2D, + static_cast(fMockInfo.fID), + GR_GL_RGBA8 }; + return true; + } +#endif + return false; +} + +void GrBackendTexture::glTextureParametersModified() { +#ifdef SK_GL + if (this->isValid() && fBackend == GrBackendApi::kOpenGL) { + fGLInfo.parameters()->invalidate(); + } +#endif +} + +bool GrBackendTexture::getMockTextureInfo(GrMockTextureInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kMock == fBackend) { + *outInfo = fMockInfo; + return true; + } + return false; +} + +bool GrBackendTexture::isProtected() const { + if (!this->isValid() || this->backend() != GrBackendApi::kVulkan) { + return false; + } + return fVkInfo.isProtected(); +} + +bool GrBackendTexture::isSameTexture(const GrBackendTexture& that) { + if (!this->isValid() || !that.isValid()) { + return false; + } + if (fBackend != that.fBackend) { + return false; + } + switch (fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + return fGLInfo.info().fID == that.fGLInfo.info().fID; +#endif +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: + return fVkInfo.snapImageInfo().fImage == that.fVkInfo.snapImageInfo().fImage; +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: + return this->fMtlInfo.fTexture == that.fMtlInfo.fTexture; +#endif + case GrBackendApi::kMock: + return fMockInfo.fID == that.fMockInfo.fID; + default: + return false; + } +} + +GrBackendFormat GrBackendTexture::getBackendFormat() const { + if (!this->isValid()) { + return GrBackendFormat(); + } + switch (fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + return GrBackendFormat::MakeGL(fGLInfo.info().fFormat, fGLInfo.info().fTarget); +#endif +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: { + auto info = fVkInfo.snapImageInfo(); + if (info.fYcbcrConversionInfo.isValid()) { + SkASSERT(info.fFormat == info.fYcbcrConversionInfo.fFormat); + return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo); + } + return GrBackendFormat::MakeVk(info.fFormat); + } +#endif +#ifdef SK_DAWN + case GrBackendApi::kDawn: { + return GrBackendFormat::MakeDawn(fDawnInfo.fFormat); + } +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: { + GrMtlTextureInfo mtlInfo; + SkAssertResult(this->getMtlTextureInfo(&mtlInfo)); + return GrBackendFormat::MakeMtl(GrGetMTLPixelFormatFromMtlTextureInfo(mtlInfo)); + } +#endif + case GrBackendApi::kMock: + return fMockInfo.getBackendFormat(); + default: + return GrBackendFormat(); + } +} + +#if GR_TEST_UTILS +bool GrBackendTexture::TestingOnly_Equals(const GrBackendTexture& t0, const GrBackendTexture& t1) { + if (!t0.isValid() || !t1.isValid()) { + return false; // two invalid backend textures are not considered equal + } + + if (t0.fWidth != t1.fWidth || + t0.fHeight != t1.fHeight || + t0.fMipMapped != t1.fMipMapped || + t0.fBackend != t1.fBackend) { + return false; + } + + switch (t0.fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + return t0.fGLInfo.info() == t1.fGLInfo.info(); +#endif + case GrBackendApi::kMock: + return t0.fMockInfo == t1.fMockInfo; +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: + return t0.fVkInfo == t1.fVkInfo; +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: + return t0.fMtlInfo == t1.fMtlInfo; +#endif +#ifdef SK_DAWN + case GrBackendApi::kDawn: + return t0.fDawnInfo == t1.fDawnInfo; +#endif + default: + return false; + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DAWN +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + int stencilBits, + const GrDawnImageInfo& dawnInfo) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fSampleCnt(sampleCnt) + , fStencilBits(stencilBits) + , fBackend(GrBackendApi::kDawn) + , fDawnInfo(dawnInfo) {} +#endif + +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + int stencilBits, + const GrVkImageInfo& vkInfo) + : GrBackendRenderTarget(width, height, sampleCnt, vkInfo) { + // This is a deprecated constructor that takes a bogus stencil bits. + SkASSERT(0 == stencilBits); +} + +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + const GrVkImageInfo& vkInfo) +#ifdef SK_VULKAN + : GrBackendRenderTarget(width, height, sampleCnt, vkInfo, + sk_sp(new GrVkImageLayout(vkInfo.fImageLayout))) {} +#else + : fIsValid(false) {} +#endif + +#ifdef SK_VULKAN +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + const GrVkImageInfo& vkInfo, + sk_sp layout) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fSampleCnt(SkTMax(1, sampleCnt)) + , fStencilBits(0) // We always create stencil buffers internally for vulkan + , fBackend(GrBackendApi::kVulkan) + , fVkInfo(vkInfo, layout.release()) {} +#endif + +#ifdef SK_METAL +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + const GrMtlTextureInfo& mtlInfo) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fSampleCnt(SkTMax(1, sampleCnt)) + , fStencilBits(0) + , fBackend(GrBackendApi::kMetal) + , fMtlInfo(mtlInfo) {} +#endif + +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + int stencilBits, + const GrGLFramebufferInfo& glInfo) + : fWidth(width) + , fHeight(height) + , fSampleCnt(SkTMax(1, sampleCnt)) + , fStencilBits(stencilBits) + , fBackend(GrBackendApi::kOpenGL) + , fGLInfo(glInfo) { + fIsValid = SkToBool(glInfo.fFormat); // the glInfo must have a valid format +} + +GrBackendRenderTarget::GrBackendRenderTarget(int width, + int height, + int sampleCnt, + int stencilBits, + const GrMockRenderTargetInfo& mockInfo) + : fIsValid(true) + , fWidth(width) + , fHeight(height) + , fSampleCnt(SkTMax(1, sampleCnt)) + , fStencilBits(stencilBits) + , fMockInfo(mockInfo) {} + +GrBackendRenderTarget::~GrBackendRenderTarget() { + this->cleanup(); +} + +void GrBackendRenderTarget::cleanup() { +#ifdef SK_VULKAN + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + fVkInfo.cleanup(); + } +#endif +} + +GrBackendRenderTarget::GrBackendRenderTarget(const GrBackendRenderTarget& that) : fIsValid(false) { + *this = that; +} + +GrBackendRenderTarget& GrBackendRenderTarget::operator=(const GrBackendRenderTarget& that) { + if (!that.isValid()) { + this->cleanup(); + fIsValid = false; + return *this; + } else if (fIsValid && this->fBackend != that.fBackend) { + this->cleanup(); + fIsValid = false; + } + fWidth = that.fWidth; + fHeight = that.fHeight; + fSampleCnt = that.fSampleCnt; + fStencilBits = that.fStencilBits; + fBackend = that.fBackend; + + switch (that.fBackend) { + case GrBackendApi::kOpenGL: + fGLInfo = that.fGLInfo; + break; + case GrBackendApi::kVulkan: +#ifdef SK_VULKAN + fVkInfo.assign(that.fVkInfo, this->isValid()); +#endif + break; +#ifdef SK_DAWN + case GrBackendApi::kDawn: + fDawnInfo = that.fDawnInfo; + break; +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: + fMtlInfo = that.fMtlInfo; + break; +#endif + case GrBackendApi::kMock: + fMockInfo = that.fMockInfo; + break; + default: + SK_ABORT("Unknown GrBackend"); + } + fIsValid = that.fIsValid; + return *this; +} + +#ifdef SK_DAWN +bool GrBackendRenderTarget::getDawnImageInfo(GrDawnImageInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kDawn == fBackend) { + *outInfo = fDawnInfo; + return true; + } + return false; +} +#endif + +bool GrBackendRenderTarget::getVkImageInfo(GrVkImageInfo* outInfo) const { +#ifdef SK_VULKAN + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + *outInfo = fVkInfo.snapImageInfo(); + return true; + } +#endif + return false; +} + +void GrBackendRenderTarget::setVkImageLayout(VkImageLayout layout) { +#ifdef SK_VULKAN + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + fVkInfo.setImageLayout(layout); + } +#endif +} + +#ifdef SK_VULKAN +sk_sp GrBackendRenderTarget::getGrVkImageLayout() const { + if (this->isValid() && GrBackendApi::kVulkan == fBackend) { + return fVkInfo.getGrVkImageLayout(); + } + return nullptr; +} +#endif + +#ifdef SK_METAL +bool GrBackendRenderTarget::getMtlTextureInfo(GrMtlTextureInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kMetal == fBackend) { + *outInfo = fMtlInfo; + return true; + } + return false; +} +#endif + +bool GrBackendRenderTarget::getGLFramebufferInfo(GrGLFramebufferInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kOpenGL == fBackend) { + *outInfo = fGLInfo; + return true; + } + return false; +} + +GrBackendFormat GrBackendRenderTarget::getBackendFormat() const { + if (!this->isValid()) { + return GrBackendFormat(); + } + switch (fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + return GrBackendFormat::MakeGL(fGLInfo.fFormat, GR_GL_TEXTURE_NONE); +#endif +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: { + auto info = fVkInfo.snapImageInfo(); + if (info.fYcbcrConversionInfo.isValid()) { + SkASSERT(info.fFormat == info.fYcbcrConversionInfo.fFormat); + return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo); + } + return GrBackendFormat::MakeVk(info.fFormat); + } +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: { + GrMtlTextureInfo mtlInfo; + SkAssertResult(this->getMtlTextureInfo(&mtlInfo)); + return GrBackendFormat::MakeMtl(GrGetMTLPixelFormatFromMtlTextureInfo(mtlInfo)); + } +#endif + case GrBackendApi::kMock: + return fMockInfo.getBackendFormat(); + default: + return GrBackendFormat(); + } +} + +bool GrBackendRenderTarget::getMockRenderTargetInfo(GrMockRenderTargetInfo* outInfo) const { + if (this->isValid() && GrBackendApi::kMock == fBackend) { + *outInfo = fMockInfo; + return true; + } + return false; +} + +bool GrBackendRenderTarget::isProtected() const { + if (!this->isValid() || this->backend() != GrBackendApi::kVulkan) { + return false; + } + return fVkInfo.isProtected(); +} + +#if GR_TEST_UTILS +bool GrBackendRenderTarget::TestingOnly_Equals(const GrBackendRenderTarget& r0, + const GrBackendRenderTarget& r1) { + if (!r0.isValid() || !r1.isValid()) { + return false; // two invalid backend rendertargets are not considered equal + } + + if (r0.fWidth != r1.fWidth || + r0.fHeight != r1.fHeight || + r0.fSampleCnt != r1.fSampleCnt || + r0.fStencilBits != r1.fStencilBits || + r0.fBackend != r1.fBackend) { + return false; + } + + switch (r0.fBackend) { +#ifdef SK_GL + case GrBackendApi::kOpenGL: + return r0.fGLInfo == r1.fGLInfo; +#endif + case GrBackendApi::kMock: + return r0.fMockInfo == r1.fMockInfo; +#ifdef SK_VULKAN + case GrBackendApi::kVulkan: + return r0.fVkInfo == r1.fVkInfo; +#endif +#ifdef SK_METAL + case GrBackendApi::kMetal: + return r0.fMtlInfo == r1.fMtlInfo; +#endif +#ifdef SK_DAWN + case GrBackendApi::kDawn: + return r0.fDawnInfo == r1.fDawnInfo; +#endif + default: + return false; + } + + SkASSERT(0); + return false; +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.cpp b/gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.cpp new file mode 100644 index 0000000000..bd4cacb529 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.cpp @@ -0,0 +1,224 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMessageBus.h" +#include "src/gpu/GrBackendTextureImageGenerator.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrResourceProviderPriv.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureProxyPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/gl/GrGLTexture.h" + +GrBackendTextureImageGenerator::RefHelper::RefHelper(GrTexture* texture, uint32_t owningContextID) + : fOriginalTexture(texture) + , fOwningContextID(owningContextID) + , fBorrowingContextReleaseProc(nullptr) + , fBorrowingContextID(SK_InvalidGenID) {} + +GrBackendTextureImageGenerator::RefHelper::~RefHelper() { + SkASSERT(fBorrowingContextID == SK_InvalidUniqueID); + + // Generator has been freed, and no one is borrowing the texture. Notify the original cache + // that it can free the last ref, so it happens on the correct thread. + GrTextureFreedMessage msg { fOriginalTexture, fOwningContextID }; + SkMessageBus::Post(msg); +} + +std::unique_ptr +GrBackendTextureImageGenerator::Make(sk_sp texture, GrSurfaceOrigin origin, + sk_sp semaphore, SkColorType colorType, + SkAlphaType alphaType, sk_sp colorSpace) { + GrContext* context = texture->getContext(); + + // Attach our texture to this context's resource cache. This ensures that deletion will happen + // in the correct thread/context. This adds the only ref to the texture that will persist from + // this point. That ref will be released when the generator's RefHelper is freed. + context->priv().getResourceCache()->insertDelayedTextureUnref(texture.get()); + + GrBackendTexture backendTexture = texture->getBackendTexture(); + + if (!context->priv().caps()->areColorTypeAndFormatCompatible( + SkColorTypeToGrColorType(colorType), backendTexture.getBackendFormat())) { + return nullptr; + } + + SkImageInfo info = SkImageInfo::Make(texture->width(), texture->height(), colorType, alphaType, + std::move(colorSpace)); + return std::unique_ptr(new GrBackendTextureImageGenerator( + info, texture.get(), origin, context->priv().contextID(), + std::move(semaphore), backendTexture)); +} + +GrBackendTextureImageGenerator::GrBackendTextureImageGenerator(const SkImageInfo& info, + GrTexture* texture, + GrSurfaceOrigin origin, + uint32_t owningContextID, + sk_sp semaphore, + const GrBackendTexture& backendTex) + : INHERITED(info) + , fRefHelper(new RefHelper(texture, owningContextID)) + , fSemaphore(std::move(semaphore)) + , fBackendTexture(backendTex) + , fSurfaceOrigin(origin) {} + +GrBackendTextureImageGenerator::~GrBackendTextureImageGenerator() { + fRefHelper->unref(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void GrBackendTextureImageGenerator::ReleaseRefHelper_TextureReleaseProc(void* ctx) { + RefHelper* refHelper = static_cast(ctx); + SkASSERT(refHelper); + + refHelper->fBorrowingContextReleaseProc = nullptr; + refHelper->fBorrowingContextID = SK_InvalidGenID; + refHelper->unref(); +} + +sk_sp GrBackendTextureImageGenerator::onGenerateTexture( + GrRecordingContext* context, const SkImageInfo& info, + const SkIPoint& origin, bool willNeedMipMaps) { + SkASSERT(context); + + if (context->backend() != fBackendTexture.backend()) { + return nullptr; + } + if (info.colorType() != this->getInfo().colorType()) { + return nullptr; + } + + auto proxyProvider = context->priv().proxyProvider(); + const GrCaps* caps = context->priv().caps(); + + fBorrowingMutex.acquire(); + sk_sp releaseProcHelper; + if (SK_InvalidGenID != fRefHelper->fBorrowingContextID) { + if (fRefHelper->fBorrowingContextID != context->priv().contextID()) { + fBorrowingMutex.release(); + return nullptr; + } else { + SkASSERT(fRefHelper->fBorrowingContextReleaseProc); + // Ref the release proc to be held by the proxy we make below + releaseProcHelper = sk_ref_sp(fRefHelper->fBorrowingContextReleaseProc); + } + } else { + SkASSERT(!fRefHelper->fBorrowingContextReleaseProc); + // The ref we add to fRefHelper here will be passed into and owned by the + // GrRefCntedCallback. + fRefHelper->ref(); + releaseProcHelper.reset( + new GrRefCntedCallback(ReleaseRefHelper_TextureReleaseProc, fRefHelper)); + fRefHelper->fBorrowingContextReleaseProc = releaseProcHelper.get(); + } + fRefHelper->fBorrowingContextID = context->priv().contextID(); + if (!fRefHelper->fBorrowedTextureKey.isValid()) { + static const auto kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(&fRefHelper->fBorrowedTextureKey, kDomain, 1); + builder[0] = this->uniqueID(); + } + fBorrowingMutex.release(); + + SkASSERT(fRefHelper->fBorrowingContextID == context->priv().contextID()); + + GrBackendFormat backendFormat = fBackendTexture.getBackendFormat(); + SkASSERT(backendFormat.isValid()); + + GrColorType grColorType = SkColorTypeToGrColorType(info.colorType()); + + GrPixelConfig config = caps->getConfigFromBackendFormat(backendFormat, grColorType); + if (kUnknown_GrPixelConfig == config) { + return nullptr; + } + + GrSurfaceDesc desc; + desc.fWidth = fBackendTexture.width(); + desc.fHeight = fBackendTexture.height(); + desc.fConfig = config; + GrMipMapped mipMapped = fBackendTexture.hasMipMaps() ? GrMipMapped::kYes : GrMipMapped::kNo; + + // Ganesh assumes that, when wrapping a mipmapped backend texture from a client, that its + // mipmaps are fully fleshed out. + GrMipMapsStatus mipMapsStatus = fBackendTexture.hasMipMaps() + ? GrMipMapsStatus::kValid : GrMipMapsStatus::kNotAllocated; + + // Must make copies of member variables to capture in the lambda since this image generator may + // be deleted before we actually execute the lambda. + sk_sp proxy = proxyProvider->createLazyProxy( + [refHelper = fRefHelper, releaseProcHelper, semaphore = fSemaphore, + backendTexture = fBackendTexture, grColorType]( + GrResourceProvider* resourceProvider) -> GrSurfaceProxy::LazyCallbackResult { + if (semaphore) { + resourceProvider->priv().gpu()->waitSemaphore(semaphore); + } + + // If a client re-draws the same image multiple times, the texture we return + // will be cached and re-used. If they draw a subset, though, we may be + // re-called. In that case, we want to re-use the borrowed texture we've + // previously created. + sk_sp tex; + SkASSERT(refHelper->fBorrowedTextureKey.isValid()); + auto surf = resourceProvider->findByUniqueKey( + refHelper->fBorrowedTextureKey); + if (surf) { + SkASSERT(surf->asTexture()); + tex = sk_ref_sp(surf->asTexture()); + } else { + // We just gained access to the texture. If we're on the original context, we + // could use the original texture, but we'd have no way of detecting that it's + // no longer in-use. So we always make a wrapped copy, where the release proc + // informs us that the context is done with it. This is unfortunate - we'll have + // two texture objects referencing the same GPU object. However, no client can + // ever see the original texture, so this should be safe. + // We make the texture uncacheable so that the release proc is called ASAP. + tex = resourceProvider->wrapBackendTexture( + backendTexture, grColorType, kBorrow_GrWrapOwnership, + GrWrapCacheable::kNo, kRead_GrIOType); + if (!tex) { + return {}; + } + tex->setRelease(releaseProcHelper); + tex->resourcePriv().setUniqueKey(refHelper->fBorrowedTextureKey); + } + // We use keys to avoid re-wrapping the GrBackendTexture in a GrTexture. This is + // unrelated to the whatever SkImage key may be assigned to the proxy. + return {std::move(tex), true, GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced}; + }, + backendFormat, desc, GrRenderable::kNo, 1, fSurfaceOrigin, mipMapped, mipMapsStatus, + GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, SkBudgeted::kNo, + GrProtected::kNo, GrSurfaceProxy::UseAllocator::kYes); + if (!proxy) { + return nullptr; + } + + if (0 == origin.fX && 0 == origin.fY && + info.width() == fBackendTexture.width() && info.height() == fBackendTexture.height() && + (!willNeedMipMaps || GrMipMapped::kYes == proxy->mipMapped())) { + // If the caller wants the entire texture and we have the correct mip support, we're done + return proxy; + } else { + // Otherwise, make a copy of the requested subset. Make sure our temporary is renderable, + // because Vulkan will want to do the copy as a draw. All other copies would require a + // layout change in Vulkan and we do not change the layout of borrowed images. + GrMipMapped mipMapped = willNeedMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo; + SkIRect subset = SkIRect::MakeXYWH(origin.fX, origin.fY, info.width(), info.height()); + + return GrSurfaceProxy::Copy(context, proxy.get(), grColorType, mipMapped, subset, + SkBackingFit::kExact, SkBudgeted::kYes); + } +} diff --git a/gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.h b/gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.h new file mode 100644 index 0000000000..8a82c5c88b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBackendTextureImageGenerator.h @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrBackendTextureImageGenerator_DEFINED +#define GrBackendTextureImageGenerator_DEFINED + +#include "include/core/SkImageGenerator.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/private/GrResourceKey.h" +#include "include/private/SkMutex.h" + +class GrSemaphore; + +/* + * This ImageGenerator is used to wrap a texture in one GrContext and can then be used as a source + * in another GrContext. It holds onto a semaphore which the producing GrContext will signal and the + * consuming GrContext will wait on before using the texture. Only one GrContext can ever be used + * as a consumer (this is mostly because Vulkan can't allow multiple things to wait on the same + * semaphore). + * + * In practice, this capability is used by clients to create backend-specific texture resources in + * one thread (with, say, GrContext-A) and then ship them over to another GrContext (say, + * GrContext-B) which will then use the texture as a source for draws. GrContext-A uses the + * semaphore to notify GrContext-B when the shared texture is ready to use. + */ +class GrBackendTextureImageGenerator : public SkImageGenerator { +public: + static std::unique_ptr Make(sk_sp, GrSurfaceOrigin, + sk_sp, SkColorType, + SkAlphaType, sk_sp); + + ~GrBackendTextureImageGenerator() override; + +protected: + // NOTE: We would like to validate that the owning context hasn't been abandoned, but we can't + // do that safely (we might be on another thread). So assume everything is fine. + bool onIsValid(GrContext*) const override { return true; } + + TexGenType onCanGenerateTexture() const override { return TexGenType::kCheap; } + sk_sp onGenerateTexture(GrRecordingContext*, const SkImageInfo&, + const SkIPoint&, bool willNeedMipMaps) override; + +private: + GrBackendTextureImageGenerator(const SkImageInfo& info, GrTexture*, GrSurfaceOrigin, + uint32_t owningContextID, sk_sp, + const GrBackendTexture&); + + static void ReleaseRefHelper_TextureReleaseProc(void* ctx); + + class RefHelper : public SkNVRefCnt { + public: + RefHelper(GrTexture*, uint32_t owningContextID); + + ~RefHelper(); + + GrTexture* fOriginalTexture; + uint32_t fOwningContextID; + + // We use this key so that we don't rewrap the GrBackendTexture in a GrTexture for each + // proxy created from this generator for a particular borrowing context. + GrUniqueKey fBorrowedTextureKey; + // There is no ref associated with this pointer. We rely on our atomic bookkeeping with the + // context ID to know when this pointer is valid and safe to use. This is used to make sure + // all uses of the wrapped texture are finished on the borrowing context before we open + // this back up to other contexts. In general a ref to this release proc is owned by all + // proxies and gpu uses of the backend texture. + GrRefCntedCallback* fBorrowingContextReleaseProc; + uint32_t fBorrowingContextID; + }; + + RefHelper* fRefHelper; + // This Mutex is used to guard the borrowing of the texture to one GrContext at a time as well + // as the creation of the fBorrowingContextReleaseProc. The latter happening if two threads with + // the same consuming GrContext try to generate a texture at the same time. + SkMutex fBorrowingMutex; + + sk_sp fSemaphore; + + GrBackendTexture fBackendTexture; + GrSurfaceOrigin fSurfaceOrigin; + + typedef SkImageGenerator INHERITED; +}; +#endif // GrBackendTextureImageGenerator_DEFINED diff --git a/gfx/skia/skia/src/gpu/GrBaseContextPriv.h b/gfx/skia/skia/src/gpu/GrBaseContextPriv.h new file mode 100644 index 0000000000..c65aacbb8b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBaseContextPriv.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBaseContextPriv_DEFINED +#define GrBaseContextPriv_DEFINED + +#include "include/private/GrContext_Base.h" + +/** Class that exposes methods on GrContext_Base that are only intended for use internal to Skia. + This class is purely a privileged window into GrContext_Base. It should never have + additional data members or virtual methods. */ +class GrBaseContextPriv { +public: + // from GrContext_Base + uint32_t contextID() const { return fContext->contextID(); } + + bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); } + + const GrContextOptions& options() const { return fContext->options(); } + + const GrCaps* caps() const { return fContext->caps(); } + sk_sp refCaps() const; + + sk_sp fpFactoryCache(); + + GrImageContext* asImageContext() { return fContext->asImageContext(); } + GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); } + GrContext* asDirectContext() { return fContext->asDirectContext(); } + +private: + explicit GrBaseContextPriv(GrContext_Base* context) : fContext(context) {} + GrBaseContextPriv(const GrBaseContextPriv&); // unimpl + GrBaseContextPriv& operator=(const GrBaseContextPriv&); // unimpl + + // No taking addresses of this type. + const GrBaseContextPriv* operator&() const; + GrBaseContextPriv* operator&(); + + GrContext_Base* fContext; + + friend class GrContext_Base; // to construct/copy this type. +}; + +inline GrBaseContextPriv GrContext_Base::priv() { return GrBaseContextPriv(this); } + +inline const GrBaseContextPriv GrContext_Base::priv () const { + return GrBaseContextPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrBitmapTextureMaker.cpp b/gfx/skia/skia/src/gpu/GrBitmapTextureMaker.cpp new file mode 100644 index 0000000000..4ec2b537bb --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBitmapTextureMaker.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrBitmapTextureMaker.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkPixelRef.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/SkGr.h" + +GrBitmapTextureMaker::GrBitmapTextureMaker(GrRecordingContext* context, const SkBitmap& bitmap, + bool useDecal) + : INHERITED(context, bitmap.width(), bitmap.height(), bitmap.info().colorInfo(), useDecal) + , fBitmap(bitmap) { + if (!bitmap.isVolatile()) { + SkIPoint origin = bitmap.pixelRefOrigin(); + SkIRect subset = SkIRect::MakeXYWH(origin.fX, origin.fY, bitmap.width(), + bitmap.height()); + GrMakeKeyFromImageID(&fOriginalKey, bitmap.pixelRef()->getGenerationID(), subset); + } +} + +sk_sp GrBitmapTextureMaker::refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType onlyIfFast) { + if (AllowedTexGenType::kCheap == onlyIfFast) { + return nullptr; + } + + GrProxyProvider* proxyProvider = this->context()->priv().proxyProvider(); + sk_sp proxy; + + if (fOriginalKey.isValid()) { + auto colorType = SkColorTypeToGrColorType(fBitmap.colorType()); + proxy = proxyProvider->findOrCreateProxyByUniqueKey(fOriginalKey, colorType, + kTopLeft_GrSurfaceOrigin); + if (proxy && (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped())) { + return proxy; + } + } + + if (!proxy) { + proxy = proxyProvider->createProxyFromBitmap(fBitmap, willBeMipped ? GrMipMapped::kYes + : GrMipMapped::kNo); + if (proxy) { + if (fOriginalKey.isValid()) { + proxyProvider->assignUniqueKeyToProxy(fOriginalKey, proxy.get()); + } + if (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped()) { + SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin); + if (fOriginalKey.isValid()) { + GrInstallBitmapUniqueKeyInvalidator( + fOriginalKey, proxyProvider->contextID(), fBitmap.pixelRef()); + } + return proxy; + } + } + } + + if (proxy) { + SkASSERT(willBeMipped); + SkASSERT(GrMipMapped::kNo == proxy->mipMapped()); + // We need a mipped proxy, but we either found a proxy earlier that wasn't mipped or + // generated a non mipped proxy. Thus we generate a new mipped surface and copy the original + // proxy into the base layer. We will then let the gpu generate the rest of the mips. + GrColorType srcColorType = SkColorTypeToGrColorType(fBitmap.colorType()); + if (auto mippedProxy = GrCopyBaseMipMapToTextureProxy(this->context(), proxy.get(), + srcColorType)) { + SkASSERT(mippedProxy->origin() == kTopLeft_GrSurfaceOrigin); + if (fOriginalKey.isValid()) { + // In this case we are stealing the key from the original proxy which should only + // happen when we have just generated mipmaps for an originally unmipped + // proxy/texture. This means that all future uses of the key will access the + // mipmapped version. The texture backing the unmipped version will remain in the + // resource cache until the last texture proxy referencing it is deleted at which + // time it too will be deleted or recycled. + SkASSERT(proxy->getUniqueKey() == fOriginalKey); + proxyProvider->removeUniqueKeyFromProxy(proxy.get()); + proxyProvider->assignUniqueKeyToProxy(fOriginalKey, mippedProxy.get()); + GrInstallBitmapUniqueKeyInvalidator(fOriginalKey, proxyProvider->contextID(), + fBitmap.pixelRef()); + } + return mippedProxy; + } + // We failed to make a mipped proxy with the base copied into it. This could have + // been from failure to make the proxy or failure to do the copy. Thus we will fall + // back to just using the non mipped proxy; See skbug.com/7094. + return proxy; + } + return nullptr; +} + +void GrBitmapTextureMaker::makeCopyKey(const CopyParams& copyParams, GrUniqueKey* copyKey) { + // Destination color space is irrelevant - we always upload the bitmap's contents as-is + if (fOriginalKey.isValid()) { + MakeCopyKeyFromOrigKey(fOriginalKey, copyParams, copyKey); + } +} + +void GrBitmapTextureMaker::didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) { + GrInstallBitmapUniqueKeyInvalidator(copyKey, contextUniqueID, fBitmap.pixelRef()); +} diff --git a/gfx/skia/skia/src/gpu/GrBitmapTextureMaker.h b/gfx/skia/skia/src/gpu/GrBitmapTextureMaker.h new file mode 100644 index 0000000000..9205b90a9e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBitmapTextureMaker.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBitmapTextureMaker_DEFINED +#define GrBitmapTextureMaker_DEFINED + +#include "include/core/SkBitmap.h" +#include "src/gpu/GrTextureMaker.h" + +/** This class manages the conversion of SW-backed bitmaps to GrTextures. If the input bitmap is + non-volatile the texture is cached using a key created from the pixels' image id and the + subset of the pixelref specified by the bitmap. */ +class GrBitmapTextureMaker : public GrTextureMaker { +public: + GrBitmapTextureMaker(GrRecordingContext* context, const SkBitmap& bitmap, + bool useDecal = false); + +protected: + sk_sp refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType onlyIfFast) override; + + void makeCopyKey(const CopyParams& copyParams, GrUniqueKey* copyKey) override; + void didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) override; + +private: + const SkBitmap fBitmap; + GrUniqueKey fOriginalKey; + + typedef GrTextureMaker INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrBlend.h b/gfx/skia/skia/src/gpu/GrBlend.h new file mode 100644 index 0000000000..8b681e8153 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBlend.h @@ -0,0 +1,151 @@ + +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBlend_DEFINED +#define GrBlend_DEFINED + +#include "include/core/SkTypes.h" + +/** + * Equations for alpha-blending. + */ +enum GrBlendEquation { + // Basic blend equations. + kAdd_GrBlendEquation, //= kFirstAdvancedGrBlendEquation + && equation != kIllegal_GrBlendEquation; +} + +static constexpr bool GrBlendModifiesDst(GrBlendEquation equation, GrBlendCoeff srcCoeff, + GrBlendCoeff dstCoeff) { + return (kAdd_GrBlendEquation != equation && kReverseSubtract_GrBlendEquation != equation) || + kZero_GrBlendCoeff != srcCoeff || kOne_GrBlendCoeff != dstCoeff; +} + +/** + * Advanced blend equations can always tweak alpha for coverage. (See GrCustomXfermode.cpp) + * + * For "add" and "reverse subtract" the blend equation with f=coverage is: + * + * D' = f * (S * srcCoeff + D * dstCoeff) + (1-f) * D + * = f * S * srcCoeff + D * (f * dstCoeff + (1 - f)) + * + * (Let srcCoeff be negative for reverse subtract.) We can tweak alpha for coverage when the + * following relationship holds: + * + * (f*S) * srcCoeff' + D * dstCoeff' == f * S * srcCoeff + D * (f * dstCoeff + (1 - f)) + * + * (Where srcCoeff' and dstCoeff' have any reference to S pre-multiplied by f.) + * + * It's easy to see this works for the src term as long as srcCoeff' == srcCoeff (meaning srcCoeff + * does not reference S). For the dst term, this will work as long as the following is true: + *| + * dstCoeff' == f * dstCoeff + (1 - f) + * dstCoeff' == 1 - f * (1 - dstCoeff) + * + * By inspection we can see this will work as long as dstCoeff has a 1, and any other term in + * dstCoeff references S. + * + * Moreover, if the blend doesn't modify the dst at all then it is ok to arbitrarily modify the src + * color so folding in coverage is allowed. + */ +static constexpr bool GrBlendAllowsCoverageAsAlpha(GrBlendEquation equation, + GrBlendCoeff srcCoeff, + GrBlendCoeff dstCoeff) { + return GrBlendEquationIsAdvanced(equation) || + !GrBlendModifiesDst(equation, srcCoeff, dstCoeff) || + ((kAdd_GrBlendEquation == equation || kReverseSubtract_GrBlendEquation == equation) && + !GrBlendCoeffRefsSrc(srcCoeff) && + (kOne_GrBlendCoeff == dstCoeff || kISC_GrBlendCoeff == dstCoeff || + kISA_GrBlendCoeff == dstCoeff)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrBlurUtils.cpp b/gfx/skia/skia/src/gpu/GrBlurUtils.cpp new file mode 100644 index 0000000000..7c922cbdca --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBlurUtils.cpp @@ -0,0 +1,478 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrBlurUtils.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrSoftwarePathRenderer.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#include "src/gpu/geometry/GrShape.h" + +#include "include/core/SkPaint.h" +#include "src/core/SkDraw.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkTLazy.h" +#include "src/gpu/SkGr.h" + +static bool clip_bounds_quick_reject(const SkIRect& clipBounds, const SkIRect& rect) { + return clipBounds.isEmpty() || rect.isEmpty() || !SkIRect::Intersects(clipBounds, rect); +} + +// Draw a mask using the supplied paint. Since the coverage/geometry +// is already burnt into the mask this boils down to a rect draw. +// Return true if the mask was successfully drawn. +static bool draw_mask(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkIRect& maskRect, + GrPaint&& paint, + sk_sp mask, + GrColorType maskColorType) { + SkMatrix inverse; + if (!viewMatrix.invert(&inverse)) { + return false; + } + + SkMatrix matrix = SkMatrix::MakeTrans(-SkIntToScalar(maskRect.fLeft), + -SkIntToScalar(maskRect.fTop)); + matrix.preConcat(viewMatrix); + paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Make(std::move(mask), maskColorType, + matrix)); + + renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::Make(maskRect), inverse); + return true; +} + +static void mask_release_proc(void* addr, void* /*context*/) { + SkMask::FreeImage(addr); +} + +static bool sw_draw_with_mask_filter(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext, + const GrClip& clipData, + const SkMatrix& viewMatrix, + const GrShape& shape, + const SkMaskFilter* filter, + const SkIRect& clipBounds, + GrPaint&& paint, + const GrUniqueKey& key) { + SkASSERT(filter); + SkASSERT(!shape.style().applies()); + + auto proxyProvider = context->priv().proxyProvider(); + + sk_sp filteredMask; + + SkStrokeRec::InitStyle fillOrHairline = shape.style().isSimpleHairline() + ? SkStrokeRec::kHairline_InitStyle + : SkStrokeRec::kFill_InitStyle; + + if (key.isValid()) { + // TODO: this cache look up is duplicated in draw_shape_with_mask_filter for gpu + filteredMask = proxyProvider->findOrCreateProxyByUniqueKey(key, GrColorType::kAlpha_8, + kTopLeft_GrSurfaceOrigin); + } + + SkIRect drawRect; + if (filteredMask) { + SkRect devBounds = shape.bounds(); + viewMatrix.mapRect(&devBounds); + + // Here we need to recompute the destination bounds in order to draw the mask correctly + SkMask srcM, dstM; + if (!SkDraw::ComputeMaskBounds(devBounds, &clipBounds, filter, &viewMatrix, + &srcM.fBounds)) { + return false; + } + + srcM.fFormat = SkMask::kA8_Format; + + if (!as_MFB(filter)->filterMask(&dstM, srcM, viewMatrix, nullptr)) { + return false; + } + + // Unfortunately, we cannot double check that the computed bounds (i.e., dstM.fBounds) + // match the stored bounds of the mask bc the proxy may have been recreated and, + // when it is recreated, it just gets the bounds of the underlying GrTexture (which + // might be a loose fit). + drawRect = dstM.fBounds; + } else { + // TODO: it seems like we could create an SkDraw here and set its fMatrix field rather + // than explicitly transforming the path to device space. + SkPath devPath; + + shape.asPath(&devPath); + + devPath.transform(viewMatrix); + + SkMask srcM, dstM; + if (!SkDraw::DrawToMask(devPath, &clipBounds, filter, &viewMatrix, &srcM, + SkMask::kComputeBoundsAndRenderImage_CreateMode, fillOrHairline)) { + return false; + } + SkAutoMaskFreeImage autoSrc(srcM.fImage); + + SkASSERT(SkMask::kA8_Format == srcM.fFormat); + + if (!as_MFB(filter)->filterMask(&dstM, srcM, viewMatrix, nullptr)) { + return false; + } + // this will free-up dstM when we're done (allocated in filterMask()) + SkAutoMaskFreeImage autoDst(dstM.fImage); + + if (clip_bounds_quick_reject(clipBounds, dstM.fBounds)) { + return false; + } + + // we now have a device-aligned 8bit mask in dstM, ready to be drawn using + // the current clip (and identity matrix) and GrPaint settings + SkBitmap bm; + if (!bm.installPixels(SkImageInfo::MakeA8(dstM.fBounds.width(), dstM.fBounds.height()), + autoDst.release(), dstM.fRowBytes, mask_release_proc, nullptr)) { + return false; + } + bm.setImmutable(); + + sk_sp image = SkImage::MakeFromBitmap(bm); + if (!image) { + return false; + } + + filteredMask = proxyProvider->createTextureProxy(std::move(image), 1, SkBudgeted::kYes, + SkBackingFit::kApprox); + if (!filteredMask) { + return false; + } + + SkASSERT(kTopLeft_GrSurfaceOrigin == filteredMask->origin()); + + drawRect = dstM.fBounds; + + if (key.isValid()) { + proxyProvider->assignUniqueKeyToProxy(key, filteredMask.get()); + } + } + + return draw_mask(renderTargetContext, clipData, viewMatrix, drawRect, + std::move(paint), std::move(filteredMask), GrColorType::kAlpha_8); +} + +// Create a mask of 'shape' and return the resulting renderTargetContext +static std::unique_ptr create_mask_GPU(GrRecordingContext* context, + const SkIRect& maskRect, + const SkMatrix& origViewMatrix, + const GrShape& shape, + int sampleCnt) { + // Use GrResourceProvider::MakeApprox to implement our own approximate size matching, but demand + // a "SkBackingFit::kExact" size match on the actual render target. We do this because the + // filter will reach outside the src bounds, so we need to pre-clear these values to ensure a + // "decal" sampling effect (i.e., ensure reads outside the src bounds return alpha=0). + // + // FIXME: Reads outside the left and top edges will actually clamp to the edge pixel. And in the + // event that MakeApprox does not change the size, reads outside the right and/or bottom will do + // the same. We should offset our filter within the render target and expand the size as needed + // to guarantee at least 1px of padding on all sides. + auto rtContext = context->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, GrResourceProvider::MakeApprox(maskRect.width()), + GrResourceProvider::MakeApprox(maskRect.height()), GrColorType::kAlpha_8, nullptr, + sampleCnt, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); + if (!rtContext) { + return nullptr; + } + + rtContext->clear(SK_PMColor4fTRANSPARENT); + + GrPaint maskPaint; + maskPaint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op); + + // setup new clip + const SkIRect clipRect = SkIRect::MakeWH(maskRect.width(), maskRect.height()); + GrFixedClip clip(clipRect); + + // Draw the mask into maskTexture with the path's integerized top-left at + // the origin using maskPaint. + SkMatrix viewMatrix = origViewMatrix; + viewMatrix.postTranslate(-SkIntToScalar(maskRect.fLeft), -SkIntToScalar(maskRect.fTop)); + rtContext->drawShape(clip, std::move(maskPaint), GrAA::kYes, viewMatrix, shape); + return rtContext; +} + +static bool get_unclipped_shape_dev_bounds(const GrShape& shape, const SkMatrix& matrix, + SkIRect* devBounds) { + SkRect shapeBounds = shape.styledBounds(); + if (shapeBounds.isEmpty()) { + return false; + } + SkRect shapeDevBounds; + matrix.mapRect(&shapeDevBounds, shapeBounds); + // Even though these are "unclipped" bounds we still clip to the int32_t range. + // This is the largest int32_t that is representable exactly as a float. The next 63 larger ints + // would round down to this value when cast to a float, but who really cares. + // INT32_MIN is exactly representable. + static constexpr int32_t kMaxInt = 2147483520; + if (!shapeDevBounds.intersect(SkRect::MakeLTRB(INT32_MIN, INT32_MIN, kMaxInt, kMaxInt))) { + return false; + } + // Make sure that the resulting SkIRect can have representable width and height + if (SkScalarRoundToInt(shapeDevBounds.width()) > kMaxInt || + SkScalarRoundToInt(shapeDevBounds.height()) > kMaxInt) { + return false; + } + shapeDevBounds.roundOut(devBounds); + return true; +} + +// Gets the shape bounds, the clip bounds, and the intersection (if any). Returns false if there +// is no intersection. +static bool get_shape_and_clip_bounds(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + const GrShape& shape, + const SkMatrix& matrix, + SkIRect* unclippedDevShapeBounds, + SkIRect* devClipBounds) { + // compute bounds as intersection of rt size, clip, and path + clip.getConservativeBounds(renderTargetContext->width(), + renderTargetContext->height(), + devClipBounds); + + if (!get_unclipped_shape_dev_bounds(shape, matrix, unclippedDevShapeBounds)) { + *unclippedDevShapeBounds = SkIRect::EmptyIRect(); + return false; + } + + return true; +} + +static void draw_shape_with_mask_filter(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkMaskFilterBase* maskFilter, + const GrShape& origShape) { + SkASSERT(maskFilter); + + const GrShape* shape = &origShape; + SkTLazy tmpShape; + + if (origShape.style().applies()) { + SkScalar styleScale = GrStyle::MatrixToScaleFactor(viewMatrix); + if (0 == styleScale) { + return; + } + + tmpShape.init(origShape.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, styleScale)); + if (tmpShape.get()->isEmpty()) { + return; + } + + shape = tmpShape.get(); + } + + if (maskFilter->directFilterMaskGPU(context, + renderTargetContext, + std::move(paint), + clip, + viewMatrix, + *shape)) { + // the mask filter was able to draw itself directly, so there's nothing + // left to do. + return; + } + assert_alive(paint); + + // If the path is hairline, ignore inverse fill. + bool inverseFilled = shape->inverseFilled() && + !GrPathRenderer::IsStrokeHairlineOrEquivalent(shape->style(), + viewMatrix, nullptr); + + SkIRect unclippedDevShapeBounds, devClipBounds; + if (!get_shape_and_clip_bounds(renderTargetContext, clip, *shape, viewMatrix, + &unclippedDevShapeBounds, + &devClipBounds)) { + // TODO: just cons up an opaque mask here + if (!inverseFilled) { + return; + } + } + + // To prevent overloading the cache with entries during animations we limit the cache of masks + // to cases where the matrix preserves axis alignment. +#ifdef SK_DISABLE_MASKFILTERED_MASK_CACHING + bool useCache = false; +#else + bool useCache = !inverseFilled && viewMatrix.preservesAxisAlignment() && + shape->hasUnstyledKey() && as_MFB(maskFilter)->asABlur(nullptr); +#endif + + const SkIRect* boundsForClip = &devClipBounds; + if (useCache) { + SkIRect clippedMaskRect, unClippedMaskRect; + maskFilter->canFilterMaskGPU(*shape, unclippedDevShapeBounds, devClipBounds, + viewMatrix, &clippedMaskRect); + maskFilter->canFilterMaskGPU(*shape, unclippedDevShapeBounds, unclippedDevShapeBounds, + viewMatrix, &unClippedMaskRect); + if (clippedMaskRect.isEmpty()) { + return; + } + + // Use the cache only if >50% of the filtered mask is visible. + int unclippedWidth = unClippedMaskRect.width(); + int unclippedHeight = unClippedMaskRect.height(); + int64_t unclippedArea = sk_64_mul(unclippedWidth, unclippedHeight); + int64_t clippedArea = sk_64_mul(clippedMaskRect.width(), clippedMaskRect.height()); + int maxTextureSize = renderTargetContext->caps()->maxTextureSize(); + if (unclippedArea > 2 * clippedArea || unclippedWidth > maxTextureSize || + unclippedHeight > maxTextureSize) { + useCache = false; + } else { + // Make the clip not affect the mask + boundsForClip = &unclippedDevShapeBounds; + } + } + + GrUniqueKey maskKey; + if (useCache) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(&maskKey, kDomain, 5 + 2 + shape->unstyledKeySize(), + "Mask Filtered Masks"); + + // We require the upper left 2x2 of the matrix to match exactly for a cache hit. + SkScalar sx = viewMatrix.get(SkMatrix::kMScaleX); + SkScalar sy = viewMatrix.get(SkMatrix::kMScaleY); + SkScalar kx = viewMatrix.get(SkMatrix::kMSkewX); + SkScalar ky = viewMatrix.get(SkMatrix::kMSkewY); + SkScalar tx = viewMatrix.get(SkMatrix::kMTransX); + SkScalar ty = viewMatrix.get(SkMatrix::kMTransY); + // Allow 8 bits each in x and y of subpixel positioning. + SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00; + SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00; + + builder[0] = SkFloat2Bits(sx); + builder[1] = SkFloat2Bits(sy); + builder[2] = SkFloat2Bits(kx); + builder[3] = SkFloat2Bits(ky); + // Distinguish between hairline and filled paths. For hairlines, we also need to include + // the cap. (SW grows hairlines by 0.5 pixel with round and square caps). Note that + // stroke-and-fill of hairlines is turned into pure fill by SkStrokeRec, so this covers + // all cases we might see. + uint32_t styleBits = shape->style().isSimpleHairline() + ? ((shape->style().strokeRec().getCap() << 1) | 1) + : 0; + builder[4] = fracX | (fracY >> 8) | (styleBits << 16); + + SkMaskFilterBase::BlurRec rec; + SkAssertResult(as_MFB(maskFilter)->asABlur(&rec)); + + builder[5] = rec.fStyle; // TODO: we could put this with the other style bits + builder[6] = SkFloat2Bits(rec.fSigma); + shape->writeUnstyledKey(&builder[7]); + } + + SkIRect maskRect; + if (maskFilter->canFilterMaskGPU(*shape, + unclippedDevShapeBounds, + *boundsForClip, + viewMatrix, + &maskRect)) { + if (clip_bounds_quick_reject(*boundsForClip, maskRect)) { + // clipped out + return; + } + + sk_sp filteredMask; + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + if (maskKey.isValid()) { + // TODO: this cache look up is duplicated in sw_draw_with_mask_filter for raster + filteredMask = proxyProvider->findOrCreateProxyByUniqueKey( + maskKey, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin); + } + + if (!filteredMask) { + std::unique_ptr maskRTC(create_mask_GPU( + context, + maskRect, + viewMatrix, + *shape, + renderTargetContext->numSamples())); + if (maskRTC) { + filteredMask = maskFilter->filterMaskGPU(context, + maskRTC->asTextureProxyRef(), + maskRTC->colorInfo().colorType(), + maskRTC->colorInfo().alphaType(), + viewMatrix, + maskRect); + SkASSERT(kTopLeft_GrSurfaceOrigin == filteredMask->origin()); + + if (filteredMask && maskKey.isValid()) { + proxyProvider->assignUniqueKeyToProxy(maskKey, filteredMask.get()); + } + } + } + + if (filteredMask) { + if (draw_mask(renderTargetContext, clip, viewMatrix, + maskRect, std::move(paint), std::move(filteredMask), + GrColorType::kAlpha_8)) { + // This path is completely drawn + return; + } + assert_alive(paint); + } + } + + sw_draw_with_mask_filter(context, renderTargetContext, clip, viewMatrix, *shape, + maskFilter, *boundsForClip, std::move(paint), maskKey); +} + +void GrBlurUtils::drawShapeWithMaskFilter(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + const GrShape& shape, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkMaskFilter* mf) { + draw_shape_with_mask_filter(context, renderTargetContext, clip, std::move(paint), + viewMatrix, as_MFB(mf), shape); +} + +void GrBlurUtils::drawShapeWithMaskFilter(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + const SkPaint& paint, + const SkMatrix& viewMatrix, + const GrShape& shape) { + if (context->priv().abandoned()) { + return; + } + + GrPaint grPaint; + if (!SkPaintToGrPaint(context, renderTargetContext->colorInfo(), paint, viewMatrix, &grPaint)) { + return; + } + + SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter()); + if (mf && !mf->hasFragmentProcessor()) { + // The MaskFilter wasn't already handled in SkPaintToGrPaint + draw_shape_with_mask_filter(context, renderTargetContext, clip, std::move(grPaint), + viewMatrix, mf, shape); + } else { + GrAA aa = GrAA(paint.isAntiAlias()); + renderTargetContext->drawShape(clip, std::move(grPaint), aa, viewMatrix, shape); + } +} diff --git a/gfx/skia/skia/src/gpu/GrBlurUtils.h b/gfx/skia/skia/src/gpu/GrBlurUtils.h new file mode 100644 index 0000000000..d2f2570de0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBlurUtils.h @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBlurUtils_DEFINED +#define GrBlurUtils_DEFINED + +#include "include/private/GrTypesPriv.h" + +class GrClip; +class GrContext; +class GrPaint; +class GrRecordingContext; +class GrRenderTarget; +class GrRenderTargetContext; +class GrShape; +class GrStyle; +struct SkIRect; +class SkMaskFilter; +class SkMatrix; +class SkPaint; +class SkPath; +class SkPathEffect; + + +/** + * Blur utilities. + */ +namespace GrBlurUtils { + /** + * Draw a shape handling the mask filter if present. + */ + void drawShapeWithMaskFilter(GrRecordingContext*, + GrRenderTargetContext*, + const GrClip&, + const SkPaint&, + const SkMatrix& viewMatrix, + const GrShape&); + + /** + * Draw a shape handling the mask filter. The mask filter is not optional. + * The GrPaint will be modified after return. + */ + void drawShapeWithMaskFilter(GrRecordingContext*, + GrRenderTargetContext*, + const GrClip&, + const GrShape&, + GrPaint&&, + const SkMatrix& viewMatrix, + const SkMaskFilter*); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrBuffer.h b/gfx/skia/skia/src/gpu/GrBuffer.h new file mode 100644 index 0000000000..b9780d1f4b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBuffer.h @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBuffer_DEFINED +#define GrBuffer_DEFINED + +#include "include/gpu/GrTypes.h" + +/** Base class for a GPU buffer object or a client side arrays. */ +class GrBuffer { +public: + GrBuffer(const GrBuffer&) = delete; + GrBuffer& operator=(const GrBuffer&) = delete; + + virtual ~GrBuffer() = default; + + // Our subclasses derive from different ref counting base classes. In order to use base + // class pointers with sk_sp we virtualize ref() and unref(). + virtual void ref() const = 0; + virtual void unref() const = 0; + + /** Size of the buffer in bytes. */ + virtual size_t size() const = 0; + + /** Is this an instance of GrCpuBuffer? Otherwise, an instance of GrGpuBuffer. */ + virtual bool isCpuBuffer() const = 0; + +protected: + GrBuffer() = default; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrBufferAllocPool.cpp b/gfx/skia/skia/src/gpu/GrBufferAllocPool.cpp new file mode 100644 index 0000000000..8d09d5c4b8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBufferAllocPool.cpp @@ -0,0 +1,534 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkMacros.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrBufferAllocPool.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrCpuBuffer.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/GrResourceProvider.h" + +sk_sp GrBufferAllocPool::CpuBufferCache::Make( + int maxBuffersToCache) { + return sk_sp(new CpuBufferCache(maxBuffersToCache)); +} + +GrBufferAllocPool::CpuBufferCache::CpuBufferCache(int maxBuffersToCache) + : fMaxBuffersToCache(maxBuffersToCache) { + if (fMaxBuffersToCache) { + fBuffers.reset(new Buffer[fMaxBuffersToCache]); + } +} + +sk_sp GrBufferAllocPool::CpuBufferCache::makeBuffer(size_t size, + bool mustBeInitialized) { + SkASSERT(size > 0); + Buffer* result = nullptr; + if (size == kDefaultBufferSize) { + int i = 0; + for (; i < fMaxBuffersToCache && fBuffers[i].fBuffer; ++i) { + SkASSERT(fBuffers[i].fBuffer->size() == kDefaultBufferSize); + if (fBuffers[i].fBuffer->unique()) { + result = &fBuffers[i]; + } + } + if (!result && i < fMaxBuffersToCache) { + fBuffers[i].fBuffer = GrCpuBuffer::Make(size); + result = &fBuffers[i]; + } + } + Buffer tempResult; + if (!result) { + tempResult.fBuffer = GrCpuBuffer::Make(size); + result = &tempResult; + } + if (mustBeInitialized && !result->fCleared) { + result->fCleared = true; + memset(result->fBuffer->data(), 0, result->fBuffer->size()); + } + return result->fBuffer; +} + +void GrBufferAllocPool::CpuBufferCache::releaseAll() { + for (int i = 0; i < fMaxBuffersToCache && fBuffers[i].fBuffer; ++i) { + fBuffers[i].fBuffer.reset(); + fBuffers[i].fCleared = false; + } +} + +////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + #define VALIDATE validate +#else + static void VALIDATE(bool = false) {} +#endif + +#define UNMAP_BUFFER(block) \ + do { \ + TRACE_EVENT_INSTANT1("skia.gpu", "GrBufferAllocPool Unmapping Buffer", \ + TRACE_EVENT_SCOPE_THREAD, "percent_unwritten", \ + (float)((block).fBytesFree) / (block).fBuffer->size()); \ + SkASSERT(!block.fBuffer->isCpuBuffer()); \ + static_cast(block.fBuffer.get())->unmap(); \ + } while (false) + +constexpr size_t GrBufferAllocPool::kDefaultBufferSize; + +GrBufferAllocPool::GrBufferAllocPool(GrGpu* gpu, GrGpuBufferType bufferType, + sk_sp cpuBufferCache) + : fBlocks(8) + , fCpuBufferCache(std::move(cpuBufferCache)) + , fGpu(gpu) + , fBufferType(bufferType) {} + +void GrBufferAllocPool::deleteBlocks() { + if (fBlocks.count()) { + GrBuffer* buffer = fBlocks.back().fBuffer.get(); + if (!buffer->isCpuBuffer() && static_cast(buffer)->isMapped()) { + UNMAP_BUFFER(fBlocks.back()); + } + } + while (!fBlocks.empty()) { + this->destroyBlock(); + } + SkASSERT(!fBufferPtr); +} + +GrBufferAllocPool::~GrBufferAllocPool() { + VALIDATE(); + this->deleteBlocks(); +} + +void GrBufferAllocPool::reset() { + VALIDATE(); + fBytesInUse = 0; + this->deleteBlocks(); + this->resetCpuData(0); + VALIDATE(); +} + +void GrBufferAllocPool::unmap() { + VALIDATE(); + + if (fBufferPtr) { + BufferBlock& block = fBlocks.back(); + GrBuffer* buffer = block.fBuffer.get(); + if (!buffer->isCpuBuffer()) { + if (static_cast(buffer)->isMapped()) { + UNMAP_BUFFER(block); + } else { + size_t flushSize = block.fBuffer->size() - block.fBytesFree; + this->flushCpuData(fBlocks.back(), flushSize); + } + } + fBufferPtr = nullptr; + } + VALIDATE(); +} + +#ifdef SK_DEBUG +void GrBufferAllocPool::validate(bool unusedBlockAllowed) const { + bool wasDestroyed = false; + if (fBufferPtr) { + SkASSERT(!fBlocks.empty()); + const GrBuffer* buffer = fBlocks.back().fBuffer.get(); + if (!buffer->isCpuBuffer() && !static_cast(buffer)->isMapped()) { + SkASSERT(fCpuStagingBuffer && fCpuStagingBuffer->data() == fBufferPtr); + } + } else if (!fBlocks.empty()) { + const GrBuffer* buffer = fBlocks.back().fBuffer.get(); + SkASSERT(buffer->isCpuBuffer() || !static_cast(buffer)->isMapped()); + } + size_t bytesInUse = 0; + for (int i = 0; i < fBlocks.count() - 1; ++i) { + const GrBuffer* buffer = fBlocks[i].fBuffer.get(); + SkASSERT(buffer->isCpuBuffer() || !static_cast(buffer)->isMapped()); + } + for (int i = 0; !wasDestroyed && i < fBlocks.count(); ++i) { + GrBuffer* buffer = fBlocks[i].fBuffer.get(); + if (!buffer->isCpuBuffer() && static_cast(buffer)->wasDestroyed()) { + wasDestroyed = true; + } else { + size_t bytes = fBlocks[i].fBuffer->size() - fBlocks[i].fBytesFree; + bytesInUse += bytes; + SkASSERT(bytes || unusedBlockAllowed); + } + } + + if (!wasDestroyed) { + SkASSERT(bytesInUse == fBytesInUse); + if (unusedBlockAllowed) { + SkASSERT((fBytesInUse && !fBlocks.empty()) || + (!fBytesInUse && (fBlocks.count() < 2))); + } else { + SkASSERT((0 == fBytesInUse) == fBlocks.empty()); + } + } +} +#endif + +void* GrBufferAllocPool::makeSpace(size_t size, + size_t alignment, + sk_sp* buffer, + size_t* offset) { + VALIDATE(); + + SkASSERT(buffer); + SkASSERT(offset); + + if (fBufferPtr) { + BufferBlock& back = fBlocks.back(); + size_t usedBytes = back.fBuffer->size() - back.fBytesFree; + size_t pad = GrSizeAlignUpPad(usedBytes, alignment); + SkSafeMath safeMath; + size_t alignedSize = safeMath.add(pad, size); + if (!safeMath.ok()) { + return nullptr; + } + if (alignedSize <= back.fBytesFree) { + memset((void*)(reinterpret_cast(fBufferPtr) + usedBytes), 0, pad); + usedBytes += pad; + *offset = usedBytes; + *buffer = back.fBuffer; + back.fBytesFree -= alignedSize; + fBytesInUse += alignedSize; + VALIDATE(); + return (void*)(reinterpret_cast(fBufferPtr) + usedBytes); + } + } + + // We could honor the space request using by a partial update of the current + // VB (if there is room). But we don't currently use draw calls to GL that + // allow the driver to know that previously issued draws won't read from + // the part of the buffer we update. Also, the GL buffer implementation + // may be cheating on the actual buffer size by shrinking the buffer on + // updateData() if the amount of data passed is less than the full buffer + // size. + + if (!this->createBlock(size)) { + return nullptr; + } + SkASSERT(fBufferPtr); + + *offset = 0; + BufferBlock& back = fBlocks.back(); + *buffer = back.fBuffer; + back.fBytesFree -= size; + fBytesInUse += size; + VALIDATE(); + return fBufferPtr; +} + +void* GrBufferAllocPool::makeSpaceAtLeast(size_t minSize, + size_t fallbackSize, + size_t alignment, + sk_sp* buffer, + size_t* offset, + size_t* actualSize) { + VALIDATE(); + + SkASSERT(buffer); + SkASSERT(offset); + SkASSERT(actualSize); + + if (fBufferPtr) { + BufferBlock& back = fBlocks.back(); + size_t usedBytes = back.fBuffer->size() - back.fBytesFree; + size_t pad = GrSizeAlignUpPad(usedBytes, alignment); + if ((minSize + pad) <= back.fBytesFree) { + // Consume padding first, to make subsequent alignment math easier + memset((void*)(reinterpret_cast(fBufferPtr) + usedBytes), 0, pad); + usedBytes += pad; + back.fBytesFree -= pad; + fBytesInUse += pad; + + // Give caller all remaining space in this block up to fallbackSize (but aligned + // correctly) + size_t size; + if (back.fBytesFree >= fallbackSize) { + SkASSERT(GrSizeAlignDown(fallbackSize, alignment) == fallbackSize); + size = fallbackSize; + } else { + size = GrSizeAlignDown(back.fBytesFree, alignment); + } + *offset = usedBytes; + *buffer = back.fBuffer; + *actualSize = size; + back.fBytesFree -= size; + fBytesInUse += size; + VALIDATE(); + return (void*)(reinterpret_cast(fBufferPtr) + usedBytes); + } + } + + // We could honor the space request using by a partial update of the current + // VB (if there is room). But we don't currently use draw calls to GL that + // allow the driver to know that previously issued draws won't read from + // the part of the buffer we update. Also, the GL buffer implementation + // may be cheating on the actual buffer size by shrinking the buffer on + // updateData() if the amount of data passed is less than the full buffer + // size. + + if (!this->createBlock(fallbackSize)) { + return nullptr; + } + SkASSERT(fBufferPtr); + + *offset = 0; + BufferBlock& back = fBlocks.back(); + *buffer = back.fBuffer; + *actualSize = fallbackSize; + back.fBytesFree -= fallbackSize; + fBytesInUse += fallbackSize; + VALIDATE(); + return fBufferPtr; +} + +void GrBufferAllocPool::putBack(size_t bytes) { + VALIDATE(); + + while (bytes) { + // caller shouldn't try to put back more than they've taken + SkASSERT(!fBlocks.empty()); + BufferBlock& block = fBlocks.back(); + size_t bytesUsed = block.fBuffer->size() - block.fBytesFree; + if (bytes >= bytesUsed) { + bytes -= bytesUsed; + fBytesInUse -= bytesUsed; + // if we locked a vb to satisfy the make space and we're releasing + // beyond it, then unmap it. + GrBuffer* buffer = block.fBuffer.get(); + if (!buffer->isCpuBuffer() && static_cast(buffer)->isMapped()) { + UNMAP_BUFFER(block); + } + this->destroyBlock(); + } else { + block.fBytesFree += bytes; + fBytesInUse -= bytes; + bytes = 0; + break; + } + } + + VALIDATE(); +} + +bool GrBufferAllocPool::createBlock(size_t requestSize) { + size_t size = SkTMax(requestSize, kDefaultBufferSize); + + VALIDATE(); + + BufferBlock& block = fBlocks.push_back(); + + block.fBuffer = this->getBuffer(size); + if (!block.fBuffer) { + fBlocks.pop_back(); + return false; + } + + block.fBytesFree = block.fBuffer->size(); + if (fBufferPtr) { + SkASSERT(fBlocks.count() > 1); + BufferBlock& prev = fBlocks.fromBack(1); + GrBuffer* buffer = prev.fBuffer.get(); + if (!buffer->isCpuBuffer()) { + if (static_cast(buffer)->isMapped()) { + UNMAP_BUFFER(prev); + } else { + this->flushCpuData(prev, prev.fBuffer->size() - prev.fBytesFree); + } + } + fBufferPtr = nullptr; + } + + SkASSERT(!fBufferPtr); + + // If the buffer is CPU-backed we "map" it because it is free to do so and saves a copy. + // Otherwise when buffer mapping is supported we map if the buffer size is greater than the + // threshold. + if (block.fBuffer->isCpuBuffer()) { + fBufferPtr = static_cast(block.fBuffer.get())->data(); + SkASSERT(fBufferPtr); + } else { + if (GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() && + size > fGpu->caps()->bufferMapThreshold()) { + fBufferPtr = static_cast(block.fBuffer.get())->map(); + } + } + if (!fBufferPtr) { + this->resetCpuData(block.fBytesFree); + fBufferPtr = fCpuStagingBuffer->data(); + } + + VALIDATE(true); + + return true; +} + +void GrBufferAllocPool::destroyBlock() { + SkASSERT(!fBlocks.empty()); + SkASSERT(fBlocks.back().fBuffer->isCpuBuffer() || + !static_cast(fBlocks.back().fBuffer.get())->isMapped()); + fBlocks.pop_back(); + fBufferPtr = nullptr; +} + +void GrBufferAllocPool::resetCpuData(size_t newSize) { + SkASSERT(newSize >= kDefaultBufferSize || !newSize); + if (!newSize) { + fCpuStagingBuffer.reset(); + return; + } + if (fCpuStagingBuffer && newSize <= fCpuStagingBuffer->size()) { + return; + } + bool mustInitialize = fGpu->caps()->mustClearUploadedBufferData(); + fCpuStagingBuffer = fCpuBufferCache ? fCpuBufferCache->makeBuffer(newSize, mustInitialize) + : GrCpuBuffer::Make(newSize); +} + +void GrBufferAllocPool::flushCpuData(const BufferBlock& block, size_t flushSize) { + SkASSERT(block.fBuffer.get()); + SkASSERT(!block.fBuffer.get()->isCpuBuffer()); + GrGpuBuffer* buffer = static_cast(block.fBuffer.get()); + SkASSERT(!buffer->isMapped()); + SkASSERT(fCpuStagingBuffer && fCpuStagingBuffer->data() == fBufferPtr); + SkASSERT(flushSize <= buffer->size()); + VALIDATE(true); + + if (GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() && + flushSize > fGpu->caps()->bufferMapThreshold()) { + void* data = buffer->map(); + if (data) { + memcpy(data, fBufferPtr, flushSize); + UNMAP_BUFFER(block); + return; + } + } + buffer->updateData(fBufferPtr, flushSize); + VALIDATE(true); +} + +sk_sp GrBufferAllocPool::getBuffer(size_t size) { + auto resourceProvider = fGpu->getContext()->priv().resourceProvider(); + + if (fGpu->caps()->preferClientSideDynamicBuffers()) { + bool mustInitialize = fGpu->caps()->mustClearUploadedBufferData(); + return fCpuBufferCache ? fCpuBufferCache->makeBuffer(size, mustInitialize) + : GrCpuBuffer::Make(size); + } + return resourceProvider->createBuffer(size, fBufferType, kDynamic_GrAccessPattern); +} + +//////////////////////////////////////////////////////////////////////////////// + +GrVertexBufferAllocPool::GrVertexBufferAllocPool(GrGpu* gpu, sk_sp cpuBufferCache) + : GrBufferAllocPool(gpu, GrGpuBufferType::kVertex, std::move(cpuBufferCache)) {} + +void* GrVertexBufferAllocPool::makeSpace(size_t vertexSize, + int vertexCount, + sk_sp* buffer, + int* startVertex) { + SkASSERT(vertexCount >= 0); + SkASSERT(buffer); + SkASSERT(startVertex); + + size_t offset SK_INIT_TO_AVOID_WARNING; + void* ptr = INHERITED::makeSpace(SkSafeMath::Mul(vertexSize, vertexCount), + vertexSize, + buffer, + &offset); + + SkASSERT(0 == offset % vertexSize); + *startVertex = static_cast(offset / vertexSize); + return ptr; +} + +void* GrVertexBufferAllocPool::makeSpaceAtLeast(size_t vertexSize, int minVertexCount, + int fallbackVertexCount, + sk_sp* buffer, int* startVertex, + int* actualVertexCount) { + SkASSERT(minVertexCount >= 0); + SkASSERT(fallbackVertexCount >= minVertexCount); + SkASSERT(buffer); + SkASSERT(startVertex); + SkASSERT(actualVertexCount); + + size_t offset SK_INIT_TO_AVOID_WARNING; + size_t actualSize SK_INIT_TO_AVOID_WARNING; + void* ptr = INHERITED::makeSpaceAtLeast(SkSafeMath::Mul(vertexSize, minVertexCount), + SkSafeMath::Mul(vertexSize, fallbackVertexCount), + vertexSize, + buffer, + &offset, + &actualSize); + + SkASSERT(0 == offset % vertexSize); + *startVertex = static_cast(offset / vertexSize); + + SkASSERT(0 == actualSize % vertexSize); + SkASSERT(actualSize >= vertexSize * minVertexCount); + *actualVertexCount = static_cast(actualSize / vertexSize); + + return ptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +GrIndexBufferAllocPool::GrIndexBufferAllocPool(GrGpu* gpu, sk_sp cpuBufferCache) + : GrBufferAllocPool(gpu, GrGpuBufferType::kIndex, std::move(cpuBufferCache)) {} + +void* GrIndexBufferAllocPool::makeSpace(int indexCount, sk_sp* buffer, + int* startIndex) { + SkASSERT(indexCount >= 0); + SkASSERT(buffer); + SkASSERT(startIndex); + + size_t offset SK_INIT_TO_AVOID_WARNING; + void* ptr = INHERITED::makeSpace(SkSafeMath::Mul(indexCount, sizeof(uint16_t)), + sizeof(uint16_t), + buffer, + &offset); + + SkASSERT(0 == offset % sizeof(uint16_t)); + *startIndex = static_cast(offset / sizeof(uint16_t)); + return ptr; +} + +void* GrIndexBufferAllocPool::makeSpaceAtLeast(int minIndexCount, int fallbackIndexCount, + sk_sp* buffer, int* startIndex, + int* actualIndexCount) { + SkASSERT(minIndexCount >= 0); + SkASSERT(fallbackIndexCount >= minIndexCount); + SkASSERT(buffer); + SkASSERT(startIndex); + SkASSERT(actualIndexCount); + + size_t offset SK_INIT_TO_AVOID_WARNING; + size_t actualSize SK_INIT_TO_AVOID_WARNING; + void* ptr = INHERITED::makeSpaceAtLeast(SkSafeMath::Mul(minIndexCount, sizeof(uint16_t)), + SkSafeMath::Mul(fallbackIndexCount, sizeof(uint16_t)), + sizeof(uint16_t), + buffer, + &offset, + &actualSize); + + SkASSERT(0 == offset % sizeof(uint16_t)); + *startIndex = static_cast(offset / sizeof(uint16_t)); + + SkASSERT(0 == actualSize % sizeof(uint16_t)); + SkASSERT(actualSize >= minIndexCount * sizeof(uint16_t)); + *actualIndexCount = static_cast(actualSize / sizeof(uint16_t)); + return ptr; +} diff --git a/gfx/skia/skia/src/gpu/GrBufferAllocPool.h b/gfx/skia/skia/src/gpu/GrBufferAllocPool.h new file mode 100644 index 0000000000..d42ef56423 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrBufferAllocPool.h @@ -0,0 +1,317 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBufferAllocPool_DEFINED +#define GrBufferAllocPool_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/GrTypesPriv.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "src/gpu/GrCpuBuffer.h" +#include "src/gpu/GrNonAtomicRef.h" + +class GrGpu; + +/** + * A pool of geometry buffers tied to a GrGpu. + * + * The pool allows a client to make space for geometry and then put back excess + * space if it over allocated. When a client is ready to draw from the pool + * it calls unmap on the pool ensure buffers are ready for drawing. The pool + * can be reset after drawing is completed to recycle space. + * + * At creation time a minimum per-buffer size can be specified. Additionally, + * a number of buffers to preallocate can be specified. These will + * be allocated at the min size and kept around until the pool is destroyed. + */ +class GrBufferAllocPool : SkNoncopyable { +public: + static constexpr size_t kDefaultBufferSize = 1 << 15; + + /** + * A cache object that can be shared by multiple GrBufferAllocPool instances. It caches + * cpu buffer allocations to avoid reallocating them. + */ + class CpuBufferCache : public GrNonAtomicRef { + public: + static sk_sp Make(int maxBuffersToCache); + + sk_sp makeBuffer(size_t size, bool mustBeInitialized); + void releaseAll(); + + private: + CpuBufferCache(int maxBuffersToCache); + + struct Buffer { + sk_sp fBuffer; + bool fCleared = false; + }; + std::unique_ptr fBuffers; + int fMaxBuffersToCache = 0; + }; + + /** + * Ensures all buffers are unmapped and have all data written to them. + * Call before drawing using buffers from the pool. + */ + void unmap(); + + /** + * Invalidates all the data in the pool, unrefs non-preallocated buffers. + */ + void reset(); + + /** + * Frees data from makeSpaces in LIFO order. + */ + void putBack(size_t bytes); + +protected: + /** + * Constructor + * + * @param gpu The GrGpu used to create the buffers. + * @param bufferType The type of buffers to create. + * @param cpuBufferCache If non-null a cache for client side array buffers + * or staging buffers used before data is uploaded to + * GPU buffer objects. + */ + GrBufferAllocPool(GrGpu* gpu, GrGpuBufferType bufferType, sk_sp cpuBufferCache); + + virtual ~GrBufferAllocPool(); + + /** + * Returns a block of memory to hold data. A buffer designated to hold the + * data is given to the caller. The buffer may or may not be locked. The + * returned ptr remains valid until any of the following: + * *makeSpace is called again. + * *unmap is called. + * *reset is called. + * *this object is destroyed. + * + * Once unmap on the pool is called the data is guaranteed to be in the + * buffer at the offset indicated by offset. Until that time it may be + * in temporary storage and/or the buffer may be locked. + * + * @param size the amount of data to make space for + * @param alignment alignment constraint from start of buffer + * @param buffer returns the buffer that will hold the data. + * @param offset returns the offset into buffer of the data. + * @return pointer to where the client should write the data. + */ + void* makeSpace(size_t size, size_t alignment, sk_sp* buffer, size_t* offset); + + /** + * Returns a block of memory to hold data. A buffer designated to hold the + * data is given to the caller. The buffer may or may not be locked. The + * returned ptr remains valid until any of the following: + * *makeSpace is called again. + * *unmap is called. + * *reset is called. + * *this object is destroyed. + * + * Once unmap on the pool is called the data is guaranteed to be in the + * buffer at the offset indicated by offset. Until that time it may be + * in temporary storage and/or the buffer may be locked. + * + * The caller requests a minimum number of bytes, but the block may be (much) + * larger. Assuming that a new block must be allocated, it will be fallbackSize bytes. + * The actual block size is returned in actualSize. + * + * @param minSize the minimum amount of data to make space for + * @param fallbackSize the amount of data to make space for if a new block is needed + * @param alignment alignment constraint from start of buffer + * @param buffer returns the buffer that will hold the data. + * @param offset returns the offset into buffer of the data. + * @param actualSize returns the capacity of the block + * @return pointer to where the client should write the data. + */ + void* makeSpaceAtLeast(size_t minSize, + size_t fallbackSize, + size_t alignment, + sk_sp* buffer, + size_t* offset, + size_t* actualSize); + + sk_sp getBuffer(size_t size); + +private: + struct BufferBlock { + size_t fBytesFree; + sk_sp fBuffer; + }; + + bool createBlock(size_t requestSize); + void destroyBlock(); + void deleteBlocks(); + void flushCpuData(const BufferBlock& block, size_t flushSize); + void resetCpuData(size_t newSize); +#ifdef SK_DEBUG + void validate(bool unusedBlockAllowed = false) const; +#endif + size_t fBytesInUse = 0; + + SkTArray fBlocks; + sk_sp fCpuBufferCache; + sk_sp fCpuStagingBuffer; + GrGpu* fGpu; + GrGpuBufferType fBufferType; + void* fBufferPtr = nullptr; +}; + +/** + * A GrBufferAllocPool of vertex buffers + */ +class GrVertexBufferAllocPool : public GrBufferAllocPool { +public: + /** + * Constructor + * + * @param gpu The GrGpu used to create the vertex buffers. + * @param cpuBufferCache If non-null a cache for client side array buffers + * or staging buffers used before data is uploaded to + * GPU buffer objects. + */ + GrVertexBufferAllocPool(GrGpu* gpu, sk_sp cpuBufferCache); + + /** + * Returns a block of memory to hold vertices. A buffer designated to hold + * the vertices given to the caller. The buffer may or may not be locked. + * The returned ptr remains valid until any of the following: + * *makeSpace is called again. + * *unmap is called. + * *reset is called. + * *this object is destroyed. + * + * Once unmap on the pool is called the vertices are guaranteed to be in + * the buffer at the offset indicated by startVertex. Until that time they + * may be in temporary storage and/or the buffer may be locked. + * + * @param vertexSize specifies size of a vertex to allocate space for + * @param vertexCount number of vertices to allocate space for + * @param buffer returns the vertex buffer that will hold the + * vertices. + * @param startVertex returns the offset into buffer of the first vertex. + * In units of the size of a vertex from layout param. + * @return pointer to first vertex. + */ + void* makeSpace(size_t vertexSize, + int vertexCount, + sk_sp* buffer, + int* startVertex); + + /** + * Returns a block of memory to hold vertices. A buffer designated to hold + * the vertices given to the caller. The buffer may or may not be locked. + * The returned ptr remains valid until any of the following: + * *makeSpace is called again. + * *unmap is called. + * *reset is called. + * *this object is destroyed. + * + * Once unmap on the pool is called the vertices are guaranteed to be in + * the buffer at the offset indicated by startVertex. Until that time they + * may be in temporary storage and/or the buffer may be locked. + * + * The caller requests a minimum number of vertices, but the block may be (much) + * larger. Assuming that a new block must be allocated, it will be sized to hold + * fallbackVertexCount vertices. The actual block size (in vertices) is returned in + * actualVertexCount. + * + * @param vertexSize specifies size of a vertex to allocate space for + * @param minVertexCount minimum number of vertices to allocate space for + * @param fallbackVertexCount number of vertices to allocate space for if a new block is needed + * @param buffer returns the vertex buffer that will hold the vertices. + * @param startVertex returns the offset into buffer of the first vertex. + * In units of the size of a vertex from layout param. + * @param actualVertexCount returns the capacity of the block (in vertices) + * @return pointer to first vertex. + */ + void* makeSpaceAtLeast(size_t vertexSize, + int minVertexCount, + int fallbackVertexCount, + sk_sp* buffer, + int* startVertex, + int* actualVertexCount); + +private: + typedef GrBufferAllocPool INHERITED; +}; + +/** + * A GrBufferAllocPool of index buffers + */ +class GrIndexBufferAllocPool : public GrBufferAllocPool { +public: + /** + * Constructor + * + * @param gpu The GrGpu used to create the index buffers. + * @param cpuBufferCache If non-null a cache for client side array buffers + * or staging buffers used before data is uploaded to + * GPU buffer objects. + */ + GrIndexBufferAllocPool(GrGpu* gpu, sk_sp cpuBufferCache); + + /** + * Returns a block of memory to hold indices. A buffer designated to hold + * the indices is given to the caller. The buffer may or may not be locked. + * The returned ptr remains valid until any of the following: + * *makeSpace is called again. + * *unmap is called. + * *reset is called. + * *this object is destroyed. + * + * Once unmap on the pool is called the indices are guaranteed to be in the + * buffer at the offset indicated by startIndex. Until that time they may be + * in temporary storage and/or the buffer may be locked. + * + * @param indexCount number of indices to allocate space for + * @param buffer returns the index buffer that will hold the indices. + * @param startIndex returns the offset into buffer of the first index. + * @return pointer to first index. + */ + void* makeSpace(int indexCount, sk_sp* buffer, int* startIndex); + + /** + * Returns a block of memory to hold indices. A buffer designated to hold + * the indices is given to the caller. The buffer may or may not be locked. + * The returned ptr remains valid until any of the following: + * *makeSpace is called again. + * *unmap is called. + * *reset is called. + * *this object is destroyed. + * + * Once unmap on the pool is called the indices are guaranteed to be in the + * buffer at the offset indicated by startIndex. Until that time they may be + * in temporary storage and/or the buffer may be locked. + * + * The caller requests a minimum number of indices, but the block may be (much) + * larger. Assuming that a new block must be allocated, it will be sized to hold + * fallbackIndexCount indices. The actual block size (in indices) is returned in + * actualIndexCount. + * + * @param minIndexCount minimum number of indices to allocate space for + * @param fallbackIndexCount number of indices to allocate space for if a new block is needed + * @param buffer returns the index buffer that will hold the indices. + * @param startIndex returns the offset into buffer of the first index. + * @param actualIndexCount returns the capacity of the block (in indices) + * @return pointer to first index. + */ + void* makeSpaceAtLeast(int minIndexCount, + int fallbackIndexCount, + sk_sp* buffer, + int* startIndex, + int* actualIndexCount); + +private: + typedef GrBufferAllocPool INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrCaps.cpp b/gfx/skia/skia/src/gpu/GrCaps.cpp new file mode 100644 index 0000000000..0b93e58a59 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrCaps.cpp @@ -0,0 +1,393 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContextOptions.h" +#include "include/gpu/GrSurface.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrWindowRectangles.h" +#include "src/utils/SkJSONWriter.h" + +GrCaps::GrCaps(const GrContextOptions& options) { + fMipMapSupport = false; + fNPOTTextureTileSupport = false; + fReuseScratchTextures = true; + fReuseScratchBuffers = true; + fGpuTracingSupport = false; + fOversizedStencilSupport = false; + fTextureBarrierSupport = false; + fSampleLocationsSupport = false; + fMultisampleDisableSupport = false; + fInstanceAttribSupport = false; + fMixedSamplesSupport = false; + fMSAAResolvesAutomatically = false; + fUsePrimitiveRestart = false; + fPreferClientSideDynamicBuffers = false; + fPreferFullscreenClears = false; + fMustClearUploadedBufferData = false; + fShouldInitializeTextures = false; + fSupportsAHardwareBufferImages = false; + fFenceSyncSupport = false; + fSemaphoreSupport = false; + fCrossContextTextureSupport = false; + fHalfFloatVertexAttributeSupport = false; + fDynamicStateArrayGeometryProcessorTextureSupport = false; + fPerformPartialClearsAsDraws = false; + fPerformColorClearsAsDraws = false; + fPerformStencilClearsAsDraws = false; + fAllowCoverageCounting = false; + fTransferBufferSupport = false; + fWritePixelsRowBytesSupport = false; + fReadPixelsRowBytesSupport = false; + fDriverBlacklistCCPR = false; + fDriverBlacklistMSAACCPR = false; + + fBlendEquationSupport = kBasic_BlendEquationSupport; + fAdvBlendEqBlacklist = 0; + + fMapBufferFlags = kNone_MapFlags; + + fMaxVertexAttributes = 0; + fMaxRenderTargetSize = 1; + fMaxPreferredRenderTargetSize = 1; + fMaxTextureSize = 1; + fMaxWindowRectangles = 0; + fInternalMultisampleCount = 0; + + fSuppressPrints = options.fSuppressPrints; +#if GR_TEST_UTILS + fWireframeMode = options.fWireframeMode; +#else + fWireframeMode = false; +#endif + fBufferMapThreshold = options.fBufferMapThreshold; + fAvoidStencilBuffers = false; + fAvoidWritePixelsFastPath = false; + + fPreferVRAMUseOverFlushes = true; + + fPreferTrianglesOverSampleMask = false; + + // Default to true, allow older versions of OpenGL to disable explicitly + fClampToBorderSupport = true; + + fDriverBugWorkarounds = options.fDriverBugWorkarounds; +} + +void GrCaps::applyOptionsOverrides(const GrContextOptions& options) { + this->onApplyOptionsOverrides(options); + if (options.fDisableDriverCorrectnessWorkarounds) { + SkASSERT(!fDriverBlacklistCCPR); + SkASSERT(!fDriverBlacklistMSAACCPR); + SkASSERT(!fAvoidStencilBuffers); + SkASSERT(!fAdvBlendEqBlacklist); + SkASSERT(!fPerformColorClearsAsDraws); + SkASSERT(!fPerformStencilClearsAsDraws); + // Don't check the partial-clear workaround, since that is a backend limitation, not a + // driver workaround (it just so happens the fallbacks are the same). + } + if (GrContextOptions::Enable::kNo == options.fUseDrawInsteadOfClear) { + fPerformColorClearsAsDraws = false; + fPerformStencilClearsAsDraws = false; + } else if (GrContextOptions::Enable::kYes == options.fUseDrawInsteadOfClear) { + fPerformColorClearsAsDraws = true; + fPerformStencilClearsAsDraws = true; + } + + fAllowCoverageCounting = !options.fDisableCoverageCountingPaths; + + fMaxTextureSize = SkTMin(fMaxTextureSize, options.fMaxTextureSizeOverride); + fMaxTileSize = fMaxTextureSize; +#if GR_TEST_UTILS + // If the max tile override is zero, it means we should use the max texture size. + if (options.fMaxTileSizeOverride && options.fMaxTileSizeOverride < fMaxTextureSize) { + fMaxTileSize = options.fMaxTileSizeOverride; + } + if (options.fSuppressGeometryShaders) { + fShaderCaps->fGeometryShaderSupport = false; + } + if (options.fClearAllTextures) { + fShouldInitializeTextures = true; + } +#endif + + if (fMaxWindowRectangles > GrWindowRectangles::kMaxWindows) { + SkDebugf("WARNING: capping window rectangles at %i. HW advertises support for %i.\n", + GrWindowRectangles::kMaxWindows, fMaxWindowRectangles); + fMaxWindowRectangles = GrWindowRectangles::kMaxWindows; + } + + fInternalMultisampleCount = options.fInternalMultisampleCount; + + fAvoidStencilBuffers = options.fAvoidStencilBuffers; + + fDriverBugWorkarounds.applyOverrides(options.fDriverBugWorkarounds); +} + + +#ifdef SK_ENABLE_DUMP_GPU +#include "src/gpu/GrTestUtils.h" + +static SkString map_flags_to_string(uint32_t flags) { + SkString str; + if (GrCaps::kNone_MapFlags == flags) { + str = "none"; + } else { + SkASSERT(GrCaps::kCanMap_MapFlag & flags); + SkDEBUGCODE(flags &= ~GrCaps::kCanMap_MapFlag); + str = "can_map"; + + if (GrCaps::kSubset_MapFlag & flags) { + str.append(" partial"); + } else { + str.append(" full"); + } + SkDEBUGCODE(flags &= ~GrCaps::kSubset_MapFlag); + if (GrCaps::kAsyncRead_MapFlag & flags) { + str.append(" async_read"); + } else { + str.append(" sync_read"); + } + SkDEBUGCODE(flags &= ~GrCaps::kAsyncRead_MapFlag); + } + SkASSERT(0 == flags); // Make sure we handled all the flags. + return str; +} + +void GrCaps::dumpJSON(SkJSONWriter* writer) const { + writer->beginObject(); + + writer->appendBool("MIP Map Support", fMipMapSupport); + writer->appendBool("NPOT Texture Tile Support", fNPOTTextureTileSupport); + writer->appendBool("Reuse Scratch Textures", fReuseScratchTextures); + writer->appendBool("Reuse Scratch Buffers", fReuseScratchBuffers); + writer->appendBool("Gpu Tracing Support", fGpuTracingSupport); + writer->appendBool("Oversized Stencil Support", fOversizedStencilSupport); + writer->appendBool("Texture Barrier Support", fTextureBarrierSupport); + writer->appendBool("Sample Locations Support", fSampleLocationsSupport); + writer->appendBool("Multisample disable support", fMultisampleDisableSupport); + writer->appendBool("Instance Attrib Support", fInstanceAttribSupport); + writer->appendBool("Mixed Samples Support", fMixedSamplesSupport); + writer->appendBool("MSAA Resolves Automatically", fMSAAResolvesAutomatically); + writer->appendBool("Use primitive restart", fUsePrimitiveRestart); + writer->appendBool("Prefer client-side dynamic buffers", fPreferClientSideDynamicBuffers); + writer->appendBool("Prefer fullscreen clears (and stencil discard)", fPreferFullscreenClears); + writer->appendBool("Must clear buffer memory", fMustClearUploadedBufferData); + writer->appendBool("Should initialize textures", fShouldInitializeTextures); + writer->appendBool("Supports importing AHardwareBuffers", fSupportsAHardwareBufferImages); + writer->appendBool("Fence sync support", fFenceSyncSupport); + writer->appendBool("Semaphore support", fSemaphoreSupport); + writer->appendBool("Cross context texture support", fCrossContextTextureSupport); + writer->appendBool("Half float vertex attribute support", fHalfFloatVertexAttributeSupport); + writer->appendBool("Specify GeometryProcessor textures as a dynamic state array", + fDynamicStateArrayGeometryProcessorTextureSupport); + writer->appendBool("Use draws for partial clears", fPerformPartialClearsAsDraws); + writer->appendBool("Use draws for color clears", fPerformColorClearsAsDraws); + writer->appendBool("Use draws for stencil clip clears", fPerformStencilClearsAsDraws); + writer->appendBool("Allow coverage counting shortcuts", fAllowCoverageCounting); + writer->appendBool("Supports transfer buffers", fTransferBufferSupport); + writer->appendBool("Write pixels row bytes support", fWritePixelsRowBytesSupport); + writer->appendBool("Read pixels row bytes support", fReadPixelsRowBytesSupport); + writer->appendBool("Blacklist CCPR on current driver [workaround]", fDriverBlacklistCCPR); + writer->appendBool("Blacklist MSAA version of CCPR on current driver [workaround]", + fDriverBlacklistMSAACCPR); + writer->appendBool("Clamp-to-border", fClampToBorderSupport); + + writer->appendBool("Prefer VRAM Use over flushes [workaround]", fPreferVRAMUseOverFlushes); + writer->appendBool("Prefer more triangles over sample mask [MSAA only]", + fPreferTrianglesOverSampleMask); + writer->appendBool("Avoid stencil buffers [workaround]", fAvoidStencilBuffers); + + if (this->advancedBlendEquationSupport()) { + writer->appendHexU32("Advanced Blend Equation Blacklist", fAdvBlendEqBlacklist); + } + + writer->appendS32("Max Vertex Attributes", fMaxVertexAttributes); + writer->appendS32("Max Texture Size", fMaxTextureSize); + writer->appendS32("Max Render Target Size", fMaxRenderTargetSize); + writer->appendS32("Max Preferred Render Target Size", fMaxPreferredRenderTargetSize); + writer->appendS32("Max Window Rectangles", fMaxWindowRectangles); + writer->appendS32("Preferred Sample Count for Internal MSAA and Mixed Samples", + fInternalMultisampleCount); + + static const char* kBlendEquationSupportNames[] = { + "Basic", + "Advanced", + "Advanced Coherent", + }; + GR_STATIC_ASSERT(0 == kBasic_BlendEquationSupport); + GR_STATIC_ASSERT(1 == kAdvanced_BlendEquationSupport); + GR_STATIC_ASSERT(2 == kAdvancedCoherent_BlendEquationSupport); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kBlendEquationSupportNames) == kLast_BlendEquationSupport + 1); + + writer->appendString("Blend Equation Support", + kBlendEquationSupportNames[fBlendEquationSupport]); + writer->appendString("Map Buffer Support", map_flags_to_string(fMapBufferFlags).c_str()); + + this->onDumpJSON(writer); + + writer->appendName("shaderCaps"); + this->shaderCaps()->dumpJSON(writer); + + writer->endObject(); +} +#else +void GrCaps::dumpJSON(SkJSONWriter* writer) const { } +#endif + +bool GrCaps::surfaceSupportsWritePixels(const GrSurface* surface) const { + return surface->readOnly() ? false : this->onSurfaceSupportsWritePixels(surface); +} + +bool GrCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const { + if (dst->readOnly()) { + return false; + } + // Currently we only ever do copies where the configs are the same. This check really should be + // checking if the backend formats, color types, and swizzle are compatible. Our copy always + // copies exact byte to byte from src to dst so when need to check the if we do this, the dst + // has the expected values stored in the right places taking the swizzle into account. For now + // we can be more restrictive and just make sure the configs are the same and if we generalize + // copies and swizzles more in the future this can be updated. + if (this->makeConfigSpecific(dst->config(), dst->backendFormat()) != + this->makeConfigSpecific(src->config(), src->backendFormat())) { + return false; + } + return this->onCanCopySurface(dst, src, srcRect, dstPoint); +} + +bool GrCaps::validateSurfaceParams(const SkISize& size, const GrBackendFormat& format, + GrPixelConfig config, GrRenderable renderable, + int renderTargetSampleCnt, GrMipMapped mipped) const { + if (!this->isFormatTexturable(format)) { + return false; + } + + if (GrMipMapped::kYes == mipped && !this->mipMapSupport()) { + return false; + } + + if (size.width() < 1 || size.height() < 1) { + return false; + } + + if (renderable == GrRenderable::kYes) { + if (!this->isFormatRenderable(format, renderTargetSampleCnt)) { + return false; + } + int maxRTSize = this->maxRenderTargetSize(); + if (size.width() > maxRTSize || size.height() > maxRTSize) { + return false; + } + } else { + // We currently do not support multisampled textures + if (renderTargetSampleCnt != 1) { + return false; + } + int maxSize = this->maxTextureSize(); + if (size.width() > maxSize || size.height() > maxSize) { + return false; + } + } + + return true; +} + +GrCaps::SupportedRead GrCaps::supportedReadPixelsColorType(GrColorType srcColorType, + const GrBackendFormat& srcFormat, + GrColorType dstColorType) const { + SupportedRead read = this->onSupportedReadPixelsColorType(srcColorType, srcFormat, + dstColorType); + + // There are known problems with 24 vs 32 bit BPP with this color type. Just fail for now if + // using a transfer buffer. + if (GrColorType::kRGB_888x == read.fColorType) { + read.fOffsetAlignmentForTransferBuffer = 0; + } + // It's very convenient to access 1 byte-per-channel 32 bit color types as uint32_t on the CPU. + // Make those aligned reads out of the buffer even if the underlying API doesn't require it. + auto componentFlags = GrColorTypeComponentFlags(read.fColorType); + if ((componentFlags == kRGBA_SkColorTypeComponentFlags || + componentFlags == kRGB_SkColorTypeComponentFlags || + componentFlags == kAlpha_SkColorTypeComponentFlag || + componentFlags == kGray_SkColorTypeComponentFlag) && + GrColorTypeBytesPerPixel(read.fColorType) == 4) { + switch (read.fOffsetAlignmentForTransferBuffer & 0b11) { + // offset alignment already a multiple of 4 + case 0: + break; + // offset alignment is a multiple of 2 but not 4. + case 2: + read.fOffsetAlignmentForTransferBuffer *= 2; + // offset alignment is not a multiple of 2. + default: + read.fOffsetAlignmentForTransferBuffer *= 4; + } + } + return read; +} + +#ifdef SK_DEBUG +bool GrCaps::AreConfigsCompatible(GrPixelConfig genericConfig, GrPixelConfig specificConfig) { + bool compatible = false; + + switch (genericConfig) { + case kAlpha_8_GrPixelConfig: + compatible = kAlpha_8_GrPixelConfig == specificConfig || // here bc of the mock context + kAlpha_8_as_Alpha_GrPixelConfig == specificConfig || + kAlpha_8_as_Red_GrPixelConfig == specificConfig; + break; + case kGray_8_GrPixelConfig: + compatible = kGray_8_GrPixelConfig == specificConfig || // here bc of the mock context + kGray_8_as_Lum_GrPixelConfig == specificConfig || + kGray_8_as_Red_GrPixelConfig == specificConfig; + break; + case kAlpha_half_GrPixelConfig: + compatible = kAlpha_half_GrPixelConfig == specificConfig || // bc of the mock context + kAlpha_half_as_Red_GrPixelConfig == specificConfig || + kAlpha_half_as_Lum_GrPixelConfig == specificConfig; + break; + case kRGB_888_GrPixelConfig: + compatible = kRGB_888_GrPixelConfig == specificConfig || + kRGB_888X_GrPixelConfig == specificConfig; + break; + case kRGBA_8888_GrPixelConfig: + compatible = kRGBA_8888_GrPixelConfig == specificConfig || + kBGRA_8888_GrPixelConfig == specificConfig; + break; + default: + compatible = genericConfig == specificConfig; + break; + } + + if (!compatible) { + SkDebugf("Configs are not compatible: %d %d\n", genericConfig, specificConfig); + } + + return compatible; +} +#endif + +GrBackendFormat GrCaps::getDefaultBackendFormat(GrColorType grColorType, + GrRenderable renderable) const { + GrBackendFormat format = this->onGetDefaultBackendFormat(grColorType, renderable); + if (!this->isFormatTexturableAndUploadable(grColorType, format)) { + return {}; + } + + if (renderable == GrRenderable::kYes) { + if (!this->isFormatAsColorTypeRenderable(grColorType, format)) { + return {}; + } + } + + return format; +} + diff --git a/gfx/skia/skia/src/gpu/GrCaps.h b/gfx/skia/skia/src/gpu/GrCaps.h new file mode 100644 index 0000000000..27f0ec4847 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrCaps.h @@ -0,0 +1,553 @@ + +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrCaps_DEFINED +#define GrCaps_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkString.h" +#include "include/gpu/GrDriverBugWorkarounds.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrBlend.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrSurfaceProxy.h" + +class GrBackendFormat; +class GrBackendRenderTarget; +class GrBackendTexture; +struct GrContextOptions; +class GrRenderTargetProxy; +class GrSurface; +class SkJSONWriter; + +/** + * Represents the capabilities of a GrContext. + */ +class GrCaps : public SkRefCnt { +public: + GrCaps(const GrContextOptions&); + + void dumpJSON(SkJSONWriter*) const; + + const GrShaderCaps* shaderCaps() const { return fShaderCaps.get(); } + + bool npotTextureTileSupport() const { return fNPOTTextureTileSupport; } + /** To avoid as-yet-unnecessary complexity we don't allow any partial support of MIP Maps (e.g. + only for POT textures) */ + bool mipMapSupport() const { return fMipMapSupport; } + + bool gpuTracingSupport() const { return fGpuTracingSupport; } + bool oversizedStencilSupport() const { return fOversizedStencilSupport; } + bool textureBarrierSupport() const { return fTextureBarrierSupport; } + bool sampleLocationsSupport() const { return fSampleLocationsSupport; } + bool multisampleDisableSupport() const { return fMultisampleDisableSupport; } + bool instanceAttribSupport() const { return fInstanceAttribSupport; } + bool mixedSamplesSupport() const { return fMixedSamplesSupport; } + // This flag indicates that we never have to resolve MSAA. In practice, it means that we have + // an MSAA-render-to-texture extension: Any render target we create internally will use the + // extension, and any wrapped render target is the client's responsibility. + bool msaaResolvesAutomatically() const { return fMSAAResolvesAutomatically; } + bool halfFloatVertexAttributeSupport() const { return fHalfFloatVertexAttributeSupport; } + + // Primitive restart functionality is core in ES 3.0, but using it will cause slowdowns on some + // systems. This cap is only set if primitive restart will improve performance. + bool usePrimitiveRestart() const { return fUsePrimitiveRestart; } + + bool preferClientSideDynamicBuffers() const { return fPreferClientSideDynamicBuffers; } + + // On tilers, an initial fullscreen clear is an OPTIMIZATION. It allows the hardware to + // initialize each tile with a constant value rather than loading each pixel from memory. + bool preferFullscreenClears() const { return fPreferFullscreenClears; } + + // Should we discard stencil values after a render pass? (Tilers get better performance if we + // always load stencil buffers with a "clear" op, and then discard the content when finished.) + bool discardStencilValuesAfterRenderPass() const { + // This method is actually just a duplicate of preferFullscreenClears(), with a descriptive + // name for the sake of readability. + return this->preferFullscreenClears(); + } + + bool preferVRAMUseOverFlushes() const { return fPreferVRAMUseOverFlushes; } + + bool preferTrianglesOverSampleMask() const { return fPreferTrianglesOverSampleMask; } + + bool avoidStencilBuffers() const { return fAvoidStencilBuffers; } + + bool avoidWritePixelsFastPath() const { return fAvoidWritePixelsFastPath; } + + /** + * Indicates the capabilities of the fixed function blend unit. + */ + enum BlendEquationSupport { + kBasic_BlendEquationSupport, //= kAdvanced_BlendEquationSupport; + } + + bool advancedCoherentBlendEquationSupport() const { + return kAdvancedCoherent_BlendEquationSupport == fBlendEquationSupport; + } + + bool isAdvancedBlendEquationBlacklisted(GrBlendEquation equation) const { + SkASSERT(GrBlendEquationIsAdvanced(equation)); + SkASSERT(this->advancedBlendEquationSupport()); + return SkToBool(fAdvBlendEqBlacklist & (1 << equation)); + } + + /** + * Indicates whether GPU->CPU memory mapping for GPU resources such as vertex buffers and + * textures allows partial mappings or full mappings. + */ + enum MapFlags { + kNone_MapFlags = 0x0, //maxWindowRectangles() > 0 && this->onIsWindowRectanglesSupportedForRT(rt); + } + + virtual bool isFormatSRGB(const GrBackendFormat&) const = 0; + + // Callers can optionally pass in an SkImage::CompressionType which will be filled in with the + // correct type if the GrBackendFormat is compressed. + virtual bool isFormatCompressed(const GrBackendFormat&, + SkImage::CompressionType* compressionType = nullptr) const = 0; + + // TODO: Once we use the supportWritePixels call for uploads, we can remove this function and + // instead only have the version that takes a GrBackendFormat. + virtual bool isFormatTexturableAndUploadable(GrColorType, const GrBackendFormat&) const = 0; + // Can a texture be made with the GrBackendFormat, and then be bound and sampled in a shader. + virtual bool isFormatTexturable(const GrBackendFormat&) const = 0; + + // Returns whether a texture of the given format can be copied to a texture of the same format. + virtual bool isFormatCopyable(const GrBackendFormat&) const = 0; + + // Returns the maximum supported sample count for a format. 0 means the format is not renderable + // 1 means the format is renderable but doesn't support MSAA. This call only refers to the + // format itself. A caller should also confirm if the format is renderable with a given + // GrColorType by calling isFormatRenderable. + virtual int maxRenderTargetSampleCount(const GrBackendFormat&) const = 0; + + // Returns the number of samples to use when performing internal draws to the given config with + // MSAA or mixed samples. If 0, Ganesh should not attempt to use internal multisampling. + int internalMultisampleCount(const GrBackendFormat& format) const { + return SkTMin(fInternalMultisampleCount, this->maxRenderTargetSampleCount(format)); + } + + virtual bool isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount = 1) const = 0; + + virtual bool isFormatRenderable(const GrBackendFormat& format, int sampleCount) const = 0; + + // Find a sample count greater than or equal to the requested count which is supported for a + // render target of the given format or 0 if no such sample count is supported. If the requested + // sample count is 1 then 1 will be returned if non-MSAA rendering is supported, otherwise 0. + // For historical reasons requestedCount==0 is handled identically to requestedCount==1. + virtual int getRenderTargetSampleCount(int requestedCount, const GrBackendFormat&) const = 0; + + // Returns the number of bytes per pixel for the given GrBackendFormat. This is only supported + // for "normal" formats. For compressed formats this will return 0. + virtual size_t bytesPerPixel(const GrBackendFormat&) const = 0; + + /** + * Backends may have restrictions on what types of surfaces support GrGpu::writePixels(). + * If this returns false then the caller should implement a fallback where a temporary texture + * is created, pixels are written to it, and then that is copied or drawn into the the surface. + */ + bool surfaceSupportsWritePixels(const GrSurface*) const; + + /** + * Indicates whether surface supports GrGpu::readPixels, must be copied, or cannot be read. + */ + enum class SurfaceReadPixelsSupport { + /** GrGpu::readPixels is supported by the surface. */ + kSupported, + /** + * GrGpu::readPixels is not supported by this surface but this surface can be drawn + * or copied to a Ganesh-created GrTextureType::kTexture2D and then that surface will be + * readable. + */ + kCopyToTexture2D, + /** + * Not supported + */ + kUnsupported, + }; + /** + * Backends may have restrictions on what types of surfaces support GrGpu::readPixels(). We may + * either be able to read directly from the surface, read from a copy of the surface, or not + * read at all. + */ + virtual SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const = 0; + + struct SupportedWrite { + GrColorType fColorType; + // If the write is occurring using GrGpu::transferPixelsTo then this provides the + // minimum alignment of the offset into the transfer buffer. + size_t fOffsetAlignmentForTransferBuffer; + }; + + /** + * Given a dst pixel config and a src color type what color type must the caller coax the + * the data into in order to use GrGpu::writePixels(). + */ + virtual SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const = 0; + + struct SupportedRead { + GrColorType fColorType; + // If the read is occurring using GrGpu::transferPixelsFrom then this provides the + // minimum alignment of the offset into the transfer buffer. + size_t fOffsetAlignmentForTransferBuffer; + }; + + /** + * Given a src surface's color type and its backend format as well as a color type the caller + * would like read into, this provides a legal color type that the caller may pass to + * GrGpu::readPixels(). The returned color type may differ from the passed dstColorType, in + * which case the caller must convert the read pixel data (see GrConvertPixels). When converting + * to dstColorType the swizzle in the returned struct should be applied. The caller must check + * the returned color type for kUnknown. + */ + SupportedRead supportedReadPixelsColorType(GrColorType srcColorType, + const GrBackendFormat& srcFormat, + GrColorType dstColorType) const; + + /** + * Do GrGpu::writePixels() and GrGpu::transferPixelsTo() support a src buffer where the row + * bytes is not equal to bpp * w? + */ + bool writePixelsRowBytesSupport() const { return fWritePixelsRowBytesSupport; } + /** + * Does GrGpu::readPixels() support a dst buffer where the row bytes is not equal to bpp * w? + */ + bool readPixelsRowBytesSupport() const { return fReadPixelsRowBytesSupport; } + + /** Are transfer buffers (to textures and from surfaces) supported? */ + bool transferBufferSupport() const { return fTransferBufferSupport; } + + bool suppressPrints() const { return fSuppressPrints; } + + size_t bufferMapThreshold() const { + SkASSERT(fBufferMapThreshold >= 0); + return fBufferMapThreshold; + } + + /** True in environments that will issue errors if memory uploaded to buffers + is not initialized (even if not read by draw calls). */ + bool mustClearUploadedBufferData() const { return fMustClearUploadedBufferData; } + + /** For some environments, there is a performance or safety concern to not + initializing textures. For example, with WebGL and Firefox, there is a large + performance hit to not doing it. + */ + bool shouldInitializeTextures() const { return fShouldInitializeTextures; } + + /** Returns true if the given backend supports importing AHardwareBuffers via the + * GrAHardwarebufferImageGenerator. This will only ever be supported on Android devices with API + * level >= 26. + * */ + bool supportsAHardwareBufferImages() const { return fSupportsAHardwareBufferImages; } + + bool wireframeMode() const { return fWireframeMode; } + + /** Supports using GrFence. */ + bool fenceSyncSupport() const { return fFenceSyncSupport; } + + /** Supports using GrSemaphore. */ + bool semaphoreSupport() const { return fSemaphoreSupport; } + + bool crossContextTextureSupport() const { return fCrossContextTextureSupport; } + /** + * Returns whether or not we will be able to do a copy given the passed in params + */ + bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const; + + bool dynamicStateArrayGeometryProcessorTextureSupport() const { + return fDynamicStateArrayGeometryProcessorTextureSupport; + } + + // Not all backends support clearing with a scissor test (e.g. Metal), this will always + // return true if performColorClearsAsDraws() returns true. + bool performPartialClearsAsDraws() const { + return fPerformColorClearsAsDraws || fPerformPartialClearsAsDraws; + } + + // Many drivers have issues with color clears. + bool performColorClearsAsDraws() const { return fPerformColorClearsAsDraws; } + + /// Adreno 4xx devices experience an issue when there are a large number of stencil clip bit + /// clears. The minimal repro steps are not precisely known but drawing a rect with a stencil + /// op instead of using glClear seems to resolve the issue. + bool performStencilClearsAsDraws() const { return fPerformStencilClearsAsDraws; } + + // Can we use coverage counting shortcuts to render paths? Coverage counting can cause artifacts + // along shared edges if care isn't taken to ensure both contours wind in the same direction. + bool allowCoverageCounting() const { return fAllowCoverageCounting; } + + // Should we disable the CCPR code due to a faulty driver? + bool driverBlacklistCCPR() const { return fDriverBlacklistCCPR; } + bool driverBlacklistMSAACCPR() const { return fDriverBlacklistMSAACCPR; } + + /** + * This is used to try to ensure a successful copy a dst in order to perform shader-based + * blending. + * + * fRectsMustMatch will be set to true if the copy operation must ensure that the src and dest + * rects are identical. + * + * fMustCopyWholeSrc will be set to true if copy rect must equal src's bounds. + * + * Caller will detect cases when copy cannot succeed and try copy-as-draw as a fallback. + */ + struct DstCopyRestrictions { + GrSurfaceProxy::RectsMustMatch fRectsMustMatch = GrSurfaceProxy::RectsMustMatch::kNo; + bool fMustCopyWholeSrc = false; + }; + virtual DstCopyRestrictions getDstCopyRestrictions(const GrRenderTargetProxy* src, + GrColorType ct) const { + return {}; + } + + bool validateSurfaceParams(const SkISize&, const GrBackendFormat&, GrPixelConfig, + GrRenderable renderable, int renderTargetSampleCnt, + GrMipMapped) const; + + bool areColorTypeAndFormatCompatible(GrColorType grCT, + const GrBackendFormat& format) const { + if (GrColorType::kUnknown == grCT) { + return false; + } + + return this->onAreColorTypeAndFormatCompatible(grCT, format); + } + + // TODO: it seems like we could pass the full SkImageInfo and validate its colorSpace too + // Returns kUnknown if a valid config could not be determined. + GrPixelConfig getConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType grCT) const { + if (GrColorType::kUnknown == grCT) { + return kUnknown_GrPixelConfig; + } + + return this->onGetConfigFromBackendFormat(format, grCT); + } + + /** + * Special method only for YUVA images. Returns a colortype that matches the backend format or + * kUnknown if a colortype could not be determined. + */ + virtual GrColorType getYUVAColorTypeFromBackendFormat(const GrBackendFormat&, + bool isAlphaChannel) const = 0; + + /** These are used when creating a new texture internally. */ + GrBackendFormat getDefaultBackendFormat(GrColorType, GrRenderable) const; + + virtual GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const = 0; + + /** + * The CLAMP_TO_BORDER wrap mode for texture coordinates was added to desktop GL in 1.3, and + * GLES 3.2, but is also available in extensions. Vulkan and Metal always have support. + */ + bool clampToBorderSupport() const { return fClampToBorderSupport; } + + /** + * Returns the GrSwizzle to use when sampling from a texture with the passed in GrBackendFormat + * and GrColorType. + */ + virtual GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const = 0; + + /** + * Returns the GrSwizzle to use when outputting to a render target with the passed in + * GrBackendFormat and GrColorType. + */ + virtual GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const = 0; + + const GrDriverBugWorkarounds& workarounds() const { return fDriverBugWorkarounds; } + + /** + * Given a possibly generic GrPixelConfig and a backend format return a specific + * GrPixelConfig. + */ + GrPixelConfig makeConfigSpecific(GrPixelConfig config, const GrBackendFormat& format) const { + auto ct = GrPixelConfigToColorType(config); + auto result = this->getConfigFromBackendFormat(format, ct); + SkASSERT(config == result || AreConfigsCompatible(config, result)); + return result; + } + +#ifdef SK_DEBUG + // This is just a debugging entry point until we're weaned off of GrPixelConfig. It + // should be used to verify that the pixel config from user-level code (the genericConfig) + // is compatible with a pixel config we've computed from scratch (the specificConfig). + static bool AreConfigsCompatible(GrPixelConfig genericConfig, GrPixelConfig specificConfig); +#endif + +#if GR_TEST_UTILS + struct TestFormatColorTypeCombination { + GrColorType fColorType; + GrBackendFormat fFormat; + }; + + virtual std::vector getTestingCombinations() const = 0; +#endif + +protected: + /** Subclasses must call this at the end of their constructors in order to apply caps + overrides requested by the client. Note that overrides will only reduce the caps never + expand them. */ + void applyOptionsOverrides(const GrContextOptions& options); + + sk_sp fShaderCaps; + + bool fNPOTTextureTileSupport : 1; + bool fMipMapSupport : 1; + bool fReuseScratchTextures : 1; + bool fReuseScratchBuffers : 1; + bool fGpuTracingSupport : 1; + bool fOversizedStencilSupport : 1; + bool fTextureBarrierSupport : 1; + bool fSampleLocationsSupport : 1; + bool fMultisampleDisableSupport : 1; + bool fInstanceAttribSupport : 1; + bool fMixedSamplesSupport : 1; + bool fMSAAResolvesAutomatically : 1; + bool fUsePrimitiveRestart : 1; + bool fPreferClientSideDynamicBuffers : 1; + bool fPreferFullscreenClears : 1; + bool fMustClearUploadedBufferData : 1; + bool fShouldInitializeTextures : 1; + bool fSupportsAHardwareBufferImages : 1; + bool fHalfFloatVertexAttributeSupport : 1; + bool fClampToBorderSupport : 1; + bool fPerformPartialClearsAsDraws : 1; + bool fPerformColorClearsAsDraws : 1; + bool fPerformStencilClearsAsDraws : 1; + bool fAllowCoverageCounting : 1; + bool fTransferBufferSupport : 1; + bool fWritePixelsRowBytesSupport : 1; + bool fReadPixelsRowBytesSupport : 1; + + // Driver workaround + bool fDriverBlacklistCCPR : 1; + bool fDriverBlacklistMSAACCPR : 1; + bool fAvoidStencilBuffers : 1; + bool fAvoidWritePixelsFastPath : 1; + + // ANGLE performance workaround + bool fPreferVRAMUseOverFlushes : 1; + + // On some platforms it's better to make more triangles than to use the sample mask (MSAA only). + bool fPreferTrianglesOverSampleMask : 1; + + bool fFenceSyncSupport : 1; + bool fSemaphoreSupport : 1; + + // Requires fence sync support in GL. + bool fCrossContextTextureSupport : 1; + + // Not (yet) implemented in VK backend. + bool fDynamicStateArrayGeometryProcessorTextureSupport : 1; + + BlendEquationSupport fBlendEquationSupport; + uint32_t fAdvBlendEqBlacklist; + GR_STATIC_ASSERT(kLast_GrBlendEquation < 32); + + uint32_t fMapBufferFlags; + int fBufferMapThreshold; + + int fMaxRenderTargetSize; + int fMaxPreferredRenderTargetSize; + int fMaxVertexAttributes; + int fMaxTextureSize; + int fMaxTileSize; + int fMaxWindowRectangles; + int fInternalMultisampleCount; + + GrDriverBugWorkarounds fDriverBugWorkarounds; + +private: + virtual void onApplyOptionsOverrides(const GrContextOptions&) {} + virtual void onDumpJSON(SkJSONWriter*) const {} + virtual bool onSurfaceSupportsWritePixels(const GrSurface*) const = 0; + virtual bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const = 0; + virtual GrBackendFormat onGetDefaultBackendFormat(GrColorType, GrRenderable) const = 0; + + // Backends should implement this if they have any extra requirements for use of window + // rectangles for a specific GrBackendRenderTarget outside of basic support. + virtual bool onIsWindowRectanglesSupportedForRT(const GrBackendRenderTarget&) const { + return true; + } + + virtual GrPixelConfig onGetConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType ct) const = 0; + + virtual bool onAreColorTypeAndFormatCompatible(GrColorType, const GrBackendFormat&) const = 0; + + virtual SupportedRead onSupportedReadPixelsColorType(GrColorType srcColorType, + const GrBackendFormat& srcFormat, + GrColorType dstColorType) const = 0; + + + bool fSuppressPrints : 1; + bool fWireframeMode : 1; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrClientMappedBufferManager.cpp b/gfx/skia/skia/src/gpu/GrClientMappedBufferManager.cpp new file mode 100644 index 0000000000..b927726c6c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrClientMappedBufferManager.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrClientMappedBufferManager.h" + +#include + +GrClientMappedBufferManager::GrClientMappedBufferManager(uint32_t contextID) + : fFinishedBufferInbox(contextID) {} + +GrClientMappedBufferManager::~GrClientMappedBufferManager() { + this->process(); + if (!fAbandoned) { + // If we're going down before we got the messages we go ahead and unmap all the buffers. + // It's up to the client to ensure that they aren't being accessed on another thread while + // this is happening (or afterwards on any thread). + for (auto& b : fClientHeldBuffers) { + b->unmap(); + } + } +} + +void GrClientMappedBufferManager::insert(sk_sp b) { + SkDEBUGCODE(auto end = fClientHeldBuffers.end()); + SkASSERT(std::find(fClientHeldBuffers.begin(), end, b) == end); + fClientHeldBuffers.emplace_front(std::move(b)); +} + +void GrClientMappedBufferManager::process() { + SkSTArray<4, BufferFinishedMessage> messages; + fFinishedBufferInbox.poll(&messages); + if (!fAbandoned) { + for (auto& m : messages) { + this->remove(m.fBuffer); + m.fBuffer->unmap(); + } + } +} + +void GrClientMappedBufferManager::abandon() { + fAbandoned = true; + fClientHeldBuffers.clear(); +} + +void GrClientMappedBufferManager::remove(const sk_sp& b) { + // There is no convenient remove only the first element that equals a value functionality in + // std::forward_list. + auto prev = fClientHeldBuffers.before_begin(); + auto end = fClientHeldBuffers.end(); + SkASSERT(std::find(fClientHeldBuffers.begin(), end, b) != end); + for (auto cur = fClientHeldBuffers.begin(); cur != end; prev = cur++) { + if (*cur == b) { + fClientHeldBuffers.erase_after(prev); + break; + } + } + SkASSERT(std::find(fClientHeldBuffers.begin(), end, b) == end); +} + +////////////////////////////////////////////////////////////////////////////// + +DECLARE_SKMESSAGEBUS_MESSAGE(GrClientMappedBufferManager::BufferFinishedMessage) + +bool SkShouldPostMessageToBus(const GrClientMappedBufferManager::BufferFinishedMessage& m, + uint32_t msgBusUniqueID) { + return m.fInboxID == msgBusUniqueID; +} diff --git a/gfx/skia/skia/src/gpu/GrClientMappedBufferManager.h b/gfx/skia/skia/src/gpu/GrClientMappedBufferManager.h new file mode 100644 index 0000000000..3f3b0678d9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrClientMappedBufferManager.h @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrClientMappedBufferManager_DEFINED +#define GrClientMappedBufferManager_DEFINED + +#include "include/private/SkTArray.h" +#include "src/core/SkMessageBus.h" +#include "src/gpu/GrGpuBuffer.h" +#include + +/** + * We sometimes hand clients objects that contain mapped GrGpuBuffers. The client may consume + * the mapped buffer on another thread. This object manages receiving messages that buffers are + * ready to be unmapped (on the direct GrContext's thread). It also handles cleaning up mapped + * buffers if the GrContext is destroyed before the client has finished with the buffer. + * + * Buffers are first registered using insert() before being passed the client. process() should be + * called periodically on the direct GrContext thread to poll for messages and process them. + */ +class GrClientMappedBufferManager final { +public: + /** + * The message type that internal users of this should post to unmap the buffer. + * Set fInboxID to inboxID(). fBuffer must have been previously passed to insert(). + */ + struct BufferFinishedMessage { + sk_sp fBuffer; + uint32_t fInboxID; + }; + using BufferFinishedMessageBus = SkMessageBus; + + GrClientMappedBufferManager(uint32_t contextID); + GrClientMappedBufferManager(const GrClientMappedBufferManager&) = delete; + GrClientMappedBufferManager(GrClientMappedBufferManager&&) = delete; + + ~GrClientMappedBufferManager(); + + GrClientMappedBufferManager& operator=(const GrClientMappedBufferManager&) = delete; + GrClientMappedBufferManager& operator=(GrClientMappedBufferManager&&) = delete; + + /** Initialize BufferFinishedMessage::fInboxID to this value. */ + uint32_t inboxID() const { return fFinishedBufferInbox.uniqueID(); } + + /** + * Let the manager know to expect a message with buffer 'b'. It's illegal for a buffer to be + * inserted again before it is unmapped by process(). + */ + void insert(sk_sp b); + + /** Poll for messages and unmap any incoming buffers. */ + void process(); + + /** Notifies the manager that the context has been abandoned. No more unmaps() will occur.*/ + void abandon(); + +private: + BufferFinishedMessageBus::Inbox fFinishedBufferInbox; + std::forward_list> fClientHeldBuffers; + bool fAbandoned = false; + + void remove(const sk_sp& b); +}; + +bool SkShouldPostMessageToBus(const GrClientMappedBufferManager::BufferFinishedMessage&, + uint32_t msgBusUniqueID); + +#endif diff --git a/gfx/skia/skia/src/gpu/GrClip.h b/gfx/skia/skia/src/gpu/GrClip.h new file mode 100644 index 0000000000..cc73c641fd --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrClip.h @@ -0,0 +1,174 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrClip_DEFINED +#define GrClip_DEFINED + +#include "include/core/SkRRect.h" +#include "include/core/SkRect.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrRenderTargetContext.h" + +class GrContext; + +/** + * GrClip is an abstract base class for applying a clip. It constructs a clip mask if necessary, and + * fills out a GrAppliedClip instructing the caller on how to set up the draw state. + */ +class GrClip { +public: + virtual bool quickContains(const SkRect&) const = 0; + virtual bool quickContains(const SkRRect& rrect) const { + return this->quickContains(rrect.getBounds()); + } + virtual void getConservativeBounds(int width, int height, SkIRect* devResult, + bool* isIntersectionOfRects = nullptr) const = 0; + /** + * This computes a GrAppliedClip from the clip which in turn can be used to build a GrPipeline. + * To determine the appropriate clipping implementation the GrClip subclass must know whether + * the draw will enable HW AA or uses the stencil buffer. On input 'bounds' is a conservative + * bounds of the draw that is to be clipped. After return 'bounds' has been intersected with a + * conservative bounds of the clip. A return value of false indicates that the draw can be + * skipped as it is fully clipped out. + */ + virtual bool apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA, + bool hasUserStencilSettings, GrAppliedClip*, SkRect* bounds) const = 0; + + virtual ~GrClip() {} + + /** + * This method quickly and conservatively determines whether the entire clip is equivalent to + * intersection with a rrect. This will only return true if the rrect does not fully contain + * the render target bounds. Moreover, the returned rrect need not be contained by the render + * target bounds. We assume all draws will be implicitly clipped by the render target bounds. + * + * @param rtBounds The bounds of the render target that the clip will be applied to. + * @param rrect If return is true rrect will contain the rrect equivalent to the clip within + * rtBounds. + * @param aa If return is true aa will indicate whether the rrect clip is antialiased. + * @return true if the clip is equivalent to a single rrect, false otherwise. + * + */ + virtual bool isRRect(const SkRect& rtBounds, SkRRect* rrect, GrAA* aa) const = 0; + + /** + * This is the maximum distance that a draw may extend beyond a clip's boundary and still count + * count as "on the other side". We leave some slack because floating point rounding error is + * likely to blame. The rationale for 1e-3 is that in the coverage case (and barring unexpected + * rounding), as long as coverage stays within 0.5 * 1/256 of its intended value it shouldn't + * have any effect on the final pixel values. + */ + constexpr static SkScalar kBoundsTolerance = 1e-3f; + + /** + * Returns true if the given query bounds count as entirely inside the clip. + * + * @param innerClipBounds device-space rect contained by the clip (SkRect or SkIRect). + * @param queryBounds device-space bounds of the query region. + */ + template + constexpr static bool IsInsideClip(const TRect& innerClipBounds, const SkRect& queryBounds) { + return innerClipBounds.fRight > innerClipBounds.fLeft + kBoundsTolerance && + innerClipBounds.fBottom > innerClipBounds.fTop + kBoundsTolerance && + innerClipBounds.fLeft < queryBounds.fLeft + kBoundsTolerance && + innerClipBounds.fTop < queryBounds.fTop + kBoundsTolerance && + innerClipBounds.fRight > queryBounds.fRight - kBoundsTolerance && + innerClipBounds.fBottom > queryBounds.fBottom - kBoundsTolerance; + } + + /** + * Returns true if the given query bounds count as entirely outside the clip. + * + * @param outerClipBounds device-space rect that contains the clip (SkRect or SkIRect). + * @param queryBounds device-space bounds of the query region. + */ + template + constexpr static bool IsOutsideClip(const TRect& outerClipBounds, const SkRect& queryBounds) { + return + // Is the clip so small that it is effectively empty? + outerClipBounds.fRight - outerClipBounds.fLeft <= kBoundsTolerance || + outerClipBounds.fBottom - outerClipBounds.fTop <= kBoundsTolerance || + + // Are the query bounds effectively outside the clip? + outerClipBounds.fLeft >= queryBounds.fRight - kBoundsTolerance || + outerClipBounds.fTop >= queryBounds.fBottom - kBoundsTolerance || + outerClipBounds.fRight <= queryBounds.fLeft + kBoundsTolerance || + outerClipBounds.fBottom <= queryBounds.fTop + kBoundsTolerance; + } + + /** + * Returns the minimal integer rect that counts as containing a given set of bounds. + */ + static SkIRect GetPixelIBounds(const SkRect& bounds) { + return SkIRect::MakeLTRB(SkScalarFloorToInt(bounds.fLeft + kBoundsTolerance), + SkScalarFloorToInt(bounds.fTop + kBoundsTolerance), + SkScalarCeilToInt(bounds.fRight - kBoundsTolerance), + SkScalarCeilToInt(bounds.fBottom - kBoundsTolerance)); + } + + /** + * Returns the minimal pixel-aligned rect that counts as containing a given set of bounds. + */ + static SkRect GetPixelBounds(const SkRect& bounds) { + return SkRect::MakeLTRB(SkScalarFloorToScalar(bounds.fLeft + kBoundsTolerance), + SkScalarFloorToScalar(bounds.fTop + kBoundsTolerance), + SkScalarCeilToScalar(bounds.fRight - kBoundsTolerance), + SkScalarCeilToScalar(bounds.fBottom - kBoundsTolerance)); + } + + /** + * Returns true if the given rect counts as aligned with pixel boundaries. + */ + static bool IsPixelAligned(const SkRect& rect) { + return SkScalarAbs(SkScalarRoundToScalar(rect.fLeft) - rect.fLeft) <= kBoundsTolerance && + SkScalarAbs(SkScalarRoundToScalar(rect.fTop) - rect.fTop) <= kBoundsTolerance && + SkScalarAbs(SkScalarRoundToScalar(rect.fRight) - rect.fRight) <= kBoundsTolerance && + SkScalarAbs(SkScalarRoundToScalar(rect.fBottom) - rect.fBottom) <= kBoundsTolerance; + } +}; + + +/** + * GrHardClip never uses coverage FPs. It can only enforce the clip using the already-existing + * stencil buffer contents and/or fixed-function state like scissor. Always aliased if MSAA is off. + */ +class GrHardClip : public GrClip { +public: + /** + * Sets the appropriate hardware state modifications on GrAppliedHardClip that will implement + * the clip. On input 'bounds' is a conservative bounds of the draw that is to be clipped. After + * return 'bounds' has been intersected with a conservative bounds of the clip. A return value + * of false indicates that the draw can be skipped as it is fully clipped out. + */ + virtual bool apply(int rtWidth, int rtHeight, GrAppliedHardClip* out, SkRect* bounds) const = 0; + +private: + bool apply(GrRecordingContext*, GrRenderTargetContext* rtc, bool useHWAA, + bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final { + return this->apply(rtc->width(), rtc->height(), &out->hardClip(), bounds); + } +}; + +/** + * Specialized implementation for no clip. + */ +class GrNoClip final : public GrHardClip { +private: + bool quickContains(const SkRect&) const final { return true; } + bool quickContains(const SkRRect&) const final { return true; } + void getConservativeBounds(int width, int height, SkIRect* devResult, + bool* isIntersectionOfRects) const final { + devResult->setXYWH(0, 0, width, height); + if (isIntersectionOfRects) { + *isIntersectionOfRects = true; + } + } + bool apply(int rtWidth, int rtHeight, GrAppliedHardClip*, SkRect*) const final { return true; } + bool isRRect(const SkRect&, SkRRect*, GrAA*) const override { return false; } +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrClipStackClip.cpp b/gfx/skia/skia/src/gpu/GrClipStackClip.cpp new file mode 100644 index 0000000000..2db564fcad --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrClipStackClip.cpp @@ -0,0 +1,544 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkTo.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTaskGroup.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrClipStackClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDeferredProxyUploader.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrSWMaskHelper.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/GrConvexPolyEffect.h" +#include "src/gpu/effects/GrRRectEffect.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/geometry/GrShape.h" + +typedef SkClipStack::Element Element; +typedef GrReducedClip::InitialState InitialState; +typedef GrReducedClip::ElementList ElementList; + +const char GrClipStackClip::kMaskTestTag[] = "clip_mask"; + +bool GrClipStackClip::quickContains(const SkRect& rect) const { + if (!fStack || fStack->isWideOpen()) { + return true; + } + return fStack->quickContains(rect); +} + +bool GrClipStackClip::quickContains(const SkRRect& rrect) const { + if (!fStack || fStack->isWideOpen()) { + return true; + } + return fStack->quickContains(rrect); +} + +bool GrClipStackClip::isRRect(const SkRect& origRTBounds, SkRRect* rr, GrAA* aa) const { + if (!fStack) { + return false; + } + const SkRect* rtBounds = &origRTBounds; + bool isAA; + if (fStack->isRRect(*rtBounds, rr, &isAA)) { + *aa = GrAA(isAA); + return true; + } + return false; +} + +void GrClipStackClip::getConservativeBounds(int width, int height, SkIRect* devResult, + bool* isIntersectionOfRects) const { + if (!fStack) { + devResult->setXYWH(0, 0, width, height); + if (isIntersectionOfRects) { + *isIntersectionOfRects = true; + } + return; + } + SkRect devBounds; + fStack->getConservativeBounds(0, 0, width, height, &devBounds, isIntersectionOfRects); + devBounds.roundOut(devResult); +} + +//////////////////////////////////////////////////////////////////////////////// +// set up the draw state to enable the aa clipping mask. +static std::unique_ptr create_fp_for_mask(sk_sp mask, + const SkIRect& devBound) { + SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height()); + return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(mask), domainTexels, + {devBound.fLeft, devBound.fTop}); +} + +// Does the path in 'element' require SW rendering? If so, return true (and, +// optionally, set 'prOut' to NULL. If not, return false (and, optionally, set +// 'prOut' to the non-SW path renderer that will do the job). +bool GrClipStackClip::PathNeedsSWRenderer(GrRecordingContext* context, + const SkIRect& scissorRect, + bool hasUserStencilSettings, + const GrRenderTargetContext* renderTargetContext, + const SkMatrix& viewMatrix, + const Element* element, + GrPathRenderer** prOut, + bool needsStencil) { + if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) { + // rects can always be drawn directly w/o using the software path + // TODO: skip rrects once we're drawing them directly. + if (prOut) { + *prOut = nullptr; + } + return false; + } else { + // We shouldn't get here with an empty clip element. + SkASSERT(Element::DeviceSpaceType::kEmpty != element->getDeviceSpaceType()); + + // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer + SkPath path; + element->asDeviceSpacePath(&path); + if (path.isInverseFillType()) { + path.toggleInverseFillType(); + } + + // We only use this method when rendering coverage clip masks. + SkASSERT(renderTargetContext->numSamples() <= 1); + auto aaType = (element->isAA()) ? GrAAType::kCoverage : GrAAType::kNone; + + GrPathRendererChain::DrawType type = + needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor + : GrPathRendererChain::DrawType::kColor; + + GrShape shape(path, GrStyle::SimpleFill()); + GrPathRenderer::CanDrawPathArgs canDrawArgs; + canDrawArgs.fCaps = context->priv().caps(); + canDrawArgs.fProxy = renderTargetContext->proxy(); + canDrawArgs.fClipConservativeBounds = &scissorRect; + canDrawArgs.fViewMatrix = &viewMatrix; + canDrawArgs.fShape = &shape; + canDrawArgs.fAAType = aaType; + SkASSERT(!renderTargetContext->wrapsVkSecondaryCB()); + canDrawArgs.fTargetIsWrappedVkSecondaryCB = false; + canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings; + + // the 'false' parameter disallows use of the SW path renderer + GrPathRenderer* pr = + context->priv().drawingManager()->getPathRenderer(canDrawArgs, false, type); + if (prOut) { + *prOut = pr; + } + return SkToBool(!pr); + } +} + +/* + * This method traverses the clip stack to see if the GrSoftwarePathRenderer + * will be used on any element. If so, it returns true to indicate that the + * entire clip should be rendered in SW and then uploaded en masse to the gpu. + */ +bool GrClipStackClip::UseSWOnlyPath(GrRecordingContext* context, + bool hasUserStencilSettings, + const GrRenderTargetContext* renderTargetContext, + const GrReducedClip& reducedClip) { + // TODO: right now it appears that GPU clip masks are strictly slower than software. We may + // want to revisit this assumption once we can test with render target sorting. + return true; + + // TODO: generalize this function so that when + // a clip gets complex enough it can just be done in SW regardless + // of whether it would invoke the GrSoftwarePathRenderer. + + // If we're avoiding stencils, always use SW. This includes drawing into a wrapped vulkan + // secondary command buffer which can't handle stencils. + if (context->priv().caps()->avoidStencilBuffers() || + renderTargetContext->wrapsVkSecondaryCB()) { + return true; + } + + // Set the matrix so that rendered clip elements are transformed to mask space from clip + // space. + SkMatrix translate; + translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top())); + + for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) { + const Element* element = iter.get(); + + SkClipOp op = element->getOp(); + bool invert = element->isInverseFilled(); + bool needsStencil = invert || + kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op; + + if (PathNeedsSWRenderer(context, reducedClip.scissor(), hasUserStencilSettings, + renderTargetContext, translate, element, nullptr, needsStencil)) { + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// sort out what kind of clip mask needs to be created: alpha, stencil, +// scissor, or entirely software +bool GrClipStackClip::apply(GrRecordingContext* context, GrRenderTargetContext* renderTargetContext, + bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out, + SkRect* bounds) const { + SkRect devBounds = SkRect::MakeIWH(renderTargetContext->width(), renderTargetContext->height()); + if (!devBounds.intersect(*bounds)) { + return false; + } + + if (!fStack || fStack->isWideOpen()) { + return true; + } + + // An default count of 4 was chosen because of the common pattern in Blink of: + // isect RR + // diff RR + // isect convex_poly + // isect convex_poly + // when drawing rounded div borders. + constexpr int kMaxAnalyticFPs = 4; + + int maxWindowRectangles = renderTargetContext->priv().maxWindowRectangles(); + int maxAnalyticFPs = kMaxAnalyticFPs; + if (renderTargetContext->numSamples() > 1 || useHWAA || hasUserStencilSettings) { + // Disable analytic clips when we have MSAA. In MSAA we never conflate coverage and opacity. + maxAnalyticFPs = 0; + // We disable MSAA when avoiding stencil. + SkASSERT(!context->priv().caps()->avoidStencilBuffers()); + } + auto* ccpr = context->priv().drawingManager()->getCoverageCountingPathRenderer(); + + GrReducedClip reducedClip(*fStack, devBounds, context->priv().caps(), + maxWindowRectangles, maxAnalyticFPs, ccpr ? maxAnalyticFPs : 0); + if (InitialState::kAllOut == reducedClip.initialState() && + reducedClip.maskElements().isEmpty()) { + return false; + } + + if (reducedClip.hasScissor() && !GrClip::IsInsideClip(reducedClip.scissor(), devBounds)) { + out->hardClip().addScissor(reducedClip.scissor(), bounds); + } + + if (!reducedClip.windowRectangles().empty()) { + out->hardClip().addWindowRectangles(reducedClip.windowRectangles(), + GrWindowRectsState::Mode::kExclusive); + } + + if (!reducedClip.maskElements().isEmpty()) { + if (!this->applyClipMask(context, renderTargetContext, reducedClip, hasUserStencilSettings, + out)) { + return false; + } + } + + // The opsTask ID must not be looked up until AFTER producing the clip mask (if any). That step + // can cause a flush or otherwise change which opstask our draw is going into. + uint32_t opsTaskID = renderTargetContext->getOpsTask()->uniqueID(); + if (auto clipFPs = reducedClip.finishAndDetachAnalyticFPs(ccpr, opsTaskID)) { + out->addCoverageFP(std::move(clipFPs)); + } + + return true; +} + +bool GrClipStackClip::applyClipMask(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext, + const GrReducedClip& reducedClip, bool hasUserStencilSettings, + GrAppliedClip* out) const { +#ifdef SK_DEBUG + SkASSERT(reducedClip.hasScissor()); + SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->width(), + renderTargetContext->height()); + const SkIRect& scissor = reducedClip.scissor(); + SkASSERT(rtIBounds.contains(scissor)); // Mask shouldn't be larger than the RT. +#endif + + // MIXED SAMPLES TODO: We may want to explore using the stencil buffer for AA clipping. + if ((renderTargetContext->numSamples() <= 1 && reducedClip.maskRequiresAA()) || + context->priv().caps()->avoidStencilBuffers() || + renderTargetContext->wrapsVkSecondaryCB()) { + sk_sp result; + if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) { + // The clip geometry is complex enough that it will be more efficient to create it + // entirely in software + result = this->createSoftwareClipMask(context, reducedClip, renderTargetContext); + } else { + result = this->createAlphaClipMask(context, reducedClip); + } + + if (result) { + // The mask's top left coord should be pinned to the rounded-out top left corner of + // the clip's device space bounds. + out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.scissor())); + return true; + } + + // If alpha or software clip mask creation fails, fall through to the stencil code paths, + // unless stencils are disallowed. + if (context->priv().caps()->avoidStencilBuffers() || + renderTargetContext->wrapsVkSecondaryCB()) { + SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. " + "Clip will be ignored.\n"); + return false; + } + } + + // This relies on the property that a reduced sub-rect of the last clip will contain all the + // relevant window rectangles that were in the last clip. This subtle requirement will go away + // after clipping is overhauled. + if (renderTargetContext->priv().mustRenderClip(reducedClip.maskGenID(), reducedClip.scissor(), + reducedClip.numAnalyticFPs())) { + reducedClip.drawStencilClipMask(context, renderTargetContext); + renderTargetContext->priv().setLastClip(reducedClip.maskGenID(), reducedClip.scissor(), + reducedClip.numAnalyticFPs()); + } + // GrAppliedClip doesn't need to figure numAnalyticFPs into its key (used by operator==) because + // it verifies the FPs are also equal. + out->hardClip().addStencilClip(reducedClip.maskGenID()); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Create a 8-bit clip mask in alpha + +static void create_clip_mask_key(uint32_t clipGenID, const SkIRect& bounds, int numAnalyticFPs, + GrUniqueKey* key) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(key, kDomain, 4, GrClipStackClip::kMaskTestTag); + builder[0] = clipGenID; + // SkToS16 because image filters outset layers to a size indicated by the filter, which can + // sometimes result in negative coordinates from device space. + builder[1] = SkToS16(bounds.fLeft) | (SkToS16(bounds.fRight) << 16); + builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16); + builder[3] = numAnalyticFPs; +} + +static void add_invalidate_on_pop_message(GrRecordingContext* context, + const SkClipStack& stack, uint32_t clipGenID, + const GrUniqueKey& clipMaskKey) { + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); + while (const Element* element = iter.prev()) { + if (element->getGenID() == clipGenID) { + element->addResourceInvalidationMessage(proxyProvider, clipMaskKey); + return; + } + } + SkDEBUGFAIL("Gen ID was not found in stack."); +} + +sk_sp GrClipStackClip::createAlphaClipMask(GrRecordingContext* context, + const GrReducedClip& reducedClip) const { + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + GrUniqueKey key; + create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(), + reducedClip.numAnalyticFPs(), &key); + + sk_sp proxy(proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin)); + if (proxy) { + return proxy; + } + + auto isProtected = proxy->isProtected() ? GrProtected::kYes : GrProtected::kNo; + auto rtc = context->priv().makeDeferredRenderTargetContextWithFallback(SkBackingFit::kApprox, + reducedClip.width(), + reducedClip.height(), + GrColorType::kAlpha_8, + nullptr, + 1, + GrMipMapped::kNo, + kTopLeft_GrSurfaceOrigin, + nullptr, + SkBudgeted::kYes, + isProtected); + if (!rtc) { + return nullptr; + } + + if (!reducedClip.drawAlphaClipMask(rtc.get())) { + return nullptr; + } + + sk_sp result(rtc->asTextureProxyRef()); + if (!result) { + return nullptr; + } + + SkASSERT(result->origin() == kTopLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, result.get()); + add_invalidate_on_pop_message(context, *fStack, reducedClip.maskGenID(), key); + + return result; +} + +namespace { + +/** + * Payload class for use with GrTDeferredProxyUploader. The clip mask code renders multiple + * elements, each storing their own AA setting (and already transformed into device space). This + * stores all of the information needed by the worker thread to draw all clip elements (see below, + * in createSoftwareClipMask). + */ +class ClipMaskData { +public: + ClipMaskData(const GrReducedClip& reducedClip) + : fScissor(reducedClip.scissor()) + , fInitialState(reducedClip.initialState()) { + for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) { + fElements.addToTail(*iter.get()); + } + } + + const SkIRect& scissor() const { return fScissor; } + InitialState initialState() const { return fInitialState; } + const ElementList& elements() const { return fElements; } + +private: + SkIRect fScissor; + InitialState fInitialState; + ElementList fElements; +}; + +} + +static void draw_clip_elements_to_mask_helper(GrSWMaskHelper& helper, const ElementList& elements, + const SkIRect& scissor, InitialState initialState) { + // Set the matrix so that rendered clip elements are transformed to mask space from clip space. + SkMatrix translate; + translate.setTranslate(SkIntToScalar(-scissor.left()), SkIntToScalar(-scissor.top())); + + helper.clear(InitialState::kAllIn == initialState ? 0xFF : 0x00); + + for (ElementList::Iter iter(elements); iter.get(); iter.next()) { + const Element* element = iter.get(); + SkClipOp op = element->getOp(); + GrAA aa = GrAA(element->isAA()); + + if (kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op) { + // Intersect and reverse difference require modifying pixels outside of the geometry + // that is being "drawn". In both cases we erase all the pixels outside of the geometry + // but leave the pixels inside the geometry alone. For reverse difference we invert all + // the pixels before clearing the ones outside the geometry. + if (kReverseDifference_SkClipOp == op) { + SkRect temp = SkRect::Make(scissor); + // invert the entire scene + helper.drawRect(temp, translate, SkRegion::kXOR_Op, GrAA::kNo, 0xFF); + } + SkPath clipPath; + element->asDeviceSpacePath(&clipPath); + clipPath.toggleInverseFillType(); + GrShape shape(clipPath, GrStyle::SimpleFill()); + helper.drawShape(shape, translate, SkRegion::kReplace_Op, aa, 0x00); + continue; + } + + // The other ops (union, xor, diff) only affect pixels inside + // the geometry so they can just be drawn normally + if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) { + helper.drawRect(element->getDeviceSpaceRect(), translate, (SkRegion::Op)op, aa, 0xFF); + } else { + SkPath path; + element->asDeviceSpacePath(&path); + GrShape shape(path, GrStyle::SimpleFill()); + helper.drawShape(shape, translate, (SkRegion::Op)op, aa, 0xFF); + } + } +} + +sk_sp GrClipStackClip::createSoftwareClipMask( + GrRecordingContext* context, const GrReducedClip& reducedClip, + GrRenderTargetContext* renderTargetContext) const { + GrUniqueKey key; + create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(), + reducedClip.numAnalyticFPs(), &key); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + const GrCaps* caps = context->priv().caps(); + + sk_sp proxy(proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin)); + if (proxy) { + return proxy; + } + + // The mask texture may be larger than necessary. We round out the clip bounds and pin the top + // left corner of the resulting rect to the top left of the texture. + SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height()); + + SkTaskGroup* taskGroup = nullptr; + if (auto direct = context->priv().asDirectContext()) { + taskGroup = direct->priv().getTaskGroup(); + } + + if (taskGroup && renderTargetContext) { + // Create our texture proxy + GrSurfaceDesc desc; + desc.fWidth = maskSpaceIBounds.width(); + desc.fHeight = maskSpaceIBounds.height(); + desc.fConfig = kAlpha_8_GrPixelConfig; + + GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kAlpha_8, + GrRenderable::kNo); + + // MDB TODO: We're going to fill this proxy with an ASAP upload (which is out of order wrt + // to ops), so it can't have any pending IO. + proxy = proxyProvider->createProxy(format, + desc, + GrRenderable::kNo, + 1, + kTopLeft_GrSurfaceOrigin, + GrMipMapped::kNo, + SkBackingFit::kApprox, + SkBudgeted::kYes, + GrProtected::kNo); + + auto uploader = skstd::make_unique>(reducedClip); + GrTDeferredProxyUploader* uploaderRaw = uploader.get(); + auto drawAndUploadMask = [uploaderRaw, maskSpaceIBounds] { + TRACE_EVENT0("skia.gpu", "Threaded SW Clip Mask Render"); + GrSWMaskHelper helper(uploaderRaw->getPixels()); + if (helper.init(maskSpaceIBounds)) { + draw_clip_elements_to_mask_helper(helper, uploaderRaw->data().elements(), + uploaderRaw->data().scissor(), + uploaderRaw->data().initialState()); + } else { + SkDEBUGFAIL("Unable to allocate SW clip mask."); + } + uploaderRaw->signalAndFreeData(); + }; + + taskGroup->add(std::move(drawAndUploadMask)); + proxy->texPriv().setDeferredUploader(std::move(uploader)); + } else { + GrSWMaskHelper helper; + if (!helper.init(maskSpaceIBounds)) { + return nullptr; + } + + draw_clip_elements_to_mask_helper(helper, reducedClip.maskElements(), reducedClip.scissor(), + reducedClip.initialState()); + + proxy = helper.toTextureProxy(context, SkBackingFit::kApprox); + } + + SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, proxy.get()); + add_invalidate_on_pop_message(context, *fStack, reducedClip.maskGenID(), key); + return proxy; +} diff --git a/gfx/skia/skia/src/gpu/GrClipStackClip.h b/gfx/skia/skia/src/gpu/GrClipStackClip.h new file mode 100644 index 0000000000..7d16d5e9b1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrClipStackClip.h @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrClipStackClip_DEFINED +#define GrClipStackClip_DEFINED + +#include "src/core/SkClipStack.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrReducedClip.h" + +class GrPathRenderer; +class GrTextureProxy; + +/** + * GrClipStackClip can apply a generic SkClipStack to the draw state. It may need to generate an + * 8-bit alpha clip mask and/or modify the stencil buffer during apply(). + */ +class GrClipStackClip final : public GrClip { +public: + GrClipStackClip(const SkClipStack* stack = nullptr) { this->reset(stack); } + + void reset(const SkClipStack* stack) { fStack = stack; } + + bool quickContains(const SkRect&) const final; + bool quickContains(const SkRRect&) const final; + void getConservativeBounds(int width, int height, SkIRect* devResult, + bool* isIntersectionOfRects) const final; + bool apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA, + bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final; + + bool isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA* aa) const override; + + sk_sp testingOnly_createClipMask(GrContext*) const; + static const char kMaskTestTag[]; + +private: + static bool PathNeedsSWRenderer(GrRecordingContext* context, + const SkIRect& scissorRect, + bool hasUserStencilSettings, + const GrRenderTargetContext*, + const SkMatrix& viewMatrix, + const SkClipStack::Element* element, + GrPathRenderer** prOut, + bool needsStencil); + + bool applyClipMask(GrRecordingContext*, GrRenderTargetContext*, const GrReducedClip&, + bool hasUserStencilSettings, GrAppliedClip*) const; + + // Creates an alpha mask of the clip. The mask is a rasterization of elements through the + // rect specified by clipSpaceIBounds. + sk_sp createAlphaClipMask(GrRecordingContext*, const GrReducedClip&) const; + + // Similar to createAlphaClipMask but it rasterizes in SW and uploads to the result texture. + sk_sp createSoftwareClipMask(GrRecordingContext*, const GrReducedClip&, + GrRenderTargetContext*) const; + + static bool UseSWOnlyPath(GrRecordingContext*, + bool hasUserStencilSettings, + const GrRenderTargetContext*, + const GrReducedClip&); + + const SkClipStack* fStack; +}; + +#endif // GrClipStackClip_DEFINED diff --git a/gfx/skia/skia/src/gpu/GrColor.h b/gfx/skia/skia/src/gpu/GrColor.h new file mode 100644 index 0000000000..39e7bfa7bf --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrColor.h @@ -0,0 +1,113 @@ + +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#ifndef GrColor_DEFINED +#define GrColor_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkColorPriv.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" + +/** + * GrColor is 4 bytes for R, G, B, A, in a specific order defined below. Whether the color is + * premultiplied or not depends on the context in which it is being used. + */ +typedef uint32_t GrColor; + +// shift amount to assign a component to a GrColor int +// These shift values are chosen for compatibility with GL attrib arrays +// ES doesn't allow BGRA vertex attrib order so if they were not in this order +// we'd have to swizzle in shaders. +#ifdef SK_CPU_BENDIAN + #define GrColor_SHIFT_R 24 + #define GrColor_SHIFT_G 16 + #define GrColor_SHIFT_B 8 + #define GrColor_SHIFT_A 0 +#else + #define GrColor_SHIFT_R 0 + #define GrColor_SHIFT_G 8 + #define GrColor_SHIFT_B 16 + #define GrColor_SHIFT_A 24 +#endif + +/** + * Pack 4 components (RGBA) into a GrColor int + */ +static inline GrColor GrColorPackRGBA(unsigned r, unsigned g, unsigned b, unsigned a) { + SkASSERT((uint8_t)r == r); + SkASSERT((uint8_t)g == g); + SkASSERT((uint8_t)b == b); + SkASSERT((uint8_t)a == a); + return (r << GrColor_SHIFT_R) | + (g << GrColor_SHIFT_G) | + (b << GrColor_SHIFT_B) | + (a << GrColor_SHIFT_A); +} + +// extract a component (byte) from a GrColor int + +#define GrColorUnpackR(color) (((color) >> GrColor_SHIFT_R) & 0xFF) +#define GrColorUnpackG(color) (((color) >> GrColor_SHIFT_G) & 0xFF) +#define GrColorUnpackB(color) (((color) >> GrColor_SHIFT_B) & 0xFF) +#define GrColorUnpackA(color) (((color) >> GrColor_SHIFT_A) & 0xFF) + +/** + * Since premultiplied means that alpha >= color, we construct a color with + * each component==255 and alpha == 0 to be "illegal" + */ +#define GrColor_ILLEGAL (~(0xFF << GrColor_SHIFT_A)) + +/** Normalizes and coverts an uint8_t to a float. [0, 255] -> [0.0, 1.0] */ +static inline float GrNormalizeByteToFloat(uint8_t value) { + static const float ONE_OVER_255 = 1.f / 255.f; + return value * ONE_OVER_255; +} + +/** Used to pick vertex attribute types. */ +static inline bool SkPMColor4fFitsInBytes(const SkPMColor4f& color) { + // Might want to instead check that the components are [0...a] instead of [0...1]? + return color.fitsInBytes(); +} + +static inline uint64_t SkPMColor4f_toFP16(const SkPMColor4f& color) { + uint64_t halfColor; + SkFloatToHalf_finite_ftz(Sk4f::Load(color.vec())).store(&halfColor); + return halfColor; +} + +/** + * GrVertexColor is a helper for writing colors to a vertex attribute. It stores either GrColor + * or four half-float channels, depending on the wideColor parameter. GrVertexWriter will write + * the correct amount of data. Note that the GP needs to have been constructed with the correct + * attribute type for colors, to match the usage here. + */ +class GrVertexColor { +public: + explicit GrVertexColor(const SkPMColor4f& color, bool wideColor) + : fWideColor(wideColor) { + if (wideColor) { + SkFloatToHalf_finite_ftz(Sk4f::Load(color.vec())).store(&fColor); + } else { + fColor[0] = color.toBytes_RGBA(); + } + } + + size_t size() const { return fWideColor ? 8 : 4; } + +private: + friend struct GrVertexWriter; + + uint32_t fColor[2]; + bool fWideColor; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrColorInfo.cpp b/gfx/skia/skia/src/gpu/GrColorInfo.cpp new file mode 100644 index 0000000000..4caf7c1c0c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrColorInfo.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrColorInfo.h" + +#include "src/core/SkColorSpacePriv.h" + +GrColorInfo::GrColorInfo( + GrColorType colorType, SkAlphaType alphaType, sk_sp colorSpace) + : fColorSpace(std::move(colorSpace)), fColorType(colorType), fAlphaType(alphaType) {} + +GrColorInfo::GrColorInfo(const SkColorInfo& ci) + : GrColorInfo(SkColorTypeToGrColorType(ci.colorType()), + ci.alphaType(), + ci.refColorSpace()) {} + +GrColorInfo::GrColorInfo(const GrColorInfo& ci) + : GrColorInfo(ci.colorType(), + ci.alphaType(), + ci.refColorSpace()) {} + +GrColorSpaceXform* GrColorInfo::colorSpaceXformFromSRGB() const { + // TODO: Make this atomic if we start accessing this on multiple threads. + if (!fInitializedColorSpaceXformFromSRGB) { + // sRGB sources are very common (SkColor, etc...), so we cache that transformation + fColorXformFromSRGB = GrColorSpaceXform::Make(sk_srgb_singleton(), kUnpremul_SkAlphaType, + fColorSpace.get(), kUnpremul_SkAlphaType); + fInitializedColorSpaceXformFromSRGB = true; + } + // You can't be color-space aware in legacy mode + SkASSERT(fColorSpace || !fColorXformFromSRGB); + return fColorXformFromSRGB.get(); +} diff --git a/gfx/skia/skia/src/gpu/GrColorInfo.h b/gfx/skia/skia/src/gpu/GrColorInfo.h new file mode 100644 index 0000000000..91b35b8dec --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrColorInfo.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrColorInfo_DEFINED +#define GrColorInfo_DEFINED + +#include "include/core/SkColorSpace.h" +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrColorSpaceXform.h" + +/** + * All the info needed to interpret a color: Color type + alpha type + color space. Also caches + * the GrColorSpaceXform from sRGB. */ +class GrColorInfo { +public: + GrColorInfo() = default; + GrColorInfo(const GrColorInfo&); + GrColorInfo(GrColorType, SkAlphaType, sk_sp); + /* implicit */ GrColorInfo(const SkColorInfo&); + + bool isLinearlyBlended() const { return fColorSpace && fColorSpace->gammaIsLinear(); } + + SkColorSpace* colorSpace() const { return fColorSpace.get(); } + sk_sp refColorSpace() const { return fColorSpace; } + + GrColorSpaceXform* colorSpaceXformFromSRGB() const; + sk_sp refColorSpaceXformFromSRGB() const { + return sk_ref_sp(this->colorSpaceXformFromSRGB()); + } + + GrColorType colorType() const { return fColorType; } + SkAlphaType alphaType() const { return fAlphaType; } + + bool isValid() const { + return fColorType != GrColorType::kUnknown && fAlphaType != kUnknown_SkAlphaType; + } + +private: + sk_sp fColorSpace; + mutable sk_sp fColorXformFromSRGB; + GrColorType fColorType = GrColorType::kUnknown; + SkAlphaType fAlphaType = kUnknown_SkAlphaType; + mutable bool fInitializedColorSpaceXformFromSRGB = false; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrColorSpaceXform.cpp b/gfx/skia/skia/src/gpu/GrColorSpaceXform.cpp new file mode 100644 index 0000000000..e8d739cfc9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrColorSpaceXform.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorSpace.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +sk_sp GrColorSpaceXform::Make(SkColorSpace* src, SkAlphaType srcAT, + SkColorSpace* dst, SkAlphaType dstAT) { + SkColorSpaceXformSteps steps(src, srcAT, dst, dstAT); + return steps.flags.mask() == 0 ? nullptr /* Noop transform */ + : sk_make_sp(steps); +} + +bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b) { + if (a == b) { + return true; + } + + if (!a || !b || a->fSteps.flags.mask() != b->fSteps.flags.mask()) { + return false; + } + + if (a->fSteps.flags.linearize && + 0 != memcmp(&a->fSteps.srcTF, &b->fSteps.srcTF, sizeof(a->fSteps.srcTF))) { + return false; + } + + if (a->fSteps.flags.gamut_transform && + 0 != memcmp(&a->fSteps.src_to_dst_matrix, &b->fSteps.src_to_dst_matrix, + sizeof(a->fSteps.src_to_dst_matrix))) { + return false; + } + + if (a->fSteps.flags.encode && + 0 != memcmp(&a->fSteps.dstTFInv, &b->fSteps.dstTFInv, sizeof(a->fSteps.dstTFInv))) { + return false; + } + + return true; +} + +SkColor4f GrColorSpaceXform::apply(const SkColor4f& srcColor) { + SkColor4f result = srcColor; + fSteps.apply(result.vec()); + return result; +} + +////////////////////////////////////////////////////////////////////////////// + +class GrGLColorSpaceXformEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs& args) override { + const GrColorSpaceXformEffect& csxe = args.fFp.cast(); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + fColorSpaceHelper.emitCode(uniformHandler, csxe.colorXform()); + + if (this->numChildProcessors()) { + SkString childColor("src_color"); + this->invokeChild(0, &childColor, args); + + SkString xformedColor; + fragBuilder->appendColorGamutXform(&xformedColor, childColor.c_str(), &fColorSpaceHelper); + fragBuilder->codeAppendf("%s = %s * %s;", args.fOutputColor, xformedColor.c_str(), + args.fInputColor); + } else { + SkString xformedColor; + fragBuilder->appendColorGamutXform(&xformedColor, args.fInputColor, &fColorSpaceHelper); + fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, xformedColor.c_str()); + } + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) override { + const GrColorSpaceXformEffect& csxe = processor.cast(); + fColorSpaceHelper.setData(pdman, csxe.colorXform()); + } + + GrGLSLColorSpaceXformHelper fColorSpaceHelper; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +GrColorSpaceXformEffect::GrColorSpaceXformEffect(std::unique_ptr child, + sk_sp colorXform) + : INHERITED(kGrColorSpaceXformEffect_ClassID, OptFlags(child.get())) + , fColorXform(std::move(colorXform)) { + if (child) { + this->registerChildProcessor(std::move(child)); + } +} + +std::unique_ptr GrColorSpaceXformEffect::clone() const { + std::unique_ptr child = + this->numChildProcessors() ? this->childProcessor(0).clone() : nullptr; + return std::unique_ptr( + new GrColorSpaceXformEffect(std::move(child), fColorXform)); +} + +bool GrColorSpaceXformEffect::onIsEqual(const GrFragmentProcessor& s) const { + const GrColorSpaceXformEffect& other = s.cast(); + return GrColorSpaceXform::Equals(fColorXform.get(), other.fColorXform.get()); +} + +void GrColorSpaceXformEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32(GrColorSpaceXform::XformKey(fColorXform.get())); +} + +GrGLSLFragmentProcessor* GrColorSpaceXformEffect::onCreateGLSLInstance() const { + return new GrGLColorSpaceXformEffect(); +} + +GrFragmentProcessor::OptimizationFlags GrColorSpaceXformEffect::OptFlags( + const GrFragmentProcessor* child) { + // TODO: Implement constant output for constant input + if (child) { + OptimizationFlags flags = kNone_OptimizationFlags; + if (child->compatibleWithCoverageAsAlpha()) { + flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } + if (child->preservesOpaqueInput()) { + flags |= kPreservesOpaqueInput_OptimizationFlag; + } + return flags; + } else { + return kCompatibleWithCoverageAsAlpha_OptimizationFlag | + kPreservesOpaqueInput_OptimizationFlag; + } +} + +std::unique_ptr GrColorSpaceXformEffect::Make(SkColorSpace* src, + SkAlphaType srcAT, + SkColorSpace* dst, + SkAlphaType dstAT) { + auto xform = GrColorSpaceXform::Make(src, srcAT, + dst, dstAT); + if (!xform) { + return nullptr; + } + + return std::unique_ptr(new GrColorSpaceXformEffect(nullptr, + std::move(xform))); +} + +std::unique_ptr GrColorSpaceXformEffect::Make( + std::unique_ptr child, + SkColorSpace* src, SkAlphaType srcAT, SkColorSpace* dst) { + if (!child) { + return nullptr; + } + + auto xform = GrColorSpaceXform::Make(src, srcAT, + dst, kPremul_SkAlphaType); + if (!xform) { + return child; + } + + return std::unique_ptr(new GrColorSpaceXformEffect(std::move(child), + std::move(xform))); +} + +std::unique_ptr GrColorSpaceXformEffect::Make( + std::unique_ptr child, sk_sp colorXform) { + if (!child) { + return nullptr; + } + if (!colorXform) { + return child; + } + + return std::unique_ptr(new GrColorSpaceXformEffect(std::move(child), + std::move(colorXform))); +} diff --git a/gfx/skia/skia/src/gpu/GrColorSpaceXform.h b/gfx/skia/skia/src/gpu/GrColorSpaceXform.h new file mode 100644 index 0000000000..65e18363b5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrColorSpaceXform.h @@ -0,0 +1,103 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrColorSpaceXform_DEFINED +#define GrColorSpaceXform_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/gpu/GrFragmentProcessor.h" + +class SkColorSpace; + + /** + * Represents a color space transformation + */ +class GrColorSpaceXform : public SkRefCnt { +public: + GrColorSpaceXform(const SkColorSpaceXformSteps& steps) : fSteps(steps) {} + + static sk_sp Make(SkColorSpace* src, SkAlphaType srcAT, + SkColorSpace* dst, SkAlphaType dstAT); + + const SkColorSpaceXformSteps& steps() const { return fSteps; } + + /** + * GrGLSLFragmentProcessor::GenKey() must call this and include the returned value in its + * computed key. + */ + static uint32_t XformKey(const GrColorSpaceXform* xform) { + // Code generation depends on which steps we apply, + // and the kinds of transfer functions (if we're applying those). + if (!xform) { return 0; } + + const SkColorSpaceXformSteps& steps(xform->fSteps); + uint32_t key = steps.flags.mask(); + if (steps.flags.linearize) { + key |= classify_transfer_fn(steps.srcTF) << 8; + } + if (steps.flags.encode) { + key |= classify_transfer_fn(steps.dstTFInv) << 16; + } + return key; + } + + static bool Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b); + + SkColor4f apply(const SkColor4f& srcColor); + +private: + friend class GrGLSLColorSpaceXformHelper; + + SkColorSpaceXformSteps fSteps; +}; + +class GrColorSpaceXformEffect : public GrFragmentProcessor { +public: + /** + * Returns a fragment processor that converts the input's color space from src to dst. + */ + static std::unique_ptr Make(SkColorSpace* src, SkAlphaType srcAT, + SkColorSpace* dst, SkAlphaType dstAT); + + /** + * Returns a fragment processor that calls the passed in fragment processor, and then converts + * the color space of the output from src to dst. + */ + static std::unique_ptr Make(std::unique_ptr child, + SkColorSpace* src, SkAlphaType srcAT, + SkColorSpace* dst); + + /** + * Returns a fragment processor that calls the passed in FP and then converts it with the given + * color xform. Returns null if child is null, returns child if the xform is null (e.g. noop). + */ + static std::unique_ptr Make(std::unique_ptr child, + sk_sp colorXform); + + const char* name() const override { return "ColorSpaceXform"; } + std::unique_ptr clone() const override; + + const GrColorSpaceXform* colorXform() const { return fColorXform.get(); } + +private: + GrColorSpaceXformEffect(std::unique_ptr child, + sk_sp colorXform); + + static OptimizationFlags OptFlags(const GrFragmentProcessor* child); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + + sk_sp fColorXform; + + typedef GrFragmentProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrContext.cpp b/gfx/skia/skia/src/gpu/GrContext.cpp new file mode 100644 index 0000000000..d9f37f0d4b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrContext.cpp @@ -0,0 +1,582 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContext.h" + +#include "include/core/SkTraceMemoryDump.h" +#include "include/gpu/GrBackendSemaphore.h" +#include "include/private/SkDeferredDisplayList.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTaskGroup.h" +#include "src/gpu/GrClientMappedBufferManager.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrPathRendererChain.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrShaderUtils.h" +#include "src/gpu/GrSoftwarePathRenderer.h" +#include "src/gpu/GrTracing.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" +#include "src/gpu/effects/GrSkSLFP.h" +#include "src/gpu/text/GrTextBlobCache.h" +#include "src/gpu/text/GrTextContext.h" +#include "src/image/SkImage_GpuBase.h" +#include "src/image/SkSurface_Gpu.h" +#include + +#define ASSERT_OWNED_PROXY(P) \ + SkASSERT(!(P) || !((P)->peekTexture()) || (P)->peekTexture()->getContext() == this) + +#define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this) +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());) +#define RETURN_IF_ABANDONED if (this->abandoned()) { return; } +#define RETURN_FALSE_IF_ABANDONED if (this->abandoned()) { return false; } +#define RETURN_NULL_IF_ABANDONED if (this->abandoned()) { return nullptr; } + +//////////////////////////////////////////////////////////////////////////////// + +GrContext::GrContext(GrBackendApi backend, const GrContextOptions& options, int32_t contextID) + : INHERITED(backend, options, contextID) { + fResourceCache = nullptr; + fResourceProvider = nullptr; +} + +GrContext::~GrContext() { + ASSERT_SINGLE_OWNER + + if (this->drawingManager()) { + this->drawingManager()->cleanup(); + } + delete fResourceProvider; + delete fResourceCache; +} + +bool GrContext::init(sk_sp caps, sk_sp FPFactoryCache) { + ASSERT_SINGLE_OWNER + SkASSERT(fThreadSafeProxy); // needs to have been initialized by derived classes + SkASSERT(this->proxyProvider()); + + if (!INHERITED::init(std::move(caps), std::move(FPFactoryCache))) { + return false; + } + + SkASSERT(this->caps()); + SkASSERT(this->getGrStrikeCache()); + SkASSERT(this->getTextBlobCache()); + + if (fGpu) { + fResourceCache = new GrResourceCache(this->caps(), this->singleOwner(), this->contextID()); + fResourceProvider = new GrResourceProvider(fGpu.get(), fResourceCache, this->singleOwner()); + fMappedBufferManager = skstd::make_unique(this->contextID()); + } + + if (fResourceCache) { + fResourceCache->setProxyProvider(this->proxyProvider()); + } + + fDidTestPMConversions = false; + + // DDL TODO: we need to think through how the task group & persistent cache + // get passed on to/shared between all the DDLRecorders created with this context. + if (this->options().fExecutor) { + fTaskGroup = skstd::make_unique(*this->options().fExecutor); + } + + fPersistentCache = this->options().fPersistentCache; + fShaderErrorHandler = this->options().fShaderErrorHandler; + if (!fShaderErrorHandler) { + fShaderErrorHandler = GrShaderUtils::DefaultShaderErrorHandler(); + } + + return true; +} + +sk_sp GrContext::threadSafeProxy() { + return fThreadSafeProxy; +} + +////////////////////////////////////////////////////////////////////////////// + +void GrContext::abandonContext() { + if (this->abandoned()) { + return; + } + + INHERITED::abandonContext(); + + fMappedBufferManager->abandon(); + + fResourceProvider->abandon(); + + // Need to cleanup the drawing manager first so all the render targets + // will be released/forgotten before they too are abandoned. + this->drawingManager()->cleanup(); + + // abandon first to so destructors + // don't try to free the resources in the API. + fResourceCache->abandonAll(); + + fGpu->disconnect(GrGpu::DisconnectType::kAbandon); + + fMappedBufferManager.reset(); +} + +void GrContext::releaseResourcesAndAbandonContext() { + if (this->abandoned()) { + return; + } + + INHERITED::abandonContext(); + + fMappedBufferManager.reset(); + + fResourceProvider->abandon(); + + // Need to cleanup the drawing manager first so all the render targets + // will be released/forgotten before they too are abandoned. + this->drawingManager()->cleanup(); + + // Release all resources in the backend 3D API. + fResourceCache->releaseAll(); + + fGpu->disconnect(GrGpu::DisconnectType::kCleanup); +} + +void GrContext::resetGLTextureBindings() { + if (this->abandoned() || this->backend() != GrBackendApi::kOpenGL) { + return; + } + fGpu->resetTextureBindings(); +} + +void GrContext::resetContext(uint32_t state) { + ASSERT_SINGLE_OWNER + fGpu->markContextDirty(state); +} + +void GrContext::freeGpuResources() { + ASSERT_SINGLE_OWNER + + // TODO: the glyph cache doesn't hold any GpuResources so this call should not be needed here. + // Some slack in the GrTextBlob's implementation requires it though. That could be fixed. + this->getGrStrikeCache()->freeAll(); + + this->drawingManager()->freeGpuResources(); + + fResourceCache->purgeAllUnlocked(); +} + +void GrContext::purgeUnlockedResources(bool scratchResourcesOnly) { + ASSERT_SINGLE_OWNER + + if (this->abandoned()) { + return; + } + + fResourceCache->purgeUnlockedResources(scratchResourcesOnly); + fResourceCache->purgeAsNeeded(); + + // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient + // place to purge stale blobs + this->getTextBlobCache()->purgeStaleBlobs(); +} + +void GrContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + ASSERT_SINGLE_OWNER + + if (this->abandoned()) { + return; + } + + fMappedBufferManager->process(); + auto purgeTime = GrStdSteadyClock::now() - msNotUsed; + + fResourceCache->purgeAsNeeded(); + fResourceCache->purgeResourcesNotUsedSince(purgeTime); + + if (auto ccpr = this->drawingManager()->getCoverageCountingPathRenderer()) { + ccpr->purgeCacheEntriesOlderThan(this->proxyProvider(), purgeTime); + } + + // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient + // place to purge stale blobs + this->getTextBlobCache()->purgeStaleBlobs(); +} + +void GrContext::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) { + ASSERT_SINGLE_OWNER + + if (this->abandoned()) { + return; + } + + fResourceCache->purgeUnlockedResources(bytesToPurge, preferScratchResources); +} + +void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const { + ASSERT_SINGLE_OWNER + + if (resourceCount) { + *resourceCount = fResourceCache->getBudgetedResourceCount(); + } + if (resourceBytes) { + *resourceBytes = fResourceCache->getBudgetedResourceBytes(); + } +} + +size_t GrContext::getResourceCachePurgeableBytes() const { + ASSERT_SINGLE_OWNER + return fResourceCache->getPurgeableBytes(); +} + +size_t GrContext::ComputeImageSize(sk_sp image, GrMipMapped mipMapped, bool useNextPow2) { + if (!image->isTextureBacked()) { + return 0; + } + SkImage_GpuBase* gpuImage = static_cast(as_IB(image.get())); + GrTextureProxy* proxy = gpuImage->peekProxy(); + if (!proxy) { + return 0; + } + + const GrCaps& caps = *gpuImage->context()->priv().caps(); + int colorSamplesPerPixel = 1; + return GrSurface::ComputeSize(caps, proxy->backendFormat(), image->width(), image->height(), + colorSamplesPerPixel, mipMapped, useNextPow2); +} + +//////////////////////////////////////////////////////////////////////////////// + +int GrContext::maxTextureSize() const { return this->caps()->maxTextureSize(); } + +int GrContext::maxRenderTargetSize() const { return this->caps()->maxRenderTargetSize(); } + +bool GrContext::colorTypeSupportedAsImage(SkColorType colorType) const { + GrBackendFormat format = + this->caps()->getDefaultBackendFormat(SkColorTypeToGrColorType(colorType), + GrRenderable::kNo); + return format.isValid(); +} + +int GrContext::maxSurfaceSampleCountForColorType(SkColorType colorType) const { + GrBackendFormat format = + this->caps()->getDefaultBackendFormat(SkColorTypeToGrColorType(colorType), + GrRenderable::kYes); + return this->caps()->maxRenderTargetSampleCount(format); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool GrContext::wait(int numSemaphores, const GrBackendSemaphore waitSemaphores[]) { + if (!fGpu || fGpu->caps()->semaphoreSupport()) { + return false; + } + for (int i = 0; i < numSemaphores; ++i) { + sk_sp sema = fResourceProvider->wrapBackendSemaphore( + waitSemaphores[i], GrResourceProvider::SemaphoreWrapType::kWillWait, + kAdopt_GrWrapOwnership); + fGpu->waitSemaphore(std::move(sema)); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +GrSemaphoresSubmitted GrContext::flush(const GrFlushInfo& info, + const GrPrepareForExternalIORequests& externalRequests) { + ASSERT_SINGLE_OWNER + if (this->abandoned()) { + return GrSemaphoresSubmitted::kNo; + } + + return this->drawingManager()->flush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess, + info, externalRequests); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrContext::checkAsyncWorkCompletion() { + if (fGpu) { + fGpu->checkFinishProcs(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrContext::storeVkPipelineCacheData() { + if (fGpu) { + fGpu->storeVkPipelineCacheData(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool GrContext::supportsDistanceFieldText() const { + return this->caps()->shaderCaps()->supportsDistanceFieldText(); +} + +////////////////////////////////////////////////////////////////////////////// + +void GrContext::getResourceCacheLimits(int* maxResources, size_t* maxResourceBytes) const { + ASSERT_SINGLE_OWNER + if (maxResources) { + *maxResources = -1; + } + if (maxResourceBytes) { + *maxResourceBytes = this->getResourceCacheLimit(); + } +} + +size_t GrContext::getResourceCacheLimit() const { + ASSERT_SINGLE_OWNER + return fResourceCache->getMaxResourceBytes(); +} + +void GrContext::setResourceCacheLimits(int unused, size_t maxResourceBytes) { + ASSERT_SINGLE_OWNER + this->setResourceCacheLimit(maxResourceBytes); +} + +void GrContext::setResourceCacheLimit(size_t maxResourceBytes) { + ASSERT_SINGLE_OWNER + fResourceCache->setLimit(maxResourceBytes); +} + +////////////////////////////////////////////////////////////////////////////// +void GrContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + ASSERT_SINGLE_OWNER + fResourceCache->dumpMemoryStatistics(traceMemoryDump); + traceMemoryDump->dumpNumericValue("skia/gr_text_blob_cache", "size", "bytes", + this->getTextBlobCache()->usedBytes()); +} + +////////////////////////////////////////////////////////////////////////////// +GrBackendTexture GrContext::createBackendTexture(int width, int height, + const GrBackendFormat& backendFormat, + GrMipMapped mipMapped, + GrRenderable renderable, + GrProtected isProtected) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + if (!this->asDirectContext()) { + return GrBackendTexture(); + } + + if (this->abandoned()) { + return GrBackendTexture(); + } + + return fGpu->createBackendTexture(width, height, backendFormat, + mipMapped, renderable, + nullptr, 0, nullptr, isProtected); +} + +GrBackendTexture GrContext::createBackendTexture(int width, int height, + SkColorType skColorType, + GrMipMapped mipMapped, + GrRenderable renderable, + GrProtected isProtected) { + if (!this->asDirectContext()) { + return GrBackendTexture(); + } + + if (this->abandoned()) { + return GrBackendTexture(); + } + + const GrBackendFormat format = this->defaultBackendFormat(skColorType, renderable); + + return this->createBackendTexture(width, height, format, mipMapped, renderable, isProtected); +} + +GrBackendTexture GrContext::createBackendTexture(const SkSurfaceCharacterization& c) { + if (!this->asDirectContext() || !c.isValid()) { + return GrBackendTexture(); + } + + if (this->abandoned()) { + return GrBackendTexture(); + } + + if (c.usesGLFBO0()) { + // If we are making the surface we will never use FBO0. + return GrBackendTexture(); + } + + if (c.vulkanSecondaryCBCompatible()) { + return {}; + } + + const GrBackendFormat format = this->defaultBackendFormat(c.colorType(), GrRenderable::kYes); + if (!format.isValid()) { + return GrBackendTexture(); + } + + GrBackendTexture result = this->createBackendTexture(c.width(), c.height(), format, + GrMipMapped(c.isMipMapped()), + GrRenderable::kYes, + c.isProtected()); + SkASSERT(c.isCompatible(result)); + return result; +} + +GrBackendTexture GrContext::createBackendTexture(const SkSurfaceCharacterization& c, + const SkColor4f& color) { + if (!this->asDirectContext() || !c.isValid()) { + return GrBackendTexture(); + } + + if (this->abandoned()) { + return GrBackendTexture(); + } + + if (c.usesGLFBO0()) { + // If we are making the surface we will never use FBO0. + return GrBackendTexture(); + } + + if (c.vulkanSecondaryCBCompatible()) { + return {}; + } + + const GrBackendFormat format = this->defaultBackendFormat(c.colorType(), GrRenderable::kYes); + if (!format.isValid()) { + return GrBackendTexture(); + } + + GrBackendTexture result = this->createBackendTexture(c.width(), c.height(), format, color, + GrMipMapped(c.isMipMapped()), + GrRenderable::kYes, + c.isProtected()); + SkASSERT(c.isCompatible(result)); + return result; +} + +GrBackendTexture GrContext::createBackendTexture(int width, int height, + const GrBackendFormat& backendFormat, + const SkColor4f& color, + GrMipMapped mipMapped, + GrRenderable renderable, + GrProtected isProtected) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + if (!this->asDirectContext()) { + return GrBackendTexture(); + } + + if (this->abandoned()) { + return GrBackendTexture(); + } + + return fGpu->createBackendTexture(width, height, backendFormat, + mipMapped, renderable, + nullptr, 0, &color, isProtected); +} + +GrBackendTexture GrContext::createBackendTexture(int width, int height, + SkColorType skColorType, + const SkColor4f& color, + GrMipMapped mipMapped, + GrRenderable renderable, + GrProtected isProtected) { + if (!this->asDirectContext()) { + return GrBackendTexture(); + } + + if (this->abandoned()) { + return GrBackendTexture(); + } + + GrBackendFormat format = this->defaultBackendFormat(skColorType, renderable); + if (!format.isValid()) { + return GrBackendTexture(); + } + + GrColorType grColorType = SkColorTypeToGrColorType(skColorType); + SkColor4f swizzledColor = this->caps()->getOutputSwizzle(format, grColorType).applyTo(color); + + return this->createBackendTexture(width, height, format, swizzledColor, mipMapped, renderable, + isProtected); +} + +GrBackendTexture GrContext::createBackendTexture(const SkPixmap srcData[], int numLevels, + GrRenderable renderable, GrProtected isProtected) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + if (!this->asDirectContext()) { + return {}; + } + + if (this->abandoned()) { + return {}; + } + + if (!srcData || !numLevels) { + return {}; + } + + int baseWidth = srcData[0].width(); + int baseHeight = srcData[0].height(); + SkColorType colorType = srcData[0].colorType(); + + GrBackendFormat backendFormat = this->defaultBackendFormat(colorType, renderable); + + return fGpu->createBackendTexture(baseWidth, baseHeight, backendFormat, + numLevels > 1 ? GrMipMapped::kYes : GrMipMapped::kNo, + renderable, srcData, numLevels, nullptr, isProtected); +} + +void GrContext::deleteBackendTexture(GrBackendTexture backendTex) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + if (this->abandoned() || !backendTex.isValid()) { + return; + } + + fGpu->deleteBackendTexture(backendTex); +} + +bool GrContext::precompileShader(const SkData& key, const SkData& data) { + return fGpu->precompileShader(key, data); +} + +#ifdef SK_ENABLE_DUMP_GPU +#include "src/utils/SkJSONWriter.h" +SkString GrContext::dump() const { + SkDynamicMemoryWStream stream; + SkJSONWriter writer(&stream, SkJSONWriter::Mode::kPretty); + writer.beginObject(); + + writer.appendString("backend", GrBackendApiToStr(this->backend())); + + writer.appendName("caps"); + this->caps()->dumpJSON(&writer); + + writer.appendName("gpu"); + this->fGpu->dumpJSON(&writer); + + // Flush JSON to the memory stream + writer.endObject(); + writer.flush(); + + // Null terminate the JSON data in the memory stream + stream.write8(0); + + // Allocate a string big enough to hold all the data, then copy out of the stream + SkString result(stream.bytesWritten()); + stream.copyToAndReset(result.writable_str()); + return result; +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrContextPriv.cpp b/gfx/skia/skia/src/gpu/GrContextPriv.cpp new file mode 100644 index 0000000000..a84ace62c8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrContextPriv.cpp @@ -0,0 +1,340 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrContextPriv.h" + +#include "include/gpu/GrContextThreadSafeProxy.h" +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" +#include "src/gpu/GrSurfaceContextPriv.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTextureContext.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/generated/GrConfigConversionEffect.h" +#include "src/gpu/text/GrTextBlobCache.h" +#include "src/image/SkImage_Base.h" +#include "src/image/SkImage_Gpu.h" + +#define ASSERT_OWNED_PROXY(P) \ + SkASSERT(!(P) || !((P)->peekTexture()) || (P)->peekTexture()->getContext() == fContext) +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fContext->singleOwner());) +#define RETURN_VALUE_IF_ABANDONED(value) if (fContext->abandoned()) { return (value); } +#define RETURN_IF_ABANDONED RETURN_VALUE_IF_ABANDONED(void) + +sk_sp GrContextPriv::refCaps() const { + return fContext->refCaps(); +} + +sk_sp GrContextPriv::fpFactoryCache() { + return fContext->fpFactoryCache(); +} + +sk_sp GrContextPriv::refOpMemoryPool() { + return fContext->refOpMemoryPool(); +} + +void GrContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) { + fContext->addOnFlushCallbackObject(onFlushCBObject); +} + +std::unique_ptr GrContextPriv::makeWrappedSurfaceContext( + sk_sp proxy, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + const SkSurfaceProps* props) { + return fContext->makeWrappedSurfaceContext(std::move(proxy), colorType, alphaType, + std::move(colorSpace), props); +} + +std::unique_ptr GrContextPriv::makeDeferredTextureContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkBudgeted budgeted, + GrProtected isProtected) { + return fContext->makeDeferredTextureContext(fit, width, height, colorType, alphaType, + std::move(colorSpace), mipMapped, origin, budgeted, + isProtected); +} + +std::unique_ptr GrContextPriv::makeDeferredRenderTargetContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + const SkSurfaceProps* surfaceProps, + SkBudgeted budgeted, + GrProtected isProtected) { + return fContext->makeDeferredRenderTargetContext(fit, width, height, colorType, + std::move(colorSpace), sampleCnt, mipMapped, + origin, surfaceProps, budgeted, isProtected); +} + +std::unique_ptr GrContextPriv::makeDeferredRenderTargetContextWithFallback( + SkBackingFit fit, int width, int height, GrColorType colorType, + sk_sp colorSpace, int sampleCnt, GrMipMapped mipMapped, + GrSurfaceOrigin origin, const SkSurfaceProps* surfaceProps, SkBudgeted budgeted, + GrProtected isProtected) { + return fContext->makeDeferredRenderTargetContextWithFallback( + fit, width, height, colorType, std::move(colorSpace), sampleCnt, mipMapped, origin, + surfaceProps, budgeted, isProtected); +} + +std::unique_ptr GrContextPriv::makeBackendTextureContext( + const GrBackendTexture& tex, + GrSurfaceOrigin origin, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace) { + ASSERT_SINGLE_OWNER + + sk_sp proxy = this->proxyProvider()->wrapBackendTexture( + tex, colorType, origin, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo, kRW_GrIOType); + if (!proxy) { + return nullptr; + } + + return this->drawingManager()->makeTextureContext(std::move(proxy), colorType, alphaType, + std::move(colorSpace)); +} + +std::unique_ptr GrContextPriv::makeBackendTextureRenderTargetContext( + const GrBackendTexture& tex, + GrSurfaceOrigin origin, + int sampleCnt, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props, + ReleaseProc releaseProc, + ReleaseContext releaseCtx) { + ASSERT_SINGLE_OWNER + SkASSERT(sampleCnt > 0); + + sk_sp proxy(this->proxyProvider()->wrapRenderableBackendTexture( + tex, origin, sampleCnt, colorType, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo, + releaseProc, releaseCtx)); + if (!proxy) { + return nullptr; + } + + return this->drawingManager()->makeRenderTargetContext(std::move(proxy), colorType, + std::move(colorSpace), props); +} + +std::unique_ptr GrContextPriv::makeBackendRenderTargetRenderTargetContext( + const GrBackendRenderTarget& backendRT, + GrSurfaceOrigin origin, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + ReleaseProc releaseProc, + ReleaseContext releaseCtx) { + ASSERT_SINGLE_OWNER + + sk_sp proxy = this->proxyProvider()->wrapBackendRenderTarget( + backendRT, colorType, origin, releaseProc, releaseCtx); + if (!proxy) { + return nullptr; + } + + return this->drawingManager()->makeRenderTargetContext(std::move(proxy), colorType, + std::move(colorSpace), surfaceProps); +} + +std::unique_ptr +GrContextPriv::makeBackendTextureAsRenderTargetRenderTargetContext(const GrBackendTexture& tex, + GrSurfaceOrigin origin, + int sampleCnt, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props) { + ASSERT_SINGLE_OWNER + SkASSERT(sampleCnt > 0); + sk_sp proxy( + this->proxyProvider()->wrapBackendTextureAsRenderTarget(tex, colorType, + origin, sampleCnt)); + if (!proxy) { + return nullptr; + } + + return this->drawingManager()->makeRenderTargetContext(std::move(proxy), colorType, + std::move(colorSpace), props); +} + +std::unique_ptr GrContextPriv::makeVulkanSecondaryCBRenderTargetContext( + const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo, const SkSurfaceProps* props) { + ASSERT_SINGLE_OWNER + sk_sp proxy( + this->proxyProvider()->wrapVulkanSecondaryCBAsRenderTarget(imageInfo, vkInfo)); + if (!proxy) { + return nullptr; + } + + return this->drawingManager()->makeRenderTargetContext( + std::move(proxy), + SkColorTypeToGrColorType(imageInfo.colorType()), + imageInfo.refColorSpace(), + props); +} + +GrSemaphoresSubmitted GrContextPriv::flushSurfaces(GrSurfaceProxy* proxies[], int numProxies, + const GrFlushInfo& info) { + ASSERT_SINGLE_OWNER + RETURN_VALUE_IF_ABANDONED(GrSemaphoresSubmitted::kNo) + GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "flushSurfaces", fContext); + SkASSERT(numProxies >= 0); + SkASSERT(!numProxies || proxies); + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + ASSERT_OWNED_PROXY(proxies[i]); + } + return fContext->drawingManager()->flushSurfaces( + proxies, numProxies, SkSurface::BackendSurfaceAccess::kNoAccess, info); +} + +void GrContextPriv::flushSurface(GrSurfaceProxy* proxy) { + this->flushSurfaces(proxy ? &proxy : nullptr, proxy ? 1 : 0, {}); +} + +void GrContextPriv::moveRenderTasksToDDL(SkDeferredDisplayList* ddl) { + fContext->drawingManager()->moveRenderTasksToDDL(ddl); +} + +void GrContextPriv::copyRenderTasksFromDDL(const SkDeferredDisplayList* ddl, + GrRenderTargetProxy* newDest) { + fContext->drawingManager()->copyRenderTasksFromDDL(ddl, newDest); +} + +////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS +void GrContextPriv::resetGpuStats() const { +#if GR_GPU_STATS + fContext->fGpu->stats()->reset(); +#endif +} + +void GrContextPriv::dumpCacheStats(SkString* out) const { +#if GR_CACHE_STATS + fContext->fResourceCache->dumpStats(out); +#endif +} + +void GrContextPriv::dumpCacheStatsKeyValuePairs(SkTArray* keys, + SkTArray* values) const { +#if GR_CACHE_STATS + fContext->fResourceCache->dumpStatsKeyValuePairs(keys, values); +#endif +} + +void GrContextPriv::printCacheStats() const { + SkString out; + this->dumpCacheStats(&out); + SkDebugf("%s", out.c_str()); +} + +void GrContextPriv::dumpGpuStats(SkString* out) const { +#if GR_GPU_STATS + return fContext->fGpu->stats()->dump(out); +#endif +} + +void GrContextPriv::dumpGpuStatsKeyValuePairs(SkTArray* keys, + SkTArray* values) const { +#if GR_GPU_STATS + return fContext->fGpu->stats()->dumpKeyValuePairs(keys, values); +#endif +} + +void GrContextPriv::printGpuStats() const { + SkString out; + this->dumpGpuStats(&out); + SkDebugf("%s", out.c_str()); +} + +void GrContextPriv::testingOnly_setTextBlobCacheLimit(size_t bytes) { + fContext->priv().getTextBlobCache()->setBudget(bytes); +} + +sk_sp GrContextPriv::testingOnly_getFontAtlasImage(GrMaskFormat format, unsigned int index) { + auto atlasManager = this->getAtlasManager(); + if (!atlasManager) { + return nullptr; + } + + unsigned int numActiveProxies; + const sk_sp* proxies = atlasManager->getProxies(format, &numActiveProxies); + if (index >= numActiveProxies || !proxies || !proxies[index]) { + return nullptr; + } + + SkASSERT(proxies[index]->priv().isExact()); + sk_sp image(new SkImage_Gpu(sk_ref_sp(fContext), kNeedNewImageUniqueID, + kPremul_SkAlphaType, proxies[index], nullptr)); + return image; +} + +void GrContextPriv::testingOnly_purgeAllUnlockedResources() { + fContext->fResourceCache->purgeAllUnlocked(); +} + +void GrContextPriv::testingOnly_flushAndRemoveOnFlushCallbackObject(GrOnFlushCallbackObject* cb) { + fContext->flush(); + fContext->drawingManager()->testingOnly_removeOnFlushCallbackObject(cb); +} +#endif + +bool GrContextPriv::validPMUPMConversionExists() { + ASSERT_SINGLE_OWNER + if (!fContext->fDidTestPMConversions) { + fContext->fPMUPMConversionsRoundTrip = + GrConfigConversionEffect::TestForPreservingPMConversions(fContext); + fContext->fDidTestPMConversions = true; + } + + // The PM<->UPM tests fail or succeed together so we only need to check one. + return fContext->fPMUPMConversionsRoundTrip; +} + +std::unique_ptr GrContextPriv::createPMToUPMEffect( + std::unique_ptr fp) { + ASSERT_SINGLE_OWNER + // We should have already called this->priv().validPMUPMConversionExists() in this case + SkASSERT(fContext->fDidTestPMConversions); + // ...and it should have succeeded + SkASSERT(this->validPMUPMConversionExists()); + + return GrConfigConversionEffect::Make(std::move(fp), PMConversion::kToUnpremul); +} + +std::unique_ptr GrContextPriv::createUPMToPMEffect( + std::unique_ptr fp) { + ASSERT_SINGLE_OWNER + // We should have already called this->priv().validPMUPMConversionExists() in this case + SkASSERT(fContext->fDidTestPMConversions); + // ...and it should have succeeded + SkASSERT(this->validPMUPMConversionExists()); + + return GrConfigConversionEffect::Make(std::move(fp), PMConversion::kToPremul); +} diff --git a/gfx/skia/skia/src/gpu/GrContextPriv.h b/gfx/skia/skia/src/gpu/GrContextPriv.h new file mode 100644 index 0000000000..64522b065e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrContextPriv.h @@ -0,0 +1,288 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrContextPriv_DEFINED +#define GrContextPriv_DEFINED + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/text/GrAtlasManager.h" + +class GrBackendFormat; +class GrBackendRenderTarget; +class GrOpMemoryPool; +class GrOnFlushCallbackObject; +class GrSemaphore; +class GrSkSLFPFactory; +class GrSkSLFPFactoryCache; +class GrSurfaceProxy; +class GrTextureContext; + +class SkDeferredDisplayList; +class SkTaskGroup; + +/** Class that adds methods to GrContext that are only intended for use internal to Skia. + This class is purely a privileged window into GrContext. It should never have additional + data members or virtual methods. */ +class GrContextPriv { +public: + + // from GrContext_Base + uint32_t contextID() const { return fContext->contextID(); } + + bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); } + + const GrContextOptions& options() const { return fContext->options(); } + + const GrCaps* caps() const { return fContext->caps(); } + sk_sp refCaps() const; + + sk_sp fpFactoryCache(); + + GrImageContext* asImageContext() { return fContext->asImageContext(); } + GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); } + GrContext* asDirectContext() { return fContext->asDirectContext(); } + + // from GrImageContext + GrProxyProvider* proxyProvider() { return fContext->proxyProvider(); } + const GrProxyProvider* proxyProvider() const { return fContext->proxyProvider(); } + + bool abandoned() const { return fContext->abandoned(); } + + /** This is only useful for debug purposes */ + SkDEBUGCODE(GrSingleOwner* singleOwner() const { return fContext->singleOwner(); } ) + + // from GrRecordingContext + GrDrawingManager* drawingManager() { return fContext->drawingManager(); } + + sk_sp refOpMemoryPool(); + GrOpMemoryPool* opMemoryPool() { return fContext->opMemoryPool(); } + + GrStrikeCache* getGrStrikeCache() { return fContext->getGrStrikeCache(); } + GrTextBlobCache* getTextBlobCache() { return fContext->getTextBlobCache(); } + + /** + * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.) + * + * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to + * ensure its lifetime is tied to that of the context. + */ + void addOnFlushCallbackObject(GrOnFlushCallbackObject*); + + std::unique_ptr makeWrappedSurfaceContext(sk_sp, + GrColorType, + SkAlphaType, + sk_sp = nullptr, + const SkSurfaceProps* = nullptr); + + /** Create a new texture context backed by a deferred-style GrTextureProxy. */ + std::unique_ptr makeDeferredTextureContext( + SkBackingFit, + int width, + int height, + GrColorType, + SkAlphaType, + sk_sp, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin = kTopLeft_GrSurfaceOrigin, + SkBudgeted = SkBudgeted::kYes, + GrProtected = GrProtected::kNo); + + /* + * Create a new render target context backed by a deferred-style + * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for + * renderTargetContexts created via this entry point. + */ + std::unique_ptr makeDeferredRenderTargetContext( + SkBackingFit fit, + int width, + int height, + GrColorType, + sk_sp colorSpace, + int sampleCnt = 1, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin, + const SkSurfaceProps* surfaceProps = nullptr, + SkBudgeted = SkBudgeted::kYes, + GrProtected isProtected = GrProtected::kNo); + + /* + * This method will attempt to create a renderTargetContext that has, at least, the number of + * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be + * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA). + * SRGB-ness will be preserved. + */ + std::unique_ptr makeDeferredRenderTargetContextWithFallback( + SkBackingFit fit, + int width, + int height, + GrColorType, + sk_sp colorSpace, + int sampleCnt = 1, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin, + const SkSurfaceProps* surfaceProps = nullptr, + SkBudgeted budgeted = SkBudgeted::kYes, + GrProtected isProtected = GrProtected::kNo); + + GrAuditTrail* auditTrail() { return fContext->auditTrail(); } + + /** + * Create a GrContext without a resource cache + */ + static sk_sp MakeDDL(const sk_sp&); + + std::unique_ptr makeBackendTextureContext(const GrBackendTexture&, + GrSurfaceOrigin, + GrColorType, + SkAlphaType, + sk_sp); + + // These match the definitions in SkSurface & GrSurface.h, for whence they came + typedef void* ReleaseContext; + typedef void (*ReleaseProc)(ReleaseContext); + + std::unique_ptr makeBackendTextureRenderTargetContext( + const GrBackendTexture& tex, + GrSurfaceOrigin origin, + int sampleCnt, + GrColorType, + sk_sp colorSpace, + const SkSurfaceProps* = nullptr, + ReleaseProc = nullptr, + ReleaseContext = nullptr); + + std::unique_ptr makeBackendRenderTargetRenderTargetContext( + const GrBackendRenderTarget&, + GrSurfaceOrigin origin, + GrColorType, + sk_sp colorSpace, + const SkSurfaceProps* = nullptr, + ReleaseProc = nullptr, + ReleaseContext = nullptr); + + std::unique_ptr makeBackendTextureAsRenderTargetRenderTargetContext( + const GrBackendTexture& tex, + GrSurfaceOrigin origin, + int sampleCnt, + GrColorType, + sk_sp colorSpace, + const SkSurfaceProps* = nullptr); + + std::unique_ptr makeVulkanSecondaryCBRenderTargetContext( + const SkImageInfo&, const GrVkDrawableInfo&, const SkSurfaceProps* = nullptr); + + /** + * Finalizes all pending reads and writes to the surfaces and also performs an MSAA resolves + * if necessary. The GrSurfaceProxy array is treated as a hint. If it is supplied the context + * will guarantee that the draws required for those proxies are flushed but it could do more. + * If no array is provided then all current work will be flushed. + * + * It is not necessary to call this before reading the render target via Skia/GrContext. + * GrContext will detect when it must perform a resolve before reading pixels back from the + * surface or using it as a texture. + */ + GrSemaphoresSubmitted flushSurfaces(GrSurfaceProxy*[], int numProxies, const GrFlushInfo&); + + /** Version of above that flushes for a single proxy and uses a default GrFlushInfo. Null is + * allowed. */ + void flushSurface(GrSurfaceProxy*); + + /** + * Returns true if createPMToUPMEffect and createUPMToPMEffect will succeed. In other words, + * did we find a pair of round-trip preserving conversion effects? + */ + bool validPMUPMConversionExists(); + + /** + * These functions create premul <-> unpremul effects, using the specialized round-trip effects + * from GrConfigConversionEffect. + */ + std::unique_ptr createPMToUPMEffect(std::unique_ptr); + std::unique_ptr createUPMToPMEffect(std::unique_ptr); + + SkTaskGroup* getTaskGroup() { return fContext->fTaskGroup.get(); } + + GrResourceProvider* resourceProvider() { return fContext->fResourceProvider; } + const GrResourceProvider* resourceProvider() const { return fContext->fResourceProvider; } + + GrResourceCache* getResourceCache() { return fContext->fResourceCache; } + + GrGpu* getGpu() { return fContext->fGpu.get(); } + const GrGpu* getGpu() const { return fContext->fGpu.get(); } + + // This accessor should only ever be called by the GrOpFlushState. + GrAtlasManager* getAtlasManager() { + return fContext->onGetAtlasManager(); + } + + void moveRenderTasksToDDL(SkDeferredDisplayList*); + void copyRenderTasksFromDDL(const SkDeferredDisplayList*, GrRenderTargetProxy* newDest); + + GrContextOptions::PersistentCache* getPersistentCache() { return fContext->fPersistentCache; } + GrContextOptions::ShaderErrorHandler* getShaderErrorHandler() const { + return fContext->fShaderErrorHandler; + } + + GrClientMappedBufferManager* clientMappedBufferManager() { + return fContext->fMappedBufferManager.get(); + } + +#if GR_TEST_UTILS + /** Reset GPU stats */ + void resetGpuStats() const ; + + /** Prints cache stats to the string if GR_CACHE_STATS == 1. */ + void dumpCacheStats(SkString*) const; + void dumpCacheStatsKeyValuePairs(SkTArray* keys, SkTArray* values) const; + void printCacheStats() const; + + /** Prints GPU stats to the string if GR_GPU_STATS == 1. */ + void dumpGpuStats(SkString*) const; + void dumpGpuStatsKeyValuePairs(SkTArray* keys, SkTArray* values) const; + void printGpuStats() const; + + /** Specify the TextBlob cache limit. If the current cache exceeds this limit it will purge. + this is for testing only */ + void testingOnly_setTextBlobCacheLimit(size_t bytes); + + /** Get pointer to atlas texture for given mask format. Note that this wraps an + actively mutating texture in an SkImage. This could yield unexpected results + if it gets cached or used more generally. */ + sk_sp testingOnly_getFontAtlasImage(GrMaskFormat format, unsigned int index = 0); + + /** + * Purge all the unlocked resources from the cache. + * This entry point is mainly meant for timing texture uploads + * and is not defined in normal builds of Skia. + */ + void testingOnly_purgeAllUnlockedResources(); + + void testingOnly_flushAndRemoveOnFlushCallbackObject(GrOnFlushCallbackObject*); +#endif + +private: + explicit GrContextPriv(GrContext* context) : fContext(context) {} + GrContextPriv(const GrContextPriv&); // unimpl + GrContextPriv& operator=(const GrContextPriv&); // unimpl + + // No taking addresses of this type. + const GrContextPriv* operator&() const; + GrContextPriv* operator&(); + + GrContext* fContext; + + friend class GrContext; // to construct/copy this type. +}; + +inline GrContextPriv GrContext::priv() { return GrContextPriv(this); } + +inline const GrContextPriv GrContext::priv() const { + return GrContextPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrContextThreadSafeProxy.cpp b/gfx/skia/skia/src/gpu/GrContextThreadSafeProxy.cpp new file mode 100644 index 0000000000..64c7bf6a4c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrContextThreadSafeProxy.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContextThreadSafeProxy.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" + +#include "include/core/SkSurfaceCharacterization.h" +#include "include/gpu/GrContext.h" +#include "src/gpu/GrBaseContextPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" +#include "src/image/SkSurface_Gpu.h" + +#ifdef SK_VULKAN +#include "src/gpu/vk/GrVkCaps.h" +#endif + +GrContextThreadSafeProxy::GrContextThreadSafeProxy(GrBackendApi backend, + const GrContextOptions& options, + uint32_t contextID) + : INHERITED(backend, options, contextID) { +} + +GrContextThreadSafeProxy::~GrContextThreadSafeProxy() = default; + +bool GrContextThreadSafeProxy::init(sk_sp caps, + sk_sp FPFactoryCache) { + return INHERITED::init(std::move(caps), std::move(FPFactoryCache)); +} + +SkSurfaceCharacterization GrContextThreadSafeProxy::createCharacterization( + size_t cacheMaxResourceBytes, + const SkImageInfo& ii, const GrBackendFormat& backendFormat, + int sampleCnt, GrSurfaceOrigin origin, + const SkSurfaceProps& surfaceProps, + bool isMipMapped, bool willUseGLFBO0, bool isTextureable, + GrProtected isProtected) { + if (!backendFormat.isValid()) { + return SkSurfaceCharacterization(); // return an invalid characterization + } + + SkASSERT(isTextureable || !isMipMapped); + + if (GrBackendApi::kOpenGL != backendFormat.backend() && willUseGLFBO0) { + // The willUseGLFBO0 flags can only be used for a GL backend. + return SkSurfaceCharacterization(); // return an invalid characterization + } + + if (!this->caps()->mipMapSupport()) { + isMipMapped = false; + } + + GrColorType grColorType = SkColorTypeToGrColorType(ii.colorType()); + + if (!this->caps()->areColorTypeAndFormatCompatible(grColorType, backendFormat)) { + return SkSurfaceCharacterization(); // return an invalid characterization + } + + if (!this->caps()->isFormatAsColorTypeRenderable(grColorType, backendFormat, sampleCnt)) { + return SkSurfaceCharacterization(); // return an invalid characterization + } + + sampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, backendFormat); + SkASSERT(sampleCnt); + + if (willUseGLFBO0 && isTextureable) { + return SkSurfaceCharacterization(); // return an invalid characterization + } + + if (isTextureable && !this->caps()->isFormatTexturable(backendFormat)) { + // Skia doesn't agree that this is textureable. + return SkSurfaceCharacterization(); // return an invalid characterization + } + + if (GrBackendApi::kVulkan == backendFormat.backend()) { + if (GrBackendApi::kVulkan != this->backend()) { + return SkSurfaceCharacterization(); // return an invalid characterization + } + +#ifdef SK_VULKAN + const GrVkCaps* vkCaps = (const GrVkCaps*) this->caps(); + + // The protection status of the characterization and the context need to match + if (isProtected != GrProtected(vkCaps->supportsProtectedMemory())) { + return SkSurfaceCharacterization(); // return an invalid characterization + } +#endif + } + + return SkSurfaceCharacterization(sk_ref_sp(this), + cacheMaxResourceBytes, ii, backendFormat, + origin, sampleCnt, + SkSurfaceCharacterization::Textureable(isTextureable), + SkSurfaceCharacterization::MipMapped(isMipMapped), + SkSurfaceCharacterization::UsesGLFBO0(willUseGLFBO0), + SkSurfaceCharacterization::VulkanSecondaryCBCompatible(false), + isProtected, + surfaceProps); +} + +//////////////////////////////////////////////////////////////////////////////// +sk_sp GrContextThreadSafeProxyPriv::fpFactoryCache() { + return fProxy->fpFactoryCache(); +} + +sk_sp GrContextThreadSafeProxyPriv::Make( + GrBackendApi backend, + const GrContextOptions& options, + uint32_t contextID, + sk_sp caps, + sk_sp cache) { + sk_sp proxy(new GrContextThreadSafeProxy(backend, options, + contextID)); + + if (!proxy->init(std::move(caps), std::move(cache))) { + return nullptr; + } + return proxy; +} + diff --git a/gfx/skia/skia/src/gpu/GrContextThreadSafeProxyPriv.h b/gfx/skia/skia/src/gpu/GrContextThreadSafeProxyPriv.h new file mode 100644 index 0000000000..9f83e4bc38 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrContextThreadSafeProxyPriv.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrContextThreadSafeProxyPriv_DEFINED +#define GrContextThreadSafeProxyPriv_DEFINED + +#include "include/gpu/GrContextThreadSafeProxy.h" + +#include "src/gpu/GrCaps.h" + +/** + * Class that adds methods to GrContextThreadSafeProxy that are only intended for use internal to + * Skia. This class is purely a privileged window into GrContextThreadSafeProxy. It should never + * have additional data members or virtual methods. + */ +class GrContextThreadSafeProxyPriv { +public: + // from GrContext_Base + uint32_t contextID() const { return fProxy->contextID(); } + + bool matches(GrContext_Base* candidate) const { return fProxy->matches(candidate); } + + const GrContextOptions& options() const { return fProxy->options(); } + + const GrCaps* caps() const { return fProxy->caps(); } + sk_sp refCaps() const { return fProxy->refCaps(); } + + sk_sp fpFactoryCache(); + + // GrContextThreadSafeProxyPriv + static sk_sp Make(GrBackendApi, + const GrContextOptions&, + uint32_t contextID, + sk_sp, + sk_sp); + +private: + explicit GrContextThreadSafeProxyPriv(GrContextThreadSafeProxy* proxy) : fProxy(proxy) {} + GrContextThreadSafeProxyPriv(const GrContextThreadSafeProxy&) = delete; + GrContextThreadSafeProxyPriv& operator=(const GrContextThreadSafeProxyPriv&) = delete; + + // No taking addresses of this type. + const GrContextThreadSafeProxyPriv* operator&() const = delete; + GrContextThreadSafeProxyPriv* operator&() = delete; + + GrContextThreadSafeProxy* fProxy; + + friend class GrContextThreadSafeProxy; // to construct/copy this type. +}; + +inline GrContextThreadSafeProxyPriv GrContextThreadSafeProxy::priv() { + return GrContextThreadSafeProxyPriv(this); +} + +inline const GrContextThreadSafeProxyPriv GrContextThreadSafeProxy::priv() const { + return GrContextThreadSafeProxyPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrContext_Base.cpp b/gfx/skia/skia/src/gpu/GrContext_Base.cpp new file mode 100644 index 0000000000..1c79b32eca --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrContext_Base.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrContext_Base.h" + +#include "src/gpu/GrBaseContextPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" + +static int32_t next_id() { + static std::atomic nextID{1}; + int32_t id; + do { + id = nextID++; + } while (id == SK_InvalidGenID); + return id; +} + +GrContext_Base::GrContext_Base(GrBackendApi backend, + const GrContextOptions& options, + uint32_t contextID) + : fBackend(backend) + , fOptions(options) + , fContextID(SK_InvalidGenID == contextID ? next_id() : contextID) { +} + +GrContext_Base::~GrContext_Base() { } + +bool GrContext_Base::init(sk_sp caps, sk_sp FPFactoryCache) { + SkASSERT(caps && FPFactoryCache); + + fCaps = caps; + fFPFactoryCache = FPFactoryCache; + return true; +} + +const GrCaps* GrContext_Base::caps() const { return fCaps.get(); } +sk_sp GrContext_Base::refCaps() const { return fCaps; } + +sk_sp GrContext_Base::fpFactoryCache() { return fFPFactoryCache; } + +GrBackendFormat GrContext_Base::defaultBackendFormat(SkColorType skColorType, + GrRenderable renderable) const { + const GrCaps* caps = this->caps(); + + GrColorType grColorType = SkColorTypeToGrColorType(skColorType); + + GrBackendFormat format = caps->getDefaultBackendFormat(grColorType, renderable); + if (!format.isValid()) { + return GrBackendFormat(); + } + + SkASSERT(caps->isFormatTexturableAndUploadable(grColorType, format)); + SkASSERT(renderable == GrRenderable::kNo || + caps->isFormatAsColorTypeRenderable(grColorType, format)); + + return format; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +sk_sp GrBaseContextPriv::refCaps() const { + return fContext->refCaps(); +} + +sk_sp GrBaseContextPriv::fpFactoryCache() { + return fContext->fpFactoryCache(); +} diff --git a/gfx/skia/skia/src/gpu/GrCoordTransform.h b/gfx/skia/skia/src/gpu/GrCoordTransform.h new file mode 100644 index 0000000000..2451363d38 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrCoordTransform.h @@ -0,0 +1,140 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCoordTransform_DEFINED +#define GrCoordTransform_DEFINED + +#include "include/core/SkMatrix.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureProxy.h" + +class GrTexture; + +/** + * A class representing a linear transformation of local coordinates. GrFragnentProcessors + * these transformations, and the GrGeometryProcessor implements the transformation. + */ +class GrCoordTransform { +public: + GrCoordTransform() + : fProxy(nullptr) + , fNormalize(false) + , fReverseY(false) + , fComputeInVertexShader(true) { + SkDEBUGCODE(fInProcessor = false); + } + + GrCoordTransform(const GrCoordTransform&) = default; + + /** + * Create a transformation that maps [0, 1] to a proxy's boundaries. The proxy origin also + * implies whether a y-reversal should be performed. + */ + GrCoordTransform(GrTextureProxy* proxy) { + SkASSERT(proxy); + SkDEBUGCODE(fInProcessor = false); + this->reset(SkMatrix::I(), proxy); + } + + /** + * Create a transformation from a matrix. The proxy origin also implies whether a y-reversal + * should be performed. + */ + GrCoordTransform(const SkMatrix& m, GrTextureProxy* proxy) { + SkASSERT(proxy); + SkDEBUGCODE(fInProcessor = false); + this->reset(m, proxy); + } + + /** + * Create a transformation that applies the matrix to a coord set. + */ + GrCoordTransform(const SkMatrix& m) { + SkDEBUGCODE(fInProcessor = false); + this->reset(m); + } + + GrCoordTransform& operator= (const GrCoordTransform& that) { + SkASSERT(!fInProcessor); + fMatrix = that.fMatrix; + fProxy = that.fProxy; + fNormalize = that.fNormalize; + fReverseY = that.fReverseY; + return *this; + } + + /** + * Access the matrix for editing. Note, this must be done before adding the transform to an + * effect, since effects are immutable. + */ + SkMatrix* accessMatrix() { + SkASSERT(!fInProcessor); + return &fMatrix; + } + + bool hasSameEffectAs(const GrCoordTransform& that) const { + if (fNormalize != that.fNormalize || + fReverseY != that.fReverseY || + !fMatrix.cheapEqualTo(that.fMatrix)) { + return false; + } + + if (fNormalize) { + if (fProxy->underlyingUniqueID() != that.fProxy->underlyingUniqueID()) { + return false; + } + } + + return true; + } + + const SkMatrix& getMatrix() const { return fMatrix; } + const GrTextureProxy* proxy() const { return fProxy; } + bool normalize() const { return fNormalize; } + bool reverseY() const { return fReverseY; } + + // This should only ever be called at flush time after the backing texture has been + // successfully instantiated + GrTexture* peekTexture() const { return fProxy->peekTexture(); } + + bool computeInVertexShader() const { return fComputeInVertexShader; } + + void setComputeInVertexShader(bool computeInVertexShader) { + fComputeInVertexShader = computeInVertexShader; + } + +private: + void reset(const SkMatrix& m, GrTextureProxy* proxy = nullptr) { + SkASSERT(!fInProcessor); + + fMatrix = m; + fProxy = proxy; + fNormalize = proxy && proxy->textureType() != GrTextureType::kRectangle; + fReverseY = proxy && kBottomLeft_GrSurfaceOrigin == proxy->origin(); + fComputeInVertexShader = true; + } + + // The textures' effect is to optionally normalize the final matrix, so a blind + // equality check could be misleading + bool operator==(const GrCoordTransform& that) const; + bool operator!=(const GrCoordTransform& that) const; + + SkMatrix fMatrix; + const GrTextureProxy* fProxy; + bool fNormalize; + bool fReverseY; + bool fComputeInVertexShader; + +#ifdef SK_DEBUG +public: + void setInProcessor() const { fInProcessor = true; } +private: + mutable bool fInProcessor; +#endif +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrCopyRenderTask.cpp b/gfx/skia/skia/src/gpu/GrCopyRenderTask.cpp new file mode 100644 index 0000000000..23e8086d3f --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrCopyRenderTask.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrCopyRenderTask.h" + +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceAllocator.h" + +sk_sp GrCopyRenderTask::Make(sk_sp srcProxy, + const SkIRect& srcRect, + sk_sp dstProxy, + const SkIPoint& dstPoint, + const GrCaps* caps) { + SkASSERT(dstProxy); + SkASSERT(srcProxy); + SkIRect clippedSrcRect; + SkIPoint clippedDstPoint; + // If the rect is outside the srcProxy or dstProxy then we've already succeeded. + if (!GrClipSrcRectAndDstPoint(dstProxy->isize(), srcProxy->isize(), srcRect, dstPoint, + &clippedSrcRect, &clippedDstPoint)) { + return nullptr; + } + + if (caps->isFormatCompressed(dstProxy->backendFormat())) { + return nullptr; + } + + SkASSERT(dstProxy->origin() == srcProxy->origin()); + if (srcProxy->origin() == kBottomLeft_GrSurfaceOrigin) { + int rectHeight = clippedSrcRect.height(); + clippedSrcRect.fTop = srcProxy->height() - clippedSrcRect.fBottom; + clippedSrcRect.fBottom = clippedSrcRect.fTop + rectHeight; + clippedDstPoint.fY = dstProxy->height() - clippedDstPoint.fY - rectHeight; + } + + sk_sp task(new GrCopyRenderTask( + std::move(srcProxy), clippedSrcRect, std::move(dstProxy), clippedDstPoint)); + return task; +} + +GrCopyRenderTask::GrCopyRenderTask(sk_sp srcProxy, + const SkIRect& srcRect, + sk_sp dstProxy, + const SkIPoint& dstPoint) + : GrRenderTask(std::move(dstProxy)) + , fSrcProxy(std::move(srcProxy)) + , fSrcRect(srcRect) + , fDstPoint(dstPoint) { + fTarget->setLastRenderTask(this); +} + +void GrCopyRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const { + // This renderTask doesn't have "normal" ops. In this case we still need to add an interval (so + // fEndOfOpsTaskOpIndices will remain in sync), so we create a fake op# to capture the fact that + // we read fSrcProxy and copy to fTarget. + alloc->addInterval(fSrcProxy.get(), alloc->curOp(), alloc->curOp(), + GrResourceAllocator::ActualUse::kYes); + alloc->addInterval(fTarget.get(), alloc->curOp(), alloc->curOp(), + GrResourceAllocator::ActualUse::kYes); + alloc->incOps(); +} + +bool GrCopyRenderTask::onExecute(GrOpFlushState* flushState) { + if (!fSrcProxy->isInstantiated() || !fTarget->isInstantiated()) { + return false; + } + GrSurface* srcSurface = fSrcProxy->peekSurface(); + GrSurface* dstSurface = fTarget->peekSurface(); + if (fSrcProxy->origin() == kBottomLeft_GrSurfaceOrigin) { + if (fSrcProxy->height() != srcSurface->height()) { + fSrcRect.offset(0, srcSurface->height() - fSrcProxy->height()); + } + if (fTarget->height() != dstSurface->height()) { + fDstPoint.fY = fDstPoint.fY + (dstSurface->height() - fTarget->height()); + } + } + return flushState->gpu()->copySurface(dstSurface, srcSurface, fSrcRect, fDstPoint); +} + diff --git a/gfx/skia/skia/src/gpu/GrCopyRenderTask.h b/gfx/skia/skia/src/gpu/GrCopyRenderTask.h new file mode 100644 index 0000000000..640bb1167c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrCopyRenderTask.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCopyRenderTask_DEFINED +#define GrCopyRenderTask_DEFINED + +#include "src/gpu/GrRenderTask.h" + +class GrCopyRenderTask final : public GrRenderTask { +public: + static sk_sp Make(sk_sp srcProxy, + const SkIRect& srcRect, + sk_sp dstProxy, + const SkIPoint& dstPoint, + const GrCaps*); + +private: + GrCopyRenderTask(sk_sp srcProxy, + const SkIRect& srcRect, + sk_sp dstProxy, + const SkIPoint& dstPoint); + + bool onIsUsed(GrSurfaceProxy* proxy) const override { + SkASSERT(proxy != fTarget.get()); // This case should be handled by GrRenderTask. + return proxy == fSrcProxy.get(); + } + // If instantiation failed, at flush time we simply will skip doing the copy. + void handleInternalAllocationFailure() override {} + void gatherProxyIntervals(GrResourceAllocator*) const override; + ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect* targetUpdateBounds) override { + targetUpdateBounds->setXYWH(fDstPoint.x(), fDstPoint.y(), fSrcRect.width(), + fSrcRect.height()); + return ExpectedOutcome::kTargetDirty; + } + bool onExecute(GrOpFlushState*) override; + +#ifdef SK_DEBUG + void visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const override { + fn(fSrcProxy.get(), GrMipMapped::kNo); + } +#endif + + sk_sp fSrcProxy; + SkIRect fSrcRect; + SkIPoint fDstPoint; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/GrCpuBuffer.h b/gfx/skia/skia/src/gpu/GrCpuBuffer.h new file mode 100644 index 0000000000..5149766676 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrCpuBuffer.h @@ -0,0 +1,45 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCpuBuffer_DEFINED +#define GrCpuBuffer_DEFINED + +#include "src/core/SkSafeMath.h" +#include "src/gpu/GrBuffer.h" +#include "src/gpu/GrNonAtomicRef.h" + +class GrCpuBuffer final : public GrNonAtomicRef, public GrBuffer { +public: + static sk_sp Make(size_t size) { + SkASSERT(size > 0); + SkSafeMath sm; + size_t combinedSize = sm.add(sizeof(GrCpuBuffer), size); + if (!sm.ok()) { + SK_ABORT("Buffer size is too big."); + } + auto mem = ::operator new(combinedSize); + return sk_sp(new (mem) GrCpuBuffer((char*)mem + sizeof(GrCpuBuffer), size)); + } + + // TODO(b/30449950): use sized delete once P0722R3 is available + static void operator delete(void* p) { ::operator delete(p); } + + void ref() const override { GrNonAtomicRef::ref(); } + void unref() const override { GrNonAtomicRef::unref(); } + size_t size() const override { return fSize; } + bool isCpuBuffer() const override { return true; } + + char* data() { return reinterpret_cast(fData); } + const char* data() const { return reinterpret_cast(fData); } + +private: + GrCpuBuffer(void* data, size_t size) : fData(data), fSize(size) {} + void* fData; + size_t fSize; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDDLContext.cpp b/gfx/skia/skia/src/gpu/GrDDLContext.cpp new file mode 100644 index 0000000000..4d14504d4e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDDLContext.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" + +/** + * The DDL Context is the one in effect during DDL Recording. It isn't backed by a GrGPU and + * cannot allocate any GPU resources. + */ +class GrDDLContext : public GrContext { +public: + GrDDLContext(sk_sp proxy) + : INHERITED(proxy->backend(), proxy->priv().options(), proxy->priv().contextID()) { + fThreadSafeProxy = std::move(proxy); + } + + ~GrDDLContext() override { } + + void abandonContext() override { + SkASSERT(0); // abandoning in a DDL Recorder doesn't make a whole lot of sense + INHERITED::abandonContext(); + } + + void releaseResourcesAndAbandonContext() override { + SkASSERT(0); // abandoning in a DDL Recorder doesn't make a whole lot of sense + INHERITED::releaseResourcesAndAbandonContext(); + } + + void freeGpuResources() override { + SkASSERT(0); // freeing resources in a DDL Recorder doesn't make a whole lot of sense + INHERITED::freeGpuResources(); + } + +protected: + // TODO: Here we're pretending this isn't derived from GrContext. Switch this to be derived from + // GrRecordingContext! + GrContext* asDirectContext() override { return nullptr; } + + bool init(sk_sp caps, sk_sp FPFactoryCache) override { + SkASSERT(caps && FPFactoryCache); + SkASSERT(fThreadSafeProxy); // should've been set in the ctor + + if (!INHERITED::init(std::move(caps), std::move(FPFactoryCache))) { + return false; + } + + // DDL contexts/drawing managers always sort the oplists and attempt to reduce opsTask + // splitting. + this->setupDrawingManager(true, true); + + SkASSERT(this->caps()); + + return true; + } + + GrAtlasManager* onGetAtlasManager() override { + SkASSERT(0); // the DDL Recorders should never invoke this + return nullptr; + } + +private: + typedef GrContext INHERITED; +}; + +sk_sp GrContextPriv::MakeDDL(const sk_sp& proxy) { + sk_sp context(new GrDDLContext(proxy)); + + if (!context->init(proxy->priv().refCaps(), proxy->priv().fpFactoryCache())) { + return nullptr; + } + return context; +} diff --git a/gfx/skia/skia/src/gpu/GrDataUtils.cpp b/gfx/skia/skia/src/gpu/GrDataUtils.cpp new file mode 100644 index 0000000000..6265aaec28 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDataUtils.cpp @@ -0,0 +1,616 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrDataUtils.h" + +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkTraceEvent.h" +#include "src/core/SkUtils.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrImageInfo.h" + +struct ETC1Block { + uint32_t fHigh; + uint32_t fLow; +}; + +static const int kNumModifierTables = 8; +static const int kNumPixelIndices = 4; + +// The index of each row in this table is the ETC1 table codeword +// The index of each column in this table is the ETC1 pixel index value +static const int kModifierTables[kNumModifierTables][kNumPixelIndices] = { + /* 0 */ { 2, 8, -2, -8 }, + /* 1 */ { 5, 17, -5, -17 }, + /* 2 */ { 9, 29, -9, -29 }, + /* 3 */ { 13, 42, -13, -42 }, + /* 4 */ { 18, 60, -18, -60 }, + /* 5 */ { 24, 80, -24, -80 }, + /* 6 */ { 33, 106, -33, -106 }, + /* 7 */ { 47, 183, -47, -183 } +}; + +static inline int convert_5To8(int b) { + int c = b & 0x1f; + return (c << 3) | (c >> 2); +} + +// Evaluate one of the entries in 'kModifierTables' to see how close it can get (r8,g8,b8) to +// the original color (rOrig, gOrib, bOrig). +static int test_table_entry(int rOrig, int gOrig, int bOrig, + int r8, int g8, int b8, + int table, int offset) { + SkASSERT(0 <= table && table < 8); + SkASSERT(0 <= offset && offset < 4); + + r8 = SkTPin(r8 + kModifierTables[table][offset], 0, 255); + g8 = SkTPin(g8 + kModifierTables[table][offset], 0, 255); + b8 = SkTPin(b8 + kModifierTables[table][offset], 0, 255); + + return SkTAbs(rOrig - r8) + SkTAbs(gOrig - g8) + SkTAbs(bOrig - b8); +} + +// Create an ETC1 compressed block that is filled with 'col' +static void create_etc1_block(SkColor col, ETC1Block* block) { + block->fHigh = 0; + block->fLow = 0; + + int rOrig = SkColorGetR(col); + int gOrig = SkColorGetG(col); + int bOrig = SkColorGetB(col); + + int r5 = SkMulDiv255Round(31, rOrig); + int g5 = SkMulDiv255Round(31, gOrig); + int b5 = SkMulDiv255Round(31, bOrig); + + int r8 = convert_5To8(r5); + int g8 = convert_5To8(g5); + int b8 = convert_5To8(b5); + + // We always encode solid color textures as 555 + zero diffs + block->fHigh |= (r5 << 27) | (g5 << 19) | (b5 << 11) | 0x2; + + int bestTableIndex = 0, bestPixelIndex = 0; + int bestSoFar = 1024; + for (int tableIndex = 0; tableIndex < kNumModifierTables; ++tableIndex) { + for (int pixelIndex = 0; pixelIndex < kNumPixelIndices; ++pixelIndex) { + int score = test_table_entry(rOrig, gOrig, bOrig, r8, g8, b8, + tableIndex, pixelIndex); + + if (bestSoFar > score) { + bestSoFar = score; + bestTableIndex = tableIndex; + bestPixelIndex = pixelIndex; + } + } + } + + block->fHigh |= (bestTableIndex << 5) | (bestTableIndex << 2); + + for (int i = 0; i < 16; ++i) { + block->fLow |= bestPixelIndex << 2*i; + } +} + +static int num_ETC1_blocks_w(int w) { + if (w < 4) { + w = 1; + } else { + SkASSERT((w & 3) == 0); + w >>= 2; + } + return w; +} + +static int num_ETC1_blocks(int w, int h) { + w = num_ETC1_blocks_w(w); + + if (h < 4) { + h = 1; + } else { + SkASSERT((h & 3) == 0); + h >>= 2; + } + + return w * h; +} + +size_t GrCompressedDataSize(SkImage::CompressionType type, int width, int height) { + switch (type) { + case SkImage::kETC1_CompressionType: + int numBlocks = num_ETC1_blocks(width, height); + return numBlocks * sizeof(ETC1Block); + } + SK_ABORT("Unexpected compression type"); +} + +size_t GrCompressedRowBytes(SkImage::CompressionType type, int width) { + switch (type) { + case SkImage::kETC1_CompressionType: + int numBlocksWidth = num_ETC1_blocks_w(width); + return numBlocksWidth * sizeof(ETC1Block); + } + SK_ABORT("Unexpected compression type"); +} + +// Fill in 'dest' with ETC1 blocks derived from 'colorf' +static void fillin_ETC1_with_color(int width, int height, const SkColor4f& colorf, void* dest) { + SkColor color = colorf.toSkColor(); + + ETC1Block block; + create_etc1_block(color, &block); + + int numBlocks = num_ETC1_blocks(width, height); + + for (int i = 0; i < numBlocks; ++i) { + ((ETC1Block*)dest)[i] = block; + } +} + +// Fill in the width x height 'dest' with the munged version of 'colorf' that matches 'config' +static bool fill_buffer_with_color(GrColorType colorType, int width, int height, + const SkColor4f& colorf, void* dest) { + GrColor color = colorf.toBytes_RGBA(); + + uint8_t r = GrColorUnpackR(color); + uint8_t g = GrColorUnpackG(color); + uint8_t b = GrColorUnpackB(color); + uint8_t a = GrColorUnpackA(color); + + switch (colorType) { + case GrColorType::kAlpha_8: { + memset(dest, a, width * height); + break; + } + case GrColorType::kGray_8: { + uint8_t gray8 = SkComputeLuminance(r, g, b); + + memset(dest, gray8, width * height); + break; + } + case GrColorType::kBGR_565: { + uint16_t rgb565 = SkPack888ToRGB16(r, g, b); + + sk_memset16((uint16_t*) dest, rgb565, width * height); + break; + } + case GrColorType::kABGR_4444: { + uint8_t r4 = (r >> 4) & 0xF; + uint8_t g4 = (g >> 4) & 0xF; + uint8_t b4 = (b >> 4) & 0xF; + uint8_t a4 = (a >> 4) & 0xF; + + uint16_t rgba4444 = r4 << SK_R4444_SHIFT | g4 << SK_G4444_SHIFT | + b4 << SK_B4444_SHIFT | a4 << SK_A4444_SHIFT; + + sk_memset16((uint16_t*) dest, rgba4444, width * height); + break; + } + case GrColorType::kRGBA_8888: { + sk_memset32((uint32_t *) dest, color, width * height); + break; + } + case GrColorType::kRGB_888x: { + GrColor opaque = GrColorPackRGBA(r, g, b, 0xFF); + + sk_memset32((uint32_t *) dest, opaque, width * height); + break; + } + case GrColorType::kRG_88: { + uint16_t rg88 = (g << 8) | r; + + sk_memset16((uint16_t*) dest, rg88, width * height); + break; + } + case GrColorType::kBGRA_8888: { + GrColor swizzled = GrColorPackRGBA(b, g, r, a); + + sk_memset32((uint32_t *) dest, swizzled, width * height); + break; + } + case GrColorType::kRGBA_8888_SRGB: { + sk_memset32((uint32_t *) dest, color, width * height); + break; + } + case GrColorType::kRGBA_1010102: { + uint32_t r10 = SkScalarRoundToInt(colorf.fR * 1023.0f); + uint32_t g10 = SkScalarRoundToInt(colorf.fG * 1023.0f); + uint32_t b10 = SkScalarRoundToInt(colorf.fB * 1023.0f); + uint8_t a2 = SkScalarRoundToInt(colorf.fA * 3.0f); + + uint32_t rgba1010102 = a2 << 30 | b10 << 20 | g10 << 10 | r10; + + sk_memset32((uint32_t *) dest, rgba1010102, width * height); + break; + } + case GrColorType::kAlpha_F16: { + SkHalf alphaHalf = SkFloatToHalf(colorf.fA); + + sk_memset16((uint16_t *) dest, alphaHalf, width * height); + break; + } + case GrColorType::kRGBA_F16_Clamped: // fall through + case GrColorType::kRGBA_F16: { + uint64_t rHalf = SkFloatToHalf(colorf.fR); + uint64_t gHalf = SkFloatToHalf(colorf.fG); + uint64_t bHalf = SkFloatToHalf(colorf.fB); + uint64_t aHalf = SkFloatToHalf(colorf.fA); + + uint64_t rgbaHalf = (aHalf << 48) | (bHalf << 32) | (gHalf << 16) | rHalf; + + sk_memset64((uint64_t *) dest, rgbaHalf, width * height); + break; + } + case GrColorType::kAlpha_16: { + uint16_t a16 = SkScalarRoundToInt(colorf.fA * 65535.0f); + sk_memset16((uint16_t*) dest, a16, width * height); + break; + } + case GrColorType::kRG_1616: { + uint32_t r16 = SkScalarRoundToInt(colorf.fR * 65535.0f); + uint32_t g16 = SkScalarRoundToInt(colorf.fG * 65535.0f); + + uint32_t rg1616 = (g16 << 16) | r16; + + sk_memset32((uint32_t*) dest, rg1616, width * height); + break; + } + case GrColorType::kRGBA_16161616: { + uint64_t r16 = SkScalarRoundToInt(colorf.fR * 65535.0f); + uint64_t g16 = SkScalarRoundToInt(colorf.fG * 65535.0f); + uint64_t b16 = SkScalarRoundToInt(colorf.fB * 65535.0f); + uint64_t a16 = SkScalarRoundToInt(colorf.fA * 65535.0f); + + uint64_t rgba16161616 = (a16 << 48) | (b16 << 32) | (g16 << 16) | r16; + sk_memset64((uint64_t*) dest, rgba16161616, width * height); + break; + } + case GrColorType::kRG_F16: { + uint32_t rHalf = SkFloatToHalf(colorf.fR); + uint32_t gHalf = SkFloatToHalf(colorf.fG); + + uint32_t rgHalf = (gHalf << 16) | rHalf; + + sk_memset32((uint32_t *) dest, rgHalf, width * height); + break; + } + default: + return false; + break; + } + + return true; +} + +size_t GrComputeTightCombinedBufferSize(size_t bytesPerPixel, int baseWidth, int baseHeight, + SkTArray* individualMipOffsets, int mipLevelCount) { + SkASSERT(individualMipOffsets && !individualMipOffsets->count()); + SkASSERT(mipLevelCount >= 1); + + individualMipOffsets->push_back(0); + + size_t combinedBufferSize = baseWidth * bytesPerPixel * baseHeight; + int currentWidth = baseWidth; + int currentHeight = baseHeight; + + // The Vulkan spec for copying a buffer to an image requires that the alignment must be at + // least 4 bytes and a multiple of the bytes per pixel of the image config. + SkASSERT(bytesPerPixel == 1 || bytesPerPixel == 2 || bytesPerPixel == 3 || + bytesPerPixel == 4 || bytesPerPixel == 8 || bytesPerPixel == 16); + int desiredAlignment = (bytesPerPixel == 3) ? 12 : (bytesPerPixel > 4 ? bytesPerPixel : 4); + + for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; ++currentMipLevel) { + currentWidth = SkTMax(1, currentWidth / 2); + currentHeight = SkTMax(1, currentHeight / 2); + + size_t trimmedSize = currentWidth * bytesPerPixel * currentHeight; + const size_t alignmentDiff = combinedBufferSize % desiredAlignment; + if (alignmentDiff != 0) { + combinedBufferSize += desiredAlignment - alignmentDiff; + } + SkASSERT((0 == combinedBufferSize % 4) && (0 == combinedBufferSize % bytesPerPixel)); + + individualMipOffsets->push_back(combinedBufferSize); + combinedBufferSize += trimmedSize; + } + + SkASSERT(individualMipOffsets->count() == mipLevelCount); + return combinedBufferSize; +} + +void GrFillInData(GrColorType colorType, int baseWidth, int baseHeight, + const SkTArray& individualMipOffsets, char* dstPixels, + const SkColor4f& colorf) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + int mipLevels = individualMipOffsets.count(); + + int currentWidth = baseWidth; + int currentHeight = baseHeight; + for (int currentMipLevel = 0; currentMipLevel < mipLevels; ++currentMipLevel) { + size_t offset = individualMipOffsets[currentMipLevel]; + + fill_buffer_with_color(colorType, currentWidth, currentHeight, colorf, + &(dstPixels[offset])); + currentWidth = SkTMax(1, currentWidth / 2); + currentHeight = SkTMax(1, currentHeight / 2); + } +} + +void GrFillInCompressedData(SkImage::CompressionType type, int baseWidth, int baseHeight, + char* dstPixels, const SkColor4f& colorf) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + int currentWidth = baseWidth; + int currentHeight = baseHeight; + if (SkImage::kETC1_CompressionType == type) { + fillin_ETC1_with_color(currentWidth, currentHeight, colorf, dstPixels); + } +} + +static GrSwizzle get_load_and_get_swizzle(GrColorType ct, SkRasterPipeline::StockStage* load, + bool* isNormalized, bool* isSRGB) { + GrSwizzle swizzle("rgba"); + *isNormalized = true; + *isSRGB = false; + switch (ct) { + case GrColorType::kAlpha_8: *load = SkRasterPipeline::load_a8; break; + case GrColorType::kAlpha_16: *load = SkRasterPipeline::load_a16; break; + case GrColorType::kBGR_565: *load = SkRasterPipeline::load_565; break; + case GrColorType::kABGR_4444: *load = SkRasterPipeline::load_4444; break; + case GrColorType::kRGBA_8888: *load = SkRasterPipeline::load_8888; break; + case GrColorType::kRG_88: *load = SkRasterPipeline::load_rg88; break; + case GrColorType::kRGBA_1010102: *load = SkRasterPipeline::load_1010102; break; + case GrColorType::kAlpha_F16: *load = SkRasterPipeline::load_af16; break; + case GrColorType::kRGBA_F16_Clamped: *load = SkRasterPipeline::load_f16; break; + case GrColorType::kRG_1616: *load = SkRasterPipeline::load_rg1616; break; + case GrColorType::kRGBA_16161616: *load = SkRasterPipeline::load_16161616; break; + + case GrColorType::kRGBA_8888_SRGB: *load = SkRasterPipeline::load_8888; + *isSRGB = true; + break; + case GrColorType::kRG_F16: *load = SkRasterPipeline::load_rgf16; + *isNormalized = false; + break; + case GrColorType::kRGBA_F16: *load = SkRasterPipeline::load_f16; + *isNormalized = false; + break; + case GrColorType::kRGBA_F32: *load = SkRasterPipeline::load_f32; + *isNormalized = false; + break; + case GrColorType::kAlpha_8xxx: *load = SkRasterPipeline::load_8888; + swizzle = GrSwizzle("000r"); + break; + case GrColorType::kAlpha_F32xxx: *load = SkRasterPipeline::load_f32; + swizzle = GrSwizzle("000r"); + break; + case GrColorType::kGray_8xxx: *load = SkRasterPipeline::load_8888; + swizzle = GrSwizzle("rrr1"); + break; + case GrColorType::kGray_8: *load = SkRasterPipeline::load_a8; + swizzle = GrSwizzle("aaa1"); + break; + case GrColorType::kBGRA_8888: *load = SkRasterPipeline::load_8888; + swizzle = GrSwizzle("bgra"); + break; + case GrColorType::kRGB_888x: *load = SkRasterPipeline::load_8888; + swizzle = GrSwizzle("rgb1"); + break; + + case GrColorType::kUnknown: + SK_ABORT("unexpected CT"); + } + return swizzle; +} + +static GrSwizzle get_dst_swizzle_and_store(GrColorType ct, SkRasterPipeline::StockStage* store, + bool* isNormalized, bool* isSRGB) { + GrSwizzle swizzle("rgba"); + *isNormalized = true; + *isSRGB = false; + switch (ct) { + case GrColorType::kAlpha_8: *store = SkRasterPipeline::store_a8; break; + case GrColorType::kAlpha_16: *store = SkRasterPipeline::store_a16; break; + case GrColorType::kBGR_565: *store = SkRasterPipeline::store_565; break; + case GrColorType::kABGR_4444: *store = SkRasterPipeline::store_4444; break; + case GrColorType::kRGBA_8888: *store = SkRasterPipeline::store_8888; break; + case GrColorType::kRG_88: *store = SkRasterPipeline::store_rg88; break; + case GrColorType::kRGBA_1010102: *store = SkRasterPipeline::store_1010102; break; + case GrColorType::kRGBA_F16_Clamped: *store = SkRasterPipeline::store_f16; break; + case GrColorType::kRG_1616: *store = SkRasterPipeline::store_rg1616; break; + case GrColorType::kRGBA_16161616: *store = SkRasterPipeline::store_16161616; break; + + case GrColorType::kRGBA_8888_SRGB: *store = SkRasterPipeline::store_8888; + *isSRGB = true; + break; + case GrColorType::kRG_F16: *store = SkRasterPipeline::store_rgf16; + *isNormalized = false; + break; + case GrColorType::kAlpha_F16: *store = SkRasterPipeline::store_af16; + *isNormalized = false; + break; + case GrColorType::kRGBA_F16: *store = SkRasterPipeline::store_f16; + *isNormalized = false; + break; + case GrColorType::kRGBA_F32: *store = SkRasterPipeline::store_f32; + *isNormalized = false; + break; + case GrColorType::kAlpha_8xxx: *store = SkRasterPipeline::store_8888; + swizzle = GrSwizzle("a000"); + break; + case GrColorType::kAlpha_F32xxx: *store = SkRasterPipeline::store_f32; + swizzle = GrSwizzle("a000"); + break; + case GrColorType::kBGRA_8888: swizzle = GrSwizzle("bgra"); + *store = SkRasterPipeline::store_8888; + break; + case GrColorType::kRGB_888x: swizzle = GrSwizzle("rgb1"); + *store = SkRasterPipeline::store_8888; + break; + + case GrColorType::kGray_8: // not currently supported as output + case GrColorType::kGray_8xxx: // not currently supported as output + case GrColorType::kUnknown: + SK_ABORT("unexpected CT"); + } + return swizzle; +} + +static inline void append_clamp_gamut(SkRasterPipeline* pipeline) { + // SkRasterPipeline may not know our color type and also doesn't like caller to directly + // append clamp_gamut. Fake it out. + static SkImageInfo fakeII = SkImageInfo::MakeN32Premul(1, 1); + pipeline->append_gamut_clamp_if_normalized(fakeII); +} + +bool GrConvertPixels(const GrImageInfo& dstInfo, void* dst, size_t dstRB, + const GrImageInfo& srcInfo, const void* src, size_t srcRB, + bool flipY) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + if (!srcInfo.isValid() || !dstInfo.isValid()) { + return false; + } + if (!src || !dst) { + return false; + } + if (dstInfo.width() != srcInfo.width() || srcInfo.height() != dstInfo.height()) { + return false; + } + if (GrColorTypeComponentFlags(dstInfo.colorType()) & kGray_SkColorTypeComponentFlag) { + // We don't currently support conversion to Gray. + return false; + } + if (dstRB < dstInfo.minRowBytes() || srcRB < srcInfo.minRowBytes()) { + return false; + } + + size_t srcBpp = srcInfo.bpp(); + size_t dstBpp = dstInfo.bpp(); + + // SkRasterPipeline operates on row-pixels not row-bytes. + SkASSERT(dstRB % dstBpp == 0); + SkASSERT(srcRB % srcBpp == 0); + + bool premul = srcInfo.alphaType() == kUnpremul_SkAlphaType && + dstInfo.alphaType() == kPremul_SkAlphaType; + bool unpremul = srcInfo.alphaType() == kPremul_SkAlphaType && + dstInfo.alphaType() == kUnpremul_SkAlphaType; + bool alphaOrCSConversion = + premul || unpremul || !SkColorSpace::Equals(srcInfo.colorSpace(), dstInfo.colorSpace()); + + if (srcInfo.colorType() == dstInfo.colorType() && !alphaOrCSConversion) { + size_t tightRB = dstBpp * dstInfo.width(); + if (flipY) { + dst = static_cast(dst) + dstRB * (dstInfo.height() - 1); + for (int y = 0; y < dstInfo.height(); ++y) { + memcpy(dst, src, tightRB); + src = static_cast(src) + srcRB; + dst = static_cast< char*>(dst) - dstRB; + } + } else { + SkRectMemcpy(dst, dstRB, src, srcRB, tightRB, srcInfo.height()); + } + return true; + } + + SkRasterPipeline::StockStage load; + bool srcIsNormalized; + bool srcIsSRGB; + auto loadSwizzle = get_load_and_get_swizzle(srcInfo.colorType(), &load, &srcIsNormalized, + &srcIsSRGB); + + SkRasterPipeline::StockStage store; + bool dstIsNormalized; + bool dstIsSRGB; + auto storeSwizzle = get_dst_swizzle_and_store(dstInfo.colorType(), &store, &dstIsNormalized, + &dstIsSRGB); + + bool clampGamut; + SkTLazy steps; + GrSwizzle loadStoreSwizzle; + if (alphaOrCSConversion) { + steps.init(srcInfo.colorSpace(), srcInfo.alphaType(), + dstInfo.colorSpace(), dstInfo.alphaType()); + clampGamut = dstIsNormalized && dstInfo.alphaType() == kPremul_SkAlphaType; + } else { + clampGamut = + dstIsNormalized && !srcIsNormalized && dstInfo.alphaType() == kPremul_SkAlphaType; + if (!clampGamut) { + loadStoreSwizzle = GrSwizzle::Concat(loadSwizzle, storeSwizzle); + } + } + int cnt = 1; + int height = srcInfo.height(); + SkRasterPipeline_MemoryCtx srcCtx{const_cast(src), SkToInt(srcRB / srcBpp)}, + dstCtx{ dst , SkToInt(dstRB / dstBpp)}; + + if (flipY) { + // It *almost* works to point the src at the last row and negate the stride and run the + // whole rectangle. However, SkRasterPipeline::run()'s control loop uses size_t loop + // variables so it winds up relying on unsigned overflow math. It works out in practice + // but UBSAN says "no!" as it's technically undefined and in theory a compiler could emit + // code that didn't do what is intended. So we go one row at a time. :( + srcCtx.pixels = static_cast(srcCtx.pixels) + srcRB * (height - 1); + std::swap(cnt, height); + } + + bool hasConversion = alphaOrCSConversion || clampGamut; + + if (srcIsSRGB && dstIsSRGB && !hasConversion) { + // No need to convert from srgb if we are just going to immediately convert it back. + srcIsSRGB = dstIsSRGB = false; + } + + hasConversion = hasConversion || srcIsSRGB || dstIsSRGB; + + for (int i = 0; i < cnt; ++i) { + SkRasterPipeline_<256> pipeline; + pipeline.append(load, &srcCtx); + if (hasConversion) { + loadSwizzle.apply(&pipeline); + if (srcIsSRGB) { + pipeline.append(SkRasterPipeline::from_srgb); + } + if (alphaOrCSConversion) { + steps->apply(&pipeline, srcIsNormalized); + } + if (clampGamut) { + append_clamp_gamut(&pipeline); + } + // If we add support for storing to Gray we would add a luminance to alpha conversion + // here. We also wouldn't then need a to_srgb stage after since it would have not effect + // on the alpha channel. It would also mean we have an SRGB Gray color type which + // doesn't exist currently. + if (dstIsSRGB) { + pipeline.append(SkRasterPipeline::to_srgb); + } + storeSwizzle.apply(&pipeline); + } else { + loadStoreSwizzle.apply(&pipeline); + } + pipeline.append(store, &dstCtx); + pipeline.run(0, 0, srcInfo.width(), height); + srcCtx.pixels = static_cast(srcCtx.pixels) - srcRB; + dstCtx.pixels = static_cast(dstCtx.pixels) + dstRB; + } + return true; +} + +GrColorType SkColorTypeAndFormatToGrColorType(const GrCaps* caps, + SkColorType skCT, + const GrBackendFormat& format) { + GrColorType grCT = SkColorTypeToGrColorType(skCT); + // Until we support SRGB in the SkColorType we have to do this manual check here to make sure + // we use the correct GrColorType. + if (caps->isFormatSRGB(format)) { + if (grCT != GrColorType::kRGBA_8888) { + return GrColorType::kUnknown; + } + grCT = GrColorType::kRGBA_8888_SRGB; + } + return grCT; +} diff --git a/gfx/skia/skia/src/gpu/GrDataUtils.h b/gfx/skia/skia/src/gpu/GrDataUtils.h new file mode 100644 index 0000000000..b51a4ddf40 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDataUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDataUtils_DEFINED +#define GrDataUtils_DEFINED + +#include "include/core/SkColor.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrSwizzle.h" + +class GrImageInfo; + +size_t GrCompressedDataSize(SkImage::CompressionType, int w, int h); + +// Returns a value that can be used to set rowBytes for a transfer function. +size_t GrCompressedRowBytes(SkImage::CompressionType, int w); + +// Compute the size of the buffer required to hold all the mipLevels of the specified type +// of data when all rowBytes are tight. +// Note there may still be padding between the mipLevels to meet alignment requirements. +size_t GrComputeTightCombinedBufferSize(size_t bytesPerPixel, int baseWidth, int baseHeight, + SkTArray* individualMipOffsets, int mipLevelCount); + +void GrFillInData(GrColorType, int baseWidth, int baseHeight, + const SkTArray& individualMipOffsets, char* dest, const SkColor4f& color); + +void GrFillInCompressedData(SkImage::CompressionType, int width, int height, char* dest, + const SkColor4f& color); + +// Swizzle param is applied after loading and before converting from srcInfo to dstInfo. +bool GrConvertPixels(const GrImageInfo& dstInfo, void* dst, size_t dstRB, + const GrImageInfo& srcInfo, const void* src, size_t srcRB, + bool flipY = false); + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.cpp b/gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.cpp new file mode 100644 index 0000000000..09a6846649 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.cpp @@ -0,0 +1,363 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrDefaultGeoProcFactory.h" + +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLUtil.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +/* + * The default Geometry Processor simply takes position and multiplies it by the uniform view + * matrix. It also leaves coverage untouched. Behind the scenes, we may add per vertex color or + * local coords. + */ + +enum GPFlag { + kColorAttribute_GPFlag = 0x1, + kColorAttributeIsSkColor_GPFlag = 0x2, + kColorAttributeIsWide_GPFlag = 0x4, + kLocalCoordAttribute_GPFlag = 0x8, + kCoverageAttribute_GPFlag = 0x10, + kCoverageAttributeTweak_GPFlag = 0x20, +}; + +class DefaultGeoProc : public GrGeometryProcessor { +public: + static sk_sp Make(const GrShaderCaps* shaderCaps, + uint32_t gpTypeFlags, + const SkPMColor4f& color, + sk_sp colorSpaceXform, + const SkMatrix& viewMatrix, + const SkMatrix& localMatrix, + bool localCoordsWillBeRead, + uint8_t coverage) { + return sk_sp(new DefaultGeoProc( + shaderCaps, gpTypeFlags, color, std::move(colorSpaceXform), viewMatrix, localMatrix, + coverage, localCoordsWillBeRead)); + } + + const char* name() const override { return "DefaultGeometryProcessor"; } + + const SkPMColor4f& color() const { return fColor; } + bool hasVertexColor() const { return fInColor.isInitialized(); } + const SkMatrix& viewMatrix() const { return fViewMatrix; } + const SkMatrix& localMatrix() const { return fLocalMatrix; } + bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; } + uint8_t coverage() const { return fCoverage; } + bool hasVertexCoverage() const { return fInCoverage.isInitialized(); } + + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + GLSLProcessor() + : fViewMatrix(SkMatrix::InvalidMatrix()) + , fColor(SK_PMColor4fILLEGAL) + , fCoverage(0xff) {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const DefaultGeoProc& gp = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(gp); + + bool tweakAlpha = SkToBool(gp.fFlags & kCoverageAttributeTweak_GPFlag); + SkASSERT(!tweakAlpha || gp.hasVertexCoverage()); + + // Setup pass through color + if (gp.hasVertexColor() || tweakAlpha) { + GrGLSLVarying varying(kHalf4_GrSLType); + varyingHandler->addVarying("color", &varying); + + // There are several optional steps to process the color. Start with the attribute, + // or with uniform color (in the case of folding coverage into a uniform color): + if (gp.hasVertexColor()) { + vertBuilder->codeAppendf("half4 color = %s;", gp.fInColor.name()); + } else { + const char* colorUniformName; + fColorUniform = uniformHandler->addUniform(kVertex_GrShaderFlag, + kHalf4_GrSLType, + "Color", + &colorUniformName); + vertBuilder->codeAppendf("half4 color = %s;", colorUniformName); + } + + // For SkColor, do a red/blue swap, possible color space conversion, and premul + if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) { + vertBuilder->codeAppend("color = color.bgra;"); + + if (gp.fColorSpaceXform) { + fColorSpaceHelper.emitCode(uniformHandler, gp.fColorSpaceXform.get(), + kVertex_GrShaderFlag); + SkString xformedColor; + vertBuilder->appendColorGamutXform(&xformedColor, "color", + &fColorSpaceHelper); + vertBuilder->codeAppendf("color = %s;", xformedColor.c_str()); + } + + vertBuilder->codeAppend("color = half4(color.rgb * color.a, color.a);"); + } + + // Optionally fold coverage into alpha (color). + if (tweakAlpha) { + vertBuilder->codeAppendf("color = color * %s;", gp.fInCoverage.name()); + } + vertBuilder->codeAppendf("%s = color;\n", varying.vsOut()); + fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn()); + } else { + this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, + &fColorUniform); + } + + // Setup position + this->writeOutputPosition(vertBuilder, + uniformHandler, + gpArgs, + gp.fInPosition.name(), + gp.viewMatrix(), + &fViewMatrixUniform); + + if (gp.fInLocalCoords.isInitialized()) { + // emit transforms with explicit local coords + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + gp.fInLocalCoords.asShaderVar(), + gp.localMatrix(), + args.fFPCoordTransformHandler); + } else { + // emit transforms with position + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + gp.fInPosition.asShaderVar(), + gp.localMatrix(), + args.fFPCoordTransformHandler); + } + + // Setup coverage as pass through + if (gp.hasVertexCoverage() && !tweakAlpha) { + fragBuilder->codeAppendf("half alpha = 1.0;"); + varyingHandler->addPassThroughAttribute(gp.fInCoverage, "alpha"); + fragBuilder->codeAppendf("%s = half4(alpha);", args.fOutputCoverage); + } else if (gp.coverage() == 0xff) { + fragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage); + } else { + const char* fragCoverage; + fCoverageUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf_GrSLType, + "Coverage", + &fragCoverage); + fragBuilder->codeAppendf("%s = half4(%s);", args.fOutputCoverage, fragCoverage); + } + } + + static inline void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const DefaultGeoProc& def = gp.cast(); + uint32_t key = def.fFlags; + key |= (def.coverage() == 0xff) ? 0x80 : 0; + key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x100 : 0; + key |= ComputePosKey(def.viewMatrix()) << 20; + b->add32(key); + b->add32(GrColorSpaceXform::XformKey(def.fColorSpaceXform.get())); + } + + void setData(const GrGLSLProgramDataManager& pdman, + const GrPrimitiveProcessor& gp, + FPCoordTransformIter&& transformIter) override { + const DefaultGeoProc& dgp = gp.cast(); + + if (!dgp.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dgp.viewMatrix())) { + fViewMatrix = dgp.viewMatrix(); + float viewMatrix[3 * 3]; + GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix); + pdman.setMatrix3f(fViewMatrixUniform, viewMatrix); + } + + if (!dgp.hasVertexColor() && dgp.color() != fColor) { + pdman.set4fv(fColorUniform, 1, dgp.color().vec()); + fColor = dgp.color(); + } + + if (dgp.coverage() != fCoverage && !dgp.hasVertexCoverage()) { + pdman.set1f(fCoverageUniform, GrNormalizeByteToFloat(dgp.coverage())); + fCoverage = dgp.coverage(); + } + this->setTransformDataHelper(dgp.fLocalMatrix, pdman, &transformIter); + + fColorSpaceHelper.setData(pdman, dgp.fColorSpaceXform.get()); + } + + private: + SkMatrix fViewMatrix; + SkPMColor4f fColor; + uint8_t fCoverage; + UniformHandle fViewMatrixUniform; + UniformHandle fColorUniform; + UniformHandle fCoverageUniform; + GrGLSLColorSpaceXformHelper fColorSpaceHelper; + + typedef GrGLSLGeometryProcessor INHERITED; + }; + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLProcessor::GenKey(*this, caps, b); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new GLSLProcessor(); + } + +private: + DefaultGeoProc(const GrShaderCaps* shaderCaps, + uint32_t gpTypeFlags, + const SkPMColor4f& color, + sk_sp colorSpaceXform, + const SkMatrix& viewMatrix, + const SkMatrix& localMatrix, + uint8_t coverage, + bool localCoordsWillBeRead) + : INHERITED(kDefaultGeoProc_ClassID) + , fColor(color) + , fViewMatrix(viewMatrix) + , fLocalMatrix(localMatrix) + , fCoverage(coverage) + , fFlags(gpTypeFlags) + , fLocalCoordsWillBeRead(localCoordsWillBeRead) + , fColorSpaceXform(std::move(colorSpaceXform)) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + if (fFlags & kColorAttribute_GPFlag) { + fInColor = MakeColorAttribute("inColor", + SkToBool(fFlags & kColorAttributeIsWide_GPFlag)); + } + if (fFlags & kLocalCoordAttribute_GPFlag) { + fInLocalCoords = {"inLocalCoord", kFloat2_GrVertexAttribType, + kFloat2_GrSLType}; + } + if (fFlags & kCoverageAttribute_GPFlag) { + fInCoverage = {"inCoverage", kFloat_GrVertexAttribType, kHalf_GrSLType}; + } + this->setVertexAttributes(&fInPosition, 4); + } + + Attribute fInPosition; + Attribute fInColor; + Attribute fInLocalCoords; + Attribute fInCoverage; + SkPMColor4f fColor; + SkMatrix fViewMatrix; + SkMatrix fLocalMatrix; + uint8_t fCoverage; + uint32_t fFlags; + bool fLocalCoordsWillBeRead; + sk_sp fColorSpaceXform; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DefaultGeoProc); + +#if GR_TEST_UTILS +sk_sp DefaultGeoProc::TestCreate(GrProcessorTestData* d) { + uint32_t flags = 0; + if (d->fRandom->nextBool()) { + flags |= kColorAttribute_GPFlag; + } + if (d->fRandom->nextBool()) { + flags |= kColorAttributeIsSkColor_GPFlag; + } + if (d->fRandom->nextBool()) { + flags |= kColorAttributeIsWide_GPFlag; + } + if (d->fRandom->nextBool()) { + flags |= kCoverageAttribute_GPFlag; + if (d->fRandom->nextBool()) { + flags |= kCoverageAttributeTweak_GPFlag; + } + } + if (d->fRandom->nextBool()) { + flags |= kLocalCoordAttribute_GPFlag; + } + + return DefaultGeoProc::Make(d->caps()->shaderCaps(), + flags, + SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)), + GrTest::TestColorXform(d->fRandom), + GrTest::TestMatrix(d->fRandom), + GrTest::TestMatrix(d->fRandom), + d->fRandom->nextBool(), + GrRandomCoverage(d->fRandom)); +} +#endif + +sk_sp GrDefaultGeoProcFactory::Make(const GrShaderCaps* shaderCaps, + const Color& color, + const Coverage& coverage, + const LocalCoords& localCoords, + const SkMatrix& viewMatrix) { + uint32_t flags = 0; + if (Color::kPremulGrColorAttribute_Type == color.fType) { + flags |= kColorAttribute_GPFlag; + } else if (Color::kUnpremulSkColorAttribute_Type == color.fType) { + flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag; + } else if (Color::kPremulWideColorAttribute_Type == color.fType) { + flags |= kColorAttribute_GPFlag | kColorAttributeIsWide_GPFlag; + } + if (Coverage::kAttribute_Type == coverage.fType) { + flags |= kCoverageAttribute_GPFlag; + } else if (Coverage::kAttributeTweakAlpha_Type == coverage.fType) { + flags |= kCoverageAttribute_GPFlag | kCoverageAttributeTweak_GPFlag; + } + flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0; + + uint8_t inCoverage = coverage.fCoverage; + bool localCoordsWillBeRead = localCoords.fType != LocalCoords::kUnused_Type; + + return DefaultGeoProc::Make(shaderCaps, + flags, + color.fColor, + color.fColorSpaceXform, + viewMatrix, + localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(), + localCoordsWillBeRead, + inCoverage); +} + +sk_sp GrDefaultGeoProcFactory::MakeForDeviceSpace( + const GrShaderCaps* shaderCaps, + const Color& color, + const Coverage& coverage, + const LocalCoords& localCoords, + const SkMatrix& viewMatrix) { + SkMatrix invert = SkMatrix::I(); + if (LocalCoords::kUnused_Type != localCoords.fType) { + SkASSERT(LocalCoords::kUsePosition_Type == localCoords.fType); + if (!viewMatrix.isIdentity() && !viewMatrix.invert(&invert)) { + return nullptr; + } + + if (localCoords.hasLocalMatrix()) { + invert.postConcat(*localCoords.fMatrix); + } + } + + LocalCoords inverted(LocalCoords::kUsePosition_Type, &invert); + return Make(shaderCaps, color, coverage, inverted, SkMatrix::I()); +} diff --git a/gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.h b/gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.h new file mode 100644 index 0000000000..fb61567d81 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDefaultGeoProcFactory.h @@ -0,0 +1,97 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDefaultGeoProcFactory_DEFINED +#define GrDefaultGeoProcFactory_DEFINED + +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrShaderCaps.h" + +/* + * A factory for creating default Geometry Processors which simply multiply position by the uniform + * view matrix and wire through color, coverage, UV coords if requested. + */ +namespace GrDefaultGeoProcFactory { + struct Color { + enum Type { + kPremulGrColorUniform_Type, + kPremulGrColorAttribute_Type, + kPremulWideColorAttribute_Type, + kUnpremulSkColorAttribute_Type, + }; + explicit Color(const SkPMColor4f& color) + : fType(kPremulGrColorUniform_Type) + , fColor(color) + , fColorSpaceXform(nullptr) {} + Color(Type type) + : fType(type) + , fColor(SK_PMColor4fILLEGAL) + , fColorSpaceXform(nullptr) { + SkASSERT(type != kPremulGrColorUniform_Type); + } + + Type fType; + SkPMColor4f fColor; + + // This only applies to SkColor. Any GrColors are assumed to have been color converted + // during paint conversion. + sk_sp fColorSpaceXform; + }; + + struct Coverage { + enum Type { + kSolid_Type, + kUniform_Type, + kAttribute_Type, + kAttributeTweakAlpha_Type, + }; + explicit Coverage(uint8_t coverage) : fType(kUniform_Type), fCoverage(coverage) {} + Coverage(Type type) : fType(type), fCoverage(0xff) { + SkASSERT(type != kUniform_Type); + } + + Type fType; + uint8_t fCoverage; + }; + + struct LocalCoords { + enum Type { + kUnused_Type, + kUsePosition_Type, + kHasExplicit_Type, + kHasTransformed_Type, + }; + LocalCoords(Type type) : fType(type), fMatrix(nullptr) {} + LocalCoords(Type type, const SkMatrix* matrix) : fType(type), fMatrix(matrix) { + SkASSERT(kUnused_Type != type); + } + bool hasLocalMatrix() const { return nullptr != fMatrix; } + + Type fType; + const SkMatrix* fMatrix; + }; + + sk_sp Make(const GrShaderCaps*, + const Color&, + const Coverage&, + const LocalCoords&, + const SkMatrix& viewMatrix); + + /* + * Use this factory to create a GrGeometryProcessor that expects a device space vertex position + * attribute. The view matrix must still be provided to compute correctly transformed + * coordinates for GrFragmentProcessors. It may fail if the view matrix is not invertible. + */ + sk_sp MakeForDeviceSpace(const GrShaderCaps*, + const Color&, + const Coverage&, + const LocalCoords&, + const SkMatrix& viewMatrix); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDeferredProxyUploader.h b/gfx/skia/skia/src/gpu/GrDeferredProxyUploader.h new file mode 100644 index 0000000000..c0047c0841 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDeferredProxyUploader.h @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDeferredProxyUploader_DEFINED +#define GrDeferredProxyUploader_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/SkSemaphore.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkMakeUnique.h" + +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrTextureProxyPriv.h" + +/** + * GrDeferredProxyUploader assists with threaded generation of textures. Currently used by both + * software clip masks, and the software path renderer. The calling code typically needs to store + * some additional data (T) for use on the worker thread. GrTDeferredProxyUploader allows storing + * such data. The common flow is: + * + * 1) A GrTDeferredProxyUploader is created, with some payload (eg an SkPath to draw). + * The uploader is owned by the proxy that it's going to populate. + * 2) A task is created with a pointer to the uploader. A worker thread executes that task, using + * the payload data to allocate and fill in the fPixels pixmap. + * 3) The worker thread calls signalAndFreeData(), which notifies the main thread that the pixmap + * is ready, and then deletes the payload data (which is no longer needed). + * 4) In parallel to 2-3, on the main thread... Some op is created that refers to the proxy. When + * that op is added to an op list, the op list retains a pointer to the "deferred" proxies. + * 5) At flush time, the op list ensures that the deferred proxies are instantiated, then calls + * scheduleUpload on those proxies, which calls scheduleUpload on the uploader (below). + * 6) scheduleUpload defers the upload even further, by adding an ASAPUpload to the flush. + * 7) When the ASAP upload happens, we wait to make sure that the pixels are marked ready + * (from step #3 on the worker thread). Then we perform the actual upload to the texture. + * Finally, we call resetDeferredUploader, which deletes the uploader object, causing fPixels + * to be freed. + */ +class GrDeferredProxyUploader : public SkNoncopyable { +public: + GrDeferredProxyUploader() : fScheduledUpload(false), fWaited(false) {} + + virtual ~GrDeferredProxyUploader() { + // In normal usage (i.e., through GrTDeferredProxyUploader) this will be redundant + this->wait(); + } + + void scheduleUpload(GrOpFlushState* flushState, GrTextureProxy* proxy) { + if (fScheduledUpload) { + // Multiple references to the owning proxy may have caused us to already execute + return; + } + + auto uploadMask = [this, proxy](GrDeferredTextureUploadWritePixelsFn& writePixelsFn) { + this->wait(); + GrColorType pixelColorType = SkColorTypeToGrColorType(this->fPixels.info().colorType()); + // If the worker thread was unable to allocate pixels, this check will fail, and we'll + // end up drawing with an uninitialized mask texture, but at least we won't crash. + if (this->fPixels.addr()) { + writePixelsFn(proxy, 0, 0, this->fPixels.width(), this->fPixels.height(), + pixelColorType, this->fPixels.addr(), this->fPixels.rowBytes()); + } + // Upload has finished, so tell the proxy to release this GrDeferredProxyUploader + proxy->texPriv().resetDeferredUploader(); + }; + flushState->addASAPUpload(std::move(uploadMask)); + fScheduledUpload = true; + } + + void signalAndFreeData() { + this->freeData(); + fPixelsReady.signal(); + } + + SkAutoPixmapStorage* getPixels() { return &fPixels; } + +protected: + void wait() { + if (!fWaited) { + fPixelsReady.wait(); + fWaited = true; + } + } + +private: + virtual void freeData() {} + + SkAutoPixmapStorage fPixels; + SkSemaphore fPixelsReady; + bool fScheduledUpload; + bool fWaited; +}; + +template +class GrTDeferredProxyUploader : public GrDeferredProxyUploader { +public: + template + GrTDeferredProxyUploader(Args&&... args) + : fData(skstd::make_unique(std::forward(args)...)) { + } + + ~GrTDeferredProxyUploader() override { + // We need to wait here, so that we don't free fData before the worker thread is done + // with it. (This happens if the proxy is deleted early due to a full clear or failure + // of an op list to instantiate). + this->wait(); + } + + T& data() { return *fData; } + +private: + void freeData() override { + fData.reset(); + } + + std::unique_ptr fData; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDeferredUpload.h b/gfx/skia/skia/src/gpu/GrDeferredUpload.h new file mode 100644 index 0000000000..887f853d7b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDeferredUpload.h @@ -0,0 +1,147 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDeferredUpload_DEFINED +#define GrDeferredUpload_DEFINED + +#include +#include "include/gpu/GrTypes.h" +#include "include/private/GrTypesPriv.h" + +class GrTextureProxy; + +/** + * A word about deferred uploads and tokens: Ops should usually schedule their uploads to occur at + * the beginning of a frame whenever possible. These are called ASAP uploads. Of course, this + * requires that there are no draws that have yet to be flushed that rely on the old texture + * contents. In that case the ASAP upload would happen prior to the draw and therefore the draw + * would read the new (wrong) texture data. When this read-before-write data hazard exists they + * should schedule an inline upload. + * + * Ops, in conjunction with helpers such as GrDrawOpAtlas, use upload tokens to know what the most + * recent draw was that referenced a resource (or portion of a resource). Each draw is assigned a + * token. A resource (or portion thereof) can be tagged with the most recent reading draw's token. + * The deferred uploads target provides a facility for testing whether the draw corresponding to the + * token has been flushed. If it has not been flushed then the op must perform an inline upload + * instead so that the upload occurs after the draw depending on the old contents and before the + * draw depending on the updated contents. When scheduling an inline upload the op provides the + * token of the draw that the upload must occur before. + */ + +/** + * GrDeferredUploadToken is used to sequence the uploads relative to each other and to draws. + */ +class GrDeferredUploadToken { +public: + static GrDeferredUploadToken AlreadyFlushedToken() { return GrDeferredUploadToken(0); } + + GrDeferredUploadToken(const GrDeferredUploadToken&) = default; + GrDeferredUploadToken& operator=(const GrDeferredUploadToken&) = default; + + bool operator==(const GrDeferredUploadToken& that) const { + return fSequenceNumber == that.fSequenceNumber; + } + bool operator!=(const GrDeferredUploadToken& that) const { return !(*this == that); } + bool operator<(const GrDeferredUploadToken that) const { + return fSequenceNumber < that.fSequenceNumber; + } + bool operator<=(const GrDeferredUploadToken that) const { + return fSequenceNumber <= that.fSequenceNumber; + } + bool operator>(const GrDeferredUploadToken that) const { + return fSequenceNumber > that.fSequenceNumber; + } + bool operator>=(const GrDeferredUploadToken that) const { + return fSequenceNumber >= that.fSequenceNumber; + } + + GrDeferredUploadToken& operator++() { + ++fSequenceNumber; + return *this; + } + GrDeferredUploadToken operator++(int) { + auto old = fSequenceNumber; + ++fSequenceNumber; + return GrDeferredUploadToken(old); + } + + GrDeferredUploadToken next() const { return GrDeferredUploadToken(fSequenceNumber + 1); } + + /** Is this token in the [start, end] inclusive interval? */ + bool inInterval(const GrDeferredUploadToken& start, const GrDeferredUploadToken& end) { + return *this >= start && *this <= end; + } + +private: + GrDeferredUploadToken() = delete; + explicit GrDeferredUploadToken(uint64_t sequenceNumber) : fSequenceNumber(sequenceNumber) {} + uint64_t fSequenceNumber; +}; + +/* + * The GrTokenTracker encapsulates the incrementing and distribution of tokens. + */ +class GrTokenTracker { +public: + /** Gets the token one beyond the last token that has been flushed. */ + GrDeferredUploadToken nextTokenToFlush() const { return fLastFlushedToken.next(); } + + /** Gets the next draw token that will be issued by this target. This can be used by an op + to record that the next draw it issues will use a resource (e.g. texture) while preparing + that draw. */ + GrDeferredUploadToken nextDrawToken() const { return fLastIssuedToken.next(); } + +private: + // Only these three classes get to increment the token counters + friend class SkInternalAtlasTextContext; + friend class GrOpFlushState; + friend class TestingUploadTarget; + + /** Issues the next token for a draw. */ + GrDeferredUploadToken issueDrawToken() { return ++fLastIssuedToken; } + + /** Advances the last flushed token by one. */ + GrDeferredUploadToken flushToken() { return ++fLastFlushedToken; } + + GrDeferredUploadToken fLastIssuedToken = GrDeferredUploadToken::AlreadyFlushedToken(); + GrDeferredUploadToken fLastFlushedToken = GrDeferredUploadToken::AlreadyFlushedToken(); +}; + +/** + * Passed to a deferred upload when it is executed, this method allows the deferred upload to + * actually write its pixel data into a texture. + */ +using GrDeferredTextureUploadWritePixelsFn = + std::function; + +/** + * A deferred texture upload is simply a std::function that takes a + * GrDeferredTextureUploadWritePixelsFn as a parameter. It is called when it should perform its + * upload as the draw/upload sequence is executed. + */ +using GrDeferredTextureUploadFn = std::function; + +/** + * An interface for scheduling deferred uploads. It accepts asap and deferred inline uploads. + */ +class GrDeferredUploadTarget { +public: + virtual ~GrDeferredUploadTarget() {} + + virtual const GrTokenTracker* tokenTracker() = 0; + + /** Returns the token of the draw that this upload will occur before. */ + virtual GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) = 0; + + /** Returns the token of the draw that this upload will occur before. Since ASAP uploads + are done first during a flush, this will be the first token since the most recent + flush. */ + virtual GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&& upload) = 0; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.cpp b/gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.cpp new file mode 100644 index 0000000000..29aa16dcb0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.cpp @@ -0,0 +1,870 @@ +/* + * Copyright 2017 ARM Ltd. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDistanceFieldGen.h" +#include "src/gpu/GrDistanceFieldGenFromVector.h" + +#include "include/core/SkMatrix.h" +#include "include/gpu/GrConfig.h" +#include "include/pathops/SkPathOps.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkRectPriv.h" +#include "src/gpu/geometry/GrPathUtils.h" + +/** + * If a scanline (a row of texel) cross from the kRight_SegSide + * of a segment to the kLeft_SegSide, the winding score should + * add 1. + * And winding score should subtract 1 if the scanline cross + * from kLeft_SegSide to kRight_SegSide. + * Always return kNA_SegSide if the scanline does not cross over + * the segment. Winding score should be zero in this case. + * You can get the winding number for each texel of the scanline + * by adding the winding score from left to right. + * Assuming we always start from outside, so the winding number + * should always start from zero. + * ________ ________ + * | | | | + * ...R|L......L|R.....L|R......R|L..... <= Scanline & side of segment + * |+1 |-1 |-1 |+1 <= Winding score + * 0 | 1 ^ 0 ^ -1 |0 <= Winding number + * |________| |________| + * + * .......NA................NA.......... + * 0 0 + */ +enum SegSide { + kLeft_SegSide = -1, + kOn_SegSide = 0, + kRight_SegSide = 1, + kNA_SegSide = 2, +}; + +struct DFData { + float fDistSq; // distance squared to nearest (so far) edge + int fDeltaWindingScore; // +1 or -1 whenever a scanline cross over a segment +}; + +/////////////////////////////////////////////////////////////////////////////// + +/* + * Type definition for double precision DPoint and DAffineMatrix + */ + +// Point with double precision +struct DPoint { + double fX, fY; + + static DPoint Make(double x, double y) { + DPoint pt; + pt.set(x, y); + return pt; + } + + double x() const { return fX; } + double y() const { return fY; } + + void set(double x, double y) { fX = x; fY = y; } + + /** Returns the euclidian distance from (0,0) to (x,y) + */ + static double Length(double x, double y) { + return sqrt(x * x + y * y); + } + + /** Returns the euclidian distance between a and b + */ + static double Distance(const DPoint& a, const DPoint& b) { + return Length(a.fX - b.fX, a.fY - b.fY); + } + + double distanceToSqd(const DPoint& pt) const { + double dx = fX - pt.fX; + double dy = fY - pt.fY; + return dx * dx + dy * dy; + } +}; + +// Matrix with double precision for affine transformation. +// We don't store row 3 because its always (0, 0, 1). +class DAffineMatrix { +public: + double operator[](int index) const { + SkASSERT((unsigned)index < 6); + return fMat[index]; + } + + double& operator[](int index) { + SkASSERT((unsigned)index < 6); + return fMat[index]; + } + + void setAffine(double m11, double m12, double m13, + double m21, double m22, double m23) { + fMat[0] = m11; + fMat[1] = m12; + fMat[2] = m13; + fMat[3] = m21; + fMat[4] = m22; + fMat[5] = m23; + } + + /** Set the matrix to identity + */ + void reset() { + fMat[0] = fMat[4] = 1.0; + fMat[1] = fMat[3] = + fMat[2] = fMat[5] = 0.0; + } + + // alias for reset() + void setIdentity() { this->reset(); } + + DPoint mapPoint(const SkPoint& src) const { + DPoint pt = DPoint::Make(src.x(), src.y()); + return this->mapPoint(pt); + } + + DPoint mapPoint(const DPoint& src) const { + return DPoint::Make(fMat[0] * src.x() + fMat[1] * src.y() + fMat[2], + fMat[3] * src.x() + fMat[4] * src.y() + fMat[5]); + } +private: + double fMat[6]; +}; + +/////////////////////////////////////////////////////////////////////////////// + +static const double kClose = (SK_Scalar1 / 16.0); +static const double kCloseSqd = kClose * kClose; +static const double kNearlyZero = (SK_Scalar1 / (1 << 18)); +static const double kTangentTolerance = (SK_Scalar1 / (1 << 11)); +static const float kConicTolerance = 0.25f; + +static inline bool between_closed_open(double a, double b, double c, + double tolerance = 0.0, + bool xformToleranceToX = false) { + SkASSERT(tolerance >= 0.0); + double tolB = tolerance; + double tolC = tolerance; + + if (xformToleranceToX) { + // Canonical space is y = x^2 and the derivative of x^2 is 2x. + // So the slope of the tangent line at point (x, x^2) is 2x. + // + // /| + // sqrt(2x * 2x + 1 * 1) / | 2x + // /__| + // 1 + tolB = tolerance / sqrt(4.0 * b * b + 1.0); + tolC = tolerance / sqrt(4.0 * c * c + 1.0); + } + return b < c ? (a >= b - tolB && a < c - tolC) : + (a >= c - tolC && a < b - tolB); +} + +static inline bool between_closed(double a, double b, double c, + double tolerance = 0.0, + bool xformToleranceToX = false) { + SkASSERT(tolerance >= 0.0); + double tolB = tolerance; + double tolC = tolerance; + + if (xformToleranceToX) { + tolB = tolerance / sqrt(4.0 * b * b + 1.0); + tolC = tolerance / sqrt(4.0 * c * c + 1.0); + } + return b < c ? (a >= b - tolB && a <= c + tolC) : + (a >= c - tolC && a <= b + tolB); +} + +static inline bool nearly_zero(double x, double tolerance = kNearlyZero) { + SkASSERT(tolerance >= 0.0); + return fabs(x) <= tolerance; +} + +static inline bool nearly_equal(double x, double y, + double tolerance = kNearlyZero, + bool xformToleranceToX = false) { + SkASSERT(tolerance >= 0.0); + if (xformToleranceToX) { + tolerance = tolerance / sqrt(4.0 * y * y + 1.0); + } + return fabs(x - y) <= tolerance; +} + +static inline double sign_of(const double &val) { + return (val < 0.0) ? -1.0 : 1.0; +} + +static bool is_colinear(const SkPoint pts[3]) { + return nearly_zero((pts[1].y() - pts[0].y()) * (pts[1].x() - pts[2].x()) - + (pts[1].y() - pts[2].y()) * (pts[1].x() - pts[0].x()), kCloseSqd); +} + +class PathSegment { +public: + enum { + // These enum values are assumed in member functions below. + kLine = 0, + kQuad = 1, + } fType; + + // line uses 2 pts, quad uses 3 pts + SkPoint fPts[3]; + + DPoint fP0T, fP2T; + DAffineMatrix fXformMatrix; + double fScalingFactor; + double fScalingFactorSqd; + double fNearlyZeroScaled; + double fTangentTolScaledSqd; + SkRect fBoundingBox; + + void init(); + + int countPoints() { + GR_STATIC_ASSERT(0 == kLine && 1 == kQuad); + return fType + 2; + } + + const SkPoint& endPt() const { + GR_STATIC_ASSERT(0 == kLine && 1 == kQuad); + return fPts[fType + 1]; + } +}; + +typedef SkTArray PathSegmentArray; + +void PathSegment::init() { + const DPoint p0 = DPoint::Make(fPts[0].x(), fPts[0].y()); + const DPoint p2 = DPoint::Make(this->endPt().x(), this->endPt().y()); + const double p0x = p0.x(); + const double p0y = p0.y(); + const double p2x = p2.x(); + const double p2y = p2.y(); + + fBoundingBox.set(fPts[0], this->endPt()); + + if (fType == PathSegment::kLine) { + fScalingFactorSqd = fScalingFactor = 1.0; + double hypotenuse = DPoint::Distance(p0, p2); + + const double cosTheta = (p2x - p0x) / hypotenuse; + const double sinTheta = (p2y - p0y) / hypotenuse; + + fXformMatrix.setAffine( + cosTheta, sinTheta, -(cosTheta * p0x) - (sinTheta * p0y), + -sinTheta, cosTheta, (sinTheta * p0x) - (cosTheta * p0y) + ); + } else { + SkASSERT(fType == PathSegment::kQuad); + + // Calculate bounding box + const SkPoint _P1mP0 = fPts[1] - fPts[0]; + SkPoint t = _P1mP0 - fPts[2] + fPts[1]; + t.fX = _P1mP0.x() / t.x(); + t.fY = _P1mP0.y() / t.y(); + t.fX = SkScalarClampMax(t.x(), 1.0); + t.fY = SkScalarClampMax(t.y(), 1.0); + t.fX = _P1mP0.x() * t.x(); + t.fY = _P1mP0.y() * t.y(); + const SkPoint m = fPts[0] + t; + SkRectPriv::GrowToInclude(&fBoundingBox, m); + + const double p1x = fPts[1].x(); + const double p1y = fPts[1].y(); + + const double p0xSqd = p0x * p0x; + const double p0ySqd = p0y * p0y; + const double p2xSqd = p2x * p2x; + const double p2ySqd = p2y * p2y; + const double p1xSqd = p1x * p1x; + const double p1ySqd = p1y * p1y; + + const double p01xProd = p0x * p1x; + const double p02xProd = p0x * p2x; + const double b12xProd = p1x * p2x; + const double p01yProd = p0y * p1y; + const double p02yProd = p0y * p2y; + const double b12yProd = p1y * p2y; + + const double sqrtA = p0y - (2.0 * p1y) + p2y; + const double a = sqrtA * sqrtA; + const double h = -1.0 * (p0y - (2.0 * p1y) + p2y) * (p0x - (2.0 * p1x) + p2x); + const double sqrtB = p0x - (2.0 * p1x) + p2x; + const double b = sqrtB * sqrtB; + const double c = (p0xSqd * p2ySqd) - (4.0 * p01xProd * b12yProd) + - (2.0 * p02xProd * p02yProd) + (4.0 * p02xProd * p1ySqd) + + (4.0 * p1xSqd * p02yProd) - (4.0 * b12xProd * p01yProd) + + (p2xSqd * p0ySqd); + const double g = (p0x * p02yProd) - (2.0 * p0x * p1ySqd) + + (2.0 * p0x * b12yProd) - (p0x * p2ySqd) + + (2.0 * p1x * p01yProd) - (4.0 * p1x * p02yProd) + + (2.0 * p1x * b12yProd) - (p2x * p0ySqd) + + (2.0 * p2x * p01yProd) + (p2x * p02yProd) + - (2.0 * p2x * p1ySqd); + const double f = -((p0xSqd * p2y) - (2.0 * p01xProd * p1y) + - (2.0 * p01xProd * p2y) - (p02xProd * p0y) + + (4.0 * p02xProd * p1y) - (p02xProd * p2y) + + (2.0 * p1xSqd * p0y) + (2.0 * p1xSqd * p2y) + - (2.0 * b12xProd * p0y) - (2.0 * b12xProd * p1y) + + (p2xSqd * p0y)); + + const double cosTheta = sqrt(a / (a + b)); + const double sinTheta = -1.0 * sign_of((a + b) * h) * sqrt(b / (a + b)); + + const double gDef = cosTheta * g - sinTheta * f; + const double fDef = sinTheta * g + cosTheta * f; + + + const double x0 = gDef / (a + b); + const double y0 = (1.0 / (2.0 * fDef)) * (c - (gDef * gDef / (a + b))); + + + const double lambda = -1.0 * ((a + b) / (2.0 * fDef)); + fScalingFactor = fabs(1.0 / lambda); + fScalingFactorSqd = fScalingFactor * fScalingFactor; + + const double lambda_cosTheta = lambda * cosTheta; + const double lambda_sinTheta = lambda * sinTheta; + + fXformMatrix.setAffine( + lambda_cosTheta, -lambda_sinTheta, lambda * x0, + lambda_sinTheta, lambda_cosTheta, lambda * y0 + ); + } + + fNearlyZeroScaled = kNearlyZero / fScalingFactor; + fTangentTolScaledSqd = kTangentTolerance * kTangentTolerance / fScalingFactorSqd; + + fP0T = fXformMatrix.mapPoint(p0); + fP2T = fXformMatrix.mapPoint(p2); +} + +static void init_distances(DFData* data, int size) { + DFData* currData = data; + + for (int i = 0; i < size; ++i) { + // init distance to "far away" + currData->fDistSq = SK_DistanceFieldMagnitude * SK_DistanceFieldMagnitude; + currData->fDeltaWindingScore = 0; + ++currData; + } +} + +static inline void add_line_to_segment(const SkPoint pts[2], + PathSegmentArray* segments) { + segments->push_back(); + segments->back().fType = PathSegment::kLine; + segments->back().fPts[0] = pts[0]; + segments->back().fPts[1] = pts[1]; + + segments->back().init(); +} + +static inline void add_quad_segment(const SkPoint pts[3], + PathSegmentArray* segments) { + if (SkPointPriv::DistanceToSqd(pts[0], pts[1]) < kCloseSqd || + SkPointPriv::DistanceToSqd(pts[1], pts[2]) < kCloseSqd || + is_colinear(pts)) { + if (pts[0] != pts[2]) { + SkPoint line_pts[2]; + line_pts[0] = pts[0]; + line_pts[1] = pts[2]; + add_line_to_segment(line_pts, segments); + } + } else { + segments->push_back(); + segments->back().fType = PathSegment::kQuad; + segments->back().fPts[0] = pts[0]; + segments->back().fPts[1] = pts[1]; + segments->back().fPts[2] = pts[2]; + + segments->back().init(); + } +} + +static inline void add_cubic_segments(const SkPoint pts[4], + PathSegmentArray* segments) { + SkSTArray<15, SkPoint, true> quads; + GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, &quads); + int count = quads.count(); + for (int q = 0; q < count; q += 3) { + add_quad_segment(&quads[q], segments); + } +} + +static float calculate_nearest_point_for_quad( + const PathSegment& segment, + const DPoint &xFormPt) { + static const float kThird = 0.33333333333f; + static const float kTwentySeventh = 0.037037037f; + + const float a = 0.5f - (float)xFormPt.y(); + const float b = -0.5f * (float)xFormPt.x(); + + const float a3 = a * a * a; + const float b2 = b * b; + + const float c = (b2 * 0.25f) + (a3 * kTwentySeventh); + + if (c >= 0.f) { + const float sqrtC = sqrt(c); + const float result = (float)cbrt((-b * 0.5f) + sqrtC) + (float)cbrt((-b * 0.5f) - sqrtC); + return result; + } else { + const float cosPhi = (float)sqrt((b2 * 0.25f) * (-27.f / a3)) * ((b > 0) ? -1.f : 1.f); + const float phi = (float)acos(cosPhi); + float result; + if (xFormPt.x() > 0.f) { + result = 2.f * (float)sqrt(-a * kThird) * (float)cos(phi * kThird); + if (!between_closed(result, segment.fP0T.x(), segment.fP2T.x())) { + result = 2.f * (float)sqrt(-a * kThird) * (float)cos((phi * kThird) + (SK_ScalarPI * 2.f * kThird)); + } + } else { + result = 2.f * (float)sqrt(-a * kThird) * (float)cos((phi * kThird) + (SK_ScalarPI * 2.f * kThird)); + if (!between_closed(result, segment.fP0T.x(), segment.fP2T.x())) { + result = 2.f * (float)sqrt(-a * kThird) * (float)cos(phi * kThird); + } + } + return result; + } +} + +// This structure contains some intermediate values shared by the same row. +// It is used to calculate segment side of a quadratic bezier. +struct RowData { + // The intersection type of a scanline and y = x * x parabola in canonical space. + enum IntersectionType { + kNoIntersection, + kVerticalLine, + kTangentLine, + kTwoPointsIntersect + } fIntersectionType; + + // The direction of the quadratic segment/scanline in the canonical space. + // 1: The quadratic segment/scanline going from negative x-axis to positive x-axis. + // 0: The scanline is a vertical line in the canonical space. + // -1: The quadratic segment/scanline going from positive x-axis to negative x-axis. + int fQuadXDirection; + int fScanlineXDirection; + + // The y-value(equal to x*x) of intersection point for the kVerticalLine intersection type. + double fYAtIntersection; + + // The x-value for two intersection points. + double fXAtIntersection1; + double fXAtIntersection2; +}; + +void precomputation_for_row( + RowData *rowData, + const PathSegment& segment, + const SkPoint& pointLeft, + const SkPoint& pointRight + ) { + if (segment.fType != PathSegment::kQuad) { + return; + } + + const DPoint& xFormPtLeft = segment.fXformMatrix.mapPoint(pointLeft); + const DPoint& xFormPtRight = segment.fXformMatrix.mapPoint(pointRight); + + rowData->fQuadXDirection = (int)sign_of(segment.fP2T.x() - segment.fP0T.x()); + rowData->fScanlineXDirection = (int)sign_of(xFormPtRight.x() - xFormPtLeft.x()); + + const double x1 = xFormPtLeft.x(); + const double y1 = xFormPtLeft.y(); + const double x2 = xFormPtRight.x(); + const double y2 = xFormPtRight.y(); + + if (nearly_equal(x1, x2, segment.fNearlyZeroScaled, true)) { + rowData->fIntersectionType = RowData::kVerticalLine; + rowData->fYAtIntersection = x1 * x1; + rowData->fScanlineXDirection = 0; + return; + } + + // Line y = mx + b + const double m = (y2 - y1) / (x2 - x1); + const double b = -m * x1 + y1; + + const double m2 = m * m; + const double c = m2 + 4.0 * b; + + const double tol = 4.0 * segment.fTangentTolScaledSqd / (m2 + 1.0); + + // Check if the scanline is the tangent line of the curve, + // and the curve start or end at the same y-coordinate of the scanline + if ((rowData->fScanlineXDirection == 1 && + (segment.fPts[0].y() == pointLeft.y() || + segment.fPts[2].y() == pointLeft.y())) && + nearly_zero(c, tol)) { + rowData->fIntersectionType = RowData::kTangentLine; + rowData->fXAtIntersection1 = m / 2.0; + rowData->fXAtIntersection2 = m / 2.0; + } else if (c <= 0.0) { + rowData->fIntersectionType = RowData::kNoIntersection; + return; + } else { + rowData->fIntersectionType = RowData::kTwoPointsIntersect; + const double d = sqrt(c); + rowData->fXAtIntersection1 = (m + d) / 2.0; + rowData->fXAtIntersection2 = (m - d) / 2.0; + } +} + +SegSide calculate_side_of_quad( + const PathSegment& segment, + const SkPoint& point, + const DPoint& xFormPt, + const RowData& rowData) { + SegSide side = kNA_SegSide; + + if (RowData::kVerticalLine == rowData.fIntersectionType) { + side = (SegSide)(int)(sign_of(xFormPt.y() - rowData.fYAtIntersection) * rowData.fQuadXDirection); + } + else if (RowData::kTwoPointsIntersect == rowData.fIntersectionType) { + const double p1 = rowData.fXAtIntersection1; + const double p2 = rowData.fXAtIntersection2; + + int signP1 = (int)sign_of(p1 - xFormPt.x()); + bool includeP1 = true; + bool includeP2 = true; + + if (rowData.fScanlineXDirection == 1) { + if ((rowData.fQuadXDirection == -1 && segment.fPts[0].y() <= point.y() && + nearly_equal(segment.fP0T.x(), p1, segment.fNearlyZeroScaled, true)) || + (rowData.fQuadXDirection == 1 && segment.fPts[2].y() <= point.y() && + nearly_equal(segment.fP2T.x(), p1, segment.fNearlyZeroScaled, true))) { + includeP1 = false; + } + if ((rowData.fQuadXDirection == -1 && segment.fPts[2].y() <= point.y() && + nearly_equal(segment.fP2T.x(), p2, segment.fNearlyZeroScaled, true)) || + (rowData.fQuadXDirection == 1 && segment.fPts[0].y() <= point.y() && + nearly_equal(segment.fP0T.x(), p2, segment.fNearlyZeroScaled, true))) { + includeP2 = false; + } + } + + if (includeP1 && between_closed(p1, segment.fP0T.x(), segment.fP2T.x(), + segment.fNearlyZeroScaled, true)) { + side = (SegSide)(signP1 * rowData.fQuadXDirection); + } + if (includeP2 && between_closed(p2, segment.fP0T.x(), segment.fP2T.x(), + segment.fNearlyZeroScaled, true)) { + int signP2 = (int)sign_of(p2 - xFormPt.x()); + if (side == kNA_SegSide || signP2 == 1) { + side = (SegSide)(-signP2 * rowData.fQuadXDirection); + } + } + } else if (RowData::kTangentLine == rowData.fIntersectionType) { + // The scanline is the tangent line of current quadratic segment. + + const double p = rowData.fXAtIntersection1; + int signP = (int)sign_of(p - xFormPt.x()); + if (rowData.fScanlineXDirection == 1) { + // The path start or end at the tangent point. + if (segment.fPts[0].y() == point.y()) { + side = (SegSide)(signP); + } else if (segment.fPts[2].y() == point.y()) { + side = (SegSide)(-signP); + } + } + } + + return side; +} + +static float distance_to_segment(const SkPoint& point, + const PathSegment& segment, + const RowData& rowData, + SegSide* side) { + SkASSERT(side); + + const DPoint xformPt = segment.fXformMatrix.mapPoint(point); + + if (segment.fType == PathSegment::kLine) { + float result = SK_DistanceFieldPad * SK_DistanceFieldPad; + + if (between_closed(xformPt.x(), segment.fP0T.x(), segment.fP2T.x())) { + result = (float)(xformPt.y() * xformPt.y()); + } else if (xformPt.x() < segment.fP0T.x()) { + result = (float)(xformPt.x() * xformPt.x() + xformPt.y() * xformPt.y()); + } else { + result = (float)((xformPt.x() - segment.fP2T.x()) * (xformPt.x() - segment.fP2T.x()) + + xformPt.y() * xformPt.y()); + } + + if (between_closed_open(point.y(), segment.fBoundingBox.top(), + segment.fBoundingBox.bottom())) { + *side = (SegSide)(int)sign_of(xformPt.y()); + } else { + *side = kNA_SegSide; + } + return result; + } else { + SkASSERT(segment.fType == PathSegment::kQuad); + + const float nearestPoint = calculate_nearest_point_for_quad(segment, xformPt); + + float dist; + + if (between_closed(nearestPoint, segment.fP0T.x(), segment.fP2T.x())) { + DPoint x = DPoint::Make(nearestPoint, nearestPoint * nearestPoint); + dist = (float)xformPt.distanceToSqd(x); + } else { + const float distToB0T = (float)xformPt.distanceToSqd(segment.fP0T); + const float distToB2T = (float)xformPt.distanceToSqd(segment.fP2T); + + if (distToB0T < distToB2T) { + dist = distToB0T; + } else { + dist = distToB2T; + } + } + + if (between_closed_open(point.y(), segment.fBoundingBox.top(), + segment.fBoundingBox.bottom())) { + *side = calculate_side_of_quad(segment, point, xformPt, rowData); + } else { + *side = kNA_SegSide; + } + + return (float)(dist * segment.fScalingFactorSqd); + } +} + +static void calculate_distance_field_data(PathSegmentArray* segments, + DFData* dataPtr, + int width, int height) { + int count = segments->count(); + for (int a = 0; a < count; ++a) { + PathSegment& segment = (*segments)[a]; + const SkRect& segBB = segment.fBoundingBox.makeOutset( + SK_DistanceFieldPad, SK_DistanceFieldPad); + int startColumn = (int)segBB.left(); + int endColumn = SkScalarCeilToInt(segBB.right()); + + int startRow = (int)segBB.top(); + int endRow = SkScalarCeilToInt(segBB.bottom()); + + SkASSERT((startColumn >= 0) && "StartColumn < 0!"); + SkASSERT((endColumn <= width) && "endColumn > width!"); + SkASSERT((startRow >= 0) && "StartRow < 0!"); + SkASSERT((endRow <= height) && "EndRow > height!"); + + // Clip inside the distance field to avoid overflow + startColumn = SkTMax(startColumn, 0); + endColumn = SkTMin(endColumn, width); + startRow = SkTMax(startRow, 0); + endRow = SkTMin(endRow, height); + + for (int row = startRow; row < endRow; ++row) { + SegSide prevSide = kNA_SegSide; + const float pY = row + 0.5f; + RowData rowData; + + const SkPoint pointLeft = SkPoint::Make((SkScalar)startColumn, pY); + const SkPoint pointRight = SkPoint::Make((SkScalar)endColumn, pY); + + if (between_closed_open(pY, segment.fBoundingBox.top(), + segment.fBoundingBox.bottom())) { + precomputation_for_row(&rowData, segment, pointLeft, pointRight); + } + + for (int col = startColumn; col < endColumn; ++col) { + int idx = (row * width) + col; + + const float pX = col + 0.5f; + const SkPoint point = SkPoint::Make(pX, pY); + + const float distSq = dataPtr[idx].fDistSq; + int dilation = distSq < 1.5 * 1.5 ? 1 : + distSq < 2.5 * 2.5 ? 2 : + distSq < 3.5 * 3.5 ? 3 : SK_DistanceFieldPad; + if (dilation > SK_DistanceFieldPad) { + dilation = SK_DistanceFieldPad; + } + + // Optimisation for not calculating some points. + if (dilation != SK_DistanceFieldPad && !segment.fBoundingBox.roundOut() + .makeOutset(dilation, dilation).contains(col, row)) { + continue; + } + + SegSide side = kNA_SegSide; + int deltaWindingScore = 0; + float currDistSq = distance_to_segment(point, segment, rowData, &side); + if (prevSide == kLeft_SegSide && side == kRight_SegSide) { + deltaWindingScore = -1; + } else if (prevSide == kRight_SegSide && side == kLeft_SegSide) { + deltaWindingScore = 1; + } + + prevSide = side; + + if (currDistSq < distSq) { + dataPtr[idx].fDistSq = currDistSq; + } + + dataPtr[idx].fDeltaWindingScore += deltaWindingScore; + } + } + } +} + +template +static unsigned char pack_distance_field_val(float dist) { + // The distance field is constructed as unsigned char values, so that the zero value is at 128, + // Beside 128, we have 128 values in range [0, 128), but only 127 values in range (128, 255]. + // So we multiply distanceMagnitude by 127/128 at the latter range to avoid overflow. + dist = SkScalarPin(-dist, -distanceMagnitude, distanceMagnitude * 127.0f / 128.0f); + + // Scale into the positive range for unsigned distance. + dist += distanceMagnitude; + + // Scale into unsigned char range. + // Round to place negative and positive values as equally as possible around 128 + // (which represents zero). + return (unsigned char)SkScalarRoundToInt(dist / (2 * distanceMagnitude) * 256.0f); +} + +bool GrGenerateDistanceFieldFromPath(unsigned char* distanceField, + const SkPath& path, const SkMatrix& drawMatrix, + int width, int height, size_t rowBytes) { + SkASSERT(distanceField); + +#ifdef SK_DEBUG + SkPath xformPath; + path.transform(drawMatrix, &xformPath); + SkIRect pathBounds = xformPath.getBounds().roundOut(); + SkIRect expectPathBounds = + SkIRect::MakeWH(width - 2 * SK_DistanceFieldPad, height - 2 * SK_DistanceFieldPad); +#endif + + SkASSERT(expectPathBounds.isEmpty() || + expectPathBounds.contains(pathBounds.x(), pathBounds.y())); + SkASSERT(expectPathBounds.isEmpty() || pathBounds.isEmpty() || + expectPathBounds.contains(pathBounds)); + + SkPath simplifiedPath; + SkPath workingPath; + if (Simplify(path, &simplifiedPath)) { + workingPath = simplifiedPath; + } else { + workingPath = path; + } + + if (!IsDistanceFieldSupportedFillType(workingPath.getFillType())) { + return false; + } + + workingPath.transform(drawMatrix); + + SkDEBUGCODE(pathBounds = workingPath.getBounds().roundOut()); + SkASSERT(expectPathBounds.isEmpty() || + expectPathBounds.contains(pathBounds.x(), pathBounds.y())); + SkASSERT(expectPathBounds.isEmpty() || pathBounds.isEmpty() || + expectPathBounds.contains(pathBounds)); + + // translate path to offset (SK_DistanceFieldPad, SK_DistanceFieldPad) + SkMatrix dfMatrix; + dfMatrix.setTranslate(SK_DistanceFieldPad, SK_DistanceFieldPad); + workingPath.transform(dfMatrix); + + // create temp data + size_t dataSize = width * height * sizeof(DFData); + SkAutoSMalloc<1024> dfStorage(dataSize); + DFData* dataPtr = (DFData*) dfStorage.get(); + + // create initial distance data + init_distances(dataPtr, width * height); + + SkPathEdgeIter iter(workingPath); + SkSTArray<15, PathSegment, true> segments; + + while (auto e = iter.next()) { + switch (e.fEdge) { + case SkPathEdgeIter::Edge::kLine: { + add_line_to_segment(e.fPts, &segments); + break; + } + case SkPathEdgeIter::Edge::kQuad: + add_quad_segment(e.fPts, &segments); + break; + case SkPathEdgeIter::Edge::kConic: { + SkScalar weight = iter.conicWeight(); + SkAutoConicToQuads converter; + const SkPoint* quadPts = converter.computeQuads(e.fPts, weight, kConicTolerance); + for (int i = 0; i < converter.countQuads(); ++i) { + add_quad_segment(quadPts + 2*i, &segments); + } + break; + } + case SkPathEdgeIter::Edge::kCubic: { + add_cubic_segments(e.fPts, &segments); + break; + } + } + } + + calculate_distance_field_data(&segments, dataPtr, width, height); + + for (int row = 0; row < height; ++row) { + int windingNumber = 0; // Winding number start from zero for each scanline + for (int col = 0; col < width; ++col) { + int idx = (row * width) + col; + windingNumber += dataPtr[idx].fDeltaWindingScore; + + enum DFSign { + kInside = -1, + kOutside = 1 + } dfSign; + + if (workingPath.getFillType() == SkPath::kWinding_FillType) { + dfSign = windingNumber ? kInside : kOutside; + } else if (workingPath.getFillType() == SkPath::kInverseWinding_FillType) { + dfSign = windingNumber ? kOutside : kInside; + } else if (workingPath.getFillType() == SkPath::kEvenOdd_FillType) { + dfSign = (windingNumber % 2) ? kInside : kOutside; + } else { + SkASSERT(workingPath.getFillType() == SkPath::kInverseEvenOdd_FillType); + dfSign = (windingNumber % 2) ? kOutside : kInside; + } + + // The winding number at the end of a scanline should be zero. + SkASSERT(((col != width - 1) || (windingNumber == 0)) && + "Winding number should be zero at the end of a scan line."); + // Fallback to use SkPath::contains to determine the sign of pixel in release build. + if (col == width - 1 && windingNumber != 0) { + for (int col = 0; col < width; ++col) { + int idx = (row * width) + col; + dfSign = workingPath.contains(col + 0.5, row + 0.5) ? kInside : kOutside; + const float miniDist = sqrt(dataPtr[idx].fDistSq); + const float dist = dfSign * miniDist; + + unsigned char pixelVal = pack_distance_field_val(dist); + + distanceField[(row * rowBytes) + col] = pixelVal; + } + continue; + } + + const float miniDist = sqrt(dataPtr[idx].fDistSq); + const float dist = dfSign * miniDist; + + unsigned char pixelVal = pack_distance_field_val(dist); + + distanceField[(row * rowBytes) + col] = pixelVal; + } + } + return true; +} diff --git a/gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.h b/gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.h new file mode 100644 index 0000000000..8e8d1b3413 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDistanceFieldGenFromVector.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 ARM Ltd. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDistanceFieldGenFromVector_DEFINED +#define GrDistanceFieldGenFromVector_DEFINED + +#include "include/core/SkPath.h" + +class SkMatrix; + +#ifndef SK_USE_LEGACY_DISTANCE_FIELDS + #define SK_USE_LEGACY_DISTANCE_FIELDS +#endif + +/** Given a vector path, generate the associated distance field. + + * @param distanceField The distance field to be generated. Should already be allocated + * by the client with the padding defined in "SkDistanceFieldGen.h". + * @param path The path we're using to generate the distance field. + * @param matrix Transformation matrix for path. + * @param width Width of the distance field. + * @param height Height of the distance field. + * @param rowBytes Size of each row in the distance field, in bytes. + */ +bool GrGenerateDistanceFieldFromPath(unsigned char* distanceField, + const SkPath& path, const SkMatrix& viewMatrix, + int width, int height, size_t rowBytes); + +inline bool IsDistanceFieldSupportedFillType(SkPath::FillType fFillType) +{ + return (SkPath::kEvenOdd_FillType == fFillType || + SkPath::kInverseEvenOdd_FillType == fFillType); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDrawOpAtlas.cpp b/gfx/skia/skia/src/gpu/GrDrawOpAtlas.cpp new file mode 100644 index 0000000000..b41d34b141 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDrawOpAtlas.cpp @@ -0,0 +1,664 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrDrawOpAtlas.h" + +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRectanizer.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrResourceProviderPriv.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTracing.h" + +// When proxy allocation is deferred until flush time the proxies acting as atlases require +// special handling. This is because the usage that can be determined from the ops themselves +// isn't sufficient. Independent of the ops there will be ASAP and inline uploads to the +// atlases. Extending the usage interval of any op that uses an atlas to the start of the +// flush (as is done for proxies that are used for sw-generated masks) also won't work because +// the atlas persists even beyond the last use in an op - for a given flush. Given this, atlases +// must explicitly manage the lifetime of their backing proxies via the onFlushCallback system +// (which calls this method). +void GrDrawOpAtlas::instantiate(GrOnFlushResourceProvider* onFlushResourceProvider) { + for (uint32_t i = 0; i < fNumActivePages; ++i) { + // All the atlas pages are now instantiated at flush time in the activeNewPage method. + SkASSERT(fProxies[i] && fProxies[i]->isInstantiated()); + } +} + +std::unique_ptr GrDrawOpAtlas::Make(GrProxyProvider* proxyProvider, + const GrBackendFormat& format, + GrColorType colorType, int width, + int height, int plotWidth, int plotHeight, + AllowMultitexturing allowMultitexturing, + GrDrawOpAtlas::EvictionFunc func, void* data) { + if (!format.isValid()) { + return nullptr; + } + + std::unique_ptr atlas(new GrDrawOpAtlas(proxyProvider, format, colorType, width, + height, plotWidth, plotHeight, + allowMultitexturing)); + if (!atlas->getProxies()[0]) { + return nullptr; + } + + atlas->registerEvictionCallback(func, data); + return atlas; +} + +#ifdef DUMP_ATLAS_DATA +static bool gDumpAtlasData = false; +#endif + +//////////////////////////////////////////////////////////////////////////////// +GrDrawOpAtlas::Plot::Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY, + int width, int height, GrColorType colorType) + : fLastUpload(GrDeferredUploadToken::AlreadyFlushedToken()) + , fLastUse(GrDeferredUploadToken::AlreadyFlushedToken()) + , fFlushesSinceLastUse(0) + , fPageIndex(pageIndex) + , fPlotIndex(plotIndex) + , fGenID(genID) + , fID(CreateId(fPageIndex, fPlotIndex, fGenID)) + , fData(nullptr) + , fWidth(width) + , fHeight(height) + , fX(offX) + , fY(offY) + , fRects(nullptr) + , fOffset(SkIPoint16::Make(fX * fWidth, fY * fHeight)) + , fColorType(colorType) + , fBytesPerPixel(GrColorTypeBytesPerPixel(colorType)) +#ifdef SK_DEBUG + , fDirty(false) +#endif +{ + // We expect the allocated dimensions to be a multiple of 4 bytes + SkASSERT(((width*fBytesPerPixel) & 0x3) == 0); + // The padding for faster uploads only works for 1, 2 and 4 byte texels + SkASSERT(fBytesPerPixel != 3 && fBytesPerPixel <= 4); + fDirtyRect.setEmpty(); +} + +GrDrawOpAtlas::Plot::~Plot() { + sk_free(fData); + delete fRects; +} + +bool GrDrawOpAtlas::Plot::addSubImage(int width, int height, const void* image, SkIPoint16* loc) { + SkASSERT(width <= fWidth && height <= fHeight); + + if (!fRects) { + fRects = GrRectanizer::Factory(fWidth, fHeight); + } + + if (!fRects->addRect(width, height, loc)) { + return false; + } + + if (!fData) { + fData = reinterpret_cast(sk_calloc_throw(fBytesPerPixel * fWidth * + fHeight)); + } + size_t rowBytes = width * fBytesPerPixel; + const unsigned char* imagePtr = (const unsigned char*)image; + // point ourselves at the right starting spot + unsigned char* dataPtr = fData; + dataPtr += fBytesPerPixel * fWidth * loc->fY; + dataPtr += fBytesPerPixel * loc->fX; + // copy into the data buffer, swizzling as we go if this is ARGB data + if (4 == fBytesPerPixel && kSkia8888_GrPixelConfig == kBGRA_8888_GrPixelConfig) { + for (int i = 0; i < height; ++i) { + SkOpts::RGBA_to_BGRA((uint32_t*)dataPtr, (const uint32_t*)imagePtr, width); + dataPtr += fBytesPerPixel * fWidth; + imagePtr += rowBytes; + } + } else { + for (int i = 0; i < height; ++i) { + memcpy(dataPtr, imagePtr, rowBytes); + dataPtr += fBytesPerPixel * fWidth; + imagePtr += rowBytes; + } + } + + fDirtyRect.join({loc->fX, loc->fY, loc->fX + width, loc->fY + height}); + + loc->fX += fOffset.fX; + loc->fY += fOffset.fY; + SkDEBUGCODE(fDirty = true;) + + return true; +} + +void GrDrawOpAtlas::Plot::uploadToTexture(GrDeferredTextureUploadWritePixelsFn& writePixels, + GrTextureProxy* proxy) { + // We should only be issuing uploads if we are in fact dirty + SkASSERT(fDirty && fData && proxy && proxy->peekTexture()); + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + size_t rowBytes = fBytesPerPixel * fWidth; + const unsigned char* dataPtr = fData; + // Clamp to 4-byte aligned boundaries + unsigned int clearBits = 0x3 / fBytesPerPixel; + fDirtyRect.fLeft &= ~clearBits; + fDirtyRect.fRight += clearBits; + fDirtyRect.fRight &= ~clearBits; + SkASSERT(fDirtyRect.fRight <= fWidth); + // Set up dataPtr + dataPtr += rowBytes * fDirtyRect.fTop; + dataPtr += fBytesPerPixel * fDirtyRect.fLeft; + + writePixels(proxy, fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop, + fDirtyRect.width(), fDirtyRect.height(), fColorType, dataPtr, rowBytes); + fDirtyRect.setEmpty(); + SkDEBUGCODE(fDirty = false;) +} + +void GrDrawOpAtlas::Plot::resetRects() { + if (fRects) { + fRects->reset(); + } + + fGenID++; + fID = CreateId(fPageIndex, fPlotIndex, fGenID); + fLastUpload = GrDeferredUploadToken::AlreadyFlushedToken(); + fLastUse = GrDeferredUploadToken::AlreadyFlushedToken(); + + // zero out the plot + if (fData) { + sk_bzero(fData, fBytesPerPixel * fWidth * fHeight); + } + + fDirtyRect.setEmpty(); + SkDEBUGCODE(fDirty = false;) +} + +/////////////////////////////////////////////////////////////////////////////// + +GrDrawOpAtlas::GrDrawOpAtlas(GrProxyProvider* proxyProvider, const GrBackendFormat& format, + GrColorType colorType, int width, int height, + int plotWidth, int plotHeight, AllowMultitexturing allowMultitexturing) + : fFormat(format) + , fColorType(colorType) + , fTextureWidth(width) + , fTextureHeight(height) + , fPlotWidth(plotWidth) + , fPlotHeight(plotHeight) + , fAtlasGeneration(kInvalidAtlasGeneration + 1) + , fPrevFlushToken(GrDeferredUploadToken::AlreadyFlushedToken()) + , fMaxPages(AllowMultitexturing::kYes == allowMultitexturing ? kMaxMultitexturePages : 1) + , fNumActivePages(0) { + int numPlotsX = width/plotWidth; + int numPlotsY = height/plotHeight; + SkASSERT(numPlotsX * numPlotsY <= GrDrawOpAtlas::kMaxPlots); + SkASSERT(fPlotWidth * numPlotsX == fTextureWidth); + SkASSERT(fPlotHeight * numPlotsY == fTextureHeight); + + fNumPlots = numPlotsX * numPlotsY; + + this->createPages(proxyProvider); +} + +inline void GrDrawOpAtlas::processEviction(AtlasID id) { + for (int i = 0; i < fEvictionCallbacks.count(); i++) { + (*fEvictionCallbacks[i].fFunc)(id, fEvictionCallbacks[i].fData); + } + ++fAtlasGeneration; +} + +inline bool GrDrawOpAtlas::updatePlot(GrDeferredUploadTarget* target, AtlasID* id, Plot* plot) { + int pageIdx = GetPageIndexFromID(plot->id()); + this->makeMRU(plot, pageIdx); + + // If our most recent upload has already occurred then we have to insert a new + // upload. Otherwise, we already have a scheduled upload that hasn't yet ocurred. + // This new update will piggy back on that previously scheduled update. + if (plot->lastUploadToken() < target->tokenTracker()->nextTokenToFlush()) { + // With c+14 we could move sk_sp into lamba to only ref once. + sk_sp plotsp(SkRef(plot)); + + GrTextureProxy* proxy = fProxies[pageIdx].get(); + SkASSERT(proxy->isInstantiated()); // This is occurring at flush time + + GrDeferredUploadToken lastUploadToken = target->addASAPUpload( + [plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) { + plotsp->uploadToTexture(writePixels, proxy); + }); + plot->setLastUploadToken(lastUploadToken); + } + *id = plot->id(); + return true; +} + +bool GrDrawOpAtlas::uploadToPage(const GrCaps& caps, unsigned int pageIdx, AtlasID* id, + GrDeferredUploadTarget* target, int width, int height, + const void* image, SkIPoint16* loc) { + SkASSERT(fProxies[pageIdx] && fProxies[pageIdx]->isInstantiated()); + + // look through all allocated plots for one we can share, in Most Recently Refed order + PlotList::Iter plotIter; + plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart); + + for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) { + SkASSERT(caps.bytesPerPixel(fProxies[pageIdx]->backendFormat()) == plot->bpp()); + + if (plot->addSubImage(width, height, image, loc)) { + return this->updatePlot(target, id, plot); + } + } + + return false; +} + +// Number of atlas-related flushes beyond which we consider a plot to no longer be in use. +// +// This value is somewhat arbitrary -- the idea is to keep it low enough that +// a page with unused plots will get removed reasonably quickly, but allow it +// to hang around for a bit in case it's needed. The assumption is that flushes +// are rare; i.e., we are not continually refreshing the frame. +static constexpr auto kRecentlyUsedCount = 256; + +GrDrawOpAtlas::ErrorCode GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider, + AtlasID* id, GrDeferredUploadTarget* target, + int width, int height, + const void* image, SkIPoint16* loc) { + if (width > fPlotWidth || height > fPlotHeight) { + return ErrorCode::kError; + } + + const GrCaps& caps = *resourceProvider->caps(); + + // Look through each page to see if we can upload without having to flush + // We prioritize this upload to the first pages, not the most recently used, to make it easier + // to remove unused pages in reverse page order. + for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) { + if (this->uploadToPage(caps, pageIdx, id, target, width, height, image, loc)) { + return ErrorCode::kSucceeded; + } + } + + // If the above fails, then see if the least recently used plot per page has already been + // flushed to the gpu if we're at max page allocation, or if the plot has aged out otherwise. + // We wait until we've grown to the full number of pages to begin evicting already flushed + // plots so that we can maximize the opportunity for reuse. + // As before we prioritize this upload to the first pages, not the most recently used. + if (fNumActivePages == this->maxPages()) { + for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) { + Plot* plot = fPages[pageIdx].fPlotList.tail(); + SkASSERT(plot); + if (plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush()) { + this->processEvictionAndResetRects(plot); + SkASSERT(caps.bytesPerPixel(fProxies[pageIdx]->backendFormat()) == plot->bpp()); + SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc); + SkASSERT(verify); + if (!this->updatePlot(target, id, plot)) { + return ErrorCode::kError; + } + return ErrorCode::kSucceeded; + } + } + } else { + // If we haven't activated all the available pages, try to create a new one and add to it + if (!this->activateNewPage(resourceProvider)) { + return ErrorCode::kError; + } + + if (this->uploadToPage(caps, fNumActivePages-1, id, target, width, height, image, loc)) { + return ErrorCode::kSucceeded; + } else { + // If we fail to upload to a newly activated page then something has gone terribly + // wrong - return an error + return ErrorCode::kError; + } + } + + if (!fNumActivePages) { + return ErrorCode::kError; + } + + // Try to find a plot that we can perform an inline upload to. + // We prioritize this upload in reverse order of pages to counterbalance the order above. + Plot* plot = nullptr; + for (int pageIdx = ((int)fNumActivePages)-1; pageIdx >= 0; --pageIdx) { + Plot* currentPlot = fPages[pageIdx].fPlotList.tail(); + if (currentPlot->lastUseToken() != target->tokenTracker()->nextDrawToken()) { + plot = currentPlot; + break; + } + } + + // If we can't find a plot that is not used in a draw currently being prepared by an op, then + // we have to fail. This gives the op a chance to enqueue the draw, and call back into this + // function. When that draw is enqueued, the draw token advances, and the subsequent call will + // continue past this branch and prepare an inline upload that will occur after the enqueued + // draw which references the plot's pre-upload content. + if (!plot) { + return ErrorCode::kTryAgain; + } + + this->processEviction(plot->id()); + int pageIdx = GetPageIndexFromID(plot->id()); + fPages[pageIdx].fPlotList.remove(plot); + sk_sp& newPlot = fPages[pageIdx].fPlotArray[plot->index()]; + newPlot.reset(plot->clone()); + + fPages[pageIdx].fPlotList.addToHead(newPlot.get()); + SkASSERT(caps.bytesPerPixel(fProxies[pageIdx]->backendFormat()) == newPlot->bpp()); + SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, loc); + SkASSERT(verify); + + // Note that this plot will be uploaded inline with the draws whereas the + // one it displaced most likely was uploaded ASAP. + // With c+14 we could move sk_sp into lambda to only ref once. + sk_sp plotsp(SkRef(newPlot.get())); + + GrTextureProxy* proxy = fProxies[pageIdx].get(); + SkASSERT(proxy->isInstantiated()); + + GrDeferredUploadToken lastUploadToken = target->addInlineUpload( + [plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) { + plotsp->uploadToTexture(writePixels, proxy); + }); + newPlot->setLastUploadToken(lastUploadToken); + + *id = newPlot->id(); + + return ErrorCode::kSucceeded; +} + +void GrDrawOpAtlas::compact(GrDeferredUploadToken startTokenForNextFlush) { + if (fNumActivePages <= 1) { + fPrevFlushToken = startTokenForNextFlush; + return; + } + + // For all plots, reset number of flushes since used if used this frame. + PlotList::Iter plotIter; + bool atlasUsedThisFlush = false; + for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) { + plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart); + while (Plot* plot = plotIter.get()) { + // Reset number of flushes since used + if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) { + plot->resetFlushesSinceLastUsed(); + atlasUsedThisFlush = true; + } + + plotIter.next(); + } + } + + // We only try to compact if the atlas was used in the recently completed flush. + // This is to handle the case where a lot of text or path rendering has occurred but then just + // a blinking cursor is drawn. + // TODO: consider if we should also do this if it's been a long time since the last atlas use + if (atlasUsedThisFlush) { + SkTArray availablePlots; + uint32_t lastPageIndex = fNumActivePages - 1; + + // For all plots but the last one, update number of flushes since used, and check to see + // if there are any in the first pages that the last page can safely upload to. + for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) { +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("page %d: ", pageIndex); + } +#endif + plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart); + while (Plot* plot = plotIter.get()) { + // Update number of flushes since plot was last used + // We only increment the 'sinceLastUsed' count for flushes where the atlas was used + // to avoid deleting everything when we return to text drawing in the blinking + // cursor case + if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) { + plot->incFlushesSinceLastUsed(); + } + +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("%d ", plot->flushesSinceLastUsed()); + } +#endif + // Count plots we can potentially upload to in all pages except the last one + // (the potential compactee). + if (plot->flushesSinceLastUsed() > kRecentlyUsedCount) { + availablePlots.push_back() = plot; + } + + plotIter.next(); + } +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("\n"); + } +#endif + } + + // Count recently used plots in the last page and evict any that are no longer in use. + // Since we prioritize uploading to the first pages, this will eventually + // clear out usage of this page unless we have a large need. + plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart); + unsigned int usedPlots = 0; +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("page %d: ", lastPageIndex); + } +#endif + while (Plot* plot = plotIter.get()) { + // Update number of flushes since plot was last used + if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) { + plot->incFlushesSinceLastUsed(); + } + +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("%d ", plot->flushesSinceLastUsed()); + } +#endif + // If this plot was used recently + if (plot->flushesSinceLastUsed() <= kRecentlyUsedCount) { + usedPlots++; + } else if (plot->lastUseToken() != GrDeferredUploadToken::AlreadyFlushedToken()) { + // otherwise if aged out just evict it. + this->processEvictionAndResetRects(plot); + } + plotIter.next(); + } +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("\n"); + } +#endif + + // If recently used plots in the last page are using less than a quarter of the page, try + // to evict them if there's available space in earlier pages. Since we prioritize uploading + // to the first pages, this will eventually clear out usage of this page unless we have a + // large need. + if (availablePlots.count() && usedPlots && usedPlots <= fNumPlots / 4) { + plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart); + while (Plot* plot = plotIter.get()) { + // If this plot was used recently + if (plot->flushesSinceLastUsed() <= kRecentlyUsedCount) { + // See if there's room in an earlier page and if so evict. + // We need to be somewhat harsh here so that a handful of plots that are + // consistently in use don't end up locking the page in memory. + if (availablePlots.count() > 0) { + this->processEvictionAndResetRects(plot); + this->processEvictionAndResetRects(availablePlots.back()); + availablePlots.pop_back(); + --usedPlots; + } + if (!usedPlots || !availablePlots.count()) { + break; + } + } + plotIter.next(); + } + } + + // If none of the plots in the last page have been used recently, delete it. + if (!usedPlots) { +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("delete %d\n", fNumPages-1); + } +#endif + this->deactivateLastPage(); + } + } + + fPrevFlushToken = startTokenForNextFlush; +} + +bool GrDrawOpAtlas::createPages(GrProxyProvider* proxyProvider) { + SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight)); + + GrSurfaceDesc desc; + desc.fWidth = fTextureWidth; + desc.fHeight = fTextureHeight; + desc.fConfig = GrColorTypeToPixelConfig(fColorType); + + int numPlotsX = fTextureWidth/fPlotWidth; + int numPlotsY = fTextureHeight/fPlotHeight; + + for (uint32_t i = 0; i < this->maxPages(); ++i) { + fProxies[i] = proxyProvider->createProxy( + fFormat, desc, GrRenderable::kNo, 1, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, + SkBackingFit::kExact, SkBudgeted::kYes, GrProtected::kNo, + GrInternalSurfaceFlags::kNone, GrSurfaceProxy::UseAllocator::kNo); + if (!fProxies[i]) { + return false; + } + + // set up allocated plots + fPages[i].fPlotArray.reset(new sk_sp[ numPlotsX * numPlotsY ]); + + sk_sp* currPlot = fPages[i].fPlotArray.get(); + for (int y = numPlotsY - 1, r = 0; y >= 0; --y, ++r) { + for (int x = numPlotsX - 1, c = 0; x >= 0; --x, ++c) { + uint32_t plotIndex = r * numPlotsX + c; + currPlot->reset(new Plot(i, plotIndex, 1, x, y, fPlotWidth, fPlotHeight, + fColorType)); + + // build LRU list + fPages[i].fPlotList.addToHead(currPlot->get()); + ++currPlot; + } + } + + } + + return true; +} + + +bool GrDrawOpAtlas::activateNewPage(GrResourceProvider* resourceProvider) { + SkASSERT(fNumActivePages < this->maxPages()); + + if (!fProxies[fNumActivePages]->instantiate(resourceProvider)) { + return false; + } + +#ifdef DUMP_ATLAS_DATA + if (gDumpAtlasData) { + SkDebugf("activated page#: %d\n", fNumActivePages); + } +#endif + + ++fNumActivePages; + return true; +} + + +inline void GrDrawOpAtlas::deactivateLastPage() { + SkASSERT(fNumActivePages); + + uint32_t lastPageIndex = fNumActivePages - 1; + + int numPlotsX = fTextureWidth/fPlotWidth; + int numPlotsY = fTextureHeight/fPlotHeight; + + fPages[lastPageIndex].fPlotList.reset(); + for (int r = 0; r < numPlotsY; ++r) { + for (int c = 0; c < numPlotsX; ++c) { + uint32_t plotIndex = r * numPlotsX + c; + + Plot* currPlot = fPages[lastPageIndex].fPlotArray[plotIndex].get(); + currPlot->resetRects(); + currPlot->resetFlushesSinceLastUsed(); + + // rebuild the LRU list + SkDEBUGCODE(currPlot->fPrev = currPlot->fNext = nullptr); + SkDEBUGCODE(currPlot->fList = nullptr); + fPages[lastPageIndex].fPlotList.addToHead(currPlot); + } + } + + // remove ref to the backing texture + fProxies[lastPageIndex]->deinstantiate(); + --fNumActivePages; +} + +GrDrawOpAtlasConfig::GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes) { + static const SkISize kARGBDimensions[] = { + {256, 256}, // maxBytes < 2^19 + {512, 256}, // 2^19 <= maxBytes < 2^20 + {512, 512}, // 2^20 <= maxBytes < 2^21 + {1024, 512}, // 2^21 <= maxBytes < 2^22 + {1024, 1024}, // 2^22 <= maxBytes < 2^23 + {2048, 1024}, // 2^23 <= maxBytes + }; + + // Index 0 corresponds to maxBytes of 2^18, so start by dividing it by that + maxBytes >>= 18; + // Take the floor of the log to get the index + int index = maxBytes > 0 + ? SkTPin(SkPrevLog2(maxBytes), 0, SK_ARRAY_COUNT(kARGBDimensions) - 1) + : 0; + + SkASSERT(kARGBDimensions[index].width() <= kMaxAtlasDim); + SkASSERT(kARGBDimensions[index].height() <= kMaxAtlasDim); + fARGBDimensions.set(SkTMin(kARGBDimensions[index].width(), maxTextureSize), + SkTMin(kARGBDimensions[index].height(), maxTextureSize)); + fMaxTextureSize = SkTMin(maxTextureSize, kMaxAtlasDim); +} + +SkISize GrDrawOpAtlasConfig::atlasDimensions(GrMaskFormat type) const { + if (kA8_GrMaskFormat == type) { + // A8 is always 2x the ARGB dimensions, clamped to the max allowed texture size + return { SkTMin(2 * fARGBDimensions.width(), fMaxTextureSize), + SkTMin(2 * fARGBDimensions.height(), fMaxTextureSize) }; + } else { + return fARGBDimensions; + } +} + +SkISize GrDrawOpAtlasConfig::plotDimensions(GrMaskFormat type) const { + if (kA8_GrMaskFormat == type) { + SkISize atlasDimensions = this->atlasDimensions(type); + // For A8 we want to grow the plots at larger texture sizes to accept more of the + // larger SDF glyphs. Since the largest SDF glyph can be 170x170 with padding, this + // allows us to pack 3 in a 512x256 plot, or 9 in a 512x512 plot. + + // This will give us 512x256 plots for 2048x1024, 512x512 plots for 2048x2048, + // and 256x256 plots otherwise. + int plotWidth = atlasDimensions.width() >= 2048 ? 512 : 256; + int plotHeight = atlasDimensions.height() >= 2048 ? 512 : 256; + + return { plotWidth, plotHeight }; + } else { + // ARGB and LCD always use 256x256 plots -- this has been shown to be faster + return { 256, 256 }; + } +} + +constexpr int GrDrawOpAtlasConfig::kMaxAtlasDim; diff --git a/gfx/skia/skia/src/gpu/GrDrawOpAtlas.h b/gfx/skia/skia/src/gpu/GrDrawOpAtlas.h new file mode 100644 index 0000000000..5056d68078 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDrawOpAtlas.h @@ -0,0 +1,450 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawOpAtlas_DEFINED +#define GrDrawOpAtlas_DEFINED + +#include + +#include "include/core/SkSize.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkIPoint16.h" +#include "src/core/SkTInternalLList.h" + +#include "src/gpu/ops/GrDrawOp.h" + +class GrOnFlushResourceProvider; +class GrRectanizer; + + +/** + * This class manages one or more atlas textures on behalf of GrDrawOps. The draw ops that use the + * atlas perform texture uploads when preparing their draws during flush. The class provides + * facilities for using GrDrawOpUploadToken to detect data hazards. Op's uploads are performed in + * "ASAP" mode until it is impossible to add data without overwriting texels read by draws that + * have not yet executed on the gpu. At that point, the atlas will attempt to allocate a new + * atlas texture (or "page") of the same size, up to a maximum number of textures, and upload + * to that texture. If that's not possible, the uploads are performed "inline" between draws. If a + * single draw would use enough subimage space to overflow the atlas texture then the atlas will + * fail to add a subimage. This gives the op the chance to end the draw and begin a new one. + * Additional uploads will then succeed in inline mode. + * + * When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e., + * it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using + * excess space, periodic garbage collection is needed to shift data from the higher index pages to + * the lower ones, and then eventually remove any pages that are no longer in use. "In use" is + * determined by using the GrDrawUploadToken system: After a flush each subarea of the page + * is checked to see whether it was used in that flush; if it is not, a counter is incremented. + * Once that counter reaches a threshold that subarea is considered to be no longer in use. + * + * Garbage collection is initiated by the GrDrawOpAtlas's client via the compact() method. One + * solution is to make the client a subclass of GrOnFlushCallbackObject, register it with the + * GrContext via addOnFlushCallbackObject(), and the client's postFlush() method calls compact() + * and passes in the given GrDrawUploadToken. + */ +class GrDrawOpAtlas { +private: + static constexpr auto kMaxMultitexturePages = 4; + + +public: + /** Is the atlas allowed to use more than one texture? */ + enum class AllowMultitexturing : bool { kNo, kYes }; + + static constexpr int kMaxPlots = 32; // restricted by the fPlotAlreadyUpdated bitfield + // in BulkUseTokenUpdater + + /** + * An AtlasID is an opaque handle which callers can use to determine if the atlas contains + * a specific piece of data. + */ + typedef uint64_t AtlasID; + static const uint32_t kInvalidAtlasID = 0; + static const uint64_t kInvalidAtlasGeneration = 0; + + /** + * A function pointer for use as a callback during eviction. Whenever GrDrawOpAtlas evicts a + * specific AtlasID, it will call all of the registered listeners so they can process the + * eviction. + */ + typedef void (*EvictionFunc)(GrDrawOpAtlas::AtlasID, void*); + + /** + * Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas + * should only be used inside of GrMeshDrawOp::onPrepareDraws. + * @param GrColorType The colorType which this atlas will store + * @param width width in pixels of the atlas + * @param height height in pixels of the atlas + * @param numPlotsX The number of plots the atlas should be broken up into in the X + * direction + * @param numPlotsY The number of plots the atlas should be broken up into in the Y + * direction + * @param allowMultitexturing Can the atlas use more than one texture. + * @param func An eviction function which will be called whenever the atlas has to + * evict data + * @param data User supplied data which will be passed into func whenever an + * eviction occurs + * @return An initialized GrDrawOpAtlas, or nullptr if creation fails + */ + static std::unique_ptr Make(GrProxyProvider*, + const GrBackendFormat& format, + GrColorType, + int width, int height, + int plotWidth, int plotHeight, + AllowMultitexturing allowMultitexturing, + GrDrawOpAtlas::EvictionFunc func, void* data); + + /** + * Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns + * the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if + * the subimage cannot fit in the atlas without overwriting texels that will be read in the + * current draw. This indicates that the op should end its current draw and begin another + * before adding more data. Upon success, an upload of the provided image data will have + * been added to the GrDrawOp::Target, in "asap" mode if possible, otherwise in "inline" mode. + * Successive uploads in either mode may be consolidated. + * 'kError' will be returned when some unrecoverable error was encountered while trying to + * add the subimage. In this case the op being created should be discarded. + * + * NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call + * 'setUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to + * addToAtlas might cause the previous data to be overwritten before it has been read. + */ + + enum class ErrorCode { + kError, + kSucceeded, + kTryAgain + }; + + ErrorCode addToAtlas(GrResourceProvider*, AtlasID*, GrDeferredUploadTarget*, + int width, int height, + const void* image, SkIPoint16* loc); + + const sk_sp* getProxies() const { return fProxies; } + + uint64_t atlasGeneration() const { return fAtlasGeneration; } + + inline bool hasID(AtlasID id) { + if (kInvalidAtlasID == id) { + return false; + } + uint32_t plot = GetPlotIndexFromID(id); + SkASSERT(plot < fNumPlots); + uint32_t page = GetPageIndexFromID(id); + SkASSERT(page < fNumActivePages); + return fPages[page].fPlotArray[plot]->genID() == GetGenerationFromID(id); + } + + /** To ensure the atlas does not evict a given entry, the client must set the last use token. */ + inline void setLastUseToken(AtlasID id, GrDeferredUploadToken token) { + SkASSERT(this->hasID(id)); + uint32_t plotIdx = GetPlotIndexFromID(id); + SkASSERT(plotIdx < fNumPlots); + uint32_t pageIdx = GetPageIndexFromID(id); + SkASSERT(pageIdx < fNumActivePages); + Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get(); + this->makeMRU(plot, pageIdx); + plot->setLastUseToken(token); + } + + inline void registerEvictionCallback(EvictionFunc func, void* userData) { + EvictionData* data = fEvictionCallbacks.append(); + data->fFunc = func; + data->fData = userData; + } + + uint32_t numActivePages() { return fNumActivePages; } + + /** + * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk. The + * current max number of plots per page the GrDrawOpAtlas can handle is 32. If in the future + * this is insufficient then we can move to a 64 bit int. + */ + class BulkUseTokenUpdater { + public: + BulkUseTokenUpdater() { + memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated)); + } + BulkUseTokenUpdater(const BulkUseTokenUpdater& that) + : fPlotsToUpdate(that.fPlotsToUpdate) { + memcpy(fPlotAlreadyUpdated, that.fPlotAlreadyUpdated, sizeof(fPlotAlreadyUpdated)); + } + + bool add(AtlasID id) { + int index = GrDrawOpAtlas::GetPlotIndexFromID(id); + int pageIdx = GrDrawOpAtlas::GetPageIndexFromID(id); + if (this->find(pageIdx, index)) { + return false; + } + this->set(pageIdx, index); + return true; + } + + void reset() { + fPlotsToUpdate.reset(); + memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated)); + } + + struct PlotData { + PlotData(int pageIdx, int plotIdx) : fPageIndex(pageIdx), fPlotIndex(plotIdx) {} + uint32_t fPageIndex; + uint32_t fPlotIndex; + }; + + private: + bool find(int pageIdx, int index) const { + SkASSERT(index < kMaxPlots); + return (fPlotAlreadyUpdated[pageIdx] >> index) & 1; + } + + void set(int pageIdx, int index) { + SkASSERT(!this->find(pageIdx, index)); + fPlotAlreadyUpdated[pageIdx] |= (1 << index); + fPlotsToUpdate.push_back(PlotData(pageIdx, index)); + } + + static constexpr int kMinItems = 4; + SkSTArray fPlotsToUpdate; + uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages]; // TODO: increase this to uint64_t + // to allow more plots per page + + friend class GrDrawOpAtlas; + }; + + void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDeferredUploadToken token) { + int count = updater.fPlotsToUpdate.count(); + for (int i = 0; i < count; i++) { + const BulkUseTokenUpdater::PlotData& pd = updater.fPlotsToUpdate[i]; + // it's possible we've added a plot to the updater and subsequently the plot's page + // was deleted -- so we check to prevent a crash + if (pd.fPageIndex < fNumActivePages) { + Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get(); + this->makeMRU(plot, pd.fPageIndex); + plot->setLastUseToken(token); + } + } + } + + void compact(GrDeferredUploadToken startTokenForNextFlush); + + static uint32_t GetPageIndexFromID(AtlasID id) { + return id & 0xff; + } + + void instantiate(GrOnFlushResourceProvider*); + + uint32_t maxPages() const { + return fMaxPages; + } + + int numAllocated_TestingOnly() const; + void setMaxPages_TestingOnly(uint32_t maxPages); + +private: + GrDrawOpAtlas(GrProxyProvider*, const GrBackendFormat& format, GrColorType, int width, + int height, int plotWidth, int plotHeight, + AllowMultitexturing allowMultitexturing); + + /** + * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots + * keep track of subimage placement via their GrRectanizer. A Plot manages the lifetime of its + * data using two tokens, a last use token and a last upload token. Once a Plot is "full" (i.e. + * there is no room for the new subimage according to the GrRectanizer), it can no longer be + * used unless the last use of the Plot has already been flushed through to the gpu. + */ + class Plot : public SkRefCnt { + SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot); + + public: + /** index() is a unique id for the plot relative to the owning GrAtlas and page. */ + uint32_t index() const { return fPlotIndex; } + /** + * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know + * if a particular subimage is still present in the atlas. + */ + uint64_t genID() const { return fGenID; } + GrDrawOpAtlas::AtlasID id() const { + SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != fID); + return fID; + } + SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; }) + + bool addSubImage(int width, int height, const void* image, SkIPoint16* loc); + + /** + * To manage the lifetime of a plot, we use two tokens. We use the last upload token to + * know when we can 'piggy back' uploads, i.e. if the last upload hasn't been flushed to + * the gpu, we don't need to issue a new upload even if we update the cpu backing store. We + * use lastUse to determine when we can evict a plot from the cache, i.e. if the last use + * has already flushed through the gpu then we can reuse the plot. + */ + GrDeferredUploadToken lastUploadToken() const { return fLastUpload; } + GrDeferredUploadToken lastUseToken() const { return fLastUse; } + void setLastUploadToken(GrDeferredUploadToken token) { fLastUpload = token; } + void setLastUseToken(GrDeferredUploadToken token) { fLastUse = token; } + + void uploadToTexture(GrDeferredTextureUploadWritePixelsFn&, GrTextureProxy*); + void resetRects(); + + int flushesSinceLastUsed() { return fFlushesSinceLastUse; } + void resetFlushesSinceLastUsed() { fFlushesSinceLastUse = 0; } + void incFlushesSinceLastUsed() { fFlushesSinceLastUse++; } + + private: + Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY, + int width, int height, GrColorType colorType); + + ~Plot() override; + + /** + * Create a clone of this plot. The cloned plot will take the place of the current plot in + * the atlas + */ + Plot* clone() const { + return new Plot(fPageIndex, fPlotIndex, fGenID + 1, fX, fY, fWidth, fHeight, + fColorType); + } + + static GrDrawOpAtlas::AtlasID CreateId(uint32_t pageIdx, uint32_t plotIdx, + uint64_t generation) { + SkASSERT(pageIdx < (1 << 8)); + SkASSERT(pageIdx < kMaxMultitexturePages); + SkASSERT(plotIdx < (1 << 8)); + SkASSERT(generation < ((uint64_t)1 << 48)); + return generation << 16 | plotIdx << 8 | pageIdx; + } + + GrDeferredUploadToken fLastUpload; + GrDeferredUploadToken fLastUse; + // the number of flushes since this plot has been last used + int fFlushesSinceLastUse; + + struct { + const uint32_t fPageIndex : 16; + const uint32_t fPlotIndex : 16; + }; + uint64_t fGenID; + GrDrawOpAtlas::AtlasID fID; + unsigned char* fData; + const int fWidth; + const int fHeight; + const int fX; + const int fY; + GrRectanizer* fRects; + const SkIPoint16 fOffset; // the offset of the plot in the backing texture + const GrColorType fColorType; + const size_t fBytesPerPixel; + SkIRect fDirtyRect; + SkDEBUGCODE(bool fDirty); + + friend class GrDrawOpAtlas; + + typedef SkRefCnt INHERITED; + }; + + typedef SkTInternalLList PlotList; + + static uint32_t GetPlotIndexFromID(AtlasID id) { + return (id >> 8) & 0xff; + } + + // top 48 bits are reserved for the generation ID + static uint64_t GetGenerationFromID(AtlasID id) { + return (id >> 16) & 0xffffffffffff; + } + + inline bool updatePlot(GrDeferredUploadTarget*, AtlasID*, Plot*); + + inline void makeMRU(Plot* plot, int pageIdx) { + if (fPages[pageIdx].fPlotList.head() == plot) { + return; + } + + fPages[pageIdx].fPlotList.remove(plot); + fPages[pageIdx].fPlotList.addToHead(plot); + + // No MRU update for pages -- since we will always try to add from + // the front and remove from the back there is no need for MRU. + } + + bool uploadToPage(const GrCaps&, unsigned int pageIdx, AtlasID* id, + GrDeferredUploadTarget* target, int width, int height, const void* image, + SkIPoint16* loc); + + bool createPages(GrProxyProvider*); + bool activateNewPage(GrResourceProvider*); + void deactivateLastPage(); + + void processEviction(AtlasID); + inline void processEvictionAndResetRects(Plot* plot) { + this->processEviction(plot->id()); + plot->resetRects(); + } + + GrBackendFormat fFormat; + GrColorType fColorType; + int fTextureWidth; + int fTextureHeight; + int fPlotWidth; + int fPlotHeight; + unsigned int fNumPlots; + + uint64_t fAtlasGeneration; + // nextTokenToFlush() value at the end of the previous flush + GrDeferredUploadToken fPrevFlushToken; + + struct EvictionData { + EvictionFunc fFunc; + void* fData; + }; + + SkTDArray fEvictionCallbacks; + + struct Page { + // allocated array of Plots + std::unique_ptr[]> fPlotArray; + // LRU list of Plots (MRU at head - LRU at tail) + PlotList fPlotList; + }; + // proxies kept separate to make it easier to pass them up to client + sk_sp fProxies[kMaxMultitexturePages]; + Page fPages[kMaxMultitexturePages]; + uint32_t fMaxPages; + + uint32_t fNumActivePages; +}; + +// There are three atlases (A8, 565, ARGB) that are kept in relation with one another. In +// general, the A8 dimensions are 2x the 565 and ARGB dimensions with the constraint that an atlas +// size will always contain at least one plot. Since the ARGB atlas takes the most space, its +// dimensions are used to size the other two atlases. +class GrDrawOpAtlasConfig { +public: + // The capabilities of the GPU define maxTextureSize. The client provides maxBytes, and this + // represents the largest they want a single atlas texture to be. Due to multitexturing, we + // may expand temporarily to use more space as needed. + GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes); + + // For testing only - make minimum sized atlases -- a single plot for ARGB, four for A8 + GrDrawOpAtlasConfig() : GrDrawOpAtlasConfig(kMaxAtlasDim, 0) {} + + SkISize atlasDimensions(GrMaskFormat type) const; + SkISize plotDimensions(GrMaskFormat type) const; + +private: + // On some systems texture coordinates are represented using half-precision floating point, + // which limits the largest atlas dimensions to 2048x2048. + // For simplicity we'll use this constraint for all of our atlas textures. + // This can be revisited later if we need larger atlases. + static constexpr int kMaxAtlasDim = 2048; + + SkISize fARGBDimensions; + int fMaxTextureSize; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDrawOpTest.cpp b/gfx/skia/skia/src/gpu/GrDrawOpTest.cpp new file mode 100644 index 0000000000..5b93c3fa4a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDrawOpTest.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/GrContext_Base.h" +#include "include/utils/SkRandom.h" +#include "src/gpu/GrBaseContextPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrUserStencilSettings.h" + +#if GR_TEST_UTILS + +const GrUserStencilSettings* GrGetRandomStencil(SkRandom* random, GrContext_Base* context) { + if (context->priv().caps()->avoidStencilBuffers()) { + return &GrUserStencilSettings::kUnused; + } + static constexpr GrUserStencilSettings kReads( + GrUserStencilSettings::StaticInit< + 0x8080, + GrUserStencilTest::kLess, + 0xffff, + GrUserStencilOp::kKeep, + GrUserStencilOp::kKeep, + 0xffff>() + ); + static constexpr GrUserStencilSettings kWrites( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kReplace, + GrUserStencilOp::kReplace, + 0xffff>() + ); + static constexpr GrUserStencilSettings kReadsAndWrites( + GrUserStencilSettings::StaticInit< + 0x8000, + GrUserStencilTest::kEqual, + 0x6000, + GrUserStencilOp::kIncWrap, + GrUserStencilOp::kInvert, + 0x77ff>() + ); + + static const GrUserStencilSettings* kStencilSettings[] = { + &GrUserStencilSettings::kUnused, + &kReads, + &kWrites, + &kReadsAndWrites, + }; + return kStencilSettings[random->nextULessThan(SK_ARRAY_COUNT(kStencilSettings))]; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDrawOpTest.h b/gfx/skia/skia/src/gpu/GrDrawOpTest.h new file mode 100644 index 0000000000..fe8047cbb2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDrawOpTest.h @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawOpTest_DEFINED +#define GrDrawOpTest_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrTestUtils.h" + +#if GR_TEST_UTILS + +class GrContext_Base; +class GrDrawOp; +class GrPaint; +class GrRecordingContext; +class GrRenderTargetContext; +struct GrUserStencilSettings; +class SkRandom; + +/** This function draws a randomly configured GrDrawOp for testing purposes. */ +void GrDrawRandomOp(SkRandom*, GrRenderTargetContext*, GrPaint&&); + +/** GrDrawOp subclasses should define test factory functions using this macro. */ +#define GR_DRAW_OP_TEST_DEFINE(Op) \ + std::unique_ptr Op##__Test(GrPaint&& paint, SkRandom* random, \ + GrRecordingContext* context, int numSamples) +#define GR_DRAW_OP_TEST_FRIEND(Op) \ + friend std::unique_ptr Op##__Test(GrPaint&& paint, SkRandom* random, \ + GrRecordingContext* context, int numSamples) + +/** Helper for op test factories to pick a random stencil state. */ +const GrUserStencilSettings* GrGetRandomStencil(SkRandom* random, GrContext_Base*); + +#endif +#endif diff --git a/gfx/skia/skia/src/gpu/GrDrawingManager.cpp b/gfx/skia/skia/src/gpu/GrDrawingManager.cpp new file mode 100644 index 0000000000..b651700886 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDrawingManager.cpp @@ -0,0 +1,939 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrDrawingManager.h" + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "include/private/SkDeferredDisplayList.h" +#include "src/core/SkTTopoSort.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrClientMappedBufferManager.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrCopyRenderTask.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrRenderTask.h" +#include "src/gpu/GrResourceAllocator.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSoftwarePathRenderer.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureContext.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/GrTextureProxyPriv.h" +#include "src/gpu/GrTextureResolveRenderTask.h" +#include "src/gpu/GrTracing.h" +#include "src/gpu/GrTransferFromRenderTask.h" +#include "src/gpu/GrWaitRenderTask.h" +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" +#include "src/gpu/text/GrTextContext.h" +#include "src/image/SkSurface_Gpu.h" + +GrDrawingManager::RenderTaskDAG::RenderTaskDAG(bool sortRenderTasks) + : fSortRenderTasks(sortRenderTasks) {} + +GrDrawingManager::RenderTaskDAG::~RenderTaskDAG() {} + +void GrDrawingManager::RenderTaskDAG::gatherIDs(SkSTArray<8, uint32_t, true>* idArray) const { + idArray->reset(fRenderTasks.count()); + for (int i = 0; i < fRenderTasks.count(); ++i) { + if (fRenderTasks[i]) { + (*idArray)[i] = fRenderTasks[i]->uniqueID(); + } + } +} + +void GrDrawingManager::RenderTaskDAG::reset() { + fRenderTasks.reset(); +} + +void GrDrawingManager::RenderTaskDAG::removeRenderTask(int index) { + if (!fRenderTasks[index]->unique()) { + // TODO: Eventually this should be guaranteed unique: http://skbug.com/7111 + fRenderTasks[index]->endFlush(); + } + + fRenderTasks[index] = nullptr; +} + +void GrDrawingManager::RenderTaskDAG::removeRenderTasks(int startIndex, int stopIndex) { + for (int i = startIndex; i < stopIndex; ++i) { + if (!fRenderTasks[i]) { + continue; + } + this->removeRenderTask(i); + } +} + +bool GrDrawingManager::RenderTaskDAG::isUsed(GrSurfaceProxy* proxy) const { + for (int i = 0; i < fRenderTasks.count(); ++i) { + if (fRenderTasks[i] && fRenderTasks[i]->isUsed(proxy)) { + return true; + } + } + + return false; +} + +GrRenderTask* GrDrawingManager::RenderTaskDAG::add(sk_sp renderTask) { + if (renderTask) { + return fRenderTasks.emplace_back(std::move(renderTask)).get(); + } + return nullptr; +} + +GrRenderTask* GrDrawingManager::RenderTaskDAG::addBeforeLast(sk_sp renderTask) { + SkASSERT(!fRenderTasks.empty()); + if (renderTask) { + // Release 'fRenderTasks.back()' and grab the raw pointer, in case the SkTArray grows + // and reallocates during emplace_back. + fRenderTasks.emplace_back(fRenderTasks.back().release()); + return (fRenderTasks[fRenderTasks.count() - 2] = std::move(renderTask)).get(); + } + return nullptr; +} + +void GrDrawingManager::RenderTaskDAG::add(const SkTArray>& renderTasks) { + fRenderTasks.push_back_n(renderTasks.count(), renderTasks.begin()); +} + +void GrDrawingManager::RenderTaskDAG::swap(SkTArray>* renderTasks) { + SkASSERT(renderTasks->empty()); + renderTasks->swap(fRenderTasks); +} + +void GrDrawingManager::RenderTaskDAG::prepForFlush() { + if (fSortRenderTasks) { + SkDEBUGCODE(bool result =) SkTTopoSort( + &fRenderTasks); + SkASSERT(result); + } + +#ifdef SK_DEBUG + // This block checks for any unnecessary splits in the opsTasks. If two sequential opsTasks + // share the same backing GrSurfaceProxy it means the opsTask was artificially split. + if (fRenderTasks.count()) { + GrOpsTask* prevOpsTask = fRenderTasks[0]->asOpsTask(); + for (int i = 1; i < fRenderTasks.count(); ++i) { + GrOpsTask* curOpsTask = fRenderTasks[i]->asOpsTask(); + + if (prevOpsTask && curOpsTask) { + SkASSERT(prevOpsTask->fTarget.get() != curOpsTask->fTarget.get()); + } + + prevOpsTask = curOpsTask; + } + } +#endif +} + +void GrDrawingManager::RenderTaskDAG::closeAll(const GrCaps* caps) { + for (int i = 0; i < fRenderTasks.count(); ++i) { + if (fRenderTasks[i]) { + fRenderTasks[i]->makeClosed(*caps); + } + } +} + +void GrDrawingManager::RenderTaskDAG::cleanup(const GrCaps* caps) { + for (int i = 0; i < fRenderTasks.count(); ++i) { + if (!fRenderTasks[i]) { + continue; + } + + // no renderTask should receive a dependency + fRenderTasks[i]->makeClosed(*caps); + + // We shouldn't need to do this, but it turns out some clients still hold onto opsTasks + // after a cleanup. + // MDB TODO: is this still true? + if (!fRenderTasks[i]->unique()) { + // TODO: Eventually this should be guaranteed unique. + // https://bugs.chromium.org/p/skia/issues/detail?id=7111 + fRenderTasks[i]->endFlush(); + } + } + + fRenderTasks.reset(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +GrDrawingManager::GrDrawingManager(GrRecordingContext* context, + const GrPathRendererChain::Options& optionsForPathRendererChain, + const GrTextContext::Options& optionsForTextContext, + bool sortRenderTasks, + bool reduceOpsTaskSplitting) + : fContext(context) + , fOptionsForPathRendererChain(optionsForPathRendererChain) + , fOptionsForTextContext(optionsForTextContext) + , fDAG(sortRenderTasks) + , fTextContext(nullptr) + , fPathRendererChain(nullptr) + , fSoftwarePathRenderer(nullptr) + , fFlushing(false) + , fReduceOpsTaskSplitting(reduceOpsTaskSplitting) { +} + +void GrDrawingManager::cleanup() { + fDAG.cleanup(fContext->priv().caps()); + + fPathRendererChain = nullptr; + fSoftwarePathRenderer = nullptr; + + fOnFlushCBObjects.reset(); +} + +GrDrawingManager::~GrDrawingManager() { + this->cleanup(); +} + +bool GrDrawingManager::wasAbandoned() const { + return fContext->priv().abandoned(); +} + +void GrDrawingManager::freeGpuResources() { + for (int i = fOnFlushCBObjects.count() - 1; i >= 0; --i) { + if (!fOnFlushCBObjects[i]->retainOnFreeGpuResources()) { + // it's safe to just do this because we're iterating in reverse + fOnFlushCBObjects.removeShuffle(i); + } + } + + // a path renderer may be holding onto resources + fPathRendererChain = nullptr; + fSoftwarePathRenderer = nullptr; +} + +// MDB TODO: make use of the 'proxy' parameter. +GrSemaphoresSubmitted GrDrawingManager::flush(GrSurfaceProxy* proxies[], int numProxies, + SkSurface::BackendSurfaceAccess access, const GrFlushInfo& info, + const GrPrepareForExternalIORequests& externalRequests) { + SkASSERT(numProxies >= 0); + SkASSERT(!numProxies || proxies); + GR_CREATE_TRACE_MARKER_CONTEXT("GrDrawingManager", "flush", fContext); + + if (fFlushing || this->wasAbandoned()) { + if (info.fFinishedProc) { + info.fFinishedProc(info.fFinishedContext); + } + return GrSemaphoresSubmitted::kNo; + } + + SkDEBUGCODE(this->validate()); + + if (kNone_GrFlushFlags == info.fFlags && !info.fNumSemaphores && !info.fFinishedProc && + !externalRequests.hasRequests()) { + bool canSkip = numProxies > 0; + for (int i = 0; i < numProxies && canSkip; ++i) { + canSkip = !fDAG.isUsed(proxies[i]) && !this->isDDLTarget(proxies[i]); + } + if (canSkip) { + return GrSemaphoresSubmitted::kNo; + } + } + + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + if (info.fFinishedProc) { + info.fFinishedProc(info.fFinishedContext); + } + return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording + } + direct->priv().clientMappedBufferManager()->process(); + + GrGpu* gpu = direct->priv().getGpu(); + if (!gpu) { + if (info.fFinishedProc) { + info.fFinishedProc(info.fFinishedContext); + } + return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording + } + + fFlushing = true; + + auto resourceProvider = direct->priv().resourceProvider(); + auto resourceCache = direct->priv().getResourceCache(); + + // Semi-usually the GrRenderTasks are already closed at this point, but sometimes Ganesh needs + // to flush mid-draw. In that case, the SkGpuDevice's opsTasks won't be closed but need to be + // flushed anyway. Closing such opsTasks here will mean new ones will be created to replace them + // if the SkGpuDevice(s) write to them again. + fDAG.closeAll(fContext->priv().caps()); + fActiveOpsTask = nullptr; + + fDAG.prepForFlush(); + if (!fCpuBufferCache) { + // We cache more buffers when the backend is using client side arrays. Otherwise, we + // expect each pool will use a CPU buffer as a staging buffer before uploading to a GPU + // buffer object. Each pool only requires one staging buffer at a time. + int maxCachedBuffers = fContext->priv().caps()->preferClientSideDynamicBuffers() ? 2 : 6; + fCpuBufferCache = GrBufferAllocPool::CpuBufferCache::Make(maxCachedBuffers); + } + + GrOpFlushState flushState(gpu, resourceProvider, &fTokenTracker, fCpuBufferCache); + + GrOnFlushResourceProvider onFlushProvider(this); + // TODO: AFAICT the only reason fFlushState is on GrDrawingManager rather than on the + // stack here is to preserve the flush tokens. + + // Prepare any onFlush op lists (e.g. atlases). + if (!fOnFlushCBObjects.empty()) { + fDAG.gatherIDs(&fFlushingRenderTaskIDs); + + for (GrOnFlushCallbackObject* onFlushCBObject : fOnFlushCBObjects) { + onFlushCBObject->preFlush(&onFlushProvider, fFlushingRenderTaskIDs.begin(), + fFlushingRenderTaskIDs.count()); + } + for (const auto& onFlushRenderTask : fOnFlushRenderTasks) { + onFlushRenderTask->makeClosed(*fContext->priv().caps()); +#ifdef SK_DEBUG + // OnFlush callbacks are invoked during flush, and are therefore expected to handle + // resource allocation & usage on their own. (No deferred or lazy proxies!) + onFlushRenderTask->visitTargetAndSrcProxies_debugOnly( + [](GrSurfaceProxy* p, GrMipMapped mipMapped) { + SkASSERT(!p->asTextureProxy() || !p->asTextureProxy()->texPriv().isDeferred()); + SkASSERT(!p->isLazy()); + if (p->requiresManualMSAAResolve()) { + // The onFlush callback is responsible for ensuring MSAA gets resolved. + SkASSERT(p->asRenderTargetProxy() && !p->asRenderTargetProxy()->isMSAADirty()); + } + if (GrMipMapped::kYes == mipMapped) { + // The onFlush callback is responsible for regenerating mips if needed. + SkASSERT(p->asTextureProxy() && !p->asTextureProxy()->mipMapsAreDirty()); + } + }); +#endif + onFlushRenderTask->prepare(&flushState); + } + } + +#if 0 + // Enable this to print out verbose GrOp information + SkDEBUGCODE(SkDebugf("onFlush renderTasks:")); + for (const auto& onFlushRenderTask : fOnFlushRenderTasks) { + SkDEBUGCODE(onFlushRenderTask->dump();) + } + SkDEBUGCODE(SkDebugf("Normal renderTasks:")); + for (int i = 0; i < fRenderTasks.count(); ++i) { + SkDEBUGCODE(fRenderTasks[i]->dump();) + } +#endif + + int startIndex, stopIndex; + bool flushed = false; + + { + GrResourceAllocator alloc(resourceProvider SkDEBUGCODE(, fDAG.numRenderTasks())); + for (int i = 0; i < fDAG.numRenderTasks(); ++i) { + if (fDAG.renderTask(i)) { + fDAG.renderTask(i)->gatherProxyIntervals(&alloc); + } + alloc.markEndOfOpsTask(i); + } + alloc.determineRecyclability(); + + GrResourceAllocator::AssignError error = GrResourceAllocator::AssignError::kNoError; + int numRenderTasksExecuted = 0; + while (alloc.assign(&startIndex, &stopIndex, &error)) { + if (GrResourceAllocator::AssignError::kFailedProxyInstantiation == error) { + for (int i = startIndex; i < stopIndex; ++i) { + GrRenderTask* renderTask = fDAG.renderTask(i); + if (!renderTask) { + continue; + } + if (!renderTask->isInstantiated()) { + // No need to call the renderTask's handleInternalAllocationFailure + // since we will already skip executing the renderTask since it is not + // instantiated. + continue; + } + renderTask->handleInternalAllocationFailure(); + } + } + + if (this->executeRenderTasks( + startIndex, stopIndex, &flushState, &numRenderTasksExecuted)) { + flushed = true; + } + } + } + +#ifdef SK_DEBUG + for (int i = 0; i < fDAG.numRenderTasks(); ++i) { + // If there are any remaining opsTaskss at this point, make sure they will not survive the + // flush. Otherwise we need to call endFlush() on them. + // http://skbug.com/7111 + SkASSERT(!fDAG.renderTask(i) || fDAG.renderTask(i)->unique()); + } +#endif + fDAG.reset(); + this->clearDDLTargets(); + +#ifdef SK_DEBUG + // In non-DDL mode this checks that all the flushed ops have been freed from the memory pool. + // When we move to partial flushes this assert will no longer be valid. + // In DDL mode this check is somewhat superfluous since the memory for most of the ops/opsTasks + // will be stored in the DDL's GrOpMemoryPools. + GrOpMemoryPool* opMemoryPool = fContext->priv().opMemoryPool(); + opMemoryPool->isEmpty(); +#endif + + GrSemaphoresSubmitted result = gpu->finishFlush(proxies, numProxies, access, info, + externalRequests); + + // Give the cache a chance to purge resources that become purgeable due to flushing. + if (flushed) { + resourceCache->purgeAsNeeded(); + flushed = false; + } + for (GrOnFlushCallbackObject* onFlushCBObject : fOnFlushCBObjects) { + onFlushCBObject->postFlush(fTokenTracker.nextTokenToFlush(), fFlushingRenderTaskIDs.begin(), + fFlushingRenderTaskIDs.count()); + flushed = true; + } + if (flushed) { + resourceCache->purgeAsNeeded(); + } + fFlushingRenderTaskIDs.reset(); + fFlushing = false; + + return result; +} + +bool GrDrawingManager::executeRenderTasks(int startIndex, int stopIndex, GrOpFlushState* flushState, + int* numRenderTasksExecuted) { + SkASSERT(startIndex <= stopIndex && stopIndex <= fDAG.numRenderTasks()); + +#if GR_FLUSH_TIME_OP_SPEW + SkDebugf("Flushing opsTask: %d to %d out of [%d, %d]\n", + startIndex, stopIndex, 0, fDAG.numRenderTasks()); + for (int i = startIndex; i < stopIndex; ++i) { + if (fDAG.renderTask(i)) { + fDAG.renderTask(i)->dump(true); + } + } +#endif + + bool anyRenderTasksExecuted = false; + + for (int i = startIndex; i < stopIndex; ++i) { + GrRenderTask* renderTask = fDAG.renderTask(i); + if (!renderTask || !renderTask->isInstantiated()) { + continue; + } + + SkASSERT(renderTask->deferredProxiesAreInstantiated()); + + renderTask->prepare(flushState); + } + + // Upload all data to the GPU + flushState->preExecuteDraws(); + + // For Vulkan, if we have too many oplists to be flushed we end up allocating a lot of resources + // for each command buffer associated with the oplists. If this gets too large we can cause the + // devices to go OOM. In practice we usually only hit this case in our tests, but to be safe we + // put a cap on the number of oplists we will execute before flushing to the GPU to relieve some + // memory pressure. + static constexpr int kMaxRenderTasksBeforeFlush = 100; + + // Execute the onFlush renderTasks first, if any. + for (sk_sp& onFlushRenderTask : fOnFlushRenderTasks) { + if (!onFlushRenderTask->execute(flushState)) { + SkDebugf("WARNING: onFlushRenderTask failed to execute.\n"); + } + SkASSERT(onFlushRenderTask->unique()); + onFlushRenderTask = nullptr; + (*numRenderTasksExecuted)++; + if (*numRenderTasksExecuted >= kMaxRenderTasksBeforeFlush) { + flushState->gpu()->finishFlush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess, + GrFlushInfo(), GrPrepareForExternalIORequests()); + *numRenderTasksExecuted = 0; + } + } + fOnFlushRenderTasks.reset(); + + // Execute the normal op lists. + for (int i = startIndex; i < stopIndex; ++i) { + GrRenderTask* renderTask = fDAG.renderTask(i); + if (!renderTask || !renderTask->isInstantiated()) { + continue; + } + + if (renderTask->execute(flushState)) { + anyRenderTasksExecuted = true; + } + (*numRenderTasksExecuted)++; + if (*numRenderTasksExecuted >= kMaxRenderTasksBeforeFlush) { + flushState->gpu()->finishFlush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess, + GrFlushInfo(), GrPrepareForExternalIORequests()); + *numRenderTasksExecuted = 0; + } + } + + SkASSERT(!flushState->opsRenderPass()); + SkASSERT(fTokenTracker.nextDrawToken() == fTokenTracker.nextTokenToFlush()); + + // We reset the flush state before the RenderTasks so that the last resources to be freed are + // those that are written to in the RenderTasks. This helps to make sure the most recently used + // resources are the last to be purged by the resource cache. + flushState->reset(); + + fDAG.removeRenderTasks(startIndex, stopIndex); + + return anyRenderTasksExecuted; +} + +GrSemaphoresSubmitted GrDrawingManager::flushSurfaces(GrSurfaceProxy* proxies[], int numProxies, + SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info) { + if (this->wasAbandoned()) { + return GrSemaphoresSubmitted::kNo; + } + SkDEBUGCODE(this->validate()); + SkASSERT(numProxies >= 0); + SkASSERT(!numProxies || proxies); + + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording + } + + GrGpu* gpu = direct->priv().getGpu(); + if (!gpu) { + return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording + } + + // TODO: It is important to upgrade the drawingmanager to just flushing the + // portion of the DAG required by 'proxies' in order to restore some of the + // semantics of this method. + GrSemaphoresSubmitted result = this->flush(proxies, numProxies, access, info, + GrPrepareForExternalIORequests()); + for (int i = 0; i < numProxies; ++i) { + GrSurfaceProxy* proxy = proxies[i]; + if (!proxy->isInstantiated()) { + return result; + } + // In the flushSurfaces case, we need to resolve MSAA immediately after flush. This is + // because the client will call through to this method when drawing into a target created by + // wrapBackendTextureAsRenderTarget, and will expect the original texture to be fully + // resolved upon return. + if (proxy->requiresManualMSAAResolve()) { + auto* rtProxy = proxy->asRenderTargetProxy(); + SkASSERT(rtProxy); + if (rtProxy->isMSAADirty()) { + SkASSERT(rtProxy->peekRenderTarget()); + gpu->resolveRenderTarget(rtProxy->peekRenderTarget(), rtProxy->msaaDirtyRect(), + rtProxy->origin(), GrGpu::ForExternalIO::kYes); + rtProxy->markMSAAResolved(); + } + } + // If, after a flush, any of the proxies of interest have dirty mipmaps, regenerate them in + // case their backend textures are being stolen. + // (This special case is exercised by the ReimportImageTextureWithMipLevels test.) + // FIXME: It may be more ideal to plumb down a "we're going to steal the backends" flag. + if (auto* textureProxy = proxy->asTextureProxy()) { + if (textureProxy->mipMapsAreDirty()) { + SkASSERT(textureProxy->peekTexture()); + gpu->regenerateMipMapLevels(textureProxy->peekTexture()); + textureProxy->markMipMapsClean(); + } + } + } + + SkDEBUGCODE(this->validate()); + return result; +} + +void GrDrawingManager::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) { + fOnFlushCBObjects.push_back(onFlushCBObject); +} + +#if GR_TEST_UTILS +void GrDrawingManager::testingOnly_removeOnFlushCallbackObject(GrOnFlushCallbackObject* cb) { + int n = std::find(fOnFlushCBObjects.begin(), fOnFlushCBObjects.end(), cb) - + fOnFlushCBObjects.begin(); + SkASSERT(n < fOnFlushCBObjects.count()); + fOnFlushCBObjects.removeShuffle(n); +} +#endif + +void GrDrawingManager::moveRenderTasksToDDL(SkDeferredDisplayList* ddl) { + SkDEBUGCODE(this->validate()); + + // no renderTask should receive a new command after this + fDAG.closeAll(fContext->priv().caps()); + fActiveOpsTask = nullptr; + + fDAG.swap(&ddl->fRenderTasks); + + for (auto renderTask : ddl->fRenderTasks) { + renderTask->prePrepare(fContext); + } + + if (fPathRendererChain) { + if (auto ccpr = fPathRendererChain->getCoverageCountingPathRenderer()) { + ddl->fPendingPaths = ccpr->detachPendingPaths(); + } + } + + SkDEBUGCODE(this->validate()); +} + +void GrDrawingManager::copyRenderTasksFromDDL(const SkDeferredDisplayList* ddl, + GrRenderTargetProxy* newDest) { + SkDEBUGCODE(this->validate()); + + if (fActiveOpsTask) { + // This is a temporary fix for the partial-MDB world. In that world we're not + // reordering so ops that (in the single opsTask world) would've just glommed onto the + // end of the single opsTask but referred to a far earlier RT need to appear in their + // own opsTask. + fActiveOpsTask->makeClosed(*fContext->priv().caps()); + fActiveOpsTask = nullptr; + } + + this->addDDLTarget(newDest); + + // Here we jam the proxy that backs the current replay SkSurface into the LazyProxyData. + // The lazy proxy that references it (in the copied opsTasks) will steal its GrTexture. + ddl->fLazyProxyData->fReplayDest = newDest; + + if (ddl->fPendingPaths.size()) { + GrCoverageCountingPathRenderer* ccpr = this->getCoverageCountingPathRenderer(); + + ccpr->mergePendingPaths(ddl->fPendingPaths); + } + + fDAG.add(ddl->fRenderTasks); + + SkDEBUGCODE(this->validate()); +} + +#ifdef SK_DEBUG +void GrDrawingManager::validate() const { + if (fDAG.sortingRenderTasks() && fReduceOpsTaskSplitting) { + SkASSERT(!fActiveOpsTask); + } else { + if (fActiveOpsTask) { + SkASSERT(!fDAG.empty()); + SkASSERT(!fActiveOpsTask->isClosed()); + SkASSERT(fActiveOpsTask == fDAG.back()); + } + + for (int i = 0; i < fDAG.numRenderTasks(); ++i) { + if (fActiveOpsTask != fDAG.renderTask(i)) { + // The resolveTask associated with the activeTask remains open for as long as the + // activeTask does. + bool isActiveResolveTask = + fActiveOpsTask && fActiveOpsTask->fTextureResolveTask == fDAG.renderTask(i); + SkASSERT(isActiveResolveTask || fDAG.renderTask(i)->isClosed()); + } + } + + if (!fDAG.empty() && !fDAG.back()->isClosed()) { + SkASSERT(fActiveOpsTask == fDAG.back()); + } + } +} +#endif + +void GrDrawingManager::closeRenderTasksForNewRenderTask(GrSurfaceProxy* target) { + if (target && fDAG.sortingRenderTasks() && fReduceOpsTaskSplitting) { + // In this case we need to close all the renderTasks that rely on the current contents of + // 'target'. That is bc we're going to update the content of the proxy so they need to be + // split in case they use both the old and new content. (This is a bit of an overkill: they + // really only need to be split if they ever reference proxy's contents again but that is + // hard to predict/handle). + if (GrRenderTask* lastRenderTask = target->getLastRenderTask()) { + lastRenderTask->closeThoseWhoDependOnMe(*fContext->priv().caps()); + } + } else if (fActiveOpsTask) { + // This is a temporary fix for the partial-MDB world. In that world we're not + // reordering so ops that (in the single opsTask world) would've just glommed onto the + // end of the single opsTask but referred to a far earlier RT need to appear in their + // own opsTask. + fActiveOpsTask->makeClosed(*fContext->priv().caps()); + fActiveOpsTask = nullptr; + } +} + +sk_sp GrDrawingManager::newOpsTask(sk_sp rtp, bool managedOpsTask) { + SkDEBUGCODE(this->validate()); + SkASSERT(fContext); + + this->closeRenderTasksForNewRenderTask(rtp.get()); + + sk_sp opsTask(new GrOpsTask(fContext->priv().refOpMemoryPool(), rtp, + fContext->priv().auditTrail())); + SkASSERT(rtp->getLastRenderTask() == opsTask.get()); + + if (managedOpsTask) { + fDAG.add(opsTask); + + if (!fDAG.sortingRenderTasks() || !fReduceOpsTaskSplitting) { + fActiveOpsTask = opsTask.get(); + } + } + + SkDEBUGCODE(this->validate()); + return opsTask; +} + +GrTextureResolveRenderTask* GrDrawingManager::newTextureResolveRenderTask(const GrCaps& caps) { + // Unlike in the "new opsTask" case, we do not want to close the active opsTask, nor (if we are + // in sorting and opsTask reduction mode) the render tasks that depend on any proxy's current + // state. This is because those opsTasks can still receive new ops and because if they refer to + // the mipmapped version of 'proxy', they will then come to depend on the render task being + // created here. + // + // Add the new textureResolveTask before the fActiveOpsTask (if not in + // sorting/opsTask-splitting-reduction mode) because it will depend upon this resolve task. + // NOTE: Putting it here will also reduce the amount of work required by the topological sort. + return static_cast(fDAG.addBeforeLast( + sk_make_sp())); +} + +void GrDrawingManager::newWaitRenderTask(sk_sp proxy, + std::unique_ptr[]> semaphores, + int numSemaphores) { + SkDEBUGCODE(this->validate()); + SkASSERT(fContext); + + const GrCaps& caps = *fContext->priv().caps(); + + sk_sp waitTask = sk_make_sp(proxy, std::move(semaphores), + numSemaphores); + if (fReduceOpsTaskSplitting) { + GrRenderTask* lastTask = proxy->getLastRenderTask(); + if (lastTask && !lastTask->isClosed()) { + // We directly make the currently open renderTask depend on waitTask instead of using + // the proxy version of addDependency. The waitTask will never need to trigger any + // resolves or mip map generation which is the main advantage of going through the proxy + // version. Additionally we would've had to temporarily set the wait task as the + // lastRenderTask on the proxy, add the dependency, and then reset the lastRenderTask to + // lastTask. Additionally we add all dependencies of lastTask to waitTask so that the + // waitTask doesn't get reordered before them and unnecessarily block those tasks. + // Note: Any previous Ops already in lastTask will get blocked by the wait semaphore + // even though they don't need to be for correctness. + + // Make sure we add the dependencies of lastTask to waitTask first or else we'll get a + // circular self dependency of waitTask on waitTask. + waitTask->addDependenciesFromOtherTask(lastTask); + lastTask->addDependency(waitTask.get()); + } else { + // If there is a last task we set the waitTask to depend on it so that it doesn't get + // reordered in front of the lastTask causing the lastTask to be blocked by the + // semaphore. Again we directly just go through adding the dependency to the task and + // not the proxy since we don't need to worry about resolving anything. + if (lastTask) { + waitTask->addDependency(lastTask); + } + proxy->setLastRenderTask(waitTask.get()); + } + fDAG.add(waitTask); + } else { + if (fActiveOpsTask && (fActiveOpsTask->fTarget == proxy)) { + SkASSERT(proxy->getLastRenderTask() == fActiveOpsTask); + fDAG.addBeforeLast(waitTask); + // In this case we keep the current renderTask open but just insert the new waitTask + // before it in the list. The waitTask will never need to trigger any resolves or mip + // map generation which is the main advantage of going through the proxy version. + // Additionally we would've had to temporarily set the wait task as the lastRenderTask + // on the proxy, add the dependency, and then reset the lastRenderTask to + // fActiveOpsTask. Additionally we make the waitTask depend on all of fActiveOpsTask + // dependencies so that we don't unnecessarily reorder the waitTask before them. + // Note: Any previous Ops already in fActiveOpsTask will get blocked by the wait + // semaphore even though they don't need to be for correctness. + + // Make sure we add the dependencies of fActiveOpsTask to waitTask first or else we'll + // get a circular self dependency of waitTask on waitTask. + waitTask->addDependenciesFromOtherTask(fActiveOpsTask); + fActiveOpsTask->addDependency(waitTask.get()); + } else { + // In this case we just close the previous RenderTask and start and append the waitTask + // to the DAG. Since it is the last task now we call setLastRenderTask on the proxy. If + // there is a lastTask on the proxy we make waitTask depend on that task. This + // dependency isn't strictly needed but it does keep the DAG from reordering the + // waitTask earlier and blocking more tasks. + if (GrRenderTask* lastTask = proxy->getLastRenderTask()) { + waitTask->addDependency(lastTask); + } + proxy->setLastRenderTask(waitTask.get()); + this->closeRenderTasksForNewRenderTask(proxy.get()); + fDAG.add(waitTask); + } + } + waitTask->makeClosed(caps); + + SkDEBUGCODE(this->validate()); +} + +void GrDrawingManager::newTransferFromRenderTask(sk_sp srcProxy, + const SkIRect& srcRect, + GrColorType surfaceColorType, + GrColorType dstColorType, + sk_sp dstBuffer, + size_t dstOffset) { + SkDEBUGCODE(this->validate()); + SkASSERT(fContext); + // This copies from srcProxy to dstBuffer so it doesn't have a real target. + this->closeRenderTasksForNewRenderTask(nullptr); + + GrRenderTask* task = fDAG.add(sk_make_sp( + srcProxy, srcRect, surfaceColorType, dstColorType, std::move(dstBuffer), dstOffset)); + + const GrCaps& caps = *fContext->priv().caps(); + + // We always say GrMipMapped::kNo here since we are always just copying from the base layer. We + // don't need to make sure the whole mip map chain is valid. + task->addDependency(srcProxy.get(), GrMipMapped::kNo, GrTextureResolveManager(this), caps); + task->makeClosed(caps); + + // We have closed the previous active oplist but since a new oplist isn't being added there + // shouldn't be an active one. + SkASSERT(!fActiveOpsTask); + SkDEBUGCODE(this->validate()); +} + +bool GrDrawingManager::newCopyRenderTask(sk_sp srcProxy, + const SkIRect& srcRect, + sk_sp dstProxy, + const SkIPoint& dstPoint) { + SkDEBUGCODE(this->validate()); + SkASSERT(fContext); + + this->closeRenderTasksForNewRenderTask(dstProxy.get()); + const GrCaps& caps = *fContext->priv().caps(); + + GrRenderTask* task = + fDAG.add(GrCopyRenderTask::Make(srcProxy, srcRect, dstProxy, dstPoint, &caps)); + if (!task) { + return false; + } + + + // We always say GrMipMapped::kNo here since we are always just copying from the base layer to + // another base layer. We don't need to make sure the whole mip map chain is valid. + task->addDependency(srcProxy.get(), GrMipMapped::kNo, GrTextureResolveManager(this), caps); + task->makeClosed(caps); + + // We have closed the previous active oplist but since a new oplist isn't being added there + // shouldn't be an active one. + SkASSERT(!fActiveOpsTask); + SkDEBUGCODE(this->validate()); + return true; +} + +GrTextContext* GrDrawingManager::getTextContext() { + if (!fTextContext) { + fTextContext = GrTextContext::Make(fOptionsForTextContext); + } + + return fTextContext.get(); +} + +/* + * This method finds a path renderer that can draw the specified path on + * the provided target. + * Due to its expense, the software path renderer has split out so it can + * can be individually allowed/disallowed via the "allowSW" boolean. + */ +GrPathRenderer* GrDrawingManager::getPathRenderer(const GrPathRenderer::CanDrawPathArgs& args, + bool allowSW, + GrPathRendererChain::DrawType drawType, + GrPathRenderer::StencilSupport* stencilSupport) { + + if (!fPathRendererChain) { + fPathRendererChain.reset(new GrPathRendererChain(fContext, fOptionsForPathRendererChain)); + } + + GrPathRenderer* pr = fPathRendererChain->getPathRenderer(args, drawType, stencilSupport); + if (!pr && allowSW) { + auto swPR = this->getSoftwarePathRenderer(); + if (GrPathRenderer::CanDrawPath::kNo != swPR->canDrawPath(args)) { + pr = swPR; + } + } + + return pr; +} + +GrPathRenderer* GrDrawingManager::getSoftwarePathRenderer() { + if (!fSoftwarePathRenderer) { + fSoftwarePathRenderer.reset( + new GrSoftwarePathRenderer(fContext->priv().proxyProvider(), + fOptionsForPathRendererChain.fAllowPathMaskCaching)); + } + return fSoftwarePathRenderer.get(); +} + +GrCoverageCountingPathRenderer* GrDrawingManager::getCoverageCountingPathRenderer() { + if (!fPathRendererChain) { + fPathRendererChain.reset(new GrPathRendererChain(fContext, fOptionsForPathRendererChain)); + } + return fPathRendererChain->getCoverageCountingPathRenderer(); +} + +void GrDrawingManager::flushIfNecessary() { + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + return; + } + + auto resourceCache = direct->priv().getResourceCache(); + if (resourceCache && resourceCache->requestsFlush()) { + this->flush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess, GrFlushInfo(), + GrPrepareForExternalIORequests()); + resourceCache->purgeAsNeeded(); + } +} + +std::unique_ptr GrDrawingManager::makeRenderTargetContext( + sk_sp sProxy, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + bool managedOpsTask) { + if (this->wasAbandoned() || !sProxy->asRenderTargetProxy()) { + return nullptr; + } + + sk_sp renderTargetProxy(sk_ref_sp(sProxy->asRenderTargetProxy())); + + return std::unique_ptr( + new GrRenderTargetContext(fContext, + std::move(renderTargetProxy), + colorType, + std::move(colorSpace), + surfaceProps, + managedOpsTask)); +} + +std::unique_ptr GrDrawingManager::makeTextureContext( + sk_sp sProxy, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace) { + if (this->wasAbandoned() || !sProxy->asTextureProxy()) { + return nullptr; + } + + // GrTextureRenderTargets should always be using a GrRenderTargetContext + SkASSERT(!sProxy->asRenderTargetProxy()); + + sk_sp textureProxy(sk_ref_sp(sProxy->asTextureProxy())); + + return std::unique_ptr(new GrTextureContext( + fContext, std::move(textureProxy), colorType, alphaType, std::move(colorSpace))); +} diff --git a/gfx/skia/skia/src/gpu/GrDrawingManager.h b/gfx/skia/skia/src/gpu/GrDrawingManager.h new file mode 100644 index 0000000000..72f3211ba1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDrawingManager.h @@ -0,0 +1,246 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawingManager_DEFINED +#define GrDrawingManager_DEFINED + +#include +#include "include/core/SkSurface.h" +#include "include/private/SkTArray.h" +#include "src/gpu/GrBufferAllocPool.h" +#include "src/gpu/GrDeferredUpload.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/GrPathRendererChain.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/text/GrTextContext.h" + +class GrCoverageCountingPathRenderer; +class GrOnFlushCallbackObject; +class GrOpFlushState; +class GrOpsTask; +class GrRecordingContext; +class GrRenderTargetContext; +class GrRenderTargetProxy; +class GrSoftwarePathRenderer; +class GrTextureContext; +class GrTextureResolveRenderTask; +class SkDeferredDisplayList; + +class GrDrawingManager { +public: + ~GrDrawingManager(); + + void freeGpuResources(); + + std::unique_ptr makeRenderTargetContext(sk_sp, + GrColorType, + sk_sp, + const SkSurfaceProps*, + bool managedOpsTask = true); + std::unique_ptr makeTextureContext(sk_sp, + GrColorType, + SkAlphaType, + sk_sp); + + // A managed opsTask is controlled by the drawing manager (i.e., sorted & flushed with the + // others). An unmanaged one is created and used by the onFlushCallback. + sk_sp newOpsTask(sk_sp, bool managedOpsTask); + + // Create a render task that can resolve MSAA and/or regenerate mipmap levels on proxies. This + // method will only add the new render task to the list. It is up to the caller to call + // addProxy() on the returned object. + GrTextureResolveRenderTask* newTextureResolveRenderTask(const GrCaps&); + + // Create a new render task that will cause the gpu to wait on semaphores before executing any + // more RenderTasks that target proxy. It is possible for this wait to also block additional + // work (even to other proxies) that has already been recorded or will be recorded later. The + // only guarantee is that future work to the passed in proxy will wait on the semaphores to be + // signaled. + void newWaitRenderTask(sk_sp proxy, std::unique_ptr[]>, + int numSemaphores); + + // Create a new render task which copies the pixels from the srcProxy into the dstBuffer. This + // is used to support the asynchronous readback API. The srcRect is the region of the srcProxy + // to be copied. The surfaceColorType says how we should interpret the data when reading back + // from the source. DstColorType describes how the data should be stored in the dstBuffer. + // DstOffset is the offset into the dstBuffer where we will start writing data. + void newTransferFromRenderTask(sk_sp srcProxy, const SkIRect& srcRect, + GrColorType surfaceColorType, GrColorType dstColorType, + sk_sp dstBuffer, size_t dstOffset); + + // Creates a new render task which copies a pixel rectangle from srcProxy into dstProxy. The src + // pixels copied are specified by srcRect. They are copied to a rect of the same size in + // dstProxy with top left at dstPoint. If the src rect is clipped by the src bounds then pixel + // values in the dst rect corresponding to the area clipped by the src rect are not overwritten. + // This method is not guaranteed to succeed depending on the type of surface, formats, etc, and + // the backend-specific limitations. + bool newCopyRenderTask(sk_sp srcProxy, const SkIRect& srcRect, + sk_sp dstProxy, const SkIPoint& dstPoint); + + GrRecordingContext* getContext() { return fContext; } + + GrTextContext* getTextContext(); + + GrPathRenderer* getPathRenderer(const GrPathRenderer::CanDrawPathArgs& args, + bool allowSW, + GrPathRendererChain::DrawType drawType, + GrPathRenderer::StencilSupport* stencilSupport = nullptr); + + GrPathRenderer* getSoftwarePathRenderer(); + + // Returns a direct pointer to the coverage counting path renderer, or null if it is not + // supported and turned on. + GrCoverageCountingPathRenderer* getCoverageCountingPathRenderer(); + + void flushIfNecessary(); + + static bool ProgramUnitTest(GrContext* context, int maxStages, int maxLevels); + + GrSemaphoresSubmitted flushSurfaces(GrSurfaceProxy* proxies[], + int cnt, + SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info); + GrSemaphoresSubmitted flushSurface(GrSurfaceProxy* proxy, + SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info) { + return this->flushSurfaces(&proxy, 1, access, info); + } + + void addOnFlushCallbackObject(GrOnFlushCallbackObject*); + +#if GR_TEST_UTILS + void testingOnly_removeOnFlushCallbackObject(GrOnFlushCallbackObject*); +#endif + + void moveRenderTasksToDDL(SkDeferredDisplayList* ddl); + void copyRenderTasksFromDDL(const SkDeferredDisplayList*, GrRenderTargetProxy* newDest); + +private: + // This class encapsulates maintenance and manipulation of the drawing manager's DAG of + // renderTasks. + class RenderTaskDAG { + public: + RenderTaskDAG(bool sortRenderTasks); + ~RenderTaskDAG(); + + // Currently, when explicitly allocating resources, this call will topologically sort the + // GrRenderTasks. + // MDB TODO: remove once incremental GrRenderTask sorting is enabled + void prepForFlush(); + + void closeAll(const GrCaps* caps); + + // A yucky combination of closeAll and reset + void cleanup(const GrCaps* caps); + + void gatherIDs(SkSTArray<8, uint32_t, true>* idArray) const; + + void reset(); + + // These calls forceably remove a GrRenderTask from the DAG. They are problematic bc they + // just remove the GrRenderTask but don't cleanup any refering pointers (i.e., dependency + // pointers in the DAG). They work right now bc they are only called at flush time, after + // the topological sort is complete (so the dangling pointers aren't used). + void removeRenderTask(int index); + void removeRenderTasks(int startIndex, int stopIndex); + + bool empty() const { return fRenderTasks.empty(); } + int numRenderTasks() const { return fRenderTasks.count(); } + + bool isUsed(GrSurfaceProxy*) const; + + GrRenderTask* renderTask(int index) { return fRenderTasks[index].get(); } + const GrRenderTask* renderTask(int index) const { return fRenderTasks[index].get(); } + + GrRenderTask* back() { return fRenderTasks.back().get(); } + const GrRenderTask* back() const { return fRenderTasks.back().get(); } + + GrRenderTask* add(sk_sp); + GrRenderTask* addBeforeLast(sk_sp); + void add(const SkTArray>&); + + void swap(SkTArray>* renderTasks); + + bool sortingRenderTasks() const { return fSortRenderTasks; } + + private: + SkTArray> fRenderTasks; + bool fSortRenderTasks; + }; + + GrDrawingManager(GrRecordingContext*, const GrPathRendererChain::Options&, + const GrTextContext::Options&, + bool sortRenderTasks, + bool reduceOpsTaskSplitting); + + bool wasAbandoned() const; + + void cleanup(); + + // Closes the target's dependent render tasks (or, if not in sorting/opsTask-splitting-reduction + // mode, closes fActiveOpsTask) in preparation for us opening a new opsTask that will write to + // 'target'. + void closeRenderTasksForNewRenderTask(GrSurfaceProxy* target); + + // return true if any GrRenderTasks were actually executed; false otherwise + bool executeRenderTasks(int startIndex, int stopIndex, GrOpFlushState*, + int* numRenderTasksExecuted); + + GrSemaphoresSubmitted flush(GrSurfaceProxy* proxies[], + int numProxies, + SkSurface::BackendSurfaceAccess access, + const GrFlushInfo&, + const GrPrepareForExternalIORequests&); + + SkDEBUGCODE(void validate() const); + + friend class GrContext; // access to: flush & cleanup + friend class GrContextPriv; // access to: flush + friend class GrOnFlushResourceProvider; // this is just a shallow wrapper around this class + friend class GrRecordingContext; // access to: ctor + friend class SkImage; // for access to: flush + + static const int kNumPixelGeometries = 5; // The different pixel geometries + static const int kNumDFTOptions = 2; // DFT or no DFT + + GrRecordingContext* fContext; + GrPathRendererChain::Options fOptionsForPathRendererChain; + GrTextContext::Options fOptionsForTextContext; + // This cache is used by both the vertex and index pools. It reuses memory across multiple + // flushes. + sk_sp fCpuBufferCache; + + RenderTaskDAG fDAG; + GrOpsTask* fActiveOpsTask = nullptr; + // These are the IDs of the opsTask currently being flushed (in internalFlush) + SkSTArray<8, uint32_t, true> fFlushingRenderTaskIDs; + // These are the new renderTasks generated by the onFlush CBs + SkSTArray<4, sk_sp> fOnFlushRenderTasks; + + std::unique_ptr fTextContext; + + std::unique_ptr fPathRendererChain; + sk_sp fSoftwarePathRenderer; + + GrTokenTracker fTokenTracker; + bool fFlushing; + bool fReduceOpsTaskSplitting; + + SkTArray fOnFlushCBObjects; + + void addDDLTarget(GrSurfaceProxy* proxy) { fDDLTargets.insert(proxy); } + bool isDDLTarget(GrSurfaceProxy* proxy) { return fDDLTargets.find(proxy) != fDDLTargets.end(); } + void clearDDLTargets() { fDDLTargets.clear(); } + + // We play a trick with lazy proxies to retarget the base target of a DDL to the SkSurface + // it is replayed on. Because of this remapping we need to explicitly store the targets of + // DDL replaying. + // Note: we do not expect a whole lot of these per flush + std::set fDDLTargets; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrDriverBugWorkarounds.cpp b/gfx/skia/skia/src/gpu/GrDriverBugWorkarounds.cpp new file mode 100644 index 0000000000..e91623ce8d --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrDriverBugWorkarounds.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrDriverBugWorkarounds.h" + +#include "include/core/SkTypes.h" + +GrDriverBugWorkarounds::GrDriverBugWorkarounds() = default; + +GrDriverBugWorkarounds::GrDriverBugWorkarounds( + const std::vector& enabled_driver_bug_workarounds) { + for (auto id : enabled_driver_bug_workarounds) { + switch (id) { +#define GPU_OP(type, name) \ + case GrDriverBugWorkaroundType::type: \ + name = true; \ + break; + + GPU_DRIVER_BUG_WORKAROUNDS(GPU_OP) +#undef GPU_OP + default: + SK_ABORT("Not implemented"); + break; + } + } +} + +void GrDriverBugWorkarounds::applyOverrides( + const GrDriverBugWorkarounds& workarounds) { +#define GPU_OP(type, name) \ + name |= workarounds.name; + + GPU_DRIVER_BUG_WORKAROUNDS(GPU_OP) +#undef GPU_OP +} + +GrDriverBugWorkarounds::~GrDriverBugWorkarounds() = default; diff --git a/gfx/skia/skia/src/gpu/GrFPArgs.h b/gfx/skia/skia/src/gpu/GrFPArgs.h new file mode 100644 index 0000000000..87b7e368d8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrFPArgs.h @@ -0,0 +1,97 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrFPArgs_DEFINED +#define GrFPArgs_DEFINED + +#include "include/core/SkFilterQuality.h" +#include "include/core/SkMatrix.h" + +class GrColorInfo; +class GrRecordingContext; + +struct GrFPArgs { + GrFPArgs(GrRecordingContext* context, + const SkMatrix* viewMatrix, + SkFilterQuality filterQuality, + const GrColorInfo* dstColorInfo) + : fContext(context) + , fViewMatrix(viewMatrix) + , fFilterQuality(filterQuality) + , fDstColorInfo(dstColorInfo) { + SkASSERT(fContext); + SkASSERT(fViewMatrix); + } + + class WithPreLocalMatrix; + class WithPostLocalMatrix; + + GrRecordingContext* fContext; + const SkMatrix* fViewMatrix; + + // We track both pre and post local matrix adjustments. For a given FP: + // + // total_local_matrix = postLocalMatrix x FP_localMatrix x preLocalMatrix + // + // Use the helpers above to create pre/post GrFPArgs wrappers. + // + const SkMatrix* fPreLocalMatrix = nullptr; + const SkMatrix* fPostLocalMatrix = nullptr; + + // Make this SkAlphaType? + bool fInputColorIsOpaque = false; + + SkFilterQuality fFilterQuality; + const GrColorInfo* fDstColorInfo; +}; + +class GrFPArgs::WithPreLocalMatrix final : public GrFPArgs { +public: + WithPreLocalMatrix(const GrFPArgs& args, const SkMatrix& lm) : INHERITED(args) { + if (!lm.isIdentity()) { + if (fPreLocalMatrix) { + fStorage.setConcat(lm, *fPreLocalMatrix); + fPreLocalMatrix = fStorage.isIdentity() ? nullptr : &fStorage; + } else { + fPreLocalMatrix = &lm; + } + } + } + +private: + WithPreLocalMatrix(const WithPreLocalMatrix&) = delete; + WithPreLocalMatrix& operator=(const WithPreLocalMatrix&) = delete; + + SkMatrix fStorage; + + using INHERITED = GrFPArgs; +}; + +class GrFPArgs::WithPostLocalMatrix final : public GrFPArgs { +public: + WithPostLocalMatrix(const GrFPArgs& args, const SkMatrix& lm) : INHERITED(args) { + if (!lm.isIdentity()) { + if (fPostLocalMatrix) { + fStorage.setConcat(*fPostLocalMatrix, lm); + fPostLocalMatrix = fStorage.isIdentity() ? nullptr : &fStorage; + } else { + fPostLocalMatrix = &lm; + } + } + } + +private: + WithPostLocalMatrix(const WithPostLocalMatrix&) = delete; + WithPostLocalMatrix& operator=(const WithPostLocalMatrix&) = delete; + + SkMatrix fStorage; + + using INHERITED = GrFPArgs; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/GrFixedClip.cpp b/gfx/skia/skia/src/gpu/GrFixedClip.cpp new file mode 100644 index 0000000000..2977d9d72b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrFixedClip.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrFixedClip.h" + +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrRenderTargetContext.h" + +bool GrFixedClip::quickContains(const SkRect& rect) const { + if (fWindowRectsState.enabled()) { + return false; + } + return !fScissorState.enabled() || GrClip::IsInsideClip(fScissorState.rect(), rect); +} + +void GrFixedClip::getConservativeBounds(int w, int h, SkIRect* devResult, bool* iior) const { + devResult->setXYWH(0, 0, w, h); + if (fScissorState.enabled()) { + if (!devResult->intersect(fScissorState.rect())) { + devResult->setEmpty(); + } + } + if (iior) { + *iior = true; + } +} + +bool GrFixedClip::isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA* aa) const { + if (fWindowRectsState.enabled()) { + return false; + } + if (fScissorState.enabled()) { + SkRect rect = SkRect::Make(fScissorState.rect()); + if (!rect.intersects(rtBounds)) { + return false; + } + rr->setRect(rect); + *aa = GrAA::kNo; + return true; + } + return false; +}; + +bool GrFixedClip::apply(int rtWidth, int rtHeight, GrAppliedHardClip* out, SkRect* bounds) const { + if (fScissorState.enabled()) { + SkIRect tightScissor = SkIRect::MakeWH(rtWidth, rtHeight); + if (!tightScissor.intersect(fScissorState.rect())) { + return false; + } + if (IsOutsideClip(tightScissor, *bounds)) { + return false; + } + if (!IsInsideClip(fScissorState.rect(), *bounds)) { + out->addScissor(tightScissor, bounds); + } + } + + if (fWindowRectsState.enabled()) { + out->addWindowRectangles(fWindowRectsState); + } + + return true; +} + +const GrFixedClip& GrFixedClip::Disabled() { + static const GrFixedClip disabled = GrFixedClip(); + return disabled; +} diff --git a/gfx/skia/skia/src/gpu/GrFixedClip.h b/gfx/skia/skia/src/gpu/GrFixedClip.h new file mode 100644 index 0000000000..087617eacd --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrFixedClip.h @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrFixedClip_DEFINED +#define GrFixedClip_DEFINED + +#include "src/gpu/GrClip.h" +#include "src/gpu/GrScissorState.h" +#include "src/gpu/GrWindowRectsState.h" + +/** + * Implements GrHardClip with scissor and window rectangles. + */ +class GrFixedClip final : public GrHardClip { +public: + GrFixedClip() = default; + explicit GrFixedClip(const SkIRect& scissorRect) : fScissorState(scissorRect) {} + + const GrScissorState& scissorState() const { return fScissorState; } + bool scissorEnabled() const { return fScissorState.enabled(); } + const SkIRect& scissorRect() const { SkASSERT(scissorEnabled()); return fScissorState.rect(); } + + void disableScissor() { fScissorState.setDisabled(); } + + void setScissor(const SkIRect& irect) { + fScissorState.set(irect); + } + bool SK_WARN_UNUSED_RESULT intersect(const SkIRect& irect) { + return fScissorState.intersect(irect); + } + + const GrWindowRectsState& windowRectsState() const { return fWindowRectsState; } + bool hasWindowRectangles() const { return fWindowRectsState.enabled(); } + + void disableWindowRectangles() { fWindowRectsState.setDisabled(); } + + void setWindowRectangles(const GrWindowRectangles& windows, GrWindowRectsState::Mode mode) { + fWindowRectsState.set(windows, mode); + } + + bool quickContains(const SkRect&) const override; + void getConservativeBounds(int w, int h, SkIRect* devResult, bool* iior) const override; + bool isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA*) const override; + bool apply(int rtWidth, int rtHeight, GrAppliedHardClip*, SkRect*) const override; + + static const GrFixedClip& Disabled(); + +private: + GrScissorState fScissorState; + GrWindowRectsState fWindowRectsState; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/GrFragmentProcessor.cpp new file mode 100644 index 0000000000..1e1f2e42a4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrFragmentProcessor.cpp @@ -0,0 +1,431 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProcessorAnalysis.h" +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h" +#include "src/gpu/effects/generated/GrPremulInputFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +bool GrFragmentProcessor::isEqual(const GrFragmentProcessor& that) const { + if (this->classID() != that.classID()) { + return false; + } + if (this->numTextureSamplers() != that.numTextureSamplers()) { + return false; + } + for (int i = 0; i < this->numTextureSamplers(); ++i) { + if (this->textureSampler(i) != that.textureSampler(i)) { + return false; + } + } + if (!this->hasSameTransforms(that)) { + return false; + } + if (!this->onIsEqual(that)) { + return false; + } + if (this->numChildProcessors() != that.numChildProcessors()) { + return false; + } + for (int i = 0; i < this->numChildProcessors(); ++i) { + if (!this->childProcessor(i).isEqual(that.childProcessor(i))) { + return false; + } + } + return true; +} + +void GrFragmentProcessor::visitProxies(const GrOp::VisitProxyFunc& func) { + GrFragmentProcessor::TextureAccessIter iter(this); + while (const TextureSampler* sampler = iter.next()) { + bool mipped = (GrSamplerState::Filter::kMipMap == sampler->samplerState().filter()); + func(sampler->proxy(), GrMipMapped(mipped)); + } +} + +GrGLSLFragmentProcessor* GrFragmentProcessor::createGLSLInstance() const { + GrGLSLFragmentProcessor* glFragProc = this->onCreateGLSLInstance(); + glFragProc->fChildProcessors.push_back_n(fChildProcessors.count()); + for (int i = 0; i < fChildProcessors.count(); ++i) { + glFragProc->fChildProcessors[i] = fChildProcessors[i]->createGLSLInstance(); + } + return glFragProc; +} + +const GrFragmentProcessor::TextureSampler& GrFragmentProcessor::textureSampler(int i) const { + SkASSERT(i >= 0 && i < fTextureSamplerCnt); + return this->onTextureSampler(i); +} + +void GrFragmentProcessor::addCoordTransform(GrCoordTransform* transform) { + transform->setComputeInVertexShader(this->computeLocalCoordsInVertexShader()); + fCoordTransforms.push_back(transform); + fFlags |= kUsesLocalCoords_Flag; + SkDEBUGCODE(transform->setInProcessor();) +} + +#ifdef SK_DEBUG +bool GrFragmentProcessor::isInstantiated() const { + for (int i = 0; i < fTextureSamplerCnt; ++i) { + if (!this->textureSampler(i).isInstantiated()) { + return false; + } + } + + for (int i = 0; i < this->numChildProcessors(); ++i) { + if (!this->childProcessor(i).isInstantiated()) { + return false; + } + } + + return true; +} +#endif + +int GrFragmentProcessor::registerChildProcessor(std::unique_ptr child) { + if (child->usesLocalCoords()) { + fFlags |= kUsesLocalCoords_Flag; + } + fRequestedFeatures |= child->fRequestedFeatures; + + int index = fChildProcessors.count(); + fChildProcessors.push_back(std::move(child)); + + return index; +} + +bool GrFragmentProcessor::hasSameTransforms(const GrFragmentProcessor& that) const { + if (this->numCoordTransforms() != that.numCoordTransforms()) { + return false; + } + int count = this->numCoordTransforms(); + for (int i = 0; i < count; ++i) { + if (!this->coordTransform(i).hasSameEffectAs(that.coordTransform(i))) { + return false; + } + } + return true; +} + +std::unique_ptr GrFragmentProcessor::MulChildByInputAlpha( + std::unique_ptr fp) { + if (!fp) { + return nullptr; + } + return GrXfermodeFragmentProcessor::MakeFromDstProcessor(std::move(fp), SkBlendMode::kDstIn); +} + +std::unique_ptr GrFragmentProcessor::MulInputByChildAlpha( + std::unique_ptr fp) { + if (!fp) { + return nullptr; + } + return GrXfermodeFragmentProcessor::MakeFromDstProcessor(std::move(fp), SkBlendMode::kSrcIn); +} + +std::unique_ptr GrFragmentProcessor::PremulInput( + std::unique_ptr fp) { + if (!fp) { + return nullptr; + } + std::unique_ptr fpPipeline[] = { GrPremulInputFragmentProcessor::Make(), + std::move(fp) }; + return GrFragmentProcessor::RunInSeries(fpPipeline, 2); +} + +std::unique_ptr GrFragmentProcessor::SwizzleOutput( + std::unique_ptr fp, const GrSwizzle& swizzle) { + class SwizzleFragmentProcessor : public GrFragmentProcessor { + public: + static std::unique_ptr Make(const GrSwizzle& swizzle) { + return std::unique_ptr(new SwizzleFragmentProcessor(swizzle)); + } + + const char* name() const override { return "Swizzle"; } + const GrSwizzle& swizzle() const { return fSwizzle; } + + std::unique_ptr clone() const override { return Make(fSwizzle); } + + private: + SwizzleFragmentProcessor(const GrSwizzle& swizzle) + : INHERITED(kSwizzleFragmentProcessor_ClassID, kAll_OptimizationFlags) + , fSwizzle(swizzle) {} + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + class GLFP : public GrGLSLFragmentProcessor { + public: + void emitCode(EmitArgs& args) override { + const SwizzleFragmentProcessor& sfp = args.fFp.cast(); + const GrSwizzle& swizzle = sfp.swizzle(); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + fragBuilder->codeAppendf("%s = %s.%s;", + args.fOutputColor, args.fInputColor, swizzle.c_str()); + } + }; + return new GLFP; + } + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { + b->add32(fSwizzle.asKey()); + } + + bool onIsEqual(const GrFragmentProcessor& other) const override { + const SwizzleFragmentProcessor& sfp = other.cast(); + return fSwizzle == sfp.fSwizzle; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return fSwizzle.applyTo(input); + } + + GrSwizzle fSwizzle; + + typedef GrFragmentProcessor INHERITED; + }; + + if (!fp) { + return nullptr; + } + if (GrSwizzle::RGBA() == swizzle) { + return fp; + } + std::unique_ptr fpPipeline[] = { std::move(fp), + SwizzleFragmentProcessor::Make(swizzle) }; + return GrFragmentProcessor::RunInSeries(fpPipeline, 2); +} + +std::unique_ptr GrFragmentProcessor::MakeInputPremulAndMulByOutput( + std::unique_ptr fp) { + class PremulFragmentProcessor : public GrFragmentProcessor { + public: + static std::unique_ptr Make( + std::unique_ptr processor) { + return std::unique_ptr( + new PremulFragmentProcessor(std::move(processor))); + } + + const char* name() const override { return "Premultiply"; } + + std::unique_ptr clone() const override { + return Make(this->childProcessor(0).clone()); + } + + private: + PremulFragmentProcessor(std::unique_ptr processor) + : INHERITED(kPremulFragmentProcessor_ClassID, OptFlags(processor.get())) { + this->registerChildProcessor(std::move(processor)); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + class GLFP : public GrGLSLFragmentProcessor { + public: + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + this->invokeChild(0, args); + fragBuilder->codeAppendf("%s.rgb *= %s.rgb;", args.fOutputColor, + args.fInputColor); + fragBuilder->codeAppendf("%s *= %s.a;", args.fOutputColor, args.fInputColor); + } + }; + return new GLFP; + } + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {} + + bool onIsEqual(const GrFragmentProcessor&) const override { return true; } + + static OptimizationFlags OptFlags(const GrFragmentProcessor* inner) { + OptimizationFlags flags = kNone_OptimizationFlags; + if (inner->preservesOpaqueInput()) { + flags |= kPreservesOpaqueInput_OptimizationFlag; + } + if (inner->hasConstantOutputForConstantInput()) { + flags |= kConstantOutputForConstantInput_OptimizationFlag; + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + SkPMColor4f childColor = ConstantOutputForConstantInput(this->childProcessor(0), + SK_PMColor4fWHITE); + SkPMColor4f premulInput = SkColor4f{ input.fR, input.fG, input.fB, input.fA }.premul(); + return premulInput * childColor; + } + + typedef GrFragmentProcessor INHERITED; + }; + if (!fp) { + return nullptr; + } + return PremulFragmentProcessor::Make(std::move(fp)); +} + +////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrFragmentProcessor::OverrideInput( + std::unique_ptr fp, const SkPMColor4f& color, bool useUniform) { + if (!fp) { + return nullptr; + } + return GrOverrideInputFragmentProcessor::Make(std::move(fp), color, useUniform); +} + +std::unique_ptr GrFragmentProcessor::RunInSeries( + std::unique_ptr* series, int cnt) { + class SeriesFragmentProcessor : public GrFragmentProcessor { + public: + static std::unique_ptr Make( + std::unique_ptr* children, int cnt) { + return std::unique_ptr(new SeriesFragmentProcessor(children, cnt)); + } + + const char* name() const override { return "Series"; } + + std::unique_ptr clone() const override { + SkSTArray<4, std::unique_ptr> children(this->numChildProcessors()); + for (int i = 0; i < this->numChildProcessors(); ++i) { + if (!children.push_back(this->childProcessor(i).clone())) { + return nullptr; + } + } + return Make(children.begin(), this->numChildProcessors()); + } + + private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + class GLFP : public GrGLSLFragmentProcessor { + public: + void emitCode(EmitArgs& args) override { + // First guy's input might be nil. + SkString temp("out0"); + this->invokeChild(0, args.fInputColor, &temp, args); + SkString input = temp; + for (int i = 1; i < this->numChildProcessors() - 1; ++i) { + temp.printf("out%d", i); + this->invokeChild(i, input.c_str(), &temp, args); + input = temp; + } + // Last guy writes to our output variable. + this->invokeChild(this->numChildProcessors() - 1, input.c_str(), args); + } + }; + return new GLFP; + } + + SeriesFragmentProcessor(std::unique_ptr* children, int cnt) + : INHERITED(kSeriesFragmentProcessor_ClassID, OptFlags(children, cnt)) { + SkASSERT(cnt > 1); + for (int i = 0; i < cnt; ++i) { + this->registerChildProcessor(std::move(children[i])); + } + } + + static OptimizationFlags OptFlags(std::unique_ptr* children, int cnt) { + OptimizationFlags flags = kAll_OptimizationFlags; + for (int i = 0; i < cnt && flags != kNone_OptimizationFlags; ++i) { + flags &= children[i]->optimizationFlags(); + } + return flags; + } + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {} + + bool onIsEqual(const GrFragmentProcessor&) const override { return true; } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& inColor) const override { + SkPMColor4f color = inColor; + int childCnt = this->numChildProcessors(); + for (int i = 0; i < childCnt; ++i) { + color = ConstantOutputForConstantInput(this->childProcessor(i), color); + } + return color; + } + + typedef GrFragmentProcessor INHERITED; + }; + + if (!cnt) { + return nullptr; + } + if (1 == cnt) { + return std::move(series[0]); + } + // Run the through the series, do the invariant output processing, and look for eliminations. + GrProcessorAnalysisColor inputColor; + inputColor.setToUnknown(); + GrColorFragmentProcessorAnalysis info(inputColor, unique_ptr_address_as_pointer_address(series), + cnt); + SkTArray> replacementSeries; + SkPMColor4f knownColor; + int leadingFPsToEliminate = info.initialProcessorsToEliminate(&knownColor); + if (leadingFPsToEliminate) { + std::unique_ptr colorFP( + GrConstColorProcessor::Make(knownColor, GrConstColorProcessor::InputMode::kIgnore)); + if (leadingFPsToEliminate == cnt) { + return colorFP; + } + cnt = cnt - leadingFPsToEliminate + 1; + replacementSeries.reserve(cnt); + replacementSeries.emplace_back(std::move(colorFP)); + for (int i = 0; i < cnt - 1; ++i) { + replacementSeries.emplace_back(std::move(series[leadingFPsToEliminate + i])); + } + series = replacementSeries.begin(); + } + return SeriesFragmentProcessor::Make(series, cnt); +} + +////////////////////////////////////////////////////////////////////////////// + +GrFragmentProcessor::Iter::Iter(const GrPipeline& pipeline) { + for (int i = pipeline.numFragmentProcessors() - 1; i >= 0; --i) { + fFPStack.push_back(&pipeline.getFragmentProcessor(i)); + } +} + +GrFragmentProcessor::Iter::Iter(const GrPaint& paint) { + for (int i = paint.numCoverageFragmentProcessors() - 1; i >= 0; --i) { + fFPStack.push_back(paint.getCoverageFragmentProcessor(i)); + } + for (int i = paint.numColorFragmentProcessors() - 1; i >= 0; --i) { + fFPStack.push_back(paint.getColorFragmentProcessor(i)); + } +} + +const GrFragmentProcessor* GrFragmentProcessor::Iter::next() { + if (fFPStack.empty()) { + return nullptr; + } + const GrFragmentProcessor* back = fFPStack.back(); + fFPStack.pop_back(); + for (int i = back->numChildProcessors() - 1; i >= 0; --i) { + fFPStack.push_back(&back->childProcessor(i)); + } + return back; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +GrFragmentProcessor::TextureSampler::TextureSampler(sk_sp proxy, + const GrSamplerState& samplerState) { + this->reset(std::move(proxy), samplerState); +} + +void GrFragmentProcessor::TextureSampler::reset(sk_sp proxy, + const GrSamplerState& samplerState) { + fProxy = std::move(proxy); + fSamplerState = samplerState; + fSamplerState.setFilterMode(SkTMin(samplerState.filter(), this->proxy()->highestFilterMode())); +} diff --git a/gfx/skia/skia/src/gpu/GrFragmentProcessor.h b/gfx/skia/skia/src/gpu/GrFragmentProcessor.h new file mode 100644 index 0000000000..21e27b868b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrFragmentProcessor.h @@ -0,0 +1,475 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrFragmentProcessor_DEFINED +#define GrFragmentProcessor_DEFINED + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/ops/GrOp.h" + +class GrGLSLFragmentProcessor; +class GrPaint; +class GrPipeline; +class GrProcessorKeyBuilder; +class GrShaderCaps; +class GrSwizzle; + +/** Provides custom fragment shader code. Fragment processors receive an input color (half4) and + produce an output color. They may reference textures and uniforms. They may use + GrCoordTransforms to receive a transformation of the local coordinates that map from local space + to the fragment being processed. + */ +class GrFragmentProcessor : public GrProcessor { +public: + class TextureSampler; + + /** + * In many instances (e.g. SkShader::asFragmentProcessor() implementations) it is desirable to + * only consider the input color's alpha. However, there is a competing desire to have reusable + * GrFragmentProcessor subclasses that can be used in other scenarios where the entire input + * color is considered. This function exists to filter the input color and pass it to a FP. It + * does so by returning a parent FP that multiplies the passed in FPs output by the parent's + * input alpha. The passed in FP will not receive an input color. + */ + static std::unique_ptr MulChildByInputAlpha( + std::unique_ptr child); + + /** + * Like MulChildByInputAlpha(), but reverses the sense of src and dst. In this case, return + * the input modulated by the child's alpha. The passed in FP will not receive an input color. + * + * output = input * child.a + */ + static std::unique_ptr MulInputByChildAlpha( + std::unique_ptr child); + + /** + * This assumes that the input color to the returned processor will be unpremul and that the + * passed processor (which becomes the returned processor's child) produces a premul output. + * The result of the returned processor is a premul of its input color modulated by the child + * processor's premul output. + */ + static std::unique_ptr MakeInputPremulAndMulByOutput( + std::unique_ptr); + + /** + * Returns a parent fragment processor that adopts the passed fragment processor as a child. + * The parent will ignore its input color and instead feed the passed in color as input to the + * child. + */ + static std::unique_ptr OverrideInput(std::unique_ptr, + const SkPMColor4f&, + bool useUniform = true); + + /** + * Returns a fragment processor that premuls the input before calling the passed in fragment + * processor. + */ + static std::unique_ptr PremulInput(std::unique_ptr); + + /** + * Returns a fragment processor that calls the passed in fragment processor, and then swizzles + * the output. + */ + static std::unique_ptr SwizzleOutput(std::unique_ptr, + const GrSwizzle&); + + /** + * Returns a fragment processor that runs the passed in array of fragment processors in a + * series. The original input is passed to the first, the first's output is passed to the + * second, etc. The output of the returned processor is the output of the last processor of the + * series. + * + * The array elements with be moved. + */ + static std::unique_ptr RunInSeries(std::unique_ptr*, + int cnt); + + /** + * Makes a copy of this fragment processor that draws equivalently to the original. + * If the processor has child processors they are cloned as well. + */ + virtual std::unique_ptr clone() const = 0; + + GrGLSLFragmentProcessor* createGLSLInstance() const; + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const { + this->onGetGLSLProcessorKey(caps, b); + for (int i = 0; i < fChildProcessors.count(); ++i) { + fChildProcessors[i]->getGLSLProcessorKey(caps, b); + } + } + + int numTextureSamplers() const { return fTextureSamplerCnt; } + const TextureSampler& textureSampler(int i) const; + + int numCoordTransforms() const { return fCoordTransforms.count(); } + + /** Returns the coordinate transformation at index. index must be valid according to + numTransforms(). */ + const GrCoordTransform& coordTransform(int index) const { return *fCoordTransforms[index]; } + + const SkTArray& coordTransforms() const { + return fCoordTransforms; + } + + int numChildProcessors() const { return fChildProcessors.count(); } + + const GrFragmentProcessor& childProcessor(int index) const { return *fChildProcessors[index]; } + + SkDEBUGCODE(bool isInstantiated() const;) + + /** Do any of the coordtransforms for this processor require local coords? */ + bool usesLocalCoords() const { return SkToBool(fFlags & kUsesLocalCoords_Flag); } + + bool computeLocalCoordsInVertexShader() const { + return SkToBool(fFlags & kComputeLocalCoordsInVertexShader_Flag); + } + + void setComputeLocalCoordsInVertexShader(bool value) const { + if (value) { + fFlags |= kComputeLocalCoordsInVertexShader_Flag; + } else { + fFlags &= ~kComputeLocalCoordsInVertexShader_Flag; + } + for (GrCoordTransform* transform : fCoordTransforms) { + transform->setComputeInVertexShader(value); + } + for (const auto& child : fChildProcessors) { + child->setComputeLocalCoordsInVertexShader(value); + } + } + + /** + * A GrDrawOp may premultiply its antialiasing coverage into its GrGeometryProcessor's color + * output under the following scenario: + * * all the color fragment processors report true to this query, + * * all the coverage fragment processors report true to this query, + * * the blend mode arithmetic allows for it it. + * To be compatible a fragment processor's output must be a modulation of its input color or + * alpha with a computed premultiplied color or alpha that is in 0..1 range. The computed color + * or alpha that is modulated against the input cannot depend on the input's alpha. The computed + * value cannot depend on the input's color channels unless it unpremultiplies the input color + * channels by the input alpha. + */ + bool compatibleWithCoverageAsAlpha() const { + return SkToBool(fFlags & kCompatibleWithCoverageAsAlpha_OptimizationFlag); + } + + /** + * If this is true then all opaque input colors to the processor produce opaque output colors. + */ + bool preservesOpaqueInput() const { + return SkToBool(fFlags & kPreservesOpaqueInput_OptimizationFlag); + } + + /** + * Tests whether given a constant input color the processor produces a constant output color + * (for all fragments). If true outputColor will contain the constant color produces for + * inputColor. + */ + bool hasConstantOutputForConstantInput(SkPMColor4f inputColor, SkPMColor4f* outputColor) const { + if (fFlags & kConstantOutputForConstantInput_OptimizationFlag) { + *outputColor = this->constantOutputForConstantInput(inputColor); + return true; + } + return false; + } + bool hasConstantOutputForConstantInput() const { + return SkToBool(fFlags & kConstantOutputForConstantInput_OptimizationFlag); + } + + /** Returns true if this and other processor conservatively draw identically. It can only return + true when the two processor are of the same subclass (i.e. they return the same object from + from getFactory()). + + A return value of true from isEqual() should not be used to test whether the processor would + generate the same shader code. To test for identical code generation use getGLSLProcessorKey + */ + bool isEqual(const GrFragmentProcessor& that) const; + + /** + * Pre-order traversal of a FP hierarchy, or of the forest of FPs in a GrPipeline. In the latter + * case the tree rooted at each FP in the GrPipeline is visited successively. + */ + class Iter : public SkNoncopyable { + public: + explicit Iter(const GrFragmentProcessor* fp) { fFPStack.push_back(fp); } + explicit Iter(const GrPipeline& pipeline); + explicit Iter(const GrPaint&); + const GrFragmentProcessor* next(); + + private: + SkSTArray<4, const GrFragmentProcessor*, true> fFPStack; + }; + + /** + * Iterates over all the Ts owned by a GrFragmentProcessor and its children or over all the Ts + * owned by the forest of GrFragmentProcessors in a GrPipeline. FPs are visited in the same + * order as Iter and each of an FP's Ts are visited in order. + */ + template + class FPItemIter : public SkNoncopyable { + public: + explicit FPItemIter(const GrFragmentProcessor* fp) + : fCurrFP(nullptr) + , fCTIdx(0) + , fFPIter(fp) { + fCurrFP = fFPIter.next(); + } + explicit FPItemIter(const GrPipeline& pipeline) + : fCurrFP(nullptr) + , fCTIdx(0) + , fFPIter(pipeline) { + fCurrFP = fFPIter.next(); + } + + const T* next() { + if (!fCurrFP) { + return nullptr; + } + while (fCTIdx == (fCurrFP->*COUNT)()) { + fCTIdx = 0; + fCurrFP = fFPIter.next(); + if (!fCurrFP) { + return nullptr; + } + } + return &(fCurrFP->*GET)(fCTIdx++); + } + + private: + const GrFragmentProcessor* fCurrFP; + int fCTIdx; + GrFragmentProcessor::Iter fFPIter; + }; + + using CoordTransformIter = FPItemIter; + + using TextureAccessIter = FPItemIter; + + void visitProxies(const GrOp::VisitProxyFunc& func); + +protected: + enum OptimizationFlags : uint32_t { + kNone_OptimizationFlags, + kCompatibleWithCoverageAsAlpha_OptimizationFlag = 0x1, + kPreservesOpaqueInput_OptimizationFlag = 0x2, + kConstantOutputForConstantInput_OptimizationFlag = 0x4, + kAll_OptimizationFlags = kCompatibleWithCoverageAsAlpha_OptimizationFlag | + kPreservesOpaqueInput_OptimizationFlag | + kConstantOutputForConstantInput_OptimizationFlag + }; + GR_DECL_BITFIELD_OPS_FRIENDS(OptimizationFlags) + + /** + * Can be used as a helper to decide which fragment processor OptimizationFlags should be set. + * This assumes that the subclass output color will be a modulation of the input color with a + * value read from a texture of the passed color type and that the texture contains + * premultiplied color or alpha values that are in range. + * + * Since there are multiple ways in which a sampler may have its coordinates clamped or wrapped, + * callers must determine on their own if the sampling uses a decal strategy in any way, in + * which case the texture may become transparent regardless of the color type. + */ + static OptimizationFlags ModulateForSamplerOptFlags(GrColorType colorType, bool samplingDecal) { + if (samplingDecal) { + return kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } else { + return ModulateForClampedSamplerOptFlags(colorType); + } + } + + // As above, but callers should somehow ensure or assert their sampler still uses clamping + static OptimizationFlags ModulateForClampedSamplerOptFlags(GrColorType colorType) { + if (!GrColorTypeHasAlpha(colorType)) { + return kCompatibleWithCoverageAsAlpha_OptimizationFlag | + kPreservesOpaqueInput_OptimizationFlag; + } else { + return kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } + } + + GrFragmentProcessor(ClassID classID, OptimizationFlags optimizationFlags) + : INHERITED(classID) + , fFlags(optimizationFlags | kComputeLocalCoordsInVertexShader_Flag) { + SkASSERT((optimizationFlags & ~kAll_OptimizationFlags) == 0); + } + + OptimizationFlags optimizationFlags() const { + return static_cast(kAll_OptimizationFlags & fFlags); + } + + /** Useful when you can't call fp->optimizationFlags() on a base class object from a subclass.*/ + static OptimizationFlags ProcessorOptimizationFlags(const GrFragmentProcessor* fp) { + return fp->optimizationFlags(); + } + + /** + * This allows one subclass to access another subclass's implementation of + * constantOutputForConstantInput. It must only be called when + * hasConstantOutputForConstantInput() is known to be true. + */ + static SkPMColor4f ConstantOutputForConstantInput(const GrFragmentProcessor& fp, + const SkPMColor4f& input) { + SkASSERT(fp.hasConstantOutputForConstantInput()); + return fp.constantOutputForConstantInput(input); + } + + /** + * Fragment Processor subclasses call this from their constructor to register coordinate + * transformations. Coord transforms provide a mechanism for a processor to receive coordinates + * in their FS code. The matrix expresses a transformation from local space. For a given + * fragment the matrix will be applied to the local coordinate that maps to the fragment. + * + * When the transformation has perspective, the transformed coordinates will have + * 3 components. Otherwise they'll have 2. + * + * This must only be called from the constructor because GrProcessors are immutable. The + * processor subclass manages the lifetime of the transformations (this function only stores a + * pointer). The GrCoordTransform is typically a member field of the GrProcessor subclass. + * + * A processor subclass that has multiple methods of construction should always add its coord + * transforms in a consistent order. The non-virtual implementation of isEqual() automatically + * compares transforms and will assume they line up across the two processor instances. + */ + void addCoordTransform(GrCoordTransform*); + + /** + * FragmentProcessor subclasses call this from their constructor to register any child + * FragmentProcessors they have. This must be called AFTER all texture accesses and coord + * transforms have been added. + * This is for processors whose shader code will be composed of nested processors whose output + * colors will be combined somehow to produce its output color. Registering these child + * processors will allow the ProgramBuilder to automatically handle their transformed coords and + * texture accesses and mangle their uniform and output color names. + */ + int registerChildProcessor(std::unique_ptr child); + + void setTextureSamplerCnt(int cnt) { + SkASSERT(cnt >= 0); + fTextureSamplerCnt = cnt; + } + + /** + * Helper for implementing onTextureSampler(). E.g.: + * return IthTexureSampler(i, fMyFirstSampler, fMySecondSampler, fMyThirdSampler); + */ + template + static const TextureSampler& IthTextureSampler(int i, const TextureSampler& samp0, + const Args&... samps) { + return (0 == i) ? samp0 : IthTextureSampler(i - 1, samps...); + } + inline static const TextureSampler& IthTextureSampler(int i); + +private: + virtual SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& /* inputColor */) const { + SK_ABORT("Subclass must override this if advertising this optimization."); + } + + /** Returns a new instance of the appropriate *GL* implementation class + for the given GrFragmentProcessor; caller is responsible for deleting + the object. */ + virtual GrGLSLFragmentProcessor* onCreateGLSLInstance() const = 0; + + /** Implemented using GLFragmentProcessor::GenKey as described in this class's comment. */ + virtual void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const = 0; + + /** + * Subclass implements this to support isEqual(). It will only be called if it is known that + * the two processors are of the same subclass (i.e. they return the same object from + * getFactory()). The processor subclass should not compare its coord transforms as that will + * be performed automatically in the non-virtual isEqual(). + */ + virtual bool onIsEqual(const GrFragmentProcessor&) const = 0; + + virtual const TextureSampler& onTextureSampler(int) const { return IthTextureSampler(0); } + + bool hasSameTransforms(const GrFragmentProcessor&) const; + + enum PrivateFlags { + kFirstPrivateFlag = kAll_OptimizationFlags + 1, + kUsesLocalCoords_Flag = kFirstPrivateFlag, + kComputeLocalCoordsInVertexShader_Flag = kFirstPrivateFlag << 1, + }; + + mutable uint32_t fFlags = kComputeLocalCoordsInVertexShader_Flag; + + int fTextureSamplerCnt = 0; + + SkSTArray<4, GrCoordTransform*, true> fCoordTransforms; + + SkSTArray<1, std::unique_ptr, true> fChildProcessors; + + typedef GrProcessor INHERITED; +}; + +/** + * Used to represent a texture that is required by a GrFragmentProcessor. It holds a GrTextureProxy + * along with an associated GrSamplerState. TextureSamplers don't perform any coord manipulation to + * account for texture origin. + */ +class GrFragmentProcessor::TextureSampler { +public: + TextureSampler() = default; + + /** + * This copy constructor is used by GrFragmentProcessor::clone() implementations. + */ + explicit TextureSampler(const TextureSampler& that) + : fProxy(that.fProxy) + , fSamplerState(that.fSamplerState) {} + + TextureSampler(sk_sp, const GrSamplerState& = GrSamplerState::ClampNearest()); + + TextureSampler& operator=(const TextureSampler&) = delete; + + void reset(sk_sp, const GrSamplerState&); + + bool operator==(const TextureSampler& that) const { + return this->proxy()->underlyingUniqueID() == that.proxy()->underlyingUniqueID() && + fSamplerState == that.fSamplerState; + } + + bool operator!=(const TextureSampler& other) const { return !(*this == other); } + + SkDEBUGCODE(bool isInstantiated() const { return fProxy->isInstantiated(); }) + + // 'peekTexture' should only ever be called after a successful 'instantiate' call + GrTexture* peekTexture() const { + SkASSERT(fProxy->isInstantiated()); + return fProxy->peekTexture(); + } + + GrTextureProxy* proxy() const { return fProxy.get(); } + const GrSamplerState& samplerState() const { return fSamplerState; } + const GrSwizzle& swizzle() const { return this->proxy()->textureSwizzle(); } + + bool isInitialized() const { return SkToBool(fProxy.get()); } + +private: + sk_sp fProxy; + GrSamplerState fSamplerState; +}; + +////////////////////////////////////////////////////////////////////////////// + +const GrFragmentProcessor::TextureSampler& GrFragmentProcessor::IthTextureSampler(int i) { + SK_ABORT("Illegal texture sampler index"); + static const TextureSampler kBogus; + return kBogus; +} + +GR_MAKE_BITFIELD_OPS(GrFragmentProcessor::OptimizationFlags) + +#endif diff --git a/gfx/skia/skia/src/gpu/GrGeometryProcessor.h b/gfx/skia/skia/src/gpu/GrGeometryProcessor.h new file mode 100644 index 0000000000..6f596a5ae4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGeometryProcessor.h @@ -0,0 +1,45 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGeometryProcessor_DEFINED +#define GrGeometryProcessor_DEFINED + +#include "src/gpu/GrPrimitiveProcessor.h" + +/** + * A GrGeometryProcessor is a flexible method for rendering a primitive. The GrGeometryProcessor + * has complete control over vertex attributes and uniforms(aside from the render target) but it + * must obey the same contract as any GrPrimitiveProcessor, specifically it must emit a color and + * coverage into the fragment shader. Where this color and coverage come from is completely the + * responsibility of the GrGeometryProcessor. + */ +class GrGeometryProcessor : public GrPrimitiveProcessor { +public: + GrGeometryProcessor(ClassID classID) + : INHERITED(classID) + , fWillUseGeoShader(false) {} + + bool willUseGeoShader() const final { return fWillUseGeoShader; } + +protected: + void setWillUseGeoShader() { fWillUseGeoShader = true; } + + // GPs that need to use either half-float or ubyte colors can just call this to get a correctly + // configured Attribute struct + static Attribute MakeColorAttribute(const char* name, bool wideColor) { + return { name, + wideColor ? kHalf4_GrVertexAttribType : kUByte4_norm_GrVertexAttribType, + kHalf4_GrSLType }; + } + +private: + bool fWillUseGeoShader; + + typedef GrPrimitiveProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrGlyph.h b/gfx/skia/skia/src/gpu/GrGlyph.h new file mode 100644 index 0000000000..96ea558150 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGlyph.h @@ -0,0 +1,103 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGlyph_DEFINED +#define GrGlyph_DEFINED + +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/geometry/GrRect.h" + +#include "include/core/SkPath.h" +#include "include/private/SkChecksum.h" +#include "include/private/SkFixed.h" + +struct GrGlyph { + enum MaskStyle { + kCoverage_MaskStyle, + kDistance_MaskStyle + }; + + static GrMaskFormat FormatFromSkGlyph(SkMask::Format format) { + switch (format) { + case SkMask::kBW_Format: + case SkMask::kSDF_Format: + // fall through to kA8 -- we store BW and SDF glyphs in our 8-bit cache + case SkMask::kA8_Format: + return kA8_GrMaskFormat; + case SkMask::k3D_Format: + return kA8_GrMaskFormat; // ignore the mul and add planes, just use the mask + case SkMask::kLCD16_Format: + return kA565_GrMaskFormat; + case SkMask::kARGB32_Format: + return kARGB_GrMaskFormat; + default: + SkDEBUGFAIL("unsupported SkMask::Format"); + return kA8_GrMaskFormat; + } + } + + static MaskStyle MaskStyleFromSkGlyph(const SkGlyph& skGlyph) { + return skGlyph.maskFormat() == SkMask::kSDF_Format + ? GrGlyph::MaskStyle::kDistance_MaskStyle + : GrGlyph::MaskStyle::kCoverage_MaskStyle; + } + + GrGlyph(const SkGlyph& skGlyph) + : fPackedID{skGlyph.getPackedID()} + , fMaskFormat{FormatFromSkGlyph(skGlyph.maskFormat())} + , fMaskStyle{MaskStyleFromSkGlyph(skGlyph)} + , fBounds{GrIRect16::Make(skGlyph.iRect())} {} + + + SkRect destRect(SkPoint origin) { + return SkRect::MakeXYWH( + SkIntToScalar(fBounds.fLeft) + origin.x(), + SkIntToScalar(fBounds.fTop) + origin.y(), + SkIntToScalar(fBounds.width()), + SkIntToScalar(fBounds.height())); + } + + SkRect destRect(SkPoint origin, SkScalar textScale) { + if (fMaskStyle == kCoverage_MaskStyle) { + return SkRect::MakeXYWH( + SkIntToScalar(fBounds.fLeft) * textScale + origin.x(), + SkIntToScalar(fBounds.fTop) * textScale + origin.y(), + SkIntToScalar(fBounds.width()) * textScale, + SkIntToScalar(fBounds.height()) * textScale); + } else { + return SkRect::MakeXYWH( + (SkIntToScalar(fBounds.fLeft) + SK_DistanceFieldInset) * textScale + origin.x(), + (SkIntToScalar(fBounds.fTop) + SK_DistanceFieldInset) * textScale + origin.y(), + (SkIntToScalar(fBounds.width()) - 2 * SK_DistanceFieldInset) * textScale, + (SkIntToScalar(fBounds.height()) - 2 * SK_DistanceFieldInset) * textScale); + } + } + + int width() const { return fBounds.width(); } + int height() const { return fBounds.height(); } + uint32_t pageIndex() const { return GrDrawOpAtlas::GetPageIndexFromID(fID); } + MaskStyle maskStyle() const { return fMaskStyle; } + + // GetKey and Hash for the the hash table. + static const SkPackedGlyphID& GetKey(const GrGlyph& glyph) { + return glyph.fPackedID; + } + + static uint32_t Hash(SkPackedGlyphID key) { + return SkChecksum::Mix(key.hash()); + } + + const SkPackedGlyphID fPackedID; + const GrMaskFormat fMaskFormat; + const MaskStyle fMaskStyle; + const GrIRect16 fBounds; + SkIPoint16 fAtlasLocation{0, 0}; + GrDrawOpAtlas::AtlasID fID{GrDrawOpAtlas::kInvalidAtlasID}; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrGpu.cpp b/gfx/skia/skia/src/gpu/GrGpu.cpp new file mode 100644 index 0000000000..40be00406a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpu.cpp @@ -0,0 +1,775 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/GrGpu.h" + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrNativeRect.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureProxyPriv.h" +#include "src/gpu/GrTracing.h" +#include "src/utils/SkJSONWriter.h" + +//////////////////////////////////////////////////////////////////////////////// + +GrGpu::GrGpu(GrContext* context) : fResetBits(kAll_GrBackendState), fContext(context) {} + +GrGpu::~GrGpu() {} + +void GrGpu::disconnect(DisconnectType) {} + +//////////////////////////////////////////////////////////////////////////////// + +bool GrGpu::IsACopyNeededForRepeatWrapMode(const GrCaps* caps, GrTextureProxy* texProxy, + int width, int height, + GrSamplerState::Filter filter, + GrTextureProducer::CopyParams* copyParams, + SkScalar scaleAdjust[2]) { + if (!caps->npotTextureTileSupport() && + (!SkIsPow2(width) || !SkIsPow2(height))) { + SkASSERT(scaleAdjust); + copyParams->fWidth = GrNextPow2(width); + copyParams->fHeight = GrNextPow2(height); + SkASSERT(scaleAdjust); + scaleAdjust[0] = ((SkScalar)copyParams->fWidth) / width; + scaleAdjust[1] = ((SkScalar)copyParams->fHeight) / height; + switch (filter) { + case GrSamplerState::Filter::kNearest: + copyParams->fFilter = GrSamplerState::Filter::kNearest; + break; + case GrSamplerState::Filter::kBilerp: + case GrSamplerState::Filter::kMipMap: + // We are only ever scaling up so no reason to ever indicate kMipMap. + copyParams->fFilter = GrSamplerState::Filter::kBilerp; + break; + } + return true; + } + + if (texProxy) { + // If the texture format itself doesn't support repeat wrap mode or mipmapping (and + // those capabilities are required) force a copy. + if (texProxy->hasRestrictedSampling()) { + copyParams->fFilter = GrSamplerState::Filter::kNearest; + copyParams->fWidth = texProxy->width(); + copyParams->fHeight = texProxy->height(); + return true; + } + } + + return false; +} + +bool GrGpu::IsACopyNeededForMips(const GrCaps* caps, const GrTextureProxy* texProxy, + GrSamplerState::Filter filter, + GrTextureProducer::CopyParams* copyParams) { + SkASSERT(texProxy); + bool willNeedMips = GrSamplerState::Filter::kMipMap == filter && caps->mipMapSupport(); + // If the texture format itself doesn't support mipmapping (and those capabilities are required) + // force a copy. + if (willNeedMips && texProxy->mipMapped() == GrMipMapped::kNo) { + copyParams->fFilter = GrSamplerState::Filter::kNearest; + copyParams->fWidth = texProxy->width(); + copyParams->fHeight = texProxy->height(); + return true; + } + + return false; +} + +static bool validate_texel_levels(int w, int h, GrColorType texelColorType, + const GrMipLevel* texels, int mipLevelCount, const GrCaps* caps) { + SkASSERT(mipLevelCount > 0); + bool hasBasePixels = texels[0].fPixels; + int levelsWithPixelsCnt = 0; + auto bpp = GrColorTypeBytesPerPixel(texelColorType); + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; ++currentMipLevel) { + if (texels[currentMipLevel].fPixels) { + const size_t minRowBytes = w * bpp; + if (caps->writePixelsRowBytesSupport()) { + if (texels[currentMipLevel].fRowBytes < minRowBytes) { + return false; + } + if (texels[currentMipLevel].fRowBytes % bpp) { + return false; + } + } else { + if (texels[currentMipLevel].fRowBytes != minRowBytes) { + return false; + } + } + ++levelsWithPixelsCnt; + } + if (w == 1 && h == 1) { + if (currentMipLevel != mipLevelCount - 1) { + return false; + } + } else { + w = std::max(w / 2, 1); + h = std::max(h / 2, 1); + } + } + // Either just a base layer or a full stack is required. + if (mipLevelCount != 1 && (w != 1 || h != 1)) { + return false; + } + // Can specify just the base, all levels, or no levels. + if (!hasBasePixels) { + return levelsWithPixelsCnt == 0; + } + return levelsWithPixelsCnt == 1 || levelsWithPixelsCnt == mipLevelCount; +} + +sk_sp GrGpu::createTextureCommon(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + int mipLevelCount, + uint32_t levelClearMask) { + if (this->caps()->isFormatCompressed(format)) { + // Call GrGpu::createCompressedTexture. + return nullptr; + } + + GrMipMapped mipMapped = mipLevelCount > 1 ? GrMipMapped::kYes : GrMipMapped::kNo; + if (!this->caps()->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, + renderable, renderTargetSampleCnt, mipMapped)) { + return nullptr; + } + + if (renderable == GrRenderable::kYes) { + renderTargetSampleCnt = + this->caps()->getRenderTargetSampleCount(renderTargetSampleCnt, format); + } + // Attempt to catch un- or wrongly initialized sample counts. + SkASSERT(renderTargetSampleCnt > 0 && renderTargetSampleCnt <= 64); + this->handleDirtyContext(); + auto tex = this->onCreateTexture(desc, + format, + renderable, + renderTargetSampleCnt, + budgeted, + isProtected, + mipLevelCount, + levelClearMask); + if (tex) { + SkASSERT(tex->backendFormat() == format); + SkASSERT(GrRenderable::kNo == renderable || tex->asRenderTarget()); + if (!this->caps()->reuseScratchTextures() && renderable == GrRenderable::kNo) { + tex->resourcePriv().removeScratchKey(); + } + fStats.incTextureCreates(); + if (renderTargetSampleCnt > 1 && !this->caps()->msaaResolvesAutomatically()) { + SkASSERT(GrRenderable::kYes == renderable); + tex->asRenderTarget()->setRequiresManualMSAAResolve(); + } + } + return tex; +} + +sk_sp GrGpu::createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrMipMapped mipMapped, + SkBudgeted budgeted, + GrProtected isProtected) { + int mipLevelCount = 1; + if (mipMapped == GrMipMapped::kYes) { + mipLevelCount = 32 - SkCLZ(static_cast(SkTMax(desc.fWidth, desc.fHeight))); + } + uint32_t levelClearMask = + this->caps()->shouldInitializeTextures() ? (1 << mipLevelCount) - 1 : 0; + auto tex = this->createTextureCommon(desc, format, renderable, renderTargetSampleCnt, budgeted, + isProtected, mipLevelCount, levelClearMask); + if (tex && mipMapped == GrMipMapped::kYes && levelClearMask) { + tex->texturePriv().markMipMapsClean(); + } + return tex; +} + +sk_sp GrGpu::createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + GrColorType textureColorType, + GrColorType srcColorType, + const GrMipLevel texels[], + int texelLevelCount) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + if (texelLevelCount) { + if (!validate_texel_levels(desc.fWidth, desc.fHeight, srcColorType, texels, texelLevelCount, + this->caps())) { + return nullptr; + } + } + + int mipLevelCount = SkTMax(1, texelLevelCount); + uint32_t levelClearMask = 0; + if (this->caps()->shouldInitializeTextures()) { + if (texelLevelCount) { + for (int i = 0; i < mipLevelCount; ++i) { + if (!texels->fPixels) { + levelClearMask |= static_cast(1 << i); + } + } + } else { + levelClearMask = static_cast((1 << mipLevelCount) - 1); + } + } + + auto tex = this->createTextureCommon(desc, format, renderable, renderTargetSampleCnt, budgeted, + isProtected, texelLevelCount, levelClearMask); + if (tex) { + bool markMipLevelsClean = false; + // Currently if level 0 does not have pixels then no other level may, as enforced by + // validate_texel_levels. + if (texelLevelCount && texels[0].fPixels) { + if (!this->writePixels(tex.get(), 0, 0, desc.fWidth, desc.fHeight, textureColorType, + srcColorType, texels, texelLevelCount)) { + return nullptr; + } + // Currently if level[1] of mip map has pixel data then so must all other levels. + // as enforced by validate_texel_levels. + markMipLevelsClean = (texelLevelCount > 1 && !levelClearMask && texels[1].fPixels); + fStats.incTextureUploads(); + } else if (levelClearMask && mipLevelCount > 1) { + markMipLevelsClean = true; + } + if (markMipLevelsClean) { + tex->texturePriv().markMipMapsClean(); + } + } + return tex; +} + +sk_sp GrGpu::createCompressedTexture(int width, int height, + const GrBackendFormat& format, + SkImage::CompressionType compressionType, + SkBudgeted budgeted, const void* data, + size_t dataSize) { + // If we ever add a new CompressionType, we should add a check here to make sure the + // GrBackendFormat and CompressionType are compatible with eachother. + SkASSERT(compressionType == SkImage::kETC1_CompressionType); + + this->handleDirtyContext(); + if (width < 1 || width > this->caps()->maxTextureSize() || + height < 1 || height > this->caps()->maxTextureSize()) { + return nullptr; + } + // Note if we relax the requirement that data must be provided then we must check + // caps()->shouldInitializeTextures() here. + if (!data) { + return nullptr; + } + if (!this->caps()->isFormatTexturable(format)) { + return nullptr; + } + if (dataSize < GrCompressedDataSize(compressionType, width, height)) { + return nullptr; + } + return this->onCreateCompressedTexture(width, height, format, compressionType, budgeted, data); +} + +sk_sp GrGpu::wrapBackendTexture(const GrBackendTexture& backendTex, + GrColorType colorType, + GrWrapOwnership ownership, GrWrapCacheable cacheable, + GrIOType ioType) { + SkASSERT(ioType != kWrite_GrIOType); + this->handleDirtyContext(); + + const GrCaps* caps = this->caps(); + SkASSERT(caps); + + if (!caps->isFormatTexturable(backendTex.getBackendFormat())) { + return nullptr; + } + if (backendTex.width() > caps->maxTextureSize() || + backendTex.height() > caps->maxTextureSize()) { + return nullptr; + } + + return this->onWrapBackendTexture(backendTex, colorType, ownership, cacheable, ioType); +} + +sk_sp GrGpu::wrapRenderableBackendTexture(const GrBackendTexture& backendTex, + int sampleCnt, GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable) { + this->handleDirtyContext(); + if (sampleCnt < 1) { + return nullptr; + } + + const GrCaps* caps = this->caps(); + + if (!caps->isFormatTexturable(backendTex.getBackendFormat()) || + !caps->isFormatRenderable(backendTex.getBackendFormat(), sampleCnt)) { + return nullptr; + } + + if (backendTex.width() > caps->maxRenderTargetSize() || + backendTex.height() > caps->maxRenderTargetSize()) { + return nullptr; + } + sk_sp tex = this->onWrapRenderableBackendTexture(backendTex, sampleCnt, colorType, + ownership, cacheable); + SkASSERT(!tex || tex->asRenderTarget()); + if (tex && sampleCnt > 1 && !caps->msaaResolvesAutomatically()) { + tex->asRenderTarget()->setRequiresManualMSAAResolve(); + } + return tex; +} + +sk_sp GrGpu::wrapBackendRenderTarget(const GrBackendRenderTarget& backendRT, + GrColorType colorType) { + this->handleDirtyContext(); + + const GrCaps* caps = this->caps(); + + if (!caps->isFormatRenderable(backendRT.getBackendFormat(), backendRT.sampleCnt())) { + return nullptr; + } + + return this->onWrapBackendRenderTarget(backendRT, colorType); +} + +sk_sp GrGpu::wrapBackendTextureAsRenderTarget(const GrBackendTexture& backendTex, + int sampleCnt, + GrColorType colorType) { + this->handleDirtyContext(); + + const GrCaps* caps = this->caps(); + + int maxSize = caps->maxTextureSize(); + if (backendTex.width() > maxSize || backendTex.height() > maxSize) { + return nullptr; + } + + if (!caps->isFormatRenderable(backendTex.getBackendFormat(), sampleCnt)) { + return nullptr; + } + + auto rt = this->onWrapBackendTextureAsRenderTarget(backendTex, sampleCnt, colorType); + if (rt && sampleCnt > 1 && !this->caps()->msaaResolvesAutomatically()) { + rt->setRequiresManualMSAAResolve(); + } + return rt; +} + +sk_sp GrGpu::wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo& imageInfo, + const GrVkDrawableInfo& vkInfo) { + return this->onWrapVulkanSecondaryCBAsRenderTarget(imageInfo, vkInfo); +} + +sk_sp GrGpu::onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo& imageInfo, + const GrVkDrawableInfo& vkInfo) { + // This is only supported on Vulkan so we default to returning nullptr here + return nullptr; +} + +sk_sp GrGpu::createBuffer(size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, const void* data) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + this->handleDirtyContext(); + sk_sp buffer = this->onCreateBuffer(size, intendedType, accessPattern, data); + if (!this->caps()->reuseScratchBuffers()) { + buffer->resourcePriv().removeScratchKey(); + } + return buffer; +} + +bool GrGpu::copySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(dst && src); + + if (dst->readOnly()) { + return false; + } + + this->handleDirtyContext(); + + return this->onCopySurface(dst, src, srcRect, dstPoint); +} + +bool GrGpu::readPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(surface); + SkASSERT(this->caps()->isFormatTexturable(surface->backendFormat())); + + auto subRect = SkIRect::MakeXYWH(left, top, width, height); + auto bounds = SkIRect::MakeWH(surface->width(), surface->height()); + if (!bounds.contains(subRect)) { + return false; + } + + size_t minRowBytes = SkToSizeT(GrColorTypeBytesPerPixel(dstColorType) * width); + if (!this->caps()->readPixelsRowBytesSupport()) { + if (rowBytes != minRowBytes) { + return false; + } + } else { + if (rowBytes < minRowBytes) { + return false; + } + if (rowBytes % GrColorTypeBytesPerPixel(dstColorType)) { + return false; + } + } + + if (this->caps()->isFormatCompressed(surface->backendFormat())) { + return false; + } + + this->handleDirtyContext(); + + return this->onReadPixels(surface, left, top, width, height, surfaceColorType, dstColorType, + buffer, rowBytes); +} + +bool GrGpu::writePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, bool prepForTexSampling) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(surface); + SkASSERT(this->caps()->isFormatTexturableAndUploadable(surfaceColorType, + surface->backendFormat())); + + if (surface->readOnly()) { + return false; + } + + if (mipLevelCount == 0) { + return false; + } else if (mipLevelCount == 1) { + // We require that if we are not mipped, then the write region is contained in the surface + auto subRect = SkIRect::MakeXYWH(left, top, width, height); + auto bounds = SkIRect::MakeWH(surface->width(), surface->height()); + if (!bounds.contains(subRect)) { + return false; + } + } else if (0 != left || 0 != top || width != surface->width() || height != surface->height()) { + // We require that if the texels are mipped, than the write region is the entire surface + return false; + } + + if (!validate_texel_levels(width, height, srcColorType, texels, mipLevelCount, this->caps())) { + return false; + } + + this->handleDirtyContext(); + if (this->onWritePixels(surface, left, top, width, height, surfaceColorType, srcColorType, + texels, mipLevelCount, prepForTexSampling)) { + SkIRect rect = SkIRect::MakeXYWH(left, top, width, height); + this->didWriteToSurface(surface, kTopLeft_GrSurfaceOrigin, &rect, mipLevelCount); + fStats.incTextureUploads(); + return true; + } + return false; +} + +bool GrGpu::transferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(texture); + SkASSERT(transferBuffer); + SkASSERT(this->caps()->isFormatTexturableAndUploadable(textureColorType, + texture->backendFormat())); + + if (texture->readOnly()) { + return false; + } + + // We require that the write region is contained in the texture + SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); + SkIRect bounds = SkIRect::MakeWH(texture->width(), texture->height()); + if (!bounds.contains(subRect)) { + return false; + } + + size_t bpp = GrColorTypeBytesPerPixel(bufferColorType); + if (this->caps()->writePixelsRowBytesSupport()) { + if (rowBytes < SkToSizeT(bpp * width)) { + return false; + } + if (rowBytes % bpp) { + return false; + } + } else { + if (rowBytes != SkToSizeT(bpp * width)) { + return false; + } + } + + this->handleDirtyContext(); + if (this->onTransferPixelsTo(texture, left, top, width, height, textureColorType, + bufferColorType, transferBuffer, offset, rowBytes)) { + SkIRect rect = SkIRect::MakeXYWH(left, top, width, height); + this->didWriteToSurface(texture, kTopLeft_GrSurfaceOrigin, &rect); + fStats.incTransfersToTexture(); + + return true; + } + return false; +} + +bool GrGpu::transferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(surface); + SkASSERT(transferBuffer); + SkASSERT(this->caps()->isFormatTexturable(surface->backendFormat())); + +#ifdef SK_DEBUG + auto supportedRead = this->caps()->supportedReadPixelsColorType( + surfaceColorType, surface->backendFormat(), bufferColorType); + SkASSERT(supportedRead.fOffsetAlignmentForTransferBuffer); + SkASSERT(offset % supportedRead.fOffsetAlignmentForTransferBuffer == 0); +#endif + + // We require that the write region is contained in the texture + SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); + SkIRect bounds = SkIRect::MakeWH(surface->width(), surface->height()); + if (!bounds.contains(subRect)) { + return false; + } + + this->handleDirtyContext(); + if (this->onTransferPixelsFrom(surface, left, top, width, height, surfaceColorType, + bufferColorType, transferBuffer, offset)) { + fStats.incTransfersFromSurface(); + return true; + } + return false; +} + +bool GrGpu::regenerateMipMapLevels(GrTexture* texture) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(texture); + SkASSERT(this->caps()->mipMapSupport()); + SkASSERT(texture->texturePriv().mipMapped() == GrMipMapped::kYes); + if (!texture->texturePriv().mipMapsAreDirty()) { + // This can happen when the proxy expects mipmaps to be dirty, but they are not dirty on the + // actual target. This may be caused by things that the drawingManager could not predict, + // i.e., ops that don't draw anything, aborting a draw for exceptional circumstances, etc. + // NOTE: This goes away once we quit tracking mipmap state on the actual texture. + return true; + } + if (texture->readOnly()) { + return false; + } + if (this->onRegenerateMipMapLevels(texture)) { + texture->texturePriv().markMipMapsClean(); + return true; + } + return false; +} + +void GrGpu::resetTextureBindings() { + this->handleDirtyContext(); + this->onResetTextureBindings(); +} + +void GrGpu::resolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin origin, ForExternalIO forExternalIO) { + SkASSERT(target); + this->handleDirtyContext(); + this->onResolveRenderTarget(target, resolveRect, origin, forExternalIO); +} + +void GrGpu::didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds, + uint32_t mipLevels) const { + SkASSERT(surface); + SkASSERT(!surface->readOnly()); + // Mark any MIP chain and resolve buffer as dirty if and only if there is a non-empty bounds. + if (nullptr == bounds || !bounds->isEmpty()) { + GrTexture* texture = surface->asTexture(); + if (texture && 1 == mipLevels) { + texture->texturePriv().markMipMapsDirty(); + } + } +} + +int GrGpu::findOrAssignSamplePatternKey(GrRenderTarget* renderTarget) { + SkASSERT(this->caps()->sampleLocationsSupport()); + SkASSERT(renderTarget->numSamples() > 1 || + (renderTarget->renderTargetPriv().getStencilAttachment() && + renderTarget->renderTargetPriv().getStencilAttachment()->numSamples() > 1)); + + SkSTArray<16, SkPoint> sampleLocations; + this->querySampleLocations(renderTarget, &sampleLocations); + return fSamplePatternDictionary.findOrAssignSamplePatternKey(sampleLocations); +} + +GrSemaphoresSubmitted GrGpu::finishFlush(GrSurfaceProxy* proxies[], + int n, + SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info, + const GrPrepareForExternalIORequests& externalRequests) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + this->stats()->incNumFinishFlushes(); + GrResourceProvider* resourceProvider = fContext->priv().resourceProvider(); + + if (this->caps()->semaphoreSupport()) { + for (int i = 0; i < info.fNumSemaphores; ++i) { + sk_sp semaphore; + if (info.fSignalSemaphores[i].isInitialized()) { + semaphore = resourceProvider->wrapBackendSemaphore( + info.fSignalSemaphores[i], + GrResourceProvider::SemaphoreWrapType::kWillSignal, + kBorrow_GrWrapOwnership); + } else { + semaphore = resourceProvider->makeSemaphore(false); + } + this->insertSemaphore(semaphore); + + if (!info.fSignalSemaphores[i].isInitialized()) { + info.fSignalSemaphores[i] = semaphore->backendSemaphore(); + } + } + } + this->onFinishFlush(proxies, n, access, info, externalRequests); + return this->caps()->semaphoreSupport() ? GrSemaphoresSubmitted::kYes + : GrSemaphoresSubmitted::kNo; +} + +#ifdef SK_ENABLE_DUMP_GPU +void GrGpu::dumpJSON(SkJSONWriter* writer) const { + writer->beginObject(); + + // TODO: Is there anything useful in the base class to dump here? + + this->onDumpJSON(writer); + + writer->endObject(); +} +#else +void GrGpu::dumpJSON(SkJSONWriter* writer) const { } +#endif + +#if GR_TEST_UTILS + +#if GR_GPU_STATS +void GrGpu::Stats::dump(SkString* out) { + out->appendf("Render Target Binds: %d\n", fRenderTargetBinds); + out->appendf("Shader Compilations: %d\n", fShaderCompilations); + out->appendf("Textures Created: %d\n", fTextureCreates); + out->appendf("Texture Uploads: %d\n", fTextureUploads); + out->appendf("Transfers to Texture: %d\n", fTransfersToTexture); + out->appendf("Transfers from Surface: %d\n", fTransfersFromSurface); + out->appendf("Stencil Buffer Creates: %d\n", fStencilAttachmentCreates); + out->appendf("Number of draws: %d\n", fNumDraws); + out->appendf("Number of Scratch Textures reused %d\n", fNumScratchTexturesReused); +} + +void GrGpu::Stats::dumpKeyValuePairs(SkTArray* keys, SkTArray* values) { + keys->push_back(SkString("render_target_binds")); values->push_back(fRenderTargetBinds); + keys->push_back(SkString("shader_compilations")); values->push_back(fShaderCompilations); +} + +#endif // GR_GPU_STATS +#endif // GR_TEST_UTILS + + +bool GrGpu::MipMapsAreCorrect(int baseWidth, int baseHeight, GrMipMapped mipMapped, + const SkPixmap srcData[], int numMipLevels) { + if (!srcData) { + return true; + } + + if (baseWidth != srcData[0].width() || baseHeight != srcData[0].height()) { + return false; + } + + if (mipMapped == GrMipMapped::kYes) { + if (numMipLevels != SkMipMap::ComputeLevelCount(baseWidth, baseHeight) + 1) { + return false; + } + + SkColorType colorType = srcData[0].colorType(); + + int currentWidth = baseWidth; + int currentHeight = baseHeight; + for (int i = 1; i < numMipLevels; ++i) { + currentWidth = SkTMax(1, currentWidth / 2); + currentHeight = SkTMax(1, currentHeight / 2); + + if (srcData[i].colorType() != colorType) { // all levels must have same colorType + return false; + } + + if (srcData[i].width() != currentWidth || srcData[i].height() != currentHeight) { + return false; + } + } + } else if (numMipLevels != 1) { + return false; + } + + return true; +} + +GrBackendTexture GrGpu::createBackendTexture(int w, int h, const GrBackendFormat& format, + GrMipMapped mipMapped, GrRenderable renderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected isProtected) { + const GrCaps* caps = this->caps(); + + if (!format.isValid()) { + return {}; + } + + if (caps->isFormatCompressed(format)) { + // Compressed formats must go through the createCompressedBackendTexture API + return {}; + } + + if (w < 1 || w > caps->maxTextureSize() || h < 1 || h > caps->maxTextureSize()) { + return {}; + } + + // TODO: maybe just ignore the mipMapped parameter in this case + if (mipMapped == GrMipMapped::kYes && !this->caps()->mipMapSupport()) { + return {}; + } + + if (!MipMapsAreCorrect(w, h, mipMapped, srcData, numMipLevels)) { + return {}; + } + + return this->onCreateBackendTexture(w, h, format, mipMapped, renderable, + srcData, numMipLevels, color, isProtected); +} diff --git a/gfx/skia/skia/src/gpu/GrGpu.h b/gfx/skia/skia/src/gpu/GrGpu.h new file mode 100644 index 0000000000..580345778e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpu.h @@ -0,0 +1,685 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGpu_DEFINED +#define GrGpu_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkSurface.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkTArray.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/GrSamplePatternDictionary.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/GrTextureProducer.h" +#include "src/gpu/GrXferProcessor.h" +#include + +class GrBackendRenderTarget; +class GrBackendSemaphore; +class GrGpuBuffer; +class GrContext; +struct GrContextOptions; +class GrGLContext; +class GrMesh; +class GrPath; +class GrPathRenderer; +class GrPathRendererChain; +class GrPathRendering; +class GrPipeline; +class GrPrimitiveProcessor; +class GrRenderTarget; +class GrSemaphore; +class GrStencilAttachment; +class GrStencilSettings; +class GrSurface; +class GrTexture; +class SkJSONWriter; + +class GrGpu : public SkRefCnt { +public: + GrGpu(GrContext* context); + ~GrGpu() override; + + GrContext* getContext() { return fContext; } + const GrContext* getContext() const { return fContext; } + + /** + * Gets the capabilities of the draw target. + */ + const GrCaps* caps() const { return fCaps.get(); } + sk_sp refCaps() const { return fCaps; } + + GrPathRendering* pathRendering() { return fPathRendering.get(); } + + enum class DisconnectType { + // No cleanup should be attempted, immediately cease making backend API calls + kAbandon, + // Free allocated resources (not known by GrResourceCache) before returning and + // ensure no backend backend 3D API calls will be made after disconnect() returns. + kCleanup, + }; + + // Called by GrContext when the underlying backend context is already or will be destroyed + // before GrContext. + virtual void disconnect(DisconnectType); + + /** + * The GrGpu object normally assumes that no outsider is setting state + * within the underlying 3D API's context/device/whatever. This call informs + * the GrGpu that the state was modified and it shouldn't make assumptions + * about the state. + */ + void markContextDirty(uint32_t state = kAll_GrBackendState) { fResetBits |= state; } + + /** + * Creates a texture object. If renderable is kYes then the returned texture can + * be used as a render target by calling GrTexture::asRenderTarget(). Not all + * pixel configs can be used as render targets. Support for configs as textures + * or render targets can be checked using GrCaps. + * + * @param desc describes the texture to be created. + * @param format the format for the texture (not currently used). + * @param renderable should the resulting texture be renderable + * @param renderTargetSampleCnt The number of samples to use for rendering if renderable is + * kYes. If renderable is kNo then this must be 1. + * @param budgeted does this texture count against the resource cache budget? + * @param isProtected should the texture be created as protected. + * @param texels array of mipmap levels containing texel data to load. + * If level i has pixels then it is assumed that its dimensions are + * max(1, floor(desc.fWidth / 2)) by max(1, floor(desc.fHeight / 2)). + * If texels[i].fPixels == nullptr for all i <= mipLevelCount or + * mipLevelCount is 0 then the texture's contents are uninitialized. + * If a level has non-null pixels, its row bytes must be a multiple of the + * config's bytes-per-pixel. The row bytes must be tight to the + * level width if !caps->writePixelsRowBytesSupport(). + * If mipLevelCount > 1 and texels[i].fPixels != nullptr for any i > 0 + * then all levels must have non-null pixels. All levels must have + * non-null pixels if GrCaps::createTextureMustSpecifyAllLevels() is true. + * @param textureColorType The color type interpretation of the texture for the purpose of + * of uploading texel data. + * @param srcColorType The color type of data in texels[]. + * @param texelLevelCount the number of levels in 'texels'. May be 0, 1, or + * floor(max((log2(desc.fWidth), log2(desc.fHeight)))). It must be the + * latter if GrCaps::createTextureMustSpecifyAllLevels() is true. + * @return The texture object if successful, otherwise nullptr. + */ + sk_sp createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted, + GrProtected isProtected, + GrColorType textureColorType, + GrColorType srcColorType, + const GrMipLevel texels[], + int texelLevelCount); + + /** + * Simplified createTexture() interface for when there is no initial texel data to upload. + */ + sk_sp createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrMipMapped, + SkBudgeted budgeted, + GrProtected isProtected); + + sk_sp createCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, const void* data, + size_t dataSize); + + /** + * Implements GrResourceProvider::wrapBackendTexture + */ + sk_sp wrapBackendTexture(const GrBackendTexture&, GrColorType, + GrWrapOwnership, GrWrapCacheable, GrIOType); + + /** + * Implements GrResourceProvider::wrapRenderableBackendTexture + */ + sk_sp wrapRenderableBackendTexture(const GrBackendTexture&, int sampleCnt, + GrColorType, GrWrapOwnership, GrWrapCacheable); + + /** + * Implements GrResourceProvider::wrapBackendRenderTarget + */ + sk_sp wrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType colorType); + + /** + * Implements GrResourceProvider::wrapBackendTextureAsRenderTarget + */ + sk_sp wrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, + GrColorType colorType); + + /** + * Implements GrResourceProvider::wrapVulkanSecondaryCBAsRenderTarget + */ + sk_sp wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&, + const GrVkDrawableInfo&); + + /** + * Creates a buffer in GPU memory. For a client-side buffer use GrBuffer::CreateCPUBacked. + * + * @param size size of buffer to create. + * @param intendedType hint to the graphics subsystem about what the buffer will be used for. + * @param accessPattern hint to the graphics subsystem about how the data will be accessed. + * @param data optional data with which to initialize the buffer. + * + * @return the buffer if successful, otherwise nullptr. + */ + sk_sp createBuffer(size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, const void* data = nullptr); + + enum class ForExternalIO : bool { + kYes = true, + kNo = false + }; + + /** + * Resolves MSAA. + */ + void resolveRenderTarget(GrRenderTarget*, const SkIRect& resolveRect, GrSurfaceOrigin, + ForExternalIO); + + /** + * Uses the base of the texture to recompute the contents of the other levels. + */ + bool regenerateMipMapLevels(GrTexture*); + + /** + * If the backend API has stateful texture bindings, this resets them back to defaults. + */ + void resetTextureBindings(); + + /** + * Reads a rectangle of pixels from a render target. No sRGB/linear conversions are performed. + * + * @param surface The surface to read from + * @param left left edge of the rectangle to read (inclusive) + * @param top top edge of the rectangle to read (inclusive) + * @param width width of rectangle to read in pixels. + * @param height height of rectangle to read in pixels. + * @param surfaceColorType the color type for this use of the surface. + * @param dstColorType the color type of the destination buffer. + * @param buffer memory to read the rectangle into. + * @param rowBytes the number of bytes between consecutive rows. Must be a multiple of + * dstColorType's bytes-per-pixel. Must be tight to width if + * !caps->readPixelsRowBytesSupport(). + * + * @return true if the read succeeded, false if not. The read can fail + * because of the surface doesn't support reading, the color type + * is not allowed for the format of the surface or if the rectangle + * read is not contained in the surface. + */ + bool readPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes); + + /** + * Updates the pixels in a rectangle of a surface. No sRGB/linear conversions are performed. + * + * @param surface The surface to write to. + * @param left left edge of the rectangle to write (inclusive) + * @param top top edge of the rectangle to write (inclusive) + * @param width width of rectangle to write in pixels. + * @param height height of rectangle to write in pixels. + * @param surfaceColorType the color type for this use of the surface. + * @param srcColorType the color type of the source buffer. + * @param texels array of mipmap levels containing texture data. Row bytes must be a + * multiple of srcColorType's bytes-per-pixel. Must be tight to level + * width if !caps->writePixelsRowBytesSupport(). + * @param mipLevelCount number of levels in 'texels' + * @param prepForTexSampling After doing write pixels should the surface be prepared for texture + * sampling. This is currently only used by Vulkan for inline uploads + * to set that layout back to sampled after doing the upload. Inline + * uploads currently can happen between draws in a single op so it is + * not trivial to break up the GrOpsTask into two tasks when we see + * an inline upload. However, once we are able to support doing that + * we can remove this parameter. + * + * @return true if the write succeeded, false if not. The read can fail + * because of the surface doesn't support writing (e.g. read only), + * the color type is not allowed for the format of the surface or + * if the rectangle written is not contained in the surface. + */ + bool writePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, bool prepForTexSampling = false); + + /** + * Helper for the case of a single level. + */ + bool writePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, const void* buffer, + size_t rowBytes, bool prepForTexSampling = false) { + GrMipLevel mipLevel = {buffer, rowBytes}; + return this->writePixels(surface, left, top, width, height, surfaceColorType, srcColorType, + &mipLevel, 1, prepForTexSampling); + } + + /** + * Updates the pixels in a rectangle of a texture using a buffer. If the texture is MIP mapped, + * the base level is written to. + * + * @param texture The texture to write to. + * @param left left edge of the rectangle to write (inclusive) + * @param top top edge of the rectangle to write (inclusive) + * @param width width of rectangle to write in pixels. + * @param height height of rectangle to write in pixels. + * @param textureColorType the color type for this use of the surface. + * @param bufferColorType the color type of the transfer buffer's pixel data + * @param transferBuffer GrBuffer to read pixels from (type must be "kXferCpuToGpu") + * @param offset offset from the start of the buffer + * @param rowBytes number of bytes between consecutive rows in the buffer. Must be a + * multiple of bufferColorType's bytes-per-pixel. Must be tight to width + * if !caps->writePixelsRowBytesSupport(). + */ + bool transferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes); + + /** + * Reads the pixels from a rectangle of a surface into a buffer. Use + * GrCaps::SupportedRead::fOffsetAlignmentForTransferBuffer to determine the requirements for + * the buffer offset alignment. If the surface is a MIP mapped texture, the base level is read. + * + * If successful the row bytes in the buffer is always: + * GrColorTypeBytesPerPixel(bufferColorType) * width + * + * Asserts that the caller has passed a properly aligned offset and that the buffer is + * large enough to hold the result + * + * @param surface The surface to read from. + * @param left left edge of the rectangle to read (inclusive) + * @param top top edge of the rectangle to read (inclusive) + * @param width width of rectangle to read in pixels. + * @param height height of rectangle to read in pixels. + * @param surfaceColorType the color type for this use of the surface. + * @param bufferColorType the color type of the transfer buffer's pixel data + * @param transferBuffer GrBuffer to write pixels to (type must be "kXferGpuToCpu") + * @param offset offset from the start of the buffer + */ + bool transferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset); + + // Called to perform a surface to surface copy. Fallbacks to issuing a draw from the src to dst + // take place at higher levels and this function implement faster copy paths. The rect + // and point are pre-clipped. The src rect and implied dst rect are guaranteed to be within the + // src/dst bounds and non-empty. They must also be in their exact device space coords, including + // already being transformed for origin if need be. If canDiscardOutsideDstRect is set to true + // then we don't need to preserve any data on the dst surface outside of the copy. + bool copySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + // Queries the per-pixel HW sample locations for the given render target, and then finds or + // assigns a key that uniquely identifies the sample pattern. The actual sample locations can be + // retrieved with retrieveSampleLocations(). + int findOrAssignSamplePatternKey(GrRenderTarget*); + + // Retrieves the per-pixel HW sample locations for the given sample pattern key, and, as a + // by-product, the actual number of samples in use. (This may differ from the number of samples + // requested by the render target.) Sample locations are returned as 0..1 offsets relative to + // the top-left corner of the pixel. + const SkTArray& retrieveSampleLocations(int samplePatternKey) const { + return fSamplePatternDictionary.retrieveSampleLocations(samplePatternKey); + } + + // Returns a GrOpsRenderPass which GrOpsTasks send draw commands to instead of directly + // to the Gpu object. The 'bounds' rect is the content rect of the renderTarget. + virtual GrOpsRenderPass* getOpsRenderPass( + GrRenderTarget* renderTarget, GrSurfaceOrigin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) = 0; + + // Called by GrDrawingManager when flushing. + // Provides a hook for post-flush actions (e.g. Vulkan command buffer submits). This will also + // insert any numSemaphore semaphores on the gpu and set the backendSemaphores to match the + // inserted semaphores. + GrSemaphoresSubmitted finishFlush(GrSurfaceProxy*[], int n, + SkSurface::BackendSurfaceAccess access, const GrFlushInfo&, + const GrPrepareForExternalIORequests&); + + virtual void submit(GrOpsRenderPass*) = 0; + + virtual GrFence SK_WARN_UNUSED_RESULT insertFence() = 0; + virtual bool waitFence(GrFence, uint64_t timeout = 1000) = 0; + virtual void deleteFence(GrFence) const = 0; + + virtual sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned = true) = 0; + virtual sk_sp wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) = 0; + virtual void insertSemaphore(sk_sp semaphore) = 0; + virtual void waitSemaphore(sk_sp semaphore) = 0; + + virtual void checkFinishProcs() = 0; + + /** + * Put this texture in a safe and known state for use across multiple GrContexts. Depending on + * the backend, this may return a GrSemaphore. If so, other contexts should wait on that + * semaphore before using this texture. + */ + virtual sk_sp prepareTextureForCrossContextUsage(GrTexture*) = 0; + + /////////////////////////////////////////////////////////////////////////// + // Debugging and Stats + + class Stats { + public: +#if GR_GPU_STATS + Stats() = default; + + void reset() { *this = {}; } + + int renderTargetBinds() const { return fRenderTargetBinds; } + void incRenderTargetBinds() { fRenderTargetBinds++; } + + int shaderCompilations() const { return fShaderCompilations; } + void incShaderCompilations() { fShaderCompilations++; } + + int textureCreates() const { return fTextureCreates; } + void incTextureCreates() { fTextureCreates++; } + + int textureUploads() const { return fTextureUploads; } + void incTextureUploads() { fTextureUploads++; } + + int transfersToTexture() const { return fTransfersToTexture; } + void incTransfersToTexture() { fTransfersToTexture++; } + + int transfersFromSurface() const { return fTransfersFromSurface; } + void incTransfersFromSurface() { fTransfersFromSurface++; } + + int stencilAttachmentCreates() const { return fStencilAttachmentCreates; } + void incStencilAttachmentCreates() { fStencilAttachmentCreates++; } + + int numDraws() const { return fNumDraws; } + void incNumDraws() { fNumDraws++; } + + int numFailedDraws() const { return fNumFailedDraws; } + void incNumFailedDraws() { ++fNumFailedDraws; } + + int numFinishFlushes() const { return fNumFinishFlushes; } + void incNumFinishFlushes() { ++fNumFinishFlushes; } + + int numScratchTexturesReused() const { return fNumScratchTexturesReused; } + void incNumScratchTexturesReused() { ++fNumScratchTexturesReused; } + +#if GR_TEST_UTILS + void dump(SkString*); + void dumpKeyValuePairs(SkTArray* keys, SkTArray* values); +#endif + private: + int fRenderTargetBinds = 0; + int fShaderCompilations = 0; + int fTextureCreates = 0; + int fTextureUploads = 0; + int fTransfersToTexture = 0; + int fTransfersFromSurface = 0; + int fStencilAttachmentCreates = 0; + int fNumDraws = 0; + int fNumFailedDraws = 0; + int fNumFinishFlushes = 0; + int fNumScratchTexturesReused = 0; +#else + +#if GR_TEST_UTILS + void dump(SkString*) {} + void dumpKeyValuePairs(SkTArray*, SkTArray*) {} +#endif + void incRenderTargetBinds() {} + void incShaderCompilations() {} + void incTextureCreates() {} + void incTextureUploads() {} + void incTransfersToTexture() {} + void incStencilAttachmentCreates() {} + void incNumDraws() {} + void incNumFailedDraws() {} + void incNumFinishFlushes() {} +#endif + }; + + Stats* stats() { return &fStats; } + void dumpJSON(SkJSONWriter*) const; + + /** + * Creates a texture directly in the backend API without wrapping it in a GrTexture. + * Must be matched with a call to deleteBackendTexture(). + * + * If srcData is provided it will be used to initialize the texture. If srcData is + * not provided but a color is then it is used to initialize the texture. If neither + * srcData nor a color is provided then the texture is left uninitialized. + * + * If srcData is provided and mipMapped is kYes then data for all the miplevels must be + * provided (or the method will fail). If only a color is provided and mipMapped is kYes + * then all the mip levels will be allocated and initialized to the color. If neither + * srcData nor a color is provided but mipMapped is kYes then the mip levels will be allocated + * but left uninitialized. + * + * Note: if more than one pixmap is provided (i.e., for mipmap levels) they must all share + * the same SkColorType. + */ + GrBackendTexture createBackendTexture(int w, int h, const GrBackendFormat&, + GrMipMapped, GrRenderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected isProtected); + + /** + * Frees a texture created by createBackendTexture(). If ownership of the backend + * texture has been transferred to a GrContext using adopt semantics this should not be called. + */ + virtual void deleteBackendTexture(const GrBackendTexture&) = 0; + + virtual bool precompileShader(const SkData& key, const SkData& data) { return false; } + +#if GR_TEST_UTILS + /** Check a handle represents an actual texture in the backend API that has not been freed. */ + virtual bool isTestingOnlyBackendTexture(const GrBackendTexture&) const = 0; + + virtual GrBackendRenderTarget createTestingOnlyBackendRenderTarget(int w, int h, + GrColorType) = 0; + + virtual void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) = 0; + + // This is only to be used in GL-specific tests. + virtual const GrGLContext* glContextForTesting() const { return nullptr; } + + // This is only to be used by testing code + virtual void resetShaderCacheForTesting() const {} + + /** + * Flushes all work to the gpu and forces the GPU to wait until all the gpu work has completed. + * This is for testing purposes only. + */ + virtual void testingOnly_flushGpuAndSync() = 0; + + /** + * Inserted as a pair around a block of code to do a GPU frame capture. + * Currently only works with the Metal backend. + */ + virtual void testingOnly_startCapture() {} + virtual void testingOnly_endCapture() {} +#endif + + // width and height may be larger than rt (if underlying API allows it). + // Returns nullptr if compatible sb could not be created, otherwise the caller owns the ref on + // the GrStencilAttachment. + virtual GrStencilAttachment* createStencilAttachmentForRenderTarget( + const GrRenderTarget*, int width, int height, int numStencilSamples) = 0; + + // Determines whether a texture will need to be rescaled in order to be used with the + // GrSamplerState. + static bool IsACopyNeededForRepeatWrapMode(const GrCaps*, GrTextureProxy* texProxy, + int width, int height, + GrSamplerState::Filter, + GrTextureProducer::CopyParams*, + SkScalar scaleAdjust[2]); + + // Determines whether a texture will need to be copied because the draw requires mips but the + // texutre doesn't have any. This call should be only checked if IsACopyNeededForTextureParams + // fails. If the previous call succeeds, then a copy should be done using those params and the + // mip mapping requirements will be handled there. + static bool IsACopyNeededForMips(const GrCaps* caps, const GrTextureProxy* texProxy, + GrSamplerState::Filter filter, + GrTextureProducer::CopyParams* copyParams); + + void handleDirtyContext() { + if (fResetBits) { + this->resetContext(); + } + } + + /** + * Returns a key that represents the sampler that will be created for the passed in parameters. + * Currently this key is only used when we are building a vulkan pipeline with immutable + * samplers. In that case, we need our cache key to also contain this key. + * + * A return value of 0 indicates that the program/pipeline we are creating is not affected by + * the sampler. + */ + virtual uint32_t getExtraSamplerKeyForProgram(const GrSamplerState&, const GrBackendFormat&) { + return 0; + } + + virtual void storeVkPipelineCacheData() {} + +protected: + static bool MipMapsAreCorrect(int baseWidth, int baseHeight, GrMipMapped, + const SkPixmap srcData[], int numMipLevels); + + // Handles cases where a surface will be updated without a call to flushRenderTarget. + void didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds, + uint32_t mipLevels = 1) const; + + Stats fStats; + std::unique_ptr fPathRendering; + // Subclass must initialize this in its constructor. + sk_sp fCaps; + +private: + virtual GrBackendTexture onCreateBackendTexture(int w, int h, const GrBackendFormat&, + GrMipMapped, GrRenderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected) = 0; + + // called when the 3D context state is unknown. Subclass should emit any + // assumed 3D context state and dirty any state cache. + virtual void onResetContext(uint32_t resetBits) = 0; + + // Implementation of resetTextureBindings. + virtual void onResetTextureBindings() {} + + // Queries the effective number of samples in use by the hardware for the given render target, + // and queries the individual sample locations. + virtual void querySampleLocations(GrRenderTarget*, SkTArray*) = 0; + + // Called before certain draws in order to guarantee coherent results from dst reads. + virtual void xferBarrier(GrRenderTarget*, GrXferBarrierType) = 0; + + // overridden by backend-specific derived class to create objects. + // Texture size, renderablility, format support, sample count will have already been validated + // in base class before onCreateTexture is called. + // If the ith bit is set in levelClearMask then the ith MIP level should be cleared. + virtual sk_sp onCreateTexture(const GrSurfaceDesc&, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted, + GrProtected, + int mipLevelCoont, + uint32_t levelClearMask) = 0; + virtual sk_sp onCreateCompressedTexture(int width, int height, + const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, + const void* data) = 0; + virtual sk_sp onWrapBackendTexture(const GrBackendTexture&, GrColorType, + GrWrapOwnership, GrWrapCacheable, GrIOType) = 0; + virtual sk_sp onWrapRenderableBackendTexture(const GrBackendTexture&, int sampleCnt, + GrColorType, GrWrapOwnership, + GrWrapCacheable) = 0; + virtual sk_sp onWrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType) = 0; + virtual sk_sp onWrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, + GrColorType) = 0; + virtual sk_sp onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&, + const GrVkDrawableInfo&); + + virtual sk_sp onCreateBuffer(size_t size, GrGpuBufferType intendedType, + GrAccessPattern, const void* data) = 0; + + // overridden by backend-specific derived class to perform the surface read + virtual bool onReadPixels(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) = 0; + + // overridden by backend-specific derived class to perform the surface write + virtual bool onWritePixels(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) = 0; + + // overridden by backend-specific derived class to perform the texture transfer + virtual bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, + GrColorType textiueColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, + size_t rowBytes) = 0; + // overridden by backend-specific derived class to perform the surface transfer + virtual bool onTransferPixelsFrom(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) = 0; + + // overridden by backend-specific derived class to perform the resolve + virtual void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin resolveOrigin, ForExternalIO) = 0; + + // overridden by backend specific derived class to perform mip map level regeneration. + virtual bool onRegenerateMipMapLevels(GrTexture*) = 0; + + // overridden by backend specific derived class to perform the copy surface + virtual bool onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) = 0; + + virtual void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo&, const GrPrepareForExternalIORequests&) = 0; + +#ifdef SK_ENABLE_DUMP_GPU + virtual void onDumpJSON(SkJSONWriter*) const {} +#endif + + sk_sp createTextureCommon(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + int mipLevelCnt, + uint32_t levelClearMask); + + void resetContext() { + this->onResetContext(fResetBits); + fResetBits = 0; + } + + uint32_t fResetBits; + // The context owns us, not vice-versa, so this ptr is not ref'ed by Gpu. + GrContext* fContext; + GrSamplePatternDictionary fSamplePatternDictionary; + + friend class GrPathRendering; + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrGpuBuffer.cpp b/gfx/skia/skia/src/gpu/GrGpuBuffer.cpp new file mode 100644 index 0000000000..64e1167723 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpuBuffer.cpp @@ -0,0 +1,68 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrGpuBuffer.h" + +GrGpuBuffer::GrGpuBuffer(GrGpu* gpu, size_t sizeInBytes, GrGpuBufferType type, + GrAccessPattern pattern) + : GrGpuResource(gpu) + , fMapPtr(nullptr) + , fSizeInBytes(sizeInBytes) + , fAccessPattern(pattern) + , fIntendedType(type) {} + +void* GrGpuBuffer::map() { + if (this->wasDestroyed()) { + return nullptr; + } + if (!fMapPtr) { + this->onMap(); + } + return fMapPtr; +} + +void GrGpuBuffer::unmap() { + if (this->wasDestroyed()) { + return; + } + SkASSERT(fMapPtr); + this->onUnmap(); + fMapPtr = nullptr; +} + +bool GrGpuBuffer::isMapped() const { return SkToBool(fMapPtr); } + +bool GrGpuBuffer::updateData(const void* src, size_t srcSizeInBytes) { + SkASSERT(!this->isMapped()); + SkASSERT(srcSizeInBytes <= fSizeInBytes); + if (this->intendedType() == GrGpuBufferType::kXferGpuToCpu) { + return false; + } + return this->onUpdateData(src, srcSizeInBytes); +} + +void GrGpuBuffer::ComputeScratchKeyForDynamicVBO(size_t size, GrGpuBufferType intendedType, + GrScratchKey* key) { + static const GrScratchKey::ResourceType kType = GrScratchKey::GenerateResourceType(); + GrScratchKey::Builder builder(key, kType, 1 + (sizeof(size_t) + 3) / 4); + // TODO: There's not always reason to cache a buffer by type. In some (all?) APIs it's just + // a chunk of memory we can use/reuse for any type of data. We really only need to + // differentiate between the "read" types (e.g. kGpuToCpu_BufferType) and "draw" types. + builder[0] = SkToU32(intendedType); + builder[1] = (uint32_t)size; + if (sizeof(size_t) > 4) { + builder[2] = (uint32_t)((uint64_t)size >> 32); + } +} + +void GrGpuBuffer::computeScratchKey(GrScratchKey* key) const { + if (SkIsPow2(fSizeInBytes) && kDynamic_GrAccessPattern == fAccessPattern) { + ComputeScratchKeyForDynamicVBO(fSizeInBytes, fIntendedType, key); + } +} diff --git a/gfx/skia/skia/src/gpu/GrGpuBuffer.h b/gfx/skia/skia/src/gpu/GrGpuBuffer.h new file mode 100644 index 0000000000..7e3561eb26 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpuBuffer.h @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGpuBuffer_DEFINED +#define GrGpuBuffer_DEFINED + +#include "include/gpu/GrGpuResource.h" +#include "src/gpu/GrBuffer.h" + +class GrGpu; + +class GrGpuBuffer : public GrGpuResource, public GrBuffer { +public: + /** + * Computes a scratch key for a GPU-side buffer with a "dynamic" access pattern. (Buffers with + * "static" and "stream" patterns are disqualified by nature from being cached and reused.) + */ + static void ComputeScratchKeyForDynamicVBO(size_t size, GrGpuBufferType, GrScratchKey*); + + GrAccessPattern accessPattern() const { return fAccessPattern; } + + size_t size() const final { return fSizeInBytes; } + + void ref() const final { GrGpuResource::ref(); } + + void unref() const final { GrGpuResource::unref(); } + + /** + * Maps the buffer to be read or written by the CPU. + * + * It is an error to draw from the buffer while it is mapped or transfer to/from the buffer. It + * may fail if the backend doesn't support mapping the buffer. Once a buffer is mapped, + * subsequent calls to map() trivially succeed. No matter how many times map() is called, + * umap() will unmap the buffer on the first call if it is mapped. + * + * If the buffer is of type GrGpuBufferType::kXferGpuToCpu then it is mapped for reading only. + * Otherwise it is mapped writing only. Writing to a buffer that is mapped for reading or vice + * versa produces undefined results. If the buffer is mapped for writing then then the buffer's + * previous contents are invalidated. + * + * @return a pointer to the data or nullptr if the map fails. + */ + void* map(); + + /** + * Unmaps the buffer if it is mapped. + * + * The pointer returned by the previous map call will no longer be valid. + */ + void unmap(); + + /** + * Queries whether the buffer has been mapped. + * + * @return true if the buffer is mapped, false otherwise. + */ + bool isMapped() const; + + bool isCpuBuffer() const final { return false; } + + /** + * Updates the buffer data. + * + * The size of the buffer will be preserved. The src data will be + * placed at the beginning of the buffer and any remaining contents will + * be undefined. srcSizeInBytes must be <= to the buffer size. + * + * The buffer must not be mapped. + * + * Fails for GrGpuBufferType::kXferGpuToCpu. + * + * Note that buffer updates do not go through GrContext and therefore are + * not serialized with other operations. + * + * @return returns true if the update succeeds, false otherwise. + */ + bool updateData(const void* src, size_t srcSizeInBytes); + +protected: + GrGpuBuffer(GrGpu*, size_t sizeInBytes, GrGpuBufferType, GrAccessPattern); + GrGpuBufferType intendedType() const { return fIntendedType; } + + void* fMapPtr; + +private: + virtual void onMap() = 0; + virtual void onUnmap() = 0; + virtual bool onUpdateData(const void* src, size_t srcSizeInBytes) = 0; + + size_t onGpuMemorySize() const override { return fSizeInBytes; } + const char* getResourceType() const override { return "Buffer Object"; } + void computeScratchKey(GrScratchKey* key) const override; + + size_t fSizeInBytes; + GrAccessPattern fAccessPattern; + GrGpuBufferType fIntendedType; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrGpuResource.cpp b/gfx/skia/skia/src/gpu/GrGpuResource.cpp new file mode 100644 index 0000000000..71f92932fa --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpuResource.cpp @@ -0,0 +1,214 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTraceMemoryDump.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrGpuResource.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrResourceCache.h" +#include + +static inline GrResourceCache* get_resource_cache(GrGpu* gpu) { + SkASSERT(gpu); + SkASSERT(gpu->getContext()); + SkASSERT(gpu->getContext()->priv().getResourceCache()); + return gpu->getContext()->priv().getResourceCache(); +} + +GrGpuResource::GrGpuResource(GrGpu* gpu) : fGpu(gpu), fUniqueID(CreateUniqueID()) { + SkDEBUGCODE(fCacheArrayIndex = -1); +} + +void GrGpuResource::registerWithCache(SkBudgeted budgeted) { + SkASSERT(fBudgetedType == GrBudgetedType::kUnbudgetedUncacheable); + fBudgetedType = budgeted == SkBudgeted::kYes ? GrBudgetedType::kBudgeted + : GrBudgetedType::kUnbudgetedUncacheable; + this->computeScratchKey(&fScratchKey); + get_resource_cache(fGpu)->resourceAccess().insertResource(this); +} + +void GrGpuResource::registerWithCacheWrapped(GrWrapCacheable wrapType) { + SkASSERT(fBudgetedType == GrBudgetedType::kUnbudgetedUncacheable); + // Resources referencing wrapped objects are never budgeted. They may be cached or uncached. + fBudgetedType = wrapType == GrWrapCacheable::kNo ? GrBudgetedType::kUnbudgetedUncacheable + : GrBudgetedType::kUnbudgetedCacheable; + fRefsWrappedObjects = true; + get_resource_cache(fGpu)->resourceAccess().insertResource(this); +} + +GrGpuResource::~GrGpuResource() { + // The cache should have released or destroyed this resource. + SkASSERT(this->wasDestroyed()); +} + +void GrGpuResource::release() { + SkASSERT(fGpu); + this->onRelease(); + get_resource_cache(fGpu)->resourceAccess().removeResource(this); + fGpu = nullptr; + fGpuMemorySize = 0; +} + +void GrGpuResource::abandon() { + if (this->wasDestroyed()) { + return; + } + SkASSERT(fGpu); + this->onAbandon(); + get_resource_cache(fGpu)->resourceAccess().removeResource(this); + fGpu = nullptr; + fGpuMemorySize = 0; +} + +void GrGpuResource::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + if (this->fRefsWrappedObjects && !traceMemoryDump->shouldDumpWrappedObjects()) { + return; + } + + this->dumpMemoryStatisticsPriv(traceMemoryDump, this->getResourceName(), + this->getResourceType(), this->gpuMemorySize()); +} + +void GrGpuResource::dumpMemoryStatisticsPriv(SkTraceMemoryDump* traceMemoryDump, + const SkString& resourceName, + const char* type, size_t size) const { + const char* tag = "Scratch"; + if (fUniqueKey.isValid()) { + tag = (fUniqueKey.tag() != nullptr) ? fUniqueKey.tag() : "Other"; + } + + traceMemoryDump->dumpNumericValue(resourceName.c_str(), "size", "bytes", size); + traceMemoryDump->dumpStringValue(resourceName.c_str(), "type", type); + traceMemoryDump->dumpStringValue(resourceName.c_str(), "category", tag); + if (this->isPurgeable()) { + traceMemoryDump->dumpNumericValue(resourceName.c_str(), "purgeable_size", "bytes", size); + } + + this->setMemoryBacking(traceMemoryDump, resourceName); +} + +bool GrGpuResource::isPurgeable() const { + // Resources in the kUnbudgetedCacheable state are never purgeable when they have a unique + // key. The key must be removed/invalidated to make them purgeable. + return !this->hasRef() && + !(fBudgetedType == GrBudgetedType::kUnbudgetedCacheable && fUniqueKey.isValid()); +} + +bool GrGpuResource::hasRef() const { return this->internalHasRef(); } + +SkString GrGpuResource::getResourceName() const { + // Dump resource as "skia/gpu_resources/resource_#". + SkString resourceName("skia/gpu_resources/resource_"); + resourceName.appendU32(this->uniqueID().asUInt()); + return resourceName; +} + +const GrContext* GrGpuResource::getContext() const { + if (fGpu) { + return fGpu->getContext(); + } else { + return nullptr; + } +} + +GrContext* GrGpuResource::getContext() { + if (fGpu) { + return fGpu->getContext(); + } else { + return nullptr; + } +} + +void GrGpuResource::removeUniqueKey() { + if (this->wasDestroyed()) { + return; + } + SkASSERT(fUniqueKey.isValid()); + get_resource_cache(fGpu)->resourceAccess().removeUniqueKey(this); +} + +void GrGpuResource::setUniqueKey(const GrUniqueKey& key) { + SkASSERT(this->internalHasRef()); + SkASSERT(key.isValid()); + + // Uncached resources can never have a unique key, unless they're wrapped resources. Wrapped + // resources are a special case: the unique keys give us a weak ref so that we can reuse the + // same resource (rather than re-wrapping). When a wrapped resource is no longer referenced, + // it will always be released - it is never converted to a scratch resource. + if (this->resourcePriv().budgetedType() != GrBudgetedType::kBudgeted && + !this->fRefsWrappedObjects) { + return; + } + + if (this->wasDestroyed()) { + return; + } + + get_resource_cache(fGpu)->resourceAccess().changeUniqueKey(this, key); +} + +void GrGpuResource::notifyRefCntWillBeZero() const { + GrGpuResource* mutableThis = const_cast(this); + mutableThis->willRemoveLastRef(); +} + +void GrGpuResource::notifyRefCntIsZero() const { + if (this->wasDestroyed()) { + // We've already been removed from the cache. Goodbye cruel world! + delete this; + return; + } + + GrGpuResource* mutableThis = const_cast(this); + + get_resource_cache(fGpu)->resourceAccess().notifyRefCntReachedZero(mutableThis); +} + +void GrGpuResource::removeScratchKey() { + if (!this->wasDestroyed() && fScratchKey.isValid()) { + get_resource_cache(fGpu)->resourceAccess().willRemoveScratchKey(this); + fScratchKey.reset(); + } +} + +void GrGpuResource::makeBudgeted() { + // We should never make a wrapped resource budgeted. + SkASSERT(!fRefsWrappedObjects); + // Only wrapped resources can be in the kUnbudgetedCacheable state. + SkASSERT(fBudgetedType != GrBudgetedType::kUnbudgetedCacheable); + if (!this->wasDestroyed() && fBudgetedType == GrBudgetedType::kUnbudgetedUncacheable) { + // Currently resources referencing wrapped objects are not budgeted. + fBudgetedType = GrBudgetedType::kBudgeted; + get_resource_cache(fGpu)->resourceAccess().didChangeBudgetStatus(this); + } +} + +void GrGpuResource::makeUnbudgeted() { + if (!this->wasDestroyed() && fBudgetedType == GrBudgetedType::kBudgeted && + !fUniqueKey.isValid()) { + fBudgetedType = GrBudgetedType::kUnbudgetedUncacheable; + get_resource_cache(fGpu)->resourceAccess().didChangeBudgetStatus(this); + } +} + +uint32_t GrGpuResource::CreateUniqueID() { + static std::atomic nextID{1}; + uint32_t id; + do { + id = nextID++; + } while (id == SK_InvalidUniqueID); + return id; +} + +////////////////////////////////////////////////////////////////////////////// + +void GrGpuResource::ProxyAccess::ref(GrResourceCache* cache) { + SkASSERT(cache == fResource->getContext()->priv().getResourceCache()); + cache->resourceAccess().refResource(fResource); +} diff --git a/gfx/skia/skia/src/gpu/GrGpuResourceCacheAccess.h b/gfx/skia/skia/src/gpu/GrGpuResourceCacheAccess.h new file mode 100644 index 0000000000..b18949c262 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpuResourceCacheAccess.h @@ -0,0 +1,103 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGpuResourceCacheAccess_DEFINED +#define GrGpuResourceCacheAccess_DEFINED + +#include "include/gpu/GrGpuResource.h" +#include "src/gpu/GrGpuResourcePriv.h" + +namespace skiatest { + class Reporter; +} + +/** + * This class allows GrResourceCache increased privileged access to GrGpuResource objects. + */ +class GrGpuResource::CacheAccess { +private: + /** The cache is allowed to go from no refs to 1 ref. */ + void ref() { fResource->addInitialRef(); } + + /** + * Is the resource currently cached as scratch? This means it is cached, has a valid scratch + * key, and does not have a unique key. + */ + bool isScratch() const { + return !fResource->getUniqueKey().isValid() && fResource->fScratchKey.isValid() && + GrBudgetedType::kBudgeted == fResource->resourcePriv().budgetedType(); + } + + /** + * Called by the cache to delete the resource under normal circumstances. + */ + void release() { + fResource->release(); + if (!fResource->hasRef()) { + delete fResource; + } + } + + /** + * Called by the cache to delete the resource when the backend 3D context is no longer valid. + */ + void abandon() { + fResource->abandon(); + if (!fResource->hasRef()) { + delete fResource; + } + } + + /** Called by the cache to assign a new unique key. */ + void setUniqueKey(const GrUniqueKey& key) { fResource->fUniqueKey = key; } + + /** Is the resource ref'ed */ + bool hasRef() const { return fResource->hasRef(); } + + /** Called by the cache to make the unique key invalid. */ + void removeUniqueKey() { fResource->fUniqueKey.reset(); } + + uint32_t timestamp() const { return fResource->fTimestamp; } + void setTimestamp(uint32_t ts) { fResource->fTimestamp = ts; } + + void setTimeWhenResourceBecomePurgeable() { + SkASSERT(fResource->isPurgeable()); + fResource->fTimeWhenBecamePurgeable = GrStdSteadyClock::now(); + } + /** + * Called by the cache to determine whether this resource should be purged based on the length + * of time it has been available for purging. + */ + GrStdSteadyClock::time_point timeWhenResourceBecamePurgeable() { + SkASSERT(fResource->isPurgeable()); + return fResource->fTimeWhenBecamePurgeable; + } + + int* accessCacheIndex() const { return &fResource->fCacheArrayIndex; } + + CacheAccess(GrGpuResource* resource) : fResource(resource) {} + CacheAccess(const CacheAccess& that) : fResource(that.fResource) {} + CacheAccess& operator=(const CacheAccess&); // unimpl + + // No taking addresses of this type. + const CacheAccess* operator&() const = delete; + CacheAccess* operator&() = delete; + + GrGpuResource* fResource; + + friend class GrGpuResource; // to construct/copy this type. + friend class GrResourceCache; // to use this type + friend void test_unbudgeted_to_scratch(skiatest::Reporter* reporter); // for unit testing +}; + +inline GrGpuResource::CacheAccess GrGpuResource::cacheAccess() { return CacheAccess(this); } + +inline const GrGpuResource::CacheAccess GrGpuResource::cacheAccess() const { + return CacheAccess(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrGpuResourcePriv.h b/gfx/skia/skia/src/gpu/GrGpuResourcePriv.h new file mode 100644 index 0000000000..fc1e5935b3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrGpuResourcePriv.h @@ -0,0 +1,95 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGpuResourcePriv_DEFINED +#define GrGpuResourcePriv_DEFINED + +#include "include/gpu/GrGpuResource.h" + +/** + * This class allows code internal to Skia privileged access to manage the cache keys and budget + * status of a GrGpuResource object. + */ +class GrGpuResource::ResourcePriv { +public: + /** + * Sets a unique key for the resource. If the resource was previously cached as scratch it will + * be converted to a uniquely-keyed resource. If the key is invalid then this is equivalent to + * removeUniqueKey(). If another resource is using the key then its unique key is removed and + * this resource takes over the key. + */ + void setUniqueKey(const GrUniqueKey& key) { fResource->setUniqueKey(key); } + + /** Removes the unique key from a resource. If the resource has a scratch key, it may be + preserved for recycling as scratch. */ + void removeUniqueKey() { fResource->removeUniqueKey(); } + + /** + * If the resource is uncached make it cached. Has no effect on resources that are wrapped or + * already cached. + */ + void makeBudgeted() { fResource->makeBudgeted(); } + + /** + * If the resource is cached make it uncached. Has no effect on resources that are wrapped or + * already uncached. Furthermore, resources with unique keys cannot be made unbudgeted. + */ + void makeUnbudgeted() { fResource->makeUnbudgeted(); } + + /** + * Get the resource's budgeted-type which indicates whether it counts against the resource cache + * budget and if not whether it is allowed to be cached. + */ + GrBudgetedType budgetedType() const { + SkASSERT(GrBudgetedType::kBudgeted == fResource->fBudgetedType || + !fResource->getUniqueKey().isValid() || fResource->fRefsWrappedObjects); + return fResource->fBudgetedType; + } + + /** + * Is the resource object wrapping an externally allocated GPU resource? + */ + bool refsWrappedObjects() const { return fResource->fRefsWrappedObjects; } + + /** + * If this resource can be used as a scratch resource this returns a valid scratch key. + * Otherwise it returns a key for which isNullScratch is true. The resource may currently be + * used as a uniquely keyed resource rather than scratch. Check isScratch(). + */ + const GrScratchKey& getScratchKey() const { return fResource->fScratchKey; } + + /** + * If the resource has a scratch key, the key will be removed. Since scratch keys are installed + * at resource creation time, this means the resource will never again be used as scratch. + */ + void removeScratchKey() const { fResource->removeScratchKey(); } + + bool isPurgeable() const { return fResource->isPurgeable(); } + + bool hasRef() const { return fResource->hasRef(); } + +protected: + ResourcePriv(GrGpuResource* resource) : fResource(resource) { } + ResourcePriv(const ResourcePriv& that) : fResource(that.fResource) {} + ResourcePriv& operator=(const CacheAccess&); // unimpl + + // No taking addresses of this type. + const ResourcePriv* operator&() const; + ResourcePriv* operator&(); + + GrGpuResource* fResource; + + friend class GrGpuResource; // to construct/copy this type. +}; + +inline GrGpuResource::ResourcePriv GrGpuResource::resourcePriv() { return ResourcePriv(this); } + +inline const GrGpuResource::ResourcePriv GrGpuResource::resourcePriv() const { + return ResourcePriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrImageContext.cpp b/gfx/skia/skia/src/gpu/GrImageContext.cpp new file mode 100644 index 0000000000..db4c9cba54 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrImageContext.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrImageContext.h" + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrImageContextPriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" + +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());) + +/////////////////////////////////////////////////////////////////////////////////////////////////// +GrImageContext::GrImageContext(GrBackendApi backend, + const GrContextOptions& options, + uint32_t contextID) + : INHERITED(backend, options, contextID) { + fProxyProvider.reset(new GrProxyProvider(this)); +} + +GrImageContext::~GrImageContext() {} + +void GrImageContext::abandonContext() { + ASSERT_SINGLE_OWNER + + fAbandoned = true; +} + +bool GrImageContext::abandoned() const { + ASSERT_SINGLE_OWNER + + return fAbandoned; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +sk_sp GrImageContextPriv::refCaps() const { + return fContext->refCaps(); +} + +sk_sp GrImageContextPriv::fpFactoryCache() { + return fContext->fpFactoryCache(); +} diff --git a/gfx/skia/skia/src/gpu/GrImageContextPriv.h b/gfx/skia/skia/src/gpu/GrImageContextPriv.h new file mode 100644 index 0000000000..db9313e51b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrImageContextPriv.h @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrImageContextPriv_DEFINED +#define GrImageContextPriv_DEFINED + +#include "include/private/GrImageContext.h" + +/** Class that exposes methods on GrImageContext that are only intended for use internal to Skia. + This class is purely a privileged window into GrImageContext. It should never have + additional data members or virtual methods. */ +class GrImageContextPriv { +public: + // from GrContext_Base + uint32_t contextID() const { return fContext->contextID(); } + + bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); } + + const GrContextOptions& options() const { return fContext->options(); } + + const GrCaps* caps() const { return fContext->caps(); } + sk_sp refCaps() const; + + sk_sp fpFactoryCache(); + + GrImageContext* asImageContext() { return fContext->asImageContext(); } + GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); } + GrContext* asDirectContext() { return fContext->asDirectContext(); } + + // from GrImageContext + GrProxyProvider* proxyProvider() { return fContext->proxyProvider(); } + const GrProxyProvider* proxyProvider() const { return fContext->proxyProvider(); } + + bool abandoned() const { return fContext->abandoned(); } + + /** This is only useful for debug purposes */ + SkDEBUGCODE(GrSingleOwner* singleOwner() const { return fContext->singleOwner(); } ) + +private: + explicit GrImageContextPriv(GrImageContext* context) : fContext(context) {} + GrImageContextPriv(const GrImageContextPriv&); // unimpl + GrImageContextPriv& operator=(const GrImageContextPriv&); // unimpl + + // No taking addresses of this type. + const GrImageContextPriv* operator&() const; + GrImageContextPriv* operator&(); + + GrImageContext* fContext; + + friend class GrImageContext; // to construct/copy this type. +}; + +inline GrImageContextPriv GrImageContext::priv() { return GrImageContextPriv(this); } + +inline const GrImageContextPriv GrImageContext::priv () const { + return GrImageContextPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrImageInfo.h b/gfx/skia/skia/src/gpu/GrImageInfo.h new file mode 100644 index 0000000000..800d3f433f --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrImageInfo.h @@ -0,0 +1,92 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrImageInfo_DEFINED +#define GrImageInfo_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrColorInfo.h" + +class GrImageInfo { +public: + GrImageInfo() = default; + + /* implicit */ GrImageInfo(const SkImageInfo& info) + : fColorInfo(info.colorInfo()), fDimensions(info.dimensions()) {} + + GrImageInfo(GrColorType ct, SkAlphaType at, sk_sp cs, int w, int h) + : fColorInfo(ct, at, std::move(cs)), fDimensions{w,h} {} + + GrImageInfo(GrColorType ct, SkAlphaType at, sk_sp cs, const SkISize& dimensions) + : fColorInfo(ct, at, std::move(cs)), fDimensions(dimensions) {} + + GrImageInfo(const GrImageInfo&) = default; + GrImageInfo(GrImageInfo&&) = default; + GrImageInfo& operator=(const GrImageInfo&) = default; + GrImageInfo& operator=(GrImageInfo&&) = default; + + GrImageInfo makeColorType(GrColorType ct) { + return {ct, this->alphaType(), this->refColorSpace(), this->width(), this->height()}; + } + + GrImageInfo makeAlphaType(SkAlphaType at) { + return {this->colorType(), at, this->refColorSpace(), this->width(), this->height()}; + } + + GrImageInfo makeWH(int width, int height) { + return {this->colorType(), this->alphaType(), this->refColorSpace(), width, height}; + } + + GrColorType colorType() const { return fColorInfo.colorType(); } + + SkAlphaType alphaType() const { return fColorInfo.alphaType(); } + + SkColorSpace* colorSpace() const { return fColorInfo.colorSpace(); } + + sk_sp refColorSpace() const { return fColorInfo.refColorSpace(); } + + SkISize dimensions() const { return fDimensions; } + + int width() const { return fDimensions.width(); } + + int height() const { return fDimensions.height(); } + + size_t bpp() const { return GrColorTypeBytesPerPixel(this->colorType()); } + + size_t minRowBytes() const { return this->bpp() * this->width(); } + + /** + * Place this image rect in a surface of dimensions surfaceWidth x surfaceHeight size offset at + * surfacePt and then clip the pixel rectangle to the bounds of the surface. If the pixel rect + * does not intersect the rectangle or is empty then return false. If clipped, the input + * surfacePt, the width/height of this GrImageInfo, and the data pointer will be modified to + * reflect the clipped rectangle. + */ + template + bool clip(int surfaceWidth, int surfaceHeight, SkIPoint* surfacePt, T** data, size_t rowBytes) { + auto bounds = SkIRect::MakeWH(surfaceWidth, surfaceHeight); + auto rect = SkIRect::MakeXYWH(surfacePt->fX, surfacePt->fY, this->width(), this->height()); + if (!rect.intersect(bounds)) { + return false; + } + *data = SkTAddOffset(*data, (rect.fTop - surfacePt->fY) * rowBytes + + (rect.fLeft - surfacePt->fX) * this->bpp()); + surfacePt->fX = rect.fLeft; + surfacePt->fY = rect.fTop; + fDimensions = rect.size(); + return true; + } + + bool isValid() const { return fColorInfo.isValid() && this->width() > 0 && this->height() > 0; } + +private: + GrColorInfo fColorInfo = {}; + SkISize fDimensions; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrImageTextureMaker.cpp b/gfx/skia/skia/src/gpu/GrImageTextureMaker.cpp new file mode 100644 index 0000000000..13ca46dad3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrImageTextureMaker.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrImageTextureMaker.h" + +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrYUVtoRGBEffect.h" +#include "src/image/SkImage_GpuYUVA.h" +#include "src/image/SkImage_Lazy.h" + +GrImageTextureMaker::GrImageTextureMaker(GrRecordingContext* context, const SkImage* client, + SkImage::CachingHint chint, bool useDecal) + : INHERITED(context, client->width(), client->height(), client->imageInfo().colorInfo(), + useDecal) + , fImage(static_cast(client)) + , fCachingHint(chint) { + SkASSERT(client->isLazyGenerated()); + GrMakeKeyFromImageID(&fOriginalKey, client->uniqueID(), + SkIRect::MakeWH(this->width(), this->height())); +} + +sk_sp GrImageTextureMaker::refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType onlyIfFast) { + return fImage->lockTextureProxy(this->context(), fOriginalKey, fCachingHint, + willBeMipped, onlyIfFast); +} + +void GrImageTextureMaker::makeCopyKey(const CopyParams& stretch, GrUniqueKey* paramsCopyKey) { + if (fOriginalKey.isValid() && SkImage::kAllow_CachingHint == fCachingHint) { + GrUniqueKey cacheKey; + fImage->makeCacheKeyFromOrigKey(fOriginalKey, &cacheKey); + MakeCopyKeyFromOrigKey(cacheKey, stretch, paramsCopyKey); + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////////// + +GrYUVAImageTextureMaker::GrYUVAImageTextureMaker(GrContext* context, const SkImage* client, + bool useDecal) + : INHERITED(context, client->width(), client->height(), client->imageInfo().colorInfo(), + useDecal) + , fImage(static_cast(client)) { + SkASSERT(as_IB(client)->isYUVA()); + GrMakeKeyFromImageID(&fOriginalKey, client->uniqueID(), + SkIRect::MakeWH(this->width(), this->height())); +} + +sk_sp GrYUVAImageTextureMaker::refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType onlyIfFast) { + if (AllowedTexGenType::kCheap == onlyIfFast) { + return nullptr; + } + + if (willBeMipped) { + return fImage->asMippedTextureProxyRef(this->context()); + } else { + return fImage->asTextureProxyRef(this->context()); + } +} + +void GrYUVAImageTextureMaker::makeCopyKey(const CopyParams& stretch, GrUniqueKey* paramsCopyKey) { + // TODO: Do we ever want to disable caching? + if (fOriginalKey.isValid()) { + GrUniqueKey cacheKey; + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(&cacheKey, fOriginalKey, kDomain, 0, "Image"); + MakeCopyKeyFromOrigKey(cacheKey, stretch, paramsCopyKey); + } +} + +std::unique_ptr GrYUVAImageTextureMaker::createFragmentProcessor( + const SkMatrix& textureMatrix, + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) { + + // Check simple cases to see if we need to fall back to flattening the image (or whether it's + // already been flattened.) + if (!filterOrNullForBicubic || this->domainNeedsDecal() || fImage->fRGBProxy) { + return this->INHERITED::createFragmentProcessor(textureMatrix, constraintRect, + filterConstraint, + coordsLimitedToConstraintRect, + filterOrNullForBicubic); + } + + // Check to see if the client has given us pre-mipped textures or we can generate them + // If not, fall back to bilerp. Also fall back to bilerp when a domain is requested + GrSamplerState::Filter filter = *filterOrNullForBicubic; + if (GrSamplerState::Filter::kMipMap == filter && + (filterConstraint == GrTextureProducer::kYes_FilterConstraint || + !fImage->setupMipmapsForPlanes(this->context()))) { + filter = GrSamplerState::Filter::kBilerp; + } + + // Cannot rely on GrTextureProducer's domain infrastructure since we need to calculate domain's + // per plane, which may be different, so respect the filterConstraint without any additional + // analysis. + const SkRect* domain = nullptr; + if (filterConstraint == GrTextureProducer::kYes_FilterConstraint) { + domain = &constraintRect; + } + + auto fp = GrYUVtoRGBEffect::Make(fImage->fProxies, fImage->fYUVAIndices, + fImage->fYUVColorSpace, filter, textureMatrix, domain); + if (fImage->fFromColorSpace) { + fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->fFromColorSpace.get(), + fImage->alphaType(), fImage->colorSpace()); + } + return fp; +} diff --git a/gfx/skia/skia/src/gpu/GrImageTextureMaker.h b/gfx/skia/skia/src/gpu/GrImageTextureMaker.h new file mode 100644 index 0000000000..44e74243e5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrImageTextureMaker.h @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrImageTextureMaker_DEFINED +#define GrImageTextureMaker_DEFINED + +#include "include/core/SkImage.h" +#include "src/gpu/GrTextureMaker.h" + +class SkImage_Lazy; +class SkImage_GpuYUVA; + +/** This class manages the conversion of generator-backed images to GrTextures. If the caching hint + is kAllow the image's ID is used for the cache key. */ +class GrImageTextureMaker : public GrTextureMaker { +public: + GrImageTextureMaker(GrRecordingContext* context, const SkImage* client, + SkImage::CachingHint chint, bool useDecal = false); + +protected: + // TODO: consider overriding this, for the case where the underlying generator might be + // able to efficiently produce a "stretched" texture natively (e.g. picture-backed) + // GrTexture* generateTextureForParams(const CopyParams&) override; + sk_sp refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType onlyIfFast) override; + + void makeCopyKey(const CopyParams& stretch, GrUniqueKey* paramsCopyKey) override; + void didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) override {} + +private: + const SkImage_Lazy* fImage; + GrUniqueKey fOriginalKey; + SkImage::CachingHint fCachingHint; + + typedef GrTextureMaker INHERITED; +}; + +/** This class manages the conversion of generator-backed YUVA images to GrTextures. */ +class GrYUVAImageTextureMaker : public GrTextureMaker { +public: + GrYUVAImageTextureMaker(GrContext* context, const SkImage* client, bool useDecal = false); + + // This could be made more nuanced and compare all of the texture proxy resolutions, but + // it's probably not worth the effort. + bool hasMixedResolutions() const override { return true; } +protected: + // TODO: consider overriding this, for the case where the underlying generator might be + // able to efficiently produce a "stretched" texture natively (e.g. picture-backed) + // GrTexture* generateTextureForParams(const CopyParams&) override; + sk_sp refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType onlyIfFast) override; + + void makeCopyKey(const CopyParams& stretch, GrUniqueKey* paramsCopyKey) override; + void didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) override {} + + std::unique_ptr createFragmentProcessor( + const SkMatrix& textureMatrix, + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) override; + +private: + const SkImage_GpuYUVA* fImage; + GrUniqueKey fOriginalKey; + + typedef GrTextureMaker INHERITED; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/GrLegacyDirectContext.cpp b/gfx/skia/skia/src/gpu/GrLegacyDirectContext.cpp new file mode 100644 index 0000000000..15746e9d71 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrLegacyDirectContext.cpp @@ -0,0 +1,243 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/gpu/GrContext.h" + +#include "include/gpu/GrContextThreadSafeProxy.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" +#include "src/gpu/GrGpu.h" + +#include "src/gpu/effects/GrSkSLFP.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/mock/GrMockGpu.h" +#include "src/gpu/text/GrStrikeCache.h" +#ifdef SK_METAL +#include "src/gpu/mtl/GrMtlTrampoline.h" +#endif +#ifdef SK_VULKAN +#include "src/gpu/vk/GrVkGpu.h" +#endif +#ifdef SK_DAWN +#include "src/gpu/dawn/GrDawnGpu.h" +#endif + +#ifdef SK_DISABLE_REDUCE_OPLIST_SPLITTING +static const bool kDefaultReduceOpsTaskSplitting = false; +#else +static const bool kDefaultReduceOpsTaskSplitting = false; +#endif + +class GrLegacyDirectContext : public GrContext { +public: + GrLegacyDirectContext(GrBackendApi backend, const GrContextOptions& options) + : INHERITED(backend, options) + , fAtlasManager(nullptr) { + } + + ~GrLegacyDirectContext() override { + // this if-test protects against the case where the context is being destroyed + // before having been fully created + if (this->priv().getGpu()) { + this->flush(); + } + + delete fAtlasManager; + } + + void abandonContext() override { + INHERITED::abandonContext(); + fAtlasManager->freeAll(); + } + + void releaseResourcesAndAbandonContext() override { + INHERITED::releaseResourcesAndAbandonContext(); + fAtlasManager->freeAll(); + } + + void freeGpuResources() override { + this->flush(); + fAtlasManager->freeAll(); + + INHERITED::freeGpuResources(); + } + +protected: + bool init(sk_sp caps, sk_sp FPFactoryCache) override { + SkASSERT(caps && !FPFactoryCache); + SkASSERT(!fThreadSafeProxy); + + FPFactoryCache.reset(new GrSkSLFPFactoryCache()); + fThreadSafeProxy = GrContextThreadSafeProxyPriv::Make(this->backend(), + this->options(), + this->contextID(), + caps, FPFactoryCache); + + if (!INHERITED::init(std::move(caps), std::move(FPFactoryCache))) { + return false; + } + + bool reduceOpsTaskSplitting = kDefaultReduceOpsTaskSplitting; + if (GrContextOptions::Enable::kNo == this->options().fReduceOpsTaskSplitting) { + reduceOpsTaskSplitting = false; + } else if (GrContextOptions::Enable::kYes == this->options().fReduceOpsTaskSplitting) { + reduceOpsTaskSplitting = true; + } + + this->setupDrawingManager(true, reduceOpsTaskSplitting); + + SkASSERT(this->caps()); + + GrDrawOpAtlas::AllowMultitexturing allowMultitexturing; + if (GrContextOptions::Enable::kNo == this->options().fAllowMultipleGlyphCacheTextures || + // multitexturing supported only if range can represent the index + texcoords fully + !(this->caps()->shaderCaps()->floatIs32Bits() || + this->caps()->shaderCaps()->integerSupport())) { + allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kNo; + } else { + allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kYes; + } + + GrStrikeCache* glyphCache = this->priv().getGrStrikeCache(); + GrProxyProvider* proxyProvider = this->priv().proxyProvider(); + + fAtlasManager = new GrAtlasManager(proxyProvider, glyphCache, + this->options().fGlyphCacheTextureMaximumBytes, + allowMultitexturing); + this->priv().addOnFlushCallbackObject(fAtlasManager); + + return true; + } + + GrAtlasManager* onGetAtlasManager() override { return fAtlasManager; } + +private: + GrAtlasManager* fAtlasManager; + + typedef GrContext INHERITED; +}; + +sk_sp GrContext::MakeGL(sk_sp interface) { + GrContextOptions defaultOptions; + return MakeGL(std::move(interface), defaultOptions); +} + +sk_sp GrContext::MakeGL(const GrContextOptions& options) { + return MakeGL(nullptr, options); +} + +sk_sp GrContext::MakeGL() { + GrContextOptions defaultOptions; + return MakeGL(nullptr, defaultOptions); +} + +sk_sp GrContext::MakeGL(sk_sp interface, + const GrContextOptions& options) { + sk_sp context(new GrLegacyDirectContext(GrBackendApi::kOpenGL, options)); + + context->fGpu = GrGLGpu::Make(std::move(interface), options, context.get()); + if (!context->fGpu) { + return nullptr; + } + + if (!context->init(context->fGpu->refCaps(), nullptr)) { + return nullptr; + } + return context; +} + +sk_sp GrContext::MakeMock(const GrMockOptions* mockOptions) { + GrContextOptions defaultOptions; + return MakeMock(mockOptions, defaultOptions); +} + +sk_sp GrContext::MakeMock(const GrMockOptions* mockOptions, + const GrContextOptions& options) { + sk_sp context(new GrLegacyDirectContext(GrBackendApi::kMock, options)); + + context->fGpu = GrMockGpu::Make(mockOptions, options, context.get()); + if (!context->fGpu) { + return nullptr; + } + + if (!context->init(context->fGpu->refCaps(), nullptr)) { + return nullptr; + } + return context; +} + +sk_sp GrContext::MakeVulkan(const GrVkBackendContext& backendContext) { +#ifdef SK_VULKAN + GrContextOptions defaultOptions; + return MakeVulkan(backendContext, defaultOptions); +#else + return nullptr; +#endif +} + +sk_sp GrContext::MakeVulkan(const GrVkBackendContext& backendContext, + const GrContextOptions& options) { +#ifdef SK_VULKAN + GrContextOptions defaultOptions; + sk_sp context(new GrLegacyDirectContext(GrBackendApi::kVulkan, options)); + + context->fGpu = GrVkGpu::Make(backendContext, options, context.get()); + if (!context->fGpu) { + return nullptr; + } + + if (!context->init(context->fGpu->refCaps(), nullptr)) { + return nullptr; + } + return context; +#else + return nullptr; +#endif +} + +#ifdef SK_METAL +sk_sp GrContext::MakeMetal(void* device, void* queue) { + GrContextOptions defaultOptions; + return MakeMetal(device, queue, defaultOptions); +} + +sk_sp GrContext::MakeMetal(void* device, void* queue, const GrContextOptions& options) { + sk_sp context(new GrLegacyDirectContext(GrBackendApi::kMetal, options)); + + context->fGpu = GrMtlTrampoline::MakeGpu(context.get(), options, device, queue); + if (!context->fGpu) { + return nullptr; + } + + if (!context->init(context->fGpu->refCaps(), nullptr)) { + return nullptr; + } + return context; +} +#endif + +#ifdef SK_DAWN +sk_sp GrContext::MakeDawn(const dawn::Device& device) { + GrContextOptions defaultOptions; + return MakeDawn(device, defaultOptions); +} + +sk_sp GrContext::MakeDawn(const dawn::Device& device, const GrContextOptions& options) { + sk_sp context(new GrLegacyDirectContext(GrBackendApi::kDawn, options)); + + context->fGpu = GrDawnGpu::Make(device, options, context.get()); + if (!context->fGpu) { + return nullptr; + } + + if (!context->init(context->fGpu->refCaps(), nullptr)) { + return nullptr; + } + return context; +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrMemoryPool.cpp b/gfx/skia/skia/src/gpu/GrMemoryPool.cpp new file mode 100644 index 0000000000..8a92a5e384 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrMemoryPool.cpp @@ -0,0 +1,217 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/ops/GrOp.h" +#ifdef SK_DEBUG + #include +#endif + +#ifdef SK_DEBUG + #define VALIDATE this->validate() +#else + #define VALIDATE +#endif + +void GrOpMemoryPool::release(std::unique_ptr op) { + GrOp* tmp = op.release(); + SkASSERT(tmp); + tmp->~GrOp(); + fMemoryPool.release(tmp); +} + +constexpr size_t GrMemoryPool::kSmallestMinAllocSize; + +GrMemoryPool::GrMemoryPool(size_t preallocSize, size_t minAllocSize) { + SkDEBUGCODE(fAllocationCnt = 0); + SkDEBUGCODE(fAllocBlockCnt = 0); + + minAllocSize = SkTMax(GrSizeAlignUp(minAllocSize, kAlignment), kSmallestMinAllocSize); + preallocSize = SkTMax(GrSizeAlignUp(preallocSize, kAlignment), minAllocSize); + + fMinAllocSize = minAllocSize; + fSize = 0; + + fHead = CreateBlock(preallocSize); + fTail = fHead; + fHead->fNext = nullptr; + fHead->fPrev = nullptr; + VALIDATE; +}; + +GrMemoryPool::~GrMemoryPool() { + VALIDATE; +#ifdef SK_DEBUG + int i = 0; + int n = fAllocatedIDs.count(); + fAllocatedIDs.foreach([&i, n] (int32_t id) { + if (++i == 1) { + SkDebugf("Leaked IDs (in no particular order): %d", id); + } else if (i < 11) { + SkDebugf(", %d%s", id, (n == i ? "\n" : "")); + } else if (i == 11) { + SkDebugf(", ...\n"); + } + }); +#endif + SkASSERT(0 == fAllocationCnt); + SkASSERT(fHead == fTail); + SkASSERT(0 == fHead->fLiveCount); + DeleteBlock(fHead); +}; + +void* GrMemoryPool::allocate(size_t size) { + VALIDATE; + size += kPerAllocPad; + size = GrSizeAlignUp(size, kAlignment); + if (fTail->fFreeSize < size) { + size_t blockSize = size + kHeaderSize; + blockSize = SkTMax(blockSize, fMinAllocSize); + BlockHeader* block = CreateBlock(blockSize); + + block->fPrev = fTail; + block->fNext = nullptr; + SkASSERT(nullptr == fTail->fNext); + fTail->fNext = block; + fTail = block; + fSize += block->fSize; + SkDEBUGCODE(++fAllocBlockCnt); + } + SkASSERT(kAssignedMarker == fTail->fBlockSentinal); + SkASSERT(fTail->fFreeSize >= size); + intptr_t ptr = fTail->fCurrPtr; + // We stash a pointer to the block header, just before the allocated space, + // so that we can decrement the live count on delete in constant time. + AllocHeader* allocData = reinterpret_cast(ptr); + SkDEBUGCODE(allocData->fSentinal = kAssignedMarker); + SkDEBUGCODE(allocData->fID = []{ + static std::atomic nextID{1}; + return nextID++; + }()); + // You can set a breakpoint here when a leaked ID is allocated to see the stack frame. + SkDEBUGCODE(fAllocatedIDs.add(allocData->fID)); + allocData->fHeader = fTail; + ptr += kPerAllocPad; + fTail->fPrevPtr = fTail->fCurrPtr; + fTail->fCurrPtr += size; + fTail->fFreeSize -= size; + fTail->fLiveCount += 1; + SkDEBUGCODE(++fAllocationCnt); + VALIDATE; + return reinterpret_cast(ptr); +} + +void GrMemoryPool::release(void* p) { + VALIDATE; + intptr_t ptr = reinterpret_cast(p) - kPerAllocPad; + AllocHeader* allocData = reinterpret_cast(ptr); + SkASSERT(kAssignedMarker == allocData->fSentinal); + SkDEBUGCODE(allocData->fSentinal = kFreedMarker); + SkDEBUGCODE(fAllocatedIDs.remove(allocData->fID)); + BlockHeader* block = allocData->fHeader; + SkASSERT(kAssignedMarker == block->fBlockSentinal); + if (1 == block->fLiveCount) { + // the head block is special, it is reset rather than deleted + if (fHead == block) { + fHead->fCurrPtr = reinterpret_cast(fHead) + kHeaderSize; + fHead->fLiveCount = 0; + fHead->fFreeSize = fHead->fSize - kHeaderSize; + } else { + BlockHeader* prev = block->fPrev; + BlockHeader* next = block->fNext; + SkASSERT(prev); + prev->fNext = next; + if (next) { + next->fPrev = prev; + } else { + SkASSERT(fTail == block); + fTail = prev; + } + fSize -= block->fSize; + DeleteBlock(block); + SkDEBUGCODE(fAllocBlockCnt--); + } + } else { + --block->fLiveCount; + // Trivial reclaim: if we're releasing the most recent allocation, reuse it + if (block->fPrevPtr == ptr) { + block->fFreeSize += (block->fCurrPtr - block->fPrevPtr); + block->fCurrPtr = block->fPrevPtr; + } + } + SkDEBUGCODE(--fAllocationCnt); + VALIDATE; +} + +GrMemoryPool::BlockHeader* GrMemoryPool::CreateBlock(size_t blockSize) { + blockSize = SkTMax(blockSize, kHeaderSize); + BlockHeader* block = + reinterpret_cast(sk_malloc_throw(blockSize)); + // we assume malloc gives us aligned memory + SkASSERT(!(reinterpret_cast(block) % kAlignment)); + SkDEBUGCODE(block->fBlockSentinal = kAssignedMarker); + block->fLiveCount = 0; + block->fFreeSize = blockSize - kHeaderSize; + block->fCurrPtr = reinterpret_cast(block) + kHeaderSize; + block->fPrevPtr = 0; // gcc warns on assigning nullptr to an intptr_t. + block->fSize = blockSize; + return block; +} + +void GrMemoryPool::DeleteBlock(BlockHeader* block) { + SkASSERT(kAssignedMarker == block->fBlockSentinal); + SkDEBUGCODE(block->fBlockSentinal = kFreedMarker); // FWIW + sk_free(block); +} + +void GrMemoryPool::validate() { +#ifdef SK_DEBUG + BlockHeader* block = fHead; + BlockHeader* prev = nullptr; + SkASSERT(block); + int allocCount = 0; + do { + SkASSERT(kAssignedMarker == block->fBlockSentinal); + allocCount += block->fLiveCount; + SkASSERT(prev == block->fPrev); + if (prev) { + SkASSERT(prev->fNext == block); + } + + intptr_t b = reinterpret_cast(block); + size_t ptrOffset = block->fCurrPtr - b; + size_t totalSize = ptrOffset + block->fFreeSize; + intptr_t userStart = b + kHeaderSize; + + SkASSERT(!(b % kAlignment)); + SkASSERT(!(totalSize % kAlignment)); + SkASSERT(!(block->fCurrPtr % kAlignment)); + if (fHead != block) { + SkASSERT(block->fLiveCount); + SkASSERT(totalSize >= fMinAllocSize); + } else { + SkASSERT(totalSize == block->fSize); + } + if (!block->fLiveCount) { + SkASSERT(ptrOffset == kHeaderSize); + SkASSERT(userStart == block->fCurrPtr); + } else { + AllocHeader* allocData = reinterpret_cast(userStart); + SkASSERT(allocData->fSentinal == kAssignedMarker || + allocData->fSentinal == kFreedMarker); + SkASSERT(block == allocData->fHeader); + } + + prev = block; + } while ((block = block->fNext)); + SkASSERT(allocCount == fAllocationCnt); + SkASSERT(fAllocationCnt == fAllocatedIDs.count()); + SkASSERT(prev == fTail); + SkASSERT(fAllocBlockCnt != 0 || fSize == 0); +#endif +} diff --git a/gfx/skia/skia/src/gpu/GrMemoryPool.h b/gfx/skia/skia/src/gpu/GrMemoryPool.h new file mode 100644 index 0000000000..a4428305c0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrMemoryPool.h @@ -0,0 +1,155 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMemoryPool_DEFINED +#define GrMemoryPool_DEFINED + +#include "include/gpu/GrTypes.h" + +#include "include/core/SkRefCnt.h" + +#ifdef SK_DEBUG +#include "include/private/SkTHash.h" +#endif + +/** + * Allocates memory in blocks and parcels out space in the blocks for allocation + * requests. It is optimized for allocate / release speed over memory + * efficiency. The interface is designed to be used to implement operator new + * and delete overrides. All allocations are expected to be released before the + * pool's destructor is called. Allocations will be 8-byte aligned. + */ +class GrMemoryPool { +public: + /** + * Prealloc size is the amount of space to allocate at pool creation + * time and keep around until pool destruction. The min alloc size is + * the smallest allowed size of additional allocations. Both sizes are + * adjusted to ensure that: + * 1. they are are 8-byte aligned + * 2. minAllocSize >= kSmallestMinAllocSize + * 3. preallocSize >= minAllocSize + * + * Both sizes is what the pool will end up allocating from the system, and + * portions of the allocated memory is used for internal bookkeeping. + */ + GrMemoryPool(size_t preallocSize, size_t minAllocSize); + + ~GrMemoryPool(); + + /** + * Allocates memory. The memory must be freed with release(). + */ + void* allocate(size_t size); + + /** + * p must have been returned by allocate() + */ + void release(void* p); + + /** + * Returns true if there are no unreleased allocations. + */ + bool isEmpty() const { return fTail == fHead && !fHead->fLiveCount; } + + /** + * Returns the total allocated size of the GrMemoryPool minus any preallocated amount + */ + size_t size() const { return fSize; } + + /** + * Returns the preallocated size of the GrMemoryPool + */ + size_t preallocSize() const { return fHead->fSize; } + + /** + * Minimum value of minAllocSize constructor argument. + */ + constexpr static size_t kSmallestMinAllocSize = 1 << 10; + +private: + struct BlockHeader; + + static BlockHeader* CreateBlock(size_t size); + + static void DeleteBlock(BlockHeader* block); + + void validate(); + + struct BlockHeader { +#ifdef SK_DEBUG + uint32_t fBlockSentinal; ///< known value to check for bad back pointers to blocks +#endif + BlockHeader* fNext; ///< doubly-linked list of blocks. + BlockHeader* fPrev; + int fLiveCount; ///< number of outstanding allocations in the + ///< block. + intptr_t fCurrPtr; ///< ptr to the start of blocks free space. + intptr_t fPrevPtr; ///< ptr to the last allocation made + size_t fFreeSize; ///< amount of free space left in the block. + size_t fSize; ///< total allocated size of the block + }; + + static const uint32_t kAssignedMarker = 0xCDCDCDCD; + static const uint32_t kFreedMarker = 0xEFEFEFEF; + + struct AllocHeader { +#ifdef SK_DEBUG + uint32_t fSentinal; ///< known value to check for memory stomping (e.g., (CD)*) + int32_t fID; ///< ID that can be used to track down leaks by clients. +#endif + BlockHeader* fHeader; ///< pointer back to the block header in which an alloc resides + }; + + size_t fSize; + size_t fMinAllocSize; + BlockHeader* fHead; + BlockHeader* fTail; +#ifdef SK_DEBUG + int fAllocationCnt; + int fAllocBlockCnt; + SkTHashSet fAllocatedIDs; +#endif + +protected: + enum { + // We assume this alignment is good enough for everybody. + kAlignment = 8, + kHeaderSize = GrSizeAlignUp(sizeof(BlockHeader), kAlignment), + kPerAllocPad = GrSizeAlignUp(sizeof(AllocHeader), kAlignment), + }; +}; + +class GrOp; + +// DDL TODO: for the DLL use case this could probably be the non-intrinsic-based style of +// ref counting +class GrOpMemoryPool : public SkRefCnt { +public: + GrOpMemoryPool(size_t preallocSize, size_t minAllocSize) + : fMemoryPool(preallocSize, minAllocSize) { + } + + template + std::unique_ptr allocate(OpArgs&&... opArgs) { + char* mem = (char*) fMemoryPool.allocate(sizeof(Op)); + return std::unique_ptr(new (mem) Op(std::forward(opArgs)...)); + } + + void* allocate(size_t size) { + return fMemoryPool.allocate(size); + } + + void release(std::unique_ptr op); + + bool isEmpty() const { return fMemoryPool.isEmpty(); } + +private: + GrMemoryPool fMemoryPool; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrMesh.h b/gfx/skia/skia/src/gpu/GrMesh.h new file mode 100644 index 0000000000..08daf0e32b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrMesh.h @@ -0,0 +1,270 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMesh_DEFINED +#define GrMesh_DEFINED + +#include "src/gpu/GrBuffer.h" +#include "src/gpu/GrGpuBuffer.h" + +class GrPrimitiveProcessor; + +/** + * Used to communicate index and vertex buffers, counts, and offsets for a draw from GrOp to + * GrGpu. It also holds the primitive type for the draw. TODO: Consider moving ownership of this + * and draw-issuing responsibility to GrPrimitiveProcessor. The rest of the vertex info lives there + * already (stride, attribute mappings). + */ +class GrMesh { +public: + GrMesh(GrPrimitiveType primitiveType = GrPrimitiveType::kTriangles) + : fPrimitiveType(primitiveType), fBaseVertex(0) { + SkDEBUGCODE(fNonIndexNonInstanceData.fVertexCount = -1;) + } + + void setPrimitiveType(GrPrimitiveType type) { fPrimitiveType = type; } + GrPrimitiveType primitiveType() const { return fPrimitiveType; } + + bool isIndexed() const { return SkToBool(fIndexBuffer.get()); } + GrPrimitiveRestart primitiveRestart() const { + return GrPrimitiveRestart(fFlags & Flags::kUsePrimitiveRestart); + } + bool isInstanced() const { return fFlags & Flags::kIsInstanced; } + bool hasInstanceData() const { return SkToBool(fInstanceBuffer.get()); } + bool hasVertexData() const { return SkToBool(fVertexBuffer.get()); } + + void setNonIndexedNonInstanced(int vertexCount); + + void setIndexed(sk_sp indexBuffer, int indexCount, int baseIndex, + uint16_t minIndexValue, uint16_t maxIndexValue, GrPrimitiveRestart); + void setIndexedPatterned(sk_sp indexBuffer, int indexCount, int vertexCount, + int patternRepeatCount, int maxPatternRepetitionsInIndexBuffer); + + void setInstanced(sk_sp instanceBuffer, int instanceCount, int baseInstance, + int vertexCount); + void setIndexedInstanced(sk_sp indexBuffer, int indexCount, + sk_sp instanceBuffer, int instanceCount, + int baseInstance, GrPrimitiveRestart); + + void setVertexData(sk_sp vertexBuffer, int baseVertex = 0); + + class SendToGpuImpl { + public: + virtual void sendMeshToGpu(GrPrimitiveType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex) = 0; + + virtual void sendIndexedMeshToGpu(GrPrimitiveType, const GrBuffer* indexBuffer, + int indexCount, int baseIndex, uint16_t minIndexValue, + uint16_t maxIndexValue, const GrBuffer* vertexBuffer, + int baseVertex, GrPrimitiveRestart) = 0; + + virtual void sendInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* vertexBuffer, + int vertexCount, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance) = 0; + + virtual void sendIndexedInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* indexBuffer, + int indexCount, int baseIndex, + const GrBuffer* vertexBuffer, int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, int baseInstance, + GrPrimitiveRestart) = 0; + + virtual ~SendToGpuImpl() {} + }; + + void sendToGpu(SendToGpuImpl*) const; + +private: + enum class Flags { + kNone = 0, + kUsePrimitiveRestart = 1 << 0, + kIsInstanced = 1 << 1, + }; + + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags); + GR_STATIC_ASSERT(Flags(GrPrimitiveRestart::kNo) == Flags::kNone); + GR_STATIC_ASSERT(Flags(GrPrimitiveRestart::kYes) == Flags::kUsePrimitiveRestart); + + GrPrimitiveType fPrimitiveType; + sk_sp fIndexBuffer; + sk_sp fInstanceBuffer; + sk_sp fVertexBuffer; + int fBaseVertex; + Flags fFlags; + + union { + struct { // When fIndexBuffer == nullptr and isInstanced() == false. + int fVertexCount; + } fNonIndexNonInstanceData; + + struct { // When fIndexBuffer != nullptr and isInstanced() == false. + struct { + int fIndexCount; + int fPatternRepeatCount; + } fIndexData; + + union { + struct { // When fPatternRepeatCount == 0. + int fBaseIndex; + uint16_t fMinIndexValue; + uint16_t fMaxIndexValue; + } fNonPatternIndexData; + + struct { // When fPatternRepeatCount != 0. + int fVertexCount; + int fMaxPatternRepetitionsInIndexBuffer; + } fPatternData; + }; + }; + + struct { // When isInstanced() != false. + struct { + int fInstanceCount; + int fBaseInstance; + } fInstanceData; + + union { // When fIndexBuffer == nullptr. + struct { + int fVertexCount; + } fInstanceNonIndexData; + + struct { // When fIndexBuffer != nullptr. + int fIndexCount; + } fInstanceIndexData; + }; + }; + }; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrMesh::Flags); + +inline void GrMesh::setNonIndexedNonInstanced(int vertexCount) { + fIndexBuffer.reset(); + fInstanceBuffer.reset(); + fNonIndexNonInstanceData.fVertexCount = vertexCount; + fFlags = Flags::kNone; +} + +inline void GrMesh::setIndexed(sk_sp indexBuffer, int indexCount, int baseIndex, + uint16_t minIndexValue, uint16_t maxIndexValue, + GrPrimitiveRestart primitiveRestart) { + SkASSERT(indexBuffer); + SkASSERT(indexCount >= 1); + SkASSERT(baseIndex >= 0); + SkASSERT(maxIndexValue >= minIndexValue); + fIndexBuffer = std::move(indexBuffer); + fInstanceBuffer.reset(); + fIndexData.fIndexCount = indexCount; + fIndexData.fPatternRepeatCount = 0; + fNonPatternIndexData.fBaseIndex = baseIndex; + fNonPatternIndexData.fMinIndexValue = minIndexValue; + fNonPatternIndexData.fMaxIndexValue = maxIndexValue; + fFlags = Flags(primitiveRestart); +} + +inline void GrMesh::setIndexedPatterned(sk_sp indexBuffer, int indexCount, + int vertexCount, int patternRepeatCount, + int maxPatternRepetitionsInIndexBuffer) { + SkASSERT(indexBuffer); + SkASSERT(indexCount >= 1); + SkASSERT(vertexCount >= 1); + SkASSERT(patternRepeatCount >= 1); + SkASSERT(maxPatternRepetitionsInIndexBuffer >= 1); + fIndexBuffer = std::move(indexBuffer); + fInstanceBuffer.reset(); + fIndexData.fIndexCount = indexCount; + fIndexData.fPatternRepeatCount = patternRepeatCount; + fPatternData.fVertexCount = vertexCount; + fPatternData.fMaxPatternRepetitionsInIndexBuffer = maxPatternRepetitionsInIndexBuffer; + fFlags = Flags::kNone; +} + +inline void GrMesh::setInstanced(sk_sp instanceBuffer, int instanceCount, + int baseInstance, int vertexCount) { + SkASSERT(instanceCount >= 1); + SkASSERT(baseInstance >= 0); + fIndexBuffer.reset(); + fInstanceBuffer = std::move(instanceBuffer); + fInstanceData.fInstanceCount = instanceCount; + fInstanceData.fBaseInstance = baseInstance; + fInstanceNonIndexData.fVertexCount = vertexCount; + fFlags = Flags::kIsInstanced; +} + +inline void GrMesh::setIndexedInstanced(sk_sp indexBuffer, int indexCount, + sk_sp instanceBuffer, int instanceCount, + int baseInstance, GrPrimitiveRestart primitiveRestart) { + SkASSERT(indexBuffer); + SkASSERT(indexCount >= 1); + SkASSERT(instanceCount >= 1); + SkASSERT(baseInstance >= 0); + fIndexBuffer = std::move(indexBuffer); + fInstanceBuffer = std::move(instanceBuffer); + fInstanceData.fInstanceCount = instanceCount; + fInstanceData.fBaseInstance = baseInstance; + fInstanceIndexData.fIndexCount = indexCount; + fFlags = Flags::kIsInstanced | Flags(primitiveRestart); +} + +inline void GrMesh::setVertexData(sk_sp vertexBuffer, int baseVertex) { + SkASSERT(baseVertex >= 0); + fVertexBuffer = std::move(vertexBuffer); + fBaseVertex = baseVertex; +} + +inline void GrMesh::sendToGpu(SendToGpuImpl* impl) const { + if (this->isInstanced()) { + if (!this->isIndexed()) { + impl->sendInstancedMeshToGpu(fPrimitiveType, fVertexBuffer.get(), + fInstanceNonIndexData.fVertexCount, fBaseVertex, + fInstanceBuffer.get(), fInstanceData.fInstanceCount, + fInstanceData.fBaseInstance); + } else { + impl->sendIndexedInstancedMeshToGpu( + fPrimitiveType, fIndexBuffer.get(), fInstanceIndexData.fIndexCount, 0, + fVertexBuffer.get(), fBaseVertex, fInstanceBuffer.get(), + fInstanceData.fInstanceCount, fInstanceData.fBaseInstance, + this->primitiveRestart()); + } + return; + } + + if (!this->isIndexed()) { + SkASSERT(fNonIndexNonInstanceData.fVertexCount > 0); + impl->sendMeshToGpu(fPrimitiveType, fVertexBuffer.get(), + fNonIndexNonInstanceData.fVertexCount, fBaseVertex); + return; + } + + if (0 == fIndexData.fPatternRepeatCount) { + impl->sendIndexedMeshToGpu( + fPrimitiveType, fIndexBuffer.get(), fIndexData.fIndexCount, + fNonPatternIndexData.fBaseIndex, fNonPatternIndexData.fMinIndexValue, + fNonPatternIndexData.fMaxIndexValue, fVertexBuffer.get(), fBaseVertex, + this->primitiveRestart()); + return; + } + + SkASSERT(fIndexData.fPatternRepeatCount > 0); + int baseRepetition = 0; + do { + int repeatCount = SkTMin(fPatternData.fMaxPatternRepetitionsInIndexBuffer, + fIndexData.fPatternRepeatCount - baseRepetition); + // A patterned index buffer must contain indices in the range [0..vertexCount]. + int minIndexValue = 0; + int maxIndexValue = fPatternData.fVertexCount * repeatCount - 1; + SkASSERT(!(fFlags & Flags::kUsePrimitiveRestart)); + impl->sendIndexedMeshToGpu( + fPrimitiveType, fIndexBuffer.get(), fIndexData.fIndexCount * repeatCount, 0, + minIndexValue, maxIndexValue, fVertexBuffer.get(), + fBaseVertex + fPatternData.fVertexCount * baseRepetition, GrPrimitiveRestart::kNo); + baseRepetition += repeatCount; + } while (baseRepetition < fIndexData.fPatternRepeatCount); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrNativeRect.h b/gfx/skia/skia/src/gpu/GrNativeRect.h new file mode 100644 index 0000000000..24b5b25011 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrNativeRect.h @@ -0,0 +1,98 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#ifndef GrNativeRect_DEFINED +#define GrNativeRect_DEFINED + +#include "include/core/SkRect.h" +#include "include/gpu/GrTypes.h" + +/** + * Helper struct for dealing with bottom-up surface origins (bottom-up instead of top-down). + */ +struct GrNativeRect { + int fX; + int fY; + int fWidth; + int fHeight; + + static GrNativeRect MakeRelativeTo(GrSurfaceOrigin org, int rtHeight, const SkIRect& devRect) { + GrNativeRect nativeRect; + nativeRect.setRelativeTo(org, rtHeight, devRect); + return nativeRect; + } + + static GrNativeRect MakeRelativeTo(GrSurfaceOrigin origin, int surfaceHeight, int leftOffset, + int topOffset, int width, int height) { + GrNativeRect nativeRect; + nativeRect.setRelativeTo(origin, surfaceHeight, leftOffset, topOffset, width, height); + return nativeRect; + } + + /** + * cast-safe way to treat the rect as an array of (4) ints. + */ + const int* asInts() const { + return &fX; + + GR_STATIC_ASSERT(0 == offsetof(GrNativeRect, fX)); + GR_STATIC_ASSERT(4 == offsetof(GrNativeRect, fY)); + GR_STATIC_ASSERT(8 == offsetof(GrNativeRect, fWidth)); + GR_STATIC_ASSERT(12 == offsetof(GrNativeRect, fHeight)); + GR_STATIC_ASSERT(16 == sizeof(GrNativeRect)); // For an array of GrNativeRect. + } + int* asInts() { return &fX; } + + SkIRect asSkIRect() const { return SkIRect::MakeXYWH(fX, fY, fWidth, fHeight); } + + // sometimes we have a SkIRect from the client that we + // want to simultaneously make relative to GL's viewport + // and (optionally) convert from top-down to bottom-up. + // The GL's viewport will always be the full size of the + // current render target so we just pass in the rtHeight + // here. + void setRelativeTo(GrSurfaceOrigin org, int rtHeight, const SkIRect& devRect) { + this->setRelativeTo(org, rtHeight, devRect.x(), devRect.y(), devRect.width(), + devRect.height()); + } + + void setRelativeTo(GrSurfaceOrigin origin, int surfaceHeight, int leftOffset, int topOffset, + int width, int height) { + fX = leftOffset; + fWidth = width; + if (kBottomLeft_GrSurfaceOrigin == origin) { + fY = surfaceHeight - topOffset - height; + } else { + fY = topOffset; + } + fHeight = height; + + SkASSERT(fWidth >= 0); + SkASSERT(fHeight >= 0); + } + + bool contains(int width, int height) const { + return fX <= 0 && + fY <= 0 && + fX + fWidth >= width && + fY + fHeight >= height; + } + + void invalidate() {fX = fWidth = fY = fHeight = -1;} + bool isInvalid() const { return fX == -1 && fWidth == -1 && fY == -1 + && fHeight == -1; } + + bool operator ==(const GrNativeRect& that) const { + return 0 == memcmp(this, &that, sizeof(GrNativeRect)); + } + + bool operator !=(const GrNativeRect& that) const {return !(*this == that);} +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrNonAtomicRef.h b/gfx/skia/skia/src/gpu/GrNonAtomicRef.h new file mode 100644 index 0000000000..db7eea2c9a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrNonAtomicRef.h @@ -0,0 +1,63 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrNonAtomicRef_DEFINED +#define GrNonAtomicRef_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTArray.h" + +/** + * A simple non-atomic ref used in the GrBackendApi when we don't want to pay for the overhead of a + * threadsafe ref counted object + */ +template class GrNonAtomicRef : public SkNoncopyable { +public: + GrNonAtomicRef() : fRefCnt(1) {} + +#ifdef SK_DEBUG + ~GrNonAtomicRef() { + // fRefCnt can be one when a subclass is created statically + SkASSERT((0 == fRefCnt || 1 == fRefCnt)); + // Set to invalid values. + fRefCnt = -10; + } +#endif + + bool unique() const { return 1 == fRefCnt; } + + // We allow this getter because this type is not thread-safe, meaning only one thread should + // have ownership and be manipulating the ref count or querying this. + int refCnt() const { return fRefCnt; } + + void ref() const { + // Once the ref cnt reaches zero it should never be ref'ed again. + SkASSERT(fRefCnt > 0); + ++fRefCnt; + } + + void unref() const { + SkASSERT(fRefCnt > 0); + --fRefCnt; + if (0 == fRefCnt) { + GrTDeleteNonAtomicRef(static_cast(this)); + return; + } + } + +private: + mutable int32_t fRefCnt; + + typedef SkNoncopyable INHERITED; +}; + +template inline void GrTDeleteNonAtomicRef(const T* ref) { + delete ref; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.cpp b/gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.cpp new file mode 100644 index 0000000000..2cf4af869c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrOnFlushResourceProvider.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrTextureResolveRenderTask.h" + +std::unique_ptr GrOnFlushResourceProvider::makeRenderTargetContext( + sk_sp proxy, GrColorType colorType, sk_sp colorSpace, + const SkSurfaceProps* props) { + // Since this is at flush time and these won't be allocated for us by the GrResourceAllocator + // we have to manually ensure it is allocated here. + if (!this->instatiateProxy(proxy.get())) { + return nullptr; + } + + auto renderTargetContext = fDrawingMgr->makeRenderTargetContext( + std::move(proxy), colorType, std::move(colorSpace), props, false); + + if (!renderTargetContext) { + return nullptr; + } + + renderTargetContext->discard(); + + // FIXME: http://skbug.com/9357: This breaks if the renderTargetContext splits its opsTask. + fDrawingMgr->fOnFlushRenderTasks.push_back(sk_ref_sp(renderTargetContext->getOpsTask())); + + return renderTargetContext; +} + +void GrOnFlushResourceProvider::addTextureResolveTask(sk_sp textureProxy, + GrSurfaceProxy::ResolveFlags resolveFlags) { + // Since we are bypassing normal DAG operation, we need to ensure the textureProxy's last render + // task gets closed before making a texture resolve task. makeClosed is what will mark msaa and + // mipmaps dirty. + if (GrRenderTask* renderTask = textureProxy->getLastRenderTask()) { + renderTask->makeClosed(*this->caps()); + } + auto task = static_cast(fDrawingMgr->fOnFlushRenderTasks.push_back( + sk_make_sp()).get()); + task->addProxy(textureProxy, resolveFlags, *this->caps()); + task->makeClosed(*this->caps()); +} + +bool GrOnFlushResourceProvider::assignUniqueKeyToProxy(const GrUniqueKey& key, + GrTextureProxy* proxy) { + auto proxyProvider = fDrawingMgr->getContext()->priv().proxyProvider(); + return proxyProvider->assignUniqueKeyToProxy(key, proxy); +} + +void GrOnFlushResourceProvider::removeUniqueKeyFromProxy(GrTextureProxy* proxy) { + auto proxyProvider = fDrawingMgr->getContext()->priv().proxyProvider(); + proxyProvider->removeUniqueKeyFromProxy(proxy); +} + +void GrOnFlushResourceProvider::processInvalidUniqueKey(const GrUniqueKey& key) { + auto proxyProvider = fDrawingMgr->getContext()->priv().proxyProvider(); + proxyProvider->processInvalidUniqueKey(key, nullptr, + GrProxyProvider::InvalidateGPUResource::kYes); +} + +sk_sp GrOnFlushResourceProvider::findOrCreateProxyByUniqueKey( + const GrUniqueKey& key, + GrColorType colorType, + GrSurfaceOrigin origin, + UseAllocator useAllocator) { + auto proxyProvider = fDrawingMgr->getContext()->priv().proxyProvider(); + return proxyProvider->findOrCreateProxyByUniqueKey(key, colorType, origin, useAllocator); +} + +bool GrOnFlushResourceProvider::instatiateProxy(GrSurfaceProxy* proxy) { + SkASSERT(proxy->canSkipResourceAllocator()); + + // TODO: this class should probably just get a GrDirectContext + auto direct = fDrawingMgr->getContext()->priv().asDirectContext(); + if (!direct) { + return false; + } + + auto resourceProvider = direct->priv().resourceProvider(); + + if (proxy->isLazy()) { + return proxy->priv().doLazyInstantiation(resourceProvider); + } + + return proxy->instantiate(resourceProvider); +} + +sk_sp GrOnFlushResourceProvider::makeBuffer(GrGpuBufferType intendedType, size_t size, + const void* data) { + // TODO: this class should probably just get a GrDirectContext + auto direct = fDrawingMgr->getContext()->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + auto resourceProvider = direct->priv().resourceProvider(); + + return sk_sp( + resourceProvider->createBuffer(size, intendedType, kDynamic_GrAccessPattern, data)); +} + +sk_sp GrOnFlushResourceProvider::findOrMakeStaticBuffer( + GrGpuBufferType intendedType, size_t size, const void* data, const GrUniqueKey& key) { + // TODO: class should probably just get a GrDirectContext + auto direct = fDrawingMgr->getContext()->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + auto resourceProvider = direct->priv().resourceProvider(); + + return resourceProvider->findOrMakeStaticBuffer(intendedType, size, data, key); +} + +uint32_t GrOnFlushResourceProvider::contextID() const { + return fDrawingMgr->getContext()->priv().contextID(); +} + +const GrCaps* GrOnFlushResourceProvider::caps() const { + return fDrawingMgr->getContext()->priv().caps(); +} diff --git a/gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.h b/gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.h new file mode 100644 index 0000000000..78a31f3038 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOnFlushResourceProvider.h @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOnFlushResourceProvider_DEFINED +#define GrOnFlushResourceProvider_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/SkTArray.h" +#include "src/gpu/GrDeferredUpload.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceProvider.h" + +class GrDrawingManager; +class GrOnFlushResourceProvider; +class GrRenderTargetContext; +class GrSurfaceProxy; +class SkColorSpace; +class SkSurfaceProps; + +/* + * This is the base class from which all pre-flush callback objects must be derived. It + * provides the "preFlush" / "postFlush" interface. + */ +class GrOnFlushCallbackObject { +public: + virtual ~GrOnFlushCallbackObject() {} + + /* + * The onFlush callback allows subsystems (e.g., text, path renderers) to create atlases + * for a specific flush. All the GrOpsTask IDs required for the flush are passed into the + * callback. The callback should return the render target contexts used to render the atlases + * in 'results'. + */ + virtual void preFlush(GrOnFlushResourceProvider*, const uint32_t* opsTaskIDs, + int numOpsTaskIDs) = 0; + + /** + * Called once flushing is complete and all ops indicated by preFlush have been executed and + * released. startTokenForNextFlush can be used to track resources used in the current flush. + */ + virtual void postFlush(GrDeferredUploadToken startTokenForNextFlush, + const uint32_t* opsTaskIDs, int numOpsTaskIDs) {} + + /** + * Tells the callback owner to hold onto this object when freeing GPU resources + * + * In particular, GrDrawingManager::freeGPUResources() deletes all the path renderers. + * Any OnFlushCallbackObject associated with a path renderer will need to be deleted. + */ + virtual bool retainOnFreeGpuResources() { return false; } +}; + +/* + * This class is a shallow wrapper around the drawing manager. It is passed into the + * onFlush callbacks and is intended to limit the functionality available to them. + * It should never have additional data members or virtual methods. + */ +class GrOnFlushResourceProvider { +public: + using UseAllocator = GrSurfaceProxy::UseAllocator; + + explicit GrOnFlushResourceProvider(GrDrawingManager* drawingMgr) : fDrawingMgr(drawingMgr) {} + + std::unique_ptr makeRenderTargetContext( + sk_sp, GrColorType, sk_sp, const SkSurfaceProps*); + + void addTextureResolveTask(sk_sp, GrSurfaceProxy::ResolveFlags); + + // Proxy unique key management. See GrProxyProvider.h. + bool assignUniqueKeyToProxy(const GrUniqueKey&, GrTextureProxy*); + void removeUniqueKeyFromProxy(GrTextureProxy*); + void processInvalidUniqueKey(const GrUniqueKey&); + // GrColorType is necessary to set the proxy's texture swizzle. + sk_sp findOrCreateProxyByUniqueKey(const GrUniqueKey&, + GrColorType, + GrSurfaceOrigin, + UseAllocator); + + bool instatiateProxy(GrSurfaceProxy*); + + // Creates a GPU buffer with a "dynamic" access pattern. + sk_sp makeBuffer(GrGpuBufferType, size_t, const void* data = nullptr); + + // Either finds and refs, or creates a static GPU buffer with the given data. + sk_sp findOrMakeStaticBuffer(GrGpuBufferType, size_t, const void* data, + const GrUniqueKey&); + + uint32_t contextID() const; + const GrCaps* caps() const; + +private: + GrOnFlushResourceProvider(const GrOnFlushResourceProvider&) = delete; + GrOnFlushResourceProvider& operator=(const GrOnFlushResourceProvider&) = delete; + + GrDrawingManager* fDrawingMgr; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrOpFlushState.cpp b/gfx/skia/skia/src/gpu/GrOpFlushState.cpp new file mode 100644 index 0000000000..05e42e18ac --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOpFlushState.cpp @@ -0,0 +1,230 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrOpFlushState.h" + +#include "include/gpu/GrTexture.h" +#include "src/core/SkConvertPixels.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrResourceProvider.h" + +////////////////////////////////////////////////////////////////////////////// + +GrOpFlushState::GrOpFlushState(GrGpu* gpu, GrResourceProvider* resourceProvider, + GrTokenTracker* tokenTracker, + sk_sp cpuBufferCache) + : fVertexPool(gpu, cpuBufferCache) + , fIndexPool(gpu, std::move(cpuBufferCache)) + , fGpu(gpu) + , fResourceProvider(resourceProvider) + , fTokenTracker(tokenTracker) {} + +const GrCaps& GrOpFlushState::caps() const { + return *fGpu->caps(); +} + +void GrOpFlushState::executeDrawsAndUploadsForMeshDrawOp( + const GrOp* op, const SkRect& chainBounds, GrProcessorSet&& processorSet, + GrPipeline::InputFlags pipelineFlags, const GrUserStencilSettings* stencilSettings) { + SkASSERT(this->opsRenderPass()); + + GrPipeline::InitArgs pipelineArgs; + pipelineArgs.fInputFlags = pipelineFlags; + pipelineArgs.fDstProxy = this->dstProxy(); + pipelineArgs.fCaps = &this->caps(); + pipelineArgs.fUserStencil = stencilSettings; + pipelineArgs.fOutputSwizzle = this->drawOpArgs().outputSwizzle(); + GrPipeline* pipeline = this->allocator()->make(pipelineArgs, + std::move(processorSet), + this->detachAppliedClip()); + + while (fCurrDraw != fDraws.end() && fCurrDraw->fOp == op) { + GrDeferredUploadToken drawToken = fTokenTracker->nextTokenToFlush(); + while (fCurrUpload != fInlineUploads.end() && + fCurrUpload->fUploadBeforeToken == drawToken) { + this->opsRenderPass()->inlineUpload(this, fCurrUpload->fUpload); + ++fCurrUpload; + } + + GrProgramInfo programInfo(this->proxy()->numSamples(), + this->proxy()->origin(), + *pipeline, + *fCurrDraw->fGeometryProcessor, + fCurrDraw->fFixedDynamicState, + fCurrDraw->fDynamicStateArrays, + fCurrDraw->fMeshCnt); + + this->opsRenderPass()->draw(programInfo, fCurrDraw->fMeshes, + fCurrDraw->fMeshCnt, chainBounds); + fTokenTracker->flushToken(); + ++fCurrDraw; + } +} + +void GrOpFlushState::preExecuteDraws() { + fVertexPool.unmap(); + fIndexPool.unmap(); + for (auto& upload : fASAPUploads) { + this->doUpload(upload); + } + // Setup execution iterators. + fCurrDraw = fDraws.begin(); + fCurrUpload = fInlineUploads.begin(); +} + +void GrOpFlushState::reset() { + SkASSERT(fCurrDraw == fDraws.end()); + SkASSERT(fCurrUpload == fInlineUploads.end()); + fVertexPool.reset(); + fIndexPool.reset(); + fArena.reset(); + fASAPUploads.reset(); + fInlineUploads.reset(); + fDraws.reset(); + fBaseDrawToken = GrDeferredUploadToken::AlreadyFlushedToken(); +} + +void GrOpFlushState::doUpload(GrDeferredTextureUploadFn& upload, + bool shouldPrepareSurfaceForSampling) { + GrDeferredTextureUploadWritePixelsFn wp = [this, shouldPrepareSurfaceForSampling]( + GrTextureProxy* dstProxy, int left, int top, int width, int height, + GrColorType colorType, const void* buffer, size_t rowBytes) { + GrSurface* dstSurface = dstProxy->peekSurface(); + if (!fGpu->caps()->surfaceSupportsWritePixels(dstSurface)) { + return false; + } + GrCaps::SupportedWrite supportedWrite = fGpu->caps()->supportedWritePixelsColorType( + colorType, dstSurface->backendFormat(), colorType); + size_t tightRB = width * GrColorTypeBytesPerPixel(supportedWrite.fColorType); + SkASSERT(rowBytes >= tightRB); + std::unique_ptr tmpPixels; + if (supportedWrite.fColorType != colorType || + (!fGpu->caps()->writePixelsRowBytesSupport() && rowBytes != tightRB)) { + tmpPixels.reset(new char[height * tightRB]); + // Use kUnpremul to ensure no alpha type conversions or clamping occur. + static constexpr auto kAT = kUnpremul_SkAlphaType; + GrImageInfo srcInfo(colorType, kAT, nullptr, width, height); + GrImageInfo tmpInfo(supportedWrite.fColorType, kAT, nullptr, width, + height); + if (!GrConvertPixels(tmpInfo, tmpPixels.get(), tightRB, srcInfo, buffer, rowBytes)) { + return false; + } + rowBytes = tightRB; + buffer = tmpPixels.get(); + } + return this->fGpu->writePixels(dstSurface, left, top, width, height, colorType, + supportedWrite.fColorType, buffer, rowBytes, + shouldPrepareSurfaceForSampling); + }; + upload(wp); +} + +GrDeferredUploadToken GrOpFlushState::addInlineUpload(GrDeferredTextureUploadFn&& upload) { + return fInlineUploads.append(&fArena, std::move(upload), fTokenTracker->nextDrawToken()) + .fUploadBeforeToken; +} + +GrDeferredUploadToken GrOpFlushState::addASAPUpload(GrDeferredTextureUploadFn&& upload) { + fASAPUploads.append(&fArena, std::move(upload)); + return fTokenTracker->nextTokenToFlush(); +} + +void GrOpFlushState::recordDraw( + sk_sp gp, const GrMesh meshes[], int meshCnt, + const GrPipeline::FixedDynamicState* fixedDynamicState, + const GrPipeline::DynamicStateArrays* dynamicStateArrays) { + SkASSERT(fOpArgs); + SkDEBUGCODE(fOpArgs->validate()); + bool firstDraw = fDraws.begin() == fDraws.end(); + auto& draw = fDraws.append(&fArena); + GrDeferredUploadToken token = fTokenTracker->issueDrawToken(); + if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) { + for (int i = 0; i < gp->numTextureSamplers(); ++i) { + fixedDynamicState->fPrimitiveProcessorTextures[i]->ref(); + } + } + if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) { + int n = gp->numTextureSamplers() * meshCnt; + for (int i = 0; i < n; ++i) { + dynamicStateArrays->fPrimitiveProcessorTextures[i]->ref(); + } + } + draw.fGeometryProcessor = std::move(gp); + draw.fFixedDynamicState = fixedDynamicState; + draw.fDynamicStateArrays = dynamicStateArrays; + draw.fMeshes = meshes; + draw.fMeshCnt = meshCnt; + draw.fOp = fOpArgs->op(); + if (firstDraw) { + fBaseDrawToken = token; + } +} + +void* GrOpFlushState::makeVertexSpace(size_t vertexSize, int vertexCount, + sk_sp* buffer, int* startVertex) { + return fVertexPool.makeSpace(vertexSize, vertexCount, buffer, startVertex); +} + +uint16_t* GrOpFlushState::makeIndexSpace(int indexCount, sk_sp* buffer, + int* startIndex) { + return reinterpret_cast(fIndexPool.makeSpace(indexCount, buffer, startIndex)); +} + +void* GrOpFlushState::makeVertexSpaceAtLeast(size_t vertexSize, int minVertexCount, + int fallbackVertexCount, sk_sp* buffer, + int* startVertex, int* actualVertexCount) { + return fVertexPool.makeSpaceAtLeast(vertexSize, minVertexCount, fallbackVertexCount, buffer, + startVertex, actualVertexCount); +} + +uint16_t* GrOpFlushState::makeIndexSpaceAtLeast(int minIndexCount, int fallbackIndexCount, + sk_sp* buffer, int* startIndex, + int* actualIndexCount) { + return reinterpret_cast(fIndexPool.makeSpaceAtLeast( + minIndexCount, fallbackIndexCount, buffer, startIndex, actualIndexCount)); +} + +void GrOpFlushState::putBackIndices(int indexCount) { + fIndexPool.putBack(indexCount * sizeof(uint16_t)); +} + +void GrOpFlushState::putBackVertices(int vertices, size_t vertexStride) { + fVertexPool.putBack(vertices * vertexStride); +} + +GrAppliedClip GrOpFlushState::detachAppliedClip() { + return fOpArgs->appliedClip() ? std::move(*fOpArgs->appliedClip()) : GrAppliedClip(); +} + +GrStrikeCache* GrOpFlushState::glyphCache() const { + return fGpu->getContext()->priv().getGrStrikeCache(); +} + +GrAtlasManager* GrOpFlushState::atlasManager() const { + return fGpu->getContext()->priv().getAtlasManager(); +} + +////////////////////////////////////////////////////////////////////////////// + +GrOpFlushState::Draw::~Draw() { + if (fFixedDynamicState && fFixedDynamicState->fPrimitiveProcessorTextures) { + for (int i = 0; i < fGeometryProcessor->numTextureSamplers(); ++i) { + fFixedDynamicState->fPrimitiveProcessorTextures[i]->unref(); + } + } + if (fDynamicStateArrays && fDynamicStateArrays->fPrimitiveProcessorTextures) { + int n = fGeometryProcessor->numTextureSamplers() * fMeshCnt; + const auto* textures = fDynamicStateArrays->fPrimitiveProcessorTextures; + for (int i = 0; i < n; ++i) { + textures[i]->unref(); + } + } +} diff --git a/gfx/skia/skia/src/gpu/GrOpFlushState.h b/gfx/skia/skia/src/gpu/GrOpFlushState.h new file mode 100644 index 0000000000..27dac24774 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOpFlushState.h @@ -0,0 +1,203 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOpFlushState_DEFINED +#define GrOpFlushState_DEFINED + +#include +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkArenaAllocList.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrBufferAllocPool.h" +#include "src/gpu/GrDeferredUpload.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/ops/GrMeshDrawOp.h" + +class GrGpu; +class GrOpsRenderPass; +class GrResourceProvider; + +/** Tracks the state across all the GrOps (really just the GrDrawOps) in a GrOpsTask flush. */ +class GrOpFlushState final : public GrDeferredUploadTarget, public GrMeshDrawOp::Target { +public: + // vertexSpace and indexSpace may either be null or an alloation of size + // GrBufferAllocPool::kDefaultBufferSize. If the latter, then CPU memory is only allocated for + // vertices/indices when a buffer larger than kDefaultBufferSize is required. + GrOpFlushState(GrGpu*, GrResourceProvider*, GrTokenTracker*, + sk_sp = nullptr); + + ~GrOpFlushState() final { this->reset(); } + + /** This is called after each op has a chance to prepare its draws and before the draws are + executed. */ + void preExecuteDraws(); + + /** Called to upload data to a texture using the GrDeferredTextureUploadFn. If the uploaded + surface needs to be prepared for being sampled in a draw after the upload, the caller + should pass in true for shouldPrepareSurfaceForSampling. This feature is needed for Vulkan + when doing inline uploads to reset the image layout back to sampled. */ + void doUpload(GrDeferredTextureUploadFn&, bool shouldPrepareSurfaceForSampling = false); + + /** Called as ops are executed. Must be called in the same order as the ops were prepared. */ + void executeDrawsAndUploadsForMeshDrawOp( + const GrOp* op, const SkRect& chainBounds, GrProcessorSet&&, + GrPipeline::InputFlags = GrPipeline::InputFlags::kNone, + const GrUserStencilSettings* = &GrUserStencilSettings::kUnused); + + GrOpsRenderPass* opsRenderPass() { return fOpsRenderPass; } + void setOpsRenderPass(GrOpsRenderPass* renderPass) { fOpsRenderPass = renderPass; } + + GrGpu* gpu() { return fGpu; } + + void reset(); + + /** Additional data required on a per-op basis when executing GrOps. */ + struct OpArgs { + explicit OpArgs(GrOp* op, GrRenderTargetProxy* proxy, GrAppliedClip* appliedClip, + const GrXferProcessor::DstProxy& dstProxy) + : fOp(op) + , fProxy(proxy) + , fAppliedClip(appliedClip) + , fDstProxy(dstProxy) { + } + + int numSamples() const { return fProxy->numSamples(); } + GrSurfaceOrigin origin() const { return fProxy->origin(); } + GrSwizzle outputSwizzle() const { return fProxy->outputSwizzle(); } + + GrOp* op() { return fOp; } + GrRenderTargetProxy* proxy() const { return fProxy; } + GrRenderTarget* renderTarget() const { return fProxy->peekRenderTarget(); } + GrAppliedClip* appliedClip() { return fAppliedClip; } + const GrAppliedClip* appliedClip() const { return fAppliedClip; } + const GrXferProcessor::DstProxy& dstProxy() const { return fDstProxy; } + +#ifdef SK_DEBUG + void validate() const { + SkASSERT(fOp); + SkASSERT(fProxy); + } +#endif + + private: + GrOp* fOp; + GrRenderTargetProxy* fProxy; + GrAppliedClip* fAppliedClip; + GrXferProcessor::DstProxy fDstProxy; // TODO: do we still need the dst proxy here? + }; + + void setOpArgs(OpArgs* opArgs) { fOpArgs = opArgs; } + + const OpArgs& drawOpArgs() const { + SkASSERT(fOpArgs); + SkDEBUGCODE(fOpArgs->validate()); + return *fOpArgs; + } + + void setSampledProxyArray(SkTArray* sampledProxies) { + fSampledProxies = sampledProxies; + } + + SkTArray* sampledProxyArray() override { + return fSampledProxies; + } + + /** Overrides of GrDeferredUploadTarget. */ + + const GrTokenTracker* tokenTracker() final { return fTokenTracker; } + GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) final; + GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&&) final; + + /** Overrides of GrMeshDrawOp::Target. */ + void recordDraw(sk_sp, const GrMesh[], int meshCnt, + const GrPipeline::FixedDynamicState*, + const GrPipeline::DynamicStateArrays*) final; + void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp*, + int* startVertex) final; + uint16_t* makeIndexSpace(int indexCount, sk_sp*, int* startIndex) final; + void* makeVertexSpaceAtLeast(size_t vertexSize, int minVertexCount, int fallbackVertexCount, + sk_sp*, int* startVertex, + int* actualVertexCount) final; + uint16_t* makeIndexSpaceAtLeast(int minIndexCount, int fallbackIndexCount, + sk_sp*, int* startIndex, + int* actualIndexCount) final; + void putBackIndices(int indexCount) final; + void putBackVertices(int vertices, size_t vertexStride) final; + GrRenderTargetProxy* proxy() const final { return fOpArgs->proxy(); } + const GrAppliedClip* appliedClip() final { return fOpArgs->appliedClip(); } + GrAppliedClip detachAppliedClip() final; + const GrXferProcessor::DstProxy& dstProxy() const final { return fOpArgs->dstProxy(); } + GrDeferredUploadTarget* deferredUploadTarget() final { return this; } + const GrCaps& caps() const final; + GrResourceProvider* resourceProvider() const final { return fResourceProvider; } + + GrStrikeCache* glyphCache() const final; + + // At this point we know we're flushing so full access to the GrAtlasManager is required (and + // permissible). + GrAtlasManager* atlasManager() const final; + + /** GrMeshDrawOp::Target override. */ + SkArenaAlloc* allocator() override { return &fArena; } + +private: + struct InlineUpload { + InlineUpload(GrDeferredTextureUploadFn&& upload, GrDeferredUploadToken token) + : fUpload(std::move(upload)), fUploadBeforeToken(token) {} + GrDeferredTextureUploadFn fUpload; + GrDeferredUploadToken fUploadBeforeToken; + }; + + // A set of contiguous draws that share a draw token, geometry processor, and pipeline. The + // meshes for the draw are stored in the fMeshes array. The reason for coalescing meshes + // that share a geometry processor into a Draw is that it allows the Gpu object to setup + // the shared state once and then issue draws for each mesh. + struct Draw { + ~Draw(); + sk_sp fGeometryProcessor; + const GrPipeline::FixedDynamicState* fFixedDynamicState; + const GrPipeline::DynamicStateArrays* fDynamicStateArrays; + const GrMesh* fMeshes = nullptr; + const GrOp* fOp = nullptr; + int fMeshCnt = 0; + }; + + // Storage for ops' pipelines, draws, and inline uploads. + SkArenaAlloc fArena{sizeof(GrPipeline) * 100}; + + // Store vertex and index data on behalf of ops that are flushed. + GrVertexBufferAllocPool fVertexPool; + GrIndexBufferAllocPool fIndexPool; + + // Data stored on behalf of the ops being flushed. + SkArenaAllocList fASAPUploads; + SkArenaAllocList fInlineUploads; + SkArenaAllocList fDraws; + + // All draws we store have an implicit draw token. This is the draw token for the first draw + // in fDraws. + GrDeferredUploadToken fBaseDrawToken = GrDeferredUploadToken::AlreadyFlushedToken(); + + // Info about the op that is currently preparing or executing using the flush state or null if + // an op is not currently preparing of executing. + OpArgs* fOpArgs = nullptr; + + // This field is only transiently set during flush. Each GrOpsTask will set it to point to an + // array of proxies it uses before call onPrepare and onExecute. + SkTArray* fSampledProxies; + + GrGpu* fGpu; + GrResourceProvider* fResourceProvider; + GrTokenTracker* fTokenTracker; + GrOpsRenderPass* fOpsRenderPass = nullptr; + + // Variables that are used to track where we are in lists as ops are executed + SkArenaAllocList::Iter fCurrDraw; + SkArenaAllocList::Iter fCurrUpload; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrOpsRenderPass.cpp b/gfx/skia/skia/src/gpu/GrOpsRenderPass.cpp new file mode 100644 index 0000000000..1b8ebeae05 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOpsRenderPass.cpp @@ -0,0 +1,68 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/GrOpsRenderPass.h" + +#include "include/core/SkRect.h" +#include "include/gpu/GrContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrPrimitiveProcessor.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrTexturePriv.h" + +void GrOpsRenderPass::clear(const GrFixedClip& clip, const SkPMColor4f& color) { + SkASSERT(fRenderTarget); + // A clear at this level will always be a true clear, so make sure clears were not supposed to + // be redirected to draws instead + SkASSERT(!this->gpu()->caps()->performColorClearsAsDraws()); + SkASSERT(!clip.scissorEnabled() || !this->gpu()->caps()->performPartialClearsAsDraws()); + this->onClear(clip, color); +} + +void GrOpsRenderPass::clearStencilClip(const GrFixedClip& clip, bool insideStencilMask) { + // As above, make sure the stencil clear wasn't supposed to be a draw rect with stencil settings + SkASSERT(!this->gpu()->caps()->performStencilClearsAsDraws()); + this->onClearStencilClip(clip, insideStencilMask); +} + +bool GrOpsRenderPass::draw(const GrProgramInfo& programInfo, + const GrMesh meshes[], int meshCount, const SkRect& bounds) { + if (!meshCount) { + return true; + } + +#ifdef SK_DEBUG + SkASSERT(!programInfo.primProc().hasInstanceAttributes() || + this->gpu()->caps()->instanceAttribSupport()); + + programInfo.compatibleWithMeshes(meshes, meshCount); + programInfo.checkAllInstantiated(); + programInfo.checkMSAAAndMIPSAreResolved(); +#endif + + if (programInfo.primProc().numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) { + this->gpu()->stats()->incNumFailedDraws(); + return false; + } + this->onDraw(programInfo, meshes, meshCount, bounds); + +#ifdef SK_DEBUG + GrProcessor::CustomFeatures processorFeatures = programInfo.requestedFeatures(); + if (GrProcessor::CustomFeatures::kSampleLocations & processorFeatures) { + // Verify we always have the same sample pattern key, regardless of graphics state. + SkASSERT(this->gpu()->findOrAssignSamplePatternKey(fRenderTarget) + == fRenderTarget->renderTargetPriv().getSamplePatternKey()); + } +#endif + return true; +} diff --git a/gfx/skia/skia/src/gpu/GrOpsRenderPass.h b/gfx/skia/skia/src/gpu/GrOpsRenderPass.h new file mode 100644 index 0000000000..e3d24a7b15 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOpsRenderPass.h @@ -0,0 +1,109 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrOpsRenderPass_DEFINED +#define GrOpsRenderPass_DEFINED + +#include "include/core/SkDrawable.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/ops/GrDrawOp.h" + +class GrOpFlushState; +class GrFixedClip; +class GrGpu; +class GrMesh; +class GrPipeline; +class GrPrimitiveProcessor; +class GrProgramInfo; +class GrRenderTarget; +class GrSemaphore; +struct SkIRect; +struct SkRect; + +/** + * The GrOpsRenderPass is a series of commands (draws, clears, and discards), which all target the + * same render target. It is possible that these commands execute immediately (GL), or get buffered + * up for later execution (Vulkan). GrOps execute into a GrOpsRenderPass. + */ +class GrOpsRenderPass { +public: + virtual ~GrOpsRenderPass() {} + + virtual void insertEventMarker(const char*) = 0; + + struct LoadAndStoreInfo { + GrLoadOp fLoadOp; + GrStoreOp fStoreOp; + SkPMColor4f fClearColor; + }; + + // Load-time clears of the stencil buffer are always to 0 so we don't store + // an 'fStencilClearValue' + struct StencilLoadAndStoreInfo { + GrLoadOp fLoadOp; + GrStoreOp fStoreOp; + }; + + virtual void begin() = 0; + // Signals the end of recording to the GrOpsRenderPass and that it can now be submitted. + virtual void end() = 0; + + // We pass in an array of meshCount GrMesh to the draw. The backend should loop over each + // GrMesh object and emit a draw for it. Each draw will use the same GrPipeline and + // GrPrimitiveProcessor. This may fail if the draw would exceed any resource limits (e.g. + // number of vertex attributes is too large). + bool draw(const GrProgramInfo&, const GrMesh[], int meshCount, const SkRect& bounds); + + // Performs an upload of vertex data in the middle of a set of a set of draws + virtual void inlineUpload(GrOpFlushState*, GrDeferredTextureUploadFn&) = 0; + + /** + * Clear the owned render target. Ignores the draw state and clip. + */ + void clear(const GrFixedClip&, const SkPMColor4f&); + + void clearStencilClip(const GrFixedClip&, bool insideStencilMask); + + /** + * Executes the SkDrawable object for the underlying backend. + */ + virtual void executeDrawable(std::unique_ptr) {} + +protected: + GrOpsRenderPass() : fOrigin(kTopLeft_GrSurfaceOrigin), fRenderTarget(nullptr) {} + + GrOpsRenderPass(GrRenderTarget* rt, GrSurfaceOrigin origin) + : fOrigin(origin) + , fRenderTarget(rt) { + } + + void set(GrRenderTarget* rt, GrSurfaceOrigin origin) { + SkASSERT(!fRenderTarget); + + fRenderTarget = rt; + fOrigin = origin; + } + + GrSurfaceOrigin fOrigin; + GrRenderTarget* fRenderTarget; + +private: + virtual GrGpu* gpu() = 0; + + // overridden by backend-specific derived class to perform the draw call. + virtual void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount, + const SkRect& bounds) = 0; + + // overridden by backend-specific derived class to perform the clear. + virtual void onClear(const GrFixedClip&, const SkPMColor4f&) = 0; + + virtual void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) = 0; + + typedef GrOpsRenderPass INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrOpsTask.cpp b/gfx/skia/skia/src/gpu/GrOpsTask.cpp new file mode 100644 index 0000000000..4d4236b2d9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOpsTask.cpp @@ -0,0 +1,856 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrOpsTask.h" + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkExchange.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrResourceAllocator.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/geometry/GrRect.h" +#include "src/gpu/ops/GrClearOp.h" + +//////////////////////////////////////////////////////////////////////////////// + +// Experimentally we have found that most combining occurs within the first 10 comparisons. +static const int kMaxOpMergeDistance = 10; +static const int kMaxOpChainDistance = 10; + +//////////////////////////////////////////////////////////////////////////////// + +using DstProxy = GrXferProcessor::DstProxy; + +//////////////////////////////////////////////////////////////////////////////// + +static inline bool can_reorder(const SkRect& a, const SkRect& b) { return !GrRectsOverlap(a, b); } + +//////////////////////////////////////////////////////////////////////////////// + +inline GrOpsTask::OpChain::List::List(std::unique_ptr op) + : fHead(std::move(op)), fTail(fHead.get()) { + this->validate(); +} + +inline GrOpsTask::OpChain::List::List(List&& that) { *this = std::move(that); } + +inline GrOpsTask::OpChain::List& GrOpsTask::OpChain::List::operator=(List&& that) { + fHead = std::move(that.fHead); + fTail = that.fTail; + that.fTail = nullptr; + this->validate(); + return *this; +} + +inline std::unique_ptr GrOpsTask::OpChain::List::popHead() { + SkASSERT(fHead); + auto temp = fHead->cutChain(); + std::swap(temp, fHead); + if (!fHead) { + SkASSERT(fTail == temp.get()); + fTail = nullptr; + } + return temp; +} + +inline std::unique_ptr GrOpsTask::OpChain::List::removeOp(GrOp* op) { +#ifdef SK_DEBUG + auto head = op; + while (head->prevInChain()) { head = head->prevInChain(); } + SkASSERT(head == fHead.get()); +#endif + auto prev = op->prevInChain(); + if (!prev) { + SkASSERT(op == fHead.get()); + return this->popHead(); + } + auto temp = prev->cutChain(); + if (auto next = temp->cutChain()) { + prev->chainConcat(std::move(next)); + } else { + SkASSERT(fTail == op); + fTail = prev; + } + this->validate(); + return temp; +} + +inline void GrOpsTask::OpChain::List::pushHead(std::unique_ptr op) { + SkASSERT(op); + SkASSERT(op->isChainHead()); + SkASSERT(op->isChainTail()); + if (fHead) { + op->chainConcat(std::move(fHead)); + fHead = std::move(op); + } else { + fHead = std::move(op); + fTail = fHead.get(); + } +} + +inline void GrOpsTask::OpChain::List::pushTail(std::unique_ptr op) { + SkASSERT(op->isChainTail()); + fTail->chainConcat(std::move(op)); + fTail = fTail->nextInChain(); +} + +inline void GrOpsTask::OpChain::List::validate() const { +#ifdef SK_DEBUG + if (fHead) { + SkASSERT(fTail); + fHead->validateChain(fTail); + } +#endif +} + +//////////////////////////////////////////////////////////////////////////////// + +GrOpsTask::OpChain::OpChain(std::unique_ptr op, + GrProcessorSet::Analysis processorAnalysis, + GrAppliedClip* appliedClip, const DstProxy* dstProxy) + : fList{std::move(op)} + , fProcessorAnalysis(processorAnalysis) + , fAppliedClip(appliedClip) { + if (fProcessorAnalysis.requiresDstTexture()) { + SkASSERT(dstProxy && dstProxy->proxy()); + fDstProxy = *dstProxy; + } + fBounds = fList.head()->bounds(); +} + +void GrOpsTask::OpChain::visitProxies(const GrOp::VisitProxyFunc& func) const { + if (fList.empty()) { + return; + } + for (const auto& op : GrOp::ChainRange<>(fList.head())) { + op.visitProxies(func); + } + if (fDstProxy.proxy()) { + func(fDstProxy.proxy(), GrMipMapped::kNo); + } + if (fAppliedClip) { + fAppliedClip->visitProxies(func); + } +} + +void GrOpsTask::OpChain::deleteOps(GrOpMemoryPool* pool) { + while (!fList.empty()) { + pool->release(fList.popHead()); + } +} + +// Concatenates two op chains and attempts to merge ops across the chains. Assumes that we know that +// the two chains are chainable. Returns the new chain. +GrOpsTask::OpChain::List GrOpsTask::OpChain::DoConcat( + List chainA, List chainB, const GrCaps& caps, GrOpMemoryPool* pool, + GrAuditTrail* auditTrail) { + // We process ops in chain b from head to tail. We attempt to merge with nodes in a, starting + // at chain a's tail and working toward the head. We produce one of the following outcomes: + // 1) b's head is merged into an op in a. + // 2) An op from chain a is merged into b's head. (In this case b's head gets processed again.) + // 3) b's head is popped from chain a and added at the tail of a. + // After result 3 we don't want to attempt to merge the next head of b with the new tail of a, + // as we assume merges were already attempted when chain b was created. So we keep track of the + // original tail of a and start our iteration of a there. We also track the bounds of the nodes + // appended to chain a that will be skipped for bounds testing. If the original tail of a is + // merged into an op in b (case 2) then we advance the "original tail" towards the head of a. + GrOp* origATail = chainA.tail(); + SkRect skipBounds = SkRectPriv::MakeLargestInverted(); + do { + int numMergeChecks = 0; + bool merged = false; + bool noSkip = (origATail == chainA.tail()); + SkASSERT(noSkip == (skipBounds == SkRectPriv::MakeLargestInverted())); + bool canBackwardMerge = noSkip || can_reorder(chainB.head()->bounds(), skipBounds); + SkRect forwardMergeBounds = skipBounds; + GrOp* a = origATail; + while (a) { + bool canForwardMerge = + (a == chainA.tail()) || can_reorder(a->bounds(), forwardMergeBounds); + if (canForwardMerge || canBackwardMerge) { + auto result = a->combineIfPossible(chainB.head(), caps); + SkASSERT(result != GrOp::CombineResult::kCannotCombine); + merged = (result == GrOp::CombineResult::kMerged); + GrOP_INFO("\t\t: (%s opID: %u) -> Combining with (%s, opID: %u)\n", + chainB.head()->name(), chainB.head()->uniqueID(), a->name(), + a->uniqueID()); + } + if (merged) { + GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(auditTrail, a, chainB.head()); + if (canBackwardMerge) { + pool->release(chainB.popHead()); + } else { + // We merged the contents of b's head into a. We will replace b's head with a in + // chain b. + SkASSERT(canForwardMerge); + if (a == origATail) { + origATail = a->prevInChain(); + } + std::unique_ptr detachedA = chainA.removeOp(a); + pool->release(chainB.popHead()); + chainB.pushHead(std::move(detachedA)); + if (chainA.empty()) { + // We merged all the nodes in chain a to chain b. + return chainB; + } + } + break; + } else { + if (++numMergeChecks == kMaxOpMergeDistance) { + break; + } + forwardMergeBounds.joinNonEmptyArg(a->bounds()); + canBackwardMerge = + canBackwardMerge && can_reorder(chainB.head()->bounds(), a->bounds()); + a = a->prevInChain(); + } + } + // If we weren't able to merge b's head then pop b's head from chain b and make it the new + // tail of a. + if (!merged) { + chainA.pushTail(chainB.popHead()); + skipBounds.joinNonEmptyArg(chainA.tail()->bounds()); + } + } while (!chainB.empty()); + return chainA; +} + +// Attempts to concatenate the given chain onto our own and merge ops across the chains. Returns +// whether the operation succeeded. On success, the provided list will be returned empty. +bool GrOpsTask::OpChain::tryConcat( + List* list, GrProcessorSet::Analysis processorAnalysis, const DstProxy& dstProxy, + const GrAppliedClip* appliedClip, const SkRect& bounds, const GrCaps& caps, + GrOpMemoryPool* pool, GrAuditTrail* auditTrail) { + SkASSERT(!fList.empty()); + SkASSERT(!list->empty()); + SkASSERT(fProcessorAnalysis.requiresDstTexture() == SkToBool(fDstProxy.proxy())); + SkASSERT(processorAnalysis.requiresDstTexture() == SkToBool(dstProxy.proxy())); + // All returns use explicit tuple constructor rather than {a, b} to work around old GCC bug. + if (fList.head()->classID() != list->head()->classID() || + SkToBool(fAppliedClip) != SkToBool(appliedClip) || + (fAppliedClip && *fAppliedClip != *appliedClip) || + (fProcessorAnalysis.requiresNonOverlappingDraws() != + processorAnalysis.requiresNonOverlappingDraws()) || + (fProcessorAnalysis.requiresNonOverlappingDraws() && + // Non-overlaping draws are only required when Ganesh will either insert a barrier, + // or read back a new dst texture between draws. In either case, we can neither + // chain nor combine overlapping Ops. + GrRectsTouchOrOverlap(fBounds, bounds)) || + (fProcessorAnalysis.requiresDstTexture() != processorAnalysis.requiresDstTexture()) || + (fProcessorAnalysis.requiresDstTexture() && fDstProxy != dstProxy)) { + return false; + } + + SkDEBUGCODE(bool first = true;) + do { + switch (fList.tail()->combineIfPossible(list->head(), caps)) { + case GrOp::CombineResult::kCannotCombine: + // If an op supports chaining then it is required that chaining is transitive and + // that if any two ops in two different chains can merge then the two chains + // may also be chained together. Thus, we should only hit this on the first + // iteration. + SkASSERT(first); + return false; + case GrOp::CombineResult::kMayChain: + fList = DoConcat(std::move(fList), skstd::exchange(*list, List()), caps, pool, + auditTrail); + // The above exchange cleared out 'list'. The list needs to be empty now for the + // loop to terminate. + SkASSERT(list->empty()); + break; + case GrOp::CombineResult::kMerged: { + GrOP_INFO("\t\t: (%s opID: %u) -> Combining with (%s, opID: %u)\n", + list->tail()->name(), list->tail()->uniqueID(), list->head()->name(), + list->head()->uniqueID()); + GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(auditTrail, fList.tail(), list->head()); + pool->release(list->popHead()); + break; + } + } + SkDEBUGCODE(first = false); + } while (!list->empty()); + + // The new ops were successfully merged and/or chained onto our own. + fBounds.joinPossiblyEmptyRect(bounds); + return true; +} + +bool GrOpsTask::OpChain::prependChain(OpChain* that, const GrCaps& caps, GrOpMemoryPool* pool, + GrAuditTrail* auditTrail) { + if (!that->tryConcat( + &fList, fProcessorAnalysis, fDstProxy, fAppliedClip, fBounds, caps, pool, auditTrail)) { + this->validate(); + // append failed + return false; + } + + // 'that' owns the combined chain. Move it into 'this'. + SkASSERT(fList.empty()); + fList = std::move(that->fList); + fBounds = that->fBounds; + + that->fDstProxy.setProxy(nullptr); + if (that->fAppliedClip) { + for (int i = 0; i < that->fAppliedClip->numClipCoverageFragmentProcessors(); ++i) { + that->fAppliedClip->detachClipCoverageFragmentProcessor(i); + } + } + this->validate(); + return true; +} + +std::unique_ptr GrOpsTask::OpChain::appendOp( + std::unique_ptr op, GrProcessorSet::Analysis processorAnalysis, + const DstProxy* dstProxy, const GrAppliedClip* appliedClip, const GrCaps& caps, + GrOpMemoryPool* pool, GrAuditTrail* auditTrail) { + const GrXferProcessor::DstProxy noDstProxy; + if (!dstProxy) { + dstProxy = &noDstProxy; + } + SkASSERT(op->isChainHead() && op->isChainTail()); + SkRect opBounds = op->bounds(); + List chain(std::move(op)); + if (!this->tryConcat( + &chain, processorAnalysis, *dstProxy, appliedClip, opBounds, caps, pool, auditTrail)) { + // append failed, give the op back to the caller. + this->validate(); + return chain.popHead(); + } + + SkASSERT(chain.empty()); + this->validate(); + return nullptr; +} + +inline void GrOpsTask::OpChain::validate() const { +#ifdef SK_DEBUG + fList.validate(); + for (const auto& op : GrOp::ChainRange<>(fList.head())) { + // Not using SkRect::contains because we allow empty rects. + SkASSERT(fBounds.fLeft <= op.bounds().fLeft && fBounds.fTop <= op.bounds().fTop && + fBounds.fRight >= op.bounds().fRight && fBounds.fBottom >= op.bounds().fBottom); + } +#endif +} + +//////////////////////////////////////////////////////////////////////////////// + +GrOpsTask::GrOpsTask(sk_sp opMemoryPool, + sk_sp rtProxy, + GrAuditTrail* auditTrail) + : GrRenderTask(std::move(rtProxy)) + , fOpMemoryPool(std::move(opMemoryPool)) + , fAuditTrail(auditTrail) + , fLastClipStackGenID(SK_InvalidUniqueID) + SkDEBUGCODE(, fNumClips(0)) { + SkASSERT(fOpMemoryPool); + fTarget->setLastRenderTask(this); +} + +void GrOpsTask::deleteOps() { + for (auto& chain : fOpChains) { + chain.deleteOps(fOpMemoryPool.get()); + } + fOpChains.reset(); +} + +GrOpsTask::~GrOpsTask() { + this->deleteOps(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrOpsTask::endFlush() { + fLastClipStackGenID = SK_InvalidUniqueID; + this->deleteOps(); + fClipAllocator.reset(); + + if (fTarget && this == fTarget->getLastRenderTask()) { + fTarget->setLastRenderTask(nullptr); + } + + fTarget.reset(); + fDeferredProxies.reset(); + fSampledProxies.reset(); + fAuditTrail = nullptr; +} + +void GrOpsTask::onPrePrepare(GrRecordingContext* context) { + SkASSERT(this->isClosed()); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + TRACE_EVENT0("skia.gpu", TRACE_FUNC); +#endif + // TODO: remove the check for discard here once reduced op splitting is turned on. Currently we + // can end up with GrOpsTasks that only have a discard load op and no ops. For vulkan validation + // we need to keep that discard and not drop it. Once we have reduce op list splitting enabled + // we shouldn't end up with GrOpsTasks with only discard. + if (this->isNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) { + return; + } + + for (const auto& chain : fOpChains) { + if (chain.shouldExecute()) { + chain.head()->prePrepare(context); + } + } +} + +void GrOpsTask::onPrepare(GrOpFlushState* flushState) { + SkASSERT(fTarget->peekRenderTarget()); + SkASSERT(this->isClosed()); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + TRACE_EVENT0("skia.gpu", TRACE_FUNC); +#endif + // TODO: remove the check for discard here once reduced op splitting is turned on. Currently we + // can end up with GrOpsTasks that only have a discard load op and no ops. For vulkan validation + // we need to keep that discard and not drop it. Once we have reduce op list splitting enabled + // we shouldn't end up with GrOpsTasks with only discard. + if (this->isNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) { + return; + } + + flushState->setSampledProxyArray(&fSampledProxies); + // Loop over the ops that haven't yet been prepared. + for (const auto& chain : fOpChains) { + if (chain.shouldExecute()) { +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + TRACE_EVENT0("skia.gpu", chain.head()->name()); +#endif + GrOpFlushState::OpArgs opArgs(chain.head(), + fTarget->asRenderTargetProxy(), + chain.appliedClip(), + chain.dstProxy()); + + flushState->setOpArgs(&opArgs); + // GrOp::prePrepare may or may not have been called at this point + chain.head()->prepare(flushState); + flushState->setOpArgs(nullptr); + } + } + flushState->setSampledProxyArray(nullptr); +} + +static GrOpsRenderPass* create_render_pass( + GrGpu* gpu, GrRenderTarget* rt, GrSurfaceOrigin origin, const SkIRect& bounds, + GrLoadOp colorLoadOp, const SkPMColor4f& loadClearColor, GrLoadOp stencilLoadOp, + GrStoreOp stencilStoreOp, const SkTArray& sampledProxies) { + const GrOpsRenderPass::LoadAndStoreInfo kColorLoadStoreInfo { + colorLoadOp, + GrStoreOp::kStore, + loadClearColor + }; + + // TODO: + // We would like to (at this level) only ever clear & discard. We would need + // to stop splitting up higher level OpsTasks for copyOps to achieve that. + // Note: we would still need SB loads and stores but they would happen at a + // lower level (inside the VK command buffer). + const GrOpsRenderPass::StencilLoadAndStoreInfo stencilLoadAndStoreInfo { + stencilLoadOp, + stencilStoreOp, + }; + + return gpu->getOpsRenderPass(rt, origin, bounds, kColorLoadStoreInfo, stencilLoadAndStoreInfo, + sampledProxies); +} + +// TODO: this is where GrOp::renderTarget is used (which is fine since it +// is at flush time). However, we need to store the RenderTargetProxy in the +// Ops and instantiate them here. +bool GrOpsTask::onExecute(GrOpFlushState* flushState) { + // TODO: remove the check for discard here once reduced op splitting is turned on. Currently we + // can end up with GrOpsTasks that only have a discard load op and no ops. For vulkan validation + // we need to keep that discard and not drop it. Once we have reduce op list splitting enabled + // we shouldn't end up with GrOpsTasks with only discard. + if (this->isNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) { + return false; + } + + SkASSERT(fTarget->peekRenderTarget()); + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + // Make sure load ops are not kClear if the GPU needs to use draws for clears + SkASSERT(fColorLoadOp != GrLoadOp::kClear || + !flushState->gpu()->caps()->performColorClearsAsDraws()); + + const GrCaps& caps = *flushState->gpu()->caps(); + GrRenderTarget* renderTarget = fTarget.get()->peekRenderTarget(); + SkASSERT(renderTarget); + GrStencilAttachment* stencil = renderTarget->renderTargetPriv().getStencilAttachment(); + + GrLoadOp stencilLoadOp; + switch (fInitialStencilContent) { + case StencilContent::kDontCare: + stencilLoadOp = GrLoadOp::kDiscard; + break; + case StencilContent::kUserBitsCleared: + SkASSERT(!caps.performStencilClearsAsDraws()); + SkASSERT(stencil); + if (caps.discardStencilValuesAfterRenderPass()) { + // Always clear the stencil if it is being discarded after render passes. This is + // also an optimization because we are on a tiler and it avoids loading the values + // from memory. + stencilLoadOp = GrLoadOp::kClear; + break; + } + if (!stencil->hasPerformedInitialClear()) { + stencilLoadOp = GrLoadOp::kClear; + stencil->markHasPerformedInitialClear(); + break; + } + // renderTargetContexts are required to leave the user stencil bits in a cleared state + // once finished, meaning the stencil values will always remain cleared after the + // initial clear. Just fall through to reloading the existing (cleared) stencil values + // from memory. + case StencilContent::kPreserved: + SkASSERT(stencil); + stencilLoadOp = GrLoadOp::kLoad; + break; + } + + // NOTE: If fMustPreserveStencil is set, then we are executing a renderTargetContext that split + // its opsTask. + // + // FIXME: We don't currently flag render passes that don't use stencil at all. In that case + // their store op might be "discard", and we currently make the assumption that a discard will + // not invalidate what's already in main memory. This is probably ok for now, but certainly + // something we want to address soon. + GrStoreOp stencilStoreOp = (caps.discardStencilValuesAfterRenderPass() && !fMustPreserveStencil) + ? GrStoreOp::kDiscard + : GrStoreOp::kStore; + + GrOpsRenderPass* renderPass = create_render_pass( + flushState->gpu(), fTarget->peekRenderTarget(), fTarget->origin(), + fClippedContentBounds, fColorLoadOp, fLoadClearColor, stencilLoadOp, stencilStoreOp, + fSampledProxies); + flushState->setOpsRenderPass(renderPass); + renderPass->begin(); + + // Draw all the generated geometry. + for (const auto& chain : fOpChains) { + if (!chain.shouldExecute()) { + continue; + } +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + TRACE_EVENT0("skia.gpu", chain.head()->name()); +#endif + + GrOpFlushState::OpArgs opArgs(chain.head(), + fTarget->asRenderTargetProxy(), + chain.appliedClip(), + chain.dstProxy()); + + flushState->setOpArgs(&opArgs); + chain.head()->execute(flushState, chain.bounds()); + flushState->setOpArgs(nullptr); + } + + renderPass->end(); + flushState->gpu()->submit(renderPass); + flushState->setOpsRenderPass(nullptr); + + return true; +} + +void GrOpsTask::setColorLoadOp(GrLoadOp op, const SkPMColor4f& color) { + fColorLoadOp = op; + fLoadClearColor = color; + if (GrLoadOp::kClear == fColorLoadOp) { + fTotalBounds.setWH(fTarget->width(), fTarget->height()); + } +} + +bool GrOpsTask::resetForFullscreenClear(CanDiscardPreviousOps canDiscardPreviousOps) { + // If we previously recorded a wait op, we cannot delete the wait op. Until we track the wait + // ops separately from normal ops, we have to avoid clearing out any ops in this case as well. + if (fHasWaitOp) { + canDiscardPreviousOps = CanDiscardPreviousOps::kNo; + } + + if (CanDiscardPreviousOps::kYes == canDiscardPreviousOps || this->isEmpty()) { + this->deleteOps(); + fDeferredProxies.reset(); + fSampledProxies.reset(); + + // If the opsTask is using a render target which wraps a vulkan command buffer, we can't do + // a clear load since we cannot change the render pass that we are using. Thus we fall back + // to making a clear op in this case. + return !fTarget->asRenderTargetProxy()->wrapsVkSecondaryCB(); + } + + // Could not empty the task, so an op must be added to handle the clear + return false; +} + +void GrOpsTask::discard() { + // Discard calls to in-progress opsTasks are ignored. Calls at the start update the + // opsTasks' color & stencil load ops. + if (this->isEmpty()) { + fColorLoadOp = GrLoadOp::kDiscard; + fInitialStencilContent = StencilContent::kDontCare; + fTotalBounds.setEmpty(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG +void GrOpsTask::dump(bool printDependencies) const { + GrRenderTask::dump(printDependencies); + + SkDebugf("fColorLoadOp: "); + switch (fColorLoadOp) { + case GrLoadOp::kLoad: + SkDebugf("kLoad\n"); + break; + case GrLoadOp::kClear: + SkDebugf("kClear (0x%x)\n", fLoadClearColor.toBytes_RGBA()); + break; + case GrLoadOp::kDiscard: + SkDebugf("kDiscard\n"); + break; + } + + SkDebugf("fInitialStencilContent: "); + switch (fInitialStencilContent) { + case StencilContent::kDontCare: + SkDebugf("kDontCare\n"); + break; + case StencilContent::kUserBitsCleared: + SkDebugf("kUserBitsCleared\n"); + break; + case StencilContent::kPreserved: + SkDebugf("kPreserved\n"); + break; + } + + SkDebugf("ops (%d):\n", fOpChains.count()); + for (int i = 0; i < fOpChains.count(); ++i) { + SkDebugf("*******************************\n"); + if (!fOpChains[i].head()) { + SkDebugf("%d: \n", i); + } else { + SkDebugf("%d: %s\n", i, fOpChains[i].head()->name()); + SkRect bounds = fOpChains[i].bounds(); + SkDebugf("ClippedBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", bounds.fLeft, + bounds.fTop, bounds.fRight, bounds.fBottom); + for (const auto& op : GrOp::ChainRange<>(fOpChains[i].head())) { + SkString info = SkTabString(op.dumpInfo(), 1); + SkDebugf("%s\n", info.c_str()); + bounds = op.bounds(); + SkDebugf("\tClippedBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", bounds.fLeft, + bounds.fTop, bounds.fRight, bounds.fBottom); + } + } + } +} + +void GrOpsTask::visitProxies_debugOnly(const VisitSurfaceProxyFunc& func) const { + auto textureFunc = [ func ] (GrTextureProxy* tex, GrMipMapped mipmapped) { + func(tex, mipmapped); + }; + + for (const OpChain& chain : fOpChains) { + chain.visitProxies(textureFunc); + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// + +bool GrOpsTask::onIsUsed(GrSurfaceProxy* proxyToCheck) const { + bool used = false; + + auto visit = [ proxyToCheck, &used ] (GrSurfaceProxy* p, GrMipMapped) { + if (p == proxyToCheck) { + used = true; + } + }; + for (const OpChain& recordedOp : fOpChains) { + recordedOp.visitProxies(visit); + } + + return used; +} + +void GrOpsTask::handleInternalAllocationFailure() { + bool hasUninstantiatedProxy = false; + auto checkInstantiation = [&hasUninstantiatedProxy](GrSurfaceProxy* p, GrMipMapped) { + if (!p->isInstantiated()) { + hasUninstantiatedProxy = true; + } + }; + for (OpChain& recordedOp : fOpChains) { + hasUninstantiatedProxy = false; + recordedOp.visitProxies(checkInstantiation); + if (hasUninstantiatedProxy) { + recordedOp.setSkipExecuteFlag(); + } + } +} + +void GrOpsTask::gatherProxyIntervals(GrResourceAllocator* alloc) const { + for (int i = 0; i < fDeferredProxies.count(); ++i) { + SkASSERT(!fDeferredProxies[i]->isInstantiated()); + // We give all the deferred proxies a write usage at the very start of flushing. This + // locks them out of being reused for the entire flush until they are read - and then + // they can be recycled. This is a bit unfortunate because a flush can proceed in waves + // with sub-flushes. The deferred proxies only need to be pinned from the start of + // the sub-flush in which they appear. + alloc->addInterval(fDeferredProxies[i], 0, 0, GrResourceAllocator::ActualUse::kNo); + } + + // Add the interval for all the writes to this GrOpsTasks's target + if (fOpChains.count()) { + unsigned int cur = alloc->curOp(); + + alloc->addInterval(fTarget.get(), cur, cur + fOpChains.count() - 1, + GrResourceAllocator::ActualUse::kYes); + } else { + // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we + // still need to add an interval for the destination so we create a fake op# for + // the missing clear op. + alloc->addInterval(fTarget.get(), alloc->curOp(), alloc->curOp(), + GrResourceAllocator::ActualUse::kYes); + alloc->incOps(); + } + + auto gather = [ alloc SkDEBUGCODE(, this) ] (GrSurfaceProxy* p, GrMipMapped) { + alloc->addInterval(p, alloc->curOp(), alloc->curOp(), GrResourceAllocator::ActualUse::kYes + SkDEBUGCODE(, fTarget.get() == p)); + }; + for (const OpChain& recordedOp : fOpChains) { + recordedOp.visitProxies(gather); + + // Even though the op may have been (re)moved we still need to increment the op count to + // keep all the math consistent. + alloc->incOps(); + } +} + +void GrOpsTask::recordOp( + std::unique_ptr op, GrProcessorSet::Analysis processorAnalysis, GrAppliedClip* clip, + const DstProxy* dstProxy, const GrCaps& caps) { + SkDEBUGCODE(op->validate();) + SkASSERT(processorAnalysis.requiresDstTexture() == (dstProxy && dstProxy->proxy())); + SkASSERT(fTarget); + + // A closed GrOpsTask should never receive new/more ops + SkASSERT(!this->isClosed()); + if (!op->bounds().isFinite()) { + fOpMemoryPool->release(std::move(op)); + return; + } + + // Account for this op's bounds before we attempt to combine. + // NOTE: The caller should have already called "op->setClippedBounds()" by now, if applicable. + fTotalBounds.join(op->bounds()); + + // Check if there is an op we can combine with by linearly searching back until we either + // 1) check every op + // 2) intersect with something + // 3) find a 'blocker' + GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get(), fTarget->uniqueID()); + GrOP_INFO("opsTask: %d Recording (%s, opID: %u)\n" + "\tBounds [L: %.2f, T: %.2f R: %.2f B: %.2f]\n", + this->uniqueID(), + op->name(), + op->uniqueID(), + op->bounds().fLeft, op->bounds().fTop, + op->bounds().fRight, op->bounds().fBottom); + GrOP_INFO(SkTabString(op->dumpInfo(), 1).c_str()); + GrOP_INFO("\tOutcome:\n"); + int maxCandidates = SkTMin(kMaxOpChainDistance, fOpChains.count()); + if (maxCandidates) { + int i = 0; + while (true) { + OpChain& candidate = fOpChains.fromBack(i); + op = candidate.appendOp(std::move(op), processorAnalysis, dstProxy, clip, caps, + fOpMemoryPool.get(), fAuditTrail); + if (!op) { + return; + } + // Stop going backwards if we would cause a painter's order violation. + if (!can_reorder(candidate.bounds(), op->bounds())) { + GrOP_INFO("\t\tBackward: Intersects with chain (%s, head opID: %u)\n", + candidate.head()->name(), candidate.head()->uniqueID()); + break; + } + if (++i == maxCandidates) { + GrOP_INFO("\t\tBackward: Reached max lookback or beginning of op array %d\n", i); + break; + } + } + } else { + GrOP_INFO("\t\tBackward: FirstOp\n"); + } + if (clip) { + clip = fClipAllocator.make(std::move(*clip)); + SkDEBUGCODE(fNumClips++;) + } + fOpChains.emplace_back(std::move(op), processorAnalysis, clip, dstProxy); +} + +void GrOpsTask::forwardCombine(const GrCaps& caps) { + SkASSERT(!this->isClosed()); + GrOP_INFO("opsTask: %d ForwardCombine %d ops:\n", this->uniqueID(), fOpChains.count()); + + for (int i = 0; i < fOpChains.count() - 1; ++i) { + OpChain& chain = fOpChains[i]; + int maxCandidateIdx = SkTMin(i + kMaxOpChainDistance, fOpChains.count() - 1); + int j = i + 1; + while (true) { + OpChain& candidate = fOpChains[j]; + if (candidate.prependChain(&chain, caps, fOpMemoryPool.get(), fAuditTrail)) { + break; + } + // Stop traversing if we would cause a painter's order violation. + if (!can_reorder(chain.bounds(), candidate.bounds())) { + GrOP_INFO( + "\t\t%d: chain (%s head opID: %u) -> " + "Intersects with chain (%s, head opID: %u)\n", + i, chain.head()->name(), chain.head()->uniqueID(), candidate.head()->name(), + candidate.head()->uniqueID()); + break; + } + if (++j > maxCandidateIdx) { + GrOP_INFO("\t\t%d: chain (%s opID: %u) -> Reached max lookahead or end of array\n", + i, chain.head()->name(), chain.head()->uniqueID()); + break; + } + } + } +} + +GrRenderTask::ExpectedOutcome GrOpsTask::onMakeClosed( + const GrCaps& caps, SkIRect* targetUpdateBounds) { + this->forwardCombine(caps); + if (!this->isNoOp()) { + SkRect clippedContentBounds = SkRect::MakeIWH(fTarget->width(), fTarget->height()); + // TODO: If we can fix up GLPrograms test to always intersect the fTarget bounds then we can + // simply assert here that the bounds intersect. + if (clippedContentBounds.intersect(fTotalBounds)) { + clippedContentBounds.roundOut(&fClippedContentBounds); + *targetUpdateBounds = fClippedContentBounds; + return ExpectedOutcome::kTargetDirty; + } + } + return ExpectedOutcome::kTargetUnchanged; +} diff --git a/gfx/skia/skia/src/gpu/GrOpsTask.h b/gfx/skia/skia/src/gpu/GrOpsTask.h new file mode 100644 index 0000000000..c2238120b6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrOpsTask.h @@ -0,0 +1,299 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOpsTask_DEFINED +#define GrOpsTask_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStrokeRec.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkClipStack.h" +#include "src/core/SkStringUtils.h" +#include "src/core/SkTLazy.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrPrimitiveProcessor.h" +#include "src/gpu/GrRenderTask.h" +#include "src/gpu/ops/GrDrawOp.h" +#include "src/gpu/ops/GrOp.h" + +class GrAuditTrail; +class GrCaps; +class GrClearOp; +class GrGpuBuffer; +class GrOpMemoryPool; +class GrRenderTargetProxy; + +class GrOpsTask : public GrRenderTask { +private: + using DstProxy = GrXferProcessor::DstProxy; + +public: + GrOpsTask(sk_sp, sk_sp, GrAuditTrail*); + ~GrOpsTask() override; + + GrOpsTask* asOpsTask() override { return this; } + + bool isEmpty() const { return fOpChains.empty(); } + + /** + * Empties the draw buffer of any queued up draws. + */ + void endFlush() override; + + void onPrePrepare(GrRecordingContext*) override; + /** + * Together these two functions flush all queued up draws to GrCommandBuffer. The return value + * of executeOps() indicates whether any commands were actually issued to the GPU. + */ + void onPrepare(GrOpFlushState* flushState) override; + bool onExecute(GrOpFlushState* flushState) override; + + void addSampledTexture(GrTextureProxy* proxy) { + fSampledProxies.push_back(proxy); + } + + void addOp(std::unique_ptr op, GrTextureResolveManager textureResolveManager, + const GrCaps& caps) { + auto addDependency = [ textureResolveManager, &caps, this ] ( + GrTextureProxy* p, GrMipMapped mipmapped) { + this->addDependency(p, mipmapped, textureResolveManager, caps); + }; + + op->visitProxies(addDependency); + + this->recordOp(std::move(op), GrProcessorSet::EmptySetAnalysis(), nullptr, nullptr, caps); + } + + void addWaitOp(std::unique_ptr op, GrTextureResolveManager textureResolveManager, + const GrCaps& caps) { + fHasWaitOp = true; + this->addOp(std::move(op), textureResolveManager, caps); + } + + void addDrawOp(std::unique_ptr op, const GrProcessorSet::Analysis& processorAnalysis, + GrAppliedClip&& clip, const DstProxy& dstProxy, + GrTextureResolveManager textureResolveManager, const GrCaps& caps) { + auto addDependency = [ textureResolveManager, &caps, this ] ( + GrTextureProxy* p, GrMipMapped mipmapped) { + this->addSampledTexture(p); + this->addDependency(p, mipmapped, textureResolveManager, caps); + }; + + op->visitProxies(addDependency); + clip.visitProxies(addDependency); + if (dstProxy.proxy()) { + this->addSampledTexture(dstProxy.proxy()); + addDependency(dstProxy.proxy(), GrMipMapped::kNo); + } + + this->recordOp(std::move(op), processorAnalysis, clip.doesClip() ? &clip : nullptr, + &dstProxy, caps); + } + + void discard(); + + SkDEBUGCODE(void dump(bool printDependencies) const override;) + SkDEBUGCODE(int numClips() const override { return fNumClips; }) + SkDEBUGCODE(void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const override;) + +private: + bool isNoOp() const { + // TODO: GrLoadOp::kDiscard (i.e., storing a discard) should also be grounds for skipping + // execution. We currently don't because of Vulkan. See http://skbug.com/9373. + // + // TODO: We should also consider stencil load/store here. We get away with it for now + // because we never discard stencil buffers. + return fOpChains.empty() && GrLoadOp::kLoad == fColorLoadOp; + } + + void deleteOps(); + + enum class StencilContent { + kDontCare, + kUserBitsCleared, // User bits: cleared + // Clip bit: don't care (Ganesh always pre-clears the clip bit.) + kPreserved + }; + + // Lets the caller specify what the content of the stencil buffer should be at the beginning + // of the render pass. + // + // When requesting kClear: Tilers will load the stencil buffer with a "clear" op; non-tilers + // will clear the stencil on first load, and then preserve it on subsequent loads. (Preserving + // works because renderTargetContexts are required to leave the user bits in a cleared state + // once finished.) + // + // NOTE: initialContent must not be kClear if caps.performStencilClearsAsDraws() is true. + void setInitialStencilContent(StencilContent initialContent) { + fInitialStencilContent = initialContent; + } + + // If a renderTargetContext splits its opsTask, it uses this method to guarantee stencil values + // get preserved across its split tasks. + void setMustPreserveStencil() { fMustPreserveStencil = true; } + + // Must only be called if native color buffer clearing is enabled. + void setColorLoadOp(GrLoadOp op, const SkPMColor4f& color); + // Sets the clear color to transparent black + void setColorLoadOp(GrLoadOp op) { + static const SkPMColor4f kDefaultClearColor = {0.f, 0.f, 0.f, 0.f}; + this->setColorLoadOp(op, kDefaultClearColor); + } + + enum class CanDiscardPreviousOps : bool { + kYes = true, + kNo = false + }; + + // Perform book-keeping for a fullscreen clear, regardless of how the clear is implemented later + // (i.e. setColorLoadOp(), adding a ClearOp, or adding a GrFillRectOp that covers the device). + // Returns true if the clear can be converted into a load op (barring device caps). + bool resetForFullscreenClear(CanDiscardPreviousOps); + + class OpChain { + public: + OpChain(const OpChain&) = delete; + OpChain& operator=(const OpChain&) = delete; + OpChain(std::unique_ptr, GrProcessorSet::Analysis, GrAppliedClip*, const DstProxy*); + + ~OpChain() { + // The ops are stored in a GrMemoryPool and must be explicitly deleted via the pool. + SkASSERT(fList.empty()); + } + + void visitProxies(const GrOp::VisitProxyFunc&) const; + + GrOp* head() const { return fList.head(); } + + GrAppliedClip* appliedClip() const { return fAppliedClip; } + const DstProxy& dstProxy() const { return fDstProxy; } + const SkRect& bounds() const { return fBounds; } + + // Deletes all the ops in the chain via the pool. + void deleteOps(GrOpMemoryPool* pool); + + // Attempts to move the ops from the passed chain to this chain at the head. Also attempts + // to merge ops between the chains. Upon success the passed chain is empty. + // Fails when the chains aren't of the same op type, have different clips or dst proxies. + bool prependChain(OpChain*, const GrCaps&, GrOpMemoryPool*, GrAuditTrail*); + + // Attempts to add 'op' to this chain either by merging or adding to the tail. Returns + // 'op' to the caller upon failure, otherwise null. Fails when the op and chain aren't of + // the same op type, have different clips or dst proxies. + std::unique_ptr appendOp(std::unique_ptr op, GrProcessorSet::Analysis, + const DstProxy*, const GrAppliedClip*, const GrCaps&, + GrOpMemoryPool*, GrAuditTrail*); + + void setSkipExecuteFlag() { fSkipExecute = true; } + bool shouldExecute() const { + return SkToBool(this->head()) && !fSkipExecute; + } + + private: + class List { + public: + List() = default; + List(std::unique_ptr); + List(List&&); + List& operator=(List&& that); + + bool empty() const { return !SkToBool(fHead); } + GrOp* head() const { return fHead.get(); } + GrOp* tail() const { return fTail; } + + std::unique_ptr popHead(); + std::unique_ptr removeOp(GrOp* op); + void pushHead(std::unique_ptr op); + void pushTail(std::unique_ptr); + + void validate() const; + + private: + std::unique_ptr fHead; + GrOp* fTail = nullptr; + }; + + void validate() const; + + bool tryConcat(List*, GrProcessorSet::Analysis, const DstProxy&, const GrAppliedClip*, + const SkRect& bounds, const GrCaps&, GrOpMemoryPool*, GrAuditTrail*); + static List DoConcat(List, List, const GrCaps&, GrOpMemoryPool*, GrAuditTrail*); + + List fList; + GrProcessorSet::Analysis fProcessorAnalysis; + DstProxy fDstProxy; + GrAppliedClip* fAppliedClip; + SkRect fBounds; + + // We set this flag to true if any of the ops' proxies fail to instantiate so that we know + // not to try and draw the op. + bool fSkipExecute = false; + }; + + + bool onIsUsed(GrSurfaceProxy*) const override; + + void handleInternalAllocationFailure() override; + + void gatherProxyIntervals(GrResourceAllocator*) const override; + + void recordOp(std::unique_ptr, GrProcessorSet::Analysis, GrAppliedClip*, const DstProxy*, + const GrCaps& caps); + + void forwardCombine(const GrCaps&); + + ExpectedOutcome onMakeClosed(const GrCaps& caps, SkIRect* targetUpdateBounds) override; + + friend class GrRenderTargetContextPriv; // for stencil clip state. TODO: this is invasive + + // The RTC and OpsTask have to work together to handle buffer clears. In most cases, buffer + // clearing can be done natively, in which case the op list's load ops are sufficient. In other + // cases, draw ops must be used, which makes the RTC the best place for those decisions. This, + // however, requires that the RTC be able to coordinate with the op list to achieve similar ends + friend class GrRenderTargetContext; + + // This is a backpointer to the GrOpMemoryPool that holds the memory for this GrOpsTask's ops. + // In the DDL case, these back pointers keep the DDL's GrOpMemoryPool alive as long as its + // constituent GrOpsTask survives. + sk_sp fOpMemoryPool; + GrAuditTrail* fAuditTrail; + + GrLoadOp fColorLoadOp = GrLoadOp::kLoad; + SkPMColor4f fLoadClearColor = SK_PMColor4fTRANSPARENT; + StencilContent fInitialStencilContent = StencilContent::kDontCare; + bool fMustPreserveStencil = false; + + uint32_t fLastClipStackGenID; + SkIRect fLastDevClipBounds; + int fLastClipNumAnalyticFPs; + + // We must track if we have a wait op so that we don't delete the op when we have a full clear. + bool fHasWaitOp = false;; + + // For ops/opsTask we have mean: 5 stdDev: 28 + SkSTArray<25, OpChain, true> fOpChains; + + // MDB TODO: 4096 for the first allocation of the clip space will be huge overkill. + // Gather statistics to determine the correct size. + SkArenaAlloc fClipAllocator{4096}; + SkDEBUGCODE(int fNumClips;) + + // TODO: We could look into this being a set if we find we're adding a lot of duplicates that is + // causing slow downs. + SkTArray fSampledProxies; + + SkRect fTotalBounds = SkRect::MakeEmpty(); + SkIRect fClippedContentBounds = SkIRect::MakeEmpty(); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPaint.cpp b/gfx/skia/skia/src/gpu/GrPaint.cpp new file mode 100644 index 0000000000..bc7f5384a8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPaint.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/effects/GrCoverageSetOpXP.h" +#include "src/gpu/effects/GrPorterDuffXferProcessor.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" + +GrPaint::GrPaint(const GrPaint& that) + : fXPFactory(that.fXPFactory) + , fColorFragmentProcessors(that.fColorFragmentProcessors.count()) + , fCoverageFragmentProcessors(that.fCoverageFragmentProcessors.count()) + , fTrivial(that.fTrivial) + , fColor(that.fColor) { + for (int i = 0; i < that.fColorFragmentProcessors.count(); ++i) { + fColorFragmentProcessors.push_back(that.fColorFragmentProcessors[i]->clone()); + SkASSERT(fColorFragmentProcessors[i]); + } + for (int i = 0; i < that.fCoverageFragmentProcessors.count(); ++i) { + fCoverageFragmentProcessors.push_back(that.fCoverageFragmentProcessors[i]->clone()); + SkASSERT(fCoverageFragmentProcessors[i]); + } +} + +void GrPaint::setPorterDuffXPFactory(SkBlendMode mode) { + this->setXPFactory(GrPorterDuffXPFactory::Get(mode)); +} + +void GrPaint::setCoverageSetOpXPFactory(SkRegion::Op regionOp, bool invertCoverage) { + this->setXPFactory(GrCoverageSetOpXPFactory::Get(regionOp, invertCoverage)); +} + +void GrPaint::addColorTextureProcessor(sk_sp proxy, GrColorType srcColorType, + const SkMatrix& matrix) { + this->addColorFragmentProcessor(GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, + matrix)); +} + +void GrPaint::addColorTextureProcessor(sk_sp proxy, GrColorType srcColorType, + const SkMatrix& matrix, const GrSamplerState& samplerState) { + this->addColorFragmentProcessor(GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, + matrix, samplerState)); +} + +bool GrPaint::isConstantBlendedColor(SkPMColor4f* constantColor) const { + // This used to do a more sophisticated analysis but now it just explicitly looks for common + // cases. + static const GrXPFactory* kSrc = GrPorterDuffXPFactory::Get(SkBlendMode::kSrc); + static const GrXPFactory* kClear = GrPorterDuffXPFactory::Get(SkBlendMode::kClear); + if (kClear == fXPFactory) { + *constantColor = SK_PMColor4fTRANSPARENT; + return true; + } + if (this->numColorFragmentProcessors()) { + return false; + } + if (kSrc == fXPFactory || (!fXPFactory && fColor.isOpaque())) { + *constantColor = fColor; + return true; + } + return false; +} diff --git a/gfx/skia/skia/src/gpu/GrPaint.h b/gfx/skia/skia/src/gpu/GrPaint.h new file mode 100644 index 0000000000..c2520b0eaf --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPaint.h @@ -0,0 +1,136 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrPaint_DEFINED +#define GrPaint_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkRegion.h" +#include "src/core/SkTLazy.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrFragmentProcessor.h" + +class GrTextureProxy; +class GrXPFactory; + +/** + * The paint describes how color and coverage are computed at each pixel by GrContext draw + * functions and the how color is blended with the destination pixel. + * + * The paint allows installation of custom color and coverage stages. New types of stages are + * created by subclassing GrProcessor. + * + * The primitive color computation starts with the color specified by setColor(). This color is the + * input to the first color stage. Each color stage feeds its output to the next color stage. + * + * Fractional pixel coverage follows a similar flow. The GrGeometryProcessor (specified elsewhere) + * provides the initial coverage which is passed to the first coverage fragment processor, which + * feeds its output to next coverage fragment processor. + * + * setXPFactory is used to control blending between the output color and dest. It also implements + * the application of fractional coverage from the coverage pipeline. + */ +class GrPaint { +public: + GrPaint() = default; + ~GrPaint() = default; + + static GrPaint Clone(const GrPaint& src) { return GrPaint(src); } + + /** + * The initial color of the drawn primitive. Defaults to solid white. + */ + void setColor4f(const SkPMColor4f& color) { fColor = color; } + const SkPMColor4f& getColor4f() const { return fColor; } + + void setXPFactory(const GrXPFactory* xpFactory) { + fXPFactory = xpFactory; + fTrivial &= !SkToBool(xpFactory); + } + + void setPorterDuffXPFactory(SkBlendMode mode); + + void setCoverageSetOpXPFactory(SkRegion::Op, bool invertCoverage = false); + + /** + * Appends an additional color processor to the color computation. + */ + void addColorFragmentProcessor(std::unique_ptr fp) { + SkASSERT(fp); + fColorFragmentProcessors.push_back(std::move(fp)); + fTrivial = false; + } + + /** + * Appends an additional coverage processor to the coverage computation. + */ + void addCoverageFragmentProcessor(std::unique_ptr fp) { + SkASSERT(fp); + fCoverageFragmentProcessors.push_back(std::move(fp)); + fTrivial = false; + } + + /** + * Helpers for adding color or coverage effects that sample a texture. The matrix is applied + * to the src space position to compute texture coordinates. + */ + void addColorTextureProcessor(sk_sp, GrColorType srcColorType, const SkMatrix&); + void addColorTextureProcessor(sk_sp, GrColorType srcColorType, const SkMatrix&, + const GrSamplerState&); + + int numColorFragmentProcessors() const { return fColorFragmentProcessors.count(); } + int numCoverageFragmentProcessors() const { return fCoverageFragmentProcessors.count(); } + int numTotalFragmentProcessors() const { return this->numColorFragmentProcessors() + + this->numCoverageFragmentProcessors(); } + + const GrXPFactory* getXPFactory() const { return fXPFactory; } + + GrFragmentProcessor* getColorFragmentProcessor(int i) const { + return fColorFragmentProcessors[i].get(); + } + GrFragmentProcessor* getCoverageFragmentProcessor(int i) const { + return fCoverageFragmentProcessors[i].get(); + } + + /** + * Returns true if the paint's output color will be constant after blending. If the result is + * true, constantColor will be updated to contain the constant color. Note that we can conflate + * coverage and color, so the actual values written to pixels with partial coverage may still + * not seem constant, even if this function returns true. + */ + bool isConstantBlendedColor(SkPMColor4f* constantColor) const; + + /** + * A trivial paint is one that uses src-over and has no fragment processors. + * It may have variable sRGB settings. + **/ + bool isTrivial() const { return fTrivial; } + + friend void assert_alive(GrPaint& p) { + SkASSERT(p.fAlive); + } + +private: + // Since paint copying is expensive if there are fragment processors, we require going through + // the Clone() method. + GrPaint(const GrPaint&); + GrPaint& operator=(const GrPaint&) = delete; + + friend class GrProcessorSet; + + const GrXPFactory* fXPFactory = nullptr; + SkSTArray<4, std::unique_ptr> fColorFragmentProcessors; + SkSTArray<2, std::unique_ptr> fCoverageFragmentProcessors; + bool fTrivial = true; + SkPMColor4f fColor = SK_PMColor4fWHITE; + SkDEBUGCODE(bool fAlive = true;) // Set false after moved from. +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPath.cpp b/gfx/skia/skia/src/gpu/GrPath.cpp new file mode 100644 index 0000000000..625f8c4722 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPath.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrPath.h" +#include "src/gpu/geometry/GrShape.h" + +static inline void write_style_key(uint32_t* key, const GrStyle& style) { + // Pass 1 for the scale since the GPU will apply the style not GrStyle::applyToPath(). + GrStyle::WriteKey(key, style, GrStyle::Apply::kPathEffectAndStrokeRec, SK_Scalar1); +} + + +void GrPath::ComputeKey(const GrShape& shape, GrUniqueKey* key, bool* outIsVolatile) { + int geoCnt = shape.unstyledKeySize(); + int styleCnt = GrStyle::KeySize(shape.style(), GrStyle::Apply::kPathEffectAndStrokeRec); + // This should only fail for an arbitrary path effect, and we should not have gotten + // here with anything other than a dash path effect. + SkASSERT(styleCnt >= 0); + if (geoCnt < 0) { + *outIsVolatile = true; + return; + } + static const GrUniqueKey::Domain kGeneralPathDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(key, kGeneralPathDomain, geoCnt + styleCnt, "Path"); + shape.writeUnstyledKey(&builder[0]); + if (styleCnt) { + write_style_key(&builder[geoCnt], shape.style()); + } + *outIsVolatile = false; +} + +#ifdef SK_DEBUG +bool GrPath::isEqualTo(const SkPath& path, const GrStyle& style) const { + // Since this is only called in debug we don't care about performance. + int cnt0 = GrStyle::KeySize(fStyle, GrStyle::Apply::kPathEffectAndStrokeRec); + int cnt1 = GrStyle::KeySize(style, GrStyle::Apply::kPathEffectAndStrokeRec); + if (cnt0 < 0 || cnt1 < 0 || cnt0 != cnt1) { + return false; + } + if (cnt0) { + SkAutoTArray key0(cnt0); + SkAutoTArray key1(cnt0); + write_style_key(key0.get(), fStyle); + write_style_key(key1.get(), style); + if (0 != memcmp(key0.get(), key1.get(), cnt0)) { + return false; + } + } + return fSkPath == path; +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrPath.h b/gfx/skia/skia/src/gpu/GrPath.h new file mode 100644 index 0000000000..6b43af5d10 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPath.h @@ -0,0 +1,58 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPath_DEFINED +#define GrPath_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkRect.h" +#include "include/gpu/GrGpuResource.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrStyle.h" + +class GrShape; + +class GrPath : public GrGpuResource { +public: + /** + * Initialize to a path with a fixed stroke. Stroke must not be hairline. + */ + GrPath(GrGpu* gpu, const SkPath& skPath, const GrStyle& style) + : INHERITED(gpu) + , fBounds(SkRect::MakeEmpty()) + , fFillType(GrPathRendering::kWinding_FillType) +#ifdef SK_DEBUG + , fSkPath(skPath) + , fStyle(style) +#endif + { + } + + static void ComputeKey(const GrShape&, GrUniqueKey* key, bool* outIsVolatile); + + const SkRect& getBounds() const { return fBounds; } + + GrPathRendering::FillType getFillType() const { return fFillType; } +#ifdef SK_DEBUG + bool isEqualTo(const SkPath& path, const GrStyle& style) const; +#endif + +protected: + // Subclass should init these. + SkRect fBounds; + GrPathRendering::FillType fFillType; +#ifdef SK_DEBUG + SkPath fSkPath; + GrStyle fStyle; +#endif + +private: + const char* getResourceType() const override { return "Path Data"; } + typedef GrGpuResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPathProcessor.cpp b/gfx/skia/skia/src/gpu/GrPathProcessor.cpp new file mode 100644 index 0000000000..861ff1bd3c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathProcessor.cpp @@ -0,0 +1,141 @@ +/* +* Copyright 2013 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/GrPathProcessor.h" + +#include "include/private/SkTo.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLVaryingHandler.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +class GrGLPathProcessor : public GrGLSLPrimitiveProcessor { +public: + GrGLPathProcessor() : fColor(SK_PMColor4fILLEGAL) {} + + static void GenKey(const GrPathProcessor& pathProc, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + b->add32(SkToInt(pathProc.viewMatrix().hasPerspective())); + } + + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrPathProcessor& pathProc = args.fGP.cast(); + + if (!pathProc.viewMatrix().hasPerspective()) { + args.fVaryingHandler->setNoPerspective(); + } + + // emit transforms + this->emitTransforms(args.fVaryingHandler, args.fFPCoordTransformHandler); + + // Setup uniform color + const char* stagedLocalVarName; + fColorUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, + "Color", + &stagedLocalVarName); + fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, stagedLocalVarName); + + // setup constant solid coverage + fragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage); + } + + SkString matrix_to_sksl(const SkMatrix& m) { + return SkStringPrintf("float3x3(%f, %f, %f, %f, %f, %f, %f, %f, %f)", m[0], m[1], m[2], + m[3], m[4], m[5], m[6], m[7], m[8]); + } + + void emitTransforms(GrGLSLVaryingHandler* varyingHandler, + FPCoordTransformHandler* transformHandler) { + int i = 0; + while (const GrCoordTransform* coordTransform = transformHandler->nextCoordTransform()) { + GrSLType varyingType = + coordTransform->getMatrix().hasPerspective() ? kHalf3_GrSLType + : kHalf2_GrSLType; + + SkString strVaryingName; + strVaryingName.printf("TransformedCoord_%d", i); + GrGLSLVarying v(varyingType); + GrGLVaryingHandler* glVaryingHandler = (GrGLVaryingHandler*) varyingHandler; + fInstalledTransforms.push_back().fHandle = + glVaryingHandler->addPathProcessingVarying(strVaryingName.c_str(), + &v).toIndex(); + fInstalledTransforms.back().fType = varyingType; + + transformHandler->specifyCoordsForCurrCoordTransform( + matrix_to_sksl(coordTransform->getMatrix()), + UniformHandle(), + GrShaderVar(SkString(v.fsIn()), + varyingType)); + ++i; + } + } + + void setData(const GrGLSLProgramDataManager& pd, + const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + const GrPathProcessor& pathProc = primProc.cast(); + if (pathProc.color() != fColor) { + pd.set4fv(fColorUniform, 1, pathProc.color().vec()); + fColor = pathProc.color(); + } + + int t = 0; + while (const GrCoordTransform* coordTransform = transformIter.next()) { + SkASSERT(fInstalledTransforms[t].fHandle.isValid()); + const SkMatrix& m = GetTransformMatrix(pathProc.localMatrix(), *coordTransform); + if (fInstalledTransforms[t].fCurrentValue.cheapEqualTo(m)) { + continue; + } + fInstalledTransforms[t].fCurrentValue = m; + + SkASSERT(fInstalledTransforms[t].fType == kHalf2_GrSLType || + fInstalledTransforms[t].fType == kHalf3_GrSLType); + unsigned components = fInstalledTransforms[t].fType == kHalf2_GrSLType ? 2 : 3; + pd.setPathFragmentInputTransform(fInstalledTransforms[t].fHandle, components, m); + ++t; + } + } + +private: + typedef GrGLSLProgramDataManager::VaryingHandle VaryingHandle; + struct TransformVarying { + VaryingHandle fHandle; + SkMatrix fCurrentValue = SkMatrix::InvalidMatrix(); + GrSLType fType = kVoid_GrSLType; + }; + + SkTArray fInstalledTransforms; + + UniformHandle fColorUniform; + SkPMColor4f fColor; + + typedef GrGLSLPrimitiveProcessor INHERITED; +}; + +GrPathProcessor::GrPathProcessor(const SkPMColor4f& color, + const SkMatrix& viewMatrix, + const SkMatrix& localMatrix) + : INHERITED(kGrPathProcessor_ClassID) + , fColor(color) + , fViewMatrix(viewMatrix) + , fLocalMatrix(localMatrix) {} + +void GrPathProcessor::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLPathProcessor::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* GrPathProcessor::createGLSLInstance(const GrShaderCaps& caps) const { + SkASSERT(caps.pathRenderingSupport()); + return new GrGLPathProcessor(); +} diff --git a/gfx/skia/skia/src/gpu/GrPathProcessor.h b/gfx/skia/skia/src/gpu/GrPathProcessor.h new file mode 100644 index 0000000000..aee919f8b2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathProcessor.h @@ -0,0 +1,50 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPathProcessor_DEFINED +#define GrPathProcessor_DEFINED + +#include "src/gpu/GrPrimitiveProcessor.h" + +/* + * The path equivalent of the GP. For now this just manages color. In the long term we plan on + * extending this class to handle all nvpr uniform / varying / program work. + */ +class GrPathProcessor : public GrPrimitiveProcessor { +public: + static GrPathProcessor* Create(const SkPMColor4f& color, + const SkMatrix& viewMatrix = SkMatrix::I(), + const SkMatrix& localMatrix = SkMatrix::I()) { + return new GrPathProcessor(color, viewMatrix, localMatrix); + } + + const char* name() const override { return "PathProcessor"; } + + const SkPMColor4f& color() const { return fColor; } + const SkMatrix& viewMatrix() const { return fViewMatrix; } + const SkMatrix& localMatrix() const { return fLocalMatrix; } + + bool willUseGeoShader() const override { return false; } + + virtual void getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override; + + virtual bool isPathRendering() const override { return true; } + +private: + GrPathProcessor(const SkPMColor4f&, const SkMatrix& viewMatrix, const SkMatrix& localMatrix); + + SkPMColor4f fColor; + const SkMatrix fViewMatrix; + const SkMatrix fLocalMatrix; + + typedef GrPrimitiveProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPathRenderer.cpp b/gfx/skia/skia/src/gpu/GrPathRenderer.cpp new file mode 100644 index 0000000000..16d98db460 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRenderer.cpp @@ -0,0 +1,120 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDrawProcs.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrUserStencilSettings.h" +#include "src/gpu/geometry/GrShape.h" + +#ifdef SK_DEBUG +void GrPathRenderer::StencilPathArgs::validate() const { + SkASSERT(fContext); + SkASSERT(fRenderTargetContext); + SkASSERT(fClipConservativeBounds); + SkASSERT(fViewMatrix); + SkASSERT(fShape); + SkASSERT(fShape->style().isSimpleFill()); + SkPath path; + fShape->asPath(&path); + SkASSERT(!path.isInverseFillType()); +} +#endif + +////////////////////////////////////////////////////////////////////////////// + +GrPathRenderer::GrPathRenderer() {} + +GrPathRenderer::StencilSupport GrPathRenderer::getStencilSupport(const GrShape& shape) const { + SkDEBUGCODE(SkPath path;) + SkDEBUGCODE(shape.asPath(&path);) + SkASSERT(shape.style().isSimpleFill()); + SkASSERT(!path.isInverseFillType()); + return this->onGetStencilSupport(shape); +} + +bool GrPathRenderer::drawPath(const DrawPathArgs& args) { +#ifdef SK_DEBUG + args.validate(); + CanDrawPathArgs canArgs; + canArgs.fCaps = args.fContext->priv().caps(); + canArgs.fProxy = args.fRenderTargetContext->proxy(); + canArgs.fClipConservativeBounds = args.fClipConservativeBounds; + canArgs.fViewMatrix = args.fViewMatrix; + canArgs.fShape = args.fShape; + canArgs.fAAType = args.fAAType; + canArgs.fTargetIsWrappedVkSecondaryCB = args.fRenderTargetContext->wrapsVkSecondaryCB(); + canArgs.validate(); + + canArgs.fHasUserStencilSettings = !args.fUserStencilSettings->isUnused(); + SkASSERT(CanDrawPath::kNo != this->canDrawPath(canArgs)); + if (!args.fUserStencilSettings->isUnused()) { + SkPath path; + args.fShape->asPath(&path); + SkASSERT(args.fShape->style().isSimpleFill()); + SkASSERT(kNoRestriction_StencilSupport == this->getStencilSupport(*args.fShape)); + } +#endif + return this->onDrawPath(args); +} + +bool GrPathRenderer::IsStrokeHairlineOrEquivalent(const GrStyle& style, const SkMatrix& matrix, + SkScalar* outCoverage) { + if (style.pathEffect()) { + return false; + } + const SkStrokeRec& stroke = style.strokeRec(); + if (stroke.isHairlineStyle()) { + if (outCoverage) { + *outCoverage = SK_Scalar1; + } + return true; + } + return stroke.getStyle() == SkStrokeRec::kStroke_Style && + SkDrawTreatAAStrokeAsHairline(stroke.getWidth(), matrix, outCoverage); +} + + +void GrPathRenderer::GetPathDevBounds(const SkPath& path, + int devW, int devH, + const SkMatrix& matrix, + SkRect* bounds) { + if (path.isInverseFillType()) { + *bounds = SkRect::MakeWH(SkIntToScalar(devW), SkIntToScalar(devH)); + return; + } + *bounds = path.getBounds(); + matrix.mapRect(bounds); +} + +void GrPathRenderer::onStencilPath(const StencilPathArgs& args) { + static constexpr GrUserStencilSettings kIncrementStencil( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kReplace, + GrUserStencilOp::kReplace, + 0xffff>() + ); + + GrPaint paint; + DrawPathArgs drawArgs{args.fContext, + std::move(paint), + &kIncrementStencil, + args.fRenderTargetContext, + nullptr, // clip + args.fClipConservativeBounds, + args.fViewMatrix, + args.fShape, + (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone, + false}; + this->drawPath(drawArgs); +} diff --git a/gfx/skia/skia/src/gpu/GrPathRenderer.h b/gfx/skia/skia/src/gpu/GrPathRenderer.h new file mode 100644 index 0000000000..a1d074154a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRenderer.h @@ -0,0 +1,208 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPathRenderer_DEFINED +#define GrPathRenderer_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/GrTypesPriv.h" +#include "include/private/SkTArray.h" + +class GrCaps; +class GrClip; +class GrFixedClip; +class GrHardClip; +class GrPaint; +class GrRecordingContext; +class GrRenderTargetContext; +class GrRenderTargetProxy; +class GrShape; +class GrStyle; +struct GrUserStencilSettings; +struct SkIRect; +class SkMatrix; +class SkPath; + +/** + * Base class for drawing paths into a GrOpsTask. + */ +class GrPathRenderer : public SkRefCnt { +public: + GrPathRenderer(); + + /** + * A caller may wish to use a path renderer to draw a path into the stencil buffer. However, + * the path renderer itself may require use of the stencil buffer. Also a path renderer may + * use a GrProcessor coverage stage that sets coverage to zero to eliminate pixels that are + * covered by bounding geometry but outside the path. These exterior pixels would still be + * rendered into the stencil. + * + * A GrPathRenderer can provide three levels of support for stenciling paths: + * 1) kNoRestriction: This is the most general. The caller passes a GrPaint and calls drawPath(). + * The path is rendered exactly as the draw state indicates including support + * for simultaneous color and stenciling with arbitrary stenciling rules. + * Pixels partially covered by AA paths are affected by the stencil settings. + * 2) kStencilOnly: The path renderer cannot apply arbitrary stencil rules nor shade and stencil + * simultaneously. The path renderer does support the stencilPath() function + * which performs no color writes and writes a non-zero stencil value to pixels + * covered by the path. + * 3) kNoSupport: This path renderer cannot be used to stencil the path. + */ + enum StencilSupport { + kNoSupport_StencilSupport, + kStencilOnly_StencilSupport, + kNoRestriction_StencilSupport, + }; + + /** + * This function is to get the stencil support for a particular path. The path's fill must + * not be an inverse type. The path will always be filled and not stroked. + * + * @param shape the shape that will be drawn. Must be simple fill styled and non-inverse + * filled. + */ + StencilSupport getStencilSupport(const GrShape& shape) const; + + enum class CanDrawPath { + kNo, + kAsBackup, // i.e. This renderer is better than SW fallback if no others can draw the path. + kYes + }; + + struct CanDrawPathArgs { + SkDEBUGCODE(CanDrawPathArgs() { memset(this, 0, sizeof(*this)); }) // For validation. + + const GrCaps* fCaps; + const GrRenderTargetProxy* fProxy; + const SkIRect* fClipConservativeBounds; + const SkMatrix* fViewMatrix; + const GrShape* fShape; + GrAAType fAAType; + bool fTargetIsWrappedVkSecondaryCB; + + // This is only used by GrStencilAndCoverPathRenderer + bool fHasUserStencilSettings; + +#ifdef SK_DEBUG + void validate() const { + SkASSERT(fCaps); + SkASSERT(fProxy); + SkASSERT(fClipConservativeBounds); + SkASSERT(fViewMatrix); + SkASSERT(fShape); + } +#endif + }; + + /** + * Returns how well this path renderer is able to render the given path. Returning kNo or + * kAsBackup allows the caller to keep searching for a better path renderer. This function is + * called when searching for the best path renderer to draw a path. + */ + CanDrawPath canDrawPath(const CanDrawPathArgs& args) const { + SkDEBUGCODE(args.validate();) + return this->onCanDrawPath(args); + } + + struct DrawPathArgs { + GrRecordingContext* fContext; + GrPaint&& fPaint; + const GrUserStencilSettings* fUserStencilSettings; + GrRenderTargetContext* fRenderTargetContext; + const GrClip* fClip; + const SkIRect* fClipConservativeBounds; + const SkMatrix* fViewMatrix; + const GrShape* fShape; + GrAAType fAAType; + bool fGammaCorrect; +#ifdef SK_DEBUG + void validate() const { + SkASSERT(fContext); + SkASSERT(fUserStencilSettings); + SkASSERT(fRenderTargetContext); + SkASSERT(fClip); + SkASSERT(fClipConservativeBounds); + SkASSERT(fViewMatrix); + SkASSERT(fShape); + } +#endif + }; + + /** + * Draws the path into the draw target. If getStencilSupport() would return kNoRestriction then + * the subclass must respect the stencil settings. + */ + bool drawPath(const DrawPathArgs& args); + /** + * Args to stencilPath(). fAAType cannot be kCoverage. + */ + struct StencilPathArgs { + SkDEBUGCODE(StencilPathArgs() { memset(this, 0, sizeof(*this)); }) // For validation. + + GrRecordingContext* fContext; + GrRenderTargetContext* fRenderTargetContext; + const GrHardClip* fClip; + const SkIRect* fClipConservativeBounds; + const SkMatrix* fViewMatrix; + const GrShape* fShape; + GrAA fDoStencilMSAA; + + SkDEBUGCODE(void validate() const); + }; + + /** + * Draws the path to the stencil buffer. Assume the writable stencil bits are already + * initialized to zero. The pixels inside the path will have non-zero stencil values afterwards. + */ + void stencilPath(const StencilPathArgs& args) { + SkDEBUGCODE(args.validate();) + SkASSERT(kNoSupport_StencilSupport != this->getStencilSupport(*args.fShape)); + this->onStencilPath(args); + } + + // Helper for determining if we can treat a thin stroke as a hairline w/ coverage. + // If we can, we draw lots faster (raster device does this same test). + static bool IsStrokeHairlineOrEquivalent(const GrStyle&, const SkMatrix&, + SkScalar* outCoverage); + +protected: + // Helper for getting the device bounds of a path. Inverse filled paths will have bounds set + // by devSize. Non-inverse path bounds will not necessarily be clipped to devSize. + static void GetPathDevBounds(const SkPath& path, + int devW, + int devH, + const SkMatrix& matrix, + SkRect* bounds); + +private: + /** + * Subclass overrides if it has any limitations of stenciling support. + */ + virtual StencilSupport onGetStencilSupport(const GrShape&) const { + return kNoRestriction_StencilSupport; + } + + /** + * Subclass implementation of drawPath() + */ + virtual bool onDrawPath(const DrawPathArgs& args) = 0; + + /** + * Subclass implementation of canDrawPath() + */ + virtual CanDrawPath onCanDrawPath(const CanDrawPathArgs& args) const = 0; + + /** + * Subclass implementation of stencilPath(). Subclass must override iff it ever returns + * kStencilOnly in onGetStencilSupport(). + */ + virtual void onStencilPath(const StencilPathArgs&); + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPathRendererChain.cpp b/gfx/skia/skia/src/gpu/GrPathRendererChain.cpp new file mode 100644 index 0000000000..e21d1bc148 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRendererChain.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/GrPathRendererChain.h" + +#include "include/gpu/GrContext.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" +#include "src/gpu/ops/GrAAConvexPathRenderer.h" +#include "src/gpu/ops/GrAAHairLinePathRenderer.h" +#include "src/gpu/ops/GrAALinearizingConvexPathRenderer.h" +#include "src/gpu/ops/GrDashLinePathRenderer.h" +#include "src/gpu/ops/GrDefaultPathRenderer.h" +#include "src/gpu/ops/GrSmallPathRenderer.h" +#include "src/gpu/ops/GrStencilAndCoverPathRenderer.h" +#include "src/gpu/ops/GrTessellatingPathRenderer.h" + +GrPathRendererChain::GrPathRendererChain(GrRecordingContext* context, const Options& options) { + const GrCaps& caps = *context->priv().caps(); + if (options.fGpuPathRenderers & GpuPathRenderers::kDashLine) { + fChain.push_back(sk_make_sp()); + } + if (options.fGpuPathRenderers & GpuPathRenderers::kAAConvex) { + fChain.push_back(sk_make_sp()); + } + if (options.fGpuPathRenderers & GpuPathRenderers::kCoverageCounting) { + using AllowCaching = GrCoverageCountingPathRenderer::AllowCaching; + if (auto ccpr = GrCoverageCountingPathRenderer::CreateIfSupported( + caps, AllowCaching(options.fAllowPathMaskCaching), + context->priv().contextID())) { + fCoverageCountingPathRenderer = ccpr.get(); + context->priv().addOnFlushCallbackObject(fCoverageCountingPathRenderer); + fChain.push_back(std::move(ccpr)); + } + } + if (options.fGpuPathRenderers & GpuPathRenderers::kAAHairline) { + fChain.push_back(sk_make_sp()); + } + if (options.fGpuPathRenderers & GpuPathRenderers::kAALinearizing) { + fChain.push_back(sk_make_sp()); + } + if (options.fGpuPathRenderers & GpuPathRenderers::kSmall) { + auto spr = sk_make_sp(); + context->priv().addOnFlushCallbackObject(spr.get()); + fChain.push_back(std::move(spr)); + } + if (options.fGpuPathRenderers & GpuPathRenderers::kStencilAndCover) { + auto direct = context->priv().asDirectContext(); + if (direct) { + auto resourceProvider = direct->priv().resourceProvider(); + + sk_sp pr( + GrStencilAndCoverPathRenderer::Create(resourceProvider, caps)); + if (pr) { + fChain.push_back(std::move(pr)); + } + } + } + if (options.fGpuPathRenderers & GpuPathRenderers::kTessellating) { + fChain.push_back(sk_make_sp()); + } + + // We always include the default path renderer (as well as SW), so we can draw any path + fChain.push_back(sk_make_sp()); +} + +GrPathRenderer* GrPathRendererChain::getPathRenderer( + const GrPathRenderer::CanDrawPathArgs& args, + DrawType drawType, + GrPathRenderer::StencilSupport* stencilSupport) { + GR_STATIC_ASSERT(GrPathRenderer::kNoSupport_StencilSupport < + GrPathRenderer::kStencilOnly_StencilSupport); + GR_STATIC_ASSERT(GrPathRenderer::kStencilOnly_StencilSupport < + GrPathRenderer::kNoRestriction_StencilSupport); + GrPathRenderer::StencilSupport minStencilSupport; + if (DrawType::kStencil == drawType) { + minStencilSupport = GrPathRenderer::kStencilOnly_StencilSupport; + } else if (DrawType::kStencilAndColor == drawType) { + minStencilSupport = GrPathRenderer::kNoRestriction_StencilSupport; + } else { + minStencilSupport = GrPathRenderer::kNoSupport_StencilSupport; + } + if (minStencilSupport != GrPathRenderer::kNoSupport_StencilSupport) { + // We don't support (and shouldn't need) stenciling of non-fill paths. + if (!args.fShape->style().isSimpleFill()) { + return nullptr; + } + } + + GrPathRenderer* bestPathRenderer = nullptr; + for (const sk_sp& pr : fChain) { + GrPathRenderer::StencilSupport support = GrPathRenderer::kNoSupport_StencilSupport; + if (GrPathRenderer::kNoSupport_StencilSupport != minStencilSupport) { + support = pr->getStencilSupport(*args.fShape); + if (support < minStencilSupport) { + continue; + } + } + GrPathRenderer::CanDrawPath canDrawPath = pr->canDrawPath(args); + if (GrPathRenderer::CanDrawPath::kNo == canDrawPath) { + continue; + } + if (GrPathRenderer::CanDrawPath::kAsBackup == canDrawPath && bestPathRenderer) { + continue; + } + if (stencilSupport) { + *stencilSupport = support; + } + bestPathRenderer = pr.get(); + if (GrPathRenderer::CanDrawPath::kYes == canDrawPath) { + break; + } + } + return bestPathRenderer; +} diff --git a/gfx/skia/skia/src/gpu/GrPathRendererChain.h b/gfx/skia/skia/src/gpu/GrPathRendererChain.h new file mode 100644 index 0000000000..3bd955c726 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRendererChain.h @@ -0,0 +1,65 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPathRendererChain_DEFINED +#define GrPathRendererChain_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +#include "include/core/SkTypes.h" +#include "include/private/GrTypesPriv.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTArray.h" + +class GrContext; +class GrCoverageCountingPathRenderer; + +/** + * Keeps track of an ordered list of path renderers. When a path needs to be + * drawn this list is scanned to find the most preferred renderer. To add your + * path renderer to the list implement the GrPathRenderer::AddPathRenderers + * function. + */ +class GrPathRendererChain : public SkNoncopyable { +public: + struct Options { + bool fAllowPathMaskCaching = false; + GpuPathRenderers fGpuPathRenderers = GpuPathRenderers::kAll; + }; + GrPathRendererChain(GrRecordingContext* context, const Options&); + + /** Documents how the caller plans to use a GrPathRenderer to draw a path. It affects the PR + returned by getPathRenderer */ + enum class DrawType { + kColor, // draw to the color buffer, no AA + kStencil, // draw just to the stencil buffer + kStencilAndColor, // draw the stencil and color buffer, no AA + }; + + /** Returns a GrPathRenderer compatible with the request if one is available. If the caller + is drawing the path to the stencil buffer then stencilSupport can be used to determine + whether the path can be rendered with arbitrary stencil rules or not. See comments on + StencilSupport in GrPathRenderer.h. */ + GrPathRenderer* getPathRenderer(const GrPathRenderer::CanDrawPathArgs& args, + DrawType drawType, + GrPathRenderer::StencilSupport* stencilSupport); + + /** Returns a direct pointer to the coverage counting path renderer, or null if it is not in the + chain. */ + GrCoverageCountingPathRenderer* getCoverageCountingPathRenderer() { + return fCoverageCountingPathRenderer; + } + +private: + enum { + kPreAllocCount = 8, + }; + SkSTArray> fChain; + GrCoverageCountingPathRenderer* fCoverageCountingPathRenderer = nullptr; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPathRendering.cpp b/gfx/skia/skia/src/gpu/GrPathRendering.cpp new file mode 100644 index 0000000000..5e44531d2e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRendering.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "include/core/SkTypeface.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkScalerContext.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRenderTarget.h" + +const GrUserStencilSettings& GrPathRendering::GetStencilPassSettings(FillType fill) { + switch (fill) { + default: + SK_ABORT("Unexpected path fill."); + case GrPathRendering::kWinding_FillType: { + constexpr static GrUserStencilSettings kWindingStencilPass( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kIncWrap, + GrUserStencilOp::kIncWrap, + 0xffff>() + ); + return kWindingStencilPass; + } + case GrPathRendering::kEvenOdd_FillType: { + constexpr static GrUserStencilSettings kEvenOddStencilPass( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kInvert, + GrUserStencilOp::kInvert, + 0xffff>() + ); + return kEvenOddStencilPass; + } + } +} + +void GrPathRendering::stencilPath(const StencilPathArgs& args, const GrPath* path) { + fGpu->handleDirtyContext(); + this->onStencilPath(args, path); +} + +void GrPathRendering::drawPath(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + // Cover pass settings in pipeline. + const GrStencilSettings& stencilPassSettings, + const GrPath* path) { + fGpu->handleDirtyContext(); + if (auto barrierType = programInfo.pipeline().xferBarrierType(renderTarget->asTexture(), + *fGpu->caps())) { + fGpu->xferBarrier(renderTarget, barrierType); + } + this->onDrawPath(renderTarget, programInfo, stencilPassSettings, path); +} diff --git a/gfx/skia/skia/src/gpu/GrPathRendering.h b/gfx/skia/skia/src/gpu/GrPathRendering.h new file mode 100644 index 0000000000..3e143e4d0f --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRendering.h @@ -0,0 +1,134 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPathRendering_DEFINED +#define GrPathRendering_DEFINED + +#include "include/core/SkPath.h" + +class GrGpu; +class GrPath; +class GrProgramInfo; +class GrRenderTarget; +class GrRenderTargetProxy; +class GrScissorState; +class GrStencilSettings; +class GrStyle; +struct GrUserStencilSettings; +struct SkScalerContextEffects; +class SkDescriptor; +class SkTypeface; + +/** + * Abstract class wrapping HW path rendering API. + * + * The subclasses of this class use the possible HW API to render paths (as opposed to path + * rendering implemented in Skia on top of a "3d" HW API). + * The subclasses hold the global state needed to render paths, including shadow of the global HW + * API state. Similar to GrGpu. + * + * It is expected that the lifetimes of GrGpuXX and GrXXPathRendering are the same. The call context + * interface (eg. * the concrete instance of GrGpu subclass) should be provided to the instance + * during construction. + */ +class GrPathRendering { +public: + virtual ~GrPathRendering() { } + + enum PathTransformType { + kNone_PathTransformType, //!< [] + kTranslateX_PathTransformType, //!< [kMTransX] + kTranslateY_PathTransformType, //!< [kMTransY] + kTranslate_PathTransformType, //!< [kMTransX, kMTransY] + kAffine_PathTransformType, //!< [kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY] + + kLast_PathTransformType = kAffine_PathTransformType + }; + + static inline int PathTransformSize(PathTransformType type) { + switch (type) { + case kNone_PathTransformType: + return 0; + case kTranslateX_PathTransformType: + case kTranslateY_PathTransformType: + return 1; + case kTranslate_PathTransformType: + return 2; + case kAffine_PathTransformType: + return 6; + + default: + SK_ABORT("Unknown path transform type"); + } + } + + // No native support for inverse at this time + enum FillType { + /** Specifies that "inside" is computed by a non-zero sum of signed + edge crossings + */ + kWinding_FillType, + /** Specifies that "inside" is computed by an odd number of edge + crossings + */ + kEvenOdd_FillType, + }; + + static const GrUserStencilSettings& GetStencilPassSettings(FillType); + + /** + * Creates a new gpu path, based on the specified path and stroke and returns it. + * + * @param SkPath the geometry. + * @param GrStyle the style applied to the path. Styles with non-dash path effects are not + * allowed. + * @return a new GPU path object. + */ + virtual sk_sp createPath(const SkPath&, const GrStyle&) = 0; + + /** None of these params are optional, pointers used just to avoid making copies. */ + struct StencilPathArgs { + StencilPathArgs(bool useHWAA, + GrRenderTargetProxy* proxy, + const SkMatrix* viewMatrix, + const GrScissorState* scissor, + const GrStencilSettings* stencil) + : fUseHWAA(useHWAA) + , fProxy(proxy) + , fViewMatrix(viewMatrix) + , fScissor(scissor) + , fStencil(stencil) { + } + bool fUseHWAA; + GrRenderTargetProxy* fProxy; + const SkMatrix* fViewMatrix; + const GrScissorState* fScissor; + const GrStencilSettings* fStencil; + }; + + void stencilPath(const StencilPathArgs& args, const GrPath* path); + + void drawPath(GrRenderTarget*, + const GrProgramInfo&, + const GrStencilSettings& stencilPassSettings, // Cover pass settings in pipeline. + const GrPath* path); + +protected: + GrPathRendering(GrGpu* gpu) : fGpu(gpu) { } + + virtual void onStencilPath(const StencilPathArgs&, const GrPath*) = 0; + virtual void onDrawPath(GrRenderTarget*, + const GrProgramInfo&, + const GrStencilSettings&, + const GrPath*) = 0; + + GrGpu* fGpu; +private: + GrPathRendering& operator=(const GrPathRendering&); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPathRendering_none.cpp b/gfx/skia/skia/src/gpu/GrPathRendering_none.cpp new file mode 100644 index 0000000000..3c2878d083 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPathRendering_none.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrPath.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLPathRendering.h" +#include "src/gpu/ops/GrStencilAndCoverPathRenderer.h" +#include "src/gpu/ops/GrStencilPathOp.h" + +class GrRecordingContext; + +GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrResourceProvider* resourceProvider, + const GrCaps& caps) { + return nullptr; +} + +GrGLPathRendering::GrGLPathRendering(GrGLGpu* gpu) + : GrPathRendering(gpu) + , fPreallocatedPathCount(0) {} + +GrGLPathRendering::~GrGLPathRendering() {} + +void GrGLPathRendering::disconnect(GrGpu::DisconnectType) {} + +void GrGLPathRendering::resetContext() {} + +void GrGLPathRendering::setProgramPathFragmentInputTransform(GrGLuint, GrGLint, + GrGLenum, GrGLint, + const SkMatrix&) {} + +void GrGLPathRendering::setProjectionMatrix(const SkMatrix&, const SkISize&, GrSurfaceOrigin) {} + +sk_sp GrGLPathRendering::createPath(const SkPath&, const GrStyle&) { return nullptr; } + +void GrGLPathRendering::onDrawPath(GrRenderTarget*, + const GrProgramInfo&, + const GrStencilSettings&, + const GrPath*) {} + +void GrGLPathRendering::onStencilPath(const StencilPathArgs&, const GrPath*) {} + +std::unique_ptr GrStencilPathOp::Make(GrRecordingContext*, + const SkMatrix&, + bool, + bool, + const GrScissorState&, + sk_sp) { return nullptr; } + +void GrPath::ComputeKey(const GrShape&, GrUniqueKey*, bool*) {} diff --git a/gfx/skia/skia/src/gpu/GrPersistentCacheUtils.h b/gfx/skia/skia/src/gpu/GrPersistentCacheUtils.h new file mode 100644 index 0000000000..b460184616 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPersistentCacheUtils.h @@ -0,0 +1,105 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPersistentCacheEntry_DEFINED +#define GrPersistentCacheEntry_DEFINED + +#include "include/core/SkData.h" +#include "include/private/GrTypesPriv.h" +#include "src/core/SkReader32.h" +#include "src/core/SkWriter32.h" +#include "src/sksl/SkSLString.h" +#include "src/sksl/ir/SkSLProgram.h" + +// The GrPersistentCache stores opaque blobs, as far as clients are concerned. It's helpful to +// inspect certain kinds of cached data within our tools, so for those cases (GLSL, SPIR-V), we +// put the serialization logic here, to be shared by the backend code and the tool code. +namespace GrPersistentCacheUtils { + +struct ShaderMetadata { + SkSL::Program::Settings* fSettings = nullptr; + SkTArray fAttributeNames; + bool fHasCustomColorOutput = false; + bool fHasSecondaryColorOutput = false; +}; + +static inline sk_sp PackCachedShaders(SkFourByteTag shaderType, + const SkSL::String shaders[], + const SkSL::Program::Inputs inputs[], + int numInputs, + const ShaderMetadata* meta = nullptr) { + // For consistency (so tools can blindly pack and unpack cached shaders), we always write + // kGrShaderTypeCount inputs. If the backend gives us fewer, we just replicate the last one. + SkASSERT(numInputs >= 1 && numInputs <= kGrShaderTypeCount); + + SkWriter32 writer; + writer.write32(shaderType); + for (int i = 0; i < kGrShaderTypeCount; ++i) { + writer.writeString(shaders[i].c_str(), shaders[i].size()); + writer.writePad(&inputs[SkTMin(i, numInputs - 1)], sizeof(SkSL::Program::Inputs)); + } + writer.writeBool(SkToBool(meta)); + if (meta) { + writer.writeBool(SkToBool(meta->fSettings)); + if (meta->fSettings) { + writer.writeBool(meta->fSettings->fFlipY); + writer.writeBool(meta->fSettings->fFragColorIsInOut); + writer.writeBool(meta->fSettings->fForceHighPrecision); + } + + writer.writeInt(meta->fAttributeNames.count()); + for (const auto& attr : meta->fAttributeNames) { + writer.writeString(attr.c_str(), attr.size()); + } + + writer.writeBool(meta->fHasCustomColorOutput); + writer.writeBool(meta->fHasSecondaryColorOutput); + } + return writer.snapshotAsData(); +} + +static inline void UnpackCachedShaders(SkReader32* reader, + SkSL::String shaders[], + SkSL::Program::Inputs inputs[], + int numInputs, + ShaderMetadata* meta = nullptr) { + for (int i = 0; i < kGrShaderTypeCount; ++i) { + size_t stringLen = 0; + const char* string = reader->readString(&stringLen); + shaders[i] = SkSL::String(string, stringLen); + + // GL, for example, only wants one set of Inputs + if (i < numInputs) { + reader->read(&inputs[i], sizeof(inputs[i])); + } else { + reader->skip(sizeof(SkSL::Program::Inputs)); + } + } + if (reader->readBool() && meta) { + SkASSERT(meta->fSettings != nullptr); + + if (reader->readBool()) { + meta->fSettings->fFlipY = reader->readBool(); + meta->fSettings->fFragColorIsInOut = reader->readBool(); + meta->fSettings->fForceHighPrecision = reader->readBool(); + } + + meta->fAttributeNames.resize(reader->readInt()); + for (int i = 0; i < meta->fAttributeNames.count(); ++i) { + size_t stringLen = 0; + const char* string = reader->readString(&stringLen); + meta->fAttributeNames[i] = SkSL::String(string, stringLen); + } + + meta->fHasCustomColorOutput = reader->readBool(); + meta->fHasSecondaryColorOutput = reader->readBool(); + } +} + +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPipeline.cpp b/gfx/skia/skia/src/gpu/GrPipeline.cpp new file mode 100644 index 0000000000..3e5e18d68c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPipeline.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrPipeline.h" + +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrXferProcessor.h" + +#include "src/gpu/ops/GrOp.h" + +GrPipeline::GrPipeline(const InitArgs& args, + GrProcessorSet&& processors, + GrAppliedClip&& appliedClip) + : fOutputSwizzle(args.fOutputSwizzle) { + SkASSERT(processors.isFinalized()); + + fFlags = (Flags)args.fInputFlags; + if (appliedClip.hasStencilClip()) { + fFlags |= Flags::kHasStencilClip; + } + if (appliedClip.scissorState().enabled()) { + fFlags |= Flags::kScissorEnabled; + } + + fWindowRectsState = appliedClip.windowRectsState(); + if (!args.fUserStencil->isDisabled(fFlags & Flags::kHasStencilClip)) { + fFlags |= Flags::kStencilEnabled; + } + + fUserStencilSettings = args.fUserStencil; + + fXferProcessor = processors.refXferProcessor(); + + if (args.fDstProxy.proxy()) { + SkASSERT(args.fDstProxy.proxy()->isInstantiated()); + + fDstTextureProxy = args.fDstProxy.refProxy(); + fDstTextureOffset = args.fDstProxy.offset(); + } + + // Copy GrFragmentProcessors from GrProcessorSet to Pipeline + fNumColorProcessors = processors.numColorFragmentProcessors(); + int numTotalProcessors = fNumColorProcessors + + processors.numCoverageFragmentProcessors() + + appliedClip.numClipCoverageFragmentProcessors(); + fFragmentProcessors.reset(numTotalProcessors); + + int currFPIdx = 0; + for (int i = 0; i < processors.numColorFragmentProcessors(); ++i, ++currFPIdx) { + fFragmentProcessors[currFPIdx] = processors.detachColorFragmentProcessor(i); + } + for (int i = 0; i < processors.numCoverageFragmentProcessors(); ++i, ++currFPIdx) { + fFragmentProcessors[currFPIdx] = processors.detachCoverageFragmentProcessor(i); + } + for (int i = 0; i < appliedClip.numClipCoverageFragmentProcessors(); ++i, ++currFPIdx) { + fFragmentProcessors[currFPIdx] = appliedClip.detachClipCoverageFragmentProcessor(i); + } + +#ifdef SK_DEBUG + for (int i = 0; i < numTotalProcessors; ++i) { + if (!fFragmentProcessors[i]->isInstantiated()) { + this->markAsBad(); + break; + } + } +#endif +} + +GrXferBarrierType GrPipeline::xferBarrierType(GrTexture* texture, const GrCaps& caps) const { + if (fDstTextureProxy && fDstTextureProxy->peekTexture() == texture) { + return kTexture_GrXferBarrierType; + } + return this->getXferProcessor().xferBarrierType(caps); +} + +GrPipeline::GrPipeline(GrScissorTest scissorTest, sk_sp xp, + const GrSwizzle& outputSwizzle, InputFlags inputFlags, + const GrUserStencilSettings* userStencil) + : fWindowRectsState() + , fUserStencilSettings(userStencil) + , fFlags((Flags)inputFlags) + , fXferProcessor(std::move(xp)) + , fFragmentProcessors() + , fNumColorProcessors(0) + , fOutputSwizzle(outputSwizzle) { + if (GrScissorTest::kEnabled == scissorTest) { + fFlags |= Flags::kScissorEnabled; + } + if (!userStencil->isDisabled(false)) { + fFlags |= Flags::kStencilEnabled; + } +} + +uint32_t GrPipeline::getBlendInfoKey() const { + const GrXferProcessor::BlendInfo& blendInfo = this->getXferProcessor().getBlendInfo(); + + static const uint32_t kBlendWriteShift = 1; + static const uint32_t kBlendCoeffShift = 5; + GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift)); + GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4); + + uint32_t key = blendInfo.fWriteColor; + key |= (blendInfo.fSrcBlend << kBlendWriteShift); + key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift)); + key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift)); + + return key; +} diff --git a/gfx/skia/skia/src/gpu/GrPipeline.h b/gfx/skia/skia/src/gpu/GrPipeline.h new file mode 100644 index 0000000000..b57e66d200 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPipeline.h @@ -0,0 +1,241 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPipeline_DEFINED +#define GrPipeline_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/GrProcessorSet.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/GrScissorState.h" +#include "src/gpu/GrUserStencilSettings.h" +#include "src/gpu/GrWindowRectsState.h" +#include "src/gpu/effects/GrCoverageSetOpXP.h" +#include "src/gpu/effects/GrDisableColorXP.h" +#include "src/gpu/effects/GrPorterDuffXferProcessor.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#include "src/gpu/geometry/GrRect.h" + +class GrAppliedClip; +class GrOp; +class GrRenderTargetContext; + +/** + * This immutable object contains information needed to set build a shader program and set API + * state for a draw. It is used along with a GrPrimitiveProcessor and a source of geometric + * data (GrMesh or GrPath) to draw. + */ +class GrPipeline { +public: + /////////////////////////////////////////////////////////////////////////// + /// @name Creation + + // Pipeline options that the caller may enable. + // NOTE: This enum is extended later by GrPipeline::Flags. + enum class InputFlags : uint8_t { + kNone = 0, + /** + * Perform HW anti-aliasing. This means either HW FSAA, if supported by the render target, + * or smooth-line rendering if a line primitive is drawn and line smoothing is supported by + * the 3D API. + */ + kHWAntialias = (1 << 0), + /** + * Modifies the vertex shader so that vertices will be positioned at pixel centers. + */ + kSnapVerticesToPixelCenters = (1 << 1), // This value must be last. (See kLastInputFlag.) + }; + + struct InitArgs { + InputFlags fInputFlags = InputFlags::kNone; + const GrUserStencilSettings* fUserStencil = &GrUserStencilSettings::kUnused; + const GrCaps* fCaps = nullptr; + GrXferProcessor::DstProxy fDstProxy; + GrSwizzle fOutputSwizzle; + }; + + /** + * Some state can be changed between GrMeshes without changing GrPipelines. This is generally + * less expensive then using multiple pipelines. Such state is called "dynamic state". It can + * be specified in two ways: + * 1) FixedDynamicState - use this to specify state that does not vary between GrMeshes. + * 2) DynamicStateArrays - use this to specify per mesh values for dynamic state. + **/ + struct FixedDynamicState { + explicit FixedDynamicState(const SkIRect& scissorRect) : fScissorRect(scissorRect) {} + FixedDynamicState() = default; + SkIRect fScissorRect = SkIRect::EmptyIRect(); + // Must have GrPrimitiveProcessor::numTextureSamplers() entries. Can be null if no samplers + // or textures are passed using DynamicStateArrays. + GrTextureProxy** fPrimitiveProcessorTextures = nullptr; + }; + + /** + * Any non-null array overrides the FixedDynamicState on a mesh-by-mesh basis. Arrays must + * have one entry for each GrMesh. + */ + struct DynamicStateArrays { + const SkIRect* fScissorRects = nullptr; + // Must have GrPrimitiveProcessor::numTextureSamplers() * num_meshes entries. + // Can be null if no samplers or to use the same textures for all meshes via' + // FixedDynamicState. + GrTextureProxy** fPrimitiveProcessorTextures = nullptr; + }; + + /** + * Creates a simple pipeline with default settings and no processors. The provided blend mode + * must be "Porter Duff" (<= kLastCoeffMode). If using GrScissorTest::kEnabled, the caller must + * specify a scissor rectangle through the DynamicState struct. + **/ + GrPipeline(GrScissorTest scissor, SkBlendMode blend, const GrSwizzle& outputSwizzle, + InputFlags flags = InputFlags::kNone, + const GrUserStencilSettings* stencil = &GrUserStencilSettings::kUnused) + : GrPipeline(scissor, GrPorterDuffXPFactory::MakeNoCoverageXP(blend), outputSwizzle, + flags, stencil) { + } + + GrPipeline(GrScissorTest, sk_sp, const GrSwizzle& outputSwizzle, + InputFlags = InputFlags::kNone, + const GrUserStencilSettings* = &GrUserStencilSettings::kUnused); + + GrPipeline(const InitArgs&, GrProcessorSet&&, GrAppliedClip&&); + + GrPipeline(const GrPipeline&) = delete; + GrPipeline& operator=(const GrPipeline&) = delete; + + /// @} + + /////////////////////////////////////////////////////////////////////////// + /// @name GrFragmentProcessors + + int numColorFragmentProcessors() const { return fNumColorProcessors; } + int numCoverageFragmentProcessors() const { + return fFragmentProcessors.count() - fNumColorProcessors; + } + int numFragmentProcessors() const { return fFragmentProcessors.count(); } + + const GrXferProcessor& getXferProcessor() const { + if (fXferProcessor) { + return *fXferProcessor.get(); + } else { + // A null xp member means the common src-over case. GrXferProcessor's ref'ing + // mechanism is not thread safe so we do not hold a ref on this global. + return GrPorterDuffXPFactory::SimpleSrcOverXP(); + } + } + + /** + * If the GrXferProcessor uses a texture to access the dst color, then this returns that + * texture and the offset to the dst contents within that texture. + */ + GrTextureProxy* dstTextureProxy(SkIPoint* offset = nullptr) const { + if (offset) { + *offset = fDstTextureOffset; + } + + return fDstTextureProxy.get(); + } + + GrTexture* peekDstTexture(SkIPoint* offset = nullptr) const { + if (GrTextureProxy* dstProxy = this->dstTextureProxy(offset)) { + return dstProxy->peekTexture(); + } + + return nullptr; + } + + const GrFragmentProcessor& getColorFragmentProcessor(int idx) const { + SkASSERT(idx < this->numColorFragmentProcessors()); + return *fFragmentProcessors[idx].get(); + } + + const GrFragmentProcessor& getCoverageFragmentProcessor(int idx) const { + SkASSERT(idx < this->numCoverageFragmentProcessors()); + return *fFragmentProcessors[fNumColorProcessors + idx].get(); + } + + const GrFragmentProcessor& getFragmentProcessor(int idx) const { + return *fFragmentProcessors[idx].get(); + } + + /// @} + + const GrUserStencilSettings* getUserStencil() const { return fUserStencilSettings; } + + bool isScissorEnabled() const { + return SkToBool(fFlags & Flags::kScissorEnabled); + } + + const GrWindowRectsState& getWindowRectsState() const { return fWindowRectsState; } + + bool isHWAntialiasState() const { return SkToBool(fFlags & InputFlags::kHWAntialias); } + bool snapVerticesToPixelCenters() const { + return SkToBool(fFlags & InputFlags::kSnapVerticesToPixelCenters); + } + bool hasStencilClip() const { + return SkToBool(fFlags & Flags::kHasStencilClip); + } + bool isStencilEnabled() const { + return SkToBool(fFlags & Flags::kStencilEnabled); + } + SkDEBUGCODE(bool isBad() const { return SkToBool(fFlags & Flags::kIsBad); }) + + GrXferBarrierType xferBarrierType(GrTexture*, const GrCaps&) const; + + // Used by Vulkan and Metal to cache their respective pipeline objects + uint32_t getBlendInfoKey() const; + + const GrSwizzle& outputSwizzle() const { return fOutputSwizzle; } + +private: + + SkDEBUGCODE(void markAsBad() { fFlags |= Flags::kIsBad; }) + + static constexpr uint8_t kLastInputFlag = (uint8_t)InputFlags::kSnapVerticesToPixelCenters; + + /** This is a continuation of the public "InputFlags" enum. */ + enum class Flags : uint8_t { + kHasStencilClip = (kLastInputFlag << 1), + kStencilEnabled = (kLastInputFlag << 2), + kScissorEnabled = (kLastInputFlag << 3), +#ifdef SK_DEBUG + kIsBad = (kLastInputFlag << 4), +#endif + }; + + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags); + + friend bool operator&(Flags, InputFlags); + + using FragmentProcessorArray = SkAutoSTArray<8, std::unique_ptr>; + + sk_sp fDstTextureProxy; + SkIPoint fDstTextureOffset; + GrWindowRectsState fWindowRectsState; + const GrUserStencilSettings* fUserStencilSettings; + Flags fFlags; + sk_sp fXferProcessor; + FragmentProcessorArray fFragmentProcessors; + + // This value is also the index in fFragmentProcessors where coverage processors begin. + int fNumColorProcessors; + + GrSwizzle fOutputSwizzle; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrPipeline::InputFlags); +GR_MAKE_BITFIELD_CLASS_OPS(GrPipeline::Flags); + +inline bool operator&(GrPipeline::Flags flags, GrPipeline::InputFlags inputFlag) { + return (flags & (GrPipeline::Flags)inputFlag); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrPrimitiveProcessor.cpp b/gfx/skia/skia/src/gpu/GrPrimitiveProcessor.cpp new file mode 100644 index 0000000000..fea8e2dc6b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPrimitiveProcessor.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrPrimitiveProcessor.h" + +#include "src/gpu/GrCoordTransform.h" + +/** + * We specialize the vertex code for each of these matrix types. + */ +enum MatrixType { + kNoPersp_MatrixType = 0, + kGeneral_MatrixType = 1, +}; + +GrPrimitiveProcessor::GrPrimitiveProcessor(ClassID classID) : GrProcessor(classID) {} + +const GrPrimitiveProcessor::TextureSampler& GrPrimitiveProcessor::textureSampler(int i) const { + SkASSERT(i >= 0 && i < this->numTextureSamplers()); + return this->onTextureSampler(i); +} + +uint32_t +GrPrimitiveProcessor::getTransformKey(const SkTArray& coords, + int numCoords) const { + uint32_t totalKey = 0; + for (int t = 0; t < numCoords; ++t) { + uint32_t key = 0; + const GrCoordTransform* coordTransform = coords[t]; + if (coordTransform->getMatrix().hasPerspective()) { + key |= kGeneral_MatrixType; + } else { + key |= kNoPersp_MatrixType; + } + key <<= t; + SkASSERT(0 == (totalKey & key)); // keys for each transform ought not to overlap + totalKey |= key; + } + return totalKey; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static inline GrSamplerState::Filter clamp_filter(GrTextureType type, + GrSamplerState::Filter requestedFilter) { + if (GrTextureTypeHasRestrictedSampling(type)) { + return SkTMin(requestedFilter, GrSamplerState::Filter::kBilerp); + } + return requestedFilter; +} + +GrPrimitiveProcessor::TextureSampler::TextureSampler(GrTextureType textureType, + const GrSamplerState& samplerState, + const GrSwizzle& swizzle, + uint32_t extraSamplerKey) { + this->reset(textureType, samplerState, swizzle, extraSamplerKey); +} + +void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType, + const GrSamplerState& samplerState, + const GrSwizzle& swizzle, + uint32_t extraSamplerKey) { + fSamplerState = samplerState; + fSamplerState.setFilterMode(clamp_filter(textureType, samplerState.filter())); + fSwizzle = swizzle; + fTextureType = textureType; + fExtraSamplerKey = extraSamplerKey; + fIsInitialized = true; +} + diff --git a/gfx/skia/skia/src/gpu/GrPrimitiveProcessor.h b/gfx/skia/skia/src/gpu/GrPrimitiveProcessor.h new file mode 100644 index 0000000000..41f37a3c48 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrPrimitiveProcessor.h @@ -0,0 +1,369 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPrimitiveProcessor_DEFINED +#define GrPrimitiveProcessor_DEFINED + +#include "src/gpu/GrColor.h" +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrShaderVar.h" + +class GrCoordTransform; + +/* + * The GrPrimitiveProcessor represents some kind of geometric primitive. This includes the shape + * of the primitive and the inherent color of the primitive. The GrPrimitiveProcessor is + * responsible for providing a color and coverage input into the Ganesh rendering pipeline. Through + * optimization, Ganesh may decide a different color, no color, and / or no coverage are required + * from the GrPrimitiveProcessor, so the GrPrimitiveProcessor must be able to support this + * functionality. + * + * There are two feedback loops between the GrFragmentProcessors, the GrXferProcessor, and the + * GrPrimitiveProcessor. These loops run on the CPU and to determine known properties of the final + * color and coverage inputs to the GrXferProcessor in order to perform optimizations that preserve + * correctness. The GrDrawOp seeds these loops with initial color and coverage, in its + * getProcessorAnalysisInputs implementation. These seed values are processed by the + * subsequent + * stages of the rendering pipeline and the output is then fed back into the GrDrawOp in + * the applyPipelineOptimizations call, where the op can use the information to inform decisions + * about GrPrimitiveProcessor creation. + */ + +class GrGLSLPrimitiveProcessor; + +/** + * GrPrimitiveProcessor defines an interface which all subclasses must implement. All + * GrPrimitiveProcessors must proivide seed color and coverage for the Ganesh color / coverage + * pipelines, and they must provide some notion of equality + * + * TODO: This class does not really need to be ref counted. Instances should be allocated using + * GrOpFlushState's arena and destroyed when the arena is torn down. + */ +class GrPrimitiveProcessor : public GrProcessor, public GrNonAtomicRef { +public: + class TextureSampler; + + /** Describes a vertex or instance attribute. */ + class Attribute { + public: + constexpr Attribute() = default; + constexpr Attribute(const char* name, + GrVertexAttribType cpuType, + GrSLType gpuType) + : fName(name), fCPUType(cpuType), fGPUType(gpuType) {} + constexpr Attribute(const Attribute&) = default; + + Attribute& operator=(const Attribute&) = default; + + constexpr bool isInitialized() const { return SkToBool(fName); } + + constexpr const char* name() const { return fName; } + constexpr GrVertexAttribType cpuType() const { return fCPUType; } + constexpr GrSLType gpuType() const { return fGPUType; } + + inline constexpr size_t size() const; + constexpr size_t sizeAlign4() const { return SkAlign4(this->size()); } + + GrShaderVar asShaderVar() const { + return {fName, fGPUType, GrShaderVar::kIn_TypeModifier}; + } + + private: + const char* fName = nullptr; + GrVertexAttribType fCPUType = kFloat_GrVertexAttribType; + GrSLType fGPUType = kFloat_GrSLType; + }; + + class Iter { + public: + Iter() : fCurr(nullptr), fRemaining(0) {} + Iter(const Iter& iter) : fCurr(iter.fCurr), fRemaining(iter.fRemaining) {} + Iter& operator= (const Iter& iter) { + fCurr = iter.fCurr; + fRemaining = iter.fRemaining; + return *this; + } + Iter(const Attribute* attrs, int count) : fCurr(attrs), fRemaining(count) { + this->skipUninitialized(); + } + + bool operator!=(const Iter& that) const { return fCurr != that.fCurr; } + const Attribute& operator*() const { return *fCurr; } + void operator++() { + if (fRemaining) { + fRemaining--; + fCurr++; + this->skipUninitialized(); + } + } + + private: + void skipUninitialized() { + if (!fRemaining) { + fCurr = nullptr; + } else { + while (!fCurr->isInitialized()) { + ++fCurr; + } + } + } + + const Attribute* fCurr; + int fRemaining; + }; + + class AttributeSet { + public: + Iter begin() const { return Iter(fAttributes, fCount); } + Iter end() const { return Iter(); } + + private: + friend class GrPrimitiveProcessor; + + void init(const Attribute* attrs, int count) { + fAttributes = attrs; + fRawCount = count; + fCount = 0; + fStride = 0; + for (int i = 0; i < count; ++i) { + if (attrs[i].isInitialized()) { + fCount++; + fStride += attrs[i].sizeAlign4(); + } + } + } + + const Attribute* fAttributes = nullptr; + int fRawCount = 0; + int fCount = 0; + size_t fStride = 0; + }; + + GrPrimitiveProcessor(ClassID); + + int numTextureSamplers() const { return fTextureSamplerCnt; } + const TextureSampler& textureSampler(int index) const; + int numVertexAttributes() const { return fVertexAttributes.fCount; } + const AttributeSet& vertexAttributes() const { return fVertexAttributes; } + int numInstanceAttributes() const { return fInstanceAttributes.fCount; } + const AttributeSet& instanceAttributes() const { return fInstanceAttributes; } + + bool hasVertexAttributes() const { return SkToBool(fVertexAttributes.fCount); } + bool hasInstanceAttributes() const { return SkToBool(fInstanceAttributes.fCount); } + + /** + * A common practice is to populate the the vertex/instance's memory using an implicit array of + * structs. In this case, it is best to assert that: + * stride == sizeof(struct) + */ + size_t vertexStride() const { return fVertexAttributes.fStride; } + size_t instanceStride() const { return fInstanceAttributes.fStride; } + + // Only the GrGeometryProcessor subclass actually has a geo shader or vertex attributes, but + // we put these calls on the base class to prevent having to cast + virtual bool willUseGeoShader() const = 0; + + /** + * Computes a transformKey from an array of coord transforms. Will only look at the first + * transforms in the array. + * + * TODO: A better name for this function would be "compute" instead of "get". + */ + uint32_t getTransformKey(const SkTArray& coords, + int numCoords) const; + + /** + * Sets a unique key on the GrProcessorKeyBuilder that is directly associated with this geometry + * processor's GL backend implementation. + * + * TODO: A better name for this function would be "compute" instead of "get". + */ + virtual void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const = 0; + + + void getAttributeKey(GrProcessorKeyBuilder* b) const { + // Ensure that our CPU and GPU type fields fit together in a 32-bit value, and we never + // collide with the "uninitialized" value. + static_assert(kGrVertexAttribTypeCount < (1 << 8), ""); + static_assert(kGrSLTypeCount < (1 << 8), ""); + + auto add_attributes = [=](const Attribute* attrs, int attrCount) { + for (int i = 0; i < attrCount; ++i) { + b->add32(attrs[i].isInitialized() ? (attrs[i].cpuType() << 16) | attrs[i].gpuType() + : ~0); + } + }; + add_attributes(fVertexAttributes.fAttributes, fVertexAttributes.fRawCount); + add_attributes(fInstanceAttributes.fAttributes, fInstanceAttributes.fRawCount); + } + + /** Returns a new instance of the appropriate *GL* implementation class + for the given GrProcessor; caller is responsible for deleting + the object. */ + virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const = 0; + + virtual bool isPathRendering() const { return false; } + +protected: + void setVertexAttributes(const Attribute* attrs, int attrCount) { + fVertexAttributes.init(attrs, attrCount); + } + void setInstanceAttributes(const Attribute* attrs, int attrCount) { + SkASSERT(attrCount >= 0); + fInstanceAttributes.init(attrs, attrCount); + } + void setTextureSamplerCnt(int cnt) { + SkASSERT(cnt >= 0); + fTextureSamplerCnt = cnt; + } + + /** + * Helper for implementing onTextureSampler(). E.g.: + * return IthTexureSampler(i, fMyFirstSampler, fMySecondSampler, fMyThirdSampler); + */ + template + static const TextureSampler& IthTextureSampler(int i, const TextureSampler& samp0, + const Args&... samps) { + return (0 == i) ? samp0 : IthTextureSampler(i - 1, samps...); + } + inline static const TextureSampler& IthTextureSampler(int i); + +private: + virtual const TextureSampler& onTextureSampler(int) const { return IthTextureSampler(0); } + + AttributeSet fVertexAttributes; + AttributeSet fInstanceAttributes; + + int fTextureSamplerCnt = 0; + typedef GrProcessor INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +/** + * Used to capture the properties of the GrTextureProxies required/expected by a primitiveProcessor + * along with an associated GrSamplerState. The actual proxies used are stored in either the + * fixed or dynamic state arrays. TextureSamplers don't perform any coord manipulation to account + * for texture origin. + */ +class GrPrimitiveProcessor::TextureSampler { +public: + TextureSampler() = default; + + TextureSampler(GrTextureType, const GrSamplerState&, const GrSwizzle&, + uint32_t extraSamplerKey = 0); + + TextureSampler(const TextureSampler&) = delete; + TextureSampler& operator=(const TextureSampler&) = delete; + + void reset(GrTextureType, const GrSamplerState&, const GrSwizzle&, + uint32_t extraSamplerKey = 0); + + GrTextureType textureType() const { return fTextureType; } + + const GrSamplerState& samplerState() const { return fSamplerState; } + const GrSwizzle& swizzle() const { return fSwizzle; } + + uint32_t extraSamplerKey() const { return fExtraSamplerKey; } + + bool isInitialized() const { return fIsInitialized; } + +private: + GrSamplerState fSamplerState; + GrSwizzle fSwizzle; + GrTextureType fTextureType = GrTextureType::k2D; + uint32_t fExtraSamplerKey = 0; + bool fIsInitialized = false; +}; + +const GrPrimitiveProcessor::TextureSampler& GrPrimitiveProcessor::IthTextureSampler(int i) { + SK_ABORT("Illegal texture sampler index"); + static const TextureSampler kBogus; + return kBogus; +} + +////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the size of the attrib type in bytes. + * This was moved from include/private/GrTypesPriv.h in service of Skia dependents that build + * with C++11. + */ +static constexpr inline size_t GrVertexAttribTypeSize(GrVertexAttribType type) { + switch (type) { + case kFloat_GrVertexAttribType: + return sizeof(float); + case kFloat2_GrVertexAttribType: + return 2 * sizeof(float); + case kFloat3_GrVertexAttribType: + return 3 * sizeof(float); + case kFloat4_GrVertexAttribType: + return 4 * sizeof(float); + case kHalf_GrVertexAttribType: + return sizeof(uint16_t); + case kHalf2_GrVertexAttribType: + return 2 * sizeof(uint16_t); + case kHalf3_GrVertexAttribType: + return 3 * sizeof(uint16_t); + case kHalf4_GrVertexAttribType: + return 4 * sizeof(uint16_t); + case kInt2_GrVertexAttribType: + return 2 * sizeof(int32_t); + case kInt3_GrVertexAttribType: + return 3 * sizeof(int32_t); + case kInt4_GrVertexAttribType: + return 4 * sizeof(int32_t); + case kByte_GrVertexAttribType: + return 1 * sizeof(char); + case kByte2_GrVertexAttribType: + return 2 * sizeof(char); + case kByte3_GrVertexAttribType: + return 3 * sizeof(char); + case kByte4_GrVertexAttribType: + return 4 * sizeof(char); + case kUByte_GrVertexAttribType: + return 1 * sizeof(char); + case kUByte2_GrVertexAttribType: + return 2 * sizeof(char); + case kUByte3_GrVertexAttribType: + return 3 * sizeof(char); + case kUByte4_GrVertexAttribType: + return 4 * sizeof(char); + case kUByte_norm_GrVertexAttribType: + return 1 * sizeof(char); + case kUByte4_norm_GrVertexAttribType: + return 4 * sizeof(char); + case kShort2_GrVertexAttribType: + return 2 * sizeof(int16_t); + case kShort4_GrVertexAttribType: + return 4 * sizeof(int16_t); + case kUShort2_GrVertexAttribType: // fall through + case kUShort2_norm_GrVertexAttribType: + return 2 * sizeof(uint16_t); + case kInt_GrVertexAttribType: + return sizeof(int32_t); + case kUint_GrVertexAttribType: + return sizeof(uint32_t); + case kUShort_norm_GrVertexAttribType: + return sizeof(uint16_t); + case kUShort4_norm_GrVertexAttribType: + return 4 * sizeof(uint16_t); + } + // GCC fails because SK_ABORT evaluates to non constexpr. clang and cl.exe think this is + // unreachable and don't complain. +#if defined(__clang__) || !defined(__GNUC__) + SK_ABORT("Unsupported type conversion"); +#endif + return 0; +} + +constexpr size_t GrPrimitiveProcessor::Attribute::size() const { + return GrVertexAttribTypeSize(fCPUType); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProcessor.cpp b/gfx/skia/skia/src/gpu/GrProcessor.cpp new file mode 100644 index 0000000000..d7a0c57860 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessor.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContext.h" +#include "include/private/SkSpinlock.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/GrXferProcessor.h" + +#if GR_TEST_UTILS + +GrResourceProvider* GrProcessorTestData::resourceProvider() { + return fContext->priv().resourceProvider(); +} + +GrProxyProvider* GrProcessorTestData::proxyProvider() { + return fContext->priv().proxyProvider(); +} + +const GrCaps* GrProcessorTestData::caps() { return fContext->priv().caps(); } + +class GrFragmentProcessor; +class GrGeometryProcessor; + +/* + * Originally these were both in the processor unit test header, but then it seemed to cause linker + * problems on android. + */ +template <> +SkTArray* GrFragmentProcessorTestFactory::GetFactories() { + static SkTArray gFactories; + return &gFactories; +} + +template <> +SkTArray* GrGeometryProcessorTestFactory::GetFactories() { + static SkTArray gFactories; + return &gFactories; +} + +SkTArray* GrXPFactoryTestFactory::GetFactories() { + static SkTArray gFactories; + return &gFactories; +} + +/* + * To ensure we always have successful static initialization, before creating from the factories + * we verify the count is as expected. If a new factory is added, then these numbers must be + * manually adjusted. + */ +#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS +static const int kFPFactoryCount = 37; +static const int kGPFactoryCount = 14; +static const int kXPFactoryCount = 4; +#else +static const int kFPFactoryCount = 0; +static const int kGPFactoryCount = 0; +static const int kXPFactoryCount = 0; +#endif + +template <> +void GrFragmentProcessorTestFactory::VerifyFactoryCount() { + if (kFPFactoryCount != GetFactories()->count()) { + SkDebugf("\nExpected %d fragment processor factories, found %d.\n", + kFPFactoryCount, GetFactories()->count()); + SK_ABORT("Wrong number of fragment processor factories!"); + } +} + +template <> +void GrGeometryProcessorTestFactory::VerifyFactoryCount() { + if (kGPFactoryCount != GetFactories()->count()) { + SkDebugf("\nExpected %d geometry processor factories, found %d.\n", + kGPFactoryCount, GetFactories()->count()); + SK_ABORT("Wrong number of geometry processor factories!"); + } +} + +void GrXPFactoryTestFactory::VerifyFactoryCount() { + if (kXPFactoryCount != GetFactories()->count()) { + SkDebugf("\nExpected %d xp factory factories, found %d.\n", + kXPFactoryCount, GetFactories()->count()); + SK_ABORT("Wrong number of xp factory factories!"); + } +} + +#endif + + +// We use a global pool protected by a mutex(spinlock). Chrome may use the same GrContext on +// different threads. The GrContext is not used concurrently on different threads and there is a +// memory barrier between accesses of a context on different threads. Also, there may be multiple +// GrContexts and those contexts may be in use concurrently on different threads. +namespace { +#if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) +static SkSpinlock gProcessorSpinlock; +#endif +class MemoryPoolAccessor { +public: + +// We know in the Android framework there is only one GrContext. +#if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) + MemoryPoolAccessor() {} + ~MemoryPoolAccessor() {} +#else + MemoryPoolAccessor() { gProcessorSpinlock.acquire(); } + ~MemoryPoolAccessor() { gProcessorSpinlock.release(); } +#endif + + GrMemoryPool* pool() const { + static GrMemoryPool gPool(4096, 4096); + return &gPool; + } +}; +} + +/////////////////////////////////////////////////////////////////////////////// + +void* GrProcessor::operator new(size_t size) { return MemoryPoolAccessor().pool()->allocate(size); } + +void GrProcessor::operator delete(void* target) { + return MemoryPoolAccessor().pool()->release(target); +} diff --git a/gfx/skia/skia/src/gpu/GrProcessor.h b/gfx/skia/skia/src/gpu/GrProcessor.h new file mode 100644 index 0000000000..326c6742b8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessor.h @@ -0,0 +1,226 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProcessor_DEFINED +#define GrProcessor_DEFINED + +#include "include/core/SkMath.h" +#include "include/core/SkString.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/GrProcessorUnitTest.h" +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureProxy.h" + +class GrContext; +class GrResourceProvider; + +/** + * Used by processors to build their keys. It incorporates each per-processor key into a larger + * shader key. + */ +class GrProcessorKeyBuilder { +public: + GrProcessorKeyBuilder(SkTArray* data) : fData(data), fCount(0) { + SkASSERT(0 == fData->count() % sizeof(uint32_t)); + } + + void add32(uint32_t v) { + ++fCount; + fData->push_back_n(4, reinterpret_cast(&v)); + } + + /** Inserts count uint32_ts into the key. The returned pointer is only valid until the next + add*() call. */ + uint32_t* SK_WARN_UNUSED_RESULT add32n(int count) { + SkASSERT(count > 0); + fCount += count; + return reinterpret_cast(fData->push_back_n(4 * count)); + } + + size_t size() const { return sizeof(uint32_t) * fCount; } + +private: + SkTArray* fData; // unowned ptr to the larger key. + int fCount; // number of uint32_ts added to fData by the processor. +}; + +/** Provides custom shader code to the Ganesh shading pipeline. GrProcessor objects *must* be + immutable: after being constructed, their fields may not change. + + Dynamically allocated GrProcessors are managed by a per-thread memory pool. The ref count of an + processor must reach 0 before the thread terminates and the pool is destroyed. + */ +class GrProcessor { +public: + enum ClassID { + kBigKeyProcessor_ClassID, + kBlockInputFragmentProcessor_ClassID, + kButtCapStrokedCircleGeometryProcessor_ClassID, + kCircleGeometryProcessor_ClassID, + kCircularRRectEffect_ClassID, + kClockwiseTestProcessor_ClassID, + kColorTableEffect_ClassID, + kComposeOneFragmentProcessor_ClassID, + kComposeTwoFragmentProcessor_ClassID, + kCoverageSetOpXP_ClassID, + kCubicStrokeProcessor_ClassID, + kCustomXP_ClassID, + kDashingCircleEffect_ClassID, + kDashingLineEffect_ClassID, + kDefaultGeoProc_ClassID, + kDIEllipseGeometryProcessor_ClassID, + kDisableColorXP_ClassID, + kEllipseGeometryProcessor_ClassID, + kEllipticalRRectEffect_ClassID, + kGP_ClassID, + kVertexColorSpaceBenchGP_ClassID, + kGrAARectEffect_ClassID, + kGrAlphaThresholdFragmentProcessor_ClassID, + kGrBicubicEffect_ClassID, + kGrBitmapTextGeoProc_ClassID, + kGrBlurredEdgeFragmentProcessor_ClassID, + kGrCCClipProcessor_ClassID, + kGrCCPathProcessor_ClassID, + kGrCircleBlurFragmentProcessor_ClassID, + kGrCircleEffect_ClassID, + kGrClampedGradientEffect_ClassID, + kGrColorMatrixFragmentProcessor_ClassID, + kGrColorSpaceXformEffect_ClassID, + kGrComposeLerpEffect_ClassID, + kGrComposeLerpRedEffect_ClassID, + kGrConfigConversionEffect_ClassID, + kGrConicEffect_ClassID, + kGrConstColorProcessor_ClassID, + kGrConvexPolyEffect_ClassID, + kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID, + kGrDiffuseLightingEffect_ClassID, + kGrDisplacementMapEffect_ClassID, + kGrDistanceFieldA8TextGeoProc_ClassID, + kGrDistanceFieldLCDTextGeoProc_ClassID, + kGrDistanceFieldPathGeoProc_ClassID, + kGrDualIntervalGradientColorizer_ClassID, + kGrEllipseEffect_ClassID, + kGrFillRRectOp_Processor_ClassID, + kGrGaussianConvolutionFragmentProcessor_ClassID, + kGrGSCoverageProcessor_ClassID, + kGrImprovedPerlinNoiseEffect_ClassID, + kGrLinearGradientLayout_ClassID, + kGrLumaColorFilterEffect_ClassID, + kGrMagnifierEffect_ClassID, + kGrMatrixConvolutionEffect_ClassID, + kGrMeshTestProcessor_ClassID, + kGrMorphologyEffect_ClassID, + kGrMixerEffect_ClassID, + kGrOverrideInputFragmentProcessor_ClassID, + kGrPathProcessor_ClassID, + kGrPerlinNoise2Effect_ClassID, + kGrPipelineDynamicStateTestProcessor_ClassID, + kGrPremulInputFragmentProcessor_ClassID, + kGrQuadEffect_ClassID, + kGrRadialGradientLayout_ClassID, + kGrRectBlurEffect_ClassID, + kGrRRectBlurEffect_ClassID, + kGrRRectShadowGeoProc_ClassID, + kGrSimpleTextureEffect_ClassID, + kGrSingleIntervalGradientColorizer_ClassID, + kGrSkSLFP_ClassID, + kGrSpecularLightingEffect_ClassID, + kGrSRGBEffect_ClassID, + kGrSampleMaskProcessor_ClassID, + kGrSaturateProcessor_ClassID, + kGrSweepGradientLayout_ClassID, + kGrTextureDomainEffect_ClassID, + kGrTextureGradientColorizer_ClassID, + kGrTiledGradientEffect_ClassID, + kGrTwoPointConicalGradientLayout_ClassID, + kGrUnrolledBinaryGradientColorizer_ClassID, + kGrVSCoverageProcessor_ClassID, + kGrYUVtoRGBEffect_ClassID, + kHighContrastFilterEffect_ClassID, + kLatticeGP_ClassID, + kPDLCDXferProcessor_ClassID, + kPorterDuffXferProcessor_ClassID, + kPremulFragmentProcessor_ClassID, + kQuadEdgeEffect_ClassID, + kQuadPerEdgeAAGeometryProcessor_ClassID, + kSampleLocationsTestProcessor_ClassID, + kSeriesFragmentProcessor_ClassID, + kShaderPDXferProcessor_ClassID, + kStencilResolveProcessor_ClassID, + kFwidthSquircleTestProcessor_ClassID, + kSwizzleFragmentProcessor_ClassID, + kTestFP_ClassID, + kFlatNormalsFP_ClassID, + kMappedNormalsFP_ClassID, + kLightingFP_ClassID, + kLinearStrokeProcessor_ClassID, + }; + + virtual ~GrProcessor() = default; + + /** Human-meaningful string to identify this prcoessor; may be embedded in generated shader + code. */ + virtual const char* name() const = 0; + + /** Human-readable dump of all information */ +#ifdef SK_DEBUG + virtual SkString dumpInfo() const { + SkString str; + str.appendf("Missing data"); + return str; + } +#else + SkString dumpInfo() const { return SkString(""); } +#endif + + /** + * Custom shader features provided by the framework. These require special handling when + * preparing shaders, so a processor must call setWillUseCustomFeature() from its constructor if + * it intends to use one. + */ + enum class CustomFeatures { + kNone = 0, + kSampleLocations = 1 << 0, + }; + + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(CustomFeatures); + + CustomFeatures requestedFeatures() const { return fRequestedFeatures; } + + void* operator new(size_t size); + void operator delete(void* target); + + void* operator new(size_t size, void* placement) { + return ::operator new(size, placement); + } + void operator delete(void* target, void* placement) { + ::operator delete(target, placement); + } + + /** Helper for down-casting to a GrProcessor subclass */ + template const T& cast() const { return *static_cast(this); } + + ClassID classID() const { return fClassID; } + +protected: + GrProcessor(ClassID classID) : fClassID(classID) {} + GrProcessor(const GrProcessor&) = delete; + GrProcessor& operator=(const GrProcessor&) = delete; + + void setWillUseCustomFeature(CustomFeatures feature) { fRequestedFeatures |= feature; } + void resetCustomFeatures() { fRequestedFeatures = CustomFeatures::kNone; } + + const ClassID fClassID; + CustomFeatures fRequestedFeatures = CustomFeatures::kNone; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrProcessor::CustomFeatures); + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProcessorAnalysis.cpp b/gfx/skia/skia/src/gpu/GrProcessorAnalysis.cpp new file mode 100644 index 0000000000..1f98300ffd --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessorAnalysis.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProcessorAnalysis.h" +#include "src/gpu/ops/GrDrawOp.h" + +GrColorFragmentProcessorAnalysis::GrColorFragmentProcessorAnalysis( + const GrProcessorAnalysisColor& input, + const GrFragmentProcessor* const* processors, + int cnt) { + fCompatibleWithCoverageAsAlpha = true; + fIsOpaque = input.isOpaque(); + fUsesLocalCoords = false; + fProcessorsToEliminate = 0; + fKnowOutputColor = input.isConstant(&fLastKnownOutputColor); + for (int i = 0; i < cnt; ++i) { + if (fUsesLocalCoords && !fKnowOutputColor && !fCompatibleWithCoverageAsAlpha && + !fIsOpaque) { + break; + } + const GrFragmentProcessor* fp = processors[i]; + if (fKnowOutputColor && + fp->hasConstantOutputForConstantInput(fLastKnownOutputColor, &fLastKnownOutputColor)) { + ++fProcessorsToEliminate; + fIsOpaque = fLastKnownOutputColor.isOpaque(); + // We reset these since the caller is expected to not use the earlier fragment + // processors. + fCompatibleWithCoverageAsAlpha = true; + fUsesLocalCoords = false; + } else { + fKnowOutputColor = false; + if (fIsOpaque && !fp->preservesOpaqueInput()) { + fIsOpaque = false; + } + if (fCompatibleWithCoverageAsAlpha && !fp->compatibleWithCoverageAsAlpha()) { + fCompatibleWithCoverageAsAlpha = false; + } + if (fp->usesLocalCoords()) { + fUsesLocalCoords = true; + } + } + } +} diff --git a/gfx/skia/skia/src/gpu/GrProcessorAnalysis.h b/gfx/skia/skia/src/gpu/GrProcessorAnalysis.h new file mode 100644 index 0000000000..5f0f22ed69 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessorAnalysis.h @@ -0,0 +1,153 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProcessorAnalysis_DEFINED +#define GrProcessorAnalysis_DEFINED + +#include "include/private/SkColorData.h" + +class GrDrawOp; +class GrFragmentProcessor; + +class GrProcessorAnalysisColor { +public: + enum class Opaque { + kNo, + kYes, + }; + + constexpr GrProcessorAnalysisColor(Opaque opaque = Opaque::kNo) + : fFlags(opaque == Opaque::kYes ? kIsOpaque_Flag : 0) + , fColor(SK_PMColor4fTRANSPARENT) {} + + GrProcessorAnalysisColor(const SkPMColor4f& color) { this->setToConstant(color); } + + void setToConstant(const SkPMColor4f& color) { + fColor = color; + if (color.isOpaque()) { + fFlags = kColorIsKnown_Flag | kIsOpaque_Flag; + } else { + fFlags = kColorIsKnown_Flag; + } + } + + void setToUnknown() { fFlags = 0; } + + void setToUnknownOpaque() { fFlags = kIsOpaque_Flag; } + + bool isUnknown() const { return SkToBool(fFlags == 0); } + + bool isOpaque() const { return SkToBool(kIsOpaque_Flag & fFlags); } + + bool isConstant(SkPMColor4f* color = nullptr) const { + if (kColorIsKnown_Flag & fFlags) { + if (color) { + *color = fColor; + } + return true; + } + return false; + } + + bool operator==(const GrProcessorAnalysisColor& that) const { + if (fFlags != that.fFlags) { + return false; + } + return (kColorIsKnown_Flag & fFlags) ? fColor == that.fColor : true; + } + + /** The returned value reflects the common properties of the two inputs. */ + static GrProcessorAnalysisColor Combine(const GrProcessorAnalysisColor& a, + const GrProcessorAnalysisColor& b) { + GrProcessorAnalysisColor result; + uint32_t commonFlags = a.fFlags & b.fFlags; + if ((kColorIsKnown_Flag & commonFlags) && a.fColor == b.fColor) { + result.fColor = a.fColor; + result.fFlags = a.fFlags; + } else if (kIsOpaque_Flag & commonFlags) { + result.fFlags = kIsOpaque_Flag; + } + return result; + } + +private: + enum Flags { + kColorIsKnown_Flag = 0x1, + kIsOpaque_Flag = 0x2, + }; + uint32_t fFlags; + SkPMColor4f fColor; +}; + +enum class GrProcessorAnalysisCoverage { kNone, kSingleChannel, kLCD }; + +/** + * GrColorFragmentProcessorAnalysis gathers invariant data from a set of color fragment processor. + * It is used to recognize optimizations that can simplify the generated shader or make blending + * more effecient. + */ +class GrColorFragmentProcessorAnalysis { +public: + GrColorFragmentProcessorAnalysis() = delete; + + GrColorFragmentProcessorAnalysis(const GrProcessorAnalysisColor& input, + const GrFragmentProcessor* const* processors, + int cnt); + + bool isOpaque() const { return fIsOpaque; } + + /** + * Are all the fragment processors compatible with conflating coverage with color prior to the + * the first fragment processor. This result assumes that processors that should be eliminated + * as indicated by initialProcessorsToEliminate() are in fact eliminated. + */ + bool allProcessorsCompatibleWithCoverageAsAlpha() const { + return fCompatibleWithCoverageAsAlpha; + } + + /** + * Do any of the fragment processors require local coords. This result assumes that + * processors that should be eliminated as indicated by initialProcessorsToEliminate() are in + * fact eliminated. + */ + bool usesLocalCoords() const { return fUsesLocalCoords; } + + /** + * If we detected that the result after the first N processors is a known color then we + * eliminate those N processors and replace the GrDrawOp's color input to the GrPipeline with + * the known output of the Nth processor, so that the Nth+1 fragment processor (or the XP if + * there are only N processors) sees its expected input. If this returns 0 then there are no + * processors to eliminate. + */ + int initialProcessorsToEliminate(SkPMColor4f* newPipelineInputColor) const { + if (fProcessorsToEliminate > 0) { + *newPipelineInputColor = fLastKnownOutputColor; + } + return fProcessorsToEliminate; + } + + /** + * Provides known information about the last processor's output color. + */ + GrProcessorAnalysisColor outputColor() const { + if (fKnowOutputColor) { + return fLastKnownOutputColor; + } + return fIsOpaque ? GrProcessorAnalysisColor::Opaque::kYes + : GrProcessorAnalysisColor::Opaque::kNo; + } + +private: + bool fIsOpaque; + bool fCompatibleWithCoverageAsAlpha; + bool fUsesLocalCoords; + bool fKnowOutputColor; + int fProcessorsToEliminate; + SkPMColor4f fLastKnownOutputColor; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProcessorSet.cpp b/gfx/skia/skia/src/gpu/GrProcessorSet.cpp new file mode 100644 index 0000000000..cd30a887d2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessorSet.cpp @@ -0,0 +1,253 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBlendModePriv.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrProcessorSet.h" +#include "src/gpu/GrUserStencilSettings.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/effects/GrPorterDuffXferProcessor.h" + +const GrProcessorSet& GrProcessorSet::EmptySet() { + static GrProcessorSet gEmpty(GrProcessorSet::Empty::kEmpty); + return gEmpty; +} + +GrProcessorSet GrProcessorSet::MakeEmptySet() { + return GrProcessorSet(GrProcessorSet::Empty::kEmpty); +} + +GrProcessorSet::GrProcessorSet(GrPaint&& paint) : fXP(paint.getXPFactory()) { + fFlags = 0; + if (paint.numColorFragmentProcessors() <= kMaxColorProcessors) { + fColorFragmentProcessorCnt = paint.numColorFragmentProcessors(); + fFragmentProcessors.reset(paint.numTotalFragmentProcessors()); + int i = 0; + for (auto& fp : paint.fColorFragmentProcessors) { + SkASSERT(fp.get()); + fFragmentProcessors[i++] = std::move(fp); + } + for (auto& fp : paint.fCoverageFragmentProcessors) { + SkASSERT(fp.get()); + fFragmentProcessors[i++] = std::move(fp); + } + } else { + SkDebugf("Insane number of color fragment processors in paint. Dropping all processors."); + fColorFragmentProcessorCnt = 0; + } + SkDEBUGCODE(paint.fAlive = false;) +} + +GrProcessorSet::GrProcessorSet(SkBlendMode mode) + : fXP(SkBlendMode_AsXPFactory(mode)) + , fColorFragmentProcessorCnt(0) + , fFragmentProcessorOffset(0) + , fFlags(0) {} + +GrProcessorSet::GrProcessorSet(std::unique_ptr colorFP) + : fFragmentProcessors(1) + , fXP((const GrXPFactory*)nullptr) + , fColorFragmentProcessorCnt(1) + , fFragmentProcessorOffset(0) + , fFlags(0) { + SkASSERT(colorFP); + fFragmentProcessors[0] = std::move(colorFP); +} + +GrProcessorSet::GrProcessorSet(GrProcessorSet&& that) + : fXP(std::move(that.fXP)) + , fColorFragmentProcessorCnt(that.fColorFragmentProcessorCnt) + , fFragmentProcessorOffset(0) + , fFlags(that.fFlags) { + fFragmentProcessors.reset(that.fFragmentProcessors.count() - that.fFragmentProcessorOffset); + for (int i = 0; i < fFragmentProcessors.count(); ++i) { + fFragmentProcessors[i] = + std::move(that.fFragmentProcessors[i + that.fFragmentProcessorOffset]); + } + that.fColorFragmentProcessorCnt = 0; + that.fFragmentProcessors.reset(0); +} + +GrProcessorSet::~GrProcessorSet() { + if (this->isFinalized() && this->xferProcessor()) { + this->xferProcessor()->unref(); + } +} + +#ifdef SK_DEBUG +SkString dump_fragment_processor_tree(const GrFragmentProcessor* fp, int indentCnt) { + SkString result; + SkString indentString; + for (int i = 0; i < indentCnt; ++i) { + indentString.append(" "); + } + result.appendf("%s%s %s \n", indentString.c_str(), fp->name(), fp->dumpInfo().c_str()); + if (fp->numChildProcessors()) { + for (int i = 0; i < fp->numChildProcessors(); ++i) { + result += dump_fragment_processor_tree(&fp->childProcessor(i), indentCnt + 1); + } + } + return result; +} + +SkString GrProcessorSet::dumpProcessors() const { + SkString result; + if (this->numFragmentProcessors()) { + if (this->numColorFragmentProcessors()) { + result.append("Color Fragment Processors:\n"); + for (int i = 0; i < this->numColorFragmentProcessors(); ++i) { + result += dump_fragment_processor_tree(this->colorFragmentProcessor(i), 1); + } + } else { + result.append("No color fragment processors.\n"); + } + if (this->numCoverageFragmentProcessors()) { + result.append("Coverage Fragment Processors:\n"); + for (int i = 0; i < this->numColorFragmentProcessors(); ++i) { + result += dump_fragment_processor_tree(this->coverageFragmentProcessor(i), 1); + } + } else { + result.append("No coverage fragment processors.\n"); + } + } else { + result.append("No color or coverage fragment processors.\n"); + } + if (this->isFinalized()) { + result.append("Xfer Processor: "); + if (this->xferProcessor()) { + result.appendf("%s\n", this->xferProcessor()->name()); + } else { + result.append("SrcOver\n"); + } + } else { + result.append("XP Factory dumping not implemented.\n"); + } + return result; +} +#endif + +bool GrProcessorSet::operator==(const GrProcessorSet& that) const { + SkASSERT(this->isFinalized()); + SkASSERT(that.isFinalized()); + int fpCount = this->numFragmentProcessors(); + if (((fFlags ^ that.fFlags) & ~kFinalized_Flag) || fpCount != that.numFragmentProcessors() || + fColorFragmentProcessorCnt != that.fColorFragmentProcessorCnt) { + return false; + } + + for (int i = 0; i < fpCount; ++i) { + int a = i + fFragmentProcessorOffset; + int b = i + that.fFragmentProcessorOffset; + if (!fFragmentProcessors[a]->isEqual(*that.fFragmentProcessors[b])) { + return false; + } + } + // Most of the time both of these are null + if (!this->xferProcessor() && !that.xferProcessor()) { + return true; + } + const GrXferProcessor& thisXP = this->xferProcessor() + ? *this->xferProcessor() + : GrPorterDuffXPFactory::SimpleSrcOverXP(); + const GrXferProcessor& thatXP = that.xferProcessor() + ? *that.xferProcessor() + : GrPorterDuffXPFactory::SimpleSrcOverXP(); + return thisXP.isEqual(thatXP); +} + +GrProcessorSet::Analysis GrProcessorSet::finalize( + const GrProcessorAnalysisColor& colorInput, const GrProcessorAnalysisCoverage coverageInput, + const GrAppliedClip* clip, const GrUserStencilSettings* userStencil, + bool hasMixedSampledCoverage, const GrCaps& caps, GrClampType clampType, + SkPMColor4f* overrideInputColor) { + SkASSERT(!this->isFinalized()); + SkASSERT(!fFragmentProcessorOffset); + + GrProcessorSet::Analysis analysis; + analysis.fCompatibleWithCoverageAsAlpha = GrProcessorAnalysisCoverage::kLCD != coverageInput; + + const std::unique_ptr* fps = + fFragmentProcessors.get() + fFragmentProcessorOffset; + GrColorFragmentProcessorAnalysis colorAnalysis( + colorInput, unique_ptr_address_as_pointer_address(fps), fColorFragmentProcessorCnt); + fps += fColorFragmentProcessorCnt; + int n = this->numCoverageFragmentProcessors(); + bool hasCoverageFP = n > 0; + bool coverageUsesLocalCoords = false; + for (int i = 0; i < n; ++i) { + if (!fps[i]->compatibleWithCoverageAsAlpha()) { + analysis.fCompatibleWithCoverageAsAlpha = false; + } + coverageUsesLocalCoords |= fps[i]->usesLocalCoords(); + } + if (clip) { + hasCoverageFP = hasCoverageFP || clip->numClipCoverageFragmentProcessors(); + for (int i = 0; i < clip->numClipCoverageFragmentProcessors(); ++i) { + const GrFragmentProcessor* clipFP = clip->clipCoverageFragmentProcessor(i); + analysis.fCompatibleWithCoverageAsAlpha &= clipFP->compatibleWithCoverageAsAlpha(); + coverageUsesLocalCoords |= clipFP->usesLocalCoords(); + } + } + int colorFPsToEliminate = colorAnalysis.initialProcessorsToEliminate(overrideInputColor); + analysis.fInputColorType = static_cast( + colorFPsToEliminate ? Analysis::kOverridden_InputColorType + : Analysis::kOriginal_InputColorType); + + GrProcessorAnalysisCoverage outputCoverage; + if (GrProcessorAnalysisCoverage::kLCD == coverageInput) { + outputCoverage = GrProcessorAnalysisCoverage::kLCD; + } else if (hasCoverageFP || GrProcessorAnalysisCoverage::kSingleChannel == coverageInput) { + outputCoverage = GrProcessorAnalysisCoverage::kSingleChannel; + } else { + outputCoverage = GrProcessorAnalysisCoverage::kNone; + } + + GrXPFactory::AnalysisProperties props = GrXPFactory::GetAnalysisProperties( + this->xpFactory(), colorAnalysis.outputColor(), outputCoverage, caps, clampType); + if (!this->numCoverageFragmentProcessors() && + GrProcessorAnalysisCoverage::kNone == coverageInput) { + } + analysis.fRequiresDstTexture = + SkToBool(props & GrXPFactory::AnalysisProperties::kRequiresDstTexture); + analysis.fCompatibleWithCoverageAsAlpha &= + SkToBool(props & GrXPFactory::AnalysisProperties::kCompatibleWithCoverageAsAlpha); + analysis.fRequiresNonOverlappingDraws = SkToBool( + props & GrXPFactory::AnalysisProperties::kRequiresNonOverlappingDraws); + if (props & GrXPFactory::AnalysisProperties::kIgnoresInputColor) { + colorFPsToEliminate = this->numColorFragmentProcessors(); + analysis.fInputColorType = + static_cast(Analysis::kIgnored_InputColorType); + analysis.fUsesLocalCoords = coverageUsesLocalCoords; + } else { + analysis.fCompatibleWithCoverageAsAlpha &= + colorAnalysis.allProcessorsCompatibleWithCoverageAsAlpha(); + analysis.fUsesLocalCoords = coverageUsesLocalCoords | colorAnalysis.usesLocalCoords(); + } + for (int i = 0; i < colorFPsToEliminate; ++i) { + fFragmentProcessors[i].reset(nullptr); + } + fFragmentProcessorOffset = colorFPsToEliminate; + fColorFragmentProcessorCnt -= colorFPsToEliminate; + analysis.fHasColorFragmentProcessor = (fColorFragmentProcessorCnt != 0); + + auto xp = GrXPFactory::MakeXferProcessor(this->xpFactory(), colorAnalysis.outputColor(), + outputCoverage, hasMixedSampledCoverage, caps, + clampType); + fXP.fProcessor = xp.release(); + + fFlags |= kFinalized_Flag; + analysis.fIsInitialized = true; +#ifdef SK_DEBUG + bool hasXferBarrier = + fXP.fProcessor && + GrXferBarrierType::kNone_GrXferBarrierType != fXP.fProcessor->xferBarrierType(caps); + bool needsNonOverlappingDraws = analysis.fRequiresDstTexture || hasXferBarrier; + SkASSERT(analysis.fRequiresNonOverlappingDraws == needsNonOverlappingDraws); +#endif + return analysis; +} diff --git a/gfx/skia/skia/src/gpu/GrProcessorSet.h b/gfx/skia/skia/src/gpu/GrProcessorSet.h new file mode 100644 index 0000000000..95838645d5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessorSet.h @@ -0,0 +1,205 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProcessorSet_DEFINED +#define GrProcessorSet_DEFINED + +#include "include/private/SkTemplates.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrProcessorAnalysis.h" +#include "src/gpu/GrXferProcessor.h" + +struct GrUserStencilSettings; +class GrAppliedClip; +class GrXPFactory; + +class GrProcessorSet { +private: + // Arbitrary constructor arg for empty set and analysis + enum class Empty { kEmpty }; + +public: + GrProcessorSet(GrPaint&&); + GrProcessorSet(SkBlendMode); + GrProcessorSet(std::unique_ptr colorFP); + GrProcessorSet(GrProcessorSet&&); + GrProcessorSet(const GrProcessorSet&) = delete; + GrProcessorSet& operator=(const GrProcessorSet&) = delete; + + ~GrProcessorSet(); + + int numColorFragmentProcessors() const { return fColorFragmentProcessorCnt; } + int numCoverageFragmentProcessors() const { + return this->numFragmentProcessors() - fColorFragmentProcessorCnt; + } + + const GrFragmentProcessor* colorFragmentProcessor(int idx) const { + SkASSERT(idx < fColorFragmentProcessorCnt); + return fFragmentProcessors[idx + fFragmentProcessorOffset].get(); + } + const GrFragmentProcessor* coverageFragmentProcessor(int idx) const { + return fFragmentProcessors[idx + fColorFragmentProcessorCnt + + fFragmentProcessorOffset].get(); + } + + const GrXferProcessor* xferProcessor() const { + SkASSERT(this->isFinalized()); + return fXP.fProcessor; + } + sk_sp refXferProcessor() const { + SkASSERT(this->isFinalized()); + return sk_ref_sp(fXP.fProcessor); + } + + std::unique_ptr detachColorFragmentProcessor(int idx) { + SkASSERT(idx < fColorFragmentProcessorCnt); + return std::move(fFragmentProcessors[idx + fFragmentProcessorOffset]); + } + + std::unique_ptr detachCoverageFragmentProcessor(int idx) { + return std::move( + fFragmentProcessors[idx + fFragmentProcessorOffset + fColorFragmentProcessorCnt]); + } + + /** Comparisons are only legal on finalized processor sets. */ + bool operator==(const GrProcessorSet& that) const; + bool operator!=(const GrProcessorSet& that) const { return !(*this == that); } + + /** + * This is used to report results of processor analysis when a processor set is finalized (see + * below). + */ + class Analysis { + public: + Analysis(const Analysis&) = default; + Analysis() { *reinterpret_cast(this) = 0; } + + bool isInitialized() const { return fIsInitialized; } + bool usesLocalCoords() const { return fUsesLocalCoords; } + bool requiresDstTexture() const { return fRequiresDstTexture; } + bool requiresNonOverlappingDraws() const { return fRequiresNonOverlappingDraws; } + bool isCompatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; } + // Indicates whether all color fragment processors were eliminated in the analysis. + bool hasColorFragmentProcessor() const { return fHasColorFragmentProcessor; } + + bool inputColorIsIgnored() const { return fInputColorType == kIgnored_InputColorType; } + bool inputColorIsOverridden() const { + return fInputColorType == kOverridden_InputColorType; + } + + private: + constexpr Analysis(Empty) + : fUsesLocalCoords(false) + , fCompatibleWithCoverageAsAlpha(true) + , fRequiresDstTexture(false) + , fRequiresNonOverlappingDraws(false) + , fHasColorFragmentProcessor(false) + , fIsInitialized(true) + , fInputColorType(kOriginal_InputColorType) {} + enum InputColorType : uint32_t { + kOriginal_InputColorType, + kOverridden_InputColorType, + kIgnored_InputColorType + }; + + // MSVS 2015 won't pack different underlying types + using PackedBool = uint32_t; + using PackedInputColorType = uint32_t; + + PackedBool fUsesLocalCoords : 1; + PackedBool fCompatibleWithCoverageAsAlpha : 1; + PackedBool fRequiresDstTexture : 1; + PackedBool fRequiresNonOverlappingDraws : 1; + PackedBool fHasColorFragmentProcessor : 1; + PackedBool fIsInitialized : 1; + PackedInputColorType fInputColorType : 2; + + friend class GrProcessorSet; + }; + GR_STATIC_ASSERT(sizeof(Analysis) <= sizeof(uint32_t)); + + /** + * This analyzes the processors given an op's input color and coverage as well as a clip. The + * state of the processor set may change to an equivalent but more optimal set of processors. + * This new state requires that the caller respect the returned 'inputColorOverride'. This is + * indicated by the returned Analysis's inputColorIsOverridden(). 'inputColorOverride' will not + * be written if the analysis does not override the input color. + * + * This must be called before the processor set is used to construct a GrPipeline and may only + * be called once. + * + * This also puts the processors in "pending execution" state and must be called when an op + * that owns a processor set is recorded to ensure pending and writes are propagated to + * resources referred to by the processors. Otherwise, data hazards may occur. + */ + Analysis finalize( + const GrProcessorAnalysisColor&, const GrProcessorAnalysisCoverage, + const GrAppliedClip*, const GrUserStencilSettings*, bool hasMixedSampledCoverage, + const GrCaps&, GrClampType, SkPMColor4f* inputColorOverride); + + bool isFinalized() const { return SkToBool(kFinalized_Flag & fFlags); } + + /** These are valid only for non-LCD coverage. */ + static const GrProcessorSet& EmptySet(); + static GrProcessorSet MakeEmptySet(); + static constexpr const Analysis EmptySetAnalysis() { return Analysis(Empty::kEmpty); } + +#ifdef SK_DEBUG + SkString dumpProcessors() const; +#endif + + void visitProxies(const GrOp::VisitProxyFunc& func) const { + for (int i = 0; i < this->numFragmentProcessors(); ++i) { + GrFragmentProcessor::TextureAccessIter iter(this->fragmentProcessor(i)); + while (const GrFragmentProcessor::TextureSampler* sampler = iter.next()) { + bool mipped = (GrSamplerState::Filter::kMipMap == sampler->samplerState().filter()); + func(sampler->proxy(), GrMipMapped(mipped)); + } + } + } + +private: + GrProcessorSet(Empty) : fXP((const GrXferProcessor*)nullptr), fFlags(kFinalized_Flag) {} + + int numFragmentProcessors() const { + return fFragmentProcessors.count() - fFragmentProcessorOffset; + } + + const GrFragmentProcessor* fragmentProcessor(int idx) const { + return fFragmentProcessors[idx + fFragmentProcessorOffset].get(); + } + + // This absurdly large limit allows Analysis and this to pack fields together. + static constexpr int kMaxColorProcessors = UINT8_MAX; + + enum Flags : uint16_t { kFinalized_Flag = 0x1 }; + + union XP { + XP(const GrXPFactory* factory) : fFactory(factory) {} + XP(const GrXferProcessor* processor) : fProcessor(processor) {} + explicit XP(XP&& that) : fProcessor(that.fProcessor) { + SkASSERT(fProcessor == that.fProcessor); + that.fProcessor = nullptr; + } + const GrXPFactory* fFactory; + const GrXferProcessor* fProcessor; + }; + + const GrXPFactory* xpFactory() const { + SkASSERT(!this->isFinalized()); + return fXP.fFactory; + } + + SkAutoSTArray<4, std::unique_ptr> fFragmentProcessors; + XP fXP; + uint8_t fColorFragmentProcessorCnt = 0; + uint8_t fFragmentProcessorOffset = 0; + uint8_t fFlags; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProcessorUnitTest.cpp b/gfx/skia/skia/src/gpu/GrProcessorUnitTest.cpp new file mode 100644 index 0000000000..8d8efa7685 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessorUnitTest.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrProcessorUnitTest.h" + +#if GR_TEST_UTILS + +std::unique_ptr GrProcessorUnitTest::MakeChildFP(GrProcessorTestData* data) { +#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS + std::unique_ptr fp; + do { + fp = GrFragmentProcessorTestFactory::Make(data); + SkASSERT(fp); + } while (fp->numChildProcessors() != 0); + return fp; +#else + SK_ABORT("Should not be called if !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS"); +#endif +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrProcessorUnitTest.h b/gfx/skia/skia/src/gpu/GrProcessorUnitTest.h new file mode 100644 index 0000000000..6b0443e2b1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProcessorUnitTest.h @@ -0,0 +1,220 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProcessorUnitTest_DEFINED +#define GrProcessorUnitTest_DEFINED + +#include "include/core/SkTypes.h" + +#if GR_TEST_UTILS + +#include "include/private/SkTArray.h" +#include "src/gpu/GrTestUtils.h" +#include "src/gpu/GrTextureProxy.h" + +class SkMatrix; +class GrCaps; +class GrContext; +class GrProxyProvider; +class GrRenderTargetContext; +struct GrProcessorTestData; +class GrTexture; +class GrXPFactory; +class GrGeometryProcessor; + +namespace GrProcessorUnitTest { + +// Used to access the dummy textures in TestCreate procs. +enum { + kSkiaPMTextureIdx = 0, + kAlphaTextureIdx = 1, +}; + +/** This allows parent FPs to implement a test create with known leaf children in order to avoid +creating an unbounded FP tree which may overflow various shader limits. */ +std::unique_ptr MakeChildFP(GrProcessorTestData*); + +} + +/* + * GrProcessorTestData is an argument struct to TestCreate functions + * fTextures are valid textures that can optionally be used to construct + * TextureSampler. The first texture has config kSkia8888_GrPixelConfig and the second has + * kAlpha_8_GrPixelConfig. TestCreate functions are also free to create additional textures using + * the GrContext. + */ +struct GrProcessorTestData { + GrProcessorTestData(SkRandom* random, + GrContext* context, + const GrRenderTargetContext* renderTargetContext, + sk_sp proxies[2], + GrColorType proxyColorTypes[2]) + : fRandom(random) + , fRenderTargetContext(renderTargetContext) + , fContext(context) { + SkASSERT(proxies[0] && proxies[1]); + fProxies[0] = proxies[0]; + fProxies[1] = proxies[1]; + fProxyColorTypes[0] = proxyColorTypes[0]; + fProxyColorTypes[1] = proxyColorTypes[1]; + } + SkRandom* fRandom; + const GrRenderTargetContext* fRenderTargetContext; + + GrContext* context() { return fContext; } + GrResourceProvider* resourceProvider(); + GrProxyProvider* proxyProvider(); + const GrCaps* caps(); + sk_sp textureProxy(int index) { return fProxies[index]; } + GrColorType textureProxyColorType(int index) { return fProxyColorTypes[index]; } + +private: + GrContext* fContext; + sk_sp fProxies[2]; + GrColorType fProxyColorTypes[2]; +}; + +class GrProcessor; +class GrTexture; + +template +class GrProcessorTestFactory : private SkNoncopyable { +public: + using Processor = typename ProcessorSmartPtr::element_type; + using MakeProc = ProcessorSmartPtr (*)(GrProcessorTestData*); + + GrProcessorTestFactory(MakeProc makeProc) { + fMakeProc = makeProc; + GetFactories()->push_back(this); + } + + /** Pick a random factory function and create a processor. */ + static ProcessorSmartPtr Make(GrProcessorTestData* data) { + VerifyFactoryCount(); + if (GetFactories()->count() == 0) { + return nullptr; + } + uint32_t idx = data->fRandom->nextRangeU(0, GetFactories()->count() - 1); + return MakeIdx(idx, data); + } + + /** Number of registered factory functions */ + static int Count() { return GetFactories()->count(); } + + /** Use factory function at Index idx to create a processor. */ + static ProcessorSmartPtr MakeIdx(int idx, GrProcessorTestData* data) { + SkASSERT(idx < GetFactories()->count()); + GrProcessorTestFactory* factory = (*GetFactories())[idx]; + ProcessorSmartPtr processor = factory->fMakeProc(data); + SkASSERT(processor); + return processor; + } + +private: + /** + * A test function which verifies the count of factories. + */ + static void VerifyFactoryCount(); + + MakeProc fMakeProc; + + static SkTArray*, true>* GetFactories(); +}; + +using GrFragmentProcessorTestFactory = GrProcessorTestFactory>; +using GrGeometryProcessorTestFactory = GrProcessorTestFactory>; + +class GrXPFactoryTestFactory : private SkNoncopyable { +public: + using GetFn = const GrXPFactory*(GrProcessorTestData*); + + GrXPFactoryTestFactory(GetFn* getProc) : fGetProc(getProc) { GetFactories()->push_back(this); } + + static const GrXPFactory* Get(GrProcessorTestData* data) { + VerifyFactoryCount(); + if (GetFactories()->count() == 0) { + return nullptr; + } + uint32_t idx = data->fRandom->nextRangeU(0, GetFactories()->count() - 1); + const GrXPFactory* xpf = (*GetFactories())[idx]->fGetProc(data); + SkASSERT(xpf); + return xpf; + } + +private: + static void VerifyFactoryCount(); + + GetFn* fGetProc; + static SkTArray* GetFactories(); +}; + +#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS + +/** GrProcessor subclasses should insert this macro in their declaration to be included in the + * program generation unit test. + */ +#define GR_DECLARE_GEOMETRY_PROCESSOR_TEST \ + static GrGeometryProcessorTestFactory gTestFactory SK_UNUSED; \ + static sk_sp TestCreate(GrProcessorTestData*); + +#define GR_DECLARE_FRAGMENT_PROCESSOR_TEST \ + static GrFragmentProcessorTestFactory gTestFactory SK_UNUSED; \ + static std::unique_ptr TestCreate(GrProcessorTestData*); + +#define GR_DECLARE_XP_FACTORY_TEST \ + static GrXPFactoryTestFactory gTestFactory SK_UNUSED; \ + static const GrXPFactory* TestGet(GrProcessorTestData*); + +/** GrProcessor subclasses should insert this macro in their implementation file. They must then + * also implement this static function: + * GrProcessor* TestCreate(GrProcessorTestData*); + */ +#define GR_DEFINE_FRAGMENT_PROCESSOR_TEST(Effect) \ + GrFragmentProcessorTestFactory Effect::gTestFactory(Effect::TestCreate) + +#define GR_DEFINE_GEOMETRY_PROCESSOR_TEST(Effect) \ + GrGeometryProcessorTestFactory Effect::gTestFactory(Effect::TestCreate) + +#define GR_DEFINE_XP_FACTORY_TEST(Factory) \ + GrXPFactoryTestFactory Factory::gTestFactory(Factory::TestGet) + +#else // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS + +// The unit test relies on static initializers. Just declare the TestCreate function so that +// its definitions will compile. +#define GR_DECLARE_FRAGMENT_PROCESSOR_TEST \ + static std::unique_ptr TestCreate(GrProcessorTestData*); +#define GR_DEFINE_FRAGMENT_PROCESSOR_TEST(X) + +// The unit test relies on static initializers. Just declare the TestCreate function so that +// its definitions will compile. +#define GR_DECLARE_GEOMETRY_PROCESSOR_TEST \ + static sk_sp TestCreate(GrProcessorTestData*); +#define GR_DEFINE_GEOMETRY_PROCESSOR_TEST(X) + +// The unit test relies on static initializers. Just declare the TestGet function so that +// its definitions will compile. +#define GR_DECLARE_XP_FACTORY_TEST \ + const GrXPFactory* TestGet(GrProcessorTestData*); +#define GR_DEFINE_XP_FACTORY_TEST(X) + +#endif // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS +#else // GR_TEST_UTILS + #define GR_DECLARE_GEOMETRY_PROCESSOR_TEST + #define GR_DECLARE_FRAGMENT_PROCESSOR_TEST + #define GR_DECLARE_XP_FACTORY_TEST + #define GR_DEFINE_FRAGMENT_PROCESSOR_TEST(...) + #define GR_DEFINE_GEOMETRY_PROCESSOR_TEST(...) + #define GR_DEFINE_XP_FACTORY_TEST(...) + #define GR_DECLARE_FRAGMENT_PROCESSOR_TEST + #define GR_DEFINE_FRAGMENT_PROCESSOR_TEST(...) + #define GR_DECLARE_GEOMETRY_PROCESSOR_TEST + #define GR_DEFINE_GEOMETRY_PROCESSOR_TEST(...) + #define GR_DECLARE_XP_FACTORY_TEST + #define GR_DEFINE_XP_FACTORY_TEST(...) +#endif // GR_TEST_UTILS +#endif // GrProcessorUnitTest_DEFINED diff --git a/gfx/skia/skia/src/gpu/GrProgramDesc.cpp b/gfx/skia/skia/src/gpu/GrProgramDesc.cpp new file mode 100644 index 0000000000..9d42d694a5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProgramDesc.cpp @@ -0,0 +1,268 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrProgramDesc.h" + +#include "include/private/SkChecksum.h" +#include "include/private/SkTo.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrPrimitiveProcessor.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +enum { + kSamplerOrImageTypeKeyBits = 4 +}; + +static inline uint16_t texture_type_key(GrTextureType type) { + int value = UINT16_MAX; + switch (type) { + case GrTextureType::k2D: + value = 0; + break; + case GrTextureType::kExternal: + value = 1; + break; + case GrTextureType::kRectangle: + value = 2; + break; + default: + SK_ABORT("Unexpected texture type"); + value = 3; + break; + } + SkASSERT((value & ((1 << kSamplerOrImageTypeKeyBits) - 1)) == value); + return SkToU16(value); +} + +static uint32_t sampler_key(GrTextureType textureType, const GrSwizzle& swizzle, + const GrShaderCaps& caps) { + int samplerTypeKey = texture_type_key(textureType); + + GR_STATIC_ASSERT(2 == sizeof(swizzle.asKey())); + uint16_t swizzleKey = 0; + if (caps.textureSwizzleAppliedInShader()) { + swizzleKey = swizzle.asKey(); + } + return SkToU32(samplerTypeKey | swizzleKey << kSamplerOrImageTypeKeyBits); +} + +static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrFragmentProcessor& fp, + GrGpu* gpu, const GrShaderCaps& caps) { + int numTextureSamplers = fp.numTextureSamplers(); + if (!numTextureSamplers) { + return; + } + for (int i = 0; i < numTextureSamplers; ++i) { + const GrFragmentProcessor::TextureSampler& sampler = fp.textureSampler(i); + const GrTexture* tex = sampler.peekTexture(); + uint32_t samplerKey = sampler_key( + tex->texturePriv().textureType(), sampler.swizzle(), caps); + uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram( + sampler.samplerState(), sampler.proxy()->backendFormat()); + if (extraSamplerKey) { + // We first mark the normal sampler key with last bit to flag that it has an extra + // sampler key. We then add both keys. + SkASSERT((samplerKey & (1 << 31)) == 0); + b->add32(samplerKey | (1 << 31)); + b->add32(extraSamplerKey); + } else { + b->add32(samplerKey); + } + } +} + +static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrPrimitiveProcessor& pp, + const GrShaderCaps& caps) { + int numTextureSamplers = pp.numTextureSamplers(); + if (!numTextureSamplers) { + return; + } + for (int i = 0; i < numTextureSamplers; ++i) { + const GrPrimitiveProcessor::TextureSampler& sampler = pp.textureSampler(i); + uint32_t samplerKey = sampler_key( + sampler.textureType(), sampler.swizzle(), caps); + uint32_t extraSamplerKey = sampler.extraSamplerKey(); + if (extraSamplerKey) { + // We first mark the normal sampler key with last bit to flag that it has an extra + // sampler key. We then add both keys. + SkASSERT((samplerKey & (1 << 31)) == 0); + b->add32(samplerKey | (1 << 31)); + b->add32(extraSamplerKey); + } else { + b->add32(samplerKey); + } + } +} + +/** + * A function which emits a meta key into the key builder. This is required because shader code may + * be dependent on properties of the effect that the effect itself doesn't use + * in its key (e.g. the pixel format of textures used). So we create a meta-key for + * every effect using this function. It is also responsible for inserting the effect's class ID + * which must be different for every GrProcessor subclass. It can fail if an effect uses too many + * transforms, etc, for the space allotted in the meta-key. NOTE, both FPs and GPs share this + * function because it is hairy, though FPs do not have attribs, and GPs do not have transforms + */ +static bool gen_meta_key(const GrFragmentProcessor& fp, + GrGpu* gpu, + const GrShaderCaps& shaderCaps, + uint32_t transformKey, + GrProcessorKeyBuilder* b) { + size_t processorKeySize = b->size(); + uint32_t classID = fp.classID(); + + // Currently we allow 16 bits for the class id and the overall processor key size. + static const uint32_t kMetaKeyInvalidMask = ~((uint32_t)UINT16_MAX); + if ((processorKeySize | classID) & kMetaKeyInvalidMask) { + return false; + } + + add_sampler_keys(b, fp, gpu, shaderCaps); + + uint32_t* key = b->add32n(2); + key[0] = (classID << 16) | SkToU32(processorKeySize); + key[1] = transformKey; + return true; +} + +static bool gen_meta_key(const GrPrimitiveProcessor& pp, + const GrShaderCaps& shaderCaps, + uint32_t transformKey, + GrProcessorKeyBuilder* b) { + size_t processorKeySize = b->size(); + uint32_t classID = pp.classID(); + + // Currently we allow 16 bits for the class id and the overall processor key size. + static const uint32_t kMetaKeyInvalidMask = ~((uint32_t)UINT16_MAX); + if ((processorKeySize | classID) & kMetaKeyInvalidMask) { + return false; + } + + add_sampler_keys(b, pp, shaderCaps); + + uint32_t* key = b->add32n(2); + key[0] = (classID << 16) | SkToU32(processorKeySize); + key[1] = transformKey; + return true; +} + +static bool gen_meta_key(const GrXferProcessor& xp, + const GrShaderCaps& shaderCaps, + GrProcessorKeyBuilder* b) { + size_t processorKeySize = b->size(); + uint32_t classID = xp.classID(); + + // Currently we allow 16 bits for the class id and the overall processor key size. + static const uint32_t kMetaKeyInvalidMask = ~((uint32_t)UINT16_MAX); + if ((processorKeySize | classID) & kMetaKeyInvalidMask) { + return false; + } + + b->add32((classID << 16) | SkToU32(processorKeySize)); + return true; +} + +static bool gen_frag_proc_and_meta_keys(const GrPrimitiveProcessor& primProc, + const GrFragmentProcessor& fp, + GrGpu* gpu, + const GrShaderCaps& shaderCaps, + GrProcessorKeyBuilder* b) { + for (int i = 0; i < fp.numChildProcessors(); ++i) { + if (!gen_frag_proc_and_meta_keys(primProc, fp.childProcessor(i), gpu, shaderCaps, b)) { + return false; + } + } + + fp.getGLSLProcessorKey(shaderCaps, b); + + return gen_meta_key(fp, gpu, shaderCaps, primProc.getTransformKey(fp.coordTransforms(), + fp.numCoordTransforms()), b); +} + +bool GrProgramDesc::Build(GrProgramDesc* desc, const GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, GrPrimitiveType primitiveType, + GrGpu* gpu) { + // The descriptor is used as a cache key. Thus when a field of the + // descriptor will not affect program generation (because of the attribute + // bindings in use or other descriptor field settings) it should be set + // to a canonical value to avoid duplicate programs with different keys. + + const GrShaderCaps& shaderCaps = *gpu->caps()->shaderCaps(); + + GR_STATIC_ASSERT(0 == kProcessorKeysOffset % sizeof(uint32_t)); + // Make room for everything up to the effect keys. + desc->key().reset(); + desc->key().push_back_n(kProcessorKeysOffset); + + GrProcessorKeyBuilder b(&desc->key()); + + programInfo.primProc().getGLSLProcessorKey(shaderCaps, &b); + programInfo.primProc().getAttributeKey(&b); + if (!gen_meta_key(programInfo.primProc(), shaderCaps, 0, &b)) { + desc->key().reset(); + return false; + } + + for (int i = 0; i < programInfo.pipeline().numFragmentProcessors(); ++i) { + const GrFragmentProcessor& fp = programInfo.pipeline().getFragmentProcessor(i); + if (!gen_frag_proc_and_meta_keys(programInfo.primProc(), fp, gpu, shaderCaps, &b)) { + desc->key().reset(); + return false; + } + } + + const GrXferProcessor& xp = programInfo.pipeline().getXferProcessor(); + const GrSurfaceOrigin* originIfDstTexture = nullptr; + GrSurfaceOrigin origin; + if (programInfo.pipeline().dstTextureProxy()) { + origin = programInfo.pipeline().dstTextureProxy()->origin(); + originIfDstTexture = &origin; + } + xp.getGLSLProcessorKey(shaderCaps, &b, originIfDstTexture); + if (!gen_meta_key(xp, shaderCaps, &b)) { + desc->key().reset(); + return false; + } + + if (programInfo.requestedFeatures() & GrProcessor::CustomFeatures::kSampleLocations) { + SkASSERT(programInfo.pipeline().isHWAntialiasState()); + b.add32(renderTarget->renderTargetPriv().getSamplePatternKey()); + } + + // --------DO NOT MOVE HEADER ABOVE THIS LINE-------------------------------------------------- + // Because header is a pointer into the dynamic array, we can't push any new data into the key + // below here. + KeyHeader* header = desc->atOffset(); + + // make sure any padding in the header is zeroed. + memset(header, 0, kHeaderSize); + header->fOutputSwizzle = programInfo.pipeline().outputSwizzle().asKey(); + header->fColorFragmentProcessorCnt = programInfo.pipeline().numColorFragmentProcessors(); + header->fCoverageFragmentProcessorCnt = programInfo.pipeline().numCoverageFragmentProcessors(); + // Fail if the client requested more processors than the key can fit. + if (header->fColorFragmentProcessorCnt != programInfo.pipeline().numColorFragmentProcessors() || + header->fCoverageFragmentProcessorCnt != + programInfo.pipeline().numCoverageFragmentProcessors()) { + return false; + } + // If we knew the shader won't depend on origin, we could skip this (and use the same program + // for both origins). Instrumenting all fragment processors would be difficult and error prone. + header->fSurfaceOriginKey = + GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(programInfo.origin()); + header->fProcessorFeatures = (uint8_t)programInfo.requestedFeatures(); + // Ensure enough bits. + SkASSERT(header->fProcessorFeatures == (int) programInfo.requestedFeatures()); + header->fSnapVerticesToPixelCenters = programInfo.pipeline().snapVerticesToPixelCenters(); + header->fHasPointSize = (primitiveType == GrPrimitiveType::kPoints); + return true; +} diff --git a/gfx/skia/skia/src/gpu/GrProgramDesc.h b/gfx/skia/skia/src/gpu/GrProgramDesc.h new file mode 100644 index 0000000000..391bfe1a80 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProgramDesc.h @@ -0,0 +1,144 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProgramDesc_DEFINED +#define GrProgramDesc_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTo.h" +#include "src/core/SkOpts.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +class GrProgramInfo; +class GrShaderCaps; + +/** This class describes a program to generate. It also serves as a program cache key */ +class GrProgramDesc { +public: + // Creates an uninitialized key that must be populated by GrGpu::buildProgramDesc() + GrProgramDesc() {} + + /** + * Builds a program descriptor. Before the descriptor can be used, the client must call finalize + * on the filled in GrProgramDesc. + * + * @param desc The built and finalized descriptor + * @param renderTarget The target of the draw + * @param programInfo Program information need to build the key + * @param primitiveType Controls whether the shader will output a point size. + * @param gpu Pointer to the GrGpu object the program will be used with. + **/ + static bool Build(GrProgramDesc*, const GrRenderTarget*, const GrProgramInfo&, + GrPrimitiveType, GrGpu*); + + // This is strictly an OpenGL call since the other backends have additional data in their + // keys + static bool BuildFromData(GrProgramDesc* desc, const void* keyData, size_t keyLength) { + if (!SkTFitsIn(keyLength)) { + return false; + } + desc->fKey.reset(SkToInt(keyLength)); + memcpy(desc->fKey.begin(), keyData, keyLength); + return true; + } + + // Returns this as a uint32_t array to be used as a key in the program cache. + const uint32_t* asKey() const { + return reinterpret_cast(fKey.begin()); + } + + // Gets the number of bytes in asKey(). It will be a 4-byte aligned value. + uint32_t keyLength() const { + SkASSERT(0 == (fKey.count() % 4)); + return fKey.count(); + } + + GrProgramDesc& operator= (const GrProgramDesc& other) { + uint32_t keyLength = other.keyLength(); + fKey.reset(SkToInt(keyLength)); + memcpy(fKey.begin(), other.fKey.begin(), keyLength); + return *this; + } + + bool operator== (const GrProgramDesc& that) const { + if (this->keyLength() != that.keyLength()) { + return false; + } + + SkASSERT(SkIsAlign4(this->keyLength())); + int l = this->keyLength() >> 2; + const uint32_t* aKey = this->asKey(); + const uint32_t* bKey = that.asKey(); + for (int i = 0; i < l; ++i) { + if (aKey[i] != bKey[i]) { + return false; + } + } + return true; + } + + bool operator!= (const GrProgramDesc& other) const { + return !(*this == other); + } + + // TODO: remove this use of the header + bool hasPointSize() const { return this->header().fHasPointSize; } + +protected: + struct KeyHeader { + // Set to uniquely identify any swizzling of the shader's output color(s). + uint16_t fOutputSwizzle; + uint8_t fColorFragmentProcessorCnt; // Can be packed into 4 bits if required. + uint8_t fCoverageFragmentProcessorCnt; + // Set to uniquely identify the rt's origin, or 0 if the shader does not require this info. + uint8_t fSurfaceOriginKey : 2; + uint8_t fProcessorFeatures : 1; + bool fSnapVerticesToPixelCenters : 1; + bool fHasPointSize : 1; + uint8_t fPad : 3; + }; + GR_STATIC_ASSERT(sizeof(KeyHeader) == 6); + + const KeyHeader& header() const { return *this->atOffset(); } + + template T* atOffset() { + return reinterpret_cast(reinterpret_cast(fKey.begin()) + OFFSET); + } + + template const T* atOffset() const { + return reinterpret_cast(reinterpret_cast(fKey.begin()) + OFFSET); + } + + // The key, stored in fKey, is composed of two parts: + // 1. Header struct defined above. + // 2. A Backend specific payload which includes the per-processor keys. + enum KeyOffsets { + kHeaderOffset = 0, + kHeaderSize = SkAlign4(sizeof(KeyHeader)), + // Part 4. + // This is the offset into the backenend specific part of the key, which includes + // per-processor keys. + kProcessorKeysOffset = kHeaderOffset + kHeaderSize, + }; + + enum { + kMaxPreallocProcessors = 8, + kIntsPerProcessor = 4, // This is an overestimate of the average effect key size. + kPreAllocSize = kHeaderOffset + kHeaderSize + + kMaxPreallocProcessors * sizeof(uint32_t) * kIntsPerProcessor, + }; + + SkSTArray& key() { return fKey; } + const SkSTArray& key() const { return fKey; } + +private: + SkSTArray fKey; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProgramInfo.cpp b/gfx/skia/skia/src/gpu/GrProgramInfo.cpp new file mode 100644 index 0000000000..3391b61c27 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProgramInfo.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrProgramInfo.h" + + +#ifdef SK_DEBUG +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrTexturePriv.h" + +void GrProgramInfo::validate() const { + SkASSERT(!fPipeline.isBad()); + + if (this->hasDynamicPrimProcTextures()) { + SkASSERT(!this->hasFixedPrimProcTextures()); + SkASSERT(fPrimProc.numTextureSamplers()); + } else if (this->hasFixedPrimProcTextures()) { + SkASSERT(fPrimProc.numTextureSamplers()); + } else { + SkASSERT(!fPrimProc.numTextureSamplers()); + } + + SkASSERT(!fPipeline.isScissorEnabled() || this->hasFixedScissor() || + this->hasDynamicScissors()); + + if (this->hasDynamicPrimProcTextures()) { + // Check that, for a given sampler, the properties of the dynamic textures remain + // the same for all the meshes + for (int s = 0; s < this->primProc().numTextureSamplers(); ++s) { + auto dynamicPrimProcTextures = this->dynamicPrimProcTextures(0); + + const GrBackendFormat& format = dynamicPrimProcTextures[s]->backendFormat(); + GrTextureType type = dynamicPrimProcTextures[s]->textureType(); + GrPixelConfig config = dynamicPrimProcTextures[s]->config(); + + for (int m = 1; m < fNumDynamicStateArrays; ++m) { + dynamicPrimProcTextures = this->dynamicPrimProcTextures(m); + + auto testProxy = dynamicPrimProcTextures[s]; + SkASSERT(testProxy->backendFormat() == format); + SkASSERT(testProxy->textureType() == type); + SkASSERT(testProxy->config() == config); + } + } + } +} + +void GrProgramInfo::checkAllInstantiated() const { + if (this->hasFixedPrimProcTextures()) { + auto fixedPrimProcTextures = this->fixedPrimProcTextures(); + for (int s = 0; s < this->primProc().numTextureSamplers(); ++s) { + SkASSERT(fixedPrimProcTextures[s]->isInstantiated()); + } + } + + if (this->hasDynamicPrimProcTextures()) { + for (int m = 0; m < fNumDynamicStateArrays; ++m) { + auto dynamicPrimProcTextures = this->dynamicPrimProcTextures(m); + for (int s = 0; s < this->primProc().numTextureSamplers(); ++s) { + SkASSERT(dynamicPrimProcTextures[s]->isInstantiated()); + } + } + } +} + +void GrProgramInfo::checkMSAAAndMIPSAreResolved() const { + + auto assertResolved = [](GrTexture* tex, const GrSamplerState& sampler) { + SkASSERT(tex); + + // Ensure mipmaps were all resolved ahead of time by the DAG. + if (GrSamplerState::Filter::kMipMap == sampler.filter() && + (tex->width() != 1 || tex->height() != 1)) { + // There are some cases where we might be given a non-mipmapped texture with a mipmap + // filter. See skbug.com/7094. + SkASSERT(tex->texturePriv().mipMapped() != GrMipMapped::kYes || + !tex->texturePriv().mipMapsAreDirty()); + } + }; + + if (this->hasDynamicPrimProcTextures()) { + for (int m = 0; m < fNumDynamicStateArrays; ++m) { + auto dynamicPrimProcTextures = this->dynamicPrimProcTextures(m); + + for (int s = 0; s < this->primProc().numTextureSamplers(); ++s) { + auto* tex = dynamicPrimProcTextures[s]->peekTexture(); + assertResolved(tex, this->primProc().textureSampler(s).samplerState()); + } + } + } else if (this->hasFixedPrimProcTextures()) { + auto fixedPrimProcTextures = this->fixedPrimProcTextures(); + + for (int s = 0; s < this->primProc().numTextureSamplers(); ++s) { + auto* tex = fixedPrimProcTextures[s]->peekTexture(); + assertResolved(tex, this->primProc().textureSampler(s).samplerState()); + } + } + + GrFragmentProcessor::Iter iter(this->pipeline()); + while (const GrFragmentProcessor* fp = iter.next()) { + for (int s = 0; s < fp->numTextureSamplers(); ++s) { + const auto& textureSampler = fp->textureSampler(s); + assertResolved(textureSampler.peekTexture(), textureSampler.samplerState()); + } + } +} + +void GrProgramInfo::compatibleWithMeshes(const GrMesh meshes[], int meshCount) const { + SkASSERT(!fNumDynamicStateArrays || meshCount == fNumDynamicStateArrays); + + for (int i = 0; i < meshCount; ++i) { + SkASSERT(fPrimProc.hasVertexAttributes() == meshes[i].hasVertexData()); + SkASSERT(fPrimProc.hasInstanceAttributes() == meshes[i].hasInstanceData()); + } +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProgramInfo.h b/gfx/skia/skia/src/gpu/GrProgramInfo.h new file mode 100644 index 0000000000..a4c46e6749 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProgramInfo.h @@ -0,0 +1,115 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProgramInfo_DEFINED +#define GrProgramInfo_DEFINED + +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrPrimitiveProcessor.h" + +class GrMesh; + +class GrProgramInfo { +public: + GrProgramInfo(int numSamples, + GrSurfaceOrigin origin, + const GrPipeline& pipeline, + const GrPrimitiveProcessor& primProc, + const GrPipeline::FixedDynamicState* fixedDynamicState, + const GrPipeline::DynamicStateArrays* dynamicStateArrays, + int numDynamicStateArrays) + : fNumSamples(numSamples) + , fOrigin(origin) + , fPipeline(pipeline) + , fPrimProc(primProc) + , fFixedDynamicState(fixedDynamicState) + , fDynamicStateArrays(dynamicStateArrays) + , fNumDynamicStateArrays(numDynamicStateArrays) { + fRequestedFeatures = fPrimProc.requestedFeatures(); + for (int i = 0; i < fPipeline.numFragmentProcessors(); ++i) { + fRequestedFeatures |= fPipeline.getFragmentProcessor(i).requestedFeatures(); + } + fRequestedFeatures |= fPipeline.getXferProcessor().requestedFeatures(); + + SkDEBUGCODE(this->validate();) + (void) fNumDynamicStateArrays; // touch this to quiet unused member warnings + } + + GrProcessor::CustomFeatures requestedFeatures() const { return fRequestedFeatures; } + + int numSamples() const { return fNumSamples; } + GrSurfaceOrigin origin() const { return fOrigin; } + const GrPipeline& pipeline() const { return fPipeline; } + const GrPrimitiveProcessor& primProc() const { return fPrimProc; } + const GrPipeline::FixedDynamicState* fixedDynamicState() const { return fFixedDynamicState; } + + bool hasDynamicScissors() const { + return fPipeline.isScissorEnabled() && + fDynamicStateArrays && fDynamicStateArrays->fScissorRects; + } + + const SkIRect& dynamicScissor(int i) const { + SkASSERT(this->hasDynamicScissors()); + + return fDynamicStateArrays->fScissorRects[i]; + } + + bool hasFixedScissor() const { return fPipeline.isScissorEnabled() && fFixedDynamicState; } + + const SkIRect& fixedScissor() const { + SkASSERT(this->hasFixedScissor()); + + return fFixedDynamicState->fScissorRect; + } + + bool hasDynamicPrimProcTextures() const { + return fDynamicStateArrays && fDynamicStateArrays->fPrimitiveProcessorTextures; + } + + const GrTextureProxy* const* dynamicPrimProcTextures(int i) const { + SkASSERT(this->hasDynamicPrimProcTextures()); + SkASSERT(i < fNumDynamicStateArrays); + + return fDynamicStateArrays->fPrimitiveProcessorTextures + + i * fPrimProc.numTextureSamplers(); + } + + bool hasFixedPrimProcTextures() const { + return fFixedDynamicState && fFixedDynamicState->fPrimitiveProcessorTextures; + } + + const GrTextureProxy* const* fixedPrimProcTextures() const { + SkASSERT(this->hasFixedPrimProcTextures()); + + return fFixedDynamicState->fPrimitiveProcessorTextures; + } + +#ifdef SK_DEBUG + void validate() const; + void checkAllInstantiated() const; + void checkMSAAAndMIPSAreResolved() const; + void compatibleWithMeshes(const GrMesh meshes[], int meshCount) const; + + bool isNVPR() const { + return fPrimProc.isPathRendering() && !fPrimProc.willUseGeoShader() && + !fPrimProc.numVertexAttributes() && !fPrimProc.numInstanceAttributes(); + } +#endif + +private: + const int fNumSamples; + const GrSurfaceOrigin fOrigin; + const GrPipeline& fPipeline; + const GrPrimitiveProcessor& fPrimProc; + const GrPipeline::FixedDynamicState* fFixedDynamicState; + const GrPipeline::DynamicStateArrays* fDynamicStateArrays; + const int fNumDynamicStateArrays; + GrProcessor::CustomFeatures fRequestedFeatures; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrProxyProvider.cpp b/gfx/skia/skia/src/gpu/GrProxyProvider.cpp new file mode 100644 index 0000000000..2c7153ae7e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProxyProvider.cpp @@ -0,0 +1,958 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrProxyProvider.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkImage.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrImageContext.h" +#include "include/private/GrResourceKey.h" +#include "include/private/GrSingleOwner.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrImageContextPriv.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureProxyCacheAccess.h" +#include "src/gpu/GrTextureRenderTargetProxy.h" +#include "src/gpu/SkGr.h" +#include "src/image/SkImage_Base.h" + +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fImageContext->priv().singleOwner());) + +GrProxyProvider::GrProxyProvider(GrImageContext* imageContext) : fImageContext(imageContext) {} + +GrProxyProvider::~GrProxyProvider() { + if (this->renderingDirectly()) { + // In DDL-mode a proxy provider can still have extant uniquely keyed proxies (since + // they need their unique keys to, potentially, find a cached resource when the + // DDL is played) but, in non-DDL-mode they should all have been cleaned up by this point. + SkASSERT(!fUniquelyKeyedProxies.count()); + } +} + +bool GrProxyProvider::assignUniqueKeyToProxy(const GrUniqueKey& key, GrTextureProxy* proxy) { + ASSERT_SINGLE_OWNER + SkASSERT(key.isValid()); + if (this->isAbandoned() || !proxy) { + return false; + } + +#ifdef SK_DEBUG + { + GrContext* direct = fImageContext->priv().asDirectContext(); + if (direct) { + GrResourceCache* resourceCache = direct->priv().getResourceCache(); + // If there is already a GrResource with this key then the caller has violated the + // normal usage pattern of uniquely keyed resources (e.g., they have created one w/o + // first seeing if it already existed in the cache). + SkASSERT(!resourceCache->findAndRefUniqueResource(key)); + } + } +#endif + + SkASSERT(!fUniquelyKeyedProxies.find(key)); // multiple proxies can't get the same key + + proxy->cacheAccess().setUniqueKey(this, key); + SkASSERT(proxy->getUniqueKey() == key); + fUniquelyKeyedProxies.add(proxy); + return true; +} + +void GrProxyProvider::adoptUniqueKeyFromSurface(GrTextureProxy* proxy, const GrSurface* surf) { + SkASSERT(surf->getUniqueKey().isValid()); + proxy->cacheAccess().setUniqueKey(this, surf->getUniqueKey()); + SkASSERT(proxy->getUniqueKey() == surf->getUniqueKey()); + // multiple proxies can't get the same key + SkASSERT(!fUniquelyKeyedProxies.find(surf->getUniqueKey())); + fUniquelyKeyedProxies.add(proxy); +} + +void GrProxyProvider::removeUniqueKeyFromProxy(GrTextureProxy* proxy) { + ASSERT_SINGLE_OWNER + SkASSERT(proxy); + SkASSERT(proxy->getUniqueKey().isValid()); + + if (this->isAbandoned()) { + return; + } + + this->processInvalidUniqueKey(proxy->getUniqueKey(), proxy, InvalidateGPUResource::kYes); +} + +sk_sp GrProxyProvider::findProxyByUniqueKey(const GrUniqueKey& key, + GrSurfaceOrigin origin) { + ASSERT_SINGLE_OWNER + + if (this->isAbandoned()) { + return nullptr; + } + + GrTextureProxy* proxy = fUniquelyKeyedProxies.find(key); + if (proxy) { + SkASSERT(proxy->refCnt() >= 1); + SkASSERT(proxy->origin() == origin); + return sk_ref_sp(proxy); + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS +sk_sp GrProxyProvider::testingOnly_createInstantiatedProxy( + const SkISize& size, + GrColorType colorType, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected) { + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + if (this->caps()->isFormatCompressed(format)) { + // TODO: Allow this to go to GrResourceProvider::createCompressedTexture() once we no longer + // rely on GrColorType to get to GrPixelConfig. Currently this will cause + // makeConfigSpecific() to assert because GrColorTypeToPixelConfig() never returns a + // compressed GrPixelConfig. + return nullptr; + } + GrSurfaceDesc desc; + desc.fConfig = GrColorTypeToPixelConfig(colorType); + desc.fConfig = this->caps()->makeConfigSpecific(desc.fConfig, format); + desc.fWidth = size.width(); + desc.fHeight = size.height(); + + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + sk_sp tex; + + if (SkBackingFit::kApprox == fit) { + tex = resourceProvider->createApproxTexture(desc, format, renderable, renderTargetSampleCnt, + isProtected); + } else { + tex = resourceProvider->createTexture(desc, format, renderable, renderTargetSampleCnt, + GrMipMapped::kNo, budgeted, isProtected); + } + if (!tex) { + return nullptr; + } + + return this->createWrapped(std::move(tex), colorType, origin, UseAllocator::kYes); +} + +sk_sp GrProxyProvider::testingOnly_createInstantiatedProxy( + const SkISize& size, + GrColorType colorType, + GrRenderable renderable, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected) { + auto format = this->caps()->getDefaultBackendFormat(colorType, renderable); + return this->testingOnly_createInstantiatedProxy(size, + colorType, + format, + renderable, + renderTargetSampleCnt, + origin, + fit, + budgeted, + isProtected); +} + +sk_sp GrProxyProvider::testingOnly_createWrapped(sk_sp tex, + GrColorType colorType, + GrSurfaceOrigin origin) { + return this->createWrapped(std::move(tex), colorType, origin, UseAllocator::kYes); +} +#endif + +sk_sp GrProxyProvider::createWrapped(sk_sp tex, + GrColorType colorType, + GrSurfaceOrigin origin, + UseAllocator useAllocator) { +#ifdef SK_DEBUG + if (tex->getUniqueKey().isValid()) { + SkASSERT(!this->findProxyByUniqueKey(tex->getUniqueKey(), origin)); + } +#endif + GrSwizzle texSwizzle = this->caps()->getTextureSwizzle(tex->backendFormat(), colorType); + + if (tex->asRenderTarget()) { + GrSwizzle outSwizzle = this->caps()->getOutputSwizzle(tex->backendFormat(), colorType); + return sk_sp(new GrTextureRenderTargetProxy( + std::move(tex), origin, texSwizzle, outSwizzle, useAllocator)); + } else { + return sk_sp( + new GrTextureProxy(std::move(tex), origin, texSwizzle, useAllocator)); + } +} + +sk_sp GrProxyProvider::findOrCreateProxyByUniqueKey(const GrUniqueKey& key, + GrColorType colorType, + GrSurfaceOrigin origin, + UseAllocator useAllocator) { + ASSERT_SINGLE_OWNER + + if (this->isAbandoned()) { + return nullptr; + } + + sk_sp result = this->findProxyByUniqueKey(key, origin); + if (result) { + return result; + } + + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + GrResourceCache* resourceCache = direct->priv().getResourceCache(); + + GrGpuResource* resource = resourceCache->findAndRefUniqueResource(key); + if (!resource) { + return nullptr; + } + + sk_sp texture(static_cast(resource)->asTexture()); + SkASSERT(texture); + + result = this->createWrapped(std::move(texture), colorType, origin, useAllocator); + SkASSERT(result->getUniqueKey() == key); + // createWrapped should've added this for us + SkASSERT(fUniquelyKeyedProxies.find(key)); + SkASSERT(result->textureSwizzle() == + this->caps()->getTextureSwizzle(result->backendFormat(), colorType)); + return result; +} + +sk_sp GrProxyProvider::createTextureProxy(sk_sp srcImage, + int sampleCnt, + SkBudgeted budgeted, + SkBackingFit fit, + GrInternalSurfaceFlags surfaceFlags) { + ASSERT_SINGLE_OWNER + SkASSERT(srcImage); + + if (this->isAbandoned()) { + return nullptr; + } + + const SkImageInfo& info = srcImage->imageInfo(); + GrColorType ct = SkColorTypeToGrColorType(info.colorType()); + + GrBackendFormat format = this->caps()->getDefaultBackendFormat(ct, GrRenderable::kNo); + + if (!format.isValid()) { + SkBitmap copy8888; + if (!copy8888.tryAllocPixels(info.makeColorType(kRGBA_8888_SkColorType)) || + !srcImage->readPixels(copy8888.pixmap(), 0, 0)) { + return nullptr; + } + copy8888.setImmutable(); + srcImage = SkMakeImageFromRasterBitmap(copy8888, kNever_SkCopyPixelsMode); + ct = GrColorType::kRGBA_8888; + format = this->caps()->getDefaultBackendFormat(ct, GrRenderable::kNo); + if (!format.isValid()) { + return nullptr; + } + } + + GrPixelConfig config = GrColorTypeToPixelConfig(ct); + if (kUnknown_GrPixelConfig == config) { + return nullptr; + } + + GrSurfaceDesc desc; + desc.fWidth = srcImage->width(); + desc.fHeight = srcImage->height(); + desc.fConfig = config; + + sk_sp proxy = this->createLazyProxy( + [desc, format, sampleCnt, budgeted, srcImage, fit, + ct](GrResourceProvider* resourceProvider) { + SkPixmap pixMap; + SkAssertResult(srcImage->peekPixels(&pixMap)); + GrMipLevel mipLevel = { pixMap.addr(), pixMap.rowBytes() }; + + return LazyCallbackResult(resourceProvider->createTexture( + desc, format, ct, GrRenderable::kNo, sampleCnt, budgeted, fit, + GrProtected::kNo, mipLevel)); + }, + format, desc, GrRenderable::kNo, sampleCnt, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, + GrMipMapsStatus::kNotAllocated, surfaceFlags, fit, budgeted, GrProtected::kNo, + UseAllocator::kYes); + + if (!proxy) { + return nullptr; + } + + GrContext* direct = fImageContext->priv().asDirectContext(); + if (direct) { + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + + // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however + // we're better off instantiating the proxy immediately here. + if (!proxy->priv().doLazyInstantiation(resourceProvider)) { + return nullptr; + } + } + + SkASSERT(proxy->width() == desc.fWidth); + SkASSERT(proxy->height() == desc.fHeight); + return proxy; +} + +sk_sp GrProxyProvider::createProxyFromBitmap(const SkBitmap& bitmap, + GrMipMapped mipMapped) { + ASSERT_SINGLE_OWNER + SkASSERT(GrMipMapped::kNo == mipMapped || this->caps()->mipMapSupport()); + + if (this->isAbandoned()) { + return nullptr; + } + + if (!SkImageInfoIsValid(bitmap.info())) { + return nullptr; + } + + ATRACE_ANDROID_FRAMEWORK("Upload %sTexture [%ux%u]", + GrMipMapped::kYes == mipMapped ? "MipMap " : "", + bitmap.width(), bitmap.height()); + + // In non-ddl we will always instantiate right away. Thus we never want to copy the SkBitmap + // even if its mutable. In ddl, if the bitmap is mutable then we must make a copy since the + // upload of the data to the gpu can happen at anytime and the bitmap may change by then. + SkCopyPixelsMode copyMode = this->renderingDirectly() ? kNever_SkCopyPixelsMode + : kIfMutable_SkCopyPixelsMode; + sk_sp baseLevel = SkMakeImageFromRasterBitmap(bitmap, copyMode); + if (!baseLevel) { + return nullptr; + } + + // If mips weren't requested (or this was too small to have any), then take the fast path + if (GrMipMapped::kNo == mipMapped || + 0 == SkMipMap::ComputeLevelCount(baseLevel->width(), baseLevel->height())) { + return this->createTextureProxy(std::move(baseLevel), 1, SkBudgeted::kYes, + SkBackingFit::kExact); + } + + GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(bitmap.info()); + + GrBackendFormat format = this->caps()->getDefaultBackendFormat( + SkColorTypeToGrColorType(bitmap.info().colorType()), GrRenderable::kNo); + if (!format.isValid()) { + SkBitmap copy8888; + if (!copy8888.tryAllocPixels(bitmap.info().makeColorType(kRGBA_8888_SkColorType)) || + !bitmap.readPixels(copy8888.pixmap())) { + return nullptr; + } + copy8888.setImmutable(); + baseLevel = SkMakeImageFromRasterBitmap(copy8888, kNever_SkCopyPixelsMode); + desc.fConfig = kRGBA_8888_GrPixelConfig; + format = this->caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, GrRenderable::kNo); + if (!format.isValid()) { + return nullptr; + } + } + + SkPixmap pixmap; + SkAssertResult(baseLevel->peekPixels(&pixmap)); + sk_sp mipmaps(SkMipMap::Build(pixmap, nullptr)); + if (!mipmaps) { + return nullptr; + } + + sk_sp proxy = this->createLazyProxy( + [desc, format, baseLevel, mipmaps](GrResourceProvider* resourceProvider) { + const int mipLevelCount = mipmaps->countLevels() + 1; + std::unique_ptr texels(new GrMipLevel[mipLevelCount]); + + SkPixmap pixmap; + SkAssertResult(baseLevel->peekPixels(&pixmap)); + + texels[0].fPixels = pixmap.addr(); + texels[0].fRowBytes = pixmap.rowBytes(); + + auto colorType = SkColorTypeToGrColorType(pixmap.colorType()); + for (int i = 1; i < mipLevelCount; ++i) { + SkMipMap::Level generatedMipLevel; + mipmaps->getLevel(i - 1, &generatedMipLevel); + texels[i].fPixels = generatedMipLevel.fPixmap.addr(); + texels[i].fRowBytes = generatedMipLevel.fPixmap.rowBytes(); + SkASSERT(texels[i].fPixels); + SkASSERT(generatedMipLevel.fPixmap.colorType() == pixmap.colorType()); + } + return LazyCallbackResult(resourceProvider->createTexture( + desc, format, colorType, GrRenderable::kNo, 1, SkBudgeted::kYes, + GrProtected::kNo, texels.get(), mipLevelCount)); + }, + format, desc, GrRenderable::kNo, 1, kTopLeft_GrSurfaceOrigin, GrMipMapped::kYes, + GrMipMapsStatus::kValid, GrInternalSurfaceFlags::kNone, SkBackingFit::kExact, + SkBudgeted::kYes, GrProtected::kNo, UseAllocator::kYes); + + if (!proxy) { + return nullptr; + } + + GrContext* direct = fImageContext->priv().asDirectContext(); + if (direct) { + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however + // we're better off instantiating the proxy immediately here. + if (!proxy->priv().doLazyInstantiation(resourceProvider)) { + return nullptr; + } + } + return proxy; +} + +sk_sp GrProxyProvider::createProxy(const GrBackendFormat& format, + const GrSurfaceDesc& desc, + GrRenderable renderable, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + GrSurfaceProxy::UseAllocator useAllocator) { + const GrCaps* caps = this->caps(); + + if (caps->isFormatCompressed(format)) { + // Deferred proxies for compressed textures are not supported. + return nullptr; + } + + GrColorType colorType = GrPixelConfigToColorType(desc.fConfig); + + SkASSERT(GrCaps::AreConfigsCompatible(desc.fConfig, + caps->getConfigFromBackendFormat(format, colorType))); + + if (GrMipMapped::kYes == mipMapped) { + // SkMipMap doesn't include the base level in the level count so we have to add 1 + int mipCount = SkMipMap::ComputeLevelCount(desc.fWidth, desc.fHeight) + 1; + if (1 == mipCount) { + mipMapped = GrMipMapped::kNo; + } + } + + if (!caps->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, renderable, + renderTargetSampleCnt, mipMapped)) { + return nullptr; + } + GrSurfaceDesc copyDesc = desc; + GrMipMapsStatus mipMapsStatus = (GrMipMapped::kYes == mipMapped) + ? GrMipMapsStatus::kDirty + : GrMipMapsStatus::kNotAllocated; + GrSwizzle texSwizzle = caps->getTextureSwizzle(format, colorType); + if (renderable == GrRenderable::kYes) { + renderTargetSampleCnt = + caps->getRenderTargetSampleCount(renderTargetSampleCnt, format); + SkASSERT(renderTargetSampleCnt); + // We know anything we instantiate later from this deferred path will be + // both texturable and renderable + GrSwizzle outSwizzle = caps->getOutputSwizzle(format, colorType); + return sk_sp(new GrTextureRenderTargetProxy( + *caps, format, copyDesc, renderTargetSampleCnt, origin, mipMapped, mipMapsStatus, + texSwizzle, outSwizzle, fit, budgeted, isProtected, surfaceFlags, useAllocator)); + } + + return sk_sp(new GrTextureProxy(format, copyDesc, origin, mipMapped, + mipMapsStatus, texSwizzle, fit, budgeted, + isProtected, surfaceFlags, useAllocator)); +} + +sk_sp GrProxyProvider::createCompressedTextureProxy( + int width, int height, SkBudgeted budgeted, SkImage::CompressionType compressionType, + sk_sp data) { + + GrSurfaceDesc desc; + desc.fConfig = GrCompressionTypePixelConfig(compressionType); + desc.fWidth = width; + desc.fHeight = height; + + GrBackendFormat format = this->caps()->getBackendFormatFromCompressionType(compressionType); + + if (!this->caps()->isFormatTexturable(format)) { + return nullptr; + } + + sk_sp proxy = this->createLazyProxy( + [width, height, format, compressionType, budgeted, + data](GrResourceProvider* resourceProvider) { + return LazyCallbackResult(resourceProvider->createCompressedTexture( + width, height, format, compressionType, budgeted, data.get())); + }, + format, desc, GrRenderable::kNo, 1, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, + GrMipMapsStatus::kNotAllocated, GrInternalSurfaceFlags::kNone, SkBackingFit::kExact, + SkBudgeted::kYes, GrProtected::kNo, UseAllocator::kYes); + + if (!proxy) { + return nullptr; + } + + GrContext* direct = fImageContext->priv().asDirectContext(); + if (direct) { + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however + // we're better off instantiating the proxy immediately here. + if (!proxy->priv().doLazyInstantiation(resourceProvider)) { + return nullptr; + } + } + return proxy; +} + +sk_sp GrProxyProvider::wrapBackendTexture(const GrBackendTexture& backendTex, + GrColorType grColorType, + GrSurfaceOrigin origin, + GrWrapOwnership ownership, + GrWrapCacheable cacheable, + GrIOType ioType, + ReleaseProc releaseProc, + ReleaseContext releaseCtx) { + SkASSERT(ioType != kWrite_GrIOType); + if (this->isAbandoned()) { + return nullptr; + } + + // This is only supported on a direct GrContext. + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + const GrCaps* caps = this->caps(); + + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + + sk_sp tex = + resourceProvider->wrapBackendTexture(backendTex, grColorType, + ownership, cacheable, ioType); + if (!tex) { + return nullptr; + } + + if (releaseProc) { + tex->setRelease(releaseProc, releaseCtx); + } + + SkASSERT(!tex->asRenderTarget()); // Strictly a GrTexture + // Make sure we match how we created the proxy with SkBudgeted::kNo + SkASSERT(GrBudgetedType::kBudgeted != tex->resourcePriv().budgetedType()); + + GrSwizzle texSwizzle = caps->getTextureSwizzle(tex->backendFormat(), grColorType); + + return sk_sp( + new GrTextureProxy(std::move(tex), origin, texSwizzle, UseAllocator::kNo)); +} + +sk_sp GrProxyProvider::wrapRenderableBackendTexture( + const GrBackendTexture& backendTex, GrSurfaceOrigin origin, int sampleCnt, + GrColorType colorType, GrWrapOwnership ownership, GrWrapCacheable cacheable, + ReleaseProc releaseProc, ReleaseContext releaseCtx) { + if (this->isAbandoned()) { + return nullptr; + } + + // This is only supported on a direct GrContext. + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + const GrCaps* caps = this->caps(); + + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + + // TODO: This should have been checked and validated before getting into GrProxyProvider. + if (!caps->isFormatAsColorTypeRenderable(colorType, backendTex.getBackendFormat(), sampleCnt)) { + return nullptr; + } + + sampleCnt = caps->getRenderTargetSampleCount(sampleCnt, backendTex.getBackendFormat()); + SkASSERT(sampleCnt); + + sk_sp tex = resourceProvider->wrapRenderableBackendTexture(backendTex, sampleCnt, + colorType, ownership, + cacheable); + if (!tex) { + return nullptr; + } + + if (releaseProc) { + tex->setRelease(releaseProc, releaseCtx); + } + + SkASSERT(tex->asRenderTarget()); // A GrTextureRenderTarget + // Make sure we match how we created the proxy with SkBudgeted::kNo + SkASSERT(GrBudgetedType::kBudgeted != tex->resourcePriv().budgetedType()); + + GrSwizzle texSwizzle = caps->getTextureSwizzle(tex->backendFormat(), colorType); + GrSwizzle outSwizzle = caps->getOutputSwizzle(tex->backendFormat(), colorType); + + return sk_sp(new GrTextureRenderTargetProxy(std::move(tex), origin, texSwizzle, + outSwizzle, UseAllocator::kNo)); +} + +sk_sp GrProxyProvider::wrapBackendRenderTarget( + const GrBackendRenderTarget& backendRT, GrColorType grColorType, + GrSurfaceOrigin origin, ReleaseProc releaseProc, ReleaseContext releaseCtx) { + if (this->isAbandoned()) { + return nullptr; + } + + // This is only supported on a direct GrContext. + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + const GrCaps* caps = this->caps(); + + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + + sk_sp rt = resourceProvider->wrapBackendRenderTarget(backendRT, grColorType); + if (!rt) { + return nullptr; + } + + if (releaseProc) { + rt->setRelease(releaseProc, releaseCtx); + } + + SkASSERT(!rt->asTexture()); // A GrRenderTarget that's not textureable + SkASSERT(!rt->getUniqueKey().isValid()); + // Make sure we match how we created the proxy with SkBudgeted::kNo + SkASSERT(GrBudgetedType::kBudgeted != rt->resourcePriv().budgetedType()); + + GrSwizzle texSwizzle = caps->getTextureSwizzle(rt->backendFormat(), grColorType); + GrSwizzle outSwizzle = caps->getOutputSwizzle(rt->backendFormat(), grColorType); + + return sk_sp(new GrRenderTargetProxy(std::move(rt), origin, texSwizzle, + outSwizzle, UseAllocator::kNo)); +} + +sk_sp GrProxyProvider::wrapBackendTextureAsRenderTarget( + const GrBackendTexture& backendTex, GrColorType grColorType, + GrSurfaceOrigin origin, int sampleCnt) { + if (this->isAbandoned()) { + return nullptr; + } + + // This is only supported on a direct GrContext. + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + const GrCaps* caps = this->caps(); + + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + + sk_sp rt = + resourceProvider->wrapBackendTextureAsRenderTarget(backendTex, sampleCnt, grColorType); + if (!rt) { + return nullptr; + } + SkASSERT(!rt->asTexture()); // A GrRenderTarget that's not textureable + SkASSERT(!rt->getUniqueKey().isValid()); + // This proxy should be unbudgeted because we're just wrapping an external resource + SkASSERT(GrBudgetedType::kBudgeted != rt->resourcePriv().budgetedType()); + + GrSwizzle texSwizzle = caps->getTextureSwizzle(rt->backendFormat(), grColorType); + GrSwizzle outSwizzle = caps->getOutputSwizzle(rt->backendFormat(), grColorType); + + return sk_sp(new GrRenderTargetProxy(std::move(rt), origin, texSwizzle, + outSwizzle, UseAllocator::kNo)); +} + +sk_sp GrProxyProvider::wrapVulkanSecondaryCBAsRenderTarget( + const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) { + if (this->isAbandoned()) { + return nullptr; + } + + // This is only supported on a direct GrContext. + GrContext* direct = fImageContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + + sk_sp rt = resourceProvider->wrapVulkanSecondaryCBAsRenderTarget(imageInfo, + vkInfo); + if (!rt) { + return nullptr; + } + + SkASSERT(!rt->asTexture()); // A GrRenderTarget that's not textureable + SkASSERT(!rt->getUniqueKey().isValid()); + // This proxy should be unbudgeted because we're just wrapping an external resource + SkASSERT(GrBudgetedType::kBudgeted != rt->resourcePriv().budgetedType()); + + GrColorType colorType = SkColorTypeToGrColorType(imageInfo.colorType()); + GrSwizzle texSwizzle = this->caps()->getTextureSwizzle(rt->backendFormat(), colorType); + GrSwizzle outSwizzle = this->caps()->getOutputSwizzle(rt->backendFormat(), colorType); + + if (!this->caps()->isFormatAsColorTypeRenderable(colorType, rt->backendFormat(), + rt->numSamples())) { + return nullptr; + } + + // All Vulkan surfaces uses top left origins. + return sk_sp(new GrRenderTargetProxy( + std::move(rt), kTopLeft_GrSurfaceOrigin, texSwizzle, outSwizzle, UseAllocator::kNo, + GrRenderTargetProxy::WrapsVkSecondaryCB::kYes)); +} + +sk_sp GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + GrRenderable renderable, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + GrMipMapsStatus mipMapsStatus, + GrInternalSurfaceFlags surfaceFlags, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrSurfaceProxy::UseAllocator useAllocator) { + SkASSERT((desc.fWidth <= 0 && desc.fHeight <= 0) || + (desc.fWidth > 0 && desc.fHeight > 0)); + + if (!format.isValid()) { + return nullptr; + } + + if (desc.fWidth > this->caps()->maxTextureSize() || + desc.fHeight > this->caps()->maxTextureSize()) { + return nullptr; + } + + GrColorType colorType = GrPixelConfigToColorType(desc.fConfig); + GrSwizzle texSwizzle = this->caps()->getTextureSwizzle(format, colorType); + GrSwizzle outSwizzle = this->caps()->getOutputSwizzle(format, colorType); + + if (renderable == GrRenderable::kYes) { + return sk_sp(new GrTextureRenderTargetProxy(*this->caps(), + std::move(callback), + format, + desc, + renderTargetSampleCnt, + origin, + mipMapped, + mipMapsStatus, + texSwizzle, + outSwizzle, + fit, + budgeted, + isProtected, + surfaceFlags, + useAllocator)); + } else { + return sk_sp(new GrTextureProxy(std::move(callback), + format, + desc, + origin, + mipMapped, + mipMapsStatus, + texSwizzle, + fit, + budgeted, + isProtected, + surfaceFlags, + useAllocator)); + } +} + +sk_sp GrProxyProvider::createLazyRenderTargetProxy( + LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + int sampleCnt, + GrSurfaceOrigin origin, + GrInternalSurfaceFlags surfaceFlags, + const TextureInfo* textureInfo, + GrMipMapsStatus mipMapsStatus, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + bool wrapsVkSecondaryCB, + UseAllocator useAllocator) { + SkASSERT((desc.fWidth <= 0 && desc.fHeight <= 0) || + (desc.fWidth > 0 && desc.fHeight > 0)); + + if (desc.fWidth > this->caps()->maxRenderTargetSize() || + desc.fHeight > this->caps()->maxRenderTargetSize()) { + return nullptr; + } + + GrColorType colorType = GrPixelConfigToColorType(desc.fConfig); + GrSwizzle texSwizzle = this->caps()->getTextureSwizzle(format, colorType); + GrSwizzle outSwizzle = this->caps()->getOutputSwizzle(format, colorType); + + if (textureInfo) { + // Wrapped vulkan secondary command buffers don't support texturing since we won't have an + // actual VkImage to texture from. + SkASSERT(!wrapsVkSecondaryCB); + return sk_sp(new GrTextureRenderTargetProxy( + *this->caps(), std::move(callback), format, desc, sampleCnt, origin, + textureInfo->fMipMapped, mipMapsStatus, texSwizzle, outSwizzle, fit, budgeted, + isProtected, surfaceFlags, useAllocator)); + } + + GrRenderTargetProxy::WrapsVkSecondaryCB vkSCB = + wrapsVkSecondaryCB ? GrRenderTargetProxy::WrapsVkSecondaryCB::kYes + : GrRenderTargetProxy::WrapsVkSecondaryCB::kNo; + + return sk_sp(new GrRenderTargetProxy( + std::move(callback), format, desc, sampleCnt, origin, texSwizzle, outSwizzle, fit, + budgeted, isProtected, surfaceFlags, useAllocator, vkSCB)); +} + +sk_sp GrProxyProvider::MakeFullyLazyProxy(LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrProtected isProtected, + GrSurfaceOrigin origin, + GrPixelConfig config, + const GrCaps& caps, + UseAllocator useAllocator) { + if (!format.isValid()) { + return nullptr; + } + + SkASSERT(renderTargetSampleCnt == 1 || renderable == GrRenderable::kYes); + GrSurfaceDesc desc; + GrInternalSurfaceFlags surfaceFlags = GrInternalSurfaceFlags::kNone; + desc.fWidth = -1; + desc.fHeight = -1; + desc.fConfig = config; + + GrColorType colorType = GrPixelConfigToColorType(desc.fConfig); + GrSwizzle texSwizzle = caps.getTextureSwizzle(format, colorType); + GrSwizzle outSwizzle = caps.getOutputSwizzle(format, colorType); + + if (GrRenderable::kYes == renderable) { + return sk_sp(new GrTextureRenderTargetProxy( + caps, std::move(callback), format, desc, renderTargetSampleCnt, origin, + GrMipMapped::kNo, GrMipMapsStatus::kNotAllocated, texSwizzle, outSwizzle, + SkBackingFit::kApprox, SkBudgeted::kYes, isProtected, surfaceFlags, useAllocator)); + } else { + return sk_sp(new GrTextureProxy( + std::move(callback), format, desc, origin, GrMipMapped::kNo, + GrMipMapsStatus::kNotAllocated, texSwizzle, SkBackingFit::kApprox, SkBudgeted::kYes, + isProtected, surfaceFlags, useAllocator)); + } +} + +bool GrProxyProvider::IsFunctionallyExact(GrSurfaceProxy* proxy) { + const bool isInstantiated = proxy->isInstantiated(); + // A proxy is functionally exact if: + // it is exact (obvs) + // when it is instantiated it will be exact (i.e., power of two dimensions) + // it is already instantiated and the proxy covers the entire backing surface + return proxy->priv().isExact() || + (!isInstantiated && SkIsPow2(proxy->width()) && SkIsPow2(proxy->height())) || + (isInstantiated && proxy->worstCaseWidth() == proxy->width() && + proxy->worstCaseHeight() == proxy->height()); +} + +void GrProxyProvider::processInvalidUniqueKey(const GrUniqueKey& key, GrTextureProxy* proxy, + InvalidateGPUResource invalidateGPUResource) { + SkASSERT(key.isValid()); + + if (!proxy) { + proxy = fUniquelyKeyedProxies.find(key); + } + SkASSERT(!proxy || proxy->getUniqueKey() == key); + + // Locate the corresponding GrGpuResource (if it needs to be invalidated) before clearing the + // proxy's unique key. We must do it in this order because 'key' may alias the proxy's key. + sk_sp invalidGpuResource; + if (InvalidateGPUResource::kYes == invalidateGPUResource) { + GrContext* direct = fImageContext->priv().asDirectContext(); + if (direct) { + GrResourceProvider* resourceProvider = direct->priv().resourceProvider(); + invalidGpuResource = resourceProvider->findByUniqueKey(key); + } + SkASSERT(!invalidGpuResource || invalidGpuResource->getUniqueKey() == key); + } + + // Note: this method is called for the whole variety of GrGpuResources so often 'key' + // will not be in 'fUniquelyKeyedProxies'. + if (proxy) { + fUniquelyKeyedProxies.remove(key); + proxy->cacheAccess().clearUniqueKey(); + } + + if (invalidGpuResource) { + invalidGpuResource->resourcePriv().removeUniqueKey(); + } +} + +uint32_t GrProxyProvider::contextID() const { + return fImageContext->priv().contextID(); +} + +const GrCaps* GrProxyProvider::caps() const { + return fImageContext->priv().caps(); +} + +sk_sp GrProxyProvider::refCaps() const { + return fImageContext->priv().refCaps(); +} + +bool GrProxyProvider::isAbandoned() const { + return fImageContext->priv().abandoned(); +} + +void GrProxyProvider::orphanAllUniqueKeys() { + UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies); + for (UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies); !iter.done(); ++iter) { + GrTextureProxy& tmp = *iter; + + tmp.fProxyProvider = nullptr; + } +} + +void GrProxyProvider::removeAllUniqueKeys() { + UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies); + for (UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies); !iter.done(); ++iter) { + GrTextureProxy& tmp = *iter; + + this->processInvalidUniqueKey(tmp.getUniqueKey(), &tmp, InvalidateGPUResource::kNo); + } + SkASSERT(!fUniquelyKeyedProxies.count()); +} + +bool GrProxyProvider::renderingDirectly() const { + return fImageContext->priv().asDirectContext(); +} diff --git a/gfx/skia/skia/src/gpu/GrProxyProvider.h b/gfx/skia/skia/src/gpu/GrProxyProvider.h new file mode 100644 index 0000000000..b8de8c8983 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrProxyProvider.h @@ -0,0 +1,296 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrProxyProvider_DEFINED +#define GrProxyProvider_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/private/GrResourceKey.h" +#include "src/core/SkTDynamicHash.h" +#include "src/gpu/GrTextureProxy.h" + +class GrImageContext; +class GrBackendRenderTarget; +class SkBitmap; +class SkImage; + +/* + * A factory for creating GrSurfaceProxy-derived objects. + */ +class GrProxyProvider { +public: + using UseAllocator = GrSurfaceProxy::UseAllocator; + + GrProxyProvider(GrImageContext*); + + ~GrProxyProvider(); + + /* + * Assigns a unique key to a proxy. The proxy will be findable via this key using + * findProxyByUniqueKey(). It is an error if an existing proxy already has a key. + */ + bool assignUniqueKeyToProxy(const GrUniqueKey&, GrTextureProxy*); + + /* + * Sets the unique key of the provided proxy to the unique key of the surface. The surface must + * have a valid unique key. + */ + void adoptUniqueKeyFromSurface(GrTextureProxy* proxy, const GrSurface*); + + /* + * Removes a unique key from a proxy. If the proxy has already been instantiated, it will + * also remove the unique key from the target GrSurface. + */ + void removeUniqueKeyFromProxy(GrTextureProxy*); + + /* + * Finds a proxy by unique key. + */ + sk_sp findProxyByUniqueKey(const GrUniqueKey&, GrSurfaceOrigin); + + /* + * Finds a proxy by unique key or creates a new one that wraps a resource matching the unique + * key. GrColorType is required to set the proxy's texture swizzle on creation. For any key, + * each call that might result in a cache hit must provide the same colorType as the call that + * caused a cache miss and created the proxy. + */ + sk_sp findOrCreateProxyByUniqueKey(const GrUniqueKey&, + GrColorType colorType, + GrSurfaceOrigin, + UseAllocator = UseAllocator::kYes); + + /* + * Create an un-mipmapped texture proxy with data. The SkImage must be a raster backend image. + * Since the SkImage is ref counted, we simply take a ref on it to keep the data alive until we + * actually upload the data to the gpu. + */ + sk_sp createTextureProxy( + sk_sp srcImage, int sampleCnt, SkBudgeted, SkBackingFit, + GrInternalSurfaceFlags = GrInternalSurfaceFlags::kNone); + + /* + * Creates a new texture proxy for the bitmap, optionally with mip levels generated by the cpu. + */ + sk_sp createProxyFromBitmap(const SkBitmap& bitmap, GrMipMapped); + + /* + * Create a GrSurfaceProxy without any data. + */ + sk_sp createProxy(const GrBackendFormat&, + const GrSurfaceDesc&, + GrRenderable, + int renderTargetSampleCnt, + GrSurfaceOrigin, + GrMipMapped, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags = GrInternalSurfaceFlags::kNone, + UseAllocator useAllocator = UseAllocator::kYes); + + /* + * Create a texture proxy from compressed texture data. + */ + sk_sp createCompressedTextureProxy(int width, int height, SkBudgeted budgeted, + SkImage::CompressionType compressionType, + sk_sp data); + + // These match the definitions in SkImage & GrTexture.h, for whence they came + typedef void* ReleaseContext; + typedef void (*ReleaseProc)(ReleaseContext); + + /* + * Create a texture proxy that wraps a (non-renderable) backend texture. GrIOType must be + * kRead or kRW. + */ + sk_sp wrapBackendTexture(const GrBackendTexture&, GrColorType, GrSurfaceOrigin, + GrWrapOwnership, GrWrapCacheable, GrIOType, + ReleaseProc = nullptr, ReleaseContext = nullptr); + + /* + * Create a texture proxy that wraps a backend texture and is both texture-able and renderable + */ + sk_sp wrapRenderableBackendTexture(const GrBackendTexture&, GrSurfaceOrigin, + int sampleCnt, GrColorType, + GrWrapOwnership, GrWrapCacheable, + ReleaseProc = nullptr, + ReleaseContext = nullptr); + + /* + * Create a render target proxy that wraps a backend render target + */ + sk_sp wrapBackendRenderTarget(const GrBackendRenderTarget&, GrColorType, + GrSurfaceOrigin, + ReleaseProc = nullptr, ReleaseContext = nullptr); + + /* + * Create a render target proxy that wraps a backend texture + */ + sk_sp wrapBackendTextureAsRenderTarget(const GrBackendTexture&, + GrColorType, + GrSurfaceOrigin, + int sampleCnt); + + sk_sp wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&, + const GrVkDrawableInfo&); + + using LazyInstantiationKeyMode = GrSurfaceProxy::LazyInstantiationKeyMode; + using LazyCallbackResult = GrSurfaceProxy::LazyCallbackResult; + using LazyInstantiateCallback = GrSurfaceProxy::LazyInstantiateCallback; + + struct TextureInfo { + GrMipMapped fMipMapped; + GrTextureType fTextureType; + }; + + /** + * Creates a texture proxy that will be instantiated by a user-supplied callback during flush. + * (Stencil is not supported by this method.) The width and height must either both be greater + * than 0 or both less than or equal to zero. A non-positive value is a signal that the width + * and height are currently unknown. + * + * When called, the callback must be able to cleanup any resources that it captured at creation. + * It also must support being passed in a null GrResourceProvider. When this happens, the + * callback should cleanup any resources it captured and return an empty sk_sp. + */ + sk_sp createLazyProxy(LazyInstantiateCallback&&, + const GrBackendFormat&, + const GrSurfaceDesc&, + GrRenderable, + int renderTargetSampleCnt, + GrSurfaceOrigin, + GrMipMapped, + GrMipMapsStatus, + GrInternalSurfaceFlags, + SkBackingFit, + SkBudgeted, + GrProtected, + UseAllocator); + + /** A null TextureInfo indicates a non-textureable render target. */ + sk_sp createLazyRenderTargetProxy(LazyInstantiateCallback&&, + const GrBackendFormat&, + const GrSurfaceDesc&, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + GrInternalSurfaceFlags, + const TextureInfo*, + GrMipMapsStatus, + SkBackingFit, + SkBudgeted, + GrProtected, + bool wrapsVkSecondaryCB, + UseAllocator useAllocator); + + /** + * Fully lazy proxies have unspecified width and height. Methods that rely on those values + * (e.g., width, height, getBoundsRect) should be avoided. + */ + static sk_sp MakeFullyLazyProxy(LazyInstantiateCallback&&, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + GrProtected, + GrSurfaceOrigin, + GrPixelConfig, + const GrCaps&, + UseAllocator); + + // 'proxy' is about to be used as a texture src or drawn to. This query can be used to + // determine if it is going to need a texture domain or a full clear. + static bool IsFunctionallyExact(GrSurfaceProxy* proxy); + + enum class InvalidateGPUResource : bool { kNo = false, kYes = true }; + + /* + * This method ensures that, if a proxy w/ the supplied unique key exists, it is removed from + * the proxy provider's map and its unique key is removed. If 'invalidateSurface' is true, it + * will independently ensure that the unique key is removed from any GrGpuResources that may + * have it. + * + * If 'proxy' is provided (as an optimization to stop re-looking it up), its unique key must be + * valid and match the provided unique key. + * + * This method is called if either the proxy attached to the unique key is being deleted + * (in which case we don't want it cluttering up the hash table) or the client has indicated + * that it will never refer to the unique key again. + */ + void processInvalidUniqueKey(const GrUniqueKey&, GrTextureProxy*, InvalidateGPUResource); + + // TODO: remove these entry points - it is a bit sloppy to be getting context info from here + uint32_t contextID() const; + const GrCaps* caps() const; + sk_sp refCaps() const; + + int numUniqueKeyProxies_TestOnly() const; + + // This is called on a DDL's proxyprovider when the DDL is finished. The uniquely keyed + // proxies need to keep their unique key but cannot hold on to the proxy provider unique + // pointer. + void orphanAllUniqueKeys(); + // This is only used by GrContext::releaseResourcesAndAbandonContext() + void removeAllUniqueKeys(); + + /** + * Does the proxy provider have access to a GrDirectContext? If so, proxies will be + * instantiated immediately. + */ + bool renderingDirectly() const; + +#if GR_TEST_UTILS + /* + * Create a texture proxy that is backed by an instantiated GrSurface. + * TODO: Remove GrColorType. Currently used to infer a GrPixelConfig. + */ + sk_sp testingOnly_createInstantiatedProxy(const SkISize& size, + GrColorType colorType, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected); + + /** Version of above that picks the default format for the color type. */ + sk_sp testingOnly_createInstantiatedProxy(const SkISize& size, + GrColorType colorType, + GrRenderable renderable, + int renderTargetSampleCnt, + GrSurfaceOrigin origin, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected); + + sk_sp testingOnly_createWrapped(sk_sp, GrColorType, GrSurfaceOrigin); +#endif + +private: + friend class GrAHardwareBufferImageGenerator; // for createWrapped + friend class GrResourceProvider; // for createWrapped + + bool isAbandoned() const; + + // GrColorType is used to determine the proxy's texture swizzle. + sk_sp createWrapped(sk_sp tex, GrColorType, GrSurfaceOrigin origin, + UseAllocator useAllocator); + + struct UniquelyKeyedProxyHashTraits { + static const GrUniqueKey& GetKey(const GrTextureProxy& p) { return p.getUniqueKey(); } + + static uint32_t Hash(const GrUniqueKey& key) { return key.hash(); } + }; + typedef SkTDynamicHash UniquelyKeyedProxyHash; + + // This holds the texture proxies that have unique keys. The resourceCache does not get a ref + // on these proxies but they must send a message to the resourceCache when they are deleted. + UniquelyKeyedProxyHash fUniquelyKeyedProxies; + + GrImageContext* fImageContext; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRecordingContext.cpp b/gfx/skia/skia/src/gpu/GrRecordingContext.cpp new file mode 100644 index 0000000000..081b693f51 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRecordingContext.cpp @@ -0,0 +1,389 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrRecordingContext.h" + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" +#include "src/gpu/GrTextureContext.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/text/GrTextBlobCache.h" + +#define ASSERT_SINGLE_OWNER_PRIV \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());) + +GrRecordingContext::GrRecordingContext(GrBackendApi backend, + const GrContextOptions& options, + uint32_t contextID) + : INHERITED(backend, options, contextID) + , fAuditTrail(new GrAuditTrail()) { +} + +GrRecordingContext::~GrRecordingContext() { } + +/** + * TODO: move textblob draw calls below context (see comment below) + */ +static void textblobcache_overbudget_CB(void* data) { + SkASSERT(data); + GrRecordingContext* context = reinterpret_cast(data); + + GrContext* direct = context->priv().asDirectContext(); + if (!direct) { + return; + } + + // TextBlobs are drawn at the SkGpuDevice level, therefore they cannot rely on + // GrRenderTargetContext to perform a necessary flush. The solution is to move drawText calls + // to below the GrContext level, but this is not trivial because they call drawPath on + // SkGpuDevice. + direct->flush(); +} + +bool GrRecordingContext::init(sk_sp caps, sk_sp cache) { + + if (!INHERITED::init(std::move(caps), std::move(cache))) { + return false; + } + + fStrikeCache.reset(new GrStrikeCache(this->caps(), + this->options().fGlyphCacheTextureMaximumBytes)); + + fTextBlobCache.reset(new GrTextBlobCache(textblobcache_overbudget_CB, this, + this->contextID())); + + return true; +} + +void GrRecordingContext::setupDrawingManager(bool sortOpsTasks, bool reduceOpsTaskSplitting) { + GrPathRendererChain::Options prcOptions; + prcOptions.fAllowPathMaskCaching = this->options().fAllowPathMaskCaching; +#if GR_TEST_UTILS + prcOptions.fGpuPathRenderers = this->options().fGpuPathRenderers; +#endif + // FIXME: Once this is removed from Chrome and Android, rename to fEnable"". + if (!this->options().fDisableCoverageCountingPaths) { + prcOptions.fGpuPathRenderers |= GpuPathRenderers::kCoverageCounting; + } + if (this->options().fDisableDistanceFieldPaths) { + prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall; + } + + if (!this->proxyProvider()->renderingDirectly()) { + // DDL TODO: remove this crippling of the path renderer chain + // Disable the small path renderer bc of the proxies in the atlas. They need to be + // unified when the opsTasks are added back to the destination drawing manager. + prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall; + } + + GrTextContext::Options textContextOptions; + textContextOptions.fMaxDistanceFieldFontSize = this->options().fGlyphsAsPathsFontSize; + textContextOptions.fMinDistanceFieldFontSize = this->options().fMinDistanceFieldFontSize; + textContextOptions.fDistanceFieldVerticesAlwaysHaveW = false; +#if SK_SUPPORT_ATLAS_TEXT + if (GrContextOptions::Enable::kYes == this->options().fDistanceFieldGlyphVerticesAlwaysHaveW) { + textContextOptions.fDistanceFieldVerticesAlwaysHaveW = true; + } +#endif + + fDrawingManager.reset(new GrDrawingManager(this, + prcOptions, + textContextOptions, + sortOpsTasks, + reduceOpsTaskSplitting)); +} + +void GrRecordingContext::abandonContext() { + INHERITED::abandonContext(); + + fStrikeCache->freeAll(); + fTextBlobCache->freeAll(); +} + +GrDrawingManager* GrRecordingContext::drawingManager() { + return fDrawingManager.get(); +} + +sk_sp GrRecordingContext::refOpMemoryPool() { + if (!fOpMemoryPool) { + // DDL TODO: should the size of the memory pool be decreased in DDL mode? CPU-side memory + // consumed in DDL mode vs. normal mode for a single skp might be a good metric of wasted + // memory. + fOpMemoryPool = sk_sp(new GrOpMemoryPool(16384, 16384)); + } + + SkASSERT(fOpMemoryPool); + return fOpMemoryPool; +} + +GrOpMemoryPool* GrRecordingContext::opMemoryPool() { + return this->refOpMemoryPool().get(); +} + +GrTextBlobCache* GrRecordingContext::getTextBlobCache() { + return fTextBlobCache.get(); +} + +const GrTextBlobCache* GrRecordingContext::getTextBlobCache() const { + return fTextBlobCache.get(); +} + +void GrRecordingContext::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) { + this->drawingManager()->addOnFlushCallbackObject(onFlushCBObject); +} + +std::unique_ptr GrRecordingContext::makeWrappedSurfaceContext( + sk_sp proxy, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + const SkSurfaceProps* props) { + ASSERT_SINGLE_OWNER_PRIV + + SkASSERT(proxy); + + if (proxy->asRenderTargetProxy()) { + SkASSERT(kPremul_SkAlphaType == alphaType || kOpaque_SkAlphaType == alphaType); + return this->drawingManager()->makeRenderTargetContext(std::move(proxy), colorType, + std::move(colorSpace), props); + } else { + SkASSERT(proxy->asTextureProxy()); + SkASSERT(!props); + return this->drawingManager()->makeTextureContext(std::move(proxy), colorType, alphaType, + std::move(colorSpace)); + } +} + +std::unique_ptr GrRecordingContext::makeDeferredTextureContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkBudgeted budgeted, + GrProtected isProtected) { + auto format = this->caps()->getDefaultBackendFormat(colorType, GrRenderable::kNo); + if (!format.isValid()) { + return nullptr; + } + auto config = this->caps()->getConfigFromBackendFormat(format, colorType); + if (config == kUnknown_GrPixelConfig) { + return nullptr; + } + + GrSurfaceDesc desc; + desc.fWidth = width; + desc.fHeight = height; + desc.fConfig = config; + + sk_sp texture = this->proxyProvider()->createProxy( + format, desc, GrRenderable::kNo, 1, origin, mipMapped, fit, budgeted, isProtected); + if (!texture) { + return nullptr; + } + + auto drawingManager = this->drawingManager(); + + return drawingManager->makeTextureContext(std::move(texture), colorType, alphaType, + std::move(colorSpace)); +} + +std::unique_ptr GrRecordingContext::makeDeferredRenderTargetContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + const SkSurfaceProps* surfaceProps, + SkBudgeted budgeted, + GrProtected isProtected) { + SkASSERT(sampleCnt > 0); + if (this->abandoned()) { + return nullptr; + } + + auto format = this->caps()->getDefaultBackendFormat(colorType, GrRenderable::kYes); + if (!format.isValid()) { + return nullptr; + } + auto config = this->caps()->getConfigFromBackendFormat(format, colorType); + if (config == kUnknown_GrPixelConfig) { + return nullptr; + } + + GrSurfaceDesc desc; + desc.fWidth = width; + desc.fHeight = height; + desc.fConfig = config; + + sk_sp rtp = + this->proxyProvider()->createProxy(format, desc, GrRenderable::kYes, sampleCnt, origin, + mipMapped, fit, budgeted, isProtected); + if (!rtp) { + return nullptr; + } + + auto drawingManager = this->drawingManager(); + + auto renderTargetContext = drawingManager->makeRenderTargetContext( + std::move(rtp), colorType, std::move(colorSpace), surfaceProps); + if (!renderTargetContext) { + return nullptr; + } + + renderTargetContext->discard(); + + return renderTargetContext; +} + +static inline GrColorType color_type_fallback(GrColorType ct) { + switch (ct) { + // kRGBA_8888 is our default fallback for many color types that may not have renderable + // backend formats. + case GrColorType::kAlpha_8: + case GrColorType::kBGR_565: + case GrColorType::kABGR_4444: + case GrColorType::kBGRA_8888: + case GrColorType::kRGBA_1010102: + case GrColorType::kRGBA_F16: + case GrColorType::kRGBA_F16_Clamped: + return GrColorType::kRGBA_8888; + case GrColorType::kAlpha_F16: + return GrColorType::kRGBA_F16; + case GrColorType::kGray_8: + return GrColorType::kRGB_888x; + default: + return GrColorType::kUnknown; + } +} + +std::unique_ptr +GrRecordingContext::makeDeferredRenderTargetContextWithFallback(SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + const SkSurfaceProps* surfaceProps, + SkBudgeted budgeted, + GrProtected isProtected) { + SkASSERT(sampleCnt > 0); + std::unique_ptr rtc; + do { + rtc = this->makeDeferredRenderTargetContext(fit, width, height, colorType, colorSpace, + sampleCnt, mipMapped, origin, surfaceProps, + budgeted, isProtected); + colorType = color_type_fallback(colorType); + } while (!rtc && colorType != GrColorType::kUnknown); + return rtc; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +sk_sp GrRecordingContextPriv::refCaps() const { + return fContext->refCaps(); +} + +sk_sp GrRecordingContextPriv::fpFactoryCache() { + return fContext->fpFactoryCache(); +} + +sk_sp GrRecordingContextPriv::refOpMemoryPool() { + return fContext->refOpMemoryPool(); +} + +void GrRecordingContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) { + fContext->addOnFlushCallbackObject(onFlushCBObject); +} + +std::unique_ptr GrRecordingContextPriv::makeWrappedSurfaceContext( + sk_sp proxy, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + const SkSurfaceProps* props) { + return fContext->makeWrappedSurfaceContext(std::move(proxy), colorType, alphaType, + std::move(colorSpace), props); +} + +std::unique_ptr GrRecordingContextPriv::makeDeferredTextureContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkBudgeted budgeted, + GrProtected isProtected) { + return fContext->makeDeferredTextureContext(fit, width, height, colorType, alphaType, + std::move(colorSpace), mipMapped, origin, budgeted, + isProtected); +} + +std::unique_ptr GrRecordingContextPriv::makeDeferredRenderTargetContext( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + const SkSurfaceProps* surfaceProps, + SkBudgeted budgeted, + GrProtected isProtected) { + return fContext->makeDeferredRenderTargetContext(fit, width, height, colorType, + std::move(colorSpace), sampleCnt, mipMapped, + origin, surfaceProps, budgeted, isProtected); +} + +std::unique_ptr +GrRecordingContextPriv::makeDeferredRenderTargetContextWithFallback( + SkBackingFit fit, + int width, + int height, + GrColorType colorType, + sk_sp colorSpace, + int sampleCnt, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + const SkSurfaceProps* surfaceProps, + SkBudgeted budgeted, + GrProtected isProtected) { + return fContext->makeDeferredRenderTargetContextWithFallback(fit, + width, + height, + colorType, + std::move(colorSpace), + sampleCnt, + mipMapped, + origin, + surfaceProps, + budgeted, + isProtected); +} + +GrContext* GrRecordingContextPriv::backdoor() { + return (GrContext*) fContext; +} + diff --git a/gfx/skia/skia/src/gpu/GrRecordingContextPriv.h b/gfx/skia/skia/src/gpu/GrRecordingContextPriv.h new file mode 100644 index 0000000000..2987778d6b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRecordingContextPriv.h @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRecordingContextPriv_DEFINED +#define GrRecordingContextPriv_DEFINED + +#include "include/private/GrRecordingContext.h" + +/** Class that exposes methods to GrRecordingContext that are only intended for use internal to + Skia. This class is purely a privileged window into GrRecordingContext. It should never have + additional data members or virtual methods. */ +class GrRecordingContextPriv { +public: + // from GrContext_Base + uint32_t contextID() const { return fContext->contextID(); } + + bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); } + + const GrContextOptions& options() const { return fContext->options(); } + + const GrCaps* caps() const { return fContext->caps(); } + sk_sp refCaps() const; + + sk_sp fpFactoryCache(); + + GrImageContext* asImageContext() { return fContext->asImageContext(); } + GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); } + GrContext* asDirectContext() { return fContext->asDirectContext(); } + + // from GrImageContext + GrProxyProvider* proxyProvider() { return fContext->proxyProvider(); } + const GrProxyProvider* proxyProvider() const { return fContext->proxyProvider(); } + + bool abandoned() const { return fContext->abandoned(); } + + /** This is only useful for debug purposes */ + SkDEBUGCODE(GrSingleOwner* singleOwner() const { return fContext->singleOwner(); } ) + + // from GrRecordingContext + GrDrawingManager* drawingManager() { return fContext->drawingManager(); } + + sk_sp refOpMemoryPool(); + GrOpMemoryPool* opMemoryPool() { return fContext->opMemoryPool(); } + + GrStrikeCache* getGrStrikeCache() { return fContext->getGrStrikeCache(); } + GrTextBlobCache* getTextBlobCache() { return fContext->getTextBlobCache(); } + + /** + * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.) + * + * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to + * ensure its lifetime is tied to that of the context. + */ + void addOnFlushCallbackObject(GrOnFlushCallbackObject*); + + std::unique_ptr makeWrappedSurfaceContext(sk_sp, + GrColorType, + SkAlphaType, + sk_sp = nullptr, + const SkSurfaceProps* = nullptr); + + /** Create a new texture context backed by a deferred-style GrTextureProxy. */ + std::unique_ptr makeDeferredTextureContext( + SkBackingFit, + int width, + int height, + GrColorType, + SkAlphaType, + sk_sp, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin = kTopLeft_GrSurfaceOrigin, + SkBudgeted = SkBudgeted::kYes, + GrProtected = GrProtected::kNo); + + /* + * Create a new render target context backed by a deferred-style + * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for + * renderTargetContexts created via this entry point. + */ + std::unique_ptr makeDeferredRenderTargetContext( + SkBackingFit fit, + int width, + int height, + GrColorType, + sk_sp colorSpace, + int sampleCnt = 1, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin, + const SkSurfaceProps* surfaceProps = nullptr, + SkBudgeted = SkBudgeted::kYes, + GrProtected isProtected = GrProtected::kNo); + + /* + * This method will attempt to create a renderTargetContext that has, at least, the number of + * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be + * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA). + * SRGB-ness will be preserved. + */ + std::unique_ptr makeDeferredRenderTargetContextWithFallback( + SkBackingFit fit, + int width, + int height, + GrColorType, + sk_sp colorSpace, + int sampleCnt = 1, + GrMipMapped = GrMipMapped::kNo, + GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin, + const SkSurfaceProps* surfaceProps = nullptr, + SkBudgeted budgeted = SkBudgeted::kYes, + GrProtected isProtected = GrProtected::kNo); + + GrAuditTrail* auditTrail() { return fContext->auditTrail(); } + + // CONTEXT TODO: remove this backdoor + // In order to make progress we temporarily need a way to break CL impasses. + GrContext* backdoor(); + +private: + explicit GrRecordingContextPriv(GrRecordingContext* context) : fContext(context) {} + GrRecordingContextPriv(const GrRecordingContextPriv&); // unimpl + GrRecordingContextPriv& operator=(const GrRecordingContextPriv&); // unimpl + + // No taking addresses of this type. + const GrRecordingContextPriv* operator&() const; + GrRecordingContextPriv* operator&(); + + GrRecordingContext* fContext; + + friend class GrRecordingContext; // to construct/copy this type. +}; + +inline GrRecordingContextPriv GrRecordingContext::priv() { return GrRecordingContextPriv(this); } + +inline const GrRecordingContextPriv GrRecordingContext::priv () const { + return GrRecordingContextPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRectanizer.h b/gfx/skia/skia/src/gpu/GrRectanizer.h new file mode 100644 index 0000000000..2e558967fb --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRectanizer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRectanizer_DEFINED +#define GrRectanizer_DEFINED + +#include "include/gpu/GrTypes.h" + +struct SkIPoint16; + +class GrRectanizer { +public: + GrRectanizer(int width, int height) : fWidth(width), fHeight(height) { + SkASSERT(width >= 0); + SkASSERT(height >= 0); + } + + virtual ~GrRectanizer() {} + + virtual void reset() = 0; + + int width() const { return fWidth; } + int height() const { return fHeight; } + + // Attempt to add a rect. Return true on success; false on failure. If + // successful the position in the atlas is returned in 'loc'. + virtual bool addRect(int width, int height, SkIPoint16* loc) = 0; + virtual float percentFull() const = 0; + + /** + * Our factory, which returns the subclass du jour + */ + static GrRectanizer* Factory(int width, int height); + +private: + int fWidth; + int fHeight; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRectanizer_pow2.cpp b/gfx/skia/skia/src/gpu/GrRectanizer_pow2.cpp new file mode 100644 index 0000000000..1d0328a8fb --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRectanizer_pow2.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrRectanizer_pow2.h" + +bool GrRectanizerPow2::addRect(int width, int height, SkIPoint16* loc) { + if ((unsigned)width > (unsigned)this->width() || + (unsigned)height > (unsigned)this->height()) { + return false; + } + + int32_t area = width * height; // computed here since height will be modified + + height = GrNextPow2(height); + if (height < kMIN_HEIGHT_POW2) { + height = kMIN_HEIGHT_POW2; + } + + Row* row = &fRows[HeightToRowIndex(height)]; + SkASSERT(row->fRowHeight == 0 || row->fRowHeight == height); + + if (0 == row->fRowHeight) { + if (!this->canAddStrip(height)) { + return false; + } + this->initRow(row, height); + } else { + if (!row->canAddWidth(width, this->width())) { + if (!this->canAddStrip(height)) { + return false; + } + // that row is now "full", so retarget our Row record for + // another one + this->initRow(row, height); + } + } + + SkASSERT(row->fRowHeight == height); + SkASSERT(row->canAddWidth(width, this->width())); + *loc = row->fLoc; + row->fLoc.fX += width; + + SkASSERT(row->fLoc.fX <= this->width()); + SkASSERT(row->fLoc.fY <= this->height()); + SkASSERT(fNextStripY <= this->height()); + fAreaSoFar += area; + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +// factory is now in GrRectanizer_skyline.cpp +//GrRectanizer* GrRectanizer::Factory(int width, int height) { +// return new GrRectanizerPow2 (width, height); +//} diff --git a/gfx/skia/skia/src/gpu/GrRectanizer_pow2.h b/gfx/skia/skia/src/gpu/GrRectanizer_pow2.h new file mode 100644 index 0000000000..70e8399337 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRectanizer_pow2.h @@ -0,0 +1,81 @@ +/* +* Copyright 2014 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrRectanizer_pow2_DEFINED +#define GrRectanizer_pow2_DEFINED + +#include "include/private/SkMalloc.h" +#include "src/core/SkIPoint16.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrRectanizer.h" + +// This Rectanizer quantizes the incoming rects to powers of 2. Each power +// of two can have, at most, one active row/shelf. Once a row/shelf for +// a particular power of two gets full its fRows entry is recycled to point +// to a new row. +// The skyline algorithm almost always provides a better packing. +class GrRectanizerPow2 : public GrRectanizer { +public: + GrRectanizerPow2(int w, int h) : INHERITED(w, h) { + this->reset(); + } + + ~GrRectanizerPow2() override {} + + void reset() override { + fNextStripY = 0; + fAreaSoFar = 0; + sk_bzero(fRows, sizeof(fRows)); + } + + bool addRect(int w, int h, SkIPoint16* loc) override; + + float percentFull() const override { + return fAreaSoFar / ((float)this->width() * this->height()); + } + +private: + static const int kMIN_HEIGHT_POW2 = 2; + static const int kMaxExponent = 16; + + struct Row { + SkIPoint16 fLoc; + // fRowHeight is actually known by this struct's position in fRows + // but it is used to signal if there exists an open row of this height + int fRowHeight; + + bool canAddWidth(int width, int containerWidth) const { + return fLoc.fX + width <= containerWidth; + } + }; + + Row fRows[kMaxExponent]; // 0-th entry will be unused + + int fNextStripY; + int32_t fAreaSoFar; + + static int HeightToRowIndex(int height) { + SkASSERT(height >= kMIN_HEIGHT_POW2); + int index = 32 - SkCLZ(height - 1); + SkASSERT(index < kMaxExponent); + return index; + } + + bool canAddStrip(int height) const { + return fNextStripY + height <= this->height(); + } + + void initRow(Row* row, int rowHeight) { + row->fLoc.set(0, fNextStripY); + row->fRowHeight = rowHeight; + fNextStripY += rowHeight; + } + + typedef GrRectanizer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRectanizer_skyline.cpp b/gfx/skia/skia/src/gpu/GrRectanizer_skyline.cpp new file mode 100644 index 0000000000..250cfc5ffb --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRectanizer_skyline.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkIPoint16.h" +#include "src/gpu/GrRectanizer_skyline.h" + +bool GrRectanizerSkyline::addRect(int width, int height, SkIPoint16* loc) { + if ((unsigned)width > (unsigned)this->width() || + (unsigned)height > (unsigned)this->height()) { + return false; + } + + // find position for new rectangle + int bestWidth = this->width() + 1; + int bestX = 0; + int bestY = this->height() + 1; + int bestIndex = -1; + for (int i = 0; i < fSkyline.count(); ++i) { + int y; + if (this->rectangleFits(i, width, height, &y)) { + // minimize y position first, then width of skyline + if (y < bestY || (y == bestY && fSkyline[i].fWidth < bestWidth)) { + bestIndex = i; + bestWidth = fSkyline[i].fWidth; + bestX = fSkyline[i].fX; + bestY = y; + } + } + } + + // add rectangle to skyline + if (-1 != bestIndex) { + this->addSkylineLevel(bestIndex, bestX, bestY, width, height); + loc->fX = bestX; + loc->fY = bestY; + + fAreaSoFar += width*height; + return true; + } + + loc->fX = 0; + loc->fY = 0; + return false; +} + +bool GrRectanizerSkyline::rectangleFits(int skylineIndex, int width, int height, int* ypos) const { + int x = fSkyline[skylineIndex].fX; + if (x + width > this->width()) { + return false; + } + + int widthLeft = width; + int i = skylineIndex; + int y = fSkyline[skylineIndex].fY; + while (widthLeft > 0) { + y = SkMax32(y, fSkyline[i].fY); + if (y + height > this->height()) { + return false; + } + widthLeft -= fSkyline[i].fWidth; + ++i; + SkASSERT(i < fSkyline.count() || widthLeft <= 0); + } + + *ypos = y; + return true; +} + +void GrRectanizerSkyline::addSkylineLevel(int skylineIndex, int x, int y, int width, int height) { + SkylineSegment newSegment; + newSegment.fX = x; + newSegment.fY = y + height; + newSegment.fWidth = width; + fSkyline.insert(skylineIndex, 1, &newSegment); + + SkASSERT(newSegment.fX + newSegment.fWidth <= this->width()); + SkASSERT(newSegment.fY <= this->height()); + + // delete width of the new skyline segment from following ones + for (int i = skylineIndex+1; i < fSkyline.count(); ++i) { + // The new segment subsumes all or part of fSkyline[i] + SkASSERT(fSkyline[i-1].fX <= fSkyline[i].fX); + + if (fSkyline[i].fX < fSkyline[i-1].fX + fSkyline[i-1].fWidth) { + int shrink = fSkyline[i-1].fX + fSkyline[i-1].fWidth - fSkyline[i].fX; + + fSkyline[i].fX += shrink; + fSkyline[i].fWidth -= shrink; + + if (fSkyline[i].fWidth <= 0) { + // fully consumed + fSkyline.remove(i); + --i; + } else { + // only partially consumed + break; + } + } else { + break; + } + } + + // merge fSkylines + for (int i = 0; i < fSkyline.count()-1; ++i) { + if (fSkyline[i].fY == fSkyline[i+1].fY) { + fSkyline[i].fWidth += fSkyline[i+1].fWidth; + fSkyline.remove(i+1); + --i; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +GrRectanizer* GrRectanizer::Factory(int width, int height) { + return new GrRectanizerSkyline(width, height); +} diff --git a/gfx/skia/skia/src/gpu/GrRectanizer_skyline.h b/gfx/skia/skia/src/gpu/GrRectanizer_skyline.h new file mode 100644 index 0000000000..60663fdc5b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRectanizer_skyline.h @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRectanizer_skyline_DEFINED +#define GrRectanizer_skyline_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/gpu/GrRectanizer.h" + +// Pack rectangles and track the current silhouette +// Based, in part, on Jukka Jylanki's work at http://clb.demon.fi +class GrRectanizerSkyline : public GrRectanizer { +public: + GrRectanizerSkyline(int w, int h) : INHERITED(w, h) { + this->reset(); + } + + ~GrRectanizerSkyline() override { } + + void reset() override { + fAreaSoFar = 0; + fSkyline.reset(); + SkylineSegment* seg = fSkyline.append(1); + seg->fX = 0; + seg->fY = 0; + seg->fWidth = this->width(); + } + + bool addRect(int w, int h, SkIPoint16* loc) override; + + float percentFull() const override { + return fAreaSoFar / ((float)this->width() * this->height()); + } + +private: + struct SkylineSegment { + int fX; + int fY; + int fWidth; + }; + + SkTDArray fSkyline; + + int32_t fAreaSoFar; + + // Can a width x height rectangle fit in the free space represented by + // the skyline segments >= 'skylineIndex'? If so, return true and fill in + // 'y' with the y-location at which it fits (the x location is pulled from + // 'skylineIndex's segment. + bool rectangleFits(int skylineIndex, int width, int height, int* y) const; + // Update the skyline structure to include a width x height rect located + // at x,y. + void addSkylineLevel(int skylineIndex, int x, int y, int width, int height); + + typedef GrRectanizer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrReducedClip.cpp b/gfx/skia/skia/src/gpu/GrReducedClip.cpp new file mode 100644 index 0000000000..9224e005c6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrReducedClip.cpp @@ -0,0 +1,991 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkClipOpPriv.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrReducedClip.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrStencilClip.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrUserStencilSettings.h" +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" +#include "src/gpu/effects/GrConvexPolyEffect.h" +#include "src/gpu/effects/GrRRectEffect.h" +#include "src/gpu/effects/generated/GrAARectEffect.h" +#include "src/gpu/geometry/GrShape.h" + +/** + * There are plenty of optimizations that could be added here. Maybe flips could be folded into + * earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps + * for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations + * based on later intersect operations, and perhaps remove intersect-rects. We could optionally + * take a rect in case the caller knows a bound on what is to be drawn through this clip. + */ +GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds, + const GrCaps* caps, int maxWindowRectangles, int maxAnalyticFPs, + int maxCCPRClipPaths) + : fCaps(caps) + , fMaxWindowRectangles(maxWindowRectangles) + , fMaxAnalyticFPs(maxAnalyticFPs) + , fMaxCCPRClipPaths(maxCCPRClipPaths) { + SkASSERT(!queryBounds.isEmpty()); + SkASSERT(fMaxWindowRectangles <= GrWindowRectangles::kMaxWindows); + SkASSERT(fMaxCCPRClipPaths <= fMaxAnalyticFPs); + fHasScissor = false; + fAAClipRectGenID = SK_InvalidGenID; + + if (stack.isWideOpen()) { + fInitialState = InitialState::kAllIn; + return; + } + + SkClipStack::BoundsType stackBoundsType; + SkRect stackBounds; + bool iior; + stack.getBounds(&stackBounds, &stackBoundsType, &iior); + + if (GrClip::IsOutsideClip(stackBounds, queryBounds)) { + bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType; + fInitialState = insideOut ? InitialState::kAllIn : InitialState::kAllOut; + return; + } + + if (iior) { + // "Is intersection of rects" means the clip is a single rect indicated by the stack bounds. + // This should only be true if aa/non-aa status matches among all elements. + SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType); + + if (GrClip::IsInsideClip(stackBounds, queryBounds)) { + fInitialState = InitialState::kAllIn; + return; + } + + SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); + + if (!iter.prev()->isAA() || GrClip::IsPixelAligned(stackBounds)) { + // The clip is a non-aa rect. Here we just implement the entire thing using fScissor. + stackBounds.round(&fScissor); + fHasScissor = true; + fInitialState = fScissor.isEmpty() ? InitialState::kAllOut : InitialState::kAllIn; + return; + } + + SkRect tightBounds; + SkAssertResult(tightBounds.intersect(stackBounds, queryBounds)); + fScissor = GrClip::GetPixelIBounds(tightBounds); + if (fScissor.isEmpty()) { + fInitialState = InitialState::kAllOut; + return; + } + fHasScissor = true; + + fAAClipRect = stackBounds; + fAAClipRectGenID = stack.getTopmostGenID(); + SkASSERT(SK_InvalidGenID != fAAClipRectGenID); + + fInitialState = InitialState::kAllIn; + } else { + SkRect tighterQuery = queryBounds; + if (SkClipStack::kNormal_BoundsType == stackBoundsType) { + // Tighten the query by introducing a new clip at the stack's pixel boundaries. (This + // new clip will be enforced by the scissor.) + SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds))); + } + + fScissor = GrClip::GetPixelIBounds(tighterQuery); + if (fScissor.isEmpty()) { + fInitialState = InitialState::kAllOut; + return; + } + fHasScissor = true; + + // Now that we have determined the bounds to use and filtered out the trivial cases, call + // the helper that actually walks the stack. + this->walkStack(stack, tighterQuery); + } + + if (SK_InvalidGenID != fAAClipRectGenID && // Is there an AA clip rect? + ClipResult::kNotClipped == this->addAnalyticFP(fAAClipRect, Invert::kNo, GrAA::kYes)) { + if (fMaskElements.isEmpty()) { + // Use a replace since it is faster than intersect. + fMaskElements.addToHead(fAAClipRect, SkMatrix::I(), kReplace_SkClipOp, true /*doAA*/); + fInitialState = InitialState::kAllOut; + } else { + fMaskElements.addToTail(fAAClipRect, SkMatrix::I(), kIntersect_SkClipOp, true /*doAA*/); + } + fMaskRequiresAA = true; + fMaskGenID = fAAClipRectGenID; + } +} + +void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBounds) { + // walk backwards until we get to: + // a) the beginning + // b) an operation that is known to make the bounds all inside/outside + // c) a replace operation + + enum class InitialTriState { + kUnknown = -1, + kAllIn = (int)GrReducedClip::InitialState::kAllIn, + kAllOut = (int)GrReducedClip::InitialState::kAllOut + } initialTriState = InitialTriState::kUnknown; + + // During our backwards walk, track whether we've seen ops that either grow or shrink the clip. + // TODO: track these per saved clip so that we can consider them on the forward pass. + bool embiggens = false; + bool emsmallens = false; + + // We use a slightly relaxed set of query bounds for element containment tests. This is to + // account for floating point rounding error that may have occurred during coord transforms. + SkRect relaxedQueryBounds = queryBounds.makeInset(GrClip::kBoundsTolerance, + GrClip::kBoundsTolerance); + if (relaxedQueryBounds.isEmpty()) { + relaxedQueryBounds = queryBounds; + } + + SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); + int numAAElements = 0; + while (InitialTriState::kUnknown == initialTriState) { + const Element* element = iter.prev(); + if (nullptr == element) { + initialTriState = InitialTriState::kAllIn; + break; + } + if (SkClipStack::kEmptyGenID == element->getGenID()) { + initialTriState = InitialTriState::kAllOut; + break; + } + if (SkClipStack::kWideOpenGenID == element->getGenID()) { + initialTriState = InitialTriState::kAllIn; + break; + } + + bool skippable = false; + bool isFlip = false; // does this op just flip the in/out state of every point in the bounds + + switch (element->getOp()) { + case kDifference_SkClipOp: + // check if the shape subtracted either contains the entire bounds (and makes + // the clip empty) or is outside the bounds and therefore can be skipped. + if (element->isInverseFilled()) { + if (element->contains(relaxedQueryBounds)) { + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (!embiggens) { + ClipResult result = this->clipInsideElement(element); + if (ClipResult::kMadeEmpty == result) { + return; + } + skippable = (ClipResult::kClipped == result); + } + } else { + if (element->contains(relaxedQueryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + skippable = true; + } else if (!embiggens) { + ClipResult result = this->clipOutsideElement(element); + if (ClipResult::kMadeEmpty == result) { + return; + } + skippable = (ClipResult::kClipped == result); + } + } + if (!skippable) { + emsmallens = true; + } + break; + case kIntersect_SkClipOp: + // check if the shape intersected contains the entire bounds and therefore can + // be skipped or it is outside the entire bounds and therefore makes the clip + // empty. + if (element->isInverseFilled()) { + if (element->contains(relaxedQueryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + skippable = true; + } else if (!embiggens) { + ClipResult result = this->clipOutsideElement(element); + if (ClipResult::kMadeEmpty == result) { + return; + } + skippable = (ClipResult::kClipped == result); + } + } else { + if (element->contains(relaxedQueryBounds)) { + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (!embiggens) { + ClipResult result = this->clipInsideElement(element); + if (ClipResult::kMadeEmpty == result) { + return; + } + skippable = (ClipResult::kClipped == result); + } + } + if (!skippable) { + emsmallens = true; + } + break; + case kUnion_SkClipOp: + // If the union-ed shape contains the entire bounds then after this element + // the bounds is entirely inside the clip. If the union-ed shape is outside the + // bounds then this op can be skipped. + if (element->isInverseFilled()) { + if (element->contains(relaxedQueryBounds)) { + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + initialTriState = InitialTriState::kAllIn; + skippable = true; + } + } else { + if (element->contains(relaxedQueryBounds)) { + initialTriState = InitialTriState::kAllIn; + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + skippable = true; + } + } + if (!skippable) { + embiggens = true; + } + break; + case kXOR_SkClipOp: + // If the bounds is entirely inside the shape being xor-ed then the effect is + // to flip the inside/outside state of every point in the bounds. We may be + // able to take advantage of this in the forward pass. If the xor-ed shape + // doesn't intersect the bounds then it can be skipped. + if (element->isInverseFilled()) { + if (element->contains(relaxedQueryBounds)) { + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + isFlip = true; + } + } else { + if (element->contains(relaxedQueryBounds)) { + isFlip = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + skippable = true; + } + } + if (!skippable) { + emsmallens = embiggens = true; + } + break; + case kReverseDifference_SkClipOp: + // When the bounds is entirely within the rev-diff shape then this behaves like xor + // and reverses every point inside the bounds. If the shape is completely outside + // the bounds then we know after this element is applied that the bounds will be + // all outside the current clip.B + if (element->isInverseFilled()) { + if (element->contains(relaxedQueryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + isFlip = true; + } + } else { + if (element->contains(relaxedQueryBounds)) { + isFlip = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } + } + if (!skippable) { + emsmallens = embiggens = true; + } + break; + + case kReplace_SkClipOp: + // Replace will always terminate our walk. We will either begin the forward walk + // at the replace op or detect here than the shape is either completely inside + // or completely outside the bounds. In this latter case it can be skipped by + // setting the correct value for initialTriState. + if (element->isInverseFilled()) { + if (element->contains(relaxedQueryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + initialTriState = InitialTriState::kAllIn; + skippable = true; + } else if (!embiggens) { + ClipResult result = this->clipOutsideElement(element); + if (ClipResult::kMadeEmpty == result) { + return; + } + if (ClipResult::kClipped == result) { + initialTriState = InitialTriState::kAllIn; + skippable = true; + } + } + } else { + if (element->contains(relaxedQueryBounds)) { + initialTriState = InitialTriState::kAllIn; + skippable = true; + } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) { + initialTriState = InitialTriState::kAllOut; + skippable = true; + } else if (!embiggens) { + ClipResult result = this->clipInsideElement(element); + if (ClipResult::kMadeEmpty == result) { + return; + } + if (ClipResult::kClipped == result) { + initialTriState = InitialTriState::kAllIn; + skippable = true; + } + } + } + if (!skippable) { + initialTriState = InitialTriState::kAllOut; + embiggens = emsmallens = true; + } + break; + default: + SkDEBUGFAIL("Unexpected op."); + break; + } + if (!skippable) { + if (fMaskElements.isEmpty()) { + // This will be the last element. Record the stricter genID. + fMaskGenID = element->getGenID(); + } + + // if it is a flip, change it to a bounds-filling rect + if (isFlip) { + SkASSERT(kXOR_SkClipOp == element->getOp() || + kReverseDifference_SkClipOp == element->getOp()); + fMaskElements.addToHead(SkRect::Make(fScissor), SkMatrix::I(), + kReverseDifference_SkClipOp, false); + } else { + Element* newElement = fMaskElements.addToHead(*element); + if (newElement->isAA()) { + ++numAAElements; + } + // Intersecting an inverse shape is the same as differencing the non-inverse shape. + // Replacing with an inverse shape is the same as setting initialState=kAllIn and + // differencing the non-inverse shape. + bool isReplace = kReplace_SkClipOp == newElement->getOp(); + if (newElement->isInverseFilled() && + (kIntersect_SkClipOp == newElement->getOp() || isReplace)) { + newElement->invertShapeFillType(); + newElement->setOp(kDifference_SkClipOp); + if (isReplace) { + SkASSERT(InitialTriState::kAllOut == initialTriState); + initialTriState = InitialTriState::kAllIn; + } + } + } + } + } + + if ((InitialTriState::kAllOut == initialTriState && !embiggens) || + (InitialTriState::kAllIn == initialTriState && !emsmallens)) { + fMaskElements.reset(); + numAAElements = 0; + } else { + Element* element = fMaskElements.headIter().get(); + while (element) { + bool skippable = false; + switch (element->getOp()) { + case kDifference_SkClipOp: + // subtracting from the empty set yields the empty set. + skippable = InitialTriState::kAllOut == initialTriState; + break; + case kIntersect_SkClipOp: + // intersecting with the empty set yields the empty set + if (InitialTriState::kAllOut == initialTriState) { + skippable = true; + } else { + // We can clear to zero and then simply draw the clip element. + initialTriState = InitialTriState::kAllOut; + element->setOp(kReplace_SkClipOp); + } + break; + case kUnion_SkClipOp: + if (InitialTriState::kAllIn == initialTriState) { + // unioning the infinite plane with anything is a no-op. + skippable = true; + } else { + // unioning the empty set with a shape is the shape. + element->setOp(kReplace_SkClipOp); + } + break; + case kXOR_SkClipOp: + if (InitialTriState::kAllOut == initialTriState) { + // xor could be changed to diff in the kAllIn case, not sure it's a win. + element->setOp(kReplace_SkClipOp); + } + break; + case kReverseDifference_SkClipOp: + if (InitialTriState::kAllIn == initialTriState) { + // subtracting the whole plane will yield the empty set. + skippable = true; + initialTriState = InitialTriState::kAllOut; + } else { + // this picks up flips inserted in the backwards pass. + skippable = element->isInverseFilled() ? + GrClip::IsOutsideClip(element->getBounds(), queryBounds) : + element->contains(relaxedQueryBounds); + if (skippable) { + initialTriState = InitialTriState::kAllIn; + } else { + element->setOp(kReplace_SkClipOp); + } + } + break; + case kReplace_SkClipOp: + skippable = false; // we would have skipped it in the backwards walk if we + // could've. + break; + default: + SkDEBUGFAIL("Unexpected op."); + break; + } + if (!skippable) { + break; + } else { + if (element->isAA()) { + --numAAElements; + } + fMaskElements.popHead(); + element = fMaskElements.headIter().get(); + } + } + } + fMaskRequiresAA = numAAElements > 0; + + SkASSERT(InitialTriState::kUnknown != initialTriState); + fInitialState = static_cast(initialTriState); +} + +GrReducedClip::ClipResult GrReducedClip::clipInsideElement(const Element* element) { + SkIRect elementIBounds; + if (!element->isAA()) { + element->getBounds().round(&elementIBounds); + } else { + elementIBounds = GrClip::GetPixelIBounds(element->getBounds()); + } + SkASSERT(fHasScissor); + if (!fScissor.intersect(elementIBounds)) { + this->makeEmpty(); + return ClipResult::kMadeEmpty; + } + + switch (element->getDeviceSpaceType()) { + case Element::DeviceSpaceType::kEmpty: + return ClipResult::kMadeEmpty; + + case Element::DeviceSpaceType::kRect: + SkASSERT(element->getBounds() == element->getDeviceSpaceRect()); + SkASSERT(!element->isInverseFilled()); + if (element->isAA()) { + if (SK_InvalidGenID == fAAClipRectGenID) { // No AA clip rect yet? + fAAClipRect = element->getDeviceSpaceRect(); + // fAAClipRectGenID is the value we should use for fMaskGenID if we end up + // moving the AA clip rect into the mask. The mask GenID is simply the topmost + // element's GenID. And since we walk the stack backwards, this means it's just + // the first element we don't skip during our walk. + fAAClipRectGenID = fMaskElements.isEmpty() ? element->getGenID() : fMaskGenID; + SkASSERT(SK_InvalidGenID != fAAClipRectGenID); + } else if (!fAAClipRect.intersect(element->getDeviceSpaceRect())) { + this->makeEmpty(); + return ClipResult::kMadeEmpty; + } + } + return ClipResult::kClipped; + + case Element::DeviceSpaceType::kRRect: + SkASSERT(!element->isInverseFilled()); + return this->addAnalyticFP(element->getDeviceSpaceRRect(), Invert::kNo, + GrAA(element->isAA())); + + case Element::DeviceSpaceType::kPath: + return this->addAnalyticFP(element->getDeviceSpacePath(), + Invert(element->isInverseFilled()), GrAA(element->isAA())); + } + + SK_ABORT("Unexpected DeviceSpaceType"); +} + +GrReducedClip::ClipResult GrReducedClip::clipOutsideElement(const Element* element) { + switch (element->getDeviceSpaceType()) { + case Element::DeviceSpaceType::kEmpty: + return ClipResult::kMadeEmpty; + + case Element::DeviceSpaceType::kRect: + SkASSERT(!element->isInverseFilled()); + if (fWindowRects.count() < fMaxWindowRectangles) { + // Clip out the inside of every rect. We won't be able to entirely skip the AA ones, + // but it saves processing time. + this->addWindowRectangle(element->getDeviceSpaceRect(), element->isAA()); + if (!element->isAA()) { + return ClipResult::kClipped; + } + } + return this->addAnalyticFP(element->getDeviceSpaceRect(), Invert::kYes, + GrAA(element->isAA())); + + case Element::DeviceSpaceType::kRRect: { + SkASSERT(!element->isInverseFilled()); + const SkRRect& clipRRect = element->getDeviceSpaceRRect(); + ClipResult clipResult = this->addAnalyticFP(clipRRect, Invert::kYes, + GrAA(element->isAA())); + if (fWindowRects.count() >= fMaxWindowRectangles) { + return clipResult; + } + + // Clip out the interiors of round rects with two window rectangles in the shape of a + // "plus". This doesn't let us skip the clip element, but still saves processing time. + SkVector insetTL = clipRRect.radii(SkRRect::kUpperLeft_Corner); + SkVector insetBR = clipRRect.radii(SkRRect::kLowerRight_Corner); + if (SkRRect::kComplex_Type == clipRRect.getType()) { + const SkVector& insetTR = clipRRect.radii(SkRRect::kUpperRight_Corner); + const SkVector& insetBL = clipRRect.radii(SkRRect::kLowerLeft_Corner); + insetTL.fX = SkTMax(insetTL.x(), insetBL.x()); + insetTL.fY = SkTMax(insetTL.y(), insetTR.y()); + insetBR.fX = SkTMax(insetBR.x(), insetTR.x()); + insetBR.fY = SkTMax(insetBR.y(), insetBL.y()); + } + const SkRect& bounds = clipRRect.getBounds(); + if (insetTL.x() + insetBR.x() >= bounds.width() || + insetTL.y() + insetBR.y() >= bounds.height()) { + return clipResult; // The interior "plus" is empty. + } + + SkRect horzRect = SkRect::MakeLTRB(bounds.left(), bounds.top() + insetTL.y(), + bounds.right(), bounds.bottom() - insetBR.y()); + this->addWindowRectangle(horzRect, element->isAA()); + + if (fWindowRects.count() < fMaxWindowRectangles) { + SkRect vertRect = SkRect::MakeLTRB(bounds.left() + insetTL.x(), bounds.top(), + bounds.right() - insetBR.x(), bounds.bottom()); + this->addWindowRectangle(vertRect, element->isAA()); + } + + return clipResult; + } + + case Element::DeviceSpaceType::kPath: + return this->addAnalyticFP(element->getDeviceSpacePath(), + Invert(!element->isInverseFilled()), GrAA(element->isAA())); + } + + SK_ABORT("Unexpected DeviceSpaceType"); +} + +inline void GrReducedClip::addWindowRectangle(const SkRect& elementInteriorRect, bool elementIsAA) { + SkIRect window; + if (!elementIsAA) { + elementInteriorRect.round(&window); + } else { + elementInteriorRect.roundIn(&window); + } + if (!window.isEmpty()) { // Skip very thin windows that round to zero or negative dimensions. + fWindowRects.addWindow(window); + } +} + +GrClipEdgeType GrReducedClip::GetClipEdgeType(Invert invert, GrAA aa) { + if (Invert::kNo == invert) { + return (GrAA::kYes == aa) ? GrClipEdgeType::kFillAA : GrClipEdgeType::kFillBW; + } else { + return (GrAA::kYes == aa) ? GrClipEdgeType::kInverseFillAA : GrClipEdgeType::kInverseFillBW; + } +} + +GrReducedClip::ClipResult GrReducedClip::addAnalyticFP(const SkRect& deviceSpaceRect, + Invert invert, GrAA aa) { + if (this->numAnalyticFPs() >= fMaxAnalyticFPs) { + return ClipResult::kNotClipped; + } + + fAnalyticFPs.push_back(GrAARectEffect::Make(GetClipEdgeType(invert, aa), deviceSpaceRect)); + SkASSERT(fAnalyticFPs.back()); + + return ClipResult::kClipped; +} + +GrReducedClip::ClipResult GrReducedClip::addAnalyticFP(const SkRRect& deviceSpaceRRect, + Invert invert, GrAA aa) { + if (this->numAnalyticFPs() >= fMaxAnalyticFPs) { + return ClipResult::kNotClipped; + } + + if (auto fp = GrRRectEffect::Make(GetClipEdgeType(invert, aa), deviceSpaceRRect, + *fCaps->shaderCaps())) { + fAnalyticFPs.push_back(std::move(fp)); + return ClipResult::kClipped; + } + + SkPath deviceSpacePath; + deviceSpacePath.setIsVolatile(true); + deviceSpacePath.addRRect(deviceSpaceRRect); + return this->addAnalyticFP(deviceSpacePath, invert, aa); +} + +GrReducedClip::ClipResult GrReducedClip::addAnalyticFP(const SkPath& deviceSpacePath, + Invert invert, GrAA aa) { + if (this->numAnalyticFPs() >= fMaxAnalyticFPs) { + return ClipResult::kNotClipped; + } + + if (auto fp = GrConvexPolyEffect::Make(GetClipEdgeType(invert, aa), deviceSpacePath)) { + fAnalyticFPs.push_back(std::move(fp)); + return ClipResult::kClipped; + } + + if (fCCPRClipPaths.count() < fMaxCCPRClipPaths && GrAA::kYes == aa) { + // Set aside CCPR paths for later. We will create their clip FPs once we know the ID of the + // opsTask they will operate in. + SkPath& ccprClipPath = fCCPRClipPaths.push_back(deviceSpacePath); + if (Invert::kYes == invert) { + ccprClipPath.toggleInverseFillType(); + } + return ClipResult::kClipped; + } + + return ClipResult::kNotClipped; +} + +void GrReducedClip::makeEmpty() { + fHasScissor = false; + fAAClipRectGenID = SK_InvalidGenID; + fWindowRects.reset(); + fMaskElements.reset(); + fInitialState = InitialState::kAllOut; +} + +//////////////////////////////////////////////////////////////////////////////// +// Create a 8-bit clip mask in alpha + +static bool stencil_element(GrRenderTargetContext* rtc, + const GrFixedClip& clip, + const GrUserStencilSettings* ss, + const SkMatrix& viewMatrix, + const SkClipStack::Element* element) { + GrAA aa = GrAA(element->isAA()); + switch (element->getDeviceSpaceType()) { + case SkClipStack::Element::DeviceSpaceType::kEmpty: + SkDEBUGFAIL("Should never get here with an empty element."); + break; + case SkClipStack::Element::DeviceSpaceType::kRect: { + GrPaint paint; + paint.setCoverageSetOpXPFactory((SkRegion::Op)element->getOp(), + element->isInverseFilled()); + rtc->priv().stencilRect(clip, ss, std::move(paint), aa, viewMatrix, + element->getDeviceSpaceRect()); + return true; + } + default: { + SkPath path; + element->asDeviceSpacePath(&path); + if (path.isInverseFillType()) { + path.toggleInverseFillType(); + } + + return rtc->priv().drawAndStencilPath(clip, ss, (SkRegion::Op)element->getOp(), + element->isInverseFilled(), aa, viewMatrix, path); + } + } + + return false; +} + +static void stencil_device_rect(GrRenderTargetContext* rtc, + const GrHardClip& clip, + const GrUserStencilSettings* ss, + GrAA aa, + const SkRect& rect) { + GrPaint paint; + paint.setXPFactory(GrDisableColorXPFactory::Get()); + rtc->priv().stencilRect(clip, ss, std::move(paint), aa, SkMatrix::I(), rect); +} + +static void draw_element(GrRenderTargetContext* rtc, + const GrClip& clip, // TODO: can this just always be WideOpen? + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkClipStack::Element* element) { + // TODO: Draw rrects directly here. + switch (element->getDeviceSpaceType()) { + case SkClipStack::Element::DeviceSpaceType::kEmpty: + SkDEBUGFAIL("Should never get here with an empty element."); + break; + case SkClipStack::Element::DeviceSpaceType::kRect: + rtc->drawRect(clip, std::move(paint), aa, viewMatrix, element->getDeviceSpaceRect()); + break; + default: { + SkPath path; + element->asDeviceSpacePath(&path); + if (path.isInverseFillType()) { + path.toggleInverseFillType(); + } + + rtc->drawPath(clip, std::move(paint), aa, viewMatrix, path, GrStyle::SimpleFill()); + break; + } + } +} + +bool GrReducedClip::drawAlphaClipMask(GrRenderTargetContext* rtc) const { + // The texture may be larger than necessary, this rect represents the part of the texture + // we populate with a rasterization of the clip. + GrFixedClip clip(SkIRect::MakeWH(fScissor.width(), fScissor.height())); + + if (!fWindowRects.empty()) { + clip.setWindowRectangles(fWindowRects.makeOffset(-fScissor.left(), -fScissor.top()), + GrWindowRectsState::Mode::kExclusive); + } + + // The scratch texture that we are drawing into can be substantially larger than the mask. Only + // clear the part that we care about. + SkPMColor4f initialCoverage = + InitialState::kAllIn == this->initialState() ? SK_PMColor4fWHITE : SK_PMColor4fTRANSPARENT; + rtc->priv().clear(clip, initialCoverage, GrRenderTargetContext::CanClearFullscreen::kYes); + + // Set the matrix so that rendered clip elements are transformed to mask space from clip space. + SkMatrix translate; + translate.setTranslate(SkIntToScalar(-fScissor.left()), SkIntToScalar(-fScissor.top())); + + // walk through each clip element and perform its set op + for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) { + const Element* element = iter.get(); + SkRegion::Op op = (SkRegion::Op)element->getOp(); + GrAA aa = GrAA(element->isAA()); + bool invert = element->isInverseFilled(); + if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) { + // draw directly into the result with the stencil set to make the pixels affected + // by the clip shape be non-zero. + static constexpr GrUserStencilSettings kStencilInElement( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kReplace, + GrUserStencilOp::kReplace, + 0xffff>() + ); + if (!stencil_element(rtc, clip, &kStencilInElement, translate, element)) { + return false; + } + + // Draw to the exterior pixels (those with a zero stencil value). + static constexpr GrUserStencilSettings kDrawOutsideElement( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqual, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kZero, + 0xffff>() + ); + + GrPaint paint; + paint.setCoverageSetOpXPFactory(op, !invert); + rtc->priv().stencilRect(clip, &kDrawOutsideElement, std::move(paint), GrAA::kNo, + translate, SkRect::Make(fScissor)); + } else { + // all the remaining ops can just be directly draw into the accumulation buffer + GrPaint paint; + paint.setCoverageSetOpXPFactory(op, false); + + draw_element(rtc, clip, std::move(paint), aa, translate, element); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Create a 1-bit clip mask in the stencil buffer. + +bool GrReducedClip::drawStencilClipMask(GrRecordingContext* context, + GrRenderTargetContext* renderTargetContext) const { + // We set the current clip to the bounds so that our recursive draws are scissored to them. + GrStencilClip stencilClip(fScissor, this->maskGenID()); + + if (!fWindowRects.empty()) { + stencilClip.fixedClip().setWindowRectangles(fWindowRects, + GrWindowRectsState::Mode::kExclusive); + } + + bool initialState = InitialState::kAllIn == this->initialState(); + renderTargetContext->priv().clearStencilClip(stencilClip.fixedClip(), initialState); + + // walk through each clip element and perform its set op with the existing clip. + for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) { + const Element* element = iter.get(); + // MIXED SAMPLES TODO: We can use stencil with mixed samples as well. + bool doStencilMSAA = element->isAA() && renderTargetContext->numSamples() > 1; + // Since we are only drawing to the stencil buffer, we can use kMSAA even if the render + // target is mixed sampled. + auto pathAAType = (doStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone; + bool fillInverted = false; + + // This will be used to determine whether the clip shape can be rendered into the + // stencil with arbitrary stencil settings. + GrPathRenderer::StencilSupport stencilSupport; + + SkRegion::Op op = (SkRegion::Op)element->getOp(); + + GrPathRenderer* pr = nullptr; + SkPath clipPath; + if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) { + stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport; + fillInverted = false; + } else { + element->asDeviceSpacePath(&clipPath); + fillInverted = clipPath.isInverseFillType(); + if (fillInverted) { + clipPath.toggleInverseFillType(); + } + + GrShape shape(clipPath, GrStyle::SimpleFill()); + GrPathRenderer::CanDrawPathArgs canDrawArgs; + canDrawArgs.fCaps = context->priv().caps(); + canDrawArgs.fProxy = renderTargetContext->proxy(); + canDrawArgs.fClipConservativeBounds = &stencilClip.fixedClip().scissorRect(); + canDrawArgs.fViewMatrix = &SkMatrix::I(); + canDrawArgs.fShape = &shape; + canDrawArgs.fAAType = pathAAType; + canDrawArgs.fHasUserStencilSettings = false; + canDrawArgs.fTargetIsWrappedVkSecondaryCB = renderTargetContext->wrapsVkSecondaryCB(); + + GrDrawingManager* dm = context->priv().drawingManager(); + pr = dm->getPathRenderer(canDrawArgs, false, GrPathRendererChain::DrawType::kStencil, + &stencilSupport); + if (!pr) { + return false; + } + } + + bool canRenderDirectToStencil = + GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport; + bool drawDirectToClip; // Given the renderer, the element, + // fill rule, and set operation should + // we render the element directly to + // stencil bit used for clipping. + GrUserStencilSettings const* const* stencilPasses = + GrStencilSettings::GetClipPasses(op, canRenderDirectToStencil, fillInverted, + &drawDirectToClip); + + // draw the element to the client stencil bits if necessary + if (!drawDirectToClip) { + static constexpr GrUserStencilSettings kDrawToStencil( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kIncMaybeClamp, + GrUserStencilOp::kIncMaybeClamp, + 0xffff>() + ); + if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) { + stencil_device_rect(renderTargetContext, stencilClip.fixedClip(), &kDrawToStencil, + GrAA(doStencilMSAA), element->getDeviceSpaceRect()); + } else { + if (!clipPath.isEmpty()) { + GrShape shape(clipPath, GrStyle::SimpleFill()); + if (canRenderDirectToStencil) { + GrPaint paint; + paint.setXPFactory(GrDisableColorXPFactory::Get()); + + GrPathRenderer::DrawPathArgs args{context, + std::move(paint), + &kDrawToStencil, + renderTargetContext, + &stencilClip.fixedClip(), + &stencilClip.fixedClip().scissorRect(), + &SkMatrix::I(), + &shape, + pathAAType, + false}; + pr->drawPath(args); + } else { + GrPathRenderer::StencilPathArgs args; + args.fContext = context; + args.fRenderTargetContext = renderTargetContext; + args.fClip = &stencilClip.fixedClip(); + args.fClipConservativeBounds = &stencilClip.fixedClip().scissorRect(); + args.fViewMatrix = &SkMatrix::I(); + args.fDoStencilMSAA = GrAA(doStencilMSAA); + args.fShape = &shape; + pr->stencilPath(args); + } + } + } + } + + // now we modify the clip bit by rendering either the clip + // element directly or a bounding rect of the entire clip. + for (GrUserStencilSettings const* const* pass = stencilPasses; *pass; ++pass) { + if (drawDirectToClip) { + if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) { + stencil_device_rect(renderTargetContext, stencilClip, *pass, + GrAA(doStencilMSAA), element->getDeviceSpaceRect()); + } else { + GrShape shape(clipPath, GrStyle::SimpleFill()); + GrPaint paint; + paint.setXPFactory(GrDisableColorXPFactory::Get()); + GrPathRenderer::DrawPathArgs args{context, + std::move(paint), + *pass, + renderTargetContext, + &stencilClip, + &stencilClip.fixedClip().scissorRect(), + &SkMatrix::I(), + &shape, + pathAAType, + false}; + pr->drawPath(args); + } + } else { + // The view matrix is setup to do clip space -> stencil space translation, so + // draw rect in clip space. + stencil_device_rect(renderTargetContext, stencilClip, *pass, GrAA(doStencilMSAA), + SkRect::Make(fScissor)); + } + } + } + return true; +} + +std::unique_ptr GrReducedClip::finishAndDetachAnalyticFPs( + GrCoverageCountingPathRenderer* ccpr, uint32_t opsTaskID) { + // Make sure finishAndDetachAnalyticFPs hasn't been called already. + SkDEBUGCODE(for (const auto& fp : fAnalyticFPs) { SkASSERT(fp); }) + + if (!fCCPRClipPaths.empty()) { + fAnalyticFPs.reserve(fAnalyticFPs.count() + fCCPRClipPaths.count()); + for (const SkPath& ccprClipPath : fCCPRClipPaths) { + SkASSERT(ccpr); + SkASSERT(fHasScissor); + auto fp = ccpr->makeClipProcessor(opsTaskID, ccprClipPath, fScissor, *fCaps); + fAnalyticFPs.push_back(std::move(fp)); + } + fCCPRClipPaths.reset(); + } + + return GrFragmentProcessor::RunInSeries(fAnalyticFPs.begin(), fAnalyticFPs.count()); +} diff --git a/gfx/skia/skia/src/gpu/GrReducedClip.h b/gfx/skia/skia/src/gpu/GrReducedClip.h new file mode 100644 index 0000000000..ca95bd0994 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrReducedClip.h @@ -0,0 +1,151 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrReducedClip_DEFINED +#define GrReducedClip_DEFINED + +#include "src/core/SkClipStack.h" +#include "src/core/SkTLList.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrWindowRectangles.h" + +class GrCoverageCountingPathRenderer; +class GrRecordingContext; +class GrRenderTargetContext; + +/** + * This class takes a clip stack and produces a reduced set of elements that are equivalent to + * applying that full stack within a specified query rectangle. + */ +class GrReducedClip { +public: + using Element = SkClipStack::Element; + using ElementList = SkTLList; + + GrReducedClip(const SkClipStack&, const SkRect& queryBounds, const GrCaps* caps, + int maxWindowRectangles = 0, int maxAnalyticFPs = 0, int maxCCPRClipPaths = 0); + + enum class InitialState : bool { + kAllIn, + kAllOut + }; + + InitialState initialState() const { return fInitialState; } + + /** + * If hasScissor() is true, the clip mask is not valid outside this rect and the caller must + * enforce this scissor during draw. + */ + const SkIRect& scissor() const { SkASSERT(fHasScissor); return fScissor; } + int left() const { return this->scissor().left(); } + int top() const { return this->scissor().top(); } + int width() const { return this->scissor().width(); } + int height() const { return this->scissor().height(); } + + /** + * Indicates whether scissor() is defined. It will always be defined if the maskElements() are + * nonempty. + */ + bool hasScissor() const { return fHasScissor; } + + /** + * If nonempty, the clip mask is not valid inside these windows and the caller must clip them + * out using the window rectangles GPU extension. + */ + const GrWindowRectangles& windowRectangles() const { return fWindowRects; } + + /** + * An ordered list of clip elements that could not be skipped or implemented by other means. If + * nonempty, the caller must create an alpha and/or stencil mask for these elements and apply it + * during draw. + */ + const ElementList& maskElements() const { return fMaskElements; } + + /** + * If maskElements() are nonempty, uniquely identifies the region of the clip mask that falls + * inside of scissor(). + * + * NOTE: since clip elements might fall outside the query bounds, different regions of the same + * clip stack might have more or less restrictive IDs. + * + * FIXME: this prevents us from reusing a sub-rect of a perfectly good mask when that rect has + * been assigned a less restrictive ID. + */ + uint32_t maskGenID() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskGenID; } + + /** + * Indicates whether antialiasing is required to process any of the mask elements. + */ + bool maskRequiresAA() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskRequiresAA; } + + bool drawAlphaClipMask(GrRenderTargetContext*) const; + bool drawStencilClipMask(GrRecordingContext*, GrRenderTargetContext*) const; + + int numAnalyticFPs() const { return fAnalyticFPs.count() + fCCPRClipPaths.count(); } + + /** + * Called once the client knows the ID of the opsTask that the clip FPs will operate in. This + * method finishes any outstanding work that was waiting for the opsTask ID, then detaches and + * returns this class's list of FPs that complete the clip. + * + * NOTE: this must be called AFTER producing the clip mask (if any) because draw calls on + * the render target context, surface allocations, and even switching render targets (pre MDB) + * may cause flushes or otherwise change which opsTask the actual draw is going into. + */ + std::unique_ptr finishAndDetachAnalyticFPs( + GrCoverageCountingPathRenderer*, uint32_t opsTaskID); + +private: + void walkStack(const SkClipStack&, const SkRect& queryBounds); + + enum class ClipResult { + kNotClipped, + kClipped, + kMadeEmpty + }; + + // Intersects the clip with the element's interior, regardless of inverse fill type. + // NOTE: do not call for elements followed by ops that can grow the clip. + ClipResult clipInsideElement(const Element*); + + // Intersects the clip with the element's exterior, regardless of inverse fill type. + // NOTE: do not call for elements followed by ops that can grow the clip. + ClipResult clipOutsideElement(const Element*); + + void addWindowRectangle(const SkRect& elementInteriorRect, bool elementIsAA); + + enum class Invert : bool { + kNo = false, + kYes = true + }; + + static GrClipEdgeType GetClipEdgeType(Invert, GrAA); + ClipResult addAnalyticFP(const SkRect& deviceSpaceRect, Invert, GrAA); + ClipResult addAnalyticFP(const SkRRect& deviceSpaceRRect, Invert, GrAA); + ClipResult addAnalyticFP(const SkPath& deviceSpacePath, Invert, GrAA); + + void makeEmpty(); + + const GrCaps* fCaps; + const int fMaxWindowRectangles; + const int fMaxAnalyticFPs; + const int fMaxCCPRClipPaths; + + InitialState fInitialState; + SkIRect fScissor; + bool fHasScissor; + SkRect fAAClipRect; + uint32_t fAAClipRectGenID; // GenID the mask will have if includes the AA clip rect. + GrWindowRectangles fWindowRects; + ElementList fMaskElements; + uint32_t fMaskGenID; + bool fMaskRequiresAA; + SkSTArray<4, std::unique_ptr> fAnalyticFPs; + SkSTArray<4, SkPath> fCCPRClipPaths; // Will convert to FPs once we have an opsTask ID for CCPR. +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTarget.cpp b/gfx/skia/skia/src/gpu/GrRenderTarget.cpp new file mode 100644 index 0000000000..2197d63239 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTarget.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/GrRenderTarget.h" + +#include "include/gpu/GrContext.h" +#include "src/core/SkRectPriv.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrSamplePatternDictionary.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrStencilSettings.h" + +GrRenderTarget::GrRenderTarget(GrGpu* gpu, const SkISize& size, GrPixelConfig config, + int sampleCount, GrProtected isProtected, + GrStencilAttachment* stencil) + : INHERITED(gpu, size, config, isProtected) + , fSampleCnt(sampleCount) + , fSamplePatternKey(GrSamplePatternDictionary::kInvalidSamplePatternKey) + , fStencilAttachment(stencil) { +} + +GrRenderTarget::~GrRenderTarget() = default; + +void GrRenderTarget::onRelease() { + fStencilAttachment = nullptr; + + INHERITED::onRelease(); +} + +void GrRenderTarget::onAbandon() { + fStencilAttachment = nullptr; + + INHERITED::onAbandon(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrRenderTargetPriv::attachStencilAttachment(sk_sp stencil) { +#ifdef SK_DEBUG + if (1 == fRenderTarget->fSampleCnt) { + // TODO: We don't expect a mixed sampled render target to ever change its stencil buffer + // right now. But if it does swap in a stencil buffer with a different number of samples, + // and if we have a valid fSamplePatternKey, we will need to invalidate fSamplePatternKey + // here and add tests to make sure we it properly. + SkASSERT(GrSamplePatternDictionary::kInvalidSamplePatternKey == + fRenderTarget->fSamplePatternKey); + } else { + // Render targets with >1 color sample should never use mixed samples. (This would lead to + // different sample patterns, depending on stencil state.) + SkASSERT(!stencil || stencil->numSamples() == fRenderTarget->fSampleCnt); + } +#endif + + if (!stencil && !fRenderTarget->fStencilAttachment) { + // No need to do any work since we currently don't have a stencil attachment and + // we're not actually adding one. + return; + } + + fRenderTarget->fStencilAttachment = std::move(stencil); + if (!fRenderTarget->completeStencilAttachment()) { + fRenderTarget->fStencilAttachment = nullptr; + } +} + +int GrRenderTargetPriv::numStencilBits() const { + SkASSERT(this->getStencilAttachment()); + return this->getStencilAttachment()->bits(); +} + +int GrRenderTargetPriv::getSamplePatternKey() const { +#ifdef SK_DEBUG + GrStencilAttachment* stencil = fRenderTarget->fStencilAttachment.get(); + if (fRenderTarget->fSampleCnt <= 1) { + // If the color buffer is not multisampled, the sample pattern better come from the stencil + // buffer (mixed samples). + SkASSERT(stencil && stencil->numSamples() > 1); + } else { + // The color sample count and stencil count cannot both be unequal and both greater than + // one. If this were the case, there would be more than one sample pattern associated with + // the render target. + SkASSERT(!stencil || stencil->numSamples() == fRenderTarget->fSampleCnt); + } +#endif + if (GrSamplePatternDictionary::kInvalidSamplePatternKey == fRenderTarget->fSamplePatternKey) { + fRenderTarget->fSamplePatternKey = + fRenderTarget->getGpu()->findOrAssignSamplePatternKey(fRenderTarget); + } + SkASSERT(GrSamplePatternDictionary::kInvalidSamplePatternKey + != fRenderTarget->fSamplePatternKey); + return fRenderTarget->fSamplePatternKey; +} diff --git a/gfx/skia/skia/src/gpu/GrRenderTarget.h b/gfx/skia/skia/src/gpu/GrRenderTarget.h new file mode 100644 index 0000000000..c9e173eec1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTarget.h @@ -0,0 +1,77 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTarget_DEFINED +#define GrRenderTarget_DEFINED + +#include "include/core/SkRect.h" +#include "include/gpu/GrSurface.h" + +class GrCaps; +class GrRenderTargetPriv; +class GrStencilAttachment; +class GrBackendRenderTarget; + +/** + * GrRenderTarget represents a 2D buffer of pixels that can be rendered to. + * A context's render target is set by setRenderTarget(). Render targets are + * created by a createTexture with the kRenderTarget_SurfaceFlag flag. + * Additionally, GrContext provides methods for creating GrRenderTargets + * that wrap externally created render targets. + */ +class GrRenderTarget : virtual public GrSurface { +public: + // Make manual MSAA resolve publicly accessible from GrRenderTarget. + using GrSurface::setRequiresManualMSAAResolve; + using GrSurface::requiresManualMSAAResolve; + + virtual bool alwaysClearStencil() const { return false; } + + // GrSurface overrides + GrRenderTarget* asRenderTarget() override { return this; } + const GrRenderTarget* asRenderTarget() const override { return this; } + + /** + * Returns the number of samples/pixel in the color buffer (One if non-MSAA). + */ + int numSamples() const { return fSampleCnt; } + + virtual GrBackendRenderTarget getBackendRenderTarget() const = 0; + + // Checked when this object is asked to attach a stencil buffer. + virtual bool canAttemptStencilAttachment() const = 0; + + // Provides access to functions that aren't part of the public API. + GrRenderTargetPriv renderTargetPriv(); + const GrRenderTargetPriv renderTargetPriv() const; + +protected: + GrRenderTarget(GrGpu*, const SkISize&, GrPixelConfig, int sampleCount, GrProtected, + GrStencilAttachment* = nullptr); + ~GrRenderTarget() override; + + // override of GrResource + void onAbandon() override; + void onRelease() override; + +private: + // Allows the backends to perform any additional work that is required for attaching a + // GrStencilAttachment. When this is called, the GrStencilAttachment has already been put onto + // the GrRenderTarget. This function must return false if any failures occur when completing the + // stencil attachment. + virtual bool completeStencilAttachment() = 0; + + friend class GrRenderTargetPriv; + + int fSampleCnt; + int fSamplePatternKey; + sk_sp fStencilAttachment; + + typedef GrSurface INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetContext.cpp b/gfx/skia/skia/src/gpu/GrRenderTargetContext.cpp new file mode 100644 index 0000000000..517a34cf7c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetContext.cpp @@ -0,0 +1,2466 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrRenderTargetContext.h" + +#include "include/core/SkDrawable.h" +#include "include/gpu/GrBackendSemaphore.h" +#include "include/private/GrRecordingContext.h" +#include "include/private/SkShadowFlags.h" +#include "include/utils/SkShadowUtils.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkDrawShadowInfo.h" +#include "src/core/SkGlyphRunPainter.h" +#include "src/core/SkLatticeIter.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkRRectPriv.h" +#include "src/core/SkSurfacePriv.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrBlurUtils.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClientMappedBufferManager.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrTracing.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrBicubicEffect.h" +#include "src/gpu/effects/GrRRectEffect.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/geometry/GrQuadUtils.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrAtlasTextOp.h" +#include "src/gpu/ops/GrClearOp.h" +#include "src/gpu/ops/GrClearStencilClipOp.h" +#include "src/gpu/ops/GrDebugMarkerOp.h" +#include "src/gpu/ops/GrDrawAtlasOp.h" +#include "src/gpu/ops/GrDrawOp.h" +#include "src/gpu/ops/GrDrawVerticesOp.h" +#include "src/gpu/ops/GrDrawableOp.h" +#include "src/gpu/ops/GrFillRRectOp.h" +#include "src/gpu/ops/GrFillRectOp.h" +#include "src/gpu/ops/GrLatticeOp.h" +#include "src/gpu/ops/GrOp.h" +#include "src/gpu/ops/GrOvalOpFactory.h" +#include "src/gpu/ops/GrRegionOp.h" +#include "src/gpu/ops/GrShadowRRectOp.h" +#include "src/gpu/ops/GrStencilPathOp.h" +#include "src/gpu/ops/GrStrokeRectOp.h" +#include "src/gpu/ops/GrTextureOp.h" +#include "src/gpu/text/GrTextContext.h" +#include "src/gpu/text/GrTextTarget.h" + +class GrRenderTargetContext::TextTarget : public GrTextTarget { +public: + TextTarget(GrRenderTargetContext* renderTargetContext) + : GrTextTarget(renderTargetContext->width(), renderTargetContext->height(), + renderTargetContext->colorInfo()) + , fRenderTargetContext(renderTargetContext) + , fGlyphPainter{*renderTargetContext} {} + + void addDrawOp(const GrClip& clip, std::unique_ptr op) override { + fRenderTargetContext->addDrawOp(clip, std::move(op)); + } + + void drawShape(const GrClip& clip, const SkPaint& paint, + const SkMatrix& viewMatrix, const GrShape& shape) override { + GrBlurUtils::drawShapeWithMaskFilter(fRenderTargetContext->fContext, fRenderTargetContext, + clip, paint, viewMatrix, shape); + } + + void makeGrPaint(GrMaskFormat maskFormat, const SkPaint& skPaint, const SkMatrix& viewMatrix, + GrPaint* grPaint) override { + auto context = fRenderTargetContext->fContext; + const GrColorInfo& colorInfo = fRenderTargetContext->colorInfo(); + if (kARGB_GrMaskFormat == maskFormat) { + SkPaintToGrPaintWithPrimitiveColor(context, colorInfo, skPaint, grPaint); + } else { + SkPaintToGrPaint(context, colorInfo, skPaint, viewMatrix, grPaint); + } + } + + GrRecordingContext* getContext() override { + return fRenderTargetContext->fContext; + } + + SkGlyphRunListPainter* glyphPainter() override { + return &fGlyphPainter; + } + +private: + GrRenderTargetContext* fRenderTargetContext; + SkGlyphRunListPainter fGlyphPainter; + +}; + +#define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this->drawingManager()->getContext()) +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());) +#define ASSERT_SINGLE_OWNER_PRIV \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fRenderTargetContext->singleOwner());) +#define RETURN_IF_ABANDONED if (fContext->priv().abandoned()) { return; } +#define RETURN_IF_ABANDONED_PRIV if (fRenderTargetContext->fContext->priv().abandoned()) { return; } +#define RETURN_FALSE_IF_ABANDONED if (fContext->priv().abandoned()) { return false; } +#define RETURN_FALSE_IF_ABANDONED_PRIV if (fRenderTargetContext->fContext->priv().abandoned()) { return false; } +#define RETURN_NULL_IF_ABANDONED if (fContext->priv().abandoned()) { return nullptr; } + +////////////////////////////////////////////////////////////////////////////// + +class AutoCheckFlush { +public: + AutoCheckFlush(GrDrawingManager* drawingManager) : fDrawingManager(drawingManager) { + SkASSERT(fDrawingManager); + } + ~AutoCheckFlush() { fDrawingManager->flushIfNecessary(); } + +private: + GrDrawingManager* fDrawingManager; +}; + +// In MDB mode the reffing of the 'getLastOpsTask' call's result allows in-progress +// GrOpsTask to be picked up and added to by renderTargetContexts lower in the call +// stack. When this occurs with a closed GrOpsTask, a new one will be allocated +// when the renderTargetContext attempts to use it (via getOpsTask). +GrRenderTargetContext::GrRenderTargetContext(GrRecordingContext* context, + sk_sp rtp, + GrColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + bool managedOpsTask) + : GrSurfaceContext(context, colorType, kPremul_SkAlphaType, std::move(colorSpace)) + , fRenderTargetProxy(std::move(rtp)) + , fOpsTask(sk_ref_sp(fRenderTargetProxy->getLastOpsTask())) + , fSurfaceProps(SkSurfacePropsCopyOrDefault(surfaceProps)) + , fManagedOpsTask(managedOpsTask) { + fTextTarget.reset(new TextTarget(this)); + SkDEBUGCODE(this->validate();) +} + +#ifdef SK_DEBUG +void GrRenderTargetContext::validate() const { + SkASSERT(fRenderTargetProxy); + fRenderTargetProxy->validate(fContext); + + SkASSERT(fContext->priv().caps()->areColorTypeAndFormatCompatible( + this->colorInfo().colorType(), fRenderTargetProxy->backendFormat())); + + if (fOpsTask && !fOpsTask->isClosed()) { + SkASSERT(fRenderTargetProxy->getLastRenderTask() == fOpsTask.get()); + } +} +#endif + +GrRenderTargetContext::~GrRenderTargetContext() { + ASSERT_SINGLE_OWNER +} + +inline GrAAType GrRenderTargetContext::chooseAAType(GrAA aa) { + if (GrAA::kNo == aa) { + // On some devices we cannot disable MSAA if it is enabled so we make the AA type reflect + // that. + if (this->numSamples() > 1 && !this->caps()->multisampleDisableSupport()) { + return GrAAType::kMSAA; + } + return GrAAType::kNone; + } + return (this->numSamples() > 1) ? GrAAType::kMSAA : GrAAType::kCoverage; +} + +GrTextureProxy* GrRenderTargetContext::asTextureProxy() { + return fRenderTargetProxy->asTextureProxy(); +} + +const GrTextureProxy* GrRenderTargetContext::asTextureProxy() const { + return fRenderTargetProxy->asTextureProxy(); +} + +sk_sp GrRenderTargetContext::asTextureProxyRef() { + return sk_ref_sp(fRenderTargetProxy->asTextureProxy()); +} + +GrMipMapped GrRenderTargetContext::mipMapped() const { + if (const GrTextureProxy* proxy = this->asTextureProxy()) { + return proxy->mipMapped(); + } + return GrMipMapped::kNo; +} + +GrOpsTask* GrRenderTargetContext::getOpsTask() { + ASSERT_SINGLE_OWNER + SkDEBUGCODE(this->validate();) + + if (!fOpsTask || fOpsTask->isClosed()) { + sk_sp newOpsTask = + this->drawingManager()->newOpsTask(fRenderTargetProxy, fManagedOpsTask); + if (fOpsTask && fNumStencilSamples > 0) { + // Store the stencil values in memory upon completion of fOpsTask. + fOpsTask->setMustPreserveStencil(); + // Reload the stencil buffer content at the beginning of newOpsTask. + // FIXME: Could the topo sort insert a task between these two that modifies the stencil + // values? + newOpsTask->setInitialStencilContent(GrOpsTask::StencilContent::kPreserved); + } + fOpsTask = std::move(newOpsTask); + } + + return fOpsTask.get(); +} + +void GrRenderTargetContext::drawGlyphRunList( + const GrClip& clip, const SkMatrix& viewMatrix, + const SkGlyphRunList& blob) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawGlyphRunList", fContext); + + // Drawing text can cause us to do inline uploads. This is not supported for wrapped vulkan + // secondary command buffers because it would require stopping and starting a render pass which + // we don't have access to. + if (this->wrapsVkSecondaryCB()) { + return; + } + + GrTextContext* atlasTextContext = this->drawingManager()->getTextContext(); + atlasTextContext->drawGlyphRunList(fContext, fTextTarget.get(), clip, viewMatrix, + fSurfaceProps, blob); +} + +void GrRenderTargetContext::discard() { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "discard", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + this->getOpsTask()->discard(); +} + +void GrRenderTargetContext::clear(const SkIRect* rect, + const SkPMColor4f& color, + CanClearFullscreen canClearFullscreen) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "clear", fContext); + + AutoCheckFlush acf(this->drawingManager()); + this->internalClear(rect ? GrFixedClip(*rect) : GrFixedClip::Disabled(), color, + canClearFullscreen); +} + +void GrRenderTargetContextPriv::clear(const GrFixedClip& clip, + const SkPMColor4f& color, + CanClearFullscreen canClearFullscreen) { + ASSERT_SINGLE_OWNER_PRIV + RETURN_IF_ABANDONED_PRIV + SkDEBUGCODE(fRenderTargetContext->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "clear", + fRenderTargetContext->fContext); + + AutoCheckFlush acf(fRenderTargetContext->drawingManager()); + fRenderTargetContext->internalClear(clip, color, canClearFullscreen); +} + +static void clear_to_grpaint(const SkPMColor4f& color, GrPaint* paint) { + paint->setColor4f(color); + if (color.isOpaque()) { + // Can just rely on the src-over blend mode to do the right thing + paint->setPorterDuffXPFactory(SkBlendMode::kSrcOver); + } else { + // A clear overwrites the prior color, so even if it's transparent, it behaves as if it + // were src blended + paint->setPorterDuffXPFactory(SkBlendMode::kSrc); + } +} + +void GrRenderTargetContext::internalClear(const GrFixedClip& clip, + const SkPMColor4f& color, + CanClearFullscreen canClearFullscreen) { + bool isFull = false; + if (!clip.hasWindowRectangles()) { + // TODO: wrt the shouldInitializeTextures path, it would be more performant to + // only clear the entire target if we knew it had not been cleared before. As + // is this could end up doing a lot of redundant clears. + isFull = !clip.scissorEnabled() || + (CanClearFullscreen::kYes == canClearFullscreen && + (this->caps()->preferFullscreenClears() || this->caps()->shouldInitializeTextures())) || + clip.scissorRect().contains(SkIRect::MakeWH(this->width(), this->height())); + } + + if (isFull) { + GrOpsTask* opsTask = this->getOpsTask(); + if (opsTask->resetForFullscreenClear(this->canDiscardPreviousOpsOnFullClear()) && + !this->caps()->performColorClearsAsDraws()) { + // The op list was emptied and native clears are allowed, so just use the load op + opsTask->setColorLoadOp(GrLoadOp::kClear, color); + return; + } else { + // Will use an op for the clear, reset the load op to discard since the op will + // blow away the color buffer contents + opsTask->setColorLoadOp(GrLoadOp::kDiscard); + } + + // Must add an op to the list (either because we couldn't use a load op, or because the + // clear load op isn't supported) + if (this->caps()->performColorClearsAsDraws()) { + SkRect rtRect = SkRect::MakeWH(this->width(), this->height()); + GrPaint paint; + clear_to_grpaint(color, &paint); + this->addDrawOp(GrFixedClip::Disabled(), + GrFillRectOp::MakeNonAARect(fContext, std::move(paint), SkMatrix::I(), + rtRect)); + } else { + this->addOp(GrClearOp::Make( + fContext, SkIRect::MakeEmpty(), color, /* fullscreen */ true)); + } + } else { + if (this->caps()->performPartialClearsAsDraws()) { + // performPartialClearsAsDraws() also returns true if any clear has to be a draw. + GrPaint paint; + clear_to_grpaint(color, &paint); + + this->addDrawOp(clip, + GrFillRectOp::MakeNonAARect(fContext, std::move(paint), SkMatrix::I(), + SkRect::Make(clip.scissorRect()))); + } else { + std::unique_ptr op(GrClearOp::Make(fContext, clip, color, + this->asSurfaceProxy())); + // This version of the clear op factory can return null if the clip doesn't intersect + // with the surface proxy's boundary + if (!op) { + return; + } + this->addOp(std::move(op)); + } + } +} + +void GrRenderTargetContext::drawPaint(const GrClip& clip, + GrPaint&& paint, + const SkMatrix& viewMatrix) { + // Start with the render target, since that is the maximum content we could possibly fill. + // drawFilledQuad() will automatically restrict it to clip bounds for us if possible. + SkRect r = fRenderTargetProxy->getBoundsRect(); + if (!paint.numTotalFragmentProcessors()) { + // The paint is trivial so we won't need to use local coordinates, so skip calculating the + // inverse view matrix. + this->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), r, r); + } else { + // Use the inverse view matrix to arrive at appropriate local coordinates for the paint. + SkMatrix localMatrix; + if (!viewMatrix.invert(&localMatrix)) { + return; + } + this->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), r, + localMatrix); + } +} + +enum class GrRenderTargetContext::QuadOptimization { + // The rect to draw doesn't intersect clip or render target, so no draw op should be added + kDiscarded, + // The rect to draw was converted to some other op and appended to the oplist, so no additional + // op is necessary. Currently this can convert it to a clear op or a rrect op. Only valid if + // a constColor is provided. + kSubmitted, + // The clip was folded into the device quad, with updated edge flags and local coords, and + // caller is responsible for adding an appropriate op. + kClipApplied, + // No change to clip, but quad updated to better fit clip/render target, and caller is + // responsible for adding an appropriate op. + kCropped +}; + +static bool make_vertex_finite(float* value) { + if (SkScalarIsNaN(*value)) { + return false; + } + + if (!SkScalarIsFinite(*value)) { + // +/- infinity at this point. Don't use exactly SK_ScalarMax so that we have some precision + // left when calculating crops. + static constexpr float kNearInfinity = SK_ScalarMax / 4.f; + *value = *value < 0.f ? -kNearInfinity : kNearInfinity; + } + + return true; +} + +GrRenderTargetContext::QuadOptimization GrRenderTargetContext::attemptQuadOptimization( + const GrClip& clip, const SkPMColor4f* constColor, + const GrUserStencilSettings* stencilSettings, GrAA* aa, GrQuadAAFlags* edgeFlags, + GrQuad* deviceQuad, GrQuad* localQuad) { + // Optimization requirements: + // 1. kDiscard applies when clip bounds and quad bounds do not intersect + // 2. kClear applies when constColor and final geom is pixel aligned rect; + // pixel aligned rect requires rect clip and (rect quad or quad covers clip) + // 3. kRRect applies when constColor and rrect clip and quad covers clip + // 4. kExplicitClip applies when rect clip and (rect quad or quad covers clip) + // 5. kCropped applies when rect quad (currently) + // 6. kNone always applies + GrQuadAAFlags newFlags = *edgeFlags; + + SkRect rtRect; + if (stencilSettings) { + // Must use worst case bounds so that stencil buffer updates on approximately sized render + // targets don't get corrupted. + rtRect = SkRect::MakeWH(fRenderTargetProxy->worstCaseWidth(), + fRenderTargetProxy->worstCaseHeight()); + } else { + // Use the logical size of the render target, which allows for "fullscreen" clears even if + // the render target has an approximate backing fit + rtRect = SkRect::MakeWH(this->width(), this->height()); + } + + SkRect drawBounds = deviceQuad->bounds(); + if (constColor) { + // Don't bother updating local coordinates when the paint will ignore them anyways + localQuad = nullptr; + // If the device quad is not finite, coerce into a finite quad. This is acceptable since it + // will be cropped to the finite 'clip' or render target and there is no local space mapping + if (!deviceQuad->isFinite()) { + for (int i = 0; i < 4; ++i) { + if (!make_vertex_finite(deviceQuad->xs() + i) || + !make_vertex_finite(deviceQuad->ys() + i) || + !make_vertex_finite(deviceQuad->ws() + i)) { + // Discard if we see a nan + return QuadOptimization::kDiscarded; + } + } + SkASSERT(deviceQuad->isFinite()); + } + } else { + // CropToRect requires the quads to be finite. If they are not finite and we have local + // coordinates, the mapping from local space to device space is poorly defined so drop it + if (!deviceQuad->isFinite()) { + return QuadOptimization::kDiscarded; + } + } + + // If the quad is entirely off screen, it doesn't matter what the clip does + if (!rtRect.intersects(drawBounds)) { + return QuadOptimization::kDiscarded; + } + + // Check if clip can be represented as a rounded rect (initialize as if clip fully contained + // the render target). + SkRRect clipRRect = SkRRect::MakeRect(rtRect); + // We initialize clipAA to *aa when there are stencil settings so that we don't artificially + // encounter mixed-aa edges (not allowed for stencil), but we want to start as non-AA for + // regular draws so that if we fully cover the render target, that can stop being anti-aliased. + GrAA clipAA = stencilSettings ? *aa : GrAA::kNo; + bool axisAlignedClip = true; + if (!clip.quickContains(rtRect)) { + if (!clip.isRRect(rtRect, &clipRRect, &clipAA)) { + axisAlignedClip = false; + } + } + + // If the clip rrect is valid (i.e. axis-aligned), we can potentially combine it with the + // draw geometry so that no clip is needed when drawing. + if (axisAlignedClip && (!stencilSettings || clipAA == *aa)) { + // Tighten clip bounds (if clipRRect.isRect() is true, clipBounds now holds the intersection + // of the render target and the clip rect) + SkRect clipBounds = rtRect; + if (!clipBounds.intersect(clipRRect.rect()) || !clipBounds.intersects(drawBounds)) { + return QuadOptimization::kDiscarded; + } + + if (clipRRect.isRect()) { + // No rounded corners, so the kClear and kExplicitClip optimizations are possible + if (GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad)) { + if (constColor && deviceQuad->quadType() == GrQuad::Type::kAxisAligned) { + // Clear optimization is possible + drawBounds = deviceQuad->bounds(); + if (drawBounds.contains(rtRect)) { + // Fullscreen clear + this->clear(nullptr, *constColor, CanClearFullscreen::kYes); + return QuadOptimization::kSubmitted; + } else if (GrClip::IsPixelAligned(drawBounds) && + drawBounds.width() > 256 && drawBounds.height() > 256) { + // Scissor + clear (round shouldn't do anything since we are pixel aligned) + SkIRect scissorRect; + drawBounds.round(&scissorRect); + this->clear(&scissorRect, *constColor, CanClearFullscreen::kNo); + return QuadOptimization::kSubmitted; + } + } + + // Update overall AA setting. + *edgeFlags = newFlags; + if (*aa == GrAA::kNo && clipAA == GrAA::kYes && + newFlags != GrQuadAAFlags::kNone) { + // The clip was anti-aliased and now the draw needs to be upgraded to AA to + // properly reflect the smooth edge of the clip. + *aa = GrAA::kYes; + } + // We intentionally do not downgrade AA here because we don't know if we need to + // preserve MSAA (see GrQuadAAFlags docs). But later in the pipeline, the ops can + // use GrResolveAATypeForQuad() to turn off coverage AA when all flags are off. + + // deviceQuad is exactly the intersection of original quad and clip, so it can be + // drawn with no clip (submitted by caller) + return QuadOptimization::kClipApplied; + } else { + // The quads have been updated to better fit the clip bounds, but can't get rid of + // the clip entirely + return QuadOptimization::kCropped; + } + } else if (constColor) { + // Rounded corners and constant filled color (limit ourselves to solid colors because + // there is no way to use custom local coordinates with drawRRect). + if (GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad) && + deviceQuad->quadType() == GrQuad::Type::kAxisAligned && + deviceQuad->bounds().contains(clipBounds)) { + // Since the cropped quad became a rectangle which covered the bounds of the rrect, + // we can draw the rrect directly and ignore the edge flags + GrPaint paint; + clear_to_grpaint(*constColor, &paint); + this->drawRRect(GrFixedClip::Disabled(), std::move(paint), clipAA, SkMatrix::I(), + clipRRect, GrStyle::SimpleFill()); + return QuadOptimization::kSubmitted; + } else { + // The quad has been updated to better fit clip bounds, but can't remove the clip + return QuadOptimization::kCropped; + } + } + } + + // Crop the quad to the conservative bounds of the clip. + SkIRect clipDevBounds; + clip.getConservativeBounds(rtRect.width(), rtRect.height(), &clipDevBounds); + SkRect clipBounds = SkRect::Make(clipDevBounds); + + // One final check for discarding, since we may have gone here directly due to a complex clip + if (!clipBounds.intersects(drawBounds)) { + return QuadOptimization::kDiscarded; + } + + // Even if this were to return true, the crop rect does not exactly match the clip, so can not + // report explicit-clip. Since these edges aren't visible, don't update the final edge flags. + GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad); + + return QuadOptimization::kCropped; +} + +void GrRenderTargetContext::drawFilledQuad(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + GrQuadAAFlags edgeFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const GrUserStencilSettings* ss) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawFilledQuad", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + SkPMColor4f* constColor = nullptr; + SkPMColor4f paintColor; + if (!ss && !paint.numCoverageFragmentProcessors() && + paint.isConstantBlendedColor(&paintColor)) { + // Only consider clears/rrects when it's easy to guarantee 100% fill with single color + constColor = &paintColor; + } + + GrQuad croppedDeviceQuad = deviceQuad; + GrQuad croppedLocalQuad = localQuad; + QuadOptimization opt = this->attemptQuadOptimization(clip, constColor, ss, &aa, &edgeFlags, + &croppedDeviceQuad, &croppedLocalQuad); + if (opt >= QuadOptimization::kClipApplied) { + // These optimizations require caller to add an op themselves + const GrClip& finalClip = opt == QuadOptimization::kClipApplied ? GrFixedClip::Disabled() + : clip; + GrAAType aaType = ss ? (aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone) + : this->chooseAAType(aa); + this->addDrawOp(finalClip, GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeFlags, + croppedDeviceQuad, croppedLocalQuad, ss)); + } + // All other optimization levels were completely handled inside attempt(), so no extra op needed +} + +void GrRenderTargetContext::drawTexturedQuad(const GrClip& clip, + sk_sp proxy, + GrColorType srcColorType, + sk_sp textureXform, + GrSamplerState::Filter filter, + const SkPMColor4f& color, + SkBlendMode blendMode, + GrAA aa, + GrQuadAAFlags edgeFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + SkASSERT(proxy); + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawTexturedQuad", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + // Functionally this is very similar to drawFilledQuad except that there's no constColor to + // enable the kSubmitted optimizations, no stencil settings support, and its a GrTextureOp. + GrQuad croppedDeviceQuad = deviceQuad; + GrQuad croppedLocalQuad = localQuad; + QuadOptimization opt = this->attemptQuadOptimization(clip, nullptr, nullptr, &aa, &edgeFlags, + &croppedDeviceQuad, &croppedLocalQuad); + + SkASSERT(opt != QuadOptimization::kSubmitted); + if (opt != QuadOptimization::kDiscarded) { + // And the texture op if not discarded + const GrClip& finalClip = opt == QuadOptimization::kClipApplied ? GrFixedClip::Disabled() + : clip; + GrAAType aaType = this->chooseAAType(aa); + auto clampType = GrColorTypeClampType(this->colorInfo().colorType()); + auto saturate = clampType == GrClampType::kManual ? GrTextureOp::Saturate::kYes + : GrTextureOp::Saturate::kNo; + // Use the provided domain, although hypothetically we could detect that the cropped local + // quad is sufficiently inside the domain and the constraint could be dropped. + this->addDrawOp(finalClip, + GrTextureOp::Make(fContext, std::move(proxy), srcColorType, + std::move(textureXform), filter, color, saturate, + blendMode, aaType, edgeFlags, croppedDeviceQuad, + croppedLocalQuad, domain)); + } +} + +void GrRenderTargetContext::drawRect(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRect& rect, + const GrStyle* style) { + if (!style) { + style = &GrStyle::SimpleFill(); + } + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawRect", fContext); + + // Path effects should've been devolved to a path in SkGpuDevice + SkASSERT(!style->pathEffect()); + + AutoCheckFlush acf(this->drawingManager()); + + const SkStrokeRec& stroke = style->strokeRec(); + if (stroke.getStyle() == SkStrokeRec::kFill_Style) { + // Fills the rect, using rect as its own local coordinates + this->fillRectToRect(clip, std::move(paint), aa, viewMatrix, rect, rect); + return; + } else if (stroke.getStyle() == SkStrokeRec::kStroke_Style || + stroke.getStyle() == SkStrokeRec::kHairline_Style) { + if ((!rect.width() || !rect.height()) && + SkStrokeRec::kHairline_Style != stroke.getStyle()) { + SkScalar r = stroke.getWidth() / 2; + // TODO: Move these stroke->fill fallbacks to GrShape? + switch (stroke.getJoin()) { + case SkPaint::kMiter_Join: + this->drawRect( + clip, std::move(paint), aa, viewMatrix, + {rect.fLeft - r, rect.fTop - r, rect.fRight + r, rect.fBottom + r}, + &GrStyle::SimpleFill()); + return; + case SkPaint::kRound_Join: + // Raster draws nothing when both dimensions are empty. + if (rect.width() || rect.height()){ + SkRRect rrect = SkRRect::MakeRectXY(rect.makeOutset(r, r), r, r); + this->drawRRect(clip, std::move(paint), aa, viewMatrix, rrect, + GrStyle::SimpleFill()); + return; + } + case SkPaint::kBevel_Join: + if (!rect.width()) { + this->drawRect(clip, std::move(paint), aa, viewMatrix, + {rect.fLeft - r, rect.fTop, rect.fRight + r, rect.fBottom}, + &GrStyle::SimpleFill()); + } else { + this->drawRect(clip, std::move(paint), aa, viewMatrix, + {rect.fLeft, rect.fTop - r, rect.fRight, rect.fBottom + r}, + &GrStyle::SimpleFill()); + } + return; + } + } + + std::unique_ptr op; + + GrAAType aaType = this->chooseAAType(aa); + op = GrStrokeRectOp::Make(fContext, std::move(paint), aaType, viewMatrix, rect, stroke); + // op may be null if the stroke is not supported or if using coverage aa and the view matrix + // does not preserve rectangles. + if (op) { + this->addDrawOp(clip, std::move(op)); + return; + } + } + assert_alive(paint); + this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrShape(rect, *style)); +} + +void GrRenderTargetContext::drawQuadSet(const GrClip& clip, GrPaint&& paint, GrAA aa, + const SkMatrix& viewMatrix, const QuadSetEntry quads[], + int cnt) { + GrAAType aaType = this->chooseAAType(aa); + this->addDrawOp(clip, GrFillRectOp::MakeSet(fContext, std::move(paint), aaType, viewMatrix, + quads, cnt)); +} + +int GrRenderTargetContextPriv::maxWindowRectangles() const { + return fRenderTargetContext->fRenderTargetProxy->maxWindowRectangles( + *fRenderTargetContext->caps()); +} + +GrOpsTask::CanDiscardPreviousOps GrRenderTargetContext::canDiscardPreviousOpsOnFullClear( + ) const { +#if GR_TEST_UTILS + if (fPreserveOpsOnFullClear_TestingOnly) { + return GrOpsTask::CanDiscardPreviousOps::kNo; + } +#endif + // Regardless of how the clear is implemented (native clear or a fullscreen quad), all prior ops + // would normally be overwritten. The one exception is if the render target context is marked as + // needing a stencil buffer then there may be a prior op that writes to the stencil buffer. + // Although the clear will ignore the stencil buffer, following draw ops may not so we can't get + // rid of all the preceding ops. Beware! If we ever add any ops that have a side effect beyond + // modifying the stencil buffer we will need a more elaborate tracking system (skbug.com/7002). + return GrOpsTask::CanDiscardPreviousOps(!fNumStencilSamples); +} + +void GrRenderTargetContext::setNeedsStencil(bool useMixedSamplesIfNotMSAA) { + // Don't clear stencil until after we've changed fNumStencilSamples. This ensures we don't loop + // forever in the event that there are driver bugs and we need to clear as a draw. + bool hasInitializedStencil = fNumStencilSamples > 0; + + int numRequiredSamples = this->numSamples(); + if (useMixedSamplesIfNotMSAA && 1 == numRequiredSamples) { + SkASSERT(fRenderTargetProxy->canUseMixedSamples(*this->caps())); + numRequiredSamples = this->caps()->internalMultisampleCount( + this->asSurfaceProxy()->backendFormat()); + } + SkASSERT(numRequiredSamples > 0); + + if (numRequiredSamples > fNumStencilSamples) { + fNumStencilSamples = numRequiredSamples; + fRenderTargetProxy->setNeedsStencil(fNumStencilSamples); + } + + if (!hasInitializedStencil) { + if (this->caps()->performStencilClearsAsDraws()) { + // There is a driver bug with clearing stencil. We must use an op to manually clear the + // stencil buffer before the op that required 'setNeedsStencil'. + this->internalStencilClear(GrFixedClip::Disabled(), /* inside mask */ false); + } else { + this->getOpsTask()->setInitialStencilContent( + GrOpsTask::StencilContent::kUserBitsCleared); + } + } +} + +void GrRenderTargetContextPriv::clearStencilClip(const GrFixedClip& clip, bool insideStencilMask) { + ASSERT_SINGLE_OWNER_PRIV + RETURN_IF_ABANDONED_PRIV + SkDEBUGCODE(fRenderTargetContext->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "clearStencilClip", + fRenderTargetContext->fContext); + + AutoCheckFlush acf(fRenderTargetContext->drawingManager()); + + fRenderTargetContext->internalStencilClear(clip, insideStencilMask); +} + +void GrRenderTargetContext::internalStencilClear(const GrFixedClip& clip, bool insideStencilMask) { + this->setNeedsStencil(/* useMixedSamplesIfNotMSAA = */ false); + + if (this->caps()->performStencilClearsAsDraws()) { + const GrUserStencilSettings* ss = GrStencilSettings::SetClipBitSettings(insideStencilMask); + SkRect rtRect = SkRect::MakeWH(this->width(), this->height()); + + // Configure the paint to have no impact on the color buffer + GrPaint paint; + paint.setXPFactory(GrDisableColorXPFactory::Get()); + this->addDrawOp(clip, GrFillRectOp::MakeNonAARect(fContext, std::move(paint), SkMatrix::I(), + rtRect, ss)); + } else { + std::unique_ptr op(GrClearStencilClipOp::Make(fContext, clip, insideStencilMask, + fRenderTargetProxy.get())); + if (!op) { + return; + } + this->addOp(std::move(op)); + } +} + +void GrRenderTargetContextPriv::stencilPath(const GrHardClip& clip, + GrAA doStencilMSAA, + const SkMatrix& viewMatrix, + sk_sp path) { + ASSERT_SINGLE_OWNER_PRIV + RETURN_IF_ABANDONED_PRIV + SkDEBUGCODE(fRenderTargetContext->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "stencilPath", + fRenderTargetContext->fContext); + + // TODO: extract portions of checkDraw that are relevant to path stenciling. + SkASSERT(path); + SkASSERT(fRenderTargetContext->caps()->shaderCaps()->pathRenderingSupport()); + + // FIXME: Use path bounds instead of this WAR once + // https://bugs.chromium.org/p/skia/issues/detail?id=5640 is resolved. + SkRect bounds = SkRect::MakeIWH(fRenderTargetContext->width(), fRenderTargetContext->height()); + + // Setup clip + GrAppliedHardClip appliedClip; + if (!clip.apply(fRenderTargetContext->width(), fRenderTargetContext->height(), &appliedClip, + &bounds)) { + return; + } + + std::unique_ptr op = GrStencilPathOp::Make(fRenderTargetContext->fContext, + viewMatrix, + GrAA::kYes == doStencilMSAA, + appliedClip.hasStencilClip(), + appliedClip.scissorState(), + std::move(path)); + if (!op) { + return; + } + op->setClippedBounds(bounds); + + fRenderTargetContext->setNeedsStencil(GrAA::kYes == doStencilMSAA); + fRenderTargetContext->addOp(std::move(op)); +} + +void GrRenderTargetContext::drawTextureSet(const GrClip& clip, const TextureSetEntry set[], int cnt, + GrSamplerState::Filter filter, SkBlendMode mode, + GrAA aa, SkCanvas::SrcRectConstraint constraint, + const SkMatrix& viewMatrix, + sk_sp texXform) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawTextureSet", fContext); + + if (mode != SkBlendMode::kSrcOver || + !fContext->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) { + // Draw one at a time since the bulk API doesn't support non src-over blending, or the + // backend can't support the bulk geometry processor yet. + SkMatrix ctm; + for (int i = 0; i < cnt; ++i) { + float alpha = set[i].fAlpha; + ctm = viewMatrix; + if (set[i].fPreViewMatrix) { + ctm.preConcat(*set[i].fPreViewMatrix); + } + + GrQuad quad, srcQuad; + if (set[i].fDstClipQuad) { + quad = GrQuad::MakeFromSkQuad(set[i].fDstClipQuad, ctm); + + SkPoint srcPts[4]; + GrMapRectPoints(set[i].fDstRect, set[i].fSrcRect, set[i].fDstClipQuad, srcPts, 4); + srcQuad = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I()); + } else { + quad = GrQuad::MakeFromRect(set[i].fDstRect, ctm); + srcQuad = GrQuad(set[i].fSrcRect); + } + + const SkRect* domain = constraint == SkCanvas::kStrict_SrcRectConstraint + ? &set[i].fSrcRect : nullptr; + this->drawTexturedQuad(clip, set[i].fProxy, set[i].fSrcColorType, texXform, filter, + {alpha, alpha, alpha, alpha}, mode, aa, set[i].fAAFlags, + quad, srcQuad, domain); + } + } else { + // Can use a single op, avoiding GrPaint creation, and can batch across proxies + AutoCheckFlush acf(this->drawingManager()); + GrAAType aaType = this->chooseAAType(aa); + auto clampType = GrColorTypeClampType(this->colorInfo().colorType()); + auto saturate = clampType == GrClampType::kManual ? GrTextureOp::Saturate::kYes + : GrTextureOp::Saturate::kNo; + auto op = GrTextureOp::MakeSet(fContext, set, cnt, filter, saturate, aaType, constraint, + viewMatrix, std::move(texXform)); + this->addDrawOp(clip, std::move(op)); + } +} + +void GrRenderTargetContext::drawVertices(const GrClip& clip, + GrPaint&& paint, + const SkMatrix& viewMatrix, + sk_sp vertices, + const SkVertices::Bone bones[], + int boneCount, + GrPrimitiveType* overridePrimType) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawVertices", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + SkASSERT(vertices); + GrAAType aaType = this->chooseAAType(GrAA::kNo); + std::unique_ptr op = GrDrawVerticesOp::Make( + fContext, std::move(paint), std::move(vertices), bones, boneCount, viewMatrix, aaType, + this->colorInfo().refColorSpaceXformFromSRGB(), overridePrimType); + this->addDrawOp(clip, std::move(op)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrRenderTargetContext::drawAtlas(const GrClip& clip, + GrPaint&& paint, + const SkMatrix& viewMatrix, + int spriteCount, + const SkRSXform xform[], + const SkRect texRect[], + const SkColor colors[]) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawAtlas", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + GrAAType aaType = this->chooseAAType(GrAA::kNo); + std::unique_ptr op = GrDrawAtlasOp::Make(fContext, std::move(paint), viewMatrix, + aaType, spriteCount, xform, texRect, colors); + this->addDrawOp(clip, std::move(op)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrRenderTargetContext::drawRRect(const GrClip& origClip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRRect& rrect, + const GrStyle& style) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawRRect", fContext); + + const SkStrokeRec& stroke = style.strokeRec(); + if (stroke.getStyle() == SkStrokeRec::kFill_Style && rrect.isEmpty()) { + return; + } + + GrNoClip noclip; + const GrClip* clip = &origClip; +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // The Android framework frequently clips rrects to themselves where the clip is non-aa and the + // draw is aa. Since our lower level clip code works from op bounds, which are SkRects, it + // doesn't detect that the clip can be ignored (modulo antialiasing). The following test + // attempts to mitigate the stencil clip cost but will only help when the entire clip stack + // can be ignored. We'd prefer to fix this in the framework by removing the clips calls. This + // only works for filled rrects since the stroke width outsets beyond the rrect itself. + SkRRect devRRect; + if (stroke.getStyle() == SkStrokeRec::kFill_Style && rrect.transform(viewMatrix, &devRRect) && + clip->quickContains(devRRect)) { + clip = &noclip; + } +#endif + SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice + + AutoCheckFlush acf(this->drawingManager()); + + GrAAType aaType = this->chooseAAType(aa); + + std::unique_ptr op; + if (GrAAType::kCoverage == aaType && rrect.isSimple() && + rrect.getSimpleRadii().fX == rrect.getSimpleRadii().fY && + viewMatrix.rectStaysRect() && viewMatrix.isSimilarity()) { + // In coverage mode, we draw axis-aligned circular roundrects with the GrOvalOpFactory + // to avoid perf regressions on some platforms. + assert_alive(paint); + op = GrOvalOpFactory::MakeCircularRRectOp( + fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps()); + } + if (!op && style.isSimpleFill()) { + assert_alive(paint); + op = GrFillRRectOp::Make( + fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint)); + } + if (!op && GrAAType::kCoverage == aaType) { + assert_alive(paint); + op = GrOvalOpFactory::MakeRRectOp( + fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps()); + } + if (op) { + this->addDrawOp(*clip, std::move(op)); + return; + } + + assert_alive(paint); + this->drawShapeUsingPathRenderer(*clip, std::move(paint), aa, viewMatrix, + GrShape(rrect, style)); +} + +/////////////////////////////////////////////////////////////////////////////// + +static SkPoint3 map(const SkMatrix& m, const SkPoint3& pt) { + SkPoint3 result; + m.mapXY(pt.fX, pt.fY, (SkPoint*)&result.fX); + result.fZ = pt.fZ; + return result; +} + +bool GrRenderTargetContext::drawFastShadow(const GrClip& clip, + const SkMatrix& viewMatrix, + const SkPath& path, + const SkDrawShadowRec& rec) { + ASSERT_SINGLE_OWNER + if (fContext->priv().abandoned()) { + return true; + } + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawFastShadow", fContext); + + // check z plane + bool tiltZPlane = SkToBool(!SkScalarNearlyZero(rec.fZPlaneParams.fX) || + !SkScalarNearlyZero(rec.fZPlaneParams.fY)); + bool skipAnalytic = SkToBool(rec.fFlags & SkShadowFlags::kGeometricOnly_ShadowFlag); + if (tiltZPlane || skipAnalytic || !viewMatrix.rectStaysRect() || !viewMatrix.isSimilarity()) { + return false; + } + + SkRRect rrect; + SkRect rect; + // we can only handle rects, circles, and rrects with circular corners + bool isRRect = path.isRRect(&rrect) && SkRRectPriv::IsSimpleCircular(rrect) && + rrect.radii(SkRRect::kUpperLeft_Corner).fX > SK_ScalarNearlyZero; + if (!isRRect && + path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height()) && + rect.width() > SK_ScalarNearlyZero) { + rrect.setOval(rect); + isRRect = true; + } + if (!isRRect && path.isRect(&rect)) { + rrect.setRect(rect); + isRRect = true; + } + + if (!isRRect) { + return false; + } + + if (rrect.isEmpty()) { + return true; + } + + AutoCheckFlush acf(this->drawingManager()); + + // transform light + SkPoint3 devLightPos = map(viewMatrix, rec.fLightPos); + + // 1/scale + SkScalar devToSrcScale = viewMatrix.isScaleTranslate() ? + SkScalarInvert(SkScalarAbs(viewMatrix[SkMatrix::kMScaleX])) : + sk_float_rsqrt(viewMatrix[SkMatrix::kMScaleX] * viewMatrix[SkMatrix::kMScaleX] + + viewMatrix[SkMatrix::kMSkewX] * viewMatrix[SkMatrix::kMSkewX]); + + SkScalar occluderHeight = rec.fZPlaneParams.fZ; + bool transparent = SkToBool(rec.fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag); + + if (SkColorGetA(rec.fAmbientColor) > 0) { + SkScalar devSpaceInsetWidth = SkDrawShadowMetrics::AmbientBlurRadius(occluderHeight); + const SkScalar umbraRecipAlpha = SkDrawShadowMetrics::AmbientRecipAlpha(occluderHeight); + const SkScalar devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha; + + // Outset the shadow rrect to the border of the penumbra + SkScalar ambientPathOutset = devSpaceInsetWidth * devToSrcScale; + SkRRect ambientRRect; + SkRect outsetRect = rrect.rect().makeOutset(ambientPathOutset, ambientPathOutset); + // If the rrect was an oval then its outset will also be one. + // We set it explicitly to avoid errors. + if (rrect.isOval()) { + ambientRRect = SkRRect::MakeOval(outsetRect); + } else { + SkScalar outsetRad = SkRRectPriv::GetSimpleRadii(rrect).fX + ambientPathOutset; + ambientRRect = SkRRect::MakeRectXY(outsetRect, outsetRad, outsetRad); + } + + GrColor ambientColor = SkColorToPremulGrColor(rec.fAmbientColor); + if (transparent) { + // set a large inset to force a fill + devSpaceInsetWidth = ambientRRect.width(); + } + + std::unique_ptr op = GrShadowRRectOp::Make(fContext, + ambientColor, + viewMatrix, + ambientRRect, + devSpaceAmbientBlur, + devSpaceInsetWidth); + if (op) { + this->addDrawOp(clip, std::move(op)); + } + } + + if (SkColorGetA(rec.fSpotColor) > 0) { + SkScalar devSpaceSpotBlur; + SkScalar spotScale; + SkVector spotOffset; + SkDrawShadowMetrics::GetSpotParams(occluderHeight, devLightPos.fX, devLightPos.fY, + devLightPos.fZ, rec.fLightRadius, + &devSpaceSpotBlur, &spotScale, &spotOffset); + // handle scale of radius due to CTM + const SkScalar srcSpaceSpotBlur = devSpaceSpotBlur * devToSrcScale; + + // Adjust translate for the effect of the scale. + spotOffset.fX += spotScale*viewMatrix[SkMatrix::kMTransX]; + spotOffset.fY += spotScale*viewMatrix[SkMatrix::kMTransY]; + // This offset is in dev space, need to transform it into source space. + SkMatrix ctmInverse; + if (viewMatrix.invert(&ctmInverse)) { + ctmInverse.mapPoints(&spotOffset, 1); + } else { + // Since the matrix is a similarity, this should never happen, but just in case... + SkDebugf("Matrix is degenerate. Will not render spot shadow correctly!\n"); + SkASSERT(false); + } + + // Compute the transformed shadow rrect + SkRRect spotShadowRRect; + SkMatrix shadowTransform; + shadowTransform.setScaleTranslate(spotScale, spotScale, spotOffset.fX, spotOffset.fY); + rrect.transform(shadowTransform, &spotShadowRRect); + SkScalar spotRadius = SkRRectPriv::GetSimpleRadii(spotShadowRRect).fX; + + // Compute the insetWidth + SkScalar blurOutset = srcSpaceSpotBlur; + SkScalar insetWidth = blurOutset; + if (transparent) { + // If transparent, just do a fill + insetWidth += spotShadowRRect.width(); + } else { + // For shadows, instead of using a stroke we specify an inset from the penumbra + // border. We want to extend this inset area so that it meets up with the caster + // geometry. The inset geometry will by default already be inset by the blur width. + // + // We compare the min and max corners inset by the radius between the original + // rrect and the shadow rrect. The distance between the two plus the difference + // between the scaled radius and the original radius gives the distance from the + // transformed shadow shape to the original shape in that corner. The max + // of these gives the maximum distance we need to cover. + // + // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to + // that to get the full insetWidth. + SkScalar maxOffset; + if (rrect.isRect()) { + // Manhattan distance works better for rects + maxOffset = SkTMax(SkTMax(SkTAbs(spotShadowRRect.rect().fLeft - + rrect.rect().fLeft), + SkTAbs(spotShadowRRect.rect().fTop - + rrect.rect().fTop)), + SkTMax(SkTAbs(spotShadowRRect.rect().fRight - + rrect.rect().fRight), + SkTAbs(spotShadowRRect.rect().fBottom - + rrect.rect().fBottom))); + } else { + SkScalar dr = spotRadius - SkRRectPriv::GetSimpleRadii(rrect).fX; + SkPoint upperLeftOffset = SkPoint::Make(spotShadowRRect.rect().fLeft - + rrect.rect().fLeft + dr, + spotShadowRRect.rect().fTop - + rrect.rect().fTop + dr); + SkPoint lowerRightOffset = SkPoint::Make(spotShadowRRect.rect().fRight - + rrect.rect().fRight - dr, + spotShadowRRect.rect().fBottom - + rrect.rect().fBottom - dr); + maxOffset = SkScalarSqrt(SkTMax(SkPointPriv::LengthSqd(upperLeftOffset), + SkPointPriv::LengthSqd(lowerRightOffset))) + dr; + } + insetWidth += SkTMax(blurOutset, maxOffset); + } + + // Outset the shadow rrect to the border of the penumbra + SkRect outsetRect = spotShadowRRect.rect().makeOutset(blurOutset, blurOutset); + if (spotShadowRRect.isOval()) { + spotShadowRRect = SkRRect::MakeOval(outsetRect); + } else { + SkScalar outsetRad = spotRadius + blurOutset; + spotShadowRRect = SkRRect::MakeRectXY(outsetRect, outsetRad, outsetRad); + } + + GrColor spotColor = SkColorToPremulGrColor(rec.fSpotColor); + + std::unique_ptr op = GrShadowRRectOp::Make(fContext, + spotColor, + viewMatrix, + spotShadowRRect, + 2.0f * devSpaceSpotBlur, + insetWidth); + if (op) { + this->addDrawOp(clip, std::move(op)); + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool GrRenderTargetContext::drawFilledDRRect(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRRect& origOuter, + const SkRRect& origInner) { + SkASSERT(!origInner.isEmpty()); + SkASSERT(!origOuter.isEmpty()); + + SkTCopyOnFirstWrite inner(origInner), outer(origOuter); + + GrAAType aaType = this->chooseAAType(aa); + + if (GrAAType::kMSAA == aaType) { + return false; + } + + if (GrAAType::kCoverage == aaType && SkRRectPriv::IsCircle(*inner) + && SkRRectPriv::IsCircle(*outer)) { + auto outerR = outer->width() / 2.f; + auto innerR = inner->width() / 2.f; + auto cx = outer->getBounds().fLeft + outerR; + auto cy = outer->getBounds().fTop + outerR; + if (SkScalarNearlyEqual(cx, inner->getBounds().fLeft + innerR) && + SkScalarNearlyEqual(cy, inner->getBounds().fTop + innerR)) { + auto avgR = (innerR + outerR) / 2.f; + auto circleBounds = SkRect::MakeLTRB(cx - avgR, cy - avgR, cx + avgR, cy + avgR); + SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); + stroke.setStrokeStyle(outerR - innerR); + auto op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, + circleBounds, GrStyle(stroke, nullptr), + this->caps()->shaderCaps()); + if (op) { + this->addDrawOp(clip, std::move(op)); + return true; + } + assert_alive(paint); + } + } + + GrClipEdgeType innerEdgeType, outerEdgeType; + if (GrAAType::kCoverage == aaType) { + innerEdgeType = GrClipEdgeType::kInverseFillAA; + outerEdgeType = GrClipEdgeType::kFillAA; + } else { + innerEdgeType = GrClipEdgeType::kInverseFillBW; + outerEdgeType = GrClipEdgeType::kFillBW; + } + + SkMatrix inverseVM; + if (!viewMatrix.isIdentity()) { + if (!origInner.transform(viewMatrix, inner.writable())) { + return false; + } + if (!origOuter.transform(viewMatrix, outer.writable())) { + return false; + } + if (!viewMatrix.invert(&inverseVM)) { + return false; + } + } else { + inverseVM.reset(); + } + + const auto& caps = *this->caps()->shaderCaps(); + // TODO these need to be a geometry processors + auto innerEffect = GrRRectEffect::Make(innerEdgeType, *inner, caps); + if (!innerEffect) { + return false; + } + + auto outerEffect = GrRRectEffect::Make(outerEdgeType, *outer, caps); + if (!outerEffect) { + return false; + } + + paint.addCoverageFragmentProcessor(std::move(innerEffect)); + paint.addCoverageFragmentProcessor(std::move(outerEffect)); + + SkRect bounds = outer->getBounds(); + if (GrAAType::kCoverage == aaType) { + bounds.outset(SK_ScalarHalf, SK_ScalarHalf); + } + + this->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), bounds, + inverseVM); + return true; +} + +void GrRenderTargetContext::drawDRRect(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRRect& outer, + const SkRRect& inner) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawDRRect", fContext); + + SkASSERT(!outer.isEmpty()); + SkASSERT(!inner.isEmpty()); + + AutoCheckFlush acf(this->drawingManager()); + + if (this->drawFilledDRRect(clip, std::move(paint), aa, viewMatrix, outer, inner)) { + return; + } + assert_alive(paint); + + SkPath path; + path.setIsVolatile(true); + path.addRRect(inner); + path.addRRect(outer); + path.setFillType(SkPath::kEvenOdd_FillType); + this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrShape(path)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrRenderTargetContext::drawRegion(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRegion& region, + const GrStyle& style, + const GrUserStencilSettings* ss) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawRegion", fContext); + + if (GrAA::kYes == aa) { + // GrRegionOp performs no antialiasing but is much faster, so here we check the matrix + // to see whether aa is really required. + if (!SkToBool(viewMatrix.getType() & ~(SkMatrix::kTranslate_Mask)) && + SkScalarIsInt(viewMatrix.getTranslateX()) && + SkScalarIsInt(viewMatrix.getTranslateY())) { + aa = GrAA::kNo; + } + } + bool complexStyle = !style.isSimpleFill(); + if (complexStyle || GrAA::kYes == aa) { + SkPath path; + region.getBoundaryPath(&path); + path.setIsVolatile(true); + + return this->drawPath(clip, std::move(paint), aa, viewMatrix, path, style); + } + + GrAAType aaType = this->chooseAAType(GrAA::kNo); + std::unique_ptr op = GrRegionOp::Make(fContext, std::move(paint), viewMatrix, region, + aaType, ss); + this->addDrawOp(clip, std::move(op)); +} + +void GrRenderTargetContext::drawOval(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRect& oval, + const GrStyle& style) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawOval", fContext); + + const SkStrokeRec& stroke = style.strokeRec(); + + if (oval.isEmpty() && !style.pathEffect()) { + if (stroke.getStyle() == SkStrokeRec::kFill_Style) { + return; + } + + this->drawRect(clip, std::move(paint), aa, viewMatrix, oval, &style); + return; + } + + AutoCheckFlush acf(this->drawingManager()); + + GrAAType aaType = this->chooseAAType(aa); + + std::unique_ptr op; + if (GrAAType::kCoverage == aaType && oval.width() > SK_ScalarNearlyZero && + oval.width() == oval.height() && viewMatrix.isSimilarity()) { + // We don't draw true circles as round rects in coverage mode, because it can + // cause perf regressions on some platforms as compared to the dedicated circle Op. + assert_alive(paint); + op = GrOvalOpFactory::MakeCircleOp(fContext, std::move(paint), viewMatrix, oval, style, + this->caps()->shaderCaps()); + } + if (!op && style.isSimpleFill()) { + // GrFillRRectOp has special geometry and a fragment-shader branch to conditionally evaluate + // the arc equation. This same special geometry and fragment branch also turn out to be a + // substantial optimization for drawing ovals (namely, by not evaluating the arc equation + // inside the oval's inner diamond). Given these optimizations, it's a clear win to draw + // ovals the exact same way we do round rects. + assert_alive(paint); + op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval), + *this->caps(), std::move(paint)); + } + if (!op && GrAAType::kCoverage == aaType) { + assert_alive(paint); + op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, oval, style, + this->caps()->shaderCaps()); + } + if (op) { + this->addDrawOp(clip, std::move(op)); + return; + } + + assert_alive(paint); + this->drawShapeUsingPathRenderer( + clip, std::move(paint), aa, viewMatrix, + GrShape(SkRRect::MakeOval(oval), SkPath::kCW_Direction, 2, false, style)); +} + +void GrRenderTargetContext::drawArc(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRect& oval, + SkScalar startAngle, + SkScalar sweepAngle, + bool useCenter, + const GrStyle& style) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawArc", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + GrAAType aaType = this->chooseAAType(aa); + if (GrAAType::kCoverage == aaType) { + const GrShaderCaps* shaderCaps = this->caps()->shaderCaps(); + std::unique_ptr op = GrOvalOpFactory::MakeArcOp(fContext, + std::move(paint), + viewMatrix, + oval, + startAngle, + sweepAngle, + useCenter, + style, + shaderCaps); + if (op) { + this->addDrawOp(clip, std::move(op)); + return; + } + assert_alive(paint); + } + this->drawShapeUsingPathRenderer( + clip, std::move(paint), aa, viewMatrix, + GrShape::MakeArc(oval, startAngle, sweepAngle, useCenter, style)); +} + +void GrRenderTargetContext::drawImageLattice(const GrClip& clip, + GrPaint&& paint, + const SkMatrix& viewMatrix, + sk_sp image, + GrColorType srcColorType, + sk_sp csxf, + GrSamplerState::Filter filter, + std::unique_ptr iter, + const SkRect& dst) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawImageLattice", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + std::unique_ptr op = + GrLatticeOp::MakeNonAA(fContext, std::move(paint), viewMatrix, std::move(image), + srcColorType, std::move(csxf), filter, std::move(iter), dst); + this->addDrawOp(clip, std::move(op)); +} + +void GrRenderTargetContext::drawDrawable(std::unique_ptr drawable, + const SkRect& bounds) { + std::unique_ptr op(GrDrawableOp::Make(fContext, std::move(drawable), bounds)); + SkASSERT(op); + this->addOp(std::move(op)); +} + +void GrRenderTargetContext::asyncRescaleAndReadPixels( + const SkImageInfo& info, const SkIRect& srcRect, SkSurface::RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, ReadPixelsCallback callback, ReadPixelsContext context) { + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + callback(context, nullptr); + return; + } + if (fRenderTargetProxy->wrapsVkSecondaryCB()) { + callback(context, nullptr); + return; + } + auto dstCT = SkColorTypeToGrColorType(info.colorType()); + // TODO: Support reading to gray. + if (dstCT == GrColorType::kUnknown || + GrColorTypeComponentFlags(dstCT) & kGray_SkColorTypeComponentFlag) { + callback(context, nullptr); + return; + } + bool needsRescale = srcRect.width() != info.width() || srcRect.height() != info.height(); + auto colorTypeOfFinalContext = this->colorInfo().colorType(); + auto backendFormatOfFinalContext = fRenderTargetProxy->backendFormat(); + if (needsRescale) { + colorTypeOfFinalContext = dstCT; + backendFormatOfFinalContext = this->caps()->getDefaultBackendFormat(dstCT, + GrRenderable::kYes); + } + auto readInfo = this->caps()->supportedReadPixelsColorType(colorTypeOfFinalContext, + backendFormatOfFinalContext, dstCT); + // Fail if we can't read from the source surface's color type. + if (readInfo.fColorType == GrColorType::kUnknown) { + callback(context, nullptr); + return; + } + // Fail if read color type does not have all of dstCT's color channels and those missing color + // channels are in the src. + uint32_t dstComponents = GrColorTypeComponentFlags(dstCT); + uint32_t legalReadComponents = GrColorTypeComponentFlags(readInfo.fColorType); + uint32_t srcComponents = GrColorTypeComponentFlags(this->colorInfo().colorType()); + if ((~legalReadComponents & dstComponents) & srcComponents) { + callback(context, nullptr); + return; + } + + std::unique_ptr tempRTC; + int x = srcRect.fLeft; + int y = srcRect.fTop; + if (needsRescale) { + tempRTC = this->rescale(info, srcRect, rescaleGamma, rescaleQuality); + if (!tempRTC) { + callback(context, nullptr); + return; + } + SkASSERT(SkColorSpace::Equals(tempRTC->colorInfo().colorSpace(), info.colorSpace())); + SkASSERT(tempRTC->origin() == kTopLeft_GrSurfaceOrigin); + x = y = 0; + } else { + sk_sp xform = GrColorSpaceXform::Make(this->colorInfo().colorSpace(), + this->colorInfo().alphaType(), + info.colorSpace(), + info.alphaType()); + // Insert a draw to a temporary surface if we need to do a y-flip or color space conversion. + if (this->origin() == kBottomLeft_GrSurfaceOrigin || xform) { + // We flip or color convert by drawing and we don't currently support drawing to + // kPremul. + if (info.alphaType() == kUnpremul_SkAlphaType) { + callback(context, nullptr); + return; + } + sk_sp texProxy = sk_ref_sp(fRenderTargetProxy->asTextureProxy()); + SkRect srcRectToDraw = SkRect::Make(srcRect); + // If the src is not texturable first try to make a copy to a texture. + if (!texProxy) { + texProxy = GrSurfaceProxy::Copy(fContext, fRenderTargetProxy.get(), + this->colorInfo().colorType(), + GrMipMapped::kNo, srcRect, SkBackingFit::kApprox, + SkBudgeted::kNo); + if (!texProxy) { + callback(context, nullptr); + return; + } + srcRectToDraw = SkRect::MakeWH(srcRect.width(), srcRect.height()); + } + tempRTC = direct->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, srcRect.width(), srcRect.height(), + this->colorInfo().colorType(), info.refColorSpace(), 1, GrMipMapped::kNo, + kTopLeft_GrSurfaceOrigin); + if (!tempRTC) { + callback(context, nullptr); + return; + } + tempRTC->drawTexture(GrNoClip(), std::move(texProxy), this->colorInfo().colorType(), + GrSamplerState::Filter::kNearest, SkBlendMode::kSrc, + SK_PMColor4fWHITE, srcRectToDraw, + SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo, + GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, + SkMatrix::I(), std::move(xform)); + x = y = 0; + } + } + auto rtc = tempRTC ? tempRTC.get() : this; + return rtc->asyncReadPixels(SkIRect::MakeXYWH(x, y, info.width(), info.height()), + info.colorType(), callback, context); +} + +class GrRenderTargetContext::AsyncReadResult : public SkSurface::AsyncReadResult { +public: + AsyncReadResult(uint32_t inboxID) : fInboxID(inboxID) {} + ~AsyncReadResult() override { + for (int i = 0; i < fPlanes.count(); ++i) { + if (!fPlanes[i].fMappedBuffer) { + delete[] static_cast(fPlanes[i].fData); + } else { + GrClientMappedBufferManager::BufferFinishedMessageBus::Post( + {std::move(fPlanes[i].fMappedBuffer), fInboxID}); + } + } + } + + int count() const override { return fPlanes.count(); } + const void* data(int i) const override { return fPlanes[i].fData; } + size_t rowBytes(int i) const override { return fPlanes[i].fRowBytes; } + + bool addTransferResult(const PixelTransferResult& result, + SkISize size, + size_t rowBytes, + GrClientMappedBufferManager* manager) { + SkASSERT(!result.fTransferBuffer->isMapped()); + const void* mappedData = result.fTransferBuffer->map(); + if (!mappedData) { + return false; + } + if (result.fPixelConverter) { + std::unique_ptr convertedData(new char[rowBytes * size.height()]); + result.fPixelConverter(convertedData.get(), mappedData); + this->addCpuPlane(std::move(convertedData), rowBytes); + result.fTransferBuffer->unmap(); + } else { + manager->insert(result.fTransferBuffer); + this->addMappedPlane(mappedData, rowBytes, std::move(result.fTransferBuffer)); + } + return true; + } + + void addCpuPlane(std::unique_ptr data, size_t rowBytes) { + SkASSERT(data); + SkASSERT(rowBytes > 0); + fPlanes.emplace_back(data.release(), rowBytes, nullptr); + } + +private: + void addMappedPlane(const void* data, size_t rowBytes, sk_sp mappedBuffer) { + SkASSERT(data); + SkASSERT(rowBytes > 0); + SkASSERT(mappedBuffer); + SkASSERT(mappedBuffer->isMapped()); + fPlanes.emplace_back(data, rowBytes, std::move(mappedBuffer)); + } + + struct Plane { + Plane(const void* data, size_t rowBytes, sk_sp buffer) + : fData(data), fRowBytes(rowBytes), fMappedBuffer(std::move(buffer)) {} + const void* fData; + size_t fRowBytes; + // If this is null then fData is heap alloc and must be delete[]ed as const char[]. + sk_sp fMappedBuffer; + }; + SkSTArray<3, Plane> fPlanes; + uint32_t fInboxID; +}; + +void GrRenderTargetContext::asyncReadPixels(const SkIRect& rect, SkColorType colorType, + ReadPixelsCallback callback, + ReadPixelsContext context) { + SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width()); + SkASSERT(rect.fTop >= 0 && rect.fBottom <= this->height()); + + auto directContext = fContext->priv().asDirectContext(); + SkASSERT(directContext); + auto mappedBufferManager = directContext->priv().clientMappedBufferManager(); + + auto transferResult = this->transferPixels(SkColorTypeToGrColorType(colorType), rect); + + if (!transferResult.fTransferBuffer) { + auto ii = SkImageInfo::Make(rect.size(), colorType, + this->colorInfo().alphaType(), + this->colorInfo().refColorSpace()); + auto result = skstd::make_unique(0); + std::unique_ptr data(new char[ii.computeMinByteSize()]); + SkPixmap pm(ii, data.get(), ii.minRowBytes()); + result->addCpuPlane(std::move(data), pm.rowBytes()); + + if (!this->readPixels(ii, pm.writable_addr(), pm.rowBytes(), {rect.fLeft, rect.fTop})) { + callback(context, nullptr); + } + callback(context, std::move(result)); + return; + } + + struct FinishContext { + ReadPixelsCallback* fClientCallback; + ReadPixelsContext fClientContext; + SkISize fSize; + SkColorType fColorType; + GrClientMappedBufferManager* fMappedBufferManager; + PixelTransferResult fTransferResult; + }; + // Assumption is that the caller would like to flush. We could take a parameter or require an + // explicit flush from the caller. We'd have to have a way to defer attaching the finish + // callback to GrGpu until after the next flush that flushes our op list, though. + auto* finishContext = new FinishContext{callback, + context, + rect.size(), + colorType, + mappedBufferManager, + std::move(transferResult)}; + auto finishCallback = [](GrGpuFinishedContext c) { + const auto* context = reinterpret_cast(c); + auto result = skstd::make_unique(context->fMappedBufferManager->inboxID()); + size_t rowBytes = context->fSize.width() * SkColorTypeBytesPerPixel(context->fColorType); + if (!result->addTransferResult(context->fTransferResult, context->fSize, rowBytes, + context->fMappedBufferManager)) { + result.reset(); + } + (*context->fClientCallback)(context->fClientContext, std::move(result)); + delete context; + }; + GrFlushInfo flushInfo; + flushInfo.fFinishedContext = finishContext; + flushInfo.fFinishedProc = finishCallback; + this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo); +} + +void GrRenderTargetContext::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) { + SkASSERT(srcRect.fLeft >= 0 && srcRect.fRight <= this->width()); + SkASSERT(srcRect.fTop >= 0 && srcRect.fBottom <= this->height()); + SkASSERT(!dstSize.isZero()); + SkASSERT((dstSize.width() % 2 == 0) && (dstSize.height() % 2 == 0)); + + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + callback(context, nullptr); + return; + } + if (fRenderTargetProxy->wrapsVkSecondaryCB()) { + callback(context, nullptr); + return; + } + int x = srcRect.fLeft; + int y = srcRect.fTop; + std::unique_ptr tempRTC; + bool needsRescale = srcRect.size() != dstSize; + if (needsRescale) { + // We assume the caller wants kPremul. There is no way to indicate a preference. + auto info = SkImageInfo::Make(dstSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType, + dstColorSpace); + // TODO: Incorporate the YUV conversion into last pass of rescaling. + tempRTC = this->rescale(info, srcRect, rescaleGamma, rescaleQuality); + if (!tempRTC) { + callback(context, nullptr); + return; + } + SkASSERT(SkColorSpace::Equals(tempRTC->colorInfo().colorSpace(), info.colorSpace())); + SkASSERT(tempRTC->origin() == kTopLeft_GrSurfaceOrigin); + x = y = 0; + } else { + // We assume the caller wants kPremul. There is no way to indicate a preference. + sk_sp xform = GrColorSpaceXform::Make( + this->colorInfo().colorSpace(), this->colorInfo().alphaType(), dstColorSpace.get(), + kPremul_SkAlphaType); + if (xform) { + sk_sp texProxy = this->asTextureProxyRef(); + // TODO: Do something if the input is not a texture already. + if (!texProxy) { + callback(context, nullptr); + return; + } + SkRect srcRectToDraw = SkRect::Make(srcRect); + tempRTC = direct->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, dstSize.width(), dstSize.height(), + this->colorInfo().colorType(), dstColorSpace, 1, GrMipMapped::kNo, + kTopLeft_GrSurfaceOrigin); + if (!tempRTC) { + callback(context, nullptr); + return; + } + tempRTC->drawTexture(GrNoClip(), std::move(texProxy), this->colorInfo().colorType(), + GrSamplerState::Filter::kNearest, SkBlendMode::kSrc, + SK_PMColor4fWHITE, srcRectToDraw, + SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo, + GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, + SkMatrix::I(), std::move(xform)); + x = y = 0; + } + } + auto srcProxy = tempRTC ? tempRTC->asTextureProxyRef() : this->asTextureProxyRef(); + // TODO: Do something if the input is not a texture already. + if (!srcProxy) { + callback(context, nullptr); + return; + } + GrColorType srcColorType = tempRTC ? tempRTC->colorInfo().colorType() + : this->colorInfo().colorType(); + + auto yRTC = direct->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kApprox, dstSize.width(), dstSize.height(), GrColorType::kAlpha_8, + dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); + int halfW = dstSize.width()/2; + int halfH = dstSize.height()/2; + auto uRTC = direct->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kApprox, halfW, halfH, GrColorType::kAlpha_8, dstColorSpace, 1, + GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); + auto vRTC = direct->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kApprox, halfW, halfH, GrColorType::kAlpha_8, dstColorSpace, 1, + GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); + if (!yRTC || !uRTC || !vRTC) { + callback(context, nullptr); + return; + } + + static constexpr float kRec601M[] { + 65.481f / 255, 128.553f / 255, 24.966f / 255, 16.f / 255, // y + -37.797f / 255, -74.203f / 255, 112.0f / 255, 128.f / 255, // u + 112.f / 255, -93.786f / 255, -18.214f / 255, 128.f / 255, // v + }; + static constexpr float kRec709M[] { + 45.5594f / 255, 156.6288f / 255, 15.8118f / 255, 16.f / 255, // y + -25.6642f / 255, -86.3358f / 255, 112.f / 255, 128.f / 255, // u + 112.f / 255, -101.7303f / 255, -10.2697f / 255, 128.f / 255, // v + }; + static constexpr float kJpegM[] { + 0.299f , 0.587f , 0.114f , 0.f / 255, // y + -0.168736f, -0.331264f, 0.5f , 128.f / 255, // u + 0.5f , -0.418688f, -0.081312f, 128.f / 255, // v + }; + static constexpr float kIM[] { + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + }; + const float* baseM = kIM; + switch (yuvColorSpace) { + case kRec601_SkYUVColorSpace: + baseM = kRec601M; + break; + case kRec709_SkYUVColorSpace: + baseM = kRec709M; + break; + case kJPEG_SkYUVColorSpace: + baseM = kJpegM; + break; + case kIdentity_SkYUVColorSpace: + baseM = kIM; + break; + } + // TODO: Use one transfer buffer for all three planes to reduce map/unmap cost? + + auto texMatrix = SkMatrix::MakeTrans(x, y); + + SkRect dstRectY = SkRect::Make(dstSize); + SkRect dstRectUV = SkRect::MakeWH(halfW, halfH); + + // This matrix generates (r,g,b,a) = (0, 0, 0, y) + float yM[20]; + std::fill_n(yM, 15, 0.f); + yM[15] = baseM[0]; yM[16] = baseM[1]; yM[17] = baseM[2]; yM[18] = 0; yM[19] = baseM[3]; + GrPaint yPaint; + yPaint.addColorTextureProcessor(srcProxy, srcColorType, texMatrix); + auto yFP = GrColorMatrixFragmentProcessor::Make(yM, false, true, false); + yPaint.addColorFragmentProcessor(std::move(yFP)); + yPaint.setPorterDuffXPFactory(SkBlendMode::kSrc); + yRTC->fillRectToRect(GrNoClip(), std::move(yPaint), GrAA::kNo, SkMatrix::I(), + dstRectY, dstRectY); + auto yTransfer = yRTC->transferPixels(GrColorType::kAlpha_8, + SkIRect::MakeWH(yRTC->width(), yRTC->height())); + if (!yTransfer.fTransferBuffer) { + callback(context, nullptr); + return; + } + + texMatrix.preScale(2.f, 2.f); + // This matrix generates (r,g,b,a) = (0, 0, 0, u) + float uM[20]; + std::fill_n(uM, 15, 0.f); + uM[15] = baseM[4]; uM[16] = baseM[5]; uM[17] = baseM[6]; uM[18] = 0; uM[19] = baseM[7]; + GrPaint uPaint; + uPaint.addColorTextureProcessor(srcProxy, srcColorType, texMatrix, + GrSamplerState::ClampBilerp()); + auto uFP = GrColorMatrixFragmentProcessor::Make(uM, false, true, false); + uPaint.addColorFragmentProcessor(std::move(uFP)); + uPaint.setPorterDuffXPFactory(SkBlendMode::kSrc); + uRTC->fillRectToRect(GrNoClip(), std::move(uPaint), GrAA::kNo, SkMatrix::I(), + dstRectUV, dstRectUV); + auto uTransfer = uRTC->transferPixels(GrColorType::kAlpha_8, + SkIRect::MakeWH(uRTC->width(), uRTC->height())); + if (!uTransfer.fTransferBuffer) { + callback(context, nullptr); + return; + } + + // This matrix generates (r,g,b,a) = (0, 0, 0, v) + float vM[20]; + std::fill_n(vM, 15, 0.f); + vM[15] = baseM[8]; vM[16] = baseM[9]; vM[17] = baseM[10]; vM[18] = 0; vM[19] = baseM[11]; + GrPaint vPaint; + vPaint.addColorTextureProcessor(srcProxy, srcColorType, texMatrix, + GrSamplerState::ClampBilerp()); + auto vFP = GrColorMatrixFragmentProcessor::Make(vM, false, true, false); + vPaint.addColorFragmentProcessor(std::move(vFP)); + vPaint.setPorterDuffXPFactory(SkBlendMode::kSrc); + vRTC->fillRectToRect(GrNoClip(), std::move(vPaint), GrAA::kNo, SkMatrix::I(), + dstRectUV, dstRectUV); + auto vTransfer = vRTC->transferPixels(GrColorType::kAlpha_8, + SkIRect::MakeWH(vRTC->width(), vRTC->height())); + if (!vTransfer.fTransferBuffer) { + callback(context, nullptr); + return; + } + + struct FinishContext { + ReadPixelsCallback* fClientCallback; + ReadPixelsContext fClientContext; + GrClientMappedBufferManager* fMappedBufferManager; + SkISize fSize; + PixelTransferResult fYTransfer; + PixelTransferResult fUTransfer; + PixelTransferResult fVTransfer; + }; + // Assumption is that the caller would like to flush. We could take a parameter or require an + // explicit flush from the caller. We'd have to have a way to defer attaching the finish + // callback to GrGpu until after the next flush that flushes our op list, though. + auto* finishContext = new FinishContext{callback, + context, + direct->priv().clientMappedBufferManager(), + dstSize, + std::move(yTransfer), + std::move(uTransfer), + std::move(vTransfer)}; + auto finishCallback = [](GrGpuFinishedContext c) { + const auto* context = reinterpret_cast(c); + auto result = skstd::make_unique(context->fMappedBufferManager->inboxID()); + auto manager = context->fMappedBufferManager; + size_t rowBytes = SkToSizeT(context->fSize.width()); + if (!result->addTransferResult(context->fYTransfer, context->fSize, rowBytes, manager)) { + (*context->fClientCallback)(context->fClientContext, nullptr); + delete context; + return; + } + rowBytes /= 2; + SkISize uvSize = {context->fSize.width()/2, context->fSize.height()/2}; + if (!result->addTransferResult(context->fUTransfer, uvSize, rowBytes, manager)) { + (*context->fClientCallback)(context->fClientContext, nullptr); + delete context; + return; + } + if (!result->addTransferResult(context->fVTransfer, uvSize, rowBytes, manager)) { + (*context->fClientCallback)(context->fClientContext, nullptr); + delete context; + return; + } + (*context->fClientCallback)(context->fClientContext, std::move(result)); + delete context; + }; + GrFlushInfo flushInfo; + flushInfo.fFinishedContext = finishContext; + flushInfo.fFinishedProc = finishCallback; + this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo); +} + +GrSemaphoresSubmitted GrRenderTargetContext::flush(SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info) { + ASSERT_SINGLE_OWNER + if (fContext->priv().abandoned()) { + return GrSemaphoresSubmitted::kNo; + } + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "flush", fContext); + + return this->drawingManager()->flushSurface(fRenderTargetProxy.get(), access, info); +} + +bool GrRenderTargetContext::waitOnSemaphores(int numSemaphores, + const GrBackendSemaphore waitSemaphores[]) { + ASSERT_SINGLE_OWNER + RETURN_FALSE_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "waitOnSemaphores", fContext); + + AutoCheckFlush acf(this->drawingManager()); + + if (numSemaphores && !this->caps()->semaphoreSupport()) { + return false; + } + + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + return false; + } + + auto resourceProvider = direct->priv().resourceProvider(); + + std::unique_ptr[]> grSemaphores(new sk_sp[numSemaphores]); + for (int i = 0; i < numSemaphores; ++i) { + grSemaphores[i] = resourceProvider->wrapBackendSemaphore( + waitSemaphores[i], GrResourceProvider::SemaphoreWrapType::kWillWait, + kAdopt_GrWrapOwnership); + } + this->drawingManager()->newWaitRenderTask(this->asSurfaceProxyRef(), std::move(grSemaphores), + numSemaphores); + return true; +} + +void GrRenderTargetContext::insertEventMarker(const SkString& str) { + std::unique_ptr op(GrDebugMarkerOp::Make(fContext, fRenderTargetProxy.get(), str)); + this->addOp(std::move(op)); +} + +void GrRenderTargetContext::drawPath(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkPath& path, + const GrStyle& style) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawPath", fContext); + + GrShape shape(path, style); + + this->drawShape(clip, std::move(paint), aa, viewMatrix, shape); +} + +void GrRenderTargetContext::drawShape(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const GrShape& shape) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawShape", fContext); + + if (shape.isEmpty()) { + if (shape.inverseFilled()) { + this->drawPaint(clip, std::move(paint), viewMatrix); + } + return; + } + + AutoCheckFlush acf(this->drawingManager()); + + if (!shape.style().hasPathEffect()) { + GrAAType aaType = this->chooseAAType(aa); + SkRRect rrect; + // We can ignore the starting point and direction since there is no path effect. + bool inverted; + if (shape.asRRect(&rrect, nullptr, nullptr, &inverted) && !inverted) { + if (rrect.isRect()) { + this->drawRect(clip, std::move(paint), aa, viewMatrix, rrect.rect(), + &shape.style()); + return; + } else if (rrect.isOval()) { + this->drawOval(clip, std::move(paint), aa, viewMatrix, rrect.rect(), shape.style()); + return; + } + this->drawRRect(clip, std::move(paint), aa, viewMatrix, rrect, shape.style()); + return; + } else if (GrAAType::kCoverage == aaType && shape.style().isSimpleFill() && + viewMatrix.rectStaysRect()) { + // TODO: the rectStaysRect restriction could be lifted if we were willing to apply + // the matrix to all the points individually rather than just to the rect + SkRect rects[2]; + if (shape.asNestedRects(rects)) { + // Concave AA paths are expensive - try to avoid them for special cases + std::unique_ptr op = GrStrokeRectOp::MakeNested( + fContext, std::move(paint), viewMatrix, rects); + if (op) { + this->addDrawOp(clip, std::move(op)); + } + // Returning here indicates that there is nothing to draw in this case. + return; + } + } + } + + this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, shape); +} + +bool GrRenderTargetContextPriv::drawAndStencilPath(const GrHardClip& clip, + const GrUserStencilSettings* ss, + SkRegion::Op op, + bool invert, + GrAA aa, + const SkMatrix& viewMatrix, + const SkPath& path) { + ASSERT_SINGLE_OWNER_PRIV + RETURN_FALSE_IF_ABANDONED_PRIV + SkDEBUGCODE(fRenderTargetContext->validate();) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "drawAndStencilPath", + fRenderTargetContext->fContext); + + if (path.isEmpty() && path.isInverseFillType()) { + GrPaint paint; + paint.setCoverageSetOpXPFactory(op, invert); + this->stencilRect(clip, ss, std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::MakeIWH(fRenderTargetContext->width(), + fRenderTargetContext->height())); + return true; + } + + AutoCheckFlush acf(fRenderTargetContext->drawingManager()); + + // An Assumption here is that path renderer would use some form of tweaking + // the src color (either the input alpha or in the frag shader) to implement + // aa. If we have some future driver-mojo path AA that can do the right + // thing WRT to the blend then we'll need some query on the PR. + GrAAType aaType = fRenderTargetContext->chooseAAType(aa); + bool hasUserStencilSettings = !ss->isUnused(); + + SkIRect clipConservativeBounds; + clip.getConservativeBounds(fRenderTargetContext->width(), fRenderTargetContext->height(), + &clipConservativeBounds, nullptr); + + GrShape shape(path, GrStyle::SimpleFill()); + GrPathRenderer::CanDrawPathArgs canDrawArgs; + canDrawArgs.fCaps = fRenderTargetContext->caps(); + canDrawArgs.fProxy = fRenderTargetContext->proxy(); + canDrawArgs.fViewMatrix = &viewMatrix; + canDrawArgs.fShape = &shape; + canDrawArgs.fClipConservativeBounds = &clipConservativeBounds; + canDrawArgs.fAAType = aaType; + SkASSERT(!fRenderTargetContext->wrapsVkSecondaryCB()); + canDrawArgs.fTargetIsWrappedVkSecondaryCB = false; + canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings; + + // Don't allow the SW renderer + GrPathRenderer* pr = fRenderTargetContext->drawingManager()->getPathRenderer( + canDrawArgs, false, GrPathRendererChain::DrawType::kStencilAndColor); + if (!pr) { + return false; + } + + GrPaint paint; + paint.setCoverageSetOpXPFactory(op, invert); + + GrPathRenderer::DrawPathArgs args{fRenderTargetContext->drawingManager()->getContext(), + std::move(paint), + ss, + fRenderTargetContext, + &clip, + &clipConservativeBounds, + &viewMatrix, + &shape, + aaType, + fRenderTargetContext->colorInfo().isLinearlyBlended()}; + pr->drawPath(args); + return true; +} + +SkBudgeted GrRenderTargetContextPriv::isBudgeted() const { + ASSERT_SINGLE_OWNER_PRIV + + if (fRenderTargetContext->fContext->priv().abandoned()) { + return SkBudgeted::kNo; + } + + SkDEBUGCODE(fRenderTargetContext->validate();) + + return fRenderTargetContext->fRenderTargetProxy->isBudgeted(); +} + +void GrRenderTargetContext::drawShapeUsingPathRenderer(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const GrShape& originalShape) { + ASSERT_SINGLE_OWNER + RETURN_IF_ABANDONED + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "internalDrawPath", fContext); + + if (!viewMatrix.isFinite() || !originalShape.bounds().isFinite()) { + return; + } + + SkIRect clipConservativeBounds; + clip.getConservativeBounds(this->width(), this->height(), &clipConservativeBounds, nullptr); + + GrShape tempShape; + GrAAType aaType = this->chooseAAType(aa); + + GrPathRenderer::CanDrawPathArgs canDrawArgs; + canDrawArgs.fCaps = this->caps(); + canDrawArgs.fProxy = this->proxy(); + canDrawArgs.fViewMatrix = &viewMatrix; + canDrawArgs.fShape = &originalShape; + canDrawArgs.fClipConservativeBounds = &clipConservativeBounds; + canDrawArgs.fTargetIsWrappedVkSecondaryCB = this->wrapsVkSecondaryCB(); + canDrawArgs.fHasUserStencilSettings = false; + + GrPathRenderer* pr; + static constexpr GrPathRendererChain::DrawType kType = GrPathRendererChain::DrawType::kColor; + if (originalShape.isEmpty() && !originalShape.inverseFilled()) { + return; + } + + canDrawArgs.fAAType = aaType; + + // Try a 1st time without applying any of the style to the geometry (and barring sw) + pr = this->drawingManager()->getPathRenderer(canDrawArgs, false, kType); + SkScalar styleScale = GrStyle::MatrixToScaleFactor(viewMatrix); + + if (!pr && originalShape.style().pathEffect()) { + // It didn't work above, so try again with the path effect applied. + tempShape = originalShape.applyStyle(GrStyle::Apply::kPathEffectOnly, styleScale); + if (tempShape.isEmpty()) { + return; + } + canDrawArgs.fShape = &tempShape; + pr = this->drawingManager()->getPathRenderer(canDrawArgs, false, kType); + } + if (!pr) { + if (canDrawArgs.fShape->style().applies()) { + tempShape = canDrawArgs.fShape->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, + styleScale); + if (tempShape.isEmpty()) { + return; + } + canDrawArgs.fShape = &tempShape; + // This time, allow SW renderer + pr = this->drawingManager()->getPathRenderer(canDrawArgs, true, kType); + } else { + pr = this->drawingManager()->getSoftwarePathRenderer(); + } + } + + if (!pr) { +#ifdef SK_DEBUG + SkDebugf("Unable to find path renderer compatible with path.\n"); +#endif + return; + } + + GrPathRenderer::DrawPathArgs args{this->drawingManager()->getContext(), + std::move(paint), + &GrUserStencilSettings::kUnused, + this, + &clip, + &clipConservativeBounds, + &viewMatrix, + canDrawArgs.fShape, + aaType, + this->colorInfo().isLinearlyBlended()}; + pr->drawPath(args); +} + +static void op_bounds(SkRect* bounds, const GrOp* op) { + *bounds = op->bounds(); + if (op->hasZeroArea()) { + if (op->hasAABloat()) { + bounds->outset(0.5f, 0.5f); + } else { + // We don't know which way the particular GPU will snap lines or points at integer + // coords. So we ensure that the bounds is large enough for either snap. + SkRect before = *bounds; + bounds->roundOut(bounds); + if (bounds->fLeft == before.fLeft) { + bounds->fLeft -= 1; + } + if (bounds->fTop == before.fTop) { + bounds->fTop -= 1; + } + if (bounds->fRight == before.fRight) { + bounds->fRight += 1; + } + if (bounds->fBottom == before.fBottom) { + bounds->fBottom += 1; + } + } + } +} + +void GrRenderTargetContext::addOp(std::unique_ptr op) { + this->getOpsTask()->addOp( + std::move(op), GrTextureResolveManager(this->drawingManager()), *this->caps()); +} + +void GrRenderTargetContext::addDrawOp(const GrClip& clip, std::unique_ptr op, + const std::function& willAddFn) { + ASSERT_SINGLE_OWNER + if (fContext->priv().abandoned()) { + fContext->priv().opMemoryPool()->release(std::move(op)); + return; + } + SkDEBUGCODE(this->validate();) + SkDEBUGCODE(op->fAddDrawOpCalled = true;) + GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "addDrawOp", fContext); + + // Setup clip + SkRect bounds; + op_bounds(&bounds, op.get()); + GrAppliedClip appliedClip; + GrDrawOp::FixedFunctionFlags fixedFunctionFlags = op->fixedFunctionFlags(); + bool usesHWAA = fixedFunctionFlags & GrDrawOp::FixedFunctionFlags::kUsesHWAA; + bool usesStencil = fixedFunctionFlags & GrDrawOp::FixedFunctionFlags::kUsesStencil; + + if (usesStencil) { + this->setNeedsStencil(usesHWAA); + } + + if (!clip.apply(fContext, this, usesHWAA, usesStencil, &appliedClip, &bounds)) { + fContext->priv().opMemoryPool()->release(std::move(op)); + return; + } + + SkASSERT((!usesStencil && !appliedClip.hasStencilClip()) || (fNumStencilSamples > 0)); + + GrClampType clampType = GrColorTypeClampType(this->colorInfo().colorType()); + // MIXED SAMPLES TODO: If we start using mixed samples for clips we will need to check the clip + // here as well. + bool hasMixedSampledCoverage = (usesHWAA && this->numSamples() <= 1); +#ifdef SK_DEBUG + if (hasMixedSampledCoverage) { + SkASSERT(usesStencil); + SkASSERT(fRenderTargetProxy->canUseMixedSamples(*this->caps())); + } +#endif + GrProcessorSet::Analysis analysis = op->finalize( + *this->caps(), &appliedClip, hasMixedSampledCoverage, clampType); + + GrXferProcessor::DstProxy dstProxy; + if (analysis.requiresDstTexture()) { + if (!this->setupDstProxy(clip, *op, &dstProxy)) { + fContext->priv().opMemoryPool()->release(std::move(op)); + return; + } + } + + op->setClippedBounds(bounds); + auto opsTask = this->getOpsTask(); + if (willAddFn) { + willAddFn(op.get(), opsTask->uniqueID()); + } + opsTask->addDrawOp(std::move(op), analysis, std::move(appliedClip), dstProxy, + GrTextureResolveManager(this->drawingManager()), *this->caps()); +} + +bool GrRenderTargetContext::setupDstProxy(const GrClip& clip, const GrOp& op, + GrXferProcessor::DstProxy* dstProxy) { + // If we are wrapping a vulkan secondary command buffer, we can't make a dst copy because we + // don't actually have a VkImage to make a copy of. Additionally we don't have the power to + // start and stop the render pass in order to make the copy. + if (fRenderTargetProxy->wrapsVkSecondaryCB()) { + return false; + } + + if (this->caps()->textureBarrierSupport() && !fRenderTargetProxy->requiresManualMSAAResolve()) { + if (GrTextureProxy* texProxy = fRenderTargetProxy->asTextureProxy()) { + // The render target is a texture, so we can read from it directly in the shader. The XP + // will be responsible to detect this situation and request a texture barrier. + dstProxy->setProxy(sk_ref_sp(texProxy)); + dstProxy->setOffset(0, 0); + return true; + } + } + + SkIRect copyRect = SkIRect::MakeWH(fRenderTargetProxy->width(), fRenderTargetProxy->height()); + + SkIRect clippedRect; + clip.getConservativeBounds( + fRenderTargetProxy->width(), fRenderTargetProxy->height(), &clippedRect); + SkRect opBounds = op.bounds(); + // If the op has aa bloating or is a infinitely thin geometry (hairline) outset the bounds by + // 0.5 pixels. + if (op.hasAABloat() || op.hasZeroArea()) { + opBounds.outset(0.5f, 0.5f); + // An antialiased/hairline draw can sometimes bleed outside of the clips bounds. For + // performance we may ignore the clip when the draw is entirely inside the clip is float + // space but will hit pixels just outside the clip when actually rasterizing. + clippedRect.outset(1, 1); + clippedRect.intersect(SkIRect::MakeWH( + fRenderTargetProxy->width(), fRenderTargetProxy->height())); + } + SkIRect opIBounds; + opBounds.roundOut(&opIBounds); + if (!clippedRect.intersect(opIBounds)) { +#ifdef SK_DEBUG + GrCapsDebugf(this->caps(), "setupDstTexture: Missed an early reject bailing on draw."); +#endif + return false; + } + + // MSAA consideration: When there is support for reading MSAA samples in the shader we could + // have per-sample dst values by making the copy multisampled. + GrCaps::DstCopyRestrictions restrictions = this->caps()->getDstCopyRestrictions( + fRenderTargetProxy.get(), this->colorInfo().colorType()); + + if (!restrictions.fMustCopyWholeSrc) { + copyRect = clippedRect; + } + + SkIPoint dstOffset; + SkBackingFit fit; + if (restrictions.fRectsMustMatch == GrSurfaceProxy::RectsMustMatch::kYes) { + dstOffset = {0, 0}; + fit = SkBackingFit::kExact; + } else { + dstOffset = {copyRect.fLeft, copyRect.fTop}; + fit = SkBackingFit::kApprox; + } + sk_sp newProxy = GrSurfaceProxy::Copy( + fContext, fRenderTargetProxy.get(), this->colorInfo().colorType(), GrMipMapped::kNo, + copyRect, fit, SkBudgeted::kYes, restrictions.fRectsMustMatch); + SkASSERT(newProxy); + + dstProxy->setProxy(std::move(newProxy)); + dstProxy->setOffset(dstOffset); + return true; +} + +bool GrRenderTargetContext::blitTexture(GrTextureProxy* src, GrColorType srcColorType, + const SkIRect& srcRect, const SkIPoint& dstPoint) { + SkIRect clippedSrcRect; + SkIPoint clippedDstPoint; + if (!GrClipSrcRectAndDstPoint(this->asSurfaceProxy()->isize(), src->isize(), srcRect, dstPoint, + &clippedSrcRect, &clippedDstPoint)) { + return false; + } + + GrPaint paint; + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + auto fp = GrSimpleTextureEffect::Make(sk_ref_sp(src), srcColorType, SkMatrix::I()); + if (!fp) { + return false; + } + paint.addColorFragmentProcessor(std::move(fp)); + + this->fillRectToRect( + GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::MakeXYWH(clippedDstPoint.fX, clippedDstPoint.fY, clippedSrcRect.width(), + clippedSrcRect.height()), + SkRect::Make(clippedSrcRect)); + return true; +} + diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetContext.h b/gfx/skia/skia/src/gpu/GrRenderTargetContext.h new file mode 100644 index 0000000000..c10bf008a8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetContext.h @@ -0,0 +1,657 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTargetContext_DEFINED +#define GrRenderTargetContext_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkDrawable.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurface.h" +#include "include/core/SkSurfaceProps.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrOpsTask.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/text/GrTextTarget.h" + +class GrBackendSemaphore; +class GrClip; +class GrColorSpaceXform; +class GrCoverageCountingPathRenderer; +class GrDrawingManager; +class GrDrawOp; +class GrFixedClip; +class GrOp; +class GrRenderTarget; +class GrRenderTargetContextPriv; +class GrShape; +class GrStyle; +class GrTextureProxy; +struct GrUserStencilSettings; +struct SkDrawShadowRec; +class SkGlyphRunList; +struct SkIPoint; +struct SkIRect; +class SkLatticeIter; +class SkMatrix; +class SkPaint; +class SkPath; +struct SkPoint; +struct SkRect; +class SkRegion; +class SkRRect; +struct SkRSXform; +class SkTextBlob; +class SkVertices; + +/** + * A helper object to orchestrate commands (draws, etc...) for GrSurfaces that are GrRenderTargets. + */ +class GrRenderTargetContext : public GrSurfaceContext { +public: + ~GrRenderTargetContext() override; + + virtual void drawGlyphRunList(const GrClip&, const SkMatrix& viewMatrix, const SkGlyphRunList&); + + /** + * Provides a perfomance hint that the render target's contents are allowed + * to become undefined. + */ + void discard(); + + enum class CanClearFullscreen : bool { + kNo = false, + kYes = true + }; + + /** + * Clear the entire or rect of the render target, ignoring any clips. + * @param rect the rect to clear or the whole thing if rect is NULL. + * @param color the color to clear to. + * @param CanClearFullscreen allows partial clears to be converted to fullscreen clears on + * tiling platforms where that is an optimization. + */ + void clear(const SkIRect* rect, const SkPMColor4f& color, CanClearFullscreen); + + void clear(const SkPMColor4f& color) { + return this->clear(nullptr, color, CanClearFullscreen::kYes); + } + + /** + * Draw everywhere (respecting the clip) with the paint. + */ + void drawPaint(const GrClip&, GrPaint&&, const SkMatrix& viewMatrix); + + /** + * Draw the rect using a paint. + * @param paint describes how to color pixels. + * @param GrAA Controls whether rect is antialiased + * @param viewMatrix transformation matrix + * @param style The style to apply. Null means fill. Currently path effects are not + * allowed. + * The rects coords are used to access the paint (through texture matrix) + */ + void drawRect(const GrClip&, + GrPaint&& paint, + GrAA, + const SkMatrix& viewMatrix, + const SkRect&, + const GrStyle* style = nullptr); + + /** + * Maps a rectangle of shader coordinates to a rectangle and fills that rectangle. + * + * @param paint describes how to color pixels. + * @param GrAA Controls whether rect is antialiased + * @param viewMatrix transformation matrix which applies to rectToDraw + * @param rectToDraw the rectangle to draw + * @param localRect the rectangle of shader coordinates applied to rectToDraw + */ + void fillRectToRect(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRect& rectToDraw, + const SkRect& localRect) { + this->drawFilledQuad(clip, std::move(paint), aa, + aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone, + GrQuad::MakeFromRect(rectToDraw, viewMatrix), GrQuad(localRect)); + } + + /** + * Fills a rect with a paint and a localMatrix. + */ + void fillRectWithLocalMatrix(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkMatrix& localMatrix) { + this->drawFilledQuad(clip, std::move(paint), aa, + aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone, + GrQuad::MakeFromRect(rect, viewMatrix), + GrQuad::MakeFromRect(rect, localMatrix)); + } + + /** + * Creates an op that draws a fill rect with per-edge control over anti-aliasing. + * + * This is a specialized version of fillQuadWithEdgeAA, but is kept separate since knowing + * the geometry is a rectangle affords more optimizations. + */ + void fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA, + const SkMatrix& viewMatrix, const SkRect& rect, + const SkRect* optionalLocalRect = nullptr) { + const SkRect& localRect = optionalLocalRect ? *optionalLocalRect : rect; + this->drawFilledQuad(clip, std::move(paint), aa, edgeAA, + GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(localRect)); + } + + /** + * Similar to fillRectWithEdgeAA but draws an arbitrary 2D convex quadrilateral transformed + * by 'viewMatrix', with per-edge control over anti-aliasing. The quad should follow the + * ordering used by SkRect::toQuad(), which determines how the edge AA is applied: + * - "top" = points [0] and [1] + * - "right" = points[1] and [2] + * - "bottom" = points[2] and [3] + * - "left" = points[3] and [0] + * + * The last argument, 'optionalLocalQuad', can be null if no separate local coordinates are + * necessary. + */ + void fillQuadWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA, + const SkMatrix& viewMatrix, const SkPoint quad[4], + const SkPoint optionalLocalQuad[4]) { + const SkPoint* localQuad = optionalLocalQuad ? optionalLocalQuad : quad; + this->drawFilledQuad(clip, std::move(paint), aa, edgeAA, + GrQuad::MakeFromSkQuad(quad, viewMatrix), + GrQuad::MakeFromSkQuad(localQuad, SkMatrix::I())); + } + + /** Used with drawQuadSet */ + struct QuadSetEntry { + SkRect fRect; + SkPMColor4f fColor; // Overrides any color on the GrPaint + SkMatrix fLocalMatrix; + GrQuadAAFlags fAAFlags; + }; + + // TODO(michaelludwig) - remove if the bulk API is not useful for SkiaRenderer + void drawQuadSet(const GrClip& clip, GrPaint&& paint, GrAA aa, const SkMatrix& viewMatrix, + const QuadSetEntry[], int cnt); + + /** + * Creates an op that draws a subrectangle of a texture. The passed color is modulated by the + * texture's color. 'srcRect' specifies the rectangle of the texture to draw. 'dstRect' + * specifies the rectangle to draw in local coords which will be transformed by 'viewMatrix' to + * device space. + */ + void drawTexture(const GrClip& clip, sk_sp proxy, GrColorType srcColorType, + GrSamplerState::Filter filter, SkBlendMode mode, const SkPMColor4f& color, + const SkRect& srcRect, const SkRect& dstRect, GrAA aa, GrQuadAAFlags edgeAA, + SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix, + sk_sp texXform) { + const SkRect* domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? + &srcRect : nullptr; + this->drawTexturedQuad(clip, std::move(proxy), srcColorType, std::move(texXform), filter, + color, mode, aa, edgeAA, GrQuad::MakeFromRect(dstRect, viewMatrix), + GrQuad(srcRect), domain); + } + + /** + * Variant of drawTexture that instead draws the texture applied to 'dstQuad' transformed by + * 'viewMatrix', using the 'srcQuad' texture coordinates clamped to the optional 'domain'. If + * 'domain' is null, it's equivalent to using the fast src rect constraint. If 'domain' is + * provided, the strict src rect constraint is applied using 'domain'. + */ + void drawTextureQuad(const GrClip& clip, sk_sp proxy, GrColorType srcColorType, + GrSamplerState::Filter filter, SkBlendMode mode, const SkPMColor4f& color, + const SkPoint srcQuad[4], const SkPoint dstQuad[4], GrAA aa, + GrQuadAAFlags edgeAA, const SkRect* domain, const SkMatrix& viewMatrix, + sk_sp texXform) { + this->drawTexturedQuad(clip, std::move(proxy), srcColorType, std::move(texXform), filter, + color, mode, aa, edgeAA, GrQuad::MakeFromSkQuad(dstQuad, viewMatrix), + GrQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()), domain); + } + + /** Used with drawTextureSet */ + struct TextureSetEntry { + sk_sp fProxy; + GrColorType fSrcColorType; + SkRect fSrcRect; + SkRect fDstRect; + const SkPoint* fDstClipQuad; // Must be null, or point to an array of 4 points + const SkMatrix* fPreViewMatrix; // If not null, entry's CTM is 'viewMatrix' * fPreViewMatrix + float fAlpha; + GrQuadAAFlags fAAFlags; + }; + /** + * Draws a set of textures with a shared filter, color, view matrix, color xform, and + * texture color xform. The textures must all have the same GrTextureType and GrConfig. + * + * If any entries provide a non-null fDstClip array, it will be read from immediately based on + * fDstClipCount, so the pointer can become invalid after this returns. + */ + void drawTextureSet(const GrClip&, const TextureSetEntry[], int cnt, GrSamplerState::Filter, + SkBlendMode mode, GrAA aa, SkCanvas::SrcRectConstraint, + const SkMatrix& viewMatrix, sk_sp texXform); + + /** + * Draw a roundrect using a paint. + * + * @param paint describes how to color pixels. + * @param GrAA Controls whether rrect is antialiased. + * @param viewMatrix transformation matrix + * @param rrect the roundrect to draw + * @param style style to apply to the rrect. Currently path effects are not allowed. + */ + void drawRRect(const GrClip&, + GrPaint&&, + GrAA, + const SkMatrix& viewMatrix, + const SkRRect& rrect, + const GrStyle& style); + + /** + * Use a fast method to render the ambient and spot shadows for a path. + * Will return false if not possible for the given path. + * + * @param viewMatrix transformation matrix + * @param path the path to shadow + * @param rec parameters for shadow rendering + */ + bool drawFastShadow(const GrClip&, + const SkMatrix& viewMatrix, + const SkPath& path, + const SkDrawShadowRec& rec); + + /** + * Shortcut for filling a SkPath consisting of nested rrects using a paint. The result is + * undefined if outer does not contain inner. + * + * @param paint describes how to color pixels. + * @param GrAA Controls whether rrects edges are antialiased + * @param viewMatrix transformation matrix + * @param outer the outer roundrect + * @param inner the inner roundrect + */ + void drawDRRect(const GrClip&, + GrPaint&&, + GrAA, + const SkMatrix& viewMatrix, + const SkRRect& outer, + const SkRRect& inner); + + /** + * Draws a path. + * + * @param paint describes how to color pixels. + * @param GrAA Controls whether the path is antialiased. + * @param viewMatrix transformation matrix + * @param path the path to draw + * @param style style to apply to the path. + */ + void drawPath(const GrClip&, + GrPaint&&, + GrAA, + const SkMatrix& viewMatrix, + const SkPath&, + const GrStyle&); + + /** + * Draws a shape. + * + * @param paint describes how to color pixels. + * @param GrAA Controls whether the path is antialiased. + * @param viewMatrix transformation matrix + * @param shape the shape to draw + */ + void drawShape(const GrClip&, + GrPaint&&, + GrAA, + const SkMatrix& viewMatrix, + const GrShape&); + + + /** + * Draws vertices with a paint. + * + * @param paint describes how to color pixels. + * @param viewMatrix transformation matrix + * @param vertices specifies the mesh to draw. + * @param bones bone deformation matrices. + * @param boneCount number of bone matrices. + * @param overridePrimType primitive type to draw. If NULL, derive prim type from vertices. + */ + void drawVertices(const GrClip&, + GrPaint&& paint, + const SkMatrix& viewMatrix, + sk_sp vertices, + const SkVertices::Bone bones[], + int boneCount, + GrPrimitiveType* overridePrimType = nullptr); + + /** + * Draws textured sprites from an atlas with a paint. This currently does not support AA for the + * sprite rectangle edges. + * + * @param paint describes how to color pixels. + * @param viewMatrix transformation matrix + * @param spriteCount number of sprites. + * @param xform array of compressed transformation data, required. + * @param texRect array of texture rectangles used to access the paint. + * @param colors optional array of per-sprite colors, supercedes + * the paint's color field. + */ + void drawAtlas(const GrClip&, + GrPaint&& paint, + const SkMatrix& viewMatrix, + int spriteCount, + const SkRSXform xform[], + const SkRect texRect[], + const SkColor colors[]); + + /** + * Draws a region. + * + * @param paint describes how to color pixels + * @param viewMatrix transformation matrix + * @param aa should the rects of the region be antialiased. + * @param region the region to be drawn + * @param style style to apply to the region + */ + void drawRegion(const GrClip&, + GrPaint&& paint, + GrAA aa, + const SkMatrix& viewMatrix, + const SkRegion& region, + const GrStyle& style, + const GrUserStencilSettings* ss = nullptr); + + /** + * Draws an oval. + * + * @param paint describes how to color pixels. + * @param GrAA Controls whether the oval is antialiased. + * @param viewMatrix transformation matrix + * @param oval the bounding rect of the oval. + * @param style style to apply to the oval. Currently path effects are not allowed. + */ + void drawOval(const GrClip&, + GrPaint&& paint, + GrAA, + const SkMatrix& viewMatrix, + const SkRect& oval, + const GrStyle& style); + /** + * Draws a partial arc of an oval. + * + * @param paint describes how to color pixels. + * @param GrGrAA Controls whether the arc is antialiased. + * @param viewMatrix transformation matrix. + * @param oval the bounding rect of the oval. + * @param startAngle starting angle in degrees. + * @param sweepAngle angle to sweep in degrees. Must be in (-360, 360) + * @param useCenter true means that the implied path begins at the oval center, connects as + * a line to the point indicated by the start contains the arc indicated by + * the sweep angle. If false the line beginning at the center point is + * omitted. + * @param style style to apply to the oval. + */ + void drawArc(const GrClip&, + GrPaint&& paint, + GrAA, + const SkMatrix& viewMatrix, + const SkRect& oval, + SkScalar startAngle, + SkScalar sweepAngle, + bool useCenter, + const GrStyle& style); + + /** + * Draw the image as a set of rects, specified by |iter|. + */ + void drawImageLattice(const GrClip&, + GrPaint&&, + const SkMatrix& viewMatrix, + sk_sp, + GrColorType srcColorType, + sk_sp, + GrSamplerState::Filter, + std::unique_ptr, + const SkRect& dst); + + /** + * Draws the src texture with no matrix. The dstRect is the dstPoint with the width and height + * of the srcRect. The srcRect and dstRect are clipped to the bounds of the src and dst surfaces + * respectively. + */ + bool blitTexture(GrTextureProxy* src, GrColorType srcColorType, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + /** + * Adds the necessary signal and wait semaphores and adds the passed in SkDrawable to the + * command stream. + */ + void drawDrawable(std::unique_ptr, const SkRect& bounds); + + using ReadPixelsCallback = SkSurface::ReadPixelsCallback; + using ReadPixelsContext = SkSurface::ReadPixelsContext; + using RescaleGamma = SkSurface::RescaleGamma; + + // GPU implementation for SkSurface::asyncRescaleAndReadPixels. + void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect, + RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, ReadPixelsContext context); + // GPU implementation for SkSurface::asyncRescaleAndReadPixelsYUV420. + void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context); + + /** + * After this returns any pending surface IO will be issued to the backend 3D API and + * if the surface has MSAA it will be resolved. + */ + GrSemaphoresSubmitted flush(SkSurface::BackendSurfaceAccess access, const GrFlushInfo&); + + /** + * The next time this GrRenderTargetContext is flushed, the gpu will wait on the passed in + * semaphores before executing any commands. + */ + bool waitOnSemaphores(int numSemaphores, const GrBackendSemaphore waitSemaphores[]); + + void insertEventMarker(const SkString&); + + const GrRenderTargetProxy* proxy() const { return fRenderTargetProxy.get(); } + int width() const { return fRenderTargetProxy->width(); } + int height() const { return fRenderTargetProxy->height(); } + int numSamples() const { return fRenderTargetProxy->numSamples(); } + const SkSurfaceProps& surfaceProps() const { return fSurfaceProps; } + GrSurfaceOrigin origin() const { return fRenderTargetProxy->origin(); } + bool wrapsVkSecondaryCB() const { return fRenderTargetProxy->wrapsVkSecondaryCB(); } + GrMipMapped mipMapped() const; + + // This entry point should only be called if the backing GPU object is known to be + // instantiated. + GrRenderTarget* accessRenderTarget() { return fRenderTargetProxy->peekRenderTarget(); } + + GrSurfaceProxy* asSurfaceProxy() override { return fRenderTargetProxy.get(); } + const GrSurfaceProxy* asSurfaceProxy() const override { return fRenderTargetProxy.get(); } + sk_sp asSurfaceProxyRef() override { return fRenderTargetProxy; } + + GrTextureProxy* asTextureProxy() override; + const GrTextureProxy* asTextureProxy() const override; + sk_sp asTextureProxyRef() override; + + GrRenderTargetProxy* asRenderTargetProxy() override { return fRenderTargetProxy.get(); } + sk_sp asRenderTargetProxyRef() override { return fRenderTargetProxy; } + + GrRenderTargetContext* asRenderTargetContext() override { return this; } + + // Provides access to functions that aren't part of the public API. + GrRenderTargetContextPriv priv(); + const GrRenderTargetContextPriv priv() const; + + GrTextTarget* textTarget() { return fTextTarget.get(); } + +#if GR_TEST_UTILS + bool testingOnly_IsInstantiated() const { return fRenderTargetProxy->isInstantiated(); } + void testingOnly_SetPreserveOpsOnFullClear() { fPreserveOpsOnFullClear_TestingOnly = true; } + GrOpsTask* testingOnly_PeekLastOpsTask() { return fOpsTask.get(); } +#endif + +protected: + GrRenderTargetContext(GrRecordingContext*, sk_sp, GrColorType, + sk_sp, const SkSurfaceProps*, bool managedOpsTask = true); + + SkDEBUGCODE(void validate() const override;) + +private: + class TextTarget; + enum class QuadOptimization; + + GrAAType chooseAAType(GrAA); + + friend class GrAtlasTextBlob; // for access to add[Mesh]DrawOp + friend class GrClipStackClip; // for access to getOpsTask + friend class GrOnFlushResourceProvider; // for access to getOpsTask (http://skbug.com/9357) + + friend class GrDrawingManager; // for ctor + friend class GrRenderTargetContextPriv; + + // All the path renderers currently make their own ops + friend class GrSoftwarePathRenderer; // for access to add[Mesh]DrawOp + friend class GrAAConvexPathRenderer; // for access to add[Mesh]DrawOp + friend class GrDashLinePathRenderer; // for access to add[Mesh]DrawOp + friend class GrAAHairLinePathRenderer; // for access to add[Mesh]DrawOp + friend class GrAALinearizingConvexPathRenderer; // for access to add[Mesh]DrawOp + friend class GrSmallPathRenderer; // for access to add[Mesh]DrawOp + friend class GrDefaultPathRenderer; // for access to add[Mesh]DrawOp + friend class GrStencilAndCoverPathRenderer; // for access to add[Mesh]DrawOp + friend class GrTessellatingPathRenderer; // for access to add[Mesh]DrawOp + friend class GrCCPerFlushResources; // for access to addDrawOp + friend class GrCoverageCountingPathRenderer; // for access to addDrawOp + // for a unit test + friend void test_draw_op(GrContext*, + GrRenderTargetContext*, + std::unique_ptr, + sk_sp, + GrColorType); + + GrOpsTask::CanDiscardPreviousOps canDiscardPreviousOpsOnFullClear() const; + void setNeedsStencil(bool useMixedSamplesIfNotMSAA); + + void internalClear(const GrFixedClip&, const SkPMColor4f&, CanClearFullscreen); + void internalStencilClear(const GrFixedClip&, bool insideStencilMask); + + // Only consumes the GrPaint if successful. + bool drawFilledDRRect(const GrClip& clip, + GrPaint&& paint, + GrAA, + const SkMatrix& viewMatrix, + const SkRRect& origOuter, + const SkRRect& origInner); + + // If the drawn quad's paint is a const blended color, provide it as a non-null pointer to + // 'constColor', which enables the draw-as-clear optimization. Otherwise it is assumed the paint + // requires some form of shading that invalidates using a clear op. + // + // The non-const pointers should be the original draw request on input, and will be updated as + // appropriate depending on the returned optimization level. + // + // 'stencilSettings' are provided merely for decision making purposes; When non-null, + // optimization strategies that submit special ops are avoided. + QuadOptimization attemptQuadOptimization(const GrClip& clip, + const SkPMColor4f* constColor, + const GrUserStencilSettings* stencilSettings, + GrAA* aa, + GrQuadAAFlags* edgeFlags, + GrQuad* deviceQuad, + GrQuad* localQuad); + + // If stencil settings, 'ss', are non-null, AA controls MSAA or no AA. If they are null, then AA + // can choose between coverage, MSAA as per chooseAAType(). This will always attempt to apply + // quad optimizations, so all quad/rect public APIs should rely on this function for consistent + // clipping behavior. + void drawFilledQuad(const GrClip& clip, + GrPaint&& paint, + GrAA aa, + GrQuadAAFlags edgeFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const GrUserStencilSettings* ss = nullptr); + + // Like drawFilledQuad but does not require using a GrPaint or FP for texturing + void drawTexturedQuad(const GrClip& clip, + sk_sp proxy, + GrColorType srcColorType, + sk_sp textureXform, + GrSamplerState::Filter filter, + const SkPMColor4f& color, + SkBlendMode blendMode, + GrAA aa, + GrQuadAAFlags edgeFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain = nullptr); + + void drawShapeUsingPathRenderer(const GrClip&, GrPaint&&, GrAA, const SkMatrix&, + const GrShape&); + + void addOp(std::unique_ptr); + + // Allows caller of addDrawOp to know which op list an op will be added to. + using WillAddOpFn = void(GrOp*, uint32_t opsTaskID); + // These perform processing specific to GrDrawOp-derived ops before recording them into an + // op list. Before adding the op to an op list the WillAddOpFn is called. Note that it + // will not be called in the event that the op is discarded. Moreover, the op may merge into + // another op after the function is called (either before addDrawOp returns or some time later). + void addDrawOp(const GrClip&, std::unique_ptr, + const std::function& = std::function()); + + // Makes a copy of the proxy if it is necessary for the draw and places the texture that should + // be used by GrXferProcessor to access the destination color in 'result'. If the return + // value is false then a texture copy could not be made. + bool SK_WARN_UNUSED_RESULT setupDstProxy(const GrClip&, const GrOp& op, + GrXferProcessor::DstProxy* result); + + class AsyncReadResult; + + // The async read step of asyncRescaleAndReadPixels() + void asyncReadPixels(const SkIRect& rect, SkColorType colorType, ReadPixelsCallback callback, + ReadPixelsContext context); + + GrOpsTask* getOpsTask(); + + std::unique_ptr fTextTarget; + sk_sp fRenderTargetProxy; + + // In MDB-mode the GrOpsTask can be closed by some other renderTargetContext that has picked + // it up. For this reason, the GrOpsTask should only ever be accessed via 'getOpsTask'. + sk_sp fOpsTask; + + SkSurfaceProps fSurfaceProps; + bool fManagedOpsTask; + + int fNumStencilSamples = 0; +#if GR_TEST_UTILS + bool fPreserveOpsOnFullClear_TestingOnly = false; +#endif + + typedef GrSurfaceContext INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetContextPriv.h b/gfx/skia/skia/src/gpu/GrRenderTargetContextPriv.h new file mode 100644 index 0000000000..4a12ba0208 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetContextPriv.h @@ -0,0 +1,128 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTargetContextPriv_DEFINED +#define GrRenderTargetContextPriv_DEFINED + +#include "src/gpu/GrOpsTask.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrRenderTargetContext.h" + +class GrFixedClip; +class GrHardClip; +class GrPath; +class GrRenderTargetPriv; +struct GrUserStencilSettings; + +/** Class that adds methods to GrRenderTargetContext that are only intended for use internal to + Skia. This class is purely a privileged window into GrRenderTargetContext. It should never have + additional data members or virtual methods. */ +class GrRenderTargetContextPriv { +public: + // called to note the last clip drawn to the stencil buffer. + // TODO: remove after clipping overhaul. + void setLastClip(uint32_t clipStackGenID, const SkIRect& devClipBounds, + int numClipAnalyticFPs) { + GrOpsTask* opsTask = fRenderTargetContext->getOpsTask(); + opsTask->fLastClipStackGenID = clipStackGenID; + opsTask->fLastDevClipBounds = devClipBounds; + opsTask->fLastClipNumAnalyticFPs = numClipAnalyticFPs; + } + + // called to determine if we have to render the clip into SB. + // TODO: remove after clipping overhaul. + bool mustRenderClip(uint32_t clipStackGenID, const SkIRect& devClipBounds, + int numClipAnalyticFPs) const { + GrOpsTask* opsTask = fRenderTargetContext->getOpsTask(); + return opsTask->fLastClipStackGenID != clipStackGenID || + !opsTask->fLastDevClipBounds.contains(devClipBounds) || + opsTask->fLastClipNumAnalyticFPs != numClipAnalyticFPs; + } + + using CanClearFullscreen = GrRenderTargetContext::CanClearFullscreen; + + void clear(const GrFixedClip&, const SkPMColor4f&, CanClearFullscreen); + + void clearStencilClip(const GrFixedClip&, bool insideStencilMask); + + // While this can take a general clip, since GrReducedClip relies on this function, it must take + // care to only provide hard clips or we could get stuck in a loop. The general clip is needed + // so that path renderers can use this function. + void stencilRect( + const GrClip& clip, const GrUserStencilSettings* ss, GrPaint&& paint, + GrAA doStencilMSAA, const SkMatrix& viewMatrix, const SkRect& rect, + const SkMatrix* localMatrix = nullptr) { + // Since this provides stencil settings to drawFilledQuad, it performs a different AA type + // resolution compared to regular rect draws, which is the main reason it remains separate. + GrQuad localQuad = localMatrix ? GrQuad::MakeFromRect(rect, *localMatrix) : GrQuad(rect); + fRenderTargetContext->drawFilledQuad( + clip, std::move(paint), doStencilMSAA, GrQuadAAFlags::kNone, + GrQuad::MakeFromRect(rect, viewMatrix), localQuad, ss); + } + + void stencilPath( + const GrHardClip&, GrAA doStencilMSAA, const SkMatrix& viewMatrix, sk_sp); + + /** + * Draws a path, either AA or not, and touches the stencil buffer with the user stencil settings + * for each color sample written. + */ + bool drawAndStencilPath(const GrHardClip&, + const GrUserStencilSettings*, + SkRegion::Op op, + bool invert, + GrAA doStencilMSAA, + const SkMatrix& viewMatrix, + const SkPath&); + + SkBudgeted isBudgeted() const; + + int maxWindowRectangles() const; + + /* + * This unique ID will not change for a given RenderTargetContext. However, it is _NOT_ + * guaranteed to match the uniqueID of the underlying GrRenderTarget - beware! + */ + GrSurfaceProxy::UniqueID uniqueID() const { + return fRenderTargetContext->fRenderTargetProxy->uniqueID(); + } + + uint32_t testingOnly_getOpsTaskID(); + + using WillAddOpFn = GrRenderTargetContext::WillAddOpFn; + void testingOnly_addDrawOp(std::unique_ptr); + void testingOnly_addDrawOp(const GrClip&, std::unique_ptr, + const std::function& = std::function()); + + bool refsWrappedObjects() const { + return fRenderTargetContext->fRenderTargetProxy->refsWrappedObjects(); + } + +private: + explicit GrRenderTargetContextPriv(GrRenderTargetContext* renderTargetContext) + : fRenderTargetContext(renderTargetContext) {} + GrRenderTargetContextPriv(const GrRenderTargetPriv&) {} // unimpl + GrRenderTargetContextPriv& operator=(const GrRenderTargetPriv&); // unimpl + + // No taking addresses of this type. + const GrRenderTargetContextPriv* operator&() const; + GrRenderTargetContextPriv* operator&(); + + GrRenderTargetContext* fRenderTargetContext; + + friend class GrRenderTargetContext; // to construct/copy this type. +}; + +inline GrRenderTargetContextPriv GrRenderTargetContext::priv() { + return GrRenderTargetContextPriv(this); +} + +inline const GrRenderTargetContextPriv GrRenderTargetContext::priv() const { + return GrRenderTargetContextPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetPriv.h b/gfx/skia/skia/src/gpu/GrRenderTargetPriv.h new file mode 100644 index 0000000000..f81bd1f49a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetPriv.h @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTargetPriv_DEFINED +#define GrRenderTargetPriv_DEFINED + +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTarget.h" + +class GrStencilSettings; + +/** Class that adds methods to GrRenderTarget that are only intended for use internal to Skia. + This class is purely a privileged window into GrRenderTarget. It should never have additional + data members or virtual methods. */ +class GrRenderTargetPriv { +public: + /** + * GrStencilAttachment is not part of the public API. + */ + GrStencilAttachment* getStencilAttachment() const { + return fRenderTarget->fStencilAttachment.get(); + } + + /** + * Attaches the GrStencilAttachment onto the render target. If stencil is a nullptr then the + * currently attached GrStencilAttachment will be removed if one was previously attached. This + * function returns false if there were any failure in attaching the GrStencilAttachment. + */ + void attachStencilAttachment(sk_sp stencil); + + int numStencilBits() const; + + /** + * Returns a unique key that identifies this render target's sample pattern. (Must be + * multisampled.) + */ + int getSamplePatternKey() const; + + /** + * Retrieves the per-pixel HW sample locations for this render target, and, as a by-product, the + * actual number of samples in use. (This may differ from fSampleCnt.) Sample locations are + * returned as 0..1 offsets relative to the top-left corner of the pixel. + */ + const SkTArray& getSampleLocations() const { + int samplePatternKey = this->getSamplePatternKey(); + return fRenderTarget->getGpu()->retrieveSampleLocations(samplePatternKey); + } + +private: + explicit GrRenderTargetPriv(GrRenderTarget* renderTarget) : fRenderTarget(renderTarget) {} + GrRenderTargetPriv(const GrRenderTargetPriv&) {} // unimpl + GrRenderTargetPriv& operator=(const GrRenderTargetPriv&); // unimpl + + // No taking addresses of this type. + const GrRenderTargetPriv* operator&() const; + GrRenderTargetPriv* operator&(); + + GrRenderTarget* fRenderTarget; + + friend class GrRenderTarget; // to construct/copy this type. +}; + +inline GrRenderTargetPriv GrRenderTarget::renderTargetPriv() { return GrRenderTargetPriv(this); } + +inline const GrRenderTargetPriv GrRenderTarget::renderTargetPriv () const { + return GrRenderTargetPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetProxy.cpp b/gfx/skia/skia/src/gpu/GrRenderTargetProxy.cpp new file mode 100644 index 0000000000..6e7d3a5ed0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetProxy.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrRenderTargetProxy.h" + +#include "include/gpu/GrContext.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrOpsTask.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTextureRenderTargetProxy.h" + +// Deferred version +// TODO: we can probably munge the 'desc' in both the wrapped and deferred +// cases to make the sampleConfig/numSamples stuff more rational. +GrRenderTargetProxy::GrRenderTargetProxy(const GrCaps& caps, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + int sampleCount, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : INHERITED(format, desc, GrRenderable::kYes, origin, textureSwizzle, fit, budgeted, + isProtected, surfaceFlags, useAllocator) + , fSampleCnt(sampleCount) + , fWrapsVkSecondaryCB(WrapsVkSecondaryCB::kNo) + , fOutputSwizzle(outputSwizzle) {} + +// Lazy-callback version +GrRenderTargetProxy::GrRenderTargetProxy(LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + int sampleCount, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator, + WrapsVkSecondaryCB wrapsVkSecondaryCB) + : INHERITED(std::move(callback), format, desc, GrRenderable::kYes, origin, textureSwizzle, + fit, budgeted, isProtected, surfaceFlags, useAllocator) + , fSampleCnt(sampleCount) + , fWrapsVkSecondaryCB(wrapsVkSecondaryCB) + , fOutputSwizzle(outputSwizzle) {} + +// Wrapped version +GrRenderTargetProxy::GrRenderTargetProxy(sk_sp surf, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + UseAllocator useAllocator, + WrapsVkSecondaryCB wrapsVkSecondaryCB) + : INHERITED(std::move(surf), origin, textureSwizzle, SkBackingFit::kExact, useAllocator) + , fSampleCnt(fTarget->asRenderTarget()->numSamples()) + , fWrapsVkSecondaryCB(wrapsVkSecondaryCB) + , fOutputSwizzle(outputSwizzle) { + // The kRequiresManualMSAAResolve flag better not be set if we are not multisampled or if + // MSAA resolve should happen automatically. + // + // From the other side, we don't know enough about the wrapped surface to assert when + // kRequiresManualMSAAResolve *should* be set. e.g., The caller might be wrapping a backend + // texture as a render target at this point but we wouldn't know it. + SkASSERT(!(this->numSamples() <= 1 || + fTarget->getContext()->priv().caps()->msaaResolvesAutomatically()) || + !this->requiresManualMSAAResolve()); +} + +int GrRenderTargetProxy::maxWindowRectangles(const GrCaps& caps) const { + return this->glRTFBOIDIs0() ? 0 : caps.maxWindowRectangles(); +} + +bool GrRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) { + if (this->isLazy()) { + return false; + } + if (!this->instantiateImpl(resourceProvider, fSampleCnt, fNumStencilSamples, GrRenderable::kYes, + GrMipMapped::kNo, nullptr)) { + return false; + } + + SkASSERT(this->peekRenderTarget()); + SkASSERT(!this->peekTexture()); + return true; +} + +bool GrRenderTargetProxy::canChangeStencilAttachment() const { + if (!fTarget) { + // If we aren't instantiated, then we definitely are an internal render target. Ganesh is + // free to change stencil attachments on internal render targets. + return true; + } + return fTarget->asRenderTarget()->canAttemptStencilAttachment(); +} + +sk_sp GrRenderTargetProxy::createSurface(GrResourceProvider* resourceProvider) const { + sk_sp surface = this->createSurfaceImpl( + resourceProvider, fSampleCnt, fNumStencilSamples, GrRenderable::kYes, GrMipMapped::kNo); + if (!surface) { + return nullptr; + } + SkASSERT(surface->asRenderTarget()); + SkASSERT(!surface->asTexture()); + return surface; +} + +size_t GrRenderTargetProxy::onUninstantiatedGpuMemorySize(const GrCaps& caps) const { + int colorSamplesPerPixel = this->numSamples(); + if (colorSamplesPerPixel > 1) { + // Add one for the resolve buffer. + ++colorSamplesPerPixel; + } + + // TODO: do we have enough information to improve this worst case estimate? + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + colorSamplesPerPixel, GrMipMapped::kNo, !this->priv().isExact()); +} + +bool GrRenderTargetProxy::refsWrappedObjects() const { + if (!this->isInstantiated()) { + return false; + } + + GrSurface* surface = this->peekSurface(); + return surface->resourcePriv().refsWrappedObjects(); +} + +#ifdef SK_DEBUG +void GrRenderTargetProxy::onValidateSurface(const GrSurface* surface) { + // We do not check that surface->asTexture returns null since, when replaying DDLs we + // can fulfill a renderTarget-only proxy w/ a textureRenderTarget. + + // Anything that is checked here should be duplicated in GrTextureRenderTargetProxy's version + SkASSERT(surface->asRenderTarget()); + SkASSERT(surface->asRenderTarget()->numSamples() == this->numSamples()); + + GrInternalSurfaceFlags proxyFlags = fSurfaceFlags; + GrInternalSurfaceFlags surfaceFlags = surface->surfacePriv().flags(); + SkASSERT(((int)proxyFlags & kGrInternalRenderTargetFlagsMask) == + ((int)surfaceFlags & kGrInternalRenderTargetFlagsMask)); +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetProxy.h b/gfx/skia/skia/src/gpu/GrRenderTargetProxy.h new file mode 100644 index 0000000000..d4fad64331 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetProxy.h @@ -0,0 +1,179 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTargetProxy_DEFINED +#define GrRenderTargetProxy_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrSwizzle.h" + +class GrResourceProvider; +class GrRenderTargetProxyPriv; + +// This class delays the acquisition of RenderTargets until they are actually +// required +// Beware: the uniqueID of the RenderTargetProxy will usually be different than +// the uniqueID of the RenderTarget it represents! +class GrRenderTargetProxy : virtual public GrSurfaceProxy { +public: + GrRenderTargetProxy* asRenderTargetProxy() override { return this; } + const GrRenderTargetProxy* asRenderTargetProxy() const override { return this; } + + // Actually instantiate the backing rendertarget, if necessary. + bool instantiate(GrResourceProvider*) override; + + bool canUseMixedSamples(const GrCaps& caps) const { + return caps.mixedSamplesSupport() && !this->glRTFBOIDIs0() && + caps.internalMultisampleCount(this->backendFormat()) > 0 && + this->canChangeStencilAttachment(); + } + + /* + * Indicate that a draw to this proxy requires stencil, and how many stencil samples it needs. + * The number of stencil samples on this proxy will be equal to the largest sample count passed + * to this method. + */ + void setNeedsStencil(int8_t numStencilSamples) { + SkASSERT(numStencilSamples >= fSampleCnt); + fNumStencilSamples = SkTMax(numStencilSamples, fNumStencilSamples); + } + + /** + * Returns the number of stencil samples required by this proxy. + * NOTE: Once instantiated, the actual render target may have more samples, but it is guaranteed + * to have at least this many. (After a multisample stencil buffer has been attached to a render + * target, we never "downgrade" it to one with fewer samples.) + */ + int numStencilSamples() const { return fNumStencilSamples; } + + /** + * Returns the number of samples/pixel in the color buffer (One if non-MSAA). + */ + int numSamples() const { return fSampleCnt; } + + int maxWindowRectangles(const GrCaps& caps) const; + + const GrSwizzle& outputSwizzle() const { return fOutputSwizzle; } + + bool wrapsVkSecondaryCB() const { return fWrapsVkSecondaryCB == WrapsVkSecondaryCB::kYes; } + + void markMSAADirty(const SkIRect& dirtyRect) { + SkASSERT(SkIRect::MakeWH(this->width(), this->height()).contains(dirtyRect)); + SkASSERT(this->requiresManualMSAAResolve()); + fMSAADirtyRect.join(dirtyRect); + } + void markMSAAResolved() { + SkASSERT(this->requiresManualMSAAResolve()); + fMSAADirtyRect.setEmpty(); + } + bool isMSAADirty() const { + SkASSERT(fMSAADirtyRect.isEmpty() || this->requiresManualMSAAResolve()); + return this->requiresManualMSAAResolve() && !fMSAADirtyRect.isEmpty(); + } + const SkIRect& msaaDirtyRect() const { + SkASSERT(this->requiresManualMSAAResolve()); + return fMSAADirtyRect; + } + + // TODO: move this to a priv class! + bool refsWrappedObjects() const; + + // Provides access to special purpose functions. + GrRenderTargetProxyPriv rtPriv(); + const GrRenderTargetProxyPriv rtPriv() const; + +protected: + friend class GrProxyProvider; // for ctors + friend class GrRenderTargetProxyPriv; + + // Deferred version + GrRenderTargetProxy(const GrCaps&, + const GrBackendFormat&, + const GrSurfaceDesc&, + int sampleCount, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + + enum class WrapsVkSecondaryCB : bool { kNo = false, kYes = true }; + + // Lazy-callback version + // There are two main use cases for lazily-instantiated proxies: + // basic knowledge - width, height, config, samples, origin are known + // minimal knowledge - only config is known. + // + // The basic knowledge version is used for DDL where we know the type of proxy we are going to + // use, but we don't have access to the GPU yet to instantiate it. + // + // The minimal knowledge version is used for CCPR where we are generating an atlas but we do not + // know the final size until flush time. + GrRenderTargetProxy(LazyInstantiateCallback&&, + const GrBackendFormat&, + const GrSurfaceDesc&, + int sampleCount, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator, + WrapsVkSecondaryCB); + + // Wrapped version + GrRenderTargetProxy(sk_sp, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + UseAllocator, + WrapsVkSecondaryCB = WrapsVkSecondaryCB::kNo); + + sk_sp createSurface(GrResourceProvider*) const override; + +private: + void setGLRTFBOIDIs0() { + fSurfaceFlags |= GrInternalSurfaceFlags::kGLRTFBOIDIs0; + } + bool glRTFBOIDIs0() const { + return fSurfaceFlags & GrInternalSurfaceFlags::kGLRTFBOIDIs0; + } + bool canChangeStencilAttachment() const; + + size_t onUninstantiatedGpuMemorySize(const GrCaps&) const override; + SkDEBUGCODE(void onValidateSurface(const GrSurface*) override;) + + // WARNING: Be careful when adding or removing fields here. ASAN is likely to trigger warnings + // when instantiating GrTextureRenderTargetProxy. The std::function in GrSurfaceProxy makes + // each class in the diamond require 16 byte alignment. Clang appears to layout the fields for + // each class to achieve the necessary alignment. However, ASAN checks the alignment of 'this' + // in the constructors, and always looks for the full 16 byte alignment, even if the fields in + // that particular class don't require it. Changing the size of this object can move the start + // address of other types, leading to this problem. + int8_t fSampleCnt; + int8_t fNumStencilSamples = 0; + WrapsVkSecondaryCB fWrapsVkSecondaryCB; + GrSwizzle fOutputSwizzle; + SkIRect fMSAADirtyRect = SkIRect::MakeEmpty(); + // This is to fix issue in large comment above. Without the padding we end 6 bytes into a 16 + // byte range, so the GrTextureProxy ends up starting 8 byte aligned by not 16. We add the + // padding here to get us right up to the 16 byte alignment (technically any padding of 3-10 + // bytes would work since it always goes up to 8 byte alignment, but we use 10 to more explicit + // about what we're doing). + char fDummyPadding[10]; + + typedef GrSurfaceProxy INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTargetProxyPriv.h b/gfx/skia/skia/src/gpu/GrRenderTargetProxyPriv.h new file mode 100644 index 0000000000..091e4f2f5b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTargetProxyPriv.h @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTargetProxyPriv_DEFINED +#define GrRenderTargetProxyPriv_DEFINED + +#include "src/gpu/GrRenderTargetProxy.h" + +/** + * This class hides the more specialized capabilities of GrRenderTargetProxy. + */ +class GrRenderTargetProxyPriv { +public: + void setGLRTFBOIDIs0() { + // FBO0 should never be wrapped as a texture render target. + SkASSERT(!fRenderTargetProxy->requiresManualMSAAResolve()); + SkASSERT(!fRenderTargetProxy->asTextureProxy()); + fRenderTargetProxy->setGLRTFBOIDIs0(); + } + + bool glRTFBOIDIs0() const { + return fRenderTargetProxy->glRTFBOIDIs0(); + } + +private: + explicit GrRenderTargetProxyPriv(GrRenderTargetProxy* renderTargetProxy) + : fRenderTargetProxy(renderTargetProxy) {} + GrRenderTargetProxyPriv(const GrRenderTargetProxyPriv&) {} // unimpl + GrRenderTargetProxyPriv& operator=(const GrRenderTargetProxyPriv&); // unimpl + + // No taking addresses of this type. + const GrRenderTargetProxyPriv* operator&() const; + GrRenderTargetProxyPriv* operator&(); + + GrRenderTargetProxy* fRenderTargetProxy; + + friend class GrRenderTargetProxy; // to construct/copy this type. +}; + +inline GrRenderTargetProxyPriv GrRenderTargetProxy::rtPriv() { + return GrRenderTargetProxyPriv(this); +} + +inline const GrRenderTargetProxyPriv GrRenderTargetProxy::rtPriv() const { + return GrRenderTargetProxyPriv(const_cast(this)); +} + +#endif + diff --git a/gfx/skia/skia/src/gpu/GrRenderTask.cpp b/gfx/skia/skia/src/gpu/GrRenderTask.cpp new file mode 100644 index 0000000000..a69c48ba0c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTask.cpp @@ -0,0 +1,301 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrRenderTask.h" + +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrTextureProxyPriv.h" +#include "src/gpu/GrTextureResolveRenderTask.h" + +uint32_t GrRenderTask::CreateUniqueID() { + static std::atomic nextID{1}; + uint32_t id; + do { + id = nextID++; + } while (id == SK_InvalidUniqueID); + return id; +} + +GrRenderTask::GrRenderTask(sk_sp target) + : fTarget(std::move(target)) + , fUniqueID(CreateUniqueID()) + , fFlags(0) { +} + +GrRenderTask::~GrRenderTask() { + if (fTarget && this == fTarget->getLastRenderTask()) { + // Ensure the target proxy doesn't keep hold of a dangling back pointer. + fTarget->setLastRenderTask(nullptr); + } +} + +#ifdef SK_DEBUG +bool GrRenderTask::deferredProxiesAreInstantiated() const { + for (int i = 0; i < fDeferredProxies.count(); ++i) { + if (!fDeferredProxies[i]->isInstantiated()) { + return false; + } + } + + return true; +} +#endif + +void GrRenderTask::makeClosed(const GrCaps& caps) { + if (this->isClosed()) { + return; + } + + SkIRect targetUpdateBounds; + if (ExpectedOutcome::kTargetDirty == this->onMakeClosed(caps, &targetUpdateBounds)) { + SkASSERT(SkIRect::MakeWH(fTarget->width(), fTarget->height()).contains(targetUpdateBounds)); + if (fTarget->requiresManualMSAAResolve()) { + SkASSERT(fTarget->asRenderTargetProxy()); + fTarget->asRenderTargetProxy()->markMSAADirty(targetUpdateBounds); + } + GrTextureProxy* textureProxy = fTarget->asTextureProxy(); + if (textureProxy && GrMipMapped::kYes == textureProxy->mipMapped()) { + textureProxy->markMipMapsDirty(); + } + } + + if (fTextureResolveTask) { + this->addDependency(fTextureResolveTask); + fTextureResolveTask->makeClosed(caps); + fTextureResolveTask = nullptr; + } + + this->setFlag(kClosed_Flag); +} + +void GrRenderTask::prepare(GrOpFlushState* flushState) { + for (int i = 0; i < fDeferredProxies.count(); ++i) { + fDeferredProxies[i]->texPriv().scheduleUpload(flushState); + } + + this->onPrepare(flushState); +} + +// Add a GrRenderTask-based dependency +void GrRenderTask::addDependency(GrRenderTask* dependedOn) { + SkASSERT(!dependedOn->dependsOn(this)); // loops are bad + SkASSERT(!this->dependsOn(dependedOn)); // caller should weed out duplicates + + fDependencies.push_back(dependedOn); + dependedOn->addDependent(this); + + SkDEBUGCODE(this->validate()); +} + +void GrRenderTask::addDependenciesFromOtherTask(GrRenderTask* otherTask) { + SkASSERT(otherTask); + for (GrRenderTask* task : otherTask->fDependencies) { + // The task should not be adding a dependency to itself. + SkASSERT(task != this); + if (!this->dependsOn(task)) { + this->addDependency(task); + } + } +} + +// Convert from a GrSurface-based dependency to a GrRenderTask one +void GrRenderTask::addDependency(GrSurfaceProxy* dependedOn, GrMipMapped mipMapped, + GrTextureResolveManager textureResolveManager, + const GrCaps& caps) { + // If it is still receiving dependencies, this GrRenderTask shouldn't be closed + SkASSERT(!this->isClosed()); + + GrRenderTask* dependedOnTask = dependedOn->getLastRenderTask(); + + if (dependedOnTask == this) { + // self-read - presumably for dst reads. We don't need to do anything in this case. The + // XferProcessor will detect what is happening and insert a texture barrier. + SkASSERT(GrMipMapped::kNo == mipMapped); + // We should never attempt a self-read on a surface that has a separate MSAA renderbuffer. + SkASSERT(!dependedOn->requiresManualMSAAResolve()); + SkASSERT(!dependedOn->asTextureProxy() || + !dependedOn->asTextureProxy()->texPriv().isDeferred()); + return; + } + + if (dependedOnTask) { + if (this->dependsOn(dependedOnTask) || fTextureResolveTask == dependedOnTask) { + return; // don't add duplicate dependencies + } + + // We are closing 'dependedOnTask' here bc the current contents of it are what 'this' + // renderTask depends on. We need a break in 'dependedOnTask' so that the usage of + // that state has a chance to execute. + dependedOnTask->makeClosed(caps); + } + + auto resolveFlags = GrSurfaceProxy::ResolveFlags::kNone; + + if (dependedOn->requiresManualMSAAResolve()) { + auto* renderTargetProxy = dependedOn->asRenderTargetProxy(); + SkASSERT(renderTargetProxy); + if (renderTargetProxy->isMSAADirty()) { + resolveFlags |= GrSurfaceProxy::ResolveFlags::kMSAA; + } + } + + GrTextureProxy* textureProxy = dependedOn->asTextureProxy(); + if (GrMipMapped::kYes == mipMapped) { + SkASSERT(textureProxy); + if (GrMipMapped::kYes != textureProxy->mipMapped()) { + // There are some cases where we might be given a non-mipmapped texture with a mipmap + // filter. See skbug.com/7094. + mipMapped = GrMipMapped::kNo; + } else if (textureProxy->mipMapsAreDirty()) { + resolveFlags |= GrSurfaceProxy::ResolveFlags::kMipMaps; + } + } + + // Does this proxy have msaa to resolve and/or mipmaps to regenerate? + if (GrSurfaceProxy::ResolveFlags::kNone != resolveFlags) { + if (!fTextureResolveTask) { + fTextureResolveTask = textureResolveManager.newTextureResolveRenderTask(caps); + } + fTextureResolveTask->addProxy(sk_ref_sp(dependedOn), resolveFlags, caps); + + // addProxy() should have closed the texture proxy's previous task. + SkASSERT(!dependedOnTask || dependedOnTask->isClosed()); + SkASSERT(dependedOn->getLastRenderTask() == fTextureResolveTask); + +#ifdef SK_DEBUG + // addProxy() should have called addDependency (in this instance, recursively) on + // fTextureResolveTask. + if (dependedOnTask) { + SkASSERT(fTextureResolveTask->dependsOn(dependedOnTask)); + } + if (textureProxy && textureProxy->texPriv().isDeferred()) { + SkASSERT(fTextureResolveTask->fDeferredProxies.back() == textureProxy); + } + + // The GrTextureResolveRenderTask factory should have also marked the proxy clean, set the + // last renderTask on the textureProxy to textureResolveTask, and closed textureResolveTask. + if (GrRenderTargetProxy* renderTargetProxy = dependedOn->asRenderTargetProxy()) { + SkASSERT(!renderTargetProxy->isMSAADirty()); + } + if (textureProxy) { + SkASSERT(!textureProxy->mipMapsAreDirty()); + } + SkASSERT(dependedOn->getLastRenderTask() == fTextureResolveTask); +#endif + return; + } + + if (textureProxy && textureProxy->texPriv().isDeferred()) { + fDeferredProxies.push_back(textureProxy); + } + + if (dependedOnTask) { + this->addDependency(dependedOnTask); + } +} + +bool GrRenderTask::dependsOn(const GrRenderTask* dependedOn) const { + for (int i = 0; i < fDependencies.count(); ++i) { + if (fDependencies[i] == dependedOn) { + return true; + } + } + + return false; +} + + +void GrRenderTask::addDependent(GrRenderTask* dependent) { + fDependents.push_back(dependent); +} + +#ifdef SK_DEBUG +bool GrRenderTask::isDependedent(const GrRenderTask* dependent) const { + for (int i = 0; i < fDependents.count(); ++i) { + if (fDependents[i] == dependent) { + return true; + } + } + + return false; +} + +void GrRenderTask::validate() const { + // TODO: check for loops and duplicates + + for (int i = 0; i < fDependencies.count(); ++i) { + SkASSERT(fDependencies[i]->isDependedent(this)); + } +} +#endif + +void GrRenderTask::closeThoseWhoDependOnMe(const GrCaps& caps) { + for (int i = 0; i < fDependents.count(); ++i) { + if (!fDependents[i]->isClosed()) { + fDependents[i]->makeClosed(caps); + } + } +} + +bool GrRenderTask::isInstantiated() const { + // Some renderTasks (e.g. GrTransferFromRenderTask) don't have a target. + if (!fTarget) { + return true; + } + + if (!fTarget->isInstantiated()) { + return false; + } + + int minStencilSampleCount = (fTarget->asRenderTargetProxy()) + ? fTarget->asRenderTargetProxy()->numStencilSamples() + : 0; + + if (minStencilSampleCount) { + GrRenderTarget* rt = fTarget->peekRenderTarget(); + SkASSERT(rt); + + GrStencilAttachment* stencil = rt->renderTargetPriv().getStencilAttachment(); + if (!stencil) { + return false; + } + SkASSERT(stencil->numSamples() >= minStencilSampleCount); + } + + GrSurface* surface = fTarget->peekSurface(); + if (surface->wasDestroyed()) { + return false; + } + + return true; +} + +#ifdef SK_DEBUG +void GrRenderTask::dump(bool printDependencies) const { + SkDebugf("--------------------------------------------------------------\n"); + SkDebugf("renderTaskID: %d - proxyID: %d - surfaceID: %d\n", fUniqueID, + fTarget ? fTarget->uniqueID().asUInt() : -1, + fTarget && fTarget->peekSurface() + ? fTarget->peekSurface()->uniqueID().asUInt() + : -1); + + if (printDependencies) { + SkDebugf("I rely On (%d): ", fDependencies.count()); + for (int i = 0; i < fDependencies.count(); ++i) { + SkDebugf("%d, ", fDependencies[i]->fUniqueID); + } + SkDebugf("\n"); + + SkDebugf("(%d) Rely On Me: ", fDependents.count()); + for (int i = 0; i < fDependents.count(); ++i) { + SkDebugf("%d, ", fDependents[i]->fUniqueID); + } + SkDebugf("\n"); + } +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrRenderTask.h b/gfx/skia/skia/src/gpu/GrRenderTask.h new file mode 100644 index 0000000000..3fb3a18bab --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrRenderTask.h @@ -0,0 +1,207 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRenderTask_DEFINED +#define GrRenderTask_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTDArray.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/GrTextureResolveManager.h" + +class GrOpFlushState; +class GrOpsTask; +class GrResourceAllocator; +class GrTextureResolveRenderTask; + +// This class abstracts a task that targets a single GrSurfaceProxy, participates in the +// GrDrawingManager's DAG, and implements the onExecute method to modify its target proxy's +// contents. (e.g., an opsTask that executes a command buffer, a task to regenerate mipmaps, etc.) +class GrRenderTask : public SkRefCnt { +public: + GrRenderTask(sk_sp target); + ~GrRenderTask() override; + + void makeClosed(const GrCaps&); + + void prePrepare(GrRecordingContext* context) { this->onPrePrepare(context); } + + // These two methods are only invoked at flush time + void prepare(GrOpFlushState* flushState); + bool execute(GrOpFlushState* flushState) { return this->onExecute(flushState); } + + // Called when this class will survive a flush and needs to truncate its ops and start over. + // TODO: ultimately it should be invalid for an op list to survive a flush. + // https://bugs.chromium.org/p/skia/issues/detail?id=7111 + virtual void endFlush() {} + + bool isClosed() const { return this->isSetFlag(kClosed_Flag); } + + /* + * Notify this GrRenderTask that it relies on the contents of 'dependedOn' + */ + void addDependency(GrSurfaceProxy* dependedOn, GrMipMapped, GrTextureResolveManager, + const GrCaps& caps); + + /* + * Notify this GrRenderTask that it relies on the contents of all GrRenderTasks which otherTask + * depends on. + */ + void addDependenciesFromOtherTask(GrRenderTask* otherTask); + + /* + * Does this renderTask depend on 'dependedOn'? + */ + bool dependsOn(const GrRenderTask* dependedOn) const; + + uint32_t uniqueID() const { return fUniqueID; } + + /* + * Safely cast this GrRenderTask to a GrOpsTask (if possible). + */ + virtual GrOpsTask* asOpsTask() { return nullptr; } + +#ifdef SK_DEBUG + /* + * Dump out the GrRenderTask dependency DAG + */ + virtual void dump(bool printDependencies) const; + + virtual int numClips() const { return 0; } + + using VisitSurfaceProxyFunc = std::function; + + virtual void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const = 0; + + void visitTargetAndSrcProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const { + this->visitProxies_debugOnly(fn); + if (fTarget) { + fn(fTarget.get(), GrMipMapped::kNo); + } + } +#endif + +protected: + // In addition to just the GrSurface being allocated, has the stencil buffer been allocated (if + // it is required)? + bool isInstantiated() const; + + SkDEBUGCODE(bool deferredProxiesAreInstantiated() const;) + + enum class ExpectedOutcome : bool { + kTargetUnchanged, + kTargetDirty, + }; + + // Performs any work to finalize this renderTask prior to execution. If returning + // ExpectedOutcome::kTargetDiry, the caller is also responsible to fill out the area it will + // modify in targetUpdateBounds. + // + // targetUpdateBounds must not extend beyond the proxy bounds. + virtual ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect* targetUpdateBounds) = 0; + + sk_sp fTarget; + + // List of texture proxies whose contents are being prepared on a worker thread + // TODO: this list exists so we can fire off the proper upload when an renderTask begins + // executing. Can this be replaced? + SkTArray fDeferredProxies; + +private: + // for resetFlag, TopoSortTraits, gatherProxyIntervals, handleInternalAllocationFailure + friend class GrDrawingManager; + + // Drops any pending operations that reference proxies that are not instantiated. + // NOTE: Derived classes don't need to check fTarget. That is handled when the drawingManager + // calls isInstantiated. + virtual void handleInternalAllocationFailure() = 0; + + virtual bool onIsUsed(GrSurfaceProxy*) const = 0; + + bool isUsed(GrSurfaceProxy* proxy) const { + if (proxy == fTarget.get()) { + return true; + } + + return this->onIsUsed(proxy); + } + + void addDependency(GrRenderTask* dependedOn); + void addDependent(GrRenderTask* dependent); + SkDEBUGCODE(bool isDependedent(const GrRenderTask* dependent) const;) + SkDEBUGCODE(void validate() const;) + void closeThoseWhoDependOnMe(const GrCaps&); + + // Feed proxy usage intervals to the GrResourceAllocator class + virtual void gatherProxyIntervals(GrResourceAllocator*) const = 0; + + static uint32_t CreateUniqueID(); + + enum Flags { + kClosed_Flag = 0x01, //!< This GrRenderTask can't accept any more dependencies. + + kWasOutput_Flag = 0x02, //!< Flag for topological sorting + kTempMark_Flag = 0x04, //!< Flag for topological sorting + }; + + void setFlag(uint32_t flag) { + fFlags |= flag; + } + + void resetFlag(uint32_t flag) { + fFlags &= ~flag; + } + + bool isSetFlag(uint32_t flag) const { + return SkToBool(fFlags & flag); + } + + struct TopoSortTraits { + static void Output(GrRenderTask* renderTask, int /* index */) { + renderTask->setFlag(kWasOutput_Flag); + } + static bool WasOutput(const GrRenderTask* renderTask) { + return renderTask->isSetFlag(kWasOutput_Flag); + } + static void SetTempMark(GrRenderTask* renderTask) { + renderTask->setFlag(kTempMark_Flag); + } + static void ResetTempMark(GrRenderTask* renderTask) { + renderTask->resetFlag(kTempMark_Flag); + } + static bool IsTempMarked(const GrRenderTask* renderTask) { + return renderTask->isSetFlag(kTempMark_Flag); + } + static int NumDependencies(const GrRenderTask* renderTask) { + return renderTask->fDependencies.count(); + } + static GrRenderTask* Dependency(GrRenderTask* renderTask, int index) { + return renderTask->fDependencies[index]; + } + }; + + // Only the GrOpsTask currently overrides this virtual + virtual void onPrePrepare(GrRecordingContext*) {} + virtual void onPrepare(GrOpFlushState*) {} // Only the GrOpsTask overrides this virtual + virtual bool onExecute(GrOpFlushState* flushState) = 0; + + const uint32_t fUniqueID; + uint32_t fFlags; + + // 'this' GrRenderTask relies on the output of the GrRenderTasks in 'fDependencies' + SkSTArray<1, GrRenderTask*, true> fDependencies; + // 'this' GrRenderTask's output is relied on by the GrRenderTasks in 'fDependents' + SkSTArray<1, GrRenderTask*, true> fDependents; + + // For performance reasons, we should perform texture resolves back-to-back as much as possible. + // (http://skbug.com/9406). To accomplish this, we make and reuse one single resolve task for + // each render task, then add it as a dependency during makeClosed(). + GrTextureResolveRenderTask* fTextureResolveTask = nullptr; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrResourceAllocator.cpp b/gfx/skia/skia/src/gpu/GrResourceAllocator.cpp new file mode 100644 index 0000000000..6ab2bf0488 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceAllocator.cpp @@ -0,0 +1,510 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrResourceAllocator.h" + +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrOpsTask.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureProxy.h" + +#if GR_TRACK_INTERVAL_CREATION + #include + + uint32_t GrResourceAllocator::Interval::CreateUniqueID() { + static std::atomic nextID{1}; + uint32_t id; + do { + id = nextID++; + } while (id == SK_InvalidUniqueID); + return id; + } +#endif + +void GrResourceAllocator::Interval::assign(sk_sp s) { + SkASSERT(!fAssignedSurface); + fAssignedSurface = s; + fProxy->priv().assign(std::move(s)); +} + +void GrResourceAllocator::determineRecyclability() { + for (Interval* cur = fIntvlList.peekHead(); cur; cur = cur->next()) { + if (cur->proxy()->canSkipResourceAllocator()) { + // These types of proxies can slip in here if they require a stencil buffer + continue; + } + + if (cur->uses() >= cur->proxy()->refCnt()) { + // All the refs on the proxy are known to the resource allocator thus no one + // should be holding onto it outside of Ganesh. + SkASSERT(cur->uses() == cur->proxy()->refCnt()); + cur->markAsRecyclable(); + } + } +} + +void GrResourceAllocator::markEndOfOpsTask(int opsTaskIndex) { + SkASSERT(!fAssigned); // We shouldn't be adding any opsTasks after (or during) assignment + + SkASSERT(fEndOfOpsTaskOpIndices.count() == opsTaskIndex); + if (!fEndOfOpsTaskOpIndices.empty()) { + SkASSERT(fEndOfOpsTaskOpIndices.back() < this->curOp()); + } + + // This is the first op index of the next opsTask + fEndOfOpsTaskOpIndices.push_back(this->curOp()); + SkASSERT(fEndOfOpsTaskOpIndices.count() <= fNumOpsTasks); +} + +GrResourceAllocator::~GrResourceAllocator() { + SkASSERT(fIntvlList.empty()); + SkASSERT(fActiveIntvls.empty()); + SkASSERT(!fIntvlHash.count()); +} + +void GrResourceAllocator::addInterval(GrSurfaceProxy* proxy, unsigned int start, unsigned int end, + ActualUse actualUse + SkDEBUGCODE(, bool isDirectDstRead)) { + SkASSERT(start <= end); + SkASSERT(!fAssigned); // We shouldn't be adding any intervals after (or during) assignment + + if (proxy->canSkipResourceAllocator()) { + // If the proxy is still not instantiated at this point but will need stencil, it will + // attach its own stencil buffer upon onFlush instantiation. + if (proxy->isInstantiated()) { + auto rt = proxy->asRenderTargetProxy(); + int minStencilSampleCount = rt ? rt->numStencilSamples() : 0; + if (minStencilSampleCount) { + if (!GrSurfaceProxyPriv::AttachStencilIfNeeded( + fResourceProvider, proxy->peekSurface(), minStencilSampleCount)) { + SkDebugf("WARNING: failed to attach stencil buffer. " + "Rendering may be incorrect.\n"); + } + } + } + return; + } + + // If a proxy is read only it must refer to a texture with specific content that cannot be + // recycled. We don't need to assign a texture to it and no other proxy can be instantiated + // with the same texture. + if (proxy->readOnly()) { + if (proxy->isLazy() && !proxy->priv().doLazyInstantiation(fResourceProvider)) { + fLazyInstantiationError = true; + } else { + // Since we aren't going to add an interval we won't revisit this proxy in assign(). So + // must already be instantiated or it must be a lazy proxy that we instantiated above. + SkASSERT(proxy->isInstantiated()); + } + return; + } + if (Interval* intvl = fIntvlHash.find(proxy->uniqueID().asUInt())) { + // Revise the interval for an existing use +#ifdef SK_DEBUG + if (0 == start && 0 == end) { + // This interval is for the initial upload to a deferred proxy. Due to the vagaries + // of how deferred proxies are collected they can appear as uploads multiple times + // in a single opsTasks' list and as uploads in several opsTasks. + SkASSERT(0 == intvl->start()); + } else if (isDirectDstRead) { + // Direct reads from the render target itself should occur w/in the existing + // interval + SkASSERT(intvl->start() <= start && intvl->end() >= end); + } else { + SkASSERT(intvl->end() <= start && intvl->end() <= end); + } +#endif + if (ActualUse::kYes == actualUse) { + intvl->addUse(); + } + intvl->extendEnd(end); + return; + } + Interval* newIntvl; + if (fFreeIntervalList) { + newIntvl = fFreeIntervalList; + fFreeIntervalList = newIntvl->next(); + newIntvl->setNext(nullptr); + newIntvl->resetTo(proxy, start, end); + } else { + newIntvl = fIntervalAllocator.make(proxy, start, end); + } + + if (ActualUse::kYes == actualUse) { + newIntvl->addUse(); + } + fIntvlList.insertByIncreasingStart(newIntvl); + fIntvlHash.add(newIntvl); +} + +GrResourceAllocator::Interval* GrResourceAllocator::IntervalList::popHead() { + SkDEBUGCODE(this->validate()); + + Interval* temp = fHead; + if (temp) { + fHead = temp->next(); + if (!fHead) { + fTail = nullptr; + } + temp->setNext(nullptr); + } + + SkDEBUGCODE(this->validate()); + return temp; +} + +// TODO: fuse this with insertByIncreasingEnd +void GrResourceAllocator::IntervalList::insertByIncreasingStart(Interval* intvl) { + SkDEBUGCODE(this->validate()); + SkASSERT(!intvl->next()); + + if (!fHead) { + // 14% + fHead = fTail = intvl; + } else if (intvl->start() <= fHead->start()) { + // 3% + intvl->setNext(fHead); + fHead = intvl; + } else if (fTail->start() <= intvl->start()) { + // 83% + fTail->setNext(intvl); + fTail = intvl; + } else { + // almost never + Interval* prev = fHead; + Interval* next = prev->next(); + for (; intvl->start() > next->start(); prev = next, next = next->next()) { + } + + SkASSERT(next); + intvl->setNext(next); + prev->setNext(intvl); + } + + SkDEBUGCODE(this->validate()); +} + +// TODO: fuse this with insertByIncreasingStart +void GrResourceAllocator::IntervalList::insertByIncreasingEnd(Interval* intvl) { + SkDEBUGCODE(this->validate()); + SkASSERT(!intvl->next()); + + if (!fHead) { + // 14% + fHead = fTail = intvl; + } else if (intvl->end() <= fHead->end()) { + // 64% + intvl->setNext(fHead); + fHead = intvl; + } else if (fTail->end() <= intvl->end()) { + // 3% + fTail->setNext(intvl); + fTail = intvl; + } else { + // 19% but 81% of those land right after the list's head + Interval* prev = fHead; + Interval* next = prev->next(); + for (; intvl->end() > next->end(); prev = next, next = next->next()) { + } + + SkASSERT(next); + intvl->setNext(next); + prev->setNext(intvl); + } + + SkDEBUGCODE(this->validate()); +} + +#ifdef SK_DEBUG +void GrResourceAllocator::IntervalList::validate() const { + SkASSERT(SkToBool(fHead) == SkToBool(fTail)); + + Interval* prev = nullptr; + for (Interval* cur = fHead; cur; prev = cur, cur = cur->next()) { + } + + SkASSERT(fTail == prev); +} +#endif + + GrResourceAllocator::Interval* GrResourceAllocator::IntervalList::detachAll() { + Interval* tmp = fHead; + fHead = nullptr; + fTail = nullptr; + return tmp; +} + +// 'surface' can be reused. Add it back to the free pool. +void GrResourceAllocator::recycleSurface(sk_sp surface) { + const GrScratchKey &key = surface->resourcePriv().getScratchKey(); + + if (!key.isValid()) { + return; // can't do it w/o a valid scratch key + } + + if (surface->getUniqueKey().isValid()) { + // If the surface has a unique key we throw it back into the resource cache. + // If things get really tight 'findSurfaceFor' may pull it back out but there is + // no need to have it in tight rotation. + return; + } + +#if GR_ALLOCATION_SPEW + SkDebugf("putting surface %d back into pool\n", surface->uniqueID().asUInt()); +#endif + // TODO: fix this insertion so we get a more LRU-ish behavior + fFreePool.insert(key, surface.release()); +} + +// First try to reuse one of the recently allocated/used GrSurfaces in the free pool. +// If we can't find a useable one, create a new one. +sk_sp GrResourceAllocator::findSurfaceFor(const GrSurfaceProxy* proxy, + int minStencilSampleCount) { + + if (proxy->asTextureProxy() && proxy->asTextureProxy()->getUniqueKey().isValid()) { + // First try to reattach to a cached version if the proxy is uniquely keyed + sk_sp surface = fResourceProvider->findByUniqueKey( + proxy->asTextureProxy()->getUniqueKey()); + if (surface) { + if (!GrSurfaceProxyPriv::AttachStencilIfNeeded(fResourceProvider, surface.get(), + minStencilSampleCount)) { + return nullptr; + } + + return surface; + } + } + + // First look in the free pool + GrScratchKey key; + + proxy->priv().computeScratchKey(&key); + + auto filter = [] (const GrSurface* s) { + return true; + }; + sk_sp surface(fFreePool.findAndRemove(key, filter)); + if (surface) { + if (SkBudgeted::kYes == proxy->isBudgeted() && + GrBudgetedType::kBudgeted != surface->resourcePriv().budgetedType()) { + // This gets the job done but isn't quite correct. It would be better to try to + // match budgeted proxies w/ budgeted surfaces and unbudgeted w/ unbudgeted. + surface->resourcePriv().makeBudgeted(); + } + + if (!GrSurfaceProxyPriv::AttachStencilIfNeeded(fResourceProvider, surface.get(), + minStencilSampleCount)) { + return nullptr; + } + SkASSERT(!surface->getUniqueKey().isValid()); + return surface; + } + + // Failing that, try to grab a new one from the resource cache + return proxy->priv().createSurface(fResourceProvider); +} + +// Remove any intervals that end before the current index. Return their GrSurfaces +// to the free pool if possible. +void GrResourceAllocator::expire(unsigned int curIndex) { + while (!fActiveIntvls.empty() && fActiveIntvls.peekHead()->end() < curIndex) { + Interval* temp = fActiveIntvls.popHead(); + SkASSERT(!temp->next()); + + if (temp->wasAssignedSurface()) { + sk_sp surface = temp->detachSurface(); + + if (temp->isRecyclable()) { + this->recycleSurface(std::move(surface)); + } + } + + // Add temp to the free interval list so it can be reused + SkASSERT(!temp->wasAssignedSurface()); // it had better not have a ref on a surface + temp->setNext(fFreeIntervalList); + fFreeIntervalList = temp; + } +} + +bool GrResourceAllocator::onOpsTaskBoundary() const { + if (fIntvlList.empty()) { + SkASSERT(fCurOpsTaskIndex+1 <= fNumOpsTasks); + // Although technically on an opsTask boundary there is no need to force an + // intermediate flush here + return false; + } + + const Interval* tmp = fIntvlList.peekHead(); + return fEndOfOpsTaskOpIndices[fCurOpsTaskIndex] <= tmp->start(); +} + +void GrResourceAllocator::forceIntermediateFlush(int* stopIndex) { + *stopIndex = fCurOpsTaskIndex+1; + + // This is interrupting the allocation of resources for this flush. We need to + // proactively clear the active interval list of any intervals that aren't + // guaranteed to survive the partial flush lest they become zombies (i.e., + // holding a deleted surface proxy). + const Interval* tmp = fIntvlList.peekHead(); + SkASSERT(fEndOfOpsTaskOpIndices[fCurOpsTaskIndex] <= tmp->start()); + + fCurOpsTaskIndex++; + SkASSERT(fCurOpsTaskIndex < fNumOpsTasks); + + this->expire(tmp->start()); +} + +bool GrResourceAllocator::assign(int* startIndex, int* stopIndex, AssignError* outError) { + SkASSERT(outError); + *outError = fLazyInstantiationError ? AssignError::kFailedProxyInstantiation + : AssignError::kNoError; + + SkASSERT(fNumOpsTasks == fEndOfOpsTaskOpIndices.count()); + + fIntvlHash.reset(); // we don't need the interval hash anymore + + if (fCurOpsTaskIndex >= fEndOfOpsTaskOpIndices.count()) { + return false; // nothing to render + } + + *startIndex = fCurOpsTaskIndex; + *stopIndex = fEndOfOpsTaskOpIndices.count(); + + if (fIntvlList.empty()) { + fCurOpsTaskIndex = fEndOfOpsTaskOpIndices.count(); + return true; // no resources to assign + } + +#if GR_ALLOCATION_SPEW + SkDebugf("assigning opsTasks %d through %d out of %d numOpsTasks\n", + *startIndex, *stopIndex, fNumOpsTasks); + SkDebugf("EndOfOpsTaskIndices: "); + for (int i = 0; i < fEndOfOpsTaskOpIndices.count(); ++i) { + SkDebugf("%d ", fEndOfOpsTaskOpIndices[i]); + } + SkDebugf("\n"); +#endif + + SkDEBUGCODE(fAssigned = true;) + +#if GR_ALLOCATION_SPEW + this->dumpIntervals(); +#endif + while (Interval* cur = fIntvlList.popHead()) { + while (fEndOfOpsTaskOpIndices[fCurOpsTaskIndex] <= cur->start()) { + fCurOpsTaskIndex++; + SkASSERT(fCurOpsTaskIndex < fNumOpsTasks); + } + + this->expire(cur->start()); + + int minStencilSampleCount = (cur->proxy()->asRenderTargetProxy()) + ? cur->proxy()->asRenderTargetProxy()->numStencilSamples() + : 0; + + if (cur->proxy()->isInstantiated()) { + if (!GrSurfaceProxyPriv::AttachStencilIfNeeded( + fResourceProvider, cur->proxy()->peekSurface(), minStencilSampleCount)) { + *outError = AssignError::kFailedProxyInstantiation; + } + + fActiveIntvls.insertByIncreasingEnd(cur); + + if (fResourceProvider->overBudget()) { + // Only force intermediate draws on opsTask boundaries + if (this->onOpsTaskBoundary()) { + this->forceIntermediateFlush(stopIndex); + return true; + } + } + + continue; + } + + if (cur->proxy()->isLazy()) { + if (!cur->proxy()->priv().doLazyInstantiation(fResourceProvider)) { + *outError = AssignError::kFailedProxyInstantiation; + } + } else if (sk_sp surface = + this->findSurfaceFor(cur->proxy(), minStencilSampleCount)) { + // TODO: make getUniqueKey virtual on GrSurfaceProxy + GrTextureProxy* texProxy = cur->proxy()->asTextureProxy(); + + if (texProxy && texProxy->getUniqueKey().isValid()) { + if (!surface->getUniqueKey().isValid()) { + fResourceProvider->assignUniqueKeyToResource(texProxy->getUniqueKey(), + surface.get()); + } + SkASSERT(surface->getUniqueKey() == texProxy->getUniqueKey()); + } + +#if GR_ALLOCATION_SPEW + SkDebugf("Assigning %d to %d\n", + surface->uniqueID().asUInt(), + cur->proxy()->uniqueID().asUInt()); +#endif + + cur->assign(std::move(surface)); + } else { + SkASSERT(!cur->proxy()->isInstantiated()); + *outError = AssignError::kFailedProxyInstantiation; + } + + fActiveIntvls.insertByIncreasingEnd(cur); + + if (fResourceProvider->overBudget()) { + // Only force intermediate draws on opsTask boundaries + if (this->onOpsTaskBoundary()) { + this->forceIntermediateFlush(stopIndex); + return true; + } + } + } + + // expire all the remaining intervals to drain the active interval list + this->expire(std::numeric_limits::max()); + return true; +} + +#if GR_ALLOCATION_SPEW +void GrResourceAllocator::dumpIntervals() { + // Print all the intervals while computing their range + SkDebugf("------------------------------------------------------------\n"); + unsigned int min = std::numeric_limits::max(); + unsigned int max = 0; + for(const Interval* cur = fIntvlList.peekHead(); cur; cur = cur->next()) { + SkDebugf("{ %3d,%3d }: [%2d, %2d] - proxyRefs:%d surfaceRefs:%d\n", + cur->proxy()->uniqueID().asUInt(), + cur->proxy()->isInstantiated() ? cur->proxy()->underlyingUniqueID().asUInt() : -1, + cur->start(), + cur->end(), + cur->proxy()->priv().getProxyRefCnt(), + cur->proxy()->testingOnly_getBackingRefCnt()); + min = SkTMin(min, cur->start()); + max = SkTMax(max, cur->end()); + } + + // Draw a graph of the useage intervals + for(const Interval* cur = fIntvlList.peekHead(); cur; cur = cur->next()) { + SkDebugf("{ %3d,%3d }: ", + cur->proxy()->uniqueID().asUInt(), + cur->proxy()->isInstantiated() ? cur->proxy()->underlyingUniqueID().asUInt() : -1); + for (unsigned int i = min; i <= max; ++i) { + if (i >= cur->start() && i <= cur->end()) { + SkDebugf("x"); + } else { + SkDebugf(" "); + } + } + SkDebugf("\n"); + } +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrResourceAllocator.h b/gfx/skia/skia/src/gpu/GrResourceAllocator.h new file mode 100644 index 0000000000..6108b54537 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceAllocator.h @@ -0,0 +1,277 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrResourceAllocator_DEFINED +#define GrResourceAllocator_DEFINED + +#include "include/gpu/GrSurface.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrSurfaceProxy.h" + +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkTDynamicHash.h" +#include "src/core/SkTMultiMap.h" + +class GrResourceProvider; + +// Print out explicit allocation information +#define GR_ALLOCATION_SPEW 0 + +// Print out information about interval creation +#define GR_TRACK_INTERVAL_CREATION 0 + +/* + * The ResourceAllocator explicitly distributes GPU resources at flush time. It operates by + * being given the usage intervals of the various proxies. It keeps these intervals in a singly + * linked list sorted by increasing start index. (It also maintains a hash table from proxyID + * to interval to find proxy reuse). When it comes time to allocate the resources it + * traverses the sorted list and: + * removes intervals from the active list that have completed (returning their GrSurfaces + * to the free pool) + + * allocates a new resource (preferably from the free pool) for the new interval + * adds the new interval to the active list (that is sorted by increasing end index) + * + * Note: the op indices (used in the usage intervals) come from the order of the ops in + * their opsTasks after the opsTask DAG has been linearized. + * + ************************************************************************************************* + * How does instantiation failure handling work when explicitly allocating? + * + * In the gather usage intervals pass all the GrSurfaceProxies used in the flush should be + * gathered (i.e., in GrOpsTask::gatherProxyIntervals). + * + * The allocator will churn through this list but could fail anywhere. + * + * Allocation failure handling occurs at two levels: + * + * 1) If the GrSurface backing an opsTask fails to allocate then the entire opsTask is dropped. + * + * 2) If an individual GrSurfaceProxy fails to allocate then any ops that use it are dropped + * (via GrOpsTask::purgeOpsWithUninstantiatedProxies) + * + * The pass to determine which ops to drop is a bit laborious so we only check the opsTasks and + * individual ops when something goes wrong in allocation (i.e., when the return code from + * GrResourceAllocator::assign is bad) + * + * All together this means we should never attempt to draw an op which is missing some + * required GrSurface. + * + * One wrinkle in this plan is that promise images are fulfilled during the gather interval pass. + * If any of the promise images fail at this stage then the allocator is set into an error + * state and all allocations are then scanned for failures during the main allocation pass. + */ +class GrResourceAllocator { +public: + GrResourceAllocator(GrResourceProvider* resourceProvider SkDEBUGCODE(, int numOpsTasks)) + : fResourceProvider(resourceProvider) SkDEBUGCODE(, fNumOpsTasks(numOpsTasks)) {} + + ~GrResourceAllocator(); + + unsigned int curOp() const { return fNumOps; } + void incOps() { fNumOps++; } + + /** Indicates whether a given call to addInterval represents an actual usage of the + * provided proxy. This is mainly here to accomodate deferred proxies attached to opsTasks. + * In that case we need to create an extra long interval for them (due to the upload) but + * don't want to count that usage/reference towards the proxy's recyclability. + */ + enum class ActualUse : bool { + kNo = false, + kYes = true + }; + + // Add a usage interval from 'start' to 'end' inclusive. This is usually used for renderTargets. + // If an existing interval already exists it will be expanded to include the new range. + void addInterval(GrSurfaceProxy*, unsigned int start, unsigned int end, ActualUse actualUse + SkDEBUGCODE(, bool isDirectDstRead = false)); + + enum class AssignError { + kNoError, + kFailedProxyInstantiation + }; + + // Returns true when the opsTasks from 'startIndex' to 'stopIndex' should be executed; + // false when nothing remains to be executed. + // If any proxy fails to instantiate, the AssignError will be set to kFailedProxyInstantiation. + // If this happens, the caller should remove all ops which reference an uninstantiated proxy. + // This is used to execute a portion of the queued opsTasks in order to reduce the total + // amount of GPU resources required. + bool assign(int* startIndex, int* stopIndex, AssignError* outError); + + void determineRecyclability(); + void markEndOfOpsTask(int opsTaskIndex); + +#if GR_ALLOCATION_SPEW + void dumpIntervals(); +#endif + +private: + class Interval; + + // Remove dead intervals from the active list + void expire(unsigned int curIndex); + + bool onOpsTaskBoundary() const; + void forceIntermediateFlush(int* stopIndex); + + // These two methods wrap the interactions with the free pool + void recycleSurface(sk_sp surface); + sk_sp findSurfaceFor(const GrSurfaceProxy* proxy, int minStencilSampleCount); + + struct FreePoolTraits { + static const GrScratchKey& GetKey(const GrSurface& s) { + return s.resourcePriv().getScratchKey(); + } + + static uint32_t Hash(const GrScratchKey& key) { return key.hash(); } + static void OnFree(GrSurface* s) { s->unref(); } + }; + typedef SkTMultiMap FreePoolMultiMap; + + typedef SkTDynamicHash IntvlHash; + + class Interval { + public: + Interval(GrSurfaceProxy* proxy, unsigned int start, unsigned int end) + : fProxy(proxy) + , fProxyID(proxy->uniqueID().asUInt()) + , fStart(start) + , fEnd(end) + , fNext(nullptr) { + SkASSERT(proxy); +#if GR_TRACK_INTERVAL_CREATION + fUniqueID = CreateUniqueID(); + SkDebugf("New intvl %d: proxyID: %d [ %d, %d ]\n", + fUniqueID, proxy->uniqueID().asUInt(), start, end); +#endif + } + + // Used when recycling an interval + void resetTo(GrSurfaceProxy* proxy, unsigned int start, unsigned int end) { + SkASSERT(proxy); + SkASSERT(!fProxy && !fNext); + + fUses = 0; + fProxy = proxy; + fProxyID = proxy->uniqueID().asUInt(); + fStart = start; + fEnd = end; + fNext = nullptr; +#if GR_TRACK_INTERVAL_CREATION + fUniqueID = CreateUniqueID(); + SkDebugf("New intvl %d: proxyID: %d [ %d, %d ]\n", + fUniqueID, proxy->uniqueID().asUInt(), start, end); +#endif + } + + ~Interval() { + SkASSERT(!fAssignedSurface); + } + + const GrSurfaceProxy* proxy() const { return fProxy; } + GrSurfaceProxy* proxy() { return fProxy; } + + unsigned int start() const { return fStart; } + unsigned int end() const { return fEnd; } + + void setNext(Interval* next) { fNext = next; } + const Interval* next() const { return fNext; } + Interval* next() { return fNext; } + + void markAsRecyclable() { fIsRecyclable = true;} + bool isRecyclable() const { return fIsRecyclable; } + + void addUse() { fUses++; } + int uses() { return fUses; } + + void extendEnd(unsigned int newEnd) { + if (newEnd > fEnd) { + fEnd = newEnd; +#if GR_TRACK_INTERVAL_CREATION + SkDebugf("intvl %d: extending from %d to %d\n", fUniqueID, fEnd, newEnd); +#endif + } + } + + void assign(sk_sp); + bool wasAssignedSurface() const { return fAssignedSurface != nullptr; } + sk_sp detachSurface() { return std::move(fAssignedSurface); } + + // for SkTDynamicHash + static const uint32_t& GetKey(const Interval& intvl) { + return intvl.fProxyID; + } + static uint32_t Hash(const uint32_t& key) { return key; } + + private: + sk_sp fAssignedSurface; + GrSurfaceProxy* fProxy; + uint32_t fProxyID; // This is here b.c. DynamicHash requires a ref to the key + unsigned int fStart; + unsigned int fEnd; + Interval* fNext; + unsigned int fUses = 0; + bool fIsRecyclable = false; + +#if GR_TRACK_INTERVAL_CREATION + uint32_t fUniqueID; + + uint32_t CreateUniqueID(); +#endif + }; + + class IntervalList { + public: + IntervalList() = default; + ~IntervalList() { + // The only time we delete an IntervalList is in the GrResourceAllocator dtor. + // Since the arena allocator will clean up for us we don't bother here. + } + + bool empty() const { + SkASSERT(SkToBool(fHead) == SkToBool(fTail)); + return !SkToBool(fHead); + } + const Interval* peekHead() const { return fHead; } + Interval* peekHead() { return fHead; } + Interval* popHead(); + void insertByIncreasingStart(Interval*); + void insertByIncreasingEnd(Interval*); + Interval* detachAll(); + + private: + SkDEBUGCODE(void validate() const;) + + Interval* fHead = nullptr; + Interval* fTail = nullptr; + }; + + // Compositing use cases can create > 80 intervals. + static const int kInitialArenaSize = 128 * sizeof(Interval); + + GrResourceProvider* fResourceProvider; + FreePoolMultiMap fFreePool; // Recently created/used GrSurfaces + IntvlHash fIntvlHash; // All the intervals, hashed by proxyID + + IntervalList fIntvlList; // All the intervals sorted by increasing start + IntervalList fActiveIntvls; // List of live intervals during assignment + // (sorted by increasing end) + unsigned int fNumOps = 0; + SkTArray fEndOfOpsTaskOpIndices; + int fCurOpsTaskIndex = 0; + SkDEBUGCODE(const int fNumOpsTasks = -1;) + + SkDEBUGCODE(bool fAssigned = false;) + + char fStorage[kInitialArenaSize]; + SkArenaAlloc fIntervalAllocator{fStorage, kInitialArenaSize, kInitialArenaSize}; + Interval* fFreeIntervalList = nullptr; + bool fLazyInstantiationError = false; +}; + +#endif // GrResourceAllocator_DEFINED diff --git a/gfx/skia/skia/src/gpu/GrResourceCache.cpp b/gfx/skia/skia/src/gpu/GrResourceCache.cpp new file mode 100644 index 0000000000..6d9a961886 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceCache.cpp @@ -0,0 +1,945 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrResourceCache.h" +#include +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrSingleOwner.h" +#include "include/private/SkTo.h" +#include "include/utils/SkRandom.h" +#include "src/core/SkExchange.h" +#include "src/core/SkMessageBus.h" +#include "src/core/SkOpts.h" +#include "src/core/SkScopeExit.h" +#include "src/core/SkTSort.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpuResourceCacheAccess.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrTextureProxyCacheAccess.h" +#include "src/gpu/GrTracing.h" +#include "src/gpu/SkGr.h" + +DECLARE_SKMESSAGEBUS_MESSAGE(GrUniqueKeyInvalidatedMessage); + +DECLARE_SKMESSAGEBUS_MESSAGE(GrTextureFreedMessage); + +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fSingleOwner);) + +////////////////////////////////////////////////////////////////////////////// + +GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() { + static std::atomic nextType{INHERITED::kInvalidDomain + 1}; + + int32_t type = nextType++; + if (type > SkTo(UINT16_MAX)) { + SK_ABORT("Too many Resource Types"); + } + + return static_cast(type); +} + +GrUniqueKey::Domain GrUniqueKey::GenerateDomain() { + static std::atomic nextDomain{INHERITED::kInvalidDomain + 1}; + + int32_t domain = nextDomain++; + if (domain > SkTo(UINT16_MAX)) { + SK_ABORT("Too many GrUniqueKey Domains"); + } + + return static_cast(domain); +} + +uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) { + return SkOpts::hash(data, size); +} + +////////////////////////////////////////////////////////////////////////////// + +class GrResourceCache::AutoValidate : ::SkNoncopyable { +public: + AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); } + ~AutoValidate() { fCache->validate(); } +private: + GrResourceCache* fCache; +}; + +////////////////////////////////////////////////////////////////////////////// + +inline GrResourceCache::TextureAwaitingUnref::TextureAwaitingUnref() = default; + +inline GrResourceCache::TextureAwaitingUnref::TextureAwaitingUnref(GrTexture* texture) + : fTexture(texture), fNumUnrefs(1) {} + +inline GrResourceCache::TextureAwaitingUnref::TextureAwaitingUnref(TextureAwaitingUnref&& that) { + fTexture = skstd::exchange(that.fTexture, nullptr); + fNumUnrefs = skstd::exchange(that.fNumUnrefs, 0); +} + +inline GrResourceCache::TextureAwaitingUnref& GrResourceCache::TextureAwaitingUnref::operator=( + TextureAwaitingUnref&& that) { + fTexture = skstd::exchange(that.fTexture, nullptr); + fNumUnrefs = skstd::exchange(that.fNumUnrefs, 0); + return *this; +} + +inline GrResourceCache::TextureAwaitingUnref::~TextureAwaitingUnref() { + if (fTexture) { + for (int i = 0; i < fNumUnrefs; ++i) { + fTexture->unref(); + } + } +} + +inline void GrResourceCache::TextureAwaitingUnref::TextureAwaitingUnref::addRef() { ++fNumUnrefs; } + +inline void GrResourceCache::TextureAwaitingUnref::unref() { + SkASSERT(fNumUnrefs > 0); + fTexture->unref(); + --fNumUnrefs; +} + +inline bool GrResourceCache::TextureAwaitingUnref::finished() { return !fNumUnrefs; } + +////////////////////////////////////////////////////////////////////////////// + +GrResourceCache::GrResourceCache(const GrCaps* caps, GrSingleOwner* singleOwner, + uint32_t contextUniqueID) + : fInvalidUniqueKeyInbox(contextUniqueID) + , fFreedTextureInbox(contextUniqueID) + , fContextUniqueID(contextUniqueID) + , fSingleOwner(singleOwner) + , fPreferVRAMUseOverFlushes(caps->preferVRAMUseOverFlushes()) { + SkASSERT(contextUniqueID != SK_InvalidUniqueID); +} + +GrResourceCache::~GrResourceCache() { + this->releaseAll(); +} + +void GrResourceCache::setLimit(size_t bytes) { + fMaxBytes = bytes; + this->purgeAsNeeded(); +} + +void GrResourceCache::insertResource(GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + SkASSERT(resource); + SkASSERT(!this->isInCache(resource)); + SkASSERT(!resource->wasDestroyed()); + SkASSERT(!resource->resourcePriv().isPurgeable()); + + // We must set the timestamp before adding to the array in case the timestamp wraps and we wind + // up iterating over all the resources that already have timestamps. + resource->cacheAccess().setTimestamp(this->getNextTimestamp()); + + this->addToNonpurgeableArray(resource); + + size_t size = resource->gpuMemorySize(); + SkDEBUGCODE(++fCount;) + fBytes += size; +#if GR_CACHE_STATS + fHighWaterCount = SkTMax(this->getResourceCount(), fHighWaterCount); + fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); +#endif + if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) { + ++fBudgetedCount; + fBudgetedBytes += size; + TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used", + fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes); +#if GR_CACHE_STATS + fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount); + fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); +#endif + } + if (resource->resourcePriv().getScratchKey().isValid() && + !resource->getUniqueKey().isValid()) { + SkASSERT(!resource->resourcePriv().refsWrappedObjects()); + fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource); + } + + this->purgeAsNeeded(); +} + +void GrResourceCache::removeResource(GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + this->validate(); + SkASSERT(this->isInCache(resource)); + + size_t size = resource->gpuMemorySize(); + if (resource->resourcePriv().isPurgeable()) { + fPurgeableQueue.remove(resource); + fPurgeableBytes -= size; + } else { + this->removeFromNonpurgeableArray(resource); + } + + SkDEBUGCODE(--fCount;) + fBytes -= size; + if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) { + --fBudgetedCount; + fBudgetedBytes -= size; + TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used", + fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes); + } + + if (resource->resourcePriv().getScratchKey().isValid() && + !resource->getUniqueKey().isValid()) { + fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); + } + if (resource->getUniqueKey().isValid()) { + fUniqueHash.remove(resource->getUniqueKey()); + } + this->validate(); +} + +void GrResourceCache::abandonAll() { + AutoValidate av(this); + + // We need to make sure to free any resources that were waiting on a free message but never + // received one. + fTexturesAwaitingUnref.reset(); + + while (fNonpurgeableResources.count()) { + GrGpuResource* back = *(fNonpurgeableResources.end() - 1); + SkASSERT(!back->wasDestroyed()); + back->cacheAccess().abandon(); + } + + while (fPurgeableQueue.count()) { + GrGpuResource* top = fPurgeableQueue.peek(); + SkASSERT(!top->wasDestroyed()); + top->cacheAccess().abandon(); + } + + SkASSERT(!fScratchMap.count()); + SkASSERT(!fUniqueHash.count()); + SkASSERT(!fCount); + SkASSERT(!this->getResourceCount()); + SkASSERT(!fBytes); + SkASSERT(!fBudgetedCount); + SkASSERT(!fBudgetedBytes); + SkASSERT(!fPurgeableBytes); + SkASSERT(!fTexturesAwaitingUnref.count()); +} + +void GrResourceCache::releaseAll() { + AutoValidate av(this); + + this->processFreedGpuResources(); + + // We need to make sure to free any resources that were waiting on a free message but never + // received one. + fTexturesAwaitingUnref.reset(); + + SkASSERT(fProxyProvider); // better have called setProxyProvider + // We must remove the uniqueKeys from the proxies here. While they possess a uniqueKey + // they also have a raw pointer back to this class (which is presumably going away)! + fProxyProvider->removeAllUniqueKeys(); + + while (fNonpurgeableResources.count()) { + GrGpuResource* back = *(fNonpurgeableResources.end() - 1); + SkASSERT(!back->wasDestroyed()); + back->cacheAccess().release(); + } + + while (fPurgeableQueue.count()) { + GrGpuResource* top = fPurgeableQueue.peek(); + SkASSERT(!top->wasDestroyed()); + top->cacheAccess().release(); + } + + SkASSERT(!fScratchMap.count()); + SkASSERT(!fUniqueHash.count()); + SkASSERT(!fCount); + SkASSERT(!this->getResourceCount()); + SkASSERT(!fBytes); + SkASSERT(!fBudgetedCount); + SkASSERT(!fBudgetedBytes); + SkASSERT(!fPurgeableBytes); + SkASSERT(!fTexturesAwaitingUnref.count()); +} + +void GrResourceCache::refResource(GrGpuResource* resource) { + SkASSERT(resource); + SkASSERT(resource->getContext()->priv().getResourceCache() == this); + if (resource->cacheAccess().hasRef()) { + resource->ref(); + } else { + this->refAndMakeResourceMRU(resource); + } + this->validate(); +} + +class GrResourceCache::AvailableForScratchUse { +public: + AvailableForScratchUse() { } + + bool operator()(const GrGpuResource* resource) const { + SkASSERT(!resource->getUniqueKey().isValid() && + resource->resourcePriv().getScratchKey().isValid()); + + // isScratch() also tests that the resource is budgeted. + if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) { + return false; + } + return true; + } +}; + +GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey) { + SkASSERT(scratchKey.isValid()); + + GrGpuResource* resource = fScratchMap.find(scratchKey, AvailableForScratchUse()); + if (resource) { + this->refAndMakeResourceMRU(resource); + this->validate(); + } + return resource; +} + +void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + SkASSERT(resource->resourcePriv().getScratchKey().isValid()); + if (!resource->getUniqueKey().isValid()) { + fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); + } +} + +void GrResourceCache::removeUniqueKey(GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + // Someone has a ref to this resource in order to have removed the key. When the ref count + // reaches zero we will get a ref cnt notification and figure out what to do with it. + if (resource->getUniqueKey().isValid()) { + SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey())); + fUniqueHash.remove(resource->getUniqueKey()); + } + resource->cacheAccess().removeUniqueKey(); + if (resource->resourcePriv().getScratchKey().isValid()) { + fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource); + } + + // Removing a unique key from a kUnbudgetedCacheable resource would make the resource + // require purging. However, the resource must be ref'ed to get here and therefore can't + // be purgeable. We'll purge it when the refs reach zero. + SkASSERT(!resource->resourcePriv().isPurgeable()); + this->validate(); +} + +void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const GrUniqueKey& newKey) { + ASSERT_SINGLE_OWNER + SkASSERT(resource); + SkASSERT(this->isInCache(resource)); + + // If another resource has the new key, remove its key then install the key on this resource. + if (newKey.isValid()) { + if (GrGpuResource* old = fUniqueHash.find(newKey)) { + // If the old resource using the key is purgeable and is unreachable, then remove it. + if (!old->resourcePriv().getScratchKey().isValid() && + old->resourcePriv().isPurgeable()) { + old->cacheAccess().release(); + } else { + // removeUniqueKey expects an external owner of the resource. + this->removeUniqueKey(sk_ref_sp(old).get()); + } + } + SkASSERT(nullptr == fUniqueHash.find(newKey)); + + // Remove the entry for this resource if it already has a unique key. + if (resource->getUniqueKey().isValid()) { + SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey())); + fUniqueHash.remove(resource->getUniqueKey()); + SkASSERT(nullptr == fUniqueHash.find(resource->getUniqueKey())); + } else { + // 'resource' didn't have a valid unique key before so it is switching sides. Remove it + // from the ScratchMap + if (resource->resourcePriv().getScratchKey().isValid()) { + fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); + } + } + + resource->cacheAccess().setUniqueKey(newKey); + fUniqueHash.add(resource); + } else { + this->removeUniqueKey(resource); + } + + this->validate(); +} + +void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + SkASSERT(resource); + SkASSERT(this->isInCache(resource)); + + if (resource->resourcePriv().isPurgeable()) { + // It's about to become unpurgeable. + fPurgeableBytes -= resource->gpuMemorySize(); + fPurgeableQueue.remove(resource); + this->addToNonpurgeableArray(resource); + } else if (!resource->cacheAccess().hasRef() && + resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) { + SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable > 0); + fNumBudgetedResourcesFlushWillMakePurgeable--; + } + resource->cacheAccess().ref(); + + resource->cacheAccess().setTimestamp(this->getNextTimestamp()); + this->validate(); +} + +void GrResourceCache::notifyRefCntReachedZero(GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + SkASSERT(resource); + SkASSERT(!resource->wasDestroyed()); + SkASSERT(this->isInCache(resource)); + // This resource should always be in the nonpurgeable array when this function is called. It + // will be moved to the queue if it is newly purgeable. + SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource); + +#ifdef SK_DEBUG + // When the timestamp overflows validate() is called. validate() checks that resources in + // the nonpurgeable array are indeed not purgeable. However, the movement from the array to + // the purgeable queue happens just below in this function. So we mark it as an exception. + if (resource->resourcePriv().isPurgeable()) { + fNewlyPurgeableResourceForValidation = resource; + } +#endif + resource->cacheAccess().setTimestamp(this->getNextTimestamp()); + SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr); + + if (!resource->resourcePriv().isPurgeable() && + resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) { + ++fNumBudgetedResourcesFlushWillMakePurgeable; + } + + if (!resource->resourcePriv().isPurgeable()) { + this->validate(); + return; + } + + this->removeFromNonpurgeableArray(resource); + fPurgeableQueue.insert(resource); + resource->cacheAccess().setTimeWhenResourceBecomePurgeable(); + fPurgeableBytes += resource->gpuMemorySize(); + + bool hasUniqueKey = resource->getUniqueKey().isValid(); + + GrBudgetedType budgetedType = resource->resourcePriv().budgetedType(); + + if (budgetedType == GrBudgetedType::kBudgeted) { + // Purge the resource immediately if we're over budget + // Also purge if the resource has neither a valid scratch key nor a unique key. + bool hasKey = resource->resourcePriv().getScratchKey().isValid() || hasUniqueKey; + if (!this->overBudget() && hasKey) { + return; + } + } else { + // We keep unbudgeted resources with a unique key in the purgeable queue of the cache so + // they can be reused again by the image connected to the unique key. + if (hasUniqueKey && budgetedType == GrBudgetedType::kUnbudgetedCacheable) { + return; + } + // Check whether this resource could still be used as a scratch resource. + if (!resource->resourcePriv().refsWrappedObjects() && + resource->resourcePriv().getScratchKey().isValid()) { + // We won't purge an existing resource to make room for this one. + if (this->wouldFit(resource->gpuMemorySize())) { + resource->resourcePriv().makeBudgeted(); + return; + } + } + } + + SkDEBUGCODE(int beforeCount = this->getResourceCount();) + resource->cacheAccess().release(); + // We should at least free this resource, perhaps dependent resources as well. + SkASSERT(this->getResourceCount() < beforeCount); + this->validate(); +} + +void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + SkASSERT(resource); + SkASSERT(this->isInCache(resource)); + + size_t size = resource->gpuMemorySize(); + // Changing from BudgetedType::kUnbudgetedCacheable to another budgeted type could make + // resource become purgeable. However, we should never allow that transition. Wrapped + // resources are the only resources that can be in that state and they aren't allowed to + // transition from one budgeted state to another. + SkDEBUGCODE(bool wasPurgeable = resource->resourcePriv().isPurgeable()); + if (resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) { + ++fBudgetedCount; + fBudgetedBytes += size; +#if GR_CACHE_STATS + fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); + fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount); +#endif + if (!resource->resourcePriv().isPurgeable() && !resource->cacheAccess().hasRef()) { + ++fNumBudgetedResourcesFlushWillMakePurgeable; + } + this->purgeAsNeeded(); + } else { + SkASSERT(resource->resourcePriv().budgetedType() != GrBudgetedType::kUnbudgetedCacheable); + --fBudgetedCount; + fBudgetedBytes -= size; + if (!resource->resourcePriv().isPurgeable() && !resource->cacheAccess().hasRef()) { + --fNumBudgetedResourcesFlushWillMakePurgeable; + } + } + SkASSERT(wasPurgeable == resource->resourcePriv().isPurgeable()); + TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used", + fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes); + + this->validate(); +} + +void GrResourceCache::purgeAsNeeded() { + SkTArray invalidKeyMsgs; + fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs); + if (invalidKeyMsgs.count()) { + SkASSERT(fProxyProvider); + + for (int i = 0; i < invalidKeyMsgs.count(); ++i) { + fProxyProvider->processInvalidUniqueKey(invalidKeyMsgs[i].key(), nullptr, + GrProxyProvider::InvalidateGPUResource::kYes); + SkASSERT(!this->findAndRefUniqueResource(invalidKeyMsgs[i].key())); + } + } + + this->processFreedGpuResources(); + + bool stillOverbudget = this->overBudget(); + while (stillOverbudget && fPurgeableQueue.count()) { + GrGpuResource* resource = fPurgeableQueue.peek(); + SkASSERT(resource->resourcePriv().isPurgeable()); + resource->cacheAccess().release(); + stillOverbudget = this->overBudget(); + } + + this->validate(); +} + +void GrResourceCache::purgeUnlockedResources(bool scratchResourcesOnly) { + if (!scratchResourcesOnly) { + // We could disable maintaining the heap property here, but it would add a lot of + // complexity. Moreover, this is rarely called. + while (fPurgeableQueue.count()) { + GrGpuResource* resource = fPurgeableQueue.peek(); + SkASSERT(resource->resourcePriv().isPurgeable()); + resource->cacheAccess().release(); + } + } else { + // Sort the queue + fPurgeableQueue.sort(); + + // Make a list of the scratch resources to delete + SkTDArray scratchResources; + for (int i = 0; i < fPurgeableQueue.count(); i++) { + GrGpuResource* resource = fPurgeableQueue.at(i); + SkASSERT(resource->resourcePriv().isPurgeable()); + if (!resource->getUniqueKey().isValid()) { + *scratchResources.append() = resource; + } + } + + // Delete the scratch resources. This must be done as a separate pass + // to avoid messing up the sorted order of the queue + for (int i = 0; i < scratchResources.count(); i++) { + scratchResources.getAt(i)->cacheAccess().release(); + } + } + + this->validate(); +} + +void GrResourceCache::purgeResourcesNotUsedSince(GrStdSteadyClock::time_point purgeTime) { + while (fPurgeableQueue.count()) { + const GrStdSteadyClock::time_point resourceTime = + fPurgeableQueue.peek()->cacheAccess().timeWhenResourceBecamePurgeable(); + if (resourceTime >= purgeTime) { + // Resources were given both LRU timestamps and tagged with a frame number when + // they first became purgeable. The LRU timestamp won't change again until the + // resource is made non-purgeable again. So, at this point all the remaining + // resources in the timestamp-sorted queue will have a frame number >= to this + // one. + break; + } + GrGpuResource* resource = fPurgeableQueue.peek(); + SkASSERT(resource->resourcePriv().isPurgeable()); + resource->cacheAccess().release(); + } +} + +void GrResourceCache::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) { + + const size_t tmpByteBudget = SkTMax((size_t)0, fBytes - bytesToPurge); + bool stillOverbudget = tmpByteBudget < fBytes; + + if (preferScratchResources && bytesToPurge < fPurgeableBytes) { + // Sort the queue + fPurgeableQueue.sort(); + + // Make a list of the scratch resources to delete + SkTDArray scratchResources; + size_t scratchByteCount = 0; + for (int i = 0; i < fPurgeableQueue.count() && stillOverbudget; i++) { + GrGpuResource* resource = fPurgeableQueue.at(i); + SkASSERT(resource->resourcePriv().isPurgeable()); + if (!resource->getUniqueKey().isValid()) { + *scratchResources.append() = resource; + scratchByteCount += resource->gpuMemorySize(); + stillOverbudget = tmpByteBudget < fBytes - scratchByteCount; + } + } + + // Delete the scratch resources. This must be done as a separate pass + // to avoid messing up the sorted order of the queue + for (int i = 0; i < scratchResources.count(); i++) { + scratchResources.getAt(i)->cacheAccess().release(); + } + stillOverbudget = tmpByteBudget < fBytes; + + this->validate(); + } + + // Purge any remaining resources in LRU order + if (stillOverbudget) { + const size_t cachedByteCount = fMaxBytes; + fMaxBytes = tmpByteBudget; + this->purgeAsNeeded(); + fMaxBytes = cachedByteCount; + } +} +bool GrResourceCache::requestsFlush() const { + return this->overBudget() && !fPurgeableQueue.count() && + fNumBudgetedResourcesFlushWillMakePurgeable > 0; +} + + +void GrResourceCache::insertDelayedTextureUnref(GrTexture* texture) { + texture->ref(); + uint32_t id = texture->uniqueID().asUInt(); + if (auto* data = fTexturesAwaitingUnref.find(id)) { + data->addRef(); + } else { + fTexturesAwaitingUnref.set(id, {texture}); + } +} + +void GrResourceCache::processFreedGpuResources() { + if (!fTexturesAwaitingUnref.count()) { + return; + } + + SkTArray msgs; + fFreedTextureInbox.poll(&msgs); + for (int i = 0; i < msgs.count(); ++i) { + SkASSERT(msgs[i].fOwningUniqueID == fContextUniqueID); + uint32_t id = msgs[i].fTexture->uniqueID().asUInt(); + TextureAwaitingUnref* info = fTexturesAwaitingUnref.find(id); + // If we called release or abandon on the GrContext we will have already released our ref on + // the GrGpuResource. If then the message arrives before the actual GrContext gets destroyed + // we will try to process the message when we destroy the GrContext. This protects us from + // trying to unref the resource twice. + if (info) { + info->unref(); + if (info->finished()) { + fTexturesAwaitingUnref.remove(id); + } + } + } +} + +void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) { + int index = fNonpurgeableResources.count(); + *fNonpurgeableResources.append() = resource; + *resource->cacheAccess().accessCacheIndex() = index; +} + +void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) { + int* index = resource->cacheAccess().accessCacheIndex(); + // Fill the whole we will create in the array with the tail object, adjust its index, and + // then pop the array + GrGpuResource* tail = *(fNonpurgeableResources.end() - 1); + SkASSERT(fNonpurgeableResources[*index] == resource); + fNonpurgeableResources[*index] = tail; + *tail->cacheAccess().accessCacheIndex() = *index; + fNonpurgeableResources.pop(); + SkDEBUGCODE(*index = -1); +} + +uint32_t GrResourceCache::getNextTimestamp() { + // If we wrap then all the existing resources will appear older than any resources that get + // a timestamp after the wrap. + if (0 == fTimestamp) { + int count = this->getResourceCount(); + if (count) { + // Reset all the timestamps. We sort the resources by timestamp and then assign + // sequential timestamps beginning with 0. This is O(n*lg(n)) but it should be extremely + // rare. + SkTDArray sortedPurgeableResources; + sortedPurgeableResources.setReserve(fPurgeableQueue.count()); + + while (fPurgeableQueue.count()) { + *sortedPurgeableResources.append() = fPurgeableQueue.peek(); + fPurgeableQueue.pop(); + } + + SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1, + CompareTimestamp); + + // Pick resources out of the purgeable and non-purgeable arrays based on lowest + // timestamp and assign new timestamps. + int currP = 0; + int currNP = 0; + while (currP < sortedPurgeableResources.count() && + currNP < fNonpurgeableResources.count()) { + uint32_t tsP = sortedPurgeableResources[currP]->cacheAccess().timestamp(); + uint32_t tsNP = fNonpurgeableResources[currNP]->cacheAccess().timestamp(); + SkASSERT(tsP != tsNP); + if (tsP < tsNP) { + sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++); + } else { + // Correct the index in the nonpurgeable array stored on the resource post-sort. + *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP; + fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++); + } + } + + // The above loop ended when we hit the end of one array. Finish the other one. + while (currP < sortedPurgeableResources.count()) { + sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++); + } + while (currNP < fNonpurgeableResources.count()) { + *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP; + fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++); + } + + // Rebuild the queue. + for (int i = 0; i < sortedPurgeableResources.count(); ++i) { + fPurgeableQueue.insert(sortedPurgeableResources[i]); + } + + this->validate(); + SkASSERT(count == this->getResourceCount()); + + // count should be the next timestamp we return. + SkASSERT(fTimestamp == SkToU32(count)); + } + } + return fTimestamp++; +} + +void GrResourceCache::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + for (int i = 0; i < fNonpurgeableResources.count(); ++i) { + fNonpurgeableResources[i]->dumpMemoryStatistics(traceMemoryDump); + } + for (int i = 0; i < fPurgeableQueue.count(); ++i) { + fPurgeableQueue.at(i)->dumpMemoryStatistics(traceMemoryDump); + } +} + +#if GR_CACHE_STATS +void GrResourceCache::getStats(Stats* stats) const { + stats->reset(); + + stats->fTotal = this->getResourceCount(); + stats->fNumNonPurgeable = fNonpurgeableResources.count(); + stats->fNumPurgeable = fPurgeableQueue.count(); + + for (int i = 0; i < fNonpurgeableResources.count(); ++i) { + stats->update(fNonpurgeableResources[i]); + } + for (int i = 0; i < fPurgeableQueue.count(); ++i) { + stats->update(fPurgeableQueue.at(i)); + } +} + +#if GR_TEST_UTILS +void GrResourceCache::dumpStats(SkString* out) const { + this->validate(); + + Stats stats; + + this->getStats(&stats); + + float byteUtilization = (100.f * fBudgetedBytes) / fMaxBytes; + + out->appendf("Budget: %d bytes\n", (int)fMaxBytes); + out->appendf("\t\tEntry Count: current %d" + " (%d budgeted, %d wrapped, %d locked, %d scratch), high %d\n", + stats.fTotal, fBudgetedCount, stats.fWrapped, stats.fNumNonPurgeable, + stats.fScratch, fHighWaterCount); + out->appendf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full, %d unbudgeted) high %d\n", + SkToInt(fBytes), SkToInt(fBudgetedBytes), byteUtilization, + SkToInt(stats.fUnbudgetedSize), SkToInt(fHighWaterBytes)); +} + +void GrResourceCache::dumpStatsKeyValuePairs(SkTArray* keys, + SkTArray* values) const { + this->validate(); + + Stats stats; + this->getStats(&stats); + + keys->push_back(SkString("gpu_cache_purgable_entries")); values->push_back(stats.fNumPurgeable); +} +#endif + +#endif + +#ifdef SK_DEBUG +void GrResourceCache::validate() const { + // Reduce the frequency of validations for large resource counts. + static SkRandom gRandom; + int mask = (SkNextPow2(fCount + 1) >> 5) - 1; + if (~mask && (gRandom.nextU() & mask)) { + return; + } + + struct Stats { + size_t fBytes; + int fBudgetedCount; + size_t fBudgetedBytes; + int fLocked; + int fScratch; + int fCouldBeScratch; + int fContent; + const ScratchMap* fScratchMap; + const UniqueHash* fUniqueHash; + + Stats(const GrResourceCache* cache) { + memset(this, 0, sizeof(*this)); + fScratchMap = &cache->fScratchMap; + fUniqueHash = &cache->fUniqueHash; + } + + void update(GrGpuResource* resource) { + fBytes += resource->gpuMemorySize(); + + if (!resource->resourcePriv().isPurgeable()) { + ++fLocked; + } + + const GrScratchKey& scratchKey = resource->resourcePriv().getScratchKey(); + const GrUniqueKey& uniqueKey = resource->getUniqueKey(); + + if (resource->cacheAccess().isScratch()) { + SkASSERT(!uniqueKey.isValid()); + ++fScratch; + SkASSERT(fScratchMap->countForKey(scratchKey)); + SkASSERT(!resource->resourcePriv().refsWrappedObjects()); + } else if (scratchKey.isValid()) { + SkASSERT(GrBudgetedType::kBudgeted != resource->resourcePriv().budgetedType() || + uniqueKey.isValid()); + if (!uniqueKey.isValid()) { + ++fCouldBeScratch; + SkASSERT(fScratchMap->countForKey(scratchKey)); + } + SkASSERT(!resource->resourcePriv().refsWrappedObjects()); + } + if (uniqueKey.isValid()) { + ++fContent; + SkASSERT(fUniqueHash->find(uniqueKey) == resource); + SkASSERT(GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType() || + resource->resourcePriv().refsWrappedObjects()); + + if (scratchKey.isValid()) { + SkASSERT(!fScratchMap->has(resource, scratchKey)); + } + } + + if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) { + ++fBudgetedCount; + fBudgetedBytes += resource->gpuMemorySize(); + } + } + }; + + { + ScratchMap::ConstIter iter(&fScratchMap); + + int count = 0; + for ( ; !iter.done(); ++iter) { + const GrGpuResource* resource = *iter; + SkASSERT(resource->resourcePriv().getScratchKey().isValid()); + SkASSERT(!resource->getUniqueKey().isValid()); + count++; + } + SkASSERT(count == fScratchMap.count()); // ensure the iterator is working correctly + } + + Stats stats(this); + size_t purgeableBytes = 0; + int numBudgetedResourcesFlushWillMakePurgeable = 0; + + for (int i = 0; i < fNonpurgeableResources.count(); ++i) { + SkASSERT(!fNonpurgeableResources[i]->resourcePriv().isPurgeable() || + fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]); + SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i); + SkASSERT(!fNonpurgeableResources[i]->wasDestroyed()); + if (fNonpurgeableResources[i]->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted && + !fNonpurgeableResources[i]->cacheAccess().hasRef() && + fNewlyPurgeableResourceForValidation != fNonpurgeableResources[i]) { + ++numBudgetedResourcesFlushWillMakePurgeable; + } + stats.update(fNonpurgeableResources[i]); + } + for (int i = 0; i < fPurgeableQueue.count(); ++i) { + SkASSERT(fPurgeableQueue.at(i)->resourcePriv().isPurgeable()); + SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i); + SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed()); + stats.update(fPurgeableQueue.at(i)); + purgeableBytes += fPurgeableQueue.at(i)->gpuMemorySize(); + } + + SkASSERT(fCount == this->getResourceCount()); + SkASSERT(fBudgetedCount <= fCount); + SkASSERT(fBudgetedBytes <= fBytes); + SkASSERT(stats.fBytes == fBytes); + SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable == + numBudgetedResourcesFlushWillMakePurgeable); + SkASSERT(stats.fBudgetedBytes == fBudgetedBytes); + SkASSERT(stats.fBudgetedCount == fBudgetedCount); + SkASSERT(purgeableBytes == fPurgeableBytes); +#if GR_CACHE_STATS + SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount); + SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes); + SkASSERT(fBytes <= fHighWaterBytes); + SkASSERT(fCount <= fHighWaterCount); + SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes); + SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount); +#endif + SkASSERT(stats.fContent == fUniqueHash.count()); + SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count()); + + // This assertion is not currently valid because we can be in recursive notifyCntReachedZero() + // calls. This will be fixed when subresource registration is explicit. + // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount; + // SkASSERT(!overBudget || locked == count || fPurging); +} + +bool GrResourceCache::isInCache(const GrGpuResource* resource) const { + int index = *resource->cacheAccess().accessCacheIndex(); + if (index < 0) { + return false; + } + if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) { + return true; + } + if (index < fNonpurgeableResources.count() && fNonpurgeableResources[index] == resource) { + return true; + } + SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache."); + return false; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrResourceCache.h b/gfx/skia/skia/src/gpu/GrResourceCache.h new file mode 100644 index 0000000000..de554b429e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceCache.h @@ -0,0 +1,448 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrResourceCache_DEFINED +#define GrResourceCache_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrGpuResource.h" +#include "include/private/GrResourceKey.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTHash.h" +#include "src/core/SkMessageBus.h" +#include "src/core/SkTDPQueue.h" +#include "src/core/SkTInternalLList.h" +#include "src/core/SkTMultiMap.h" +#include "src/gpu/GrGpuResourceCacheAccess.h" +#include "src/gpu/GrGpuResourcePriv.h" + +class GrCaps; +class GrProxyProvider; +class SkString; +class SkTraceMemoryDump; +class GrSingleOwner; + +struct GrTextureFreedMessage { + GrTexture* fTexture; + uint32_t fOwningUniqueID; +}; + +static inline bool SkShouldPostMessageToBus( + const GrTextureFreedMessage& msg, uint32_t msgBusUniqueID) { + // The inbox's ID is the unique ID of the owning GrContext. + return msgBusUniqueID == msg.fOwningUniqueID; +} + +/** + * Manages the lifetime of all GrGpuResource instances. + * + * Resources may have optionally have two types of keys: + * 1) A scratch key. This is for resources whose allocations are cached but not their contents. + * Multiple resources can share the same scratch key. This is so a caller can have two + * resource instances with the same properties (e.g. multipass rendering that ping-pongs + * between two temporary surfaces). The scratch key is set at resource creation time and + * should never change. Resources need not have a scratch key. + * 2) A unique key. This key's meaning is specific to the domain that created the key. Only one + * resource may have a given unique key. The unique key can be set, cleared, or changed + * anytime after resource creation. + * + * A unique key always takes precedence over a scratch key when a resource has both types of keys. + * If a resource has neither key type then it will be deleted as soon as the last reference to it + * is dropped. + */ +class GrResourceCache { +public: + GrResourceCache(const GrCaps*, GrSingleOwner* owner, uint32_t contextUniqueID); + ~GrResourceCache(); + + // Default maximum number of bytes of gpu memory of budgeted resources in the cache. + static const size_t kDefaultMaxSize = 96 * (1 << 20); + + /** Used to access functionality needed by GrGpuResource for lifetime management. */ + class ResourceAccess; + ResourceAccess resourceAccess(); + + /** Unique ID of the owning GrContext. */ + uint32_t contextUniqueID() const { return fContextUniqueID; } + + /** Sets the max gpu memory byte size of the cache. */ + void setLimit(size_t bytes); + + /** + * Returns the number of resources. + */ + int getResourceCount() const { + return fPurgeableQueue.count() + fNonpurgeableResources.count(); + } + + /** + * Returns the number of resources that count against the budget. + */ + int getBudgetedResourceCount() const { return fBudgetedCount; } + + /** + * Returns the number of bytes consumed by resources. + */ + size_t getResourceBytes() const { return fBytes; } + + /** + * Returns the number of bytes held by unlocked reosources which are available for purging. + */ + size_t getPurgeableBytes() const { return fPurgeableBytes; } + + /** + * Returns the number of bytes consumed by budgeted resources. + */ + size_t getBudgetedResourceBytes() const { return fBudgetedBytes; } + + /** + * Returns the number of bytes consumed by cached resources. + */ + size_t getMaxResourceBytes() const { return fMaxBytes; } + + /** + * Abandons the backend API resources owned by all GrGpuResource objects and removes them from + * the cache. + */ + void abandonAll(); + + /** + * Releases the backend API resources owned by all GrGpuResource objects and removes them from + * the cache. + */ + void releaseAll(); + + /** + * Find a resource that matches a scratch key. + */ + GrGpuResource* findAndRefScratchResource(const GrScratchKey& scratchKey); + +#ifdef SK_DEBUG + // This is not particularly fast and only used for validation, so debug only. + int countScratchEntriesForKey(const GrScratchKey& scratchKey) const { + return fScratchMap.countForKey(scratchKey); + } +#endif + + /** + * Find a resource that matches a unique key. + */ + GrGpuResource* findAndRefUniqueResource(const GrUniqueKey& key) { + GrGpuResource* resource = fUniqueHash.find(key); + if (resource) { + this->refAndMakeResourceMRU(resource); + } + return resource; + } + + /** + * Query whether a unique key exists in the cache. + */ + bool hasUniqueKey(const GrUniqueKey& key) const { + return SkToBool(fUniqueHash.find(key)); + } + + /** Purges resources to become under budget and processes resources with invalidated unique + keys. */ + void purgeAsNeeded(); + + /** Purges all resources that don't have external owners. */ + void purgeAllUnlocked() { this->purgeUnlockedResources(false); } + + // Purge unlocked resources. If 'scratchResourcesOnly' is true the purgeable resources + // containing persistent data are spared. If it is false then all purgeable resources will + // be deleted. + void purgeUnlockedResources(bool scratchResourcesOnly); + + /** Purge all resources not used since the passed in time. */ + void purgeResourcesNotUsedSince(GrStdSteadyClock::time_point); + + bool overBudget() const { return fBudgetedBytes > fMaxBytes; } + + /** + * Purge unlocked resources from the cache until the the provided byte count has been reached + * or we have purged all unlocked resources. The default policy is to purge in LRU order, but + * can be overridden to prefer purging scratch resources (in LRU order) prior to purging other + * resource types. + * + * @param maxBytesToPurge the desired number of bytes to be purged. + * @param preferScratchResources If true scratch resources will be purged prior to other + * resource types. + */ + void purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources); + + /** Returns true if the cache would like a flush to occur in order to make more resources + purgeable. */ + bool requestsFlush() const; + + /** Maintain a ref to this texture until we receive a GrTextureFreedMessage. */ + void insertDelayedTextureUnref(GrTexture*); + +#if GR_CACHE_STATS + struct Stats { + int fTotal; + int fNumPurgeable; + int fNumNonPurgeable; + + int fScratch; + int fWrapped; + size_t fUnbudgetedSize; + + Stats() { this->reset(); } + + void reset() { + fTotal = 0; + fNumPurgeable = 0; + fNumNonPurgeable = 0; + fScratch = 0; + fWrapped = 0; + fUnbudgetedSize = 0; + } + + void update(GrGpuResource* resource) { + if (resource->cacheAccess().isScratch()) { + ++fScratch; + } + if (resource->resourcePriv().refsWrappedObjects()) { + ++fWrapped; + } + if (GrBudgetedType::kBudgeted != resource->resourcePriv().budgetedType()) { + fUnbudgetedSize += resource->gpuMemorySize(); + } + } + }; + + void getStats(Stats*) const; + +#if GR_TEST_UTILS + void dumpStats(SkString*) const; + + void dumpStatsKeyValuePairs(SkTArray* keys, SkTArray* value) const; +#endif + +#endif + +#ifdef SK_DEBUG + int countUniqueKeysWithTag(const char* tag) const; +#endif + + // This function is for unit testing and is only defined in test tools. + void changeTimestamp(uint32_t newTimestamp); + + // Enumerates all cached resources and dumps their details to traceMemoryDump. + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const; + + void setProxyProvider(GrProxyProvider* proxyProvider) { fProxyProvider = proxyProvider; } + +private: + /////////////////////////////////////////////////////////////////////////// + /// @name Methods accessible via ResourceAccess + //// + void insertResource(GrGpuResource*); + void removeResource(GrGpuResource*); + void notifyRefCntReachedZero(GrGpuResource*); + void changeUniqueKey(GrGpuResource*, const GrUniqueKey&); + void removeUniqueKey(GrGpuResource*); + void willRemoveScratchKey(const GrGpuResource*); + void didChangeBudgetStatus(GrGpuResource*); + void refResource(GrGpuResource* resource); + /// @} + + void refAndMakeResourceMRU(GrGpuResource*); + void processFreedGpuResources(); + void addToNonpurgeableArray(GrGpuResource*); + void removeFromNonpurgeableArray(GrGpuResource*); + + bool wouldFit(size_t bytes) const { return fBudgetedBytes+bytes <= fMaxBytes; } + + uint32_t getNextTimestamp(); + +#ifdef SK_DEBUG + bool isInCache(const GrGpuResource* r) const; + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate; + + class AvailableForScratchUse; + + struct ScratchMapTraits { + static const GrScratchKey& GetKey(const GrGpuResource& r) { + return r.resourcePriv().getScratchKey(); + } + + static uint32_t Hash(const GrScratchKey& key) { return key.hash(); } + static void OnFree(GrGpuResource*) { } + }; + typedef SkTMultiMap ScratchMap; + + struct UniqueHashTraits { + static const GrUniqueKey& GetKey(const GrGpuResource& r) { return r.getUniqueKey(); } + + static uint32_t Hash(const GrUniqueKey& key) { return key.hash(); } + }; + typedef SkTDynamicHash UniqueHash; + + class TextureAwaitingUnref { + public: + TextureAwaitingUnref(); + TextureAwaitingUnref(GrTexture* texture); + TextureAwaitingUnref(const TextureAwaitingUnref&) = delete; + TextureAwaitingUnref& operator=(const TextureAwaitingUnref&) = delete; + TextureAwaitingUnref(TextureAwaitingUnref&&); + TextureAwaitingUnref& operator=(TextureAwaitingUnref&&); + ~TextureAwaitingUnref(); + void addRef(); + void unref(); + bool finished(); + + private: + GrTexture* fTexture = nullptr; + int fNumUnrefs = 0; + }; + using TexturesAwaitingUnref = SkTHashMap; + + static bool CompareTimestamp(GrGpuResource* const& a, GrGpuResource* const& b) { + return a->cacheAccess().timestamp() < b->cacheAccess().timestamp(); + } + + static int* AccessResourceIndex(GrGpuResource* const& res) { + return res->cacheAccess().accessCacheIndex(); + } + + typedef SkMessageBus::Inbox InvalidUniqueKeyInbox; + typedef SkMessageBus::Inbox FreedTextureInbox; + typedef SkTDPQueue PurgeableQueue; + typedef SkTDArray ResourceArray; + + GrProxyProvider* fProxyProvider = nullptr; + // Whenever a resource is added to the cache or the result of a cache lookup, fTimestamp is + // assigned as the resource's timestamp and then incremented. fPurgeableQueue orders the + // purgeable resources by this value, and thus is used to purge resources in LRU order. + uint32_t fTimestamp = 0; + PurgeableQueue fPurgeableQueue; + ResourceArray fNonpurgeableResources; + + // This map holds all resources that can be used as scratch resources. + ScratchMap fScratchMap; + // This holds all resources that have unique keys. + UniqueHash fUniqueHash; + + // our budget, used in purgeAsNeeded() + size_t fMaxBytes = kDefaultMaxSize; + +#if GR_CACHE_STATS + int fHighWaterCount = 0; + size_t fHighWaterBytes = 0; + int fBudgetedHighWaterCount = 0; + size_t fBudgetedHighWaterBytes = 0; +#endif + + // our current stats for all resources + SkDEBUGCODE(int fCount = 0;) + size_t fBytes = 0; + + // our current stats for resources that count against the budget + int fBudgetedCount = 0; + size_t fBudgetedBytes = 0; + size_t fPurgeableBytes = 0; + int fNumBudgetedResourcesFlushWillMakePurgeable = 0; + + InvalidUniqueKeyInbox fInvalidUniqueKeyInbox; + FreedTextureInbox fFreedTextureInbox; + TexturesAwaitingUnref fTexturesAwaitingUnref; + + uint32_t fContextUniqueID = SK_InvalidUniqueID; + GrSingleOwner* fSingleOwner = nullptr; + + // This resource is allowed to be in the nonpurgeable array for the sake of validate() because + // we're in the midst of converting it to purgeable status. + SkDEBUGCODE(GrGpuResource* fNewlyPurgeableResourceForValidation = nullptr;) + + bool fPreferVRAMUseOverFlushes = false; +}; + +class GrResourceCache::ResourceAccess { +private: + ResourceAccess(GrResourceCache* cache) : fCache(cache) { } + ResourceAccess(const ResourceAccess& that) : fCache(that.fCache) { } + ResourceAccess& operator=(const ResourceAccess&); // unimpl + + /** + * Insert a resource into the cache. + */ + void insertResource(GrGpuResource* resource) { fCache->insertResource(resource); } + + /** + * Removes a resource from the cache. + */ + void removeResource(GrGpuResource* resource) { fCache->removeResource(resource); } + + /** + * Adds a ref to a resource with proper tracking if the resource has 0 refs prior to + * adding the ref. + */ + void refResource(GrGpuResource* resource) { fCache->refResource(resource); } + + /** + * Notifications that should be sent to the cache when the ref/io cnt status of resources + * changes. + */ + enum RefNotificationFlags { + /** All types of refs on the resource have reached zero. */ + kAllCntsReachedZero_RefNotificationFlag = 0x1, + /** The normal (not pending IO type) ref cnt has reached zero. */ + kRefCntReachedZero_RefNotificationFlag = 0x2, + }; + /** + * Called by GrGpuResources when they detect that their ref cnt has reached zero. + */ + void notifyRefCntReachedZero(GrGpuResource* resource) { + fCache->notifyRefCntReachedZero(resource); + } + + /** + * Called by GrGpuResources to change their unique keys. + */ + void changeUniqueKey(GrGpuResource* resource, const GrUniqueKey& newKey) { + fCache->changeUniqueKey(resource, newKey); + } + + /** + * Called by a GrGpuResource to remove its unique key. + */ + void removeUniqueKey(GrGpuResource* resource) { fCache->removeUniqueKey(resource); } + + /** + * Called by a GrGpuResource when it removes its scratch key. + */ + void willRemoveScratchKey(const GrGpuResource* resource) { + fCache->willRemoveScratchKey(resource); + } + + /** + * Called by GrGpuResources when they change from budgeted to unbudgeted or vice versa. + */ + void didChangeBudgetStatus(GrGpuResource* resource) { fCache->didChangeBudgetStatus(resource); } + + // No taking addresses of this type. + const ResourceAccess* operator&() const; + ResourceAccess* operator&(); + + GrResourceCache* fCache; + + friend class GrGpuResource; // To access all the proxy inline methods. + friend class GrResourceCache; // To create this type. +}; + +inline GrResourceCache::ResourceAccess GrResourceCache::resourceAccess() { + return ResourceAccess(this); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrResourceHandle.h b/gfx/skia/skia/src/gpu/GrResourceHandle.h new file mode 100644 index 0000000000..30440a7125 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceHandle.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. +*/ + +#ifndef GrResourceHandle_DEFINED +#define GrResourceHandle_DEFINED + +#include "include/core/SkTypes.h" + +// Opaque handle to a resource. Users should always use the macro below to create a specific +// template instantiation of GrResourceHandle. +template class GrResourceHandle { +public: + GrResourceHandle(int value) : fValue(value) { + SkASSERT(this->isValid()); + } + + GrResourceHandle() : fValue(kInvalid_ResourceHandle) {} + + bool operator==(const GrResourceHandle& other) const { return other.fValue == fValue; } + bool isValid() const { return kInvalid_ResourceHandle != fValue; } + int toIndex() const { SkASSERT(this->isValid()); return fValue; } + +private: + static const int kInvalid_ResourceHandle = -1; + int fValue; +}; + +// Creates a type "name", which is a specfic template instantiation of GrResourceHandle. +#define GR_DEFINE_RESOURCE_HANDLE_CLASS(name) \ + struct name##Kind {}; \ + using name = GrResourceHandle; +#endif diff --git a/gfx/skia/skia/src/gpu/GrResourceProvider.cpp b/gfx/skia/skia/src/gpu/GrResourceProvider.cpp new file mode 100644 index 0000000000..9ba9505a4a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceProvider.cpp @@ -0,0 +1,581 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrResourceProvider.h" + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrContext.h" +#include "include/private/GrResourceKey.h" +#include "include/private/GrSingleOwner.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrPath.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/SkGr.h" + +const uint32_t GrResourceProvider::kMinScratchTextureSize = 16; + +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fSingleOwner);) + +GrResourceProvider::GrResourceProvider(GrGpu* gpu, GrResourceCache* cache, GrSingleOwner* owner) + : fCache(cache) + , fGpu(gpu) +#ifdef SK_DEBUG + , fSingleOwner(owner) +#endif +{ + fCaps = sk_ref_sp(fGpu->caps()); +} + +sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrColorType colorType, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + const GrMipLevel texels[], + int mipLevelCount) { + ASSERT_SINGLE_OWNER + + SkASSERT(mipLevelCount > 0); + + if (this->isAbandoned()) { + return nullptr; + } + + GrMipMapped mipMapped = mipLevelCount > 1 ? GrMipMapped::kYes : GrMipMapped::kNo; + if (!fCaps->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, renderable, + renderTargetSampleCnt, mipMapped)) { + return nullptr; + } + // Current rule is that you can provide no level data, just the base, or all the levels. + bool hasPixels = mipLevelCount && texels[0].fPixels; + auto scratch = this->getExactScratch(desc, format, renderable, renderTargetSampleCnt, budgeted, + mipMapped, isProtected); + if (scratch) { + if (!hasPixels) { + return scratch; + } + return this->writePixels(std::move(scratch), colorType, {desc.fWidth, desc.fHeight}, texels, + mipLevelCount); + } + SkAutoSTMalloc<14, GrMipLevel> tmpTexels; + SkAutoSTArray<14, std::unique_ptr> tmpDatas; + GrColorType tempColorType = GrColorType::kUnknown; + if (hasPixels) { + tempColorType = this->prepareLevels(format, colorType, {desc.fWidth, desc.fHeight}, texels, + mipLevelCount, &tmpTexels, &tmpDatas); + if (tempColorType == GrColorType::kUnknown) { + return nullptr; + } + } + return fGpu->createTexture(desc, format, renderable, renderTargetSampleCnt, budgeted, + isProtected, colorType, tempColorType, tmpTexels.get(), + mipLevelCount); +} + +sk_sp GrResourceProvider::getExactScratch(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrMipMapped mipMapped, + GrProtected isProtected) { + sk_sp tex(this->refScratchTexture(desc, format, renderable, renderTargetSampleCnt, + mipMapped, isProtected)); + if (tex && SkBudgeted::kNo == budgeted) { + tex->resourcePriv().makeUnbudgeted(); + } + + return tex; +} + +sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrColorType colorType, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + SkBackingFit fit, + GrProtected isProtected, + const GrMipLevel& mipLevel) { + ASSERT_SINGLE_OWNER + + if (!mipLevel.fPixels) { + return nullptr; + } + + if (SkBackingFit::kApprox == fit) { + if (this->isAbandoned()) { + return nullptr; + } + if (!fCaps->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, + renderable, renderTargetSampleCnt, GrMipMapped::kNo)) { + return nullptr; + } + + auto tex = this->createApproxTexture(desc, format, renderable, renderTargetSampleCnt, + isProtected); + if (!tex) { + return nullptr; + } + return this->writePixels(std::move(tex), colorType, {desc.fWidth, desc.fHeight}, &mipLevel, + 1); + } else { + return this->createTexture(desc, format, colorType, renderable, renderTargetSampleCnt, + budgeted, isProtected, &mipLevel, 1); + } +} + +sk_sp GrResourceProvider::createCompressedTexture(int width, int height, + const GrBackendFormat& format, + SkImage::CompressionType compression, + SkBudgeted budgeted, SkData* data) { + ASSERT_SINGLE_OWNER + if (this->isAbandoned()) { + return nullptr; + } + return fGpu->createCompressedTexture(width, height, format, compression, budgeted, data->data(), + data->size()); +} + +sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrMipMapped mipMapped, + SkBudgeted budgeted, + GrProtected isProtected) { + ASSERT_SINGLE_OWNER + if (this->isAbandoned()) { + return nullptr; + } + + if (!fCaps->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, renderable, + renderTargetSampleCnt, mipMapped)) { + return nullptr; + } + + // Compressed textures are read-only so they don't support re-use for scratch. + // TODO: Support GrMipMapped::kYes in scratch texture lookup here. + if (!GrPixelConfigIsCompressed(desc.fConfig)) { + sk_sp tex = this->getExactScratch( + desc, format, renderable, renderTargetSampleCnt, budgeted, mipMapped, isProtected); + if (tex) { + return tex; + } + } + + return fGpu->createTexture(desc, format, renderable, renderTargetSampleCnt, mipMapped, budgeted, + isProtected); +} + +// Map 'value' to a larger multiple of 2. Values <= 'kMagicTol' will pop up to +// the next power of 2. Those above 'kMagicTol' will only go up half the floor power of 2. +uint32_t GrResourceProvider::MakeApprox(uint32_t value) { + static const int kMagicTol = 1024; + + value = SkTMax(kMinScratchTextureSize, value); + + if (SkIsPow2(value)) { + return value; + } + + uint32_t ceilPow2 = GrNextPow2(value); + if (value <= kMagicTol) { + return ceilPow2; + } + + uint32_t floorPow2 = ceilPow2 >> 1; + uint32_t mid = floorPow2 + (floorPow2 >> 1); + + if (value <= mid) { + return mid; + } + + return ceilPow2; +} + +sk_sp GrResourceProvider::createApproxTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrProtected isProtected) { + ASSERT_SINGLE_OWNER + + if (this->isAbandoned()) { + return nullptr; + } + + // Currently we don't recycle compressed textures as scratch. + if (GrPixelConfigIsCompressed(desc.fConfig)) { + return nullptr; + } + + if (!fCaps->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, renderable, + renderTargetSampleCnt, GrMipMapped::kNo)) { + return nullptr; + } + + // bin by some multiple or power of 2 with a reasonable min + GrSurfaceDesc copyDesc(desc); + copyDesc.fWidth = MakeApprox(desc.fWidth); + copyDesc.fHeight = MakeApprox(desc.fHeight); + + if (auto tex = this->refScratchTexture(copyDesc, format, renderable, renderTargetSampleCnt, + GrMipMapped::kNo, isProtected)) { + return tex; + } + + return fGpu->createTexture(copyDesc, format, renderable, renderTargetSampleCnt, + GrMipMapped::kNo, SkBudgeted::kYes, isProtected); +} + +sk_sp GrResourceProvider::refScratchTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrMipMapped mipMapped, + GrProtected isProtected) { + ASSERT_SINGLE_OWNER + SkASSERT(!this->isAbandoned()); + SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig)); + SkASSERT(fCaps->validateSurfaceParams({desc.fWidth, desc.fHeight}, format, desc.fConfig, + renderable, renderTargetSampleCnt, GrMipMapped::kNo)); + + // We could make initial clears work with scratch textures but it is a rare case so we just opt + // to fall back to making a new texture. + if (fGpu->caps()->reuseScratchTextures() || renderable == GrRenderable::kYes) { + GrScratchKey key; + GrTexturePriv::ComputeScratchKey(desc.fConfig, desc.fWidth, desc.fHeight, renderable, + renderTargetSampleCnt, mipMapped, isProtected, &key); + GrGpuResource* resource = fCache->findAndRefScratchResource(key); + if (resource) { + fGpu->stats()->incNumScratchTexturesReused(); + GrSurface* surface = static_cast(resource); + return sk_sp(surface->asTexture()); + } + } + + return nullptr; +} + +sk_sp GrResourceProvider::wrapBackendTexture(const GrBackendTexture& tex, + GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable, + GrIOType ioType) { + ASSERT_SINGLE_OWNER + if (this->isAbandoned()) { + return nullptr; + } + return fGpu->wrapBackendTexture(tex, colorType, ownership, cacheable, ioType); +} + +sk_sp GrResourceProvider::wrapRenderableBackendTexture(const GrBackendTexture& tex, + int sampleCnt, + GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable) { + ASSERT_SINGLE_OWNER + if (this->isAbandoned()) { + return nullptr; + } + return fGpu->wrapRenderableBackendTexture(tex, sampleCnt, colorType, ownership, cacheable); +} + +sk_sp GrResourceProvider::wrapBackendRenderTarget( + const GrBackendRenderTarget& backendRT, GrColorType colorType) +{ + ASSERT_SINGLE_OWNER + return this->isAbandoned() ? nullptr : fGpu->wrapBackendRenderTarget(backendRT, colorType); +} + +sk_sp GrResourceProvider::wrapVulkanSecondaryCBAsRenderTarget( + const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) { + ASSERT_SINGLE_OWNER + return this->isAbandoned() ? nullptr : fGpu->wrapVulkanSecondaryCBAsRenderTarget(imageInfo, + vkInfo); + +} + +void GrResourceProvider::assignUniqueKeyToResource(const GrUniqueKey& key, + GrGpuResource* resource) { + ASSERT_SINGLE_OWNER + if (this->isAbandoned() || !resource) { + return; + } + resource->resourcePriv().setUniqueKey(key); +} + +sk_sp GrResourceProvider::findResourceByUniqueKey(const GrUniqueKey& key) { + ASSERT_SINGLE_OWNER + return this->isAbandoned() ? nullptr + : sk_sp(fCache->findAndRefUniqueResource(key)); +} + +sk_sp GrResourceProvider::findOrMakeStaticBuffer(GrGpuBufferType intendedType, + size_t size, + const void* data, + const GrUniqueKey& key) { + if (auto buffer = this->findByUniqueKey(key)) { + return buffer; + } + if (auto buffer = this->createBuffer(size, intendedType, kStatic_GrAccessPattern, data)) { + // We shouldn't bin and/or cache static buffers. + SkASSERT(buffer->size() == size); + SkASSERT(!buffer->resourcePriv().getScratchKey().isValid()); + buffer->resourcePriv().setUniqueKey(key); + return sk_sp(buffer); + } + return nullptr; +} + +sk_sp GrResourceProvider::createPatternedIndexBuffer(const uint16_t* pattern, + int patternSize, + int reps, + int vertCount, + const GrUniqueKey* key) { + size_t bufferSize = patternSize * reps * sizeof(uint16_t); + + sk_sp buffer( + this->createBuffer(bufferSize, GrGpuBufferType::kIndex, kStatic_GrAccessPattern)); + if (!buffer) { + return nullptr; + } + uint16_t* data = (uint16_t*) buffer->map(); + SkAutoTArray temp; + if (!data) { + temp.reset(reps * patternSize); + data = temp.get(); + } + for (int i = 0; i < reps; ++i) { + int baseIdx = i * patternSize; + uint16_t baseVert = (uint16_t)(i * vertCount); + for (int j = 0; j < patternSize; ++j) { + data[baseIdx+j] = baseVert + pattern[j]; + } + } + if (temp.get()) { + if (!buffer->updateData(data, bufferSize)) { + return nullptr; + } + } else { + buffer->unmap(); + } + if (key) { + SkASSERT(key->isValid()); + this->assignUniqueKeyToResource(*key, buffer.get()); + } + return buffer; +} + +static constexpr int kMaxQuads = 1 << 12; // max possible: (1 << 14) - 1; + +sk_sp GrResourceProvider::createQuadIndexBuffer() { + GR_STATIC_ASSERT(4 * kMaxQuads <= 65535); + static const uint16_t kPattern[] = { 0, 1, 2, 2, 1, 3 }; + return this->createPatternedIndexBuffer(kPattern, 6, kMaxQuads, 4, nullptr); +} + +int GrResourceProvider::QuadCountOfQuadBuffer() { return kMaxQuads; } + +sk_sp GrResourceProvider::createPath(const SkPath& path, const GrStyle& style) { + if (this->isAbandoned()) { + return nullptr; + } + + SkASSERT(this->gpu()->pathRendering()); + return this->gpu()->pathRendering()->createPath(path, style); +} + +sk_sp GrResourceProvider::createBuffer(size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, + const void* data) { + if (this->isAbandoned()) { + return nullptr; + } + if (kDynamic_GrAccessPattern != accessPattern) { + return this->gpu()->createBuffer(size, intendedType, accessPattern, data); + } + // bin by pow2 with a reasonable min + static const size_t MIN_SIZE = 1 << 12; + size_t allocSize = SkTMax(MIN_SIZE, GrNextSizePow2(size)); + + GrScratchKey key; + GrGpuBuffer::ComputeScratchKeyForDynamicVBO(allocSize, intendedType, &key); + auto buffer = + sk_sp(static_cast(this->cache()->findAndRefScratchResource( + key))); + if (!buffer) { + buffer = this->gpu()->createBuffer(allocSize, intendedType, kDynamic_GrAccessPattern); + if (!buffer) { + return nullptr; + } + } + if (data) { + buffer->updateData(data, size); + } + return buffer; +} + +bool GrResourceProvider::attachStencilAttachment(GrRenderTarget* rt, int minStencilSampleCount) { + SkASSERT(rt); + GrStencilAttachment* stencil = rt->renderTargetPriv().getStencilAttachment(); + if (stencil && stencil->numSamples() >= minStencilSampleCount) { + return true; + } + + if (!rt->wasDestroyed() && rt->canAttemptStencilAttachment()) { + GrUniqueKey sbKey; + + int width = rt->width(); + int height = rt->height(); +#if 0 + if (this->caps()->oversizedStencilSupport()) { + width = SkNextPow2(width); + height = SkNextPow2(height); + } +#endif + GrStencilAttachment::ComputeSharedStencilAttachmentKey( + width, height, minStencilSampleCount, &sbKey); + auto stencil = this->findByUniqueKey(sbKey); + if (!stencil) { + // Need to try and create a new stencil + stencil.reset(this->gpu()->createStencilAttachmentForRenderTarget( + rt, width, height, minStencilSampleCount)); + if (!stencil) { + return false; + } + this->assignUniqueKeyToResource(sbKey, stencil.get()); + } + rt->renderTargetPriv().attachStencilAttachment(std::move(stencil)); + } + + if (GrStencilAttachment* stencil = rt->renderTargetPriv().getStencilAttachment()) { + return stencil->numSamples() >= minStencilSampleCount; + } + return false; +} + +sk_sp GrResourceProvider::wrapBackendTextureAsRenderTarget( + const GrBackendTexture& tex, int sampleCnt, GrColorType colorType) +{ + if (this->isAbandoned()) { + return nullptr; + } + return fGpu->wrapBackendTextureAsRenderTarget(tex, sampleCnt, colorType); +} + +sk_sp SK_WARN_UNUSED_RESULT GrResourceProvider::makeSemaphore(bool isOwned) { + return fGpu->makeSemaphore(isOwned); +} + +sk_sp GrResourceProvider::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + SemaphoreWrapType wrapType, + GrWrapOwnership ownership) { + ASSERT_SINGLE_OWNER + return this->isAbandoned() ? nullptr : fGpu->wrapBackendSemaphore(semaphore, + wrapType, + ownership); +} + +// Ensures the row bytes are populated (not 0) and makes a copy to a temporary +// to make the row bytes tight if necessary. Returns false if the input row bytes are invalid. +static bool prepare_level(const GrMipLevel& inLevel, + const SkISize& size, + bool rowBytesSupport, + GrColorType origColorType, + GrColorType allowedColorType, + GrMipLevel* outLevel, + std::unique_ptr* data) { + if (!inLevel.fPixels) { + outLevel->fPixels = nullptr; + outLevel->fRowBytes = 0; + return true; + } + size_t minRB = size.fWidth * GrColorTypeBytesPerPixel(origColorType); + size_t actualRB = inLevel.fRowBytes ? inLevel.fRowBytes : minRB; + if (actualRB < minRB) { + return false; + } + if (origColorType == allowedColorType && (actualRB == minRB || rowBytesSupport)) { + outLevel->fRowBytes = actualRB; + outLevel->fPixels = inLevel.fPixels; + return true; + } + auto tempRB = size.fWidth * GrColorTypeBytesPerPixel(allowedColorType); + data->reset(new char[tempRB * size.fHeight]); + outLevel->fPixels = data->get(); + outLevel->fRowBytes = tempRB; + GrImageInfo srcInfo(origColorType, kUnpremul_SkAlphaType, nullptr, size); + GrImageInfo dstInfo(allowedColorType, kUnpremul_SkAlphaType, nullptr, size); + return GrConvertPixels(dstInfo, data->get(), tempRB, srcInfo, inLevel.fPixels, actualRB); +} + +GrColorType GrResourceProvider::prepareLevels(const GrBackendFormat& format, + GrColorType colorType, + const SkISize& baseSize, + const GrMipLevel texels[], + int mipLevelCount, + TempLevels* tempLevels, + TempLevelDatas* tempLevelDatas) const { + SkASSERT(mipLevelCount && texels && texels[0].fPixels); + + auto allowedColorType = + this->caps()->supportedWritePixelsColorType(colorType, format, colorType).fColorType; + if (allowedColorType == GrColorType::kUnknown) { + return GrColorType::kUnknown; + } + bool rowBytesSupport = this->caps()->writePixelsRowBytesSupport(); + tempLevels->reset(mipLevelCount); + tempLevelDatas->reset(mipLevelCount); + auto size = baseSize; + for (int i = 0; i < mipLevelCount; ++i) { + if (!prepare_level(texels[i], size, rowBytesSupport, colorType, allowedColorType, + &(*tempLevels)[i], &(*tempLevelDatas)[i])) { + return GrColorType::kUnknown; + } + size = {std::max(size.fWidth / 2, 1), std::max(size.fHeight / 2, 1)}; + } + return allowedColorType; +} + +sk_sp GrResourceProvider::writePixels(sk_sp texture, + GrColorType colorType, + const SkISize& baseSize, + const GrMipLevel texels[], + int mipLevelCount) const { + SkASSERT(!this->isAbandoned()); + SkASSERT(texture); + SkASSERT(colorType != GrColorType::kUnknown); + SkASSERT(mipLevelCount && texels && texels[0].fPixels); + + SkAutoSTMalloc<14, GrMipLevel> tmpTexels; + SkAutoSTArray<14, std::unique_ptr> tmpDatas; + auto tempColorType = this->prepareLevels(texture->backendFormat(), colorType, baseSize, texels, + mipLevelCount, &tmpTexels, &tmpDatas); + if (tempColorType == GrColorType::kUnknown) { + return nullptr; + } + SkAssertResult(fGpu->writePixels(texture.get(), 0, 0, baseSize.fWidth, baseSize.fHeight, + colorType, tempColorType, tmpTexels.get(), mipLevelCount)); + return texture; +} diff --git a/gfx/skia/skia/src/gpu/GrResourceProvider.h b/gfx/skia/skia/src/gpu/GrResourceProvider.h new file mode 100644 index 0000000000..12d5d02ce9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceProvider.h @@ -0,0 +1,360 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrResourceProvider_DEFINED +#define GrResourceProvider_DEFINED + +#include "include/gpu/GrContextOptions.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkScalerContext.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/GrResourceCache.h" + +class GrBackendRenderTarget; +class GrBackendSemaphore; +class GrBackendTexture; +class GrGpu; +class GrPath; +class GrRenderTarget; +class GrResourceProviderPriv; +class GrSemaphore; +class GrSingleOwner; +class GrStencilAttachment; +class GrTexture; +struct GrVkDrawableInfo; + +class GrStyle; +class SkDescriptor; +class SkPath; +class SkTypeface; + +/** + * A factory for arbitrary resource types. + */ +class GrResourceProvider { +public: + GrResourceProvider(GrGpu*, GrResourceCache*, GrSingleOwner*); + + /** + * Finds a resource in the cache, based on the specified key. Prior to calling this, the caller + * must be sure that if a resource of exists in the cache with the given unique key then it is + * of type T. + */ + template + typename std::enable_if::value, sk_sp>::type + findByUniqueKey(const GrUniqueKey& key) { + return sk_sp(static_cast(this->findResourceByUniqueKey(key).release())); + } + + /////////////////////////////////////////////////////////////////////////// + // Textures + + /** + * Finds a texture that approximately matches the descriptor. Will be at least as large in width + * and height as desc specifies. If renderable is kYes then the GrTexture will also be a + * GrRenderTarget. The texture's format and sample count will always match the request. + * The contents of the texture are undefined. + */ + sk_sp createApproxTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrProtected isProtected); + + /** Create an exact fit texture with no initial data to upload. */ + sk_sp createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + GrMipMapped mipMapped, + SkBudgeted budgeted, + GrProtected isProtected); + + /** + * Create an exact fit texture with initial data to upload. The color type must be valid + * for the format and also describe the texel data. This will ensure any conversions that + * need to get applied to the data before upload are applied. + */ + sk_sp createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrColorType colorType, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + const GrMipLevel texels[], + int mipLevelCount); + + /** + * Create a potentially loose fit texture with the provided data. The color type must be valid + * for the format and also describe the texel data. This will ensure any conversions that + * need to get applied to the data before upload are applied. + */ + sk_sp createTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrColorType srcColorType, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + SkBackingFit fit, + GrProtected isProtected, + const GrMipLevel& mipLevel); + + /** + * Creates a compressed texture. The GrGpu must support the SkImageImage::Compression type. + * This does not currently support MIP maps. It will not be renderable. + */ + sk_sp createCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, SkData* data); + + /////////////////////////////////////////////////////////////////////////// + // Wrapped Backend Surfaces + + /** + * Wraps an existing texture with a GrTexture object. + * + * GrIOType must either be kRead or kRW. kRead blocks any operations that would modify the + * pixels (e.g. dst for a copy, regenerating MIP levels, write pixels). + * + * OpenGL: if the object is a texture Gr may change its GL texture params + * when it is drawn. + * + * @return GrTexture object or NULL on failure. + */ + sk_sp wrapBackendTexture(const GrBackendTexture& tex, GrColorType, GrWrapOwnership, + GrWrapCacheable, GrIOType); + + /** + * This makes the backend texture be renderable. If sampleCnt is > 1 and the underlying API + * uses separate MSAA render buffers then a MSAA render buffer is created that resolves + * to the texture. + */ + sk_sp wrapRenderableBackendTexture(const GrBackendTexture& tex, + int sampleCnt, + GrColorType, + GrWrapOwnership, + GrWrapCacheable); + + /** + * Wraps an existing render target with a GrRenderTarget object. It is + * similar to wrapBackendTexture but can be used to draw into surfaces + * that are not also textures (e.g. FBO 0 in OpenGL, or an MSAA buffer that + * the client will resolve to a texture). Currently wrapped render targets + * always use the kBorrow_GrWrapOwnership and GrWrapCacheable::kNo semantics. + * + * @return GrRenderTarget object or NULL on failure. + */ + sk_sp wrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType colorType); + + sk_sp wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&, + const GrVkDrawableInfo&); + + static const uint32_t kMinScratchTextureSize; + + /** + * Either finds and refs, or creates a static buffer with the given parameters and contents. + * + * @param intendedType hint to the graphics subsystem about what the buffer will be used for. + * @param size minimum size of buffer to return. + * @param data optional data with which to initialize the buffer. + * @param key Key to be assigned to the buffer. + * + * @return The buffer if successful, otherwise nullptr. + */ + sk_sp findOrMakeStaticBuffer(GrGpuBufferType intendedType, size_t size, + const void* data, const GrUniqueKey& key); + + /** + * Either finds and refs, or creates an index buffer with a repeating pattern for drawing + * contiguous vertices of a repeated mesh. If the return is non-null, the caller owns a ref on + * the returned GrBuffer. + * + * @param pattern the pattern of indices to repeat + * @param patternSize size in bytes of the pattern + * @param reps number of times to repeat the pattern + * @param vertCount number of vertices the pattern references + * @param key Key to be assigned to the index buffer. + * + * @return The index buffer if successful, otherwise nullptr. + */ + sk_sp findOrCreatePatternedIndexBuffer(const uint16_t* pattern, + int patternSize, + int reps, + int vertCount, + const GrUniqueKey& key) { + if (auto buffer = this->findByUniqueKey(key)) { + return buffer; + } + return this->createPatternedIndexBuffer(pattern, patternSize, reps, vertCount, &key); + } + + /** + * Returns an index buffer that can be used to render quads. + * Six indices per quad: 0, 1, 2, 2, 1, 3, etc. + * The max number of quads is the buffer's index capacity divided by 6. + * Draw with GrPrimitiveType::kTriangles + * @ return the quad index buffer + */ + sk_sp refQuadIndexBuffer() { + if (!fQuadIndexBuffer) { + fQuadIndexBuffer = this->createQuadIndexBuffer(); + } + return fQuadIndexBuffer; + } + + static int QuadCountOfQuadBuffer(); + + /** + * Factories for GrPath objects. It's an error to call these if path rendering + * is not supported. + */ + sk_sp createPath(const SkPath&, const GrStyle&); + + /** + * Returns a buffer. + * + * @param size minimum size of buffer to return. + * @param intendedType hint to the graphics subsystem about what the buffer will be used for. + * @param GrAccessPattern hint to the graphics subsystem about how the data will be accessed. + * @param flags see Flags enum. + * @param data optional data with which to initialize the buffer. + * + * @return the buffer if successful, otherwise nullptr. + */ + sk_sp createBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern, + const void* data = nullptr); + + /** + * If passed in render target already has a stencil buffer with at least "numSamples" samples, + * return true. Otherwise attempt to attach one and return true on success. + */ + bool attachStencilAttachment(GrRenderTarget* rt, int numStencilSamples); + + /** + * Wraps an existing texture with a GrRenderTarget object. This is useful when the provided + * texture has a format that cannot be textured from by Skia, but we want to raster to it. + * + * The texture is wrapped as borrowed. The texture object will not be freed once the + * render target is destroyed. + * + * @return GrRenderTarget object or NULL on failure. + */ + sk_sp wrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, + GrColorType); + + /** + * Assigns a unique key to a resource. If the key is associated with another resource that + * association is removed and replaced by this resource. + */ + void assignUniqueKeyToResource(const GrUniqueKey&, GrGpuResource*); + + sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned = true); + + enum class SemaphoreWrapType { + kWillSignal, + kWillWait, + }; + + sk_sp wrapBackendSemaphore(const GrBackendSemaphore&, + SemaphoreWrapType wrapType, + GrWrapOwnership = kBorrow_GrWrapOwnership); + + void abandon() { + fCache = nullptr; + fGpu = nullptr; + } + + uint32_t contextUniqueID() const { return fCache->contextUniqueID(); } + const GrCaps* caps() const { return fCaps.get(); } + bool overBudget() const { return fCache->overBudget(); } + + static uint32_t MakeApprox(uint32_t value); + + inline GrResourceProviderPriv priv(); + inline const GrResourceProviderPriv priv() const; + +private: + sk_sp findResourceByUniqueKey(const GrUniqueKey&); + + // Attempts to find a resource in the cache that exactly matches the GrSurfaceDesc. Failing that + // it returns null. If non-null, the resulting texture is always budgeted. + sk_sp refScratchTexture(const GrSurfaceDesc&, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + GrMipMapped, + GrProtected); + + /* + * Try to find an existing scratch texture that exactly matches 'desc'. If successful + * update the budgeting accordingly. + */ + sk_sp getExactScratch(const GrSurfaceDesc&, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted, + GrMipMapped, + GrProtected); + + // Used to perform any conversions necessary to texel data before creating a texture with + // existing data or uploading to a scratch texture. + using TempLevels = SkAutoSTMalloc<14, GrMipLevel>; + using TempLevelDatas = SkAutoSTArray<14, std::unique_ptr>; + GrColorType prepareLevels(const GrBackendFormat& format, + GrColorType, + const SkISize& baseSize, + const GrMipLevel texels[], + int mipLevelCount, + TempLevels*, + TempLevelDatas*) const; + + // GrResourceProvider may be asked to "create" a new texture with initial pixel data to populate + // it. In implementation it may pull an existing texture from GrResourceCache and then write the + // pixel data to the texture. It takes a width/height for the base level because we may be + // using an approximate-sized scratch texture. On success the texture is returned and nullptr + // on failure. + sk_sp writePixels(sk_sp texture, + GrColorType colorType, + const SkISize& baseSize, + const GrMipLevel texels[], + int mipLevelCount) const; + + GrResourceCache* cache() { return fCache; } + const GrResourceCache* cache() const { return fCache; } + + friend class GrResourceProviderPriv; + + // Method made available via GrResourceProviderPriv + GrGpu* gpu() { return fGpu; } + const GrGpu* gpu() const { return fGpu; } + + bool isAbandoned() const { + SkASSERT(SkToBool(fGpu) == SkToBool(fCache)); + return !SkToBool(fCache); + } + + sk_sp createPatternedIndexBuffer(const uint16_t* pattern, + int patternSize, + int reps, + int vertCount, + const GrUniqueKey* key); + + sk_sp createQuadIndexBuffer(); + + GrResourceCache* fCache; + GrGpu* fGpu; + sk_sp fCaps; + sk_sp fQuadIndexBuffer; + + // In debug builds we guard against improper thread handling + SkDEBUGCODE(mutable GrSingleOwner* fSingleOwner;) +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrResourceProviderPriv.h b/gfx/skia/skia/src/gpu/GrResourceProviderPriv.h new file mode 100644 index 0000000000..a440f959b3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrResourceProviderPriv.h @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrResourceProviderPriv_DEFINED +#define GrResourceProviderPriv_DEFINED + +#include "src/gpu/GrResourceProvider.h" + +/** Class that adds methods to GrResourceProvider that are only intended for use internal to Skia. + This class is purely a privileged window into GrResourceProvider. It should never have + additional data members or virtual methods. */ +class GrResourceProviderPriv { +public: + GrGpu* gpu() { return fResourceProvider->gpu(); } + +private: + explicit GrResourceProviderPriv(GrResourceProvider* provider) : fResourceProvider(provider) {} + GrResourceProviderPriv(const GrResourceProviderPriv&); // unimpl + GrResourceProviderPriv& operator=(const GrResourceProviderPriv&); // unimpl + + // No taking addresses of this type. + const GrResourceProviderPriv* operator&() const; + GrResourceProviderPriv* operator&(); + + GrResourceProvider* fResourceProvider; + friend class GrResourceProvider; // to construct/copy this type +}; + +inline GrResourceProviderPriv GrResourceProvider::priv() { return GrResourceProviderPriv(this); } + +inline const GrResourceProviderPriv GrResourceProvider::priv() const { + return GrResourceProviderPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSWMaskHelper.cpp b/gfx/skia/skia/src/gpu/GrSWMaskHelper.cpp new file mode 100644 index 0000000000..879a028511 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSWMaskHelper.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrSWMaskHelper.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/geometry/GrShape.h" + +/* + * Convert a boolean operation into a transfer mode code + */ +static SkBlendMode op_to_mode(SkRegion::Op op) { + + static const SkBlendMode modeMap[] = { + SkBlendMode::kDstOut, // kDifference_Op + SkBlendMode::kModulate, // kIntersect_Op + SkBlendMode::kSrcOver, // kUnion_Op + SkBlendMode::kXor, // kXOR_Op + SkBlendMode::kClear, // kReverseDifference_Op + SkBlendMode::kSrc, // kReplace_Op + }; + + return modeMap[op]; +} + +/** + * Draw a single rect element of the clip stack into the accumulation bitmap + */ +void GrSWMaskHelper::drawRect(const SkRect& rect, const SkMatrix& matrix, SkRegion::Op op, GrAA aa, + uint8_t alpha) { + SkPaint paint; + paint.setBlendMode(op_to_mode(op)); + paint.setAntiAlias(GrAA::kYes == aa); + paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); + + SkMatrix translatedMatrix = matrix; + translatedMatrix.postTranslate(fTranslate.fX, fTranslate.fY); + fDraw.fMatrix = &translatedMatrix; + + fDraw.drawRect(rect, paint); +} + +/** + * Draw a single path element of the clip stack into the accumulation bitmap + */ +void GrSWMaskHelper::drawShape(const GrShape& shape, const SkMatrix& matrix, SkRegion::Op op, + GrAA aa, uint8_t alpha) { + SkPaint paint; + paint.setPathEffect(shape.style().refPathEffect()); + shape.style().strokeRec().applyToPaint(&paint); + paint.setAntiAlias(GrAA::kYes == aa); + + SkMatrix translatedMatrix = matrix; + translatedMatrix.postTranslate(fTranslate.fX, fTranslate.fY); + fDraw.fMatrix = &translatedMatrix; + + SkPath path; + shape.asPath(&path); + if (SkRegion::kReplace_Op == op && 0xFF == alpha) { + SkASSERT(0xFF == paint.getAlpha()); + fDraw.drawPathCoverage(path, paint); + } else { + paint.setBlendMode(op_to_mode(op)); + paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); + fDraw.drawPath(path, paint); + } +}; + +bool GrSWMaskHelper::init(const SkIRect& resultBounds) { + // We will need to translate draws so the bound's UL corner is at the origin + fTranslate = {-SkIntToScalar(resultBounds.fLeft), -SkIntToScalar(resultBounds.fTop)}; + SkIRect bounds = SkIRect::MakeWH(resultBounds.width(), resultBounds.height()); + + const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(bounds.width(), bounds.height()); + if (!fPixels->tryAlloc(bmImageInfo)) { + return false; + } + fPixels->erase(0); + + fDraw.fDst = *fPixels; + fRasterClip.setRect(bounds); + fDraw.fRC = &fRasterClip; + return true; +} + +sk_sp GrSWMaskHelper::toTextureProxy(GrRecordingContext* context, + SkBackingFit fit) { + SkImageInfo ii = SkImageInfo::MakeA8(fPixels->width(), fPixels->height()); + size_t rowBytes = fPixels->rowBytes(); + + sk_sp data = fPixels->detachPixelsAsData(); + if (!data) { + return nullptr; + } + + sk_sp img = SkImage::MakeRasterData(ii, std::move(data), rowBytes); + if (!img) { + return nullptr; + } + + return context->priv().proxyProvider()->createTextureProxy(std::move(img), 1, SkBudgeted::kYes, + fit); +} diff --git a/gfx/skia/skia/src/gpu/GrSWMaskHelper.h b/gfx/skia/skia/src/gpu/GrSWMaskHelper.h new file mode 100644 index 0000000000..0a042e1e54 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSWMaskHelper.h @@ -0,0 +1,71 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSWMaskHelper_DEFINED +#define GrSWMaskHelper_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkRegion.h" +#include "include/core/SkTypes.h" +#include "include/private/GrTypesPriv.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkDraw.h" +#include "src/core/SkRasterClip.h" + +class GrShape; +class GrRecordingContext; +class GrTextureProxy; + +/** + * The GrSWMaskHelper helps generate clip masks using the software rendering + * path. It is intended to be used as: + * + * GrSWMaskHelper helper(context); + * helper.init(...); + * + * draw one or more paths/rects specifying the required boolean ops + * + * toTextureProxy(); // to get it from the internal bitmap to the GPU + * + * The result of this process will be the final mask (on the GPU) in the + * upper left hand corner of the texture. + */ +class GrSWMaskHelper : SkNoncopyable { +public: + GrSWMaskHelper(SkAutoPixmapStorage* pixels = nullptr) + : fPixels(pixels ? pixels : &fPixelsStorage) { } + + // set up the internal state in preparation for draws. Since many masks + // may be accumulated in the helper during creation, "resultBounds" + // allows the caller to specify the region of interest - to limit the + // amount of work. + bool init(const SkIRect& resultBounds); + + // Draw a single rect into the accumulation bitmap using the specified op + void drawRect(const SkRect& rect, const SkMatrix& matrix, SkRegion::Op op, GrAA, uint8_t alpha); + + // Draw a single path into the accumuation bitmap using the specified op + void drawShape(const GrShape&, const SkMatrix& matrix, SkRegion::Op op, GrAA, uint8_t alpha); + + sk_sp toTextureProxy(GrRecordingContext*, SkBackingFit fit); + + // Reset the internal bitmap + void clear(uint8_t alpha) { + fPixels->erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF)); + } + +private: + SkVector fTranslate; + SkAutoPixmapStorage* fPixels; + SkAutoPixmapStorage fPixelsStorage; + SkDraw fDraw; + SkRasterClip fRasterClip; + + typedef SkNoncopyable INHERITED; +}; + +#endif // GrSWMaskHelper_DEFINED diff --git a/gfx/skia/skia/src/gpu/GrSamplePatternDictionary.cpp b/gfx/skia/skia/src/gpu/GrSamplePatternDictionary.cpp new file mode 100644 index 0000000000..61142896b2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSamplePatternDictionary.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrSamplePatternDictionary.h" + +bool GrSamplePatternDictionary::LessThan::operator()( + const SkTArray& a, const SkTArray& b) const { + if (a.count() != b.count()) { + return a.count() < b.count(); + } + for (int i = 0; i < a.count(); ++i) { + // This doesn't have geometric meaning. We just need to define an ordering for std::map. + if (a[i].x() != b[i].x()) { + return a[i].x() < b[i].x(); + } + if (a[i].y() != b[i].y()) { + return a[i].y() < b[i].y(); + } + } + return false; // Both sample patterns are equal, therefore, "a < b" is false. +} + +int GrSamplePatternDictionary::findOrAssignSamplePatternKey( + const SkTArray& sampleLocations) { + if (std::numeric_limits::max() == fSampleLocationsArray.count()) { + return 0; + } + const auto& insertResult = fSamplePatternKeyMap.insert( + {sampleLocations, fSampleLocationsArray.count()}); + if (insertResult.second) { + // This means the "insert" call did not find the pattern in the key map already, and + // therefore an actual insertion took place. (We don't expect to see many unique sample + // patterns.) + const SkTArray& sampleLocations = insertResult.first->first; + fSampleLocationsArray.push_back(&sampleLocations); + } + return insertResult.first->second; // Return the new sample pattern key. +} diff --git a/gfx/skia/skia/src/gpu/GrSamplePatternDictionary.h b/gfx/skia/skia/src/gpu/GrSamplePatternDictionary.h new file mode 100644 index 0000000000..b0044782a6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSamplePatternDictionary.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSamplePatternDictionary_DEFINED +#define GrSamplePatternDictionary_DEFINED + +#include "include/core/SkPoint.h" +#include "include/private/SkTArray.h" +#include + +/** + * A bidirectional dictionary mapping between sample patterns (i.e., a list of sample locations) and + * unique keys. Since we expect that most render targets will draw from the same small pool of + * sample patterns, we favor sample pattern keys over actual arrays of points. + */ +class GrSamplePatternDictionary { +public: + static constexpr int kInvalidSamplePatternKey = -1; + + int findOrAssignSamplePatternKey(const SkTArray& sampleLocations); + + const SkTArray& retrieveSampleLocations(int samplePatternKey) const { + return *fSampleLocationsArray[samplePatternKey]; + } + +private: + struct LessThan { + bool operator()(const SkTArray&, const SkTArray&) const; + }; + + std::map, int, LessThan> fSamplePatternKeyMap; + SkTArray*> fSampleLocationsArray; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSamplerState.h b/gfx/skia/skia/src/gpu/GrSamplerState.h new file mode 100644 index 0000000000..34915c7bbd --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSamplerState.h @@ -0,0 +1,86 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSamplerState_DEFINED +#define GrSamplerState_DEFINED + +#include "include/gpu/GrTypes.h" + +/** + * Represents the filtering and tile modes used to access a texture. + */ +class GrSamplerState { +public: + enum class Filter : uint8_t { kNearest, kBilerp, kMipMap }; + enum class WrapMode : uint8_t { kClamp, kRepeat, kMirrorRepeat, kClampToBorder }; + + static constexpr GrSamplerState ClampNearest() { return GrSamplerState(); } + static constexpr GrSamplerState ClampBilerp() { + return GrSamplerState(WrapMode::kClamp, Filter::kBilerp); + } + + constexpr GrSamplerState() : GrSamplerState(WrapMode::kClamp, Filter::kNearest) {} + + constexpr GrSamplerState(WrapMode wrapXAndY, Filter filter) + : fWrapModes{wrapXAndY, wrapXAndY}, fFilter(filter) {} + + constexpr GrSamplerState(const WrapMode wrapModes[2], Filter filter) + : fWrapModes{wrapModes[0], wrapModes[1]}, fFilter(filter) {} + + constexpr GrSamplerState(const GrSamplerState&) = default; + + GrSamplerState& operator=(const GrSamplerState& that) { + fWrapModes[0] = that.fWrapModes[0]; + fWrapModes[1] = that.fWrapModes[1]; + fFilter = that.fFilter; + return *this; + } + + Filter filter() const { return fFilter; } + + void setFilterMode(Filter filterMode) { fFilter = filterMode; } + + void setWrapModeX(const WrapMode wrap) { fWrapModes[0] = wrap; } + void setWrapModeY(const WrapMode wrap) { fWrapModes[1] = wrap; } + + WrapMode wrapModeX() const { return fWrapModes[0]; } + WrapMode wrapModeY() const { return fWrapModes[1]; } + + bool isRepeated() const { + return (WrapMode::kClamp != fWrapModes[0] && WrapMode::kClampToBorder != fWrapModes[0]) || + (WrapMode::kClamp != fWrapModes[1] && WrapMode::kClampToBorder != fWrapModes[1]); + } + + bool operator==(const GrSamplerState& that) const { + return fWrapModes[0] == that.fWrapModes[0] && fWrapModes[1] == that.fWrapModes[1] && + fFilter == that.fFilter; + } + + bool operator!=(const GrSamplerState& that) const { return !(*this == that); } + + static uint8_t GenerateKey(const GrSamplerState& samplerState) { + const int kTileModeXShift = 2; + const int kTileModeYShift = 4; + + SkASSERT(static_cast(samplerState.filter()) <= 3); + uint8_t key = static_cast(samplerState.filter()); + + SkASSERT(static_cast(samplerState.wrapModeX()) <= 3); + key |= (static_cast(samplerState.wrapModeX()) << kTileModeXShift); + + SkASSERT(static_cast(samplerState.wrapModeY()) <= 3); + key |= (static_cast(samplerState.wrapModeY()) << kTileModeYShift); + + return key; + } + +private: + WrapMode fWrapModes[2]; + Filter fFilter; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrScissorState.h b/gfx/skia/skia/src/gpu/GrScissorState.h new file mode 100644 index 0000000000..79ba47f1e6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrScissorState.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrScissorState_DEFINED +#define GrScissorState_DEFINED + +#include "include/core/SkRect.h" + +class GrScissorState { +public: + GrScissorState() : fEnabled(false) {} + GrScissorState(const SkIRect& rect) : fEnabled(true), fRect(rect) {} + void setDisabled() { fEnabled = false; } + void set(const SkIRect& rect) { fRect = rect; fEnabled = true; } + bool SK_WARN_UNUSED_RESULT intersect(const SkIRect& rect) { + if (!fEnabled) { + this->set(rect); + return true; + } + return fRect.intersect(rect); + } + bool operator==(const GrScissorState& other) const { + return fEnabled == other.fEnabled && + (false == fEnabled || fRect == other.fRect); + } + bool operator!=(const GrScissorState& other) const { return !(*this == other); } + + bool enabled() const { return fEnabled; } + const SkIRect& rect() const { return fRect; } + +private: + bool fEnabled; + SkIRect fRect; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSemaphore.h b/gfx/skia/skia/src/gpu/GrSemaphore.h new file mode 100644 index 0000000000..858cf63cbc --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSemaphore.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSemaphore_DEFINED +#define GrSemaphore_DEFINED + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrGpuResource.h" + +/** + * Represents a semaphore-like GPU synchronization object. This is a slightly odd fit for + * GrGpuResource because we don't care about budgeting, recycling, or read/write references for + * these. However, making it a GrGpuResource makes it simpler to handle releasing/abandoning these + * along with other resources. If more cases like this arise we could consider moving some of the + * unused functionality off of GrGpuResource. + */ +class GrSemaphore : public GrGpuResource { +public: + // The derived class can return its GrBackendSemaphore. This is used when flushing with signal + // semaphores so we can set the client's GrBackendSemaphore object after we've created the + // internal semaphore. + virtual GrBackendSemaphore backendSemaphore() const = 0; + + const char* getResourceType() const override { return "semaphore"; } + +protected: + explicit GrSemaphore(GrGpu* gpu) : INHERITED(gpu) {} + +private: + size_t onGpuMemorySize() const override { return 0; } + + typedef GrGpuResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrShaderCaps.cpp b/gfx/skia/skia/src/gpu/GrShaderCaps.cpp new file mode 100644 index 0000000000..b2be03bd01 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrShaderCaps.cpp @@ -0,0 +1,172 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/GrShaderCaps.h" + +#include "include/gpu/GrContextOptions.h" +#include "src/utils/SkJSONWriter.h" + +//////////////////////////////////////////////////////////////////////////////////////////// + +GrShaderCaps::GrShaderCaps(const GrContextOptions& options) { + fGLSLGeneration = k330_GrGLSLGeneration; + fShaderDerivativeSupport = false; + fGeometryShaderSupport = false; + fGSInvocationsSupport = false; + fPathRenderingSupport = false; + fDstReadInShaderSupport = false; + fDualSourceBlendingSupport = false; + fIntegerSupport = false; + fFBFetchSupport = false; + fFBFetchNeedsCustomOutput = false; + fUsesPrecisionModifiers = false; + fCanUseAnyFunctionInShader = true; + fCanUseMinAndAbsTogether = true; + fCanUseFractForNegativeValues = true; + fMustForceNegatedAtanParamToFloat = false; + fAtan2ImplementedAsAtanYOverX = false; + fMustDoOpBetweenFloorAndAbs = false; + fRequiresLocalOutputColorForFBFetch = false; + fMustObfuscateUniformColor = false; + fMustGuardDivisionEvenAfterExplicitZeroCheck = false; + fCanUseFragCoord = true; + fIncompleteShortIntPrecision = false; + fAddAndTrueToLoopCondition = false; + fUnfoldShortCircuitAsTernary = false; + fEmulateAbsIntFunction = false; + fRewriteDoWhileLoops = false; + fRemovePowWithConstantExponent = false; + fMustWriteToFragColor = false; + fNoDefaultPrecisionForExternalSamplers = false; + fFlatInterpolationSupport = false; + fPreferFlatInterpolation = false; + fNoPerspectiveInterpolationSupport = false; + fSampleVariablesSupport = false; + fSampleVariablesStencilSupport = false; + fExternalTextureSupport = false; + fVertexIDSupport = false; + fFPManipulationSupport = false; + fFloatIs32Bits = true; + fHalfIs32Bits = false; + fHasLowFragmentPrecision = false; + // Backed API support is required to be able to make swizzle-neutral shaders (e.g. + // GL_ARB_texture_swizzle). + fTextureSwizzleAppliedInShader = true; + fBuiltinFMASupport = false; + + fVersionDeclString = nullptr; + fShaderDerivativeExtensionString = nullptr; + fGeometryShaderExtensionString = nullptr; + fGSInvocationsExtensionString = nullptr; + fFragCoordConventionsExtensionString = nullptr; + fSecondaryOutputExtensionString = nullptr; + fExternalTextureExtensionString = nullptr; + fSecondExternalTextureExtensionString = nullptr; + fNoPerspectiveInterpolationExtensionString = nullptr; + fSampleVariablesExtensionString = nullptr; + fFBFetchColorName = nullptr; + fFBFetchExtensionString = nullptr; + fMaxFragmentSamplers = 0; + fAdvBlendEqInteraction = kNotSupported_AdvBlendEqInteraction; +} + +#ifdef SK_ENABLE_DUMP_GPU +void GrShaderCaps::dumpJSON(SkJSONWriter* writer) const { + writer->beginObject(); + + writer->appendBool("Shader Derivative Support", fShaderDerivativeSupport); + writer->appendBool("Geometry Shader Support", fGeometryShaderSupport); + writer->appendBool("Geometry Shader Invocations Support", fGSInvocationsSupport); + writer->appendBool("Path Rendering Support", fPathRenderingSupport); + writer->appendBool("Dst Read In Shader Support", fDstReadInShaderSupport); + writer->appendBool("Dual Source Blending Support", fDualSourceBlendingSupport); + writer->appendBool("Integer Support", fIntegerSupport); + + static const char* kAdvBlendEqInteractionStr[] = { + "Not Supported", + "Automatic", + "General Enable", + "Specific Enables", + }; + GR_STATIC_ASSERT(0 == kNotSupported_AdvBlendEqInteraction); + GR_STATIC_ASSERT(1 == kAutomatic_AdvBlendEqInteraction); + GR_STATIC_ASSERT(2 == kGeneralEnable_AdvBlendEqInteraction); + GR_STATIC_ASSERT(3 == kSpecificEnables_AdvBlendEqInteraction); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kAdvBlendEqInteractionStr) == kLast_AdvBlendEqInteraction + 1); + + writer->appendBool("FB Fetch Support", fFBFetchSupport); + writer->appendBool("Uses precision modifiers", fUsesPrecisionModifiers); + writer->appendBool("Can use any() function", fCanUseAnyFunctionInShader); + writer->appendBool("Can use min() and abs() together", fCanUseMinAndAbsTogether); + writer->appendBool("Can use fract() for negative values", fCanUseFractForNegativeValues); + writer->appendBool("Must force negated atan param to float", fMustForceNegatedAtanParamToFloat); + writer->appendBool("Must do op between floor and abs", fMustDoOpBetweenFloorAndAbs); + writer->appendBool("Must use local out color for FBFetch", fRequiresLocalOutputColorForFBFetch); + writer->appendBool("Must obfuscate uniform color", fMustObfuscateUniformColor); + writer->appendBool("Must guard division even after explicit zero check", + fMustGuardDivisionEvenAfterExplicitZeroCheck); + writer->appendBool("Can use gl_FragCoord", fCanUseFragCoord); + writer->appendBool("Incomplete short int precision", fIncompleteShortIntPrecision); + writer->appendBool("Add and true to loops workaround", fAddAndTrueToLoopCondition); + writer->appendBool("Unfold short circuit as ternary", fUnfoldShortCircuitAsTernary); + writer->appendBool("Emulate abs(int) function", fEmulateAbsIntFunction); + writer->appendBool("Rewrite do while loops", fRewriteDoWhileLoops); + writer->appendBool("Rewrite pow with constant exponent", fRemovePowWithConstantExponent); + writer->appendBool("Must write to sk_FragColor [workaround]", fMustWriteToFragColor); + writer->appendBool("Don't add default precision statement for samplerExternalOES", + fNoDefaultPrecisionForExternalSamplers); + writer->appendBool("Flat interpolation support", fFlatInterpolationSupport); + writer->appendBool("Prefer flat interpolation", fPreferFlatInterpolation); + writer->appendBool("No perspective interpolation support", fNoPerspectiveInterpolationSupport); + writer->appendBool("Sample variables support", fSampleVariablesSupport); + writer->appendBool("Sample variables stencil support [workaround]", + fSampleVariablesStencilSupport); + writer->appendBool("External texture support", fExternalTextureSupport); + writer->appendBool("sk_VertexID support", fVertexIDSupport); + writer->appendBool("Floating point manipulation support", fFPManipulationSupport); + writer->appendBool("float == fp32", fFloatIs32Bits); + writer->appendBool("half == fp32", fHalfIs32Bits); + writer->appendBool("Has poor fragment precision", fHasLowFragmentPrecision); + writer->appendBool("Texture swizzle applied in shader", fTextureSwizzleAppliedInShader); + writer->appendBool("Builtin fma() support", fBuiltinFMASupport); + + writer->appendS32("Max FS Samplers", fMaxFragmentSamplers); + writer->appendString("Advanced blend equation interaction", + kAdvBlendEqInteractionStr[fAdvBlendEqInteraction]); + + writer->endObject(); +} +#else +void GrShaderCaps::dumpJSON(SkJSONWriter* writer) const { } +#endif + +void GrShaderCaps::applyOptionsOverrides(const GrContextOptions& options) { + if (options.fDisableDriverCorrectnessWorkarounds) { + SkASSERT(fCanUseAnyFunctionInShader); + SkASSERT(fCanUseMinAndAbsTogether); + SkASSERT(fCanUseFractForNegativeValues); + SkASSERT(!fMustForceNegatedAtanParamToFloat); + SkASSERT(!fAtan2ImplementedAsAtanYOverX); + SkASSERT(!fMustDoOpBetweenFloorAndAbs); + SkASSERT(!fRequiresLocalOutputColorForFBFetch); + SkASSERT(!fMustObfuscateUniformColor); + SkASSERT(!fMustGuardDivisionEvenAfterExplicitZeroCheck); + SkASSERT(fCanUseFragCoord); + SkASSERT(!fIncompleteShortIntPrecision); + SkASSERT(!fAddAndTrueToLoopCondition); + SkASSERT(!fUnfoldShortCircuitAsTernary); + SkASSERT(!fEmulateAbsIntFunction); + SkASSERT(!fRewriteDoWhileLoops); + SkASSERT(!fRemovePowWithConstantExponent); + SkASSERT(!fMustWriteToFragColor); + SkASSERT(!fNoDefaultPrecisionForExternalSamplers); + } +#if GR_TEST_UTILS + fDualSourceBlendingSupport = fDualSourceBlendingSupport && !options.fSuppressDualSourceBlending; +#endif +} diff --git a/gfx/skia/skia/src/gpu/GrShaderCaps.h b/gfx/skia/skia/src/gpu/GrShaderCaps.h new file mode 100644 index 0000000000..fb68ec354e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrShaderCaps.h @@ -0,0 +1,314 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrShaderCaps_DEFINED +#define GrShaderCaps_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/glsl/GrGLSL.h" + +namespace SkSL { +class ShaderCapsFactory; +} + +struct GrContextOptions; +class SkJSONWriter; + +class GrShaderCaps : public SkRefCnt { +public: + /** + * Indicates how GLSL must interact with advanced blend equations. The KHR extension requires + * special layout qualifiers in the fragment shader. + */ + enum AdvBlendEqInteraction { + kNotSupported_AdvBlendEqInteraction, //= kGeneralEnable_AdvBlendEqInteraction; + } + + bool mustEnableSpecificAdvBlendEqs() const { + return fAdvBlendEqInteraction == kSpecificEnables_AdvBlendEqInteraction; + } + + bool mustDeclareFragmentShaderOutput() const { return fGLSLGeneration > k110_GrGLSLGeneration; } + + bool usesPrecisionModifiers() const { return fUsesPrecisionModifiers; } + + // Returns whether we can use the glsl function any() in our shader code. + bool canUseAnyFunctionInShader() const { return fCanUseAnyFunctionInShader; } + + bool canUseMinAndAbsTogether() const { return fCanUseMinAndAbsTogether; } + + bool canUseFractForNegativeValues() const { return fCanUseFractForNegativeValues; } + + bool mustForceNegatedAtanParamToFloat() const { return fMustForceNegatedAtanParamToFloat; } + + // Returns whether a device incorrectly implements atan(y,x) as atan(y/x) + bool atan2ImplementedAsAtanYOverX() const { return fAtan2ImplementedAsAtanYOverX; } + + // If this returns true some operation (could be a no op) must be called between floor and abs + // to make sure the driver compiler doesn't inline them together which can cause a driver bug in + // the shader. + bool mustDoOpBetweenFloorAndAbs() const { return fMustDoOpBetweenFloorAndAbs; } + + // If false, SkSL uses a workaround so that sk_FragCoord doesn't actually query gl_FragCoord + bool canUseFragCoord() const { return fCanUseFragCoord; } + + // If true, short ints can't represent every integer in the 16-bit two's complement range as + // required by the spec. SKSL will always emit full ints. + bool incompleteShortIntPrecision() const { return fIncompleteShortIntPrecision; } + + // If true, then conditions in for loops need "&& true" to work around driver bugs. + bool addAndTrueToLoopCondition() const { return fAddAndTrueToLoopCondition; } + + // If true, then expressions such as "x && y" or "x || y" are rewritten as + // ternary to work around driver bugs. + bool unfoldShortCircuitAsTernary() const { return fUnfoldShortCircuitAsTernary; } + + bool emulateAbsIntFunction() const { return fEmulateAbsIntFunction; } + + bool rewriteDoWhileLoops() const { return fRewriteDoWhileLoops; } + + bool removePowWithConstantExponent() const { return fRemovePowWithConstantExponent; } + + bool requiresLocalOutputColorForFBFetch() const { return fRequiresLocalOutputColorForFBFetch; } + + bool mustObfuscateUniformColor() const { return fMustObfuscateUniformColor; } + + // The D3D shader compiler, when targeting PS 3.0 (ie within ANGLE) fails to compile certain + // constructs. See detailed comments in GrGLCaps.cpp. + bool mustGuardDivisionEvenAfterExplicitZeroCheck() const { + return fMustGuardDivisionEvenAfterExplicitZeroCheck; + } + + // On Nexus 6, the GL context can get lost if a shader does not write a value to gl_FragColor. + // https://bugs.chromium.org/p/chromium/issues/detail?id=445377 + bool mustWriteToFragColor() const { return fMustWriteToFragColor; } + + // The Android emulator claims samplerExternalOES is an unknown type if a default precision + // statement is made for the type. + bool noDefaultPrecisionForExternalSamplers() const { + return fNoDefaultPrecisionForExternalSamplers; + } + + // Returns the string of an extension that must be enabled in the shader to support + // derivatives. If nullptr is returned then no extension needs to be enabled. Before calling + // this function, the caller should check that shaderDerivativeSupport exists. + const char* shaderDerivativeExtensionString() const { + SkASSERT(this->shaderDerivativeSupport()); + return fShaderDerivativeExtensionString; + } + + // Returns the string of an extension that must be enabled in the shader to support geometry + // shaders. If nullptr is returned then no extension needs to be enabled. Before calling this + // function, the caller must verify that geometryShaderSupport exists. + const char* geometryShaderExtensionString() const { + SkASSERT(this->geometryShaderSupport()); + return fGeometryShaderExtensionString; + } + + // Returns the string of an extension that must be enabled in the shader to support + // geometry shader invocations. If nullptr is returned then no extension needs to be enabled. + // Before calling this function, the caller must verify that gsInvocationsSupport exists. + const char* gsInvocationsExtensionString() const { + SkASSERT(this->gsInvocationsSupport()); + return fGSInvocationsExtensionString; + } + + // Returns the string of an extension that will do all necessary coord transfomations needed + // when reading the fragment position. If such an extension does not exisits, this function + // returns a nullptr, and all transforms of the frag position must be done manually in the + // shader. + const char* fragCoordConventionsExtensionString() const { + return fFragCoordConventionsExtensionString; + } + + // This returns the name of an extension that must be enabled in the shader, if such a thing is + // required in order to use a secondary output in the shader. This returns a nullptr if no such + // extension is required. However, the return value of this function does not say whether dual + // source blending is supported. + const char* secondaryOutputExtensionString() const { return fSecondaryOutputExtensionString; } + + // This returns the name of an extension that must be enabled in the shader to support external + // textures. In some cases, two extensions must be enabled - the second extension is returned + // by secondExternalTextureExtensionString(). If that function returns nullptr, then only one + // extension is required. + const char* externalTextureExtensionString() const { + SkASSERT(this->externalTextureSupport()); + return fExternalTextureExtensionString; + } + + const char* secondExternalTextureExtensionString() const { + SkASSERT(this->externalTextureSupport()); + return fSecondExternalTextureExtensionString; + } + + const char* noperspectiveInterpolationExtensionString() const { + SkASSERT(this->noperspectiveInterpolationSupport()); + return fNoPerspectiveInterpolationExtensionString; + } + + const char* sampleVariablesExtensionString() const { + SkASSERT(this->sampleVariablesSupport() || this->sampleVariablesStencilSupport()); + return fSampleVariablesExtensionString; + } + + int maxFragmentSamplers() const { return fMaxFragmentSamplers; } + + bool textureSwizzleAppliedInShader() const { return fTextureSwizzleAppliedInShader; } + + GrGLSLGeneration generation() const { return fGLSLGeneration; } + +private: + void applyOptionsOverrides(const GrContextOptions& options); + + GrGLSLGeneration fGLSLGeneration; + + bool fShaderDerivativeSupport : 1; + bool fGeometryShaderSupport : 1; + bool fGSInvocationsSupport : 1; + bool fPathRenderingSupport : 1; + bool fDstReadInShaderSupport : 1; + bool fDualSourceBlendingSupport : 1; + bool fIntegerSupport : 1; + bool fFBFetchSupport : 1; + bool fFBFetchNeedsCustomOutput : 1; + bool fUsesPrecisionModifiers : 1; + bool fFlatInterpolationSupport : 1; + bool fPreferFlatInterpolation : 1; + bool fNoPerspectiveInterpolationSupport : 1; + bool fSampleVariablesSupport : 1; + bool fSampleVariablesStencilSupport : 1; + bool fExternalTextureSupport : 1; + bool fVertexIDSupport : 1; + bool fFPManipulationSupport : 1; + bool fFloatIs32Bits : 1; + bool fHalfIs32Bits : 1; + bool fHasLowFragmentPrecision : 1; + bool fTextureSwizzleAppliedInShader : 1; + + // Used by SkSL to know when to generate polyfills. + bool fBuiltinFMASupport : 1; + + // Used for specific driver bug work arounds + bool fCanUseAnyFunctionInShader : 1; + bool fCanUseMinAndAbsTogether : 1; + bool fCanUseFractForNegativeValues : 1; + bool fMustForceNegatedAtanParamToFloat : 1; + bool fAtan2ImplementedAsAtanYOverX : 1; + bool fMustDoOpBetweenFloorAndAbs : 1; + bool fRequiresLocalOutputColorForFBFetch : 1; + bool fMustObfuscateUniformColor : 1; + bool fMustGuardDivisionEvenAfterExplicitZeroCheck : 1; + bool fCanUseFragCoord : 1; + bool fIncompleteShortIntPrecision : 1; + bool fAddAndTrueToLoopCondition : 1; + bool fUnfoldShortCircuitAsTernary : 1; + bool fEmulateAbsIntFunction : 1; + bool fRewriteDoWhileLoops : 1; + bool fRemovePowWithConstantExponent : 1; + bool fMustWriteToFragColor : 1; + bool fNoDefaultPrecisionForExternalSamplers : 1; + + const char* fVersionDeclString; + + const char* fShaderDerivativeExtensionString; + const char* fGeometryShaderExtensionString; + const char* fGSInvocationsExtensionString; + const char* fFragCoordConventionsExtensionString; + const char* fSecondaryOutputExtensionString; + const char* fExternalTextureExtensionString; + const char* fSecondExternalTextureExtensionString; + const char* fNoPerspectiveInterpolationExtensionString; + const char* fSampleVariablesExtensionString; + + const char* fFBFetchColorName; + const char* fFBFetchExtensionString; + + int fMaxFragmentSamplers; + + AdvBlendEqInteraction fAdvBlendEqInteraction; + + friend class GrCaps; // For initialization. + friend class GrDawnCaps; + friend class GrGLCaps; + friend class GrMockCaps; + friend class GrMtlCaps; + friend class GrVkCaps; + friend class SkSL::ShaderCapsFactory; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrShaderUtils.cpp b/gfx/skia/skia/src/gpu/GrShaderUtils.cpp new file mode 100644 index 0000000000..335922b403 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrShaderUtils.cpp @@ -0,0 +1,220 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrShaderUtils.h" +#include "src/sksl/SkSLString.h" + +namespace GrShaderUtils { + +class GLSLPrettyPrint { +public: + GLSLPrettyPrint() {} + + SkSL::String prettify(const SkSL::String& string) { + fTabs = 0; + fFreshline = true; + + // If a string breaks while in the middle 'parse until' we need to continue parsing on the + // next string + fInParseUntilNewline = false; + fInParseUntil = false; + + int parensDepth = 0; + + // setup pretty state + fIndex = 0; + fLength = string.length(); + fInput = string.c_str(); + + while (fLength > fIndex) { + /* the heart and soul of our prettification algorithm. The rules should hopefully + * be self explanatory. For '#' and '//' tokens we parse until we reach a newline. + * + * For long style comments like this one, we search for the ending token. We also + * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines + * ourselves. This allows us to remain in control of line numbers, and matching + * tabs Existing tabs in the input string are copied over too, but this will look + * funny + * + * '{' and '}' are handled in basically the same way. We add a newline if we aren't + * on a fresh line, dirty the line, then add a second newline, ie braces are always + * on their own lines indented properly. The one funkiness here is structs print + * with the semicolon on its own line. Its not a problem for a glsl compiler though + * + * '(' and ')' are basically ignored, except as a sign we need to ignore ';' ala + * in for loops. + * + * ';' means add a new line + * + * '\t' and '\n' are ignored in general parsing for backwards compatability with + * existing shader code and we also have a special case for handling whitespace + * at the beginning of fresh lines. + * + * Otherwise just add the new character to the pretty string, indenting if + * necessary. + */ + if (fInParseUntilNewline) { + this->parseUntilNewline(); + } else if (fInParseUntil) { + this->parseUntil(fInParseUntilToken); + } else if (this->hasToken("#") || this->hasToken("//")) { + this->parseUntilNewline(); + } else if (this->hasToken("/*")) { + this->parseUntil("*/"); + } else if ('{' == fInput[fIndex]) { + this->newline(); + this->appendChar('{'); + fTabs++; + this->newline(); + } else if ('}' == fInput[fIndex]) { + fTabs--; + this->newline(); + this->appendChar('}'); + this->newline(); + } else if (this->hasToken(")")) { + parensDepth--; + } else if (this->hasToken("(")) { + parensDepth++; + } else if (!parensDepth && this->hasToken(";")) { + this->newline(); + } else if ('\t' == fInput[fIndex] || '\n' == fInput[fIndex] || + (fFreshline && ' ' == fInput[fIndex])) { + fIndex++; + } else { + this->appendChar(fInput[fIndex]); + } + } + + return fPretty; + } + +private: + void appendChar(char c) { + this->tabString(); + fPretty.appendf("%c", fInput[fIndex++]); + fFreshline = false; + } + + // hasToken automatically consumes the next token, if it is a match, and then tabs + // if necessary, before inserting the token into the pretty string + bool hasToken(const char* token) { + size_t i = fIndex; + for (size_t j = 0; token[j] && fLength > i; i++, j++) { + if (token[j] != fInput[i]) { + return false; + } + } + this->tabString(); + fIndex = i; + fPretty.append(token); + fFreshline = false; + return true; + } + + void parseUntilNewline() { + while (fLength > fIndex) { + if ('\n' == fInput[fIndex]) { + fIndex++; + this->newline(); + fInParseUntilNewline = false; + break; + } + fPretty.appendf("%c", fInput[fIndex++]); + fInParseUntilNewline = true; + } + } + + // this code assumes it is not actually searching for a newline. If you need to search for a + // newline, then use the function above. If you do search for a newline with this function + // it will consume the entire string and the output will certainly not be prettified + void parseUntil(const char* token) { + while (fLength > fIndex) { + // For embedded newlines, this code will make sure to embed the newline in the + // pretty string, increase the linecount, and tab out the next line to the appropriate + // place + if ('\n' == fInput[fIndex]) { + this->newline(); + this->tabString(); + fIndex++; + } + if (this->hasToken(token)) { + fInParseUntil = false; + break; + } + fFreshline = false; + fPretty.appendf("%c", fInput[fIndex++]); + fInParseUntil = true; + fInParseUntilToken = token; + } + } + + // We only tab if on a newline, otherwise consider the line tabbed + void tabString() { + if (fFreshline) { + for (int t = 0; t < fTabs; t++) { + fPretty.append("\t"); + } + } + } + + // newline is really a request to add a newline, if we are on a fresh line there is no reason + // to add another newline + void newline() { + if (!fFreshline) { + fFreshline = true; + fPretty.append("\n"); + } + } + + bool fFreshline; + int fTabs; + size_t fIndex, fLength; + const char* fInput; + SkSL::String fPretty; + + // Some helpers for parseUntil when we go over a string length + bool fInParseUntilNewline; + bool fInParseUntil; + const char* fInParseUntilToken; +}; + +SkSL::String PrettyPrint(const SkSL::String& string) { + GLSLPrettyPrint pp; + return pp.prettify(string); +} + +// Prints shaders one line at the time. This ensures they don't get truncated by the adb log. +void PrintLineByLine(const char* header, const SkSL::String& text) { + if (header) { + SkDebugf("%s\n", header); + } + SkTArray lines; + SkStrSplit(text.c_str(), "\n", kStrict_SkStrSplitMode, &lines); + for (int i = 0; i < lines.count(); ++i) { + SkDebugf("%4i\t%s\n", i + 1, lines[i].c_str()); + } +} + +GrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler() { + class GrDefaultShaderErrorHandler : public GrContextOptions::ShaderErrorHandler { + public: + void compileError(const char* shader, const char* errors) override { + SkDebugf("Shader compilation error\n" + "------------------------\n"); + PrintLineByLine(nullptr, shader); + SkDebugf("Errors:\n%s\n", errors); + SkDEBUGFAIL("Shader compilation failed!"); + } + }; + + static GrDefaultShaderErrorHandler gHandler; + return &gHandler; +} + +} // namespace diff --git a/gfx/skia/skia/src/gpu/GrShaderUtils.h b/gfx/skia/skia/src/gpu/GrShaderUtils.h new file mode 100644 index 0000000000..c30120dcc8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrShaderUtils.h @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrShaderUtils_DEFINED +#define GrShaderUtils_DEFINED + +#include "include/core/SkTypes.h" +#include "include/gpu/GrContextOptions.h" +#include "src/sksl/SkSLString.h" + +namespace GrShaderUtils { + +SkSL::String PrettyPrint(const SkSL::String& string); +void PrintLineByLine(const char* header, const SkSL::String& text); +GrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler(); + +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrShaderVar.cpp b/gfx/skia/skia/src/gpu/GrShaderVar.cpp new file mode 100644 index 0000000000..6f7ccccd60 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrShaderVar.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrShaderVar.h" + +static const char* type_modifier_string(GrShaderVar::TypeModifier t) { + switch (t) { + case GrShaderVar::kNone_TypeModifier: return ""; + case GrShaderVar::kIn_TypeModifier: return "in"; + case GrShaderVar::kInOut_TypeModifier: return "inout"; + case GrShaderVar::kOut_TypeModifier: return "out"; + case GrShaderVar::kUniform_TypeModifier: return "uniform"; + } + SK_ABORT("Unknown shader variable type modifier."); +} + +void GrShaderVar::setIOType(GrIOType ioType) { + switch (ioType) { + case kRW_GrIOType: + return; + case kRead_GrIOType: + this->addModifier("readonly"); + return; + case kWrite_GrIOType: + this->addModifier("writeonly"); + return; + } + SK_ABORT("Unknown io type."); +} + +void GrShaderVar::appendDecl(const GrShaderCaps* shaderCaps, SkString* out) const { + SkString layout = fLayoutQualifier; + if (!fLayoutQualifier.isEmpty()) { + out->appendf("layout(%s) ", fLayoutQualifier.c_str()); + } + out->append(fExtraModifiers); + if (this->getTypeModifier() != kNone_TypeModifier) { + out->append(type_modifier_string(this->getTypeModifier())); + out->append(" "); + } + GrSLType effectiveType = this->getType(); + if (this->isArray()) { + if (this->isUnsizedArray()) { + out->appendf("%s %s[]", GrGLSLTypeString(effectiveType), this->getName().c_str()); + } else { + SkASSERT(this->getArrayCount() > 0); + out->appendf("%s %s[%d]", + GrGLSLTypeString(effectiveType), + this->getName().c_str(), + this->getArrayCount()); + } + } else { + out->appendf("%s %s", GrGLSLTypeString(effectiveType), this->getName().c_str()); + } +} diff --git a/gfx/skia/skia/src/gpu/GrShaderVar.h b/gfx/skia/skia/src/gpu/GrShaderVar.h new file mode 100644 index 0000000000..791d3594ae --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrShaderVar.h @@ -0,0 +1,295 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrShaderVar_DEFINED +#define GrShaderVar_DEFINED + +#include "include/core/SkString.h" +#include "include/private/GrTypesPriv.h" + +class GrShaderCaps; + +#define USE_UNIFORM_FLOAT_ARRAYS true + +/** + * Represents a variable in a shader + */ +class GrShaderVar { +public: + enum TypeModifier { + kNone_TypeModifier, + kOut_TypeModifier, + kIn_TypeModifier, + kInOut_TypeModifier, + kUniform_TypeModifier, + }; + + /** + * Values for array count that have special meaning. We allow 1-sized arrays.git + */ + enum { + kNonArray = 0, // not an array + kUnsizedArray = -1, // an unsized array (declared with []) + }; + + /** + * Defaults to a non-arry half with no type modifier or layout qualifier. + */ + GrShaderVar() + : fType(kHalf_GrSLType) + , fTypeModifier(kNone_TypeModifier) + , fCount(kNonArray) + , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) { + } + + GrShaderVar(const SkString& name, GrSLType type, int arrayCount = kNonArray) + : fType(type) + , fTypeModifier(kNone_TypeModifier) + , fCount(arrayCount) + , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) + , fName(name) { + SkASSERT(kVoid_GrSLType != type); + fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS; + } + + GrShaderVar(const char* name, GrSLType type, int arrayCount = kNonArray) + : fType(type) + , fTypeModifier(kNone_TypeModifier) + , fCount(arrayCount) + , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) + , fName(name) { + SkASSERT(kVoid_GrSLType != type); + fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS; + } + + GrShaderVar(const char* name, GrSLType type, TypeModifier typeModifier) + : fType(type) + , fTypeModifier(typeModifier) + , fCount(kNonArray) + , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) + , fName(name) { + SkASSERT(kVoid_GrSLType != type); + } + + GrShaderVar(const char* name, GrSLType type, TypeModifier typeModifier, int arrayCount) + : fType(type) + , fTypeModifier(typeModifier) + , fCount(arrayCount) + , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) + , fName(name) { + SkASSERT(kVoid_GrSLType != type); + } + + GrShaderVar(const GrShaderVar& that) + : fType(that.fType) + , fTypeModifier(that.fTypeModifier) + , fCount(that.fCount) + , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) + , fName(that.fName) + , fLayoutQualifier(that.fLayoutQualifier) + , fExtraModifiers(that.fExtraModifiers) { + SkASSERT(kVoid_GrSLType != that.getType()); + } + + /** + * Sets as a non-array. + */ + void set(GrSLType type, + const SkString& name, + TypeModifier typeModifier = kNone_TypeModifier, + const char* layoutQualifier = nullptr, + const char* extraModifiers = nullptr, + bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) { + SkASSERT(kVoid_GrSLType != type); + fType = type; + fTypeModifier = typeModifier; + fName = name; + fCount = kNonArray; + fLayoutQualifier = layoutQualifier; + if (extraModifiers) { + fExtraModifiers.printf("%s ", extraModifiers); + } + fUseUniformFloatArrays = useUniformFloatArrays; + } + + /** + * Sets as a non-array. + */ + void set(GrSLType type, + const char* name, + TypeModifier typeModifier = kNone_TypeModifier, + const char* layoutQualifier = nullptr, + const char* extraModifiers = nullptr, + bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) { + SkASSERT(kVoid_GrSLType != type); + fType = type; + fTypeModifier = typeModifier; + fName = name; + fCount = kNonArray; + fLayoutQualifier = layoutQualifier; + if (extraModifiers) { + fExtraModifiers.printf("%s ", extraModifiers); + } + fUseUniformFloatArrays = useUniformFloatArrays; + } + + /** + * Set all var options + */ + void set(GrSLType type, + const SkString& name, + int count, + TypeModifier typeModifier, + const char* layoutQualifier = nullptr, + const char* extraModifiers = nullptr, + bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) { + SkASSERT(kVoid_GrSLType != type); + fType = type; + fTypeModifier = typeModifier; + fName = name; + fCount = count; + fLayoutQualifier = layoutQualifier; + if (extraModifiers) { + fExtraModifiers.printf("%s ", extraModifiers); + } + fUseUniformFloatArrays = useUniformFloatArrays; + } + + /** + * Set all var options + */ + void set(GrSLType type, + const char* name, + int count, + TypeModifier typeModifier, + const char* layoutQualifier = nullptr, + const char* extraModifiers = nullptr, + bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) { + SkASSERT(kVoid_GrSLType != type); + fType = type; + fTypeModifier = typeModifier; + fName = name; + fCount = count; + fLayoutQualifier = layoutQualifier; + if (extraModifiers) { + fExtraModifiers.printf("%s ", extraModifiers); + } + fUseUniformFloatArrays = useUniformFloatArrays; + } + + /** + * Is the var an array. + */ + bool isArray() const { return kNonArray != fCount; } + /** + * Is this an unsized array, (i.e. declared with []). + */ + bool isUnsizedArray() const { return kUnsizedArray == fCount; } + /** + * Get the array length of the var. + */ + int getArrayCount() const { return fCount; } + /** + * Set the array length of the var + */ + void setArrayCount(int count) { fCount = count; } + /** + * Set to be a non-array. + */ + void setNonArray() { fCount = kNonArray; } + /** + * Set to be an unsized array. + */ + void setUnsizedArray() { fCount = kUnsizedArray; } + + /** + * Access the var name as a writable string + */ + SkString* accessName() { return &fName; } + /** + * Set the var name + */ + void setName(const SkString& n) { fName = n; } + void setName(const char* n) { fName = n; } + + /** + * Get the var name. + */ + const SkString& getName() const { return fName; } + + /** + * Shortcut for this->getName().c_str(); + */ + const char* c_str() const { return this->getName().c_str(); } + + /** + * Get the type of the var + */ + GrSLType getType() const { return fType; } + /** + * Set the type of the var + */ + void setType(GrSLType type) { fType = type; } + + TypeModifier getTypeModifier() const { return fTypeModifier; } + void setTypeModifier(TypeModifier type) { fTypeModifier = type; } + + /** + * Appends to the layout qualifier + */ + void addLayoutQualifier(const char* layoutQualifier) { + if (!layoutQualifier || !strlen(layoutQualifier)) { + return; + } + if (fLayoutQualifier.isEmpty()) { + fLayoutQualifier = layoutQualifier; + } else { + fLayoutQualifier.appendf(", %s", layoutQualifier); + } + } + + void setIOType(GrIOType); + + void addModifier(const char* modifier) { + if (modifier) { + fExtraModifiers.appendf("%s ", modifier); + } + } + + /** + * Write a declaration of this variable to out. + */ + void appendDecl(const GrShaderCaps*, SkString* out) const; + + void appendArrayAccess(int index, SkString* out) const { + out->appendf("%s[%d]%s", + this->getName().c_str(), + index, + fUseUniformFloatArrays ? "" : ".x"); + } + + void appendArrayAccess(const char* indexName, SkString* out) const { + out->appendf("%s[%s]%s", + this->getName().c_str(), + indexName, + fUseUniformFloatArrays ? "" : ".x"); + } + +private: + GrSLType fType; + TypeModifier fTypeModifier; + int fCount; + /// Work around driver bugs on some hardware that don't correctly + /// support uniform float [] + bool fUseUniformFloatArrays; + + SkString fName; + SkString fLayoutQualifier; + SkString fExtraModifiers; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSkSLFPFactoryCache.h b/gfx/skia/skia/src/gpu/GrSkSLFPFactoryCache.h new file mode 100644 index 0000000000..48869f896b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSkSLFPFactoryCache.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSkSLFPFactoryCache_DEFINED +#define GrSkSLFPFactoryCache_DEFINED + +#include "include/core/SkRefCnt.h" + +#include + +class GrSkSLFPFactory; + +// This is a cache used by GrSkSLFP to retain GrSkSLFPFactory instances, so we don't have to +// re-process the SkSL source code every time we create a GrSkSLFP instance. +// For thread safety, it is important that GrSkSLFP only interact with the cache from methods that +// are only called from within the rendering thread, like onCreateGLSLInstance and +// onGetGLSLProcessorKey. +class GrSkSLFPFactoryCache : public SkNVRefCnt { +public: + // Returns a factory by its numeric index, or null if no such factory exists. Indices are + // allocated by GrSkSLFP::NewIndex(). + sk_sp get(int index); + + // Stores a new factory with the given index. + void set(int index, sk_sp factory); + + ~GrSkSLFPFactoryCache(); + +private: + std::vector fFactories; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.cpp b/gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.cpp new file mode 100644 index 0000000000..4245e9ed29 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.cpp @@ -0,0 +1,395 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSemaphore.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTaskGroup.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDeferredProxyUploader.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrSWMaskHelper.h" +#include "src/gpu/GrSoftwarePathRenderer.h" +#include "src/gpu/GrSurfaceContextPriv.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrDrawOp.h" + +//////////////////////////////////////////////////////////////////////////////// +GrPathRenderer::CanDrawPath +GrSoftwarePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + // Pass on any style that applies. The caller will apply the style if a suitable renderer is + // not found and try again with the new GrShape. + if (!args.fShape->style().applies() && SkToBool(fProxyProvider) && + (args.fAAType == GrAAType::kCoverage || args.fAAType == GrAAType::kNone)) { + // This is the fallback renderer for when a path is too complicated for the GPU ones. + return CanDrawPath::kAsBackup; + } + return CanDrawPath::kNo; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool get_unclipped_shape_dev_bounds(const GrShape& shape, const SkMatrix& matrix, + SkIRect* devBounds) { + SkRect shapeBounds = shape.styledBounds(); + if (shapeBounds.isEmpty()) { + return false; + } + SkRect shapeDevBounds; + matrix.mapRect(&shapeDevBounds, shapeBounds); + // Even though these are "unclipped" bounds we still clip to the int32_t range. + // This is the largest int32_t that is representable exactly as a float. The next 63 larger ints + // would round down to this value when cast to a float, but who really cares. + // INT32_MIN is exactly representable. + static constexpr int32_t kMaxInt = 2147483520; + if (!shapeDevBounds.intersect(SkRect::MakeLTRB(INT32_MIN, INT32_MIN, kMaxInt, kMaxInt))) { + return false; + } + // Make sure that the resulting SkIRect can have representable width and height + if (SkScalarRoundToInt(shapeDevBounds.width()) > kMaxInt || + SkScalarRoundToInt(shapeDevBounds.height()) > kMaxInt) { + return false; + } + shapeDevBounds.roundOut(devBounds); + return true; +} + +// Gets the shape bounds, the clip bounds, and the intersection (if any). Returns false if there +// is no intersection. +bool GrSoftwarePathRenderer::GetShapeAndClipBounds(GrRenderTargetContext* renderTargetContext, + const GrClip& clip, + const GrShape& shape, + const SkMatrix& matrix, + SkIRect* unclippedDevShapeBounds, + SkIRect* clippedDevShapeBounds, + SkIRect* devClipBounds) { + // compute bounds as intersection of rt size, clip, and path + clip.getConservativeBounds(renderTargetContext->width(), + renderTargetContext->height(), + devClipBounds); + + if (!get_unclipped_shape_dev_bounds(shape, matrix, unclippedDevShapeBounds)) { + *unclippedDevShapeBounds = SkIRect::EmptyIRect(); + *clippedDevShapeBounds = SkIRect::EmptyIRect(); + return false; + } + if (!clippedDevShapeBounds->intersect(*devClipBounds, *unclippedDevShapeBounds)) { + *clippedDevShapeBounds = SkIRect::EmptyIRect(); + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrSoftwarePathRenderer::DrawNonAARect(GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkMatrix& localMatrix) { + renderTargetContext->priv().stencilRect(clip, &userStencilSettings, std::move(paint), GrAA::kNo, + viewMatrix, rect, &localMatrix); +} + +void GrSoftwarePathRenderer::DrawAroundInvPath(GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkIRect& devClipBounds, + const SkIRect& devPathBounds) { + SkMatrix invert; + if (!viewMatrix.invert(&invert)) { + return; + } + + SkRect rect; + if (devClipBounds.fTop < devPathBounds.fTop) { + rect.setLTRB(SkIntToScalar(devClipBounds.fLeft), SkIntToScalar(devClipBounds.fTop), + SkIntToScalar(devClipBounds.fRight), SkIntToScalar(devPathBounds.fTop)); + DrawNonAARect(renderTargetContext, GrPaint::Clone(paint), userStencilSettings, clip, + SkMatrix::I(), rect, invert); + } + if (devClipBounds.fLeft < devPathBounds.fLeft) { + rect.setLTRB(SkIntToScalar(devClipBounds.fLeft), SkIntToScalar(devPathBounds.fTop), + SkIntToScalar(devPathBounds.fLeft), SkIntToScalar(devPathBounds.fBottom)); + DrawNonAARect(renderTargetContext, GrPaint::Clone(paint), userStencilSettings, clip, + SkMatrix::I(), rect, invert); + } + if (devClipBounds.fRight > devPathBounds.fRight) { + rect.setLTRB(SkIntToScalar(devPathBounds.fRight), SkIntToScalar(devPathBounds.fTop), + SkIntToScalar(devClipBounds.fRight), SkIntToScalar(devPathBounds.fBottom)); + DrawNonAARect(renderTargetContext, GrPaint::Clone(paint), userStencilSettings, clip, + SkMatrix::I(), rect, invert); + } + if (devClipBounds.fBottom > devPathBounds.fBottom) { + rect.setLTRB(SkIntToScalar(devClipBounds.fLeft), SkIntToScalar(devPathBounds.fBottom), + SkIntToScalar(devClipBounds.fRight), SkIntToScalar(devClipBounds.fBottom)); + DrawNonAARect(renderTargetContext, std::move(paint), userStencilSettings, clip, + SkMatrix::I(), rect, invert); + } +} + +void GrSoftwarePathRenderer::DrawToTargetWithShapeMask( + sk_sp proxy, + GrColorType srcColorType, + GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkIPoint& textureOriginInDeviceSpace, + const SkIRect& deviceSpaceRectToDraw) { + SkMatrix invert; + if (!viewMatrix.invert(&invert)) { + return; + } + + SkRect dstRect = SkRect::Make(deviceSpaceRectToDraw); + + // We use device coords to compute the texture coordinates. We take the device coords and apply + // a translation so that the top-left of the device bounds maps to 0,0, and then a scaling + // matrix to normalized coords. + SkMatrix maskMatrix = SkMatrix::MakeTrans(SkIntToScalar(-textureOriginInDeviceSpace.fX), + SkIntToScalar(-textureOriginInDeviceSpace.fY)); + maskMatrix.preConcat(viewMatrix); + paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Make( + std::move(proxy), srcColorType, maskMatrix, GrSamplerState::Filter::kNearest)); + DrawNonAARect(renderTargetContext, std::move(paint), userStencilSettings, clip, SkMatrix::I(), + dstRect, invert); +} + +static sk_sp make_deferred_mask_texture_proxy(GrRecordingContext* context, + SkBackingFit fit, + int width, int height) { + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + const GrCaps* caps = context->priv().caps(); + + GrSurfaceDesc desc; + desc.fWidth = width; + desc.fHeight = height; + desc.fConfig = kAlpha_8_GrPixelConfig; + + const GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kAlpha_8, + GrRenderable::kNo); + + return proxyProvider->createProxy(format, desc, GrRenderable::kNo, 1, kTopLeft_GrSurfaceOrigin, + GrMipMapped::kNo, fit, SkBudgeted::kYes, GrProtected::kNo); +} + +namespace { + +/** + * Payload class for use with GrTDeferredProxyUploader. The software path renderer only draws + * a single path into the mask texture. This stores all of the information needed by the worker + * thread's call to drawShape (see below, in onDrawPath). + */ +class SoftwarePathData { +public: + SoftwarePathData(const SkIRect& maskBounds, const SkMatrix& viewMatrix, const GrShape& shape, + GrAA aa) + : fMaskBounds(maskBounds) + , fViewMatrix(viewMatrix) + , fShape(shape) + , fAA(aa) {} + + const SkIRect& getMaskBounds() const { return fMaskBounds; } + const SkMatrix* getViewMatrix() const { return &fViewMatrix; } + const GrShape& getShape() const { return fShape; } + GrAA getAA() const { return fAA; } + +private: + SkIRect fMaskBounds; + SkMatrix fViewMatrix; + GrShape fShape; + GrAA fAA; +}; + +// When the SkPathRef genID changes, invalidate a corresponding GrResource described by key. +class PathInvalidator : public SkPathRef::GenIDChangeListener { +public: + PathInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID) + : fMsg(key, contextUniqueID) {} + +private: + GrUniqueKeyInvalidatedMessage fMsg; + + void onChange() override { + SkMessageBus::Post(fMsg); + } +}; + +} + +//////////////////////////////////////////////////////////////////////////////// +// return true on success; false on failure +bool GrSoftwarePathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrSoftwarePathRenderer::onDrawPath"); + if (!fProxyProvider) { + return false; + } + + SkASSERT(!args.fShape->style().applies()); + // We really need to know if the shape will be inverse filled or not + // If the path is hairline, ignore inverse fill. + bool inverseFilled = args.fShape->inverseFilled() && + !IsStrokeHairlineOrEquivalent(args.fShape->style(), + *args.fViewMatrix, nullptr); + + SkIRect unclippedDevShapeBounds, clippedDevShapeBounds, devClipBounds; + // To prevent overloading the cache with entries during animations we limit the cache of masks + // to cases where the matrix preserves axis alignment. + bool useCache = fAllowCaching && !inverseFilled && args.fViewMatrix->preservesAxisAlignment() && + args.fShape->hasUnstyledKey() && (GrAAType::kCoverage == args.fAAType); + + if (!GetShapeAndClipBounds(args.fRenderTargetContext, + *args.fClip, *args.fShape, + *args.fViewMatrix, &unclippedDevShapeBounds, + &clippedDevShapeBounds, + &devClipBounds)) { + if (inverseFilled) { + DrawAroundInvPath(args.fRenderTargetContext, std::move(args.fPaint), + *args.fUserStencilSettings, *args.fClip, *args.fViewMatrix, + devClipBounds, unclippedDevShapeBounds); + } + return true; + } + + const SkIRect* boundsForMask = &clippedDevShapeBounds; + if (useCache) { + // Use the cache only if >50% of the path is visible. + int unclippedWidth = unclippedDevShapeBounds.width(); + int unclippedHeight = unclippedDevShapeBounds.height(); + int64_t unclippedArea = sk_64_mul(unclippedWidth, unclippedHeight); + int64_t clippedArea = sk_64_mul(clippedDevShapeBounds.width(), + clippedDevShapeBounds.height()); + int maxTextureSize = args.fRenderTargetContext->caps()->maxTextureSize(); + if (unclippedArea > 2 * clippedArea || unclippedWidth > maxTextureSize || + unclippedHeight > maxTextureSize) { + useCache = false; + } else { + boundsForMask = &unclippedDevShapeBounds; + } + } + + GrUniqueKey maskKey; + if (useCache) { + // We require the upper left 2x2 of the matrix to match exactly for a cache hit. + SkScalar sx = args.fViewMatrix->get(SkMatrix::kMScaleX); + SkScalar sy = args.fViewMatrix->get(SkMatrix::kMScaleY); + SkScalar kx = args.fViewMatrix->get(SkMatrix::kMSkewX); + SkScalar ky = args.fViewMatrix->get(SkMatrix::kMSkewY); + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(&maskKey, kDomain, 5 + args.fShape->unstyledKeySize(), + "SW Path Mask"); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // Fractional translate does not affect caching on Android. This is done for better cache + // hit ratio and speed, but it is matching HWUI behavior, which doesn't consider the matrix + // at all when caching paths. + SkFixed fracX = 0; + SkFixed fracY = 0; +#else + SkScalar tx = args.fViewMatrix->get(SkMatrix::kMTransX); + SkScalar ty = args.fViewMatrix->get(SkMatrix::kMTransY); + // Allow 8 bits each in x and y of subpixel positioning. + SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00; + SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00; +#endif + builder[0] = SkFloat2Bits(sx); + builder[1] = SkFloat2Bits(sy); + builder[2] = SkFloat2Bits(kx); + builder[3] = SkFloat2Bits(ky); + // Distinguish between hairline and filled paths. For hairlines, we also need to include + // the cap. (SW grows hairlines by 0.5 pixel with round and square caps). Note that + // stroke-and-fill of hairlines is turned into pure fill by SkStrokeRec, so this covers + // all cases we might see. + uint32_t styleBits = args.fShape->style().isSimpleHairline() ? + ((args.fShape->style().strokeRec().getCap() << 1) | 1) : 0; + builder[4] = fracX | (fracY >> 8) | (styleBits << 16); + args.fShape->writeUnstyledKey(&builder[5]); + } + + sk_sp proxy; + if (useCache) { + proxy = fProxyProvider->findOrCreateProxyByUniqueKey(maskKey, GrColorType::kAlpha_8, + kTopLeft_GrSurfaceOrigin); + } + if (!proxy) { + SkBackingFit fit = useCache ? SkBackingFit::kExact : SkBackingFit::kApprox; + GrAA aa = GrAA(GrAAType::kCoverage == args.fAAType); + + SkTaskGroup* taskGroup = nullptr; + if (auto direct = args.fContext->priv().asDirectContext()) { + taskGroup = direct->priv().getTaskGroup(); + } + + if (taskGroup) { + proxy = make_deferred_mask_texture_proxy(args.fContext, fit, + boundsForMask->width(), + boundsForMask->height()); + if (!proxy) { + return false; + } + + auto uploader = skstd::make_unique>( + *boundsForMask, *args.fViewMatrix, *args.fShape, aa); + GrTDeferredProxyUploader* uploaderRaw = uploader.get(); + + auto drawAndUploadMask = [uploaderRaw] { + TRACE_EVENT0("skia.gpu", "Threaded SW Mask Render"); + GrSWMaskHelper helper(uploaderRaw->getPixels()); + if (helper.init(uploaderRaw->data().getMaskBounds())) { + helper.drawShape(uploaderRaw->data().getShape(), + *uploaderRaw->data().getViewMatrix(), + SkRegion::kReplace_Op, uploaderRaw->data().getAA(), 0xFF); + } else { + SkDEBUGFAIL("Unable to allocate SW mask."); + } + uploaderRaw->signalAndFreeData(); + }; + taskGroup->add(std::move(drawAndUploadMask)); + proxy->texPriv().setDeferredUploader(std::move(uploader)); + } else { + GrSWMaskHelper helper; + if (!helper.init(*boundsForMask)) { + return false; + } + helper.drawShape(*args.fShape, *args.fViewMatrix, SkRegion::kReplace_Op, aa, 0xFF); + proxy = helper.toTextureProxy(args.fContext, fit); + } + + if (!proxy) { + return false; + } + if (useCache) { + SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin); + fProxyProvider->assignUniqueKeyToProxy(maskKey, proxy.get()); + args.fShape->addGenIDChangeListener( + sk_make_sp(maskKey, args.fContext->priv().contextID())); + } + } + if (inverseFilled) { + DrawAroundInvPath(args.fRenderTargetContext, GrPaint::Clone(args.fPaint), + *args.fUserStencilSettings, *args.fClip, *args.fViewMatrix, devClipBounds, + unclippedDevShapeBounds); + } + DrawToTargetWithShapeMask( + std::move(proxy), GrColorType::kAlpha_8, args.fRenderTargetContext, + std::move(args.fPaint), *args.fUserStencilSettings, *args.fClip, *args.fViewMatrix, + SkIPoint{boundsForMask->fLeft, boundsForMask->fTop}, *boundsForMask); + + return true; +} diff --git a/gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.h b/gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.h new file mode 100644 index 0000000000..d407791137 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSoftwarePathRenderer.h @@ -0,0 +1,79 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSoftwarePathRenderer_DEFINED +#define GrSoftwarePathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +class GrProxyProvider; +class GrTextureProxy; + +/** + * This class uses the software side to render a path to an SkBitmap and + * then uploads the result to the gpu + */ +class GrSoftwarePathRenderer : public GrPathRenderer { +public: + GrSoftwarePathRenderer(GrProxyProvider* proxyProvider, bool allowCaching) + : fProxyProvider(proxyProvider) + , fAllowCaching(allowCaching) { + } + + static bool GetShapeAndClipBounds(GrRenderTargetContext*, + const GrClip& clip, + const GrShape& shape, + const SkMatrix& matrix, + SkIRect* unclippedDevShapeBounds, + SkIRect* clippedDevShapeBounds, + SkIRect* devClipBounds); + +private: + static void DrawNonAARect(GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkMatrix& localMatrix); + static void DrawAroundInvPath(GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkIRect& devClipBounds, + const SkIRect& devPathBounds); + + // This utility draws a path mask using a provided paint. The rectangle is drawn in device + // space. The 'viewMatrix' will be used to ensure the correct local coords are provided to + // any fragment processors in the paint. + static void DrawToTargetWithShapeMask(sk_sp proxy, + GrColorType srcColorType, + GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkIPoint& textureOriginInDeviceSpace, + const SkIRect& deviceSpaceRectToDraw); + + StencilSupport onGetStencilSupport(const GrShape&) const override { + return GrPathRenderer::kNoSupport_StencilSupport; + } + + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; + +private: + GrProxyProvider* fProxyProvider; + bool fAllowCaching; + + typedef GrPathRenderer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrStencilAttachment.cpp b/gfx/skia/skia/src/gpu/GrStencilAttachment.cpp new file mode 100644 index 0000000000..ac1c88d2c9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStencilAttachment.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrResourceKey.h" +#include "src/gpu/GrStencilAttachment.h" + +void GrStencilAttachment::ComputeSharedStencilAttachmentKey(int width, int height, int sampleCnt, + GrUniqueKey* key) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(key, kDomain, 3); + builder[0] = width; + builder[1] = height; + builder[2] = sampleCnt; +} diff --git a/gfx/skia/skia/src/gpu/GrStencilAttachment.h b/gfx/skia/skia/src/gpu/GrStencilAttachment.h new file mode 100644 index 0000000000..5a56d3022b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStencilAttachment.h @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrStencilAttachment_DEFINED +#define GrStencilAttachment_DEFINED + +#include "include/gpu/GrGpuResource.h" +#include "src/core/SkClipStack.h" + +class GrRenderTarget; +class GrResourceKey; + +class GrStencilAttachment : public GrGpuResource { +public: + ~GrStencilAttachment() override { + // TODO: allow SB to be purged and detach itself from rts + } + + int width() const { return fWidth; } + int height() const { return fHeight; } + int bits() const { return fBits; } + int numSamples() const { return fSampleCnt; } + + bool hasPerformedInitialClear() const { return fHasPerformedInitialClear; } + void markHasPerformedInitialClear() { fHasPerformedInitialClear = true; } + + // We create a unique stencil buffer at each width, height and sampleCnt and share it for + // all render targets that require a stencil with those params. + static void ComputeSharedStencilAttachmentKey(int width, int height, int sampleCnt, + GrUniqueKey* key); + +protected: + GrStencilAttachment(GrGpu* gpu, int width, int height, int bits, int sampleCnt) + : INHERITED(gpu) + , fWidth(width) + , fHeight(height) + , fBits(bits) + , fSampleCnt(sampleCnt) { + } + +private: + const char* getResourceType() const override { return "Stencil"; } + + int fWidth; + int fHeight; + int fBits; + int fSampleCnt; + bool fHasPerformedInitialClear = false; + + typedef GrGpuResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrStencilClip.h b/gfx/skia/skia/src/gpu/GrStencilClip.h new file mode 100644 index 0000000000..d45d392e48 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStencilClip.h @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStencilClip_DEFINED +#define GrStencilClip_DEFINED + +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrFixedClip.h" + +/** + * Implements GrHardClip with the currently-existing stencil buffer contents and GrFixedClip. + */ +class GrStencilClip final : public GrHardClip { +public: + GrStencilClip(uint32_t stencilStackID = SK_InvalidGenID) : fStencilStackID(stencilStackID) {} + + explicit GrStencilClip(const SkIRect& scissorRect, uint32_t stencilStackID = SK_InvalidGenID) + : fFixedClip(scissorRect) + , fStencilStackID(stencilStackID) { + } + + const GrFixedClip& fixedClip() const { return fFixedClip; } + GrFixedClip& fixedClip() { return fFixedClip; } + + bool stencilStackID() const { return fStencilStackID; } + bool hasStencilClip() const { return SK_InvalidGenID != fStencilStackID; } + void setStencilClip(uint32_t stencilStackID) { fStencilStackID = stencilStackID; } + + bool quickContains(const SkRect& rect) const override { + return !this->hasStencilClip() && fFixedClip.quickContains(rect); + } + void getConservativeBounds(int width, int height, SkIRect* bounds, bool* iior) const override { + fFixedClip.getConservativeBounds(width, height, bounds, iior); + } + bool isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA* aa) const override { + return !this->hasStencilClip() && fFixedClip.isRRect(rtBounds, rr, aa); + } + bool apply(int rtWidth, int rtHeight, GrAppliedHardClip* out, SkRect* bounds) const override { + if (!fFixedClip.apply(rtWidth, rtHeight, out, bounds)) { + return false; + } + if (this->hasStencilClip()) { + out->addStencilClip(fStencilStackID); + } + return true; + } + +private: + GrFixedClip fFixedClip; + uint32_t fStencilStackID; + + typedef GrClip INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrStencilSettings.cpp b/gfx/skia/skia/src/gpu/GrStencilSettings.cpp new file mode 100644 index 0000000000..d03165cad4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStencilSettings.cpp @@ -0,0 +1,506 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/GrStencilSettings.h" + +#include "src/gpu/GrProcessor.h" + +constexpr const GrUserStencilSettings gUnused( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kKeep, + GrUserStencilOp::kKeep, + 0x0000>() +); + +GR_STATIC_ASSERT(kAll_StencilFlags == (gUnused.fFrontFlags[0] & gUnused.fBackFlags[0])); + +const GrUserStencilSettings& GrUserStencilSettings::kUnused = gUnused; + +void GrStencilSettings::reset(const GrUserStencilSettings& user, bool hasStencilClip, + int numStencilBits) { + uint16_t frontFlags = user.fFrontFlags[hasStencilClip]; + if (frontFlags & kSingleSided_StencilFlag) { + SkASSERT(frontFlags == user.fBackFlags[hasStencilClip]); + fFlags = frontFlags; + if (!this->isDisabled()) { + fFront.reset(user.fFront, hasStencilClip, numStencilBits); + } + return; + } + + uint16_t backFlags = user.fBackFlags[hasStencilClip]; + fFlags = frontFlags & backFlags; + if (this->isDisabled()) { + return; + } + if (!(frontFlags & kDisabled_StencilFlag)) { + fFront.reset(user.fFront, hasStencilClip, numStencilBits); + } else { + fFront.setDisabled(); + } + if (!(backFlags & kDisabled_StencilFlag)) { + fBack.reset(user.fBack, hasStencilClip, numStencilBits); + } else { + fBack.setDisabled(); + } +} + +void GrStencilSettings::reset(const GrStencilSettings& that) { + fFlags = that.fFlags; + if ((kInvalid_PrivateFlag | kDisabled_StencilFlag) & fFlags) { + return; + } + if (!this->isTwoSided()) { + memcpy(&fFront, &that.fFront, sizeof(Face)); + } else { + memcpy(&fFront, &that.fFront, 2 * sizeof(Face)); + GR_STATIC_ASSERT(sizeof(Face) == + offsetof(GrStencilSettings, fBack) - offsetof(GrStencilSettings, fFront)); + } +} + +bool GrStencilSettings::operator==(const GrStencilSettings& that) const { + if ((kInvalid_PrivateFlag | kDisabled_StencilFlag) & (fFlags | that.fFlags)) { + // At least one is invalid and/or disabled. + if (kInvalid_PrivateFlag & (fFlags | that.fFlags)) { + return false; // We never allow invalid stencils to be equal. + } + // They're only equal if both are disabled. + return kDisabled_StencilFlag & (fFlags & that.fFlags); + } + if (kSingleSided_StencilFlag & (fFlags & that.fFlags)) { + return 0 == memcmp(&fFront, &that.fFront, sizeof(Face)); // Both are single sided. + } else if (kSingleSided_StencilFlag & (fFlags | that.fFlags)) { + return false; + } else { + return 0 == memcmp(&fFront, &that.fFront, 2 * sizeof(Face)); + GR_STATIC_ASSERT(sizeof(Face) == + offsetof(GrStencilSettings, fBack) - offsetof(GrStencilSettings, fFront)); + } + // memcmp relies on GrStencilSettings::Face being tightly packed. + GR_STATIC_ASSERT(0 == offsetof(Face, fRef)); + GR_STATIC_ASSERT(2 == sizeof(Face::fRef)); + GR_STATIC_ASSERT(2 == offsetof(Face, fTest)); + GR_STATIC_ASSERT(2 == sizeof(Face::fTest)); + GR_STATIC_ASSERT(4 == offsetof(Face, fTestMask)); + GR_STATIC_ASSERT(2 == sizeof(Face::fTestMask)); + GR_STATIC_ASSERT(6 == offsetof(Face, fPassOp)); + GR_STATIC_ASSERT(1 == sizeof(Face::fPassOp)); + GR_STATIC_ASSERT(7 == offsetof(Face, fFailOp)); + GR_STATIC_ASSERT(1 == sizeof(Face::fFailOp)); + GR_STATIC_ASSERT(8 == offsetof(Face, fWriteMask)); + GR_STATIC_ASSERT(2 == sizeof(Face::fWriteMask)); + GR_STATIC_ASSERT(10 == sizeof(Face)); +} + +static constexpr GrStencilTest gUserStencilTestToRaw[kGrUserStencilTestCount] = { + // Tests that respect the clip. + GrStencilTest::kAlways, // kAlwaysIfInClip (This is only for when there is not a stencil clip). + GrStencilTest::kEqual, // kEqualIfInClip. + GrStencilTest::kLess, // kLessIfInClip. + GrStencilTest::kLEqual, // kLEqualIfInClip. + + // Tests that ignore the clip. + GrStencilTest::kAlways, + GrStencilTest::kNever, + GrStencilTest::kGreater, + GrStencilTest::kGEqual, + GrStencilTest::kLess, + GrStencilTest::kLEqual, + GrStencilTest::kEqual, + GrStencilTest::kNotEqual +}; + +GR_STATIC_ASSERT(0 == (int)GrUserStencilTest::kAlwaysIfInClip); +GR_STATIC_ASSERT(1 == (int)GrUserStencilTest::kEqualIfInClip); +GR_STATIC_ASSERT(2 == (int)GrUserStencilTest::kLessIfInClip); +GR_STATIC_ASSERT(3 == (int)GrUserStencilTest::kLEqualIfInClip); +GR_STATIC_ASSERT(4 == (int)GrUserStencilTest::kAlways); +GR_STATIC_ASSERT(5 == (int)GrUserStencilTest::kNever); +GR_STATIC_ASSERT(6 == (int)GrUserStencilTest::kGreater); +GR_STATIC_ASSERT(7 == (int)GrUserStencilTest::kGEqual); +GR_STATIC_ASSERT(8 == (int)GrUserStencilTest::kLess); +GR_STATIC_ASSERT(9 == (int)GrUserStencilTest::kLEqual); +GR_STATIC_ASSERT(10 == (int)GrUserStencilTest::kEqual); +GR_STATIC_ASSERT(11 == (int)GrUserStencilTest::kNotEqual); + +static constexpr GrStencilOp gUserStencilOpToRaw[kGrUserStencilOpCount] = { + GrStencilOp::kKeep, + + // Ops that only modify user bits. + GrStencilOp::kZero, + GrStencilOp::kReplace, + GrStencilOp::kInvert, + GrStencilOp::kIncWrap, + GrStencilOp::kDecWrap, + GrStencilOp::kIncClamp, // kIncMaybeClamp. + GrStencilOp::kDecClamp, // kDecMaybeClamp. + + // Ops that only modify the clip bit. + GrStencilOp::kZero, // kZeroClipBit. + GrStencilOp::kReplace, // kSetClipBit. + GrStencilOp::kInvert, // kInvertClipBit. + + // Ops that modify clip and user bits. + GrStencilOp::kReplace, // kSetClipAndReplaceUserBits. + GrStencilOp::kZero // kZeroClipAndUserBits. +}; + +GR_STATIC_ASSERT(0 == (int)GrUserStencilOp::kKeep); +GR_STATIC_ASSERT(1 == (int)GrUserStencilOp::kZero); +GR_STATIC_ASSERT(2 == (int)GrUserStencilOp::kReplace); +GR_STATIC_ASSERT(3 == (int)GrUserStencilOp::kInvert); +GR_STATIC_ASSERT(4 == (int)GrUserStencilOp::kIncWrap); +GR_STATIC_ASSERT(5 == (int)GrUserStencilOp::kDecWrap); +GR_STATIC_ASSERT(6 == (int)GrUserStencilOp::kIncMaybeClamp); +GR_STATIC_ASSERT(7 == (int)GrUserStencilOp::kDecMaybeClamp); +GR_STATIC_ASSERT(8 == (int)GrUserStencilOp::kZeroClipBit); +GR_STATIC_ASSERT(9 == (int)GrUserStencilOp::kSetClipBit); +GR_STATIC_ASSERT(10 == (int)GrUserStencilOp::kInvertClipBit); +GR_STATIC_ASSERT(11 == (int)GrUserStencilOp::kSetClipAndReplaceUserBits); +GR_STATIC_ASSERT(12 == (int)GrUserStencilOp::kZeroClipAndUserBits); + +void GrStencilSettings::Face::reset(const GrUserStencilSettings::Face& user, bool hasStencilClip, + int numStencilBits) { + SkASSERT(user.fTest < (GrUserStencilTest)kGrUserStencilTestCount); + SkASSERT(user.fPassOp < (GrUserStencilOp)kGrUserStencilOpCount); + SkASSERT(user.fFailOp < (GrUserStencilOp)kGrUserStencilOpCount); + SkASSERT(numStencilBits > 0 && numStencilBits <= 16); + int clipBit = 1 << (numStencilBits - 1); + int userMask = clipBit - 1; + + GrUserStencilOp maxOp = SkTMax(user.fPassOp, user.fFailOp); + SkDEBUGCODE(GrUserStencilOp otherOp = SkTMin(user.fPassOp, user.fFailOp);) + if (maxOp <= kLastUserOnlyStencilOp) { + // Ops that only modify user bits. + fWriteMask = user.fWriteMask & userMask; + SkASSERT(otherOp <= kLastUserOnlyStencilOp); + } else if (maxOp <= kLastClipOnlyStencilOp) { + // Ops that only modify the clip bit. + fWriteMask = clipBit; + SkASSERT(GrUserStencilOp::kKeep == otherOp || + (otherOp > kLastUserOnlyStencilOp && otherOp <= kLastClipOnlyStencilOp)); + } else { + // Ops that modify both clip and user bits. + fWriteMask = clipBit | (user.fWriteMask & userMask); + SkASSERT(GrUserStencilOp::kKeep == otherOp || otherOp > kLastClipOnlyStencilOp); + } + + fFailOp = gUserStencilOpToRaw[(int)user.fFailOp]; + fPassOp = gUserStencilOpToRaw[(int)user.fPassOp]; + + if (!hasStencilClip || user.fTest > kLastClippedStencilTest) { + // Ignore the clip. + fTestMask = user.fTestMask & userMask; + fTest = gUserStencilTestToRaw[(int)user.fTest]; + } else if (GrUserStencilTest::kAlwaysIfInClip != user.fTest) { + // Respect the clip. + fTestMask = clipBit | (user.fTestMask & userMask); + fTest = gUserStencilTestToRaw[(int)user.fTest]; + } else { + // Test only for clip. + fTestMask = clipBit; + fTest = GrStencilTest::kEqual; + } + + fRef = (clipBit | user.fRef) & (fTestMask | fWriteMask); +} + +void GrStencilSettings::Face::setDisabled() { + memset(this, 0, sizeof(*this)); + GR_STATIC_ASSERT(0 == (int)GrStencilTest::kAlways); + GR_STATIC_ASSERT(0 == (int)GrStencilOp::kKeep); +} + +//////////////////////////////////////////////////////////////////////////////// +// Stencil Rules for Merging user stencil space into clip +// + +/////// +// Replace +static constexpr GrUserStencilSettings gUserToClipReplace( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kSetClipAndReplaceUserBits, + GrUserStencilOp::kZeroClipAndUserBits, + 0xffff>() +); + +static constexpr GrUserStencilSettings gInvUserToClipReplace( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqual, + 0xffff, + GrUserStencilOp::kSetClipAndReplaceUserBits, + GrUserStencilOp::kZeroClipAndUserBits, + 0xffff>() +); + +/////// +// Intersect +static constexpr GrUserStencilSettings gUserToClipIsect( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kLessIfInClip, // "0 < userBits" is equivalent to "0 != userBits". + 0xffff, + GrUserStencilOp::kSetClipAndReplaceUserBits, + GrUserStencilOp::kZeroClipAndUserBits, + 0xffff>() +); + +/////// +// Difference +static constexpr GrUserStencilSettings gUserToClipDiff( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqualIfInClip, + 0xffff, + GrUserStencilOp::kSetClipAndReplaceUserBits, + GrUserStencilOp::kZeroClipAndUserBits, + 0xffff>() +); + +/////// +// Union +static constexpr GrUserStencilSettings gUserToClipUnion( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kSetClipAndReplaceUserBits, + GrUserStencilOp::kKeep, + 0xffff>() +); + +static constexpr GrUserStencilSettings gInvUserToClipUnionPass0( // Does not zero user bits. + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqual, + 0xffff, + GrUserStencilOp::kSetClipBit, + GrUserStencilOp::kKeep, + 0x0000>() +); + +/////// +// Xor +static constexpr GrUserStencilSettings gUserToClipXorPass0( // Does not zero user bits. + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kInvertClipBit, + GrUserStencilOp::kKeep, + 0x0000>() +); + +static constexpr GrUserStencilSettings gInvUserToClipXorPass0( // Does not zero user bits. + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqual, + 0xffff, + GrUserStencilOp::kInvertClipBit, + GrUserStencilOp::kKeep, + 0x0000>() +); + +/////// +// Reverse Diff +static constexpr GrUserStencilSettings gUserToClipRDiffPass0( // Does not zero user bits. + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kInvertClipBit, + GrUserStencilOp::kZeroClipBit, + 0x0000>() +); + +static constexpr GrUserStencilSettings gInvUserToClipRDiffPass0( // Does not zero user bits. + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqual, + 0xffff, + GrUserStencilOp::kInvertClipBit, + GrUserStencilOp::kZeroClipBit, + 0x0000>() +); + +/////// +// Second pass to clear user bits (only needed sometimes) +static constexpr GrUserStencilSettings gZeroUserBits( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kKeep, + 0xffff>() +); + +static constexpr const GrUserStencilSettings* gUserToClipTable[2][1 + SkRegion::kLastOp][3] = { + { /* Normal fill. */ + {&gUserToClipDiff, nullptr, nullptr}, // kDifference_Op. + {&gUserToClipIsect, nullptr, nullptr}, // kIntersect_Op. + {&gUserToClipUnion, nullptr, nullptr}, // kUnion_Op. + {&gUserToClipXorPass0, &gZeroUserBits, nullptr}, // kXOR_Op. + {&gUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // kReverseDifference_Op. + {&gUserToClipReplace, nullptr, nullptr} // kReplace_Op. + + }, /* Inverse fill. */ { + {&gUserToClipIsect, nullptr, nullptr}, // ~diff (aka isect). + {&gUserToClipDiff, nullptr, nullptr}, // ~isect (aka diff). + {&gInvUserToClipUnionPass0, &gZeroUserBits, nullptr}, // ~union. + {&gInvUserToClipXorPass0, &gZeroUserBits, nullptr}, // ~xor. + {&gInvUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // ~reverse diff. + {&gInvUserToClipReplace, nullptr, nullptr} // ~replace. + } +}; + +GR_STATIC_ASSERT(0 == SkRegion::kDifference_Op); +GR_STATIC_ASSERT(1 == SkRegion::kIntersect_Op); +GR_STATIC_ASSERT(2 == SkRegion::kUnion_Op); +GR_STATIC_ASSERT(3 == SkRegion::kXOR_Op); +GR_STATIC_ASSERT(4 == SkRegion::kReverseDifference_Op); +GR_STATIC_ASSERT(5 == SkRegion::kReplace_Op); + +/////// +// Direct to Stencil + +// We can render a clip element directly without first writing to the client +// portion of the clip when the fill is not inverse and the set operation will +// only modify the in/out status of samples covered by the clip element. + +// this one only works if used right after stencil clip was cleared. +// Our clip mask creation code doesn't allow midstream replace ops. +static constexpr GrUserStencilSettings gReplaceClip( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kSetClipBit, + GrUserStencilOp::kSetClipBit, + 0x0000>() +); + +static constexpr GrUserStencilSettings gUnionClip( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kKeep, + GrUserStencilOp::kSetClipBit, + 0x0000>() +); + +static constexpr GrUserStencilSettings gXorClip( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kInvertClipBit, + GrUserStencilOp::kInvertClipBit, + 0x0000>() +); + +static constexpr GrUserStencilSettings gDiffClip( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kZeroClipBit, + GrUserStencilOp::kKeep, + 0x0000>() +); + +static constexpr const GrUserStencilSettings* gDirectDrawTable[1 + SkRegion::kLastOp][2] = { + {&gDiffClip, nullptr}, // kDifference_Op. + {nullptr, nullptr}, // kIntersect_Op. + {&gUnionClip, nullptr}, // kUnion_Op. + {&gXorClip, nullptr}, // kXOR_Op. + {nullptr, nullptr}, // kReverseDifference_Op. + {&gReplaceClip, nullptr} // kReplace_Op. +}; + +GR_STATIC_ASSERT(0 == SkRegion::kDifference_Op); +GR_STATIC_ASSERT(1 == SkRegion::kIntersect_Op); +GR_STATIC_ASSERT(2 == SkRegion::kUnion_Op); +GR_STATIC_ASSERT(3 == SkRegion::kXOR_Op); +GR_STATIC_ASSERT(4 == SkRegion::kReverseDifference_Op); +GR_STATIC_ASSERT(5 == SkRegion::kReplace_Op); + +GrUserStencilSettings const* const* GrStencilSettings::GetClipPasses(SkRegion::Op op, + bool canBeDirect, + bool invertedFill, + bool* drawDirectToClip) { + SkASSERT((unsigned)op <= SkRegion::kLastOp); + if (canBeDirect && !invertedFill) { // TODO: inverse fill + intersect op can be direct. + GrUserStencilSettings const* const* directPass = gDirectDrawTable[op]; + if (directPass[0]) { + *drawDirectToClip = true; + return directPass; + } + } + *drawDirectToClip = false; + return gUserToClipTable[invertedFill][op]; +} + +static constexpr GrUserStencilSettings gZeroStencilClipBit( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlways, + 0xffff, + GrUserStencilOp::kZeroClipBit, + GrUserStencilOp::kZeroClipBit, + 0x0000>() +); + +const GrUserStencilSettings* GrStencilSettings::SetClipBitSettings(bool setToInside) { + return setToInside ? &gReplaceClip : &gZeroStencilClipBit; +} + +void GrStencilSettings::genKey(GrProcessorKeyBuilder* b) const { + b->add32(fFlags); + if (this->isDisabled()) { + return; + } + if (!this->isTwoSided()) { + constexpr int kCount16 = sizeof(Face) / sizeof(uint16_t); + GR_STATIC_ASSERT(0 == sizeof(Face) % sizeof(uint16_t)); + uint16_t* key = reinterpret_cast(b->add32n((kCount16 + 1) / 2)); + memcpy(key, &fFront, sizeof(Face)); + key[kCount16] = 0; + GR_STATIC_ASSERT(1 == kCount16 % 2); + } else { + constexpr int kCount32 = (2 * sizeof(Face)) / sizeof(uint32_t); + GR_STATIC_ASSERT(0 == (2 * sizeof(Face)) % sizeof(uint32_t)); + uint32_t* key = b->add32n(kCount32); + memcpy(key, &fFront, 2 * sizeof(Face)); + GR_STATIC_ASSERT(sizeof(Face) == + offsetof(GrStencilSettings, fBack) - offsetof(GrStencilSettings, fFront)); + } + // We rely on GrStencilSettings::Face being tightly packed for the key to be reliable. + GR_STATIC_ASSERT(0 == offsetof(Face, fRef)); + GR_STATIC_ASSERT(2 == sizeof(Face::fRef)); + GR_STATIC_ASSERT(2 == offsetof(Face, fTest)); + GR_STATIC_ASSERT(2 == sizeof(Face::fTest)); + GR_STATIC_ASSERT(4 == offsetof(Face, fTestMask)); + GR_STATIC_ASSERT(2 == sizeof(Face::fTestMask)); + GR_STATIC_ASSERT(6 == offsetof(Face, fPassOp)); + GR_STATIC_ASSERT(1 == sizeof(Face::fPassOp)); + GR_STATIC_ASSERT(7 == offsetof(Face, fFailOp)); + GR_STATIC_ASSERT(1 == sizeof(Face::fFailOp)); + GR_STATIC_ASSERT(8 == offsetof(Face, fWriteMask)); + GR_STATIC_ASSERT(2 == sizeof(Face::fWriteMask)); + GR_STATIC_ASSERT(10 == sizeof(Face)); +} diff --git a/gfx/skia/skia/src/gpu/GrStencilSettings.h b/gfx/skia/skia/src/gpu/GrStencilSettings.h new file mode 100644 index 0000000000..e6942361b8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStencilSettings.h @@ -0,0 +1,135 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrStencilSettings_DEFINED +#define GrStencilSettings_DEFINED + +#include "include/core/SkRegion.h" +#include "src/gpu/GrUserStencilSettings.h" + +class GrProcessorKeyBuilder; + +enum class GrStencilTest : uint16_t { + kAlways, + kNever, + kGreater, + kGEqual, + kLess, + kLEqual, + kEqual, + kNotEqual +}; +static constexpr int kGrStencilTestCount = 1 + (int)GrStencilTest::kNotEqual; + +enum class GrStencilOp : uint8_t { + kKeep, + kZero, + kReplace, // Replace stencil value with fRef (only the bits enabled in fWriteMask). + kInvert, + kIncWrap, + kDecWrap, + // NOTE: clamping occurs before the write mask. So if the MSB is zero and masked out, stencil + // values will still wrap when using clamping ops. + kIncClamp, + kDecClamp +}; +static constexpr int kGrStencilOpCount = 1 + (int)GrStencilOp::kDecClamp; + +/** + * This class defines concrete stencil settings that map directly to the underlying hardware. It + * is deduced from user stencil settings, stencil clip status, and the number of bits in the + * target stencil buffer. + */ +class GrStencilSettings { +public: + GrStencilSettings() { this->setDisabled(); } + GrStencilSettings(const GrUserStencilSettings& user, bool hasStencilClip, int numStencilBits) { + this->reset(user, hasStencilClip, numStencilBits); + } + GrStencilSettings(const GrStencilSettings& that) { this->reset(that); } + GrStencilSettings& operator=(const GrStencilSettings& that) { this->reset(that); return *this; } + + void invalidate() { fFlags |= kInvalid_PrivateFlag; } + void setDisabled() { fFlags = kAll_StencilFlags; } + void reset(const GrUserStencilSettings&, bool hasStencilClip, int numStencilBits); + void reset(const GrStencilSettings&); + + bool isValid() const { return !(fFlags & kInvalid_PrivateFlag); } + bool isDisabled() const { SkASSERT(this->isValid()); return fFlags & kDisabled_StencilFlag; } + bool doesWrite() const { SkASSERT(this->isValid()); + return !(fFlags & kNoModifyStencil_StencilFlag); } + bool isTwoSided() const { SkASSERT(this->isValid()); + return !(fFlags & kSingleSided_StencilFlag); } + bool usesWrapOp() const { SkASSERT(this->isValid()); + return !(fFlags & kNoWrapOps_StencilFlag); } + + void genKey(GrProcessorKeyBuilder* b) const; + + bool operator!=(const GrStencilSettings& that) const { return !(*this == that); } + bool operator==(const GrStencilSettings&) const; + + struct Face : public GrTStencilFaceSettings { + void reset(const GrUserStencilSettings::Face&, bool useStencilClip, int numStencilBits); + void setDisabled(); + }; + + const Face& frontAndBack() const { + SkASSERT(!this->isDisabled()); + SkASSERT(!this->isTwoSided()); + return fFront; + } + const Face& front(GrSurfaceOrigin origin) const { + SkASSERT(this->isTwoSided()); + return (kTopLeft_GrSurfaceOrigin == origin) ? fFront : fBack; + } + const Face& back(GrSurfaceOrigin origin) const { + SkASSERT(this->isTwoSided()); + return (kTopLeft_GrSurfaceOrigin == origin) ? fBack : fFront; + } + + /** + * Given a thing to draw into the stencil clip, a fill type, and a set op + * this function determines: + * 1. Whether the thing can be draw directly to the stencil clip or + * needs to be drawn to the client portion of the stencil first. + * 2. How many passes are needed. + * 3. What those passes are. + * + * @param op the set op to combine this element with the existing clip + * @param canBeDirect can the caller draw this element directly (without using stencil)? + * @param invertedFill is this path inverted + * @param drawDirectToClip out: true if caller should draw the element directly, false if it + * should draw it into the user stencil bits first. + * + * @return a null-terminated array of settings for stencil passes. + * + * If drawDirectToClip is false, the caller must first draw the element into the user + * stencil bits, and then cover the clip area with multiple passes using the returned + * stencil settings. + * + * If drawDirectToClip is true, the returned array will only have one pass and the + * caller should use those stencil settings while drawing the element directly. + */ + static GrUserStencilSettings const* const* GetClipPasses(SkRegion::Op op, + bool canBeDirect, + bool invertedFill, + bool* drawDirectToClip); + + /** Gets the user stencil settings to directly set the clip bit. */ + static const GrUserStencilSettings* SetClipBitSettings(bool setToInside); + +private: + // Internal flag for backends to optionally mark their tracked stencil state as invalid. + enum { kInvalid_PrivateFlag = (kLast_StencilFlag << 1) }; + + uint32_t fFlags; + Face fFront; + Face fBack; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrStyle.cpp b/gfx/skia/skia/src/gpu/GrStyle.cpp new file mode 100644 index 0000000000..305b5b3ab5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStyle.cpp @@ -0,0 +1,199 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrStyle.h" +#include "src/utils/SkDashPathPriv.h" + +int GrStyle::KeySize(const GrStyle &style, Apply apply, uint32_t flags) { + GR_STATIC_ASSERT(sizeof(uint32_t) == sizeof(SkScalar)); + int size = 0; + if (style.isDashed()) { + // One scalar for scale, one for dash phase, and one for each dash value. + size += 2 + style.dashIntervalCnt(); + } else if (style.pathEffect()) { + // No key for a generic path effect. + return -1; + } + + if (Apply::kPathEffectOnly == apply) { + return size; + } + + if (style.strokeRec().needToApply()) { + // One for res scale, one for style/cap/join, one for miter limit, and one for width. + size += 4; + } + return size; +} + +void GrStyle::WriteKey(uint32_t *key, const GrStyle &style, Apply apply, SkScalar scale, + uint32_t flags) { + SkASSERT(key); + SkASSERT(KeySize(style, apply) >= 0); + GR_STATIC_ASSERT(sizeof(uint32_t) == sizeof(SkScalar)); + + int i = 0; + // The scale can influence both the path effect and stroking. We want to preserve the + // property that the following two are equal: + // 1. WriteKey with apply == kPathEffectAndStrokeRec + // 2. WriteKey with apply == kPathEffectOnly followed by WriteKey of a GrStyle made + // from SkStrokeRec output by the the path effect (and no additional path effect). + // Since the scale can affect both parts of 2 we write it into the key twice. + if (style.isDashed()) { + GR_STATIC_ASSERT(sizeof(style.dashPhase()) == sizeof(uint32_t)); + SkScalar phase = style.dashPhase(); + memcpy(&key[i++], &scale, sizeof(SkScalar)); + memcpy(&key[i++], &phase, sizeof(SkScalar)); + + int32_t count = style.dashIntervalCnt(); + // Dash count should always be even. + SkASSERT(0 == (count & 0x1)); + const SkScalar *intervals = style.dashIntervals(); + int intervalByteCnt = count * sizeof(SkScalar); + memcpy(&key[i], intervals, intervalByteCnt); + i += count; + } else { + SkASSERT(!style.pathEffect()); + } + + if (Apply::kPathEffectAndStrokeRec == apply && style.strokeRec().needToApply()) { + memcpy(&key[i++], &scale, sizeof(SkScalar)); + enum { + kStyleBits = 2, + kJoinBits = 2, + kCapBits = 32 - kStyleBits - kJoinBits, + + kJoinShift = kStyleBits, + kCapShift = kJoinShift + kJoinBits, + }; + GR_STATIC_ASSERT(SkStrokeRec::kStyleCount <= (1 << kStyleBits)); + GR_STATIC_ASSERT(SkPaint::kJoinCount <= (1 << kJoinBits)); + GR_STATIC_ASSERT(SkPaint::kCapCount <= (1 << kCapBits)); + // The cap type only matters for unclosed shapes. However, a path effect could unclose + // the shape before it is stroked. + SkPaint::Cap cap = SkPaint::kDefault_Cap; + if (!(flags & kClosed_KeyFlag) || style.pathEffect()) { + cap = style.strokeRec().getCap(); + } + SkScalar miter = -1.f; + SkPaint::Join join = SkPaint::kDefault_Join; + + // Dashing will not insert joins but other path effects may. + if (!(flags & kNoJoins_KeyFlag) || style.hasNonDashPathEffect()) { + join = style.strokeRec().getJoin(); + // Miter limit only affects miter joins + if (SkPaint::kMiter_Join == join) { + miter = style.strokeRec().getMiter(); + } + } + + key[i++] = style.strokeRec().getStyle() | + join << kJoinShift | + cap << kCapShift; + + memcpy(&key[i++], &miter, sizeof(miter)); + + SkScalar width = style.strokeRec().getWidth(); + memcpy(&key[i++], &width, sizeof(width)); + } + SkASSERT(KeySize(style, apply) == i); +} + +void GrStyle::initPathEffect(sk_sp pe) { + SkASSERT(!fPathEffect); + SkASSERT(SkPathEffect::kNone_DashType == fDashInfo.fType); + SkASSERT(0 == fDashInfo.fIntervals.count()); + if (!pe) { + return; + } + SkPathEffect::DashInfo info; + if (SkPathEffect::kDash_DashType == pe->asADash(&info)) { + SkStrokeRec::Style recStyle = fStrokeRec.getStyle(); + if (recStyle != SkStrokeRec::kFill_Style && recStyle != SkStrokeRec::kStrokeAndFill_Style) { + fDashInfo.fType = SkPathEffect::kDash_DashType; + fDashInfo.fIntervals.reset(info.fCount); + fDashInfo.fPhase = info.fPhase; + info.fIntervals = fDashInfo.fIntervals.get(); + pe->asADash(&info); + fPathEffect = std::move(pe); + } + } else { + fPathEffect = std::move(pe); + } +} + +bool GrStyle::applyPathEffect(SkPath* dst, SkStrokeRec* strokeRec, const SkPath& src) const { + if (!fPathEffect) { + return false; + } + if (SkPathEffect::kDash_DashType == fDashInfo.fType) { + // We apply the dash ourselves here rather than using the path effect. This is so that + // we can control whether the dasher applies the strokeRec for special cases. Our keying + // depends on the strokeRec being applied separately. + SkScalar phase = fDashInfo.fPhase; + const SkScalar* intervals = fDashInfo.fIntervals.get(); + int intervalCnt = fDashInfo.fIntervals.count(); + SkScalar initialLength; + int initialIndex; + SkScalar intervalLength; + SkDashPath::CalcDashParameters(phase, intervals, intervalCnt, &initialLength, + &initialIndex, &intervalLength); + if (!SkDashPath::InternalFilter(dst, src, strokeRec, + nullptr, intervals, intervalCnt, + initialLength, initialIndex, intervalLength, + SkDashPath::StrokeRecApplication::kDisallow)) { + return false; + } + } else if (!fPathEffect->filterPath(dst, src, strokeRec, nullptr)) { + return false; + } + dst->setIsVolatile(true); + return true; +} + +bool GrStyle::applyPathEffectToPath(SkPath *dst, SkStrokeRec *remainingStroke, + const SkPath &src, SkScalar resScale) const { + SkASSERT(dst); + SkStrokeRec strokeRec = fStrokeRec; + strokeRec.setResScale(resScale); + if (!this->applyPathEffect(dst, &strokeRec, src)) { + return false; + } + *remainingStroke = strokeRec; + return true; +} + +bool GrStyle::applyToPath(SkPath* dst, SkStrokeRec::InitStyle* style, const SkPath& src, + SkScalar resScale) const { + SkASSERT(style); + SkASSERT(dst); + SkStrokeRec strokeRec = fStrokeRec; + strokeRec.setResScale(resScale); + const SkPath* pathForStrokeRec = &src; + if (this->applyPathEffect(dst, &strokeRec, src)) { + pathForStrokeRec = dst; + } else if (fPathEffect) { + return false; + } + if (strokeRec.needToApply()) { + if (!strokeRec.applyToPath(dst, *pathForStrokeRec)) { + return false; + } + dst->setIsVolatile(true); + *style = SkStrokeRec::kFill_InitStyle; + } else if (!fPathEffect) { + // Nothing to do for path effect or stroke, fail. + return false; + } else { + SkASSERT(SkStrokeRec::kFill_Style == strokeRec.getStyle() || + SkStrokeRec::kHairline_Style == strokeRec.getStyle()); + *style = strokeRec.getStyle() == SkStrokeRec::kFill_Style + ? SkStrokeRec::kFill_InitStyle + : SkStrokeRec::kHairline_InitStyle; + } + return true; +} diff --git a/gfx/skia/skia/src/gpu/GrStyle.h b/gfx/skia/skia/src/gpu/GrStyle.h new file mode 100644 index 0000000000..1dce72d6f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrStyle.h @@ -0,0 +1,215 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStyle_DEFINED +#define GrStyle_DEFINED + +#include "include/core/SkPathEffect.h" +#include "include/core/SkStrokeRec.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkTemplates.h" + +/** + * Represents the various ways that a GrShape can be styled. It has fill/stroking information + * as well as an optional path effect. If the path effect represents dashing, the dashing + * information is extracted from the path effect and stored explicitly. + * + * This will replace GrStrokeInfo as GrShape is deployed. + */ +class GrStyle { +public: + /** + * A style object that represents a fill with no path effect. + * TODO: constexpr with C++14 + */ + static const GrStyle& SimpleFill() { + static const GrStyle kFill(SkStrokeRec::kFill_InitStyle); + return kFill; + } + + /** + * A style object that represents a hairline stroke with no path effect. + * TODO: constexpr with C++14 + */ + static const GrStyle& SimpleHairline() { + static const GrStyle kHairline(SkStrokeRec::kHairline_InitStyle); + return kHairline; + } + + enum class Apply { + kPathEffectOnly, + kPathEffectAndStrokeRec + }; + + /** + * Optional flags for computing keys that may remove unnecessary variation in the key due to + * style settings that don't affect particular classes of geometry. + */ + enum KeyFlags { + // The shape being styled has no open contours. + kClosed_KeyFlag = 0x1, + // The shape being styled doesn't have any joins and so isn't affected by join type. + kNoJoins_KeyFlag = 0x2 + }; + + /** + * Computes the key length for a GrStyle. The return will be negative if it cannot be turned + * into a key. This occurs when there is a path effect that is not a dash. The key can + * either reflect just the path effect (if one) or the path effect and the strokerec. Note + * that a simple fill has a zero sized key. + */ + static int KeySize(const GrStyle&, Apply, uint32_t flags = 0); + + /** + * Writes a unique key for the style into the provided buffer. This function assumes the buffer + * has room for at least KeySize() values. It assumes that KeySize() returns a non-negative + * value for the combination of GrStyle, Apply and flags params. This is written so that the key + * for just dash application followed by the key for the remaining SkStrokeRec is the same as + * the key for applying dashing and SkStrokeRec all at once. + */ + static void WriteKey(uint32_t*, const GrStyle&, Apply, SkScalar scale, uint32_t flags = 0); + + GrStyle() : GrStyle(SkStrokeRec::kFill_InitStyle) {} + + explicit GrStyle(SkStrokeRec::InitStyle initStyle) : fStrokeRec(initStyle) {} + + GrStyle(const SkStrokeRec& strokeRec, sk_sp pe) : fStrokeRec(strokeRec) { + this->initPathEffect(std::move(pe)); + } + + GrStyle(const GrStyle& that) = default; + + explicit GrStyle(const SkPaint& paint) : fStrokeRec(paint) { + this->initPathEffect(paint.refPathEffect()); + } + + explicit GrStyle(const SkPaint& paint, SkPaint::Style overrideStyle) + : fStrokeRec(paint, overrideStyle) { + this->initPathEffect(paint.refPathEffect()); + } + + GrStyle& operator=(const GrStyle& that) { + fPathEffect = that.fPathEffect; + fDashInfo = that.fDashInfo; + fStrokeRec = that.fStrokeRec; + return *this; + } + + void resetToInitStyle(SkStrokeRec::InitStyle fillOrHairline) { + fDashInfo.reset(); + fPathEffect.reset(nullptr); + if (SkStrokeRec::kFill_InitStyle == fillOrHairline) { + fStrokeRec.setFillStyle(); + } else { + fStrokeRec.setHairlineStyle(); + } + } + + /** Is this style a fill with no path effect? */ + bool isSimpleFill() const { return fStrokeRec.isFillStyle() && !fPathEffect; } + + /** Is this style a hairline with no path effect? */ + bool isSimpleHairline() const { return fStrokeRec.isHairlineStyle() && !fPathEffect; } + + SkPathEffect* pathEffect() const { return fPathEffect.get(); } + sk_sp refPathEffect() const { return fPathEffect; } + + bool hasPathEffect() const { return SkToBool(fPathEffect.get()); } + + bool hasNonDashPathEffect() const { return fPathEffect.get() && !this->isDashed(); } + + bool isDashed() const { return SkPathEffect::kDash_DashType == fDashInfo.fType; } + SkScalar dashPhase() const { + SkASSERT(this->isDashed()); + return fDashInfo.fPhase; + } + int dashIntervalCnt() const { + SkASSERT(this->isDashed()); + return fDashInfo.fIntervals.count(); + } + const SkScalar* dashIntervals() const { + SkASSERT(this->isDashed()); + return fDashInfo.fIntervals.get(); + } + + const SkStrokeRec& strokeRec() const { return fStrokeRec; } + + /** Hairline or fill styles without path effects make no alterations to a geometry. */ + bool applies() const { + return this->pathEffect() || (!fStrokeRec.isFillStyle() && !fStrokeRec.isHairlineStyle()); + } + + static SkScalar MatrixToScaleFactor(const SkMatrix& matrix) { + // getMaxScale will return -1 if the matrix has perspective. In that case we can use a scale + // factor of 1. This isn't necessarily a good choice and in the future we might consider + // taking a bounds here for the perspective case. + return SkScalarAbs(matrix.getMaxScale()); + } + /** + * Applies just the path effect and returns remaining stroke information. This will fail if + * there is no path effect. dst may or may not have been overwritten on failure. Scale controls + * geometric approximations made by the path effect. It is typically computed from the view + * matrix. + */ + bool SK_WARN_UNUSED_RESULT applyPathEffectToPath(SkPath* dst, SkStrokeRec* remainingStoke, + const SkPath& src, SkScalar scale) const; + + /** + * If this succeeds then the result path should be filled or hairlined as indicated by the + * returned SkStrokeRec::InitStyle value. Will fail if there is no path effect and the + * strokerec doesn't change the geometry. When this fails the outputs may or may not have + * been overwritten. Scale controls geometric approximations made by the path effect and + * stroker. It is typically computed from the view matrix. + */ + bool SK_WARN_UNUSED_RESULT applyToPath(SkPath* dst, SkStrokeRec::InitStyle* fillOrHairline, + const SkPath& src, SkScalar scale) const; + + /** Given bounds of a path compute the bounds of path with the style applied. */ + void adjustBounds(SkRect* dst, const SkRect& src) const { + if (this->pathEffect()) { + this->pathEffect()->computeFastBounds(dst, src); + // This may not be the correct SkStrokeRec to use. skbug.com/5299 + // It happens to work for dashing. + SkScalar radius = fStrokeRec.getInflationRadius(); + dst->outset(radius, radius); + } else { + SkScalar radius = fStrokeRec.getInflationRadius(); + *dst = src.makeOutset(radius, radius); + } + } + +private: + void initPathEffect(sk_sp pe); + + struct DashInfo { + DashInfo() : fType(SkPathEffect::kNone_DashType) {} + DashInfo(const DashInfo& that) { *this = that; } + DashInfo& operator=(const DashInfo& that) { + fType = that.fType; + fPhase = that.fPhase; + fIntervals.reset(that.fIntervals.count()); + sk_careful_memcpy(fIntervals.get(), that.fIntervals.get(), + sizeof(SkScalar) * that.fIntervals.count()); + return *this; + } + void reset() { + fType = SkPathEffect::kNone_DashType; + fIntervals.reset(0); + } + SkPathEffect::DashType fType; + SkScalar fPhase{0}; + SkAutoSTArray<4, SkScalar> fIntervals; + }; + + bool applyPathEffect(SkPath* dst, SkStrokeRec* strokeRec, const SkPath& src) const; + + SkStrokeRec fStrokeRec; + sk_sp fPathEffect; + DashInfo fDashInfo; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSurface.cpp b/gfx/skia/skia/src/gpu/GrSurface.cpp new file mode 100644 index 0000000000..90f75a82cb --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurface.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContext.h" +#include "include/gpu/GrSurface.h" +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrSurfacePriv.h" + +#include "src/core/SkMathPriv.h" +#include "src/gpu/SkGr.h" + +size_t GrSurface::ComputeSize(const GrCaps& caps, + const GrBackendFormat& format, + int width, + int height, + int colorSamplesPerPixel, + GrMipMapped mipMapped, + bool binSize) { + size_t colorSize; + + width = binSize ? GrResourceProvider::MakeApprox(width) : width; + height = binSize ? GrResourceProvider::MakeApprox(height) : height; + + // Just setting a defualt value here to appease warnings on uninitialized object. + SkImage::CompressionType compressionType = SkImage::kETC1_CompressionType; + if (caps.isFormatCompressed(format, &compressionType)) { + colorSize = GrCompressedFormatDataSize(compressionType, width, height); + } else { + colorSize = (size_t)width * height * caps.bytesPerPixel(format); + } + SkASSERT(colorSize > 0); + + size_t finalSize = colorSamplesPerPixel * colorSize; + + if (GrMipMapped::kYes == mipMapped) { + // We don't have to worry about the mipmaps being a different size than + // we'd expect because we never change fDesc.fWidth/fHeight. + finalSize += colorSize/3; + } + return finalSize; +} + +////////////////////////////////////////////////////////////////////////////// + +void GrSurface::onRelease() { + this->invokeReleaseProc(); + this->INHERITED::onRelease(); +} + +void GrSurface::onAbandon() { + this->invokeReleaseProc(); + this->INHERITED::onAbandon(); +} diff --git a/gfx/skia/skia/src/gpu/GrSurfaceContext.cpp b/gfx/skia/skia/src/gpu/GrSurfaceContext.cpp new file mode 100644 index 0000000000..4b5e0a8c7c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfaceContext.cpp @@ -0,0 +1,661 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrSurfaceContext.h" + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSurfaceContextPriv.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTextureContext.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrBicubicEffect.h" + +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());) +#define RETURN_FALSE_IF_ABANDONED if (this->fContext->priv().abandoned()) { return false; } + +// In MDB mode the reffing of the 'getLastOpsTask' call's result allows in-progress +// GrOpsTasks to be picked up and added to by renderTargetContexts lower in the call +// stack. When this occurs with a closed GrOpsTask, a new one will be allocated +// when the renderTargetContext attempts to use it (via getOpsTask). +GrSurfaceContext::GrSurfaceContext(GrRecordingContext* context, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace) + : fContext(context), fColorInfo(colorType, alphaType, std::move(colorSpace)) {} + +const GrCaps* GrSurfaceContext::caps() const { return fContext->priv().caps(); } + +GrAuditTrail* GrSurfaceContext::auditTrail() { + return fContext->priv().auditTrail(); +} + +GrDrawingManager* GrSurfaceContext::drawingManager() { + return fContext->priv().drawingManager(); +} + +const GrDrawingManager* GrSurfaceContext::drawingManager() const { + return fContext->priv().drawingManager(); +} + +#ifdef SK_DEBUG +GrSingleOwner* GrSurfaceContext::singleOwner() { + return fContext->priv().singleOwner(); +} +#endif + +bool GrSurfaceContext::readPixels(const GrImageInfo& origDstInfo, void* dst, size_t rowBytes, + SkIPoint pt, GrContext* direct) { + ASSERT_SINGLE_OWNER + RETURN_FALSE_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContext::readPixels"); + + if (!direct && !(direct = fContext->priv().asDirectContext())) { + return false; + } + + if (!dst) { + return false; + } + + size_t tightRowBytes = origDstInfo.minRowBytes(); + if (!rowBytes) { + rowBytes = tightRowBytes; + } else if (rowBytes < tightRowBytes) { + return false; + } + + if (!origDstInfo.isValid()) { + return false; + } + + GrSurfaceProxy* srcProxy = this->asSurfaceProxy(); + + // MDB TODO: delay this instantiation until later in the method + if (!srcProxy->instantiate(direct->priv().resourceProvider())) { + return false; + } + + GrSurface* srcSurface = srcProxy->peekSurface(); + + auto dstInfo = origDstInfo; + if (!dstInfo.clip(this->width(), this->height(), &pt, &dst, rowBytes)) { + return false; + } + // Our tight row bytes may have been changed by clipping. + tightRowBytes = dstInfo.minRowBytes(); + + bool premul = this->colorInfo().alphaType() == kUnpremul_SkAlphaType && + dstInfo.alphaType() == kPremul_SkAlphaType; + bool unpremul = this->colorInfo().alphaType() == kPremul_SkAlphaType && + dstInfo.alphaType() == kUnpremul_SkAlphaType; + + bool needColorConversion = + SkColorSpaceXformSteps::Required(this->colorInfo().colorSpace(), dstInfo.colorSpace()); + + const GrCaps* caps = direct->priv().caps(); + // This is the getImageData equivalent to the canvas2D putImageData fast path. We probably don't + // care so much about getImageData performance. However, in order to ensure putImageData/ + // getImageData in "legacy" mode are round-trippable we use the GPU to do the complementary + // unpremul step to writeSurfacePixels's premul step (which is determined empirically in + // fContext->vaildaPMUPMConversionExists()). + GrBackendFormat defaultRGBAFormat = caps->getDefaultBackendFormat(GrColorType::kRGBA_8888, + GrRenderable::kYes); + bool canvas2DFastPath = unpremul && !needColorConversion && + (GrColorType::kRGBA_8888 == dstInfo.colorType() || + GrColorType::kBGRA_8888 == dstInfo.colorType()) && + SkToBool(srcProxy->asTextureProxy()) && + (srcProxy->config() == kRGBA_8888_GrPixelConfig || + srcProxy->config() == kBGRA_8888_GrPixelConfig) && + defaultRGBAFormat.isValid() && + direct->priv().validPMUPMConversionExists(); + + auto readFlag = caps->surfaceSupportsReadPixels(srcSurface); + if (readFlag == GrCaps::SurfaceReadPixelsSupport::kUnsupported) { + return false; + } + + if (readFlag == GrCaps::SurfaceReadPixelsSupport::kCopyToTexture2D || canvas2DFastPath) { + GrColorType colorType = + canvas2DFastPath ? GrColorType::kRGBA_8888 : this->colorInfo().colorType(); + sk_sp cs = canvas2DFastPath ? nullptr : this->colorInfo().refColorSpace(); + + auto tempCtx = direct->priv().makeDeferredRenderTargetContext( + SkBackingFit::kApprox, dstInfo.width(), dstInfo.height(), colorType, std::move(cs), + 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin, nullptr, SkBudgeted::kYes); + if (!tempCtx) { + return false; + } + + std::unique_ptr fp; + if (canvas2DFastPath) { + fp = direct->priv().createPMToUPMEffect( + GrSimpleTextureEffect::Make(sk_ref_sp(srcProxy->asTextureProxy()), + this->colorInfo().colorType(), SkMatrix::I())); + if (dstInfo.colorType() == GrColorType::kBGRA_8888) { + fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), GrSwizzle::BGRA()); + dstInfo = dstInfo.makeColorType(GrColorType::kRGBA_8888); + } + // The render target context is incorrectly tagged as kPremul even though we're writing + // unpremul data thanks to the PMToUPM effect. Fake out the dst alpha type so we don't + // double unpremul. + dstInfo = dstInfo.makeAlphaType(kPremul_SkAlphaType); + } else { + fp = GrSimpleTextureEffect::Make(sk_ref_sp(srcProxy->asTextureProxy()), + this->colorInfo().colorType(), SkMatrix::I()); + } + if (!fp) { + return false; + } + GrPaint paint; + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + paint.addColorFragmentProcessor(std::move(fp)); + + tempCtx->asRenderTargetContext()->fillRectToRect( + GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::MakeWH(dstInfo.width(), dstInfo.height()), + SkRect::MakeXYWH(pt.fX, pt.fY, dstInfo.width(), dstInfo.height())); + + return tempCtx->readPixels(dstInfo, dst, rowBytes, {0, 0}, direct); + } + + bool flip = srcProxy->origin() == kBottomLeft_GrSurfaceOrigin; + + auto supportedRead = caps->supportedReadPixelsColorType( + this->colorInfo().colorType(), srcProxy->backendFormat(), dstInfo.colorType()); + + bool makeTight = !caps->readPixelsRowBytesSupport() && tightRowBytes != rowBytes; + + bool convert = unpremul || premul || needColorConversion || flip || makeTight || + (dstInfo.colorType() != supportedRead.fColorType); + + std::unique_ptr tmpPixels; + GrImageInfo tmpInfo; + void* readDst = dst; + size_t readRB = rowBytes; + if (convert) { + tmpInfo = {supportedRead.fColorType, this->colorInfo().alphaType(), + this->colorInfo().refColorSpace(), dstInfo.width(), dstInfo.height()}; + size_t tmpRB = tmpInfo.minRowBytes(); + size_t size = tmpRB * tmpInfo.height(); + // Chrome MSAN bots require the data to be initialized (hence the ()). + tmpPixels.reset(new char[size]()); + + readDst = tmpPixels.get(); + readRB = tmpRB; + pt.fY = flip ? srcSurface->height() - pt.fY - dstInfo.height() : pt.fY; + } + + direct->priv().flushSurface(srcProxy); + + if (!direct->priv().getGpu()->readPixels(srcSurface, pt.fX, pt.fY, dstInfo.width(), + dstInfo.height(), this->colorInfo().colorType(), + supportedRead.fColorType, readDst, readRB)) { + return false; + } + + if (convert) { + return GrConvertPixels(dstInfo, dst, rowBytes, tmpInfo, readDst, readRB, flip); + } + return true; +} + +bool GrSurfaceContext::writePixels(const GrImageInfo& origSrcInfo, const void* src, size_t rowBytes, + SkIPoint pt, GrContext* direct) { + ASSERT_SINGLE_OWNER + RETURN_FALSE_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContext::writePixels"); + + if (!direct && !(direct = fContext->priv().asDirectContext())) { + return false; + } + + if (this->asSurfaceProxy()->readOnly()) { + return false; + } + + if (!src) { + return false; + } + + size_t tightRowBytes = origSrcInfo.minRowBytes(); + if (!rowBytes) { + rowBytes = tightRowBytes; + } else if (rowBytes < tightRowBytes) { + return false; + } + + if (!origSrcInfo.isValid()) { + return false; + } + + GrSurfaceProxy* dstProxy = this->asSurfaceProxy(); + if (!dstProxy->instantiate(direct->priv().resourceProvider())) { + return false; + } + + GrSurface* dstSurface = dstProxy->peekSurface(); + + auto srcInfo = origSrcInfo; + if (!srcInfo.clip(this->width(), this->height(), &pt, &src, rowBytes)) { + return false; + } + // Our tight row bytes may have been changed by clipping. + tightRowBytes = srcInfo.minRowBytes(); + + bool premul = this->colorInfo().alphaType() == kPremul_SkAlphaType && + srcInfo.alphaType() == kUnpremul_SkAlphaType; + bool unpremul = this->colorInfo().alphaType() == kUnpremul_SkAlphaType && + srcInfo.alphaType() == kPremul_SkAlphaType; + + bool needColorConversion = + SkColorSpaceXformSteps::Required(srcInfo.colorSpace(), this->colorInfo().colorSpace()); + + const GrCaps* caps = direct->priv().caps(); + + auto rgbaDefaultFormat = caps->getDefaultBackendFormat(GrColorType::kRGBA_8888, + GrRenderable::kNo); + + // For canvas2D putImageData performance we have a special code path for unpremul RGBA_8888 srcs + // that are premultiplied on the GPU. This is kept as narrow as possible for now. + bool canvas2DFastPath = !caps->avoidWritePixelsFastPath() && premul && !needColorConversion && + (srcInfo.colorType() == GrColorType::kRGBA_8888 || + srcInfo.colorType() == GrColorType::kBGRA_8888) && + SkToBool(this->asRenderTargetContext()) && + (dstProxy->config() == kRGBA_8888_GrPixelConfig || + dstProxy->config() == kBGRA_8888_GrPixelConfig) && + rgbaDefaultFormat.isValid() && + direct->priv().validPMUPMConversionExists(); + + if (!caps->surfaceSupportsWritePixels(dstSurface) || canvas2DFastPath) { + GrSurfaceDesc desc; + desc.fWidth = srcInfo.width(); + desc.fHeight = srcInfo.height(); + GrColorType colorType; + + GrBackendFormat format; + SkAlphaType alphaType; + if (canvas2DFastPath) { + desc.fConfig = kRGBA_8888_GrPixelConfig; + colorType = GrColorType::kRGBA_8888; + format = rgbaDefaultFormat; + alphaType = kUnpremul_SkAlphaType; + } else { + desc.fConfig = dstProxy->config(); + colorType = this->colorInfo().colorType(); + format = dstProxy->backendFormat().makeTexture2D(); + if (!format.isValid()) { + return false; + } + alphaType = this->colorInfo().alphaType(); + } + + // It is more efficient for us to write pixels into a top left origin so we prefer that. + // However, if the final proxy isn't a render target then we must use a copy to move the + // data into it which requires the origins to match. If the final proxy is a render target + // we can use a draw instead which doesn't have this origin restriction. Thus for render + // targets we will use top left and otherwise we will make the origins match. + GrSurfaceOrigin tempOrigin = + this->asRenderTargetContext() ? kTopLeft_GrSurfaceOrigin : dstProxy->origin(); + auto tempProxy = direct->priv().proxyProvider()->createProxy( + format, desc, GrRenderable::kNo, 1, tempOrigin, GrMipMapped::kNo, + SkBackingFit::kApprox, SkBudgeted::kYes, GrProtected::kNo); + + if (!tempProxy) { + return false; + } + auto tempCtx = direct->priv().drawingManager()->makeTextureContext( + tempProxy, colorType, alphaType, this->colorInfo().refColorSpace()); + if (!tempCtx) { + return false; + } + + // In the fast path we always write the srcData to the temp context as though it were RGBA. + // When the data is really BGRA the write will cause the R and B channels to be swapped in + // the intermediate surface which gets corrected by a swizzle effect when drawing to the + // dst. + if (canvas2DFastPath) { + srcInfo = srcInfo.makeColorType(GrColorType::kRGBA_8888); + } + if (!tempCtx->writePixels(srcInfo, src, rowBytes, {0, 0}, direct)) { + return false; + } + + if (this->asRenderTargetContext()) { + std::unique_ptr fp; + if (canvas2DFastPath) { + fp = direct->priv().createUPMToPMEffect( + GrSimpleTextureEffect::Make(std::move(tempProxy), colorType, + SkMatrix::I())); + // Important: check the original src color type here! + if (origSrcInfo.colorType() == GrColorType::kBGRA_8888) { + fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), GrSwizzle::BGRA()); + } + } else { + fp = GrSimpleTextureEffect::Make(std::move(tempProxy), colorType, SkMatrix::I()); + } + if (!fp) { + return false; + } + GrPaint paint; + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + paint.addColorFragmentProcessor(std::move(fp)); + this->asRenderTargetContext()->fillRectToRect( + GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::MakeXYWH(pt.fX, pt.fY, srcInfo.width(), srcInfo.height()), + SkRect::MakeWH(srcInfo.width(), srcInfo.height())); + } else { + SkIRect srcRect = SkIRect::MakeWH(srcInfo.width(), srcInfo.height()); + SkIPoint dstPoint = SkIPoint::Make(pt.fX, pt.fY); + if (!this->copy(tempProxy.get(), srcRect, dstPoint)) { + return false; + } + } + return true; + } + + GrColorType allowedColorType = + caps->supportedWritePixelsColorType(this->colorInfo().colorType(), + dstProxy->backendFormat(), + srcInfo.colorType()).fColorType; + bool flip = dstProxy->origin() == kBottomLeft_GrSurfaceOrigin; + bool makeTight = !caps->writePixelsRowBytesSupport() && rowBytes != tightRowBytes; + bool convert = premul || unpremul || needColorConversion || makeTight || + (srcInfo.colorType() != allowedColorType) || flip; + + std::unique_ptr tmpPixels; + GrColorType srcColorType = srcInfo.colorType(); + if (convert) { + GrImageInfo tmpInfo(allowedColorType, this->colorInfo().alphaType(), + this->colorInfo().refColorSpace(), srcInfo.width(), srcInfo.height()); + auto tmpRB = tmpInfo.minRowBytes(); + tmpPixels.reset(new char[tmpRB * tmpInfo.height()]); + + GrConvertPixels(tmpInfo, tmpPixels.get(), tmpRB, srcInfo, src, rowBytes, flip); + + srcColorType = tmpInfo.colorType(); + rowBytes = tmpRB; + src = tmpPixels.get(); + pt.fY = flip ? dstSurface->height() - pt.fY - tmpInfo.height() : pt.fY; + } + + // On platforms that prefer flushes over VRAM use (i.e., ANGLE) we're better off forcing a + // complete flush here. On platforms that prefer VRAM use over flushes we're better off + // giving the drawing manager the chance of skipping the flush (i.e., by passing in the + // destination proxy) + // TODO: should this policy decision just be moved into the drawing manager? + direct->priv().flushSurface(caps->preferVRAMUseOverFlushes() ? dstProxy : nullptr); + + return direct->priv().getGpu()->writePixels(dstSurface, pt.fX, pt.fY, srcInfo.width(), + srcInfo.height(), this->colorInfo().colorType(), + srcColorType, src, rowBytes); +} + +bool GrSurfaceContext::copy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint) { + ASSERT_SINGLE_OWNER + RETURN_FALSE_IF_ABANDONED + SkDEBUGCODE(this->validate();) + GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContextPriv::copy"); + + const GrCaps* caps = fContext->priv().caps(); + + SkASSERT(src->backendFormat().textureType() != GrTextureType::kExternal); + SkASSERT(src->origin() == this->asSurfaceProxy()->origin()); + SkASSERT(caps->makeConfigSpecific(src->config(), src->backendFormat()) == + caps->makeConfigSpecific(this->asSurfaceProxy()->config(), + this->asSurfaceProxy()->backendFormat())); + + if (!caps->canCopySurface(this->asSurfaceProxy(), src, srcRect, dstPoint)) { + return false; + } + + return this->drawingManager()->newCopyRenderTask(sk_ref_sp(src), srcRect, + this->asSurfaceProxyRef(), dstPoint); +} + +std::unique_ptr GrSurfaceContext::rescale( + const SkImageInfo& info, + const SkIRect& srcRect, + SkSurface::RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality) { + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + return nullptr; + } + auto rtProxy = this->asRenderTargetProxy(); + if (rtProxy && rtProxy->wrapsVkSecondaryCB()) { + return nullptr; + } + + // We rescale by drawing and don't currently support drawing to a kUnpremul destination. + if (info.alphaType() == kUnpremul_SkAlphaType) { + return nullptr; + } + + int srcW = srcRect.width(); + int srcH = srcRect.height(); + int srcX = srcRect.fLeft; + int srcY = srcRect.fTop; + sk_sp texProxy = sk_ref_sp(this->asTextureProxy()); + SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint; + GrColorType srcColorType = this->colorInfo().colorType(); + if (!texProxy) { + texProxy = GrSurfaceProxy::Copy(fContext, this->asSurfaceProxy(), srcColorType, + GrMipMapped::kNo, srcRect, SkBackingFit::kApprox, + SkBudgeted::kNo); + if (!texProxy) { + return nullptr; + } + srcX = 0; + srcY = 0; + constraint = SkCanvas::kFast_SrcRectConstraint; + } + + float sx = (float)info.width() / srcW; + float sy = (float)info.height() / srcH; + + // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling. + int stepsX; + int stepsY; + if (rescaleQuality > kNone_SkFilterQuality) { + stepsX = static_cast((sx > 1.f) ? ceil(log2f(sx)) : floor(log2f(sx))); + stepsY = static_cast((sy > 1.f) ? ceil(log2f(sy)) : floor(log2f(sy))); + } else { + stepsX = sx != 1.f; + stepsY = sy != 1.f; + } + SkASSERT(stepsX || stepsY); + + // Within a rescaling pass A is the input (if not null) and B is the output. At the end of the + // pass B is moved to A. If 'this' is the input on the first pass then tempA is null. + std::unique_ptr tempA; + std::unique_ptr tempB; + + // Assume we should ignore the rescale linear request if the surface has no color space since + // it's unclear how we'd linearize from an unknown color space. + if (rescaleGamma == SkSurface::kLinear && this->colorInfo().colorSpace() && + !this->colorInfo().colorSpace()->gammaIsLinear()) { + auto cs = this->colorInfo().colorSpace()->makeLinearGamma(); + auto xform = GrColorSpaceXform::Make(this->colorInfo().colorSpace(), + this->colorInfo().alphaType(), cs.get(), + kPremul_SkAlphaType); + // We'll fall back to kRGBA_8888 if half float not supported. + auto linearRTC = fContext->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, srcW, srcH, GrColorType::kRGBA_F16, cs, 1, GrMipMapped::kNo, + kTopLeft_GrSurfaceOrigin); + if (!linearRTC) { + return nullptr; + } + linearRTC->drawTexture(GrNoClip(), texProxy, srcColorType, GrSamplerState::Filter::kNearest, + SkBlendMode::kSrc, SK_PMColor4fWHITE, SkRect::Make(srcRect), + SkRect::MakeWH(srcW, srcH), GrAA::kNo, GrQuadAAFlags::kNone, + constraint, SkMatrix::I(), std::move(xform)); + texProxy = linearRTC->asTextureProxyRef(); + tempA = std::move(linearRTC); + srcX = 0; + srcY = 0; + constraint = SkCanvas::kFast_SrcRectConstraint; + } + while (stepsX || stepsY) { + int nextW = info.width(); + int nextH = info.height(); + if (stepsX < 0) { + nextW = info.width() << (-stepsX - 1); + stepsX++; + } else if (stepsX != 0) { + if (stepsX > 1) { + nextW = srcW * 2; + } + --stepsX; + } + if (stepsY < 0) { + nextH = info.height() << (-stepsY - 1); + stepsY++; + } else if (stepsY != 0) { + if (stepsY > 1) { + nextH = srcH * 2; + } + --stepsY; + } + auto input = tempA ? tempA.get() : this; + GrColorType colorType = input->colorInfo().colorType(); + auto cs = input->colorInfo().refColorSpace(); + sk_sp xform; + auto prevAlphaType = input->colorInfo().alphaType(); + if (!stepsX && !stepsY) { + // Might as well fold conversion to final info in the last step. + cs = info.refColorSpace(); + colorType = SkColorTypeToGrColorType(info.colorType()); + xform = GrColorSpaceXform::Make(input->colorInfo().colorSpace(), + input->colorInfo().alphaType(), cs.get(), + info.alphaType()); + } + tempB = fContext->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, nextW, nextH, colorType, std::move(cs), 1, GrMipMapped::kNo, + kTopLeft_GrSurfaceOrigin); + if (!tempB) { + return nullptr; + } + auto dstRect = SkRect::MakeWH(nextW, nextH); + if (rescaleQuality == kHigh_SkFilterQuality) { + SkMatrix matrix; + matrix.setScaleTranslate((float)srcW / nextW, (float)srcH / nextH, srcX, srcY); + std::unique_ptr fp; + auto dir = GrBicubicEffect::Direction::kXY; + if (nextW == srcW) { + dir = GrBicubicEffect::Direction::kY; + } else if (nextH == srcH) { + dir = GrBicubicEffect::Direction::kX; + } + if (srcW != texProxy->width() || srcH != texProxy->height()) { + auto domain = GrTextureDomain::MakeTexelDomain( + SkIRect::MakeXYWH(srcX, srcY, srcW, srcH), GrTextureDomain::kClamp_Mode); + fp = GrBicubicEffect::Make(texProxy, srcColorType, matrix, domain, dir, + prevAlphaType); + } else { + fp = GrBicubicEffect::Make(texProxy, srcColorType, matrix, dir, prevAlphaType); + } + if (xform) { + fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(xform)); + } + GrPaint paint; + paint.addColorFragmentProcessor(std::move(fp)); + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + tempB->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect, + dstRect); + } else { + auto filter = rescaleQuality == kNone_SkFilterQuality ? GrSamplerState::Filter::kNearest + : GrSamplerState::Filter::kBilerp; + auto srcSubset = SkRect::MakeXYWH(srcX, srcY, srcW, srcH); + tempB->drawTexture(GrNoClip(), texProxy, srcColorType, filter, SkBlendMode::kSrc, + SK_PMColor4fWHITE, srcSubset, dstRect, GrAA::kNo, + GrQuadAAFlags::kNone, constraint, SkMatrix::I(), std::move(xform)); + } + texProxy = tempB->asTextureProxyRef(); + tempA = std::move(tempB); + srcX = srcY = 0; + srcW = nextW; + srcH = nextH; + constraint = SkCanvas::kFast_SrcRectConstraint; + } + SkASSERT(tempA); + return tempA; +} + +GrSurfaceContext::PixelTransferResult GrSurfaceContext::transferPixels(GrColorType dstCT, + const SkIRect& rect) { + SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width()); + SkASSERT(rect.fTop >= 0 && rect.fBottom <= this->height()); + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + return {}; + } + auto rtProxy = this->asRenderTargetProxy(); + if (rtProxy && rtProxy->wrapsVkSecondaryCB()) { + return {}; + } + + auto proxy = this->asSurfaceProxy(); + auto supportedRead = this->caps()->supportedReadPixelsColorType(this->colorInfo().colorType(), + proxy->backendFormat(), dstCT); + // Fail if read color type does not have all of dstCT's color channels and those missing color + // channels are in the src. + uint32_t dstComponents = GrColorTypeComponentFlags(dstCT); + uint32_t legalReadComponents = GrColorTypeComponentFlags(supportedRead.fColorType); + uint32_t srcComponents = GrColorTypeComponentFlags(this->colorInfo().colorType()); + if ((~legalReadComponents & dstComponents) & srcComponents) { + return {}; + } + + if (!this->caps()->transferBufferSupport() || + !supportedRead.fOffsetAlignmentForTransferBuffer) { + return {}; + } + + size_t rowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * rect.width(); + size_t size = rowBytes * rect.height(); + auto buffer = direct->priv().resourceProvider()->createBuffer( + size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern); + if (!buffer) { + return {}; + } + auto srcRect = rect; + bool flip = proxy->origin() == kBottomLeft_GrSurfaceOrigin; + if (flip) { + srcRect = SkIRect::MakeLTRB(rect.fLeft, this->height() - rect.fBottom, rect.fRight, + this->height() - rect.fTop); + } + this->drawingManager()->newTransferFromRenderTask(this->asSurfaceProxyRef(), srcRect, + this->colorInfo().colorType(), + supportedRead.fColorType, buffer, 0); + PixelTransferResult result; + result.fTransferBuffer = std::move(buffer); + auto at = this->colorInfo().alphaType(); + if (supportedRead.fColorType != dstCT || flip) { + result.fPixelConverter = [w = rect.width(), h = rect.height(), dstCT, supportedRead, at]( + void* dst, const void* src) { + GrImageInfo srcInfo(supportedRead.fColorType, at, nullptr, w, h); + GrImageInfo dstInfo(dstCT, at, nullptr, w, h); + GrConvertPixels(dstInfo, dst, dstInfo.minRowBytes(), + srcInfo, src, srcInfo.minRowBytes(), + /* flipY = */ false); + }; + } + return result; +} diff --git a/gfx/skia/skia/src/gpu/GrSurfaceContext.h b/gfx/skia/skia/src/gpu/GrSurfaceContext.h new file mode 100644 index 0000000000..c9de7d0c16 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfaceContext.h @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSurfaceContext_DEFINED +#define GrSurfaceContext_DEFINED + +#include "include/core/SkFilterQuality.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurface.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrSurfaceProxy.h" + +class GrAuditTrail; +class GrDrawingManager; +class GrRecordingContext; +class GrRenderTargetContext; +class GrRenderTargetProxy; +class GrSingleOwner; +class GrSurface; +class GrSurfaceContextPriv; +class GrSurfaceProxy; +class GrTextureProxy; +struct SkIPoint; +struct SkIRect; + +/** + * A helper object to orchestrate commands for a particular surface + */ +class GrSurfaceContext { +public: + virtual ~GrSurfaceContext() = default; + + const GrColorInfo& colorInfo() const { return fColorInfo; } + + // TODO: these two calls would be way cooler if this object had a GrSurfaceProxy pointer + int width() const { return this->asSurfaceProxy()->width(); } + int height() const { return this->asSurfaceProxy()->height(); } + + const GrCaps* caps() const; + + /** + * Reads a rectangle of pixels from the render target context. + * @param dstInfo image info for the destination + * @param dst destination pixels for the read + * @param rowBytes bytes in a row of 'dst' + * @param srcPt offset w/in the surface context from which to read + * @param direct The direct context to use. If null will use our GrRecordingContext if it + * is a GrDirectContext and fail otherwise. + */ + bool readPixels(const GrImageInfo& dstInfo, void* dst, size_t rowBytes, SkIPoint srcPt, + GrContext* direct = nullptr); + + /** + * Writes a rectangle of pixels [srcInfo, srcBuffer, srcRowbytes] into the + * renderTargetContext at the specified position. + * @param srcInfo image info for the source pixels + * @param src source for the write + * @param rowBytes bytes in a row of 'src' + * @param dstPt offset w/in the surface context at which to write + * @param direct The direct context to use. If null will use our GrRecordingContext if it + * is a GrDirectContext and fail otherwise. + */ + bool writePixels(const GrImageInfo& srcInfo, const void* src, size_t rowBytes, SkIPoint dstPt, + GrContext* direct = nullptr); + + // TODO: this is virtual b.c. this object doesn't have a pointer to the wrapped GrSurfaceProxy? + virtual GrSurfaceProxy* asSurfaceProxy() = 0; + virtual const GrSurfaceProxy* asSurfaceProxy() const = 0; + virtual sk_sp asSurfaceProxyRef() = 0; + + virtual GrTextureProxy* asTextureProxy() = 0; + virtual const GrTextureProxy* asTextureProxy() const = 0; + virtual sk_sp asTextureProxyRef() = 0; + + virtual GrRenderTargetProxy* asRenderTargetProxy() = 0; + virtual sk_sp asRenderTargetProxyRef() = 0; + + virtual GrRenderTargetContext* asRenderTargetContext() { return nullptr; } + + GrAuditTrail* auditTrail(); + + // Provides access to functions that aren't part of the public API. + GrSurfaceContextPriv surfPriv(); + const GrSurfaceContextPriv surfPriv() const; + +#if GR_TEST_UTILS + bool testCopy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint) { + return this->copy(src, srcRect, dstPoint); + } + + bool testCopy(GrSurfaceProxy* src) { + return this->copy(src); + } +#endif + + +protected: + friend class GrSurfaceContextPriv; + + GrSurfaceContext(GrRecordingContext*, GrColorType, SkAlphaType, sk_sp); + + GrDrawingManager* drawingManager(); + const GrDrawingManager* drawingManager() const; + + SkDEBUGCODE(virtual void validate() const = 0;) + + SkDEBUGCODE(GrSingleOwner* singleOwner();) + + GrRecordingContext* fContext; + + // The rescaling step of asyncRescaleAndReadPixels[YUV420](). + std::unique_ptr rescale(const SkImageInfo& info, const SkIRect& srcRect, + SkSurface::RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality); + + // Inserts a transfer, part of the implementation of asyncReadPixels and + // asyncRescaleAndReadPixelsYUV420(). + struct PixelTransferResult { + using ConversionFn = void(void* dst, const void* mappedBuffer); + // If null then the transfer could not be performed. Otherwise this buffer will contain + // the pixel data when the transfer is complete. + sk_sp fTransferBuffer; + // If this is null then the transfer buffer will contain the data in the requested + // color type. Otherwise, when the transfer is done this must be called to convert + // from the transfer buffer's color type to the requested color type. + std::function fPixelConverter; + }; + PixelTransferResult transferPixels(GrColorType colorType, const SkIRect& rect); + +private: + friend class GrSurfaceProxy; // for copy + + /** + * Copy 'src' into the proxy backing this context. This call will not do any draw fallback. + * Currently only writePixels and replaceRenderTarget call this directly. All other copies + * should go through GrSurfaceProxy::Copy. + * @param src src of pixels + * @param srcRect the subset of 'src' to copy + * @param dstPoint the origin of the 'srcRect' in the destination coordinate space + * @return true if the copy succeeded; false otherwise + * + * Note: Notionally, 'srcRect' is clipped to 'src's extent with 'dstPoint' being adjusted. + * Then the 'srcRect' offset by 'dstPoint' is clipped against the dst's extent. + * The end result is only valid src pixels and dst pixels will be touched but the copied + * regions will not be shifted. The 'src' must have the same origin as the backing proxy + * of fSurfaceContext. + */ + bool copy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint); + + bool copy(GrSurfaceProxy* src) { + return this->copy(src, SkIRect::MakeWH(src->width(), src->height()), SkIPoint::Make(0, 0)); + } + + GrColorInfo fColorInfo; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSurfaceContextPriv.h b/gfx/skia/skia/src/gpu/GrSurfaceContextPriv.h new file mode 100644 index 0000000000..33d2aa7e4c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfaceContextPriv.h @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSurfaceContextPriv_DEFINED +#define GrSurfaceContextPriv_DEFINED + +#include "src/gpu/GrSurfaceContext.h" + +/** Class that adds methods to GrSurfaceContext that are only intended for use internal to + Skia. This class is purely a privileged window into GrSurfaceContext. It should never have + additional data members or virtual methods. */ +class GrSurfaceContextPriv { +public: + GrRecordingContext* getContext() { return fSurfaceContext->fContext; } + +private: + explicit GrSurfaceContextPriv(GrSurfaceContext* surfaceContext) + : fSurfaceContext(surfaceContext) { + } + + GrSurfaceContextPriv(const GrSurfaceContextPriv&) {} // unimpl + GrSurfaceContextPriv& operator=(const GrSurfaceContextPriv&); // unimpl + + // No taking addresses of this type. + const GrSurfaceContextPriv* operator&() const; + GrSurfaceContextPriv* operator&(); + + GrSurfaceContext* fSurfaceContext; + + friend class GrSurfaceContext; // to construct/copy this type. +}; + +inline GrSurfaceContextPriv GrSurfaceContext::surfPriv() { + return GrSurfaceContextPriv(this); +} + +inline const GrSurfaceContextPriv GrSurfaceContext::surfPriv() const { + return GrSurfaceContextPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSurfacePriv.h b/gfx/skia/skia/src/gpu/GrSurfacePriv.h new file mode 100644 index 0000000000..bd7aa539f6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfacePriv.h @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSurfacePriv_DEFINED +#define GrSurfacePriv_DEFINED + +#include "include/gpu/GrSurface.h" + +/** Class that adds methods to GrSurface that are only intended for use internal to Skia. + This class is purely a privileged window into GrSurface. It should never have additional data + members or virtual methods. + Non-static methods that are not trivial inlines should be spring-boarded (e.g. declared and + implemented privately in GrSurface with a inline public method here). */ +class GrSurfacePriv { +public: + GrInternalSurfaceFlags flags() const { return fSurface->fSurfaceFlags; } + +private: + explicit GrSurfacePriv(GrSurface* surface) : fSurface(surface) {} + GrSurfacePriv(const GrSurfacePriv&); // unimpl + GrSurfacePriv& operator=(const GrSurfacePriv&); // unimpl + + // No taking addresses of this type. + const GrSurfacePriv* operator&() const; + GrSurfacePriv* operator&(); + + GrSurface* fSurface; + + friend class GrSurface; // to construct/copy this type. +}; + +inline GrSurfacePriv GrSurface::surfacePriv() { return GrSurfacePriv(this); } + +inline const GrSurfacePriv GrSurface::surfacePriv() const { + return GrSurfacePriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSurfaceProxy.cpp b/gfx/skia/skia/src/gpu/GrSurfaceProxy.cpp new file mode 100644 index 0000000000..a4878477e9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfaceProxy.cpp @@ -0,0 +1,523 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrSurfaceProxyPriv.h" + +#include "include/gpu/GrContext.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrOpsTask.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTextureContext.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureRenderTargetProxy.h" + +#ifdef SK_DEBUG +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetPriv.h" + +static bool is_valid_lazy(const GrSurfaceDesc& desc, SkBackingFit fit) { + // A "fully" lazy proxy's width and height are not known until instantiation time. + // So fully lazy proxies are created with width and height < 0. Regular lazy proxies must be + // created with positive widths and heights. The width and height are set to 0 only after a + // failed instantiation. The former must be "approximate" fit while the latter can be either. + return desc.fConfig != kUnknown_GrPixelConfig && + ((desc.fWidth < 0 && desc.fHeight < 0 && SkBackingFit::kApprox == fit) || + (desc.fWidth > 0 && desc.fHeight > 0)); +} + +static bool is_valid_non_lazy(const GrSurfaceDesc& desc) { + return desc.fWidth > 0 && desc.fHeight > 0 && desc.fConfig != kUnknown_GrPixelConfig; +} +#endif + +// Deferred version +GrSurfaceProxy::GrSurfaceProxy(const GrBackendFormat& format, + const GrSurfaceDesc& desc, + GrRenderable renderable, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : fSurfaceFlags(surfaceFlags) + , fFormat(format) + , fConfig(desc.fConfig) + , fWidth(desc.fWidth) + , fHeight(desc.fHeight) + , fOrigin(origin) + , fTextureSwizzle(textureSwizzle) + , fFit(fit) + , fBudgeted(budgeted) + , fUseAllocator(useAllocator) + , fIsProtected(isProtected) + , fGpuMemorySize(kInvalidGpuMemorySize) { + SkASSERT(fFormat.isValid()); + SkASSERT(is_valid_non_lazy(desc)); + if (GrPixelConfigIsCompressed(desc.fConfig)) { + SkASSERT(renderable == GrRenderable::kNo); + fSurfaceFlags |= GrInternalSurfaceFlags::kReadOnly; + } +} + +// Lazy-callback version +GrSurfaceProxy::GrSurfaceProxy(LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + GrRenderable renderable, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : fSurfaceFlags(surfaceFlags) + , fFormat(format) + , fConfig(desc.fConfig) + , fWidth(desc.fWidth) + , fHeight(desc.fHeight) + , fOrigin(origin) + , fTextureSwizzle(textureSwizzle) + , fFit(fit) + , fBudgeted(budgeted) + , fUseAllocator(useAllocator) + , fLazyInstantiateCallback(std::move(callback)) + , fIsProtected(isProtected) + , fGpuMemorySize(kInvalidGpuMemorySize) { + SkASSERT(fFormat.isValid()); + SkASSERT(fLazyInstantiateCallback); + SkASSERT(is_valid_lazy(desc, fit)); + if (GrPixelConfigIsCompressed(desc.fConfig)) { + SkASSERT(renderable == GrRenderable::kNo); + fSurfaceFlags |= GrInternalSurfaceFlags::kReadOnly; + } +} + +// Wrapped version +GrSurfaceProxy::GrSurfaceProxy(sk_sp surface, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + SkBackingFit fit, + UseAllocator useAllocator) + : fTarget(std::move(surface)) + , fSurfaceFlags(fTarget->surfacePriv().flags()) + , fFormat(fTarget->backendFormat()) + , fConfig(fTarget->config()) + , fWidth(fTarget->width()) + , fHeight(fTarget->height()) + , fOrigin(origin) + , fTextureSwizzle(textureSwizzle) + , fFit(fit) + , fBudgeted(fTarget->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted + ? SkBudgeted::kYes + : SkBudgeted::kNo) + , fUseAllocator(useAllocator) + , fUniqueID(fTarget->uniqueID()) // Note: converting from unique resource ID to a proxy ID! + , fIsProtected(fTarget->isProtected() ? GrProtected::kYes : GrProtected::kNo) + , fGpuMemorySize(kInvalidGpuMemorySize) { + SkASSERT(fFormat.isValid()); +} + +GrSurfaceProxy::~GrSurfaceProxy() { + // For this to be deleted the opsTask that held a ref on it (if there was one) must have been + // deleted. Which would have cleared out this back pointer. + SkASSERT(!fLastRenderTask); +} + +bool GrSurfaceProxyPriv::AttachStencilIfNeeded(GrResourceProvider* resourceProvider, + GrSurface* surface, int minStencilSampleCount) { + if (minStencilSampleCount) { + GrRenderTarget* rt = surface->asRenderTarget(); + if (!rt) { + SkASSERT(0); + return false; + } + + if (!resourceProvider->attachStencilAttachment(rt, minStencilSampleCount)) { + return false; + } + } + + return true; +} + +sk_sp GrSurfaceProxy::createSurfaceImpl(GrResourceProvider* resourceProvider, + int sampleCnt, + int minStencilSampleCount, + GrRenderable renderable, + GrMipMapped mipMapped) const { + SkASSERT(mipMapped == GrMipMapped::kNo || fFit == SkBackingFit::kExact); + SkASSERT(!this->isLazy()); + SkASSERT(!fTarget); + GrSurfaceDesc desc; + desc.fWidth = fWidth; + desc.fHeight = fHeight; + desc.fConfig = fConfig; + + sk_sp surface; + if (SkBackingFit::kApprox == fFit) { + surface = resourceProvider->createApproxTexture(desc, fFormat, renderable, sampleCnt, + fIsProtected); + } else { + surface = resourceProvider->createTexture(desc, fFormat, renderable, sampleCnt, mipMapped, + fBudgeted, fIsProtected); + } + if (!surface) { + return nullptr; + } + + if (!GrSurfaceProxyPriv::AttachStencilIfNeeded(resourceProvider, surface.get(), + minStencilSampleCount)) { + return nullptr; + } + + return surface; +} + +bool GrSurfaceProxy::canSkipResourceAllocator() const { + if (fUseAllocator == UseAllocator::kNo) { + // Usually an atlas or onFlush proxy + return true; + } + + auto peek = this->peekSurface(); + if (!peek) { + return false; + } + // If this resource is already allocated and not recyclable then the resource allocator does + // not need to do anything with it. + return !peek->resourcePriv().getScratchKey().isValid(); +} + +void GrSurfaceProxy::assign(sk_sp surface) { + SkASSERT(!fTarget && surface); + + SkDEBUGCODE(this->validateSurface(surface.get());) + + fTarget = std::move(surface); + +#ifdef SK_DEBUG + if (this->asRenderTargetProxy()) { + SkASSERT(fTarget->asRenderTarget()); + if (int minStencilSampleCount = this->asRenderTargetProxy()->numStencilSamples()) { + auto* stencil = fTarget->asRenderTarget()->renderTargetPriv().getStencilAttachment(); + SkASSERT(stencil); + SkASSERT(stencil->numSamples() >= minStencilSampleCount); + } + } + + if (kInvalidGpuMemorySize != this->getRawGpuMemorySize_debugOnly()) { + SkASSERT(fTarget->gpuMemorySize() <= this->getRawGpuMemorySize_debugOnly()); + } +#endif +} + +bool GrSurfaceProxy::instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt, + int minStencilSampleCount, GrRenderable renderable, + GrMipMapped mipMapped, const GrUniqueKey* uniqueKey) { + SkASSERT(!this->isLazy()); + if (fTarget) { + if (uniqueKey && uniqueKey->isValid()) { + SkASSERT(fTarget->getUniqueKey().isValid() && fTarget->getUniqueKey() == *uniqueKey); + } + return GrSurfaceProxyPriv::AttachStencilIfNeeded(resourceProvider, fTarget.get(), + minStencilSampleCount); + } + + sk_sp surface = this->createSurfaceImpl( + resourceProvider, sampleCnt, minStencilSampleCount, renderable, mipMapped); + if (!surface) { + return false; + } + + // If there was an invalidation message pending for this key, we might have just processed it, + // causing the key (stored on this proxy) to become invalid. + if (uniqueKey && uniqueKey->isValid()) { + resourceProvider->assignUniqueKeyToResource(*uniqueKey, surface.get()); + } + + this->assign(std::move(surface)); + + return true; +} + +void GrSurfaceProxy::deinstantiate() { + SkASSERT(this->isInstantiated()); + fTarget = nullptr; +} + +void GrSurfaceProxy::computeScratchKey(GrScratchKey* key) const { + SkASSERT(!this->isFullyLazy()); + GrRenderable renderable = GrRenderable::kNo; + int sampleCount = 1; + if (const auto* rtp = this->asRenderTargetProxy()) { + renderable = GrRenderable::kYes; + sampleCount = rtp->numSamples(); + } + + const GrTextureProxy* tp = this->asTextureProxy(); + GrMipMapped mipMapped = GrMipMapped::kNo; + if (tp) { + mipMapped = tp->mipMapped(); + } + + int width = this->worstCaseWidth(); + int height = this->worstCaseHeight(); + + GrTexturePriv::ComputeScratchKey(this->config(), width, height, renderable, sampleCount, + mipMapped, fIsProtected, key); +} + +void GrSurfaceProxy::setLastRenderTask(GrRenderTask* renderTask) { +#ifdef SK_DEBUG + if (fLastRenderTask) { + SkASSERT(fLastRenderTask->isClosed()); + } +#endif + + // Un-reffed + fLastRenderTask = renderTask; +} + +GrOpsTask* GrSurfaceProxy::getLastOpsTask() { + return fLastRenderTask ? fLastRenderTask->asOpsTask() : nullptr; +} + +int GrSurfaceProxy::worstCaseWidth() const { + SkASSERT(!this->isFullyLazy()); + if (fTarget) { + return fTarget->width(); + } + + if (SkBackingFit::kExact == fFit) { + return fWidth; + } + return GrResourceProvider::MakeApprox(fWidth); +} + +int GrSurfaceProxy::worstCaseHeight() const { + SkASSERT(!this->isFullyLazy()); + if (fTarget) { + return fTarget->height(); + } + + if (SkBackingFit::kExact == fFit) { + return fHeight; + } + return GrResourceProvider::MakeApprox(fHeight); +} + +#ifdef SK_DEBUG +void GrSurfaceProxy::validate(GrContext_Base* context) const { + if (fTarget) { + SkASSERT(fTarget->getContext() == context); + } +} +#endif + +sk_sp GrSurfaceProxy::Copy(GrRecordingContext* context, + GrSurfaceProxy* src, + GrColorType srcColorType, + GrMipMapped mipMapped, + SkIRect srcRect, + SkBackingFit fit, + SkBudgeted budgeted, + RectsMustMatch rectsMustMatch) { + SkASSERT(!src->isFullyLazy()); + GrProtected isProtected = src->isProtected() ? GrProtected::kYes : GrProtected::kNo; + int width; + int height; + + SkIPoint dstPoint; + if (rectsMustMatch == RectsMustMatch::kYes) { + width = src->width(); + height = src->height(); + dstPoint = {srcRect.fLeft, srcRect.fTop}; + } else { + width = srcRect.width(); + height = srcRect.height(); + dstPoint = {0, 0}; + } + + if (!srcRect.intersect(SkIRect::MakeWH(src->width(), src->height()))) { + return nullptr; + } + auto colorType = GrPixelConfigToColorType(src->config()); + if (src->backendFormat().textureType() != GrTextureType::kExternal) { + auto dstContext = context->priv().makeDeferredTextureContext( + fit, width, height, colorType, kUnknown_SkAlphaType, nullptr, mipMapped, + src->origin(), budgeted, isProtected); + if (!dstContext) { + return nullptr; + } + if (dstContext->copy(src, srcRect, dstPoint)) { + return dstContext->asTextureProxyRef(); + } + } + if (src->asTextureProxy()) { + auto dstContext = context->priv().makeDeferredRenderTargetContext( + fit, width, height, colorType, nullptr, 1, mipMapped, src->origin(), nullptr, + budgeted); + + if (dstContext && dstContext->blitTexture(src->asTextureProxy(), srcColorType, srcRect, + dstPoint)) { + return dstContext->asTextureProxyRef(); + } + } + // Can't use backend copies or draws. + return nullptr; +} + +sk_sp GrSurfaceProxy::Copy(GrRecordingContext* context, GrSurfaceProxy* src, + GrColorType srcColorType, GrMipMapped mipMapped, + SkBackingFit fit, SkBudgeted budgeted) { + SkASSERT(!src->isFullyLazy()); + return Copy(context, src, srcColorType, mipMapped, SkIRect::MakeWH(src->width(), src->height()), + fit, budgeted); +} + +#if GR_TEST_UTILS +int32_t GrSurfaceProxy::testingOnly_getBackingRefCnt() const { + if (fTarget) { + return fTarget->testingOnly_getRefCnt(); + } + + return -1; // no backing GrSurface +} + +GrInternalSurfaceFlags GrSurfaceProxy::testingOnly_getFlags() const { + return fSurfaceFlags; +} +#endif + +void GrSurfaceProxyPriv::exactify(bool allocatedCaseOnly) { + SkASSERT(!fProxy->isFullyLazy()); + if (this->isExact()) { + return; + } + + SkASSERT(SkBackingFit::kApprox == fProxy->fFit); + + if (fProxy->fTarget) { + // The kApprox but already instantiated case. Setting the proxy's width & height to + // the instantiated width & height could have side-effects going forward, since we're + // obliterating the area of interest information. This call (exactify) only used + // when converting an SkSpecialImage to an SkImage so the proxy shouldn't be + // used for additional draws. + fProxy->fWidth = fProxy->fTarget->width(); + fProxy->fHeight = fProxy->fTarget->height(); + return; + } + +#ifndef SK_CRIPPLE_TEXTURE_REUSE + // In the post-implicit-allocation world we can't convert this proxy to be exact fit + // at this point. With explicit allocation switching this to exact will result in a + // different allocation at flush time. With implicit allocation, allocation would occur + // at draw time (rather than flush time) so this pathway was encountered less often (if + // at all). + if (allocatedCaseOnly) { + return; + } +#endif + + // The kApprox uninstantiated case. Making this proxy be exact should be okay. + // It could mess things up if prior decisions were based on the approximate size. + fProxy->fFit = SkBackingFit::kExact; + // If fGpuMemorySize is used when caching specialImages for the image filter DAG. If it has + // already been computed we want to leave it alone so that amount will be removed when + // the special image goes away. If it hasn't been computed yet it might as well compute the + // exact amount. +} + +bool GrSurfaceProxyPriv::doLazyInstantiation(GrResourceProvider* resourceProvider) { + SkASSERT(fProxy->isLazy()); + + sk_sp surface; + if (fProxy->asTextureProxy() && fProxy->asTextureProxy()->getUniqueKey().isValid()) { + // First try to reattach to a cached version if the proxy is uniquely keyed + surface = resourceProvider->findByUniqueKey( + fProxy->asTextureProxy()->getUniqueKey()); + } + + bool syncKey = true; + bool releaseCallback = false; + if (!surface) { + auto result = fProxy->fLazyInstantiateCallback(resourceProvider); + surface = std::move(result.fSurface); + syncKey = result.fKeyMode == GrSurfaceProxy::LazyInstantiationKeyMode::kSynced; + releaseCallback = surface && result.fReleaseCallback; + } + if (!surface) { + fProxy->fWidth = 0; + fProxy->fHeight = 0; + return false; + } + + if (fProxy->isFullyLazy()) { + // This was a fully lazy proxy. We need to fill in the width & height. For partially + // lazy proxies we must preserve the original width & height since that indicates + // the content area. + fProxy->fWidth = surface->width(); + fProxy->fHeight = surface->height(); + } + + SkASSERT(fProxy->fWidth <= surface->width()); + SkASSERT(fProxy->fHeight <= surface->height()); + + auto rt = fProxy->asRenderTargetProxy(); + int minStencilSampleCount = rt ? rt->numSamples() : 0; + + if (!GrSurfaceProxyPriv::AttachStencilIfNeeded( + resourceProvider, surface.get(), minStencilSampleCount)) { + return false; + } + + if (GrTextureProxy* texProxy = fProxy->asTextureProxy()) { + texProxy->setTargetKeySync(syncKey); + if (syncKey) { + const GrUniqueKey& key = texProxy->getUniqueKey(); + if (key.isValid()) { + if (!surface->asTexture()->getUniqueKey().isValid()) { + // If 'surface' is newly created, attach the unique key + resourceProvider->assignUniqueKeyToResource(key, surface.get()); + } else { + // otherwise we had better have reattached to a cached version + SkASSERT(surface->asTexture()->getUniqueKey() == key); + } + } else { + SkASSERT(!surface->getUniqueKey().isValid()); + } + } + } + + this->assign(std::move(surface)); + if (releaseCallback) { + fProxy->fLazyInstantiateCallback = nullptr; + } + + return true; +} + +#ifdef SK_DEBUG +void GrSurfaceProxy::validateSurface(const GrSurface* surface) { + SkASSERT(surface->config() == fConfig); + + this->onValidateSurface(surface); +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrSurfaceProxy.h b/gfx/skia/skia/src/gpu/GrSurfaceProxy.h new file mode 100644 index 0000000000..7ce65d82cf --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfaceProxy.h @@ -0,0 +1,445 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSurfaceProxy_DEFINED +#define GrSurfaceProxy_DEFINED + +#include "include/core/SkRect.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrGpuResource.h" +#include "include/gpu/GrSurface.h" +#include "include/gpu/GrTexture.h" +#include "include/private/SkNoncopyable.h" +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/GrSwizzle.h" + +class GrCaps; +class GrContext_Base; +class GrOpsTask; +class GrRecordingContext; +class GrRenderTargetProxy; +class GrRenderTask; +class GrResourceProvider; +class GrSurfaceContext; +class GrSurfaceProxyPriv; +class GrTextureProxy; + +class GrSurfaceProxy : public GrNonAtomicRef { +public: + virtual ~GrSurfaceProxy(); + + /** + * Indicates "resolutions" that need to be done on a surface before its pixels can be accessed. + * If both types of resolve are requested, the MSAA resolve will happen first. + */ + enum class ResolveFlags { + kNone = 0, + kMSAA = 1 << 0, // Blit and resolve an internal MSAA render buffer into the texture. + kMipMaps = 1 << 1, // Regenerate all mipmap levels. + }; + + /** + * Some lazy proxy callbacks want to set their own (or no key) on the GrSurfaces they return. + * Others want the GrSurface's key to be kept in sync with the proxy's key. This enum controls + * the key relationship between proxies and their targets. + */ + enum class LazyInstantiationKeyMode { + /** + * Don't key the GrSurface with the proxy's key. The lazy instantiation callback is free to + * return a GrSurface that already has a unique key unrelated to the proxy's key. + */ + kUnsynced, + /** + * Keep the GrSurface's unique key in sync with the proxy's unique key. The GrSurface + * returned from the lazy instantiation callback must not have a unique key or have the same + * same unique key as the proxy. If the proxy is later assigned a key it is in turn assigned + * to the GrSurface. + */ + kSynced + }; + + struct LazyCallbackResult { + LazyCallbackResult() = default; + LazyCallbackResult(const LazyCallbackResult&) = default; + LazyCallbackResult(LazyCallbackResult&& that) = default; + LazyCallbackResult(sk_sp surf, + bool releaseCallback = true, + LazyInstantiationKeyMode mode = LazyInstantiationKeyMode::kSynced) + : fSurface(std::move(surf)), fKeyMode(mode), fReleaseCallback(releaseCallback) {} + LazyCallbackResult(sk_sp tex) + : LazyCallbackResult(sk_sp(std::move(tex))) {} + + LazyCallbackResult& operator=(const LazyCallbackResult&) = default; + LazyCallbackResult& operator=(LazyCallbackResult&&) = default; + + sk_sp fSurface; + LazyInstantiationKeyMode fKeyMode = LazyInstantiationKeyMode::kSynced; + /** + * Should the callback be disposed of after it has returned or preserved until the proxy + * is freed. Only honored if fSurface is not-null. If it is null the callback is preserved. + */ + bool fReleaseCallback = true; + }; + + using LazyInstantiateCallback = std::function; + + enum class UseAllocator { + /** + * This proxy will be instantiated outside the allocator (e.g. for proxies that are + * instantiated in on-flush callbacks). + */ + kNo = false, + /** + * GrResourceAllocator should instantiate this proxy. + */ + kYes = true, + }; + + bool isLazy() const { return !this->isInstantiated() && SkToBool(fLazyInstantiateCallback); } + + bool isFullyLazy() const { + bool result = fHeight < 0; + SkASSERT(result == (fWidth < 0)); + SkASSERT(!result || this->isLazy()); + return result; + } + + GrPixelConfig config() const { return fConfig; } + + int width() const { + SkASSERT(!this->isFullyLazy()); + return fWidth; + } + + int height() const { + SkASSERT(!this->isFullyLazy()); + return fHeight; + } + + SkISize isize() const { return {fWidth, fHeight}; } + + int worstCaseWidth() const; + int worstCaseHeight() const; + /** + * Helper that gets the width and height of the surface as a bounding rectangle. + */ + SkRect getBoundsRect() const { + SkASSERT(!this->isFullyLazy()); + return SkRect::MakeIWH(this->width(), this->height()); + } + /** + * Helper that gets the worst case width and height of the surface as a bounding rectangle. + */ + SkRect getWorstCaseBoundsRect() const { + SkASSERT(!this->isFullyLazy()); + return SkRect::MakeIWH(this->worstCaseWidth(), this->worstCaseHeight()); + } + + GrSurfaceOrigin origin() const { + SkASSERT(kTopLeft_GrSurfaceOrigin == fOrigin || kBottomLeft_GrSurfaceOrigin == fOrigin); + return fOrigin; + } + + const GrSwizzle& textureSwizzle() const { return fTextureSwizzle; } + + const GrBackendFormat& backendFormat() const { return fFormat; } + + class UniqueID { + public: + static UniqueID InvalidID() { + return UniqueID(uint32_t(SK_InvalidUniqueID)); + } + + // wrapped + explicit UniqueID(const GrGpuResource::UniqueID& id) : fID(id.asUInt()) { } + // deferred and lazy-callback + UniqueID() : fID(GrGpuResource::CreateUniqueID()) { } + + uint32_t asUInt() const { return fID; } + + bool operator==(const UniqueID& other) const { + return fID == other.fID; + } + bool operator!=(const UniqueID& other) const { + return !(*this == other); + } + + void makeInvalid() { fID = SK_InvalidUniqueID; } + bool isInvalid() const { return SK_InvalidUniqueID == fID; } + + private: + explicit UniqueID(uint32_t id) : fID(id) {} + + uint32_t fID; + }; + + /* + * The contract for the uniqueID is: + * for wrapped resources: + * the uniqueID will match that of the wrapped resource + * + * for deferred resources: + * the uniqueID will be different from the real resource, when it is allocated + * the proxy's uniqueID will not change across the instantiate call + * + * the uniqueIDs of the proxies and the resources draw from the same pool + * + * What this boils down to is that the uniqueID of a proxy can be used to consistently + * track/identify a proxy but should never be used to distinguish between + * resources and proxies - beware! + */ + UniqueID uniqueID() const { return fUniqueID; } + + UniqueID underlyingUniqueID() const { + if (fTarget) { + return UniqueID(fTarget->uniqueID()); + } + + return fUniqueID; + } + + virtual bool instantiate(GrResourceProvider*) = 0; + + void deinstantiate(); + + /** + * Proxies that are already instantiated and whose backing surface cannot be recycled to + * instantiate other proxies do not need to be considered by GrResourceAllocator. + */ + bool canSkipResourceAllocator() const; + + /** + * @return the texture proxy associated with the surface proxy, may be NULL. + */ + virtual GrTextureProxy* asTextureProxy() { return nullptr; } + virtual const GrTextureProxy* asTextureProxy() const { return nullptr; } + + /** + * @return the render target proxy associated with the surface proxy, may be NULL. + */ + virtual GrRenderTargetProxy* asRenderTargetProxy() { return nullptr; } + virtual const GrRenderTargetProxy* asRenderTargetProxy() const { return nullptr; } + + bool isInstantiated() const { return SkToBool(fTarget); } + + // If the proxy is already instantiated, return its backing GrTexture; if not, return null. + GrSurface* peekSurface() const { return fTarget.get(); } + + // If this is a texture proxy and the proxy is already instantiated, return its backing + // GrTexture; if not, return null. + GrTexture* peekTexture() const { return fTarget ? fTarget->asTexture() : nullptr; } + + // If this is a render target proxy and the proxy is already instantiated, return its backing + // GrRenderTarget; if not, return null. + GrRenderTarget* peekRenderTarget() const { + return fTarget ? fTarget->asRenderTarget() : nullptr; + } + + /** + * Does the resource count against the resource budget? + */ + SkBudgeted isBudgeted() const { return fBudgeted; } + + /** + * The pixel values of this proxy's surface cannot be modified (e.g. doesn't support write + * pixels or MIP map level regen). Read-only proxies also bypass interval tracking and + * assignment in GrResourceAllocator. + */ + bool readOnly() const { return fSurfaceFlags & GrInternalSurfaceFlags::kReadOnly; } + + /** + * This means surface is a multisampled render target, and internally holds a non-msaa texture + * for resolving into. The render target resolves itself by blitting into this internal texture. + * (asTexture() might or might not return the internal texture, but if it does, we always + * resolve the render target before accessing this texture's data.) + */ + bool requiresManualMSAAResolve() const { + return fSurfaceFlags & GrInternalSurfaceFlags::kRequiresManualMSAAResolve; + } + + void setLastRenderTask(GrRenderTask*); + GrRenderTask* getLastRenderTask() { return fLastRenderTask; } + + GrOpsTask* getLastOpsTask(); + + /** + * Retrieves the amount of GPU memory that will be or currently is used by this resource + * in bytes. It is approximate since we aren't aware of additional padding or copies made + * by the driver. + * + * @return the amount of GPU memory used in bytes + */ + size_t gpuMemorySize(const GrCaps& caps) const { + SkASSERT(!this->isFullyLazy()); + if (fTarget) { + return fTarget->gpuMemorySize(); + } + if (kInvalidGpuMemorySize == fGpuMemorySize) { + fGpuMemorySize = this->onUninstantiatedGpuMemorySize(caps); + SkASSERT(kInvalidGpuMemorySize != fGpuMemorySize); + } + return fGpuMemorySize; + } + + enum class RectsMustMatch : bool { + kNo = false, + kYes = true + }; + + // Helper function that creates a temporary SurfaceContext to perform the copy + // The copy is is not a render target and not multisampled. + static sk_sp Copy(GrRecordingContext*, GrSurfaceProxy* src, + GrColorType srcColorType, GrMipMapped, + SkIRect srcRect, SkBackingFit, SkBudgeted, + RectsMustMatch = RectsMustMatch::kNo); + + // Copy the entire 'src' + static sk_sp Copy(GrRecordingContext*, GrSurfaceProxy* src, + GrColorType srcColortype, GrMipMapped, SkBackingFit, + SkBudgeted); + +#if GR_TEST_UTILS + int32_t testingOnly_getBackingRefCnt() const; + GrInternalSurfaceFlags testingOnly_getFlags() const; +#endif + + SkDEBUGCODE(void validate(GrContext_Base*) const;) + + // Provides access to functions that aren't part of the public API. + inline GrSurfaceProxyPriv priv(); + inline const GrSurfaceProxyPriv priv() const; + + // Returns true if we are working with protected content. + bool isProtected() const { return fIsProtected == GrProtected::kYes; } + +protected: + // Deferred version - takes a new UniqueID from the shared resource/proxy pool. + GrSurfaceProxy(const GrBackendFormat&, + const GrSurfaceDesc&, + GrRenderable, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + // Lazy-callback version - takes a new UniqueID from the shared resource/proxy pool. + GrSurfaceProxy(LazyInstantiateCallback&&, + const GrBackendFormat&, + const GrSurfaceDesc&, + GrRenderable, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + + // Wrapped version - shares the UniqueID of the passed surface. + // Takes UseAllocator because even though this is already instantiated it still can participate + // in allocation by having its backing resource recycled to other uninstantiated proxies or + // not depending on UseAllocator. + GrSurfaceProxy(sk_sp, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + SkBackingFit, + UseAllocator); + + friend class GrSurfaceProxyPriv; + + // Methods made available via GrSurfaceProxyPriv + bool ignoredByResourceAllocator() const { return fIgnoredByResourceAllocator; } + void setIgnoredByResourceAllocator() { fIgnoredByResourceAllocator = true; } + + void computeScratchKey(GrScratchKey*) const; + + virtual sk_sp createSurface(GrResourceProvider*) const = 0; + void assign(sk_sp surface); + + sk_sp createSurfaceImpl(GrResourceProvider*, int sampleCnt, + int minStencilSampleCount, GrRenderable, GrMipMapped) const; + + // Once the size of a fully-lazy proxy is decided, and before it gets instantiated, the client + // can use this optional method to specify the proxy's size. (A proxy's size can be less than + // the GPU surface that backs it. e.g., SkBackingFit::kApprox.) Otherwise, the proxy's size will + // be set to match the underlying GPU surface upon instantiation. + void setLazySize(int width, int height) { + SkASSERT(this->isFullyLazy()); + SkASSERT(width > 0 && height > 0); + fWidth = width; + fHeight = height; + } + + bool instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt, + int minStencilSampleCount, GrRenderable, GrMipMapped, const GrUniqueKey*); + + // For deferred proxies this will be null until the proxy is instantiated. + // For wrapped proxies it will point to the wrapped resource. + sk_sp fTarget; + + // In many cases these flags aren't actually known until the proxy has been instantiated. + // However, Ganesh frequently needs to change its behavior based on these settings. For + // internally create proxies we will know these properties ahead of time. For wrapped + // proxies we will copy the properties off of the GrSurface. For lazy proxies we force the + // call sites to provide the required information ahead of time. At instantiation time + // we verify that the assumed properties match the actual properties. + GrInternalSurfaceFlags fSurfaceFlags; + +private: + // For wrapped resources, 'fFormat', 'fConfig', 'fWidth', 'fHeight', and 'fOrigin; will always + // be filled in from the wrapped resource. + const GrBackendFormat fFormat; + const GrPixelConfig fConfig; + int fWidth; + int fHeight; + const GrSurfaceOrigin fOrigin; + const GrSwizzle fTextureSwizzle; + + SkBackingFit fFit; // always kApprox for lazy-callback resources + // always kExact for wrapped resources + mutable SkBudgeted fBudgeted; // always kYes for lazy-callback resources + // set from the backing resource for wrapped resources + // mutable bc of SkSurface/SkImage wishy-washiness + // Only meaningful if fLazyInstantiateCallback is non-null. + UseAllocator fUseAllocator; + + const UniqueID fUniqueID; // set from the backing resource for wrapped resources + + LazyInstantiateCallback fLazyInstantiateCallback; + + SkDEBUGCODE(void validateSurface(const GrSurface*);) + SkDEBUGCODE(virtual void onValidateSurface(const GrSurface*) = 0;) + + static const size_t kInvalidGpuMemorySize = ~static_cast(0); + SkDEBUGCODE(size_t getRawGpuMemorySize_debugOnly() const { return fGpuMemorySize; }) + + virtual size_t onUninstantiatedGpuMemorySize(const GrCaps&) const = 0; + + bool fIgnoredByResourceAllocator = false; + GrProtected fIsProtected; + + // This entry is lazily evaluated so, when the proxy wraps a resource, the resource + // will be called but, when the proxy is deferred, it will compute the answer itself. + // If the proxy computes its own answer that answer is checked (in debug mode) in + // the instantiation method. + mutable size_t fGpuMemorySize; + + // The last GrRenderTask that wrote to or is currently going to write to this surface + // The GrRenderTask can be closed (e.g., no surface context is currently bound + // to this proxy). + // This back-pointer is required so that we can add a dependancy between + // the GrRenderTask used to create the current contents of this surface + // and the GrRenderTask of a destination surface to which this one is being drawn or copied. + // This pointer is unreffed. GrRenderTasks own a ref on their surface proxies. + GrRenderTask* fLastRenderTask = nullptr; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrSurfaceProxy::ResolveFlags) + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSurfaceProxyPriv.h b/gfx/skia/skia/src/gpu/GrSurfaceProxyPriv.h new file mode 100644 index 0000000000..60704ac0d8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSurfaceProxyPriv.h @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSurfaceProxyPriv_DEFINED +#define GrSurfaceProxyPriv_DEFINED + +#include "src/gpu/GrSurfaceProxy.h" + +#include "src/gpu/GrResourceProvider.h" + +/** Class that adds methods to GrSurfaceProxy that are only intended for use internal to Skia. + This class is purely a privileged window into GrSurfaceProxy. It should never have additional + data members or virtual methods. */ +class GrSurfaceProxyPriv { +public: + void computeScratchKey(GrScratchKey* key) const { return fProxy->computeScratchKey(key); } + + // Create a GrSurface-derived class that meets the requirements (i.e, desc, renderability) + // of the GrSurfaceProxy. + sk_sp createSurface(GrResourceProvider* resourceProvider) const { + return fProxy->createSurface(resourceProvider); + } + + // Assign this proxy the provided GrSurface as its backing surface + void assign(sk_sp surface) { fProxy->assign(std::move(surface)); } + + // Don't abuse this call!!!!!!! + bool isExact() const { return SkBackingFit::kExact == fProxy->fFit; } + + // Don't. Just don't. + void exactify(bool allocatedCaseOnly); + + void setLazySize(int width, int height) { fProxy->setLazySize(width, height); } + + bool doLazyInstantiation(GrResourceProvider*); + + + static bool SK_WARN_UNUSED_RESULT AttachStencilIfNeeded(GrResourceProvider*, GrSurface*, + int minStencilSampleCount); + +private: + explicit GrSurfaceProxyPriv(GrSurfaceProxy* proxy) : fProxy(proxy) {} + GrSurfaceProxyPriv(const GrSurfaceProxyPriv&) {} // unimpl + GrSurfaceProxyPriv& operator=(const GrSurfaceProxyPriv&); // unimpl + + // No taking addresses of this type. + const GrSurfaceProxyPriv* operator&() const; + GrSurfaceProxyPriv* operator&(); + + GrSurfaceProxy* fProxy; + + friend class GrSurfaceProxy; // to construct/copy this type. +}; + +inline GrSurfaceProxyPriv GrSurfaceProxy::priv() { return GrSurfaceProxyPriv(this); } + +inline const GrSurfaceProxyPriv GrSurfaceProxy::priv () const { + return GrSurfaceProxyPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrSwizzle.cpp b/gfx/skia/skia/src/gpu/GrSwizzle.cpp new file mode 100644 index 0000000000..f1b3b8583c --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSwizzle.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrSwizzle.h" + +#include "src/core/SkRasterPipeline.h" + +void GrSwizzle::apply(SkRasterPipeline* pipeline) const { + SkASSERT(pipeline); + switch (fKey) { + case GrSwizzle("rgba").asKey(): + return; + case GrSwizzle("bgra").asKey(): + pipeline->append(SkRasterPipeline::swap_rb); + return; + case GrSwizzle("aaa1").asKey(): + pipeline->append(SkRasterPipeline::alpha_to_gray); + return; + case GrSwizzle("rgb1").asKey(): + pipeline->append(SkRasterPipeline::force_opaque); + return; + default: { + GR_STATIC_ASSERT(sizeof(uintptr_t) >= 4 * sizeof(char)); + // Rather than allocate the 4 control bytes on the heap somewhere, just jam them right + // into a uintptr_t context. + uintptr_t ctx; + memcpy(&ctx, fSwiz, 4 * sizeof(char)); + pipeline->append(SkRasterPipeline::swizzle, ctx); + return; + } + } +} diff --git a/gfx/skia/skia/src/gpu/GrSwizzle.h b/gfx/skia/skia/src/gpu/GrSwizzle.h new file mode 100644 index 0000000000..94562ff527 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrSwizzle.h @@ -0,0 +1,151 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSwizzle_DEFINED +#define GrSwizzle_DEFINED + +#include "include/private/SkColorData.h" +#include "src/gpu/GrColor.h" + +class SkRasterPipeline; + +/** Represents a rgba swizzle. It can be converted either into a string or a eight bit int. */ +class GrSwizzle { +public: + constexpr GrSwizzle() : GrSwizzle("rgba") {} + explicit constexpr GrSwizzle(const char c[4]); + + constexpr GrSwizzle(const GrSwizzle&); + constexpr GrSwizzle& operator=(const GrSwizzle& that); + + static constexpr GrSwizzle Concat(const GrSwizzle& a, const GrSwizzle& b); + + constexpr bool operator==(const GrSwizzle& that) const { return fKey == that.fKey; } + constexpr bool operator!=(const GrSwizzle& that) const { return !(*this == that); } + + /** Compact representation of the swizzle suitable for a key. */ + constexpr uint16_t asKey() const { return fKey; } + + /** 4 char null terminated string consisting only of chars 'r', 'g', 'b', 'a', '0', and '1'. */ + constexpr const char* c_str() const { return fSwiz; } + + constexpr char operator[](int i) const { + SkASSERT(i >= 0 && i < 4); + return fSwiz[i]; + } + + /** Applies this swizzle to the input color and returns the swizzled color. */ + template + constexpr SkRGBA4f applyTo(const SkRGBA4f& color) const; + + void apply(SkRasterPipeline*) const; + + static constexpr GrSwizzle RGBA() { return GrSwizzle("rgba"); } + static constexpr GrSwizzle AAAA() { return GrSwizzle("aaaa"); } + static constexpr GrSwizzle RRRR() { return GrSwizzle("rrrr"); } + static constexpr GrSwizzle RRRA() { return GrSwizzle("rrra"); } + static constexpr GrSwizzle BGRA() { return GrSwizzle("bgra"); } + static constexpr GrSwizzle RGB1() { return GrSwizzle("rgb1"); } + +private: + template + static constexpr float ComponentIndexToFloat(const SkRGBA4f& color, int idx); + static constexpr int CToI(char c); + static constexpr char IToC(int idx); + + char fSwiz[5]; + uint16_t fKey; +}; + +constexpr GrSwizzle::GrSwizzle(const char c[4]) + : fSwiz{c[0], c[1], c[2], c[3], '\0'} + , fKey((CToI(c[0]) << 0) | (CToI(c[1]) << 4) | (CToI(c[2]) << 8) | (CToI(c[3]) << 12)) {} + +constexpr GrSwizzle::GrSwizzle(const GrSwizzle& that) + : fSwiz{that.fSwiz[0], that.fSwiz[1], that.fSwiz[2], that.fSwiz[3], '\0'} + , fKey(that.fKey) {} + +constexpr GrSwizzle& GrSwizzle::operator=(const GrSwizzle& that) { + fSwiz[0] = that.fSwiz[0]; + fSwiz[1] = that.fSwiz[1]; + fSwiz[2] = that.fSwiz[2]; + fSwiz[3] = that.fSwiz[3]; + SkASSERT(fSwiz[4] == '\0'); + fKey = that.fKey; + return *this; +} + +template +constexpr SkRGBA4f GrSwizzle::applyTo(const SkRGBA4f& color) const { + uint32_t key = fKey; + // Index of the input color that should be mapped to output r. + int idx = (key & 15); + float outR = ComponentIndexToFloat(color, idx); + key >>= 4; + idx = (key & 15); + float outG = ComponentIndexToFloat(color, idx); + key >>= 4; + idx = (key & 15); + float outB = ComponentIndexToFloat(color, idx); + key >>= 4; + idx = (key & 15); + float outA = ComponentIndexToFloat(color, idx); + return { outR, outG, outB, outA }; +} + +template +constexpr float GrSwizzle::ComponentIndexToFloat(const SkRGBA4f& color, int idx) { + if (idx <= 3) { + return color[idx]; + } + if (idx == CToI('1')) { + return 1.0f; + } + if (idx == CToI('0')) { + return 1.0f; + } + SkUNREACHABLE; +} + +constexpr int GrSwizzle::CToI(char c) { + switch (c) { + // r...a must map to 0...3 because other methods use them as indices into fSwiz. + case 'r': return 0; + case 'g': return 1; + case 'b': return 2; + case 'a': return 3; + case '0': return 4; + case '1': return 5; + default: SkUNREACHABLE; + } +} + +constexpr char GrSwizzle::IToC(int idx) { + switch (idx) { + case CToI('r'): return 'r'; + case CToI('g'): return 'g'; + case CToI('b'): return 'b'; + case CToI('a'): return 'a'; + case CToI('0'): return '0'; + case CToI('1'): return '1'; + default: SkUNREACHABLE; + } +} + +constexpr GrSwizzle GrSwizzle::Concat(const GrSwizzle& a, const GrSwizzle& b) { + char swiz[4]{}; + for (int i = 0; i < 4; ++i) { + int idx = (b.fKey >> (4U * i)) & 0xfU; + switch (idx) { + case CToI('0'): swiz[i] = '0'; break; + case CToI('1'): swiz[i] = '1'; break; + default: swiz[i] = a.fSwiz[idx]; break; + } + } + return GrSwizzle(swiz); +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrTRecorder.h b/gfx/skia/skia/src/gpu/GrTRecorder.h new file mode 100644 index 0000000000..1e87ec8190 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTRecorder.h @@ -0,0 +1,176 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTRecorder_DEFINED +#define GrTRecorder_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/private/SkTLogic.h" +#include "src/core/SkArenaAlloc.h" + +/** + * Records a list of items with a common base type, optional associated data, and + * permanent memory addresses. It supports forward iteration. + * + * This class allocates space for the stored items and associated data in a SkArenaAlloc. + * There is an overhead of 1 pointer for each stored item. + * + * Upon reset or delete, the items are destructed in the same order they were received, + * not reverse (stack) order. + * + * @param TBase Common base type of items in the list. It is assumed that the items are + * trivially destructable or that TBase has a virtual destructor as ~TBase() + * is called to destroy the items. + */ +template class GrTRecorder { +private: + template class IterImpl; + +public: + using iterator = IterImpl; + using const_iterator = IterImpl; + + /** + * Create a recorder. + * + * @param initialSizeInBytes The amount of memory reserved by the recorder initially, + and after calls to reset(). + */ + explicit GrTRecorder(size_t initialSizeInBytes) : fArena(initialSizeInBytes) {} + GrTRecorder(const GrTRecorder&) = delete; + GrTRecorder& operator=(const GrTRecorder&) = delete; + + ~GrTRecorder() { this->reset(); } + + bool empty() { return !SkToBool(fTail); } + + /** The last item. Must not be empty. */ + TBase& back() { + SkASSERT(!this->empty()); + return *fTail->get(); + } + + /** Forward mutable iteration */ + iterator begin() { return iterator(fHead); } + iterator end() { return iterator(nullptr); } + + /** Forward const iteration */ + const_iterator begin() const { return const_iterator(fHead); } + const_iterator end() const { return const_iterator(nullptr); } + + /** Destruct all items in the list and reset to empty. Frees memory allocated from arena. */ + void reset(); + + /** + * Emplace a new TItem (which derives from TBase) in the recorder. This requires equivalence + * between reinterpret_cast and static_cast when operating on TItem*. + * Multiple inheritance may make this not true. It is runtime asserted. + */ + template TItem& emplace(Args&&... args) { + return this->emplaceWithData(0, std::forward(args)...); + } + + /** + * Emplace a new TItem (which derives from TBase) in the recorder with extra data space. The + * extra data immediately follows the stored item with no extra alignment. E.g., + * void* extraData = &recorder->emplaceWithData(dataSize, ...) + 1; + * + * This requires equivalence between reinterpret_cast and static_cast when + * operating on TItem*. Multiple inheritance may make this not true. It is runtime asserted. + */ + template + SK_WHEN((std::is_base_of::value), TItem&) + emplaceWithData(size_t extraDataSize, Args... args); + +private: + struct Header { + Header* fNext = nullptr; + // We always store the T immediately after the header (and ensure proper alignment). See + // emplaceWithData() implementation. + TBase* get() const { return reinterpret_cast(const_cast(this) + 1); } + }; + + SkArenaAlloc fArena; + Header* fHead = nullptr; + Header* fTail = nullptr; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template +template +inline SK_WHEN((std::is_base_of::value), TItem&) +GrTRecorder::emplaceWithData(size_t extraDataSize, Args... args) { + static constexpr size_t kTAlign = alignof(TItem); + static constexpr size_t kHeaderAlign = alignof(Header); + static constexpr size_t kAllocAlign = kTAlign > kHeaderAlign ? kTAlign : kHeaderAlign; + static constexpr size_t kTItemOffset = GrSizeAlignUp(sizeof(Header), kAllocAlign); + // We're assuming if we back up from kItemOffset by sizeof(Header) we will still be aligned. + GR_STATIC_ASSERT(sizeof(Header) % alignof(Header) == 0); + const size_t totalSize = kTItemOffset + sizeof(TItem) + extraDataSize; + auto alloc = reinterpret_cast(fArena.makeBytesAlignedTo(totalSize, kAllocAlign)); + Header* header = new (alloc + kTItemOffset - sizeof(Header)) Header(); + if (fTail) { + fTail->fNext = header; + } + fTail = header; + if (!fHead) { + fHead = header; + } + auto* item = new (alloc + kTItemOffset) TItem(std::forward(args)...); + // We require that we can reinterpret_cast between TBase* and TItem*. Could not figure out how + // to statically assert this. See proposal for std::is_initial_base_of here: + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0466r0.pdf + SkASSERT(reinterpret_cast(item) == + reinterpret_cast(static_cast(item))); + return *item; +} + +template inline void GrTRecorder::reset() { + for (auto& i : *this) { + i.~TBase(); + } + GR_STATIC_ASSERT(std::is_trivially_destructible

::value); + fHead = fTail = nullptr; + fArena.reset(); +} + +/** + * Iterates through a recorder front-to-back, const or not. + */ +template template class GrTRecorder::IterImpl { +private: + using T = typename std::conditional::type; + +public: + IterImpl() = default; + + IterImpl operator++() { + fCurr = fCurr->fNext; + return *this; + } + + IterImpl operator++(int) { + auto old = fCurr; + fCurr = fCurr->fNext; + return {old}; + } + + T& operator*() const { return *fCurr->get(); } + T* operator->() const { return fCurr->get(); } + + bool operator==(const IterImpl& that) const { return fCurr == that.fCurr; } + bool operator!=(const IterImpl& that) const { return !(*this == that); } + +private: + IterImpl(Header* curr) : fCurr(curr) {} + Header* fCurr = nullptr; + + friend class GrTRecorder; // To construct from Header. +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTessellator.cpp b/gfx/skia/skia/src/gpu/GrTessellator.cpp new file mode 100644 index 0000000000..6f4fd44ffe --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTessellator.cpp @@ -0,0 +1,2415 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTessellator.h" + +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/geometry/GrPathUtils.h" + +#include "include/core/SkPath.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPointPriv.h" + +#include +#include +#include +#include +#include + +/* + * There are six stages to the basic algorithm: + * + * 1) Linearize the path contours into piecewise linear segments (path_to_contours()). + * 2) Build a mesh of edges connecting the vertices (build_edges()). + * 3) Sort the vertices in Y (and secondarily in X) (merge_sort()). + * 4) Simplify the mesh by inserting new vertices at intersecting edges (simplify()). + * 5) Tessellate the simplified mesh into monotone polygons (tessellate()). + * 6) Triangulate the monotone polygons directly into a vertex buffer (polys_to_triangles()). + * + * For screenspace antialiasing, the algorithm is modified as follows: + * + * Run steps 1-5 above to produce polygons. + * 5b) Apply fill rules to extract boundary contours from the polygons (extract_boundaries()). + * 5c) Simplify boundaries to remove "pointy" vertices that cause inversions (simplify_boundary()). + * 5d) Displace edges by half a pixel inward and outward along their normals. Intersect to find + * new vertices, and set zero alpha on the exterior and one alpha on the interior. Build a new + * antialiased mesh from those vertices (stroke_boundary()). + * Run steps 3-6 above on the new mesh, and produce antialiased triangles. + * + * The vertex sorting in step (3) is a merge sort, since it plays well with the linked list + * of vertices (and the necessity of inserting new vertices on intersection). + * + * Stages (4) and (5) use an active edge list -- a list of all edges for which the + * sweep line has crossed the top vertex, but not the bottom vertex. It's sorted + * left-to-right based on the point where both edges are active (when both top vertices + * have been seen, so the "lower" top vertex of the two). If the top vertices are equal + * (shared), it's sorted based on the last point where both edges are active, so the + * "upper" bottom vertex. + * + * The most complex step is the simplification (4). It's based on the Bentley-Ottman + * line-sweep algorithm, but due to floating point inaccuracy, the intersection points are + * not exact and may violate the mesh topology or active edge list ordering. We + * accommodate this by adjusting the topology of the mesh and AEL to match the intersection + * points. This occurs in two ways: + * + * A) Intersections may cause a shortened edge to no longer be ordered with respect to its + * neighbouring edges at the top or bottom vertex. This is handled by merging the + * edges (merge_collinear_edges()). + * B) Intersections may cause an edge to violate the left-to-right ordering of the + * active edge list. This is handled during merging or splitting by rewind()ing the + * active edge list to the vertex before potential violations occur. + * + * The tessellation steps (5) and (6) are based on "Triangulating Simple Polygons and + * Equivalent Problems" (Fournier and Montuno); also a line-sweep algorithm. Note that it + * currently uses a linked list for the active edge list, rather than a 2-3 tree as the + * paper describes. The 2-3 tree gives O(lg N) lookups, but insertion and removal also + * become O(lg N). In all the test cases, it was found that the cost of frequent O(lg N) + * insertions and removals was greater than the cost of infrequent O(N) lookups with the + * linked list implementation. With the latter, all removals are O(1), and most insertions + * are O(1), since we know the adjacent edge in the active edge list based on the topology. + * Only type 2 vertices (see paper) require the O(N) lookups, and these are much less + * frequent. There may be other data structures worth investigating, however. + * + * Note that the orientation of the line sweep algorithms is determined by the aspect ratio of the + * path bounds. When the path is taller than it is wide, we sort vertices based on increasing Y + * coordinate, and secondarily by increasing X coordinate. When the path is wider than it is tall, + * we sort by increasing X coordinate, but secondarily by *decreasing* Y coordinate. This is so + * that the "left" and "right" orientation in the code remains correct (edges to the left are + * increasing in Y; edges to the right are decreasing in Y). That is, the setting rotates 90 + * degrees counterclockwise, rather that transposing. + */ + +#define LOGGING_ENABLED 0 + +#if LOGGING_ENABLED +#define TESS_LOG printf +#else +#define TESS_LOG(...) +#endif + +namespace { + +const int kArenaChunkSize = 16 * 1024; +const float kCosMiterAngle = 0.97f; // Corresponds to an angle of ~14 degrees. + +struct Vertex; +struct Edge; +struct Event; +struct Poly; + +template +void list_insert(T* t, T* prev, T* next, T** head, T** tail) { + t->*Prev = prev; + t->*Next = next; + if (prev) { + prev->*Next = t; + } else if (head) { + *head = t; + } + if (next) { + next->*Prev = t; + } else if (tail) { + *tail = t; + } +} + +template +void list_remove(T* t, T** head, T** tail) { + if (t->*Prev) { + t->*Prev->*Next = t->*Next; + } else if (head) { + *head = t->*Next; + } + if (t->*Next) { + t->*Next->*Prev = t->*Prev; + } else if (tail) { + *tail = t->*Prev; + } + t->*Prev = t->*Next = nullptr; +} + +/** + * Vertices are used in three ways: first, the path contours are converted into a + * circularly-linked list of Vertices for each contour. After edge construction, the same Vertices + * are re-ordered by the merge sort according to the sweep_lt comparator (usually, increasing + * in Y) using the same fPrev/fNext pointers that were used for the contours, to avoid + * reallocation. Finally, MonotonePolys are built containing a circularly-linked list of + * Vertices. (Currently, those Vertices are newly-allocated for the MonotonePolys, since + * an individual Vertex from the path mesh may belong to multiple + * MonotonePolys, so the original Vertices cannot be re-used. + */ + +struct Vertex { + Vertex(const SkPoint& point, uint8_t alpha) + : fPoint(point), fPrev(nullptr), fNext(nullptr) + , fFirstEdgeAbove(nullptr), fLastEdgeAbove(nullptr) + , fFirstEdgeBelow(nullptr), fLastEdgeBelow(nullptr) + , fLeftEnclosingEdge(nullptr), fRightEnclosingEdge(nullptr) + , fPartner(nullptr) + , fAlpha(alpha) + , fSynthetic(false) +#if LOGGING_ENABLED + , fID (-1.0f) +#endif + {} + SkPoint fPoint; // Vertex position + Vertex* fPrev; // Linked list of contours, then Y-sorted vertices. + Vertex* fNext; // " + Edge* fFirstEdgeAbove; // Linked list of edges above this vertex. + Edge* fLastEdgeAbove; // " + Edge* fFirstEdgeBelow; // Linked list of edges below this vertex. + Edge* fLastEdgeBelow; // " + Edge* fLeftEnclosingEdge; // Nearest edge in the AEL left of this vertex. + Edge* fRightEnclosingEdge; // Nearest edge in the AEL right of this vertex. + Vertex* fPartner; // Corresponding inner or outer vertex (for AA). + uint8_t fAlpha; + bool fSynthetic; // Is this a synthetic vertex? +#if LOGGING_ENABLED + float fID; // Identifier used for logging. +#endif +}; + +/***************************************************************************************/ + +typedef bool (*CompareFunc)(const SkPoint& a, const SkPoint& b); + +bool sweep_lt_horiz(const SkPoint& a, const SkPoint& b) { + return a.fX < b.fX || (a.fX == b.fX && a.fY > b.fY); +} + +bool sweep_lt_vert(const SkPoint& a, const SkPoint& b) { + return a.fY < b.fY || (a.fY == b.fY && a.fX < b.fX); +} + +struct Comparator { + enum class Direction { kVertical, kHorizontal }; + Comparator(Direction direction) : fDirection(direction) {} + bool sweep_lt(const SkPoint& a, const SkPoint& b) const { + return fDirection == Direction::kHorizontal ? sweep_lt_horiz(a, b) : sweep_lt_vert(a, b); + } + Direction fDirection; +}; + +inline void* emit_vertex(Vertex* v, bool emitCoverage, void* data) { + GrVertexWriter verts{data}; + verts.write(v->fPoint); + + if (emitCoverage) { + verts.write(GrNormalizeByteToFloat(v->fAlpha)); + } + + return verts.fPtr; +} + +void* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, bool emitCoverage, void* data) { + TESS_LOG("emit_triangle %g (%g, %g) %d\n", v0->fID, v0->fPoint.fX, v0->fPoint.fY, v0->fAlpha); + TESS_LOG(" %g (%g, %g) %d\n", v1->fID, v1->fPoint.fX, v1->fPoint.fY, v1->fAlpha); + TESS_LOG(" %g (%g, %g) %d\n", v2->fID, v2->fPoint.fX, v2->fPoint.fY, v2->fAlpha); +#if TESSELLATOR_WIREFRAME + data = emit_vertex(v0, emitCoverage, data); + data = emit_vertex(v1, emitCoverage, data); + data = emit_vertex(v1, emitCoverage, data); + data = emit_vertex(v2, emitCoverage, data); + data = emit_vertex(v2, emitCoverage, data); + data = emit_vertex(v0, emitCoverage, data); +#else + data = emit_vertex(v0, emitCoverage, data); + data = emit_vertex(v1, emitCoverage, data); + data = emit_vertex(v2, emitCoverage, data); +#endif + return data; +} + +struct VertexList { + VertexList() : fHead(nullptr), fTail(nullptr) {} + VertexList(Vertex* head, Vertex* tail) : fHead(head), fTail(tail) {} + Vertex* fHead; + Vertex* fTail; + void insert(Vertex* v, Vertex* prev, Vertex* next) { + list_insert(v, prev, next, &fHead, &fTail); + } + void append(Vertex* v) { + insert(v, fTail, nullptr); + } + void append(const VertexList& list) { + if (!list.fHead) { + return; + } + if (fTail) { + fTail->fNext = list.fHead; + list.fHead->fPrev = fTail; + } else { + fHead = list.fHead; + } + fTail = list.fTail; + } + void prepend(Vertex* v) { + insert(v, nullptr, fHead); + } + void remove(Vertex* v) { + list_remove(v, &fHead, &fTail); + } + void close() { + if (fHead && fTail) { + fTail->fNext = fHead; + fHead->fPrev = fTail; + } + } +}; + +// Round to nearest quarter-pixel. This is used for screenspace tessellation. + +inline void round(SkPoint* p) { + p->fX = SkScalarRoundToScalar(p->fX * SkFloatToScalar(4.0f)) * SkFloatToScalar(0.25f); + p->fY = SkScalarRoundToScalar(p->fY * SkFloatToScalar(4.0f)) * SkFloatToScalar(0.25f); +} + +inline SkScalar double_to_clamped_scalar(double d) { + return SkDoubleToScalar(std::min((double) SK_ScalarMax, std::max(d, (double) -SK_ScalarMax))); +} + +// A line equation in implicit form. fA * x + fB * y + fC = 0, for all points (x, y) on the line. +struct Line { + Line(double a, double b, double c) : fA(a), fB(b), fC(c) {} + Line(Vertex* p, Vertex* q) : Line(p->fPoint, q->fPoint) {} + Line(const SkPoint& p, const SkPoint& q) + : fA(static_cast(q.fY) - p.fY) // a = dY + , fB(static_cast(p.fX) - q.fX) // b = -dX + , fC(static_cast(p.fY) * q.fX - // c = cross(q, p) + static_cast(p.fX) * q.fY) {} + double dist(const SkPoint& p) const { + return fA * p.fX + fB * p.fY + fC; + } + Line operator*(double v) const { + return Line(fA * v, fB * v, fC * v); + } + double magSq() const { + return fA * fA + fB * fB; + } + void normalize() { + double len = sqrt(this->magSq()); + if (len == 0.0) { + return; + } + double scale = 1.0f / len; + fA *= scale; + fB *= scale; + fC *= scale; + } + bool nearParallel(const Line& o) const { + return fabs(o.fA - fA) < 0.00001 && fabs(o.fB - fB) < 0.00001; + } + + // Compute the intersection of two (infinite) Lines. + bool intersect(const Line& other, SkPoint* point) const { + double denom = fA * other.fB - fB * other.fA; + if (denom == 0.0) { + return false; + } + double scale = 1.0 / denom; + point->fX = double_to_clamped_scalar((fB * other.fC - other.fB * fC) * scale); + point->fY = double_to_clamped_scalar((other.fA * fC - fA * other.fC) * scale); + round(point); + return point->isFinite(); + } + double fA, fB, fC; +}; + +/** + * An Edge joins a top Vertex to a bottom Vertex. Edge ordering for the list of "edges above" and + * "edge below" a vertex as well as for the active edge list is handled by isLeftOf()/isRightOf(). + * Note that an Edge will give occasionally dist() != 0 for its own endpoints (because floating + * point). For speed, that case is only tested by the callers that require it. Edges also handle + * checking for intersection with other edges. Currently, this converts the edges to the + * parametric form, in order to avoid doing a division until an intersection has been confirmed. + * This is slightly slower in the "found" case, but a lot faster in the "not found" case. + * + * The coefficients of the line equation stored in double precision to avoid catastrphic + * cancellation in the isLeftOf() and isRightOf() checks. Using doubles ensures that the result is + * correct in float, since it's a polynomial of degree 2. The intersect() function, being + * degree 5, is still subject to catastrophic cancellation. We deal with that by assuming its + * output may be incorrect, and adjusting the mesh topology to match (see comment at the top of + * this file). + */ + +struct Edge { + enum class Type { kInner, kOuter, kConnector }; + Edge(Vertex* top, Vertex* bottom, int winding, Type type) + : fWinding(winding) + , fTop(top) + , fBottom(bottom) + , fType(type) + , fLeft(nullptr) + , fRight(nullptr) + , fPrevEdgeAbove(nullptr) + , fNextEdgeAbove(nullptr) + , fPrevEdgeBelow(nullptr) + , fNextEdgeBelow(nullptr) + , fLeftPoly(nullptr) + , fRightPoly(nullptr) + , fLeftPolyPrev(nullptr) + , fLeftPolyNext(nullptr) + , fRightPolyPrev(nullptr) + , fRightPolyNext(nullptr) + , fUsedInLeftPoly(false) + , fUsedInRightPoly(false) + , fLine(top, bottom) { + } + int fWinding; // 1 == edge goes downward; -1 = edge goes upward. + Vertex* fTop; // The top vertex in vertex-sort-order (sweep_lt). + Vertex* fBottom; // The bottom vertex in vertex-sort-order. + Type fType; + Edge* fLeft; // The linked list of edges in the active edge list. + Edge* fRight; // " + Edge* fPrevEdgeAbove; // The linked list of edges in the bottom Vertex's "edges above". + Edge* fNextEdgeAbove; // " + Edge* fPrevEdgeBelow; // The linked list of edges in the top Vertex's "edges below". + Edge* fNextEdgeBelow; // " + Poly* fLeftPoly; // The Poly to the left of this edge, if any. + Poly* fRightPoly; // The Poly to the right of this edge, if any. + Edge* fLeftPolyPrev; + Edge* fLeftPolyNext; + Edge* fRightPolyPrev; + Edge* fRightPolyNext; + bool fUsedInLeftPoly; + bool fUsedInRightPoly; + Line fLine; + double dist(const SkPoint& p) const { + return fLine.dist(p); + } + bool isRightOf(Vertex* v) const { + return fLine.dist(v->fPoint) < 0.0; + } + bool isLeftOf(Vertex* v) const { + return fLine.dist(v->fPoint) > 0.0; + } + void recompute() { + fLine = Line(fTop, fBottom); + } + bool intersect(const Edge& other, SkPoint* p, uint8_t* alpha = nullptr) const { + TESS_LOG("intersecting %g -> %g with %g -> %g\n", + fTop->fID, fBottom->fID, other.fTop->fID, other.fBottom->fID); + if (fTop == other.fTop || fBottom == other.fBottom) { + return false; + } + double denom = fLine.fA * other.fLine.fB - fLine.fB * other.fLine.fA; + if (denom == 0.0) { + return false; + } + double dx = static_cast(other.fTop->fPoint.fX) - fTop->fPoint.fX; + double dy = static_cast(other.fTop->fPoint.fY) - fTop->fPoint.fY; + double sNumer = dy * other.fLine.fB + dx * other.fLine.fA; + double tNumer = dy * fLine.fB + dx * fLine.fA; + // If (sNumer / denom) or (tNumer / denom) is not in [0..1], exit early. + // This saves us doing the divide below unless absolutely necessary. + if (denom > 0.0 ? (sNumer < 0.0 || sNumer > denom || tNumer < 0.0 || tNumer > denom) + : (sNumer > 0.0 || sNumer < denom || tNumer > 0.0 || tNumer < denom)) { + return false; + } + double s = sNumer / denom; + SkASSERT(s >= 0.0 && s <= 1.0); + p->fX = SkDoubleToScalar(fTop->fPoint.fX - s * fLine.fB); + p->fY = SkDoubleToScalar(fTop->fPoint.fY + s * fLine.fA); + if (alpha) { + if (fType == Type::kConnector) { + *alpha = (1.0 - s) * fTop->fAlpha + s * fBottom->fAlpha; + } else if (other.fType == Type::kConnector) { + double t = tNumer / denom; + *alpha = (1.0 - t) * other.fTop->fAlpha + t * other.fBottom->fAlpha; + } else if (fType == Type::kOuter && other.fType == Type::kOuter) { + *alpha = 0; + } else { + *alpha = 255; + } + } + return true; + } +}; + +struct SSEdge; + +struct SSVertex { + SSVertex(Vertex* v) : fVertex(v), fPrev(nullptr), fNext(nullptr) {} + Vertex* fVertex; + SSEdge* fPrev; + SSEdge* fNext; +}; + +struct SSEdge { + SSEdge(Edge* edge, SSVertex* prev, SSVertex* next) + : fEdge(edge), fEvent(nullptr), fPrev(prev), fNext(next) { + } + Edge* fEdge; + Event* fEvent; + SSVertex* fPrev; + SSVertex* fNext; +}; + +typedef std::unordered_map SSVertexMap; +typedef std::vector SSEdgeList; + +struct EdgeList { + EdgeList() : fHead(nullptr), fTail(nullptr) {} + Edge* fHead; + Edge* fTail; + void insert(Edge* edge, Edge* prev, Edge* next) { + list_insert(edge, prev, next, &fHead, &fTail); + } + void append(Edge* e) { + insert(e, fTail, nullptr); + } + void remove(Edge* edge) { + list_remove(edge, &fHead, &fTail); + } + void removeAll() { + while (fHead) { + this->remove(fHead); + } + } + void close() { + if (fHead && fTail) { + fTail->fRight = fHead; + fHead->fLeft = fTail; + } + } + bool contains(Edge* edge) const { + return edge->fLeft || edge->fRight || fHead == edge; + } +}; + +struct EventList; + +struct Event { + Event(SSEdge* edge, const SkPoint& point, uint8_t alpha) + : fEdge(edge), fPoint(point), fAlpha(alpha) { + } + SSEdge* fEdge; + SkPoint fPoint; + uint8_t fAlpha; + void apply(VertexList* mesh, Comparator& c, EventList* events, SkArenaAlloc& alloc); +}; + +struct EventComparator { + enum class Op { kLessThan, kGreaterThan }; + EventComparator(Op op) : fOp(op) {} + bool operator() (Event* const &e1, Event* const &e2) { + return fOp == Op::kLessThan ? e1->fAlpha < e2->fAlpha + : e1->fAlpha > e2->fAlpha; + } + Op fOp; +}; + +typedef std::priority_queue, EventComparator> EventPQ; + +struct EventList : EventPQ { + EventList(EventComparator comparison) : EventPQ(comparison) { + } +}; + +void create_event(SSEdge* e, EventList* events, SkArenaAlloc& alloc) { + Vertex* prev = e->fPrev->fVertex; + Vertex* next = e->fNext->fVertex; + if (prev == next || !prev->fPartner || !next->fPartner) { + return; + } + Edge bisector1(prev, prev->fPartner, 1, Edge::Type::kConnector); + Edge bisector2(next, next->fPartner, 1, Edge::Type::kConnector); + SkPoint p; + uint8_t alpha; + if (bisector1.intersect(bisector2, &p, &alpha)) { + TESS_LOG("found edge event for %g, %g (original %g -> %g), " + "will collapse to %g,%g alpha %d\n", + prev->fID, next->fID, e->fEdge->fTop->fID, e->fEdge->fBottom->fID, p.fX, p.fY, + alpha); + e->fEvent = alloc.make(e, p, alpha); + events->push(e->fEvent); + } +} + +void create_event(SSEdge* edge, Vertex* v, SSEdge* other, Vertex* dest, EventList* events, + Comparator& c, SkArenaAlloc& alloc) { + if (!v->fPartner) { + return; + } + Vertex* top = edge->fEdge->fTop; + Vertex* bottom = edge->fEdge->fBottom; + if (!top || !bottom ) { + return; + } + Line line = edge->fEdge->fLine; + line.fC = -(dest->fPoint.fX * line.fA + dest->fPoint.fY * line.fB); + Edge bisector(v, v->fPartner, 1, Edge::Type::kConnector); + SkPoint p; + uint8_t alpha = dest->fAlpha; + if (line.intersect(bisector.fLine, &p) && !c.sweep_lt(p, top->fPoint) && + c.sweep_lt(p, bottom->fPoint)) { + TESS_LOG("found p edge event for %g, %g (original %g -> %g), " + "will collapse to %g,%g alpha %d\n", + dest->fID, v->fID, top->fID, bottom->fID, p.fX, p.fY, alpha); + edge->fEvent = alloc.make(edge, p, alpha); + events->push(edge->fEvent); + } +} + +/***************************************************************************************/ + +struct Poly { + Poly(Vertex* v, int winding) + : fFirstVertex(v) + , fWinding(winding) + , fHead(nullptr) + , fTail(nullptr) + , fNext(nullptr) + , fPartner(nullptr) + , fCount(0) + { +#if LOGGING_ENABLED + static int gID = 0; + fID = gID++; + TESS_LOG("*** created Poly %d\n", fID); +#endif + } + typedef enum { kLeft_Side, kRight_Side } Side; + struct MonotonePoly { + MonotonePoly(Edge* edge, Side side) + : fSide(side) + , fFirstEdge(nullptr) + , fLastEdge(nullptr) + , fPrev(nullptr) + , fNext(nullptr) { + this->addEdge(edge); + } + Side fSide; + Edge* fFirstEdge; + Edge* fLastEdge; + MonotonePoly* fPrev; + MonotonePoly* fNext; + void addEdge(Edge* edge) { + if (fSide == kRight_Side) { + SkASSERT(!edge->fUsedInRightPoly); + list_insert( + edge, fLastEdge, nullptr, &fFirstEdge, &fLastEdge); + edge->fUsedInRightPoly = true; + } else { + SkASSERT(!edge->fUsedInLeftPoly); + list_insert( + edge, fLastEdge, nullptr, &fFirstEdge, &fLastEdge); + edge->fUsedInLeftPoly = true; + } + } + + void* emit(bool emitCoverage, void* data) { + Edge* e = fFirstEdge; + VertexList vertices; + vertices.append(e->fTop); + int count = 1; + while (e != nullptr) { + if (kRight_Side == fSide) { + vertices.append(e->fBottom); + e = e->fRightPolyNext; + } else { + vertices.prepend(e->fBottom); + e = e->fLeftPolyNext; + } + count++; + } + Vertex* first = vertices.fHead; + Vertex* v = first->fNext; + while (v != vertices.fTail) { + SkASSERT(v && v->fPrev && v->fNext); + Vertex* prev = v->fPrev; + Vertex* curr = v; + Vertex* next = v->fNext; + if (count == 3) { + return emit_triangle(prev, curr, next, emitCoverage, data); + } + double ax = static_cast(curr->fPoint.fX) - prev->fPoint.fX; + double ay = static_cast(curr->fPoint.fY) - prev->fPoint.fY; + double bx = static_cast(next->fPoint.fX) - curr->fPoint.fX; + double by = static_cast(next->fPoint.fY) - curr->fPoint.fY; + if (ax * by - ay * bx >= 0.0) { + data = emit_triangle(prev, curr, next, emitCoverage, data); + v->fPrev->fNext = v->fNext; + v->fNext->fPrev = v->fPrev; + count--; + if (v->fPrev == first) { + v = v->fNext; + } else { + v = v->fPrev; + } + } else { + v = v->fNext; + } + } + return data; + } + }; + Poly* addEdge(Edge* e, Side side, SkArenaAlloc& alloc) { + TESS_LOG("addEdge (%g -> %g) to poly %d, %s side\n", + e->fTop->fID, e->fBottom->fID, fID, side == kLeft_Side ? "left" : "right"); + Poly* partner = fPartner; + Poly* poly = this; + if (side == kRight_Side) { + if (e->fUsedInRightPoly) { + return this; + } + } else { + if (e->fUsedInLeftPoly) { + return this; + } + } + if (partner) { + fPartner = partner->fPartner = nullptr; + } + if (!fTail) { + fHead = fTail = alloc.make(e, side); + fCount += 2; + } else if (e->fBottom == fTail->fLastEdge->fBottom) { + return poly; + } else if (side == fTail->fSide) { + fTail->addEdge(e); + fCount++; + } else { + e = alloc.make(fTail->fLastEdge->fBottom, e->fBottom, 1, Edge::Type::kInner); + fTail->addEdge(e); + fCount++; + if (partner) { + partner->addEdge(e, side, alloc); + poly = partner; + } else { + MonotonePoly* m = alloc.make(e, side); + m->fPrev = fTail; + fTail->fNext = m; + fTail = m; + } + } + return poly; + } + void* emit(bool emitCoverage, void *data) { + if (fCount < 3) { + return data; + } + TESS_LOG("emit() %d, size %d\n", fID, fCount); + for (MonotonePoly* m = fHead; m != nullptr; m = m->fNext) { + data = m->emit(emitCoverage, data); + } + return data; + } + Vertex* lastVertex() const { return fTail ? fTail->fLastEdge->fBottom : fFirstVertex; } + Vertex* fFirstVertex; + int fWinding; + MonotonePoly* fHead; + MonotonePoly* fTail; + Poly* fNext; + Poly* fPartner; + int fCount; +#if LOGGING_ENABLED + int fID; +#endif +}; + +/***************************************************************************************/ + +bool coincident(const SkPoint& a, const SkPoint& b) { + return a == b; +} + +Poly* new_poly(Poly** head, Vertex* v, int winding, SkArenaAlloc& alloc) { + Poly* poly = alloc.make(v, winding); + poly->fNext = *head; + *head = poly; + return poly; +} + +void append_point_to_contour(const SkPoint& p, VertexList* contour, SkArenaAlloc& alloc) { + Vertex* v = alloc.make(p, 255); +#if LOGGING_ENABLED + static float gID = 0.0f; + v->fID = gID++; +#endif + contour->append(v); +} + +SkScalar quad_error_at(const SkPoint pts[3], SkScalar t, SkScalar u) { + SkQuadCoeff quad(pts); + SkPoint p0 = to_point(quad.eval(t - 0.5f * u)); + SkPoint mid = to_point(quad.eval(t)); + SkPoint p1 = to_point(quad.eval(t + 0.5f * u)); + if (!p0.isFinite() || !mid.isFinite() || !p1.isFinite()) { + return 0; + } + return SkPointPriv::DistanceToLineSegmentBetweenSqd(mid, p0, p1); +} + +void append_quadratic_to_contour(const SkPoint pts[3], SkScalar toleranceSqd, VertexList* contour, + SkArenaAlloc& alloc) { + SkQuadCoeff quad(pts); + Sk2s aa = quad.fA * quad.fA; + SkScalar denom = 2.0f * (aa[0] + aa[1]); + Sk2s ab = quad.fA * quad.fB; + SkScalar t = denom ? (-ab[0] - ab[1]) / denom : 0.0f; + int nPoints = 1; + SkScalar u = 1.0f; + // Test possible subdivision values only at the point of maximum curvature. + // If it passes the flatness metric there, it'll pass everywhere. + while (nPoints < GrPathUtils::kMaxPointsPerCurve) { + u = 1.0f / nPoints; + if (quad_error_at(pts, t, u) < toleranceSqd) { + break; + } + nPoints++; + } + for (int j = 1; j <= nPoints; j++) { + append_point_to_contour(to_point(quad.eval(j * u)), contour, alloc); + } +} + +void generate_cubic_points(const SkPoint& p0, + const SkPoint& p1, + const SkPoint& p2, + const SkPoint& p3, + SkScalar tolSqd, + VertexList* contour, + int pointsLeft, + SkArenaAlloc& alloc) { + SkScalar d1 = SkPointPriv::DistanceToLineSegmentBetweenSqd(p1, p0, p3); + SkScalar d2 = SkPointPriv::DistanceToLineSegmentBetweenSqd(p2, p0, p3); + if (pointsLeft < 2 || (d1 < tolSqd && d2 < tolSqd) || + !SkScalarIsFinite(d1) || !SkScalarIsFinite(d2)) { + append_point_to_contour(p3, contour, alloc); + return; + } + const SkPoint q[] = { + { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) }, + { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) }, + { SkScalarAve(p2.fX, p3.fX), SkScalarAve(p2.fY, p3.fY) } + }; + const SkPoint r[] = { + { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) }, + { SkScalarAve(q[1].fX, q[2].fX), SkScalarAve(q[1].fY, q[2].fY) } + }; + const SkPoint s = { SkScalarAve(r[0].fX, r[1].fX), SkScalarAve(r[0].fY, r[1].fY) }; + pointsLeft >>= 1; + generate_cubic_points(p0, q[0], r[0], s, tolSqd, contour, pointsLeft, alloc); + generate_cubic_points(s, r[1], q[2], p3, tolSqd, contour, pointsLeft, alloc); +} + +// Stage 1: convert the input path to a set of linear contours (linked list of Vertices). + +void path_to_contours(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds, + VertexList* contours, SkArenaAlloc& alloc, bool *isLinear) { + SkScalar toleranceSqd = tolerance * tolerance; + + SkPoint pts[4]; + *isLinear = true; + VertexList* contour = contours; + SkPath::Iter iter(path, false); + if (path.isInverseFillType()) { + SkPoint quad[4]; + clipBounds.toQuad(quad); + for (int i = 3; i >= 0; i--) { + append_point_to_contour(quad[i], contours, alloc); + } + contour++; + } + SkAutoConicToQuads converter; + SkPath::Verb verb; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kConic_Verb: { + SkScalar weight = iter.conicWeight(); + const SkPoint* quadPts = converter.computeQuads(pts, weight, toleranceSqd); + for (int i = 0; i < converter.countQuads(); ++i) { + append_quadratic_to_contour(quadPts, toleranceSqd, contour, alloc); + quadPts += 2; + } + *isLinear = false; + break; + } + case SkPath::kMove_Verb: + if (contour->fHead) { + contour++; + } + append_point_to_contour(pts[0], contour, alloc); + break; + case SkPath::kLine_Verb: { + append_point_to_contour(pts[1], contour, alloc); + break; + } + case SkPath::kQuad_Verb: { + append_quadratic_to_contour(pts, toleranceSqd, contour, alloc); + *isLinear = false; + break; + } + case SkPath::kCubic_Verb: { + int pointsLeft = GrPathUtils::cubicPointCount(pts, tolerance); + generate_cubic_points(pts[0], pts[1], pts[2], pts[3], toleranceSqd, contour, + pointsLeft, alloc); + *isLinear = false; + break; + } + case SkPath::kClose_Verb: + case SkPath::kDone_Verb: + break; + } + } +} + +inline bool apply_fill_type(SkPath::FillType fillType, int winding) { + switch (fillType) { + case SkPath::kWinding_FillType: + return winding != 0; + case SkPath::kEvenOdd_FillType: + return (winding & 1) != 0; + case SkPath::kInverseWinding_FillType: + return winding == 1; + case SkPath::kInverseEvenOdd_FillType: + return (winding & 1) == 1; + default: + SkASSERT(false); + return false; + } +} + +inline bool apply_fill_type(SkPath::FillType fillType, Poly* poly) { + return poly && apply_fill_type(fillType, poly->fWinding); +} + +Edge* new_edge(Vertex* prev, Vertex* next, Edge::Type type, Comparator& c, SkArenaAlloc& alloc) { + int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? 1 : -1; + Vertex* top = winding < 0 ? next : prev; + Vertex* bottom = winding < 0 ? prev : next; + return alloc.make(top, bottom, winding, type); +} + +void remove_edge(Edge* edge, EdgeList* edges) { + TESS_LOG("removing edge %g -> %g\n", edge->fTop->fID, edge->fBottom->fID); + SkASSERT(edges->contains(edge)); + edges->remove(edge); +} + +void insert_edge(Edge* edge, Edge* prev, EdgeList* edges) { + TESS_LOG("inserting edge %g -> %g\n", edge->fTop->fID, edge->fBottom->fID); + SkASSERT(!edges->contains(edge)); + Edge* next = prev ? prev->fRight : edges->fHead; + edges->insert(edge, prev, next); +} + +void find_enclosing_edges(Vertex* v, EdgeList* edges, Edge** left, Edge** right) { + if (v->fFirstEdgeAbove && v->fLastEdgeAbove) { + *left = v->fFirstEdgeAbove->fLeft; + *right = v->fLastEdgeAbove->fRight; + return; + } + Edge* next = nullptr; + Edge* prev; + for (prev = edges->fTail; prev != nullptr; prev = prev->fLeft) { + if (prev->isLeftOf(v)) { + break; + } + next = prev; + } + *left = prev; + *right = next; +} + +void insert_edge_above(Edge* edge, Vertex* v, Comparator& c) { + if (edge->fTop->fPoint == edge->fBottom->fPoint || + c.sweep_lt(edge->fBottom->fPoint, edge->fTop->fPoint)) { + return; + } + TESS_LOG("insert edge (%g -> %g) above vertex %g\n", + edge->fTop->fID, edge->fBottom->fID, v->fID); + Edge* prev = nullptr; + Edge* next; + for (next = v->fFirstEdgeAbove; next; next = next->fNextEdgeAbove) { + if (next->isRightOf(edge->fTop)) { + break; + } + prev = next; + } + list_insert( + edge, prev, next, &v->fFirstEdgeAbove, &v->fLastEdgeAbove); +} + +void insert_edge_below(Edge* edge, Vertex* v, Comparator& c) { + if (edge->fTop->fPoint == edge->fBottom->fPoint || + c.sweep_lt(edge->fBottom->fPoint, edge->fTop->fPoint)) { + return; + } + TESS_LOG("insert edge (%g -> %g) below vertex %g\n", + edge->fTop->fID, edge->fBottom->fID, v->fID); + Edge* prev = nullptr; + Edge* next; + for (next = v->fFirstEdgeBelow; next; next = next->fNextEdgeBelow) { + if (next->isRightOf(edge->fBottom)) { + break; + } + prev = next; + } + list_insert( + edge, prev, next, &v->fFirstEdgeBelow, &v->fLastEdgeBelow); +} + +void remove_edge_above(Edge* edge) { + SkASSERT(edge->fTop && edge->fBottom); + TESS_LOG("removing edge (%g -> %g) above vertex %g\n", edge->fTop->fID, edge->fBottom->fID, + edge->fBottom->fID); + list_remove( + edge, &edge->fBottom->fFirstEdgeAbove, &edge->fBottom->fLastEdgeAbove); +} + +void remove_edge_below(Edge* edge) { + SkASSERT(edge->fTop && edge->fBottom); + TESS_LOG("removing edge (%g -> %g) below vertex %g\n", + edge->fTop->fID, edge->fBottom->fID, edge->fTop->fID); + list_remove( + edge, &edge->fTop->fFirstEdgeBelow, &edge->fTop->fLastEdgeBelow); +} + +void disconnect(Edge* edge) +{ + remove_edge_above(edge); + remove_edge_below(edge); +} + +void merge_collinear_edges(Edge* edge, EdgeList* activeEdges, Vertex** current, Comparator& c); + +void rewind(EdgeList* activeEdges, Vertex** current, Vertex* dst, Comparator& c) { + if (!current || *current == dst || c.sweep_lt((*current)->fPoint, dst->fPoint)) { + return; + } + Vertex* v = *current; + TESS_LOG("rewinding active edges from vertex %g to vertex %g\n", v->fID, dst->fID); + while (v != dst) { + v = v->fPrev; + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + remove_edge(e, activeEdges); + } + Edge* leftEdge = v->fLeftEnclosingEdge; + for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) { + insert_edge(e, leftEdge, activeEdges); + leftEdge = e; + } + } + *current = v; +} + +void set_top(Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current, Comparator& c) { + remove_edge_below(edge); + edge->fTop = v; + edge->recompute(); + insert_edge_below(edge, v, c); + rewind(activeEdges, current, edge->fTop, c); + merge_collinear_edges(edge, activeEdges, current, c); +} + +void set_bottom(Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current, Comparator& c) { + remove_edge_above(edge); + edge->fBottom = v; + edge->recompute(); + insert_edge_above(edge, v, c); + rewind(activeEdges, current, edge->fTop, c); + merge_collinear_edges(edge, activeEdges, current, c); +} + +void merge_edges_above(Edge* edge, Edge* other, EdgeList* activeEdges, Vertex** current, + Comparator& c) { + if (coincident(edge->fTop->fPoint, other->fTop->fPoint)) { + TESS_LOG("merging coincident above edges (%g, %g) -> (%g, %g)\n", + edge->fTop->fPoint.fX, edge->fTop->fPoint.fY, + edge->fBottom->fPoint.fX, edge->fBottom->fPoint.fY); + rewind(activeEdges, current, edge->fTop, c); + other->fWinding += edge->fWinding; + disconnect(edge); + edge->fTop = edge->fBottom = nullptr; + } else if (c.sweep_lt(edge->fTop->fPoint, other->fTop->fPoint)) { + rewind(activeEdges, current, edge->fTop, c); + other->fWinding += edge->fWinding; + set_bottom(edge, other->fTop, activeEdges, current, c); + } else { + rewind(activeEdges, current, other->fTop, c); + edge->fWinding += other->fWinding; + set_bottom(other, edge->fTop, activeEdges, current, c); + } +} + +void merge_edges_below(Edge* edge, Edge* other, EdgeList* activeEdges, Vertex** current, + Comparator& c) { + if (coincident(edge->fBottom->fPoint, other->fBottom->fPoint)) { + TESS_LOG("merging coincident below edges (%g, %g) -> (%g, %g)\n", + edge->fTop->fPoint.fX, edge->fTop->fPoint.fY, + edge->fBottom->fPoint.fX, edge->fBottom->fPoint.fY); + rewind(activeEdges, current, edge->fTop, c); + other->fWinding += edge->fWinding; + disconnect(edge); + edge->fTop = edge->fBottom = nullptr; + } else if (c.sweep_lt(edge->fBottom->fPoint, other->fBottom->fPoint)) { + rewind(activeEdges, current, other->fTop, c); + edge->fWinding += other->fWinding; + set_top(other, edge->fBottom, activeEdges, current, c); + } else { + rewind(activeEdges, current, edge->fTop, c); + other->fWinding += edge->fWinding; + set_top(edge, other->fBottom, activeEdges, current, c); + } +} + +bool top_collinear(Edge* left, Edge* right) { + if (!left || !right) { + return false; + } + return left->fTop->fPoint == right->fTop->fPoint || + !left->isLeftOf(right->fTop) || !right->isRightOf(left->fTop); +} + +bool bottom_collinear(Edge* left, Edge* right) { + if (!left || !right) { + return false; + } + return left->fBottom->fPoint == right->fBottom->fPoint || + !left->isLeftOf(right->fBottom) || !right->isRightOf(left->fBottom); +} + +void merge_collinear_edges(Edge* edge, EdgeList* activeEdges, Vertex** current, Comparator& c) { + for (;;) { + if (top_collinear(edge->fPrevEdgeAbove, edge)) { + merge_edges_above(edge->fPrevEdgeAbove, edge, activeEdges, current, c); + } else if (top_collinear(edge, edge->fNextEdgeAbove)) { + merge_edges_above(edge->fNextEdgeAbove, edge, activeEdges, current, c); + } else if (bottom_collinear(edge->fPrevEdgeBelow, edge)) { + merge_edges_below(edge->fPrevEdgeBelow, edge, activeEdges, current, c); + } else if (bottom_collinear(edge, edge->fNextEdgeBelow)) { + merge_edges_below(edge->fNextEdgeBelow, edge, activeEdges, current, c); + } else { + break; + } + } + SkASSERT(!top_collinear(edge->fPrevEdgeAbove, edge)); + SkASSERT(!top_collinear(edge, edge->fNextEdgeAbove)); + SkASSERT(!bottom_collinear(edge->fPrevEdgeBelow, edge)); + SkASSERT(!bottom_collinear(edge, edge->fNextEdgeBelow)); +} + +bool split_edge(Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current, Comparator& c, + SkArenaAlloc& alloc) { + if (!edge->fTop || !edge->fBottom || v == edge->fTop || v == edge->fBottom) { + return false; + } + TESS_LOG("splitting edge (%g -> %g) at vertex %g (%g, %g)\n", + edge->fTop->fID, edge->fBottom->fID, v->fID, v->fPoint.fX, v->fPoint.fY); + Vertex* top; + Vertex* bottom; + int winding = edge->fWinding; + if (c.sweep_lt(v->fPoint, edge->fTop->fPoint)) { + top = v; + bottom = edge->fTop; + set_top(edge, v, activeEdges, current, c); + } else if (c.sweep_lt(edge->fBottom->fPoint, v->fPoint)) { + top = edge->fBottom; + bottom = v; + set_bottom(edge, v, activeEdges, current, c); + } else { + top = v; + bottom = edge->fBottom; + set_bottom(edge, v, activeEdges, current, c); + } + Edge* newEdge = alloc.make(top, bottom, winding, edge->fType); + insert_edge_below(newEdge, top, c); + insert_edge_above(newEdge, bottom, c); + merge_collinear_edges(newEdge, activeEdges, current, c); + return true; +} + +bool intersect_edge_pair(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current, Comparator& c, SkArenaAlloc& alloc) { + if (!left->fTop || !left->fBottom || !right->fTop || !right->fBottom) { + return false; + } + if (left->fTop == right->fTop || left->fBottom == right->fBottom) { + return false; + } + if (c.sweep_lt(left->fTop->fPoint, right->fTop->fPoint)) { + if (!left->isLeftOf(right->fTop)) { + rewind(activeEdges, current, right->fTop, c); + return split_edge(left, right->fTop, activeEdges, current, c, alloc); + } + } else { + if (!right->isRightOf(left->fTop)) { + rewind(activeEdges, current, left->fTop, c); + return split_edge(right, left->fTop, activeEdges, current, c, alloc); + } + } + if (c.sweep_lt(right->fBottom->fPoint, left->fBottom->fPoint)) { + if (!left->isLeftOf(right->fBottom)) { + rewind(activeEdges, current, right->fBottom, c); + return split_edge(left, right->fBottom, activeEdges, current, c, alloc); + } + } else { + if (!right->isRightOf(left->fBottom)) { + rewind(activeEdges, current, left->fBottom, c); + return split_edge(right, left->fBottom, activeEdges, current, c, alloc); + } + } + return false; +} + +Edge* connect(Vertex* prev, Vertex* next, Edge::Type type, Comparator& c, SkArenaAlloc& alloc, + int winding_scale = 1) { + if (!prev || !next || prev->fPoint == next->fPoint) { + return nullptr; + } + Edge* edge = new_edge(prev, next, type, c, alloc); + insert_edge_below(edge, edge->fTop, c); + insert_edge_above(edge, edge->fBottom, c); + edge->fWinding *= winding_scale; + merge_collinear_edges(edge, nullptr, nullptr, c); + return edge; +} + +void merge_vertices(Vertex* src, Vertex* dst, VertexList* mesh, Comparator& c, + SkArenaAlloc& alloc) { + TESS_LOG("found coincident verts at %g, %g; merging %g into %g\n", + src->fPoint.fX, src->fPoint.fY, src->fID, dst->fID); + dst->fAlpha = SkTMax(src->fAlpha, dst->fAlpha); + if (src->fPartner) { + src->fPartner->fPartner = dst; + } + while (Edge* edge = src->fFirstEdgeAbove) { + set_bottom(edge, dst, nullptr, nullptr, c); + } + while (Edge* edge = src->fFirstEdgeBelow) { + set_top(edge, dst, nullptr, nullptr, c); + } + mesh->remove(src); + dst->fSynthetic = true; +} + +Vertex* create_sorted_vertex(const SkPoint& p, uint8_t alpha, VertexList* mesh, + Vertex* reference, Comparator& c, SkArenaAlloc& alloc) { + Vertex* prevV = reference; + while (prevV && c.sweep_lt(p, prevV->fPoint)) { + prevV = prevV->fPrev; + } + Vertex* nextV = prevV ? prevV->fNext : mesh->fHead; + while (nextV && c.sweep_lt(nextV->fPoint, p)) { + prevV = nextV; + nextV = nextV->fNext; + } + Vertex* v; + if (prevV && coincident(prevV->fPoint, p)) { + v = prevV; + } else if (nextV && coincident(nextV->fPoint, p)) { + v = nextV; + } else { + v = alloc.make(p, alpha); +#if LOGGING_ENABLED + if (!prevV) { + v->fID = mesh->fHead->fID - 1.0f; + } else if (!nextV) { + v->fID = mesh->fTail->fID + 1.0f; + } else { + v->fID = (prevV->fID + nextV->fID) * 0.5f; + } +#endif + mesh->insert(v, prevV, nextV); + } + return v; +} + +// If an edge's top and bottom points differ only by 1/2 machine epsilon in the primary +// sort criterion, it may not be possible to split correctly, since there is no point which is +// below the top and above the bottom. This function detects that case. +bool nearly_flat(Comparator& c, Edge* edge) { + SkPoint diff = edge->fBottom->fPoint - edge->fTop->fPoint; + float primaryDiff = c.fDirection == Comparator::Direction::kHorizontal ? diff.fX : diff.fY; + return fabs(primaryDiff) < std::numeric_limits::epsilon() && primaryDiff != 0.0f; +} + +SkPoint clamp(SkPoint p, SkPoint min, SkPoint max, Comparator& c) { + if (c.sweep_lt(p, min)) { + return min; + } else if (c.sweep_lt(max, p)) { + return max; + } else { + return p; + } +} + +void compute_bisector(Edge* edge1, Edge* edge2, Vertex* v, SkArenaAlloc& alloc) { + Line line1 = edge1->fLine; + Line line2 = edge2->fLine; + line1.normalize(); + line2.normalize(); + double cosAngle = line1.fA * line2.fA + line1.fB * line2.fB; + if (cosAngle > 0.999) { + return; + } + line1.fC += edge1->fWinding > 0 ? -1 : 1; + line2.fC += edge2->fWinding > 0 ? -1 : 1; + SkPoint p; + if (line1.intersect(line2, &p)) { + uint8_t alpha = edge1->fType == Edge::Type::kOuter ? 255 : 0; + v->fPartner = alloc.make(p, alpha); + TESS_LOG("computed bisector (%g,%g) alpha %d for vertex %g\n", p.fX, p.fY, alpha, v->fID); + } +} + +bool check_for_intersection(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current, + VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { + if (!left || !right) { + return false; + } + SkPoint p; + uint8_t alpha; + if (left->intersect(*right, &p, &alpha) && p.isFinite()) { + Vertex* v; + TESS_LOG("found intersection, pt is %g, %g\n", p.fX, p.fY); + Vertex* top = *current; + // If the intersection point is above the current vertex, rewind to the vertex above the + // intersection. + while (top && c.sweep_lt(p, top->fPoint)) { + top = top->fPrev; + } + if (!nearly_flat(c, left)) { + p = clamp(p, left->fTop->fPoint, left->fBottom->fPoint, c); + } + if (!nearly_flat(c, right)) { + p = clamp(p, right->fTop->fPoint, right->fBottom->fPoint, c); + } + if (p == left->fTop->fPoint) { + v = left->fTop; + } else if (p == left->fBottom->fPoint) { + v = left->fBottom; + } else if (p == right->fTop->fPoint) { + v = right->fTop; + } else if (p == right->fBottom->fPoint) { + v = right->fBottom; + } else { + v = create_sorted_vertex(p, alpha, mesh, top, c, alloc); + if (left->fTop->fPartner) { + v->fSynthetic = true; + compute_bisector(left, right, v, alloc); + } + } + rewind(activeEdges, current, top ? top : v, c); + split_edge(left, v, activeEdges, current, c, alloc); + split_edge(right, v, activeEdges, current, c, alloc); + v->fAlpha = SkTMax(v->fAlpha, alpha); + return true; + } + return intersect_edge_pair(left, right, activeEdges, current, c, alloc); +} + +void sanitize_contours(VertexList* contours, int contourCnt, bool approximate) { + for (VertexList* contour = contours; contourCnt > 0; --contourCnt, ++contour) { + SkASSERT(contour->fHead); + Vertex* prev = contour->fTail; + if (approximate) { + round(&prev->fPoint); + } + for (Vertex* v = contour->fHead; v;) { + if (approximate) { + round(&v->fPoint); + } + Vertex* next = v->fNext; + Vertex* nextWrap = next ? next : contour->fHead; + if (coincident(prev->fPoint, v->fPoint)) { + TESS_LOG("vertex %g,%g coincident; removing\n", v->fPoint.fX, v->fPoint.fY); + contour->remove(v); + } else if (!v->fPoint.isFinite()) { + TESS_LOG("vertex %g,%g non-finite; removing\n", v->fPoint.fX, v->fPoint.fY); + contour->remove(v); + } else if (Line(prev->fPoint, nextWrap->fPoint).dist(v->fPoint) == 0.0) { + TESS_LOG("vertex %g,%g collinear; removing\n", v->fPoint.fX, v->fPoint.fY); + contour->remove(v); + } else { + prev = v; + } + v = next; + } + } +} + +bool merge_coincident_vertices(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { + if (!mesh->fHead) { + return false; + } + bool merged = false; + for (Vertex* v = mesh->fHead->fNext; v;) { + Vertex* next = v->fNext; + if (c.sweep_lt(v->fPoint, v->fPrev->fPoint)) { + v->fPoint = v->fPrev->fPoint; + } + if (coincident(v->fPrev->fPoint, v->fPoint)) { + merge_vertices(v, v->fPrev, mesh, c, alloc); + merged = true; + } + v = next; + } + return merged; +} + +// Stage 2: convert the contours to a mesh of edges connecting the vertices. + +void build_edges(VertexList* contours, int contourCnt, VertexList* mesh, Comparator& c, + SkArenaAlloc& alloc) { + for (VertexList* contour = contours; contourCnt > 0; --contourCnt, ++contour) { + Vertex* prev = contour->fTail; + for (Vertex* v = contour->fHead; v;) { + Vertex* next = v->fNext; + connect(prev, v, Edge::Type::kInner, c, alloc); + mesh->append(v); + prev = v; + v = next; + } + } +} + +void connect_partners(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { + for (Vertex* outer = mesh->fHead; outer; outer = outer->fNext) { + if (Vertex* inner = outer->fPartner) { + if ((inner->fPrev || inner->fNext) && (outer->fPrev || outer->fNext)) { + // Connector edges get zero winding, since they're only structural (i.e., to ensure + // no 0-0-0 alpha triangles are produced), and shouldn't affect the poly winding + // number. + connect(outer, inner, Edge::Type::kConnector, c, alloc, 0); + inner->fPartner = outer->fPartner = nullptr; + } + } + } +} + +template +void sorted_merge(VertexList* front, VertexList* back, VertexList* result) { + Vertex* a = front->fHead; + Vertex* b = back->fHead; + while (a && b) { + if (sweep_lt(a->fPoint, b->fPoint)) { + front->remove(a); + result->append(a); + a = front->fHead; + } else { + back->remove(b); + result->append(b); + b = back->fHead; + } + } + result->append(*front); + result->append(*back); +} + +void sorted_merge(VertexList* front, VertexList* back, VertexList* result, Comparator& c) { + if (c.fDirection == Comparator::Direction::kHorizontal) { + sorted_merge(front, back, result); + } else { + sorted_merge(front, back, result); + } +#if LOGGING_ENABLED + float id = 0.0f; + for (Vertex* v = result->fHead; v; v = v->fNext) { + v->fID = id++; + } +#endif +} + +// Stage 3: sort the vertices by increasing sweep direction. + +template +void merge_sort(VertexList* vertices) { + Vertex* slow = vertices->fHead; + if (!slow) { + return; + } + Vertex* fast = slow->fNext; + if (!fast) { + return; + } + do { + fast = fast->fNext; + if (fast) { + fast = fast->fNext; + slow = slow->fNext; + } + } while (fast); + VertexList front(vertices->fHead, slow); + VertexList back(slow->fNext, vertices->fTail); + front.fTail->fNext = back.fHead->fPrev = nullptr; + + merge_sort(&front); + merge_sort(&back); + + vertices->fHead = vertices->fTail = nullptr; + sorted_merge(&front, &back, vertices); +} + +void dump_mesh(const VertexList& mesh) { +#if LOGGING_ENABLED + for (Vertex* v = mesh.fHead; v; v = v->fNext) { + TESS_LOG("vertex %g (%g, %g) alpha %d", v->fID, v->fPoint.fX, v->fPoint.fY, v->fAlpha); + if (Vertex* p = v->fPartner) { + TESS_LOG(", partner %g (%g, %g) alpha %d\n", + p->fID, p->fPoint.fX, p->fPoint.fY, p->fAlpha); + } else { + TESS_LOG(", null partner\n"); + } + for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) { + TESS_LOG(" edge %g -> %g, winding %d\n", e->fTop->fID, e->fBottom->fID, e->fWinding); + } + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + TESS_LOG(" edge %g -> %g, winding %d\n", e->fTop->fID, e->fBottom->fID, e->fWinding); + } + } +#endif +} + +void dump_skel(const SSEdgeList& ssEdges) { +#if LOGGING_ENABLED + for (SSEdge* edge : ssEdges) { + if (edge->fEdge) { + TESS_LOG("skel edge %g -> %g", + edge->fPrev->fVertex->fID, + edge->fNext->fVertex->fID); + if (edge->fEdge->fTop && edge->fEdge->fBottom) { + TESS_LOG(" (original %g -> %g)\n", + edge->fEdge->fTop->fID, + edge->fEdge->fBottom->fID); + } else { + TESS_LOG("\n"); + } + } + } +#endif +} + +#ifdef SK_DEBUG +void validate_edge_pair(Edge* left, Edge* right, Comparator& c) { + if (!left || !right) { + return; + } + if (left->fTop == right->fTop) { + SkASSERT(left->isLeftOf(right->fBottom)); + SkASSERT(right->isRightOf(left->fBottom)); + } else if (c.sweep_lt(left->fTop->fPoint, right->fTop->fPoint)) { + SkASSERT(left->isLeftOf(right->fTop)); + } else { + SkASSERT(right->isRightOf(left->fTop)); + } + if (left->fBottom == right->fBottom) { + SkASSERT(left->isLeftOf(right->fTop)); + SkASSERT(right->isRightOf(left->fTop)); + } else if (c.sweep_lt(right->fBottom->fPoint, left->fBottom->fPoint)) { + SkASSERT(left->isLeftOf(right->fBottom)); + } else { + SkASSERT(right->isRightOf(left->fBottom)); + } +} + +void validate_edge_list(EdgeList* edges, Comparator& c) { + Edge* left = edges->fHead; + if (!left) { + return; + } + for (Edge* right = left->fRight; right; right = right->fRight) { + validate_edge_pair(left, right, c); + left = right; + } +} +#endif + +// Stage 4: Simplify the mesh by inserting new vertices at intersecting edges. + +bool connected(Vertex* v) { + return v->fFirstEdgeAbove || v->fFirstEdgeBelow; +} + +bool simplify(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { + TESS_LOG("simplifying complex polygons\n"); + EdgeList activeEdges; + bool found = false; + for (Vertex* v = mesh->fHead; v != nullptr; v = v->fNext) { + if (!connected(v)) { + continue; + } + Edge* leftEnclosingEdge; + Edge* rightEnclosingEdge; + bool restartChecks; + do { + TESS_LOG("\nvertex %g: (%g,%g), alpha %d\n", + v->fID, v->fPoint.fX, v->fPoint.fY, v->fAlpha); + restartChecks = false; + find_enclosing_edges(v, &activeEdges, &leftEnclosingEdge, &rightEnclosingEdge); + v->fLeftEnclosingEdge = leftEnclosingEdge; + v->fRightEnclosingEdge = rightEnclosingEdge; + if (v->fFirstEdgeBelow) { + for (Edge* edge = v->fFirstEdgeBelow; edge; edge = edge->fNextEdgeBelow) { + if (check_for_intersection(leftEnclosingEdge, edge, &activeEdges, &v, mesh, c, + alloc)) { + restartChecks = true; + break; + } + if (check_for_intersection(edge, rightEnclosingEdge, &activeEdges, &v, mesh, c, + alloc)) { + restartChecks = true; + break; + } + } + } else { + if (check_for_intersection(leftEnclosingEdge, rightEnclosingEdge, + &activeEdges, &v, mesh, c, alloc)) { + restartChecks = true; + } + + } + found = found || restartChecks; + } while (restartChecks); +#ifdef SK_DEBUG + validate_edge_list(&activeEdges, c); +#endif + for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) { + remove_edge(e, &activeEdges); + } + Edge* leftEdge = leftEnclosingEdge; + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + insert_edge(e, leftEdge, &activeEdges); + leftEdge = e; + } + } + SkASSERT(!activeEdges.fHead && !activeEdges.fTail); + return found; +} + +// Stage 5: Tessellate the simplified mesh into monotone polygons. + +Poly* tessellate(const VertexList& vertices, SkArenaAlloc& alloc) { + TESS_LOG("\ntessellating simple polygons\n"); + EdgeList activeEdges; + Poly* polys = nullptr; + for (Vertex* v = vertices.fHead; v != nullptr; v = v->fNext) { + if (!connected(v)) { + continue; + } +#if LOGGING_ENABLED + TESS_LOG("\nvertex %g: (%g,%g), alpha %d\n", v->fID, v->fPoint.fX, v->fPoint.fY, v->fAlpha); +#endif + Edge* leftEnclosingEdge; + Edge* rightEnclosingEdge; + find_enclosing_edges(v, &activeEdges, &leftEnclosingEdge, &rightEnclosingEdge); + Poly* leftPoly; + Poly* rightPoly; + if (v->fFirstEdgeAbove) { + leftPoly = v->fFirstEdgeAbove->fLeftPoly; + rightPoly = v->fLastEdgeAbove->fRightPoly; + } else { + leftPoly = leftEnclosingEdge ? leftEnclosingEdge->fRightPoly : nullptr; + rightPoly = rightEnclosingEdge ? rightEnclosingEdge->fLeftPoly : nullptr; + } +#if LOGGING_ENABLED + TESS_LOG("edges above:\n"); + for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) { + TESS_LOG("%g -> %g, lpoly %d, rpoly %d\n", + e->fTop->fID, e->fBottom->fID, + e->fLeftPoly ? e->fLeftPoly->fID : -1, + e->fRightPoly ? e->fRightPoly->fID : -1); + } + TESS_LOG("edges below:\n"); + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + TESS_LOG("%g -> %g, lpoly %d, rpoly %d\n", + e->fTop->fID, e->fBottom->fID, + e->fLeftPoly ? e->fLeftPoly->fID : -1, + e->fRightPoly ? e->fRightPoly->fID : -1); + } +#endif + if (v->fFirstEdgeAbove) { + if (leftPoly) { + leftPoly = leftPoly->addEdge(v->fFirstEdgeAbove, Poly::kRight_Side, alloc); + } + if (rightPoly) { + rightPoly = rightPoly->addEdge(v->fLastEdgeAbove, Poly::kLeft_Side, alloc); + } + for (Edge* e = v->fFirstEdgeAbove; e != v->fLastEdgeAbove; e = e->fNextEdgeAbove) { + Edge* rightEdge = e->fNextEdgeAbove; + remove_edge(e, &activeEdges); + if (e->fRightPoly) { + e->fRightPoly->addEdge(e, Poly::kLeft_Side, alloc); + } + if (rightEdge->fLeftPoly && rightEdge->fLeftPoly != e->fRightPoly) { + rightEdge->fLeftPoly->addEdge(e, Poly::kRight_Side, alloc); + } + } + remove_edge(v->fLastEdgeAbove, &activeEdges); + if (!v->fFirstEdgeBelow) { + if (leftPoly && rightPoly && leftPoly != rightPoly) { + SkASSERT(leftPoly->fPartner == nullptr && rightPoly->fPartner == nullptr); + rightPoly->fPartner = leftPoly; + leftPoly->fPartner = rightPoly; + } + } + } + if (v->fFirstEdgeBelow) { + if (!v->fFirstEdgeAbove) { + if (leftPoly && rightPoly) { + if (leftPoly == rightPoly) { + if (leftPoly->fTail && leftPoly->fTail->fSide == Poly::kLeft_Side) { + leftPoly = new_poly(&polys, leftPoly->lastVertex(), + leftPoly->fWinding, alloc); + leftEnclosingEdge->fRightPoly = leftPoly; + } else { + rightPoly = new_poly(&polys, rightPoly->lastVertex(), + rightPoly->fWinding, alloc); + rightEnclosingEdge->fLeftPoly = rightPoly; + } + } + Edge* join = alloc.make(leftPoly->lastVertex(), v, 1, Edge::Type::kInner); + leftPoly = leftPoly->addEdge(join, Poly::kRight_Side, alloc); + rightPoly = rightPoly->addEdge(join, Poly::kLeft_Side, alloc); + } + } + Edge* leftEdge = v->fFirstEdgeBelow; + leftEdge->fLeftPoly = leftPoly; + insert_edge(leftEdge, leftEnclosingEdge, &activeEdges); + for (Edge* rightEdge = leftEdge->fNextEdgeBelow; rightEdge; + rightEdge = rightEdge->fNextEdgeBelow) { + insert_edge(rightEdge, leftEdge, &activeEdges); + int winding = leftEdge->fLeftPoly ? leftEdge->fLeftPoly->fWinding : 0; + winding += leftEdge->fWinding; + if (winding != 0) { + Poly* poly = new_poly(&polys, v, winding, alloc); + leftEdge->fRightPoly = rightEdge->fLeftPoly = poly; + } + leftEdge = rightEdge; + } + v->fLastEdgeBelow->fRightPoly = rightPoly; + } +#if LOGGING_ENABLED + TESS_LOG("\nactive edges:\n"); + for (Edge* e = activeEdges.fHead; e != nullptr; e = e->fRight) { + TESS_LOG("%g -> %g, lpoly %d, rpoly %d\n", + e->fTop->fID, e->fBottom->fID, + e->fLeftPoly ? e->fLeftPoly->fID : -1, + e->fRightPoly ? e->fRightPoly->fID : -1); + } +#endif + } + return polys; +} + +void remove_non_boundary_edges(const VertexList& mesh, SkPath::FillType fillType, + SkArenaAlloc& alloc) { + TESS_LOG("removing non-boundary edges\n"); + EdgeList activeEdges; + for (Vertex* v = mesh.fHead; v != nullptr; v = v->fNext) { + if (!connected(v)) { + continue; + } + Edge* leftEnclosingEdge; + Edge* rightEnclosingEdge; + find_enclosing_edges(v, &activeEdges, &leftEnclosingEdge, &rightEnclosingEdge); + bool prevFilled = leftEnclosingEdge && + apply_fill_type(fillType, leftEnclosingEdge->fWinding); + for (Edge* e = v->fFirstEdgeAbove; e;) { + Edge* next = e->fNextEdgeAbove; + remove_edge(e, &activeEdges); + bool filled = apply_fill_type(fillType, e->fWinding); + if (filled == prevFilled) { + disconnect(e); + } + prevFilled = filled; + e = next; + } + Edge* prev = leftEnclosingEdge; + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + if (prev) { + e->fWinding += prev->fWinding; + } + insert_edge(e, prev, &activeEdges); + prev = e; + } + } +} + +// Note: this is the normal to the edge, but not necessarily unit length. +void get_edge_normal(const Edge* e, SkVector* normal) { + normal->set(SkDoubleToScalar(e->fLine.fA), + SkDoubleToScalar(e->fLine.fB)); +} + +// Stage 5c: detect and remove "pointy" vertices whose edge normals point in opposite directions +// and whose adjacent vertices are less than a quarter pixel from an edge. These are guaranteed to +// invert on stroking. + +void simplify_boundary(EdgeList* boundary, Comparator& c, SkArenaAlloc& alloc) { + Edge* prevEdge = boundary->fTail; + SkVector prevNormal; + get_edge_normal(prevEdge, &prevNormal); + for (Edge* e = boundary->fHead; e != nullptr;) { + Vertex* prev = prevEdge->fWinding == 1 ? prevEdge->fTop : prevEdge->fBottom; + Vertex* next = e->fWinding == 1 ? e->fBottom : e->fTop; + double distPrev = e->dist(prev->fPoint); + double distNext = prevEdge->dist(next->fPoint); + SkVector normal; + get_edge_normal(e, &normal); + constexpr double kQuarterPixelSq = 0.25f * 0.25f; + if (prev == next) { + remove_edge(prevEdge, boundary); + remove_edge(e, boundary); + prevEdge = boundary->fTail; + e = boundary->fHead; + if (prevEdge) { + get_edge_normal(prevEdge, &prevNormal); + } + } else if (prevNormal.dot(normal) < 0.0 && + (distPrev * distPrev <= kQuarterPixelSq || distNext * distNext <= kQuarterPixelSq)) { + Edge* join = new_edge(prev, next, Edge::Type::kInner, c, alloc); + if (prev->fPoint != next->fPoint) { + join->fLine.normalize(); + join->fLine = join->fLine * join->fWinding; + } + insert_edge(join, e, boundary); + remove_edge(prevEdge, boundary); + remove_edge(e, boundary); + if (join->fLeft && join->fRight) { + prevEdge = join->fLeft; + e = join; + } else { + prevEdge = boundary->fTail; + e = boundary->fHead; // join->fLeft ? join->fLeft : join; + } + get_edge_normal(prevEdge, &prevNormal); + } else { + prevEdge = e; + prevNormal = normal; + e = e->fRight; + } + } +} + +void ss_connect(Vertex* v, Vertex* dest, Comparator& c, SkArenaAlloc& alloc) { + if (v == dest) { + return; + } + TESS_LOG("ss_connecting vertex %g to vertex %g\n", v->fID, dest->fID); + if (v->fSynthetic) { + connect(v, dest, Edge::Type::kConnector, c, alloc, 0); + } else if (v->fPartner) { + TESS_LOG("setting %g's partner to %g ", v->fPartner->fID, dest->fID); + TESS_LOG("and %g's partner to null\n", v->fID); + v->fPartner->fPartner = dest; + v->fPartner = nullptr; + } +} + +void Event::apply(VertexList* mesh, Comparator& c, EventList* events, SkArenaAlloc& alloc) { + if (!fEdge) { + return; + } + Vertex* prev = fEdge->fPrev->fVertex; + Vertex* next = fEdge->fNext->fVertex; + SSEdge* prevEdge = fEdge->fPrev->fPrev; + SSEdge* nextEdge = fEdge->fNext->fNext; + if (!prevEdge || !nextEdge || !prevEdge->fEdge || !nextEdge->fEdge) { + return; + } + Vertex* dest = create_sorted_vertex(fPoint, fAlpha, mesh, prev, c, alloc); + dest->fSynthetic = true; + SSVertex* ssv = alloc.make(dest); + TESS_LOG("collapsing %g, %g (original edge %g -> %g) to %g (%g, %g) alpha %d\n", + prev->fID, next->fID, fEdge->fEdge->fTop->fID, fEdge->fEdge->fBottom->fID, dest->fID, + fPoint.fX, fPoint.fY, fAlpha); + fEdge->fEdge = nullptr; + + ss_connect(prev, dest, c, alloc); + ss_connect(next, dest, c, alloc); + + prevEdge->fNext = nextEdge->fPrev = ssv; + ssv->fPrev = prevEdge; + ssv->fNext = nextEdge; + if (!prevEdge->fEdge || !nextEdge->fEdge) { + return; + } + if (prevEdge->fEvent) { + prevEdge->fEvent->fEdge = nullptr; + } + if (nextEdge->fEvent) { + nextEdge->fEvent->fEdge = nullptr; + } + if (prevEdge->fPrev == nextEdge->fNext) { + ss_connect(prevEdge->fPrev->fVertex, dest, c, alloc); + prevEdge->fEdge = nextEdge->fEdge = nullptr; + } else { + compute_bisector(prevEdge->fEdge, nextEdge->fEdge, dest, alloc); + SkASSERT(prevEdge != fEdge && nextEdge != fEdge); + if (dest->fPartner) { + create_event(prevEdge, events, alloc); + create_event(nextEdge, events, alloc); + } else { + create_event(prevEdge, prevEdge->fPrev->fVertex, nextEdge, dest, events, c, alloc); + create_event(nextEdge, nextEdge->fNext->fVertex, prevEdge, dest, events, c, alloc); + } + } +} + +bool is_overlap_edge(Edge* e) { + if (e->fType == Edge::Type::kOuter) { + return e->fWinding != 0 && e->fWinding != 1; + } else if (e->fType == Edge::Type::kInner) { + return e->fWinding != 0 && e->fWinding != -2; + } else { + return false; + } +} + +// This is a stripped-down version of tessellate() which computes edges which +// join two filled regions, which represent overlap regions, and collapses them. +bool collapse_overlap_regions(VertexList* mesh, Comparator& c, SkArenaAlloc& alloc, + EventComparator comp) { + TESS_LOG("\nfinding overlap regions\n"); + EdgeList activeEdges; + EventList events(comp); + SSVertexMap ssVertices; + SSEdgeList ssEdges; + for (Vertex* v = mesh->fHead; v != nullptr; v = v->fNext) { + if (!connected(v)) { + continue; + } + Edge* leftEnclosingEdge; + Edge* rightEnclosingEdge; + find_enclosing_edges(v, &activeEdges, &leftEnclosingEdge, &rightEnclosingEdge); + for (Edge* e = v->fLastEdgeAbove; e && e != leftEnclosingEdge;) { + Edge* prev = e->fPrevEdgeAbove ? e->fPrevEdgeAbove : leftEnclosingEdge; + remove_edge(e, &activeEdges); + bool leftOverlap = prev && is_overlap_edge(prev); + bool rightOverlap = is_overlap_edge(e); + bool isOuterBoundary = e->fType == Edge::Type::kOuter && + (!prev || prev->fWinding == 0 || e->fWinding == 0); + if (prev) { + e->fWinding -= prev->fWinding; + } + if (leftOverlap && rightOverlap) { + TESS_LOG("found interior overlap edge %g -> %g, disconnecting\n", + e->fTop->fID, e->fBottom->fID); + disconnect(e); + } else if (leftOverlap || rightOverlap) { + TESS_LOG("found overlap edge %g -> %g%s\n", + e->fTop->fID, e->fBottom->fID, + isOuterBoundary ? ", is outer boundary" : ""); + Vertex* prevVertex = e->fWinding < 0 ? e->fBottom : e->fTop; + Vertex* nextVertex = e->fWinding < 0 ? e->fTop : e->fBottom; + SSVertex* ssPrev = ssVertices[prevVertex]; + if (!ssPrev) { + ssPrev = ssVertices[prevVertex] = alloc.make(prevVertex); + } + SSVertex* ssNext = ssVertices[nextVertex]; + if (!ssNext) { + ssNext = ssVertices[nextVertex] = alloc.make(nextVertex); + } + SSEdge* ssEdge = alloc.make(e, ssPrev, ssNext); + ssEdges.push_back(ssEdge); +// SkASSERT(!ssPrev->fNext && !ssNext->fPrev); + ssPrev->fNext = ssNext->fPrev = ssEdge; + create_event(ssEdge, &events, alloc); + if (!isOuterBoundary) { + disconnect(e); + } + } + e = prev; + } + Edge* prev = leftEnclosingEdge; + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + if (prev) { + e->fWinding += prev->fWinding; + } + insert_edge(e, prev, &activeEdges); + prev = e; + } + } + bool complex = events.size() > 0; + + TESS_LOG("\ncollapsing overlap regions\n"); + TESS_LOG("skeleton before:\n"); + dump_skel(ssEdges); + while (events.size() > 0) { + Event* event = events.top(); + events.pop(); + event->apply(mesh, c, &events, alloc); + } + TESS_LOG("skeleton after:\n"); + dump_skel(ssEdges); + for (SSEdge* edge : ssEdges) { + if (Edge* e = edge->fEdge) { + connect(edge->fPrev->fVertex, edge->fNext->fVertex, e->fType, c, alloc, 0); + } + } + return complex; +} + +bool inversion(Vertex* prev, Vertex* next, Edge* origEdge, Comparator& c) { + if (!prev || !next) { + return true; + } + int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? 1 : -1; + return winding != origEdge->fWinding; +} + +// Stage 5d: Displace edges by half a pixel inward and outward along their normals. Intersect to +// find new vertices, and set zero alpha on the exterior and one alpha on the interior. Build a +// new antialiased mesh from those vertices. + +void stroke_boundary(EdgeList* boundary, VertexList* innerMesh, VertexList* outerMesh, + Comparator& c, SkArenaAlloc& alloc) { + TESS_LOG("\nstroking boundary\n"); + // A boundary with fewer than 3 edges is degenerate. + if (!boundary->fHead || !boundary->fHead->fRight || !boundary->fHead->fRight->fRight) { + return; + } + Edge* prevEdge = boundary->fTail; + Vertex* prevV = prevEdge->fWinding > 0 ? prevEdge->fTop : prevEdge->fBottom; + SkVector prevNormal; + get_edge_normal(prevEdge, &prevNormal); + double radius = 0.5; + Line prevInner(prevEdge->fLine); + prevInner.fC -= radius; + Line prevOuter(prevEdge->fLine); + prevOuter.fC += radius; + VertexList innerVertices; + VertexList outerVertices; + bool innerInversion = true; + bool outerInversion = true; + for (Edge* e = boundary->fHead; e != nullptr; e = e->fRight) { + Vertex* v = e->fWinding > 0 ? e->fTop : e->fBottom; + SkVector normal; + get_edge_normal(e, &normal); + Line inner(e->fLine); + inner.fC -= radius; + Line outer(e->fLine); + outer.fC += radius; + SkPoint innerPoint, outerPoint; + TESS_LOG("stroking vertex %g (%g, %g)\n", v->fID, v->fPoint.fX, v->fPoint.fY); + if (!prevEdge->fLine.nearParallel(e->fLine) && prevInner.intersect(inner, &innerPoint) && + prevOuter.intersect(outer, &outerPoint)) { + float cosAngle = normal.dot(prevNormal); + if (cosAngle < -kCosMiterAngle) { + Vertex* nextV = e->fWinding > 0 ? e->fBottom : e->fTop; + + // This is a pointy vertex whose angle is smaller than the threshold; miter it. + Line bisector(innerPoint, outerPoint); + Line tangent(v->fPoint, v->fPoint + SkPoint::Make(bisector.fA, bisector.fB)); + if (tangent.fA == 0 && tangent.fB == 0) { + continue; + } + tangent.normalize(); + Line innerTangent(tangent); + Line outerTangent(tangent); + innerTangent.fC -= 0.5; + outerTangent.fC += 0.5; + SkPoint innerPoint1, innerPoint2, outerPoint1, outerPoint2; + if (prevNormal.cross(normal) > 0) { + // Miter inner points + if (!innerTangent.intersect(prevInner, &innerPoint1) || + !innerTangent.intersect(inner, &innerPoint2) || + !outerTangent.intersect(bisector, &outerPoint)) { + continue; + } + Line prevTangent(prevV->fPoint, + prevV->fPoint + SkVector::Make(prevOuter.fA, prevOuter.fB)); + Line nextTangent(nextV->fPoint, + nextV->fPoint + SkVector::Make(outer.fA, outer.fB)); + if (prevTangent.dist(outerPoint) > 0) { + bisector.intersect(prevTangent, &outerPoint); + } + if (nextTangent.dist(outerPoint) < 0) { + bisector.intersect(nextTangent, &outerPoint); + } + outerPoint1 = outerPoint2 = outerPoint; + } else { + // Miter outer points + if (!outerTangent.intersect(prevOuter, &outerPoint1) || + !outerTangent.intersect(outer, &outerPoint2)) { + continue; + } + Line prevTangent(prevV->fPoint, + prevV->fPoint + SkVector::Make(prevInner.fA, prevInner.fB)); + Line nextTangent(nextV->fPoint, + nextV->fPoint + SkVector::Make(inner.fA, inner.fB)); + if (prevTangent.dist(innerPoint) > 0) { + bisector.intersect(prevTangent, &innerPoint); + } + if (nextTangent.dist(innerPoint) < 0) { + bisector.intersect(nextTangent, &innerPoint); + } + innerPoint1 = innerPoint2 = innerPoint; + } + if (!innerPoint1.isFinite() || !innerPoint2.isFinite() || + !outerPoint1.isFinite() || !outerPoint2.isFinite()) { + continue; + } + TESS_LOG("inner (%g, %g), (%g, %g), ", + innerPoint1.fX, innerPoint1.fY, innerPoint2.fX, innerPoint2.fY); + TESS_LOG("outer (%g, %g), (%g, %g)\n", + outerPoint1.fX, outerPoint1.fY, outerPoint2.fX, outerPoint2.fY); + Vertex* innerVertex1 = alloc.make(innerPoint1, 255); + Vertex* innerVertex2 = alloc.make(innerPoint2, 255); + Vertex* outerVertex1 = alloc.make(outerPoint1, 0); + Vertex* outerVertex2 = alloc.make(outerPoint2, 0); + innerVertex1->fPartner = outerVertex1; + innerVertex2->fPartner = outerVertex2; + outerVertex1->fPartner = innerVertex1; + outerVertex2->fPartner = innerVertex2; + if (!inversion(innerVertices.fTail, innerVertex1, prevEdge, c)) { + innerInversion = false; + } + if (!inversion(outerVertices.fTail, outerVertex1, prevEdge, c)) { + outerInversion = false; + } + innerVertices.append(innerVertex1); + innerVertices.append(innerVertex2); + outerVertices.append(outerVertex1); + outerVertices.append(outerVertex2); + } else { + TESS_LOG("inner (%g, %g), ", innerPoint.fX, innerPoint.fY); + TESS_LOG("outer (%g, %g)\n", outerPoint.fX, outerPoint.fY); + Vertex* innerVertex = alloc.make(innerPoint, 255); + Vertex* outerVertex = alloc.make(outerPoint, 0); + innerVertex->fPartner = outerVertex; + outerVertex->fPartner = innerVertex; + if (!inversion(innerVertices.fTail, innerVertex, prevEdge, c)) { + innerInversion = false; + } + if (!inversion(outerVertices.fTail, outerVertex, prevEdge, c)) { + outerInversion = false; + } + innerVertices.append(innerVertex); + outerVertices.append(outerVertex); + } + } + prevInner = inner; + prevOuter = outer; + prevV = v; + prevEdge = e; + prevNormal = normal; + } + if (!inversion(innerVertices.fTail, innerVertices.fHead, prevEdge, c)) { + innerInversion = false; + } + if (!inversion(outerVertices.fTail, outerVertices.fHead, prevEdge, c)) { + outerInversion = false; + } + // Outer edges get 1 winding, and inner edges get -2 winding. This ensures that the interior + // is always filled (1 + -2 = -1 for normal cases, 1 + 2 = 3 for thin features where the + // interior inverts). + // For total inversion cases, the shape has now reversed handedness, so invert the winding + // so it will be detected during collapse_overlap_regions(). + int innerWinding = innerInversion ? 2 : -2; + int outerWinding = outerInversion ? -1 : 1; + for (Vertex* v = innerVertices.fHead; v && v->fNext; v = v->fNext) { + connect(v, v->fNext, Edge::Type::kInner, c, alloc, innerWinding); + } + connect(innerVertices.fTail, innerVertices.fHead, Edge::Type::kInner, c, alloc, innerWinding); + for (Vertex* v = outerVertices.fHead; v && v->fNext; v = v->fNext) { + connect(v, v->fNext, Edge::Type::kOuter, c, alloc, outerWinding); + } + connect(outerVertices.fTail, outerVertices.fHead, Edge::Type::kOuter, c, alloc, outerWinding); + innerMesh->append(innerVertices); + outerMesh->append(outerVertices); +} + +void extract_boundary(EdgeList* boundary, Edge* e, SkPath::FillType fillType, SkArenaAlloc& alloc) { + TESS_LOG("\nextracting boundary\n"); + bool down = apply_fill_type(fillType, e->fWinding); + Vertex* start = down ? e->fTop : e->fBottom; + do { + e->fWinding = down ? 1 : -1; + Edge* next; + e->fLine.normalize(); + e->fLine = e->fLine * e->fWinding; + boundary->append(e); + if (down) { + // Find outgoing edge, in clockwise order. + if ((next = e->fNextEdgeAbove)) { + down = false; + } else if ((next = e->fBottom->fLastEdgeBelow)) { + down = true; + } else if ((next = e->fPrevEdgeAbove)) { + down = false; + } + } else { + // Find outgoing edge, in counter-clockwise order. + if ((next = e->fPrevEdgeBelow)) { + down = true; + } else if ((next = e->fTop->fFirstEdgeAbove)) { + down = false; + } else if ((next = e->fNextEdgeBelow)) { + down = true; + } + } + disconnect(e); + e = next; + } while (e && (down ? e->fTop : e->fBottom) != start); +} + +// Stage 5b: Extract boundaries from mesh, simplify and stroke them into a new mesh. + +void extract_boundaries(const VertexList& inMesh, VertexList* innerVertices, + VertexList* outerVertices, SkPath::FillType fillType, + Comparator& c, SkArenaAlloc& alloc) { + remove_non_boundary_edges(inMesh, fillType, alloc); + for (Vertex* v = inMesh.fHead; v; v = v->fNext) { + while (v->fFirstEdgeBelow) { + EdgeList boundary; + extract_boundary(&boundary, v->fFirstEdgeBelow, fillType, alloc); + simplify_boundary(&boundary, c, alloc); + stroke_boundary(&boundary, innerVertices, outerVertices, c, alloc); + } + } +} + +// This is a driver function that calls stages 2-5 in turn. + +void contours_to_mesh(VertexList* contours, int contourCnt, bool antialias, + VertexList* mesh, Comparator& c, SkArenaAlloc& alloc) { +#if LOGGING_ENABLED + for (int i = 0; i < contourCnt; ++i) { + Vertex* v = contours[i].fHead; + SkASSERT(v); + TESS_LOG("path.moveTo(%20.20g, %20.20g);\n", v->fPoint.fX, v->fPoint.fY); + for (v = v->fNext; v; v = v->fNext) { + TESS_LOG("path.lineTo(%20.20g, %20.20g);\n", v->fPoint.fX, v->fPoint.fY); + } + } +#endif + sanitize_contours(contours, contourCnt, antialias); + build_edges(contours, contourCnt, mesh, c, alloc); +} + +void sort_mesh(VertexList* vertices, Comparator& c, SkArenaAlloc& alloc) { + if (!vertices || !vertices->fHead) { + return; + } + + // Sort vertices in Y (secondarily in X). + if (c.fDirection == Comparator::Direction::kHorizontal) { + merge_sort(vertices); + } else { + merge_sort(vertices); + } +#if LOGGING_ENABLED + for (Vertex* v = vertices->fHead; v != nullptr; v = v->fNext) { + static float gID = 0.0f; + v->fID = gID++; + } +#endif +} + +Poly* contours_to_polys(VertexList* contours, int contourCnt, SkPath::FillType fillType, + const SkRect& pathBounds, bool antialias, VertexList* outerMesh, + SkArenaAlloc& alloc) { + Comparator c(pathBounds.width() > pathBounds.height() ? Comparator::Direction::kHorizontal + : Comparator::Direction::kVertical); + VertexList mesh; + contours_to_mesh(contours, contourCnt, antialias, &mesh, c, alloc); + sort_mesh(&mesh, c, alloc); + merge_coincident_vertices(&mesh, c, alloc); + simplify(&mesh, c, alloc); + TESS_LOG("\nsimplified mesh:\n"); + dump_mesh(mesh); + if (antialias) { + VertexList innerMesh; + extract_boundaries(mesh, &innerMesh, outerMesh, fillType, c, alloc); + sort_mesh(&innerMesh, c, alloc); + sort_mesh(outerMesh, c, alloc); + merge_coincident_vertices(&innerMesh, c, alloc); + bool was_complex = merge_coincident_vertices(outerMesh, c, alloc); + was_complex = simplify(&innerMesh, c, alloc) || was_complex; + was_complex = simplify(outerMesh, c, alloc) || was_complex; + TESS_LOG("\ninner mesh before:\n"); + dump_mesh(innerMesh); + TESS_LOG("\nouter mesh before:\n"); + dump_mesh(*outerMesh); + EventComparator eventLT(EventComparator::Op::kLessThan); + EventComparator eventGT(EventComparator::Op::kGreaterThan); + was_complex = collapse_overlap_regions(&innerMesh, c, alloc, eventLT) || was_complex; + was_complex = collapse_overlap_regions(outerMesh, c, alloc, eventGT) || was_complex; + if (was_complex) { + TESS_LOG("found complex mesh; taking slow path\n"); + VertexList aaMesh; + TESS_LOG("\ninner mesh after:\n"); + dump_mesh(innerMesh); + TESS_LOG("\nouter mesh after:\n"); + dump_mesh(*outerMesh); + connect_partners(outerMesh, c, alloc); + connect_partners(&innerMesh, c, alloc); + sorted_merge(&innerMesh, outerMesh, &aaMesh, c); + merge_coincident_vertices(&aaMesh, c, alloc); + simplify(&aaMesh, c, alloc); + TESS_LOG("combined and simplified mesh:\n"); + dump_mesh(aaMesh); + outerMesh->fHead = outerMesh->fTail = nullptr; + return tessellate(aaMesh, alloc); + } else { + TESS_LOG("no complex polygons; taking fast path\n"); + return tessellate(innerMesh, alloc); + } + } else { + return tessellate(mesh, alloc); + } +} + +// Stage 6: Triangulate the monotone polygons into a vertex buffer. +void* polys_to_triangles(Poly* polys, SkPath::FillType fillType, bool emitCoverage, void* data) { + for (Poly* poly = polys; poly; poly = poly->fNext) { + if (apply_fill_type(fillType, poly)) { + data = poly->emit(emitCoverage, data); + } + } + return data; +} + +Poly* path_to_polys(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds, + int contourCnt, SkArenaAlloc& alloc, bool antialias, bool* isLinear, + VertexList* outerMesh) { + SkPath::FillType fillType = path.getFillType(); + if (SkPath::IsInverseFillType(fillType)) { + contourCnt++; + } + std::unique_ptr contours(new VertexList[contourCnt]); + + path_to_contours(path, tolerance, clipBounds, contours.get(), alloc, isLinear); + return contours_to_polys(contours.get(), contourCnt, path.getFillType(), path.getBounds(), + antialias, outerMesh, alloc); +} + +int get_contour_count(const SkPath& path, SkScalar tolerance) { + int contourCnt; + int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tolerance); + if (maxPts <= 0) { + return 0; + } + return contourCnt; +} + +int64_t count_points(Poly* polys, SkPath::FillType fillType) { + int64_t count = 0; + for (Poly* poly = polys; poly; poly = poly->fNext) { + if (apply_fill_type(fillType, poly) && poly->fCount >= 3) { + count += (poly->fCount - 2) * (TESSELLATOR_WIREFRAME ? 6 : 3); + } + } + return count; +} + +int64_t count_outer_mesh_points(const VertexList& outerMesh) { + int64_t count = 0; + for (Vertex* v = outerMesh.fHead; v; v = v->fNext) { + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + count += TESSELLATOR_WIREFRAME ? 12 : 6; + } + } + return count; +} + +void* outer_mesh_to_triangles(const VertexList& outerMesh, bool emitCoverage, void* data) { + for (Vertex* v = outerMesh.fHead; v; v = v->fNext) { + for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) { + Vertex* v0 = e->fTop; + Vertex* v1 = e->fBottom; + Vertex* v2 = e->fBottom->fPartner; + Vertex* v3 = e->fTop->fPartner; + data = emit_triangle(v0, v1, v2, emitCoverage, data); + data = emit_triangle(v0, v2, v3, emitCoverage, data); + } + } + return data; +} + +} // namespace + +namespace GrTessellator { + +// Stage 6: Triangulate the monotone polygons into a vertex buffer. + +int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds, + VertexAllocator* vertexAllocator, bool antialias, bool* isLinear) { + int contourCnt = get_contour_count(path, tolerance); + if (contourCnt <= 0) { + *isLinear = true; + return 0; + } + SkArenaAlloc alloc(kArenaChunkSize); + VertexList outerMesh; + Poly* polys = path_to_polys(path, tolerance, clipBounds, contourCnt, alloc, antialias, + isLinear, &outerMesh); + SkPath::FillType fillType = antialias ? SkPath::kWinding_FillType : path.getFillType(); + int64_t count64 = count_points(polys, fillType); + if (antialias) { + count64 += count_outer_mesh_points(outerMesh); + } + if (0 == count64 || count64 > SK_MaxS32) { + return 0; + } + int count = count64; + + void* verts = vertexAllocator->lock(count); + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return 0; + } + + TESS_LOG("emitting %d verts\n", count); + void* end = polys_to_triangles(polys, fillType, antialias, verts); + end = outer_mesh_to_triangles(outerMesh, true, end); + + int actualCount = static_cast((static_cast(end) - static_cast(verts)) + / vertexAllocator->stride()); + SkASSERT(actualCount <= count); + vertexAllocator->unlock(actualCount); + return actualCount; +} + +int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds, + GrTessellator::WindingVertex** verts) { + int contourCnt = get_contour_count(path, tolerance); + if (contourCnt <= 0) { + *verts = nullptr; + return 0; + } + SkArenaAlloc alloc(kArenaChunkSize); + bool isLinear; + Poly* polys = path_to_polys(path, tolerance, clipBounds, contourCnt, alloc, false, &isLinear, + nullptr); + SkPath::FillType fillType = path.getFillType(); + int64_t count64 = count_points(polys, fillType); + if (0 == count64 || count64 > SK_MaxS32) { + *verts = nullptr; + return 0; + } + int count = count64; + + *verts = new GrTessellator::WindingVertex[count]; + GrTessellator::WindingVertex* vertsEnd = *verts; + SkPoint* points = new SkPoint[count]; + SkPoint* pointsEnd = points; + for (Poly* poly = polys; poly; poly = poly->fNext) { + if (apply_fill_type(fillType, poly)) { + SkPoint* start = pointsEnd; + pointsEnd = static_cast(poly->emit(false, pointsEnd)); + while (start != pointsEnd) { + vertsEnd->fPos = *start; + vertsEnd->fWinding = poly->fWinding; + ++start; + ++vertsEnd; + } + } + } + int actualCount = static_cast(vertsEnd - *verts); + SkASSERT(actualCount <= count); + SkASSERT(pointsEnd - points == actualCount); + delete[] points; + return actualCount; +} + +} // namespace diff --git a/gfx/skia/skia/src/gpu/GrTessellator.h b/gfx/skia/skia/src/gpu/GrTessellator.h new file mode 100644 index 0000000000..e892a19b14 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTessellator.h @@ -0,0 +1,53 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTessellator_DEFINED +#define GrTessellator_DEFINED + +#include "include/core/SkPoint.h" +#include "include/private/SkColorData.h" +#include "src/gpu/GrColor.h" + +class SkPath; +struct SkRect; + +/** + * Provides utility functions for converting paths to a collection of triangles. + */ + +#define TESSELLATOR_WIREFRAME 0 + +namespace GrTessellator { + +class VertexAllocator { +public: + VertexAllocator(size_t stride) : fStride(stride) {} + virtual ~VertexAllocator() {} + virtual void* lock(int vertexCount) = 0; + virtual void unlock(int actualCount) = 0; + size_t stride() const { return fStride; } +private: + size_t fStride; +}; + +struct WindingVertex { + SkPoint fPos; + int fWinding; +}; + +// Triangulates a path to an array of vertices. Each triangle is represented as a set of three +// WindingVertex entries, each of which contains the position and winding count (which is the same +// for all three vertices of a triangle). The 'verts' out parameter is set to point to the resultant +// vertex array. CALLER IS RESPONSIBLE for deleting this buffer to avoid a memory leak! +int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds, + WindingVertex** verts); + +int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds, + VertexAllocator*, bool antialias, bool *isLinear); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTestUtils.cpp b/gfx/skia/skia/src/gpu/GrTestUtils.cpp new file mode 100644 index 0000000000..42aba5ef53 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTestUtils.cpp @@ -0,0 +1,350 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTestUtils.h" + +#include "include/core/SkMatrix.h" +#include "include/core/SkPath.h" +#include "include/core/SkRRect.h" +#include "include/gpu/GrContext.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkRectPriv.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrProcessorUnitTest.h" +#include "src/gpu/GrStyle.h" +#include "src/utils/SkDashPathPriv.h" + +#if GR_TEST_UTILS + +static const SkMatrix& test_matrix(SkRandom* random, + bool includeNonPerspective, + bool includePerspective) { + static SkMatrix gMatrices[5]; + static const int kPerspectiveCount = 1; + static bool gOnce; + if (!gOnce) { + gOnce = true; + gMatrices[0].reset(); + gMatrices[1].setTranslate(SkIntToScalar(-100), SkIntToScalar(100)); + gMatrices[2].setRotate(SkIntToScalar(17)); + gMatrices[3].setRotate(SkIntToScalar(185)); + gMatrices[3].postTranslate(SkIntToScalar(66), SkIntToScalar(-33)); + gMatrices[3].postScale(SkIntToScalar(2), SK_ScalarHalf); + + // Perspective matrices + gMatrices[4].setRotate(SkIntToScalar(215)); + gMatrices[4].set(SkMatrix::kMPersp0, 0.00013f); + gMatrices[4].set(SkMatrix::kMPersp1, -0.000039f); + } + + uint32_t count = static_cast(SK_ARRAY_COUNT(gMatrices)); + if (includeNonPerspective && includePerspective) { + return gMatrices[random->nextULessThan(count)]; + } else if (!includeNonPerspective) { + return gMatrices[count - 1 - random->nextULessThan(kPerspectiveCount)]; + } else { + SkASSERT(includeNonPerspective && !includePerspective); + return gMatrices[random->nextULessThan(count - kPerspectiveCount)]; + } +} + +namespace GrTest { +const SkMatrix& TestMatrix(SkRandom* random) { return test_matrix(random, true, true); } + +const SkMatrix& TestMatrixPreservesRightAngles(SkRandom* random) { + static SkMatrix gMatrices[5]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + // identity + gMatrices[0].reset(); + // translation + gMatrices[1].setTranslate(SkIntToScalar(-100), SkIntToScalar(100)); + // scale + gMatrices[2].setScale(SkIntToScalar(17), SkIntToScalar(17)); + // scale + translation + gMatrices[3].setScale(SkIntToScalar(-17), SkIntToScalar(-17)); + gMatrices[3].postTranslate(SkIntToScalar(66), SkIntToScalar(-33)); + // orthogonal basis vectors + gMatrices[4].reset(); + gMatrices[4].setScale(SkIntToScalar(-1), SkIntToScalar(-1)); + gMatrices[4].setRotate(47); + + for (size_t i = 0; i < SK_ARRAY_COUNT(gMatrices); i++) { + SkASSERT(gMatrices[i].preservesRightAngles()); + } + } + return gMatrices[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gMatrices)))]; +} + +const SkMatrix& TestMatrixRectStaysRect(SkRandom* random) { + static SkMatrix gMatrices[6]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + // identity + gMatrices[0].reset(); + // translation + gMatrices[1].setTranslate(SkIntToScalar(-100), SkIntToScalar(100)); + // scale + gMatrices[2].setScale(SkIntToScalar(17), SkIntToScalar(17)); + // scale + translation + gMatrices[3].setScale(SkIntToScalar(-17), SkIntToScalar(-17)); + gMatrices[3].postTranslate(SkIntToScalar(66), SkIntToScalar(-33)); + // reflection + gMatrices[4].setScale(SkIntToScalar(-1), SkIntToScalar(-1)); + // 90 degress rotation + gMatrices[5].setRotate(90); + + for (size_t i = 0; i < SK_ARRAY_COUNT(gMatrices); i++) { + SkASSERT(gMatrices[i].rectStaysRect()); + } + } + return gMatrices[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gMatrices)))]; +} + +const SkMatrix& TestMatrixInvertible(SkRandom* random) { return test_matrix(random, true, false); } +const SkMatrix& TestMatrixPerspective(SkRandom* random) { return test_matrix(random, false, true); } + +void TestWrapModes(SkRandom* random, GrSamplerState::WrapMode wrapModes[2]) { + static const GrSamplerState::WrapMode kWrapModes[] = { + GrSamplerState::WrapMode::kClamp, + GrSamplerState::WrapMode::kRepeat, + GrSamplerState::WrapMode::kMirrorRepeat, + }; + wrapModes[0] = kWrapModes[random->nextULessThan(SK_ARRAY_COUNT(kWrapModes))]; + wrapModes[1] = kWrapModes[random->nextULessThan(SK_ARRAY_COUNT(kWrapModes))]; +} +const SkRect& TestRect(SkRandom* random) { + static SkRect gRects[7]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + gRects[0] = SkRect::MakeWH(1.f, 1.f); + gRects[1] = SkRect::MakeWH(1.0f, 256.0f); + gRects[2] = SkRect::MakeWH(256.0f, 1.0f); + gRects[3] = SkRectPriv::MakeLargest(); + gRects[4] = SkRect::MakeLTRB(-65535.0f, -65535.0f, 65535.0f, 65535.0f); + gRects[5] = SkRect::MakeLTRB(-10.0f, -10.0f, 10.0f, 10.0f); + } + return gRects[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gRects)))]; +} + +// Just some simple rects for code which expects its input very sanitized +const SkRect& TestSquare(SkRandom* random) { + static SkRect gRects[2]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + gRects[0] = SkRect::MakeWH(128.f, 128.f); + gRects[1] = SkRect::MakeWH(256.0f, 256.0f); + } + return gRects[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gRects)))]; +} + +const SkRRect& TestRRectSimple(SkRandom* random) { + static SkRRect gRRect[2]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + SkRect rectangle = SkRect::MakeWH(10.f, 20.f); + // true round rect with circular corners + gRRect[0].setRectXY(rectangle, 1.f, 1.f); + // true round rect with elliptical corners + gRRect[1].setRectXY(rectangle, 2.0f, 1.0f); + + for (size_t i = 0; i < SK_ARRAY_COUNT(gRRect); i++) { + SkASSERT(gRRect[i].isSimple()); + } + } + return gRRect[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gRRect)))]; +} + +const SkPath& TestPath(SkRandom* random) { + static SkPath gPath[7]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + // line + gPath[0].moveTo(0.f, 0.f); + gPath[0].lineTo(10.f, 10.f); + // quad + gPath[1].moveTo(0.f, 0.f); + gPath[1].quadTo(10.f, 10.f, 20.f, 20.f); + // conic + gPath[2].moveTo(0.f, 0.f); + gPath[2].conicTo(10.f, 10.f, 20.f, 20.f, 1.f); + // cubic + gPath[3].moveTo(0.f, 0.f); + gPath[3].cubicTo(10.f, 10.f, 20.f, 20.f, 30.f, 30.f); + // all three + gPath[4].moveTo(0.f, 0.f); + gPath[4].lineTo(10.f, 10.f); + gPath[4].quadTo(10.f, 10.f, 20.f, 20.f); + gPath[4].conicTo(10.f, 10.f, 20.f, 20.f, 1.f); + gPath[4].cubicTo(10.f, 10.f, 20.f, 20.f, 30.f, 30.f); + // convex + gPath[5].moveTo(0.0f, 0.0f); + gPath[5].lineTo(10.0f, 0.0f); + gPath[5].lineTo(10.0f, 10.0f); + gPath[5].lineTo(0.0f, 10.0f); + gPath[5].close(); + // concave + gPath[6].moveTo(0.0f, 0.0f); + gPath[6].lineTo(5.0f, 5.0f); + gPath[6].lineTo(10.0f, 0.0f); + gPath[6].lineTo(10.0f, 10.0f); + gPath[6].lineTo(0.0f, 10.0f); + gPath[6].close(); + } + + return gPath[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gPath)))]; +} + +const SkPath& TestPathConvex(SkRandom* random) { + static SkPath gPath[3]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + // narrow rect + gPath[0].moveTo(-1.5f, -50.0f); + gPath[0].lineTo(-1.5f, -50.0f); + gPath[0].lineTo( 1.5f, -50.0f); + gPath[0].lineTo( 1.5f, 50.0f); + gPath[0].lineTo(-1.5f, 50.0f); + // degenerate + gPath[1].moveTo(-0.025f, -0.025f); + gPath[1].lineTo(-0.025f, -0.025f); + gPath[1].lineTo( 0.025f, -0.025f); + gPath[1].lineTo( 0.025f, 0.025f); + gPath[1].lineTo(-0.025f, 0.025f); + // clipped triangle + gPath[2].moveTo(-10.0f, -50.0f); + gPath[2].lineTo(-10.0f, -50.0f); + gPath[2].lineTo( 10.0f, -50.0f); + gPath[2].lineTo( 50.0f, 31.0f); + gPath[2].lineTo( 40.0f, 50.0f); + gPath[2].lineTo(-40.0f, 50.0f); + gPath[2].lineTo(-50.0f, 31.0f); + + for (size_t i = 0; i < SK_ARRAY_COUNT(gPath); i++) { + SkASSERT(SkPath::kConvex_Convexity == gPath[i].getConvexity()); + } + } + + return gPath[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gPath)))]; +} + +static void randomize_stroke_rec(SkStrokeRec* rec, SkRandom* random) { + bool strokeAndFill = random->nextBool(); + SkScalar strokeWidth = random->nextBool() ? 0.f : 1.f; + rec->setStrokeStyle(strokeWidth, strokeAndFill); + + SkPaint::Cap cap = SkPaint::Cap(random->nextULessThan(SkPaint::kCapCount)); + SkPaint::Join join = SkPaint::Join(random->nextULessThan(SkPaint::kJoinCount)); + SkScalar miterLimit = random->nextRangeScalar(1.f, 5.f); + rec->setStrokeParams(cap, join, miterLimit); +} + +SkStrokeRec TestStrokeRec(SkRandom* random) { + SkStrokeRec::InitStyle style = + SkStrokeRec::InitStyle(random->nextULessThan(SkStrokeRec::kFill_InitStyle + 1)); + SkStrokeRec rec(style); + randomize_stroke_rec(&rec, random); + return rec; +} + +void TestStyle(SkRandom* random, GrStyle* style) { + SkStrokeRec::InitStyle initStyle = + SkStrokeRec::InitStyle(random->nextULessThan(SkStrokeRec::kFill_InitStyle + 1)); + SkStrokeRec stroke(initStyle); + randomize_stroke_rec(&stroke, random); + sk_sp pe; + if (random->nextBool()) { + int cnt = random->nextRangeU(1, 50) * 2; + std::unique_ptr intervals(new SkScalar[cnt]); + SkScalar sum = 0; + for (int i = 0; i < cnt; i++) { + intervals[i] = random->nextRangeScalar(SkDoubleToScalar(0.01), + SkDoubleToScalar(10.0)); + sum += intervals[i]; + } + SkScalar phase = random->nextRangeScalar(0, sum); + pe = TestDashPathEffect::Make(intervals.get(), cnt, phase); + } + *style = GrStyle(stroke, std::move(pe)); +} + +TestDashPathEffect::TestDashPathEffect(const SkScalar* intervals, int count, SkScalar phase) { + fCount = count; + fIntervals.reset(count); + memcpy(fIntervals.get(), intervals, count * sizeof(SkScalar)); + SkDashPath::CalcDashParameters(phase, intervals, count, &fInitialDashLength, + &fInitialDashIndex, &fIntervalLength, &fPhase); +} + + bool TestDashPathEffect::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect) const { + return SkDashPath::InternalFilter(dst, src, rec, cullRect, fIntervals.get(), fCount, + fInitialDashLength, fInitialDashIndex, fIntervalLength); +} + +SkPathEffect::DashType TestDashPathEffect::onAsADash(DashInfo* info) const { + if (info) { + if (info->fCount >= fCount && info->fIntervals) { + memcpy(info->fIntervals, fIntervals.get(), fCount * sizeof(SkScalar)); + } + info->fCount = fCount; + info->fPhase = fPhase; + } + return kDash_DashType; +} + +sk_sp TestColorSpace(SkRandom* random) { + static sk_sp gColorSpaces[3]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + // No color space (legacy mode) + gColorSpaces[0] = nullptr; + // sRGB or color-spin sRGB + gColorSpaces[1] = SkColorSpace::MakeSRGB(); + gColorSpaces[2] = SkColorSpace::MakeSRGB()->makeColorSpin(); + } + return gColorSpaces[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gColorSpaces)))]; +} + +sk_sp TestColorXform(SkRandom* random) { + // TODO: Add many more kinds of xforms here + static sk_sp gXforms[3]; + static bool gOnce; + if (!gOnce) { + gOnce = true; + sk_sp srgb = SkColorSpace::MakeSRGB(); + sk_sp spin = SkColorSpace::MakeSRGB()->makeColorSpin(); + // No gamut change + gXforms[0] = nullptr; + gXforms[1] = GrColorSpaceXform::Make(srgb.get(), kPremul_SkAlphaType, + spin.get(), kPremul_SkAlphaType); + gXforms[2] = GrColorSpaceXform::Make(spin.get(), kPremul_SkAlphaType, + srgb.get(), kPremul_SkAlphaType); + } + return gXforms[random->nextULessThan(static_cast(SK_ARRAY_COUNT(gXforms)))]; +} + +TestAsFPArgs::TestAsFPArgs(GrProcessorTestData* d) + : fViewMatrixStorage(TestMatrix(d->fRandom)) + , fColorInfoStorage(skstd::make_unique( + GrColorType::kRGBA_8888, kPremul_SkAlphaType, TestColorSpace(d->fRandom))) + , fArgs(d->context(), &fViewMatrixStorage, kNone_SkFilterQuality, fColorInfoStorage.get()) { +} + +TestAsFPArgs::~TestAsFPArgs() {} + +} // namespace GrTest + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTestUtils.h b/gfx/skia/skia/src/gpu/GrTestUtils.h new file mode 100644 index 0000000000..b3e71a29a3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTestUtils.h @@ -0,0 +1,157 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTestUtils_DEFINED +#define GrTestUtils_DEFINED + +#include "include/core/SkTypes.h" + +#if GR_TEST_UTILS + +#include "include/core/SkPathEffect.h" +#include "include/core/SkStrokeRec.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTemplates.h" +#include "include/utils/SkRandom.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrFPArgs.h" +#include "src/gpu/GrSamplerState.h" +#include "src/shaders/SkShaderBase.h" + +class GrColorInfo; +class GrColorSpaceXform; +struct GrProcessorTestData; +class GrStyle; +class SkMatrix; +class SkPath; +class SkRRect; +struct SkRect; + +namespace GrTest { +/** + * Helpers for use in Test functions. + */ +const SkMatrix& TestMatrix(SkRandom*); +const SkMatrix& TestMatrixPreservesRightAngles(SkRandom*); +const SkMatrix& TestMatrixRectStaysRect(SkRandom*); +const SkMatrix& TestMatrixInvertible(SkRandom*); +const SkMatrix& TestMatrixPerspective(SkRandom*); +void TestWrapModes(SkRandom*, GrSamplerState::WrapMode[2]); +const SkRect& TestRect(SkRandom*); +const SkRect& TestSquare(SkRandom*); +const SkRRect& TestRRectSimple(SkRandom*); +const SkPath& TestPath(SkRandom*); +const SkPath& TestPathConvex(SkRandom*); +SkStrokeRec TestStrokeRec(SkRandom*); +/** Creates styles with dash path effects and null path effects */ +void TestStyle(SkRandom*, GrStyle*); +sk_sp TestColorSpace(SkRandom*); +sk_sp TestColorXform(SkRandom*); + +class TestAsFPArgs { +public: + TestAsFPArgs(GrProcessorTestData*); + ~TestAsFPArgs(); + const GrFPArgs& args() const { return fArgs; } + +private: + SkMatrix fViewMatrixStorage; + std::unique_ptr fColorInfoStorage; + GrFPArgs fArgs; +}; + +// We have a simplified dash path effect here to avoid relying on SkDashPathEffect which +// is in the optional build target effects. +class TestDashPathEffect : public SkPathEffect { +public: + static sk_sp Make(const SkScalar* intervals, int count, SkScalar phase) { + return sk_sp(new TestDashPathEffect(intervals, count, phase)); + } + + Factory getFactory() const override { return nullptr; } + const char* getTypeName() const override { return nullptr; } + +protected: + bool onFilterPath(SkPath* dst, const SkPath&, SkStrokeRec* , const SkRect*) const override; + DashType onAsADash(DashInfo* info) const override; + +private: + TestDashPathEffect(const SkScalar* intervals, int count, SkScalar phase); + + int fCount; + SkAutoTArray fIntervals; + SkScalar fPhase; + SkScalar fInitialDashLength; + int fInitialDashIndex; + SkScalar fIntervalLength; +}; + +} // namespace GrTest + +static inline GrColor GrRandomColor(SkRandom* random) { + // There are only a few cases of random colors which interest us + enum ColorMode { + kAllOnes_ColorMode, + kAllZeros_ColorMode, + kAlphaOne_ColorMode, + kRandom_ColorMode, + kLast_ColorMode = kRandom_ColorMode + }; + + ColorMode colorMode = ColorMode(random->nextULessThan(kLast_ColorMode + 1)); + GrColor color SK_INIT_TO_AVOID_WARNING; + switch (colorMode) { + case kAllOnes_ColorMode: + color = GrColorPackRGBA(0xFF, 0xFF, 0xFF, 0xFF); + break; + case kAllZeros_ColorMode: + color = GrColorPackRGBA(0, 0, 0, 0); + break; + case kAlphaOne_ColorMode: + color = GrColorPackRGBA(random->nextULessThan(256), + random->nextULessThan(256), + random->nextULessThan(256), + 0xFF); + break; + case kRandom_ColorMode: { + uint8_t alpha = random->nextULessThan(256); + color = GrColorPackRGBA(random->nextRangeU(0, alpha), + random->nextRangeU(0, alpha), + random->nextRangeU(0, alpha), + alpha); + break; + } + } + return color; +} + +static inline uint8_t GrRandomCoverage(SkRandom* random) { + enum CoverageMode { + kZero_CoverageMode, + kAllOnes_CoverageMode, + kRandom_CoverageMode, + kLast_CoverageMode = kRandom_CoverageMode + }; + + CoverageMode colorMode = CoverageMode(random->nextULessThan(kLast_CoverageMode + 1)); + uint8_t coverage SK_INIT_TO_AVOID_WARNING; + switch (colorMode) { + case kZero_CoverageMode: + coverage = 0; + break; + case kAllOnes_CoverageMode: + coverage = 0xff; + break; + case kRandom_CoverageMode: + coverage = random->nextULessThan(256); + break; + } + return coverage; +} + +#endif +#endif diff --git a/gfx/skia/skia/src/gpu/GrTexture.cpp b/gfx/skia/skia/src/gpu/GrTexture.cpp new file mode 100644 index 0000000000..df0f2ebbbd --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTexture.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMath.h" +#include "include/core/SkTypes.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrResourceKey.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTexturePriv.h" + +void GrTexture::markMipMapsDirty() { + if (GrMipMapsStatus::kValid == fMipMapsStatus) { + fMipMapsStatus = GrMipMapsStatus::kDirty; + } +} + +void GrTexture::markMipMapsClean() { + SkASSERT(GrMipMapsStatus::kNotAllocated != fMipMapsStatus); + fMipMapsStatus = GrMipMapsStatus::kValid; +} + +size_t GrTexture::onGpuMemorySize() const { + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + 1, this->texturePriv().mipMapped()); +} + +///////////////////////////////////////////////////////////////////////////// +GrTexture::GrTexture(GrGpu* gpu, const SkISize& size, GrPixelConfig config, GrProtected isProtected, + GrTextureType textureType, GrMipMapsStatus mipMapsStatus) + : INHERITED(gpu, size, config, isProtected) + , fTextureType(textureType) + , fMipMapsStatus(mipMapsStatus) { + if (GrMipMapsStatus::kNotAllocated == fMipMapsStatus) { + fMaxMipMapLevel = 0; + } else { + fMaxMipMapLevel = SkMipMap::ComputeLevelCount(this->width(), this->height()); + } +} + +bool GrTexture::StealBackendTexture(sk_sp texture, + GrBackendTexture* backendTexture, + SkImage::BackendTextureReleaseProc* releaseProc) { + if (!texture->unique()) { + return false; + } + + if (!texture->onStealBackendTexture(backendTexture, releaseProc)) { + return false; + } +#ifdef SK_DEBUG + GrResourceCache* cache = texture->getContext()->priv().getResourceCache(); + int preCount = cache->getResourceCount(); +#endif + // Ensure that the texture will be released by the cache when we drop the last ref. + // A texture that has no refs and no keys should be immediately removed. + if (texture->getUniqueKey().isValid()) { + texture->resourcePriv().removeUniqueKey(); + } + if (texture->resourcePriv().getScratchKey().isValid()) { + texture->resourcePriv().removeScratchKey(); + } +#ifdef SK_DEBUG + texture.reset(); + int postCount = cache->getResourceCount(); + SkASSERT(postCount < preCount); +#endif + return true; +} + +void GrTexture::computeScratchKey(GrScratchKey* key) const { + if (!this->getGpu()->caps()->isFormatCompressed(this->backendFormat())) { + int sampleCount = 1; + GrRenderable renderable = GrRenderable::kNo; + if (const auto* rt = this->asRenderTarget()) { + sampleCount = rt->numSamples(); + renderable = GrRenderable::kYes; + } + auto isProtected = this->isProtected() ? GrProtected::kYes : GrProtected::kNo; + GrTexturePriv::ComputeScratchKey(this->config(), this->width(), this->height(), renderable, + sampleCount, this->texturePriv().mipMapped(), isProtected, + key); + } +} + +void GrTexturePriv::ComputeScratchKey(GrPixelConfig config, + int width, + int height, + GrRenderable renderable, + int sampleCnt, + GrMipMapped mipMapped, + GrProtected isProtected, + GrScratchKey* key) { + static const GrScratchKey::ResourceType kType = GrScratchKey::GenerateResourceType(); + SkASSERT(width > 0); + SkASSERT(height > 0); + SkASSERT(sampleCnt > 0); + SkASSERT(1 == sampleCnt || renderable == GrRenderable::kYes); + + // make sure desc.fConfig fits in 5 bits + SkASSERT(sk_float_log2(kLast_GrPixelConfig) <= 5); + SkASSERT(static_cast(config) < (1 << 5)); + SkASSERT(static_cast(mipMapped) <= 1); + SkASSERT(static_cast(isProtected) <= 1); + SkASSERT(static_cast(renderable) <= 1); + SkASSERT(static_cast(sampleCnt) < (1 << (32 - 8))); + + GrScratchKey::Builder builder(key, kType, 3); + builder[0] = width; + builder[1] = height; + builder[2] = (static_cast(config) << 0) + | (static_cast(mipMapped) << 5) + | (static_cast(isProtected) << 6) + | (static_cast(renderable) << 7) + | (static_cast(sampleCnt) << 8); +} diff --git a/gfx/skia/skia/src/gpu/GrTextureAdjuster.cpp b/gfx/skia/skia/src/gpu/GrTextureAdjuster.cpp new file mode 100644 index 0000000000..9b6ce36311 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureAdjuster.cpp @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrTextureAdjuster.h" +#include "src/gpu/SkGr.h" + +GrTextureAdjuster::GrTextureAdjuster(GrRecordingContext* context, + sk_sp original, + GrColorType colorType, + SkAlphaType alphaType, + uint32_t uniqueID, + SkColorSpace* cs, + bool useDecal) + : INHERITED(context, original->width(), original->height(), + GrColorInfo(colorType, alphaType, sk_ref_sp(cs)), useDecal) + , fOriginal(std::move(original)) + , fUniqueID(uniqueID) {} + +GrTextureAdjuster::GrTextureAdjuster(GrRecordingContext* context, + sk_sp original, + const GrColorInfo& colorInfo, + uint32_t uniqueID, + bool useDecal) + : INHERITED(context, original->width(), original->height(), colorInfo, useDecal) + , fOriginal(std::move(original)) + , fUniqueID(uniqueID) {} + +void GrTextureAdjuster::makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) { + // Destination color space is irrelevant - we already have a texture so we're just sub-setting + GrUniqueKey baseKey; + GrMakeKeyFromImageID(&baseKey, fUniqueID, SkIRect::MakeWH(this->width(), this->height())); + MakeCopyKeyFromOrigKey(baseKey, params, copyKey); +} + +void GrTextureAdjuster::didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) { + // We don't currently have a mechanism for notifications on Images! +} + +sk_sp GrTextureAdjuster::refTextureProxyCopy(const CopyParams& copyParams, + bool willBeMipped) { + GrProxyProvider* proxyProvider = this->context()->priv().proxyProvider(); + + GrUniqueKey key; + this->makeCopyKey(copyParams, &key); + sk_sp cachedCopy; + if (key.isValid()) { + cachedCopy = proxyProvider->findOrCreateProxyByUniqueKey(key, this->colorType(), + this->originalProxy()->origin()); + if (cachedCopy && (!willBeMipped || GrMipMapped::kYes == cachedCopy->mipMapped())) { + return cachedCopy; + } + } + + sk_sp proxy = this->originalProxyRef(); + + sk_sp copy = CopyOnGpu(this->context(), std::move(proxy), this->colorType(), + copyParams, willBeMipped); + if (copy) { + if (key.isValid()) { + SkASSERT(copy->origin() == this->originalProxy()->origin()); + if (cachedCopy) { + SkASSERT(GrMipMapped::kYes == copy->mipMapped() && + GrMipMapped::kNo == cachedCopy->mipMapped()); + // If we had a cachedProxy, that means there already is a proxy in the cache which + // matches the key, but it does not have mip levels and we require them. Thus we + // must remove the unique key from that proxy. + SkASSERT(cachedCopy->getUniqueKey() == key); + proxyProvider->removeUniqueKeyFromProxy(cachedCopy.get()); + } + proxyProvider->assignUniqueKeyToProxy(key, copy.get()); + this->didCacheCopy(key, proxyProvider->contextID()); + } + } + return copy; +} + +sk_sp GrTextureAdjuster::onRefTextureProxyForParams( + const GrSamplerState& params, + bool willBeMipped, + SkScalar scaleAdjust[2]) { + sk_sp proxy = this->originalProxyRef(); + CopyParams copyParams; + + if (this->context()->priv().abandoned()) { + // The texture was abandoned. + return nullptr; + } + + SkASSERT(this->width() <= this->context()->priv().caps()->maxTextureSize() && + this->height() <= this->context()->priv().caps()->maxTextureSize()); + + bool needsCopyForMipsOnly = false; + if (!params.isRepeated() || + !GrGpu::IsACopyNeededForRepeatWrapMode(this->context()->priv().caps(), proxy.get(), + proxy->width(), proxy->height(), params.filter(), + ©Params, scaleAdjust)) { + needsCopyForMipsOnly = GrGpu::IsACopyNeededForMips(this->context()->priv().caps(), + proxy.get(), params.filter(), + ©Params); + if (!needsCopyForMipsOnly) { + return proxy; + } + } + + sk_sp result = this->refTextureProxyCopy(copyParams, willBeMipped); + if (!result && needsCopyForMipsOnly) { + // If we were unable to make a copy and we only needed a copy for mips, then we will return + // the source texture here and require that the GPU backend is able to fall back to using + // bilerp if mips are required. + return this->originalProxyRef(); + } + return result; +} + +std::unique_ptr GrTextureAdjuster::createFragmentProcessor( + const SkMatrix& origTextureMatrix, + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) { + SkMatrix textureMatrix = origTextureMatrix; + + SkScalar scaleAdjust[2] = { 1.0f, 1.0f }; + sk_sp proxy( + this->refTextureProxyForParams(filterOrNullForBicubic, scaleAdjust)); + if (!proxy) { + return nullptr; + } + // If we made a copy then we only copied the contentArea, in which case the new texture is all + // content. + if (proxy.get() != this->originalProxy()) { + textureMatrix.postScale(scaleAdjust[0], scaleAdjust[1]); + } + + SkRect domain; + DomainMode domainMode = + DetermineDomainMode(constraintRect, filterConstraint, coordsLimitedToConstraintRect, + proxy.get(), filterOrNullForBicubic, &domain); + if (kTightCopy_DomainMode == domainMode) { + // TODO: Copy the texture and adjust the texture matrix (both parts need to consider + // non-int constraint rect) + // For now: treat as bilerp and ignore what goes on above level 0. + + // We only expect MIP maps to require a tight copy. + SkASSERT(filterOrNullForBicubic && + GrSamplerState::Filter::kMipMap == *filterOrNullForBicubic); + static const GrSamplerState::Filter kBilerp = GrSamplerState::Filter::kBilerp; + domainMode = + DetermineDomainMode(constraintRect, filterConstraint, coordsLimitedToConstraintRect, + proxy.get(), &kBilerp, &domain); + SkASSERT(kTightCopy_DomainMode != domainMode); + } + SkASSERT(kNoDomain_DomainMode == domainMode || + (domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom)); + return this->createFragmentProcessorForDomainAndFilter( + std::move(proxy), textureMatrix, domainMode, domain, filterOrNullForBicubic); +} diff --git a/gfx/skia/skia/src/gpu/GrTextureAdjuster.h b/gfx/skia/skia/src/gpu/GrTextureAdjuster.h new file mode 100644 index 0000000000..ce93eee61e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureAdjuster.h @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureAdjuster_DEFINED +#define GrTextureAdjuster_DEFINED + +#include "src/core/SkTLazy.h" +#include "src/gpu/GrTextureProducer.h" +#include "src/gpu/GrTextureProxy.h" + +class GrRecordingContext; + +/** + * Base class for sources that start out as textures. Optionally allows for a content area subrect. + * The intent is not to use content area for subrect rendering. Rather, the pixels outside the + * content area have undefined values and shouldn't be read *regardless* of filtering mode or + * the SkCanvas::SrcRectConstraint used for subrect draws. + */ +class GrTextureAdjuster : public GrTextureProducer { +public: + std::unique_ptr createFragmentProcessor( + const SkMatrix& textureMatrix, + const SkRect& constraintRect, + FilterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) override; + + GrTextureAdjuster(GrRecordingContext*, sk_sp, GrColorType, SkAlphaType, + uint32_t uniqueID, SkColorSpace*, bool useDecal = false); + + GrTextureAdjuster(GrRecordingContext*, sk_sp, const GrColorInfo&, + uint32_t uniqueID, bool useDecal = false); + +protected: + void makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) override; + void didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) override; + + GrTextureProxy* originalProxy() const { return fOriginal.get(); } + sk_sp originalProxyRef() const { return fOriginal; } + +private: + sk_sp onRefTextureProxyForParams(const GrSamplerState&, + bool willBeMipped, + SkScalar scaleAdjust[2]) override; + + sk_sp refTextureProxyCopy(const CopyParams& copyParams, bool willBeMipped); + + sk_sp fOriginal; + uint32_t fUniqueID; + + typedef GrTextureProducer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureContext.cpp b/gfx/skia/skia/src/gpu/GrTextureContext.cpp new file mode 100644 index 0000000000..ccdcc800e3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureContext.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTextureContext.h" + +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrRecordingContextPriv.h" + +#define ASSERT_SINGLE_OWNER \ + SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());) +#define RETURN_FALSE_IF_ABANDONED if (this->drawingManager()->wasAbandoned()) { return false; } + +GrTextureContext::GrTextureContext(GrRecordingContext* context, + sk_sp textureProxy, + GrColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace) + : GrSurfaceContext(context, colorType, alphaType, std::move(colorSpace)) + , fTextureProxy(std::move(textureProxy)) { + SkDEBUGCODE(this->validate();) +} + +#ifdef SK_DEBUG +void GrTextureContext::validate() const { + SkASSERT(fTextureProxy); + fTextureProxy->validate(fContext); + SkASSERT(fContext->priv().caps()->areColorTypeAndFormatCompatible( + this->colorInfo().colorType(), fTextureProxy->backendFormat())); +} +#endif + +GrTextureContext::~GrTextureContext() { + ASSERT_SINGLE_OWNER +} + +GrRenderTargetProxy* GrTextureContext::asRenderTargetProxy() { + // If the proxy can return an RTProxy it should've been wrapped in a RTContext + SkASSERT(!fTextureProxy->asRenderTargetProxy()); + return nullptr; +} + +sk_sp GrTextureContext::asRenderTargetProxyRef() { + // If the proxy can return an RTProxy it should've been wrapped in a RTContext + SkASSERT(!fTextureProxy->asRenderTargetProxy()); + return nullptr; +} diff --git a/gfx/skia/skia/src/gpu/GrTextureContext.h b/gfx/skia/skia/src/gpu/GrTextureContext.h new file mode 100644 index 0000000000..626c2d3451 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureContext.h @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureContext_DEFINED +#define GrTextureContext_DEFINED + +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/GrTextureProxy.h" + +class GrContext; +class GrDrawingManager; +class GrSurface; +class GrTextureProxy; +struct SkIPoint; +struct SkIRect; + +/** + * A helper object to orchestrate commands (currently just copies) for GrSurfaces that are + * GrTextures and not GrRenderTargets. + */ +class GrTextureContext : public GrSurfaceContext { +public: + ~GrTextureContext() override; + + GrSurfaceProxy* asSurfaceProxy() override { return fTextureProxy.get(); } + const GrSurfaceProxy* asSurfaceProxy() const override { return fTextureProxy.get(); } + sk_sp asSurfaceProxyRef() override { return fTextureProxy; } + + GrTextureProxy* asTextureProxy() override { return fTextureProxy.get(); } + const GrTextureProxy* asTextureProxy() const override { return fTextureProxy.get(); } + sk_sp asTextureProxyRef() override { return fTextureProxy; } + + GrRenderTargetProxy* asRenderTargetProxy() override; + sk_sp asRenderTargetProxyRef() override; + +protected: + GrTextureContext(GrRecordingContext*, + sk_sp, + GrColorType, + SkAlphaType, + sk_sp); + + SkDEBUGCODE(void validate() const override;) + +private: + friend class GrDrawingManager; // for ctor + + sk_sp fTextureProxy; + + typedef GrSurfaceContext INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureMaker.cpp b/gfx/skia/skia/src/gpu/GrTextureMaker.cpp new file mode 100644 index 0000000000..fa428ed0fb --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureMaker.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTextureMaker.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" + +sk_sp GrTextureMaker::onRefTextureProxyForParams(const GrSamplerState& params, + bool willBeMipped, + SkScalar scaleAdjust[2]) { + if (this->width() > this->context()->priv().caps()->maxTextureSize() || + this->height() > this->context()->priv().caps()->maxTextureSize()) { + return nullptr; + } + + CopyParams copyParams; + + sk_sp original(this->refOriginalTextureProxy(willBeMipped, + AllowedTexGenType::kCheap)); + bool needsCopyForMipsOnly = false; + if (original) { + if (!params.isRepeated() || + !GrGpu::IsACopyNeededForRepeatWrapMode(this->context()->priv().caps(), original.get(), + original->width(), original->height(), + params.filter(), ©Params, scaleAdjust)) { + needsCopyForMipsOnly = GrGpu::IsACopyNeededForMips(this->context()->priv().caps(), + original.get(), params.filter(), + ©Params); + if (!needsCopyForMipsOnly) { + return original; + } + } + } else { + if (!params.isRepeated() || + !GrGpu::IsACopyNeededForRepeatWrapMode(this->context()->priv().caps(), nullptr, + this->width(), this->height(), + params.filter(), ©Params, scaleAdjust)) { + return this->refOriginalTextureProxy(willBeMipped, AllowedTexGenType::kAny); + } + } + + GrProxyProvider* proxyProvider = this->context()->priv().proxyProvider(); + + GrSurfaceOrigin origOrigin = original ? original->origin() : kTopLeft_GrSurfaceOrigin; + GrUniqueKey copyKey; + this->makeCopyKey(copyParams, ©Key); + sk_sp cachedProxy; + if (copyKey.isValid()) { + cachedProxy = + proxyProvider->findOrCreateProxyByUniqueKey(copyKey, this->colorType(), origOrigin); + if (cachedProxy && (!willBeMipped || GrMipMapped::kYes == cachedProxy->mipMapped())) { + return cachedProxy; + } + } + + sk_sp source; + if (original) { + source = std::move(original); + } else if (cachedProxy) { + source = cachedProxy; + } else { + // Since we will be copying this texture there is no reason to make it mipped + source = this->refOriginalTextureProxy(false, AllowedTexGenType::kAny); + } + + if (!source) { + return nullptr; + } + + sk_sp result = + CopyOnGpu(this->context(), source, this->colorType(), copyParams, willBeMipped); + + if (!result) { + // If we were unable to make a copy and we only needed a copy for mips, then we will return + // the source texture here and require that the GPU backend is able to fall back to using + // bilerp if mips are required. + if (needsCopyForMipsOnly) { + return source; + } + return nullptr; + } + + if (copyKey.isValid()) { + SkASSERT(result->origin() == origOrigin); + if (cachedProxy) { + SkASSERT(GrMipMapped::kYes == result->mipMapped() && + GrMipMapped::kNo == cachedProxy->mipMapped()); + // If we had a cachedProxy, that means there already is a proxy in the cache which + // matches the key, but it does not have mip levels and we require them. Thus we must + // remove the unique key from that proxy. + SkASSERT(cachedProxy->getUniqueKey() == copyKey); + proxyProvider->removeUniqueKeyFromProxy(cachedProxy.get()); + } + proxyProvider->assignUniqueKeyToProxy(copyKey, result.get()); + this->didCacheCopy(copyKey, proxyProvider->contextID()); + } + return result; +} + +std::unique_ptr GrTextureMaker::createFragmentProcessor( + const SkMatrix& textureMatrix, + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) { + const GrSamplerState::Filter* fmForDetermineDomain = filterOrNullForBicubic; + if (filterOrNullForBicubic && GrSamplerState::Filter::kMipMap == *filterOrNullForBicubic && + kYes_FilterConstraint == filterConstraint) { + // TODO: Here we should force a copy restricted to the constraintRect since MIP maps will + // read outside the constraint rect. However, as in the adjuster case, we aren't currently + // doing that. + // We instead we compute the domain as though were bilerping which is only correct if we + // only sample level 0. + static const GrSamplerState::Filter kBilerp = GrSamplerState::Filter::kBilerp; + fmForDetermineDomain = &kBilerp; + } + + SkScalar scaleAdjust[2] = { 1.0f, 1.0f }; + sk_sp proxy(this->refTextureProxyForParams(filterOrNullForBicubic, + scaleAdjust)); + if (!proxy) { + return nullptr; + } + SkMatrix adjustedMatrix = textureMatrix; + adjustedMatrix.postScale(scaleAdjust[0], scaleAdjust[1]); + + SkRect domain; + DomainMode domainMode = + DetermineDomainMode(constraintRect, filterConstraint, coordsLimitedToConstraintRect, + proxy.get(), fmForDetermineDomain, &domain); + SkASSERT(kTightCopy_DomainMode != domainMode); + return this->createFragmentProcessorForDomainAndFilter( + std::move(proxy), adjustedMatrix, domainMode, domain, filterOrNullForBicubic); +} diff --git a/gfx/skia/skia/src/gpu/GrTextureMaker.h b/gfx/skia/skia/src/gpu/GrTextureMaker.h new file mode 100644 index 0000000000..5d558d1c68 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureMaker.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureMaker_DEFINED +#define GrTextureMaker_DEFINED + +#include "src/gpu/GrTextureProducer.h" + +/** + * Base class for sources that start out as something other than a texture (encoded image, + * picture, ...). + */ +class GrTextureMaker : public GrTextureProducer { +public: + enum class AllowedTexGenType : bool { kCheap, kAny }; + + std::unique_ptr createFragmentProcessor( + const SkMatrix& textureMatrix, + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) override; + +protected: + GrTextureMaker(GrRecordingContext* context, int width, int height, const GrColorInfo& info, + bool domainNeedsLocal) + : INHERITED(context, width, height, info, domainNeedsLocal) {} + + /** + * Return the maker's "original" texture. It is the responsibility of the maker to handle any + * caching of the original if desired. + * If "genType" argument equals AllowedTexGenType::kCheap and the texture is not trivial to + * construct then refOriginalTextureProxy should return nullptr (for example if texture is made + * by drawing into a render target). + */ + virtual sk_sp refOriginalTextureProxy(bool willBeMipped, + AllowedTexGenType genType) = 0; + +private: + sk_sp onRefTextureProxyForParams(const GrSamplerState&, + bool willBeMipped, + SkScalar scaleAdjust[2]) override; + + typedef GrTextureProducer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTexturePriv.h b/gfx/skia/skia/src/gpu/GrTexturePriv.h new file mode 100644 index 0000000000..4ce3e057ea --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTexturePriv.h @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTexturePriv_DEFINED +#define GrTexturePriv_DEFINED + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrSamplerState.h" + +/** Class that adds methods to GrTexture that are only intended for use internal to Skia. + This class is purely a privileged window into GrTexture. It should never have additional data + members or virtual methods. + Non-static methods that are not trivial inlines should be spring-boarded (e.g. declared and + implemented privately in GrTexture with a inline public method here). */ +class GrTexturePriv { +public: + void markMipMapsDirty() { + fTexture->markMipMapsDirty(); + } + + void markMipMapsClean() { + fTexture->markMipMapsClean(); + } + + GrMipMapsStatus mipMapsStatus() const { return fTexture->fMipMapsStatus; } + + bool mipMapsAreDirty() const { + return GrMipMapsStatus::kValid != this->mipMapsStatus(); + } + + GrMipMapped mipMapped() const { + if (GrMipMapsStatus::kNotAllocated != this->mipMapsStatus()) { + return GrMipMapped::kYes; + } + return GrMipMapped::kNo; + } + + int maxMipMapLevel() const { + return fTexture->fMaxMipMapLevel; + } + + GrTextureType textureType() const { return fTexture->fTextureType; } + bool hasRestrictedSampling() const { + return GrTextureTypeHasRestrictedSampling(this->textureType()); + } + /** Filtering is clamped to this value. */ + GrSamplerState::Filter highestFilterMode() const { + return this->hasRestrictedSampling() ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kMipMap; + } + + static void ComputeScratchKey(GrPixelConfig config, + int width, + int height, + GrRenderable, + int sampleCnt, + GrMipMapped, + GrProtected, + GrScratchKey* key); + +private: + GrTexturePriv(GrTexture* texture) : fTexture(texture) { } + GrTexturePriv(const GrTexturePriv& that) : fTexture(that.fTexture) { } + GrTexturePriv& operator=(const GrTexturePriv&); // unimpl + + // No taking addresses of this type. + const GrTexturePriv* operator&() const; + GrTexturePriv* operator&(); + + GrTexture* fTexture; + + friend class GrTexture; // to construct/copy this type. +}; + +inline GrTexturePriv GrTexture::texturePriv() { return GrTexturePriv(this); } + +inline const GrTexturePriv GrTexture::texturePriv () const { + return GrTexturePriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureProducer.cpp b/gfx/skia/skia/src/gpu/GrTextureProducer.cpp new file mode 100644 index 0000000000..7b54e9c9d0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureProducer.cpp @@ -0,0 +1,306 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkRectPriv.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProducer.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrBicubicEffect.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" + +sk_sp GrTextureProducer::CopyOnGpu(GrRecordingContext* context, + sk_sp inputProxy, + GrColorType colorType, + const CopyParams& copyParams, + bool dstWillRequireMipMaps) { + SkASSERT(context); + + const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight); + GrMipMapped mipMapped = dstWillRequireMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo; + + SkRect localRect = SkRect::MakeWH(inputProxy->width(), inputProxy->height()); + + bool needsDomain = false; + bool resizing = false; + if (copyParams.fFilter != GrSamplerState::Filter::kNearest) { + bool resizing = localRect.width() != dstRect.width() || + localRect.height() != dstRect.height(); + needsDomain = resizing && !GrProxyProvider::IsFunctionallyExact(inputProxy.get()); + } + + if (copyParams.fFilter == GrSamplerState::Filter::kNearest && !needsDomain && !resizing && + dstWillRequireMipMaps) { + sk_sp proxy = GrCopyBaseMipMapToTextureProxy(context, inputProxy.get(), + colorType); + if (proxy) { + return proxy; + } + } + + auto copyRTC = context->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, dstRect.width(), dstRect.height(), colorType, nullptr, 1, + mipMapped, inputProxy->origin()); + if (!copyRTC) { + return nullptr; + } + + GrPaint paint; + + if (needsDomain) { + const SkRect domain = localRect.makeInset(0.5f, 0.5f); + // This would cause us to read values from outside the subset. Surely, the caller knows + // better! + SkASSERT(copyParams.fFilter != GrSamplerState::Filter::kMipMap); + paint.addColorFragmentProcessor( + GrTextureDomainEffect::Make(std::move(inputProxy), colorType, SkMatrix::I(), domain, + GrTextureDomain::kClamp_Mode, copyParams.fFilter)); + } else { + GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, copyParams.fFilter); + paint.addColorTextureProcessor(std::move(inputProxy), colorType, SkMatrix::I(), + samplerState); + } + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect, + localRect); + return copyRTC->asTextureProxyRef(); +} + +/** Determines whether a texture domain is necessary and if so what domain to use. There are two + * rectangles to consider: + * - The first is the content area specified by the texture adjuster (i.e., textureContentArea). + * We can *never* allow filtering to cause bleed of pixels outside this rectangle. + * - The second rectangle is the constraint rectangle (i.e., constraintRect), which is known to + * be contained by the content area. The filterConstraint specifies whether we are allowed to + * bleed across this rect. + * + * We want to avoid using a domain if possible. We consider the above rectangles, the filter type, + * and whether the coords generated by the draw would all fall within the constraint rect. If the + * latter is true we only need to consider whether the filter would extend beyond the rects. + */ +GrTextureProducer::DomainMode GrTextureProducer::DetermineDomainMode( + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + GrTextureProxy* proxy, + const GrSamplerState::Filter* filterModeOrNullForBicubic, + SkRect* domainRect) { + const SkIRect proxyBounds = SkIRect::MakeWH(proxy->width(), proxy->height()); + + SkASSERT(proxyBounds.contains(constraintRect)); + + const bool proxyIsExact = GrProxyProvider::IsFunctionallyExact(proxy); + + // If the constraint rectangle contains the whole proxy then no need for a domain. + if (constraintRect.contains(proxyBounds) && proxyIsExact) { + return kNoDomain_DomainMode; + } + + bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint); + + // If we can filter outside the constraint rect, and there is no non-content area of the + // proxy, and we aren't going to generate sample coords outside the constraint rect then we + // don't need a domain. + if (!restrictFilterToRect && proxyIsExact && coordsLimitedToConstraintRect) { + return kNoDomain_DomainMode; + } + + // Get the domain inset based on sampling mode (or bail if mipped) + SkScalar filterHalfWidth = 0.f; + if (filterModeOrNullForBicubic) { + switch (*filterModeOrNullForBicubic) { + case GrSamplerState::Filter::kNearest: + if (coordsLimitedToConstraintRect) { + return kNoDomain_DomainMode; + } else { + filterHalfWidth = 0.f; + } + break; + case GrSamplerState::Filter::kBilerp: + filterHalfWidth = .5f; + break; + case GrSamplerState::Filter::kMipMap: + if (restrictFilterToRect || !proxyIsExact) { + // No domain can save us here. + return kTightCopy_DomainMode; + } + return kNoDomain_DomainMode; + } + } else { + // bicubic does nearest filtering internally. + filterHalfWidth = 1.5f; + } + + // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center + // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps + + static const SkScalar kDomainInset = 0.5f; + // Figure out the limits of pixels we're allowed to sample from. + // Unless we know the amount of outset and the texture matrix we have to conservatively enforce + // the domain. + if (restrictFilterToRect) { + *domainRect = constraintRect.makeInset(kDomainInset, kDomainInset); + } else if (!proxyIsExact) { + // If we got here then: proxy is not exact, the coords are limited to the + // constraint rect, and we're allowed to filter across the constraint rect boundary. So + // we check whether the filter would reach across the edge of the proxy. + // We will only set the sides that are required. + + *domainRect = SkRectPriv::MakeLargest(); + if (coordsLimitedToConstraintRect) { + // We may be able to use the fact that the texture coords are limited to the constraint + // rect in order to avoid having to add a domain. + bool needContentAreaConstraint = false; + if (proxyBounds.fRight - filterHalfWidth < constraintRect.fRight) { + domainRect->fRight = proxyBounds.fRight - kDomainInset; + needContentAreaConstraint = true; + } + if (proxyBounds.fBottom - filterHalfWidth < constraintRect.fBottom) { + domainRect->fBottom = proxyBounds.fBottom - kDomainInset; + needContentAreaConstraint = true; + } + if (!needContentAreaConstraint) { + return kNoDomain_DomainMode; + } + } else { + // Our sample coords for the texture are allowed to be outside the constraintRect so we + // don't consider it when computing the domain. + domainRect->fRight = proxyBounds.fRight - kDomainInset; + domainRect->fBottom = proxyBounds.fBottom - kDomainInset; + } + } else { + return kNoDomain_DomainMode; + } + + if (domainRect->fLeft > domainRect->fRight) { + domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight); + } + if (domainRect->fTop > domainRect->fBottom) { + domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom); + } + return kDomain_DomainMode; +} + +std::unique_ptr GrTextureProducer::createFragmentProcessorForDomainAndFilter( + sk_sp proxy, + const SkMatrix& textureMatrix, + DomainMode domainMode, + const SkRect& domain, + const GrSamplerState::Filter* filterOrNullForBicubic) { + SkASSERT(kTightCopy_DomainMode != domainMode); + bool clampToBorderSupport = fContext->priv().caps()->clampToBorderSupport(); + GrColorType srcColorType = this->colorType(); + if (filterOrNullForBicubic) { + if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) { + GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode + : GrTextureDomain::kClamp_Mode; + return GrTextureDomainEffect::Make(std::move(proxy), srcColorType, textureMatrix, + domain, wrapMode, *filterOrNullForBicubic); + } else { + GrSamplerState::WrapMode wrapMode = + fDomainNeedsDecal ? GrSamplerState::WrapMode::kClampToBorder + : GrSamplerState::WrapMode::kClamp; + GrSamplerState samplerState(wrapMode, *filterOrNullForBicubic); + return GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, textureMatrix, + samplerState); + } + } else { + static const GrSamplerState::WrapMode kClampClamp[] = { + GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp}; + static const GrSamplerState::WrapMode kDecalDecal[] = { + GrSamplerState::WrapMode::kClampToBorder, GrSamplerState::WrapMode::kClampToBorder}; + + static constexpr auto kDir = GrBicubicEffect::Direction::kXY; + if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) { + GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode + : GrTextureDomain::kClamp_Mode; + return GrBicubicEffect::Make(std::move(proxy), srcColorType, textureMatrix, kClampClamp, + wrapMode, wrapMode, kDir, this->alphaType(), + kDomain_DomainMode == domainMode ? &domain : nullptr); + } else { + return GrBicubicEffect::Make(std::move(proxy), srcColorType, textureMatrix, + fDomainNeedsDecal ? kDecalDecal : kClampClamp, kDir, + this->alphaType()); + } + } +} + +sk_sp GrTextureProducer::refTextureProxyForParams( + const GrSamplerState::Filter* filterOrNullForBicubic, + SkScalar scaleAdjust[2]) { + GrSamplerState sampler; // Default is nearest + clamp + if (filterOrNullForBicubic) { + sampler.setFilterMode(*filterOrNullForBicubic); + } + if (fDomainNeedsDecal) { + // Assuming hardware support, switch to clamp-to-border instead of clamp + if (fContext->priv().caps()->clampToBorderSupport()) { + sampler.setWrapModeX(GrSamplerState::WrapMode::kClampToBorder); + sampler.setWrapModeY(GrSamplerState::WrapMode::kClampToBorder); + } + } + return this->refTextureProxyForParams(sampler, scaleAdjust); +} + +sk_sp GrTextureProducer::refTextureProxyForParams( + const GrSamplerState& sampler, + SkScalar scaleAdjust[2]) { + // Check that the caller pre-initialized scaleAdjust + SkASSERT(!scaleAdjust || (scaleAdjust[0] == 1 && scaleAdjust[1] == 1)); + // Check that if the caller passed nullptr for scaleAdjust that we're in the case where there + // can be no scaling. + SkDEBUGCODE(bool expectNoScale = (sampler.filter() != GrSamplerState::Filter::kMipMap && + !sampler.isRepeated())); + SkASSERT(scaleAdjust || expectNoScale); + + int mipCount = SkMipMap::ComputeLevelCount(this->width(), this->height()); + bool willBeMipped = GrSamplerState::Filter::kMipMap == sampler.filter() && mipCount && + this->context()->priv().caps()->mipMapSupport(); + + auto result = this->onRefTextureProxyForParams(sampler, willBeMipped, scaleAdjust); + + // Check to make sure that if we say the texture willBeMipped that the returned texture has mip + // maps, unless the config is not copyable. + SkASSERT(!result || !willBeMipped || result->mipMapped() == GrMipMapped::kYes || + !this->context()->priv().caps()->isFormatCopyable(result->backendFormat())); + + // Check that the "no scaling expected" case always returns a proxy of the same size as the + // producer. + SkASSERT(!result || !expectNoScale || + (result->width() == this->width() && result->height() == this->height())); + return result; +} + +sk_sp GrTextureProducer::refTextureProxy(GrMipMapped willNeedMips) { + GrSamplerState::Filter filter = + GrMipMapped::kNo == willNeedMips ? GrSamplerState::Filter::kNearest + : GrSamplerState::Filter::kMipMap; + GrSamplerState sampler(GrSamplerState::WrapMode::kClamp, filter); + + int mipCount = SkMipMap::ComputeLevelCount(this->width(), this->height()); + bool willBeMipped = GrSamplerState::Filter::kMipMap == sampler.filter() && mipCount && + this->context()->priv().caps()->mipMapSupport(); + + auto result = this->onRefTextureProxyForParams(sampler, willBeMipped, nullptr); + + // Check to make sure that if we say the texture willBeMipped that the returned texture has mip + // maps, unless the config is not copyable. + SkASSERT(!result || !willBeMipped || result->mipMapped() == GrMipMapped::kYes || + !this->context()->priv().caps()->isFormatCopyable(result->backendFormat())); + + // Check that no scaling occured and we returned a proxy of the same size as the producer. + SkASSERT(!result || (result->width() == this->width() && result->height() == this->height())); + return result; +} diff --git a/gfx/skia/skia/src/gpu/GrTextureProducer.h b/gfx/skia/skia/src/gpu/GrTextureProducer.h new file mode 100644 index 0000000000..d124bf7328 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureProducer.h @@ -0,0 +1,203 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureProducer_DEFINED +#define GrTextureProducer_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/private/GrResourceKey.h" +#include "include/private/SkNoncopyable.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrSamplerState.h" + +class GrFragmentProcessor; +class GrRecordingContext; +class GrTexture; +class GrTextureProxy; +class SkColorSpace; +class SkMatrix; +struct SkRect; + +/** + * Different GPUs and API extensions have different requirements with respect to what texture + * sampling parameters may be used with textures of various types. This class facilitates making + * texture compatible with a given GrSamplerState. There are two immediate subclasses defined + * below. One is a base class for sources that are inherently texture-backed (e.g. a texture-backed + * SkImage). It supports subsetting the original texture. The other is for use cases where the + * source can generate a texture that represents some content (e.g. cpu pixels, SkPicture, ...). + */ +class GrTextureProducer : public SkNoncopyable { +public: + struct CopyParams { + GrSamplerState::Filter fFilter; + int fWidth; + int fHeight; + }; + + enum FilterConstraint { + kYes_FilterConstraint, + kNo_FilterConstraint, + }; + + /** + * Helper for creating a fragment processor to sample the texture with a given filtering mode. + * It attempts to avoid making texture copies or using domains whenever possible. + * + * @param textureMatrix Matrix used to access the texture. It is applied to + * the local coords. The post-transformed coords should + * be in texel units (rather than normalized) with + * respect to this Producer's bounds (width()/height()). + * @param constraintRect A rect that represents the area of the texture to be + * sampled. It must be contained in the Producer's + * bounds as defined by width()/height(). + * @param filterConstriant Indicates whether filtering is limited to + * constraintRect. + * @param coordsLimitedToConstraintRect Is it known that textureMatrix*localCoords is bound + * by the portion of the texture indicated by + * constraintRect (without consideration of filter + * width, just the raw coords). + * @param filterOrNullForBicubic If non-null indicates the filter mode. If null means + * use bicubic filtering. + **/ + virtual std::unique_ptr createFragmentProcessor( + const SkMatrix& textureMatrix, + const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + const GrSamplerState::Filter* filterOrNullForBicubic) = 0; + + /** + * Returns a texture that is safe for use with the params. + * + * If the size of the returned texture does not match width()/height() then the contents of the + * original may have been scaled to fit the texture or the original may have been copied into + * a subrect of the copy. 'scaleAdjust' must be applied to the normalized texture coordinates + * in order to correct for the latter case. + * + * If the GrSamplerState is known to clamp and use kNearest or kBilerp filter mode then the + * proxy will always be unscaled and nullptr can be passed for scaleAdjust. There is a weird + * contract that if scaleAdjust is not null it must be initialized to {1, 1} before calling + * this method. (TODO: Fix this and make this function always initialize scaleAdjust). + */ + sk_sp refTextureProxyForParams(const GrSamplerState&, + SkScalar scaleAdjust[2]); + + sk_sp refTextureProxyForParams( + const GrSamplerState::Filter* filterOrNullForBicubic, SkScalar scaleAdjust[2]); + + /** + * Returns a texture. If willNeedMips is true then the returned texture is guaranteed to have + * allocated mip map levels. This can be a performance win if future draws with the texture + * require mip maps. + */ + // TODO: Once we remove support for npot textures, we should add a flag for must support repeat + // wrap mode. To support that flag now would require us to support scaleAdjust array like in + // refTextureProxyForParams, however the current public API that uses this call does not expose + // that array. + sk_sp refTextureProxy(GrMipMapped willNeedMips); + + virtual ~GrTextureProducer() {} + + int width() const { return fWidth; } + int height() const { return fHeight; } + const GrColorInfo& colorInfo() const { return fColorInfo; } + GrColorType colorType() const { return fColorInfo.colorType(); } + SkAlphaType alphaType() const { return fColorInfo.alphaType(); } + SkColorSpace* colorSpace() const { return fColorInfo.colorSpace(); } + bool isAlphaOnly() const { return GrColorTypeIsAlphaOnly(fColorInfo.colorType()); } + bool domainNeedsDecal() const { return fDomainNeedsDecal; } + // If the "texture" samples multiple images that have different resolutions (e.g. YUV420) + virtual bool hasMixedResolutions() const { return false; } + +protected: + friend class GrTextureProducer_TestAccess; + + GrTextureProducer(GrRecordingContext* context, int width, int height, + const GrColorInfo& colorInfo, bool domainNeedsDecal) + : fContext(context) + , fWidth(width) + , fHeight(height) + , fColorInfo(colorInfo) + , fDomainNeedsDecal(domainNeedsDecal) {} + + /** Helper for creating a key for a copy from an original key. */ + static void MakeCopyKeyFromOrigKey(const GrUniqueKey& origKey, + const CopyParams& copyParams, + GrUniqueKey* copyKey) { + SkASSERT(!copyKey->isValid()); + if (origKey.isValid()) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(copyKey, origKey, kDomain, 3); + builder[0] = static_cast(copyParams.fFilter); + builder[1] = copyParams.fWidth; + builder[2] = copyParams.fHeight; + } + } + + /** + * If we need to make a copy in order to be compatible with GrTextureParams producer is asked to + * return a key that identifies its original content + the CopyParms parameter. If the producer + * does not want to cache the stretched version (e.g. the producer is volatile), this should + * simply return without initializing the copyKey. If the texture generated by this producer + * depends on the destination color space, then that information should also be incorporated + * in the key. + */ + virtual void makeCopyKey(const CopyParams&, GrUniqueKey* copyKey) = 0; + + /** + * If a stretched version of the texture is generated, it may be cached (assuming that + * makeCopyKey() returns true). In that case, the maker is notified in case it + * wants to note that for when the maker is destroyed. + */ + virtual void didCacheCopy(const GrUniqueKey& copyKey, uint32_t contextUniqueID) = 0; + + enum DomainMode { + kNoDomain_DomainMode, + kDomain_DomainMode, + kTightCopy_DomainMode + }; + + // This can draw to accomplish the copy, thus the recording context is needed + static sk_sp CopyOnGpu(GrRecordingContext*, + sk_sp inputProxy, + GrColorType, + const CopyParams& copyParams, + bool dstWillRequireMipMaps); + + static DomainMode DetermineDomainMode(const SkRect& constraintRect, + FilterConstraint filterConstraint, + bool coordsLimitedToConstraintRect, + GrTextureProxy*, + const GrSamplerState::Filter* filterModeOrNullForBicubic, + SkRect* domainRect); + + std::unique_ptr createFragmentProcessorForDomainAndFilter( + sk_sp proxy, + const SkMatrix& textureMatrix, + DomainMode, + const SkRect& domain, + const GrSamplerState::Filter* filterOrNullForBicubic); + + GrRecordingContext* context() const { return fContext; } + +private: + virtual sk_sp onRefTextureProxyForParams(const GrSamplerState&, + bool willBeMipped, + SkScalar scaleAdjust[2]) = 0; + + GrRecordingContext* fContext; + const int fWidth; + const int fHeight; + const GrColorInfo fColorInfo; + // If true, any domain effect uses kDecal instead of kClamp, and sampler filter uses + // kClampToBorder instead of kClamp. + const bool fDomainNeedsDecal; + + typedef SkNoncopyable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureProxy.cpp b/gfx/skia/skia/src/gpu/GrTextureProxy.cpp new file mode 100644 index 0000000000..ba8530e32d --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureProxy.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/GrTextureProxyPriv.h" + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDeferredProxyUploader.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTexturePriv.h" + +// Deferred version - no data +GrTextureProxy::GrTextureProxy(const GrBackendFormat& format, + const GrSurfaceDesc& srcDesc, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + GrMipMapsStatus mipMapsStatus, + const GrSwizzle& textureSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : INHERITED(format, srcDesc, GrRenderable::kNo, origin, textureSwizzle, fit, budgeted, + isProtected, surfaceFlags, useAllocator) + , fMipMapped(mipMapped) + , fMipMapsStatus(mipMapsStatus) SkDEBUGCODE(, fInitialMipMapsStatus(fMipMapsStatus)) + , fProxyProvider(nullptr) + , fDeferredUploader(nullptr) {} + +// Lazy-callback version +GrTextureProxy::GrTextureProxy(LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + GrMipMapsStatus mipMapsStatus, + const GrSwizzle& texSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : INHERITED(std::move(callback), format, desc, GrRenderable::kNo, origin, texSwizzle, fit, + budgeted, isProtected, surfaceFlags, useAllocator) + , fMipMapped(mipMapped) + , fMipMapsStatus(mipMapsStatus) SkDEBUGCODE(, fInitialMipMapsStatus(fMipMapsStatus)) + , fProxyProvider(nullptr) + , fDeferredUploader(nullptr) {} + +// Wrapped version +GrTextureProxy::GrTextureProxy(sk_sp surf, + GrSurfaceOrigin origin, + const GrSwizzle& textureSwizzle, + UseAllocator useAllocator) + : INHERITED(std::move(surf), origin, textureSwizzle, SkBackingFit::kExact, useAllocator) + , fMipMapped(fTarget->asTexture()->texturePriv().mipMapped()) + , fMipMapsStatus(fTarget->asTexture()->texturePriv().mipMapsStatus()) + SkDEBUGCODE(, fInitialMipMapsStatus(fMipMapsStatus)) + , fProxyProvider(nullptr) + , fDeferredUploader(nullptr) { + if (fTarget->getUniqueKey().isValid()) { + fProxyProvider = fTarget->asTexture()->getContext()->priv().proxyProvider(); + fProxyProvider->adoptUniqueKeyFromSurface(this, fTarget.get()); + } +} + +GrTextureProxy::~GrTextureProxy() { + // Due to the order of cleanup the GrSurface this proxy may have wrapped may have gone away + // at this point. Zero out the pointer so the cache invalidation code doesn't try to use it. + fTarget = nullptr; + + // In DDL-mode, uniquely keyed proxies keep their key even after their originating + // proxy provider has gone away. In that case there is noone to send the invalid key + // message to (Note: in this case we don't want to remove its cached resource). + if (fUniqueKey.isValid() && fProxyProvider) { + fProxyProvider->processInvalidUniqueKey(fUniqueKey, this, + GrProxyProvider::InvalidateGPUResource::kNo); + } else { + SkASSERT(!fProxyProvider); + } +} + +bool GrTextureProxy::instantiate(GrResourceProvider* resourceProvider) { + if (this->isLazy()) { + return false; + } + if (!this->instantiateImpl(resourceProvider, 1, /* needsStencil = */ false, GrRenderable::kNo, + fMipMapped, fUniqueKey.isValid() ? &fUniqueKey : nullptr)) { + return false; + } + + SkASSERT(!this->peekRenderTarget()); + SkASSERT(this->peekTexture()); + return true; +} + +sk_sp GrTextureProxy::createSurface(GrResourceProvider* resourceProvider) const { + sk_sp surface = + this->createSurfaceImpl(resourceProvider, 1, + /* needsStencil = */ false, GrRenderable::kNo, fMipMapped); + if (!surface) { + return nullptr; + } + + SkASSERT(!surface->asRenderTarget()); + SkASSERT(surface->asTexture()); + return surface; +} + +void GrTextureProxyPriv::setDeferredUploader(std::unique_ptr uploader) { + SkASSERT(!fTextureProxy->fDeferredUploader); + fTextureProxy->fDeferredUploader = std::move(uploader); +} + +void GrTextureProxyPriv::scheduleUpload(GrOpFlushState* flushState) { + // The texture proxy's contents may already have been uploaded or instantiation may have failed + if (fTextureProxy->fDeferredUploader && fTextureProxy->isInstantiated()) { + fTextureProxy->fDeferredUploader->scheduleUpload(flushState, fTextureProxy); + } +} + +void GrTextureProxyPriv::resetDeferredUploader() { + SkASSERT(fTextureProxy->fDeferredUploader); + fTextureProxy->fDeferredUploader.reset(); +} + +GrSamplerState::Filter GrTextureProxy::highestFilterMode() const { + return this->hasRestrictedSampling() ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kMipMap; +} + +GrMipMapped GrTextureProxy::mipMapped() const { + if (this->isInstantiated()) { + return this->peekTexture()->texturePriv().mipMapped(); + } + return fMipMapped; +} + +size_t GrTextureProxy::onUninstantiatedGpuMemorySize(const GrCaps& caps) const { + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + 1, this->proxyMipMapped(), !this->priv().isExact()); +} + +bool GrTextureProxy::ProxiesAreCompatibleAsDynamicState(const GrTextureProxy* first, + const GrTextureProxy* second) { + return first->config() == second->config() && + first->textureType() == second->textureType() && + first->backendFormat() == second->backendFormat(); +} + +void GrTextureProxy::setUniqueKey(GrProxyProvider* proxyProvider, const GrUniqueKey& key) { + SkASSERT(key.isValid()); + SkASSERT(!fUniqueKey.isValid()); // proxies can only ever get one uniqueKey + + if (fTarget && fSyncTargetKey) { + if (!fTarget->getUniqueKey().isValid()) { + fTarget->resourcePriv().setUniqueKey(key); + } + SkASSERT(fTarget->getUniqueKey() == key); + } + + fUniqueKey = key; + fProxyProvider = proxyProvider; +} + +void GrTextureProxy::clearUniqueKey() { + fUniqueKey.reset(); + fProxyProvider = nullptr; +} + +#ifdef SK_DEBUG +void GrTextureProxy::onValidateSurface(const GrSurface* surface) { + SkASSERT(!surface->asRenderTarget()); + + // Anything that is checked here should be duplicated in GrTextureRenderTargetProxy's version + SkASSERT(surface->asTexture()); + // It is possible to fulfill a non-mipmapped proxy with a mipmapped texture. + SkASSERT(GrMipMapped::kNo == this->proxyMipMapped() || + GrMipMapped::kYes == surface->asTexture()->texturePriv().mipMapped()); + + SkASSERT(surface->asTexture()->texturePriv().textureType() == this->textureType()); + + GrInternalSurfaceFlags proxyFlags = fSurfaceFlags; + GrInternalSurfaceFlags surfaceFlags = surface->surfacePriv().flags(); + SkASSERT(((int)proxyFlags & kGrInternalTextureFlagsMask) == + ((int)surfaceFlags & kGrInternalTextureFlagsMask)); +} + +#endif + diff --git a/gfx/skia/skia/src/gpu/GrTextureProxy.h b/gfx/skia/skia/src/gpu/GrTextureProxy.h new file mode 100644 index 0000000000..1718794815 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureProxy.h @@ -0,0 +1,196 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureProxy_DEFINED +#define GrTextureProxy_DEFINED + +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/GrSurfaceProxy.h" + +class GrCaps; +class GrDeferredProxyUploader; +class GrProxyProvider; +class GrResourceProvider; +class GrTextureProxyPriv; + +// This class delays the acquisition of textures until they are actually required +class GrTextureProxy : virtual public GrSurfaceProxy { +public: + GrTextureProxy* asTextureProxy() override { return this; } + const GrTextureProxy* asTextureProxy() const override { return this; } + + // Actually instantiate the backing texture, if necessary + bool instantiate(GrResourceProvider*) override; + + GrSamplerState::Filter highestFilterMode() const; + + // If we are instantiated and have a target, return the mip state of that target. Otherwise + // returns the proxy's mip state from creation time. This is useful for lazy proxies which may + // claim to not need mips at creation time, but the instantiation happens to give us a mipped + // target. In that case we should use that for our benefit to avoid possible copies/mip + // generation later. + GrMipMapped mipMapped() const; + + bool mipMapsAreDirty() const { + SkASSERT((GrMipMapped::kNo == fMipMapped) == + (GrMipMapsStatus::kNotAllocated == fMipMapsStatus)); + return GrMipMapped::kYes == fMipMapped && GrMipMapsStatus::kValid != fMipMapsStatus; + } + void markMipMapsDirty() { + SkASSERT(GrMipMapped::kYes == fMipMapped); + fMipMapsStatus = GrMipMapsStatus::kDirty; + } + void markMipMapsClean() { + SkASSERT(GrMipMapped::kYes == fMipMapped); + fMipMapsStatus = GrMipMapsStatus::kValid; + } + + // Returns the GrMipMapped value of the proxy from creation time regardless of whether it has + // been instantiated or not. + GrMipMapped proxyMipMapped() const { return fMipMapped; } + + GrTextureType textureType() const { return this->backendFormat().textureType(); } + + /** If true then the texture does not support MIP maps and only supports clamp wrap mode. */ + bool hasRestrictedSampling() const { + return GrTextureTypeHasRestrictedSampling(this->textureType()); + } + + // Returns true if the passed in proxies can be used as dynamic state together when flushing + // draws to the gpu. + static bool ProxiesAreCompatibleAsDynamicState(const GrTextureProxy* first, + const GrTextureProxy* second); + + /** + * Return the texture proxy's unique key. It will be invalid if the proxy doesn't have one. + */ + const GrUniqueKey& getUniqueKey() const { +#ifdef SK_DEBUG + if (this->isInstantiated() && fUniqueKey.isValid() && fSyncTargetKey) { + GrSurface* surface = this->peekSurface(); + SkASSERT(surface); + + SkASSERT(surface->getUniqueKey().isValid()); + // It is possible for a non-keyed proxy to have a uniquely keyed resource assigned to + // it. This just means that a future user of the resource will be filling it with unique + // data. However, if the proxy has a unique key its attached resource should also + // have that key. + SkASSERT(fUniqueKey == surface->getUniqueKey()); + } +#endif + + return fUniqueKey; + } + + /** + * Internal-only helper class used for manipulations of the resource by the cache. + */ + class CacheAccess; + inline CacheAccess cacheAccess(); + inline const CacheAccess cacheAccess() const; + + // Provides access to special purpose functions. + GrTextureProxyPriv texPriv(); + const GrTextureProxyPriv texPriv() const; + +protected: + // DDL TODO: rm the GrSurfaceProxy friending + friend class GrSurfaceProxy; // for ctors + friend class GrProxyProvider; // for ctors + friend class GrTextureProxyPriv; + friend class GrSurfaceProxyPriv; // ability to change key sync state after lazy instantiation. + + // Deferred version - no data. + GrTextureProxy(const GrBackendFormat&, + const GrSurfaceDesc&, + GrSurfaceOrigin, + GrMipMapped, + GrMipMapsStatus, + const GrSwizzle& textureSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + + // Lazy-callback version + // There are two main use cases for lazily-instantiated proxies: + // basic knowledge - width, height, config, origin are known + // minimal knowledge - only config is known. + // + // The basic knowledge version is used for DDL where we know the type of proxy we are going to + // use, but we don't have access to the GPU yet to instantiate it. + // + // The minimal knowledge version is used for CCPR where we are generating an atlas but we do not + // know the final size until flush time. + GrTextureProxy(LazyInstantiateCallback&&, + const GrBackendFormat&, + const GrSurfaceDesc& desc, + GrSurfaceOrigin, + GrMipMapped, + GrMipMapsStatus, + const GrSwizzle& textureSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + + // Wrapped version + GrTextureProxy(sk_sp, GrSurfaceOrigin, const GrSwizzle&, UseAllocator); + + ~GrTextureProxy() override; + + sk_sp createSurface(GrResourceProvider*) const override; + + void setTargetKeySync(bool sync) { fSyncTargetKey = sync; } + +private: + // WARNING: Be careful when adding or removing fields here. ASAN is likely to trigger warnings + // when instantiating GrTextureRenderTargetProxy. The std::function in GrSurfaceProxy makes + // each class in the diamond require 16 byte alignment. Clang appears to layout the fields for + // each class to achieve the necessary alignment. However, ASAN checks the alignment of 'this' + // in the constructors, and always looks for the full 16 byte alignment, even if the fields in + // that particular class don't require it. Changing the size of this object can move the start + // address of other types, leading to this problem. + + GrMipMapped fMipMapped; + + // This tracks the mipmap status at the proxy level and is thus somewhat distinct from the + // backing GrTexture's mipmap status. In particular, this status is used to determine when + // mipmap levels need to be explicitly regenerated during the execution of a DAG of opsTasks. + GrMipMapsStatus fMipMapsStatus; + // TEMPORARY: We are in the process of moving GrMipMapsStatus from the texture to the proxy. + // We track the fInitialMipMapsStatus here so we can assert that the proxy did indeed expect + // the correct mipmap status immediately after instantiation. + // + // NOTE: fMipMapsStatus may no longer be equal to fInitialMipMapsStatus by the time the texture + // is instantiated, since it tracks mipmaps in the time frame in which the DAG is being built. + SkDEBUGCODE(const GrMipMapsStatus fInitialMipMapsStatus); + + bool fSyncTargetKey = true; // Should target's unique key be sync'ed with ours. + + GrUniqueKey fUniqueKey; + GrProxyProvider* fProxyProvider; // only set when fUniqueKey is valid + + // Only used for proxies whose contents are being prepared on a worker thread. This object + // stores the texture data, allowing the proxy to remain uninstantiated until flush. At that + // point, the proxy is instantiated, and this data is used to perform an ASAP upload. + std::unique_ptr fDeferredUploader; + + size_t onUninstantiatedGpuMemorySize(const GrCaps&) const override; + + // Methods made available via GrTextureProxy::CacheAccess + void setUniqueKey(GrProxyProvider*, const GrUniqueKey&); + void clearUniqueKey(); + + SkDEBUGCODE(void onValidateSurface(const GrSurface*) override;) + + typedef GrSurfaceProxy INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureProxyCacheAccess.h b/gfx/skia/skia/src/gpu/GrTextureProxyCacheAccess.h new file mode 100644 index 0000000000..26478c6101 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureProxyCacheAccess.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureProxyCacheAccess_DEFINED +#define GrTextureProxyCacheAccess_DEFINED + +#include "src/gpu/GrTextureProxy.h" + +/** + * This class allows GrResourceCache increased privileged access to GrTextureProxy objects. + */ +class GrTextureProxy::CacheAccess { +private: + void setUniqueKey(GrProxyProvider* proxyProvider, const GrUniqueKey& key) { + fTextureProxy->setUniqueKey(proxyProvider, key); + } + + void clearUniqueKey() { + fTextureProxy->clearUniqueKey(); + } + + explicit CacheAccess(GrTextureProxy* textureProxy) : fTextureProxy(textureProxy) {} + CacheAccess(const CacheAccess&) {} // unimpl + CacheAccess& operator=(const CacheAccess&); // unimpl + + // No taking addresses of this type. + const CacheAccess* operator&() const; + CacheAccess* operator&(); + + GrTextureProxy* fTextureProxy; + + friend class GrTextureProxy; // to construct/copy this type. + friend class GrProxyProvider; // to use this type +}; + +inline GrTextureProxy::CacheAccess GrTextureProxy::cacheAccess() { return CacheAccess(this); } + +inline const GrTextureProxy::CacheAccess GrTextureProxy::cacheAccess() const { + return CacheAccess(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureProxyPriv.h b/gfx/skia/skia/src/gpu/GrTextureProxyPriv.h new file mode 100644 index 0000000000..1e64194523 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureProxyPriv.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureProxyPriv_DEFINED +#define GrTextureProxyPriv_DEFINED + +#include "src/gpu/GrTextureProxy.h" + +class GrDeferredProxyUploader; +class GrOpFlushState; + +/** + * This class hides the more specialized capabilities of GrTextureProxy. + */ +class GrTextureProxyPriv { +public: + // Attach a deferred uploader to the proxy. Holds data being prepared by a worker thread. + void setDeferredUploader(std::unique_ptr); + bool isDeferred() const { return SkToBool(fTextureProxy->fDeferredUploader.get()); } + // For a deferred proxy (one that has a deferred uploader attached), this schedules an ASAP + // upload of that data to the instantiated texture. + void scheduleUpload(GrOpFlushState*); + // Clears any deferred uploader object on the proxy. Used to free the CPU data after the + // contents have been uploaded. + void resetDeferredUploader(); + +private: + explicit GrTextureProxyPriv(GrTextureProxy* textureProxy) : fTextureProxy(textureProxy) {} + GrTextureProxyPriv(const GrTextureProxyPriv&) {} // unimpl + GrTextureProxyPriv& operator=(const GrTextureProxyPriv&); // unimpl + + // No taking addresses of this type. + const GrTextureProxyPriv* operator&() const; + GrTextureProxyPriv* operator&(); + + GrTextureProxy* fTextureProxy; + + friend class GrTextureProxy; // to construct/copy this type. +}; + +inline GrTextureProxyPriv GrTextureProxy::texPriv() { return GrTextureProxyPriv(this); } + +inline const GrTextureProxyPriv GrTextureProxy::texPriv() const { + return GrTextureProxyPriv(const_cast(this)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.cpp b/gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.cpp new file mode 100644 index 0000000000..ae70c0fa76 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.cpp @@ -0,0 +1,182 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTextureRenderTargetProxy.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetProxyPriv.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureProxyPriv.h" + +// Deferred version +// This class is virtually derived from GrSurfaceProxy (via both GrTextureProxy and +// GrRenderTargetProxy) so its constructor must be explicitly called. +GrTextureRenderTargetProxy::GrTextureRenderTargetProxy(const GrCaps& caps, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + int sampleCnt, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + GrMipMapsStatus mipMapsStatus, + const GrSwizzle& texSwizzle, + const GrSwizzle& outSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : GrSurfaceProxy(format, desc, GrRenderable::kYes, origin, texSwizzle, fit, budgeted, + isProtected, surfaceFlags, useAllocator) + // for now textures w/ data are always wrapped + , GrRenderTargetProxy(caps, format, desc, sampleCnt, origin, texSwizzle, outSwizzle, fit, + budgeted, isProtected, surfaceFlags, useAllocator) + , GrTextureProxy(format, desc, origin, mipMapped, mipMapsStatus, texSwizzle, fit, budgeted, + isProtected, surfaceFlags, useAllocator) { + this->initSurfaceFlags(caps); +} + +// Lazy-callback version +GrTextureRenderTargetProxy::GrTextureRenderTargetProxy(const GrCaps& caps, + LazyInstantiateCallback&& callback, + const GrBackendFormat& format, + const GrSurfaceDesc& desc, + int sampleCnt, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + GrMipMapsStatus mipMapsStatus, + const GrSwizzle& texSwizzle, + const GrSwizzle& outSwizzle, + SkBackingFit fit, + SkBudgeted budgeted, + GrProtected isProtected, + GrInternalSurfaceFlags surfaceFlags, + UseAllocator useAllocator) + : GrSurfaceProxy(std::move(callback), format, desc, GrRenderable::kYes, origin, texSwizzle, + fit, budgeted, isProtected, surfaceFlags, useAllocator) + // Since we have virtual inheritance, we initialize GrSurfaceProxy directly. Send null + // callbacks to the texture and RT proxies simply to route to the appropriate constructors. + , GrRenderTargetProxy(LazyInstantiateCallback(), format, desc, sampleCnt, origin, + texSwizzle, outSwizzle, fit, budgeted, isProtected, surfaceFlags, + useAllocator, WrapsVkSecondaryCB::kNo) + , GrTextureProxy(LazyInstantiateCallback(), format, desc, origin, mipMapped, mipMapsStatus, + texSwizzle, fit, budgeted, isProtected, surfaceFlags, useAllocator) { + this->initSurfaceFlags(caps); +} + +// Wrapped version +// This class is virtually derived from GrSurfaceProxy (via both GrTextureProxy and +// GrRenderTargetProxy) so its constructor must be explicitly called. +GrTextureRenderTargetProxy::GrTextureRenderTargetProxy(sk_sp surf, + GrSurfaceOrigin origin, + const GrSwizzle& texSwizzle, + const GrSwizzle& outSwizzle, + UseAllocator useAllocator) + : GrSurfaceProxy(surf, origin, texSwizzle, SkBackingFit::kExact, useAllocator) + , GrRenderTargetProxy(surf, origin, texSwizzle, outSwizzle, useAllocator) + , GrTextureProxy(surf, origin, texSwizzle, useAllocator) { + SkASSERT(surf->asTexture()); + SkASSERT(surf->asRenderTarget()); + SkASSERT(fSurfaceFlags == fTarget->surfacePriv().flags()); + SkASSERT((this->numSamples() <= 1 || + fTarget->getContext()->priv().caps()->msaaResolvesAutomatically()) != + this->requiresManualMSAAResolve()); +} + +void GrTextureRenderTargetProxy::initSurfaceFlags(const GrCaps& caps) { + // FBO 0 should never be wrapped as a texture render target. + SkASSERT(!this->rtPriv().glRTFBOIDIs0()); + if (this->numSamples() > 1 && !caps.msaaResolvesAutomatically()) { + // MSAA texture-render-targets always require manual resolve if we are not using a + // multisampled-render-to-texture extension. + // + // NOTE: This is the only instance where we need to set the manual resolve flag on a proxy. + // Any other proxies that require manual resolve (e.g., wrapBackendTextureAsRenderTarget()) + // will be wrapped, and the wrapped version of the GrSurface constructor will automatically + // get the manual resolve flag when copying the target GrSurface's flags. + fSurfaceFlags |= GrInternalSurfaceFlags::kRequiresManualMSAAResolve; + } +} + +size_t GrTextureRenderTargetProxy::onUninstantiatedGpuMemorySize(const GrCaps& caps) const { + int colorSamplesPerPixel = this->numSamples(); + if (colorSamplesPerPixel > 1) { + // Add one to account for the resolve buffer. + ++colorSamplesPerPixel; + } + + // TODO: do we have enough information to improve this worst case estimate? + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + colorSamplesPerPixel, this->proxyMipMapped(), + !this->priv().isExact()); +} + +bool GrTextureRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) { + if (this->isLazy()) { + return false; + } + + const GrUniqueKey& key = this->getUniqueKey(); + + if (!this->instantiateImpl(resourceProvider, this->numSamples(), this->numStencilSamples(), + GrRenderable::kYes, this->mipMapped(), + key.isValid() ? &key : nullptr)) { + return false; + } + if (key.isValid()) { + SkASSERT(key == this->getUniqueKey()); + } + + SkASSERT(this->peekRenderTarget()); + SkASSERT(this->peekTexture()); + + return true; +} + +sk_sp GrTextureRenderTargetProxy::createSurface( + GrResourceProvider* resourceProvider) const { + sk_sp surface = + this->createSurfaceImpl(resourceProvider, this->numSamples(), this->numStencilSamples(), + GrRenderable::kYes, this->mipMapped()); + if (!surface) { + return nullptr; + } + SkASSERT(surface->asRenderTarget()); + SkASSERT(surface->asTexture()); + + return surface; +} + +#ifdef SK_DEBUG +void GrTextureRenderTargetProxy::onValidateSurface(const GrSurface* surface) { + // Anything checked here should also be checking the GrTextureProxy version + SkASSERT(surface->asTexture()); + SkASSERT(GrMipMapped::kNo == this->proxyMipMapped() || + GrMipMapped::kYes == surface->asTexture()->texturePriv().mipMapped()); + + // Anything checked here should also be checking the GrRenderTargetProxy version + SkASSERT(surface->asRenderTarget()); + SkASSERT(surface->asRenderTarget()->numSamples() == this->numSamples()); + + SkASSERT(surface->asTexture()->texturePriv().textureType() == this->textureType()); + + GrInternalSurfaceFlags proxyFlags = fSurfaceFlags; + GrInternalSurfaceFlags surfaceFlags = surface->surfacePriv().flags(); + + // Only non-RT textures can be read only. + SkASSERT(!(proxyFlags & GrInternalSurfaceFlags::kReadOnly)); + SkASSERT(!(surfaceFlags & GrInternalSurfaceFlags::kReadOnly)); + + SkASSERT(((int)proxyFlags & kGrInternalTextureRenderTargetFlagsMask) == + ((int)surfaceFlags & kGrInternalTextureRenderTargetFlagsMask)); +} +#endif + diff --git a/gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.h b/gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.h new file mode 100644 index 0000000000..46c273d6a2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureRenderTargetProxy.h @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureRenderTargetProxy_DEFINED +#define GrTextureRenderTargetProxy_DEFINED + +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrTextureProxy.h" + +#ifdef SK_BUILD_FOR_WIN +// Windows gives warnings about inheriting asTextureProxy/asRenderTargetProxy via dominance. +#pragma warning(push) +#pragma warning(disable: 4250) +#endif + +// This class delays the acquisition of RenderTargets that are also textures until +// they are actually required +// Beware: the uniqueID of the TextureRenderTargetProxy will usually be different than +// the uniqueID of the RenderTarget/Texture it represents! +class GrTextureRenderTargetProxy : public GrRenderTargetProxy, public GrTextureProxy { +private: + // DDL TODO: rm the GrSurfaceProxy friending + friend class GrSurfaceProxy; // for ctors + friend class GrProxyProvider; // for ctors + + // Deferred version + GrTextureRenderTargetProxy(const GrCaps&, + const GrBackendFormat&, + const GrSurfaceDesc&, + int sampleCnt, + GrSurfaceOrigin, + GrMipMapped, + GrMipMapsStatus, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + + // Lazy-callback version + GrTextureRenderTargetProxy(const GrCaps&, + LazyInstantiateCallback&&, + const GrBackendFormat&, + const GrSurfaceDesc& desc, + int sampleCnt, + GrSurfaceOrigin, + GrMipMapped, + GrMipMapsStatus, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + SkBackingFit, + SkBudgeted, + GrProtected, + GrInternalSurfaceFlags, + UseAllocator); + + // Wrapped version + GrTextureRenderTargetProxy(sk_sp, + GrSurfaceOrigin, + const GrSwizzle& textureSwizzle, + const GrSwizzle& outputSwizzle, + UseAllocator); + + void initSurfaceFlags(const GrCaps&); + + bool instantiate(GrResourceProvider*) override; + sk_sp createSurface(GrResourceProvider*) const override; + + size_t onUninstantiatedGpuMemorySize(const GrCaps&) const override; + + SkDEBUGCODE(void onValidateSurface(const GrSurface*) override;) +}; + +#ifdef SK_BUILD_FOR_WIN +#pragma warning(pop) +#endif + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureResolveManager.h b/gfx/skia/skia/src/gpu/GrTextureResolveManager.h new file mode 100644 index 0000000000..f610c6b64a --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureResolveManager.h @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureResolveManager_DEFINED +#define GrTextureResolveManager_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrDrawingManager.h" + +class GrCaps; +class GrDrawingManager; +class GrRenderTask; + +/* + * This class is a shallow view of the drawing manager. It is passed to render tasks when setting up + * the dependency DAG, and gives them limited access to functionality for making new tasks that + * regenerate mipmaps and/or resolve MSAA. + */ +class GrTextureResolveManager { +public: + explicit GrTextureResolveManager(GrDrawingManager* drawingManager) + : fDrawingManager(drawingManager) {} + + GrTextureResolveRenderTask* newTextureResolveRenderTask(const GrCaps& caps) const { + SkASSERT(fDrawingManager); + return fDrawingManager->newTextureResolveRenderTask(caps); + } + +private: + GrDrawingManager* fDrawingManager; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.cpp b/gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.cpp new file mode 100644 index 0000000000..e12bbc8e30 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTextureResolveRenderTask.h" + +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrResourceAllocator.h" +#include "src/gpu/GrTexturePriv.h" + +GrTextureResolveRenderTask::~GrTextureResolveRenderTask() { + for (const auto& resolve : fResolves) { + // Ensure the proxy doesn't keep hold of a dangling back pointer. + resolve.fProxy->setLastRenderTask(nullptr); + } +} + +void GrTextureResolveRenderTask::addProxy( + sk_sp proxyHolder, GrSurfaceProxy::ResolveFlags flags, const GrCaps& caps) { + fResolves.emplace_back(std::move(proxyHolder), flags); + GrSurfaceProxy* proxy = fResolves.back().fProxy.get(); + + // Ensure the last render task that operated on the proxy is closed. That's where msaa and + // mipmaps should have been marked dirty. + SkASSERT(!proxy->getLastRenderTask() || proxy->getLastRenderTask()->isClosed()); + SkASSERT(GrSurfaceProxy::ResolveFlags::kNone != flags); + + if (GrSurfaceProxy::ResolveFlags::kMSAA & flags) { + GrRenderTargetProxy* renderTargetProxy = proxy->asRenderTargetProxy(); + SkASSERT(renderTargetProxy); + SkASSERT(renderTargetProxy->isMSAADirty()); + fResolves.back().fMSAAResolveRect = renderTargetProxy->msaaDirtyRect(); + renderTargetProxy->markMSAAResolved(); + } + + if (GrSurfaceProxy::ResolveFlags::kMipMaps & flags) { + GrTextureProxy* textureProxy = proxy->asTextureProxy(); + SkASSERT(GrMipMapped::kYes == textureProxy->mipMapped()); + SkASSERT(textureProxy->mipMapsAreDirty()); + textureProxy->markMipMapsClean(); + } + + // Add the proxy as a dependency: We will read the existing contents of this texture while + // generating mipmap levels and/or resolving MSAA. + this->addDependency(proxy, GrMipMapped::kNo, GrTextureResolveManager(nullptr), caps); + proxy->setLastRenderTask(this); +} + +void GrTextureResolveRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const { + // This renderTask doesn't have "normal" ops, however we still need to add intervals so + // fEndOfOpsTaskOpIndices will remain in sync. We create fake op#'s to capture the fact that we + // manipulate the resolve proxies. + auto fakeOp = alloc->curOp(); + for (const auto& resolve : fResolves) { + alloc->addInterval(resolve.fProxy.get(), fakeOp, fakeOp, + GrResourceAllocator::ActualUse::kYes); + } + alloc->incOps(); +} + +bool GrTextureResolveRenderTask::onExecute(GrOpFlushState* flushState) { + // Resolve all msaa back-to-back, before regenerating mipmaps. + for (const auto& resolve : fResolves) { + if (GrSurfaceProxy::ResolveFlags::kMSAA & resolve.fFlags) { + // peekRenderTarget might be null if there was an instantiation error. + if (GrRenderTarget* renderTarget = resolve.fProxy->peekRenderTarget()) { + flushState->gpu()->resolveRenderTarget(renderTarget, resolve.fMSAAResolveRect, + resolve.fProxy->origin(), + GrGpu::ForExternalIO::kNo); + } + } + } + // Regenerate all mipmaps back-to-back. + for (const auto& resolve : fResolves) { + if (GrSurfaceProxy::ResolveFlags::kMipMaps & resolve.fFlags) { + // peekTexture might be null if there was an instantiation error. + GrTexture* texture = resolve.fProxy->peekTexture(); + if (texture && texture->texturePriv().mipMapsAreDirty()) { + flushState->gpu()->regenerateMipMapLevels(texture); + SkASSERT(!texture->texturePriv().mipMapsAreDirty()); + } + } + } + + return true; +} + +#ifdef SK_DEBUG +void GrTextureResolveRenderTask::visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const { + for (const auto& resolve : fResolves) { + fn(resolve.fProxy.get(), GrMipMapped::kNo); + } +} +#endif diff --git a/gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.h b/gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.h new file mode 100644 index 0000000000..48fea3905b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTextureResolveRenderTask.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureResolveRenderTask_DEFINED +#define GrTextureResolveRenderTask_DEFINED + +#include "src/gpu/GrRenderTask.h" + +class GrTextureResolveRenderTask final : public GrRenderTask { +public: + GrTextureResolveRenderTask() : GrRenderTask(nullptr) {} + ~GrTextureResolveRenderTask() override; + + void addProxy(sk_sp, GrSurfaceProxy::ResolveFlags, const GrCaps&); + +private: + bool onIsUsed(GrSurfaceProxy* proxy) const override { + SkASSERT(proxy != fTarget.get()); // This case should be handled by GrRenderTask. + return false; + } + void handleInternalAllocationFailure() override { + // No need to do anything special here. We just double check the proxies during onExecute. + } + void gatherProxyIntervals(GrResourceAllocator*) const override; + + ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect*) override { + return ExpectedOutcome::kTargetUnchanged; + } + + bool onExecute(GrOpFlushState*) override; + +#ifdef SK_DEBUG + SkDEBUGCODE(void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const override;) +#endif + + struct Resolve { + Resolve(sk_sp proxy, GrSurfaceProxy::ResolveFlags flags) + : fProxy(std::move(proxy)), fFlags(flags) {} + sk_sp fProxy; + GrSurfaceProxy::ResolveFlags fFlags; + SkIRect fMSAAResolveRect; + }; + + SkSTArray<4, Resolve> fResolves; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrTracing.h b/gfx/skia/skia/src/gpu/GrTracing.h new file mode 100644 index 0000000000..df66eb81d5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTracing.h @@ -0,0 +1,23 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTracing_DEFINED +#define GrTracing_DEFINED + +#include "src/core/SkTraceEvent.h" + +#include "src/gpu/GrAuditTrail.h" + +class GrContext; + +/** + * Context level GrTracing macros, classname and op are const char*, context is GrContext + */ +#define GR_CREATE_TRACE_MARKER_CONTEXT(classname, op, context) \ + GR_AUDIT_TRAIL_AUTO_FRAME(context->priv().auditTrail(), classname "::" op); \ + TRACE_EVENT0("skia.gpu", classname "::" op) +#endif diff --git a/gfx/skia/skia/src/gpu/GrTransferFromRenderTask.cpp b/gfx/skia/skia/src/gpu/GrTransferFromRenderTask.cpp new file mode 100644 index 0000000000..7826bceca9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTransferFromRenderTask.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrTransferFromRenderTask.h" + +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceAllocator.h" + +void GrTransferFromRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const { + // This renderTask doesn't have "normal" ops. In this case we still need to add an interval (so + // fEndOfOpsTaskOpIndices will remain in sync), so we create a fake op# to capture the fact that + // we read fSrcProxy. + alloc->addInterval(fSrcProxy.get(), alloc->curOp(), alloc->curOp(), + GrResourceAllocator::ActualUse::kYes); + alloc->incOps(); +} + +bool GrTransferFromRenderTask::onExecute(GrOpFlushState* flushState) { + if (!fSrcProxy->isInstantiated()) { + return false; + } + return flushState->gpu()->transferPixelsFrom( + fSrcProxy->peekSurface(), fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.width(), + fSrcRect.height(), fSurfaceColorType, fDstColorType, fDstBuffer.get(), fDstOffset); +} diff --git a/gfx/skia/skia/src/gpu/GrTransferFromRenderTask.h b/gfx/skia/skia/src/gpu/GrTransferFromRenderTask.h new file mode 100644 index 0000000000..40e89da6be --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrTransferFromRenderTask.h @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTransferFromRenderTask_DEFINED +#define GrTransferFromRenderTask_DEFINED + +#include "src/gpu/GrRenderTask.h" + +class GrTransferFromRenderTask final : public GrRenderTask { +public: + GrTransferFromRenderTask(sk_sp srcProxy, + const SkIRect& srcRect, + GrColorType surfaceColorType, + GrColorType dstColorType, + sk_sp dstBuffer, + size_t dstOffset) + : GrRenderTask(nullptr) + , fSrcProxy(std::move(srcProxy)) + , fSrcRect(srcRect) + , fSurfaceColorType(surfaceColorType) + , fDstColorType(dstColorType) + , fDstBuffer(std::move(dstBuffer)) + , fDstOffset(dstOffset) {} + +private: + bool onIsUsed(GrSurfaceProxy* proxy) const override { + SkASSERT(!fTarget); + return proxy == fSrcProxy.get(); + } + // If fSrcProxy is uninstantiated at flush time we simply will skip doing the transfer. + void handleInternalAllocationFailure() override {} + void gatherProxyIntervals(GrResourceAllocator*) const override; + + ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect*) override { + return ExpectedOutcome::kTargetUnchanged; + } + + bool onExecute(GrOpFlushState*) override; + +#ifdef SK_DEBUG + void visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const override { + fn(fSrcProxy.get(), GrMipMapped::kNo); + } +#endif + + sk_sp fSrcProxy; + SkIRect fSrcRect; + GrColorType fSurfaceColorType; + GrColorType fDstColorType; + sk_sp fDstBuffer; + size_t fDstOffset; + +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/GrUserStencilSettings.h b/gfx/skia/skia/src/gpu/GrUserStencilSettings.h new file mode 100644 index 0000000000..83a19b776e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrUserStencilSettings.h @@ -0,0 +1,257 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrUserStencilSettings_DEFINED +#define GrUserStencilSettings_DEFINED + +#include "include/gpu/GrTypes.h" + +/** + * Gr uses the stencil buffer to implement complex clipping inside the + * GrOpsTask class. The GrOpsTask makes a subset of the stencil buffer + * bits available for other uses by external code (user bits). Client code can + * modify these bits. GrOpsTask will ignore ref, mask, and writemask bits + * provided by clients that fall outside the user range. + * + * When code outside the GrOpsTask class uses the stencil buffer the contract + * is as follows: + * + * > Normal stencil funcs allow the client to pass / fail regardless of the + * reserved clip bits. + * > Additional functions allow a test against the clip along with a limited + * set of tests against the user bits. + * > Client can assume all user bits are zero initially. + * > Client must ensure that after all its passes are finished it has only + * written to the color buffer in the region inside the clip. Furthermore, it + * must zero all user bits that were modifed (both inside and outside the + * clip). + */ + +enum GrStencilFlags { + kDisabled_StencilFlag = (1 << 0), + kTestAlwaysPasses_StencilFlag = (1 << 1), + kNoModifyStencil_StencilFlag = (1 << 2), + kNoWrapOps_StencilFlag = (1 << 3), + kSingleSided_StencilFlag = (1 << 4), + + kLast_StencilFlag = kSingleSided_StencilFlag, + kAll_StencilFlags = kLast_StencilFlag | (kLast_StencilFlag - 1) +}; + +template struct GrTStencilFaceSettings { + uint16_t fRef; // Reference value for stencil test and ops. + TTest fTest; // Stencil test function, where fRef is on the left side. + uint16_t fTestMask; // Bitwise "and" to perform on fRef and stencil values before testing. + // (e.g. (fRef & fTestMask) < (stencil & fTestMask)) + TOp fPassOp; // Op to perform when the test passes. + TOp fFailOp; // Op to perform when the test fails. + uint16_t fWriteMask; // Indicates which bits in the stencil buffer should be updated. + // (e.g. stencil = (newValue & fWriteMask) | (stencil & ~fWriteMask)) +}; + +enum class GrUserStencilTest : uint16_t { + // Tests that respect the clip bit. If a stencil clip is not in effect, the "IfInClip" is + // ignored and these only act on user bits. + kAlwaysIfInClip, + kEqualIfInClip, + kLessIfInClip, + kLEqualIfInClip, + + // Tests that ignore the clip bit. The client is responsible to ensure no color write occurs + // outside the clip if it is in use. + kAlways, + kNever, + kGreater, + kGEqual, + kLess, + kLEqual, + kEqual, + kNotEqual +}; +constexpr static GrUserStencilTest kLastClippedStencilTest = GrUserStencilTest::kLEqualIfInClip; +constexpr static int kGrUserStencilTestCount = 1 + (int)GrUserStencilTest::kNotEqual; + +enum class GrUserStencilOp : uint8_t { + kKeep, + + // Ops that only modify user bits. These must not be paired with ops that modify the clip bit. + kZero, + kReplace, // Replace stencil value with fRef (only the bits enabled in fWriteMask). + kInvert, + kIncWrap, + kDecWrap, + // These two should only be used if wrap ops are not supported, or if the math is guaranteed + // to not overflow. The user bits may or may not clamp, depending on the state of non-user bits. + kIncMaybeClamp, + kDecMaybeClamp, + + // Ops that only modify the clip bit. These must not be paired with ops that modify user bits. + kZeroClipBit, + kSetClipBit, + kInvertClipBit, + + // Ops that modify both clip and user bits. These can only be paired with kKeep or each other. + kSetClipAndReplaceUserBits, + kZeroClipAndUserBits +}; +constexpr static GrUserStencilOp kLastUserOnlyStencilOp = GrUserStencilOp::kDecMaybeClamp; +constexpr static GrUserStencilOp kLastClipOnlyStencilOp = GrUserStencilOp::kInvertClipBit; +constexpr static int kGrUserStencilOpCount = 1 + (int)GrUserStencilOp::kZeroClipAndUserBits; + +/** + * This struct is a compile-time constant representation of user stencil settings. It describes in + * abstract terms how a draw will use the stencil buffer. It gets ODR-used at runtime to define a + * draw's stencil settings, and is later translated into concrete settings when the pipeline is + * finalized. + */ +struct GrUserStencilSettings { + typedef GrTStencilFaceSettings Face; + + template struct Attrs; + + // Unfortunately, this is the only way to pass template arguments to a constructor. + template struct Init {}; + + template struct InitSeparate {}; + + template + constexpr static Init StaticInit() { + return Init(); + } + + template + constexpr static InitSeparate StaticInitSeparate() { + return InitSeparate(); + } + + // We construct with template arguments in order to enforce that the struct be compile-time + // constant and to make use of static asserts. + template > + constexpr explicit GrUserStencilSettings( + const Init&) + : fFrontFlags{(uint16_t)(Attrs::Flags(false) | kSingleSided_StencilFlag), + (uint16_t)(Attrs::Flags(true) | kSingleSided_StencilFlag)} + , fFront{Ref, Test, Attrs::EffectiveTestMask(TestMask), PassOp, FailOp, + Attrs::EffectiveWriteMask(WriteMask)} + , fBackFlags{(uint16_t)(Attrs::Flags(false) | kSingleSided_StencilFlag), + (uint16_t)(Attrs::Flags(true) | kSingleSided_StencilFlag)} + , fBack{Ref, Test, Attrs::EffectiveTestMask(TestMask), PassOp, FailOp, + Attrs::EffectiveWriteMask(WriteMask)} { + } + + template, + typename BkAttrs = Attrs > + constexpr explicit GrUserStencilSettings( + const InitSeparate&) + : fFrontFlags{FtAttrs::Flags(false), FtAttrs::Flags(true)} + , fFront{FtRef, FtTest, FtAttrs::EffectiveTestMask(FtTestMask), FtPassOp, FtFailOp, + FtAttrs::EffectiveWriteMask(FtWriteMask)} + , fBackFlags{BkAttrs::Flags(false), BkAttrs::Flags(true)} + , fBack{BkRef, BkTest, BkAttrs::EffectiveTestMask(BkTestMask), BkPassOp, BkFailOp, + BkAttrs::EffectiveWriteMask(BkWriteMask)} {} + + // This struct can only be constructed with static initializers. + GrUserStencilSettings() = delete; + GrUserStencilSettings(const GrUserStencilSettings&) = delete; + + uint16_t flags(bool hasStencilClip) const { + return fFrontFlags[hasStencilClip] & fBackFlags[hasStencilClip]; + } + bool isDisabled(bool hasStencilClip) const { + return this->flags(hasStencilClip) & kDisabled_StencilFlag; + } + bool testAlwaysPasses(bool hasStencilClip) const { + return this->flags(hasStencilClip) & kTestAlwaysPasses_StencilFlag; + } + bool isTwoSided(bool hasStencilClip) const { + return !(this->flags(hasStencilClip) & kSingleSided_StencilFlag); + } + bool usesWrapOp(bool hasStencilClip) const { + return !(this->flags(hasStencilClip) & kNoWrapOps_StencilFlag); + } + + const uint16_t fFrontFlags[2]; // frontFlagsForDraw = fFrontFlags[hasStencilClip]. + const Face fFront; + const uint16_t fBackFlags[2]; // backFlagsForDraw = fBackFlags[hasStencilClip]. + const Face fBack; + + static const GrUserStencilSettings& kUnused; + + bool isUnused() const { return this == &kUnused; } +}; + +template +struct GrUserStencilSettings::Attrs { + // Ensure an op that only modifies user bits isn't paired with one that modifies clip bits. + GR_STATIC_ASSERT(GrUserStencilOp::kKeep == PassOp || GrUserStencilOp::kKeep == FailOp || + (PassOp <= kLastUserOnlyStencilOp) == (FailOp <= kLastUserOnlyStencilOp)); + // Ensure an op that only modifies clip bits isn't paired with one that modifies clip and user. + GR_STATIC_ASSERT(GrUserStencilOp::kKeep == PassOp || GrUserStencilOp::kKeep == FailOp || + (PassOp <= kLastClipOnlyStencilOp) == (FailOp <= kLastClipOnlyStencilOp)); + + constexpr static bool TestAlwaysPasses(bool hasStencilClip) { + return (!hasStencilClip && GrUserStencilTest::kAlwaysIfInClip == Test) || + GrUserStencilTest::kAlways == Test; + } + constexpr static bool DoesNotModifyStencil(bool hasStencilClip) { + return (GrUserStencilTest::kNever == Test || GrUserStencilOp::kKeep == PassOp) && + (TestAlwaysPasses(hasStencilClip) || GrUserStencilOp::kKeep == FailOp); + } + constexpr static bool IsDisabled(bool hasStencilClip) { + return TestAlwaysPasses(hasStencilClip) && DoesNotModifyStencil(hasStencilClip); + } + constexpr static bool UsesWrapOps() { + return GrUserStencilOp::kIncWrap == PassOp || GrUserStencilOp::kDecWrap == PassOp || + GrUserStencilOp::kIncWrap == FailOp || GrUserStencilOp::kDecWrap == FailOp; + } + constexpr static bool TestIgnoresRef() { + return (GrUserStencilTest::kAlwaysIfInClip == Test || GrUserStencilTest::kAlways == Test || + GrUserStencilTest::kNever == Test); + } + constexpr static uint16_t Flags(bool hasStencilClip) { + return (IsDisabled(hasStencilClip) ? kDisabled_StencilFlag : 0) | + (TestAlwaysPasses(hasStencilClip) ? kTestAlwaysPasses_StencilFlag : 0) | + (DoesNotModifyStencil(hasStencilClip) ? kNoModifyStencil_StencilFlag : 0) | + (UsesWrapOps() ? 0 : kNoWrapOps_StencilFlag); + } + constexpr static uint16_t EffectiveTestMask(uint16_t testMask) { + return TestIgnoresRef() ? 0 : testMask; + } + constexpr static uint16_t EffectiveWriteMask(uint16_t writeMask) { + // We don't modify the mask differently when hasStencilClip=false because either the entire + // face gets disabled in that case (e.g. Test=kAlwaysIfInClip, PassOp=kKeep), or else the + // effective mask stays the same either way. + return DoesNotModifyStencil(true) ? 0 : writeMask; + } +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrUtil.h b/gfx/skia/skia/src/gpu/GrUtil.h new file mode 100644 index 0000000000..5412f0b292 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrUtil.h @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrUtil_DEFINED +#define GrUtil_DEFINED + +#include "include/core/SkTypes.h" + +enum GrIntelGpuFamily { + kUnknown_IntelGpuFamily, + + // 6th gen + kSandyBridge_IntelGpuFamily, + + // 7th gen + kIvyBridge_IntelGpuFamily, + kValleyView_IntelGpuFamily, // aka BayTrail + kHaswell_IntelGpuFamily, + + // 8th gen + kCherryView_IntelGpuFamily, // aka Braswell + kBroadwell_IntelGpuFamily, + + // 9th gen + kApolloLake_IntelGpuFamily, + kSkyLake_IntelGpuFamily, + kGeminiLake_IntelGpuFamily, + kKabyLake_IntelGpuFamily, + kCoffeeLake_IntelGpuFamily, + + // 11th gen + kIceLake_IntelGpuFamily, +}; + +GrIntelGpuFamily GrGetIntelGpuFamily(uint32_t deviceID) { + // https://en.wikipedia.org/wiki/List_of_Intel_graphics_processing_units + uint32_t maskedID = deviceID & 0xFF00; + switch (maskedID) { + case 0x0100: + switch (deviceID & 0xFFF0) { + case 0x0100: + case 0x0110: + case 0x0120: + return kSandyBridge_IntelGpuFamily; + case 0x0150: + if (deviceID == 0x0155 || deviceID == 0x0157) { + return kValleyView_IntelGpuFamily; + } + if (deviceID == 0x0152 || deviceID == 0x015A) { + return kIvyBridge_IntelGpuFamily; + } + break; + case 0x0160: + return kIvyBridge_IntelGpuFamily; + default: + break; + } + break; + case 0x0F00: + return kValleyView_IntelGpuFamily; + case 0x0400: + case 0x0A00: + case 0x0D00: + return kHaswell_IntelGpuFamily; + case 0x2200: + return kCherryView_IntelGpuFamily; + case 0x1600: + return kBroadwell_IntelGpuFamily; + case 0x5A00: + return kApolloLake_IntelGpuFamily; + case 0x1900: + return kSkyLake_IntelGpuFamily; + case 0x3100: + return kGeminiLake_IntelGpuFamily; + case 0x5900: + return kKabyLake_IntelGpuFamily; + case 0x3E00: + return kCoffeeLake_IntelGpuFamily; + case 0x8A00: + return kIceLake_IntelGpuFamily; + default: + break; + } + return kUnknown_IntelGpuFamily; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrVertexWriter.h b/gfx/skia/skia/src/gpu/GrVertexWriter.h new file mode 100644 index 0000000000..7b1ddc1d6b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrVertexWriter.h @@ -0,0 +1,176 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVertexWriter_DEFINED +#define GrVertexWriter_DEFINED + +#include "include/private/SkTemplates.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/geometry/GrQuad.h" +#include + +/** + * Helper for writing vertex data to a buffer. Usage: + * GrVertexWriter vertices{target->makeVertexSpace(...)}; + * vertices.write(A0, B0, C0, ...); + * vertices.write(A1, B1, C1, ...); + * + * Supports any number of arguments. Each argument must be POD (plain old data), or an array + * thereof. + */ +struct GrVertexWriter { + void* fPtr; + + template + class Conditional { + public: + explicit Conditional(bool condition, const T& value) + : fCondition(condition), fValue(value) {} + private: + friend struct GrVertexWriter; + + bool fCondition; + T fValue; + }; + + template + static Conditional If(bool condition, const T& value) { + return Conditional(condition, value); + } + + template + struct Skip {}; + + template + void write(const T& val, const Args&... remainder) { + static_assert(std::is_pod::value, ""); + // This assert is barely related to what we're trying to check - that our vertex data + // matches our attribute layouts, where each attribute is aligned to four bytes. If this + // becomes a problem, just remove it. + static_assert(alignof(T) <= 4, ""); + memcpy(fPtr, &val, sizeof(T)); + fPtr = SkTAddOffset(fPtr, sizeof(T)); + this->write(remainder...); + } + + template + void write(const T(&val)[N], const Args&... remainder) { + static_assert(std::is_pod::value, ""); + static_assert(alignof(T) <= 4, ""); + memcpy(fPtr, val, N * sizeof(T)); + fPtr = SkTAddOffset(fPtr, N * sizeof(T)); + this->write(remainder...); + } + + template + void write(const GrVertexColor& color, const Args&... remainder) { + this->write(color.fColor[0]); + if (color.fWideColor) { + this->write(color.fColor[1]); + } + this->write(remainder...); + } + + template + void write(const Conditional& val, const Args&... remainder) { + if (val.fCondition) { + this->write(val.fValue); + } + this->write(remainder...); + } + + template + void write(const Skip& val, const Args&... remainder) { + fPtr = SkTAddOffset(fPtr, sizeof(T)); + this->write(remainder...); + } + + template + void write(const Sk4f& vector, const Args&... remainder) { + float buffer[4]; + vector.store(buffer); + this->write(buffer); + this->write(remainder...); + } + + void write() {} + + /** + * Specialized utility for writing a four-vertices, with some data being replicated at each + * vertex, and other data being the appropriate 2-components from an SkRect to construct a + * triangle strip. + * + * writeQuad(A, B, C, ...) is similar to write(A, B, C, ...), except that: + * + * - Four sets of data will be written + * - For any arguments of type TriStrip, a unique SkPoint will be written at each vertex, + * in this order: left-top, left-bottom, right-top, right-bottom. + */ + template + struct TriStrip { T l, t, r, b; }; + + static TriStrip TriStripFromRect(const SkRect& r) { + return { r.fLeft, r.fTop, r.fRight, r.fBottom }; + } + + template + struct TriFan { T l, t, r, b; }; + + static TriFan TriFanFromRect(const SkRect& r) { + return { r.fLeft, r.fTop, r.fRight, r.fBottom }; + } + + template + void writeQuad(const Args&... remainder) { + this->writeQuadVert<0>(remainder...); + this->writeQuadVert<1>(remainder...); + this->writeQuadVert<2>(remainder...); + this->writeQuadVert<3>(remainder...); + } + +private: + template + void writeQuadVert(const T& val, const Args&... remainder) { + this->writeQuadValue(val); + this->writeQuadVert(remainder...); + } + + template + void writeQuadVert() {} + + template + void writeQuadValue(const T& val) { + this->write(val); + } + + template + void writeQuadValue(const TriStrip& r) { + switch (corner) { + case 0: this->write(r.l, r.t); break; + case 1: this->write(r.l, r.b); break; + case 2: this->write(r.r, r.t); break; + case 3: this->write(r.r, r.b); break; + } + } + + template + void writeQuadValue(const TriFan& r) { + switch (corner) { + case 0: this->write(r.l, r.t); break; + case 1: this->write(r.l, r.b); break; + case 2: this->write(r.r, r.b); break; + case 3: this->write(r.r, r.t); break; + } + } + + template + void writeQuadValue(const GrQuad& q) { + this->write(q.point(corner)); + } +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrWaitRenderTask.cpp b/gfx/skia/skia/src/gpu/GrWaitRenderTask.cpp new file mode 100644 index 0000000000..a09a9a617e --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrWaitRenderTask.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrWaitRenderTask.h" + +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceAllocator.h" + +void GrWaitRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const { + // This renderTask doesn't have "normal" ops. In this case we still need to add an interval (so + // fEndOfOpsTaskOpIndices will remain in sync), so we create a fake op# to capture the fact that + // we manipulate fTarget. + alloc->addInterval(fTarget.get(), alloc->curOp(), alloc->curOp(), + GrResourceAllocator::ActualUse::kYes); + alloc->incOps(); +} + +bool GrWaitRenderTask::onExecute(GrOpFlushState* flushState) { + for (int i = 0; i < fNumSemaphores; ++i) { + flushState->gpu()->waitSemaphore(fSemaphores[i]); + } + return true; +} diff --git a/gfx/skia/skia/src/gpu/GrWaitRenderTask.h b/gfx/skia/skia/src/gpu/GrWaitRenderTask.h new file mode 100644 index 0000000000..fc736e1c9b --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrWaitRenderTask.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrWaitRenderTask_DEFINED +#define GrWaitRenderTask_DEFINED + +#include "src/gpu/GrRenderTask.h" +#include "src/gpu/GrSemaphore.h" + +class GrWaitRenderTask final : public GrRenderTask { +public: + GrWaitRenderTask(sk_sp proxy, std::unique_ptr[]> semaphores, + int numSemaphores) + : GrRenderTask(std::move(proxy)) + , fSemaphores(std::move(semaphores)) + , fNumSemaphores(numSemaphores){} + +private: + bool onIsUsed(GrSurfaceProxy* proxy) const override { + SkASSERT(proxy != fTarget.get()); // This case should be handled by GrRenderTask. + return false; + } + void handleInternalAllocationFailure() override {} + void gatherProxyIntervals(GrResourceAllocator*) const override; + + ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect*) override { + return ExpectedOutcome::kTargetUnchanged; + } + + bool onExecute(GrOpFlushState*) override; + +#ifdef SK_DEBUG + // No non-dst proxies. + void visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const override {} +#endif + std::unique_ptr[]> fSemaphores; + int fNumSemaphores; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrWindowRectangles.h b/gfx/skia/skia/src/gpu/GrWindowRectangles.h new file mode 100644 index 0000000000..1b6bc4dfee --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrWindowRectangles.h @@ -0,0 +1,123 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrWindowRectangles_DEFINED +#define GrWindowRectangles_DEFINED + +#include "include/core/SkRect.h" +#include "src/gpu/GrNonAtomicRef.h" + +class GrWindowRectangles { +public: + constexpr static int kMaxWindows = 8; + + GrWindowRectangles() : fCount(0) {} + GrWindowRectangles(const GrWindowRectangles& that) : fCount(0) { *this = that; } + ~GrWindowRectangles() { SkSafeUnref(this->rec()); } + + GrWindowRectangles makeOffset(int dx, int dy) const; + + bool empty() const { return !fCount; } + int count() const { return fCount; } + const SkIRect* data() const; + + void reset(); + GrWindowRectangles& operator=(const GrWindowRectangles&); + + SkIRect& addWindow(const SkIRect& window) { return this->addWindow() = window; } + SkIRect& addWindow(); + + bool operator!=(const GrWindowRectangles& that) const { return !(*this == that); } + bool operator==(const GrWindowRectangles&) const; + +private: + constexpr static int kNumLocalWindows = 1; + struct Rec; + + const Rec* rec() const { return fCount <= kNumLocalWindows ? nullptr : fRec; } + + int fCount; + union { + SkIRect fLocalWindows[kNumLocalWindows]; // If fCount <= kNumLocalWindows. + Rec* fRec; // If fCount > kNumLocalWindows. + }; +}; + +struct GrWindowRectangles::Rec : public GrNonAtomicRef { + Rec(const SkIRect* windows, int numWindows) { + SkASSERT(numWindows < kMaxWindows); + memcpy(fData, windows, sizeof(SkIRect) * numWindows); + } + Rec() = default; + + SkIRect fData[kMaxWindows]; +}; + +inline const SkIRect* GrWindowRectangles::data() const { + return fCount <= kNumLocalWindows ? fLocalWindows : fRec->fData; +} + +inline void GrWindowRectangles::reset() { + SkSafeUnref(this->rec()); + fCount = 0; +} + +inline GrWindowRectangles& GrWindowRectangles::operator=(const GrWindowRectangles& that) { + SkSafeUnref(this->rec()); + fCount = that.fCount; + if (fCount <= kNumLocalWindows) { + memcpy(fLocalWindows, that.fLocalWindows, fCount * sizeof(SkIRect)); + } else { + fRec = SkRef(that.fRec); + } + return *this; +} + +inline GrWindowRectangles GrWindowRectangles::makeOffset(int dx, int dy) const { + if (!dx && !dy) { + return *this; + } + GrWindowRectangles result; + result.fCount = fCount; + SkIRect* windows; + if (result.fCount > kNumLocalWindows) { + result.fRec = new Rec(); + windows = result.fRec->fData; + } else { + windows = result.fLocalWindows; + } + for (int i = 0; i < fCount; ++i) { + windows[i] = this->data()[i].makeOffset(dx, dy); + } + return result; +} + +inline SkIRect& GrWindowRectangles::addWindow() { + SkASSERT(fCount < kMaxWindows); + if (fCount < kNumLocalWindows) { + return fLocalWindows[fCount++]; + } + if (fCount == kNumLocalWindows) { + fRec = new Rec(fLocalWindows, kNumLocalWindows); + } else if (!fRec->unique()) { // Simple copy-on-write. + fRec->unref(); + fRec = new Rec(fRec->fData, fCount); + } + return fRec->fData[fCount++]; +} + +inline bool GrWindowRectangles::operator==(const GrWindowRectangles& that) const { + if (fCount != that.fCount) { + return false; + } + if (fCount > kNumLocalWindows && fRec == that.fRec) { + return true; + } + return !fCount || !memcmp(this->data(), that.data(), sizeof(SkIRect) * fCount); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/GrWindowRectsState.h b/gfx/skia/skia/src/gpu/GrWindowRectsState.h new file mode 100644 index 0000000000..59ff938bfd --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrWindowRectsState.h @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrWindowRectsState_DEFINED +#define GrWindowRectsState_DEFINED + +#include "src/gpu/GrWindowRectangles.h" + +class GrWindowRectsState { +public: + enum class Mode : bool { + kExclusive, + kInclusive + }; + + GrWindowRectsState() : fMode(Mode::kExclusive) {} + GrWindowRectsState(const GrWindowRectangles& windows, Mode mode) + : fMode(mode) + , fWindows(windows) { + } + + bool enabled() const { return Mode::kInclusive == fMode || !fWindows.empty(); } + Mode mode() const { return fMode; } + const GrWindowRectangles& windows() const { return fWindows; } + int numWindows() const { return fWindows.count(); } + + void setDisabled() { + fMode = Mode::kExclusive; + fWindows.reset(); + } + + void set(const GrWindowRectangles& windows, Mode mode) { + fMode = mode; + fWindows = windows; + } + + bool operator==(const GrWindowRectsState& that) const { + if (fMode != that.fMode) { + return false; + } + return fWindows == that.fWindows; + } + bool operator!=(const GrWindowRectsState& that) const { return !(*this == that); } + +private: + Mode fMode; + GrWindowRectangles fWindows; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/GrXferProcessor.cpp b/gfx/skia/skia/src/gpu/GrXferProcessor.cpp new file mode 100644 index 0000000000..14d0355a06 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrXferProcessor.cpp @@ -0,0 +1,191 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrXferProcessor.h" + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrPipeline.h" + +GrXferProcessor::GrXferProcessor(ClassID classID) + : INHERITED(classID) + , fWillReadDstColor(false) + , fDstReadUsesMixedSamples(false) + , fIsLCD(false) {} + +GrXferProcessor::GrXferProcessor(ClassID classID, bool willReadDstColor, bool hasMixedSamples, + GrProcessorAnalysisCoverage coverage) + : INHERITED(classID) + , fWillReadDstColor(willReadDstColor) + , fDstReadUsesMixedSamples(willReadDstColor && hasMixedSamples) + , fIsLCD(GrProcessorAnalysisCoverage::kLCD == coverage) {} + +bool GrXferProcessor::hasSecondaryOutput() const { + if (!this->willReadDstColor()) { + return this->onHasSecondaryOutput(); + } + return this->dstReadUsesMixedSamples(); +} + +void GrXferProcessor::getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b, + const GrSurfaceOrigin* originIfDstTexture) const { + uint32_t key = this->willReadDstColor() ? 0x1 : 0x0; + if (key) { + if (originIfDstTexture) { + key |= 0x2; + if (kTopLeft_GrSurfaceOrigin == *originIfDstTexture) { + key |= 0x4; + } + } + if (this->dstReadUsesMixedSamples()) { + key |= 0x8; + } + } + if (fIsLCD) { + key |= 0x10; + } + b->add32(key); + this->onGetGLSLProcessorKey(caps, b); +} + +#ifdef SK_DEBUG +static const char* equation_string(GrBlendEquation eq) { + switch (eq) { + case kAdd_GrBlendEquation: + return "add"; + case kSubtract_GrBlendEquation: + return "subtract"; + case kReverseSubtract_GrBlendEquation: + return "reverse_subtract"; + case kScreen_GrBlendEquation: + return "screen"; + case kOverlay_GrBlendEquation: + return "overlay"; + case kDarken_GrBlendEquation: + return "darken"; + case kLighten_GrBlendEquation: + return "lighten"; + case kColorDodge_GrBlendEquation: + return "color_dodge"; + case kColorBurn_GrBlendEquation: + return "color_burn"; + case kHardLight_GrBlendEquation: + return "hard_light"; + case kSoftLight_GrBlendEquation: + return "soft_light"; + case kDifference_GrBlendEquation: + return "difference"; + case kExclusion_GrBlendEquation: + return "exclusion"; + case kMultiply_GrBlendEquation: + return "multiply"; + case kHSLHue_GrBlendEquation: + return "hsl_hue"; + case kHSLSaturation_GrBlendEquation: + return "hsl_saturation"; + case kHSLColor_GrBlendEquation: + return "hsl_color"; + case kHSLLuminosity_GrBlendEquation: + return "hsl_luminosity"; + case kIllegal_GrBlendEquation: + SkASSERT(false); + return ""; + } + return ""; +} + +static const char* coeff_string(GrBlendCoeff coeff) { + switch (coeff) { + case kZero_GrBlendCoeff: + return "zero"; + case kOne_GrBlendCoeff: + return "one"; + case kSC_GrBlendCoeff: + return "src_color"; + case kISC_GrBlendCoeff: + return "inv_src_color"; + case kDC_GrBlendCoeff: + return "dst_color"; + case kIDC_GrBlendCoeff: + return "inv_dst_color"; + case kSA_GrBlendCoeff: + return "src_alpha"; + case kISA_GrBlendCoeff: + return "inv_src_alpha"; + case kDA_GrBlendCoeff: + return "dst_alpha"; + case kIDA_GrBlendCoeff: + return "inv_dst_alpha"; + case kConstC_GrBlendCoeff: + return "const_color"; + case kIConstC_GrBlendCoeff: + return "inv_const_color"; + case kConstA_GrBlendCoeff: + return "const_alpha"; + case kIConstA_GrBlendCoeff: + return "inv_const_alpha"; + case kS2C_GrBlendCoeff: + return "src2_color"; + case kIS2C_GrBlendCoeff: + return "inv_src2_color"; + case kS2A_GrBlendCoeff: + return "src2_alpha"; + case kIS2A_GrBlendCoeff: + return "inv_src2_alpha"; + case kIllegal_GrBlendCoeff: + SkASSERT(false); + return ""; + } + return ""; +} + +SkString GrXferProcessor::BlendInfo::dump() const { + SkString out; + out.printf("write_color(%d) equation(%s) src_coeff(%s) dst_coeff:(%s) const(0x%08x)", + fWriteColor, equation_string(fEquation), coeff_string(fSrcBlend), + coeff_string(fDstBlend), fBlendConstant.toBytes_RGBA()); + return out; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +GrXPFactory::AnalysisProperties GrXPFactory::GetAnalysisProperties( + const GrXPFactory* factory, + const GrProcessorAnalysisColor& color, + const GrProcessorAnalysisCoverage& coverage, + const GrCaps& caps, + GrClampType clampType) { + AnalysisProperties result; + if (factory) { + result = factory->analysisProperties(color, coverage, caps, clampType); + } else { + result = GrPorterDuffXPFactory::SrcOverAnalysisProperties(color, coverage, caps, + clampType); + } + SkASSERT(!(result & AnalysisProperties::kRequiresDstTexture)); + if ((result & AnalysisProperties::kReadsDstInShader) && + !caps.shaderCaps()->dstReadInShaderSupport()) { + result |= AnalysisProperties::kRequiresDstTexture | + AnalysisProperties::kRequiresNonOverlappingDraws; + } + return result; +} + +sk_sp GrXPFactory::MakeXferProcessor(const GrXPFactory* factory, + const GrProcessorAnalysisColor& color, + GrProcessorAnalysisCoverage coverage, + bool hasMixedSamples, + const GrCaps& caps, + GrClampType clampType) { + SkASSERT(!hasMixedSamples || caps.shaderCaps()->dualSourceBlendingSupport()); + if (factory) { + return factory->makeXferProcessor(color, coverage, hasMixedSamples, caps, clampType); + } else { + return GrPorterDuffXPFactory::MakeSrcOverXferProcessor(color, coverage, hasMixedSamples, + caps); + } +} diff --git a/gfx/skia/skia/src/gpu/GrXferProcessor.h b/gfx/skia/skia/src/gpu/GrXferProcessor.h new file mode 100644 index 0000000000..18525e7c05 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrXferProcessor.h @@ -0,0 +1,323 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrXferProcessor_DEFINED +#define GrXferProcessor_DEFINED + +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrBlend.h" +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrProcessorAnalysis.h" + +class GrGLSLXferProcessor; +class GrProcessorSet; +class GrShaderCaps; + +/** + * Barriers for blending. When a shader reads the dst directly, an Xfer barrier is sometimes + * required after a pixel has been written, before it can be safely read again. + */ +enum GrXferBarrierType { + kNone_GrXferBarrierType = 0, // { +public: + /** + * A texture that contains the dst pixel values and an integer coord offset from device space + * to the space of the texture. Depending on GPU capabilities a DstTexture may be used by a + * GrXferProcessor for blending in the fragment shader. + */ + class DstProxy { + public: + DstProxy() { fOffset.set(0, 0); } + + DstProxy(const DstProxy& other) { + *this = other; + } + + DstProxy(sk_sp proxy, const SkIPoint& offset) + : fProxy(std::move(proxy)) { + if (fProxy) { + fOffset = offset; + } else { + fOffset.set(0, 0); + } + } + + DstProxy& operator=(const DstProxy& other) { + fProxy = other.fProxy; + fOffset = other.fOffset; + return *this; + } + + bool operator==(const DstProxy& that) const { + return fProxy == that.fProxy && fOffset == that.fOffset; + } + bool operator!=(const DstProxy& that) const { return !(*this == that); } + + const SkIPoint& offset() const { return fOffset; } + + void setOffset(const SkIPoint& offset) { fOffset = offset; } + void setOffset(int ox, int oy) { fOffset.set(ox, oy); } + + GrTextureProxy* proxy() const { return fProxy.get(); } + sk_sp refProxy() const { return fProxy; } + + void setProxy(sk_sp proxy) { + fProxy = std::move(proxy); + if (!fProxy) { + fOffset = {0, 0}; + } + } + + private: + sk_sp fProxy; + SkIPoint fOffset; + }; + + /** + * Sets a unique key on the GrProcessorKeyBuilder calls onGetGLSLProcessorKey(...) to get the + * specific subclass's key. + */ + void getGLSLProcessorKey(const GrShaderCaps&, + GrProcessorKeyBuilder*, + const GrSurfaceOrigin* originIfDstTexture) const; + + /** Returns a new instance of the appropriate *GL* implementation class + for the given GrXferProcessor; caller is responsible for deleting + the object. */ + virtual GrGLSLXferProcessor* createGLSLInstance() const = 0; + + /** + * Returns the barrier type, if any, that this XP will require. Note that the possibility + * that a kTexture type barrier is required is handled by the GrPipeline and need not be + * considered by subclass overrides of this function. + */ + virtual GrXferBarrierType xferBarrierType(const GrCaps& caps) const { + return kNone_GrXferBarrierType; + } + + struct BlendInfo { + SkDEBUGCODE(SkString dump() const;) + + GrBlendEquation fEquation = kAdd_GrBlendEquation; + GrBlendCoeff fSrcBlend = kOne_GrBlendCoeff; + GrBlendCoeff fDstBlend = kZero_GrBlendCoeff; + SkPMColor4f fBlendConstant = SK_PMColor4fTRANSPARENT; + bool fWriteColor = true; + }; + + inline BlendInfo getBlendInfo() const { + BlendInfo blendInfo; + if (!this->willReadDstColor()) { + this->onGetBlendInfo(&blendInfo); + } else if (this->dstReadUsesMixedSamples()) { + blendInfo.fDstBlend = kIS2A_GrBlendCoeff; + } + return blendInfo; + } + + bool willReadDstColor() const { return fWillReadDstColor; } + + /** + * If we are performing a dst read, returns whether the base class will use mixed samples to + * antialias the shader's final output. If not doing a dst read, the subclass is responsible + * for antialiasing and this returns false. + */ + bool dstReadUsesMixedSamples() const { return fDstReadUsesMixedSamples; } + + /** + * Returns whether or not this xferProcossor will set a secondary output to be used with dual + * source blending. + */ + bool hasSecondaryOutput() const; + + bool isLCD() const { return fIsLCD; } + + /** Returns true if this and other processor conservatively draw identically. It can only return + true when the two processor are of the same subclass (i.e. they return the same object from + from getFactory()). + + A return value of true from isEqual() should not be used to test whether the processor would + generate the same shader code. To test for identical code generation use getGLSLProcessorKey + */ + + bool isEqual(const GrXferProcessor& that) const { + if (this->classID() != that.classID()) { + return false; + } + if (this->fWillReadDstColor != that.fWillReadDstColor) { + return false; + } + if (this->fDstReadUsesMixedSamples != that.fDstReadUsesMixedSamples) { + return false; + } + if (fIsLCD != that.fIsLCD) { + return false; + } + return this->onIsEqual(that); + } + +protected: + GrXferProcessor(ClassID classID); + GrXferProcessor(ClassID classID, bool willReadDstColor, bool hasMixedSamples, + GrProcessorAnalysisCoverage); + +private: + /** + * Sets a unique key on the GrProcessorKeyBuilder that is directly associated with this xfer + * processor's GL backend implementation. + */ + virtual void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const = 0; + + /** + * If we are not performing a dst read, returns whether the subclass will set a secondary + * output. When using dst reads, the base class controls the secondary output and this method + * will not be called. + */ + virtual bool onHasSecondaryOutput() const { return false; } + + /** + * If we are not performing a dst read, retrieves the fixed-function blend state required by the + * subclass. When using dst reads, the base class controls the fixed-function blend state and + * this method will not be called. The BlendInfo struct comes initialized to "no blending". + */ + virtual void onGetBlendInfo(BlendInfo*) const {} + + virtual bool onIsEqual(const GrXferProcessor&) const = 0; + + bool fWillReadDstColor; + bool fDstReadUsesMixedSamples; + bool fIsLCD; + + typedef GrProcessor INHERITED; +}; + +/** + * We install a GrXPFactory (XPF) early on in the pipeline before all the final draw information is + * known (e.g. whether there is fractional pixel coverage, will coverage be 1 or 4 channel, is the + * draw opaque, etc.). Once the state of the draw is finalized, we use the XPF along with all the + * draw information to create a GrXferProcessor (XP) which can implement the desired blending for + * the draw. + * + * Before the XP is created, the XPF is able to answer queries about what functionality the XPs it + * creates will have. For example, can it create an XP that supports RGB coverage or will the XP + * blend with the destination color. + * + * GrXPFactories are intended to be static immutable objects. We pass them around as raw pointers + * and expect the pointers to always be valid and for the factories to be reusable and thread safe. + * Equality is tested for using pointer comparison. GrXPFactory destructors must be no-ops. + */ + +// In order to construct GrXPFactory subclass instances as constexpr the subclass, and therefore +// GrXPFactory, must be a literal type. One requirement is having a trivial destructor. This is ok +// since these objects have no need for destructors. However, GCC and clang throw a warning when a +// class has virtual functions and a non-virtual destructor. We suppress that warning here and +// for the subclasses. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif +class GrXPFactory { +public: + typedef GrXferProcessor::DstProxy DstProxy; + + enum class AnalysisProperties : unsigned { + kNone = 0x0, + /** + * The fragment shader will require the destination color. + */ + kReadsDstInShader = 0x1, + /** + * The op may apply coverage as alpha and still blend correctly. + */ + kCompatibleWithCoverageAsAlpha = 0x2, + /** + * The color input to the GrXferProcessor will be ignored. + */ + kIgnoresInputColor = 0x4, + /** + * The destination color will be provided to the fragment processor using a texture. This is + * additional information about the implementation of kReadsDstInShader. + */ + kRequiresDstTexture = 0x10, + /** + * If set, each pixel can only be touched once during a draw (e.g., because we have a dst + * texture or because we need an xfer barrier). + */ + kRequiresNonOverlappingDraws = 0x20, + }; + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(AnalysisProperties); + + static sk_sp MakeXferProcessor(const GrXPFactory*, + const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps& caps, + GrClampType); + + static AnalysisProperties GetAnalysisProperties(const GrXPFactory*, + const GrProcessorAnalysisColor&, + const GrProcessorAnalysisCoverage&, + const GrCaps&, + GrClampType); + +protected: + constexpr GrXPFactory() {} + +private: + virtual sk_sp makeXferProcessor(const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps&, + GrClampType) const = 0; + + /** + * Subclass analysis implementation. This should not return kNeedsDstInTexture as that will be + * inferred by the base class based on kReadsDstInShader and the caps. + */ + virtual AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&, + const GrProcessorAnalysisCoverage&, + const GrCaps&, + GrClampType) const = 0; +}; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +GR_MAKE_BITFIELD_CLASS_OPS(GrXPFactory::AnalysisProperties); + +#endif diff --git a/gfx/skia/skia/src/gpu/GrYUVProvider.cpp b/gfx/skia/skia/src/gpu/GrYUVProvider.cpp new file mode 100644 index 0000000000..b61b8c38cc --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrYUVProvider.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrYUVProvider.h" + +#include "include/core/SkRefCnt.h" +#include "include/core/SkYUVAIndex.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkCachedData.h" +#include "src/core/SkResourceCache.h" +#include "src/core/SkYUVPlanesCache.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/effects/GrYUVtoRGBEffect.h" + +sk_sp GrYUVProvider::getPlanes(SkYUVASizeInfo* size, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace, + const void* constPlanes[SkYUVASizeInfo::kMaxCount]) { + sk_sp data; + SkYUVPlanesCache::Info yuvInfo; + data.reset(SkYUVPlanesCache::FindAndRef(this->onGetID(), &yuvInfo)); + + void* planes[SkYUVASizeInfo::kMaxCount]; + + if (data.get()) { + planes[0] = (void*)data->data(); // we should always have at least one plane + + for (int i = 1; i < SkYUVASizeInfo::kMaxCount; ++i) { + if (!yuvInfo.fSizeInfo.fWidthBytes[i]) { + SkASSERT(!yuvInfo.fSizeInfo.fWidthBytes[i] && + !yuvInfo.fSizeInfo.fSizes[i].fHeight); + planes[i] = nullptr; + continue; + } + + planes[i] = (uint8_t*)planes[i-1] + (yuvInfo.fSizeInfo.fWidthBytes[i-1] * + yuvInfo.fSizeInfo.fSizes[i-1].fHeight); + } + } else { + // Fetch yuv plane sizes for memory allocation. + if (!this->onQueryYUVA8(&yuvInfo.fSizeInfo, yuvInfo.fYUVAIndices, &yuvInfo.fColorSpace)) { + return nullptr; + } + + // Allocate the memory for YUVA + size_t totalSize(0); + for (int i = 0; i < SkYUVASizeInfo::kMaxCount; i++) { + SkASSERT((yuvInfo.fSizeInfo.fWidthBytes[i] && yuvInfo.fSizeInfo.fSizes[i].fHeight) || + (!yuvInfo.fSizeInfo.fWidthBytes[i] && !yuvInfo.fSizeInfo.fSizes[i].fHeight)); + + totalSize += yuvInfo.fSizeInfo.fWidthBytes[i] * yuvInfo.fSizeInfo.fSizes[i].fHeight; + } + + data.reset(SkResourceCache::NewCachedData(totalSize)); + + planes[0] = data->writable_data(); + + for (int i = 1; i < SkYUVASizeInfo::kMaxCount; ++i) { + if (!yuvInfo.fSizeInfo.fWidthBytes[i]) { + SkASSERT(!yuvInfo.fSizeInfo.fWidthBytes[i] && + !yuvInfo.fSizeInfo.fSizes[i].fHeight); + planes[i] = nullptr; + continue; + } + + planes[i] = (uint8_t*)planes[i-1] + (yuvInfo.fSizeInfo.fWidthBytes[i-1] * + yuvInfo.fSizeInfo.fSizes[i-1].fHeight); + } + + // Get the YUV planes. + if (!this->onGetYUVA8Planes(yuvInfo.fSizeInfo, yuvInfo.fYUVAIndices, planes)) { + return nullptr; + } + + // Decoding is done, cache the resulting YUV planes + SkYUVPlanesCache::Add(this->onGetID(), data.get(), &yuvInfo); + } + + *size = yuvInfo.fSizeInfo; + memcpy(yuvaIndices, yuvInfo.fYUVAIndices, sizeof(yuvInfo.fYUVAIndices)); + *colorSpace = yuvInfo.fColorSpace; + constPlanes[0] = planes[0]; + constPlanes[1] = planes[1]; + constPlanes[2] = planes[2]; + constPlanes[3] = planes[3]; + return data; +} + +void GrYUVProvider::YUVGen_DataReleaseProc(const void*, void* data) { + SkCachedData* cachedData = static_cast(data); + SkASSERT(cachedData); + cachedData->unref(); +} + +sk_sp GrYUVProvider::refAsTextureProxy(GrRecordingContext* ctx, + const GrSurfaceDesc& desc, + GrColorType colorType, + SkColorSpace* srcColorSpace, + SkColorSpace* dstColorSpace) { + SkYUVASizeInfo yuvSizeInfo; + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount]; + SkYUVColorSpace yuvColorSpace; + const void* planes[SkYUVASizeInfo::kMaxCount]; + + sk_sp dataStorage = this->getPlanes(&yuvSizeInfo, yuvaIndices, + &yuvColorSpace, planes); + if (!dataStorage) { + return nullptr; + } + + sk_sp yuvTextureProxies[SkYUVASizeInfo::kMaxCount]; + for (int i = 0; i < SkYUVASizeInfo::kMaxCount; ++i) { + if (yuvSizeInfo.fSizes[i].isEmpty()) { + SkASSERT(!yuvSizeInfo.fWidthBytes[i]); + continue; + } + + int componentWidth = yuvSizeInfo.fSizes[i].fWidth; + int componentHeight = yuvSizeInfo.fSizes[i].fHeight; + // If the sizes of the components are not all the same we choose to create exact-match + // textures for the smaller ones rather than add a texture domain to the draw. + // TODO: revisit this decision to improve texture reuse? + SkBackingFit fit = + (componentWidth != yuvSizeInfo.fSizes[0].fWidth) || + (componentHeight != yuvSizeInfo.fSizes[0].fHeight) + ? SkBackingFit::kExact : SkBackingFit::kApprox; + + SkImageInfo imageInfo = SkImageInfo::MakeA8(componentWidth, componentHeight); + SkPixmap pixmap(imageInfo, planes[i], yuvSizeInfo.fWidthBytes[i]); + SkCachedData* dataStoragePtr = dataStorage.get(); + // We grab a ref to cached yuv data. When the SkImage we create below goes away it will call + // the YUVGen_DataReleaseProc which will release this ref. + // DDL TODO: Currently we end up creating a lazy proxy that will hold onto a ref to the + // SkImage in its lambda. This means that we'll keep the ref on the YUV data around for the + // life time of the proxy and not just upload. For non-DDL draws we should look into + // releasing this SkImage after uploads (by deleting the lambda after instantiation). + dataStoragePtr->ref(); + sk_sp yuvImage = SkImage::MakeFromRaster(pixmap, YUVGen_DataReleaseProc, + dataStoragePtr); + + auto proxyProvider = ctx->priv().proxyProvider(); + yuvTextureProxies[i] = + proxyProvider->createTextureProxy(yuvImage, 1, SkBudgeted::kYes, fit); + + SkASSERT(yuvTextureProxies[i]->width() == yuvSizeInfo.fSizes[i].fWidth); + SkASSERT(yuvTextureProxies[i]->height() == yuvSizeInfo.fSizes[i].fHeight); + } + + // TODO: investigate preallocating mip maps here + auto renderTargetContext = ctx->priv().makeDeferredRenderTargetContext( + SkBackingFit::kExact, desc.fWidth, desc.fHeight, colorType, nullptr, 1, + GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); + if (!renderTargetContext) { + return nullptr; + } + + GrPaint paint; + auto yuvToRgbProcessor = GrYUVtoRGBEffect::Make(yuvTextureProxies, yuvaIndices, yuvColorSpace, + GrSamplerState::Filter::kNearest); + paint.addColorFragmentProcessor(std::move(yuvToRgbProcessor)); + + // If the caller expects the pixels in a different color space than the one from the image, + // apply a color conversion to do this. + std::unique_ptr colorConversionProcessor = + GrColorSpaceXformEffect::Make(srcColorSpace, kOpaque_SkAlphaType, + dstColorSpace, kOpaque_SkAlphaType); + if (colorConversionProcessor) { + paint.addColorFragmentProcessor(std::move(colorConversionProcessor)); + } + + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + const SkRect r = SkRect::MakeIWH(yuvSizeInfo.fSizes[0].fWidth, + yuvSizeInfo.fSizes[0].fHeight); + + SkMatrix m = SkEncodedOriginToMatrix(yuvSizeInfo.fOrigin, r.width(), r.height()); + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, m, r); + + return renderTargetContext->asTextureProxyRef(); +} diff --git a/gfx/skia/skia/src/gpu/GrYUVProvider.h b/gfx/skia/skia/src/gpu/GrYUVProvider.h new file mode 100644 index 0000000000..330c7fbe64 --- /dev/null +++ b/gfx/skia/skia/src/gpu/GrYUVProvider.h @@ -0,0 +1,93 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrYUVProvider_DEFINED +#define GrYUVProvider_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkYUVAIndex.h" +#include "include/core/SkYUVASizeInfo.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrTypesPriv.h" + +class GrBackendFormat; +class GrRecordingContext; +struct GrSurfaceDesc; +class GrTexture; +class GrTextureProxy; +class SkCachedData; + +/** + * There are at least 2 different ways to extract/retrieve YUV planar data... + * - SkPixelRef + * - SkImageGenerator + * + * To share common functionality around using the planar data, we use this abstract base-class + * to represent accessing that data. + */ +class GrYUVProvider { +public: + virtual ~GrYUVProvider() {} + + /** + * On success, this returns a texture proxy that has converted the YUV data from the provider + * into a form that is supported by the GPU (typically transformed into RGB). The texture will + * automatically have a key added, so it can be retrieved from the cache (assuming it is + * requested by a provider w/ the same genID). If srcColorSpace and dstColorSpace are + * specified, then a color conversion from src to dst will be applied to the pixels. + * + * On failure (e.g. the provider had no data), this returns NULL. + */ + sk_sp refAsTextureProxy(GrRecordingContext*, + const GrSurfaceDesc&, + GrColorType colorType, + SkColorSpace* srcColorSpace, + SkColorSpace* dstColorSpace); + + sk_sp getPlanes(SkYUVASizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount], + SkYUVColorSpace*, const void* planes[SkYUVASizeInfo::kMaxCount]); + +private: + virtual uint32_t onGetID() const = 0; + + // These are not meant to be called by a client, only by the implementation + + /** + * If decoding to YUV is supported, this returns true. Otherwise, this + * returns false and does not modify any of the parameters. + * + * @param sizeInfo Output parameter indicating the sizes and required + * allocation widths of the Y, U, V, and A planes. + * @param yuvaIndices How the YUVA planes are used/organized + * @param colorSpace Output parameter. + */ + virtual bool onQueryYUVA8(SkYUVASizeInfo* sizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace) const = 0; + + /** + * Returns true on success and false on failure. + * This always attempts to perform a full decode. If the client only + * wants size, it should call onQueryYUVA8(). + * + * @param sizeInfo Needs to exactly match the values returned by the + * query, except the WidthBytes may be larger than the + * recommendation (but not smaller). + * @param yuvaIndices How the YUVA planes are used/organized + * @param planes Memory for each of the Y, U, V, and A planes. + */ + virtual bool onGetYUVA8Planes(const SkYUVASizeInfo& sizeInfo, + const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + void* planes[]) = 0; + + // This is used as release callback for the YUV data that we capture in an SkImage when + // uploading to a gpu. When the upload is complete and we release the SkImage this callback will + // release the underlying data. + static void YUVGen_DataReleaseProc(const void*, void* data); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/SkGpuDevice.cpp b/gfx/skia/skia/src/gpu/SkGpuDevice.cpp new file mode 100644 index 0000000000..77b9963ba5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/SkGpuDevice.cpp @@ -0,0 +1,1671 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/SkGpuDevice.h" + +#include "include/core/SkImageFilter.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkPicture.h" +#include "include/core/SkSurface.h" +#include "include/core/SkVertices.h" +#include "include/gpu/GrContext.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkShadowFlags.h" +#include "include/private/SkTo.h" +#include "src/core/SkCanvasPriv.h" +#include "src/core/SkClipStack.h" +#include "src/core/SkDraw.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkLatticeIter.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkPictureData.h" +#include "src/core/SkRRectPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkRecord.h" +#include "src/core/SkSpecialImage.h" +#include "src/core/SkStroke.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkVertState.h" +#include "src/gpu/GrBitmapTextureMaker.h" +#include "src/gpu/GrBlurUtils.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrImageTextureMaker.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureAdjuster.h" +#include "src/gpu/GrTracing.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrBicubicEffect.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/text/GrTextTarget.h" +#include "src/image/SkImage_Base.h" +#include "src/image/SkReadPixelsRec.h" +#include "src/image/SkSurface_Gpu.h" +#include "src/utils/SkUTF.h" + +#define ASSERT_SINGLE_OWNER \ +SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fContext->priv().singleOwner());) + + +/////////////////////////////////////////////////////////////////////////////// + +/** Checks that the alpha type is legal and gets constructor flags. Returns false if device creation + should fail. */ +bool SkGpuDevice::CheckAlphaTypeAndGetFlags( + const SkImageInfo* info, SkGpuDevice::InitContents init, unsigned* flags) { + *flags = 0; + if (info) { + switch (info->alphaType()) { + case kPremul_SkAlphaType: + break; + case kOpaque_SkAlphaType: + *flags |= SkGpuDevice::kIsOpaque_Flag; + break; + default: // If it is unpremul or unknown don't try to render + return false; + } + } + if (kClear_InitContents == init) { + *flags |= kNeedClear_Flag; + } + return true; +} + +sk_sp SkGpuDevice::Make(GrContext* context, + std::unique_ptr renderTargetContext, + InitContents init) { + if (!renderTargetContext || context->priv().abandoned()) { + return nullptr; + } + + SkColorType ct = GrColorTypeToSkColorType(renderTargetContext->colorInfo().colorType()); + + unsigned flags; + if (!context->colorTypeSupportedAsSurface(ct) || + !CheckAlphaTypeAndGetFlags(nullptr, init, &flags)) { + return nullptr; + } + return sk_sp(new SkGpuDevice(context, std::move(renderTargetContext), flags)); +} + +sk_sp SkGpuDevice::Make(GrContext* context, SkBudgeted budgeted, + const SkImageInfo& info, int sampleCount, + GrSurfaceOrigin origin, const SkSurfaceProps* props, + GrMipMapped mipMapped, InitContents init) { + unsigned flags; + if (!context->colorTypeSupportedAsSurface(info.colorType()) || + !CheckAlphaTypeAndGetFlags(&info, init, &flags)) { + return nullptr; + } + + auto renderTargetContext = + MakeRenderTargetContext(context, budgeted, info, sampleCount, origin, props, mipMapped); + if (!renderTargetContext) { + return nullptr; + } + + return sk_sp(new SkGpuDevice(context, std::move(renderTargetContext), flags)); +} + +static SkImageInfo make_info(GrRenderTargetContext* context, bool opaque) { + SkColorType colorType = GrColorTypeToSkColorType(context->colorInfo().colorType()); + return SkImageInfo::Make(context->width(), context->height(), colorType, + opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType, + context->colorInfo().refColorSpace()); +} + +SkGpuDevice::SkGpuDevice(GrContext* context, + std::unique_ptr renderTargetContext, + unsigned flags) + : INHERITED(make_info(renderTargetContext.get(), SkToBool(flags & kIsOpaque_Flag)), + renderTargetContext->surfaceProps()) + , fContext(SkRef(context)) + , fRenderTargetContext(std::move(renderTargetContext)) { + if (flags & kNeedClear_Flag) { + this->clearAll(); + } +} + +std::unique_ptr SkGpuDevice::MakeRenderTargetContext( + GrContext* context, + SkBudgeted budgeted, + const SkImageInfo& origInfo, + int sampleCount, + GrSurfaceOrigin origin, + const SkSurfaceProps* surfaceProps, + GrMipMapped mipMapped) { + if (!context) { + return nullptr; + } + + // This method is used to create SkGpuDevice's for SkSurface_Gpus. In this case + // they need to be exact. + return context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kExact, origInfo.width(), origInfo.height(), + SkColorTypeToGrColorType(origInfo.colorType()), origInfo.refColorSpace(), sampleCount, + mipMapped, origin, surfaceProps, budgeted); +} + +sk_sp SkGpuDevice::filterTexture(SkSpecialImage* srcImg, + int left, int top, + SkIPoint* offset, + const SkImageFilter* filter) { + SkASSERT(srcImg->isTextureBacked()); + SkASSERT(filter); + + SkMatrix matrix = this->ctm(); + matrix.postTranslate(SkIntToScalar(-left), SkIntToScalar(-top)); + const SkIRect clipBounds = this->devClipBounds().makeOffset(-left, -top); + sk_sp cache(this->getImageFilterCache()); + SkColorType colorType = GrColorTypeToSkColorType(fRenderTargetContext->colorInfo().colorType()); + if (colorType == kUnknown_SkColorType) { + colorType = kRGBA_8888_SkColorType; + } + SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), colorType, + fRenderTargetContext->colorInfo().colorSpace(), srcImg); + + return as_IFB(filter)->filterImage(ctx).imageAndOffset(offset); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkGpuDevice::onReadPixels(const SkPixmap& pm, int x, int y) { + ASSERT_SINGLE_OWNER + + if (!SkImageInfoValidConversion(pm.info(), this->imageInfo())) { + return false; + } + + return fRenderTargetContext->readPixels(pm.info(), pm.writable_addr(), pm.rowBytes(), {x, y}); +} + +bool SkGpuDevice::onWritePixels(const SkPixmap& pm, int x, int y) { + ASSERT_SINGLE_OWNER + + if (!SkImageInfoValidConversion(this->imageInfo(), pm.info())) { + return false; + } + + return fRenderTargetContext->writePixels(pm.info(), pm.addr(), pm.rowBytes(), {x, y}); +} + +bool SkGpuDevice::onAccessPixels(SkPixmap* pmap) { + ASSERT_SINGLE_OWNER + return false; +} + +GrRenderTargetContext* SkGpuDevice::accessRenderTargetContext() { + ASSERT_SINGLE_OWNER + return fRenderTargetContext.get(); +} + +void SkGpuDevice::clearAll() { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "clearAll", fContext.get()); + + SkIRect rect = SkIRect::MakeWH(this->width(), this->height()); + fRenderTargetContext->clear(&rect, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); +} + +void SkGpuDevice::replaceRenderTargetContext(std::unique_ptr rtc, + bool shouldRetainContent) { + SkASSERT(rtc->width() == this->width()); + SkASSERT(rtc->height() == this->height()); + SkASSERT(rtc->numSamples() == fRenderTargetContext->numSamples()); + SkASSERT(rtc->asSurfaceProxy()->priv().isExact()); + if (shouldRetainContent) { + if (this->context()->abandoned()) { + return; + } + + SkASSERT(fRenderTargetContext->asTextureProxy()); + SkAssertResult(rtc->blitTexture(fRenderTargetContext->asTextureProxy(), + fRenderTargetContext->colorInfo().colorType(), + SkIRect::MakeWH(this->width(), this->height()), + SkIPoint::Make(0,0))); + } + + fRenderTargetContext = std::move(rtc); +} + +void SkGpuDevice::replaceRenderTargetContext(bool shouldRetainContent) { + ASSERT_SINGLE_OWNER + + SkBudgeted budgeted = fRenderTargetContext->priv().isBudgeted(); + + // This entry point is used by SkSurface_Gpu::onCopyOnWrite so it must create a + // kExact-backed render target context. + auto newRTC = MakeRenderTargetContext(this->context(), + budgeted, + this->imageInfo(), + fRenderTargetContext->numSamples(), + fRenderTargetContext->origin(), + &this->surfaceProps(), + fRenderTargetContext->mipMapped()); + if (!newRTC) { + return; + } + this->replaceRenderTargetContext(std::move(newRTC), shouldRetainContent); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawPaint(const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawPaint", fContext.get()); + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + &grPaint)) { + return; + } + + fRenderTargetContext->drawPaint(this->clip(), std::move(grPaint), this->ctm()); +} + +static inline GrPrimitiveType point_mode_to_primitive_type(SkCanvas::PointMode mode) { + switch (mode) { + case SkCanvas::kPoints_PointMode: + return GrPrimitiveType::kPoints; + case SkCanvas::kLines_PointMode: + return GrPrimitiveType::kLines; + case SkCanvas::kPolygon_PointMode: + return GrPrimitiveType::kLineStrip; + } + SK_ABORT("Unexpected mode"); +} + +void SkGpuDevice::drawPoints(SkCanvas::PointMode mode, + size_t count, const SkPoint pts[], const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawPoints", fContext.get()); + SkScalar width = paint.getStrokeWidth(); + if (width < 0) { + return; + } + + if (paint.getPathEffect() && 2 == count && SkCanvas::kLines_PointMode == mode) { + GrStyle style(paint, SkPaint::kStroke_Style); + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, + this->ctm(), &grPaint)) { + return; + } + SkPath path; + path.setIsVolatile(true); + path.moveTo(pts[0]); + path.lineTo(pts[1]); + fRenderTargetContext->drawPath(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), path, style); + return; + } + + SkScalar scales[2]; + bool isHairline = (0 == width) || (1 == width && this->ctm().getMinMaxScales(scales) && + SkScalarNearlyEqual(scales[0], 1.f) && + SkScalarNearlyEqual(scales[1], 1.f)); + // we only handle non-antialiased hairlines and paints without path effects or mask filters, + // else we let the SkDraw call our drawPath() + if (!isHairline || paint.getPathEffect() || paint.getMaskFilter() || paint.isAntiAlias()) { + SkRasterClip rc(this->devClipBounds()); + SkDraw draw; + draw.fDst = SkPixmap(SkImageInfo::MakeUnknown(this->width(), this->height()), nullptr, 0); + draw.fMatrix = &this->ctm(); + draw.fRC = &rc; + draw.drawPoints(mode, count, pts, paint, this); + return; + } + + GrPrimitiveType primitiveType = point_mode_to_primitive_type(mode); + + const SkMatrix* viewMatrix = &this->ctm(); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // This offsetting in device space matches the expectations of the Android framework for non-AA + // points and lines. + SkMatrix tempMatrix; + if (GrIsPrimTypeLines(primitiveType) || GrPrimitiveType::kPoints == primitiveType) { + tempMatrix = *viewMatrix; + static const SkScalar kOffset = 0.063f; // Just greater than 1/16. + tempMatrix.postTranslate(kOffset, kOffset); + viewMatrix = &tempMatrix; + } +#endif + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, *viewMatrix, + &grPaint)) { + return; + } + + static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode; + sk_sp vertices = SkVertices::MakeCopy(kIgnoredMode, SkToS32(count), pts, nullptr, + nullptr); + + fRenderTargetContext->drawVertices(this->clip(), std::move(grPaint), *viewMatrix, + std::move(vertices), nullptr, 0, &primitiveType); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawRect(const SkRect& rect, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawRect", fContext.get()); + + GrStyle style(paint); + + // A couple reasons we might need to call drawPath. + if (paint.getMaskFilter() || paint.getPathEffect()) { + GrShape shape(rect, style); + + GrBlurUtils::drawShapeWithMaskFilter(fContext.get(), fRenderTargetContext.get(), + this->clip(), paint, this->ctm(), shape); + return; + } + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + &grPaint)) { + return; + } + + fRenderTargetContext->drawRect(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), rect, &style); +} + +void SkGpuDevice::drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], + SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, + SkBlendMode mode) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawEdgeAAQuad", fContext.get()); + + SkPMColor4f dstColor = SkColor4fPrepForDst(color, fRenderTargetContext->colorInfo()).premul(); + + GrPaint grPaint; + grPaint.setColor4f(dstColor); + if (mode != SkBlendMode::kSrcOver) { + grPaint.setXPFactory(SkBlendMode_AsXPFactory(mode)); + } + + // This is exclusively meant for tiling operations, so keep AA enabled to handle MSAA seaming + GrQuadAAFlags grAA = SkToGrQuadAAFlags(aaFlags); + if (clip) { + // Use fillQuadWithEdgeAA + fRenderTargetContext->fillQuadWithEdgeAA(this->clip(), std::move(grPaint), GrAA::kYes, grAA, + this->ctm(), clip, nullptr); + } else { + // Use fillRectWithEdgeAA to preserve mathematical properties of dst being rectangular + fRenderTargetContext->fillRectWithEdgeAA(this->clip(), std::move(grPaint), GrAA::kYes, grAA, + this->ctm(), rect); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawRRect(const SkRRect& rrect, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawRRect", fContext.get()); + + SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter()); + if (mf) { + if (mf->hasFragmentProcessor()) { + mf = nullptr; // already handled in SkPaintToGrPaint + } + } + + GrStyle style(paint); + + if (mf || style.pathEffect()) { + // A path effect will presumably transform this rrect into something else. + GrShape shape(rrect, style); + + GrBlurUtils::drawShapeWithMaskFilter(fContext.get(), fRenderTargetContext.get(), + this->clip(), paint, this->ctm(), shape); + return; + } + + SkASSERT(!style.pathEffect()); + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + &grPaint)) { + return; + } + + fRenderTargetContext->drawRRect(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), rrect, style); +} + + +void SkGpuDevice::drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawDRRect", fContext.get()); + if (outer.isEmpty()) { + return; + } + + if (inner.isEmpty()) { + return this->drawRRect(outer, paint); + } + + SkStrokeRec stroke(paint); + + if (stroke.isFillStyle() && !paint.getMaskFilter() && !paint.getPathEffect()) { + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, + this->ctm(), &grPaint)) { + return; + } + + fRenderTargetContext->drawDRRect(this->clip(), std::move(grPaint), + GrAA(paint.isAntiAlias()), this->ctm(), outer, inner); + return; + } + + SkPath path; + path.setIsVolatile(true); + path.addRRect(outer); + path.addRRect(inner); + path.setFillType(SkPath::kEvenOdd_FillType); + + // TODO: We are losing the possible mutability of the path here but this should probably be + // fixed by upgrading GrShape to handle DRRects. + GrShape shape(path, paint); + + GrBlurUtils::drawShapeWithMaskFilter(fContext.get(), fRenderTargetContext.get(), this->clip(), + paint, this->ctm(), shape); +} + + +///////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawRegion(const SkRegion& region, const SkPaint& paint) { + if (paint.getMaskFilter()) { + SkPath path; + region.getBoundaryPath(&path); + path.setIsVolatile(true); + return this->drawPath(path, paint, true); + } + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + &grPaint)) { + return; + } + + fRenderTargetContext->drawRegion(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), region, GrStyle(paint)); +} + +void SkGpuDevice::drawOval(const SkRect& oval, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawOval", fContext.get()); + + if (paint.getMaskFilter()) { + // The RRect path can handle special case blurring + SkRRect rr = SkRRect::MakeOval(oval); + return this->drawRRect(rr, paint); + } + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + &grPaint)) { + return; + } + + fRenderTargetContext->drawOval(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), oval, GrStyle(paint)); +} + +void SkGpuDevice::drawArc(const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawArc", fContext.get()); + if (paint.getMaskFilter()) { + this->INHERITED::drawArc(oval, startAngle, sweepAngle, useCenter, paint); + return; + } + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + &grPaint)) { + return; + } + + fRenderTargetContext->drawArc(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), oval, startAngle, sweepAngle, useCenter, + GrStyle(paint)); +} + +#include "include/core/SkMaskFilter.h" + +/////////////////////////////////////////////////////////////////////////////// +void SkGpuDevice::drawStrokedLine(const SkPoint points[2], + const SkPaint& origPaint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawStrokedLine", fContext.get()); + // Adding support for round capping would require a + // GrRenderTargetContext::fillRRectWithLocalMatrix entry point + SkASSERT(SkPaint::kRound_Cap != origPaint.getStrokeCap()); + SkASSERT(SkPaint::kStroke_Style == origPaint.getStyle()); + SkASSERT(!origPaint.getPathEffect()); + SkASSERT(!origPaint.getMaskFilter()); + + const SkScalar halfWidth = 0.5f * origPaint.getStrokeWidth(); + SkASSERT(halfWidth > 0); + + SkVector v = points[1] - points[0]; + + SkScalar length = SkPoint::Normalize(&v); + if (!length) { + v.fX = 1.0f; + v.fY = 0.0f; + } + + SkPaint newPaint(origPaint); + newPaint.setStyle(SkPaint::kFill_Style); + + SkScalar xtraLength = 0.0f; + if (SkPaint::kButt_Cap != origPaint.getStrokeCap()) { + xtraLength = halfWidth; + } + + SkPoint mid = points[0] + points[1]; + mid.scale(0.5f); + + SkRect rect = SkRect::MakeLTRB(mid.fX-halfWidth, mid.fY - 0.5f*length - xtraLength, + mid.fX+halfWidth, mid.fY + 0.5f*length + xtraLength); + SkMatrix m; + m.setSinCos(v.fX, -v.fY, mid.fX, mid.fY); + + SkMatrix local = m; + + m.postConcat(this->ctm()); + + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), newPaint, m, + &grPaint)) { + return; + } + + fRenderTargetContext->fillRectWithLocalMatrix( + this->clip(), std::move(grPaint), GrAA(newPaint.isAntiAlias()), m, rect, local); +} + +void SkGpuDevice::drawPath(const SkPath& origSrcPath, const SkPaint& paint, bool pathIsMutable) { + ASSERT_SINGLE_OWNER + if (!origSrcPath.isInverseFillType() && !paint.getPathEffect()) { + SkPoint points[2]; + if (SkPaint::kStroke_Style == paint.getStyle() && paint.getStrokeWidth() > 0 && + !paint.getMaskFilter() && SkPaint::kRound_Cap != paint.getStrokeCap() && + this->ctm().preservesRightAngles() && origSrcPath.isLine(points)) { + // Path-based stroking looks better for thin rects + SkScalar strokeWidth = this->ctm().getMaxScale() * paint.getStrokeWidth(); + if (strokeWidth >= 1.0f) { + // Round capping support is currently disabled b.c. it would require a RRect + // GrDrawOp that takes a localMatrix. + this->drawStrokedLine(points, paint); + return; + } + } + } + + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawPath", fContext.get()); + if (!paint.getMaskFilter()) { + GrPaint grPaint; + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), paint, + this->ctm(), &grPaint)) { + return; + } + fRenderTargetContext->drawPath(this->clip(), std::move(grPaint), GrAA(paint.isAntiAlias()), + this->ctm(), origSrcPath, GrStyle(paint)); + return; + } + + // TODO: losing possible mutability of 'origSrcPath' here + GrShape shape(origSrcPath, paint); + + GrBlurUtils::drawShapeWithMaskFilter(fContext.get(), fRenderTargetContext.get(), this->clip(), + paint, this->ctm(), shape); +} + +static const int kBmpSmallTileSize = 1 << 10; + +static inline int get_tile_count(const SkIRect& srcRect, int tileSize) { + int tilesX = (srcRect.fRight / tileSize) - (srcRect.fLeft / tileSize) + 1; + int tilesY = (srcRect.fBottom / tileSize) - (srcRect.fTop / tileSize) + 1; + return tilesX * tilesY; +} + +static int determine_tile_size(const SkIRect& src, int maxTileSize) { + if (maxTileSize <= kBmpSmallTileSize) { + return maxTileSize; + } + + size_t maxTileTotalTileSize = get_tile_count(src, maxTileSize); + size_t smallTotalTileSize = get_tile_count(src, kBmpSmallTileSize); + + maxTileTotalTileSize *= maxTileSize * maxTileSize; + smallTotalTileSize *= kBmpSmallTileSize * kBmpSmallTileSize; + + if (maxTileTotalTileSize > 2 * smallTotalTileSize) { + return kBmpSmallTileSize; + } else { + return maxTileSize; + } +} + +// Given a bitmap, an optional src rect, and a context with a clip and matrix determine what +// pixels from the bitmap are necessary. +static void determine_clipped_src_rect(int width, int height, + const GrClip& clip, + const SkMatrix& viewMatrix, + const SkMatrix& srcToDstRect, + const SkISize& imageSize, + const SkRect* srcRectPtr, + SkIRect* clippedSrcIRect) { + clip.getConservativeBounds(width, height, clippedSrcIRect, nullptr); + SkMatrix inv = SkMatrix::Concat(viewMatrix, srcToDstRect); + if (!inv.invert(&inv)) { + clippedSrcIRect->setEmpty(); + return; + } + SkRect clippedSrcRect = SkRect::Make(*clippedSrcIRect); + inv.mapRect(&clippedSrcRect); + if (srcRectPtr) { + if (!clippedSrcRect.intersect(*srcRectPtr)) { + clippedSrcIRect->setEmpty(); + return; + } + } + clippedSrcRect.roundOut(clippedSrcIRect); + SkIRect bmpBounds = SkIRect::MakeSize(imageSize); + if (!clippedSrcIRect->intersect(bmpBounds)) { + clippedSrcIRect->setEmpty(); + } +} + +const GrCaps* SkGpuDevice::caps() const { + return fContext->priv().caps(); +} + +bool SkGpuDevice::shouldTileImageID(uint32_t imageID, + const SkIRect& imageRect, + const SkMatrix& viewMatrix, + const SkMatrix& srcToDstRect, + const GrSamplerState& params, + const SkRect* srcRectPtr, + int maxTileSize, + int* tileSize, + SkIRect* clippedSubset) const { + ASSERT_SINGLE_OWNER + // if it's larger than the max tile size, then we have no choice but tiling. + if (imageRect.width() > maxTileSize || imageRect.height() > maxTileSize) { + determine_clipped_src_rect(fRenderTargetContext->width(), fRenderTargetContext->height(), + this->clip(), viewMatrix, srcToDstRect, imageRect.size(), + srcRectPtr, clippedSubset); + *tileSize = determine_tile_size(*clippedSubset, maxTileSize); + return true; + } + + // If the image would only produce 4 tiles of the smaller size, don't bother tiling it. + const size_t area = imageRect.width() * imageRect.height(); + if (area < 4 * kBmpSmallTileSize * kBmpSmallTileSize) { + return false; + } + + // At this point we know we could do the draw by uploading the entire bitmap + // as a texture. However, if the texture would be large compared to the + // cache size and we don't require most of it for this draw then tile to + // reduce the amount of upload and cache spill. + + // assumption here is that sw bitmap size is a good proxy for its size as + // a texture + size_t bmpSize = area * sizeof(SkPMColor); // assume 32bit pixels + size_t cacheSize = fContext->getResourceCacheLimit(); + if (bmpSize < cacheSize / 2) { + return false; + } + + // Figure out how much of the src we will need based on the src rect and clipping. Reject if + // tiling memory savings would be < 50%. + determine_clipped_src_rect(fRenderTargetContext->width(), fRenderTargetContext->height(), + this->clip(), viewMatrix, srcToDstRect, imageRect.size(), srcRectPtr, + clippedSubset); + *tileSize = kBmpSmallTileSize; // already know whole bitmap fits in one max sized tile. + size_t usedTileBytes = get_tile_count(*clippedSubset, kBmpSmallTileSize) * + kBmpSmallTileSize * kBmpSmallTileSize * + sizeof(SkPMColor); // assume 32bit pixels; + + return usedTileBytes * 2 < bmpSize; +} + +bool SkGpuDevice::shouldTileImage(const SkImage* image, const SkRect* srcRectPtr, + SkCanvas::SrcRectConstraint constraint, SkFilterQuality quality, + const SkMatrix& viewMatrix, + const SkMatrix& srcToDstRect) const { + ASSERT_SINGLE_OWNER + // If image is explicitly texture backed then we shouldn't get here. + SkASSERT(!image->isTextureBacked()); + + GrSamplerState samplerState; + bool doBicubic; + GrSamplerState::Filter textureFilterMode = GrSkFilterQualityToGrFilterMode( + image->width(), image->height(), quality, viewMatrix, srcToDstRect, + fContext->priv().options().fSharpenMipmappedTextures, &doBicubic); + + int tileFilterPad; + if (doBicubic) { + tileFilterPad = GrBicubicEffect::kFilterTexelPad; + } else if (GrSamplerState::Filter::kNearest == textureFilterMode) { + tileFilterPad = 0; + } else { + tileFilterPad = 1; + } + samplerState.setFilterMode(textureFilterMode); + + int maxTileSize = this->caps()->maxTileSize() - 2 * tileFilterPad; + + // these are output, which we safely ignore, as we just want to know the predicate + int outTileSize; + SkIRect outClippedSrcRect; + + return this->shouldTileImageID(image->unique(), image->bounds(), viewMatrix, srcToDstRect, + samplerState, srcRectPtr, maxTileSize, &outTileSize, + &outClippedSrcRect); +} + +// This method outsets 'iRect' by 'outset' all around and then clamps its extents to +// 'clamp'. 'offset' is adjusted to remain positioned over the top-left corner +// of 'iRect' for all possible outsets/clamps. +static inline void clamped_outset_with_offset(SkIRect* iRect, + int outset, + SkPoint* offset, + const SkIRect& clamp) { + iRect->outset(outset, outset); + + int leftClampDelta = clamp.fLeft - iRect->fLeft; + if (leftClampDelta > 0) { + offset->fX -= outset - leftClampDelta; + iRect->fLeft = clamp.fLeft; + } else { + offset->fX -= outset; + } + + int topClampDelta = clamp.fTop - iRect->fTop; + if (topClampDelta > 0) { + offset->fY -= outset - topClampDelta; + iRect->fTop = clamp.fTop; + } else { + offset->fY -= outset; + } + + if (iRect->fRight > clamp.fRight) { + iRect->fRight = clamp.fRight; + } + if (iRect->fBottom > clamp.fBottom) { + iRect->fBottom = clamp.fBottom; + } +} + +// Break 'bitmap' into several tiles to draw it since it has already +// been determined to be too large to fit in VRAM +void SkGpuDevice::drawTiledBitmap(const SkBitmap& bitmap, + const SkMatrix& viewMatrix, + const SkMatrix& dstMatrix, + const SkRect& srcRect, + const SkIRect& clippedSrcIRect, + const GrSamplerState& params, + const SkPaint& origPaint, + SkCanvas::SrcRectConstraint constraint, + int tileSize, + bool bicubic) { + ASSERT_SINGLE_OWNER + + // This is the funnel for all paths that draw tiled bitmaps/images. Log histogram entries. + SK_HISTOGRAM_BOOLEAN("DrawTiled", true); + LogDrawScaleFactor(viewMatrix, SkMatrix::I(), origPaint.getFilterQuality()); + + const SkPaint* paint = &origPaint; + SkPaint tempPaint; + if (origPaint.isAntiAlias() && fRenderTargetContext->numSamples() <= 1) { + // Drop antialiasing to avoid seams at tile boundaries. + tempPaint = origPaint; + tempPaint.setAntiAlias(false); + paint = &tempPaint; + } + SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect); + + int nx = bitmap.width() / tileSize; + int ny = bitmap.height() / tileSize; + for (int x = 0; x <= nx; x++) { + for (int y = 0; y <= ny; y++) { + SkRect tileR; + tileR.setLTRB(SkIntToScalar(x * tileSize), SkIntToScalar(y * tileSize), + SkIntToScalar((x + 1) * tileSize), SkIntToScalar((y + 1) * tileSize)); + + if (!SkRect::Intersects(tileR, clippedSrcRect)) { + continue; + } + + if (!tileR.intersect(srcRect)) { + continue; + } + + SkIRect iTileR; + tileR.roundOut(&iTileR); + SkVector offset = SkPoint::Make(SkIntToScalar(iTileR.fLeft), + SkIntToScalar(iTileR.fTop)); + SkRect rectToDraw = tileR; + dstMatrix.mapRect(&rectToDraw); + if (GrSamplerState::Filter::kNearest != params.filter() || bicubic) { + SkIRect iClampRect; + + if (SkCanvas::kFast_SrcRectConstraint == constraint) { + // In bleed mode we want to always expand the tile on all edges + // but stay within the bitmap bounds + iClampRect = SkIRect::MakeWH(bitmap.width(), bitmap.height()); + } else { + // In texture-domain/clamp mode we only want to expand the + // tile on edges interior to "srcRect" (i.e., we want to + // not bleed across the original clamped edges) + srcRect.roundOut(&iClampRect); + } + int outset = bicubic ? GrBicubicEffect::kFilterTexelPad : 1; + clamped_outset_with_offset(&iTileR, outset, &offset, iClampRect); + } + + SkBitmap tmpB; + if (bitmap.extractSubset(&tmpB, iTileR)) { + // now offset it to make it "local" to our tmp bitmap + tileR.offset(-offset.fX, -offset.fY); + // de-optimized this determination + bool needsTextureDomain = true; + this->drawBitmapTile(tmpB, + viewMatrix, + rectToDraw, + tileR, + params, + *paint, + constraint, + bicubic, + needsTextureDomain); + } + } + } +} + +void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap, + const SkMatrix& viewMatrix, + const SkRect& dstRect, + const SkRect& srcRect, + const GrSamplerState& samplerState, + const SkPaint& paint, + SkCanvas::SrcRectConstraint constraint, + bool bicubic, + bool needsTextureDomain) { + // We should have already handled bitmaps larger than the max texture size. + SkASSERT(bitmap.width() <= this->caps()->maxTextureSize() && + bitmap.height() <= this->caps()->maxTextureSize()); + // We should be respecting the max tile size by the time we get here. + SkASSERT(bitmap.width() <= this->caps()->maxTileSize() && + bitmap.height() <= this->caps()->maxTileSize()); + SkASSERT(!samplerState.isRepeated()); + + SkScalar scales[2] = {1.f, 1.f}; + sk_sp proxy = + GrRefCachedBitmapTextureProxy(fContext.get(), bitmap, samplerState, scales); + if (!proxy) { + return; + } + + // Compute a matrix that maps the rect we will draw to the src rect. + SkMatrix texMatrix = SkMatrix::MakeRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit); + texMatrix.postScale(scales[0], scales[1]); + + GrColorType srcColorType = SkColorTypeToGrColorType(bitmap.colorType()); + + // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring + // the rest from the SkPaint. + std::unique_ptr fp; + + if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) { + // Use a constrained texture domain to avoid color bleeding + SkRect domain; + if (srcRect.width() > SK_Scalar1) { + domain.fLeft = srcRect.fLeft + 0.5f; + domain.fRight = srcRect.fRight - 0.5f; + } else { + domain.fLeft = domain.fRight = srcRect.centerX(); + } + if (srcRect.height() > SK_Scalar1) { + domain.fTop = srcRect.fTop + 0.5f; + domain.fBottom = srcRect.fBottom - 0.5f; + } else { + domain.fTop = domain.fBottom = srcRect.centerY(); + } + if (bicubic) { + static constexpr auto kDir = GrBicubicEffect::Direction::kXY; + fp = GrBicubicEffect::Make(std::move(proxy), srcColorType, texMatrix, domain, kDir, + bitmap.alphaType()); + } else { + fp = GrTextureDomainEffect::Make(std::move(proxy), srcColorType, texMatrix, domain, + GrTextureDomain::kClamp_Mode, samplerState.filter()); + } + } else if (bicubic) { + SkASSERT(GrSamplerState::Filter::kNearest == samplerState.filter()); + GrSamplerState::WrapMode wrapMode[2] = {samplerState.wrapModeX(), samplerState.wrapModeY()}; + static constexpr auto kDir = GrBicubicEffect::Direction::kXY; + fp = GrBicubicEffect::Make(std::move(proxy), srcColorType, texMatrix, wrapMode, kDir, + bitmap.alphaType()); + } else { + fp = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, texMatrix, samplerState); + } + + fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(), bitmap.alphaType(), + fRenderTargetContext->colorInfo().colorSpace()); + GrPaint grPaint; + if (!SkPaintToGrPaintWithTexture(this->context(), fRenderTargetContext->colorInfo(), paint, + viewMatrix, std::move(fp), + kAlpha_8_SkColorType == bitmap.colorType(), &grPaint)) { + return; + } + + // Coverage-based AA would cause seams between tiles. + GrAA aa = GrAA(paint.isAntiAlias() && fRenderTargetContext->numSamples() > 1); + fRenderTargetContext->drawRect(this->clip(), std::move(grPaint), aa, viewMatrix, dstRect); +} + +void SkGpuDevice::drawSprite(const SkBitmap& bitmap, + int left, int top, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawSprite", fContext.get()); + + if (fContext->priv().abandoned()) { + return; + } + + sk_sp srcImg = this->makeSpecial(bitmap); + if (!srcImg) { + return; + } + + this->drawSpecial(srcImg.get(), left, top, paint, nullptr, SkMatrix::I()); +} + + +void SkGpuDevice::drawSpecial(SkSpecialImage* special, int left, int top, const SkPaint& paint, + SkImage* clipImage, const SkMatrix& clipMatrix) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawSpecial", fContext.get()); + + sk_sp result; + if (paint.getImageFilter()) { + SkIPoint offset = { 0, 0 }; + + result = this->filterTexture(special, left, top, &offset, paint.getImageFilter()); + if (!result) { + return; + } + + left += offset.fX; + top += offset.fY; + } else { + result = sk_ref_sp(special); + } + + SkASSERT(result->isTextureBacked()); + sk_sp proxy = result->asTextureProxyRef(this->context()); + if (!proxy) { + return; + } + + SkMatrix ctm = this->ctm(); + ctm.postTranslate(-SkIntToScalar(left), -SkIntToScalar(top)); + + SkPaint tmpUnfiltered(paint); + if (tmpUnfiltered.getMaskFilter()) { + tmpUnfiltered.setMaskFilter(tmpUnfiltered.getMaskFilter()->makeWithMatrix(ctm)); + } + + tmpUnfiltered.setImageFilter(nullptr); + + GrColorType srcColorType = SkColorTypeToGrColorType(result->colorType()); + auto fp = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, SkMatrix::I()); + fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(), result->alphaType(), + fRenderTargetContext->colorInfo().colorSpace()); + if (GrColorTypeIsAlphaOnly(SkColorTypeToGrColorType(result->colorType()))) { + fp = GrFragmentProcessor::MakeInputPremulAndMulByOutput(std::move(fp)); + } else { + if (paint.getColor4f().isOpaque()) { + fp = GrFragmentProcessor::OverrideInput(std::move(fp), SK_PMColor4fWHITE, false); + } else { + fp = GrFragmentProcessor::MulChildByInputAlpha(std::move(fp)); + } + } + + GrPaint grPaint; + if (!SkPaintToGrPaintReplaceShader(this->context(), fRenderTargetContext->colorInfo(), + tmpUnfiltered, std::move(fp), &grPaint)) { + return; + } + + const SkIRect& subset = result->subset(); + SkRect dstRect = SkRect::Make(SkIRect::MakeXYWH(left, top, subset.width(), subset.height())); + SkRect srcRect = SkRect::Make(subset); + if (clipImage) { + // Add the image as a simple texture effect applied to coverage. Accessing content outside + // of the clip image should behave as if it were a decal (i.e. zero coverage). However, to + // limit pixels touched and hardware checks, we draw the clip image geometry to get the + // decal effect. + GrSamplerState sampler = paint.getFilterQuality() > kNone_SkFilterQuality ? + GrSamplerState::ClampBilerp() : GrSamplerState::ClampNearest(); + sk_sp clipProxy = as_IB(clipImage)->asTextureProxyRef(this->context(), + sampler, nullptr); + // Fold clip matrix into ctm + ctm.preConcat(clipMatrix); + SkMatrix inverseClipMatrix; + + std::unique_ptr cfp; + if (clipProxy && ctm.invert(&inverseClipMatrix)) { + GrColorType srcColorType = SkColorTypeToGrColorType(clipImage->colorType()); + cfp = GrSimpleTextureEffect::Make(std::move(clipProxy), srcColorType, inverseClipMatrix, + sampler); + if (srcColorType != GrColorType::kAlpha_8) { + cfp = GrFragmentProcessor::SwizzleOutput(std::move(cfp), GrSwizzle::AAAA()); + } + } + + if (cfp) { + // If the grPaint already has coverage, this adds an additional stage that multiples + // the image's alpha channel with the prior coverage. + grPaint.addCoverageFragmentProcessor(std::move(cfp)); + + // Undo the offset that was needed for shader coord transforms to get the transform for + // the actual drawn geometry. + ctm.postTranslate(SkIntToScalar(left), SkIntToScalar(top)); + inverseClipMatrix.preTranslate(-SkIntToScalar(left), -SkIntToScalar(top)); + SkRect clipGeometry = SkRect::MakeWH(clipImage->width(), clipImage->height()); + if (!clipGeometry.contains(inverseClipMatrix.mapRect(dstRect))) { + // Draw the clip geometry since it is smaller, using dstRect as an extra scissor + SkClipStack clip(this->cs()); + clip.clipDevRect(SkIRect::MakeXYWH(left, top, subset.width(), subset.height()), + SkClipOp::kIntersect); + SkMatrix local = SkMatrix::Concat(SkMatrix::MakeRectToRect( + dstRect, srcRect, SkMatrix::kFill_ScaleToFit), ctm); + fRenderTargetContext->fillRectWithLocalMatrix(GrClipStackClip(&clip), + std::move(grPaint), GrAA(paint.isAntiAlias()), ctm, clipGeometry, local); + return; + } + // Else fall through and draw the subset since that is contained in the clip geometry + } + // Else some issue configuring the coverage FP, so just draw without the clip mask image + } + // Draw directly in screen space, possibly with an extra coverage processor + fRenderTargetContext->fillRectToRect(this->clip(), std::move(grPaint), + GrAA(paint.isAntiAlias()), SkMatrix::I(), dstRect, srcRect); +} + +void SkGpuDevice::drawBitmapRect(const SkBitmap& bitmap, + const SkRect* src, const SkRect& origDst, + const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { + ASSERT_SINGLE_OWNER + // The src rect is inferred to be the bmp bounds if not provided. Otherwise, the src rect must + // be clipped to the bmp bounds. To determine tiling parameters we need the filter mode which + // in turn requires knowing the src-to-dst mapping. If the src was clipped to the bmp bounds + // then we use the src-to-dst mapping to compute a new clipped dst rect. + const SkRect* dst = &origDst; + const SkRect bmpBounds = SkRect::MakeIWH(bitmap.width(), bitmap.height()); + // Compute matrix from the two rectangles + if (!src) { + src = &bmpBounds; + } + + SkMatrix srcToDstMatrix; + if (!srcToDstMatrix.setRectToRect(*src, *dst, SkMatrix::kFill_ScaleToFit)) { + return; + } + SkRect tmpSrc, tmpDst; + if (src != &bmpBounds) { + if (!bmpBounds.contains(*src)) { + tmpSrc = *src; + if (!tmpSrc.intersect(bmpBounds)) { + return; // nothing to draw + } + src = &tmpSrc; + srcToDstMatrix.mapRect(&tmpDst, *src); + dst = &tmpDst; + } + } + + int maxTileSize = this->caps()->maxTileSize(); + + // The tile code path doesn't currently support AA, so if the paint asked for aa and we could + // draw untiled, then we bypass checking for tiling purely for optimization reasons. + bool useCoverageAA = fRenderTargetContext->numSamples() <= 1 && + paint.isAntiAlias() && bitmap.width() <= maxTileSize && + bitmap.height() <= maxTileSize; + + bool skipTileCheck = useCoverageAA || paint.getMaskFilter(); + + if (!skipTileCheck) { + int tileSize; + SkIRect clippedSrcRect; + + GrSamplerState sampleState; + bool doBicubic; + GrSamplerState::Filter textureFilterMode = GrSkFilterQualityToGrFilterMode( + bitmap.width(), bitmap.height(), paint.getFilterQuality(), this->ctm(), + srcToDstMatrix, fContext->priv().options().fSharpenMipmappedTextures, &doBicubic); + + int tileFilterPad; + + if (doBicubic) { + tileFilterPad = GrBicubicEffect::kFilterTexelPad; + } else if (GrSamplerState::Filter::kNearest == textureFilterMode) { + tileFilterPad = 0; + } else { + tileFilterPad = 1; + } + sampleState.setFilterMode(textureFilterMode); + + int maxTileSizeForFilter = this->caps()->maxTileSize() - 2 * tileFilterPad; + if (this->shouldTileImageID(bitmap.getGenerationID(), bitmap.getSubset(), this->ctm(), + srcToDstMatrix, sampleState, src, maxTileSizeForFilter, + &tileSize, &clippedSrcRect)) { + this->drawTiledBitmap(bitmap, this->ctm(), srcToDstMatrix, *src, clippedSrcRect, + sampleState, paint, constraint, tileSize, doBicubic); + return; + } + } + GrBitmapTextureMaker maker(fContext.get(), bitmap); + this->drawTextureProducer(&maker, src, dst, constraint, this->ctm(), paint, true); +} + +sk_sp SkGpuDevice::makeSpecial(const SkBitmap& bitmap) { + // TODO: this makes a tight copy of 'bitmap' but it doesn't have to be (given SkSpecialImage's + // semantics). Since this is cached we would have to bake the fit into the cache key though. + sk_sp proxy = GrMakeCachedBitmapProxy(fContext->priv().proxyProvider(), + bitmap); + if (!proxy) { + return nullptr; + } + + const SkIRect rect = SkIRect::MakeWH(proxy->width(), proxy->height()); + + // GrMakeCachedBitmapProxy creates a tight copy of 'bitmap' so we don't have to subset + // the special image + return SkSpecialImage::MakeDeferredFromGpu(fContext.get(), + rect, + bitmap.getGenerationID(), + std::move(proxy), + SkColorTypeToGrColorType(bitmap.colorType()), + bitmap.refColorSpace(), + &this->surfaceProps()); +} + +sk_sp SkGpuDevice::makeSpecial(const SkImage* image) { + SkPixmap pm; + if (image->isTextureBacked()) { + sk_sp proxy = as_IB(image)->asTextureProxyRef(this->context()); + + return SkSpecialImage::MakeDeferredFromGpu(fContext.get(), + SkIRect::MakeWH(image->width(), image->height()), + image->uniqueID(), + std::move(proxy), + SkColorTypeToGrColorType(image->colorType()), + image->refColorSpace(), + &this->surfaceProps()); + } else if (image->peekPixels(&pm)) { + SkBitmap bm; + + bm.installPixels(pm); + return this->makeSpecial(bm); + } else { + return nullptr; + } +} + +sk_sp SkGpuDevice::snapSpecial(const SkIRect& subset, bool forceCopy) { + GrRenderTargetContext* rtc = this->accessRenderTargetContext(); + + // If we are wrapping a vulkan secondary command buffer, then we can't snap off a special image + // since it would require us to make a copy of the underlying VkImage which we don't have access + // to. Additionaly we can't stop and start the render pass that is used with the secondary + // command buffer. + if (rtc->wrapsVkSecondaryCB()) { + return nullptr; + } + + SkASSERT(rtc->asSurfaceProxy()); + + SkIRect finalSubset = subset; + sk_sp proxy(rtc->asTextureProxyRef()); + if (forceCopy || !proxy) { + // When the device doesn't have a texture, or a copy is requested, we create a temporary + // texture that matches the device contents + proxy = GrSurfaceProxy::Copy(fContext.get(), + rtc->asSurfaceProxy(), + rtc->colorInfo().colorType(), + GrMipMapped::kNo, // Don't auto generate mips + subset, + SkBackingFit::kApprox, + SkBudgeted::kYes); // Always budgeted + if (!proxy) { + return nullptr; + } + + // Since this copied only the requested subset, the special image wrapping the proxy no + // longer needs the original subset. + finalSubset = SkIRect::MakeSize(proxy->isize()); + } + + GrColorType ct = SkColorTypeToGrColorType(this->imageInfo().colorType()); + + return SkSpecialImage::MakeDeferredFromGpu(fContext.get(), + finalSubset, + kNeedNewImageUniqueID_SpecialImage, + std::move(proxy), + ct, + this->imageInfo().refColorSpace(), + &this->surfaceProps()); +} + +void SkGpuDevice::drawDevice(SkBaseDevice* device, + int left, int top, const SkPaint& paint) { + SkASSERT(!paint.getImageFilter()); + + ASSERT_SINGLE_OWNER + // clear of the source device must occur before CHECK_SHOULD_DRAW + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawDevice", fContext.get()); + + // drawDevice is defined to be in device coords. + SkGpuDevice* dev = static_cast(device); + sk_sp srcImg(dev->snapSpecial(SkIRect::MakeWH(dev->width(), dev->height()))); + if (!srcImg) { + return; + } + + this->drawSpecial(srcImg.get(), left, top, paint, nullptr, SkMatrix::I()); +} + +void SkGpuDevice::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { + ASSERT_SINGLE_OWNER + GrQuadAAFlags aaFlags = paint.isAntiAlias() ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone; + this->drawImageQuad(image, src, &dst, nullptr, GrAA(paint.isAntiAlias()), aaFlags, nullptr, + paint, constraint); +} + +// When drawing nine-patches or n-patches, cap the filter quality at kBilerp. +static GrSamplerState::Filter compute_lattice_filter_mode(const SkPaint& paint) { + if (paint.getFilterQuality() == kNone_SkFilterQuality) { + return GrSamplerState::Filter::kNearest; + } + + return GrSamplerState::Filter::kBilerp; +} + +void SkGpuDevice::drawImageNine(const SkImage* image, + const SkIRect& center, const SkRect& dst, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + uint32_t pinnedUniqueID; + auto iter = skstd::make_unique(image->width(), image->height(), center, dst); + if (sk_sp proxy = as_IB(image)->refPinnedTextureProxy(this->context(), + &pinnedUniqueID)) { + GrTextureAdjuster adjuster(this->context(), std::move(proxy), + SkColorTypeToGrColorType(image->colorType()), image->alphaType(), + pinnedUniqueID, image->colorSpace()); + this->drawProducerLattice(&adjuster, std::move(iter), dst, paint); + } else { + SkBitmap bm; + if (image->isLazyGenerated()) { + GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint); + this->drawProducerLattice(&maker, std::move(iter), dst, paint); + } else if (as_IB(image)->getROPixels(&bm)) { + GrBitmapTextureMaker maker(fContext.get(), bm); + this->drawProducerLattice(&maker, std::move(iter), dst, paint); + } + } +} + +void SkGpuDevice::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + auto iter = skstd::make_unique(bitmap.width(), bitmap.height(), center, dst); + GrBitmapTextureMaker maker(fContext.get(), bitmap); + this->drawProducerLattice(&maker, std::move(iter), dst, paint); +} + +void SkGpuDevice::drawProducerLattice(GrTextureProducer* producer, + std::unique_ptr iter, const SkRect& dst, + const SkPaint& origPaint) { + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawProducerLattice", fContext.get()); + SkTCopyOnFirstWrite paint(&origPaint); + + if (!producer->isAlphaOnly() && (paint->getColor() & 0x00FFFFFF) != 0x00FFFFFF) { + paint.writable()->setColor(SkColorSetARGB(origPaint.getAlpha(), 0xFF, 0xFF, 0xFF)); + } + GrPaint grPaint; + if (!SkPaintToGrPaintWithPrimitiveColor(this->context(), fRenderTargetContext->colorInfo(), + *paint, &grPaint)) { + return; + } + + auto dstColorSpace = fRenderTargetContext->colorInfo().colorSpace(); + const GrSamplerState::Filter filter = compute_lattice_filter_mode(*paint); + auto proxy = producer->refTextureProxyForParams(&filter, nullptr); + if (!proxy) { + return; + } + auto csxf = GrColorSpaceXform::Make(producer->colorSpace(), producer->alphaType(), + dstColorSpace, kPremul_SkAlphaType); + + fRenderTargetContext->drawImageLattice(this->clip(), std::move(grPaint), this->ctm(), + std::move(proxy), producer->colorType(), std::move(csxf), + filter, std::move(iter), dst); +} + +void SkGpuDevice::drawImageLattice(const SkImage* image, + const SkCanvas::Lattice& lattice, const SkRect& dst, + const SkPaint& paint) { + ASSERT_SINGLE_OWNER + uint32_t pinnedUniqueID; + auto iter = skstd::make_unique(lattice, dst); + if (sk_sp proxy = as_IB(image)->refPinnedTextureProxy(this->context(), + &pinnedUniqueID)) { + GrTextureAdjuster adjuster(this->context(), std::move(proxy), + SkColorTypeToGrColorType(image->colorType()), image->alphaType(), + pinnedUniqueID, image->colorSpace()); + this->drawProducerLattice(&adjuster, std::move(iter), dst, paint); + } else { + SkBitmap bm; + if (image->isLazyGenerated()) { + GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint); + this->drawProducerLattice(&maker, std::move(iter), dst, paint); + } else if (as_IB(image)->getROPixels(&bm)) { + GrBitmapTextureMaker maker(fContext.get(), bm); + this->drawProducerLattice(&maker, std::move(iter), dst, paint); + } + } +} + +void SkGpuDevice::drawBitmapLattice(const SkBitmap& bitmap, + const SkCanvas::Lattice& lattice, const SkRect& dst, + const SkPaint& paint) { + ASSERT_SINGLE_OWNER + auto iter = skstd::make_unique(lattice, dst); + GrBitmapTextureMaker maker(fContext.get(), bitmap); + this->drawProducerLattice(&maker, std::move(iter), dst, paint); +} + +static bool init_vertices_paint(GrContext* context, const GrColorInfo& colorInfo, + const SkPaint& skPaint, const SkMatrix& matrix, SkBlendMode bmode, + bool hasTexs, bool hasColors, GrPaint* grPaint) { + if (hasTexs && skPaint.getShader()) { + if (hasColors) { + // When there are texs and colors the shader and colors are combined using bmode. + return SkPaintToGrPaintWithXfermode(context, colorInfo, skPaint, matrix, bmode, + grPaint); + } else { + // We have a shader, but no colors to blend it against. + return SkPaintToGrPaint(context, colorInfo, skPaint, matrix, grPaint); + } + } else { + if (hasColors) { + // We have colors, but either have no shader or no texture coords (which implies that + // we should ignore the shader). + return SkPaintToGrPaintWithPrimitiveColor(context, colorInfo, skPaint, grPaint); + } else { + // No colors and no shaders. Just draw with the paint color. + return SkPaintToGrPaintNoShader(context, colorInfo, skPaint, grPaint); + } + } +} + +void SkGpuDevice::wireframeVertices(SkVertices::VertexMode vmode, int vertexCount, + const SkPoint vertices[], + const SkVertices::Bone bones[], int boneCount, + SkBlendMode bmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "wireframeVertices", fContext.get()); + + SkPaint copy(paint); + copy.setStyle(SkPaint::kStroke_Style); + copy.setStrokeWidth(0); + + GrPaint grPaint; + // we ignore the shader since we have no texture coordinates. + if (!SkPaintToGrPaintNoShader(this->context(), fRenderTargetContext->colorInfo(), copy, + &grPaint)) { + return; + } + + int triangleCount = 0; + int n = (nullptr == indices) ? vertexCount : indexCount; + switch (vmode) { + case SkVertices::kTriangles_VertexMode: + triangleCount = n / 3; + break; + case SkVertices::kTriangleStrip_VertexMode: + triangleCount = n - 2; + break; + case SkVertices::kTriangleFan_VertexMode: + SK_ABORT("Unexpected triangle fan."); + break; + } + + VertState state(vertexCount, indices, indexCount); + VertState::Proc vertProc = state.chooseProc(vmode); + + //number of indices for lines per triangle with kLines + indexCount = triangleCount * 6; + + static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode; + SkVertices::Builder builder(kIgnoredMode, vertexCount, indexCount, 0); + memcpy(builder.positions(), vertices, vertexCount * sizeof(SkPoint)); + + uint16_t* lineIndices = builder.indices(); + int i = 0; + while (vertProc(&state)) { + lineIndices[i] = state.f0; + lineIndices[i + 1] = state.f1; + lineIndices[i + 2] = state.f1; + lineIndices[i + 3] = state.f2; + lineIndices[i + 4] = state.f2; + lineIndices[i + 5] = state.f0; + i += 6; + } + + GrPrimitiveType primitiveType = GrPrimitiveType::kLines; + fRenderTargetContext->drawVertices(this->clip(), + std::move(grPaint), + this->ctm(), + builder.detach(), + bones, + boneCount, + &primitiveType); +} + +void SkGpuDevice::drawVertices(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode mode, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawVertices", fContext.get()); + + SkASSERT(vertices); + GrPaint grPaint; + bool hasColors = vertices->hasColors(); + bool hasTexs = vertices->hasTexCoords(); + if ((!hasTexs || !paint.getShader()) && !hasColors) { + // The dreaded wireframe mode. Fallback to drawVertices and go so slooooooow. + this->wireframeVertices(vertices->mode(), vertices->vertexCount(), vertices->positions(), + bones, boneCount, mode, vertices->indices(), vertices->indexCount(), + paint); + return; + } + if (!init_vertices_paint(fContext.get(), fRenderTargetContext->colorInfo(), paint, this->ctm(), + mode, hasTexs, hasColors, &grPaint)) { + return; + } + fRenderTargetContext->drawVertices(this->clip(), std::move(grPaint), this->ctm(), + sk_ref_sp(const_cast(vertices)), + bones, boneCount); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { + + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawShadow", fContext.get()); + + if (!fRenderTargetContext->drawFastShadow(this->clip(), this->ctm(), path, rec)) { + // failed to find an accelerated case + this->INHERITED::drawShadow(path, rec); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawAtlas(const SkImage* atlas, const SkRSXform xform[], + const SkRect texRect[], const SkColor colors[], int count, + SkBlendMode mode, const SkPaint& paint) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawAtlas", fContext.get()); + + SkPaint p(paint); + p.setShader(atlas->makeShader()); + + GrPaint grPaint; + if (colors) { + if (!SkPaintToGrPaintWithXfermode(this->context(), fRenderTargetContext->colorInfo(), p, + this->ctm(), (SkBlendMode)mode, &grPaint)) { + return; + } + } else { + if (!SkPaintToGrPaint(this->context(), fRenderTargetContext->colorInfo(), p, this->ctm(), + &grPaint)) { + return; + } + } + + fRenderTargetContext->drawAtlas( + this->clip(), std::move(grPaint), this->ctm(), count, xform, texRect, colors); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) { + ASSERT_SINGLE_OWNER + GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawGlyphRunList", fContext.get()); + + // Check for valid input + const SkMatrix& ctm = this->ctm(); + if (!ctm.isFinite() || !glyphRunList.allFontsFinite()) { + return; + } + + fRenderTargetContext->drawGlyphRunList(this->clip(), ctm, glyphRunList); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawDrawable(SkDrawable* drawable, const SkMatrix* matrix, SkCanvas* canvas) { + GrBackendApi api = this->context()->backend(); + if (GrBackendApi::kVulkan == api) { + const SkMatrix& ctm = canvas->getTotalMatrix(); + const SkMatrix& combinedMatrix = matrix ? SkMatrix::Concat(ctm, *matrix) : ctm; + std::unique_ptr gpuDraw = + drawable->snapGpuDrawHandler(api, combinedMatrix, canvas->getDeviceClipBounds(), + this->imageInfo()); + if (gpuDraw) { + fRenderTargetContext->drawDrawable(std::move(gpuDraw), drawable->getBounds()); + return; + } + } + this->INHERITED::drawDrawable(drawable, matrix, canvas); +} + + +/////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::flush() { + this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, GrFlushInfo()); +} + +GrSemaphoresSubmitted SkGpuDevice::flush(SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info) { + ASSERT_SINGLE_OWNER + + return fRenderTargetContext->flush(access, info); +} + +bool SkGpuDevice::wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) { + ASSERT_SINGLE_OWNER + + return fRenderTargetContext->waitOnSemaphores(numSemaphores, waitSemaphores); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkBaseDevice* SkGpuDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint*) { + ASSERT_SINGLE_OWNER + + SkSurfaceProps props(this->surfaceProps().flags(), cinfo.fPixelGeometry); + + // layers are never drawn in repeat modes, so we can request an approx + // match and ignore any padding. + SkBackingFit fit = kNever_TileUsage == cinfo.fTileUsage ? SkBackingFit::kApprox + : SkBackingFit::kExact; + + SkASSERT(cinfo.fInfo.colorType() != kRGBA_1010102_SkColorType); + + auto rtc = fContext->priv().makeDeferredRenderTargetContextWithFallback( + fit, + cinfo.fInfo.width(), + cinfo.fInfo.height(), + SkColorTypeToGrColorType(cinfo.fInfo.colorType()), + fRenderTargetContext->colorInfo().refColorSpace(), + fRenderTargetContext->numSamples(), + GrMipMapped::kNo, + kBottomLeft_GrSurfaceOrigin, + &props, + SkBudgeted::kYes, + fRenderTargetContext->asSurfaceProxy()->isProtected() ? GrProtected::kYes + : GrProtected::kNo); + if (!rtc) { + return nullptr; + } + + // Skia's convention is to only clear a device if it is non-opaque. + InitContents init = cinfo.fInfo.isOpaque() ? kUninit_InitContents : kClear_InitContents; + + return SkGpuDevice::Make(fContext.get(), std::move(rtc), init).release(); +} + +sk_sp SkGpuDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) { + ASSERT_SINGLE_OWNER + // TODO: Change the signature of newSurface to take a budgeted parameter. + static const SkBudgeted kBudgeted = SkBudgeted::kNo; + return SkSurface::MakeRenderTarget(fContext.get(), kBudgeted, info, + fRenderTargetContext->numSamples(), + fRenderTargetContext->origin(), &props); +} + +SkImageFilterCache* SkGpuDevice::getImageFilterCache() { + ASSERT_SINGLE_OWNER + // We always return a transient cache, so it is freed after each + // filter traversal. + return SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize); +} + diff --git a/gfx/skia/skia/src/gpu/SkGpuDevice.h b/gfx/skia/skia/src/gpu/SkGpuDevice.h new file mode 100644 index 0000000000..a37d75245b --- /dev/null +++ b/gfx/skia/skia/src/gpu/SkGpuDevice.h @@ -0,0 +1,247 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGpuDevice_DEFINED +#define SkGpuDevice_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkPicture.h" +#include "include/core/SkRegion.h" +#include "include/core/SkSurface.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTypes.h" +#include "src/core/SkClipStackDevice.h" +#include "src/gpu/GrClipStackClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/SkGr.h" + +class GrAccelData; +class GrTextureMaker; +class GrTextureProducer; +struct GrCachedLayer; + +class SkSpecialImage; + +/** + * Subclass of SkBaseDevice, which directs all drawing to the GrGpu owned by the + * canvas. + */ +class SkGpuDevice : public SkClipStackDevice { +public: + enum InitContents { + kClear_InitContents, + kUninit_InitContents + }; + + /** + * Creates an SkGpuDevice from a GrRenderTargetContext whose backing width/height is + * different than its actual width/height (e.g., approx-match scratch texture). + */ + static sk_sp Make( + GrContext*, std::unique_ptr, InitContents); + + /** + * New device that will create an offscreen renderTarget based on the ImageInfo and + * sampleCount. The mipMapped flag tells the gpu to create the underlying render target with + * mips. The Budgeted param controls whether the device's backing store counts against the + * resource cache budget. On failure, returns nullptr. + * This entry point creates a kExact backing store. It is used when creating SkGpuDevices + * for SkSurfaces. + */ + static sk_sp Make(GrContext*, SkBudgeted, const SkImageInfo&, + int sampleCount, GrSurfaceOrigin, const SkSurfaceProps*, + GrMipMapped mipMapped, InitContents); + + ~SkGpuDevice() override {} + + GrContext* context() const override { return fContext.get(); } + + // set all pixels to 0 + void clearAll(); + + void replaceRenderTargetContext(bool shouldRetainContent); + void replaceRenderTargetContext(std::unique_ptr, + bool shouldRetainContent); + + GrRenderTargetContext* accessRenderTargetContext() override; + + void drawPaint(const SkPaint& paint) override; + void drawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint[], + const SkPaint& paint) override; + void drawRect(const SkRect& r, const SkPaint& paint) override; + void drawRRect(const SkRRect& r, const SkPaint& paint) override; + void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override; + void drawRegion(const SkRegion& r, const SkPaint& paint) override; + void drawOval(const SkRect& oval, const SkPaint& paint) override; + void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) override; + void drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) override; + void drawBitmapRect(const SkBitmap&, const SkRect* srcOrNull, const SkRect& dst, + const SkPaint& paint, SkCanvas::SrcRectConstraint) override; + void drawSprite(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint) override; + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override; + void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, SkBlendMode, + const SkPaint&) override; + void drawShadow(const SkPath&, const SkDrawShadowRec&) override; + void drawAtlas(const SkImage* atlas, const SkRSXform[], const SkRect[], + const SkColor[], int count, SkBlendMode, const SkPaint&) override; + void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) override; + + void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint&, SkCanvas::SrcRectConstraint) override; + + void drawImageNine(const SkImage* image, const SkIRect& center, + const SkRect& dst, const SkPaint& paint) override; + void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint& paint) override; + + void drawImageLattice(const SkImage*, const SkCanvas::Lattice&, + const SkRect& dst, const SkPaint&) override; + void drawBitmapLattice(const SkBitmap&, const SkCanvas::Lattice&, + const SkRect& dst, const SkPaint&) override; + + void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas* canvas) override; + + void drawSpecial(SkSpecialImage*, int left, int top, const SkPaint& paint, + SkImage*, const SkMatrix&) override; + + void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], SkCanvas::QuadAAFlags aaFlags, + const SkColor4f& color, SkBlendMode mode) override; + void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count, const SkPoint dstClips[], + const SkMatrix[], const SkPaint&, SkCanvas::SrcRectConstraint) override; + + sk_sp makeSpecial(const SkBitmap&) override; + sk_sp makeSpecial(const SkImage*) override; + sk_sp snapSpecial(const SkIRect&, bool = false) override; + + void flush() override; + GrSemaphoresSubmitted flush(SkSurface::BackendSurfaceAccess access, const GrFlushInfo&); + bool wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores); + + bool onAccessPixels(SkPixmap*) override; + +protected: + bool onReadPixels(const SkPixmap&, int, int) override; + bool onWritePixels(const SkPixmap&, int, int) override; + +private: + // We want these unreffed in RenderTargetContext, GrContext order. + sk_sp fContext; + std::unique_ptr fRenderTargetContext; + + enum Flags { + kNeedClear_Flag = 1 << 0, //!< Surface requires an initial clear + kIsOpaque_Flag = 1 << 1, //!< Hint from client that rendering to this device will be + // opaque even if the config supports alpha. + }; + static bool CheckAlphaTypeAndGetFlags(const SkImageInfo* info, InitContents init, + unsigned* flags); + + SkGpuDevice(GrContext*, std::unique_ptr, unsigned flags); + + SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override; + + sk_sp makeSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + SkImageFilterCache* getImageFilterCache() override; + + bool forceConservativeRasterClip() const override { return true; } + + GrClipStackClip clip() const { return GrClipStackClip(&this->cs()); } + + const GrCaps* caps() const; + + /** + * Helper functions called by drawBitmapCommon. By the time these are called the SkDraw's + * matrix, clip, and the device's render target has already been set on GrContext. + */ + + // The tileSize and clippedSrcRect will be valid only if true is returned. + bool shouldTileImageID(uint32_t imageID, + const SkIRect& imageRect, + const SkMatrix& viewMatrix, + const SkMatrix& srcToDstRectMatrix, + const GrSamplerState& params, + const SkRect* srcRectPtr, + int maxTileSize, + int* tileSize, + SkIRect* clippedSubset) const; + // Just returns the predicate, not the out-tileSize or out-clippedSubset, as they are not + // needed at the moment. + bool shouldTileImage(const SkImage* image, const SkRect* srcRectPtr, + SkCanvas::SrcRectConstraint constraint, SkFilterQuality quality, + const SkMatrix& viewMatrix, const SkMatrix& srcToDstRect) const; + + sk_sp filterTexture(SkSpecialImage*, + int left, int top, + SkIPoint* offset, + const SkImageFilter* filter); + + // Splits bitmap into tiles of tileSize and draws them using separate textures for each tile. + void drawTiledBitmap(const SkBitmap& bitmap, + const SkMatrix& viewMatrix, + const SkMatrix& srcToDstMatrix, + const SkRect& srcRect, + const SkIRect& clippedSrcRect, + const GrSamplerState& params, + const SkPaint& paint, + SkCanvas::SrcRectConstraint, + int tileSize, + bool bicubic); + + // Used by drawTiledBitmap to draw each tile. + void drawBitmapTile(const SkBitmap&, + const SkMatrix& viewMatrix, + const SkRect& dstRect, + const SkRect& srcRect, + const GrSamplerState& samplerState, + const SkPaint& paint, + SkCanvas::SrcRectConstraint, + bool bicubic, + bool needsTextureDomain); + + // If not null, dstClip must be contained inside dst and will also respect the edge AA flags. + // If 'preViewMatrix' is not null, final CTM will be this->ctm() * preViewMatrix. + void drawImageQuad(const SkImage*, const SkRect* src, const SkRect* dst, + const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags, + const SkMatrix* preViewMatrix, const SkPaint&, SkCanvas::SrcRectConstraint); + + // TODO(michaelludwig): This can be removed once drawBitmapRect is removed from SkDevice + // so that drawImageQuad is the sole entry point into the draw-single-image op + void drawTextureProducer(GrTextureProducer*, + const SkRect* srcRect, + const SkRect* dstRect, + SkCanvas::SrcRectConstraint, + const SkMatrix& viewMatrix, + const SkPaint&, + bool attemptDrawTexture); + + void drawProducerLattice(GrTextureProducer*, std::unique_ptr, const SkRect& dst, + const SkPaint&); + + void drawStrokedLine(const SkPoint pts[2], const SkPaint&); + + void wireframeVertices(SkVertices::VertexMode, int vertexCount, const SkPoint verts[], + const SkVertices::Bone bones[], int boneCount, SkBlendMode, + const uint16_t indices[], int indexCount, const SkPaint&); + + static std::unique_ptr MakeRenderTargetContext(GrContext*, + SkBudgeted, + const SkImageInfo&, + int sampleCount, + GrSurfaceOrigin, + const SkSurfaceProps*, + GrMipMapped); + + friend class GrAtlasTextContext; + friend class SkSurface_Gpu; // for access to surfaceProps + typedef SkClipStackDevice INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/SkGpuDevice_drawTexture.cpp b/gfx/skia/skia/src/gpu/SkGpuDevice_drawTexture.cpp new file mode 100644 index 0000000000..20c4976fd4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/SkGpuDevice_drawTexture.cpp @@ -0,0 +1,608 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/SkGpuDevice.h" + +#include "include/core/SkYUVAIndex.h" +#include "src/core/SkDraw.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/gpu/GrBitmapTextureMaker.h" +#include "src/gpu/GrBlurUtils.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrImageTextureMaker.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrTextureAdjuster.h" +#include "src/gpu/GrTextureMaker.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrBicubicEffect.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/image/SkImage_Base.h" + +namespace { + +static inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) { + return textureIsAlphaOnly && paint.getShader(); +} + +////////////////////////////////////////////////////////////////////////////// +// Helper functions for dropping src rect constraint in bilerp mode. + +static const SkScalar kColorBleedTolerance = 0.001f; + +static bool has_aligned_samples(const SkRect& srcRect, const SkRect& transformedRect) { + // detect pixel disalignment + if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) - transformedRect.left()) < kColorBleedTolerance && + SkScalarAbs(SkScalarRoundToScalar(transformedRect.top()) - transformedRect.top()) < kColorBleedTolerance && + SkScalarAbs(transformedRect.width() - srcRect.width()) < kColorBleedTolerance && + SkScalarAbs(transformedRect.height() - srcRect.height()) < kColorBleedTolerance) { + return true; + } + return false; +} + +static bool may_color_bleed(const SkRect& srcRect, + const SkRect& transformedRect, + const SkMatrix& m, + int numSamples) { + // Only gets called if has_aligned_samples returned false. + // So we can assume that sampling is axis aligned but not texel aligned. + SkASSERT(!has_aligned_samples(srcRect, transformedRect)); + SkRect innerSrcRect(srcRect), innerTransformedRect, outerTransformedRect(transformedRect); + if (numSamples > 1) { + innerSrcRect.inset(SK_Scalar1, SK_Scalar1); + } else { + innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf); + } + m.mapRect(&innerTransformedRect, innerSrcRect); + + // The gap between outerTransformedRect and innerTransformedRect + // represents the projection of the source border area, which is + // problematic for color bleeding. We must check whether any + // destination pixels sample the border area. + outerTransformedRect.inset(kColorBleedTolerance, kColorBleedTolerance); + innerTransformedRect.outset(kColorBleedTolerance, kColorBleedTolerance); + SkIRect outer, inner; + outerTransformedRect.round(&outer); + innerTransformedRect.round(&inner); + // If the inner and outer rects round to the same result, it means the + // border does not overlap any pixel centers. Yay! + return inner != outer; +} + +static bool can_ignore_bilerp_constraint(const GrTextureProducer& producer, + const SkRect& srcRect, + const SkMatrix& srcRectToDeviceSpace, + int numSamples) { + if (srcRectToDeviceSpace.rectStaysRect()) { + // sampling is axis-aligned + SkRect transformedRect; + srcRectToDeviceSpace.mapRect(&transformedRect, srcRect); + + if (has_aligned_samples(srcRect, transformedRect) || + !may_color_bleed(srcRect, transformedRect, srcRectToDeviceSpace, numSamples)) { + return true; + } + } + return false; +} + +enum class ImageDrawMode { + // Src and dst have been restricted to the image content. May need to clamp, no need to decal. + kOptimized, + // Src and dst are their original sizes, requires use of a decal instead of plain clamping. + // This is used when a dst clip is provided and extends outside of the optimized dst rect. + kDecal, + // Src or dst are empty, or do not intersect the image content so don't draw anything. + kSkip +}; + +/** + * Optimize the src rect sampling area within an image (sized 'width' x 'height') such that + * 'outSrcRect' will be completely contained in the image's bounds. The corresponding rect + * to draw will be output to 'outDstRect'. The mapping between src and dst will be cached in + * 'srcToDst'. Outputs are not always updated when kSkip is returned. + * + * If 'origSrcRect' is null, implicitly use the image bounds. If 'origDstRect' is null, use the + * original src rect. 'dstClip' should be null when there is no additional clipping. + */ +static ImageDrawMode optimize_sample_area(const SkISize& image, const SkRect* origSrcRect, + const SkRect* origDstRect, const SkPoint dstClip[4], + SkRect* outSrcRect, SkRect* outDstRect, + SkMatrix* srcToDst) { + SkRect srcBounds = SkRect::MakeIWH(image.fWidth, image.fHeight); + + SkRect src = origSrcRect ? *origSrcRect : srcBounds; + SkRect dst = origDstRect ? *origDstRect : src; + + if (src.isEmpty() || dst.isEmpty()) { + return ImageDrawMode::kSkip; + } + + if (outDstRect) { + srcToDst->setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit); + } else { + srcToDst->setIdentity(); + } + + if (origSrcRect && !srcBounds.contains(src)) { + if (!src.intersect(srcBounds)) { + return ImageDrawMode::kSkip; + } + srcToDst->mapRect(&dst, src); + + // Both src and dst have gotten smaller. If dstClip is provided, confirm it is still + // contained in dst, otherwise cannot optimize the sample area and must use a decal instead + if (dstClip) { + for (int i = 0; i < 4; ++i) { + if (!dst.contains(dstClip[i].fX, dstClip[i].fY)) { + // Must resort to using a decal mode restricted to the clipped 'src', and + // use the original dst rect (filling in src bounds as needed) + *outSrcRect = src; + *outDstRect = (origDstRect ? *origDstRect + : (origSrcRect ? *origSrcRect : srcBounds)); + return ImageDrawMode::kDecal; + } + } + } + } + + // The original src and dst were fully contained in the image, or there was no dst clip to + // worry about, or the clip was still contained in the restricted dst rect. + *outSrcRect = src; + *outDstRect = dst; + return ImageDrawMode::kOptimized; +} + +/** + * Checks whether the paint is compatible with using GrRenderTargetContext::drawTexture. It is more + * efficient than the GrTextureProducer general case. + */ +static bool can_use_draw_texture(const SkPaint& paint) { + return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() && + !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality); +} + +// Assumes srcRect and dstRect have already been optimized to fit the proxy +static void draw_texture(GrRenderTargetContext* rtc, const GrClip& clip, const SkMatrix& ctm, + const SkPaint& paint, const SkRect& srcRect, const SkRect& dstRect, + const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags, + SkCanvas::SrcRectConstraint constraint, sk_sp proxy, + const GrColorInfo& srcColorInfo) { + const GrColorInfo& dstInfo(rtc->colorInfo()); + auto textureXform = + GrColorSpaceXform::Make(srcColorInfo.colorSpace(), srcColorInfo.alphaType(), + dstInfo.colorSpace(), kPremul_SkAlphaType); + GrSamplerState::Filter filter; + switch (paint.getFilterQuality()) { + case kNone_SkFilterQuality: + filter = GrSamplerState::Filter::kNearest; + break; + case kLow_SkFilterQuality: + filter = GrSamplerState::Filter::kBilerp; + break; + case kMedium_SkFilterQuality: + case kHigh_SkFilterQuality: + SK_ABORT("Quality level not allowed."); + } + + // Must specify the strict constraint when the proxy is not functionally exact and the src + // rect would access pixels outside the proxy's content area without the constraint. + if (constraint != SkCanvas::kStrict_SrcRectConstraint && + !GrProxyProvider::IsFunctionallyExact(proxy.get())) { + // Conservative estimate of how much a coord could be outset from src rect: + // 1/2 pixel for AA and 1/2 pixel for bilerp + float buffer = 0.5f * (aa == GrAA::kYes) + + 0.5f * (filter == GrSamplerState::Filter::kBilerp); + SkRect safeBounds = SkRect::MakeWH(proxy->width(), proxy->height()); + safeBounds.inset(buffer, buffer); + if (!safeBounds.contains(srcRect)) { + constraint = SkCanvas::kStrict_SrcRectConstraint; + } + } + SkPMColor4f color; + if (GrColorTypeIsAlphaOnly(srcColorInfo.colorType())) { + color = SkColor4fPrepForDst(paint.getColor4f(), dstInfo).premul(); + } else { + float paintAlpha = paint.getColor4f().fA; + color = { paintAlpha, paintAlpha, paintAlpha, paintAlpha }; + } + + if (dstClip) { + // Get source coords corresponding to dstClip + SkPoint srcQuad[4]; + GrMapRectPoints(dstRect, srcRect, dstClip, srcQuad, 4); + + rtc->drawTextureQuad(clip, std::move(proxy), srcColorInfo.colorType(), filter, + paint.getBlendMode(), color, srcQuad, dstClip, aa, aaFlags, + constraint == SkCanvas::kStrict_SrcRectConstraint ? &srcRect : nullptr, + ctm, std::move(textureXform)); + } else { + rtc->drawTexture(clip, std::move(proxy), srcColorInfo.colorType(), filter, + paint.getBlendMode(), color, srcRect, dstRect, aa, aaFlags, constraint, + ctm, std::move(textureXform)); + } +} + +// Assumes srcRect and dstRect have already been optimized to fit the proxy. +static void draw_texture_producer(GrContext* context, GrRenderTargetContext* rtc, + const GrClip& clip, const SkMatrix& ctm, + const SkPaint& paint, GrTextureProducer* producer, + const SkRect& src, const SkRect& dst, const SkPoint dstClip[4], + const SkMatrix& srcToDst, GrAA aa, GrQuadAAFlags aaFlags, + SkCanvas::SrcRectConstraint constraint, bool attemptDrawTexture) { + if (attemptDrawTexture && can_use_draw_texture(paint)) { + // We've done enough checks above to allow us to pass ClampNearest() and not check for + // scaling adjustments. + auto proxy = producer->refTextureProxyForParams(GrSamplerState::ClampNearest(), nullptr); + if (!proxy) { + return; + } + + draw_texture(rtc, clip, ctm, paint, src, dst, dstClip, aa, aaFlags, constraint, + std::move(proxy), producer->colorInfo()); + return; + } + + const SkMaskFilter* mf = paint.getMaskFilter(); + + // The shader expects proper local coords, so we can't replace local coords with texture coords + // if the shader will be used. If we have a mask filter we will change the underlying geometry + // that is rendered. + bool canUseTextureCoordsAsLocalCoords = !use_shader(producer->isAlphaOnly(), paint) && !mf; + + // Specifying the texture coords as local coordinates is an attempt to enable more GrDrawOp + // combining by not baking anything about the srcRect, dstRect, or ctm, into the texture + // FP. In the future this should be an opaque optimization enabled by the combination of + // GrDrawOp/GP and FP. + if (mf && as_MFB(mf)->hasFragmentProcessor()) { + mf = nullptr; + } + bool doBicubic; + GrSamplerState::Filter fm = GrSkFilterQualityToGrFilterMode( + producer->width(), producer->height(), paint.getFilterQuality(), ctm, srcToDst, + context->priv().options().fSharpenMipmappedTextures, &doBicubic); + const GrSamplerState::Filter* filterMode = doBicubic ? nullptr : &fm; + + GrTextureProducer::FilterConstraint constraintMode; + if (SkCanvas::kFast_SrcRectConstraint == constraint) { + constraintMode = GrTextureAdjuster::kNo_FilterConstraint; + } else { + constraintMode = GrTextureAdjuster::kYes_FilterConstraint; + } + + // If we have to outset for AA then we will generate texture coords outside the src rect. The + // same happens for any mask filter that extends the bounds rendered in the dst. + // This is conservative as a mask filter does not have to expand the bounds rendered. + bool coordsAllInsideSrcRect = aaFlags == GrQuadAAFlags::kNone && !mf; + + // Check for optimization to drop the src rect constraint when on bilerp. + if (filterMode && GrSamplerState::Filter::kBilerp == *filterMode && + GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect && + !producer->hasMixedResolutions()) { + SkMatrix combinedMatrix; + combinedMatrix.setConcat(ctm, srcToDst); + if (can_ignore_bilerp_constraint(*producer, src, combinedMatrix, rtc->numSamples())) { + constraintMode = GrTextureAdjuster::kNo_FilterConstraint; + } + } + + SkMatrix textureMatrix; + if (canUseTextureCoordsAsLocalCoords) { + textureMatrix = SkMatrix::I(); + } else { + if (!srcToDst.invert(&textureMatrix)) { + return; + } + } + auto fp = producer->createFragmentProcessor(textureMatrix, src, constraintMode, + coordsAllInsideSrcRect, filterMode); + fp = GrColorSpaceXformEffect::Make(std::move(fp), producer->colorSpace(), producer->alphaType(), + rtc->colorInfo().colorSpace()); + if (!fp) { + return; + } + + GrPaint grPaint; + if (!SkPaintToGrPaintWithTexture(context, rtc->colorInfo(), paint, ctm, std::move(fp), + producer->isAlphaOnly(), &grPaint)) { + return; + } + + if (!mf) { + // Can draw the image directly (any mask filter on the paint was converted to an FP already) + if (dstClip) { + SkPoint srcClipPoints[4]; + SkPoint* srcClip = nullptr; + if (canUseTextureCoordsAsLocalCoords) { + // Calculate texture coordinates that match the dst clip + GrMapRectPoints(dst, src, dstClip, srcClipPoints, 4); + srcClip = srcClipPoints; + } + rtc->fillQuadWithEdgeAA(clip, std::move(grPaint), aa, aaFlags, ctm, dstClip, srcClip); + } else { + // Provide explicit texture coords when possible, otherwise rely on texture matrix + rtc->fillRectWithEdgeAA(clip, std::move(grPaint), aa, aaFlags, ctm, dst, + canUseTextureCoordsAsLocalCoords ? &src : nullptr); + } + } else { + // Must draw the mask filter as a GrShape. For now, this loses the per-edge AA information + // since it always draws with AA, but that is should not be noticeable since the mask filter + // is probably a blur. + GrShape shape; + if (dstClip) { + // Represent it as an SkPath formed from the dstClip + SkPath path; + path.addPoly(dstClip, 4, true); + shape = GrShape(path); + } else { + shape = GrShape(dst); + } + + GrBlurUtils::drawShapeWithMaskFilter( + context, rtc, clip, shape, std::move(grPaint), ctm, mf); + } +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////////////// + +void SkGpuDevice::drawImageQuad(const SkImage* image, const SkRect* srcRect, const SkRect* dstRect, + const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags, + const SkMatrix* preViewMatrix, const SkPaint& paint, + SkCanvas::SrcRectConstraint constraint) { + SkRect src; + SkRect dst; + SkMatrix srcToDst; + ImageDrawMode mode = optimize_sample_area(SkISize::Make(image->width(), image->height()), + srcRect, dstRect, dstClip, &src, &dst, &srcToDst); + if (mode == ImageDrawMode::kSkip) { + return; + } + + if (src.contains(image->bounds())) { + constraint = SkCanvas::kFast_SrcRectConstraint; + } + // Depending on the nature of image, it can flow through more or less optimal pipelines + bool useDecal = mode == ImageDrawMode::kDecal; + bool attemptDrawTexture = !useDecal; // rtc->drawTexture() only clamps + + // Get final CTM matrix + SkMatrix ctm = this->ctm(); + if (preViewMatrix) { + ctm.preConcat(*preViewMatrix); + } + + // YUVA images can be stored in multiple images with different plane resolutions, so this + // uses an effect to combine them dynamically on the GPU. This is done before requesting a + // pinned texture proxy because YUV images force-flatten to RGBA in that scenario. + if (as_IB(image)->isYUVA()) { + SK_HISTOGRAM_BOOLEAN("DrawTiled", false); + LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality()); + + GrYUVAImageTextureMaker maker(fContext.get(), image, useDecal); + draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm, + paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint, + /* attempt draw texture */ false); + return; + } + + // Pinned texture proxies can be rendered directly as textures, or with relatively simple + // adjustments applied to the image content (scaling, mipmaps, color space, etc.) + uint32_t pinnedUniqueID; + if (sk_sp proxy = as_IB(image)->refPinnedTextureProxy(this->context(), + &pinnedUniqueID)) { + SK_HISTOGRAM_BOOLEAN("DrawTiled", false); + LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality()); + + GrColorInfo colorInfo(image->imageInfo().colorInfo()); + + if (attemptDrawTexture && can_use_draw_texture(paint)) { + draw_texture(fRenderTargetContext.get(), this->clip(), ctm, paint, src, dst, + dstClip, aa, aaFlags, constraint, std::move(proxy), colorInfo); + return; + } + GrTextureAdjuster adjuster(fContext.get(), std::move(proxy), colorInfo, pinnedUniqueID, + useDecal); + draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm, + paint, &adjuster, src, dst, dstClip, srcToDst, aa, aaFlags, + constraint, /* attempt draw_texture */ false); + return; + } + + // Next up, try tiling the image + // TODO (michaelludwig): Implement this with per-edge AA flags to handle seaming properly + // instead of going through drawBitmapRect (which will be removed from SkDevice in the future) + SkBitmap bm; + if (this->shouldTileImage(image, &src, constraint, paint.getFilterQuality(), ctm, srcToDst)) { + // only support tiling as bitmap at the moment, so force raster-version + if (!as_IB(image)->getROPixels(&bm)) { + return; + } + this->drawBitmapRect(bm, &src, dst, paint, constraint); + return; + } + + // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry. + SK_HISTOGRAM_BOOLEAN("DrawTiled", false); + LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality()); + + // Lazily generated images must get drawn as a texture producer that handles the final + // texture creation. + if (image->isLazyGenerated()) { + GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint, useDecal); + draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm, + paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint, + attemptDrawTexture); + return; + } + if (as_IB(image)->getROPixels(&bm)) { + GrBitmapTextureMaker maker(fContext.get(), bm, useDecal); + draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm, + paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint, + attemptDrawTexture); + } + + // Otherwise don't know how to draw it +} + +void SkGpuDevice::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count, + const SkPoint dstClips[], const SkMatrix preViewMatrices[], + const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { + SkASSERT(count > 0); + if (!can_use_draw_texture(paint)) { + // Send every entry through drawImageQuad() to handle the more complicated paint + int dstClipIndex = 0; + for (int i = 0; i < count; ++i) { + // Only no clip or quad clip are supported + SkASSERT(!set[i].fHasClip || dstClips); + SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices); + + SkTCopyOnFirstWrite entryPaint(paint); + if (set[i].fAlpha != 1.f) { + auto paintAlpha = paint.getAlphaf(); + entryPaint.writable()->setAlphaf(paintAlpha * set[i].fAlpha); + } + // Always send GrAA::kYes to preserve seaming across tiling in MSAA + this->drawImageQuad( + set[i].fImage.get(), &set[i].fSrcRect, &set[i].fDstRect, + set[i].fHasClip ? dstClips + dstClipIndex : nullptr, GrAA::kYes, + SkToGrQuadAAFlags(set[i].fAAFlags), + set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex, + *entryPaint, constraint); + dstClipIndex += 4 * set[i].fHasClip; + } + return; + } + + GrSamplerState::Filter filter = kNone_SkFilterQuality == paint.getFilterQuality() ? + GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kBilerp; + SkBlendMode mode = paint.getBlendMode(); + + SkAutoTArray textures(count); + // We accumulate compatible proxies until we find an an incompatible one or reach the end and + // issue the accumulated 'n' draws starting at 'base'. + int base = 0, n = 0; + auto draw = [&] { + if (n > 0) { + auto textureXform = GrColorSpaceXform::Make( + set[base].fImage->colorSpace(), set[base].fImage->alphaType(), + fRenderTargetContext->colorInfo().colorSpace(), kPremul_SkAlphaType); + fRenderTargetContext->drawTextureSet(this->clip(), textures.get() + base, n, + filter, mode, GrAA::kYes, constraint, this->ctm(), + std::move(textureXform)); + } + }; + int dstClipIndex = 0; + for (int i = 0; i < count; ++i) { + SkASSERT(!set[i].fHasClip || dstClips); + SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices); + + // Manage the dst clip pointer tracking before any continues are used so we don't lose + // our place in the dstClips array. + const SkPoint* clip = set[i].fHasClip ? dstClips + dstClipIndex : nullptr; + dstClipIndex += 4 * set[i].fHasClip; + + // The default SkBaseDevice implementation is based on drawImageRect which does not allow + // non-sorted src rects. TODO: Decide this is OK or make sure we handle it. + if (!set[i].fSrcRect.isSorted()) { + draw(); + base = i + 1; + n = 0; + continue; + } + + sk_sp proxy; + const SkImage_Base* image = as_IB(set[i].fImage.get()); + // Extract proxy from image, but skip YUV images so they get processed through + // drawImageQuad and the proper effect to dynamically sample their planes. + if (!image->isYUVA()) { + uint32_t uniqueID; + proxy = image->refPinnedTextureProxy(this->context(), &uniqueID); + if (!proxy) { + proxy = image->asTextureProxyRef(this->context(), GrSamplerState::ClampBilerp(), + nullptr); + } + } + + if (!proxy) { + // This image can't go through the texture op, send through general image pipeline + // after flushing current batch. + draw(); + base = i + 1; + n = 0; + SkTCopyOnFirstWrite entryPaint(paint); + if (set[i].fAlpha != 1.f) { + auto paintAlpha = paint.getAlphaf(); + entryPaint.writable()->setAlphaf(paintAlpha * set[i].fAlpha); + } + this->drawImageQuad( + image, &set[i].fSrcRect, &set[i].fDstRect, clip, GrAA::kYes, + SkToGrQuadAAFlags(set[i].fAAFlags), + set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex, + *entryPaint, constraint); + continue; + } + + textures[i].fProxy = std::move(proxy); + textures[i].fSrcColorType = SkColorTypeToGrColorType(image->colorType()); + textures[i].fSrcRect = set[i].fSrcRect; + textures[i].fDstRect = set[i].fDstRect; + textures[i].fDstClipQuad = clip; + textures[i].fPreViewMatrix = + set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex; + textures[i].fAlpha = set[i].fAlpha * paint.getAlphaf(); + textures[i].fAAFlags = SkToGrQuadAAFlags(set[i].fAAFlags); + + if (n > 0 && + (!GrTextureProxy::ProxiesAreCompatibleAsDynamicState(textures[i].fProxy.get(), + textures[base].fProxy.get()) || + set[i].fImage->alphaType() != set[base].fImage->alphaType() || + !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) { + draw(); + base = i; + n = 1; + } else { + ++n; + } + } + draw(); +} + +// TODO (michaelludwig) - to be removed when drawBitmapRect doesn't need it anymore +void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer, + const SkRect* srcRect, + const SkRect* dstRect, + SkCanvas::SrcRectConstraint constraint, + const SkMatrix& viewMatrix, + const SkPaint& paint, + bool attemptDrawTexture) { + // The texture refactor split the old logic of drawTextureProducer into the beginning of + // drawImageQuad() and into the static draw_texture_producer. Replicate necessary logic that + // drawImageQuad() handles. + SkRect src; + SkRect dst; + SkMatrix srcToDst; + ImageDrawMode mode = optimize_sample_area(SkISize::Make(producer->width(), producer->height()), + srcRect, dstRect, nullptr, &src, &dst, &srcToDst); + if (mode == ImageDrawMode::kSkip) { + return; + } + // There's no dstClip to worry about and the producer is already made so we wouldn't be able + // to tell it to use decals if we had to + SkASSERT(mode != ImageDrawMode::kDecal); + + draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), viewMatrix, + paint, producer, src, dst, /* clip */ nullptr, srcToDst, + GrAA(paint.isAntiAlias()), + paint.isAntiAlias() ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone, + constraint, attemptDrawTexture); +} diff --git a/gfx/skia/skia/src/gpu/SkGr.cpp b/gfx/skia/skia/src/gpu/SkGr.cpp new file mode 100644 index 0000000000..bdb3c3c95d --- /dev/null +++ b/gfx/skia/skia/src/gpu/SkGr.cpp @@ -0,0 +1,609 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/SkGr.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkColorFilter.h" +#include "include/core/SkData.h" +#include "include/core/SkPixelRef.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrRecordingContext.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkMessageBus.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkPaintPriv.h" +#include "src/core/SkResourceCache.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrBitmapTextureMaker.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/effects/GrBicubicEffect.h" +#include "src/gpu/effects/GrPorterDuffXferProcessor.h" +#include "src/gpu/effects/GrSkSLFP.h" +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/effects/generated/GrSaturateProcessor.h" +#include "src/image/SkImage_Base.h" +#include "src/shaders/SkShaderBase.h" + +GR_FP_SRC_STRING SKSL_DITHER_SRC = R"( +// This controls the range of values added to color channels +in int rangeType; + +void main(float x, float y, inout half4 color) { + half value; + half range; + @switch (rangeType) { + case 0: + range = 1.0 / 255.0; + break; + case 1: + range = 1.0 / 63.0; + break; + default: + // Experimentally this looks better than the expected value of 1/15. + range = 1.0 / 15.0; + break; + } + @if (sk_Caps.integerSupport) { + // This ordered-dither code is lifted from the cpu backend. + uint x = uint(x); + uint y = uint(y); + uint m = (y & 1) << 5 | (x & 1) << 4 | + (y & 2) << 2 | (x & 2) << 1 | + (y & 4) >> 1 | (x & 4) >> 2; + value = half(m) * 1.0 / 64.0 - 63.0 / 128.0; + } else { + // Simulate the integer effect used above using step/mod. For speed, simulates a 4x4 + // dither pattern rather than an 8x8 one. + half4 modValues = mod(half4(half(x), half(y), half(x), half(y)), half4(2.0, 2.0, 4.0, 4.0)); + half4 stepValues = step(modValues, half4(1.0, 1.0, 2.0, 2.0)); + value = dot(stepValues, half4(8.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0)) - 15.0 / 32.0; + } + // For each color channel, add the random offset to the channel value and then clamp + // between 0 and alpha to keep the color premultiplied. + color = half4(clamp(color.rgb + value * range, 0.0, color.a), color.a); +} +)"; + +GrSurfaceDesc GrImageInfoToSurfaceDesc(const SkImageInfo& info) { + GrSurfaceDesc desc; + desc.fWidth = info.width(); + desc.fHeight = info.height(); + desc.fConfig = SkImageInfo2GrPixelConfig(info); + return desc; +} + +void GrMakeKeyFromImageID(GrUniqueKey* key, uint32_t imageID, const SkIRect& imageBounds) { + SkASSERT(key); + SkASSERT(imageID); + SkASSERT(!imageBounds.isEmpty()); + static const GrUniqueKey::Domain kImageIDDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(key, kImageIDDomain, 5, "Image"); + builder[0] = imageID; + builder[1] = imageBounds.fLeft; + builder[2] = imageBounds.fTop; + builder[3] = imageBounds.fRight; + builder[4] = imageBounds.fBottom; +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID, + SkPixelRef* pixelRef) { + class Invalidator : public SkPixelRef::GenIDChangeListener { + public: + explicit Invalidator(const GrUniqueKey& key, uint32_t contextUniqueID) + : fMsg(key, contextUniqueID) {} + + private: + GrUniqueKeyInvalidatedMessage fMsg; + + void onChange() override { SkMessageBus::Post(fMsg); } + }; + + pixelRef->addGenIDChangeListener(new Invalidator(key, contextUniqueID)); +} + +sk_sp GrCopyBaseMipMapToTextureProxy(GrRecordingContext* ctx, + GrTextureProxy* baseProxy, + GrColorType srcColorType) { + SkASSERT(baseProxy); + + if (!ctx->priv().caps()->isFormatCopyable(baseProxy->backendFormat())) { + return nullptr; + } + return GrSurfaceProxy::Copy(ctx, baseProxy, srcColorType, GrMipMapped::kYes, + SkBackingFit::kExact, SkBudgeted::kYes); +} + +sk_sp GrRefCachedBitmapTextureProxy(GrRecordingContext* ctx, + const SkBitmap& bitmap, + const GrSamplerState& params, + SkScalar scaleAdjust[2]) { + return GrBitmapTextureMaker(ctx, bitmap).refTextureProxyForParams(params, scaleAdjust); +} + +sk_sp GrMakeCachedBitmapProxy(GrProxyProvider* proxyProvider, + const SkBitmap& bitmap, + SkBackingFit fit) { + if (!bitmap.peekPixels(nullptr)) { + return nullptr; + } + + // In non-ddl we will always instantiate right away. Thus we never want to copy the SkBitmap + // even if its mutable. In ddl, if the bitmap is mutable then we must make a copy since the + // upload of the data to the gpu can happen at anytime and the bitmap may change by then. + SkCopyPixelsMode cpyMode = proxyProvider->renderingDirectly() ? kNever_SkCopyPixelsMode + : kIfMutable_SkCopyPixelsMode; + sk_sp image = SkMakeImageFromRasterBitmap(bitmap, cpyMode); + + if (!image) { + return nullptr; + } + + return GrMakeCachedImageProxy(proxyProvider, std::move(image), fit); +} + +static void create_unique_key_for_image(const SkImage* image, GrUniqueKey* result) { + if (!image) { + result->reset(); // will be invalid + return; + } + + if (const SkBitmap* bm = as_IB(image)->onPeekBitmap()) { + if (!bm->isVolatile()) { + SkIPoint origin = bm->pixelRefOrigin(); + SkIRect subset = SkIRect::MakeXYWH(origin.fX, origin.fY, bm->width(), bm->height()); + GrMakeKeyFromImageID(result, bm->getGenerationID(), subset); + } + return; + } + + GrMakeKeyFromImageID(result, image->uniqueID(), image->bounds()); +} + +sk_sp GrMakeCachedImageProxy(GrProxyProvider* proxyProvider, + sk_sp srcImage, + SkBackingFit fit) { + sk_sp proxy; + GrUniqueKey originalKey; + + create_unique_key_for_image(srcImage.get(), &originalKey); + + if (originalKey.isValid()) { + proxy = proxyProvider->findOrCreateProxyByUniqueKey( + originalKey, SkColorTypeToGrColorType(srcImage->colorType()), + kTopLeft_GrSurfaceOrigin); + } + if (!proxy) { + proxy = proxyProvider->createTextureProxy(srcImage, 1, SkBudgeted::kYes, fit); + if (proxy && originalKey.isValid()) { + proxyProvider->assignUniqueKeyToProxy(originalKey, proxy.get()); + const SkBitmap* bm = as_IB(srcImage.get())->onPeekBitmap(); + // When recording DDLs we do not want to install change listeners because doing + // so isn't threadsafe. + if (bm && proxyProvider->renderingDirectly()) { + GrInstallBitmapUniqueKeyInvalidator(originalKey, proxyProvider->contextID(), + bm->pixelRef()); + } + } + } + + return proxy; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPMColor4f SkColorToPMColor4f(SkColor c, const GrColorInfo& colorInfo) { + SkColor4f color = SkColor4f::FromColor(c); + if (auto* xform = colorInfo.colorSpaceXformFromSRGB()) { + color = xform->apply(color); + } + return color.premul(); +} + +SkColor4f SkColor4fPrepForDst(SkColor4f color, const GrColorInfo& colorInfo) { + if (auto* xform = colorInfo.colorSpaceXformFromSRGB()) { + color = xform->apply(color); + } + return color; +} + +/////////////////////////////////////////////////////////////////////////////// + +GrPixelConfig SkColorType2GrPixelConfig(const SkColorType type) { + switch (type) { + case kUnknown_SkColorType: + return kUnknown_GrPixelConfig; + case kAlpha_8_SkColorType: + return kAlpha_8_GrPixelConfig; + case kRGB_565_SkColorType: + return kRGB_565_GrPixelConfig; + case kARGB_4444_SkColorType: + return kRGBA_4444_GrPixelConfig; + case kRGBA_8888_SkColorType: + return kRGBA_8888_GrPixelConfig; + case kRGB_888x_SkColorType: + return kRGB_888_GrPixelConfig; + case kBGRA_8888_SkColorType: + return kBGRA_8888_GrPixelConfig; + case kRGBA_1010102_SkColorType: + return kRGBA_1010102_GrPixelConfig; + case kRGB_101010x_SkColorType: + return kUnknown_GrPixelConfig; + case kGray_8_SkColorType: + return kGray_8_GrPixelConfig; + case kRGBA_F16Norm_SkColorType: + return kRGBA_half_Clamped_GrPixelConfig; + case kRGBA_F16_SkColorType: + return kRGBA_half_GrPixelConfig; + case kRGBA_F32_SkColorType: + return kUnknown_GrPixelConfig; + case kR8G8_unorm_SkColorType: + return kRG_88_GrPixelConfig; + case kR16G16_unorm_SkColorType: + return kRG_1616_GrPixelConfig; + case kA16_unorm_SkColorType: + return kAlpha_16_GrPixelConfig; + case kA16_float_SkColorType: + return kAlpha_half_GrPixelConfig; + case kR16G16_float_SkColorType: + return kRG_half_GrPixelConfig; + case kR16G16B16A16_unorm_SkColorType: + return kRGBA_16161616_GrPixelConfig; + } + SkUNREACHABLE; +} + +GrPixelConfig SkImageInfo2GrPixelConfig(const SkImageInfo& info) { + return SkColorType2GrPixelConfig(info.colorType()); +} + +bool GrPixelConfigToColorType(GrPixelConfig config, SkColorType* ctOut) { + SkColorType ct = GrColorTypeToSkColorType(GrPixelConfigToColorType(config)); + if (kUnknown_SkColorType != ct) { + if (ctOut) { + *ctOut = ct; + } + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +static inline bool blend_requires_shader(const SkBlendMode mode) { + return SkBlendMode::kDst != mode; +} + +#ifndef SK_IGNORE_GPU_DITHER +static inline int32_t dither_range_type_for_config(GrColorType dstColorType) { + switch (dstColorType) { + case GrColorType::kGray_8: + case GrColorType::kRGBA_8888: + case GrColorType::kRGB_888x: + case GrColorType::kRG_88: + case GrColorType::kBGRA_8888: + case GrColorType::kRG_1616: + case GrColorType::kRGBA_16161616: + case GrColorType::kRG_F16: + return 0; + case GrColorType::kBGR_565: + return 1; + case GrColorType::kABGR_4444: + return 2; + case GrColorType::kUnknown: + case GrColorType::kRGBA_8888_SRGB: + case GrColorType::kRGBA_1010102: + case GrColorType::kAlpha_F16: + case GrColorType::kRGBA_F32: + case GrColorType::kRGBA_F16: + case GrColorType::kRGBA_F16_Clamped: + case GrColorType::kAlpha_8: + case GrColorType::kAlpha_8xxx: + case GrColorType::kAlpha_16: + case GrColorType::kAlpha_F32xxx: + case GrColorType::kGray_8xxx: + return -1; + } + SkUNREACHABLE; +} +#endif + +static inline bool skpaint_to_grpaint_impl(GrRecordingContext* context, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + const SkMatrix& viewM, + std::unique_ptr* shaderProcessor, + SkBlendMode* primColorMode, + GrPaint* grPaint) { + // Convert SkPaint color to 4f format in the destination color space + SkColor4f origColor = SkColor4fPrepForDst(skPaint.getColor4f(), dstColorInfo); + + GrFPArgs fpArgs(context, &viewM, skPaint.getFilterQuality(), &dstColorInfo); + + // Setup the initial color considering the shader, the SkPaint color, and the presence or not + // of per-vertex colors. + std::unique_ptr shaderFP; + if (!primColorMode || blend_requires_shader(*primColorMode)) { + fpArgs.fInputColorIsOpaque = origColor.isOpaque(); + if (shaderProcessor) { + shaderFP = std::move(*shaderProcessor); + } else if (const auto* shader = as_SB(skPaint.getShader())) { + shaderFP = shader->asFragmentProcessor(fpArgs); + if (!shaderFP) { + return false; + } + } + } + + // Set this in below cases if the output of the shader/paint-color/paint-alpha/primXfermode is + // a known constant value. In that case we can simply apply a color filter during this + // conversion without converting the color filter to a GrFragmentProcessor. + bool applyColorFilterToPaintColor = false; + if (shaderFP) { + if (primColorMode) { + // There is a blend between the primitive color and the shader color. The shader sees + // the opaque paint color. The shader's output is blended using the provided mode by + // the primitive color. The blended color is then modulated by the paint's alpha. + + // The geometry processor will insert the primitive color to start the color chain, so + // the GrPaint color will be ignored. + + SkPMColor4f shaderInput = origColor.makeOpaque().premul(); + shaderFP = GrFragmentProcessor::OverrideInput(std::move(shaderFP), shaderInput); + shaderFP = GrXfermodeFragmentProcessor::MakeFromSrcProcessor(std::move(shaderFP), + *primColorMode); + + // The above may return null if compose results in a pass through of the prim color. + if (shaderFP) { + grPaint->addColorFragmentProcessor(std::move(shaderFP)); + } + + // We can ignore origColor here - alpha is unchanged by gamma + float paintAlpha = skPaint.getColor4f().fA; + if (1.0f != paintAlpha) { + // No gamut conversion - paintAlpha is a (linear) alpha value, splatted to all + // color channels. It's value should be treated as the same in ANY color space. + grPaint->addColorFragmentProcessor(GrConstColorProcessor::Make( + { paintAlpha, paintAlpha, paintAlpha, paintAlpha }, + GrConstColorProcessor::InputMode::kModulateRGBA)); + } + } else { + // The shader's FP sees the paint *unpremul* color + SkPMColor4f origColorAsPM = { origColor.fR, origColor.fG, origColor.fB, origColor.fA }; + grPaint->setColor4f(origColorAsPM); + grPaint->addColorFragmentProcessor(std::move(shaderFP)); + } + } else { + if (primColorMode) { + // There is a blend between the primitive color and the paint color. The blend considers + // the opaque paint color. The paint's alpha is applied to the post-blended color. + SkPMColor4f opaqueColor = origColor.makeOpaque().premul(); + auto processor = GrConstColorProcessor::Make(opaqueColor, + GrConstColorProcessor::InputMode::kIgnore); + processor = GrXfermodeFragmentProcessor::MakeFromSrcProcessor(std::move(processor), + *primColorMode); + if (processor) { + grPaint->addColorFragmentProcessor(std::move(processor)); + } + + grPaint->setColor4f(opaqueColor); + + // We can ignore origColor here - alpha is unchanged by gamma + float paintAlpha = skPaint.getColor4f().fA; + if (1.0f != paintAlpha) { + // No gamut conversion - paintAlpha is a (linear) alpha value, splatted to all + // color channels. It's value should be treated as the same in ANY color space. + grPaint->addColorFragmentProcessor(GrConstColorProcessor::Make( + { paintAlpha, paintAlpha, paintAlpha, paintAlpha }, + GrConstColorProcessor::InputMode::kModulateRGBA)); + } + } else { + // No shader, no primitive color. + grPaint->setColor4f(origColor.premul()); + applyColorFilterToPaintColor = true; + } + } + + SkColorFilter* colorFilter = skPaint.getColorFilter(); + if (colorFilter) { + if (applyColorFilterToPaintColor) { + SkColorSpace* dstCS = dstColorInfo.colorSpace(); + grPaint->setColor4f(colorFilter->filterColor4f(origColor, dstCS, dstCS).premul()); + } else { + auto cfFP = colorFilter->asFragmentProcessor(context, dstColorInfo); + if (cfFP) { + grPaint->addColorFragmentProcessor(std::move(cfFP)); + } else { + return false; + } + } + } + + SkMaskFilterBase* maskFilter = as_MFB(skPaint.getMaskFilter()); + if (maskFilter) { + // We may have set this before passing to the SkShader. + fpArgs.fInputColorIsOpaque = false; + if (auto mfFP = maskFilter->asFragmentProcessor(fpArgs)) { + grPaint->addCoverageFragmentProcessor(std::move(mfFP)); + } + } + + // When the xfermode is null on the SkPaint (meaning kSrcOver) we need the XPFactory field on + // the GrPaint to also be null (also kSrcOver). + SkASSERT(!grPaint->getXPFactory()); + if (!skPaint.isSrcOver()) { + grPaint->setXPFactory(SkBlendMode_AsXPFactory(skPaint.getBlendMode())); + } + +#ifndef SK_IGNORE_GPU_DITHER + // Conservative default, in case GrPixelConfigToColorType() fails. + GrColorType ct = dstColorInfo.colorType(); + if (SkPaintPriv::ShouldDither(skPaint, GrColorTypeToSkColorType(ct)) && + grPaint->numColorFragmentProcessors() > 0) { + int32_t ditherRange = dither_range_type_for_config(ct); + if (ditherRange >= 0) { + static int ditherIndex = GrSkSLFP::NewIndex(); + auto ditherFP = GrSkSLFP::Make(context, ditherIndex, "Dither", SKSL_DITHER_SRC, + &ditherRange, sizeof(ditherRange)); + if (ditherFP) { + grPaint->addColorFragmentProcessor(std::move(ditherFP)); + } + } + } +#endif + if (GrColorTypeClampType(dstColorInfo.colorType()) == GrClampType::kManual) { + if (grPaint->numColorFragmentProcessors()) { + grPaint->addColorFragmentProcessor(GrSaturateProcessor::Make()); + } else { + auto color = grPaint->getColor4f(); + grPaint->setColor4f({SkTPin(color.fR, 0.f, 1.f), + SkTPin(color.fG, 0.f, 1.f), + SkTPin(color.fB, 0.f, 1.f), + SkTPin(color.fA, 0.f, 1.f)}); + } + } + return true; +} + +bool SkPaintToGrPaint(GrRecordingContext* context, const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, const SkMatrix& viewM, GrPaint* grPaint) { + return skpaint_to_grpaint_impl(context, dstColorInfo, skPaint, viewM, nullptr, nullptr, + grPaint); +} + +/** Replaces the SkShader (if any) on skPaint with the passed in GrFragmentProcessor. */ +bool SkPaintToGrPaintReplaceShader(GrRecordingContext* context, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + std::unique_ptr shaderFP, + GrPaint* grPaint) { + if (!shaderFP) { + return false; + } + return skpaint_to_grpaint_impl(context, dstColorInfo, skPaint, SkMatrix::I(), &shaderFP, + nullptr, grPaint); +} + +/** Ignores the SkShader (if any) on skPaint. */ +bool SkPaintToGrPaintNoShader(GrRecordingContext* context, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + GrPaint* grPaint) { + // Use a ptr to a nullptr to to indicate that the SkShader is ignored and not replaced. + std::unique_ptr nullShaderFP(nullptr); + return skpaint_to_grpaint_impl(context, dstColorInfo, skPaint, SkMatrix::I(), &nullShaderFP, + nullptr, grPaint); +} + +/** Blends the SkPaint's shader (or color if no shader) with a per-primitive color which must +be setup as a vertex attribute using the specified SkBlendMode. */ +bool SkPaintToGrPaintWithXfermode(GrRecordingContext* context, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + const SkMatrix& viewM, + SkBlendMode primColorMode, + GrPaint* grPaint) { + return skpaint_to_grpaint_impl(context, dstColorInfo, skPaint, viewM, nullptr, &primColorMode, + grPaint); +} + +bool SkPaintToGrPaintWithTexture(GrRecordingContext* context, + const GrColorInfo& dstColorInfo, + const SkPaint& paint, + const SkMatrix& viewM, + std::unique_ptr fp, + bool textureIsAlphaOnly, + GrPaint* grPaint) { + std::unique_ptr shaderFP; + if (textureIsAlphaOnly) { + if (const auto* shader = as_SB(paint.getShader())) { + shaderFP = shader->asFragmentProcessor( + GrFPArgs(context, &viewM, paint.getFilterQuality(), &dstColorInfo)); + if (!shaderFP) { + return false; + } + std::unique_ptr fpSeries[] = { std::move(shaderFP), std::move(fp) }; + shaderFP = GrFragmentProcessor::RunInSeries(fpSeries, 2); + } else { + shaderFP = GrFragmentProcessor::MakeInputPremulAndMulByOutput(std::move(fp)); + } + } else { + if (paint.getColor4f().isOpaque()) { + shaderFP = GrFragmentProcessor::OverrideInput(std::move(fp), SK_PMColor4fWHITE, false); + } else { + shaderFP = GrFragmentProcessor::MulChildByInputAlpha(std::move(fp)); + } + } + + return SkPaintToGrPaintReplaceShader(context, dstColorInfo, paint, std::move(shaderFP), + grPaint); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +GrSamplerState::Filter GrSkFilterQualityToGrFilterMode(int imageWidth, int imageHeight, + SkFilterQuality paintFilterQuality, + const SkMatrix& viewM, + const SkMatrix& localM, + bool sharpenMipmappedTextures, + bool* doBicubic) { + *doBicubic = false; + if (imageWidth <= 1 && imageHeight <= 1) { + return GrSamplerState::Filter::kNearest; + } + switch (paintFilterQuality) { + case kNone_SkFilterQuality: + return GrSamplerState::Filter::kNearest; + case kLow_SkFilterQuality: + return GrSamplerState::Filter::kBilerp; + case kMedium_SkFilterQuality: { + SkMatrix matrix; + matrix.setConcat(viewM, localM); + // With sharp mips, we bias lookups by -0.5. That means our final LOD is >= 0 until the + // computed LOD is >= 0.5. At what scale factor does a texture get an LOD of 0.5? + // + // Want: 0 = log2(1/s) - 0.5 + // 0.5 = log2(1/s) + // 2^0.5 = 1/s + // 1/2^0.5 = s + // 2^0.5/2 = s + SkScalar mipScale = sharpenMipmappedTextures ? SK_ScalarRoot2Over2 : SK_Scalar1; + if (matrix.getMinScale() < mipScale) { + return GrSamplerState::Filter::kMipMap; + } else { + // Don't trigger MIP level generation unnecessarily. + return GrSamplerState::Filter::kBilerp; + } + } + case kHigh_SkFilterQuality: { + SkMatrix matrix; + matrix.setConcat(viewM, localM); + GrSamplerState::Filter textureFilterMode; + *doBicubic = GrBicubicEffect::ShouldUseBicubic(matrix, &textureFilterMode); + return textureFilterMode; + } + } + SkUNREACHABLE; +} diff --git a/gfx/skia/skia/src/gpu/SkGr.h b/gfx/skia/skia/src/gpu/SkGr.h new file mode 100644 index 0000000000..79a68822b9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/SkGr.h @@ -0,0 +1,232 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGr_DEFINED +#define SkGr_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkFilterQuality.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkVertices.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkColorData.h" +#include "src/core/SkBlendModePriv.h" +#include "src/gpu/GrBlend.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrSamplerState.h" + +class GrCaps; +class GrColorInfo; +class GrColorSpaceXform; +class GrContext; +class GrFragmentProcessor; +class GrPaint; +class GrRecordingContext; +class GrResourceProvider; +class GrTextureProxy; +class GrUniqueKey; +class SkBitmap; +class SkData; +class SkPaint; +class SkPixelRef; +class SkPixmap; +struct SkIRect; + +//////////////////////////////////////////////////////////////////////////////// +// Color type conversions + +static inline GrColor SkColorToPremulGrColor(SkColor c) { + SkPMColor pm = SkPreMultiplyColor(c); + unsigned r = SkGetPackedR32(pm); + unsigned g = SkGetPackedG32(pm); + unsigned b = SkGetPackedB32(pm); + unsigned a = SkGetPackedA32(pm); + return GrColorPackRGBA(r, g, b, a); +} + +static inline GrColor SkColorToUnpremulGrColor(SkColor c) { + unsigned r = SkColorGetR(c); + unsigned g = SkColorGetG(c); + unsigned b = SkColorGetB(c); + unsigned a = SkColorGetA(c); + return GrColorPackRGBA(r, g, b, a); +} + +/** Similar, but using SkPMColor4f. */ +SkPMColor4f SkColorToPMColor4f(SkColor, const GrColorInfo&); + +/** Converts an SkColor4f to the destination color space. */ +SkColor4f SkColor4fPrepForDst(SkColor4f, const GrColorInfo&); + +/** Returns true if half-floats are required to store the color in a vertex (and half-floats + are supported). */ +static inline bool SkPMColor4fNeedsWideColor(SkPMColor4f color, GrClampType clampType, + const GrCaps& caps) { + return GrClampType::kNone == clampType && + caps.halfFloatVertexAttributeSupport() && + !color.fitsInBytes(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Paint conversion + +/** Converts an SkPaint to a GrPaint for a given GrRecordingContext. The matrix is required in order + to convert the SkShader (if any) on the SkPaint. The primitive itself has no color. */ +bool SkPaintToGrPaint(GrRecordingContext*, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + const SkMatrix& viewM, + GrPaint* grPaint); + +/** Same as above but ignores the SkShader (if any) on skPaint. */ +bool SkPaintToGrPaintNoShader(GrRecordingContext*, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + GrPaint* grPaint); + +/** Replaces the SkShader (if any) on skPaint with the passed in GrFragmentProcessor. The processor + should expect an unpremul input color and produce a premultiplied output color. There is + no primitive color. */ +bool SkPaintToGrPaintReplaceShader(GrRecordingContext*, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + std::unique_ptr shaderFP, + GrPaint* grPaint); + +/** Blends the SkPaint's shader (or color if no shader) with the color which specified via a + GrOp's GrPrimitiveProcesssor. */ +bool SkPaintToGrPaintWithXfermode(GrRecordingContext*, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + const SkMatrix& viewM, + SkBlendMode primColorMode, + GrPaint* grPaint); + +/** This is used when there is a primitive color, but the shader should be ignored. Currently, + the expectation is that the primitive color will be premultiplied, though it really should be + unpremultiplied so that interpolation is done in unpremul space. The paint's alpha will be + applied to the primitive color after interpolation. */ +inline bool SkPaintToGrPaintWithPrimitiveColor(GrRecordingContext* context, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + GrPaint* grPaint) { + return SkPaintToGrPaintWithXfermode(context, dstColorInfo, skPaint, SkMatrix::I(), + SkBlendMode::kDst, grPaint); +} + +/** This is used when there may or may not be a shader, and the caller wants to plugin a texture + lookup. If there is a shader, then its output will only be used if the texture is alpha8. */ +bool SkPaintToGrPaintWithTexture(GrRecordingContext*, + const GrColorInfo& dstColorInfo, + const SkPaint& skPaint, + const SkMatrix& viewM, + std::unique_ptr fp, + bool textureIsAlphaOnly, + GrPaint* grPaint); + +//////////////////////////////////////////////////////////////////////////////// +// Misc Sk to Gr type conversions + +GrSurfaceDesc GrImageInfoToSurfaceDesc(const SkImageInfo&); +GrPixelConfig SkColorType2GrPixelConfig(const SkColorType); +GrPixelConfig SkImageInfo2GrPixelConfig(const SkImageInfo& info); + +bool GrPixelConfigToColorType(GrPixelConfig, SkColorType*); + +GrSamplerState::Filter GrSkFilterQualityToGrFilterMode(int imageWidth, int imageHeight, + SkFilterQuality paintFilterQuality, + const SkMatrix& viewM, + const SkMatrix& localM, + bool sharpenMipmappedTextures, + bool* doBicubic); + +////////////////////////////////////////////////////////////////////////////// + +static inline GrPrimitiveType SkVertexModeToGrPrimitiveType(SkVertices::VertexMode mode) { + switch (mode) { + case SkVertices::kTriangles_VertexMode: + return GrPrimitiveType::kTriangles; + case SkVertices::kTriangleStrip_VertexMode: + return GrPrimitiveType::kTriangleStrip; + case SkVertices::kTriangleFan_VertexMode: + break; + } + SK_ABORT("Invalid mode"); +} + +////////////////////////////////////////////////////////////////////////////// + +GR_STATIC_ASSERT((int)kZero_GrBlendCoeff == (int)SkBlendModeCoeff::kZero); +GR_STATIC_ASSERT((int)kOne_GrBlendCoeff == (int)SkBlendModeCoeff::kOne); +GR_STATIC_ASSERT((int)kSC_GrBlendCoeff == (int)SkBlendModeCoeff::kSC); +GR_STATIC_ASSERT((int)kISC_GrBlendCoeff == (int)SkBlendModeCoeff::kISC); +GR_STATIC_ASSERT((int)kDC_GrBlendCoeff == (int)SkBlendModeCoeff::kDC); +GR_STATIC_ASSERT((int)kIDC_GrBlendCoeff == (int)SkBlendModeCoeff::kIDC); +GR_STATIC_ASSERT((int)kSA_GrBlendCoeff == (int)SkBlendModeCoeff::kSA); +GR_STATIC_ASSERT((int)kISA_GrBlendCoeff == (int)SkBlendModeCoeff::kISA); +GR_STATIC_ASSERT((int)kDA_GrBlendCoeff == (int)SkBlendModeCoeff::kDA); +GR_STATIC_ASSERT((int)kIDA_GrBlendCoeff == (int)SkBlendModeCoeff::kIDA); +//GR_STATIC_ASSERT(SkXfermode::kCoeffCount == 10); + +//////////////////////////////////////////////////////////////////////////////// +// Texture management + +/** Returns a texture representing the bitmap that is compatible with the GrSamplerState. The + * texture is inserted into the cache (unless the bitmap is marked volatile) and can be + * retrieved again via this function. + * The 'scaleAdjust' in/out parameter will be updated to hold any rescaling that needs to be + * performed on the absolute texture coordinates (e.g., if the texture is resized out to + * the next power of two). It can be null if the caller is sure the bitmap won't be resized. + */ +sk_sp GrRefCachedBitmapTextureProxy(GrRecordingContext*, + const SkBitmap&, + const GrSamplerState&, + SkScalar scaleAdjust[2]); + +/** + * Creates a new texture with mipmap levels and copies the baseProxy into the base layer. + */ +sk_sp GrCopyBaseMipMapToTextureProxy(GrRecordingContext*, + GrTextureProxy* baseProxy, + GrColorType srcColorType); + +/* + * Create a texture proxy from the provided bitmap by wrapping it in an image and calling + * GrMakeCachedImageProxy. + */ +sk_sp GrMakeCachedBitmapProxy(GrProxyProvider*, const SkBitmap& bitmap, + SkBackingFit fit = SkBackingFit::kExact); + +/* + * Create a texture proxy from the provided 'srcImage' and add it to the texture cache + * using the key also extracted from 'srcImage'. + */ +sk_sp GrMakeCachedImageProxy(GrProxyProvider*, sk_sp srcImage, + SkBackingFit fit = SkBackingFit::kExact); + +/** + * Our key includes the offset, width, and height so that bitmaps created by extractSubset() + * are unique. + * + * The imageID is in the shared namespace (see SkNextID::ImageID()) + * - SkBitmap/SkPixelRef + * - SkImage + * - SkImageGenerator + * + * Note: width/height must fit in 16bits for this impl. + */ +void GrMakeKeyFromImageID(GrUniqueKey* key, uint32_t imageID, const SkIRect& imageBounds); + +/** Call this after installing a GrUniqueKey on texture. It will cause the texture's key to be + removed should the bitmap's contents change or be destroyed. */ +void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextID, + SkPixelRef* pixelRef); + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.cpp new file mode 100644 index 0000000000..55d8f970b0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.cpp @@ -0,0 +1,268 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCAtlas.h" + +#include "include/gpu/GrTexture.h" +#include "src/core/SkIPoint16.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRectanizer_skyline.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/ccpr/GrCCPathCache.h" +#include + +class GrCCAtlas::Node { +public: + Node(std::unique_ptr previous, int l, int t, int r, int b) + : fPrevious(std::move(previous)), fX(l), fY(t), fRectanizer(r - l, b - t) {} + + Node* previous() const { return fPrevious.get(); } + + bool addRect(int w, int h, SkIPoint16* loc, int maxAtlasSize) { + // Pad all paths except those that are expected to take up an entire physical texture. + if (w < maxAtlasSize) { + w = SkTMin(w + kPadding, maxAtlasSize); + } + if (h < maxAtlasSize) { + h = SkTMin(h + kPadding, maxAtlasSize); + } + if (!fRectanizer.addRect(w, h, loc)) { + return false; + } + loc->fX += fX; + loc->fY += fY; + return true; + } + +private: + const std::unique_ptr fPrevious; + const int fX, fY; + GrRectanizerSkyline fRectanizer; +}; + +sk_sp GrCCAtlas::MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback& callback, + CoverageType coverageType, + const GrCaps& caps, + GrSurfaceProxy::UseAllocator useAllocator) { + GrPixelConfig pixelConfig; + int sampleCount; + + auto colorType = CoverageTypeToColorType(coverageType); + GrBackendFormat format = caps.getDefaultBackendFormat(colorType, GrRenderable::kYes); + switch (coverageType) { + case CoverageType::kFP16_CoverageCount: + pixelConfig = kAlpha_half_GrPixelConfig; + sampleCount = 1; + break; + case CoverageType::kA8_Multisample: + SkASSERT(caps.internalMultisampleCount(format) > 1); + pixelConfig = kAlpha_8_GrPixelConfig; + sampleCount = (caps.mixedSamplesSupport()) ? 1 : caps.internalMultisampleCount(format); + break; + case CoverageType::kA8_LiteralCoverage: + pixelConfig = kAlpha_8_GrPixelConfig; + sampleCount = 1; + break; + } + + auto instantiate = [cb = std::move(callback), pixelConfig, format, + sampleCount](GrResourceProvider* rp) { + return cb(rp, pixelConfig, format, sampleCount); + }; + sk_sp proxy = GrProxyProvider::MakeFullyLazyProxy( + std::move(instantiate), format, GrRenderable::kYes, sampleCount, GrProtected::kNo, + kTextureOrigin, pixelConfig, caps, useAllocator); + + return proxy; +} + +GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps& caps) + : fCoverageType(coverageType) + , fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth), + specs.fMaxPreferredTextureSize)) { + // Caller should have cropped any paths to the destination render target instead of asking for + // an atlas larger than maxRenderTargetSize. + SkASSERT(fMaxTextureSize <= caps.maxTextureSize()); + SkASSERT(specs.fMaxPreferredTextureSize > 0); + + // Begin with the first pow2 dimensions whose area is theoretically large enough to contain the + // pending paths, favoring height over width if necessary. + int log2area = SkNextLog2(SkTMax(specs.fApproxNumPixels, 1)); + fHeight = 1 << ((log2area + 1) / 2); + fWidth = 1 << (log2area / 2); + + fWidth = SkTClamp(fWidth, specs.fMinTextureSize, specs.fMaxPreferredTextureSize); + fHeight = SkTClamp(fHeight, specs.fMinTextureSize, specs.fMaxPreferredTextureSize); + + if (fWidth < specs.fMinWidth || fHeight < specs.fMinHeight) { + // They want to stuff a particularly large path into the atlas. Just punt and go with their + // min width and height. The atlas will grow as needed. + fWidth = SkTMin(specs.fMinWidth + kPadding, fMaxTextureSize); + fHeight = SkTMin(specs.fMinHeight + kPadding, fMaxTextureSize); + } + + fTopNode = skstd::make_unique(nullptr, 0, 0, fWidth, fHeight); + + fTextureProxy = MakeLazyAtlasProxy( + [this](GrResourceProvider* resourceProvider, GrPixelConfig pixelConfig, + const GrBackendFormat& format, int sampleCount) { + if (!fBackingTexture) { + GrSurfaceDesc desc; + desc.fWidth = fWidth; + desc.fHeight = fHeight; + desc.fConfig = pixelConfig; + fBackingTexture = resourceProvider->createTexture( + desc, format, GrRenderable::kYes, sampleCount, GrMipMapped::kNo, + SkBudgeted::kYes, GrProtected::kNo); + } + return fBackingTexture; + }, + fCoverageType, caps, GrSurfaceProxy::UseAllocator::kNo); +} + +GrCCAtlas::~GrCCAtlas() { +} + +bool GrCCAtlas::addRect(const SkIRect& devIBounds, SkIVector* offset) { + // This can't be called anymore once makeRenderTargetContext() has been called. + SkASSERT(!fTextureProxy->isInstantiated()); + + SkIPoint16 location; + if (!this->internalPlaceRect(devIBounds.width(), devIBounds.height(), &location)) { + return false; + } + offset->set(location.x() - devIBounds.left(), location.y() - devIBounds.top()); + + fDrawBounds.fWidth = SkTMax(fDrawBounds.width(), location.x() + devIBounds.width()); + fDrawBounds.fHeight = SkTMax(fDrawBounds.height(), location.y() + devIBounds.height()); + return true; +} + +bool GrCCAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) { + for (Node* node = fTopNode.get(); node; node = node->previous()) { + if (node->addRect(w, h, loc, fMaxTextureSize)) { + return true; + } + } + + // The rect didn't fit. Grow the atlas and try again. + do { + if (fWidth == fMaxTextureSize && fHeight == fMaxTextureSize) { + return false; + } + if (fHeight <= fWidth) { + int top = fHeight; + fHeight = SkTMin(fHeight * 2, fMaxTextureSize); + fTopNode = skstd::make_unique(std::move(fTopNode), 0, top, fWidth, fHeight); + } else { + int left = fWidth; + fWidth = SkTMin(fWidth * 2, fMaxTextureSize); + fTopNode = skstd::make_unique(std::move(fTopNode), left, 0, fWidth, fHeight); + } + } while (!fTopNode->addRect(w, h, loc, fMaxTextureSize)); + + return true; +} + +void GrCCAtlas::setFillBatchID(int id) { + // This can't be called anymore once makeRenderTargetContext() has been called. + SkASSERT(!fTextureProxy->isInstantiated()); + fFillBatchID = id; +} + +void GrCCAtlas::setStrokeBatchID(int id) { + // This can't be called anymore once makeRenderTargetContext() has been called. + SkASSERT(!fTextureProxy->isInstantiated()); + fStrokeBatchID = id; +} + +void GrCCAtlas::setEndStencilResolveInstance(int idx) { + // This can't be called anymore once makeRenderTargetContext() has been called. + SkASSERT(!fTextureProxy->isInstantiated()); + fEndStencilResolveInstance = idx; +} + +static uint32_t next_atlas_unique_id() { + static std::atomic nextID; + return nextID++; +} + +sk_sp GrCCAtlas::refOrMakeCachedAtlas(GrOnFlushResourceProvider* onFlushRP) { + if (!fCachedAtlas) { + static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain(); + + GrUniqueKey atlasUniqueKey; + GrUniqueKey::Builder builder(&atlasUniqueKey, kAtlasDomain, 1, "CCPR Atlas"); + builder[0] = next_atlas_unique_id(); + builder.finish(); + + onFlushRP->assignUniqueKeyToProxy(atlasUniqueKey, fTextureProxy.get()); + + fCachedAtlas = sk_make_sp(fCoverageType, atlasUniqueKey, fTextureProxy); + } + + SkASSERT(fCachedAtlas->coverageType() == fCoverageType); + SkASSERT(fCachedAtlas->getOnFlushProxy() == fTextureProxy.get()); + return fCachedAtlas; +} + +std::unique_ptr GrCCAtlas::makeRenderTargetContext( + GrOnFlushResourceProvider* onFlushRP, sk_sp backingTexture) { + SkASSERT(!fTextureProxy->isInstantiated()); // This method should only be called once. + // Caller should have cropped any paths to the destination render target instead of asking for + // an atlas larger than maxRenderTargetSize. + SkASSERT(SkTMax(fHeight, fWidth) <= fMaxTextureSize); + SkASSERT(fMaxTextureSize <= onFlushRP->caps()->maxRenderTargetSize()); + + // Finalize the content size of our proxy. The GPU can potentially make optimizations if it + // knows we only intend to write out a smaller sub-rectangle of the backing texture. + fTextureProxy->priv().setLazySize(fDrawBounds.width(), fDrawBounds.height()); + + if (backingTexture) { +#ifdef SK_DEBUG + auto backingRT = backingTexture->asRenderTarget(); + SkASSERT(backingRT); + SkASSERT(backingRT->config() == fTextureProxy->config()); + SkASSERT(backingRT->numSamples() == fTextureProxy->asRenderTargetProxy()->numSamples()); + SkASSERT(backingRT->width() == fWidth); + SkASSERT(backingRT->height() == fHeight); +#endif + fBackingTexture = std::move(backingTexture); + } + auto colorType = (CoverageType::kFP16_CoverageCount == fCoverageType) + ? GrColorType::kAlpha_F16 : GrColorType::kAlpha_8; + auto rtc = onFlushRP->makeRenderTargetContext(fTextureProxy, colorType, nullptr, nullptr); + if (!rtc) { + SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some paths will not be drawn.\n", + fWidth, fHeight); + return nullptr; + } + + SkIRect clearRect = SkIRect::MakeSize(fDrawBounds); + rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); + return rtc; +} + +GrCCAtlas* GrCCAtlasStack::addRect(const SkIRect& devIBounds, SkIVector* devToAtlasOffset) { + GrCCAtlas* retiredAtlas = nullptr; + if (fAtlases.empty() || !fAtlases.back().addRect(devIBounds, devToAtlasOffset)) { + // The retired atlas is out of room and can't grow any bigger. + retiredAtlas = !fAtlases.empty() ? &fAtlases.back() : nullptr; + fAtlases.emplace_back(fCoverageType, fSpecs, *fCaps); + SkASSERT(devIBounds.width() <= fSpecs.fMinWidth); + SkASSERT(devIBounds.height() <= fSpecs.fMinHeight); + SkAssertResult(fAtlases.back().addRect(devIBounds, devToAtlasOffset)); + } + return retiredAtlas; +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.h b/gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.h new file mode 100644 index 0000000000..ac7faaa41d --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCAtlas.h @@ -0,0 +1,177 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCAtlas_DEFINED +#define GrCCAtlas_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkSize.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrResourceKey.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/GrSurfaceProxy.h" + +class GrCCCachedAtlas; +class GrOnFlushResourceProvider; +class GrRenderTargetContext; +class GrResourceProvider; +class GrTextureProxy; +struct SkIPoint16; +struct SkIRect; + +/** + * This class implements a dynamic size GrRectanizer that grows until it reaches the implementation- + * dependent max texture size. When finalized, it also creates and stores a GrTextureProxy for the + * underlying atlas. + */ +class GrCCAtlas { +public: + // As long as GrSurfaceOrigin exists, we just have to decide on one for the atlas texture. + static constexpr GrSurfaceOrigin kTextureOrigin = kTopLeft_GrSurfaceOrigin; + static constexpr int kPadding = 1; // Amount of padding below and to the right of each path. + + // This struct encapsulates the minimum and desired requirements for an atlas, as well as an + // approximate number of pixels to help select a good initial size. + struct Specs { + int fMaxPreferredTextureSize = 0; + int fMinTextureSize = 0; + int fMinWidth = 0; // If there are 100 20x10 paths, this should be 20. + int fMinHeight = 0; // If there are 100 20x10 paths, this should be 10. + int fApproxNumPixels = 0; + + // Add space for a rect in the desired atlas specs. + void accountForSpace(int width, int height); + }; + + enum class CoverageType { + kFP16_CoverageCount, + kA8_Multisample, + kA8_LiteralCoverage + }; + + static constexpr GrColorType CoverageTypeToColorType(CoverageType coverageType) { + switch (coverageType) { + case CoverageType::kFP16_CoverageCount: + return GrColorType::kAlpha_F16; + case CoverageType::kA8_Multisample: + case CoverageType::kA8_LiteralCoverage: + return GrColorType::kAlpha_8; + } + SkUNREACHABLE; + } + + using LazyInstantiateAtlasCallback = std::function( + GrResourceProvider*, GrPixelConfig, const GrBackendFormat&, int sampleCount)>; + + static sk_sp MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback&, + CoverageType, + const GrCaps&, + GrSurfaceProxy::UseAllocator); + + GrCCAtlas(CoverageType, const Specs&, const GrCaps&); + ~GrCCAtlas(); + + GrTextureProxy* textureProxy() const { return fTextureProxy.get(); } + int currentWidth() const { return fWidth; } + int currentHeight() const { return fHeight; } + + // Attempts to add a rect to the atlas. If successful, returns the integer offset from + // device-space pixels where the path will be drawn, to atlas pixels where its mask resides. + bool addRect(const SkIRect& devIBounds, SkIVector* atlasOffset); + const SkISize& drawBounds() { return fDrawBounds; } + + // This is an optional space for the caller to jot down user-defined instance data to use when + // rendering atlas content. + void setFillBatchID(int id); + int getFillBatchID() const { return fFillBatchID; } + void setStrokeBatchID(int id); + int getStrokeBatchID() const { return fStrokeBatchID; } + void setEndStencilResolveInstance(int idx); + int getEndStencilResolveInstance() const { return fEndStencilResolveInstance; } + + sk_sp refOrMakeCachedAtlas(GrOnFlushResourceProvider*); + + // Instantiates our texture proxy for the atlas and returns a pre-cleared GrRenderTargetContext + // that the caller may use to render the content. After this call, it is no longer valid to call + // addRect(), setUserBatchID(), or this method again. + // + // 'backingTexture', if provided, is a renderable texture with which to instantiate our proxy. + // If null then we will create a texture using the resource provider. The purpose of this param + // is to provide a guaranteed way to recycle a stashed atlas texture from a previous flush. + std::unique_ptr makeRenderTargetContext( + GrOnFlushResourceProvider*, sk_sp backingTexture = nullptr); + +private: + class Node; + + bool internalPlaceRect(int w, int h, SkIPoint16* loc); + + const CoverageType fCoverageType; + const int fMaxTextureSize; + int fWidth, fHeight; + std::unique_ptr fTopNode; + SkISize fDrawBounds = {0, 0}; + + int fFillBatchID; + int fStrokeBatchID; + int fEndStencilResolveInstance; + + sk_sp fCachedAtlas; + sk_sp fTextureProxy; + sk_sp fBackingTexture; +}; + +/** + * This class implements an unbounded stack of atlases. When the current atlas reaches the + * implementation-dependent max texture size, a new one is pushed to the back and we continue on. + */ +class GrCCAtlasStack { +public: + using CoverageType = GrCCAtlas::CoverageType; + + GrCCAtlasStack(CoverageType coverageType, const GrCCAtlas::Specs& specs, const GrCaps* caps) + : fCoverageType(coverageType), fSpecs(specs), fCaps(caps) {} + + CoverageType coverageType() const { return fCoverageType; } + bool empty() const { return fAtlases.empty(); } + const GrCCAtlas& front() const { SkASSERT(!this->empty()); return fAtlases.front(); } + GrCCAtlas& front() { SkASSERT(!this->empty()); return fAtlases.front(); } + GrCCAtlas& current() { SkASSERT(!this->empty()); return fAtlases.back(); } + + class Iter { + public: + Iter(GrCCAtlasStack& stack) : fImpl(&stack.fAtlases) {} + bool next() { return fImpl.next(); } + GrCCAtlas* operator->() const { return fImpl.get(); } + private: + typename GrTAllocator::Iter fImpl; + }; + + // Adds a rect to the current atlas and returns the offset from device space to atlas space. + // Call current() to get the atlas it was added to. + // + // If the return value is non-null, it means the given rect did not fit in the then-current + // atlas, so it was retired and a new one was added to the stack. The return value is the + // newly-retired atlas. The caller should call setUserBatchID() on the retired atlas before + // moving on. + GrCCAtlas* addRect(const SkIRect& devIBounds, SkIVector* devToAtlasOffset); + +private: + const CoverageType fCoverageType; + const GrCCAtlas::Specs fSpecs; + const GrCaps* const fCaps; + GrSTAllocator<4, GrCCAtlas> fAtlases; +}; + +inline void GrCCAtlas::Specs::accountForSpace(int width, int height) { + fMinWidth = SkTMax(width, fMinWidth); + fMinHeight = SkTMax(height, fMinHeight); + fApproxNumPixels += (width + kPadding) * (height + kPadding); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.cpp new file mode 100644 index 0000000000..9e86d2e2d2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCClipPath.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/ccpr/GrCCPerFlushResources.h" + +void GrCCClipPath::init( + const SkPath& deviceSpacePath, const SkIRect& accessRect, + GrCCAtlas::CoverageType atlasCoverageType, const GrCaps& caps) { + SkASSERT(!this->isInitialized()); + + fAtlasLazyProxy = GrCCAtlas::MakeLazyAtlasProxy( + [this](GrResourceProvider* resourceProvider, GrPixelConfig, + const GrBackendFormat& format, int sampleCount) { + SkASSERT(fHasAtlas); + SkASSERT(!fHasAtlasTransform); + + GrTextureProxy* textureProxy = fAtlas ? fAtlas->textureProxy() : nullptr; + + if (!textureProxy || !textureProxy->instantiate(resourceProvider)) { + fAtlasScale = fAtlasTranslate = {0, 0}; + SkDEBUGCODE(fHasAtlasTransform = true); + return sk_sp(); + } + + sk_sp texture = sk_ref_sp(textureProxy->peekTexture()); + SkASSERT(texture); + SkASSERT(texture->backendFormat() == format); + SkASSERT(texture->asRenderTarget()->numSamples() == sampleCount); + SkASSERT(textureProxy->origin() == kTopLeft_GrSurfaceOrigin); + + fAtlasScale = {1.f / texture->width(), 1.f / texture->height()}; + fAtlasTranslate.set(fDevToAtlasOffset.fX * fAtlasScale.x(), + fDevToAtlasOffset.fY * fAtlasScale.y()); + SkDEBUGCODE(fHasAtlasTransform = true); + + return texture; + }, + atlasCoverageType, caps, GrSurfaceProxy::UseAllocator::kYes); + + fDeviceSpacePath = deviceSpacePath; + fDeviceSpacePath.getBounds().roundOut(&fPathDevIBounds); + fAccessRect = accessRect; +} + +void GrCCClipPath::accountForOwnPath(GrCCPerFlushResourceSpecs* specs) const { + SkASSERT(this->isInitialized()); + + ++specs->fNumClipPaths; + specs->fRenderedPathStats[GrCCPerFlushResourceSpecs::kFillIdx].statPath(fDeviceSpacePath); + + SkIRect ibounds; + if (ibounds.intersect(fAccessRect, fPathDevIBounds)) { + specs->fRenderedAtlasSpecs.accountForSpace(ibounds.width(), ibounds.height()); + } +} + +void GrCCClipPath::renderPathInAtlas(GrCCPerFlushResources* resources, + GrOnFlushResourceProvider* onFlushRP) { + SkASSERT(this->isInitialized()); + SkASSERT(!fHasAtlas); + fAtlas = resources->renderDeviceSpacePathInAtlas( + fAccessRect, fDeviceSpacePath, fPathDevIBounds, GrFillRuleForSkPath(fDeviceSpacePath), + &fDevToAtlasOffset); + SkDEBUGCODE(fHasAtlas = true); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.h b/gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.h new file mode 100644 index 0000000000..32e5ae49f7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCClipPath.h @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCClipPath_DEFINED +#define GrCCClipPath_DEFINED + +#include "include/core/SkPath.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/ccpr/GrCCAtlas.h" + +struct GrCCPerFlushResourceSpecs; +class GrCCPerFlushResources; +class GrOnFlushResourceProvider; +class GrProxyProvider; + +/** + * These are keyed by SkPath generation ID, and store which device-space paths are accessed and + * where by clip FPs in a given opsTask. A single GrCCClipPath can be referenced by multiple FPs. At + * flush time their coverage count masks are packed into atlas(es) alongside normal DrawPathOps. + */ +class GrCCClipPath { +public: + GrCCClipPath() = default; + GrCCClipPath(const GrCCClipPath&) = delete; + + ~GrCCClipPath() { + // Ensure no clip FP exists with a dangling pointer back into this class. This works because + // a clip FP will have a ref on the proxy if it exists. + // + // This assert also guarantees there won't be a lazy proxy callback with a dangling pointer + // back into this class, since no proxy will exist after we destruct, if the assert passes. + SkASSERT(!fAtlasLazyProxy || fAtlasLazyProxy->unique()); + } + + bool isInitialized() const { return fAtlasLazyProxy != nullptr; } + void init(const SkPath& deviceSpacePath, const SkIRect& accessRect, + GrCCAtlas::CoverageType atlasCoverageType, const GrCaps&); + + void addAccess(const SkIRect& accessRect) { + SkASSERT(this->isInitialized()); + fAccessRect.join(accessRect); + } + GrTextureProxy* atlasLazyProxy() const { + SkASSERT(this->isInitialized()); + return fAtlasLazyProxy.get(); + } + const SkPath& deviceSpacePath() const { + SkASSERT(this->isInitialized()); + return fDeviceSpacePath; + } + const SkIRect& pathDevIBounds() const { + SkASSERT(this->isInitialized()); + return fPathDevIBounds; + } + + void accountForOwnPath(GrCCPerFlushResourceSpecs*) const; + void renderPathInAtlas(GrCCPerFlushResources*, GrOnFlushResourceProvider*); + + const SkVector& atlasScale() const { SkASSERT(fHasAtlasTransform); return fAtlasScale; } + const SkVector& atlasTranslate() const { SkASSERT(fHasAtlasTransform); return fAtlasTranslate; } + +private: + sk_sp fAtlasLazyProxy; + SkPath fDeviceSpacePath; + SkIRect fPathDevIBounds; + SkIRect fAccessRect; + + const GrCCAtlas* fAtlas = nullptr; + SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas. + SkDEBUGCODE(bool fHasAtlas = false;) + + SkVector fAtlasScale; + SkVector fAtlasTranslate; + SkDEBUGCODE(bool fHasAtlasTransform = false;) +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.cpp new file mode 100644 index 0000000000..75535dd58a --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCClipProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/core/SkMakeUnique.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/ccpr/GrCCClipPath.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +GrCCClipProcessor::GrCCClipProcessor(const GrCCClipPath* clipPath, IsCoverageCount isCoverageCount, + MustCheckBounds mustCheckBounds) + : INHERITED(kGrCCClipProcessor_ClassID, kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fClipPath(clipPath) + , fIsCoverageCount(IsCoverageCount::kYes == isCoverageCount) + , fMustCheckBounds(MustCheckBounds::kYes == mustCheckBounds) + , fAtlasAccess(sk_ref_sp(fClipPath->atlasLazyProxy()), GrSamplerState::ClampNearest()) { + SkASSERT(fAtlasAccess.proxy()); + this->setTextureSamplerCnt(1); +} + +std::unique_ptr GrCCClipProcessor::clone() const { + return skstd::make_unique( + fClipPath, IsCoverageCount(fIsCoverageCount), MustCheckBounds(fMustCheckBounds)); +} + +void GrCCClipProcessor::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const { + const SkPath& clipPath = fClipPath->deviceSpacePath(); + uint32_t key = (fIsCoverageCount) ? (uint32_t)GrFillRuleForSkPath(clipPath) : 0; + key = (key << 1) | ((clipPath.isInverseFillType()) ? 1 : 0); + key = (key << 1) | ((fMustCheckBounds) ? 1 : 0); + b->add32(key); +} + +bool GrCCClipProcessor::onIsEqual(const GrFragmentProcessor& fp) const { + const GrCCClipProcessor& that = fp.cast(); + // Each ClipPath path has a unique atlas proxy, so hasSameSamplersAndAccesses should have + // already weeded out FPs with different ClipPaths. + SkASSERT(that.fClipPath->deviceSpacePath().getGenerationID() == + fClipPath->deviceSpacePath().getGenerationID()); + return that.fClipPath->deviceSpacePath().getFillType() == + fClipPath->deviceSpacePath().getFillType() && + that.fIsCoverageCount == fIsCoverageCount && that.fMustCheckBounds == fMustCheckBounds; +} + +class GrCCClipProcessor::Impl : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs& args) override { + const GrCCClipProcessor& proc = args.fFp.cast(); + GrGLSLUniformHandler* uniHandler = args.fUniformHandler; + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + + f->codeAppend ("half coverage;"); + + if (proc.fMustCheckBounds) { + const char* pathIBounds; + fPathIBoundsUniform = uniHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "path_ibounds", &pathIBounds); + f->codeAppendf("if (all(greaterThan(float4(sk_FragCoord.xy, %s.zw), " + "float4(%s.xy, sk_FragCoord.xy)))) {", + pathIBounds, pathIBounds); + } + + const char* atlasTransform; + fAtlasTransformUniform = uniHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "atlas_transform", &atlasTransform); + f->codeAppendf("float2 texcoord = sk_FragCoord.xy * %s.xy + %s.zw;", + atlasTransform, atlasTransform); + + f->codeAppend ("coverage = "); + f->appendTextureLookup(args.fTexSamplers[0], "texcoord", kHalf2_GrSLType); + f->codeAppend (".a;"); + + if (proc.fIsCoverageCount) { + auto fillRule = GrFillRuleForSkPath(proc.fClipPath->deviceSpacePath()); + if (GrFillRule::kEvenOdd == fillRule) { + f->codeAppend ("half t = mod(abs(coverage), 2);"); + f->codeAppend ("coverage = 1 - abs(t - 1);"); + } else { + SkASSERT(GrFillRule::kNonzero == fillRule); + f->codeAppend ("coverage = min(abs(coverage), 1);"); + } + } + + if (proc.fMustCheckBounds) { + f->codeAppend ("} else {"); + f->codeAppend ( "coverage = 0;"); + f->codeAppend ("}"); + } + + if (proc.fClipPath->deviceSpacePath().isInverseFillType()) { + f->codeAppend ("coverage = 1 - coverage;"); + } + + f->codeAppendf("%s = %s * coverage;", args.fOutputColor, args.fInputColor); + } + + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& fp) override { + const GrCCClipProcessor& proc = fp.cast(); + if (proc.fMustCheckBounds) { + const SkRect pathIBounds = SkRect::Make(proc.fClipPath->pathDevIBounds()); + pdman.set4f(fPathIBoundsUniform, pathIBounds.left(), pathIBounds.top(), + pathIBounds.right(), pathIBounds.bottom()); + } + const SkVector& scale = proc.fClipPath->atlasScale(); + const SkVector& trans = proc.fClipPath->atlasTranslate(); + pdman.set4f(fAtlasTransformUniform, scale.x(), scale.y(), trans.x(), trans.y()); + } + +private: + UniformHandle fPathIBoundsUniform; + UniformHandle fAtlasTransformUniform; +}; + +GrGLSLFragmentProcessor* GrCCClipProcessor::onCreateGLSLInstance() const { + return new Impl(); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.h b/gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.h new file mode 100644 index 0000000000..0a719f80e9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCClipProcessor.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCClipProcessor_DEFINED +#define GrCCClipProcessor_DEFINED + +#include "src/gpu/GrFragmentProcessor.h" + +class GrCCClipPath; + +class GrCCClipProcessor : public GrFragmentProcessor { +public: + enum class IsCoverageCount : bool { + kNo = false, + kYes = true + }; + + enum class MustCheckBounds : bool { + kNo = false, + kYes = true + }; + + GrCCClipProcessor(const GrCCClipPath*, IsCoverageCount, MustCheckBounds); + + const char* name() const override { return "GrCCClipProcessor"; } + std::unique_ptr clone() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + const TextureSampler& onTextureSampler(int) const override { return fAtlasAccess; } + +private: + const GrCCClipPath* const fClipPath; + const bool fIsCoverageCount; + const bool fMustCheckBounds; + const TextureSampler fAtlasAccess; + + class Impl; + + typedef GrFragmentProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.cpp new file mode 100644 index 0000000000..82a15609e0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCConicShader.h" + +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +void GrCCConicShader::emitSetupCode( + GrGLSLVertexGeoBuilder* s, const char* pts, const char** outHull4) const { + // K is distance from the line P2 -> P0. L is distance from the line P0 -> P1, scaled by 2w. + // M is distance from the line P1 -> P2, scaled by 2w. We do this in a space where P1=0. + s->declareGlobal(fKLMMatrix); + s->codeAppendf("float x0 = %s[0].x - %s[1].x, x2 = %s[2].x - %s[1].x;", pts, pts, pts, pts); + s->codeAppendf("float y0 = %s[0].y - %s[1].y, y2 = %s[2].y - %s[1].y;", pts, pts, pts, pts); + s->codeAppendf("float w = %s[3].x;", pts); + s->codeAppendf("%s = float3x3(y2 - y0, x0 - x2, x2*y0 - x0*y2, " + "2*w * float2(+y0, -x0), 0, " + "2*w * float2(-y2, +x2), 0);", fKLMMatrix.c_str()); + + s->declareGlobal(fControlPoint); + s->codeAppendf("%s = %s[1];", fControlPoint.c_str(), pts); + + // Scale KLM by the inverse Manhattan width of K, and make sure K is positive. This allows K to + // double as the flat opposite edge AA. kwidth will not be 0 because we cull degenerate conics + // on the CPU. + s->codeAppendf("float kwidth = 2*bloat * (abs(%s[0].x) + abs(%s[0].y)) * sign(%s[0].z);", + fKLMMatrix.c_str(), fKLMMatrix.c_str(), fKLMMatrix.c_str()); + s->codeAppendf("%s *= 1/kwidth;", fKLMMatrix.c_str()); + + if (outHull4) { + // Clip the conic triangle by the tangent line at maximum height. Conics have the nice + // property that maximum height always occurs at T=.5. This is a simple application for + // De Casteljau's algorithm. + s->codeAppendf("float2 p1w = %s[1]*w;", pts); + s->codeAppend ("float r = 1 / (1 + w);"); + s->codeAppend ("float2 conic_hull[4];"); + s->codeAppendf("conic_hull[0] = %s[0];", pts); + s->codeAppendf("conic_hull[1] = (%s[0] + p1w) * r;", pts); + s->codeAppendf("conic_hull[2] = (p1w + %s[2]) * r;", pts); + s->codeAppendf("conic_hull[3] = %s[2];", pts); + *outHull4 = "conic_hull"; + } +} + +void GrCCConicShader::onEmitVaryings( + GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code, + const char* position, const char* coverage, const char* cornerCoverage, const char* wind) { + code->appendf("float3 klm = float3(%s - %s, 1) * %s;", + position, fControlPoint.c_str(), fKLMMatrix.c_str()); + if (coverage) { + fKLM_fWind.reset(kFloat4_GrSLType, scope); + varyingHandler->addVarying("klm_and_wind", &fKLM_fWind); + code->appendf("%s.w = %s;", OutName(fKLM_fWind), wind); + } else { + fKLM_fWind.reset(kFloat3_GrSLType, scope); + varyingHandler->addVarying("klm", &fKLM_fWind); + } + code->appendf("%s.xyz = klm;", OutName(fKLM_fWind)); + + fGrad_fCorner.reset(cornerCoverage ? kFloat4_GrSLType : kFloat2_GrSLType, scope); + varyingHandler->addVarying((cornerCoverage) ? "grad_and_corner" : "grad", &fGrad_fCorner); + code->appendf("%s.xy = 2*bloat * (float3x2(%s) * float3(2*klm[0], -klm[2], -klm[1]));", + OutName(fGrad_fCorner), fKLMMatrix.c_str()); + + if (cornerCoverage) { + SkASSERT(coverage); + code->appendf("half hull_coverage;"); + this->calcHullCoverage(code, "klm", OutName(fGrad_fCorner), "hull_coverage"); + code->appendf("%s.zw = half2(hull_coverage, 1) * %s;", + OutName(fGrad_fCorner), cornerCoverage); + } +} + +void GrCCConicShader::emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const { + this->calcHullCoverage(&AccessCodeString(f), fKLM_fWind.fsIn(), fGrad_fCorner.fsIn(), + outputCoverage); + f->codeAppendf("%s *= half(%s.w);", outputCoverage, fKLM_fWind.fsIn()); // Wind. + + if (kFloat4_GrSLType == fGrad_fCorner.type()) { + f->codeAppendf("%s = fma(half(%s.z), half(%s.w), %s);", // Attenuated corner coverage. + outputCoverage, fGrad_fCorner.fsIn(), fGrad_fCorner.fsIn(), + outputCoverage); + } +} + +void GrCCConicShader::calcHullCoverage(SkString* code, const char* klm, const char* grad, + const char* outputCoverage) const { + code->appendf("float k = %s.x, l = %s.y, m = %s.z;", klm, klm, klm); + code->append ("float f = k*k - l*m;"); + code->appendf("float fwidth = abs(%s.x) + abs(%s.y);", grad, grad); + code->appendf("float curve_coverage = min(0.5 - f/fwidth, 1);"); + // K doubles as the flat opposite edge's AA. + code->append ("float edge_coverage = min(k - 0.5, 0);"); + // Total hull coverage. + code->appendf("%s = max(half(curve_coverage + edge_coverage), 0);", outputCoverage); +} + +void GrCCConicShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const { + f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;", + fKLM_fWind.fsIn(), fKLM_fWind.fsIn(), fKLM_fWind.fsIn()); + f->codeAppendf("float f = k*k - l*m;"); + f->codeAppendf("float2 grad = %s;", fGrad_fCorner.fsIn()); + f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.h b/gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.h new file mode 100644 index 0000000000..3645c7e5ee --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCConicShader.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCConicShader_DEFINED +#define GrCCConicShader_DEFINED + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +/** + * This class renders the coverage of closed conic curves using the techniques outlined in + * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and + * Jim Blinn: + * + * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + * + * The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0]. + * (Use GrCCGeometry::conicTo().) + */ +class GrCCConicShader : public GrCCCoverageProcessor::Shader { +public: + bool calculatesOwnEdgeCoverage() const override { return true; } + + void emitSetupCode( + GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override; + + void onEmitVaryings( + GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position, + const char* coverage, const char* cornerCoverage, const char* wind) override; + + void emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override; + + void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override; + +private: + void calcHullCoverage(SkString* code, const char* klm, const char* grad, + const char* outputCoverage) const; + + const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType}; + const GrShaderVar fControlPoint{"control_point", kFloat2_GrSLType}; + GrGLSLVarying fKLM_fWind; + GrGLSLVarying fGrad_fCorner; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.cpp new file mode 100644 index 0000000000..6abb04e859 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.cpp @@ -0,0 +1,215 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +#include "src/core/SkMakeUnique.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/ccpr/GrCCConicShader.h" +#include "src/gpu/ccpr/GrCCCubicShader.h" +#include "src/gpu/ccpr/GrCCQuadraticShader.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +class GrCCCoverageProcessor::TriangleShader : public GrCCCoverageProcessor::Shader { + void onEmitVaryings( + GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code, + const char* position, const char* coverage, const char* cornerCoverage, + const char* /*wind*/) override { + if (!cornerCoverage) { + fCoverages.reset(kHalf_GrSLType, scope); + varyingHandler->addVarying("coverage", &fCoverages); + code->appendf("%s = %s;", OutName(fCoverages), coverage); + } else { + fCoverages.reset(kHalf3_GrSLType, scope); + varyingHandler->addVarying("coverages", &fCoverages); + code->appendf("%s = half3(%s, %s);", OutName(fCoverages), coverage, cornerCoverage); + } + } + + void emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const override { + if (kHalf_GrSLType == fCoverages.type()) { + f->codeAppendf("%s = %s;", outputCoverage, fCoverages.fsIn()); + } else { + f->codeAppendf("%s = %s.z * %s.y + %s.x;", + outputCoverage, fCoverages.fsIn(), fCoverages.fsIn(), fCoverages.fsIn()); + } + } + + void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override { return; } + + GrGLSLVarying fCoverages; +}; + +void GrCCCoverageProcessor::Shader::CalcWind(const GrCCCoverageProcessor& proc, + GrGLSLVertexGeoBuilder* s, const char* pts, + const char* outputWind) { + if (3 == proc.numInputPoints()) { + s->codeAppendf("float2 a = %s[0] - %s[1], " + "b = %s[0] - %s[2];", pts, pts, pts, pts); + } else { + // All inputs are convex, so it's sufficient to just average the middle two input points. + SkASSERT(4 == proc.numInputPoints()); + s->codeAppendf("float2 p12 = (%s[1] + %s[2]) * .5;", pts, pts); + s->codeAppendf("float2 a = %s[0] - p12, " + "b = %s[0] - %s[3];", pts, pts, pts); + } + + s->codeAppend ("float area_x2 = determinant(float2x2(a, b));"); + if (proc.isTriangles()) { + // We cull extremely thin triangles by zeroing wind. When a triangle gets too thin it's + // possible for FP round-off error to actually give us the wrong winding direction, causing + // rendering artifacts. The criteria we choose is "height <~ 1/1024". So we drop a triangle + // if the max effect it can have on any single pixel is <~ 1/1024, or 1/4 of a bit in 8888. + s->codeAppend ("float2 bbox_size = max(abs(a), abs(b));"); + s->codeAppend ("float basewidth = max(bbox_size.x + bbox_size.y, 1);"); + s->codeAppendf("%s = (abs(area_x2 * 1024) > basewidth) ? sign(half(area_x2)) : 0;", + outputWind); + } else { + // We already converted nearly-flat curves to lines on the CPU, so no need to worry about + // thin curve hulls at this point. + s->codeAppendf("%s = sign(half(area_x2));", outputWind); + } +} + +void GrCCCoverageProcessor::Shader::CalcEdgeCoverageAtBloatVertex(GrGLSLVertexGeoBuilder* s, + const char* leftPt, + const char* rightPt, + const char* rasterVertexDir, + const char* outputCoverage) { + // Here we find an edge's coverage at one corner of a conservative raster bloat box whose center + // falls on the edge in question. (A bloat box is axis-aligned and the size of one pixel.) We + // always set up coverage so it is -1 at the outermost corner, 0 at the innermost, and -.5 at + // the center. Interpolated, these coverage values convert jagged conservative raster edges into + // smooth antialiased edges. + // + // d1 == (P + sign(n) * bloat) dot n (Distance at the bloat box vertex whose + // == P dot n + (abs(n.x) + abs(n.y)) * bloatSize coverage=-1, where the bloat box is + // centered on P.) + // + // d0 == (P - sign(n) * bloat) dot n (Distance at the bloat box vertex whose + // == P dot n - (abs(n.x) + abs(n.y)) * bloatSize coverage=0, where the bloat box is + // centered on P.) + // + // d == (P + rasterVertexDir * bloatSize) dot n (Distance at the bloat box vertex whose + // == P dot n + (rasterVertexDir dot n) * bloatSize coverage we wish to calculate.) + // + // coverage == -(d - d0) / (d1 - d0) (coverage=-1 at d=d1; coverage=0 at d=d0) + // + // == (rasterVertexDir dot n) / (abs(n.x) + abs(n.y)) * -.5 - .5 + // + s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);", + rightPt, leftPt, leftPt, rightPt); + s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);"); + s->codeAppendf("float t = dot(%s, n);", rasterVertexDir); + // The below conditional guarantees we get exactly 1 on the divide when nwidth=t (in case the + // GPU divides by multiplying by the reciprocal?) It also guards against NaN when nwidth=0. + s->codeAppendf("%s = half(abs(t) != nwidth ? t / nwidth : sign(t)) * -.5 - .5;", + outputCoverage); +} + +void GrCCCoverageProcessor::Shader::CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder* s, + const char* leftPt, + const char* rightPt, + const char* bloatDir1, + const char* bloatDir2, + const char* outputCoverages) { + // See comments in CalcEdgeCoverageAtBloatVertex. + s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);", + rightPt, leftPt, leftPt, rightPt); + s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);"); + s->codeAppendf("float2 t = n * float2x2(%s, %s);", bloatDir1, bloatDir2); + s->codeAppendf("for (int i = 0; i < 2; ++i) {"); + s->codeAppendf( "%s[i] = half(abs(t[i]) != nwidth ? t[i] / nwidth : sign(t[i])) * -.5 - .5;", + outputCoverages); + s->codeAppendf("}"); +} + +void GrCCCoverageProcessor::Shader::CalcCornerAttenuation(GrGLSLVertexGeoBuilder* s, + const char* leftDir, const char* rightDir, + const char* outputAttenuation) { + // obtuseness = cos(corner_angle) if corner_angle > 90 degrees + // 0 if corner_angle <= 90 degrees + // + // NOTE: leftDir and rightDir are normalized and point in the same direction the path was + // defined with, i.e., leftDir points into the corner and rightDir points away from the corner. + s->codeAppendf("half obtuseness = max(half(dot(%s, %s)), 0);", leftDir, rightDir); + + // axis_alignedness = 1 - tan(angle_to_nearest_axis_from_corner_bisector) + // (i.e., 1 when the corner bisector is aligned with the x- or y-axis + // 0 when the corner bisector falls on a 45 degree angle + // 0..1 when the corner bisector falls somewhere in between + s->codeAppendf("half2 abs_bisect_maybe_transpose = abs((0 == obtuseness) ? half2(%s - %s) : " + "half2(%s + %s));", + leftDir, rightDir, leftDir, rightDir); + s->codeAppend ("half axis_alignedness = " + "1 - min(abs_bisect_maybe_transpose.y, abs_bisect_maybe_transpose.x) / " + "max(abs_bisect_maybe_transpose.x, abs_bisect_maybe_transpose.y);"); + + // ninety_degreesness = sin^2(corner_angle) + // sin^2 just because... it's always positive and the results looked better than plain sine... ? + s->codeAppendf("half ninety_degreesness = determinant(half2x2(%s, %s));", leftDir, rightDir); + s->codeAppend ("ninety_degreesness = ninety_degreesness * ninety_degreesness;"); + + // The below formula is not smart. It was just arrived at by considering the following + // observations: + // + // 1. 90-degree, axis-aligned corners have full attenuation along the bisector. + // (i.e. coverage = 1 - distance_to_corner^2) + // (i.e. outputAttenuation = 0) + // + // 2. 180-degree corners always have zero attenuation. + // (i.e. coverage = 1 - distance_to_corner) + // (i.e. outputAttenuation = 1) + // + // 3. 90-degree corners whose bisector falls on a 45 degree angle also do not attenuate. + // (i.e. outputAttenuation = 1) + s->codeAppendf("%s = max(obtuseness, axis_alignedness * ninety_degreesness);", + outputAttenuation); +} + +GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const { + std::unique_ptr shader; + switch (fPrimitiveType) { + case PrimitiveType::kTriangles: + case PrimitiveType::kWeightedTriangles: + shader = skstd::make_unique(); + break; + case PrimitiveType::kQuadratics: + shader = skstd::make_unique(); + break; + case PrimitiveType::kCubics: + shader = skstd::make_unique(); + break; + case PrimitiveType::kConics: + shader = skstd::make_unique(); + break; + } + return this->onCreateGLSLInstance(std::move(shader)); +} + +void GrCCCoverageProcessor::draw( + GrOpFlushState* flushState, const GrPipeline& pipeline, const SkIRect scissorRects[], + const GrMesh meshes[], int meshCount, const SkRect& drawBounds) const { + GrPipeline::DynamicStateArrays dynamicStateArrays; + dynamicStateArrays.fScissorRects = scissorRects; + GrOpsRenderPass* renderPass = flushState->opsRenderPass(); + + GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(), + flushState->drawOpArgs().origin(), + pipeline, + *this, + nullptr, + &dynamicStateArrays, 0); + + + renderPass->draw(programInfo, meshes, meshCount, drawBounds); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.h b/gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.h new file mode 100644 index 0000000000..5fdb488ea5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCCoverageProcessor.h @@ -0,0 +1,298 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCCoverageProcessor_DEFINED +#define GrCCCoverageProcessor_DEFINED + +#include "include/private/SkNx.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +class GrGLSLFPFragmentBuilder; +class GrGLSLVertexGeoBuilder; +class GrMesh; +class GrOpFlushState; + +/** + * This is the geometry processor for the simple convex primitive shapes (triangles and closed, + * convex bezier curves) from which ccpr paths are composed. The output is a single-channel alpha + * value, positive for clockwise shapes and negative for counter-clockwise, that indicates coverage. + * + * The caller is responsible to draw all primitives as produced by GrCCGeometry into a cleared, + * floating point, alpha-only render target using SkBlendMode::kPlus. Once all of a path's + * primitives have been drawn, the render target contains a composite coverage count that can then + * be used to draw the path (see GrCCPathProcessor). + * + * To draw primitives, use appendMesh() and draw() (defined below). + */ +class GrCCCoverageProcessor : public GrGeometryProcessor { +public: + enum class PrimitiveType { + kTriangles, + kWeightedTriangles, // Triangles (from the tessellator) whose winding magnitude > 1. + kQuadratics, + kCubics, + kConics + }; + static const char* PrimitiveTypeName(PrimitiveType); + + // Defines a single primitive shape with 3 input points (i.e. Triangles and Quadratics). + // X,Y point values are transposed. + struct TriPointInstance { + float fValues[6]; + + enum class Ordering : bool { + kXYTransposed, + kXYInterleaved, + }; + + void set(const SkPoint[3], const Sk2f& translate, Ordering); + void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& translate, Ordering); + void set(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& translate, Ordering); + }; + + // Defines a single primitive shape with 4 input points, or 3 input points plus a "weight" + // parameter duplicated in both lanes of the 4th input (i.e. Cubics, Conics, and Triangles with + // a weighted winding number). X,Y point values are transposed. + struct QuadPointInstance { + float fX[4]; + float fY[4]; + + void set(const SkPoint[4], float dx, float dy); + void setW(const SkPoint[3], const Sk2f& trans, float w); + void setW(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w); + void setW(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans, float w); + }; + + virtual void reset(PrimitiveType, GrResourceProvider*) = 0; + + PrimitiveType primitiveType() const { return fPrimitiveType; } + + // Number of bezier points for curves, or 3 for triangles. + int numInputPoints() const { return PrimitiveType::kCubics == fPrimitiveType ? 4 : 3; } + + bool isTriangles() const { + return PrimitiveType::kTriangles == fPrimitiveType || + PrimitiveType::kWeightedTriangles == fPrimitiveType; + } + + int hasInputWeight() const { + return PrimitiveType::kWeightedTriangles == fPrimitiveType || + PrimitiveType::kConics == fPrimitiveType; + } + + // GrPrimitiveProcessor overrides. + const char* name() const override { return PrimitiveTypeName(fPrimitiveType); } +#ifdef SK_DEBUG + SkString dumpInfo() const override { + return SkStringPrintf("%s\n%s", this->name(), this->INHERITED::dumpInfo().c_str()); + } +#endif + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { + SkDEBUGCODE(this->getDebugBloatKey(b)); + b->add32((int)fPrimitiveType); + } + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final; + +#ifdef SK_DEBUG + // Increases the 1/2 pixel AA bloat by a factor of debugBloat. + void enableDebugBloat(float debugBloat) { fDebugBloat = debugBloat; } + bool debugBloatEnabled() const { return fDebugBloat > 0; } + float debugBloat() const { SkASSERT(this->debugBloatEnabled()); return fDebugBloat; } + void getDebugBloatKey(GrProcessorKeyBuilder* b) const { + uint32_t bloatBits; + memcpy(&bloatBits, &fDebugBloat, 4); + b->add32(bloatBits); + } +#endif + + // Appends a GrMesh that will draw the provided instances. The instanceBuffer must be an array + // of either TriPointInstance or QuadPointInstance, depending on this processor's RendererPass, + // with coordinates in the desired shape's final atlas-space position. + virtual void appendMesh(sk_sp instanceBuffer, int instanceCount, + int baseInstance, SkTArray* out) const = 0; + + virtual void draw(GrOpFlushState*, const GrPipeline&, const SkIRect scissorRects[], + const GrMesh[], int meshCount, const SkRect& drawBounds) const; + + // The Shader provides code to calculate each pixel's coverage in a RenderPass. It also + // provides details about shape-specific geometry. + class Shader { + public: + // Returns true if the Impl should not calculate the coverage argument for emitVaryings(). + // If true, then "coverage" will have a signed magnitude of 1. + virtual bool calculatesOwnEdgeCoverage() const { return false; } + + // Called before generating geometry. Subclasses may set up internal member variables during + // this time that will be needed during onEmitVaryings (e.g. transformation matrices). + // + // If the 'outHull4' parameter is provided, and there are not 4 input points, the subclass + // is required to fill it with the name of a 4-point hull around which the Impl can generate + // its geometry. If it is left unchanged, the Impl will use the regular input points. + virtual void emitSetupCode( + GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4 = nullptr) const { + SkASSERT(!outHull4); + } + + void emitVaryings( + GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code, + const char* position, const char* coverage, const char* cornerCoverage, + const char* wind) { + SkASSERT(GrGLSLVarying::Scope::kVertToGeo != scope); + this->onEmitVaryings( + varyingHandler, scope, code, position, coverage, cornerCoverage, wind); + } + + // Writes the signed coverage value at the current pixel to "outputCoverage". + virtual void emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder*, const char* outputCoverage) const = 0; + + // Assigns the built-in sample mask at the current pixel. + virtual void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const = 0; + + // Calculates the winding direction of the input points (+1, -1, or 0). Wind for extremely + // thin triangles gets rounded to zero. + static void CalcWind(const GrCCCoverageProcessor&, GrGLSLVertexGeoBuilder*, const char* pts, + const char* outputWind); + + // Calculates an edge's coverage at a conservative raster vertex. The edge is defined by two + // clockwise-ordered points, 'leftPt' and 'rightPt'. 'rasterVertexDir' is a pair of +/-1 + // values that point in the direction of conservative raster bloat, starting from an + // endpoint. + // + // Coverage values ramp from -1 (completely outside the edge) to 0 (completely inside). + static void CalcEdgeCoverageAtBloatVertex(GrGLSLVertexGeoBuilder*, const char* leftPt, + const char* rightPt, const char* rasterVertexDir, + const char* outputCoverage); + + // Calculates an edge's coverage at two conservative raster vertices. + // (See CalcEdgeCoverageAtBloatVertex). + static void CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder*, const char* leftPt, + const char* rightPt, const char* bloatDir1, + const char* bloatDir2, + const char* outputCoverages); + + // Corner boxes require an additional "attenuation" varying that is multiplied by the + // regular (linearly-interpolated) coverage. This function calculates the attenuation value + // to use in the single, outermost vertex. The remaining three vertices of the corner box + // all use an attenuation value of 1. + static void CalcCornerAttenuation(GrGLSLVertexGeoBuilder*, const char* leftDir, + const char* rightDir, const char* outputAttenuation); + + virtual ~Shader() {} + + protected: + // Here the subclass adds its internal varyings to the handler and produces code to + // initialize those varyings from a given position and coverage values. + // + // NOTE: the coverage values are signed appropriately for wind. + // 'coverage' will only be +1 or -1 on curves. + virtual void onEmitVaryings( + GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position, + const char* coverage, const char* cornerCoverage, const char* wind) = 0; + + // Returns the name of a Shader's internal varying at the point where where its value is + // assigned. This is intended to work whether called for a vertex or a geometry shader. + const char* OutName(const GrGLSLVarying& varying) const { + using Scope = GrGLSLVarying::Scope; + SkASSERT(Scope::kVertToGeo != varying.scope()); + return Scope::kGeoToFrag == varying.scope() ? varying.gsOut() : varying.vsOut(); + } + + // Our friendship with GrGLSLShaderBuilder does not propagate to subclasses. + inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); } + }; + +protected: + // Slightly undershoot a bloat radius of 0.5 so vertices that fall on integer boundaries don't + // accidentally bleed into neighbor pixels. + static constexpr float kAABloatRadius = 0.491111f; + + GrCCCoverageProcessor(ClassID classID) : INHERITED(classID) {} + + virtual GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr) const = 0; + + // Our friendship with GrGLSLShaderBuilder does not propagate to subclasses. + inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); } + + PrimitiveType fPrimitiveType; + SkDEBUGCODE(float fDebugBloat = 0); + + class TriangleShader; + + typedef GrGeometryProcessor INHERITED; +}; + +inline const char* GrCCCoverageProcessor::PrimitiveTypeName(PrimitiveType type) { + switch (type) { + case PrimitiveType::kTriangles: return "kTriangles"; + case PrimitiveType::kWeightedTriangles: return "kWeightedTriangles"; + case PrimitiveType::kQuadratics: return "kQuadratics"; + case PrimitiveType::kCubics: return "kCubics"; + case PrimitiveType::kConics: return "kConics"; + } + SK_ABORT("Invalid PrimitiveType"); +} + +inline void GrCCCoverageProcessor::TriPointInstance::set( + const SkPoint p[3], const Sk2f& translate, Ordering ordering) { + this->set(p[0], p[1], p[2], translate, ordering); +} + +inline void GrCCCoverageProcessor::TriPointInstance::set( + const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, const Sk2f& translate, + Ordering ordering) { + Sk2f P0 = Sk2f::Load(&p0); + Sk2f P1 = Sk2f::Load(&p1); + Sk2f P2 = Sk2f::Load(&p2); + this->set(P0, P1, P2, translate, ordering); +} + +inline void GrCCCoverageProcessor::TriPointInstance::set( + const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& translate, Ordering ordering) { + if (Ordering::kXYTransposed == ordering) { + Sk2f::Store3(fValues, P0 + translate, P1 + translate, P2 + translate); + } else { + (P0 + translate).store(fValues); + (P1 + translate).store(fValues + 2); + (P2 + translate).store(fValues + 4); + } +} + +inline void GrCCCoverageProcessor::QuadPointInstance::set(const SkPoint p[4], float dx, float dy) { + Sk4f X,Y; + Sk4f::Load2(p, &X, &Y); + (X + dx).store(&fX); + (Y + dy).store(&fY); +} + +inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint p[3], const Sk2f& trans, + float w) { + this->setW(p[0], p[1], p[2], trans, w); +} + +inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint& p0, const SkPoint& p1, + const SkPoint& p2, const Sk2f& trans, + float w) { + Sk2f P0 = Sk2f::Load(&p0); + Sk2f P1 = Sk2f::Load(&p1); + Sk2f P2 = Sk2f::Load(&p2); + this->setW(P0, P1, P2, trans, w); +} + +inline void GrCCCoverageProcessor::QuadPointInstance::setW(const Sk2f& P0, const Sk2f& P1, + const Sk2f& P2, const Sk2f& trans, + float w) { + Sk2f W = Sk2f(w); + Sk2f::Store4(this, P0 + trans, P1 + trans, P2 + trans, W); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.cpp new file mode 100644 index 0000000000..aff20194e5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.cpp @@ -0,0 +1,159 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCCubicShader.h" + +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +using Shader = GrCCCoverageProcessor::Shader; + +void GrCCCubicShader::emitSetupCode( + GrGLSLVertexGeoBuilder* s, const char* pts, const char** /*outHull4*/) const { + // Find the cubic's power basis coefficients. + s->codeAppendf("float2x4 C = float4x4(-1, 3, -3, 1, " + " 3, -6, 3, 0, " + "-3, 3, 0, 0, " + " 1, 0, 0, 0) * transpose(%s);", pts); + + // Find the cubic's inflection function. + s->codeAppend ("float D3 = +determinant(float2x2(C[0].yz, C[1].yz));"); + s->codeAppend ("float D2 = -determinant(float2x2(C[0].xz, C[1].xz));"); + s->codeAppend ("float D1 = +determinant(float2x2(C));"); + + // Shift the exponents in D so the largest magnitude falls somewhere in 1..2. This protects us + // from overflow while solving for roots and KLM functionals. + s->codeAppend ("float Dmax = max(max(abs(D1), abs(D2)), abs(D3));"); + s->codeAppend ("float norm;"); + if (s->getProgramBuilder()->shaderCaps()->fpManipulationSupport()) { + s->codeAppend ("int exp;"); + s->codeAppend ("frexp(Dmax, exp);"); + s->codeAppend ("norm = ldexp(1, 1 - exp);"); + } else { + s->codeAppend ("norm = 1/Dmax;"); // Dmax will not be 0 because we cull line cubics on CPU. + } + s->codeAppend ("D3 *= norm;"); + s->codeAppend ("D2 *= norm;"); + s->codeAppend ("D1 *= norm;"); + + // Calculate the KLM matrix. + s->declareGlobal(fKLMMatrix); + s->codeAppend ("float discr = 3*D2*D2 - 4*D1*D3;"); + s->codeAppend ("float x = discr >= 0 ? 3 : 1;"); + s->codeAppend ("float q = sqrt(x * abs(discr));"); + s->codeAppend ("q = x*D2 + (D2 >= 0 ? q : -q);"); + + s->codeAppend ("float2 l, m;"); + s->codeAppend ("l.ts = float2(q, 2*x * D1);"); + s->codeAppend ("m.ts = float2(2, q) * (discr >= 0 ? float2(D3, 1) " + ": float2(D2*D2 - D3*D1, D1));"); + + s->codeAppend ("float4 K;"); + s->codeAppend ("float4 lm = l.sstt * m.stst;"); + s->codeAppend ("K = float4(0, lm.x, -lm.y - lm.z, lm.w);"); + + s->codeAppend ("float4 L, M;"); + s->codeAppend ("lm.yz += 2*lm.zy;"); + s->codeAppend ("L = float4(-1,x,-x,1) * l.sstt * (discr >= 0 ? l.ssst * l.sttt : lm);"); + s->codeAppend ("M = float4(-1,x,-x,1) * m.sstt * (discr >= 0 ? m.ssst * m.sttt : lm.xzyw);"); + + s->codeAppend ("int middlerow = abs(D2) > abs(D1) ? 2 : 1;"); + s->codeAppend ("float3x3 CI = inverse(float3x3(C[0][0], C[0][middlerow], C[0][3], " + "C[1][0], C[1][middlerow], C[1][3], " + " 0, 0, 1));"); + s->codeAppendf("%s = CI * float3x3(K[0], K[middlerow], K[3], " + "L[0], L[middlerow], L[3], " + "M[0], M[middlerow], M[3]);", fKLMMatrix.c_str()); + + // Evaluate the cubic at T=.5 for a mid-ish point. + s->codeAppendf("float2 midpoint = %s * float4(.125, .375, .375, .125);", pts); + + // Orient the KLM matrix so L & M are both positive on the side of the curve we wish to fill. + s->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));", + fKLMMatrix.c_str(), fKLMMatrix.c_str()); + s->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, " + "0, orientation[0], 0, " + "0, 0, orientation[1]);", fKLMMatrix.c_str()); +} + +void GrCCCubicShader::onEmitVaryings( + GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code, + const char* position, const char* coverage, const char* cornerCoverage, const char* wind) { + code->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str()); + if (coverage) { + fKLM_fEdge.reset(kFloat4_GrSLType, scope); + varyingHandler->addVarying("klm_and_edge", &fKLM_fEdge); + // Give L&M both the same sign as wind, in order to pass this value to the fragment shader. + // (Cubics are pre-chopped such that L&M do not change sign within any individual segment.) + code->appendf("%s.xyz = klm * float3(1, %s, %s);", OutName(fKLM_fEdge), wind, wind); + // Flat edge opposite the curve. + code->appendf("%s.w = %s;", OutName(fKLM_fEdge), coverage); + } else { + fKLM_fEdge.reset(kFloat3_GrSLType, scope); + varyingHandler->addVarying("klm", &fKLM_fEdge); + code->appendf("%s = klm;", OutName(fKLM_fEdge)); + } + + fGradMatrix.reset(kFloat4_GrSLType, scope); + varyingHandler->addVarying("grad_matrix", &fGradMatrix); + code->appendf("%s.xy = 2*bloat * 3 * klm[0] * %s[0].xy;", + OutName(fGradMatrix), fKLMMatrix.c_str()); + code->appendf("%s.zw = -2*bloat * (klm[1] * %s[2].xy + klm[2] * %s[1].xy);", + OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str()); + + if (cornerCoverage) { + SkASSERT(coverage); + code->appendf("half hull_coverage; {"); + this->calcHullCoverage(code, OutName(fKLM_fEdge), OutName(fGradMatrix), "hull_coverage"); + code->appendf("}"); + fCornerCoverage.reset(kHalf2_GrSLType, scope); + varyingHandler->addVarying("corner_coverage", &fCornerCoverage); + code->appendf("%s = half2(hull_coverage, 1) * %s;", + OutName(fCornerCoverage), cornerCoverage); + } +} + +void GrCCCubicShader::emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const { + this->calcHullCoverage( + &AccessCodeString(f), fKLM_fEdge.fsIn(), fGradMatrix.fsIn(), outputCoverage); + + // Wind is the sign of both L and/or M. Take the sign of whichever has the larger magnitude. + // (In reality, either would be fine because we chop cubics with more than a half pixel of + // padding around the L & M lines, so neither should approach zero.) + f->codeAppend ("half wind = sign(half(l + m));"); + f->codeAppendf("%s *= wind;", outputCoverage); + + if (fCornerCoverage.fsIn()) { + f->codeAppendf("%s = %s.x * %s.y + %s;", // Attenuated corner coverage. + outputCoverage, fCornerCoverage.fsIn(), fCornerCoverage.fsIn(), + outputCoverage); + } +} + +void GrCCCubicShader::calcHullCoverage(SkString* code, const char* klmAndEdge, + const char* gradMatrix, const char* outputCoverage) const { + code->appendf("float k = %s.x, l = %s.y, m = %s.z;", klmAndEdge, klmAndEdge, klmAndEdge); + code->append ("float f = k*k*k - l*m;"); + code->appendf("float2 grad = %s.xy * k + %s.zw;", gradMatrix, gradMatrix); + code->append ("float fwidth = abs(grad.x) + abs(grad.y);"); + code->appendf("float curve_coverage = min(0.5 - f/fwidth, 1);"); + // Flat edge opposite the curve. + code->appendf("float edge_coverage = min(%s.w, 0);", klmAndEdge); + // Total hull coverage. + code->appendf("%s = max(half(curve_coverage + edge_coverage), 0);", outputCoverage); +} + +void GrCCCubicShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const { + f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;", + fKLM_fEdge.fsIn(), fKLM_fEdge.fsIn(), fKLM_fEdge.fsIn()); + f->codeAppendf("float f = k*k*k - l*m;"); + f->codeAppendf("float2x2 grad_matrix = float2x2(%s);", fGradMatrix.fsIn()); + f->codeAppendf("float2 grad = grad_matrix * float2(k, 1);"); + f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.h b/gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.h new file mode 100644 index 0000000000..723c21ea16 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCCubicShader.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCCubicShader_DEFINED +#define GrCCCubicShader_DEFINED + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +/** + * This class renders the coverage of convex closed cubic segments using the techniques outlined in + * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and + * Jim Blinn: + * + * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + * + * The provided curve segments must be convex, monotonic with respect to the vector of their closing + * edge [P3 - P0], and must not contain or be near any inflection points or loop intersections. + * (Use GrCCGeometry::cubicTo().) + */ +class GrCCCubicShader : public GrCCCoverageProcessor::Shader { +public: + void emitSetupCode( + GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override; + + void onEmitVaryings( + GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position, + const char* coverage, const char* cornerCoverage, const char* wind) override; + + void emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override; + + void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override; + +private: + void calcHullCoverage(SkString* code, const char* klmAndEdge, const char* gradMatrix, + const char* outputCoverage) const; + + const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType}; + GrGLSLVarying fKLM_fEdge; + GrGLSLVarying fGradMatrix; + GrGLSLVarying fCornerCoverage; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp new file mode 100644 index 0000000000..06556c493d --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp @@ -0,0 +1,461 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCDrawPathsOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/ccpr/GrCCPathCache.h" +#include "src/gpu/ccpr/GrCCPerFlushResources.h" +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" +#include "src/gpu/ccpr/GrOctoBounds.h" + +static bool has_coord_transforms(const GrPaint& paint) { + GrFragmentProcessor::Iter iter(paint); + while (const GrFragmentProcessor* fp = iter.next()) { + if (!fp->coordTransforms().empty()) { + return true; + } + } + return false; +} + +std::unique_ptr GrCCDrawPathsOp::Make( + GrRecordingContext* context, const SkIRect& clipIBounds, const SkMatrix& m, + const GrShape& shape, GrPaint&& paint) { + SkRect conservativeDevBounds; + m.mapRect(&conservativeDevBounds, shape.bounds()); + + const SkStrokeRec& stroke = shape.style().strokeRec(); + float strokeDevWidth = 0; + float conservativeInflationRadius = 0; + if (!stroke.isFillStyle()) { + strokeDevWidth = GrCoverageCountingPathRenderer::GetStrokeDevWidth( + m, stroke, &conservativeInflationRadius); + conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius); + } + + std::unique_ptr op; + float conservativeSize = SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width()); + if (conservativeSize > GrCoverageCountingPathRenderer::kPathCropThreshold) { + // The path is too large. Crop it or analytic AA can run out of fp32 precision. + SkPath croppedDevPath; + shape.asPath(&croppedDevPath); + croppedDevPath.transform(m, &croppedDevPath); + + SkIRect cropBox = clipIBounds; + GrShape croppedDevShape; + if (stroke.isFillStyle()) { + GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath); + croppedDevShape = GrShape(croppedDevPath); + conservativeDevBounds = croppedDevShape.bounds(); + } else { + int r = SkScalarCeilToInt(conservativeInflationRadius); + cropBox.outset(r, r); + GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath); + SkStrokeRec devStroke = stroke; + devStroke.setStrokeStyle(strokeDevWidth); + croppedDevShape = GrShape(croppedDevPath, GrStyle(devStroke, nullptr)); + conservativeDevBounds = croppedDevPath.getBounds(); + conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius); + } + + // FIXME: This breaks local coords: http://skbug.com/8003 + return InternalMake(context, clipIBounds, SkMatrix::I(), croppedDevShape, strokeDevWidth, + conservativeDevBounds, std::move(paint)); + } + + return InternalMake(context, clipIBounds, m, shape, strokeDevWidth, conservativeDevBounds, + std::move(paint)); +} + +std::unique_ptr GrCCDrawPathsOp::InternalMake( + GrRecordingContext* context, const SkIRect& clipIBounds, const SkMatrix& m, + const GrShape& shape, float strokeDevWidth, const SkRect& conservativeDevBounds, + GrPaint&& paint) { + // The path itself should have been cropped if larger than kPathCropThreshold. If it had a + // stroke, that would have further inflated its draw bounds. + SkASSERT(SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width()) < + GrCoverageCountingPathRenderer::kPathCropThreshold + + GrCoverageCountingPathRenderer::kMaxBoundsInflationFromStroke*2 + 1); + + SkIRect shapeConservativeIBounds; + conservativeDevBounds.roundOut(&shapeConservativeIBounds); + + SkIRect maskDevIBounds; + if (!maskDevIBounds.intersect(clipIBounds, shapeConservativeIBounds)) { + return nullptr; + } + + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + return pool->allocate(m, shape, strokeDevWidth, shapeConservativeIBounds, + maskDevIBounds, conservativeDevBounds, std::move(paint)); +} + +GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape, float strokeDevWidth, + const SkIRect& shapeConservativeIBounds, + const SkIRect& maskDevIBounds, const SkRect& conservativeDevBounds, + GrPaint&& paint) + : GrDrawOp(ClassID()) + , fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I()) + , fDraws(m, shape, strokeDevWidth, shapeConservativeIBounds, maskDevIBounds, + paint.getColor4f()) + , fProcessors(std::move(paint)) { // Paint must be moved after fetching its color above. + SkDEBUGCODE(fBaseInstance = -1); + // If the path is clipped, CCPR will only draw the visible portion. This helps improve batching, + // since it eliminates the need for scissor when drawing to the main canvas. + // FIXME: We should parse the path right here. It will provide a tighter bounding box for us to + // give the opsTask, as well as enabling threaded parsing when using DDL. + SkRect clippedDrawBounds; + if (!clippedDrawBounds.intersect(conservativeDevBounds, SkRect::Make(maskDevIBounds))) { + clippedDrawBounds.setEmpty(); + } + // We always have AA bloat, even in MSAA atlas mode. This is because by the time this Op comes + // along and draws to the main canvas, the atlas has been resolved to analytic coverage. + this->setBounds(clippedDrawBounds, GrOp::HasAABloat::kYes, GrOp::IsHairline::kNo); +} + +GrCCDrawPathsOp::~GrCCDrawPathsOp() { + if (fOwningPerOpsTaskPaths) { + // Remove the list's dangling pointer to this Op before deleting it. + fOwningPerOpsTaskPaths->fDrawOps.remove(this); + } +} + +GrCCDrawPathsOp::SingleDraw::SingleDraw(const SkMatrix& m, const GrShape& shape, + float strokeDevWidth, + const SkIRect& shapeConservativeIBounds, + const SkIRect& maskDevIBounds, const SkPMColor4f& color) + : fMatrix(m) + , fShape(shape) + , fStrokeDevWidth(strokeDevWidth) + , fShapeConservativeIBounds(shapeConservativeIBounds) + , fMaskDevIBounds(maskDevIBounds) + , fColor(color) { +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + if (fShape.hasUnstyledKey()) { + // On AOSP we round view matrix translates to integer values for cachable paths. We do this + // to match HWUI's cache hit ratio, which doesn't consider the matrix when caching paths. + fMatrix.setTranslateX(SkScalarRoundToScalar(fMatrix.getTranslateX())); + fMatrix.setTranslateY(SkScalarRoundToScalar(fMatrix.getTranslateY())); + } +#endif +} + +GrProcessorSet::Analysis GrCCDrawPathsOp::finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) { + SkASSERT(1 == fNumDraws); // There should only be one single path draw in this Op right now. + return fDraws.head().finalize(caps, clip, hasMixedSampledCoverage, clampType, &fProcessors); +} + +GrProcessorSet::Analysis GrCCDrawPathsOp::SingleDraw::finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, GrClampType + clampType, GrProcessorSet* processors) { + const GrProcessorSet::Analysis& analysis = processors->finalize( + fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, + &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType, &fColor); + + // Lines start looking jagged when they get thinner than 1px. For thin strokes it looks better + // if we can convert them to hairline (i.e., inflate the stroke width to 1px), and instead + // reduce the opacity to create the illusion of thin-ness. This strategy also helps reduce + // artifacts from coverage dilation when there are self intersections. + if (analysis.isCompatibleWithCoverageAsAlpha() && + !fShape.style().strokeRec().isFillStyle() && fStrokeDevWidth < 1) { + // Modifying the shape affects its cache key. The draw can't have a cache entry yet or else + // our next step would invalidate it. + SkASSERT(!fCacheEntry); + SkASSERT(SkStrokeRec::kStroke_Style == fShape.style().strokeRec().getStyle()); + + SkPath path; + fShape.asPath(&path); + + // Create a hairline version of our stroke. + SkStrokeRec hairlineStroke = fShape.style().strokeRec(); + hairlineStroke.setStrokeStyle(0); + + // How transparent does a 1px stroke have to be in order to appear as thin as the real one? + float coverage = fStrokeDevWidth; + + fShape = GrShape(path, GrStyle(hairlineStroke, nullptr)); + fStrokeDevWidth = 1; + + // fShapeConservativeIBounds already accounted for this possibility of inflating the stroke. + fColor = fColor * coverage; + } + + return analysis; +} + +GrOp::CombineResult GrCCDrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps&) { + GrCCDrawPathsOp* that = op->cast(); + SkASSERT(fOwningPerOpsTaskPaths); + SkASSERT(fNumDraws); + SkASSERT(!that->fOwningPerOpsTaskPaths || + that->fOwningPerOpsTaskPaths == fOwningPerOpsTaskPaths); + SkASSERT(that->fNumDraws); + + if (fProcessors != that->fProcessors || + fViewMatrixIfUsingLocalCoords != that->fViewMatrixIfUsingLocalCoords) { + return CombineResult::kCannotCombine; + } + + fDraws.append(std::move(that->fDraws), &fOwningPerOpsTaskPaths->fAllocator); + + SkDEBUGCODE(fNumDraws += that->fNumDraws); + SkDEBUGCODE(that->fNumDraws = 0); + return CombineResult::kMerged; +} + +void GrCCDrawPathsOp::addToOwningPerOpsTaskPaths(sk_sp owningPerOpsTaskPaths) { + SkASSERT(1 == fNumDraws); + SkASSERT(!fOwningPerOpsTaskPaths); + fOwningPerOpsTaskPaths = std::move(owningPerOpsTaskPaths); + fOwningPerOpsTaskPaths->fDrawOps.addToTail(this); +} + +void GrCCDrawPathsOp::accountForOwnPaths(GrCCPathCache* pathCache, + GrOnFlushResourceProvider* onFlushRP, + GrCCPerFlushResourceSpecs* specs) { + for (SingleDraw& draw : fDraws) { + draw.accountForOwnPath(pathCache, onFlushRP, specs); + } +} + +void GrCCDrawPathsOp::SingleDraw::accountForOwnPath( + GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, + GrCCPerFlushResourceSpecs* specs) { + using CoverageType = GrCCAtlas::CoverageType; + + SkPath path; + fShape.asPath(&path); + + SkASSERT(!fCacheEntry); + + if (pathCache) { + fCacheEntry = pathCache->find( + onFlushRP, fShape, fMaskDevIBounds, fMatrix, &fCachedMaskShift); + } + + if (fCacheEntry) { + if (const GrCCCachedAtlas* cachedAtlas = fCacheEntry->cachedAtlas()) { + SkASSERT(cachedAtlas->getOnFlushProxy()); + if (CoverageType::kA8_LiteralCoverage == cachedAtlas->coverageType()) { + ++specs->fNumCachedPaths; + } else { + // Suggest that this path be copied to a literal coverage atlas, to save memory. + // (The client may decline this copy via DoCopiesToA8Coverage::kNo.) + int idx = (fShape.style().strokeRec().isFillStyle()) + ? GrCCPerFlushResourceSpecs::kFillIdx + : GrCCPerFlushResourceSpecs::kStrokeIdx; + ++specs->fNumCopiedPaths[idx]; + specs->fCopyPathStats[idx].statPath(path); + specs->fCopyAtlasSpecs.accountForSpace(fCacheEntry->width(), fCacheEntry->height()); + fDoCopyToA8Coverage = true; + } + return; + } + + if (this->shouldCachePathMask(onFlushRP->caps()->maxRenderTargetSize())) { + fDoCachePathMask = true; + // We don't cache partial masks; ensure the bounds include the entire path. + fMaskDevIBounds = fShapeConservativeIBounds; + } + } + + // Plan on rendering this path in a new atlas. + int idx = (fShape.style().strokeRec().isFillStyle()) + ? GrCCPerFlushResourceSpecs::kFillIdx + : GrCCPerFlushResourceSpecs::kStrokeIdx; + ++specs->fNumRenderedPaths[idx]; + specs->fRenderedPathStats[idx].statPath(path); + specs->fRenderedAtlasSpecs.accountForSpace(fMaskDevIBounds.width(), fMaskDevIBounds.height()); + SkDEBUGCODE(fWasCountedAsRender = true); +} + +bool GrCCDrawPathsOp::SingleDraw::shouldCachePathMask(int maxRenderTargetSize) const { + SkASSERT(fCacheEntry); + SkASSERT(!fCacheEntry->cachedAtlas()); + if (fCacheEntry->hitCount() <= 1) { + return false; // Don't cache a path mask until at least its second hit. + } + + int shapeMaxDimension = SkTMax( + fShapeConservativeIBounds.height(), fShapeConservativeIBounds.width()); + if (shapeMaxDimension > maxRenderTargetSize) { + return false; // This path isn't cachable. + } + + int64_t shapeArea = sk_64_mul( + fShapeConservativeIBounds.height(), fShapeConservativeIBounds.width()); + if (shapeArea < 100*100) { + // If a path is small enough, we might as well try to render and cache the entire thing, no + // matter how much of it is actually visible. + return true; + } + + // The hitRect should already be contained within the shape's bounds, but we still intersect it + // because it's possible for edges very near pixel boundaries (e.g., 0.999999), to round out + // inconsistently, depending on the integer translation values and fp32 precision. + SkIRect hitRect = fCacheEntry->hitRect().makeOffset(fCachedMaskShift); + hitRect.intersect(fShapeConservativeIBounds); + + // Render and cache the entire path mask if we see enough of it to justify rendering all the + // pixels. Our criteria for "enough" is that we must have seen at least 50% of the path in the + // past, and in this particular draw we must see at least 10% of it. + int64_t hitArea = sk_64_mul(hitRect.height(), hitRect.width()); + int64_t drawArea = sk_64_mul(fMaskDevIBounds.height(), fMaskDevIBounds.width()); + return hitArea*2 >= shapeArea && drawArea*10 >= shapeArea; +} + +void GrCCDrawPathsOp::setupResources( + GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, + GrCCPerFlushResources* resources, DoCopiesToA8Coverage doCopies) { + SkASSERT(fNumDraws > 0); + SkASSERT(-1 == fBaseInstance); + fBaseInstance = resources->nextPathInstanceIdx(); + + for (SingleDraw& draw : fDraws) { + draw.setupResources(pathCache, onFlushRP, resources, doCopies, this); + } + + if (!fInstanceRanges.empty()) { + fInstanceRanges.back().fEndInstanceIdx = resources->nextPathInstanceIdx(); + } +} + +void GrCCDrawPathsOp::SingleDraw::setupResources( + GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, + GrCCPerFlushResources* resources, DoCopiesToA8Coverage doCopies, GrCCDrawPathsOp* op) { + SkPath path; + fShape.asPath(&path); + + auto fillRule = (fShape.style().strokeRec().isFillStyle()) + ? GrFillRuleForSkPath(path) + : GrFillRule::kNonzero; + + if (fCacheEntry) { + // Does the path already exist in a cached atlas texture? + if (fCacheEntry->cachedAtlas()) { + SkASSERT(fCacheEntry->cachedAtlas()->getOnFlushProxy()); + if (DoCopiesToA8Coverage::kYes == doCopies && fDoCopyToA8Coverage) { + resources->upgradeEntryToLiteralCoverageAtlas( + pathCache, onFlushRP, fCacheEntry.get(), fillRule); + SkASSERT(fCacheEntry->cachedAtlas()); + SkASSERT(GrCCAtlas::CoverageType::kA8_LiteralCoverage + == fCacheEntry->cachedAtlas()->coverageType()); + SkASSERT(fCacheEntry->cachedAtlas()->getOnFlushProxy()); + } +#if 0 + // Simple color manipulation to visualize cached paths. + fColor = (GrCCAtlas::CoverageType::kA8_LiteralCoverage + == fCacheEntry->cachedAtlas()->coverageType()) + ? SkPMColor4f{0,0,.25,.25} : SkPMColor4f{0,.25,0,.25}; +#endif + auto coverageMode = GrCCPathProcessor::GetCoverageMode( + fCacheEntry->cachedAtlas()->coverageType()); + op->recordInstance(coverageMode, fCacheEntry->cachedAtlas()->getOnFlushProxy(), + resources->nextPathInstanceIdx()); + resources->appendDrawPathInstance().set( + *fCacheEntry, fCachedMaskShift, SkPMColor4f_toFP16(fColor), fillRule); +#ifdef SK_DEBUG + if (fWasCountedAsRender) { + // A path mask didn't exist for this path at the beginning of flush, but we have one + // now. What this means is that we've drawn the same path multiple times this flush. + // Let the resources know that we reused one for their internal debug counters. + resources->debugOnly_didReuseRenderedPath(); + } +#endif + return; + } + } + + // Render the raw path into a coverage count atlas. renderShapeInAtlas() gives us two tight + // bounding boxes: One in device space, as well as a second one rotated an additional 45 + // degrees. The path vertex shader uses these two bounding boxes to generate an octagon that + // circumscribes the path. + GrOctoBounds octoBounds; + SkIRect devIBounds; + SkIVector devToAtlasOffset; + if (auto atlas = resources->renderShapeInAtlas( + fMaskDevIBounds, fMatrix, fShape, fStrokeDevWidth, &octoBounds, &devIBounds, + &devToAtlasOffset)) { + auto coverageMode = GrCCPathProcessor::GetCoverageMode( + resources->renderedPathCoverageType()); + op->recordInstance(coverageMode, atlas->textureProxy(), resources->nextPathInstanceIdx()); + resources->appendDrawPathInstance().set( + octoBounds, devToAtlasOffset, SkPMColor4f_toFP16(fColor), fillRule); + + if (fDoCachePathMask) { + SkASSERT(fCacheEntry); + SkASSERT(!fCacheEntry->cachedAtlas()); + SkASSERT(fShapeConservativeIBounds == fMaskDevIBounds); + fCacheEntry->setCoverageCountAtlas( + onFlushRP, atlas, devToAtlasOffset, octoBounds, devIBounds, fCachedMaskShift); + } + } +} + +inline void GrCCDrawPathsOp::recordInstance( + GrCCPathProcessor::CoverageMode coverageMode, GrTextureProxy* atlasProxy, int instanceIdx) { + if (fInstanceRanges.empty()) { + fInstanceRanges.push_back({coverageMode, atlasProxy, instanceIdx}); + } else if (fInstanceRanges.back().fAtlasProxy != atlasProxy) { + fInstanceRanges.back().fEndInstanceIdx = instanceIdx; + fInstanceRanges.push_back({coverageMode, atlasProxy, instanceIdx}); + } + SkASSERT(fInstanceRanges.back().fCoverageMode == coverageMode); + SkASSERT(fInstanceRanges.back().fAtlasProxy == atlasProxy); +} + +void GrCCDrawPathsOp::onPrepare(GrOpFlushState* flushState) { + // The CCPR ops don't know their atlas textures until after the preFlush calls have been + // executed at the start GrDrawingManger::flush. Thus the proxies are not added during the + // normal visitProxies calls doing addDrawOp. Therefore, the atlas proxies are added now. + for (const InstanceRange& range : fInstanceRanges) { + flushState->sampledProxyArray()->push_back(range.fAtlasProxy); + } +} + +void GrCCDrawPathsOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + SkASSERT(fOwningPerOpsTaskPaths); + + const GrCCPerFlushResources* resources = fOwningPerOpsTaskPaths->fFlushResources.get(); + if (!resources) { + return; // Setup failed. + } + + GrPipeline::InitArgs initArgs; + initArgs.fCaps = &flushState->caps(); + initArgs.fDstProxy = flushState->drawOpArgs().dstProxy(); + initArgs.fOutputSwizzle = flushState->drawOpArgs().outputSwizzle(); + auto clip = flushState->detachAppliedClip(); + GrPipeline::FixedDynamicState fixedDynamicState(clip.scissorState().rect()); + GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip)); + + int baseInstance = fBaseInstance; + SkASSERT(baseInstance >= 0); // Make sure setupResources() has been called. + + for (const InstanceRange& range : fInstanceRanges) { + SkASSERT(range.fEndInstanceIdx > baseInstance); + + const GrTextureProxy* atlas = range.fAtlasProxy; + SkASSERT(atlas->isInstantiated()); + + GrCCPathProcessor pathProc( + range.fCoverageMode, atlas->peekTexture(), atlas->textureSwizzle(), atlas->origin(), + fViewMatrixIfUsingLocalCoords); + GrTextureProxy* atlasProxy = range.fAtlasProxy; + fixedDynamicState.fPrimitiveProcessorTextures = &atlasProxy; + pathProc.drawPaths(flushState, pipeline, &fixedDynamicState, *resources, baseInstance, + range.fEndInstanceIdx, this->bounds()); + + baseInstance = range.fEndInstanceIdx; + } +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.h b/gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.h new file mode 100644 index 0000000000..2a63953326 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCDrawPathsOp.h @@ -0,0 +1,147 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCDrawPathsOp_DEFINED +#define GrCCDrawPathsOp_DEFINED + +#include "src/core/SkTInternalLList.h" +#include "src/gpu/ccpr/GrCCPathCache.h" +#include "src/gpu/ccpr/GrCCSTLList.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrDrawOp.h" + +class GrCCAtlas; +class GrCCPerFlushResources; +struct GrCCPerFlushResourceSpecs; +struct GrCCPerOpsTaskPaths; +class GrOnFlushResourceProvider; +class GrRecordingContext; + +/** + * This is the Op that draws paths to the actual canvas, using atlases generated by CCPR. + */ +class GrCCDrawPathsOp : public GrDrawOp { +public: + DEFINE_OP_CLASS_ID + SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCDrawPathsOp); + + static std::unique_ptr Make(GrRecordingContext*, const SkIRect& clipIBounds, + const SkMatrix&, const GrShape&, GrPaint&&); + ~GrCCDrawPathsOp() override; + + const char* name() const override { return "GrCCDrawPathsOp"; } + FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override; + CombineResult onCombineIfPossible(GrOp*, const GrCaps&) override; + void visitProxies(const VisitProxyFunc& fn) const override { + for (const auto& range : fInstanceRanges) { + fn(range.fAtlasProxy, GrMipMapped::kNo); + } + fProcessors.visitProxies(fn); + } + void onPrepare(GrOpFlushState*) override; + + void addToOwningPerOpsTaskPaths(sk_sp owningPerOpsTaskPaths); + + // Makes decisions about how to draw each path (cached, copied, rendered, etc.), and + // increments/fills out the corresponding GrCCPerFlushResourceSpecs. + void accountForOwnPaths(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResourceSpecs*); + + // Allows the caller to decide whether to actually do the suggested copies from cached 16-bit + // coverage count atlases, and into 8-bit literal coverage atlases. Purely to save space. + enum class DoCopiesToA8Coverage : bool { + kNo = false, + kYes = true + }; + + // Allocates the GPU resources indicated by accountForOwnPaths(), in preparation for drawing. If + // DoCopiesToA8Coverage is kNo, the paths slated for copy will instead be left in their 16-bit + // coverage count atlases. + // + // NOTE: If using DoCopiesToA8Coverage::kNo, it is the caller's responsibility to have called + // cancelCopies() on the GrCCPerFlushResourceSpecs, prior to making this call. + void setupResources(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResources*, + DoCopiesToA8Coverage); + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + +private: + friend class GrOpMemoryPool; + + static std::unique_ptr InternalMake(GrRecordingContext*, + const SkIRect& clipIBounds, + const SkMatrix&, const GrShape&, + float strokeDevWidth, + const SkRect& conservativeDevBounds, + GrPaint&&); + + GrCCDrawPathsOp(const SkMatrix&, const GrShape&, float strokeDevWidth, + const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds, + const SkRect& conservativeDevBounds, GrPaint&&); + + void recordInstance( + GrCCPathProcessor::CoverageMode, GrTextureProxy* atlasProxy, int instanceIdx); + + const SkMatrix fViewMatrixIfUsingLocalCoords; + + class SingleDraw { + public: + SingleDraw(const SkMatrix&, const GrShape&, float strokeDevWidth, + const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds, + const SkPMColor4f&); + + // See the corresponding methods in GrCCDrawPathsOp. + GrProcessorSet::Analysis finalize( + const GrCaps&, const GrAppliedClip*, bool hasMixedSampledCoverage, GrClampType, + GrProcessorSet*); + void accountForOwnPath(GrCCPathCache*, GrOnFlushResourceProvider*, + GrCCPerFlushResourceSpecs*); + void setupResources(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResources*, + DoCopiesToA8Coverage, GrCCDrawPathsOp*); + + private: + bool shouldCachePathMask(int maxRenderTargetSize) const; + + SkMatrix fMatrix; + GrShape fShape; + float fStrokeDevWidth; + const SkIRect fShapeConservativeIBounds; + SkIRect fMaskDevIBounds; + SkPMColor4f fColor; + + GrCCPathCache::OnFlushEntryRef fCacheEntry; + SkIVector fCachedMaskShift; + bool fDoCopyToA8Coverage = false; + bool fDoCachePathMask = false; + SkDEBUGCODE(bool fWasCountedAsRender = false); + + SingleDraw* fNext = nullptr; + + friend class GrCCSTLList; // To access fNext. + }; + + // Declare fOwningPerOpsTaskPaths first, before fDraws. The draws use memory allocated by + // fOwningPerOpsTaskPaths, so it must not be unreffed until after fDraws is destroyed. + sk_sp fOwningPerOpsTaskPaths; + + GrCCSTLList fDraws; + SkDEBUGCODE(int fNumDraws = 1); + + GrProcessorSet fProcessors; + + struct InstanceRange { + GrCCPathProcessor::CoverageMode fCoverageMode; + GrTextureProxy* fAtlasProxy; + int fEndInstanceIdx; + }; + + SkSTArray<2, InstanceRange, true> fInstanceRanges; + int fBaseInstance SkDEBUGCODE(= -1); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.cpp new file mode 100644 index 0000000000..75b7fd05e5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.cpp @@ -0,0 +1,802 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCFillGeometry.h" + +#include "include/gpu/GrTypes.h" +#include "src/core/SkGeometry.h" +#include +#include +#include + +static constexpr float kFlatnessThreshold = 1/16.f; // 1/16 of a pixel. + +void GrCCFillGeometry::beginPath() { + SkASSERT(!fBuildingContour); + fVerbs.push_back(Verb::kBeginPath); +} + +void GrCCFillGeometry::beginContour(const SkPoint& pt) { + SkASSERT(!fBuildingContour); + // Store the current verb count in the fTriangles field for now. When we close the contour we + // will use this value to calculate the actual number of triangles in its fan. + fCurrContourTallies = {fVerbs.count(), 0, 0, 0, 0}; + + fPoints.push_back(pt); + fVerbs.push_back(Verb::kBeginContour); + fCurrAnchorPoint = pt; + + SkDEBUGCODE(fBuildingContour = true); +} + +void GrCCFillGeometry::lineTo(const SkPoint P[2]) { + SkASSERT(fBuildingContour); + SkASSERT(P[0] == fPoints.back()); + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + this->appendLine(p0, p1); +} + +inline void GrCCFillGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) { + SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1])); + if ((p0 == p1).allTrue()) { + return; + } + p1.store(&fPoints.push_back()); + fVerbs.push_back(Verb::kLineTo); +} + +static inline Sk2f normalize(const Sk2f& n) { + Sk2f nn = n*n; + return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt(); +} + +static inline float dot(const Sk2f& a, const Sk2f& b) { + float product[2]; + (a * b).store(product); + return product[0] + product[1]; +} + +static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + float tolerance = kFlatnessThreshold) { + Sk2f l = p2 - p0; // Line from p0 -> p2. + + // lwidth = Manhattan width of l. + Sk2f labs = l.abs(); + float lwidth = labs[0] + labs[1]; + + // d = |p1 - p0| dot | l.y| + // |-l.x| = distance from p1 to l. + Sk2f dd = (p1 - p0) * SkNx_shuffle<1,0>(l); + float d = dd[0] - dd[1]; + + // We are collinear if a box with radius "tolerance", centered on p1, touches the line l. + // To decide this, we check if the distance from p1 to the line is less than the distance from + // p1 to the far corner of this imaginary box, along that same normal vector. + // The far corner of the box can be found at "p1 + sign(n) * tolerance", where n is normal to l: + // + // abs(dot(p1 - p0, n)) <= dot(sign(n) * tolerance, n) + // + // Which reduces to: + // + // abs(d) <= (n.x * sign(n.x) + n.y * sign(n.y)) * tolerance + // abs(d) <= (abs(n.x) + abs(n.y)) * tolerance + // + // Use "<=" in case l == 0. + return std::abs(d) <= lwidth * tolerance; +} + +static inline bool are_collinear(const SkPoint P[4], float tolerance = kFlatnessThreshold) { + Sk4f Px, Py; // |Px Py| |p0 - p3| + Sk4f::Load2(P, &Px, &Py); // |. . | = |p1 - p3| + Px -= Px[3]; // |. . | |p2 - p3| + Py -= Py[3]; // |. . | | 0 | + + // Find [lx, ly] = the line from p3 to the furthest-away point from p3. + Sk4f Pwidth = Px.abs() + Py.abs(); // Pwidth = Manhattan width of each point. + int lidx = Pwidth[0] > Pwidth[1] ? 0 : 1; + lidx = Pwidth[lidx] > Pwidth[2] ? lidx : 2; + float lx = Px[lidx], ly = Py[lidx]; + float lwidth = Pwidth[lidx]; // lwidth = Manhattan width of [lx, ly]. + + // |Px Py| + // d = |. . | * | ly| = distances from each point to l (two of the distances will be zero). + // |. . | |-lx| + // |. . | + Sk4f d = Px*ly - Py*lx; + + // We are collinear if boxes with radius "tolerance", centered on all 4 points all touch line l. + // (See the rationale for this formula in the above, 3-point version of this function.) + // Use "<=" in case l == 0. + return (d.abs() <= lwidth * tolerance).allTrue(); +} + +// Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt]. +static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& tan0, + const Sk2f& endPt, const Sk2f& tan1) { + Sk2f v = endPt - startPt; + float dot0 = dot(tan0, v); + float dot1 = dot(tan1, v); + + // A small, negative tolerance handles floating-point error in the case when one tangent + // approaches 0 length, meaning the (convex) curve segment is effectively a flat line. + float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero; + return dot0 >= tolerance && dot1 >= tolerance; +} + +template static inline SkNx lerp(const SkNx& a, const SkNx& b, + const SkNx& t) { + return SkNx_fma(t, b - a, a); +} + +void GrCCFillGeometry::quadraticTo(const SkPoint P[3]) { + SkASSERT(fBuildingContour); + SkASSERT(P[0] == fPoints.back()); + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + Sk2f p2 = Sk2f::Load(P+2); + + // Don't crunch on the curve if it is nearly flat (or just very small). Flat curves can break + // The monotonic chopping math. + if (are_collinear(p0, p1, p2)) { + this->appendLine(p0, p2); + return; + } + + this->appendQuadratics(p0, p1, p2); +} + +inline void GrCCFillGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) { + Sk2f tan0 = p1 - p0; + Sk2f tan1 = p2 - p1; + + // This should almost always be this case for well-behaved curves in the real world. + if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) { + this->appendMonotonicQuadratic(p0, p1, p2); + return; + } + + // Chop the curve into two segments with equal curvature. To do this we find the T value whose + // tangent angle is halfway between tan0 and tan1. + Sk2f n = normalize(tan0) - normalize(tan1); + + // The midtangent can be found where (dQ(t) dot n) = 0: + // + // 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n | + // | -2*p0 + 2*p1 | | . | + // + // = | 2*t 1 | * | tan1 - tan0 | * | n | + // | 2*tan0 | | . | + // + // = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n) + // + // t = (tan0 dot n) / ((tan0 - tan1) dot n) + Sk2f dQ1n = (tan0 - tan1) * n; + Sk2f dQ0n = tan0 * n; + Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n)); + t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error. + + Sk2f p01 = SkNx_fma(t, tan0, p0); + Sk2f p12 = SkNx_fma(t, tan1, p1); + Sk2f p012 = lerp(p01, p12, t); + + this->appendMonotonicQuadratic(p0, p01, p012); + this->appendMonotonicQuadratic(p012, p12, p2); +} + +inline void GrCCFillGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2) { + // Don't send curves to the GPU if we know they are nearly flat (or just very small). + if (are_collinear(p0, p1, p2)) { + this->appendLine(p0, p2); + return; + } + + SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1])); + SkASSERT((p0 != p2).anyTrue()); + p1.store(&fPoints.push_back()); + p2.store(&fPoints.push_back()); + fVerbs.push_back(Verb::kMonotonicQuadraticTo); + ++fCurrContourTallies.fQuadratics; +} + +static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) { + Sk2f aa = a*a; + aa += SkNx_shuffle<1,0>(aa); + SkASSERT(aa[0] == aa[1]); + + Sk2f bb = b*b; + bb += SkNx_shuffle<1,0>(bb); + SkASSERT(bb[0] == bb[1]); + + return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b); +} + +static inline void get_cubic_tangents(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3, Sk2f* tan0, Sk2f* tan1) { + *tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0); + *tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1); +} + +static inline bool is_cubic_nearly_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3, const Sk2f& tan0, const Sk2f& tan1, + Sk2f* c) { + Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0); + Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan1, p3); + *c = (c1 + c2) * .5f; // Hopefully optimized out if not used? + return ((c1 - c2).abs() <= 1).allTrue(); +} + +enum class ExcludedTerm : bool { + kQuadraticTerm, + kLinearTerm +}; + +// Finds where to chop a non-loop around its inflection points. The resulting cubic segments will be +// chopped such that a box of radius 'padRadius', centered at any point along the curve segment, is +// guaranteed to not cross the tangent lines at the inflection points (a.k.a lines L & M). +// +// 'chops' will be filled with 0, 2, or 4 T values. The segments between T0..T1 and T2..T3 must be +// drawn with flat lines instead of cubics. +// +// A serpentine cubic has two inflection points, so this method takes Sk2f and computes the padding +// for both in SIMD. +static inline void find_chops_around_inflection_points(float padRadius, Sk2f tl, Sk2f sl, + const Sk2f& C0, const Sk2f& C1, + ExcludedTerm skipTerm, float Cdet, + SkSTArray<4, float>* chops) { + SkASSERT(chops->empty()); + SkASSERT(padRadius >= 0); + + padRadius /= std::abs(Cdet); // Scale this single value rather than all of C^-1 later on. + + // The homogeneous parametric functions for distance from lines L & M are: + // + // l(t,s) = (t*sl - s*tl)^3 + // m(t,s) = (t*sm - s*tm)^3 + // + // See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", + // 4.3 Finding klmn: + // + // https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + // + // From here on we use Sk2f with "L" names, but the second lane will be for line M. + tl = (sl > 0).thenElse(tl, -tl); // Tl=tl/sl is the triple root of l(t,s). Normalize so s >= 0. + sl = sl.abs(); + + // Convert l(t,s), m(t,s) to power-basis form: + // + // | l3 m3 | + // |l(t,s) m(t,s)| = |t^3 t^2*s t*s^2 s^3| * | l2 m2 | + // | l1 m1 | + // | l0 m0 | + // + Sk2f l3 = sl*sl*sl; + Sk2f l2or1 = (ExcludedTerm::kLinearTerm == skipTerm) ? sl*sl*tl*-3 : sl*tl*tl*3; + + // The equation for line L can be found as follows: + // + // L = C^-1 * (l excluding skipTerm) + // + // (See comments for GrPathUtils::calcCubicInverseTransposePowerBasisMatrix.) + // We are only interested in the normal to L, so only need the upper 2x2 of C^-1. And rather + // than divide by determinant(C) here, we have already performed this divide on padRadius. + Sk2f Lx = C1[1]*l3 - C0[1]*l2or1; + Sk2f Ly = -C1[0]*l3 + C0[0]*l2or1; + + // A box of radius "padRadius" is touching line L if "center dot L" is less than the Manhattan + // with of L. (See rationale in are_collinear.) + Sk2f Lwidth = Lx.abs() + Ly.abs(); + Sk2f pad = Lwidth * padRadius; + + // Will T=(t + cbrt(pad))/s be greater than 0? No need to solve roots outside T=0..1. + Sk2f insideLeftPad = pad + tl*tl*tl; + + // Will T=(t - cbrt(pad))/s be less than 1? No need to solve roots outside T=0..1. + Sk2f tms = tl - sl; + Sk2f insideRightPad = pad - tms*tms*tms; + + // Solve for the T values where abs(l(T)) = pad. + if (insideLeftPad[0] > 0 && insideRightPad[0] > 0) { + float padT = cbrtf(pad[0]); + Sk2f pts = (tl[0] + Sk2f(-padT, +padT)) / sl[0]; + pts.store(chops->push_back_n(2)); + } + + // Solve for the T values where abs(m(T)) = pad. + if (insideLeftPad[1] > 0 && insideRightPad[1] > 0) { + float padT = cbrtf(pad[1]); + Sk2f pts = (tl[1] + Sk2f(-padT, +padT)) / sl[1]; + pts.store(chops->push_back_n(2)); + } +} + +static inline void swap_if_greater(float& a, float& b) { + if (a > b) { + std::swap(a, b); + } +} + +// Finds where to chop a non-loop around its intersection point. The resulting cubic segments will +// be chopped such that a box of radius 'padRadius', centered at any point along the curve segment, +// is guaranteed to not cross the tangent lines at the intersection point (a.k.a lines L & M). +// +// 'chops' will be filled with 0, 2, or 4 T values. The segments between T0..T1 and T2..T3 must be +// drawn with quadratic splines instead of cubics. +// +// A loop intersection falls at two different T values, so this method takes Sk2f and computes the +// padding for both in SIMD. +static inline void find_chops_around_loop_intersection(float padRadius, Sk2f t2, Sk2f s2, + const Sk2f& C0, const Sk2f& C1, + ExcludedTerm skipTerm, float Cdet, + SkSTArray<4, float>* chops) { + SkASSERT(chops->empty()); + SkASSERT(padRadius >= 0); + + padRadius /= std::abs(Cdet); // Scale this single value rather than all of C^-1 later on. + + // The parametric functions for distance from lines L & M are: + // + // l(T) = (T - Td)^2 * (T - Te) + // m(T) = (T - Td) * (T - Te)^2 + // + // See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", + // 4.3 Finding klmn: + // + // https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + Sk2f T2 = t2/s2; // T2 is the double root of l(T). + Sk2f T1 = SkNx_shuffle<1,0>(T2); // T1 is the other root of l(T). + + // Convert l(T), m(T) to power-basis form: + // + // | 1 1 | + // |l(T) m(T)| = |T^3 T^2 T 1| * | l2 m2 | + // | l1 m1 | + // | l0 m0 | + // + // From here on we use Sk2f with "L" names, but the second lane will be for line M. + Sk2f l2 = SkNx_fma(Sk2f(-2), T2, -T1); + Sk2f l1 = T2 * SkNx_fma(Sk2f(2), T1, T2); + Sk2f l0 = -T2*T2*T1; + + // The equation for line L can be found as follows: + // + // L = C^-1 * (l excluding skipTerm) + // + // (See comments for GrPathUtils::calcCubicInverseTransposePowerBasisMatrix.) + // We are only interested in the normal to L, so only need the upper 2x2 of C^-1. And rather + // than divide by determinant(C) here, we have already performed this divide on padRadius. + Sk2f l2or1 = (ExcludedTerm::kLinearTerm == skipTerm) ? l2 : l1; + Sk2f Lx = -C0[1]*l2or1 + C1[1]; // l3 is always 1. + Sk2f Ly = C0[0]*l2or1 - C1[0]; + + // A box of radius "padRadius" is touching line L if "center dot L" is less than the Manhattan + // with of L. (See rationale in are_collinear.) + Sk2f Lwidth = Lx.abs() + Ly.abs(); + Sk2f pad = Lwidth * padRadius; + + // Is l(T=0) outside the padding around line L? + Sk2f lT0 = l0; // l(T=0) = |0 0 0 1| dot |1 l2 l1 l0| = l0 + Sk2f outsideT0 = lT0.abs() - pad; + + // Is l(T=1) outside the padding around line L? + Sk2f lT1 = (Sk2f(1) + l2 + l1 + l0).abs(); // l(T=1) = |1 1 1 1| dot |1 l2 l1 l0| + Sk2f outsideT1 = lT1.abs() - pad; + + // Values for solving the cubic. + Sk2f p, q, qqq, discr, numRoots, D; + bool hasDiscr = false; + + // Values for calculating one root (rarely needed). + Sk2f R, QQ; + bool hasOneRootVals = false; + + // Values for calculating three roots. + Sk2f P, cosTheta3; + bool hasThreeRootVals = false; + + // Solve for the T values where l(T) = +pad and m(T) = -pad. + for (int i = 0; i < 2; ++i) { + float T = T2[i]; // T is the point we are chopping around. + if ((T < 0 && outsideT0[i] >= 0) || (T > 1 && outsideT1[i] >= 0)) { + // The padding around T is completely out of range. No point solving for it. + continue; + } + + if (!hasDiscr) { + p = Sk2f(+.5f, -.5f) * pad; + q = (1.f/3) * (T2 - T1); + qqq = q*q*q; + discr = qqq*p*2 + p*p; + numRoots = (discr < 0).thenElse(3, 1); + D = T2 - q; + hasDiscr = true; + } + + if (1 == numRoots[i]) { + if (!hasOneRootVals) { + Sk2f r = qqq + p; + Sk2f s = r.abs() + discr.sqrt(); + R = (r > 0).thenElse(-s, s); + QQ = q*q; + hasOneRootVals = true; + } + + float A = cbrtf(R[i]); + float B = A != 0 ? QQ[i]/A : 0; + // When there is only one root, ine L chops from root..1, line M chops from 0..root. + if (1 == i) { + chops->push_back(0); + } + chops->push_back(A + B + D[i]); + if (0 == i) { + chops->push_back(1); + } + continue; + } + + if (!hasThreeRootVals) { + P = q.abs() * -2; + cosTheta3 = (q >= 0).thenElse(1, -1) + p / qqq.abs(); + hasThreeRootVals = true; + } + + static constexpr float k2PiOver3 = 2 * SK_ScalarPI / 3; + float theta = std::acos(cosTheta3[i]) * (1.f/3); + float roots[3] = {P[i] * std::cos(theta) + D[i], + P[i] * std::cos(theta + k2PiOver3) + D[i], + P[i] * std::cos(theta - k2PiOver3) + D[i]}; + + // Sort the three roots. + swap_if_greater(roots[0], roots[1]); + swap_if_greater(roots[1], roots[2]); + swap_if_greater(roots[0], roots[1]); + + // Line L chops around the first 2 roots, line M chops around the second 2. + chops->push_back_n(2, &roots[i]); + } +} + +void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) { + SkASSERT(fBuildingContour); + SkASSERT(P[0] == fPoints.back()); + + // Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small). + // Flat curves can break the math below. + if (are_collinear(P)) { + Sk2f p0 = Sk2f::Load(P); + Sk2f p3 = Sk2f::Load(P+3); + this->appendLine(p0, p3); + return; + } + + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + Sk2f p2 = Sk2f::Load(P+2); + Sk2f p3 = Sk2f::Load(P+3); + + // Also detect near-quadratics ahead of time. + Sk2f tan0, tan1, c; + get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1); + if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c)) { + this->appendQuadratics(p0, c, p3); + return; + } + + double tt[2], ss[2], D[4]; + fCurrCubicType = SkClassifyCubic(P, tt, ss, D); + SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); + Sk2f t = Sk2f(static_cast(tt[0]), static_cast(tt[1])); + Sk2f s = Sk2f(static_cast(ss[0]), static_cast(ss[1])); + + ExcludedTerm skipTerm = (std::abs(D[2]) > std::abs(D[1])) + ? ExcludedTerm::kQuadraticTerm + : ExcludedTerm::kLinearTerm; + Sk2f C0 = SkNx_fma(Sk2f(3), p1 - p2, p3 - p0); + Sk2f C1 = (ExcludedTerm::kLinearTerm == skipTerm + ? SkNx_fma(Sk2f(-2), p1, p0 + p2) + : p1 - p0) * 3; + Sk2f C0x1 = C0 * SkNx_shuffle<1,0>(C1); + float Cdet = C0x1[0] - C0x1[1]; + + SkSTArray<4, float> chops; + if (SkCubicType::kLoop != fCurrCubicType) { + find_chops_around_inflection_points(inflectPad, t, s, C0, C1, skipTerm, Cdet, &chops); + } else { + find_chops_around_loop_intersection(loopIntersectPad, t, s, C0, C1, skipTerm, Cdet, &chops); + } + if (4 == chops.count() && chops[1] >= chops[2]) { + // This just the means the KLM roots are so close that their paddings overlap. We will + // approximate the entire middle section, but still have it chopped midway. For loops this + // chop guarantees the append code only sees convex segments. Otherwise, it means we are (at + // least almost) a cusp and the chop makes sure we get a sharp point. + Sk2f ts = t * SkNx_shuffle<1,0>(s); + chops[1] = chops[2] = (ts[0] + ts[1]) / (2*s[0]*s[1]); + } + +#ifdef SK_DEBUG + for (int i = 1; i < chops.count(); ++i) { + SkASSERT(chops[i] >= chops[i - 1]); + } +#endif + this->appendCubics(AppendCubicMode::kLiteral, p0, p1, p2, p3, chops.begin(), chops.count()); +} + +static inline void chop_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, const Sk2f& p3, + float T, Sk2f* ab, Sk2f* abc, Sk2f* abcd, Sk2f* bcd, Sk2f* cd) { + Sk2f TT = T; + *ab = lerp(p0, p1, TT); + Sk2f bc = lerp(p1, p2, TT); + *cd = lerp(p2, p3, TT); + *abc = lerp(*ab, bc, TT); + *bcd = lerp(bc, *cd, TT); + *abcd = lerp(*abc, *bcd, TT); +} + +void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2, const Sk2f& p3, const float chops[], + int numChops, float localT0, float localT1) { + if (numChops) { + SkASSERT(numChops > 0); + int midChopIdx = numChops/2; + float T = chops[midChopIdx]; + // Chops alternate between literal and approximate mode. + AppendCubicMode rightMode = (AppendCubicMode)((bool)mode ^ (midChopIdx & 1) ^ 1); + + if (T <= localT0) { + // T is outside 0..1. Append the right side only. + this->appendCubics(rightMode, p0, p1, p2, p3, &chops[midChopIdx + 1], + numChops - midChopIdx - 1, localT0, localT1); + return; + } + + if (T >= localT1) { + // T is outside 0..1. Append the left side only. + this->appendCubics(mode, p0, p1, p2, p3, chops, midChopIdx, localT0, localT1); + return; + } + + float localT = (T - localT0) / (localT1 - localT0); + Sk2f p01, p02, pT, p11, p12; + chop_cubic(p0, p1, p2, p3, localT, &p01, &p02, &pT, &p11, &p12); + this->appendCubics(mode, p0, p01, p02, pT, chops, midChopIdx, localT0, T); + this->appendCubics(rightMode, pT, p11, p12, p3, &chops[midChopIdx + 1], + numChops - midChopIdx - 1, T, localT1); + return; + } + + this->appendCubics(mode, p0, p1, p2, p3); +} + +void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) { + if (SkCubicType::kLoop != fCurrCubicType) { + // Serpentines and cusps are always monotonic after chopping around inflection points. + SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); + + if (AppendCubicMode::kApproximate == mode) { + // This section passes through an inflection point, so we can get away with a flat line. + // This can cause some curves to feel slightly more flat when inspected rigorously back + // and forth against another renderer, but for now this seems acceptable given the + // simplicity. + this->appendLine(p0, p3); + return; + } + } else { + Sk2f tan0, tan1; + get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1); + + if (maxSubdivisions && !is_convex_curve_monotonic(p0, tan0, p3, tan1)) { + this->chopAndAppendCubicAtMidTangent(mode, p0, p1, p2, p3, tan0, tan1, + maxSubdivisions - 1); + return; + } + + if (AppendCubicMode::kApproximate == mode) { + Sk2f c; + if (!is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c) && maxSubdivisions) { + this->chopAndAppendCubicAtMidTangent(mode, p0, p1, p2, p3, tan0, tan1, + maxSubdivisions - 1); + return; + } + + this->appendMonotonicQuadratic(p0, c, p3); + return; + } + } + + // Don't send curves to the GPU if we know they are nearly flat (or just very small). + // Since the cubic segment is known to be convex at this point, our flatness check is simple. + if (are_collinear(p0, (p1 + p2) * .5f, p3)) { + this->appendLine(p0, p3); + return; + } + + SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1])); + SkASSERT((p0 != p3).anyTrue()); + p1.store(&fPoints.push_back()); + p2.store(&fPoints.push_back()); + p3.store(&fPoints.push_back()); + fVerbs.push_back(Verb::kMonotonicCubicTo); + ++fCurrContourTallies.fCubics; +} + +// Given a convex curve segment with the following order-2 tangent function: +// +// |C2x C2y| +// tan = some_scale * |dx/dt dy/dt| = |t^2 t 1| * |C1x C1y| +// |C0x C0y| +// +// This function finds the T value whose tangent angle is halfway between the tangents at T=0 and +// T=1 (tan0 and tan1). +static inline float find_midtangent(const Sk2f& tan0, const Sk2f& tan1, + const Sk2f& C2, const Sk2f& C1, const Sk2f& C0) { + // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the + // midtangent. 'n' will therefore bisect tan0 and -tan1, giving us the normal to the midtangent. + // + // n dot midtangent = 0 + // + Sk2f n = normalize(tan0) - normalize(tan1); + + // Find the T value at the midtangent. This is a simple quadratic equation: + // + // midtangent dot n = 0 + // + // (|t^2 t 1| * C) dot n = 0 + // + // |t^2 t 1| dot C*n = 0 + // + // First find coeffs = C*n. + Sk4f C[2]; + Sk2f::Store4(C, C2, C1, C0, 0); + Sk4f coeffs = C[0]*n[0] + C[1]*n[1]; + + // Now solve the quadratic. + float a = coeffs[0], b = coeffs[1], c = coeffs[2]; + float discr = b*b - 4*a*c; + if (discr < 0) { + return 0; // This will only happen if the curve is a line. + } + + // The roots are q/a and c/q. Pick the one closer to T=.5. + float q = -.5f * (b + copysignf(std::sqrt(discr), b)); + float r = .5f*q*a; + return std::abs(q*q - r) < std::abs(a*c - r) ? q/a : c/q; +} + +inline void GrCCFillGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0, + const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3, const Sk2f& tan0, + const Sk2f& tan1, + int maxFutureSubdivisions) { + float midT = find_midtangent(tan0, tan1, p3 + (p1 - p2)*3 - p0, + (p0 - p1*2 + p2)*2, + p1 - p0); + // Use positive logic since NaN fails comparisons. (However midT should not be NaN since we cull + // near-flat cubics in cubicTo().) + if (!(midT > 0 && midT < 1)) { + // The cubic is flat. Otherwise there would be a real midtangent inside T=0..1. + this->appendLine(p0, p3); + return; + } + + Sk2f p01, p02, pT, p11, p12; + chop_cubic(p0, p1, p2, p3, midT, &p01, &p02, &pT, &p11, &p12); + this->appendCubics(mode, p0, p01, p02, pT, maxFutureSubdivisions); + this->appendCubics(mode, pT, p11, p12, p3, maxFutureSubdivisions); +} + +void GrCCFillGeometry::conicTo(const SkPoint P[3], float w) { + SkASSERT(fBuildingContour); + SkASSERT(P[0] == fPoints.back()); + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + Sk2f p2 = Sk2f::Load(P+2); + + Sk2f tan0 = p1 - p0; + Sk2f tan1 = p2 - p1; + + if (!is_convex_curve_monotonic(p0, tan0, p2, tan1)) { + // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't + // necessary if we are only interested in a vector in the same *direction* as a given + // tangent line. Since the denominator scales dx and dy uniformly, we can throw it out + // completely after evaluating the derivative with the standard quotient rule. This leaves + // us with a simpler quadratic function that we use to find the midtangent. + float midT = find_midtangent(tan0, tan1, (w - 1) * (p2 - p0), + (p2 - p0) - 2*w*(p1 - p0), + w*(p1 - p0)); + // Use positive logic since NaN fails comparisons. (However midT should not be NaN since we + // cull near-linear conics above. And while w=0 is flat, it's not a line and has valid + // midtangents.) + if (!(midT > 0 && midT < 1)) { + // The conic is flat. Otherwise there would be a real midtangent inside T=0..1. + this->appendLine(p0, p2); + return; + } + + // Chop the conic at midtangent to produce two monotonic segments. + Sk4f p3d0 = Sk4f(p0[0], p0[1], 1, 0); + Sk4f p3d1 = Sk4f(p1[0], p1[1], 1, 0) * w; + Sk4f p3d2 = Sk4f(p2[0], p2[1], 1, 0); + Sk4f midT4 = midT; + + Sk4f p3d01 = lerp(p3d0, p3d1, midT4); + Sk4f p3d12 = lerp(p3d1, p3d2, midT4); + Sk4f p3d012 = lerp(p3d01, p3d12, midT4); + + Sk2f midpoint = Sk2f(p3d012[0], p3d012[1]) / p3d012[2]; + Sk2f ww = Sk2f(p3d01[2], p3d12[2]) * Sk2f(p3d012[2]).rsqrt(); + + this->appendMonotonicConic(p0, Sk2f(p3d01[0], p3d01[1]) / p3d01[2], midpoint, ww[0]); + this->appendMonotonicConic(midpoint, Sk2f(p3d12[0], p3d12[1]) / p3d12[2], p2, ww[1]); + return; + } + + this->appendMonotonicConic(p0, p1, p2, w); +} + +void GrCCFillGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + float w) { + SkASSERT(w >= 0); + + Sk2f base = p2 - p0; + Sk2f baseAbs = base.abs(); + float baseWidth = baseAbs[0] + baseAbs[1]; + + // Find the height of the curve. Max height always occurs at T=.5 for conics. + Sk2f d = (p1 - p0) * SkNx_shuffle<1,0>(base); + float h1 = std::abs(d[1] - d[0]); // Height of p1 above the base. + float ht = h1*w, hs = 1 + w; // Height of the conic = ht/hs. + + // i.e. (ht/hs <= baseWidth * kFlatnessThreshold). Use "<=" in case base == 0. + if (ht <= (baseWidth*hs) * kFlatnessThreshold) { + // We are flat. (See rationale in are_collinear.) + this->appendLine(p0, p2); + return; + } + + // i.e. (w > 1 && h1 - ht/hs < baseWidth). + if (w > 1 && h1*hs - ht < baseWidth*hs) { + // If we get within 1px of p1 when w > 1, we will pick up artifacts from the implicit + // function's reflection. Chop at max height (T=.5) and draw a triangle instead. + Sk2f p1w = p1*w; + Sk2f ab = p0 + p1w; + Sk2f bc = p1w + p2; + Sk2f highpoint = (ab + bc) / (2*(1 + w)); + this->appendLine(p0, highpoint); + this->appendLine(highpoint, p2); + return; + } + + SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1])); + SkASSERT((p0 != p2).anyTrue()); + p1.store(&fPoints.push_back()); + p2.store(&fPoints.push_back()); + fConicWeights.push_back(w); + fVerbs.push_back(Verb::kMonotonicConicTo); + ++fCurrContourTallies.fConics; +} + +GrCCFillGeometry::PrimitiveTallies GrCCFillGeometry::endContour() { + SkASSERT(fBuildingContour); + SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles); + + // The fTriangles field currently contains this contour's starting verb index. We can now + // use it to calculate the size of the contour's fan. + int fanSize = fVerbs.count() - fCurrContourTallies.fTriangles; + if (fPoints.back() == fCurrAnchorPoint) { + --fanSize; + fVerbs.push_back(Verb::kEndClosedContour); + } else { + fVerbs.push_back(Verb::kEndOpenContour); + } + + fCurrContourTallies.fTriangles = SkTMax(fanSize - 2, 0); + + SkDEBUGCODE(fBuildingContour = false); + return fCurrContourTallies; +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.h b/gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.h new file mode 100644 index 0000000000..d0a3c1e12d --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCFillGeometry.h @@ -0,0 +1,143 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGrCCFillGeometry_DEFINED +#define GrGrCCFillGeometry_DEFINED + +#include "include/core/SkPoint.h" +#include "include/private/SkNx.h" +#include "include/private/SkTArray.h" +#include "src/core/SkGeometry.h" + +/** + * This class chops device-space contours up into a series of segments that CCPR knows how to + * fill. (See GrCCFillGeometry::Verb.) + * + * NOTE: This must be done in device space, since an affine transformation can change whether a + * curve is monotonic. + */ +class GrCCFillGeometry { +public: + // These are the verbs that CCPR knows how to fill. If a path has any segments that don't map to + // this list, then they are chopped into smaller ones that do. A list of these comprise a + // compact representation of what can later be expanded into GPU instance data. + enum class Verb : uint8_t { + kBeginPath, // Included only for caller convenience. + kBeginContour, + kLineTo, + kMonotonicQuadraticTo, // Monotonic relative to the vector between its endpoints [P2 - P0]. + kMonotonicCubicTo, + kMonotonicConicTo, + kEndClosedContour, // endPt == startPt. + kEndOpenContour // endPt != startPt. + }; + + // These tallies track numbers of CCPR primitives that are required to draw a contour. + struct PrimitiveTallies { + int fTriangles; // Number of triangles in the contour's fan. + int fWeightedTriangles; // Triangles (from the tessellator) whose winding magnitude > 1. + int fQuadratics; + int fCubics; + int fConics; + + void operator+=(const PrimitiveTallies&); + PrimitiveTallies operator-(const PrimitiveTallies&) const; + bool operator==(const PrimitiveTallies&); + }; + + GrCCFillGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0) + : fPoints(numSkPoints * 3) // Reserve for a 3x expansion in points and verbs. + , fVerbs(numSkVerbs * 3) + , fConicWeights(numConicWeights * 3/2) {} + + const SkTArray& points() const { SkASSERT(!fBuildingContour); return fPoints; } + const SkTArray& verbs() const { SkASSERT(!fBuildingContour); return fVerbs; } + float getConicWeight(int idx) const { SkASSERT(!fBuildingContour); return fConicWeights[idx]; } + + void reset() { + SkASSERT(!fBuildingContour); + fPoints.reset(); + fVerbs.reset(); + } + + void beginPath(); + void beginContour(const SkPoint&); + void lineTo(const SkPoint P[2]); + void quadraticTo(const SkPoint[3]); + + // We pass through inflection points and loop intersections using a line and quadratic(s) + // respectively. 'inflectPad' and 'loopIntersectPad' specify how close (in pixels) cubic + // segments are allowed to get to these points. For normal rendering you will want to use the + // default values, but these can be overridden for testing purposes. + // + // NOTE: loops do appear to require two full pixels of padding around the intersection point. + // With just one pixel-width of pad, we start to see bad pixels. Ultimately this has a + // minimal effect on the total amount of segments produced. Most sections that pass + // through the loop intersection can be approximated with a single quadratic anyway, + // regardless of whether we are use one pixel of pad or two (1.622 avg. quads per loop + // intersection vs. 1.489 on the tiger). + void cubicTo(const SkPoint[4], float inflectPad = 0.55f, float loopIntersectPad = 2); + + void conicTo(const SkPoint[3], float w); + + PrimitiveTallies endContour(); // Returns the numbers of primitives needed to draw the contour. + +private: + inline void appendLine(const Sk2f& p0, const Sk2f& p1); + + inline void appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2); + inline void appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2); + + enum class AppendCubicMode : bool { + kLiteral, + kApproximate + }; + void appendCubics(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3, const float chops[], int numChops, float localT0 = 0, + float localT1 = 1); + void appendCubics(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3, int maxSubdivisions = 2); + void chopAndAppendCubicAtMidTangent(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2, const Sk2f& p3, const Sk2f& tan0, + const Sk2f& tan1, int maxFutureSubdivisions); + + void appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w); + + // Transient state used while building a contour. + SkPoint fCurrAnchorPoint; + PrimitiveTallies fCurrContourTallies; + SkCubicType fCurrCubicType; + SkDEBUGCODE(bool fBuildingContour = false); + + SkSTArray<128, SkPoint, true> fPoints; + SkSTArray<128, Verb, true> fVerbs; + SkSTArray<32, float, true> fConicWeights; +}; + +inline void GrCCFillGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) { + fTriangles += b.fTriangles; + fWeightedTriangles += b.fWeightedTriangles; + fQuadratics += b.fQuadratics; + fCubics += b.fCubics; + fConics += b.fConics; +} + +GrCCFillGeometry::PrimitiveTallies +inline GrCCFillGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const { + return {fTriangles - b.fTriangles, + fWeightedTriangles - b.fWeightedTriangles, + fQuadratics - b.fQuadratics, + fCubics - b.fCubics, + fConics - b.fConics}; +} + +inline bool GrCCFillGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) { + return fTriangles == b.fTriangles && fWeightedTriangles == b.fWeightedTriangles && + fQuadratics == b.fQuadratics && fCubics == b.fCubics && fConics == b.fConics; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCFiller.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCFiller.cpp new file mode 100644 index 0000000000..4553c62bac --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCFiller.cpp @@ -0,0 +1,583 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCFiller.h" + +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkPathPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrOpFlushState.h" +#include + +using TriPointInstance = GrCCCoverageProcessor::TriPointInstance; +using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance; + +GrCCFiller::GrCCFiller(Algorithm algorithm, int numPaths, int numSkPoints, int numSkVerbs, + int numConicWeights) + : fAlgorithm(algorithm) + , fGeometry(numSkPoints, numSkVerbs, numConicWeights) + , fPathInfos(numPaths) + , fScissorSubBatches(numPaths) + , fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} { + // Batches decide what to draw by looking where the previous one ended. Define initial batches + // that "end" at the beginning of the data. These will not be drawn, but will only be be read by + // the first actual batch. + fScissorSubBatches.push_back() = {PrimitiveTallies(), SkIRect::MakeEmpty()}; + fBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(), PrimitiveTallies()}; +} + +void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts, + GrScissorTest scissorTest, const SkIRect& clippedDevIBounds, + const SkIVector& devToAtlasOffset) { + SkASSERT(!fInstanceBuffer); // Can't call after prepareToDraw(). + SkASSERT(!path.isEmpty()); + + int currPathPointsIdx = fGeometry.points().count(); + int currPathVerbsIdx = fGeometry.verbs().count(); + PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies(); + + fGeometry.beginPath(); + + const float* conicWeights = SkPathPriv::ConicWeightData(path); + int ptsIdx = 0; + int conicWeightsIdx = 0; + bool insideContour = false; + + for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { + switch (verb) { + case SkPath::kMove_Verb: + if (insideContour) { + currPathPrimitiveCounts += fGeometry.endContour(); + } + fGeometry.beginContour(deviceSpacePts[ptsIdx]); + ++ptsIdx; + insideContour = true; + continue; + case SkPath::kClose_Verb: + if (insideContour) { + currPathPrimitiveCounts += fGeometry.endContour(); + } + insideContour = false; + continue; + case SkPath::kLine_Verb: + fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]); + ++ptsIdx; + continue; + case SkPath::kQuad_Verb: + fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]); + ptsIdx += 2; + continue; + case SkPath::kCubic_Verb: + fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]); + ptsIdx += 3; + continue; + case SkPath::kConic_Verb: + fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]); + ptsIdx += 2; + ++conicWeightsIdx; + continue; + default: + SK_ABORT("Unexpected path verb."); + } + } + SkASSERT(ptsIdx == path.countPoints()); + SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path)); + + if (insideContour) { + currPathPrimitiveCounts += fGeometry.endContour(); + } + + fPathInfos.emplace_back(scissorTest, devToAtlasOffset); + + // Tessellate fans from very large and/or simple paths, in order to reduce overdraw. + int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1; + int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N. + int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width(); + if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100. + fPathInfos.back().tessellateFan( + fAlgorithm, path, fGeometry, currPathVerbsIdx, currPathPointsIdx, clippedDevIBounds, + &currPathPrimitiveCounts); + } + + fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts; + + if (GrScissorTest::kEnabled == scissorTest) { + fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled], + clippedDevIBounds.makeOffset(devToAtlasOffset)}; + } +} + +void GrCCFiller::PathInfo::tessellateFan( + Algorithm algorithm, const SkPath& originalPath, const GrCCFillGeometry& geometry, + int verbsIdx, int ptsIdx, const SkIRect& clippedDevIBounds, + PrimitiveTallies* newTriangleCounts) { + using Verb = GrCCFillGeometry::Verb; + SkASSERT(-1 == fFanTessellationCount); + SkASSERT(!fFanTessellation); + + const SkTArray& verbs = geometry.verbs(); + const SkTArray& pts = geometry.points(); + + newTriangleCounts->fTriangles = + newTriangleCounts->fWeightedTriangles = 0; + + // Build an SkPath of the Redbook fan. + SkPath fan; + if (Algorithm::kCoverageCount == algorithm) { + // We use "winding" fill type right now because we are producing a coverage count, and must + // fill in every region that has non-zero wind. The path processor will convert coverage + // count to the appropriate fill type later. + fan.setFillType(SkPath::kWinding_FillType); + } else { + // When counting winding numbers in the stencil buffer, it works to use even/odd for the fan + // tessellation (where applicable). But we need to strip out inverse fill info because + // inverse-ness gets accounted for later on. + fan.setFillType(SkPath::ConvertToNonInverseFillType(originalPath.getFillType())); + } + SkASSERT(Verb::kBeginPath == verbs[verbsIdx]); + for (int i = verbsIdx + 1; i < verbs.count(); ++i) { + switch (verbs[i]) { + case Verb::kBeginPath: + SK_ABORT("Invalid GrCCFillGeometry"); + continue; + + case Verb::kBeginContour: + fan.moveTo(pts[ptsIdx++]); + continue; + + case Verb::kLineTo: + fan.lineTo(pts[ptsIdx++]); + continue; + + case Verb::kMonotonicQuadraticTo: + case Verb::kMonotonicConicTo: + fan.lineTo(pts[ptsIdx + 1]); + ptsIdx += 2; + continue; + + case Verb::kMonotonicCubicTo: + fan.lineTo(pts[ptsIdx + 2]); + ptsIdx += 3; + continue; + + case Verb::kEndClosedContour: + case Verb::kEndOpenContour: + fan.close(); + continue; + } + } + + GrTessellator::WindingVertex* vertices = nullptr; + SkASSERT(!fan.isInverseFillType()); + fFanTessellationCount = GrTessellator::PathToVertices( + fan, std::numeric_limits::infinity(), SkRect::Make(clippedDevIBounds), + &vertices); + if (fFanTessellationCount <= 0) { + SkASSERT(0 == fFanTessellationCount); + SkASSERT(nullptr == vertices); + return; + } + + SkASSERT(0 == fFanTessellationCount % 3); + for (int i = 0; i < fFanTessellationCount; i += 3) { + int tessWinding = vertices[i].fWinding; + SkASSERT(tessWinding == vertices[i + 1].fWinding); + SkASSERT(tessWinding == vertices[i + 2].fWinding); + + // Ensure this triangle's points actually wind in the same direction as tessWinding. + // CCPR shaders use the sign of wind to determine which direction to bloat, so even for + // "wound" triangles the winding sign and point ordering need to agree. + float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX; + float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY; + float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX; + float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY; + float wind = ax*by - ay*bx; + if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense. + std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos); + } + + int weight = abs(tessWinding); + SkASSERT(SkPath::kEvenOdd_FillType != fan.getFillType() || weight == 1); + if (weight > 1 && Algorithm::kCoverageCount == algorithm) { + ++newTriangleCounts->fWeightedTriangles; + } else { + newTriangleCounts->fTriangles += weight; + } + } + + fFanTessellation.reset(vertices); +} + +GrCCFiller::BatchID GrCCFiller::closeCurrentBatch() { + SkASSERT(!fInstanceBuffer); + SkASSERT(!fBatches.empty()); + + const auto& lastBatch = fBatches.back(); + int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx; + fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes); + + const auto& lastScissorSubBatch = fScissorSubBatches[lastBatch.fEndScissorSubBatchIdx - 1]; + PrimitiveTallies batchTotalCounts = fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled] - + lastBatch.fEndNonScissorIndices; + batchTotalCounts += fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled] - + lastScissorSubBatch.fEndPrimitiveIndices; + + // This will invalidate lastBatch. + fBatches.push_back() = { + fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled], + fScissorSubBatches.count(), + batchTotalCounts + }; + return fBatches.count() - 1; +} + +// Emits a contour's triangle fan. +// +// Classic Redbook fanning would be the triangles: [0 1 2], [0 2 3], ..., [0 n-2 n-1]. +// +// This function emits the triangle: [0 n/3 n*2/3], and then recurses on all three sides. The +// advantage to this approach is that for a convex-ish contour, it generates larger triangles. +// Classic fanning tends to generate long, skinny triangles, which are expensive to draw since they +// have a longer perimeter to rasterize and antialias. +// +// The indices array indexes the fan's points (think: glDrawElements), and must have at least log3 +// elements past the end for this method to use as scratch space. +// +// Returns the next triangle instance after the final one emitted. +static TriPointInstance* emit_recursive_fan( + const SkTArray& pts, SkTArray& indices, int firstIndex, + int indexCount, const Sk2f& devToAtlasOffset, TriPointInstance::Ordering ordering, + TriPointInstance out[]) { + if (indexCount < 3) { + return out; + } + + int32_t oneThirdCount = indexCount / 3; + int32_t twoThirdsCount = (2 * indexCount) / 3; + out++->set(pts[indices[firstIndex]], pts[indices[firstIndex + oneThirdCount]], + pts[indices[firstIndex + twoThirdsCount]], devToAtlasOffset, ordering); + + out = emit_recursive_fan( + pts, indices, firstIndex, oneThirdCount + 1, devToAtlasOffset, ordering, out); + out = emit_recursive_fan( + pts, indices, firstIndex + oneThirdCount, twoThirdsCount - oneThirdCount + 1, + devToAtlasOffset, ordering, out); + + int endIndex = firstIndex + indexCount; + int32_t oldValue = indices[endIndex]; + indices[endIndex] = indices[firstIndex]; + out = emit_recursive_fan( + pts, indices, firstIndex + twoThirdsCount, indexCount - twoThirdsCount + 1, + devToAtlasOffset, ordering, out); + indices[endIndex] = oldValue; + + return out; +} + +void GrCCFiller::emitTessellatedFan( + const GrTessellator::WindingVertex* vertices, int numVertices, const Sk2f& devToAtlasOffset, + TriPointInstance::Ordering ordering, TriPointInstance* triPointInstanceData, + QuadPointInstance* quadPointInstanceData, GrCCFillGeometry::PrimitiveTallies* indices) { + for (int i = 0; i < numVertices; i += 3) { + int weight = abs(vertices[i].fWinding); + SkASSERT(weight >= 1); + if (weight > 1 && Algorithm::kStencilWindingCount != fAlgorithm) { + quadPointInstanceData[indices->fWeightedTriangles++].setW( + vertices[i].fPos, vertices[i+1].fPos, vertices[i + 2].fPos, devToAtlasOffset, + static_cast(abs(vertices[i].fWinding))); + } else for (int j = 0; j < weight; ++j) { + // Unfortunately, there is not a way to increment stencil values by an amount larger + // than 1. Instead we draw the triangle 'weight' times. + triPointInstanceData[indices->fTriangles++].set( + vertices[i].fPos, vertices[i + 1].fPos, vertices[i + 2].fPos, devToAtlasOffset, + ordering); + } + } +} + +bool GrCCFiller::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) { + using Verb = GrCCFillGeometry::Verb; + SkASSERT(!fInstanceBuffer); + SkASSERT(fBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch(). + fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled]); + SkASSERT(fBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count()); + + auto triangleOrdering = (Algorithm::kCoverageCount == fAlgorithm) + ? TriPointInstance::Ordering::kXYTransposed + : TriPointInstance::Ordering::kXYInterleaved; + + // Here we build a single instance buffer to share with every internal batch. + // + // CCPR processs 3 different types of primitives: triangles, quadratics, cubics. Each primitive + // type is further divided into instances that require a scissor and those that don't. This + // leaves us with 3*2 = 6 independent instance arrays to build for the GPU. + // + // Rather than place each instance array in its own GPU buffer, we allocate a single + // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in + // our draw calls to direct the GPU to the applicable elements within a given array. + // + // We already know how big to make each of the 6 arrays from fTotalPrimitiveCounts, so layout is + // straightforward. Start with triangles and quadratics. They both view the instance buffer as + // an array of TriPointInstance[], so we can begin at zero and lay them out one after the other. + fBaseInstances[0].fTriangles = 0; + fBaseInstances[1].fTriangles = fBaseInstances[0].fTriangles + + fTotalPrimitiveCounts[0].fTriangles; + fBaseInstances[0].fQuadratics = fBaseInstances[1].fTriangles + + fTotalPrimitiveCounts[1].fTriangles; + fBaseInstances[1].fQuadratics = fBaseInstances[0].fQuadratics + + fTotalPrimitiveCounts[0].fQuadratics; + int triEndIdx = fBaseInstances[1].fQuadratics + fTotalPrimitiveCounts[1].fQuadratics; + + // Wound triangles and cubics both view the same instance buffer as an array of + // QuadPointInstance[]. So, reinterpreting the instance data as QuadPointInstance[], we start + // them on the first index that will not overwrite previous TriPointInstance data. + int quadBaseIdx = + GrSizeDivRoundUp(triEndIdx * sizeof(TriPointInstance), sizeof(QuadPointInstance)); + fBaseInstances[0].fWeightedTriangles = quadBaseIdx; + fBaseInstances[1].fWeightedTriangles = fBaseInstances[0].fWeightedTriangles + + fTotalPrimitiveCounts[0].fWeightedTriangles; + fBaseInstances[0].fCubics = fBaseInstances[1].fWeightedTriangles + + fTotalPrimitiveCounts[1].fWeightedTriangles; + fBaseInstances[1].fCubics = fBaseInstances[0].fCubics + fTotalPrimitiveCounts[0].fCubics; + fBaseInstances[0].fConics = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics; + fBaseInstances[1].fConics = fBaseInstances[0].fConics + fTotalPrimitiveCounts[0].fConics; + int quadEndIdx = fBaseInstances[1].fConics + fTotalPrimitiveCounts[1].fConics; + + fInstanceBuffer = + onFlushRP->makeBuffer(GrGpuBufferType::kVertex, quadEndIdx * sizeof(QuadPointInstance)); + if (!fInstanceBuffer) { + SkDebugf("WARNING: failed to allocate CCPR fill instance buffer.\n"); + return false; + } + + TriPointInstance* triPointInstanceData = static_cast(fInstanceBuffer->map()); + QuadPointInstance* quadPointInstanceData = + reinterpret_cast(triPointInstanceData); + SkASSERT(quadPointInstanceData); + + PathInfo* nextPathInfo = fPathInfos.begin(); + Sk2f devToAtlasOffset; + PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]}; + PrimitiveTallies* currIndices = nullptr; + SkSTArray<256, int32_t, true> currFan; + bool currFanIsTessellated = false; + + const SkTArray& pts = fGeometry.points(); + int ptsIdx = -1; + int nextConicWeightIdx = 0; + + // Expand the ccpr verbs into GPU instance buffers. + for (Verb verb : fGeometry.verbs()) { + switch (verb) { + case Verb::kBeginPath: + SkASSERT(currFan.empty()); + currIndices = &instanceIndices[(int)nextPathInfo->scissorTest()]; + devToAtlasOffset = Sk2f(static_cast(nextPathInfo->devToAtlasOffset().fX), + static_cast(nextPathInfo->devToAtlasOffset().fY)); + currFanIsTessellated = nextPathInfo->hasFanTessellation(); + if (currFanIsTessellated) { + this->emitTessellatedFan( + nextPathInfo->fanTessellation(), nextPathInfo->fanTessellationCount(), + devToAtlasOffset, triangleOrdering, triPointInstanceData, + quadPointInstanceData, currIndices); + } + ++nextPathInfo; + continue; + + case Verb::kBeginContour: + SkASSERT(currFan.empty()); + ++ptsIdx; + if (!currFanIsTessellated) { + currFan.push_back(ptsIdx); + } + continue; + + case Verb::kLineTo: + ++ptsIdx; + if (!currFanIsTessellated) { + SkASSERT(!currFan.empty()); + currFan.push_back(ptsIdx); + } + continue; + + case Verb::kMonotonicQuadraticTo: + triPointInstanceData[currIndices->fQuadratics++].set( + &pts[ptsIdx], devToAtlasOffset, TriPointInstance::Ordering::kXYTransposed); + ptsIdx += 2; + if (!currFanIsTessellated) { + SkASSERT(!currFan.empty()); + currFan.push_back(ptsIdx); + } + continue; + + case Verb::kMonotonicCubicTo: + quadPointInstanceData[currIndices->fCubics++].set( + &pts[ptsIdx], devToAtlasOffset[0], devToAtlasOffset[1]); + ptsIdx += 3; + if (!currFanIsTessellated) { + SkASSERT(!currFan.empty()); + currFan.push_back(ptsIdx); + } + continue; + + case Verb::kMonotonicConicTo: + quadPointInstanceData[currIndices->fConics++].setW( + &pts[ptsIdx], devToAtlasOffset, + fGeometry.getConicWeight(nextConicWeightIdx)); + ptsIdx += 2; + ++nextConicWeightIdx; + if (!currFanIsTessellated) { + SkASSERT(!currFan.empty()); + currFan.push_back(ptsIdx); + } + continue; + + case Verb::kEndClosedContour: // endPt == startPt. + if (!currFanIsTessellated) { + SkASSERT(!currFan.empty()); + currFan.pop_back(); + } + // fallthru. + case Verb::kEndOpenContour: // endPt != startPt. + SkASSERT(!currFanIsTessellated || currFan.empty()); + if (!currFanIsTessellated && currFan.count() >= 3) { + int fanSize = currFan.count(); + // Reserve space for emit_recursive_fan. Technically this can grow to + // fanSize + log3(fanSize), but we approximate with log2. + currFan.push_back_n(SkNextLog2(fanSize)); + SkDEBUGCODE(TriPointInstance* end =) emit_recursive_fan( + pts, currFan, 0, fanSize, devToAtlasOffset, triangleOrdering, + triPointInstanceData + currIndices->fTriangles); + currIndices->fTriangles += fanSize - 2; + SkASSERT(triPointInstanceData + currIndices->fTriangles == end); + } + currFan.reset(); + continue; + } + } + + fInstanceBuffer->unmap(); + + SkASSERT(nextPathInfo == fPathInfos.end()); + SkASSERT(ptsIdx == pts.count() - 1); + SkASSERT(instanceIndices[0].fTriangles == fBaseInstances[1].fTriangles); + SkASSERT(instanceIndices[1].fTriangles == fBaseInstances[0].fQuadratics); + SkASSERT(instanceIndices[0].fQuadratics == fBaseInstances[1].fQuadratics); + SkASSERT(instanceIndices[1].fQuadratics == triEndIdx); + SkASSERT(instanceIndices[0].fWeightedTriangles == fBaseInstances[1].fWeightedTriangles); + SkASSERT(instanceIndices[1].fWeightedTriangles == fBaseInstances[0].fCubics); + SkASSERT(instanceIndices[0].fCubics == fBaseInstances[1].fCubics); + SkASSERT(instanceIndices[1].fCubics == fBaseInstances[0].fConics); + SkASSERT(instanceIndices[0].fConics == fBaseInstances[1].fConics); + SkASSERT(instanceIndices[1].fConics == quadEndIdx); + + fMeshesScratchBuffer.reserve(fMaxMeshesPerDraw); + fScissorRectScratchBuffer.reserve(fMaxMeshesPerDraw); + + return true; +} + +void GrCCFiller::drawFills( + GrOpFlushState* flushState, GrCCCoverageProcessor* proc, const GrPipeline& pipeline, + BatchID batchID, const SkIRect& drawBounds) const { + using PrimitiveType = GrCCCoverageProcessor::PrimitiveType; + + SkASSERT(fInstanceBuffer); + + GrResourceProvider* rp = flushState->resourceProvider(); + const PrimitiveTallies& batchTotalCounts = fBatches[batchID].fTotalPrimitiveCounts; + + if (batchTotalCounts.fTriangles) { + proc->reset(PrimitiveType::kTriangles, rp); + this->drawPrimitives( + flushState, *proc, pipeline, batchID, &PrimitiveTallies::fTriangles, drawBounds); + } + + if (batchTotalCounts.fWeightedTriangles) { + SkASSERT(Algorithm::kStencilWindingCount != fAlgorithm); + proc->reset(PrimitiveType::kWeightedTriangles, rp); + this->drawPrimitives( + flushState, *proc, pipeline, batchID, &PrimitiveTallies::fWeightedTriangles, + drawBounds); + } + + if (batchTotalCounts.fQuadratics) { + proc->reset(PrimitiveType::kQuadratics, rp); + this->drawPrimitives( + flushState, *proc, pipeline, batchID, &PrimitiveTallies::fQuadratics, drawBounds); + } + + if (batchTotalCounts.fCubics) { + proc->reset(PrimitiveType::kCubics, rp); + this->drawPrimitives( + flushState, *proc, pipeline, batchID, &PrimitiveTallies::fCubics, drawBounds); + } + + if (batchTotalCounts.fConics) { + proc->reset(PrimitiveType::kConics, rp); + this->drawPrimitives( + flushState, *proc, pipeline, batchID, &PrimitiveTallies::fConics, drawBounds); + } +} + +void GrCCFiller::drawPrimitives( + GrOpFlushState* flushState, const GrCCCoverageProcessor& proc, const GrPipeline& pipeline, + BatchID batchID, int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const { + SkASSERT(pipeline.isScissorEnabled()); + + // Don't call reset(), as that also resets the reserve count. + fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count()); + fScissorRectScratchBuffer.pop_back_n(fScissorRectScratchBuffer.count()); + + SkASSERT(batchID > 0); + SkASSERT(batchID < fBatches.count()); + const Batch& previousBatch = fBatches[batchID - 1]; + const Batch& batch = fBatches[batchID]; + SkDEBUGCODE(int totalInstanceCount = 0); + + if (int instanceCount = batch.fEndNonScissorIndices.*instanceType - + previousBatch.fEndNonScissorIndices.*instanceType) { + SkASSERT(instanceCount > 0); + int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*instanceType + + previousBatch.fEndNonScissorIndices.*instanceType; + proc.appendMesh(fInstanceBuffer, instanceCount, baseInstance, &fMeshesScratchBuffer); + fScissorRectScratchBuffer.push_back().setXYWH(0, 0, drawBounds.width(), + drawBounds.height()); + SkDEBUGCODE(totalInstanceCount += instanceCount); + } + + SkASSERT(previousBatch.fEndScissorSubBatchIdx > 0); + SkASSERT(batch.fEndScissorSubBatchIdx <= fScissorSubBatches.count()); + int baseScissorInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*instanceType; + for (int i = previousBatch.fEndScissorSubBatchIdx; i < batch.fEndScissorSubBatchIdx; ++i) { + const ScissorSubBatch& previousSubBatch = fScissorSubBatches[i - 1]; + const ScissorSubBatch& scissorSubBatch = fScissorSubBatches[i]; + int startIndex = previousSubBatch.fEndPrimitiveIndices.*instanceType; + int instanceCount = scissorSubBatch.fEndPrimitiveIndices.*instanceType - startIndex; + if (!instanceCount) { + continue; + } + SkASSERT(instanceCount > 0); + proc.appendMesh(fInstanceBuffer, instanceCount, baseScissorInstance + startIndex, + &fMeshesScratchBuffer); + fScissorRectScratchBuffer.push_back() = scissorSubBatch.fScissor; + SkDEBUGCODE(totalInstanceCount += instanceCount); + } + + SkASSERT(fMeshesScratchBuffer.count() == fScissorRectScratchBuffer.count()); + SkASSERT(fMeshesScratchBuffer.count() <= fMaxMeshesPerDraw); + SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType); + + if (!fMeshesScratchBuffer.empty()) { + proc.draw(flushState, pipeline, fScissorRectScratchBuffer.begin(), + fMeshesScratchBuffer.begin(), fMeshesScratchBuffer.count(), + SkRect::Make(drawBounds)); + } +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCFiller.h b/gfx/skia/skia/src/gpu/ccpr/GrCCFiller.h new file mode 100644 index 0000000000..9a3b2e4d51 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCFiller.h @@ -0,0 +1,127 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPathParser_DEFINED +#define GrCCPathParser_DEFINED + +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "src/core/SkPathPriv.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrTessellator.h" +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" +#include "src/gpu/ccpr/GrCCFillGeometry.h" +#include "src/gpu/ops/GrDrawOp.h" + +class GrOnFlushResourceProvider; +class SkMatrix; +class SkPath; + +/** + * This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their + * coverage counts. + */ +class GrCCFiller { +public: + enum class Algorithm : bool { + kCoverageCount, + kStencilWindingCount + }; + + GrCCFiller(Algorithm, int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights); + + // Parses a device-space SkPath into the current batch, using the SkPath's original verbs and + // 'deviceSpacePts'. Accepts an optional post-device-space translate for placement in an atlas. + void parseDeviceSpaceFill(const SkPath&, const SkPoint* deviceSpacePts, GrScissorTest, + const SkIRect& clippedDevIBounds, const SkIVector& devToAtlasOffset); + + using BatchID = int; + + // Compiles the outstanding parsed paths into a batch, and returns an ID that can be used to + // draw their fills in the future. + BatchID closeCurrentBatch(); + + // Builds internal GPU buffers and prepares for calls to drawFills(). Caller must close the + // current batch before calling this method, and cannot parse new paths afer. + bool prepareToDraw(GrOnFlushResourceProvider*); + + // Called after prepareToDraw(). Draws the given batch of path fills. + void drawFills(GrOpFlushState*, GrCCCoverageProcessor*, const GrPipeline&, BatchID, + const SkIRect& drawBounds) const; + +private: + static constexpr int kNumScissorModes = 2; + using PrimitiveTallies = GrCCFillGeometry::PrimitiveTallies; + + // Every kBeginPath verb has a corresponding PathInfo entry. + class PathInfo { + public: + PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset) + : fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {} + + GrScissorTest scissorTest() const { return fScissorTest; } + const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; } + + // An empty tessellation fan is also valid; we use negative count to denote not tessellated. + bool hasFanTessellation() const { return fFanTessellationCount >= 0; } + int fanTessellationCount() const { + SkASSERT(this->hasFanTessellation()); + return fFanTessellationCount; + } + const GrTessellator::WindingVertex* fanTessellation() const { + SkASSERT(this->hasFanTessellation()); + return fFanTessellation.get(); + } + void tessellateFan( + Algorithm, const SkPath& originalPath, const GrCCFillGeometry&, int verbsIdx, + int ptsIdx, const SkIRect& clippedDevIBounds, PrimitiveTallies* newTriangleCounts); + + private: + GrScissorTest fScissorTest; + SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas. + int fFanTessellationCount = -1; + std::unique_ptr fFanTessellation; + }; + + // Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous + // Batch in the list. + struct Batch { + PrimitiveTallies fEndNonScissorIndices; + int fEndScissorSubBatchIdx; + PrimitiveTallies fTotalPrimitiveCounts; + }; + + // Defines a sub-batch that will be drawn with the given scissor rect. Start indices are deduced + // by looking at the previous ScissorSubBatch in the list. + struct ScissorSubBatch { + PrimitiveTallies fEndPrimitiveIndices; + SkIRect fScissor; + }; + + void emitTessellatedFan( + const GrTessellator::WindingVertex*, int numVertices, const Sk2f& devToAtlasOffset, + GrCCCoverageProcessor::TriPointInstance::Ordering, + GrCCCoverageProcessor::TriPointInstance*, GrCCCoverageProcessor::QuadPointInstance*, + GrCCFillGeometry::PrimitiveTallies*); + void drawPrimitives(GrOpFlushState*, const GrCCCoverageProcessor&, const GrPipeline&, BatchID, + int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const; + + const Algorithm fAlgorithm; + GrCCFillGeometry fGeometry; + SkSTArray<32, PathInfo, true> fPathInfos; + SkSTArray<32, Batch, true> fBatches; + SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches; + PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes]; + int fMaxMeshesPerDraw = 0; + + sk_sp fInstanceBuffer; + PrimitiveTallies fBaseInstances[kNumScissorModes]; + mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer; + mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.cpp new file mode 100644 index 0000000000..347ce3dca5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.cpp @@ -0,0 +1,442 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCPathCache.h" + +#include "include/private/SkNx.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrProxyProvider.h" + +static constexpr int kMaxKeyDataCountU32 = 256; // 1kB of uint32_t's. + +DECLARE_SKMESSAGEBUS_MESSAGE(sk_sp); + +static inline uint32_t next_path_cache_id() { + static std::atomic gNextID(1); + for (;;) { + uint32_t id = gNextID.fetch_add(+1, std::memory_order_acquire); + if (SK_InvalidUniqueID != id) { + return id; + } + } +} + +static inline bool SkShouldPostMessageToBus( + const sk_sp& key, uint32_t msgBusUniqueID) { + return key->pathCacheUniqueID() == msgBusUniqueID; +} + +// The maximum number of cache entries we allow in our own cache. +static constexpr int kMaxCacheCount = 1 << 16; + + +GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift) + : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} { + SkASSERT(!m.hasPerspective()); + Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY()); + Sk2f transFloor; +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // On Android framework we pre-round view matrix translates to integers for better caching. + transFloor = translate; +#else + transFloor = translate.floor(); + (translate - transFloor).store(fSubpixelTranslate); +#endif + shift->set((int)transFloor[0], (int)transFloor[1]); + SkASSERT((float)shift->fX == transFloor[0]); // Make sure transFloor had integer values. + SkASSERT((float)shift->fY == transFloor[1]); +} + +inline static bool fuzzy_equals(const GrCCPathCache::MaskTransform& a, + const GrCCPathCache::MaskTransform& b) { + if ((Sk4f::Load(a.fMatrix2x2) != Sk4f::Load(b.fMatrix2x2)).anyTrue()) { + return false; + } +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + if (((Sk2f::Load(a.fSubpixelTranslate) - + Sk2f::Load(b.fSubpixelTranslate)).abs() > 1.f/256).anyTrue()) { + return false; + } +#endif + return true; +} + +sk_sp GrCCPathCache::Key::Make(uint32_t pathCacheUniqueID, + int dataCountU32, const void* data) { + void* memory = ::operator new (sizeof(Key) + dataCountU32 * sizeof(uint32_t)); + sk_sp key(new (memory) Key(pathCacheUniqueID, dataCountU32)); + if (data) { + memcpy(key->data(), data, key->dataSizeInBytes()); + } + return key; +} + +void GrCCPathCache::Key::operator delete(void* p) { ::operator delete(p); } + +const uint32_t* GrCCPathCache::Key::data() const { + // The shape key is a variable-length footer to the entry allocation. + return reinterpret_cast(reinterpret_cast(this) + sizeof(Key)); +} + +uint32_t* GrCCPathCache::Key::data() { + // The shape key is a variable-length footer to the entry allocation. + return reinterpret_cast(reinterpret_cast(this) + sizeof(Key)); +} + +void GrCCPathCache::Key::onChange() { + // Our key's corresponding path was invalidated. Post a thread-safe eviction message. + SkMessageBus>::Post(sk_ref_sp(this)); +} + +GrCCPathCache::GrCCPathCache(uint32_t contextUniqueID) + : fContextUniqueID(contextUniqueID) + , fInvalidatedKeysInbox(next_path_cache_id()) + , fScratchKey(Key::Make(fInvalidatedKeysInbox.uniqueID(), kMaxKeyDataCountU32)) { +} + +GrCCPathCache::~GrCCPathCache() { + while (!fLRU.isEmpty()) { + this->evict(*fLRU.tail()->fCacheKey, fLRU.tail()); + } + SkASSERT(0 == fHashTable.count()); // Ensure the hash table and LRU list were coherent. + + // Now take all the atlas textures we just invalidated and purge them from the GrResourceCache. + // We just purge via message bus since we don't have any access to the resource cache right now. + for (sk_sp& proxy : fInvalidatedProxies) { + SkMessageBus::Post( + GrUniqueKeyInvalidatedMessage(proxy->getUniqueKey(), fContextUniqueID)); + } + for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) { + SkMessageBus::Post( + GrUniqueKeyInvalidatedMessage(key, fContextUniqueID)); + } +} + +namespace { + +// Produces a key that accounts both for a shape's path geometry, as well as any stroke/style. +class WriteKeyHelper { +public: + static constexpr int kStrokeWidthIdx = 0; + static constexpr int kStrokeMiterIdx = 1; + static constexpr int kStrokeCapJoinIdx = 2; + static constexpr int kShapeUnstyledKeyIdx = 3; + + WriteKeyHelper(const GrShape& shape) : fShapeUnstyledKeyCount(shape.unstyledKeySize()) {} + + // Returns the total number of uint32_t's to allocate for the key. + int allocCountU32() const { return kShapeUnstyledKeyIdx + fShapeUnstyledKeyCount; } + + // Writes the key data to out[]. + void write(const GrShape& shape, uint32_t* out) { + // Stroke key. + // We don't use GrStyle::WriteKey() because it does not account for hairlines. + // http://skbug.com/8273 + SkASSERT(!shape.style().hasPathEffect()); + const SkStrokeRec& stroke = shape.style().strokeRec(); + if (stroke.isFillStyle()) { + // Use a value for width that won't collide with a valid fp32 value >= 0. + out[kStrokeWidthIdx] = ~0; + out[kStrokeMiterIdx] = out[kStrokeCapJoinIdx] = 0; + } else { + float width = stroke.getWidth(), miterLimit = stroke.getMiter(); + memcpy(&out[kStrokeWidthIdx], &width, sizeof(float)); + memcpy(&out[kStrokeMiterIdx], &miterLimit, sizeof(float)); + out[kStrokeCapJoinIdx] = (stroke.getCap() << 16) | stroke.getJoin(); + GR_STATIC_ASSERT(sizeof(out[kStrokeWidthIdx]) == sizeof(float)); + } + + // Shape unstyled key. + shape.writeUnstyledKey(&out[kShapeUnstyledKeyIdx]); + } + +private: + int fShapeUnstyledKeyCount; +}; + +} + +GrCCPathCache::OnFlushEntryRef GrCCPathCache::find( + GrOnFlushResourceProvider* onFlushRP, const GrShape& shape, + const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix, SkIVector* maskShift) { + if (!shape.hasUnstyledKey()) { + return OnFlushEntryRef(); + } + + WriteKeyHelper writeKeyHelper(shape); + if (writeKeyHelper.allocCountU32() > kMaxKeyDataCountU32) { + return OnFlushEntryRef(); + } + + SkASSERT(fScratchKey->unique()); + fScratchKey->resetDataCountU32(writeKeyHelper.allocCountU32()); + writeKeyHelper.write(shape, fScratchKey->data()); + + MaskTransform m(viewMatrix, maskShift); + GrCCPathCacheEntry* entry = nullptr; + if (HashNode* node = fHashTable.find(*fScratchKey)) { + entry = node->entry(); + SkASSERT(fLRU.isInList(entry)); + + if (!fuzzy_equals(m, entry->fMaskTransform)) { + // The path was reused with an incompatible matrix. + if (entry->unique()) { + // This entry is unique: recycle it instead of deleting and malloc-ing a new one. + SkASSERT(0 == entry->fOnFlushRefCnt); // Because we are unique. + entry->fMaskTransform = m; + entry->fHitCount = 0; + entry->fHitRect = SkIRect::MakeEmpty(); + entry->releaseCachedAtlas(this); + } else { + this->evict(*fScratchKey); + entry = nullptr; + } + } + } + + if (!entry) { + if (fHashTable.count() >= kMaxCacheCount) { + SkDEBUGCODE(HashNode* node = fHashTable.find(*fLRU.tail()->fCacheKey)); + SkASSERT(node && node->entry() == fLRU.tail()); + this->evict(*fLRU.tail()->fCacheKey); // We've exceeded our limit. + } + + // Create a new entry in the cache. + sk_sp permanentKey = Key::Make(fInvalidatedKeysInbox.uniqueID(), + writeKeyHelper.allocCountU32(), fScratchKey->data()); + SkASSERT(*permanentKey == *fScratchKey); + SkASSERT(!fHashTable.find(*permanentKey)); + entry = fHashTable.set(HashNode(this, std::move(permanentKey), m, shape))->entry(); + + SkASSERT(fHashTable.count() <= kMaxCacheCount); + } else { + fLRU.remove(entry); // Will be re-added at head. + } + + SkDEBUGCODE(HashNode* node = fHashTable.find(*fScratchKey)); + SkASSERT(node && node->entry() == entry); + fLRU.addToHead(entry); + + if (0 == entry->fOnFlushRefCnt) { + // Only update the time stamp and hit count if we haven't seen this entry yet during the + // current flush. + entry->fTimestamp = this->quickPerFlushTimestamp(); + ++entry->fHitCount; + + if (entry->fCachedAtlas) { + SkASSERT(SkToBool(entry->fCachedAtlas->peekOnFlushRefCnt()) == + SkToBool(entry->fCachedAtlas->getOnFlushProxy())); + if (!entry->fCachedAtlas->getOnFlushProxy()) { + auto ct = GrCCAtlas::CoverageTypeToColorType(entry->fCachedAtlas->coverageType()); + if (sk_sp onFlushProxy = onFlushRP->findOrCreateProxyByUniqueKey( + entry->fCachedAtlas->textureKey(), ct, GrCCAtlas::kTextureOrigin, + GrSurfaceProxy::UseAllocator::kNo)) { + entry->fCachedAtlas->setOnFlushProxy(std::move(onFlushProxy)); + } + } + if (!entry->fCachedAtlas->getOnFlushProxy()) { + // Our atlas's backing texture got purged from the GrResourceCache. Release the + // cached atlas. + entry->releaseCachedAtlas(this); + } + } + } + entry->fHitRect.join(clippedDrawBounds.makeOffset(-*maskShift)); + SkASSERT(!entry->fCachedAtlas || entry->fCachedAtlas->getOnFlushProxy()); + return OnFlushEntryRef::OnFlushRef(entry); +} + +void GrCCPathCache::evict(const GrCCPathCache::Key& key, GrCCPathCacheEntry* entry) { + if (!entry) { + HashNode* node = fHashTable.find(key); + SkASSERT(node); + entry = node->entry(); + } + SkASSERT(*entry->fCacheKey == key); + SkASSERT(!entry->hasBeenEvicted()); + entry->fCacheKey->markShouldUnregisterFromPath(); // Unregister the path listener. + entry->releaseCachedAtlas(this); + fLRU.remove(entry); + fHashTable.remove(key); +} + +void GrCCPathCache::doPreFlushProcessing() { + this->evictInvalidatedCacheKeys(); + + // Mark the per-flush timestamp as needing to be updated with a newer clock reading. + fPerFlushTimestamp = GrStdSteadyClock::time_point::min(); +} + +void GrCCPathCache::purgeEntriesOlderThan(GrProxyProvider* proxyProvider, + const GrStdSteadyClock::time_point& purgeTime) { + this->evictInvalidatedCacheKeys(); + +#ifdef SK_DEBUG + auto lastTimestamp = (fLRU.isEmpty()) + ? GrStdSteadyClock::time_point::max() + : fLRU.tail()->fTimestamp; +#endif + + // Evict every entry from our local path cache whose timestamp is older than purgeTime. + while (!fLRU.isEmpty() && fLRU.tail()->fTimestamp < purgeTime) { +#ifdef SK_DEBUG + // Verify that fLRU is sorted by timestamp. + auto timestamp = fLRU.tail()->fTimestamp; + SkASSERT(timestamp >= lastTimestamp); + lastTimestamp = timestamp; +#endif + this->evict(*fLRU.tail()->fCacheKey); + } + + // Now take all the atlas textures we just invalidated and purge them from the GrResourceCache. + this->purgeInvalidatedAtlasTextures(proxyProvider); +} + +void GrCCPathCache::purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider* onFlushRP) { + for (sk_sp& proxy : fInvalidatedProxies) { + onFlushRP->removeUniqueKeyFromProxy(proxy.get()); + } + fInvalidatedProxies.reset(); + + for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) { + onFlushRP->processInvalidUniqueKey(key); + } + fInvalidatedProxyUniqueKeys.reset(); +} + +void GrCCPathCache::purgeInvalidatedAtlasTextures(GrProxyProvider* proxyProvider) { + for (sk_sp& proxy : fInvalidatedProxies) { + proxyProvider->removeUniqueKeyFromProxy(proxy.get()); + } + fInvalidatedProxies.reset(); + + for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) { + proxyProvider->processInvalidUniqueKey(key, nullptr, + GrProxyProvider::InvalidateGPUResource::kYes); + } + fInvalidatedProxyUniqueKeys.reset(); +} + +void GrCCPathCache::evictInvalidatedCacheKeys() { + SkTArray> invalidatedKeys; + fInvalidatedKeysInbox.poll(&invalidatedKeys); + for (const sk_sp& key : invalidatedKeys) { + bool isInCache = !key->shouldUnregisterFromPath(); // Gets set upon exiting the cache. + if (isInCache) { + this->evict(*key); + } + } +} + +GrCCPathCache::OnFlushEntryRef +GrCCPathCache::OnFlushEntryRef::OnFlushRef(GrCCPathCacheEntry* entry) { + entry->ref(); + ++entry->fOnFlushRefCnt; + if (entry->fCachedAtlas) { + entry->fCachedAtlas->incrOnFlushRefCnt(); + } + return OnFlushEntryRef(entry); +} + +GrCCPathCache::OnFlushEntryRef::~OnFlushEntryRef() { + if (!fEntry) { + return; + } + --fEntry->fOnFlushRefCnt; + SkASSERT(fEntry->fOnFlushRefCnt >= 0); + if (fEntry->fCachedAtlas) { + fEntry->fCachedAtlas->decrOnFlushRefCnt(); + } + fEntry->unref(); +} + + +void GrCCPathCacheEntry::setCoverageCountAtlas( + GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas, const SkIVector& atlasOffset, + const GrOctoBounds& octoBounds, const SkIRect& devIBounds, const SkIVector& maskShift) { + SkASSERT(fOnFlushRefCnt > 0); + SkASSERT(!fCachedAtlas); // Otherwise we would need to call releaseCachedAtlas(). + + if (this->hasBeenEvicted()) { + // This entry will never be found in the path cache again. Don't bother trying to save an + // atlas texture for it in the GrResourceCache. + return; + } + + fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP); + fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt); + fCachedAtlas->addPathPixels(devIBounds.height() * devIBounds.width()); + + fAtlasOffset = atlasOffset + maskShift; + + fOctoBounds.setOffset(octoBounds, -maskShift.fX, -maskShift.fY); + fDevIBounds = devIBounds.makeOffset(-maskShift); +} + +GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::upgradeToLiteralCoverageAtlas( + GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas, + const SkIVector& newAtlasOffset) { + SkASSERT(!this->hasBeenEvicted()); + SkASSERT(fOnFlushRefCnt > 0); + SkASSERT(fCachedAtlas); + SkASSERT(GrCCAtlas::CoverageType::kA8_LiteralCoverage != fCachedAtlas->coverageType()); + + ReleaseAtlasResult releaseAtlasResult = this->releaseCachedAtlas(pathCache); + + fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP); + fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt); + fCachedAtlas->addPathPixels(this->height() * this->width()); + + fAtlasOffset = newAtlasOffset; + return releaseAtlasResult; +} + +GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::releaseCachedAtlas( + GrCCPathCache* pathCache) { + ReleaseAtlasResult result = ReleaseAtlasResult::kNone; + if (fCachedAtlas) { + result = fCachedAtlas->invalidatePathPixels(pathCache, this->height() * this->width()); + if (fOnFlushRefCnt) { + SkASSERT(fOnFlushRefCnt > 0); + fCachedAtlas->decrOnFlushRefCnt(fOnFlushRefCnt); + } + fCachedAtlas = nullptr; + } + return result; +} + +GrCCPathCacheEntry::ReleaseAtlasResult GrCCCachedAtlas::invalidatePathPixels( + GrCCPathCache* pathCache, int numPixels) { + // Mark the pixels invalid in the cached atlas texture. + fNumInvalidatedPathPixels += numPixels; + SkASSERT(fNumInvalidatedPathPixels <= fNumPathPixels); + if (!fIsInvalidatedFromResourceCache && fNumInvalidatedPathPixels >= fNumPathPixels / 2) { + // Too many invalidated pixels: purge the atlas texture from the resource cache. + if (fOnFlushProxy) { + // Don't clear (or std::move) fOnFlushProxy. Other path cache entries might still have a + // reference on this atlas and expect to use our proxy during the current flush. + // fOnFlushProxy will be cleared once fOnFlushRefCnt decrements to zero. + pathCache->fInvalidatedProxies.push_back(fOnFlushProxy); + } else { + pathCache->fInvalidatedProxyUniqueKeys.push_back(fTextureKey); + } + fIsInvalidatedFromResourceCache = true; + return ReleaseAtlasResult::kDidInvalidateFromCache; + } + return ReleaseAtlasResult::kNone; +} + +void GrCCCachedAtlas::decrOnFlushRefCnt(int count) const { + SkASSERT(count > 0); + fOnFlushRefCnt -= count; + SkASSERT(fOnFlushRefCnt >= 0); + if (0 == fOnFlushRefCnt) { + // Don't hold the actual proxy past the end of the current flush. + SkASSERT(fOnFlushProxy); + fOnFlushProxy = nullptr; + } +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.h b/gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.h new file mode 100644 index 0000000000..a49d7585be --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPathCache.h @@ -0,0 +1,371 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPathCache_DEFINED +#define GrCCPathCache_DEFINED + +#include "include/private/SkTHash.h" +#include "src/core/SkExchange.h" +#include "src/core/SkTInternalLList.h" +#include "src/gpu/ccpr/GrCCAtlas.h" +#include "src/gpu/ccpr/GrCCPathProcessor.h" +#include "src/gpu/geometry/GrShape.h" + +class GrCCPathCacheEntry; +class GrShape; + +/** + * This class implements an LRU cache that maps from GrShape to GrCCPathCacheEntry objects. Shapes + * are only given one entry in the cache, so any time they are accessed with a different matrix, the + * old entry gets evicted. + */ +class GrCCPathCache { +public: + GrCCPathCache(uint32_t contextUniqueID); + ~GrCCPathCache(); + + class Key : public SkPathRef::GenIDChangeListener { + public: + static sk_sp Make(uint32_t pathCacheUniqueID, int dataCountU32, + const void* data = nullptr); + + uint32_t pathCacheUniqueID() const { return fPathCacheUniqueID; } + + int dataSizeInBytes() const { return fDataSizeInBytes; } + const uint32_t* data() const; + + void resetDataCountU32(int dataCountU32) { + SkASSERT(dataCountU32 <= fDataReserveCountU32); + fDataSizeInBytes = dataCountU32 * sizeof(uint32_t); + } + uint32_t* data(); + + bool operator==(const Key& that) const { + return fDataSizeInBytes == that.fDataSizeInBytes && + !memcmp(this->data(), that.data(), fDataSizeInBytes); + } + + // Called when our corresponding path is modified or deleted. Not threadsafe. + void onChange() override; + + // TODO(b/30449950): use sized delete once P0722R3 is available + static void operator delete(void* p); + + private: + Key(uint32_t pathCacheUniqueID, int dataCountU32) + : fPathCacheUniqueID(pathCacheUniqueID) + , fDataSizeInBytes(dataCountU32 * sizeof(uint32_t)) + SkDEBUGCODE(, fDataReserveCountU32(dataCountU32)) { + SkASSERT(SK_InvalidUniqueID != fPathCacheUniqueID); + } + + const uint32_t fPathCacheUniqueID; + int fDataSizeInBytes; + SkDEBUGCODE(const int fDataReserveCountU32); + // The GrShape's unstyled key is stored as a variable-length footer to this class. GetKey + // provides access to it. + }; + + // Stores the components of a transformation that affect a path mask (i.e. everything but + // integer translation). During construction, any integer portions of the matrix's translate are + // shaved off and returned to the caller. The caller is responsible for those integer shifts. + struct MaskTransform { + MaskTransform(const SkMatrix& m, SkIVector* shift); + float fMatrix2x2[4]; +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + // Except on AOSP, cache hits must have matching subpixel portions of their view matrix. + // On AOSP we follow after HWUI and ignore the subpixel translate. + float fSubpixelTranslate[2]; +#endif + }; + + // Represents a ref on a GrCCPathCacheEntry that should only be used during the current flush. + class OnFlushEntryRef : SkNoncopyable { + public: + static OnFlushEntryRef OnFlushRef(GrCCPathCacheEntry*); + OnFlushEntryRef() = default; + OnFlushEntryRef(OnFlushEntryRef&& ref) : fEntry(skstd::exchange(ref.fEntry, nullptr)) {} + ~OnFlushEntryRef(); + + GrCCPathCacheEntry* get() const { return fEntry; } + GrCCPathCacheEntry* operator->() const { return fEntry; } + GrCCPathCacheEntry& operator*() const { return *fEntry; } + explicit operator bool() const { return fEntry; } + void operator=(OnFlushEntryRef&& ref) { fEntry = skstd::exchange(ref.fEntry, nullptr); } + + private: + OnFlushEntryRef(GrCCPathCacheEntry* entry) : fEntry(entry) {} + GrCCPathCacheEntry* fEntry = nullptr; + }; + + // Finds an entry in the cache that matches the given shape and transformation matrix. + // 'maskShift' is filled with an integer post-translate that the caller must apply when drawing + // the entry's mask to the device. + // + // NOTE: Shapes are only given one entry, so any time they are accessed with a new + // transformation, the old entry gets evicted. + OnFlushEntryRef find(GrOnFlushResourceProvider*, const GrShape&, + const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix, + SkIVector* maskShift); + + void doPreFlushProcessing(); + + void purgeEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point& purgeTime); + + // As we evict entries from our local path cache, we accumulate a list of invalidated atlas + // textures. This call purges the invalidated atlas textures from the mainline GrResourceCache. + // This call is available with two different "provider" objects, to accomodate whatever might + // be available at the callsite. + void purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider*); + void purgeInvalidatedAtlasTextures(GrProxyProvider*); + +private: + // This is a special ref ptr for GrCCPathCacheEntry, used by the hash table. It provides static + // methods for SkTHash, and can only be moved. This guarantees the hash table holds exactly one + // reference for each entry. Also, when a HashNode goes out of scope, that means it is exiting + // the hash table. We take that opportunity to remove it from the LRU list and do some cleanup. + class HashNode : SkNoncopyable { + public: + static const Key& GetKey(const HashNode&); + inline static uint32_t Hash(const Key& key) { + return GrResourceKeyHash(key.data(), key.dataSizeInBytes()); + } + + HashNode() = default; + HashNode(GrCCPathCache*, sk_sp, const MaskTransform&, const GrShape&); + HashNode(HashNode&& node) + : fPathCache(node.fPathCache), fEntry(std::move(node.fEntry)) { + SkASSERT(!node.fEntry); + } + + ~HashNode(); + + void operator=(HashNode&& node); + + GrCCPathCacheEntry* entry() const { return fEntry.get(); } + + private: + GrCCPathCache* fPathCache = nullptr; + sk_sp fEntry; + }; + + GrStdSteadyClock::time_point quickPerFlushTimestamp() { + // time_point::min() means it's time to update fPerFlushTimestamp with a newer clock read. + if (GrStdSteadyClock::time_point::min() == fPerFlushTimestamp) { + fPerFlushTimestamp = GrStdSteadyClock::now(); + } + return fPerFlushTimestamp; + } + + void evict(const GrCCPathCache::Key&, GrCCPathCacheEntry* = nullptr); + + // Evicts all the cache entries whose keys have been queued up in fInvalidatedKeysInbox via + // SkPath listeners. + void evictInvalidatedCacheKeys(); + + const uint32_t fContextUniqueID; + + SkTHashTable fHashTable; + SkTInternalLList fLRU; + SkMessageBus>::Inbox fInvalidatedKeysInbox; + sk_sp fScratchKey; // Reused for creating a temporary key in the find() method. + + // We only read the clock once per flush, and cache it in this variable. This prevents us from + // excessive clock reads for cache timestamps that might degrade performance. + GrStdSteadyClock::time_point fPerFlushTimestamp = GrStdSteadyClock::time_point::min(); + + // As we evict entries from our local path cache, we accumulate lists of invalidated atlas + // textures in these two members. We hold these until we purge them from the GrResourceCache + // (e.g. via purgeInvalidatedAtlasTextures().) + SkSTArray<4, sk_sp> fInvalidatedProxies; + SkSTArray<4, GrUniqueKey> fInvalidatedProxyUniqueKeys; + + friend class GrCCCachedAtlas; // To append to fInvalidatedProxies, fInvalidatedProxyUniqueKeys. + +public: + const SkTHashTable& testingOnly_getHashTable() const; + const SkTInternalLList& testingOnly_getLRU() const; +}; + +/** + * This class stores all the data necessary to draw a specific path + matrix combination from their + * corresponding cached atlas. + */ +class GrCCPathCacheEntry : public GrNonAtomicRef { +public: + SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCPathCacheEntry); + + ~GrCCPathCacheEntry() { + SkASSERT(this->hasBeenEvicted()); // Should have called GrCCPathCache::evict(). + SkASSERT(!fCachedAtlas); + SkASSERT(0 == fOnFlushRefCnt); + } + + const GrCCPathCache::Key& cacheKey() const { SkASSERT(fCacheKey); return *fCacheKey; } + + // The number of flushes during which this specific entry (path + matrix combination) has been + // pulled from the path cache. If a path is pulled from the cache more than once in a single + // flush, the hit count is only incremented once. + // + // If the entry did not previously exist, its hit count will be 1. + int hitCount() const { return fHitCount; } + + // The accumulative region of the path that has been drawn during the lifetime of this cache + // entry (as defined by the 'clippedDrawBounds' parameter for GrCCPathCache::find). + const SkIRect& hitRect() const { return fHitRect; } + + const GrCCCachedAtlas* cachedAtlas() const { return fCachedAtlas.get(); } + + const SkIRect& devIBounds() const { return fDevIBounds; } + int width() const { return fDevIBounds.width(); } + int height() const { return fDevIBounds.height(); } + + enum class ReleaseAtlasResult : bool { + kNone, + kDidInvalidateFromCache + }; + + // Called once our path has been rendered into the mainline CCPR (fp16, coverage count) atlas. + // The caller will stash this atlas texture away after drawing, and during the next flush, + // recover it and attempt to copy any paths that got reused into permanent 8-bit atlases. + void setCoverageCountAtlas( + GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& atlasOffset, + const GrOctoBounds& octoBounds, const SkIRect& devIBounds, const SkIVector& maskShift); + + // Called once our path mask has been copied into a permanent, 8-bit atlas. This method points + // the entry at the new atlas and updates the GrCCCCachedAtlas data. + ReleaseAtlasResult upgradeToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*, + GrCCAtlas*, const SkIVector& newAtlasOffset); + +private: + using MaskTransform = GrCCPathCache::MaskTransform; + + GrCCPathCacheEntry(sk_sp cacheKey, const MaskTransform& maskTransform) + : fCacheKey(std::move(cacheKey)), fMaskTransform(maskTransform) { + } + + bool hasBeenEvicted() const { return fCacheKey->shouldUnregisterFromPath(); } + + // Resets this entry back to not having an atlas, and purges its previous atlas texture from the + // resource cache if needed. + ReleaseAtlasResult releaseCachedAtlas(GrCCPathCache*); + + sk_sp fCacheKey; + GrStdSteadyClock::time_point fTimestamp; + int fHitCount = 0; + SkIRect fHitRect = SkIRect::MakeEmpty(); + + sk_sp fCachedAtlas; + SkIVector fAtlasOffset; + + MaskTransform fMaskTransform; + GrOctoBounds fOctoBounds; + SkIRect fDevIBounds; + + int fOnFlushRefCnt = 0; + + friend class GrCCPathCache; + friend void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry&, const SkIVector&, + uint64_t color, GrFillRule); // To access data. + +public: + int testingOnly_peekOnFlushRefCnt() const; +}; + +/** + * Encapsulates the data for an atlas whose texture is stored in the mainline GrResourceCache. Many + * instances of GrCCPathCacheEntry will reference the same GrCCCachedAtlas. + * + * We use this object to track the percentage of the original atlas pixels that could still ever + * potentially be reused (i.e., those which still represent an extant path). When the percentage + * of useful pixels drops below 50%, we purge the entire texture from the resource cache. + * + * This object also holds a ref on the atlas's actual texture proxy during flush. When + * fOnFlushRefCnt decrements back down to zero, we release fOnFlushProxy and reset it back to null. + */ +class GrCCCachedAtlas : public GrNonAtomicRef { +public: + using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult; + + GrCCCachedAtlas(GrCCAtlas::CoverageType type, const GrUniqueKey& textureKey, + sk_sp onFlushProxy) + : fCoverageType(type) + , fTextureKey(textureKey) + , fOnFlushProxy(std::move(onFlushProxy)) {} + + ~GrCCCachedAtlas() { + SkASSERT(!fOnFlushProxy); + SkASSERT(!fOnFlushRefCnt); + } + + GrCCAtlas::CoverageType coverageType() const { return fCoverageType; } + const GrUniqueKey& textureKey() const { return fTextureKey; } + + GrTextureProxy* getOnFlushProxy() const { return fOnFlushProxy.get(); } + + void setOnFlushProxy(sk_sp proxy) { + SkASSERT(!fOnFlushProxy); + fOnFlushProxy = std::move(proxy); + } + + void addPathPixels(int numPixels) { fNumPathPixels += numPixels; } + ReleaseAtlasResult invalidatePathPixels(GrCCPathCache*, int numPixels); + + int peekOnFlushRefCnt() const { return fOnFlushRefCnt; } + void incrOnFlushRefCnt(int count = 1) const { + SkASSERT(count > 0); + SkASSERT(fOnFlushProxy); + fOnFlushRefCnt += count; + } + void decrOnFlushRefCnt(int count = 1) const; + +private: + const GrCCAtlas::CoverageType fCoverageType; + const GrUniqueKey fTextureKey; + + int fNumPathPixels = 0; + int fNumInvalidatedPathPixels = 0; + bool fIsInvalidatedFromResourceCache = false; + + mutable sk_sp fOnFlushProxy; + mutable int fOnFlushRefCnt = 0; + +public: + int testingOnly_peekOnFlushRefCnt() const; +}; + + +inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp key, + const MaskTransform& m, const GrShape& shape) + : fPathCache(pathCache) + , fEntry(new GrCCPathCacheEntry(key, m)) { + SkASSERT(shape.hasUnstyledKey()); + shape.addGenIDChangeListener(std::move(key)); +} + +inline const GrCCPathCache::Key& GrCCPathCache::HashNode::GetKey( + const GrCCPathCache::HashNode& node) { + return *node.entry()->fCacheKey; +} + +inline GrCCPathCache::HashNode::~HashNode() { + SkASSERT(!fEntry || fEntry->hasBeenEvicted()); // Should have called GrCCPathCache::evict(). +} + +inline void GrCCPathCache::HashNode::operator=(HashNode&& node) { + SkASSERT(!fEntry || fEntry->hasBeenEvicted()); // Should have called GrCCPathCache::evict(). + fEntry = skstd::exchange(node.fEntry, nullptr); +} + +inline void GrCCPathProcessor::Instance::set( + const GrCCPathCacheEntry& entry, const SkIVector& shift, uint64_t color, + GrFillRule fillRule) { + float dx = (float)shift.fX, dy = (float)shift.fY; + this->set(entry.fOctoBounds.makeOffset(dx, dy), entry.fAtlasOffset - shift, color, fillRule); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.cpp new file mode 100644 index 0000000000..9fba60df05 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCPathProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/ccpr/GrCCPerFlushResources.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +// Paths are drawn as octagons. Each point on the octagon is the intersection of two lines: one edge +// from the path's bounding box and one edge from its 45-degree bounding box. The selectors +// below indicate one corner from the bounding box, paired with a corner from the 45-degree bounding +// box. The octagon vertex is the point that lies between these two corners, found by intersecting +// their edges. +static constexpr float kOctoEdgeNorms[8*4] = { + // bbox // bbox45 + 0,0, 0,0, + 0,0, 1,0, + 1,0, 1,0, + 1,0, 1,1, + 1,1, 1,1, + 1,1, 0,1, + 0,1, 0,1, + 0,1, 0,0, +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey); + +sk_sp GrCCPathProcessor::FindVertexBuffer(GrOnFlushResourceProvider* onFlushRP) { + GR_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey); + return onFlushRP->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, sizeof(kOctoEdgeNorms), + kOctoEdgeNorms, gVertexBufferKey); +} + +static constexpr uint16_t kRestartStrip = 0xffff; + +static constexpr uint16_t kOctoIndicesAsStrips[] = { + 3, 4, 2, 0, 1, kRestartStrip, // First half. + 7, 0, 6, 4, 5 // Second half. +}; + +static constexpr uint16_t kOctoIndicesAsTris[] = { + // First half. + 3, 4, 2, + 4, 0, 2, + 2, 0, 1, + + // Second half. + 7, 0, 6, + 0, 4, 6, + 6, 4, 5, +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey); + +constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kInstanceAttribs[]; +constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kCornersAttrib; + +sk_sp GrCCPathProcessor::FindIndexBuffer(GrOnFlushResourceProvider* onFlushRP) { + GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey); + if (onFlushRP->caps()->usePrimitiveRestart()) { + return onFlushRP->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, + sizeof(kOctoIndicesAsStrips), kOctoIndicesAsStrips, + gIndexBufferKey); + } else { + return onFlushRP->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, + sizeof(kOctoIndicesAsTris), kOctoIndicesAsTris, + gIndexBufferKey); + } +} + +GrCCPathProcessor::GrCCPathProcessor(CoverageMode coverageMode, const GrTexture* atlasTexture, + const GrSwizzle& swizzle, GrSurfaceOrigin atlasOrigin, + const SkMatrix& viewMatrixIfUsingLocalCoords) + : INHERITED(kGrCCPathProcessor_ClassID) + , fCoverageMode(coverageMode) + , fAtlasAccess(atlasTexture->texturePriv().textureType(), GrSamplerState::ClampNearest(), + swizzle) + , fAtlasSize(SkISize::Make(atlasTexture->width(), atlasTexture->height())) + , fAtlasOrigin(atlasOrigin) { + // TODO: Can we just assert that atlas has GrCCAtlas::kTextureOrigin and remove fAtlasOrigin? + this->setInstanceAttributes(kInstanceAttribs, SK_ARRAY_COUNT(kInstanceAttribs)); + SkASSERT(this->instanceStride() == sizeof(Instance)); + + this->setVertexAttributes(&kCornersAttrib, 1); + this->setTextureSamplerCnt(1); + + if (!viewMatrixIfUsingLocalCoords.invert(&fLocalMatrix)) { + fLocalMatrix.setIdentity(); + } +} + +class GrCCPathProcessor::Impl : public GrGLSLGeometryProcessor { +public: + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override; + +private: + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + const auto& proc = primProc.cast(); + pdman.set2f( + fAtlasAdjustUniform, 1.0f / proc.fAtlasSize.fWidth, 1.0f / proc.fAtlasSize.fHeight); + this->setTransformDataHelper(proc.fLocalMatrix, pdman, &transformIter); + } + + GrGLSLUniformHandler::UniformHandle fAtlasAdjustUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +GrGLSLPrimitiveProcessor* GrCCPathProcessor::createGLSLInstance(const GrShaderCaps&) const { + return new Impl(); +} + +void GrCCPathProcessor::drawPaths(GrOpFlushState* flushState, const GrPipeline& pipeline, + const GrPipeline::FixedDynamicState* fixedDynamicState, + const GrCCPerFlushResources& resources, int baseInstance, + int endInstance, const SkRect& bounds) const { + const GrCaps& caps = flushState->caps(); + GrPrimitiveType primitiveType = caps.usePrimitiveRestart() + ? GrPrimitiveType::kTriangleStrip + : GrPrimitiveType::kTriangles; + int numIndicesPerInstance = caps.usePrimitiveRestart() + ? SK_ARRAY_COUNT(kOctoIndicesAsStrips) + : SK_ARRAY_COUNT(kOctoIndicesAsTris); + GrMesh mesh(primitiveType); + auto enablePrimitiveRestart = GrPrimitiveRestart(flushState->caps().usePrimitiveRestart()); + + mesh.setIndexedInstanced(resources.refIndexBuffer(), numIndicesPerInstance, + resources.refInstanceBuffer(), endInstance - baseInstance, + baseInstance, enablePrimitiveRestart); + mesh.setVertexData(resources.refVertexBuffer()); + + GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(), + flushState->drawOpArgs().origin(), + pipeline, + *this, + fixedDynamicState, + nullptr, 0); + + flushState->opsRenderPass()->draw(programInfo, &mesh, 1, bounds); +} + +void GrCCPathProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + using Interpolation = GrGLSLVaryingHandler::Interpolation; + + const GrCCPathProcessor& proc = args.fGP.cast(); + GrGLSLUniformHandler* uniHandler = args.fUniformHandler; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + bool isCoverageCount = (CoverageMode::kCoverageCount == proc.fCoverageMode); + + const char* atlasAdjust; + fAtlasAdjustUniform = uniHandler->addUniform( + kVertex_GrShaderFlag, kFloat2_GrSLType, "atlas_adjust", &atlasAdjust); + + varyingHandler->emitAttributes(proc); + + GrGLSLVarying texcoord((isCoverageCount) ? kFloat3_GrSLType : kFloat2_GrSLType); + varyingHandler->addVarying("texcoord", &texcoord); + + GrGLSLVarying color(kHalf4_GrSLType); + varyingHandler->addPassThroughAttribute( + kInstanceAttribs[kColorAttribIdx], args.fOutputColor, Interpolation::kCanBeFlat); + + // The vertex shader bloats and intersects the devBounds and devBounds45 rectangles, in order to + // find an octagon that circumscribes the (bloated) path. + GrGLSLVertexBuilder* v = args.fVertBuilder; + + // Are we clockwise? (Positive wind => nonzero fill rule.) + // Or counter-clockwise? (negative wind => even/odd fill rule.) + v->codeAppendf("float wind = sign(devbounds.z - devbounds.x);"); + + // Find our reference corner from the device-space bounding box. + v->codeAppendf("float2 refpt = mix(devbounds.xy, devbounds.zw, corners.xy);"); + + // Find our reference corner from the 45-degree bounding box. + v->codeAppendf("float2 refpt45 = mix(devbounds45.xy, devbounds45.zw, corners.zw);"); + // Transform back to device space. + v->codeAppendf("refpt45 *= float2x2(+1, +1, -wind, +wind) * .5;"); + + // Find the normals to each edge, then intersect them to find our octagon vertex. + v->codeAppendf("float2x2 N = float2x2(" + "corners.z + corners.w - 1, corners.w - corners.z, " + "corners.xy*2 - 1);"); + v->codeAppendf("N = float2x2(wind, 0, 0, 1) * N;"); + v->codeAppendf("float2 K = float2(dot(N[0], refpt), dot(N[1], refpt45));"); + v->codeAppendf("float2 octocoord = K * inverse(N);"); + + // Round the octagon out to ensure we rasterize every pixel the path might touch. (Positive + // bloatdir means we should take the "ceil" and negative means to take the "floor".) + // + // NOTE: If we were just drawing a rect, ceil/floor would be enough. But since there are also + // diagonals in the octagon that cross through pixel centers, we need to outset by another + // quarter px to ensure those pixels get rasterized. + v->codeAppendf("float2 bloatdir = (0 != N[0].x) " + "? float2(N[0].x, N[1].y)" + ": float2(N[1].x, N[0].y);"); + v->codeAppendf("octocoord = (ceil(octocoord * bloatdir - 1e-4) + 0.25) * bloatdir;"); + v->codeAppendf("float2 atlascoord = octocoord + float2(dev_to_atlas_offset);"); + + // Convert to atlas coordinates in order to do our texture lookup. + if (kTopLeft_GrSurfaceOrigin == proc.fAtlasOrigin) { + v->codeAppendf("%s.xy = atlascoord * %s;", texcoord.vsOut(), atlasAdjust); + } else { + SkASSERT(kBottomLeft_GrSurfaceOrigin == proc.fAtlasOrigin); + v->codeAppendf("%s.xy = float2(atlascoord.x * %s.x, 1 - atlascoord.y * %s.y);", + texcoord.vsOut(), atlasAdjust, atlasAdjust); + } + if (isCoverageCount) { + v->codeAppendf("%s.z = wind * .5;", texcoord.vsOut()); + } + + gpArgs->fPositionVar.set(kFloat2_GrSLType, "octocoord"); + this->emitTransforms(v, varyingHandler, uniHandler, gpArgs->fPositionVar, proc.fLocalMatrix, + args.fFPCoordTransformHandler); + + // Fragment shader. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + + // Look up coverage in the atlas. + f->codeAppendf("half coverage = "); + f->appendTextureLookup(args.fTexSamplers[0], SkStringPrintf("%s.xy", texcoord.fsIn()).c_str(), + kFloat2_GrSLType); + f->codeAppendf(".a;"); + + if (isCoverageCount) { + f->codeAppendf("coverage = abs(coverage);"); + + // Scale coverage count by .5. Make it negative for even-odd paths and positive for + // winding ones. Clamp winding coverage counts at 1.0 (i.e. min(coverage/2, .5)). + f->codeAppendf("coverage = min(abs(coverage) * half(%s.z), .5);", texcoord.fsIn()); + + // For negative values, this finishes the even-odd sawtooth function. Since positive + // (winding) values were clamped at "coverage/2 = .5", this only undoes the previous + // multiply by .5. + f->codeAppend ("coverage = 1 - abs(fract(coverage) * 2 - 1);"); + } + + f->codeAppendf("%s = half4(coverage);", args.fOutputCoverage); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.h b/gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.h new file mode 100644 index 0000000000..6ad872043e --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPathProcessor.h @@ -0,0 +1,123 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPathProcessor_DEFINED +#define GrCCPathProcessor_DEFINED + +#include +#include "include/core/SkPath.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/ccpr/GrCCAtlas.h" +#include "src/gpu/ccpr/GrOctoBounds.h" + +class GrCCPathCacheEntry; +class GrCCPerFlushResources; +class GrOnFlushResourceProvider; +class GrOpFlushState; + +/** + * This class draws AA paths using the coverage count masks produced by GrCCCoverageProcessor. + * + * Paths are drawn as bloated octagons, and coverage is derived from the coverage count mask and + * fill rule. + * + * To draw paths, the caller must set up an instance buffer as detailed below, then call drawPaths() + * providing its own instance buffer alongside the buffers found by calling FindIndexBuffer/ + * FindVertexBuffer. + */ +class GrCCPathProcessor : public GrGeometryProcessor { +public: + struct Instance { + SkRect fDevBounds; // "right < left" indicates even-odd fill type. + SkRect fDevBounds45; // Bounding box in "| 1 -1 | * devCoords" space. See GrOctoBounds. + // | 1 1 | + SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas. + uint64_t fColor; // Color always stored as 4 x fp16 + + void set(const GrOctoBounds&, const SkIVector& devToAtlasOffset, uint64_t, GrFillRule); + void set(const GrCCPathCacheEntry&, const SkIVector& shift, uint64_t, GrFillRule); + }; + + GR_STATIC_ASSERT(4 * 12 == sizeof(Instance)); + + static sk_sp FindVertexBuffer(GrOnFlushResourceProvider*); + static sk_sp FindIndexBuffer(GrOnFlushResourceProvider*); + + enum class CoverageMode : bool { + kCoverageCount, + kLiteral + }; + + static CoverageMode GetCoverageMode(GrCCAtlas::CoverageType coverageType) { + return (GrCCAtlas::CoverageType::kFP16_CoverageCount == coverageType) + ? CoverageMode::kCoverageCount + : CoverageMode::kLiteral; + } + + GrCCPathProcessor( + CoverageMode, const GrTexture* atlasTexture, const GrSwizzle&, + GrSurfaceOrigin atlasOrigin, + const SkMatrix& viewMatrixIfUsingLocalCoords = SkMatrix::I()); + + const char* name() const override { return "GrCCPathProcessor"; } + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { + b->add32((uint32_t)fCoverageMode); + } + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + + void drawPaths(GrOpFlushState*, const GrPipeline&, const GrPipeline::FixedDynamicState*, + const GrCCPerFlushResources&, int baseInstance, int endInstance, + const SkRect& bounds) const; + +private: + const TextureSampler& onTextureSampler(int) const override { return fAtlasAccess; } + + const CoverageMode fCoverageMode; + const TextureSampler fAtlasAccess; + SkISize fAtlasSize; + GrSurfaceOrigin fAtlasOrigin; + + SkMatrix fLocalMatrix; + static constexpr Attribute kInstanceAttribs[] = { + {"devbounds", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + {"devbounds45", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + {"dev_to_atlas_offset", kInt2_GrVertexAttribType, kInt2_GrSLType}, + {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType} + }; + static constexpr int kColorAttribIdx = 3; + static constexpr Attribute kCornersAttrib = + {"corners", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + + class Impl; + + typedef GrGeometryProcessor INHERITED; +}; + +inline void GrCCPathProcessor::Instance::set( + const GrOctoBounds& octoBounds, const SkIVector& devToAtlasOffset, uint64_t color, + GrFillRule fillRule) { + if (GrFillRule::kNonzero == fillRule) { + // We cover "nonzero" paths with clockwise triangles, which is the default result from + // normal octo bounds. + fDevBounds = octoBounds.bounds(); + fDevBounds45 = octoBounds.bounds45(); + } else { + // We cover "even/odd" paths with counterclockwise triangles. Here we reorder the bounding + // box vertices so the output is flipped horizontally. + fDevBounds.setLTRB( + octoBounds.right(), octoBounds.top(), octoBounds.left(), octoBounds.bottom()); + fDevBounds45.setLTRB( + octoBounds.bottom45(), octoBounds.right45(), octoBounds.top45(), + octoBounds.left45()); + } + fDevToAtlasOffset = devToAtlasOffset; + fColor = color; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.cpp new file mode 100644 index 0000000000..4174dba318 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.cpp @@ -0,0 +1,611 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCPerFlushResources.h" + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMakeUnique.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSurfaceContextPriv.h" +#include "src/gpu/ccpr/GrCCPathCache.h" +#include "src/gpu/ccpr/GrGSCoverageProcessor.h" +#include "src/gpu/ccpr/GrSampleMaskProcessor.h" +#include "src/gpu/ccpr/GrVSCoverageProcessor.h" +#include "src/gpu/geometry/GrShape.h" + +using CoverageType = GrCCAtlas::CoverageType; +using FillBatchID = GrCCFiller::BatchID; +using StrokeBatchID = GrCCStroker::BatchID; +using PathInstance = GrCCPathProcessor::Instance; + +static constexpr int kFillIdx = GrCCPerFlushResourceSpecs::kFillIdx; +static constexpr int kStrokeIdx = GrCCPerFlushResourceSpecs::kStrokeIdx; + +namespace { + +// Base class for an Op that renders a CCPR atlas. +class AtlasOp : public GrDrawOp { +public: + FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override { + return GrProcessorSet::EmptySetAnalysis(); + } + CombineResult onCombineIfPossible(GrOp* other, const GrCaps&) override { + // We will only make multiple copy ops if they have different source proxies. + // TODO: make use of texture chaining. + return CombineResult::kCannotCombine; + } + void onPrepare(GrOpFlushState*) override {} + +protected: + AtlasOp(uint32_t classID, sk_sp resources, + const SkISize& drawBounds) + : GrDrawOp(classID) + , fResources(std::move(resources)) { + this->setBounds(SkRect::MakeIWH(drawBounds.width(), drawBounds.height()), + GrOp::HasAABloat::kNo, GrOp::IsHairline::kNo); + } + + const sk_sp fResources; +}; + +// Copies paths from a cached coverage count or msaa atlas into an 8-bit literal-coverage atlas. +class CopyAtlasOp : public AtlasOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make( + GrRecordingContext* context, sk_sp resources, + sk_sp copyProxy, int baseInstance, int endInstance, + const SkISize& drawBounds) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(std::move(resources), std::move(copyProxy), baseInstance, + endInstance, drawBounds); + } + + const char* name() const override { return "CopyAtlasOp (CCPR)"; } + + void visitProxies(const VisitProxyFunc& fn) const override { + fn(fSrcProxy.get(), GrMipMapped::kNo); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + SkASSERT(fSrcProxy); + auto srcProxy = fSrcProxy.get(); + SkASSERT(srcProxy->isInstantiated()); + + auto coverageMode = GrCCPathProcessor::GetCoverageMode( + fResources->renderedPathCoverageType()); + GrCCPathProcessor pathProc(coverageMode, srcProxy->peekTexture(), + srcProxy->textureSwizzle(), srcProxy->origin()); + + GrPipeline pipeline(GrScissorTest::kDisabled, SkBlendMode::kSrc, + flushState->drawOpArgs().outputSwizzle()); + GrPipeline::FixedDynamicState dynamicState; + dynamicState.fPrimitiveProcessorTextures = &srcProxy; + + pathProc.drawPaths(flushState, pipeline, &dynamicState, *fResources, fBaseInstance, + fEndInstance, this->bounds()); + } + +private: + friend class ::GrOpMemoryPool; // for ctor + + CopyAtlasOp(sk_sp resources, sk_sp srcProxy, + int baseInstance, int endInstance, const SkISize& drawBounds) + : AtlasOp(ClassID(), std::move(resources), drawBounds) + , fSrcProxy(srcProxy) + , fBaseInstance(baseInstance) + , fEndInstance(endInstance) { + } + sk_sp fSrcProxy; + const int fBaseInstance; + const int fEndInstance; +}; + +// Renders coverage counts to a CCPR atlas using the resources' pre-filled GrCCPathParser. +template class RenderAtlasOp : public AtlasOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make( + GrRecordingContext* context, sk_sp resources, + FillBatchID fillBatchID, StrokeBatchID strokeBatchID, const SkISize& drawBounds) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate( + std::move(resources), fillBatchID, strokeBatchID, drawBounds); + } + + // GrDrawOp interface. + const char* name() const override { return "RenderAtlasOp (CCPR)"; } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + ProcessorType proc; + GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus, + flushState->drawOpArgs().outputSwizzle()); + fResources->filler().drawFills(flushState, &proc, pipeline, fFillBatchID, fDrawBounds); + fResources->stroker().drawStrokes(flushState, &proc, fStrokeBatchID, fDrawBounds); + } + +private: + friend class ::GrOpMemoryPool; // for ctor + + RenderAtlasOp(sk_sp resources, FillBatchID fillBatchID, + StrokeBatchID strokeBatchID, const SkISize& drawBounds) + : AtlasOp(ClassID(), std::move(resources), drawBounds) + , fFillBatchID(fillBatchID) + , fStrokeBatchID(strokeBatchID) + , fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) { + } + + const FillBatchID fFillBatchID; + const StrokeBatchID fStrokeBatchID; + const SkIRect fDrawBounds; +}; + +} + +static int inst_buffer_count(const GrCCPerFlushResourceSpecs& specs) { + return specs.fNumCachedPaths + + // Copies get two instances per draw: 1 copy + 1 draw. + (specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]) * 2 + + specs.fNumRenderedPaths[kFillIdx] + specs.fNumRenderedPaths[kStrokeIdx]; + // No clips in instance buffers. +} + +GrCCPerFlushResources::GrCCPerFlushResources( + GrOnFlushResourceProvider* onFlushRP, CoverageType coverageType, + const GrCCPerFlushResourceSpecs& specs) + // Overallocate by one point so we can call Sk4f::Store at the final SkPoint in the array. + // (See transform_path_pts below.) + // FIXME: instead use built-in instructions to write only the first two lanes of an Sk4f. + : fLocalDevPtsBuffer(SkTMax(specs.fRenderedPathStats[kFillIdx].fMaxPointsPerPath, + specs.fRenderedPathStats[kStrokeIdx].fMaxPointsPerPath) + 1) + , fFiller((CoverageType::kFP16_CoverageCount == coverageType) + ? GrCCFiller::Algorithm::kCoverageCount + : GrCCFiller::Algorithm::kStencilWindingCount, + specs.fNumRenderedPaths[kFillIdx] + specs.fNumClipPaths, + specs.fRenderedPathStats[kFillIdx].fNumTotalSkPoints, + specs.fRenderedPathStats[kFillIdx].fNumTotalSkVerbs, + specs.fRenderedPathStats[kFillIdx].fNumTotalConicWeights) + , fStroker(specs.fNumRenderedPaths[kStrokeIdx], + specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkPoints, + specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkVerbs) + , fCopyAtlasStack(CoverageType::kA8_LiteralCoverage, specs.fCopyAtlasSpecs, + onFlushRP->caps()) + , fRenderedAtlasStack(coverageType, specs.fRenderedAtlasSpecs, onFlushRP->caps()) + , fIndexBuffer(GrCCPathProcessor::FindIndexBuffer(onFlushRP)) + , fVertexBuffer(GrCCPathProcessor::FindVertexBuffer(onFlushRP)) + , fInstanceBuffer(onFlushRP->makeBuffer(GrGpuBufferType::kVertex, + inst_buffer_count(specs) * sizeof(PathInstance))) + , fNextCopyInstanceIdx(0) + , fNextPathInstanceIdx( + specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]) { + if (!fIndexBuffer) { + SkDebugf("WARNING: failed to allocate CCPR index buffer. No paths will be drawn.\n"); + return; + } + if (!fVertexBuffer) { + SkDebugf("WARNING: failed to allocate CCPR vertex buffer. No paths will be drawn.\n"); + return; + } + if (!fInstanceBuffer) { + SkDebugf("WARNING: failed to allocate CCPR instance buffer. No paths will be drawn.\n"); + return; + } + fPathInstanceData = static_cast(fInstanceBuffer->map()); + SkASSERT(fPathInstanceData); + + if (CoverageType::kA8_Multisample == coverageType) { + int numRenderedPaths = + specs.fNumRenderedPaths[kFillIdx] + specs.fNumRenderedPaths[kStrokeIdx] + + specs.fNumClipPaths; + fStencilResolveBuffer = onFlushRP->makeBuffer( + GrGpuBufferType::kVertex, + numRenderedPaths * sizeof(GrStencilAtlasOp::ResolveRectInstance)); + fStencilResolveInstanceData = static_cast( + fStencilResolveBuffer->map()); + SkASSERT(fStencilResolveInstanceData); + SkDEBUGCODE(fEndStencilResolveInstance = numRenderedPaths); + } + + SkDEBUGCODE(fEndCopyInstance = + specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]); + SkDEBUGCODE(fEndPathInstance = inst_buffer_count(specs)); +} + +void GrCCPerFlushResources::upgradeEntryToLiteralCoverageAtlas( + GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCPathCacheEntry* entry, + GrFillRule fillRule) { + using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult; + SkASSERT(this->isMapped()); + SkASSERT(fNextCopyInstanceIdx < fEndCopyInstance); + + const GrCCCachedAtlas* cachedAtlas = entry->cachedAtlas(); + SkASSERT(cachedAtlas); + SkASSERT(cachedAtlas->getOnFlushProxy()); + + if (CoverageType::kA8_LiteralCoverage == cachedAtlas->coverageType()) { + // This entry has already been upgraded to literal coverage. The path must have been drawn + // multiple times during the flush. + SkDEBUGCODE(--fEndCopyInstance); + return; + } + + SkIVector newAtlasOffset; + if (GrCCAtlas* retiredAtlas = fCopyAtlasStack.addRect(entry->devIBounds(), &newAtlasOffset)) { + // We did not fit in the previous copy atlas and it was retired. We will render the ranges + // up until fCopyPathRanges.count() into the retired atlas during finalize(). + retiredAtlas->setFillBatchID(fCopyPathRanges.count()); + fCurrCopyAtlasRangesIdx = fCopyPathRanges.count(); + } + + this->recordCopyPathInstance( + *entry, newAtlasOffset, fillRule, sk_ref_sp(cachedAtlas->getOnFlushProxy())); + + sk_sp previousAtlasTexture = + sk_ref_sp(cachedAtlas->getOnFlushProxy()->peekTexture()); + GrCCAtlas* newAtlas = &fCopyAtlasStack.current(); + if (ReleaseAtlasResult::kDidInvalidateFromCache == + entry->upgradeToLiteralCoverageAtlas(pathCache, onFlushRP, newAtlas, newAtlasOffset)) { + // This texture just got booted out of the cache. Keep it around, in case we might be able + // to recycle it for a new atlas. We can recycle it because copying happens before rendering + // new paths, and every path from the atlas that we're planning to use this flush will be + // copied to a new atlas. We'll never copy some and leave others. + fRecyclableAtlasTextures.push_back(std::move(previousAtlasTexture)); + } +} + +template +static void emplace_at_memcpy(SkTArray* array, int idx, Args&&... args) { + if (int moveCount = array->count() - idx) { + array->push_back(); + T* location = array->begin() + idx; + memcpy(location+1, location, moveCount * sizeof(T)); + new (location) T(std::forward(args)...); + } else { + array->emplace_back(std::forward(args)...); + } +} + +void GrCCPerFlushResources::recordCopyPathInstance( + const GrCCPathCacheEntry& entry, const SkIVector& newAtlasOffset, GrFillRule fillRule, + sk_sp srcProxy) { + SkASSERT(fNextCopyInstanceIdx < fEndCopyInstance); + + // Write the instance at the back of the array. + int currentInstanceIdx = fNextCopyInstanceIdx++; + constexpr uint64_t kWhite = (((uint64_t) SK_Half1) << 0) | + (((uint64_t) SK_Half1) << 16) | + (((uint64_t) SK_Half1) << 32) | + (((uint64_t) SK_Half1) << 48); + fPathInstanceData[currentInstanceIdx].set(entry, newAtlasOffset, kWhite, fillRule); + + // Percolate the instance forward until it's contiguous with other instances that share the same + // proxy. + for (int i = fCopyPathRanges.count() - 1; i >= fCurrCopyAtlasRangesIdx; --i) { + if (fCopyPathRanges[i].fSrcProxy == srcProxy) { + ++fCopyPathRanges[i].fCount; + return; + } + int rangeFirstInstanceIdx = currentInstanceIdx - fCopyPathRanges[i].fCount; + std::swap(fPathInstanceData[rangeFirstInstanceIdx], fPathInstanceData[currentInstanceIdx]); + currentInstanceIdx = rangeFirstInstanceIdx; + } + + // An instance with this particular proxy did not yet exist in the array. Add a range for it. + emplace_at_memcpy(&fCopyPathRanges, fCurrCopyAtlasRangesIdx, std::move(srcProxy), 1); +} + +static bool transform_path_pts( + const SkMatrix& m, const SkPath& path, const SkAutoSTArray<32, SkPoint>& outDevPts, + GrOctoBounds* octoBounds) { + const SkPoint* pts = SkPathPriv::PointData(path); + int numPts = path.countPoints(); + SkASSERT(numPts + 1 <= outDevPts.count()); + SkASSERT(numPts); + + // m45 transforms path points into "45 degree" device space. A bounding box in this space gives + // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal + // transform is not necessary as long as the shader uses the correct inverse. + SkMatrix m45; + m45.setSinCos(1, 1); + m45.preConcat(m); + + // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points: + // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords). + // | 1 1 | + Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); + Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); + Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); + + // Map the path's points to device space and accumulate bounding boxes. + Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T); + devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt); + Sk4f topLeft = devPt; + Sk4f bottomRight = devPt; + + // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two, + // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must + // be at least one larger than the number of points. + devPt.store(&outDevPts[0]); + + for (int i = 1; i < numPts; ++i) { + devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T); + devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt); + topLeft = Sk4f::Min(topLeft, devPt); + bottomRight = Sk4f::Max(bottomRight, devPt); + devPt.store(&outDevPts[i]); + } + + if (!(Sk4f(0) == topLeft*0).allTrue() || !(Sk4f(0) == bottomRight*0).allTrue()) { + // The bounds are infinite or NaN. + return false; + } + + SkPoint topLeftPts[2], bottomRightPts[2]; + topLeft.store(topLeftPts); + bottomRight.store(bottomRightPts); + + const SkRect& devBounds = SkRect::MakeLTRB( + topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(), bottomRightPts[0].y()); + const SkRect& devBounds45 = SkRect::MakeLTRB( + topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(), bottomRightPts[1].y()); + + octoBounds->set(devBounds, devBounds45); + return true; +} + +GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas( + const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape, float strokeDevWidth, + GrOctoBounds* octoBounds, SkIRect* devIBounds, SkIVector* devToAtlasOffset) { + SkASSERT(this->isMapped()); + SkASSERT(fNextPathInstanceIdx < fEndPathInstance); + + SkPath path; + shape.asPath(&path); + if (path.isEmpty()) { + SkDEBUGCODE(--fEndPathInstance); + SkDEBUGCODE(--fEndStencilResolveInstance); + return nullptr; + } + if (!transform_path_pts(m, path, fLocalDevPtsBuffer, octoBounds)) { + // The transformed path had infinite or NaN bounds. + SkDEBUGCODE(--fEndPathInstance); + SkDEBUGCODE(--fEndStencilResolveInstance); + return nullptr; + } + + const SkStrokeRec& stroke = shape.style().strokeRec(); + if (!stroke.isFillStyle()) { + float r = SkStrokeRec::GetInflationRadius( + stroke.getJoin(), stroke.getMiter(), stroke.getCap(), strokeDevWidth); + octoBounds->outset(r); + } + + GrScissorTest enableScissorInAtlas; + if (clipIBounds.contains(octoBounds->bounds())) { + enableScissorInAtlas = GrScissorTest::kDisabled; + } else if (octoBounds->clip(clipIBounds)) { + enableScissorInAtlas = GrScissorTest::kEnabled; + } else { + // The clip and octo bounds do not intersect. Draw nothing. + SkDEBUGCODE(--fEndPathInstance); + SkDEBUGCODE(--fEndStencilResolveInstance); + return nullptr; + } + octoBounds->roundOut(devIBounds); + SkASSERT(clipIBounds.contains(*devIBounds)); + + this->placeRenderedPathInAtlas(*devIBounds, enableScissorInAtlas, devToAtlasOffset); + + GrFillRule fillRule; + if (stroke.isFillStyle()) { + SkASSERT(0 == strokeDevWidth); + fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), enableScissorInAtlas, + *devIBounds, *devToAtlasOffset); + fillRule = GrFillRuleForSkPath(path); + } else { + // Stroke-and-fill is not yet supported. + SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() || stroke.isHairlineStyle()); + SkASSERT(!stroke.isHairlineStyle() || 1 == strokeDevWidth); + fStroker.parseDeviceSpaceStroke( + path, fLocalDevPtsBuffer.begin(), stroke, strokeDevWidth, enableScissorInAtlas, + *devIBounds, *devToAtlasOffset); + fillRule = GrFillRule::kNonzero; + } + + if (GrCCAtlas::CoverageType::kA8_Multisample == this->renderedPathCoverageType()) { + this->recordStencilResolveInstance(*devIBounds, *devToAtlasOffset, fillRule); + } + + return &fRenderedAtlasStack.current(); +} + +const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas( + const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds, + GrFillRule fillRule, SkIVector* devToAtlasOffset) { + SkASSERT(this->isMapped()); + + if (devPath.isEmpty()) { + SkDEBUGCODE(--fEndStencilResolveInstance); + return nullptr; + } + + GrScissorTest enableScissorInAtlas; + SkIRect clippedPathIBounds; + if (clipIBounds.contains(devPathIBounds)) { + clippedPathIBounds = devPathIBounds; + enableScissorInAtlas = GrScissorTest::kDisabled; + } else if (clippedPathIBounds.intersect(clipIBounds, devPathIBounds)) { + enableScissorInAtlas = GrScissorTest::kEnabled; + } else { + // The clip and path bounds do not intersect. Draw nothing. + SkDEBUGCODE(--fEndStencilResolveInstance); + return nullptr; + } + + this->placeRenderedPathInAtlas(clippedPathIBounds, enableScissorInAtlas, devToAtlasOffset); + fFiller.parseDeviceSpaceFill(devPath, SkPathPriv::PointData(devPath), enableScissorInAtlas, + clippedPathIBounds, *devToAtlasOffset); + + // In MSAA mode we also record an internal draw instance that will be used to resolve stencil + // winding values to coverage when the atlas is generated. + if (GrCCAtlas::CoverageType::kA8_Multisample == this->renderedPathCoverageType()) { + this->recordStencilResolveInstance(clippedPathIBounds, *devToAtlasOffset, fillRule); + } + + return &fRenderedAtlasStack.current(); +} + +void GrCCPerFlushResources::placeRenderedPathInAtlas( + const SkIRect& clippedPathIBounds, GrScissorTest scissorTest, SkIVector* devToAtlasOffset) { + if (GrCCAtlas* retiredAtlas = + fRenderedAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) { + // We did not fit in the previous coverage count atlas and it was retired. Close the path + // parser's current batch (which does not yet include the path we just parsed). We will + // render this batch into the retired atlas during finalize(). + retiredAtlas->setFillBatchID(fFiller.closeCurrentBatch()); + retiredAtlas->setStrokeBatchID(fStroker.closeCurrentBatch()); + retiredAtlas->setEndStencilResolveInstance(fNextStencilResolveInstanceIdx); + } +} + +void GrCCPerFlushResources::recordStencilResolveInstance( + const SkIRect& clippedPathIBounds, const SkIVector& devToAtlasOffset, GrFillRule fillRule) { + SkASSERT(GrCCAtlas::CoverageType::kA8_Multisample == this->renderedPathCoverageType()); + SkASSERT(fNextStencilResolveInstanceIdx < fEndStencilResolveInstance); + + SkIRect atlasIBounds = clippedPathIBounds.makeOffset(devToAtlasOffset); + if (GrFillRule::kEvenOdd == fillRule) { + // Make even/odd fills counterclockwise. The resolve draw uses two-sided stencil, with + // "nonzero" settings in front and "even/odd" settings in back. + std::swap(atlasIBounds.fLeft, atlasIBounds.fRight); + } + fStencilResolveInstanceData[fNextStencilResolveInstanceIdx++] = { + (int16_t)atlasIBounds.left(), (int16_t)atlasIBounds.top(), + (int16_t)atlasIBounds.right(), (int16_t)atlasIBounds.bottom()}; +} + +bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP) { + SkASSERT(this->isMapped()); + SkASSERT(fNextPathInstanceIdx == fEndPathInstance); + SkASSERT(fNextCopyInstanceIdx == fEndCopyInstance); + SkASSERT(GrCCAtlas::CoverageType::kA8_Multisample != this->renderedPathCoverageType() || + fNextStencilResolveInstanceIdx == fEndStencilResolveInstance); + + fInstanceBuffer->unmap(); + fPathInstanceData = nullptr; + + if (fStencilResolveBuffer) { + fStencilResolveBuffer->unmap(); + fStencilResolveInstanceData = nullptr; + } + + if (!fCopyAtlasStack.empty()) { + fCopyAtlasStack.current().setFillBatchID(fCopyPathRanges.count()); + fCurrCopyAtlasRangesIdx = fCopyPathRanges.count(); + } + if (!fRenderedAtlasStack.empty()) { + fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch()); + fRenderedAtlasStack.current().setStrokeBatchID(fStroker.closeCurrentBatch()); + fRenderedAtlasStack.current().setEndStencilResolveInstance(fNextStencilResolveInstanceIdx); + } + + // Build the GPU buffers to render path coverage counts. (This must not happen until after the + // final calls to fFiller/fStroker.closeCurrentBatch().) + if (!fFiller.prepareToDraw(onFlushRP)) { + return false; + } + if (!fStroker.prepareToDraw(onFlushRP)) { + return false; + } + + // Draw the copies from coverage count or msaa atlas(es) into 8-bit cached atlas(es). + int copyRangeIdx = 0; + int baseCopyInstance = 0; + for (GrCCAtlasStack::Iter atlas(fCopyAtlasStack); atlas.next();) { + int endCopyRange = atlas->getFillBatchID(); + SkASSERT(endCopyRange > copyRangeIdx); + + auto rtc = atlas->makeRenderTargetContext(onFlushRP); + for (; copyRangeIdx < endCopyRange; ++copyRangeIdx) { + const CopyPathRange& copyRange = fCopyPathRanges[copyRangeIdx]; + int endCopyInstance = baseCopyInstance + copyRange.fCount; + if (rtc) { + auto op = CopyAtlasOp::Make( + rtc->surfPriv().getContext(), sk_ref_sp(this), copyRange.fSrcProxy, + baseCopyInstance, endCopyInstance, atlas->drawBounds()); + rtc->addDrawOp(GrNoClip(), std::move(op)); + } + baseCopyInstance = endCopyInstance; + } + } + SkASSERT(fCopyPathRanges.count() == copyRangeIdx); + SkASSERT(fNextCopyInstanceIdx == baseCopyInstance); + SkASSERT(baseCopyInstance == fEndCopyInstance); + + // Render the coverage count atlas(es). + int baseStencilResolveInstance = 0; + for (GrCCAtlasStack::Iter atlas(fRenderedAtlasStack); atlas.next();) { + // Copies will be finished by the time we get to rendering new atlases. See if we can + // recycle any previous invalidated atlas textures instead of creating new ones. + sk_sp backingTexture; + for (sk_sp& texture : fRecyclableAtlasTextures) { + if (texture && atlas->currentHeight() == texture->height() && + atlas->currentWidth() == texture->width()) { + backingTexture = skstd::exchange(texture, nullptr); + break; + } + } + + if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) { + std::unique_ptr op; + if (CoverageType::kA8_Multisample == fRenderedAtlasStack.coverageType()) { + op = GrStencilAtlasOp::Make( + rtc->surfPriv().getContext(), sk_ref_sp(this), atlas->getFillBatchID(), + atlas->getStrokeBatchID(), baseStencilResolveInstance, + atlas->getEndStencilResolveInstance(), atlas->drawBounds()); + } else if (onFlushRP->caps()->shaderCaps()->geometryShaderSupport()) { + op = RenderAtlasOp::Make( + rtc->surfPriv().getContext(), sk_ref_sp(this), atlas->getFillBatchID(), + atlas->getStrokeBatchID(), atlas->drawBounds()); + } else { + op = RenderAtlasOp::Make( + rtc->surfPriv().getContext(), sk_ref_sp(this), atlas->getFillBatchID(), + atlas->getStrokeBatchID(), atlas->drawBounds()); + } + rtc->addDrawOp(GrNoClip(), std::move(op)); + if (rtc->proxy()->requiresManualMSAAResolve()) { + onFlushRP->addTextureResolveTask(sk_ref_sp(rtc->proxy()->asTextureProxy()), + GrSurfaceProxy::ResolveFlags::kMSAA); + } + } + + SkASSERT(atlas->getEndStencilResolveInstance() >= baseStencilResolveInstance); + baseStencilResolveInstance = atlas->getEndStencilResolveInstance(); + } + SkASSERT(GrCCAtlas::CoverageType::kA8_Multisample != this->renderedPathCoverageType() || + baseStencilResolveInstance == fEndStencilResolveInstance); + + return true; +} + +void GrCCPerFlushResourceSpecs::cancelCopies() { + // Convert copies to cached draws. + fNumCachedPaths += fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx]; + fNumCopiedPaths[kFillIdx] = fNumCopiedPaths[kStrokeIdx] = 0; + fCopyPathStats[kFillIdx] = fCopyPathStats[kStrokeIdx] = GrCCRenderedPathStats(); + fCopyAtlasSpecs = GrCCAtlas::Specs(); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.h b/gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.h new file mode 100644 index 0000000000..d446da7829 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPerFlushResources.h @@ -0,0 +1,205 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPerFlushResources_DEFINED +#define GrCCPerFlushResources_DEFINED + +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/ccpr/GrCCAtlas.h" +#include "src/gpu/ccpr/GrCCFiller.h" +#include "src/gpu/ccpr/GrCCPathProcessor.h" +#include "src/gpu/ccpr/GrCCStroker.h" +#include "src/gpu/ccpr/GrStencilAtlasOp.h" + +class GrCCPathCache; +class GrCCPathCacheEntry; +class GrOctoBounds; +class GrOnFlushResourceProvider; +class GrShape; + +/** + * This struct counts values that help us preallocate buffers for rendered path geometry. + */ +struct GrCCRenderedPathStats { + int fMaxPointsPerPath = 0; + int fNumTotalSkPoints = 0; + int fNumTotalSkVerbs = 0; + int fNumTotalConicWeights = 0; + + void statPath(const SkPath&); +}; + +/** + * This struct encapsulates the minimum and desired requirements for the GPU resources required by + * CCPR in a given flush. + */ +struct GrCCPerFlushResourceSpecs { + static constexpr int kFillIdx = 0; + static constexpr int kStrokeIdx = 1; + + int fNumCachedPaths = 0; + + int fNumCopiedPaths[2] = {0, 0}; + GrCCRenderedPathStats fCopyPathStats[2]; + GrCCAtlas::Specs fCopyAtlasSpecs; + + int fNumRenderedPaths[2] = {0, 0}; + int fNumClipPaths = 0; + GrCCRenderedPathStats fRenderedPathStats[2]; + GrCCAtlas::Specs fRenderedAtlasSpecs; + + bool isEmpty() const { + return 0 == fNumCachedPaths + fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx] + + fNumRenderedPaths[kFillIdx] + fNumRenderedPaths[kStrokeIdx] + fNumClipPaths; + } + // Converts the copies to normal cached draws. + void cancelCopies(); +}; + +/** + * This class wraps all the GPU resources that CCPR builds at flush time. It is allocated in CCPR's + * preFlush() method, and referenced by all the GrCCPerOpsTaskPaths objects that are being flushed. + * It is deleted in postFlush() once all the flushing GrCCPerOpsTaskPaths objects are deleted. + */ +class GrCCPerFlushResources : public GrNonAtomicRef { +public: + GrCCPerFlushResources( + GrOnFlushResourceProvider*, GrCCAtlas::CoverageType,const GrCCPerFlushResourceSpecs&); + + bool isMapped() const { return SkToBool(fPathInstanceData); } + + GrCCAtlas::CoverageType renderedPathCoverageType() const { + return fRenderedAtlasStack.coverageType(); + } + + // Copies a coverage-counted path out of the given texture proxy, and into a cached, 8-bit, + // literal coverage atlas. Updates the cache entry to reference the new atlas. + void upgradeEntryToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*, + GrCCPathCacheEntry*, GrFillRule); + + // These two methods render a path into a temporary coverage count atlas. See + // GrCCPathProcessor::Instance for a description of the outputs. + // + // strokeDevWidth must be 0 for fills, 1 for hairlines, or the stroke width in device-space + // pixels for non-hairline strokes (implicitly requiring a rigid-body transform). + GrCCAtlas* renderShapeInAtlas( + const SkIRect& clipIBounds, const SkMatrix&, const GrShape&, float strokeDevWidth, + GrOctoBounds*, SkIRect* devIBounds, SkIVector* devToAtlasOffset); + const GrCCAtlas* renderDeviceSpacePathInAtlas( + const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds, + GrFillRule fillRule, SkIVector* devToAtlasOffset); + + // Returns the index in instanceBuffer() of the next instance that will be added by + // appendDrawPathInstance(). + int nextPathInstanceIdx() const { return fNextPathInstanceIdx; } + + // Appends an instance to instanceBuffer() that will draw a path to the destination render + // target. The caller is responsible to call set() on the returned instance, to keep track of + // its atlas and index (see nextPathInstanceIdx()), and to issue the actual draw call. + GrCCPathProcessor::Instance& appendDrawPathInstance() { + SkASSERT(this->isMapped()); + SkASSERT(fNextPathInstanceIdx < fEndPathInstance); + return fPathInstanceData[fNextPathInstanceIdx++]; + } + + // Finishes off the GPU buffers and renders the atlas(es). + bool finalize(GrOnFlushResourceProvider*); + + // Accessors used by draw calls, once the resources have been finalized. + const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; } + const GrCCStroker& stroker() const { SkASSERT(!this->isMapped()); return fStroker; } + sk_sp refIndexBuffer() const { + SkASSERT(!this->isMapped()); + return fIndexBuffer; + } + sk_sp refVertexBuffer() const { + SkASSERT(!this->isMapped()); + return fVertexBuffer; + } + sk_sp refInstanceBuffer() const { + SkASSERT(!this->isMapped()); + return fInstanceBuffer; + } + sk_sp refStencilResolveBuffer() const { + SkASSERT(!this->isMapped()); + return fStencilResolveBuffer; + } + +private: + void recordCopyPathInstance(const GrCCPathCacheEntry&, const SkIVector& newAtlasOffset, + GrFillRule, sk_sp srcProxy); + void placeRenderedPathInAtlas( + const SkIRect& clippedPathIBounds, GrScissorTest, SkIVector* devToAtlasOffset); + + // In MSAA mode we record an additional instance per path that draws a rectangle on top of its + // corresponding path in the atlas and resolves stencil winding values to coverage. + void recordStencilResolveInstance( + const SkIRect& clippedPathIBounds, const SkIVector& devToAtlasOffset, GrFillRule); + + const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer; + GrCCFiller fFiller; + GrCCStroker fStroker; + GrCCAtlasStack fCopyAtlasStack; + GrCCAtlasStack fRenderedAtlasStack; + + const sk_sp fIndexBuffer; + const sk_sp fVertexBuffer; + const sk_sp fInstanceBuffer; + + GrCCPathProcessor::Instance* fPathInstanceData = nullptr; + int fNextCopyInstanceIdx; + SkDEBUGCODE(int fEndCopyInstance); + int fNextPathInstanceIdx; + int fBasePathInstanceIdx; + SkDEBUGCODE(int fEndPathInstance); + + // Represents a range of copy-path instances that all share the same source proxy. (i.e. Draw + // instances that copy a path mask from a 16-bit coverage count atlas into an 8-bit literal + // coverage atlas.) + struct CopyPathRange { + CopyPathRange() = default; + CopyPathRange(sk_sp srcProxy, int count) + : fSrcProxy(std::move(srcProxy)), fCount(count) {} + sk_sp fSrcProxy; + int fCount; + }; + + SkSTArray<4, CopyPathRange> fCopyPathRanges; + int fCurrCopyAtlasRangesIdx = 0; + + // This is a list of coverage count atlas textures that have been invalidated due to us copying + // their paths into new 8-bit literal coverage atlases. Since copying is finished by the time + // we begin rendering new atlases, we can recycle these textures for the rendered atlases rather + // than allocating new texture objects upon instantiation. + SkSTArray<2, sk_sp> fRecyclableAtlasTextures; + + // Used in MSAA mode make an intermediate draw that resolves stencil winding values to coverage. + sk_sp fStencilResolveBuffer; + GrStencilAtlasOp::ResolveRectInstance* fStencilResolveInstanceData = nullptr; + int fNextStencilResolveInstanceIdx = 0; + SkDEBUGCODE(int fEndStencilResolveInstance); + +public: +#ifdef SK_DEBUG + void debugOnly_didReuseRenderedPath() { + if (GrCCAtlas::CoverageType::kA8_Multisample == this->renderedPathCoverageType()) { + --fEndStencilResolveInstance; + } + } +#endif + const GrTexture* testingOnly_frontCopyAtlasTexture() const; + const GrTexture* testingOnly_frontRenderedAtlasTexture() const; +}; + +inline void GrCCRenderedPathStats::statPath(const SkPath& path) { + fMaxPointsPerPath = SkTMax(fMaxPointsPerPath, path.countPoints()); + fNumTotalSkPoints += path.countPoints(); + fNumTotalSkVerbs += path.countVerbs(); + fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h b/gfx/skia/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h new file mode 100644 index 0000000000..ff8a224e66 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPerOpsTaskPaths_DEFINED +#define GrCCPerOpsTaskPaths_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkTInternalLList.h" +#include "src/gpu/ccpr/GrCCClipPath.h" + +#include + +class GrCCDrawPathsOp; +class GrCCPerFlushResources; + +/** + * Tracks all the CCPR paths in a given opsTask that will be drawn when it flushes. + */ +// DDL TODO: given the usage pattern in DDL mode, this could probably be non-atomic refcounting. +struct GrCCPerOpsTaskPaths : public SkRefCnt { + SkTInternalLList fDrawOps; // This class does not own these ops. + std::map fClipPaths; + SkSTArenaAlloc<10 * 1024> fAllocator{10 * 1024 * 2}; + sk_sp fFlushResources; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.cpp new file mode 100644 index 0000000000..5012e3c41b --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCQuadraticShader.h" + +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +void GrCCQuadraticShader::emitSetupCode( + GrGLSLVertexGeoBuilder* s, const char* pts, const char** outHull4) const { + s->declareGlobal(fQCoordMatrix); + s->codeAppendf("%s = float2x2(1, 1, .5, 0) * inverse(float2x2(%s[2] - %s[0], %s[1] - %s[0]));", + fQCoordMatrix.c_str(), pts, pts, pts, pts); + + s->declareGlobal(fQCoord0); + s->codeAppendf("%s = %s[0];", fQCoord0.c_str(), pts); + + if (outHull4) { + // Clip the bezier triangle by the tangent line at maximum height. Quadratics have the nice + // property that maximum height always occurs at T=.5. This is a simple application for + // De Casteljau's algorithm. + s->codeAppend ("float2 quadratic_hull[4];"); + s->codeAppendf("quadratic_hull[0] = %s[0];", pts); + s->codeAppendf("quadratic_hull[1] = (%s[0] + %s[1]) * .5;", pts, pts); + s->codeAppendf("quadratic_hull[2] = (%s[1] + %s[2]) * .5;", pts, pts); + s->codeAppendf("quadratic_hull[3] = %s[2];", pts); + *outHull4 = "quadratic_hull"; + } +} + +void GrCCQuadraticShader::onEmitVaryings( + GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code, + const char* position, const char* coverage, const char* cornerCoverage, const char* wind) { + fCoord_fGrad.reset(kFloat4_GrSLType, scope); + varyingHandler->addVarying("coord_and_grad", &fCoord_fGrad); + code->appendf("%s.xy = %s * (%s - %s);", // Quadratic coords. + OutName(fCoord_fGrad), fQCoordMatrix.c_str(), position, fQCoord0.c_str()); + code->appendf("%s.zw = 2*bloat * float2(2 * %s.x, -1) * %s;", // Gradient. + OutName(fCoord_fGrad), OutName(fCoord_fGrad), fQCoordMatrix.c_str()); + + if (coverage) { + // Coverages need full precision since distance to the opposite edge can be large. + fEdge_fWind_fCorner.reset((cornerCoverage) ? kFloat4_GrSLType : kFloat2_GrSLType, scope); + varyingHandler->addVarying((cornerCoverage) ? "edge_and_wind_and_corner" : "edge_and_wind", + &fEdge_fWind_fCorner); + code->appendf("%s.x = %s;", OutName(fEdge_fWind_fCorner), coverage); + code->appendf("%s.y = %s;", OutName(fEdge_fWind_fCorner), wind); + } + + if (cornerCoverage) { + SkASSERT(coverage); + code->appendf("half hull_coverage;"); + this->calcHullCoverage(code, OutName(fCoord_fGrad), coverage, "hull_coverage"); + code->appendf("%s.zw = half2(hull_coverage, 1) * %s;", + OutName(fEdge_fWind_fCorner), cornerCoverage); + } +} + +void GrCCQuadraticShader::emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const { + this->calcHullCoverage(&AccessCodeString(f), fCoord_fGrad.fsIn(), + SkStringPrintf("%s.x", fEdge_fWind_fCorner.fsIn()).c_str(), + outputCoverage); + f->codeAppendf("%s *= half(%s.y);", outputCoverage, fEdge_fWind_fCorner.fsIn()); // Wind. + + if (kFloat4_GrSLType == fEdge_fWind_fCorner.type()) { + f->codeAppendf("%s = half(%s.z * %s.w) + %s;", // Attenuated corner coverage. + outputCoverage, fEdge_fWind_fCorner.fsIn(), fEdge_fWind_fCorner.fsIn(), + outputCoverage); + } +} + +void GrCCQuadraticShader::calcHullCoverage(SkString* code, const char* coordAndGrad, + const char* edge, const char* outputCoverage) const { + code->appendf("float x = %s.x, y = %s.y;", coordAndGrad, coordAndGrad); + code->appendf("float2 grad = %s.zw;", coordAndGrad); + code->append ("float f = x*x - y;"); + code->append ("float fwidth = abs(grad.x) + abs(grad.y);"); + code->appendf("float curve_coverage = min(0.5 - f/fwidth, 1);"); + // Flat edge opposite the curve. + code->appendf("float edge_coverage = min(%s, 0);", edge); + // Total hull coverage. + code->appendf("%s = max(half(curve_coverage + edge_coverage), 0);", outputCoverage); +} + +void GrCCQuadraticShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const { + f->codeAppendf("float x = %s.x, y = %s.y;", fCoord_fGrad.fsIn(), fCoord_fGrad.fsIn()); + f->codeAppendf("float f = x*x - y;"); + f->codeAppendf("float2 grad = %s.zw;", fCoord_fGrad.fsIn()); + f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.h b/gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.h new file mode 100644 index 0000000000..2942ad1841 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCQuadraticShader.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCQuadraticShader_DEFINED +#define GrCCQuadraticShader_DEFINED + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +/** + * This class renders the coverage of closed quadratic curves using the techniques outlined in + * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and + * Jim Blinn: + * + * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + * + * The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0]. + * (Use GrCCGeometry::quadraticTo().) + */ +class GrCCQuadraticShader : public GrCCCoverageProcessor::Shader { +public: + void emitSetupCode( + GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override; + + void onEmitVaryings( + GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position, + const char* coverage, const char* cornerCoverage, const char* wind) override; + + void emitFragmentCoverageCode( + GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override; + + void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override; + +private: + void calcHullCoverage(SkString* code, const char* coordAndGrad, const char* d, + const char* outputCoverage) const; + + const GrShaderVar fQCoordMatrix{"qcoord_matrix", kFloat2x2_GrSLType}; + const GrShaderVar fQCoord0{"qcoord0", kFloat2_GrSLType}; + GrGLSLVarying fCoord_fGrad; + GrGLSLVarying fEdge_fWind_fCorner; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCSTLList.h b/gfx/skia/skia/src/gpu/ccpr/GrCCSTLList.h new file mode 100644 index 0000000000..29f26b95c1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCSTLList.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCSTLList_DEFINED +#define GrCCSTLList_DEFINED + +#include "include/private/SkNoncopyable.h" +#include "src/core/SkArenaAlloc.h" +#include + +/** + * A singly-linked list whose head element is a local class member. This is required by + * GrCCDrawPathsOp because the owning opsTask is unknown at the time of creation, so we can't use + * its associated allocator to create the first element. + */ +template class GrCCSTLList : SkNoncopyable { +public: + template + GrCCSTLList(Args&&... args) : fHead(std::forward(args)...) {} + + ~GrCCSTLList() { + T* draw = fHead.fNext; // fHead will be destructed automatically. + while (draw) { + T* next = draw->fNext; + draw->~T(); + draw = next; + } + } + + const T& head() const { return fHead; } + T& head() { return fHead; } + + void append(GrCCSTLList&& right, SkArenaAlloc* alloc) { + T* nextTail = (&right.fHead == right.fTail) ? nullptr : right.fTail; + T* newRightHead = + new (alloc->makeBytesAlignedTo(sizeof(T), alignof(T))) T(std::move(right.fHead)); + + // Finish the move of right.fHead. + right.fHead.fNext = nullptr; + right.fTail = &right.fHead; + + fTail->fNext = newRightHead; + fTail = !nextTail ? newRightHead : nextTail; + } + + template struct Iter { + bool operator!=(const Iter& that) { return fCurr != that.fCurr; } + U& operator*() { return *fCurr; } + void operator++() { fCurr = fCurr->fNext; } + U* fCurr; + }; + Iter begin() const { return Iter{&fHead}; } + Iter end() const { return Iter{nullptr}; } + Iter begin() { return Iter{&fHead}; } + Iter end() { return Iter{nullptr}; } + +private: + T fHead; + T* fTail = &fHead; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp new file mode 100644 index 0000000000..40884d7d8f --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp @@ -0,0 +1,583 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCStrokeGeometry.h" + +#include "include/core/SkStrokeRec.h" +#include "include/private/SkNx.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkMathPriv.h" + +// This is the maximum distance in pixels that we can stray from the edge of a stroke when +// converting it to flat line segments. +static constexpr float kMaxErrorFromLinearization = 1/8.f; + +static inline float length(const Sk2f& n) { + Sk2f nn = n*n; + return SkScalarSqrt(nn[0] + nn[1]); +} + +static inline Sk2f normalize(const Sk2f& v) { + Sk2f vv = v*v; + vv += SkNx_shuffle<1,0>(vv); + return v * vv.rsqrt(); +} + +static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) { + float transpose[4]; + a.store(transpose); + b.store(transpose+2); + Sk2f::Load2(transpose, X, Y); +} + +static inline void normalize2(const Sk2f& v0, const Sk2f& v1, SkPoint out[2]) { + Sk2f X, Y; + transpose(v0, v1, &X, &Y); + Sk2f invlength = (X*X + Y*Y).rsqrt(); + Sk2f::Store2(out, Y * invlength, -X * invlength); +} + +static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) { + Sk2f X, Y; + transpose(leftTan, rightTan, &X, &Y); + Sk2f invlength = (X*X + Y*Y).rsqrt(); + Sk2f dotprod = leftTan * rightTan; + return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1]; +} + +static GrCCStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) { + using Verb = GrCCStrokeGeometry::Verb; + switch (join) { + case SkPaint::kBevel_Join: + return Verb::kBevelJoin; + case SkPaint::kMiter_Join: + return Verb::kMiterJoin; + case SkPaint::kRound_Join: + return Verb::kRoundJoin; + } + SK_ABORT("Invalid SkPaint::Join."); +} + +void GrCCStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth, + InstanceTallies* tallies) { + SkASSERT(!fInsideContour); + // Client should have already converted the stroke to device space (i.e. width=1 for hairline). + SkASSERT(strokeDevWidth > 0); + + fCurrStrokeRadius = strokeDevWidth/2; + fCurrStrokeJoinVerb = join_verb_from_join(stroke.getJoin()); + fCurrStrokeCapType = stroke.getCap(); + fCurrStrokeTallies = tallies; + + if (Verb::kMiterJoin == fCurrStrokeJoinVerb) { + // We implement miters by placing a triangle-shaped cap on top of a bevel join. Convert the + // "miter limit" to how tall that triangle cap can be. + float m = stroke.getMiter(); + fMiterMaxCapHeightOverWidth = .5f * SkScalarSqrt(m*m - 1); + } + + // Find the angle of curvature where the arc height above a simple line from point A to point B + // is equal to kMaxErrorFromLinearization. + float r = SkTMax(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f); + fMaxCurvatureCosTheta = 2*r*r - 1; + + fCurrContourFirstPtIdx = -1; + fCurrContourFirstNormalIdx = -1; + + fVerbs.push_back(Verb::kBeginPath); +} + +void GrCCStrokeGeometry::moveTo(SkPoint pt) { + SkASSERT(!fInsideContour); + fCurrContourFirstPtIdx = fPoints.count(); + fCurrContourFirstNormalIdx = fNormals.count(); + fPoints.push_back(pt); + SkDEBUGCODE(fInsideContour = true); +} + +void GrCCStrokeGeometry::lineTo(SkPoint pt) { + SkASSERT(fInsideContour); + this->lineTo(fCurrStrokeJoinVerb, pt); +} + +void GrCCStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) { + Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back()); + if ((tan == 0).allTrue()) { + return; + } + + tan = normalize(tan); + SkVector n = SkVector::Make(tan[1], -tan[0]); + + this->recordLeftJoinIfNotEmpty(leftJoinVerb, n); + fNormals.push_back(n); + + this->recordStroke(Verb::kLinearStroke, 0); + fPoints.push_back(pt); +} + +void GrCCStrokeGeometry::quadraticTo(const SkPoint P[3]) { + SkASSERT(fInsideContour); + this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P)); +} + +// Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric +// sense) line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization" +// from the actual curve. +static inline float wangs_formula_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) { + static constexpr float k = 2 / (8 * kMaxErrorFromLinearization); + float f = SkScalarSqrt(k * length(p2 - p1*2 + p0)); + return SkScalarCeilToInt(f); +} + +void GrCCStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float maxCurvatureT) { + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + Sk2f p2 = Sk2f::Load(P+2); + + Sk2f tan0 = p1 - p0; + Sk2f tan1 = p2 - p1; + + // Snap to a "lineTo" if the control point is so close to an endpoint that FP error will become + // an issue. + if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() || // p0 ~= p1 + (tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p1 ~= p2 + this->lineTo(leftJoinVerb, P[2]); + return; + } + + SkPoint normals[2]; + normalize2(tan0, tan1, normals); + + // Decide how many flat line segments to chop the curve into. + int numSegments = wangs_formula_quadratic(p0, p1, p2); + numSegments = SkTMin(numSegments, 1 << kMaxNumLinearSegmentsLog2); + if (numSegments <= 1) { + this->rotateTo(leftJoinVerb, normals[0]); + this->lineTo(Verb::kInternalRoundJoin, P[2]); + this->rotateTo(Verb::kInternalRoundJoin, normals[1]); + return; + } + + // At + B gives a vector tangent to the quadratic. + Sk2f A = p0 - p1*2 + p2; + Sk2f B = p1 - p0; + + // Find a line segment that crosses max curvature. + float segmentLength = SkScalarInvert(numSegments); + float leftT = maxCurvatureT - segmentLength/2; + float rightT = maxCurvatureT + segmentLength/2; + Sk2f leftTan, rightTan; + if (leftT <= 0) { + leftT = 0; + leftTan = tan0; + rightT = segmentLength; + rightTan = A*rightT + B; + } else if (rightT >= 1) { + leftT = 1 - segmentLength; + leftTan = A*leftT + B; + rightT = 1; + rightTan = tan1; + } else { + leftTan = A*leftT + B; + rightTan = A*rightT + B; + } + + // Check if curvature is too strong for a triangle strip on the line segment that crosses max + // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins. + // + // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We + // would benefit significantly from a quick reject that detects curves that don't need special + // treatment for strong curvature. + bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta; + if (isCurvatureTooStrong) { + SkPoint ptsBuffer[5]; + const SkPoint* currQuadratic = P; + + if (leftT > 0) { + SkChopQuadAt(currQuadratic, ptsBuffer, leftT); + this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1); + if (rightT < 1) { + rightT = (rightT - leftT) / (1 - leftT); + } + currQuadratic = ptsBuffer + 2; + } else { + this->rotateTo(leftJoinVerb, normals[0]); + } + + if (rightT < 1) { + SkChopQuadAt(currQuadratic, ptsBuffer, rightT); + this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]); + this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0); + } else { + this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]); + this->rotateTo(Verb::kInternalRoundJoin, normals[1]); + } + return; + } + + this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]); + fNormals.push_back_n(2, normals); + + this->recordStroke(Verb::kQuadraticStroke, SkNextLog2(numSegments)); + p1.store(&fPoints.push_back()); + p2.store(&fPoints.push_back()); +} + +void GrCCStrokeGeometry::cubicTo(const SkPoint P[4]) { + SkASSERT(fInsideContour); + float roots[3]; + int numRoots = SkFindCubicMaxCurvature(P, roots); + this->cubicTo(fCurrStrokeJoinVerb, P, + numRoots > 0 ? roots[numRoots/2] : 0, + numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone, + numRoots > 2 ? roots[2] : kRightMaxCurvatureNone); +} + +// Wang's formula for cubics (1985) gives us the number of evenly spaced (in the parametric sense) +// line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization" +// from the actual curve. +static inline float wangs_formula_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3) { + static constexpr float k = (3 * 2) / (8 * kMaxErrorFromLinearization); + float f = SkScalarSqrt(k * length(Sk2f::Max((p2 - p1*2 + p0).abs(), + (p3 - p2*2 + p1).abs()))); + return SkScalarCeilToInt(f); +} + +void GrCCStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxCurvatureT, + float leftMaxCurvatureT, float rightMaxCurvatureT) { + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + Sk2f p2 = Sk2f::Load(P+2); + Sk2f p3 = Sk2f::Load(P+3); + + Sk2f tan0 = p1 - p0; + Sk2f tan1 = p3 - p2; + + // Snap control points to endpoints if they are so close that FP error will become an issue. + if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 + p1 = p0; + tan0 = p2 - p0; + if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 ~= p2 + this->lineTo(leftJoinVerb, P[3]); + return; + } + } + if ((tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p2 ~= p3 + p2 = p3; + tan1 = p3 - p1; + if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() || // p1 ~= p2 ~= p3 + (p0 == p1).allTrue()) { // p0 ~= p1 AND p2 ~= p3 + this->lineTo(leftJoinVerb, P[3]); + return; + } + } + + SkPoint normals[2]; + normalize2(tan0, tan1, normals); + + // Decide how many flat line segments to chop the curve into. + int numSegments = wangs_formula_cubic(p0, p1, p2, p3); + numSegments = SkTMin(numSegments, 1 << kMaxNumLinearSegmentsLog2); + if (numSegments <= 1) { + this->rotateTo(leftJoinVerb, normals[0]); + this->lineTo(leftJoinVerb, P[3]); + this->rotateTo(Verb::kInternalRoundJoin, normals[1]); + return; + } + + // At^2 + Bt + C gives a vector tangent to the cubic. (More specifically, it's the derivative + // minus an irrelevant scale by 3, since all we care about is the direction.) + Sk2f A = p3 + (p1 - p2)*3 - p0; + Sk2f B = (p0 - p1*2 + p2)*2; + Sk2f C = p1 - p0; + + // Find a line segment that crosses max curvature. + float segmentLength = SkScalarInvert(numSegments); + float leftT = maxCurvatureT - segmentLength/2; + float rightT = maxCurvatureT + segmentLength/2; + Sk2f leftTan, rightTan; + if (leftT <= 0) { + leftT = 0; + leftTan = tan0; + rightT = segmentLength; + rightTan = A*rightT*rightT + B*rightT + C; + } else if (rightT >= 1) { + leftT = 1 - segmentLength; + leftTan = A*leftT*leftT + B*leftT + C; + rightT = 1; + rightTan = tan1; + } else { + leftTan = A*leftT*leftT + B*leftT + C; + rightTan = A*rightT*rightT + B*rightT + C; + } + + // Check if curvature is too strong for a triangle strip on the line segment that crosses max + // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins. + // + // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We + // would benefit significantly from a quick reject that detects curves that don't need special + // treatment for strong curvature. + bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta; + if (isCurvatureTooStrong) { + SkPoint ptsBuffer[7]; + p0.store(ptsBuffer); + p1.store(ptsBuffer + 1); + p2.store(ptsBuffer + 2); + p3.store(ptsBuffer + 3); + const SkPoint* currCubic = ptsBuffer; + + if (leftT > 0) { + SkChopCubicAt(currCubic, ptsBuffer, leftT); + this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1, + (kLeftMaxCurvatureNone != leftMaxCurvatureT) + ? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone, + kRightMaxCurvatureNone); + if (rightT < 1) { + rightT = (rightT - leftT) / (1 - leftT); + } + if (rightMaxCurvatureT < 1 && kRightMaxCurvatureNone != rightMaxCurvatureT) { + rightMaxCurvatureT = (rightMaxCurvatureT - leftT) / (1 - leftT); + } + currCubic = ptsBuffer + 3; + } else { + this->rotateTo(leftJoinVerb, normals[0]); + } + + if (rightT < 1) { + SkChopCubicAt(currCubic, ptsBuffer, rightT); + this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]); + currCubic = ptsBuffer + 3; + this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0, + kLeftMaxCurvatureNone, kRightMaxCurvatureNone); + } else { + this->lineTo(Verb::kInternalRoundJoin, currCubic[3]); + this->rotateTo(Verb::kInternalRoundJoin, normals[1]); + } + return; + } + + // Recurse and check the other two points of max curvature, if any. + if (kRightMaxCurvatureNone != rightMaxCurvatureT) { + this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT, + kRightMaxCurvatureNone); + return; + } + if (kLeftMaxCurvatureNone != leftMaxCurvatureT) { + SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT); + this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, kLeftMaxCurvatureNone, + kRightMaxCurvatureNone); + return; + } + + this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]); + fNormals.push_back_n(2, normals); + + this->recordStroke(Verb::kCubicStroke, SkNextLog2(numSegments)); + p1.store(&fPoints.push_back()); + p2.store(&fPoints.push_back()); + p3.store(&fPoints.push_back()); +} + +void GrCCStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) { + SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2); + SkASSERT(numSegmentsLog2 <= kMaxNumLinearSegmentsLog2); + fVerbs.push_back(verb); + if (Verb::kLinearStroke != verb) { + SkASSERT(numSegmentsLog2 > 0); + fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2; + } + ++fCurrStrokeTallies->fStrokes[numSegmentsLog2]; +} + +void GrCCStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal) { + this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal); + fNormals.push_back(normal); +} + +void GrCCStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) { + if (fNormals.count() <= fCurrContourFirstNormalIdx) { + // The contour is empty. Nothing to join with. + SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx); + return; + } + + if (Verb::kBevelJoin == joinVerb) { + this->recordBevelJoin(Verb::kBevelJoin); + return; + } + + Sk2f n0 = Sk2f::Load(&fNormals.back()); + Sk2f n1 = Sk2f::Load(&nextNormal); + Sk2f base = n1 - n0; + if ((base.abs() * fCurrStrokeRadius < kMaxErrorFromLinearization).allTrue()) { + // Treat any join as a bevel when the outside corners of the two adjoining strokes are + // close enough to each other. This is important because "miterCapHeightOverWidth" becomes + // unstable when n0 and n1 are nearly equal. + this->recordBevelJoin(joinVerb); + return; + } + + // We implement miters and round joins by placing a triangle-shaped cap on top of a bevel join. + // (For round joins this triangle cap comprises the conic control points.) Find how tall to make + // this triangle cap, relative to its width. + // + // NOTE: This value would be infinite at 180 degrees, but we clamp miterCapHeightOverWidth at + // near-infinity. 180-degree round joins still look perfectly acceptable like this (though + // technically not pure arcs). + Sk2f cross = base * SkNx_shuffle<1,0>(n0); + Sk2f dot = base * n0; + float miterCapHeight = SkScalarAbs(dot[0] + dot[1]); + float miterCapWidth = SkScalarAbs(cross[0] - cross[1]) * 2; + + if (Verb::kMiterJoin == joinVerb) { + if (miterCapHeight > fMiterMaxCapHeightOverWidth * miterCapWidth) { + // This join is tighter than the miter limit. Treat it as a bevel. + this->recordBevelJoin(Verb::kMiterJoin); + return; + } + this->recordMiterJoin(miterCapHeight / miterCapWidth); + return; + } + + SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb); + + // Conic arcs become unstable when they approach 180 degrees. When the conic control point + // begins shooting off to infinity (i.e., height/width > 32), split the conic into two. + static constexpr float kAlmost180Degrees = 32; + if (miterCapHeight > kAlmost180Degrees * miterCapWidth) { + Sk2f bisect = normalize(n0 - n1); + this->rotateTo(joinVerb, SkVector::Make(-bisect[1], bisect[0])); + this->recordLeftJoinIfNotEmpty(joinVerb, nextNormal); + return; + } + + float miterCapHeightOverWidth = miterCapHeight / miterCapWidth; + + // Find the heights of this round join's conic control point as well as the arc itself. + Sk2f X, Y; + transpose(base * base, n0 * n1, &X, &Y); + Sk2f r = Sk2f::Max(X + Y + Sk2f(0, 1), 0.f).sqrt(); + Sk2f heights = SkNx_fma(r, Sk2f(miterCapHeightOverWidth, -SK_ScalarRoot2Over2), Sk2f(0, 1)); + float controlPointHeight = SkScalarAbs(heights[0]); + float curveHeight = heights[1]; + if (curveHeight * fCurrStrokeRadius < kMaxErrorFromLinearization) { + // Treat round joins as bevels when their curvature is nearly flat. + this->recordBevelJoin(joinVerb); + return; + } + + float w = curveHeight / (controlPointHeight - curveHeight); + this->recordRoundJoin(joinVerb, miterCapHeightOverWidth, w); +} + +void GrCCStrokeGeometry::recordBevelJoin(Verb originalJoinVerb) { + if (!IsInternalJoinVerb(originalJoinVerb)) { + fVerbs.push_back(Verb::kBevelJoin); + ++fCurrStrokeTallies->fTriangles; + } else { + fVerbs.push_back(Verb::kInternalBevelJoin); + fCurrStrokeTallies->fTriangles += 2; + } +} + +void GrCCStrokeGeometry::recordMiterJoin(float miterCapHeightOverWidth) { + fVerbs.push_back(Verb::kMiterJoin); + fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth; + fCurrStrokeTallies->fTriangles += 2; +} + +void GrCCStrokeGeometry::recordRoundJoin(Verb joinVerb, float miterCapHeightOverWidth, + float conicWeight) { + fVerbs.push_back(joinVerb); + fParams.push_back().fConicWeight = conicWeight; + fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth; + if (Verb::kRoundJoin == joinVerb) { + ++fCurrStrokeTallies->fTriangles; + ++fCurrStrokeTallies->fConics; + } else { + SkASSERT(Verb::kInternalRoundJoin == joinVerb); + fCurrStrokeTallies->fTriangles += 2; + fCurrStrokeTallies->fConics += 2; + } +} + +void GrCCStrokeGeometry::closeContour() { + SkASSERT(fInsideContour); + SkASSERT(fPoints.count() > fCurrContourFirstPtIdx); + if (fPoints.back() != fPoints[fCurrContourFirstPtIdx]) { + // Draw a line back to the beginning. + this->lineTo(fCurrStrokeJoinVerb, fPoints[fCurrContourFirstPtIdx]); + } + if (fNormals.count() > fCurrContourFirstNormalIdx) { + // Join the first and last lines. + this->rotateTo(fCurrStrokeJoinVerb,fNormals[fCurrContourFirstNormalIdx]); + } else { + // This contour is empty. Add a bogus normal since the iterator always expects one. + SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx); + fNormals.push_back({0, 0}); + } + fVerbs.push_back(Verb::kEndContour); + SkDEBUGCODE(fInsideContour = false); +} + +void GrCCStrokeGeometry::capContourAndExit() { + SkASSERT(fInsideContour); + if (fCurrContourFirstNormalIdx >= fNormals.count()) { + // This contour is empty. Add a normal in the direction that caps orient on empty geometry. + SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx); + fNormals.push_back({1, 0}); + } + + this->recordCapsIfAny(); + fVerbs.push_back(Verb::kEndContour); + + SkDEBUGCODE(fInsideContour = false); +} + +void GrCCStrokeGeometry::recordCapsIfAny() { + SkASSERT(fInsideContour); + SkASSERT(fCurrContourFirstNormalIdx < fNormals.count()); + + if (SkPaint::kButt_Cap == fCurrStrokeCapType) { + return; + } + + Verb capVerb; + if (SkPaint::kSquare_Cap == fCurrStrokeCapType) { + if (fCurrStrokeRadius * SK_ScalarRoot2Over2 < kMaxErrorFromLinearization) { + return; + } + capVerb = Verb::kSquareCap; + fCurrStrokeTallies->fStrokes[0] += 2; + } else { + SkASSERT(SkPaint::kRound_Cap == fCurrStrokeCapType); + if (fCurrStrokeRadius < kMaxErrorFromLinearization) { + return; + } + capVerb = Verb::kRoundCap; + fCurrStrokeTallies->fTriangles += 2; + fCurrStrokeTallies->fConics += 4; + } + + fVerbs.push_back(capVerb); + fVerbs.push_back(Verb::kEndContour); + + fVerbs.push_back(capVerb); + + // Reserve the space first, since push_back() takes the point by reference and might + // invalidate the reference if the array grows. + fPoints.reserve(fPoints.count() + 1); + fPoints.push_back(fPoints[fCurrContourFirstPtIdx]); + + // Reserve the space first, since push_back() takes the normal by reference and might + // invalidate the reference if the array grows. (Although in this case we should be fine + // since there is a negate operator.) + fNormals.reserve(fNormals.count() + 1); + fNormals.push_back(-fNormals[fCurrContourFirstNormalIdx]); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.h b/gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.h new file mode 100644 index 0000000000..24569accc4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCStrokeGeometry.h @@ -0,0 +1,179 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGrCCStrokeGeometry_DEFINED +#define GrGrCCStrokeGeometry_DEFINED + +#include "include/core/SkPaint.h" +#include "include/core/SkPoint.h" +#include "include/private/SkTArray.h" + +class SkStrokeRec; + +/** + * This class converts device-space stroked paths into a set of independent strokes, joins, and caps + * that map directly to coverage-counted GPU instances. Non-hairline strokes can only be drawn with + * rigid body transforms; we don't yet support skewing the stroke lines themselves. + */ +class GrCCStrokeGeometry { +public: + static constexpr int kMaxNumLinearSegmentsLog2 = 15; + + GrCCStrokeGeometry(int numSkPoints = 0, int numSkVerbs = 0) + : fVerbs(numSkVerbs * 5/2) // Reserve for a 2.5x expansion in verbs. (Joins get their + // own separate verb in our representation.) + , fParams(numSkVerbs * 3) // Somewhere around 1-2 params per verb. + , fPoints(numSkPoints * 5/4) // Reserve for a 1.25x expansion in points and normals. + , fNormals(numSkPoints * 5/4) {} + + // A string of verbs and their corresponding, params, points, and normals are a compact + // representation of what will eventually be independent instances in GPU buffers. When added + // up, the combined coverage of all these instances will make complete stroked paths. + enum class Verb : uint8_t { + kBeginPath, // Instructs the iterator to advance its stroke width, atlas offset, etc. + + // Independent strokes of a single line or curve, with (antialiased) butt caps on the ends. + kLinearStroke, + kQuadraticStroke, + kCubicStroke, + + // Joins are a triangles that connect the outer corners of two adjoining strokes. Miters + // have an additional triangle cap on top of the bevel, and round joins have an arc on top. + kBevelJoin, + kMiterJoin, + kRoundJoin, + + // We use internal joins when we have to internally break up a stroke because its curvature + // is too strong for a triangle strip. They are coverage-counted, self-intersecting + // quadrilaterals that tie the four corners of two adjoining strokes together a like a + // shoelace. (Coverage is negative on the inside half.) We place an arc on both ends of an + // internal round join. + kInternalBevelJoin, + kInternalRoundJoin, + + kSquareCap, + kRoundCap, + + kEndContour // Instructs the iterator to advance its internal point and normal ptrs. + }; + static bool IsInternalJoinVerb(Verb verb); + + // Some verbs require additional parameters(s). + union Parameter { + // For cubic and quadratic strokes: How many flat line segments to chop the curve into? + int fNumLinearSegmentsLog2; + // For miter and round joins: How tall should the triangle cap be on top of the join? + // (This triangle is the conic control points for a round join.) + float fMiterCapHeightOverWidth; + float fConicWeight; // Round joins only. + }; + + const SkTArray& verbs() const { SkASSERT(!fInsideContour); return fVerbs; } + const SkTArray& params() const { SkASSERT(!fInsideContour); return fParams; } + const SkTArray& points() const { SkASSERT(!fInsideContour); return fPoints; } + const SkTArray& normals() const { SkASSERT(!fInsideContour); return fNormals; } + + // These track the numbers of instances required to draw all the recorded strokes. + struct InstanceTallies { + int fStrokes[kMaxNumLinearSegmentsLog2 + 1]; + int fTriangles; + int fConics; + + InstanceTallies operator+(const InstanceTallies&) const; + }; + + void beginPath(const SkStrokeRec&, float strokeDevWidth, InstanceTallies*); + void moveTo(SkPoint); + void lineTo(SkPoint); + void quadraticTo(const SkPoint[3]); + void cubicTo(const SkPoint[4]); + void closeContour(); // Connect back to the first point in the contour and exit. + void capContourAndExit(); // Add endcaps (if any) and exit the contour. + +private: + void lineTo(Verb leftJoinVerb, SkPoint); + void quadraticTo(Verb leftJoinVerb, const SkPoint[3], float maxCurvatureT); + + static constexpr float kLeftMaxCurvatureNone = 1; + static constexpr float kRightMaxCurvatureNone = 0; + void cubicTo(Verb leftJoinVerb, const SkPoint[4], float maxCurvatureT, float leftMaxCurvatureT, + float rightMaxCurvatureT); + + // Pushes a new normal to fNormals and records a join, without changing the current position. + void rotateTo(Verb leftJoinVerb, SkVector normal); + + // Records a stroke in fElememts. + void recordStroke(Verb, int numSegmentsLog2); + + // Records a join in fElememts with the previous stroke, if the cuurent contour is not empty. + void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal); + void recordBevelJoin(Verb originalJoinVerb); + void recordMiterJoin(float miterCapHeightOverWidth); + void recordRoundJoin(Verb roundJoinVerb, float miterCapHeightOverWidth, float conicWeight); + + void recordCapsIfAny(); + + float fCurrStrokeRadius; + Verb fCurrStrokeJoinVerb; + SkPaint::Cap fCurrStrokeCapType; + InstanceTallies* fCurrStrokeTallies = nullptr; + + // We implement miters by placing a triangle-shaped cap on top of a bevel join. This field tells + // us what the miter limit is, restated in terms of how tall that triangle cap can be. + float fMiterMaxCapHeightOverWidth; + + // Any curvature on the original curve gets magnified on the outer edge of the stroke, + // proportional to how thick the stroke radius is. This field tells us the maximum curvature we + // can tolerate using the current stroke radius, before linearization artifacts begin to appear + // on the outer edge. + // + // (Curvature this strong is quite rare in practice, but when it does happen, we decompose the + // section with strong curvature into lineTo's with round joins in between.) + float fMaxCurvatureCosTheta; + + int fCurrContourFirstPtIdx; + int fCurrContourFirstNormalIdx; + + SkDEBUGCODE(bool fInsideContour = false); + + SkSTArray<128, Verb, true> fVerbs; + SkSTArray<128, Parameter, true> fParams; + SkSTArray<128, SkPoint, true> fPoints; + SkSTArray<128, SkVector, true> fNormals; +}; + +inline GrCCStrokeGeometry::InstanceTallies GrCCStrokeGeometry::InstanceTallies::operator+( + const InstanceTallies& t) const { + InstanceTallies ret; + for (int i = 0; i <= kMaxNumLinearSegmentsLog2; ++i) { + ret.fStrokes[i] = fStrokes[i] + t.fStrokes[i]; + } + ret.fTriangles = fTriangles + t.fTriangles; + ret.fConics = fConics + t.fConics; + return ret; +} + +inline bool GrCCStrokeGeometry::IsInternalJoinVerb(Verb verb) { + switch (verb) { + case Verb::kInternalBevelJoin: + case Verb::kInternalRoundJoin: + return true; + case Verb::kBeginPath: + case Verb::kLinearStroke: + case Verb::kQuadraticStroke: + case Verb::kCubicStroke: + case Verb::kBevelJoin: + case Verb::kMiterJoin: + case Verb::kRoundJoin: + case Verb::kSquareCap: + case Verb::kRoundCap: + case Verb::kEndContour: + return false; + } + SK_ABORT("Invalid GrCCStrokeGeometry::Verb."); +} +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCStroker.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCCStroker.cpp new file mode 100644 index 0000000000..cbfa5ae7f2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCStroker.cpp @@ -0,0 +1,836 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCCStroker.h" + +#include "include/core/SkStrokeRec.h" +#include "src/core/SkPathPriv.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +static constexpr int kMaxNumLinearSegmentsLog2 = GrCCStrokeGeometry::kMaxNumLinearSegmentsLog2; +using TriangleInstance = GrCCCoverageProcessor::TriPointInstance; +using ConicInstance = GrCCCoverageProcessor::QuadPointInstance; + +namespace { + +struct LinearStrokeInstance { + float fEndpoints[4]; + float fStrokeRadius; + + inline void set(const SkPoint[2], float dx, float dy, float strokeRadius); +}; + +inline void LinearStrokeInstance::set(const SkPoint P[2], float dx, float dy, float strokeRadius) { + Sk2f X, Y; + Sk2f::Load2(P, &X, &Y); + Sk2f::Store2(fEndpoints, X + dx, Y + dy); + fStrokeRadius = strokeRadius; +} + +struct CubicStrokeInstance { + float fX[4]; + float fY[4]; + float fStrokeRadius; + float fNumSegments; + + inline void set(const SkPoint[4], float dx, float dy, float strokeRadius, int numSegments); + inline void set(const Sk4f& X, const Sk4f& Y, float dx, float dy, float strokeRadius, + int numSegments); +}; + +inline void CubicStrokeInstance::set(const SkPoint P[4], float dx, float dy, float strokeRadius, + int numSegments) { + Sk4f X, Y; + Sk4f::Load2(P, &X, &Y); + this->set(X, Y, dx, dy, strokeRadius, numSegments); +} + +inline void CubicStrokeInstance::set(const Sk4f& X, const Sk4f& Y, float dx, float dy, + float strokeRadius, int numSegments) { + (X + dx).store(&fX); + (Y + dy).store(&fY); + fStrokeRadius = strokeRadius; + fNumSegments = static_cast(numSegments); +} + +// This class draws stroked lines in post-transform device space (a.k.a. rectangles). Rigid-body +// transforms can be achieved by transforming the line ahead of time and adjusting the stroke +// width. Skews of the stroke itself are not yet supported. +// +// Corner coverage is AA-correct, meaning, n^2 attenuation along the diagonals. This is important +// for seamless integration with the connecting geometry. +class LinearStrokeProcessor : public GrGeometryProcessor { +public: + LinearStrokeProcessor() : GrGeometryProcessor(kLinearStrokeProcessor_ClassID) { + this->setInstanceAttributes(kInstanceAttribs, 2); +#ifdef SK_DEBUG + using Instance = LinearStrokeInstance; + SkASSERT(this->instanceStride() == sizeof(Instance)); +#endif + } + +private: + const char* name() const override { return "LinearStrokeProcessor"; } + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {} + + static constexpr Attribute kInstanceAttribs[2] = { + {"endpts", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + {"stroke_radius", kFloat_GrVertexAttribType, kFloat_GrSLType} + }; + + class Impl : public GrGLSLGeometryProcessor { + void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&&) override {} + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override; + }; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new Impl(); + } +}; + +void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniHandler = args.fUniformHandler; + + varyingHandler->emitAttributes(args.fGP.cast()); + + GrGLSLVertexBuilder* v = args.fVertBuilder; + v->codeAppend ("float2 tan = normalize(endpts.zw - endpts.xy);"); + v->codeAppend ("float2 n = float2(tan.y, -tan.x);"); + v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);"); + + // Outset the vertex position for AA butt caps. + v->codeAppend ("float2 outset = tan*nwidth/2;"); + v->codeAppend ("float2 position = (sk_VertexID < 2) " + "? endpts.xy - outset : endpts.zw + outset;"); + + // Calculate Manhattan distance from both butt caps, where distance=0 on the actual endpoint and + // distance=-.5 on the outset edge. + GrGLSLVarying edgeDistances(kFloat4_GrSLType); + varyingHandler->addVarying("edge_distances", &edgeDistances); + v->codeAppendf("%s.xz = float2(-.5, dot(endpts.zw - endpts.xy, tan) / nwidth + .5);", + edgeDistances.vsOut()); + v->codeAppendf("%s.xz = (sk_VertexID < 2) ? %s.xz : %s.zx;", + edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut()); + + // Outset the vertex position for stroke radius plus edge AA. + v->codeAppend ("outset = n * (stroke_radius + nwidth/2);"); + v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? +outset : -outset;"); + + // Calculate Manhattan distance from both edges, where distance=0 on the actual edge and + // distance=-.5 on the outset. + v->codeAppendf("%s.yw = float2(-.5, 2*stroke_radius / nwidth + .5);", edgeDistances.vsOut()); + v->codeAppendf("%s.yw = (0 == (sk_VertexID & 1)) ? %s.yw : %s.wy;", + edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut()); + + gpArgs->fPositionVar.set(kFloat2_GrSLType, "position"); + this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType), + SkMatrix::I(), args.fFPCoordTransformHandler); + + // Use the 4 edge distances to calculate coverage in the fragment shader. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + f->codeAppendf("half2 coverages = half2(min(%s.xy, .5) + min(%s.zw, .5));", + edgeDistances.fsIn(), edgeDistances.fsIn()); + f->codeAppendf("%s = half4(coverages.x * coverages.y);", args.fOutputColor); + + // This shader doesn't use the built-in Ganesh coverage. + f->codeAppendf("%s = half4(1);", args.fOutputCoverage); +} + +constexpr GrPrimitiveProcessor::Attribute LinearStrokeProcessor::kInstanceAttribs[]; + +// This class draws stroked cubics in post-transform device space. Rigid-body transforms can be +// achieved by transforming the curve ahead of time and adjusting the stroke width. Skews of the +// stroke itself are not yet supported. Quadratics can be drawn by converting them to cubics. +// +// This class works by finding stroke-width line segments orthogonal to the curve at a +// pre-determined number of evenly spaced points along the curve (evenly spaced in the parametric +// sense). It then connects the segments with a triangle strip. As for common in CCPR, clockwise- +// winding triangles from the strip emit positive coverage, counter-clockwise triangles emit +// negative, and we use SkBlendMode::kPlus. +class CubicStrokeProcessor : public GrGeometryProcessor { +public: + CubicStrokeProcessor() : GrGeometryProcessor(kCubicStrokeProcessor_ClassID) { + this->setInstanceAttributes(kInstanceAttribs, 3); +#ifdef SK_DEBUG + using Instance = CubicStrokeInstance; + SkASSERT(this->instanceStride() == sizeof(Instance)); +#endif + } + +private: + const char* name() const override { return "CubicStrokeProcessor"; } + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {} + + static constexpr Attribute kInstanceAttribs[3] = { + {"X", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + {"Y", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + {"stroke_info", kFloat2_GrVertexAttribType, kFloat2_GrSLType} + }; + + class Impl : public GrGLSLGeometryProcessor { + void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&&) override {} + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override; + }; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new Impl(); + } +}; + +void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniHandler = args.fUniformHandler; + + varyingHandler->emitAttributes(args.fGP.cast()); + + GrGLSLVertexBuilder* v = args.fVertBuilder; + v->codeAppend ("float4x2 P = transpose(float2x4(X, Y));"); + v->codeAppend ("float stroke_radius = stroke_info[0];"); + v->codeAppend ("float num_segments = stroke_info[1];"); + + // Find the parametric T value at which we will emit our orthogonal line segment. We emit two + // line segments at T=0 and double at T=1 as well for AA butt caps. + v->codeAppend ("float point_id = float(sk_VertexID/2);"); + v->codeAppend ("float T = max((point_id - 1) / num_segments, 0);"); + v->codeAppend ("T = (point_id >= num_segments + 1) ? 1 : T;"); // In case x/x !== 1. + + // Use De Casteljau's algorithm to find the position and tangent for our orthogonal line + // segment. De Casteljau's is more numerically stable than evaluating the curve and derivative + // directly. + v->codeAppend ("float2 ab = mix(P[0], P[1], T);"); + v->codeAppend ("float2 bc = mix(P[1], P[2], T);"); + v->codeAppend ("float2 cd = mix(P[2], P[3], T);"); + v->codeAppend ("float2 abc = mix(ab, bc, T);"); + v->codeAppend ("float2 bcd = mix(bc, cd, T);"); + v->codeAppend ("float2 position = mix(abc, bcd, T);"); + v->codeAppend ("float2 tan = bcd - abc;"); + + // Find actual tangents for the corner cases when De Casteljau's yields tan=0. (We shouldn't + // encounter other numerically unstable cases where tan ~= 0, because GrCCStrokeGeometry snaps + // control points to endpoints in curves where they are almost equal.) + v->codeAppend ("if (0 == T && P[0] == P[1]) {"); + v->codeAppend ( "tan = P[2] - P[0];"); + v->codeAppend ("}"); + v->codeAppend ("if (1 == T && P[2] == P[3]) {"); + v->codeAppend ( "tan = P[3] - P[1];"); + v->codeAppend ("}"); + v->codeAppend ("tan = normalize(tan);"); + v->codeAppend ("float2 n = float2(tan.y, -tan.x);"); + v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);"); + + // Outset the vertex position for stroke radius plus edge AA. + v->codeAppend ("float2 outset = n * (stroke_radius + nwidth/2);"); + v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? -outset : +outset;"); + + // Calculate the Manhattan distance from both edges, where distance=0 on the actual edge and + // distance=-.5 on the outset. + GrGLSLVarying coverages(kFloat3_GrSLType); + varyingHandler->addVarying("coverages", &coverages); + v->codeAppendf("%s.xy = float2(-.5, 2*stroke_radius / nwidth + .5);", coverages.vsOut()); + v->codeAppendf("%s.xy = (0 == (sk_VertexID & 1)) ? %s.xy : %s.yx;", + coverages.vsOut(), coverages.vsOut(), coverages.vsOut()); + + // Adjust the orthogonal line segments on the endpoints so they straddle the actual endpoint + // at a Manhattan distance of .5 on either side. + v->codeAppend ("if (0 == point_id || num_segments+1 == point_id) {"); + v->codeAppend ( "position -= tan*nwidth/2;"); + v->codeAppend ("}"); + v->codeAppend ("if (1 == point_id || num_segments+2 == point_id) {"); + v->codeAppend ( "position += tan*nwidth/2;"); + v->codeAppend ("}"); + + // Interpolate coverage for butt cap AA from 0 on the outer segment to 1 on the inner. + v->codeAppendf("%s.z = (0 == point_id || num_segments+2 == point_id) ? 0 : 1;", + coverages.vsOut()); + + gpArgs->fPositionVar.set(kFloat2_GrSLType, "position"); + this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType), + SkMatrix::I(), args.fFPCoordTransformHandler); + + // Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + f->codeAppendf("half2 edge_coverages = min(half2(%s.xy), .5);", coverages.fsIn()); + f->codeAppend ("half coverage = edge_coverages.x + edge_coverages.y;"); + f->codeAppendf("coverage *= half(%s.z);", coverages.fsIn()); // Butt cap AA. + + // As is common for CCPR, clockwise-winding triangles from the strip emit positive coverage, and + // counter-clockwise triangles emit negative. + f->codeAppendf("%s = half4(sk_Clockwise ? +coverage : -coverage);", args.fOutputColor); + + // This shader doesn't use the built-in Ganesh coverage. + f->codeAppendf("%s = half4(1);", args.fOutputCoverage); +} + +constexpr GrPrimitiveProcessor::Attribute CubicStrokeProcessor::kInstanceAttribs[]; + +} // anonymous namespace + +void GrCCStroker::parseDeviceSpaceStroke(const SkPath& path, const SkPoint* deviceSpacePts, + const SkStrokeRec& stroke, float strokeDevWidth, + GrScissorTest scissorTest, + const SkIRect& clippedDevIBounds, + const SkIVector& devToAtlasOffset) { + SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() || + SkStrokeRec::kHairline_Style == stroke.getStyle()); + SkASSERT(!fInstanceBuffer); + SkASSERT(!path.isEmpty()); + + if (!fHasOpenBatch) { + fBatches.emplace_back(&fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kDisabled], + fScissorSubBatches.count()); + fInstanceCounts[(int)GrScissorTest::kDisabled] = fBatches.back().fNonScissorEndInstances; + fHasOpenBatch = true; + } + + InstanceTallies* currStrokeEndIndices; + if (GrScissorTest::kEnabled == scissorTest) { + SkASSERT(fBatches.back().fEndScissorSubBatch == fScissorSubBatches.count()); + fScissorSubBatches.emplace_back(&fTalliesAllocator, + *fInstanceCounts[(int)GrScissorTest::kEnabled], + clippedDevIBounds.makeOffset(devToAtlasOffset)); + fBatches.back().fEndScissorSubBatch = fScissorSubBatches.count(); + fInstanceCounts[(int)GrScissorTest::kEnabled] = + currStrokeEndIndices = fScissorSubBatches.back().fEndInstances; + } else { + currStrokeEndIndices = fBatches.back().fNonScissorEndInstances; + } + + fGeometry.beginPath(stroke, strokeDevWidth, currStrokeEndIndices); + + fPathInfos.push_back() = {devToAtlasOffset, strokeDevWidth/2, scissorTest}; + + int devPtsIdx = 0; + SkPath::Verb previousVerb = SkPath::kClose_Verb; + + for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { + SkASSERT(SkPath::kDone_Verb != previousVerb); + const SkPoint* P = &deviceSpacePts[devPtsIdx - 1]; + switch (verb) { + case SkPath::kMove_Verb: + if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) { + fGeometry.capContourAndExit(); + } + fGeometry.moveTo(deviceSpacePts[devPtsIdx]); + ++devPtsIdx; + break; + case SkPath::kClose_Verb: + SkASSERT(SkPath::kClose_Verb != previousVerb); + fGeometry.closeContour(); + break; + case SkPath::kLine_Verb: + SkASSERT(SkPath::kClose_Verb != previousVerb); + fGeometry.lineTo(P[1]); + ++devPtsIdx; + break; + case SkPath::kQuad_Verb: + SkASSERT(SkPath::kClose_Verb != previousVerb); + fGeometry.quadraticTo(P); + devPtsIdx += 2; + break; + case SkPath::kCubic_Verb: { + SkASSERT(SkPath::kClose_Verb != previousVerb); + fGeometry.cubicTo(P); + devPtsIdx += 3; + break; + } + case SkPath::kConic_Verb: + SkASSERT(SkPath::kClose_Verb != previousVerb); + SK_ABORT("Stroked conics not supported."); + break; + case SkPath::kDone_Verb: + break; + } + previousVerb = verb; + } + + if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) { + fGeometry.capContourAndExit(); + } +} + +// This class encapsulates the process of expanding ready-to-draw geometry from GrCCStrokeGeometry +// directly into GPU instance buffers. +class GrCCStroker::InstanceBufferBuilder { +public: + InstanceBufferBuilder(GrOnFlushResourceProvider* onFlushRP, GrCCStroker* stroker) { + memcpy(fNextInstances, stroker->fBaseInstances, sizeof(fNextInstances)); +#ifdef SK_DEBUG + fEndInstances[0] = stroker->fBaseInstances[0] + *stroker->fInstanceCounts[0]; + fEndInstances[1] = stroker->fBaseInstances[1] + *stroker->fInstanceCounts[1]; +#endif + + int endConicsIdx = stroker->fBaseInstances[1].fConics + + stroker->fInstanceCounts[1]->fConics; + fInstanceBuffer = onFlushRP->makeBuffer(GrGpuBufferType::kVertex, + endConicsIdx * sizeof(ConicInstance)); + if (!fInstanceBuffer) { + SkDebugf("WARNING: failed to allocate CCPR stroke instance buffer.\n"); + return; + } + fInstanceBufferData = fInstanceBuffer->map(); + } + + bool isMapped() const { return SkToBool(fInstanceBufferData); } + + void updateCurrentInfo(const PathInfo& pathInfo) { + SkASSERT(this->isMapped()); + fCurrDX = static_cast(pathInfo.fDevToAtlasOffset.x()); + fCurrDY = static_cast(pathInfo.fDevToAtlasOffset.y()); + fCurrStrokeRadius = pathInfo.fStrokeRadius; + fCurrNextInstances = &fNextInstances[(int)pathInfo.fScissorTest]; + SkDEBUGCODE(fCurrEndInstances = &fEndInstances[(int)pathInfo.fScissorTest]); + } + + void appendLinearStroke(const SkPoint endpts[2]) { + SkASSERT(this->isMapped()); + this->appendLinearStrokeInstance().set(endpts, fCurrDX, fCurrDY, fCurrStrokeRadius); + } + + void appendQuadraticStroke(const SkPoint P[3], int numLinearSegmentsLog2) { + SkASSERT(this->isMapped()); + SkASSERT(numLinearSegmentsLog2 > 0); + + Sk4f ptsT[2]; + Sk2f p0 = Sk2f::Load(P); + Sk2f p1 = Sk2f::Load(P+1); + Sk2f p2 = Sk2f::Load(P+2); + + // Convert the quadratic to cubic. + Sk2f c1 = SkNx_fma(Sk2f(2/3.f), p1 - p0, p0); + Sk2f c2 = SkNx_fma(Sk2f(1/3.f), p2 - p1, p1); + Sk2f::Store4(ptsT, p0, c1, c2, p2); + + this->appendCubicStrokeInstance(numLinearSegmentsLog2).set( + ptsT[0], ptsT[1], fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2); + } + + void appendCubicStroke(const SkPoint P[3], int numLinearSegmentsLog2) { + SkASSERT(this->isMapped()); + SkASSERT(numLinearSegmentsLog2 > 0); + this->appendCubicStrokeInstance(numLinearSegmentsLog2).set( + P, fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2); + } + + void appendJoin(Verb joinVerb, const SkPoint& center, const SkVector& leftNorm, + const SkVector& rightNorm, float miterCapHeightOverWidth, float conicWeight) { + SkASSERT(this->isMapped()); + + Sk2f offset = Sk2f::Load(¢er) + Sk2f(fCurrDX, fCurrDY); + Sk2f n0 = Sk2f::Load(&leftNorm); + Sk2f n1 = Sk2f::Load(&rightNorm); + + // Identify the outer edge. + Sk2f cross = n0 * SkNx_shuffle<1,0>(n1); + if (cross[0] < cross[1]) { + Sk2f tmp = n0; + n0 = -n1; + n1 = -tmp; + } + + if (!GrCCStrokeGeometry::IsInternalJoinVerb(joinVerb)) { + // Normal joins are a triangle that connects the outer corners of two adjoining strokes. + this->appendTriangleInstance().set( + n1 * fCurrStrokeRadius, Sk2f(0, 0), n0 * fCurrStrokeRadius, offset, + TriangleInstance::Ordering::kXYTransposed); + if (Verb::kBevelJoin == joinVerb) { + return; + } + } else { + // Internal joins are coverage-counted, self-intersecting quadrilaterals that tie the + // four corners of two adjoining strokes together a like a shoelace. Coverage is + // negative on the inside half. We implement this geometry with a pair of triangles. + this->appendTriangleInstance().set( + -n0 * fCurrStrokeRadius, n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius, + offset, TriangleInstance::Ordering::kXYTransposed); + if (Verb::kBevelJoin == joinVerb) { + return; + } + this->appendTriangleInstance().set( + -n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius, -n1 * fCurrStrokeRadius, + offset, TriangleInstance::Ordering::kXYTransposed); + if (Verb::kBevelJoin == joinVerb) { + return; + } + if (Verb::kInternalBevelJoin == joinVerb) { + return; + } + } + + // For miter and round joins, we place an additional triangle cap on top of the bevel. This + // triangle is literal for miters and is conic control points for round joins. + SkASSERT(miterCapHeightOverWidth >= 0 || SkScalarIsNaN(miterCapHeightOverWidth)); + Sk2f base = n1 - n0; + Sk2f baseNorm = Sk2f(base[1], -base[0]); + Sk2f c = (n0 + n1) * .5f + baseNorm * miterCapHeightOverWidth; + + if (Verb::kMiterJoin == joinVerb) { + this->appendTriangleInstance().set( + n0 * fCurrStrokeRadius, c * fCurrStrokeRadius, n1 * fCurrStrokeRadius, offset, + TriangleInstance::Ordering::kXYTransposed); + } else { + SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb); + this->appendConicInstance().setW(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius, + n1 * fCurrStrokeRadius, offset, conicWeight); + if (Verb::kInternalRoundJoin == joinVerb) { + this->appendConicInstance().setW(-n1 * fCurrStrokeRadius, c * -fCurrStrokeRadius, + -n0 * fCurrStrokeRadius, offset, conicWeight); + } + } + } + + void appendCap(Verb capType, const SkPoint& pt, const SkVector& norm) { + SkASSERT(this->isMapped()); + + Sk2f n = Sk2f::Load(&norm) * fCurrStrokeRadius; + Sk2f v = Sk2f(-n[1], n[0]); + Sk2f offset = Sk2f::Load(&pt) + Sk2f(fCurrDX, fCurrDY); + + if (Verb::kSquareCap == capType) { + SkPoint endPts[2] = {{0, 0}, {v[0], v[1]}}; + this->appendLinearStrokeInstance().set(endPts, offset[0], offset[1], fCurrStrokeRadius); + } else { + SkASSERT(Verb::kRoundCap == capType); + this->appendTriangleInstance().set( + n, v, -n, offset, TriangleInstance::Ordering::kXYTransposed); + this->appendConicInstance().setW(n, n + v, v, offset, SK_ScalarRoot2Over2); + this->appendConicInstance().setW(v, v - n, -n, offset, SK_ScalarRoot2Over2); + } + } + + sk_sp finish() { + SkASSERT(this->isMapped()); + SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances))); + fInstanceBuffer->unmap(); + fInstanceBufferData = nullptr; + SkASSERT(!this->isMapped()); + return std::move(fInstanceBuffer); + } + +private: + LinearStrokeInstance& appendLinearStrokeInstance() { + int instanceIdx = fCurrNextInstances->fStrokes[0]++; + SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[0]); + + return reinterpret_cast(fInstanceBufferData)[instanceIdx]; + } + + CubicStrokeInstance& appendCubicStrokeInstance(int numLinearSegmentsLog2) { + SkASSERT(numLinearSegmentsLog2 > 0); + SkASSERT(numLinearSegmentsLog2 <= kMaxNumLinearSegmentsLog2); + + int instanceIdx = fCurrNextInstances->fStrokes[numLinearSegmentsLog2]++; + SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[numLinearSegmentsLog2]); + + return reinterpret_cast(fInstanceBufferData)[instanceIdx]; + } + + TriangleInstance& appendTriangleInstance() { + int instanceIdx = fCurrNextInstances->fTriangles++; + SkASSERT(instanceIdx < fCurrEndInstances->fTriangles); + + return reinterpret_cast(fInstanceBufferData)[instanceIdx]; + } + + ConicInstance& appendConicInstance() { + int instanceIdx = fCurrNextInstances->fConics++; + SkASSERT(instanceIdx < fCurrEndInstances->fConics); + + return reinterpret_cast(fInstanceBufferData)[instanceIdx]; + } + + float fCurrDX, fCurrDY; + float fCurrStrokeRadius; + InstanceTallies* fCurrNextInstances; + SkDEBUGCODE(const InstanceTallies* fCurrEndInstances); + + sk_sp fInstanceBuffer; + void* fInstanceBufferData = nullptr; + InstanceTallies fNextInstances[2]; + SkDEBUGCODE(InstanceTallies fEndInstances[2]); +}; + +GrCCStroker::BatchID GrCCStroker::closeCurrentBatch() { + if (!fHasOpenBatch) { + return kEmptyBatchID; + } + int start = (fBatches.count() < 2) ? 0 : fBatches[fBatches.count() - 2].fEndScissorSubBatch; + int end = fBatches.back().fEndScissorSubBatch; + fMaxNumScissorSubBatches = SkTMax(fMaxNumScissorSubBatches, end - start); + fHasOpenBatch = false; + return fBatches.count() - 1; +} + +bool GrCCStroker::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) { + SkASSERT(!fInstanceBuffer); + SkASSERT(!fHasOpenBatch); // Call closeCurrentBatch() first. + + // Here we layout a single instance buffer to share with every internal batch. + // + // Rather than place each instance array in its own GPU buffer, we allocate a single + // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in + // our draw calls to direct the GPU to the applicable elements within a given array. + fBaseInstances[0].fStrokes[0] = 0; + fBaseInstances[1].fStrokes[0] = fInstanceCounts[0]->fStrokes[0]; + int endLinearStrokesIdx = fBaseInstances[1].fStrokes[0] + fInstanceCounts[1]->fStrokes[0]; + + int cubicStrokesIdx = GrSizeDivRoundUp(endLinearStrokesIdx * sizeof(LinearStrokeInstance), + sizeof(CubicStrokeInstance)); + for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) { + for (int j = 0; j < kNumScissorModes; ++j) { + fBaseInstances[j].fStrokes[i] = cubicStrokesIdx; + cubicStrokesIdx += fInstanceCounts[j]->fStrokes[i]; + } + } + + int trianglesIdx = GrSizeDivRoundUp(cubicStrokesIdx * sizeof(CubicStrokeInstance), + sizeof(TriangleInstance)); + fBaseInstances[0].fTriangles = trianglesIdx; + fBaseInstances[1].fTriangles = + fBaseInstances[0].fTriangles + fInstanceCounts[0]->fTriangles; + int endTrianglesIdx = + fBaseInstances[1].fTriangles + fInstanceCounts[1]->fTriangles; + + int conicsIdx = + GrSizeDivRoundUp(endTrianglesIdx * sizeof(TriangleInstance), sizeof(ConicInstance)); + fBaseInstances[0].fConics = conicsIdx; + fBaseInstances[1].fConics = fBaseInstances[0].fConics + fInstanceCounts[0]->fConics; + + InstanceBufferBuilder builder(onFlushRP, this); + if (!builder.isMapped()) { + return false; // Buffer allocation failed. + } + + // Now parse the GrCCStrokeGeometry and expand it into the instance buffer. + int pathIdx = 0; + int ptsIdx = 0; + int paramsIdx = 0; + int normalsIdx = 0; + + const SkTArray& params = fGeometry.params(); + const SkTArray& pts = fGeometry.points(); + const SkTArray& normals = fGeometry.normals(); + + float miterCapHeightOverWidth=0, conicWeight=0; + + for (Verb verb : fGeometry.verbs()) { + switch (verb) { + case Verb::kBeginPath: + builder.updateCurrentInfo(fPathInfos[pathIdx]); + ++pathIdx; + continue; + + case Verb::kLinearStroke: + builder.appendLinearStroke(&pts[ptsIdx]); + ++ptsIdx; + continue; + case Verb::kQuadraticStroke: + builder.appendQuadraticStroke(&pts[ptsIdx], + params[paramsIdx++].fNumLinearSegmentsLog2); + ptsIdx += 2; + ++normalsIdx; + continue; + case Verb::kCubicStroke: + builder.appendCubicStroke(&pts[ptsIdx], params[paramsIdx++].fNumLinearSegmentsLog2); + ptsIdx += 3; + ++normalsIdx; + continue; + + case Verb::kRoundJoin: + case Verb::kInternalRoundJoin: + conicWeight = params[paramsIdx++].fConicWeight; + // fallthru + case Verb::kMiterJoin: + miterCapHeightOverWidth = params[paramsIdx++].fMiterCapHeightOverWidth; + // fallthru + case Verb::kBevelJoin: + case Verb::kInternalBevelJoin: + builder.appendJoin(verb, pts[ptsIdx], normals[normalsIdx], normals[normalsIdx + 1], + miterCapHeightOverWidth, conicWeight); + ++normalsIdx; + continue; + + case Verb::kSquareCap: + case Verb::kRoundCap: + builder.appendCap(verb, pts[ptsIdx], normals[normalsIdx]); + continue; + + case Verb::kEndContour: + ++ptsIdx; + ++normalsIdx; + continue; + } + SK_ABORT("Invalid CCPR stroke element."); + } + + fInstanceBuffer = builder.finish(); + SkASSERT(fPathInfos.count() == pathIdx); + SkASSERT(pts.count() == ptsIdx); + SkASSERT(normals.count() == normalsIdx); + + fMeshesBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2); + fScissorsBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2); + return true; +} + +void GrCCStroker::drawStrokes(GrOpFlushState* flushState, GrCCCoverageProcessor* proc, + BatchID batchID, const SkIRect& drawBounds) const { + using PrimitiveType = GrCCCoverageProcessor::PrimitiveType; + SkASSERT(fInstanceBuffer); + + if (kEmptyBatchID == batchID) { + return; + } + const Batch& batch = fBatches[batchID]; + int startScissorSubBatch = (!batchID) ? 0 : fBatches[batchID - 1].fEndScissorSubBatch; + + const InstanceTallies* startIndices[2]; + startIndices[(int)GrScissorTest::kDisabled] = (!batchID) + ? &fZeroTallies : fBatches[batchID - 1].fNonScissorEndInstances; + startIndices[(int)GrScissorTest::kEnabled] = (!startScissorSubBatch) + ? &fZeroTallies : fScissorSubBatches[startScissorSubBatch - 1].fEndInstances; + + GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus, + flushState->drawOpArgs().outputSwizzle()); + + // Draw linear strokes. + this->appendStrokeMeshesToBuffers(0, batch, startIndices, startScissorSubBatch, drawBounds); + if (!fMeshesBuffer.empty()) { + LinearStrokeProcessor linearProc; + this->flushBufferedMeshesAsStrokes(linearProc, flushState, pipeline, drawBounds); + } + + // Draw cubic strokes. (Quadratics were converted to cubics for GPU processing.) + for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) { + this->appendStrokeMeshesToBuffers(i, batch, startIndices, startScissorSubBatch, drawBounds); + } + if (!fMeshesBuffer.empty()) { + CubicStrokeProcessor cubicProc; + this->flushBufferedMeshesAsStrokes(cubicProc, flushState, pipeline, drawBounds); + } + + // Draw triangles. + proc->reset(PrimitiveType::kTriangles, flushState->resourceProvider()); + this->drawConnectingGeometry<&InstanceTallies::fTriangles>( + flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds); + + // Draw conics. + proc->reset(PrimitiveType::kConics, flushState->resourceProvider()); + this->drawConnectingGeometry<&InstanceTallies::fConics>( + flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds); +} + +void GrCCStroker::appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch& batch, + const InstanceTallies* startIndices[2], + int startScissorSubBatch, + const SkIRect& drawBounds) const { + // Linear strokes draw a quad. Cubic strokes emit a strip with normals at "numSegments" + // evenly-spaced points along the curve, plus one more for the final endpoint, plus two more for + // AA butt caps. (i.e., 2 vertices * (numSegments + 3).) + int numStripVertices = (0 == numSegmentsLog2) ? 4 : ((1 << numSegmentsLog2) + 3) * 2; + + // Append non-scissored meshes. + int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].fStrokes[numSegmentsLog2]; + int startIdx = startIndices[(int)GrScissorTest::kDisabled]->fStrokes[numSegmentsLog2]; + int endIdx = batch.fNonScissorEndInstances->fStrokes[numSegmentsLog2]; + SkASSERT(endIdx >= startIdx); + if (int instanceCount = endIdx - startIdx) { + GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip); + mesh.setInstanced(fInstanceBuffer, instanceCount, baseInstance + startIdx, + numStripVertices); + fScissorsBuffer.push_back(drawBounds); + } + + // Append scissored meshes. + baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].fStrokes[numSegmentsLog2]; + startIdx = startIndices[(int)GrScissorTest::kEnabled]->fStrokes[numSegmentsLog2]; + for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) { + const ScissorSubBatch& subBatch = fScissorSubBatches[i]; + endIdx = subBatch.fEndInstances->fStrokes[numSegmentsLog2]; + SkASSERT(endIdx >= startIdx); + if (int instanceCount = endIdx - startIdx) { + GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip); + mesh.setInstanced(fInstanceBuffer, instanceCount, baseInstance + startIdx, + numStripVertices); + fScissorsBuffer.push_back(subBatch.fScissor); + startIdx = endIdx; + } + } +} + +void GrCCStroker::flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor& processor, + GrOpFlushState* flushState, + const GrPipeline& pipeline, + const SkIRect& drawBounds) const { + SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count()); + GrPipeline::DynamicStateArrays dynamicStateArrays; + dynamicStateArrays.fScissorRects = fScissorsBuffer.begin(); + + GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(), + flushState->drawOpArgs().origin(), + pipeline, + processor, + nullptr, + &dynamicStateArrays, 0); + + flushState->opsRenderPass()->draw(programInfo, + fMeshesBuffer.begin(), fMeshesBuffer.count(), + SkRect::Make(drawBounds)); + // Don't call reset(), as that also resets the reserve count. + fMeshesBuffer.pop_back_n(fMeshesBuffer.count()); + fScissorsBuffer.pop_back_n(fScissorsBuffer.count()); +} + +template +void GrCCStroker::drawConnectingGeometry(GrOpFlushState* flushState, const GrPipeline& pipeline, + const GrCCCoverageProcessor& processor, + const Batch& batch, const InstanceTallies* startIndices[2], + int startScissorSubBatch, + const SkIRect& drawBounds) const { + // Append non-scissored meshes. + int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*InstanceType; + int startIdx = startIndices[(int)GrScissorTest::kDisabled]->*InstanceType; + int endIdx = batch.fNonScissorEndInstances->*InstanceType; + SkASSERT(endIdx >= startIdx); + if (int instanceCount = endIdx - startIdx) { + processor.appendMesh(fInstanceBuffer, instanceCount, baseInstance + startIdx, + &fMeshesBuffer); + fScissorsBuffer.push_back(drawBounds); + } + + // Append scissored meshes. + baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*InstanceType; + startIdx = startIndices[(int)GrScissorTest::kEnabled]->*InstanceType; + for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) { + const ScissorSubBatch& subBatch = fScissorSubBatches[i]; + endIdx = subBatch.fEndInstances->*InstanceType; + SkASSERT(endIdx >= startIdx); + if (int instanceCount = endIdx - startIdx) { + processor.appendMesh(fInstanceBuffer, instanceCount, baseInstance + startIdx, + &fMeshesBuffer); + fScissorsBuffer.push_back(subBatch.fScissor); + startIdx = endIdx; + } + } + + // Flush the geometry. + if (!fMeshesBuffer.empty()) { + SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count()); + processor.draw(flushState, pipeline, fScissorsBuffer.begin(), fMeshesBuffer.begin(), + fMeshesBuffer.count(), SkRect::Make(drawBounds)); + // Don't call reset(), as that also resets the reserve count. + fMeshesBuffer.pop_back_n(fMeshesBuffer.count()); + fScissorsBuffer.pop_back_n(fScissorsBuffer.count()); + } +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCCStroker.h b/gfx/skia/skia/src/gpu/ccpr/GrCCStroker.h new file mode 100644 index 0000000000..985ac38400 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCCStroker.h @@ -0,0 +1,128 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCStroker_DEFINED +#define GrCCStroker_DEFINED + +#include "include/private/SkNx.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/ccpr/GrCCStrokeGeometry.h" + +class GrGpuBuffer; +class GrCCCoverageProcessor; +class GrOnFlushResourceProvider; +class GrOpFlushState; +class GrPipeline; +class GrPrimitiveProcessor; +class SkMatrix; +class SkPath; +class SkStrokeRec; + +/** + * This class parses stroked SkPaths into a GPU instance buffer, then issues calls to draw their + * coverage counts. + */ +class GrCCStroker { +public: + GrCCStroker(int numPaths, int numSkPoints, int numSkVerbs) + : fGeometry(numSkPoints, numSkVerbs), fPathInfos(numPaths) {} + + // Parses a device-space SkPath into the current batch, using the SkPath's original verbs with + // 'deviceSpacePts', and the SkStrokeRec's original settings with 'strokeDevWidth'. Accepts an + // optional post-device-space translate for placement in an atlas. + // + // Strokes intended as hairlines must have a strokeDevWidth of 1. Non-hairline strokes can only + // be drawn with rigid body transforms; affine transformation of the stroke lines themselves is + // not yet supported. + void parseDeviceSpaceStroke(const SkPath&, const SkPoint* deviceSpacePts, const SkStrokeRec&, + float strokeDevWidth, GrScissorTest, + const SkIRect& clippedDevIBounds, + const SkIVector& devToAtlasOffset); + + using BatchID = int; + + // Compiles the outstanding parsed paths into a batch, and returns an ID that can be used to + // draw their strokes in the future. + BatchID closeCurrentBatch(); + + // Builds an internal GPU buffer and prepares for calls to drawStrokes(). Caller must close the + // current batch before calling this method, and cannot parse new paths afer. + bool prepareToDraw(GrOnFlushResourceProvider*); + + // Called after prepareToDraw(). Draws the given batch of path strokes. + void drawStrokes( + GrOpFlushState*, GrCCCoverageProcessor*, BatchID, const SkIRect& drawBounds) const; + +private: + static constexpr int kNumScissorModes = 2; + static constexpr BatchID kEmptyBatchID = -1; + using Verb = GrCCStrokeGeometry::Verb; + using InstanceTallies = GrCCStrokeGeometry::InstanceTallies; + + // Every kBeginPath verb has a corresponding PathInfo entry. + struct PathInfo { + SkIVector fDevToAtlasOffset; + float fStrokeRadius; + GrScissorTest fScissorTest; + }; + + // Defines a sub-batch of stroke instances that have a scissor test and the same scissor rect. + // Start indices are deduced by looking at the previous ScissorSubBatch. + struct ScissorSubBatch { + ScissorSubBatch(GrTAllocator* alloc, const InstanceTallies& startIndices, + const SkIRect& scissor) + : fEndInstances(&alloc->emplace_back(startIndices)), fScissor(scissor) {} + InstanceTallies* fEndInstances; + SkIRect fScissor; + }; + + // Defines a batch of stroke instances that can be drawn with drawStrokes(). Start indices are + // deduced by looking at the previous Batch in the list. + struct Batch { + Batch(GrTAllocator* alloc, const InstanceTallies& startNonScissorIndices, + int startScissorSubBatch) + : fNonScissorEndInstances(&alloc->emplace_back(startNonScissorIndices)) + , fEndScissorSubBatch(startScissorSubBatch) {} + InstanceTallies* fNonScissorEndInstances; + int fEndScissorSubBatch; + }; + + class InstanceBufferBuilder; + + void appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch&, + const InstanceTallies* startIndices[2], + int startScissorSubBatch, const SkIRect& drawBounds) const; + void flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor&, GrOpFlushState*, const + GrPipeline&, const SkIRect& drawBounds) const; + + template + void drawConnectingGeometry(GrOpFlushState*, const GrPipeline&, + const GrCCCoverageProcessor&, const Batch&, + const InstanceTallies* startIndices[2], int startScissorSubBatch, + const SkIRect& drawBounds) const; + + GrCCStrokeGeometry fGeometry; + SkSTArray<32, PathInfo> fPathInfos; + SkSTArray<32, Batch> fBatches; + SkSTArray<32, ScissorSubBatch> fScissorSubBatches; + int fMaxNumScissorSubBatches = 0; + bool fHasOpenBatch = false; + + const InstanceTallies fZeroTallies = InstanceTallies(); + GrSTAllocator<128, InstanceTallies> fTalliesAllocator; + const InstanceTallies* fInstanceCounts[kNumScissorModes] = {&fZeroTallies, &fZeroTallies}; + + sk_sp fInstanceBuffer; + // The indices stored in batches are relative to these base instances. + InstanceTallies fBaseInstances[kNumScissorModes]; + + mutable SkSTArray<32, GrMesh> fMeshesBuffer; + mutable SkSTArray<32, SkIRect> fScissorsBuffer; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp new file mode 100644 index 0000000000..4e86d7e071 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp @@ -0,0 +1,374 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" + +#include "include/pathops/SkPathOps.h" +#include "src/core/SkMakeUnique.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/ccpr/GrCCClipProcessor.h" +#include "src/gpu/ccpr/GrCCDrawPathsOp.h" +#include "src/gpu/ccpr/GrCCPathCache.h" + +using PathInstance = GrCCPathProcessor::Instance; + +bool GrCoverageCountingPathRenderer::IsSupported(const GrCaps& caps, CoverageType* coverageType) { + const GrShaderCaps& shaderCaps = *caps.shaderCaps(); + GrBackendFormat defaultA8Format = caps.getDefaultBackendFormat(GrColorType::kAlpha_8, + GrRenderable::kYes); + if (caps.driverBlacklistCCPR() || !shaderCaps.integerSupport() || + !caps.instanceAttribSupport() || !shaderCaps.floatIs32Bits() || + GrCaps::kNone_MapFlags == caps.mapBufferFlags() || + !defaultA8Format.isValid() || // This checks both texturable and renderable + !caps.halfFloatVertexAttributeSupport()) { + return false; + } + + GrBackendFormat defaultAHalfFormat = caps.getDefaultBackendFormat(GrColorType::kAlpha_F16, + GrRenderable::kYes); + if (caps.allowCoverageCounting() && + defaultAHalfFormat.isValid()) { // This checks both texturable and renderable + if (coverageType) { + *coverageType = CoverageType::kFP16_CoverageCount; + } + return true; + } + + if (!caps.driverBlacklistMSAACCPR() && + caps.internalMultisampleCount(defaultA8Format) > 1 && + caps.sampleLocationsSupport() && + shaderCaps.sampleVariablesStencilSupport()) { + if (coverageType) { + *coverageType = CoverageType::kA8_Multisample; + } + return true; + } + + return false; +} + +sk_sp GrCoverageCountingPathRenderer::CreateIfSupported( + const GrCaps& caps, AllowCaching allowCaching, uint32_t contextUniqueID) { + CoverageType coverageType; + if (IsSupported(caps, &coverageType)) { + return sk_sp(new GrCoverageCountingPathRenderer( + coverageType, allowCaching, contextUniqueID)); + } + return nullptr; +} + +GrCoverageCountingPathRenderer::GrCoverageCountingPathRenderer( + CoverageType coverageType, AllowCaching allowCaching, uint32_t contextUniqueID) + : fCoverageType(coverageType) { + if (AllowCaching::kYes == allowCaching) { + fPathCache = skstd::make_unique(contextUniqueID); + } +} + +GrCCPerOpsTaskPaths* GrCoverageCountingPathRenderer::lookupPendingPaths(uint32_t opsTaskID) { + auto it = fPendingPaths.find(opsTaskID); + if (fPendingPaths.end() == it) { + sk_sp paths = sk_make_sp(); + it = fPendingPaths.insert(std::make_pair(opsTaskID, std::move(paths))).first; + } + return it->second.get(); +} + +GrPathRenderer::CanDrawPath GrCoverageCountingPathRenderer::onCanDrawPath( + const CanDrawPathArgs& args) const { + const GrShape& shape = *args.fShape; + // We use "kCoverage", or analytic AA, no mater what the coverage type of our atlas: Even if the + // atlas is multisampled, that resolves into analytic coverage before we draw the path to the + // main canvas. + if (GrAAType::kCoverage != args.fAAType || shape.style().hasPathEffect() || + args.fViewMatrix->hasPerspective() || shape.inverseFilled()) { + return CanDrawPath::kNo; + } + + SkPath path; + shape.asPath(&path); + + const SkStrokeRec& stroke = shape.style().strokeRec(); + switch (stroke.getStyle()) { + case SkStrokeRec::kFill_Style: { + SkRect devBounds; + args.fViewMatrix->mapRect(&devBounds, path.getBounds()); + + SkIRect clippedIBounds; + devBounds.roundOut(&clippedIBounds); + if (!clippedIBounds.intersect(*args.fClipConservativeBounds)) { + // The path is completely clipped away. Our code will eventually notice this before + // doing any real work. + return CanDrawPath::kYes; + } + + int64_t numPixels = sk_64_mul(clippedIBounds.height(), clippedIBounds.width()); + if (path.countVerbs() > 1000 && path.countPoints() > numPixels) { + // This is a complicated path that has more vertices than pixels! Let's let the SW + // renderer have this one: It will probably be faster and a bitmap will require less + // total memory on the GPU than CCPR instance buffers would for the raw path data. + return CanDrawPath::kNo; + } + + if (numPixels > 256 * 256) { + // Large paths can blow up the atlas fast. And they are not ideal for a two-pass + // rendering algorithm. Give the simpler direct renderers a chance before we commit + // to drawing it. + return CanDrawPath::kAsBackup; + } + + if (args.fShape->hasUnstyledKey() && path.countVerbs() > 50) { + // Complex paths do better cached in an SDF, if the renderer will accept them. + return CanDrawPath::kAsBackup; + } + + return CanDrawPath::kYes; + } + + case SkStrokeRec::kStroke_Style: + if (!args.fViewMatrix->isSimilarity()) { + // The stroker currently only supports rigid-body transfoms for the stroke lines + // themselves. This limitation doesn't affect hairlines since their stroke lines are + // defined relative to device space. + return CanDrawPath::kNo; + } + // fallthru + case SkStrokeRec::kHairline_Style: { + if (CoverageType::kFP16_CoverageCount != fCoverageType) { + // Stroking is not yet supported in MSAA atlas mode. + return CanDrawPath::kNo; + } + float inflationRadius; + GetStrokeDevWidth(*args.fViewMatrix, stroke, &inflationRadius); + if (!(inflationRadius <= kMaxBoundsInflationFromStroke)) { + // Let extremely wide strokes be converted to fill paths and drawn by the CCPR + // filler instead. (Cast the logic negatively in order to also catch r=NaN.) + return CanDrawPath::kNo; + } + SkASSERT(!SkScalarIsNaN(inflationRadius)); + if (SkPathPriv::ConicWeightCnt(path)) { + // The stroker does not support conics yet. + return CanDrawPath::kNo; + } + return CanDrawPath::kYes; + } + + case SkStrokeRec::kStrokeAndFill_Style: + return CanDrawPath::kNo; + } + + SK_ABORT("Invalid stroke style."); +} + +bool GrCoverageCountingPathRenderer::onDrawPath(const DrawPathArgs& args) { + SkASSERT(!fFlushing); + + SkIRect clipIBounds; + GrRenderTargetContext* rtc = args.fRenderTargetContext; + args.fClip->getConservativeBounds(rtc->width(), rtc->height(), &clipIBounds, nullptr); + + auto op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, *args.fShape, + std::move(args.fPaint)); + this->recordOp(std::move(op), args); + return true; +} + +void GrCoverageCountingPathRenderer::recordOp(std::unique_ptr op, + const DrawPathArgs& args) { + if (op) { + auto addToOwningPerOpsTaskPaths = [this](GrOp* op, uint32_t opsTaskID) { + op->cast()->addToOwningPerOpsTaskPaths( + sk_ref_sp(this->lookupPendingPaths(opsTaskID))); + }; + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op), + addToOwningPerOpsTaskPaths); + } +} + +std::unique_ptr GrCoverageCountingPathRenderer::makeClipProcessor( + uint32_t opsTaskID, const SkPath& deviceSpacePath, const SkIRect& accessRect, + const GrCaps& caps) { + SkASSERT(!fFlushing); + + uint32_t key = deviceSpacePath.getGenerationID(); + if (CoverageType::kA8_Multisample == fCoverageType) { + // We only need to consider fill rule in MSAA mode. In coverage count mode Even/Odd and + // Nonzero both reference the same coverage count mask. + key = (key << 1) | (uint32_t)GrFillRuleForSkPath(deviceSpacePath); + } + GrCCClipPath& clipPath = + this->lookupPendingPaths(opsTaskID)->fClipPaths[key]; + if (!clipPath.isInitialized()) { + // This ClipPath was just created during lookup. Initialize it. + const SkRect& pathDevBounds = deviceSpacePath.getBounds(); + if (SkTMax(pathDevBounds.height(), pathDevBounds.width()) > kPathCropThreshold) { + // The path is too large. Crop it or analytic AA can run out of fp32 precision. + SkPath croppedPath; + int maxRTSize = caps.maxRenderTargetSize(); + CropPath(deviceSpacePath, SkIRect::MakeWH(maxRTSize, maxRTSize), &croppedPath); + clipPath.init(croppedPath, accessRect, fCoverageType, caps); + } else { + clipPath.init(deviceSpacePath, accessRect, fCoverageType, caps); + } + } else { + clipPath.addAccess(accessRect); + } + + auto isCoverageCount = GrCCClipProcessor::IsCoverageCount( + CoverageType::kFP16_CoverageCount == fCoverageType); + auto mustCheckBounds = GrCCClipProcessor::MustCheckBounds( + !clipPath.pathDevIBounds().contains(accessRect)); + return skstd::make_unique(&clipPath, isCoverageCount, mustCheckBounds); +} + +void GrCoverageCountingPathRenderer::preFlush( + GrOnFlushResourceProvider* onFlushRP, const uint32_t* opsTaskIDs, int numOpsTaskIDs) { + using DoCopiesToA8Coverage = GrCCDrawPathsOp::DoCopiesToA8Coverage; + SkASSERT(!fFlushing); + SkASSERT(fFlushingPaths.empty()); + SkDEBUGCODE(fFlushing = true); + + if (fPathCache) { + fPathCache->doPreFlushProcessing(); + } + + if (fPendingPaths.empty()) { + return; // Nothing to draw. + } + + GrCCPerFlushResourceSpecs specs; + int maxPreferredRTSize = onFlushRP->caps()->maxPreferredRenderTargetSize(); + specs.fCopyAtlasSpecs.fMaxPreferredTextureSize = SkTMin(2048, maxPreferredRTSize); + SkASSERT(0 == specs.fCopyAtlasSpecs.fMinTextureSize); + specs.fRenderedAtlasSpecs.fMaxPreferredTextureSize = maxPreferredRTSize; + specs.fRenderedAtlasSpecs.fMinTextureSize = SkTMin(512, maxPreferredRTSize); + + // Move the per-opsTask paths that are about to be flushed from fPendingPaths to fFlushingPaths, + // and count them up so we can preallocate buffers. + fFlushingPaths.reserve(numOpsTaskIDs); + for (int i = 0; i < numOpsTaskIDs; ++i) { + auto iter = fPendingPaths.find(opsTaskIDs[i]); + if (fPendingPaths.end() == iter) { + continue; // No paths on this opsTask. + } + + fFlushingPaths.push_back(std::move(iter->second)); + fPendingPaths.erase(iter); + + for (GrCCDrawPathsOp* op : fFlushingPaths.back()->fDrawOps) { + op->accountForOwnPaths(fPathCache.get(), onFlushRP, &specs); + } + for (const auto& clipsIter : fFlushingPaths.back()->fClipPaths) { + clipsIter.second.accountForOwnPath(&specs); + } + } + + if (specs.isEmpty()) { + return; // Nothing to draw. + } + + // Determine if there are enough reusable paths from last flush for it to be worth our time to + // copy them to cached atlas(es). + int numCopies = specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx] + + specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx]; + auto doCopies = DoCopiesToA8Coverage(numCopies > 100 || + specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256); + if (numCopies && DoCopiesToA8Coverage::kNo == doCopies) { + specs.cancelCopies(); + } + + auto resources = sk_make_sp(onFlushRP, fCoverageType, specs); + if (!resources->isMapped()) { + return; // Some allocation failed. + } + + // Layout the atlas(es) and parse paths. + for (const auto& flushingPaths : fFlushingPaths) { + for (GrCCDrawPathsOp* op : flushingPaths->fDrawOps) { + op->setupResources(fPathCache.get(), onFlushRP, resources.get(), doCopies); + } + for (auto& clipsIter : flushingPaths->fClipPaths) { + clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP); + } + } + + if (fPathCache) { + // Purge invalidated textures from previous atlases *before* calling finalize(). That way, + // the underlying textures objects can be freed up and reused for the next atlases. + fPathCache->purgeInvalidatedAtlasTextures(onFlushRP); + } + + // Allocate resources and then render the atlas(es). + if (!resources->finalize(onFlushRP)) { + return; + } + + // Commit flushing paths to the resources once they are successfully completed. + for (auto& flushingPaths : fFlushingPaths) { + SkASSERT(!flushingPaths->fFlushResources); + flushingPaths->fFlushResources = resources; + } +} + +void GrCoverageCountingPathRenderer::postFlush(GrDeferredUploadToken, const uint32_t* opsTaskIDs, + int numOpsTaskIDs) { + SkASSERT(fFlushing); + + if (!fFlushingPaths.empty()) { + // In DDL mode these aren't guaranteed to be deleted so we must clear out the perFlush + // resources manually. + for (auto& flushingPaths : fFlushingPaths) { + flushingPaths->fFlushResources = nullptr; + } + + // We wait to erase these until after flush, once Ops and FPs are done accessing their data. + fFlushingPaths.reset(); + } + + SkDEBUGCODE(fFlushing = false); +} + +void GrCoverageCountingPathRenderer::purgeCacheEntriesOlderThan( + GrProxyProvider* proxyProvider, const GrStdSteadyClock::time_point& purgeTime) { + if (fPathCache) { + fPathCache->purgeEntriesOlderThan(proxyProvider, purgeTime); + } +} + +void GrCoverageCountingPathRenderer::CropPath(const SkPath& path, const SkIRect& cropbox, + SkPath* out) { + SkPath cropboxPath; + cropboxPath.addRect(SkRect::Make(cropbox)); + if (!Op(cropboxPath, path, kIntersect_SkPathOp, out)) { + // This can fail if the PathOps encounter NaN or infinities. + out->reset(); + } + out->setIsVolatile(true); +} + +float GrCoverageCountingPathRenderer::GetStrokeDevWidth(const SkMatrix& m, + const SkStrokeRec& stroke, + float* inflationRadius) { + float strokeDevWidth; + if (stroke.isHairlineStyle()) { + strokeDevWidth = 1; + } else { + SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle()); + SkASSERT(m.isSimilarity()); // Otherwise matrixScaleFactor = m.getMaxScale(). + float matrixScaleFactor = SkVector::Length(m.getScaleX(), m.getSkewY()); + strokeDevWidth = stroke.getWidth() * matrixScaleFactor; + } + if (inflationRadius) { + // Inflate for a minimum stroke width of 1. In some cases when the stroke is less than 1px + // wide, we may inflate it to 1px and instead reduce the opacity. + *inflationRadius = SkStrokeRec::GetInflationRadius( + stroke.getJoin(), stroke.getMiter(), stroke.getCap(), SkTMax(strokeDevWidth, 1.f)); + } + return strokeDevWidth; +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.h new file mode 100644 index 0000000000..e70a15a6bc --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer.h @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCoverageCountingPathRenderer_DEFINED +#define GrCoverageCountingPathRenderer_DEFINED + +#include +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrOpsTask.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/ccpr/GrCCPerFlushResources.h" +#include "src/gpu/ccpr/GrCCPerOpsTaskPaths.h" + +class GrCCDrawPathsOp; +class GrCCPathCache; + +/** + * This is a path renderer that draws antialiased paths by counting coverage in an offscreen + * buffer. (See GrCCCoverageProcessor, GrCCPathProcessor.) + * + * It also serves as the per-render-target tracker for pending path draws, and at the start of + * flush, it compiles GPU buffers and renders a "coverage count atlas" for the upcoming paths. + */ +class GrCoverageCountingPathRenderer : public GrPathRenderer, public GrOnFlushCallbackObject { +public: + using CoverageType = GrCCAtlas::CoverageType; + + static bool IsSupported(const GrCaps&, CoverageType* = nullptr); + + enum class AllowCaching : bool { + kNo = false, + kYes = true + }; + + static sk_sp CreateIfSupported( + const GrCaps&, AllowCaching, uint32_t contextUniqueID); + + CoverageType coverageType() const { return fCoverageType; } + + using PendingPathsMap = std::map>; + + // In DDL mode, Ganesh needs to be able to move the pending GrCCPerOpsTaskPaths to the DDL + // object (detachPendingPaths) and then return them upon replay (mergePendingPaths). + PendingPathsMap detachPendingPaths() { return std::move(fPendingPaths); } + + void mergePendingPaths(const PendingPathsMap& paths) { +#ifdef SK_DEBUG + // Ensure there are no duplicate opsTask IDs between the incoming path map and ours. + // This should always be true since opsTask IDs are globally unique and these are coming + // from different DDL recordings. + for (const auto& it : paths) { + SkASSERT(!fPendingPaths.count(it.first)); + } +#endif + + fPendingPaths.insert(paths.begin(), paths.end()); + } + + std::unique_ptr makeClipProcessor( + uint32_t oplistID, const SkPath& deviceSpacePath, const SkIRect& accessRect, + const GrCaps&); + + // GrOnFlushCallbackObject overrides. + void preFlush(GrOnFlushResourceProvider*, const uint32_t* opsTaskIDs, + int numOpsTaskIDs) override; + void postFlush(GrDeferredUploadToken, const uint32_t* opsTaskIDs, int numOpsTaskIDs) override; + + void purgeCacheEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point&); + + // If a path spans more pixels than this, we need to crop it or else analytic AA can run out of + // fp32 precision. + static constexpr float kPathCropThreshold = 1 << 16; + + static void CropPath(const SkPath&, const SkIRect& cropbox, SkPath* out); + + // Maximum inflation of path bounds due to stroking (from width, miter, caps). Strokes wider + // than this will be converted to fill paths and drawn by the CCPR filler instead. + static constexpr float kMaxBoundsInflationFromStroke = 4096; + + static float GetStrokeDevWidth(const SkMatrix&, const SkStrokeRec&, + float* inflationRadius = nullptr); + +private: + GrCoverageCountingPathRenderer(CoverageType, AllowCaching, uint32_t contextUniqueID); + + // GrPathRenderer overrides. + StencilSupport onGetStencilSupport(const GrShape&) const override { + return GrPathRenderer::kNoSupport_StencilSupport; + } + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + bool onDrawPath(const DrawPathArgs&) override; + + GrCCPerOpsTaskPaths* lookupPendingPaths(uint32_t opsTaskID); + void recordOp(std::unique_ptr, const DrawPathArgs&); + + const CoverageType fCoverageType; + + // fPendingPaths holds the GrCCPerOpsTaskPaths objects that have already been created, but not + // flushed, and those that are still being created. All GrCCPerOpsTaskPaths objects will first + // reside in fPendingPaths, then be moved to fFlushingPaths during preFlush(). + PendingPathsMap fPendingPaths; + + // fFlushingPaths holds the GrCCPerOpsTaskPaths objects that are currently being flushed. + // (It will only contain elements when fFlushing is true.) + SkSTArray<4, sk_sp> fFlushingPaths; + + std::unique_ptr fPathCache; + + SkDEBUGCODE(bool fFlushing = false); + +public: + void testingOnly_drawPathDirectly(const DrawPathArgs&); + const GrCCPerFlushResources* testingOnly_getCurrentFlushResources(); + const GrCCPathCache* testingOnly_getPathCache() const; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp b/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp new file mode 100644 index 0000000000..1b2a55e0e7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2018 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h" + +bool GrCoverageCountingPathRenderer::IsSupported(const GrCaps& caps, CoverageType*) { + return false; +} + +sk_sp GrCoverageCountingPathRenderer::CreateIfSupported( + const GrCaps& caps, AllowCaching allowCaching, uint32_t contextUniqueID) { + return nullptr; +} + +std::unique_ptr GrCoverageCountingPathRenderer::makeClipProcessor( + uint32_t opsTaskID, const SkPath& deviceSpacePath, const SkIRect& accessRect, + const GrCaps& caps) { + return nullptr; +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.cpp b/gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.cpp new file mode 100644 index 0000000000..1398d5b5f0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.cpp @@ -0,0 +1,463 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrGSCoverageProcessor.h" + +#include "src/gpu/GrMesh.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +using InputType = GrGLSLGeometryBuilder::InputType; +using OutputType = GrGLSLGeometryBuilder::OutputType; + +/** + * This class and its subclasses implement the coverage processor with geometry shaders. + */ +class GrGSCoverageProcessor::Impl : public GrGLSLGeometryProcessor { +protected: + Impl(std::unique_ptr shader) : fShader(std::move(shader)) {} + + virtual bool hasCoverage(const GrGSCoverageProcessor& proc) const { return false; } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, + FPCoordTransformIter&& transformIter) final { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final { + const GrGSCoverageProcessor& proc = args.fGP.cast(); + + // The vertex shader simply forwards transposed x or y values to the geometry shader. + SkASSERT(1 == proc.numVertexAttributes()); + gpArgs->fPositionVar = proc.fInputXOrYValues.asShaderVar(); + + // Geometry shader. + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + this->emitGeometryShader(proc, varyingHandler, args.fGeomBuilder, args.fRTAdjustName); + varyingHandler->emitAttributes(proc); + varyingHandler->setNoPerspective(); + SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform()); + + // Fragment shader. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + f->codeAppendf("half coverage;"); + fShader->emitFragmentCoverageCode(f, "coverage"); + f->codeAppendf("%s = half4(coverage);", args.fOutputColor); + f->codeAppendf("%s = half4(1);", args.fOutputCoverage); + } + + void emitGeometryShader( + const GrGSCoverageProcessor& proc, GrGLSLVaryingHandler* varyingHandler, + GrGLSLGeometryBuilder* g, const char* rtAdjust) const { + int numInputPoints = proc.numInputPoints(); + SkASSERT(3 == numInputPoints || 4 == numInputPoints); + + int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3; + const char* posValues = (4 == inputWidth) ? "sk_Position" : "sk_Position.xyz"; + g->codeAppendf("float%ix2 pts = transpose(float2x%i(sk_in[0].%s, sk_in[1].%s));", + inputWidth, inputWidth, posValues, posValues); + + GrShaderVar wind("wind", kHalf_GrSLType); + g->declareGlobal(wind); + Shader::CalcWind(proc, g, "pts", wind.c_str()); + if (PrimitiveType::kWeightedTriangles == proc.primitiveType()) { + SkASSERT(3 == numInputPoints); + SkASSERT(kFloat4_GrVertexAttribType == proc.fInputXOrYValues.cpuType()); + g->codeAppendf("%s *= half(sk_in[0].sk_Position.w);", wind.c_str()); + } + + SkString emitVertexFn; + SkSTArray<3, GrShaderVar> emitArgs; + const char* corner = emitArgs.emplace_back("corner", kFloat2_GrSLType).c_str(); + const char* bloatdir = emitArgs.emplace_back("bloatdir", kFloat2_GrSLType).c_str(); + const char* inputCoverage = nullptr; + if (this->hasCoverage(proc)) { + inputCoverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str(); + } + const char* cornerCoverage = nullptr; + if (Subpass::kCorners == proc.fSubpass) { + cornerCoverage = emitArgs.emplace_back("corner_coverage", kHalf2_GrSLType).c_str(); + } + g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() { + SkString fnBody; + fnBody.appendf("float2 vertexpos = fma(%s, float2(bloat), %s);", bloatdir, corner); + const char* coverage = inputCoverage; + if (!coverage) { + if (!fShader->calculatesOwnEdgeCoverage()) { + // Flat edge opposite the curve. Coverages need full precision since distance + // to the opposite edge can be large. + fnBody.appendf("float coverage = dot(float3(vertexpos, 1), %s);", + fEdgeDistanceEquation.c_str()); + } else { + // The "coverage" param should hold only the signed winding value. + fnBody.appendf("float coverage = 1;"); + } + coverage = "coverage"; + } + fnBody.appendf("%s *= %s;", coverage, wind.c_str()); + if (cornerCoverage) { + fnBody.appendf("%s.x *= %s;", cornerCoverage, wind.c_str()); + } + fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kGeoToFrag, &fnBody, + "vertexpos", coverage, cornerCoverage, wind.c_str()); + g->emitVertex(&fnBody, "vertexpos", rtAdjust); + return fnBody; + }().c_str(), &emitVertexFn); + + float bloat = kAABloatRadius; +#ifdef SK_DEBUG + if (proc.debugBloatEnabled()) { + bloat *= proc.debugBloat(); + } +#endif + g->defineConstant("bloat", bloat); + + if (!this->hasCoverage(proc) && !fShader->calculatesOwnEdgeCoverage()) { + // Determine the amount of coverage to subtract out for the flat edge of the curve. + g->declareGlobal(fEdgeDistanceEquation); + g->codeAppendf("float2 p0 = pts[0], p1 = pts[%i];", numInputPoints - 1); + g->codeAppendf("float2 n = float2(p0.y - p1.y, p1.x - p0.x);"); + g->codeAppend ("float nwidth = bloat*2 * (abs(n.x) + abs(n.y));"); + // When nwidth=0, wind must also be 0 (and coverage * wind = 0). So it doesn't matter + // what we come up with here as long as it isn't NaN or Inf. + g->codeAppend ("n /= (0 != nwidth) ? nwidth : 1;"); + g->codeAppendf("%s = float3(-n, dot(n, p0) - .5*sign(%s));", + fEdgeDistanceEquation.c_str(), wind.c_str()); + } + + this->onEmitGeometryShader(proc, g, wind, emitVertexFn.c_str()); + } + + virtual void onEmitGeometryShader(const GrGSCoverageProcessor&, GrGLSLGeometryBuilder*, + const GrShaderVar& wind, const char* emitVertexFn) const = 0; + + const std::unique_ptr fShader; + const GrShaderVar fEdgeDistanceEquation{"edge_distance_equation", kFloat3_GrSLType}; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +/** + * Generates conservative rasters around a triangle and its edges, and calculates coverage ramps. + * + * Triangle rough outlines are drawn in two steps: (1) draw a conservative raster of the entire + * triangle, with a coverage of +1, and (2) draw conservative rasters around each edge, with a + * coverage ramp from -1 to 0. These edge coverage values convert jagged conservative raster edges + * into smooth, antialiased ones. + * + * The final corners get touched up in a later step by TriangleCornerImpl. + */ +class GrGSCoverageProcessor::TriangleHullImpl : public GrGSCoverageProcessor::Impl { +public: + TriangleHullImpl(std::unique_ptr shader) : Impl(std::move(shader)) {} + + bool hasCoverage(const GrGSCoverageProcessor& proc) const override { return true; } + + void onEmitGeometryShader(const GrGSCoverageProcessor&, GrGLSLGeometryBuilder* g, + const GrShaderVar& wind, const char* emitVertexFn) const override { + fShader->emitSetupCode(g, "pts"); + + // Visualize the input triangle as upright and equilateral, with a flat base. Paying special + // attention to wind, we can identify the points as top, bottom-left, and bottom-right. + // + // NOTE: We generate the rasters in 5 independent invocations, so each invocation designates + // the corner it will begin with as the top. + g->codeAppendf("int i = (%s > 0 ? sk_InvocationID : 4 - sk_InvocationID) %% 3;", + wind.c_str()); + g->codeAppend ("float2 top = pts[i];"); + g->codeAppendf("float2 right = pts[(i + (%s > 0 ? 1 : 2)) %% 3];", wind.c_str()); + g->codeAppendf("float2 left = pts[(i + (%s > 0 ? 2 : 1)) %% 3];", wind.c_str()); + + // Determine which direction to outset the conservative raster from each of the three edges. + g->codeAppend ("float2 leftbloat = sign(top - left);"); + g->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, " + "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);"); + + g->codeAppend ("float2 rightbloat = sign(right - top);"); + g->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, " + "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);"); + + g->codeAppend ("float2 downbloat = sign(left - right);"); + g->codeAppend ("downbloat = float2(0 != downbloat.y ? downbloat.y : downbloat.x, " + "0 != downbloat.x ? -downbloat.x : -downbloat.y);"); + + // The triangle's conservative raster has a coverage of +1 all around. + g->codeAppend ("half4 coverages = half4(+1);"); + + // Edges have coverage ramps. + g->codeAppend ("if (sk_InvocationID >= 2) {"); // Are we an edge? + Shader::CalcEdgeCoverageAtBloatVertex(g, "top", "right", + "float2(+rightbloat.y, -rightbloat.x)", + "coverages[0]"); + g->codeAppend ( "coverages.yzw = half3(-1, 0, -1 - coverages[0]);"); + // Reassign bloats to characterize a conservative raster around a single edge, rather than + // the entire triangle. + g->codeAppend ( "leftbloat = downbloat = -rightbloat;"); + g->codeAppend ("}"); + + // Here we generate the conservative raster geometry. The triangle's conservative raster is + // the convex hull of 3 pixel-size boxes centered on the input points. This translates to a + // convex polygon with either one, two, or three vertices at each input point (depending on + // how sharp the corner is) that we split between two invocations. Edge conservative rasters + // are convex hulls of 2 pixel-size boxes, one at each endpoint. For more details on + // conservative raster, see: + // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html + g->codeAppendf("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);"); + g->codeAppend ("if (all(left_right_notequal)) {"); + // The top corner will have three conservative raster vertices. Emit the + // middle one first to the triangle strip. + g->codeAppendf( "%s(top, float2(-leftbloat.y, +leftbloat.x), coverages[0]);", + emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("if (any(left_right_notequal)) {"); + // Second conservative raster vertex for the top corner. + g->codeAppendf( "%s(top, rightbloat, coverages[1]);", emitVertexFn); + g->codeAppend ("}"); + + // Main interior body. + g->codeAppendf("%s(top, leftbloat, coverages[2]);", emitVertexFn); + g->codeAppendf("%s(right, rightbloat, coverages[1]);", emitVertexFn); + + // Here the invocations diverge slightly. We can't symmetrically divide three triangle + // points between two invocations, so each does the following: + // + // sk_InvocationID=0: Finishes the main interior body of the triangle hull. + // sk_InvocationID=1: Remaining two conservative raster vertices for the third hull corner. + // sk_InvocationID=2..4: Finish the opposite endpoint of their corresponding edge. + g->codeAppendf("bool2 right_down_notequal = notEqual(rightbloat, downbloat);"); + g->codeAppend ("if (any(right_down_notequal) || 0 == sk_InvocationID) {"); + g->codeAppendf( "%s((0 == sk_InvocationID) ? left : right, " + "(0 == sk_InvocationID) ? leftbloat : downbloat, " + "coverages[2]);", emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("if (all(right_down_notequal) && 0 != sk_InvocationID) {"); + g->codeAppendf( "%s(right, float2(-rightbloat.y, +rightbloat.x), coverages[3]);", + emitVertexFn); + g->codeAppend ("}"); + + // 5 invocations: 2 triangle hull invocations and 3 edges. + g->configure(InputType::kLines, OutputType::kTriangleStrip, 6, 5); + } +}; + +/** + * Generates a conservative raster around a convex quadrilateral that encloses a cubic or quadratic. + */ +class GrGSCoverageProcessor::CurveHullImpl : public GrGSCoverageProcessor::Impl { +public: + CurveHullImpl(std::unique_ptr shader) : Impl(std::move(shader)) {} + + void onEmitGeometryShader(const GrGSCoverageProcessor&, GrGLSLGeometryBuilder* g, + const GrShaderVar& wind, const char* emitVertexFn) const override { + const char* hullPts = "pts"; + fShader->emitSetupCode(g, "pts", &hullPts); + + // Visualize the input (convex) quadrilateral as a square. Paying special attention to wind, + // we can identify the points by their corresponding corner. + // + // NOTE: We split the square down the diagonal from top-right to bottom-left, and generate + // the hull in two independent invocations. Each invocation designates the corner it will + // begin with as top-left. + g->codeAppend ("int i = sk_InvocationID * 2;"); + g->codeAppendf("float2 topleft = %s[i];", hullPts); + g->codeAppendf("float2 topright = %s[%s > 0 ? i + 1 : 3 - i];", hullPts, wind.c_str()); + g->codeAppendf("float2 bottomleft = %s[%s > 0 ? 3 - i : i + 1];", hullPts, wind.c_str()); + g->codeAppendf("float2 bottomright = %s[2 - i];", hullPts); + + // Determine how much to outset the conservative raster hull from the relevant edges. + g->codeAppend ("float2 leftbloat = float2(topleft.y > bottomleft.y ? +1 : -1, " + "topleft.x > bottomleft.x ? -1 : +1);"); + g->codeAppend ("float2 upbloat = float2(topright.y > topleft.y ? +1 : -1, " + "topright.x > topleft.x ? -1 : +1);"); + g->codeAppend ("float2 rightbloat = float2(bottomright.y > topright.y ? +1 : -1, " + "bottomright.x > topright.x ? -1 : +1);"); + + // Here we generate the conservative raster geometry. It is the convex hull of 4 pixel-size + // boxes centered on the input points, split evenly between two invocations. This translates + // to a polygon with either one, two, or three vertices at each input point, depending on + // how sharp the corner is. For more details on conservative raster, see: + // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html + g->codeAppendf("bool2 left_up_notequal = notEqual(leftbloat, upbloat);"); + g->codeAppend ("if (all(left_up_notequal)) {"); + // The top-left corner will have three conservative raster vertices. + // Emit the middle one first to the triangle strip. + g->codeAppendf( "%s(topleft, float2(-leftbloat.y, leftbloat.x));", emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("if (any(left_up_notequal)) {"); + // Second conservative raster vertex for the top-left corner. + g->codeAppendf( "%s(topleft, leftbloat);", emitVertexFn); + g->codeAppend ("}"); + + // Main interior body of this invocation's half of the hull. + g->codeAppendf("%s(topleft, upbloat);", emitVertexFn); + g->codeAppendf("%s(bottomleft, leftbloat);", emitVertexFn); + g->codeAppendf("%s(topright, upbloat);", emitVertexFn); + + // Remaining two conservative raster vertices for the top-right corner. + g->codeAppendf("bool2 up_right_notequal = notEqual(upbloat, rightbloat);"); + g->codeAppend ("if (any(up_right_notequal)) {"); + g->codeAppendf( "%s(topright, rightbloat);", emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("if (all(up_right_notequal)) {"); + g->codeAppendf( "%s(topright, float2(-upbloat.y, upbloat.x));", emitVertexFn); + g->codeAppend ("}"); + + g->configure(InputType::kLines, OutputType::kTriangleStrip, 7, 2); + } +}; + +/** + * Generates conservative rasters around corners (aka pixel-size boxes) and calculates + * coverage and attenuation ramps to fix up the coverage values written by the hulls. + */ +class GrGSCoverageProcessor::CornerImpl : public GrGSCoverageProcessor::Impl { +public: + CornerImpl(std::unique_ptr shader) : Impl(std::move(shader)) {} + + bool hasCoverage(const GrGSCoverageProcessor& proc) const override { + return proc.isTriangles(); + } + + void onEmitGeometryShader(const GrGSCoverageProcessor& proc, GrGLSLGeometryBuilder* g, + const GrShaderVar& wind, const char* emitVertexFn) const override { + fShader->emitSetupCode(g, "pts"); + + g->codeAppendf("int corneridx = sk_InvocationID;"); + if (!proc.isTriangles()) { + g->codeAppendf("corneridx *= %i;", proc.numInputPoints() - 1); + } + + g->codeAppendf("float2 corner = pts[corneridx];"); + g->codeAppendf("float2 left = pts[(corneridx + (%s > 0 ? %i : 1)) %% %i];", + wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints()); + g->codeAppendf("float2 right = pts[(corneridx + (%s > 0 ? 1 : %i)) %% %i];", + wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints()); + + g->codeAppend ("float2 leftdir = corner - left;"); + g->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);"); + + g->codeAppend ("float2 rightdir = right - corner;"); + g->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);"); + + // Find "outbloat" and "crossbloat" at our corner. The outbloat points diagonally out of the + // triangle, in the direction that should ramp to zero coverage with attenuation. The + // crossbloat runs perpindicular to outbloat. + g->codeAppend ("float2 outbloat = float2(leftdir.x > rightdir.x ? +1 : -1, " + "leftdir.y > rightdir.y ? +1 : -1);"); + g->codeAppend ("float2 crossbloat = float2(-outbloat.y, +outbloat.x);"); + + g->codeAppend ("half attenuation; {"); + Shader::CalcCornerAttenuation(g, "leftdir", "rightdir", "attenuation"); + g->codeAppend ("}"); + + if (proc.isTriangles()) { + g->codeAppend ("half2 left_coverages; {"); + Shader::CalcEdgeCoveragesAtBloatVertices(g, "left", "corner", "-outbloat", + "-crossbloat", "left_coverages"); + g->codeAppend ("}"); + + g->codeAppend ("half2 right_coverages; {"); + Shader::CalcEdgeCoveragesAtBloatVertices(g, "corner", "right", "-outbloat", + "crossbloat", "right_coverages"); + g->codeAppend ("}"); + + // Emit a corner box. The first coverage argument erases the values that were written + // previously by the hull and edge geometry. The second pair are multiplied together by + // the fragment shader. They ramp to 0 with attenuation in the direction of outbloat, + // and linearly from left-edge coverage to right-edge coverage in the direction of + // crossbloat. + // + // NOTE: Since this is not a linear mapping, it is important that the box's diagonal + // shared edge points in the direction of outbloat. + g->codeAppendf("%s(corner, -crossbloat, right_coverages[1] - left_coverages[1]," + "half2(1 + left_coverages[1], 1));", + emitVertexFn); + + g->codeAppendf("%s(corner, outbloat, 1 + left_coverages[0] + right_coverages[0], " + "half2(0, attenuation));", + emitVertexFn); + + g->codeAppendf("%s(corner, -outbloat, -1 - left_coverages[0] - right_coverages[0], " + "half2(1 + left_coverages[0] + right_coverages[0], 1));", + emitVertexFn); + + g->codeAppendf("%s(corner, crossbloat, left_coverages[1] - right_coverages[1]," + "half2(1 + right_coverages[1], 1));", + emitVertexFn); + } else { + // Curves are simpler. Setting "wind = -wind" causes the Shader to erase what it had + // written in the previous pass hull. Then, at each vertex of the corner box, the Shader + // will calculate the curve's local coverage value, interpolate it alongside our + // attenuation parameter, and multiply the two together for a final coverage value. + g->codeAppendf("%s = -%s;", wind.c_str(), wind.c_str()); + if (!fShader->calculatesOwnEdgeCoverage()) { + g->codeAppendf("%s = -%s;", + fEdgeDistanceEquation.c_str(), fEdgeDistanceEquation.c_str()); + } + g->codeAppendf("%s(corner, -crossbloat, half2(-1, 1));", emitVertexFn); + g->codeAppendf("%s(corner, outbloat, half2(0, attenuation));", + emitVertexFn); + g->codeAppendf("%s(corner, -outbloat, half2(-1, 1));", emitVertexFn); + g->codeAppendf("%s(corner, crossbloat, half2(-1, 1));", emitVertexFn); + } + + g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, proc.isTriangles() ? 3 : 2); + } +}; + +void GrGSCoverageProcessor::reset(PrimitiveType primitiveType, GrResourceProvider*) { + fPrimitiveType = primitiveType; // This will affect the return values for numInputPoints, etc. + + if (4 == this->numInputPoints() || this->hasInputWeight()) { + fInputXOrYValues = + {"x_or_y_values", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + GR_STATIC_ASSERT(sizeof(QuadPointInstance) == + 2 * GrVertexAttribTypeSize(kFloat4_GrVertexAttribType)); + GR_STATIC_ASSERT(offsetof(QuadPointInstance, fY) == + GrVertexAttribTypeSize(kFloat4_GrVertexAttribType)); + } else { + fInputXOrYValues = + {"x_or_y_values", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + GR_STATIC_ASSERT(sizeof(TriPointInstance) == + 2 * GrVertexAttribTypeSize(kFloat3_GrVertexAttribType)); + } + + this->setVertexAttributes(&fInputXOrYValues, 1); +} + +void GrGSCoverageProcessor::appendMesh(sk_sp instanceBuffer, int instanceCount, + int baseInstance, SkTArray* out) const { + // We don't actually make instanced draw calls. Instead, we feed transposed x,y point values to + // the GPU in a regular vertex array and draw kLines (see initGS). Then, each vertex invocation + // receives either the shape's x or y values as inputs, which it forwards to the geometry + // shader. + GrMesh& mesh = out->emplace_back(GrPrimitiveType::kLines); + mesh.setNonIndexedNonInstanced(instanceCount * 2); + mesh.setVertexData(std::move(instanceBuffer), baseInstance * 2); +} + +void GrGSCoverageProcessor::draw( + GrOpFlushState* flushState, const GrPipeline& pipeline, const SkIRect scissorRects[], + const GrMesh meshes[], int meshCount, const SkRect& drawBounds) const { + // The geometry shader impl draws primitives in two subpasses: The first pass fills the interior + // and does edge AA. The second pass does touch up on corner pixels. + for (int i = 0; i < 2; ++i) { + fSubpass = (Subpass) i; + this->GrCCCoverageProcessor::draw( + flushState, pipeline, scissorRects, meshes, meshCount, drawBounds); + } +} + +GrGLSLPrimitiveProcessor* GrGSCoverageProcessor::onCreateGLSLInstance( + std::unique_ptr shader) const { + if (Subpass::kHulls == fSubpass) { + return this->isTriangles() + ? (Impl*) new TriangleHullImpl(std::move(shader)) + : (Impl*) new CurveHullImpl(std::move(shader)); + } + SkASSERT(Subpass::kCorners == fSubpass); + return new CornerImpl(std::move(shader)); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.h b/gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.h new file mode 100644 index 0000000000..2e3f7dc248 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrGSCoverageProcessor.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGSCoverageProcessor_DEFINED +#define GrGSCoverageProcessor_DEFINED + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +/** + * This class implements GrCCCoverageProcessor with analytic coverage using geometry shaders. + */ +class GrGSCoverageProcessor : public GrCCCoverageProcessor { +public: + GrGSCoverageProcessor() : GrCCCoverageProcessor(kGrGSCoverageProcessor_ClassID) { + this->setWillUseGeoShader(); + } + +private: + void reset(PrimitiveType, GrResourceProvider*) override; + + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { + SkDEBUGCODE(this->getDebugBloatKey(b)); + b->add32(((int)fPrimitiveType << 16) | (int)fSubpass); + } + + void appendMesh(sk_sp instanceBuffer, int instanceCount, int baseInstance, + SkTArray* out) const override; + + void draw(GrOpFlushState*, const GrPipeline&, const SkIRect scissorRects[], const GrMesh[], + int meshCount, const SkRect& drawBounds) const override; + + GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr) const override; + + // The geometry shader impl draws primitives in two subpasses. The first pass fills the interior + // and does edge AA. The second pass does touch up on corner pixels. + enum class Subpass : bool { + kHulls, + kCorners + }; + + Attribute fInputXOrYValues; + mutable Subpass fSubpass = Subpass::kHulls; + + class Impl; + class TriangleHullImpl; + class CurveHullImpl; + class CornerImpl; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.cpp b/gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.cpp new file mode 100644 index 0000000000..72aa6f0630 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrOctoBounds.h" +#include + +bool GrOctoBounds::clip(const SkIRect& clipRect) { + // Intersect dev bounds with the clip rect. + float l = std::max(fBounds.left(), (float)clipRect.left()); + float t = std::max(fBounds.top(), (float)clipRect.top()); + float r = std::min(fBounds.right(), (float)clipRect.right()); + float b = std::min(fBounds.bottom(), (float)clipRect.bottom()); + + float l45 = fBounds45.left(); + float t45 = fBounds45.top(); + float r45 = fBounds45.right(); + float b45 = fBounds45.bottom(); + + // Check if either the bounds or 45-degree bounds are empty. We write this check as the NOT of + // non-empty rects, so we will return false if any values are NaN. + if (!(l < r && t < b && l45 < r45 && t45 < b45)) { + return false; + } + + // Tighten dev bounds around the new (octagonal) intersection that results after clipping. This + // may be tighter now even than the clipped bounds, depending on the diagonals. Shader code that + // emits octagons expects both bounding boxes to circumcribe the inner octagon, and will fail if + // they do not. + if (l45 > Get_x45(r,b)) { + // Slide the bottom upward until it crosses the l45 diagonal at x=r. + // y = x + (y0 - x0) + // Substitute: l45 = x0 - y0 + // y = x - l45 + b = SkScalarPin(r - l45, t, b); + } else if (r45 < Get_x45(r,b)) { + // Slide the right side leftward until it crosses the r45 diagonal at y=b. + // x = y + (x0 - y0) + // Substitute: r45 = x0 - y0 + // x = y + r45 + r = SkScalarPin(b + r45, l, r); + } + if (l45 > Get_x45(l,t)) { + // Slide the left side rightward until it crosses the l45 diagonal at y=t. + // x = y + (x0 - y0) + // Substitute: l45 = x0 - y0 + // x = y + l45 + l = SkScalarPin(t + l45, l, r); + } else if (r45 < Get_x45(l,t)) { + // Slide the top downward until it crosses the r45 diagonal at x=l. + // y = x + (y0 - x0) + // Substitute: r45 = x0 - y0 + // y = x - r45 + t = SkScalarPin(l - r45, t, b); + } + if (t45 > Get_y45(l,b)) { + // Slide the left side rightward until it crosses the t45 diagonal at y=b. + // x = -y + (x0 + y0) + // Substitute: t45 = x0 + y0 + // x = -y + t45 + l = SkScalarPin(t45 - b, l, r); + } else if (b45 < Get_y45(l,b)) { + // Slide the bottom upward until it crosses the b45 diagonal at x=l. + // y = -x + (y0 + x0) + // Substitute: b45 = x0 + y0 + // y = -x + b45 + b = SkScalarPin(b45 - l, t, b); + } + if (t45 > Get_y45(r,t)) { + // Slide the top downward until it crosses the t45 diagonal at x=r. + // y = -x + (y0 + x0) + // Substitute: t45 = x0 + y0 + // y = -x + t45 + t = SkScalarPin(t45 - r, t, b); + } else if (b45 < Get_y45(r,t)) { + // Slide the right side leftward until it crosses the b45 diagonal at y=t. + // x = -y + (x0 + y0) + // Substitute: b45 = x0 + y0 + // x = -y + b45 + r = SkScalarPin(b45 - t, l, r); + } + + // Tighten the 45-degree bounding box. Since the dev bounds are now fully tightened, we only + // have to clamp the diagonals to outer corners. + // NOTE: This will not cause l,t,r,b to need more insetting. We only ever change a diagonal by + // pinning it to a FAR corner, which, by definition, is still outside the other corners. + l45 = SkScalarPin(Get_x45(l,b), l45, r45); + t45 = SkScalarPin(Get_y45(l,t), t45, b45); + r45 = SkScalarPin(Get_x45(r,t), l45, r45); + b45 = SkScalarPin(Get_y45(r,b), t45, b45); + + // Make one final check for empty or NaN bounds. If the dev bounds were clipped completely + // outside one of the diagonals, they will have been pinned to empty. It's also possible that + // some Infs crept in and turned into NaNs. + if (!(l < r && t < b && l45 < r45 && t45 < b45)) { + return false; + } + + fBounds.setLTRB(l, t, r, b); + fBounds45.setLTRB(l45, t45, r45, b45); + +#ifdef SK_DEBUG + // Verify dev bounds are inside the clip rect. + SkASSERT(l >= (float)clipRect.left()); + SkASSERT(t >= (float)clipRect.top()); + SkASSERT(r <= (float)clipRect.right()); + SkASSERT(b <= (float)clipRect.bottom()); + this->validateBoundsAreTight(); +#endif + + return true; +} + +#if defined(SK_DEBUG) || defined(GR_TEST_UTILS) +void GrOctoBounds::validateBoundsAreTight() const { + this->validateBoundsAreTight([](bool cond, const char* file, int line, const char* code) { + SkASSERTF(cond, "%s(%d): assertion failure: \"assert(%s)\"", file, line, code); + }); +} + +void GrOctoBounds::validateBoundsAreTight(const std::function& validateFn) const { + // The octobounds calculated in GrCCPerFlushResources::renderShapeInAtlas use FMAs to compute + // M * (x,y) and T45 * M * (x,y) in parallel. This leads to a not-insignificant floating point + // difference between (T45 * M * (x,y)) stored in fBounds45, and T45 * (M * (x,y)) calculated + // here from fBounds with the Get_xy45 functions. + constexpr static float epsilon = 1e-2f; + + float l=fBounds.left(), l45=fBounds45.left(); + float t=fBounds.top(), t45=fBounds45.top(); + float r=fBounds.right(), r45=fBounds45.right(); + float b=fBounds.bottom(), b45=fBounds45.bottom(); + +#define VALIDATE(CODE) validateFn(CODE, __FILE__, __LINE__, #CODE) + // Verify diagonals are inside far corners of the dev bounds. + VALIDATE(l45 >= Get_x45(l,b) - epsilon); + VALIDATE(t45 >= Get_y45(l,t) - epsilon); + VALIDATE(r45 <= Get_x45(r,t) + epsilon); + VALIDATE(b45 <= Get_y45(r,b) + epsilon); + // Verify verticals and horizontals are inside far corners of the 45-degree dev bounds. + VALIDATE(l >= Get_x(l45,t45) - epsilon); + VALIDATE(t >= Get_y(r45,t45) - epsilon); + VALIDATE(r <= Get_x(r45,b45) + epsilon); + VALIDATE(b <= Get_y(l45,b45) + epsilon); + // Verify diagonals are outside middle corners of the dev bounds. + VALIDATE(l45 <= Get_x45(r,b) + epsilon); + VALIDATE(l45 <= Get_x45(l,t) + epsilon); + VALIDATE(t45 <= Get_y45(l,b) + epsilon); + VALIDATE(t45 <= Get_y45(r,t) + epsilon); + VALIDATE(r45 >= Get_x45(l,t) - epsilon); + VALIDATE(r45 >= Get_x45(r,b) - epsilon); + VALIDATE(b45 >= Get_y45(r,t) - epsilon); + VALIDATE(b45 >= Get_y45(l,b) - epsilon); + // Verify verticals and horizontals are outside middle corners of the 45-degree dev bounds. + VALIDATE(l <= Get_x(l45,b45) + epsilon); + VALIDATE(l <= Get_x(r45,t45) + epsilon); + VALIDATE(t <= Get_y(r45,b45) + epsilon); + VALIDATE(t <= Get_y(l45,t45) + epsilon); + VALIDATE(r >= Get_x(r45,t45) - epsilon); + VALIDATE(r >= Get_x(l45,b45) - epsilon); + VALIDATE(b >= Get_y(l45,t45) - epsilon); + VALIDATE(b >= Get_y(r45,b45) - epsilon); +#undef VALIDATE +} +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.h b/gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.h new file mode 100644 index 0000000000..d2272ae005 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrOctoBounds.h @@ -0,0 +1,115 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOctoBounds_DEFINED +#define GrOctoBounds_DEFINED + +#include "include/core/SkRect.h" +#include + +/** + * This class is composed of two bounding boxes: one in device space, and one in a 45-degree rotated + * space. + * + * The 45-degree bounding box resides in "| 1 -1 | * coords" space. + * | 1 1 | + * + * The intersection of these two boxes defines the bounding octagon of a shape. + * + * Furthermore, both bounding boxes are fully tightened. This means we can blindly find the + * intersections between each diagonal and its vertical and horizontal neighbors, and be left with + * 8 points that define a convex (possibly degenerate) octagon. + */ +class GrOctoBounds { +public: + GrOctoBounds() = default; + GrOctoBounds(const SkRect& bounds, const SkRect& bounds45) { + this->set(bounds, bounds45); + } + + void set(const SkRect& bounds, const SkRect& bounds45) { + fBounds = bounds; + fBounds45 = bounds45; + SkDEBUGCODE(this->validateBoundsAreTight()); + } + + bool operator==(const GrOctoBounds& that) const { + return fBounds == that.fBounds && fBounds45 == that.fBounds45; + } + bool operator!=(const GrOctoBounds& that) const { return !(*this == that); } + + const SkRect& bounds() const { return fBounds; } + float left() const { return fBounds.left(); } + float top() const { return fBounds.top(); } + float right() const { return fBounds.right(); } + float bottom() const { return fBounds.bottom(); } + + + // The 45-degree bounding box resides in "| 1 -1 | * coords" space. + // | 1 1 | + const SkRect& bounds45() const { return fBounds45; } + float left45() const { return fBounds45.left(); } + float top45() const { return fBounds45.top(); } + float right45() const { return fBounds45.right(); } + float bottom45() const { return fBounds45.bottom(); } + + void roundOut(SkIRect* out) const { + // The octagon is the intersection of fBounds and fBounds45 (see the comment at the start of + // the class). The octagon's bounding box is therefore just fBounds. And the integer + // bounding box can be found by simply rounding out fBounds. + fBounds.roundOut(out); + } + + GrOctoBounds makeOffset(float dx, float dy) const { + GrOctoBounds offset; + offset.setOffset(*this, dx, dy); + return offset; + } + + void setOffset(const GrOctoBounds& octoBounds, float dx, float dy) { + fBounds = octoBounds.fBounds.makeOffset(dx, dy); + fBounds45 = octoBounds.fBounds45.makeOffset(dx - dy, dx + dy); + SkDEBUGCODE(this->validateBoundsAreTight()); + } + + void outset(float radius) { + fBounds.outset(radius, radius); + fBounds45.outset(radius*SK_ScalarSqrt2, radius*SK_ScalarSqrt2); + SkDEBUGCODE(this->validateBoundsAreTight()); + } + + // Clips the octo bounds by a clip rect and ensures the resulting bounds are fully tightened. + // Returns false if the octagon and clipRect do not intersect at all. + // + // NOTE: Does not perform a trivial containment test before the clip routine. It is probably a + // good idea to not call this method if 'this->bounds()' are fully contained within 'clipRect'. + bool SK_WARN_UNUSED_RESULT clip(const SkIRect& clipRect); + + // The 45-degree bounding box resides in "| 1 -1 | * coords" space. + // | 1 1 | + // + // i.e., | x45 | = | x - y | + // | y45 | = | x + y | + // + // These methods transform points between device space and 45-degree space. + constexpr static float Get_x45(float x, float y) { return x - y; } + constexpr static float Get_y45(float x, float y) { return x + y; } + constexpr static float Get_x(float x45, float y45) { return (x45 + y45) * .5f; } + constexpr static float Get_y(float x45, float y45) { return (y45 - x45) * .5f; } + +#if defined(SK_DEBUG) || defined(GR_TEST_UTILS) + void validateBoundsAreTight() const; + void validateBoundsAreTight(const std::function& validateFn) const; +#endif + +private: + SkRect fBounds; + SkRect fBounds45; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.cpp b/gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.cpp new file mode 100644 index 0000000000..41da1f9bf7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrSampleMaskProcessor.h" + +#include "src/gpu/GrMesh.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +class GrSampleMaskProcessor::Impl : public GrGLSLGeometryProcessor { +public: + Impl(std::unique_ptr shader) : fShader(std::move(shader)) {} + +private: + void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&&) override {} + + void onEmitCode(EmitArgs&, GrGPArgs*) override; + + const std::unique_ptr fShader; +}; + +void GrSampleMaskProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + const GrSampleMaskProcessor& proc = args.fGP.cast(); + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLVertexBuilder* v = args.fVertBuilder; + int numInputPoints = proc.numInputPoints(); + int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3; + + varyingHandler->emitAttributes(proc); + SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform()); + + if (PrimitiveType::kTriangles == proc.fPrimitiveType) { + SkASSERT(!proc.hasInstanceAttributes()); // Triangles are drawn with vertex arrays. + gpArgs->fPositionVar = proc.fInputAttribs.front().asShaderVar(); + } else { + SkASSERT(!proc.hasVertexAttributes()); // Curves are drawn with instanced rendering. + + // Shaders expect a global "bloat" variable when calculating gradients. + v->defineConstant("half", "bloat", ".5"); + + const char* swizzle = (4 == numInputPoints || proc.hasInputWeight()) ? "xyzw" : "xyz"; + v->codeAppendf("float%ix2 pts = transpose(float2x%i(X.%s, Y.%s));", + inputWidth, inputWidth, swizzle, swizzle); + + const char* hullPts = "pts"; + fShader->emitSetupCode(v, "pts", &hullPts); + v->codeAppendf("float2 vertexpos = %s[sk_VertexID ^ (sk_VertexID >> 1)];", hullPts); + gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos"); + + fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, + &AccessCodeString(v), "vertexpos", nullptr, nullptr, nullptr); + } + + // Fragment shader. + fShader->emitSampleMaskCode(args.fFragBuilder); +} + +void GrSampleMaskProcessor::reset(PrimitiveType primitiveType, GrResourceProvider* rp) { + fPrimitiveType = primitiveType; // This will affect the return values for numInputPoints, etc. + SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType); + + this->resetCustomFeatures(); + fInputAttribs.reset(); + + switch (fPrimitiveType) { + case PrimitiveType::kTriangles: + case PrimitiveType::kWeightedTriangles: + fInputAttribs.emplace_back("point", kFloat2_GrVertexAttribType, kFloat2_GrSLType); + this->setVertexAttributes(fInputAttribs.begin(), 1); + this->setInstanceAttributes(nullptr, 0); + break; + case PrimitiveType::kQuadratics: + case PrimitiveType::kCubics: + case PrimitiveType::kConics: { + auto instanceAttribType = (PrimitiveType::kQuadratics == fPrimitiveType) + ? kFloat3_GrVertexAttribType : kFloat4_GrVertexAttribType; + auto shaderVarType = (PrimitiveType::kQuadratics == fPrimitiveType) + ? kFloat3_GrSLType : kFloat4_GrSLType; + fInputAttribs.emplace_back("X", instanceAttribType, shaderVarType); + fInputAttribs.emplace_back("Y", instanceAttribType, shaderVarType); + this->setVertexAttributes(nullptr, 0); + this->setInstanceAttributes(fInputAttribs.begin(), fInputAttribs.count()); + this->setWillUseCustomFeature(CustomFeatures::kSampleLocations); + break; + } + } +} + +void GrSampleMaskProcessor::appendMesh(sk_sp instanceBuffer, int instanceCount, + int baseInstance, SkTArray* out) const { + SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType); + + switch (fPrimitiveType) { + case PrimitiveType::kTriangles: + case PrimitiveType::kWeightedTriangles: { + GrMesh& mesh = out->emplace_back(GrPrimitiveType::kTriangles); + mesh.setNonIndexedNonInstanced(instanceCount * 3); + mesh.setVertexData(std::move(instanceBuffer), baseInstance * 3); + break; + } + case PrimitiveType::kQuadratics: + case PrimitiveType::kCubics: + case PrimitiveType::kConics: { + GrMesh& mesh = out->emplace_back(GrPrimitiveType::kTriangleStrip); + mesh.setInstanced(std::move(instanceBuffer), instanceCount, baseInstance, 4); + break; + } + } +} + +GrGLSLPrimitiveProcessor* GrSampleMaskProcessor::onCreateGLSLInstance( + std::unique_ptr shader) const { + return new Impl(std::move(shader)); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.h b/gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.h new file mode 100644 index 0000000000..3fdd96f5e8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrSampleMaskProcessor.h @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSampleMaskProcessor_DEFINED +#define GrSampleMaskProcessor_DEFINED + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +/** + * This class implements GrCCCoverageProcessor with MSAA using the sample mask. + */ +class GrSampleMaskProcessor : public GrCCCoverageProcessor { +public: + GrSampleMaskProcessor() : GrCCCoverageProcessor(kGrSampleMaskProcessor_ClassID) {} + +private: + void reset(PrimitiveType, GrResourceProvider*) override; + + void appendMesh(sk_sp instanceBuffer, int instanceCount, int baseInstance, + SkTArray* out) const override; + + GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr) const override; + + SkSTArray<2, Attribute> fInputAttribs; + + class Impl; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.cpp b/gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.cpp new file mode 100644 index 0000000000..c7ec6da90c --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrStencilAtlasOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/ccpr/GrCCPerFlushResources.h" +#include "src/gpu/ccpr/GrSampleMaskProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +namespace { + +class StencilResolveProcessor : public GrGeometryProcessor { +public: + StencilResolveProcessor() : GrGeometryProcessor(kStencilResolveProcessor_ClassID) { + static constexpr Attribute kIBounds = { + "ibounds", kShort4_GrVertexAttribType, kShort4_GrSLType}; + this->setInstanceAttributes(&kIBounds, 1); + SkASSERT(this->instanceStride() == sizeof(GrStencilAtlasOp::ResolveRectInstance)); + } + +private: + const char* name() const override { return "GrCCPathProcessor"; } + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {} + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + class Impl; +}; + +// This processor draws pixel-aligned rectangles directly on top of every path in the atlas. +// The caller should have set up the instance data such that "Nonzero" paths get clockwise +// rectangles (l < r) and "even/odd" paths get counter-clockwise (r < l). Its purpose +// is to convert winding counts in the stencil buffer to A8 coverage in the color buffer. +class StencilResolveProcessor::Impl : public GrGLSLGeometryProcessor { + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + args.fVaryingHandler->emitAttributes(args.fGP.cast()); + + GrGLSLVertexBuilder* v = args.fVertBuilder; + v->codeAppendf("short2 devcoord;"); + v->codeAppendf("devcoord.x = (0 == (sk_VertexID & 1)) ? ibounds.x : ibounds.z;"); + v->codeAppendf("devcoord.y = (sk_VertexID < 2) ? ibounds.y : ibounds.w;"); + + v->codeAppendf("float2 atlascoord = float2(devcoord);"); + gpArgs->fPositionVar.set(kFloat2_GrSLType, "atlascoord"); + + // Just output "1" for coverage. This will be modulated by the MSAA stencil test. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + f->codeAppendf("%s = %s = half4(1);", args.fOutputColor, args.fOutputCoverage); + } + + void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&&) override {} +}; + +GrGLSLPrimitiveProcessor* StencilResolveProcessor::createGLSLInstance(const GrShaderCaps&) const { + return new Impl(); +} + +} + +std::unique_ptr GrStencilAtlasOp::Make( + GrRecordingContext* context, sk_sp resources, + FillBatchID fillBatchID, StrokeBatchID strokeBatchID, int baseStencilResolveInstance, + int endStencilResolveInstance, const SkISize& drawBounds) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate( + std::move(resources), fillBatchID, strokeBatchID, baseStencilResolveInstance, + endStencilResolveInstance, drawBounds); +} + +// Increments clockwise triangles and decrements counterclockwise. We use the same incr/decr +// settings regardless of fill rule; fill rule is accounted for during the resolve step. +static constexpr GrUserStencilSettings kIncrDecrStencil( + GrUserStencilSettings::StaticInitSeparate< + 0x0000, 0x0000, + GrUserStencilTest::kNever, GrUserStencilTest::kNever, + 0xffff, 0xffff, + GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap, + GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap, + 0xffff, 0xffff>() +); + +// Resolves stencil winding counts to A8 coverage. Leaves stencil values untouched. +static constexpr GrUserStencilSettings kResolveStencilCoverage( + GrUserStencilSettings::StaticInitSeparate< + 0x0000, 0x0000, + GrUserStencilTest::kNotEqual, GrUserStencilTest::kNotEqual, + 0xffff, 0x1, + GrUserStencilOp::kKeep, GrUserStencilOp::kKeep, + GrUserStencilOp::kKeep, GrUserStencilOp::kKeep, + 0xffff, 0xffff>() +); + +// Same as above, but also resets stencil values to zero. This is better for non-tilers +// where we prefer to not clear the stencil buffer at the beginning of every render pass. +static constexpr GrUserStencilSettings kResolveStencilCoverageAndReset( + GrUserStencilSettings::StaticInitSeparate< + 0x0000, 0x0000, + GrUserStencilTest::kNotEqual, GrUserStencilTest::kNotEqual, + 0xffff, 0x1, + GrUserStencilOp::kZero, GrUserStencilOp::kZero, + GrUserStencilOp::kKeep, GrUserStencilOp::kKeep, + 0xffff, 0xffff>() +); + +void GrStencilAtlasOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + SkIRect drawBoundsRect = SkIRect::MakeWH(fDrawBounds.width(), fDrawBounds.height()); + + GrPipeline pipeline( + GrScissorTest::kEnabled, GrDisableColorXPFactory::MakeXferProcessor(), + flushState->drawOpArgs().outputSwizzle(), GrPipeline::InputFlags::kHWAntialias, + &kIncrDecrStencil); + + GrSampleMaskProcessor sampleMaskProc; + + fResources->filler().drawFills( + flushState, &sampleMaskProc, pipeline, fFillBatchID, drawBoundsRect); + + fResources->stroker().drawStrokes( + flushState, &sampleMaskProc, fStrokeBatchID, drawBoundsRect); + + // We resolve the stencil coverage to alpha by drawing pixel-aligned boxes. Fine raster is + // not necessary, and will even cause artifacts if using mixed samples. + constexpr auto noHWAA = GrPipeline::InputFlags::kNone; + + const auto* stencilResolveSettings = (flushState->caps().discardStencilValuesAfterRenderPass()) + // The next draw will be the final op in the renderTargetContext. So if Ganesh is + // planning to discard the stencil values anyway, we don't actually need to reset them + // back to zero. + ? &kResolveStencilCoverage + : &kResolveStencilCoverageAndReset; + + GrPipeline resolvePipeline(GrScissorTest::kEnabled, SkBlendMode::kSrc, + flushState->drawOpArgs().outputSwizzle(), noHWAA, + stencilResolveSettings); + GrPipeline::FixedDynamicState scissorRectState(drawBoundsRect); + + GrMesh mesh(GrPrimitiveType::kTriangleStrip); + mesh.setInstanced(fResources->refStencilResolveBuffer(), + fEndStencilResolveInstance - fBaseStencilResolveInstance, + fBaseStencilResolveInstance, 4); + + StencilResolveProcessor primProc; + + GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(), + flushState->drawOpArgs().origin(), + resolvePipeline, + primProc, + &scissorRectState, + nullptr, 0); + + flushState->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::Make(drawBoundsRect)); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.h b/gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.h new file mode 100644 index 0000000000..8c1f4fbddd --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrStencilAtlasOp.h @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStencilAtlasOp_DEFINED +#define GrStencilAtlasOp_DEFINED + +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/ccpr/GrCCFiller.h" +#include "src/gpu/ccpr/GrCCStroker.h" +#include "src/gpu/ops/GrDrawOp.h" + +class GrCCPerFlushResources; + +// Renders literal A8 coverage to a CCPR atlas using an intermediate MSAA stencil buffer. +class GrStencilAtlasOp : public GrDrawOp { +public: + DEFINE_OP_CLASS_ID + + using FillBatchID = GrCCFiller::BatchID; + using StrokeBatchID = GrCCStroker::BatchID; + + // Once all the paths in an atlas have been drawn to the stencil buffer, we make a final pass + // where we draw "resolve" rects over each path whose purpose is to convert winding counts to A8 + // coverage. + struct ResolveRectInstance { + int16_t l, t, r, b; + }; + + // GrDrawOp interface. + const char* name() const override { return "StencilAtlasOp (CCPR)"; } + FixedFunctionFlags fixedFunctionFlags() const override { + return FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil; + } + + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override { + return GrProcessorSet::EmptySetAnalysis(); + } + CombineResult onCombineIfPossible(GrOp* other, const GrCaps&) override { + // We will only make multiple copy ops if they have different source proxies. + // TODO: make use of texture chaining. + return CombineResult::kCannotCombine; + } + void onPrepare(GrOpFlushState*) override {} + + static std::unique_ptr Make( + GrRecordingContext*, sk_sp, FillBatchID, StrokeBatchID, + int baseStencilResolveInstance, int endStencilResolveInstance, + const SkISize& drawBounds); + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override; + +private: + friend class ::GrOpMemoryPool; // for ctor + + GrStencilAtlasOp(sk_sp resources, FillBatchID fillBatchID, + StrokeBatchID strokeBatchID, int baseStencilResolveInstance, + int endStencilResolveInstance, const SkISize& drawBounds) + : GrDrawOp(ClassID()) + , fResources(std::move(resources)) + , fFillBatchID(fillBatchID) + , fStrokeBatchID(strokeBatchID) + , fBaseStencilResolveInstance(baseStencilResolveInstance) + , fEndStencilResolveInstance(endStencilResolveInstance) + , fDrawBounds(drawBounds) { + this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()), + GrOp::HasAABloat::kNo, GrOp::IsHairline::kNo); + } + + const sk_sp fResources; + const FillBatchID fFillBatchID; + const StrokeBatchID fStrokeBatchID; + const int fBaseStencilResolveInstance; + const int fEndStencilResolveInstance; + const SkISize fDrawBounds; + int fResolveBaseVertex; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.cpp b/gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.cpp new file mode 100644 index 0000000000..ae5cfca504 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.cpp @@ -0,0 +1,553 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ccpr/GrVSCoverageProcessor.h" + +#include "src/gpu/GrMesh.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +// This class implements the coverage processor with vertex shaders. +class GrVSCoverageProcessor::Impl : public GrGLSLGeometryProcessor { +public: + Impl(std::unique_ptr shader, int numSides) + : fShader(std::move(shader)), fNumSides(numSides) {} + +private: + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, + FPCoordTransformIter&& transformIter) final { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + + void onEmitCode(EmitArgs&, GrGPArgs*) override; + + const std::unique_ptr fShader; + const int fNumSides; +}; + +static constexpr int kInstanceAttribIdx_X = 0; // Transposed X values of all input points. +static constexpr int kInstanceAttribIdx_Y = 1; // Transposed Y values of all input points. + +// Vertex data tells the shader how to offset vertices for conservative raster, as well as how to +// calculate coverage values for corners and edges. +static constexpr int kVertexData_LeftNeighborIdShift = 10; +static constexpr int kVertexData_RightNeighborIdShift = 8; +static constexpr int kVertexData_BloatIdxShift = 6; +static constexpr int kVertexData_InvertNegativeCoverageBit = 1 << 5; +static constexpr int kVertexData_IsCornerBit = 1 << 4; +static constexpr int kVertexData_IsEdgeBit = 1 << 3; +static constexpr int kVertexData_IsHullBit = 1 << 2; + +static constexpr int32_t pack_vertex_data(int32_t leftNeighborID, int32_t rightNeighborID, + int32_t bloatIdx, int32_t cornerID, + int32_t extraData = 0) { + return (leftNeighborID << kVertexData_LeftNeighborIdShift) | + (rightNeighborID << kVertexData_RightNeighborIdShift) | + (bloatIdx << kVertexData_BloatIdxShift) | + cornerID | extraData; +} + +static constexpr int32_t hull_vertex_data(int32_t cornerID, int32_t bloatIdx, int n) { + return pack_vertex_data((cornerID + n - 1) % n, (cornerID + 1) % n, bloatIdx, cornerID, + kVertexData_IsHullBit); +} + +static constexpr int32_t edge_vertex_data(int32_t edgeID, int32_t endptIdx, int32_t bloatIdx, + int n) { + return pack_vertex_data(0 == endptIdx ? (edgeID + 1) % n : edgeID, + 0 == endptIdx ? (edgeID + 1) % n : edgeID, + bloatIdx, 0 == endptIdx ? edgeID : (edgeID + 1) % n, + kVertexData_IsEdgeBit | + (!endptIdx ? kVertexData_InvertNegativeCoverageBit : 0)); +} + +static constexpr int32_t corner_vertex_data(int32_t leftID, int32_t cornerID, int32_t rightID, + int32_t bloatIdx) { + return pack_vertex_data(leftID, rightID, bloatIdx, cornerID, kVertexData_IsCornerBit); +} + +static constexpr int32_t kTriangleVertices[] = { + hull_vertex_data(0, 0, 3), + hull_vertex_data(0, 1, 3), + hull_vertex_data(0, 2, 3), + hull_vertex_data(1, 0, 3), + hull_vertex_data(1, 1, 3), + hull_vertex_data(1, 2, 3), + hull_vertex_data(2, 0, 3), + hull_vertex_data(2, 1, 3), + hull_vertex_data(2, 2, 3), + + edge_vertex_data(0, 0, 0, 3), + edge_vertex_data(0, 0, 1, 3), + edge_vertex_data(0, 0, 2, 3), + edge_vertex_data(0, 1, 0, 3), + edge_vertex_data(0, 1, 1, 3), + edge_vertex_data(0, 1, 2, 3), + + edge_vertex_data(1, 0, 0, 3), + edge_vertex_data(1, 0, 1, 3), + edge_vertex_data(1, 0, 2, 3), + edge_vertex_data(1, 1, 0, 3), + edge_vertex_data(1, 1, 1, 3), + edge_vertex_data(1, 1, 2, 3), + + edge_vertex_data(2, 0, 0, 3), + edge_vertex_data(2, 0, 1, 3), + edge_vertex_data(2, 0, 2, 3), + edge_vertex_data(2, 1, 0, 3), + edge_vertex_data(2, 1, 1, 3), + edge_vertex_data(2, 1, 2, 3), + + corner_vertex_data(2, 0, 1, 0), + corner_vertex_data(2, 0, 1, 1), + corner_vertex_data(2, 0, 1, 2), + corner_vertex_data(2, 0, 1, 3), + + corner_vertex_data(0, 1, 2, 0), + corner_vertex_data(0, 1, 2, 1), + corner_vertex_data(0, 1, 2, 2), + corner_vertex_data(0, 1, 2, 3), + + corner_vertex_data(1, 2, 0, 0), + corner_vertex_data(1, 2, 0, 1), + corner_vertex_data(1, 2, 0, 2), + corner_vertex_data(1, 2, 0, 3), +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey); + +static constexpr uint16_t kRestartStrip = 0xffff; + +static constexpr uint16_t kTriangleIndicesAsStrips[] = { + 1, 2, 0, 3, 8, kRestartStrip, // First corner and main body of the hull. + 4, 5, 3, 6, 8, 7, kRestartStrip, // Opposite side and corners of the hull. + 10, 9, 11, 14, 12, 13, kRestartStrip, // First edge. + 16, 15, 17, 20, 18, 19, kRestartStrip, // Second edge. + 22, 21, 23, 26, 24, 25, kRestartStrip, // Third edge. + 28, 27, 29, 30, kRestartStrip, // First corner. + 32, 31, 33, 34, kRestartStrip, // Second corner. + 36, 35, 37, 38 // Third corner. +}; + +static constexpr uint16_t kTriangleIndicesAsTris[] = { + // First corner and main body of the hull. + 1, 2, 0, + 2, 3, 0, + 0, 3, 8, // Main body. + + // Opposite side and corners of the hull. + 4, 5, 3, + 5, 6, 3, + 3, 6, 8, + 6, 7, 8, + + // First edge. + 10, 9, 11, + 9, 14, 11, + 11, 14, 12, + 14, 13, 12, + + // Second edge. + 16, 15, 17, + 15, 20, 17, + 17, 20, 18, + 20, 19, 18, + + // Third edge. + 22, 21, 23, + 21, 26, 23, + 23, 26, 24, + 26, 25, 24, + + // First corner. + 28, 27, 29, + 27, 30, 29, + + // Second corner. + 32, 31, 33, + 31, 34, 33, + + // Third corner. + 36, 35, 37, + 35, 38, 37, +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey); + +// Curves, including quadratics, are drawn with a four-sided hull. +static constexpr int32_t kCurveVertices[] = { + hull_vertex_data(0, 0, 4), + hull_vertex_data(0, 1, 4), + hull_vertex_data(0, 2, 4), + hull_vertex_data(1, 0, 4), + hull_vertex_data(1, 1, 4), + hull_vertex_data(1, 2, 4), + hull_vertex_data(2, 0, 4), + hull_vertex_data(2, 1, 4), + hull_vertex_data(2, 2, 4), + hull_vertex_data(3, 0, 4), + hull_vertex_data(3, 1, 4), + hull_vertex_data(3, 2, 4), + + corner_vertex_data(3, 0, 1, 0), + corner_vertex_data(3, 0, 1, 1), + corner_vertex_data(3, 0, 1, 2), + corner_vertex_data(3, 0, 1, 3), + + corner_vertex_data(2, 3, 0, 0), + corner_vertex_data(2, 3, 0, 1), + corner_vertex_data(2, 3, 0, 2), + corner_vertex_data(2, 3, 0, 3), +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gCurveVertexBufferKey); + +static constexpr uint16_t kCurveIndicesAsStrips[] = { + 1, 0, 2, 11, 3, 5, 4, kRestartStrip, // First half of the hull (split diagonally). + 7, 6, 8, 5, 9, 11, 10, kRestartStrip, // Second half of the hull. + 13, 12, 14, 15, kRestartStrip, // First corner. + 17, 16, 18, 19 // Final corner. +}; + +static constexpr uint16_t kCurveIndicesAsTris[] = { + // First half of the hull (split diagonally). + 1, 0, 2, + 0, 11, 2, + 2, 11, 3, + 11, 5, 3, + 3, 5, 4, + + // Second half of the hull. + 7, 6, 8, + 6, 5, 8, + 8, 5, 9, + 5, 11, 9, + 9, 11, 10, + + // First corner. + 13, 12, 14, + 12, 15, 14, + + // Final corner. + 17, 16, 18, + 16, 19, 18, +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gCurveIndexBufferKey); + +// Generates a conservative raster hull around a triangle or curve. For triangles we generate +// additional conservative rasters with coverage ramps around the edges and corners. +// +// Triangles are drawn in three steps: (1) Draw a conservative raster of the entire triangle, with a +// coverage of +1. (2) Draw conservative rasters around each edge, with a coverage ramp from -1 to +// 0. These edge coverage values convert jagged conservative raster edges into smooth, antialiased +// ones. (3) Draw conservative rasters (aka pixel-size boxes) around each corner, replacing the +// previous coverage values with ones that ramp to zero in the bloat vertices that fall outside the +// triangle. +// +// Curve shaders handle the opposite edge and corners on their own. For curves we just generate a +// conservative raster here and the shader does the rest. +void GrVSCoverageProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + const GrVSCoverageProcessor& proc = args.fGP.cast(); + GrGLSLVertexBuilder* v = args.fVertBuilder; + int numInputPoints = proc.numInputPoints(); + + int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3; + const char* swizzle = (4 == inputWidth) ? "xyzw" : "xyz"; + v->codeAppendf("float%ix2 pts = transpose(float2x%i(%s.%s, %s.%s));", inputWidth, inputWidth, + proc.fInputXAndYValues[kInstanceAttribIdx_X].name(), swizzle, + proc.fInputXAndYValues[kInstanceAttribIdx_Y].name(), swizzle); + + v->codeAppend ("half wind;"); + Shader::CalcWind(proc, v, "pts", "wind"); + if (PrimitiveType::kWeightedTriangles == proc.fPrimitiveType) { + SkASSERT(3 == numInputPoints); + SkASSERT(kFloat4_GrVertexAttribType == + proc.fInputXAndYValues[kInstanceAttribIdx_X].cpuType()); + v->codeAppendf("wind *= half(%s.w);", + proc.fInputXAndYValues[kInstanceAttribIdx_X].name()); + } + + float bloat = kAABloatRadius; +#ifdef SK_DEBUG + if (proc.debugBloatEnabled()) { + bloat *= proc.debugBloat(); + } +#endif + v->defineConstant("bloat", bloat); + + const char* hullPts = "pts"; + fShader->emitSetupCode(v, "pts", (4 == fNumSides) ? &hullPts : nullptr); + + // Reverse all indices if the wind is counter-clockwise: [0, 1, 2] -> [2, 1, 0]. + v->codeAppendf("int clockwise_indices = wind > 0 ? %s : 0x%x - %s;", + proc.fPerVertexData.name(), + ((fNumSides - 1) << kVertexData_LeftNeighborIdShift) | + ((fNumSides - 1) << kVertexData_RightNeighborIdShift) | + (((1 << kVertexData_RightNeighborIdShift) - 1) ^ 3) | + (fNumSides - 1), + proc.fPerVertexData.name()); + + // Here we generate conservative raster geometry for the input polygon. It is the convex + // hull of N pixel-size boxes, one centered on each the input points. Each corner has three + // vertices, where one or two may cause degenerate triangles. The vertex data tells us how + // to offset each vertex. Triangle edges and corners are also handled here using the same + // concept. For more details on conservative raster, see: + // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html + v->codeAppendf("float2 corner = %s[clockwise_indices & 3];", hullPts); + v->codeAppendf("float2 left = %s[clockwise_indices >> %i];", + hullPts, kVertexData_LeftNeighborIdShift); + v->codeAppendf("float2 right = %s[(clockwise_indices >> %i) & 3];", + hullPts, kVertexData_RightNeighborIdShift); + + v->codeAppend ("float2 leftbloat = sign(corner - left);"); + v->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, " + "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);"); + + v->codeAppend ("float2 rightbloat = sign(right - corner);"); + v->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, " + "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);"); + + v->codeAppend ("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);"); + + v->codeAppend ("float2 bloatdir = leftbloat;"); + + v->codeAppend ("float2 leftdir = corner - left;"); + v->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);"); + + v->codeAppend ("float2 rightdir = right - corner;"); + v->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);"); + + v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner? + proc.fPerVertexData.name(), kVertexData_IsCornerBit); + + // In corner boxes, all 4 coverage values will not map linearly. + // Therefore it is important to align the box so its diagonal shared + // edge points out of the triangle, in the direction that ramps to 0. + v->codeAppend ( "bloatdir = float2(leftdir.x > rightdir.x ? +1 : -1, " + "leftdir.y > rightdir.y ? +1 : -1);"); + + // For corner boxes, we hack left_right_notequal to always true. This + // in turn causes the upcoming code to always rotate, generating all + // 4 vertices of the corner box. + v->codeAppendf( "left_right_notequal = bool2(true);"); + v->codeAppend ("}"); + + // At each corner of the polygon, our hull will have either 1, 2, or 3 vertices (or 4 if + // it's a corner box). We begin with this corner's first raster vertex (leftbloat), then + // continue rotating 90 degrees clockwise until we reach the desired raster vertex for this + // invocation. Corners with less than 3 corresponding raster vertices will result in + // redundant vertices and degenerate triangles. + v->codeAppendf("int bloatidx = (%s >> %i) & 3;", proc.fPerVertexData.name(), + kVertexData_BloatIdxShift); + v->codeAppend ("switch (bloatidx) {"); + v->codeAppend ( "case 3:"); + // Only corners will have bloatidx=3, and corners always rotate. + v->codeAppend ( "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW. + // fallthru. + v->codeAppend ( "case 2:"); + v->codeAppendf( "if (all(left_right_notequal)) {"); + v->codeAppend ( "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW. + v->codeAppend ( "}"); + // fallthru. + v->codeAppend ( "case 1:"); + v->codeAppendf( "if (any(left_right_notequal)) {"); + v->codeAppend ( "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW. + v->codeAppend ( "}"); + // fallthru. + v->codeAppend ("}"); + + v->codeAppend ("float2 vertexpos = fma(bloatdir, float2(bloat), corner);"); + gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos"); + + // Hulls have a coverage of +1 all around. + v->codeAppend ("half coverage = +1;"); + + if (3 == fNumSides) { + v->codeAppend ("half left_coverage; {"); + Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "left_coverage"); + v->codeAppend ("}"); + + v->codeAppend ("half right_coverage; {"); + Shader::CalcEdgeCoverageAtBloatVertex(v, "corner", "right", "bloatdir", "right_coverage"); + v->codeAppend ("}"); + + v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge? + proc.fPerVertexData.name(), kVertexData_IsEdgeBit); + v->codeAppend ( "coverage = left_coverage;"); + v->codeAppend ("}"); + + v->codeAppendf("if (0 != (%s & %i)) {", // Invert coverage? + proc.fPerVertexData.name(), + kVertexData_InvertNegativeCoverageBit); + v->codeAppend ( "coverage = -1 - coverage;"); + v->codeAppend ("}"); + } else if (!fShader->calculatesOwnEdgeCoverage()) { + // Determine the amount of coverage to subtract out for the flat edge of the curve. + v->codeAppendf("float2 p0 = pts[0], p1 = pts[%i];", numInputPoints - 1); + v->codeAppendf("float2 n = float2(p0.y - p1.y, p1.x - p0.x);"); + v->codeAppend ("float nwidth = bloat*2 * (abs(n.x) + abs(n.y));"); + // When nwidth=0, wind must also be 0 (and coverage * wind = 0). So it doesn't matter + // what we come up with here as long as it isn't NaN or Inf. + v->codeAppend ("float d = dot(p0 - vertexpos, n);"); + v->codeAppend ("d /= (0 != nwidth) ? nwidth : 1;"); + v->codeAppend ("coverage = half(d) - .5*sign(wind);"); + } + + // Non-corner geometry should have zero effect from corner coverage. + v->codeAppend ("half2 corner_coverage = half2(0);"); + + v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner? + proc.fPerVertexData.name(), kVertexData_IsCornerBit); + // Erase what the previous geometry wrote. + v->codeAppend ( "wind = -wind;"); + if (3 == fNumSides) { + v->codeAppend ("coverage = 1 + left_coverage + right_coverage;"); + } else if (!fShader->calculatesOwnEdgeCoverage()) { + v->codeAppend ("coverage = -coverage;"); + } + + // Corner boxes require attenuated coverage. + v->codeAppend ( "half attenuation; {"); + Shader::CalcCornerAttenuation(v, "leftdir", "rightdir", "attenuation"); + v->codeAppend ( "}"); + + // Attenuate corner coverage towards the outermost vertex (where bloatidx=0). + // This is all that curves need: At each vertex of the corner box, the curve + // Shader will calculate the curve's local coverage value, interpolate it + // alongside our attenuation parameter, and multiply the two together for a + // final coverage value. + v->codeAppend ( "corner_coverage = (0 == bloatidx) ? half2(0, attenuation) : half2(-1,+1);"); + + if (3 == fNumSides) { + // For triangles we also provide the actual coverage values at each vertex of + // the corner box. + v->codeAppend ("if (1 == bloatidx || 2 == bloatidx) {"); + v->codeAppend ( "corner_coverage.x -= right_coverage;"); + v->codeAppend ("}"); + v->codeAppend ("if (bloatidx >= 2) {"); + v->codeAppend ( "corner_coverage.x -= left_coverage;"); + v->codeAppend ("}"); + } + v->codeAppend ("}"); + + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + v->codeAppend ("coverage *= wind;"); + v->codeAppend ("corner_coverage.x *= wind;"); + fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, &AccessCodeString(v), + "vertexpos", "coverage", "corner_coverage", "wind"); + + varyingHandler->emitAttributes(proc); + SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform()); + + // Fragment shader. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + f->codeAppendf("half coverage;"); + fShader->emitFragmentCoverageCode(f, "coverage"); + f->codeAppendf("%s = half4(coverage);", args.fOutputColor); + f->codeAppendf("%s = half4(1);", args.fOutputCoverage); +} + +void GrVSCoverageProcessor::reset(PrimitiveType primitiveType, GrResourceProvider* rp) { + const GrCaps& caps = *rp->caps(); + + fPrimitiveType = primitiveType; + switch (fPrimitiveType) { + case PrimitiveType::kTriangles: + case PrimitiveType::kWeightedTriangles: { + GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey); + fVertexBuffer = rp->findOrMakeStaticBuffer( + GrGpuBufferType::kVertex, sizeof(kTriangleVertices), kTriangleVertices, + gTriangleVertexBufferKey); + GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey); + if (caps.usePrimitiveRestart()) { + fIndexBuffer = rp->findOrMakeStaticBuffer( + GrGpuBufferType::kIndex, sizeof(kTriangleIndicesAsStrips), + kTriangleIndicesAsStrips, gTriangleIndexBufferKey); + fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsStrips); + } else { + fIndexBuffer = rp->findOrMakeStaticBuffer( + GrGpuBufferType::kIndex, sizeof(kTriangleIndicesAsTris), + kTriangleIndicesAsTris, gTriangleIndexBufferKey); + fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsTris); + } + break; + } + + case PrimitiveType::kQuadratics: + case PrimitiveType::kCubics: + case PrimitiveType::kConics: { + GR_DEFINE_STATIC_UNIQUE_KEY(gCurveVertexBufferKey); + fVertexBuffer = rp->findOrMakeStaticBuffer( + GrGpuBufferType::kVertex, sizeof(kCurveVertices), kCurveVertices, + gCurveVertexBufferKey); + GR_DEFINE_STATIC_UNIQUE_KEY(gCurveIndexBufferKey); + if (caps.usePrimitiveRestart()) { + fIndexBuffer = rp->findOrMakeStaticBuffer( + GrGpuBufferType::kIndex, sizeof(kCurveIndicesAsStrips), + kCurveIndicesAsStrips, gCurveIndexBufferKey); + fNumIndicesPerInstance = SK_ARRAY_COUNT(kCurveIndicesAsStrips); + } else { + fIndexBuffer = rp->findOrMakeStaticBuffer( + GrGpuBufferType::kIndex, sizeof(kCurveIndicesAsTris), kCurveIndicesAsTris, + gCurveIndexBufferKey); + fNumIndicesPerInstance = SK_ARRAY_COUNT(kCurveIndicesAsTris); + } + break; + } + } + + GrVertexAttribType xyAttribType; + GrSLType xySLType; + if (4 == this->numInputPoints() || this->hasInputWeight()) { + GR_STATIC_ASSERT(offsetof(QuadPointInstance, fX) == 0); + GR_STATIC_ASSERT(sizeof(QuadPointInstance::fX) == + GrVertexAttribTypeSize(kFloat4_GrVertexAttribType)); + GR_STATIC_ASSERT(sizeof(QuadPointInstance::fY) == + GrVertexAttribTypeSize(kFloat4_GrVertexAttribType)); + xyAttribType = kFloat4_GrVertexAttribType; + xySLType = kFloat4_GrSLType; + } else { + GR_STATIC_ASSERT(sizeof(TriPointInstance) == + 2 * GrVertexAttribTypeSize(kFloat3_GrVertexAttribType)); + xyAttribType = kFloat3_GrVertexAttribType; + xySLType = kFloat3_GrSLType; + } + fInputXAndYValues[kInstanceAttribIdx_X] = {"X", xyAttribType, xySLType}; + fInputXAndYValues[kInstanceAttribIdx_Y] = {"Y", xyAttribType, xySLType}; + this->setInstanceAttributes(fInputXAndYValues, 2); + fPerVertexData = {"vertexdata", kInt_GrVertexAttribType, kInt_GrSLType}; + this->setVertexAttributes(&fPerVertexData, 1); + + if (caps.usePrimitiveRestart()) { + fTriangleType = GrPrimitiveType::kTriangleStrip; + } else { + fTriangleType = GrPrimitiveType::kTriangles; + } +} + +void GrVSCoverageProcessor::appendMesh(sk_sp instanceBuffer, int instanceCount, + int baseInstance, SkTArray* out) const { + GrMesh& mesh = out->emplace_back(fTriangleType); + auto primitiveRestart = GrPrimitiveRestart(GrPrimitiveType::kTriangleStrip == fTriangleType); + mesh.setIndexedInstanced(fIndexBuffer, fNumIndicesPerInstance, std::move(instanceBuffer), + instanceCount, baseInstance, primitiveRestart); + mesh.setVertexData(fVertexBuffer, 0); +} + +GrGLSLPrimitiveProcessor* GrVSCoverageProcessor::onCreateGLSLInstance( + std::unique_ptr shader) const { + switch (fPrimitiveType) { + case PrimitiveType::kTriangles: + case PrimitiveType::kWeightedTriangles: + return new Impl(std::move(shader), 3); + case PrimitiveType::kQuadratics: + case PrimitiveType::kCubics: + case PrimitiveType::kConics: + return new Impl(std::move(shader), 4); + } + SK_ABORT("Invalid PrimitiveType"); +} diff --git a/gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.h b/gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.h new file mode 100644 index 0000000000..6f4ce45c72 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ccpr/GrVSCoverageProcessor.h @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVSCoverageProcessor_DEFINED +#define GrVSCoverageProcessor_DEFINED + +#include "src/gpu/ccpr/GrCCCoverageProcessor.h" + +/** + * This class implements GrCCCoverageProcessor with analytic coverage using vertex shaders. + */ +class GrVSCoverageProcessor : public GrCCCoverageProcessor { +public: + GrVSCoverageProcessor() : GrCCCoverageProcessor(kGrVSCoverageProcessor_ClassID) {} + +private: + void reset(PrimitiveType, GrResourceProvider*) override; + + void appendMesh(sk_sp instanceBuffer, int instanceCount, int baseInstance, + SkTArray* out) const override; + + GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr) const override; + + Attribute fPerVertexData; + Attribute fInputXAndYValues[2]; + sk_sp fVertexBuffer; + sk_sp fIndexBuffer; + int fNumIndicesPerInstance; + GrPrimitiveType fTriangleType; + + class Impl; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.cpp new file mode 100644 index 0000000000..b01cbcddff --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnBuffer.h" + +#include "src/gpu/dawn/GrDawnGpu.h" + +namespace { + dawn::BufferUsage GrGpuBufferTypeToDawnUsageBit(GrGpuBufferType type) { + switch (type) { + case GrGpuBufferType::kVertex: + return dawn::BufferUsage::Vertex; + case GrGpuBufferType::kIndex: + return dawn::BufferUsage::Index; + case GrGpuBufferType::kXferCpuToGpu: + return dawn::BufferUsage::CopySrc; + case GrGpuBufferType::kXferGpuToCpu: + return dawn::BufferUsage::CopyDst; + default: + SkASSERT(!"buffer type not supported by Dawn"); + return dawn::BufferUsage::Vertex; + } + } +} + +GrDawnBuffer::GrDawnBuffer(GrDawnGpu* gpu, size_t sizeInBytes, GrGpuBufferType type, + GrAccessPattern pattern) + : INHERITED(gpu, sizeInBytes, type, pattern) + , fStagingBuffer(nullptr) { + dawn::BufferDescriptor bufferDesc; + bufferDesc.size = sizeInBytes; + bufferDesc.usage = GrGpuBufferTypeToDawnUsageBit(type) | dawn::BufferUsage::CopyDst; + fBuffer = this->getDawnGpu()->device().CreateBuffer(&bufferDesc); + this->registerWithCache(SkBudgeted::kYes); +} + +GrDawnBuffer::~GrDawnBuffer() { +} + +void GrDawnBuffer::onMap() { + if (this->wasDestroyed()) { + return; + } + fStagingBuffer = getDawnGpu()->getStagingBuffer(this->size()); + fMapPtr = fStagingBuffer->fData; +} + +void GrDawnBuffer::onUnmap() { + if (this->wasDestroyed()) { + return; + } + fStagingBuffer->fBuffer.Unmap(); + fMapPtr = nullptr; + getDawnGpu()->getCopyEncoder() + .CopyBufferToBuffer(fStagingBuffer->fBuffer, 0, fBuffer, 0, this->size()); +} + +bool GrDawnBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) { + if (this->wasDestroyed()) { + return false; + } + this->onMap(); + memcpy(fStagingBuffer->fData, src, srcSizeInBytes); + this->onUnmap(); + return true; +} + +GrDawnGpu* GrDawnBuffer::getDawnGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.h b/gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.h new file mode 100644 index 0000000000..17fa1c8d6e --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnBuffer.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnBuffer_DEFINED +#define GrDawnBuffer_DEFINED + +#include "src/gpu/GrGpuBuffer.h" +#include "dawn/dawncpp.h" + +class GrDawnGpu; +struct GrDawnStagingBuffer; + +class GrDawnBuffer : public GrGpuBuffer { +public: + GrDawnBuffer(GrDawnGpu* gpu, size_t sizeInBytes, GrGpuBufferType tpye, GrAccessPattern pattern); + ~GrDawnBuffer() override; + + void onMap() override; + void onUnmap() override; + bool onUpdateData(const void* src, size_t srcSizeInBytes) override; + + GrDawnGpu* getDawnGpu() const; + dawn::Buffer get() const { return fBuffer; } + +private: + dawn::Buffer fBuffer; + GrDawnStagingBuffer* fStagingBuffer; + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnCaps.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnCaps.cpp new file mode 100644 index 0000000000..8caa134c62 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnCaps.cpp @@ -0,0 +1,231 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnCaps.h" + +GrDawnCaps::GrDawnCaps(const GrContextOptions& contextOptions) : INHERITED(contextOptions) { + fMipMapSupport = true; + fBufferMapThreshold = SK_MaxS32; // FIXME: get this from Dawn? + fShaderCaps.reset(new GrShaderCaps(contextOptions)); + fMaxTextureSize = fMaxRenderTargetSize = 4096; // FIXME + fMaxVertexAttributes = 16; // FIXME + fClampToBorderSupport = false; + fPerformPartialClearsAsDraws = true; + + fShaderCaps->fFlatInterpolationSupport = true; + fShaderCaps->fIntegerSupport = true; + // FIXME: each fragment sampler takes two binding slots in Dawn (sampler + texture). Limit to + // 6 * 2 = 12, since kMaxBindingsPerGroup is 16 in Dawn, and we need to keep a few for + // non-texture bindings. Eventually, we may be able to increase kMaxBindingsPerGroup in Dawn. + fShaderCaps->fMaxFragmentSamplers = 6; + fShaderCaps->fShaderDerivativeSupport = true; + + this->applyOptionsOverrides(contextOptions); + fShaderCaps->applyOptionsOverrides(contextOptions); +} + +bool GrDawnCaps::isFormatSRGB(const GrBackendFormat& format) const { + return false; +} + +bool GrDawnCaps::isFormatCompressed(const GrBackendFormat& format, + SkImage::CompressionType* compressionType) const { + return false; +} + +bool GrDawnCaps::isFormatTexturable(const GrBackendFormat& format) const { + // Currently, all the formats in GrDawnFormatToPixelConfig are texturable. + dawn::TextureFormat dawnFormat; + return format.asDawnFormat(&dawnFormat); +} + +GrPixelConfig GrDawnCaps::onGetConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType colorType) const { + dawn::TextureFormat dawnFormat; + if (!format.asDawnFormat(&dawnFormat)) { + return kUnknown_GrPixelConfig; + } + switch (colorType) { + case GrColorType::kUnknown: + return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_8: + if (dawn::TextureFormat::R8Unorm == dawnFormat) { + return kAlpha_8_as_Red_GrPixelConfig; + } + break; + case GrColorType::kRGBA_8888: + if (dawn::TextureFormat::RGBA8Unorm == dawnFormat) { + return kRGBA_8888_GrPixelConfig; + } else if (dawn::TextureFormat::BGRA8Unorm == dawnFormat) { + // FIXME: This shouldn't be necessary, but on some platforms (Mac) + // Skia byte order is RGBA, while preferred swap format is BGRA. + return kBGRA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGB_888x: + break; + case GrColorType::kBGRA_8888: + if (dawn::TextureFormat::BGRA8Unorm == dawnFormat) { + return kBGRA_8888_GrPixelConfig; + } else if (dawn::TextureFormat::RGBA8Unorm == dawnFormat) { + return kRGBA_8888_GrPixelConfig; + } + break; + default: + break; + } + return kUnknown_GrPixelConfig; +} + +static GrSwizzle get_swizzle(const GrBackendFormat& format, GrColorType colorType, + bool forOutput) { + switch (colorType) { + case GrColorType::kAlpha_8: // fall through + case GrColorType::kAlpha_F16: + if (forOutput) { + return GrSwizzle::AAAA(); + } else { + return GrSwizzle::RRRR(); + } + case GrColorType::kGray_8: + if (!forOutput) { + return GrSwizzle::RRRA(); + } + break; + case GrColorType::kRGB_888x: + if (!forOutput) { + return GrSwizzle::RGB1(); + } + default: + return GrSwizzle::RGBA(); + } + return GrSwizzle::RGBA(); +} + +bool GrDawnCaps::isFormatTexturableAndUploadable(GrColorType ct, + const GrBackendFormat& format) const { + dawn::TextureFormat dawnFormat; + if (!format.asDawnFormat(&dawnFormat)) { + return false; + } + switch (ct) { + case GrColorType::kAlpha_8: + return dawn::TextureFormat::R8Unorm == dawnFormat; + case GrColorType::kRGBA_8888: + case GrColorType::kRGB_888x: + case GrColorType::kBGRA_8888: + return dawn::TextureFormat::RGBA8Unorm == dawnFormat || + dawn::TextureFormat::BGRA8Unorm == dawnFormat; + default: + return false; + } +} + +bool GrDawnCaps::isFormatRenderable(const GrBackendFormat& format, + int sampleCount) const { + dawn::TextureFormat dawnFormat; + if (!format.isValid() || sampleCount > 1 || !format.asDawnFormat(&dawnFormat)) { + return false; + } + + return GrDawnFormatIsRenderable(dawnFormat); +} + +bool GrDawnCaps::isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount) const { + return isFormatRenderable(format, sampleCount); +} + +size_t GrDawnCaps::bytesPerPixel(const GrBackendFormat& backendFormat) const { + dawn::TextureFormat dawnFormat; + if (!backendFormat.asDawnFormat(&dawnFormat)) { + return 0; + } + return GrDawnBytesPerPixel(dawnFormat); +} + +int GrDawnCaps::getRenderTargetSampleCount(int requestedCount, + const GrBackendFormat& backendFormat) const { + dawn::TextureFormat dawnFormat; + if (!backendFormat.asDawnFormat(&dawnFormat)) { + return 0; + } + return GrDawnFormatIsRenderable(dawnFormat) ? 1 : 0; +} + +int GrDawnCaps::maxRenderTargetSampleCount(const GrBackendFormat& format) const { + return format.isValid() ? 1 : 0; +} + +GrBackendFormat GrDawnCaps::onGetDefaultBackendFormat(GrColorType ct, + GrRenderable renderable) const { + GrPixelConfig config = GrColorTypeToPixelConfig(ct); + if (config == kUnknown_GrPixelConfig) { + return GrBackendFormat(); + } + dawn::TextureFormat format; + if (!GrPixelConfigToDawnFormat(config, &format)) { + return GrBackendFormat(); + } + return GrBackendFormat::MakeDawn(format); +} + +GrBackendFormat GrDawnCaps::getBackendFormatFromCompressionType(SkImage::CompressionType type) const +{ + return GrBackendFormat(); +} + +GrSwizzle GrDawnCaps::getTextureSwizzle(const GrBackendFormat& format, GrColorType colorType) const +{ + return get_swizzle(format, colorType, false); +} + +GrSwizzle GrDawnCaps::getOutputSwizzle(const GrBackendFormat& format, GrColorType colorType) const +{ + return get_swizzle(format, colorType, true); +} + +bool GrDawnCaps::onAreColorTypeAndFormatCompatible(GrColorType ct, + const GrBackendFormat& format) const { + return true; +} + +GrColorType GrDawnCaps::getYUVAColorTypeFromBackendFormat(const GrBackendFormat& backendFormat, + bool isAlphaChannel) const { + dawn::TextureFormat textureFormat; + if (!backendFormat.asDawnFormat(&textureFormat)) { + return GrColorType::kUnknown; + } + switch (textureFormat) { + case dawn::TextureFormat::R8Unorm: return isAlphaChannel ? GrColorType::kAlpha_8 + : GrColorType::kGray_8; + case dawn::TextureFormat::RGBA8Unorm: return GrColorType::kRGBA_8888; + case dawn::TextureFormat::BGRA8Unorm: return GrColorType::kBGRA_8888; + default: return GrColorType::kUnknown; + } +} + +#if GR_TEST_UTILS +std::vector GrDawnCaps::getTestingCombinations() const { + std::vector combos = { + { GrColorType::kAlpha_8, GrBackendFormat::MakeDawn(dawn::TextureFormat::R8Unorm) }, + { GrColorType::kRGBA_8888, GrBackendFormat::MakeDawn(dawn::TextureFormat::RGBA8Unorm) }, + { GrColorType::kRGBA_8888, GrBackendFormat::MakeDawn(dawn::TextureFormat::BGRA8Unorm) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeDawn(dawn::TextureFormat::RGBA8Unorm) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeDawn(dawn::TextureFormat::BGRA8Unorm) }, + { GrColorType::kBGRA_8888, GrBackendFormat::MakeDawn(dawn::TextureFormat::BGRA8Unorm) }, + { GrColorType::kBGRA_8888, GrBackendFormat::MakeDawn(dawn::TextureFormat::RGBA8Unorm) }, + }; + +#ifdef SK_DEBUG + for (auto combo : combos) { + SkASSERT(this->onAreColorTypeAndFormatCompatible(combo.fColorType, combo.fFormat)); + } +#endif + return combos; +} +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnCaps.h b/gfx/skia/skia/src/gpu/dawn/GrDawnCaps.h new file mode 100644 index 0000000000..6af5d7d0a4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnCaps.h @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnCaps_DEFINED +#define GrDawnCaps_DEFINED + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/dawn/GrDawnUtil.h" + +class GrDawnCaps : public GrCaps { +public: + GrDawnCaps(const GrContextOptions& contextOptions); + + bool isFormatSRGB(const GrBackendFormat&) const override; + bool isFormatCompressed(const GrBackendFormat&, + SkImage::CompressionType* compressionType = nullptr) const override; + + bool isFormatTexturableAndUploadable(GrColorType, const GrBackendFormat& format) const override; + bool isFormatRenderable(const GrBackendFormat& format, + int sampleCount = 1) const override; + bool isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount = 1) const override; + + + bool isFormatCopyable(const GrBackendFormat& format) const override { return true; } + + bool isFormatTexturable(const GrBackendFormat& format) const override; + + SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const override { + return {surfaceColorType, GrColorTypeBytesPerPixel(surfaceColorType)}; + } + + SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const override { + return SurfaceReadPixelsSupport::kSupported; + } + + size_t bytesPerPixel(const GrBackendFormat&) const override; + + int getRenderTargetSampleCount(int requestedCount, + const GrBackendFormat&) const override; + + int maxRenderTargetSampleCount(const GrBackendFormat& format) const override; + + GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override; + + GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override; + + GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override; + + GrColorType getYUVAColorTypeFromBackendFormat(const GrBackendFormat&, + bool isAlphaChannel) const override; + +#if GR_TEST_UTILS + std::vector getTestingCombinations() const override; +#endif + +private: + bool onSurfaceSupportsWritePixels(const GrSurface* surface) const override { + return true; + } + bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const override { + return true; + } + GrBackendFormat onGetDefaultBackendFormat(GrColorType, GrRenderable) const override; + + GrPixelConfig onGetConfigFromBackendFormat(const GrBackendFormat&, GrColorType) const override; + + bool onAreColorTypeAndFormatCompatible(GrColorType, const GrBackendFormat&) const override; + + SupportedRead onSupportedReadPixelsColorType(GrColorType srcColorType, + const GrBackendFormat& backendFormat, + GrColorType dstColorType) const override { + return { srcColorType, GrColorTypeBytesPerPixel(srcColorType) }; + } + + typedef GrCaps INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnGpu.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnGpu.cpp new file mode 100644 index 0000000000..54c7c569f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnGpu.cpp @@ -0,0 +1,699 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnGpu.h" + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrGpuResourceCacheAccess.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/dawn/GrDawnBuffer.h" +#include "src/gpu/dawn/GrDawnCaps.h" +#include "src/gpu/dawn/GrDawnOpsRenderPass.h" +#include "src/gpu/dawn/GrDawnProgramBuilder.h" +#include "src/gpu/dawn/GrDawnRenderTarget.h" +#include "src/gpu/dawn/GrDawnStencilAttachment.h" +#include "src/gpu/dawn/GrDawnTexture.h" +#include "src/gpu/dawn/GrDawnUtil.h" + +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkMipMap.h" +#include "src/sksl/SkSLCompiler.h" + +#if !defined(SK_BUILD_FOR_WIN) +#include +#endif // !defined(SK_BUILD_FOR_WIN) + +const int kMaxRenderPipelineEntries = 1024; + +static dawn::FilterMode to_dawn_filter_mode(GrSamplerState::Filter filter) { + switch (filter) { + case GrSamplerState::Filter::kNearest: + return dawn::FilterMode::Nearest; + case GrSamplerState::Filter::kBilerp: + case GrSamplerState::Filter::kMipMap: + return dawn::FilterMode::Linear; + default: + SkASSERT(!"unsupported filter mode"); + return dawn::FilterMode::Nearest; + } +} + +static dawn::AddressMode to_dawn_address_mode(GrSamplerState::WrapMode wrapMode) { + switch (wrapMode) { + case GrSamplerState::WrapMode::kClamp: + return dawn::AddressMode::ClampToEdge; + case GrSamplerState::WrapMode::kRepeat: + return dawn::AddressMode::Repeat; + case GrSamplerState::WrapMode::kMirrorRepeat: + return dawn::AddressMode::MirrorRepeat; + case GrSamplerState::WrapMode::kClampToBorder: + SkASSERT(!"unsupported address mode"); + } + SkASSERT(!"unsupported address mode"); + return dawn::AddressMode::ClampToEdge; + +} + +// FIXME: taken from GrVkPipelineState; refactor. +static uint32_t get_blend_info_key(const GrPipeline& pipeline) { + GrXferProcessor::BlendInfo blendInfo = pipeline.getXferProcessor().getBlendInfo(); + + static const uint32_t kBlendWriteShift = 1; + static const uint32_t kBlendCoeffShift = 5; + GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift)); + GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4); + + uint32_t key = blendInfo.fWriteColor; + key |= (blendInfo.fSrcBlend << kBlendWriteShift); + key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift)); + key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift)); + + return key; +} + +class Desc : public GrProgramDesc { +public: + static bool Build(Desc* desc, + GrRenderTarget* rt, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + bool hasDepthStencil, + GrGpu* gpu) { + if (!GrProgramDesc::Build(desc, rt, programInfo, primitiveType, gpu)) { + return false; + } + GrProcessorKeyBuilder b(&desc->key()); + + GrStencilSettings stencil; + const GrPipeline& pipeline = programInfo.pipeline(); + stencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(), 8); + stencil.genKey(&b); + b.add32(rt->config()); + b.add32(static_cast(hasDepthStencil)); + b.add32(get_blend_info_key(pipeline)); + b.add32(static_cast(primitiveType)); + return true; + } +}; + +sk_sp GrDawnGpu::Make(const dawn::Device& device, + const GrContextOptions& options, GrContext* context) { + if (!device) { + return nullptr; + } + + return sk_sp(new GrDawnGpu(context, options, device)); +} + +//////////////////////////////////////////////////////////////////////////////// + +GrDawnGpu::GrDawnGpu(GrContext* context, const GrContextOptions& options, + const dawn::Device& device) + : INHERITED(context) + , fDevice(device) + , fQueue(device.CreateQueue()) + , fCompiler(new SkSL::Compiler()) + , fUniformRingBuffer(this, dawn::BufferUsage::Uniform) + , fRenderPipelineCache(kMaxRenderPipelineEntries) + , fStagingManager(fDevice) { + fCaps.reset(new GrDawnCaps(options)); +} + +GrDawnGpu::~GrDawnGpu() { +} + + +void GrDawnGpu::disconnect(DisconnectType type) { + SkASSERT(!"unimplemented"); +} + +/////////////////////////////////////////////////////////////////////////////// + +GrOpsRenderPass* GrDawnGpu::getOpsRenderPass( + GrRenderTarget* rt, GrSurfaceOrigin origin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, + const SkTArray& sampledProxies) { + fOpsRenderPass.reset(new GrDawnOpsRenderPass(this, rt, origin, colorInfo, stencilInfo)); + return fOpsRenderPass.get(); +} + +/////////////////////////////////////////////////////////////////////////////// +sk_sp GrDawnGpu::onCreateBuffer(size_t size, GrGpuBufferType type, + GrAccessPattern accessPattern, const void* data) { + sk_sp b(new GrDawnBuffer(this, size, type, accessPattern)); + if (data && b) { + b->updateData(data, size); + } + return b; +} + +//////////////////////////////////////////////////////////////////////////////// +bool GrDawnGpu::onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) { + GrDawnTexture* texture = static_cast(surface->asTexture()); + if (!texture) { + return false; + } + texture->upload(texels, mipLevelCount, SkIRect::MakeXYWH(left, top, width, height), + this->getCopyEncoder()); + return true; +} + +bool GrDawnGpu::onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t bufferOffset, + size_t rowBytes) { + SkASSERT(!"unimplemented"); + return false; +} + +bool GrDawnGpu::onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) { + SkASSERT(!"unimplemented"); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +sk_sp GrDawnGpu::onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& backendFormat, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected, + int mipLevelCount, + uint32_t levelClearMask) { + SkASSERT(!levelClearMask); + dawn::TextureFormat format; + if (!backendFormat.asDawnFormat(&format)) { + return nullptr; + } + + GrMipMapsStatus mipMapsStatus = + mipLevelCount > 1 ? GrMipMapsStatus::kDirty : GrMipMapsStatus::kNotAllocated; + + return GrDawnTexture::Make(this, { desc.fWidth, desc.fHeight }, + desc.fConfig, format, renderable, + renderTargetSampleCnt, budgeted, mipLevelCount, + mipMapsStatus); +} + +sk_sp GrDawnGpu::onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, + const void* data) { + SkASSERT(!"unimplemented"); + return nullptr; +} + +sk_sp GrDawnGpu::onWrapBackendTexture(const GrBackendTexture& backendTex, + GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable, + GrIOType) { + GrDawnImageInfo info; + if (!backendTex.getDawnImageInfo(&info)) { + return nullptr; + } + + SkISize size = { backendTex.width(), backendTex.height() }; + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendTex.getBackendFormat(), + colorType); + GrMipMapsStatus status = GrMipMapsStatus::kNotAllocated; + return GrDawnTexture::MakeWrapped(this, size, config, GrRenderable::kNo, 1, status, cacheable, + info); +} + +sk_sp GrDawnGpu::onWrapRenderableBackendTexture(const GrBackendTexture& tex, + int sampleCnt, GrColorType colorType, + GrWrapOwnership, + GrWrapCacheable cacheable) { + GrDawnImageInfo info; + if (!tex.getDawnImageInfo(&info) || !info.fTexture) { + return nullptr; + } + + SkISize size = { tex.width(), tex.height() }; + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(tex.getBackendFormat(), + colorType); + sampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, tex.getBackendFormat()); + if (sampleCnt < 1) { + return nullptr; + } + + GrMipMapsStatus status = GrMipMapsStatus::kNotAllocated; + return GrDawnTexture::MakeWrapped(this, size, config, GrRenderable::kYes, sampleCnt, status, + cacheable, info); +} + +sk_sp GrDawnGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& rt, + GrColorType colorType) { + GrDawnImageInfo info; + if (!rt.getDawnImageInfo(&info) && !info.fTexture) { + return nullptr; + } + + SkISize size = { rt.width(), rt.height() }; + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(rt.getBackendFormat(), + colorType); + int sampleCnt = 1; + return GrDawnRenderTarget::MakeWrapped(this, size, config, sampleCnt, info); +} + +sk_sp GrDawnGpu::onWrapBackendTextureAsRenderTarget(const GrBackendTexture& tex, + int sampleCnt, + GrColorType colorType) { + GrDawnImageInfo info; + if (!tex.getDawnImageInfo(&info) || !info.fTexture) { + return nullptr; + } + + SkISize size = { tex.width(), tex.height() }; + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(tex.getBackendFormat(), + colorType); + sampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, tex.getBackendFormat()); + if (sampleCnt < 1) { + return nullptr; + } + + return GrDawnRenderTarget::MakeWrapped(this, size, config, sampleCnt, info); +} + +GrStencilAttachment* GrDawnGpu::createStencilAttachmentForRenderTarget(const GrRenderTarget* rt, + int width, + int height, + int numStencilSamples) { + GrDawnStencilAttachment* stencil(GrDawnStencilAttachment::Create(this, + width, + height, + numStencilSamples)); + fStats.incStencilAttachmentCreates(); + return stencil; +} + +GrBackendTexture GrDawnGpu::onCreateBackendTexture(int width, int height, + const GrBackendFormat& backendFormat, + GrMipMapped mipMapped, + GrRenderable renderable, + const SkPixmap srcData[], + int numMipLevels, + const SkColor4f* color, + GrProtected isProtected) { + dawn::TextureFormat format; + if (!backendFormat.asDawnFormat(&format)) { + return GrBackendTexture(); + } + + SkASSERT(width <= this->caps()->maxTextureSize() && height <= this->caps()->maxTextureSize()); + + // FIXME: Dawn doesn't support mipmapped render targets (yet). + if (GrMipMapped::kYes == mipMapped && GrRenderable::kYes == renderable) { + return GrBackendTexture(); + } + + dawn::TextureDescriptor desc; + desc.usage = + dawn::TextureUsage::Sampled | + dawn::TextureUsage::CopySrc | + dawn::TextureUsage::CopyDst; + + if (GrRenderable::kYes == renderable) { + desc.usage |= dawn::TextureUsage::OutputAttachment; + } + + desc.size.width = width; + desc.size.height = height; + desc.size.depth = 1; + desc.format = format; + + // Figure out the number of mip levels. + if (srcData) { + desc.mipLevelCount = numMipLevels; + } else if (GrMipMapped::kYes == mipMapped) { + desc.mipLevelCount = SkMipMap::ComputeLevelCount(width, height) + 1; + } + + dawn::Texture tex = this->device().CreateTexture(&desc); + + size_t bpp = GrDawnBytesPerPixel(format); + size_t baseLayerSize = bpp * width * height; + const void* pixels; + SkAutoMalloc defaultStorage(baseLayerSize); + if (srcData) { + pixels = srcData->addr(); + } else { + pixels = defaultStorage.get(); + memset(defaultStorage.get(), 0, baseLayerSize); + } + dawn::Device device = this->device(); + dawn::CommandEncoder copyEncoder = fDevice.CreateCommandEncoder(); + int w = width, h = height; + for (uint32_t i = 0; i < desc.mipLevelCount; i++) { + size_t origRowBytes = bpp * w; + size_t rowBytes = GrDawnRoundRowBytes(origRowBytes); + size_t size = rowBytes * h; + GrDawnStagingBuffer* stagingBuffer = this->getStagingBuffer(size); + if (rowBytes == origRowBytes) { + memcpy(stagingBuffer->fData, pixels, size); + } else { + const char* src = static_cast(pixels); + char* dst = static_cast(stagingBuffer->fData); + for (int row = 0; row < h; row++) { + memcpy(dst, src, origRowBytes); + dst += rowBytes; + src += origRowBytes; + } + } + dawn::Buffer buffer = stagingBuffer->fBuffer; + buffer.Unmap(); + stagingBuffer->fData = nullptr; + dawn::BufferCopyView srcBuffer; + srcBuffer.buffer = buffer; + srcBuffer.offset = 0; + srcBuffer.rowPitch = rowBytes; + srcBuffer.imageHeight = h; + dawn::TextureCopyView dstTexture; + dstTexture.texture = tex; + dstTexture.mipLevel = i; + dstTexture.origin = {0, 0, 0}; + dawn::Extent3D copySize = {(uint32_t) w, (uint32_t) h, 1}; + copyEncoder.CopyBufferToTexture(&srcBuffer, &dstTexture, ©Size); + w = SkTMax(1, w / 2); + h = SkTMax(1, h / 2); + } + dawn::CommandBuffer cmdBuf = copyEncoder.Finish(); + fQueue.Submit(1, &cmdBuf); + GrDawnImageInfo info; + info.fTexture = tex; + info.fFormat = desc.format; + info.fLevelCount = desc.mipLevelCount; + return GrBackendTexture(width, height, info); +} + +void GrDawnGpu::deleteBackendTexture(const GrBackendTexture& tex) { + GrDawnImageInfo info; + if (tex.getDawnImageInfo(&info)) { + info.fTexture = nullptr; + } +} + +#if GR_TEST_UTILS +bool GrDawnGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const { + GrDawnImageInfo info; + if (!tex.getDawnImageInfo(&info)) { + return false; + } + + return info.fTexture.Get(); +} + +GrBackendRenderTarget GrDawnGpu::createTestingOnlyBackendRenderTarget(int width, int height, + GrColorType colorType) { + GrPixelConfig config = GrColorTypeToPixelConfig(colorType); + + if (width > this->caps()->maxTextureSize() || height > this->caps()->maxTextureSize()) { + return GrBackendRenderTarget(); + } + + dawn::TextureFormat format; + if (!GrPixelConfigToDawnFormat(config, &format)) { + return GrBackendRenderTarget(); + } + + dawn::TextureDescriptor desc; + desc.usage = + dawn::TextureUsage::CopySrc | + dawn::TextureUsage::OutputAttachment; + + desc.size.width = width; + desc.size.height = height; + desc.size.depth = 1; + desc.format = format; + + dawn::Texture tex = this->device().CreateTexture(&desc); + + GrDawnImageInfo info; + info.fTexture = tex; + info.fFormat = desc.format; + info.fLevelCount = desc.mipLevelCount; + return GrBackendRenderTarget(width, height, 1, 0, info); +} + +void GrDawnGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget& rt) { + GrDawnImageInfo info; + if (rt.getDawnImageInfo(&info)) { + info.fTexture = nullptr; + } +} + +void GrDawnGpu::testingOnly_flushGpuAndSync() { + this->flush(); +} + +#endif + +void GrDawnGpu::flush() { + this->flushCopyEncoder(); + if (!fCommandBuffers.empty()) { + fQueue.Submit(fCommandBuffers.size(), &fCommandBuffers.front()); + fCommandBuffers.clear(); + } + fStagingManager.mapBusyList(); + fDevice.Tick(); +} + +void GrDawnGpu::onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info, const GrPrepareForExternalIORequests&) { + this->flush(); +} + +static dawn::Texture get_dawn_texture_from_surface(GrSurface* src) { + if (auto rt = static_cast(src->asRenderTarget())) { + return rt->texture(); + } else if (auto t = static_cast(src->asTexture())) { + return t->texture(); + } else { + return nullptr; + } +} + +bool GrDawnGpu::onCopySurface(GrSurface* dst, + GrSurface* src, + const SkIRect& srcRect, + const SkIPoint& dstPoint) { + dawn::Texture srcTexture = get_dawn_texture_from_surface(src); + dawn::Texture dstTexture = get_dawn_texture_from_surface(dst); + if (!srcTexture || !dstTexture) { + return false; + } + + uint32_t width = srcRect.width(), height = srcRect.height(); + + dawn::TextureCopyView srcTextureView, dstTextureView; + srcTextureView.texture = srcTexture; + srcTextureView.origin = {(uint32_t) srcRect.x(), (uint32_t) srcRect.y(), 0}; + dstTextureView.texture = dstTexture; + dstTextureView.origin = {(uint32_t) dstPoint.x(), (uint32_t) dstPoint.y(), 0}; + + dawn::Extent3D copySize = {width, height, 1}; + this->getCopyEncoder().CopyTextureToTexture(&srcTextureView, &dstTextureView, ©Size); + return true; +} + +static void callback(DawnBufferMapAsyncStatus status, const void* data, uint64_t dataLength, + void* userdata) { + (*reinterpret_cast(userdata)) = data; +} + +bool GrDawnGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) { + dawn::Texture tex = get_dawn_texture_from_surface(surface); + + if (0 == rowBytes) { + return false; + } + size_t origRowBytes = rowBytes; + int origSizeInBytes = origRowBytes * height; + rowBytes = GrDawnRoundRowBytes(rowBytes); + int sizeInBytes = rowBytes * height; + + dawn::BufferDescriptor desc; + desc.usage = dawn::BufferUsage::CopyDst | dawn::BufferUsage::MapRead; + desc.size = sizeInBytes; + + dawn::Buffer buf = device().CreateBuffer(&desc); + + dawn::TextureCopyView srcTexture; + srcTexture.texture = tex; + srcTexture.origin = {(uint32_t) left, (uint32_t) top, 0}; + + dawn::BufferCopyView dstBuffer; + dstBuffer.buffer = buf; + dstBuffer.offset = 0; + dstBuffer.rowPitch = rowBytes; + dstBuffer.imageHeight = height; + + dawn::Extent3D copySize = {(uint32_t) width, (uint32_t) height, 1}; + this->getCopyEncoder().CopyTextureToBuffer(&srcTexture, &dstBuffer, ©Size); + flush(); + + const void *readPixelsPtr = nullptr; + buf.MapReadAsync(callback, &readPixelsPtr); + while (!readPixelsPtr) { + device().Tick(); + } + + if (rowBytes == origRowBytes) { + memcpy(buffer, readPixelsPtr, origSizeInBytes); + } else { + const char* src = static_cast(readPixelsPtr); + char* dst = static_cast(buffer); + for (int row = 0; row < height; row++) { + memcpy(dst, src, origRowBytes); + dst += origRowBytes; + src += rowBytes; + } + } + buf.Unmap(); + return true; +} + +bool GrDawnGpu::onRegenerateMipMapLevels(GrTexture*) { + SkASSERT(!"unimplemented"); + return false; +} + +void GrDawnGpu::submit(GrOpsRenderPass* renderPass) { + this->flushCopyEncoder(); + static_cast(renderPass)->submit(); +} + +GrFence SK_WARN_UNUSED_RESULT GrDawnGpu::insertFence() { + SkASSERT(!"unimplemented"); + return GrFence(); +} + +bool GrDawnGpu::waitFence(GrFence fence, uint64_t timeout) { + SkASSERT(!"unimplemented"); + return false; +} + +void GrDawnGpu::deleteFence(GrFence fence) const { + SkASSERT(!"unimplemented"); +} + +sk_sp SK_WARN_UNUSED_RESULT GrDawnGpu::makeSemaphore(bool isOwned) { + SkASSERT(!"unimplemented"); + return nullptr; +} + +sk_sp GrDawnGpu::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) { + SkASSERT(!"unimplemented"); + return nullptr; +} + +void GrDawnGpu::insertSemaphore(sk_sp semaphore) { + SkASSERT(!"unimplemented"); +} + +void GrDawnGpu::waitSemaphore(sk_sp semaphore) { + SkASSERT(!"unimplemented"); +} + +void GrDawnGpu::checkFinishProcs() { + SkASSERT(!"unimplemented"); +} + +sk_sp GrDawnGpu::prepareTextureForCrossContextUsage(GrTexture* texture) { + SkASSERT(!"unimplemented"); + return nullptr; +} + +sk_sp GrDawnGpu::getOrCreateRenderPipeline( + GrRenderTarget* rt, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType) { + bool hasDepthStencil = rt->renderTargetPriv().getStencilAttachment() != nullptr; + Desc desc; + if (!Desc::Build(&desc, rt, programInfo, primitiveType, hasDepthStencil, this)) { + return nullptr; + } + + if (sk_sp* program = fRenderPipelineCache.find(desc)) { + return *program; + } + + dawn::TextureFormat colorFormat; + SkAssertResult(GrPixelConfigToDawnFormat(rt->config(), &colorFormat)); + dawn::TextureFormat stencilFormat = dawn::TextureFormat::Depth24PlusStencil8; + + sk_sp program = GrDawnProgramBuilder::Build( + this, rt, programInfo, primitiveType, colorFormat, + hasDepthStencil, stencilFormat, &desc); + fRenderPipelineCache.insert(desc, program); + return program; +} + +dawn::Sampler GrDawnGpu::getOrCreateSampler(const GrSamplerState& samplerState) { + auto i = fSamplers.find(samplerState); + if (i != fSamplers.end()) { + return i->second; + } + dawn::SamplerDescriptor desc; + desc.addressModeU = to_dawn_address_mode(samplerState.wrapModeX()); + desc.addressModeV = to_dawn_address_mode(samplerState.wrapModeY()); + desc.addressModeW = dawn::AddressMode::ClampToEdge; + desc.magFilter = desc.minFilter = to_dawn_filter_mode(samplerState.filter()); + desc.mipmapFilter = dawn::FilterMode::Linear; + desc.lodMinClamp = 0.0f; + desc.lodMaxClamp = 1000.0f; + desc.compare = dawn::CompareFunction::Never; + dawn::Sampler sampler = device().CreateSampler(&desc); + fSamplers.insert(std::pair(samplerState, sampler)); + return sampler; +} + +GrDawnRingBuffer::Slice GrDawnGpu::allocateUniformRingBufferSlice(int size) { + return fUniformRingBuffer.allocate(size); +} + +GrDawnStagingBuffer* GrDawnGpu::getStagingBuffer(size_t size) { + return fStagingManager.findOrCreateStagingBuffer(size); +} + +void GrDawnGpu::appendCommandBuffer(dawn::CommandBuffer commandBuffer) { + if (commandBuffer) { + fCommandBuffers.push_back(commandBuffer); + } +} + +dawn::CommandEncoder GrDawnGpu::getCopyEncoder() { + if (!fCopyEncoder) { + fCopyEncoder = fDevice.CreateCommandEncoder(); + } + return fCopyEncoder; +} + +void GrDawnGpu::flushCopyEncoder() { + if (fCopyEncoder) { + fCommandBuffers.push_back(fCopyEncoder.Finish()); + fCopyEncoder = nullptr; + } +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnGpu.h b/gfx/skia/skia/src/gpu/dawn/GrDawnGpu.h new file mode 100644 index 0000000000..96cb54b8b9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnGpu.h @@ -0,0 +1,191 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnGpu_DEFINED +#define GrDawnGpu_DEFINED + +#include "src/gpu/GrGpu.h" +#include "dawn/dawncpp.h" +#include "src/core/SkLRUCache.h" +#include "src/gpu/dawn/GrDawnRingBuffer.h" +#include "src/gpu/dawn/GrDawnStagingManager.h" + +#include + +class GrDawnOpsRenderPass; +class GrPipeline; +struct GrDawnProgram; + +namespace SkSL { + class Compiler; +} + +class GrDawnGpu : public GrGpu { +public: + static sk_sp Make(const dawn::Device& device, const GrContextOptions&, GrContext*); + GrDawnGpu(GrContext* context, const GrContextOptions& options, const dawn::Device& device); + + ~GrDawnGpu() override; + + void disconnect(DisconnectType) override; + + const dawn::Device& device() const { return fDevice; } + const dawn::Queue& queue() const { return fQueue; } + + void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {} + + GrBackendTexture onCreateBackendTexture(int w, int h, + const GrBackendFormat&, + GrMipMapped, + GrRenderable, + const SkPixmap srcData[], + int numMipLevels, + const SkColor4f* color, + GrProtected isProtected) override; + void deleteBackendTexture(const GrBackendTexture&) override; +#if GR_TEST_UTILS + bool isTestingOnlyBackendTexture(const GrBackendTexture&) const override; + + GrBackendRenderTarget createTestingOnlyBackendRenderTarget(int w, int h, GrColorType) override; + void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) override; + + void testingOnly_flushGpuAndSync() override; +#endif + void flush(); + + GrStencilAttachment* createStencilAttachmentForRenderTarget(const GrRenderTarget*, + int width, + int height, + int numStencilSamples) override; + + GrOpsRenderPass* getOpsRenderPass( + GrRenderTarget*, GrSurfaceOrigin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) override; + + SkSL::Compiler* shaderCompiler() const { + return fCompiler.get(); + } + + void submit(GrOpsRenderPass*) override; + + GrFence SK_WARN_UNUSED_RESULT insertFence() override; + bool waitFence(GrFence, uint64_t timeout) override; + void deleteFence(GrFence) const override; + + sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned = true) override; + sk_sp wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) override; + void insertSemaphore(sk_sp semaphore) override; + void waitSemaphore(sk_sp semaphore) override; + void checkFinishProcs() override; + + sk_sp prepareTextureForCrossContextUsage(GrTexture*) override; + + sk_sp getOrCreateRenderPipeline(GrRenderTarget*, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType); + + dawn::Sampler getOrCreateSampler(const GrSamplerState& samplerState); + + GrDawnRingBuffer::Slice allocateUniformRingBufferSlice(int size); + GrDawnStagingBuffer* getStagingBuffer(size_t size); + GrDawnStagingManager* getStagingManager() { return &fStagingManager; } + dawn::CommandEncoder getCopyEncoder(); + void flushCopyEncoder(); + void appendCommandBuffer(dawn::CommandBuffer commandBuffer); + +private: + void onResetContext(uint32_t resetBits) override {} + + virtual void querySampleLocations(GrRenderTarget*, SkTArray*) override {} + + sk_sp onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted, + GrProtected, + int mipLevelCount, + uint32_t levelClearMask) override; + + sk_sp onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, + const void* data) override; + + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrColorType, GrWrapOwnership, + GrWrapCacheable, GrIOType) override; + sk_sp onWrapRenderableBackendTexture(const GrBackendTexture&, int sampleCnt, + GrColorType, GrWrapOwnership, + GrWrapCacheable) override; + sk_sp onWrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType) override; + + sk_sp onWrapBackendTextureAsRenderTarget(const GrBackendTexture&, int sampleCnt, + GrColorType) override; + + sk_sp onCreateBuffer(size_t size, GrGpuBufferType type, GrAccessPattern, + const void* data) override; + + bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) override; + + bool onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) override; + + bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) override; + + bool onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) override; + + void onResolveRenderTarget(GrRenderTarget*, const SkIRect&, GrSurfaceOrigin, + ForExternalIO) override {} + + bool onRegenerateMipMapLevels(GrTexture*) override; + + bool onCopySurface(GrSurface* dst, GrSurface* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) override; + + void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info, const GrPrepareForExternalIORequests&) override; + + dawn::Device fDevice; + dawn::Queue fQueue; + std::unique_ptr fCompiler; + std::unique_ptr fOpsRenderPass; + GrDawnRingBuffer fUniformRingBuffer; + dawn::CommandEncoder fCopyEncoder; + std::vector fCommandBuffers; + + struct ProgramDescHash { + uint32_t operator()(const GrProgramDesc& desc) const { + return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0); + } + }; + + struct SamplerHash { + size_t operator()(const GrSamplerState& samplerState) const { + return SkOpts::hash_fn(&samplerState, sizeof(samplerState), 0); + } + }; + + SkLRUCache, ProgramDescHash> fRenderPipelineCache; + std::unordered_map fSamplers; + GrDawnStagingManager fStagingManager; + + typedef GrGpu INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.cpp new file mode 100644 index 0000000000..48fc8a7f48 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnOpsRenderPass.h" + +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/dawn/GrDawnBuffer.h" +#include "src/gpu/dawn/GrDawnGpu.h" +#include "src/gpu/dawn/GrDawnProgramBuilder.h" +#include "src/gpu/dawn/GrDawnRenderTarget.h" +#include "src/gpu/dawn/GrDawnStencilAttachment.h" +#include "src/gpu/dawn/GrDawnTexture.h" +#include "src/gpu/dawn/GrDawnUtil.h" +#include "src/sksl/SkSLCompiler.h" + +//////////////////////////////////////////////////////////////////////////////// + +static dawn::LoadOp to_dawn_load_op(GrLoadOp loadOp) { + switch (loadOp) { + case GrLoadOp::kLoad: + return dawn::LoadOp::Load; + case GrLoadOp::kDiscard: + // Use LoadOp::Load to emulate DontCare. + // Dawn doesn't have DontCare, for security reasons. + // Load should be equivalent to DontCare for desktop; Clear would + // probably be better for tilers. If Dawn does add DontCare + // as an extension, use it here. + return dawn::LoadOp::Load; + case GrLoadOp::kClear: + return dawn::LoadOp::Clear; + default: + SK_ABORT("Invalid LoadOp"); + } +} + +GrDawnOpsRenderPass::GrDawnOpsRenderPass(GrDawnGpu* gpu, GrRenderTarget* rt, GrSurfaceOrigin origin, + const LoadAndStoreInfo& colorInfo, + const StencilLoadAndStoreInfo& stencilInfo) + : INHERITED(rt, origin) + , fGpu(gpu) + , fColorInfo(colorInfo) { + fEncoder = fGpu->device().CreateCommandEncoder(); + dawn::LoadOp colorOp = to_dawn_load_op(colorInfo.fLoadOp); + dawn::LoadOp stencilOp = to_dawn_load_op(stencilInfo.fLoadOp); + fPassEncoder = beginRenderPass(colorOp, stencilOp); +} + +dawn::RenderPassEncoder GrDawnOpsRenderPass::beginRenderPass(dawn::LoadOp colorOp, + dawn::LoadOp stencilOp) { + dawn::Texture texture = static_cast(fRenderTarget)->texture(); + auto stencilAttachment = static_cast( + fRenderTarget->renderTargetPriv().getStencilAttachment()); + dawn::TextureView colorView = texture.CreateView(); + const float *c = fColorInfo.fClearColor.vec(); + + dawn::RenderPassColorAttachmentDescriptor colorAttachment; + colorAttachment.attachment = colorView; + colorAttachment.resolveTarget = nullptr; + colorAttachment.clearColor = { c[0], c[1], c[2], c[3] }; + colorAttachment.loadOp = colorOp; + colorAttachment.storeOp = dawn::StoreOp::Store; + dawn::RenderPassColorAttachmentDescriptor* colorAttachments = { &colorAttachment }; + dawn::RenderPassDescriptor renderPassDescriptor; + renderPassDescriptor.colorAttachmentCount = 1; + renderPassDescriptor.colorAttachments = colorAttachments; + if (stencilAttachment) { + dawn::RenderPassDepthStencilAttachmentDescriptor depthStencilAttachment; + depthStencilAttachment.attachment = stencilAttachment->view(); + depthStencilAttachment.depthLoadOp = stencilOp; + depthStencilAttachment.stencilLoadOp = stencilOp; + depthStencilAttachment.clearDepth = 1.0f; + depthStencilAttachment.clearStencil = 0; + depthStencilAttachment.depthStoreOp = dawn::StoreOp::Store; + depthStencilAttachment.stencilStoreOp = dawn::StoreOp::Store; + renderPassDescriptor.depthStencilAttachment = &depthStencilAttachment; + } else { + renderPassDescriptor.depthStencilAttachment = nullptr; + } + return fEncoder.BeginRenderPass(&renderPassDescriptor); +} + +GrDawnOpsRenderPass::~GrDawnOpsRenderPass() { +} + +GrGpu* GrDawnOpsRenderPass::gpu() { return fGpu; } + +void GrDawnOpsRenderPass::end() { + fPassEncoder.EndPass(); +} + +void GrDawnOpsRenderPass::submit() { + fGpu->appendCommandBuffer(fEncoder.Finish()); +} + +void GrDawnOpsRenderPass::insertEventMarker(const char* msg) { + SkASSERT(!"unimplemented"); +} + +void GrDawnOpsRenderPass::onClearStencilClip(const GrFixedClip& clip, bool insideStencilMask) { + fPassEncoder.EndPass(); + fPassEncoder = beginRenderPass(dawn::LoadOp::Load, dawn::LoadOp::Clear); +} + +void GrDawnOpsRenderPass::onClear(const GrFixedClip& clip, const SkPMColor4f& color) { + fPassEncoder.EndPass(); + fPassEncoder = beginRenderPass(dawn::LoadOp::Clear, dawn::LoadOp::Load); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrDawnOpsRenderPass::inlineUpload(GrOpFlushState* state, + GrDeferredTextureUploadFn& upload) { + SkASSERT(!"unimplemented"); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrDawnOpsRenderPass::setScissorState(const GrProgramInfo& programInfo) { + SkIRect rect; + if (programInfo.pipeline().isScissorEnabled()) { + constexpr SkIRect kBogusScissor{0, 0, 1, 1}; + rect = programInfo.hasFixedScissor() ? programInfo.fixedScissor() : kBogusScissor; + if (kBottomLeft_GrSurfaceOrigin == fOrigin) { + rect.setXYWH(rect.x(), fRenderTarget->height() - rect.bottom(), + rect.width(), rect.height()); + } + } else { + rect = SkIRect::MakeWH(fRenderTarget->width(), fRenderTarget->height()); + } + fPassEncoder.SetScissorRect(rect.x(), rect.y(), rect.width(), rect.height()); +} + +void GrDawnOpsRenderPass::applyState(const GrProgramInfo& programInfo, + const GrPrimitiveType primitiveType) { + sk_sp program = fGpu->getOrCreateRenderPipeline(fRenderTarget, + programInfo, + primitiveType); + auto bindGroup = program->setData(fGpu, fRenderTarget, programInfo); + fPassEncoder.SetPipeline(program->fRenderPipeline); + fPassEncoder.SetBindGroup(0, bindGroup, 0, nullptr); + const GrPipeline& pipeline = programInfo.pipeline(); + if (pipeline.isStencilEnabled()) { + fPassEncoder.SetStencilReference(pipeline.getUserStencil()->fFront.fRef); + } + GrXferProcessor::BlendInfo blendInfo = pipeline.getXferProcessor().getBlendInfo(); + const float* c = blendInfo.fBlendConstant.vec(); + dawn::Color color{c[0], c[1], c[2], c[3]}; + fPassEncoder.SetBlendColor(&color); + this->setScissorState(programInfo); +} + +void GrDawnOpsRenderPass::onDraw(const GrProgramInfo& programInfo, + const GrMesh meshes[], + int meshCount, + const SkRect& bounds) { + if (!meshCount) { + return; + } + for (int i = 0; i < meshCount; ++i) { + applyState(programInfo, meshes[0].primitiveType()); + meshes[i].sendToGpu(this); + } +} + +void GrDawnOpsRenderPass::sendInstancedMeshToGpu(GrPrimitiveType, + const GrBuffer* vertexBuffer, + int vertexCount, + int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, + int baseInstance) { + dawn::Buffer vb = static_cast(vertexBuffer)->get(); + fPassEncoder.SetVertexBuffer(0, vb); + fPassEncoder.Draw(vertexCount, 1, baseVertex, baseInstance); + fGpu->stats()->incNumDraws(); +} + +void GrDawnOpsRenderPass::sendIndexedInstancedMeshToGpu(GrPrimitiveType, + const GrBuffer* indexBuffer, + int indexCount, + int baseIndex, + const GrBuffer* vertexBuffer, + int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, + int baseInstance, + GrPrimitiveRestart restart) { + dawn::Buffer vb = static_cast(vertexBuffer)->get(); + dawn::Buffer ib = static_cast(indexBuffer)->get(); + fPassEncoder.SetIndexBuffer(ib); + fPassEncoder.SetVertexBuffer(0, vb); + fPassEncoder.DrawIndexed(indexCount, 1, baseIndex, baseVertex, baseInstance); + fGpu->stats()->incNumDraws(); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.h b/gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.h new file mode 100644 index 0000000000..a1a9036236 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnOpsRenderPass.h @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnOpsRenderPass_DEFINED +#define GrDawnOpsRenderPass_DEFINED + +#include "src/gpu/GrOpsRenderPass.h" + +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrMesh.h" +#include "dawn/dawncpp.h" + +class GrDawnGpu; +class GrDawnRenderTarget; + +class GrDawnOpsRenderPass : public GrOpsRenderPass, private GrMesh::SendToGpuImpl { +public: + GrDawnOpsRenderPass(GrDawnGpu*, GrRenderTarget*, GrSurfaceOrigin, + const LoadAndStoreInfo&, const StencilLoadAndStoreInfo&); + + ~GrDawnOpsRenderPass() override; + + void begin() override { } + void end() override; + + dawn::RenderPassEncoder beginRenderPass(dawn::LoadOp colorOp, dawn::LoadOp stencilOp); + void insertEventMarker(const char*) override; + + void inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) override; + + void submit(); + +private: + GrGpu* gpu() override; + + void setScissorState(const GrProgramInfo&); + void applyState(const GrProgramInfo& programInfo, + const GrPrimitiveType primitiveType); + + void onDraw(const GrProgramInfo& programInfo, + const GrMesh mesh[], + int meshCount, + const SkRect& bounds) override; + + void sendMeshToGpu(GrPrimitiveType primType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex) final { + this->sendInstancedMeshToGpu(primType, vertexBuffer, vertexCount, baseVertex, + nullptr, 1, 0); + } + + void sendIndexedMeshToGpu(GrPrimitiveType primType, + const GrBuffer* indexBuffer, int indexCount, int baseIndex, + uint16_t /*minIndexValue*/, uint16_t /*maxIndexValue*/, + const GrBuffer* vertexBuffer, int baseVertex, + GrPrimitiveRestart restart) final { + this->sendIndexedInstancedMeshToGpu(primType, indexBuffer, indexCount, baseIndex, + vertexBuffer, baseVertex, nullptr, 1, 0, restart); + } + + void sendInstancedMeshToGpu(GrPrimitiveType, + const GrBuffer* vertexBuffer, int vertexCount, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance) final; + + void sendIndexedInstancedMeshToGpu(GrPrimitiveType, + const GrBuffer* indexBuffer, int indexCount, int baseIndex, + const GrBuffer* vertexBuffer, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance, GrPrimitiveRestart) final; + + void onClear(const GrFixedClip&, const SkPMColor4f& color) override; + + void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) override; + + struct InlineUploadInfo { + InlineUploadInfo(GrOpFlushState* state, const GrDeferredTextureUploadFn& upload) + : fFlushState(state), fUpload(upload) {} + + GrOpFlushState* fFlushState; + GrDeferredTextureUploadFn fUpload; + }; + + GrDawnGpu* fGpu; + dawn::CommandEncoder fEncoder; + dawn::RenderPassEncoder fPassEncoder; + LoadAndStoreInfo fColorInfo; + + typedef GrOpsRenderPass INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.cpp new file mode 100644 index 0000000000..a33e9bbeb7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.cpp @@ -0,0 +1,555 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnProgramBuilder.h" + +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrShaderUtils.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/dawn/GrDawnGpu.h" +#include "src/gpu/dawn/GrDawnTexture.h" +#include "src/sksl/SkSLCompiler.h" + +static SkSL::String sksl_to_spirv(const GrDawnGpu* gpu, const char* shaderString, + SkSL::Program::Kind kind, bool flipY, + SkSL::Program::Inputs* inputs) { + SkSL::Program::Settings settings; + settings.fCaps = gpu->caps()->shaderCaps(); + settings.fFlipY = flipY; + std::unique_ptr program = gpu->shaderCompiler()->convertProgram( + kind, + shaderString, + settings); + if (!program) { + SkDebugf("SkSL error:\n%s\n", gpu->shaderCompiler()->errorText().c_str()); + SkASSERT(false); + return ""; + } + *inputs = program->fInputs; + SkSL::String code; + if (!gpu->shaderCompiler()->toSPIRV(*program, &code)) { + return ""; + } + return code; +} + +static dawn::BlendFactor to_dawn_blend_factor(GrBlendCoeff coeff) { + switch (coeff) { + case kZero_GrBlendCoeff: + return dawn::BlendFactor::Zero; + case kOne_GrBlendCoeff: + return dawn::BlendFactor::One; + case kSC_GrBlendCoeff: + return dawn::BlendFactor::SrcColor; + case kISC_GrBlendCoeff: + return dawn::BlendFactor::OneMinusSrcColor; + case kDC_GrBlendCoeff: + return dawn::BlendFactor::DstColor; + case kIDC_GrBlendCoeff: + return dawn::BlendFactor::OneMinusDstColor; + case kSA_GrBlendCoeff: + return dawn::BlendFactor::SrcAlpha; + case kISA_GrBlendCoeff: + return dawn::BlendFactor::OneMinusSrcAlpha; + case kDA_GrBlendCoeff: + return dawn::BlendFactor::DstAlpha; + case kIDA_GrBlendCoeff: + return dawn::BlendFactor::OneMinusDstAlpha; + case kConstC_GrBlendCoeff: + return dawn::BlendFactor::BlendColor; + case kIConstC_GrBlendCoeff: + return dawn::BlendFactor::OneMinusBlendColor; + case kConstA_GrBlendCoeff: + case kIConstA_GrBlendCoeff: + case kS2C_GrBlendCoeff: + case kIS2C_GrBlendCoeff: + case kS2A_GrBlendCoeff: + case kIS2A_GrBlendCoeff: + default: + SkASSERT(!"unsupported blend coefficient"); + return dawn::BlendFactor::One; + } +} + +static dawn::BlendFactor to_dawn_blend_factor_for_alpha(GrBlendCoeff coeff) { + switch (coeff) { + // Force all srcColor used in alpha slot to alpha version. + case kSC_GrBlendCoeff: + return dawn::BlendFactor::SrcAlpha; + case kISC_GrBlendCoeff: + return dawn::BlendFactor::OneMinusSrcAlpha; + case kDC_GrBlendCoeff: + return dawn::BlendFactor::DstAlpha; + case kIDC_GrBlendCoeff: + return dawn::BlendFactor::OneMinusDstAlpha; + default: + return to_dawn_blend_factor(coeff); + } +} + +static dawn::BlendOperation to_dawn_blend_operation(GrBlendEquation equation) { + switch (equation) { + case kAdd_GrBlendEquation: + return dawn::BlendOperation::Add; + case kSubtract_GrBlendEquation: + return dawn::BlendOperation::Subtract; + case kReverseSubtract_GrBlendEquation: + return dawn::BlendOperation::ReverseSubtract; + default: + SkASSERT(!"unsupported blend equation"); + return dawn::BlendOperation::Add; + } +} + +static dawn::CompareFunction to_dawn_compare_function(GrStencilTest test) { + switch (test) { + case GrStencilTest::kAlways: + return dawn::CompareFunction::Always; + case GrStencilTest::kNever: + return dawn::CompareFunction::Never; + case GrStencilTest::kGreater: + return dawn::CompareFunction::Greater; + case GrStencilTest::kGEqual: + return dawn::CompareFunction::GreaterEqual; + case GrStencilTest::kLess: + return dawn::CompareFunction::Less; + case GrStencilTest::kLEqual: + return dawn::CompareFunction::LessEqual; + case GrStencilTest::kEqual: + return dawn::CompareFunction::Equal; + case GrStencilTest::kNotEqual: + return dawn::CompareFunction::NotEqual; + default: + SkASSERT(!"unsupported stencil test"); + return dawn::CompareFunction::Always; + } +} + +static dawn::StencilOperation to_dawn_stencil_operation(GrStencilOp op) { + switch (op) { + case GrStencilOp::kKeep: + return dawn::StencilOperation::Keep; + case GrStencilOp::kZero: + return dawn::StencilOperation::Zero; + case GrStencilOp::kReplace: + return dawn::StencilOperation::Replace; + case GrStencilOp::kInvert: + return dawn::StencilOperation::Invert; + case GrStencilOp::kIncClamp: + return dawn::StencilOperation::IncrementClamp; + case GrStencilOp::kDecClamp: + return dawn::StencilOperation::DecrementClamp; + case GrStencilOp::kIncWrap: + return dawn::StencilOperation::IncrementWrap; + case GrStencilOp::kDecWrap: + return dawn::StencilOperation::DecrementWrap; + default: + SkASSERT(!"unsupported stencil function"); + return dawn::StencilOperation::Keep; + } +} + +static dawn::PrimitiveTopology to_dawn_primitive_topology(GrPrimitiveType primitiveType) { + switch (primitiveType) { + case GrPrimitiveType::kTriangles: + return dawn::PrimitiveTopology::TriangleList; + case GrPrimitiveType::kTriangleStrip: + return dawn::PrimitiveTopology::TriangleStrip; + case GrPrimitiveType::kPoints: + return dawn::PrimitiveTopology::PointList; + case GrPrimitiveType::kLines: + return dawn::PrimitiveTopology::LineList; + case GrPrimitiveType::kLineStrip: + return dawn::PrimitiveTopology::LineStrip; + case GrPrimitiveType::kPath: + default: + SkASSERT(!"unsupported primitive topology"); + return dawn::PrimitiveTopology::TriangleList; + } +} + +static dawn::VertexFormat to_dawn_vertex_format(GrVertexAttribType type) { + switch (type) { + case kFloat_GrVertexAttribType: + case kHalf_GrVertexAttribType: + return dawn::VertexFormat::Float; + case kFloat2_GrVertexAttribType: + case kHalf2_GrVertexAttribType: + return dawn::VertexFormat::Float2; + case kFloat3_GrVertexAttribType: + case kHalf3_GrVertexAttribType: + return dawn::VertexFormat::Float3; + case kFloat4_GrVertexAttribType: + case kHalf4_GrVertexAttribType: + return dawn::VertexFormat::Float4; + case kUShort2_GrVertexAttribType: + return dawn::VertexFormat::UShort2; + case kInt_GrVertexAttribType: + return dawn::VertexFormat::Int; + case kUByte4_norm_GrVertexAttribType: + return dawn::VertexFormat::UChar4Norm; + default: + SkASSERT(!"unsupported vertex format"); + return dawn::VertexFormat::Float4; + } +} + +static dawn::ColorStateDescriptor create_color_state(const GrDawnGpu* gpu, + const GrPipeline& pipeline, + dawn::TextureFormat colorFormat) { + GrXferProcessor::BlendInfo blendInfo = pipeline.getXferProcessor().getBlendInfo(); + GrBlendEquation equation = blendInfo.fEquation; + GrBlendCoeff srcCoeff = blendInfo.fSrcBlend; + GrBlendCoeff dstCoeff = blendInfo.fDstBlend; + + dawn::BlendFactor srcFactor = to_dawn_blend_factor(srcCoeff); + dawn::BlendFactor dstFactor = to_dawn_blend_factor(dstCoeff); + dawn::BlendFactor srcFactorAlpha = to_dawn_blend_factor_for_alpha(srcCoeff); + dawn::BlendFactor dstFactorAlpha = to_dawn_blend_factor_for_alpha(dstCoeff); + dawn::BlendOperation operation = to_dawn_blend_operation(equation); + auto mask = blendInfo.fWriteColor ? dawn::ColorWriteMask::All : dawn::ColorWriteMask::None; + + dawn::BlendDescriptor colorDesc = {operation, srcFactor, dstFactor}; + dawn::BlendDescriptor alphaDesc = {operation, srcFactorAlpha, dstFactorAlpha}; + + dawn::ColorStateDescriptor descriptor; + descriptor.format = colorFormat; + descriptor.alphaBlend = alphaDesc; + descriptor.colorBlend = colorDesc; + descriptor.nextInChain = nullptr; + descriptor.writeMask = mask; + + return descriptor; +} + +static dawn::StencilStateFaceDescriptor to_stencil_state_face(const GrStencilSettings::Face& face) { + dawn::StencilStateFaceDescriptor desc; + desc.compare = to_dawn_compare_function(face.fTest); + desc.failOp = desc.depthFailOp = to_dawn_stencil_operation(face.fFailOp); + desc.passOp = to_dawn_stencil_operation(face.fPassOp); + return desc; +} + +static dawn::DepthStencilStateDescriptor create_depth_stencil_state( + const GrStencilSettings& stencilSettings, + dawn::TextureFormat depthStencilFormat, + GrSurfaceOrigin origin) { + dawn::DepthStencilStateDescriptor state; + state.format = depthStencilFormat; + if (!stencilSettings.isDisabled()) { + const GrStencilSettings::Face& front = stencilSettings.front(origin); + state.stencilReadMask = front.fTestMask; + state.stencilWriteMask = front.fWriteMask; + state.stencilFront = to_stencil_state_face(stencilSettings.front(origin)); + if (stencilSettings.isTwoSided()) { + state.stencilBack = to_stencil_state_face(stencilSettings.back(origin)); + } else { + state.stencilBack = state.stencilFront; + } + } + return state; +} + +static dawn::BindGroupBinding make_bind_group_binding(uint32_t binding, const dawn::Buffer& buffer, + uint32_t offset, uint32_t size, const + dawn::Sampler& sampler, + const dawn::TextureView& textureView) { + dawn::BindGroupBinding result; + result.binding = binding; + result.buffer = buffer; + result.offset = offset; + result.size = size; + result.sampler = sampler; + result.textureView = textureView; + return result; +} + +static dawn::BindGroupBinding make_bind_group_binding(uint32_t binding, const dawn::Buffer& buffer, + uint32_t offset, uint32_t size) { + return make_bind_group_binding(binding, buffer, offset, size, nullptr, nullptr); +} + +static dawn::BindGroupBinding make_bind_group_binding(uint32_t binding, + const dawn::Sampler& sampler) { + return make_bind_group_binding(binding, nullptr, 0, 0, sampler, nullptr); +} + +static dawn::BindGroupBinding make_bind_group_binding(uint32_t binding, + const dawn::TextureView& textureView) { + return make_bind_group_binding(binding, nullptr, 0, 0, nullptr, textureView); +} + +sk_sp GrDawnProgramBuilder::Build(GrDawnGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + dawn::TextureFormat colorFormat, + bool hasDepthStencil, + dawn::TextureFormat depthStencilFormat, + GrProgramDesc* desc) { + GrDawnProgramBuilder builder(gpu, renderTarget, programInfo, desc); + if (!builder.emitAndInstallProcs()) { + return nullptr; + } + + builder.fVS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n"); + builder.fFS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n"); + builder.fVS.extensions().appendf("#extension GL_ARB_shading_language_420pack : enable\n"); + builder.fFS.extensions().appendf("#extension GL_ARB_shading_language_420pack : enable\n"); + + builder.finalizeShaders(); + + SkSL::Program::Inputs vertInputs, fragInputs; + GrDawnUniformHandler::UniformInfoArray& uniforms = builder.fUniformHandler.fUniforms; + uint32_t geometryUniformSize = builder.fUniformHandler.fCurrentGeometryUBOOffset; + uint32_t fragmentUniformSize = builder.fUniformHandler.fCurrentFragmentUBOOffset; + sk_sp result( + new GrDawnProgram(uniforms, geometryUniformSize, fragmentUniformSize)); + bool flipY = programInfo.origin() != kTopLeft_GrSurfaceOrigin; + auto vsModule = builder.createShaderModule(builder.fVS, SkSL::Program::kVertex_Kind, flipY, + &vertInputs); + auto fsModule = builder.createShaderModule(builder.fFS, SkSL::Program::kFragment_Kind, flipY, + &fragInputs); + result->fGeometryProcessor = std::move(builder.fGeometryProcessor); + result->fXferProcessor = std::move(builder.fXferProcessor); + result->fFragmentProcessors = std::move(builder.fFragmentProcessors); + result->fFragmentProcessorCnt = builder.fFragmentProcessorCnt; + std::vector layoutBindings; + if (0 != geometryUniformSize) { + layoutBindings.push_back({ GrDawnUniformHandler::kGeometryBinding, + dawn::ShaderStage::Vertex, + dawn::BindingType::UniformBuffer}); + } + if (0 != fragmentUniformSize) { + layoutBindings.push_back({ GrDawnUniformHandler::kFragBinding, + dawn::ShaderStage::Fragment, + dawn::BindingType::UniformBuffer}); + } + uint32_t binding = GrDawnUniformHandler::kSamplerBindingBase; + for (int i = 0; i < builder.fUniformHandler.fSamplers.count(); ++i) { + layoutBindings.push_back({ binding++, dawn::ShaderStage::Fragment, + dawn::BindingType::Sampler}); + layoutBindings.push_back({ binding++, dawn::ShaderStage::Fragment, + dawn::BindingType::SampledTexture}); + } + dawn::BindGroupLayoutDescriptor bindGroupLayoutDesc; + bindGroupLayoutDesc.bindingCount = layoutBindings.size(); + bindGroupLayoutDesc.bindings = layoutBindings.data(); + result->fBindGroupLayout = gpu->device().CreateBindGroupLayout(&bindGroupLayoutDesc); + dawn::PipelineLayoutDescriptor pipelineLayoutDesc; + pipelineLayoutDesc.bindGroupLayoutCount = 1; + pipelineLayoutDesc.bindGroupLayouts = &result->fBindGroupLayout; + auto pipelineLayout = gpu->device().CreatePipelineLayout(&pipelineLayoutDesc); + result->fBuiltinUniformHandles = builder.fUniformHandles; + const GrPipeline& pipeline = programInfo.pipeline(); + auto colorState = create_color_state(gpu, pipeline, colorFormat); + dawn::DepthStencilStateDescriptor depthStencilState; + GrStencilSettings stencil; + if (pipeline.isStencilEnabled()) { + int numStencilBits = renderTarget->renderTargetPriv().numStencilBits(); + stencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(), numStencilBits); + } + depthStencilState = create_depth_stencil_state(stencil, depthStencilFormat, + programInfo.origin()); + + std::vector inputs; + + std::vector vertexAttributes; + const GrPrimitiveProcessor& primProc = programInfo.primProc(); + if (primProc.numVertexAttributes() > 0) { + size_t offset = 0; + int i = 0; + for (const auto& attrib : primProc.vertexAttributes()) { + dawn::VertexAttributeDescriptor attribute; + attribute.shaderLocation = i; + attribute.offset = offset; + attribute.format = to_dawn_vertex_format(attrib.cpuType()); + vertexAttributes.push_back(attribute); + offset += attrib.sizeAlign4(); + i++; + } + dawn::VertexBufferDescriptor input; + input.stride = offset; + input.stepMode = dawn::InputStepMode::Vertex; + input.attributeCount = vertexAttributes.size(); + input.attributes = &vertexAttributes.front(); + inputs.push_back(input); + } + std::vector instanceAttributes; + if (primProc.numInstanceAttributes() > 0) { + size_t offset = 0; + int i = 0; + for (const auto& attrib : primProc.instanceAttributes()) { + dawn::VertexAttributeDescriptor attribute; + attribute.shaderLocation = i; + attribute.offset = offset; + attribute.format = to_dawn_vertex_format(attrib.cpuType()); + instanceAttributes.push_back(attribute); + offset += attrib.sizeAlign4(); + i++; + } + dawn::VertexBufferDescriptor input; + input.stride = offset; + input.stepMode = dawn::InputStepMode::Instance; + input.attributeCount = instanceAttributes.size(); + input.attributes = &instanceAttributes.front(); + inputs.push_back(input); + } + dawn::VertexInputDescriptor vertexInput; + vertexInput.indexFormat = dawn::IndexFormat::Uint16; + vertexInput.bufferCount = inputs.size(); + vertexInput.buffers = &inputs.front(); + + dawn::ProgrammableStageDescriptor vsDesc; + vsDesc.module = vsModule; + vsDesc.entryPoint = "main"; + + dawn::ProgrammableStageDescriptor fsDesc; + fsDesc.module = fsModule; + fsDesc.entryPoint = "main"; + + dawn::RenderPipelineDescriptor rpDesc; + rpDesc.layout = pipelineLayout; + rpDesc.vertexStage = vsDesc; + rpDesc.fragmentStage = &fsDesc; + rpDesc.vertexInput = &vertexInput; + rpDesc.primitiveTopology = to_dawn_primitive_topology(primitiveType); + if (hasDepthStencil) { + rpDesc.depthStencilState = &depthStencilState; + } + rpDesc.colorStateCount = 1; + rpDesc.colorStates = &colorState; + result->fRenderPipeline = gpu->device().CreateRenderPipeline(&rpDesc); + return result; +} + +GrDawnProgramBuilder::GrDawnProgramBuilder(GrDawnGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrProgramDesc* desc) + : INHERITED(renderTarget, programInfo, desc) + , fGpu(gpu) + , fVaryingHandler(this) + , fUniformHandler(this) { +} + +dawn::ShaderModule GrDawnProgramBuilder::createShaderModule(const GrGLSLShaderBuilder& builder, + SkSL::Program::Kind kind, + bool flipY, + SkSL::Program::Inputs* inputs) { + dawn::Device device = fGpu->device(); + SkString source(builder.fCompilerString.c_str()); + +#if 0 + SkSL::String sksl = GrShaderUtils::PrettyPrint(builder.fCompilerString); + printf("converting program:\n%s\n", sksl.c_str()); +#endif + + SkSL::String spirvSource = sksl_to_spirv(fGpu, source.c_str(), kind, flipY, inputs); + + dawn::ShaderModuleDescriptor desc; + desc.codeSize = spirvSource.size() / 4; + desc.code = reinterpret_cast(spirvSource.c_str()); + + return device.CreateShaderModule(&desc); +}; + +const GrCaps* GrDawnProgramBuilder::caps() const { + return fGpu->caps(); +} + +void GrDawnProgram::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin) { + // Load the RT height uniform if it is needed to y-flip gl_FragCoord. + if (fBuiltinUniformHandles.fRTHeightUni.isValid() && + fRenderTargetState.fRenderTargetSize.fHeight != rt->height()) { + fDataManager.set1f(fBuiltinUniformHandles.fRTHeightUni, SkIntToScalar(rt->height())); + } + + // set RT adjustment + SkISize size; + size.set(rt->width(), rt->height()); + SkASSERT(fBuiltinUniformHandles.fRTAdjustmentUni.isValid()); + if (fRenderTargetState.fRenderTargetOrigin != origin || + fRenderTargetState.fRenderTargetSize != size) { + fRenderTargetState.fRenderTargetSize = size; + fRenderTargetState.fRenderTargetOrigin = origin; + + float rtAdjustmentVec[4]; + fRenderTargetState.getRTAdjustmentVec(rtAdjustmentVec); + fDataManager.set4fv(fBuiltinUniformHandles.fRTAdjustmentUni, 1, rtAdjustmentVec); + } +} + +static void setTexture(GrDawnGpu* gpu, const GrSamplerState& state, GrTexture* texture, + std::vector *bindings, int* binding) { + // FIXME: could probably cache samplers in GrDawnProgram + dawn::Sampler sampler = gpu->getOrCreateSampler(state); + bindings->push_back(make_bind_group_binding((*binding)++, sampler)); + GrDawnTexture* tex = static_cast(texture); + dawn::TextureView textureView = tex->textureView(); + bindings->push_back(make_bind_group_binding((*binding)++, textureView)); +} + +dawn::BindGroup GrDawnProgram::setData(GrDawnGpu* gpu, const GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo) { + std::vector bindings; + GrDawnRingBuffer::Slice geom, frag; + uint32_t geometryUniformSize = fDataManager.geometryUniformSize(); + uint32_t fragmentUniformSize = fDataManager.fragmentUniformSize(); + if (0 != geometryUniformSize) { + geom = gpu->allocateUniformRingBufferSlice(geometryUniformSize); + bindings.push_back(make_bind_group_binding(GrDawnUniformHandler::kGeometryBinding, + geom.fBuffer, geom.fOffset, + geometryUniformSize)); + } + if (0 != fragmentUniformSize) { + frag = gpu->allocateUniformRingBufferSlice(fragmentUniformSize); + bindings.push_back(make_bind_group_binding(GrDawnUniformHandler::kFragBinding, + frag.fBuffer, frag.fOffset, + fragmentUniformSize)); + } + this->setRenderTargetState(renderTarget, programInfo.origin()); + const GrPipeline& pipeline = programInfo.pipeline(); + const GrPrimitiveProcessor& primProc = programInfo.primProc(); + fGeometryProcessor->setData(fDataManager, primProc, + GrFragmentProcessor::CoordTransformIter(pipeline)); + int binding = GrDawnUniformHandler::kSamplerBindingBase; + auto primProcTextures = programInfo.hasFixedPrimProcTextures() ? + programInfo.fixedPrimProcTextures() : nullptr; + + for (int i = 0; i < primProc.numTextureSamplers(); ++i) { + auto& sampler = primProc.textureSampler(i); + setTexture(gpu, sampler.samplerState(), primProcTextures[i]->peekTexture(), &bindings, + &binding); + } + GrFragmentProcessor::Iter iter(pipeline); + GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt); + const GrFragmentProcessor* fp = iter.next(); + GrGLSLFragmentProcessor* glslFP = glslIter.next(); + while (fp && glslFP) { + glslFP->setData(fDataManager, *fp); + for (int i = 0; i < fp->numTextureSamplers(); ++i) { + auto& s = fp->textureSampler(i); + setTexture(gpu, s.samplerState(), s.peekTexture(), &bindings, &binding); + } + fp = iter.next(); + glslFP = glslIter.next(); + } + SkIPoint offset; + GrTexture* dstTexture = pipeline.peekDstTexture(&offset); + fXferProcessor->setData(fDataManager, pipeline.getXferProcessor(), dstTexture, offset); + if (GrTextureProxy* proxy = pipeline.dstTextureProxy()) { + GrFragmentProcessor::TextureSampler sampler(sk_ref_sp(proxy)); + setTexture(gpu, sampler.samplerState(), sampler.peekTexture(), &bindings, &binding); + } + fDataManager.uploadUniformBuffers(gpu, geom, frag); + dawn::BindGroupDescriptor descriptor; + descriptor.layout = fBindGroupLayout; + descriptor.bindingCount = bindings.size(); + descriptor.bindings = bindings.data(); + return gpu->device().CreateBindGroup(&descriptor); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.h b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.h new file mode 100644 index 0000000000..1cc3231741 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramBuilder.h @@ -0,0 +1,101 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnProgramBuilder_DEFINED +#define GrDawnProgramBuilder_DEFINED + +#include "src/gpu/dawn/GrDawnProgramDataManager.h" +#include "src/gpu/dawn/GrDawnUniformHandler.h" +#include "src/gpu/dawn/GrDawnVaryingHandler.h" +#include "src/sksl/SkSLCompiler.h" +#include "dawn/dawncpp.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" + +class GrPipeline; + +struct GrDawnProgram : public SkRefCnt { + struct RenderTargetState { + SkISize fRenderTargetSize; + GrSurfaceOrigin fRenderTargetOrigin; + + RenderTargetState() { this->invalidate(); } + void invalidate() { + fRenderTargetSize.fWidth = -1; + fRenderTargetSize.fHeight = -1; + fRenderTargetOrigin = (GrSurfaceOrigin) -1; + } + + /** + * Gets a float4 that adjusts the position from Skia device coords to GL's normalized device + * coords. Assuming the transformed position, pos, is a homogeneous float3, the vec, v, is + * applied as such: + * pos.x = dot(v.xy, pos.xz) + * pos.y = dot(v.zw, pos.yz) + */ + void getRTAdjustmentVec(float* destVec) { + destVec[0] = 2.f / fRenderTargetSize.fWidth; + destVec[1] = -1.f; + if (kTopLeft_GrSurfaceOrigin == fRenderTargetOrigin) { + destVec[2] = -2.f / fRenderTargetSize.fHeight; + destVec[3] = 1.f; + } else { + destVec[2] = 2.f / fRenderTargetSize.fHeight; + destVec[3] = -1.f; + } + } + }; + typedef GrGLSLBuiltinUniformHandles BuiltinUniformHandles; + GrDawnProgram(const GrDawnUniformHandler::UniformInfoArray& uniforms, + uint32_t geometryUniformSize, + uint32_t fragmentUniformSize) + : fDataManager(uniforms, geometryUniformSize, fragmentUniformSize) { + } + std::unique_ptr fGeometryProcessor; + std::unique_ptr fXferProcessor; + std::unique_ptr[]> fFragmentProcessors; + int fFragmentProcessorCnt; + dawn::BindGroupLayout fBindGroupLayout; + dawn::RenderPipeline fRenderPipeline; + GrDawnProgramDataManager fDataManager; + RenderTargetState fRenderTargetState; + BuiltinUniformHandles fBuiltinUniformHandles; + + void setRenderTargetState(const GrRenderTarget*, GrSurfaceOrigin); + dawn::BindGroup setData(GrDawnGpu* gpu, const GrRenderTarget*, const GrProgramInfo&); +}; + +class GrDawnProgramBuilder : public GrGLSLProgramBuilder { +public: + static sk_sp Build(GrDawnGpu*, + GrRenderTarget* rt, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + dawn::TextureFormat colorFormat, + bool hasDepthStencil, + dawn::TextureFormat depthStencilFormat, + GrProgramDesc* desc); + const GrCaps* caps() const override; + GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; } + const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; } + GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; } + + GrDawnGpu* gpu() const { return fGpu; } + +private: + GrDawnProgramBuilder(GrDawnGpu*, + GrRenderTarget*, + const GrProgramInfo&, + GrProgramDesc*); + dawn::ShaderModule createShaderModule(const GrGLSLShaderBuilder&, SkSL::Program::Kind, + bool flipY, SkSL::Program::Inputs* inputs); + GrDawnGpu* fGpu; + GrDawnVaryingHandler fVaryingHandler; + GrDawnUniformHandler fUniformHandler; + + typedef GrGLSLProgramBuilder INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.cpp new file mode 100644 index 0000000000..db22f255f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.cpp @@ -0,0 +1,290 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnProgramDataManager.h" + +#include "src/gpu/dawn/GrDawnGpu.h" + +GrDawnProgramDataManager::GrDawnProgramDataManager(const UniformInfoArray& uniforms, + uint32_t geometryUniformSize, + uint32_t fragmentUniformSize) + : fGeometryUniformSize(geometryUniformSize) + , fFragmentUniformSize(fragmentUniformSize) + , fGeometryUniformsDirty(false) + , fFragmentUniformsDirty(false) { + fGeometryUniformData.reset(geometryUniformSize); + fFragmentUniformData.reset(fragmentUniformSize); + memset(fGeometryUniformData.get(), 0, fGeometryUniformSize); + memset(fFragmentUniformData.get(), 0, fFragmentUniformSize); + int count = uniforms.count(); + fUniforms.push_back_n(count); + // We must add uniforms in same order is the UniformInfoArray so that UniformHandles already + // owned by other objects will still match up here. + for (int i = 0; i < count; i++) { + Uniform& uniform = fUniforms[i]; + const GrDawnUniformHandler::UniformInfo uniformInfo = uniforms[i]; + SkDEBUGCODE( + uniform.fArrayCount = uniformInfo.fVar.getArrayCount(); + uniform.fType = uniformInfo.fVar.getType(); + ); + + if (!(kFragment_GrShaderFlag & uniformInfo.fVisibility)) { + uniform.fBinding = GrDawnUniformHandler::kGeometryBinding; + } else { + SkASSERT(kFragment_GrShaderFlag == uniformInfo.fVisibility); + uniform.fBinding = GrDawnUniformHandler::kFragBinding; + } + uniform.fOffset = uniformInfo.fUBOOffset; + } +} + +void* GrDawnProgramDataManager::getBufferPtrAndMarkDirty(const Uniform& uni) const { + void* buffer; + if (GrDawnUniformHandler::kGeometryBinding == uni.fBinding) { + buffer = fGeometryUniformData.get(); + fGeometryUniformsDirty = true; + } else { + SkASSERT(GrDawnUniformHandler::kFragBinding == uni.fBinding); + buffer = fFragmentUniformData.get(); + fFragmentUniformsDirty = true; + } + buffer = static_cast(buffer)+uni.fOffset; + return buffer; +} + +void GrDawnProgramDataManager::set1i(UniformHandle u, int32_t i) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + memcpy(buffer, &i, sizeof(int32_t)); +} + +void GrDawnProgramDataManager::set1iv(UniformHandle u, int arrayCount, const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + uint32_t* buffer = static_cast(this->getBufferPtrAndMarkDirty(uni)); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[i]; + memcpy(buffer, curVec, sizeof(int32_t)); + buffer += 4; + } +} + +void GrDawnProgramDataManager::set2i(UniformHandle u, int32_t i0, int32_t i1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[2] = { i0, i1 }; + memcpy(buffer, v, 2 * sizeof(int32_t)); +} + +void GrDawnProgramDataManager::set2iv(UniformHandle u, int arrayCount, const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + uint32_t* buffer = static_cast(this->getBufferPtrAndMarkDirty(uni)); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[2 * i]; + memcpy(buffer, curVec, 2 * sizeof(int32_t)); + buffer += 4; + } +} + +void GrDawnProgramDataManager::set3i(UniformHandle u, int32_t i0, int32_t i1, int32_t i2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[3] = { i0, i1, i2 }; + memcpy(buffer, v, 3 * sizeof(int32_t)); +} + +void GrDawnProgramDataManager::set3iv(UniformHandle u, int arrayCount, const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + uint32_t* buffer = static_cast(this->getBufferPtrAndMarkDirty(uni)); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[3 * i]; + memcpy(buffer, curVec, 3 * sizeof(int32_t)); + buffer += 4; + } +} + +void GrDawnProgramDataManager::set4i(UniformHandle u, + int32_t i0, + int32_t i1, + int32_t i2, + int32_t i3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[4] = { i0, i1, i2, i3 }; + memcpy(buffer, v, sizeof(v)); +} + +void GrDawnProgramDataManager::set4iv(UniformHandle u, int arrayCount, const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + uint32_t* buffer = static_cast(this->getBufferPtrAndMarkDirty(uni)); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[4 * i]; + memcpy(buffer, curVec, 4 * sizeof(int32_t)); + buffer += 4; + } +} + +void GrDawnProgramDataManager::set1f(UniformHandle u, float v0) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + memcpy(buffer, &v0, sizeof(float)); +} + +void GrDawnProgramDataManager::set1fv(UniformHandle u, int arrayCount, const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[i]; + memcpy(buffer, curVec, sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrDawnProgramDataManager::set2f(UniformHandle u, float v0, float v1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + float v[2] = { v0, v1 }; + memcpy(buffer, v, 2 * sizeof(float)); +} + +void GrDawnProgramDataManager::set2fv(UniformHandle u, int arrayCount, const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[2 * i]; + memcpy(buffer, curVec, 2 * sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrDawnProgramDataManager::set3f(UniformHandle u, float v0, float v1, float v2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + float v[3] = { v0, v1, v2 }; + memcpy(buffer, v, 3 * sizeof(float)); +} + +void GrDawnProgramDataManager::set3fv(UniformHandle u, int arrayCount, const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[3 * i]; + memcpy(buffer, curVec, 3 * sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrDawnProgramDataManager::set4f(UniformHandle u, + float v0, + float v1, + float v2, + float v3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + float v[4] = { v0, v1, v2, v3 }; + memcpy(buffer, v, 4 * sizeof(float)); +} + +void GrDawnProgramDataManager::set4fv(UniformHandle u, int arrayCount, const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer = this->getBufferPtrAndMarkDirty(uni); + memcpy(buffer, v, arrayCount * 4 * sizeof(float)); +} + +void GrDawnProgramDataManager::setMatrix2f(UniformHandle u, const float matrix[]) const { + this->setMatrices<2>(u, 1, matrix); +} + +void GrDawnProgramDataManager::setMatrix2fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<2>(u, arrayCount, m); +} + +void GrDawnProgramDataManager::setMatrix3f(UniformHandle u, const float matrix[]) const { + this->setMatrices<3>(u, 1, matrix); +} + +void GrDawnProgramDataManager::setMatrix3fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<3>(u, arrayCount, m); +} + +void GrDawnProgramDataManager::setMatrix4f(UniformHandle u, const float matrix[]) const { + this->setMatrices<4>(u, 1, matrix); +} + +void GrDawnProgramDataManager::setMatrix4fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<4>(u, arrayCount, m); +} + +template struct set_uniform_matrix; + +template inline void GrDawnProgramDataManager::setMatrices(UniformHandle u, + int arrayCount, + const float matrices[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + void* buffer; + if (GrDawnUniformHandler::kGeometryBinding == uni.fBinding) { + buffer = fGeometryUniformData.get(); + fGeometryUniformsDirty = true; + } else { + SkASSERT(GrDawnUniformHandler::kFragBinding == uni.fBinding); + buffer = fFragmentUniformData.get(); + fFragmentUniformsDirty = true; + } + + set_uniform_matrix::set(buffer, uni.fOffset, arrayCount, matrices); +} + +template struct set_uniform_matrix { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + for (int i = 0; i < count; ++i) { + const float* matrix = &matrices[N * N * i]; + for (int j = 0; j < N; ++j) { + memcpy(buffer, &matrix[j * N], N * sizeof(float)); + buffer = static_cast(buffer) + 4 * sizeof(float); + } + } + } +}; + +template<> struct set_uniform_matrix<4> { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + memcpy(buffer, matrices, count * 16 * sizeof(float)); + } +}; + +void GrDawnProgramDataManager::uploadUniformBuffers(GrDawnGpu* gpu, + GrDawnRingBuffer::Slice geometryBuffer, + GrDawnRingBuffer::Slice fragmentBuffer) const { + dawn::Buffer geom = geometryBuffer.fBuffer; + uint32_t geomOffset = geometryBuffer.fOffset; + dawn::Buffer frag = fragmentBuffer.fBuffer; + uint32_t fragOffset = fragmentBuffer.fOffset; + auto copyEncoder = gpu->getCopyEncoder(); + if (geom && fGeometryUniformsDirty) { + GrDawnStagingBuffer* stagingBuffer = gpu->getStagingBuffer(fGeometryUniformSize); + memcpy(stagingBuffer->fData, fGeometryUniformData.get(), fGeometryUniformSize); + stagingBuffer->fBuffer.Unmap(); + copyEncoder + .CopyBufferToBuffer(stagingBuffer->fBuffer, 0, geom, geomOffset, fGeometryUniformSize); + } + if (frag && fFragmentUniformsDirty) { + GrDawnStagingBuffer* stagingBuffer = gpu->getStagingBuffer(fFragmentUniformSize); + memcpy(stagingBuffer->fData, fFragmentUniformData.get(), fFragmentUniformSize); + stagingBuffer->fBuffer.Unmap(); + copyEncoder + .CopyBufferToBuffer(stagingBuffer->fBuffer, 0, frag, fragOffset, fFragmentUniformSize); + } +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.h b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.h new file mode 100644 index 0000000000..b77d5ffe3f --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnProgramDataManager.h @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnProgramDataManager_DEFINED +#define GrDawnProgramDataManager_DEFINED + +#include "src/gpu/dawn/GrDawnRingBuffer.h" +#include "src/gpu/dawn/GrDawnUniformHandler.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "dawn/dawncpp.h" + +#include "src/core/SkAutoMalloc.h" + +class GrDawnGpu; +class GrDawnUniformBuffer; + +class GrDawnProgramDataManager : public GrGLSLProgramDataManager { +public: + typedef GrDawnUniformHandler::UniformInfoArray UniformInfoArray; + + GrDawnProgramDataManager(const UniformInfoArray&, + uint32_t geometryUniformSize, + uint32_t fragmentUniformSize); + + void set1i(UniformHandle, int32_t) const override; + void set1iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set2i(UniformHandle, int32_t, int32_t) const override; + void set2iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set3i(UniformHandle, int32_t, int32_t, int32_t) const override; + void set3iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set4i(UniformHandle, int32_t, int32_t, int32_t, int32_t) const override; + void set4iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + + void set1f(UniformHandle, float v0) const override; + void set1fv(UniformHandle, int arrayCount, const float v[]) const override; + void set2f(UniformHandle, float, float) const override; + void set2fv(UniformHandle, int arrayCount, const float v[]) const override; + void set3f(UniformHandle, float, float, float) const override; + void set3fv(UniformHandle, int arrayCount, const float v[]) const override; + void set4f(UniformHandle, float, float, float, float) const override; + void set4fv(UniformHandle, int arrayCount, const float v[]) const override; + // matrices are column-major, the first two upload a single matrix, the latter two upload + // arrayCount matrices into a uniform array. + void setMatrix2f(UniformHandle, const float matrix[]) const override; + void setMatrix3f(UniformHandle, const float matrix[]) const override; + void setMatrix4f(UniformHandle, const float matrix[]) const override; + void setMatrix2fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix3fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix4fv(UniformHandle, int arrayCount, const float matrices[]) const override; + + // for nvpr only + void setPathFragmentInputTransform(VaryingHandle u, int components, + const SkMatrix& matrix) const override { + SK_ABORT("Only supported in NVPR, which is not in Dawn"); + } + + void uploadUniformBuffers(GrDawnGpu* gpu, + GrDawnRingBuffer::Slice geometryBuffer, + GrDawnRingBuffer::Slice fragmentBuffer) const; + + uint32_t geometryUniformSize() const { return fGeometryUniformSize; } + uint32_t fragmentUniformSize() const { return fFragmentUniformSize; } +private: + struct Uniform { + uint32_t fBinding; + uint32_t fOffset; + SkDEBUGCODE( + GrSLType fType; + int fArrayCount; + ); + }; + + template inline void setMatrices(UniformHandle, int arrayCount, + const float matrices[]) const; + + void* getBufferPtrAndMarkDirty(const Uniform& uni) const; + + uint32_t fGeometryUniformSize; + uint32_t fFragmentUniformSize; + + SkTArray fUniforms; + + mutable SkAutoMalloc fGeometryUniformData; + mutable SkAutoMalloc fFragmentUniformData; + mutable bool fGeometryUniformsDirty; + mutable bool fFragmentUniformsDirty; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.cpp new file mode 100644 index 0000000000..a75ab13fba --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnRenderTarget.h" + +#include "include/gpu/GrBackendSurface.h" +#include "src/gpu/dawn/GrDawnGpu.h" +#include "src/gpu/dawn/GrDawnUtil.h" + +GrDawnRenderTarget::GrDawnRenderTarget(GrDawnGpu* gpu, + const SkISize& size, + GrPixelConfig config, + int sampleCnt, + const GrDawnImageInfo& info) + : GrSurface(gpu, size, config, GrProtected::kNo) + , GrRenderTarget(gpu, size, config, sampleCnt, GrProtected::kNo) + , fInfo(info) { +} + +sk_sp +GrDawnRenderTarget::MakeWrapped(GrDawnGpu* gpu, + const SkISize& size, + GrPixelConfig config, + int sampleCnt, + const GrDawnImageInfo& info) { + sk_sp rt(new GrDawnRenderTarget(gpu, size, config, sampleCnt, info)); + rt->registerWithCacheWrapped(GrWrapCacheable::kNo); + return rt; +} + +size_t GrDawnRenderTarget::onGpuMemorySize() const { + // The plus 1 is to account for the resolve texture or if not using msaa the RT itself + int numSamples = this->numSamples() + 1; + const GrCaps& caps = *getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numSamples, GrMipMapped::kNo); +} + +bool GrDawnRenderTarget::completeStencilAttachment() { + return true; +} + +GrDawnRenderTarget::~GrDawnRenderTarget() { +} + +void GrDawnRenderTarget::onRelease() { + INHERITED::onRelease(); +} + +void GrDawnRenderTarget::onAbandon() { + INHERITED::onAbandon(); +} + +GrBackendRenderTarget GrDawnRenderTarget::getBackendRenderTarget() const { + return GrBackendRenderTarget(this->width(), this->height(), this->numSamples(), + this->numSamples(), fInfo); +} + +GrBackendFormat GrDawnRenderTarget::backendFormat() const { + return GrBackendFormat::MakeDawn(fInfo.fFormat); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.h b/gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.h new file mode 100644 index 0000000000..fe5c77fba1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnRenderTarget.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnRenderTarget_DEFINED +#define GrDawnRenderTarget_DEFINED + +#include "include/gpu/dawn/GrDawnTypes.h" +#include "src/gpu/GrRenderTarget.h" + +class GrDawnGpu; + +class GrDawnRenderTarget: public GrRenderTarget { +public: + static sk_sp MakeWrapped(GrDawnGpu*, const SkISize& size, + GrPixelConfig config, int sampleCnt, + const GrDawnImageInfo&); + + ~GrDawnRenderTarget() override; + + bool canAttemptStencilAttachment() const override { + return true; + } + + GrBackendRenderTarget getBackendRenderTarget() const override; + GrBackendFormat backendFormat() const override; + dawn::Texture texture() const { return fInfo.fTexture; } + +protected: + GrDawnRenderTarget(GrDawnGpu* gpu, + const SkISize& size, + GrPixelConfig config, + int sampleCnt, + const GrDawnImageInfo& info); + + void onAbandon() override; + void onRelease() override; + void onSetRelease(sk_sp releaseHelper) override {} + + // This accounts for the texture's memory and any MSAA renderbuffer's memory. + size_t onGpuMemorySize() const override; + + static GrDawnRenderTarget* Create(GrDawnGpu*, const GrSurfaceDesc&, int sampleCnt, + const GrDawnImageInfo&); + + bool completeStencilAttachment() override; + GrDawnImageInfo fInfo; + typedef GrRenderTarget INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.cpp new file mode 100644 index 0000000000..caf0d578ee --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnRingBuffer.h" + +#include "src/gpu/dawn/GrDawnGpu.h" +#include "src/gpu/dawn/GrDawnUtil.h" + +namespace { + const int kDefaultSize = 512 * 1024; +} + +GrDawnRingBuffer::GrDawnRingBuffer(GrDawnGpu* gpu, dawn::BufferUsage usage) + : fGpu(gpu) , fUsage(usage) { +} + +GrDawnRingBuffer::~GrDawnRingBuffer() { +} + +GrDawnRingBuffer::Slice GrDawnRingBuffer::allocate(int size) { + if (!fBuffer || fOffset + size > kDefaultSize) { + dawn::BufferDescriptor desc; + desc.usage = fUsage | dawn::BufferUsage::CopyDst; + desc.size = kDefaultSize; + fBuffer = fGpu->device().CreateBuffer(&desc); + fOffset = 0; + } + int offset = fOffset; + fOffset += size; + fOffset = GrDawnRoundRowBytes(fOffset); + return Slice(fBuffer, offset); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.h b/gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.h new file mode 100644 index 0000000000..0dbe5059af --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnRingBuffer.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnRingBuffer_DEFINED +#define GrDawnRingBuffer_DEFINED + +#include "src/gpu/GrBuffer.h" +#include "src/gpu/dawn/GrDawnBuffer.h" +#include "dawn/dawncpp.h" + +class GrDawnGpu; + +class GrDawnRingBuffer : public SkRefCnt { +public: + GrDawnRingBuffer(GrDawnGpu* gpu, dawn::BufferUsage usage); + ~GrDawnRingBuffer() override; + + struct Slice { + Slice(dawn::Buffer buffer, int offset) : fBuffer(buffer), fOffset(offset) {} + Slice() : fBuffer(nullptr), fOffset(0) {} + Slice(const Slice& other) : fBuffer(other.fBuffer), fOffset(other.fOffset) {} + Slice& operator=(const Slice& other) { + fBuffer = other.fBuffer; + fOffset = other.fOffset; + return *this; + } + dawn::Buffer fBuffer; + int fOffset; + }; + Slice allocate(int size); + +private: + GrDawnGpu* fGpu; + dawn::BufferUsage fUsage; + dawn::Buffer fBuffer; + int fOffset = 0; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.cpp new file mode 100644 index 0000000000..68e0e9785f --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnStagingManager.h" + +#include "src/core/SkMathPriv.h" + +GrDawnStagingManager::GrDawnStagingManager(dawn::Device device) : fDevice(device) { +} + +GrDawnStagingManager::~GrDawnStagingManager() { + // Clean up any pending callbacks before destroying the StagingBuffers. + while (fWaitingCount > 0) { + fDevice.Tick(); + } +} + +GrDawnStagingBuffer* GrDawnStagingManager::findOrCreateStagingBuffer(size_t size) { + size_t sizePow2 = GrNextPow2(size); + GrDawnStagingBuffer* stagingBuffer; + auto i = fReadyPool.find(sizePow2); + if (i != fReadyPool.end()) { + stagingBuffer = i->second; + fReadyPool.erase(i); + } else { + dawn::BufferDescriptor desc; + desc.usage = dawn::BufferUsage::MapWrite | dawn::BufferUsage::CopySrc; + desc.size = sizePow2; + dawn::CreateBufferMappedResult result = fDevice.CreateBufferMapped(&desc); + std::unique_ptr b(new GrDawnStagingBuffer( + this, result.buffer, sizePow2, result.data)); + stagingBuffer = b.get(); + fBuffers.push_back(std::move(b)); + } + fBusyList.push_back(stagingBuffer); + return stagingBuffer; +} + +static void callback(DawnBufferMapAsyncStatus status, void* data, uint64_t dataLength, + void* userData) { + GrDawnStagingBuffer* buffer = static_cast(userData); + buffer->fData = data; + if (buffer->fManager) { + buffer->fManager->addToReadyPool(buffer); + } +} + +void GrDawnStagingManager::mapBusyList() { + // Map all buffers on the busy list for writing. When they're no longer in flight on the GPU, + // their callback will be called and they'll be moved to the ready pool. + for (GrDawnStagingBuffer* buffer : fBusyList) { + buffer->fBuffer.MapWriteAsync(callback, buffer); + fWaitingCount++; + } + fBusyList.clear(); +} + +void GrDawnStagingManager::addToReadyPool(GrDawnStagingBuffer* buffer) { + fWaitingCount--; + fReadyPool.insert(std::pair(buffer->fSize, buffer)); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.h b/gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.h new file mode 100644 index 0000000000..b3974ada6c --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnStagingManager.h @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnStagingManager_DEFINED +#define GrDawnStagingManager_DEFINED + +#include +#include +#include + +#include "dawn/dawncpp.h" + +struct GrDawnStagingBuffer; + +class GrDawnStagingManager { +public: + GrDawnStagingManager(dawn::Device device); + ~GrDawnStagingManager(); + GrDawnStagingBuffer* findOrCreateStagingBuffer(size_t size); + + void addToReadyPool(GrDawnStagingBuffer* buffer); + void mapBusyList(); + +private: + dawn::Device fDevice; + std::vector> fBuffers; + std::multimap fReadyPool; + std::vector fBusyList; + int fWaitingCount = 0; +}; + +struct GrDawnStagingBuffer { + GrDawnStagingBuffer(GrDawnStagingManager* manager, dawn::Buffer buffer, size_t size, + void* data) + : fManager(manager), fBuffer(buffer), fSize(size), fData(data) {} + ~GrDawnStagingBuffer() { + fManager = nullptr; + } + GrDawnStagingManager* fManager; + dawn::Buffer fBuffer; + size_t fSize; + void* fData; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.cpp new file mode 100644 index 0000000000..7e4047a146 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnStencilAttachment.h" + +#include "src/gpu/dawn/GrDawnGpu.h" +#include "src/gpu/dawn/GrDawnUtil.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +GrDawnStencilAttachment::GrDawnStencilAttachment(GrDawnGpu* gpu, + int width, + int height, + int bits, + int samples, + dawn::Texture texture, + dawn::TextureView view) + : INHERITED(gpu, width, height, bits, samples) + , fTexture(texture) + , fView(view) { + this->registerWithCache(SkBudgeted::kYes); +} + +GrDawnStencilAttachment* GrDawnStencilAttachment::Create(GrDawnGpu* gpu, + int width, + int height, + int sampleCnt) { + dawn::TextureDescriptor desc; + desc.usage = dawn::TextureUsage::OutputAttachment; + desc.size.width = width; + desc.size.height = height; + desc.size.depth = 1; + desc.format = dawn::TextureFormat::Depth24PlusStencil8; + dawn::Texture texture = gpu->device().CreateTexture(&desc); + if (!texture) { + return nullptr; + } + dawn::TextureView view = texture.CreateView(); + if (!view) { + return nullptr; + } + return new GrDawnStencilAttachment(gpu, width, height, 8, sampleCnt, texture, view); +} + +GrDawnStencilAttachment::~GrDawnStencilAttachment() { +} + +size_t GrDawnStencilAttachment::onGpuMemorySize() const { + uint64_t size = this->width(); + size *= this->height(); + size *= 32; + size *= SkTMax(1,this->numSamples()); + return static_cast(size / 8); +} + +void GrDawnStencilAttachment::onRelease() { + GrStencilAttachment::onRelease(); +} + +void GrDawnStencilAttachment::onAbandon() { + GrStencilAttachment::onAbandon(); +} + +GrDawnGpu* GrDawnStencilAttachment::getDawnGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.h b/gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.h new file mode 100644 index 0000000000..b0afc23585 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnStencilAttachment.h @@ -0,0 +1,43 @@ +/* +* Copyright 2019 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrDawnStencil_DEFINED +#define GrDawnStencil_DEFINED + +#include "src/gpu/GrStencilAttachment.h" + +#include "dawn/dawncpp.h" + +class GrDawnGpu; + +class GrDawnStencilAttachment : public GrStencilAttachment { +public: + static GrDawnStencilAttachment* Create(GrDawnGpu* gpu, int width, int height, + int sampleCnt); + + ~GrDawnStencilAttachment() override; + dawn::TextureView view() const { return fView; } + +protected: + void onRelease() override; + void onAbandon() override; + +private: + size_t onGpuMemorySize() const override; + + GrDawnStencilAttachment(GrDawnGpu* gpu, int width, int height, int bits, int samples, + dawn::Texture texture, dawn::TextureView view); + + GrDawnGpu* getDawnGpu() const; + + dawn::Texture fTexture; + dawn::TextureView fView; + + typedef GrStencilAttachment INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnTexture.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnTexture.cpp new file mode 100644 index 0000000000..aa7f60755b --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnTexture.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnTexture.h" + +#include "src/gpu/dawn/GrDawnGpu.h" +#include "src/gpu/dawn/GrDawnTextureRenderTarget.h" +#include "src/gpu/dawn/GrDawnUtil.h" + +GrDawnTexture::GrDawnTexture(GrDawnGpu* gpu, + const SkISize& size, + GrPixelConfig config, + dawn::TextureView textureView, + const GrDawnImageInfo& info, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, size, config, GrProtected::kNo) + , GrTexture(gpu, size, config, GrProtected::kNo, GrTextureType::k2D, mipMapsStatus) + , fInfo(info) + , fTextureView(textureView) { +} + +sk_sp GrDawnTexture::Make(GrDawnGpu* gpu, const SkISize& size, GrPixelConfig config, + dawn::TextureFormat format, GrRenderable renderable, + int sampleCnt, SkBudgeted budgeted, int mipLevels, + GrMipMapsStatus status) { + bool renderTarget = renderable == GrRenderable::kYes; + dawn::TextureDescriptor textureDesc; + + textureDesc.usage = + dawn::TextureUsage::Sampled | + dawn::TextureUsage::CopySrc | + dawn::TextureUsage::CopyDst; + + if (renderTarget) { + textureDesc.usage |= dawn::TextureUsage::OutputAttachment; + } + + textureDesc.size.width = size.fWidth; + textureDesc.size.height = size.fHeight; + textureDesc.size.depth = 1; + textureDesc.format = format; + textureDesc.mipLevelCount = std::max(mipLevels, 1); + textureDesc.sampleCount = sampleCnt; + + dawn::Texture tex = gpu->device().CreateTexture(&textureDesc); + + if (!tex) { + return nullptr; + } + + dawn::TextureView textureView = tex.CreateView(); + + if (!textureView) { + return nullptr; + } + + GrDawnImageInfo info; + info.fTexture = tex; + info.fFormat = textureDesc.format; + info.fLevelCount = mipLevels; + sk_sp result; + if (renderTarget) { + result = sk_sp(new GrDawnTextureRenderTarget(gpu, + size, + config, + textureView, + sampleCnt, + info, + status)); + } else { + result = sk_sp(new GrDawnTexture(gpu, size, config, textureView, info, + status)); + } + result->registerWithCache(budgeted); + return result; +} + +GrBackendFormat GrDawnTexture::backendFormat() const { + return GrBackendFormat::MakeDawn(fInfo.fFormat); +} + +sk_sp GrDawnTexture::MakeWrapped(GrDawnGpu* gpu, const SkISize& size, + GrPixelConfig config, GrRenderable renderable, + int sampleCnt, GrMipMapsStatus status, + GrWrapCacheable cacheable, + const GrDawnImageInfo& info) { + dawn::TextureView textureView = info.fTexture.CreateView(); + if (!textureView) { + return nullptr; + } + + sk_sp tex; + if (GrRenderable::kYes == renderable) { + tex = sk_sp(new GrDawnTextureRenderTarget(gpu, size, config, textureView, + sampleCnt, info, status)); + } else { + tex = sk_sp(new GrDawnTexture(gpu, size, config, textureView, info, status)); + } + tex->registerWithCacheWrapped(cacheable); + return tex; +} + +GrDawnTexture::~GrDawnTexture() { +} + +GrDawnGpu* GrDawnTexture::getDawnGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +void GrDawnTexture::onRelease() { + INHERITED::onRelease(); +} + +void GrDawnTexture::onAbandon() { + INHERITED::onAbandon(); +} + +GrBackendTexture GrDawnTexture::getBackendTexture() const { + return GrBackendTexture(this->width(), this->height(), fInfo); +} + +void GrDawnTexture::upload(const GrMipLevel texels[], int mipLevels, + dawn::CommandEncoder copyEncoder) { + this->upload(texels, mipLevels, SkIRect::MakeWH(width(), height()), copyEncoder); +} + +void GrDawnTexture::upload(const GrMipLevel texels[], int mipLevels, const SkIRect& rect, + dawn::CommandEncoder copyEncoder) { + dawn::Device device = this->getDawnGpu()->device(); + + uint32_t x = rect.x(); + uint32_t y = rect.y(); + uint32_t width = rect.width(); + uint32_t height = rect.height(); + + for (int i = 0; i < mipLevels; i++) { + size_t origRowBytes = texels[i].fRowBytes; + SkBitmap bitmap; + SkPixmap pixmap; + const char* src; + if (kRGBA_4444_GrPixelConfig == this->config() || + kRGB_565_GrPixelConfig == this->config() || + kGray_8_GrPixelConfig == this->config()) { + SkImageInfo info; + info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); + SkImageInfo srcInfo; + SkColorType colorType = + GrColorTypeToSkColorType(GrPixelConfigToColorType(this->config())); + srcInfo = SkImageInfo::Make(width, height, colorType, kOpaque_SkAlphaType); + SkPixmap srcPixmap(srcInfo, texels[i].fPixels, origRowBytes); + origRowBytes = GrDawnRoundRowBytes(info.minRowBytes()); + bitmap.allocPixels(info, origRowBytes); + bitmap.writePixels(srcPixmap); + if (!bitmap.peekPixels(&pixmap)) { + continue; + } + src = static_cast(pixmap.addr()); + } else { + src = static_cast(texels[i].fPixels); + } + size_t rowBytes = GrDawnRoundRowBytes(origRowBytes); + size_t size = rowBytes * height; + GrDawnStagingBuffer* stagingBuffer = getDawnGpu()->getStagingBuffer(size); + if (rowBytes == origRowBytes) { + memcpy(stagingBuffer->fData, src, size); + } else { + char* dst = static_cast(stagingBuffer->fData); + for (uint32_t row = 0; row < height; row++) { + memcpy(dst, src, origRowBytes); + dst += rowBytes; + src += texels[i].fRowBytes; + } + } + dawn::Buffer buffer = stagingBuffer->fBuffer; + buffer.Unmap(); + stagingBuffer->fData = nullptr; + + dawn::BufferCopyView srcBuffer; + srcBuffer.buffer = buffer; + srcBuffer.offset = 0; + srcBuffer.rowPitch = rowBytes; + srcBuffer.imageHeight = height; + + dawn::TextureCopyView dstTexture; + dstTexture.texture = fInfo.fTexture; + dstTexture.mipLevel = i; + dstTexture.origin = {x, y, 0}; + + dawn::Extent3D copySize = {width, height, 1}; + copyEncoder.CopyBufferToTexture(&srcBuffer, &dstTexture, ©Size); + x /= 2; + y /= 2; + width /= 2; + height /= 2; + } +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnTexture.h b/gfx/skia/skia/src/gpu/dawn/GrDawnTexture.h new file mode 100644 index 0000000000..cc69fd7d9f --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnTexture.h @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnTexture_DEFINED +#define GrDawnTexture_DEFINED + +#include "include/gpu/GrTexture.h" +#include "dawn/dawncpp.h" + +class GrDawnGpu; +struct GrDawnImageInfo; + +class GrDawnTexture : public GrTexture { +public: + static sk_sp Make(GrDawnGpu*, const SkISize& size, GrPixelConfig config, + dawn::TextureFormat format, GrRenderable, int sampleCnt, + SkBudgeted, int mipLevels, GrMipMapsStatus); + + static sk_sp MakeWrapped(GrDawnGpu*, const SkISize& size, GrPixelConfig config, + GrRenderable, int sampleCnt, + GrMipMapsStatus, GrWrapCacheable, + const GrDawnImageInfo&); + + ~GrDawnTexture() override; + + GrBackendTexture getBackendTexture() const override; + GrBackendFormat backendFormat() const override; + + void textureParamsModified() override {} + + void upload(const GrMipLevel texels[], int mipLevels, dawn::CommandEncoder copyEncoder); + void upload(const GrMipLevel texels[], int mipLevels, const SkIRect& dstRect, + dawn::CommandEncoder copyEncoder); + + dawn::Texture texture() const { return fInfo.fTexture; } + dawn::TextureView textureView() const { return fTextureView; } +protected: + GrDawnTexture(GrDawnGpu*, const SkISize& size, GrPixelConfig config, + dawn::TextureView, const GrDawnImageInfo&, GrMipMapsStatus); + + GrDawnGpu* getDawnGpu() const; + + void onAbandon() override; + void onRelease() override; + + bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override { + return false; + } + +private: + GrDawnTexture(GrDawnGpu*, const GrSurfaceDesc&, const GrDawnImageInfo&, GrMipMapsStatus); + + GrDawnImageInfo fInfo; + dawn::TextureView fTextureView; + + typedef GrTexture INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.cpp new file mode 100644 index 0000000000..a9e70d71ba --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnTextureRenderTarget.h" + +#include "include/core/SkTraceMemoryDump.h" +#include "include/gpu/GrContext.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/dawn/GrDawnGpu.h" + +GrDawnTextureRenderTarget::GrDawnTextureRenderTarget(GrDawnGpu* gpu, + const SkISize& size, + GrPixelConfig config, + dawn::TextureView textureView, + int sampleCnt, + const GrDawnImageInfo& info, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, size, config, GrProtected::kNo) + , GrDawnTexture(gpu, size, config, textureView, info, mipMapsStatus) + , GrDawnRenderTarget(gpu, size, config, sampleCnt, info) { +} + +bool GrDawnTextureRenderTarget::canAttemptStencilAttachment() const { + return true; +} + +size_t GrDawnTextureRenderTarget::onGpuMemorySize() const { + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + 1, // FIXME: for MSAA + this->texturePriv().mipMapped()); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.h b/gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.h new file mode 100644 index 0000000000..69c94b034e --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnTextureRenderTarget.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnTextureRenderTarget_DEFINED +#define GrDawnTextureRenderTarget_DEFINED + +#include "src/gpu/dawn/GrDawnRenderTarget.h" +#include "src/gpu/dawn/GrDawnTexture.h" + +class GrDawnGpu; + +#ifdef SK_BUILD_FOR_WIN +// Windows gives bogus warnings about inheriting asTexture/asRenderTarget via dominance. +#pragma warning(push) +#pragma warning(disable: 4250) +#endif + +class GrDawnTextureRenderTarget : public GrDawnTexture, public GrDawnRenderTarget { +public: + GrDawnTextureRenderTarget(GrDawnGpu* gpu, + const SkISize& size, + GrPixelConfig config, + const dawn::TextureView textureView, + int sampleCnt, + const GrDawnImageInfo& info, + GrMipMapsStatus mipMapsStatus); + + bool canAttemptStencilAttachment() const override; + + GrBackendFormat backendFormat() const override { return GrDawnTexture::backendFormat(); } + +protected: + void onAbandon() override { + GrDawnRenderTarget::onAbandon(); + GrDawnTexture::onAbandon(); + } + + void onRelease() override { + GrDawnRenderTarget::onRelease(); + GrDawnTexture::onRelease(); + } + +private: + size_t onGpuMemorySize() const override; +}; + +#ifdef SK_BUILD_FOR_WIN +#pragma warning(pop) +#endif + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.cpp new file mode 100644 index 0000000000..74fd1e31a4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.cpp @@ -0,0 +1,326 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnUniformHandler.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" + +GrDawnUniformHandler::GrDawnUniformHandler(GrGLSLProgramBuilder* program) + : INHERITED(program) + , fUniforms(kUniformsPerBlock) + , fSamplers(kUniformsPerBlock) + , fTextures(kUniformsPerBlock) +{ +} + +const GrShaderVar& GrDawnUniformHandler::getUniformVariable(UniformHandle u) const { + return fUniforms[u.toIndex()].fVar; +} + +const char* GrDawnUniformHandler::getUniformCStr(UniformHandle u) const { + return fUniforms[u.toIndex()].fVar.getName().c_str(); +} + +// FIXME: this code was ripped from GrVkUniformHandler; should be refactored. +namespace { + +uint32_t grsltype_to_alignment_mask(GrSLType type) { + switch(type) { + case kByte_GrSLType: // fall through + case kUByte_GrSLType: + return 0x0; + case kByte2_GrSLType: // fall through + case kUByte2_GrSLType: + return 0x1; + case kByte3_GrSLType: // fall through + case kByte4_GrSLType: + case kUByte3_GrSLType: + case kUByte4_GrSLType: + return 0x3; + case kShort_GrSLType: // fall through + case kUShort_GrSLType: + return 0x1; + case kShort2_GrSLType: // fall through + case kUShort2_GrSLType: + return 0x3; + case kShort3_GrSLType: // fall through + case kShort4_GrSLType: + case kUShort3_GrSLType: + case kUShort4_GrSLType: + return 0x7; + case kInt_GrSLType: + case kUint_GrSLType: + return 0x3; + case kHalf_GrSLType: // fall through + case kFloat_GrSLType: + return 0x3; + case kHalf2_GrSLType: // fall through + case kFloat2_GrSLType: + return 0x7; + case kHalf3_GrSLType: // fall through + case kFloat3_GrSLType: + return 0xF; + case kHalf4_GrSLType: // fall through + case kFloat4_GrSLType: + return 0xF; + case kUint2_GrSLType: + return 0x7; + case kInt2_GrSLType: + return 0x7; + case kInt3_GrSLType: + return 0xF; + case kInt4_GrSLType: + return 0xF; + case kHalf2x2_GrSLType: // fall through + case kFloat2x2_GrSLType: + return 0x7; + case kHalf3x3_GrSLType: // fall through + case kFloat3x3_GrSLType: + return 0xF; + case kHalf4x4_GrSLType: // fall through + case kFloat4x4_GrSLType: + return 0xF; + + // This query is only valid for certain types. + case kVoid_GrSLType: + case kBool_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kTexture2D_GrSLType: + case kSampler_GrSLType: + break; + } + SK_ABORT("Unexpected type"); +} + +static inline uint32_t grsltype_to_size(GrSLType type) { + switch(type) { + case kByte_GrSLType: + case kUByte_GrSLType: + return 1; + case kByte2_GrSLType: + case kUByte2_GrSLType: + return 2; + case kByte3_GrSLType: + case kUByte3_GrSLType: + return 3; + case kByte4_GrSLType: + case kUByte4_GrSLType: + return 4; + case kShort_GrSLType: + return sizeof(int16_t); + case kShort2_GrSLType: + return 2 * sizeof(int16_t); + case kShort3_GrSLType: + return 3 * sizeof(int16_t); + case kShort4_GrSLType: + return 4 * sizeof(int16_t); + case kUShort_GrSLType: + return sizeof(uint16_t); + case kUShort2_GrSLType: + return 2 * sizeof(uint16_t); + case kUShort3_GrSLType: + return 3 * sizeof(uint16_t); + case kUShort4_GrSLType: + return 4 * sizeof(uint16_t); + case kInt_GrSLType: + return sizeof(int32_t); + case kUint_GrSLType: + return sizeof(int32_t); + case kHalf_GrSLType: // fall through + case kFloat_GrSLType: + return sizeof(float); + case kHalf2_GrSLType: // fall through + case kFloat2_GrSLType: + return 2 * sizeof(float); + case kHalf3_GrSLType: // fall through + case kFloat3_GrSLType: + return 3 * sizeof(float); + case kHalf4_GrSLType: // fall through + case kFloat4_GrSLType: + return 4 * sizeof(float); + case kUint2_GrSLType: + return 2 * sizeof(uint32_t); + case kInt2_GrSLType: + return 2 * sizeof(int32_t); + case kInt3_GrSLType: + return 3 * sizeof(int32_t); + case kInt4_GrSLType: + return 4 * sizeof(int32_t); + case kHalf2x2_GrSLType: // fall through + case kFloat2x2_GrSLType: + //TODO: this will be 4 * szof(float) on std430. + return 8 * sizeof(float); + case kHalf3x3_GrSLType: // fall through + case kFloat3x3_GrSLType: + return 12 * sizeof(float); + case kHalf4x4_GrSLType: // fall through + case kFloat4x4_GrSLType: + return 16 * sizeof(float); + + // This query is only valid for certain types. + case kVoid_GrSLType: + case kBool_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kTexture2D_GrSLType: + case kSampler_GrSLType: + break; + } + SK_ABORT("Unexpected type"); +} + +uint32_t get_ubo_offset(uint32_t* currentOffset, + GrSLType type, + int arrayCount) { + uint32_t alignmentMask = grsltype_to_alignment_mask(type); + // We want to use the std140 layout here, so we must make arrays align to 16 bytes. + if (arrayCount || type == kFloat2x2_GrSLType) { + alignmentMask = 0xF; + } + uint32_t offsetDiff = *currentOffset & alignmentMask; + if (offsetDiff != 0) { + offsetDiff = alignmentMask - offsetDiff + 1; + } + uint32_t uniformOffset = *currentOffset + offsetDiff; + SkASSERT(sizeof(float) == 4); + if (arrayCount) { + uint32_t elementSize = SkTMax(16, grsltype_to_size(type)); + SkASSERT(0 == (elementSize & 0xF)); + *currentOffset = uniformOffset + elementSize * arrayCount; + } else { + *currentOffset = uniformOffset + grsltype_to_size(type); + } + return uniformOffset; +} + +} + +GrGLSLUniformHandler::UniformHandle GrDawnUniformHandler::internalAddUniformArray( + uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) { + UniformInfo& info = fUniforms.push_back(); + info.fVisibility = visibility; + if (visibility == kFragment_GrShaderFlag) { + info.fUBOOffset = get_ubo_offset(&fCurrentFragmentUBOOffset, type, arrayCount); + } else { + info.fUBOOffset = get_ubo_offset(&fCurrentGeometryUBOOffset, type, arrayCount); + } + GrShaderVar& var = info.fVar; + char prefix = 'u'; + if ('u' == name[0] || !strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX))) { + prefix = '\0'; + } + fProgramBuilder->nameVariable(var.accessName(), prefix, name, mangleName); + var.setType(type); + var.setTypeModifier(GrShaderVar::kNone_TypeModifier); + var.setArrayCount(arrayCount); + SkString layoutQualifier; + layoutQualifier.appendf("offset = %d", info.fUBOOffset); + var.addLayoutQualifier(layoutQualifier.c_str()); + if (outName) { + *outName = var.c_str(); + } + return GrGLSLUniformHandler::UniformHandle(fUniforms.count() - 1); +} + +void GrDawnUniformHandler::updateUniformVisibility(UniformHandle u, uint32_t visibility) { + fUniforms[u.toIndex()].fVisibility |= visibility; +} + +GrGLSLUniformHandler::SamplerHandle GrDawnUniformHandler::addSampler(const GrTextureProxy*, + const GrSamplerState&, + const GrSwizzle& swizzle, + const char* name, + const GrShaderCaps* caps) { + SkString mangleName; + char prefix = 's'; + fProgramBuilder->nameVariable(&mangleName, prefix, name, true); + + GrSLType samplerType = kSampler_GrSLType, textureType = kTexture2D_GrSLType; + int binding = kSamplerBindingBase + fSamplers.count() * 2; + UniformInfo& info = fSamplers.push_back(); + info.fVar.setType(samplerType); + info.fVar.setTypeModifier(GrShaderVar::kUniform_TypeModifier); + info.fVar.setName(mangleName); + SkString layoutQualifier; + layoutQualifier.appendf("set = 0, binding = %d", binding); + info.fVar.addLayoutQualifier(layoutQualifier.c_str()); + info.fVisibility = kFragment_GrShaderFlag; + info.fUBOOffset = 0; + fSamplerSwizzles.push_back(swizzle); + SkASSERT(fSamplerSwizzles.count() == fSamplers.count()); + + SkString mangleTexName; + char texPrefix = 't'; + fProgramBuilder->nameVariable(&mangleTexName, texPrefix, name, true); + UniformInfo& texInfo = fTextures.push_back(); + texInfo.fVar.setType(textureType); + texInfo.fVar.setTypeModifier(GrShaderVar::kUniform_TypeModifier); + texInfo.fVar.setName(mangleTexName); + SkString texLayoutQualifier; + texLayoutQualifier.appendf("set = 0, binding = %d", binding + 1); + texInfo.fVar.addLayoutQualifier(texLayoutQualifier.c_str()); + texInfo.fVisibility = kFragment_GrShaderFlag; + texInfo.fUBOOffset = 0; + + SkString reference; + reference.printf("makeSampler2D(%s, %s)", texInfo.fVar.getName().c_str(), + info.fVar.getName().c_str()); + fSamplerReferences.push_back() = reference; + return GrGLSLUniformHandler::SamplerHandle(fSamplers.count() - 1); +} + +const char* GrDawnUniformHandler::samplerVariable( + GrGLSLUniformHandler::SamplerHandle handle) const { + return fSamplerReferences[handle.toIndex()].c_str(); +} + +GrSwizzle GrDawnUniformHandler::samplerSwizzle(GrGLSLUniformHandler::SamplerHandle handle) const { + return fSamplerSwizzles[handle.toIndex()]; +} + +void GrDawnUniformHandler::appendUniformDecls(GrShaderFlags visibility, SkString* out) const { + for (int i = 0; i < fSamplers.count(); ++i) { + if (fSamplers[i].fVisibility & visibility) { + fSamplers[i].fVar.appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";\n"); + fTextures[i].fVar.appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";\n"); + } + } + SkString uniformsString; + for (int i = 0; i < fUniforms.count(); ++i) { + if (fUniforms[i].fVisibility & visibility) { + fUniforms[i].fVar.appendDecl(fProgramBuilder->shaderCaps(), &uniformsString); + uniformsString.append(";\n"); + } + } + if (!uniformsString.isEmpty()) { + uint32_t uniformBinding; + const char* stage; + if (kVertex_GrShaderFlag == visibility) { + uniformBinding = kGeometryBinding; + stage = "vertex"; + } else if (kGeometry_GrShaderFlag == visibility) { + uniformBinding = kGeometryBinding; + stage = "geometry"; + } else { + SkASSERT(kFragment_GrShaderFlag == visibility); + uniformBinding = kFragBinding; + stage = "fragment"; + } + out->appendf("layout (set = 0, binding = %d) uniform %sUniformBuffer\n{\n", + uniformBinding, stage); + out->appendf("%s\n};\n", uniformsString.c_str()); + } +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.h b/gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.h new file mode 100644 index 0000000000..e5d9950253 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnUniformHandler.h @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnUniformHandler_DEFINED +#define GrDawnUniformHandler_DEFINED + +#include "src/gpu/GrAllocator.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrDawnGpu; + +class GrDawnUniformHandler : public GrGLSLUniformHandler { +public: + static const int kUniformsPerBlock = 8; + + const GrShaderVar& getUniformVariable(UniformHandle u) const override; + const char* getUniformCStr(UniformHandle u) const override; + + struct UniformInfo { + GrShaderVar fVar; + int fUBOOffset; + int fVisibility; + }; + typedef GrTAllocator UniformInfoArray; + enum { + kGeometryBinding = 0, + kFragBinding = 1, + kSamplerBindingBase = 2, + }; + +private: + explicit GrDawnUniformHandler(GrGLSLProgramBuilder* program); + + SamplerHandle addSampler(const GrTextureProxy*, const GrSamplerState&, const GrSwizzle&, + const char* name, const GrShaderCaps*) override; + const char* samplerVariable(SamplerHandle handle) const override; + GrSwizzle samplerSwizzle(SamplerHandle handle) const override; + void appendUniformDecls(GrShaderFlags visibility, SkString*) const override; + UniformHandle internalAddUniformArray(uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) override; + + void updateUniformVisibility(UniformHandle u, uint32_t visibility) override; + + UniformInfoArray fUniforms; + UniformInfoArray fSamplers; + UniformInfoArray fTextures; + SkTArray fSamplerSwizzles; + SkTArray fSamplerReferences; + + uint32_t fCurrentGeometryUBOOffset = 0; + uint32_t fCurrentFragmentUBOOffset = 0; + + friend class GrDawnProgramBuilder; + typedef GrGLSLUniformHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnUtil.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnUtil.cpp new file mode 100644 index 0000000000..7404dac008 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnUtil.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnUtil.h" + +size_t GrDawnBytesPerPixel(dawn::TextureFormat format) { + switch (format) { + case dawn::TextureFormat::RGBA8Unorm: + case dawn::TextureFormat::BGRA8Unorm: + return 4; + case dawn::TextureFormat::R8Unorm: + return 1; + case dawn::TextureFormat::Depth24PlusStencil8: + return 4; + default: + SkASSERT(false); + return 4; + } +} + +bool GrDawnFormatIsRenderable(dawn::TextureFormat format) { + // For now, all the formats above are renderable. If a non-renderable format is added + // (see dawn/src/dawn_native/Format.cpp), an exception should be added here. + return true; +} + +bool GrPixelConfigToDawnFormat(GrPixelConfig config, dawn::TextureFormat* format) { + switch (config) { + case kRGBA_8888_GrPixelConfig: + case kRGBA_4444_GrPixelConfig: + case kRGB_565_GrPixelConfig: + case kGray_8_GrPixelConfig: + *format = dawn::TextureFormat::RGBA8Unorm; + return true; + case kBGRA_8888_GrPixelConfig: + *format = dawn::TextureFormat::BGRA8Unorm; + return true; + case kAlpha_8_GrPixelConfig: + case kAlpha_8_as_Red_GrPixelConfig: + *format = dawn::TextureFormat::R8Unorm; + return true; + default: + return false; + } +} + +size_t GrDawnRoundRowBytes(size_t rowBytes) { + // Dawn requires that rowBytes be a multiple of 256. (This is actually imposed by D3D12.) + return (rowBytes + 0xFF) & ~0xFF; +} + +#if GR_TEST_UTILS +const char* GrDawnFormatToStr(dawn::TextureFormat format) { + switch (format) { + case dawn::TextureFormat::RGBA8Unorm: + return "RGBA8Unorm"; + case dawn::TextureFormat::BGRA8Unorm: + return "BGRA8Unorm"; + case dawn::TextureFormat::R8Unorm: + return "R8Unorm"; + case dawn::TextureFormat::Depth24PlusStencil8: + return "Depth24PlusStencil8"; + default: + SkASSERT(false); + return "Unknown"; + } +} +#endif diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnUtil.h b/gfx/skia/skia/src/gpu/dawn/GrDawnUtil.h new file mode 100644 index 0000000000..0e5a41bade --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnUtil.h @@ -0,0 +1,22 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnUtil_DEFINED +#define GrDawnUtil_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "dawn/dawncpp.h" + +size_t GrDawnBytesPerPixel(dawn::TextureFormat format); +bool GrDawnFormatIsRenderable(dawn::TextureFormat format); +bool GrPixelConfigToDawnFormat(GrPixelConfig config, dawn::TextureFormat* format); +size_t GrDawnRoundRowBytes(size_t rowBytes); +#if GR_TEST_UTILS +const char* GrDawnFormatToStr(dawn::TextureFormat format); +#endif + +#endif // GrDawnUtil_DEFINED diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.cpp b/gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.cpp new file mode 100644 index 0000000000..b3885d20c9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/dawn/GrDawnVaryingHandler.h" + +/** Returns the number of locations take up by a given GrSLType. We assume that all + scalar values are 32 bits. */ +static inline int grsltype_to_location_size(GrSLType type) { + switch(type) { + case kVoid_GrSLType: + return 0; + case kFloat_GrSLType: // fall through + case kHalf_GrSLType: + return 1; + case kFloat2_GrSLType: // fall through + case kHalf2_GrSLType: + return 1; + case kFloat3_GrSLType: + case kHalf3_GrSLType: + return 1; + case kFloat4_GrSLType: + case kHalf4_GrSLType: + return 1; + case kUint2_GrSLType: + return 1; + case kInt2_GrSLType: + case kShort2_GrSLType: + case kUShort2_GrSLType: + case kByte2_GrSLType: + case kUByte2_GrSLType: + return 1; + case kInt3_GrSLType: + case kShort3_GrSLType: + case kUShort3_GrSLType: + case kByte3_GrSLType: + case kUByte3_GrSLType: + return 1; + case kInt4_GrSLType: + case kShort4_GrSLType: + case kUShort4_GrSLType: + case kByte4_GrSLType: + case kUByte4_GrSLType: + return 1; + case kFloat2x2_GrSLType: + case kHalf2x2_GrSLType: + return 2; + case kFloat3x3_GrSLType: + case kHalf3x3_GrSLType: + return 3; + case kFloat4x4_GrSLType: + case kHalf4x4_GrSLType: + return 4; + case kTexture2DSampler_GrSLType: + return 0; + case kTextureExternalSampler_GrSLType: + return 0; + case kTexture2DRectSampler_GrSLType: + return 0; + case kBool_GrSLType: + return 1; + case kInt_GrSLType: // fall through + case kShort_GrSLType: + case kByte_GrSLType: + return 1; + case kUint_GrSLType: // fall through + case kUShort_GrSLType: + case kUByte_GrSLType: + return 1; + case kTexture2D_GrSLType: + return 0; + case kSampler_GrSLType: + return 0; + } + SK_ABORT("Unexpected type"); +} + +static void finalize_helper(GrDawnVaryingHandler::VarArray& vars) { + int locationIndex = 0; + for (int i = 0; i < vars.count(); ++i) { + GrShaderVar& var = vars[i]; + SkString location; + location.appendf("location = %d", locationIndex); + var.addLayoutQualifier(location.c_str()); + + int elementSize = grsltype_to_location_size(var.getType()); + SkASSERT(elementSize > 0); + int numElements = 1; + if (var.isArray() && !var.isUnsizedArray()) { + numElements = var.getArrayCount(); + } + SkASSERT(numElements > 0); + locationIndex += elementSize * numElements; + } + // TODO: determine the layout limits for Dawn, and enforce them via asserts here. +} + +void GrDawnVaryingHandler::onFinalize() { + finalize_helper(fVertexInputs); + finalize_helper(fVertexOutputs); + finalize_helper(fGeomInputs); + finalize_helper(fGeomOutputs); + finalize_helper(fFragInputs); + finalize_helper(fFragOutputs); +} diff --git a/gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.h b/gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.h new file mode 100644 index 0000000000..d0d0ecd9d3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/dawn/GrDawnVaryingHandler.h @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDawnVaryingHandler_DEFINED +#define GrDawnVaryingHandler_DEFINED + +#include "src/gpu/glsl/GrGLSLVarying.h" + +class GrDawnVaryingHandler : public GrGLSLVaryingHandler { +public: + GrDawnVaryingHandler(GrGLSLProgramBuilder* program) : INHERITED(program) {} + + typedef GrGLSLVaryingHandler::VarArray VarArray; + +private: + void onFinalize() override; + + friend class GrDawnPipelineStateBuilder; + + typedef GrGLSLVaryingHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrAARectEffect.fp b/gfx/skia/skia/src/gpu/effects/GrAARectEffect.fp new file mode 100644 index 0000000000..856b761a0f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrAARectEffect.fp @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +layout(key) in GrClipEdgeType edgeType; +layout(ctype=SkRect) in float4 rect; +layout(ctype=SkRect) float4 prevRect = float4(-1); +uniform float4 rectUniform; + +@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag } + +void main() { + half alpha; + @switch (edgeType) { + case GrClipEdgeType::kFillBW: // fall through + case GrClipEdgeType::kInverseFillBW: + // non-AA + alpha = all(greaterThan(float4(sk_FragCoord.xy, rectUniform.zw), + float4(rectUniform.xy, sk_FragCoord.xy))) ? 1 : 0; + break; + default: + // The amount of coverage removed in x and y by the edges is computed as a pair of + // negative numbers, xSub and ySub. + half xSub, ySub; + xSub = min(half(sk_FragCoord.x - rectUniform.x), 0.0); + xSub += min(half(rectUniform.z - sk_FragCoord.x), 0.0); + ySub = min(half(sk_FragCoord.y - rectUniform.y), 0.0); + ySub += min(half(rectUniform.w - sk_FragCoord.y), 0.0); + // Now compute coverage in x and y and multiply them to get the fraction of the pixel + // covered. + alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0)); + } + + @if (edgeType == GrClipEdgeType::kInverseFillBW || edgeType == GrClipEdgeType::kInverseFillAA) { + alpha = 1.0 - alpha; + } + sk_OutColor = sk_InColor * alpha; +} + +@setData(pdman) { + const SkRect& newRect = GrProcessorEdgeTypeIsAA(edgeType) ? + rect.makeInset(.5f, .5f) : rect; + if (newRect != prevRect) { + pdman.set4f(rectUniform, newRect.fLeft, newRect.fTop, newRect.fRight, newRect.fBottom); + prevRect = newRect; + } +} + +@test(d) { + SkRect rect = SkRect::MakeLTRB(d->fRandom->nextSScalar1(), + d->fRandom->nextSScalar1(), + d->fRandom->nextSScalar1(), + d->fRandom->nextSScalar1()); + std::unique_ptr fp; + do { + GrClipEdgeType edgeType = static_cast( + d->fRandom->nextULessThan(kGrClipEdgeTypeCnt)); + + fp = GrAARectEffect::Make(edgeType, rect); + } while (nullptr == fp); + return fp; +} diff --git a/gfx/skia/skia/src/gpu/effects/GrAlphaThresholdFragmentProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrAlphaThresholdFragmentProcessor.fp new file mode 100644 index 0000000000..cab332bd9c --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrAlphaThresholdFragmentProcessor.fp @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in uniform sampler2D mask; +in uniform half innerThreshold; +in uniform half outerThreshold; + +@class { + inline OptimizationFlags optFlags(float outerThreshold); +} + +@constructorParams { + const SkIRect& bounds +} + +@make { + static std::unique_ptr Make(sk_sp mask, + float innerThreshold, + float outerThreshold, + const SkIRect& bounds) { + return std::unique_ptr(new GrAlphaThresholdFragmentProcessor( + mask, innerThreshold, outerThreshold, bounds)); + } +} + +@coordTransform(mask) { + SkMatrix::MakeTrans(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())) +} + +@cpp { + inline GrFragmentProcessor::OptimizationFlags GrAlphaThresholdFragmentProcessor::optFlags( + float outerThreshold) { + if (outerThreshold >= 1.0) { + return kPreservesOpaqueInput_OptimizationFlag | + kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } else { + return kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } + } +} + +void main() { + half4 color = sk_InColor; + half4 mask_color = sample(mask, sk_TransformedCoords2D[0]); + if (mask_color.a < 0.5) { + if (color.a > outerThreshold) { + half scale = outerThreshold / color.a; + color.rgb *= scale; + color.a = outerThreshold; + } + } else if (color.a < innerThreshold) { + half scale = innerThreshold / max(0.001, color.a); + color.rgb *= scale; + color.a = innerThreshold; + } + sk_OutColor = color; +} + +@test(testData) { + sk_sp maskProxy = testData->textureProxy(GrProcessorUnitTest::kAlphaTextureIdx); + // Make the inner and outer thresholds be in (0, 1) exclusive and be sorted correctly. + float innerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f; + float outerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f; + const int kMaxWidth = 1000; + const int kMaxHeight = 1000; + uint32_t width = testData->fRandom->nextULessThan(kMaxWidth); + uint32_t height = testData->fRandom->nextULessThan(kMaxHeight); + uint32_t x = testData->fRandom->nextULessThan(kMaxWidth - width); + uint32_t y = testData->fRandom->nextULessThan(kMaxHeight - height); + SkIRect bounds = SkIRect::MakeXYWH(x, y, width, height); + return GrAlphaThresholdFragmentProcessor::Make(std::move(maskProxy), innerThresh, outerThresh, + bounds); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrAtlasedShaderHelpers.h b/gfx/skia/skia/src/gpu/effects/GrAtlasedShaderHelpers.h new file mode 100644 index 0000000000..94b6f98a51 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrAtlasedShaderHelpers.h @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAtlasedShaderHelpers_DEFINED +#define GrAtlasedShaderHelpers_DEFINED + +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +static void append_index_uv_varyings(GrGLSLPrimitiveProcessor::EmitArgs& args, + const char* inTexCoordsName, + const char* atlasSizeInvName, + GrGLSLVarying *uv, + GrGLSLVarying *texIdx, + GrGLSLVarying *st) { + using Interpolation = GrGLSLVaryingHandler::Interpolation; + + // This extracts the texture index and texel coordinates from the same variable + // Packing structure: texel coordinates are multiplied by 2 (or shifted left 1) + // texture index is stored as lower bits of both x and y + if (args.fShaderCaps->integerSupport()) { + args.fVertBuilder->codeAppendf("int2 signedCoords = int2(%s.x, %s.y);", + inTexCoordsName, inTexCoordsName); + args.fVertBuilder->codeAppend("int texIdx = 2*(signedCoords.x & 0x1) + (signedCoords.y & 0x1);"); + args.fVertBuilder->codeAppend("float2 unormTexCoords = float2(signedCoords.x/2, signedCoords.y/2);"); + } else { + args.fVertBuilder->codeAppendf("float2 indexTexCoords = float2(%s.x, %s.y);", + inTexCoordsName, inTexCoordsName); + args.fVertBuilder->codeAppend("float2 unormTexCoords = floor(0.5*indexTexCoords);"); + args.fVertBuilder->codeAppend("float2 diff = indexTexCoords - 2.0*unormTexCoords;"); + args.fVertBuilder->codeAppend("float texIdx = 2.0*diff.x + diff.y;"); + } + + // Multiply by 1/atlasSize to get normalized texture coordinates + args.fVaryingHandler->addVarying("TextureCoords", uv); + args.fVertBuilder->codeAppendf("%s = unormTexCoords * %s;", uv->vsOut(), atlasSizeInvName); + + args.fVaryingHandler->addVarying("TexIndex", texIdx, args.fShaderCaps->integerSupport() + ? Interpolation::kMustBeFlat + : Interpolation::kCanBeFlat); + args.fVertBuilder->codeAppendf("%s = texIdx;", texIdx->vsOut()); + + if (st) { + args.fVaryingHandler->addVarying("IntTextureCoords", st); + args.fVertBuilder->codeAppendf("%s = unormTexCoords;", st->vsOut()); + } +} + +static void append_multitexture_lookup(GrGLSLPrimitiveProcessor::EmitArgs& args, + int numTextureSamplers, + const GrGLSLVarying &texIdx, + const char* coordName, + const char* colorName) { + // conditionally load from the indexed texture sampler + for (int i = 0; i < numTextureSamplers-1; ++i) { + args.fFragBuilder->codeAppendf("if (%s == %d) { %s = ", texIdx.fsIn(), i, colorName); + args.fFragBuilder->appendTextureLookup(args.fTexSamplers[i], coordName, + kFloat2_GrSLType); + args.fFragBuilder->codeAppend("; } else "); + } + args.fFragBuilder->codeAppendf("{ %s = ", colorName); + args.fFragBuilder->appendTextureLookup(args.fTexSamplers[numTextureSamplers-1], coordName, + kFloat2_GrSLType); + args.fFragBuilder->codeAppend("; }"); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrBezierEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrBezierEffect.cpp new file mode 100644 index 0000000000..7c9e5f0983 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBezierEffect.cpp @@ -0,0 +1,462 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/effects/GrBezierEffect.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLUtil.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +class GrGLConicEffect : public GrGLSLGeometryProcessor { +public: + GrGLConicEffect(const GrGeometryProcessor&); + + void onEmitCode(EmitArgs&, GrGPArgs*) override; + + static inline void GenKey(const GrGeometryProcessor&, + const GrShaderCaps&, + GrProcessorKeyBuilder*); + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + const GrConicEffect& ce = primProc.cast(); + + if (!ce.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(ce.viewMatrix())) { + fViewMatrix = ce.viewMatrix(); + float viewMatrix[3 * 3]; + GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix); + pdman.setMatrix3f(fViewMatrixUniform, viewMatrix); + } + + if (ce.color() != fColor) { + pdman.set4fv(fColorUniform, 1, ce.color().vec()); + fColor = ce.color(); + } + + if (ce.coverageScale() != 0xff && ce.coverageScale() != fCoverageScale) { + pdman.set1f(fCoverageScaleUniform, GrNormalizeByteToFloat(ce.coverageScale())); + fCoverageScale = ce.coverageScale(); + } + this->setTransformDataHelper(ce.localMatrix(), pdman, &transformIter); + } + +private: + SkMatrix fViewMatrix; + SkPMColor4f fColor; + uint8_t fCoverageScale; + GrClipEdgeType fEdgeType; + UniformHandle fColorUniform; + UniformHandle fCoverageScaleUniform; + UniformHandle fViewMatrixUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +GrGLConicEffect::GrGLConicEffect(const GrGeometryProcessor& processor) + : fViewMatrix(SkMatrix::InvalidMatrix()), fColor(SK_PMColor4fILLEGAL), fCoverageScale(0xff) { + const GrConicEffect& ce = processor.cast(); + fEdgeType = ce.getEdgeType(); +} + +void GrGLConicEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + const GrConicEffect& gp = args.fGP.cast(); + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(gp); + + GrGLSLVarying v(kFloat4_GrSLType); + varyingHandler->addVarying("ConicCoeffs", &v); + vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inConicCoeffs().name()); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // Setup pass through color + this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform); + + // Setup position + this->writeOutputPosition(vertBuilder, + uniformHandler, + gpArgs, + gp.inPosition().name(), + gp.viewMatrix(), + &fViewMatrixUniform); + + // emit transforms with position + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + gp.inPosition().asShaderVar(), + gp.localMatrix(), + args.fFPCoordTransformHandler); + + // TODO: we should check on the number of bits float and half provide and use the smallest one + // that suffices. Additionally we should assert that the upstream code only lets us get here if + // either float or half provides the required number of bits. + + GrShaderVar edgeAlpha("edgeAlpha", kHalf_GrSLType, 0); + GrShaderVar dklmdx("dklmdx", kFloat3_GrSLType, 0); + GrShaderVar dklmdy("dklmdy", kFloat3_GrSLType, 0); + GrShaderVar dfdx("dfdx", kFloat_GrSLType, 0); + GrShaderVar dfdy("dfdy", kFloat_GrSLType, 0); + GrShaderVar gF("gF", kFloat2_GrSLType, 0); + GrShaderVar gFM("gFM", kFloat_GrSLType, 0); + GrShaderVar func("func", kFloat_GrSLType, 0); + + fragBuilder->declAppend(edgeAlpha); + fragBuilder->declAppend(dklmdx); + fragBuilder->declAppend(dklmdy); + fragBuilder->declAppend(dfdx); + fragBuilder->declAppend(dfdy); + fragBuilder->declAppend(gF); + fragBuilder->declAppend(gFM); + fragBuilder->declAppend(func); + + switch (fEdgeType) { + case GrClipEdgeType::kHairlineAA: { + fragBuilder->codeAppendf("%s = dFdx(%s.xyz);", dklmdx.c_str(), v.fsIn()); + fragBuilder->codeAppendf("%s = dFdy(%s.xyz);", dklmdy.c_str(), v.fsIn()); + fragBuilder->codeAppendf("%s = 2.0 * %s.x * %s.x - %s.y * %s.z - %s.z * %s.y;", + dfdx.c_str(), + v.fsIn(), dklmdx.c_str(), + v.fsIn(), dklmdx.c_str(), + v.fsIn(), dklmdx.c_str()); + fragBuilder->codeAppendf("%s = 2.0 * %s.x * %s.x - %s.y * %s.z - %s.z * %s.y;", + dfdy.c_str(), + v.fsIn(), dklmdy.c_str(), + v.fsIn(), dklmdy.c_str(), + v.fsIn(), dklmdy.c_str()); + fragBuilder->codeAppendf("%s = float2(%s, %s);", gF.c_str(), dfdx.c_str(), + dfdy.c_str()); + fragBuilder->codeAppendf("%s = sqrt(dot(%s, %s));", + gFM.c_str(), gF.c_str(), gF.c_str()); + fragBuilder->codeAppendf("%s = %s.x*%s.x - %s.y*%s.z;", + func.c_str(), v.fsIn(), v.fsIn(), v.fsIn(), v.fsIn()); + fragBuilder->codeAppendf("%s = abs(%s);", func.c_str(), func.c_str()); + fragBuilder->codeAppendf("%s = half(%s / %s);", + edgeAlpha.c_str(), func.c_str(), gFM.c_str()); + fragBuilder->codeAppendf("%s = max(1.0 - %s, 0.0);", + edgeAlpha.c_str(), edgeAlpha.c_str()); + // Add line below for smooth cubic ramp + // fragBuilder->codeAppend("edgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);"); + break; + } + case GrClipEdgeType::kFillAA: { + fragBuilder->codeAppendf("%s = dFdx(%s.xyz);", dklmdx.c_str(), v.fsIn()); + fragBuilder->codeAppendf("%s = dFdy(%s.xyz);", dklmdy.c_str(), v.fsIn()); + fragBuilder->codeAppendf("%s =" + "2.0 * %s.x * %s.x - %s.y * %s.z - %s.z * %s.y;", + dfdx.c_str(), + v.fsIn(), dklmdx.c_str(), + v.fsIn(), dklmdx.c_str(), + v.fsIn(), dklmdx.c_str()); + fragBuilder->codeAppendf("%s =" + "2.0 * %s.x * %s.x - %s.y * %s.z - %s.z * %s.y;", + dfdy.c_str(), + v.fsIn(), dklmdy.c_str(), + v.fsIn(), dklmdy.c_str(), + v.fsIn(), dklmdy.c_str()); + fragBuilder->codeAppendf("%s = float2(%s, %s);", gF.c_str(), dfdx.c_str(), + dfdy.c_str()); + fragBuilder->codeAppendf("%s = sqrt(dot(%s, %s));", + gFM.c_str(), gF.c_str(), gF.c_str()); + fragBuilder->codeAppendf("%s = %s.x * %s.x - %s.y * %s.z;", + func.c_str(), v.fsIn(), v.fsIn(), v.fsIn(), v.fsIn()); + fragBuilder->codeAppendf("%s = half(%s / %s);", + edgeAlpha.c_str(), func.c_str(), gFM.c_str()); + fragBuilder->codeAppendf("%s = saturate(0.5 - %s);", + edgeAlpha.c_str(), edgeAlpha.c_str()); + // Add line below for smooth cubic ramp + // fragBuilder->codeAppend("edgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);"); + break; + } + case GrClipEdgeType::kFillBW: { + fragBuilder->codeAppendf("%s = half(%s.x * %s.x - %s.y * %s.z);", + edgeAlpha.c_str(), v.fsIn(), v.fsIn(), v.fsIn(), v.fsIn()); + fragBuilder->codeAppendf("%s = half(%s < 0.0);", + edgeAlpha.c_str(), edgeAlpha.c_str()); + break; + } + default: + SK_ABORT("Shouldn't get here"); + } + + // TODO should we really be doing this? + if (gp.coverageScale() != 0xff) { + const char* coverageScale; + fCoverageScaleUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, + "Coverage", + &coverageScale); + fragBuilder->codeAppendf("%s = half4(half(%s) * %s);", + args.fOutputCoverage, coverageScale, edgeAlpha.c_str()); + } else { + fragBuilder->codeAppendf("%s = half4(%s);", args.fOutputCoverage, edgeAlpha.c_str()); + } +} + +void GrGLConicEffect::GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrConicEffect& ce = gp.cast(); + uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2; + key |= 0xff != ce.coverageScale() ? 0x8 : 0x0; + key |= ce.usesLocalCoords() && ce.localMatrix().hasPerspective() ? 0x10 : 0x0; + key |= ComputePosKey(ce.viewMatrix()) << 5; + b->add32(key); +} + +////////////////////////////////////////////////////////////////////////////// + +constexpr GrPrimitiveProcessor::Attribute GrConicEffect::kAttributes[]; + +GrConicEffect::~GrConicEffect() {} + +void GrConicEffect::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLConicEffect::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* GrConicEffect::createGLSLInstance(const GrShaderCaps&) const { + return new GrGLConicEffect(*this); +} + +GrConicEffect::GrConicEffect(const SkPMColor4f& color, const SkMatrix& viewMatrix, uint8_t coverage, + GrClipEdgeType edgeType, const SkMatrix& localMatrix, + bool usesLocalCoords) + : INHERITED(kGrConicEffect_ClassID) + , fColor(color) + , fViewMatrix(viewMatrix) + , fLocalMatrix(viewMatrix) + , fUsesLocalCoords(usesLocalCoords) + , fCoverageScale(coverage) + , fEdgeType(edgeType) { + this->setVertexAttributes(kAttributes, SK_ARRAY_COUNT(kAttributes)); +} + +////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrConicEffect); + +#if GR_TEST_UTILS +sk_sp GrConicEffect::TestCreate(GrProcessorTestData* d) { + sk_sp gp; + do { + GrClipEdgeType edgeType = + static_cast( + d->fRandom->nextULessThan(kGrClipEdgeTypeCnt)); + gp = GrConicEffect::Make(SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)), + GrTest::TestMatrix(d->fRandom), edgeType, *d->caps(), + GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool()); + } while (nullptr == gp); + return gp; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// Quad +////////////////////////////////////////////////////////////////////////////// + +class GrGLQuadEffect : public GrGLSLGeometryProcessor { +public: + GrGLQuadEffect(const GrGeometryProcessor&); + + void onEmitCode(EmitArgs&, GrGPArgs*) override; + + static inline void GenKey(const GrGeometryProcessor&, + const GrShaderCaps&, + GrProcessorKeyBuilder*); + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + const GrQuadEffect& qe = primProc.cast(); + + if (!qe.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(qe.viewMatrix())) { + fViewMatrix = qe.viewMatrix(); + float viewMatrix[3 * 3]; + GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix); + pdman.setMatrix3f(fViewMatrixUniform, viewMatrix); + } + + if (qe.color() != fColor) { + pdman.set4fv(fColorUniform, 1, qe.color().vec()); + fColor = qe.color(); + } + + if (qe.coverageScale() != 0xff && qe.coverageScale() != fCoverageScale) { + pdman.set1f(fCoverageScaleUniform, GrNormalizeByteToFloat(qe.coverageScale())); + fCoverageScale = qe.coverageScale(); + } + this->setTransformDataHelper(qe.localMatrix(), pdman, &transformIter); + } + +private: + SkMatrix fViewMatrix; + SkPMColor4f fColor; + uint8_t fCoverageScale; + GrClipEdgeType fEdgeType; + UniformHandle fColorUniform; + UniformHandle fCoverageScaleUniform; + UniformHandle fViewMatrixUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +GrGLQuadEffect::GrGLQuadEffect(const GrGeometryProcessor& processor) + : fViewMatrix(SkMatrix::InvalidMatrix()), fColor(SK_PMColor4fILLEGAL), fCoverageScale(0xff) { + const GrQuadEffect& ce = processor.cast(); + fEdgeType = ce.getEdgeType(); +} + +void GrGLQuadEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + const GrQuadEffect& gp = args.fGP.cast(); + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(gp); + + GrGLSLVarying v(kHalf4_GrSLType); + varyingHandler->addVarying("HairQuadEdge", &v); + vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inHairQuadEdge().name()); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // Setup pass through color + this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform); + + // Setup position + this->writeOutputPosition(vertBuilder, + uniformHandler, + gpArgs, + gp.inPosition().name(), + gp.viewMatrix(), + &fViewMatrixUniform); + + // emit transforms with position + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + gp.inPosition().asShaderVar(), + gp.localMatrix(), + args.fFPCoordTransformHandler); + + fragBuilder->codeAppendf("half edgeAlpha;"); + + switch (fEdgeType) { + case GrClipEdgeType::kHairlineAA: { + fragBuilder->codeAppendf("half2 duvdx = half2(dFdx(%s.xy));", v.fsIn()); + fragBuilder->codeAppendf("half2 duvdy = half2(dFdy(%s.xy));", v.fsIn()); + fragBuilder->codeAppendf("half2 gF = half2(2.0 * %s.x * duvdx.x - duvdx.y," + " 2.0 * %s.x * duvdy.x - duvdy.y);", + v.fsIn(), v.fsIn()); + fragBuilder->codeAppendf("edgeAlpha = half(%s.x * %s.x - %s.y);", + v.fsIn(), v.fsIn(), v.fsIn()); + fragBuilder->codeAppend("edgeAlpha = sqrt(edgeAlpha * edgeAlpha / dot(gF, gF));"); + fragBuilder->codeAppend("edgeAlpha = max(1.0 - edgeAlpha, 0.0);"); + // Add line below for smooth cubic ramp + // fragBuilder->codeAppend("edgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);"); + break; + } + case GrClipEdgeType::kFillAA: { + fragBuilder->codeAppendf("half2 duvdx = half2(dFdx(%s.xy));", v.fsIn()); + fragBuilder->codeAppendf("half2 duvdy = half2(dFdy(%s.xy));", v.fsIn()); + fragBuilder->codeAppendf("half2 gF = half2(2.0 * %s.x * duvdx.x - duvdx.y," + " 2.0 * %s.x * duvdy.x - duvdy.y);", + v.fsIn(), v.fsIn()); + fragBuilder->codeAppendf("edgeAlpha = half(%s.x * %s.x - %s.y);", + v.fsIn(), v.fsIn(), v.fsIn()); + fragBuilder->codeAppend("edgeAlpha = edgeAlpha / sqrt(dot(gF, gF));"); + fragBuilder->codeAppend("edgeAlpha = saturate(0.5 - edgeAlpha);"); + // Add line below for smooth cubic ramp + // fragBuilder->codeAppend("edgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);"); + break; + } + case GrClipEdgeType::kFillBW: { + fragBuilder->codeAppendf("edgeAlpha = half(%s.x * %s.x - %s.y);", + v.fsIn(), v.fsIn(), v.fsIn()); + fragBuilder->codeAppend("edgeAlpha = half(edgeAlpha < 0.0);"); + break; + } + default: + SK_ABORT("Shouldn't get here"); + } + + if (0xff != gp.coverageScale()) { + const char* coverageScale; + fCoverageScaleUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf_GrSLType, + "Coverage", + &coverageScale); + fragBuilder->codeAppendf("%s = half4(%s * edgeAlpha);", args.fOutputCoverage, + coverageScale); + } else { + fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage); + } +} + +void GrGLQuadEffect::GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrQuadEffect& ce = gp.cast(); + uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2; + key |= ce.coverageScale() != 0xff ? 0x8 : 0x0; + key |= ce.usesLocalCoords() && ce.localMatrix().hasPerspective() ? 0x10 : 0x0; + key |= ComputePosKey(ce.viewMatrix()) << 5; + b->add32(key); +} + +////////////////////////////////////////////////////////////////////////////// + +constexpr GrPrimitiveProcessor::Attribute GrQuadEffect::kAttributes[]; + +GrQuadEffect::~GrQuadEffect() {} + +void GrQuadEffect::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLQuadEffect::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* GrQuadEffect::createGLSLInstance(const GrShaderCaps&) const { + return new GrGLQuadEffect(*this); +} + +GrQuadEffect::GrQuadEffect(const SkPMColor4f& color, const SkMatrix& viewMatrix, uint8_t coverage, + GrClipEdgeType edgeType, const SkMatrix& localMatrix, + bool usesLocalCoords) + : INHERITED(kGrQuadEffect_ClassID) + , fColor(color) + , fViewMatrix(viewMatrix) + , fLocalMatrix(localMatrix) + , fUsesLocalCoords(usesLocalCoords) + , fCoverageScale(coverage) + , fEdgeType(edgeType) { + this->setVertexAttributes(kAttributes, SK_ARRAY_COUNT(kAttributes)); +} + +////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrQuadEffect); + +#if GR_TEST_UTILS +sk_sp GrQuadEffect::TestCreate(GrProcessorTestData* d) { + sk_sp gp; + do { + GrClipEdgeType edgeType = static_cast( + d->fRandom->nextULessThan(kGrClipEdgeTypeCnt)); + gp = GrQuadEffect::Make(SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)), + GrTest::TestMatrix(d->fRandom), edgeType, *d->caps(), + GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool()); + } while (nullptr == gp); + return gp; +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrBezierEffect.h b/gfx/skia/skia/src/gpu/effects/GrBezierEffect.h new file mode 100644 index 0000000000..c63002bb53 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBezierEffect.h @@ -0,0 +1,216 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBezierEffect_DEFINED +#define GrBezierEffect_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProcessor.h" + +/** + * Shader is based off of Loop-Blinn Quadratic GPU Rendering + * The output of this effect is a hairline edge for conics. + * Conics specified by implicit equation K^2 - LM. + * K, L, and M, are the first three values of the vertex attribute, + * the fourth value is not used. Distance is calculated using a + * first order approximation from the taylor series. + * Coverage for AA is max(0, 1-distance). + * + * Test were also run using a second order distance approximation. + * There were two versions of the second order approx. The first version + * is of roughly the form: + * f(q) = |f(p)| - ||f'(p)||*||q-p|| - ||f''(p)||*||q-p||^2. + * The second is similar: + * f(q) = |f(p)| + ||f'(p)||*||q-p|| + ||f''(p)||*||q-p||^2. + * The exact version of the equations can be found in the paper + * "Distance Approximations for Rasterizing Implicit Curves" by Gabriel Taubin + * + * In both versions we solve the quadratic for ||q-p||. + * Version 1: + * gFM is magnitude of first partials and gFM2 is magnitude of 2nd partials (as derived from paper) + * builder->fsCodeAppend("\t\tedgeAlpha = (sqrt(gFM*gFM+4.0*func*gF2M) - gFM)/(2.0*gF2M);\n"); + * Version 2: + * builder->fsCodeAppend("\t\tedgeAlpha = (gFM - sqrt(gFM*gFM-4.0*func*gF2M))/(2.0*gF2M);\n"); + * + * Also note that 2nd partials of k,l,m are zero + * + * When comparing the two second order approximations to the first order approximations, + * the following results were found. Version 1 tends to underestimate the distances, thus it + * basically increases all the error that we were already seeing in the first order + * approx. So this version is not the one to use. Version 2 has the opposite effect + * and tends to overestimate the distances. This is much closer to what we are + * looking for. It is able to render ellipses (even thin ones) without the need to chop. + * However, it can not handle thin hyperbolas well and thus would still rely on + * chopping to tighten the clipping. Another side effect of the overestimating is + * that the curves become much thinner and "ropey". If all that was ever rendered + * were "not too thin" curves and ellipses then 2nd order may have an advantage since + * only one geometry would need to be rendered. However no benches were run comparing + * chopped first order and non chopped 2nd order. + */ +class GrGLConicEffect; + +class GrConicEffect : public GrGeometryProcessor { +public: + static sk_sp Make(const SkPMColor4f& color, + const SkMatrix& viewMatrix, + const GrClipEdgeType edgeType, + const GrCaps& caps, + const SkMatrix& localMatrix, + bool usesLocalCoords, + uint8_t coverage = 0xff) { + switch (edgeType) { + case GrClipEdgeType::kFillAA: + if (!caps.shaderCaps()->shaderDerivativeSupport()) { + return nullptr; + } + return sk_sp( + new GrConicEffect(color, viewMatrix, coverage, GrClipEdgeType::kFillAA, + localMatrix, usesLocalCoords)); + case GrClipEdgeType::kHairlineAA: + if (!caps.shaderCaps()->shaderDerivativeSupport()) { + return nullptr; + } + return sk_sp( + new GrConicEffect(color, viewMatrix, coverage, + GrClipEdgeType::kHairlineAA, localMatrix, + usesLocalCoords)); + case GrClipEdgeType::kFillBW: + return sk_sp( + new GrConicEffect(color, viewMatrix, coverage, GrClipEdgeType::kFillBW, + localMatrix, usesLocalCoords)); + default: + return nullptr; + } + } + + ~GrConicEffect() override; + + const char* name() const override { return "Conic"; } + + inline const Attribute& inPosition() const { return kAttributes[0]; } + inline const Attribute& inConicCoeffs() const { return kAttributes[1]; } + inline bool isAntiAliased() const { return GrProcessorEdgeTypeIsAA(fEdgeType); } + inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); } + inline GrClipEdgeType getEdgeType() const { return fEdgeType; } + const SkPMColor4f& color() const { return fColor; } + const SkMatrix& viewMatrix() const { return fViewMatrix; } + const SkMatrix& localMatrix() const { return fLocalMatrix; } + bool usesLocalCoords() const { return fUsesLocalCoords; } + uint8_t coverageScale() const { return fCoverageScale; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + GrConicEffect(const SkPMColor4f&, const SkMatrix& viewMatrix, uint8_t coverage, GrClipEdgeType, + const SkMatrix& localMatrix, bool usesLocalCoords); + + SkPMColor4f fColor; + SkMatrix fViewMatrix; + SkMatrix fLocalMatrix; + bool fUsesLocalCoords; + uint8_t fCoverageScale; + GrClipEdgeType fEdgeType; + static constexpr Attribute kAttributes[] = { + {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}, + {"inConicCoeffs", kFloat4_GrVertexAttribType, kHalf4_GrSLType} + }; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// +/** + * The output of this effect is a hairline edge for quadratics. + * Quadratic specified by 0=u^2-v canonical coords. u and v are the first + * two components of the vertex attribute. At the three control points that define + * the Quadratic, u, v have the values {0,0}, {1/2, 0}, and {1, 1} respectively. + * Coverage for AA is min(0, 1-distance). 3rd & 4th cimponent unused. + * Requires shader derivative instruction support. + */ +class GrGLQuadEffect; + +class GrQuadEffect : public GrGeometryProcessor { +public: + static sk_sp Make(const SkPMColor4f& color, + const SkMatrix& viewMatrix, + const GrClipEdgeType edgeType, + const GrCaps& caps, + const SkMatrix& localMatrix, + bool usesLocalCoords, + uint8_t coverage = 0xff) { + switch (edgeType) { + case GrClipEdgeType::kFillAA: + if (!caps.shaderCaps()->shaderDerivativeSupport()) { + return nullptr; + } + return sk_sp( + new GrQuadEffect(color, viewMatrix, coverage, GrClipEdgeType::kFillAA, + localMatrix, usesLocalCoords)); + case GrClipEdgeType::kHairlineAA: + if (!caps.shaderCaps()->shaderDerivativeSupport()) { + return nullptr; + } + return sk_sp( + new GrQuadEffect(color, viewMatrix, coverage, + GrClipEdgeType::kHairlineAA, localMatrix, + usesLocalCoords)); + case GrClipEdgeType::kFillBW: + return sk_sp( + new GrQuadEffect(color, viewMatrix, coverage, GrClipEdgeType::kFillBW, + localMatrix, usesLocalCoords)); + default: + return nullptr; + } + } + + ~GrQuadEffect() override; + + const char* name() const override { return "Quad"; } + + inline const Attribute& inPosition() const { return kAttributes[0]; } + inline const Attribute& inHairQuadEdge() const { return kAttributes[1]; } + inline bool isAntiAliased() const { return GrProcessorEdgeTypeIsAA(fEdgeType); } + inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); } + inline GrClipEdgeType getEdgeType() const { return fEdgeType; } + const SkPMColor4f& color() const { return fColor; } + const SkMatrix& viewMatrix() const { return fViewMatrix; } + const SkMatrix& localMatrix() const { return fLocalMatrix; } + bool usesLocalCoords() const { return fUsesLocalCoords; } + uint8_t coverageScale() const { return fCoverageScale; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + GrQuadEffect(const SkPMColor4f&, const SkMatrix& viewMatrix, uint8_t coverage, GrClipEdgeType, + const SkMatrix& localMatrix, bool usesLocalCoords); + + SkPMColor4f fColor; + SkMatrix fViewMatrix; + SkMatrix fLocalMatrix; + bool fUsesLocalCoords; + uint8_t fCoverageScale; + GrClipEdgeType fEdgeType; + + static constexpr Attribute kAttributes[] = { + {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}, + {"inHairQuadEdge", kFloat4_GrVertexAttribType, kHalf4_GrSLType} + }; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrBicubicEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrBicubicEffect.cpp new file mode 100644 index 0000000000..6d7b431ab3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBicubicEffect.cpp @@ -0,0 +1,255 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkMatrixPriv.h" +#include "src/gpu/effects/GrBicubicEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrGLBicubicEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor& effect, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrBicubicEffect& bicubicEffect = effect.cast(); + b->add32(GrTextureDomain::GLDomain::DomainKey(bicubicEffect.domain())); + uint32_t bidir = bicubicEffect.direction() == GrBicubicEffect::Direction::kXY ? 1 : 0; + b->add32(bidir | (bicubicEffect.alphaType() << 1)); + } + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; + + UniformHandle fDimensions; + GrTextureDomain::GLDomain fDomain; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GrGLBicubicEffect::emitCode(EmitArgs& args) { + const GrBicubicEffect& bicubicEffect = args.fFp.cast(); + + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + fDimensions = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "Dimensions"); + + const char* dims = uniformHandler->getUniformCStr(fDimensions); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + + /* + * Filter weights come from Don Mitchell & Arun Netravali's 'Reconstruction Filters in Computer + * Graphics', ACM SIGGRAPH Computer Graphics 22, 4 (Aug. 1988). + * ACM DL: http://dl.acm.org/citation.cfm?id=378514 + * Free : http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf + * + * The authors define a family of cubic filters with two free parameters (B and C): + * + * { (12 - 9B - 6C)|x|^3 + (-18 + 12B + 6C)|x|^2 + (6 - 2B) if |x| < 1 + * k(x) = 1/6 { (-B - 6C)|x|^3 + (6B + 30C)|x|^2 + (-12B - 48C)|x| + (8B + 24C) if 1 <= |x| < 2 + * { 0 otherwise + * + * Various well-known cubic splines can be generated, and the authors select (1/3, 1/3) as their + * favorite overall spline - this is now commonly known as the Mitchell filter, and is the + * source of the specific weights below. + * + * This is GLSL, so the matrix is column-major (transposed from standard matrix notation). + */ + fragBuilder->codeAppend("half4x4 kMitchellCoefficients = half4x4(" + " 1.0 / 18.0, 16.0 / 18.0, 1.0 / 18.0, 0.0 / 18.0," + "-9.0 / 18.0, 0.0 / 18.0, 9.0 / 18.0, 0.0 / 18.0," + "15.0 / 18.0, -36.0 / 18.0, 27.0 / 18.0, -6.0 / 18.0," + "-7.0 / 18.0, 21.0 / 18.0, -21.0 / 18.0, 7.0 / 18.0);"); + fragBuilder->codeAppendf("float2 coord = %s - %s.xy * float2(0.5);", coords2D.c_str(), dims); + // We unnormalize the coord in order to determine our fractional offset (f) within the texel + // We then snap coord to a texel center and renormalize. The snap prevents cases where the + // starting coords are near a texel boundary and accumulations of dims would cause us to skip/ + // double hit a texel. + fragBuilder->codeAppendf("half2 f = half2(fract(coord * %s.zw));", dims); + fragBuilder->codeAppendf("coord = coord + (half2(0.5) - f) * %s.xy;", dims); + if (bicubicEffect.direction() == GrBicubicEffect::Direction::kXY) { + fragBuilder->codeAppend( + "half4 wx = kMitchellCoefficients * half4(1.0, f.x, f.x * f.x, f.x * f.x * f.x);"); + fragBuilder->codeAppend( + "half4 wy = kMitchellCoefficients * half4(1.0, f.y, f.y * f.y, f.y * f.y * f.y);"); + fragBuilder->codeAppend("half4 rowColors[4];"); + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + SkString coord; + coord.printf("coord + %s.xy * float2(%d, %d)", dims, x - 1, y - 1); + SkString sampleVar; + sampleVar.printf("rowColors[%d]", x); + fDomain.sampleTexture(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + bicubicEffect.domain(), + sampleVar.c_str(), + coord, + args.fTexSamplers[0]); + } + fragBuilder->codeAppendf( + "half4 s%d = wx.x * rowColors[0] + wx.y * rowColors[1] + wx.z * rowColors[2] + " + "wx.w * rowColors[3];", + y); + } + fragBuilder->codeAppend( + "half4 bicubicColor = wy.x * s0 + wy.y * s1 + wy.z * s2 + wy.w * s3;"); + } else { + // One of the dims.xy values will be zero. So v here selects the nonzero value of f. + fragBuilder->codeAppend("half v = f.x + f.y;"); + fragBuilder->codeAppend("half v2 = v * v;"); + fragBuilder->codeAppend("half4 w = kMitchellCoefficients * half4(1.0, v, v2, v2 * v);"); + fragBuilder->codeAppend("half4 c[4];"); + for (int i = 0; i < 4; ++i) { + SkString coord; + coord.printf("coord + %s.xy * half(%d)", dims, i - 1); + SkString samplerVar; + samplerVar.printf("c[%d]", i); + // With added complexity we could apply the domain once in X or Y depending on + // direction rather than for each of the four lookups, but then we might not be + // be able to share code for Direction::kX and ::kY. + fDomain.sampleTexture(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + bicubicEffect.domain(), + samplerVar.c_str(), + coord, + args.fTexSamplers[0]); + } + fragBuilder->codeAppend( + "half4 bicubicColor = c[0] * w.x + c[1] * w.y + c[2] * w.z + c[3] * w.w;"); + } + // Bicubic can send colors out of range, so clamp to get them back in (source) gamut. + // The kind of clamp we have to do depends on the alpha type. + if (kPremul_SkAlphaType == bicubicEffect.alphaType()) { + fragBuilder->codeAppend("bicubicColor.a = saturate(bicubicColor.a);"); + fragBuilder->codeAppend( + "bicubicColor.rgb = max(half3(0.0), min(bicubicColor.rgb, bicubicColor.aaa));"); + } else { + fragBuilder->codeAppend("bicubicColor = saturate(bicubicColor);"); + } + fragBuilder->codeAppendf("%s = bicubicColor * %s;", args.fOutputColor, args.fInputColor); +} + +void GrGLBicubicEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + const GrBicubicEffect& bicubicEffect = processor.cast(); + GrTextureProxy* proxy = processor.textureSampler(0).proxy(); + GrTexture* texture = proxy->peekTexture(); + + float dims[4] = {0, 0, 0, 0}; + if (bicubicEffect.direction() != GrBicubicEffect::Direction::kY) { + dims[0] = 1.0f / texture->width(); + dims[2] = texture->width(); + } + if (bicubicEffect.direction() != GrBicubicEffect::Direction::kX) { + dims[1] = 1.0f / texture->height(); + dims[3] = texture->height(); + } + pdman.set4fv(fDimensions, 1, dims); + fDomain.setData(pdman, bicubicEffect.domain(), proxy, + processor.textureSampler(0).samplerState()); +} + +GrBicubicEffect::GrBicubicEffect(sk_sp proxy, GrColorType srcColorType, + const SkMatrix& matrix, const SkRect& domain, + const GrSamplerState::WrapMode wrapModes[2], + GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY, + Direction direction, SkAlphaType alphaType) + : INHERITED{kGrBicubicEffect_ClassID, + ModulateForSamplerOptFlags( + srcColorType, + GrTextureDomain::IsDecalSampled(wrapModes, modeX, modeY))} + , fCoordTransform(matrix, proxy.get()) + , fDomain(proxy.get(), domain, modeX, modeY) + , fTextureSampler(std::move(proxy), + GrSamplerState(wrapModes, GrSamplerState::Filter::kNearest)) + , fAlphaType(alphaType) + , fDirection(direction) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); +} + +GrBicubicEffect::GrBicubicEffect(const GrBicubicEffect& that) + : INHERITED(kGrBicubicEffect_ClassID, that.optimizationFlags()) + , fCoordTransform(that.fCoordTransform) + , fDomain(that.fDomain) + , fTextureSampler(that.fTextureSampler) + , fAlphaType(that.fAlphaType) + , fDirection(that.fDirection) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); +} + +void GrBicubicEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLBicubicEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrBicubicEffect::onCreateGLSLInstance() const { + return new GrGLBicubicEffect; +} + +bool GrBicubicEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrBicubicEffect& s = sBase.cast(); + return fDomain == s.fDomain && fDirection == s.fDirection && fAlphaType == s.fAlphaType; +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrBicubicEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrBicubicEffect::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + static const GrSamplerState::WrapMode kClampClamp[] = {GrSamplerState::WrapMode::kClamp, + GrSamplerState::WrapMode::kClamp}; + SkAlphaType alphaType = d->fRandom->nextBool() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + Direction direction = Direction::kX; + switch (d->fRandom->nextULessThan(3)) { + case 0: + direction = Direction::kX; + break; + case 1: + direction = Direction::kY; + break; + case 2: + direction = Direction::kXY; + break; + } + return GrBicubicEffect::Make(d->textureProxy(texIdx), d->textureProxyColorType(texIdx), + SkMatrix::I(), kClampClamp, direction, alphaType); +} +#endif + +////////////////////////////////////////////////////////////////////////////// + +bool GrBicubicEffect::ShouldUseBicubic(const SkMatrix& matrix, GrSamplerState::Filter* filterMode) { + switch (SkMatrixPriv::AdjustHighQualityFilterLevel(matrix)) { + case kNone_SkFilterQuality: + *filterMode = GrSamplerState::Filter::kNearest; + break; + case kLow_SkFilterQuality: + *filterMode = GrSamplerState::Filter::kBilerp; + break; + case kMedium_SkFilterQuality: + *filterMode = GrSamplerState::Filter::kMipMap; + break; + case kHigh_SkFilterQuality: + // When we use the bicubic filtering effect each sample is read from the texture using + // nearest neighbor sampling. + *filterMode = GrSamplerState::Filter::kNearest; + return true; + } + return false; +} diff --git a/gfx/skia/skia/src/gpu/effects/GrBicubicEffect.h b/gfx/skia/skia/src/gpu/effects/GrBicubicEffect.h new file mode 100644 index 0000000000..a2dc249000 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBicubicEffect.h @@ -0,0 +1,148 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBicubicTextureEffect_DEFINED +#define GrBicubicTextureEffect_DEFINED + +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" + +class GrInvariantOutput; + +class GrBicubicEffect : public GrFragmentProcessor { +public: + enum { + kFilterTexelPad = 2, // Given a src rect in texels to be filtered, this number of + // surrounding texels are needed by the kernel in x and y. + }; + + enum class Direction { + /** Apply bicubic kernel in local coord x, nearest neighbor in y. */ + kX, + /** Apply bicubic kernel in local coord y, nearest neighbor in x. */ + kY, + /** Apply bicubic in both x and y. */ + kXY + }; + + const char* name() const override { return "Bicubic"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrBicubicEffect(*this)); + } + + const GrTextureDomain& domain() const { return fDomain; } + + Direction direction() const { return fDirection; } + + SkAlphaType alphaType() const { return fAlphaType; } + + /** + * Create a Mitchell filter effect with specified texture matrix with clamp wrap mode. + */ + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + Direction direction, + SkAlphaType alphaType) { + static constexpr GrSamplerState::WrapMode kClampClamp[] = { + GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp}; + return Make(std::move(proxy), srcColorType, matrix, kClampClamp, + GrTextureDomain::kIgnore_Mode, GrTextureDomain::kIgnore_Mode, direction, + alphaType); + } + + /** + * Create a Mitchell filter effect with specified texture matrix and x/y tile modes. + */ + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const GrSamplerState::WrapMode wrapModes[2], + Direction direction, + SkAlphaType alphaType) { + // Ignore the domain on x and y, since this factory relies solely on the wrap mode of the + // sampler to constrain texture coordinates + return Make(std::move(proxy), srcColorType, matrix, wrapModes, + GrTextureDomain::kIgnore_Mode, GrTextureDomain::kIgnore_Mode, direction, + alphaType); + } + + /** + * Create a Mitchell filter effect with specified texture matrix and x/y tile modes. This + * supports providing modes for the texture domain explicitly, in the event that it should + * override the behavior of the sampler's tile mode (e.g. clamp to border unsupported). + */ + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const GrSamplerState::WrapMode wrapModes[2], + GrTextureDomain::Mode modeX, + GrTextureDomain::Mode modeY, + Direction direction, + SkAlphaType alphaType, + const SkRect* domain = nullptr) { + SkRect resolvedDomain = domain ? *domain : GrTextureDomain::MakeTexelDomain( + SkIRect::MakeWH(proxy->width(), proxy->height()), modeX, modeY); + return std::unique_ptr( + new GrBicubicEffect(std::move(proxy), srcColorType, matrix, resolvedDomain, + wrapModes, modeX, modeY, direction, alphaType)); + } + + /** + * Create a Mitchell filter effect with a texture matrix and a domain. + */ + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const SkRect& domain, + Direction direction, + SkAlphaType alphaType) { + static const GrSamplerState::WrapMode kClampClamp[] = { + GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp}; + return Make(std::move(proxy), srcColorType, matrix, kClampClamp, + GrTextureDomain::kClamp_Mode, GrTextureDomain::kClamp_Mode, direction, + alphaType, &domain); + } + + /** + * Determines whether the bicubic effect should be used based on the transformation from the + * local coords to the device. Returns true if the bicubic effect should be used. filterMode + * is set to appropriate filtering mode to use regardless of the return result (e.g. when this + * returns false it may indicate that the best fallback is to use kMipMap, kBilerp, or + * kNearest). + */ + static bool ShouldUseBicubic(const SkMatrix& localCoordsToDevice, + GrSamplerState::Filter* filterMode); + +private: + GrBicubicEffect(sk_sp, GrColorType srcColorType, const SkMatrix& matrix, + const SkRect& domain, const GrSamplerState::WrapMode wrapModes[2], + GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY, Direction direction, + SkAlphaType alphaType); + explicit GrBicubicEffect(const GrBicubicEffect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; } + + GrCoordTransform fCoordTransform; + GrTextureDomain fDomain; + TextureSampler fTextureSampler; + SkAlphaType fAlphaType; + Direction fDirection; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.cpp b/gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.cpp new file mode 100644 index 0000000000..4a8c25da6c --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.cpp @@ -0,0 +1,233 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrBitmapTextGeoProc.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/effects/GrAtlasedShaderHelpers.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +class GrGLBitmapTextGeoProc : public GrGLSLGeometryProcessor { +public: + GrGLBitmapTextGeoProc() : fColor(SK_PMColor4fILLEGAL), fAtlasSize({0,0}) {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const GrBitmapTextGeoProc& btgp = args.fGP.cast(); + + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(btgp); + + const char* atlasSizeInvName; + fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag, + kFloat2_GrSLType, + "AtlasSizeInv", + &atlasSizeInvName); + + GrGLSLVarying uv(kFloat2_GrSLType); + GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType; + GrGLSLVarying texIdx(texIdxType); + append_index_uv_varyings(args, btgp.inTextureCoords().name(), atlasSizeInvName, &uv, + &texIdx, nullptr); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // Setup pass through color + if (btgp.hasVertexColor()) { + varyingHandler->addPassThroughAttribute(btgp.inColor(), args.fOutputColor); + } else { + this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, + &fColorUniform); + } + + // Setup position + gpArgs->fPositionVar = btgp.inPosition().asShaderVar(); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + btgp.inPosition().asShaderVar(), + btgp.localMatrix(), + args.fFPCoordTransformHandler); + + fragBuilder->codeAppend("half4 texColor;"); + append_multitexture_lookup(args, btgp.numTextureSamplers(), + texIdx, uv.fsIn(), "texColor"); + + if (btgp.maskFormat() == kARGB_GrMaskFormat) { + // modulate by color + fragBuilder->codeAppendf("%s = %s * texColor;", args.fOutputColor, args.fOutputColor); + fragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage); + } else { + fragBuilder->codeAppendf("%s = texColor;", args.fOutputCoverage); + } + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp, + FPCoordTransformIter&& transformIter) override { + const GrBitmapTextGeoProc& btgp = gp.cast(); + if (btgp.color() != fColor && !btgp.hasVertexColor()) { + pdman.set4fv(fColorUniform, 1, btgp.color().vec()); + fColor = btgp.color(); + } + + const SkISize& atlasSize = btgp.atlasSize(); + SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight)); + + if (fAtlasSize != atlasSize) { + pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight); + fAtlasSize = atlasSize; + } + this->setTransformDataHelper(btgp.localMatrix(), pdman, &transformIter); + } + + static inline void GenKey(const GrGeometryProcessor& proc, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrBitmapTextGeoProc& btgp = proc.cast(); + uint32_t key = 0; + key |= btgp.usesW() ? 0x1 : 0x0; + key |= btgp.maskFormat() << 1; + b->add32(key); + b->add32(btgp.numTextureSamplers()); + } + +private: + SkPMColor4f fColor; + UniformHandle fColorUniform; + + SkISize fAtlasSize; + UniformHandle fAtlasSizeInvUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrBitmapTextGeoProc::GrBitmapTextGeoProc(const GrShaderCaps& caps, + const SkPMColor4f& color, + bool wideColor, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params, GrMaskFormat format, + const SkMatrix& localMatrix, bool usesW) + : INHERITED(kGrBitmapTextGeoProc_ClassID) + , fColor(color) + , fLocalMatrix(localMatrix) + , fUsesW(usesW) + , fMaskFormat(format) { + SkASSERT(numActiveProxies <= kMaxTextures); + + if (usesW) { + fInPosition = {"inPosition", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + } else { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + } + + bool hasVertexColor = kA8_GrMaskFormat == fMaskFormat || + kA565_GrMaskFormat == fMaskFormat; + if (hasVertexColor) { + fInColor = MakeColorAttribute("inColor", wideColor); + } + + fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType, + caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); + + if (numActiveProxies) { + fAtlasSize = proxies[0]->isize(); + } + for (int i = 0; i < numActiveProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + fTextureSamplers[i].reset(proxies[i]->textureType(), params, proxies[i]->textureSwizzle()); + } + this->setTextureSamplerCnt(numActiveProxies); +} + +void GrBitmapTextGeoProc::addNewProxies(const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params) { + SkASSERT(numActiveProxies <= kMaxTextures); + + if (!fTextureSamplers[0].isInitialized()) { + fAtlasSize = proxies[0]->isize(); + } + + for (int i = 0; i < numActiveProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + + if (!fTextureSamplers[i].isInitialized()) { + fTextureSamplers[i].reset(proxies[i]->textureType(), params, + proxies[i]->textureSwizzle()); + } + } + this->setTextureSamplerCnt(numActiveProxies); +} + +void GrBitmapTextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLBitmapTextGeoProc::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* GrBitmapTextGeoProc::createGLSLInstance(const GrShaderCaps& caps) const { + return new GrGLBitmapTextGeoProc(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrBitmapTextGeoProc); + +#if GR_TEST_UTILS + +sk_sp GrBitmapTextGeoProc::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxies[kMaxTextures] = { + d->textureProxy(texIdx), + nullptr, + nullptr, + nullptr + }; + + GrSamplerState::WrapMode wrapModes[2]; + GrTest::TestWrapModes(d->fRandom, wrapModes); + GrSamplerState samplerState(wrapModes, d->fRandom->nextBool() + ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kNearest); + + GrMaskFormat format = kARGB_GrMaskFormat; // init to avoid warning + switch (d->fRandom->nextULessThan(3)) { + case 0: + format = kA8_GrMaskFormat; + break; + case 1: + format = kA565_GrMaskFormat; + break; + case 2: + format = kARGB_GrMaskFormat; + break; + } + + return GrBitmapTextGeoProc::Make(*d->caps()->shaderCaps(), + SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)), + d->fRandom->nextBool(), + proxies, 1, samplerState, format, + GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool()); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.h b/gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.h new file mode 100644 index 0000000000..3c29c4a585 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBitmapTextGeoProc.h @@ -0,0 +1,80 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBitmapTextGeoProc_DEFINED +#define GrBitmapTextGeoProc_DEFINED + +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProcessor.h" + +class GrGLBitmapTextGeoProc; +class GrInvariantOutput; + +/** + * The output color of this effect is a modulation of the input color and a sample from a texture. + * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input + * coords are a custom attribute. + */ +class GrBitmapTextGeoProc : public GrGeometryProcessor { +public: + static constexpr int kMaxTextures = 4; + + static sk_sp Make(const GrShaderCaps& caps, + const SkPMColor4f& color, bool wideColor, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& p, GrMaskFormat format, + const SkMatrix& localMatrix, bool usesW) { + return sk_sp( + new GrBitmapTextGeoProc(caps, color, wideColor, proxies, numActiveProxies, p, format, + localMatrix, usesW)); + } + + ~GrBitmapTextGeoProc() override {} + + const char* name() const override { return "Texture"; } + + const Attribute& inPosition() const { return fInPosition; } + const Attribute& inColor() const { return fInColor; } + const Attribute& inTextureCoords() const { return fInTextureCoords; } + GrMaskFormat maskFormat() const { return fMaskFormat; } + const SkPMColor4f& color() const { return fColor; } + bool hasVertexColor() const { return fInColor.isInitialized(); } + const SkMatrix& localMatrix() const { return fLocalMatrix; } + bool usesW() const { return fUsesW; } + const SkISize& atlasSize() const { return fAtlasSize; } + + void addNewProxies(const sk_sp*, int numActiveProxies, const GrSamplerState&); + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override; + +private: + GrBitmapTextGeoProc(const GrShaderCaps&, const SkPMColor4f&, bool wideColor, + const sk_sp* proxies, int numProxies, + const GrSamplerState& params, GrMaskFormat format, + const SkMatrix& localMatrix, bool usesW); + + const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; } + + SkPMColor4f fColor; + SkMatrix fLocalMatrix; + bool fUsesW; + SkISize fAtlasSize; // size for all textures used with fTextureSamplers[]. + TextureSampler fTextureSamplers[kMaxTextures]; + Attribute fInPosition; + Attribute fInColor; + Attribute fInTextureCoords; + GrMaskFormat fMaskFormat; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp new file mode 100644 index 0000000000..6126c7d5f6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +enum class Mode { + kGaussian = 0, + kSmoothStep = 1 +}; + +layout(key) in Mode mode; + +void main() { + half factor = 1.0 - sk_InColor.a; + @switch (mode) { + case Mode::kGaussian: + factor = half(exp(-factor * factor * 4.0) - 0.018); + break; + case Mode::kSmoothStep: + factor = smoothstep(1.0, 0.0, factor); + break; + } + sk_OutColor = half4(factor); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrCircleBlurFragmentProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrCircleBlurFragmentProcessor.fp new file mode 100644 index 0000000000..4228a0333f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrCircleBlurFragmentProcessor.fp @@ -0,0 +1,292 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in half4 circleRect; +in half textureRadius; +in half solidRadius; +in uniform sampler2D blurProfileSampler; + +// The data is formatted as: +// x, y - the center of the circle +// z - inner radius that should map to 0th entry in the texture. +// w - the inverse of the distance over which the texture is stretched. +uniform half4 circleData; + +@optimizationFlags { + kCompatibleWithCoverageAsAlpha_OptimizationFlag +} + +@make { + static std::unique_ptr Make(GrProxyProvider*, + const SkRect& circle, float sigma); +} + +@setData(data) { + data.set4f(circleData, circleRect.centerX(), circleRect.centerY(), solidRadius, + 1.f / textureRadius); +} + +@cpp { + #include "src/gpu/GrProxyProvider.h" + + // Computes an unnormalized half kernel (right side). Returns the summation of all the half + // kernel values. + static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) { + const float invSigma = 1.f / sigma; + const float b = -0.5f * invSigma * invSigma; + float tot = 0.0f; + // Compute half kernel values at half pixel steps out from the center. + float t = 0.5f; + for (int i = 0; i < halfKernelSize; ++i) { + float value = expf(t * t * b); + tot += value; + halfKernel[i] = value; + t += 1.f; + } + return tot; + } + + // Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number + // of discrete steps. The half kernel is normalized to sum to 0.5. + static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel, + int halfKernelSize, float sigma) { + // The half kernel should sum to 0.5 not 1.0. + const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma); + float sum = 0.f; + for (int i = 0; i < halfKernelSize; ++i) { + halfKernel[i] /= tot; + sum += halfKernel[i]; + summedHalfKernel[i] = sum; + } + } + + // Applies the 1D half kernel vertically at points along the x axis to a circle centered at the + // origin with radius circleR. + void apply_kernel_in_y(float* results, int numSteps, float firstX, float circleR, + int halfKernelSize, const float* summedHalfKernelTable) { + float x = firstX; + for (int i = 0; i < numSteps; ++i, x += 1.f) { + if (x < -circleR || x > circleR) { + results[i] = 0; + continue; + } + float y = sqrtf(circleR * circleR - x * x); + // In the column at x we exit the circle at +y and -y + // The summed table entry j is actually reflects an offset of j + 0.5. + y -= 0.5f; + int yInt = SkScalarFloorToInt(y); + SkASSERT(yInt >= -1); + if (y < 0) { + results[i] = (y + 0.5f) * summedHalfKernelTable[0]; + } else if (yInt >= halfKernelSize - 1) { + results[i] = 0.5f; + } else { + float yFrac = y - yInt; + results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] + + yFrac * summedHalfKernelTable[yInt + 1]; + } + } + } + + // Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR. + // This relies on having a half kernel computed for the Gaussian and a table of applications of + // the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX + + // halfKernel) passed in as yKernelEvaluations. + static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int halfKernelSize, + const float* yKernelEvaluations) { + float acc = 0; + + float x = evalX - halfKernelSize; + for (int i = 0; i < halfKernelSize; ++i, x += 1.f) { + if (x < -circleR || x > circleR) { + continue; + } + float verticalEval = yKernelEvaluations[i]; + acc += verticalEval * halfKernel[halfKernelSize - i - 1]; + } + for (int i = 0; i < halfKernelSize; ++i, x += 1.f) { + if (x < -circleR || x > circleR) { + continue; + } + float verticalEval = yKernelEvaluations[i + halfKernelSize]; + acc += verticalEval * halfKernel[i]; + } + // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about + // the x axis). + return SkUnitScalarClampToByte(2.f * acc); + } + + // This function creates a profile of a blurred circle. It does this by computing a kernel for + // half the Gaussian and a matching summed area table. The summed area table is used to compute + // an array of vertical applications of the half kernel to the circle along the x axis. The + // table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is + // the size of the profile being computed. Then for each of the n profile entries we walk out k + // steps in each horizontal direction multiplying the corresponding y evaluation by the half + // kernel entry and sum these values to compute the profile entry. + static void create_circle_profile(uint8_t* weights, float sigma, float circleR, + int profileTextureWidth) { + const int numSteps = profileTextureWidth; + + // The full kernel is 6 sigmas wide. + int halfKernelSize = SkScalarCeilToInt(6.0f * sigma); + // round up to next multiple of 2 and then divide by 2 + halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; + + // Number of x steps at which to apply kernel in y to cover all the profile samples in x. + int numYSteps = numSteps + 2 * halfKernelSize; + + SkAutoTArray bulkAlloc(halfKernelSize + halfKernelSize + numYSteps); + float* halfKernel = bulkAlloc.get(); + float* summedKernel = bulkAlloc.get() + halfKernelSize; + float* yEvals = bulkAlloc.get() + 2 * halfKernelSize; + make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma); + + float firstX = -halfKernelSize + 0.5f; + apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel); + + for (int i = 0; i < numSteps - 1; ++i) { + float evalX = i + 0.5f; + weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i); + } + // Ensure the tail of the Gaussian goes to zero. + weights[numSteps - 1] = 0; + } + + static void create_half_plane_profile(uint8_t* profile, int profileWidth) { + SkASSERT(!(profileWidth & 0x1)); + // The full kernel is 6 sigmas wide. + float sigma = profileWidth / 6.f; + int halfKernelSize = profileWidth / 2; + + SkAutoTArray halfKernel(halfKernelSize); + + // The half kernel should sum to 0.5. + const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, + sigma); + float sum = 0.f; + // Populate the profile from the right edge to the middle. + for (int i = 0; i < halfKernelSize; ++i) { + halfKernel[halfKernelSize - i - 1] /= tot; + sum += halfKernel[halfKernelSize - i - 1]; + profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum); + } + // Populate the profile from the middle to the left edge (by flipping the half kernel and + // continuing the summation). + for (int i = 0; i < halfKernelSize; ++i) { + sum += halfKernel[i]; + profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum); + } + // Ensure tail goes to 0. + profile[profileWidth - 1] = 0; + } + + static sk_sp create_profile_texture(GrProxyProvider* proxyProvider, + const SkRect& circle, + float sigma, + float* solidRadius, float* textureRadius) { + float circleR = circle.width() / 2.0f; + if (circleR < SK_ScalarNearlyZero) { + return nullptr; + } + // Profile textures are cached by the ratio of sigma to circle radius and by the size of the + // profile texture (binned by powers of 2). + SkScalar sigmaToCircleRRatio = sigma / circleR; + // When sigma is really small this becomes a equivalent to convolving a Gaussian with a + // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the + // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet + // implemented this latter optimization. + sigmaToCircleRRatio = SkTMin(sigmaToCircleRRatio, 8.f); + SkFixed sigmaToCircleRRatioFixed; + static const SkScalar kHalfPlaneThreshold = 0.1f; + bool useHalfPlaneApprox = false; + if (sigmaToCircleRRatio <= kHalfPlaneThreshold) { + useHalfPlaneApprox = true; + sigmaToCircleRRatioFixed = 0; + *solidRadius = circleR - 3 * sigma; + *textureRadius = 6 * sigma; + } else { + // Convert to fixed point for the key. + sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio); + // We shave off some bits to reduce the number of unique entries. We could probably + // shave off more than we do. + sigmaToCircleRRatioFixed &= ~0xff; + sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed); + sigma = circleR * sigmaToCircleRRatio; + *solidRadius = 0; + *textureRadius = circleR + 3 * sigma; + } + + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur"); + builder[0] = sigmaToCircleRRatioFixed; + builder.finish(); + + sk_sp blurProfile = proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin); + if (!blurProfile) { + static constexpr int kProfileTextureWidth = 512; + + SkBitmap bm; + if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) { + return nullptr; + } + + if (useHalfPlaneApprox) { + create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth); + } else { + // Rescale params to the size of the texture we're creating. + SkScalar scale = kProfileTextureWidth / *textureRadius; + create_circle_profile(bm.getAddr8(0, 0), sigma * scale, circleR * scale, + kProfileTextureWidth); + } + + bm.setImmutable(); + sk_sp image = SkImage::MakeFromBitmap(bm); + + blurProfile = proxyProvider->createTextureProxy(std::move(image), 1, + SkBudgeted::kYes, SkBackingFit::kExact); + if (!blurProfile) { + return nullptr; + } + + SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get()); + } + + return blurProfile; + } + + std::unique_ptr GrCircleBlurFragmentProcessor::Make( + GrProxyProvider* proxyProvider, const SkRect& circle, float sigma) { + float solidRadius; + float textureRadius; + sk_sp profile(create_profile_texture(proxyProvider, circle, sigma, + &solidRadius, &textureRadius)); + if (!profile) { + return nullptr; + } + return std::unique_ptr(new GrCircleBlurFragmentProcessor( + circle, textureRadius, solidRadius, std::move(profile))); + } +} + +void main() { + // We just want to compute "(length(vec) - circleData.z + 0.5) * circleData.w" but need to + // rearrange for precision. + half2 vec = half2(half((sk_FragCoord.x - circleData.x) * circleData.w), + half((sk_FragCoord.y - circleData.y) * circleData.w)); + half dist = length(vec) + (0.5 - circleData.z) * circleData.w; + sk_OutColor = sk_InColor * sample(blurProfileSampler, half2(dist, 0.5)).a; +} + +@test(testData) { + SkScalar wh = testData->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar sigma = testData->fRandom->nextRangeF(1.f,10.f); + SkRect circle = SkRect::MakeWH(wh, wh); + return GrCircleBlurFragmentProcessor::Make(testData->proxyProvider(), circle, sigma); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrCircleEffect.fp b/gfx/skia/skia/src/gpu/effects/GrCircleEffect.fp new file mode 100644 index 0000000000..957497a3fc --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrCircleEffect.fp @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +layout(key) in GrClipEdgeType edgeType; +in float2 center; +in float radius; + +float2 prevCenter; +float prevRadius = -1; +// The circle uniform is (center.x, center.y, radius + 0.5, 1 / (radius + 0.5)) for regular +// fills and (..., radius - 0.5, 1 / (radius - 0.5)) for inverse fills. +uniform float4 circle; + +@make { + static std::unique_ptr Make(GrClipEdgeType edgeType, SkPoint center, + float radius) { + // A radius below half causes the implicit insetting done by this processor to become + // inverted. We could handle this case by making the processor code more complicated. + if (radius < .5f && GrProcessorEdgeTypeIsInverseFill(edgeType)) { + return nullptr; + } + return std::unique_ptr(new GrCircleEffect(edgeType, center, radius)); + } +} + +@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag } + +@setData(pdman) { + if (radius != prevRadius || center != prevCenter) { + SkScalar effectiveRadius = radius; + if (GrProcessorEdgeTypeIsInverseFill((GrClipEdgeType) edgeType)) { + effectiveRadius -= 0.5f; + // When the radius is 0.5 effectiveRadius is 0 which causes an inf * 0 in the shader. + effectiveRadius = SkTMax(0.001f, effectiveRadius); + } else { + effectiveRadius += 0.5f; + } + pdman.set4f(circle, center.fX, center.fY, effectiveRadius, + SkScalarInvert(effectiveRadius)); + prevCenter = center; + prevRadius = radius; + } +} + +void main() { + // TODO: Right now the distance to circle calculation is performed in a space normalized to the + // radius and then denormalized. This is to mitigate overflow on devices that don't have full + // float. + half d; + @if (edgeType == GrClipEdgeType::kInverseFillBW || + edgeType == GrClipEdgeType::kInverseFillAA) { + d = half((length((circle.xy - sk_FragCoord.xy) * circle.w) - 1.0) * circle.z); + } else { + d = half((1.0 - length((circle.xy - sk_FragCoord.xy) * circle.w)) * circle.z); + } + @if (edgeType == GrClipEdgeType::kFillAA || + edgeType == GrClipEdgeType::kInverseFillAA || + edgeType == GrClipEdgeType::kHairlineAA) { + sk_OutColor = sk_InColor * saturate(d); + } else { + sk_OutColor = d > 0.5 ? sk_InColor : half4(0); + } +} + +@test(testData) { + SkPoint center; + center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f); + center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f); + SkScalar radius = testData->fRandom->nextRangeF(1.f, 1000.f); + GrClipEdgeType et; + do { + et = (GrClipEdgeType) testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt); + } while (GrClipEdgeType::kHairlineAA == et); + return GrCircleEffect::Make(et, center, radius); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrColorMatrixFragmentProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrColorMatrixFragmentProcessor.fp new file mode 100644 index 0000000000..74203b5c7f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrColorMatrixFragmentProcessor.fp @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +layout(ctype=SkMatrix44, tracked) in uniform half4x4 m; +layout(ctype=SkVector4, tracked) in uniform half4 v; +layout(key) in bool unpremulInput; +layout(key) in bool clampRGBOutput; +layout(key) in bool premulOutput; + +@optimizationFlags { + kConstantOutputForConstantInput_OptimizationFlag +} + +void main() { + half4 inputColor = sk_InColor; + @if (unpremulInput) { + // The max() is to guard against 0 / 0 during unpremul when the incoming color is + // transparent black. + half nonZeroAlpha = max(inputColor.a, 0.0001); + inputColor = half4(inputColor.rgb / nonZeroAlpha, nonZeroAlpha); + } + sk_OutColor = m * inputColor + v; + @if (clampRGBOutput) { + sk_OutColor = saturate(sk_OutColor); + } else { + sk_OutColor.a = saturate(sk_OutColor.a); + } + @if (premulOutput) { + sk_OutColor.rgb *= sk_OutColor.a; + } +} + +@class { + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + SkColor4f color; + if (unpremulInput) { + color = input.unpremul(); + } else { + color.fR = input.fR; + color.fG = input.fG; + color.fB = input.fB; + color.fA = input.fA; + } + m.mapScalars(color.vec()); + color.fR += v.fData[0]; + color.fG += v.fData[1]; + color.fB += v.fData[2]; + color.fA += v.fData[3]; + color.fA = SkTPin(color.fA, 0.f, 1.f); + if (clampRGBOutput) { + color.fR = SkTPin(color.fR, 0.f, 1.f); + color.fG = SkTPin(color.fG, 0.f, 1.f); + color.fB = SkTPin(color.fB, 0.f, 1.f); + } + if (premulOutput) { + return color.premul(); + } else { + return {color.fR, color.fG, color.fB, color.fA}; + } + } +} + +@make { + static std::unique_ptr Make(const float matrix[20], bool unpremulInput, bool clampRGBOutput, bool premulOutput) { + SkMatrix44 m44; + m44.set4x4( + matrix[0], matrix[5], matrix[10], matrix[15], + matrix[1], matrix[6], matrix[11], matrix[16], + matrix[2], matrix[7], matrix[12], matrix[17], + matrix[3], matrix[8], matrix[13], matrix[18] + ); + auto v4 = SkVector4(matrix[4], matrix[9], matrix[14], matrix[19]); + return std::unique_ptr(new GrColorMatrixFragmentProcessor(m44, v4, unpremulInput, clampRGBOutput, premulOutput)); + } +} + +@test(d) { + float m[20]; + for (int i = 0; i < 20; ++i) { + m[i] = d->fRandom->nextRangeScalar(-10.f, 10.f); + } + bool unpremul = d->fRandom->nextBool(); + bool clampRGB = d->fRandom->nextBool(); + bool premul = d->fRandom->nextBool(); + return Make(m, unpremul, clampRGB, premul); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrComposeLerpEffect.fp b/gfx/skia/skia/src/gpu/effects/GrComposeLerpEffect.fp new file mode 100644 index 0000000000..8b03de228d --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrComposeLerpEffect.fp @@ -0,0 +1,16 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in fragmentProcessor? child1; +in fragmentProcessor? child2; +in uniform float weight; + +void main() { + sk_OutColor = mix(child1 != null ? sample(child1) : sk_InColor, + child2 != null ? sample(child2) : sk_InColor, + half(weight)); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrComposeLerpRedEffect.fp b/gfx/skia/skia/src/gpu/effects/GrComposeLerpRedEffect.fp new file mode 100644 index 0000000000..e0f7486c4e --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrComposeLerpRedEffect.fp @@ -0,0 +1,16 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in fragmentProcessor? child1; +in fragmentProcessor? child2; +in fragmentProcessor lerp; + +void main() { + sk_OutColor = mix(child1 != null ? sample(child1) : sk_InColor, + child2 != null ? sample(child2) : sk_InColor, + sample(lerp).r); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrConfigConversionEffect.fp b/gfx/skia/skia/src/gpu/effects/GrConfigConversionEffect.fp new file mode 100644 index 0000000000..0387c3b39c --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrConfigConversionEffect.fp @@ -0,0 +1,174 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +@header { + #include "include/gpu/GrContext.h" + #include "src/gpu/GrClip.h" + #include "src/gpu/GrContextPriv.h" + #include "src/gpu/GrImageInfo.h" + #include "src/gpu/GrProxyProvider.h" + #include "src/gpu/GrRenderTargetContext.h" +} + +@class { + static bool TestForPreservingPMConversions(GrContext* context) { + static constexpr int kSize = 256; + static constexpr GrColorType kColorType = GrColorType::kRGBA_8888; + SkAutoTMalloc data(kSize * kSize * 3); + uint32_t* srcData = data.get(); + uint32_t* firstRead = data.get() + kSize * kSize; + uint32_t* secondRead = data.get() + 2 * kSize * kSize; + + // Fill with every possible premultiplied A, color channel value. There will be 256-y + // duplicate values in row y. We set r, g, and b to the same value since they are handled + // identically. + for (int y = 0; y < kSize; ++y) { + for (int x = 0; x < kSize; ++x) { + uint8_t* color = reinterpret_cast(&srcData[kSize*y + x]); + color[3] = y; + color[2] = SkTMin(x, y); + color[1] = SkTMin(x, y); + color[0] = SkTMin(x, y); + } + } + memset(firstRead, 0, kSize * kSize * sizeof(uint32_t)); + memset(secondRead, 0, kSize * kSize * sizeof(uint32_t)); + + const SkImageInfo ii = SkImageInfo::Make(kSize, kSize, + kRGBA_8888_SkColorType, kPremul_SkAlphaType); + + auto readRTC = + context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, + kSize, kSize, + kColorType, nullptr); + auto tempRTC = + context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, + kSize, kSize, + kColorType, nullptr); + if (!readRTC || !readRTC->asTextureProxy() || !tempRTC) { + return false; + } + // Adding discard to appease vulkan validation warning about loading uninitialized data on + // draw + readRTC->discard(); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + SkPixmap pixmap(ii, srcData, 4 * kSize); + + // This function is only ever called if we are in a GrContext that has a GrGpu since we are + // calling read pixels here. Thus the pixel data will be uploaded immediately and we don't + // need to keep the pixel data alive in the proxy. Therefore the ReleaseProc is nullptr. + sk_sp image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr); + GrColorType dataColorType = SkColorTypeToGrColorType(image->colorType()); + sk_sp dataProxy = proxyProvider->createTextureProxy(std::move(image), + 1, + SkBudgeted::kYes, + SkBackingFit::kExact); + if (!dataProxy) { + return false; + } + + static const SkRect kRect = SkRect::MakeIWH(kSize, kSize); + + // We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw + // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data. + // We then verify that two reads produced the same values. + + GrPaint paint1; + GrPaint paint2; + GrPaint paint3; + std::unique_ptr pmToUPM( + new GrConfigConversionEffect(PMConversion::kToUnpremul)); + std::unique_ptr upmToPM( + new GrConfigConversionEffect(PMConversion::kToPremul)); + + paint1.addColorTextureProcessor(dataProxy, dataColorType, SkMatrix::I()); + paint1.addColorFragmentProcessor(pmToUPM->clone()); + paint1.setPorterDuffXPFactory(SkBlendMode::kSrc); + + readRTC->fillRectToRect(GrNoClip(), std::move(paint1), GrAA::kNo, SkMatrix::I(), kRect, + kRect); + if (!readRTC->readPixels(ii, firstRead, 0, {0, 0})) { + return false; + } + + // Adding discard to appease vulkan validation warning about loading uninitialized data on + // draw + tempRTC->discard(); + + paint2.addColorTextureProcessor(readRTC->asTextureProxyRef(), kColorType, SkMatrix::I()); + paint2.addColorFragmentProcessor(std::move(upmToPM)); + paint2.setPorterDuffXPFactory(SkBlendMode::kSrc); + + tempRTC->fillRectToRect(GrNoClip(), std::move(paint2), GrAA::kNo, SkMatrix::I(), kRect, + kRect); + + paint3.addColorTextureProcessor(tempRTC->asTextureProxyRef(), kColorType, SkMatrix::I()); + paint3.addColorFragmentProcessor(std::move(pmToUPM)); + paint3.setPorterDuffXPFactory(SkBlendMode::kSrc); + + readRTC->fillRectToRect(GrNoClip(), std::move(paint3), GrAA::kNo, SkMatrix::I(), kRect, + kRect); + + if (!readRTC->readPixels(ii, secondRead, 0, {0, 0})) { + return false; + } + + for (int y = 0; y < kSize; ++y) { + for (int x = 0; x <= y; ++x) { + if (firstRead[kSize * y + x] != secondRead[kSize * y + x]) { + return false; + } + } + } + + return true; + } +} + +@make { + static std::unique_ptr Make(std::unique_ptr fp, + PMConversion pmConversion) { + if (!fp) { + return nullptr; + } + std::unique_ptr ccFP(new GrConfigConversionEffect(pmConversion)); + std::unique_ptr fpPipeline[] = { std::move(fp), std::move(ccFP) }; + return GrFragmentProcessor::RunInSeries(fpPipeline, 2); + } +} + +layout(key) in PMConversion pmConversion; + +@emitCode { + fragBuilder->forceHighPrecision(); +} + +void main() { + // Aggressively round to the nearest exact (N / 255) floating point value. This lets us find a + // round-trip preserving pair on some GPUs that do odd byte to float conversion. + sk_OutColor = floor(sk_InColor * 255 + 0.5) / 255; + + @switch (pmConversion) { + case PMConversion::kToPremul: + sk_OutColor.rgb = floor(sk_OutColor.rgb * sk_OutColor.a * 255 + 0.5) / 255; + break; + + case PMConversion::kToUnpremul: + sk_OutColor.rgb = sk_OutColor.a <= 0.0 ? + half3(0) : + floor(sk_OutColor.rgb / sk_OutColor.a * 255 + 0.5) / 255; + break; + } +} + +@test(data) { + PMConversion pmConv = static_cast(data->fRandom->nextULessThan( + (int) PMConversion::kPMConversionCnt)); + return std::unique_ptr(new GrConfigConversionEffect(pmConv)); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrConstColorProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrConstColorProcessor.fp new file mode 100644 index 0000000000..18cc91a4b1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrConstColorProcessor.fp @@ -0,0 +1,86 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +enum class InputMode { + kIgnore, + kModulateRGBA, + kModulateA, + + kLast = kModulateA +}; + +layout(ctype=SkPMColor4f, tracked) in uniform half4 color; +layout(key) in InputMode mode; + +@optimizationFlags { + OptFlags(color, mode) +} + +void main() { + @switch (mode) { + case InputMode::kIgnore: + sk_OutColor = color; + break; + case InputMode::kModulateRGBA: + sk_OutColor = sk_InColor * color; + break; + case InputMode::kModulateA: + sk_OutColor = sk_InColor.a * color; + break; + } +} + +@class { + static const int kInputModeCnt = (int) InputMode::kLast + 1; + + static OptimizationFlags OptFlags(const SkPMColor4f& color, InputMode mode) { + OptimizationFlags flags = kConstantOutputForConstantInput_OptimizationFlag; + if (mode != InputMode::kIgnore) { + flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } + if (color.isOpaque()) { + flags |= kPreservesOpaqueInput_OptimizationFlag; + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + switch (mode) { + case InputMode::kIgnore: + return color; + case InputMode::kModulateA: + return color * input.fA; + case InputMode::kModulateRGBA: + return color * input; + } + SK_ABORT("Unexpected mode"); + } +} + +@test(d) { + SkPMColor4f color; + int colorPicker = d->fRandom->nextULessThan(3); + switch (colorPicker) { + case 0: { + uint32_t a = d->fRandom->nextULessThan(0x100); + uint32_t r = d->fRandom->nextULessThan(a+1); + uint32_t g = d->fRandom->nextULessThan(a+1); + uint32_t b = d->fRandom->nextULessThan(a+1); + color = SkPMColor4f::FromBytes_RGBA(GrColorPackRGBA(r, g, b, a)); + break; + } + case 1: + color = SK_PMColor4fTRANSPARENT; + break; + case 2: + uint32_t c = d->fRandom->nextULessThan(0x100); + color = SkPMColor4f::FromBytes_RGBA(c | (c << 8) | (c << 16) | (c << 24)); + break; + } + InputMode mode = static_cast(d->fRandom->nextULessThan(kInputModeCnt)); + return GrConstColorProcessor::Make(color, mode); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.cpp new file mode 100644 index 0000000000..64911cc78f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkPathPriv.h" +#include "src/gpu/effects/GrConvexPolyEffect.h" +#include "src/gpu/effects/generated/GrAARectEffect.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +////////////////////////////////////////////////////////////////////////////// + +class GrGLConvexPolyEffect : public GrGLSLFragmentProcessor { +public: + GrGLConvexPolyEffect() { + for (size_t i = 0; i < SK_ARRAY_COUNT(fPrevEdges); ++i) { + fPrevEdges[i] = SK_ScalarNaN; + } + } + + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fEdgeUniform; + SkScalar fPrevEdges[3 * GrConvexPolyEffect::kMaxEdges]; + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GrGLConvexPolyEffect::emitCode(EmitArgs& args) { + const GrConvexPolyEffect& cpe = args.fFp.cast(); + + const char *edgeArrayName; + fEdgeUniform = args.fUniformHandler->addUniformArray(kFragment_GrShaderFlag, + kHalf3_GrSLType, + "edges", + cpe.getEdgeCount(), + &edgeArrayName); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + fragBuilder->codeAppend("\t\thalf alpha = 1.0;\n"); + fragBuilder->codeAppend("\t\thalf edge;\n"); + for (int i = 0; i < cpe.getEdgeCount(); ++i) { + fragBuilder->codeAppendf("\t\tedge = dot(%s[%d], half3(half(sk_FragCoord.x), " + "half(sk_FragCoord.y), " + "1));\n", + edgeArrayName, i); + if (GrProcessorEdgeTypeIsAA(cpe.getEdgeType())) { + fragBuilder->codeAppend("\t\tedge = saturate(edge);\n"); + } else { + fragBuilder->codeAppend("\t\tedge = edge >= 0.5 ? 1.0 : 0.0;\n"); + } + fragBuilder->codeAppend("\t\talpha *= edge;\n"); + } + + if (GrProcessorEdgeTypeIsInverseFill(cpe.getEdgeType())) { + fragBuilder->codeAppend("\talpha = 1.0 - alpha;\n"); + } + fragBuilder->codeAppendf("\t%s = %s * alpha;\n", args.fOutputColor, args.fInputColor); +} + +void GrGLConvexPolyEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& effect) { + const GrConvexPolyEffect& cpe = effect.cast(); + size_t byteSize = 3 * cpe.getEdgeCount() * sizeof(SkScalar); + if (0 != memcmp(fPrevEdges, cpe.getEdges(), byteSize)) { + pdman.set3fv(fEdgeUniform, cpe.getEdgeCount(), cpe.getEdges()); + memcpy(fPrevEdges, cpe.getEdges(), byteSize); + } +} + +void GrGLConvexPolyEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrConvexPolyEffect& cpe = processor.cast(); + GR_STATIC_ASSERT(kGrClipEdgeTypeCnt <= 8); + uint32_t key = (cpe.getEdgeCount() << 3) | (int) cpe.getEdgeType(); + b->add32(key); +} + +////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrConvexPolyEffect::Make(GrClipEdgeType type, + const SkPath& path) { + if (GrClipEdgeType::kHairlineAA == type) { + return nullptr; + } + if (path.getSegmentMasks() != SkPath::kLine_SegmentMask || + !path.isConvex()) { + return nullptr; + } + + SkPathPriv::FirstDirection dir; + // The only way this should fail is if the clip is effectively a infinitely thin line. In that + // case nothing is inside the clip. It'd be nice to detect this at a higher level and either + // skip the draw or omit the clip element. + if (!SkPathPriv::CheapComputeFirstDirection(path, &dir)) { + if (GrProcessorEdgeTypeIsInverseFill(type)) { + return GrConstColorProcessor::Make(SK_PMColor4fWHITE, + GrConstColorProcessor::InputMode::kModulateRGBA); + } + // This could use kIgnore instead of kModulateRGBA but it would trigger a debug print + // about a coverage processor not being compatible with the alpha-as-coverage optimization. + // We don't really care about this unlikely case so we just use kModulateRGBA to suppress + // the print. + return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kModulateRGBA); + } + + SkScalar edges[3 * kMaxEdges]; + SkPoint pts[4]; + SkPath::Verb verb; + SkPath::Iter iter(path, true); + + // SkPath considers itself convex so long as there is a convex contour within it, + // regardless of any degenerate contours such as a string of moveTos before it. + // Iterate here to consume any degenerate contours and only process the points + // on the actual convex contour. + int n = 0; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + SkASSERT(n == 0); + case SkPath::kClose_Verb: + break; + case SkPath::kLine_Verb: { + if (n >= kMaxEdges) { + return nullptr; + } + if (pts[0] != pts[1]) { + SkVector v = pts[1] - pts[0]; + v.normalize(); + if (SkPathPriv::kCCW_FirstDirection == dir) { + edges[3 * n] = v.fY; + edges[3 * n + 1] = -v.fX; + } else { + edges[3 * n] = -v.fY; + edges[3 * n + 1] = v.fX; + } + edges[3 * n + 2] = -(edges[3 * n] * pts[1].fX + edges[3 * n + 1] * pts[1].fY); + ++n; + } + break; + } + default: + return nullptr; + } + } + + if (path.isInverseFillType()) { + type = GrInvertProcessorEdgeType(type); + } + return Make(type, n, edges); +} + +std::unique_ptr GrConvexPolyEffect::Make(GrClipEdgeType edgeType, + const SkRect& rect) { + if (GrClipEdgeType::kHairlineAA == edgeType){ + return nullptr; + } + return GrAARectEffect::Make(edgeType, rect); +} + +GrConvexPolyEffect::~GrConvexPolyEffect() {} + +void GrConvexPolyEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLConvexPolyEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrConvexPolyEffect::onCreateGLSLInstance() const { + return new GrGLConvexPolyEffect; +} + +GrConvexPolyEffect::GrConvexPolyEffect(GrClipEdgeType edgeType, int n, const SkScalar edges[]) + : INHERITED(kGrConvexPolyEffect_ClassID, kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fEdgeType(edgeType) + , fEdgeCount(n) { + // Factory function should have already ensured this. + SkASSERT(n <= kMaxEdges); + memcpy(fEdges, edges, 3 * n * sizeof(SkScalar)); + // Outset the edges by 0.5 so that a pixel with center on an edge is 50% covered in the AA case + // and 100% covered in the non-AA case. + for (int i = 0; i < n; ++i) { + fEdges[3 * i + 2] += SK_ScalarHalf; + } +} + +GrConvexPolyEffect::GrConvexPolyEffect(const GrConvexPolyEffect& that) + : INHERITED(kGrConvexPolyEffect_ClassID, kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fEdgeType(that.fEdgeType) + , fEdgeCount(that.fEdgeCount) { + memcpy(fEdges, that.fEdges, 3 * that.fEdgeCount * sizeof(SkScalar)); +} + +std::unique_ptr GrConvexPolyEffect::clone() const { + return std::unique_ptr(new GrConvexPolyEffect(*this)); +} + +bool GrConvexPolyEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrConvexPolyEffect& cpe = other.cast(); + // ignore the fact that 0 == -0 and just use memcmp. + return (cpe.fEdgeType == fEdgeType && cpe.fEdgeCount == fEdgeCount && + 0 == memcmp(cpe.fEdges, fEdges, 3 * fEdgeCount * sizeof(SkScalar))); +} + +////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConvexPolyEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrConvexPolyEffect::TestCreate(GrProcessorTestData* d) { + int count = d->fRandom->nextULessThan(kMaxEdges) + 1; + SkScalar edges[kMaxEdges * 3]; + for (int i = 0; i < 3 * count; ++i) { + edges[i] = d->fRandom->nextSScalar1(); + } + + std::unique_ptr fp; + do { + GrClipEdgeType edgeType = static_cast( + d->fRandom->nextULessThan(kGrClipEdgeTypeCnt)); + fp = GrConvexPolyEffect::Make(edgeType, count, edges); + } while (nullptr == fp); + return fp; +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.h b/gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.h new file mode 100644 index 0000000000..2bb33736f8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrConvexPolyEffect.h @@ -0,0 +1,92 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrConvexPolyEffect_DEFINED +#define GrConvexPolyEffect_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrProcessor.h" + +class GrInvariantOutput; +class SkPath; + +/** + * An effect that renders a convex polygon. It is intended to be used as a coverage effect. + * Bounding geometry is rendered and the effect computes coverage based on the fragment's + * position relative to the polygon. + */ +class GrConvexPolyEffect : public GrFragmentProcessor { +public: + enum { + kMaxEdges = 8, + }; + + /** + * edges is a set of n edge equations where n is limited to kMaxEdges. It contains 3*n values. + * The edges should form a convex polygon. The positive half-plane is considered to be the + * inside. The equations should be normalized such that the first two coefficients are a unit + * 2d vector. + * + * Currently the edges are specified in device space. In the future we may prefer to specify + * them in src space. There are a number of ways this could be accomplished but we'd probably + * have to modify the effect/shaderbuilder interface to make it possible (e.g. give access + * to the view matrix or untransformed positions in the fragment shader). + */ + static std::unique_ptr Make(GrClipEdgeType edgeType, int n, + const SkScalar edges[]) { + if (n <= 0 || n > kMaxEdges || GrClipEdgeType::kHairlineAA == edgeType) { + return nullptr; + } + return std::unique_ptr(new GrConvexPolyEffect(edgeType, n, edges)); + } + + /** + * Creates an effect that clips against the path. If the path is not a convex polygon, is + * inverse filled, or has too many edges, this will return nullptr. + */ + static std::unique_ptr Make(GrClipEdgeType, const SkPath&); + + /** + * Creates an effect that fills inside the rect with AA edges.. + */ + static std::unique_ptr Make(GrClipEdgeType, const SkRect&); + + ~GrConvexPolyEffect() override; + + const char* name() const override { return "ConvexPoly"; } + + std::unique_ptr clone() const override; + + GrClipEdgeType getEdgeType() const { return fEdgeType; } + + int getEdgeCount() const { return fEdgeCount; } + + const SkScalar* getEdges() const { return fEdges; } + +private: + GrConvexPolyEffect(GrClipEdgeType edgeType, int n, const SkScalar edges[]); + GrConvexPolyEffect(const GrConvexPolyEffect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& other) const override; + + GrClipEdgeType fEdgeType; + int fEdgeCount; + SkScalar fEdges[3 * kMaxEdges]; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.cpp b/gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.cpp new file mode 100644 index 0000000000..b75842c94a --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.cpp @@ -0,0 +1,234 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/effects/GrCoverageSetOpXP.h" +#include "src/gpu/glsl/GrGLSLBlend.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +class CoverageSetOpXP : public GrXferProcessor { +public: + CoverageSetOpXP(SkRegion::Op regionOp, bool invertCoverage) + : INHERITED(kCoverageSetOpXP_ClassID) + , fRegionOp(regionOp) + , fInvertCoverage(invertCoverage) {} + + const char* name() const override { return "Coverage Set Op"; } + + GrGLSLXferProcessor* createGLSLInstance() const override; + + bool invertCoverage() const { return fInvertCoverage; } + +private: + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + void onGetBlendInfo(GrXferProcessor::BlendInfo* blendInfo) const override; + + bool onIsEqual(const GrXferProcessor& xpBase) const override { + const CoverageSetOpXP& xp = xpBase.cast(); + return (fRegionOp == xp.fRegionOp && + fInvertCoverage == xp.fInvertCoverage); + } + + SkRegion::Op fRegionOp; + bool fInvertCoverage; + + typedef GrXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GLCoverageSetOpXP : public GrGLSLXferProcessor { +public: + GLCoverageSetOpXP(const GrProcessor&) {} + + ~GLCoverageSetOpXP() override {} + + static void GenKey(const GrProcessor& processor, const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) { + const CoverageSetOpXP& xp = processor.cast(); + uint32_t key = xp.invertCoverage() ? 0x0 : 0x1; + b->add32(key); + } + +private: + void emitOutputsForBlendState(const EmitArgs& args) override { + const CoverageSetOpXP& xp = args.fXP.cast(); + GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder; + + if (xp.invertCoverage()) { + fragBuilder->codeAppendf("%s = 1.0 - %s;", args.fOutputPrimary, args.fInputCoverage); + } else { + fragBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputCoverage); + } + } + + void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) override {} + + typedef GrGLSLXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +void CoverageSetOpXP::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLCoverageSetOpXP::GenKey(*this, caps, b); +} + +GrGLSLXferProcessor* CoverageSetOpXP::createGLSLInstance() const { + return new GLCoverageSetOpXP(*this); +} + +void CoverageSetOpXP::onGetBlendInfo(GrXferProcessor::BlendInfo* blendInfo) const { + switch (fRegionOp) { + case SkRegion::kReplace_Op: + blendInfo->fSrcBlend = kOne_GrBlendCoeff; + blendInfo->fDstBlend = kZero_GrBlendCoeff; + break; + case SkRegion::kIntersect_Op: + blendInfo->fSrcBlend = kDC_GrBlendCoeff; + blendInfo->fDstBlend = kZero_GrBlendCoeff; + break; + case SkRegion::kUnion_Op: + blendInfo->fSrcBlend = kOne_GrBlendCoeff; + blendInfo->fDstBlend = kISC_GrBlendCoeff; + break; + case SkRegion::kXOR_Op: + blendInfo->fSrcBlend = kIDC_GrBlendCoeff; + blendInfo->fDstBlend = kISC_GrBlendCoeff; + break; + case SkRegion::kDifference_Op: + blendInfo->fSrcBlend = kZero_GrBlendCoeff; + blendInfo->fDstBlend = kISC_GrBlendCoeff; + break; + case SkRegion::kReverseDifference_Op: + blendInfo->fSrcBlend = kIDC_GrBlendCoeff; + blendInfo->fDstBlend = kZero_GrBlendCoeff; + break; + } + blendInfo->fBlendConstant = SK_PMColor4fTRANSPARENT; +} + +/////////////////////////////////////////////////////////////////////////////// + +constexpr GrCoverageSetOpXPFactory::GrCoverageSetOpXPFactory(SkRegion::Op regionOp, + bool invertCoverage) + : fRegionOp(regionOp), fInvertCoverage(invertCoverage) {} + +const GrXPFactory* GrCoverageSetOpXPFactory::Get(SkRegion::Op regionOp, bool invertCoverage) { + // If these objects are constructed as static constexpr by cl.exe (2015 SP2) the vtables are + // null. +#ifdef SK_BUILD_FOR_WIN +#define _CONSTEXPR_ +#else +#define _CONSTEXPR_ constexpr +#endif + switch (regionOp) { + case SkRegion::kReplace_Op: { + if (invertCoverage) { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gReplaceCDXPFI( + SkRegion::kReplace_Op, true); + return &gReplaceCDXPFI; + } else { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gReplaceCDXPF( + SkRegion::kReplace_Op, false); + return &gReplaceCDXPF; + } + } + case SkRegion::kIntersect_Op: { + if (invertCoverage) { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gIntersectCDXPFI( + SkRegion::kIntersect_Op, true); + return &gIntersectCDXPFI; + } else { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gIntersectCDXPF( + SkRegion::kIntersect_Op, false); + return &gIntersectCDXPF; + } + } + case SkRegion::kUnion_Op: { + if (invertCoverage) { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gUnionCDXPFI(SkRegion::kUnion_Op, + true); + return &gUnionCDXPFI; + } else { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gUnionCDXPF(SkRegion::kUnion_Op, + false); + return &gUnionCDXPF; + } + } + case SkRegion::kXOR_Op: { + if (invertCoverage) { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gXORCDXPFI(SkRegion::kXOR_Op, + true); + return &gXORCDXPFI; + } else { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gXORCDXPF(SkRegion::kXOR_Op, + false); + return &gXORCDXPF; + } + } + case SkRegion::kDifference_Op: { + if (invertCoverage) { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gDifferenceCDXPFI( + SkRegion::kDifference_Op, true); + return &gDifferenceCDXPFI; + } else { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gDifferenceCDXPF( + SkRegion::kDifference_Op, false); + return &gDifferenceCDXPF; + } + } + case SkRegion::kReverseDifference_Op: { + if (invertCoverage) { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gRevDiffCDXPFI( + SkRegion::kReverseDifference_Op, true); + return &gRevDiffCDXPFI; + } else { + static _CONSTEXPR_ const GrCoverageSetOpXPFactory gRevDiffCDXPF( + SkRegion::kReverseDifference_Op, false); + return &gRevDiffCDXPF; + } + } + } +#undef _CONSTEXPR_ + SK_ABORT("Unknown region op."); +} + +sk_sp GrCoverageSetOpXPFactory::makeXferProcessor( + const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps& caps, + GrClampType) const { + // We don't support inverting coverage with mixed samples. We don't expect to ever want this in + // the future, however we could at some point make this work using an inverted coverage + // modulation table. Note that an inverted table still won't work if there are coverage procs. + if (fInvertCoverage && hasMixedSamples) { + SkASSERT(false); + return nullptr; + } + + return sk_sp(new CoverageSetOpXP(fRegionOp, fInvertCoverage)); +} + +GR_DEFINE_XP_FACTORY_TEST(GrCoverageSetOpXPFactory); + +#if GR_TEST_UTILS +const GrXPFactory* GrCoverageSetOpXPFactory::TestGet(GrProcessorTestData* d) { + SkRegion::Op regionOp = SkRegion::Op(d->fRandom->nextULessThan(SkRegion::kLastOp + 1)); + bool invertCoverage = d->fRandom->nextBool(); + return GrCoverageSetOpXPFactory::Get(regionOp, invertCoverage); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.h b/gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.h new file mode 100644 index 0000000000..3510505d44 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrCoverageSetOpXP.h @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCoverageSetOpXP_DEFINED +#define GrCoverageSetOpXP_DEFINED + +#include "include/core/SkRegion.h" +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrXferProcessor.h" + +// See the comment above GrXPFactory's definition about this warning suppression. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + +/** + * This xfer processor directly blends the the src coverage with the dst using a set operator. It is + * useful for rendering coverage masks using CSG. It can optionally invert the src coverage before + * applying the set operator. + */ +class GrCoverageSetOpXPFactory : public GrXPFactory { +public: + static const GrXPFactory* Get(SkRegion::Op regionOp, bool invertCoverage = false); + +private: + constexpr GrCoverageSetOpXPFactory(SkRegion::Op regionOp, bool invertCoverage); + + sk_sp makeXferProcessor(const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps&, + GrClampType) const override; + + AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&, + const GrProcessorAnalysisCoverage&, + const GrCaps&, + GrClampType) const override { + return AnalysisProperties::kIgnoresInputColor; + } + + + GR_DECLARE_XP_FACTORY_TEST + + SkRegion::Op fRegionOp; + bool fInvertCoverage; + + typedef GrXPFactory INHERITED; +}; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrCustomXfermode.cpp b/gfx/skia/skia/src/gpu/effects/GrCustomXfermode.cpp new file mode 100644 index 0000000000..4af53458c1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrCustomXfermode.cpp @@ -0,0 +1,436 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrCustomXfermode.h" + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/glsl/GrGLSLBlend.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +bool GrCustomXfermode::IsSupportedMode(SkBlendMode mode) { + return (int)mode > (int)SkBlendMode::kLastCoeffMode && + (int)mode <= (int)SkBlendMode::kLastMode; +} + +/////////////////////////////////////////////////////////////////////////////// +// Static helpers +/////////////////////////////////////////////////////////////////////////////// + +static constexpr GrBlendEquation hw_blend_equation(SkBlendMode mode) { +// In C++14 this could be a constexpr int variable. +#define EQ_OFFSET (kOverlay_GrBlendEquation - (int)SkBlendMode::kOverlay) + GR_STATIC_ASSERT(kOverlay_GrBlendEquation == (int)SkBlendMode::kOverlay + EQ_OFFSET); + GR_STATIC_ASSERT(kDarken_GrBlendEquation == (int)SkBlendMode::kDarken + EQ_OFFSET); + GR_STATIC_ASSERT(kLighten_GrBlendEquation == (int)SkBlendMode::kLighten + EQ_OFFSET); + GR_STATIC_ASSERT(kColorDodge_GrBlendEquation == (int)SkBlendMode::kColorDodge + EQ_OFFSET); + GR_STATIC_ASSERT(kColorBurn_GrBlendEquation == (int)SkBlendMode::kColorBurn + EQ_OFFSET); + GR_STATIC_ASSERT(kHardLight_GrBlendEquation == (int)SkBlendMode::kHardLight + EQ_OFFSET); + GR_STATIC_ASSERT(kSoftLight_GrBlendEquation == (int)SkBlendMode::kSoftLight + EQ_OFFSET); + GR_STATIC_ASSERT(kDifference_GrBlendEquation == (int)SkBlendMode::kDifference + EQ_OFFSET); + GR_STATIC_ASSERT(kExclusion_GrBlendEquation == (int)SkBlendMode::kExclusion + EQ_OFFSET); + GR_STATIC_ASSERT(kMultiply_GrBlendEquation == (int)SkBlendMode::kMultiply + EQ_OFFSET); + GR_STATIC_ASSERT(kHSLHue_GrBlendEquation == (int)SkBlendMode::kHue + EQ_OFFSET); + GR_STATIC_ASSERT(kHSLSaturation_GrBlendEquation == (int)SkBlendMode::kSaturation + EQ_OFFSET); + GR_STATIC_ASSERT(kHSLColor_GrBlendEquation == (int)SkBlendMode::kColor + EQ_OFFSET); + GR_STATIC_ASSERT(kHSLLuminosity_GrBlendEquation == (int)SkBlendMode::kLuminosity + EQ_OFFSET); + + // There's an illegal GrBlendEquation that corresponds to no SkBlendMode, hence the extra +1. + GR_STATIC_ASSERT(kGrBlendEquationCnt == (int)SkBlendMode::kLastMode + 1 + 1 + EQ_OFFSET); + + return static_cast((int)mode + EQ_OFFSET); +#undef EQ_OFFSET +} + +static bool can_use_hw_blend_equation(GrBlendEquation equation, + GrProcessorAnalysisCoverage coverage, const GrCaps& caps) { + if (!caps.advancedBlendEquationSupport()) { + return false; + } + if (GrProcessorAnalysisCoverage::kLCD == coverage) { + return false; // LCD coverage must be applied after the blend equation. + } + if (caps.isAdvancedBlendEquationBlacklisted(equation)) { + return false; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Xfer Processor +/////////////////////////////////////////////////////////////////////////////// + +class CustomXP : public GrXferProcessor { +public: + CustomXP(SkBlendMode mode, GrBlendEquation hwBlendEquation) + : INHERITED(kCustomXP_ClassID) + , fMode(mode) + , fHWBlendEquation(hwBlendEquation) {} + + CustomXP(bool hasMixedSamples, SkBlendMode mode, GrProcessorAnalysisCoverage coverage) + : INHERITED(kCustomXP_ClassID, true, hasMixedSamples, coverage) + , fMode(mode) + , fHWBlendEquation(kIllegal_GrBlendEquation) { + } + + const char* name() const override { return "Custom Xfermode"; } + + GrGLSLXferProcessor* createGLSLInstance() const override; + + SkBlendMode mode() const { return fMode; } + bool hasHWBlendEquation() const { return kIllegal_GrBlendEquation != fHWBlendEquation; } + + GrBlendEquation hwBlendEquation() const { + SkASSERT(this->hasHWBlendEquation()); + return fHWBlendEquation; + } + + GrXferBarrierType xferBarrierType(const GrCaps&) const override; + +private: + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + void onGetBlendInfo(BlendInfo*) const override; + + bool onIsEqual(const GrXferProcessor& xpBase) const override; + + const SkBlendMode fMode; + const GrBlendEquation fHWBlendEquation; + + typedef GrXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GLCustomXP : public GrGLSLXferProcessor { +public: + GLCustomXP(const GrXferProcessor&) {} + ~GLCustomXP() override {} + + static void GenKey(const GrXferProcessor& p, const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) { + const CustomXP& xp = p.cast(); + uint32_t key = 0; + if (xp.hasHWBlendEquation()) { + SkASSERT(caps.advBlendEqInteraction() > 0); // 0 will mean !xp.hasHWBlendEquation(). + key |= caps.advBlendEqInteraction(); + GR_STATIC_ASSERT(GrShaderCaps::kLast_AdvBlendEqInteraction < 4); + } + if (!xp.hasHWBlendEquation() || caps.mustEnableSpecificAdvBlendEqs()) { + key |= (int)xp.mode() << 3; + } + b->add32(key); + } + +private: + void emitOutputsForBlendState(const EmitArgs& args) override { + const CustomXP& xp = args.fXP.cast(); + SkASSERT(xp.hasHWBlendEquation()); + + GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder; + fragBuilder->enableAdvancedBlendEquationIfNeeded(xp.hwBlendEquation()); + + // Apply coverage by multiplying it into the src color before blending. Mixed samples will + // "just work" automatically. (See onGetOptimizations()) + fragBuilder->codeAppendf("%s = %s * %s;", args.fOutputPrimary, args.fInputCoverage, + args.fInputColor); + } + + void emitBlendCodeForDstRead(GrGLSLXPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const char* srcColor, + const char* srcCoverage, + const char* dstColor, + const char* outColor, + const char* outColorSecondary, + const GrXferProcessor& proc) override { + const CustomXP& xp = proc.cast(); + SkASSERT(!xp.hasHWBlendEquation()); + + GrGLSLBlend::AppendMode(fragBuilder, srcColor, dstColor, outColor, xp.mode()); + + // Apply coverage. + INHERITED::DefaultCoverageModulation(fragBuilder, srcCoverage, dstColor, outColor, + outColorSecondary, xp); + } + + void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) override {} + + typedef GrGLSLXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +void CustomXP::onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const { + GLCustomXP::GenKey(*this, caps, b); +} + +GrGLSLXferProcessor* CustomXP::createGLSLInstance() const { + SkASSERT(this->willReadDstColor() != this->hasHWBlendEquation()); + return new GLCustomXP(*this); +} + +bool CustomXP::onIsEqual(const GrXferProcessor& other) const { + const CustomXP& s = other.cast(); + return fMode == s.fMode && fHWBlendEquation == s.fHWBlendEquation; +} + +GrXferBarrierType CustomXP::xferBarrierType(const GrCaps& caps) const { + if (this->hasHWBlendEquation() && !caps.advancedCoherentBlendEquationSupport()) { + return kBlend_GrXferBarrierType; + } + return kNone_GrXferBarrierType; +} + +void CustomXP::onGetBlendInfo(BlendInfo* blendInfo) const { + if (this->hasHWBlendEquation()) { + blendInfo->fEquation = this->hwBlendEquation(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// See the comment above GrXPFactory's definition about this warning suppression. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif +class CustomXPFactory : public GrXPFactory { +public: + constexpr CustomXPFactory(SkBlendMode mode) + : fMode(mode), fHWBlendEquation(hw_blend_equation(mode)) {} + +private: + sk_sp makeXferProcessor(const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps&, + GrClampType) const override; + + AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&, + const GrProcessorAnalysisCoverage&, + const GrCaps&, + GrClampType) const override; + + GR_DECLARE_XP_FACTORY_TEST + + SkBlendMode fMode; + GrBlendEquation fHWBlendEquation; + + typedef GrXPFactory INHERITED; +}; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +sk_sp CustomXPFactory::makeXferProcessor( + const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage coverage, + bool hasMixedSamples, + const GrCaps& caps, + GrClampType clampType) const { + SkASSERT(GrCustomXfermode::IsSupportedMode(fMode)); + if (can_use_hw_blend_equation(fHWBlendEquation, coverage, caps)) { + return sk_sp(new CustomXP(fMode, fHWBlendEquation)); + } + return sk_sp(new CustomXP(hasMixedSamples, fMode, coverage)); +} + +GrXPFactory::AnalysisProperties CustomXPFactory::analysisProperties( + const GrProcessorAnalysisColor&, const GrProcessorAnalysisCoverage& coverage, + const GrCaps& caps, GrClampType clampType) const { + /* + The general SVG blend equation is defined in the spec as follows: + + Dca' = B(Sc, Dc) * Sa * Da + Y * Sca * (1-Da) + Z * Dca * (1-Sa) + Da' = X * Sa * Da + Y * Sa * (1-Da) + Z * Da * (1-Sa) + + (Note that Sca, Dca indicate RGB vectors that are premultiplied by alpha, + and that B(Sc, Dc) is a mode-specific function that accepts non-multiplied + RGB colors.) + + For every blend mode supported by this class, i.e. the "advanced" blend + modes, X=Y=Z=1 and this equation reduces to the PDF blend equation. + + It can be shown that when X=Y=Z=1, these equations can modulate alpha for + coverage. + + + == Color == + + We substitute Y=Z=1 and define a blend() function that calculates Dca' in + terms of premultiplied alpha only: + + blend(Sca, Dca, Sa, Da) = {Dca : if Sa == 0, + Sca : if Da == 0, + B(Sca/Sa, Dca/Da) * Sa * Da + Sca * (1-Da) + Dca * (1-Sa) : if + Sa,Da != 0} + + And for coverage modulation, we use a post blend src-over model: + + Dca'' = f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + + (Where f is the fractional coverage.) + + Next we show that canTweakAlphaForCoverage() is true by proving the + following relationship: + + blend(f*Sca, Dca, f*Sa, Da) == f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + + General case (f,Sa,Da != 0): + + f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = f * (B(Sca/Sa, Dca/Da) * Sa * Da + Sca * (1-Da) + Dca * (1-Sa)) + (1-f) * Dca [Sa,Da != + 0, definition of blend()] + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + f*Dca * (1-Sa) + Dca - f*Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca - f*Sca * Da + f*Dca - f*Dca * Sa + Dca - f*Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca - f*Sca * Da - f*Dca * Sa + Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) - f*Dca * Sa + Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + Dca * (1 - f*Sa) + = B(f*Sca/f*Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + Dca * (1 - f*Sa) [f!=0] + = blend(f*Sca, Dca, f*Sa, Da) [definition of blend()] + + Corner cases (Sa=0, Da=0, and f=0): + + Sa=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = f * Dca + (1-f) * Dca [Sa=0, definition of blend()] + = Dca + = blend(0, Dca, 0, Da) [definition of blend()] + = blend(f*Sca, Dca, f*Sa, Da) [Sa=0] + + Da=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = f * Sca + (1-f) * Dca [Da=0, definition of blend()] + = f * Sca [Da=0] + = blend(f*Sca, 0, f*Sa, 0) [definition of blend()] + = blend(f*Sca, Dca, f*Sa, Da) [Da=0] + + f=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = Dca [f=0] + = blend(0, Dca, 0, Da) [definition of blend()] + = blend(f*Sca, Dca, f*Sa, Da) [f=0] + + == Alpha == + + We substitute X=Y=Z=1 and define a blend() function that calculates Da': + + blend(Sa, Da) = Sa * Da + Sa * (1-Da) + Da * (1-Sa) + = Sa * Da + Sa - Sa * Da + Da - Da * Sa + = Sa + Da - Sa * Da + + We use the same model for coverage modulation as we did with color: + + Da'' = f * blend(Sa, Da) + (1-f) * Da + + And show that canTweakAlphaForCoverage() is true by proving the following + relationship: + + blend(f*Sa, Da) == f * blend(Sa, Da) + (1-f) * Da + + + f * blend(Sa, Da) + (1-f) * Da + = f * (Sa + Da - Sa * Da) + (1-f) * Da + = f*Sa + f*Da - f*Sa * Da + Da - f*Da + = f*Sa - f*Sa * Da + Da + = f*Sa + Da - f*Sa * Da + = blend(f*Sa, Da) + */ + if (can_use_hw_blend_equation(fHWBlendEquation, coverage, caps)) { + if (caps.blendEquationSupport() == GrCaps::kAdvancedCoherent_BlendEquationSupport) { + return AnalysisProperties::kCompatibleWithCoverageAsAlpha; + } else { + return AnalysisProperties::kCompatibleWithCoverageAsAlpha | + AnalysisProperties::kRequiresNonOverlappingDraws; + } + } + return AnalysisProperties::kCompatibleWithCoverageAsAlpha | + AnalysisProperties::kReadsDstInShader; +} + +GR_DEFINE_XP_FACTORY_TEST(CustomXPFactory); +#if GR_TEST_UTILS +const GrXPFactory* CustomXPFactory::TestGet(GrProcessorTestData* d) { + int mode = d->fRandom->nextRangeU((int)SkBlendMode::kLastCoeffMode + 1, + (int)SkBlendMode::kLastSeparableMode); + + return GrCustomXfermode::Get((SkBlendMode)mode); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +const GrXPFactory* GrCustomXfermode::Get(SkBlendMode mode) { + // If these objects are constructed as static constexpr by cl.exe (2015 SP2) the vtables are + // null. +#ifdef SK_BUILD_FOR_WIN +#define _CONSTEXPR_ +#else +#define _CONSTEXPR_ constexpr +#endif + static _CONSTEXPR_ const CustomXPFactory gOverlay(SkBlendMode::kOverlay); + static _CONSTEXPR_ const CustomXPFactory gDarken(SkBlendMode::kDarken); + static _CONSTEXPR_ const CustomXPFactory gLighten(SkBlendMode::kLighten); + static _CONSTEXPR_ const CustomXPFactory gColorDodge(SkBlendMode::kColorDodge); + static _CONSTEXPR_ const CustomXPFactory gColorBurn(SkBlendMode::kColorBurn); + static _CONSTEXPR_ const CustomXPFactory gHardLight(SkBlendMode::kHardLight); + static _CONSTEXPR_ const CustomXPFactory gSoftLight(SkBlendMode::kSoftLight); + static _CONSTEXPR_ const CustomXPFactory gDifference(SkBlendMode::kDifference); + static _CONSTEXPR_ const CustomXPFactory gExclusion(SkBlendMode::kExclusion); + static _CONSTEXPR_ const CustomXPFactory gMultiply(SkBlendMode::kMultiply); + static _CONSTEXPR_ const CustomXPFactory gHue(SkBlendMode::kHue); + static _CONSTEXPR_ const CustomXPFactory gSaturation(SkBlendMode::kSaturation); + static _CONSTEXPR_ const CustomXPFactory gColor(SkBlendMode::kColor); + static _CONSTEXPR_ const CustomXPFactory gLuminosity(SkBlendMode::kLuminosity); +#undef _CONSTEXPR_ + switch (mode) { + case SkBlendMode::kOverlay: + return &gOverlay; + case SkBlendMode::kDarken: + return &gDarken; + case SkBlendMode::kLighten: + return &gLighten; + case SkBlendMode::kColorDodge: + return &gColorDodge; + case SkBlendMode::kColorBurn: + return &gColorBurn; + case SkBlendMode::kHardLight: + return &gHardLight; + case SkBlendMode::kSoftLight: + return &gSoftLight; + case SkBlendMode::kDifference: + return &gDifference; + case SkBlendMode::kExclusion: + return &gExclusion; + case SkBlendMode::kMultiply: + return &gMultiply; + case SkBlendMode::kHue: + return &gHue; + case SkBlendMode::kSaturation: + return &gSaturation; + case SkBlendMode::kColor: + return &gColor; + case SkBlendMode::kLuminosity: + return &gLuminosity; + default: + SkASSERT(!GrCustomXfermode::IsSupportedMode(mode)); + return nullptr; + } +} diff --git a/gfx/skia/skia/src/gpu/effects/GrCustomXfermode.h b/gfx/skia/skia/src/gpu/effects/GrCustomXfermode.h new file mode 100644 index 0000000000..672a512d09 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrCustomXfermode.h @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCustomXfermode_DEFINED +#define GrCustomXfermode_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkRefCnt.h" + +class GrTexture; +class GrXPFactory; + +/** + * Custom Xfer modes are used for blending when the blend mode cannot be represented using blend + * coefficients. + */ +namespace GrCustomXfermode { + bool IsSupportedMode(SkBlendMode mode); + const GrXPFactory* Get(SkBlendMode mode); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrDisableColorXP.cpp b/gfx/skia/skia/src/gpu/effects/GrDisableColorXP.cpp new file mode 100644 index 0000000000..81ccb5f5d1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrDisableColorXP.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/effects/GrDisableColorXP.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +/** + * This xfer processor disables color writing. Thus color and coverage and ignored and no blending + * occurs. This XP is usful for things like stenciling. + */ +class DisableColorXP : public GrXferProcessor { +public: + DisableColorXP() : INHERITED(kDisableColorXP_ClassID) {} + +private: + const char* name() const override { return "Disable Color"; } + bool onIsEqual(const GrXferProcessor& xpBase) const override { return true; } + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + return; // No key. + } + void onGetBlendInfo(GrXferProcessor::BlendInfo* blendInfo) const override { + blendInfo->fWriteColor = false; + } + GrGLSLXferProcessor* createGLSLInstance() const override; + + typedef GrXferProcessor INHERITED; +}; + +class GLDisableColorXP : public GrGLSLXferProcessor { +private: + void emitOutputsForBlendState(const EmitArgs& args) override { + if (args.fShaderCaps->mustWriteToFragColor()) { + // This emit code should be empty. However, on the nexus 6 there is a driver bug where + // if you do not give gl_FragColor a value, the gl context is lost and we end up drawing + // nothing. So this fix just sets the gl_FragColor arbitrarily to 0. + // https://bugs.chromium.org/p/chromium/issues/detail?id=445377 + GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder; + fragBuilder->codeAppendf("%s = half4(0);", args.fOutputPrimary); + } + } + + void emitOutputSwizzle( + GrGLSLXPFragmentBuilder*, const GrSwizzle&, const char*, const char*) const override { + // Don't write any swizzling. This makes sure the final shader does not output a color. + return; + } + + void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) override {} + + typedef GrGLSLXferProcessor INHERITED; +}; + +GrGLSLXferProcessor* DisableColorXP::createGLSLInstance() const { + return new GLDisableColorXP(); +} + +sk_sp GrDisableColorXPFactory::MakeXferProcessor() { + return sk_make_sp(); +} + +GR_DEFINE_XP_FACTORY_TEST(GrDisableColorXPFactory); + +#if GR_TEST_UTILS +const GrXPFactory* GrDisableColorXPFactory::TestGet(GrProcessorTestData*) { + return GrDisableColorXPFactory::Get(); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrDisableColorXP.h b/gfx/skia/skia/src/gpu/effects/GrDisableColorXP.h new file mode 100644 index 0000000000..a78adef9e3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrDisableColorXP.h @@ -0,0 +1,67 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDisableColorXP_DEFINED +#define GrDisableColorXP_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrXferProcessor.h" + +// See the comment above GrXPFactory's definition about this warning suppression. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif +class GrDisableColorXPFactory : public GrXPFactory { +public: + static const GrDisableColorXPFactory* Get(); + + static sk_sp MakeXferProcessor(); + +private: + constexpr GrDisableColorXPFactory() {} + + AnalysisProperties analysisProperties( + const GrProcessorAnalysisColor&, const GrProcessorAnalysisCoverage&, const GrCaps&, + GrClampType) const override { + return AnalysisProperties::kCompatibleWithCoverageAsAlpha | + AnalysisProperties::kIgnoresInputColor; + } + + sk_sp makeXferProcessor( + const GrProcessorAnalysisColor&, GrProcessorAnalysisCoverage, bool hasMixedSamples, + const GrCaps&, GrClampType) const override { + return MakeXferProcessor(); + } + + GR_DECLARE_XP_FACTORY_TEST + + typedef GrXPFactory INHERITED; +}; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +inline const GrDisableColorXPFactory* GrDisableColorXPFactory::Get() { + // If this is constructed as static constexpr by cl.exe (2015 SP2) the vtable is null. +#ifdef SK_BUILD_FOR_WIN + static const GrDisableColorXPFactory gDisableColorXPFactory; +#else + static constexpr const GrDisableColorXPFactory gDisableColorXPFactory; +#endif + return &gDisableColorXPFactory; +} + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.cpp new file mode 100644 index 0000000000..08c36dc015 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.cpp @@ -0,0 +1,928 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrTexture.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/effects/GrAtlasedShaderHelpers.h" +#include "src/gpu/effects/GrDistanceFieldGeoProc.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLUtil.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +// Assuming a radius of a little less than the diagonal of the fragment +#define SK_DistanceFieldAAFactor "0.65" + +class GrGLDistanceFieldA8TextGeoProc : public GrGLSLGeometryProcessor { +public: + GrGLDistanceFieldA8TextGeoProc() = default; + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{ + const GrDistanceFieldA8TextGeoProc& dfTexEffect = + args.fGP.cast(); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(dfTexEffect); + + const char* atlasSizeInvName; + fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag, + kFloat2_GrSLType, + "AtlasSizeInv", + &atlasSizeInvName); +#ifdef SK_GAMMA_APPLY_TO_A8 + // adjust based on gamma + const char* distanceAdjustUniName = nullptr; + // width, height, 1/(3*width) + fDistanceAdjustUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "DistanceAdjust", &distanceAdjustUniName); +#endif + + // Setup pass through color + varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor); + + // Setup position + gpArgs->fPositionVar = dfTexEffect.inPosition().asShaderVar(); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + dfTexEffect.inPosition().asShaderVar(), + dfTexEffect.localMatrix(), + args.fFPCoordTransformHandler); + + // add varyings + GrGLSLVarying uv(kFloat2_GrSLType); + GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType; + GrGLSLVarying texIdx(texIdxType); + GrGLSLVarying st(kFloat2_GrSLType); + append_index_uv_varyings(args, dfTexEffect.inTextureCoords().name(), atlasSizeInvName, &uv, + &texIdx, &st); + + bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) == + kUniformScale_DistanceFieldEffectMask; + bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag); + bool isGammaCorrect = + SkToBool(dfTexEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag); + bool isAliased = + SkToBool(dfTexEffect.getFlags() & kAliased_DistanceFieldEffectFlag); + + // Use highp to work around aliasing issues + fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn()); + fragBuilder->codeAppend("half4 texColor;"); + append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(), + texIdx, "uv", "texColor"); + + fragBuilder->codeAppend("half distance = " + SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");"); +#ifdef SK_GAMMA_APPLY_TO_A8 + // adjust width based on gamma + fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName); +#endif + + fragBuilder->codeAppend("half afwidth;"); + if (isUniformScale) { + // For uniform scale, we adjust for the effect of the transformation on the distance + // by using the length of the gradient of the t coordinate in the y direction. + // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space. + + // this gives us a smooth step across approximately one fragment +#ifdef SK_VULKAN + fragBuilder->codeAppendf("afwidth = abs(" SK_DistanceFieldAAFactor + "*half(dFdx(%s.x)));", st.fsIn()); +#else + // We use the y gradient because there is a bug in the Mali 400 in the x direction. + fragBuilder->codeAppendf("afwidth = abs(" SK_DistanceFieldAAFactor + "*half(dFdy(%s.y)));", st.fsIn()); +#endif + } else if (isSimilarity) { + // For similarity transform, we adjust the effect of the transformation on the distance + // by using the length of the gradient of the texture coordinates. We use st coordinates + // to ensure we're mapping 1:1 from texel space to pixel space. + // We use the y gradient because there is a bug in the Mali 400 in the x direction. + + // this gives us a smooth step across approximately one fragment +#ifdef SK_VULKAN + fragBuilder->codeAppendf("half st_grad_len = length(half2(dFdx(%s)));", st.fsIn()); +#else + // We use the y gradient because there is a bug in the Mali 400 in the x direction. + fragBuilder->codeAppendf("half st_grad_len = length(half2(dFdy(%s)));", st.fsIn()); +#endif + fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);"); + } else { + // For general transforms, to determine the amount of correction we multiply a unit + // vector pointing along the SDF gradient direction by the Jacobian of the st coords + // (which is the inverse transform for this fragment) and take the length of the result. + fragBuilder->codeAppend("half2 dist_grad = half2(float2(dFdx(distance), " + "dFdy(distance)));"); + // the length of the gradient may be 0, so we need to check for this + // this also compensates for the Adreno, which likes to drop tiles on division by 0 + fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);"); + fragBuilder->codeAppend("if (dg_len2 < 0.0001) {"); + fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);"); + fragBuilder->codeAppend("} else {"); + fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));"); + fragBuilder->codeAppend("}"); + + fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn()); + fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn()); + fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,"); + fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);"); + + // this gives us a smooth step across approximately one fragment + fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);"); + } + + if (isAliased) { + fragBuilder->codeAppend("half val = distance > 0 ? 1.0 : 0.0;"); + } else if (isGammaCorrect) { + // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are + // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want + // distance mapped linearly to coverage, so use a linear step: + fragBuilder->codeAppend( + "half val = saturate((distance + afwidth) / (2.0 * afwidth));"); + } else { + fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);"); + } + + fragBuilder->codeAppendf("%s = half4(val);", args.fOutputCoverage); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, + FPCoordTransformIter&& transformIter) override { + const GrDistanceFieldA8TextGeoProc& dfa8gp = proc.cast(); + +#ifdef SK_GAMMA_APPLY_TO_A8 + float distanceAdjust = dfa8gp.getDistanceAdjust(); + if (distanceAdjust != fDistanceAdjust) { + fDistanceAdjust = distanceAdjust; + pdman.set1f(fDistanceAdjustUni, distanceAdjust); + } +#endif + + const SkISize& atlasSize = dfa8gp.atlasSize(); + SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight)); + + if (fAtlasSize != atlasSize) { + pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight); + fAtlasSize = atlasSize; + } + this->setTransformDataHelper(dfa8gp.localMatrix(), pdman, &transformIter); + } + + static inline void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrDistanceFieldA8TextGeoProc& dfTexEffect = gp.cast(); + uint32_t key = dfTexEffect.getFlags(); + b->add32(key); + b->add32(dfTexEffect.numTextureSamplers()); + } + +private: +#ifdef SK_GAMMA_APPLY_TO_A8 + float fDistanceAdjust = -1.f; + UniformHandle fDistanceAdjustUni; +#endif + SkISize fAtlasSize = {0, 0}; + UniformHandle fAtlasSizeInvUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps, + const sk_sp* proxies, + int numProxies, + const GrSamplerState& params, +#ifdef SK_GAMMA_APPLY_TO_A8 + float distanceAdjust, +#endif + uint32_t flags, + const SkMatrix& localMatrix) + : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID) + , fLocalMatrix(localMatrix) + , fFlags(flags & kNonLCD_DistanceFieldEffectMask) +#ifdef SK_GAMMA_APPLY_TO_A8 + , fDistanceAdjust(distanceAdjust) +#endif +{ + SkASSERT(numProxies <= kMaxTextures); + SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask)); + + if (flags & kPerspective_DistanceFieldEffectFlag) { + fInPosition = {"inPosition", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + } else { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + } + fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType }; + fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType, + caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); + + if (numProxies) { + fAtlasSize = proxies[0]->isize(); + } + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + fTextureSamplers[i].reset(proxies[i]->textureType(), params, proxies[i]->textureSwizzle()); + } + this->setTextureSamplerCnt(numProxies); +} + +void GrDistanceFieldA8TextGeoProc::addNewProxies(const sk_sp* proxies, + int numProxies, + const GrSamplerState& params) { + SkASSERT(numProxies <= kMaxTextures); + + if (!fTextureSamplers[0].isInitialized()) { + fAtlasSize = proxies[0]->isize(); + } + + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + if (!fTextureSamplers[i].isInitialized()) { + fTextureSamplers[i].reset(proxies[i]->textureType(), params, + proxies[i]->textureSwizzle()); + } + } + this->setTextureSamplerCnt(numProxies); +} + +void GrDistanceFieldA8TextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLDistanceFieldA8TextGeoProc::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* +GrDistanceFieldA8TextGeoProc::createGLSLInstance(const GrShaderCaps&) const { + return new GrGLDistanceFieldA8TextGeoProc(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc); + +#if GR_TEST_UTILS +sk_sp GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxies[kMaxTextures] = { + d->textureProxy(texIdx), + nullptr, + nullptr, + nullptr + }; + + GrSamplerState::WrapMode wrapModes[2]; + GrTest::TestWrapModes(d->fRandom, wrapModes); + GrSamplerState samplerState(wrapModes, d->fRandom->nextBool() + ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kNearest); + + uint32_t flags = 0; + flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0; + if (flags & kSimilarity_DistanceFieldEffectFlag) { + flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0; + } + SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom); +#ifdef SK_GAMMA_APPLY_TO_A8 + float lum = d->fRandom->nextF(); +#endif + return GrDistanceFieldA8TextGeoProc::Make(*d->caps()->shaderCaps(), + proxies, 1, + samplerState, +#ifdef SK_GAMMA_APPLY_TO_A8 + lum, +#endif + flags, localMatrix); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLDistanceFieldPathGeoProc : public GrGLSLGeometryProcessor { +public: + GrGLDistanceFieldPathGeoProc() + : fMatrix(SkMatrix::InvalidMatrix()) + , fAtlasSize({0,0}) { + } + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{ + const GrDistanceFieldPathGeoProc& dfPathEffect = + args.fGP.cast(); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(dfPathEffect); + + const char* atlasSizeInvName; + fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag, + kFloat2_GrSLType, + "AtlasSizeInv", + &atlasSizeInvName); + + GrGLSLVarying uv(kFloat2_GrSLType); + GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType; + GrGLSLVarying texIdx(texIdxType); + GrGLSLVarying st(kFloat2_GrSLType); + append_index_uv_varyings(args, dfPathEffect.inTextureCoords().name(), atlasSizeInvName, &uv, + &texIdx, &st); + + // setup pass through color + varyingHandler->addPassThroughAttribute(dfPathEffect.inColor(), args.fOutputColor); + + if (dfPathEffect.matrix().hasPerspective()) { + // Setup position + this->writeOutputPosition(vertBuilder, + uniformHandler, + gpArgs, + dfPathEffect.inPosition().name(), + dfPathEffect.matrix(), + &fMatrixUniform); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + dfPathEffect.inPosition().asShaderVar(), + args.fFPCoordTransformHandler); + } else { + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, dfPathEffect.inPosition().name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + dfPathEffect.inPosition().asShaderVar(), + dfPathEffect.matrix(), + args.fFPCoordTransformHandler); + } + + // Use highp to work around aliasing issues + fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn()); + fragBuilder->codeAppend("half4 texColor;"); + append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv", + "texColor"); + + fragBuilder->codeAppend("half distance = " + SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");"); + + fragBuilder->codeAppend("half afwidth;"); + bool isUniformScale = (dfPathEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) == + kUniformScale_DistanceFieldEffectMask; + bool isSimilarity = SkToBool(dfPathEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag); + bool isGammaCorrect = + SkToBool(dfPathEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag); + if (isUniformScale) { + // For uniform scale, we adjust for the effect of the transformation on the distance + // by using the length of the gradient of the t coordinate in the y direction. + // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space. + + // this gives us a smooth step across approximately one fragment +#ifdef SK_VULKAN + fragBuilder->codeAppendf("afwidth = abs(" SK_DistanceFieldAAFactor + "*half(dFdx(%s.x)));", st.fsIn()); +#else + // We use the y gradient because there is a bug in the Mali 400 in the x direction. + fragBuilder->codeAppendf("afwidth = abs(" SK_DistanceFieldAAFactor + "*half(dFdy(%s.y)));", st.fsIn()); +#endif + } else if (isSimilarity) { + // For similarity transform, we adjust the effect of the transformation on the distance + // by using the length of the gradient of the texture coordinates. We use st coordinates + // to ensure we're mapping 1:1 from texel space to pixel space. + + // this gives us a smooth step across approximately one fragment +#ifdef SK_VULKAN + fragBuilder->codeAppendf("half st_grad_len = half(length(dFdx(%s)));", st.fsIn()); +#else + // We use the y gradient because there is a bug in the Mali 400 in the x direction. + fragBuilder->codeAppendf("half st_grad_len = half(length(dFdy(%s)));", st.fsIn()); +#endif + fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);"); + } else { + // For general transforms, to determine the amount of correction we multiply a unit + // vector pointing along the SDF gradient direction by the Jacobian of the st coords + // (which is the inverse transform for this fragment) and take the length of the result. + fragBuilder->codeAppend("half2 dist_grad = half2(dFdx(distance), " + "dFdy(distance));"); + // the length of the gradient may be 0, so we need to check for this + // this also compensates for the Adreno, which likes to drop tiles on division by 0 + fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);"); + fragBuilder->codeAppend("if (dg_len2 < 0.0001) {"); + fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);"); + fragBuilder->codeAppend("} else {"); + fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));"); + fragBuilder->codeAppend("}"); + + fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn()); + fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn()); + fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,"); + fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);"); + + // this gives us a smooth step across approximately one fragment + fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);"); + } + // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are + // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance + // mapped linearly to coverage, so use a linear step: + if (isGammaCorrect) { + fragBuilder->codeAppend( + "half val = saturate((distance + afwidth) / (2.0 * afwidth));"); + } else { + fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);"); + } + + fragBuilder->codeAppendf("%s = half4(val);", args.fOutputCoverage); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, + FPCoordTransformIter&& transformIter) override { + + const GrDistanceFieldPathGeoProc& dfpgp = proc.cast(); + + if (dfpgp.matrix().hasPerspective() && !fMatrix.cheapEqualTo(dfpgp.matrix())) { + fMatrix = dfpgp.matrix(); + float matrix[3 * 3]; + GrGLSLGetMatrix<3>(matrix, fMatrix); + pdman.setMatrix3f(fMatrixUniform, matrix); + } + + const SkISize& atlasSize = dfpgp.atlasSize(); + SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight)); + if (fAtlasSize != atlasSize) { + pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight); + fAtlasSize = atlasSize; + } + + if (dfpgp.matrix().hasPerspective()) { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } else { + this->setTransformDataHelper(dfpgp.matrix(), pdman, &transformIter); + } + } + + static inline void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrDistanceFieldPathGeoProc& dfTexEffect = gp.cast(); + + uint32_t key = dfTexEffect.getFlags(); + key |= ComputePosKey(dfTexEffect.matrix()) << 16; + b->add32(key); + b->add32(dfTexEffect.matrix().hasPerspective()); + b->add32(dfTexEffect.numTextureSamplers()); + } + +private: + SkMatrix fMatrix; // view matrix if perspective, local matrix otherwise + UniformHandle fMatrixUniform; + + SkISize fAtlasSize; + UniformHandle fAtlasSizeInvUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps, + const SkMatrix& matrix, + bool wideColor, + const sk_sp* proxies, + int numProxies, + const GrSamplerState& params, + uint32_t flags) + : INHERITED(kGrDistanceFieldPathGeoProc_ClassID) + , fMatrix(matrix) + , fFlags(flags & kNonLCD_DistanceFieldEffectMask) { + SkASSERT(numProxies <= kMaxTextures); + SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask)); + + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = MakeColorAttribute("inColor", wideColor); + fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType, + caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); + + if (numProxies) { + fAtlasSize = proxies[0]->isize(); + } + + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + fTextureSamplers[i].reset(proxies[i]->textureType(), params, proxies[i]->textureSwizzle()); + } + this->setTextureSamplerCnt(numProxies); +} + +void GrDistanceFieldPathGeoProc::addNewProxies(const sk_sp* proxies, + int numProxies, + const GrSamplerState& params) { + SkASSERT(numProxies <= kMaxTextures); + + if (!fTextureSamplers[0].isInitialized()) { + fAtlasSize = proxies[0]->isize(); + } + + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + + if (!fTextureSamplers[i].isInitialized()) { + fTextureSamplers[i].reset(proxies[i]->textureType(), params, + proxies[i]->textureSwizzle()); + } + } + this->setTextureSamplerCnt(numProxies); +} + +void GrDistanceFieldPathGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLDistanceFieldPathGeoProc::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* +GrDistanceFieldPathGeoProc::createGLSLInstance(const GrShaderCaps&) const { + return new GrGLDistanceFieldPathGeoProc(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc); + +#if GR_TEST_UTILS +sk_sp GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxies[kMaxTextures] = { + d->textureProxy(texIdx), + nullptr, + nullptr, + nullptr + }; + + GrSamplerState::WrapMode wrapModes[2]; + GrTest::TestWrapModes(d->fRandom, wrapModes); + GrSamplerState samplerState(wrapModes, d->fRandom->nextBool() + ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kNearest); + + uint32_t flags = 0; + flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0; + if (flags & kSimilarity_DistanceFieldEffectFlag) { + flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0; + } + + return GrDistanceFieldPathGeoProc::Make(*d->caps()->shaderCaps(), + GrTest::TestMatrix(d->fRandom), + d->fRandom->nextBool(), + proxies, 1, + samplerState, + flags); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLDistanceFieldLCDTextGeoProc : public GrGLSLGeometryProcessor { +public: + GrGLDistanceFieldLCDTextGeoProc() : fAtlasSize({0, 0}) { + fDistanceAdjust = GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(1.0f, 1.0f, 1.0f); + } + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{ + const GrDistanceFieldLCDTextGeoProc& dfTexEffect = + args.fGP.cast(); + + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(dfTexEffect); + + const char* atlasSizeInvName; + fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag, + kFloat2_GrSLType, + "AtlasSizeInv", + &atlasSizeInvName); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + // setup pass through color + varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor); + + // Setup position + gpArgs->fPositionVar = dfTexEffect.inPosition().asShaderVar(); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + dfTexEffect.inPosition().asShaderVar(), + dfTexEffect.localMatrix(), + args.fFPCoordTransformHandler); + + // set up varyings + GrGLSLVarying uv(kFloat2_GrSLType); + GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType; + GrGLSLVarying texIdx(texIdxType); + GrGLSLVarying st(kFloat2_GrSLType); + append_index_uv_varyings(args, dfTexEffect.inTextureCoords().name(), atlasSizeInvName, &uv, + &texIdx, &st); + + GrGLSLVarying delta(kFloat_GrSLType); + varyingHandler->addVarying("Delta", &delta); + if (dfTexEffect.getFlags() & kBGR_DistanceFieldEffectFlag) { + vertBuilder->codeAppendf("%s = -%s.x/3.0;", delta.vsOut(), atlasSizeInvName); + } else { + vertBuilder->codeAppendf("%s = %s.x/3.0;", delta.vsOut(), atlasSizeInvName); + } + + // add frag shader code + bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) == + kUniformScale_DistanceFieldEffectMask; + bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag); + bool isGammaCorrect = + SkToBool(dfTexEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag); + + // create LCD offset adjusted by inverse of transform + // Use highp to work around aliasing issues + fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn()); + + if (isUniformScale) { +#ifdef SK_VULKAN + fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdx(%s.x)));", st.fsIn()); +#else + // We use the y gradient because there is a bug in the Mali 400 in the x direction. + fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdy(%s.y)));", st.fsIn()); +#endif + fragBuilder->codeAppendf("half2 offset = half2(half(st_grad_len*%s), 0.0);", + delta.fsIn()); + } else if (isSimilarity) { + // For a similarity matrix with rotation, the gradient will not be aligned + // with the texel coordinate axes, so we need to calculate it. +#ifdef SK_VULKAN + fragBuilder->codeAppendf("half2 st_grad = half2(dFdx(%s));", st.fsIn()); + fragBuilder->codeAppendf("half2 offset = half(%s)*st_grad;", delta.fsIn()); +#else + // We use dFdy because of a Mali 400 bug, and rotate -90 degrees to + // get the gradient in the x direction. + fragBuilder->codeAppendf("half2 st_grad = half2(dFdy(%s));", st.fsIn()); + fragBuilder->codeAppendf("half2 offset = half2(%s*float2(st_grad.y, -st_grad.x));", + delta.fsIn()); +#endif + fragBuilder->codeAppend("half st_grad_len = length(st_grad);"); + } else { + fragBuilder->codeAppendf("half2 st = half2(%s);\n", st.fsIn()); + + fragBuilder->codeAppend("half2 Jdx = half2(dFdx(st));"); + fragBuilder->codeAppend("half2 Jdy = half2(dFdy(st));"); + fragBuilder->codeAppendf("half2 offset = half2(half(%s))*Jdx;", delta.fsIn()); + } + + // sample the texture by index + fragBuilder->codeAppend("half4 texColor;"); + append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(), + texIdx, "uv", "texColor"); + + // green is distance to uv center + fragBuilder->codeAppend("half3 distance;"); + fragBuilder->codeAppend("distance.y = texColor.r;"); + // red is distance to left offset + fragBuilder->codeAppend("half2 uv_adjusted = half2(uv) - offset;"); + append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(), + texIdx, "uv_adjusted", "texColor"); + fragBuilder->codeAppend("distance.x = texColor.r;"); + // blue is distance to right offset + fragBuilder->codeAppend("uv_adjusted = half2(uv) + offset;"); + append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(), + texIdx, "uv_adjusted", "texColor"); + fragBuilder->codeAppend("distance.z = texColor.r;"); + + fragBuilder->codeAppend("distance = " + "half3(" SK_DistanceFieldMultiplier ")*(distance - half3(" SK_DistanceFieldThreshold"));"); + + // adjust width based on gamma + const char* distanceAdjustUniName = nullptr; + fDistanceAdjustUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, + "DistanceAdjust", &distanceAdjustUniName); + fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName); + + // To be strictly correct, we should compute the anti-aliasing factor separately + // for each color component. However, this is only important when using perspective + // transformations, and even then using a single factor seems like a reasonable + // trade-off between quality and speed. + fragBuilder->codeAppend("half afwidth;"); + if (isSimilarity) { + // For similarity transform (uniform scale-only is a subset of this), we adjust for the + // effect of the transformation on the distance by using the length of the gradient of + // the texture coordinates. We use st coordinates to ensure we're mapping 1:1 from texel + // space to pixel space. + + // this gives us a smooth step across approximately one fragment + fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*st_grad_len;"); + } else { + // For general transforms, to determine the amount of correction we multiply a unit + // vector pointing along the SDF gradient direction by the Jacobian of the st coords + // (which is the inverse transform for this fragment) and take the length of the result. + fragBuilder->codeAppend("half2 dist_grad = half2(half(dFdx(distance.r)), " + "half(dFdy(distance.r)));"); + // the length of the gradient may be 0, so we need to check for this + // this also compensates for the Adreno, which likes to drop tiles on division by 0 + fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);"); + fragBuilder->codeAppend("if (dg_len2 < 0.0001) {"); + fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);"); + fragBuilder->codeAppend("} else {"); + fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));"); + fragBuilder->codeAppend("}"); + fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,"); + fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);"); + + // this gives us a smooth step across approximately one fragment + fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);"); + } + + // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are + // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance + // mapped linearly to coverage, so use a linear step: + if (isGammaCorrect) { + fragBuilder->codeAppendf("%s = " + "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);", + args.fOutputCoverage); + } else { + fragBuilder->codeAppendf( + "%s = half4(smoothstep(half3(-afwidth), half3(afwidth), distance), 1.0);", + args.fOutputCoverage); + } + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& processor, + FPCoordTransformIter&& transformIter) override { + SkASSERT(fDistanceAdjustUni.isValid()); + + const GrDistanceFieldLCDTextGeoProc& dflcd = processor.cast(); + GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.getDistanceAdjust(); + if (wa != fDistanceAdjust) { + pdman.set3f(fDistanceAdjustUni, + wa.fR, + wa.fG, + wa.fB); + fDistanceAdjust = wa; + } + + const SkISize& atlasSize = dflcd.atlasSize(); + SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight)); + if (fAtlasSize != atlasSize) { + pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight); + fAtlasSize = atlasSize; + } + this->setTransformDataHelper(dflcd.localMatrix(), pdman, &transformIter); + } + + static inline void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrDistanceFieldLCDTextGeoProc& dfTexEffect = gp.cast(); + + uint32_t key = dfTexEffect.getFlags(); + b->add32(key); + b->add32(dfTexEffect.numTextureSamplers()); + } + +private: + GrDistanceFieldLCDTextGeoProc::DistanceAdjust fDistanceAdjust; + UniformHandle fDistanceAdjustUni; + + SkISize fAtlasSize; + UniformHandle fAtlasSizeInvUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps, + const sk_sp* proxies, + int numProxies, + const GrSamplerState& params, + DistanceAdjust distanceAdjust, + uint32_t flags, + const SkMatrix& localMatrix) + : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID) + , fLocalMatrix(localMatrix) + , fDistanceAdjust(distanceAdjust) + , fFlags(flags & kLCD_DistanceFieldEffectMask) { + SkASSERT(numProxies <= kMaxTextures); + SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag)); + + if (fFlags & kPerspective_DistanceFieldEffectFlag) { + fInPosition = {"inPosition", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + } else { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + } + fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType}; + fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType, + caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); + + if (numProxies) { + fAtlasSize = proxies[0]->isize(); + } + + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + fTextureSamplers[i].reset(proxies[i]->textureType(), params, proxies[i]->textureSwizzle()); + } + this->setTextureSamplerCnt(numProxies); +} + +void GrDistanceFieldLCDTextGeoProc::addNewProxies(const sk_sp* proxies, + int numProxies, + const GrSamplerState& params) { + SkASSERT(numProxies <= kMaxTextures); + + if (!fTextureSamplers[0].isInitialized()) { + fAtlasSize = proxies[0]->isize(); + } + + for (int i = 0; i < numProxies; ++i) { + SkASSERT(proxies[i]); + SkASSERT(proxies[i]->isize() == fAtlasSize); + + if (!fTextureSamplers[i].isInitialized()) { + fTextureSamplers[i].reset(proxies[i]->textureType(), params, + proxies[i]->textureSwizzle()); + } + } + this->setTextureSamplerCnt(numProxies); +} + +void GrDistanceFieldLCDTextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLDistanceFieldLCDTextGeoProc::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* GrDistanceFieldLCDTextGeoProc::createGLSLInstance(const GrShaderCaps&) const { + return new GrGLDistanceFieldLCDTextGeoProc(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc); + +#if GR_TEST_UTILS +sk_sp GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx : + GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxies[kMaxTextures] = { + d->textureProxy(texIdx), + nullptr, + nullptr, + nullptr + }; + + GrSamplerState::WrapMode wrapModes[2]; + GrTest::TestWrapModes(d->fRandom, wrapModes); + GrSamplerState samplerState(wrapModes, d->fRandom->nextBool() + ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kNearest); + DistanceAdjust wa = { 0.0f, 0.1f, -0.1f }; + uint32_t flags = kUseLCD_DistanceFieldEffectFlag; + flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0; + if (flags & kSimilarity_DistanceFieldEffectFlag) { + flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0; + } + flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0; + SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom); + return GrDistanceFieldLCDTextGeoProc::Make(*d->caps()->shaderCaps(), proxies, 1, samplerState, + wa, flags, localMatrix); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.h b/gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.h new file mode 100644 index 0000000000..20bb6262ae --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrDistanceFieldGeoProc.h @@ -0,0 +1,265 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDistanceFieldGeoProc_DEFINED +#define GrDistanceFieldGeoProc_DEFINED + +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProcessor.h" + +class GrGLDistanceFieldA8TextGeoProc; +class GrGLDistanceFieldPathGeoProc; +class GrGLDistanceFieldLCDTextGeoProc; +class GrInvariantOutput; + +enum GrDistanceFieldEffectFlags { + kSimilarity_DistanceFieldEffectFlag = 0x01, // ctm is similarity matrix + kScaleOnly_DistanceFieldEffectFlag = 0x02, // ctm has only scale and translate + kPerspective_DistanceFieldEffectFlag = 0x04, // ctm has perspective (and positions are x,y,w) + kUseLCD_DistanceFieldEffectFlag = 0x08, // use lcd text + kBGR_DistanceFieldEffectFlag = 0x10, // lcd display has bgr order + kPortrait_DistanceFieldEffectFlag = 0x20, // lcd display is in portrait mode (not used yet) + kGammaCorrect_DistanceFieldEffectFlag = 0x40, // assume gamma-correct output (linear blending) + kAliased_DistanceFieldEffectFlag = 0x80, // monochrome output + + kInvalid_DistanceFieldEffectFlag = 0x100, // invalid state (for initialization) + + kUniformScale_DistanceFieldEffectMask = kSimilarity_DistanceFieldEffectFlag | + kScaleOnly_DistanceFieldEffectFlag, + // The subset of the flags relevant to GrDistanceFieldA8TextGeoProc + kNonLCD_DistanceFieldEffectMask = kSimilarity_DistanceFieldEffectFlag | + kScaleOnly_DistanceFieldEffectFlag | + kPerspective_DistanceFieldEffectFlag | + kGammaCorrect_DistanceFieldEffectFlag | + kAliased_DistanceFieldEffectFlag, + // The subset of the flags relevant to GrDistanceFieldLCDTextGeoProc + kLCD_DistanceFieldEffectMask = kSimilarity_DistanceFieldEffectFlag | + kScaleOnly_DistanceFieldEffectFlag | + kPerspective_DistanceFieldEffectFlag | + kUseLCD_DistanceFieldEffectFlag | + kBGR_DistanceFieldEffectFlag | + kGammaCorrect_DistanceFieldEffectFlag, +}; + +/** + * The output color of this effect is a modulation of the input color and a sample from a + * distance field texture (using a smoothed step function near 0.5). + * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input + * coords are a custom attribute. Gamma correction is handled via a texture LUT. + */ +class GrDistanceFieldA8TextGeoProc : public GrGeometryProcessor { +public: + static constexpr int kMaxTextures = 4; + + /** The local matrix should be identity if local coords are not required by the GrPipeline. */ +#ifdef SK_GAMMA_APPLY_TO_A8 + static sk_sp Make(const GrShaderCaps& caps, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params, float lum, uint32_t flags, + const SkMatrix& localMatrixIfUsesLocalCoords) { + return sk_sp(new GrDistanceFieldA8TextGeoProc( + caps, proxies, numActiveProxies, params, lum, flags, localMatrixIfUsesLocalCoords)); + } +#else + static sk_sp Make(const GrShaderCaps& caps, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params, uint32_t flags, + const SkMatrix& localMatrixIfUsesLocalCoords) { + return sk_sp(new GrDistanceFieldA8TextGeoProc( + caps, proxies, numActiveProxies, params, flags, localMatrixIfUsesLocalCoords)); + } +#endif + + ~GrDistanceFieldA8TextGeoProc() override {} + + const char* name() const override { return "DistanceFieldA8Text"; } + + const Attribute& inPosition() const { return fInPosition; } + const Attribute& inColor() const { return fInColor; } + const Attribute& inTextureCoords() const { return fInTextureCoords; } + const SkMatrix& localMatrix() const { return fLocalMatrix; } +#ifdef SK_GAMMA_APPLY_TO_A8 + float getDistanceAdjust() const { return fDistanceAdjust; } +#endif + uint32_t getFlags() const { return fFlags; } + const SkISize& atlasSize() const { return fAtlasSize; } + + void addNewProxies(const sk_sp* proxies, int numProxies, const GrSamplerState&); + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params, +#ifdef SK_GAMMA_APPLY_TO_A8 + float distanceAdjust, +#endif + uint32_t flags, const SkMatrix& localMatrix); + + const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; } + + TextureSampler fTextureSamplers[kMaxTextures]; + SkISize fAtlasSize; // size for all textures used with fTextureSamplers[]. + SkMatrix fLocalMatrix; + Attribute fInPosition; + Attribute fInColor; + Attribute fInTextureCoords; + uint32_t fFlags; +#ifdef SK_GAMMA_APPLY_TO_A8 + float fDistanceAdjust; +#endif + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +/** + * The output color of this effect is a modulation of the input color and a sample from a + * distance field texture (using a smoothed step function near 0.5). + * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input + * coords are a custom attribute. No gamma correct blending is applied. Used for paths only. + */ +class GrDistanceFieldPathGeoProc : public GrGeometryProcessor { +public: + static constexpr int kMaxTextures = 4; + + /** The local matrix should be identity if local coords are not required by the GrPipeline. */ + static sk_sp Make(const GrShaderCaps& caps, + const SkMatrix& matrix, + bool wideColor, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params, uint32_t flags) { + return sk_sp( + new GrDistanceFieldPathGeoProc(caps, matrix, wideColor, proxies, numActiveProxies, + params, flags)); + } + + ~GrDistanceFieldPathGeoProc() override {} + + const char* name() const override { return "DistanceFieldPath"; } + + const Attribute& inPosition() const { return fInPosition; } + const Attribute& inColor() const { return fInColor; } + const Attribute& inTextureCoords() const { return fInTextureCoords; } + const SkMatrix& matrix() const { return fMatrix; } + uint32_t getFlags() const { return fFlags; } + const SkISize& atlasSize() const { return fAtlasSize; } + + void addNewProxies(const sk_sp*, int numActiveProxies, const GrSamplerState&); + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + GrDistanceFieldPathGeoProc(const GrShaderCaps& caps, + const SkMatrix& matrix, + bool wideColor, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState&, uint32_t flags); + + const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; } + + SkMatrix fMatrix; // view matrix if perspective, local matrix otherwise + TextureSampler fTextureSamplers[kMaxTextures]; + SkISize fAtlasSize; // size for all textures used with fTextureSamplers[]. + Attribute fInPosition; + Attribute fInColor; + Attribute fInTextureCoords; + uint32_t fFlags; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +/** + * The output color of this effect is a modulation of the input color and samples from a + * distance field texture (using a smoothed step function near 0.5), adjusted for LCD displays. + * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input + * coords are a custom attribute. Gamma correction is handled via a texture LUT. + */ +class GrDistanceFieldLCDTextGeoProc : public GrGeometryProcessor { +public: + static constexpr int kMaxTextures = 4; + + struct DistanceAdjust { + SkScalar fR, fG, fB; + static DistanceAdjust Make(SkScalar r, SkScalar g, SkScalar b) { + DistanceAdjust result; + result.fR = r; result.fG = g; result.fB = b; + return result; + } + bool operator==(const DistanceAdjust& wa) const { + return (fR == wa.fR && fG == wa.fG && fB == wa.fB); + } + bool operator!=(const DistanceAdjust& wa) const { + return !(*this == wa); + } + }; + + static sk_sp Make(const GrShaderCaps& caps, + const sk_sp* proxies, + int numActiveProxies, + const GrSamplerState& params, + DistanceAdjust distanceAdjust, + uint32_t flags, + const SkMatrix& localMatrixIfUsesLocalCoords) { + return sk_sp( + new GrDistanceFieldLCDTextGeoProc(caps, proxies, numActiveProxies, params, + distanceAdjust, flags, localMatrixIfUsesLocalCoords)); + } + + ~GrDistanceFieldLCDTextGeoProc() override {} + + const char* name() const override { return "DistanceFieldLCDText"; } + + const Attribute& inPosition() const { return fInPosition; } + const Attribute& inColor() const { return fInColor; } + const Attribute& inTextureCoords() const { return fInTextureCoords; } + DistanceAdjust getDistanceAdjust() const { return fDistanceAdjust; } + uint32_t getFlags() const { return fFlags; } + const SkMatrix& localMatrix() const { return fLocalMatrix; } + const SkISize& atlasSize() const { return fAtlasSize; } + + void addNewProxies(const sk_sp*, int numActiveProxies, const GrSamplerState&); + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps, const sk_sp* proxies, + int numActiveProxies, const GrSamplerState& params, + DistanceAdjust wa, uint32_t flags, const SkMatrix& localMatrix); + + const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; } + + TextureSampler fTextureSamplers[kMaxTextures]; + SkISize fAtlasSize; // size for all textures used with fTextureSamplers[]. + const SkMatrix fLocalMatrix; + DistanceAdjust fDistanceAdjust; + Attribute fInPosition; + Attribute fInColor; + Attribute fInTextureCoords; + uint32_t fFlags; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrEllipseEffect.fp b/gfx/skia/skia/src/gpu/effects/GrEllipseEffect.fp new file mode 100644 index 0000000000..8e8c766d0f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrEllipseEffect.fp @@ -0,0 +1,132 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +@header { + #include "src/gpu/GrShaderCaps.h" +} + +layout(key) in GrClipEdgeType edgeType; +in float2 center; +in float2 radii; + +float2 prevCenter; +float2 prevRadii = float2(-1); +// The ellipse uniform is (center.x, center.y, 1 / rx^2, 1 / ry^2) +// The last two terms can underflow when float != fp32, so we also provide a workaround. +uniform float4 ellipse; + +bool medPrecision = !sk_Caps.floatIs32Bits; +layout(when=medPrecision) uniform float2 scale; + +@make { + static std::unique_ptr Make(GrClipEdgeType edgeType, SkPoint center, + SkPoint radii, const GrShaderCaps& caps) { + // Small radii produce bad results on devices without full float. + if (!caps.floatIs32Bits() && (radii.fX < 0.5f || radii.fY < 0.5f)) { + return nullptr; + } + // Very narrow ellipses produce bad results on devices without full float + if (!caps.floatIs32Bits() && (radii.fX > 255*radii.fY || radii.fY > 255*radii.fX)) { + return nullptr; + } + // Very large ellipses produce bad results on devices without full float + if (!caps.floatIs32Bits() && (radii.fX > 16384 || radii.fY > 16384)) { + return nullptr; + } + return std::unique_ptr(new GrEllipseEffect(edgeType, center, radii)); + } +} + +@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag } + +@setData(pdman) { + if (radii != prevRadii || center != prevCenter) { + float invRXSqd; + float invRYSqd; + // If we're using a scale factor to work around precision issues, choose the larger + // radius as the scale factor. The inv radii need to be pre-adjusted by the scale + // factor. + if (scale.isValid()) { + if (radii.fX > radii.fY) { + invRXSqd = 1.f; + invRYSqd = (radii.fX * radii.fX) / (radii.fY * radii.fY); + pdman.set2f(scale, radii.fX, 1.f / radii.fX); + } else { + invRXSqd = (radii.fY * radii.fY) / (radii.fX * radii.fX); + invRYSqd = 1.f; + pdman.set2f(scale, radii.fY, 1.f / radii.fY); + } + } else { + invRXSqd = 1.f / (radii.fX * radii.fX); + invRYSqd = 1.f / (radii.fY * radii.fY); + } + pdman.set4f(ellipse, center.fX, center.fY, invRXSqd, invRYSqd); + prevCenter = center; + prevRadii = radii; + } +} + +void main() { + // d is the offset to the ellipse center + float2 d = sk_FragCoord.xy - ellipse.xy; + // If we're on a device with a "real" mediump then we'll do the distance computation in a space + // that is normalized by the larger radius or 128, whichever is smaller. The scale uniform will + // be scale, 1/scale. The inverse squared radii uniform values are already in this normalized space. + // The center is not. + @if (medPrecision) { + d *= scale.y; + } + float2 Z = d * ellipse.zw; + // implicit is the evaluation of (x/rx)^2 + (y/ry)^2 - 1. + float implicit = dot(Z, d) - 1; + // grad_dot is the squared length of the gradient of the implicit. + float grad_dot = 4 * dot(Z, Z); + // Avoid calling inversesqrt on zero. + @if (medPrecision) { + grad_dot = max(grad_dot, 6.1036e-5); + } else { + grad_dot = max(grad_dot, 1.1755e-38); + } + float approx_dist = implicit * inversesqrt(grad_dot); + @if (medPrecision) { + approx_dist *= scale.x; + } + + half alpha; + @switch (edgeType) { + case GrClipEdgeType::kFillBW: + alpha = approx_dist > 0.0 ? 0.0 : 1.0; + break; + case GrClipEdgeType::kFillAA: + alpha = saturate(0.5 - half(approx_dist)); + break; + case GrClipEdgeType::kInverseFillBW: + alpha = approx_dist > 0.0 ? 1.0 : 0.0; + break; + case GrClipEdgeType::kInverseFillAA: + alpha = saturate(0.5 + half(approx_dist)); + break; + default: + // hairline not supported + discard; + } + sk_OutColor = sk_InColor * alpha; +} + +@test(testData) { + SkPoint center; + center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f); + center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f); + SkScalar rx = testData->fRandom->nextRangeF(0.f, 1000.f); + SkScalar ry = testData->fRandom->nextRangeF(0.f, 1000.f); + GrClipEdgeType et; + do { + et = (GrClipEdgeType) testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt); + } while (GrClipEdgeType::kHairlineAA == et); + return GrEllipseEffect::Make(et, center, SkPoint::Make(rx, ry), + *testData->caps()->shaderCaps()); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp new file mode 100644 index 0000000000..fd70616a6b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp @@ -0,0 +1,305 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +// For brevity +using UniformHandle = GrGLSLProgramDataManager::UniformHandle; +using Direction = GrGaussianConvolutionFragmentProcessor::Direction; + +class GrGLConvolutionEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + UniformHandle fKernelUni; + UniformHandle fImageIncrementUni; + UniformHandle fBoundsUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GrGLConvolutionEffect::emitCode(EmitArgs& args) { + const GrGaussianConvolutionFragmentProcessor& ce = + args.fFp.cast(); + + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "ImageIncrement"); + if (ce.useBounds()) { + fBoundsUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "Bounds"); + } + + int width = ce.width(); + + int arrayCount = (width + 3) / 4; + SkASSERT(4 * arrayCount >= width); + + fKernelUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, kHalf4_GrSLType, + "Kernel", arrayCount); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + + fragBuilder->codeAppendf("%s = half4(0, 0, 0, 0);", args.fOutputColor); + + const GrShaderVar& kernel = uniformHandler->getUniformVariable(fKernelUni); + const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni); + + fragBuilder->codeAppendf("float2 coord = %s - %d.0 * %s;", coords2D.c_str(), ce.radius(), imgInc); + fragBuilder->codeAppend("float2 coordSampled = half2(0, 0);"); + + // Manually unroll loop because some drivers don't; yields 20-30% speedup. + const char* kVecSuffix[4] = {".x", ".y", ".z", ".w"}; + for (int i = 0; i < width; i++) { + SkString index; + SkString kernelIndex; + index.appendS32(i / 4); + kernel.appendArrayAccess(index.c_str(), &kernelIndex); + kernelIndex.append(kVecSuffix[i & 0x3]); + + fragBuilder->codeAppend("coordSampled = coord;"); + if (ce.useBounds()) { + // We used to compute a bool indicating whether we're in bounds or not, cast it to a + // float, and then mul weight*texture_sample by the float. However, the Adreno 430 seems + // to have a bug that caused corruption. + const char* bounds = uniformHandler->getUniformCStr(fBoundsUni); + const char* component = ce.direction() == Direction::kY ? "y" : "x"; + + switch (ce.mode()) { + case GrTextureDomain::kClamp_Mode: { + fragBuilder->codeAppendf("coordSampled.%s = clamp(coord.%s, %s.x, %s.y);\n", + component, component, bounds, bounds); + break; + } + case GrTextureDomain::kRepeat_Mode: { + fragBuilder->codeAppendf("coordSampled.%s = " + "mod(coord.%s - %s.x, %s.y - %s.x) + %s.x;\n", + component, component, bounds, bounds, bounds, bounds); + break; + } + case GrTextureDomain::kDecal_Mode: { + fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {", + component, bounds, component, bounds); + break; + } + default: { + SK_ABORT("Unsupported operation."); + } + } + } + fragBuilder->codeAppendf("%s += ", args.fOutputColor); + fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coordSampled"); + fragBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str()); + if (GrTextureDomain::kDecal_Mode == ce.mode()) { + fragBuilder->codeAppend("}"); + } + fragBuilder->codeAppendf("coord += %s;\n", imgInc); + } + fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor); +} + +void GrGLConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + const GrGaussianConvolutionFragmentProcessor& conv = + processor.cast(); + GrSurfaceProxy* proxy = conv.textureSampler(0).proxy(); + GrTexture& texture = *proxy->peekTexture(); + + float imageIncrement[2] = {0}; + float ySign = proxy->origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f; + switch (conv.direction()) { + case Direction::kX: + imageIncrement[0] = 1.0f / texture.width(); + break; + case Direction::kY: + imageIncrement[1] = ySign / texture.height(); + break; + default: + SK_ABORT("Unknown filter direction."); + } + pdman.set2fv(fImageIncrementUni, 1, imageIncrement); + if (conv.useBounds()) { + float bounds[2] = {0}; + bounds[0] = conv.bounds()[0]; + bounds[1] = conv.bounds()[1]; + if (GrTextureDomain::kClamp_Mode == conv.mode()) { + bounds[0] += SK_ScalarHalf; + bounds[1] -= SK_ScalarHalf; + } + if (Direction::kX == conv.direction()) { + SkScalar inv = SkScalarInvert(SkIntToScalar(texture.width())); + bounds[0] *= inv; + bounds[1] *= inv; + } else { + SkScalar inv = SkScalarInvert(SkIntToScalar(texture.height())); + if (proxy->origin() != kTopLeft_GrSurfaceOrigin) { + float tmp = bounds[0]; + bounds[0] = 1.0f - (inv * bounds[1]); + bounds[1] = 1.0f - (inv * tmp); + } else { + bounds[0] *= inv; + bounds[1] *= inv; + } + } + + SkASSERT(bounds[0] <= bounds[1]); + pdman.set2f(fBoundsUni, bounds[0], bounds[1]); + } + int width = conv.width(); + + int arrayCount = (width + 3) / 4; + SkASSERT(4 * arrayCount >= width); + pdman.set4fv(fKernelUni, arrayCount, conv.kernel()); +} + +void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrGaussianConvolutionFragmentProcessor& conv = + processor.cast(); + uint32_t key = conv.radius(); + key <<= 3; + if (conv.useBounds()) { + key |= Direction::kY == conv.direction() ? 0x4 : 0x0; + } + key |= static_cast(conv.mode()); + b->add32(key); +} + +/////////////////////////////////////////////////////////////////////////////// +static void fill_in_1D_gaussian_kernel(float* kernel, int width, float gaussianSigma, int radius) { + const float twoSigmaSqrd = 2.0f * gaussianSigma * gaussianSigma; + if (SkScalarNearlyZero(twoSigmaSqrd, SK_ScalarNearlyZero)) { + for (int i = 0; i < width; ++i) { + kernel[i] = 0.0f; + } + return; + } + + const float denom = 1.0f / twoSigmaSqrd; + + float sum = 0.0f; + for (int i = 0; i < width; ++i) { + float x = static_cast(i - radius); + // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian + // is dropped here, since we renormalize the kernel below. + kernel[i] = sk_float_exp(-x * x * denom); + sum += kernel[i]; + } + // Normalize the kernel + float scale = 1.0f / sum; + for (int i = 0; i < width; ++i) { + kernel[i] *= scale; + } +} + +GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor( + sk_sp proxy, + GrColorType srcColorType, + Direction direction, + int radius, + float gaussianSigma, + GrTextureDomain::Mode mode, + int bounds[2]) + : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, + ModulateForSamplerOptFlags(srcColorType, + mode == GrTextureDomain::kDecal_Mode)) + , fCoordTransform(proxy.get()) + , fTextureSampler(std::move(proxy)) + , fRadius(radius) + , fDirection(direction) + , fMode(mode) { + // Make sure the sampler's ctor uses the clamp wrap mode + SkASSERT(fTextureSampler.samplerState().wrapModeX() == GrSamplerState::WrapMode::kClamp && + fTextureSampler.samplerState().wrapModeY() == GrSamplerState::WrapMode::kClamp); + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); + SkASSERT(radius <= kMaxKernelRadius); + + fill_in_1D_gaussian_kernel(fKernel, this->width(), gaussianSigma, this->radius()); + + memcpy(fBounds, bounds, sizeof(fBounds)); +} + +GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor( + const GrGaussianConvolutionFragmentProcessor& that) + : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, that.optimizationFlags()) + , fCoordTransform(that.fCoordTransform) + , fTextureSampler(that.fTextureSampler) + , fRadius(that.fRadius) + , fDirection(that.fDirection) + , fMode(that.fMode) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); + memcpy(fKernel, that.fKernel, that.width() * sizeof(float)); + memcpy(fBounds, that.fBounds, sizeof(fBounds)); +} + +void GrGaussianConvolutionFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLConvolutionEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrGaussianConvolutionFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLConvolutionEffect; +} + +bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrGaussianConvolutionFragmentProcessor& s = + sBase.cast(); + return (this->radius() == s.radius() && this->direction() == s.direction() && + this->mode() == s.mode() && + 0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) && + 0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float))); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor); + +#if GR_TEST_UTILS +std::unique_ptr GrGaussianConvolutionFragmentProcessor::TestCreate( + GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + + int bounds[2]; + int modeIdx = d->fRandom->nextRangeU(0, GrTextureDomain::kModeCount-1); + + Direction dir; + if (d->fRandom->nextBool()) { + dir = Direction::kX; + bounds[0] = d->fRandom->nextRangeU(0, proxy->width()-2); + bounds[1] = d->fRandom->nextRangeU(bounds[0]+1, proxy->width()-1); + } else { + dir = Direction::kY; + bounds[0] = d->fRandom->nextRangeU(0, proxy->height()-2); + bounds[1] = d->fRandom->nextRangeU(bounds[0]+1, proxy->height()-1); + } + + int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius); + float sigma = radius / 3.f; + + return GrGaussianConvolutionFragmentProcessor::Make( + std::move(proxy), d->textureProxyColorType(texIdx), + dir, radius, sigma, static_cast(modeIdx), bounds); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h new file mode 100644 index 0000000000..843ed30d65 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h @@ -0,0 +1,104 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGaussianConvolutionFragmentProcessor_DEFINED +#define GrGaussianConvolutionFragmentProcessor_DEFINED + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/effects/GrTextureDomain.h" + +/** + * A 1D Gaussian convolution effect. The kernel is computed as an array of 2 * half-width weights. + * Each texel is multiplied by it's weight and summed to determine the filtered color. The output + * color is set to a modulation of the filtered and input colors. + */ +class GrGaussianConvolutionFragmentProcessor : public GrFragmentProcessor { +public: + enum class Direction { kX, kY }; + + /// Convolve with a Gaussian kernel + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + Direction dir, + int halfWidth, + float gaussianSigma, + GrTextureDomain::Mode mode, + int* bounds) { + return std::unique_ptr(new GrGaussianConvolutionFragmentProcessor( + std::move(proxy), srcColorType, dir, halfWidth, gaussianSigma, mode, bounds)); + } + + const float* kernel() const { return fKernel; } + + const int* bounds() const { return fBounds; } + bool useBounds() const { return fMode != GrTextureDomain::kIgnore_Mode; } + int radius() const { return fRadius; } + int width() const { return 2 * fRadius + 1; } + Direction direction() const { return fDirection; } + + GrTextureDomain::Mode mode() const { return fMode; } + + const char* name() const override { return "GaussianConvolution"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + str.appendf("dir: %s radius: %d bounds: [%d %d]", + Direction::kX == fDirection ? "X" : "Y", + fRadius, + fBounds[0], fBounds[1]); + return str; + } +#endif + + std::unique_ptr clone() const override { + return std::unique_ptr( + new GrGaussianConvolutionFragmentProcessor(*this)); + } + + // This was decided based on the min allowed value for the max texture + // samples per fragment program run in DX9SM2 (32). A sigma param of 4.0 + // on a blur filter gives a kernel width of 25 while a sigma of 5.0 + // would exceed a 32 wide kernel. + static const int kMaxKernelRadius = 12; + // With a C++11 we could have a constexpr version of WidthFromRadius() + // and not have to duplicate this calculation. + static const int kMaxKernelWidth = 2 * kMaxKernelRadius + 1; + +private: + /// Convolve with a Gaussian kernel + GrGaussianConvolutionFragmentProcessor(sk_sp, GrColorType srcColorType, + Direction, int halfWidth, float gaussianSigma, + GrTextureDomain::Mode mode, int bounds[2]); + + explicit GrGaussianConvolutionFragmentProcessor(const GrGaussianConvolutionFragmentProcessor&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + GrCoordTransform fCoordTransform; + TextureSampler fTextureSampler; + // TODO: Inline the kernel constants into the generated shader code. This may involve pulling + // some of the logic from SkGpuBlurUtils into this class related to radius/sigma calculations. + float fKernel[kMaxKernelWidth]; + int fBounds[2]; + int fRadius; + Direction fDirection; + GrTextureDomain::Mode fMode; + + typedef GrFragmentProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrLumaColorFilterEffect.fp b/gfx/skia/skia/src/gpu/effects/GrLumaColorFilterEffect.fp new file mode 100644 index 0000000000..1548b26e0b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrLumaColorFilterEffect.fp @@ -0,0 +1,27 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +@optimizationFlags { + kConstantOutputForConstantInput_OptimizationFlag +} + +@class { + #include "include/private/SkColorData.h" + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + float luma = SK_ITU_BT709_LUM_COEFF_R * input.fR + + SK_ITU_BT709_LUM_COEFF_G * input.fG + + SK_ITU_BT709_LUM_COEFF_B * input.fB; + return { 0, 0, 0, SkTPin(luma, 0.0f, 1.0f) }; + } +} + +void main() { + const half3 SK_ITU_BT709_LUM_COEFF = half3(0.2126, 0.7152, 0.0722); + half luma = saturate(dot(SK_ITU_BT709_LUM_COEFF, sk_InColor.rgb)); + sk_OutColor = half4(0, 0, 0, luma); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrMagnifierEffect.fp b/gfx/skia/skia/src/gpu/effects/GrMagnifierEffect.fp new file mode 100644 index 0000000000..e6a9aa257a --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrMagnifierEffect.fp @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in uniform sampler2D src; +layout(ctype=SkIRect) in int4 bounds; +uniform float4 boundsUniform; +layout(ctype=SkRect) in float4 srcRect; +in uniform float xInvZoom; +in uniform float yInvZoom; +in uniform float xInvInset; +in uniform float yInvInset; + +uniform half2 offset; + +@coordTransform(src) { + SkMatrix::I() +} + +void main() { + float2 coord = sk_TransformedCoords2D[0]; + float2 zoom_coord = offset + coord * float2(xInvZoom, yInvZoom); + float2 delta = (coord - boundsUniform.xy) * boundsUniform.zw; + delta = min(delta, half2(1.0, 1.0) - delta); + delta *= float2(xInvInset, yInvInset); + + float weight = 0.0; + if (delta.s < 2.0 && delta.t < 2.0) { + delta = half2(2.0, 2.0) - delta; + float dist = length(delta); + dist = max(2.0 - dist, 0.0); + weight = min(dist * dist, 1.0); + } else { + float2 delta_squared = delta * delta; + weight = min(min(delta_squared.x, delta_squared.y), 1.0); + } + + sk_OutColor = sample(src, mix(coord, zoom_coord, weight)); +} + +@setData(pdman) { + SkScalar invW = 1.0f / src.width(); + SkScalar invH = 1.0f / src.height(); + + { + SkScalar y = srcRect.y() * invH; + if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) { + y = 1.0f - (srcRect.height() / bounds.height()) - y; + } + + pdman.set2f(offset, srcRect.x() * invW, y); + } + + { + SkScalar y = bounds.y() * invH; + SkScalar hSign = 1.f; + if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) { + y = 1.0f - bounds.y() * invH; + hSign = -1.f; + } + + pdman.set4f(boundsUniform, + bounds.x() * invW, + y, + SkIntToScalar(src.width()) / bounds.width(), + hSign * SkIntToScalar(src.height()) / bounds.height()); + } +} + +@test(d) { + sk_sp proxy = d->textureProxy(0); + const int kMaxWidth = 200; + const int kMaxHeight = 200; + const SkScalar kMaxInset = 20.0f; + uint32_t width = d->fRandom->nextULessThan(kMaxWidth); + uint32_t height = d->fRandom->nextULessThan(kMaxHeight); + SkScalar inset = d->fRandom->nextRangeScalar(1.0f, kMaxInset); + + SkIRect bounds = SkIRect::MakeWH(SkIntToScalar(kMaxWidth), SkIntToScalar(kMaxHeight)); + SkRect srcRect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); + + auto effect = GrMagnifierEffect::Make(std::move(proxy), + bounds, + srcRect, + srcRect.width() / bounds.width(), + srcRect.height() / bounds.height(), + bounds.width() / inset, + bounds.height() / inset); + SkASSERT(effect); + return effect; +} diff --git a/gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.cpp new file mode 100644 index 0000000000..86df7a02ec --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/gpu/effects/GrMatrixConvolutionEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrGLMatrixConvolutionEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; + + UniformHandle fKernelUni; + UniformHandle fImageIncrementUni; + UniformHandle fKernelOffsetUni; + UniformHandle fGainUni; + UniformHandle fBiasUni; + GrTextureDomain::GLDomain fDomain; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GrGLMatrixConvolutionEffect::emitCode(EmitArgs& args) { + const GrMatrixConvolutionEffect& mce = args.fFp.cast(); + const GrTextureDomain& domain = mce.domain(); + + int kWidth = mce.kernelSize().width(); + int kHeight = mce.kernelSize().height(); + + int arrayCount = (kWidth * kHeight + 3) / 4; + SkASSERT(4 * arrayCount >= kWidth * kHeight); + + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "ImageIncrement"); + fKernelUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, kHalf4_GrSLType, + "Kernel", + arrayCount); + fKernelOffsetUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "KernelOffset"); + fGainUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "Gain"); + fBiasUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "Bias"); + + const char* kernelOffset = uniformHandler->getUniformCStr(fKernelOffsetUni); + const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni); + const char* kernel = uniformHandler->getUniformCStr(fKernelUni); + const char* gain = uniformHandler->getUniformCStr(fGainUni); + const char* bias = uniformHandler->getUniformCStr(fBiasUni); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppend("half4 sum = half4(0, 0, 0, 0);"); + fragBuilder->codeAppendf("float2 coord = %s - %s * %s;", coords2D.c_str(), kernelOffset, imgInc); + fragBuilder->codeAppend("half4 c;"); + + const char* kVecSuffix[4] = { ".x", ".y", ".z", ".w" }; + for (int y = 0; y < kHeight; y++) { + for (int x = 0; x < kWidth; x++) { + GrGLSLShaderBuilder::ShaderBlock block(fragBuilder); + int offset = y*kWidth + x; + + fragBuilder->codeAppendf("half k = %s[%d]%s;", kernel, offset / 4, + kVecSuffix[offset & 0x3]); + SkString coord; + coord.printf("coord + half2(%d, %d) * %s", x, y, imgInc); + fDomain.sampleTexture(fragBuilder, + uniformHandler, + args.fShaderCaps, + domain, + "c", + coord, + args.fTexSamplers[0]); + if (!mce.convolveAlpha()) { + fragBuilder->codeAppend("c.rgb /= c.a;"); + fragBuilder->codeAppend("c.rgb = saturate(c.rgb);"); + } + fragBuilder->codeAppend("sum += c * k;"); + } + } + if (mce.convolveAlpha()) { + fragBuilder->codeAppendf("%s = sum * %s + %s;", args.fOutputColor, gain, bias); + fragBuilder->codeAppendf("%s.a = saturate(%s.a);", args.fOutputColor, args.fOutputColor); + fragBuilder->codeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);", + args.fOutputColor, args.fOutputColor, args.fOutputColor); + } else { + fDomain.sampleTexture(fragBuilder, + uniformHandler, + args.fShaderCaps, + domain, + "c", + coords2D, + args.fTexSamplers[0]); + fragBuilder->codeAppendf("%s.a = c.a;", args.fOutputColor); + fragBuilder->codeAppendf("%s.rgb = saturate(sum.rgb * %s + %s);", args.fOutputColor, gain, bias); + fragBuilder->codeAppendf("%s.rgb *= %s.a;", args.fOutputColor, args.fOutputColor); + } + fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor); +} + +void GrGLMatrixConvolutionEffect::GenKey(const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const GrMatrixConvolutionEffect& m = processor.cast(); + SkASSERT(m.kernelSize().width() <= 0x7FFF && m.kernelSize().height() <= 0xFFFF); + uint32_t key = m.kernelSize().width() << 16 | m.kernelSize().height(); + key |= m.convolveAlpha() ? 1U << 31 : 0; + b->add32(key); + b->add32(GrTextureDomain::GLDomain::DomainKey(m.domain())); +} + +void GrGLMatrixConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + const GrMatrixConvolutionEffect& conv = processor.cast(); + GrTextureProxy* proxy = conv.textureSampler(0).proxy(); + GrTexture* texture = proxy->peekTexture(); + + float imageIncrement[2]; + float ySign = proxy->origin() == kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f; + imageIncrement[0] = 1.0f / texture->width(); + imageIncrement[1] = ySign / texture->height(); + pdman.set2fv(fImageIncrementUni, 1, imageIncrement); + pdman.set2fv(fKernelOffsetUni, 1, conv.kernelOffset()); + int kernelCount = conv.kernelSize().width() * conv.kernelSize().height(); + int arrayCount = (kernelCount + 3) / 4; + SkASSERT(4 * arrayCount >= kernelCount); + pdman.set4fv(fKernelUni, arrayCount, conv.kernel()); + pdman.set1f(fGainUni, conv.gain()); + pdman.set1f(fBiasUni, conv.bias()); + fDomain.setData(pdman, conv.domain(), proxy, conv.textureSampler(0).samplerState()); +} + +GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(sk_sp srcProxy, + const SkIRect& srcBounds, + const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha) + // To advertise either the modulation or opaqueness optimizations we'd have to examine the + // parameters. + : INHERITED(kGrMatrixConvolutionEffect_ClassID, kNone_OptimizationFlags) + , fCoordTransform(srcProxy.get()) + , fDomain(srcProxy.get(), GrTextureDomain::MakeTexelDomain(srcBounds, tileMode), + tileMode, tileMode) + , fTextureSampler(std::move(srcProxy)) + , fKernelSize(kernelSize) + , fGain(SkScalarToFloat(gain)) + , fBias(SkScalarToFloat(bias) / 255.0f) + , fConvolveAlpha(convolveAlpha) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); + for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) { + fKernel[i] = SkScalarToFloat(kernel[i]); + } + fKernelOffset[0] = static_cast(kernelOffset.x()); + fKernelOffset[1] = static_cast(kernelOffset.y()); +} + +GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(const GrMatrixConvolutionEffect& that) + : INHERITED(kGrMatrixConvolutionEffect_ClassID, kNone_OptimizationFlags) + , fCoordTransform(that.fCoordTransform) + , fDomain(that.fDomain) + , fTextureSampler(that.fTextureSampler) + , fKernelSize(that.fKernelSize) + , fGain(that.fGain) + , fBias(that.fBias) + , fConvolveAlpha(that.fConvolveAlpha) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); + memcpy(fKernel, that.fKernel, sizeof(float) * fKernelSize.width() * fKernelSize.height()); + memcpy(fKernelOffset, that.fKernelOffset, sizeof(fKernelOffset)); +} + +std::unique_ptr GrMatrixConvolutionEffect::clone() const { + return std::unique_ptr(new GrMatrixConvolutionEffect(*this)); +} + +void GrMatrixConvolutionEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLMatrixConvolutionEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrMatrixConvolutionEffect::onCreateGLSLInstance() const { + return new GrGLMatrixConvolutionEffect; +} + +bool GrMatrixConvolutionEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrMatrixConvolutionEffect& s = sBase.cast(); + return fKernelSize == s.kernelSize() && + !memcmp(fKernel, s.kernel(), + fKernelSize.width() * fKernelSize.height() * sizeof(float)) && + fGain == s.gain() && + fBias == s.bias() && + !memcmp(fKernelOffset, s.kernelOffset(), sizeof(fKernelOffset)) && + fConvolveAlpha == s.convolveAlpha() && + fDomain == s.domain(); +} + +static void fill_in_1D_gaussian_kernel_with_stride(float* kernel, int size, int stride, + float twoSigmaSqrd) { + SkASSERT(!SkScalarNearlyZero(twoSigmaSqrd, SK_ScalarNearlyZero)); + + const float sigmaDenom = 1.0f / twoSigmaSqrd; + const int radius = size / 2; + + float sum = 0.0f; + for (int i = 0; i < size; ++i) { + float term = static_cast(i - radius); + // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian + // is dropped here, since we renormalize the kernel below. + kernel[i * stride] = sk_float_exp(-term * term * sigmaDenom); + sum += kernel[i * stride]; + } + // Normalize the kernel + float scale = 1.0f / sum; + for (int i = 0; i < size; ++i) { + kernel[i * stride] *= scale; + } +} + +static void fill_in_2D_gaussian_kernel(float* kernel, int width, int height, + SkScalar sigmaX, SkScalar sigmaY) { + SkASSERT(width * height <= MAX_KERNEL_SIZE); + const float twoSigmaSqrdX = 2.0f * SkScalarToFloat(SkScalarSquare(sigmaX)); + const float twoSigmaSqrdY = 2.0f * SkScalarToFloat(SkScalarSquare(sigmaY)); + + // TODO: in all of these degenerate cases we're uploading (and using) a whole lot of zeros. + if (SkScalarNearlyZero(twoSigmaSqrdX, SK_ScalarNearlyZero) || + SkScalarNearlyZero(twoSigmaSqrdY, SK_ScalarNearlyZero)) { + // In this case the 2D Gaussian degenerates to a 1D Gaussian (in X or Y) or a point + SkASSERT(3 == width || 3 == height); + memset(kernel, 0, width*height*sizeof(float)); + + if (SkScalarNearlyZero(twoSigmaSqrdX, SK_ScalarNearlyZero) && + SkScalarNearlyZero(twoSigmaSqrdY, SK_ScalarNearlyZero)) { + // A point + SkASSERT(3 == width && 3 == height); + kernel[4] = 1.0f; + } else if (SkScalarNearlyZero(twoSigmaSqrdX, SK_ScalarNearlyZero)) { + // A 1D Gaussian in Y + SkASSERT(3 == width); + // Down the middle column of the kernel with a stride of width + fill_in_1D_gaussian_kernel_with_stride(&kernel[1], height, width, twoSigmaSqrdY); + } else { + // A 1D Gaussian in X + SkASSERT(SkScalarNearlyZero(twoSigmaSqrdY, SK_ScalarNearlyZero)); + SkASSERT(3 == height); + // Down the middle row of the kernel with a stride of 1 + fill_in_1D_gaussian_kernel_with_stride(&kernel[width], width, 1, twoSigmaSqrdX); + } + return; + } + + const float sigmaXDenom = 1.0f / twoSigmaSqrdX; + const float sigmaYDenom = 1.0f / twoSigmaSqrdY; + const int xRadius = width / 2; + const int yRadius = height / 2; + + float sum = 0.0f; + for (int x = 0; x < width; x++) { + float xTerm = static_cast(x - xRadius); + xTerm = xTerm * xTerm * sigmaXDenom; + for (int y = 0; y < height; y++) { + float yTerm = static_cast(y - yRadius); + float xyTerm = sk_float_exp(-(xTerm + yTerm * yTerm * sigmaYDenom)); + // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian + // is dropped here, since we renormalize the kernel below. + kernel[y * width + x] = xyTerm; + sum += xyTerm; + } + } + // Normalize the kernel + float scale = 1.0f / sum; + for (int i = 0; i < width * height; ++i) { + kernel[i] *= scale; + } +} + +// Static function to create a 2D convolution +std::unique_ptr GrMatrixConvolutionEffect::MakeGaussian( + sk_sp srcProxy, + const SkIRect& srcBounds, + const SkISize& kernelSize, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha, + SkScalar sigmaX, + SkScalar sigmaY) { + float kernel[MAX_KERNEL_SIZE]; + + fill_in_2D_gaussian_kernel(kernel, kernelSize.width(), kernelSize.height(), sigmaX, sigmaY); + + return std::unique_ptr( + new GrMatrixConvolutionEffect(std::move(srcProxy), srcBounds, kernelSize, kernel, + gain, bias, kernelOffset, tileMode, convolveAlpha)); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMatrixConvolutionEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrMatrixConvolutionEffect::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + + int width = d->fRandom->nextRangeU(1, MAX_KERNEL_SIZE); + int height = d->fRandom->nextRangeU(1, MAX_KERNEL_SIZE / width); + SkISize kernelSize = SkISize::Make(width, height); + std::unique_ptr kernel(new SkScalar[width * height]); + for (int i = 0; i < width * height; i++) { + kernel.get()[i] = d->fRandom->nextSScalar1(); + } + SkScalar gain = d->fRandom->nextSScalar1(); + SkScalar bias = d->fRandom->nextSScalar1(); + SkIPoint kernelOffset = SkIPoint::Make(d->fRandom->nextRangeU(0, kernelSize.width()), + d->fRandom->nextRangeU(0, kernelSize.height())); + SkIRect bounds = SkIRect::MakeXYWH(d->fRandom->nextRangeU(0, proxy->width()), + d->fRandom->nextRangeU(0, proxy->height()), + d->fRandom->nextRangeU(0, proxy->width()), + d->fRandom->nextRangeU(0, proxy->height())); + GrTextureDomain::Mode tileMode = + static_cast(d->fRandom->nextRangeU(0, 2)); + bool convolveAlpha = d->fRandom->nextBool(); + return GrMatrixConvolutionEffect::Make(std::move(proxy), + bounds, + kernelSize, + kernel.get(), + gain, + bias, + kernelOffset, + tileMode, + convolveAlpha); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.h b/gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.h new file mode 100644 index 0000000000..212d5c0973 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrMatrixConvolutionEffect.h @@ -0,0 +1,96 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMatrixConvolutionEffect_DEFINED +#define GrMatrixConvolutionEffect_DEFINED + +#include "src/gpu/effects/GrTextureDomain.h" + +// A little bit less than the minimum # uniforms required by DX9SM2 (32). +// Allows for a 5x5 kernel (or 25x1, for that matter). +#define MAX_KERNEL_SIZE 25 + +class GrMatrixConvolutionEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp srcProxy, + const SkIRect& srcBounds, + const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha) { + return std::unique_ptr( + new GrMatrixConvolutionEffect(std::move(srcProxy), srcBounds, kernelSize, kernel, + gain, bias, kernelOffset, tileMode, convolveAlpha)); + } + + static std::unique_ptr MakeGaussian(sk_sp srcProxy, + const SkIRect& srcBounds, + const SkISize& kernelSize, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha, + SkScalar sigmaX, + SkScalar sigmaY); + + const SkIRect& bounds() const { return fBounds; } + const SkISize& kernelSize() const { return fKernelSize; } + const float* kernelOffset() const { return fKernelOffset; } + const float* kernel() const { return fKernel; } + float gain() const { return fGain; } + float bias() const { return fBias; } + bool convolveAlpha() const { return fConvolveAlpha; } + const GrTextureDomain& domain() const { return fDomain; } + + const char* name() const override { return "MatrixConvolution"; } + + std::unique_ptr clone() const override; + +private: + // srcProxy is the texture that is going to be convolved + // srcBounds is the subset of 'srcProxy' that will be used (e.g., for clamp mode) + GrMatrixConvolutionEffect(sk_sp srcProxy, + const SkIRect& srcBounds, + const SkISize& kernelSize, + const SkScalar* kernel, + SkScalar gain, + SkScalar bias, + const SkIPoint& kernelOffset, + GrTextureDomain::Mode tileMode, + bool convolveAlpha); + + GrMatrixConvolutionEffect(const GrMatrixConvolutionEffect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const TextureSampler& onTextureSampler(int i) const override { return fTextureSampler; } + + GrCoordTransform fCoordTransform; + GrTextureDomain fDomain; + TextureSampler fTextureSampler; + SkIRect fBounds; + SkISize fKernelSize; + float fKernel[MAX_KERNEL_SIZE]; + float fGain; + float fBias; + float fKernelOffset[2]; + bool fConvolveAlpha; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrMixerEffect.fp b/gfx/skia/skia/src/gpu/effects/GrMixerEffect.fp new file mode 100644 index 0000000000..f2bf79ec98 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrMixerEffect.fp @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Mixes the output of two FPs. + +in fragmentProcessor fp0; +in fragmentProcessor? fp1; +in uniform half weight; + +@class { + + static OptimizationFlags OptFlags(const std::unique_ptr& fp0, + const std::unique_ptr& fp1) { + auto flags = ProcessorOptimizationFlags(fp0.get()); + if (fp1) { + flags &= ProcessorOptimizationFlags(fp1.get()); + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + const auto c0 = ConstantOutputForConstantInput(this->childProcessor(0), input), + c1 = (this->numChildProcessors() > 1) + ? ConstantOutputForConstantInput(this->childProcessor(1), input) + : input; + return { + c0.fR + (c1.fR - c0.fR) * weight, + c0.fG + (c1.fG - c0.fG) * weight, + c0.fB + (c1.fB - c0.fB) * weight, + c0.fA + (c1.fA - c0.fA) * weight + }; + } +} + +@optimizationFlags { OptFlags(fp0, fp1) } + +void main() { + half4 in0 = sample(fp0, sk_InColor); + half4 in1 = (fp1 != null) ? sample(fp1, sk_InColor) : sk_InColor; + + sk_OutColor = mix(in0, in1, weight); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrOvalEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrOvalEffect.cpp new file mode 100644 index 0000000000..b7c001cd54 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrOvalEffect.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrOvalEffect.h" + +#include "include/core/SkRect.h" +#include "src/gpu/effects/generated/GrCircleEffect.h" +#include "src/gpu/effects/generated/GrEllipseEffect.h" + +std::unique_ptr GrOvalEffect::Make(GrClipEdgeType edgeType, const SkRect& oval, + const GrShaderCaps& caps) { + if (GrClipEdgeType::kHairlineAA == edgeType) { + return nullptr; + } + SkScalar w = oval.width(); + SkScalar h = oval.height(); + if (SkScalarNearlyEqual(w, h)) { + w /= 2; + return GrCircleEffect::Make(edgeType, SkPoint::Make(oval.fLeft + w, oval.fTop + w), + w); + } else { + w /= 2; + h /= 2; + return GrEllipseEffect::Make(edgeType, SkPoint::Make(oval.fLeft + w, oval.fTop + h), + SkPoint::Make(w, h), caps); + } + + return nullptr; +} diff --git a/gfx/skia/skia/src/gpu/effects/GrOvalEffect.h b/gfx/skia/skia/src/gpu/effects/GrOvalEffect.h new file mode 100644 index 0000000000..8b4c95d71b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrOvalEffect.h @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOvalEffect_DEFINED +#define GrOvalEffect_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrTypesPriv.h" + +class GrFragmentProcessor; +class GrShaderCaps; +struct SkRect; + +namespace GrOvalEffect { + +/** + * Creates an effect that performs clipping against an oval. + */ +std::unique_ptr Make(GrClipEdgeType, const SkRect&, const GrShaderCaps&); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrOverrideInputFragmentProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrOverrideInputFragmentProcessor.fp new file mode 100644 index 0000000000..786178879d --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrOverrideInputFragmentProcessor.fp @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Ignores its own input color and invokes 'fp' with a constant color +// The constant color can either be specified as a literal or as a +// uniform, controlled by useUniform. + +in fragmentProcessor fp; +layout(key) in bool useUniform; +layout(when=useUniform, ctype=SkPMColor4f) in uniform half4 uniformColor; +layout(when=!useUniform, key, ctype=SkPMColor4f) in half4 literalColor; + +@make { + static std::unique_ptr Make(std::unique_ptr fp, + const SkPMColor4f& color, + bool useUniform = true) { + return std::unique_ptr( + new GrOverrideInputFragmentProcessor(std::move(fp), useUniform, color, color)); + } +} + +@class { + static OptimizationFlags OptFlags(const std::unique_ptr& fp, + const SkPMColor4f& color) { + auto childFlags = ProcessorOptimizationFlags(fp.get()); + auto flags = kNone_OptimizationFlags; + if (childFlags & kConstantOutputForConstantInput_OptimizationFlag) { + flags |= kConstantOutputForConstantInput_OptimizationFlag; + } + if ((childFlags & kPreservesOpaqueInput_OptimizationFlag) && color.isOpaque()) { + flags |= kPreservesOpaqueInput_OptimizationFlag; + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return ConstantOutputForConstantInput(this->childProcessor(0), uniformColor); + } +} + +@optimizationFlags { OptFlags(fp, useUniform ? uniformColor : literalColor) } + +void main() { + half4 constColor; + @if(useUniform) { + constColor = uniformColor; + } else { + constColor = literalColor; + } + sk_OutColor = sample(fp, constColor); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.cpp b/gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.cpp new file mode 100644 index 0000000000..47d92dbde4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.cpp @@ -0,0 +1,945 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrPorterDuffXferProcessor.h" + +#include "include/gpu/GrTypes.h" +#include "include/private/SkTo.h" +#include "src/gpu/GrBlend.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrProcessorAnalysis.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/glsl/GrGLSLBlend.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +/** + * Wraps the shader outputs and HW blend state that comprise a Porter Duff blend mode with coverage. + */ +class BlendFormula { +public: + /** + * Values the shader can write to primary and secondary outputs. These must all be modulated by + * coverage to support mixed samples. The XP will ignore the multiplies when not using coverage. + */ + enum OutputType { + kNone_OutputType, //validatePreoptimized()); + SkDEBUGCODE(other.validatePreoptimized()); + return fData == other.fData; + } + + bool hasSecondaryOutput() const { + SkDEBUGCODE(this->validatePreoptimized()); + return kNone_OutputType != fSecondaryOutputType; + } + bool modifiesDst() const { + SkDEBUGCODE(this->validatePreoptimized()); + return SkToBool(fProps & kModifiesDst_Property); + } + bool usesDstColor() const { + SkDEBUGCODE(this->validatePreoptimized()); + return SkToBool(fProps & kUsesDstColor_Property); + } + bool usesInputColor() const { + SkDEBUGCODE(this->validatePreoptimized()); + return SkToBool(fProps & kUsesInputColor_Property); + } + bool canTweakAlphaForCoverage() const { + SkDEBUGCODE(this->validatePreoptimized()); + return SkToBool(fProps & kCanTweakAlphaForCoverage_Property); + } + + GrBlendEquation equation() const { + SkDEBUGCODE(this->validatePreoptimized()); + return fBlendEquation; + } + + GrBlendCoeff srcCoeff() const { + SkDEBUGCODE(this->validatePreoptimized()); + return fSrcCoeff; + } + + GrBlendCoeff dstCoeff() const { + SkDEBUGCODE(this->validatePreoptimized()); + return fDstCoeff; + } + + OutputType primaryOutput() const { + SkDEBUGCODE(this->validatePreoptimized()); + return fPrimaryOutputType; + } + + OutputType secondaryOutput() const { + SkDEBUGCODE(this->validatePreoptimized()); + return fSecondaryOutputType; + } + +private: + enum Properties { + kModifiesDst_Property = 1, + kUsesDstColor_Property = 1 << 1, + kUsesInputColor_Property = 1 << 2, + kCanTweakAlphaForCoverage_Property = 1 << 3, + + kLast_Property = kCanTweakAlphaForCoverage_Property + }; + GR_DECL_BITFIELD_OPS_FRIENDS(Properties) + +#ifdef SK_DEBUG + void validatePreoptimized() const { + // The provided formula should already be optimized before a BlendFormula is constructed. + // Preferably these asserts would be done statically in the constexpr constructor, but this + // is not allowed in C++11. + SkASSERT((kNone_OutputType == fPrimaryOutputType) == + !GrBlendCoeffsUseSrcColor(fSrcCoeff, fDstCoeff)); + SkASSERT(!GrBlendCoeffRefsSrc2(fSrcCoeff)); + SkASSERT((kNone_OutputType == fSecondaryOutputType) == !GrBlendCoeffRefsSrc2(fDstCoeff)); + SkASSERT(fPrimaryOutputType != fSecondaryOutputType || + kNone_OutputType == fPrimaryOutputType); + SkASSERT(kNone_OutputType != fPrimaryOutputType || + kNone_OutputType == fSecondaryOutputType); + } +#endif + + /** + * Deduce the properties of a BlendFormula. + */ + static constexpr Properties GetProperties(OutputType PrimaryOut, OutputType SecondaryOut, + GrBlendEquation BlendEquation, GrBlendCoeff SrcCoeff, + GrBlendCoeff DstCoeff); + + union { + struct { + // We allot the enums one more bit than they require because MSVC seems to sign-extend + // them when the top bit is set. (This is in violation of the C++03 standard 9.6/4) + OutputType fPrimaryOutputType : 4; + OutputType fSecondaryOutputType : 4; + GrBlendEquation fBlendEquation : 6; + GrBlendCoeff fSrcCoeff : 6; + GrBlendCoeff fDstCoeff : 6; + Properties fProps : 32 - (4 + 4 + 6 + 6 + 6); + }; + uint32_t fData; + }; + + GR_STATIC_ASSERT(kLast_OutputType < (1 << 3)); + GR_STATIC_ASSERT(kLast_GrBlendEquation < (1 << 5)); + GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << 5)); + GR_STATIC_ASSERT(kLast_Property < (1 << 6)); +}; + +GR_STATIC_ASSERT(4 == sizeof(BlendFormula)); + +GR_MAKE_BITFIELD_OPS(BlendFormula::Properties); + +constexpr BlendFormula::Properties BlendFormula::GetProperties(OutputType PrimaryOut, + OutputType SecondaryOut, + GrBlendEquation BlendEquation, + GrBlendCoeff SrcCoeff, + GrBlendCoeff DstCoeff) { + return static_cast( + (GrBlendModifiesDst(BlendEquation, SrcCoeff, DstCoeff) ? kModifiesDst_Property : 0) | + (GrBlendCoeffsUseDstColor(SrcCoeff, DstCoeff) ? kUsesDstColor_Property : 0) | + ((PrimaryOut >= kModulate_OutputType && GrBlendCoeffsUseSrcColor(SrcCoeff, DstCoeff)) || + (SecondaryOut >= kModulate_OutputType && + GrBlendCoeffRefsSrc2(DstCoeff)) + ? kUsesInputColor_Property + : 0) | // We assert later that SrcCoeff doesn't ref src2. + ((kModulate_OutputType == PrimaryOut || kNone_OutputType == PrimaryOut) && + kNone_OutputType == SecondaryOut && + GrBlendAllowsCoverageAsAlpha(BlendEquation, SrcCoeff, DstCoeff) + ? kCanTweakAlphaForCoverage_Property + : 0)); +} + +/** + * When there is no coverage, or the blend mode can tweak alpha for coverage, we use the standard + * Porter Duff formula. + */ +static constexpr BlendFormula MakeCoeffFormula(GrBlendCoeff srcCoeff, GrBlendCoeff dstCoeff) { + // When the coeffs are (Zero, Zero) or (Zero, One) we set the primary output to none. + return (kZero_GrBlendCoeff == srcCoeff && + (kZero_GrBlendCoeff == dstCoeff || kOne_GrBlendCoeff == dstCoeff)) + ? BlendFormula(BlendFormula::kNone_OutputType, BlendFormula::kNone_OutputType, + kAdd_GrBlendEquation, kZero_GrBlendCoeff, dstCoeff) + : BlendFormula(BlendFormula::kModulate_OutputType, BlendFormula::kNone_OutputType, + kAdd_GrBlendEquation, srcCoeff, dstCoeff); +} + +/** + * Basic coeff formula similar to MakeCoeffFormula but we will make the src f*Sa. This is used in + * LCD dst-out. + */ +static constexpr BlendFormula MakeSAModulateFormula(GrBlendCoeff srcCoeff, GrBlendCoeff dstCoeff) { + return BlendFormula(BlendFormula::kSAModulate_OutputType, BlendFormula::kNone_OutputType, + kAdd_GrBlendEquation, srcCoeff, dstCoeff); +} + +/** + * When there is coverage, the equation with f=coverage is: + * + * D' = f * (S * srcCoeff + D * dstCoeff) + (1-f) * D + * + * This can be rewritten as: + * + * D' = f * S * srcCoeff + D * (1 - [f * (1 - dstCoeff)]) + * + * To implement this formula, we output [f * (1 - dstCoeff)] for the secondary color and replace the + * HW dst coeff with IS2C. + * + * Xfer modes: dst-atop (Sa!=1) + */ +static constexpr BlendFormula MakeCoverageFormula( + BlendFormula::OutputType oneMinusDstCoeffModulateOutput, GrBlendCoeff srcCoeff) { + return BlendFormula(BlendFormula::kModulate_OutputType, oneMinusDstCoeffModulateOutput, + kAdd_GrBlendEquation, srcCoeff, kIS2C_GrBlendCoeff); +} + +/** + * When there is coverage and the src coeff is Zero, the equation with f=coverage becomes: + * + * D' = f * D * dstCoeff + (1-f) * D + * + * This can be rewritten as: + * + * D' = D - D * [f * (1 - dstCoeff)] + * + * To implement this formula, we output [f * (1 - dstCoeff)] for the primary color and use a reverse + * subtract HW blend equation with coeffs of (DC, One). + * + * Xfer modes: clear, dst-out (Sa=1), dst-in (Sa!=1), modulate (Sc!=1) + */ +static constexpr BlendFormula MakeCoverageSrcCoeffZeroFormula( + BlendFormula::OutputType oneMinusDstCoeffModulateOutput) { + return BlendFormula(oneMinusDstCoeffModulateOutput, BlendFormula::kNone_OutputType, + kReverseSubtract_GrBlendEquation, kDC_GrBlendCoeff, kOne_GrBlendCoeff); +} + +/** + * When there is coverage and the dst coeff is Zero, the equation with f=coverage becomes: + * + * D' = f * S * srcCoeff + (1-f) * D + * + * To implement this formula, we output [f] for the secondary color and replace the HW dst coeff + * with IS2A. (Note that we can avoid dual source blending when Sa=1 by using ISA.) + * + * Xfer modes (Sa!=1): src, src-in, src-out + */ +static constexpr BlendFormula MakeCoverageDstCoeffZeroFormula(GrBlendCoeff srcCoeff) { + return BlendFormula(BlendFormula::kModulate_OutputType, BlendFormula::kCoverage_OutputType, + kAdd_GrBlendEquation, srcCoeff, kIS2A_GrBlendCoeff); +} + +// Older GCC won't like the constexpr arrays because of +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61484. +// MSVC 2015 crashes with an internal compiler error. +#if !defined(__clang__) && ((defined(__GNUC__) && __GNUC__ < 5) || (defined(_MSC_VER) && _MSC_VER <= 1910)) +# define MAYBE_CONSTEXPR const +#else +# define MAYBE_CONSTEXPR constexpr +#endif + +/** + * This table outlines the blend formulas we will use with each xfermode, with and without coverage, + * with and without an opaque input color. Optimization properties are deduced at compile time so we + * can make runtime decisions quickly. RGB coverage is not supported. + */ +static MAYBE_CONSTEXPR BlendFormula gBlendTable[2][2][(int)SkBlendMode::kLastCoeffMode + 1] = { + /*>> No coverage, input color unknown <<*/ {{ + + /* clear */ MakeCoeffFormula(kZero_GrBlendCoeff, kZero_GrBlendCoeff), + /* src */ MakeCoeffFormula(kOne_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-over */ MakeCoeffFormula(kOne_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-over */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-in */ MakeCoeffFormula(kDA_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst-in */ MakeCoeffFormula(kZero_GrBlendCoeff, kSA_GrBlendCoeff), + /* src-out */ MakeCoeffFormula(kIDA_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst-out */ MakeCoeffFormula(kZero_GrBlendCoeff, kISA_GrBlendCoeff), + /* src-atop */ MakeCoeffFormula(kDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-atop */ MakeCoeffFormula(kIDA_GrBlendCoeff, kSA_GrBlendCoeff), + /* xor */ MakeCoeffFormula(kIDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* plus */ MakeCoeffFormula(kOne_GrBlendCoeff, kOne_GrBlendCoeff), + /* modulate */ MakeCoeffFormula(kZero_GrBlendCoeff, kSC_GrBlendCoeff), + /* screen */ MakeCoeffFormula(kOne_GrBlendCoeff, kISC_GrBlendCoeff), + + }, /*>> Has coverage, input color unknown <<*/ { + + /* clear */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), + /* src */ MakeCoverageDstCoeffZeroFormula(kOne_GrBlendCoeff), + /* dst */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-over */ MakeCoeffFormula(kOne_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-over */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-in */ MakeCoverageDstCoeffZeroFormula(kDA_GrBlendCoeff), + /* dst-in */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISAModulate_OutputType), + /* src-out */ MakeCoverageDstCoeffZeroFormula(kIDA_GrBlendCoeff), + /* dst-out */ MakeCoeffFormula(kZero_GrBlendCoeff, kISA_GrBlendCoeff), + /* src-atop */ MakeCoeffFormula(kDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-atop */ MakeCoverageFormula(BlendFormula::kISAModulate_OutputType, kIDA_GrBlendCoeff), + /* xor */ MakeCoeffFormula(kIDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* plus */ MakeCoeffFormula(kOne_GrBlendCoeff, kOne_GrBlendCoeff), + /* modulate */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISCModulate_OutputType), + /* screen */ MakeCoeffFormula(kOne_GrBlendCoeff, kISC_GrBlendCoeff), + + }}, /*>> No coverage, input color opaque <<*/ {{ + + /* clear */ MakeCoeffFormula(kZero_GrBlendCoeff, kZero_GrBlendCoeff), + /* src */ MakeCoeffFormula(kOne_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-over */ MakeCoeffFormula(kOne_GrBlendCoeff, kISA_GrBlendCoeff), // see comment below + /* dst-over */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-in */ MakeCoeffFormula(kDA_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst-in */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-out */ MakeCoeffFormula(kIDA_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst-out */ MakeCoeffFormula(kZero_GrBlendCoeff, kZero_GrBlendCoeff), + /* src-atop */ MakeCoeffFormula(kDA_GrBlendCoeff, kZero_GrBlendCoeff), + /* dst-atop */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* xor */ MakeCoeffFormula(kIDA_GrBlendCoeff, kZero_GrBlendCoeff), + /* plus */ MakeCoeffFormula(kOne_GrBlendCoeff, kOne_GrBlendCoeff), + /* modulate */ MakeCoeffFormula(kZero_GrBlendCoeff, kSC_GrBlendCoeff), + /* screen */ MakeCoeffFormula(kOne_GrBlendCoeff, kISC_GrBlendCoeff), + + }, /*>> Has coverage, input color opaque <<*/ { + + /* clear */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), + /* src */ MakeCoeffFormula(kOne_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-over */ MakeCoeffFormula(kOne_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-over */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-in */ MakeCoeffFormula(kDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-in */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-out */ MakeCoeffFormula(kIDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-out */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), + /* src-atop */ MakeCoeffFormula(kDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* dst-atop */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* xor */ MakeCoeffFormula(kIDA_GrBlendCoeff, kISA_GrBlendCoeff), + /* plus */ MakeCoeffFormula(kOne_GrBlendCoeff, kOne_GrBlendCoeff), + /* modulate */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISCModulate_OutputType), + /* screen */ MakeCoeffFormula(kOne_GrBlendCoeff, kISC_GrBlendCoeff), +}}}; +// In the above table src-over is not optimized to src mode when the color is opaque because we +// found no advantage to doing so. Also, we are using a global src-over XP in most cases which is +// not specialized for opaque input. If the table were set to use the src formula then we'd have to +// change when we use this global XP to keep analysis and practice in sync. + +static MAYBE_CONSTEXPR BlendFormula gLCDBlendTable[(int)SkBlendMode::kLastCoeffMode + 1] = { + /* clear */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), + /* src */ MakeCoverageFormula(BlendFormula::kCoverage_OutputType, kOne_GrBlendCoeff), + /* dst */ MakeCoeffFormula(kZero_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-over */ MakeCoverageFormula(BlendFormula::kSAModulate_OutputType, kOne_GrBlendCoeff), + /* dst-over */ MakeCoeffFormula(kIDA_GrBlendCoeff, kOne_GrBlendCoeff), + /* src-in */ MakeCoverageFormula(BlendFormula::kCoverage_OutputType, kDA_GrBlendCoeff), + /* dst-in */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISAModulate_OutputType), + /* src-out */ MakeCoverageFormula(BlendFormula::kCoverage_OutputType, kIDA_GrBlendCoeff), + /* dst-out */ MakeSAModulateFormula(kZero_GrBlendCoeff, kISC_GrBlendCoeff), + /* src-atop */ MakeCoverageFormula(BlendFormula::kSAModulate_OutputType, kDA_GrBlendCoeff), + /* dst-atop */ MakeCoverageFormula(BlendFormula::kISAModulate_OutputType, kIDA_GrBlendCoeff), + /* xor */ MakeCoverageFormula(BlendFormula::kSAModulate_OutputType, kIDA_GrBlendCoeff), + /* plus */ MakeCoeffFormula(kOne_GrBlendCoeff, kOne_GrBlendCoeff), + /* modulate */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISCModulate_OutputType), + /* screen */ MakeCoeffFormula(kOne_GrBlendCoeff, kISC_GrBlendCoeff), +}; + +#undef MAYBE_CONSTEXPR + +static BlendFormula get_blend_formula(bool isOpaque, + bool hasCoverage, + bool hasMixedSamples, + SkBlendMode xfermode) { + SkASSERT((unsigned)xfermode <= (unsigned)SkBlendMode::kLastCoeffMode); + bool conflatesCoverage = hasCoverage || hasMixedSamples; + return gBlendTable[isOpaque][conflatesCoverage][(int)xfermode]; +} + +static BlendFormula get_lcd_blend_formula(SkBlendMode xfermode) { + SkASSERT((unsigned)xfermode <= (unsigned)SkBlendMode::kLastCoeffMode); + + return gLCDBlendTable[(int)xfermode]; +} + +/////////////////////////////////////////////////////////////////////////////// + +class PorterDuffXferProcessor : public GrXferProcessor { +public: + PorterDuffXferProcessor(BlendFormula blendFormula, GrProcessorAnalysisCoverage coverage) + : INHERITED(kPorterDuffXferProcessor_ClassID, false, false, coverage) + , fBlendFormula(blendFormula) { + } + + const char* name() const override { return "Porter Duff"; } + + GrGLSLXferProcessor* createGLSLInstance() const override; + + BlendFormula getBlendFormula() const { return fBlendFormula; } + +private: + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + bool onHasSecondaryOutput() const override { return fBlendFormula.hasSecondaryOutput(); } + + void onGetBlendInfo(GrXferProcessor::BlendInfo* blendInfo) const override { + blendInfo->fEquation = fBlendFormula.equation(); + blendInfo->fSrcBlend = fBlendFormula.srcCoeff(); + blendInfo->fDstBlend = fBlendFormula.dstCoeff(); + blendInfo->fWriteColor = fBlendFormula.modifiesDst(); + } + + bool onIsEqual(const GrXferProcessor& xpBase) const override { + const PorterDuffXferProcessor& xp = xpBase.cast(); + return fBlendFormula == xp.fBlendFormula; + } + + const BlendFormula fBlendFormula; + + typedef GrXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +static void append_color_output(const PorterDuffXferProcessor& xp, + GrGLSLXPFragmentBuilder* fragBuilder, + BlendFormula::OutputType outputType, const char* output, + const char* inColor, const char* inCoverage) { + SkASSERT(inCoverage); + SkASSERT(inColor); + switch (outputType) { + case BlendFormula::kNone_OutputType: + fragBuilder->codeAppendf("%s = half4(0.0);", output); + break; + case BlendFormula::kCoverage_OutputType: + // We can have a coverage formula while not reading coverage if there are mixed samples. + fragBuilder->codeAppendf("%s = %s;", output, inCoverage); + break; + case BlendFormula::kModulate_OutputType: + fragBuilder->codeAppendf("%s = %s * %s;", output, inColor, inCoverage); + break; + case BlendFormula::kSAModulate_OutputType: + fragBuilder->codeAppendf("%s = %s.a * %s;", output, inColor, inCoverage); + break; + case BlendFormula::kISAModulate_OutputType: + fragBuilder->codeAppendf("%s = (1.0 - %s.a) * %s;", output, inColor, inCoverage); + break; + case BlendFormula::kISCModulate_OutputType: + fragBuilder->codeAppendf("%s = (half4(1.0) - %s) * %s;", output, inColor, inCoverage); + break; + default: + SK_ABORT("Unsupported output type."); + break; + } +} + +class GLPorterDuffXferProcessor : public GrGLSLXferProcessor { +public: + static void GenKey(const GrProcessor& processor, GrProcessorKeyBuilder* b) { + const PorterDuffXferProcessor& xp = processor.cast(); + b->add32(xp.getBlendFormula().primaryOutput() | + (xp.getBlendFormula().secondaryOutput() << 3)); + GR_STATIC_ASSERT(BlendFormula::kLast_OutputType < 8); + } + +private: + void emitOutputsForBlendState(const EmitArgs& args) override { + const PorterDuffXferProcessor& xp = args.fXP.cast(); + GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder; + + BlendFormula blendFormula = xp.getBlendFormula(); + if (blendFormula.hasSecondaryOutput()) { + append_color_output(xp, fragBuilder, blendFormula.secondaryOutput(), + args.fOutputSecondary, args.fInputColor, args.fInputCoverage); + } + append_color_output(xp, fragBuilder, blendFormula.primaryOutput(), args.fOutputPrimary, + args.fInputColor, args.fInputCoverage); + } + + void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) override {} + + typedef GrGLSLXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +void PorterDuffXferProcessor::onGetGLSLProcessorKey(const GrShaderCaps&, + GrProcessorKeyBuilder* b) const { + GLPorterDuffXferProcessor::GenKey(*this, b); +} + +GrGLSLXferProcessor* PorterDuffXferProcessor::createGLSLInstance() const { + return new GLPorterDuffXferProcessor; +} + +/////////////////////////////////////////////////////////////////////////////// + +class ShaderPDXferProcessor : public GrXferProcessor { +public: + ShaderPDXferProcessor(bool hasMixedSamples, SkBlendMode xfermode, + GrProcessorAnalysisCoverage coverage) + : INHERITED(kShaderPDXferProcessor_ClassID, true, hasMixedSamples, coverage) + , fXfermode(xfermode) { + } + + const char* name() const override { return "Porter Duff Shader"; } + + GrGLSLXferProcessor* createGLSLInstance() const override; + + SkBlendMode getXfermode() const { return fXfermode; } + +private: + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + bool onIsEqual(const GrXferProcessor& xpBase) const override { + const ShaderPDXferProcessor& xp = xpBase.cast(); + return fXfermode == xp.fXfermode; + } + + const SkBlendMode fXfermode; + + typedef GrXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GLShaderPDXferProcessor : public GrGLSLXferProcessor { +public: + static void GenKey(const GrProcessor& processor, GrProcessorKeyBuilder* b) { + const ShaderPDXferProcessor& xp = processor.cast(); + b->add32((int)xp.getXfermode()); + } + +private: + void emitBlendCodeForDstRead(GrGLSLXPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const char* srcColor, + const char* srcCoverage, + const char* dstColor, + const char* outColor, + const char* outColorSecondary, + const GrXferProcessor& proc) override { + const ShaderPDXferProcessor& xp = proc.cast(); + + GrGLSLBlend::AppendMode(fragBuilder, srcColor, dstColor, outColor, xp.getXfermode()); + + // Apply coverage. + INHERITED::DefaultCoverageModulation(fragBuilder, srcCoverage, dstColor, outColor, + outColorSecondary, xp); + } + + void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) override {} + + typedef GrGLSLXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +void ShaderPDXferProcessor::onGetGLSLProcessorKey(const GrShaderCaps&, + GrProcessorKeyBuilder* b) const { + GLShaderPDXferProcessor::GenKey(*this, b); +} + +GrGLSLXferProcessor* ShaderPDXferProcessor::createGLSLInstance() const { + return new GLShaderPDXferProcessor; +} + +/////////////////////////////////////////////////////////////////////////////// + +class PDLCDXferProcessor : public GrXferProcessor { +public: + static sk_sp Make(SkBlendMode mode, + const GrProcessorAnalysisColor& inputColor); + + ~PDLCDXferProcessor() override; + + const char* name() const override { return "Porter Duff LCD"; } + + GrGLSLXferProcessor* createGLSLInstance() const override; + + float alpha() const { return fAlpha; } + +private: + PDLCDXferProcessor(const SkPMColor4f& blendConstant, float alpha); + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + void onGetBlendInfo(GrXferProcessor::BlendInfo* blendInfo) const override { + blendInfo->fSrcBlend = kConstC_GrBlendCoeff; + blendInfo->fDstBlend = kISC_GrBlendCoeff; + blendInfo->fBlendConstant = fBlendConstant; + } + + bool onIsEqual(const GrXferProcessor& xpBase) const override { + const PDLCDXferProcessor& xp = xpBase.cast(); + if (fBlendConstant != xp.fBlendConstant || fAlpha != xp.fAlpha) { + return false; + } + return true; + } + + SkPMColor4f fBlendConstant; + float fAlpha; + + typedef GrXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class GLPDLCDXferProcessor : public GrGLSLXferProcessor { +public: + GLPDLCDXferProcessor(const GrProcessor&) : fLastAlpha(SK_FloatNaN) {} + + ~GLPDLCDXferProcessor() override {} + + static void GenKey(const GrProcessor& processor, const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) {} + +private: + void emitOutputsForBlendState(const EmitArgs& args) override { + const char* alpha; + fAlphaUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "alpha", &alpha); + GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder; + // We want to force our primary output to be alpha * Coverage, where alpha is the alpha + // value of the src color. We know that there are no color stages (or we wouldn't have + // created this xp) and the r,g, and b channels of the op's input color are baked into the + // blend constant. + SkASSERT(args.fInputCoverage); + fragBuilder->codeAppendf("%s = %s * %s;", args.fOutputPrimary, alpha, args.fInputCoverage); + } + + void onSetData(const GrGLSLProgramDataManager& pdm, const GrXferProcessor& xp) override { + float alpha = xp.cast().alpha(); + if (fLastAlpha != alpha) { + pdm.set1f(fAlphaUniform, alpha); + fLastAlpha = alpha; + } + } + + GrGLSLUniformHandler::UniformHandle fAlphaUniform; + float fLastAlpha; + typedef GrGLSLXferProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +PDLCDXferProcessor::PDLCDXferProcessor(const SkPMColor4f& blendConstant, float alpha) + : INHERITED(kPDLCDXferProcessor_ClassID, false, false, GrProcessorAnalysisCoverage::kLCD) + , fBlendConstant(blendConstant) + , fAlpha(alpha) { +} + +sk_sp PDLCDXferProcessor::Make(SkBlendMode mode, + const GrProcessorAnalysisColor& color) { + if (SkBlendMode::kSrcOver != mode) { + return nullptr; + } + SkPMColor4f blendConstantPM; + if (!color.isConstant(&blendConstantPM)) { + return nullptr; + } + SkColor4f blendConstantUPM = blendConstantPM.unpremul(); + float alpha = blendConstantUPM.fA; + blendConstantPM = { blendConstantUPM.fR, blendConstantUPM.fG, blendConstantUPM.fB, 1 }; + return sk_sp(new PDLCDXferProcessor(blendConstantPM, alpha)); +} + +PDLCDXferProcessor::~PDLCDXferProcessor() { +} + +void PDLCDXferProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLPDLCDXferProcessor::GenKey(*this, caps, b); +} + +GrGLSLXferProcessor* PDLCDXferProcessor::createGLSLInstance() const { + return new GLPDLCDXferProcessor(*this); +} + +/////////////////////////////////////////////////////////////////////////////// + +constexpr GrPorterDuffXPFactory::GrPorterDuffXPFactory(SkBlendMode xfermode) + : fBlendMode(xfermode) {} + +const GrXPFactory* GrPorterDuffXPFactory::Get(SkBlendMode blendMode) { + SkASSERT((unsigned)blendMode <= (unsigned)SkBlendMode::kLastCoeffMode); + + // If these objects are constructed as static constexpr by cl.exe (2015 SP2) the vtables are + // null. +#ifdef SK_BUILD_FOR_WIN +#define _CONSTEXPR_ +#else +#define _CONSTEXPR_ constexpr +#endif + static _CONSTEXPR_ const GrPorterDuffXPFactory gClearPDXPF(SkBlendMode::kClear); + static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcPDXPF(SkBlendMode::kSrc); + static _CONSTEXPR_ const GrPorterDuffXPFactory gDstPDXPF(SkBlendMode::kDst); + static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcOverPDXPF(SkBlendMode::kSrcOver); + static _CONSTEXPR_ const GrPorterDuffXPFactory gDstOverPDXPF(SkBlendMode::kDstOver); + static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcInPDXPF(SkBlendMode::kSrcIn); + static _CONSTEXPR_ const GrPorterDuffXPFactory gDstInPDXPF(SkBlendMode::kDstIn); + static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcOutPDXPF(SkBlendMode::kSrcOut); + static _CONSTEXPR_ const GrPorterDuffXPFactory gDstOutPDXPF(SkBlendMode::kDstOut); + static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcATopPDXPF(SkBlendMode::kSrcATop); + static _CONSTEXPR_ const GrPorterDuffXPFactory gDstATopPDXPF(SkBlendMode::kDstATop); + static _CONSTEXPR_ const GrPorterDuffXPFactory gXorPDXPF(SkBlendMode::kXor); + static _CONSTEXPR_ const GrPorterDuffXPFactory gPlusPDXPF(SkBlendMode::kPlus); + static _CONSTEXPR_ const GrPorterDuffXPFactory gModulatePDXPF(SkBlendMode::kModulate); + static _CONSTEXPR_ const GrPorterDuffXPFactory gScreenPDXPF(SkBlendMode::kScreen); +#undef _CONSTEXPR_ + + switch (blendMode) { + case SkBlendMode::kClear: + return &gClearPDXPF; + case SkBlendMode::kSrc: + return &gSrcPDXPF; + case SkBlendMode::kDst: + return &gDstPDXPF; + case SkBlendMode::kSrcOver: + return &gSrcOverPDXPF; + case SkBlendMode::kDstOver: + return &gDstOverPDXPF; + case SkBlendMode::kSrcIn: + return &gSrcInPDXPF; + case SkBlendMode::kDstIn: + return &gDstInPDXPF; + case SkBlendMode::kSrcOut: + return &gSrcOutPDXPF; + case SkBlendMode::kDstOut: + return &gDstOutPDXPF; + case SkBlendMode::kSrcATop: + return &gSrcATopPDXPF; + case SkBlendMode::kDstATop: + return &gDstATopPDXPF; + case SkBlendMode::kXor: + return &gXorPDXPF; + case SkBlendMode::kPlus: + return &gPlusPDXPF; + case SkBlendMode::kModulate: + return &gModulatePDXPF; + case SkBlendMode::kScreen: + return &gScreenPDXPF; + default: + SK_ABORT("Unexpected blend mode."); + } +} + +sk_sp GrPorterDuffXPFactory::makeXferProcessor( + const GrProcessorAnalysisColor& color, GrProcessorAnalysisCoverage coverage, + bool hasMixedSamples, const GrCaps& caps, GrClampType clampType) const { + BlendFormula blendFormula; + bool isLCD = coverage == GrProcessorAnalysisCoverage::kLCD; + if (isLCD) { + // See comment in MakeSrcOverXferProcessor about color.isOpaque here + if (SkBlendMode::kSrcOver == fBlendMode && color.isConstant() && /*color.isOpaque() &&*/ + !caps.shaderCaps()->dualSourceBlendingSupport() && + !caps.shaderCaps()->dstReadInShaderSupport()) { + // If we don't have dual source blending or in shader dst reads, we fall back to this + // trick for rendering SrcOver LCD text instead of doing a dst copy. + return PDLCDXferProcessor::Make(fBlendMode, color); + } + blendFormula = get_lcd_blend_formula(fBlendMode); + } else { + blendFormula = + get_blend_formula(color.isOpaque(), GrProcessorAnalysisCoverage::kNone != coverage, + hasMixedSamples, fBlendMode); + } + + // Skia always saturates after the kPlus blend mode, so it requires shader-based blending when + // pixels aren't guaranteed to automatically be normalized (i.e. any floating point config). + if ((blendFormula.hasSecondaryOutput() && !caps.shaderCaps()->dualSourceBlendingSupport()) || + (isLCD && (SkBlendMode::kSrcOver != fBlendMode /*|| !color.isOpaque()*/)) || + (GrClampType::kAuto != clampType && SkBlendMode::kPlus == fBlendMode)) { + return sk_sp(new ShaderPDXferProcessor(hasMixedSamples, fBlendMode, + coverage)); + } + return sk_sp(new PorterDuffXferProcessor(blendFormula, coverage)); +} + +static inline GrXPFactory::AnalysisProperties analysis_properties( + const GrProcessorAnalysisColor& color, const GrProcessorAnalysisCoverage& coverage, + const GrCaps& caps, GrClampType clampType, SkBlendMode mode) { + using AnalysisProperties = GrXPFactory::AnalysisProperties; + AnalysisProperties props = AnalysisProperties::kNone; + bool hasCoverage = GrProcessorAnalysisCoverage::kNone != coverage; + bool isLCD = GrProcessorAnalysisCoverage::kLCD == coverage; + BlendFormula formula; + if (isLCD) { + formula = gLCDBlendTable[(int)mode]; + } else { + formula = gBlendTable[color.isOpaque()][hasCoverage][(int)mode]; + } + + if (formula.canTweakAlphaForCoverage() && !isLCD) { + props |= AnalysisProperties::kCompatibleWithCoverageAsAlpha; + } + + if (isLCD) { + // See comment in MakeSrcOverXferProcessor about color.isOpaque here + if (SkBlendMode::kSrcOver == mode && color.isConstant() && /*color.isOpaque() &&*/ + !caps.shaderCaps()->dualSourceBlendingSupport() && + !caps.shaderCaps()->dstReadInShaderSupport()) { + props |= AnalysisProperties::kIgnoresInputColor; + } else { + // For LCD blending, if the color is not opaque we must read the dst in shader even if + // we have dual source blending. The opaqueness check must be done after blending so for + // simplicity we only allow src-over to not take the dst read path (though src, src-in, + // and DstATop would also work). We also fall into the dst read case for src-over if we + // do not have dual source blending. + if (SkBlendMode::kSrcOver != mode || + /*!color.isOpaque() ||*/ // See comment in MakeSrcOverXferProcessor about isOpaque. + (formula.hasSecondaryOutput() && !caps.shaderCaps()->dualSourceBlendingSupport())) { + props |= AnalysisProperties::kReadsDstInShader; + } + } + } else { + // With dual-source blending we never need the destination color in the shader. + if (!caps.shaderCaps()->dualSourceBlendingSupport()) { + // Mixed samples implicity computes a fractional coverage from sample coverage. This + // could affect the formula used. However, we don't expect to have mixed samples without + // dual source blending. + SkASSERT(!caps.mixedSamplesSupport()); + if (formula.hasSecondaryOutput()) { + props |= AnalysisProperties::kReadsDstInShader; + } + } + } + + if (GrClampType::kAuto != clampType && SkBlendMode::kPlus == mode) { + props |= AnalysisProperties::kReadsDstInShader; + } + + if (!formula.modifiesDst() || !formula.usesInputColor()) { + props |= AnalysisProperties::kIgnoresInputColor; + } + return props; +} + +GrXPFactory::AnalysisProperties GrPorterDuffXPFactory::analysisProperties( + const GrProcessorAnalysisColor& color, + const GrProcessorAnalysisCoverage& coverage, + const GrCaps& caps, + GrClampType clampType) const { + return analysis_properties(color, coverage, caps, clampType, fBlendMode); +} + +GR_DEFINE_XP_FACTORY_TEST(GrPorterDuffXPFactory); + +#if GR_TEST_UTILS +const GrXPFactory* GrPorterDuffXPFactory::TestGet(GrProcessorTestData* d) { + SkBlendMode mode = SkBlendMode(d->fRandom->nextULessThan((int)SkBlendMode::kLastCoeffMode)); + return GrPorterDuffXPFactory::Get(mode); +} +#endif + +void GrPorterDuffXPFactory::TestGetXPOutputTypes(const GrXferProcessor* xp, + int* outPrimary, + int* outSecondary) { + if (!!strcmp(xp->name(), "Porter Duff")) { + *outPrimary = *outSecondary = -1; + return; + } + BlendFormula blendFormula = static_cast(xp)->getBlendFormula(); + *outPrimary = blendFormula.primaryOutput(); + *outSecondary = blendFormula.secondaryOutput(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// SrcOver Global functions +//////////////////////////////////////////////////////////////////////////////////////////////// +const GrXferProcessor& GrPorterDuffXPFactory::SimpleSrcOverXP() { + static BlendFormula gSrcOverBlendFormula = + MakeCoeffFormula(kOne_GrBlendCoeff, kISA_GrBlendCoeff); + static PorterDuffXferProcessor gSrcOverXP(gSrcOverBlendFormula, + GrProcessorAnalysisCoverage::kSingleChannel); + return gSrcOverXP; +} + +sk_sp GrPorterDuffXPFactory::MakeSrcOverXferProcessor( + const GrProcessorAnalysisColor& color, GrProcessorAnalysisCoverage coverage, + bool hasMixedSamples, const GrCaps& caps) { + // We want to not make an xfer processor if possible. Thus for the simple case where we are not + // doing lcd blending we will just use our global SimpleSrcOverXP. This slightly differs from + // the general case where we convert a src-over blend that has solid coverage and an opaque + // color to src-mode, which allows disabling of blending. + if (coverage != GrProcessorAnalysisCoverage::kLCD) { + // We return nullptr here, which our caller interprets as meaning "use SimpleSrcOverXP". + // We don't simply return the address of that XP here because our caller would have to unref + // it and since it is a global object and GrProgramElement's ref-cnting system is not thread + // safe. + return nullptr; + } + + // Currently up the stack Skia is requiring that the dst is opaque or that the client has said + // the opaqueness doesn't matter. Thus for src-over we don't need to worry about the src color + // being opaque or not. This allows us to use faster code paths as well as avoid various bugs + // that occur with dst reads in the shader blending. For now we disable the check for + // opaqueness, but in the future we should pass down the knowledge about dst opaqueness and make + // the correct decision here. + // + // This also fixes a chrome bug on macs where we are getting random fuzziness when doing + // blending in the shader for non opaque sources. + if (color.isConstant() && /*color.isOpaque() &&*/ + !caps.shaderCaps()->dualSourceBlendingSupport() && + !caps.shaderCaps()->dstReadInShaderSupport()) { + // If we don't have dual source blending or in shader dst reads, we fall + // back to this trick for rendering SrcOver LCD text instead of doing a + // dst copy. + return PDLCDXferProcessor::Make(SkBlendMode::kSrcOver, color); + } + + BlendFormula blendFormula; + blendFormula = get_lcd_blend_formula(SkBlendMode::kSrcOver); + // See comment above regarding why the opaque check is commented out here. + if (/*!color.isOpaque() ||*/ + (blendFormula.hasSecondaryOutput() && !caps.shaderCaps()->dualSourceBlendingSupport())) { + return sk_sp( + new ShaderPDXferProcessor(hasMixedSamples, SkBlendMode::kSrcOver, coverage)); + } + return sk_sp(new PorterDuffXferProcessor(blendFormula, coverage)); +} + +sk_sp GrPorterDuffXPFactory::MakeNoCoverageXP(SkBlendMode blendmode) { + BlendFormula formula = get_blend_formula(false, false, false, blendmode); + return sk_make_sp(formula, GrProcessorAnalysisCoverage::kNone); +} + +GrXPFactory::AnalysisProperties GrPorterDuffXPFactory::SrcOverAnalysisProperties( + const GrProcessorAnalysisColor& color, + const GrProcessorAnalysisCoverage& coverage, + const GrCaps& caps, + GrClampType clampType) { + return analysis_properties(color, coverage, caps, clampType, SkBlendMode::kSrcOver); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.h b/gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.h new file mode 100644 index 0000000000..22453131b5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrPorterDuffXferProcessor.h @@ -0,0 +1,76 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPorterDuffXferProcessor_DEFINED +#define GrPorterDuffXferProcessor_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/gpu/GrTypes.h" +#include "src/gpu/GrXferProcessor.h" + +// See the comment above GrXPFactory's definition about this warning suppression. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif +class GrPorterDuffXPFactory : public GrXPFactory { +public: + static const GrXPFactory* Get(SkBlendMode blendMode); + + /** Because src-over is so common we special case it for performance reasons. If this returns + null then the SimpleSrcOverXP() below should be used. */ + static sk_sp MakeSrcOverXferProcessor(const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps&); + + /** Returns a simple non-LCD porter duff blend XP with no optimizations or coverage. */ + static sk_sp MakeNoCoverageXP(SkBlendMode); + + /** This XP implements non-LCD src-over using hw blend with no optimizations. It is returned + by reference because it is global and its ref-cnting methods are not thread safe. */ + static const GrXferProcessor& SimpleSrcOverXP(); + + static AnalysisProperties SrcOverAnalysisProperties(const GrProcessorAnalysisColor&, + const GrProcessorAnalysisCoverage&, + const GrCaps&, + GrClampType); + +private: + constexpr GrPorterDuffXPFactory(SkBlendMode); + + sk_sp makeXferProcessor(const GrProcessorAnalysisColor&, + GrProcessorAnalysisCoverage, + bool hasMixedSamples, + const GrCaps&, + GrClampType) const override; + + AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&, + const GrProcessorAnalysisCoverage&, + const GrCaps&, + GrClampType) const override; + + GR_DECLARE_XP_FACTORY_TEST + static void TestGetXPOutputTypes(const GrXferProcessor*, int* outPrimary, int* outSecondary); + + SkBlendMode fBlendMode; + + friend class GrPorterDuffTest; // for TestGetXPOutputTypes() + typedef GrXPFactory INHERITED; +}; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrPremulInputFragmentProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrPremulInputFragmentProcessor.fp new file mode 100644 index 0000000000..cbba607074 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrPremulInputFragmentProcessor.fp @@ -0,0 +1,21 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +@optimizationFlags { + kPreservesOpaqueInput_OptimizationFlag | kConstantOutputForConstantInput_OptimizationFlag +} + +void main() { + sk_OutColor = sk_InColor; + sk_OutColor.rgb *= sk_InColor.a; +} + +@class { + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return SkColor4f { input.fR, input.fG, input.fB, input.fA }.premul(); + } +} diff --git a/gfx/skia/skia/src/gpu/effects/GrRRectBlurEffect.fp b/gfx/skia/skia/src/gpu/effects/GrRRectBlurEffect.fp new file mode 100644 index 0000000000..b0b770e3c2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrRRectBlurEffect.fp @@ -0,0 +1,207 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in float sigma; +layout(ctype=SkRect) in float4 rect; +in uniform half cornerRadius; +in uniform sampler2D ninePatchSampler; +layout(ctype=SkRect) uniform float4 proxyRect; +uniform half blurRadius; + +@header { + #include "include/effects/SkBlurMaskFilter.h" + #include "include/gpu/GrContext.h" + #include "include/private/GrRecordingContext.h" + #include "src/core/SkBlurPriv.h" + #include "src/core/SkGpuBlurUtils.h" + #include "src/core/SkRRectPriv.h" + #include "src/gpu/GrCaps.h" + #include "src/gpu/GrClip.h" + #include "src/gpu/GrPaint.h" + #include "src/gpu/GrProxyProvider.h" + #include "src/gpu/GrRecordingContextPriv.h" + #include "src/gpu/GrRenderTargetContext.h" + #include "src/gpu/GrStyle.h" +} + +@class { + static sk_sp find_or_create_rrect_blur_mask(GrRecordingContext* context, + const SkRRect& rrectToDraw, + const SkISize& size, + float xformedSigma) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 9, "RoundRect Blur Mask"); + builder[0] = SkScalarCeilToInt(xformedSigma-1/6.0f); + + int index = 1; + for (auto c : { SkRRect::kUpperLeft_Corner, SkRRect::kUpperRight_Corner, + SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner }) { + SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) && + SkScalarIsInt(rrectToDraw.radii(c).fY)); + builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX); + builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY); + } + builder.finish(); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + sk_sp mask(proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kBottomLeft_GrSurfaceOrigin)); + if (!mask) { + // TODO: this could be SkBackingFit::kApprox, but: + // 1) The texture coords would need to be updated. + // 2) We would have to use GrTextureDomain::kClamp_Mode for the GaussianBlur. + auto rtc = context->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, size.fWidth, size.fHeight, GrColorType::kAlpha_8, + nullptr); + if (!rtc) { + return nullptr; + } + + GrPaint paint; + + rtc->clear(nullptr, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); + rtc->drawRRect(GrNoClip(), std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw, + GrStyle::SimpleFill()); + + sk_sp srcProxy(rtc->asTextureProxyRef()); + if (!srcProxy) { + return nullptr; + } + auto rtc2 = + SkGpuBlurUtils::GaussianBlur(context, + std::move(srcProxy), + rtc->colorInfo().colorType(), + rtc->colorInfo().alphaType(), + SkIPoint::Make(0, 0), + nullptr, + SkIRect::MakeWH(size.fWidth, size.fHeight), + SkIRect::EmptyIRect(), + xformedSigma, + xformedSigma, + GrTextureDomain::kIgnore_Mode, + SkBackingFit::kExact); + if (!rtc2) { + return nullptr; + } + + mask = rtc2->asTextureProxyRef(); + if (!mask) { + return nullptr; + } + SkASSERT(mask->origin() == kBottomLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, mask.get()); + } + + return mask; + } +} + +@optimizationFlags { + kCompatibleWithCoverageAsAlpha_OptimizationFlag +} + +@make { + static std::unique_ptr Make(GrRecordingContext* context, + float sigma, + float xformedSigma, + const SkRRect& srcRRect, + const SkRRect& devRRect); +} + +@cpp { + std::unique_ptr GrRRectBlurEffect::Make(GrRecordingContext* context, + float sigma, + float xformedSigma, + const SkRRect& srcRRect, + const SkRRect& devRRect) { + SkASSERT(!SkRRectPriv::IsCircle(devRRect) && !devRRect.isRect()); // Should've been caught up-stream + + // TODO: loosen this up + if (!SkRRectPriv::IsSimpleCircular(devRRect)) { + return nullptr; + } + + // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be + // sufficiently small relative to both the size of the corner radius and the + // width (and height) of the rrect. + SkRRect rrectToDraw; + SkISize size; + SkScalar ignored[kSkBlurRRectMaxDivisions]; + int ignoredSize; + uint32_t ignored32; + + bool ninePatchable = SkComputeBlurredRRectParams(srcRRect, devRRect, + SkRect::MakeEmpty(), + sigma, xformedSigma, + &rrectToDraw, &size, + ignored, ignored, + ignored, ignored, + &ignoredSize, &ignoredSize, + &ignored32); + if (!ninePatchable) { + return nullptr; + } + + sk_sp mask(find_or_create_rrect_blur_mask(context, rrectToDraw, + size, xformedSigma)); + if (!mask) { + return nullptr; + } + + return std::unique_ptr( + new GrRRectBlurEffect(xformedSigma, devRRect.getBounds(), + SkRRectPriv::GetSimpleRadii(devRRect).fX, std::move(mask))); + } +} + +@test(d) { + SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar r = d->fRandom->nextRangeF(1.f, 9.f); + SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); + SkRRect rrect; + rrect.setRectXY(SkRect::MakeWH(w, h), r, r); + return GrRRectBlurEffect::Make(d->context(), sigma, sigma, rrect, rrect); +} + +void main() { + // warp the fragment position to the appropriate part of the 9patch blur texture + + half2 rectCenter = half2((proxyRect.xy + proxyRect.zw) / 2.0); + half2 translatedFragPos = half2(sk_FragCoord.xy - proxyRect.xy); + half threshold = cornerRadius + 2.0 * blurRadius; + half2 middle = half2(proxyRect.zw - proxyRect.xy - 2.0 * threshold); + + if (translatedFragPos.x >= threshold && translatedFragPos.x < (middle.x + threshold)) { + translatedFragPos.x = threshold; + } else if (translatedFragPos.x >= (middle.x + threshold)) { + translatedFragPos.x -= middle.x - 1.0; + } + + if (translatedFragPos.y > threshold && translatedFragPos.y < (middle.y+threshold)) { + translatedFragPos.y = threshold; + } else if (translatedFragPos.y >= (middle.y + threshold)) { + translatedFragPos.y -= middle.y - 1.0; + } + + half2 proxyDims = half2(2.0 * threshold + 1.0); + half2 texCoord = translatedFragPos / proxyDims; + + sk_OutColor = sk_InColor * sample(ninePatchSampler, texCoord); +} + +@setData(pdman) { + float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f); + pdman.set1f(blurRadius, blurRadiusValue); + + SkRect outset = rect; + outset.outset(blurRadiusValue, blurRadiusValue); + pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrRRectEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrRRectEffect.cpp new file mode 100644 index 0000000000..9c9dc31320 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrRRectEffect.cpp @@ -0,0 +1,769 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrRRectEffect.h" + +#include "src/core/SkRRectPriv.h" +#include "src/core/SkTLazy.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/effects/GrConvexPolyEffect.h" +#include "src/gpu/effects/GrOvalEffect.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +// The effects defined here only handle rrect radii >= kRadiusMin. +static const SkScalar kRadiusMin = SK_ScalarHalf; + +////////////////////////////////////////////////////////////////////////////// + +class CircularRRectEffect : public GrFragmentProcessor { +public: + + enum CornerFlags { + kTopLeft_CornerFlag = (1 << SkRRect::kUpperLeft_Corner), + kTopRight_CornerFlag = (1 << SkRRect::kUpperRight_Corner), + kBottomRight_CornerFlag = (1 << SkRRect::kLowerRight_Corner), + kBottomLeft_CornerFlag = (1 << SkRRect::kLowerLeft_Corner), + + kLeft_CornerFlags = kTopLeft_CornerFlag | kBottomLeft_CornerFlag, + kTop_CornerFlags = kTopLeft_CornerFlag | kTopRight_CornerFlag, + kRight_CornerFlags = kTopRight_CornerFlag | kBottomRight_CornerFlag, + kBottom_CornerFlags = kBottomLeft_CornerFlag | kBottomRight_CornerFlag, + + kAll_CornerFlags = kTopLeft_CornerFlag | kTopRight_CornerFlag | + kBottomLeft_CornerFlag | kBottomRight_CornerFlag, + + kNone_CornerFlags = 0 + }; + + // The flags are used to indicate which corners are circluar (unflagged corners are assumed to + // be square). + static std::unique_ptr Make(GrClipEdgeType, + uint32_t circularCornerFlags, const SkRRect&); + + ~CircularRRectEffect() override {} + + const char* name() const override { return "CircularRRect"; } + + std::unique_ptr clone() const override; + + const SkRRect& getRRect() const { return fRRect; } + + uint32_t getCircularCornerFlags() const { return fCircularCornerFlags; } + + GrClipEdgeType getEdgeType() const { return fEdgeType; } + +private: + CircularRRectEffect(GrClipEdgeType, uint32_t circularCornerFlags, const SkRRect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& other) const override; + + SkRRect fRRect; + GrClipEdgeType fEdgeType; + uint32_t fCircularCornerFlags; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +std::unique_ptr CircularRRectEffect::Make(GrClipEdgeType edgeType, + uint32_t circularCornerFlags, + const SkRRect& rrect) { + if (GrClipEdgeType::kFillAA != edgeType && GrClipEdgeType::kInverseFillAA != edgeType) { + return nullptr; + } + return std::unique_ptr( + new CircularRRectEffect(edgeType, circularCornerFlags, rrect)); +} + +CircularRRectEffect::CircularRRectEffect(GrClipEdgeType edgeType, uint32_t circularCornerFlags, + const SkRRect& rrect) + : INHERITED(kCircularRRectEffect_ClassID, kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fRRect(rrect) + , fEdgeType(edgeType) + , fCircularCornerFlags(circularCornerFlags) { +} + +std::unique_ptr CircularRRectEffect::clone() const { + return std::unique_ptr( + new CircularRRectEffect(fEdgeType, fCircularCornerFlags, fRRect)); +} + +bool CircularRRectEffect::onIsEqual(const GrFragmentProcessor& other) const { + const CircularRRectEffect& crre = other.cast(); + // The corner flags are derived from fRRect, so no need to check them. + return fEdgeType == crre.fEdgeType && fRRect == crre.fRRect; +} + +////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(CircularRRectEffect); + +#if GR_TEST_UTILS +std::unique_ptr CircularRRectEffect::TestCreate(GrProcessorTestData* d) { + SkScalar w = d->fRandom->nextRangeScalar(20.f, 1000.f); + SkScalar h = d->fRandom->nextRangeScalar(20.f, 1000.f); + SkScalar r = d->fRandom->nextRangeF(kRadiusMin, 9.f); + SkRRect rrect; + rrect.setRectXY(SkRect::MakeWH(w, h), r, r); + std::unique_ptr fp; + do { + GrClipEdgeType et = + (GrClipEdgeType)d->fRandom->nextULessThan(kGrClipEdgeTypeCnt); + fp = GrRRectEffect::Make(et, rrect, *d->caps()->shaderCaps()); + } while (nullptr == fp); + return fp; +} +#endif + +////////////////////////////////////////////////////////////////////////////// + +class GLCircularRRectEffect : public GrGLSLFragmentProcessor { +public: + GLCircularRRectEffect() = default; + + virtual void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fInnerRectUniform; + GrGLSLProgramDataManager::UniformHandle fRadiusPlusHalfUniform; + SkRRect fPrevRRect; + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GLCircularRRectEffect::emitCode(EmitArgs& args) { + const CircularRRectEffect& crre = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + const char *rectName; + const char *radiusPlusHalfName; + // The inner rect is the rrect bounds inset by the radius. Its left, top, right, and bottom + // edges correspond to components x, y, z, and w, respectively. When a side of the rrect has + // only rectangular corners, that side's value corresponds to the rect edge's value outset by + // half a pixel. + fInnerRectUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "innerRect", &rectName); + // x is (r + .5) and y is 1/(r + .5) + fRadiusPlusHalfUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "radiusPlusHalf", &radiusPlusHalfName); + + // If we're on a device where float != fp32 then the length calculation could overflow. + SkString clampedCircleDistance; + if (!args.fShaderCaps->floatIs32Bits()) { + clampedCircleDistance.printf("saturate(%s.x * (1.0 - length(dxy * %s.y)))", + radiusPlusHalfName, radiusPlusHalfName); + } else { + clampedCircleDistance.printf("saturate(%s.x - length(dxy))", radiusPlusHalfName); + } + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // At each quarter-circle corner we compute a vector that is the offset of the fragment position + // from the circle center. The vector is pinned in x and y to be in the quarter-plane relevant + // to that corner. This means that points near the interior near the rrect top edge will have + // a vector that points straight up for both the TL left and TR corners. Computing an + // alpha from this vector at either the TR or TL corner will give the correct result. Similarly, + // fragments near the other three edges will get the correct AA. Fragments in the interior of + // the rrect will have a (0,0) vector at all four corners. So long as the radius > 0.5 they will + // correctly produce an alpha value of 1 at all four corners. We take the min of all the alphas. + // The code below is a simplified version of the above that performs maxs on the vector + // components before computing distances and alpha values so that only one distance computation + // need be computed to determine the min alpha. + // + // For the cases where one half of the rrect is rectangular we drop one of the x or y + // computations, compute a separate rect edge alpha for the rect side, and mul the two computed + // alphas together. + switch (crre.getCircularCornerFlags()) { + case CircularRRectEffect::kAll_CornerFlags: + fragBuilder->codeAppendf("float2 dxy0 = %s.xy - sk_FragCoord.xy;", rectName); + fragBuilder->codeAppendf("float2 dxy1 = sk_FragCoord.xy - %s.zw;", rectName); + fragBuilder->codeAppend("float2 dxy = max(max(dxy0, dxy1), 0.0);"); + fragBuilder->codeAppendf("half alpha = half(%s);", clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kTopLeft_CornerFlag: + fragBuilder->codeAppendf("float2 dxy = max(%s.xy - sk_FragCoord.xy, 0.0);", + rectName); + fragBuilder->codeAppendf("half rightAlpha = half(saturate(%s.z - sk_FragCoord.x));", + rectName); + fragBuilder->codeAppendf("half bottomAlpha = half(saturate(%s.w - sk_FragCoord.y));", + rectName); + fragBuilder->codeAppendf("half alpha = bottomAlpha * rightAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kTopRight_CornerFlag: + fragBuilder->codeAppendf("float2 dxy = max(float2(sk_FragCoord.x - %s.z, " + "%s.y - sk_FragCoord.y), 0.0);", + rectName, rectName); + fragBuilder->codeAppendf("half leftAlpha = half(saturate(sk_FragCoord.x - %s.x));", + rectName); + fragBuilder->codeAppendf("half bottomAlpha = half(saturate(%s.w - sk_FragCoord.y));", + rectName); + fragBuilder->codeAppendf("half alpha = bottomAlpha * leftAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kBottomRight_CornerFlag: + fragBuilder->codeAppendf("float2 dxy = max(sk_FragCoord.xy - %s.zw, 0.0);", + rectName); + fragBuilder->codeAppendf("half leftAlpha = half(saturate(sk_FragCoord.x - %s.x));", + rectName); + fragBuilder->codeAppendf("half topAlpha = half(saturate(sk_FragCoord.y - %s.y));", + rectName); + fragBuilder->codeAppendf("half alpha = topAlpha * leftAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kBottomLeft_CornerFlag: + fragBuilder->codeAppendf("float2 dxy = max(float2(%s.x - sk_FragCoord.x, " + "sk_FragCoord.y - %s.w), 0.0);", + rectName, rectName); + fragBuilder->codeAppendf("half rightAlpha = half(saturate(%s.z - sk_FragCoord.x));", + rectName); + fragBuilder->codeAppendf("half topAlpha = half(saturate(sk_FragCoord.y - %s.y));", + rectName); + fragBuilder->codeAppendf("half alpha = topAlpha * rightAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kLeft_CornerFlags: + fragBuilder->codeAppendf("float2 dxy0 = %s.xy - sk_FragCoord.xy;", rectName); + fragBuilder->codeAppendf("float dy1 = sk_FragCoord.y - %s.w;", rectName); + fragBuilder->codeAppend("float2 dxy = max(float2(dxy0.x, max(dxy0.y, dy1)), 0.0);"); + fragBuilder->codeAppendf("half rightAlpha = half(saturate(%s.z - sk_FragCoord.x));", + rectName); + fragBuilder->codeAppendf("half alpha = rightAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kTop_CornerFlags: + fragBuilder->codeAppendf("float2 dxy0 = %s.xy - sk_FragCoord.xy;", rectName); + fragBuilder->codeAppendf("float dx1 = sk_FragCoord.x - %s.z;", rectName); + fragBuilder->codeAppend("float2 dxy = max(float2(max(dxy0.x, dx1), dxy0.y), 0.0);"); + fragBuilder->codeAppendf("half bottomAlpha = half(saturate(%s.w - sk_FragCoord.y));", + rectName); + fragBuilder->codeAppendf("half alpha = bottomAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kRight_CornerFlags: + fragBuilder->codeAppendf("float dy0 = %s.y - sk_FragCoord.y;", rectName); + fragBuilder->codeAppendf("float2 dxy1 = sk_FragCoord.xy - %s.zw;", rectName); + fragBuilder->codeAppend("float2 dxy = max(float2(dxy1.x, max(dy0, dxy1.y)), 0.0);"); + fragBuilder->codeAppendf("half leftAlpha = half(saturate(sk_FragCoord.x - %s.x));", + rectName); + fragBuilder->codeAppendf("half alpha = leftAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + case CircularRRectEffect::kBottom_CornerFlags: + fragBuilder->codeAppendf("float dx0 = %s.x - sk_FragCoord.x;", rectName); + fragBuilder->codeAppendf("float2 dxy1 = sk_FragCoord.xy - %s.zw;", rectName); + fragBuilder->codeAppend("float2 dxy = max(float2(max(dx0, dxy1.x), dxy1.y), 0.0);"); + fragBuilder->codeAppendf("half topAlpha = half(saturate(sk_FragCoord.y - %s.y));", + rectName); + fragBuilder->codeAppendf("half alpha = topAlpha * half(%s);", + clampedCircleDistance.c_str()); + break; + } + + if (GrClipEdgeType::kInverseFillAA == crre.getEdgeType()) { + fragBuilder->codeAppend("alpha = 1.0 - alpha;"); + } + + fragBuilder->codeAppendf("%s = %s * alpha;", args.fOutputColor, args.fInputColor); +} + +void GLCircularRRectEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const CircularRRectEffect& crre = processor.cast(); + GR_STATIC_ASSERT(kGrClipEdgeTypeCnt <= 8); + b->add32((crre.getCircularCornerFlags() << 3) | (int) crre.getEdgeType()); +} + +void GLCircularRRectEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + const CircularRRectEffect& crre = processor.cast(); + const SkRRect& rrect = crre.getRRect(); + if (rrect != fPrevRRect) { + SkRect rect = rrect.getBounds(); + SkScalar radius = 0; + switch (crre.getCircularCornerFlags()) { + case CircularRRectEffect::kAll_CornerFlags: + SkASSERT(SkRRectPriv::IsSimpleCircular(rrect)); + radius = SkRRectPriv::GetSimpleRadii(rrect).fX; + SkASSERT(radius >= kRadiusMin); + rect.inset(radius, radius); + break; + case CircularRRectEffect::kTopLeft_CornerFlag: + radius = rrect.radii(SkRRect::kUpperLeft_Corner).fX; + rect.fLeft += radius; + rect.fTop += radius; + rect.fRight += 0.5f; + rect.fBottom += 0.5f; + break; + case CircularRRectEffect::kTopRight_CornerFlag: + radius = rrect.radii(SkRRect::kUpperRight_Corner).fX; + rect.fLeft -= 0.5f; + rect.fTop += radius; + rect.fRight -= radius; + rect.fBottom += 0.5f; + break; + case CircularRRectEffect::kBottomRight_CornerFlag: + radius = rrect.radii(SkRRect::kLowerRight_Corner).fX; + rect.fLeft -= 0.5f; + rect.fTop -= 0.5f; + rect.fRight -= radius; + rect.fBottom -= radius; + break; + case CircularRRectEffect::kBottomLeft_CornerFlag: + radius = rrect.radii(SkRRect::kLowerLeft_Corner).fX; + rect.fLeft += radius; + rect.fTop -= 0.5f; + rect.fRight += 0.5f; + rect.fBottom -= radius; + break; + case CircularRRectEffect::kLeft_CornerFlags: + radius = rrect.radii(SkRRect::kUpperLeft_Corner).fX; + rect.fLeft += radius; + rect.fTop += radius; + rect.fRight += 0.5f; + rect.fBottom -= radius; + break; + case CircularRRectEffect::kTop_CornerFlags: + radius = rrect.radii(SkRRect::kUpperLeft_Corner).fX; + rect.fLeft += radius; + rect.fTop += radius; + rect.fRight -= radius; + rect.fBottom += 0.5f; + break; + case CircularRRectEffect::kRight_CornerFlags: + radius = rrect.radii(SkRRect::kUpperRight_Corner).fX; + rect.fLeft -= 0.5f; + rect.fTop += radius; + rect.fRight -= radius; + rect.fBottom -= radius; + break; + case CircularRRectEffect::kBottom_CornerFlags: + radius = rrect.radii(SkRRect::kLowerLeft_Corner).fX; + rect.fLeft += radius; + rect.fTop -= 0.5f; + rect.fRight -= radius; + rect.fBottom -= radius; + break; + default: + SK_ABORT("Should have been one of the above cases."); + } + pdman.set4f(fInnerRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + radius += 0.5f; + pdman.set2f(fRadiusPlusHalfUniform, radius, 1.f / radius); + fPrevRRect = rrect; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void CircularRRectEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLCircularRRectEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* CircularRRectEffect::onCreateGLSLInstance() const { + return new GLCircularRRectEffect; +} + +////////////////////////////////////////////////////////////////////////////// + +class EllipticalRRectEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(GrClipEdgeType, const SkRRect&); + + ~EllipticalRRectEffect() override {} + + const char* name() const override { return "EllipticalRRect"; } + + std::unique_ptr clone() const override; + + const SkRRect& getRRect() const { return fRRect; } + + GrClipEdgeType getEdgeType() const { return fEdgeType; } + +private: + EllipticalRRectEffect(GrClipEdgeType, const SkRRect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& other) const override; + + SkRRect fRRect; + GrClipEdgeType fEdgeType; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +std::unique_ptr EllipticalRRectEffect::Make(GrClipEdgeType edgeType, + const SkRRect& rrect) { + if (GrClipEdgeType::kFillAA != edgeType && GrClipEdgeType::kInverseFillAA != edgeType) { + return nullptr; + } + return std::unique_ptr(new EllipticalRRectEffect(edgeType, rrect)); +} + +EllipticalRRectEffect::EllipticalRRectEffect(GrClipEdgeType edgeType, const SkRRect& rrect) + : INHERITED(kEllipticalRRectEffect_ClassID, kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fRRect(rrect) + , fEdgeType(edgeType) { +} + +std::unique_ptr EllipticalRRectEffect::clone() const { + return std::unique_ptr(new EllipticalRRectEffect(fEdgeType, fRRect)); +} + +bool EllipticalRRectEffect::onIsEqual(const GrFragmentProcessor& other) const { + const EllipticalRRectEffect& erre = other.cast(); + return fEdgeType == erre.fEdgeType && fRRect == erre.fRRect; +} + +////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(EllipticalRRectEffect); + +#if GR_TEST_UTILS +std::unique_ptr EllipticalRRectEffect::TestCreate(GrProcessorTestData* d) { + SkScalar w = d->fRandom->nextRangeScalar(20.f, 1000.f); + SkScalar h = d->fRandom->nextRangeScalar(20.f, 1000.f); + SkVector r[4]; + r[SkRRect::kUpperLeft_Corner].fX = d->fRandom->nextRangeF(kRadiusMin, 9.f); + // ensure at least one corner really is elliptical + do { + r[SkRRect::kUpperLeft_Corner].fY = d->fRandom->nextRangeF(kRadiusMin, 9.f); + } while (r[SkRRect::kUpperLeft_Corner].fY == r[SkRRect::kUpperLeft_Corner].fX); + + SkRRect rrect; + if (d->fRandom->nextBool()) { + // half the time create a four-radii rrect. + r[SkRRect::kLowerRight_Corner].fX = d->fRandom->nextRangeF(kRadiusMin, 9.f); + r[SkRRect::kLowerRight_Corner].fY = d->fRandom->nextRangeF(kRadiusMin, 9.f); + + r[SkRRect::kUpperRight_Corner].fX = r[SkRRect::kLowerRight_Corner].fX; + r[SkRRect::kUpperRight_Corner].fY = r[SkRRect::kUpperLeft_Corner].fY; + + r[SkRRect::kLowerLeft_Corner].fX = r[SkRRect::kUpperLeft_Corner].fX; + r[SkRRect::kLowerLeft_Corner].fY = r[SkRRect::kLowerRight_Corner].fY; + + rrect.setRectRadii(SkRect::MakeWH(w, h), r); + } else { + rrect.setRectXY(SkRect::MakeWH(w, h), r[SkRRect::kUpperLeft_Corner].fX, + r[SkRRect::kUpperLeft_Corner].fY); + } + std::unique_ptr fp; + do { + GrClipEdgeType et = (GrClipEdgeType)d->fRandom->nextULessThan(kGrClipEdgeTypeCnt); + fp = GrRRectEffect::Make(et, rrect, *d->caps()->shaderCaps()); + } while (nullptr == fp); + return fp; +} +#endif + +////////////////////////////////////////////////////////////////////////////// + +class GLEllipticalRRectEffect : public GrGLSLFragmentProcessor { +public: + GLEllipticalRRectEffect() = default; + + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fInnerRectUniform; + GrGLSLProgramDataManager::UniformHandle fInvRadiiSqdUniform; + GrGLSLProgramDataManager::UniformHandle fScaleUniform; + SkRRect fPrevRRect; + typedef GrGLSLFragmentProcessor INHERITED; +}; + +void GLEllipticalRRectEffect::emitCode(EmitArgs& args) { + const EllipticalRRectEffect& erre = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + const char *rectName; + // The inner rect is the rrect bounds inset by the x/y radii + fInnerRectUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "innerRect", &rectName); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // At each quarter-ellipse corner we compute a vector that is the offset of the fragment pos + // to the ellipse center. The vector is pinned in x and y to be in the quarter-plane relevant + // to that corner. This means that points near the interior near the rrect top edge will have + // a vector that points straight up for both the TL left and TR corners. Computing an + // alpha from this vector at either the TR or TL corner will give the correct result. Similarly, + // fragments near the other three edges will get the correct AA. Fragments in the interior of + // the rrect will have a (0,0) vector at all four corners. So long as the radii > 0.5 they will + // correctly produce an alpha value of 1 at all four corners. We take the min of all the alphas. + // + // The code below is a simplified version of the above that performs maxs on the vector + // components before computing distances and alpha values so that only one distance computation + // need be computed to determine the min alpha. + fragBuilder->codeAppendf("float2 dxy0 = %s.xy - sk_FragCoord.xy;", rectName); + fragBuilder->codeAppendf("float2 dxy1 = sk_FragCoord.xy - %s.zw;", rectName); + + // If we're on a device where float != fp32 then we'll do the distance computation in a space + // that is normalized by the largest radius. The scale uniform will be scale, 1/scale. The + // radii uniform values are already in this normalized space. + const char* scaleName = nullptr; + if (!args.fShaderCaps->floatIs32Bits()) { + fScaleUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "scale", + &scaleName); + } + + // The uniforms with the inv squared radii are highp to prevent underflow. + switch (erre.getRRect().getType()) { + case SkRRect::kSimple_Type: { + const char *invRadiiXYSqdName; + fInvRadiiSqdUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat2_GrSLType, + "invRadiiXY", + &invRadiiXYSqdName); + fragBuilder->codeAppend("float2 dxy = max(max(dxy0, dxy1), 0.0);"); + if (scaleName) { + fragBuilder->codeAppendf("dxy *= %s.y;", scaleName); + } + // Z is the x/y offsets divided by squared radii. + fragBuilder->codeAppendf("float2 Z = dxy * %s.xy;", invRadiiXYSqdName); + break; + } + case SkRRect::kNinePatch_Type: { + const char *invRadiiLTRBSqdName; + fInvRadiiSqdUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, + "invRadiiLTRB", + &invRadiiLTRBSqdName); + if (scaleName) { + fragBuilder->codeAppendf("dxy0 *= %s.y;", scaleName); + fragBuilder->codeAppendf("dxy1 *= %s.y;", scaleName); + } + fragBuilder->codeAppend("float2 dxy = max(max(dxy0, dxy1), 0.0);"); + // Z is the x/y offsets divided by squared radii. We only care about the (at most) one + // corner where both the x and y offsets are positive, hence the maxes. (The inverse + // squared radii will always be positive.) + fragBuilder->codeAppendf("float2 Z = max(max(dxy0 * %s.xy, dxy1 * %s.zw), 0.0);", + invRadiiLTRBSqdName, invRadiiLTRBSqdName); + + break; + } + default: + SK_ABORT("RRect should always be simple or nine-patch."); + } + // implicit is the evaluation of (x/a)^2 + (y/b)^2 - 1. + fragBuilder->codeAppend("half implicit = half(dot(Z, dxy) - 1.0);"); + // grad_dot is the squared length of the gradient of the implicit. + fragBuilder->codeAppend("half grad_dot = half(4.0 * dot(Z, Z));"); + // avoid calling inversesqrt on zero. + fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);"); + fragBuilder->codeAppend("half approx_dist = implicit * half(inversesqrt(grad_dot));"); + if (scaleName) { + fragBuilder->codeAppendf("approx_dist *= %s.x;", scaleName); + } + + if (GrClipEdgeType::kFillAA == erre.getEdgeType()) { + fragBuilder->codeAppend("half alpha = clamp(0.5 - approx_dist, 0.0, 1.0);"); + } else { + fragBuilder->codeAppend("half alpha = clamp(0.5 + approx_dist, 0.0, 1.0);"); + } + + fragBuilder->codeAppendf("%s = %s * alpha;", args.fOutputColor, args.fInputColor); +} + +void GLEllipticalRRectEffect::GenKey(const GrProcessor& effect, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const EllipticalRRectEffect& erre = effect.cast(); + GR_STATIC_ASSERT((int) GrClipEdgeType::kLast < (1 << 3)); + b->add32(erre.getRRect().getType() | (int) erre.getEdgeType() << 3); +} + +void GLEllipticalRRectEffect::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& effect) { + const EllipticalRRectEffect& erre = effect.cast(); + const SkRRect& rrect = erre.getRRect(); + // If we're using a scale factor to work around precision issues, choose the largest radius + // as the scale factor. The inv radii need to be pre-adjusted by the scale factor. + if (rrect != fPrevRRect) { + SkRect rect = rrect.getBounds(); + const SkVector& r0 = rrect.radii(SkRRect::kUpperLeft_Corner); + SkASSERT(r0.fX >= kRadiusMin); + SkASSERT(r0.fY >= kRadiusMin); + switch (erre.getRRect().getType()) { + case SkRRect::kSimple_Type: + rect.inset(r0.fX, r0.fY); + if (fScaleUniform.isValid()) { + if (r0.fX > r0.fY) { + pdman.set2f(fInvRadiiSqdUniform, 1.f, (r0.fX * r0.fX) / (r0.fY * r0.fY)); + pdman.set2f(fScaleUniform, r0.fX, 1.f / r0.fX); + } else { + pdman.set2f(fInvRadiiSqdUniform, (r0.fY * r0.fY) / (r0.fX * r0.fX), 1.f); + pdman.set2f(fScaleUniform, r0.fY, 1.f / r0.fY); + } + } else { + pdman.set2f(fInvRadiiSqdUniform, 1.f / (r0.fX * r0.fX), + 1.f / (r0.fY * r0.fY)); + } + break; + case SkRRect::kNinePatch_Type: { + const SkVector& r1 = rrect.radii(SkRRect::kLowerRight_Corner); + SkASSERT(r1.fX >= kRadiusMin); + SkASSERT(r1.fY >= kRadiusMin); + rect.fLeft += r0.fX; + rect.fTop += r0.fY; + rect.fRight -= r1.fX; + rect.fBottom -= r1.fY; + if (fScaleUniform.isValid()) { + float scale = SkTMax(SkTMax(r0.fX, r0.fY), SkTMax(r1.fX, r1.fY)); + float scaleSqd = scale * scale; + pdman.set4f(fInvRadiiSqdUniform, scaleSqd / (r0.fX * r0.fX), + scaleSqd / (r0.fY * r0.fY), + scaleSqd / (r1.fX * r1.fX), + scaleSqd / (r1.fY * r1.fY)); + pdman.set2f(fScaleUniform, scale, 1.f / scale); + } else { + pdman.set4f(fInvRadiiSqdUniform, 1.f / (r0.fX * r0.fX), + 1.f / (r0.fY * r0.fY), + 1.f / (r1.fX * r1.fX), + 1.f / (r1.fY * r1.fY)); + } + break; + } + default: + SK_ABORT("RRect should always be simple or nine-patch."); + } + pdman.set4f(fInnerRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + fPrevRRect = rrect; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void EllipticalRRectEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLEllipticalRRectEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* EllipticalRRectEffect::onCreateGLSLInstance() const { + return new GLEllipticalRRectEffect; +} + +////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrRRectEffect::Make(GrClipEdgeType edgeType, + const SkRRect& rrect, + const GrShaderCaps& caps) { + if (rrect.isRect()) { + return GrConvexPolyEffect::Make(edgeType, rrect.getBounds()); + } + + if (rrect.isOval()) { + return GrOvalEffect::Make(edgeType, rrect.getBounds(), caps); + } + + if (rrect.isSimple()) { + if (SkRRectPriv::GetSimpleRadii(rrect).fX < kRadiusMin || + SkRRectPriv::GetSimpleRadii(rrect).fY < kRadiusMin) { + // In this case the corners are extremely close to rectangular and we collapse the + // clip to a rectangular clip. + return GrConvexPolyEffect::Make(edgeType, rrect.getBounds()); + } + if (SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY) { + return CircularRRectEffect::Make(edgeType, CircularRRectEffect::kAll_CornerFlags, + rrect); + } else { + return EllipticalRRectEffect::Make(edgeType, rrect); + } + } + + if (rrect.isComplex() || rrect.isNinePatch()) { + // Check for the "tab" cases - two adjacent circular corners and two square corners. + SkScalar circularRadius = 0; + uint32_t cornerFlags = 0; + + SkVector radii[4]; + bool squashedRadii = false; + for (int c = 0; c < 4; ++c) { + radii[c] = rrect.radii((SkRRect::Corner)c); + SkASSERT((0 == radii[c].fX) == (0 == radii[c].fY)); + if (0 == radii[c].fX) { + // The corner is square, so no need to squash or flag as circular. + continue; + } + if (radii[c].fX < kRadiusMin || radii[c].fY < kRadiusMin) { + radii[c].set(0, 0); + squashedRadii = true; + continue; + } + if (radii[c].fX != radii[c].fY) { + cornerFlags = ~0U; + break; + } + if (!cornerFlags) { + circularRadius = radii[c].fX; + cornerFlags = 1 << c; + } else { + if (radii[c].fX != circularRadius) { + cornerFlags = ~0U; + break; + } + cornerFlags |= 1 << c; + } + } + + switch (cornerFlags) { + case CircularRRectEffect::kAll_CornerFlags: + // This rrect should have been caught in the simple case above. Though, it would + // be correctly handled in the fallthrough code. + SkASSERT(false); + case CircularRRectEffect::kTopLeft_CornerFlag: + case CircularRRectEffect::kTopRight_CornerFlag: + case CircularRRectEffect::kBottomRight_CornerFlag: + case CircularRRectEffect::kBottomLeft_CornerFlag: + case CircularRRectEffect::kLeft_CornerFlags: + case CircularRRectEffect::kTop_CornerFlags: + case CircularRRectEffect::kRight_CornerFlags: + case CircularRRectEffect::kBottom_CornerFlags: { + SkTCopyOnFirstWrite rr(rrect); + if (squashedRadii) { + rr.writable()->setRectRadii(rrect.getBounds(), radii); + } + return CircularRRectEffect::Make(edgeType, cornerFlags, *rr); + } + case CircularRRectEffect::kNone_CornerFlags: + return GrConvexPolyEffect::Make(edgeType, rrect.getBounds()); + default: { + if (squashedRadii) { + // If we got here then we squashed some but not all the radii to zero. (If all + // had been squashed cornerFlags would be 0.) The elliptical effect doesn't + // support some rounded and some square corners. + return nullptr; + } + if (rrect.isNinePatch()) { + return EllipticalRRectEffect::Make(edgeType, rrect); + } + return nullptr; + } + } + } + + return nullptr; +} diff --git a/gfx/skia/skia/src/gpu/effects/GrRRectEffect.h b/gfx/skia/skia/src/gpu/effects/GrRRectEffect.h new file mode 100644 index 0000000000..426cb8f3c2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrRRectEffect.h @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRRectEffect_DEFINED +#define GrRRectEffect_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" +#include "include/private/GrTypesPriv.h" + +class GrFragmentProcessor; +class GrShaderCaps; +class GrProcessor; +class SkRRect; + +namespace GrRRectEffect { + +/** + * Creates an effect that performs anti-aliased clipping against a SkRRect. It doesn't support + * all varieties of SkRRect so the caller must check for a nullptr return. + */ +std::unique_ptr Make(GrClipEdgeType, const SkRRect&, const GrShaderCaps&); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrRectBlurEffect.fp b/gfx/skia/skia/src/gpu/effects/GrRectBlurEffect.fp new file mode 100644 index 0000000000..175fa658f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrRectBlurEffect.fp @@ -0,0 +1,205 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +@header { +#include +#include "include/core/SkRect.h" +#include "include/core/SkScalar.h" +#include "src/core/SkBlurMask.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrShaderCaps.h" +} + +in float4 rect; + +layout(key) bool highp = abs(rect.x) > 16000 || abs(rect.y) > 16000 || + abs(rect.z) > 16000 || abs(rect.w) > 16000; + +layout(when= highp) uniform float4 rectF; +layout(when=!highp) uniform half4 rectH; + +// Texture that is a LUT for integral of normal distribution. The value at x (where x is a texture +// coord between 0 and 1) is the integral from -inf to (3 * sigma * (-2 * x - 1)). I.e. x is mapped +// 0 3*sigma to -3 sigma. The flip saves a reversal in the shader. +in uniform sampler2D integral; +// Used to produce normalized texture coords for lookups in 'integral' +in uniform half invSixSigma; + +// There is a fast variant of the effect that does 2 texture lookups and a more general one for +// wider blurs relative to rect sizes that does 4. +layout(key) in bool isFast; + +@constructorParams { + GrSamplerState samplerParams +} + +@samplerParams(integral) { + samplerParams +} +@class { +static sk_sp CreateIntegralTexture(GrProxyProvider* proxyProvider, + float sixSigma) { + // The texture we're producing represents the integral of a normal distribution over a six-sigma + // range centered at zero. We want enough resolution so that the linear interpolation done in + // texture lookup doesn't introduce noticeable artifacts. We conservatively choose to have 2 + // texels for each dst pixel. + int minWidth = 2 * sk_float_ceil2int(sixSigma); + // Bin by powers of 2 with a minimum so we get good profile reuse. + int width = SkTMax(SkNextPow2(minWidth), 32); + + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask"); + builder[0] = width; + builder.finish(); + + sk_sp proxy(proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin)); + if (!proxy) { + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(width, 1))) { + return nullptr; + } + *bitmap.getAddr8(0, 0) = 255; + const float invWidth = 1.f / width; + for (int i = 1; i < width - 1; ++i) { + float x = (i + 0.5f) * invWidth; + x = (-6 * x + 3) * SK_ScalarRoot2Over2; + float integral = 0.5f * (std::erf(x) + 1.f); + *bitmap.getAddr8(i, 0) = SkToU8(sk_float_round2int(255.f * integral)); + } + *bitmap.getAddr8(width - 1, 0) = 0; + bitmap.setImmutable(); + proxy = proxyProvider->createProxyFromBitmap(bitmap, GrMipMapped::kNo); + if (!proxy) { + return nullptr; + } + SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, proxy.get()); + } + return proxy; +} +} + +@make { + static std::unique_ptr Make(GrProxyProvider* proxyProvider, + const GrShaderCaps& caps, + const SkRect& rect, float sigma) { + SkASSERT(rect.isSorted()); + if (!caps.floatIs32Bits()) { + // We promote the math that gets us into the Gaussian space to full float when the rect + // coords are large. If we don't have full float then fail. We could probably clip the + // rect to an outset device bounds instead. + if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f || + SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f) { + return nullptr; + } + } + + const float sixSigma = 6 * sigma; + auto integral = CreateIntegralTexture(proxyProvider, sixSigma); + if (!integral) { + return nullptr; + } + + // In the fast variant we think of the midpoint of the integral texture as aligning + // with the closest rect edge both in x and y. To simplify texture coord calculation we + // inset the rect so that the edge of the inset rect corresponds to t = 0 in the texture. + // It actually simplifies things a bit in the !isFast case, too. + float threeSigma = sixSigma / 2; + SkRect insetRect = {rect.fLeft + threeSigma, + rect.fTop + threeSigma, + rect.fRight - threeSigma, + rect.fBottom - threeSigma}; + + // In our fast variant we find the nearest horizontal and vertical edges and for each + // do a lookup in the integral texture for each and multiply them. When the rect is + // less than 6 sigma wide then things aren't so simple and we have to consider both the + // left and right edge of the rectangle (and similar in y). + bool isFast = insetRect.isSorted(); + // 1 / (6 * sigma) is the domain of the integral texture. We use the inverse to produce + // normalized texture coords from frag coord distances. + float invSixSigma = 1.f / sixSigma; + return std::unique_ptr(new GrRectBlurEffect(insetRect, + std::move(integral), invSixSigma, isFast, GrSamplerState::ClampBilerp())); + } +} + +void main() { + half xCoverage, yCoverage; + @if (isFast) { + // Get the smaller of the signed distance from the frag coord to the left and right + // edges and similar for y. + // The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below + // computations align the left edge of the integral texture with the inset rect's edge + // extending outward 6 * sigma from the inset rect. + half x, y; + @if (highp) { + x = max(half(rectF.x - sk_FragCoord.x), half(sk_FragCoord.x - rectF.z)); + y = max(half(rectF.y - sk_FragCoord.y), half(sk_FragCoord.y - rectF.w)); + } else { + x = max(half(rectH.x - sk_FragCoord.x), half(sk_FragCoord.x - rectH.z)); + y = max(half(rectH.y - sk_FragCoord.y), half(sk_FragCoord.y - rectH.w)); + } + xCoverage = sample(integral, half2(x * invSixSigma, 0.5)).a; + yCoverage = sample(integral, half2(y * invSixSigma, 0.5)).a; + sk_OutColor = sk_InColor * xCoverage * yCoverage; + } else { + // We just consider just the x direction here. In practice we compute x and y separately + // and multiply them together. + // We define our coord system so that the point at which we're evaluating a kernel + // defined by the normal distribution (K) as 0. In this coord system let L be left + // edge and R be the right edge of the rectangle. + // We can calculate C by integrating K with the half infinite ranges outside the L to R + // range and subtracting from 1: + // C = 1 - - + // K is symmetric about x=0 so: + // C = 1 - - + + // The integral texture goes "backwards" (from 3*sigma to -3*sigma) which is factored + // in to the below calculations. + // Also, our rect uniform was pre-inset by 3 sigma from the actual rect being blurred, + // also factored in. + half l, r, t, b; + @if (highp) { + l = half(sk_FragCoord.x - rectF.x); + r = half(rectF.z - sk_FragCoord.x); + t = half(sk_FragCoord.y - rectF.y); + b = half(rectF.w - sk_FragCoord.y); + } else { + l = half(sk_FragCoord.x - rectH.x); + r = half(rectH.z - sk_FragCoord.x); + t = half(sk_FragCoord.y - rectH.y); + b = half(rectH.w - sk_FragCoord.y); + } + half il = 1 + l * invSixSigma; + half ir = 1 + r * invSixSigma; + half it = 1 + t * invSixSigma; + half ib = 1 + b * invSixSigma; + xCoverage = 1 - sample(integral, half2(il, 0.5)).a + - sample(integral, half2(ir, 0.5)).a; + yCoverage = 1 - sample(integral, half2(it, 0.5)).a + - sample(integral, half2(ib, 0.5)).a; + } + sk_OutColor = sk_InColor * xCoverage * yCoverage; +} + +@setData(pdman) { + float r[] {rect.fLeft, rect.fTop, rect.fRight, rect.fBottom}; + pdman.set4fv(highp ? rectF : rectH, 1, r); +} + +@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag } + +@test(data) { + float sigma = data->fRandom->nextRangeF(3,8); + float width = data->fRandom->nextRangeF(200,300); + float height = data->fRandom->nextRangeF(200,300); + return GrRectBlurEffect::Make(data->proxyProvider(), *data->caps()->shaderCaps(), + SkRect::MakeWH(width, height), sigma); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrSRGBEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrSRGBEffect.cpp new file mode 100644 index 0000000000..b7e27a6b1f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrSRGBEffect.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrSRGBEffect.h" + +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +class GrGLSRGBEffect : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs& args) override { + const GrSRGBEffect& srgbe = args.fFp.cast(); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + SkString srgbFuncName; + const GrShaderVar gSrgbArgs[] = { + GrShaderVar("x", kHalf_GrSLType), + }; + switch (srgbe.mode()) { + case GrSRGBEffect::Mode::kLinearToSRGB: + fragBuilder->emitFunction(kHalf_GrSLType, + "linear_to_srgb", + SK_ARRAY_COUNT(gSrgbArgs), + gSrgbArgs, + "return (x <= 0.0031308) ? (x * 12.92) " + ": (1.055 * pow(x, 0.416666667) - 0.055);", + &srgbFuncName); + break; + case GrSRGBEffect::Mode::kSRGBToLinear: + fragBuilder->emitFunction(kHalf_GrSLType, + "srgb_to_linear", + SK_ARRAY_COUNT(gSrgbArgs), + gSrgbArgs, + "return (x <= 0.04045) ? (x / 12.92) " + ": pow((x + 0.055) / 1.055, 2.4);", + &srgbFuncName); + break; + } + + // Mali Bifrost uses fp16 for mediump. Making the intermediate color variable highp causes + // calculations to be performed with sufficient precision. + fragBuilder->codeAppendf("float4 color = %s;", args.fInputColor); + if (srgbe.alpha() == GrSRGBEffect::Alpha::kPremul) { + fragBuilder->codeAppendf("float nonZeroAlpha = max(color.a, 0.0001);"); + fragBuilder->codeAppendf("color = float4(color.rgb / nonZeroAlpha, color.a);"); + } + fragBuilder->codeAppendf("color = float4(%s(half(color.r)), %s(half(color.g)), " + "%s(half(color.b)), color.a);", + srgbFuncName.c_str(), + srgbFuncName.c_str(), + srgbFuncName.c_str()); + if (srgbe.alpha() == GrSRGBEffect::Alpha::kPremul) { + fragBuilder->codeAppendf("color = float4(color.rgb, 1) * color.a;"); + } + fragBuilder->codeAppendf("%s = half4(color);", args.fOutputColor); + } + + static inline void GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrSRGBEffect& srgbe = processor.cast(); + uint32_t key = static_cast(srgbe.mode()) | + (static_cast(srgbe.alpha()) << 1); + b->add32(key); + } + +private: + typedef GrGLSLFragmentProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrSRGBEffect::GrSRGBEffect(Mode mode, Alpha alpha) + : INHERITED(kGrSRGBEffect_ClassID, kPreservesOpaqueInput_OptimizationFlag | + kConstantOutputForConstantInput_OptimizationFlag) + , fMode(mode) + , fAlpha(alpha) +{ +} + +std::unique_ptr GrSRGBEffect::clone() const { return Make(fMode, fAlpha); } + +bool GrSRGBEffect::onIsEqual(const GrFragmentProcessor& s) const { + const GrSRGBEffect& other = s.cast(); + return other.fMode == fMode; +} + +static inline float srgb_to_linear(float srgb) { + return (srgb <= 0.04045f) ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f); +} +static inline float linear_to_srgb(float linear) { + return (linear <= 0.0031308) ? linear * 12.92f : 1.055f * powf(linear, 1.f / 2.4f) - 0.055f; +} + +SkPMColor4f GrSRGBEffect::constantOutputForConstantInput(const SkPMColor4f& inColor) const { + SkColor4f color = inColor.unpremul(); + switch (fMode) { + case Mode::kLinearToSRGB: + color = { linear_to_srgb(color.fR), linear_to_srgb(color.fG), linear_to_srgb(color.fB), + color.fA }; + break; + case Mode::kSRGBToLinear: + color = { srgb_to_linear(color.fR), srgb_to_linear(color.fG), srgb_to_linear(color.fB), + color.fA }; + break; + } + return color.premul(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSRGBEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrSRGBEffect::TestCreate(GrProcessorTestData* d) { + Mode testMode = static_cast(d->fRandom->nextRangeU(0, 1)); + return GrSRGBEffect::Make(testMode, Alpha::kPremul); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +void GrSRGBEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrGLSRGBEffect::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* GrSRGBEffect::onCreateGLSLInstance() const { + return new GrGLSRGBEffect; +} + diff --git a/gfx/skia/skia/src/gpu/effects/GrSRGBEffect.h b/gfx/skia/skia/src/gpu/effects/GrSRGBEffect.h new file mode 100644 index 0000000000..2135a50279 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrSRGBEffect.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSRGBEffect_DEFINED +#define GrSRGBEffect_DEFINED + +#include "src/gpu/GrFragmentProcessor.h" + +class GrSRGBEffect : public GrFragmentProcessor { +public: + enum class Mode { + kLinearToSRGB, + kSRGBToLinear, + }; + + enum class Alpha { + kPremul, + kOpaque, + }; + + /** + * Creates an effect that applies the sRGB transfer function (or its inverse) + */ + static std::unique_ptr Make(Mode mode, Alpha alpha) { + return std::unique_ptr(new GrSRGBEffect(mode, alpha)); + } + + const char* name() const override { return "sRGB"; } + + Mode mode() const { return fMode; } + Alpha alpha() const { return fAlpha; } + + std::unique_ptr clone() const override; + +private: + GrSRGBEffect(Mode mode, Alpha); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override; + + Mode fMode; + Alpha fAlpha; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrSaturateProcessor.fp b/gfx/skia/skia/src/gpu/effects/GrSaturateProcessor.fp new file mode 100644 index 0000000000..137205c790 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrSaturateProcessor.fp @@ -0,0 +1,25 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +@optimizationFlags { + kConstantOutputForConstantInput_OptimizationFlag | + kPreservesOpaqueInput_OptimizationFlag +} + +void main() { sk_OutColor = saturate(sk_InColor); } + +@class { + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return {SkTPin(input.fR, 0.f, 1.f), + SkTPin(input.fG, 0.f, 1.f), + SkTPin(input.fB, 0.f, 1.f), + SkTPin(input.fA, 0.f, 1.f)}; + } +} + +@test(d) { return GrSaturateProcessor::Make(); } diff --git a/gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.cpp b/gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.cpp new file mode 100644 index 0000000000..5af096c63d --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrShadowGeoProc.h" + +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +class GrGLSLRRectShadowGeoProc : public GrGLSLGeometryProcessor { +public: + GrGLSLRRectShadowGeoProc() {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const GrRRectShadowGeoProc& rsgp = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + // emit attributes + varyingHandler->emitAttributes(rsgp); + fragBuilder->codeAppend("half3 shadowParams;"); + varyingHandler->addPassThroughAttribute(rsgp.inShadowParams(), "shadowParams"); + + // setup pass through color + varyingHandler->addPassThroughAttribute(rsgp.inColor(), args.fOutputColor); + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, rsgp.inPosition().name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + rsgp.inPosition().asShaderVar(), + args.fFPCoordTransformHandler); + + fragBuilder->codeAppend("half d = length(shadowParams.xy);"); + fragBuilder->codeAppend("half distance = shadowParams.z * (1.0 - d);"); + + fragBuilder->codeAppend("half factor = 1.0 - clamp(distance, 0.0, 1.0);"); + fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;"); + fragBuilder->codeAppendf("%s = half4(factor);", + args.fOutputCoverage); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, + FPCoordTransformIter&& transformIter) override { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + +private: + typedef GrGLSLGeometryProcessor INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrRRectShadowGeoProc::GrRRectShadowGeoProc() : INHERITED(kGrRRectShadowGeoProc_ClassID) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType}; + fInShadowParams = {"inShadowParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); +} + +GrGLSLPrimitiveProcessor* GrRRectShadowGeoProc::createGLSLInstance(const GrShaderCaps&) const { + return new GrGLSLRRectShadowGeoProc(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrRRectShadowGeoProc); + +#if GR_TEST_UTILS +sk_sp GrRRectShadowGeoProc::TestCreate(GrProcessorTestData* d) { + return GrRRectShadowGeoProc::Make(); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.h b/gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.h new file mode 100644 index 0000000000..b821a6a505 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrShadowGeoProc.h @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrShadowGeoProc_DEFINED +#define GrShadowGeoProc_DEFINED + +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProcessor.h" + +class GrGLRRectShadowGeoProc; + +/** + * The output color of this effect is a coverage mask for a rrect shadow, + * assuming circular corner geometry. + */ +class GrRRectShadowGeoProc : public GrGeometryProcessor { +public: + static sk_sp Make() { + return sk_sp(new GrRRectShadowGeoProc()); + } + + const char* name() const override { return "RRectShadow"; } + + const Attribute& inPosition() const { return fInPosition; } + const Attribute& inColor() const { return fInColor; } + const Attribute& inShadowParams() const { return fInShadowParams; } + GrColor color() const { return fColor; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {} + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + GrRRectShadowGeoProc(); + + GrColor fColor; + + Attribute fInPosition; + Attribute fInColor; + Attribute fInShadowParams; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrSimpleTextureEffect.fp b/gfx/skia/skia/src/gpu/effects/GrSimpleTextureEffect.fp new file mode 100644 index 0000000000..5c30cc070f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrSimpleTextureEffect.fp @@ -0,0 +1,81 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in uniform sampler2D image; +in half4x4 matrix; + +@constructorParams { + GrColorType srcColorType, + GrSamplerState samplerParams +} + +@coordTransform(image) { + matrix +} + +@samplerParams(image) { + samplerParams +} + +@make { + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix) { + return std::unique_ptr( + new GrSimpleTextureEffect(std::move(proxy), matrix, srcColorType, + GrSamplerState(GrSamplerState::WrapMode::kClamp, GrSamplerState::Filter::kNearest))); + } + + /* clamp mode */ + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + GrSamplerState::Filter filter) { + return std::unique_ptr( + new GrSimpleTextureEffect(std::move(proxy), matrix, srcColorType, + GrSamplerState(GrSamplerState::WrapMode::kClamp, filter))); + } + + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const GrSamplerState& p) { + return std::unique_ptr( + new GrSimpleTextureEffect(std::move(proxy), matrix, srcColorType, p)); + } +} + +@optimizationFlags { + ModulateForSamplerOptFlags(srcColorType, + samplerParams.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder || + samplerParams.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder) +} + +void main() { + sk_OutColor = sk_InColor * sample(image, sk_TransformedCoords2D[0]); +} + +@test(testData) { + int texIdx = testData->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + GrSamplerState::WrapMode wrapModes[2]; + GrTest::TestWrapModes(testData->fRandom, wrapModes); + if (!testData->caps()->npotTextureTileSupport()) { + // Performing repeat sampling on npot textures will cause asserts on HW + // that lacks support. + wrapModes[0] = GrSamplerState::WrapMode::kClamp; + wrapModes[1] = GrSamplerState::WrapMode::kClamp; + } + + GrSamplerState params(wrapModes, testData->fRandom->nextBool() + ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kNearest); + + const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom); + return GrSimpleTextureEffect::Make(testData->textureProxy(texIdx), + testData->textureProxyColorType(texIdx), matrix, params); +} diff --git a/gfx/skia/skia/src/gpu/effects/GrSkSLFP.cpp b/gfx/skia/skia/src/gpu/effects/GrSkSLFP.cpp new file mode 100644 index 0000000000..9269ebf668 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrSkSLFP.cpp @@ -0,0 +1,573 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrSkSLFP.h" + +#include "include/gpu/GrTexture.h" +#include "include/private/GrContext_Base.h" +#include "src/gpu/GrBaseContextPriv.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" + +GrSkSLFPFactory::GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl, + SkSL::Program::Kind kind) + : fKind(kind) + , fName(name) { + SkSL::Program::Settings settings; + settings.fCaps = shaderCaps; + fBaseProgram = fCompiler.convertProgram(fKind, SkSL::String(sksl), settings); + if (fCompiler.errorCount()) { + SkDebugf("%s\n", fCompiler.errorText().c_str()); + } + SkASSERT(fBaseProgram); + SkASSERT(!fCompiler.errorCount()); + for (const auto& e : *fBaseProgram) { + if (e.fKind == SkSL::ProgramElement::kVar_Kind) { + SkSL::VarDeclarations& v = (SkSL::VarDeclarations&) e; + for (const auto& varStatement : v.fVars) { + const SkSL::Variable& var = *((SkSL::VarDeclaration&) *varStatement).fVar; + if ((var.fModifiers.fFlags & SkSL::Modifiers::kIn_Flag) || + (var.fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag)) { + fInAndUniformVars.push_back(&var); + } + // "in uniform" doesn't make sense outside of .fp files + SkASSERT((var.fModifiers.fFlags & SkSL::Modifiers::kIn_Flag) == 0 || + (var.fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) == 0); + // "layout(key)" doesn't make sense outside of .fp files; all 'in' variables are + // part of the key + SkASSERT(!var.fModifiers.fLayout.fKey); + } + } + } +} + +const SkSL::Program* GrSkSLFPFactory::getSpecialization(const SkSL::String& key, const void* inputs, + size_t inputSize) { + const auto& found = fSpecializations.find(key); + if (found != fSpecializations.end()) { + return found->second.get(); + } + + std::unordered_map inputMap; + size_t offset = 0; + for (const auto& v : fInAndUniformVars) { + SkSL::String name(v->fName); + if (&v->fType == fCompiler.context().fInt_Type.get() || + &v->fType == fCompiler.context().fShort_Type.get()) { + offset = SkAlign4(offset); + int32_t v = *(int32_t*) (((uint8_t*) inputs) + offset); + inputMap.insert(std::make_pair(name, SkSL::Program::Settings::Value(v))); + offset += sizeof(int32_t); + } else if (&v->fType == fCompiler.context().fFloat_Type.get() || + &v->fType == fCompiler.context().fHalf_Type.get()) { + offset = SkAlign4(offset); + float v = *(float*) (((uint8_t*) inputs) + offset); + inputMap.insert(std::make_pair(name, SkSL::Program::Settings::Value(v))); + offset += sizeof(float); + } else if (&v->fType == fCompiler.context().fBool_Type.get()) { + bool v = *(((bool*) inputs) + offset); + inputMap.insert(std::make_pair(name, SkSL::Program::Settings::Value(v))); + offset += sizeof(bool); + } else if (&v->fType == fCompiler.context().fFloat4_Type.get() || + &v->fType == fCompiler.context().fHalf4_Type.get()) { + offset = SkAlign4(offset) + sizeof(float) * 4; + } else if (&v->fType == fCompiler.context().fFragmentProcessor_Type.get()) { + // do nothing + } else { + printf("can't handle input var: %s\n", SkSL::String(v->fType.fName).c_str()); + SkASSERT(false); + } + } + + std::unique_ptr specialized = fCompiler.specialize(*fBaseProgram, inputMap); + bool optimized = fCompiler.optimize(*specialized); + if (!optimized) { + SkDebugf("%s\n", fCompiler.errorText().c_str()); + SkASSERT(false); + } + const SkSL::Program* result = specialized.get(); + fSpecializations.insert(std::make_pair(key, std::move(specialized))); + return result; +} + +static SkSL::Layout::CType get_ctype(const SkSL::Context& context, const SkSL::Variable& v) { + SkSL::Layout::CType result = v.fModifiers.fLayout.fCType; + if (result == SkSL::Layout::CType::kDefault) { + if (&v.fType == context.fFloat_Type.get()) { + result = SkSL::Layout::CType::kFloat; + } else if (&v.fType == context.fFloat4_Type.get()) { + result = SkSL::Layout::CType::kSkRect; + } else if (&v.fType == context.fHalf4_Type.get()) { + result = SkSL::Layout::CType::kSkPMColor; + } else if (&v.fType == context.fInt_Type.get()) { + result = SkSL::Layout::CType::kInt32; + } else if (&v.fType == context.fBool_Type.get()) { + result = SkSL::Layout::CType::kBool; + } else { + return SkSL::Layout::CType::kDefault; + } + } + return result; +} + +class GrGLSLSkSLFP : public GrGLSLFragmentProcessor { +public: + GrGLSLSkSLFP(const SkSL::Context* context, + const std::vector* inAndUniformVars, + SkSL::String glsl, std::vector formatArgs, + std::vector functions) + : fContext(*context) + , fInAndUniformVars(*inAndUniformVars) + , fGLSL(glsl) + , fFormatArgs(std::move(formatArgs)) + , fFunctions(std::move(functions)) {} + + GrSLType uniformType(const SkSL::Type& type) { + if (type == *fContext.fFloat_Type) { + return kFloat_GrSLType; + } else if (type == *fContext.fHalf_Type) { + return kHalf_GrSLType; + } else if (type == *fContext.fFloat2_Type) { + return kFloat2_GrSLType; + } else if (type == *fContext.fHalf2_Type) { + return kHalf2_GrSLType; + } else if (type == *fContext.fFloat4_Type) { + return kFloat4_GrSLType; + } else if (type == *fContext.fHalf4_Type) { + return kHalf4_GrSLType; + } else if (type == *fContext.fFloat4x4_Type) { + return kFloat4x4_GrSLType; + } else if (type == *fContext.fHalf4x4_Type) { + return kHalf4x4_GrSLType; + } else if (type == *fContext.fBool_Type) { + return kBool_GrSLType; + } else if (type == *fContext.fInt_Type) { + return kInt_GrSLType; + } + printf("%s\n", SkSL::String(type.fName).c_str()); + SK_ABORT("unsupported uniform type"); + } + + void emitCode(EmitArgs& args) override { + for (const auto& v : fInAndUniformVars) { + if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag && v->fType != + *fContext.fFragmentProcessor_Type) { + fUniformHandles.push_back(args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, + this->uniformType(v->fType), + SkSL::String(v->fName).c_str())); + } + } + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + for (const auto& f : fFunctions) { + fFunctionNames.emplace_back(); + fragBuilder->emitFunction(f.fReturnType, + f.fName.c_str(), + f.fParameters.size(), + f.fParameters.data(), + f.fBody.c_str(), + &fFunctionNames.back()); + } + std::vector childNames; + for (int i = 0; i < this->numChildProcessors(); ++i) { + childNames.push_back(SkStringPrintf("_child%d", i)); + this->invokeChild(i, &childNames[i], args); + } + int substringStartIndex = 0; + int formatArgIndex = 0; + SkString coords = args.fTransformedCoords.count() + ? fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint) + : SkString("sk_FragCoord"); + for (size_t i = 0; i < fGLSL.length(); ++i) { + char c = fGLSL[i]; + if (c == '%') { + fragBuilder->codeAppend(fGLSL.c_str() + substringStartIndex, + i - substringStartIndex); + ++i; + c = fGLSL[i]; + switch (c) { + case 's': { + SkSL::Compiler::FormatArg& arg = fFormatArgs[formatArgIndex++]; + switch (arg.fKind) { + case SkSL::Compiler::FormatArg::Kind::kInput: + fragBuilder->codeAppend(args.fInputColor); + break; + case SkSL::Compiler::FormatArg::Kind::kOutput: + fragBuilder->codeAppend(args.fOutputColor); + break; + case SkSL::Compiler::FormatArg::Kind::kCoordX: + fragBuilder->codeAppendf("%s.x", coords.c_str()); + break; + case SkSL::Compiler::FormatArg::Kind::kCoordY: + fragBuilder->codeAppendf("%s.y", coords.c_str()); + break; + case SkSL::Compiler::FormatArg::Kind::kUniform: + fragBuilder->codeAppend(args.fUniformHandler->getUniformCStr( + fUniformHandles[arg.fIndex])); + break; + case SkSL::Compiler::FormatArg::Kind::kChildProcessor: + fragBuilder->codeAppend(childNames[arg.fIndex].c_str()); + break; + case SkSL::Compiler::FormatArg::Kind::kFunctionName: + fragBuilder->codeAppend(fFunctionNames[arg.fIndex].c_str()); + break; + } + break; + } + default: + fragBuilder->codeAppendf("%c", c); + } + substringStartIndex = i + 1; + } + } + fragBuilder->codeAppend(fGLSL.c_str() + substringStartIndex, + fGLSL.length() - substringStartIndex); + } + + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + size_t uniformIndex = 0; + size_t offset = 0; + const GrSkSLFP& outer = _proc.cast(); + char* inputs = (char*) outer.fInputs.get(); + for (const auto& v : outer.fFactory->fInAndUniformVars) { + switch (get_ctype(fContext, *v)) { + case SkSL::Layout::CType::kSkPMColor: { + float f1 = ((uint8_t*) inputs)[offset++] / 255.0; + float f2 = ((uint8_t*) inputs)[offset++] / 255.0; + float f3 = ((uint8_t*) inputs)[offset++] / 255.0; + float f4 = ((uint8_t*) inputs)[offset++] / 255.0; + if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) { + pdman.set4f(fUniformHandles[uniformIndex++], f1, f2, f3, f4); + } + break; + } + case SkSL::Layout::CType::kSkPMColor4f: + case SkSL::Layout::CType::kSkRect: { + offset = SkAlign4(offset); + float f1 = *(float*) (inputs + offset); + offset += sizeof(float); + float f2 = *(float*) (inputs + offset); + offset += sizeof(float); + float f3 = *(float*) (inputs + offset); + offset += sizeof(float); + float f4 = *(float*) (inputs + offset); + offset += sizeof(float); + if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) { + pdman.set4f(fUniformHandles[uniformIndex++], f1, f2, f3, f4); + } + break; + } + case SkSL::Layout::CType::kInt32: { + int32_t i = *(int32_t*) (inputs + offset); + offset += sizeof(int32_t); + if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) { + pdman.set1i(fUniformHandles[uniformIndex++], i); + } + break; + } + case SkSL::Layout::CType::kFloat: { + float f = *(float*) (inputs + offset); + offset += sizeof(float); + if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) { + pdman.set1f(fUniformHandles[uniformIndex++], f); + } + break; + } + case SkSL::Layout::CType::kBool: + SkASSERT(!(v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag)); + ++offset; + break; + default: + SkASSERT(&v->fType == fContext.fFragmentProcessor_Type.get()); + } + } + } + + const SkSL::Context& fContext; + const std::vector& fInAndUniformVars; + // nearly-finished GLSL; still contains printf-style "%s" format tokens + const SkSL::String fGLSL; + std::vector fFormatArgs; + std::vector fFunctions; + std::vector fUniformHandles; + std::vector fFunctionNames; +}; + +std::unique_ptr GrSkSLFP::Make(GrContext_Base* context, int index, const char* name, + const char* sksl, const void* inputs, + size_t inputSize, SkSL::Program::Kind kind, + const SkMatrix* matrix) { + return std::unique_ptr(new GrSkSLFP(context->priv().fpFactoryCache(), + context->priv().caps()->shaderCaps(), + kind, index, name, sksl, SkString(), + inputs, inputSize, matrix)); +} + +std::unique_ptr GrSkSLFP::Make(GrContext_Base* context, int index, const char* name, + SkString sksl, const void* inputs, size_t inputSize, + SkSL::Program::Kind kind, const SkMatrix* matrix) { + return std::unique_ptr(new GrSkSLFP(context->priv().fpFactoryCache(), + context->priv().caps()->shaderCaps(), + kind, index, name, nullptr, std::move(sksl), + inputs, inputSize, matrix)); +} + +GrSkSLFP::GrSkSLFP(sk_sp factoryCache, const GrShaderCaps* shaderCaps, + SkSL::Program::Kind kind, int index, const char* name, const char* sksl, + SkString skslString, const void* inputs, size_t inputSize, + const SkMatrix* matrix) + : INHERITED(kGrSkSLFP_ClassID, kNone_OptimizationFlags) + , fFactoryCache(factoryCache) + , fShaderCaps(sk_ref_sp(shaderCaps)) + , fKind(kind) + , fIndex(index) + , fName(name) + , fSkSLString(skslString) + , fSkSL(sksl ? sksl : fSkSLString.c_str()) + , fInputs(new int8_t[inputSize]) + , fInputSize(inputSize) { + if (fInputSize) { + memcpy(fInputs.get(), inputs, inputSize); + } + if (matrix) { + fCoordTransform = GrCoordTransform(*matrix); + this->addCoordTransform(&fCoordTransform); + } +} + +GrSkSLFP::GrSkSLFP(const GrSkSLFP& other) + : INHERITED(kGrSkSLFP_ClassID, kNone_OptimizationFlags) + , fFactoryCache(other.fFactoryCache) + , fShaderCaps(other.fShaderCaps) + , fFactory(other.fFactory) + , fKind(other.fKind) + , fIndex(other.fIndex) + , fName(other.fName) + , fSkSLString(other.fSkSLString) + , fSkSL(other.fSkSL) + , fInputs(new int8_t[other.fInputSize]) + , fInputSize(other.fInputSize) { + if (fInputSize) { + memcpy(fInputs.get(), other.fInputs.get(), fInputSize); + } + if (other.numCoordTransforms()) { + fCoordTransform = other.fCoordTransform; + this->addCoordTransform(&fCoordTransform); + } +} + +const char* GrSkSLFP::name() const { + return fName; +} + +void GrSkSLFP::createFactory() const { + if (!fFactory) { + fFactory = fFactoryCache->get(fIndex); + if (!fFactory) { + fFactory = sk_sp(new GrSkSLFPFactory(fName, fShaderCaps.get(), fSkSL, + fKind)); + fFactoryCache->set(fIndex, fFactory); + } + } +} + +void GrSkSLFP::addChild(std::unique_ptr child) { + this->registerChildProcessor(std::move(child)); +} + +GrGLSLFragmentProcessor* GrSkSLFP::onCreateGLSLInstance() const { + this->createFactory(); + const SkSL::Program* specialized = fFactory->getSpecialization(fKey, fInputs.get(), fInputSize); + SkSL::String glsl; + std::vector formatArgs; + std::vector functions; + if (!fFactory->fCompiler.toPipelineStage(*specialized, &glsl, &formatArgs, &functions)) { + printf("%s\n", fFactory->fCompiler.errorText().c_str()); + SkASSERT(false); + } + return new GrGLSLSkSLFP(specialized->fContext.get(), &fFactory->fInAndUniformVars, glsl, + formatArgs, functions); +} + +void GrSkSLFP::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + this->createFactory(); + b->add32(fIndex); + size_t offset = 0; + char* inputs = (char*) fInputs.get(); + const SkSL::Context& context = fFactory->fCompiler.context(); + for (const auto& v : fFactory->fInAndUniformVars) { + if (&v->fType == context.fFragmentProcessor_Type.get()) { + continue; + } + switch (get_ctype(context, *v)) { + case SkSL::Layout::CType::kBool: + if (v->fModifiers.fFlags & SkSL::Modifiers::kIn_Flag) { + fKey += inputs[offset]; + b->add32(inputs[offset]); + } + ++offset; + break; + case SkSL::Layout::CType::kInt32: { + offset = SkAlign4(offset); + if (v->fModifiers.fFlags & SkSL::Modifiers::kIn_Flag) { + fKey += inputs[offset + 0]; + fKey += inputs[offset + 1]; + fKey += inputs[offset + 2]; + fKey += inputs[offset + 3]; + b->add32(*(int32_t*) (inputs + offset)); + } + offset += sizeof(int32_t); + break; + } + case SkSL::Layout::CType::kFloat: { + offset = SkAlign4(offset); + if (v->fModifiers.fFlags & SkSL::Modifiers::kIn_Flag) { + fKey += inputs[offset + 0]; + fKey += inputs[offset + 1]; + fKey += inputs[offset + 2]; + fKey += inputs[offset + 3]; + b->add32(*(float*) (inputs + offset)); + } + offset += sizeof(float); + break; + } + case SkSL::Layout::CType::kSkPMColor: + case SkSL::Layout::CType::kSkPMColor4f: + case SkSL::Layout::CType::kSkRect: + if (v->fModifiers.fFlags & SkSL::Modifiers::kIn_Flag) { + for (size_t i = 0; i < sizeof(float) * 4; ++i) { + fKey += inputs[offset + i]; + } + b->add32(*(int32_t*) (inputs + offset)); + offset += sizeof(float); + b->add32(*(int32_t*) (inputs + offset)); + offset += sizeof(float); + b->add32(*(int32_t*) (inputs + offset)); + offset += sizeof(float); + b->add32(*(int32_t*) (inputs + offset)); + offset += sizeof(float); + } else { + offset += sizeof(float) * 4; + } + break; + default: + // unsupported input var type + printf("%s\n", SkSL::String(v->fType.fName).c_str()); + SkASSERT(false); + } + } +} + +bool GrSkSLFP::onIsEqual(const GrFragmentProcessor& other) const { + const GrSkSLFP& sk = other.cast(); + SkASSERT(fIndex != sk.fIndex || fInputSize == sk.fInputSize); + return fIndex == sk.fIndex && + !memcmp(fInputs.get(), sk.fInputs.get(), fInputSize); +} + +std::unique_ptr GrSkSLFP::clone() const { + std::unique_ptr result(new GrSkSLFP(*this)); + for (int i = 0; i < this->numChildProcessors(); ++i) { + result->registerChildProcessor(this->childProcessor(i).clone()); + } + return std::unique_ptr(result.release()); +} + +// We have to do a bit of manual refcounting in the cache methods below. Ideally, we could just +// define fFactories to contain sk_sp rather than GrSkSLFPFactory*, but that would +// require GrContext to include GrSkSLFP, which creates much bigger headaches than a few manual +// refcounts. + +sk_sp GrSkSLFPFactoryCache::get(int index) { + if (index >= (int) fFactories.size()) { + return nullptr; + } + GrSkSLFPFactory* result = fFactories[index]; + SkSafeRef(result); + return sk_sp(result); +} + +void GrSkSLFPFactoryCache::set(int index, sk_sp factory) { + while (index >= (int) fFactories.size()) { + fFactories.emplace_back(); + } + factory->ref(); + SkASSERT(!fFactories[index]); + fFactories[index] = factory.get(); +} + +GrSkSLFPFactoryCache::~GrSkSLFPFactoryCache() { + for (GrSkSLFPFactory* factory : fFactories) { + if (factory) { + factory->unref(); + } + } +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSkSLFP); + +#if GR_TEST_UTILS + +#include "include/effects/SkArithmeticImageFilter.h" +#include "include/gpu/GrContext.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" + +extern const char* SKSL_ARITHMETIC_SRC; +extern const char* SKSL_DITHER_SRC; +extern const char* SKSL_OVERDRAW_SRC; + +using Value = SkSL::Program::Settings::Value; + +std::unique_ptr GrSkSLFP::TestCreate(GrProcessorTestData* d) { + int type = d->fRandom->nextULessThan(3); + switch (type) { + case 0: { + static int ditherIndex = NewIndex(); + int rangeType = d->fRandom->nextULessThan(3); + std::unique_ptr result = GrSkSLFP::Make(d->context(), ditherIndex, "Dither", + SKSL_DITHER_SRC, &rangeType, + sizeof(rangeType)); + return std::unique_ptr(result.release()); + } + case 1: { + static int arithmeticIndex = NewIndex(); + ArithmeticFPInputs inputs; + inputs.k[0] = d->fRandom->nextF(); + inputs.k[1] = d->fRandom->nextF(); + inputs.k[2] = d->fRandom->nextF(); + inputs.k[3] = d->fRandom->nextF(); + inputs.enforcePMColor = d->fRandom->nextBool(); + std::unique_ptr result = GrSkSLFP::Make(d->context(), arithmeticIndex, + "Arithmetic", SKSL_ARITHMETIC_SRC, + &inputs, sizeof(inputs)); + result->addChild(GrConstColorProcessor::Make( + SK_PMColor4fWHITE, + GrConstColorProcessor::InputMode::kIgnore)); + return std::unique_ptr(result.release()); + } + case 2: { + static int overdrawIndex = NewIndex(); + SkPMColor inputs[6]; + for (int i = 0; i < 6; ++i) { + inputs[i] = d->fRandom->nextU(); + } + std::unique_ptr result = GrSkSLFP::Make(d->context(), overdrawIndex, + "Overdraw", SKSL_OVERDRAW_SRC, + &inputs, sizeof(inputs)); + return std::unique_ptr(result.release()); + } + } + SK_ABORT("unreachable"); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrSkSLFP.h b/gfx/skia/skia/src/gpu/effects/GrSkSLFP.h new file mode 100644 index 0000000000..eba33c6112 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrSkSLFP.h @@ -0,0 +1,202 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSkSLFP_DEFINED +#define GrSkSLFP_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrSkSLFPFactoryCache.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLPipelineStageCodeGenerator.h" +#include + +#if GR_TEST_UTILS +#define GR_FP_SRC_STRING const char* +#else +#define GR_FP_SRC_STRING static const char* +#endif + +class GrContext_Base; +class GrSkSLFPFactory; + +class GrSkSLFP : public GrFragmentProcessor { +public: + /** + * Returns a new unique identifier. Each different SkSL fragment processor should call + * NewIndex once, statically, and use this index for all calls to Make. + */ + static int NewIndex() { + static std::atomic nextIndex{0}; + return nextIndex++; + } + + /** + * Creates a new fragment processor from an SkSL source string and a struct of inputs to the + * program. The input struct's type is derived from the 'in' and 'uniform' variables in the SkSL + * source, so e.g. the shader: + * + * in bool dither; + * uniform float x; + * uniform float y; + * .... + * + * would expect a pointer to a struct set up like: + * + * struct { + * bool dither; + * float x; + * float y; + * }; + * + * While both 'in' and 'uniform' variables go into this struct, the difference between them is + * that 'in' variables are statically "baked in" to the generated code, becoming literals, + * whereas uniform variables may be changed from invocation to invocation without having to + * recompile the shader. + * + * As the decision of whether to create a new shader or just upload new uniforms all happens + * behind the scenes, the difference between the two from an end-user perspective is primarily + * in performance: on the one hand, changing the value of an 'in' variable is very expensive + * (requiring the compiler to regenerate the code, upload a new shader to the GPU, and so + * forth), but on the other hand the compiler can optimize around its value because it is known + * at compile time. 'in' variables are therefore suitable for things like flags, where there are + * only a few possible values and a known-in-advance value can cause entire chunks of code to + * become dead (think static @ifs), while 'uniform's are used for continuous values like colors + * and coordinates, where it would be silly to create a separate shader for each possible set of + * values. Other than the (significant) performance implications, the only difference between + * the two is that 'in' variables can be used in static @if / @switch tests. When in doubt, use + * 'uniform'. + * + * As turning SkSL into GLSL / SPIR-V / etc. is fairly expensive, and the output may differ + * based on the inputs, internally the process is divided into two steps: we first parse and + * semantically analyze the SkSL into an internal representation, and then "specialize" this + * internal representation based on the inputs. The unspecialized internal representation of + * the program is cached, so further specializations of the same code are much faster than the + * first call. + * + * This caching is based on the 'index' parameter, which should be derived by statically calling + * 'NewIndex()'. Each given SkSL string should have a single, statically defined index + * associated with it. + */ + static std::unique_ptr Make( + GrContext_Base* context, + int index, + const char* name, + const char* sksl, + const void* inputs, + size_t inputSize, + SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind, + const SkMatrix* matrix = nullptr); + + static std::unique_ptr Make( + GrContext_Base* context, + int index, + const char* name, + SkString sksl, + const void* inputs, + size_t inputSize, + SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind, + const SkMatrix* matrix = nullptr); + + const char* name() const override; + + void addChild(std::unique_ptr child); + + std::unique_ptr clone() const override; + +private: + GrSkSLFP(sk_sp factoryCache, const GrShaderCaps* shaderCaps, + SkSL::Program::Kind kind, int fIndex, const char* name, const char* sksl, + SkString skslString, const void* inputs, size_t inputSize, const SkMatrix* matrix); + + GrSkSLFP(const GrSkSLFP& other); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + void createFactory() const; + + sk_sp fFactoryCache; + + const sk_sp fShaderCaps; + + mutable sk_sp fFactory; + + SkSL::Program::Kind fKind; + + int fIndex; + + const char* fName; + + // For object lifetime purposes, we have fields for the SkSL as both a const char* and a + // SkString. The const char* is the one we actually use, but it may point to the SkString's + // bytes. Since GrSkSLFPs are frequently created from constant strings, this allows us to + // generally avoid the overhead of copying the bytes into an SkString (in which case fSkSLString + // is the empty string), while still allowing the GrSkSLFP to manage the string's lifetime when + // needed. + SkString fSkSLString; + + const char* fSkSL; + + const std::unique_ptr fInputs; + + size_t fInputSize; + + GrCoordTransform fCoordTransform; + + mutable SkSL::String fKey; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; + + friend class GrGLSLSkSLFP; + + friend class GrSkSLFPFactory; +}; + +/** + * Produces GrFragmentProcessors from SkSL code. As the shader code produced from the SkSL depends + * upon the inputs to the SkSL (static if's, etc.) we first create a factory for a given SkSL + * string, then use that to create the actual GrFragmentProcessor. + */ +class GrSkSLFPFactory : public SkNVRefCnt { +public: + /** + * Constructs a GrSkSLFPFactory for a given SkSL source string. Creating a factory will + * preprocess the SkSL and determine which of its inputs are declared "key" (meaning they cause + * the produced shaders to differ), so it is important to reuse the same factory instance for + * the same shader in order to avoid repeatedly re-parsing the SkSL. + */ + GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl, + SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind); + + const SkSL::Program* getSpecialization(const SkSL::String& key, const void* inputs, + size_t inputSize); + + SkSL::Program::Kind fKind; + + const char* fName; + + SkSL::Compiler fCompiler; + + std::shared_ptr fBaseProgram; + + std::vector fInAndUniformVars; + + std::unordered_map> fSpecializations; + + friend class GrSkSLFP; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrTextureDomain.cpp b/gfx/skia/skia/src/gpu/effects/GrTextureDomain.cpp new file mode 100644 index 0000000000..25b5bb3d6b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrTextureDomain.cpp @@ -0,0 +1,507 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrTextureDomain.h" + +#include "include/gpu/GrTexture.h" +#include "include/private/SkFloatingPoint.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +#include + +GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mode modeX, + Mode modeY, int index) + : fModeX(modeX) + , fModeY(modeY) + , fIndex(index) { + + if (!proxy) { + SkASSERT(modeX == kIgnore_Mode && modeY == kIgnore_Mode); + return; + } + + const SkRect kFullRect = SkRect::MakeIWH(proxy->width(), proxy->height()); + + // We don't currently handle domains that are empty or don't intersect the texture. + // It is OK if the domain rect is a line or point, but it should not be inverted. We do not + // handle rects that do not intersect the [0..1]x[0..1] rect. + SkASSERT(domain.fLeft <= domain.fRight); + SkASSERT(domain.fTop <= domain.fBottom); + fDomain.fLeft = SkScalarPin(domain.fLeft, 0.0f, kFullRect.fRight); + fDomain.fRight = SkScalarPin(domain.fRight, fDomain.fLeft, kFullRect.fRight); + fDomain.fTop = SkScalarPin(domain.fTop, 0.0f, kFullRect.fBottom); + fDomain.fBottom = SkScalarPin(domain.fBottom, fDomain.fTop, kFullRect.fBottom); + SkASSERT(fDomain.fLeft <= fDomain.fRight); + SkASSERT(fDomain.fTop <= fDomain.fBottom); +} + +////////////////////////////////////////////////////////////////////////////// + +static SkString clamp_expression(GrTextureDomain::Mode mode, const char* inCoord, + const char* coordSwizzle, const char* domain, + const char* minSwizzle, const char* maxSwizzle) { + SkString clampedExpr; + switch(mode) { + case GrTextureDomain::kIgnore_Mode: + clampedExpr.printf("%s.%s\n", inCoord, coordSwizzle); + break; + case GrTextureDomain::kDecal_Mode: + // The lookup coordinate to use for decal will be clamped just like kClamp_Mode, + // it's just that the post-processing will be different, so fall through + case GrTextureDomain::kClamp_Mode: + clampedExpr.printf("clamp(%s.%s, %s.%s, %s.%s)", + inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle); + break; + case GrTextureDomain::kRepeat_Mode: + clampedExpr.printf("mod(%s.%s - %s.%s, %s.%s - %s.%s) + %s.%s", + inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle, + domain, minSwizzle, domain, minSwizzle); + break; + default: + SkASSERTF(false, "Unknown texture domain mode: %u\n", (uint32_t) mode); + break; + } + return clampedExpr; +} + +void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* shaderCaps, + const GrTextureDomain& textureDomain, + const char* outColor, + const SkString& inCoords, + GrGLSLFragmentProcessor::SamplerHandle sampler, + const char* inModulateColor) { + SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY)); + SkDEBUGCODE(fModeX = textureDomain.modeX();) + SkDEBUGCODE(fModeY = textureDomain.modeY();) + SkDEBUGCODE(fHasMode = true;) + + if ((textureDomain.modeX() != kIgnore_Mode || textureDomain.modeY() != kIgnore_Mode) && + !fDomainUni.isValid()) { + // Must include the domain uniform since at least one axis uses it + const char* name; + SkString uniName("TexDom"); + if (textureDomain.fIndex >= 0) { + uniName.appendS32(textureDomain.fIndex); + } + fDomainUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, + uniName.c_str(), &name); + fDomainName = name; + } + + bool decalX = textureDomain.modeX() == kDecal_Mode; + bool decalY = textureDomain.modeY() == kDecal_Mode; + if ((decalX || decalY) && !fDecalUni.isValid()) { + const char* name; + SkString uniName("DecalParams"); + if (textureDomain.fIndex >= 0) { + uniName.appendS32(textureDomain.fIndex); + } + // Half3 since this will hold texture width, height, and then a step function control param + fDecalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, + uniName.c_str(), &name); + fDecalName = name; + } + + // Add a block so that we can declare variables + GrGLSLShaderBuilder::ShaderBlock block(builder); + // Always use a local variable for the input coordinates; often callers pass in an expression + // and we want to cache it across all of its references in the code below + builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str()); + builder->codeAppend("float2 clampedCoord = "); + if (textureDomain.modeX() != textureDomain.modeY()) { + // The wrap modes differ on the two axes, so build up a coordinate that respects each axis' + // domain rule independently before sampling the texture. + SkString tcX = clamp_expression(textureDomain.modeX(), "origCoord", "x", + fDomainName.c_str(), "x", "z"); + SkString tcY = clamp_expression(textureDomain.modeY(), "origCoord", "y", + fDomainName.c_str(), "y", "w"); + builder->codeAppendf("float2(%s, %s)", tcX.c_str(), tcY.c_str()); + } else { + // Since the x and y axis wrap modes are the same, they can be calculated together using + // more efficient vector operations + SkString tc = clamp_expression(textureDomain.modeX(), "origCoord", "xy", + fDomainName.c_str(), "xy", "zw"); + builder->codeAppend(tc.c_str()); + } + builder->codeAppend(";"); + + // Look up the texture sample at the clamped coordinate location + builder->codeAppend("half4 inside = "); + builder->appendTextureLookupAndModulate(inModulateColor, sampler, "clampedCoord", + kFloat2_GrSLType); + builder->codeAppend(";"); + + // Apply decal mode's transparency interpolation if needed + if (decalX || decalY) { + // The decal err is the max absoluate value between the clamped coordinate and the original + // pixel coordinate. This will then be clamped to 1.f if it's greater than the control + // parameter, which simulates kNearest and kBilerp behavior depending on if it's 0 or 1. + if (decalX && decalY) { + builder->codeAppendf("half err = max(half(abs(clampedCoord.x - origCoord.x) * %s.x), " + "half(abs(clampedCoord.y - origCoord.y) * %s.y));", + fDecalName.c_str(), fDecalName.c_str()); + } else if (decalX) { + builder->codeAppendf("half err = half(abs(clampedCoord.x - origCoord.x) * %s.x);", + fDecalName.c_str()); + } else { + SkASSERT(decalY); + builder->codeAppendf("half err = half(abs(clampedCoord.y - origCoord.y) * %s.y);", + fDecalName.c_str()); + } + + // Apply a transform to the error rate, which let's us simulate nearest or bilerp filtering + // in the same shader. When the texture is nearest filtered, fSizeName.z is set to 1/2 so + // this becomes a step function centered at .5 away from the clamped coordinate (but the + // domain for decal is inset by .5 so the edge lines up properly). When bilerp, fSizeName.z + // is set to 1 and it becomes a simple linear blend between texture and transparent. + builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }", + fDecalName.c_str(), fDecalName.c_str()); + builder->codeAppendf("%s = mix(inside, half4(0, 0, 0, 0), err);", outColor); + } else { + // A simple look up + builder->codeAppendf("%s = inside;", outColor); + } +} + +void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman, + const GrTextureDomain& textureDomain, + GrTextureProxy* proxy, + const GrSamplerState& sampler) { + GrTexture* tex = proxy->peekTexture(); + SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY); + if (kIgnore_Mode != textureDomain.modeX() || kIgnore_Mode != textureDomain.modeY()) { + bool sendDecalData = textureDomain.modeX() == kDecal_Mode || + textureDomain.modeY() == kDecal_Mode; + + // If the texture is using nearest filtering, then the decal filter weight should step from + // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other + // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the + // texture and transparent. + SkScalar decalFilterWeight = sampler.filter() == GrSamplerState::Filter::kNearest ? + SK_ScalarHalf : 1.0f; + SkScalar wInv, hInv, h; + if (proxy->textureType() == GrTextureType::kRectangle) { + wInv = hInv = 1.f; + h = tex->height(); + + // Don't do any scaling by texture size for decal filter rate, it's already in pixels + if (sendDecalData) { + pdman.set3f(fDecalUni, 1.f, 1.f, decalFilterWeight); + } + } else { + wInv = SK_Scalar1 / tex->width(); + hInv = SK_Scalar1 / tex->height(); + h = 1.f; + + if (sendDecalData) { + pdman.set3f(fDecalUni, tex->width(), tex->height(), decalFilterWeight); + } + } + + float values[kPrevDomainCount] = { + SkScalarToFloat(textureDomain.domain().fLeft * wInv), + SkScalarToFloat(textureDomain.domain().fTop * hInv), + SkScalarToFloat(textureDomain.domain().fRight * wInv), + SkScalarToFloat(textureDomain.domain().fBottom * hInv) + }; + + if (proxy->textureType() == GrTextureType::kRectangle) { + SkASSERT(values[0] >= 0.0f && values[0] <= proxy->width()); + SkASSERT(values[1] >= 0.0f && values[1] <= proxy->height()); + SkASSERT(values[2] >= 0.0f && values[2] <= proxy->width()); + SkASSERT(values[3] >= 0.0f && values[3] <= proxy->height()); + } else { + SkASSERT(values[0] >= 0.0f && values[0] <= 1.0f); + SkASSERT(values[1] >= 0.0f && values[1] <= 1.0f); + SkASSERT(values[2] >= 0.0f && values[2] <= 1.0f); + SkASSERT(values[3] >= 0.0f && values[3] <= 1.0f); + } + + // vertical flip if necessary + if (kBottomLeft_GrSurfaceOrigin == proxy->origin()) { + values[1] = h - values[1]; + values[3] = h - values[3]; + + // The top and bottom were just flipped, so correct the ordering + // of elements so that values = (l, t, r, b). + using std::swap; + swap(values[1], values[3]); + } + if (0 != memcmp(values, fPrevDomain, kPrevDomainCount * sizeof(float))) { + pdman.set4fv(fDomainUni, 1, values); + memcpy(fPrevDomain, values, kPrevDomainCount * sizeof(float)); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrTextureDomainEffect::Make( + sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const SkRect& domain, + GrTextureDomain::Mode mode, + GrSamplerState::Filter filterMode) { + return Make(std::move(proxy), srcColorType, matrix, domain, mode, mode, + GrSamplerState(GrSamplerState::WrapMode::kClamp, filterMode)); +} + +std::unique_ptr GrTextureDomainEffect::Make( + sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const SkRect& domain, + GrTextureDomain::Mode modeX, + GrTextureDomain::Mode modeY, + const GrSamplerState& sampler) { + // If both domain modes happen to be ignore, it would be faster to just drop the domain logic + // entirely Technically, we could also use the simple texture effect if the domain modes agree + // with the sampler modes and the proxy is the same size as the domain. It's a lot easier for + // calling code to detect these cases and handle it themselves. + return std::unique_ptr(new GrTextureDomainEffect( + std::move(proxy), srcColorType, matrix, domain, modeX, modeY, sampler)); +} + +GrTextureDomainEffect::GrTextureDomainEffect(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const SkRect& domain, + GrTextureDomain::Mode modeX, + GrTextureDomain::Mode modeY, + const GrSamplerState& sampler) + : INHERITED(kGrTextureDomainEffect_ClassID, + ModulateForSamplerOptFlags(srcColorType, + GrTextureDomain::IsDecalSampled(sampler, modeX, modeY))) + , fCoordTransform(matrix, proxy.get()) + , fTextureDomain(proxy.get(), domain, modeX, modeY) + , fTextureSampler(std::move(proxy), sampler) { + SkASSERT((modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode) || + sampler.filter() == GrSamplerState::Filter::kNearest); + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); +} + +GrTextureDomainEffect::GrTextureDomainEffect(const GrTextureDomainEffect& that) + : INHERITED(kGrTextureDomainEffect_ClassID, that.optimizationFlags()) + , fCoordTransform(that.fCoordTransform) + , fTextureDomain(that.fTextureDomain) + , fTextureSampler(that.fTextureSampler) { + this->addCoordTransform(&fCoordTransform); + this->setTextureSamplerCnt(1); +} + +void GrTextureDomainEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32(GrTextureDomain::GLDomain::DomainKey(fTextureDomain)); +} + +GrGLSLFragmentProcessor* GrTextureDomainEffect::onCreateGLSLInstance() const { + class GLSLProcessor : public GrGLSLFragmentProcessor { + public: + void emitCode(EmitArgs& args) override { + const GrTextureDomainEffect& tde = args.fFp.cast(); + const GrTextureDomain& domain = tde.fTextureDomain; + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2D = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + + fGLDomain.sampleTexture(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + domain, + args.fOutputColor, + coords2D, + args.fTexSamplers[0], + args.fInputColor); + } + + protected: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& fp) override { + const GrTextureDomainEffect& tde = fp.cast(); + const GrTextureDomain& domain = tde.fTextureDomain; + GrTextureProxy* proxy = tde.textureSampler(0).proxy(); + + fGLDomain.setData(pdman, domain, proxy, tde.textureSampler(0).samplerState()); + } + + private: + GrTextureDomain::GLDomain fGLDomain; + }; + + return new GLSLProcessor; +} + +bool GrTextureDomainEffect::onIsEqual(const GrFragmentProcessor& sBase) const { + const GrTextureDomainEffect& s = sBase.cast(); + return this->fTextureDomain == s.fTextureDomain; +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureDomainEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrTextureDomainEffect::TestCreate(GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + SkRect domain; + domain.fLeft = d->fRandom->nextRangeScalar(0, proxy->width()); + domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, proxy->width()); + domain.fTop = d->fRandom->nextRangeScalar(0, proxy->height()); + domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, proxy->height()); + GrTextureDomain::Mode modeX = + (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount); + GrTextureDomain::Mode modeY = + (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount); + const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom); + bool bilerp = modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode ? + d->fRandom->nextBool() : false; + return GrTextureDomainEffect::Make( + std::move(proxy), + d->textureProxyColorType(texIdx), + matrix, + domain, + modeX, + modeY, + GrSamplerState(GrSamplerState::WrapMode::kClamp, bilerp ? + GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest)); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +std::unique_ptr GrDeviceSpaceTextureDecalFragmentProcessor::Make( + sk_sp proxy, const SkIRect& subset, const SkIPoint& deviceSpaceOffset) { + return std::unique_ptr(new GrDeviceSpaceTextureDecalFragmentProcessor( + std::move(proxy), subset, deviceSpaceOffset)); +} + +GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor( + sk_sp proxy, const SkIRect& subset, const SkIPoint& deviceSpaceOffset) + : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID, + kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fTextureSampler(proxy, GrSamplerState::ClampNearest()) + , fTextureDomain(proxy.get(), + GrTextureDomain::MakeTexelDomain(subset, GrTextureDomain::kDecal_Mode), + GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode) { + this->setTextureSamplerCnt(1); + fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft; + fDeviceSpaceOffset.fY = deviceSpaceOffset.fY - subset.fTop; +} + +GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor( + const GrDeviceSpaceTextureDecalFragmentProcessor& that) + : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID, + kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , fTextureSampler(that.fTextureSampler) + , fTextureDomain(that.fTextureDomain) + , fDeviceSpaceOffset(that.fDeviceSpaceOffset) { + this->setTextureSamplerCnt(1); +} + +std::unique_ptr GrDeviceSpaceTextureDecalFragmentProcessor::clone() const { + return std::unique_ptr( + new GrDeviceSpaceTextureDecalFragmentProcessor(*this)); +} + +GrGLSLFragmentProcessor* GrDeviceSpaceTextureDecalFragmentProcessor::onCreateGLSLInstance() const { + class GLSLProcessor : public GrGLSLFragmentProcessor { + public: + void emitCode(EmitArgs& args) override { + const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp = + args.fFp.cast(); + const char* scaleAndTranslateName; + fScaleAndTranslateUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, + "scaleAndTranslate", + &scaleAndTranslateName); + args.fFragBuilder->codeAppendf("half2 coords = half2(sk_FragCoord.xy * %s.xy + %s.zw);", + scaleAndTranslateName, scaleAndTranslateName); + fGLDomain.sampleTexture(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + dstdfp.fTextureDomain, + args.fOutputColor, + SkString("coords"), + args.fTexSamplers[0], + args.fInputColor); + } + + protected: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& fp) override { + const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp = + fp.cast(); + GrTextureProxy* proxy = dstdfp.textureSampler(0).proxy(); + GrTexture* texture = proxy->peekTexture(); + + fGLDomain.setData(pdman, dstdfp.fTextureDomain, proxy, + dstdfp.textureSampler(0).samplerState()); + float iw = 1.f / texture->width(); + float ih = 1.f / texture->height(); + float scaleAndTransData[4] = { + iw, ih, + -dstdfp.fDeviceSpaceOffset.fX * iw, -dstdfp.fDeviceSpaceOffset.fY * ih + }; + if (proxy->origin() == kBottomLeft_GrSurfaceOrigin) { + scaleAndTransData[1] = -scaleAndTransData[1]; + scaleAndTransData[3] = 1 - scaleAndTransData[3]; + } + pdman.set4fv(fScaleAndTranslateUni, 1, scaleAndTransData); + } + + private: + GrTextureDomain::GLDomain fGLDomain; + UniformHandle fScaleAndTranslateUni; + }; + + return new GLSLProcessor; +} + +bool GrDeviceSpaceTextureDecalFragmentProcessor::onIsEqual(const GrFragmentProcessor& fp) const { + const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp = + fp.cast(); + return dstdfp.fTextureSampler.proxy()->underlyingUniqueID() == + fTextureSampler.proxy()->underlyingUniqueID() && + dstdfp.fDeviceSpaceOffset == fDeviceSpaceOffset && + dstdfp.fTextureDomain == fTextureDomain; +} + +/////////////////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDeviceSpaceTextureDecalFragmentProcessor); + +#if GR_TEST_UTILS +std::unique_ptr GrDeviceSpaceTextureDecalFragmentProcessor::TestCreate( + GrProcessorTestData* d) { + int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + sk_sp proxy = d->textureProxy(texIdx); + SkIRect subset; + subset.fLeft = d->fRandom->nextULessThan(proxy->width() - 1); + subset.fRight = d->fRandom->nextRangeU(subset.fLeft, proxy->width()); + subset.fTop = d->fRandom->nextULessThan(proxy->height() - 1); + subset.fBottom = d->fRandom->nextRangeU(subset.fTop, proxy->height()); + SkIPoint pt; + pt.fX = d->fRandom->nextULessThan(2048); + pt.fY = d->fRandom->nextULessThan(2048); + return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(proxy), subset, pt); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrTextureDomain.h b/gfx/skia/skia/src/gpu/effects/GrTextureDomain.h new file mode 100644 index 0000000000..96c2d2019b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrTextureDomain.h @@ -0,0 +1,294 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextureDomainEffect_DEFINED +#define GrTextureDomainEffect_DEFINED + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" + +class GrGLProgramBuilder; +class GrGLSLShaderBuilder; +class GrInvariantOutput; +class GrGLSLUniformHandler; +struct SkRect; + +/** + * Limits a texture's lookup coordinates to a domain. Samples outside the domain are either clamped + * the edge of the domain or result in a half4 of zeros (decal mode). The domain is clipped to + * normalized texture coords ([0,1]x[0,1] square). Bilinear filtering can cause texels outside the + * domain to affect the read value unless the caller considers this when calculating the domain. + */ +class GrTextureDomain { +public: + enum Mode { + // Ignore the texture domain rectangle. + kIgnore_Mode, + // Clamp texture coords to the domain rectangle. + kClamp_Mode, + // Treat the area outside the domain rectangle as fully transparent. + kDecal_Mode, + // Wrap texture coordinates. NOTE: filtering may not work as expected because Bilerp will + // read texels outside of the domain. We could perform additional texture reads and filter + // in the shader, but are not currently doing this for performance reasons + kRepeat_Mode, + + kLastMode = kRepeat_Mode + }; + static const int kModeCount = kLastMode + 1; + + static const GrTextureDomain& IgnoredDomain() { + static const GrTextureDomain gDomain((GrTextureProxy*)nullptr, + SkRect::MakeEmpty(), kIgnore_Mode, kIgnore_Mode); + return gDomain; + } + + /** + * @param index Pass a value >= 0 if using multiple texture domains in the same effect. + * It is used to keep inserted variables from causing name collisions. + */ + GrTextureDomain(GrTextureProxy*, const SkRect& domain, Mode modeX, Mode modeY, int index = -1); + + GrTextureDomain(const GrTextureDomain&) = default; + + const SkRect& domain() const { return fDomain; } + Mode modeX() const { return fModeX; } + Mode modeY() const { return fModeY; } + + /* + * Computes a domain that bounds all the texels in texelRect, possibly insetting by half a pixel + * depending on the mode. The mode is used for both axes. + */ + static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode mode) { + return MakeTexelDomain(texelRect, mode, mode); + } + + static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode modeX, Mode modeY) { + // For Clamp and decal modes, inset by half a texel + SkScalar insetX = ((modeX == kClamp_Mode || modeX == kDecal_Mode) && texelRect.width() > 0) + ? SK_ScalarHalf : 0; + SkScalar insetY = ((modeY == kClamp_Mode || modeY == kDecal_Mode) && texelRect.height() > 0) + ? SK_ScalarHalf : 0; + return SkRect::MakeLTRB(texelRect.fLeft + insetX, texelRect.fTop + insetY, + texelRect.fRight - insetX, texelRect.fBottom - insetY); + } + + // Convenience to determine if any axis of a texture uses an explicit decal mode or the hardware + // clamp to border decal mode. + static bool IsDecalSampled(GrSamplerState::WrapMode wrapX, GrSamplerState::WrapMode wrapY, + Mode modeX, Mode modeY) { + return wrapX == GrSamplerState::WrapMode::kClampToBorder || + wrapY == GrSamplerState::WrapMode::kClampToBorder || + modeX == kDecal_Mode || + modeY == kDecal_Mode; + } + + static bool IsDecalSampled(const GrSamplerState::WrapMode wraps[2], Mode modeX, Mode modeY) { + return IsDecalSampled(wraps[0], wraps[1], modeX, modeY); + } + + static bool IsDecalSampled(const GrSamplerState& sampler, Mode modeX, Mode modeY) { + return IsDecalSampled(sampler.wrapModeX(), sampler.wrapModeY(), modeX, modeY); + } + + bool operator==(const GrTextureDomain& that) const { + return fModeX == that.fModeX && fModeY == that.fModeY && + (kIgnore_Mode == fModeX || (fDomain.fLeft == that.fDomain.fLeft && + fDomain.fRight == that.fDomain.fRight)) && + (kIgnore_Mode == fModeY || (fDomain.fTop == that.fDomain.fTop && + fDomain.fBottom == that.fDomain.fBottom)); + } + + /** + * A GrGLSLFragmentProcessor subclass that corresponds to a GrProcessor subclass that uses + * GrTextureDomain should include this helper. It generates the texture domain GLSL, produces + * the part of the effect key that reflects the texture domain code, and performs the uniform + * uploads necessary for texture domains. + */ + class GLDomain { + public: + GLDomain() { + for (int i = 0; i < kPrevDomainCount; i++) { + fPrevDomain[i] = SK_FloatNaN; + } + } + + /** + * Call this from GrGLSLFragmentProcessor::emitCode() to sample the texture W.R.T. the + * domain and mode. + * + * @param outcolor name of half4 variable to hold the sampled color. + * @param inCoords name of float2 variable containing the coords to be used with the domain. + * It is assumed that this is a variable and not an expression. + * @param inModulateColor if non-nullptr the sampled color will be modulated with this + * expression before being written to outColor. + */ + void sampleTexture(GrGLSLShaderBuilder* builder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* shaderCaps, + const GrTextureDomain& textureDomain, + const char* outColor, + const SkString& inCoords, + GrGLSLFragmentProcessor::SamplerHandle sampler, + const char* inModulateColor = nullptr); + + /** + * Call this from GrGLSLFragmentProcessor::setData() to upload uniforms necessary for the + * texture domain. The rectangle is automatically adjusted to account for the texture's + * origin. + */ + void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrTextureProxy*, + const GrSamplerState& sampler); + + enum { + kModeBits = 2, // See DomainKey(). + kDomainKeyBits = 4 + }; + + /** + * GrGLSLFragmentProcessor::GenKey() must call this and include the returned value in it's + * computed key. The returned will be limited to the lower kDomainKeyBits bits. + */ + static uint32_t DomainKey(const GrTextureDomain& domain) { + GR_STATIC_ASSERT(kModeCount <= (1 << kModeBits)); + return domain.modeX() | (domain.modeY() << kModeBits); + } + + private: + static const int kPrevDomainCount = 4; + SkDEBUGCODE(Mode fModeX;) + SkDEBUGCODE(Mode fModeY;) + SkDEBUGCODE(bool fHasMode = false;) + GrGLSLProgramDataManager::UniformHandle fDomainUni; + SkString fDomainName; + + // Only initialized if the domain has at least one decal axis + GrGLSLProgramDataManager::UniformHandle fDecalUni; + SkString fDecalName; + + float fPrevDomain[kPrevDomainCount]; + }; + +protected: + Mode fModeX; + Mode fModeY; + SkRect fDomain; + int fIndex; +}; + +/** + * A basic texture effect that uses GrTextureDomain. + */ +class GrTextureDomainEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp, + GrColorType srcColorType, + const SkMatrix&, + const SkRect& domain, + GrTextureDomain::Mode mode, + GrSamplerState::Filter filterMode); + + static std::unique_ptr Make(sk_sp, + GrColorType srcColorType, + const SkMatrix&, + const SkRect& domain, + GrTextureDomain::Mode modeX, + GrTextureDomain::Mode modeY, + const GrSamplerState& sampler); + + const char* name() const override { return "TextureDomain"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrTextureDomainEffect(*this)); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + str.appendf("Domain: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]", + fTextureDomain.domain().fLeft, fTextureDomain.domain().fTop, + fTextureDomain.domain().fRight, fTextureDomain.domain().fBottom); + str.append(INHERITED::dumpInfo()); + return str; + } +#endif + +private: + GrCoordTransform fCoordTransform; + GrTextureDomain fTextureDomain; + TextureSampler fTextureSampler; + + GrTextureDomainEffect(sk_sp, + GrColorType srcColorType, + const SkMatrix&, + const SkRect& domain, + GrTextureDomain::Mode modeX, + GrTextureDomain::Mode modeY, + const GrSamplerState&); + + explicit GrTextureDomainEffect(const GrTextureDomainEffect&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +class GrDeviceSpaceTextureDecalFragmentProcessor : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp, + const SkIRect& subset, + const SkIPoint& deviceSpaceOffset); + + const char* name() const override { return "GrDeviceSpaceTextureDecalFragmentProcessor"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + str.appendf("Domain: [L: %.2f, T: %.2f, R: %.2f, B: %.2f] Offset: [%d %d]", + fTextureDomain.domain().fLeft, fTextureDomain.domain().fTop, + fTextureDomain.domain().fRight, fTextureDomain.domain().fBottom, + fDeviceSpaceOffset.fX, fDeviceSpaceOffset.fY); + str.append(INHERITED::dumpInfo()); + return str; + } +#endif + + std::unique_ptr clone() const override; + +private: + TextureSampler fTextureSampler; + GrTextureDomain fTextureDomain; + SkIPoint fDeviceSpaceOffset; + + GrDeviceSpaceTextureDecalFragmentProcessor(sk_sp, + const SkIRect&, const SkIPoint&); + GrDeviceSpaceTextureDecalFragmentProcessor(const GrDeviceSpaceTextureDecalFragmentProcessor&); + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + // Since we always use decal mode, there is no need for key data. + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {} + + bool onIsEqual(const GrFragmentProcessor& fp) const override; + + const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.cpp new file mode 100644 index 0000000000..d21b4987a4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.cpp @@ -0,0 +1,524 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" + +#include "src/core/SkXfermodePriv.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/glsl/GrGLSLBlend.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" + +// Some of the cpu implementations of blend modes differ too much from the GPU enough that +// we can't use the cpu implementation to implement constantOutputForConstantInput. +static inline bool does_cpu_blend_impl_match_gpu(SkBlendMode mode) { + // The non-seperable modes differ too much. So does SoftLight. ColorBurn differs too much on our + // test iOS device (but we just disable it across the aboard since it may happen on untested + // GPUs). + return mode <= SkBlendMode::kLastSeparableMode && mode != SkBlendMode::kSoftLight && + mode != SkBlendMode::kColorBurn; +} + +////////////////////////////////////////////////////////////////////////////// + +class ComposeTwoFragmentProcessor : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr src, + std::unique_ptr dst, + SkBlendMode mode) { + return std::unique_ptr( + new ComposeTwoFragmentProcessor(std::move(src), std::move(dst), mode)); + } + + const char* name() const override { return "ComposeTwo"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + + str.appendf("Mode: %s", SkBlendMode_Name(fMode)); + + for (int i = 0; i < this->numChildProcessors(); ++i) { + str.appendf(" [%s %s]", + this->childProcessor(i).name(), this->childProcessor(i).dumpInfo().c_str()); + } + return str; + } +#endif + + std::unique_ptr clone() const override; + + SkBlendMode getMode() const { return fMode; } + +private: + ComposeTwoFragmentProcessor(std::unique_ptr src, + std::unique_ptr dst, + SkBlendMode mode) + : INHERITED(kComposeTwoFragmentProcessor_ClassID, OptFlags(src.get(), dst.get(), mode)) + , fMode(mode) { + SkDEBUGCODE(int shaderAChildIndex = )this->registerChildProcessor(std::move(src)); + SkDEBUGCODE(int shaderBChildIndex = )this->registerChildProcessor(std::move(dst)); + SkASSERT(0 == shaderAChildIndex); + SkASSERT(1 == shaderBChildIndex); + } + + static OptimizationFlags OptFlags(const GrFragmentProcessor* src, + const GrFragmentProcessor* dst, SkBlendMode mode) { + OptimizationFlags flags; + switch (mode) { + case SkBlendMode::kClear: + case SkBlendMode::kSrc: + case SkBlendMode::kDst: + SK_ABORT("Should never create clear, src, or dst compose two FP."); + flags = kNone_OptimizationFlags; + break; + + // Produces opaque if both src and dst are opaque. + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: + case SkBlendMode::kModulate: + flags = src->preservesOpaqueInput() && dst->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // Produces zero when both are opaque, indeterminate if one is opaque. + case SkBlendMode::kSrcOut: + case SkBlendMode::kDstOut: + case SkBlendMode::kXor: + flags = kNone_OptimizationFlags; + break; + + // Is opaque if the dst is opaque. + case SkBlendMode::kSrcATop: + flags = dst->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque. + case SkBlendMode::kDstATop: + case SkBlendMode::kScreen: + flags = src->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // These modes are all opaque if either src or dst is opaque. All the advanced modes + // compute alpha as src-over. + case SkBlendMode::kSrcOver: + case SkBlendMode::kDstOver: + case SkBlendMode::kPlus: + case SkBlendMode::kOverlay: + case SkBlendMode::kDarken: + case SkBlendMode::kLighten: + case SkBlendMode::kColorDodge: + case SkBlendMode::kColorBurn: + case SkBlendMode::kHardLight: + case SkBlendMode::kSoftLight: + case SkBlendMode::kDifference: + case SkBlendMode::kExclusion: + case SkBlendMode::kMultiply: + case SkBlendMode::kHue: + case SkBlendMode::kSaturation: + case SkBlendMode::kColor: + case SkBlendMode::kLuminosity: + flags = src->preservesOpaqueInput() || dst->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + } + if (does_cpu_blend_impl_match_gpu(mode) && src->hasConstantOutputForConstantInput() && + dst->hasConstantOutputForConstantInput()) { + flags |= kConstantOutputForConstantInput_OptimizationFlag; + } + return flags; + } + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + b->add32((int)fMode); + } + + bool onIsEqual(const GrFragmentProcessor& other) const override { + const ComposeTwoFragmentProcessor& cs = other.cast(); + return fMode == cs.fMode; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + SkPMColor4f opaqueInput = { input.fR, input.fG, input.fB, 1 }; + SkPMColor4f src = ConstantOutputForConstantInput(this->childProcessor(0), opaqueInput); + SkPMColor4f dst = ConstantOutputForConstantInput(this->childProcessor(1), opaqueInput); + SkPMColor4f res = SkBlendMode_Apply(fMode, src, dst); + return res * input.fA; + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + SkBlendMode fMode; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GLComposeTwoFragmentProcessor : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + +private: + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ComposeTwoFragmentProcessor); + +#if GR_TEST_UTILS +std::unique_ptr ComposeTwoFragmentProcessor::TestCreate( + GrProcessorTestData* d) { + // Create two random frag procs. + std::unique_ptr fpA(GrProcessorUnitTest::MakeChildFP(d)); + std::unique_ptr fpB(GrProcessorUnitTest::MakeChildFP(d)); + + SkBlendMode mode; + do { + mode = static_cast(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); + } while (SkBlendMode::kClear == mode || SkBlendMode::kSrc == mode || SkBlendMode::kDst == mode); + return std::unique_ptr( + new ComposeTwoFragmentProcessor(std::move(fpA), std::move(fpB), mode)); +} +#endif + +std::unique_ptr ComposeTwoFragmentProcessor::clone() const { + auto src = this->childProcessor(0).clone(); + auto dst = this->childProcessor(1).clone(); + return std::unique_ptr( + new ComposeTwoFragmentProcessor(std::move(src), std::move(dst), fMode)); +} + +GrGLSLFragmentProcessor* ComposeTwoFragmentProcessor::onCreateGLSLInstance() const{ + return new GLComposeTwoFragmentProcessor; +} + +///////////////////////////////////////////////////////////////////// + +void GLComposeTwoFragmentProcessor::emitCode(EmitArgs& args) { + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const ComposeTwoFragmentProcessor& cs = args.fFp.cast(); + + const char* inputColor = nullptr; + if (args.fInputColor) { + inputColor = "inputColor"; + fragBuilder->codeAppendf("half4 inputColor = half4(%s.rgb, 1.0);", args.fInputColor); + } + + // declare outputColor and emit the code for each of the two children + SkString srcColor("xfer_src"); + this->invokeChild(0, inputColor, &srcColor, args); + + SkString dstColor("xfer_dst"); + this->invokeChild(1, inputColor, &dstColor, args); + + // emit blend code + SkBlendMode mode = cs.getMode(); + fragBuilder->codeAppendf("// Compose Xfer Mode: %s\n", SkBlendMode_Name(mode)); + GrGLSLBlend::AppendMode(fragBuilder, + srcColor.c_str(), + dstColor.c_str(), + args.fOutputColor, + mode); + + // re-multiply the output color by the input color's alpha + if (args.fInputColor) { + fragBuilder->codeAppendf("%s *= %s.a;", args.fOutputColor, args.fInputColor); + } +} + +std::unique_ptr GrXfermodeFragmentProcessor::MakeFromTwoProcessors( + std::unique_ptr src, + std::unique_ptr dst, + SkBlendMode mode) { + switch (mode) { + case SkBlendMode::kClear: + return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kIgnore); + case SkBlendMode::kSrc: + return src; + case SkBlendMode::kDst: + return dst; + default: + return ComposeTwoFragmentProcessor::Make(std::move(src), std::move(dst), mode); + } +} + +////////////////////////////////////////////////////////////////////////////// + +class ComposeOneFragmentProcessor : public GrFragmentProcessor { +public: + enum Child { + kDst_Child, + kSrc_Child, + }; + + static std::unique_ptr Make(std::unique_ptr fp, + SkBlendMode mode, Child child) { + if (!fp) { + return nullptr; + } + return std::unique_ptr( + new ComposeOneFragmentProcessor(std::move(fp), mode, child)); + } + + const char* name() const override { return "ComposeOne"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + + str.appendf("Mode: %s, Child: %s", + SkBlendMode_Name(fMode), kDst_Child == fChild ? "Dst" : "Src"); + + for (int i = 0; i < this->numChildProcessors(); ++i) { + str.appendf(" [%s %s]", + this->childProcessor(i).name(), this->childProcessor(i).dumpInfo().c_str()); + } + return str; + } +#endif + + std::unique_ptr clone() const override; + + SkBlendMode mode() const { return fMode; } + + Child child() const { return fChild; } + +private: + OptimizationFlags OptFlags(const GrFragmentProcessor* fp, SkBlendMode mode, Child child) { + OptimizationFlags flags; + switch (mode) { + case SkBlendMode::kClear: + SK_ABORT("Should never create clear compose one FP."); + flags = kNone_OptimizationFlags; + break; + + case SkBlendMode::kSrc: + SkASSERT(child == kSrc_Child); + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + case SkBlendMode::kDst: + SkASSERT(child == kDst_Child); + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // Produces opaque if both src and dst are opaque. These also will modulate the child's + // output by either the input color or alpha. However, if the child is not compatible + // with the coverage as alpha then it may produce a color that is not valid premul. + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: + case SkBlendMode::kModulate: + flags = ProcessorOptimizationFlags(fp) & + ~kConstantOutputForConstantInput_OptimizationFlag; + break; + + // Produces zero when both are opaque, indeterminate if one is opaque. + case SkBlendMode::kSrcOut: + case SkBlendMode::kDstOut: + case SkBlendMode::kXor: + flags = kNone_OptimizationFlags; + break; + + // Is opaque if the dst is opaque. + case SkBlendMode::kSrcATop: + if (child == kDst_Child) { + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + } else { + flags = kPreservesOpaqueInput_OptimizationFlag; + } + break; + + // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque. + case SkBlendMode::kDstATop: + case SkBlendMode::kScreen: + if (child == kSrc_Child) { + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + } else { + flags = kPreservesOpaqueInput_OptimizationFlag; + } + break; + + // These modes are all opaque if either src or dst is opaque. All the advanced modes + // compute alpha as src-over. + case SkBlendMode::kSrcOver: + case SkBlendMode::kDstOver: + case SkBlendMode::kPlus: + case SkBlendMode::kOverlay: + case SkBlendMode::kDarken: + case SkBlendMode::kLighten: + case SkBlendMode::kColorDodge: + case SkBlendMode::kColorBurn: + case SkBlendMode::kHardLight: + case SkBlendMode::kSoftLight: + case SkBlendMode::kDifference: + case SkBlendMode::kExclusion: + case SkBlendMode::kMultiply: + case SkBlendMode::kHue: + case SkBlendMode::kSaturation: + case SkBlendMode::kColor: + case SkBlendMode::kLuminosity: + flags = kPreservesOpaqueInput_OptimizationFlag; + break; + } + if (does_cpu_blend_impl_match_gpu(mode) && fp->hasConstantOutputForConstantInput()) { + flags |= kConstantOutputForConstantInput_OptimizationFlag; + } + return flags; + } + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GR_STATIC_ASSERT(((int)SkBlendMode::kLastMode & UINT16_MAX) == (int)SkBlendMode::kLastMode); + b->add32((int)fMode | (fChild << 16)); + } + + bool onIsEqual(const GrFragmentProcessor& that) const override { + return fMode == that.cast().fMode; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& inputColor) const override { + SkPMColor4f childColor = ConstantOutputForConstantInput(this->childProcessor(0), + SK_PMColor4fWHITE); + SkPMColor4f src, dst; + if (kSrc_Child == fChild) { + src = childColor; + dst = inputColor; + } else { + src = inputColor; + dst = childColor; + } + return SkBlendMode_Apply(fMode, src, dst); + } + +private: + ComposeOneFragmentProcessor(std::unique_ptr fp, SkBlendMode mode, + Child child) + : INHERITED(kComposeOneFragmentProcessor_ClassID, OptFlags(fp.get(), mode, child)) + , fMode(mode) + , fChild(child) { + SkDEBUGCODE(int dstIndex =) this->registerChildProcessor(std::move(fp)); + SkASSERT(0 == dstIndex); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + SkBlendMode fMode; + Child fChild; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + typedef GrFragmentProcessor INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +class GLComposeOneFragmentProcessor : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkBlendMode mode = args.fFp.cast().mode(); + ComposeOneFragmentProcessor::Child child = + args.fFp.cast().child(); + SkString childColor("child"); + this->invokeChild(0, &childColor, args); + + // emit blend code + fragBuilder->codeAppendf("// Compose Xfer Mode: %s\n", SkBlendMode_Name(mode)); + const char* childStr = childColor.c_str(); + if (ComposeOneFragmentProcessor::kDst_Child == child) { + GrGLSLBlend::AppendMode(fragBuilder, args.fInputColor, childStr, args.fOutputColor, mode); + } else { + GrGLSLBlend::AppendMode(fragBuilder, childStr, args.fInputColor, args.fOutputColor, mode); + } + } + +private: + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ComposeOneFragmentProcessor); + +#if GR_TEST_UTILS +std::unique_ptr ComposeOneFragmentProcessor::TestCreate( + GrProcessorTestData* d) { + // Create one random frag procs. + // For now, we'll prevent either children from being a shader with children to prevent the + // possibility of an arbitrarily large tree of procs. + std::unique_ptr dst(GrProcessorUnitTest::MakeChildFP(d)); + SkBlendMode mode; + ComposeOneFragmentProcessor::Child child; + do { + mode = static_cast(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); + child = d->fRandom->nextBool() ? kDst_Child : kSrc_Child; + } while (SkBlendMode::kClear == mode || (SkBlendMode::kDst == mode && child == kSrc_Child) || + (SkBlendMode::kSrc == mode && child == kDst_Child)); + return std::unique_ptr( + new ComposeOneFragmentProcessor(std::move(dst), mode, child)); +} +#endif + +GrGLSLFragmentProcessor* ComposeOneFragmentProcessor::onCreateGLSLInstance() const { + return new GLComposeOneFragmentProcessor; +} + +std::unique_ptr ComposeOneFragmentProcessor::clone() const { + return std::unique_ptr( + new ComposeOneFragmentProcessor(this->childProcessor(0).clone(), fMode, fChild)); +} + +////////////////////////////////////////////////////////////////////////////// + +// It may seems as though when the input FP is the dst and the mode is kDst (or same for src/kSrc) +// that these factories could simply return the input FP. However, that doesn't have quite +// the same effect as the returned compose FP will replace the FP's input with solid white and +// ignore the original input. This could be implemented as: +// RunInSeries(ConstColor(WHITE, kIgnoreInput), inputFP). + +std::unique_ptr GrXfermodeFragmentProcessor::MakeFromDstProcessor( + std::unique_ptr dst, SkBlendMode mode) { + switch (mode) { + case SkBlendMode::kClear: + return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kIgnore); + case SkBlendMode::kSrc: + return nullptr; + default: + return ComposeOneFragmentProcessor::Make(std::move(dst), mode, + ComposeOneFragmentProcessor::kDst_Child); + } +} + +std::unique_ptr GrXfermodeFragmentProcessor::MakeFromSrcProcessor( + std::unique_ptr src, SkBlendMode mode) { + switch (mode) { + case SkBlendMode::kClear: + return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kIgnore); + case SkBlendMode::kDst: + return nullptr; + default: + return ComposeOneFragmentProcessor::Make(std::move(src), mode, + ComposeOneFragmentProcessor::kSrc_Child); + } +} diff --git a/gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.h new file mode 100644 index 0000000000..0100339154 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrXfermodeFragmentProcessor.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrXfermodeFragmentProcessor_DEFINED +#define GrXfermodeFragmentProcessor_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkRefCnt.h" + +class GrFragmentProcessor; + +namespace GrXfermodeFragmentProcessor { + +/** The color input to the returned processor is treated as the src and the passed in processor is + the dst. */ +std::unique_ptr MakeFromDstProcessor(std::unique_ptr dst, + SkBlendMode mode); + +/** The color input to the returned processor is treated as the dst and the passed in processor is + the src. */ +std::unique_ptr MakeFromSrcProcessor(std::unique_ptr src, + SkBlendMode mode); + +/** Takes the input color, which is assumed to be unpremultiplied, passes it as an opaque color + to both src and dst. The outputs of a src and dst are blended using mode and the original + input's alpha is applied to the blended color to produce a premul output. */ +std::unique_ptr MakeFromTwoProcessors(std::unique_ptr src, + std::unique_ptr dst, + SkBlendMode mode); + +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp b/gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp new file mode 100644 index 0000000000..a82aec7076 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/effects/GrYUVtoRGBEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" + +static const float kJPEGConversionMatrix[16] = { + 1.0f, 0.0f, 1.402f, -0.703749f, + 1.0f, -0.344136f, -0.714136f, 0.531211f, + 1.0f, 1.772f, 0.0f, -0.889475f, + 0.0f, 0.0f, 0.0f, 1.0 +}; + +static const float kRec601ConversionMatrix[16] = { + 1.164f, 0.0f, 1.596f, -0.87075f, + 1.164f, -0.391f, -0.813f, 0.52925f, + 1.164f, 2.018f, 0.0f, -1.08175f, + 0.0f, 0.0f, 0.0f, 1.0 +}; + +static const float kRec709ConversionMatrix[16] = { + 1.164f, 0.0f, 1.793f, -0.96925f, + 1.164f, -0.213f, -0.533f, 0.30025f, + 1.164f, 2.112f, 0.0f, -1.12875f, + 0.0f, 0.0f, 0.0f, 1.0f +}; + +std::unique_ptr GrYUVtoRGBEffect::Make(const sk_sp proxies[], + const SkYUVAIndex yuvaIndices[4], + SkYUVColorSpace yuvColorSpace, + GrSamplerState::Filter filterMode, + const SkMatrix& localMatrix, + const SkRect* domain) { + int numPlanes; + SkAssertResult(SkYUVAIndex::AreValidIndices(yuvaIndices, &numPlanes)); + + const SkISize YSize = proxies[yuvaIndices[SkYUVAIndex::kY_Index].fIndex]->isize(); + + GrSamplerState::Filter minimizeFilterMode = GrSamplerState::Filter::kMipMap == filterMode ? + GrSamplerState::Filter::kMipMap : + GrSamplerState::Filter::kBilerp; + + GrSamplerState::Filter filterModes[4]; + SkSize scales[4]; + for (int i = 0; i < numPlanes; ++i) { + SkISize size = proxies[i]->isize(); + scales[i] = SkSize::Make(SkIntToScalar(size.width()) / SkIntToScalar(YSize.width()), + SkIntToScalar(size.height()) / SkIntToScalar(YSize.height())); + filterModes[i] = (size == YSize) ? filterMode : minimizeFilterMode; + } + + return std::unique_ptr(new GrYUVtoRGBEffect( + proxies, scales, filterModes, numPlanes, yuvaIndices, yuvColorSpace, localMatrix, + domain)); +} + +#ifdef SK_DEBUG +SkString GrYUVtoRGBEffect::dumpInfo() const { + SkString str; + for (int i = 0; i < this->numTextureSamplers(); ++i) { + str.appendf("%d: %d %d ", i, + this->textureSampler(i).proxy()->uniqueID().asUInt(), + this->textureSampler(i).proxy()->underlyingUniqueID().asUInt()); + } + str.appendf("\n"); + + return str; +} +#endif + +GrGLSLFragmentProcessor* GrYUVtoRGBEffect::onCreateGLSLInstance() const { + class GrGLSLYUVtoRGBEffect : public GrGLSLFragmentProcessor { + public: + GrGLSLYUVtoRGBEffect() {} + + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrYUVtoRGBEffect& _outer = args.fFp.cast(); + (void)_outer; + + if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) { + fColorSpaceMatrixVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4x4_GrSLType, + "colorSpaceMatrix"); + } + + int numSamplers = args.fTexSamplers.count(); + + SkString coords[4]; + for (int i = 0; i < numSamplers; ++i) { + coords[i] = fragBuilder->ensureCoords2D(args.fTransformedCoords[i].fVaryingPoint); + } + + for (int i = 0; i < numSamplers; ++i) { + SkString sampleVar; + sampleVar.printf("tmp%d", i); + fragBuilder->codeAppendf("half4 %s;", sampleVar.c_str()); + fGLDomains[i].sampleTexture(fragBuilder, args.fUniformHandler, args.fShaderCaps, + _outer.fDomains[i], sampleVar.c_str(), coords[i], args.fTexSamplers[i]); + } + + static const char kChannelToChar[4] = { 'x', 'y', 'z', 'w' }; + + fragBuilder->codeAppendf( + "half4 yuvOne = half4(tmp%d.%c, tmp%d.%c, tmp%d.%c, 1.0);", + _outer.yuvaIndex(0).fIndex, kChannelToChar[(int)_outer.yuvaIndex(0).fChannel], + _outer.yuvaIndex(1).fIndex, kChannelToChar[(int)_outer.yuvaIndex(1).fChannel], + _outer.yuvaIndex(2).fIndex, kChannelToChar[(int)_outer.yuvaIndex(2).fChannel]); + + if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) { + SkASSERT(fColorSpaceMatrixVar.isValid()); + fragBuilder->codeAppendf( + "yuvOne *= %s;", args.fUniformHandler->getUniformCStr(fColorSpaceMatrixVar)); + } + + if (_outer.yuvaIndex(3).fIndex >= 0) { + fragBuilder->codeAppendf( + "half a = tmp%d.%c;", _outer.yuvaIndex(3).fIndex, + kChannelToChar[(int)_outer.yuvaIndex(3).fChannel]); + // premultiply alpha + fragBuilder->codeAppend("yuvOne *= a;"); + } else { + fragBuilder->codeAppend("half a = 1.0;"); + } + + fragBuilder->codeAppendf("%s = half4(yuvOne.xyz, a);", args.fOutputColor); + } + + private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrYUVtoRGBEffect& _outer = _proc.cast(); + + switch (_outer.yuvColorSpace()) { + case kJPEG_SkYUVColorSpace: + SkASSERT(fColorSpaceMatrixVar.isValid()); + pdman.setMatrix4f(fColorSpaceMatrixVar, kJPEGConversionMatrix); + break; + case kRec601_SkYUVColorSpace: + SkASSERT(fColorSpaceMatrixVar.isValid()); + pdman.setMatrix4f(fColorSpaceMatrixVar, kRec601ConversionMatrix); + break; + case kRec709_SkYUVColorSpace: + SkASSERT(fColorSpaceMatrixVar.isValid()); + pdman.setMatrix4f(fColorSpaceMatrixVar, kRec709ConversionMatrix); + break; + case kIdentity_SkYUVColorSpace: + break; + } + + int numSamplers = _outer.numTextureSamplers(); + for (int i = 0; i < numSamplers; ++i) { + fGLDomains[i].setData(pdman, _outer.fDomains[i], + _outer.textureSampler(i).proxy(), _outer.textureSampler(i).samplerState()); + } + } + + UniformHandle fColorSpaceMatrixVar; + GrTextureDomain::GLDomain fGLDomains[4]; + }; + + return new GrGLSLYUVtoRGBEffect; +} +void GrYUVtoRGBEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + using Domain = GrTextureDomain::GLDomain; + + b->add32(this->numTextureSamplers()); + + uint32_t packed = 0; + uint32_t domain = 0; + for (int i = 0; i < 4; ++i) { + if (this->yuvaIndex(i).fIndex < 0) { + continue; + } + + uint8_t index = this->yuvaIndex(i).fIndex; + uint8_t chann = (uint8_t) this->yuvaIndex(i).fChannel; + + SkASSERT(index < 4 && chann < 4); + + packed |= (index | (chann << 2)) << (i * 4); + + domain |= Domain::DomainKey(fDomains[i]) << (i * Domain::kDomainKeyBits); + } + if (kIdentity_SkYUVColorSpace == this->yuvColorSpace()) { + packed |= 0x1 << 16; + } + + b->add32(packed); + b->add32(domain); +} +bool GrYUVtoRGBEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrYUVtoRGBEffect& that = other.cast(); + + for (int i = 0; i < 4; ++i) { + if (fYUVAIndices[i] != that.fYUVAIndices[i]) { + return false; + } + } + + for (int i = 0; i < this->numTextureSamplers(); ++i) { + // 'fSamplers' is checked by the base class + if (fSamplerTransforms[i] != that.fSamplerTransforms[i]) { + return false; + } + if (!(fDomains[i] == that.fDomains[i])) { + return false; + } + } + + if (fYUVColorSpace != that.fYUVColorSpace) { + return false; + } + + return true; +} +GrYUVtoRGBEffect::GrYUVtoRGBEffect(const GrYUVtoRGBEffect& src) + : INHERITED(kGrYUVtoRGBEffect_ClassID, src.optimizationFlags()) + , fDomains{src.fDomains[0], src.fDomains[1], src.fDomains[2], src.fDomains[3]} + , fYUVColorSpace(src.fYUVColorSpace) { + int numPlanes = src.numTextureSamplers(); + for (int i = 0; i < numPlanes; ++i) { + fSamplers[i].reset(sk_ref_sp(src.fSamplers[i].proxy()), src.fSamplers[i].samplerState()); + fSamplerTransforms[i] = src.fSamplerTransforms[i]; + fSamplerCoordTransforms[i] = src.fSamplerCoordTransforms[i]; + } + + this->setTextureSamplerCnt(numPlanes); + for (int i = 0; i < numPlanes; ++i) { + this->addCoordTransform(&fSamplerCoordTransforms[i]); + } + + memcpy(fYUVAIndices, src.fYUVAIndices, sizeof(fYUVAIndices)); +} +std::unique_ptr GrYUVtoRGBEffect::clone() const { + return std::unique_ptr(new GrYUVtoRGBEffect(*this)); +} +const GrFragmentProcessor::TextureSampler& GrYUVtoRGBEffect::onTextureSampler(int index) const { + SkASSERT(index < this->numTextureSamplers()); + return fSamplers[index]; +} diff --git a/gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.h b/gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.h new file mode 100644 index 0000000000..338ea14aa8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.h @@ -0,0 +1,96 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrYUVtoRGBEffect_DEFINED +#define GrYUVtoRGBEffect_DEFINED + +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/effects/GrTextureDomain.h" + +#include "include/core/SkYUVAIndex.h" + +class GrYUVtoRGBEffect : public GrFragmentProcessor { +public: + // The domain supported by this effect is more limited than the general GrTextureDomain due + // to the multi-planar, varying resolution images that it has to sample. If 'domain' is provided + // it is the Y plane's domain. This will automatically inset for bilinear filtering, and only + // the clamp wrap mode is supported. + static std::unique_ptr Make(const sk_sp proxies[], + const SkYUVAIndex indices[4], + SkYUVColorSpace yuvColorSpace, + GrSamplerState::Filter filterMode, + const SkMatrix& localMatrix = SkMatrix::I(), + const SkRect* domain = nullptr); +#ifdef SK_DEBUG + SkString dumpInfo() const override; +#endif + + SkYUVColorSpace yuvColorSpace() const { return fYUVColorSpace; } + const SkYUVAIndex& yuvaIndex(int i) const { return fYUVAIndices[i]; } + + GrYUVtoRGBEffect(const GrYUVtoRGBEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "YUVtoRGBEffect"; } + +private: + GrYUVtoRGBEffect(const sk_sp proxies[], const SkSize scales[], + const GrSamplerState::Filter filterModes[], int numPlanes, + const SkYUVAIndex yuvaIndices[4], SkYUVColorSpace yuvColorSpace, + const SkMatrix& localMatrix, const SkRect* domain) + : INHERITED(kGrYUVtoRGBEffect_ClassID, kNone_OptimizationFlags) + , fDomains{GrTextureDomain::IgnoredDomain(), GrTextureDomain::IgnoredDomain(), + GrTextureDomain::IgnoredDomain(), GrTextureDomain::IgnoredDomain()} + , fYUVColorSpace(yuvColorSpace) { + for (int i = 0; i < numPlanes; ++i) { + SkMatrix planeMatrix = SkMatrix::MakeScale(scales[i].width(), scales[i].height()); + if (domain) { + SkASSERT(filterModes[i] != GrSamplerState::Filter::kMipMap); + + SkRect scaledDomain = planeMatrix.mapRect(*domain); + if (filterModes[i] != GrSamplerState::Filter::kNearest) { + // Inset by half a pixel for bilerp, after scaling to the size of the plane + scaledDomain.inset(0.5f, 0.5f); + } + + fDomains[i] = GrTextureDomain(proxies[i].get(), scaledDomain, + GrTextureDomain::kClamp_Mode, GrTextureDomain::kClamp_Mode, i); + } + + planeMatrix.preConcat(localMatrix); + fSamplers[i].reset(std::move(proxies[i]), + GrSamplerState(GrSamplerState::WrapMode::kClamp, filterModes[i])); + fSamplerTransforms[i] = planeMatrix; + fSamplerCoordTransforms[i] = + GrCoordTransform(fSamplerTransforms[i], fSamplers[i].proxy()); + } + + this->setTextureSamplerCnt(numPlanes); + for (int i = 0; i < numPlanes; ++i) { + this->addCoordTransform(&fSamplerCoordTransforms[i]); + } + + memcpy(fYUVAIndices, yuvaIndices, sizeof(fYUVAIndices)); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + TextureSampler fSamplers[4]; + SkMatrix44 fSamplerTransforms[4]; + GrCoordTransform fSamplerCoordTransforms[4]; + GrTextureDomain fDomains[4]; + SkYUVAIndex fYUVAIndices[4]; + SkYUVColorSpace fYUVColorSpace; + + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.cpp new file mode 100644 index 0000000000..6d5833e7cd --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrAARectEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrAARectEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLAARectEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLAARectEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrAARectEffect& _outer = args.fFp.cast(); + (void)_outer; + auto edgeType = _outer.edgeType; + (void)edgeType; + auto rect = _outer.rect; + (void)rect; + prevRect = float4(-1.0); + rectUniformVar = args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, kFloat4_GrSLType, "rectUniform"); + fragBuilder->codeAppendf( + "float4 prevRect = float4(%f, %f, %f, %f);\nhalf alpha;\n@switch (%d) {\n case " + "0:\n case 2:\n alpha = half(all(greaterThan(float4(sk_FragCoord.xy, " + "%s.zw), float4(%s.xy, sk_FragCoord.xy))) ? 1 : 0);\n break;\n " + "default:\n half xSub, ySub;\n xSub = min(half(sk_FragCoord.x - " + "%s.x), 0.0);\n xSub += min(half(%s.z - sk_FragCoord.x), 0.0);\n " + "ySub = min(half(sk_FragCoord.y - %s.y), 0.0);\n ySub += min(half(%s.w - " + "sk_FragCoord.y), 0.0);\n alpha = (1.0 + ", + prevRect.left(), + prevRect.top(), + prevRect.right(), + prevRect.bottom(), + (int)_outer.edgeType, + args.fUniformHandler->getUniformCStr(rectUniformVar), + args.fUniformHandler->getUniformCStr(rectUniformVar), + args.fUniformHandler->getUniformCStr(rectUniformVar), + args.fUniformHandler->getUniformCStr(rectUniformVar), + args.fUniformHandler->getUniformCStr(rectUniformVar), + args.fUniformHandler->getUniformCStr(rectUniformVar)); + fragBuilder->codeAppendf( + "max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));\n}\n@if (%d == 2 || %d == 3) {\n " + "alpha = 1.0 - alpha;\n}\n%s = %s * alpha;\n", + (int)_outer.edgeType, + (int)_outer.edgeType, + args.fOutputColor, + args.fInputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrAARectEffect& _outer = _proc.cast(); + auto edgeType = _outer.edgeType; + (void)edgeType; + auto rect = _outer.rect; + (void)rect; + UniformHandle& rectUniform = rectUniformVar; + (void)rectUniform; + + const SkRect& newRect = GrProcessorEdgeTypeIsAA(edgeType) ? rect.makeInset(.5f, .5f) : rect; + if (newRect != prevRect) { + pdman.set4f(rectUniform, newRect.fLeft, newRect.fTop, newRect.fRight, newRect.fBottom); + prevRect = newRect; + } + } + SkRect prevRect = float4(0); + UniformHandle rectUniformVar; +}; +GrGLSLFragmentProcessor* GrAARectEffect::onCreateGLSLInstance() const { + return new GrGLSLAARectEffect(); +} +void GrAARectEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)edgeType); +} +bool GrAARectEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrAARectEffect& that = other.cast(); + (void)that; + if (edgeType != that.edgeType) return false; + if (rect != that.rect) return false; + return true; +} +GrAARectEffect::GrAARectEffect(const GrAARectEffect& src) + : INHERITED(kGrAARectEffect_ClassID, src.optimizationFlags()) + , edgeType(src.edgeType) + , rect(src.rect) {} +std::unique_ptr GrAARectEffect::clone() const { + return std::unique_ptr(new GrAARectEffect(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrAARectEffect); +#if GR_TEST_UTILS +std::unique_ptr GrAARectEffect::TestCreate(GrProcessorTestData* d) { + SkRect rect = SkRect::MakeLTRB(d->fRandom->nextSScalar1(), + d->fRandom->nextSScalar1(), + d->fRandom->nextSScalar1(), + d->fRandom->nextSScalar1()); + std::unique_ptr fp; + do { + GrClipEdgeType edgeType = + static_cast(d->fRandom->nextULessThan(kGrClipEdgeTypeCnt)); + + fp = GrAARectEffect::Make(edgeType, rect); + } while (nullptr == fp); + return fp; +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.h new file mode 100644 index 0000000000..15d5fb7594 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrAARectEffect.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrAARectEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrAARectEffect_DEFINED +#define GrAARectEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrAARectEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(GrClipEdgeType edgeType, SkRect rect) { + return std::unique_ptr(new GrAARectEffect(edgeType, rect)); + } + GrAARectEffect(const GrAARectEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "AARectEffect"; } + GrClipEdgeType edgeType; + SkRect rect; + +private: + GrAARectEffect(GrClipEdgeType edgeType, SkRect rect) + : INHERITED(kGrAARectEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , edgeType(edgeType) + , rect(rect) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp new file mode 100644 index 0000000000..506cedba5b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrAlphaThresholdFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrAlphaThresholdFragmentProcessor.h" + +inline GrFragmentProcessor::OptimizationFlags GrAlphaThresholdFragmentProcessor::optFlags( + float outerThreshold) { + if (outerThreshold >= 1.0) { + return kPreservesOpaqueInput_OptimizationFlag | + kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } else { + return kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } +} +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLAlphaThresholdFragmentProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLAlphaThresholdFragmentProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrAlphaThresholdFragmentProcessor& _outer = + args.fFp.cast(); + (void)_outer; + auto innerThreshold = _outer.innerThreshold; + (void)innerThreshold; + auto outerThreshold = _outer.outerThreshold; + (void)outerThreshold; + innerThresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "innerThreshold"); + outerThresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "outerThreshold"); + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf( + "half4 color = %s;\nhalf4 mask_color = sample(%s, %s).%s;\nif (mask_color.w < 0.5) " + "{\n if (color.w > %s) {\n half scale = %s / color.w;\n color.xyz " + "*= scale;\n color.w = %s;\n }\n} else if (color.w < %s) {\n half " + "scale = %s / max(0.0010000000474974513, color.w);\n color.xyz *= scale;\n " + "color.w = %s;\n}\n%s = color;\n", + args.fInputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + args.fUniformHandler->getUniformCStr(outerThresholdVar), + args.fUniformHandler->getUniformCStr(outerThresholdVar), + args.fUniformHandler->getUniformCStr(outerThresholdVar), + args.fUniformHandler->getUniformCStr(innerThresholdVar), + args.fUniformHandler->getUniformCStr(innerThresholdVar), + args.fUniformHandler->getUniformCStr(innerThresholdVar), args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrAlphaThresholdFragmentProcessor& _outer = + _proc.cast(); + { + pdman.set1f(innerThresholdVar, (_outer.innerThreshold)); + pdman.set1f(outerThresholdVar, (_outer.outerThreshold)); + } + } + UniformHandle innerThresholdVar; + UniformHandle outerThresholdVar; +}; +GrGLSLFragmentProcessor* GrAlphaThresholdFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLSLAlphaThresholdFragmentProcessor(); +} +void GrAlphaThresholdFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrAlphaThresholdFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrAlphaThresholdFragmentProcessor& that = other.cast(); + (void)that; + if (mask != that.mask) return false; + if (innerThreshold != that.innerThreshold) return false; + if (outerThreshold != that.outerThreshold) return false; + return true; +} +GrAlphaThresholdFragmentProcessor::GrAlphaThresholdFragmentProcessor( + const GrAlphaThresholdFragmentProcessor& src) + : INHERITED(kGrAlphaThresholdFragmentProcessor_ClassID, src.optimizationFlags()) + , maskCoordTransform(src.maskCoordTransform) + , mask(src.mask) + , innerThreshold(src.innerThreshold) + , outerThreshold(src.outerThreshold) { + this->setTextureSamplerCnt(1); + this->addCoordTransform(&maskCoordTransform); +} +std::unique_ptr GrAlphaThresholdFragmentProcessor::clone() const { + return std::unique_ptr(new GrAlphaThresholdFragmentProcessor(*this)); +} +const GrFragmentProcessor::TextureSampler& GrAlphaThresholdFragmentProcessor::onTextureSampler( + int index) const { + return IthTextureSampler(index, mask); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrAlphaThresholdFragmentProcessor); +#if GR_TEST_UTILS +std::unique_ptr GrAlphaThresholdFragmentProcessor::TestCreate( + GrProcessorTestData* testData) { + sk_sp maskProxy = testData->textureProxy(GrProcessorUnitTest::kAlphaTextureIdx); + // Make the inner and outer thresholds be in (0, 1) exclusive and be sorted correctly. + float innerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f; + float outerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f; + const int kMaxWidth = 1000; + const int kMaxHeight = 1000; + uint32_t width = testData->fRandom->nextULessThan(kMaxWidth); + uint32_t height = testData->fRandom->nextULessThan(kMaxHeight); + uint32_t x = testData->fRandom->nextULessThan(kMaxWidth - width); + uint32_t y = testData->fRandom->nextULessThan(kMaxHeight - height); + SkIRect bounds = SkIRect::MakeXYWH(x, y, width, height); + return GrAlphaThresholdFragmentProcessor::Make(std::move(maskProxy), innerThresh, outerThresh, + bounds); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h new file mode 100644 index 0000000000..730054b567 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrAlphaThresholdFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrAlphaThresholdFragmentProcessor_DEFINED +#define GrAlphaThresholdFragmentProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrAlphaThresholdFragmentProcessor : public GrFragmentProcessor { +public: + inline OptimizationFlags optFlags(float outerThreshold); + + static std::unique_ptr Make(sk_sp mask, + float innerThreshold, + float outerThreshold, + const SkIRect& bounds) { + return std::unique_ptr(new GrAlphaThresholdFragmentProcessor( + mask, innerThreshold, outerThreshold, bounds)); + } + GrAlphaThresholdFragmentProcessor(const GrAlphaThresholdFragmentProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "AlphaThresholdFragmentProcessor"; } + GrCoordTransform maskCoordTransform; + TextureSampler mask; + float innerThreshold; + float outerThreshold; + +private: + GrAlphaThresholdFragmentProcessor(sk_sp mask, float innerThreshold, + float outerThreshold, const SkIRect& bounds) + : INHERITED(kGrAlphaThresholdFragmentProcessor_ClassID, kNone_OptimizationFlags) + , maskCoordTransform( + SkMatrix::MakeTrans(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())), + mask.get()) + , mask(std::move(mask)) + , innerThreshold(innerThreshold) + , outerThreshold(outerThreshold) { + this->setTextureSamplerCnt(1); + this->addCoordTransform(&maskCoordTransform); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp new file mode 100644 index 0000000000..f286f0e3ea --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrBlurredEdgeFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrBlurredEdgeFragmentProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLBlurredEdgeFragmentProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLBlurredEdgeFragmentProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrBlurredEdgeFragmentProcessor& _outer = + args.fFp.cast(); + (void)_outer; + auto mode = _outer.mode; + (void)mode; + fragBuilder->codeAppendf( + "half factor = 1.0 - %s.w;\n@switch (%d) {\n case 0:\n factor = " + "exp((-factor * factor) * 4.0) - 0.017999999225139618;\n break;\n case " + "1:\n factor = smoothstep(1.0, 0.0, factor);\n break;\n}\n%s = " + "half4(factor);\n", + args.fInputColor, (int)_outer.mode, args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrBlurredEdgeFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLSLBlurredEdgeFragmentProcessor(); +} +void GrBlurredEdgeFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)mode); +} +bool GrBlurredEdgeFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrBlurredEdgeFragmentProcessor& that = other.cast(); + (void)that; + if (mode != that.mode) return false; + return true; +} +GrBlurredEdgeFragmentProcessor::GrBlurredEdgeFragmentProcessor( + const GrBlurredEdgeFragmentProcessor& src) + : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, src.optimizationFlags()) + , mode(src.mode) {} +std::unique_ptr GrBlurredEdgeFragmentProcessor::clone() const { + return std::unique_ptr(new GrBlurredEdgeFragmentProcessor(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h new file mode 100644 index 0000000000..e71eafb56c --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrBlurredEdgeFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrBlurredEdgeFragmentProcessor_DEFINED +#define GrBlurredEdgeFragmentProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrBlurredEdgeFragmentProcessor : public GrFragmentProcessor { +public: + enum class Mode { kGaussian = 0, kSmoothStep = 1 }; + static std::unique_ptr Make(Mode mode) { + return std::unique_ptr(new GrBlurredEdgeFragmentProcessor(mode)); + } + GrBlurredEdgeFragmentProcessor(const GrBlurredEdgeFragmentProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "BlurredEdgeFragmentProcessor"; } + Mode mode; + +private: + GrBlurredEdgeFragmentProcessor(Mode mode) + : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, kNone_OptimizationFlags) + , mode(mode) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp new file mode 100644 index 0000000000..ccef347e1b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp @@ -0,0 +1,350 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrCircleBlurFragmentProcessor.h" + +#include "src/gpu/GrProxyProvider.h" + +// Computes an unnormalized half kernel (right side). Returns the summation of all the half +// kernel values. +static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) { + const float invSigma = 1.f / sigma; + const float b = -0.5f * invSigma * invSigma; + float tot = 0.0f; + // Compute half kernel values at half pixel steps out from the center. + float t = 0.5f; + for (int i = 0; i < halfKernelSize; ++i) { + float value = expf(t * t * b); + tot += value; + halfKernel[i] = value; + t += 1.f; + } + return tot; +} + +// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number +// of discrete steps. The half kernel is normalized to sum to 0.5. +static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel, + int halfKernelSize, float sigma) { + // The half kernel should sum to 0.5 not 1.0. + const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma); + float sum = 0.f; + for (int i = 0; i < halfKernelSize; ++i) { + halfKernel[i] /= tot; + sum += halfKernel[i]; + summedHalfKernel[i] = sum; + } +} + +// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the +// origin with radius circleR. +void apply_kernel_in_y(float* results, int numSteps, float firstX, float circleR, + int halfKernelSize, const float* summedHalfKernelTable) { + float x = firstX; + for (int i = 0; i < numSteps; ++i, x += 1.f) { + if (x < -circleR || x > circleR) { + results[i] = 0; + continue; + } + float y = sqrtf(circleR * circleR - x * x); + // In the column at x we exit the circle at +y and -y + // The summed table entry j is actually reflects an offset of j + 0.5. + y -= 0.5f; + int yInt = SkScalarFloorToInt(y); + SkASSERT(yInt >= -1); + if (y < 0) { + results[i] = (y + 0.5f) * summedHalfKernelTable[0]; + } else if (yInt >= halfKernelSize - 1) { + results[i] = 0.5f; + } else { + float yFrac = y - yInt; + results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] + + yFrac * summedHalfKernelTable[yInt + 1]; + } + } +} + +// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR. +// This relies on having a half kernel computed for the Gaussian and a table of applications of +// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX + +// halfKernel) passed in as yKernelEvaluations. +static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int halfKernelSize, + const float* yKernelEvaluations) { + float acc = 0; + + float x = evalX - halfKernelSize; + for (int i = 0; i < halfKernelSize; ++i, x += 1.f) { + if (x < -circleR || x > circleR) { + continue; + } + float verticalEval = yKernelEvaluations[i]; + acc += verticalEval * halfKernel[halfKernelSize - i - 1]; + } + for (int i = 0; i < halfKernelSize; ++i, x += 1.f) { + if (x < -circleR || x > circleR) { + continue; + } + float verticalEval = yKernelEvaluations[i + halfKernelSize]; + acc += verticalEval * halfKernel[i]; + } + // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about + // the x axis). + return SkUnitScalarClampToByte(2.f * acc); +} + +// This function creates a profile of a blurred circle. It does this by computing a kernel for +// half the Gaussian and a matching summed area table. The summed area table is used to compute +// an array of vertical applications of the half kernel to the circle along the x axis. The +// table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is +// the size of the profile being computed. Then for each of the n profile entries we walk out k +// steps in each horizontal direction multiplying the corresponding y evaluation by the half +// kernel entry and sum these values to compute the profile entry. +static void create_circle_profile(uint8_t* weights, float sigma, float circleR, + int profileTextureWidth) { + const int numSteps = profileTextureWidth; + + // The full kernel is 6 sigmas wide. + int halfKernelSize = SkScalarCeilToInt(6.0f * sigma); + // round up to next multiple of 2 and then divide by 2 + halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; + + // Number of x steps at which to apply kernel in y to cover all the profile samples in x. + int numYSteps = numSteps + 2 * halfKernelSize; + + SkAutoTArray bulkAlloc(halfKernelSize + halfKernelSize + numYSteps); + float* halfKernel = bulkAlloc.get(); + float* summedKernel = bulkAlloc.get() + halfKernelSize; + float* yEvals = bulkAlloc.get() + 2 * halfKernelSize; + make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma); + + float firstX = -halfKernelSize + 0.5f; + apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel); + + for (int i = 0; i < numSteps - 1; ++i) { + float evalX = i + 0.5f; + weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i); + } + // Ensure the tail of the Gaussian goes to zero. + weights[numSteps - 1] = 0; +} + +static void create_half_plane_profile(uint8_t* profile, int profileWidth) { + SkASSERT(!(profileWidth & 0x1)); + // The full kernel is 6 sigmas wide. + float sigma = profileWidth / 6.f; + int halfKernelSize = profileWidth / 2; + + SkAutoTArray halfKernel(halfKernelSize); + + // The half kernel should sum to 0.5. + const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma); + float sum = 0.f; + // Populate the profile from the right edge to the middle. + for (int i = 0; i < halfKernelSize; ++i) { + halfKernel[halfKernelSize - i - 1] /= tot; + sum += halfKernel[halfKernelSize - i - 1]; + profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum); + } + // Populate the profile from the middle to the left edge (by flipping the half kernel and + // continuing the summation). + for (int i = 0; i < halfKernelSize; ++i) { + sum += halfKernel[i]; + profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum); + } + // Ensure tail goes to 0. + profile[profileWidth - 1] = 0; +} + +static sk_sp create_profile_texture(GrProxyProvider* proxyProvider, + const SkRect& circle, float sigma, + float* solidRadius, float* textureRadius) { + float circleR = circle.width() / 2.0f; + if (circleR < SK_ScalarNearlyZero) { + return nullptr; + } + // Profile textures are cached by the ratio of sigma to circle radius and by the size of the + // profile texture (binned by powers of 2). + SkScalar sigmaToCircleRRatio = sigma / circleR; + // When sigma is really small this becomes a equivalent to convolving a Gaussian with a + // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the + // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet + // implemented this latter optimization. + sigmaToCircleRRatio = SkTMin(sigmaToCircleRRatio, 8.f); + SkFixed sigmaToCircleRRatioFixed; + static const SkScalar kHalfPlaneThreshold = 0.1f; + bool useHalfPlaneApprox = false; + if (sigmaToCircleRRatio <= kHalfPlaneThreshold) { + useHalfPlaneApprox = true; + sigmaToCircleRRatioFixed = 0; + *solidRadius = circleR - 3 * sigma; + *textureRadius = 6 * sigma; + } else { + // Convert to fixed point for the key. + sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio); + // We shave off some bits to reduce the number of unique entries. We could probably + // shave off more than we do. + sigmaToCircleRRatioFixed &= ~0xff; + sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed); + sigma = circleR * sigmaToCircleRRatio; + *solidRadius = 0; + *textureRadius = circleR + 3 * sigma; + } + + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur"); + builder[0] = sigmaToCircleRRatioFixed; + builder.finish(); + + sk_sp blurProfile = proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin); + if (!blurProfile) { + static constexpr int kProfileTextureWidth = 512; + + SkBitmap bm; + if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) { + return nullptr; + } + + if (useHalfPlaneApprox) { + create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth); + } else { + // Rescale params to the size of the texture we're creating. + SkScalar scale = kProfileTextureWidth / *textureRadius; + create_circle_profile(bm.getAddr8(0, 0), sigma * scale, circleR * scale, + kProfileTextureWidth); + } + + bm.setImmutable(); + sk_sp image = SkImage::MakeFromBitmap(bm); + + blurProfile = proxyProvider->createTextureProxy(std::move(image), 1, SkBudgeted::kYes, + SkBackingFit::kExact); + if (!blurProfile) { + return nullptr; + } + + SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get()); + } + + return blurProfile; +} + +std::unique_ptr GrCircleBlurFragmentProcessor::Make( + GrProxyProvider* proxyProvider, const SkRect& circle, float sigma) { + float solidRadius; + float textureRadius; + sk_sp profile( + create_profile_texture(proxyProvider, circle, sigma, &solidRadius, &textureRadius)); + if (!profile) { + return nullptr; + } + return std::unique_ptr(new GrCircleBlurFragmentProcessor( + circle, textureRadius, solidRadius, std::move(profile))); +} +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLCircleBlurFragmentProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrCircleBlurFragmentProcessor& _outer = + args.fFp.cast(); + (void)_outer; + auto circleRect = _outer.circleRect; + (void)circleRect; + auto textureRadius = _outer.textureRadius; + (void)textureRadius; + auto solidRadius = _outer.solidRadius; + (void)solidRadius; + circleDataVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, + "circleData"); + fragBuilder->codeAppendf( + "half2 vec = half2(half((sk_FragCoord.x - float(%s.x)) * float(%s.w)), " + "half((sk_FragCoord.y - float(%s.y)) * float(%s.w)));\nhalf dist = length(vec) + " + "(0.5 - %s.z) * %s.w;\n%s = %s * sample(%s, float2(half2(dist, 0.5))).%s.w;\n", + args.fUniformHandler->getUniformCStr(circleDataVar), + args.fUniformHandler->getUniformCStr(circleDataVar), + args.fUniformHandler->getUniformCStr(circleDataVar), + args.fUniformHandler->getUniformCStr(circleDataVar), + args.fUniformHandler->getUniformCStr(circleDataVar), + args.fUniformHandler->getUniformCStr(circleDataVar), args.fOutputColor, + args.fInputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& data, + const GrFragmentProcessor& _proc) override { + const GrCircleBlurFragmentProcessor& _outer = _proc.cast(); + auto circleRect = _outer.circleRect; + (void)circleRect; + auto textureRadius = _outer.textureRadius; + (void)textureRadius; + auto solidRadius = _outer.solidRadius; + (void)solidRadius; + GrSurfaceProxy& blurProfileSamplerProxy = *_outer.textureSampler(0).proxy(); + GrTexture& blurProfileSampler = *blurProfileSamplerProxy.peekTexture(); + (void)blurProfileSampler; + UniformHandle& circleData = circleDataVar; + (void)circleData; + + data.set4f(circleData, circleRect.centerX(), circleRect.centerY(), solidRadius, + 1.f / textureRadius); + } + UniformHandle circleDataVar; +}; +GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLSLCircleBlurFragmentProcessor(); +} +void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrCircleBlurFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrCircleBlurFragmentProcessor& that = other.cast(); + (void)that; + if (circleRect != that.circleRect) return false; + if (textureRadius != that.textureRadius) return false; + if (solidRadius != that.solidRadius) return false; + if (blurProfileSampler != that.blurProfileSampler) return false; + return true; +} +GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor( + const GrCircleBlurFragmentProcessor& src) + : INHERITED(kGrCircleBlurFragmentProcessor_ClassID, src.optimizationFlags()) + , circleRect(src.circleRect) + , textureRadius(src.textureRadius) + , solidRadius(src.solidRadius) + , blurProfileSampler(src.blurProfileSampler) { + this->setTextureSamplerCnt(1); +} +std::unique_ptr GrCircleBlurFragmentProcessor::clone() const { + return std::unique_ptr(new GrCircleBlurFragmentProcessor(*this)); +} +const GrFragmentProcessor::TextureSampler& GrCircleBlurFragmentProcessor::onTextureSampler( + int index) const { + return IthTextureSampler(index, blurProfileSampler); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); +#if GR_TEST_UTILS +std::unique_ptr GrCircleBlurFragmentProcessor::TestCreate( + GrProcessorTestData* testData) { + SkScalar wh = testData->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar sigma = testData->fRandom->nextRangeF(1.f, 10.f); + SkRect circle = SkRect::MakeWH(wh, wh); + return GrCircleBlurFragmentProcessor::Make(testData->proxyProvider(), circle, sigma); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h new file mode 100644 index 0000000000..93d24038a4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrCircleBlurFragmentProcessor_DEFINED +#define GrCircleBlurFragmentProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrCircleBlurFragmentProcessor : public GrFragmentProcessor { +public: + static std::unique_ptr Make(GrProxyProvider*, const SkRect& circle, + float sigma); + GrCircleBlurFragmentProcessor(const GrCircleBlurFragmentProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "CircleBlurFragmentProcessor"; } + SkRect circleRect; + float textureRadius; + float solidRadius; + TextureSampler blurProfileSampler; + +private: + GrCircleBlurFragmentProcessor(SkRect circleRect, float textureRadius, float solidRadius, + sk_sp blurProfileSampler) + : INHERITED(kGrCircleBlurFragmentProcessor_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , circleRect(circleRect) + , textureRadius(textureRadius) + , solidRadius(solidRadius) + , blurProfileSampler(std::move(blurProfileSampler)) { + this->setTextureSamplerCnt(1); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.cpp new file mode 100644 index 0000000000..e616fee504 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrCircleEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrCircleEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLCircleEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLCircleEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrCircleEffect& _outer = args.fFp.cast(); + (void)_outer; + auto edgeType = _outer.edgeType; + (void)edgeType; + auto center = _outer.center; + (void)center; + auto radius = _outer.radius; + (void)radius; + prevRadius = -1.0; + circleVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "circle"); + fragBuilder->codeAppendf( + "float2 prevCenter;\nfloat prevRadius = %f;\nhalf d;\n@if (%d == 2 || %d == 3) {\n " + " d = half((length((%s.xy - sk_FragCoord.xy) * %s.w) - 1.0) * %s.z);\n} else {\n " + " d = half((1.0 - length((%s.xy - sk_FragCoord.xy) * %s.w)) * %s.z);\n}\n@if " + "((%d == 1 || %d == 3) || %d == 4) {\n %s = %s * clamp(d, 0.0, 1.0);\n} else " + "{\n %s = d > 0.5 ? %s : half4(0.0);\n}\n", + prevRadius, (int)_outer.edgeType, (int)_outer.edgeType, + args.fUniformHandler->getUniformCStr(circleVar), + args.fUniformHandler->getUniformCStr(circleVar), + args.fUniformHandler->getUniformCStr(circleVar), + args.fUniformHandler->getUniformCStr(circleVar), + args.fUniformHandler->getUniformCStr(circleVar), + args.fUniformHandler->getUniformCStr(circleVar), (int)_outer.edgeType, + (int)_outer.edgeType, (int)_outer.edgeType, args.fOutputColor, args.fInputColor, + args.fOutputColor, args.fInputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrCircleEffect& _outer = _proc.cast(); + auto edgeType = _outer.edgeType; + (void)edgeType; + auto center = _outer.center; + (void)center; + auto radius = _outer.radius; + (void)radius; + UniformHandle& circle = circleVar; + (void)circle; + + if (radius != prevRadius || center != prevCenter) { + SkScalar effectiveRadius = radius; + if (GrProcessorEdgeTypeIsInverseFill((GrClipEdgeType)edgeType)) { + effectiveRadius -= 0.5f; + // When the radius is 0.5 effectiveRadius is 0 which causes an inf * 0 in the + // shader. + effectiveRadius = SkTMax(0.001f, effectiveRadius); + } else { + effectiveRadius += 0.5f; + } + pdman.set4f(circle, center.fX, center.fY, effectiveRadius, + SkScalarInvert(effectiveRadius)); + prevCenter = center; + prevRadius = radius; + } + } + SkPoint prevCenter = float2(0); + float prevRadius = 0; + UniformHandle circleVar; +}; +GrGLSLFragmentProcessor* GrCircleEffect::onCreateGLSLInstance() const { + return new GrGLSLCircleEffect(); +} +void GrCircleEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)edgeType); +} +bool GrCircleEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrCircleEffect& that = other.cast(); + (void)that; + if (edgeType != that.edgeType) return false; + if (center != that.center) return false; + if (radius != that.radius) return false; + return true; +} +GrCircleEffect::GrCircleEffect(const GrCircleEffect& src) + : INHERITED(kGrCircleEffect_ClassID, src.optimizationFlags()) + , edgeType(src.edgeType) + , center(src.center) + , radius(src.radius) {} +std::unique_ptr GrCircleEffect::clone() const { + return std::unique_ptr(new GrCircleEffect(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleEffect); +#if GR_TEST_UTILS +std::unique_ptr GrCircleEffect::TestCreate(GrProcessorTestData* testData) { + SkPoint center; + center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f); + center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f); + SkScalar radius = testData->fRandom->nextRangeF(1.f, 1000.f); + GrClipEdgeType et; + do { + et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt); + } while (GrClipEdgeType::kHairlineAA == et); + return GrCircleEffect::Make(et, center, radius); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.h new file mode 100644 index 0000000000..c43ab987ea --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrCircleEffect.h @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrCircleEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrCircleEffect_DEFINED +#define GrCircleEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrCircleEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(GrClipEdgeType edgeType, SkPoint center, + float radius) { + // A radius below half causes the implicit insetting done by this processor to become + // inverted. We could handle this case by making the processor code more complicated. + if (radius < .5f && GrProcessorEdgeTypeIsInverseFill(edgeType)) { + return nullptr; + } + return std::unique_ptr(new GrCircleEffect(edgeType, center, radius)); + } + GrCircleEffect(const GrCircleEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "CircleEffect"; } + GrClipEdgeType edgeType; + SkPoint center; + float radius; + +private: + GrCircleEffect(GrClipEdgeType edgeType, SkPoint center, float radius) + : INHERITED(kGrCircleEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , edgeType(edgeType) + , center(center) + , radius(radius) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp new file mode 100644 index 0000000000..8afed91062 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrColorMatrixFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrColorMatrixFragmentProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLColorMatrixFragmentProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLColorMatrixFragmentProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrColorMatrixFragmentProcessor& _outer = + args.fFp.cast(); + (void)_outer; + auto m = _outer.m; + (void)m; + auto v = _outer.v; + (void)v; + auto unpremulInput = _outer.unpremulInput; + (void)unpremulInput; + auto clampRGBOutput = _outer.clampRGBOutput; + (void)clampRGBOutput; + auto premulOutput = _outer.premulOutput; + (void)premulOutput; + mVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4x4_GrSLType, "m"); + vVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "v"); + fragBuilder->codeAppendf( + "half4 inputColor = %s;\n@if (%s) {\n half nonZeroAlpha = max(inputColor.w, " + "9.9999997473787516e-05);\n inputColor = half4(inputColor.xyz / nonZeroAlpha, " + "nonZeroAlpha);\n}\n%s = %s * inputColor + %s;\n@if (%s) {\n %s = clamp(%s, " + "0.0, 1.0);\n} else {\n %s.w = clamp(%s.w, 0.0, 1.0);\n}\n@if (%s) {\n " + "%s.xyz *= %s.w;\n}\n", + args.fInputColor, (_outer.unpremulInput ? "true" : "false"), args.fOutputColor, + args.fUniformHandler->getUniformCStr(mVar), + args.fUniformHandler->getUniformCStr(vVar), + (_outer.clampRGBOutput ? "true" : "false"), args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor, (_outer.premulOutput ? "true" : "false"), + args.fOutputColor, args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrColorMatrixFragmentProcessor& _outer = _proc.cast(); + { + const SkMatrix44& mValue = _outer.m; + if (mPrev != (mValue)) { + mPrev = mValue; + pdman.setSkMatrix44(mVar, mValue); + } + const SkVector4& vValue = _outer.v; + if (vPrev != (vValue)) { + vPrev = vValue; + pdman.set4fv(vVar, 1, vValue.fData); + } + } + } + SkMatrix44 mPrev = SkMatrix44(SkMatrix44::kNaN_Constructor); + SkVector4 vPrev = SkVector4(SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN); + UniformHandle mVar; + UniformHandle vVar; +}; +GrGLSLFragmentProcessor* GrColorMatrixFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLSLColorMatrixFragmentProcessor(); +} +void GrColorMatrixFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)unpremulInput); + b->add32((int32_t)clampRGBOutput); + b->add32((int32_t)premulOutput); +} +bool GrColorMatrixFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrColorMatrixFragmentProcessor& that = other.cast(); + (void)that; + if (m != that.m) return false; + if (v != that.v) return false; + if (unpremulInput != that.unpremulInput) return false; + if (clampRGBOutput != that.clampRGBOutput) return false; + if (premulOutput != that.premulOutput) return false; + return true; +} +GrColorMatrixFragmentProcessor::GrColorMatrixFragmentProcessor( + const GrColorMatrixFragmentProcessor& src) + : INHERITED(kGrColorMatrixFragmentProcessor_ClassID, src.optimizationFlags()) + , m(src.m) + , v(src.v) + , unpremulInput(src.unpremulInput) + , clampRGBOutput(src.clampRGBOutput) + , premulOutput(src.premulOutput) {} +std::unique_ptr GrColorMatrixFragmentProcessor::clone() const { + return std::unique_ptr(new GrColorMatrixFragmentProcessor(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrColorMatrixFragmentProcessor); +#if GR_TEST_UTILS +std::unique_ptr GrColorMatrixFragmentProcessor::TestCreate( + GrProcessorTestData* d) { + float m[20]; + for (int i = 0; i < 20; ++i) { + m[i] = d->fRandom->nextRangeScalar(-10.f, 10.f); + } + bool unpremul = d->fRandom->nextBool(); + bool clampRGB = d->fRandom->nextBool(); + bool premul = d->fRandom->nextBool(); + return Make(m, unpremul, clampRGB, premul); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h new file mode 100644 index 0000000000..a9609aa3dd --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h @@ -0,0 +1,82 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrColorMatrixFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrColorMatrixFragmentProcessor_DEFINED +#define GrColorMatrixFragmentProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrColorMatrixFragmentProcessor : public GrFragmentProcessor { +public: + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + SkColor4f color; + if (unpremulInput) { + color = input.unpremul(); + } else { + color.fR = input.fR; + color.fG = input.fG; + color.fB = input.fB; + color.fA = input.fA; + } + m.mapScalars(color.vec()); + color.fR += v.fData[0]; + color.fG += v.fData[1]; + color.fB += v.fData[2]; + color.fA += v.fData[3]; + color.fA = SkTPin(color.fA, 0.f, 1.f); + if (clampRGBOutput) { + color.fR = SkTPin(color.fR, 0.f, 1.f); + color.fG = SkTPin(color.fG, 0.f, 1.f); + color.fB = SkTPin(color.fB, 0.f, 1.f); + } + if (premulOutput) { + return color.premul(); + } else { + return {color.fR, color.fG, color.fB, color.fA}; + } + } + + static std::unique_ptr Make(const float matrix[20], bool unpremulInput, + bool clampRGBOutput, bool premulOutput) { + SkMatrix44 m44; + m44.set4x4(matrix[0], matrix[5], matrix[10], matrix[15], matrix[1], matrix[6], matrix[11], + matrix[16], matrix[2], matrix[7], matrix[12], matrix[17], matrix[3], matrix[8], + matrix[13], matrix[18]); + auto v4 = SkVector4(matrix[4], matrix[9], matrix[14], matrix[19]); + return std::unique_ptr(new GrColorMatrixFragmentProcessor( + m44, v4, unpremulInput, clampRGBOutput, premulOutput)); + } + GrColorMatrixFragmentProcessor(const GrColorMatrixFragmentProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "ColorMatrixFragmentProcessor"; } + SkMatrix44 m; + SkVector4 v; + bool unpremulInput; + bool clampRGBOutput; + bool premulOutput; + +private: + GrColorMatrixFragmentProcessor(SkMatrix44 m, SkVector4 v, bool unpremulInput, + bool clampRGBOutput, bool premulOutput) + : INHERITED(kGrColorMatrixFragmentProcessor_ClassID, + (OptimizationFlags)kConstantOutputForConstantInput_OptimizationFlag) + , m(m) + , v(v) + , unpremulInput(unpremulInput) + , clampRGBOutput(clampRGBOutput) + , premulOutput(premulOutput) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.cpp new file mode 100644 index 0000000000..1af5e938c3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrComposeLerpEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrComposeLerpEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLComposeLerpEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLComposeLerpEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrComposeLerpEffect& _outer = args.fFp.cast(); + (void)_outer; + auto weight = _outer.weight; + (void)weight; + weightVar = + args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat_GrSLType, "weight"); + SkString _sample290("_sample290"); + if (_outer.child1_index >= 0) { + this->invokeChild(_outer.child1_index, &_sample290, args); + } else { + fragBuilder->codeAppendf("half4 %s;", _sample290.c_str()); + } + SkString _sample358("_sample358"); + if (_outer.child2_index >= 0) { + this->invokeChild(_outer.child2_index, &_sample358, args); + } else { + fragBuilder->codeAppendf("half4 %s;", _sample358.c_str()); + } + fragBuilder->codeAppendf("%s = mix(%s ? %s : %s, %s ? %s : %s, half(%s));\n", + args.fOutputColor, _outer.child1_index >= 0 ? "true" : "false", + _sample290.c_str(), args.fInputColor, + _outer.child2_index >= 0 ? "true" : "false", _sample358.c_str(), + args.fInputColor, args.fUniformHandler->getUniformCStr(weightVar)); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrComposeLerpEffect& _outer = _proc.cast(); + { pdman.set1f(weightVar, (_outer.weight)); } + } + UniformHandle weightVar; +}; +GrGLSLFragmentProcessor* GrComposeLerpEffect::onCreateGLSLInstance() const { + return new GrGLSLComposeLerpEffect(); +} +void GrComposeLerpEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrComposeLerpEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrComposeLerpEffect& that = other.cast(); + (void)that; + if (weight != that.weight) return false; + return true; +} +GrComposeLerpEffect::GrComposeLerpEffect(const GrComposeLerpEffect& src) + : INHERITED(kGrComposeLerpEffect_ClassID, src.optimizationFlags()) + , child1_index(src.child1_index) + , child2_index(src.child2_index) + , weight(src.weight) { + if (child1_index >= 0) { + this->registerChildProcessor(src.childProcessor(child1_index).clone()); + } + if (child2_index >= 0) { + this->registerChildProcessor(src.childProcessor(child2_index).clone()); + } +} +std::unique_ptr GrComposeLerpEffect::clone() const { + return std::unique_ptr(new GrComposeLerpEffect(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.h new file mode 100644 index 0000000000..02753b7d45 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpEffect.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrComposeLerpEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrComposeLerpEffect_DEFINED +#define GrComposeLerpEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrComposeLerpEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr child1, + std::unique_ptr child2, + float weight) { + return std::unique_ptr( + new GrComposeLerpEffect(std::move(child1), std::move(child2), weight)); + } + GrComposeLerpEffect(const GrComposeLerpEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "ComposeLerpEffect"; } + int child1_index = -1; + int child2_index = -1; + float weight; + +private: + GrComposeLerpEffect(std::unique_ptr child1, + std::unique_ptr child2, float weight) + : INHERITED(kGrComposeLerpEffect_ClassID, kNone_OptimizationFlags), weight(weight) { + if (child1) { + child1_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(child1)); + } + if (child2) { + child2_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(child2)); + } + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp new file mode 100644 index 0000000000..8a6bc6defc --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrComposeLerpRedEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrComposeLerpRedEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLComposeLerpRedEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLComposeLerpRedEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrComposeLerpRedEffect& _outer = args.fFp.cast(); + (void)_outer; + SkString _sample292("_sample292"); + if (_outer.child1_index >= 0) { + this->invokeChild(_outer.child1_index, &_sample292, args); + } else { + fragBuilder->codeAppendf("half4 %s;", _sample292.c_str()); + } + SkString _sample360("_sample360"); + if (_outer.child2_index >= 0) { + this->invokeChild(_outer.child2_index, &_sample360, args); + } else { + fragBuilder->codeAppendf("half4 %s;", _sample360.c_str()); + } + SkString _sample411("_sample411"); + this->invokeChild(_outer.lerp_index, &_sample411, args); + fragBuilder->codeAppendf("%s = mix(%s ? %s : %s, %s ? %s : %s, %s.x);\n", args.fOutputColor, + _outer.child1_index >= 0 ? "true" : "false", _sample292.c_str(), + args.fInputColor, _outer.child2_index >= 0 ? "true" : "false", + _sample360.c_str(), args.fInputColor, _sample411.c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrComposeLerpRedEffect::onCreateGLSLInstance() const { + return new GrGLSLComposeLerpRedEffect(); +} +void GrComposeLerpRedEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrComposeLerpRedEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrComposeLerpRedEffect& that = other.cast(); + (void)that; + return true; +} +GrComposeLerpRedEffect::GrComposeLerpRedEffect(const GrComposeLerpRedEffect& src) + : INHERITED(kGrComposeLerpRedEffect_ClassID, src.optimizationFlags()) + , child1_index(src.child1_index) + , child2_index(src.child2_index) + , lerp_index(src.lerp_index) { + if (child1_index >= 0) { + this->registerChildProcessor(src.childProcessor(child1_index).clone()); + } + if (child2_index >= 0) { + this->registerChildProcessor(src.childProcessor(child2_index).clone()); + } + this->registerChildProcessor(src.childProcessor(lerp_index).clone()); +} +std::unique_ptr GrComposeLerpRedEffect::clone() const { + return std::unique_ptr(new GrComposeLerpRedEffect(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.h new file mode 100644 index 0000000000..d48725fdb7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrComposeLerpRedEffect.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrComposeLerpRedEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrComposeLerpRedEffect_DEFINED +#define GrComposeLerpRedEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrComposeLerpRedEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr child1, + std::unique_ptr child2, + std::unique_ptr lerp) { + return std::unique_ptr( + new GrComposeLerpRedEffect(std::move(child1), std::move(child2), std::move(lerp))); + } + GrComposeLerpRedEffect(const GrComposeLerpRedEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "ComposeLerpRedEffect"; } + int child1_index = -1; + int child2_index = -1; + int lerp_index = -1; + +private: + GrComposeLerpRedEffect(std::unique_ptr child1, + std::unique_ptr child2, + std::unique_ptr lerp) + : INHERITED(kGrComposeLerpRedEffect_ClassID, kNone_OptimizationFlags) { + if (child1) { + child1_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(child1)); + } + if (child2) { + child2_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(child2)); + } + SkASSERT(lerp); + lerp_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(lerp)); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.cpp new file mode 100644 index 0000000000..c16796deda --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrConfigConversionEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrConfigConversionEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLConfigConversionEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLConfigConversionEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrConfigConversionEffect& _outer = args.fFp.cast(); + (void)_outer; + auto pmConversion = _outer.pmConversion; + (void)pmConversion; + + fragBuilder->forceHighPrecision(); + fragBuilder->codeAppendf( + "%s = floor(%s * 255.0 + 0.5) / 255.0;\n@switch (%d) {\n case 0:\n " + "%s.xyz = floor((%s.xyz * %s.w) * 255.0 + 0.5) / 255.0;\n break;\n case " + "1:\n %s.xyz = %s.w <= 0.0 ? half3(0.0) : floor((%s.xyz / %s.w) * 255.0 + " + "0.5) / 255.0;\n break;\n}\n", + args.fOutputColor, args.fInputColor, (int)_outer.pmConversion, args.fOutputColor, + args.fOutputColor, args.fOutputColor, args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrConfigConversionEffect::onCreateGLSLInstance() const { + return new GrGLSLConfigConversionEffect(); +} +void GrConfigConversionEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)pmConversion); +} +bool GrConfigConversionEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrConfigConversionEffect& that = other.cast(); + (void)that; + if (pmConversion != that.pmConversion) return false; + return true; +} +GrConfigConversionEffect::GrConfigConversionEffect(const GrConfigConversionEffect& src) + : INHERITED(kGrConfigConversionEffect_ClassID, src.optimizationFlags()) + , pmConversion(src.pmConversion) {} +std::unique_ptr GrConfigConversionEffect::clone() const { + return std::unique_ptr(new GrConfigConversionEffect(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConfigConversionEffect); +#if GR_TEST_UTILS +std::unique_ptr GrConfigConversionEffect::TestCreate( + GrProcessorTestData* data) { + PMConversion pmConv = static_cast( + data->fRandom->nextULessThan((int)PMConversion::kPMConversionCnt)); + return std::unique_ptr(new GrConfigConversionEffect(pmConv)); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.h new file mode 100644 index 0000000000..acbc4cc30f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrConfigConversionEffect.h @@ -0,0 +1,159 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrConfigConversionEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrConfigConversionEffect_DEFINED +#define GrConfigConversionEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRenderTargetContext.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrConfigConversionEffect : public GrFragmentProcessor { +public: + static bool TestForPreservingPMConversions(GrContext* context) { + static constexpr int kSize = 256; + static constexpr GrColorType kColorType = GrColorType::kRGBA_8888; + SkAutoTMalloc data(kSize * kSize * 3); + uint32_t* srcData = data.get(); + uint32_t* firstRead = data.get() + kSize * kSize; + uint32_t* secondRead = data.get() + 2 * kSize * kSize; + + // Fill with every possible premultiplied A, color channel value. There will be 256-y + // duplicate values in row y. We set r, g, and b to the same value since they are handled + // identically. + for (int y = 0; y < kSize; ++y) { + for (int x = 0; x < kSize; ++x) { + uint8_t* color = reinterpret_cast(&srcData[kSize * y + x]); + color[3] = y; + color[2] = SkTMin(x, y); + color[1] = SkTMin(x, y); + color[0] = SkTMin(x, y); + } + } + memset(firstRead, 0, kSize * kSize * sizeof(uint32_t)); + memset(secondRead, 0, kSize * kSize * sizeof(uint32_t)); + + const SkImageInfo ii = + SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType); + + auto readRTC = context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, kSize, + kSize, kColorType, nullptr); + auto tempRTC = context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, kSize, + kSize, kColorType, nullptr); + if (!readRTC || !readRTC->asTextureProxy() || !tempRTC) { + return false; + } + // Adding discard to appease vulkan validation warning about loading uninitialized data on + // draw + readRTC->discard(); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + SkPixmap pixmap(ii, srcData, 4 * kSize); + + // This function is only ever called if we are in a GrContext that has a GrGpu since we are + // calling read pixels here. Thus the pixel data will be uploaded immediately and we don't + // need to keep the pixel data alive in the proxy. Therefore the ReleaseProc is nullptr. + sk_sp image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr); + GrColorType dataColorType = SkColorTypeToGrColorType(image->colorType()); + sk_sp dataProxy = proxyProvider->createTextureProxy( + std::move(image), 1, SkBudgeted::kYes, SkBackingFit::kExact); + if (!dataProxy) { + return false; + } + + static const SkRect kRect = SkRect::MakeIWH(kSize, kSize); + + // We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw + // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data. + // We then verify that two reads produced the same values. + + GrPaint paint1; + GrPaint paint2; + GrPaint paint3; + std::unique_ptr pmToUPM( + new GrConfigConversionEffect(PMConversion::kToUnpremul)); + std::unique_ptr upmToPM( + new GrConfigConversionEffect(PMConversion::kToPremul)); + + paint1.addColorTextureProcessor(dataProxy, dataColorType, SkMatrix::I()); + paint1.addColorFragmentProcessor(pmToUPM->clone()); + paint1.setPorterDuffXPFactory(SkBlendMode::kSrc); + + readRTC->fillRectToRect(GrNoClip(), std::move(paint1), GrAA::kNo, SkMatrix::I(), kRect, + kRect); + if (!readRTC->readPixels(ii, firstRead, 0, {0, 0})) { + return false; + } + + // Adding discard to appease vulkan validation warning about loading uninitialized data on + // draw + tempRTC->discard(); + + paint2.addColorTextureProcessor(readRTC->asTextureProxyRef(), kColorType, SkMatrix::I()); + paint2.addColorFragmentProcessor(std::move(upmToPM)); + paint2.setPorterDuffXPFactory(SkBlendMode::kSrc); + + tempRTC->fillRectToRect(GrNoClip(), std::move(paint2), GrAA::kNo, SkMatrix::I(), kRect, + kRect); + + paint3.addColorTextureProcessor(tempRTC->asTextureProxyRef(), kColorType, SkMatrix::I()); + paint3.addColorFragmentProcessor(std::move(pmToUPM)); + paint3.setPorterDuffXPFactory(SkBlendMode::kSrc); + + readRTC->fillRectToRect(GrNoClip(), std::move(paint3), GrAA::kNo, SkMatrix::I(), kRect, + kRect); + + if (!readRTC->readPixels(ii, secondRead, 0, {0, 0})) { + return false; + } + + for (int y = 0; y < kSize; ++y) { + for (int x = 0; x <= y; ++x) { + if (firstRead[kSize * y + x] != secondRead[kSize * y + x]) { + return false; + } + } + } + + return true; + } + + static std::unique_ptr Make(std::unique_ptr fp, + PMConversion pmConversion) { + if (!fp) { + return nullptr; + } + std::unique_ptr ccFP(new GrConfigConversionEffect(pmConversion)); + std::unique_ptr fpPipeline[] = {std::move(fp), std::move(ccFP)}; + return GrFragmentProcessor::RunInSeries(fpPipeline, 2); + } + GrConfigConversionEffect(const GrConfigConversionEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "ConfigConversionEffect"; } + PMConversion pmConversion; + +private: + GrConfigConversionEffect(PMConversion pmConversion) + : INHERITED(kGrConfigConversionEffect_ClassID, kNone_OptimizationFlags) + , pmConversion(pmConversion) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.cpp new file mode 100644 index 0000000000..8f3079bff3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.cpp @@ -0,0 +1,103 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrConstColorProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrConstColorProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLConstColorProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLConstColorProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrConstColorProcessor& _outer = args.fFp.cast(); + (void)_outer; + auto color = _outer.color; + (void)color; + auto mode = _outer.mode; + (void)mode; + colorVar = + args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "color"); + fragBuilder->codeAppendf( + "@switch (%d) {\n case 0:\n %s = %s;\n break;\n case 1:\n " + " %s = %s * %s;\n break;\n case 2:\n %s = %s.w * %s;\n " + "break;\n}\n", + (int)_outer.mode, args.fOutputColor, args.fUniformHandler->getUniformCStr(colorVar), + args.fOutputColor, args.fInputColor, args.fUniformHandler->getUniformCStr(colorVar), + args.fOutputColor, args.fInputColor, + args.fUniformHandler->getUniformCStr(colorVar)); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrConstColorProcessor& _outer = _proc.cast(); + { + const SkPMColor4f& colorValue = _outer.color; + if (colorPrev != colorValue) { + colorPrev = colorValue; + pdman.set4fv(colorVar, 1, colorValue.vec()); + } + } + } + SkPMColor4f colorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + UniformHandle colorVar; +}; +GrGLSLFragmentProcessor* GrConstColorProcessor::onCreateGLSLInstance() const { + return new GrGLSLConstColorProcessor(); +} +void GrConstColorProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)mode); +} +bool GrConstColorProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrConstColorProcessor& that = other.cast(); + (void)that; + if (color != that.color) return false; + if (mode != that.mode) return false; + return true; +} +GrConstColorProcessor::GrConstColorProcessor(const GrConstColorProcessor& src) + : INHERITED(kGrConstColorProcessor_ClassID, src.optimizationFlags()) + , color(src.color) + , mode(src.mode) {} +std::unique_ptr GrConstColorProcessor::clone() const { + return std::unique_ptr(new GrConstColorProcessor(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConstColorProcessor); +#if GR_TEST_UTILS +std::unique_ptr GrConstColorProcessor::TestCreate(GrProcessorTestData* d) { + SkPMColor4f color; + int colorPicker = d->fRandom->nextULessThan(3); + switch (colorPicker) { + case 0: { + uint32_t a = d->fRandom->nextULessThan(0x100); + uint32_t r = d->fRandom->nextULessThan(a + 1); + uint32_t g = d->fRandom->nextULessThan(a + 1); + uint32_t b = d->fRandom->nextULessThan(a + 1); + color = SkPMColor4f::FromBytes_RGBA(GrColorPackRGBA(r, g, b, a)); + break; + } + case 1: + color = SK_PMColor4fTRANSPARENT; + break; + case 2: + uint32_t c = d->fRandom->nextULessThan(0x100); + color = SkPMColor4f::FromBytes_RGBA(c | (c << 8) | (c << 16) | (c << 24)); + break; + } + InputMode mode = static_cast(d->fRandom->nextULessThan(kInputModeCnt)); + return GrConstColorProcessor::Make(color, mode); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.h new file mode 100644 index 0000000000..df64bce9b7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrConstColorProcessor.h @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrConstColorProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrConstColorProcessor_DEFINED +#define GrConstColorProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrConstColorProcessor : public GrFragmentProcessor { +public: + enum class InputMode { kIgnore = 0, kLast = 2, kModulateA = 2, kModulateRGBA = 1 }; + + static const int kInputModeCnt = (int)InputMode::kLast + 1; + + static OptimizationFlags OptFlags(const SkPMColor4f& color, InputMode mode) { + OptimizationFlags flags = kConstantOutputForConstantInput_OptimizationFlag; + if (mode != InputMode::kIgnore) { + flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag; + } + if (color.isOpaque()) { + flags |= kPreservesOpaqueInput_OptimizationFlag; + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + switch (mode) { + case InputMode::kIgnore: + return color; + case InputMode::kModulateA: + return color * input.fA; + case InputMode::kModulateRGBA: + return color * input; + } + SK_ABORT("Unexpected mode"); + } + static std::unique_ptr Make(SkPMColor4f color, InputMode mode) { + return std::unique_ptr(new GrConstColorProcessor(color, mode)); + } + GrConstColorProcessor(const GrConstColorProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "ConstColorProcessor"; } + SkPMColor4f color; + InputMode mode; + +private: + GrConstColorProcessor(SkPMColor4f color, InputMode mode) + : INHERITED(kGrConstColorProcessor_ClassID, (OptimizationFlags)OptFlags(color, mode)) + , color(color) + , mode(mode) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.cpp new file mode 100644 index 0000000000..b28f9612f1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrEllipseEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrEllipseEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLEllipseEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLEllipseEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrEllipseEffect& _outer = args.fFp.cast(); + (void)_outer; + auto edgeType = _outer.edgeType; + (void)edgeType; + auto center = _outer.center; + (void)center; + auto radii = _outer.radii; + (void)radii; + prevRadii = float2(-1.0); + medPrecision = !sk_Caps.floatIs32Bits; + ellipseVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "ellipse"); + if (medPrecision) { + scaleVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2_GrSLType, + "scale"); + } + fragBuilder->codeAppendf( + "float2 prevCenter;\nfloat2 prevRadii = float2(%f, %f);\nbool medPrecision = " + "%s;\nfloat2 d = sk_FragCoord.xy - %s.xy;\n@if (medPrecision) {\n d *= " + "%s.y;\n}\nfloat2 Z = d * %s.zw;\nfloat implicit = dot(Z, d) - 1.0;\nfloat " + "grad_dot = 4.0 * dot(Z, Z);\n@if (medPrecision) {\n grad_dot = max(grad_dot, " + "6.1036000261083245e-05);\n} else {\n grad_dot = max(grad_dot, " + "1.1754999560161448e-38);\n}\nfloat approx_dist = implicit * " + "inversesqrt(grad_dot);\n@if (medPrecision) {\n approx_dist *= %s.x;\n}\nhalf " + "alph", + prevRadii.fX, prevRadii.fY, (medPrecision ? "true" : "false"), + args.fUniformHandler->getUniformCStr(ellipseVar), + scaleVar.isValid() ? args.fUniformHandler->getUniformCStr(scaleVar) : "float2(0)", + args.fUniformHandler->getUniformCStr(ellipseVar), + scaleVar.isValid() ? args.fUniformHandler->getUniformCStr(scaleVar) : "float2(0)"); + fragBuilder->codeAppendf( + "a;\n@switch (%d) {\n case 0:\n alpha = approx_dist > 0.0 ? 0.0 : 1.0;\n " + " break;\n case 1:\n alpha = clamp(0.5 - half(approx_dist), 0.0, " + "1.0);\n break;\n case 2:\n alpha = approx_dist > 0.0 ? 1.0 : " + "0.0;\n break;\n case 3:\n alpha = clamp(0.5 + half(approx_dist), " + "0.0, 1.0);\n break;\n default:\n discard;\n}\n%s = %s * alpha;\n", + (int)_outer.edgeType, args.fOutputColor, args.fInputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrEllipseEffect& _outer = _proc.cast(); + auto edgeType = _outer.edgeType; + (void)edgeType; + auto center = _outer.center; + (void)center; + auto radii = _outer.radii; + (void)radii; + UniformHandle& ellipse = ellipseVar; + (void)ellipse; + UniformHandle& scale = scaleVar; + (void)scale; + + if (radii != prevRadii || center != prevCenter) { + float invRXSqd; + float invRYSqd; + // If we're using a scale factor to work around precision issues, choose the larger + // radius as the scale factor. The inv radii need to be pre-adjusted by the scale + // factor. + if (scale.isValid()) { + if (radii.fX > radii.fY) { + invRXSqd = 1.f; + invRYSqd = (radii.fX * radii.fX) / (radii.fY * radii.fY); + pdman.set2f(scale, radii.fX, 1.f / radii.fX); + } else { + invRXSqd = (radii.fY * radii.fY) / (radii.fX * radii.fX); + invRYSqd = 1.f; + pdman.set2f(scale, radii.fY, 1.f / radii.fY); + } + } else { + invRXSqd = 1.f / (radii.fX * radii.fX); + invRYSqd = 1.f / (radii.fY * radii.fY); + } + pdman.set4f(ellipse, center.fX, center.fY, invRXSqd, invRYSqd); + prevCenter = center; + prevRadii = radii; + } + } + SkPoint prevCenter = float2(0); + SkPoint prevRadii = float2(0); + bool medPrecision = false; + UniformHandle ellipseVar; + UniformHandle scaleVar; +}; +GrGLSLFragmentProcessor* GrEllipseEffect::onCreateGLSLInstance() const { + return new GrGLSLEllipseEffect(); +} +void GrEllipseEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)edgeType); +} +bool GrEllipseEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrEllipseEffect& that = other.cast(); + (void)that; + if (edgeType != that.edgeType) return false; + if (center != that.center) return false; + if (radii != that.radii) return false; + return true; +} +GrEllipseEffect::GrEllipseEffect(const GrEllipseEffect& src) + : INHERITED(kGrEllipseEffect_ClassID, src.optimizationFlags()) + , edgeType(src.edgeType) + , center(src.center) + , radii(src.radii) {} +std::unique_ptr GrEllipseEffect::clone() const { + return std::unique_ptr(new GrEllipseEffect(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrEllipseEffect); +#if GR_TEST_UTILS +std::unique_ptr GrEllipseEffect::TestCreate(GrProcessorTestData* testData) { + SkPoint center; + center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f); + center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f); + SkScalar rx = testData->fRandom->nextRangeF(0.f, 1000.f); + SkScalar ry = testData->fRandom->nextRangeF(0.f, 1000.f); + GrClipEdgeType et; + do { + et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt); + } while (GrClipEdgeType::kHairlineAA == et); + return GrEllipseEffect::Make(et, center, SkPoint::Make(rx, ry), + *testData->caps()->shaderCaps()); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.h new file mode 100644 index 0000000000..a86427268e --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrEllipseEffect.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrEllipseEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrEllipseEffect_DEFINED +#define GrEllipseEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrShaderCaps.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrEllipseEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(GrClipEdgeType edgeType, SkPoint center, + SkPoint radii, const GrShaderCaps& caps) { + // Small radii produce bad results on devices without full float. + if (!caps.floatIs32Bits() && (radii.fX < 0.5f || radii.fY < 0.5f)) { + return nullptr; + } + // Very narrow ellipses produce bad results on devices without full float + if (!caps.floatIs32Bits() && (radii.fX > 255 * radii.fY || radii.fY > 255 * radii.fX)) { + return nullptr; + } + // Very large ellipses produce bad results on devices without full float + if (!caps.floatIs32Bits() && (radii.fX > 16384 || radii.fY > 16384)) { + return nullptr; + } + return std::unique_ptr(new GrEllipseEffect(edgeType, center, radii)); + } + GrEllipseEffect(const GrEllipseEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "EllipseEffect"; } + GrClipEdgeType edgeType; + SkPoint center; + SkPoint radii; + +private: + GrEllipseEffect(GrClipEdgeType edgeType, SkPoint center, SkPoint radii) + : INHERITED(kGrEllipseEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , edgeType(edgeType) + , center(center) + , radii(radii) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp new file mode 100644 index 0000000000..3c9f24dc6a --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrLumaColorFilterEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrLumaColorFilterEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLLumaColorFilterEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLLumaColorFilterEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrLumaColorFilterEffect& _outer = args.fFp.cast(); + (void)_outer; + fragBuilder->codeAppendf( + "\nhalf luma = clamp(dot(half3(0.2125999927520752, 0.71520000696182251, " + "0.072200000286102295), %s.xyz), 0.0, 1.0);\n%s = half4(0.0, 0.0, 0.0, luma);\n", + args.fInputColor, args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrLumaColorFilterEffect::onCreateGLSLInstance() const { + return new GrGLSLLumaColorFilterEffect(); +} +void GrLumaColorFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrLumaColorFilterEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrLumaColorFilterEffect& that = other.cast(); + (void)that; + return true; +} +GrLumaColorFilterEffect::GrLumaColorFilterEffect(const GrLumaColorFilterEffect& src) + : INHERITED(kGrLumaColorFilterEffect_ClassID, src.optimizationFlags()) {} +std::unique_ptr GrLumaColorFilterEffect::clone() const { + return std::unique_ptr(new GrLumaColorFilterEffect(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.h new file mode 100644 index 0000000000..91939217d1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrLumaColorFilterEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrLumaColorFilterEffect_DEFINED +#define GrLumaColorFilterEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrLumaColorFilterEffect : public GrFragmentProcessor { +public: +#include "include/private/SkColorData.h" + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + float luma = SK_ITU_BT709_LUM_COEFF_R * input.fR + SK_ITU_BT709_LUM_COEFF_G * input.fG + + SK_ITU_BT709_LUM_COEFF_B * input.fB; + return {0, 0, 0, SkTPin(luma, 0.0f, 1.0f)}; + } + static std::unique_ptr Make() { + return std::unique_ptr(new GrLumaColorFilterEffect()); + } + GrLumaColorFilterEffect(const GrLumaColorFilterEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "LumaColorFilterEffect"; } + +private: + GrLumaColorFilterEffect() + : INHERITED(kGrLumaColorFilterEffect_ClassID, + (OptimizationFlags)kConstantOutputForConstantInput_OptimizationFlag) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.cpp new file mode 100644 index 0000000000..4f7a2eaefb --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrMagnifierEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrMagnifierEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLMagnifierEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLMagnifierEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrMagnifierEffect& _outer = args.fFp.cast(); + (void)_outer; + auto bounds = _outer.bounds; + (void)bounds; + auto srcRect = _outer.srcRect; + (void)srcRect; + auto xInvZoom = _outer.xInvZoom; + (void)xInvZoom; + auto yInvZoom = _outer.yInvZoom; + (void)yInvZoom; + auto xInvInset = _outer.xInvInset; + (void)xInvInset; + auto yInvInset = _outer.yInvInset; + (void)yInvInset; + boundsUniformVar = args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, kFloat4_GrSLType, "boundsUniform"); + xInvZoomVar = args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, kFloat_GrSLType, "xInvZoom"); + yInvZoomVar = args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, kFloat_GrSLType, "yInvZoom"); + xInvInsetVar = args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, kFloat_GrSLType, "xInvInset"); + yInvInsetVar = args.fUniformHandler->addUniform( + kFragment_GrShaderFlag, kFloat_GrSLType, "yInvInset"); + offsetVar = + args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "offset"); + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf( + "float2 coord = %s;\nfloat2 zoom_coord = float2(%s) + coord * float2(%s, " + "%s);\nfloat2 delta = (coord - %s.xy) * %s.zw;\ndelta = min(delta, " + "float2(half2(1.0, 1.0)) - delta);\ndelta *= float2(%s, %s);\nfloat weight = " + "0.0;\nif (delta.x < 2.0 && delta.y < 2.0) {\n delta = float2(half2(2.0, 2.0)) " + "- delta;\n float dist = length(delta);\n dist = max(2.0 - dist, 0.0);\n " + "weight = min(dist * dist, 1.0);\n} else {\n float2 delta_squared = delta * " + "delta;\n weight = min(min(delta_squared.x, delta_square", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + args.fUniformHandler->getUniformCStr(offsetVar), + args.fUniformHandler->getUniformCStr(xInvZoomVar), + args.fUniformHandler->getUniformCStr(yInvZoomVar), + args.fUniformHandler->getUniformCStr(boundsUniformVar), + args.fUniformHandler->getUniformCStr(boundsUniformVar), + args.fUniformHandler->getUniformCStr(xInvInsetVar), + args.fUniformHandler->getUniformCStr(yInvInsetVar)); + fragBuilder->codeAppendf( + "d.y), 1.0);\n}\n%s = sample(%s, mix(coord, zoom_coord, weight)).%s;\n", + args.fOutputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrMagnifierEffect& _outer = _proc.cast(); + { + pdman.set1f(xInvZoomVar, (_outer.xInvZoom)); + pdman.set1f(yInvZoomVar, (_outer.yInvZoom)); + pdman.set1f(xInvInsetVar, (_outer.xInvInset)); + pdman.set1f(yInvInsetVar, (_outer.yInvInset)); + } + GrSurfaceProxy& srcProxy = *_outer.textureSampler(0).proxy(); + GrTexture& src = *srcProxy.peekTexture(); + (void)src; + auto bounds = _outer.bounds; + (void)bounds; + UniformHandle& boundsUniform = boundsUniformVar; + (void)boundsUniform; + auto srcRect = _outer.srcRect; + (void)srcRect; + UniformHandle& xInvZoom = xInvZoomVar; + (void)xInvZoom; + UniformHandle& yInvZoom = yInvZoomVar; + (void)yInvZoom; + UniformHandle& xInvInset = xInvInsetVar; + (void)xInvInset; + UniformHandle& yInvInset = yInvInsetVar; + (void)yInvInset; + UniformHandle& offset = offsetVar; + (void)offset; + + SkScalar invW = 1.0f / src.width(); + SkScalar invH = 1.0f / src.height(); + + { + SkScalar y = srcRect.y() * invH; + if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) { + y = 1.0f - (srcRect.height() / bounds.height()) - y; + } + + pdman.set2f(offset, srcRect.x() * invW, y); + } + + { + SkScalar y = bounds.y() * invH; + SkScalar hSign = 1.f; + if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) { + y = 1.0f - bounds.y() * invH; + hSign = -1.f; + } + + pdman.set4f(boundsUniform, + bounds.x() * invW, + y, + SkIntToScalar(src.width()) / bounds.width(), + hSign * SkIntToScalar(src.height()) / bounds.height()); + } + } + UniformHandle boundsUniformVar; + UniformHandle offsetVar; + UniformHandle xInvZoomVar; + UniformHandle yInvZoomVar; + UniformHandle xInvInsetVar; + UniformHandle yInvInsetVar; +}; +GrGLSLFragmentProcessor* GrMagnifierEffect::onCreateGLSLInstance() const { + return new GrGLSLMagnifierEffect(); +} +void GrMagnifierEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrMagnifierEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrMagnifierEffect& that = other.cast(); + (void)that; + if (src != that.src) return false; + if (bounds != that.bounds) return false; + if (srcRect != that.srcRect) return false; + if (xInvZoom != that.xInvZoom) return false; + if (yInvZoom != that.yInvZoom) return false; + if (xInvInset != that.xInvInset) return false; + if (yInvInset != that.yInvInset) return false; + return true; +} +GrMagnifierEffect::GrMagnifierEffect(const GrMagnifierEffect& src) + : INHERITED(kGrMagnifierEffect_ClassID, src.optimizationFlags()) + , srcCoordTransform(src.srcCoordTransform) + , src(src.src) + , bounds(src.bounds) + , srcRect(src.srcRect) + , xInvZoom(src.xInvZoom) + , yInvZoom(src.yInvZoom) + , xInvInset(src.xInvInset) + , yInvInset(src.yInvInset) { + this->setTextureSamplerCnt(1); + this->addCoordTransform(&srcCoordTransform); +} +std::unique_ptr GrMagnifierEffect::clone() const { + return std::unique_ptr(new GrMagnifierEffect(*this)); +} +const GrFragmentProcessor::TextureSampler& GrMagnifierEffect::onTextureSampler(int index) const { + return IthTextureSampler(index, src); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMagnifierEffect); +#if GR_TEST_UTILS +std::unique_ptr GrMagnifierEffect::TestCreate(GrProcessorTestData* d) { + sk_sp proxy = d->textureProxy(0); + const int kMaxWidth = 200; + const int kMaxHeight = 200; + const SkScalar kMaxInset = 20.0f; + uint32_t width = d->fRandom->nextULessThan(kMaxWidth); + uint32_t height = d->fRandom->nextULessThan(kMaxHeight); + SkScalar inset = d->fRandom->nextRangeScalar(1.0f, kMaxInset); + + SkIRect bounds = SkIRect::MakeWH(SkIntToScalar(kMaxWidth), SkIntToScalar(kMaxHeight)); + SkRect srcRect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); + + auto effect = GrMagnifierEffect::Make(std::move(proxy), + bounds, + srcRect, + srcRect.width() / bounds.width(), + srcRect.height() / bounds.height(), + bounds.width() / inset, + bounds.height() / inset); + SkASSERT(effect); + return effect; +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.h new file mode 100644 index 0000000000..cb0d00524b --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrMagnifierEffect.h @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrMagnifierEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrMagnifierEffect_DEFINED +#define GrMagnifierEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrMagnifierEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp src, SkIRect bounds, + SkRect srcRect, float xInvZoom, float yInvZoom, + float xInvInset, float yInvInset) { + return std::unique_ptr(new GrMagnifierEffect( + src, bounds, srcRect, xInvZoom, yInvZoom, xInvInset, yInvInset)); + } + GrMagnifierEffect(const GrMagnifierEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "MagnifierEffect"; } + GrCoordTransform srcCoordTransform; + TextureSampler src; + SkIRect bounds; + SkRect srcRect; + float xInvZoom; + float yInvZoom; + float xInvInset; + float yInvInset; + +private: + GrMagnifierEffect(sk_sp src, SkIRect bounds, SkRect srcRect, float xInvZoom, + float yInvZoom, float xInvInset, float yInvInset) + : INHERITED(kGrMagnifierEffect_ClassID, kNone_OptimizationFlags) + , srcCoordTransform(SkMatrix::I(), src.get()) + , src(std::move(src)) + , bounds(bounds) + , srcRect(srcRect) + , xInvZoom(xInvZoom) + , yInvZoom(yInvZoom) + , xInvInset(xInvInset) + , yInvInset(yInvInset) { + this->setTextureSamplerCnt(1); + this->addCoordTransform(&srcCoordTransform); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.cpp new file mode 100644 index 0000000000..88ca234d2f --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrMixerEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrMixerEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLMixerEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLMixerEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrMixerEffect& _outer = args.fFp.cast(); + (void)_outer; + auto weight = _outer.weight; + (void)weight; + weightVar = + args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "weight"); + SkString _input1278 = SkStringPrintf("%s", args.fInputColor); + SkString _sample1278("_sample1278"); + this->invokeChild(_outer.fp0_index, _input1278.c_str(), &_sample1278, args); + fragBuilder->codeAppendf("half4 in0 = %s;", _sample1278.c_str()); + SkString _input1335 = SkStringPrintf("%s", args.fInputColor); + SkString _sample1335("_sample1335"); + if (_outer.fp1_index >= 0) { + this->invokeChild(_outer.fp1_index, _input1335.c_str(), &_sample1335, args); + } else { + fragBuilder->codeAppendf("half4 %s;", _sample1335.c_str()); + } + fragBuilder->codeAppendf("\nhalf4 in1 = %s ? %s : %s;\n%s = mix(in0, in1, %s);\n", + _outer.fp1_index >= 0 ? "true" : "false", _sample1335.c_str(), + args.fInputColor, args.fOutputColor, + args.fUniformHandler->getUniformCStr(weightVar)); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrMixerEffect& _outer = _proc.cast(); + { pdman.set1f(weightVar, (_outer.weight)); } + } + UniformHandle weightVar; +}; +GrGLSLFragmentProcessor* GrMixerEffect::onCreateGLSLInstance() const { + return new GrGLSLMixerEffect(); +} +void GrMixerEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrMixerEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrMixerEffect& that = other.cast(); + (void)that; + if (weight != that.weight) return false; + return true; +} +GrMixerEffect::GrMixerEffect(const GrMixerEffect& src) + : INHERITED(kGrMixerEffect_ClassID, src.optimizationFlags()) + , fp0_index(src.fp0_index) + , fp1_index(src.fp1_index) + , weight(src.weight) { + this->registerChildProcessor(src.childProcessor(fp0_index).clone()); + if (fp1_index >= 0) { + this->registerChildProcessor(src.childProcessor(fp1_index).clone()); + } +} +std::unique_ptr GrMixerEffect::clone() const { + return std::unique_ptr(new GrMixerEffect(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.h new file mode 100644 index 0000000000..44d345b761 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrMixerEffect.h @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrMixerEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrMixerEffect_DEFINED +#define GrMixerEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrMixerEffect : public GrFragmentProcessor { +public: + static OptimizationFlags OptFlags(const std::unique_ptr& fp0, + const std::unique_ptr& fp1) { + auto flags = ProcessorOptimizationFlags(fp0.get()); + if (fp1) { + flags &= ProcessorOptimizationFlags(fp1.get()); + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + const auto c0 = ConstantOutputForConstantInput(this->childProcessor(0), input), + c1 = (this->numChildProcessors() > 1) + ? ConstantOutputForConstantInput(this->childProcessor(1), input) + : input; + return {c0.fR + (c1.fR - c0.fR) * weight, c0.fG + (c1.fG - c0.fG) * weight, + c0.fB + (c1.fB - c0.fB) * weight, c0.fA + (c1.fA - c0.fA) * weight}; + } + static std::unique_ptr Make(std::unique_ptr fp0, + std::unique_ptr + fp1, + float weight) { + return std::unique_ptr( + new GrMixerEffect(std::move(fp0), std::move(fp1), weight)); + } + GrMixerEffect(const GrMixerEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "MixerEffect"; } + int fp0_index = -1; + int fp1_index = -1; + float weight; + +private: + GrMixerEffect(std::unique_ptr fp0, + std::unique_ptr + fp1, + float weight) + : INHERITED(kGrMixerEffect_ClassID, (OptimizationFlags)OptFlags(fp0, fp1)) + , weight(weight) { + SkASSERT(fp0); + fp0_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(fp0)); + if (fp1) { + fp1_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(fp1)); + } + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp new file mode 100644 index 0000000000..56a55dc871 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrOverrideInputFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrOverrideInputFragmentProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLOverrideInputFragmentProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLOverrideInputFragmentProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrOverrideInputFragmentProcessor& _outer = + args.fFp.cast(); + (void)_outer; + auto useUniform = _outer.useUniform; + (void)useUniform; + auto uniformColor = _outer.uniformColor; + (void)uniformColor; + auto literalColor = _outer.literalColor; + (void)literalColor; + if (useUniform) { + uniformColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, "uniformColor"); + } + fragBuilder->codeAppendf( + "half4 constColor;\n@if (%s) {\n constColor = %s;\n} else {\n constColor = " + "half4(%f, %f, %f, %f);\n}", + (_outer.useUniform ? "true" : "false"), + uniformColorVar.isValid() ? args.fUniformHandler->getUniformCStr(uniformColorVar) + : "half4(0)", + _outer.literalColor.fR, _outer.literalColor.fG, _outer.literalColor.fB, + _outer.literalColor.fA); + SkString _input1992("constColor"); + SkString _sample1992("_sample1992"); + this->invokeChild(_outer.fp_index, _input1992.c_str(), &_sample1992, args); + fragBuilder->codeAppendf("\n%s = %s;\n", args.fOutputColor, _sample1992.c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrOverrideInputFragmentProcessor& _outer = + _proc.cast(); + { + if (uniformColorVar.isValid()) { + pdman.set4fv(uniformColorVar, 1, (_outer.uniformColor).vec()); + } + } + } + UniformHandle uniformColorVar; +}; +GrGLSLFragmentProcessor* GrOverrideInputFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLSLOverrideInputFragmentProcessor(); +} +void GrOverrideInputFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)useUniform); + if (!useUniform) { + uint16_t red = SkFloatToHalf(literalColor.fR); + uint16_t green = SkFloatToHalf(literalColor.fG); + uint16_t blue = SkFloatToHalf(literalColor.fB); + uint16_t alpha = SkFloatToHalf(literalColor.fA); + b->add32(((uint32_t)red << 16) | green); + b->add32(((uint32_t)blue << 16) | alpha); + } +} +bool GrOverrideInputFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrOverrideInputFragmentProcessor& that = other.cast(); + (void)that; + if (useUniform != that.useUniform) return false; + if (uniformColor != that.uniformColor) return false; + if (literalColor != that.literalColor) return false; + return true; +} +GrOverrideInputFragmentProcessor::GrOverrideInputFragmentProcessor( + const GrOverrideInputFragmentProcessor& src) + : INHERITED(kGrOverrideInputFragmentProcessor_ClassID, src.optimizationFlags()) + , fp_index(src.fp_index) + , useUniform(src.useUniform) + , uniformColor(src.uniformColor) + , literalColor(src.literalColor) { + this->registerChildProcessor(src.childProcessor(fp_index).clone()); +} +std::unique_ptr GrOverrideInputFragmentProcessor::clone() const { + return std::unique_ptr(new GrOverrideInputFragmentProcessor(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h new file mode 100644 index 0000000000..7e4106fd74 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrOverrideInputFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrOverrideInputFragmentProcessor_DEFINED +#define GrOverrideInputFragmentProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrOverrideInputFragmentProcessor : public GrFragmentProcessor { +public: + static OptimizationFlags OptFlags(const std::unique_ptr& fp, + const SkPMColor4f& color) { + auto childFlags = ProcessorOptimizationFlags(fp.get()); + auto flags = kNone_OptimizationFlags; + if (childFlags & kConstantOutputForConstantInput_OptimizationFlag) { + flags |= kConstantOutputForConstantInput_OptimizationFlag; + } + if ((childFlags & kPreservesOpaqueInput_OptimizationFlag) && color.isOpaque()) { + flags |= kPreservesOpaqueInput_OptimizationFlag; + } + return flags; + } + + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return ConstantOutputForConstantInput(this->childProcessor(0), uniformColor); + } + + static std::unique_ptr Make(std::unique_ptr fp, + const SkPMColor4f& color, + bool useUniform = true) { + return std::unique_ptr( + new GrOverrideInputFragmentProcessor(std::move(fp), useUniform, color, color)); + } + GrOverrideInputFragmentProcessor(const GrOverrideInputFragmentProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "OverrideInputFragmentProcessor"; } + int fp_index = -1; + bool useUniform; + SkPMColor4f uniformColor; + SkPMColor4f literalColor; + +private: + GrOverrideInputFragmentProcessor(std::unique_ptr fp, + bool useUniform, + SkPMColor4f uniformColor, + SkPMColor4f literalColor) + : INHERITED(kGrOverrideInputFragmentProcessor_ClassID, + (OptimizationFlags)OptFlags(fp, useUniform ? uniformColor : literalColor)) + , useUniform(useUniform) + , uniformColor(uniformColor) + , literalColor(literalColor) { + SkASSERT(fp); + fp_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(fp)); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp new file mode 100644 index 0000000000..728e6712bd --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrPremulInputFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrPremulInputFragmentProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLPremulInputFragmentProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLPremulInputFragmentProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrPremulInputFragmentProcessor& _outer = + args.fFp.cast(); + (void)_outer; + fragBuilder->codeAppendf("%s = %s;\n%s.xyz *= %s.w;\n", args.fOutputColor, args.fInputColor, + args.fOutputColor, args.fInputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrPremulInputFragmentProcessor::onCreateGLSLInstance() const { + return new GrGLSLPremulInputFragmentProcessor(); +} +void GrPremulInputFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrPremulInputFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrPremulInputFragmentProcessor& that = other.cast(); + (void)that; + return true; +} +GrPremulInputFragmentProcessor::GrPremulInputFragmentProcessor( + const GrPremulInputFragmentProcessor& src) + : INHERITED(kGrPremulInputFragmentProcessor_ClassID, src.optimizationFlags()) {} +std::unique_ptr GrPremulInputFragmentProcessor::clone() const { + return std::unique_ptr(new GrPremulInputFragmentProcessor(*this)); +} diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h new file mode 100644 index 0000000000..ccb876787d --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrPremulInputFragmentProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrPremulInputFragmentProcessor_DEFINED +#define GrPremulInputFragmentProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrPremulInputFragmentProcessor : public GrFragmentProcessor { +public: + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return SkColor4f{input.fR, input.fG, input.fB, input.fA}.premul(); + } + static std::unique_ptr Make() { + return std::unique_ptr(new GrPremulInputFragmentProcessor()); + } + GrPremulInputFragmentProcessor(const GrPremulInputFragmentProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "PremulInputFragmentProcessor"; } + +private: + GrPremulInputFragmentProcessor() + : INHERITED(kGrPremulInputFragmentProcessor_ClassID, + (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag | + kConstantOutputForConstantInput_OptimizationFlag) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.cpp new file mode 100644 index 0000000000..88ab11a8f2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrRRectBlurEffect.h" + +std::unique_ptr GrRRectBlurEffect::Make(GrRecordingContext* context, + float sigma, + float xformedSigma, + const SkRRect& srcRRect, + const SkRRect& devRRect) { + SkASSERT(!SkRRectPriv::IsCircle(devRRect) && + !devRRect.isRect()); // Should've been caught up-stream + + // TODO: loosen this up + if (!SkRRectPriv::IsSimpleCircular(devRRect)) { + return nullptr; + } + + // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be + // sufficiently small relative to both the size of the corner radius and the + // width (and height) of the rrect. + SkRRect rrectToDraw; + SkISize size; + SkScalar ignored[kSkBlurRRectMaxDivisions]; + int ignoredSize; + uint32_t ignored32; + + bool ninePatchable = SkComputeBlurredRRectParams( + srcRRect, devRRect, SkRect::MakeEmpty(), sigma, xformedSigma, &rrectToDraw, &size, + ignored, ignored, ignored, ignored, &ignoredSize, &ignoredSize, &ignored32); + if (!ninePatchable) { + return nullptr; + } + + sk_sp mask( + find_or_create_rrect_blur_mask(context, rrectToDraw, size, xformedSigma)); + if (!mask) { + return nullptr; + } + + return std::unique_ptr( + new GrRRectBlurEffect(xformedSigma, devRRect.getBounds(), + SkRRectPriv::GetSimpleRadii(devRRect).fX, std::move(mask))); +} +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLRRectBlurEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLRRectBlurEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrRRectBlurEffect& _outer = args.fFp.cast(); + (void)_outer; + auto sigma = _outer.sigma; + (void)sigma; + auto rect = _outer.rect; + (void)rect; + auto cornerRadius = _outer.cornerRadius; + (void)cornerRadius; + cornerRadiusVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "cornerRadius"); + proxyRectVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "proxyRect"); + blurRadiusVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "blurRadius"); + fragBuilder->codeAppendf( + "\nhalf2 translatedFragPos = half2(sk_FragCoord.xy - %s.xy);\nhalf threshold = %s " + "+ 2.0 * %s;\nhalf2 middle = half2((%s.zw - %s.xy) - float(2.0 * threshold));\nif " + "(translatedFragPos.x >= threshold && translatedFragPos.x < middle.x + threshold) " + "{\n translatedFragPos.x = threshold;\n} else if (translatedFragPos.x >= " + "middle.x + threshold) {\n translatedFragPos.x -= middle.x - 1.0;\n}\nif " + "(translatedFragPos.y > threshold && translatedFragPos.y < middle.y + threshold) " + "{\n translatedFragPos.y = threshold;", + args.fUniformHandler->getUniformCStr(proxyRectVar), + args.fUniformHandler->getUniformCStr(cornerRadiusVar), + args.fUniformHandler->getUniformCStr(blurRadiusVar), + args.fUniformHandler->getUniformCStr(proxyRectVar), + args.fUniformHandler->getUniformCStr(proxyRectVar)); + fragBuilder->codeAppendf( + "\n} else if (translatedFragPos.y >= middle.y + threshold) {\n " + "translatedFragPos.y -= middle.y - 1.0;\n}\nhalf2 proxyDims = half2(2.0 * " + "threshold + 1.0);\nhalf2 texCoord = translatedFragPos / proxyDims;\n%s = %s * " + "sample(%s, float2(texCoord)).%s;\n", + args.fOutputColor, args.fInputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrRRectBlurEffect& _outer = _proc.cast(); + { pdman.set1f(cornerRadiusVar, (_outer.cornerRadius)); } + auto sigma = _outer.sigma; + (void)sigma; + auto rect = _outer.rect; + (void)rect; + UniformHandle& cornerRadius = cornerRadiusVar; + (void)cornerRadius; + GrSurfaceProxy& ninePatchSamplerProxy = *_outer.textureSampler(0).proxy(); + GrTexture& ninePatchSampler = *ninePatchSamplerProxy.peekTexture(); + (void)ninePatchSampler; + UniformHandle& proxyRect = proxyRectVar; + (void)proxyRect; + UniformHandle& blurRadius = blurRadiusVar; + (void)blurRadius; + + float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f); + pdman.set1f(blurRadius, blurRadiusValue); + + SkRect outset = rect; + outset.outset(blurRadiusValue, blurRadiusValue); + pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom); + } + UniformHandle proxyRectVar; + UniformHandle blurRadiusVar; + UniformHandle cornerRadiusVar; +}; +GrGLSLFragmentProcessor* GrRRectBlurEffect::onCreateGLSLInstance() const { + return new GrGLSLRRectBlurEffect(); +} +void GrRRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrRRectBlurEffect& that = other.cast(); + (void)that; + if (sigma != that.sigma) return false; + if (rect != that.rect) return false; + if (cornerRadius != that.cornerRadius) return false; + if (ninePatchSampler != that.ninePatchSampler) return false; + return true; +} +GrRRectBlurEffect::GrRRectBlurEffect(const GrRRectBlurEffect& src) + : INHERITED(kGrRRectBlurEffect_ClassID, src.optimizationFlags()) + , sigma(src.sigma) + , rect(src.rect) + , cornerRadius(src.cornerRadius) + , ninePatchSampler(src.ninePatchSampler) { + this->setTextureSamplerCnt(1); +} +std::unique_ptr GrRRectBlurEffect::clone() const { + return std::unique_ptr(new GrRRectBlurEffect(*this)); +} +const GrFragmentProcessor::TextureSampler& GrRRectBlurEffect::onTextureSampler(int index) const { + return IthTextureSampler(index, ninePatchSampler); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect); +#if GR_TEST_UTILS +std::unique_ptr GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) { + SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f); + SkScalar r = d->fRandom->nextRangeF(1.f, 9.f); + SkScalar sigma = d->fRandom->nextRangeF(1.f, 10.f); + SkRRect rrect; + rrect.setRectXY(SkRect::MakeWH(w, h), r, r); + return GrRRectBlurEffect::Make(d->context(), sigma, sigma, rrect, rrect); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.h new file mode 100644 index 0000000000..436f604800 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrRRectBlurEffect.h @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrRRectBlurEffect_DEFINED +#define GrRRectBlurEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "include/effects/SkBlurMaskFilter.h" +#include "include/gpu/GrContext.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkBlurPriv.h" +#include "src/core/SkGpuBlurUtils.h" +#include "src/core/SkRRectPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrStyle.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrRRectBlurEffect : public GrFragmentProcessor { +public: + static sk_sp find_or_create_rrect_blur_mask(GrRecordingContext* context, + const SkRRect& rrectToDraw, + const SkISize& size, + float xformedSigma) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 9, "RoundRect Blur Mask"); + builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f); + + int index = 1; + for (auto c : {SkRRect::kUpperLeft_Corner, SkRRect::kUpperRight_Corner, + SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner}) { + SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) && + SkScalarIsInt(rrectToDraw.radii(c).fY)); + builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX); + builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY); + } + builder.finish(); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + sk_sp mask(proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kBottomLeft_GrSurfaceOrigin)); + if (!mask) { + // TODO: this could be SkBackingFit::kApprox, but: + // 1) The texture coords would need to be updated. + // 2) We would have to use GrTextureDomain::kClamp_Mode for the GaussianBlur. + auto rtc = context->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, size.fWidth, size.fHeight, GrColorType::kAlpha_8, + nullptr); + if (!rtc) { + return nullptr; + } + + GrPaint paint; + + rtc->clear(nullptr, SK_PMColor4fTRANSPARENT, + GrRenderTargetContext::CanClearFullscreen::kYes); + rtc->drawRRect(GrNoClip(), std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw, + GrStyle::SimpleFill()); + + sk_sp srcProxy(rtc->asTextureProxyRef()); + if (!srcProxy) { + return nullptr; + } + auto rtc2 = SkGpuBlurUtils::GaussianBlur(context, + std::move(srcProxy), + rtc->colorInfo().colorType(), + rtc->colorInfo().alphaType(), + SkIPoint::Make(0, 0), + nullptr, + SkIRect::MakeWH(size.fWidth, size.fHeight), + SkIRect::EmptyIRect(), + xformedSigma, + xformedSigma, + GrTextureDomain::kIgnore_Mode, + SkBackingFit::kExact); + if (!rtc2) { + return nullptr; + } + + mask = rtc2->asTextureProxyRef(); + if (!mask) { + return nullptr; + } + SkASSERT(mask->origin() == kBottomLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, mask.get()); + } + + return mask; + } + + static std::unique_ptr Make(GrRecordingContext* context, + float sigma, + float xformedSigma, + const SkRRect& srcRRect, + const SkRRect& devRRect); + GrRRectBlurEffect(const GrRRectBlurEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "RRectBlurEffect"; } + float sigma; + SkRect rect; + float cornerRadius; + TextureSampler ninePatchSampler; + +private: + GrRRectBlurEffect(float sigma, SkRect rect, float cornerRadius, + sk_sp ninePatchSampler) + : INHERITED(kGrRRectBlurEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , sigma(sigma) + , rect(rect) + , cornerRadius(cornerRadius) + , ninePatchSampler(std::move(ninePatchSampler)) { + this->setTextureSamplerCnt(1); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.cpp new file mode 100644 index 0000000000..186dcfe365 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrRectBlurEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrRectBlurEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLRectBlurEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLRectBlurEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrRectBlurEffect& _outer = args.fFp.cast(); + (void)_outer; + auto rect = _outer.rect; + (void)rect; + auto invSixSigma = _outer.invSixSigma; + (void)invSixSigma; + auto isFast = _outer.isFast; + (void)isFast; + highp = ((abs(rect.left()) > 16000.0 || abs(rect.top()) > 16000.0) || + abs(rect.right()) > 16000.0) || + abs(rect.bottom()) > 16000.0; + if (highp) { + rectFVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "rectF"); + } + if (!highp) { + rectHVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, + "rectH"); + } + invSixSigmaVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "invSixSigma"); + fragBuilder->codeAppendf( + "/* key */ bool highp = %s;\nhalf xCoverage, yCoverage;\n@if (%s) {\n half x, " + "y;\n @if (highp) {\n x = max(half(%s.x - sk_FragCoord.x), " + "half(sk_FragCoord.x - %s.z));\n y = max(half(%s.y - sk_FragCoord.y), " + "half(sk_FragCoord.y - %s.w));\n } else {\n x = max(half(float(%s.x) - " + "sk_FragCoord.x), half(sk_FragCoord.x - float(%s.z)));\n y = " + "max(half(float(%s.y) - sk_FragCoord.y), half(sk_FragCoord.y - float(%s.w)));\n " + "}\n xCoverage = sample(%s, float2(half2(x * %s, 0.5))).", + (highp ? "true" : "false"), (_outer.isFast ? "true" : "false"), + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + args.fUniformHandler->getUniformCStr(invSixSigmaVar)); + fragBuilder->codeAppendf( + "%s.w;\n yCoverage = sample(%s, float2(half2(y * %s, 0.5))).%s.w;\n %s = (%s " + "* xCoverage) * yCoverage;\n} else {\n half l, r, t, b;\n @if (highp) {\n " + " l = half(sk_FragCoord.x - %s.x);\n r = half(%s.z - sk_FragCoord.x);\n " + " t = half(sk_FragCoord.y - %s.y);\n b = half(%s.w - " + "sk_FragCoord.y);\n } else {\n l = half(sk_FragCoord.x - float(%s.x));\n " + " r = half(float(%s.z) - sk_FragCoord.x);\n t = half(sk_FragCoord.y - " + "float(%s.y));\n b = half(float(", + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + args.fUniformHandler->getUniformCStr(invSixSigmaVar), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + args.fOutputColor, args.fInputColor, + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectFVar.isValid() ? args.fUniformHandler->getUniformCStr(rectFVar) : "float4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)"); + fragBuilder->codeAppendf( + "%s.w) - sk_FragCoord.y);\n }\n half il = 1.0 + l * %s;\n half ir = 1.0 + " + "r * %s;\n half it = 1.0 + t * %s;\n half ib = 1.0 + b * %s;\n xCoverage " + "= (1.0 - sample(%s, float2(half2(il, 0.5))).%s.w) - sample(%s, float2(half2(ir, " + "0.5))).%s.w;\n yCoverage = (1.0 - sample(%s, float2(half2(it, 0.5))).%s.w) - " + "sample(%s, float2(half2(ib, 0.5))).%s.w;\n}\n%s = (%s * xCoverage) * yCoverage;\n", + rectHVar.isValid() ? args.fUniformHandler->getUniformCStr(rectHVar) : "half4(0)", + args.fUniformHandler->getUniformCStr(invSixSigmaVar), + args.fUniformHandler->getUniformCStr(invSixSigmaVar), + args.fUniformHandler->getUniformCStr(invSixSigmaVar), + args.fUniformHandler->getUniformCStr(invSixSigmaVar), + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(), + args.fOutputColor, args.fInputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrRectBlurEffect& _outer = _proc.cast(); + { pdman.set1f(invSixSigmaVar, (_outer.invSixSigma)); } + auto rect = _outer.rect; + (void)rect; + UniformHandle& rectF = rectFVar; + (void)rectF; + UniformHandle& rectH = rectHVar; + (void)rectH; + GrSurfaceProxy& integralProxy = *_outer.textureSampler(0).proxy(); + GrTexture& integral = *integralProxy.peekTexture(); + (void)integral; + UniformHandle& invSixSigma = invSixSigmaVar; + (void)invSixSigma; + auto isFast = _outer.isFast; + (void)isFast; + + float r[]{rect.fLeft, rect.fTop, rect.fRight, rect.fBottom}; + pdman.set4fv(highp ? rectF : rectH, 1, r); + } + bool highp = false; + UniformHandle rectFVar; + UniformHandle rectHVar; + UniformHandle invSixSigmaVar; +}; +GrGLSLFragmentProcessor* GrRectBlurEffect::onCreateGLSLInstance() const { + return new GrGLSLRectBlurEffect(); +} +void GrRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + bool highp = ((abs(rect.left()) > 16000.0 || abs(rect.top()) > 16000.0) || + abs(rect.right()) > 16000.0) || + abs(rect.bottom()) > 16000.0; + b->add32((int32_t)highp); + b->add32((int32_t)isFast); +} +bool GrRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrRectBlurEffect& that = other.cast(); + (void)that; + if (rect != that.rect) return false; + if (integral != that.integral) return false; + if (invSixSigma != that.invSixSigma) return false; + if (isFast != that.isFast) return false; + return true; +} +GrRectBlurEffect::GrRectBlurEffect(const GrRectBlurEffect& src) + : INHERITED(kGrRectBlurEffect_ClassID, src.optimizationFlags()) + , rect(src.rect) + , integral(src.integral) + , invSixSigma(src.invSixSigma) + , isFast(src.isFast) { + this->setTextureSamplerCnt(1); +} +std::unique_ptr GrRectBlurEffect::clone() const { + return std::unique_ptr(new GrRectBlurEffect(*this)); +} +const GrFragmentProcessor::TextureSampler& GrRectBlurEffect::onTextureSampler(int index) const { + return IthTextureSampler(index, integral); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRectBlurEffect); +#if GR_TEST_UTILS +std::unique_ptr GrRectBlurEffect::TestCreate(GrProcessorTestData* data) { + float sigma = data->fRandom->nextRangeF(3, 8); + float width = data->fRandom->nextRangeF(200, 300); + float height = data->fRandom->nextRangeF(200, 300); + return GrRectBlurEffect::Make(data->proxyProvider(), *data->caps()->shaderCaps(), + SkRect::MakeWH(width, height), sigma); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.h new file mode 100644 index 0000000000..b0c86bda1c --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrRectBlurEffect.h @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrRectBlurEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrRectBlurEffect_DEFINED +#define GrRectBlurEffect_DEFINED +#include "include/core/SkTypes.h" + +#include +#include "include/core/SkRect.h" +#include "include/core/SkScalar.h" +#include "src/core/SkBlurMask.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrShaderCaps.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrRectBlurEffect : public GrFragmentProcessor { +public: + static sk_sp CreateIntegralTexture(GrProxyProvider* proxyProvider, + float sixSigma) { + // The texture we're producing represents the integral of a normal distribution over a + // six-sigma range centered at zero. We want enough resolution so that the linear + // interpolation done in texture lookup doesn't introduce noticeable artifacts. We + // conservatively choose to have 2 texels for each dst pixel. + int minWidth = 2 * sk_float_ceil2int(sixSigma); + // Bin by powers of 2 with a minimum so we get good profile reuse. + int width = SkTMax(SkNextPow2(minWidth), 32); + + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask"); + builder[0] = width; + builder.finish(); + + sk_sp proxy(proxyProvider->findOrCreateProxyByUniqueKey( + key, GrColorType::kAlpha_8, kTopLeft_GrSurfaceOrigin)); + if (!proxy) { + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(width, 1))) { + return nullptr; + } + *bitmap.getAddr8(0, 0) = 255; + const float invWidth = 1.f / width; + for (int i = 1; i < width - 1; ++i) { + float x = (i + 0.5f) * invWidth; + x = (-6 * x + 3) * SK_ScalarRoot2Over2; + float integral = 0.5f * (std::erf(x) + 1.f); + *bitmap.getAddr8(i, 0) = SkToU8(sk_float_round2int(255.f * integral)); + } + *bitmap.getAddr8(width - 1, 0) = 0; + bitmap.setImmutable(); + proxy = proxyProvider->createProxyFromBitmap(bitmap, GrMipMapped::kNo); + if (!proxy) { + return nullptr; + } + SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin); + proxyProvider->assignUniqueKeyToProxy(key, proxy.get()); + } + return proxy; + } + + static std::unique_ptr Make(GrProxyProvider* proxyProvider, + const GrShaderCaps& caps, const SkRect& rect, + float sigma) { + SkASSERT(rect.isSorted()); + if (!caps.floatIs32Bits()) { + // We promote the math that gets us into the Gaussian space to full float when the rect + // coords are large. If we don't have full float then fail. We could probably clip the + // rect to an outset device bounds instead. + if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f || + SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f) { + return nullptr; + } + } + + const float sixSigma = 6 * sigma; + auto integral = CreateIntegralTexture(proxyProvider, sixSigma); + if (!integral) { + return nullptr; + } + + // In the fast variant we think of the midpoint of the integral texture as aligning + // with the closest rect edge both in x and y. To simplify texture coord calculation we + // inset the rect so that the edge of the inset rect corresponds to t = 0 in the texture. + // It actually simplifies things a bit in the !isFast case, too. + float threeSigma = sixSigma / 2; + SkRect insetRect = {rect.fLeft + threeSigma, rect.fTop + threeSigma, + rect.fRight - threeSigma, rect.fBottom - threeSigma}; + + // In our fast variant we find the nearest horizontal and vertical edges and for each + // do a lookup in the integral texture for each and multiply them. When the rect is + // less than 6 sigma wide then things aren't so simple and we have to consider both the + // left and right edge of the rectangle (and similar in y). + bool isFast = insetRect.isSorted(); + // 1 / (6 * sigma) is the domain of the integral texture. We use the inverse to produce + // normalized texture coords from frag coord distances. + float invSixSigma = 1.f / sixSigma; + return std::unique_ptr( + new GrRectBlurEffect(insetRect, std::move(integral), invSixSigma, isFast, + GrSamplerState::ClampBilerp())); + } + GrRectBlurEffect(const GrRectBlurEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "RectBlurEffect"; } + SkRect rect; + TextureSampler integral; + float invSixSigma; + bool isFast; + +private: + GrRectBlurEffect(SkRect rect, sk_sp integral, float invSixSigma, bool isFast, + GrSamplerState samplerParams) + : INHERITED(kGrRectBlurEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag) + , rect(rect) + , integral(std::move(integral), samplerParams) + , invSixSigma(invSixSigma) + , isFast(isFast) { + this->setTextureSamplerCnt(1); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.cpp new file mode 100644 index 0000000000..6f1734d139 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSaturateProcessor.fp; do not modify. + **************************************************************************************************/ +#include "GrSaturateProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLSaturateProcessor : public GrGLSLFragmentProcessor { +public: + GrGLSLSaturateProcessor() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrSaturateProcessor& _outer = args.fFp.cast(); + (void)_outer; + fragBuilder->codeAppendf("%s = clamp(%s, 0.0, 1.0);\n", args.fOutputColor, + args.fInputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrSaturateProcessor::onCreateGLSLInstance() const { + return new GrGLSLSaturateProcessor(); +} +void GrSaturateProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrSaturateProcessor::onIsEqual(const GrFragmentProcessor& other) const { + const GrSaturateProcessor& that = other.cast(); + (void)that; + return true; +} +GrSaturateProcessor::GrSaturateProcessor(const GrSaturateProcessor& src) + : INHERITED(kGrSaturateProcessor_ClassID, src.optimizationFlags()) {} +std::unique_ptr GrSaturateProcessor::clone() const { + return std::unique_ptr(new GrSaturateProcessor(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSaturateProcessor); +#if GR_TEST_UTILS +std::unique_ptr GrSaturateProcessor::TestCreate(GrProcessorTestData* d) { + return GrSaturateProcessor::Make(); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.h b/gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.h new file mode 100644 index 0000000000..28af190a33 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrSaturateProcessor.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSaturateProcessor.fp; do not modify. + **************************************************************************************************/ +#ifndef GrSaturateProcessor_DEFINED +#define GrSaturateProcessor_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrSaturateProcessor : public GrFragmentProcessor { +public: + SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { + return {SkTPin(input.fR, 0.f, 1.f), SkTPin(input.fG, 0.f, 1.f), SkTPin(input.fB, 0.f, 1.f), + SkTPin(input.fA, 0.f, 1.f)}; + } + static std::unique_ptr Make() { + return std::unique_ptr(new GrSaturateProcessor()); + } + GrSaturateProcessor(const GrSaturateProcessor& src); + std::unique_ptr clone() const override; + const char* name() const override { return "SaturateProcessor"; } + +private: + GrSaturateProcessor() + : INHERITED(kGrSaturateProcessor_ClassID, + (OptimizationFlags)kConstantOutputForConstantInput_OptimizationFlag | + kPreservesOpaqueInput_OptimizationFlag) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.cpp b/gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.cpp new file mode 100644 index 0000000000..dd843b9bad --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSimpleTextureEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrSimpleTextureEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLSimpleTextureEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLSimpleTextureEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrSimpleTextureEffect& _outer = args.fFp.cast(); + (void)_outer; + auto matrix = _outer.matrix; + (void)matrix; + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf( + "%s = %s * sample(%s, %s).%s;\n", args.fOutputColor, args.fInputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrSimpleTextureEffect::onCreateGLSLInstance() const { + return new GrGLSLSimpleTextureEffect(); +} +void GrSimpleTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrSimpleTextureEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrSimpleTextureEffect& that = other.cast(); + (void)that; + if (image != that.image) return false; + if (matrix != that.matrix) return false; + return true; +} +GrSimpleTextureEffect::GrSimpleTextureEffect(const GrSimpleTextureEffect& src) + : INHERITED(kGrSimpleTextureEffect_ClassID, src.optimizationFlags()) + , imageCoordTransform(src.imageCoordTransform) + , image(src.image) + , matrix(src.matrix) { + this->setTextureSamplerCnt(1); + this->addCoordTransform(&imageCoordTransform); +} +std::unique_ptr GrSimpleTextureEffect::clone() const { + return std::unique_ptr(new GrSimpleTextureEffect(*this)); +} +const GrFragmentProcessor::TextureSampler& GrSimpleTextureEffect::onTextureSampler( + int index) const { + return IthTextureSampler(index, image); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSimpleTextureEffect); +#if GR_TEST_UTILS +std::unique_ptr GrSimpleTextureEffect::TestCreate( + GrProcessorTestData* testData) { + int texIdx = testData->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx + : GrProcessorUnitTest::kAlphaTextureIdx; + GrSamplerState::WrapMode wrapModes[2]; + GrTest::TestWrapModes(testData->fRandom, wrapModes); + if (!testData->caps()->npotTextureTileSupport()) { + // Performing repeat sampling on npot textures will cause asserts on HW + // that lacks support. + wrapModes[0] = GrSamplerState::WrapMode::kClamp; + wrapModes[1] = GrSamplerState::WrapMode::kClamp; + } + + GrSamplerState params(wrapModes, testData->fRandom->nextBool() + ? GrSamplerState::Filter::kBilerp + : GrSamplerState::Filter::kNearest); + + const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom); + return GrSimpleTextureEffect::Make(testData->textureProxy(texIdx), + testData->textureProxyColorType(texIdx), matrix, params); +} +#endif diff --git a/gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.h b/gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.h new file mode 100644 index 0000000000..d70dd56773 --- /dev/null +++ b/gfx/skia/skia/src/gpu/effects/generated/GrSimpleTextureEffect.h @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSimpleTextureEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrSimpleTextureEffect_DEFINED +#define GrSimpleTextureEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrSimpleTextureEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix) { + return std::unique_ptr( + new GrSimpleTextureEffect(std::move(proxy), matrix, srcColorType, + GrSamplerState(GrSamplerState::WrapMode::kClamp, + GrSamplerState::Filter::kNearest))); + } + + /* clamp mode */ + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + GrSamplerState::Filter filter) { + return std::unique_ptr(new GrSimpleTextureEffect( + std::move(proxy), matrix, srcColorType, + GrSamplerState(GrSamplerState::WrapMode::kClamp, filter))); + } + + static std::unique_ptr Make(sk_sp proxy, + GrColorType srcColorType, + const SkMatrix& matrix, + const GrSamplerState& p) { + return std::unique_ptr( + new GrSimpleTextureEffect(std::move(proxy), matrix, srcColorType, p)); + } + GrSimpleTextureEffect(const GrSimpleTextureEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "SimpleTextureEffect"; } + GrCoordTransform imageCoordTransform; + TextureSampler image; + SkMatrix44 matrix; + +private: + GrSimpleTextureEffect(sk_sp image, SkMatrix44 matrix, GrColorType srcColorType, + GrSamplerState samplerParams) + : INHERITED(kGrSimpleTextureEffect_ClassID, + (OptimizationFlags)ModulateForSamplerOptFlags( + srcColorType, + samplerParams.wrapModeX() == + GrSamplerState::WrapMode::kClampToBorder || + samplerParams.wrapModeY() == + GrSamplerState::WrapMode::kClampToBorder)) + , imageCoordTransform(matrix, image.get()) + , image(std::move(image), samplerParams) + , matrix(matrix) { + this->setTextureSamplerCnt(1); + this->addCoordTransform(&imageCoordTransform); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/geometry/GrPathUtils.cpp b/gfx/skia/skia/src/gpu/geometry/GrPathUtils.cpp new file mode 100644 index 0000000000..0b3dd23f15 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrPathUtils.cpp @@ -0,0 +1,859 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/geometry/GrPathUtils.h" + +#include "include/gpu/GrTypes.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkPointPriv.h" + +static const SkScalar gMinCurveTol = 0.0001f; + +SkScalar GrPathUtils::scaleToleranceToSrc(SkScalar devTol, + const SkMatrix& viewM, + const SkRect& pathBounds) { + // In order to tesselate the path we get a bound on how much the matrix can + // scale when mapping to screen coordinates. + SkScalar stretch = viewM.getMaxScale(); + + if (stretch < 0) { + // take worst case mapRadius amoung four corners. + // (less than perfect) + for (int i = 0; i < 4; ++i) { + SkMatrix mat; + mat.setTranslate((i % 2) ? pathBounds.fLeft : pathBounds.fRight, + (i < 2) ? pathBounds.fTop : pathBounds.fBottom); + mat.postConcat(viewM); + stretch = SkMaxScalar(stretch, mat.mapRadius(SK_Scalar1)); + } + } + SkScalar srcTol = 0; + if (stretch <= 0) { + // We have degenerate bounds or some degenerate matrix. Thus we set the tolerance to be the + // max of the path pathBounds width and height. + srcTol = SkTMax(pathBounds.width(), pathBounds.height()); + } else { + srcTol = devTol / stretch; + } + if (srcTol < gMinCurveTol) { + srcTol = gMinCurveTol; + } + return srcTol; +} + +uint32_t GrPathUtils::quadraticPointCount(const SkPoint points[], SkScalar tol) { + // You should have called scaleToleranceToSrc, which guarantees this + SkASSERT(tol >= gMinCurveTol); + + SkScalar d = SkPointPriv::DistanceToLineSegmentBetween(points[1], points[0], points[2]); + if (!SkScalarIsFinite(d)) { + return kMaxPointsPerCurve; + } else if (d <= tol) { + return 1; + } else { + // Each time we subdivide, d should be cut in 4. So we need to + // subdivide x = log4(d/tol) times. x subdivisions creates 2^(x) + // points. + // 2^(log4(x)) = sqrt(x); + SkScalar divSqrt = SkScalarSqrt(d / tol); + if (((SkScalar)SK_MaxS32) <= divSqrt) { + return kMaxPointsPerCurve; + } else { + int temp = SkScalarCeilToInt(divSqrt); + int pow2 = GrNextPow2(temp); + // Because of NaNs & INFs we can wind up with a degenerate temp + // such that pow2 comes out negative. Also, our point generator + // will always output at least one pt. + if (pow2 < 1) { + pow2 = 1; + } + return SkTMin(pow2, kMaxPointsPerCurve); + } + } +} + +uint32_t GrPathUtils::generateQuadraticPoints(const SkPoint& p0, + const SkPoint& p1, + const SkPoint& p2, + SkScalar tolSqd, + SkPoint** points, + uint32_t pointsLeft) { + if (pointsLeft < 2 || + (SkPointPriv::DistanceToLineSegmentBetweenSqd(p1, p0, p2)) < tolSqd) { + (*points)[0] = p2; + *points += 1; + return 1; + } + + SkPoint q[] = { + { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) }, + { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) }, + }; + SkPoint r = { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) }; + + pointsLeft >>= 1; + uint32_t a = generateQuadraticPoints(p0, q[0], r, tolSqd, points, pointsLeft); + uint32_t b = generateQuadraticPoints(r, q[1], p2, tolSqd, points, pointsLeft); + return a + b; +} + +uint32_t GrPathUtils::cubicPointCount(const SkPoint points[], + SkScalar tol) { + // You should have called scaleToleranceToSrc, which guarantees this + SkASSERT(tol >= gMinCurveTol); + + SkScalar d = SkTMax( + SkPointPriv::DistanceToLineSegmentBetweenSqd(points[1], points[0], points[3]), + SkPointPriv::DistanceToLineSegmentBetweenSqd(points[2], points[0], points[3])); + d = SkScalarSqrt(d); + if (!SkScalarIsFinite(d)) { + return kMaxPointsPerCurve; + } else if (d <= tol) { + return 1; + } else { + SkScalar divSqrt = SkScalarSqrt(d / tol); + if (((SkScalar)SK_MaxS32) <= divSqrt) { + return kMaxPointsPerCurve; + } else { + int temp = SkScalarCeilToInt(SkScalarSqrt(d / tol)); + int pow2 = GrNextPow2(temp); + // Because of NaNs & INFs we can wind up with a degenerate temp + // such that pow2 comes out negative. Also, our point generator + // will always output at least one pt. + if (pow2 < 1) { + pow2 = 1; + } + return SkTMin(pow2, kMaxPointsPerCurve); + } + } +} + +uint32_t GrPathUtils::generateCubicPoints(const SkPoint& p0, + const SkPoint& p1, + const SkPoint& p2, + const SkPoint& p3, + SkScalar tolSqd, + SkPoint** points, + uint32_t pointsLeft) { + if (pointsLeft < 2 || + (SkPointPriv::DistanceToLineSegmentBetweenSqd(p1, p0, p3) < tolSqd && + SkPointPriv::DistanceToLineSegmentBetweenSqd(p2, p0, p3) < tolSqd)) { + (*points)[0] = p3; + *points += 1; + return 1; + } + SkPoint q[] = { + { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) }, + { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) }, + { SkScalarAve(p2.fX, p3.fX), SkScalarAve(p2.fY, p3.fY) } + }; + SkPoint r[] = { + { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) }, + { SkScalarAve(q[1].fX, q[2].fX), SkScalarAve(q[1].fY, q[2].fY) } + }; + SkPoint s = { SkScalarAve(r[0].fX, r[1].fX), SkScalarAve(r[0].fY, r[1].fY) }; + pointsLeft >>= 1; + uint32_t a = generateCubicPoints(p0, q[0], r[0], s, tolSqd, points, pointsLeft); + uint32_t b = generateCubicPoints(s, r[1], q[2], p3, tolSqd, points, pointsLeft); + return a + b; +} + +int GrPathUtils::worstCasePointCount(const SkPath& path, int* subpaths, SkScalar tol) { + // You should have called scaleToleranceToSrc, which guarantees this + SkASSERT(tol >= gMinCurveTol); + + int pointCount = 0; + *subpaths = 1; + + bool first = true; + + SkPath::Iter iter(path, false); + SkPath::Verb verb; + + SkPoint pts[4]; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + + switch (verb) { + case SkPath::kLine_Verb: + pointCount += 1; + break; + case SkPath::kConic_Verb: { + SkScalar weight = iter.conicWeight(); + SkAutoConicToQuads converter; + const SkPoint* quadPts = converter.computeQuads(pts, weight, tol); + for (int i = 0; i < converter.countQuads(); ++i) { + pointCount += quadraticPointCount(quadPts + 2*i, tol); + } + } + case SkPath::kQuad_Verb: + pointCount += quadraticPointCount(pts, tol); + break; + case SkPath::kCubic_Verb: + pointCount += cubicPointCount(pts, tol); + break; + case SkPath::kMove_Verb: + pointCount += 1; + if (!first) { + ++(*subpaths); + } + break; + default: + break; + } + first = false; + } + return pointCount; +} + +void GrPathUtils::QuadUVMatrix::set(const SkPoint qPts[3]) { + SkMatrix m; + // We want M such that M * xy_pt = uv_pt + // We know M * control_pts = [0 1/2 1] + // [0 0 1] + // [1 1 1] + // And control_pts = [x0 x1 x2] + // [y0 y1 y2] + // [1 1 1 ] + // We invert the control pt matrix and post concat to both sides to get M. + // Using the known form of the control point matrix and the result, we can + // optimize and improve precision. + + double x0 = qPts[0].fX; + double y0 = qPts[0].fY; + double x1 = qPts[1].fX; + double y1 = qPts[1].fY; + double x2 = qPts[2].fX; + double y2 = qPts[2].fY; + double det = x0*y1 - y0*x1 + x2*y0 - y2*x0 + x1*y2 - y1*x2; + + if (!sk_float_isfinite(det) + || SkScalarNearlyZero((float)det, SK_ScalarNearlyZero * SK_ScalarNearlyZero)) { + // The quad is degenerate. Hopefully this is rare. Find the pts that are + // farthest apart to compute a line (unless it is really a pt). + SkScalar maxD = SkPointPriv::DistanceToSqd(qPts[0], qPts[1]); + int maxEdge = 0; + SkScalar d = SkPointPriv::DistanceToSqd(qPts[1], qPts[2]); + if (d > maxD) { + maxD = d; + maxEdge = 1; + } + d = SkPointPriv::DistanceToSqd(qPts[2], qPts[0]); + if (d > maxD) { + maxD = d; + maxEdge = 2; + } + // We could have a tolerance here, not sure if it would improve anything + if (maxD > 0) { + // Set the matrix to give (u = 0, v = distance_to_line) + SkVector lineVec = qPts[(maxEdge + 1)%3] - qPts[maxEdge]; + // when looking from the point 0 down the line we want positive + // distances to be to the left. This matches the non-degenerate + // case. + lineVec = SkPointPriv::MakeOrthog(lineVec, SkPointPriv::kLeft_Side); + // first row + fM[0] = 0; + fM[1] = 0; + fM[2] = 0; + // second row + fM[3] = lineVec.fX; + fM[4] = lineVec.fY; + fM[5] = -lineVec.dot(qPts[maxEdge]); + } else { + // It's a point. It should cover zero area. Just set the matrix such + // that (u, v) will always be far away from the quad. + fM[0] = 0; fM[1] = 0; fM[2] = 100.f; + fM[3] = 0; fM[4] = 0; fM[5] = 100.f; + } + } else { + double scale = 1.0/det; + + // compute adjugate matrix + double a2, a3, a4, a5, a6, a7, a8; + a2 = x1*y2-x2*y1; + + a3 = y2-y0; + a4 = x0-x2; + a5 = x2*y0-x0*y2; + + a6 = y0-y1; + a7 = x1-x0; + a8 = x0*y1-x1*y0; + + // this performs the uv_pts*adjugate(control_pts) multiply, + // then does the scale by 1/det afterwards to improve precision + m[SkMatrix::kMScaleX] = (float)((0.5*a3 + a6)*scale); + m[SkMatrix::kMSkewX] = (float)((0.5*a4 + a7)*scale); + m[SkMatrix::kMTransX] = (float)((0.5*a5 + a8)*scale); + + m[SkMatrix::kMSkewY] = (float)(a6*scale); + m[SkMatrix::kMScaleY] = (float)(a7*scale); + m[SkMatrix::kMTransY] = (float)(a8*scale); + + // kMPersp0 & kMPersp1 should algebraically be zero + m[SkMatrix::kMPersp0] = 0.0f; + m[SkMatrix::kMPersp1] = 0.0f; + m[SkMatrix::kMPersp2] = (float)((a2 + a5 + a8)*scale); + + // It may not be normalized to have 1.0 in the bottom right + float m33 = m.get(SkMatrix::kMPersp2); + if (1.f != m33) { + m33 = 1.f / m33; + fM[0] = m33 * m.get(SkMatrix::kMScaleX); + fM[1] = m33 * m.get(SkMatrix::kMSkewX); + fM[2] = m33 * m.get(SkMatrix::kMTransX); + fM[3] = m33 * m.get(SkMatrix::kMSkewY); + fM[4] = m33 * m.get(SkMatrix::kMScaleY); + fM[5] = m33 * m.get(SkMatrix::kMTransY); + } else { + fM[0] = m.get(SkMatrix::kMScaleX); + fM[1] = m.get(SkMatrix::kMSkewX); + fM[2] = m.get(SkMatrix::kMTransX); + fM[3] = m.get(SkMatrix::kMSkewY); + fM[4] = m.get(SkMatrix::kMScaleY); + fM[5] = m.get(SkMatrix::kMTransY); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// k = (y2 - y0, x0 - x2, x2*y0 - x0*y2) +// l = (y1 - y0, x0 - x1, x1*y0 - x0*y1) * 2*w +// m = (y2 - y1, x1 - x2, x2*y1 - x1*y2) * 2*w +void GrPathUtils::getConicKLM(const SkPoint p[3], const SkScalar weight, SkMatrix* out) { + SkMatrix& klm = *out; + const SkScalar w2 = 2.f * weight; + klm[0] = p[2].fY - p[0].fY; + klm[1] = p[0].fX - p[2].fX; + klm[2] = p[2].fX * p[0].fY - p[0].fX * p[2].fY; + + klm[3] = w2 * (p[1].fY - p[0].fY); + klm[4] = w2 * (p[0].fX - p[1].fX); + klm[5] = w2 * (p[1].fX * p[0].fY - p[0].fX * p[1].fY); + + klm[6] = w2 * (p[2].fY - p[1].fY); + klm[7] = w2 * (p[1].fX - p[2].fX); + klm[8] = w2 * (p[2].fX * p[1].fY - p[1].fX * p[2].fY); + + // scale the max absolute value of coeffs to 10 + SkScalar scale = 0.f; + for (int i = 0; i < 9; ++i) { + scale = SkMaxScalar(scale, SkScalarAbs(klm[i])); + } + SkASSERT(scale > 0.f); + scale = 10.f / scale; + for (int i = 0; i < 9; ++i) { + klm[i] *= scale; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +// a is the first control point of the cubic. +// ab is the vector from a to the second control point. +// dc is the vector from the fourth to the third control point. +// d is the fourth control point. +// p is the candidate quadratic control point. +// this assumes that the cubic doesn't inflect and is simple +bool is_point_within_cubic_tangents(const SkPoint& a, + const SkVector& ab, + const SkVector& dc, + const SkPoint& d, + SkPathPriv::FirstDirection dir, + const SkPoint p) { + SkVector ap = p - a; + SkScalar apXab = ap.cross(ab); + if (SkPathPriv::kCW_FirstDirection == dir) { + if (apXab > 0) { + return false; + } + } else { + SkASSERT(SkPathPriv::kCCW_FirstDirection == dir); + if (apXab < 0) { + return false; + } + } + + SkVector dp = p - d; + SkScalar dpXdc = dp.cross(dc); + if (SkPathPriv::kCW_FirstDirection == dir) { + if (dpXdc < 0) { + return false; + } + } else { + SkASSERT(SkPathPriv::kCCW_FirstDirection == dir); + if (dpXdc > 0) { + return false; + } + } + return true; +} + +void convert_noninflect_cubic_to_quads(const SkPoint p[4], + SkScalar toleranceSqd, + SkTArray* quads, + int sublevel = 0, + bool preserveFirstTangent = true, + bool preserveLastTangent = true) { + // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is + // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1]. + SkVector ab = p[1] - p[0]; + SkVector dc = p[2] - p[3]; + + if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) { + if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) { + SkPoint* degQuad = quads->push_back_n(3); + degQuad[0] = p[0]; + degQuad[1] = p[0]; + degQuad[2] = p[3]; + return; + } + ab = p[2] - p[0]; + } + if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) { + dc = p[1] - p[3]; + } + + static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2; + static const int kMaxSubdivs = 10; + + ab.scale(kLengthScale); + dc.scale(kLengthScale); + + // c0 and c1 are extrapolations along vectors ab and dc. + SkPoint c0 = p[0] + ab; + SkPoint c1 = p[3] + dc; + + SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1); + if (dSqd < toleranceSqd) { + SkPoint newC; + if (preserveFirstTangent == preserveLastTangent) { + // We used to force a split when both tangents need to be preserved and c0 != c1. + // This introduced a large performance regression for tiny paths for no noticeable + // quality improvement. However, we aren't quite fulfilling our contract of guaranteeing + // the two tangent vectors and this could introduce a missed pixel in + // GrAAHairlinePathRenderer. + newC = (c0 + c1) * 0.5f; + } else if (preserveFirstTangent) { + newC = c0; + } else { + newC = c1; + } + + SkPoint* pts = quads->push_back_n(3); + pts[0] = p[0]; + pts[1] = newC; + pts[2] = p[3]; + return; + } + SkPoint choppedPts[7]; + SkChopCubicAtHalf(p, choppedPts); + convert_noninflect_cubic_to_quads( + choppedPts + 0, toleranceSqd, quads, sublevel + 1, preserveFirstTangent, false); + convert_noninflect_cubic_to_quads( + choppedPts + 3, toleranceSqd, quads, sublevel + 1, false, preserveLastTangent); +} + +void convert_noninflect_cubic_to_quads_with_constraint(const SkPoint p[4], + SkScalar toleranceSqd, + SkPathPriv::FirstDirection dir, + SkTArray* quads, + int sublevel = 0) { + // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is + // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1]. + + SkVector ab = p[1] - p[0]; + SkVector dc = p[2] - p[3]; + + if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) { + if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) { + SkPoint* degQuad = quads->push_back_n(3); + degQuad[0] = p[0]; + degQuad[1] = p[0]; + degQuad[2] = p[3]; + return; + } + ab = p[2] - p[0]; + } + if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) { + dc = p[1] - p[3]; + } + + // When the ab and cd tangents are degenerate or nearly parallel with vector from d to a the + // constraint that the quad point falls between the tangents becomes hard to enforce and we are + // likely to hit the max subdivision count. However, in this case the cubic is approaching a + // line and the accuracy of the quad point isn't so important. We check if the two middle cubic + // control points are very close to the baseline vector. If so then we just pick quadratic + // points on the control polygon. + + SkVector da = p[0] - p[3]; + bool doQuads = SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero || + SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero; + if (!doQuads) { + SkScalar invDALengthSqd = SkPointPriv::LengthSqd(da); + if (invDALengthSqd > SK_ScalarNearlyZero) { + invDALengthSqd = SkScalarInvert(invDALengthSqd); + // cross(ab, da)^2/length(da)^2 == sqd distance from b to line from d to a. + // same goes for point c using vector cd. + SkScalar detABSqd = ab.cross(da); + detABSqd = SkScalarSquare(detABSqd); + SkScalar detDCSqd = dc.cross(da); + detDCSqd = SkScalarSquare(detDCSqd); + if (detABSqd * invDALengthSqd < toleranceSqd && + detDCSqd * invDALengthSqd < toleranceSqd) { + doQuads = true; + } + } + } + if (doQuads) { + SkPoint b = p[0] + ab; + SkPoint c = p[3] + dc; + SkPoint mid = b + c; + mid.scale(SK_ScalarHalf); + // Insert two quadratics to cover the case when ab points away from d and/or dc + // points away from a. + if (SkVector::DotProduct(da, dc) < 0 || SkVector::DotProduct(ab, da) > 0) { + SkPoint* qpts = quads->push_back_n(6); + qpts[0] = p[0]; + qpts[1] = b; + qpts[2] = mid; + qpts[3] = mid; + qpts[4] = c; + qpts[5] = p[3]; + } else { + SkPoint* qpts = quads->push_back_n(3); + qpts[0] = p[0]; + qpts[1] = mid; + qpts[2] = p[3]; + } + return; + } + + static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2; + static const int kMaxSubdivs = 10; + + ab.scale(kLengthScale); + dc.scale(kLengthScale); + + // c0 and c1 are extrapolations along vectors ab and dc. + SkVector c0 = p[0] + ab; + SkVector c1 = p[3] + dc; + + SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1); + if (dSqd < toleranceSqd) { + SkPoint cAvg = (c0 + c1) * 0.5f; + bool subdivide = false; + + if (!is_point_within_cubic_tangents(p[0], ab, dc, p[3], dir, cAvg)) { + // choose a new cAvg that is the intersection of the two tangent lines. + ab = SkPointPriv::MakeOrthog(ab); + SkScalar z0 = -ab.dot(p[0]); + dc = SkPointPriv::MakeOrthog(dc); + SkScalar z1 = -dc.dot(p[3]); + cAvg.fX = ab.fY * z1 - z0 * dc.fY; + cAvg.fY = z0 * dc.fX - ab.fX * z1; + SkScalar z = ab.fX * dc.fY - ab.fY * dc.fX; + z = SkScalarInvert(z); + cAvg.fX *= z; + cAvg.fY *= z; + if (sublevel <= kMaxSubdivs) { + SkScalar d0Sqd = SkPointPriv::DistanceToSqd(c0, cAvg); + SkScalar d1Sqd = SkPointPriv::DistanceToSqd(c1, cAvg); + // We need to subdivide if d0 + d1 > tolerance but we have the sqd values. We know + // the distances and tolerance can't be negative. + // (d0 + d1)^2 > toleranceSqd + // d0Sqd + 2*d0*d1 + d1Sqd > toleranceSqd + SkScalar d0d1 = SkScalarSqrt(d0Sqd * d1Sqd); + subdivide = 2 * d0d1 + d0Sqd + d1Sqd > toleranceSqd; + } + } + if (!subdivide) { + SkPoint* pts = quads->push_back_n(3); + pts[0] = p[0]; + pts[1] = cAvg; + pts[2] = p[3]; + return; + } + } + SkPoint choppedPts[7]; + SkChopCubicAtHalf(p, choppedPts); + convert_noninflect_cubic_to_quads_with_constraint( + choppedPts + 0, toleranceSqd, dir, quads, sublevel + 1); + convert_noninflect_cubic_to_quads_with_constraint( + choppedPts + 3, toleranceSqd, dir, quads, sublevel + 1); +} +} + +void GrPathUtils::convertCubicToQuads(const SkPoint p[4], + SkScalar tolScale, + SkTArray* quads) { + if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) { + return; + } + if (!SkScalarIsFinite(tolScale)) { + return; + } + SkPoint chopped[10]; + int count = SkChopCubicAtInflections(p, chopped); + + const SkScalar tolSqd = SkScalarSquare(tolScale); + + for (int i = 0; i < count; ++i) { + SkPoint* cubic = chopped + 3*i; + convert_noninflect_cubic_to_quads(cubic, tolSqd, quads); + } +} + +void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4], + SkScalar tolScale, + SkPathPriv::FirstDirection dir, + SkTArray* quads) { + if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) { + return; + } + if (!SkScalarIsFinite(tolScale)) { + return; + } + SkPoint chopped[10]; + int count = SkChopCubicAtInflections(p, chopped); + + const SkScalar tolSqd = SkScalarSquare(tolScale); + + for (int i = 0; i < count; ++i) { + SkPoint* cubic = chopped + 3*i; + convert_noninflect_cubic_to_quads_with_constraint(cubic, tolSqd, dir, quads); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +using ExcludedTerm = GrPathUtils::ExcludedTerm; + +ExcludedTerm GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4], + SkMatrix* out) { + GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); + + // First convert the bezier coordinates p[0..3] to power basis coefficients X,Y(,W=[0 0 0 1]). + // M3 is the matrix that does this conversion. The homogeneous equation for the cubic becomes: + // + // | X Y 0 | + // C(t,s) = [t^3 t^2*s t*s^2 s^3] * | . . 0 | + // | . . 0 | + // | . . 1 | + // + const Sk4f M3[3] = {Sk4f(-1, 3, -3, 1), + Sk4f(3, -6, 3, 0), + Sk4f(-3, 3, 0, 0)}; + // 4th col of M3 = Sk4f(1, 0, 0, 0)}; + Sk4f X(p[3].x(), 0, 0, 0); + Sk4f Y(p[3].y(), 0, 0, 0); + for (int i = 2; i >= 0; --i) { + X += M3[i] * p[i].x(); + Y += M3[i] * p[i].y(); + } + + // The matrix is 3x4. In order to invert it, we first need to make it square by throwing out one + // of the middle two rows. We toss the row that leaves us with the largest absolute determinant. + // Since the right column will be [0 0 1], the respective determinants reduce to x0*y2 - y0*x2 + // and x0*y1 - y0*x1. + SkScalar dets[4]; + Sk4f D = SkNx_shuffle<0,0,2,1>(X) * SkNx_shuffle<2,1,0,0>(Y); + D -= SkNx_shuffle<2,3,0,1>(D); + D.store(dets); + ExcludedTerm skipTerm = SkScalarAbs(dets[0]) > SkScalarAbs(dets[1]) ? + ExcludedTerm::kQuadraticTerm : ExcludedTerm::kLinearTerm; + SkScalar det = dets[ExcludedTerm::kQuadraticTerm == skipTerm ? 0 : 1]; + if (0 == det) { + return ExcludedTerm::kNonInvertible; + } + SkScalar rdet = 1 / det; + + // Compute the inverse-transpose of the power basis matrix with the 'skipRow'th row removed. + // Since W=[0 0 0 1], it follows that our corresponding solution will be equal to: + // + // | y1 -x1 x1*y2 - y1*x2 | + // 1/det * | -y0 x0 -x0*y2 + y0*x2 | + // | 0 0 det | + // + SkScalar x[4], y[4], z[4]; + X.store(x); + Y.store(y); + (X * SkNx_shuffle<3,3,3,3>(Y) - Y * SkNx_shuffle<3,3,3,3>(X)).store(z); + + int middleRow = ExcludedTerm::kQuadraticTerm == skipTerm ? 2 : 1; + out->setAll( y[middleRow] * rdet, -x[middleRow] * rdet, z[middleRow] * rdet, + -y[0] * rdet, x[0] * rdet, -z[0] * rdet, + 0, 0, 1); + + return skipTerm; +} + +inline static void calc_serp_kcoeffs(SkScalar tl, SkScalar sl, SkScalar tm, SkScalar sm, + ExcludedTerm skipTerm, SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = 0; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? sl*sm : -tl*sm - tm*sl; + outCoeffs[2] = tl*tm; +} + +inline static void calc_serp_lmcoeffs(SkScalar t, SkScalar s, ExcludedTerm skipTerm, + SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = -s*s*s; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? 3*s*s*t : -3*s*t*t; + outCoeffs[2] = t*t*t; +} + +inline static void calc_loop_kcoeffs(SkScalar td, SkScalar sd, SkScalar te, SkScalar se, + SkScalar tdse, SkScalar tesd, ExcludedTerm skipTerm, + SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = 0; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? sd*se : -tdse - tesd; + outCoeffs[2] = td*te; +} + +inline static void calc_loop_lmcoeffs(SkScalar t2, SkScalar s2, SkScalar t1, SkScalar s1, + SkScalar t2s1, SkScalar t1s2, ExcludedTerm skipTerm, + SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = -s2*s2*s1; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? s2 * (2*t2s1 + t1s2) + : -t2 * (t2s1 + 2*t1s2); + outCoeffs[2] = t2*t2*t1; +} + +// For the case when a cubic bezier is actually a quadratic. We duplicate k in l so that the +// implicit becomes: +// +// k^3 - l*m == k^3 - l*k == k * (k^2 - l) +// +// In the quadratic case we can simply assign fixed values at each control point: +// +// | ..K.. | | pts[0] pts[1] pts[2] pts[3] | | 0 1/3 2/3 1 | +// | ..L.. | * | . . . . | == | 0 0 1/3 1 | +// | ..K.. | | 1 1 1 1 | | 0 1/3 2/3 1 | +// +static void calc_quadratic_klm(const SkPoint pts[4], double d3, SkMatrix* klm) { + SkMatrix klmAtPts; + klmAtPts.setAll(0, 1.f/3, 1, + 0, 0, 1, + 0, 1.f/3, 1); + + SkMatrix inversePts; + inversePts.setAll(pts[0].x(), pts[1].x(), pts[3].x(), + pts[0].y(), pts[1].y(), pts[3].y(), + 1, 1, 1); + SkAssertResult(inversePts.invert(&inversePts)); + + klm->setConcat(klmAtPts, inversePts); + + // If d3 > 0 we need to flip the orientation of our curve + // This is done by negating the k and l values + if (d3 > 0) { + klm->postScale(-1, -1); + } +} + +// For the case when a cubic bezier is actually a line. We set K=0, L=1, M=-line, which results in +// the following implicit: +// +// k^3 - l*m == 0^3 - 1*(-line) == -(-line) == line +// +static void calc_line_klm(const SkPoint pts[4], SkMatrix* klm) { + SkScalar ny = pts[0].x() - pts[3].x(); + SkScalar nx = pts[3].y() - pts[0].y(); + SkScalar k = nx * pts[0].x() + ny * pts[0].y(); + klm->setAll( 0, 0, 0, + 0, 0, 1, + -nx, -ny, k); +} + +SkCubicType GrPathUtils::getCubicKLM(const SkPoint src[4], SkMatrix* klm, double tt[2], + double ss[2]) { + double d[4]; + SkCubicType type = SkClassifyCubic(src, tt, ss, d); + + if (SkCubicType::kLineOrPoint == type) { + calc_line_klm(src, klm); + return SkCubicType::kLineOrPoint; + } + + if (SkCubicType::kQuadratic == type) { + calc_quadratic_klm(src, d[3], klm); + return SkCubicType::kQuadratic; + } + + SkMatrix CIT; + ExcludedTerm skipTerm = calcCubicInverseTransposePowerBasisMatrix(src, &CIT); + if (ExcludedTerm::kNonInvertible == skipTerm) { + // This could technically also happen if the curve were quadratic, but SkClassifyCubic + // should have detected that case already with tolerance. + calc_line_klm(src, klm); + return SkCubicType::kLineOrPoint; + } + + const SkScalar t0 = static_cast(tt[0]), t1 = static_cast(tt[1]), + s0 = static_cast(ss[0]), s1 = static_cast(ss[1]); + + SkMatrix klmCoeffs; + switch (type) { + case SkCubicType::kCuspAtInfinity: + SkASSERT(1 == t1 && 0 == s1); // Infinity. + // fallthru. + case SkCubicType::kLocalCusp: + case SkCubicType::kSerpentine: + calc_serp_kcoeffs(t0, s0, t1, s1, skipTerm, &klmCoeffs[0]); + calc_serp_lmcoeffs(t0, s0, skipTerm, &klmCoeffs[3]); + calc_serp_lmcoeffs(t1, s1, skipTerm, &klmCoeffs[6]); + break; + case SkCubicType::kLoop: { + const SkScalar tdse = t0 * s1; + const SkScalar tesd = t1 * s0; + calc_loop_kcoeffs(t0, s0, t1, s1, tdse, tesd, skipTerm, &klmCoeffs[0]); + calc_loop_lmcoeffs(t0, s0, t1, s1, tdse, tesd, skipTerm, &klmCoeffs[3]); + calc_loop_lmcoeffs(t1, s1, t0, s0, tesd, tdse, skipTerm, &klmCoeffs[6]); + break; + } + default: + SK_ABORT("Unexpected cubic type."); + break; + } + + klm->setConcat(klmCoeffs, CIT); + return type; +} + +int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm, + int* loopIndex) { + SkSTArray<2, SkScalar> chops; + *loopIndex = -1; + + double t[2], s[2]; + if (SkCubicType::kLoop == GrPathUtils::getCubicKLM(src, klm, t, s)) { + SkScalar t0 = static_cast(t[0] / s[0]); + SkScalar t1 = static_cast(t[1] / s[1]); + SkASSERT(t0 <= t1); // Technically t0 != t1 in a loop, but there may be FP error. + + if (t0 < 1 && t1 > 0) { + *loopIndex = 0; + if (t0 > 0) { + chops.push_back(t0); + *loopIndex = 1; + } + if (t1 < 1) { + chops.push_back(t1); + *loopIndex = chops.count() - 1; + } + } + } + + SkChopCubicAt(src, dst, chops.begin(), chops.count()); + return chops.count() + 1; +} diff --git a/gfx/skia/skia/src/gpu/geometry/GrPathUtils.h b/gfx/skia/skia/src/gpu/geometry/GrPathUtils.h new file mode 100644 index 0000000000..f44f2c0276 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrPathUtils.h @@ -0,0 +1,217 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPathUtils_DEFINED +#define GrPathUtils_DEFINED + +#include "include/core/SkRect.h" +#include "include/private/SkTArray.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPathPriv.h" + +class SkMatrix; + +/** + * Utilities for evaluating paths. + */ +namespace GrPathUtils { + // Very small tolerances will be increased to a minimum threshold value, to avoid division + // problems in subsequent math. + SkScalar scaleToleranceToSrc(SkScalar devTol, + const SkMatrix& viewM, + const SkRect& pathBounds); + + int worstCasePointCount(const SkPath&, + int* subpaths, + SkScalar tol); + + uint32_t quadraticPointCount(const SkPoint points[], SkScalar tol); + + uint32_t generateQuadraticPoints(const SkPoint& p0, + const SkPoint& p1, + const SkPoint& p2, + SkScalar tolSqd, + SkPoint** points, + uint32_t pointsLeft); + + uint32_t cubicPointCount(const SkPoint points[], SkScalar tol); + + uint32_t generateCubicPoints(const SkPoint& p0, + const SkPoint& p1, + const SkPoint& p2, + const SkPoint& p3, + SkScalar tolSqd, + SkPoint** points, + uint32_t pointsLeft); + + // A 2x3 matrix that goes from the 2d space coordinates to UV space where + // u^2-v = 0 specifies the quad. The matrix is determined by the control + // points of the quadratic. + class QuadUVMatrix { + public: + QuadUVMatrix() {} + // Initialize the matrix from the control pts + QuadUVMatrix(const SkPoint controlPts[3]) { this->set(controlPts); } + void set(const SkPoint controlPts[3]); + + /** + * Applies the matrix to vertex positions to compute UV coords. + * + * vertices is a pointer to the first vertex. + * vertexCount is the number of vertices. + * stride is the size of each vertex. + * uvOffset is the offset of the UV values within each vertex. + */ + void apply(void* vertices, int vertexCount, size_t stride, size_t uvOffset) const { + intptr_t xyPtr = reinterpret_cast(vertices); + intptr_t uvPtr = reinterpret_cast(vertices) + uvOffset; + float sx = fM[0]; + float kx = fM[1]; + float tx = fM[2]; + float ky = fM[3]; + float sy = fM[4]; + float ty = fM[5]; + for (int i = 0; i < vertexCount; ++i) { + const SkPoint* xy = reinterpret_cast(xyPtr); + SkPoint* uv = reinterpret_cast(uvPtr); + uv->fX = sx * xy->fX + kx * xy->fY + tx; + uv->fY = ky * xy->fX + sy * xy->fY + ty; + xyPtr += stride; + uvPtr += stride; + } + } + private: + float fM[6]; + }; + + // Input is 3 control points and a weight for a bezier conic. Calculates the + // three linear functionals (K,L,M) that represent the implicit equation of the + // conic, k^2 - lm. + // + // Output: klm holds the linear functionals K,L,M as row vectors: + // + // | ..K.. | | x | | k | + // | ..L.. | * | y | == | l | + // | ..M.. | | 1 | | m | + // + void getConicKLM(const SkPoint p[3], const SkScalar weight, SkMatrix* klm); + + // Converts a cubic into a sequence of quads. If working in device space + // use tolScale = 1, otherwise set based on stretchiness of the matrix. The + // result is sets of 3 points in quads. This will preserve the starting and + // ending tangent vectors (modulo FP precision). + void convertCubicToQuads(const SkPoint p[4], + SkScalar tolScale, + SkTArray* quads); + + // When we approximate a cubic {a,b,c,d} with a quadratic we may have to + // ensure that the new control point lies between the lines ab and cd. The + // convex path renderer requires this. It starts with a path where all the + // control points taken together form a convex polygon. It relies on this + // property and the quadratic approximation of cubics step cannot alter it. + // This variation enforces this constraint. The cubic must be simple and dir + // must specify the orientation of the contour containing the cubic. + void convertCubicToQuadsConstrainToTangents(const SkPoint p[4], + SkScalar tolScale, + SkPathPriv::FirstDirection dir, + SkTArray* quads); + + enum class ExcludedTerm { + kNonInvertible, + kQuadraticTerm, + kLinearTerm + }; + + // Computes the inverse-transpose of the cubic's power basis matrix, after removing a specific + // row of coefficients. + // + // E.g. if the cubic is defined in power basis form as follows: + // + // | x3 y3 0 | + // C(t,s) = [t^3 t^2*s t*s^2 s^3] * | x2 y2 0 | + // | x1 y1 0 | + // | x0 y0 1 | + // + // And the excluded term is "kQuadraticTerm", then the resulting inverse-transpose will be: + // + // | x3 y3 0 | -1 T + // | x1 y1 0 | + // | x0 y0 1 | + // + // (The term to exclude is chosen based on maximizing the resulting matrix determinant.) + // + // This can be used to find the KLM linear functionals: + // + // | ..K.. | | ..kcoeffs.. | + // | ..L.. | = | ..lcoeffs.. | * inverse_transpose_power_basis_matrix + // | ..M.. | | ..mcoeffs.. | + // + // NOTE: the same term that was excluded here must also be removed from the corresponding column + // of the klmcoeffs matrix. + // + // Returns which row of coefficients was removed, or kNonInvertible if the cubic was degenerate. + ExcludedTerm calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4], SkMatrix* out); + + // Computes the KLM linear functionals for the cubic implicit form. The "right" side of the + // curve (when facing in the direction of increasing parameter values) will be the area that + // satisfies: + // + // k^3 < l*m + // + // Output: + // + // klm: Holds the linear functionals K,L,M as row vectors: + // + // | ..K.. | | x | | k | + // | ..L.. | * | y | == | l | + // | ..M.. | | 1 | | m | + // + // NOTE: the KLM lines are calculated in the same space as the input control points. If you + // transform the points the lines will also need to be transformed. This can be done by mapping + // the lines with the inverse-transpose of the matrix used to map the points. + // + // t[],s[]: These are set to the two homogeneous parameter values at which points the lines L&M + // intersect with K (See SkClassifyCubic). + // + // Returns the cubic's classification. + SkCubicType getCubicKLM(const SkPoint src[4], SkMatrix* klm, double t[2], double s[2]); + + // Chops the cubic bezier passed in by src, at the double point (intersection point) + // if the curve is a cubic loop. If it is a loop, there will be two parametric values for + // the double point: t1 and t2. We chop the cubic at these values if they are between 0 and 1. + // Return value: + // Value of 3: t1 and t2 are both between (0,1), and dst will contain the three cubics, + // dst[0..3], dst[3..6], and dst[6..9] if dst is not nullptr + // Value of 2: Only one of t1 and t2 are between (0,1), and dst will contain the two cubics, + // dst[0..3] and dst[3..6] if dst is not nullptr + // Value of 1: Neither t1 nor t2 are between (0,1), and dst will contain the one original cubic, + // src[0..3] + // + // Output: + // + // klm: Holds the linear functionals K,L,M as row vectors. (See getCubicKLM().) + // + // loopIndex: This value will tell the caller which of the chopped sections (if any) are the + // actual loop. A value of -1 means there is no loop section. The caller can then use + // this value to decide how/if they want to flip the orientation of this section. + // The flip should be done by negating the k and l values as follows: + // + // KLM.postScale(-1, -1) + int chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm, + int* loopIndex); + + // When tessellating curved paths into linear segments, this defines the maximum distance + // in screen space which a segment may deviate from the mathmatically correct value. + // Above this value, the segment will be subdivided. + // This value was chosen to approximate the supersampling accuracy of the raster path (16 + // samples, or one quarter pixel). + static const SkScalar kDefaultTolerance = SkDoubleToScalar(0.25); + + // We guarantee that no quad or cubic will ever produce more than this many points + static const int kMaxPointsPerCurve = 1 << 10; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/geometry/GrQuad.cpp b/gfx/skia/skia/src/gpu/geometry/GrQuad.cpp new file mode 100644 index 0000000000..3df11cafe5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrQuad.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/geometry/GrQuad.h" + +#include "include/core/SkMatrix.h" + +using V4f = skvx::Vec<4, float>; + +static bool aa_affects_rect(float ql, float qt, float qr, float qb) { + return !SkScalarIsInt(ql) || !SkScalarIsInt(qr) || !SkScalarIsInt(qt) || !SkScalarIsInt(qb); +} + +static void map_rect_translate_scale(const SkRect& rect, const SkMatrix& m, + V4f* xs, V4f* ys) { + SkMatrix::TypeMask tm = m.getType(); + SkASSERT(tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)); + + V4f r = V4f::Load(&rect); + if (tm > SkMatrix::kIdentity_Mask) { + const V4f t{m.getTranslateX(), m.getTranslateY(), m.getTranslateX(), m.getTranslateY()}; + if (tm <= SkMatrix::kTranslate_Mask) { + r += t; + } else { + const V4f s{m.getScaleX(), m.getScaleY(), m.getScaleX(), m.getScaleY()}; + r = r * s + t; + } + } + *xs = skvx::shuffle<0, 0, 2, 2>(r); + *ys = skvx::shuffle<1, 3, 1, 3>(r); +} + +static void map_quad_general(const V4f& qx, const V4f& qy, const SkMatrix& m, + V4f* xs, V4f* ys, V4f* ws) { + *xs = mad(m.getScaleX(), qx, mad(m.getSkewX(), qy, m.getTranslateX())); + *ys = mad(m.getSkewY(), qx, mad(m.getScaleY(), qy, m.getTranslateY())); + if (m.hasPerspective()) { + V4f w = mad(m.getPerspX(), qx, + mad(m.getPerspY(), qy, m.get(SkMatrix::kMPersp2))); + if (ws) { + // Output the calculated w coordinates + *ws = w; + } else { + // Apply perspective division immediately + V4f iw = 1.f / w; + *xs *= iw; + *ys *= iw; + } + } else if (ws) { + *ws = 1.f; + } +} + +static void map_rect_general(const SkRect& rect, const SkMatrix& matrix, + V4f* xs, V4f* ys, V4f* ws) { + V4f rx{rect.fLeft, rect.fLeft, rect.fRight, rect.fRight}; + V4f ry{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom}; + map_quad_general(rx, ry, matrix, xs, ys, ws); +} + +// Rearranges (top-left, top-right, bottom-right, bottom-left) ordered skQuadPts into xs and ys +// ordered (top-left, bottom-left, top-right, bottom-right) +static void rearrange_sk_to_gr_points(const SkPoint skQuadPts[4], V4f* xs, V4f* ys) { + *xs = V4f{skQuadPts[0].fX, skQuadPts[3].fX, skQuadPts[1].fX, skQuadPts[2].fX}; + *ys = V4f{skQuadPts[0].fY, skQuadPts[3].fY, skQuadPts[1].fY, skQuadPts[2].fY}; +} + +// If an SkRect is transformed by this matrix, what class of quad is required to represent it. +static GrQuad::Type quad_type_for_transformed_rect(const SkMatrix& matrix) { + if (matrix.rectStaysRect()) { + return GrQuad::Type::kAxisAligned; + } else if (matrix.preservesRightAngles()) { + return GrQuad::Type::kRectilinear; + } else if (matrix.hasPerspective()) { + return GrQuad::Type::kPerspective; + } else { + return GrQuad::Type::kGeneral; + } +} + +// Perform minimal analysis of 'pts' (which are suitable for MakeFromSkQuad), and determine a +// quad type that will be as minimally general as possible. +static GrQuad::Type quad_type_for_points(const SkPoint pts[4], const SkMatrix& matrix) { + if (matrix.hasPerspective()) { + return GrQuad::Type::kPerspective; + } + // If 'pts' was formed by SkRect::toQuad() and not transformed further, it is safe to use the + // quad type derived from 'matrix'. Otherwise don't waste any more time and assume kStandard + // (most general 2D quad). + if ((pts[0].fX == pts[3].fX && pts[1].fX == pts[2].fX) && + (pts[0].fY == pts[1].fY && pts[2].fY == pts[3].fY)) { + return quad_type_for_transformed_rect(matrix); + } else { + return GrQuad::Type::kGeneral; + } +} + +GrQuad GrQuad::MakeFromRect(const SkRect& rect, const SkMatrix& m) { + V4f x, y, w; + SkMatrix::TypeMask tm = m.getType(); + Type type; + if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) { + map_rect_translate_scale(rect, m, &x, &y); + w = 1.f; + type = Type::kAxisAligned; + } else { + map_rect_general(rect, m, &x, &y, &w); + type = quad_type_for_transformed_rect(m); + } + return GrQuad(x, y, w, type); +} + +GrQuad GrQuad::MakeFromSkQuad(const SkPoint pts[4], const SkMatrix& matrix) { + V4f xs, ys; + rearrange_sk_to_gr_points(pts, &xs, &ys); + Type type = quad_type_for_points(pts, matrix); + if (matrix.isIdentity()) { + return GrQuad(xs, ys, 1.f, type); + } else { + V4f mx, my, mw; + map_quad_general(xs, ys, matrix, &mx, &my, &mw); + return GrQuad(mx, my, mw, type); + } +} + +bool GrQuad::aaHasEffectOnRect() const { + SkASSERT(this->quadType() == Type::kAxisAligned); + // If rect, ws must all be 1s so no need to divide + return aa_affects_rect(fX[0], fY[0], fX[3], fY[3]); +} + +bool GrQuad::asRect(SkRect* rect) const { + if (this->quadType() != Type::kAxisAligned) { + return false; + } + + *rect = this->bounds(); + // v0 at the geometric top-left is unique amongst axis-aligned vertex orders + // (90, 180, 270 rotations or axis flips all move v0). + return fX[0] == rect->fLeft && fY[0] == rect->fTop; +} diff --git a/gfx/skia/skia/src/gpu/geometry/GrQuad.h b/gfx/skia/skia/src/gpu/geometry/GrQuad.h new file mode 100644 index 0000000000..9131ff6466 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrQuad.h @@ -0,0 +1,159 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrQuad_DEFINED +#define GrQuad_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkPoint.h" +#include "include/core/SkPoint3.h" +#include "include/private/SkVx.h" + +/** + * GrQuad is a collection of 4 points which can be used to represent an arbitrary quadrilateral. The + * points make a triangle strip with CCW triangles (top-left, bottom-left, top-right, bottom-right). + */ +class GrQuad { +public: + // Quadrilaterals can be classified in several useful ways that assist AA tessellation and other + // analysis when drawing, in particular, knowing if it was originally a rectangle transformed by + // certain types of matrices: + enum class Type { + // The 4 points remain an axis-aligned rectangle; their logical indices may not respect + // TL, BL, TR, BR ordering if the transform was a 90 degre rotation or mirror. + kAxisAligned, + // The 4 points represent a rectangle subjected to a rotation, its corners are right angles. + kRectilinear, + // Arbitrary 2D quadrilateral; may have been a rectangle transformed with skew or some + // clipped polygon. Its w coordinates will all be 1. + kGeneral, + // Even more general-purpose than kGeneral, this allows the w coordinates to be non-unity. + kPerspective, + kLast = kPerspective + }; + static const int kTypeCount = static_cast(Type::kLast) + 1; + + GrQuad() = default; + + explicit GrQuad(const SkRect& rect) + : fX{rect.fLeft, rect.fLeft, rect.fRight, rect.fRight} + , fY{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom} + , fW{1.f, 1.f, 1.f, 1.f} + , fType(Type::kAxisAligned) {} + + GrQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys, Type type) + : fType(type) { + SkASSERT(type != Type::kPerspective); + xs.store(fX); + ys.store(fY); + fW[0] = fW[1] = fW[2] = fW[3] = 1.f; + } + + GrQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys, + const skvx::Vec<4, float>& ws, Type type) + : fType(type) { + xs.store(fX); + ys.store(fY); + ws.store(fW); + } + + // Copy 4 values from each of the arrays into the quad's components + GrQuad(const float xs[4], const float ys[4], const float ws[4], Type type) + : fType(type) { + memcpy(fX, xs, 4 * sizeof(float)); + memcpy(fY, ys, 4 * sizeof(float)); + memcpy(fW, ws, 4 * sizeof(float)); + } + + static GrQuad MakeFromRect(const SkRect&, const SkMatrix&); + + // Creates a GrQuad from the quadrilateral 'pts', transformed by the matrix. The input + // points array is arranged as per SkRect::toQuad (top-left, top-right, bottom-right, + // bottom-left). The returned instance's point order will still be CCW tri-strip order. + static GrQuad MakeFromSkQuad(const SkPoint pts[4], const SkMatrix&); + + GrQuad& operator=(const GrQuad&) = default; + + SkPoint3 point3(int i) const { return {fX[i], fY[i], fW[i]}; } + + SkPoint point(int i) const { + if (fType == Type::kPerspective) { + return {fX[i] / fW[i], fY[i] / fW[i]}; + } else { + return {fX[i], fY[i]}; + } + } + + SkRect bounds() const { + auto x = this->x4f(); + auto y = this->y4f(); + if (fType == Type::kPerspective) { + auto iw = this->iw4f(); + x *= iw; + y *= iw; + } + + return {min(x), min(y), max(x), max(y)}; + } + + bool isFinite() const { + // If any coordinate is infinity or NaN, then multiplying it with 0 will make accum NaN + float accum = 0; + for (int i = 0; i < 4; ++i) { + accum *= fX[i]; + accum *= fY[i]; + accum *= fW[i]; + } + SkASSERT(0 == accum || SkScalarIsNaN(accum)); + return !SkScalarIsNaN(accum); + } + + float x(int i) const { return fX[i]; } + float y(int i) const { return fY[i]; } + float w(int i) const { return fW[i]; } + float iw(int i) const { return sk_ieee_float_divide(1.f, fW[i]); } + + skvx::Vec<4, float> x4f() const { return skvx::Vec<4, float>::Load(fX); } + skvx::Vec<4, float> y4f() const { return skvx::Vec<4, float>::Load(fY); } + skvx::Vec<4, float> w4f() const { return skvx::Vec<4, float>::Load(fW); } + skvx::Vec<4, float> iw4f() const { return 1.f / this->w4f(); } + + Type quadType() const { return fType; } + + bool hasPerspective() const { return fType == Type::kPerspective; } + + // True if anti-aliasing affects this quad. Only valid when quadType == kAxisAligned + bool aaHasEffectOnRect() const; + + // True if this quad is axis-aligned and still has its top-left corner at v0. Equivalently, + // quad == GrQuad(quad->bounds()). Axis-aligned quads with flips and rotations may exactly + // fill their bounds, but their vertex order will not match TL BL TR BR anymore. + bool asRect(SkRect* rect) const; + + // The non-const pointers are provided to support modifying a GrQuad in-place, but care must be + // taken to keep its quad type aligned with the geometric nature of the new coordinates. This is + // no different than using the constructors that accept a quad type. + const float* xs() const { return fX; } + float* xs() { return fX; } + const float* ys() const { return fY; } + float* ys() { return fY; } + const float* ws() const { return fW; } + float* ws() { return fW; } + + void setQuadType(Type newType) { fType = newType; } +private: + template + friend class GrQuadListBase; // for access to fX, fY, fW + + float fX[4]; + float fY[4]; + float fW[4]; + + Type fType; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/geometry/GrQuadBuffer.h b/gfx/skia/skia/src/gpu/geometry/GrQuadBuffer.h new file mode 100644 index 0000000000..dbdc92ccc7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrQuadBuffer.h @@ -0,0 +1,378 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrQuadBuffer_DEFINED +#define GrQuadBuffer_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/gpu/geometry/GrQuad.h" + +template +class GrQuadBuffer { +public: + GrQuadBuffer() + : fCount(0) + , fDeviceType(GrQuad::Type::kAxisAligned) + , fLocalType(GrQuad::Type::kAxisAligned) { + // Pre-allocate space for 1 2D device-space quad, metadata, and header + fData.reserve(this->entrySize(fDeviceType, nullptr)); + } + + // Reserves space for the given number of entries; if 'needsLocals' is true, space will be + // reserved for each entry to also have a 2D local quad. The reserved space assumes 2D device + // quad for simplicity. Since this buffer has a variable bitrate encoding for quads, this may + // over or under reserve, but pre-allocating still helps when possible. + GrQuadBuffer(int count, bool needsLocals = false) + : fCount(0) + , fDeviceType(GrQuad::Type::kAxisAligned) + , fLocalType(GrQuad::Type::kAxisAligned) { + int entrySize = this->entrySize(fDeviceType, needsLocals ? &fLocalType : nullptr); + fData.reserve(count * entrySize); + } + + // The number of device-space quads (and metadata, and optional local quads) that are in the + // the buffer. + int count() const { return fCount; } + + // The most general type for the device-space quads in this buffer + GrQuad::Type deviceQuadType() const { return fDeviceType; } + + // The most general type for the local quads; if no local quads are ever added, this will + // return kAxisAligned. + GrQuad::Type localQuadType() const { return fLocalType; } + + // Append the given 'deviceQuad' to this buffer, with its associated 'metadata'. If 'localQuad' + // is not null, the local coordinates will also be attached to the entry. When an entry + // has local coordinates, during iteration, the Iter::hasLocals() will return true and its + // Iter::localQuad() will be equivalent to the provided local coordinates. If 'localQuad' is + // null then Iter::hasLocals() will report false for the added entry. + void append(const GrQuad& deviceQuad, T&& metadata, const GrQuad* localQuad = nullptr); + + // Copies all entries from 'that' to this buffer + void concat(const GrQuadBuffer& that); + + // Provides a read-only iterator over a quad buffer, giving access to the device quad, metadata + // and optional local quad. + class Iter { + public: + Iter(const GrQuadBuffer* buffer) + : fDeviceQuad(SkRect::MakeEmpty()) + , fLocalQuad(SkRect::MakeEmpty()) + , fBuffer(buffer) + , fCurrentEntry(nullptr) + , fNextEntry(buffer->fData.begin()) { + SkDEBUGCODE(fExpectedCount = buffer->count();) + } + + bool next(); + + const T& metadata() const { this->validate(); return *(fBuffer->metadata(fCurrentEntry)); } + + const GrQuad& deviceQuad() const { this->validate(); return fDeviceQuad; } + + // If isLocalValid() returns false, this returns an empty quad (all 0s) so that localQuad() + // can be called without triggering any sanitizers, for convenience when some other state + // ensures that the quad will eventually not be used. + const GrQuad& localQuad() const { + this->validate(); + return fLocalQuad; + } + + bool isLocalValid() const { + this->validate(); + return fBuffer->header(fCurrentEntry)->fHasLocals; + } + + private: + // Quads are stored locally so that calling code doesn't need to re-declare their own quads + GrQuad fDeviceQuad; + GrQuad fLocalQuad; + + const GrQuadBuffer* fBuffer; + // The pointer to the current entry to read metadata/header details from + const char* fCurrentEntry; + // The pointer to replace fCurrentEntry when next() is called, cached since it is calculated + // automatically while unpacking the quad data. + const char* fNextEntry; + + SkDEBUGCODE(int fExpectedCount;) + + void validate() const { + SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);) + } + }; + + Iter iterator() const { return Iter(this); } + + // Provides a *mutable* iterator over just the metadata stored in the quad buffer. This skips + // unpacking the device and local quads into GrQuads and is intended for use during op + // finalization, which may require rewriting state such as color. + class MetadataIter { + public: + MetadataIter(GrQuadBuffer* list) + : fBuffer(list) + , fCurrentEntry(nullptr) { + SkDEBUGCODE(fExpectedCount = list->count();) + } + + bool next(); + + T& operator*() { this->validate(); return *(fBuffer->metadata(fCurrentEntry)); } + + T* operator->() { this->validate(); return fBuffer->metadata(fCurrentEntry); } + + private: + GrQuadBuffer* fBuffer; + char* fCurrentEntry; + + SkDEBUGCODE(int fExpectedCount;) + + void validate() const { + SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);) + } + }; + + MetadataIter metadata() { return MetadataIter(this); } + +private: + struct alignas(int32_t) Header { + unsigned fDeviceType : 2; + unsigned fLocalType : 2; // Ignore if fHasLocals is false + unsigned fHasLocals : 1; + // Known value to detect if iteration doesn't properly advance through the buffer + SkDEBUGCODE(unsigned fSentinel : 27;) + }; + static_assert(sizeof(Header) == sizeof(int32_t), "Header should be 4 bytes"); + + static constexpr unsigned kSentinel = 0xbaffe; + static constexpr int kMetaSize = sizeof(Header) + sizeof(T); + static constexpr int k2DQuadFloats = 8; + static constexpr int k3DQuadFloats = 12; + + // Each logical entry in the buffer is a variable length tuple storing device coordinates, + // optional local coordinates, and metadata. An entry always has a header that defines the + // quad types of device and local coordinates, and always has metadata of type T. The device + // and local quads' data follows as a variable length array of floats: + // [ header ] = 4 bytes + // [ metadata ] = sizeof(T), assert alignof(T) == 4 so that pointer casts are valid + // [ device xs ] = 4 floats = 16 bytes + // [ device ys ] = 4 floats + // [ device ws ] = 4 floats or 0 floats depending on fDeviceType in header + // [ local xs ] = 4 floats or 0 floats depending on fHasLocals in header + // [ local ys ] = 4 floats or 0 floats depending on fHasLocals in header + // [ local ws ] = 4 floats or 0 floats depending on fHasLocals and fLocalType in header + // FIXME (michaelludwig) - Since this is intended only for ops, can we use the arena to + // allocate storage for the quad buffer? Since this is forward-iteration only, could also + // explore a linked-list structure for concatenating quads when batching ops + SkTDArray fData; + + int fCount; // Number of (device, local, metadata) entries + GrQuad::Type fDeviceType; // Most general type of all entries + GrQuad::Type fLocalType; + + inline int entrySize(GrQuad::Type deviceType, const GrQuad::Type* localType) const { + int size = kMetaSize; + size += (deviceType == GrQuad::Type::kPerspective ? k3DQuadFloats + : k2DQuadFloats) * sizeof(float); + if (localType) { + size += (*localType == GrQuad::Type::kPerspective ? k3DQuadFloats + : k2DQuadFloats) * sizeof(float); + } + return size; + } + inline int entrySize(const Header* header) const { + if (header->fHasLocals) { + GrQuad::Type localType = static_cast(header->fLocalType); + return this->entrySize(static_cast(header->fDeviceType), &localType); + } else { + return this->entrySize(static_cast(header->fDeviceType), nullptr); + } + } + + // Helpers to access typed sections of the buffer, given the start of an entry + inline Header* header(char* entry) { + return static_cast(static_cast(entry)); + } + inline const Header* header(const char* entry) const { + return static_cast(static_cast(entry)); + } + + inline T* metadata(char* entry) { + return static_cast(static_cast(entry + sizeof(Header))); + } + inline const T* metadata(const char* entry) const { + return static_cast(static_cast(entry + sizeof(Header))); + } + + inline float* coords(char* entry) { + return static_cast(static_cast(entry + kMetaSize)); + } + inline const float* coords(const char* entry) const { + return static_cast(static_cast(entry + kMetaSize)); + } + + // Helpers to convert from coordinates to GrQuad and vice versa, returning pointer to the + // next packed quad coordinates. + float* packQuad(const GrQuad& quad, float* coords); + const float* unpackQuad(GrQuad::Type type, const float* coords, GrQuad* quad) const; + +#ifdef SK_DEBUG + void validate(const char* entry, int expectedCount) const; +#endif +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Buffer implementation +/////////////////////////////////////////////////////////////////////////////////////////////////// + +template +float* GrQuadBuffer::packQuad(const GrQuad& quad, float* coords) { + // Copies all 12 (or 8) floats at once, so requires the 3 arrays to be contiguous + // FIXME(michaelludwig) - If this turns out not to be the case, just do 4 copies + SkASSERT(quad.xs() + 4 == quad.ys() && quad.xs() + 8 == quad.ws()); + if (quad.hasPerspective()) { + memcpy(coords, quad.xs(), k3DQuadFloats * sizeof(float)); + return coords + k3DQuadFloats; + } else { + memcpy(coords, quad.xs(), k2DQuadFloats * sizeof(float)); + return coords + k2DQuadFloats; + } +} + +template +const float* GrQuadBuffer::unpackQuad(GrQuad::Type type, const float* coords, GrQuad* quad) const { + SkASSERT(quad->xs() + 4 == quad->ys() && quad->xs() + 8 == quad->ws()); + if (type == GrQuad::Type::kPerspective) { + // Fill in X, Y, and W in one go + memcpy(quad->xs(), coords, k3DQuadFloats * sizeof(float)); + coords = coords + k3DQuadFloats; + } else { + // Fill in X and Y of the quad, and set W to 1s if needed + memcpy(quad->xs(), coords, k2DQuadFloats * sizeof(float)); + coords = coords + k2DQuadFloats; + + if (quad->quadType() == GrQuad::Type::kPerspective) { + // The output quad was previously perspective, so its ws are not 1s + static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f}; + memcpy(quad->ws(), kNoPerspectiveWs, 4 * sizeof(float)); + } + // Else the quad should already have 1s in w + SkASSERT(quad->w(0) == 1.f && quad->w(1) == 1.f && + quad->w(2) == 1.f && quad->w(3) == 1.f); + } + + quad->setQuadType(type); + return coords; +} + +template +void GrQuadBuffer::append(const GrQuad& deviceQuad, T&& metadata, const GrQuad* localQuad) { + GrQuad::Type localType = localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned; + int entrySize = this->entrySize(deviceQuad.quadType(), localQuad ? &localType : nullptr); + + // Fill in the entry, as described in fData's declaration + char* entry = fData.append(entrySize); + // First the header + Header* h = this->header(entry); + h->fDeviceType = static_cast(deviceQuad.quadType()); + h->fHasLocals = static_cast(localQuad != nullptr); + h->fLocalType = static_cast(localQuad ? localQuad->quadType() + : GrQuad::Type::kAxisAligned); + SkDEBUGCODE(h->fSentinel = static_cast(kSentinel);) + + // Second, the fixed-size metadata + static_assert(alignof(T) == 4, "Metadata must be 4 byte aligned"); + *(this->metadata(entry)) = std::move(metadata); + + // Then the variable blocks of x, y, and w float coordinates + float* coords = this->coords(entry); + coords = this->packQuad(deviceQuad, coords); + if (localQuad) { + coords = this->packQuad(*localQuad, coords); + } + SkASSERT((char*)coords - entry == entrySize); + + // Entry complete, update buffer-level state + fCount++; + if (deviceQuad.quadType() > fDeviceType) { + fDeviceType = deviceQuad.quadType(); + } + if (localQuad && localQuad->quadType() > fLocalType) { + fLocalType = localQuad->quadType(); + } +} + +template +void GrQuadBuffer::concat(const GrQuadBuffer& that) { + fData.append(that.fData.count(), that.fData.begin()); + fCount += that.fCount; + if (that.fDeviceType > fDeviceType) { + fDeviceType = that.fDeviceType; + } + if (that.fLocalType > fLocalType) { + fLocalType = that.fLocalType; + } +} + +#ifdef SK_DEBUG +template +void GrQuadBuffer::validate(const char* entry, int expectedCount) const { + // Triggers if accessing before next() is called on an iterator + SkASSERT(entry); + // Triggers if accessing after next() returns false + SkASSERT(entry < fData.end()); + // Triggers if elements have been added to the buffer while iterating entries + SkASSERT(expectedCount == fCount); + // Make sure the start of the entry looks like a header + SkASSERT(this->header(entry)->fSentinel == kSentinel); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Iterator implementations +/////////////////////////////////////////////////////////////////////////////////////////////////// + +template +bool GrQuadBuffer::Iter::next() { + SkASSERT(fNextEntry); + if (fNextEntry >= fBuffer->fData.end()) { + return false; + } + // There is at least one more entry, so store the current start for metadata access + fCurrentEntry = fNextEntry; + + // And then unpack the device and optional local coordinates into fDeviceQuad and fLocalQuad + const Header* h = fBuffer->header(fCurrentEntry); + const float* coords = fBuffer->coords(fCurrentEntry); + coords = fBuffer->unpackQuad(static_cast(h->fDeviceType), coords, &fDeviceQuad); + if (h->fHasLocals) { + coords = fBuffer->unpackQuad(static_cast(h->fLocalType), coords, &fLocalQuad); + } else { + static const GrQuad kEmptyLocal(SkRect::MakeEmpty()); + fLocalQuad = kEmptyLocal; + } + // At this point, coords points to the start of the next entry + fNextEntry = static_cast(static_cast(coords)); + SkASSERT((fNextEntry - fCurrentEntry) == fBuffer->entrySize(h)); + return true; +} + +template +bool GrQuadBuffer::MetadataIter::next() { + if (fCurrentEntry) { + // Advance pointer by entry size + if (fCurrentEntry < fBuffer->fData.end()) { + const Header* h = fBuffer->header(fCurrentEntry); + fCurrentEntry += fBuffer->entrySize(h); + } + } else { + // First call to next + fCurrentEntry = fBuffer->fData.begin(); + } + // Nothing else is needed to do but report whether or not the updated pointer is valid + return fCurrentEntry < fBuffer->fData.end(); +} +#endif // GrQuadBuffer_DEFINED diff --git a/gfx/skia/skia/src/gpu/geometry/GrQuadUtils.cpp b/gfx/skia/skia/src/gpu/geometry/GrQuadUtils.cpp new file mode 100644 index 0000000000..78edc3ccbb --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrQuadUtils.cpp @@ -0,0 +1,346 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/geometry/GrQuadUtils.h" + +#include "include/core/SkRect.h" +#include "include/private/GrTypesPriv.h" +#include "include/private/SkVx.h" +#include "src/gpu/geometry/GrQuad.h" + +using V4f = skvx::Vec<4, float>; +using M4f = skvx::Vec<4, int32_t>; + +// Since the local quad may not be type kRect, this uses the opposites for each vertex when +// interpolating, and calculates new ws in addition to new xs, ys. +static void interpolate_local(float alpha, int v0, int v1, int v2, int v3, + float lx[4], float ly[4], float lw[4]) { + SkASSERT(v0 >= 0 && v0 < 4); + SkASSERT(v1 >= 0 && v1 < 4); + SkASSERT(v2 >= 0 && v2 < 4); + SkASSERT(v3 >= 0 && v3 < 4); + + float beta = 1.f - alpha; + lx[v0] = alpha * lx[v0] + beta * lx[v2]; + ly[v0] = alpha * ly[v0] + beta * ly[v2]; + lw[v0] = alpha * lw[v0] + beta * lw[v2]; + + lx[v1] = alpha * lx[v1] + beta * lx[v3]; + ly[v1] = alpha * ly[v1] + beta * ly[v3]; + lw[v1] = alpha * lw[v1] + beta * lw[v3]; +} + +// Crops v0 to v1 based on the clipDevRect. v2 is opposite of v0, v3 is opposite of v1. +// It is written to not modify coordinates if there's no intersection along the edge. +// Ideally this would have been detected earlier and the entire draw is skipped. +static bool crop_rect_edge(const SkRect& clipDevRect, int v0, int v1, int v2, int v3, + float x[4], float y[4], float lx[4], float ly[4], float lw[4]) { + SkASSERT(v0 >= 0 && v0 < 4); + SkASSERT(v1 >= 0 && v1 < 4); + SkASSERT(v2 >= 0 && v2 < 4); + SkASSERT(v3 >= 0 && v3 < 4); + + if (SkScalarNearlyEqual(x[v0], x[v1])) { + // A vertical edge + if (x[v0] < clipDevRect.fLeft && x[v2] >= clipDevRect.fLeft) { + // Overlapping with left edge of clipDevRect + if (lx) { + float alpha = (x[v2] - clipDevRect.fLeft) / (x[v2] - x[v0]); + interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw); + } + x[v0] = clipDevRect.fLeft; + x[v1] = clipDevRect.fLeft; + return true; + } else if (x[v0] > clipDevRect.fRight && x[v2] <= clipDevRect.fRight) { + // Overlapping with right edge of clipDevRect + if (lx) { + float alpha = (clipDevRect.fRight - x[v2]) / (x[v0] - x[v2]); + interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw); + } + x[v0] = clipDevRect.fRight; + x[v1] = clipDevRect.fRight; + return true; + } + } else { + // A horizontal edge + SkASSERT(SkScalarNearlyEqual(y[v0], y[v1])); + if (y[v0] < clipDevRect.fTop && y[v2] >= clipDevRect.fTop) { + // Overlapping with top edge of clipDevRect + if (lx) { + float alpha = (y[v2] - clipDevRect.fTop) / (y[v2] - y[v0]); + interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw); + } + y[v0] = clipDevRect.fTop; + y[v1] = clipDevRect.fTop; + return true; + } else if (y[v0] > clipDevRect.fBottom && y[v2] <= clipDevRect.fBottom) { + // Overlapping with bottom edge of clipDevRect + if (lx) { + float alpha = (clipDevRect.fBottom - y[v2]) / (y[v0] - y[v2]); + interpolate_local(alpha, v0, v1, v2, v3, lx, ly, lw); + } + y[v0] = clipDevRect.fBottom; + y[v1] = clipDevRect.fBottom; + return true; + } + } + + // No overlap so don't crop it + return false; +} + +// Updates x and y to intersect with clipDevRect. lx, ly, and lw are updated appropriately and may +// be null to skip calculations. Returns bit mask of edges that were clipped. +static GrQuadAAFlags crop_rect(const SkRect& clipDevRect, float x[4], float y[4], + float lx[4], float ly[4], float lw[4]) { + GrQuadAAFlags clipEdgeFlags = GrQuadAAFlags::kNone; + + // The quad's left edge may not align with the SkRect notion of left due to 90 degree rotations + // or mirrors. So, this processes the logical edges of the quad and clamps it to the 4 sides of + // clipDevRect. + + // Quad's left is v0 to v1 (op. v2 and v3) + if (crop_rect_edge(clipDevRect, 0, 1, 2, 3, x, y, lx, ly, lw)) { + clipEdgeFlags |= GrQuadAAFlags::kLeft; + } + // Quad's top edge is v0 to v2 (op. v1 and v3) + if (crop_rect_edge(clipDevRect, 0, 2, 1, 3, x, y, lx, ly, lw)) { + clipEdgeFlags |= GrQuadAAFlags::kTop; + } + // Quad's right edge is v2 to v3 (op. v0 and v1) + if (crop_rect_edge(clipDevRect, 2, 3, 0, 1, x, y, lx, ly, lw)) { + clipEdgeFlags |= GrQuadAAFlags::kRight; + } + // Quad's bottom edge is v1 to v3 (op. v0 and v2) + if (crop_rect_edge(clipDevRect, 1, 3, 0, 2, x, y, lx, ly, lw)) { + clipEdgeFlags |= GrQuadAAFlags::kBottom; + } + + return clipEdgeFlags; +} + +// Similar to crop_rect, but assumes that both the device coordinates and optional local coordinates +// geometrically match the TL, BL, TR, BR vertex ordering, i.e. axis-aligned but not flipped, etc. +static GrQuadAAFlags crop_simple_rect(const SkRect& clipDevRect, float x[4], float y[4], + float lx[4], float ly[4]) { + GrQuadAAFlags clipEdgeFlags = GrQuadAAFlags::kNone; + + // Update local coordinates proportionately to how much the device rect edge was clipped + const SkScalar dx = lx ? (lx[2] - lx[0]) / (x[2] - x[0]) : 0.f; + const SkScalar dy = ly ? (ly[1] - ly[0]) / (y[1] - y[0]) : 0.f; + if (clipDevRect.fLeft > x[0]) { + if (lx) { + lx[0] += (clipDevRect.fLeft - x[0]) * dx; + lx[1] = lx[0]; + } + x[0] = clipDevRect.fLeft; + x[1] = clipDevRect.fLeft; + clipEdgeFlags |= GrQuadAAFlags::kLeft; + } + if (clipDevRect.fTop > y[0]) { + if (ly) { + ly[0] += (clipDevRect.fTop - y[0]) * dy; + ly[2] = ly[0]; + } + y[0] = clipDevRect.fTop; + y[2] = clipDevRect.fTop; + clipEdgeFlags |= GrQuadAAFlags::kTop; + } + if (clipDevRect.fRight < x[2]) { + if (lx) { + lx[2] -= (x[2] - clipDevRect.fRight) * dx; + lx[3] = lx[2]; + } + x[2] = clipDevRect.fRight; + x[3] = clipDevRect.fRight; + clipEdgeFlags |= GrQuadAAFlags::kRight; + } + if (clipDevRect.fBottom < y[1]) { + if (ly) { + ly[1] -= (y[1] - clipDevRect.fBottom) * dy; + ly[3] = ly[1]; + } + y[1] = clipDevRect.fBottom; + y[3] = clipDevRect.fBottom; + clipEdgeFlags |= GrQuadAAFlags::kBottom; + } + + return clipEdgeFlags; +} +// Consistent with GrQuad::asRect()'s return value but requires fewer operations since we don't need +// to calculate the bounds of the quad. +static bool is_simple_rect(const GrQuad& quad) { + if (quad.quadType() != GrQuad::Type::kAxisAligned) { + return false; + } + // v0 at the geometric top-left is unique, so we only need to compare x[0] < x[2] for left + // and y[0] < y[1] for top, but add a little padding to protect against numerical precision + // on R90 and R270 transforms tricking this check. + return ((quad.x(0) + SK_ScalarNearlyZero) < quad.x(2)) && + ((quad.y(0) + SK_ScalarNearlyZero) < quad.y(1)); +} + +// Calculates barycentric coordinates for each point in (testX, testY) in the triangle formed by +// (x0,y0) - (x1,y1) - (x2, y2) and stores them in u, v, w. +static void barycentric_coords(float x0, float y0, float x1, float y1, float x2, float y2, + const V4f& testX, const V4f& testY, + V4f* u, V4f* v, V4f* w) { + // Modeled after SkPathOpsQuad::pointInTriangle() but uses float instead of double, is + // vectorized and outputs normalized barycentric coordinates instead of inside/outside test + float v0x = x2 - x0; + float v0y = y2 - y0; + float v1x = x1 - x0; + float v1y = y1 - y0; + V4f v2x = testX - x0; + V4f v2y = testY - y0; + + float dot00 = v0x * v0x + v0y * v0y; + float dot01 = v0x * v1x + v0y * v1y; + V4f dot02 = v0x * v2x + v0y * v2y; + float dot11 = v1x * v1x + v1y * v1y; + V4f dot12 = v1x * v2x + v1y * v2y; + float invDenom = sk_ieee_float_divide(1.f, dot00 * dot11 - dot01 * dot01); + *u = (dot11 * dot02 - dot01 * dot12) * invDenom; + *v = (dot00 * dot12 - dot01 * dot02) * invDenom; + *w = 1.f - *u - *v; +} + +static M4f inside_triangle(const V4f& u, const V4f& v, const V4f& w) { + return ((u >= 0.f) & (u <= 1.f)) & ((v >= 0.f) & (v <= 1.f)) & ((w >= 0.f) & (w <= 1.f)); +} + +namespace GrQuadUtils { + +void ResolveAAType(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags, const GrQuad& quad, + GrAAType* outAAType, GrQuadAAFlags* outEdgeFlags) { + // Most cases will keep the requested types unchanged + *outAAType = requestedAAType; + *outEdgeFlags = requestedEdgeFlags; + + switch (requestedAAType) { + // When aa type is coverage, disable AA if the edge configuration doesn't actually need it + case GrAAType::kCoverage: + if (requestedEdgeFlags == GrQuadAAFlags::kNone) { + // Turn off anti-aliasing + *outAAType = GrAAType::kNone; + } else { + // For coverage AA, if the quad is a rect and it lines up with pixel boundaries + // then overall aa and per-edge aa can be completely disabled + if (quad.quadType() == GrQuad::Type::kAxisAligned && !quad.aaHasEffectOnRect()) { + *outAAType = GrAAType::kNone; + *outEdgeFlags = GrQuadAAFlags::kNone; + } + } + break; + // For no or msaa anti aliasing, override the edge flags since edge flags only make sense + // when coverage aa is being used. + case GrAAType::kNone: + *outEdgeFlags = GrQuadAAFlags::kNone; + break; + case GrAAType::kMSAA: + *outEdgeFlags = GrQuadAAFlags::kAll; + break; + } +} + +bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, GrQuad* quad, + GrQuad* local) { + SkASSERT(quad->isFinite()); + + if (quad->quadType() == GrQuad::Type::kAxisAligned) { + // crop_rect and crop_rect_simple keep the rectangles as rectangles, so the intersection + // of the crop and quad can be calculated exactly. Some care must be taken if the quad + // is axis-aligned but does not satisfy asRect() due to flips, etc. + GrQuadAAFlags clippedEdges; + if (local) { + if (is_simple_rect(*quad) && is_simple_rect(*local)) { + clippedEdges = crop_simple_rect(cropRect, quad->xs(), quad->ys(), + local->xs(), local->ys()); + } else { + clippedEdges = crop_rect(cropRect, quad->xs(), quad->ys(), + local->xs(), local->ys(), local->ws()); + } + } else { + if (is_simple_rect(*quad)) { + clippedEdges = crop_simple_rect(cropRect, quad->xs(), quad->ys(), nullptr, nullptr); + } else { + clippedEdges = crop_rect(cropRect, quad->xs(), quad->ys(), + nullptr, nullptr, nullptr); + } + } + + // Apply the clipped edge updates to the original edge flags + if (cropAA == GrAA::kYes) { + // Turn on all edges that were clipped + *edgeFlags |= clippedEdges; + } else { + // Turn off all edges that were clipped + *edgeFlags &= ~clippedEdges; + } + return true; + } + + if (local) { + // FIXME (michaelludwig) Calculate cropped local coordinates when not kAxisAligned + return false; + } + + V4f devX = quad->x4f(); + V4f devY = quad->y4f(); + V4f devIW = quad->iw4f(); + // Project the 3D coordinates to 2D + if (quad->quadType() == GrQuad::Type::kPerspective) { + devX *= devIW; + devY *= devIW; + } + + V4f clipX = {cropRect.fLeft, cropRect.fLeft, cropRect.fRight, cropRect.fRight}; + V4f clipY = {cropRect.fTop, cropRect.fBottom, cropRect.fTop, cropRect.fBottom}; + + // Calculate barycentric coordinates for the 4 rect corners in the 2 triangles that the quad + // is tessellated into when drawn. + V4f u1, v1, w1; + barycentric_coords(devX[0], devY[0], devX[1], devY[1], devX[2], devY[2], clipX, clipY, + &u1, &v1, &w1); + V4f u2, v2, w2; + barycentric_coords(devX[1], devY[1], devX[3], devY[3], devX[2], devY[2], clipX, clipY, + &u2, &v2, &w2); + + // clipDevRect is completely inside this quad if each corner is in at least one of two triangles + M4f inTri1 = inside_triangle(u1, v1, w1); + M4f inTri2 = inside_triangle(u2, v2, w2); + if (all(inTri1 | inTri2)) { + // We can crop to exactly the clipDevRect. + // FIXME (michaelludwig) - there are other ways to have determined quad covering the clip + // rect, but the barycentric coords will be useful to derive local coordinates in the future + + // Since we are cropped to exactly clipDevRect, we have discarded any perspective and the + // type becomes kRect. If updated locals were requested, they will incorporate perspective. + // FIXME (michaelludwig) - once we have local coordinates handled, it may be desirable to + // keep the draw as perspective so that the hardware does perspective interpolation instead + // of pushing it into a local coord w and having the shader do an extra divide. + clipX.store(quad->xs()); + clipY.store(quad->ys()); + quad->ws()[0] = 1.f; + quad->ws()[1] = 1.f; + quad->ws()[2] = 1.f; + quad->ws()[3] = 1.f; + quad->setQuadType(GrQuad::Type::kAxisAligned); + + // Update the edge flags to match the clip setting since all 4 edges have been clipped + *edgeFlags = cropAA == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone; + + return true; + } + + // FIXME (michaelludwig) - use the GrQuadPerEdgeAA tessellation inset/outset math to move + // edges to the closest clip corner they are outside of + + return false; +} + +}; // namespace GrQuadUtils diff --git a/gfx/skia/skia/src/gpu/geometry/GrQuadUtils.h b/gfx/skia/skia/src/gpu/geometry/GrQuadUtils.h new file mode 100644 index 0000000000..53b53d2522 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrQuadUtils.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrQuadUtils_DEFINED +#define GrQuadUtils_DEFINED + +enum class GrQuadAAFlags; +enum class GrAA : bool; +enum class GrAAType : unsigned; +class GrQuad; +struct SkRect; + +namespace GrQuadUtils { + + // Resolve disagreements between the overall requested AA type and the per-edge quad AA flags. + // Both outAAType and outEdgeFlags will be updated. + void ResolveAAType(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags, + const GrQuad& quad, GrAAType* outAAtype, GrQuadAAFlags* outEdgeFlags); + + /** + * Crops quad to the provided device-space axis-aligned rectangle. If the intersection of this + * quad (projected) and cropRect results in a quadrilateral, this returns true. If not, this + * quad may be updated to be a smaller quad of the same type such that its intersection with + * cropRect is visually the same. This function assumes that the 'quad' coordinates are finite. + * + * The provided edge flags are updated to reflect edges clipped by cropRect (toggling on or off + * based on cropAA policy). If provided, the local coordinates will be updated to reflect the + * updated device coordinates of this quad. + * + * 'local' may be null, in which case the new local coordinates will not be calculated. This is + * useful when it's known a paint does not require local coordinates. However, neither + * 'edgeFlags' nore 'quad' can be null. + */ + bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, GrQuad* quad, + GrQuad* local=nullptr); + +}; // namespace GrQuadUtils + +#endif diff --git a/gfx/skia/skia/src/gpu/geometry/GrRect.h b/gfx/skia/skia/src/gpu/geometry/GrRect.h new file mode 100644 index 0000000000..9a0bee7d8f --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrRect.h @@ -0,0 +1,148 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRect_DEFINED +#define GrRect_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTo.h" + +struct GrIRect16 { + int16_t fLeft, fTop, fRight, fBottom; + + static GrIRect16 SK_WARN_UNUSED_RESULT MakeEmpty() { + GrIRect16 r; + r.setEmpty(); + return r; + } + + static GrIRect16 SK_WARN_UNUSED_RESULT MakeWH(int16_t w, int16_t h) { + GrIRect16 r; + r.set(0, 0, w, h); + return r; + } + + static GrIRect16 SK_WARN_UNUSED_RESULT MakeXYWH(int16_t x, int16_t y, int16_t w, int16_t h) { + GrIRect16 r; + r.set(x, y, x + w, y + h); + return r; + } + + static GrIRect16 SK_WARN_UNUSED_RESULT Make(const SkIRect& ir) { + GrIRect16 r; + r.set(ir); + return r; + } + + int width() const { return fRight - fLeft; } + int height() const { return fBottom - fTop; } + int area() const { return this->width() * this->height(); } + bool isEmpty() const { return fLeft >= fRight || fTop >= fBottom; } + + void setEmpty() { memset(this, 0, sizeof(*this)); } + + void set(int16_t left, int16_t top, int16_t right, int16_t bottom) { + fLeft = left; + fTop = top; + fRight = right; + fBottom = bottom; + } + + void set(const SkIRect& r) { + fLeft = SkToS16(r.fLeft); + fTop = SkToS16(r.fTop); + fRight = SkToS16(r.fRight); + fBottom = SkToS16(r.fBottom); + } +}; + +/** Returns true if the rectangles have a nonzero area of overlap. It assumed that rects can be + infinitely small but not "inverted". */ +static inline bool GrRectsOverlap(const SkRect& a, const SkRect& b) { + // See skbug.com/6607 about the isFinite() checks. + SkASSERT(!a.isFinite() || (a.fLeft <= a.fRight && a.fTop <= a.fBottom)); + SkASSERT(!b.isFinite() || (b.fLeft <= b.fRight && b.fTop <= b.fBottom)); + return a.fRight > b.fLeft && a.fBottom > b.fTop && b.fRight > a.fLeft && b.fBottom > a.fTop; +} + +/** Returns true if the rectangles overlap or share an edge or corner. It assumed that rects can be + infinitely small but not "inverted". */ +static inline bool GrRectsTouchOrOverlap(const SkRect& a, const SkRect& b) { + // See skbug.com/6607 about the isFinite() checks. + SkASSERT(!a.isFinite() || (a.fLeft <= a.fRight && a.fTop <= a.fBottom)); + SkASSERT(!b.isFinite() || (b.fLeft <= b.fRight && b.fTop <= b.fBottom)); + return a.fRight >= b.fLeft && a.fBottom >= b.fTop && b.fRight >= a.fLeft && b.fBottom >= a.fTop; +} + +/** + * Apply the transform from 'inRect' to 'outRect' to each point in 'inPts', storing the mapped point + * into the parallel index of 'outPts'. + */ +static inline void GrMapRectPoints(const SkRect& inRect, const SkRect& outRect, + const SkPoint inPts[], SkPoint outPts[], int ptCount) { + SkMatrix rectTransform = SkMatrix::MakeRectToRect(inRect, outRect, SkMatrix::kFill_ScaleToFit); + rectTransform.mapPoints(outPts, inPts, ptCount); +} + +/** + * Clips the srcRect and the dstPoint to the bounds of the srcSize and dstSize respectively. Returns + * true if the srcRect and dstRect intersect the srcRect and dst rect (dstPoint with srcRect + * width/height). Returns false otherwise. The clipped values are returned in clippedSrcRect and + * clippedDstPoint. + */ +static inline bool GrClipSrcRectAndDstPoint(const SkISize& dstSize, + const SkISize& srcSize, + const SkIRect& srcRect, + const SkIPoint& dstPoint, + SkIRect* clippedSrcRect, + SkIPoint* clippedDstPoint) { + *clippedSrcRect = srcRect; + *clippedDstPoint = dstPoint; + + // clip the left edge to src and dst bounds, adjusting dstPoint if necessary + if (clippedSrcRect->fLeft < 0) { + clippedDstPoint->fX -= clippedSrcRect->fLeft; + clippedSrcRect->fLeft = 0; + } + if (clippedDstPoint->fX < 0) { + clippedSrcRect->fLeft -= clippedDstPoint->fX; + clippedDstPoint->fX = 0; + } + + // clip the top edge to src and dst bounds, adjusting dstPoint if necessary + if (clippedSrcRect->fTop < 0) { + clippedDstPoint->fY -= clippedSrcRect->fTop; + clippedSrcRect->fTop = 0; + } + if (clippedDstPoint->fY < 0) { + clippedSrcRect->fTop -= clippedDstPoint->fY; + clippedDstPoint->fY = 0; + } + + // clip the right edge to the src and dst bounds. + if (clippedSrcRect->fRight > srcSize.width()) { + clippedSrcRect->fRight = srcSize.width(); + } + if (clippedDstPoint->fX + clippedSrcRect->width() > dstSize.width()) { + clippedSrcRect->fRight = clippedSrcRect->fLeft + dstSize.width() - clippedDstPoint->fX; + } + + // clip the bottom edge to the src and dst bounds. + if (clippedSrcRect->fBottom > srcSize.height()) { + clippedSrcRect->fBottom = srcSize.height(); + } + if (clippedDstPoint->fY + clippedSrcRect->height() > dstSize.height()) { + clippedSrcRect->fBottom = clippedSrcRect->fTop + dstSize.height() - clippedDstPoint->fY; + } + + // The above clipping steps may have inverted the rect if it didn't intersect either the src or + // dst bounds. + return !clippedSrcRect->isEmpty(); +} +#endif diff --git a/gfx/skia/skia/src/gpu/geometry/GrShape.cpp b/gfx/skia/skia/src/gpu/geometry/GrShape.cpp new file mode 100644 index 0000000000..4fc3472ffb --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrShape.cpp @@ -0,0 +1,765 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/geometry/GrShape.h" + +#include + +GrShape& GrShape::operator=(const GrShape& that) { + fStyle = that.fStyle; + this->changeType(that.fType, Type::kPath == that.fType ? &that.path() : nullptr); + switch (fType) { + case Type::kEmpty: + break; + case Type::kInvertedEmpty: + break; + case Type::kRRect: + fRRectData = that.fRRectData; + break; + case Type::kArc: + fArcData = that.fArcData; + break; + case Type::kLine: + fLineData = that.fLineData; + break; + case Type::kPath: + fPathData.fGenID = that.fPathData.fGenID; + break; + } + fInheritedKey.reset(that.fInheritedKey.count()); + sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(), + sizeof(uint32_t) * fInheritedKey.count()); + if (that.fInheritedPathForListeners.isValid()) { + fInheritedPathForListeners.set(*that.fInheritedPathForListeners.get()); + } else { + fInheritedPathForListeners.reset(); + } + return *this; +} + +static bool flip_inversion(bool originalIsInverted, GrShape::FillInversion inversion) { + switch (inversion) { + case GrShape::FillInversion::kPreserve: + return false; + case GrShape::FillInversion::kFlip: + return true; + case GrShape::FillInversion::kForceInverted: + return !originalIsInverted; + case GrShape::FillInversion::kForceNoninverted: + return originalIsInverted; + } + return false; +} + +static bool is_inverted(bool originalIsInverted, GrShape::FillInversion inversion) { + switch (inversion) { + case GrShape::FillInversion::kPreserve: + return originalIsInverted; + case GrShape::FillInversion::kFlip: + return !originalIsInverted; + case GrShape::FillInversion::kForceInverted: + return true; + case GrShape::FillInversion::kForceNoninverted: + return false; + } + return false; +} + +GrShape GrShape::MakeFilled(const GrShape& original, FillInversion inversion) { + if (original.style().isSimpleFill() && !flip_inversion(original.inverseFilled(), inversion)) { + // By returning the original rather than falling through we can preserve any inherited style + // key. Otherwise, we wipe it out below since the style change invalidates it. + return original; + } + GrShape result; + if (original.fInheritedPathForListeners.isValid()) { + result.fInheritedPathForListeners.set(*original.fInheritedPathForListeners.get()); + } + switch (original.fType) { + case Type::kRRect: + result.fType = original.fType; + result.fRRectData.fRRect = original.fRRectData.fRRect; + result.fRRectData.fDir = kDefaultRRectDir; + result.fRRectData.fStart = kDefaultRRectStart; + result.fRRectData.fInverted = is_inverted(original.fRRectData.fInverted, inversion); + break; + case Type::kArc: + result.fType = original.fType; + result.fArcData.fOval = original.fArcData.fOval; + result.fArcData.fStartAngleDegrees = original.fArcData.fStartAngleDegrees; + result.fArcData.fSweepAngleDegrees = original.fArcData.fSweepAngleDegrees; + result.fArcData.fUseCenter = original.fArcData.fUseCenter; + result.fArcData.fInverted = is_inverted(original.fArcData.fInverted, inversion); + break; + case Type::kLine: + // Lines don't fill. + if (is_inverted(original.fLineData.fInverted, inversion)) { + result.fType = Type::kInvertedEmpty; + } else { + result.fType = Type::kEmpty; + } + break; + case Type::kEmpty: + result.fType = is_inverted(false, inversion) ? Type::kInvertedEmpty : Type::kEmpty; + break; + case Type::kInvertedEmpty: + result.fType = is_inverted(true, inversion) ? Type::kInvertedEmpty : Type::kEmpty; + break; + case Type::kPath: + result.initType(Type::kPath, &original.fPathData.fPath); + result.fPathData.fGenID = original.fPathData.fGenID; + if (flip_inversion(original.fPathData.fPath.isInverseFillType(), inversion)) { + result.fPathData.fPath.toggleInverseFillType(); + } + if (!original.style().isSimpleFill()) { + // Going from a non-filled style to fill may allow additional simplifications (e.g. + // closing an open rect that wasn't closed in the original shape because it had + // stroke style). + result.attemptToSimplifyPath(); + } + break; + } + // We don't copy the inherited key since it can contain path effect information that we just + // stripped. + return result; +} + +SkRect GrShape::bounds() const { + // Bounds where left == bottom or top == right can indicate a line or point shape. We return + // inverted bounds for a truly empty shape. + static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1); + switch (fType) { + case Type::kEmpty: + return kInverted; + case Type::kInvertedEmpty: + return kInverted; + case Type::kLine: { + SkRect bounds; + if (fLineData.fPts[0].fX < fLineData.fPts[1].fX) { + bounds.fLeft = fLineData.fPts[0].fX; + bounds.fRight = fLineData.fPts[1].fX; + } else { + bounds.fLeft = fLineData.fPts[1].fX; + bounds.fRight = fLineData.fPts[0].fX; + } + if (fLineData.fPts[0].fY < fLineData.fPts[1].fY) { + bounds.fTop = fLineData.fPts[0].fY; + bounds.fBottom = fLineData.fPts[1].fY; + } else { + bounds.fTop = fLineData.fPts[1].fY; + bounds.fBottom = fLineData.fPts[0].fY; + } + return bounds; + } + case Type::kRRect: + return fRRectData.fRRect.getBounds(); + case Type::kArc: + // Could make this less conservative by looking at angles. + return fArcData.fOval; + case Type::kPath: + return this->path().getBounds(); + } + SK_ABORT("Unknown shape type"); +} + +SkRect GrShape::styledBounds() const { + if (this->isEmpty() && !fStyle.hasNonDashPathEffect()) { + return SkRect::MakeEmpty(); + } + + SkRect bounds; + fStyle.adjustBounds(&bounds, this->bounds()); + return bounds; +} + +// If the path is small enough to be keyed from its data this returns key length, otherwise -1. +static int path_key_from_data_size(const SkPath& path) { + const int verbCnt = path.countVerbs(); + if (verbCnt > GrShape::kMaxKeyFromDataVerbCnt) { + return -1; + } + const int pointCnt = path.countPoints(); + const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path); + + GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t)); + GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t)); + // 2 is for the verb cnt and a fill type. Each verb is a byte but we'll pad the verb data out to + // a uint32_t length. + return 2 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt; +} + +// Writes the path data key into the passed pointer. +static void write_path_key_from_data(const SkPath& path, uint32_t* origKey) { + uint32_t* key = origKey; + // The check below should take care of negative values casted positive. + const int verbCnt = path.countVerbs(); + const int pointCnt = path.countPoints(); + const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path); + SkASSERT(verbCnt <= GrShape::kMaxKeyFromDataVerbCnt); + SkASSERT(pointCnt && verbCnt); + *key++ = path.getFillType(); + *key++ = verbCnt; + memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t)); + int verbKeySize = SkAlign4(verbCnt); + // pad out to uint32_t alignment using value that will stand out when debugging. + uint8_t* pad = reinterpret_cast(key)+ verbCnt; + memset(pad, 0xDE, verbKeySize - verbCnt); + key += verbKeySize >> 2; + + memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt); + GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t)); + key += 2 * pointCnt; + sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt); + GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t)); + SkDEBUGCODE(key += conicWeightCnt); + SkASSERT(key - origKey == path_key_from_data_size(path)); +} + +int GrShape::unstyledKeySize() const { + if (fInheritedKey.count()) { + return fInheritedKey.count(); + } + switch (fType) { + case Type::kEmpty: + return 1; + case Type::kInvertedEmpty: + return 1; + case Type::kRRect: + SkASSERT(!fInheritedKey.count()); + GR_STATIC_ASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t)); + // + 1 for the direction, start index, and inverseness. + return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1; + case Type::kArc: + SkASSERT(!fInheritedKey.count()); + GR_STATIC_ASSERT(0 == sizeof(fArcData) % sizeof(uint32_t)); + return sizeof(fArcData) / sizeof(uint32_t); + case Type::kLine: + GR_STATIC_ASSERT(2 * sizeof(uint32_t) == sizeof(SkPoint)); + // 4 for the end points and 1 for the inverseness + return 5; + case Type::kPath: { + if (0 == fPathData.fGenID) { + return -1; + } + int dataKeySize = path_key_from_data_size(fPathData.fPath); + if (dataKeySize >= 0) { + return dataKeySize; + } + // The key is the path ID and fill type. + return 2; + } + } + SK_ABORT("Should never get here."); +} + +void GrShape::writeUnstyledKey(uint32_t* key) const { + SkASSERT(this->unstyledKeySize()); + SkDEBUGCODE(uint32_t* origKey = key;) + if (fInheritedKey.count()) { + memcpy(key, fInheritedKey.get(), sizeof(uint32_t) * fInheritedKey.count()); + SkDEBUGCODE(key += fInheritedKey.count();) + } else { + switch (fType) { + case Type::kEmpty: + *key++ = 1; + break; + case Type::kInvertedEmpty: + *key++ = 2; + break; + case Type::kRRect: + fRRectData.fRRect.writeToMemory(key); + key += SkRRect::kSizeInMemory / sizeof(uint32_t); + *key = (fRRectData.fDir == SkPath::kCCW_Direction) ? (1 << 31) : 0; + *key |= fRRectData.fInverted ? (1 << 30) : 0; + *key++ |= fRRectData.fStart; + SkASSERT(fRRectData.fStart < 8); + break; + case Type::kArc: + memcpy(key, &fArcData, sizeof(fArcData)); + key += sizeof(fArcData) / sizeof(uint32_t); + break; + case Type::kLine: + memcpy(key, fLineData.fPts, 2 * sizeof(SkPoint)); + key += 4; + *key++ = fLineData.fInverted ? 1 : 0; + break; + case Type::kPath: { + SkASSERT(fPathData.fGenID); + int dataKeySize = path_key_from_data_size(fPathData.fPath); + if (dataKeySize >= 0) { + write_path_key_from_data(fPathData.fPath, key); + return; + } + *key++ = fPathData.fGenID; + // We could canonicalize the fill rule for paths that don't differentiate between + // even/odd or winding fill (e.g. convex). + *key++ = this->path().getFillType(); + break; + } + } + } + SkASSERT(key - origKey == this->unstyledKeySize()); +} + +void GrShape::setInheritedKey(const GrShape &parent, GrStyle::Apply apply, SkScalar scale) { + SkASSERT(!fInheritedKey.count()); + // If the output shape turns out to be simple, then we will just use its geometric key + if (Type::kPath == fType) { + // We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as + // ApplyFullStyle(shape). + // The full key is structured as (geo,path_effect,stroke). + // If we do ApplyPathEffect we get geo,path_effect as the inherited key. If we then + // do ApplyFullStyle we'll memcpy geo,path_effect into the new inherited key + // and then append the style key (which should now be stroke only) at the end. + int parentCnt = parent.fInheritedKey.count(); + bool useParentGeoKey = !parentCnt; + if (useParentGeoKey) { + parentCnt = parent.unstyledKeySize(); + if (parentCnt < 0) { + // The parent's geometry has no key so we will have no key. + fPathData.fGenID = 0; + return; + } + } + uint32_t styleKeyFlags = 0; + if (parent.knownToBeClosed()) { + styleKeyFlags |= GrStyle::kClosed_KeyFlag; + } + if (parent.asLine(nullptr, nullptr)) { + styleKeyFlags |= GrStyle::kNoJoins_KeyFlag; + } + int styleCnt = GrStyle::KeySize(parent.fStyle, apply, styleKeyFlags); + if (styleCnt < 0) { + // The style doesn't allow a key, set the path gen ID to 0 so that we fail when + // we try to get a key for the shape. + fPathData.fGenID = 0; + return; + } + fInheritedKey.reset(parentCnt + styleCnt); + if (useParentGeoKey) { + // This will be the geo key. + parent.writeUnstyledKey(fInheritedKey.get()); + } else { + // This should be (geo,path_effect). + memcpy(fInheritedKey.get(), parent.fInheritedKey.get(), + parentCnt * sizeof(uint32_t)); + } + // Now turn (geo,path_effect) or (geo) into (geo,path_effect,stroke) + GrStyle::WriteKey(fInheritedKey.get() + parentCnt, parent.fStyle, apply, scale, + styleKeyFlags); + } +} + +const SkPath* GrShape::originalPathForListeners() const { + if (fInheritedPathForListeners.isValid()) { + return fInheritedPathForListeners.get(); + } else if (Type::kPath == fType && !fPathData.fPath.isVolatile()) { + return &fPathData.fPath; + } + return nullptr; +} + +void GrShape::addGenIDChangeListener(sk_sp listener) const { + if (const auto* lp = this->originalPathForListeners()) { + SkPathPriv::AddGenIDChangeListener(*lp, std::move(listener)); + } +} + +GrShape GrShape::MakeArc(const SkRect& oval, SkScalar startAngleDegrees, SkScalar sweepAngleDegrees, + bool useCenter, const GrStyle& style) { + GrShape result; + result.changeType(Type::kArc); + result.fArcData.fOval = oval; + result.fArcData.fStartAngleDegrees = startAngleDegrees; + result.fArcData.fSweepAngleDegrees = sweepAngleDegrees; + result.fArcData.fUseCenter = useCenter; + result.fArcData.fInverted = false; + result.fStyle = style; + result.attemptToSimplifyArc(); + return result; +} + +GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) { + const SkPath* thatPath = Type::kPath == that.fType ? &that.fPathData.fPath : nullptr; + this->initType(that.fType, thatPath); + switch (fType) { + case Type::kEmpty: + break; + case Type::kInvertedEmpty: + break; + case Type::kRRect: + fRRectData = that.fRRectData; + break; + case Type::kArc: + fArcData = that.fArcData; + break; + case Type::kLine: + fLineData = that.fLineData; + break; + case Type::kPath: + fPathData.fGenID = that.fPathData.fGenID; + break; + } + fInheritedKey.reset(that.fInheritedKey.count()); + sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(), + sizeof(uint32_t) * fInheritedKey.count()); + if (that.fInheritedPathForListeners.isValid()) { + fInheritedPathForListeners.set(*that.fInheritedPathForListeners.get()); + } +} + +GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) { + // TODO: Add some quantization of scale for better cache performance here or leave that up + // to caller? + // TODO: For certain shapes and stroke params we could ignore the scale. (e.g. miter or bevel + // stroke of a rect). + if (!parent.style().applies() || + (GrStyle::Apply::kPathEffectOnly == apply && !parent.style().pathEffect())) { + this->initType(Type::kEmpty); + *this = parent; + return; + } + + SkPathEffect* pe = parent.fStyle.pathEffect(); + SkTLazy tmpPath; + const GrShape* parentForKey = &parent; + SkTLazy tmpParent; + this->initType(Type::kPath); + fPathData.fGenID = 0; + if (pe) { + const SkPath* srcForPathEffect; + if (parent.fType == Type::kPath) { + srcForPathEffect = &parent.path(); + } else { + srcForPathEffect = tmpPath.init(); + parent.asPath(tmpPath.get()); + } + // Should we consider bounds? Would have to include in key, but it'd be nice to know + // if the bounds actually modified anything before including in key. + SkStrokeRec strokeRec = parent.fStyle.strokeRec(); + if (!parent.fStyle.applyPathEffectToPath(&this->path(), &strokeRec, *srcForPathEffect, + scale)) { + tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr)); + *this = tmpParent.get()->applyStyle(apply, scale); + return; + } + // A path effect has access to change the res scale but we aren't expecting it to and it + // would mess up our key computation. + SkASSERT(scale == strokeRec.getResScale()); + if (GrStyle::Apply::kPathEffectAndStrokeRec == apply && strokeRec.needToApply()) { + // The intermediate shape may not be a general path. If we we're just applying + // the path effect then attemptToReduceFromPath would catch it. This means that + // when we subsequently applied the remaining strokeRec we would have a non-path + // parent shape that would be used to determine the the stroked path's key. + // We detect that case here and change parentForKey to a temporary that represents + // the simpler shape so that applying both path effect and the strokerec all at + // once produces the same key. + tmpParent.init(this->path(), GrStyle(strokeRec, nullptr)); + tmpParent.get()->setInheritedKey(parent, GrStyle::Apply::kPathEffectOnly, scale); + if (!tmpPath.isValid()) { + tmpPath.init(); + } + tmpParent.get()->asPath(tmpPath.get()); + SkStrokeRec::InitStyle fillOrHairline; + // The parent shape may have simplified away the strokeRec, check for that here. + if (tmpParent.get()->style().applies()) { + SkAssertResult(tmpParent.get()->style().applyToPath(&this->path(), &fillOrHairline, + *tmpPath.get(), scale)); + } else if (tmpParent.get()->style().isSimpleFill()) { + fillOrHairline = SkStrokeRec::kFill_InitStyle; + } else { + SkASSERT(tmpParent.get()->style().isSimpleHairline()); + fillOrHairline = SkStrokeRec::kHairline_InitStyle; + } + fStyle.resetToInitStyle(fillOrHairline); + parentForKey = tmpParent.get(); + } else { + fStyle = GrStyle(strokeRec, nullptr); + } + } else { + const SkPath* srcForParentStyle; + if (parent.fType == Type::kPath) { + srcForParentStyle = &parent.path(); + } else { + srcForParentStyle = tmpPath.init(); + parent.asPath(tmpPath.get()); + } + SkStrokeRec::InitStyle fillOrHairline; + SkASSERT(parent.fStyle.applies()); + SkASSERT(!parent.fStyle.pathEffect()); + SkAssertResult(parent.fStyle.applyToPath(&this->path(), &fillOrHairline, *srcForParentStyle, + scale)); + fStyle.resetToInitStyle(fillOrHairline); + } + if (parent.fInheritedPathForListeners.isValid()) { + fInheritedPathForListeners.set(*parent.fInheritedPathForListeners.get()); + } else if (Type::kPath == parent.fType && !parent.fPathData.fPath.isVolatile()) { + fInheritedPathForListeners.set(parent.fPathData.fPath); + } + this->attemptToSimplifyPath(); + this->setInheritedKey(*parentForKey, apply, scale); +} + +void GrShape::attemptToSimplifyPath() { + SkRect rect; + SkRRect rrect; + SkPath::Direction rrectDir; + unsigned rrectStart; + bool inverted = this->path().isInverseFillType(); + SkPoint pts[2]; + if (this->path().isEmpty()) { + // Dashing ignores inverseness skbug.com/5421. + this->changeType(inverted && !this->style().isDashed() ? Type::kInvertedEmpty + : Type::kEmpty); + } else if (this->path().isLine(pts)) { + this->changeType(Type::kLine); + fLineData.fPts[0] = pts[0]; + fLineData.fPts[1] = pts[1]; + fLineData.fInverted = inverted; + } else if (SkPathPriv::IsRRect(this->path(), &rrect, &rrectDir, &rrectStart)) { + this->changeType(Type::kRRect); + fRRectData.fRRect = rrect; + fRRectData.fDir = rrectDir; + fRRectData.fStart = rrectStart; + fRRectData.fInverted = inverted; + SkASSERT(!fRRectData.fRRect.isEmpty()); + } else if (SkPathPriv::IsOval(this->path(), &rect, &rrectDir, &rrectStart)) { + this->changeType(Type::kRRect); + fRRectData.fRRect.setOval(rect); + fRRectData.fDir = rrectDir; + fRRectData.fInverted = inverted; + // convert from oval indexing to rrect indexiing. + fRRectData.fStart = 2 * rrectStart; + } else if (SkPathPriv::IsSimpleClosedRect(this->path(), &rect, &rrectDir, &rrectStart)) { + this->changeType(Type::kRRect); + // When there is a path effect we restrict rect detection to the narrower API that + // gives us the starting position. Otherwise, we will retry with the more aggressive + // isRect(). + fRRectData.fRRect.setRect(rect); + fRRectData.fInverted = inverted; + fRRectData.fDir = rrectDir; + // convert from rect indexing to rrect indexiing. + fRRectData.fStart = 2 * rrectStart; + } else if (!this->style().hasPathEffect()) { + bool closed; + if (this->path().isRect(&rect, &closed, nullptr)) { + if (closed || this->style().isSimpleFill()) { + this->changeType(Type::kRRect); + fRRectData.fRRect.setRect(rect); + // Since there is no path effect the dir and start index is immaterial. + fRRectData.fDir = kDefaultRRectDir; + fRRectData.fStart = kDefaultRRectStart; + // There isn't dashing so we will have to preserver inverseness. + fRRectData.fInverted = inverted; + } + } + } + if (Type::kPath != fType) { + fInheritedKey.reset(0); + // Whenever we simplify to a non-path, break the chain so we no longer refer to the + // original path. This prevents attaching genID listeners to temporary paths created when + // drawing simple shapes. + fInheritedPathForListeners.reset(); + if (Type::kRRect == fType) { + this->attemptToSimplifyRRect(); + } else if (Type::kLine == fType) { + this->attemptToSimplifyLine(); + } + } else { + if (fInheritedKey.count() || this->path().isVolatile()) { + fPathData.fGenID = 0; + } else { + fPathData.fGenID = this->path().getGenerationID(); + } + if (!this->style().hasNonDashPathEffect()) { + if (this->style().strokeRec().getStyle() == SkStrokeRec::kStroke_Style || + this->style().strokeRec().getStyle() == SkStrokeRec::kHairline_Style) { + // Stroke styles don't differentiate between winding and even/odd. + // Moreover, dashing ignores inverseness (skbug.com/5421) + bool inverse = !this->style().isDashed() && this->path().isInverseFillType(); + if (inverse) { + this->path().setFillType(kDefaultPathInverseFillType); + } else { + this->path().setFillType(kDefaultPathFillType); + } + } else if (this->path().isConvex()) { + // There is no distinction between even/odd and non-zero winding count for convex + // paths. + if (this->path().isInverseFillType()) { + this->path().setFillType(kDefaultPathInverseFillType); + } else { + this->path().setFillType(kDefaultPathFillType); + } + } + } + } +} + +void GrShape::attemptToSimplifyRRect() { + SkASSERT(Type::kRRect == fType); + SkASSERT(!fInheritedKey.count()); + if (fRRectData.fRRect.isEmpty()) { + // An empty filled rrect is equivalent to a filled empty path with inversion preserved. + if (fStyle.isSimpleFill()) { + fType = fRRectData.fInverted ? Type::kInvertedEmpty : Type::kEmpty; + fStyle = GrStyle::SimpleFill(); + return; + } + // Dashing a rrect with no width or height is equivalent to filling an emtpy path. + // When skbug.com/7387 is fixed this should be modified or removed as a dashed zero length + // line will produce cap geometry if the effect begins in an "on" interval. + if (fStyle.isDashed() && !fRRectData.fRRect.width() && !fRRectData.fRRect.height()) { + // Dashing ignores the inverseness (currently). skbug.com/5421. + fType = Type::kEmpty; + fStyle = GrStyle::SimpleFill(); + return; + } + } + if (!this->style().hasPathEffect()) { + fRRectData.fDir = kDefaultRRectDir; + fRRectData.fStart = kDefaultRRectStart; + } else if (fStyle.isDashed()) { + // Dashing ignores the inverseness (currently). skbug.com/5421 + fRRectData.fInverted = false; + // Possible TODO here: Check whether the dash results in a single arc or line. + } + // Turn a stroke-and-filled miter rect into a filled rect. TODO: more rrect stroke shortcuts. + if (!fStyle.hasPathEffect() && + fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style && + fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join && + fStyle.strokeRec().getMiter() >= SK_ScalarSqrt2 && + fRRectData.fRRect.isRect()) { + SkScalar r = fStyle.strokeRec().getWidth() / 2; + fRRectData.fRRect = SkRRect::MakeRect(fRRectData.fRRect.rect().makeOutset(r, r)); + fStyle = GrStyle::SimpleFill(); + } +} + +void GrShape::attemptToSimplifyLine() { + SkASSERT(Type::kLine == fType); + SkASSERT(!fInheritedKey.count()); + if (fStyle.isDashed()) { + bool allOffsZero = true; + for (int i = 1; i < fStyle.dashIntervalCnt() && allOffsZero; i += 2) { + allOffsZero = !fStyle.dashIntervals()[i]; + } + if (allOffsZero && this->attemptToSimplifyStrokedLineToRRect()) { + return; + } + // Dashing ignores inverseness. + fLineData.fInverted = false; + return; + } else if (fStyle.hasPathEffect()) { + return; + } + if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) { + // Make stroke + fill be stroke since the fill is empty. + SkStrokeRec rec = fStyle.strokeRec(); + rec.setStrokeStyle(fStyle.strokeRec().getWidth(), false); + fStyle = GrStyle(rec, nullptr); + } + if (fStyle.isSimpleFill()) { + this->changeType(fLineData.fInverted ? Type::kInvertedEmpty : Type::kEmpty); + return; + } + if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style && + this->attemptToSimplifyStrokedLineToRRect()) { + return; + } + // Only path effects could care about the order of the points. Otherwise canonicalize + // the point order. + SkPoint* pts = fLineData.fPts; + if (pts[1].fY < pts[0].fY || (pts[1].fY == pts[0].fY && pts[1].fX < pts[0].fX)) { + using std::swap; + swap(pts[0], pts[1]); + } +} + +void GrShape::attemptToSimplifyArc() { + SkASSERT(fType == Type::kArc); + SkASSERT(!fArcData.fInverted); + if (fArcData.fOval.isEmpty() || !fArcData.fSweepAngleDegrees) { + this->changeType(Type::kEmpty); + return; + } + + // Assuming no path effect, a filled, stroked, hairline, or stroke-and-filled arc that traverses + // the full circle and doesn't use the center point is an oval. Unless it has square or round + // caps. They may protrude out of the oval. Round caps can't protrude out of a circle but we're + // ignoring that for now. + if (fStyle.isSimpleFill() || (!fStyle.pathEffect() && !fArcData.fUseCenter && + fStyle.strokeRec().getCap() == SkPaint::kButt_Cap)) { + if (fArcData.fSweepAngleDegrees >= 360.f || fArcData.fSweepAngleDegrees <= -360.f) { + auto oval = fArcData.fOval; + this->changeType(Type::kRRect); + this->fRRectData.fRRect.setOval(oval); + this->fRRectData.fDir = kDefaultRRectDir; + this->fRRectData.fStart = kDefaultRRectStart; + this->fRRectData.fInverted = false; + return; + } + } + if (!fStyle.pathEffect()) { + // Canonicalize the arc such that the start is always in [0, 360) and the sweep is always + // positive. + if (fArcData.fSweepAngleDegrees < 0) { + fArcData.fStartAngleDegrees = fArcData.fStartAngleDegrees + fArcData.fSweepAngleDegrees; + fArcData.fSweepAngleDegrees = -fArcData.fSweepAngleDegrees; + } + } + if (this->fArcData.fStartAngleDegrees < 0 || this->fArcData.fStartAngleDegrees >= 360.f) { + this->fArcData.fStartAngleDegrees = SkScalarMod(this->fArcData.fStartAngleDegrees, 360.f); + } + // Possible TODOs here: Look at whether dash pattern results in a single dash and convert to + // non-dashed stroke. Stroke and fill can be fill if circular and no path effect. Just stroke + // could as well if the stroke fills the center. +} + +bool GrShape::attemptToSimplifyStrokedLineToRRect() { + SkASSERT(Type::kLine == fType); + SkASSERT(fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style); + + SkRect rect; + SkVector outset; + // If we allowed a rotation angle for rrects we could capture all cases here. + if (fLineData.fPts[0].fY == fLineData.fPts[1].fY) { + rect.fLeft = SkTMin(fLineData.fPts[0].fX, fLineData.fPts[1].fX); + rect.fRight = SkTMax(fLineData.fPts[0].fX, fLineData.fPts[1].fX); + rect.fTop = rect.fBottom = fLineData.fPts[0].fY; + outset.fY = fStyle.strokeRec().getWidth() / 2.f; + outset.fX = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fY; + } else if (fLineData.fPts[0].fX == fLineData.fPts[1].fX) { + rect.fTop = SkTMin(fLineData.fPts[0].fY, fLineData.fPts[1].fY); + rect.fBottom = SkTMax(fLineData.fPts[0].fY, fLineData.fPts[1].fY); + rect.fLeft = rect.fRight = fLineData.fPts[0].fX; + outset.fX = fStyle.strokeRec().getWidth() / 2.f; + outset.fY = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fX; + } else { + return false; + } + rect.outset(outset.fX, outset.fY); + if (rect.isEmpty()) { + this->changeType(Type::kEmpty); + fStyle = GrStyle::SimpleFill(); + return true; + } + SkRRect rrect; + if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) { + SkASSERT(outset.fX == outset.fY); + rrect = SkRRect::MakeRectXY(rect, outset.fX, outset.fY); + } else { + rrect = SkRRect::MakeRect(rect); + } + bool inverted = fLineData.fInverted && !fStyle.hasPathEffect(); + this->changeType(Type::kRRect); + fRRectData.fRRect = rrect; + fRRectData.fInverted = inverted; + fRRectData.fDir = kDefaultRRectDir; + fRRectData.fStart = kDefaultRRectStart; + fStyle = GrStyle::SimpleFill(); + return true; +} diff --git a/gfx/skia/skia/src/gpu/geometry/GrShape.h b/gfx/skia/skia/src/gpu/geometry/GrShape.h new file mode 100644 index 0000000000..37f38f8844 --- /dev/null +++ b/gfx/skia/skia/src/gpu/geometry/GrShape.h @@ -0,0 +1,631 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrShape_DEFINED +#define GrShape_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkRRect.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkTLazy.h" +#include "src/gpu/GrStyle.h" +#include + +/** + * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with. + * It is possible to apply the style to the GrShape to produce a new GrShape where the geometry + * reflects the styling information (e.g. is stroked). It is also possible to apply just the + * path effect from the style. In this case the resulting shape will include any remaining + * stroking information that is to be applied after the path effect. + * + * Shapes can produce keys that represent only the geometry information, not the style. Note that + * when styling information is applied to produce a new shape then the style has been converted + * to geometric information and is included in the new shape's key. When the same style is applied + * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes + * will be the same. + * + * Currently this can only be constructed from a path, rect, or rrect though it can become a path + * applying style to the geometry. The idea is to expand this to cover most or all of the geometries + * that have fast paths in the GPU backend. + */ +class GrShape { +public: + // Keys for paths may be extracted from the path data for small paths. Clients aren't supposed + // to have to worry about this. This value is exposed for unit tests. + static constexpr int kMaxKeyFromDataVerbCnt = 10; + + GrShape() { this->initType(Type::kEmpty); } + + explicit GrShape(const SkPath& path) : GrShape(path, GrStyle::SimpleFill()) {} + + explicit GrShape(const SkRRect& rrect) : GrShape(rrect, GrStyle::SimpleFill()) {} + + explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {} + + GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) { + this->initType(Type::kPath, &path); + this->attemptToSimplifyPath(); + } + + GrShape(const SkRRect& rrect, const GrStyle& style) : fStyle(style) { + this->initType(Type::kRRect); + fRRectData.fRRect = rrect; + fRRectData.fInverted = false; + fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(), + &fRRectData.fDir); + this->attemptToSimplifyRRect(); + } + + GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted, + const GrStyle& style) + : fStyle(style) { + this->initType(Type::kRRect); + fRRectData.fRRect = rrect; + fRRectData.fInverted = inverted; + if (style.pathEffect()) { + fRRectData.fDir = dir; + fRRectData.fStart = start; + if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) { + fRRectData.fStart = (fRRectData.fStart + 1) & 0b110; + } else if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) { + fRRectData.fStart &= 0b110; + } + } else { + fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectData.fDir); + } + this->attemptToSimplifyRRect(); + } + + GrShape(const SkRect& rect, const GrStyle& style) : fStyle(style) { + this->initType(Type::kRRect); + fRRectData.fRRect = SkRRect::MakeRect(rect); + fRRectData.fInverted = false; + fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(), + &fRRectData.fDir); + this->attemptToSimplifyRRect(); + } + + GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) { + this->initType(Type::kPath, &path); + this->attemptToSimplifyPath(); + } + + GrShape(const SkRRect& rrect, const SkPaint& paint) : fStyle(paint) { + this->initType(Type::kRRect); + fRRectData.fRRect = rrect; + fRRectData.fInverted = false; + fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(), + &fRRectData.fDir); + this->attemptToSimplifyRRect(); + } + + GrShape(const SkRect& rect, const SkPaint& paint) : fStyle(paint) { + this->initType(Type::kRRect); + fRRectData.fRRect = SkRRect::MakeRect(rect); + fRRectData.fInverted = false; + fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(), + &fRRectData.fDir); + this->attemptToSimplifyRRect(); + } + + static GrShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees, + SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style); + + GrShape(const GrShape&); + GrShape& operator=(const GrShape& that); + + ~GrShape() { this->changeType(Type::kEmpty); } + + /** + * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled + * version of the shape. + */ + enum class FillInversion { + kPreserve, + kFlip, + kForceNoninverted, + kForceInverted + }; + /** + * Makes a filled shape from the pre-styled original shape and optionally modifies whether + * the fill is inverted or not. It's important to note that the original shape's geometry + * may already have been modified if doing so was neutral with respect to its style + * (e.g. filled paths are always closed when stored in a shape and dashed paths are always + * made non-inverted since dashing ignores inverseness). + */ + static GrShape MakeFilled(const GrShape& original, FillInversion = FillInversion::kPreserve); + + const GrStyle& style() const { return fStyle; } + + /** + * Returns a shape that has either applied the path effect or path effect and stroking + * information from this shape's style to its geometry. Scale is used when approximating the + * output geometry and typically is computed from the view matrix + */ + GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) const { + return GrShape(*this, apply, scale); + } + + bool isRect() const { + if (Type::kRRect != fType) { + return false; + } + + return fRRectData.fRRect.isRect(); + } + + /** Returns the unstyled geometry as a rrect if possible. */ + bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const { + if (Type::kRRect != fType) { + return false; + } + if (rrect) { + *rrect = fRRectData.fRRect; + } + if (dir) { + *dir = fRRectData.fDir; + } + if (start) { + *start = fRRectData.fStart; + } + if (inverted) { + *inverted = fRRectData.fInverted; + } + return true; + } + + /** + * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints. + * An inverse filled line path is still considered a line. + */ + bool asLine(SkPoint pts[2], bool* inverted) const { + if (fType != Type::kLine) { + return false; + } + if (pts) { + pts[0] = fLineData.fPts[0]; + pts[1] = fLineData.fPts[1]; + } + if (inverted) { + *inverted = fLineData.fInverted; + } + return true; + } + + /** Returns the unstyled geometry as a path. */ + void asPath(SkPath* out) const { + switch (fType) { + case Type::kEmpty: + out->reset(); + break; + case Type::kInvertedEmpty: + out->reset(); + out->setFillType(kDefaultPathInverseFillType); + break; + case Type::kRRect: + out->reset(); + out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart); + // Below matches the fill type that attemptToSimplifyPath uses. + if (fRRectData.fInverted) { + out->setFillType(kDefaultPathInverseFillType); + } else { + out->setFillType(kDefaultPathFillType); + } + break; + case Type::kArc: + SkPathPriv::CreateDrawArcPath(out, fArcData.fOval, fArcData.fStartAngleDegrees, + fArcData.fSweepAngleDegrees, fArcData.fUseCenter, + fStyle.isSimpleFill()); + if (fArcData.fInverted) { + out->setFillType(kDefaultPathInverseFillType); + } else { + out->setFillType(kDefaultPathFillType); + } + break; + case Type::kLine: + out->reset(); + out->moveTo(fLineData.fPts[0]); + out->lineTo(fLineData.fPts[1]); + if (fLineData.fInverted) { + out->setFillType(kDefaultPathInverseFillType); + } else { + out->setFillType(kDefaultPathFillType); + } + break; + case Type::kPath: + *out = this->path(); + break; + } + } + + // Can this shape be drawn as a pair of filled nested rectangles? + bool asNestedRects(SkRect rects[2]) const { + if (Type::kPath != fType) { + return false; + } + + // TODO: it would be better two store DRRects natively in the shape rather than converting + // them to a path and then reextracting the nested rects + if (this->path().isInverseFillType()) { + return false; + } + + SkPath::Direction dirs[2]; + if (!SkPathPriv::IsNestedFillRects(this->path(), rects, dirs)) { + return false; + } + + if (SkPath::kWinding_FillType == this->path().getFillType() && dirs[0] == dirs[1]) { + // The two rects need to be wound opposite to each other + return false; + } + + // Right now, nested rects where the margin is not the same width + // all around do not render correctly + const SkScalar* outer = rects[0].asScalars(); + const SkScalar* inner = rects[1].asScalars(); + + bool allEq = true; + + SkScalar margin = SkScalarAbs(outer[0] - inner[0]); + bool allGoE1 = margin >= SK_Scalar1; + + for (int i = 1; i < 4; ++i) { + SkScalar temp = SkScalarAbs(outer[i] - inner[i]); + if (temp < SK_Scalar1) { + allGoE1 = false; + } + if (!SkScalarNearlyEqual(margin, temp)) { + allEq = false; + } + } + + return allEq || allGoE1; + } + + /** + * Returns whether the geometry is empty. Note that applying the style could produce a + * non-empty shape. It also may have an inverse fill. + */ + bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; } + + /** + * Gets the bounds of the geometry without reflecting the shape's styling. This ignores + * the inverse fill nature of the geometry. + */ + SkRect bounds() const; + + /** + * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill + * status). + */ + SkRect styledBounds() const; + + /** + * Is this shape known to be convex, before styling is applied. An unclosed but otherwise + * convex path is considered to be closed if they styling reflects a fill and not otherwise. + * This is because filling closes all contours in the path. + */ + bool knownToBeConvex() const { + switch (fType) { + case Type::kEmpty: + return true; + case Type::kInvertedEmpty: + return true; + case Type::kRRect: + return true; + case Type::kArc: + return SkPathPriv::DrawArcIsConvex(fArcData.fSweepAngleDegrees, + SkToBool(fArcData.fUseCenter), + fStyle.isSimpleFill()); + case Type::kLine: + return true; + case Type::kPath: + // SkPath.isConvex() really means "is this path convex were it to be closed" and + // thus doesn't give the correct answer for stroked paths, hence we also check + // whether the path is either filled or closed. Convex paths may only have one + // contour hence isLastContourClosed() is a sufficient for a convex path. + return (this->style().isSimpleFill() || this->path().isLastContourClosed()) && + this->path().isConvex(); + } + return false; + } + + /** + * Does the shape have a known winding direction. Some degenerate convex shapes may not have + * a computable direction, but this is not always a requirement for path renderers so it is + * kept separate from knownToBeConvex(). + */ + bool knownDirection() const { + switch (fType) { + case Type::kEmpty: + return true; + case Type::kInvertedEmpty: + return true; + case Type::kRRect: + return true; + case Type::kArc: + return true; + case Type::kLine: + return true; + case Type::kPath: + // Assuming this is called after knownToBeConvex(), this should just be relying on + // cached convexity and direction and will be cheap. + return !SkPathPriv::CheapIsFirstDirection(this->path(), + SkPathPriv::kUnknown_FirstDirection); + } + return false; + } + + /** Is the pre-styled geometry inverse filled? */ + bool inverseFilled() const { + bool ret = false; + switch (fType) { + case Type::kEmpty: + ret = false; + break; + case Type::kInvertedEmpty: + ret = true; + break; + case Type::kRRect: + ret = fRRectData.fInverted; + break; + case Type::kArc: + ret = fArcData.fInverted; + break; + case Type::kLine: + ret = fLineData.fInverted; + break; + case Type::kPath: + ret = this->path().isInverseFillType(); + break; + } + // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421 + SkASSERT(!(ret && this->style().isDashed())); + return ret; + } + + /** + * Might applying the styling to the geometry produce an inverse fill. The "may" part comes in + * because an arbitrary path effect could produce an inverse filled path. In other cases this + * can be thought of as "inverseFilledAfterStyling()". + */ + bool mayBeInverseFilledAfterStyling() const { + // An arbitrary path effect can produce an arbitrary output path, which may be inverse + // filled. + if (this->style().hasNonDashPathEffect()) { + return true; + } + return this->inverseFilled(); + } + + /** + * Is it known that the unstyled geometry has no unclosed contours. This means that it will + * not have any caps if stroked (modulo the effect of any path effect). + */ + bool knownToBeClosed() const { + switch (fType) { + case Type::kEmpty: + return true; + case Type::kInvertedEmpty: + return true; + case Type::kRRect: + return true; + case Type::kArc: + return fArcData.fUseCenter; + case Type::kLine: + return false; + case Type::kPath: + // SkPath doesn't keep track of the closed status of each contour. + return SkPathPriv::IsClosedSingleContour(this->path()); + } + return false; + } + + uint32_t segmentMask() const { + switch (fType) { + case Type::kEmpty: + return 0; + case Type::kInvertedEmpty: + return 0; + case Type::kRRect: + if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) { + return SkPath::kConic_SegmentMask; + } else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type || + fRRectData.fRRect.getType() == SkRRect::kEmpty_Type) { + return SkPath::kLine_SegmentMask; + } + return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask; + case Type::kArc: + if (fArcData.fUseCenter) { + return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask; + } + return SkPath::kConic_SegmentMask; + case Type::kLine: + return SkPath::kLine_SegmentMask; + case Type::kPath: + return this->path().getSegmentMasks(); + } + return 0; + } + + /** + * Gets the size of the key for the shape represented by this GrShape (ignoring its styling). + * A negative value is returned if the shape has no key (shouldn't be cached). + */ + int unstyledKeySize() const; + + bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; } + + /** + * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough + * space allocated for the key and that unstyledKeySize() does not return a negative value + * for this shape. + */ + void writeUnstyledKey(uint32_t* key) const; + + /** + * Adds a listener to the *original* path. Typically used to invalidate cached resources when + * a path is no longer in-use. If the shape started out as something other than a path, this + * does nothing. + */ + void addGenIDChangeListener(sk_sp) const; + + /** + * Helpers that are only exposed for unit tests, to determine if the shape is a path, and get + * the generation ID of the *original* path. This is the path that will receive + * GenIDChangeListeners added to this shape. + */ + uint32_t testingOnly_getOriginalGenerationID() const; + bool testingOnly_isPath() const; + bool testingOnly_isNonVolatilePath() const; + +private: + enum class Type { + kEmpty, + kInvertedEmpty, + kRRect, + kArc, + kLine, + kPath, + }; + + void initType(Type type, const SkPath* path = nullptr) { + fType = Type::kEmpty; + this->changeType(type, path); + } + + void changeType(Type type, const SkPath* path = nullptr) { + bool wasPath = Type::kPath == fType; + fType = type; + bool isPath = Type::kPath == type; + SkASSERT(!path || isPath); + if (wasPath && !isPath) { + fPathData.fPath.~SkPath(); + } else if (!wasPath && isPath) { + if (path) { + new (&fPathData.fPath) SkPath(*path); + } else { + new (&fPathData.fPath) SkPath(); + } + } else if (isPath && path) { + fPathData.fPath = *path; + } + // Whether or not we use the path's gen ID is decided in attemptToSimplifyPath. + fPathData.fGenID = 0; + } + + SkPath& path() { + SkASSERT(Type::kPath == fType); + return fPathData.fPath; + } + + const SkPath& path() const { + SkASSERT(Type::kPath == fType); + return fPathData.fPath; + } + + /** Constructor used by the applyStyle() function */ + GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale); + + /** + * Determines the key we should inherit from the input shape's geometry and style when + * we are applying the style to create a new shape. + */ + void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale); + + void attemptToSimplifyPath(); + void attemptToSimplifyRRect(); + void attemptToSimplifyLine(); + void attemptToSimplifyArc(); + + bool attemptToSimplifyStrokedLineToRRect(); + + /** Gets the path that gen id listeners should be added to. */ + const SkPath* originalPathForListeners() const; + + // Defaults to use when there is no distinction between even/odd and winding fills. + static constexpr SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType; + static constexpr SkPath::FillType kDefaultPathInverseFillType = + SkPath::kInverseEvenOdd_FillType; + + static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction; + static constexpr unsigned kDefaultRRectStart = 0; + + static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect, + SkPath::Direction* dir) { + *dir = kDefaultRRectDir; + // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise + // beginning at index 0 (which happens to correspond to rrect index 0 or 7). + if (!hasPathEffect) { + // It doesn't matter what start we use, just be consistent to avoid redundant keys. + return kDefaultRRectStart; + } + // In SkPath a rect starts at index 0 by default. This is the top left corner. However, + // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the + // rect edges. Thus, we may need to modify the rrect's start index to account for the sort. + bool swapX = rect.fLeft > rect.fRight; + bool swapY = rect.fTop > rect.fBottom; + if (swapX && swapY) { + // 0 becomes start index 2 and times 2 to convert from rect the rrect indices. + return 2 * 2; + } else if (swapX) { + *dir = SkPath::kCCW_Direction; + // 0 becomes start index 1 and times 2 to convert from rect the rrect indices. + return 2 * 1; + } else if (swapY) { + *dir = SkPath::kCCW_Direction; + // 0 becomes start index 3 and times 2 to convert from rect the rrect indices. + return 2 * 3; + } + return 0; + } + + static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect, + SkPath::Direction* dir) { + // This comes from SkPath's interface. The default for adding a SkRRect to a path is + // clockwise beginning at starting index 6. + static constexpr unsigned kPathRRectStartIdx = 6; + *dir = kDefaultRRectDir; + if (!hasPathEffect) { + // It doesn't matter what start we use, just be consistent to avoid redundant keys. + return kDefaultRRectStart; + } + return kPathRRectStartIdx; + } + + union { + struct { + SkRRect fRRect; + SkPath::Direction fDir; + unsigned fStart; + bool fInverted; + } fRRectData; + struct { + SkRect fOval; + SkScalar fStartAngleDegrees; + SkScalar fSweepAngleDegrees; + int16_t fUseCenter; + int16_t fInverted; + } fArcData; + struct { + SkPath fPath; + // Gen ID of the original path (fPath may be modified) + int32_t fGenID; + } fPathData; + struct { + SkPoint fPts[2]; + bool fInverted; + } fLineData; + }; + GrStyle fStyle; + SkTLazy fInheritedPathForListeners; + SkAutoSTArray<8, uint32_t> fInheritedKey; + Type fType; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp b/gfx/skia/skia/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp new file mode 100644 index 0000000000..afaf4d3b42 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp @@ -0,0 +1,509 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * THIS FILE IS AUTOGENERATED + * Make edits to tools/gpu/gl/interface/templates.go or they will + * be overwritten. + */ + +#include "include/gpu/gl/GrGLAssembleHelpers.h" +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F) +#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S) +#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F) + +#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S) + +#if SK_DISABLE_GL_ES_INTERFACE +sk_sp GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) { + return nullptr; +} +#else +sk_sp GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) { + GET_PROC_LOCAL(GetString); + if (nullptr == GetString) { + return nullptr; + } + + const char* verStr = reinterpret_cast(GetString(GR_GL_VERSION)); + GrGLVersion glVer = GrGLGetVersionFromString(verStr); + + if (glVer < GR_GL_VER(2,0)) { + return nullptr; + } + + GET_PROC_LOCAL(GetIntegerv); + GET_PROC_LOCAL(GetStringi); + GrEGLQueryStringFn* queryString; + GrEGLDisplay display; + GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get); + GrGLExtensions extensions; + if (!extensions.init(kGLES_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString, + display)) { + return nullptr; + } + + sk_sp interface(new GrGLInterface); + GrGLInterface::Functions* functions = &interface->fFunctions; + + // Autogenerated content follows + GET_PROC(ActiveTexture); + GET_PROC(AttachShader); + GET_PROC(BindAttribLocation); + GET_PROC(BindBuffer); + GET_PROC(BindTexture); + GET_PROC(BlendColor); + GET_PROC(BlendEquation); + GET_PROC(BlendFunc); + GET_PROC(BufferData); + GET_PROC(BufferSubData); + GET_PROC(Clear); + GET_PROC(ClearColor); + GET_PROC(ClearStencil); + GET_PROC(ColorMask); + GET_PROC(CompileShader); + GET_PROC(CompressedTexImage2D); + GET_PROC(CompressedTexSubImage2D); + GET_PROC(CopyTexSubImage2D); + GET_PROC(CreateProgram); + GET_PROC(CreateShader); + GET_PROC(CullFace); + GET_PROC(DeleteBuffers); + GET_PROC(DeleteProgram); + GET_PROC(DeleteShader); + GET_PROC(DeleteTextures); + GET_PROC(DepthMask); + GET_PROC(Disable); + GET_PROC(DisableVertexAttribArray); + GET_PROC(DrawArrays); + GET_PROC(DrawElements); + GET_PROC(Enable); + GET_PROC(EnableVertexAttribArray); + GET_PROC(Finish); + GET_PROC(Flush); + GET_PROC(FrontFace); + GET_PROC(GenBuffers); + GET_PROC(GenTextures); + GET_PROC(GetBufferParameteriv); + GET_PROC(GetError); + GET_PROC(GetIntegerv); + GET_PROC(GetProgramInfoLog); + GET_PROC(GetProgramiv); + GET_PROC(GetShaderInfoLog); + GET_PROC(GetShaderiv); + GET_PROC(GetString); + GET_PROC(GetUniformLocation); + GET_PROC(IsTexture); + GET_PROC(LineWidth); + GET_PROC(LinkProgram); + GET_PROC(PixelStorei); + GET_PROC(ReadPixels); + GET_PROC(Scissor); + GET_PROC(ShaderSource); + GET_PROC(StencilFunc); + GET_PROC(StencilFuncSeparate); + GET_PROC(StencilMask); + GET_PROC(StencilMaskSeparate); + GET_PROC(StencilOp); + GET_PROC(StencilOpSeparate); + GET_PROC(TexImage2D); + GET_PROC(TexParameterf); + GET_PROC(TexParameterfv); + GET_PROC(TexParameteri); + GET_PROC(TexParameteriv); + GET_PROC(TexSubImage2D); + GET_PROC(Uniform1f); + GET_PROC(Uniform1fv); + GET_PROC(Uniform1i); + GET_PROC(Uniform1iv); + GET_PROC(Uniform2f); + GET_PROC(Uniform2fv); + GET_PROC(Uniform2i); + GET_PROC(Uniform2iv); + GET_PROC(Uniform3f); + GET_PROC(Uniform3fv); + GET_PROC(Uniform3i); + GET_PROC(Uniform3iv); + GET_PROC(Uniform4f); + GET_PROC(Uniform4fv); + GET_PROC(Uniform4i); + GET_PROC(Uniform4iv); + GET_PROC(UniformMatrix2fv); + GET_PROC(UniformMatrix3fv); + GET_PROC(UniformMatrix4fv); + GET_PROC(UseProgram); + GET_PROC(VertexAttrib1f); + GET_PROC(VertexAttrib2fv); + GET_PROC(VertexAttrib3fv); + GET_PROC(VertexAttrib4fv); + GET_PROC(VertexAttribPointer); + GET_PROC(Viewport); + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(GetStringi); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(BindVertexArray); + GET_PROC(DeleteVertexArrays); + GET_PROC(GenVertexArrays); + } else if (extensions.has("GL_OES_vertex_array_object")) { + GET_PROC_SUFFIX(BindVertexArray, OES); + GET_PROC_SUFFIX(DeleteVertexArrays, OES); + GET_PROC_SUFFIX(GenVertexArrays, OES); + } + + if (glVer >= GR_GL_VER(3,0) && extensions.has("GL_EXT_blend_func_extended")) { + GET_PROC_SUFFIX(BindFragDataLocation, EXT); + } + + if (glVer >= GR_GL_VER(3,0) && extensions.has("GL_EXT_blend_func_extended")) { + GET_PROC_SUFFIX(BindFragDataLocationIndexed, EXT); + } + + if (extensions.has("GL_KHR_blend_equation_advanced")) { + GET_PROC_SUFFIX(BlendBarrier, KHR); + } else if (extensions.has("GL_NV_blend_equation_advanced")) { + GET_PROC_SUFFIX(BlendBarrier, NV); + } + + if (extensions.has("GL_EXT_clear_texture")) { + GET_PROC_SUFFIX(ClearTexImage, EXT); + GET_PROC_SUFFIX(ClearTexSubImage, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(DrawArraysInstanced); + GET_PROC(DrawElementsInstanced); + } else if (extensions.has("GL_EXT_draw_instanced")) { + GET_PROC_SUFFIX(DrawArraysInstanced, EXT); + GET_PROC_SUFFIX(DrawElementsInstanced, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(DrawBuffers); + GET_PROC(ReadBuffer); + } + + if (glVer >= GR_GL_VER(3,1)) { + GET_PROC(DrawArraysIndirect); + GET_PROC(DrawElementsIndirect); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(DrawRangeElements); + } + + if (glVer >= GR_GL_VER(3,1)) { + GET_PROC(GetMultisamplefv); + } + + if (glVer >= GR_GL_VER(3,1)) { + GET_PROC(GetTexLevelParameteriv); + } + + if (extensions.has("GL_EXT_multi_draw_indirect")) { + GET_PROC_SUFFIX(MultiDrawArraysIndirect, EXT); + GET_PROC_SUFFIX(MultiDrawElementsIndirect, EXT); + } + + if (glVer >= GR_GL_VER(3,2)) { + GET_PROC(TexBuffer); + } else if (extensions.has("GL_OES_texture_buffer")) { + GET_PROC_SUFFIX(TexBuffer, OES); + } else if (extensions.has("GL_EXT_texture_buffer")) { + GET_PROC_SUFFIX(TexBuffer, EXT); + } + + if (glVer >= GR_GL_VER(3,2)) { + GET_PROC(TexBufferRange); + } else if (extensions.has("GL_OES_texture_buffer")) { + GET_PROC_SUFFIX(TexBufferRange, OES); + } else if (extensions.has("GL_EXT_texture_buffer")) { + GET_PROC_SUFFIX(TexBufferRange, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(TexStorage2D); + } else if (extensions.has("GL_EXT_texture_storage")) { + GET_PROC_SUFFIX(TexStorage2D, EXT); + } + + if (extensions.has("GL_NV_texture_barrier")) { + GET_PROC_SUFFIX(TextureBarrier, NV); + } + + if (extensions.has("GL_EXT_discard_framebuffer")) { + GET_PROC_SUFFIX(DiscardFramebuffer, EXT); + } + + if (extensions.has("GL_QCOM_tiled_rendering")) { + GET_PROC_SUFFIX(EndTiling, QCOM); + GET_PROC_SUFFIX(StartTiling, QCOM); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(VertexAttribDivisor); + } else if (extensions.has("GL_EXT_instanced_arrays")) { + GET_PROC_SUFFIX(VertexAttribDivisor, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(VertexAttribIPointer); + } + + GET_PROC(BindFramebuffer); + GET_PROC(BindRenderbuffer); + GET_PROC(CheckFramebufferStatus); + GET_PROC(DeleteFramebuffers); + GET_PROC(DeleteRenderbuffers); + GET_PROC(FramebufferRenderbuffer); + GET_PROC(FramebufferTexture2D); + GET_PROC(GenFramebuffers); + GET_PROC(GenRenderbuffers); + GET_PROC(GenerateMipmap); + GET_PROC(GetFramebufferAttachmentParameteriv); + GET_PROC(GetRenderbufferParameteriv); + GET_PROC(RenderbufferStorage); + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(BlitFramebuffer); + } else if (extensions.has("GL_CHROMIUM_framebuffer_multisample")) { + GET_PROC_SUFFIX(BlitFramebuffer, CHROMIUM); + } else if (extensions.has("GL_ANGLE_framebuffer_blit")) { + GET_PROC_SUFFIX(BlitFramebuffer, ANGLE); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(RenderbufferStorageMultisample); + } else if (extensions.has("GL_CHROMIUM_framebuffer_multisample")) { + GET_PROC_SUFFIX(RenderbufferStorageMultisample, CHROMIUM); + } else if (extensions.has("GL_ANGLE_framebuffer_multisample")) { + GET_PROC_SUFFIX(RenderbufferStorageMultisample, ANGLE); + } + + if (extensions.has("GL_CHROMIUM_map_sub")) { + GET_PROC_SUFFIX(MapBufferSubData, CHROMIUM); + GET_PROC_SUFFIX(MapTexSubImage2D, CHROMIUM); + GET_PROC_SUFFIX(UnmapBufferSubData, CHROMIUM); + GET_PROC_SUFFIX(UnmapTexSubImage2D, CHROMIUM); + } + + if (extensions.has("GL_EXT_multisampled_render_to_texture")) { + GET_PROC_SUFFIX(FramebufferTexture2DMultisample, EXT); + } else if (extensions.has("GL_IMG_multisampled_render_to_texture")) { + GET_PROC_SUFFIX(FramebufferTexture2DMultisample, IMG); + } + + if (extensions.has("GL_EXT_multisampled_render_to_texture")) { + functions->fRenderbufferStorageMultisampleES2EXT =(GrGLRenderbufferStorageMultisampleFn*)get(ctx, "glRenderbufferStorageMultisampleEXT"); + } + + if (extensions.has("GL_IMG_multisampled_render_to_texture")) { + functions->fRenderbufferStorageMultisampleES2EXT =(GrGLRenderbufferStorageMultisampleFn*)get(ctx, "glRenderbufferStorageMultisampleIMG"); + } + + if (extensions.has("GL_APPLE_framebuffer_multisample")) { + GET_PROC_SUFFIX(ResolveMultisampleFramebuffer, APPLE); + functions->fRenderbufferStorageMultisampleES2APPLE =(GrGLRenderbufferStorageMultisampleFn*)get(ctx, "glRenderbufferStorageMultisampleAPPLE"); + } + + if (extensions.has("GL_OES_mapbuffer")) { + GET_PROC_SUFFIX(MapBuffer, OES); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(UnmapBuffer); + } else if (extensions.has("GL_OES_mapbuffer")) { + GET_PROC_SUFFIX(UnmapBuffer, OES); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(FlushMappedBufferRange); + GET_PROC(MapBufferRange); + } else if (extensions.has("GL_EXT_map_buffer_range")) { + GET_PROC_SUFFIX(FlushMappedBufferRange, EXT); + GET_PROC_SUFFIX(MapBufferRange, EXT); + } + + if (extensions.has("GL_EXT_debug_marker")) { + GET_PROC_SUFFIX(InsertEventMarker, EXT); + GET_PROC_SUFFIX(PopGroupMarker, EXT); + GET_PROC_SUFFIX(PushGroupMarker, EXT); + } + + if (glVer >= GR_GL_VER(3,1)) { + GET_PROC(GetProgramResourceLocation); + } + + if (extensions.has("GL_CHROMIUM_path_rendering")) { + GET_PROC_SUFFIX(MatrixLoadIdentity, CHROMIUM); + GET_PROC_SUFFIX(MatrixLoadf, CHROMIUM); + } else if (extensions.has("GL_NV_path_rendering")) { + GET_PROC_SUFFIX(MatrixLoadIdentity, EXT); + GET_PROC_SUFFIX(MatrixLoadf, EXT); + } + + if (extensions.has("GL_CHROMIUM_path_rendering")) { + GET_PROC_SUFFIX(CoverFillPath, CHROMIUM); + GET_PROC_SUFFIX(CoverFillPathInstanced, CHROMIUM); + GET_PROC_SUFFIX(CoverStrokePath, CHROMIUM); + GET_PROC_SUFFIX(CoverStrokePathInstanced, CHROMIUM); + GET_PROC_SUFFIX(DeletePaths, CHROMIUM); + GET_PROC_SUFFIX(GenPaths, CHROMIUM); + GET_PROC_SUFFIX(IsPath, CHROMIUM); + GET_PROC_SUFFIX(PathCommands, CHROMIUM); + GET_PROC_SUFFIX(PathParameterf, CHROMIUM); + GET_PROC_SUFFIX(PathParameteri, CHROMIUM); + GET_PROC_SUFFIX(PathStencilFunc, CHROMIUM); + GET_PROC_SUFFIX(ProgramPathFragmentInputGen, CHROMIUM); + GET_PROC_SUFFIX(StencilFillPath, CHROMIUM); + GET_PROC_SUFFIX(StencilFillPathInstanced, CHROMIUM); + GET_PROC_SUFFIX(StencilStrokePath, CHROMIUM); + GET_PROC_SUFFIX(StencilStrokePathInstanced, CHROMIUM); + GET_PROC_SUFFIX(StencilThenCoverFillPath, CHROMIUM); + GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, CHROMIUM); + GET_PROC_SUFFIX(StencilThenCoverStrokePath, CHROMIUM); + GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, CHROMIUM); + } else if (extensions.has("GL_NV_path_rendering")) { + GET_PROC_SUFFIX(CoverFillPath, NV); + GET_PROC_SUFFIX(CoverFillPathInstanced, NV); + GET_PROC_SUFFIX(CoverStrokePath, NV); + GET_PROC_SUFFIX(CoverStrokePathInstanced, NV); + GET_PROC_SUFFIX(DeletePaths, NV); + GET_PROC_SUFFIX(GenPaths, NV); + GET_PROC_SUFFIX(IsPath, NV); + GET_PROC_SUFFIX(PathCommands, NV); + GET_PROC_SUFFIX(PathParameterf, NV); + GET_PROC_SUFFIX(PathParameteri, NV); + GET_PROC_SUFFIX(PathStencilFunc, NV); + GET_PROC_SUFFIX(ProgramPathFragmentInputGen, NV); + GET_PROC_SUFFIX(StencilFillPath, NV); + GET_PROC_SUFFIX(StencilFillPathInstanced, NV); + GET_PROC_SUFFIX(StencilStrokePath, NV); + GET_PROC_SUFFIX(StencilStrokePathInstanced, NV); + GET_PROC_SUFFIX(StencilThenCoverFillPath, NV); + GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, NV); + GET_PROC_SUFFIX(StencilThenCoverStrokePath, NV); + GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, NV); + } + + if (extensions.has("GL_CHROMIUM_path_rendering")) { + GET_PROC_SUFFIX(BindFragmentInputLocation, CHROMIUM); + } + + if (extensions.has("GL_CHROMIUM_framebuffer_mixed_samples")) { + GET_PROC_SUFFIX(CoverageModulation, CHROMIUM); + } else if (extensions.has("GL_NV_framebuffer_mixed_samples")) { + GET_PROC_SUFFIX(CoverageModulation, NV); + } + + if (extensions.has("GL_KHR_debug")) { + GET_PROC_SUFFIX(DebugMessageCallback, KHR); + GET_PROC_SUFFIX(DebugMessageControl, KHR); + GET_PROC_SUFFIX(DebugMessageInsert, KHR); + GET_PROC_SUFFIX(GetDebugMessageLog, KHR); + GET_PROC_SUFFIX(ObjectLabel, KHR); + GET_PROC_SUFFIX(PopDebugGroup, KHR); + GET_PROC_SUFFIX(PushDebugGroup, KHR); + } + + if (extensions.has("GL_CHROMIUM_bind_uniform_location")) { + GET_PROC_SUFFIX(BindUniformLocation, CHROMIUM); + } + + if (extensions.has("GL_EXT_window_rectangles")) { + GET_PROC_SUFFIX(WindowRectangles, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(ClientWaitSync); + GET_PROC(DeleteSync); + GET_PROC(FenceSync); + GET_PROC(IsSync); + GET_PROC(WaitSync); + } else if (extensions.has("GL_APPLE_sync")) { + GET_PROC_SUFFIX(ClientWaitSync, APPLE); + GET_PROC_SUFFIX(DeleteSync, APPLE); + GET_PROC_SUFFIX(FenceSync, APPLE); + GET_PROC_SUFFIX(IsSync, APPLE); + GET_PROC_SUFFIX(WaitSync, APPLE); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(GetInternalformativ); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(GetProgramBinary); + GET_PROC(ProgramBinary); + } else if (extensions.has("GL_OES_get_program_binary")) { + GET_PROC_SUFFIX(GetProgramBinary, OES); + GET_PROC_SUFFIX(ProgramBinary, OES); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(ProgramParameteri); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(BindSampler); + GET_PROC(DeleteSamplers); + GET_PROC(GenSamplers); + GET_PROC(SamplerParameteri); + GET_PROC(SamplerParameteriv); + } + + if (glVer >= GR_GL_VER(3,0)) { +#if GR_TEST_UTILS + GET_PROC(BeginQuery); + GET_PROC(DeleteQueries); + GET_PROC(EndQuery); + GET_PROC(GenQueries); + GET_PROC(GetQueryObjectuiv); + GET_PROC(GetQueryiv); +#endif + } else if (extensions.has("GL_EXT_occlusion_query_boolean")) { +#if GR_TEST_UTILS + GET_PROC_SUFFIX(BeginQuery, EXT); + GET_PROC_SUFFIX(DeleteQueries, EXT); + GET_PROC_SUFFIX(EndQuery, EXT); + GET_PROC_SUFFIX(GenQueries, EXT); + GET_PROC_SUFFIX(GetQueryObjectuiv, EXT); + GET_PROC_SUFFIX(GetQueryiv, EXT); +#endif + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(InvalidateFramebuffer); + GET_PROC(InvalidateSubFramebuffer); + } + + GET_PROC(GetShaderPrecisionFormat); + + + // End autogenerated content + // TODO(kjlubick): Do we want a feature that removes the extension if it doesn't have + // the function? This is common on some low-end GPUs. + + if (extensions.has("GL_KHR_debug")) { + // In general we have a policy against removing extension strings when the driver does + // not provide function pointers for an advertised extension. However, because there is a + // known device that advertises GL_KHR_debug but fails to provide the functions and this is + // a debugging- only extension we've made an exception. This also can happen when using + // APITRACE. + if (!interface->fFunctions.fDebugMessageControl) { + extensions.remove("GL_KHR_debug"); + } + } + interface->fStandard = kGLES_GrGLStandard; + interface->fExtensions.swap(&extensions); + + return interface; +} +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp b/gfx/skia/skia/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp new file mode 100644 index 0000000000..c4c8c6a9f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp @@ -0,0 +1,501 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * THIS FILE IS AUTOGENERATED + * Make edits to tools/gpu/gl/interface/templates.go or they will + * be overwritten. + */ + +#include "include/gpu/gl/GrGLAssembleHelpers.h" +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F) +#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S) +#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F) + +#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S) + +#if SK_DISABLE_GL_INTERFACE +sk_sp GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) { + return nullptr; +} +#else +sk_sp GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) { + GET_PROC_LOCAL(GetString); + GET_PROC_LOCAL(GetStringi); + GET_PROC_LOCAL(GetIntegerv); + + // GetStringi may be nullptr depending on the GL version. + if (nullptr == GetString || nullptr == GetIntegerv) { + return nullptr; + } + + const char* versionString = (const char*) GetString(GR_GL_VERSION); + GrGLVersion glVer = GrGLGetVersionFromString(versionString); + + if (glVer < GR_GL_VER(2,0) || GR_GL_INVALID_VER == glVer) { + // This is our minimum for non-ES GL. + return nullptr; + } + + GrEGLQueryStringFn* queryString; + GrEGLDisplay display; + GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get); + GrGLExtensions extensions; + if (!extensions.init(kGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString, + display)) { + return nullptr; + } + + sk_sp interface(new GrGLInterface()); + GrGLInterface::Functions* functions = &interface->fFunctions; + + // Autogenerated content follows + GET_PROC(ActiveTexture); + GET_PROC(AttachShader); + GET_PROC(BindAttribLocation); + GET_PROC(BindBuffer); + GET_PROC(BindTexture); + GET_PROC(BlendColor); + GET_PROC(BlendEquation); + GET_PROC(BlendFunc); + GET_PROC(BufferData); + GET_PROC(BufferSubData); + GET_PROC(Clear); + GET_PROC(ClearColor); + GET_PROC(ClearStencil); + GET_PROC(ColorMask); + GET_PROC(CompileShader); + GET_PROC(CompressedTexImage2D); + GET_PROC(CompressedTexSubImage2D); + GET_PROC(CopyTexSubImage2D); + GET_PROC(CreateProgram); + GET_PROC(CreateShader); + GET_PROC(CullFace); + GET_PROC(DeleteBuffers); + GET_PROC(DeleteProgram); + GET_PROC(DeleteShader); + GET_PROC(DeleteTextures); + GET_PROC(DepthMask); + GET_PROC(Disable); + GET_PROC(DisableVertexAttribArray); + GET_PROC(DrawArrays); + GET_PROC(DrawElements); + GET_PROC(Enable); + GET_PROC(EnableVertexAttribArray); + GET_PROC(Finish); + GET_PROC(Flush); + GET_PROC(FrontFace); + GET_PROC(GenBuffers); + GET_PROC(GenTextures); + GET_PROC(GetBufferParameteriv); + GET_PROC(GetError); + GET_PROC(GetIntegerv); + GET_PROC(GetProgramInfoLog); + GET_PROC(GetProgramiv); + GET_PROC(GetShaderInfoLog); + GET_PROC(GetShaderiv); + GET_PROC(GetString); + GET_PROC(GetUniformLocation); + GET_PROC(IsTexture); + GET_PROC(LineWidth); + GET_PROC(LinkProgram); + GET_PROC(PixelStorei); + GET_PROC(ReadPixels); + GET_PROC(Scissor); + GET_PROC(ShaderSource); + GET_PROC(StencilFunc); + GET_PROC(StencilFuncSeparate); + GET_PROC(StencilMask); + GET_PROC(StencilMaskSeparate); + GET_PROC(StencilOp); + GET_PROC(StencilOpSeparate); + GET_PROC(TexImage2D); + GET_PROC(TexParameterf); + GET_PROC(TexParameterfv); + GET_PROC(TexParameteri); + GET_PROC(TexParameteriv); + GET_PROC(TexSubImage2D); + GET_PROC(Uniform1f); + GET_PROC(Uniform1fv); + GET_PROC(Uniform1i); + GET_PROC(Uniform1iv); + GET_PROC(Uniform2f); + GET_PROC(Uniform2fv); + GET_PROC(Uniform2i); + GET_PROC(Uniform2iv); + GET_PROC(Uniform3f); + GET_PROC(Uniform3fv); + GET_PROC(Uniform3i); + GET_PROC(Uniform3iv); + GET_PROC(Uniform4f); + GET_PROC(Uniform4fv); + GET_PROC(Uniform4i); + GET_PROC(Uniform4iv); + GET_PROC(UniformMatrix2fv); + GET_PROC(UniformMatrix3fv); + GET_PROC(UniformMatrix4fv); + GET_PROC(UseProgram); + GET_PROC(VertexAttrib1f); + GET_PROC(VertexAttrib2fv); + GET_PROC(VertexAttrib3fv); + GET_PROC(VertexAttrib4fv); + GET_PROC(VertexAttribPointer); + GET_PROC(Viewport); + + GET_PROC(DrawBuffer); + GET_PROC(PolygonMode); + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(GetStringi); + } + + GET_PROC(BindVertexArray); + GET_PROC(DeleteVertexArrays); + GET_PROC(GenVertexArrays); + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(BindFragDataLocation); + } + + if (glVer >= GR_GL_VER(3,3)) { + GET_PROC(BindFragDataLocationIndexed); + } else if (extensions.has("GL_ARB_blend_func_extended")) { + GET_PROC(BindFragDataLocationIndexed); + } + + if (extensions.has("GL_KHR_blend_equation_advanced")) { + GET_PROC_SUFFIX(BlendBarrier, KHR); + } else if (extensions.has("GL_NV_blend_equation_advanced")) { + GET_PROC_SUFFIX(BlendBarrier, NV); + } + + if (glVer >= GR_GL_VER(4,4)) { + GET_PROC(ClearTexImage); + GET_PROC(ClearTexSubImage); + } else if (extensions.has("GL_ARB_clear_texture")) { + GET_PROC(ClearTexImage); + GET_PROC(ClearTexSubImage); + } + + if (glVer >= GR_GL_VER(3,1)) { + GET_PROC(DrawArraysInstanced); + GET_PROC(DrawElementsInstanced); + } else if (extensions.has("GL_ARB_draw_instanced")) { + GET_PROC(DrawArraysInstanced); + GET_PROC(DrawElementsInstanced); + } else if (extensions.has("GL_EXT_draw_instanced")) { + GET_PROC_SUFFIX(DrawArraysInstanced, EXT); + GET_PROC_SUFFIX(DrawElementsInstanced, EXT); + } + + GET_PROC(DrawBuffers); + GET_PROC(ReadBuffer); + + if (glVer >= GR_GL_VER(4,0)) { + GET_PROC(DrawArraysIndirect); + GET_PROC(DrawElementsIndirect); + } else if (extensions.has("GL_ARB_draw_indirect")) { + GET_PROC(DrawArraysIndirect); + GET_PROC(DrawElementsIndirect); + } + + GET_PROC(DrawRangeElements); + + if (glVer >= GR_GL_VER(3,2)) { + GET_PROC(GetMultisamplefv); + } else if (extensions.has("GL_ARB_texture_multisample")) { + GET_PROC(GetMultisamplefv); + } + + GET_PROC(GetTexLevelParameteriv); + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(MultiDrawArraysIndirect); + GET_PROC(MultiDrawElementsIndirect); + } else if (extensions.has("GL_ARB_multi_draw_indirect")) { + GET_PROC(MultiDrawArraysIndirect); + GET_PROC(MultiDrawElementsIndirect); + } + + if (glVer >= GR_GL_VER(3,1)) { + GET_PROC(TexBuffer); + } + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(TexBufferRange); + } + + if (glVer >= GR_GL_VER(4,2)) { + GET_PROC(TexStorage2D); + } else if (extensions.has("GL_ARB_texture_storage")) { + GET_PROC(TexStorage2D); + } else if (extensions.has("GL_EXT_texture_storage")) { + GET_PROC_SUFFIX(TexStorage2D, EXT); + } + + if (glVer >= GR_GL_VER(4,5)) { + GET_PROC(TextureBarrier); + } else if (extensions.has("GL_ARB_texture_barrier")) { + GET_PROC(TextureBarrier); + } else if (extensions.has("GL_NV_texture_barrier")) { + GET_PROC_SUFFIX(TextureBarrier, NV); + } + + if (glVer >= GR_GL_VER(3,2)) { + GET_PROC(VertexAttribDivisor); + } else if (extensions.has("GL_ARB_instanced_arrays")) { + GET_PROC(VertexAttribDivisor); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(VertexAttribIPointer); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(BindFramebuffer); + GET_PROC(BindRenderbuffer); + GET_PROC(CheckFramebufferStatus); + GET_PROC(DeleteFramebuffers); + GET_PROC(DeleteRenderbuffers); + GET_PROC(FramebufferRenderbuffer); + GET_PROC(FramebufferTexture2D); + GET_PROC(GenFramebuffers); + GET_PROC(GenRenderbuffers); + GET_PROC(GenerateMipmap); + GET_PROC(GetFramebufferAttachmentParameteriv); + GET_PROC(GetRenderbufferParameteriv); + GET_PROC(RenderbufferStorage); + } else if (extensions.has("GL_ARB_framebuffer_object")) { + GET_PROC(BindFramebuffer); + GET_PROC(BindRenderbuffer); + GET_PROC(CheckFramebufferStatus); + GET_PROC(DeleteFramebuffers); + GET_PROC(DeleteRenderbuffers); + GET_PROC(FramebufferRenderbuffer); + GET_PROC(FramebufferTexture2D); + GET_PROC(GenFramebuffers); + GET_PROC(GenRenderbuffers); + GET_PROC(GenerateMipmap); + GET_PROC(GetFramebufferAttachmentParameteriv); + GET_PROC(GetRenderbufferParameteriv); + GET_PROC(RenderbufferStorage); + } else if (extensions.has("GL_EXT_framebuffer_object")) { + GET_PROC_SUFFIX(BindFramebuffer, EXT); + GET_PROC_SUFFIX(BindRenderbuffer, EXT); + GET_PROC_SUFFIX(CheckFramebufferStatus, EXT); + GET_PROC_SUFFIX(DeleteFramebuffers, EXT); + GET_PROC_SUFFIX(DeleteRenderbuffers, EXT); + GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT); + GET_PROC_SUFFIX(FramebufferTexture2D, EXT); + GET_PROC_SUFFIX(GenFramebuffers, EXT); + GET_PROC_SUFFIX(GenRenderbuffers, EXT); + GET_PROC_SUFFIX(GenerateMipmap, EXT); + GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT); + GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT); + GET_PROC_SUFFIX(RenderbufferStorage, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(BlitFramebuffer); + } else if (extensions.has("GL_ARB_framebuffer_object")) { + GET_PROC(BlitFramebuffer); + } else if (extensions.has("GL_EXT_framebuffer_blit")) { + GET_PROC_SUFFIX(BlitFramebuffer, EXT); + } + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(RenderbufferStorageMultisample); + } else if (extensions.has("GL_ARB_framebuffer_object")) { + GET_PROC(RenderbufferStorageMultisample); + } else if (extensions.has("GL_EXT_framebuffer_multisample")) { + GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT); + } + + GET_PROC(MapBuffer); + + GET_PROC(UnmapBuffer); + + if (glVer >= GR_GL_VER(3,0)) { + GET_PROC(FlushMappedBufferRange); + GET_PROC(MapBufferRange); + } else if (extensions.has("GL_ARB_map_buffer_range")) { + GET_PROC(FlushMappedBufferRange); + GET_PROC(MapBufferRange); + } + + if (extensions.has("GL_EXT_debug_marker")) { + GET_PROC_SUFFIX(InsertEventMarker, EXT); + GET_PROC_SUFFIX(PopGroupMarker, EXT); + GET_PROC_SUFFIX(PushGroupMarker, EXT); + } + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(GetProgramResourceLocation); + } else if (extensions.has("GL_ARB_program_interface_query")) { + GET_PROC(GetProgramResourceLocation); + } + + if (extensions.has("GL_NV_path_rendering")) { + GET_PROC_SUFFIX(MatrixLoadIdentity, EXT); + GET_PROC_SUFFIX(MatrixLoadf, EXT); + } + + if (extensions.has("GL_NV_path_rendering")) { + GET_PROC_SUFFIX(CoverFillPath, NV); + GET_PROC_SUFFIX(CoverFillPathInstanced, NV); + GET_PROC_SUFFIX(CoverStrokePath, NV); + GET_PROC_SUFFIX(CoverStrokePathInstanced, NV); + GET_PROC_SUFFIX(DeletePaths, NV); + GET_PROC_SUFFIX(GenPaths, NV); + GET_PROC_SUFFIX(IsPath, NV); + GET_PROC_SUFFIX(PathCommands, NV); + GET_PROC_SUFFIX(PathParameterf, NV); + GET_PROC_SUFFIX(PathParameteri, NV); + GET_PROC_SUFFIX(PathStencilFunc, NV); + GET_PROC_SUFFIX(ProgramPathFragmentInputGen, NV); + GET_PROC_SUFFIX(StencilFillPath, NV); + GET_PROC_SUFFIX(StencilFillPathInstanced, NV); + GET_PROC_SUFFIX(StencilStrokePath, NV); + GET_PROC_SUFFIX(StencilStrokePathInstanced, NV); + GET_PROC_SUFFIX(StencilThenCoverFillPath, NV); + GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, NV); + GET_PROC_SUFFIX(StencilThenCoverStrokePath, NV); + GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, NV); + } + + if (extensions.has("GL_NV_framebuffer_mixed_samples")) { + GET_PROC_SUFFIX(CoverageModulation, NV); + } + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(DebugMessageCallback); + GET_PROC(DebugMessageControl); + GET_PROC(DebugMessageInsert); + GET_PROC(GetDebugMessageLog); + GET_PROC(ObjectLabel); + GET_PROC(PopDebugGroup); + GET_PROC(PushDebugGroup); + } else if (extensions.has("GL_KHR_debug")) { + GET_PROC(DebugMessageCallback); + GET_PROC(DebugMessageControl); + GET_PROC(DebugMessageInsert); + GET_PROC(GetDebugMessageLog); + GET_PROC(ObjectLabel); + GET_PROC(PopDebugGroup); + GET_PROC(PushDebugGroup); + } + + if (extensions.has("GL_EXT_window_rectangles")) { + GET_PROC_SUFFIX(WindowRectangles, EXT); + } + + if (glVer >= GR_GL_VER(3,2)) { + GET_PROC(ClientWaitSync); + GET_PROC(DeleteSync); + GET_PROC(FenceSync); + GET_PROC(IsSync); + GET_PROC(WaitSync); + } else if (extensions.has("GL_ARB_sync")) { + GET_PROC(ClientWaitSync); + GET_PROC(DeleteSync); + GET_PROC(FenceSync); + GET_PROC(IsSync); + GET_PROC(WaitSync); + } + + if (glVer >= GR_GL_VER(4,2)) { + GET_PROC(GetInternalformativ); + } else if (extensions.has("GL_ARB_internalformat_query")) { + GET_PROC(GetInternalformativ); + } + + if (glVer >= GR_GL_VER(4,1)) { + GET_PROC(GetProgramBinary); + GET_PROC(ProgramBinary); + } + + if (glVer >= GR_GL_VER(4,1)) { + GET_PROC(ProgramParameteri); + } + + if (glVer >= GR_GL_VER(3,2)) { + GET_PROC(BindSampler); + GET_PROC(DeleteSamplers); + GET_PROC(GenSamplers); + GET_PROC(SamplerParameteri); + GET_PROC(SamplerParameteriv); + } else if (extensions.has("GL_ARB_sampler_objects")) { + GET_PROC(BindSampler); + GET_PROC(DeleteSamplers); + GET_PROC(GenSamplers); + GET_PROC(SamplerParameteri); + GET_PROC(SamplerParameteriv); + } + + GET_PROC(GetQueryObjectiv); + +#if GR_TEST_UTILS + GET_PROC(BeginQuery); + GET_PROC(DeleteQueries); + GET_PROC(EndQuery); + GET_PROC(GenQueries); + GET_PROC(GetQueryObjectuiv); + GET_PROC(GetQueryiv); +#endif + + if (glVer >= GR_GL_VER(3,3)) { + GET_PROC(GetQueryObjecti64v); + GET_PROC(GetQueryObjectui64v); + } else if (extensions.has("GL_ARB_timer_query")) { + GET_PROC(GetQueryObjecti64v); + GET_PROC(GetQueryObjectui64v); + } else if (extensions.has("GL_EXT_timer_query")) { + GET_PROC_SUFFIX(GetQueryObjecti64v, EXT); + GET_PROC_SUFFIX(GetQueryObjectui64v, EXT); + } + + if (glVer >= GR_GL_VER(3,3)) { + GET_PROC(QueryCounter); + } else if (extensions.has("GL_ARB_timer_query")) { + GET_PROC(QueryCounter); + } + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(InvalidateBufferData); + GET_PROC(InvalidateBufferSubData); + GET_PROC(InvalidateTexImage); + GET_PROC(InvalidateTexSubImage); + } else if (extensions.has("GL_ARB_invalidate_subdata")) { + GET_PROC(InvalidateBufferData); + GET_PROC(InvalidateBufferSubData); + GET_PROC(InvalidateTexImage); + GET_PROC(InvalidateTexSubImage); + } + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(InvalidateFramebuffer); + GET_PROC(InvalidateSubFramebuffer); + } else if (extensions.has("GL_ARB_invalidate_subdata")) { + GET_PROC(InvalidateFramebuffer); + GET_PROC(InvalidateSubFramebuffer); + } + + if (glVer >= GR_GL_VER(4,3)) { + GET_PROC(GetShaderPrecisionFormat); + } else if (extensions.has("GL_ARB_ES2_compatibility")) { + GET_PROC(GetShaderPrecisionFormat); + } + + + // End autogenerated content + interface->fStandard = kGL_GrGLStandard; + interface->fExtensions.swap(&extensions); + + return interface; +} +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLAssembleHelpers.cpp b/gfx/skia/skia/src/gpu/gl/GrGLAssembleHelpers.cpp new file mode 100644 index 0000000000..5e5163f26a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLAssembleHelpers.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/gl/GrGLAssembleHelpers.h" +#include "src/gpu/gl/GrGLUtil.h" + +void GrGetEGLQueryAndDisplay(GrEGLQueryStringFn** queryString, GrEGLDisplay* display, + void* ctx, GrGLGetProc get) { + *queryString = (GrEGLQueryStringFn*)get(ctx, "eglQueryString"); + *display = GR_EGL_NO_DISPLAY; + if (*queryString) { + GrEGLGetCurrentDisplayFn* getCurrentDisplay = + (GrEGLGetCurrentDisplayFn*)get(ctx, "eglGetCurrentDisplay"); + if (getCurrentDisplay) { + *display = getCurrentDisplay(); + } else { + *queryString = nullptr; + } + } +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLAssembleInterface.cpp b/gfx/skia/skia/src/gpu/gl/GrGLAssembleInterface.cpp new file mode 100644 index 0000000000..2a0f774f0e --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLAssembleInterface.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/gpu/gl/GrGLAssembleHelpers.h" +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F) + +sk_sp GrGLMakeAssembledInterface(void *ctx, GrGLGetProc get) { + GET_PROC_LOCAL(GetString); + if (nullptr == GetString) { + return nullptr; + } + + const char* verStr = reinterpret_cast(GetString(GR_GL_VERSION)); + if (nullptr == verStr) { + return nullptr; + } + + GrGLStandard standard = GrGLGetStandardInUseFromString(verStr); + // standard can be unused (optimzed away) if SK_ASSUME_GL_ES is set + sk_ignore_unused_variable(standard); + + if (GR_IS_GR_GL_ES(standard)) { + return GrGLMakeAssembledGLESInterface(ctx, get); + } else if (GR_IS_GR_GL(standard)) { + return GrGLMakeAssembledGLInterface(ctx, get); + } else if (GR_IS_GR_WEBGL(standard)) { + return GrGLMakeAssembledWebGLInterface(ctx, get); + } + return nullptr; +} + +const GrGLInterface* GrGLAssembleInterface(void *ctx, GrGLGetProc get) { + return GrGLMakeAssembledInterface(ctx, get).release(); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp b/gfx/skia/skia/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp new file mode 100644 index 0000000000..9202de8c64 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp @@ -0,0 +1,240 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * THIS FILE IS AUTOGENERATED + * Make edits to tools/gpu/gl/interface/templates.go or they will + * be overwritten. + */ + +#include "include/gpu/gl/GrGLAssembleHelpers.h" +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F) +#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S) +#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F) + +#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S) + +#if SK_DISABLE_WEBGL_INTERFACE +sk_sp GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get) { + return nullptr; +} +#else +sk_sp GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get) { + GET_PROC_LOCAL(GetString); + if (nullptr == GetString) { + return nullptr; + } + + const char* verStr = reinterpret_cast(GetString(GR_GL_VERSION)); + GrGLVersion glVer = GrGLGetVersionFromString(verStr); + + if (glVer < GR_GL_VER(1,0)) { + return nullptr; + } + + GET_PROC_LOCAL(GetIntegerv); + GET_PROC_LOCAL(GetStringi); + GrEGLQueryStringFn* queryString; + GrEGLDisplay display; + GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get); + GrGLExtensions extensions; + if (!extensions.init(kWebGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString, + display)) { + return nullptr; + } + + sk_sp interface(new GrGLInterface); + GrGLInterface::Functions* functions = &interface->fFunctions; + + // Autogenerated content follows + GET_PROC(ActiveTexture); + GET_PROC(AttachShader); + GET_PROC(BindAttribLocation); + GET_PROC(BindBuffer); + GET_PROC(BindTexture); + GET_PROC(BlendColor); + GET_PROC(BlendEquation); + GET_PROC(BlendFunc); + GET_PROC(BufferData); + GET_PROC(BufferSubData); + GET_PROC(Clear); + GET_PROC(ClearColor); + GET_PROC(ClearStencil); + GET_PROC(ColorMask); + GET_PROC(CompileShader); + GET_PROC(CompressedTexImage2D); + GET_PROC(CompressedTexSubImage2D); + GET_PROC(CopyTexSubImage2D); + GET_PROC(CreateProgram); + GET_PROC(CreateShader); + GET_PROC(CullFace); + GET_PROC(DeleteBuffers); + GET_PROC(DeleteProgram); + GET_PROC(DeleteShader); + GET_PROC(DeleteTextures); + GET_PROC(DepthMask); + GET_PROC(Disable); + GET_PROC(DisableVertexAttribArray); + GET_PROC(DrawArrays); + GET_PROC(DrawElements); + GET_PROC(Enable); + GET_PROC(EnableVertexAttribArray); + GET_PROC(Finish); + GET_PROC(Flush); + GET_PROC(FrontFace); + GET_PROC(GenBuffers); + GET_PROC(GenTextures); + GET_PROC(GetBufferParameteriv); + GET_PROC(GetError); + GET_PROC(GetIntegerv); + GET_PROC(GetProgramInfoLog); + GET_PROC(GetProgramiv); + GET_PROC(GetShaderInfoLog); + GET_PROC(GetShaderiv); + GET_PROC(GetString); + GET_PROC(GetUniformLocation); + GET_PROC(IsTexture); + GET_PROC(LineWidth); + GET_PROC(LinkProgram); + GET_PROC(PixelStorei); + GET_PROC(ReadPixels); + GET_PROC(Scissor); + GET_PROC(ShaderSource); + GET_PROC(StencilFunc); + GET_PROC(StencilFuncSeparate); + GET_PROC(StencilMask); + GET_PROC(StencilMaskSeparate); + GET_PROC(StencilOp); + GET_PROC(StencilOpSeparate); + GET_PROC(TexImage2D); + GET_PROC(TexParameterf); + GET_PROC(TexParameterfv); + GET_PROC(TexParameteri); + GET_PROC(TexParameteriv); + GET_PROC(TexSubImage2D); + GET_PROC(Uniform1f); + GET_PROC(Uniform1fv); + GET_PROC(Uniform1i); + GET_PROC(Uniform1iv); + GET_PROC(Uniform2f); + GET_PROC(Uniform2fv); + GET_PROC(Uniform2i); + GET_PROC(Uniform2iv); + GET_PROC(Uniform3f); + GET_PROC(Uniform3fv); + GET_PROC(Uniform3i); + GET_PROC(Uniform3iv); + GET_PROC(Uniform4f); + GET_PROC(Uniform4fv); + GET_PROC(Uniform4i); + GET_PROC(Uniform4iv); + GET_PROC(UniformMatrix2fv); + GET_PROC(UniformMatrix3fv); + GET_PROC(UniformMatrix4fv); + GET_PROC(UseProgram); + GET_PROC(VertexAttrib1f); + GET_PROC(VertexAttrib2fv); + GET_PROC(VertexAttrib3fv); + GET_PROC(VertexAttrib4fv); + GET_PROC(VertexAttribPointer); + GET_PROC(Viewport); + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(GetStringi); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(BindVertexArray); + GET_PROC(DeleteVertexArrays); + GET_PROC(GenVertexArrays); + } else if (extensions.has("GL_OES_vertex_array_object")) { + GET_PROC_SUFFIX(BindVertexArray, OES); + GET_PROC_SUFFIX(DeleteVertexArrays, OES); + GET_PROC_SUFFIX(GenVertexArrays, OES); + } else if (extensions.has("OES_vertex_array_object")) { + GET_PROC_SUFFIX(BindVertexArray, OES); + GET_PROC_SUFFIX(DeleteVertexArrays, OES); + GET_PROC_SUFFIX(GenVertexArrays, OES); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(DrawArraysInstanced); + GET_PROC(DrawElementsInstanced); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(DrawBuffers); + GET_PROC(ReadBuffer); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(DrawRangeElements); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(TexStorage2D); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(VertexAttribDivisor); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(VertexAttribIPointer); + } + + GET_PROC(BindFramebuffer); + GET_PROC(BindRenderbuffer); + GET_PROC(CheckFramebufferStatus); + GET_PROC(DeleteFramebuffers); + GET_PROC(DeleteRenderbuffers); + GET_PROC(FramebufferRenderbuffer); + GET_PROC(FramebufferTexture2D); + GET_PROC(GenFramebuffers); + GET_PROC(GenRenderbuffers); + GET_PROC(GenerateMipmap); + GET_PROC(GetFramebufferAttachmentParameteriv); + GET_PROC(GetRenderbufferParameteriv); + GET_PROC(RenderbufferStorage); + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(RenderbufferStorageMultisample); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(ClientWaitSync); + GET_PROC(DeleteSync); + GET_PROC(FenceSync); + GET_PROC(IsSync); + GET_PROC(WaitSync); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(BindSampler); + GET_PROC(DeleteSamplers); + GET_PROC(GenSamplers); + GET_PROC(SamplerParameteri); + GET_PROC(SamplerParameteriv); + } + + if (glVer >= GR_GL_VER(2,0)) { + GET_PROC(InvalidateFramebuffer); + GET_PROC(InvalidateSubFramebuffer); + } + + GET_PROC(GetShaderPrecisionFormat); + + + // End autogenerated content + + interface->fStandard = kWebGL_GrGLStandard; + interface->fExtensions.swap(&extensions); + + return interface; +} +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLBuffer.cpp b/gfx/skia/skia/src/gpu/gl/GrGLBuffer.cpp new file mode 100644 index 0000000000..516c10ea34 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLBuffer.cpp @@ -0,0 +1,300 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTraceMemoryDump.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/gl/GrGLBuffer.h" +#include "src/gpu/gl/GrGLGpu.h" + +#define GL_CALL(X) GR_GL_CALL(this->glGpu()->glInterface(), X) +#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->glGpu()->glInterface(), RET, X) + +#if GR_GL_CHECK_ALLOC_WITH_GET_ERROR + #define CLEAR_ERROR_BEFORE_ALLOC(iface) GrGLClearErr(iface) + #define GL_ALLOC_CALL(iface, call) GR_GL_CALL_NOERRCHECK(iface, call) + #define CHECK_ALLOC_ERROR(iface) GR_GL_GET_ERROR(iface) +#else + #define CLEAR_ERROR_BEFORE_ALLOC(iface) + #define GL_ALLOC_CALL(iface, call) GR_GL_CALL(iface, call) + #define CHECK_ALLOC_ERROR(iface) GR_GL_NO_ERROR +#endif + +#ifdef SK_DEBUG +#define VALIDATE() this->validate() +#else +#define VALIDATE() do {} while(false) +#endif + +sk_sp GrGLBuffer::Make(GrGLGpu* gpu, size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, const void* data) { + if (gpu->glCaps().transferBufferType() == GrGLCaps::kNone_TransferBufferType && + (GrGpuBufferType::kXferCpuToGpu == intendedType || + GrGpuBufferType::kXferGpuToCpu == intendedType)) { + return nullptr; + } + + sk_sp buffer(new GrGLBuffer(gpu, size, intendedType, accessPattern, data)); + if (0 == buffer->bufferID()) { + return nullptr; + } + return buffer; +} + +// GL_STREAM_DRAW triggers an optimization in Chromium's GPU process where a client's vertex buffer +// objects are implemented as client-side-arrays on tile-deferred architectures. +#define DYNAMIC_DRAW_PARAM GR_GL_STREAM_DRAW + +inline static GrGLenum gr_to_gl_access_pattern(GrGpuBufferType bufferType, + GrAccessPattern accessPattern) { + auto drawUsage = [](GrAccessPattern pattern) { + switch (pattern) { + case kDynamic_GrAccessPattern: + // TODO: Do we really want to use STREAM_DRAW here on non-Chromium? + return DYNAMIC_DRAW_PARAM; + case kStatic_GrAccessPattern: + return GR_GL_STATIC_DRAW; + case kStream_GrAccessPattern: + return GR_GL_STREAM_DRAW; + } + SK_ABORT("Unexpected access pattern"); + }; + + auto readUsage = [](GrAccessPattern pattern) { + switch (pattern) { + case kDynamic_GrAccessPattern: + return GR_GL_DYNAMIC_READ; + case kStatic_GrAccessPattern: + return GR_GL_STATIC_READ; + case kStream_GrAccessPattern: + return GR_GL_STREAM_READ; + } + SK_ABORT("Unexpected access pattern"); + }; + + auto usageType = [&drawUsage, &readUsage](GrGpuBufferType type, GrAccessPattern pattern) { + switch (type) { + case GrGpuBufferType::kVertex: + case GrGpuBufferType::kIndex: + case GrGpuBufferType::kXferCpuToGpu: + return drawUsage(pattern); + case GrGpuBufferType::kXferGpuToCpu: + return readUsage(pattern); + } + SK_ABORT("Unexpected gpu buffer type."); + }; + + return usageType(bufferType, accessPattern); +} + +GrGLBuffer::GrGLBuffer(GrGLGpu* gpu, size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, const void* data) + : INHERITED(gpu, size, intendedType, accessPattern) + , fIntendedType(intendedType) + , fBufferID(0) + , fUsage(gr_to_gl_access_pattern(intendedType, accessPattern)) + , fGLSizeInBytes(0) + , fHasAttachedToTexture(false) { + GL_CALL(GenBuffers(1, &fBufferID)); + if (fBufferID) { + GrGLenum target = gpu->bindBuffer(fIntendedType, this); + CLEAR_ERROR_BEFORE_ALLOC(gpu->glInterface()); + // make sure driver can allocate memory for this buffer + GL_ALLOC_CALL(gpu->glInterface(), BufferData(target, + (GrGLsizeiptr) size, + data, + fUsage)); + if (CHECK_ALLOC_ERROR(gpu->glInterface()) != GR_GL_NO_ERROR) { + GL_CALL(DeleteBuffers(1, &fBufferID)); + fBufferID = 0; + } else { + fGLSizeInBytes = size; + } + } + VALIDATE(); + this->registerWithCache(SkBudgeted::kYes); + if (!fBufferID) { + this->resourcePriv().removeScratchKey(); + } +} + +inline GrGLGpu* GrGLBuffer::glGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +inline const GrGLCaps& GrGLBuffer::glCaps() const { + return this->glGpu()->glCaps(); +} + +void GrGLBuffer::onRelease() { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + if (!this->wasDestroyed()) { + VALIDATE(); + // make sure we've not been abandoned or already released + if (fBufferID) { + GL_CALL(DeleteBuffers(1, &fBufferID)); + fBufferID = 0; + fGLSizeInBytes = 0; + } + fMapPtr = nullptr; + VALIDATE(); + } + + INHERITED::onRelease(); +} + +void GrGLBuffer::onAbandon() { + fBufferID = 0; + fGLSizeInBytes = 0; + fMapPtr = nullptr; + VALIDATE(); + INHERITED::onAbandon(); +} + +void GrGLBuffer::onMap() { + SkASSERT(fBufferID); + SkASSERT(!this->wasDestroyed()); + VALIDATE(); + SkASSERT(!this->isMapped()); + + // TODO: Make this a function parameter. + bool readOnly = (GrGpuBufferType::kXferGpuToCpu == fIntendedType); + + // Handling dirty context is done in the bindBuffer call + switch (this->glCaps().mapBufferType()) { + case GrGLCaps::kNone_MapBufferType: + return; + case GrGLCaps::kMapBuffer_MapBufferType: { + GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); + if (!readOnly) { + // Let driver know it can discard the old data + if (this->glCaps().useBufferDataNullHint() || fGLSizeInBytes != this->size()) { + GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); + } + } + GL_CALL_RET(fMapPtr, MapBuffer(target, readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY)); + break; + } + case GrGLCaps::kMapBufferRange_MapBufferType: { + GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); + // Make sure the GL buffer size agrees with fDesc before mapping. + if (fGLSizeInBytes != this->size()) { + GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); + } + GrGLbitfield access; + if (readOnly) { + access = GR_GL_MAP_READ_BIT; + } else { + access = GR_GL_MAP_WRITE_BIT; + if (GrGpuBufferType::kXferCpuToGpu != fIntendedType) { + // TODO: Make this a function parameter. + access |= GR_GL_MAP_INVALIDATE_BUFFER_BIT; + } + } + GL_CALL_RET(fMapPtr, MapBufferRange(target, 0, this->size(), access)); + break; + } + case GrGLCaps::kChromium_MapBufferType: { + GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); + // Make sure the GL buffer size agrees with fDesc before mapping. + if (fGLSizeInBytes != this->size()) { + GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); + } + GL_CALL_RET(fMapPtr, MapBufferSubData(target, 0, this->size(), + readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY)); + break; + } + } + fGLSizeInBytes = this->size(); + VALIDATE(); +} + +void GrGLBuffer::onUnmap() { + SkASSERT(fBufferID); + VALIDATE(); + SkASSERT(this->isMapped()); + if (0 == fBufferID) { + fMapPtr = nullptr; + return; + } + // bind buffer handles the dirty context + switch (this->glCaps().mapBufferType()) { + case GrGLCaps::kNone_MapBufferType: + SkDEBUGFAIL("Shouldn't get here."); + return; + case GrGLCaps::kMapBuffer_MapBufferType: // fall through + case GrGLCaps::kMapBufferRange_MapBufferType: { + GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); + GL_CALL(UnmapBuffer(target)); + break; + } + case GrGLCaps::kChromium_MapBufferType: + this->glGpu()->bindBuffer(fIntendedType, this); // TODO: Is this needed? + GL_CALL(UnmapBufferSubData(fMapPtr)); + break; + } + fMapPtr = nullptr; +} + +bool GrGLBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) { + SkASSERT(fBufferID); + if (this->wasDestroyed()) { + return false; + } + + SkASSERT(!this->isMapped()); + VALIDATE(); + if (srcSizeInBytes > this->size()) { + return false; + } + SkASSERT(srcSizeInBytes <= this->size()); + // bindbuffer handles dirty context + GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); + + if (this->glCaps().useBufferDataNullHint()) { + if (this->size() == srcSizeInBytes) { + GL_CALL(BufferData(target, (GrGLsizeiptr) srcSizeInBytes, src, fUsage)); + } else { + // Before we call glBufferSubData we give the driver a hint using + // glBufferData with nullptr. This makes the old buffer contents + // inaccessible to future draws. The GPU may still be processing + // draws that reference the old contents. With this hint it can + // assign a different allocation for the new contents to avoid + // flushing the gpu past draws consuming the old contents. + // TODO I think we actually want to try calling bufferData here + GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); + GL_CALL(BufferSubData(target, 0, (GrGLsizeiptr) srcSizeInBytes, src)); + } + fGLSizeInBytes = this->size(); + } else { + // Note that we're cheating on the size here. Currently no methods + // allow a partial update that preserves contents of non-updated + // portions of the buffer (map() does a glBufferData(..size, nullptr..)) + GL_CALL(BufferData(target, srcSizeInBytes, src, fUsage)); + fGLSizeInBytes = srcSizeInBytes; + } + VALIDATE(); + return true; +} + +void GrGLBuffer::setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, + const SkString& dumpName) const { + SkString buffer_id; + buffer_id.appendU32(this->bufferID()); + traceMemoryDump->setMemoryBacking(dumpName.c_str(), "gl_buffer", + buffer_id.c_str()); +} + +#ifdef SK_DEBUG + +void GrGLBuffer::validate() const { + SkASSERT(0 != fBufferID || 0 == fGLSizeInBytes); + SkASSERT(nullptr == fMapPtr || fGLSizeInBytes <= this->size()); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLBuffer.h b/gfx/skia/skia/src/gpu/gl/GrGLBuffer.h new file mode 100644 index 0000000000..d408e468cd --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLBuffer.h @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLBuffer_DEFINED +#define GrGLBuffer_DEFINED + +#include "include/gpu/gl/GrGLTypes.h" +#include "src/gpu/GrGpuBuffer.h" + +class GrGLGpu; +class GrGLCaps; + +class GrGLBuffer : public GrGpuBuffer { +public: + static sk_sp Make(GrGLGpu*, size_t size, GrGpuBufferType intendedType, + GrAccessPattern, const void* data = nullptr); + + ~GrGLBuffer() override { + // either release or abandon should have been called by the owner of this object. + SkASSERT(0 == fBufferID); + } + + GrGLuint bufferID() const { return fBufferID; } + + /** + * Returns the actual size of the underlying GL buffer object. In certain cases we may make this + * smaller than the size reported by GrGpuBuffer. + */ + size_t glSizeInBytes() const { return fGLSizeInBytes; } + + void setHasAttachedToTexture() { fHasAttachedToTexture = true; } + bool hasAttachedToTexture() const { return fHasAttachedToTexture; } + +protected: + GrGLBuffer(GrGLGpu*, size_t size, GrGpuBufferType intendedType, GrAccessPattern, + const void* data); + + void onAbandon() override; + void onRelease() override; + void setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, + const SkString& dumpName) const override; + +private: + GrGLGpu* glGpu() const; + const GrGLCaps& glCaps() const; + + void onMap() override; + void onUnmap() override; + bool onUpdateData(const void* src, size_t srcSizeInBytes) override; + +#ifdef SK_DEBUG + void validate() const; +#endif + + GrGpuBufferType fIntendedType; + GrGLuint fBufferID; + GrGLenum fUsage; + size_t fGLSizeInBytes; + bool fHasAttachedToTexture; + + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLCaps.cpp b/gfx/skia/skia/src/gpu/gl/GrGLCaps.cpp new file mode 100644 index 0000000000..3142399c58 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLCaps.cpp @@ -0,0 +1,4246 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrContextOptions.h" +#include "src/core/SkTSearch.h" +#include "src/core/SkTSort.h" +#include "src/gpu/GrRenderTargetProxyPriv.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTextureProxyPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/gl/GrGLCaps.h" +#include "src/gpu/gl/GrGLContext.h" +#include "src/gpu/gl/GrGLRenderTarget.h" +#include "src/gpu/gl/GrGLTexture.h" +#include "src/utils/SkJSONWriter.h" + +GrGLCaps::GrGLCaps(const GrContextOptions& contextOptions, + const GrGLContextInfo& ctxInfo, + const GrGLInterface* glInterface) : INHERITED(contextOptions) { + fStandard = ctxInfo.standard(); + + fStencilFormats.reset(); + fMSFBOType = kNone_MSFBOType; + fInvalidateFBType = kNone_InvalidateFBType; + fMapBufferType = kNone_MapBufferType; + fTransferBufferType = kNone_TransferBufferType; + fMaxFragmentUniformVectors = 0; + fPackFlipYSupport = false; + fTextureUsageSupport = false; + fImagingSupport = false; + fVertexArrayObjectSupport = false; + fDebugSupport = false; + fES2CompatibilitySupport = false; + fDrawIndirectSupport = false; + fMultiDrawIndirectSupport = false; + fBaseInstanceSupport = false; + fIsCoreProfile = false; + fBindFragDataLocationSupport = false; + fRectangleTextureSupport = false; + fRGBA8888PixelsOpsAreSlow = false; + fPartialFBOReadIsSlow = false; + fMipMapLevelAndLodControlSupport = false; + fRGBAToBGRAReadbackConversionsAreSlow = false; + fUseBufferDataNullHint = false; + fDoManualMipmapping = false; + fClearToBoundaryValuesIsBroken = false; + fClearTextureSupport = false; + fDrawArraysBaseVertexIsBroken = false; + fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO = false; + fUseDrawInsteadOfAllRenderTargetWrites = false; + fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines = false; + fDetachStencilFromMSAABuffersBeforeReadPixels = false; + fDontSetBaseOrMaxLevelForExternalTextures = false; + fNeverDisableColorWrites = false; + fProgramBinarySupport = false; + fProgramParameterSupport = false; + fSamplerObjectSupport = false; + fTiledRenderingSupport = false; + fFBFetchRequiresEnablePerSample = false; + fSRGBWriteControl = false; + + fBlitFramebufferFlags = kNoSupport_BlitFramebufferFlag; + fMaxInstancesPerDrawWithoutCrashing = 0; + + fShaderCaps.reset(new GrShaderCaps(contextOptions)); + + this->init(contextOptions, ctxInfo, glInterface); +} + +void GrGLCaps::init(const GrContextOptions& contextOptions, + const GrGLContextInfo& ctxInfo, + const GrGLInterface* gli) { + GrGLStandard standard = ctxInfo.standard(); + // standard can be unused (optimzed away) if SK_ASSUME_GL_ES is set + sk_ignore_unused_variable(standard); + GrGLVersion version = ctxInfo.version(); + + if (GR_IS_GR_GL(standard)) { + GrGLint max; + GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &max); + fMaxFragmentUniformVectors = max / 4; + if (version >= GR_GL_VER(3, 2)) { + GrGLint profileMask; + GR_GL_GetIntegerv(gli, GR_GL_CONTEXT_PROFILE_MASK, &profileMask); + fIsCoreProfile = SkToBool(profileMask & GR_GL_CONTEXT_CORE_PROFILE_BIT); + } + } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS, + &fMaxFragmentUniformVectors); + } + + if (fDriverBugWorkarounds.max_fragment_uniform_vectors_32) { + fMaxFragmentUniformVectors = SkMin32(fMaxFragmentUniformVectors, 32); + } + GR_GL_GetIntegerv(gli, GR_GL_MAX_VERTEX_ATTRIBS, &fMaxVertexAttributes); + + if (GR_IS_GR_GL(standard)) { + fWritePixelsRowBytesSupport = true; + fReadPixelsRowBytesSupport = true; + fPackFlipYSupport = false; + } else if (GR_IS_GR_GL_ES(standard)) { + fWritePixelsRowBytesSupport = + version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_unpack_subimage"); + fReadPixelsRowBytesSupport = + version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_NV_pack_subimage"); + fPackFlipYSupport = + ctxInfo.hasExtension("GL_ANGLE_pack_reverse_row_order"); + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL 2.0 has these + fWritePixelsRowBytesSupport = version >= GR_GL_VER(2, 0); + fReadPixelsRowBytesSupport = version >= GR_GL_VER(2, 0); + } + if (fDriverBugWorkarounds.pack_parameters_workaround_with_pack_buffer) { + // In some cases drivers handle copying the last row incorrectly + // when using GL_PACK_ROW_LENGTH. Chromium handles this by iterating + // through every row and conditionally clobbering that value, but + // Skia already has a scratch buffer workaround when pack row length + // is not supported, so just use that. + fReadPixelsRowBytesSupport = false; + } + + fTextureUsageSupport = GR_IS_GR_GL_ES(standard) && + ctxInfo.hasExtension("GL_ANGLE_texture_usage"); + + if (GR_IS_GR_GL(standard)) { + fTextureBarrierSupport = version >= GR_GL_VER(4,5) || + ctxInfo.hasExtension("GL_ARB_texture_barrier") || + ctxInfo.hasExtension("GL_NV_texture_barrier"); + } else if (GR_IS_GR_GL_ES(standard)) { + fTextureBarrierSupport = ctxInfo.hasExtension("GL_NV_texture_barrier"); + } // no WebGL support + + if (GR_IS_GR_GL(standard)) { + fSampleLocationsSupport = version >= GR_GL_VER(3,2) || + ctxInfo.hasExtension("GL_ARB_texture_multisample"); + } else if (GR_IS_GR_GL_ES(standard)) { + fSampleLocationsSupport = version >= GR_GL_VER(3,1); + } // no WebGL support + + fImagingSupport = GR_IS_GR_GL(standard) && + ctxInfo.hasExtension("GL_ARB_imaging"); + + if (((GR_IS_GR_GL(standard) && version >= GR_GL_VER(4,3)) || + (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3,0)) || + ctxInfo.hasExtension("GL_ARB_invalidate_subdata"))) { + fInvalidateFBType = kInvalidate_InvalidateFBType; + } else if (ctxInfo.hasExtension("GL_EXT_discard_framebuffer")) { + fInvalidateFBType = kDiscard_InvalidateFBType; + } + + // For future reference on Desktop GL, GL_PRIMITIVE_RESTART_FIXED_INDEX appears in 4.3, and + // GL_PRIMITIVE_RESTART (where the client must call glPrimitiveRestartIndex) appears in 3.1. + if (GR_IS_GR_GL_ES(standard)) { + // Primitive restart can cause a 3x slowdown on Adreno. Enable conservatively. + // FIXME: Primitive restart would likely be a win on iOS if we had an enum value for it. + if (kARM_GrGLVendor == ctxInfo.vendor()) { + fUsePrimitiveRestart = version >= GR_GL_VER(3,0); + } + } + + if (kARM_GrGLVendor == ctxInfo.vendor() || + kImagination_GrGLVendor == ctxInfo.vendor() || + kQualcomm_GrGLVendor == ctxInfo.vendor() ) { + fPreferFullscreenClears = true; + } + + if (GR_IS_GR_GL(standard)) { + fVertexArrayObjectSupport = version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_ARB_vertex_array_object") || + ctxInfo.hasExtension("GL_APPLE_vertex_array_object"); + } else if (GR_IS_GR_GL_ES(standard)) { + fVertexArrayObjectSupport = version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_OES_vertex_array_object"); + } else if (GR_IS_GR_WEBGL(standard)) { + fVertexArrayObjectSupport = version >= GR_GL_VER(2, 0) || + ctxInfo.hasExtension("GL_OES_vertex_array_object") || + ctxInfo.hasExtension("OES_vertex_array_object"); + } + + if (GR_IS_GR_GL(standard) && version >= GR_GL_VER(4,3)) { + fDebugSupport = true; + } else if (GR_IS_GR_GL_ES(standard)) { + fDebugSupport = ctxInfo.hasExtension("GL_KHR_debug"); + } // no WebGL support + + if (GR_IS_GR_GL(standard)) { + fES2CompatibilitySupport = ctxInfo.hasExtension("GL_ARB_ES2_compatibility"); + } + else if (GR_IS_GR_GL_ES(standard)) { + fES2CompatibilitySupport = true; + } else if (GR_IS_GR_WEBGL(standard)) { + fES2CompatibilitySupport = true; + } + + if (GR_IS_GR_GL(standard)) { + fMultisampleDisableSupport = true; + } else if (GR_IS_GR_GL_ES(standard)) { + fMultisampleDisableSupport = ctxInfo.hasExtension("GL_EXT_multisample_compatibility"); + } // no WebGL support + + if (GR_IS_GR_GL(standard)) { + // 3.1 has draw_instanced but not instanced_arrays, for the time being we only care about + // instanced arrays, but we could make this more granular if we wanted + fInstanceAttribSupport = + version >= GR_GL_VER(3, 2) || + (ctxInfo.hasExtension("GL_ARB_draw_instanced") && + ctxInfo.hasExtension("GL_ARB_instanced_arrays")); + } else if (GR_IS_GR_GL_ES(standard)) { + fInstanceAttribSupport = + version >= GR_GL_VER(3, 0) || + (ctxInfo.hasExtension("GL_EXT_draw_instanced") && + ctxInfo.hasExtension("GL_EXT_instanced_arrays")); + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL 2.0 has DrawArraysInstanced and drawElementsInstanced + fInstanceAttribSupport = version >= GR_GL_VER(2, 0); + } + + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3, 0)) { + fBindFragDataLocationSupport = true; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3, 0) && ctxInfo.hasExtension("GL_EXT_blend_func_extended")) { + fBindFragDataLocationSupport = true; + } + } // no WebGL support + + fBindUniformLocationSupport = ctxInfo.hasExtension("GL_CHROMIUM_bind_uniform_location"); + + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3, 1) || ctxInfo.hasExtension("GL_ARB_texture_rectangle") || + ctxInfo.hasExtension("GL_ANGLE_texture_rectangle")) { + fRectangleTextureSupport = true; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (kChromium_GrGLDriver == ctxInfo.driver()) { + fRectangleTextureSupport = ctxInfo.hasExtension("GL_ARB_texture_rectangle"); + } else { + // ANGLE will advertise the extension in ES2 contexts but actually using the texture in + // a shader requires ES3 shading language. + fRectangleTextureSupport = ctxInfo.hasExtension("GL_ANGLE_texture_rectangle") && + ctxInfo.glslGeneration() >= k330_GrGLSLGeneration; + } + } // no WebGL support + + // GrCaps defaults fClampToBorderSupport to true, so disable when unsupported + if (GR_IS_GR_GL(standard)) { + // Clamp to border added in 1.3 + if (version < GR_GL_VER(1, 3) && !ctxInfo.hasExtension("GL_ARB_texture_border_clamp")) { + fClampToBorderSupport = false; + } + } else if (GR_IS_GR_GL_ES(standard)) { + // GLES didn't have clamp to border until 3.2, but provides several alternative extensions + if (version < GR_GL_VER(3, 2) && !ctxInfo.hasExtension("GL_EXT_texture_border_clamp") && + !ctxInfo.hasExtension("GL_NV_texture_border_clamp") && + !ctxInfo.hasExtension("GL_OES_texture_border_clamp")) { + fClampToBorderSupport = false; + } + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL appears to only have REPEAT, CLAMP_TO_EDGE and MIRRORED_REPEAT + fClampToBorderSupport = false; + } + + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3,3) || ctxInfo.hasExtension("GL_ARB_texture_swizzle")) { + this->fShaderCaps->fTextureSwizzleAppliedInShader = false; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3,0)) { + this->fShaderCaps->fTextureSwizzleAppliedInShader = false; + } + } // no WebGL support + + if (GR_IS_GR_GL(standard)) { + fMipMapLevelAndLodControlSupport = true; + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3,0)) { + fMipMapLevelAndLodControlSupport = true; + } + } // no WebGL support + +#ifdef SK_BUILD_FOR_WIN + // We're assuming that on Windows Chromium we're using ANGLE. + bool isANGLE = kANGLE_GrGLDriver == ctxInfo.driver() || + kChromium_GrGLDriver == ctxInfo.driver(); + // Angle has slow read/write pixel paths for 32bit RGBA (but fast for BGRA). + fRGBA8888PixelsOpsAreSlow = isANGLE; + // On DX9 ANGLE reading a partial FBO is slow. TODO: Check whether this is still true and + // check DX11 ANGLE. + fPartialFBOReadIsSlow = isANGLE; +#endif + + bool isMESA = kMesa_GrGLDriver == ctxInfo.driver(); + bool isMAC = false; +#ifdef SK_BUILD_FOR_MAC + isMAC = true; +#endif + + // Both mesa and mac have reduced performance if reading back an RGBA framebuffer as BGRA or + // vis-versa. + fRGBAToBGRAReadbackConversionsAreSlow = isMESA || isMAC; + + // Chrome's command buffer will zero out a buffer if null is passed to glBufferData to + // avoid letting an application see uninitialized memory. + if (GR_IS_GR_GL(standard) || GR_IS_GR_GL_ES(standard)) { + fUseBufferDataNullHint = kChromium_GrGLDriver != ctxInfo.driver(); + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL spec explicitly disallows null values. + fUseBufferDataNullHint = false; + } + + if (GR_IS_GR_GL(standard)) { + fClearTextureSupport = (version >= GR_GL_VER(4,4) || + ctxInfo.hasExtension("GL_ARB_clear_texture")); + } else if (GR_IS_GR_GL_ES(standard)) { + fClearTextureSupport = ctxInfo.hasExtension("GL_EXT_clear_texture"); + } // no WebGL support + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 + fSupportsAHardwareBufferImages = true; +#endif + + if (GR_IS_GR_GL(standard)) { + fSRGBWriteControl = version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_ARB_framebuffer_sRGB") || + ctxInfo.hasExtension("GL_EXT_framebuffer_sRGB"); + } else if (GR_IS_GR_GL_ES(standard)) { + // ES through 3.2 requires EXT_srgb_write_control to support toggling + // sRGB writing for destinations. + fSRGBWriteControl = ctxInfo.hasExtension("GL_EXT_sRGB_write_control"); + } // No WebGL support + + /************************************************************************** + * GrShaderCaps fields + **************************************************************************/ + + // This must be called after fCoreProfile is set on the GrGLCaps + this->initGLSL(ctxInfo, gli); + GrShaderCaps* shaderCaps = fShaderCaps.get(); + + shaderCaps->fPathRenderingSupport = this->hasPathRenderingSupport(ctxInfo, gli); + + // Enable supported shader-related caps + if (GR_IS_GR_GL(standard)) { + shaderCaps->fDualSourceBlendingSupport = (version >= GR_GL_VER(3, 3) || + ctxInfo.hasExtension("GL_ARB_blend_func_extended")) && + ctxInfo.glslGeneration() >= k130_GrGLSLGeneration; + + shaderCaps->fShaderDerivativeSupport = true; + + // we don't support GL_ARB_geometry_shader4, just GL 3.2+ GS + shaderCaps->fGeometryShaderSupport = version >= GR_GL_VER(3, 2) && + ctxInfo.glslGeneration() >= k150_GrGLSLGeneration; + if (shaderCaps->fGeometryShaderSupport) { + if (ctxInfo.glslGeneration() >= k400_GrGLSLGeneration) { + shaderCaps->fGSInvocationsSupport = true; + } else if (ctxInfo.hasExtension("GL_ARB_gpu_shader5")) { + shaderCaps->fGSInvocationsSupport = true; + shaderCaps->fGSInvocationsExtensionString = "GL_ARB_gpu_shader5"; + } + } + + shaderCaps->fIntegerSupport = version >= GR_GL_VER(3, 0) && + ctxInfo.glslGeneration() >= k130_GrGLSLGeneration; + } else if (GR_IS_GR_GL_ES(standard)) { + shaderCaps->fDualSourceBlendingSupport = ctxInfo.hasExtension("GL_EXT_blend_func_extended"); + + shaderCaps->fShaderDerivativeSupport = version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_OES_standard_derivatives"); + + // Mali and early Adreno both have support for geometry shaders, but they appear to be + // implemented in software. In practice with ccpr, they are slower than the backup impl that + // only uses vertex shaders. + if (kARM_GrGLVendor != ctxInfo.vendor() && + kAdreno3xx_GrGLRenderer != ctxInfo.renderer() && + kAdreno4xx_other_GrGLRenderer != ctxInfo.renderer()) { + + if (version >= GR_GL_VER(3,2)) { + shaderCaps->fGeometryShaderSupport = true; + } else if (ctxInfo.hasExtension("GL_EXT_geometry_shader")) { + shaderCaps->fGeometryShaderSupport = true; + shaderCaps->fGeometryShaderExtensionString = "GL_EXT_geometry_shader"; + } + shaderCaps->fGSInvocationsSupport = shaderCaps->fGeometryShaderSupport; + } + + shaderCaps->fIntegerSupport = version >= GR_GL_VER(3, 0) && + ctxInfo.glslGeneration() >= k330_GrGLSLGeneration; // We use this value for GLSL ES 3.0. + } else if (GR_IS_GR_WEBGL(standard)) { + shaderCaps->fShaderDerivativeSupport = ctxInfo.hasExtension("GL_OES_standard_derivatives") || + ctxInfo.hasExtension("OES_standard_derivatives"); + } + + // Protect ourselves against tracking huge amounts of texture state. + static const uint8_t kMaxSaneSamplers = 32; + GrGLint maxSamplers; + GR_GL_GetIntegerv(gli, GR_GL_MAX_TEXTURE_IMAGE_UNITS, &maxSamplers); + shaderCaps->fMaxFragmentSamplers = SkTMin(kMaxSaneSamplers, maxSamplers); + + // SGX and Mali GPUs have tiled architectures that have trouble with frequently changing VBOs. + // We've measured a performance increase using non-VBO vertex data for dynamic content on these + // GPUs. Perhaps we should read the renderer string and limit this decision to specific GPU + // families rather than basing it on the vendor alone. + // The Chrome command buffer blocks the use of client side buffers (but may emulate VBOs with + // them). Client side buffers are not allowed in core profiles. + if (GR_IS_GR_GL(standard) || GR_IS_GR_GL_ES(standard)) { + if (ctxInfo.driver() != kChromium_GrGLDriver && !fIsCoreProfile && + (ctxInfo.vendor() == kARM_GrGLVendor || ctxInfo.vendor() == kImagination_GrGLVendor || + ctxInfo.vendor() == kQualcomm_GrGLVendor)) { + fPreferClientSideDynamicBuffers = true; + } + } // No client side arrays in WebGL https://www.khronos.org/registry/webgl/specs/1.0/#6.2 + + if (!contextOptions.fAvoidStencilBuffers) { + // To reduce surface area, if we avoid stencil buffers, we also disable MSAA. + this->initFSAASupport(contextOptions, ctxInfo, gli); + this->initStencilSupport(ctxInfo); + } + + // Setup blit framebuffer + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3,0) || + ctxInfo.hasExtension("GL_ARB_framebuffer_object") || + ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) { + fBlitFramebufferFlags = 0; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3, 0)) { + fBlitFramebufferFlags = kNoFormatConversionForMSAASrc_BlitFramebufferFlag | + kNoMSAADst_BlitFramebufferFlag | + kRectsMustMatchForMSAASrc_BlitFramebufferFlag; + } else if (ctxInfo.hasExtension("GL_CHROMIUM_framebuffer_multisample") || + ctxInfo.hasExtension("GL_ANGLE_framebuffer_blit")) { + // The CHROMIUM extension uses the ANGLE version of glBlitFramebuffer and includes its + // limitations. + fBlitFramebufferFlags = kNoScalingOrMirroring_BlitFramebufferFlag | + kResolveMustBeFull_BlitFrambufferFlag | + kNoMSAADst_BlitFramebufferFlag | + kNoFormatConversion_BlitFramebufferFlag | + kRectsMustMatchForMSAASrc_BlitFramebufferFlag; + } + } // No WebGL 1.0 support for BlitFramebuffer + + this->initBlendEqationSupport(ctxInfo); + + if (GR_IS_GR_GL(standard)) { + fMapBufferFlags = kCanMap_MapFlag; // we require VBO support and the desktop VBO + // extension includes glMapBuffer. + if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_map_buffer_range")) { + fMapBufferFlags |= kSubset_MapFlag; + fMapBufferType = kMapBufferRange_MapBufferType; + } else { + fMapBufferType = kMapBuffer_MapBufferType; + } + } else if (GR_IS_GR_GL_ES(standard)) { + // Unextended GLES2 doesn't have any buffer mapping. + fMapBufferFlags = kNone_MapBufferType; + if (ctxInfo.hasExtension("GL_CHROMIUM_map_sub")) { + fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag; + fMapBufferType = kChromium_MapBufferType; + } else if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_map_buffer_range")) { + fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag; + fMapBufferType = kMapBufferRange_MapBufferType; + } else if (ctxInfo.hasExtension("GL_OES_mapbuffer")) { + fMapBufferFlags = kCanMap_MapFlag; + fMapBufferType = kMapBuffer_MapBufferType; + } + } else if (GR_IS_GR_WEBGL(standard)) { + // explicitly removed https://www.khronos.org/registry/webgl/specs/2.0/#5.14 + fMapBufferFlags = kNone_MapBufferType; + } + + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(2, 1) || ctxInfo.hasExtension("GL_ARB_pixel_buffer_object") || + ctxInfo.hasExtension("GL_EXT_pixel_buffer_object")) { + fTransferBufferSupport = true; + fTransferBufferType = kPBO_TransferBufferType; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3, 0) || + (ctxInfo.hasExtension("GL_NV_pixel_buffer_object") && + // GL_EXT_unpack_subimage needed to support subtexture rectangles + ctxInfo.hasExtension("GL_EXT_unpack_subimage"))) { + fTransferBufferSupport = true; + fTransferBufferType = kPBO_TransferBufferType; +// TODO: get transfer buffers working in Chrome +// } else if (ctxInfo.hasExtension("GL_CHROMIUM_pixel_transfer_buffer_object")) { +// fTransferBufferSupport = true; +// fTransferBufferType = kChromium_TransferBufferType; + } + } // no WebGL support + + // On many GPUs, map memory is very expensive, so we effectively disable it here by setting the + // threshold to the maximum unless the client gives us a hint that map memory is cheap. + if (fBufferMapThreshold < 0) { +#if 0 + // We think mapping on Chromium will be cheaper once we know ahead of time how much space + // we will use for all GrMeshDrawOps. Right now we might wind up mapping a large buffer and + // using a small subset. + fBufferMapThreshold = kChromium_GrGLDriver == ctxInfo.driver() ? 0 : SK_MaxS32; +#else + fBufferMapThreshold = SK_MaxS32; +#endif + } + + if (GR_IS_GR_GL(standard)) { + fNPOTTextureTileSupport = true; + fMipMapSupport = true; + } else if (GR_IS_GR_GL_ES(standard)) { + // Unextended ES2 supports NPOT textures with clamp_to_edge and non-mip filters only + // ES3 has no limitations. + fNPOTTextureTileSupport = version >= GR_GL_VER(3,0) || + ctxInfo.hasExtension("GL_OES_texture_npot"); + // ES2 supports MIP mapping for POT textures but our caps don't allow for limited MIP + // support. The OES extension or ES 3.0 allow for MIPS on NPOT textures. So, apparently, + // does the undocumented GL_IMG_texture_npot extension. This extension does not seem to + // to alllow arbitrary wrap modes, however. + fMipMapSupport = fNPOTTextureTileSupport || ctxInfo.hasExtension("GL_IMG_texture_npot"); + } else if (GR_IS_GR_WEBGL(standard)) { + // Texture access works in the WebGL 2.0 API as in the OpenGL ES 3.0 API + fNPOTTextureTileSupport = version >= GR_GL_VER(2,0); + // All mipmapping and all wrapping modes are supported for non-power-of- + // two images [in WebGL 2.0]. + fMipMapSupport = fNPOTTextureTileSupport; + } + + GR_GL_GetIntegerv(gli, GR_GL_MAX_TEXTURE_SIZE, &fMaxTextureSize); + + if (fDriverBugWorkarounds.max_texture_size_limit_4096) { + fMaxTextureSize = SkTMin(fMaxTextureSize, 4096); + } + + GR_GL_GetIntegerv(gli, GR_GL_MAX_RENDERBUFFER_SIZE, &fMaxRenderTargetSize); + // Our render targets are always created with textures as the color + // attachment, hence this min: + fMaxRenderTargetSize = SkTMin(fMaxTextureSize, fMaxRenderTargetSize); + fMaxPreferredRenderTargetSize = fMaxRenderTargetSize; + + if (kARM_GrGLVendor == ctxInfo.vendor()) { + // On Mali G71, RT's above 4k have been observed to incur a performance cost. + fMaxPreferredRenderTargetSize = SkTMin(4096, fMaxPreferredRenderTargetSize); + } + + fGpuTracingSupport = ctxInfo.hasExtension("GL_EXT_debug_marker"); + + // Disable scratch texture reuse on Mali and Adreno devices + fReuseScratchTextures = kARM_GrGLVendor != ctxInfo.vendor(); + +#if 0 + fReuseScratchBuffers = kARM_GrGLVendor != ctxInfo.vendor() && + kQualcomm_GrGLVendor != ctxInfo.vendor(); +#endif + + if (ctxInfo.hasExtension("GL_EXT_window_rectangles")) { + GR_GL_GetIntegerv(gli, GR_GL_MAX_WINDOW_RECTANGLES, &fMaxWindowRectangles); + } + +#ifdef SK_BUILD_FOR_WIN + // On ANGLE deferring flushes can lead to GPU starvation + fPreferVRAMUseOverFlushes = !isANGLE; +#endif + + if (kARM_GrGLVendor == ctxInfo.vendor()) { + // ARM seems to do better with larger quantities of fine triangles, as opposed to using the + // sample mask. (At least in our current round rect op.) + fPreferTrianglesOverSampleMask = true; + } + + if (kChromium_GrGLDriver == ctxInfo.driver()) { + fMustClearUploadedBufferData = true; + } + + // In a WASM build on Firefox, we see warnings like + // WebGL warning: texSubImage2D: This operation requires zeroing texture data. This is slow. + // WebGL warning: texSubImage2D: Texture has not been initialized prior to a partial upload, + // forcing the browser to clear it. This may be slow. + // Setting the initial clear seems to make those warnings go away and offers a substantial + // boost in performance in Firefox. Chrome sees a more modest increase. + if (GR_IS_GR_WEBGL(standard)) { + fShouldInitializeTextures = true; + } + + if (GR_IS_GR_GL(standard)) { + // ARB allows mixed size FBO attachments, EXT does not. + if (version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_ARB_framebuffer_object")) { + fOversizedStencilSupport = true; + } else { + SkASSERT(ctxInfo.hasExtension("GL_EXT_framebuffer_object")); + } + } else if (GR_IS_GR_GL_ES(standard)) { + // ES 3.0 supports mixed size FBO attachments, 2.0 does not. + fOversizedStencilSupport = version >= GR_GL_VER(3, 0); + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL 1.0 has some constraints for FBO attachments: + // https://www.khronos.org/registry/webgl/specs/1.0/index.html#6.6 + // These constraints "no longer apply in WebGL 2" + fOversizedStencilSupport = version >= GR_GL_VER(2, 0); + } + + if (GR_IS_GR_GL(standard)) { + fDrawIndirectSupport = version >= GR_GL_VER(4,0) || + ctxInfo.hasExtension("GL_ARB_draw_indirect"); + fBaseInstanceSupport = version >= GR_GL_VER(4,2); + fMultiDrawIndirectSupport = version >= GR_GL_VER(4,3) || + (fDrawIndirectSupport && + !fBaseInstanceSupport && // The ARB extension has no base inst. + ctxInfo.hasExtension("GL_ARB_multi_draw_indirect")); + fDrawRangeElementsSupport = version >= GR_GL_VER(2,0); + } else if (GR_IS_GR_GL_ES(standard)) { + fDrawIndirectSupport = version >= GR_GL_VER(3,1); + fMultiDrawIndirectSupport = fDrawIndirectSupport && + ctxInfo.hasExtension("GL_EXT_multi_draw_indirect"); + fBaseInstanceSupport = fDrawIndirectSupport && + ctxInfo.hasExtension("GL_EXT_base_instance"); + fDrawRangeElementsSupport = version >= GR_GL_VER(3,0); + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL lacks indirect support, but drawRange was added in WebGL 2.0 + fDrawRangeElementsSupport = version >= GR_GL_VER(2,0); + } + + // TODO: support CHROMIUM_sync_point and maybe KHR_fence_sync + if (GR_IS_GR_GL(standard)) { + fFenceSyncSupport = (version >= GR_GL_VER(3, 2) || ctxInfo.hasExtension("GL_ARB_sync")); + } else if (GR_IS_GR_GL_ES(standard)) { + fFenceSyncSupport = (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_APPLE_sync")); + } else if (GR_IS_GR_WEBGL(standard)) { + // Only in WebGL 2.0 + fFenceSyncSupport = version >= GR_GL_VER(2, 0); + } + // The same objects (GL sync objects) are used to implement GPU/CPU fence syncs and GPU/GPU + // semaphores. + fSemaphoreSupport = fFenceSyncSupport; + + // Safely moving textures between contexts requires semaphores. + fCrossContextTextureSupport = fSemaphoreSupport; + + // Half float vertex attributes requires GL3 or ES3 + // It can also work with OES_VERTEX_HALF_FLOAT, but that requires a different enum. + if (GR_IS_GR_GL(standard)) { + fHalfFloatVertexAttributeSupport = (version >= GR_GL_VER(3, 0)); + } else if (GR_IS_GR_GL_ES(standard)) { + fHalfFloatVertexAttributeSupport = (version >= GR_GL_VER(3, 0)); + } else if (GR_IS_GR_WEBGL(standard)) { + // This appears to be supported in 2.0, looking at the spec. + fHalfFloatVertexAttributeSupport = (version >= GR_GL_VER(2, 0)); + } + + fDynamicStateArrayGeometryProcessorTextureSupport = true; + + if (GR_IS_GR_GL(standard)) { + fProgramBinarySupport = (version >= GR_GL_VER(4, 1)); + fProgramParameterSupport = (version >= GR_GL_VER(4, 1)); + } else if (GR_IS_GR_GL_ES(standard)) { + fProgramBinarySupport = + (version >= GR_GL_VER(3, 0)) || ctxInfo.hasExtension("GL_OES_get_program_binary"); + fProgramParameterSupport = (version >= GR_GL_VER(3, 0)); + } // Explicitly not supported in WebGL 2.0 + // https://www.khronos.org/registry/webgl/specs/2.0/#5.4 + if (fProgramBinarySupport) { + GrGLint count; + GR_GL_GetIntegerv(gli, GR_GL_NUM_PROGRAM_BINARY_FORMATS, &count); + fProgramBinarySupport = count > 0; + } + if (GR_IS_GR_GL(standard)) { + fSamplerObjectSupport = + version >= GR_GL_VER(3,3) || ctxInfo.hasExtension("GL_ARB_sampler_objects"); + } else if (GR_IS_GR_GL_ES(standard)) { + fSamplerObjectSupport = version >= GR_GL_VER(3,0); + } else if (GR_IS_GR_WEBGL(standard)) { + fSamplerObjectSupport = version >= GR_GL_VER(2,0); + } + + if (GR_IS_GR_GL_ES(standard)) { + fTiledRenderingSupport = ctxInfo.hasExtension("GL_QCOM_tiled_rendering"); + } + + FormatWorkarounds formatWorkarounds; + + if (!contextOptions.fDisableDriverCorrectnessWorkarounds) { + this->applyDriverCorrectnessWorkarounds(ctxInfo, contextOptions, shaderCaps, + &formatWorkarounds); + } + + // Requires fTextureSwizzleSupport, msaa support, ES compatibility have + // already been detected. + this->initFormatTable(ctxInfo, gli, formatWorkarounds); + + this->applyOptionsOverrides(contextOptions); + shaderCaps->applyOptionsOverrides(contextOptions); + + // For now these two are equivalent but we could have dst read in shader via some other method. + shaderCaps->fDstReadInShaderSupport = shaderCaps->fFBFetchSupport; +} + +const char* get_glsl_version_decl_string(GrGLStandard standard, GrGLSLGeneration generation, + bool isCoreProfile) { + if (GR_IS_GR_GL(standard)) { + switch (generation) { + case k110_GrGLSLGeneration: + return "#version 110\n"; + case k130_GrGLSLGeneration: + return "#version 130\n"; + case k140_GrGLSLGeneration: + return "#version 140\n"; + case k150_GrGLSLGeneration: + if (isCoreProfile) { + return "#version 150\n"; + } else { + return "#version 150 compatibility\n"; + } + case k330_GrGLSLGeneration: + if (isCoreProfile) { + return "#version 330\n"; + } else { + return "#version 330 compatibility\n"; + } + case k400_GrGLSLGeneration: + if (isCoreProfile) { + return "#version 400\n"; + } else { + return "#version 400 compatibility\n"; + } + case k420_GrGLSLGeneration: + if (isCoreProfile) { + return "#version 420\n"; + } else { + return "#version 420 compatibility\n"; + } + default: + break; + } + } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + switch (generation) { + case k110_GrGLSLGeneration: + // ES2s shader language is based on version 1.20 but is version + // 1.00 of the ES language. + return "#version 100\n"; + case k330_GrGLSLGeneration: + return "#version 300 es\n"; + case k310es_GrGLSLGeneration: + return "#version 310 es\n"; + case k320es_GrGLSLGeneration: + return "#version 320 es\n"; + default: + break; + } + } + return ""; +} + +bool is_float_fp32(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli, GrGLenum precision) { + if (GR_IS_GR_GL(ctxInfo.standard()) && + ctxInfo.version() < GR_GL_VER(4,1) && + !ctxInfo.hasExtension("GL_ARB_ES2_compatibility")) { + // We're on a desktop GL that doesn't have precision info. Assume they're all 32bit float. + return true; + } + // glGetShaderPrecisionFormat doesn't accept GL_GEOMETRY_SHADER as a shader type. Hopefully the + // geometry shaders don't have lower precision than vertex and fragment. + for (GrGLenum shader : {GR_GL_FRAGMENT_SHADER, GR_GL_VERTEX_SHADER}) { + GrGLint range[2]; + GrGLint bits; + GR_GL_GetShaderPrecisionFormat(gli, shader, precision, range, &bits); + if (range[0] < 127 || range[1] < 127 || bits < 23) { + return false; + } + } + return true; +} + +void GrGLCaps::initGLSL(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) { + GrGLStandard standard = ctxInfo.standard(); + GrGLVersion version = ctxInfo.version(); + + /************************************************************************** + * Caps specific to GrShaderCaps + **************************************************************************/ + + GrShaderCaps* shaderCaps = fShaderCaps.get(); + shaderCaps->fGLSLGeneration = ctxInfo.glslGeneration(); + if (GR_IS_GR_GL_ES(standard)) { + // fFBFetchRequiresEnablePerSample is not a shader cap but is initialized below to keep it + // with related FB fetch logic. + if (ctxInfo.hasExtension("GL_EXT_shader_framebuffer_fetch")) { + shaderCaps->fFBFetchNeedsCustomOutput = (version >= GR_GL_VER(3, 0)); + shaderCaps->fFBFetchSupport = true; + shaderCaps->fFBFetchColorName = "gl_LastFragData[0]"; + shaderCaps->fFBFetchExtensionString = "GL_EXT_shader_framebuffer_fetch"; + fFBFetchRequiresEnablePerSample = false; + } else if (ctxInfo.hasExtension("GL_NV_shader_framebuffer_fetch")) { + // Actually, we haven't seen an ES3.0 device with this extension yet, so we don't know. + shaderCaps->fFBFetchNeedsCustomOutput = false; + shaderCaps->fFBFetchSupport = true; + shaderCaps->fFBFetchColorName = "gl_LastFragData[0]"; + shaderCaps->fFBFetchExtensionString = "GL_NV_shader_framebuffer_fetch"; + fFBFetchRequiresEnablePerSample = false; + } else if (ctxInfo.hasExtension("GL_ARM_shader_framebuffer_fetch")) { + // The arm extension also requires an additional flag which we will set onResetContext. + shaderCaps->fFBFetchNeedsCustomOutput = false; + shaderCaps->fFBFetchSupport = true; + shaderCaps->fFBFetchColorName = "gl_LastFragColorARM"; + shaderCaps->fFBFetchExtensionString = "GL_ARM_shader_framebuffer_fetch"; + fFBFetchRequiresEnablePerSample = true; + } + shaderCaps->fUsesPrecisionModifiers = true; + } else if (GR_IS_GR_WEBGL(standard)) { + shaderCaps->fUsesPrecisionModifiers = true; + } + + if (GR_IS_GR_GL(standard)) { + shaderCaps->fFlatInterpolationSupport = ctxInfo.glslGeneration() >= k130_GrGLSLGeneration; + } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + shaderCaps->fFlatInterpolationSupport = + ctxInfo.glslGeneration() >= k330_GrGLSLGeneration; // This is the value for GLSL ES 3.0. + } // not sure for WebGL + + // Flat interpolation appears to be slow on Qualcomm GPUs (tested Adreno 405 and 530). ANGLE + // Avoid on ANGLE too, it inserts a geometry shader into the pipeline to implement flat interp. + shaderCaps->fPreferFlatInterpolation = shaderCaps->fFlatInterpolationSupport && + kQualcomm_GrGLVendor != ctxInfo.vendor() && + kANGLE_GrGLDriver != ctxInfo.driver(); + if (GR_IS_GR_GL(standard)) { + shaderCaps->fNoPerspectiveInterpolationSupport = + ctxInfo.glslGeneration() >= k130_GrGLSLGeneration; + } else if (GR_IS_GR_GL_ES(standard)) { + if (ctxInfo.hasExtension("GL_NV_shader_noperspective_interpolation") && + ctxInfo.glslGeneration() >= k330_GrGLSLGeneration /* GLSL ES 3.0 */) { + shaderCaps->fNoPerspectiveInterpolationSupport = true; + shaderCaps->fNoPerspectiveInterpolationExtensionString = + "GL_NV_shader_noperspective_interpolation"; + } + } // Not sure for WebGL + + if (GR_IS_GR_GL(standard)) { + shaderCaps->fSampleVariablesSupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration; + } else if (GR_IS_GR_GL_ES(standard)) { + if (ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration) { + shaderCaps->fSampleVariablesSupport = true; + } else if (ctxInfo.hasExtension("GL_OES_sample_variables")) { + shaderCaps->fSampleVariablesSupport = true; + shaderCaps->fSampleVariablesExtensionString = "GL_OES_sample_variables"; + } + } + shaderCaps->fSampleVariablesStencilSupport = shaderCaps->fSampleVariablesSupport; + + if (kQualcomm_GrGLVendor == ctxInfo.vendor() || kATI_GrGLVendor == ctxInfo.vendor()) { + // FIXME: The sample mask round rect op draws nothing on several Adreno and Radeon bots. + // Other ops that use sample mask while rendering to stencil seem to work fine. Temporarily + // disable sample mask on color buffers while we investigate. + // http://skbug.com/8921 + shaderCaps->fSampleVariablesSupport = false; + } + + shaderCaps->fVersionDeclString = get_glsl_version_decl_string(standard, + shaderCaps->fGLSLGeneration, + fIsCoreProfile); + + if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + if (k110_GrGLSLGeneration == shaderCaps->fGLSLGeneration) { + shaderCaps->fShaderDerivativeExtensionString = "GL_OES_standard_derivatives"; + } + } // WebGL might have to check for OES_standard_derivatives + + // Frag Coords Convention support is not part of ES + if (GR_IS_GR_GL(standard) && + (ctxInfo.glslGeneration() >= k150_GrGLSLGeneration || + ctxInfo.hasExtension("GL_ARB_fragment_coord_conventions"))) { + shaderCaps->fFragCoordConventionsExtensionString = "GL_ARB_fragment_coord_conventions"; + } + + if (GR_IS_GR_GL_ES(standard)) { + shaderCaps->fSecondaryOutputExtensionString = "GL_EXT_blend_func_extended"; + } + + if (ctxInfo.hasExtension("GL_OES_EGL_image_external")) { + if (ctxInfo.glslGeneration() == k110_GrGLSLGeneration) { + shaderCaps->fExternalTextureSupport = true; + shaderCaps->fExternalTextureExtensionString = "GL_OES_EGL_image_external"; + } else if (ctxInfo.hasExtension("GL_OES_EGL_image_external_essl3") || + ctxInfo.hasExtension("OES_EGL_image_external_essl3")) { + // At least one driver has been found that has this extension without the "GL_" prefix. + shaderCaps->fExternalTextureSupport = true; + shaderCaps->fExternalTextureExtensionString = "GL_OES_EGL_image_external_essl3"; + } + } + + if (GR_IS_GR_GL(standard)) { + shaderCaps->fVertexIDSupport = true; + } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + // Desktop GLSL 3.30 == ES GLSL 3.00. + shaderCaps->fVertexIDSupport = ctxInfo.glslGeneration() >= k330_GrGLSLGeneration; + } + + if (GR_IS_GR_GL(standard)) { + shaderCaps->fFPManipulationSupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration; + } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + shaderCaps->fFPManipulationSupport = ctxInfo.glslGeneration() >= k310es_GrGLSLGeneration; + } + + shaderCaps->fFloatIs32Bits = is_float_fp32(ctxInfo, gli, GR_GL_HIGH_FLOAT); + shaderCaps->fHalfIs32Bits = is_float_fp32(ctxInfo, gli, GR_GL_MEDIUM_FLOAT); + shaderCaps->fHasLowFragmentPrecision = kMali4xx_GrGLRenderer == ctxInfo.renderer(); + + if (GR_IS_GR_GL(standard)) { + shaderCaps->fBuiltinFMASupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration; + } else if (GR_IS_GR_GL_ES(standard)) { + shaderCaps->fBuiltinFMASupport = ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration; + } +} + +bool GrGLCaps::hasPathRenderingSupport(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) { + bool hasChromiumPathRendering = ctxInfo.hasExtension("GL_CHROMIUM_path_rendering"); + + if (!(ctxInfo.hasExtension("GL_NV_path_rendering") || hasChromiumPathRendering)) { + return false; + } + + if (GR_IS_GR_GL(ctxInfo.standard())) { + if (ctxInfo.version() < GR_GL_VER(4, 3) && + !ctxInfo.hasExtension("GL_ARB_program_interface_query")) { + return false; + } + } else if (GR_IS_GR_GL_ES(ctxInfo.standard())) { + if (!hasChromiumPathRendering && + ctxInfo.version() < GR_GL_VER(3, 1)) { + return false; + } + } else if (GR_IS_GR_WEBGL(ctxInfo.standard())) { + // No WebGL support + return false; + } + // We only support v1.3+ of GL_NV_path_rendering which allows us to + // set individual fragment inputs with ProgramPathFragmentInputGen. The API + // additions are detected by checking the existence of the function. + // We also use *Then* functions that not all drivers might have. Check + // them for consistency. + if (!gli->fFunctions.fStencilThenCoverFillPath || + !gli->fFunctions.fStencilThenCoverStrokePath || + !gli->fFunctions.fStencilThenCoverFillPathInstanced || + !gli->fFunctions.fStencilThenCoverStrokePathInstanced || + !gli->fFunctions.fProgramPathFragmentInputGen) { + return false; + } + return true; +} + +void GrGLCaps::initFSAASupport(const GrContextOptions& contextOptions, const GrGLContextInfo& ctxInfo, + const GrGLInterface* gli) { + // We need dual source blending and the ability to disable multisample in order to support mixed + // samples in every corner case. + if (fMultisampleDisableSupport && this->shaderCaps()->dualSourceBlendingSupport()) { + fMixedSamplesSupport = ctxInfo.hasExtension("GL_NV_framebuffer_mixed_samples") || + ctxInfo.hasExtension("GL_CHROMIUM_framebuffer_mixed_samples"); + } + + if (GR_IS_GR_GL(ctxInfo.standard())) { + if (ctxInfo.version() >= GR_GL_VER(3,0) || + ctxInfo.hasExtension("GL_ARB_framebuffer_object")) { + fMSFBOType = kStandard_MSFBOType; + } else if (ctxInfo.hasExtension("GL_EXT_framebuffer_multisample") && + ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) { + fMSFBOType = kStandard_MSFBOType; + } + } else if (GR_IS_GR_GL_ES(ctxInfo.standard())) { + // We prefer multisampled-render-to-texture extensions over ES3 MSAA because we've observed + // ES3 driver bugs on at least one device with a tiled GPU (N10). + if (ctxInfo.hasExtension("GL_EXT_multisampled_render_to_texture")) { + fMSFBOType = kES_EXT_MsToTexture_MSFBOType; + fMSAAResolvesAutomatically = true; + } else if (ctxInfo.hasExtension("GL_IMG_multisampled_render_to_texture")) { + fMSFBOType = kES_IMG_MsToTexture_MSFBOType; + fMSAAResolvesAutomatically = true; + } else if (ctxInfo.version() >= GR_GL_VER(3,0)) { + fMSFBOType = kStandard_MSFBOType; + } else if (ctxInfo.hasExtension("GL_CHROMIUM_framebuffer_multisample")) { + fMSFBOType = kStandard_MSFBOType; + } else if (ctxInfo.hasExtension("GL_ANGLE_framebuffer_multisample")) { + fMSFBOType = kStandard_MSFBOType; + } else if (ctxInfo.hasExtension("GL_APPLE_framebuffer_multisample")) { + fMSFBOType = kES_Apple_MSFBOType; + } + } else if (GR_IS_GR_WEBGL(ctxInfo.standard())) { + // No support in WebGL 1, but there is for 2.0 + if (ctxInfo.version() >= GR_GL_VER(2,0)) { + fMSFBOType = kStandard_MSFBOType; + } else { + fMSFBOType = kNone_MSFBOType; + } + } + + // We disable MSAA for all Intel GPUs. Before Gen9, performance was very bad. Even with Gen9, + // we've seen driver crashes in the wild. We don't have data on Gen11 yet. + // chromium:527565, chromium:983926 + if (kIntel_GrGLVendor == ctxInfo.vendor()) { + fMSFBOType = kNone_MSFBOType; + } +} + +void GrGLCaps::initBlendEqationSupport(const GrGLContextInfo& ctxInfo) { + GrShaderCaps* shaderCaps = static_cast(fShaderCaps.get()); + + bool layoutQualifierSupport = false; + if ((GR_IS_GR_GL(fStandard) && shaderCaps->generation() >= k140_GrGLSLGeneration) || + (GR_IS_GR_GL_ES(fStandard) && shaderCaps->generation() >= k330_GrGLSLGeneration)) { + layoutQualifierSupport = true; + } else if (GR_IS_GR_WEBGL(fStandard)) { + return; + } + + if (ctxInfo.hasExtension("GL_NV_blend_equation_advanced_coherent")) { + fBlendEquationSupport = kAdvancedCoherent_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kAutomatic_AdvBlendEqInteraction; + } else if (ctxInfo.hasExtension("GL_KHR_blend_equation_advanced_coherent") && + layoutQualifierSupport) { + fBlendEquationSupport = kAdvancedCoherent_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kGeneralEnable_AdvBlendEqInteraction; + } else if (ctxInfo.hasExtension("GL_NV_blend_equation_advanced")) { + fBlendEquationSupport = kAdvanced_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kAutomatic_AdvBlendEqInteraction; + } else if (ctxInfo.hasExtension("GL_KHR_blend_equation_advanced") && layoutQualifierSupport) { + fBlendEquationSupport = kAdvanced_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kGeneralEnable_AdvBlendEqInteraction; + // TODO: Use kSpecificEnables_AdvBlendEqInteraction if "blend_support_all_equations" is + // slow on a particular platform. + } +} + +namespace { +const GrGLuint kUnknownBitCount = GrGLStencilAttachment::kUnknownBitCount; +} + +void GrGLCaps::initStencilSupport(const GrGLContextInfo& ctxInfo) { + + // Build up list of legal stencil formats (though perhaps not supported on + // the particular gpu/driver) from most preferred to least. + + // these consts are in order of most preferred to least preferred + // we don't bother with GL_STENCIL_INDEX1 or GL_DEPTH32F_STENCIL8 + + static const StencilFormat + // internal Format stencil bits total bits packed? + gS8 = {GR_GL_STENCIL_INDEX8, 8, 8, false}, + gS16 = {GR_GL_STENCIL_INDEX16, 16, 16, false}, + gD24S8 = {GR_GL_DEPTH24_STENCIL8, 8, 32, true }, + gS4 = {GR_GL_STENCIL_INDEX4, 4, 4, false}, + // gS = {GR_GL_STENCIL_INDEX, kUnknownBitCount, kUnknownBitCount, false}, + gDS = {GR_GL_DEPTH_STENCIL, kUnknownBitCount, kUnknownBitCount, true }; + + if (GR_IS_GR_GL(ctxInfo.standard())) { + bool supportsPackedDS = + ctxInfo.version() >= GR_GL_VER(3,0) || + ctxInfo.hasExtension("GL_EXT_packed_depth_stencil") || + ctxInfo.hasExtension("GL_ARB_framebuffer_object"); + + // S1 thru S16 formats are in GL 3.0+, EXT_FBO, and ARB_FBO since we + // require FBO support we can expect these are legal formats and don't + // check. These also all support the unsized GL_STENCIL_INDEX. + fStencilFormats.push_back() = gS8; + fStencilFormats.push_back() = gS16; + if (supportsPackedDS) { + fStencilFormats.push_back() = gD24S8; + } + fStencilFormats.push_back() = gS4; + if (supportsPackedDS) { + fStencilFormats.push_back() = gDS; + } + } else if (GR_IS_GR_GL_ES(ctxInfo.standard())) { + // ES2 has STENCIL_INDEX8 without extensions but requires extensions + // for other formats. + // ES doesn't support using the unsized format. + + fStencilFormats.push_back() = gS8; + //fStencilFormats.push_back() = gS16; + if (ctxInfo.version() >= GR_GL_VER(3,0) || + ctxInfo.hasExtension("GL_OES_packed_depth_stencil")) { + fStencilFormats.push_back() = gD24S8; + } + if (ctxInfo.hasExtension("GL_OES_stencil4")) { + fStencilFormats.push_back() = gS4; + } + } else if (GR_IS_GR_WEBGL(ctxInfo.standard())) { + fStencilFormats.push_back() = gS8; + if (ctxInfo.version() >= GR_GL_VER(2,0)) { + fStencilFormats.push_back() = gD24S8; + } + } +} + +#ifdef SK_ENABLE_DUMP_GPU +void GrGLCaps::onDumpJSON(SkJSONWriter* writer) const { + + // We are called by the base class, which has already called beginObject(). We choose to nest + // all of our caps information in a named sub-object. + writer->beginObject("GL caps"); + + writer->beginArray("Stencil Formats"); + + for (int i = 0; i < fStencilFormats.count(); ++i) { + writer->beginObject(nullptr, false); + writer->appendS32("stencil bits", fStencilFormats[i].fStencilBits); + writer->appendS32("total bits", fStencilFormats[i].fTotalBits); + writer->endObject(); + } + + writer->endArray(); + + static const char* kMSFBOExtStr[] = { + "None", + "Standard", + "Apple", + "IMG MS To Texture", + "EXT MS To Texture", + }; + GR_STATIC_ASSERT(0 == kNone_MSFBOType); + GR_STATIC_ASSERT(1 == kStandard_MSFBOType); + GR_STATIC_ASSERT(2 == kES_Apple_MSFBOType); + GR_STATIC_ASSERT(3 == kES_IMG_MsToTexture_MSFBOType); + GR_STATIC_ASSERT(4 == kES_EXT_MsToTexture_MSFBOType); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kMSFBOExtStr) == kLast_MSFBOType + 1); + + static const char* kInvalidateFBTypeStr[] = { + "None", + "Discard", + "Invalidate", + }; + GR_STATIC_ASSERT(0 == kNone_InvalidateFBType); + GR_STATIC_ASSERT(1 == kDiscard_InvalidateFBType); + GR_STATIC_ASSERT(2 == kInvalidate_InvalidateFBType); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kInvalidateFBTypeStr) == kLast_InvalidateFBType + 1); + + static const char* kMapBufferTypeStr[] = { + "None", + "MapBuffer", + "MapBufferRange", + "Chromium", + }; + GR_STATIC_ASSERT(0 == kNone_MapBufferType); + GR_STATIC_ASSERT(1 == kMapBuffer_MapBufferType); + GR_STATIC_ASSERT(2 == kMapBufferRange_MapBufferType); + GR_STATIC_ASSERT(3 == kChromium_MapBufferType); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kMapBufferTypeStr) == kLast_MapBufferType + 1); + + writer->appendBool("Core Profile", fIsCoreProfile); + writer->appendString("MSAA Type", kMSFBOExtStr[fMSFBOType]); + writer->appendString("Invalidate FB Type", kInvalidateFBTypeStr[fInvalidateFBType]); + writer->appendString("Map Buffer Type", kMapBufferTypeStr[fMapBufferType]); + writer->appendS32("Max FS Uniform Vectors", fMaxFragmentUniformVectors); + writer->appendBool("Pack Flip Y support", fPackFlipYSupport); + + writer->appendBool("Texture Usage support", fTextureUsageSupport); + writer->appendBool("GL_ARB_imaging support", fImagingSupport); + writer->appendBool("Vertex array object support", fVertexArrayObjectSupport); + writer->appendBool("Debug support", fDebugSupport); + writer->appendBool("Draw indirect support", fDrawIndirectSupport); + writer->appendBool("Multi draw indirect support", fMultiDrawIndirectSupport); + writer->appendBool("Base instance support", fBaseInstanceSupport); + writer->appendBool("RGBA 8888 pixel ops are slow", fRGBA8888PixelsOpsAreSlow); + writer->appendBool("Partial FBO read is slow", fPartialFBOReadIsSlow); + writer->appendBool("Bind uniform location support", fBindUniformLocationSupport); + writer->appendBool("Rectangle texture support", fRectangleTextureSupport); + writer->appendBool("BGRA to RGBA readback conversions are slow", + fRGBAToBGRAReadbackConversionsAreSlow); + writer->appendBool("Use buffer data null hint", fUseBufferDataNullHint); + writer->appendBool("Clear texture support", fClearTextureSupport); + writer->appendBool("Program binary support", fProgramBinarySupport); + writer->appendBool("Program parameters support", fProgramParameterSupport); + writer->appendBool("Sampler object support", fSamplerObjectSupport); + writer->appendBool("Tiled rendering support", fTiledRenderingSupport); + writer->appendBool("FB fetch requires enable per sample", fFBFetchRequiresEnablePerSample); + writer->appendBool("sRGB Write Control", fSRGBWriteControl); + + writer->appendBool("Intermediate texture for partial updates of unorm textures ever bound to FBOs", + fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO); + writer->appendBool("Intermediate texture for all updates of textures bound to FBOs", + fUseDrawInsteadOfAllRenderTargetWrites); + writer->appendBool("Max instances per draw without crashing (or zero)", + fMaxInstancesPerDrawWithoutCrashing); + + writer->beginArray("formats"); + + for (int i = 0; i < kGrGLFormatCount; ++i) { + writer->beginObject(nullptr, false); + writer->appendHexU32("flags", fFormatTable[i].fFlags); + writer->appendHexU32("f_type", (uint32_t)fFormatTable[i].fFormatType); + writer->appendHexU32("c_internal", fFormatTable[i].fCompressedInternalFormat); + writer->appendHexU32("i_for_teximage", fFormatTable[i].fInternalFormatForTexImageOrStorage); + writer->appendHexU32("i_for_renderbuffer", fFormatTable[i].fInternalFormatForRenderbuffer); + writer->appendHexU32("default_ex_format", fFormatTable[i].fDefaultExternalFormat); + writer->appendHexU32("default_ex_type", fFormatTable[i].fDefaultExternalType); + writer->appendHexU64("bpp", fFormatTable[i].fBytesPerPixel); + + writer->beginArray("surface color types"); + for (int j = 0; j < fFormatTable[i].fColorTypeInfoCount; ++j) { + const auto& ctInfo = fFormatTable[i].fColorTypeInfos[j]; + writer->beginObject(nullptr, false); + writer->appendHexU32("colorType", (uint32_t)ctInfo.fColorType); + writer->appendHexU32("flags", ctInfo.fFlags); + + writer->beginArray("data color types"); + for (int k = 0; k < ctInfo.fExternalIOFormatCount; ++k) { + const auto& ioInfo = ctInfo.fExternalIOFormats[k]; + writer->beginObject(nullptr, false); + writer->appendHexU32("colorType", (uint32_t)ioInfo.fColorType); + writer->appendHexU32("ex_type", ioInfo.fExternalType); + writer->appendHexU32("ex_teximage", ioInfo.fExternalTexImageFormat); + writer->appendHexU32("ex_read", ioInfo.fExternalReadFormat); + writer->endObject(); + } + writer->endArray(); + writer->endObject(); + } + writer->endArray(); + writer->endObject(); + } + + writer->endArray(); + writer->endObject(); +} +#else +void GrGLCaps::onDumpJSON(SkJSONWriter* writer) const { } +#endif + +void GrGLCaps::getTexImageExternalFormatAndType(GrGLFormat surfaceFormat, GrGLenum* externalFormat, + GrGLenum* externalType) const { + const auto& info = this->getFormatInfo(surfaceFormat); + *externalType = info.fDefaultExternalType; + *externalFormat = info.fDefaultExternalFormat; +} + +void GrGLCaps::getTexSubImageZeroFormatTypeAndBpp(GrGLFormat format, GrGLenum* externalFormat, + GrGLenum* externalType, size_t* bpp) const { + const auto& info = this->getFormatInfo(format); + *externalType = info.fDefaultExternalType; + *externalFormat = info.fDefaultExternalFormat; + *bpp = info.fBytesPerPixel; +} + +void GrGLCaps::getTexSubImageExternalFormatAndType(GrGLFormat surfaceFormat, + GrColorType surfaceColorType, + GrColorType memoryColorType, + GrGLenum* externalFormat, + GrGLenum* externalType) const { + this->getExternalFormat(surfaceFormat, surfaceColorType, memoryColorType, + kTexImage_ExternalFormatUsage, externalFormat, externalType); +} + +void GrGLCaps::getReadPixelsFormat(GrGLFormat surfaceFormat, GrColorType surfaceColorType, + GrColorType memoryColorType, GrGLenum* externalFormat, + GrGLenum* externalType) const { + this->getExternalFormat(surfaceFormat, surfaceColorType, memoryColorType, + kReadPixels_ExternalFormatUsage, externalFormat, externalType); +} + +void GrGLCaps::getExternalFormat(GrGLFormat surfaceFormat, GrColorType surfaceColorType, + GrColorType memoryColorType, ExternalFormatUsage usage, + GrGLenum* externalFormat, GrGLenum* externalType) const { + SkASSERT(externalFormat && externalType); + *externalFormat = this->getFormatInfo(surfaceFormat).externalFormat( + surfaceColorType, memoryColorType, usage); + *externalType = this->getFormatInfo(surfaceFormat).externalType( + surfaceColorType, memoryColorType); +} + +void GrGLCaps::setStencilFormatIndexForFormat(GrGLFormat format, int index) { + SkASSERT(!this->hasStencilFormatBeenDeterminedForFormat(format)); + this->getFormatInfo(format).fStencilFormatIndex = + index < 0 ? FormatInfo::kUnsupported_StencilFormatIndex : index; +} + +void GrGLCaps::setColorTypeFormat(GrColorType colorType, GrGLFormat format) { + int idx = static_cast(colorType); + SkASSERT(fColorTypeToFormatTable[idx] == GrGLFormat::kUnknown); + fColorTypeToFormatTable[idx] = format; +} + +void GrGLCaps::initFormatTable(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli, + const FormatWorkarounds& formatWorkarounds) { + GrGLStandard standard = ctxInfo.standard(); + // standard can be unused (optimized away) if SK_ASSUME_GL_ES is set + sk_ignore_unused_variable(standard); + GrGLVersion version = ctxInfo.version(); + + uint32_t nonMSAARenderFlags = FormatInfo::kFBOColorAttachment_Flag; + uint32_t msaaRenderFlags = nonMSAARenderFlags; + if (kNone_MSFBOType != fMSFBOType) { + msaaRenderFlags |= FormatInfo::kFBOColorAttachmentWithMSAA_Flag; + } + + bool texStorageSupported = false; + if (GR_IS_GR_GL(standard)) { + // The EXT version can apply to either GL or GLES. + texStorageSupported = version >= GR_GL_VER(4,2) || + ctxInfo.hasExtension("GL_ARB_texture_storage") || + ctxInfo.hasExtension("GL_EXT_texture_storage"); + } else if (GR_IS_GR_GL_ES(standard)) { + texStorageSupported = version >= GR_GL_VER(3,0) || + ctxInfo.hasExtension("GL_EXT_texture_storage"); + } else if (GR_IS_GR_WEBGL(standard)) { + texStorageSupported = version >= GR_GL_VER(2,0); + } + if (fDriverBugWorkarounds.disable_texture_storage) { + texStorageSupported = false; + } +#ifdef SK_BUILD_FOR_ANDROID + // crbug.com/945506. Telemetry reported a memory usage regression for Android Go Chrome/WebView + // when using glTexStorage2D. This appears to affect OOP-R (so not just over command buffer). + if (!formatWorkarounds.fDontDisableTexStorageOnAndroid) { + texStorageSupported = false; + } +#endif + + // ES 2.0 requires that the internal/external formats match so we can't use sized internal + // formats for glTexImage until ES 3.0. TODO: Support sized internal formats in WebGL2. + bool texImageSupportsSizedInternalFormat = + (GR_IS_GR_GL(standard) || (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3,0))); + + // for now we don't support floating point MSAA on ES + uint32_t fpRenderFlags = (GR_IS_GR_GL(standard)) ? msaaRenderFlags : nonMSAARenderFlags; + + for (int i = 0; i < kGrColorTypeCnt; ++i) { + fColorTypeToFormatTable[i] = GrGLFormat::kUnknown; + } + + /////////////////////////////////////////////////////////////////////////// + + GrGLenum halfFloatType = GR_GL_HALF_FLOAT; + if ((GR_IS_GR_GL_ES(standard) && version < GR_GL_VER(3, 0)) || + (GR_IS_GR_WEBGL(standard) && version < GR_GL_VER(2, 0))) { + halfFloatType = GR_GL_HALF_FLOAT_OES; + } + + // Format: RGBA8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGBA8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_RGBA8; + info.fDefaultExternalFormat = GR_GL_RGBA; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 4; + info.fFlags = FormatInfo::kTexturable_Flag; + if (GR_IS_GR_GL(standard)) { + info.fFlags |= msaaRenderFlags; + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") || + ctxInfo.hasExtension("GL_ARM_rgba8")) { + info.fFlags |= msaaRenderFlags; + } + } else if (GR_IS_GR_WEBGL(standard)) { + info.fFlags |= msaaRenderFlags; + } + + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGBA8; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGBA8 : GR_GL_RGBA; + } + + bool supportsBGRAColorType = GR_IS_GR_GL(standard) && + (version >= GR_GL_VER(1, 2) || ctxInfo.hasExtension("GL_EXT_bgra")); + info.fColorTypeInfoCount = supportsBGRAColorType ? 3 : 2; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA8, Surface: kRGBA_8888 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_8888; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRGBA_8888, GrGLFormat::kRGBA8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 1; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGBA8, Surface: kRGBA_8888, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + + // Format: RGBA8, Surface: kBGRA_8888 + if (supportsBGRAColorType) { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kBGRA_8888; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kBGRA_8888, GrGLFormat::kRGBA8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGBA8, Surface: kBGRA_8888, Data: kBGRA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kBGRA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_BGRA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGBA8, Surface: kBGRA_8888, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + + // Format: RGBA8, Surface: kRGB_888x + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGB_888x; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RGB1(); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 1; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGBA8, Surface: kRGB_888x, Data: kRGBA_888x + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGB_888x; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + + // Format: R8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kR8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_R8; + info.fDefaultExternalFormat = GR_GL_RED; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 1; + bool r8Support = false; + if (GR_IS_GR_GL(standard)) { + r8Support = version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_rg"); + } else if (GR_IS_GR_GL_ES(standard)) { + r8Support = version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_texture_rg"); + } else if (GR_IS_GR_WEBGL(standard)) { + r8Support = ctxInfo.version() >= GR_GL_VER(2, 0); + } + + if (r8Support) { + info.fFlags |= FormatInfo::kTexturable_Flag | msaaRenderFlags; + } + + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_R8; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_R8 : GR_GL_RED; + } + + if (r8Support) { + info.fColorTypeInfoCount = 2; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: R8, Surface: kAlpha_8 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + this->setColorTypeFormat(GrColorType::kAlpha_8, GrGLFormat::kR8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: R8, Surface: kAlpha_8, Data: kAlpha_8 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_8; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_RED; + ioFormat.fExternalReadFormat = 0; + } + + // Format: R8, Surface: kAlpha_8, Data: kAlpha_8xxx + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_8xxx; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + + // Format: R8, Surface: kGray_8 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kGray_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle("rrr1"); + this->setColorTypeFormat(GrColorType::kGray_8, GrGLFormat::kR8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: R8, Surface: kGray_8, Data: kGray_8 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kGray_8; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_RED; + ioFormat.fExternalReadFormat = 0; + } + + // Format: R8, Surface: kGray_8, Data: kGray_8xxx + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kGray_8xxx; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: ALPHA8 + { + bool alpha8IsValidForGL = GR_IS_GR_GL(standard) && + (!fIsCoreProfile || version <= GR_GL_VER(3, 0)); + bool alpha8IsValidForGLES = GR_IS_GR_GL_ES(standard); + bool alpha8IsValidForWebGL = GR_IS_GR_WEBGL(standard); + + FormatInfo& info = this->getFormatInfo(GrGLFormat::kALPHA8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + // GL_EXT_texture_storage adds GL_ALPHA8 for texture storage. However, ES3 has glTexStorage + // but does not have GL_ALPHA8 (and requires a sized internal format for glTexStorage). + // WebGL never has GL_ALPHA8. + bool alpha8SizedEnumSupported = + alpha8IsValidForGL || + (alpha8IsValidForGLES && ctxInfo.hasExtension("GL_EXT_texture_storage")); + bool alpha8TexStorageSupported = alpha8SizedEnumSupported && texStorageSupported; + + bool alpha8IsRenderable = false; + if (alpha8IsValidForGL) { + // Core profile removes ALPHA8 support. + // OpenGL 3.0+ (and GL_ARB_framebuffer_object) supports ALPHA8 as renderable. + alpha8IsRenderable = ctxInfo.version() >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_ARB_framebuffer_object"); + } + info.fInternalFormatForRenderbuffer = GR_GL_ALPHA8; + info.fDefaultExternalFormat = GR_GL_ALPHA; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 1; + if (alpha8IsValidForGL || alpha8IsValidForGLES || alpha8IsValidForWebGL) { + info.fFlags = FormatInfo::kTexturable_Flag; + } + if (alpha8IsRenderable && alpha8IsValidForGL) { + // We will use ALPHA8 to create MSAA renderbuffers. + SkASSERT(alpha8SizedEnumSupported); + info.fFlags |= msaaRenderFlags; + } + if (alpha8TexStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_ALPHA8; + } else { + // Even if GL_ALPHA8 is added to ES by GL_EXT_texture_storage it doesn't become legal + // for glTexImage2D. + if (!GR_IS_GR_GL_ES(standard) && texImageSupportsSizedInternalFormat && + alpha8SizedEnumSupported) { + info.fInternalFormatForTexImageOrStorage = GR_GL_ALPHA8; + } else { + info.fInternalFormatForTexImageOrStorage = GR_GL_ALPHA; + } + } + + if (alpha8IsValidForGL || alpha8IsValidForGLES || alpha8IsValidForWebGL) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: ALPHA8, Surface: kAlpha_8 + { + if (alpha8IsValidForGL || alpha8IsValidForGLES || alpha8IsValidForWebGL) { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | + ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::AAAA(); + int idx = static_cast(GrColorType::kAlpha_8); + if (fColorTypeToFormatTable[idx] == GrGLFormat::kUnknown) { + this->setColorTypeFormat(GrColorType::kAlpha_8, GrGLFormat::kALPHA8); + } + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: ALPHA8, Surface: kAlpha_8, Data: kAlpha_8 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_8; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_ALPHA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: ALPHA8, Surface: kAlpha_8, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + } + + // Format: LUMINANCE8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kLUMINANCE8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_LUMINANCE8; + info.fDefaultExternalFormat = GR_GL_LUMINANCE; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 1; + bool lum8Supported = false; + bool lum8SizedFormatSupported = false; + if (GR_IS_GR_GL(standard) && !fIsCoreProfile) { + lum8Supported = true; + lum8SizedFormatSupported = true; + } else if (GR_IS_GR_GL_ES(standard)) { + lum8Supported = true; + // Even on ES3 this extension is required to define LUMINANCE8. + lum8SizedFormatSupported = ctxInfo.hasExtension("GL_EXT_texture_storage"); + } else if (GR_IS_GR_WEBGL(standard)) { + lum8Supported = true; + } + if (lum8Supported) { + info.fFlags = FormatInfo::kTexturable_Flag; + } + if (texStorageSupported && lum8SizedFormatSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_LUMINANCE8; + } else if (texImageSupportsSizedInternalFormat && lum8SizedFormatSupported) { + info.fInternalFormatForTexImageOrStorage = GR_GL_LUMINANCE8; + } else { + info.fInternalFormatForTexImageOrStorage = GR_GL_LUMINANCE; + } + // We are not enabling attaching to an FBO for LUMINANCE8 mostly because of confusion in the + // spec. For GLES it does not seem to ever support LUMINANCE8 being color-renderable. For GL + // versions less than 3.0 it is provided by GL_ARB_framebuffer_object. However, the original + // version of that extension did not add LUMINANCE8, but was added in a later revsion. So + // even the presence of that extension does not guarantee support. GL 3.0 and higher (core + // or compatibility) do not list LUMINANCE8 as color-renderable (which is strange since the + // GL_ARB_framebuffer_object extension was meant to bring 3.0 functionality to lower + // versions). + + if (lum8Supported) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: LUMINANCE8, Surface: kGray_8 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kGray_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + int idx = static_cast(GrColorType::kGray_8); + if (fColorTypeToFormatTable[idx] == GrGLFormat::kUnknown) { + this->setColorTypeFormat(GrColorType::kGray_8, GrGLFormat::kLUMINANCE8); + } + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: LUMINANCE8, Surface: kGray_8, Data: kGray_8 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kGray_8; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_LUMINANCE; + ioFormat.fExternalReadFormat = 0; + } + + // Format: LUMINANCE8, Surface: kGray_8, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: BGRA8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kBGRA8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + + // We currently only use the renderbuffer format when allocating msaa renderbuffers, so we + // are making decisions here based on that use case. The GL_EXT_texture_format_BGRA8888 + // extension adds BGRA color renderbuffer support for ES 2.0, but this does not guarantee + // support for MSAA renderbuffers. Additionally, the renderable support was added in a later + // revision of the extension. So it is possible for older drivers to support the extension + // but only an early revision of it without renderable support. We have no way of + // distinguishing between the two. The GL_APPLE_texture_format_BGRA8888 does not add support + // for BGRA color renderbuffers at all. Ideally, for both cases we would use RGBA8 for our + // format for the MSAA buffer. In the GL_EXT_texture_format_BGRA8888 case we can still + // make the resolve BGRA and which will work for glBlitFramebuffer for resolving which just + // requires the src and dst be bindable to FBOs. However, we can't do this in the current + // world since some devices (e.g. chromium & angle) require the formats in glBlitFramebuffer + // to match. We don't have a way to really check this during resolve since we only actually + // have one GrPixelConfig and one GrBackendFormat that is shared by the GrGLRenderTarget. + // Once we break those up into different surface we can revisit doing this change. + if (ctxInfo.hasExtension("GL_APPLE_texture_format_BGRA8888")) { + info.fInternalFormatForRenderbuffer = GR_GL_RGBA8; + } else { + info.fInternalFormatForRenderbuffer = GR_GL_BGRA8; + } + + info.fDefaultExternalFormat = GR_GL_BGRA; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 4; + + GrGLenum bgraTexImageFormat; + // If BGRA is supported as an internal format it must always be specified to glTex[Sub]Image + // as a base format. Which base format depends on which extension is used. + if (ctxInfo.hasExtension("GL_APPLE_texture_format_BGRA8888")) { + // GL_APPLE_texture_format_BGRA8888: + // ES 2.0: the extension makes BGRA an external format but not an internal format. + // ES 3.0: the extension explicitly states GL_BGRA8 is not a valid internal format + // for glTexImage (just for glTexStorage). + bgraTexImageFormat = GR_GL_RGBA; + } else { + // GL_EXT_texture_format_BGRA8888: + // This extension adds GL_BGRA as an unsized internal format. However, it is + // written against ES 2.0 and therefore doesn't define a GL_BGRA8 as ES 2.0 doesn't + // have sized internal formats. See later where we check for tex storage BGRA8 + // support. + bgraTexImageFormat = GR_GL_BGRA; + } + + // TexStorage requires using a sized internal format and BGRA8 is only supported if we have + // the GL_APPLE_texture_format_BGRA8888 extension or if we have GL_EXT_texture_storage and + // GL_EXT_texture_format_BGRA8888. + bool supportsBGRATexStorage = false; + + if (GR_IS_GR_GL_ES(standard)) { + if (ctxInfo.hasExtension("GL_EXT_texture_format_BGRA8888")) { + info.fFlags = FormatInfo::kTexturable_Flag | nonMSAARenderFlags; + // GL_EXT_texture storage has defined interactions with + // GL_EXT_texture_format_BGRA8888. However, ES3 supports glTexStorage but + // without GL_EXT_texture_storage it does not allow the BGRA8 sized internal format. + if (ctxInfo.hasExtension("GL_EXT_texture_storage") && + !formatWorkarounds.fDisableBGRATextureStorageForIntelWindowsES) { + supportsBGRATexStorage = true; + } + } else if (ctxInfo.hasExtension("GL_APPLE_texture_format_BGRA8888")) { + // This APPLE extension introduces complexity on ES2. It leaves the internal format + // as RGBA, but allows BGRA as the external format. From testing, it appears that + // the driver remembers the external format when the texture is created (with + // TexImage). If you then try to upload data in the other swizzle (with + // TexSubImage), it fails. We could work around this, but it adds even more state + // tracking to code that is already too tricky. Instead, we opt not to support BGRA + // on ES2 with this extension. This also side-steps some ambiguous interactions with + // the texture storage extension. + if (version >= GR_GL_VER(3,0)) { + // The APPLE extension doesn't explicitly make this renderable, but + // internally it appears to use RGBA8, which we'll patch up below. + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + supportsBGRATexStorage = true; + } + } + } + if (texStorageSupported && supportsBGRATexStorage) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_BGRA8; + } else { + info.fInternalFormatForTexImageOrStorage = bgraTexImageFormat; + } + + if (SkToBool(info.fFlags &FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: BGRA8, Surface: kBGRA_8888 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kBGRA_8888; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kBGRA_8888, GrGLFormat::kBGRA8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: BGRA8, Surface: kBGRA_8888, Data: kBGRA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kBGRA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_BGRA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: BGRA8, Surface: kBGRA_8888, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RGB565 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGB565); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_RGB565; + info.fDefaultExternalFormat = GR_GL_RGB; + info.fDefaultExternalType = GR_GL_UNSIGNED_SHORT_5_6_5; + info.fBytesPerPixel = 2; + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(4, 2) || ctxInfo.hasExtension("GL_ARB_ES2_compatibility")) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } + } else if (GR_IS_GR_GL_ES(standard)) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } else if (GR_IS_GR_WEBGL(standard)) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } + // 565 is not a sized internal format on desktop GL. So on desktop with + // 565 we always use an unsized internal format to let the system pick + // the best sized format to convert the 565 data to. Since TexStorage + // only allows sized internal formats we disallow it. + // + // TODO: As of 4.2, regular GL supports 565. This logic is due for an + // update. + if (texStorageSupported && GR_IS_GR_GL_ES(standard)) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGB565; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGB565 : GR_GL_RGB; + } + + if (SkToBool(info.fFlags &FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGB565, Surface: kBGR_565 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kBGR_565; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kBGR_565, GrGLFormat::kRGB565); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGB565, Surface: kBGR_565, Data: kBGR_565 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kBGR_565; + ioFormat.fExternalType = GR_GL_UNSIGNED_SHORT_5_6_5; + ioFormat.fExternalTexImageFormat = GR_GL_RGB; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGB565, Surface: kBGR_565, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RGBA16F + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGBA16F); + info.fFormatType = FormatType::kFloat; + info.fInternalFormatForRenderbuffer = GR_GL_RGBA16F; + info.fDefaultExternalFormat = GR_GL_RGBA; + info.fDefaultExternalType = halfFloatType; + info.fBytesPerPixel = 8; + bool rgba16FTextureSupport = false; + bool rgba16FRenderTargetSupport = false; + + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3, 0)) { + rgba16FTextureSupport = true; + rgba16FRenderTargetSupport = true; + } else if (ctxInfo.hasExtension("GL_ARB_texture_float")) { + rgba16FTextureSupport = true; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3, 0)) { + rgba16FTextureSupport = true; + rgba16FRenderTargetSupport = + version >= GR_GL_VER(3, 2) || + ctxInfo.hasExtension("GL_EXT_color_buffer_half_float") || + ctxInfo.hasExtension("GL_EXT_color_buffer_float"); + } else if (ctxInfo.hasExtension("GL_OES_texture_half_float") && + ctxInfo.hasExtension("GL_OES_texture_half_float_linear")) { + rgba16FTextureSupport = true; + rgba16FRenderTargetSupport = ctxInfo.hasExtension("GL_EXT_color_buffer_half_float"); + } + } else if (GR_IS_GR_WEBGL(standard)) { + if (version >= GR_GL_VER(2, 0)) { + rgba16FTextureSupport = true; + rgba16FRenderTargetSupport = + ctxInfo.hasExtension("GL_EXT_color_buffer_half_float") || + ctxInfo.hasExtension("EXT_color_buffer_half_float") || + ctxInfo.hasExtension("GL_EXT_color_buffer_float") || + ctxInfo.hasExtension("EXT_color_buffer_float"); + } else if ((ctxInfo.hasExtension("GL_OES_texture_half_float") || + ctxInfo.hasExtension("OES_texture_half_float")) && + (ctxInfo.hasExtension("GL_OES_texture_half_float_linear") || + ctxInfo.hasExtension("OES_texture_half_float_linear"))) { + rgba16FTextureSupport = true; + // We don't check for EXT_color_buffer_float as it's only defined for WebGL 2. + rgba16FRenderTargetSupport = + ctxInfo.hasExtension("GL_EXT_color_buffer_half_float") || + ctxInfo.hasExtension("EXT_color_buffer_half_float"); + } + } + + if (rgba16FTextureSupport) { + info.fFlags = FormatInfo::kTexturable_Flag; + if (rgba16FRenderTargetSupport) { + info.fFlags |= fpRenderFlags; + } + } + if (texStorageSupported && !formatWorkarounds.fDisableRGBA16FTexStorageForCrBug1008003) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGBA16F; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGBA16F : GR_GL_RGBA; + } + + if (rgba16FTextureSupport) { + uint32_t flags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + + info.fColorTypeInfoCount = 2; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA16F, Surface: kRGBA_F16 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_F16; + ctInfo.fFlags = flags; + this->setColorTypeFormat(GrColorType::kRGBA_F16, GrGLFormat::kRGBA16F); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGBA16F, Surface: kRGBA_F16, Data: kRGBA_F16 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_F16; + ioFormat.fExternalType = halfFloatType; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGBA16F, Surface: kRGBA_F16, Data: kRGBA_F32 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_F32; + ioFormat.fExternalType = GR_GL_FLOAT; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + + // Format: RGBA16F, Surface: kRGBA_F16_Clamped + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_F16_Clamped; + ctInfo.fFlags = flags; + this->setColorTypeFormat(GrColorType::kRGBA_F16_Clamped, GrGLFormat::kRGBA16F); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGBA16F, Surface: kRGBA_F16_Clamped, Data: kRGBA_F16_Clamped + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_F16_Clamped; + ioFormat.fExternalType = halfFloatType; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGBA16F, Surface: kRGBA_F16_Clamped, Data: kRGBA_F32 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_F32; + ioFormat.fExternalType = GR_GL_FLOAT; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: R16F + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kR16F); + info.fFormatType = FormatType::kFloat; + info.fInternalFormatForRenderbuffer = GR_GL_R16F; + info.fDefaultExternalFormat = GR_GL_RED; + info.fDefaultExternalType = halfFloatType; + info.fBytesPerPixel = 2; + bool r16FTextureSupport = false; + bool r16FRenderTargetSupport = false; + + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_rg")) { + r16FTextureSupport = true; + r16FRenderTargetSupport = true; + } + } else if (GR_IS_GR_GL_ES(standard)) { + // It seems possible that a combination of GL_EXT_texture_rg and + // GL_EXT_color_buffer_half_float might add this format to ES 2.0 but it is not entirely + // clear. The latter mentions interaction but that may only be for renderbuffers as + // neither adds the texture format explicitly. + // GL_OES_texture_format_half_float makes no reference to RED formats. + if (version >= GR_GL_VER(3, 0)) { + r16FTextureSupport = true; + r16FRenderTargetSupport = version >= GR_GL_VER(3, 2) || + ctxInfo.hasExtension("GL_EXT_color_buffer_float") || + ctxInfo.hasExtension("GL_EXT_color_buffer_half_float"); + } + } else if (GR_IS_GR_WEBGL(standard)) { + if (version >= GR_GL_VER(2, 0)) { + r16FTextureSupport = true; + r16FRenderTargetSupport = ctxInfo.hasExtension("GL_EXT_color_buffer_float") || + ctxInfo.hasExtension("EXT_color_buffer_float"); + } + } + + if (r16FTextureSupport) { + info.fFlags = FormatInfo::kTexturable_Flag; + if (r16FRenderTargetSupport) { + info.fFlags |= fpRenderFlags; + } + } + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_R16F; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_R16F : GR_GL_RED; + } + + if (r16FTextureSupport) { + // Format: R16F, Surface: kAlpha_F16 + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_F16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + this->setColorTypeFormat(GrColorType::kAlpha_F16, GrGLFormat::kR16F); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: R16F, Surface: kAlpha_F16, Data: kAlpha_F16 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_F16; + ioFormat.fExternalType = halfFloatType; + ioFormat.fExternalTexImageFormat = GR_GL_RED; + ioFormat.fExternalReadFormat = 0; + } + + // Format: R16F, Surface: kAlpha_F16, Data: kAlpha_F32xxx + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_F32xxx; + ioFormat.fExternalType = GR_GL_FLOAT; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: LUMINANCE16F + { + // NOTE: We disallow lum16f on ES devices if linear filtering modes are not + // supported. This is for simplicity, but a more granular approach is possible. + bool lum16FSupported = false; + bool lum16FSizedFormatSupported = false; + if (GR_IS_GR_GL(standard)) { + if (!fIsCoreProfile && ctxInfo.hasExtension("GL_ARB_texture_float")) { + lum16FSupported = true; + lum16FSizedFormatSupported = true; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (ctxInfo.hasExtension("GL_OES_texture_half_float_linear") && + ctxInfo.hasExtension("GL_OES_texture_half_float")) { + lum16FSupported = true; + // Even on ES3 this extension is required to define LUMINANCE16F. + lum16FSizedFormatSupported = ctxInfo.hasExtension("GL_EXT_texture_storage"); + } + } // No WebGL support + + if (formatWorkarounds.fDisableLuminance16F) { + lum16FSupported = false; + } + + FormatInfo& info = this->getFormatInfo(GrGLFormat::kLUMINANCE16F); + info.fFormatType = FormatType::kFloat; + info.fInternalFormatForRenderbuffer = GR_GL_LUMINANCE16F; + info.fDefaultExternalFormat = GR_GL_LUMINANCE; + info.fDefaultExternalType = halfFloatType; + info.fBytesPerPixel = 2; + + if (lum16FSupported) { + info.fFlags = FormatInfo::kTexturable_Flag; + + if (texStorageSupported && lum16FSizedFormatSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_LUMINANCE16F; + } else if (texImageSupportsSizedInternalFormat && lum16FSizedFormatSupported) { + info.fInternalFormatForTexImageOrStorage = GR_GL_LUMINANCE16F; + } else { + info.fInternalFormatForTexImageOrStorage = GR_GL_LUMINANCE; + } + + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: LUMINANCE16F, Surface: kAlpha_F16 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_F16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + + int idx = static_cast(GrColorType::kAlpha_F16); + if (fColorTypeToFormatTable[idx] == GrGLFormat::kUnknown) { + this->setColorTypeFormat(GrColorType::kAlpha_F16, GrGLFormat::kLUMINANCE16F); + } + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: LUMINANCE16F, Surface: kAlpha_F16, Data: kAlpha_F16 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_F16; + ioFormat.fExternalType = halfFloatType; + ioFormat.fExternalTexImageFormat = GR_GL_LUMINANCE; + ioFormat.fExternalReadFormat = 0; + } + + // Format: LUMINANCE16F, Surface: kAlpha_F16, Data: kRGBA_F32 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_F32; + ioFormat.fExternalType = GR_GL_FLOAT; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RGB8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGB8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_RGB8; + info.fDefaultExternalFormat = GR_GL_RGB; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 4; // We assume the GPU stores this format 4 byte aligned + info.fFlags = FormatInfo::kTexturable_Flag; + if (GR_IS_GR_GL(standard)) { + // Even in OpenGL 4.6 GL_RGB8 is required to be color renderable but not required to be + // a supported render buffer format. Since we usually use render buffers for MSAA on + // non-ES GL we don't support MSAA for GL_RGB8. On 4.2+ we could check using + // glGetInternalFormativ(GL_RENDERBUFFER, GL_RGB8, GL_INTERNALFORMAT_SUPPORTED, ...) if + // this becomes an issue. + // This also would probably work in mixed-samples mode where there is no MSAA color + // buffer but we don't support that just for simplicity's sake. + info.fFlags |= nonMSAARenderFlags; + } else if (GR_IS_GR_GL_ES(standard)) { + // 3.0 and the extension support this as a render buffer format. + if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8")) { + info.fFlags |= msaaRenderFlags; + } + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL seems to support RBG8 + info.fFlags |= msaaRenderFlags; + } + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGB8; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGB8 : GR_GL_RGB; + } + if (formatWorkarounds.fDisableRGB8ForMali400) { + info.fFlags = 0; + } + + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGB8, Surface: kRGB_888x + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGB_888x; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRGB_888x, GrGLFormat::kRGB8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGB8, Surface: kRGB_888x, Data: kRGB_888x + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGB_888x; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + // This is technically the wrong format to use for this color type since the color + // type is 4 bytes but the format is 3. However, we don't currently upload data of + // this type so the format is only used when creating an empty texture. If we want + // to support uploading data we should add in RGB_888 GrColorType. Additionally, on + // the FormatInfo we should have a default format to use when we want to create an + // empty texture. + ioFormat.fExternalTexImageFormat = GR_GL_RGB; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGB8, Surface: kRGB_888x, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + + // Format: RG8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRG8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_RG8; + info.fDefaultExternalFormat = GR_GL_RG; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 2; + bool rg8Support = false; + if (GR_IS_GR_GL(standard)) { + rg8Support = version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_rg"); + } else if (GR_IS_GR_GL_ES(standard)) { + rg8Support = version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_texture_rg"); + } else if (GR_IS_GR_WEBGL(standard)) { + rg8Support = version >= GR_GL_VER(2, 0); + } + if (rg8Support) { + info.fFlags |= FormatInfo::kTexturable_Flag | msaaRenderFlags; + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RG8; + } + } + if (!(info.fFlags & FormatInfo::kUseTexStorage_Flag)) { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RG8 : GR_GL_RG; + } + if (rg8Support) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RG8, Surface: kRG_88 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRG_88; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRG_88, GrGLFormat::kRG8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RG8, Surface: kRG_88, Data: kRG_88 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRG_88; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = GR_GL_RG; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RG8, Surface: kRG_88, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RGB10_A2 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGB10_A2); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_RGB10_A2; + info.fDefaultExternalFormat = GR_GL_RGBA; + info.fDefaultExternalType = GR_GL_UNSIGNED_INT_2_10_10_10_REV; + info.fBytesPerPixel = 4; + if (GR_IS_GR_GL(standard) || + (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3, 0))) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } else if (GR_IS_GR_GL_ES(standard) && + ctxInfo.hasExtension("GL_EXT_texture_type_2_10_10_10_REV")) { + info.fFlags = FormatInfo::kTexturable_Flag; + } // No WebGL support + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGB10_A2; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGB10_A2 : GR_GL_RGBA; + } + + if (SkToBool(info.fFlags &FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGB10_A2, Surface: kRGBA_1010102 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_1010102; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRGBA_1010102, GrGLFormat::kRGB10_A2); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGB10_A2, Surface: kRGBA_1010102, Data: kRGBA_1010102 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_1010102; + ioFormat.fExternalType = GR_GL_UNSIGNED_INT_2_10_10_10_REV; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGB10_A2, Surface: kRGBA_1010102, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RGBA4 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGBA4); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_RGBA4; + info.fDefaultExternalFormat = GR_GL_RGBA; + info.fDefaultExternalType = GR_GL_UNSIGNED_SHORT_4_4_4_4; + info.fBytesPerPixel = 2; + info.fFlags = FormatInfo::kTexturable_Flag; + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(4, 2)) { + info.fFlags |= msaaRenderFlags; + } + } else if (GR_IS_GR_GL_ES(standard)) { + info.fFlags |= msaaRenderFlags; + } else if (GR_IS_GR_WEBGL(standard)) { + info.fFlags |= msaaRenderFlags; + } + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGBA4; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGBA4 : GR_GL_RGBA; + } + + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA4, Surface: kABGR_4444 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kABGR_4444; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kABGR_4444, GrGLFormat::kRGBA4); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: RGBA4, Surface: kABGR_4444, Data: kABGR_4444 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kABGR_4444; + ioFormat.fExternalType = GR_GL_UNSIGNED_SHORT_4_4_4_4; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: RGBA4, Surface: kABGR_4444, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + + // Format: SRGB8_ALPHA8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kSRGB8_ALPHA8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_SRGB8_ALPHA8; + info.fDefaultExternalType = GR_GL_UNSIGNED_BYTE; + info.fBytesPerPixel = 4; + + // We may modify the default external format below. + info.fDefaultExternalFormat = GR_GL_RGBA; + bool srgb8Alpha8TexStorageSupported = texStorageSupported; + bool srgb8Alpha8TextureSupport = false; + bool srgb8Alpha8RenderTargetSupport = false; + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3, 0)) { + srgb8Alpha8TextureSupport = true; + srgb8Alpha8RenderTargetSupport = true; + } else if (ctxInfo.hasExtension("GL_EXT_texture_sRGB")) { + srgb8Alpha8TextureSupport = true; + if (ctxInfo.hasExtension("GL_ARB_framebuffer_sRGB") || + ctxInfo.hasExtension("GL_EXT_framebuffer_sRGB")) { + srgb8Alpha8RenderTargetSupport = true; + } + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_sRGB")) { + srgb8Alpha8TextureSupport = true; + srgb8Alpha8RenderTargetSupport = true; + } + if (version < GR_GL_VER(3, 0)) { + // ES 2.0 requires that the external format matches the internal format. + info.fDefaultExternalFormat = GR_GL_SRGB_ALPHA; + // There is no defined interaction between GL_EXT_sRGB and GL_EXT_texture_storage. + srgb8Alpha8TexStorageSupported = false; + } + } else if (GR_IS_GR_WEBGL(standard)) { + // sRGB extension should be on most WebGL 1.0 contexts, although sometimes under 2 + // names. + if (version >= GR_GL_VER(2, 0) || ctxInfo.hasExtension("GL_EXT_sRGB") || + ctxInfo.hasExtension("EXT_sRGB")) { + srgb8Alpha8TextureSupport = true; + srgb8Alpha8RenderTargetSupport = true; + } + if (version < GR_GL_VER(2, 0)) { + // WebGL 1.0 requires that the external format matches the internal format. + info.fDefaultExternalFormat = GR_GL_SRGB_ALPHA; + // There is no extension to WebGL 1 that adds glTexStorage. + SkASSERT(!srgb8Alpha8TexStorageSupported); + } + } + + if (srgb8Alpha8TextureSupport) { + info.fFlags = FormatInfo::kTexturable_Flag; + if (srgb8Alpha8RenderTargetSupport) { + info.fFlags |= formatWorkarounds.fDisableSRGBRenderWithMSAAForMacAMD + ? nonMSAARenderFlags + : msaaRenderFlags; + } + } + if (srgb8Alpha8TexStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_SRGB8_ALPHA8; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_SRGB8_ALPHA8 : GR_GL_SRGB_ALPHA; + } + + if (srgb8Alpha8TextureSupport) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: SRGB8_ALPHA8, Surface: kRGBA_8888_SRGB + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_8888_SRGB; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRGBA_8888_SRGB, GrGLFormat::kSRGB8_ALPHA8); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 1; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + + // Format: SRGB8_ALPHA8, Surface: kRGBA_8888_SRGB, Data: kRGBA_8888_SRGB + { + // GL does not do srgb<->rgb conversions when transferring between cpu and gpu. + // Thus, the external format is GL_RGBA. See below for note about ES2.0 and + // glTex[Sub]Image. + GrGLenum texImageExternalFormat = GR_GL_RGBA; + + // OpenGL ES 2.0 + GL_EXT_sRGB allows GL_SRGB_ALPHA to be specified as the + // param to Tex(Sub)Image. ES 2.0 requires the and + // params to match. Thus, on ES 2.0 we will use GL_SRGB_ALPHA as the + // param. On OpenGL and ES 3.0+ GL_SRGB_ALPHA does not work for the + // param to glTexImage. + if (GR_IS_GR_GL_ES(standard) && version == GR_GL_VER(2,0)) { + texImageExternalFormat = GR_GL_SRGB_ALPHA; + } + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888_SRGB; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = texImageExternalFormat; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: COMPRESSED_RGB8_ETC2 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kCOMPRESSED_RGB8_ETC2); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForTexImageOrStorage = GR_GL_COMPRESSED_RGB8_ETC2; + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(4, 3) || ctxInfo.hasExtension("GL_ARB_ES3_compatibility")) { + info.fFlags = FormatInfo::kTexturable_Flag; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (version >= GR_GL_VER(3, 0) || + ctxInfo.hasExtension("GL_OES_compressed_ETC2_RGB8_texture")) { + info.fFlags = FormatInfo::kTexturable_Flag; + } + } // No WebGL support + + // There are no support GrColorTypes for this format + } + + // Format: COMPRESSED_ETC1_RGB8 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kCOMPRESSED_ETC1_RGB8); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForTexImageOrStorage = GR_GL_COMPRESSED_ETC1_RGB8; + if (GR_IS_GR_GL_ES(standard)) { + if (ctxInfo.hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) { + info.fFlags = FormatInfo::kTexturable_Flag; + } + } // No GL or WebGL support + + // There are no support GrColorTypes for this format + } + + // Format: R16 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kR16); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForRenderbuffer = GR_GL_R16; + info.fDefaultExternalFormat = GR_GL_RED; + info.fDefaultExternalType = GR_GL_UNSIGNED_SHORT; + info.fBytesPerPixel = 2; + bool r16Supported = false; + if (GR_IS_GR_GL(standard)) { + r16Supported = version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_rg"); + } else if (GR_IS_GR_GL_ES(standard)) { + r16Supported = ctxInfo.hasExtension("GL_EXT_texture_norm16"); + } // No WebGL support + + if (r16Supported) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } + + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_R16; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_R16 : GR_GL_RED; + } + + if (r16Supported) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: R16, Surface: kAlpha_16 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + this->setColorTypeFormat(GrColorType::kAlpha_16, GrGLFormat::kR16); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: R16, Surface: kAlpha_16, Data: kAlpha_16 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_16; + ioFormat.fExternalType = GR_GL_UNSIGNED_SHORT; + ioFormat.fExternalTexImageFormat = GR_GL_RED; + ioFormat.fExternalReadFormat = 0; + } + + // Format: R16, Surface: kAlpha_16, Data: kAlpha_8xxx + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kAlpha_8xxx; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RG16 + { + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRG16); + info.fFormatType = FormatType::kNormalizedFixedPoint; + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RG16 : GR_GL_RG; + info.fInternalFormatForRenderbuffer = GR_GL_RG16; + info.fDefaultExternalFormat = GR_GL_RG; + info.fDefaultExternalType = GR_GL_UNSIGNED_SHORT; + info.fBytesPerPixel = 4; + bool rg16Supported = false; + if (GR_IS_GR_GL(standard)) { + rg16Supported = version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_rg"); + } else if (GR_IS_GR_GL_ES(standard)) { + rg16Supported = ctxInfo.hasExtension("GL_EXT_texture_norm16"); + } // No WebGL support + + if (rg16Supported) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } + + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RG16; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RG16 : GR_GL_RG; + } + + if (rg16Supported) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: GR_GL_RG16, Surface: kRG_1616 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRG_1616; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRG_1616, GrGLFormat::kRG16); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: GR_GL_RG16, Surface: kRG_1616, Data: kRG_1616 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRG_1616; + ioFormat.fExternalType = GR_GL_UNSIGNED_SHORT; + ioFormat.fExternalTexImageFormat = GR_GL_RG; + ioFormat.fExternalReadFormat = 0; + } + + // Format: GR_GL_RG16, Surface: kRG_1616, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format: RGBA16 + { + bool rgba16Support = false; + if (GR_IS_GR_GL(standard)) { + rgba16Support = version >= GR_GL_VER(3, 0); + } else if (GR_IS_GR_GL_ES(standard)) { + rgba16Support = ctxInfo.hasExtension("GL_EXT_texture_norm16"); + } // No WebGL support + + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRGBA16); + info.fFormatType = FormatType::kNormalizedFixedPoint; + + info.fInternalFormatForRenderbuffer = GR_GL_RGBA16; + info.fDefaultExternalFormat = GR_GL_RGBA; + info.fDefaultExternalType = GR_GL_UNSIGNED_SHORT; + info.fBytesPerPixel = 8; + if (rgba16Support) { + info.fFlags = FormatInfo::kTexturable_Flag | msaaRenderFlags; + } + + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RGBA16; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RGBA16 : GR_GL_RGBA; + } + + if (rgba16Support) { + // Format: GR_GL_RGBA16, Surface: kRGBA_16161616 + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_16161616; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRGBA_16161616, GrGLFormat::kRGBA16); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: GR_GL_RGBA16, Surface: kRGBA_16161616, Data: kRGBA_16161616 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_16161616; + ioFormat.fExternalType = GR_GL_UNSIGNED_SHORT; + ioFormat.fExternalTexImageFormat = GR_GL_RGBA; + ioFormat.fExternalReadFormat = 0; + } + + // Format: GR_GL_RGBA16, Surface: kRGBA_16161616, Data: kRGBA_8888 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_8888; + ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + // Format:RG16F + { + bool rg16FTextureSupport = false; + bool rg16FRenderTargetSupport = false; + if (GR_IS_GR_GL(standard)) { + if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_float")) { + rg16FTextureSupport = true; + rg16FRenderTargetSupport = true; + } + } else if (GR_IS_GR_GL_ES(standard)) { + // It seems possible that a combination of GL_EXT_texture_rg and + // GL_EXT_color_buffer_half_float might add this format to ES 2.0 but it is not entirely + // clear. The latter mentions interaction but that may only be for renderbuffers as + // neither adds the texture format explicitly. + // GL_OES_texture_format_half_float makes no reference to RG formats. + if (version >= GR_GL_VER(3, 0)) { + rg16FTextureSupport = true; + rg16FRenderTargetSupport = version >= GR_GL_VER(3, 2) || + ctxInfo.hasExtension("GL_EXT_color_buffer_float") || + ctxInfo.hasExtension("GL_EXT_color_buffer_half_float"); + } + } else if (GR_IS_GR_WEBGL(standard)) { + if (version >= GR_GL_VER(2, 0)) { + rg16FTextureSupport = true; + rg16FRenderTargetSupport = ctxInfo.hasExtension("GL_EXT_color_buffer_half_float") || + ctxInfo.hasExtension("EXT_color_buffer_half_float") || + ctxInfo.hasExtension("GL_EXT_color_buffer_float") || + ctxInfo.hasExtension("EXT_color_buffer_float"); + } + } + + FormatInfo& info = this->getFormatInfo(GrGLFormat::kRG16F); + info.fFormatType = FormatType::kFloat; + info.fInternalFormatForRenderbuffer = GR_GL_RG16F; + info.fDefaultExternalFormat = GR_GL_RG; + info.fDefaultExternalType = halfFloatType; + info.fBytesPerPixel = 4; + if (rg16FTextureSupport) { + info.fFlags |= FormatInfo::kTexturable_Flag; + if (rg16FRenderTargetSupport) { + info.fFlags |= fpRenderFlags; + } + } + + if (texStorageSupported) { + info.fFlags |= FormatInfo::kUseTexStorage_Flag; + info.fInternalFormatForTexImageOrStorage = GR_GL_RG16F; + } else { + info.fInternalFormatForTexImageOrStorage = + texImageSupportsSizedInternalFormat ? GR_GL_RG16F : GR_GL_RG; + } + + if (rg16FTextureSupport) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: GR_GL_RG16F, Surface: kRG_F16 + { + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRG_F16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + this->setColorTypeFormat(GrColorType::kRG_F16, GrGLFormat::kRG16F); + + // External IO ColorTypes: + ctInfo.fExternalIOFormatCount = 2; + ctInfo.fExternalIOFormats.reset( + new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]()); + int ioIdx = 0; + // Format: GR_GL_RG16F, Surface: kRG_F16, Data: kRG_F16 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRG_F16; + ioFormat.fExternalType = halfFloatType; + ioFormat.fExternalTexImageFormat = GR_GL_RG; + ioFormat.fExternalReadFormat = 0; + } + + // Format: GR_GL_RG16F, Surface: kRG_F16, Data: kRGBA_F32 + { + auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++]; + ioFormat.fColorType = GrColorType::kRGBA_F32; + ioFormat.fExternalType = GR_GL_FLOAT; + ioFormat.fExternalTexImageFormat = 0; + ioFormat.fExternalReadFormat = GR_GL_RGBA; + } + } + } + } + + this->setupSampleCounts(ctxInfo, gli); + +#ifdef SK_DEBUG + for (int i = 0; i < kGrGLFormatCount; ++i) { + if (GrGLFormat::kUnknown == static_cast(i)) { + continue; + } + const auto& formatInfo = fFormatTable[i]; + // Make sure we didn't set fbo attachable with msaa and not fbo attachable. + SkASSERT(!((formatInfo.fFlags & FormatInfo::kFBOColorAttachmentWithMSAA_Flag) && + !(formatInfo.fFlags & FormatInfo::kFBOColorAttachment_Flag))); + + // Make sure we set all the formats' FormatType + SkASSERT(formatInfo.fFormatType != FormatType::kUnknown); + + // Make sure if we added a ColorTypeInfo we filled it out + for (int j = 0; j < formatInfo.fColorTypeInfoCount; ++j) { + const auto& ctInfo = formatInfo.fColorTypeInfos[j]; + SkASSERT(ctInfo.fColorType != GrColorType::kUnknown); + // Seems silly to add a color type if we don't support any flags on it. + SkASSERT(ctInfo.fFlags); + // Make sure if we added any ExternalIOFormats we filled it out + for (int k = 0; k < ctInfo.fExternalIOFormatCount; ++k) { + const auto& ioInfo = ctInfo.fExternalIOFormats[k]; + SkASSERT(ioInfo.fColorType != GrColorType::kUnknown); + // Make sure we at least support either reading or tex image. + SkASSERT(ioInfo.fExternalReadFormat || ioInfo.fExternalTexImageFormat); + } + } + } +#endif +} + +void GrGLCaps::setupSampleCounts(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) { + GrGLStandard standard = ctxInfo.standard(); + // standard can be unused (optimized away) if SK_ASSUME_GL_ES is set + sk_ignore_unused_variable(standard); + GrGLVersion version = ctxInfo.version(); + + for (int i = 0; i < kGrGLFormatCount; ++i) { + if (FormatInfo::kFBOColorAttachmentWithMSAA_Flag & fFormatTable[i].fFlags) { + // We assume that MSAA rendering is supported only if we support non-MSAA rendering. + SkASSERT(FormatInfo::kFBOColorAttachment_Flag & fFormatTable[i].fFlags); + if ((GR_IS_GR_GL(standard) && + (version >= GR_GL_VER(4,2) || + ctxInfo.hasExtension("GL_ARB_internalformat_query"))) || + (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3,0))) { + int count; + GrGLFormat grGLFormat = static_cast(i); + GrGLenum glFormat = this->getRenderbufferInternalFormat(grGLFormat); + GR_GL_GetInternalformativ(gli, GR_GL_RENDERBUFFER, glFormat, + GR_GL_NUM_SAMPLE_COUNTS, 1, &count); + if (count) { + std::unique_ptr temp(new int[count]); + GR_GL_GetInternalformativ(gli, GR_GL_RENDERBUFFER, glFormat, GR_GL_SAMPLES, + count, temp.get()); + // GL has a concept of MSAA rasterization with a single sample but we do not. + if (count && temp[count - 1] == 1) { + --count; + SkASSERT(!count || temp[count -1] > 1); + } + fFormatTable[i].fColorSampleCounts.setCount(count+1); + // We initialize our supported values with 1 (no msaa) and reverse the order + // returned by GL so that the array is ascending. + fFormatTable[i].fColorSampleCounts[0] = 1; + for (int j = 0; j < count; ++j) { + fFormatTable[i].fColorSampleCounts[j+1] = temp[count - j - 1]; + } + } + } else { + // Fake out the table using some semi-standard counts up to the max allowed sample + // count. + int maxSampleCnt = 1; + if (GrGLCaps::kES_IMG_MsToTexture_MSFBOType == fMSFBOType) { + GR_GL_GetIntegerv(gli, GR_GL_MAX_SAMPLES_IMG, &maxSampleCnt); + } else if (GrGLCaps::kNone_MSFBOType != fMSFBOType) { + GR_GL_GetIntegerv(gli, GR_GL_MAX_SAMPLES, &maxSampleCnt); + } + // Chrome has a mock GL implementation that returns 0. + maxSampleCnt = SkTMax(1, maxSampleCnt); + + static constexpr int kDefaultSamples[] = {1, 2, 4, 8}; + int count = SK_ARRAY_COUNT(kDefaultSamples); + for (; count > 0; --count) { + if (kDefaultSamples[count - 1] <= maxSampleCnt) { + break; + } + } + if (count > 0) { + fFormatTable[i].fColorSampleCounts.append(count, kDefaultSamples); + } + } + } else if (FormatInfo::kFBOColorAttachment_Flag & fFormatTable[i].fFlags) { + fFormatTable[i].fColorSampleCounts.setCount(1); + fFormatTable[i].fColorSampleCounts[0] = 1; + } + } +} + +bool GrGLCaps::canCopyTexSubImage(GrGLFormat dstFormat, bool dstHasMSAARenderBuffer, + const GrTextureType* dstTypeIfTexture, + GrGLFormat srcFormat, bool srcHasMSAARenderBuffer, + const GrTextureType* srcTypeIfTexture) const { + // Table 3.9 of the ES2 spec indicates the supported formats with CopyTexSubImage + // and BGRA isn't in the spec. There doesn't appear to be any extension that adds it. Perhaps + // many drivers would allow it to work, but ANGLE does not. + if (GR_IS_GR_GL_ES(fStandard) && + (dstFormat == GrGLFormat::kBGRA8 || srcFormat == GrGLFormat::kBGRA8)) { + return false; + } + + // CopyTexSubImage is invalid or doesn't copy what we want when we have msaa render buffers. + if (dstHasMSAARenderBuffer || srcHasMSAARenderBuffer) { + return false; + } + + // CopyTex(Sub)Image writes to a texture and we have no way of dynamically wrapping a RT in a + // texture. + if (!dstTypeIfTexture) { + return false; + } + + // Check that we could wrap the source in an FBO, that the dst is not TEXTURE_EXTERNAL, that no + // mirroring is required + return this->canFormatBeFBOColorAttachment(srcFormat) && + (!srcTypeIfTexture || *srcTypeIfTexture != GrTextureType::kExternal) && + *dstTypeIfTexture != GrTextureType::kExternal; +} + +bool GrGLCaps::canCopyAsBlit(GrGLFormat dstFormat, int dstSampleCnt, + const GrTextureType* dstTypeIfTexture, + GrGLFormat srcFormat, int srcSampleCnt, + const GrTextureType* srcTypeIfTexture, + const SkRect& srcBounds, bool srcBoundsExact, + const SkIRect& srcRect, const SkIPoint& dstPoint) const { + auto blitFramebufferFlags = this->blitFramebufferSupportFlags(); + if (!this->canFormatBeFBOColorAttachment(dstFormat) || + !this->canFormatBeFBOColorAttachment(srcFormat)) { + return false; + } + + if (dstTypeIfTexture && *dstTypeIfTexture == GrTextureType::kExternal) { + return false; + } + if (srcTypeIfTexture && *srcTypeIfTexture == GrTextureType::kExternal) { + return false; + } + + if (GrGLCaps::kNoSupport_BlitFramebufferFlag & blitFramebufferFlags) { + return false; + } + + if (GrGLCaps::kResolveMustBeFull_BlitFrambufferFlag & blitFramebufferFlags) { + if (srcSampleCnt > 1) { + if (1 == dstSampleCnt) { + return false; + } + if (SkRect::Make(srcRect) != srcBounds || !srcBoundsExact) { + return false; + } + } + } + + if (GrGLCaps::kNoMSAADst_BlitFramebufferFlag & blitFramebufferFlags) { + if (dstSampleCnt > 1) { + return false; + } + } + + if (GrGLCaps::kNoFormatConversion_BlitFramebufferFlag & blitFramebufferFlags) { + if (srcFormat != dstFormat) { + return false; + } + } else if (GrGLCaps::kNoFormatConversionForMSAASrc_BlitFramebufferFlag & blitFramebufferFlags) { + if (srcSampleCnt > 1 && srcFormat != dstFormat) { + return false; + } + } + + if (GrGLCaps::kRectsMustMatchForMSAASrc_BlitFramebufferFlag & blitFramebufferFlags) { + if (srcSampleCnt > 1) { + if (dstPoint.fX != srcRect.fLeft || dstPoint.fY != srcRect.fTop) { + return false; + } + } + } + return true; +} + +bool GrGLCaps::canCopyAsDraw(GrGLFormat dstFormat, bool srcIsTexturable) const { + return this->isFormatRenderable(dstFormat, 1) && srcIsTexturable; +} + +static bool has_msaa_render_buffer(const GrSurfaceProxy* surf, const GrGLCaps& glCaps) { + const GrRenderTargetProxy* rt = surf->asRenderTargetProxy(); + if (!rt) { + return false; + } + // A RT has a separate MSAA renderbuffer if: + // 1) It's multisampled + // 2) We're using an extension with separate MSAA renderbuffers + // 3) It's not FBO 0, which is special and always auto-resolves + return rt->numSamples() > 1 && + glCaps.usesMSAARenderBuffers() && + !rt->rtPriv().glRTFBOIDIs0(); +} + +bool GrGLCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const { + int dstSampleCnt = 0; + int srcSampleCnt = 0; + if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) { + dstSampleCnt = rtProxy->numSamples(); + } + if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) { + srcSampleCnt = rtProxy->numSamples(); + } + SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy())); + SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy())); + + const GrTextureProxy* dstTex = dst->asTextureProxy(); + const GrTextureProxy* srcTex = src->asTextureProxy(); + + GrTextureType dstTexType; + GrTextureType* dstTexTypePtr = nullptr; + GrTextureType srcTexType; + GrTextureType* srcTexTypePtr = nullptr; + if (dstTex) { + dstTexType = dstTex->textureType(); + dstTexTypePtr = &dstTexType; + } + if (srcTex) { + srcTexType = srcTex->textureType(); + srcTexTypePtr = &srcTexType; + } + + auto dstFormat = dst->backendFormat().asGLFormat(); + auto srcFormat = src->backendFormat().asGLFormat(); + return this->canCopyTexSubImage(dstFormat, has_msaa_render_buffer(dst, *this), dstTexTypePtr, + srcFormat, has_msaa_render_buffer(src, *this), srcTexTypePtr) || + this->canCopyAsBlit(dstFormat, dstSampleCnt, dstTexTypePtr, srcFormat, srcSampleCnt, + srcTexTypePtr, src->getBoundsRect(), src->priv().isExact(), srcRect, + dstPoint) || + this->canCopyAsDraw(dstFormat, SkToBool(srcTex)); +} + +GrCaps::DstCopyRestrictions GrGLCaps::getDstCopyRestrictions(const GrRenderTargetProxy* src, + GrColorType colorType) const { + // If the src is a texture, we can implement the blit as a draw assuming the config is + // renderable. + if (src->asTextureProxy() && !this->isFormatAsColorTypeRenderable(colorType, + src->backendFormat())) { + return {}; + } + + if (const auto* texProxy = src->asTextureProxy()) { + if (texProxy->textureType() == GrTextureType::kExternal) { + // Not supported for FBO blit or CopyTexSubImage. Caller will have to fall back to a + // draw (if the source is also a texture). + return {}; + } + } + + // We look for opportunities to use CopyTexSubImage, or fbo blit. If neither are + // possible and we return false to fallback to creating a render target dst for render-to- + // texture. This code prefers CopyTexSubImage to fbo blit and avoids triggering temporary fbo + // creation. It isn't clear that avoiding temporary fbo creation is actually optimal. + DstCopyRestrictions blitFramebufferRestrictions = {}; + if (src->numSamples() > 1 && + (this->blitFramebufferSupportFlags() & kResolveMustBeFull_BlitFrambufferFlag)) { + blitFramebufferRestrictions.fRectsMustMatch = GrSurfaceProxy::RectsMustMatch::kYes; + blitFramebufferRestrictions.fMustCopyWholeSrc = true; + // Mirroring causes rects to mismatch later, don't allow it. + } else if (src->numSamples() > 1 && (this->blitFramebufferSupportFlags() & + kRectsMustMatchForMSAASrc_BlitFramebufferFlag)) { + blitFramebufferRestrictions.fRectsMustMatch = GrSurfaceProxy::RectsMustMatch::kYes; + } + + auto srcFormat = src->backendFormat().asGLFormat(); + // Check for format issues with glCopyTexSubImage2D + if (srcFormat == GrGLFormat::kBGRA8) { + // glCopyTexSubImage2D doesn't work with this config. If the bgra can be used with fbo blit + // then we set up for that, otherwise fail. + if (this->canFormatBeFBOColorAttachment(srcFormat)) { + return blitFramebufferRestrictions; + } + // Caller will have to use a draw. + return {}; + } + + { + bool srcIsMSAARenderbuffer = src->numSamples() > 1 && + this->usesMSAARenderBuffers(); + if (srcIsMSAARenderbuffer) { + // It's illegal to call CopyTexSubImage2D on a MSAA renderbuffer. Set up for FBO + // blit or fail. + if (this->canFormatBeFBOColorAttachment(srcFormat)) { + return blitFramebufferRestrictions; + } + // Caller will have to use a draw. + return {}; + } + } + + // We'll do a CopyTexSubImage, no restrictions. + return {}; +} + +void GrGLCaps::applyDriverCorrectnessWorkarounds(const GrGLContextInfo& ctxInfo, + const GrContextOptions& contextOptions, + GrShaderCaps* shaderCaps, + FormatWorkarounds* formatWorkarounds) { + // A driver but on the nexus 6 causes incorrect dst copies when invalidate is called beforehand. + // Thus we are blacklisting this extension for now on Adreno4xx devices. + if (kAdreno430_GrGLRenderer == ctxInfo.renderer() || + kAdreno4xx_other_GrGLRenderer == ctxInfo.renderer() || + fDriverBugWorkarounds.disable_discard_framebuffer) { + fInvalidateFBType = kNone_InvalidateFBType; + } + + // glClearTexImage seems to have a bug in NVIDIA drivers that was fixed sometime between + // 340.96 and 367.57. + if (GR_IS_GR_GL(ctxInfo.standard()) && + ctxInfo.driver() == kNVIDIA_GrGLDriver && + ctxInfo.driverVersion() < GR_GL_DRIVER_VER(367, 57, 0)) { + fClearTextureSupport = false; + } + +#ifdef SK_BUILD_FOR_MAC + // Radeon MacBooks hit a crash in glReadPixels() when using geometry shaders. + // http://skbug.com/8097 + if (kATI_GrGLVendor == ctxInfo.vendor()) { + shaderCaps->fGeometryShaderSupport = false; + } + // On at least some MacBooks, GLSL 4.0 geometry shaders break if we use invocations. + shaderCaps->fGSInvocationsSupport = false; +#endif + + // Qualcomm driver @103.0 has been observed to crash compiling ccpr geometry + // shaders. @127.0 is the earliest verified driver to not crash. + if (kQualcomm_GrGLDriver == ctxInfo.driver() && + ctxInfo.driverVersion() < GR_GL_DRIVER_VER(127, 0, 0)) { + shaderCaps->fGeometryShaderSupport = false; + } + +#if defined(__has_feature) +#if defined(SK_BUILD_FOR_MAC) && __has_feature(thread_sanitizer) + // See skbug.com/7058 + fMapBufferType = kNone_MapBufferType; + fMapBufferFlags = kNone_MapFlags; + fTransferBufferSupport = false; + fTransferBufferType = kNone_TransferBufferType; +#endif +#endif + + // We found that the Galaxy J5 with an Adreno 306 running 6.0.1 has a bug where + // GL_INVALID_OPERATION thrown by glDrawArrays when using a buffer that was mapped. The same bug + // did not reproduce on a Nexus7 2013 with a 320 running Android M with driver 127.0. It's + // unclear whether this really affects a wide range of devices. + if (ctxInfo.renderer() == kAdreno3xx_GrGLRenderer && + ctxInfo.driverVersion() > GR_GL_DRIVER_VER(127, 0, 0)) { + fMapBufferType = kNone_MapBufferType; + fMapBufferFlags = kNone_MapFlags; + fTransferBufferSupport = false; + fTransferBufferType = kNone_TransferBufferType; + } + + // TODO: re-enable for ANGLE + if (kANGLE_GrGLDriver == ctxInfo.driver()) { + fTransferBufferSupport = false; + fTransferBufferType = kNone_TransferBufferType; + } + + // Using MIPs on this GPU seems to be a source of trouble. + if (kPowerVR54x_GrGLRenderer == ctxInfo.renderer()) { + fMipMapSupport = false; + } + +#ifndef SK_BUILD_FOR_IOS + if (kPowerVR54x_GrGLRenderer == ctxInfo.renderer() || + kPowerVRRogue_GrGLRenderer == ctxInfo.renderer() || + (kAdreno3xx_GrGLRenderer == ctxInfo.renderer() && + ctxInfo.driver() != kChromium_GrGLDriver)) { + fPerformColorClearsAsDraws = true; + } +#endif + + // A lot of GPUs have trouble with full screen clears (skbug.com/7195) + if (kAMDRadeonHD7xxx_GrGLRenderer == ctxInfo.renderer() || + kAMDRadeonR9M4xx_GrGLRenderer == ctxInfo.renderer()) { + fPerformColorClearsAsDraws = true; + } + +#ifdef SK_BUILD_FOR_MAC + // crbug.com/768134 - On MacBook Pros, the Intel Iris Pro doesn't always perform + // full screen clears + // crbug.com/773107 - On MacBook Pros, a wide range of Intel GPUs don't always + // perform full screen clears. + // Update on 4/4/2018 - This appears to be fixed on driver 10.30.12 on a macOS 10.13.2 on a + // Retina MBP Early 2015 with Iris 6100. It is possibly fixed on earlier drivers as well. + if (kIntel_GrGLVendor == ctxInfo.vendor() && + ctxInfo.driverVersion() < GR_GL_DRIVER_VER(10, 30, 12)) { + fPerformColorClearsAsDraws = true; + } + // crbug.com/969609 - NVIDIA on Mac sometimes segfaults during glClear in chrome. It seems + // mostly concentrated in 10.13/14, GT 650Ms, driver 12+. But there are instances of older + // drivers and GTX 775s, so we'll start with a broader workaround. + if (kNVIDIA_GrGLVendor == ctxInfo.vendor()) { + fPerformColorClearsAsDraws = true; + } +#endif + + // See crbug.com/755871. This could probably be narrowed to just partial clears as the driver + // bugs seems to involve clearing too much and not skipping the clear. + // See crbug.com/768134. This is also needed for full clears and was seen on an nVidia K620 + // but only for D3D11 ANGLE. + if (GrGLANGLEBackend::kD3D11 == ctxInfo.angleBackend()) { + fPerformColorClearsAsDraws = true; + } + + if (kAdreno430_GrGLRenderer == ctxInfo.renderer() || + kAdreno4xx_other_GrGLRenderer == ctxInfo.renderer()) { + // This is known to be fixed sometime between driver 145.0 and 219.0 + if (ctxInfo.driverVersion() <= GR_GL_DRIVER_VER(219, 0, 0)) { + fPerformStencilClearsAsDraws = true; + } + fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO = true; + } + + if (fDriverBugWorkarounds.gl_clear_broken) { + fPerformColorClearsAsDraws = true; + fPerformStencilClearsAsDraws = true; + } + + // This was reproduced on the following configurations: + // - A Galaxy J5 (Adreno 306) running Android 6 with driver 140.0 + // - A Nexus 7 2013 (Adreno 320) running Android 5 with driver 104.0 + // - A Nexus 7 2013 (Adreno 320) running Android 6 with driver 127.0 + // - A Nexus 5 (Adreno 330) running Android 6 with driver 127.0 + // and not produced on: + // - A Nexus 7 2013 (Adreno 320) running Android 4 with driver 53.0 + // The particular lines that get dropped from test images varies across different devices. + if (kAdreno3xx_GrGLRenderer == ctxInfo.renderer() && + ctxInfo.driverVersion() > GR_GL_DRIVER_VER(53, 0, 0)) { + fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines = true; + } + + // This was reproduced on a Pixel 1, but the unit test + config + options that exercise it are + // only tested on very specific bots. The driver claims that ReadPixels is an invalid operation + // when reading from an auto-resolving MSAA framebuffer that has stencil attached. + if (kQualcomm_GrGLDriver == ctxInfo.driver()) { + fDetachStencilFromMSAABuffersBeforeReadPixels = true; + } + + // TODO: Don't apply this on iOS? + if (kPowerVRRogue_GrGLRenderer == ctxInfo.renderer()) { + // Our Chromebook with kPowerVRRogue_GrGLRenderer crashes on large instanced draws. The + // current minimum number of instances observed to crash is somewhere between 2^14 and 2^15. + // Keep the number of instances below 1000, just to be safe. + fMaxInstancesPerDrawWithoutCrashing = 999; + } else if (fDriverBugWorkarounds.disallow_large_instanced_draw) { + fMaxInstancesPerDrawWithoutCrashing = 0x4000000; + } + + // Texture uploads sometimes seem to be ignored to textures bound to FBOS on Tegra3. + if (kTegra_PreK1_GrGLRenderer == ctxInfo.renderer()) { + fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO = true; + fUseDrawInsteadOfAllRenderTargetWrites = true; + } + +#ifdef SK_BUILD_FOR_MAC + static constexpr bool isMAC = true; +#else + static constexpr bool isMAC = false; +#endif + + // We support manual mip-map generation (via iterative downsampling draw calls). This fixes + // bugs on some cards/drivers that produce incorrect mip-maps for sRGB textures when using + // glGenerateMipmap. Our implementation requires mip-level sampling control. Additionally, + // it can be much slower (especially on mobile GPUs), so we opt-in only when necessary: + if (fMipMapLevelAndLodControlSupport && + (contextOptions.fDoManualMipmapping || + (kIntel_GrGLVendor == ctxInfo.vendor()) || + (kNVIDIA_GrGLDriver == ctxInfo.driver() && isMAC) || + (kATI_GrGLVendor == ctxInfo.vendor()))) { + fDoManualMipmapping = true; + } + + // See http://crbug.com/710443 +#ifdef SK_BUILD_FOR_MAC + if (kIntelBroadwell_GrGLRenderer == ctxInfo.renderer()) { + fClearToBoundaryValuesIsBroken = true; + } +#endif + if (kQualcomm_GrGLVendor == ctxInfo.vendor()) { + fDrawArraysBaseVertexIsBroken = true; + } + + // Currently the extension is advertised but fb fetch is broken on 500 series Adrenos like the + // Galaxy S7. + // TODO: Once this is fixed we can update the check here to look at a driver version number too. + if (kAdreno5xx_GrGLRenderer == ctxInfo.renderer()) { + shaderCaps->fFBFetchSupport = false; + } + + // On the NexusS and GalaxyNexus, the use of 'any' causes the compilation error "Calls to any + // function that may require a gradient calculation inside a conditional block may return + // undefined results". This appears to be an issue with the 'any' call since even the simple + // "result=black; if (any()) result=white;" code fails to compile. This issue comes into play + // from our GrTextureDomain processor. + shaderCaps->fCanUseAnyFunctionInShader = kImagination_GrGLVendor != ctxInfo.vendor(); + + // Known issue on at least some Intel platforms: + // http://code.google.com/p/skia/issues/detail?id=946 + if (kIntel_GrGLVendor == ctxInfo.vendor()) { + shaderCaps->fFragCoordConventionsExtensionString = nullptr; + } + + if (kTegra_PreK1_GrGLRenderer == ctxInfo.renderer()) { + // The Tegra3 compiler will sometimes never return if we have min(abs(x), 1.0), + // so we must do the abs first in a separate expression. + shaderCaps->fCanUseMinAndAbsTogether = false; + + // Tegra3 fract() seems to trigger undefined behavior for negative values, so we + // must avoid this condition. + shaderCaps->fCanUseFractForNegativeValues = false; + } + + // On Intel GPU there is an issue where it reads the second argument to atan "- %s.x" as an int + // thus must us -1.0 * %s.x to work correctly + if (kIntel_GrGLVendor == ctxInfo.vendor()) { + shaderCaps->fMustForceNegatedAtanParamToFloat = true; + } + + // On some Intel GPUs there is an issue where the driver outputs bogus values in the shader + // when floor and abs are called on the same line. Thus we must execute an Op between them to + // make sure the compiler doesn't re-inline them even if we break the calls apart. + if (kIntel_GrGLVendor == ctxInfo.vendor()) { + shaderCaps->fMustDoOpBetweenFloorAndAbs = true; + } + + // On Adreno devices with framebuffer fetch support, there is a bug where they always return + // the original dst color when reading the outColor even after being written to. By using a + // local outColor we can work around this bug. + if (shaderCaps->fFBFetchSupport && kQualcomm_GrGLVendor == ctxInfo.vendor()) { + shaderCaps->fRequiresLocalOutputColorForFBFetch = true; + } + + // Newer Mali GPUs do incorrect static analysis in specific situations: If there is uniform + // color, and that uniform contains an opaque color, and the output of the shader is only based + // on that uniform plus soemthing un-trackable (like a texture read), the compiler will deduce + // that the shader always outputs opaque values. In that case, it appears to remove the shader + // based blending code it normally injects, turning SrcOver into Src. To fix this, we always + // insert an extra bit of math on the uniform that confuses the compiler just enough... + if (kMaliT_GrGLRenderer == ctxInfo.renderer()) { + shaderCaps->fMustObfuscateUniformColor = true; + } +#ifdef SK_BUILD_FOR_WIN + // Check for ANGLE on Windows, so we can workaround a bug in D3D itself (anglebug.com/2098). + // + // Basically, if a shader has a construct like: + // + // float x = someCondition ? someValue : 0; + // float2 result = (0 == x) ? float2(x, x) + // : float2(2 * x / x, 0); + // + // ... the compiler will produce an error 'NaN and infinity literals not allowed', even though + // we've explicitly guarded the division with a check against zero. This manifests in much + // more complex ways in some of our shaders, so we use this caps bit to add an epsilon value + // to the denominator of divisions, even when we've added checks that the denominator isn't 0. + if (kANGLE_GrGLDriver == ctxInfo.driver() || kChromium_GrGLDriver == ctxInfo.driver()) { + shaderCaps->fMustGuardDivisionEvenAfterExplicitZeroCheck = true; + } +#endif + + // We've seen Adreno 3xx devices produce incorrect (flipped) values for gl_FragCoord, in some + // (rare) situations. It's sporadic, and mostly on older drivers. Additionally, old Adreno + // compilers (see crbug.com/skia/4078) crash when accessing .zw of gl_FragCoord, so just bypass + // using gl_FragCoord at all to get around it. + if (kAdreno3xx_GrGLRenderer == ctxInfo.renderer()) { + shaderCaps->fCanUseFragCoord = false; + } + + // gl_FragCoord has an incorrect subpixel offset on legacy Tegra hardware. + if (kTegra_PreK1_GrGLRenderer == ctxInfo.renderer()) { + shaderCaps->fCanUseFragCoord = false; + } + + // On Mali G71, mediump ints don't appear capable of representing every integer beyond +/-2048. + // (Are they implemented with fp16?) + if (kARM_GrGLVendor == ctxInfo.vendor()) { + shaderCaps->fIncompleteShortIntPrecision = true; + } + + if (fDriverBugWorkarounds.add_and_true_to_loop_condition) { + shaderCaps->fAddAndTrueToLoopCondition = true; + } + + if (fDriverBugWorkarounds.unfold_short_circuit_as_ternary_operation) { + shaderCaps->fUnfoldShortCircuitAsTernary = true; + } + + if (fDriverBugWorkarounds.emulate_abs_int_function) { + shaderCaps->fEmulateAbsIntFunction = true; + } + + if (fDriverBugWorkarounds.rewrite_do_while_loops) { + shaderCaps->fRewriteDoWhileLoops = true; + } + + if (fDriverBugWorkarounds.remove_pow_with_constant_exponent) { + shaderCaps->fRemovePowWithConstantExponent = true; + } + + if (kAdreno3xx_GrGLRenderer == ctxInfo.renderer() || + kAdreno4xx_other_GrGLRenderer == ctxInfo.renderer()) { + shaderCaps->fMustWriteToFragColor = true; + } + + // Disabling advanced blend on various platforms with major known issues. We also block Chrome + // for now until its own blacklists can be updated. + if (kAdreno430_GrGLRenderer == ctxInfo.renderer() || + kAdreno4xx_other_GrGLRenderer == ctxInfo.renderer() || + kAdreno5xx_GrGLRenderer == ctxInfo.renderer() || + kIntel_GrGLDriver == ctxInfo.driver() || + kChromium_GrGLDriver == ctxInfo.driver()) { + fBlendEquationSupport = kBasic_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kNotSupported_AdvBlendEqInteraction; + } + + // Non-coherent advanced blend has an issue on NVIDIA pre 337.00. + if (kNVIDIA_GrGLDriver == ctxInfo.driver() && + ctxInfo.driverVersion() < GR_GL_DRIVER_VER(337, 00, 0) && + kAdvanced_BlendEquationSupport == fBlendEquationSupport) { + fBlendEquationSupport = kBasic_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kNotSupported_AdvBlendEqInteraction; + } + + if (fDriverBugWorkarounds.disable_blend_equation_advanced) { + fBlendEquationSupport = kBasic_BlendEquationSupport; + shaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kNotSupported_AdvBlendEqInteraction; + } + + if (this->advancedBlendEquationSupport()) { + if (kNVIDIA_GrGLDriver == ctxInfo.driver() && + ctxInfo.driverVersion() < GR_GL_DRIVER_VER(355, 00, 0)) { + // Blacklist color-dodge and color-burn on pre-355.00 NVIDIA. + fAdvBlendEqBlacklist |= (1 << kColorDodge_GrBlendEquation) | + (1 << kColorBurn_GrBlendEquation); + } + if (kARM_GrGLVendor == ctxInfo.vendor()) { + // Blacklist color-burn on ARM until the fix is released. + fAdvBlendEqBlacklist |= (1 << kColorBurn_GrBlendEquation); + } + } + + // Workaround NVIDIA bug related to glInvalidateFramebuffer and mixed samples. + if (fMultisampleDisableSupport && + this->shaderCaps()->dualSourceBlendingSupport() && + this->shaderCaps()->pathRenderingSupport() && + fMixedSamplesSupport && +#if GR_TEST_UTILS + (contextOptions.fGpuPathRenderers & GpuPathRenderers::kStencilAndCover) && +#endif + (kNVIDIA_GrGLDriver == ctxInfo.driver() || + kChromium_GrGLDriver == ctxInfo.driver())) { + fInvalidateFBType = kNone_InvalidateFBType; + } + + // Many ES3 drivers only advertise the ES2 image_external extension, but support the _essl3 + // extension, and require that it be enabled to work with ESSL3. Other devices require the ES2 + // extension to be enabled, even when using ESSL3. Enabling both extensions fixes both cases. + // skbug.com/7713 + if (ctxInfo.hasExtension("GL_OES_EGL_image_external") && + ctxInfo.glslGeneration() >= k330_GrGLSLGeneration && + !shaderCaps->fExternalTextureSupport) { // i.e. Missing the _essl3 extension + shaderCaps->fExternalTextureSupport = true; + shaderCaps->fExternalTextureExtensionString = "GL_OES_EGL_image_external"; + shaderCaps->fSecondExternalTextureExtensionString = "GL_OES_EGL_image_external_essl3"; + } + +#ifdef SK_BUILD_FOR_IOS + // iOS drivers appear to implement TexSubImage by creating a staging buffer, and copying + // UNPACK_ROW_LENGTH * height bytes. That's unsafe in several scenarios, and the simplest fix + // is to just blacklist the feature. + // https://github.com/flutter/flutter/issues/16718 + // https://bugreport.apple.com/web/?problemID=39948888 + fWritePixelsRowBytesSupport = false; +#endif + + // CCPR edge AA is busted on Mesa, Sandy Bridge/Valley View (Bay Trail). + // http://skbug.com/8162 + if (kMesa_GrGLDriver == ctxInfo.driver() && + (kIntelSandyBridge_GrGLRenderer == ctxInfo.renderer() || + kIntelIvyBridge_GrGLRenderer == ctxInfo.renderer() || + kIntelValleyView_GrGLRenderer == ctxInfo.renderer())) { + fDriverBlacklistCCPR = true; + } + + // Temporarily disable the MSAA implementation of CCPR on various platforms while we work out + // specific issues. + if (kATI_GrGLVendor == ctxInfo.vendor() || // Radeon drops stencil draws that use sample mask. + kImagination_GrGLVendor == ctxInfo.vendor() || // PowerVR produces flaky results on Gold. + kQualcomm_GrGLVendor == ctxInfo.vendor() /* Pixel2 crashes in nanobench. */) { + fDriverBlacklistMSAACCPR = true; + } + +#ifdef SK_BUILD_FOR_ANDROID + // Older versions of Android have problems with setting GL_TEXTURE_BASE_LEVEL or + // GL_TEXTURE_MAX_LEVEL on GL_TEXTURE_EXTERTNAL_OES textures. We just leave them as is and hope + // the client never changes them either. + fDontSetBaseOrMaxLevelForExternalTextures = true; +#endif + + // PowerVRGX6250 drops every pixel if we modify the sample mask while color writes are disabled. + if (kPowerVRRogue_GrGLRenderer == ctxInfo.renderer()) { + fNeverDisableColorWrites = true; + shaderCaps->fMustWriteToFragColor = true; + } + + // It appears that Qualcomm drivers don't actually support + // GL_NV_shader_noperspective_interpolation in ES 3.00 or 3.10 shaders, only 3.20. + // https://crbug.com/986581 + if (kQualcomm_GrGLVendor == ctxInfo.vendor() && + k320es_GrGLSLGeneration != ctxInfo.glslGeneration()) { + shaderCaps->fNoPerspectiveInterpolationSupport = false; + } + + // We disable srgb write control for Adreno4xx devices. + // see: https://bug.skia.org/5329 + if (kAdreno430_GrGLRenderer == ctxInfo.renderer() || + kAdreno4xx_other_GrGLRenderer == ctxInfo.renderer()) { + fSRGBWriteControl = false; + } + + // MacPro devices with AMD cards fail to create MSAA sRGB render buffers. +#if defined(SK_BUILD_FOR_MAC) + formatWorkarounds->fDisableSRGBRenderWithMSAAForMacAMD = kATI_GrGLVendor == ctxInfo.vendor(); +#endif + + // Command buffer fails glTexSubImage2D with type == GL_HALF_FLOAT_OES if a GL_RGBA16F texture + // is created with glTexStorage2D. See crbug.com/1008003. + formatWorkarounds->fDisableRGBA16FTexStorageForCrBug1008003 = + kChromium_GrGLDriver == ctxInfo.driver() && ctxInfo.version() < GR_GL_VER(3, 0); + +#if defined(SK_BUILD_FOR_WIN) + // On Intel Windows ES contexts it seems that using texture storage with BGRA causes + // problems with cross-context SkImages. + formatWorkarounds->fDisableBGRATextureStorageForIntelWindowsES = + kIntel_GrGLDriver == ctxInfo.driver() && GR_IS_GR_GL_ES(ctxInfo.standard()); +#endif + + // Mali-400 fails ReadPixels tests, mostly with non-0xFF alpha values when read as GL_RGBA8. + formatWorkarounds->fDisableRGB8ForMali400 = kMali4xx_GrGLRenderer == ctxInfo.renderer(); + + // On the Intel Iris 6100, interacting with LUM16F seems to confuse the driver. After + // writing to/reading from a LUM16F texture reads from/writes to other formats behave + // erratically. + // All Adrenos claim to support LUM16F but don't appear to actually do so. + // The failing devices/gpus were: Nexus5/Adreno330, Nexus5x/Adreno418, Pixel/Adreno530, + // Pixel2XL/Adreno540 and Pixel3/Adreno630 + formatWorkarounds->fDisableLuminance16F = kIntelBroadwell_GrGLRenderer == ctxInfo.renderer() || + ctxInfo.vendor() == kQualcomm_GrGLVendor; + +#ifdef SK_BUILD_FOR_ANDROID + // We don't usually use glTexStorage() on Android for performance reasons. (crbug.com/945506). + // On a NVIDIA Shield TV running Android 7.0 creating a texture with glTexImage2D() with + // internal format GL_LUMINANCE8 fails. However, it succeeds with glTexStorage2D(). + // + // Additionally, on the Nexus 9 running Android 6.0.1 formats added by GL_EXT_texture_rg and + // GL_EXT_texture_norm16 cause errors if they are created with glTexImage2D() with + // an unsized internal format. We wouldn't normally do that but Chrome can limit us + // artificially to ES2. (crbug.com/1003481) + if (kNVIDIA_GrGLVendor == ctxInfo.vendor()) { + formatWorkarounds->fDontDisableTexStorageOnAndroid = true; + } +#endif + + // https://github.com/flutter/flutter/issues/38700 + if (kAndroidEmulator_GrGLDriver == ctxInfo.driver()) { + shaderCaps->fNoDefaultPrecisionForExternalSamplers = true; + } + + // http://skbug.com/9491: Nexus5 produces rendering artifacts when we use QCOM_tiled_rendering. + if (kAdreno3xx_GrGLRenderer == ctxInfo.renderer()) { + fTiledRenderingSupport = false; + } +} + +void GrGLCaps::onApplyOptionsOverrides(const GrContextOptions& options) { + if (options.fDisableDriverCorrectnessWorkarounds) { + SkASSERT(!fDoManualMipmapping); + SkASSERT(!fClearToBoundaryValuesIsBroken); + SkASSERT(0 == fMaxInstancesPerDrawWithoutCrashing); + SkASSERT(!fDrawArraysBaseVertexIsBroken); + SkASSERT(!fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO); + SkASSERT(!fUseDrawInsteadOfAllRenderTargetWrites); + SkASSERT(!fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines); + SkASSERT(!fDetachStencilFromMSAABuffersBeforeReadPixels); + SkASSERT(!fDontSetBaseOrMaxLevelForExternalTextures); + SkASSERT(!fNeverDisableColorWrites); + } + if (options.fDoManualMipmapping) { + fDoManualMipmapping = true; + } + if (options.fShaderCacheStrategy < GrContextOptions::ShaderCacheStrategy::kBackendBinary) { + fProgramBinarySupport = false; + } +} + +bool GrGLCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const { + if (fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO) { + if (auto tex = static_cast(surface->asTexture())) { + if (tex->hasBaseLevelBeenBoundToFBO()) { + return false; + } + } + } + if (auto rt = surface->asRenderTarget()) { + if (fUseDrawInsteadOfAllRenderTargetWrites) { + return false; + } + if (rt->numSamples() > 1 && this->usesMSAARenderBuffers()) { + return false; + } + return SkToBool(surface->asTexture()); + } + return true; +} + +GrCaps::SurfaceReadPixelsSupport GrGLCaps::surfaceSupportsReadPixels( + const GrSurface* surface) const { + if (auto tex = static_cast(surface->asTexture())) { + // We don't support reading pixels directly from EXTERNAL textures as it would require + // binding the texture to a FBO. + if (tex->target() == GR_GL_TEXTURE_EXTERNAL) { + return SurfaceReadPixelsSupport::kCopyToTexture2D; + } + } + return SurfaceReadPixelsSupport::kSupported; +} + +size_t offset_alignment_for_transfer_buffer(GrGLenum externalType) { + // This switch is derived from a table titled "Pixel data type parameter values and the + // corresponding GL data types" in the OpenGL spec (Table 8.2 in OpenGL 4.5). + switch (externalType) { + case GR_GL_UNSIGNED_BYTE: return sizeof(GrGLubyte); + case GR_GL_BYTE: return sizeof(GrGLbyte); + case GR_GL_UNSIGNED_SHORT: return sizeof(GrGLushort); + case GR_GL_SHORT: return sizeof(GrGLshort); + case GR_GL_UNSIGNED_INT: return sizeof(GrGLuint); + case GR_GL_INT: return sizeof(GrGLint); + case GR_GL_HALF_FLOAT: return sizeof(GrGLhalf); + case GR_GL_FLOAT: return sizeof(GrGLfloat); + case GR_GL_UNSIGNED_SHORT_5_6_5: return sizeof(GrGLushort); + case GR_GL_UNSIGNED_SHORT_4_4_4_4: return sizeof(GrGLushort); + case GR_GL_UNSIGNED_SHORT_5_5_5_1: return sizeof(GrGLushort); + case GR_GL_UNSIGNED_INT_2_10_10_10_REV: return sizeof(GrGLuint); +#if 0 // GL types we currently don't use. Here for future reference. + case GR_GL_UNSIGNED_BYTE_3_3_2: return sizeof(GrGLubyte); + case GR_GL_UNSIGNED_BYTE_2_3_3_REV: return sizeof(GrGLubyte); + case GR_GL_UNSIGNED_SHORT_5_6_5_REV: return sizeof(GrGLushort); + case GR_GL_UNSIGNED_SHORT_4_4_4_4_REV: return sizeof(GrGLushort); + case GR_GL_UNSIGNED_SHORT_1_5_5_5_REV: return sizeof(GrGLushort); + case GR_GL_UNSIGNED_INT_8_8_8_8: return sizeof(GrGLuint); + case GR_GL_UNSIGNED_INT_8_8_8_8_REV: return sizeof(GrGLuint); + case GR_GL_UNSIGNED_INT_10_10_10_2: return sizeof(GrGLuint); + case GR_GL_UNSIGNED_INT_24_8: return sizeof(GrGLuint); + case GR_GL_UNSIGNED_INT_10F_11F_11F_REV: return sizeof(GrGLuint); + case GR_GL_UNSIGNED_INT_5_9_9_9_REV: return sizeof(GrGLuint); + // This one is not corresponding to a GL data type and the spec just says it is 4. + case GR_GL_FLOAT_32_UNSIGNED_INT_24_8_REV: return 4; +#endif + default: return 0; + } +} + +GrCaps::SupportedRead GrGLCaps::onSupportedReadPixelsColorType( + GrColorType srcColorType, const GrBackendFormat& srcBackendFormat, + GrColorType dstColorType) const { + // We first try to find a supported read pixels GrColorType that matches the requested + // dstColorType. If that doesn't exists we will use any valid read pixels GrColorType. + GrCaps::SupportedRead fallbackRead = {GrColorType::kUnknown, 0}; + const auto& formatInfo = this->getFormatInfo(srcBackendFormat.asGLFormat()); + bool foundSrcCT = false; + for (int i = 0; !foundSrcCT && i < formatInfo.fColorTypeInfoCount; ++i) { + if (formatInfo.fColorTypeInfos[i].fColorType == srcColorType) { + const ColorTypeInfo& ctInfo = formatInfo.fColorTypeInfos[i]; + foundSrcCT = true; + for (int j = 0; j < ctInfo.fExternalIOFormatCount; ++j) { + const auto& ioInfo = ctInfo.fExternalIOFormats[j]; + if (ioInfo.fExternalReadFormat != 0) { + GrGLenum transferOffsetAlignment = + offset_alignment_for_transfer_buffer(ioInfo.fExternalType); + if (ioInfo.fColorType == dstColorType) { + return {dstColorType, transferOffsetAlignment}; + } + // Currently we just pick the first supported format that we find as our + // fallback. + if (fallbackRead.fColorType == GrColorType::kUnknown) { + fallbackRead = {ioInfo.fColorType, transferOffsetAlignment}; + } + } + } + } + } + return fallbackRead; +} + +GrCaps::SupportedWrite GrGLCaps::supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const { + // We first try to find a supported write pixels GrColorType that matches the data's + // srcColorType. If that doesn't exists we will use any supported GrColorType. + GrColorType fallbackCT = GrColorType::kUnknown; + const auto& formatInfo = this->getFormatInfo(surfaceFormat.asGLFormat()); + bool foundSurfaceCT = false; + for (int i = 0; !foundSurfaceCT && i < formatInfo.fColorTypeInfoCount; ++i) { + if (formatInfo.fColorTypeInfos[i].fColorType == surfaceColorType) { + const ColorTypeInfo& ctInfo = formatInfo.fColorTypeInfos[i]; + foundSurfaceCT = true; + for (int j = 0; j < ctInfo.fExternalIOFormatCount; ++j) { + const auto& ioInfo = ctInfo.fExternalIOFormats[j]; + if (ioInfo.fExternalTexImageFormat != 0) { + if (ioInfo.fColorType == srcColorType) { + return {srcColorType, 1}; + } + // Currently we just pick the first supported format that we find as our + // fallback. + if (fallbackCT == GrColorType::kUnknown) { + fallbackCT = ioInfo.fColorType; + } + } + } + } + } + return {fallbackCT, 1}; +} + +bool GrGLCaps::onIsWindowRectanglesSupportedForRT(const GrBackendRenderTarget& backendRT) const { + GrGLFramebufferInfo fbInfo; + SkAssertResult(backendRT.getGLFramebufferInfo(&fbInfo)); + // Window Rectangles are not supported for FBO 0; + return fbInfo.fFBOID != 0; +} + +bool GrGLCaps::isFormatSRGB(const GrBackendFormat& format) const { + return format.asGLFormat() == GrGLFormat::kSRGB8_ALPHA8; +} + +bool GrGLCaps::isFormatCompressed(const GrBackendFormat& format, + SkImage::CompressionType* compressionType) const { + auto fmt = format.asGLFormat(); + + SkImage::CompressionType dummyType; + SkImage::CompressionType* compressionTypePtr = compressionType ? compressionType : &dummyType; + + switch (fmt) { + case GrGLFormat::kCOMPRESSED_RGB8_ETC2: // fall through + case GrGLFormat::kCOMPRESSED_ETC1_RGB8: + // ETC2 uses the same compression layout as ETC1 + *compressionTypePtr = SkImage::kETC1_CompressionType; + return true; + default: + return false; + } +} + +bool GrGLCaps::isFormatTexturableAndUploadable(GrColorType ct, + const GrBackendFormat& format) const { + auto glFormat = format.asGLFormat(); + const FormatInfo& info = this->getFormatInfo(glFormat); + + return this->isFormatTexturable(glFormat) && + SkToBool(info.colorTypeFlags(ct) & ColorTypeInfo::kUploadData_Flag); +} + +bool GrGLCaps::isFormatTexturable(const GrBackendFormat& format) const { + return this->isFormatTexturable(format.asGLFormat()); +} + +bool GrGLCaps::isFormatTexturable(GrGLFormat format) const { + const FormatInfo& info = this->getFormatInfo(format); + return SkToBool(info.fFlags & FormatInfo::kTexturable_Flag); +} + +bool GrGLCaps::isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount) const { + auto f = format.asGLFormat(); + const FormatInfo& info = this->getFormatInfo(f); + if (!SkToBool(info.colorTypeFlags(ct) & ColorTypeInfo::kRenderable_Flag)) { + return false; + } + + return this->isFormatRenderable(f, sampleCount); +} + +bool GrGLCaps::isFormatRenderable(const GrBackendFormat& format, int sampleCount) const { + return this->isFormatRenderable(format.asGLFormat(), sampleCount); +} + +int GrGLCaps::getRenderTargetSampleCount(int requestedCount, GrGLFormat format) const { + const FormatInfo& info = this->getFormatInfo(format); + + int count = info.fColorSampleCounts.count(); + if (!count) { + return 0; + } + + requestedCount = SkTMax(1, requestedCount); + if (1 == requestedCount) { + return info.fColorSampleCounts[0] == 1 ? 1 : 0; + } + + for (int i = 0; i < count; ++i) { + if (info.fColorSampleCounts[i] >= requestedCount) { + int count = info.fColorSampleCounts[i]; + if (fDriverBugWorkarounds.max_msaa_sample_count_4) { + count = SkTMin(count, 4); + } + return count; + } + } + return 0; +} + +int GrGLCaps::maxRenderTargetSampleCount(GrGLFormat format) const { + const FormatInfo& info = this->getFormatInfo(format); + const auto& table = info.fColorSampleCounts; + if (!table.count()) { + return 0; + } + int count = table[table.count() - 1]; + if (fDriverBugWorkarounds.max_msaa_sample_count_4) { + count = SkTMin(count, 4); + } + return count; +} + +size_t GrGLCaps::bytesPerPixel(GrGLFormat format) const { + return this->getFormatInfo(format).fBytesPerPixel; +} + +size_t GrGLCaps::bytesPerPixel(const GrBackendFormat& format) const { + auto glFormat = format.asGLFormat(); + return this->bytesPerPixel(glFormat); +} + +bool GrGLCaps::canFormatBeFBOColorAttachment(GrGLFormat format) const { + return SkToBool(this->getFormatInfo(format).fFlags & FormatInfo::kFBOColorAttachment_Flag); +} + +bool GrGLCaps::isFormatCopyable(const GrBackendFormat& format) const { + // In GL we have three ways to be able to copy. CopyTexImage, blit, and draw. CopyTexImage + // requires the src to be an FBO attachment, blit requires both src and dst to be FBO + // attachments, and draw requires the dst to be an FBO attachment. Thus to copy from and to + // the same config, we need that config to be bindable to an FBO. + return this->canFormatBeFBOColorAttachment(format.asGLFormat()); +} + +bool GrGLCaps::formatSupportsTexStorage(GrGLFormat format) const { + return SkToBool(this->getFormatInfo(format).fFlags & FormatInfo::kUseTexStorage_Flag); +} + +static GrPixelConfig validate_sized_format(GrGLFormat format, + GrColorType ct, + GrGLStandard standard) { + switch (ct) { + case GrColorType::kUnknown: + return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_8: + if (format == GrGLFormat::kALPHA8) { + return kAlpha_8_as_Alpha_GrPixelConfig; + } else if (format == GrGLFormat::kR8) { + return kAlpha_8_as_Red_GrPixelConfig; + } + break; + case GrColorType::kBGR_565: + if (format == GrGLFormat::kRGB565) { + return kRGB_565_GrPixelConfig; + } + break; + case GrColorType::kABGR_4444: + if (format == GrGLFormat::kRGBA4) { + return kRGBA_4444_GrPixelConfig; + } + break; + case GrColorType::kRGBA_8888: + if (format == GrGLFormat::kRGBA8) { + return kRGBA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGBA_8888_SRGB: + if (format == GrGLFormat::kSRGB8_ALPHA8) { + return kSRGBA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGB_888x: + if (format == GrGLFormat::kRGB8) { + return kRGB_888_GrPixelConfig; + } else if (format == GrGLFormat::kRGBA8) { + return kRGB_888X_GrPixelConfig; + } else if (format == GrGLFormat::kCOMPRESSED_RGB8_ETC2 || + format == GrGLFormat::kCOMPRESSED_ETC1_RGB8) { + return kRGB_ETC1_GrPixelConfig; + } + break; + case GrColorType::kRG_88: + if (format == GrGLFormat::kRG8) { + return kRG_88_GrPixelConfig; + } + break; + case GrColorType::kBGRA_8888: + if (format == GrGLFormat::kRGBA8) { + if (GR_IS_GR_GL(standard)) { + return kBGRA_8888_GrPixelConfig; + } + } else if (format == GrGLFormat::kBGRA8) { + if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) { + return kBGRA_8888_GrPixelConfig; + } + } + break; + case GrColorType::kRGBA_1010102: + if (format == GrGLFormat::kRGB10_A2) { + return kRGBA_1010102_GrPixelConfig; + } + break; + case GrColorType::kGray_8: + if (format == GrGLFormat::kLUMINANCE8) { + return kGray_8_as_Lum_GrPixelConfig; + } else if (format == GrGLFormat::kR8) { + return kGray_8_as_Red_GrPixelConfig; + } + break; + case GrColorType::kAlpha_F16: + if (format == GrGLFormat::kLUMINANCE16F) { + return kAlpha_half_as_Lum_GrPixelConfig; + } else if (format == GrGLFormat::kR16F) { + return kAlpha_half_as_Red_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F16: + if (format == GrGLFormat::kRGBA16F) { + return kRGBA_half_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F16_Clamped: + if (format == GrGLFormat::kRGBA16F) { + return kRGBA_half_Clamped_GrPixelConfig; + } + break; + case GrColorType::kAlpha_16: + if (format == GrGLFormat::kR16) { + return kAlpha_16_GrPixelConfig; + } + break; + case GrColorType::kRG_1616: + if (format == GrGLFormat::kRG16) { + return kRG_1616_GrPixelConfig; + } + break; + case GrColorType::kRGBA_16161616: + if (format == GrGLFormat::kRGBA16) { + return kRGBA_16161616_GrPixelConfig; + } + break; + case GrColorType::kRG_F16: + if (format == GrGLFormat::kRG16F) { + return kRG_half_GrPixelConfig; + } + break; + + // These have no equivalent config: + case GrColorType::kRGBA_F32: + case GrColorType::kAlpha_8xxx: + case GrColorType::kAlpha_F32xxx: + case GrColorType::kGray_8xxx: + break; + } + + SkDebugf("Unknown pixel config 0x%x\n", format); + return kUnknown_GrPixelConfig; +} + +bool GrGLCaps::onAreColorTypeAndFormatCompatible(GrColorType ct, + const GrBackendFormat& format) const { + GrGLFormat glFormat = format.asGLFormat(); + const auto& info = this->getFormatInfo(glFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + if (info.fColorTypeInfos[i].fColorType == ct) { + return true; + } + } + return false; +} + +GrPixelConfig GrGLCaps::onGetConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType ct) const { + return validate_sized_format(format.asGLFormat(), ct, fStandard); +} + +GrColorType GrGLCaps::getYUVAColorTypeFromBackendFormat(const GrBackendFormat& format, + bool isAlphaChannel) const { + switch (format.asGLFormat()) { + case GrGLFormat::kLUMINANCE8: // /kGray_8_as_Lum_GrPixelConfig + case GrGLFormat::kR8: // kAlpha_8_as_Red_GrPixelConfig/kGray_8_as_Red_GrPixelConfig + case GrGLFormat::kALPHA8: // kAlpha_8_as_Alpha_GrPixelConfig/ + return isAlphaChannel ? GrColorType::kAlpha_8 + : GrColorType::kGray_8; + case GrGLFormat::kRG8: return GrColorType::kRG_88; + case GrGLFormat::kRGBA8: return GrColorType::kRGBA_8888; + case GrGLFormat::kRGB8: return GrColorType::kRGB_888x; + case GrGLFormat::kBGRA8: return GrColorType::kBGRA_8888; + case GrGLFormat::kRGB10_A2: return GrColorType::kRGBA_1010102; + case GrGLFormat::kLUMINANCE16F: // fall through + case GrGLFormat::kR16F: return GrColorType::kAlpha_F16; + case GrGLFormat::kR16: return GrColorType::kAlpha_16; + case GrGLFormat::kRG16: return GrColorType::kRG_1616; + case GrGLFormat::kRGBA16: return GrColorType::kRGBA_16161616; + case GrGLFormat::kRG16F: return GrColorType::kRG_F16; + default: return GrColorType::kUnknown; + } + + SkUNREACHABLE; +} + +GrBackendFormat GrGLCaps::onGetDefaultBackendFormat(GrColorType ct, + GrRenderable renderable) const { + // TODO: make use of renderable. + auto format = this->getFormatFromColorType(ct); + if (format == GrGLFormat::kUnknown) { + return GrBackendFormat(); + } + return GrBackendFormat::MakeGL(GrGLFormatToEnum(format), GR_GL_TEXTURE_2D); +} + +GrBackendFormat GrGLCaps::getBackendFormatFromCompressionType( + SkImage::CompressionType compressionType) const { + switch (compressionType) { + case SkImage::kETC1_CompressionType: + // if ETC2 is available default to that format + if (this->isFormatTexturable(GrGLFormat::kCOMPRESSED_RGB8_ETC2)) { + return GrBackendFormat::MakeGL(GR_GL_COMPRESSED_RGB8_ETC2, GR_GL_TEXTURE_2D); + } + return GrBackendFormat::MakeGL(GR_GL_COMPRESSED_ETC1_RGB8, GR_GL_TEXTURE_2D); + } + SK_ABORT("Invalid compression type"); +} + +GrSwizzle GrGLCaps::getTextureSwizzle(const GrBackendFormat& format, GrColorType colorType) const { + const auto& info = this->getFormatInfo(format.asGLFormat()); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == colorType) { + return ctInfo.fTextureSwizzle; + } + } + return GrSwizzle::RGBA(); +} +GrSwizzle GrGLCaps::getOutputSwizzle(const GrBackendFormat& format, GrColorType colorType) const { + const auto& info = this->getFormatInfo(format.asGLFormat()); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == colorType) { + return ctInfo.fOutputSwizzle; + } + } + return GrSwizzle::RGBA(); +} + +#if GR_TEST_UTILS +std::vector GrGLCaps::getTestingCombinations() const { + std::vector combos = { + { GrColorType::kAlpha_8, + GrBackendFormat::MakeGL(GR_GL_ALPHA8, GR_GL_TEXTURE_2D) }, + { GrColorType::kAlpha_8, + GrBackendFormat::MakeGL(GR_GL_R8, GR_GL_TEXTURE_2D) }, + { GrColorType::kBGR_565, + GrBackendFormat::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_2D) }, + { GrColorType::kABGR_4444, + GrBackendFormat::MakeGL(GR_GL_RGBA4, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGBA_8888, + GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGBA_8888_SRGB, + GrBackendFormat::MakeGL(GR_GL_SRGB8_ALPHA8, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGB_888x, + GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGB_888x, + GrBackendFormat::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGB_888x, + GrBackendFormat::MakeGL(GR_GL_COMPRESSED_RGB8_ETC2, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGB_888x, + GrBackendFormat::MakeGL(GR_GL_COMPRESSED_ETC1_RGB8, GR_GL_TEXTURE_2D) }, + { GrColorType::kRG_88, + GrBackendFormat::MakeGL(GR_GL_RG8, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGBA_1010102, + GrBackendFormat::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_2D) }, + { GrColorType::kGray_8, + GrBackendFormat::MakeGL(GR_GL_LUMINANCE8, GR_GL_TEXTURE_2D) }, + { GrColorType::kGray_8, + GrBackendFormat::MakeGL(GR_GL_R8, GR_GL_TEXTURE_2D) }, + { GrColorType::kAlpha_F16, + GrBackendFormat::MakeGL(GR_GL_R16F, GR_GL_TEXTURE_2D) }, + { GrColorType::kAlpha_F16, + GrBackendFormat::MakeGL(GR_GL_LUMINANCE16F, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGBA_F16, + GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGBA_F16_Clamped, + GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_2D) }, + { GrColorType::kAlpha_16, + GrBackendFormat::MakeGL(GR_GL_R16, GR_GL_TEXTURE_2D) }, + { GrColorType::kRG_1616, + GrBackendFormat::MakeGL(GR_GL_RG16, GR_GL_TEXTURE_2D) }, + { GrColorType::kRGBA_16161616, + GrBackendFormat::MakeGL(GR_GL_RGBA16, GR_GL_TEXTURE_2D) }, + { GrColorType::kRG_F16, + GrBackendFormat::MakeGL(GR_GL_RG16F, GR_GL_TEXTURE_2D) }, + }; + + if (GR_IS_GR_GL(fStandard)) { + combos.push_back({ GrColorType::kBGRA_8888, + GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_2D) }); + } else { + SkASSERT(GR_IS_GR_GL_ES(fStandard) || GR_IS_GR_WEBGL(fStandard)); + + combos.push_back({ GrColorType::kBGRA_8888, + GrBackendFormat::MakeGL(GR_GL_BGRA8, GR_GL_TEXTURE_2D) }); + } + + return combos; +} +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLCaps.h b/gfx/skia/skia/src/gpu/gl/GrGLCaps.h new file mode 100644 index 0000000000..ef10d76815 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLCaps.h @@ -0,0 +1,691 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLCaps_DEFINED +#define GrGLCaps_DEFINED + +#include +#include "include/private/GrGLTypesPriv.h" +#include "include/private/SkChecksum.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTHash.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/gl/GrGLStencilAttachment.h" +#include "src/gpu/gl/GrGLUtil.h" + +class GrGLContextInfo; +class GrGLRenderTarget; + +/** + * Stores some capabilities of a GL context. Most are determined by the GL + * version and the extensions string. It also tracks formats that have passed + * the FBO completeness test. + */ +class GrGLCaps : public GrCaps { +public: + typedef GrGLStencilAttachment::Format StencilFormat; + + /** + * The type of MSAA for FBOs supported. Different extensions have different + * semantics of how / when a resolve is performed. + */ + enum MSFBOType { + /** + * no support for MSAA FBOs + */ + kNone_MSFBOType = 0, + /** + * OpenGL 3.0+, OpenGL ES 3.0+, GL_ARB_framebuffer_object, + * GL_CHROMIUM_framebuffer_multisample, GL_ANGLE_framebuffer_multisample, + * or GL_EXT_framebuffer_multisample + */ + kStandard_MSFBOType, + /** + * GL_APPLE_framebuffer_multisample ES extension + */ + kES_Apple_MSFBOType, + /** + * GL_IMG_multisampled_render_to_texture. This variation does not have MSAA renderbuffers. + * Instead the texture is multisampled when bound to the FBO and then resolved automatically + * when read. It also defines an alternate value for GL_MAX_SAMPLES (which we call + * GR_GL_MAX_SAMPLES_IMG). + */ + kES_IMG_MsToTexture_MSFBOType, + /** + * GL_EXT_multisampled_render_to_texture. Same as the IMG one above but uses the standard + * GL_MAX_SAMPLES value. + */ + kES_EXT_MsToTexture_MSFBOType, + + kLast_MSFBOType = kES_EXT_MsToTexture_MSFBOType + }; + + enum BlitFramebufferFlags { + kNoSupport_BlitFramebufferFlag = 1 << 0, + kNoScalingOrMirroring_BlitFramebufferFlag = 1 << 1, + kResolveMustBeFull_BlitFrambufferFlag = 1 << 2, + kNoMSAADst_BlitFramebufferFlag = 1 << 3, + kNoFormatConversion_BlitFramebufferFlag = 1 << 4, + kNoFormatConversionForMSAASrc_BlitFramebufferFlag = 1 << 5, + kRectsMustMatchForMSAASrc_BlitFramebufferFlag = 1 << 6, + }; + + enum InvalidateFBType { + kNone_InvalidateFBType, + kDiscard_InvalidateFBType, //maxRenderTargetSampleCount(format); + } + + int getRenderTargetSampleCount(int requestedCount, + const GrBackendFormat& format) const override { + return this->getRenderTargetSampleCount(requestedCount, format.asGLFormat()); + } + int getRenderTargetSampleCount(int requestedCount, GrGLFormat) const; + + int maxRenderTargetSampleCount(const GrBackendFormat& format) const override { + return this->maxRenderTargetSampleCount(format.asGLFormat()); + } + int maxRenderTargetSampleCount(GrGLFormat) const; + + size_t bytesPerPixel(GrGLFormat) const; + size_t bytesPerPixel(const GrBackendFormat&) const override; + + bool isFormatCopyable(const GrBackendFormat&) const override; + + bool canFormatBeFBOColorAttachment(GrGLFormat) const; + + GrGLFormat getFormatFromColorType(GrColorType colorType) const { + int idx = static_cast(colorType); + return fColorTypeToFormatTable[idx]; + } + + /** + * Gets the internal format to use with glTexImage...() and glTexStorage...(). May be sized or + * base depending upon the GL. Not applicable to compressed textures. + */ + GrGLenum getTexImageOrStorageInternalFormat(GrGLFormat format) const { + return this->getFormatInfo(format).fInternalFormatForTexImageOrStorage; + } + + /** + * Gets the external format and type to pass to glTexImage2D with nullptr to create an + * uninitialized texture. See getTexImageOrStorageInternalFormat() for the internal format. + */ + void getTexImageExternalFormatAndType(GrGLFormat surfaceFormat, GrGLenum* externalFormat, + GrGLenum* externalType) const; + + /** + * Given a src data color type and a color type interpretation for a texture of a given format + * this provides the external GL format and type to use with glTexSubImage2d. The color types + * should originate from supportedWritePixelsColorType(). + */ + void getTexSubImageExternalFormatAndType(GrGLFormat surfaceFormat, GrColorType surfaceColorType, + GrColorType memoryColorType, GrGLenum* externalFormat, + GrGLenum* externalType) const; + + /** + * Gets the external format, type, and bytes per pixel to use when uploading zeros via + * glTexSubImage...() to clear the texture at creation. + */ + void getTexSubImageZeroFormatTypeAndBpp(GrGLFormat format, GrGLenum* externalFormat, + GrGLenum* externalType, size_t* bpp) const; + + void getReadPixelsFormat(GrGLFormat surfaceFormat, GrColorType surfaceColorType, + GrColorType memoryColorType, GrGLenum* externalFormat, + GrGLenum* externalType) const; + + /** + * Gets an array of legal stencil formats. These formats are not guaranteed + * to be supported by the driver but are legal GLenum names given the GL + * version and extensions supported. + */ + const SkTArray& stencilFormats() const { + return fStencilFormats; + } + + bool formatSupportsTexStorage(GrGLFormat) const; + + /** + * Gets the internal format to use with glRenderbufferStorageMultisample...(). May be sized or + * base depending upon the GL. Not applicable to compressed textures. + */ + GrGLenum getRenderbufferInternalFormat(GrGLFormat format) const { + return this->getFormatInfo(format).fInternalFormatForRenderbuffer; + } + + /** + * Gets the default external type to use with glTex[Sub]Image... when the data pointer is null. + */ + GrGLenum getFormatDefaultExternalType(GrGLFormat format) const { + return this->getFormatInfo(format).fDefaultExternalType; + } + + /** + * Has a stencil format index been found for the format (or we've found that no format works). + */ + bool hasStencilFormatBeenDeterminedForFormat(GrGLFormat format) const { + return this->getFormatInfo(format).fStencilFormatIndex != FormatInfo::kUnknown_StencilIndex; + } + + /** + * Gets the stencil format index for the format. This assumes + * hasStencilFormatBeenDeterminedForFormat has already been checked. Returns a value < 0 if + * no stencil format is supported with the format. Otherwise, returned index refers to the array + * returned by stencilFormats(). + */ + int getStencilFormatIndexForFormat(GrGLFormat format) const { + SkASSERT(this->hasStencilFormatBeenDeterminedForFormat(format)); + return this->getFormatInfo(format).fStencilFormatIndex; + } + + /** + * If index is >= 0 this records an index into stencilFormats() as the best stencil format for + * the format. If < 0 it records that the format has no supported stencil format index. + */ + void setStencilFormatIndexForFormat(GrGLFormat, int index); + + /** + * Reports the type of MSAA FBO support. + */ + MSFBOType msFBOType() const { return fMSFBOType; } + + /** + * Does the preferred MSAA FBO extension have MSAA renderbuffers? + */ + bool usesMSAARenderBuffers() const { + return kNone_MSFBOType != fMSFBOType && + kES_IMG_MsToTexture_MSFBOType != fMSFBOType && + kES_EXT_MsToTexture_MSFBOType != fMSFBOType; + } + + /** + * What functionality is supported by glBlitFramebuffer. + */ + uint32_t blitFramebufferSupportFlags() const { return fBlitFramebufferFlags; } + + /** + * Is the MSAA FBO extension one where the texture is multisampled when bound to an FBO and + * then implicitly resolved when read. + */ + bool usesImplicitMSAAResolve() const { + return kES_IMG_MsToTexture_MSFBOType == fMSFBOType || + kES_EXT_MsToTexture_MSFBOType == fMSFBOType; + } + + InvalidateFBType invalidateFBType() const { return fInvalidateFBType; } + + /// What type of buffer mapping is supported? + MapBufferType mapBufferType() const { return fMapBufferType; } + + /// What type of transfer buffer is supported? + TransferBufferType transferBufferType() const { return fTransferBufferType; } + + /// The maximum number of fragment uniform vectors (GLES has min. 16). + int maxFragmentUniformVectors() const { return fMaxFragmentUniformVectors; } + + /// Is there support for GL_PACK_REVERSE_ROW_ORDER + bool packFlipYSupport() const { return fPackFlipYSupport; } + + /// Is there support for texture parameter GL_TEXTURE_USAGE + bool textureUsageSupport() const { return fTextureUsageSupport; } + + /// Is GL_ARB_IMAGING supported + bool imagingSupport() const { return fImagingSupport; } + + /// Is there support for Vertex Array Objects? + bool vertexArrayObjectSupport() const { return fVertexArrayObjectSupport; } + + /// Is there support for GL_KHR_debug? + bool debugSupport() const { return fDebugSupport; } + + /// Is there support for ES2 compatability? + bool ES2CompatibilitySupport() const { return fES2CompatibilitySupport; } + + /// Is there support for glDraw*Instanced? + bool drawInstancedSupport() const { return fDrawInstancedSupport; } + + /// Is there support for glDraw*Indirect? Note that the baseInstance fields of indirect draw + /// commands cannot be used unless we have base instance support. + bool drawIndirectSupport() const { return fDrawIndirectSupport; } + + /// Is there support for glMultiDraw*Indirect? Note that the baseInstance fields of indirect + /// draw commands cannot be used unless we have base instance support. + bool multiDrawIndirectSupport() const { return fMultiDrawIndirectSupport; } + + /// Is there support for glDrawRangeElements? + bool drawRangeElementsSupport() const { return fDrawRangeElementsSupport; } + + /// Are the baseInstance fields supported in indirect draw commands? + bool baseInstanceSupport() const { return fBaseInstanceSupport; } + + /// Use indices or vertices in CPU arrays rather than VBOs for dynamic content. + bool useNonVBOVertexAndIndexDynamicData() const { return fUseNonVBOVertexAndIndexDynamicData; } + + SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const override; + + SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const override; + + bool isCoreProfile() const { return fIsCoreProfile; } + + bool bindFragDataLocationSupport() const { return fBindFragDataLocationSupport; } + + bool bindUniformLocationSupport() const { return fBindUniformLocationSupport; } + + /// Are textures with GL_TEXTURE_RECTANGLE type supported. + bool rectangleTextureSupport() const { return fRectangleTextureSupport; } + + bool mipMapLevelAndLodControlSupport() const { return fMipMapLevelAndLodControlSupport; } + + bool doManualMipmapping() const { return fDoManualMipmapping; } + + void onDumpJSON(SkJSONWriter*) const override; + + bool rgba8888PixelsOpsAreSlow() const { return fRGBA8888PixelsOpsAreSlow; } + bool partialFBOReadIsSlow() const { return fPartialFBOReadIsSlow; } + bool rgbaToBgraReadbackConversionsAreSlow() const { + return fRGBAToBGRAReadbackConversionsAreSlow; + } + + bool useBufferDataNullHint() const { return fUseBufferDataNullHint; } + + // Certain Intel GPUs on Mac fail to clear if the glClearColor is made up of only 1s and 0s. + bool clearToBoundaryValuesIsBroken() const { return fClearToBoundaryValuesIsBroken; } + + /// glClearTex(Sub)Image support + bool clearTextureSupport() const { return fClearTextureSupport; } + + // Adreno/MSAA drops a draw on the imagefiltersbase GM if the base vertex param to + // glDrawArrays is nonzero. + // https://bugs.chromium.org/p/skia/issues/detail?id=6650 + bool drawArraysBaseVertexIsBroken() const { return fDrawArraysBaseVertexIsBroken; } + + // If true then we must use an intermediate surface to perform partial updates to unorm textures + // that have ever been bound to a FBO. + bool disallowTexSubImageForUnormConfigTexturesEverBoundToFBO() const { + return fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO; + } + + // Use an intermediate surface to write pixels (full or partial overwrite) to into a texture + // that is bound to an FBO. + bool useDrawInsteadOfAllRenderTargetWrites() const { + return fUseDrawInsteadOfAllRenderTargetWrites; + } + + // At least some Adreno 3xx drivers draw lines incorrectly after drawing non-lines. Toggling + // face culling on and off seems to resolve this. + bool requiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines() const { + return fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines; + } + + // Some Adreno drivers refuse to ReadPixels from an MSAA buffer that has stencil attached. + bool detachStencilFromMSAABuffersBeforeReadPixels() const { + return fDetachStencilFromMSAABuffersBeforeReadPixels; + } + + // Older Android versions seem to have an issue with setting GL_TEXTURE_BASE_LEVEL or + // GL_TEXTURE_MAX_LEVEL for GL_TEXTURE_EXTERNAL_OES textures. + bool dontSetBaseOrMaxLevelForExternalTextures() const { + return fDontSetBaseOrMaxLevelForExternalTextures; + } + + // PowerVRGX6250 drops every pixel if we modify the sample mask while color writes are disabled. + bool neverDisableColorWrites() const { return fNeverDisableColorWrites; } + + // Returns the observed maximum number of instances the driver can handle in a single draw call + // without crashing, or 'pendingInstanceCount' if this workaround is not necessary. + // NOTE: the return value may be larger than pendingInstanceCount. + int maxInstancesPerDrawWithoutCrashing(int pendingInstanceCount) const { + return (fMaxInstancesPerDrawWithoutCrashing) + ? fMaxInstancesPerDrawWithoutCrashing : pendingInstanceCount; + } + + bool canCopyTexSubImage(GrGLFormat dstFormat, bool dstHasMSAARenderBuffer, + const GrTextureType* dstTypeIfTexture, + GrGLFormat srcFormat, bool srcHasMSAARenderBuffer, + const GrTextureType* srcTypeIfTexture) const; + bool canCopyAsBlit(GrGLFormat dstFormat, int dstSampleCnt, + const GrTextureType* dstTypeIfTexture, + GrGLFormat srcFormat, int srcSampleCnt, + const GrTextureType* srcTypeIfTexture, + const SkRect& srcBounds, bool srcBoundsExact, + const SkIRect& srcRect, const SkIPoint& dstPoint) const; + bool canCopyAsDraw(GrGLFormat dstFormat, bool srcIsTexturable) const; + + DstCopyRestrictions getDstCopyRestrictions(const GrRenderTargetProxy* src, + GrColorType) const override; + + bool programBinarySupport() const { return fProgramBinarySupport; } + bool programParameterSupport() const { return fProgramParameterSupport; } + + bool samplerObjectSupport() const { return fSamplerObjectSupport; } + + bool tiledRenderingSupport() const { return fTiledRenderingSupport; } + + bool fbFetchRequiresEnablePerSample() const { return fFBFetchRequiresEnablePerSample; } + + /* Is there support for enabling/disabling sRGB writes for sRGB-capable color buffers? */ + bool srgbWriteControl() const { return fSRGBWriteControl; } + + GrColorType getYUVAColorTypeFromBackendFormat(const GrBackendFormat&, + bool isAlphaChannel) const override; + + GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override; + + GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override; + GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override; + +#if GR_TEST_UTILS + GrGLStandard standard() const { return fStandard; } + + std::vector getTestingCombinations() const override; +#endif + +private: + enum ExternalFormatUsage { + kTexImage_ExternalFormatUsage, + kReadPixels_ExternalFormatUsage, + }; + void getExternalFormat(GrGLFormat surfaceFormat, GrColorType surfaceColorType, + GrColorType memoryColorType, ExternalFormatUsage usage, + GrGLenum* externalFormat, GrGLenum* externalType) const; + + void init(const GrContextOptions&, const GrGLContextInfo&, const GrGLInterface*); + void initGLSL(const GrGLContextInfo&, const GrGLInterface*); + bool hasPathRenderingSupport(const GrGLContextInfo&, const GrGLInterface*); + + struct FormatWorkarounds { + bool fDisableSRGBRenderWithMSAAForMacAMD = false; + bool fDisableRGBA16FTexStorageForCrBug1008003 = false; + bool fDisableBGRATextureStorageForIntelWindowsES = false; + bool fDisableRGB8ForMali400 = false; + bool fDisableLuminance16F = false; + bool fDontDisableTexStorageOnAndroid = false; + }; + + void applyDriverCorrectnessWorkarounds(const GrGLContextInfo&, const GrContextOptions&, + GrShaderCaps*, FormatWorkarounds*); + + void onApplyOptionsOverrides(const GrContextOptions& options) override; + + bool onIsWindowRectanglesSupportedForRT(const GrBackendRenderTarget&) const override; + + void initFSAASupport(const GrContextOptions& contextOptions, const GrGLContextInfo&, + const GrGLInterface*); + void initBlendEqationSupport(const GrGLContextInfo&); + void initStencilSupport(const GrGLContextInfo&); + // This must be called after initFSAASupport(). + void initFormatTable(const GrGLContextInfo&, const GrGLInterface*, const FormatWorkarounds&); + void setupSampleCounts(const GrGLContextInfo&, const GrGLInterface*); + bool onSurfaceSupportsWritePixels(const GrSurface*) const override; + bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const override; + GrBackendFormat onGetDefaultBackendFormat(GrColorType, GrRenderable) const override; + GrPixelConfig onGetConfigFromBackendFormat(const GrBackendFormat&, GrColorType) const override; + bool onAreColorTypeAndFormatCompatible(GrColorType, const GrBackendFormat&) const override; + + SupportedRead onSupportedReadPixelsColorType(GrColorType, const GrBackendFormat&, + GrColorType) const override; + + GrGLStandard fStandard; + + SkTArray fStencilFormats; + + int fMaxFragmentUniformVectors; + + MSFBOType fMSFBOType; + InvalidateFBType fInvalidateFBType; + MapBufferType fMapBufferType; + TransferBufferType fTransferBufferType; + + bool fPackFlipYSupport : 1; + bool fTextureUsageSupport : 1; + bool fImagingSupport : 1; + bool fVertexArrayObjectSupport : 1; + bool fDebugSupport : 1; + bool fES2CompatibilitySupport : 1; + bool fDrawInstancedSupport : 1; + bool fDrawIndirectSupport : 1; + bool fDrawRangeElementsSupport : 1; + bool fMultiDrawIndirectSupport : 1; + bool fBaseInstanceSupport : 1; + bool fUseNonVBOVertexAndIndexDynamicData : 1; + bool fIsCoreProfile : 1; + bool fBindFragDataLocationSupport : 1; + bool fRGBA8888PixelsOpsAreSlow : 1; + bool fPartialFBOReadIsSlow : 1; + bool fBindUniformLocationSupport : 1; + bool fRectangleTextureSupport : 1; + bool fMipMapLevelAndLodControlSupport : 1; + bool fRGBAToBGRAReadbackConversionsAreSlow : 1; + bool fUseBufferDataNullHint : 1; + bool fClearTextureSupport : 1; + bool fProgramBinarySupport : 1; + bool fProgramParameterSupport : 1; + bool fSamplerObjectSupport : 1; + bool fTiledRenderingSupport : 1; + bool fFBFetchRequiresEnablePerSample : 1; + bool fSRGBWriteControl : 1; + + // Driver workarounds + bool fDoManualMipmapping : 1; + bool fClearToBoundaryValuesIsBroken : 1; + bool fDrawArraysBaseVertexIsBroken : 1; + bool fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO : 1; + bool fUseDrawInsteadOfAllRenderTargetWrites : 1; + bool fRequiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines : 1; + bool fDetachStencilFromMSAABuffersBeforeReadPixels : 1; + bool fDontSetBaseOrMaxLevelForExternalTextures : 1; + bool fNeverDisableColorWrites : 1; + int fMaxInstancesPerDrawWithoutCrashing; + + uint32_t fBlitFramebufferFlags; + + struct ReadPixelsFormat { + ReadPixelsFormat() : fFormat(0), fType(0) {} + GrGLenum fFormat; + GrGLenum fType; + }; + + /** Number type of the components (with out considering number of bits.) */ + enum class FormatType { + kUnknown, + kNormalizedFixedPoint, + kFloat, + }; + + // ColorTypeInfo for a specific format + struct ColorTypeInfo { + GrColorType fColorType = GrColorType::kUnknown; + enum { + kUploadData_Flag = 0x1, + // Does Ganesh itself support rendering to this colorType & format pair. Renderability + // still additionally depends on if the format can be an FBO color attachment. + kRenderable_Flag = 0x2, + }; + uint32_t fFlags = 0; + + GrSwizzle fTextureSwizzle; + GrSwizzle fOutputSwizzle; + + struct ExternalIOFormats { + GrColorType fColorType = GrColorType::kUnknown; + + /** The external format and type are to be used when uploading/downloading data using + data of fColorType and uploading to a texture of a given GrGLFormat and its + intended GrColorType. The fExternalTexImageFormat is the format to use for TexImage + calls. The fExternalReadFormat is used when calling ReadPixels. If either is zero + that signals that either TexImage or ReadPixels is not supported for the combination + of format and color types. */ + GrGLenum fExternalType = 0; + GrGLenum fExternalTexImageFormat = 0; + GrGLenum fExternalReadFormat = 0; + }; + + GrGLenum externalFormat(GrColorType externalColorType, ExternalFormatUsage usage) const { + for (int i = 0; i < fExternalIOFormatCount; ++i) { + if (fExternalIOFormats[i].fColorType == externalColorType) { + if (usage == kTexImage_ExternalFormatUsage) { + return fExternalIOFormats[i].fExternalTexImageFormat; + } else { + SkASSERT(usage == kReadPixels_ExternalFormatUsage); + return fExternalIOFormats[i].fExternalReadFormat; + } + } + } + return 0; + } + + GrGLenum externalType(GrColorType externalColorType) const { + for (int i = 0; i < fExternalIOFormatCount; ++i) { + if (fExternalIOFormats[i].fColorType == externalColorType) { + return fExternalIOFormats[i].fExternalType; + } + } + return 0; + } + + std::unique_ptr fExternalIOFormats; + int fExternalIOFormatCount = 0; + }; + + struct FormatInfo { + uint32_t colorTypeFlags(GrColorType colorType) const { + for (int i = 0; i < fColorTypeInfoCount; ++i) { + if (fColorTypeInfos[i].fColorType == colorType) { + return fColorTypeInfos[i].fFlags; + } + } + return 0; + } + + GrGLenum externalFormat(GrColorType surfaceColorType, GrColorType externalColorType, + ExternalFormatUsage usage) const { + for (int i = 0; i < fColorTypeInfoCount; ++i) { + if (fColorTypeInfos[i].fColorType == surfaceColorType) { + return fColorTypeInfos[i].externalFormat(externalColorType, usage); + } + } + return 0; + } + + GrGLenum externalType(GrColorType surfaceColorType, GrColorType externalColorType) const { + for (int i = 0; i < fColorTypeInfoCount; ++i) { + if (fColorTypeInfos[i].fColorType == surfaceColorType) { + return fColorTypeInfos[i].externalType(externalColorType); + } + } + return 0; + } + + enum { + kTexturable_Flag = 0x1, + /** kFBOColorAttachment means that even if the format cannot be a GrRenderTarget, we can + still attach it to a FBO for blitting or reading pixels. */ + kFBOColorAttachment_Flag = 0x2, + kFBOColorAttachmentWithMSAA_Flag = 0x4, + kUseTexStorage_Flag = 0x8, + }; + uint32_t fFlags = 0; + + FormatType fFormatType = FormatType::kUnknown; + + // Not defined for uncompressed formats. Passed to glCompressedTexImage... + GrGLenum fCompressedInternalFormat = 0; + + // Value to uses as the "internalformat" argument to glTexImage or glTexStorage. It is + // initialized in coordination with the presence/absence of the kUseTexStorage flag. In + // other words, it is only guaranteed to be compatible with glTexImage if the flag is not + // set and or with glTexStorage if the flag is set. + GrGLenum fInternalFormatForTexImageOrStorage = 0; + + // Value to uses as the "internalformat" argument to glRenderbufferStorageMultisample... + GrGLenum fInternalFormatForRenderbuffer = 0; + + // Default values to use along with fInternalFormatForTexImageOrStorage for function + // glTexImage2D when not input providing data (passing nullptr). Not defined for compressed + // formats. Also used to upload zeros to initially clear a texture. + GrGLenum fDefaultExternalFormat = 0; + GrGLenum fDefaultExternalType = 0; + + // This value is only valid for regular formats. Compressed formats will be 0. + GrGLenum fBytesPerPixel = 0; + + enum { + // This indicates that a stencil format has not yet been determined for the config. + kUnknown_StencilIndex = -1, + // This indicates that there is no supported stencil format for the config. + kUnsupported_StencilFormatIndex = -2 + }; + + // Index fStencilFormats. + int fStencilFormatIndex = kUnknown_StencilIndex; + + SkTDArray fColorSampleCounts; + + std::unique_ptr fColorTypeInfos; + int fColorTypeInfoCount = 0; + }; + + FormatInfo fFormatTable[kGrGLFormatCount]; + + FormatInfo& getFormatInfo(GrGLFormat format) { return fFormatTable[static_cast(format)]; } + const FormatInfo& getFormatInfo(GrGLFormat format) const { + return fFormatTable[static_cast(format)]; + } + + GrGLFormat fColorTypeToFormatTable[kGrColorTypeCnt]; + void setColorTypeFormat(GrColorType, GrGLFormat); + + typedef GrCaps INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLContext.cpp b/gfx/skia/skia/src/gpu/gl/GrGLContext.cpp new file mode 100644 index 0000000000..14a40704f6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLContext.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLContext.h" +#include "src/gpu/gl/GrGLGLSL.h" +#include "src/sksl/SkSLCompiler.h" + +#ifdef SK_BUILD_FOR_ANDROID +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrGLContext::Make(sk_sp interface, + const GrContextOptions& options) { + if (!interface->validate()) { + return nullptr; + } + + const GrGLubyte* verUByte; + GR_GL_CALL_RET(interface.get(), verUByte, GetString(GR_GL_VERSION)); + const char* ver = reinterpret_cast(verUByte); + + const GrGLubyte* rendererUByte; + GR_GL_CALL_RET(interface.get(), rendererUByte, GetString(GR_GL_RENDERER)); + const char* renderer = reinterpret_cast(rendererUByte); + + ConstructorArgs args; + args.fGLVersion = GrGLGetVersionFromString(ver); + if (GR_GL_INVALID_VER == args.fGLVersion) { + return nullptr; + } + + if (!GrGLGetGLSLGeneration(interface.get(), &args.fGLSLGeneration)) { + return nullptr; + } + + args.fVendor = GrGLGetVendor(interface.get()); + + args.fRenderer = GrGLGetRendererFromStrings(renderer, interface->fExtensions); + + GrGLGetANGLEInfoFromString(renderer, &args.fANGLEBackend, &args.fANGLEVendor, + &args.fANGLERenderer); + + /* + * Qualcomm drivers for the 3xx series have a horrendous bug with some drivers. Though they + * claim to support GLES 3.00, some perfectly valid GLSL300 shaders will only compile with + * #version 100, and will fail to compile with #version 300 es. In the long term, we + * need to lock this down to a specific driver version. + * ?????/2019 - Qualcomm has fixed this for Android O+ devices (API 26+) + * ?????/2015 - This bug is still present in Lollipop pre-mr1 + * 06/18/2015 - This bug does not affect the nexus 6 (which has an Adreno 4xx). + */ +#ifdef SK_BUILD_FOR_ANDROID + if (!options.fDisableDriverCorrectnessWorkarounds && + kAdreno3xx_GrGLRenderer == args.fRenderer) { + char androidAPIVersion[PROP_VALUE_MAX]; + int strLength = __system_property_get("ro.build.version.sdk", androidAPIVersion); + if (strLength == 0 || atoi(androidAPIVersion) < 26) { + args.fGLSLGeneration = k110_GrGLSLGeneration; + } + } +#endif + + // Many ES3 drivers only advertise the ES2 image_external extension, but support the _essl3 + // extension, and require that it be enabled to work with ESSL3. Other devices require the ES2 + // extension to be enabled, even when using ESSL3. Some devices appear to only support the ES2 + // extension. As an extreme (optional) solution, we can fallback to using ES2 shading language + // if we want to prioritize external texture support. skbug.com/7713 + if (GR_IS_GR_GL_ES(interface->fStandard) && + options.fPreferExternalImagesOverES3 && + !options.fDisableDriverCorrectnessWorkarounds && + interface->hasExtension("GL_OES_EGL_image_external") && + args.fGLSLGeneration >= k330_GrGLSLGeneration && + !interface->hasExtension("GL_OES_EGL_image_external_essl3") && + !interface->hasExtension("OES_EGL_image_external_essl3")) { + args.fGLSLGeneration = k110_GrGLSLGeneration; + } + + GrGLGetDriverInfo(interface->fStandard, args.fVendor, renderer, ver, + &args.fDriver, &args.fDriverVersion); + + args.fContextOptions = &options; + args.fInterface = std::move(interface); + + return std::unique_ptr(new GrGLContext(std::move(args))); +} + +GrGLContext::~GrGLContext() { + delete fCompiler; +} + +SkSL::Compiler* GrGLContext::compiler() const { + if (!fCompiler) { + fCompiler = new SkSL::Compiler(); + } + return fCompiler; +} + +GrGLContextInfo::GrGLContextInfo(ConstructorArgs&& args) { + fInterface = std::move(args.fInterface); + fGLVersion = args.fGLVersion; + fGLSLGeneration = args.fGLSLGeneration; + fVendor = args.fVendor; + fRenderer = args.fRenderer; + fDriver = args.fDriver; + fDriverVersion = args.fDriverVersion; + fANGLEBackend = args.fANGLEBackend; + fANGLEVendor = args.fANGLEVendor; + fANGLERenderer = args.fANGLERenderer; + + fGLCaps = sk_make_sp(*args.fContextOptions, *this, fInterface.get()); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLContext.h b/gfx/skia/skia/src/gpu/gl/GrGLContext.h new file mode 100644 index 0000000000..5ea81781fe --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLContext.h @@ -0,0 +1,109 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLContext_DEFINED +#define GrGLContext_DEFINED + +#include "include/gpu/gl/GrGLExtensions.h" +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/gl/GrGLCaps.h" +#include "src/gpu/gl/GrGLUtil.h" +#include "src/gpu/glsl/GrGLSL.h" + +struct GrContextOptions; +namespace SkSL { + class Compiler; +} + +/** + * Encapsulates information about an OpenGL context including the OpenGL + * version, the GrGLStandard type of the context, and GLSL version. + */ +class GrGLContextInfo { +public: + GrGLContextInfo(const GrGLContextInfo&) = delete; + GrGLContextInfo& operator=(const GrGLContextInfo&) = delete; + + virtual ~GrGLContextInfo() {} + + GrGLStandard standard() const { return fInterface->fStandard; } + GrGLVersion version() const { return fGLVersion; } + GrGLSLGeneration glslGeneration() const { return fGLSLGeneration; } + GrGLVendor vendor() const { return fVendor; } + GrGLRenderer renderer() const { return fRenderer; } + GrGLANGLEBackend angleBackend() const { return fANGLEBackend; } + GrGLANGLEVendor angleVendor() const { return fANGLEVendor; } + GrGLANGLERenderer angleRenderer() const { return fANGLERenderer; } + /** What driver is running our GL implementation? This is not necessarily related to the vendor. + (e.g. Intel GPU being driven by Mesa) */ + GrGLDriver driver() const { return fDriver; } + GrGLDriverVersion driverVersion() const { return fDriverVersion; } + const GrGLCaps* caps() const { return fGLCaps.get(); } + GrGLCaps* caps() { return fGLCaps.get(); } + bool hasExtension(const char* ext) const { + return fInterface->hasExtension(ext); + } + + const GrGLExtensions& extensions() const { return fInterface->fExtensions; } + +protected: + struct ConstructorArgs { + sk_sp fInterface; + GrGLVersion fGLVersion; + GrGLSLGeneration fGLSLGeneration; + GrGLVendor fVendor; + GrGLRenderer fRenderer; + GrGLDriver fDriver; + GrGLDriverVersion fDriverVersion; + GrGLANGLEBackend fANGLEBackend; + GrGLANGLEVendor fANGLEVendor; + GrGLANGLERenderer fANGLERenderer; + const GrContextOptions* fContextOptions; + }; + + GrGLContextInfo(ConstructorArgs&&); + + sk_sp fInterface; + GrGLVersion fGLVersion; + GrGLSLGeneration fGLSLGeneration; + GrGLVendor fVendor; + GrGLRenderer fRenderer; + GrGLDriver fDriver; + GrGLDriverVersion fDriverVersion; + GrGLANGLEBackend fANGLEBackend; + GrGLANGLEVendor fANGLEVendor; + GrGLANGLERenderer fANGLERenderer; + sk_sp fGLCaps; +}; + +/** + * Extension of GrGLContextInfo that also provides access to GrGLInterface and SkSL::Compiler. + */ +class GrGLContext : public GrGLContextInfo { +public: + /** + * Creates a GrGLContext from a GrGLInterface and the currently + * bound OpenGL context accessible by the GrGLInterface. + */ + static std::unique_ptr Make(sk_sp, const GrContextOptions&); + + const GrGLInterface* interface() const { return fInterface.get(); } + + SkSL::Compiler* compiler() const; + + ~GrGLContext() override; + +private: + GrGLContext(ConstructorArgs&& args) : INHERITED(std::move(args)), fCompiler(nullptr) {} + + mutable SkSL::Compiler* fCompiler; + + typedef GrGLContextInfo INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLDefines.h b/gfx/skia/skia/src/gpu/gl/GrGLDefines.h new file mode 100644 index 0000000000..1a6d6c8058 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLDefines.h @@ -0,0 +1,1112 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#ifndef GrGLDefines_DEFINED +#define GrGLDefines_DEFINED + +/* Profiles */ +#define GR_GL_CONTEXT_PROFILE_MASK 0x9126 +#define GR_GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GR_GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 + +// The following constants consist of the intersection of GL constants +// exported by GLES 1.0, GLES 2.0, and desktop GL required by the system. + +#define GR_GL_DEPTH_BUFFER_BIT 0x00000100 +#define GR_GL_STENCIL_BUFFER_BIT 0x00000400 +#define GR_GL_COLOR_BUFFER_BIT 0x00004000 + +/* Boolean */ +#define GR_GL_FALSE 0 +#define GR_GL_TRUE 1 + +/* BeginMode */ +#define GR_GL_POINTS 0x0000 +#define GR_GL_LINES 0x0001 +#define GR_GL_LINE_LOOP 0x0002 +#define GR_GL_LINE_STRIP 0x0003 +#define GR_GL_TRIANGLES 0x0004 +#define GR_GL_TRIANGLE_STRIP 0x0005 +#define GR_GL_TRIANGLE_FAN 0x0006 + +/* AlphaFunction (not supported in ES20) */ +/* GL_NEVER */ +/* GL_LESS */ +/* GL_EQUAL */ +/* GL_LEQUAL */ +/* GL_GREATER */ +/* GL_NOTEQUAL */ +/* GL_GEQUAL */ +/* GL_ALWAYS */ + +/* Basic OpenGL blend equations */ +#define GR_GL_FUNC_ADD 0x8006 +#define GR_GL_FUNC_SUBTRACT 0x800A +#define GR_GL_FUNC_REVERSE_SUBTRACT 0x800B + +/* GL_KHR_blend_equation_advanced */ +#define GR_GL_SCREEN 0x9295 +#define GR_GL_OVERLAY 0x9296 +#define GR_GL_DARKEN 0x9297 +#define GR_GL_LIGHTEN 0x9298 +#define GR_GL_COLORDODGE 0x9299 +#define GR_GL_COLORBURN 0x929A +#define GR_GL_HARDLIGHT 0x929B +#define GR_GL_SOFTLIGHT 0x929C +#define GR_GL_DIFFERENCE 0x929E +#define GR_GL_EXCLUSION 0x92A0 +#define GR_GL_MULTIPLY 0x9294 +#define GR_GL_HSL_HUE 0x92AD +#define GR_GL_HSL_SATURATION 0x92AE +#define GR_GL_HSL_COLOR 0x92AF +#define GR_GL_HSL_LUMINOSITY 0x92B0 + +/* BlendingFactorDest */ +#define GR_GL_ZERO 0 +#define GR_GL_ONE 1 +#define GR_GL_SRC_COLOR 0x0300 +#define GR_GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GR_GL_SRC_ALPHA 0x0302 +#define GR_GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GR_GL_DST_ALPHA 0x0304 +#define GR_GL_ONE_MINUS_DST_ALPHA 0x0305 + +/* BlendingFactorSrc */ +/* GL_ZERO */ +/* GL_ONE */ +#define GR_GL_DST_COLOR 0x0306 +#define GR_GL_ONE_MINUS_DST_COLOR 0x0307 +#define GR_GL_SRC_ALPHA_SATURATE 0x0308 +/* GL_SRC_ALPHA */ +/* GL_ONE_MINUS_SRC_ALPHA */ +/* GL_DST_ALPHA */ +/* GL_ONE_MINUS_DST_ALPHA */ + +/* ExtendedBlendFactors */ +#define GR_GL_SRC1_COLOR 0x88F9 +#define GR_GL_ONE_MINUS_SRC1_COLOR 0x88FA +/* GL_SRC1_ALPHA */ +#define GR_GL_ONE_MINUS_SRC1_ALPHA 0x88FB + +/* Separate Blend Functions */ +#define GR_GL_BLEND_DST_RGB 0x80C8 +#define GR_GL_BLEND_SRC_RGB 0x80C9 +#define GR_GL_BLEND_DST_ALPHA 0x80CA +#define GR_GL_BLEND_SRC_ALPHA 0x80CB +#define GR_GL_CONSTANT_COLOR 0x8001 +#define GR_GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GR_GL_CONSTANT_ALPHA 0x8003 +#define GR_GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GR_GL_BLEND_COLOR 0x8005 + +/* Buffer Objects */ +#define GR_GL_ARRAY_BUFFER 0x8892 +#define GR_GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GR_GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GR_GL_TEXTURE_BUFFER 0x8C2A +#define GR_GL_ARRAY_BUFFER_BINDING 0x8894 +#define GR_GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GR_GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#define GR_GL_PIXEL_PACK_BUFFER 0x88EB +#define GR_GL_PIXEL_UNPACK_BUFFER 0x88EC + +#define GR_GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM 0x78EC +#define GR_GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM 0x78ED + +#define GR_GL_STREAM_DRAW 0x88E0 +#define GR_GL_STREAM_READ 0x88E1 +#define GR_GL_STATIC_DRAW 0x88E4 +#define GR_GL_STATIC_READ 0x88E5 +#define GR_GL_DYNAMIC_DRAW 0x88E8 +#define GR_GL_DYNAMIC_READ 0x88E9 + +#define GR_GL_BUFFER_SIZE 0x8764 +#define GR_GL_BUFFER_USAGE 0x8765 + +#define GR_GL_CURRENT_VERTEX_ATTRIB 0x8626 + +/* CullFaceMode */ +#define GR_GL_FRONT 0x0404 +#define GR_GL_BACK 0x0405 +#define GR_GL_FRONT_AND_BACK 0x0408 + +/* DepthFunction */ +/* GL_NEVER */ +/* GL_LESS */ +/* GL_EQUAL */ +/* GL_LEQUAL */ +/* GL_GREATER */ +/* GL_NOTEQUAL */ +/* GL_GEQUAL */ +/* GL_ALWAYS */ + +/* EnableCap */ +#define GR_GL_TEXTURE_NONE 0x0000 +#define GR_GL_TEXTURE_2D 0x0DE1 +#define GR_GL_CULL_FACE 0x0B44 +#define GR_GL_BLEND 0x0BE2 +#define GR_GL_DITHER 0x0BD0 +#define GR_GL_STENCIL_TEST 0x0B90 +#define GR_GL_DEPTH_TEST 0x0B71 +#define GR_GL_SCISSOR_TEST 0x0C11 +#define GR_GL_POLYGON_OFFSET_FILL 0x8037 +#define GR_GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GR_GL_SAMPLE_COVERAGE 0x80A0 +#define GR_GL_POLYGON_OFFSET_FILL 0x8037 +#define GR_GL_POLYGON_SMOOTH 0x0B41 +#define GR_GL_POLYGON_STIPPLE 0x0B42 +#define GR_GL_COLOR_LOGIC_OP 0x0BF2 +#define GR_GL_COLOR_TABLE 0x80D0 +#define GR_GL_INDEX_LOGIC_OP 0x0BF1 +#define GR_GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GR_GL_LINE_STIPPLE 0x0B24 +#define GR_GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GR_GL_SHADER_PIXEL_LOCAL_STORAGE 0x8F64 +#define GR_GL_SAMPLE_SHADING 0x8C36 + +/* ErrorCode */ +#define GR_GL_NO_ERROR 0 +#define GR_GL_INVALID_ENUM 0x0500 +#define GR_GL_INVALID_VALUE 0x0501 +#define GR_GL_INVALID_OPERATION 0x0502 +#define GR_GL_OUT_OF_MEMORY 0x0505 +#define GR_GL_CONTEXT_LOST 0x300E // TODO(gman): What value? + +/* FrontFaceDirection */ +#define GR_GL_CW 0x0900 +#define GR_GL_CCW 0x0901 + +/* GetPName */ +#define GR_GL_LINE_WIDTH 0x0B21 +#define GR_GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GR_GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GR_GL_CULL_FACE_MODE 0x0B45 +#define GR_GL_FRONT_FACE 0x0B46 +#define GR_GL_DEPTH_RANGE 0x0B70 +#define GR_GL_DEPTH_WRITEMASK 0x0B72 +#define GR_GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GR_GL_DEPTH_FUNC 0x0B74 +#define GR_GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GR_GL_STENCIL_FUNC 0x0B92 +#define GR_GL_STENCIL_FAIL 0x0B94 +#define GR_GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GR_GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GR_GL_STENCIL_REF 0x0B97 +#define GR_GL_STENCIL_VALUE_MASK 0x0B93 +#define GR_GL_STENCIL_WRITEMASK 0x0B98 +#define GR_GL_STENCIL_BACK_FUNC 0x8800 +#define GR_GL_STENCIL_BACK_FAIL 0x8801 +#define GR_GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GR_GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GR_GL_STENCIL_BACK_REF 0x8CA3 +#define GR_GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GR_GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GR_GL_VIEWPORT 0x0BA2 +#define GR_GL_SCISSOR_BOX 0x0C10 +/* GL_SCISSOR_TEST */ +#define GR_GL_COLOR_CLEAR_VALUE 0x0C22 +#define GR_GL_COLOR_WRITEMASK 0x0C23 +#define GR_GL_UNPACK_ALIGNMENT 0x0CF5 +#define GR_GL_PACK_ALIGNMENT 0x0D05 +#define GR_GL_PACK_REVERSE_ROW_ORDER 0x93A4 +#define GR_GL_MAX_TEXTURE_SIZE 0x0D33 +#define GR_GL_TEXTURE_MIN_LOD 0x813A +#define GR_GL_TEXTURE_MAX_LOD 0x813B +#define GR_GL_TEXTURE_BASE_LEVEL 0x813C +#define GR_GL_TEXTURE_MAX_LEVEL 0x813D +#define GR_GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GR_GL_SUBPIXEL_BITS 0x0D50 +#define GR_GL_RED_BITS 0x0D52 +#define GR_GL_GREEN_BITS 0x0D53 +#define GR_GL_BLUE_BITS 0x0D54 +#define GR_GL_ALPHA_BITS 0x0D55 +#define GR_GL_DEPTH_BITS 0x0D56 +#define GR_GL_STENCIL_BITS 0x0D57 +#define GR_GL_POLYGON_OFFSET_UNITS 0x2A00 +/* GL_POLYGON_OFFSET_FILL */ +#define GR_GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GR_GL_TEXTURE_BINDING_2D 0x8069 +#define GR_GL_SAMPLE_BUFFERS 0x80A8 +#define GR_GL_SAMPLES 0x80A9 +#define GR_GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GR_GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GR_GL_RENDERBUFFER_COVERAGE_SAMPLES 0x8CAB +#define GR_GL_RENDERBUFFER_COLOR_SAMPLES 0x8E10 +#define GR_GL_MAX_MULTISAMPLE_COVERAGE_MODES 0x8E11 +#define GR_GL_MULTISAMPLE_COVERAGE_MODES 0x8E12 +#define GR_GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B + +/* GetTextureParameter */ +/* GL_TEXTURE_MAG_FILTER */ +/* GL_TEXTURE_MIN_FILTER */ +/* GL_TEXTURE_WRAP_S */ +/* GL_TEXTURE_WRAP_T */ + +#define GR_GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GR_GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 + +/* Compressed Texture Formats */ +#define GR_GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GR_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GR_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GR_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +#define GR_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define GR_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#define GR_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define GR_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 + +#define GR_GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137 +#define GR_GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG 0x9138 + +#define GR_GL_COMPRESSED_ETC1_RGB8 0x8D64 + +#define GR_GL_COMPRESSED_R11_EAC 0x9270 +#define GR_GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GR_GL_COMPRESSED_RG11_EAC 0x9272 +#define GR_GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 + +#define GR_GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GR_GL_COMPRESSED_SRGB8 0x9275 +#define GR_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1 0x9276 +#define GR_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1 0x9277 +#define GR_GL_COMPRESSED_RGBA8_ETC2 0x9278 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ETC2 0x9279 + +#define GR_GL_COMPRESSED_LUMINANCE_LATC1 0x8C70 +#define GR_GL_COMPRESSED_SIGNED_LUMINANCE_LATC1 0x8C71 +#define GR_GL_COMPRESSED_LUMINANCE_ALPHA_LATC2 0x8C72 +#define GR_GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2 0x8C73 + +#define GR_GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GR_GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GR_GL_COMPRESSED_RED_GREEN_RGTC2 0x8DBD +#define GR_GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 0x8DBE + +#define GR_GL_COMPRESSED_3DC_X 0x87F9 +#define GR_GL_COMPRESSED_3DC_XY 0x87FA + +#define GR_GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#define GR_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D +#define GR_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E +#define GR_GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F + +#define GR_GL_COMPRESSED_RGBA_ASTC_4x4 0x93B0 +#define GR_GL_COMPRESSED_RGBA_ASTC_5x4 0x93B1 +#define GR_GL_COMPRESSED_RGBA_ASTC_5x5 0x93B2 +#define GR_GL_COMPRESSED_RGBA_ASTC_6x5 0x93B3 +#define GR_GL_COMPRESSED_RGBA_ASTC_6x6 0x93B4 +#define GR_GL_COMPRESSED_RGBA_ASTC_8x5 0x93B5 +#define GR_GL_COMPRESSED_RGBA_ASTC_8x6 0x93B6 +#define GR_GL_COMPRESSED_RGBA_ASTC_8x8 0x93B7 +#define GR_GL_COMPRESSED_RGBA_ASTC_10x5 0x93B8 +#define GR_GL_COMPRESSED_RGBA_ASTC_10x6 0x93B9 +#define GR_GL_COMPRESSED_RGBA_ASTC_10x8 0x93BA +#define GR_GL_COMPRESSED_RGBA_ASTC_10x10 0x93BB +#define GR_GL_COMPRESSED_RGBA_ASTC_12x10 0x93BC +#define GR_GL_COMPRESSED_RGBA_ASTC_12x12 0x93BD + +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4 0x93D0 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4 0x93D1 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5 0x93D2 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5 0x93D3 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6 0x93D4 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5 0x93D5 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6 0x93D6 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8 0x93D7 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5 0x93D8 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6 0x93D9 +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8 0x93DA +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10 0x93DB +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10 0x93DC +#define GR_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12 0x93DD + +/* HintMode */ +#define GR_GL_DONT_CARE 0x1100 +#define GR_GL_FASTEST 0x1101 +#define GR_GL_NICEST 0x1102 + +/* HintTarget */ +#define GR_GL_GENERATE_MIPMAP_HINT 0x8192 + +/* DataType */ +#define GR_GL_BYTE 0x1400 +#define GR_GL_UNSIGNED_BYTE 0x1401 +#define GR_GL_SHORT 0x1402 +#define GR_GL_UNSIGNED_SHORT 0x1403 +#define GR_GL_INT 0x1404 +#define GR_GL_UNSIGNED_INT 0x1405 +#define GR_GL_FLOAT 0x1406 +#define GR_GL_HALF_FLOAT 0x140B +#define GR_GL_FIXED 0x140C +#define GR_GL_HALF_FLOAT_OES 0x8D61 + +/* Lighting */ +#define GR_GL_LIGHTING 0x0B50 +#define GR_GL_LIGHT0 0x4000 +#define GR_GL_LIGHT1 0x4001 +#define GR_GL_LIGHT2 0x4002 +#define GR_GL_LIGHT3 0x4003 +#define GR_GL_LIGHT4 0x4004 +#define GR_GL_LIGHT5 0x4005 +#define GR_GL_LIGHT6 0x4006 +#define GR_GL_LIGHT7 0x4007 +#define GR_GL_SPOT_EXPONENT 0x1205 +#define GR_GL_SPOT_CUTOFF 0x1206 +#define GR_GL_CONSTANT_ATTENUATION 0x1207 +#define GR_GL_LINEAR_ATTENUATION 0x1208 +#define GR_GL_QUADRATIC_ATTENUATION 0x1209 +#define GR_GL_AMBIENT 0x1200 +#define GR_GL_DIFFUSE 0x1201 +#define GR_GL_SPECULAR 0x1202 +#define GR_GL_SHININESS 0x1601 +#define GR_GL_EMISSION 0x1600 +#define GR_GL_POSITION 0x1203 +#define GR_GL_SPOT_DIRECTION 0x1204 +#define GR_GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GR_GL_COLOR_INDEXES 0x1603 +#define GR_GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GR_GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GR_GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GR_GL_FRONT_AND_BACK 0x0408 +#define GR_GL_SHADE_MODEL 0x0B54 +#define GR_GL_FLAT 0x1D00 +#define GR_GL_SMOOTH 0x1D01 +#define GR_GL_COLOR_MATERIAL 0x0B57 +#define GR_GL_COLOR_MATERIAL_FACE 0x0B55 +#define GR_GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GR_GL_NORMALIZE 0x0BA1 + +/* Matrix Mode */ +#define GR_GL_MATRIX_MODE 0x0BA0 +#define GR_GL_MODELVIEW 0x1700 +#define GR_GL_PROJECTION 0x1701 +#define GR_GL_TEXTURE 0x1702 + +/* multisample */ +#define GR_GL_MULTISAMPLE 0x809D +#define GR_GL_SAMPLE_POSITION 0x8E50 + +/* Points */ +#define GR_GL_POINT_SMOOTH 0x0B10 +#define GR_GL_POINT_SIZE 0x0B11 +#define GR_GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GR_GL_POINT_SIZE_RANGE 0x0B12 + +/* Lines */ +#define GR_GL_LINE_SMOOTH 0x0B20 +#define GR_GL_LINE_STIPPLE 0x0B24 +#define GR_GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GR_GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GR_GL_LINE_WIDTH 0x0B21 +#define GR_GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GR_GL_LINE_WIDTH_RANGE 0x0B22 + +/* PolygonMode */ +#define GR_GL_POINT 0x1B00 +#define GR_GL_LINE 0x1B01 +#define GR_GL_FILL 0x1B02 + +/* Unsized formats */ +#define GR_GL_STENCIL_INDEX 0x1901 +#define GR_GL_DEPTH_COMPONENT 0x1902 +#define GR_GL_DEPTH_STENCIL 0x84F9 +#define GR_GL_RED 0x1903 +#define GR_GL_RED_INTEGER 0x8D94 +#define GR_GL_GREEN 0x1904 +#define GR_GL_BLUE 0x1905 +#define GR_GL_ALPHA 0x1906 +#define GR_GL_LUMINANCE 0x1909 +#define GR_GL_LUMINANCE_ALPHA 0x190A +#define GR_GL_RG_INTEGER 0x8228 +#define GR_GL_RGB 0x1907 +#define GR_GL_RGB_INTEGER 0x8D98 +#define GR_GL_SRGB 0x8C40 +#define GR_GL_RGBA 0x1908 +#define GR_GL_RG 0x8227 +#define GR_GL_SRGB_ALPHA 0x8C42 +#define GR_GL_RGBA_INTEGER 0x8D99 +#define GR_GL_BGRA 0x80E1 + +/* Stencil index sized formats */ +#define GR_GL_STENCIL_INDEX4 0x8D47 +#define GR_GL_STENCIL_INDEX8 0x8D48 +#define GR_GL_STENCIL_INDEX16 0x8D49 + +/* Depth component sized formats */ +#define GR_GL_DEPTH_COMPONENT16 0x81A5 + +/* Depth stencil sized formats */ +#define GR_GL_DEPTH24_STENCIL8 0x88F0 + +/* Red sized formats */ +#define GR_GL_R8 0x8229 +#define GR_GL_R16 0x822A +#define GR_GL_R16F 0x822D +#define GR_GL_R32F 0x822E + +/* Red integer sized formats */ +#define GR_GL_R8I 0x8231 +#define GR_GL_R8UI 0x8232 +#define GR_GL_R16I 0x8233 +#define GR_GL_R16UI 0x8234 +#define GR_GL_R32I 0x8235 +#define GR_GL_R32UI 0x8236 + +/* Luminance sized formats */ +#define GR_GL_LUMINANCE8 0x8040 +#define GR_GL_LUMINANCE16F 0x881E + +/* Alpha sized formats */ +#define GR_GL_ALPHA8 0x803C +#define GR_GL_ALPHA16 0x803E +#define GR_GL_ALPHA16F 0x881C +#define GR_GL_ALPHA32F 0x8816 + +/* Alpha integer sized formats */ +#define GR_GL_ALPHA8I 0x8D90 +#define GR_GL_ALPHA8UI 0x8D7E +#define GR_GL_ALPHA16I 0x8D8A +#define GR_GL_ALPHA16UI 0x8D78 +#define GR_GL_ALPHA32I 0x8D84 +#define GR_GL_ALPHA32UI 0x8D72 + +/* RG sized formats */ +#define GR_GL_RG8 0x822B +#define GR_GL_RG16 0x822C +#define GR_GL_R16F 0x822D +#define GR_GL_R32F 0x822E +#define GR_GL_RG16F 0x822F + +/* RG sized integer formats */ +#define GR_GL_RG8I 0x8237 +#define GR_GL_RG8UI 0x8238 +#define GR_GL_RG16I 0x8239 +#define GR_GL_RG16UI 0x823A +#define GR_GL_RG32I 0x823B +#define GR_GL_RG32UI 0x823C + +/* RGB sized formats */ +#define GR_GL_RGB5 0x8050 +#define GR_GL_RGB565 0x8D62 +#define GR_GL_RGB8 0x8051 +#define GR_GL_SRGB8 0x8C41 + +/* RGB integer sized formats */ +#define GR_GL_RGB8I 0x8D8F +#define GR_GL_RGB8UI 0x8D7D +#define GR_GL_RGB16I 0x8D89 +#define GR_GL_RGB16UI 0x8D77 +#define GR_GL_RGB32I 0x8D83 +#define GR_GL_RGB32UI 0x8D71 + +/* RGBA sized formats */ +#define GR_GL_RGBA4 0x8056 +#define GR_GL_RGB5_A1 0x8057 +#define GR_GL_RGBA8 0x8058 +#define GR_GL_RGB10_A2 0x8059 +#define GR_GL_SRGB8_ALPHA8 0x8C43 +#define GR_GL_RGBA16F 0x881A +#define GR_GL_RGBA32F 0x8814 +#define GR_GL_RG32F 0x8230 +#define GR_GL_RGBA16 0x805B + +/* RGBA integer sized formats */ +#define GR_GL_RGBA8I 0x8D8E +#define GR_GL_RGBA8UI 0x8D7C +#define GR_GL_RGBA16I 0x8D88 +#define GR_GL_RGBA16UI 0x8D76 +#define GR_GL_RGBA32I 0x8D82 +#define GR_GL_RGBA32UI 0x8D70 + +/* BGRA sized formats */ +#define GR_GL_BGRA8 0x93A1 + +/* PixelType */ +/* GL_UNSIGNED_BYTE */ +#define GR_GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GR_GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GR_GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GR_GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 + +/* Shaders */ +#define GR_GL_FRAGMENT_SHADER 0x8B30 +#define GR_GL_VERTEX_SHADER 0x8B31 +#define GR_GL_GEOMETRY_SHADER 0x8DD9 +#define GR_GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GR_GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GR_GL_MAX_VARYING_VECTORS 0x8DFC +#define GR_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GR_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GR_GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GR_GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#define GR_GL_SHADER_TYPE 0x8B4F +#define GR_GL_DELETE_STATUS 0x8B80 +#define GR_GL_LINK_STATUS 0x8B82 +#define GR_GL_VALIDATE_STATUS 0x8B83 +#define GR_GL_ATTACHED_SHADERS 0x8B85 +#define GR_GL_ACTIVE_UNIFORMS 0x8B86 +#define GR_GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GR_GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GR_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GR_GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GR_GL_CURRENT_PROGRAM 0x8B8D +#define GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GR_GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GR_GL_MAX_SHADER_PIXEL_LOCAL_STORAGE_FAST_SIZE 0x8F63 +#define GR_GL_SHADER_BINARY_FORMATS 0x8DF8 + +/* StencilFunction */ +#define GR_GL_NEVER 0x0200 +#define GR_GL_LESS 0x0201 +#define GR_GL_EQUAL 0x0202 +#define GR_GL_LEQUAL 0x0203 +#define GR_GL_GREATER 0x0204 +#define GR_GL_NOTEQUAL 0x0205 +#define GR_GL_GEQUAL 0x0206 +#define GR_GL_ALWAYS 0x0207 + +/* StencilOp */ +/* GL_ZERO */ +#define GR_GL_KEEP 0x1E00 +#define GR_GL_REPLACE 0x1E01 +#define GR_GL_INCR 0x1E02 +#define GR_GL_DECR 0x1E03 +#define GR_GL_INVERT 0x150A +#define GR_GL_INCR_WRAP 0x8507 +#define GR_GL_DECR_WRAP 0x8508 + +/* StringName */ +#define GR_GL_VENDOR 0x1F00 +#define GR_GL_RENDERER 0x1F01 +#define GR_GL_VERSION 0x1F02 +#define GR_GL_EXTENSIONS 0x1F03 + +/* StringCounts */ +#define GR_GL_NUM_EXTENSIONS 0x821D + +/* Pixel Mode / Transfer */ +#define GR_GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GR_GL_PACK_ROW_LENGTH 0x0D02 + + +/* TextureMagFilter */ +#define GR_GL_NEAREST 0x2600 +#define GR_GL_LINEAR 0x2601 + +/* TextureMinFilter */ +/* GL_NEAREST */ +/* GL_LINEAR */ +#define GR_GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GR_GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GR_GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GR_GL_LINEAR_MIPMAP_LINEAR 0x2703 + +/* TextureUsage */ +#define GR_GL_FRAMEBUFFER_ATTACHMENT 0x93A3 + +/* TextureParameterName */ +#define GR_GL_TEXTURE_MAG_FILTER 0x2800 +#define GR_GL_TEXTURE_MIN_FILTER 0x2801 +#define GR_GL_TEXTURE_WRAP_S 0x2802 +#define GR_GL_TEXTURE_WRAP_T 0x2803 +#define GR_GL_TEXTURE_USAGE 0x93A2 + +/* TextureTarget */ +/* GL_TEXTURE_2D */ +#define GR_GL_TEXTURE 0x1702 +#define GR_GL_TEXTURE_CUBE_MAP 0x8513 +#define GR_GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GR_GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GR_GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GR_GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GR_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GR_GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GR_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GR_GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C + +/* TextureUnit */ +#define GR_GL_TEXTURE0 0x84C0 +#define GR_GL_TEXTURE1 0x84C1 +#define GR_GL_TEXTURE2 0x84C2 +#define GR_GL_TEXTURE3 0x84C3 +#define GR_GL_TEXTURE4 0x84C4 +#define GR_GL_TEXTURE5 0x84C5 +#define GR_GL_TEXTURE6 0x84C6 +#define GR_GL_TEXTURE7 0x84C7 +#define GR_GL_TEXTURE8 0x84C8 +#define GR_GL_TEXTURE9 0x84C9 +#define GR_GL_TEXTURE10 0x84CA +#define GR_GL_TEXTURE11 0x84CB +#define GR_GL_TEXTURE12 0x84CC +#define GR_GL_TEXTURE13 0x84CD +#define GR_GL_TEXTURE14 0x84CE +#define GR_GL_TEXTURE15 0x84CF +#define GR_GL_TEXTURE16 0x84D0 +#define GR_GL_TEXTURE17 0x84D1 +#define GR_GL_TEXTURE18 0x84D2 +#define GR_GL_TEXTURE19 0x84D3 +#define GR_GL_TEXTURE20 0x84D4 +#define GR_GL_TEXTURE21 0x84D5 +#define GR_GL_TEXTURE22 0x84D6 +#define GR_GL_TEXTURE23 0x84D7 +#define GR_GL_TEXTURE24 0x84D8 +#define GR_GL_TEXTURE25 0x84D9 +#define GR_GL_TEXTURE26 0x84DA +#define GR_GL_TEXTURE27 0x84DB +#define GR_GL_TEXTURE28 0x84DC +#define GR_GL_TEXTURE29 0x84DD +#define GR_GL_TEXTURE30 0x84DE +#define GR_GL_TEXTURE31 0x84DF +#define GR_GL_ACTIVE_TEXTURE 0x84E0 +#define GR_GL_MAX_TEXTURE_UNITS 0x84E2 +#define GR_GL_MAX_TEXTURE_COORDS 0x8871 + +/* TextureWrapMode */ +#define GR_GL_REPEAT 0x2901 +#define GR_GL_CLAMP_TO_EDGE 0x812F +#define GR_GL_MIRRORED_REPEAT 0x8370 +#define GR_GL_CLAMP_TO_BORDER 0x812D + +/* Texture Swizzle */ +#define GR_GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GR_GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GR_GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GR_GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GR_GL_TEXTURE_SWIZZLE_RGBA 0x8E46 + +/* Texture mapping */ +#define GR_GL_TEXTURE_ENV 0x2300 +#define GR_GL_TEXTURE_ENV_MODE 0x2200 +#define GR_GL_TEXTURE_1D 0x0DE0 +/* GL_TEXTURE_2D */ +/* GL_TEXTURE_WRAP_S */ +/* GL_TEXTURE_WRAP_T */ +/* GL_TEXTURE_MAG_FILTER */ +/* GL_TEXTURE_MIN_FILTER */ +#define GR_GL_TEXTURE_ENV_COLOR 0x2201 +#define GR_GL_TEXTURE_GEN_S 0x0C60 +#define GR_GL_TEXTURE_GEN_T 0x0C61 +#define GR_GL_TEXTURE_GEN_R 0x0C62 +#define GR_GL_TEXTURE_GEN_Q 0x0C63 +#define GR_GL_TEXTURE_GEN_MODE 0x2500 +#define GR_GL_TEXTURE_BORDER_COLOR 0x1004 +#define GR_GL_TEXTURE_WIDTH 0x1000 +#define GR_GL_TEXTURE_HEIGHT 0x1001 +#define GR_GL_TEXTURE_BORDER 0x1005 +#define GR_GL_TEXTURE_COMPONENTS 0x1003 +#define GR_GL_TEXTURE_RED_SIZE 0x805C +#define GR_GL_TEXTURE_GREEN_SIZE 0x805D +#define GR_GL_TEXTURE_BLUE_SIZE 0x805E +#define GR_GL_TEXTURE_ALPHA_SIZE 0x805F +#define GR_GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GR_GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GR_GL_TEXTURE_INTERNAL_FORMAT 0x1003 +/* GL_NEAREST_MIPMAP_NEAREST */ +/* GL_NEAREST_MIPMAP_LINEAR */ +/* GL_LINEAR_MIPMAP_NEAREST */ +/* GL_LINEAR_MIPMAP_LINEAR */ +#define GR_GL_OBJECT_LINEAR 0x2401 +#define GR_GL_OBJECT_PLANE 0x2501 +#define GR_GL_EYE_LINEAR 0x2400 +#define GR_GL_EYE_PLANE 0x2502 +#define GR_GL_SPHERE_MAP 0x2402 +#define GR_GL_DECAL 0x2101 +#define GR_GL_MODULATE 0x2100 +/* GL_NEAREST */ +/* GL_REPEAT */ +#define GR_GL_CLAMP 0x2900 +#define GR_GL_S 0x2000 +#define GR_GL_T 0x2001 +#define GR_GL_R 0x2002 +#define GR_GL_Q 0x2003 +#define GR_GL_TEXTURE_GEN_R 0x0C62 +#define GR_GL_TEXTURE_GEN_Q 0x0C63 + +/* texture_env_combine */ +#define GR_GL_COMBINE 0x8570 +#define GR_GL_COMBINE_RGB 0x8571 +#define GR_GL_COMBINE_ALPHA 0x8572 +#define GR_GL_SOURCE0_RGB 0x8580 +#define GR_GL_SOURCE1_RGB 0x8581 +#define GR_GL_SOURCE2_RGB 0x8582 +#define GR_GL_SOURCE0_ALPHA 0x8588 +#define GR_GL_SOURCE1_ALPHA 0x8589 +#define GR_GL_SOURCE2_ALPHA 0x858A +#define GR_GL_OPERAND0_RGB 0x8590 +#define GR_GL_OPERAND1_RGB 0x8591 +#define GR_GL_OPERAND2_RGB 0x8592 +#define GR_GL_OPERAND0_ALPHA 0x8598 +#define GR_GL_OPERAND1_ALPHA 0x8599 +#define GR_GL_OPERAND2_ALPHA 0x859A +#define GR_GL_RGB_SCALE 0x8573 +#define GR_GL_ADD_SIGNED 0x8574 +#define GR_GL_INTERPOLATE 0x8575 +#define GR_GL_SUBTRACT 0x84E7 +#define GR_GL_CONSTANT 0x8576 +#define GR_GL_PRIMARY_COLOR 0x8577 +#define GR_GL_PREVIOUS 0x8578 +#define GR_GL_SRC0_RGB 0x8580 +#define GR_GL_SRC1_RGB 0x8581 +#define GR_GL_SRC2_RGB 0x8582 +#define GR_GL_SRC0_ALPHA 0x8588 +#define GR_GL_SRC1_ALPHA 0x8589 +#define GR_GL_SRC2_ALPHA 0x858A + +/* Uniform Types */ +#define GR_GL_FLOAT_VEC2 0x8B50 +#define GR_GL_FLOAT_VEC3 0x8B51 +#define GR_GL_FLOAT_VEC4 0x8B52 +#define GR_GL_INT_VEC2 0x8B53 +#define GR_GL_INT_VEC3 0x8B54 +#define GR_GL_INT_VEC4 0x8B55 +#define GR_GL_BOOL 0x8B56 +#define GR_GL_BOOL_VEC2 0x8B57 +#define GR_GL_BOOL_VEC3 0x8B58 +#define GR_GL_BOOL_VEC4 0x8B59 +#define GR_GL_FLOAT_MAT2 0x8B5A +#define GR_GL_FLOAT_MAT3 0x8B5B +#define GR_GL_FLOAT_MAT4 0x8B5C +#define GR_GL_SAMPLER_2D 0x8B5E +#define GR_GL_SAMPLER_CUBE 0x8B60 + +/* Vertex Arrays */ +#define GR_GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GR_GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GR_GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GR_GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GR_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GR_GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GR_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GR_GL_VERTEX_ARRAY 0x8074 +#define GR_GL_NORMAL_ARRAY 0x8075 +#define GR_GL_COLOR_ARRAY 0x8076 +#define GR_GL_SECONDARY_COLOR_ARRAY 0x845E +#define GR_GL_INDEX_ARRAY 0x8077 +#define GR_GL_TEXTURE_COORD_ARRAY 0x8078 +#define GR_GL_EDGE_FLAG_ARRAY 0x8079 +#define GR_GL_VERTEX_ARRAY_SIZE 0x807A +#define GR_GL_VERTEX_ARRAY_TYPE 0x807B +#define GR_GL_VERTEX_ARRAY_STRIDE 0x807C +#define GR_GL_NORMAL_ARRAY_TYPE 0x807E +#define GR_GL_NORMAL_ARRAY_STRIDE 0x807F +#define GR_GL_COLOR_ARRAY_SIZE 0x8081 +#define GR_GL_COLOR_ARRAY_TYPE 0x8082 +#define GR_GL_COLOR_ARRAY_STRIDE 0x8083 +#define GR_GL_INDEX_ARRAY_TYPE 0x8085 +#define GR_GL_INDEX_ARRAY_STRIDE 0x8086 +#define GR_GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GR_GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GR_GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GR_GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GR_GL_VERTEX_ARRAY_POINTER 0x808E +#define GR_GL_NORMAL_ARRAY_POINTER 0x808F +#define GR_GL_COLOR_ARRAY_POINTER 0x8090 +#define GR_GL_INDEX_ARRAY_POINTER 0x8091 +#define GR_GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GR_GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GR_GL_V2F 0x2A20 +#define GR_GL_V3F 0x2A21 +#define GR_GL_C4UB_V2F 0x2A22 +#define GR_GL_C4UB_V3F 0x2A23 +#define GR_GL_C3F_V3F 0x2A24 +#define GR_GL_N3F_V3F 0x2A25 +#define GR_GL_C4F_N3F_V3F 0x2A26 +#define GR_GL_T2F_V3F 0x2A27 +#define GR_GL_T4F_V4F 0x2A28 +#define GR_GL_T2F_C4UB_V3F 0x2A29 +#define GR_GL_T2F_C3F_V3F 0x2A2A +#define GR_GL_T2F_N3F_V3F 0x2A2B +#define GR_GL_T2F_C4F_N3F_V3F 0x2A2C +#define GR_GL_T4F_C4F_N3F_V4F 0x2A2D +#define GR_GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 + +/* Buffer Object */ +#define GR_GL_READ_ONLY 0x88B8 +#define GR_GL_WRITE_ONLY 0x88B9 +#define GR_GL_READ_WRITE 0x88BA +#define GR_GL_BUFFER_MAPPED 0x88BC + +#define GR_GL_MAP_READ_BIT 0x0001 +#define GR_GL_MAP_WRITE_BIT 0x0002 +#define GR_GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GR_GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GR_GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GR_GL_MAP_UNSYNCHRONIZED_BIT 0x0020 + +/* Read Format */ +#define GR_GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B + +/* Shader Source */ +#define GR_GL_COMPILE_STATUS 0x8B81 +#define GR_GL_INFO_LOG_LENGTH 0x8B84 +#define GR_GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GR_GL_SHADER_COMPILER 0x8DFA + +/* Shader Binary */ +#define GR_GL_SHADER_BINARY_FORMATS 0x8DF8 +#define GR_GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 + +/* Program Binary */ +#define GR_GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE + +/* Shader Precision-Specified Types */ +#define GR_GL_LOW_FLOAT 0x8DF0 +#define GR_GL_MEDIUM_FLOAT 0x8DF1 +#define GR_GL_HIGH_FLOAT 0x8DF2 +#define GR_GL_LOW_INT 0x8DF3 +#define GR_GL_MEDIUM_INT 0x8DF4 +#define GR_GL_HIGH_INT 0x8DF5 + +/* Queries */ +#define GR_GL_QUERY_COUNTER_BITS 0x8864 +#define GR_GL_CURRENT_QUERY 0x8865 +#define GR_GL_QUERY_RESULT 0x8866 +#define GR_GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GR_GL_SAMPLES_PASSED 0x8914 +#define GR_GL_ANY_SAMPLES_PASSED 0x8C2F +#define GR_GL_TIME_ELAPSED 0x88BF +#define GR_GL_TIMESTAMP 0x8E28 +#define GR_GL_PRIMITIVES_GENERATED 0x8C87 +#define GR_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 + + +/* Framebuffer Object. */ +#define GR_GL_FRAMEBUFFER 0x8D40 +#define GR_GL_READ_FRAMEBUFFER 0x8CA8 +#define GR_GL_DRAW_FRAMEBUFFER 0x8CA9 + +#define GR_GL_RENDERBUFFER 0x8D41 + +#define GR_GL_MAX_SAMPLES 0x8D57 +// GL_IMG_multisampled_render_to_texture uses a different value for GL_MAX_SAMPLES +#define GR_GL_MAX_SAMPLES_IMG 0x9135 + +#define GR_GL_RENDERBUFFER_WIDTH 0x8D42 +#define GR_GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GR_GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GR_GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GR_GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GR_GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GR_GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GR_GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GR_GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 + +#define GR_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GR_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 + +#define GR_GL_COLOR_ATTACHMENT0 0x8CE0 +#define GR_GL_DEPTH_ATTACHMENT 0x8D00 +#define GR_GL_STENCIL_ATTACHMENT 0x8D20 + +// GL_EXT_discard_framebuffer +#define GR_GL_COLOR 0x1800 +#define GR_GL_DEPTH 0x1801 +#define GR_GL_STENCIL 0x1802 + +#define GR_GL_NONE 0 +#define GR_GL_FRAMEBUFFER_DEFAULT 0x8218 + +#define GR_GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GR_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GR_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GR_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9 +#define GR_GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD + +#define GR_GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GR_GL_RENDERBUFFER_BINDING 0x8CA7 +#define GR_GL_MAX_RENDERBUFFER_SIZE 0x84E8 + +#define GR_GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 + +/* Path Rendering */ +// commands +#define GR_GL_CLOSE_PATH 0x00 +#define GR_GL_MOVE_TO 0x02 +#define GR_GL_LINE_TO 0x04 +#define GR_GL_QUADRATIC_CURVE_TO 0x0A +#define GR_GL_CUBIC_CURVE_TO 0x0C +#define GR_GL_CONIC_CURVE_TO 0x1A + +// path parameters +#define GR_GL_PATH_STROKE_WIDTH 0x9075 +#define GR_GL_PATH_END_CAPS 0x9076 +#define GR_GL_PATH_JOIN_STYLE 0x9079 +#define GR_GL_PATH_MITER_LIMIT 0x907A +#define GR_GL_PATH_STROKE_BOUND 0x9086 + +// fill modes +#define GR_GL_COUNT_UP 0x9088 + +// cover mode +#define GR_GL_BOUNDING_BOX 0x908D +#define GR_GL_BOUNDING_BOX_OF_BOUNDING_BOXES 0x909C + +// transform type +#define GR_GL_TRANSLATE_X 0x908E +#define GR_GL_TRANSLATE_Y 0x908F +#define GR_GL_TRANSLATE_2D 0x9090 +#define GR_GL_TRANSPOSE_AFFINE_2D 0x9096 + +// cap/dash values +#define GR_GL_SQUARE 0x90A3 +#define GR_GL_ROUND 0x90A4 + +// join values +#define GR_GL_BEVEL 0x90A6 +#define GR_GL_MITER_REVERT 0x90A7 + +// glyph loading values +#define GR_GL_STANDARD_FONT_FORMAT 0x936C +#define GR_GL_FONT_GLYPHS_AVAILABLE 0x9368 + +// NV_path_rendering extension to ARB_program_interface_query: +// .. corresponds to the set of active input variables used by the fragment +// shader stage of (if a fragment stage exists). +#define GR_GL_FRAGMENT_INPUT 0x936D + +// NV_path_rendering extension to EXT_direct_state_access: +// [the matrix functions] must support the PATH_PROJECTION_NV and +// PATH_MODELVIEW_NV tokens for matrixMode. +#define GR_GL_PATH_PROJECTION 0x1701 +#define GR_GL_PATH_MODELVIEW 0x1700 + +/* ARM specific define for MSAA support on framebuffer fetch */ +#define GR_GL_FETCH_PER_SAMPLE 0x8F65 + +/* GL_KHR_debug */ +#define GR_GL_DEBUG_OUTPUT 0x92E0 +#define GR_GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GR_GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 +#define GR_GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 +#define GR_GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 +#define GR_GL_DEBUG_LOGGED_MESSAGES 0x9145 +#define GR_GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 +#define GR_GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C +#define GR_GL_DEBUG_GROUP_STACK_DEPTH 0x826D +#define GR_GL_MAX_LABEL_LENGTH 0x82E8 +#define GR_GL_DEBUG_SOURCE_API 0x8246 +#define GR_GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 +#define GR_GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 +#define GR_GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 +#define GR_GL_DEBUG_SOURCE_APPLICATION 0x824A +#define GR_GL_DEBUG_SOURCE_OTHER 0x824B +#define GR_GL_DEBUG_TYPE_ERROR 0x824C +#define GR_GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D +#define GR_GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +#define GR_GL_DEBUG_TYPE_PORTABILITY 0x824F +#define GR_GL_DEBUG_TYPE_PERFORMANCE 0x8250 +#define GR_GL_DEBUG_TYPE_OTHER 0x8251 +#define GR_GL_DEBUG_TYPE_MARKER 0x8268 +#define GR_GL_DEBUG_TYPE_PUSH_GROUP 0x8269 +#define GR_GL_DEBUG_TYPE_POP_GROUP 0x826A +#define GR_GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GR_GL_DEBUG_SEVERITY_MEDIUM 0x9147 +#define GR_GL_DEBUG_SEVERITY_LOW 0x9148 +#define GR_GL_DEBUG_SEVERITY_NOTIFICATION 0x826B +#define GR_GL_STACK_UNDERFLOW 0x0504 +#define GR_GL_STACK_OVERFLOW 0x0503 +#define GR_GL_BUFFER 0x82E0 +#define GR_GL_SHADER 0x82E1 +#define GR_GL_PROGRAM 0x82E2 +#define GR_GL_QUERY 0x82E3 +#define GR_GL_PROGRAM_PIPELINE 0x82E4 +#define GR_GL_SAMPLER 0x82E6 + +/* GL_OES_EGL_image_external */ +#define GR_GL_TEXTURE_EXTERNAL 0x8D65 +#define GR_GL_TEXTURE_BINDING_EXTERNAL 0x8D67 + +/* GL_ARB_texture_rectangle or GL_ANGLE_texture_rectangle */ +#define GR_GL_TEXTURE_RECTANGLE 0x84F5 +#define GR_GL_TEXTURE_BINDING_RECTANGLE 0x84F6 + +/* GL_EXT_window_rectangles */ +#define GR_GL_MAX_WINDOW_RECTANGLES 0x8f14 +#define GR_GL_INCLUSIVE 0x8f10 +#define GR_GL_EXCLUSIVE 0x8f11 + +/** GL_QCOM_tiled_rendering */ +#define GR_GL_COLOR_BUFFER_BIT0 0x00000001 +#define GR_GL_COLOR_BUFFER_BIT1 0x00000002 +#define GR_GL_COLOR_BUFFER_BIT2 0x00000004 +#define GR_GL_COLOR_BUFFER_BIT3 0x00000008 +#define GR_GL_COLOR_BUFFER_BIT4 0x00000010 +#define GR_GL_COLOR_BUFFER_BIT5 0x00000020 +#define GR_GL_COLOR_BUFFER_BIT6 0x00000040 +#define GR_GL_COLOR_BUFFER_BIT7 0x00000080 +#define GR_GL_DEPTH_BUFFER_BIT0 0x00000100 +#define GR_GL_DEPTH_BUFFER_BIT1 0x00000200 +#define GR_GL_DEPTH_BUFFER_BIT2 0x00000400 +#define GR_GL_DEPTH_BUFFER_BIT3 0x00000800 +#define GR_GL_DEPTH_BUFFER_BIT4 0x00001000 +#define GR_GL_DEPTH_BUFFER_BIT5 0x00002000 +#define GR_GL_DEPTH_BUFFER_BIT6 0x00004000 +#define GR_GL_DEPTH_BUFFER_BIT7 0x00008000 +#define GR_GL_STENCIL_BUFFER_BIT0 0x00010000 +#define GR_GL_STENCIL_BUFFER_BIT1 0x00020000 +#define GR_GL_STENCIL_BUFFER_BIT2 0x00040000 +#define GR_GL_STENCIL_BUFFER_BIT3 0x00080000 +#define GR_GL_STENCIL_BUFFER_BIT4 0x00100000 +#define GR_GL_STENCIL_BUFFER_BIT5 0x00200000 +#define GR_GL_STENCIL_BUFFER_BIT6 0x00400000 +#define GR_GL_STENCIL_BUFFER_BIT7 0x00800000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT0 0x01000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT1 0x02000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT2 0x04000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT3 0x08000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT4 0x10000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT5 0x20000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT6 0x40000000 +#define GR_GL_MULTISAMPLE_BUFFER_BIT7 0x80000000 + +/* GL_ARB_sync */ +#define GR_GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GR_GL_ALREADY_SIGNALED 0x911A +#define GR_GL_TIMEOUT_EXPIRED 0x911B +#define GR_GL_CONDITION_SATISFIED 0x911C +#define GR_GL_WAIT_FAILED 0x911D +#define GR_GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GR_GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull + +/* GL_EXT_geometry_shader */ +#define GR_GL_LINES_ADJACENCY 0x000A + +/* GL_ARB_internalformat_query */ +#define GR_GL_NUM_SAMPLE_COUNTS 0x9380 + +/* EGL Defines */ +#define GR_EGL_NO_DISPLAY ((GrEGLDisplay)0) +#define GR_EGL_EXTENSIONS 0x3055 +#define GR_EGL_GL_TEXTURE_2D 0x30B1 +#define GR_EGL_GL_TEXTURE_LEVEL 0x30BC +#define GR_EGL_IMAGE_PRESERVED 0x30D2 +#define GR_EGL_FALSE 0x0 +#define GR_EGL_TRUE 0x1 +#define GR_EGL_NONE 0x3038 +#define GR_EGL_NO_IMAGE ((GrEGLImage)0) + +/* Programs */ +#define GR_GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLExtensions.cpp b/gfx/skia/skia/src/gpu/gl/GrGLExtensions.cpp new file mode 100644 index 0000000000..e5a989f95b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLExtensions.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/gl/GrGLExtensions.h" +#include "src/gpu/gl/GrGLDefines.h" +#include "src/gpu/gl/GrGLUtil.h" + +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTSearch.h" +#include "src/core/SkTSort.h" +#include "src/utils/SkJSONWriter.h" + +namespace { // This cannot be static because it is used as a template parameter. +inline bool extension_compare(const SkString& a, const SkString& b) { + return strcmp(a.c_str(), b.c_str()) < 0; +} +} + +// finds the index of ext in strings or a negative result if ext is not found. +static int find_string(const SkTArray& strings, const char ext[]) { + if (strings.empty()) { + return -1; + } + SkString extensionStr(ext); + int idx = SkTSearch(&strings.front(), + strings.count(), + extensionStr, + sizeof(SkString)); + return idx; +} + +GrGLExtensions::GrGLExtensions(const GrGLExtensions& that) { + *this = that; +} + +GrGLExtensions& GrGLExtensions::operator=(const GrGLExtensions& that) { + if (this != &that) { + fStrings = that.fStrings; + fInitialized = that.fInitialized; + } + return *this; +} + +static void eat_space_sep_strings(SkTArray* out, const char in[]) { + if (!in) { + return; + } + while (true) { + // skip over multiple spaces between extensions + while (' ' == *in) { + ++in; + } + // quit once we reach the end of the string. + if ('\0' == *in) { + break; + } + // we found an extension + size_t length = strcspn(in, " "); + out->push_back().set(in, length); + in += length; + } +} + +bool GrGLExtensions::init(GrGLStandard standard, + GrGLFunction getString, + GrGLFunction getStringi, + GrGLFunction getIntegerv, + GrGLFunction queryString, + GrEGLDisplay eglDisplay) { + fInitialized = false; + fStrings.reset(); + + if (!getString) { + return false; + } + + const GrGLubyte* verString = getString(GR_GL_VERSION); + GrGLVersion version = GrGLGetVersionFromString((const char*) verString); + if (GR_GL_INVALID_VER == version) { + return false; + } + + bool indexed = false; + if (GR_IS_GR_GL(standard) || GR_IS_GR_GL_ES(standard)) { + // glGetStringi and indexed extensions were added in version 3.0 of desktop GL and ES. + indexed = version >= GR_GL_VER(3, 0); + } else if (GR_IS_GR_WEBGL(standard)) { + // WebGL (1.0 or 2.0) doesn't natively support glGetStringi, but enscripten adds it in + // https://github.com/emscripten-core/emscripten/issues/3472 + indexed = version >= GR_GL_VER(2, 0); + } + + if (indexed) { + if (!getStringi || !getIntegerv) { + return false; + } + GrGLint extensionCnt = 0; + getIntegerv(GR_GL_NUM_EXTENSIONS, &extensionCnt); + fStrings.push_back_n(extensionCnt); + for (int i = 0; i < extensionCnt; ++i) { + const char* ext = (const char*) getStringi(GR_GL_EXTENSIONS, i); + fStrings[i] = ext; + } + } else { + const char* extensions = (const char*) getString(GR_GL_EXTENSIONS); + if (!extensions) { + return false; + } + eat_space_sep_strings(&fStrings, extensions); + } + if (queryString) { + const char* extensions = queryString(eglDisplay, GR_EGL_EXTENSIONS); + + eat_space_sep_strings(&fStrings, extensions); + } + if (!fStrings.empty()) { + SkTLessFunctionToFunctorAdaptor cmp; + SkTQSort(&fStrings.front(), &fStrings.back(), cmp); + } + fInitialized = true; + return true; +} + +bool GrGLExtensions::has(const char ext[]) const { + SkASSERT(fInitialized); + return find_string(fStrings, ext) >= 0; +} + +bool GrGLExtensions::remove(const char ext[]) { + SkASSERT(fInitialized); + int idx = find_string(fStrings, ext); + if (idx < 0) { + return false; + } + + // This is not terribly effecient but we really only expect this function to be called at + // most a handful of times when our test programs start. + fStrings.removeShuffle(idx); + if (idx != fStrings.count()) { + SkTLessFunctionToFunctorAdaptor cmp; + SkTInsertionSort(&(fStrings.operator[](idx)), &fStrings.back(), cmp); + } + return true; +} + +void GrGLExtensions::add(const char ext[]) { + int idx = find_string(fStrings, ext); + if (idx < 0) { + // This is not the most effecient approach since we end up looking at all of the + // extensions after the add + fStrings.emplace_back(ext); + SkTLessFunctionToFunctorAdaptor cmp; + SkTInsertionSort(&fStrings.front(), &fStrings.back(), cmp); + } +} + +#ifdef SK_ENABLE_DUMP_GPU +void GrGLExtensions::dumpJSON(SkJSONWriter* writer) const { + writer->beginArray(); + for (int i = 0; i < fStrings.count(); ++i) { + writer->appendString(fStrings[i].c_str()); + } + writer->endArray(); +} +#else +void GrGLExtensions::dumpJSON(SkJSONWriter* writer) const { } +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLGLSL.cpp b/gfx/skia/skia/src/gpu/gl/GrGLGLSL.cpp new file mode 100644 index 0000000000..2ad38bc35e --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLGLSL.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLGLSL.h" +#include "src/gpu/gl/GrGLUtil.h" + +bool GrGLGetGLSLGeneration(const GrGLInterface* gl, GrGLSLGeneration* generation) { + SkASSERT(generation); + GrGLSLVersion ver = GrGLGetGLSLVersion(gl); + if (GR_GLSL_INVALID_VER == ver) { + return false; + } + + // Workaround for a bug on some Adreno 308 devices with Android 9. The driver reports a GL + // version of 3.0, and a GLSL version of 3.1. If we use version 310 shaders, the driver reports + // that it's not supported. To keep things simple, we pin the GLSL version to the GL version. + // Note that GLSL versions have an extra digit on their minor level, so we have to scale up + // the GL version's minor revision to get a comparable GLSL version. This logic can easily + // create invalid GLSL versions (older GL didn't keep the versions in sync), but the checks + // below will further pin the GLSL generation correctly. + // https://github.com/flutter/flutter/issues/36130 + GrGLVersion glVer = GrGLGetVersion(gl); + uint32_t glMajor = GR_GL_MAJOR_VER(glVer), + glMinor = GR_GL_MINOR_VER(glVer); + ver = SkTMin(ver, GR_GLSL_VER(glMajor, 10 * glMinor)); + + if (GR_IS_GR_GL(gl->fStandard)) { + SkASSERT(ver >= GR_GLSL_VER(1,10)); + if (ver >= GR_GLSL_VER(4,20)) { + *generation = k420_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(4,00)) { + *generation = k400_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(3,30)) { + *generation = k330_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(1,50)) { + *generation = k150_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(1,40)) { + *generation = k140_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(1,30)) { + *generation = k130_GrGLSLGeneration; + } else { + *generation = k110_GrGLSLGeneration; + } + return true; + } else if (GR_IS_GR_GL_ES(gl->fStandard)) { + SkASSERT(ver >= GR_GL_VER(1,00)); + if (ver >= GR_GLSL_VER(3,20)) { + *generation = k320es_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(3,10)) { + *generation = k310es_GrGLSLGeneration; + } else if (ver >= GR_GLSL_VER(3,00)) { + *generation = k330_GrGLSLGeneration; + } else { + *generation = k110_GrGLSLGeneration; + } + return true; + } else if (GR_IS_GR_WEBGL(gl->fStandard)) { + SkASSERT(ver >= GR_GL_VER(1,0)); + if (ver >= GR_GLSL_VER(2,0)) { + *generation = k330_GrGLSLGeneration; // ES 3.0 + } else { + *generation = k110_GrGLSLGeneration; + } + return true; + } + SK_ABORT("Unknown GL Standard"); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLGLSL.h b/gfx/skia/skia/src/gpu/gl/GrGLGLSL.h new file mode 100644 index 0000000000..de86cd1f31 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLGLSL.h @@ -0,0 +1,20 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLGLSL_DEFINED +#define GrGLGLSL_DEFINED + +#include "src/gpu/glsl/GrGLSL.h" + +struct GrGLInterface; + +/** + * Gets the most recent GLSL Generation compatible with the OpenGL context. + */ +bool GrGLGetGLSLGeneration(const GrGLInterface* gl, GrGLSLGeneration* generation); + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLGpu.cpp b/gfx/skia/skia/src/gpu/gl/GrGLGpu.cpp new file mode 100644 index 0000000000..937e168db5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLGpu.cpp @@ -0,0 +1,4035 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPixmap.h" +#include "include/core/SkStrokeRec.h" +#include "include/core/SkTypes.h" +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrTypes.h" +#include "include/private/SkHalf.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrCpuBuffer.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/gl/GrGLBuffer.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLOpsRenderPass.h" +#include "src/gpu/gl/GrGLSemaphore.h" +#include "src/gpu/gl/GrGLStencilAttachment.h" +#include "src/gpu/gl/GrGLTextureRenderTarget.h" +#include "src/gpu/gl/builders/GrGLShaderStringBuilder.h" +#include "src/sksl/SkSLCompiler.h" + +#include + +#define GL_CALL(X) GR_GL_CALL(this->glInterface(), X) +#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->glInterface(), RET, X) + +#if GR_GL_CHECK_ALLOC_WITH_GET_ERROR + #define CLEAR_ERROR_BEFORE_ALLOC(iface) GrGLClearErr(iface) + #define GL_ALLOC_CALL(iface, call) GR_GL_CALL_NOERRCHECK(iface, call) + #define CHECK_ALLOC_ERROR(iface) GR_GL_GET_ERROR(iface) +#else + #define CLEAR_ERROR_BEFORE_ALLOC(iface) + #define GL_ALLOC_CALL(iface, call) GR_GL_CALL(iface, call) + #define CHECK_ALLOC_ERROR(iface) GR_GL_NO_ERROR +#endif + +//#define USE_NSIGHT + +/////////////////////////////////////////////////////////////////////////////// + +static const GrGLenum gXfermodeEquation2Blend[] = { + // Basic OpenGL blend equations. + GR_GL_FUNC_ADD, + GR_GL_FUNC_SUBTRACT, + GR_GL_FUNC_REVERSE_SUBTRACT, + + // GL_KHR_blend_equation_advanced. + GR_GL_SCREEN, + GR_GL_OVERLAY, + GR_GL_DARKEN, + GR_GL_LIGHTEN, + GR_GL_COLORDODGE, + GR_GL_COLORBURN, + GR_GL_HARDLIGHT, + GR_GL_SOFTLIGHT, + GR_GL_DIFFERENCE, + GR_GL_EXCLUSION, + GR_GL_MULTIPLY, + GR_GL_HSL_HUE, + GR_GL_HSL_SATURATION, + GR_GL_HSL_COLOR, + GR_GL_HSL_LUMINOSITY, + + // Illegal... needs to map to something. + GR_GL_FUNC_ADD, +}; +GR_STATIC_ASSERT(0 == kAdd_GrBlendEquation); +GR_STATIC_ASSERT(1 == kSubtract_GrBlendEquation); +GR_STATIC_ASSERT(2 == kReverseSubtract_GrBlendEquation); +GR_STATIC_ASSERT(3 == kScreen_GrBlendEquation); +GR_STATIC_ASSERT(4 == kOverlay_GrBlendEquation); +GR_STATIC_ASSERT(5 == kDarken_GrBlendEquation); +GR_STATIC_ASSERT(6 == kLighten_GrBlendEquation); +GR_STATIC_ASSERT(7 == kColorDodge_GrBlendEquation); +GR_STATIC_ASSERT(8 == kColorBurn_GrBlendEquation); +GR_STATIC_ASSERT(9 == kHardLight_GrBlendEquation); +GR_STATIC_ASSERT(10 == kSoftLight_GrBlendEquation); +GR_STATIC_ASSERT(11 == kDifference_GrBlendEquation); +GR_STATIC_ASSERT(12 == kExclusion_GrBlendEquation); +GR_STATIC_ASSERT(13 == kMultiply_GrBlendEquation); +GR_STATIC_ASSERT(14 == kHSLHue_GrBlendEquation); +GR_STATIC_ASSERT(15 == kHSLSaturation_GrBlendEquation); +GR_STATIC_ASSERT(16 == kHSLColor_GrBlendEquation); +GR_STATIC_ASSERT(17 == kHSLLuminosity_GrBlendEquation); +GR_STATIC_ASSERT(SK_ARRAY_COUNT(gXfermodeEquation2Blend) == kGrBlendEquationCnt); + +static const GrGLenum gXfermodeCoeff2Blend[] = { + GR_GL_ZERO, + GR_GL_ONE, + GR_GL_SRC_COLOR, + GR_GL_ONE_MINUS_SRC_COLOR, + GR_GL_DST_COLOR, + GR_GL_ONE_MINUS_DST_COLOR, + GR_GL_SRC_ALPHA, + GR_GL_ONE_MINUS_SRC_ALPHA, + GR_GL_DST_ALPHA, + GR_GL_ONE_MINUS_DST_ALPHA, + GR_GL_CONSTANT_COLOR, + GR_GL_ONE_MINUS_CONSTANT_COLOR, + GR_GL_CONSTANT_ALPHA, + GR_GL_ONE_MINUS_CONSTANT_ALPHA, + + // extended blend coeffs + GR_GL_SRC1_COLOR, + GR_GL_ONE_MINUS_SRC1_COLOR, + GR_GL_SRC1_ALPHA, + GR_GL_ONE_MINUS_SRC1_ALPHA, + + // Illegal... needs to map to something. + GR_GL_ZERO, +}; + +bool GrGLGpu::BlendCoeffReferencesConstant(GrBlendCoeff coeff) { + static const bool gCoeffReferencesBlendConst[] = { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + + // extended blend coeffs + false, + false, + false, + false, + + // Illegal. + false, + }; + return gCoeffReferencesBlendConst[coeff]; + GR_STATIC_ASSERT(kGrBlendCoeffCnt == SK_ARRAY_COUNT(gCoeffReferencesBlendConst)); + + GR_STATIC_ASSERT(0 == kZero_GrBlendCoeff); + GR_STATIC_ASSERT(1 == kOne_GrBlendCoeff); + GR_STATIC_ASSERT(2 == kSC_GrBlendCoeff); + GR_STATIC_ASSERT(3 == kISC_GrBlendCoeff); + GR_STATIC_ASSERT(4 == kDC_GrBlendCoeff); + GR_STATIC_ASSERT(5 == kIDC_GrBlendCoeff); + GR_STATIC_ASSERT(6 == kSA_GrBlendCoeff); + GR_STATIC_ASSERT(7 == kISA_GrBlendCoeff); + GR_STATIC_ASSERT(8 == kDA_GrBlendCoeff); + GR_STATIC_ASSERT(9 == kIDA_GrBlendCoeff); + GR_STATIC_ASSERT(10 == kConstC_GrBlendCoeff); + GR_STATIC_ASSERT(11 == kIConstC_GrBlendCoeff); + GR_STATIC_ASSERT(12 == kConstA_GrBlendCoeff); + GR_STATIC_ASSERT(13 == kIConstA_GrBlendCoeff); + + GR_STATIC_ASSERT(14 == kS2C_GrBlendCoeff); + GR_STATIC_ASSERT(15 == kIS2C_GrBlendCoeff); + GR_STATIC_ASSERT(16 == kS2A_GrBlendCoeff); + GR_STATIC_ASSERT(17 == kIS2A_GrBlendCoeff); + + // assertion for gXfermodeCoeff2Blend have to be in GrGpu scope + GR_STATIC_ASSERT(kGrBlendCoeffCnt == SK_ARRAY_COUNT(gXfermodeCoeff2Blend)); +} + +////////////////////////////////////////////////////////////////////////////// + +static int gl_target_to_binding_index(GrGLenum target) { + switch (target) { + case GR_GL_TEXTURE_2D: + return 0; + case GR_GL_TEXTURE_RECTANGLE: + return 1; + case GR_GL_TEXTURE_EXTERNAL: + return 2; + } + SK_ABORT("Unexpected GL texture target."); +} + +GrGpuResource::UniqueID GrGLGpu::TextureUnitBindings::boundID(GrGLenum target) const { + return fTargetBindings[gl_target_to_binding_index(target)].fBoundResourceID; +} + +bool GrGLGpu::TextureUnitBindings::hasBeenModified(GrGLenum target) const { + return fTargetBindings[gl_target_to_binding_index(target)].fHasBeenModified; +} + +void GrGLGpu::TextureUnitBindings::setBoundID(GrGLenum target, GrGpuResource::UniqueID resourceID) { + int targetIndex = gl_target_to_binding_index(target); + fTargetBindings[targetIndex].fBoundResourceID = resourceID; + fTargetBindings[targetIndex].fHasBeenModified = true; +} + +void GrGLGpu::TextureUnitBindings::invalidateForScratchUse(GrGLenum target) { + this->setBoundID(target, GrGpuResource::UniqueID()); +} + +void GrGLGpu::TextureUnitBindings::invalidateAllTargets(bool markUnmodified) { + for (auto& targetBinding : fTargetBindings) { + targetBinding.fBoundResourceID.makeInvalid(); + if (markUnmodified) { + targetBinding.fHasBeenModified = false; + } + } +} + +////////////////////////////////////////////////////////////////////////////// + +static GrGLenum filter_to_gl_mag_filter(GrSamplerState::Filter filter) { + switch (filter) { + case GrSamplerState::Filter::kNearest: return GR_GL_NEAREST; + case GrSamplerState::Filter::kBilerp: return GR_GL_LINEAR; + case GrSamplerState::Filter::kMipMap: return GR_GL_LINEAR; + } + SK_ABORT("Unknown filter"); +} + +static GrGLenum filter_to_gl_min_filter(GrSamplerState::Filter filter) { + switch (filter) { + case GrSamplerState::Filter::kNearest: return GR_GL_NEAREST; + case GrSamplerState::Filter::kBilerp: return GR_GL_LINEAR; + case GrSamplerState::Filter::kMipMap: return GR_GL_LINEAR_MIPMAP_LINEAR; + } + SK_ABORT("Unknown filter"); +} + +static inline GrGLenum wrap_mode_to_gl_wrap(GrSamplerState::WrapMode wrapMode, + const GrCaps& caps) { + switch (wrapMode) { + case GrSamplerState::WrapMode::kClamp: return GR_GL_CLAMP_TO_EDGE; + case GrSamplerState::WrapMode::kRepeat: return GR_GL_REPEAT; + case GrSamplerState::WrapMode::kMirrorRepeat: return GR_GL_MIRRORED_REPEAT; + case GrSamplerState::WrapMode::kClampToBorder: + // May not be supported but should have been caught earlier + SkASSERT(caps.clampToBorderSupport()); + return GR_GL_CLAMP_TO_BORDER; + } + SK_ABORT("Unknown wrap mode"); +} + +/////////////////////////////////////////////////////////////////////////////// + +class GrGLGpu::SamplerObjectCache { +public: + SamplerObjectCache(GrGLGpu* gpu) : fGpu(gpu) { + fNumTextureUnits = fGpu->glCaps().shaderCaps()->maxFragmentSamplers(); + fHWBoundSamplers.reset(new GrGLuint[fNumTextureUnits]); + std::fill_n(fHWBoundSamplers.get(), fNumTextureUnits, 0); + std::fill_n(fSamplers, kNumSamplers, 0); + } + + ~SamplerObjectCache() { + if (!fNumTextureUnits) { + // We've already been abandoned. + return; + } + for (GrGLuint sampler : fSamplers) { + // The spec states that "zero" values should be silently ignored, however they still + // trigger GL errors on some NVIDIA platforms. + if (sampler) { + GR_GL_CALL(fGpu->glInterface(), DeleteSamplers(1, &sampler)); + } + } + } + + void bindSampler(int unitIdx, const GrSamplerState& state) { + int index = StateToIndex(state); + if (!fSamplers[index]) { + GrGLuint s; + GR_GL_CALL(fGpu->glInterface(), GenSamplers(1, &s)); + if (!s) { + return; + } + fSamplers[index] = s; + auto minFilter = filter_to_gl_min_filter(state.filter()); + auto magFilter = filter_to_gl_mag_filter(state.filter()); + auto wrapX = wrap_mode_to_gl_wrap(state.wrapModeX(), fGpu->glCaps()); + auto wrapY = wrap_mode_to_gl_wrap(state.wrapModeY(), fGpu->glCaps()); + GR_GL_CALL(fGpu->glInterface(), + SamplerParameteri(s, GR_GL_TEXTURE_MIN_FILTER, minFilter)); + GR_GL_CALL(fGpu->glInterface(), + SamplerParameteri(s, GR_GL_TEXTURE_MAG_FILTER, magFilter)); + GR_GL_CALL(fGpu->glInterface(), SamplerParameteri(s, GR_GL_TEXTURE_WRAP_S, wrapX)); + GR_GL_CALL(fGpu->glInterface(), SamplerParameteri(s, GR_GL_TEXTURE_WRAP_T, wrapY)); + } + if (fHWBoundSamplers[unitIdx] != fSamplers[index]) { + GR_GL_CALL(fGpu->glInterface(), BindSampler(unitIdx, fSamplers[index])); + fHWBoundSamplers[unitIdx] = fSamplers[index]; + } + } + + void invalidateBindings() { + // When we have sampler support we always use samplers. So setting these to zero will cause + // a rebind on next usage. + std::fill_n(fHWBoundSamplers.get(), fNumTextureUnits, 0); + } + + void abandon() { + fHWBoundSamplers.reset(); + fNumTextureUnits = 0; + } + + void release() { + if (!fNumTextureUnits) { + // We've already been abandoned. + return; + } + GR_GL_CALL(fGpu->glInterface(), DeleteSamplers(kNumSamplers, fSamplers)); + std::fill_n(fSamplers, kNumSamplers, 0); + // Deleting a bound sampler implicitly binds sampler 0. + std::fill_n(fHWBoundSamplers.get(), fNumTextureUnits, 0); + } + +private: + static int StateToIndex(const GrSamplerState& state) { + int filter = static_cast(state.filter()); + SkASSERT(filter >= 0 && filter < 3); + int wrapX = static_cast(state.wrapModeX()); + SkASSERT(wrapX >= 0 && wrapX < 4); + int wrapY = static_cast(state.wrapModeY()); + SkASSERT(wrapY >= 0 && wrapY < 4); + int idx = 16 * filter + 4 * wrapX + wrapY; + SkASSERT(idx < kNumSamplers); + return idx; + } + + GrGLGpu* fGpu; + static constexpr int kNumSamplers = 48; + std::unique_ptr fHWBoundSamplers; + GrGLuint fSamplers[kNumSamplers]; + int fNumTextureUnits; +}; + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp GrGLGpu::Make(sk_sp interface, const GrContextOptions& options, + GrContext* context) { + if (!interface) { + interface = GrGLMakeNativeInterface(); + // For clients that have written their own GrGLCreateNativeInterface and haven't yet updated + // to GrGLMakeNativeInterface. + if (!interface) { + interface = sk_ref_sp(GrGLCreateNativeInterface()); + } + if (!interface) { + return nullptr; + } + } +#ifdef USE_NSIGHT + const_cast(options).fSuppressPathRendering = true; +#endif + auto glContext = GrGLContext::Make(std::move(interface), options); + if (!glContext) { + return nullptr; + } + return sk_sp(new GrGLGpu(std::move(glContext), context)); +} + +GrGLGpu::GrGLGpu(std::unique_ptr ctx, GrContext* context) + : GrGpu(context) + , fGLContext(std::move(ctx)) + , fProgramCache(new ProgramCache(this)) + , fHWProgramID(0) + , fTempSrcFBOID(0) + , fTempDstFBOID(0) + , fStencilClearFBOID(0) { + SkASSERT(fGLContext); + GrGLClearErr(this->glInterface()); + fCaps = sk_ref_sp(fGLContext->caps()); + + fHWTextureUnitBindings.reset(this->numTextureUnits()); + + this->hwBufferState(GrGpuBufferType::kVertex)->fGLTarget = GR_GL_ARRAY_BUFFER; + this->hwBufferState(GrGpuBufferType::kIndex)->fGLTarget = GR_GL_ELEMENT_ARRAY_BUFFER; + if (GrGLCaps::kChromium_TransferBufferType == this->glCaps().transferBufferType()) { + this->hwBufferState(GrGpuBufferType::kXferCpuToGpu)->fGLTarget = + GR_GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM; + this->hwBufferState(GrGpuBufferType::kXferGpuToCpu)->fGLTarget = + GR_GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM; + } else { + this->hwBufferState(GrGpuBufferType::kXferCpuToGpu)->fGLTarget = GR_GL_PIXEL_UNPACK_BUFFER; + this->hwBufferState(GrGpuBufferType::kXferGpuToCpu)->fGLTarget = GR_GL_PIXEL_PACK_BUFFER; + } + for (int i = 0; i < kGrGpuBufferTypeCount; ++i) { + fHWBufferState[i].invalidate(); + } + GR_STATIC_ASSERT(4 == SK_ARRAY_COUNT(fHWBufferState)); + + if (this->glCaps().shaderCaps()->pathRenderingSupport()) { + fPathRendering.reset(new GrGLPathRendering(this)); + } + + if (this->glCaps().samplerObjectSupport()) { + fSamplerObjectCache.reset(new SamplerObjectCache(this)); + } +} + +GrGLGpu::~GrGLGpu() { + // Ensure any GrGpuResource objects get deleted first, since they may require a working GrGLGpu + // to release the resources held by the objects themselves. + fPathRendering.reset(); + fCopyProgramArrayBuffer.reset(); + fMipmapProgramArrayBuffer.reset(); + + fHWProgram.reset(); + if (fHWProgramID) { + // detach the current program so there is no confusion on OpenGL's part + // that we want it to be deleted + GL_CALL(UseProgram(0)); + } + + if (fTempSrcFBOID) { + this->deleteFramebuffer(fTempSrcFBOID); + } + if (fTempDstFBOID) { + this->deleteFramebuffer(fTempDstFBOID); + } + if (fStencilClearFBOID) { + this->deleteFramebuffer(fStencilClearFBOID); + } + + for (size_t i = 0; i < SK_ARRAY_COUNT(fCopyPrograms); ++i) { + if (0 != fCopyPrograms[i].fProgram) { + GL_CALL(DeleteProgram(fCopyPrograms[i].fProgram)); + } + } + + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + if (0 != fMipmapPrograms[i].fProgram) { + GL_CALL(DeleteProgram(fMipmapPrograms[i].fProgram)); + } + } + + delete fProgramCache; + fSamplerObjectCache.reset(); +} + +void GrGLGpu::disconnect(DisconnectType type) { + INHERITED::disconnect(type); + if (DisconnectType::kCleanup == type) { + if (fHWProgramID) { + GL_CALL(UseProgram(0)); + } + if (fTempSrcFBOID) { + this->deleteFramebuffer(fTempSrcFBOID); + } + if (fTempDstFBOID) { + this->deleteFramebuffer(fTempDstFBOID); + } + if (fStencilClearFBOID) { + this->deleteFramebuffer(fStencilClearFBOID); + } + for (size_t i = 0; i < SK_ARRAY_COUNT(fCopyPrograms); ++i) { + if (fCopyPrograms[i].fProgram) { + GL_CALL(DeleteProgram(fCopyPrograms[i].fProgram)); + } + } + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + if (fMipmapPrograms[i].fProgram) { + GL_CALL(DeleteProgram(fMipmapPrograms[i].fProgram)); + } + } + + if (fSamplerObjectCache) { + fSamplerObjectCache->release(); + } + } else { + if (fProgramCache) { + fProgramCache->abandon(); + } + if (fSamplerObjectCache) { + fSamplerObjectCache->abandon(); + } + } + + fHWProgram.reset(); + delete fProgramCache; + fProgramCache = nullptr; + + fHWProgramID = 0; + fTempSrcFBOID = 0; + fTempDstFBOID = 0; + fStencilClearFBOID = 0; + fCopyProgramArrayBuffer.reset(); + for (size_t i = 0; i < SK_ARRAY_COUNT(fCopyPrograms); ++i) { + fCopyPrograms[i].fProgram = 0; + } + fMipmapProgramArrayBuffer.reset(); + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + fMipmapPrograms[i].fProgram = 0; + } + + if (this->glCaps().shaderCaps()->pathRenderingSupport()) { + this->glPathRendering()->disconnect(type); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLGpu::onResetContext(uint32_t resetBits) { + if (resetBits & kMisc_GrGLBackendState) { + // we don't use the zb at all + GL_CALL(Disable(GR_GL_DEPTH_TEST)); + GL_CALL(DepthMask(GR_GL_FALSE)); + + // We don't use face culling. + GL_CALL(Disable(GR_GL_CULL_FACE)); + // We do use separate stencil. Our algorithms don't care which face is front vs. back so + // just set this to the default for self-consistency. + GL_CALL(FrontFace(GR_GL_CCW)); + + this->hwBufferState(GrGpuBufferType::kXferCpuToGpu)->invalidate(); + this->hwBufferState(GrGpuBufferType::kXferGpuToCpu)->invalidate(); + + if (GR_IS_GR_GL(this->glStandard())) { +#ifndef USE_NSIGHT + // Desktop-only state that we never change + if (!this->glCaps().isCoreProfile()) { + GL_CALL(Disable(GR_GL_POINT_SMOOTH)); + GL_CALL(Disable(GR_GL_LINE_SMOOTH)); + GL_CALL(Disable(GR_GL_POLYGON_SMOOTH)); + GL_CALL(Disable(GR_GL_POLYGON_STIPPLE)); + GL_CALL(Disable(GR_GL_COLOR_LOGIC_OP)); + GL_CALL(Disable(GR_GL_INDEX_LOGIC_OP)); + } + // The windows NVIDIA driver has GL_ARB_imaging in the extension string when using a + // core profile. This seems like a bug since the core spec removes any mention of + // GL_ARB_imaging. + if (this->glCaps().imagingSupport() && !this->glCaps().isCoreProfile()) { + GL_CALL(Disable(GR_GL_COLOR_TABLE)); + } + GL_CALL(Disable(GR_GL_POLYGON_OFFSET_FILL)); + + if (this->caps()->wireframeMode()) { + GL_CALL(PolygonMode(GR_GL_FRONT_AND_BACK, GR_GL_LINE)); + } else { + GL_CALL(PolygonMode(GR_GL_FRONT_AND_BACK, GR_GL_FILL)); + } +#endif + // Since ES doesn't support glPointSize at all we always use the VS to + // set the point size + GL_CALL(Enable(GR_GL_VERTEX_PROGRAM_POINT_SIZE)); + + } + + if (GR_IS_GR_GL_ES(this->glStandard()) && + this->glCaps().fbFetchRequiresEnablePerSample()) { + // The arm extension requires specifically enabling MSAA fetching per sample. + // On some devices this may have a perf hit. Also multiple render targets are disabled + GL_CALL(Enable(GR_GL_FETCH_PER_SAMPLE)); + } + fHWWriteToColor = kUnknown_TriState; + // we only ever use lines in hairline mode + GL_CALL(LineWidth(1)); + GL_CALL(Disable(GR_GL_DITHER)); + + fHWClearColor[0] = fHWClearColor[1] = fHWClearColor[2] = fHWClearColor[3] = SK_FloatNaN; + } + + if (resetBits & kMSAAEnable_GrGLBackendState) { + fMSAAEnabled = kUnknown_TriState; + + if (this->caps()->mixedSamplesSupport()) { + // The skia blend modes all use premultiplied alpha and therefore expect RGBA coverage + // modulation. This state has no effect when not rendering to a mixed sampled target. + GL_CALL(CoverageModulation(GR_GL_RGBA)); + } + } + + fHWActiveTextureUnitIdx = -1; // invalid + fLastPrimitiveType = static_cast(-1); + + if (resetBits & kTextureBinding_GrGLBackendState) { + for (int s = 0; s < this->numTextureUnits(); ++s) { + fHWTextureUnitBindings[s].invalidateAllTargets(false); + } + if (fSamplerObjectCache) { + fSamplerObjectCache->invalidateBindings(); + } + } + + if (resetBits & kBlend_GrGLBackendState) { + fHWBlendState.invalidate(); + } + + if (resetBits & kView_GrGLBackendState) { + fHWScissorSettings.invalidate(); + fHWWindowRectsState.invalidate(); + fHWViewport.invalidate(); + } + + if (resetBits & kStencil_GrGLBackendState) { + fHWStencilSettings.invalidate(); + fHWStencilTestEnabled = kUnknown_TriState; + } + + // Vertex + if (resetBits & kVertex_GrGLBackendState) { + fHWVertexArrayState.invalidate(); + this->hwBufferState(GrGpuBufferType::kVertex)->invalidate(); + this->hwBufferState(GrGpuBufferType::kIndex)->invalidate(); + } + + if (resetBits & kRenderTarget_GrGLBackendState) { + fHWBoundRenderTargetUniqueID.makeInvalid(); + fHWSRGBFramebuffer = kUnknown_TriState; + } + + if (resetBits & kPathRendering_GrGLBackendState) { + if (this->caps()->shaderCaps()->pathRenderingSupport()) { + this->glPathRendering()->resetContext(); + } + } + + // we assume these values + if (resetBits & kPixelStore_GrGLBackendState) { + if (this->caps()->writePixelsRowBytesSupport()) { + GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0)); + } + if (this->glCaps().readPixelsRowBytesSupport()) { + GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, 0)); + } + if (this->glCaps().packFlipYSupport()) { + GL_CALL(PixelStorei(GR_GL_PACK_REVERSE_ROW_ORDER, GR_GL_FALSE)); + } + } + + if (resetBits & kProgram_GrGLBackendState) { + fHWProgramID = 0; + fHWProgram.reset(); + } + ++fResetTimestampForTextureParameters; +} + +static bool check_backend_texture(const GrBackendTexture& backendTex, const GrColorType colorType, + const GrGLCaps& caps, GrGLTexture::Desc* desc, + bool skipRectTexSupportCheck = false) { + GrGLTextureInfo info; + if (!backendTex.getGLTextureInfo(&info) || !info.fID || !info.fFormat) { + return false; + } + + desc->fSize = {backendTex.width(), backendTex.height()}; + desc->fTarget = info.fTarget; + desc->fID = info.fID; + desc->fFormat = GrGLFormatFromGLEnum(info.fFormat); + + if (desc->fFormat == GrGLFormat::kUnknown) { + return false; + } + if (GR_GL_TEXTURE_EXTERNAL == desc->fTarget) { + if (!caps.shaderCaps()->externalTextureSupport()) { + return false; + } + } else if (GR_GL_TEXTURE_RECTANGLE == desc->fTarget) { + if (!caps.rectangleTextureSupport() && !skipRectTexSupportCheck) { + return false; + } + } else if (GR_GL_TEXTURE_2D != desc->fTarget) { + return false; + } + if (backendTex.isProtected()) { + // Not supported in GL backend at this time. + return false; + } + + desc->fConfig = caps.getConfigFromBackendFormat(backendTex.getBackendFormat(), colorType); + SkASSERT(desc->fConfig != kUnknown_GrPixelConfig); + + return true; +} + +sk_sp GrGLGpu::onWrapBackendTexture(const GrBackendTexture& backendTex, + GrColorType colorType, GrWrapOwnership ownership, + GrWrapCacheable cacheable, GrIOType ioType) { + GrGLTexture::Desc desc; + if (!check_backend_texture(backendTex, colorType, this->glCaps(), &desc)) { + return nullptr; + } + + if (kBorrow_GrWrapOwnership == ownership) { + desc.fOwnership = GrBackendObjectOwnership::kBorrowed; + } else { + desc.fOwnership = GrBackendObjectOwnership::kOwned; + } + + GrMipMapsStatus mipMapsStatus = backendTex.hasMipMaps() ? GrMipMapsStatus::kValid + : GrMipMapsStatus::kNotAllocated; + + auto texture = GrGLTexture::MakeWrapped(this, mipMapsStatus, desc, + backendTex.getGLTextureParams(), cacheable, ioType); + // We don't know what parameters are already set on wrapped textures. + texture->textureParamsModified(); + return texture; +} + +sk_sp GrGLGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex, + int sampleCnt, + GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable) { + const GrGLCaps& caps = this->glCaps(); + + GrGLTexture::Desc desc; + if (!check_backend_texture(backendTex, colorType, this->glCaps(), &desc)) { + return nullptr; + } + SkASSERT(caps.isFormatRenderable(desc.fFormat, sampleCnt)); + SkASSERT(caps.isFormatTexturable(desc.fFormat)); + + // We don't support rendering to a EXTERNAL texture. + if (GR_GL_TEXTURE_EXTERNAL == desc.fTarget) { + return nullptr; + } + + if (kBorrow_GrWrapOwnership == ownership) { + desc.fOwnership = GrBackendObjectOwnership::kBorrowed; + } else { + desc.fOwnership = GrBackendObjectOwnership::kOwned; + } + + + sampleCnt = caps.getRenderTargetSampleCount(sampleCnt, desc.fFormat); + SkASSERT(sampleCnt); + + GrGLRenderTarget::IDs rtIDs; + if (!this->createRenderTargetObjects(desc, sampleCnt, &rtIDs)) { + return nullptr; + } + + GrMipMapsStatus mipMapsStatus = backendTex.hasMipMaps() ? GrMipMapsStatus::kDirty + : GrMipMapsStatus::kNotAllocated; + + sk_sp texRT(GrGLTextureRenderTarget::MakeWrapped( + this, sampleCnt, desc, backendTex.getGLTextureParams(), rtIDs, cacheable, + mipMapsStatus)); + texRT->baseLevelWasBoundToFBO(); + // We don't know what parameters are already set on wrapped textures. + texRT->textureParamsModified(); + return texRT; +} + +sk_sp GrGLGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& backendRT, + GrColorType grColorType) { + GrGLFramebufferInfo info; + if (!backendRT.getGLFramebufferInfo(&info)) { + return nullptr; + } + + if (backendRT.isProtected()) { + // Not supported in GL at this time. + return nullptr; + } + + const auto format = backendRT.getBackendFormat().asGLFormat(); + if (!this->glCaps().isFormatRenderable(format, backendRT.sampleCnt())) { + return nullptr; + } + + GrGLRenderTarget::IDs rtIDs; + rtIDs.fRTFBOID = info.fFBOID; + rtIDs.fMSColorRenderbufferID = 0; + rtIDs.fTexFBOID = GrGLRenderTarget::kUnresolvableFBOID; + rtIDs.fRTFBOOwnership = GrBackendObjectOwnership::kBorrowed; + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendRT.getBackendFormat(), + grColorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + const auto size = SkISize::Make(backendRT.width(), backendRT.height()); + int sampleCount = this->glCaps().getRenderTargetSampleCount(backendRT.sampleCnt(), format); + + return GrGLRenderTarget::MakeWrapped(this, size, format, config, sampleCount, rtIDs, + backendRT.stencilBits()); +} + +sk_sp GrGLGpu::onWrapBackendTextureAsRenderTarget(const GrBackendTexture& tex, + int sampleCnt, + GrColorType colorType) { + GrGLTexture::Desc desc; + // We do not check whether texture rectangle is supported by Skia - if the caller provided us + // with a texture rectangle,we assume the necessary support exists. + if (!check_backend_texture(tex, colorType, this->glCaps(), &desc, true)) { + return nullptr; + } + + if (!this->glCaps().isFormatRenderable(desc.fFormat, sampleCnt)) { + return nullptr; + } + + const int sampleCount = this->glCaps().getRenderTargetSampleCount(sampleCnt, desc.fFormat); + GrGLRenderTarget::IDs rtIDs; + if (!this->createRenderTargetObjects(desc, sampleCount, &rtIDs)) { + return nullptr; + } + return GrGLRenderTarget::MakeWrapped(this, desc.fSize, desc.fFormat, desc.fConfig, sampleCount, + rtIDs, 0); +} + +static bool check_write_and_transfer_input(GrGLTexture* glTex) { + if (!glTex) { + return false; + } + + // Write or transfer of pixels is not implemented for TEXTURE_EXTERNAL textures + if (GR_GL_TEXTURE_EXTERNAL == glTex->target()) { + return false; + } + + return true; +} + +bool GrGLGpu::onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) { + auto glTex = static_cast(surface->asTexture()); + + if (!check_write_and_transfer_input(glTex)) { + return false; + } + + this->bindTextureToScratchUnit(glTex->target(), glTex->textureID()); + + SkASSERT(!GrGLFormatIsCompressed(glTex->format())); + return this->uploadTexData(glTex->format(), surfaceColorType, glTex->width(), glTex->height(), + glTex->target(), left, top, width, height, srcColorType, texels, + mipLevelCount); +} + +bool GrGLGpu::onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) { + GrGLTexture* glTex = static_cast(texture); + + // Can't transfer compressed data + SkASSERT(!GrGLFormatIsCompressed(glTex->format())); + + if (!check_write_and_transfer_input(glTex)) { + return false; + } + + static_assert(sizeof(int) == sizeof(int32_t), ""); + if (width <= 0 || height <= 0) { + return false; + } + + this->bindTextureToScratchUnit(glTex->target(), glTex->textureID()); + + SkASSERT(!transferBuffer->isMapped()); + SkASSERT(!transferBuffer->isCpuBuffer()); + const GrGLBuffer* glBuffer = static_cast(transferBuffer); + this->bindBuffer(GrGpuBufferType::kXferCpuToGpu, glBuffer); + + SkDEBUGCODE( + SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); + SkIRect bounds = SkIRect::MakeWH(texture->width(), texture->height()); + SkASSERT(bounds.contains(subRect)); + ) + + size_t bpp = GrColorTypeBytesPerPixel(bufferColorType); + const size_t trimRowBytes = width * bpp; + const void* pixels = (void*)offset; + if (width < 0 || height < 0) { + return false; + } + + bool restoreGLRowLength = false; + if (trimRowBytes != rowBytes) { + // we should have checked for this support already + SkASSERT(this->glCaps().writePixelsRowBytesSupport()); + GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowBytes / bpp)); + restoreGLRowLength = true; + } + + GrGLFormat textureFormat = glTex->format(); + // External format and type come from the upload data. + GrGLenum externalFormat = 0; + GrGLenum externalType = 0; + this->glCaps().getTexSubImageExternalFormatAndType( + textureFormat, textureColorType, bufferColorType, &externalFormat, &externalType); + if (!externalFormat || !externalType) { + return false; + } + + GL_CALL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, 1)); + GL_CALL(TexSubImage2D(glTex->target(), + 0, + left, top, + width, + height, + externalFormat, externalType, + pixels)); + + if (restoreGLRowLength) { + GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0)); + } + + return true; +} + +bool GrGLGpu::onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, + GrGpuBuffer* transferBuffer, size_t offset) { + auto* glBuffer = static_cast(transferBuffer); + this->bindBuffer(GrGpuBufferType::kXferGpuToCpu, glBuffer); + auto offsetAsPtr = reinterpret_cast(offset); + return this->readOrTransferPixelsFrom(surface, left, top, width, height, surfaceColorType, + dstColorType, offsetAsPtr, width); +} + +void GrGLGpu::unbindCpuToGpuXferBuffer() { + auto* xferBufferState = this->hwBufferState(GrGpuBufferType::kXferCpuToGpu); + if (!xferBufferState->fBoundBufferUniqueID.isInvalid()) { + GL_CALL(BindBuffer(xferBufferState->fGLTarget, 0)); + xferBufferState->invalidate(); + } +} + +bool GrGLGpu::uploadTexData(GrGLFormat textureFormat, GrColorType textureColorType, int texWidth, + int texHeight, GrGLenum target, int left, int top, int width, + int height, GrColorType srcColorType, const GrMipLevel texels[], + int mipLevelCount, GrMipMapsStatus* mipMapsStatus) { + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrGLFormatIsCompressed(textureFormat)); + + SkASSERT(this->glCaps().isFormatTexturable(textureFormat)); + SkDEBUGCODE( + SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); + SkIRect bounds = SkIRect::MakeWH(texWidth, texHeight); + SkASSERT(bounds.contains(subRect)); + ) + SkASSERT(1 == mipLevelCount || + (0 == left && 0 == top && width == texWidth && height == texHeight)); + + this->unbindCpuToGpuXferBuffer(); + + const GrGLInterface* interface = this->glInterface(); + const GrGLCaps& caps = this->glCaps(); + + size_t bpp = GrColorTypeBytesPerPixel(srcColorType); + + if (width == 0 || height == 0) { + return false; + } + + // External format and type come from the upload data. + GrGLenum externalFormat; + GrGLenum externalType; + this->glCaps().getTexSubImageExternalFormatAndType( + textureFormat, textureColorType, srcColorType, &externalFormat, &externalType); + if (!externalFormat || !externalType) { + return false; + } + + /* + * Check whether to allocate a temporary buffer for flipping y or + * because our srcData has extra bytes past each row. If so, we need + * to trim those off here, since GL ES may not let us specify + * GL_UNPACK_ROW_LENGTH. + */ + bool restoreGLRowLength = false; + + if (mipMapsStatus) { + *mipMapsStatus = (mipLevelCount > 1) ? + GrMipMapsStatus::kValid : GrMipMapsStatus::kNotAllocated; + } + + GR_GL_CALL(interface, PixelStorei(GR_GL_UNPACK_ALIGNMENT, 1)); + + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + if (!texels[currentMipLevel].fPixels) { + if (mipMapsStatus) { + *mipMapsStatus = GrMipMapsStatus::kDirty; + } + continue; + } + int twoToTheMipLevel = 1 << currentMipLevel; + const int currentWidth = SkTMax(1, width / twoToTheMipLevel); + const int currentHeight = SkTMax(1, height / twoToTheMipLevel); + const size_t trimRowBytes = currentWidth * bpp; + const size_t rowBytes = texels[currentMipLevel].fRowBytes; + + if (caps.writePixelsRowBytesSupport() && (rowBytes != trimRowBytes || restoreGLRowLength)) { + GrGLint rowLength = static_cast(rowBytes / bpp); + GR_GL_CALL(interface, PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowLength)); + restoreGLRowLength = true; + } + + GL_CALL(TexSubImage2D(target, currentMipLevel, left, top, currentWidth, currentHeight, + externalFormat, externalType, texels[currentMipLevel].fPixels)); + } + if (restoreGLRowLength) { + SkASSERT(caps.writePixelsRowBytesSupport()); + GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0)); + } + return true; +} + +bool GrGLGpu::uploadCompressedTexData(GrGLFormat format, + SkImage::CompressionType compressionType, + const SkISize& size, + GrGLenum target, + const void* data) { + SkASSERT(format != GrGLFormat::kUnknown); + const GrGLCaps& caps = this->glCaps(); + + // We only need the internal format for compressed 2D textures. + GrGLenum internalFormat = caps.getTexImageOrStorageInternalFormat(format); + if (!internalFormat) { + return 0; + } + + bool useTexStorage = caps.formatSupportsTexStorage(format); + + static constexpr int kMipLevelCount = 1; + + // Make sure that the width and height that we pass to OpenGL + // is a multiple of the block size. + size_t dataSize = GrCompressedDataSize(compressionType, size.width(), size.height()); + + if (useTexStorage) { + // We never resize or change formats of textures. + GL_ALLOC_CALL( + this->glInterface(), + TexStorage2D(target, kMipLevelCount, internalFormat, size.width(), size.height())); + GrGLenum error = CHECK_ALLOC_ERROR(this->glInterface()); + if (error != GR_GL_NO_ERROR) { + return false; + } + GL_CALL(CompressedTexSubImage2D(target, + 0, // level + 0, // left + 0, // top + size.width(), + size.height(), + internalFormat, + SkToInt(dataSize), + data)); + } else { + GL_ALLOC_CALL(this->glInterface(), CompressedTexImage2D(target, + 0, // level + internalFormat, + size.width(), + size.height(), + 0, // border + SkToInt(dataSize), + data)); + + GrGLenum error = CHECK_ALLOC_ERROR(this->glInterface()); + if (error != GR_GL_NO_ERROR) { + return false; + } + } + return true; +} + +static bool renderbuffer_storage_msaa(const GrGLContext& ctx, + int sampleCount, + GrGLenum format, + int width, int height) { + CLEAR_ERROR_BEFORE_ALLOC(ctx.interface()); + SkASSERT(GrGLCaps::kNone_MSFBOType != ctx.caps()->msFBOType()); + switch (ctx.caps()->msFBOType()) { + case GrGLCaps::kStandard_MSFBOType: + GL_ALLOC_CALL(ctx.interface(), + RenderbufferStorageMultisample(GR_GL_RENDERBUFFER, + sampleCount, + format, + width, height)); + break; + case GrGLCaps::kES_Apple_MSFBOType: + GL_ALLOC_CALL(ctx.interface(), + RenderbufferStorageMultisampleES2APPLE(GR_GL_RENDERBUFFER, + sampleCount, + format, + width, height)); + break; + case GrGLCaps::kES_EXT_MsToTexture_MSFBOType: + case GrGLCaps::kES_IMG_MsToTexture_MSFBOType: + GL_ALLOC_CALL(ctx.interface(), + RenderbufferStorageMultisampleES2EXT(GR_GL_RENDERBUFFER, + sampleCount, + format, + width, height)); + break; + case GrGLCaps::kNone_MSFBOType: + SK_ABORT("Shouldn't be here if we don't support multisampled renderbuffers."); + break; + } + return (GR_GL_NO_ERROR == CHECK_ALLOC_ERROR(ctx.interface())); +} + +bool GrGLGpu::createRenderTargetObjects(const GrGLTexture::Desc& desc, + int sampleCount, + GrGLRenderTarget::IDs* rtIDs) { + rtIDs->fMSColorRenderbufferID = 0; + rtIDs->fRTFBOID = 0; + rtIDs->fRTFBOOwnership = GrBackendObjectOwnership::kOwned; + rtIDs->fTexFBOID = 0; + + GrGLenum colorRenderbufferFormat = 0; // suppress warning + + if (desc.fFormat == GrGLFormat::kUnknown) { + goto FAILED; + } + + if (sampleCount > 1 && GrGLCaps::kNone_MSFBOType == this->glCaps().msFBOType()) { + goto FAILED; + } + + GL_CALL(GenFramebuffers(1, &rtIDs->fTexFBOID)); + if (!rtIDs->fTexFBOID) { + goto FAILED; + } + + // If we are using multisampling we will create two FBOS. We render to one and then resolve to + // the texture bound to the other. The exception is the IMG multisample extension. With this + // extension the texture is multisampled when rendered to and then auto-resolves it when it is + // rendered from. + if (sampleCount > 1 && this->glCaps().usesMSAARenderBuffers()) { + GL_CALL(GenFramebuffers(1, &rtIDs->fRTFBOID)); + GL_CALL(GenRenderbuffers(1, &rtIDs->fMSColorRenderbufferID)); + if (!rtIDs->fRTFBOID || !rtIDs->fMSColorRenderbufferID) { + goto FAILED; + } + colorRenderbufferFormat = this->glCaps().getRenderbufferInternalFormat(desc.fFormat); + } else { + rtIDs->fRTFBOID = rtIDs->fTexFBOID; + } + + // below here we may bind the FBO + fHWBoundRenderTargetUniqueID.makeInvalid(); + if (rtIDs->fRTFBOID != rtIDs->fTexFBOID) { + SkASSERT(sampleCount > 1); + GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, rtIDs->fMSColorRenderbufferID)); + if (!renderbuffer_storage_msaa(*fGLContext, sampleCount, colorRenderbufferFormat, + desc.fSize.width(), desc.fSize.height())) { + goto FAILED; + } + this->bindFramebuffer(GR_GL_FRAMEBUFFER, rtIDs->fRTFBOID); + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_COLOR_ATTACHMENT0, + GR_GL_RENDERBUFFER, + rtIDs->fMSColorRenderbufferID)); + } + this->bindFramebuffer(GR_GL_FRAMEBUFFER, rtIDs->fTexFBOID); + + if (this->glCaps().usesImplicitMSAAResolve() && sampleCount > 1) { + GL_CALL(FramebufferTexture2DMultisample(GR_GL_FRAMEBUFFER, + GR_GL_COLOR_ATTACHMENT0, + desc.fTarget, + desc.fID, + 0, + sampleCount)); + } else { + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, + GR_GL_COLOR_ATTACHMENT0, + desc.fTarget, + desc.fID, + 0)); + } + + return true; + +FAILED: + if (rtIDs->fMSColorRenderbufferID) { + GL_CALL(DeleteRenderbuffers(1, &rtIDs->fMSColorRenderbufferID)); + } + if (rtIDs->fRTFBOID != rtIDs->fTexFBOID) { + this->deleteFramebuffer(rtIDs->fRTFBOID); + } + if (rtIDs->fTexFBOID) { + this->deleteFramebuffer(rtIDs->fTexFBOID); + } + return false; +} + +// good to set a break-point here to know when createTexture fails +static sk_sp return_null_texture() { +// SkDEBUGFAIL("null texture"); + return nullptr; +} + +static GrGLTextureParameters::SamplerOverriddenState set_initial_texture_params( + const GrGLInterface* interface, GrGLenum target) { + // Some drivers like to know filter/wrap before seeing glTexImage2D. Some + // drivers have a bug where an FBO won't be complete if it includes a + // texture that is not mipmap complete (considering the filter in use). + GrGLTextureParameters::SamplerOverriddenState state; + state.fMinFilter = GR_GL_NEAREST; + state.fMagFilter = GR_GL_NEAREST; + state.fWrapS = GR_GL_CLAMP_TO_EDGE; + state.fWrapT = GR_GL_CLAMP_TO_EDGE; + GR_GL_CALL(interface, TexParameteri(target, GR_GL_TEXTURE_MAG_FILTER, state.fMagFilter)); + GR_GL_CALL(interface, TexParameteri(target, GR_GL_TEXTURE_MIN_FILTER, state.fMinFilter)); + GR_GL_CALL(interface, TexParameteri(target, GR_GL_TEXTURE_WRAP_S, state.fWrapS)); + GR_GL_CALL(interface, TexParameteri(target, GR_GL_TEXTURE_WRAP_T, state.fWrapT)); + return state; +} + +sk_sp GrGLGpu::onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + int mipLevelCount, + uint32_t levelClearMask) { + // We don't support protected textures in GL. + if (isProtected == GrProtected::kYes) { + return nullptr; + } + SkASSERT(GrGLCaps::kNone_MSFBOType != this->glCaps().msFBOType() || renderTargetSampleCnt == 1); + + SkASSERT(mipLevelCount > 0); + GrMipMapsStatus mipMapsStatus = + mipLevelCount > 1 ? GrMipMapsStatus::kDirty : GrMipMapsStatus::kNotAllocated; + GrGLTextureParameters::SamplerOverriddenState initialState; + GrGLTexture::Desc texDesc; + texDesc.fSize = {desc.fWidth, desc.fHeight}; + texDesc.fTarget = GR_GL_TEXTURE_2D; + texDesc.fFormat = format.asGLFormat(); + texDesc.fConfig = desc.fConfig; + texDesc.fOwnership = GrBackendObjectOwnership::kOwned; + SkASSERT(texDesc.fFormat != GrGLFormat::kUnknown); + SkASSERT(!GrGLFormatIsCompressed(texDesc.fFormat)); + + texDesc.fID = this->createTexture2D({desc.fWidth, desc.fHeight}, texDesc.fFormat, renderable, + &initialState, mipLevelCount); + + if (!texDesc.fID) { + return return_null_texture(); + } + + sk_sp tex; + if (renderable == GrRenderable::kYes) { + // unbind the texture from the texture unit before binding it to the frame buffer + GL_CALL(BindTexture(texDesc.fTarget, 0)); + GrGLRenderTarget::IDs rtIDDesc; + + if (!this->createRenderTargetObjects(texDesc, renderTargetSampleCnt, &rtIDDesc)) { + GL_CALL(DeleteTextures(1, &texDesc.fID)); + return return_null_texture(); + } + tex = sk_make_sp( + this, budgeted, renderTargetSampleCnt, texDesc, rtIDDesc, mipMapsStatus); + tex->baseLevelWasBoundToFBO(); + } else { + tex = sk_make_sp(this, budgeted, texDesc, mipMapsStatus); + } + // The non-sampler params are still at their default values. + tex->parameters()->set(&initialState, GrGLTextureParameters::NonsamplerState(), + fResetTimestampForTextureParameters); + if (levelClearMask) { + GrGLenum externalFormat, externalType; + size_t bpp; + this->glCaps().getTexSubImageZeroFormatTypeAndBpp(texDesc.fFormat, &externalFormat, + &externalType, &bpp); + if (this->glCaps().clearTextureSupport()) { + for (int i = 0; i < mipLevelCount; ++i) { + if (levelClearMask & (1U << i)) { + GL_CALL(ClearTexImage(tex->textureID(), i, externalFormat, externalType, + nullptr)); + } + } + } else if (this->glCaps().canFormatBeFBOColorAttachment(format.asGLFormat()) && + !this->glCaps().performColorClearsAsDraws()) { + this->disableScissor(); + this->disableWindowRectangles(); + this->flushColorWrite(true); + this->flushClearColor(SK_PMColor4fTRANSPARENT); + for (int i = 0; i < mipLevelCount; ++i) { + if (levelClearMask & (1U << i)) { + this->bindSurfaceFBOForPixelOps(tex.get(), i, GR_GL_FRAMEBUFFER, + kDst_TempFBOTarget); + GL_CALL(Clear(GR_GL_COLOR_BUFFER_BIT)); + this->unbindSurfaceFBOForPixelOps(tex.get(), i, GR_GL_FRAMEBUFFER); + } + } + fHWBoundRenderTargetUniqueID.makeInvalid(); + } else { + std::unique_ptr zeros; + GL_CALL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, 1)); + for (int i = 0; i < mipLevelCount; ++i) { + if (levelClearMask & (1U << i)) { + int levelWidth = SkTMax(1, texDesc.fSize.width() >> i); + int levelHeight = SkTMax(1, texDesc.fSize.height() >> i); + // Levels only get smaller as we proceed. Once we create a zeros use it for all + // smaller levels that need clearing. + if (!zeros) { + size_t size = levelWidth * levelHeight * bpp; + zeros.reset(new char[size]()); + } + this->bindTextureToScratchUnit(GR_GL_TEXTURE_2D, tex->textureID()); + GL_CALL(TexSubImage2D(GR_GL_TEXTURE_2D, i, 0, 0, levelWidth, levelHeight, + externalFormat, externalType, zeros.get())); + } + } + } + } + return tex; +} + +sk_sp GrGLGpu::onCreateCompressedTexture(int width, int height, + const GrBackendFormat& format, + SkImage::CompressionType compression, + SkBudgeted budgeted, const void* data) { + GrGLTextureParameters::SamplerOverriddenState initialState; + GrGLTexture::Desc desc; + desc.fSize = {width, height}; + desc.fTarget = GR_GL_TEXTURE_2D; + desc.fConfig = GrCompressionTypePixelConfig(compression); + desc.fOwnership = GrBackendObjectOwnership::kOwned; + desc.fFormat = format.asGLFormat(); + desc.fID = this->createCompressedTexture2D(desc.fSize, desc.fFormat, compression, &initialState, + data); + if (!desc.fID) { + return nullptr; + } + auto tex = sk_make_sp(this, budgeted, desc, GrMipMapsStatus::kNotAllocated); + // The non-sampler params are still at their default values. + tex->parameters()->set(&initialState, GrGLTextureParameters::NonsamplerState(), + fResetTimestampForTextureParameters); + return tex; +} + +namespace { + +const GrGLuint kUnknownBitCount = GrGLStencilAttachment::kUnknownBitCount; + +void inline get_stencil_rb_sizes(const GrGLInterface* gl, + GrGLStencilAttachment::Format* format) { + + // we shouldn't ever know one size and not the other + SkASSERT((kUnknownBitCount == format->fStencilBits) == + (kUnknownBitCount == format->fTotalBits)); + if (kUnknownBitCount == format->fStencilBits) { + GR_GL_GetRenderbufferParameteriv(gl, GR_GL_RENDERBUFFER, + GR_GL_RENDERBUFFER_STENCIL_SIZE, + (GrGLint*)&format->fStencilBits); + if (format->fPacked) { + GR_GL_GetRenderbufferParameteriv(gl, GR_GL_RENDERBUFFER, + GR_GL_RENDERBUFFER_DEPTH_SIZE, + (GrGLint*)&format->fTotalBits); + format->fTotalBits += format->fStencilBits; + } else { + format->fTotalBits = format->fStencilBits; + } + } +} +} + +int GrGLGpu::getCompatibleStencilIndex(GrGLFormat format) { + static const int kSize = 16; + SkASSERT(this->glCaps().canFormatBeFBOColorAttachment(format)); + + if (!this->glCaps().hasStencilFormatBeenDeterminedForFormat(format)) { + // Default to unsupported, set this if we find a stencil format that works. + int firstWorkingStencilFormatIndex = -1; + + GrGLuint colorID = + this->createTexture2D({kSize, kSize}, format, GrRenderable::kYes, nullptr, 1); + if (!colorID) { + return -1; + } + // unbind the texture from the texture unit before binding it to the frame buffer + GL_CALL(BindTexture(GR_GL_TEXTURE_2D, 0)); + + // Create Framebuffer + GrGLuint fb = 0; + GL_CALL(GenFramebuffers(1, &fb)); + this->bindFramebuffer(GR_GL_FRAMEBUFFER, fb); + fHWBoundRenderTargetUniqueID.makeInvalid(); + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, + GR_GL_COLOR_ATTACHMENT0, + GR_GL_TEXTURE_2D, + colorID, + 0)); + GrGLuint sbRBID = 0; + GL_CALL(GenRenderbuffers(1, &sbRBID)); + + // look over formats till I find a compatible one + int stencilFmtCnt = this->glCaps().stencilFormats().count(); + if (sbRBID) { + GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, sbRBID)); + for (int i = 0; i < stencilFmtCnt && sbRBID; ++i) { + const GrGLCaps::StencilFormat& sFmt = this->glCaps().stencilFormats()[i]; + CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); + GL_ALLOC_CALL(this->glInterface(), RenderbufferStorage(GR_GL_RENDERBUFFER, + sFmt.fInternalFormat, + kSize, kSize)); + if (GR_GL_NO_ERROR == CHECK_ALLOC_ERROR(this->glInterface())) { + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, sbRBID)); + if (sFmt.fPacked) { + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, sbRBID)); + } else { + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + } + GrGLenum status; + GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER)); + if (status == GR_GL_FRAMEBUFFER_COMPLETE) { + firstWorkingStencilFormatIndex = i; + break; + } + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + if (sFmt.fPacked) { + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + } + } + } + GL_CALL(DeleteRenderbuffers(1, &sbRBID)); + } + GL_CALL(DeleteTextures(1, &colorID)); + this->bindFramebuffer(GR_GL_FRAMEBUFFER, 0); + this->deleteFramebuffer(fb); + fGLContext->caps()->setStencilFormatIndexForFormat(format, firstWorkingStencilFormatIndex); + } + return this->glCaps().getStencilFormatIndexForFormat(format); +} + +GrGLuint GrGLGpu::createCompressedTexture2D( + const SkISize& size, + GrGLFormat format, + SkImage::CompressionType compression, + GrGLTextureParameters::SamplerOverriddenState* initialState, + const void* data) { + if (format == GrGLFormat::kUnknown) { + return 0; + } + GrGLuint id = 0; + GL_CALL(GenTextures(1, &id)); + if (!id) { + return 0; + } + + this->bindTextureToScratchUnit(GR_GL_TEXTURE_2D, id); + + *initialState = set_initial_texture_params(this->glInterface(), GR_GL_TEXTURE_2D); + + if (!this->uploadCompressedTexData(format, compression, size, GR_GL_TEXTURE_2D, data)) { + GL_CALL(DeleteTextures(1, &id)); + return 0; + } + return id; +} + +GrGLuint GrGLGpu::createTexture2D(const SkISize& size, + GrGLFormat format, + GrRenderable renderable, + GrGLTextureParameters::SamplerOverriddenState* initialState, + int mipLevelCount) { + SkASSERT(format != GrGLFormat::kUnknown); + SkASSERT(!GrGLFormatIsCompressed(format)); + + GrGLuint id = 0; + GL_CALL(GenTextures(1, &id)); + + if (!id) { + return 0; + } + + this->bindTextureToScratchUnit(GR_GL_TEXTURE_2D, id); + + if (GrRenderable::kYes == renderable && this->glCaps().textureUsageSupport()) { + // provides a hint about how this texture will be used + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_USAGE, GR_GL_FRAMEBUFFER_ATTACHMENT)); + } + + if (initialState) { + *initialState = set_initial_texture_params(this->glInterface(), GR_GL_TEXTURE_2D); + } else { + set_initial_texture_params(this->glInterface(), GR_GL_TEXTURE_2D); + } + + GrGLenum internalFormat = this->glCaps().getTexImageOrStorageInternalFormat(format); + + bool success = false; + if (internalFormat) { + CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); + if (this->glCaps().formatSupportsTexStorage(format)) { + GL_ALLOC_CALL(this->glInterface(), + TexStorage2D(GR_GL_TEXTURE_2D, SkTMax(mipLevelCount, 1), internalFormat, + size.width(), size.height())); + success = (GR_GL_NO_ERROR == CHECK_ALLOC_ERROR(this->glInterface())); + } else { + GrGLenum externalFormat, externalType; + this->glCaps().getTexImageExternalFormatAndType(format, &externalFormat, &externalType); + GrGLenum error = GR_GL_NO_ERROR; + if (externalFormat && externalType) { + for (int level = 0; level < mipLevelCount && error == GR_GL_NO_ERROR; level++) { + const int twoToTheMipLevel = 1 << level; + const int currentWidth = SkTMax(1, size.width() / twoToTheMipLevel); + const int currentHeight = SkTMax(1, size.height() / twoToTheMipLevel); + GL_ALLOC_CALL( + this->glInterface(), + TexImage2D(GR_GL_TEXTURE_2D, level, internalFormat, currentWidth, + currentHeight, 0, externalFormat, externalType, nullptr)); + error = CHECK_ALLOC_ERROR(this->glInterface()); + } + success = (GR_GL_NO_ERROR == error); + } + } + } + if (success) { + return id; + } + GL_CALL(DeleteTextures(1, &id)); + return 0; +} + +GrStencilAttachment* GrGLGpu::createStencilAttachmentForRenderTarget( + const GrRenderTarget* rt, int width, int height, int numStencilSamples) { + SkASSERT(width >= rt->width()); + SkASSERT(height >= rt->height()); + + GrGLStencilAttachment::IDDesc sbDesc; + + int sIdx = this->getCompatibleStencilIndex(rt->backendFormat().asGLFormat()); + if (sIdx < 0) { + return nullptr; + } + + if (!sbDesc.fRenderbufferID) { + GL_CALL(GenRenderbuffers(1, &sbDesc.fRenderbufferID)); + } + if (!sbDesc.fRenderbufferID) { + return nullptr; + } + GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, sbDesc.fRenderbufferID)); + const GrGLCaps::StencilFormat& sFmt = this->glCaps().stencilFormats()[sIdx]; + CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); + // we do this "if" so that we don't call the multisample + // version on a GL that doesn't have an MSAA extension. + if (numStencilSamples > 1) { + SkAssertResult(renderbuffer_storage_msaa(*fGLContext, + numStencilSamples, + sFmt.fInternalFormat, + width, height)); + } else { + GL_ALLOC_CALL(this->glInterface(), RenderbufferStorage(GR_GL_RENDERBUFFER, + sFmt.fInternalFormat, + width, height)); + SkASSERT(GR_GL_NO_ERROR == CHECK_ALLOC_ERROR(this->glInterface())); + } + fStats.incStencilAttachmentCreates(); + // After sized formats we attempt an unsized format and take + // whatever sizes GL gives us. In that case we query for the size. + GrGLStencilAttachment::Format format = sFmt; + get_stencil_rb_sizes(this->glInterface(), &format); + GrGLStencilAttachment* stencil = new GrGLStencilAttachment(this, + sbDesc, + width, + height, + numStencilSamples, + format); + return stencil; +} + +//////////////////////////////////////////////////////////////////////////////// + +sk_sp GrGLGpu::onCreateBuffer(size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, const void* data) { + return GrGLBuffer::Make(this, size, intendedType, accessPattern, data); +} + +void GrGLGpu::flushScissor(const GrScissorState& scissorState, int rtWidth, int rtHeight, + GrSurfaceOrigin rtOrigin) { + if (scissorState.enabled()) { + auto scissor = GrNativeRect::MakeRelativeTo(rtOrigin, rtHeight, scissorState.rect()); + // if the scissor fully contains the viewport then we fall through and + // disable the scissor test. + if (!scissor.contains(rtWidth, rtHeight)) { + if (fHWScissorSettings.fRect != scissor) { + GL_CALL(Scissor(scissor.fX, scissor.fY, scissor.fWidth, scissor.fHeight)); + fHWScissorSettings.fRect = scissor; + } + if (kYes_TriState != fHWScissorSettings.fEnabled) { + GL_CALL(Enable(GR_GL_SCISSOR_TEST)); + fHWScissorSettings.fEnabled = kYes_TriState; + } + return; + } + } + + // See fall through note above + this->disableScissor(); +} + +void GrGLGpu::flushWindowRectangles(const GrWindowRectsState& windowState, + const GrGLRenderTarget* rt, GrSurfaceOrigin origin) { +#ifndef USE_NSIGHT + typedef GrWindowRectsState::Mode Mode; + SkASSERT(!windowState.enabled() || rt->renderFBOID()); // Window rects can't be used on-screen. + SkASSERT(windowState.numWindows() <= this->caps()->maxWindowRectangles()); + + if (!this->caps()->maxWindowRectangles() || + fHWWindowRectsState.knownEqualTo(origin, rt->width(), rt->height(), windowState)) { + return; + } + + // This is purely a workaround for a spurious warning generated by gcc. Otherwise the above + // assert would be sufficient. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=5912 + int numWindows = SkTMin(windowState.numWindows(), int(GrWindowRectangles::kMaxWindows)); + SkASSERT(windowState.numWindows() == numWindows); + + GrNativeRect glwindows[GrWindowRectangles::kMaxWindows]; + const SkIRect* skwindows = windowState.windows().data(); + for (int i = 0; i < numWindows; ++i) { + glwindows[i].setRelativeTo(origin, rt->height(), skwindows[i]); + } + + GrGLenum glmode = (Mode::kExclusive == windowState.mode()) ? GR_GL_EXCLUSIVE : GR_GL_INCLUSIVE; + GL_CALL(WindowRectangles(glmode, numWindows, glwindows->asInts())); + + fHWWindowRectsState.set(origin, rt->width(), rt->height(), windowState); +#endif +} + +void GrGLGpu::disableWindowRectangles() { +#ifndef USE_NSIGHT + if (!this->caps()->maxWindowRectangles() || fHWWindowRectsState.knownDisabled()) { + return; + } + GL_CALL(WindowRectangles(GR_GL_EXCLUSIVE, 0, nullptr)); + fHWWindowRectsState.setDisabled(); +#endif +} + +bool GrGLGpu::flushGLState(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType) { + + sk_sp program(fProgramCache->refProgram(this, renderTarget, programInfo, + primitiveType)); + if (!program) { + GrCapsDebugf(this->caps(), "Failed to create program!\n"); + return false; + } + + this->flushProgram(std::move(program)); + + // Swizzle the blend to match what the shader will output. + this->flushBlendAndColorWrite(programInfo.pipeline().getXferProcessor().getBlendInfo(), + programInfo.pipeline().outputSwizzle()); + + fHWProgram->updateUniformsAndTextureBindings(renderTarget, programInfo); + + GrGLRenderTarget* glRT = static_cast(renderTarget); + GrStencilSettings stencil; + if (programInfo.pipeline().isStencilEnabled()) { + // TODO: attach stencil and create settings during render target flush. + SkASSERT(glRT->renderTargetPriv().getStencilAttachment()); + stencil.reset(*programInfo.pipeline().getUserStencil(), + programInfo.pipeline().hasStencilClip(), + glRT->renderTargetPriv().numStencilBits()); + } + this->flushStencil(stencil, programInfo.origin()); + if (programInfo.pipeline().isScissorEnabled()) { + static constexpr SkIRect kBogusScissor{0, 0, 1, 1}; + GrScissorState state(programInfo.fixedDynamicState() ? programInfo.fixedScissor() + : kBogusScissor); + this->flushScissor(state, glRT->width(), glRT->height(), programInfo.origin()); + } else { + this->disableScissor(); + } + this->flushWindowRectangles(programInfo.pipeline().getWindowRectsState(), + glRT, programInfo.origin()); + this->flushHWAAState(glRT, programInfo.pipeline().isHWAntialiasState()); + + // This must come after textures are flushed because a texture may need + // to be msaa-resolved (which will modify bound FBO state). + this->flushRenderTarget(glRT); + + return true; +} + +void GrGLGpu::flushProgram(sk_sp program) { + if (!program) { + fHWProgram.reset(); + fHWProgramID = 0; + return; + } + SkASSERT((program == fHWProgram) == (fHWProgramID == program->programID())); + if (program == fHWProgram) { + return; + } + auto id = program->programID(); + SkASSERT(id); + GL_CALL(UseProgram(id)); + fHWProgram = std::move(program); + fHWProgramID = id; +} + +void GrGLGpu::flushProgram(GrGLuint id) { + SkASSERT(id); + if (fHWProgramID == id) { + SkASSERT(!fHWProgram); + return; + } + fHWProgram.reset(); + GL_CALL(UseProgram(id)); + fHWProgramID = id; +} + +void GrGLGpu::setupGeometry(const GrBuffer* indexBuffer, + const GrBuffer* vertexBuffer, + int baseVertex, + const GrBuffer* instanceBuffer, + int baseInstance, + GrPrimitiveRestart enablePrimitiveRestart) { + SkASSERT((enablePrimitiveRestart == GrPrimitiveRestart::kNo) || indexBuffer); + + GrGLAttribArrayState* attribState; + if (indexBuffer) { + SkASSERT(indexBuffer->isCpuBuffer() || + !static_cast(indexBuffer)->isMapped()); + attribState = fHWVertexArrayState.bindInternalVertexArray(this, indexBuffer); + } else { + attribState = fHWVertexArrayState.bindInternalVertexArray(this); + } + + int numAttribs = fHWProgram->numVertexAttributes() + fHWProgram->numInstanceAttributes(); + attribState->enableVertexArrays(this, numAttribs, enablePrimitiveRestart); + + if (int vertexStride = fHWProgram->vertexStride()) { + SkASSERT(vertexBuffer); + SkASSERT(vertexBuffer->isCpuBuffer() || + !static_cast(vertexBuffer)->isMapped()); + size_t bufferOffset = baseVertex * static_cast(vertexStride); + for (int i = 0; i < fHWProgram->numVertexAttributes(); ++i) { + const auto& attrib = fHWProgram->vertexAttribute(i); + static constexpr int kDivisor = 0; + attribState->set(this, attrib.fLocation, vertexBuffer, attrib.fCPUType, attrib.fGPUType, + vertexStride, bufferOffset + attrib.fOffset, kDivisor); + } + } + if (int instanceStride = fHWProgram->instanceStride()) { + SkASSERT(instanceBuffer); + SkASSERT(instanceBuffer->isCpuBuffer() || + !static_cast(instanceBuffer)->isMapped()); + size_t bufferOffset = baseInstance * static_cast(instanceStride); + int attribIdx = fHWProgram->numVertexAttributes(); + for (int i = 0; i < fHWProgram->numInstanceAttributes(); ++i, ++attribIdx) { + const auto& attrib = fHWProgram->instanceAttribute(i); + static constexpr int kDivisor = 1; + attribState->set(this, attrib.fLocation, instanceBuffer, attrib.fCPUType, + attrib.fGPUType, instanceStride, bufferOffset + attrib.fOffset, + kDivisor); + } + } +} + +GrGLenum GrGLGpu::bindBuffer(GrGpuBufferType type, const GrBuffer* buffer) { + this->handleDirtyContext(); + + // Index buffer state is tied to the vertex array. + if (GrGpuBufferType::kIndex == type) { + this->bindVertexArray(0); + } + + auto* bufferState = this->hwBufferState(type); + if (buffer->isCpuBuffer()) { + if (!bufferState->fBufferZeroKnownBound) { + GL_CALL(BindBuffer(bufferState->fGLTarget, 0)); + bufferState->fBufferZeroKnownBound = true; + bufferState->fBoundBufferUniqueID.makeInvalid(); + } + } else if (static_cast(buffer)->uniqueID() != + bufferState->fBoundBufferUniqueID) { + const GrGLBuffer* glBuffer = static_cast(buffer); + GL_CALL(BindBuffer(bufferState->fGLTarget, glBuffer->bufferID())); + bufferState->fBufferZeroKnownBound = false; + bufferState->fBoundBufferUniqueID = glBuffer->uniqueID(); + } + + return bufferState->fGLTarget; +} +void GrGLGpu::disableScissor() { + if (kNo_TriState != fHWScissorSettings.fEnabled) { + GL_CALL(Disable(GR_GL_SCISSOR_TEST)); + fHWScissorSettings.fEnabled = kNo_TriState; + return; + } +} + +void GrGLGpu::clear(const GrFixedClip& clip, const SkPMColor4f& color, + GrRenderTarget* target, GrSurfaceOrigin origin) { + // parent class should never let us get here with no RT + SkASSERT(target); + SkASSERT(!this->caps()->performColorClearsAsDraws()); + SkASSERT(!clip.scissorEnabled() || !this->caps()->performPartialClearsAsDraws()); + + this->handleDirtyContext(); + + GrGLRenderTarget* glRT = static_cast(target); + + if (clip.scissorEnabled()) { + this->flushRenderTarget(glRT, origin, clip.scissorRect()); + } else { + this->flushRenderTarget(glRT); + } + this->flushScissor(clip.scissorState(), glRT->width(), glRT->height(), origin); + this->flushWindowRectangles(clip.windowRectsState(), glRT, origin); + this->flushColorWrite(true); + this->flushClearColor(color); + GL_CALL(Clear(GR_GL_COLOR_BUFFER_BIT)); +} + +void GrGLGpu::clearStencil(GrRenderTarget* target, int clearValue) { + SkASSERT(!this->caps()->performStencilClearsAsDraws()); + + if (!target) { + return; + } + + // This should only be called internally when we know we have a stencil buffer. + SkASSERT(target->renderTargetPriv().getStencilAttachment()); + + GrGLRenderTarget* glRT = static_cast(target); + this->flushRenderTargetNoColorWrites(glRT); + + this->disableScissor(); + this->disableWindowRectangles(); + + GL_CALL(StencilMask(0xffffffff)); + GL_CALL(ClearStencil(clearValue)); + GL_CALL(Clear(GR_GL_STENCIL_BUFFER_BIT)); + fHWStencilSettings.invalidate(); +} + +static bool use_tiled_rendering(const GrGLCaps& glCaps, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore) { + // Only use the tiled rendering extension if we can explicitly clear and discard the stencil. + // Otherwise it's faster to just not use it. + return glCaps.tiledRenderingSupport() && GrLoadOp::kClear == stencilLoadStore.fLoadOp && + GrStoreOp::kDiscard == stencilLoadStore.fStoreOp; +} + +void GrGLGpu::beginCommandBuffer(GrRenderTarget* rt, const SkIRect& bounds, GrSurfaceOrigin origin, + const GrOpsRenderPass::LoadAndStoreInfo& colorLoadStore, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore) { + SkASSERT(!fIsExecutingCommandBuffer_DebugOnly); + + this->handleDirtyContext(); + + auto glRT = static_cast(rt); + this->flushRenderTarget(glRT); + SkDEBUGCODE(fIsExecutingCommandBuffer_DebugOnly = true); + + if (use_tiled_rendering(this->glCaps(), stencilLoadStore)) { + auto nativeBounds = GrNativeRect::MakeRelativeTo(origin, glRT->height(), bounds); + GrGLbitfield preserveMask = (GrLoadOp::kLoad == colorLoadStore.fLoadOp) + ? GR_GL_COLOR_BUFFER_BIT0 : GR_GL_NONE; + SkASSERT(GrLoadOp::kLoad != stencilLoadStore.fLoadOp); // Handled by use_tiled_rendering(). + GL_CALL(StartTiling(nativeBounds.fX, nativeBounds.fY, nativeBounds.fWidth, + nativeBounds.fHeight, preserveMask)); + } + + GrGLbitfield clearMask = 0; + if (GrLoadOp::kClear == colorLoadStore.fLoadOp) { + SkASSERT(!this->caps()->performColorClearsAsDraws()); + this->flushClearColor(colorLoadStore.fClearColor); + this->flushColorWrite(true); + clearMask |= GR_GL_COLOR_BUFFER_BIT; + } + if (GrLoadOp::kClear == stencilLoadStore.fLoadOp) { + SkASSERT(!this->caps()->performStencilClearsAsDraws()); + GL_CALL(StencilMask(0xffffffff)); + GL_CALL(ClearStencil(0)); + clearMask |= GR_GL_STENCIL_BUFFER_BIT; + } + if (clearMask) { + this->disableScissor(); + this->disableWindowRectangles(); + GL_CALL(Clear(clearMask)); + } +} + +void GrGLGpu::endCommandBuffer(GrRenderTarget* rt, + const GrOpsRenderPass::LoadAndStoreInfo& colorLoadStore, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore) { + SkASSERT(fIsExecutingCommandBuffer_DebugOnly); + + this->handleDirtyContext(); + + if (rt->uniqueID() != fHWBoundRenderTargetUniqueID) { + // The framebuffer binding changed in the middle of a command buffer. We should have already + // printed a warning during onFBOChanged. + return; + } + + if (GrGLCaps::kNone_InvalidateFBType != this->glCaps().invalidateFBType()) { + auto glRT = static_cast(rt); + + SkSTArray<2, GrGLenum> discardAttachments; + if (GrStoreOp::kDiscard == colorLoadStore.fStoreOp) { + discardAttachments.push_back( + (0 == glRT->renderFBOID()) ? GR_GL_COLOR : GR_GL_COLOR_ATTACHMENT0); + } + if (GrStoreOp::kDiscard == stencilLoadStore.fStoreOp) { + discardAttachments.push_back( + (0 == glRT->renderFBOID()) ? GR_GL_STENCIL : GR_GL_STENCIL_ATTACHMENT); + } + + if (!discardAttachments.empty()) { + if (GrGLCaps::kInvalidate_InvalidateFBType == this->glCaps().invalidateFBType()) { + GL_CALL(InvalidateFramebuffer(GR_GL_FRAMEBUFFER, discardAttachments.count(), + discardAttachments.begin())); + } else { + SkASSERT(GrGLCaps::kDiscard_InvalidateFBType == this->glCaps().invalidateFBType()); + GL_CALL(DiscardFramebuffer(GR_GL_FRAMEBUFFER, discardAttachments.count(), + discardAttachments.begin())); + } + } + } + + if (use_tiled_rendering(this->glCaps(), stencilLoadStore)) { + GrGLbitfield preserveMask = (GrStoreOp::kStore == colorLoadStore.fStoreOp) + ? GR_GL_COLOR_BUFFER_BIT0 : GR_GL_NONE; + // Handled by use_tiled_rendering(). + SkASSERT(GrStoreOp::kStore != stencilLoadStore.fStoreOp); + GL_CALL(EndTiling(preserveMask)); + } + + SkDEBUGCODE(fIsExecutingCommandBuffer_DebugOnly = false); +} + +void GrGLGpu::clearStencilClip(const GrFixedClip& clip, + bool insideStencilMask, + GrRenderTarget* target, GrSurfaceOrigin origin) { + SkASSERT(target); + SkASSERT(!this->caps()->performStencilClearsAsDraws()); + this->handleDirtyContext(); + + GrStencilAttachment* sb = target->renderTargetPriv().getStencilAttachment(); + // this should only be called internally when we know we have a + // stencil buffer. + SkASSERT(sb); + GrGLint stencilBitCount = sb->bits(); +#if 0 + SkASSERT(stencilBitCount > 0); + GrGLint clipStencilMask = (1 << (stencilBitCount - 1)); +#else + // we could just clear the clip bit but when we go through + // ANGLE a partial stencil mask will cause clears to be + // turned into draws. Our contract on GrOpsTask says that + // changing the clip between stencil passes may or may not + // zero the client's clip bits. So we just clear the whole thing. + static const GrGLint clipStencilMask = ~0; +#endif + GrGLint value; + if (insideStencilMask) { + value = (1 << (stencilBitCount - 1)); + } else { + value = 0; + } + GrGLRenderTarget* glRT = static_cast(target); + this->flushRenderTargetNoColorWrites(glRT); + + this->flushScissor(clip.scissorState(), glRT->width(), glRT->height(), origin); + this->flushWindowRectangles(clip.windowRectsState(), glRT, origin); + + GL_CALL(StencilMask((uint32_t) clipStencilMask)); + GL_CALL(ClearStencil(value)); + GL_CALL(Clear(GR_GL_STENCIL_BUFFER_BIT)); + fHWStencilSettings.invalidate(); +} + +bool GrGLGpu::readOrTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, + void* offsetOrPtr, int rowWidthInPixels) { + SkASSERT(surface); + + auto format = surface->backendFormat().asGLFormat(); + GrGLRenderTarget* renderTarget = static_cast(surface->asRenderTarget()); + if (!renderTarget && !this->glCaps().isFormatRenderable(format, 1)) { + return false; + } + GrGLenum externalFormat = 0; + GrGLenum externalType = 0; + this->glCaps().getReadPixelsFormat(surface->backendFormat().asGLFormat(), + surfaceColorType, + dstColorType, + &externalFormat, + &externalType); + if (!externalFormat || !externalType) { + return false; + } + + if (renderTarget) { + if (renderTarget->numSamples() <= 1 || + renderTarget->renderFBOID() == renderTarget->textureFBOID()) { // Also catches FBO 0. + SkASSERT(!renderTarget->requiresManualMSAAResolve()); + this->flushRenderTargetNoColorWrites(renderTarget); + } else if (GrGLRenderTarget::kUnresolvableFBOID == renderTarget->textureFBOID()) { + SkASSERT(!renderTarget->requiresManualMSAAResolve()); + return false; + } else { + SkASSERT(renderTarget->requiresManualMSAAResolve()); + // we don't track the state of the READ FBO ID. + this->bindFramebuffer(GR_GL_READ_FRAMEBUFFER, renderTarget->textureFBOID()); + } + } else { + // Use a temporary FBO. + this->bindSurfaceFBOForPixelOps(surface, 0, GR_GL_FRAMEBUFFER, kSrc_TempFBOTarget); + fHWBoundRenderTargetUniqueID.makeInvalid(); + } + + // the read rect is viewport-relative + GrNativeRect readRect = {left, top, width, height}; + + // determine if GL can read using the passed rowBytes or if we need a scratch buffer. + if (rowWidthInPixels != width) { + SkASSERT(this->glCaps().readPixelsRowBytesSupport()); + GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, rowWidthInPixels)); + } + GL_CALL(PixelStorei(GR_GL_PACK_ALIGNMENT, 1)); + + bool reattachStencil = false; + if (this->glCaps().detachStencilFromMSAABuffersBeforeReadPixels() && + renderTarget && + renderTarget->renderTargetPriv().getStencilAttachment() && + renderTarget->numSamples() > 1) { + // Fix Adreno devices that won't read from MSAA framebuffers with stencil attached + reattachStencil = true; + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + } + + GL_CALL(ReadPixels(readRect.fX, readRect.fY, readRect.fWidth, readRect.fHeight, + externalFormat, externalType, offsetOrPtr)); + + if (reattachStencil) { + GrGLStencilAttachment* stencilAttachment = static_cast( + renderTarget->renderTargetPriv().getStencilAttachment()); + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, stencilAttachment->renderbufferID())); + } + + if (rowWidthInPixels != width) { + SkASSERT(this->glCaps().readPixelsRowBytesSupport()); + GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, 0)); + } + + if (!renderTarget) { + this->unbindSurfaceFBOForPixelOps(surface, 0, GR_GL_FRAMEBUFFER); + } + return true; +} + +bool GrGLGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) { + SkASSERT(surface); + + size_t bytesPerPixel = GrColorTypeBytesPerPixel(dstColorType); + + // GL_PACK_ROW_LENGTH is in terms of pixels not bytes. + int rowPixelWidth; + + if (rowBytes == SkToSizeT(width * bytesPerPixel)) { + rowPixelWidth = width; + } else { + SkASSERT(!(rowBytes % bytesPerPixel)); + rowPixelWidth = rowBytes / bytesPerPixel; + } + return this->readOrTransferPixelsFrom(surface, left, top, width, height, surfaceColorType, + dstColorType, buffer, rowPixelWidth); +} + +GrOpsRenderPass* GrGLGpu::getOpsRenderPass( + GrRenderTarget* rt, GrSurfaceOrigin origin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, + const SkTArray& sampledProxies) { + if (!fCachedOpsRenderPass) { + fCachedOpsRenderPass.reset(new GrGLOpsRenderPass(this)); + } + + fCachedOpsRenderPass->set(rt, bounds, origin, colorInfo, stencilInfo); + return fCachedOpsRenderPass.get(); +} + +void GrGLGpu::flushRenderTarget(GrGLRenderTarget* target, GrSurfaceOrigin origin, + const SkIRect& bounds) { + this->flushRenderTargetNoColorWrites(target); + this->didWriteToSurface(target, origin, &bounds); +} + +void GrGLGpu::flushRenderTarget(GrGLRenderTarget* target) { + this->flushRenderTargetNoColorWrites(target); + this->didWriteToSurface(target, kTopLeft_GrSurfaceOrigin, nullptr); +} + +void GrGLGpu::flushRenderTargetNoColorWrites(GrGLRenderTarget* target) { + SkASSERT(target); + GrGpuResource::UniqueID rtID = target->uniqueID(); + if (fHWBoundRenderTargetUniqueID != rtID) { + this->bindFramebuffer(GR_GL_FRAMEBUFFER, target->renderFBOID()); +#ifdef SK_DEBUG + // don't do this check in Chromium -- this is causing + // lots of repeated command buffer flushes when the compositor is + // rendering with Ganesh, which is really slow; even too slow for + // Debug mode. + if (kChromium_GrGLDriver != this->glContext().driver()) { + GrGLenum status; + GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER)); + if (status != GR_GL_FRAMEBUFFER_COMPLETE) { + SkDebugf("GrGLGpu::flushRenderTarget glCheckFramebufferStatus %x\n", status); + } + } +#endif + fHWBoundRenderTargetUniqueID = rtID; + this->flushViewport(target->width(), target->height()); + } + + if (this->glCaps().srgbWriteControl()) { + this->flushFramebufferSRGB(this->caps()->isFormatSRGB(target->backendFormat())); + } +} + +void GrGLGpu::flushFramebufferSRGB(bool enable) { + if (enable && kYes_TriState != fHWSRGBFramebuffer) { + GL_CALL(Enable(GR_GL_FRAMEBUFFER_SRGB)); + fHWSRGBFramebuffer = kYes_TriState; + } else if (!enable && kNo_TriState != fHWSRGBFramebuffer) { + GL_CALL(Disable(GR_GL_FRAMEBUFFER_SRGB)); + fHWSRGBFramebuffer = kNo_TriState; + } +} + +void GrGLGpu::flushViewport(int width, int height) { + GrNativeRect viewport = {0, 0, width, height}; + if (fHWViewport != viewport) { + GL_CALL(Viewport(viewport.fX, viewport.fY, viewport.fWidth, viewport.fHeight)); + fHWViewport = viewport; + } +} + +#define SWAP_PER_DRAW 0 + +#if SWAP_PER_DRAW + #if defined(SK_BUILD_FOR_MAC) + #include + #elif defined(SK_BUILD_FOR_WIN) + #include + void SwapBuf() { + DWORD procID = GetCurrentProcessId(); + HWND hwnd = GetTopWindow(GetDesktopWindow()); + while(hwnd) { + DWORD wndProcID = 0; + GetWindowThreadProcessId(hwnd, &wndProcID); + if(wndProcID == procID) { + SwapBuffers(GetDC(hwnd)); + } + hwnd = GetNextWindow(hwnd, GW_HWNDNEXT); + } + } + #endif +#endif + +void GrGLGpu::draw(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + const GrMesh meshes[], + int meshCount) { + this->handleDirtyContext(); + + SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw + + GrPrimitiveType primitiveType = meshes[0].primitiveType(); + +#ifdef SK_DEBUG + // kPoints should never be intermingled in with the other primitive types + for (int i = 1; i < meshCount; ++i) { + if (primitiveType == GrPrimitiveType::kPoints) { + SkASSERT(meshes[i].primitiveType() == GrPrimitiveType::kPoints); + } else { + SkASSERT(meshes[i].primitiveType() != GrPrimitiveType::kPoints); + } + } +#endif + + // Passing 'primitiveType' here is a bit misleading. In GL's case it works out, since + // GL only cares if it is kPoints or not. + if (!this->flushGLState(renderTarget, programInfo, primitiveType)) { + return; + } + + bool hasDynamicScissors = programInfo.hasDynamicScissors(); + bool hasDynamicPrimProcTextures = programInfo.hasDynamicPrimProcTextures(); + + for (int m = 0; m < meshCount; ++m) { + if (auto barrierType = programInfo.pipeline().xferBarrierType(renderTarget->asTexture(), + *this->caps())) { + this->xferBarrier(renderTarget, barrierType); + } + + if (hasDynamicScissors) { + GrGLRenderTarget* glRT = static_cast(renderTarget); + this->flushScissor(GrScissorState(programInfo.dynamicScissor(m)), + glRT->width(), glRT->height(), programInfo.origin()); + } + if (hasDynamicPrimProcTextures) { + auto texProxyArray = programInfo.dynamicPrimProcTextures(m); + fHWProgram->updatePrimitiveProcessorTextureBindings(programInfo.primProc(), + texProxyArray); + } + if (this->glCaps().requiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines() && + GrIsPrimTypeLines(meshes[m].primitiveType()) && + !GrIsPrimTypeLines(fLastPrimitiveType)) { + GL_CALL(Enable(GR_GL_CULL_FACE)); + GL_CALL(Disable(GR_GL_CULL_FACE)); + } + meshes[m].sendToGpu(this); + fLastPrimitiveType = meshes[m].primitiveType(); + } + +#if SWAP_PER_DRAW + glFlush(); + #if defined(SK_BUILD_FOR_MAC) + aglSwapBuffers(aglGetCurrentContext()); + int set_a_break_pt_here = 9; + aglSwapBuffers(aglGetCurrentContext()); + #elif defined(SK_BUILD_FOR_WIN) + SwapBuf(); + int set_a_break_pt_here = 9; + SwapBuf(); + #endif +#endif +} + +static GrGLenum gr_primitive_type_to_gl_mode(GrPrimitiveType primitiveType) { + switch (primitiveType) { + case GrPrimitiveType::kTriangles: + return GR_GL_TRIANGLES; + case GrPrimitiveType::kTriangleStrip: + return GR_GL_TRIANGLE_STRIP; + case GrPrimitiveType::kPoints: + return GR_GL_POINTS; + case GrPrimitiveType::kLines: + return GR_GL_LINES; + case GrPrimitiveType::kLineStrip: + return GR_GL_LINE_STRIP; + case GrPrimitiveType::kPath: + SK_ABORT("non-mesh-based GrPrimitiveType"); + return 0; + } + SK_ABORT("invalid GrPrimitiveType"); +} + +void GrGLGpu::sendMeshToGpu(GrPrimitiveType primitiveType, const GrBuffer* vertexBuffer, + int vertexCount, int baseVertex) { + const GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType); + if (this->glCaps().drawArraysBaseVertexIsBroken()) { + this->setupGeometry(nullptr, vertexBuffer, baseVertex, nullptr, 0, GrPrimitiveRestart::kNo); + GL_CALL(DrawArrays(glPrimType, 0, vertexCount)); + } else { + this->setupGeometry(nullptr, vertexBuffer, 0, nullptr, 0, GrPrimitiveRestart::kNo); + GL_CALL(DrawArrays(glPrimType, baseVertex, vertexCount)); + } + fStats.incNumDraws(); +} + +static const GrGLvoid* element_ptr(const GrBuffer* indexBuffer, int baseIndex) { + size_t baseOffset = baseIndex * sizeof(uint16_t); + if (indexBuffer->isCpuBuffer()) { + return static_cast(indexBuffer)->data() + baseOffset; + } else { + return reinterpret_cast(baseOffset); + } +} + +void GrGLGpu::sendIndexedMeshToGpu(GrPrimitiveType primitiveType, const GrBuffer* indexBuffer, + int indexCount, int baseIndex, uint16_t minIndexValue, + uint16_t maxIndexValue, const GrBuffer* vertexBuffer, + int baseVertex, GrPrimitiveRestart enablePrimitiveRestart) { + const GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType); + const GrGLvoid* elementPtr = element_ptr(indexBuffer, baseIndex); + + this->setupGeometry(indexBuffer, vertexBuffer, baseVertex, nullptr, 0, enablePrimitiveRestart); + + if (this->glCaps().drawRangeElementsSupport()) { + GL_CALL(DrawRangeElements(glPrimType, minIndexValue, maxIndexValue, indexCount, + GR_GL_UNSIGNED_SHORT, elementPtr)); + } else { + GL_CALL(DrawElements(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, elementPtr)); + } + fStats.incNumDraws(); +} + +void GrGLGpu::sendInstancedMeshToGpu(GrPrimitiveType primitiveType, const GrBuffer* vertexBuffer, + int vertexCount, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance) { + GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType); + int maxInstances = this->glCaps().maxInstancesPerDrawWithoutCrashing(instanceCount); + for (int i = 0; i < instanceCount; i += maxInstances) { + this->setupGeometry(nullptr, vertexBuffer, 0, instanceBuffer, baseInstance + i, + GrPrimitiveRestart::kNo); + GL_CALL(DrawArraysInstanced(glPrimType, baseVertex, vertexCount, + SkTMin(instanceCount - i, maxInstances))); + fStats.incNumDraws(); + } +} + +void GrGLGpu::sendIndexedInstancedMeshToGpu(GrPrimitiveType primitiveType, + const GrBuffer* indexBuffer, int indexCount, + int baseIndex, const GrBuffer* vertexBuffer, + int baseVertex, const GrBuffer* instanceBuffer, + int instanceCount, int baseInstance, + GrPrimitiveRestart enablePrimitiveRestart) { + const GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType); + const GrGLvoid* elementPtr = element_ptr(indexBuffer, baseIndex); + int maxInstances = this->glCaps().maxInstancesPerDrawWithoutCrashing(instanceCount); + for (int i = 0; i < instanceCount; i += maxInstances) { + this->setupGeometry(indexBuffer, vertexBuffer, baseVertex, instanceBuffer, baseInstance + i, + enablePrimitiveRestart); + GL_CALL(DrawElementsInstanced(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, elementPtr, + SkTMin(instanceCount - i, maxInstances))); + fStats.incNumDraws(); + } +} + +void GrGLGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin resolveOrigin, ForExternalIO) { + // Some extensions automatically resolves the texture when it is read. + SkASSERT(this->glCaps().usesMSAARenderBuffers()); + + GrGLRenderTarget* rt = static_cast(target); + SkASSERT(rt->textureFBOID() != rt->renderFBOID()); + SkASSERT(rt->textureFBOID() != 0 && rt->renderFBOID() != 0); + this->bindFramebuffer(GR_GL_READ_FRAMEBUFFER, rt->renderFBOID()); + this->bindFramebuffer(GR_GL_DRAW_FRAMEBUFFER, rt->textureFBOID()); + + // make sure we go through flushRenderTarget() since we've modified + // the bound DRAW FBO ID. + fHWBoundRenderTargetUniqueID.makeInvalid(); + if (GrGLCaps::kES_Apple_MSFBOType == this->glCaps().msFBOType()) { + // Apple's extension uses the scissor as the blit bounds. + GrScissorState scissorState; + scissorState.set(resolveRect); + this->flushScissor(scissorState, rt->width(), rt->height(), resolveOrigin); + this->disableWindowRectangles(); + GL_CALL(ResolveMultisampleFramebuffer()); + } else { + int l, b, r, t; + if (GrGLCaps::kResolveMustBeFull_BlitFrambufferFlag & + this->glCaps().blitFramebufferSupportFlags()) { + l = 0; + b = 0; + r = target->width(); + t = target->height(); + } else { + auto rect = GrNativeRect::MakeRelativeTo( + resolveOrigin, rt->height(), resolveRect); + l = rect.fX; + b = rect.fY; + r = rect.fX + rect.fWidth; + t = rect.fY + rect.fHeight; + } + + // BlitFrameBuffer respects the scissor, so disable it. + this->disableScissor(); + this->disableWindowRectangles(); + GL_CALL(BlitFramebuffer(l, b, r, t, l, b, r, t, GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST)); + } +} + +namespace { + + +GrGLenum gr_to_gl_stencil_op(GrStencilOp op) { + static const GrGLenum gTable[kGrStencilOpCount] = { + GR_GL_KEEP, // kKeep + GR_GL_ZERO, // kZero + GR_GL_REPLACE, // kReplace + GR_GL_INVERT, // kInvert + GR_GL_INCR_WRAP, // kIncWrap + GR_GL_DECR_WRAP, // kDecWrap + GR_GL_INCR, // kIncClamp + GR_GL_DECR, // kDecClamp + }; + GR_STATIC_ASSERT(0 == (int)GrStencilOp::kKeep); + GR_STATIC_ASSERT(1 == (int)GrStencilOp::kZero); + GR_STATIC_ASSERT(2 == (int)GrStencilOp::kReplace); + GR_STATIC_ASSERT(3 == (int)GrStencilOp::kInvert); + GR_STATIC_ASSERT(4 == (int)GrStencilOp::kIncWrap); + GR_STATIC_ASSERT(5 == (int)GrStencilOp::kDecWrap); + GR_STATIC_ASSERT(6 == (int)GrStencilOp::kIncClamp); + GR_STATIC_ASSERT(7 == (int)GrStencilOp::kDecClamp); + SkASSERT(op < (GrStencilOp)kGrStencilOpCount); + return gTable[(int)op]; +} + +void set_gl_stencil(const GrGLInterface* gl, + const GrStencilSettings::Face& face, + GrGLenum glFace) { + GrGLenum glFunc = GrToGLStencilFunc(face.fTest); + GrGLenum glFailOp = gr_to_gl_stencil_op(face.fFailOp); + GrGLenum glPassOp = gr_to_gl_stencil_op(face.fPassOp); + + GrGLint ref = face.fRef; + GrGLint mask = face.fTestMask; + GrGLint writeMask = face.fWriteMask; + + if (GR_GL_FRONT_AND_BACK == glFace) { + // we call the combined func just in case separate stencil is not + // supported. + GR_GL_CALL(gl, StencilFunc(glFunc, ref, mask)); + GR_GL_CALL(gl, StencilMask(writeMask)); + GR_GL_CALL(gl, StencilOp(glFailOp, GR_GL_KEEP, glPassOp)); + } else { + GR_GL_CALL(gl, StencilFuncSeparate(glFace, glFunc, ref, mask)); + GR_GL_CALL(gl, StencilMaskSeparate(glFace, writeMask)); + GR_GL_CALL(gl, StencilOpSeparate(glFace, glFailOp, GR_GL_KEEP, glPassOp)); + } +} +} + +void GrGLGpu::flushStencil(const GrStencilSettings& stencilSettings, GrSurfaceOrigin origin) { + if (stencilSettings.isDisabled()) { + this->disableStencil(); + } else if (fHWStencilSettings != stencilSettings || + (stencilSettings.isTwoSided() && fHWStencilOrigin != origin)) { + if (kYes_TriState != fHWStencilTestEnabled) { + GL_CALL(Enable(GR_GL_STENCIL_TEST)); + + fHWStencilTestEnabled = kYes_TriState; + } + if (stencilSettings.isTwoSided()) { + set_gl_stencil(this->glInterface(), stencilSettings.front(origin), GR_GL_FRONT); + set_gl_stencil(this->glInterface(), stencilSettings.back(origin), GR_GL_BACK); + } else { + set_gl_stencil( + this->glInterface(), stencilSettings.frontAndBack(), GR_GL_FRONT_AND_BACK); + } + fHWStencilSettings = stencilSettings; + fHWStencilOrigin = origin; + } +} + +void GrGLGpu::disableStencil() { + if (kNo_TriState != fHWStencilTestEnabled) { + GL_CALL(Disable(GR_GL_STENCIL_TEST)); + + fHWStencilTestEnabled = kNo_TriState; + fHWStencilSettings.invalidate(); + } +} + +void GrGLGpu::flushHWAAState(GrRenderTarget* rt, bool useHWAA) { + // rt is only optional if useHWAA is false. + SkASSERT(rt || !useHWAA); +#ifdef SK_DEBUG + if (useHWAA && rt->numSamples() <= 1) { + SkASSERT(this->caps()->mixedSamplesSupport()); + SkASSERT(0 != static_cast(rt)->renderFBOID()); + SkASSERT(rt->renderTargetPriv().getStencilAttachment()); + } +#endif + + if (this->caps()->multisampleDisableSupport()) { + if (useHWAA) { + if (kYes_TriState != fMSAAEnabled) { + GL_CALL(Enable(GR_GL_MULTISAMPLE)); + fMSAAEnabled = kYes_TriState; + } + } else { + if (kNo_TriState != fMSAAEnabled) { + GL_CALL(Disable(GR_GL_MULTISAMPLE)); + fMSAAEnabled = kNo_TriState; + } + } + } +} + +void GrGLGpu::flushBlendAndColorWrite( + const GrXferProcessor::BlendInfo& blendInfo, const GrSwizzle& swizzle) { + if (this->glCaps().neverDisableColorWrites() && !blendInfo.fWriteColor) { + // We need to work around a driver bug by using a blend state that preserves the dst color, + // rather than disabling color writes. + GrXferProcessor::BlendInfo preserveDstBlend; + preserveDstBlend.fSrcBlend = kZero_GrBlendCoeff; + preserveDstBlend.fDstBlend = kOne_GrBlendCoeff; + this->flushBlendAndColorWrite(preserveDstBlend, swizzle); + return; + } + + GrBlendEquation equation = blendInfo.fEquation; + GrBlendCoeff srcCoeff = blendInfo.fSrcBlend; + GrBlendCoeff dstCoeff = blendInfo.fDstBlend; + + // Any optimization to disable blending should have already been applied and + // tweaked the equation to "add" or "subtract", and the coeffs to (1, 0). + bool blendOff = + ((kAdd_GrBlendEquation == equation || kSubtract_GrBlendEquation == equation) && + kOne_GrBlendCoeff == srcCoeff && kZero_GrBlendCoeff == dstCoeff) || + !blendInfo.fWriteColor; + + if (blendOff) { + if (kNo_TriState != fHWBlendState.fEnabled) { + GL_CALL(Disable(GR_GL_BLEND)); + + // Workaround for the ARM KHR_blend_equation_advanced blacklist issue + // https://code.google.com/p/skia/issues/detail?id=3943 + if (kARM_GrGLVendor == this->ctxInfo().vendor() && + GrBlendEquationIsAdvanced(fHWBlendState.fEquation)) { + SkASSERT(this->caps()->advancedBlendEquationSupport()); + // Set to any basic blending equation. + GrBlendEquation blend_equation = kAdd_GrBlendEquation; + GL_CALL(BlendEquation(gXfermodeEquation2Blend[blend_equation])); + fHWBlendState.fEquation = blend_equation; + } + + fHWBlendState.fEnabled = kNo_TriState; + } + } else { + if (kYes_TriState != fHWBlendState.fEnabled) { + GL_CALL(Enable(GR_GL_BLEND)); + + fHWBlendState.fEnabled = kYes_TriState; + } + + if (fHWBlendState.fEquation != equation) { + GL_CALL(BlendEquation(gXfermodeEquation2Blend[equation])); + fHWBlendState.fEquation = equation; + } + + if (GrBlendEquationIsAdvanced(equation)) { + SkASSERT(this->caps()->advancedBlendEquationSupport()); + // Advanced equations have no other blend state. + return; + } + + if (fHWBlendState.fSrcCoeff != srcCoeff || fHWBlendState.fDstCoeff != dstCoeff) { + GL_CALL(BlendFunc(gXfermodeCoeff2Blend[srcCoeff], + gXfermodeCoeff2Blend[dstCoeff])); + fHWBlendState.fSrcCoeff = srcCoeff; + fHWBlendState.fDstCoeff = dstCoeff; + } + + if ((BlendCoeffReferencesConstant(srcCoeff) || BlendCoeffReferencesConstant(dstCoeff))) { + SkPMColor4f blendConst = swizzle.applyTo(blendInfo.fBlendConstant); + if (!fHWBlendState.fConstColorValid || fHWBlendState.fConstColor != blendConst) { + GL_CALL(BlendColor(blendConst.fR, blendConst.fG, blendConst.fB, blendConst.fA)); + fHWBlendState.fConstColor = blendConst; + fHWBlendState.fConstColorValid = true; + } + } + } + + this->flushColorWrite(blendInfo.fWriteColor); +} + +static void get_gl_swizzle_values(const GrSwizzle& swizzle, GrGLenum glValues[4]) { + for (int i = 0; i < 4; ++i) { + switch (swizzle[i]) { + case 'r': glValues[i] = GR_GL_RED; break; + case 'g': glValues[i] = GR_GL_GREEN; break; + case 'b': glValues[i] = GR_GL_BLUE; break; + case 'a': glValues[i] = GR_GL_ALPHA; break; + case '0': glValues[i] = GR_GL_ZERO; break; + case '1': glValues[i] = GR_GL_ONE; break; + default: SK_ABORT("Unsupported component"); + } + } +} + +void GrGLGpu::bindTexture(int unitIdx, GrSamplerState samplerState, const GrSwizzle& swizzle, + GrGLTexture* texture) { + SkASSERT(texture); + +#ifdef SK_DEBUG + if (!this->caps()->npotTextureTileSupport()) { + if (samplerState.isRepeated()) { + const int w = texture->width(); + const int h = texture->height(); + SkASSERT(SkIsPow2(w) && SkIsPow2(h)); + } + } +#endif + + GrGpuResource::UniqueID textureID = texture->uniqueID(); + GrGLenum target = texture->target(); + if (fHWTextureUnitBindings[unitIdx].boundID(target) != textureID) { + this->setTextureUnit(unitIdx); + GL_CALL(BindTexture(target, texture->textureID())); + fHWTextureUnitBindings[unitIdx].setBoundID(target, textureID); + } + + if (samplerState.filter() == GrSamplerState::Filter::kMipMap) { + if (!this->caps()->mipMapSupport() || + texture->texturePriv().mipMapped() == GrMipMapped::kNo) { + samplerState.setFilterMode(GrSamplerState::Filter::kBilerp); + } + } + +#ifdef SK_DEBUG + // We were supposed to ensure MipMaps were up-to-date before getting here. + if (samplerState.filter() == GrSamplerState::Filter::kMipMap) { + SkASSERT(!texture->texturePriv().mipMapsAreDirty()); + } +#endif + + auto timestamp = texture->parameters()->resetTimestamp(); + bool setAll = timestamp < fResetTimestampForTextureParameters; + + const GrGLTextureParameters::SamplerOverriddenState* samplerStateToRecord = nullptr; + GrGLTextureParameters::SamplerOverriddenState newSamplerState; + if (fSamplerObjectCache) { + fSamplerObjectCache->bindSampler(unitIdx, samplerState); + } else { + const GrGLTextureParameters::SamplerOverriddenState& oldSamplerState = + texture->parameters()->samplerOverriddenState(); + samplerStateToRecord = &newSamplerState; + + newSamplerState.fMinFilter = filter_to_gl_min_filter(samplerState.filter()); + newSamplerState.fMagFilter = filter_to_gl_mag_filter(samplerState.filter()); + + newSamplerState.fWrapS = wrap_mode_to_gl_wrap(samplerState.wrapModeX(), this->glCaps()); + newSamplerState.fWrapT = wrap_mode_to_gl_wrap(samplerState.wrapModeY(), this->glCaps()); + + // These are the OpenGL default values. + newSamplerState.fMinLOD = -1000.f; + newSamplerState.fMaxLOD = 1000.f; + + if (setAll || newSamplerState.fMagFilter != oldSamplerState.fMagFilter) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_MAG_FILTER, newSamplerState.fMagFilter)); + } + if (setAll || newSamplerState.fMinFilter != oldSamplerState.fMinFilter) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_MIN_FILTER, newSamplerState.fMinFilter)); + } + if (this->glCaps().mipMapLevelAndLodControlSupport()) { + if (setAll || newSamplerState.fMinLOD != oldSamplerState.fMinLOD) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameterf(target, GR_GL_TEXTURE_MIN_LOD, newSamplerState.fMinLOD)); + } + if (setAll || newSamplerState.fMaxLOD != oldSamplerState.fMaxLOD) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameterf(target, GR_GL_TEXTURE_MAX_LOD, newSamplerState.fMaxLOD)); + } + } + if (setAll || newSamplerState.fWrapS != oldSamplerState.fWrapS) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_WRAP_S, newSamplerState.fWrapS)); + } + if (setAll || newSamplerState.fWrapT != oldSamplerState.fWrapT) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_WRAP_T, newSamplerState.fWrapT)); + } + if (this->glCaps().clampToBorderSupport()) { + // Make sure the border color is transparent black (the default) + if (setAll || oldSamplerState.fBorderColorInvalid) { + this->setTextureUnit(unitIdx); + static const GrGLfloat kTransparentBlack[4] = {0.f, 0.f, 0.f, 0.f}; + GL_CALL(TexParameterfv(target, GR_GL_TEXTURE_BORDER_COLOR, kTransparentBlack)); + } + } + } + GrGLTextureParameters::NonsamplerState newNonsamplerState; + newNonsamplerState.fBaseMipMapLevel = 0; + newNonsamplerState.fMaxMipMapLevel = texture->texturePriv().maxMipMapLevel(); + + const GrGLTextureParameters::NonsamplerState& oldNonsamplerState = + texture->parameters()->nonsamplerState(); + if (!this->caps()->shaderCaps()->textureSwizzleAppliedInShader()) { + newNonsamplerState.fSwizzleKey = swizzle.asKey(); + if (setAll || swizzle.asKey() != oldNonsamplerState.fSwizzleKey) { + GrGLenum glValues[4]; + get_gl_swizzle_values(swizzle, glValues); + this->setTextureUnit(unitIdx); + if (GR_IS_GR_GL(this->glStandard())) { + GR_STATIC_ASSERT(sizeof(glValues[0]) == sizeof(GrGLint)); + GL_CALL(TexParameteriv(target, GR_GL_TEXTURE_SWIZZLE_RGBA, + reinterpret_cast(glValues))); + } else if (GR_IS_GR_GL_ES(this->glStandard())) { + // ES3 added swizzle support but not GL_TEXTURE_SWIZZLE_RGBA. + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_R, glValues[0])); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_G, glValues[1])); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_B, glValues[2])); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_A, glValues[3])); + } + } + } + // These are not supported in ES2 contexts + if (this->glCaps().mipMapLevelAndLodControlSupport() && + (texture->texturePriv().textureType() != GrTextureType::kExternal || + !this->glCaps().dontSetBaseOrMaxLevelForExternalTextures())) { + if (newNonsamplerState.fBaseMipMapLevel != oldNonsamplerState.fBaseMipMapLevel) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_BASE_LEVEL, + newNonsamplerState.fBaseMipMapLevel)); + } + if (newNonsamplerState.fMaxMipMapLevel != oldNonsamplerState.fMaxMipMapLevel) { + this->setTextureUnit(unitIdx); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_MAX_LEVEL, + newNonsamplerState.fMaxMipMapLevel)); + } + } + texture->parameters()->set(samplerStateToRecord, newNonsamplerState, + fResetTimestampForTextureParameters); +} + +void GrGLGpu::onResetTextureBindings() { + static constexpr GrGLenum kTargets[] = {GR_GL_TEXTURE_2D, GR_GL_TEXTURE_RECTANGLE, + GR_GL_TEXTURE_EXTERNAL}; + for (int i = 0; i < this->numTextureUnits(); ++i) { + this->setTextureUnit(i); + for (auto target : kTargets) { + if (fHWTextureUnitBindings[i].hasBeenModified(target)) { + GL_CALL(BindTexture(target, 0)); + } + } + fHWTextureUnitBindings[i].invalidateAllTargets(true); + } +} + +void GrGLGpu::flushColorWrite(bool writeColor) { + if (!writeColor) { + if (kNo_TriState != fHWWriteToColor) { + GL_CALL(ColorMask(GR_GL_FALSE, GR_GL_FALSE, + GR_GL_FALSE, GR_GL_FALSE)); + fHWWriteToColor = kNo_TriState; + } + } else { + if (kYes_TriState != fHWWriteToColor) { + GL_CALL(ColorMask(GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE)); + fHWWriteToColor = kYes_TriState; + } + } +} + +void GrGLGpu::flushClearColor(const SkPMColor4f& color) { + GrGLfloat r = color.fR, g = color.fG, b = color.fB, a = color.fA; + if (this->glCaps().clearToBoundaryValuesIsBroken() && + (1 == r || 0 == r) && (1 == g || 0 == g) && (1 == b || 0 == b) && (1 == a || 0 == a)) { + static const GrGLfloat safeAlpha1 = nextafter(1.f, 2.f); + static const GrGLfloat safeAlpha0 = nextafter(0.f, -1.f); + a = (1 == a) ? safeAlpha1 : safeAlpha0; + } + if (r != fHWClearColor[0] || g != fHWClearColor[1] || + b != fHWClearColor[2] || a != fHWClearColor[3]) { + GL_CALL(ClearColor(r, g, b, a)); + fHWClearColor[0] = r; + fHWClearColor[1] = g; + fHWClearColor[2] = b; + fHWClearColor[3] = a; + } +} + +void GrGLGpu::setTextureUnit(int unit) { + SkASSERT(unit >= 0 && unit < this->numTextureUnits()); + if (unit != fHWActiveTextureUnitIdx) { + GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + unit)); + fHWActiveTextureUnitIdx = unit; + } +} + +void GrGLGpu::bindTextureToScratchUnit(GrGLenum target, GrGLint textureID) { + // Bind the last texture unit since it is the least likely to be used by GrGLProgram. + int lastUnitIdx = this->numTextureUnits() - 1; + if (lastUnitIdx != fHWActiveTextureUnitIdx) { + GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + lastUnitIdx)); + fHWActiveTextureUnitIdx = lastUnitIdx; + } + // Clear out the this field so that if a GrGLProgram does use this unit it will rebind the + // correct texture. + fHWTextureUnitBindings[lastUnitIdx].invalidateForScratchUse(target); + GL_CALL(BindTexture(target, textureID)); +} + +// Determines whether glBlitFramebuffer could be used between src and dst by onCopySurface. +static inline bool can_blit_framebuffer_for_copy_surface(const GrSurface* dst, + const GrSurface* src, + const SkIRect& srcRect, + const SkIPoint& dstPoint, + const GrGLCaps& caps) { + int dstSampleCnt = 0; + int srcSampleCnt = 0; + if (const GrRenderTarget* rt = dst->asRenderTarget()) { + dstSampleCnt = rt->numSamples(); + } + if (const GrRenderTarget* rt = src->asRenderTarget()) { + srcSampleCnt = rt->numSamples(); + } + SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTarget())); + SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTarget())); + + GrGLFormat dstFormat = dst->backendFormat().asGLFormat(); + GrGLFormat srcFormat = src->backendFormat().asGLFormat(); + + const GrGLTexture* dstTex = static_cast(dst->asTexture()); + const GrGLTexture* srcTex = static_cast(src->asTexture()); + + GrTextureType dstTexType; + GrTextureType* dstTexTypePtr = nullptr; + GrTextureType srcTexType; + GrTextureType* srcTexTypePtr = nullptr; + if (dstTex) { + dstTexType = dstTex->texturePriv().textureType(); + dstTexTypePtr = &dstTexType; + } + if (srcTex) { + srcTexType = srcTex->texturePriv().textureType(); + srcTexTypePtr = &srcTexType; + } + + return caps.canCopyAsBlit(dstFormat, dstSampleCnt, dstTexTypePtr, + srcFormat, srcSampleCnt, srcTexTypePtr, + src->getBoundsRect(), true, srcRect, dstPoint); +} + +static bool rt_has_msaa_render_buffer(const GrGLRenderTarget* rt, const GrGLCaps& glCaps) { + // A RT has a separate MSAA renderbuffer if: + // 1) It's multisampled + // 2) We're using an extension with separate MSAA renderbuffers + // 3) It's not FBO 0, which is special and always auto-resolves + return rt->numSamples() > 1 && glCaps.usesMSAARenderBuffers() && rt->renderFBOID() != 0; +} + +static inline bool can_copy_texsubimage(const GrSurface* dst, const GrSurface* src, + const GrGLCaps& caps) { + + const GrGLRenderTarget* dstRT = static_cast(dst->asRenderTarget()); + const GrGLRenderTarget* srcRT = static_cast(src->asRenderTarget()); + const GrGLTexture* dstTex = static_cast(dst->asTexture()); + const GrGLTexture* srcTex = static_cast(src->asTexture()); + + bool dstHasMSAARenderBuffer = dstRT ? rt_has_msaa_render_buffer(dstRT, caps) : false; + bool srcHasMSAARenderBuffer = srcRT ? rt_has_msaa_render_buffer(srcRT, caps) : false; + + GrGLFormat dstFormat = dst->backendFormat().asGLFormat(); + GrGLFormat srcFormat = src->backendFormat().asGLFormat(); + + GrTextureType dstTexType; + GrTextureType* dstTexTypePtr = nullptr; + GrTextureType srcTexType; + GrTextureType* srcTexTypePtr = nullptr; + if (dstTex) { + dstTexType = dstTex->texturePriv().textureType(); + dstTexTypePtr = &dstTexType; + } + if (srcTex) { + srcTexType = srcTex->texturePriv().textureType(); + srcTexTypePtr = &srcTexType; + } + + return caps.canCopyTexSubImage(dstFormat, dstHasMSAARenderBuffer, dstTexTypePtr, + srcFormat, srcHasMSAARenderBuffer, srcTexTypePtr); +} + +// If a temporary FBO was created, its non-zero ID is returned. +void GrGLGpu::bindSurfaceFBOForPixelOps(GrSurface* surface, int mipLevel, GrGLenum fboTarget, + TempFBOTarget tempFBOTarget) { + GrGLRenderTarget* rt = static_cast(surface->asRenderTarget()); + if (!rt || mipLevel > 0) { + SkASSERT(surface->asTexture()); + GrGLTexture* texture = static_cast(surface->asTexture()); + GrGLuint texID = texture->textureID(); + GrGLenum target = texture->target(); + GrGLuint* tempFBOID; + tempFBOID = kSrc_TempFBOTarget == tempFBOTarget ? &fTempSrcFBOID : &fTempDstFBOID; + + if (0 == *tempFBOID) { + GR_GL_CALL(this->glInterface(), GenFramebuffers(1, tempFBOID)); + } + + this->bindFramebuffer(fboTarget, *tempFBOID); + GR_GL_CALL( + this->glInterface(), + FramebufferTexture2D(fboTarget, GR_GL_COLOR_ATTACHMENT0, target, texID, mipLevel)); + if (mipLevel == 0) { + texture->baseLevelWasBoundToFBO(); + } + } else { + this->bindFramebuffer(fboTarget, rt->renderFBOID()); + } +} + +void GrGLGpu::unbindSurfaceFBOForPixelOps(GrSurface* surface, int mipLevel, GrGLenum fboTarget) { + // bindSurfaceFBOForPixelOps temporarily binds textures that are not render targets to + if (mipLevel > 0 || !surface->asRenderTarget()) { + SkASSERT(surface->asTexture()); + GrGLenum textureTarget = static_cast(surface->asTexture())->target(); + GR_GL_CALL(this->glInterface(), FramebufferTexture2D(fboTarget, + GR_GL_COLOR_ATTACHMENT0, + textureTarget, + 0, + 0)); + } +} + +void GrGLGpu::onFBOChanged() { + if (this->caps()->workarounds().flush_on_framebuffer_change || + this->caps()->workarounds().restore_scissor_on_fbo_change) { + GL_CALL(Flush()); + } +#ifdef SK_DEBUG + if (fIsExecutingCommandBuffer_DebugOnly) { + SkDebugf("WARNING: GL FBO binding changed while executing a command buffer. " + "This will severely hurt performance.\n"); + } +#endif +} + +void GrGLGpu::bindFramebuffer(GrGLenum target, GrGLuint fboid) { + fStats.incRenderTargetBinds(); + GL_CALL(BindFramebuffer(target, fboid)); + if (target == GR_GL_FRAMEBUFFER || target == GR_GL_DRAW_FRAMEBUFFER) { + fBoundDrawFramebuffer = fboid; + } + + if (this->caps()->workarounds().restore_scissor_on_fbo_change) { + // The driver forgets the correct scissor when modifying the FBO binding. + if (!fHWScissorSettings.fRect.isInvalid()) { + const GrNativeRect& r = fHWScissorSettings.fRect; + GL_CALL(Scissor(r.fX, r.fY, r.fWidth, r.fHeight)); + } + } + + this->onFBOChanged(); +} + +void GrGLGpu::deleteFramebuffer(GrGLuint fboid) { + if (fboid == fBoundDrawFramebuffer && + this->caps()->workarounds().unbind_attachments_on_bound_render_fbo_delete) { + // This workaround only applies to deleting currently bound framebuffers + // on Adreno 420. Because this is a somewhat rare case, instead of + // tracking all the attachments of every framebuffer instead just always + // unbind all attachments. + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, + GR_GL_RENDERBUFFER, 0)); + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + } + + GL_CALL(DeleteFramebuffers(1, &fboid)); + + // Deleting the currently bound framebuffer rebinds to 0. + if (fboid == fBoundDrawFramebuffer) { + this->onFBOChanged(); + } +} + +bool GrGLGpu::onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + // Don't prefer copying as a draw if the dst doesn't already have a FBO object. + // This implicitly handles this->glCaps().useDrawInsteadOfAllRenderTargetWrites(). + bool preferCopy = SkToBool(dst->asRenderTarget()); + auto dstFormat = dst->backendFormat().asGLFormat(); + if (preferCopy && this->glCaps().canCopyAsDraw(dstFormat, SkToBool(src->asTexture()))) { + if (this->copySurfaceAsDraw(dst, src, srcRect, dstPoint)) { + return true; + } + } + + if (can_copy_texsubimage(dst, src, this->glCaps())) { + this->copySurfaceAsCopyTexSubImage(dst, src, srcRect, dstPoint); + return true; + } + + if (can_blit_framebuffer_for_copy_surface(dst, src, srcRect, dstPoint, this->glCaps())) { + return this->copySurfaceAsBlitFramebuffer(dst, src, srcRect, dstPoint); + } + + if (!preferCopy && this->glCaps().canCopyAsDraw(dstFormat, SkToBool(src->asTexture()))) { + if (this->copySurfaceAsDraw(dst, src, srcRect, dstPoint)) { + return true; + } + } + + return false; +} + +bool GrGLGpu::createCopyProgram(GrTexture* srcTex) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + int progIdx = TextureToCopyProgramIdx(srcTex); + const GrShaderCaps* shaderCaps = this->caps()->shaderCaps(); + GrSLType samplerType = + GrSLCombinedSamplerTypeForTextureType(srcTex->texturePriv().textureType()); + + if (!fCopyProgramArrayBuffer) { + static const GrGLfloat vdata[] = { + 0, 0, + 0, 1, + 1, 0, + 1, 1 + }; + fCopyProgramArrayBuffer = GrGLBuffer::Make(this, sizeof(vdata), GrGpuBufferType::kVertex, + kStatic_GrAccessPattern, vdata); + } + if (!fCopyProgramArrayBuffer) { + return false; + } + + SkASSERT(!fCopyPrograms[progIdx].fProgram); + GL_CALL_RET(fCopyPrograms[progIdx].fProgram, CreateProgram()); + if (!fCopyPrograms[progIdx].fProgram) { + return false; + } + + GrShaderVar aVertex("a_vertex", kHalf2_GrSLType, GrShaderVar::kIn_TypeModifier); + GrShaderVar uTexCoordXform("u_texCoordXform", kHalf4_GrSLType, + GrShaderVar::kUniform_TypeModifier); + GrShaderVar uPosXform("u_posXform", kHalf4_GrSLType, GrShaderVar::kUniform_TypeModifier); + GrShaderVar uTexture("u_texture", samplerType, GrShaderVar::kUniform_TypeModifier); + GrShaderVar vTexCoord("v_texCoord", kHalf2_GrSLType, GrShaderVar::kOut_TypeModifier); + GrShaderVar oFragColor("o_FragColor", kHalf4_GrSLType, GrShaderVar::kOut_TypeModifier); + + SkString vshaderTxt; + if (shaderCaps->noperspectiveInterpolationSupport()) { + if (const char* extension = shaderCaps->noperspectiveInterpolationExtensionString()) { + vshaderTxt.appendf("#extension %s : require\n", extension); + } + vTexCoord.addModifier("noperspective"); + } + + aVertex.appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + uTexCoordXform.appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + uPosXform.appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + vTexCoord.appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + + vshaderTxt.append( + "// Copy Program VS\n" + "void main() {" + " v_texCoord = half2(a_vertex.xy * u_texCoordXform.xy + u_texCoordXform.zw);" + " sk_Position.xy = a_vertex * u_posXform.xy + u_posXform.zw;" + " sk_Position.zw = half2(0, 1);" + "}" + ); + + SkString fshaderTxt; + if (shaderCaps->noperspectiveInterpolationSupport()) { + if (const char* extension = shaderCaps->noperspectiveInterpolationExtensionString()) { + fshaderTxt.appendf("#extension %s : require\n", extension); + } + } + vTexCoord.setTypeModifier(GrShaderVar::kIn_TypeModifier); + vTexCoord.appendDecl(shaderCaps, &fshaderTxt); + fshaderTxt.append(";"); + uTexture.appendDecl(shaderCaps, &fshaderTxt); + fshaderTxt.append(";"); + fshaderTxt.appendf( + "// Copy Program FS\n" + "void main() {" + " sk_FragColor = sample(u_texture, v_texCoord);" + "}" + ); + + auto errorHandler = this->getContext()->priv().getShaderErrorHandler(); + SkSL::String sksl(vshaderTxt.c_str(), vshaderTxt.size()); + SkSL::Program::Settings settings; + settings.fCaps = shaderCaps; + SkSL::String glsl; + std::unique_ptr program = GrSkSLtoGLSL(*fGLContext, SkSL::Program::kVertex_Kind, + sksl, settings, &glsl, errorHandler); + GrGLuint vshader = GrGLCompileAndAttachShader(*fGLContext, fCopyPrograms[progIdx].fProgram, + GR_GL_VERTEX_SHADER, glsl, &fStats, errorHandler); + SkASSERT(program->fInputs.isEmpty()); + + sksl.assign(fshaderTxt.c_str(), fshaderTxt.size()); + program = GrSkSLtoGLSL(*fGLContext, SkSL::Program::kFragment_Kind, sksl, settings, &glsl, + errorHandler); + GrGLuint fshader = GrGLCompileAndAttachShader(*fGLContext, fCopyPrograms[progIdx].fProgram, + GR_GL_FRAGMENT_SHADER, glsl, &fStats, + errorHandler); + SkASSERT(program->fInputs.isEmpty()); + + GL_CALL(LinkProgram(fCopyPrograms[progIdx].fProgram)); + + GL_CALL_RET(fCopyPrograms[progIdx].fTextureUniform, + GetUniformLocation(fCopyPrograms[progIdx].fProgram, "u_texture")); + GL_CALL_RET(fCopyPrograms[progIdx].fPosXformUniform, + GetUniformLocation(fCopyPrograms[progIdx].fProgram, "u_posXform")); + GL_CALL_RET(fCopyPrograms[progIdx].fTexCoordXformUniform, + GetUniformLocation(fCopyPrograms[progIdx].fProgram, "u_texCoordXform")); + + GL_CALL(BindAttribLocation(fCopyPrograms[progIdx].fProgram, 0, "a_vertex")); + + GL_CALL(DeleteShader(vshader)); + GL_CALL(DeleteShader(fshader)); + + return true; +} + +bool GrGLGpu::createMipmapProgram(int progIdx) { + const bool oddWidth = SkToBool(progIdx & 0x2); + const bool oddHeight = SkToBool(progIdx & 0x1); + const int numTaps = (oddWidth ? 2 : 1) * (oddHeight ? 2 : 1); + + const GrShaderCaps* shaderCaps = this->caps()->shaderCaps(); + + SkASSERT(!fMipmapPrograms[progIdx].fProgram); + GL_CALL_RET(fMipmapPrograms[progIdx].fProgram, CreateProgram()); + if (!fMipmapPrograms[progIdx].fProgram) { + return false; + } + + GrShaderVar aVertex("a_vertex", kHalf2_GrSLType, GrShaderVar::kIn_TypeModifier); + GrShaderVar uTexCoordXform("u_texCoordXform", kHalf4_GrSLType, + GrShaderVar::kUniform_TypeModifier); + GrShaderVar uTexture("u_texture", kTexture2DSampler_GrSLType, + GrShaderVar::kUniform_TypeModifier); + // We need 1, 2, or 4 texture coordinates (depending on parity of each dimension): + GrShaderVar vTexCoords[] = { + GrShaderVar("v_texCoord0", kHalf2_GrSLType, GrShaderVar::kOut_TypeModifier), + GrShaderVar("v_texCoord1", kHalf2_GrSLType, GrShaderVar::kOut_TypeModifier), + GrShaderVar("v_texCoord2", kHalf2_GrSLType, GrShaderVar::kOut_TypeModifier), + GrShaderVar("v_texCoord3", kHalf2_GrSLType, GrShaderVar::kOut_TypeModifier), + }; + GrShaderVar oFragColor("o_FragColor", kHalf4_GrSLType,GrShaderVar::kOut_TypeModifier); + + SkString vshaderTxt; + if (shaderCaps->noperspectiveInterpolationSupport()) { + if (const char* extension = shaderCaps->noperspectiveInterpolationExtensionString()) { + vshaderTxt.appendf("#extension %s : require\n", extension); + } + vTexCoords[0].addModifier("noperspective"); + vTexCoords[1].addModifier("noperspective"); + vTexCoords[2].addModifier("noperspective"); + vTexCoords[3].addModifier("noperspective"); + } + + aVertex.appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + uTexCoordXform.appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + for (int i = 0; i < numTaps; ++i) { + vTexCoords[i].appendDecl(shaderCaps, &vshaderTxt); + vshaderTxt.append(";"); + } + + vshaderTxt.append( + "// Mipmap Program VS\n" + "void main() {" + " sk_Position.xy = a_vertex * half2(2, 2) - half2(1, 1);" + " sk_Position.zw = half2(0, 1);" + ); + + // Insert texture coordinate computation: + if (oddWidth && oddHeight) { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy * u_texCoordXform.yw;" + " v_texCoord1 = a_vertex.xy * u_texCoordXform.yw + half2(u_texCoordXform.x, 0);" + " v_texCoord2 = a_vertex.xy * u_texCoordXform.yw + half2(0, u_texCoordXform.z);" + " v_texCoord3 = a_vertex.xy * u_texCoordXform.yw + u_texCoordXform.xz;" + ); + } else if (oddWidth) { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy * half2(u_texCoordXform.y, 1);" + " v_texCoord1 = a_vertex.xy * half2(u_texCoordXform.y, 1) + half2(u_texCoordXform.x, 0);" + ); + } else if (oddHeight) { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy * half2(1, u_texCoordXform.w);" + " v_texCoord1 = a_vertex.xy * half2(1, u_texCoordXform.w) + half2(0, u_texCoordXform.z);" + ); + } else { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy;" + ); + } + + vshaderTxt.append("}"); + + SkString fshaderTxt; + if (shaderCaps->noperspectiveInterpolationSupport()) { + if (const char* extension = shaderCaps->noperspectiveInterpolationExtensionString()) { + fshaderTxt.appendf("#extension %s : require\n", extension); + } + } + for (int i = 0; i < numTaps; ++i) { + vTexCoords[i].setTypeModifier(GrShaderVar::kIn_TypeModifier); + vTexCoords[i].appendDecl(shaderCaps, &fshaderTxt); + fshaderTxt.append(";"); + } + uTexture.appendDecl(shaderCaps, &fshaderTxt); + fshaderTxt.append(";"); + fshaderTxt.append( + "// Mipmap Program FS\n" + "void main() {" + ); + + if (oddWidth && oddHeight) { + fshaderTxt.append( + " sk_FragColor = (sample(u_texture, v_texCoord0) + " + " sample(u_texture, v_texCoord1) + " + " sample(u_texture, v_texCoord2) + " + " sample(u_texture, v_texCoord3)) * 0.25;" + ); + } else if (oddWidth || oddHeight) { + fshaderTxt.append( + " sk_FragColor = (sample(u_texture, v_texCoord0) + " + " sample(u_texture, v_texCoord1)) * 0.5;" + ); + } else { + fshaderTxt.append( + " sk_FragColor = sample(u_texture, v_texCoord0);" + ); + } + + fshaderTxt.append("}"); + + auto errorHandler = this->getContext()->priv().getShaderErrorHandler(); + SkSL::String sksl(vshaderTxt.c_str(), vshaderTxt.size()); + SkSL::Program::Settings settings; + settings.fCaps = shaderCaps; + SkSL::String glsl; + std::unique_ptr program = GrSkSLtoGLSL(*fGLContext, SkSL::Program::kVertex_Kind, + sksl, settings, &glsl, errorHandler); + GrGLuint vshader = GrGLCompileAndAttachShader(*fGLContext, fMipmapPrograms[progIdx].fProgram, + GR_GL_VERTEX_SHADER, glsl, &fStats, errorHandler); + SkASSERT(program->fInputs.isEmpty()); + + sksl.assign(fshaderTxt.c_str(), fshaderTxt.size()); + program = GrSkSLtoGLSL(*fGLContext, SkSL::Program::kFragment_Kind, sksl, settings, &glsl, + errorHandler); + GrGLuint fshader = GrGLCompileAndAttachShader(*fGLContext, fMipmapPrograms[progIdx].fProgram, + GR_GL_FRAGMENT_SHADER, glsl, &fStats, + errorHandler); + SkASSERT(program->fInputs.isEmpty()); + + GL_CALL(LinkProgram(fMipmapPrograms[progIdx].fProgram)); + + GL_CALL_RET(fMipmapPrograms[progIdx].fTextureUniform, + GetUniformLocation(fMipmapPrograms[progIdx].fProgram, "u_texture")); + GL_CALL_RET(fMipmapPrograms[progIdx].fTexCoordXformUniform, + GetUniformLocation(fMipmapPrograms[progIdx].fProgram, "u_texCoordXform")); + + GL_CALL(BindAttribLocation(fMipmapPrograms[progIdx].fProgram, 0, "a_vertex")); + + GL_CALL(DeleteShader(vshader)); + GL_CALL(DeleteShader(fshader)); + + return true; +} + +bool GrGLGpu::copySurfaceAsDraw(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + auto* srcTex = static_cast(src->asTexture()); + auto* dstTex = static_cast(src->asTexture()); + auto* dstRT = static_cast(src->asRenderTarget()); + if (!srcTex) { + return false; + } + int progIdx = TextureToCopyProgramIdx(srcTex); + if (!dstRT) { + SkASSERT(dstTex); + if (!this->glCaps().isFormatRenderable(dstTex->format(), 1)) { + return false; + } + } + if (!fCopyPrograms[progIdx].fProgram) { + if (!this->createCopyProgram(srcTex)) { + SkDebugf("Failed to create copy program.\n"); + return false; + } + } + int w = srcRect.width(); + int h = srcRect.height(); + // We don't swizzle at all in our copies. + this->bindTexture(0, GrSamplerState::ClampNearest(), GrSwizzle::RGBA(), srcTex); + this->bindSurfaceFBOForPixelOps(dst, 0, GR_GL_FRAMEBUFFER, kDst_TempFBOTarget); + this->flushViewport(dst->width(), dst->height()); + fHWBoundRenderTargetUniqueID.makeInvalid(); + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, w, h); + this->flushProgram(fCopyPrograms[progIdx].fProgram); + fHWVertexArrayState.setVertexArrayID(this, 0); + GrGLAttribArrayState* attribs = fHWVertexArrayState.bindInternalVertexArray(this); + attribs->enableVertexArrays(this, 1); + attribs->set(this, 0, fCopyProgramArrayBuffer.get(), kFloat2_GrVertexAttribType, + kFloat2_GrSLType, 2 * sizeof(GrGLfloat), 0); + // dst rect edges in NDC (-1 to 1) + int dw = dst->width(); + int dh = dst->height(); + GrGLfloat dx0 = 2.f * dstPoint.fX / dw - 1.f; + GrGLfloat dx1 = 2.f * (dstPoint.fX + w) / dw - 1.f; + GrGLfloat dy0 = 2.f * dstPoint.fY / dh - 1.f; + GrGLfloat dy1 = 2.f * (dstPoint.fY + h) / dh - 1.f; + GrGLfloat sx0 = (GrGLfloat)srcRect.fLeft; + GrGLfloat sx1 = (GrGLfloat)(srcRect.fLeft + w); + GrGLfloat sy0 = (GrGLfloat)srcRect.fTop; + GrGLfloat sy1 = (GrGLfloat)(srcRect.fTop + h); + int sw = src->width(); + int sh = src->height(); + if (srcTex->texturePriv().textureType() != GrTextureType::kRectangle) { + // src rect edges in normalized texture space (0 to 1) + sx0 /= sw; + sx1 /= sw; + sy0 /= sh; + sy1 /= sh; + } + GL_CALL(Uniform4f(fCopyPrograms[progIdx].fPosXformUniform, dx1 - dx0, dy1 - dy0, dx0, dy0)); + GL_CALL(Uniform4f(fCopyPrograms[progIdx].fTexCoordXformUniform, + sx1 - sx0, sy1 - sy0, sx0, sy0)); + GL_CALL(Uniform1i(fCopyPrograms[progIdx].fTextureUniform, 0)); + this->flushBlendAndColorWrite(GrXferProcessor::BlendInfo(), GrSwizzle::RGBA()); + this->flushHWAAState(nullptr, false); + this->disableScissor(); + this->disableWindowRectangles(); + this->disableStencil(); + if (this->glCaps().srgbWriteControl()) { + this->flushFramebufferSRGB(true); + } + GL_CALL(DrawArrays(GR_GL_TRIANGLE_STRIP, 0, 4)); + this->unbindSurfaceFBOForPixelOps(dst, 0, GR_GL_FRAMEBUFFER); + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); + return true; +} + +void GrGLGpu::copySurfaceAsCopyTexSubImage(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + SkASSERT(can_copy_texsubimage(dst, src, this->glCaps())); + this->bindSurfaceFBOForPixelOps(src, 0, GR_GL_FRAMEBUFFER, kSrc_TempFBOTarget); + GrGLTexture* dstTex = static_cast(dst->asTexture()); + SkASSERT(dstTex); + // We modified the bound FBO + fHWBoundRenderTargetUniqueID.makeInvalid(); + + this->bindTextureToScratchUnit(dstTex->target(), dstTex->textureID()); + GL_CALL(CopyTexSubImage2D(dstTex->target(), 0, + dstPoint.fX, dstPoint.fY, + srcRect.fLeft, srcRect.fTop, + srcRect.width(), srcRect.height())); + this->unbindSurfaceFBOForPixelOps(src, 0, GR_GL_FRAMEBUFFER); + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, + srcRect.width(), srcRect.height()); + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); +} + +bool GrGLGpu::copySurfaceAsBlitFramebuffer(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + SkASSERT(can_blit_framebuffer_for_copy_surface(dst, src, srcRect, dstPoint, this->glCaps())); + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, + srcRect.width(), srcRect.height()); + if (dst == src) { + if (SkIRect::Intersects(dstRect, srcRect)) { + return false; + } + } + + this->bindSurfaceFBOForPixelOps(dst, 0, GR_GL_DRAW_FRAMEBUFFER, kDst_TempFBOTarget); + this->bindSurfaceFBOForPixelOps(src, 0, GR_GL_READ_FRAMEBUFFER, kSrc_TempFBOTarget); + // We modified the bound FBO + fHWBoundRenderTargetUniqueID.makeInvalid(); + + // BlitFrameBuffer respects the scissor, so disable it. + this->disableScissor(); + this->disableWindowRectangles(); + + GL_CALL(BlitFramebuffer(srcRect.fLeft, + srcRect.fTop, + srcRect.fRight, + srcRect.fBottom, + dstRect.fLeft, + dstRect.fTop, + dstRect.fRight, + dstRect.fBottom, + GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST)); + this->unbindSurfaceFBOForPixelOps(dst, 0, GR_GL_DRAW_FRAMEBUFFER); + this->unbindSurfaceFBOForPixelOps(src, 0, GR_GL_READ_FRAMEBUFFER); + + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); + return true; +} + +bool GrGLGpu::onRegenerateMipMapLevels(GrTexture* texture) { + auto glTex = static_cast(texture); + // Mipmaps are only supported on 2D textures: + if (GR_GL_TEXTURE_2D != glTex->target()) { + return false; + } + GrGLFormat format = glTex->format(); + // Manual implementation of mipmap generation, to work around driver bugs w/sRGB. + // Uses draw calls to do a series of downsample operations to successive mips. + + // The manual approach requires the ability to limit which level we're sampling and that the + // destination can be bound to a FBO: + if (!this->glCaps().doManualMipmapping() || !this->glCaps().isFormatRenderable(format, 1)) { + GrGLenum target = glTex->target(); + this->bindTextureToScratchUnit(target, glTex->textureID()); + GL_CALL(GenerateMipmap(glTex->target())); + return true; + } + + int width = texture->width(); + int height = texture->height(); + int levelCount = SkMipMap::ComputeLevelCount(width, height) + 1; + SkASSERT(levelCount == texture->texturePriv().maxMipMapLevel() + 1); + + // Create (if necessary), then bind temporary FBO: + if (0 == fTempDstFBOID) { + GL_CALL(GenFramebuffers(1, &fTempDstFBOID)); + } + this->bindFramebuffer(GR_GL_FRAMEBUFFER, fTempDstFBOID); + fHWBoundRenderTargetUniqueID.makeInvalid(); + + // Bind the texture, to get things configured for filtering. + // We'll be changing our base level further below: + this->setTextureUnit(0); + // The mipmap program does not do any swizzling. + this->bindTexture(0, GrSamplerState::ClampBilerp(), GrSwizzle::RGBA(), glTex); + + // Vertex data: + if (!fMipmapProgramArrayBuffer) { + static const GrGLfloat vdata[] = { + 0, 0, + 0, 1, + 1, 0, + 1, 1 + }; + fMipmapProgramArrayBuffer = GrGLBuffer::Make(this, sizeof(vdata), GrGpuBufferType::kVertex, + kStatic_GrAccessPattern, vdata); + } + if (!fMipmapProgramArrayBuffer) { + return false; + } + + fHWVertexArrayState.setVertexArrayID(this, 0); + + GrGLAttribArrayState* attribs = fHWVertexArrayState.bindInternalVertexArray(this); + attribs->enableVertexArrays(this, 1); + attribs->set(this, 0, fMipmapProgramArrayBuffer.get(), kFloat2_GrVertexAttribType, + kFloat2_GrSLType, 2 * sizeof(GrGLfloat), 0); + + // Set "simple" state once: + this->flushBlendAndColorWrite(GrXferProcessor::BlendInfo(), GrSwizzle::RGBA()); + this->flushHWAAState(nullptr, false); + this->disableScissor(); + this->disableWindowRectangles(); + this->disableStencil(); + + // Do all the blits: + width = texture->width(); + height = texture->height(); + + for (GrGLint level = 1; level < levelCount; ++level) { + // Get and bind the program for this particular downsample (filter shape can vary): + int progIdx = TextureSizeToMipmapProgramIdx(width, height); + if (!fMipmapPrograms[progIdx].fProgram) { + if (!this->createMipmapProgram(progIdx)) { + SkDebugf("Failed to create mipmap program.\n"); + // Invalidate all params to cover base level change in a previous iteration. + glTex->textureParamsModified(); + return false; + } + } + this->flushProgram(fMipmapPrograms[progIdx].fProgram); + + // Texcoord uniform is expected to contain (1/w, (w-1)/w, 1/h, (h-1)/h) + const float invWidth = 1.0f / width; + const float invHeight = 1.0f / height; + GL_CALL(Uniform4f(fMipmapPrograms[progIdx].fTexCoordXformUniform, + invWidth, (width - 1) * invWidth, invHeight, (height - 1) * invHeight)); + GL_CALL(Uniform1i(fMipmapPrograms[progIdx].fTextureUniform, 0)); + + // Only sample from previous mip + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_BASE_LEVEL, level - 1)); + + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, GR_GL_TEXTURE_2D, + glTex->textureID(), level)); + + width = SkTMax(1, width / 2); + height = SkTMax(1, height / 2); + this->flushViewport(width, height); + + GL_CALL(DrawArrays(GR_GL_TRIANGLE_STRIP, 0, 4)); + } + + // Unbind: + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, + GR_GL_TEXTURE_2D, 0, 0)); + + // We modified the base level param. + GrGLTextureParameters::NonsamplerState nonsamplerState = glTex->parameters()->nonsamplerState(); + // We drew the 2nd to last level into the last level. + nonsamplerState.fBaseMipMapLevel = levelCount - 2; + glTex->parameters()->set(nullptr, nonsamplerState, fResetTimestampForTextureParameters); + + return true; +} + +void GrGLGpu::querySampleLocations( + GrRenderTarget* renderTarget, SkTArray* sampleLocations) { + this->flushRenderTargetNoColorWrites(static_cast(renderTarget)); + + int effectiveSampleCnt; + GR_GL_GetIntegerv(this->glInterface(), GR_GL_SAMPLES, &effectiveSampleCnt); + SkASSERT(effectiveSampleCnt >= renderTarget->numSamples()); + + sampleLocations->reset(effectiveSampleCnt); + for (int i = 0; i < effectiveSampleCnt; ++i) { + GL_CALL(GetMultisamplefv(GR_GL_SAMPLE_POSITION, i, &(*sampleLocations)[i].fX)); + } +} + +void GrGLGpu::xferBarrier(GrRenderTarget* rt, GrXferBarrierType type) { + SkASSERT(type); + switch (type) { + case kTexture_GrXferBarrierType: { + GrGLRenderTarget* glrt = static_cast(rt); + SkASSERT(glrt->textureFBOID() != 0 && glrt->renderFBOID() != 0); + if (glrt->textureFBOID() != glrt->renderFBOID()) { + // The render target uses separate storage so no need for glTextureBarrier. + // FIXME: The render target will resolve automatically when its texture is bound, + // but we could resolve only the bounds that will be read if we do it here instead. + return; + } + SkASSERT(this->caps()->textureBarrierSupport()); + GL_CALL(TextureBarrier()); + return; + } + case kBlend_GrXferBarrierType: + SkASSERT(GrCaps::kAdvanced_BlendEquationSupport == + this->caps()->blendEquationSupport()); + GL_CALL(BlendBarrier()); + return; + default: break; // placate compiler warnings that kNone not handled + } +} + +static GrPixelConfig gl_format_to_pixel_config(GrGLFormat format) { + switch (format) { + case GrGLFormat::kRGBA8: return kRGBA_8888_GrPixelConfig; + case GrGLFormat::kRGB8: return kRGB_888_GrPixelConfig; + case GrGLFormat::kRG8: return kRG_88_GrPixelConfig; + case GrGLFormat::kBGRA8: return kBGRA_8888_GrPixelConfig; + case GrGLFormat::kLUMINANCE8: return kGray_8_GrPixelConfig; + case GrGLFormat::kSRGB8_ALPHA8: return kSRGBA_8888_GrPixelConfig; + case GrGLFormat::kRGB10_A2: return kRGBA_1010102_GrPixelConfig; + case GrGLFormat::kRGB565: return kRGB_565_GrPixelConfig; + case GrGLFormat::kRGBA4: return kRGBA_4444_GrPixelConfig; + case GrGLFormat::kRGBA16F: return kRGBA_half_GrPixelConfig; + case GrGLFormat::kR16: return kAlpha_16_GrPixelConfig; + case GrGLFormat::kRG16: return kRG_1616_GrPixelConfig; + case GrGLFormat::kRGBA16: return kRGBA_16161616_GrPixelConfig; + case GrGLFormat::kRG16F: return kRG_half_GrPixelConfig; + case GrGLFormat::kUnknown: return kUnknown_GrPixelConfig; + + // Configs with multiple equivalent formats. + + case GrGLFormat::kR16F: return kAlpha_half_GrPixelConfig; + case GrGLFormat::kLUMINANCE16F: return kAlpha_half_GrPixelConfig; + + case GrGLFormat::kALPHA8: return kAlpha_8_GrPixelConfig; + case GrGLFormat::kR8: return kAlpha_8_GrPixelConfig; + + case GrGLFormat::kCOMPRESSED_RGB8_ETC2: return kRGB_ETC1_GrPixelConfig; + case GrGLFormat::kCOMPRESSED_ETC1_RGB8: return kRGB_ETC1_GrPixelConfig; + } + SkUNREACHABLE; +} + +GrBackendTexture GrGLGpu::onCreateBackendTexture(int w, int h, + const GrBackendFormat& format, + GrMipMapped mipMapped, + GrRenderable renderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, + GrProtected isProtected) { + this->handleDirtyContext(); + + SkDEBUGCODE(const GrCaps* caps = this->caps();) + + // GrGpu::createBackendTexture should've ensured these conditions + SkASSERT(w >= 1 && w <= caps->maxTextureSize() && h >= 1 && h <= caps->maxTextureSize()); + SkASSERT(GrGpu::MipMapsAreCorrect(w, h, mipMapped, srcData, numMipLevels)); + SkASSERT(mipMapped == GrMipMapped::kNo || caps->mipMapSupport()); + + GrGLFormat glFormat = format.asGLFormat(); + if (glFormat == GrGLFormat::kUnknown) { + return GrBackendTexture(); // invalid + } + + // Compressed formats go through onCreateCompressedBackendTexture + SkASSERT(!GrGLFormatIsCompressed(glFormat)); + + GrPixelConfig config = gl_format_to_pixel_config(glFormat); + + if (config == kUnknown_GrPixelConfig) { + return GrBackendTexture(); // invalid + } + + auto textureColorType = GrPixelConfigToColorType(config); + + // TODO: move the texturability check up to GrGpu::createBackendTexture and just assert here + if (!this->caps()->isFormatTexturableAndUploadable(textureColorType, format)) { + return GrBackendTexture(); // invalid + } + + GrGLTextureInfo info; + GrGLTextureParameters::SamplerOverriddenState initialState; + + SkTDArray texels; + SkAutoMalloc pixelStorage; + + int mipLevelCount = 1; + if (srcData) { + mipLevelCount = numMipLevels; + texels.append(mipLevelCount); + for (int i = 0; i < mipLevelCount; ++i) { + texels[i] = { srcData[i].addr(), srcData[i].rowBytes() }; + } + } else if (color) { + if (GrMipMapped::kYes == mipMapped) { + mipLevelCount = SkMipMap::ComputeLevelCount(w, h) + 1; + } + + texels.append(mipLevelCount); + SkTArray individualMipOffsets(mipLevelCount); + + size_t bytesPerPixel = this->glCaps().bytesPerPixel(glFormat); + + size_t totalSize = GrComputeTightCombinedBufferSize( + bytesPerPixel, w, h, &individualMipOffsets, mipLevelCount); + + char* tmpPixels = (char*)pixelStorage.reset(totalSize); + + GrFillInData(textureColorType, w, h, individualMipOffsets, tmpPixels, *color); + for (int i = 0; i < mipLevelCount; ++i) { + size_t offset = individualMipOffsets[i]; + + int twoToTheMipLevel = 1 << i; + int currentWidth = SkTMax(1, w / twoToTheMipLevel); + + texels[i] = {&(tmpPixels[offset]), currentWidth * bytesPerPixel}; + } + } + + GrSurfaceDesc desc; + desc.fWidth = w; + desc.fHeight = h; + desc.fConfig = config; + + info.fTarget = GR_GL_TEXTURE_2D; + info.fFormat = GrGLFormatToEnum(glFormat); + info.fID = this->createTexture2D({desc.fWidth, desc.fHeight}, glFormat, renderable, + &initialState, SkTMax(1, texels.count())); + if (!info.fID) { + return GrBackendTexture(); // invalid + } + auto srcColorType = GrPixelConfigToColorType(desc.fConfig); + if (!texels.empty() && + !this->uploadTexData(glFormat, textureColorType, desc.fWidth, desc.fHeight, + GR_GL_TEXTURE_2D, 0, 0, desc.fWidth, desc.fHeight, srcColorType, + texels.begin(), texels.count())) { + GL_CALL(DeleteTextures(1, &info.fID)); + return GrBackendTexture(); + } + + // unbind the texture from the texture unit to avoid asserts + GL_CALL(BindTexture(info.fTarget, 0)); + + auto parameters = sk_make_sp(); + parameters->set(&initialState, GrGLTextureParameters::NonsamplerState(), + fResetTimestampForTextureParameters); + + return GrBackendTexture(w, h, mipMapped, info, std::move(parameters)); +} + +void GrGLGpu::deleteBackendTexture(const GrBackendTexture& tex) { + SkASSERT(GrBackendApi::kOpenGL == tex.backend()); + + GrGLTextureInfo info; + if (tex.getGLTextureInfo(&info)) { + GL_CALL(DeleteTextures(1, &info.fID)); + } +} + +#if GR_TEST_UTILS + +bool GrGLGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const { + SkASSERT(GrBackendApi::kOpenGL == tex.backend()); + + GrGLTextureInfo info; + if (!tex.getGLTextureInfo(&info)) { + return false; + } + + GrGLboolean result; + GL_CALL_RET(result, IsTexture(info.fID)); + + return (GR_GL_TRUE == result); +} + +GrBackendRenderTarget GrGLGpu::createTestingOnlyBackendRenderTarget(int w, int h, + GrColorType colorType) { + if (w > this->caps()->maxRenderTargetSize() || h > this->caps()->maxRenderTargetSize()) { + return GrBackendRenderTarget(); // invalid + } + this->handleDirtyContext(); + auto format = this->glCaps().getFormatFromColorType(colorType); + if (!this->glCaps().isFormatRenderable(format, 1)) { + return {}; + } + bool useTexture = format == GrGLFormat::kBGRA8; + int sFormatIdx = this->getCompatibleStencilIndex(format); + if (sFormatIdx < 0) { + return {}; + } + GrGLuint colorID = 0; + GrGLuint stencilID = 0; + auto deleteIDs = [&] { + if (colorID) { + if (useTexture) { + GL_CALL(DeleteTextures(1, &colorID)); + } else { + GL_CALL(DeleteRenderbuffers(1, &colorID)); + } + } + if (stencilID) { + GL_CALL(DeleteRenderbuffers(1, &stencilID)); + } + }; + + if (useTexture) { + GL_CALL(GenTextures(1, &colorID)); + } else { + GL_CALL(GenRenderbuffers(1, &colorID)); + } + GL_CALL(GenRenderbuffers(1, &stencilID)); + if (!stencilID || !colorID) { + deleteIDs(); + return {}; + } + + GrGLFramebufferInfo info; + info.fFBOID = 0; + info.fFormat = GrGLFormatToEnum(format); + GL_CALL(GenFramebuffers(1, &info.fFBOID)); + if (!info.fFBOID) { + deleteIDs(); + return {}; + } + + this->invalidateBoundRenderTarget(); + + this->bindFramebuffer(GR_GL_FRAMEBUFFER, info.fFBOID); + if (useTexture) { + GrGLTextureParameters::SamplerOverriddenState initialState; + colorID = this->createTexture2D({w, h}, format, GrRenderable::kYes, &initialState, 1); + if (!colorID) { + deleteIDs(); + return {}; + } + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, GR_GL_TEXTURE_2D, + colorID, 0)); + } else { + GrGLenum renderBufferFormat = this->glCaps().getRenderbufferInternalFormat(format); + GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, colorID)); + GL_ALLOC_CALL(this->glInterface(), + RenderbufferStorage(GR_GL_RENDERBUFFER, renderBufferFormat, w, h)); + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, + GR_GL_RENDERBUFFER, colorID)); + } + GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, stencilID)); + auto stencilBufferFormat = this->glCaps().stencilFormats()[sFormatIdx].fInternalFormat; + GL_ALLOC_CALL(this->glInterface(), + RenderbufferStorage(GR_GL_RENDERBUFFER, stencilBufferFormat, w, h)); + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_STENCIL_ATTACHMENT, GR_GL_RENDERBUFFER, + stencilID)); + if (this->glCaps().stencilFormats()[sFormatIdx].fPacked) { + GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, stencilID)); + } + + // We don't want to have to recover the renderbuffer/texture IDs later to delete them. OpenGL + // has this rule that if a renderbuffer/texture is deleted and a FBO other than the current FBO + // has the RB attached then deletion is delayed. So we unbind the FBO here and delete the + // renderbuffers/texture. + this->bindFramebuffer(GR_GL_FRAMEBUFFER, 0); + deleteIDs(); + + this->bindFramebuffer(GR_GL_FRAMEBUFFER, info.fFBOID); + GrGLenum status; + GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER)); + if (GR_GL_FRAMEBUFFER_COMPLETE != status) { + this->deleteFramebuffer(info.fFBOID); + return {}; + } + auto stencilBits = SkToInt(this->glCaps().stencilFormats()[sFormatIdx].fStencilBits); + + GrBackendRenderTarget beRT = GrBackendRenderTarget(w, h, 1, stencilBits, info); + SkASSERT(this->caps()->areColorTypeAndFormatCompatible(colorType, beRT.getBackendFormat())); + return beRT; +} + +void GrGLGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget& backendRT) { + SkASSERT(GrBackendApi::kOpenGL == backendRT.backend()); + GrGLFramebufferInfo info; + if (backendRT.getGLFramebufferInfo(&info)) { + if (info.fFBOID) { + this->deleteFramebuffer(info.fFBOID); + } + } +} + +void GrGLGpu::testingOnly_flushGpuAndSync() { + GL_CALL(Finish()); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +GrGLAttribArrayState* GrGLGpu::HWVertexArrayState::bindInternalVertexArray(GrGLGpu* gpu, + const GrBuffer* ibuf) { + GrGLAttribArrayState* attribState; + + if (gpu->glCaps().isCoreProfile()) { + if (!fCoreProfileVertexArray) { + GrGLuint arrayID; + GR_GL_CALL(gpu->glInterface(), GenVertexArrays(1, &arrayID)); + int attrCount = gpu->glCaps().maxVertexAttributes(); + fCoreProfileVertexArray = new GrGLVertexArray(arrayID, attrCount); + } + if (ibuf) { + attribState = fCoreProfileVertexArray->bindWithIndexBuffer(gpu, ibuf); + } else { + attribState = fCoreProfileVertexArray->bind(gpu); + } + } else { + if (ibuf) { + // bindBuffer implicitly binds VAO 0 when binding an index buffer. + gpu->bindBuffer(GrGpuBufferType::kIndex, ibuf); + } else { + this->setVertexArrayID(gpu, 0); + } + int attrCount = gpu->glCaps().maxVertexAttributes(); + if (fDefaultVertexArrayAttribState.count() != attrCount) { + fDefaultVertexArrayAttribState.resize(attrCount); + } + attribState = &fDefaultVertexArrayAttribState; + } + return attribState; +} + +void GrGLGpu::onFinishFlush(GrSurfaceProxy*[], int, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info, const GrPrepareForExternalIORequests&) { + // If we inserted semaphores during the flush, we need to call GLFlush. + bool insertedSemaphore = info.fNumSemaphores > 0 && this->caps()->semaphoreSupport(); + // We call finish if the client told us to sync or if we have a finished proc but don't support + // GLsync objects. + bool finish = (info.fFlags & kSyncCpu_GrFlushFlag) || + (info.fFinishedProc && !this->caps()->fenceSyncSupport()); + if (finish) { + GL_CALL(Finish()); + // After a finish everything previously sent to GL is done. + for (const auto& cb : fFinishCallbacks) { + cb.fCallback(cb.fContext); + this->deleteSync(cb.fSync); + } + fFinishCallbacks.clear(); + if (info.fFinishedProc) { + info.fFinishedProc(info.fFinishedContext); + } + } else { + if (info.fFinishedProc) { + FinishCallback callback; + callback.fCallback = info.fFinishedProc; + callback.fContext = info.fFinishedContext; + callback.fSync = (GrGLsync)this->insertFence(); + fFinishCallbacks.push_back(callback); + GL_CALL(Flush()); + } else if (insertedSemaphore) { + // Must call flush after semaphores in case they are waited on another GL context. + GL_CALL(Flush()); + } + // See if any previously inserted finish procs are good to go. + this->checkFinishProcs(); + } +} + +void GrGLGpu::submit(GrOpsRenderPass* renderPass) { + // The GrGLOpsRenderPass doesn't buffer ops so there is nothing to do here + SkASSERT(fCachedOpsRenderPass.get() == renderPass); + fCachedOpsRenderPass->reset(); +} + +GrFence SK_WARN_UNUSED_RESULT GrGLGpu::insertFence() { + SkASSERT(this->caps()->fenceSyncSupport()); + GrGLsync sync; + GL_CALL_RET(sync, FenceSync(GR_GL_SYNC_GPU_COMMANDS_COMPLETE, 0)); + GR_STATIC_ASSERT(sizeof(GrFence) >= sizeof(GrGLsync)); + return (GrFence)sync; +} + +bool GrGLGpu::waitSync(GrGLsync sync, uint64_t timeout, bool flush) { + GrGLbitfield flags = flush ? GR_GL_SYNC_FLUSH_COMMANDS_BIT : 0; + GrGLenum result; + GL_CALL_RET(result, ClientWaitSync(sync, flags, timeout)); + return (GR_GL_CONDITION_SATISFIED == result || GR_GL_ALREADY_SIGNALED == result); +} + +bool GrGLGpu::waitFence(GrFence fence, uint64_t timeout) { + return this->waitSync((GrGLsync)fence, timeout, /* flush = */ true); +} + +void GrGLGpu::deleteFence(GrFence fence) const { + this->deleteSync((GrGLsync)fence); +} + +sk_sp SK_WARN_UNUSED_RESULT GrGLGpu::makeSemaphore(bool isOwned) { + SkASSERT(this->caps()->semaphoreSupport()); + return GrGLSemaphore::Make(this, isOwned); +} + +sk_sp GrGLGpu::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) { + SkASSERT(this->caps()->semaphoreSupport()); + return GrGLSemaphore::MakeWrapped(this, semaphore.glSync(), ownership); +} + +void GrGLGpu::insertSemaphore(sk_sp semaphore) { + GrGLSemaphore* glSem = static_cast(semaphore.get()); + + GrGLsync sync; + GL_CALL_RET(sync, FenceSync(GR_GL_SYNC_GPU_COMMANDS_COMPLETE, 0)); + glSem->setSync(sync); +} + +void GrGLGpu::waitSemaphore(sk_sp semaphore) { + GrGLSemaphore* glSem = static_cast(semaphore.get()); + + GL_CALL(WaitSync(glSem->sync(), 0, GR_GL_TIMEOUT_IGNORED)); +} + +void GrGLGpu::checkFinishProcs() { + // Bail after the first unfinished sync since we expect they signal in the order inserted. + while (!fFinishCallbacks.empty() && this->waitSync(fFinishCallbacks.front().fSync, + /* timeout = */ 0, /* flush = */ false)) { + fFinishCallbacks.front().fCallback(fFinishCallbacks.front().fContext); + this->deleteSync(fFinishCallbacks.front().fSync); + fFinishCallbacks.pop_front(); + } +} + +void GrGLGpu::deleteSync(GrGLsync sync) const { + GL_CALL(DeleteSync(sync)); +} + +void GrGLGpu::insertEventMarker(const char* msg) { + GL_CALL(InsertEventMarker(strlen(msg), msg)); +} + +sk_sp GrGLGpu::prepareTextureForCrossContextUsage(GrTexture* texture) { + // Set up a semaphore to be signaled once the data is ready, and flush GL + sk_sp semaphore = this->makeSemaphore(true); + this->insertSemaphore(semaphore); + // We must call flush here to make sure the GrGLSync object gets created and sent to the gpu. + GL_CALL(Flush()); + + return semaphore; +} + +int GrGLGpu::TextureToCopyProgramIdx(GrTexture* texture) { + switch (GrSLCombinedSamplerTypeForTextureType(texture->texturePriv().textureType())) { + case kTexture2DSampler_GrSLType: + return 0; + case kTexture2DRectSampler_GrSLType: + return 1; + case kTextureExternalSampler_GrSLType: + return 2; + default: + SK_ABORT("Unexpected samper type"); + } +} + +#ifdef SK_ENABLE_DUMP_GPU +#include "src/utils/SkJSONWriter.h" +void GrGLGpu::onDumpJSON(SkJSONWriter* writer) const { + // We are called by the base class, which has already called beginObject(). We choose to nest + // all of our caps information in a named sub-object. + writer->beginObject("GL GPU"); + + const GrGLubyte* str; + GL_CALL_RET(str, GetString(GR_GL_VERSION)); + writer->appendString("GL_VERSION", (const char*)(str)); + GL_CALL_RET(str, GetString(GR_GL_RENDERER)); + writer->appendString("GL_RENDERER", (const char*)(str)); + GL_CALL_RET(str, GetString(GR_GL_VENDOR)); + writer->appendString("GL_VENDOR", (const char*)(str)); + GL_CALL_RET(str, GetString(GR_GL_SHADING_LANGUAGE_VERSION)); + writer->appendString("GL_SHADING_LANGUAGE_VERSION", (const char*)(str)); + + writer->appendName("extensions"); + glInterface()->fExtensions.dumpJSON(writer); + + writer->endObject(); +} +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLGpu.h b/gfx/skia/skia/src/gpu/gl/GrGLGpu.h new file mode 100644 index 0000000000..e49f45915e --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLGpu.h @@ -0,0 +1,664 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLGpu_DEFINED +#define GrGLGpu_DEFINED + +#include +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" +#include "src/core/SkLRUCache.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrNativeRect.h" +#include "src/gpu/GrWindowRectsState.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/gl/GrGLContext.h" +#include "src/gpu/gl/GrGLPathRendering.h" +#include "src/gpu/gl/GrGLProgram.h" +#include "src/gpu/gl/GrGLRenderTarget.h" +#include "src/gpu/gl/GrGLStencilAttachment.h" +#include "src/gpu/gl/GrGLTexture.h" +#include "src/gpu/gl/GrGLVertexArray.h" + +class GrGLBuffer; +class GrGLOpsRenderPass; +class GrPipeline; +class GrSwizzle; + +class GrGLGpu final : public GrGpu, private GrMesh::SendToGpuImpl { +public: + static sk_sp Make(sk_sp, const GrContextOptions&, GrContext*); + ~GrGLGpu() override; + + void disconnect(DisconnectType) override; + + const GrGLContext& glContext() const { return *fGLContext; } + + const GrGLInterface* glInterface() const { return fGLContext->interface(); } + const GrGLContextInfo& ctxInfo() const { return *fGLContext; } + GrGLStandard glStandard() const { return fGLContext->standard(); } + GrGLVersion glVersion() const { return fGLContext->version(); } + GrGLSLGeneration glslGeneration() const { return fGLContext->glslGeneration(); } + const GrGLCaps& glCaps() const { return *fGLContext->caps(); } + + GrGLPathRendering* glPathRendering() { + SkASSERT(glCaps().shaderCaps()->pathRenderingSupport()); + return static_cast(pathRendering()); + } + + // Used by GrGLProgram to configure OpenGL state. + void bindTexture(int unitIdx, GrSamplerState samplerState, const GrSwizzle&, GrGLTexture*); + + // These functions should be used to bind GL objects. They track the GL state and skip redundant + // bindings. Making the equivalent glBind calls directly will confuse the state tracking. + void bindVertexArray(GrGLuint id) { + fHWVertexArrayState.setVertexArrayID(this, id); + } + + // These callbacks update state tracking when GL objects are deleted. They are called from + // GrGLResource onRelease functions. + void notifyVertexArrayDelete(GrGLuint id) { + fHWVertexArrayState.notifyVertexArrayDelete(id); + } + + // Binds a buffer to the GL target corresponding to 'type', updates internal state tracking, and + // returns the GL target the buffer was bound to. + // When 'type' is kIndex_GrBufferType, this function will also implicitly bind the default VAO. + // If the caller wishes to bind an index buffer to a specific VAO, it can call glBind directly. + GrGLenum bindBuffer(GrGpuBufferType type, const GrBuffer*); + + // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu. + // Thus this is the implementation of the draw call for the corresponding passthrough function + // on GrGLOpsRenderPass. + void draw(GrRenderTarget*, const GrProgramInfo&, const GrMesh[], int meshCount); + + // GrMesh::SendToGpuImpl methods. These issue the actual GL draw calls. + // Marked final as a hint to the compiler to not use virtual dispatch. + void sendMeshToGpu(GrPrimitiveType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex) final; + + void sendIndexedMeshToGpu(GrPrimitiveType, const GrBuffer* indexBuffer, int indexCount, + int baseIndex, uint16_t minIndexValue, uint16_t maxIndexValue, + const GrBuffer* vertexBuffer, int baseVertex, + GrPrimitiveRestart) final; + + void sendInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex, const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance) final; + + void sendIndexedInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* indexBuffer, int indexCount, + int baseIndex, const GrBuffer* vertexBuffer, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance, GrPrimitiveRestart) final; + + // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu. + // Thus this is the implementation of the clear call for the corresponding passthrough function + // on GrGLOpsRenderPass. + void clear(const GrFixedClip&, const SkPMColor4f&, GrRenderTarget*, GrSurfaceOrigin); + + // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu. + // Thus this is the implementation of the clearStencil call for the corresponding passthrough + // function on GrGLOpsrenderPass. + void clearStencilClip(const GrFixedClip&, bool insideStencilMask, + GrRenderTarget*, GrSurfaceOrigin); + + // FIXME (michaelludwig): Can this go away and just use clearStencilClip() + marking the + // stencil buffer as not dirty? + void clearStencil(GrRenderTarget*, int clearValue); + + void beginCommandBuffer(GrRenderTarget*, const SkIRect& bounds, GrSurfaceOrigin, + const GrOpsRenderPass::LoadAndStoreInfo& colorLoadStore, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore); + + void endCommandBuffer(GrRenderTarget*, const GrOpsRenderPass::LoadAndStoreInfo& colorLoadStore, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore); + + GrOpsRenderPass* getOpsRenderPass( + GrRenderTarget*, GrSurfaceOrigin, const SkIRect&, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) override; + + void invalidateBoundRenderTarget() { + fHWBoundRenderTargetUniqueID.makeInvalid(); + } + + GrStencilAttachment* createStencilAttachmentForRenderTarget( + const GrRenderTarget* rt, int width, int height, int numStencilSamples) override; + void deleteBackendTexture(const GrBackendTexture&) override; + + bool precompileShader(const SkData& key, const SkData& data) override { + return fProgramCache->precompileShader(key, data); + } + +#if GR_TEST_UTILS + bool isTestingOnlyBackendTexture(const GrBackendTexture&) const override; + + GrBackendRenderTarget createTestingOnlyBackendRenderTarget(int w, int h, GrColorType) override; + void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) override; + + const GrGLContext* glContextForTesting() const override { return &this->glContext(); } + + void resetShaderCacheForTesting() const override { fProgramCache->reset(); } + + void testingOnly_flushGpuAndSync() override; +#endif + + void submit(GrOpsRenderPass* renderPass) override; + + GrFence SK_WARN_UNUSED_RESULT insertFence() override; + bool waitFence(GrFence, uint64_t timeout) override; + void deleteFence(GrFence) const override; + + sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned) override; + sk_sp wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) override; + void insertSemaphore(sk_sp semaphore) override; + void waitSemaphore(sk_sp semaphore) override; + + void checkFinishProcs() override; + + sk_sp prepareTextureForCrossContextUsage(GrTexture*) override; + + void deleteSync(GrGLsync) const; + + void insertEventMarker(const char*); + + void bindFramebuffer(GrGLenum fboTarget, GrGLuint fboid); + void deleteFramebuffer(GrGLuint fboid); + +private: + GrGLGpu(std::unique_ptr, GrContext*); + + // GrGpu overrides + GrBackendTexture onCreateBackendTexture(int w, int h, const GrBackendFormat&, + GrMipMapped, GrRenderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected) override; + + void onResetContext(uint32_t resetBits) override; + + void onResetTextureBindings() override; + + void querySampleLocations(GrRenderTarget*, SkTArray*) override; + + void xferBarrier(GrRenderTarget*, GrXferBarrierType) override; + + sk_sp onCreateTexture(const GrSurfaceDesc&, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted, + GrProtected, + int mipLevelCount, + uint32_t levelClearMask) override; + sk_sp onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType compression, SkBudgeted, + const void* data) override; + + sk_sp onCreateBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern, + const void* data) override; + + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrColorType, GrWrapOwnership, + GrWrapCacheable, GrIOType) override; + sk_sp onWrapRenderableBackendTexture(const GrBackendTexture&, int sampleCnt, + GrColorType, GrWrapOwnership, + GrWrapCacheable) override; + sk_sp onWrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType) override; + sk_sp onWrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, GrColorType) override; + + // Given a GL format return the index into the stencil format array on GrGLCaps to a + // compatible stencil format, or negative if there is no compatible stencil format. + int getCompatibleStencilIndex(GrGLFormat format); + + void onFBOChanged(); + + // Returns whether the texture is successfully created. On success, a non-zero texture ID is + // returned. On failure, zero is returned. + // The texture is populated with |texels|, if it is non-null. + // The texture parameters are cached in |initialTexParams|. + GrGLuint createTexture2D(const SkISize& size, + GrGLFormat format, + GrRenderable, + GrGLTextureParameters::SamplerOverriddenState* initialState, + int mipLevelCount); + + GrGLuint createCompressedTexture2D(const SkISize& size, GrGLFormat format, + SkImage::CompressionType compression, + GrGLTextureParameters::SamplerOverriddenState* initialState, + const void* data); + + bool onReadPixels(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) override; + + bool onWritePixels(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) override; + + bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) override; + bool onTransferPixelsFrom(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) override; + bool readOrTransferPixelsFrom(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, + void* offsetOrPtr, int rowWidthInPixels); + + // Before calling any variation of TexImage, TexSubImage, etc..., call this to ensure that the + // PIXEL_UNPACK_BUFFER is unbound. + void unbindCpuToGpuXferBuffer(); + + void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin resolveOrigin, ForExternalIO) override; + + bool onRegenerateMipMapLevels(GrTexture*) override; + + bool onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) override; + + // binds texture unit in GL + void setTextureUnit(int unitIdx); + + // Flushes state from GrPipeline to GL. Returns false if the state couldn't be set. + // willDrawPoints must be true if point primitives will be rendered after setting the GL state. + // If DynamicStateArrays is not null then dynamicStateArraysLength is the number of dynamic + // state entries in each array. + bool flushGLState(GrRenderTarget*, const GrProgramInfo&, GrPrimitiveType); + + void flushProgram(sk_sp); + + // Version for programs that aren't GrGLProgram. + void flushProgram(GrGLuint); + + // Sets up vertex/instance attribute pointers and strides. + void setupGeometry(const GrBuffer* indexBuffer, + const GrBuffer* vertexBuffer, + int baseVertex, + const GrBuffer* instanceBuffer, + int baseInstance, + GrPrimitiveRestart); + + void flushBlendAndColorWrite(const GrXferProcessor::BlendInfo& blendInfo, const GrSwizzle&); + + void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo&, const GrPrepareForExternalIORequests&) override; + + bool waitSync(GrGLsync, uint64_t timeout, bool flush); + + bool copySurfaceAsDraw(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + void copySurfaceAsCopyTexSubImage(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + bool copySurfaceAsBlitFramebuffer(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + static bool BlendCoeffReferencesConstant(GrBlendCoeff coeff); + + class ProgramCache : public ::SkNoncopyable { + public: + ProgramCache(GrGLGpu* gpu); + ~ProgramCache(); + + void abandon(); + void reset(); + GrGLProgram* refProgram(GrGLGpu*, GrRenderTarget*, const GrProgramInfo&, GrPrimitiveType); + bool precompileShader(const SkData& key, const SkData& data); + + private: + struct Entry; + + struct DescHash { + uint32_t operator()(const GrProgramDesc& desc) const { + return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0); + } + }; + + SkLRUCache, DescHash> fMap; + + GrGLGpu* fGpu; + }; + + void flushColorWrite(bool writeColor); + void flushClearColor(const SkPMColor4f&); + + // flushes the scissor. see the note on flushBoundTextureAndParams about + // flushing the scissor after that function is called. + void flushScissor(const GrScissorState&, int rtWidth, int rtHeight, GrSurfaceOrigin rtOrigin); + + // disables the scissor + void disableScissor(); + + void flushWindowRectangles(const GrWindowRectsState&, const GrGLRenderTarget*, GrSurfaceOrigin); + void disableWindowRectangles(); + + int numTextureUnits() const { return this->caps()->shaderCaps()->maxFragmentSamplers(); } + + // Binds a texture to a target on the "scratch" texture unit to use for texture operations + // other than usual draw flow (i.e. a GrGLProgram derived from a GrPipeline used to draw + // GrMesh). It ensures that such operations don't negatively interact with draws. + // The active texture unit and the binding for 'target' will change. + void bindTextureToScratchUnit(GrGLenum target, GrGLint textureID); + + // The passed bounds contains the render target's color values that will subsequently be + // written. + void flushRenderTarget(GrGLRenderTarget*, GrSurfaceOrigin, const SkIRect& bounds); + // This version has an implicit bounds of the entire render target. + void flushRenderTarget(GrGLRenderTarget*); + // This version can be used when the render target's colors will not be written. + void flushRenderTargetNoColorWrites(GrGLRenderTarget*); + + // Need not be called if flushRenderTarget is used. + void flushViewport(int width, int height); + + void flushStencil(const GrStencilSettings&, GrSurfaceOrigin); + void disableStencil(); + + // rt is used only if useHWAA is true. + void flushHWAAState(GrRenderTarget* rt, bool useHWAA); + + void flushFramebufferSRGB(bool enable); + + bool uploadTexData(GrGLFormat textureFormat, GrColorType textureColorType, int texWidth, + int texHeight, GrGLenum target, int left, int top, int width, int height, + GrColorType srcColorType, const GrMipLevel texels[], int mipLevelCount, + GrMipMapsStatus* mipMapsStatus = nullptr); + + // Helper for onCreateCompressedTexture. Compressed textures are read-only so we only use this + // to populate a new texture. Returns false if we failed to create and upload the texture. + bool uploadCompressedTexData(GrGLFormat, + SkImage::CompressionType, + const SkISize& size, + GrGLenum target, + const void* data); + + bool createRenderTargetObjects(const GrGLTexture::Desc&, + int sampleCount, + GrGLRenderTarget::IDs*); + + enum TempFBOTarget { + kSrc_TempFBOTarget, + kDst_TempFBOTarget + }; + + // Binds a surface as a FBO for copying, reading, or clearing. If the surface already owns an + // FBO ID then that ID is bound. If not the surface is temporarily bound to a FBO and that FBO + // is bound. This must be paired with a call to unbindSurfaceFBOForPixelOps(). + void bindSurfaceFBOForPixelOps(GrSurface* surface, int mipLevel, GrGLenum fboTarget, + TempFBOTarget tempFBOTarget); + + // Must be called if bindSurfaceFBOForPixelOps was used to bind a surface for copying. + void unbindSurfaceFBOForPixelOps(GrSurface* surface, int mipLevel, GrGLenum fboTarget); + +#ifdef SK_ENABLE_DUMP_GPU + void onDumpJSON(SkJSONWriter*) const override; +#endif + + bool createCopyProgram(GrTexture* srcTexture); + bool createMipmapProgram(int progIdx); + + std::unique_ptr fGLContext; + + // GL program-related state + ProgramCache* fProgramCache; + + /////////////////////////////////////////////////////////////////////////// + ///@name Caching of GL State + ///@{ + int fHWActiveTextureUnitIdx; + + GrGLuint fHWProgramID; + sk_sp fHWProgram; + + enum TriState { + kNo_TriState, + kYes_TriState, + kUnknown_TriState + }; + + GrGLuint fTempSrcFBOID; + GrGLuint fTempDstFBOID; + + GrGLuint fStencilClearFBOID; + + // last scissor / viewport scissor state seen by the GL. + struct { + TriState fEnabled; + GrNativeRect fRect; + void invalidate() { + fEnabled = kUnknown_TriState; + fRect.invalidate(); + } + } fHWScissorSettings; + + class { + public: + bool valid() const { return kInvalidSurfaceOrigin != fRTOrigin; } + void invalidate() { fRTOrigin = kInvalidSurfaceOrigin; } + bool knownDisabled() const { return this->valid() && !fWindowState.enabled(); } + void setDisabled() { + fRTOrigin = kTopLeft_GrSurfaceOrigin; + fWindowState.setDisabled(); + } + + void set(GrSurfaceOrigin rtOrigin, int width, int height, + const GrWindowRectsState& windowState) { + fRTOrigin = rtOrigin; + fWidth = width; + fHeight = height; + fWindowState = windowState; + } + + bool knownEqualTo(GrSurfaceOrigin rtOrigin, int width, int height, + const GrWindowRectsState& windowState) const { + if (!this->valid()) { + return false; + } + if (fWindowState.numWindows() && + (fRTOrigin != rtOrigin || fWidth != width || fHeight != height)) { + return false; + } + return fWindowState == windowState; + } + + private: + enum { kInvalidSurfaceOrigin = -1 }; + + int fRTOrigin; + int fWidth; + int fHeight; + GrWindowRectsState fWindowState; + } fHWWindowRectsState; + + GrNativeRect fHWViewport; + + /** + * Tracks vertex attrib array state. + */ + class HWVertexArrayState { + public: + HWVertexArrayState() : fCoreProfileVertexArray(nullptr) { this->invalidate(); } + + ~HWVertexArrayState() { delete fCoreProfileVertexArray; } + + void invalidate() { + fBoundVertexArrayIDIsValid = false; + fDefaultVertexArrayAttribState.invalidate(); + if (fCoreProfileVertexArray) { + fCoreProfileVertexArray->invalidateCachedState(); + } + } + + void notifyVertexArrayDelete(GrGLuint id) { + if (fBoundVertexArrayIDIsValid && fBoundVertexArrayID == id) { + // Does implicit bind to 0 + fBoundVertexArrayID = 0; + } + } + + void setVertexArrayID(GrGLGpu* gpu, GrGLuint arrayID) { + if (!gpu->glCaps().vertexArrayObjectSupport()) { + SkASSERT(0 == arrayID); + return; + } + if (!fBoundVertexArrayIDIsValid || arrayID != fBoundVertexArrayID) { + GR_GL_CALL(gpu->glInterface(), BindVertexArray(arrayID)); + fBoundVertexArrayIDIsValid = true; + fBoundVertexArrayID = arrayID; + } + } + + /** + * Binds the vertex array that should be used for internal draws, and returns its attrib + * state. This binds the default VAO (ID=zero) unless we are on a core profile, in which + * case we use a dummy array instead. + * + * If an index buffer is privided, it will be bound to the vertex array. Otherwise the + * index buffer binding will be left unchanged. + * + * The returned GrGLAttribArrayState should be used to set vertex attribute arrays. + */ + GrGLAttribArrayState* bindInternalVertexArray(GrGLGpu*, const GrBuffer* ibuff = nullptr); + + private: + GrGLuint fBoundVertexArrayID; + bool fBoundVertexArrayIDIsValid; + + // We return a non-const pointer to this from bindArrayAndBuffersToDraw when vertex array 0 + // is bound. However, this class is internal to GrGLGpu and this object never leaks out of + // GrGLGpu. + GrGLAttribArrayState fDefaultVertexArrayAttribState; + + // This is used when we're using a core profile. + GrGLVertexArray* fCoreProfileVertexArray; + } fHWVertexArrayState; + + struct { + GrGLenum fGLTarget; + GrGpuResource::UniqueID fBoundBufferUniqueID; + bool fBufferZeroKnownBound; + + void invalidate() { + fBoundBufferUniqueID.makeInvalid(); + fBufferZeroKnownBound = false; + } + } fHWBufferState[kGrGpuBufferTypeCount]; + + auto* hwBufferState(GrGpuBufferType type) { + unsigned typeAsUInt = static_cast(type); + SkASSERT(typeAsUInt < SK_ARRAY_COUNT(fHWBufferState)); + return &fHWBufferState[typeAsUInt]; + } + + struct { + GrBlendEquation fEquation; + GrBlendCoeff fSrcCoeff; + GrBlendCoeff fDstCoeff; + SkPMColor4f fConstColor; + bool fConstColorValid; + TriState fEnabled; + + void invalidate() { + fEquation = kIllegal_GrBlendEquation; + fSrcCoeff = kIllegal_GrBlendCoeff; + fDstCoeff = kIllegal_GrBlendCoeff; + fConstColorValid = false; + fEnabled = kUnknown_TriState; + } + } fHWBlendState; + + TriState fMSAAEnabled; + + GrStencilSettings fHWStencilSettings; + GrSurfaceOrigin fHWStencilOrigin; + TriState fHWStencilTestEnabled; + + + TriState fHWWriteToColor; + GrGpuResource::UniqueID fHWBoundRenderTargetUniqueID; + TriState fHWSRGBFramebuffer; + + class TextureUnitBindings { + public: + TextureUnitBindings() = default; + TextureUnitBindings(const TextureUnitBindings&) = delete; + TextureUnitBindings& operator=(const TextureUnitBindings&) = delete; + + GrGpuResource::UniqueID boundID(GrGLenum target) const; + bool hasBeenModified(GrGLenum target) const; + void setBoundID(GrGLenum target, GrGpuResource::UniqueID); + void invalidateForScratchUse(GrGLenum target); + void invalidateAllTargets(bool markUnmodified); + + private: + struct TargetBinding { + GrGpuResource::UniqueID fBoundResourceID; + bool fHasBeenModified = false; + }; + TargetBinding fTargetBindings[3]; + }; + SkAutoTArray fHWTextureUnitBindings; + + GrGLfloat fHWClearColor[4]; + + GrGLuint fBoundDrawFramebuffer = 0; + + /** IDs for copy surface program. (3 sampler types) */ + struct { + GrGLuint fProgram = 0; + GrGLint fTextureUniform = 0; + GrGLint fTexCoordXformUniform = 0; + GrGLint fPosXformUniform = 0; + } fCopyPrograms[3]; + sk_sp fCopyProgramArrayBuffer; + + /** IDs for texture mipmap program. (4 filter configurations) */ + struct { + GrGLuint fProgram = 0; + GrGLint fTextureUniform = 0; + GrGLint fTexCoordXformUniform = 0; + } fMipmapPrograms[4]; + sk_sp fMipmapProgramArrayBuffer; + + static int TextureToCopyProgramIdx(GrTexture* texture); + + static int TextureSizeToMipmapProgramIdx(int width, int height) { + const bool wide = (width > 1) && SkToBool(width & 0x1); + const bool tall = (height > 1) && SkToBool(height & 0x1); + return (wide ? 0x2 : 0x0) | (tall ? 0x1 : 0x0); + } + + GrPrimitiveType fLastPrimitiveType; + + GrGLTextureParameters::ResetTimestamp fResetTimestampForTextureParameters = 0; + + class SamplerObjectCache; + std::unique_ptr fSamplerObjectCache; + + std::unique_ptr fCachedOpsRenderPass; + + struct FinishCallback { + GrGpuFinishedProc fCallback; + GrGpuFinishedContext fContext; + GrGLsync fSync; + }; + std::list fFinishCallbacks; + + SkDEBUGCODE(bool fIsExecutingCommandBuffer_DebugOnly = false); + + friend class GrGLPathRendering; // For accessing setTextureUnit. + + typedef GrGpu INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLGpuProgramCache.cpp b/gfx/skia/skia/src/gpu/gl/GrGLGpuProgramCache.cpp new file mode 100644 index 0000000000..0109741edb --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLGpuProgramCache.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLGpu.h" + +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/gl/builders/GrGLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" + +struct GrGLGpu::ProgramCache::Entry { + Entry(sk_sp program) + : fProgram(std::move(program)) {} + + Entry(const GrGLPrecompiledProgram& precompiledProgram) + : fPrecompiledProgram(precompiledProgram) {} + + sk_sp fProgram; + GrGLPrecompiledProgram fPrecompiledProgram; +}; + +GrGLGpu::ProgramCache::ProgramCache(GrGLGpu* gpu) + : fMap(gpu->getContext()->priv().options().fRuntimeProgramCacheSize) + , fGpu(gpu) {} + +GrGLGpu::ProgramCache::~ProgramCache() {} + +void GrGLGpu::ProgramCache::abandon() { + fMap.foreach([](std::unique_ptr* e) { + if ((*e)->fProgram) { + (*e)->fProgram->abandon(); + } + }); + + this->reset(); +} + +void GrGLGpu::ProgramCache::reset() { + fMap.reset(); +} + +GrGLProgram* GrGLGpu::ProgramCache::refProgram(GrGLGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType) { + // TODO: can this be unified between GL, Vk and Mtl? + // Get GrGLProgramDesc + GrProgramDesc desc; + if (!GrProgramDesc::Build(&desc, renderTarget, programInfo, primitiveType, gpu)) { + GrCapsDebugf(gpu->caps(), "Failed to gl program descriptor!\n"); + return nullptr; + } + + std::unique_ptr* entry = fMap.find(desc); + if (entry && !(*entry)->fProgram) { + // We've pre-compiled the GL program, but don't have the GrGLProgram scaffolding + const GrGLPrecompiledProgram* precompiledProgram = &((*entry)->fPrecompiledProgram); + SkASSERT(precompiledProgram->fProgramID != 0); + GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, programInfo, + &desc, fGpu, + precompiledProgram); + if (nullptr == program) { + // Should we purge the program ID from the cache at this point? + SkDEBUGFAIL("Couldn't create program from precompiled program"); + return nullptr; + } + (*entry)->fProgram.reset(program); + } else if (!entry) { + // We have a cache miss + GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, programInfo, + &desc, fGpu); + if (nullptr == program) { + return nullptr; + } + entry = fMap.insert(desc, std::unique_ptr(new Entry(sk_sp(program)))); + } + + return SkRef((*entry)->fProgram.get()); +} + +bool GrGLGpu::ProgramCache::precompileShader(const SkData& key, const SkData& data) { + GrProgramDesc desc; + if (!GrProgramDesc::BuildFromData(&desc, key.data(), key.size())) { + return false; + } + + std::unique_ptr* entry = fMap.find(desc); + if (entry) { + // We've already seen/compiled this shader + return true; + } + + GrGLPrecompiledProgram precompiledProgram; + if (!GrGLProgramBuilder::PrecompileProgram(&precompiledProgram, fGpu, data)) { + return false; + } + + fMap.insert(desc, std::unique_ptr(new Entry(precompiledProgram))); + return true; +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLInterfaceAutogen.cpp b/gfx/skia/skia/src/gpu/gl/GrGLInterfaceAutogen.cpp new file mode 100644 index 0000000000..dd1207e72e --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLInterfaceAutogen.cpp @@ -0,0 +1,742 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * THIS FILE IS AUTOGENERATED + * Make edits to tools/gpu/gl/interface/templates.go or they will + * be overwritten. + */ + +#include "include/gpu/gl/GrGLExtensions.h" +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#include + +GrGLInterface::GrGLInterface() { + fStandard = kNone_GrGLStandard; +} + +#define RETURN_FALSE_INTERFACE \ + SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \ + return false + +bool GrGLInterface::validate() const { + + if (kNone_GrGLStandard == fStandard) { + RETURN_FALSE_INTERFACE; + } + + if (!fExtensions.isInitialized()) { + RETURN_FALSE_INTERFACE; + } + + GrGLVersion glVer = GrGLGetVersion(this); + if (GR_GL_INVALID_VER == glVer) { + RETURN_FALSE_INTERFACE; + } + // Autogenerated content follows + if (!fFunctions.fActiveTexture || + !fFunctions.fAttachShader || + !fFunctions.fBindAttribLocation || + !fFunctions.fBindBuffer || + !fFunctions.fBindTexture || + !fFunctions.fBlendColor || + !fFunctions.fBlendEquation || + !fFunctions.fBlendFunc || + !fFunctions.fBufferData || + !fFunctions.fBufferSubData || + !fFunctions.fClear || + !fFunctions.fClearColor || + !fFunctions.fClearStencil || + !fFunctions.fColorMask || + !fFunctions.fCompileShader || + !fFunctions.fCompressedTexImage2D || + !fFunctions.fCompressedTexSubImage2D || + !fFunctions.fCopyTexSubImage2D || + !fFunctions.fCreateProgram || + !fFunctions.fCreateShader || + !fFunctions.fCullFace || + !fFunctions.fDeleteBuffers || + !fFunctions.fDeleteProgram || + !fFunctions.fDeleteShader || + !fFunctions.fDeleteTextures || + !fFunctions.fDepthMask || + !fFunctions.fDisable || + !fFunctions.fDisableVertexAttribArray || + !fFunctions.fDrawArrays || + !fFunctions.fDrawElements || + !fFunctions.fEnable || + !fFunctions.fEnableVertexAttribArray || + !fFunctions.fFinish || + !fFunctions.fFlush || + !fFunctions.fFrontFace || + !fFunctions.fGenBuffers || + !fFunctions.fGenTextures || + !fFunctions.fGetBufferParameteriv || + !fFunctions.fGetError || + !fFunctions.fGetIntegerv || + !fFunctions.fGetProgramInfoLog || + !fFunctions.fGetProgramiv || + !fFunctions.fGetShaderInfoLog || + !fFunctions.fGetShaderiv || + !fFunctions.fGetString || + !fFunctions.fGetUniformLocation || + !fFunctions.fIsTexture || + !fFunctions.fLineWidth || + !fFunctions.fLinkProgram || + !fFunctions.fPixelStorei || + !fFunctions.fReadPixels || + !fFunctions.fScissor || + !fFunctions.fShaderSource || + !fFunctions.fStencilFunc || + !fFunctions.fStencilFuncSeparate || + !fFunctions.fStencilMask || + !fFunctions.fStencilMaskSeparate || + !fFunctions.fStencilOp || + !fFunctions.fStencilOpSeparate || + !fFunctions.fTexImage2D || + !fFunctions.fTexParameterf || + !fFunctions.fTexParameterfv || + !fFunctions.fTexParameteri || + !fFunctions.fTexParameteriv || + !fFunctions.fTexSubImage2D || + !fFunctions.fUniform1f || + !fFunctions.fUniform1fv || + !fFunctions.fUniform1i || + !fFunctions.fUniform1iv || + !fFunctions.fUniform2f || + !fFunctions.fUniform2fv || + !fFunctions.fUniform2i || + !fFunctions.fUniform2iv || + !fFunctions.fUniform3f || + !fFunctions.fUniform3fv || + !fFunctions.fUniform3i || + !fFunctions.fUniform3iv || + !fFunctions.fUniform4f || + !fFunctions.fUniform4fv || + !fFunctions.fUniform4i || + !fFunctions.fUniform4iv || + !fFunctions.fUniformMatrix2fv || + !fFunctions.fUniformMatrix3fv || + !fFunctions.fUniformMatrix4fv || + !fFunctions.fUseProgram || + !fFunctions.fVertexAttrib1f || + !fFunctions.fVertexAttrib2fv || + !fFunctions.fVertexAttrib3fv || + !fFunctions.fVertexAttrib4fv || + !fFunctions.fVertexAttribPointer || + !fFunctions.fViewport) { + RETURN_FALSE_INTERFACE; + } + + if (GR_IS_GR_GL(fStandard)) { + if (!fFunctions.fDrawBuffer || + !fFunctions.fPolygonMode) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fGetStringi) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_OES_vertex_array_object"))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0)) || + fExtensions.has("GL_OES_vertex_array_object") || + fExtensions.has("OES_vertex_array_object")))) { + if (!fFunctions.fBindVertexArray || + !fFunctions.fDeleteVertexArrays || + !fFunctions.fGenVertexArrays) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0) && fExtensions.has("GL_EXT_blend_func_extended"))))) { + if (!fFunctions.fBindFragDataLocation) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,3)) || + fExtensions.has("GL_ARB_blend_func_extended"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0) && fExtensions.has("GL_EXT_blend_func_extended"))))) { + if (!fFunctions.fBindFragDataLocationIndexed) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + fExtensions.has("GL_KHR_blend_equation_advanced") || + fExtensions.has("GL_NV_blend_equation_advanced"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_KHR_blend_equation_advanced") || + fExtensions.has("GL_NV_blend_equation_advanced")))) { + if (!fFunctions.fBlendBarrier) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,4)) || + fExtensions.has("GL_ARB_clear_texture"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_clear_texture")))) { + // all functions were marked optional or test_only + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,1)) || + fExtensions.has("GL_ARB_draw_instanced") || + fExtensions.has("GL_EXT_draw_instanced"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_EXT_draw_instanced"))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fDrawArraysInstanced || + !fFunctions.fDrawElementsInstanced) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fDrawBuffers || + !fFunctions.fReadBuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,0)) || + fExtensions.has("GL_ARB_draw_indirect"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,1))))) { + if (!fFunctions.fDrawArraysIndirect || + !fFunctions.fDrawElementsIndirect) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fDrawRangeElements) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,2)) || + fExtensions.has("GL_ARB_texture_multisample"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,1))))) { + if (!fFunctions.fGetMultisamplefv) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,1))))) { + if (!fFunctions.fGetTexLevelParameteriv) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_ARB_multi_draw_indirect"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_multi_draw_indirect")))) { + if (!fFunctions.fMultiDrawArraysIndirect || + !fFunctions.fMultiDrawElementsIndirect) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,1)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,2)) || + fExtensions.has("GL_OES_texture_buffer") || + fExtensions.has("GL_EXT_texture_buffer")))) { + if (!fFunctions.fTexBuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,2)) || + fExtensions.has("GL_OES_texture_buffer") || + fExtensions.has("GL_EXT_texture_buffer")))) { + if (!fFunctions.fTexBufferRange) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,2)) || + fExtensions.has("GL_ARB_texture_storage") || + fExtensions.has("GL_EXT_texture_storage"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_EXT_texture_storage"))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fTexStorage2D) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,5)) || + fExtensions.has("GL_ARB_texture_barrier") || + fExtensions.has("GL_NV_texture_barrier"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_NV_texture_barrier")))) { + if (!fFunctions.fTextureBarrier) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_discard_framebuffer")))) { + if (!fFunctions.fDiscardFramebuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_QCOM_tiled_rendering")))) { + if (!fFunctions.fEndTiling || + !fFunctions.fStartTiling) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,2)) || + fExtensions.has("GL_ARB_instanced_arrays"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_EXT_instanced_arrays"))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fVertexAttribDivisor) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fVertexAttribIPointer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_ARB_framebuffer_object") || + fExtensions.has("GL_EXT_framebuffer_object"))) || + GR_IS_GR_GL_ES(fStandard) || + GR_IS_GR_WEBGL(fStandard)) { + if (!fFunctions.fBindFramebuffer || + !fFunctions.fBindRenderbuffer || + !fFunctions.fCheckFramebufferStatus || + !fFunctions.fDeleteFramebuffers || + !fFunctions.fDeleteRenderbuffers || + !fFunctions.fFramebufferRenderbuffer || + !fFunctions.fFramebufferTexture2D || + !fFunctions.fGenFramebuffers || + !fFunctions.fGenRenderbuffers || + !fFunctions.fGenerateMipmap || + !fFunctions.fGetFramebufferAttachmentParameteriv || + !fFunctions.fGetRenderbufferParameteriv || + !fFunctions.fRenderbufferStorage) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_ARB_framebuffer_object") || + fExtensions.has("GL_EXT_framebuffer_blit"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_CHROMIUM_framebuffer_multisample") || + fExtensions.has("GL_ANGLE_framebuffer_blit")))) { + if (!fFunctions.fBlitFramebuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_ARB_framebuffer_object") || + fExtensions.has("GL_EXT_framebuffer_multisample"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_CHROMIUM_framebuffer_multisample") || + fExtensions.has("GL_ANGLE_framebuffer_multisample"))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fRenderbufferStorageMultisample) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_CHROMIUM_map_sub")))) { + if (!fFunctions.fMapBufferSubData || + !fFunctions.fMapTexSubImage2D || + !fFunctions.fUnmapBufferSubData || + !fFunctions.fUnmapTexSubImage2D) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_multisampled_render_to_texture") || + fExtensions.has("GL_IMG_multisampled_render_to_texture")))) { + if (!fFunctions.fFramebufferTexture2DMultisample) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_multisampled_render_to_texture")))) { + if (!fFunctions.fRenderbufferStorageMultisampleES2EXT) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_IMG_multisampled_render_to_texture")))) { + if (!fFunctions.fRenderbufferStorageMultisampleES2EXT) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_APPLE_framebuffer_multisample")))) { + if (!fFunctions.fResolveMultisampleFramebuffer || + !fFunctions.fRenderbufferStorageMultisampleES2APPLE) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_OES_mapbuffer")))) { + if (!fFunctions.fMapBuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_OES_mapbuffer")))) { + if (!fFunctions.fUnmapBuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_ARB_map_buffer_range"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_EXT_map_buffer_range")))) { + if (!fFunctions.fFlushMappedBufferRange || + !fFunctions.fMapBufferRange) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + fExtensions.has("GL_EXT_debug_marker"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_debug_marker")))) { + if (!fFunctions.fInsertEventMarker || + !fFunctions.fPopGroupMarker || + !fFunctions.fPushGroupMarker) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_ARB_program_interface_query"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,1))))) { + if (!fFunctions.fGetProgramResourceLocation) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + fExtensions.has("GL_NV_path_rendering"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_CHROMIUM_path_rendering") || + fExtensions.has("GL_NV_path_rendering")))) { + if (!fFunctions.fMatrixLoadIdentity || + !fFunctions.fMatrixLoadf) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + fExtensions.has("GL_NV_path_rendering"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_CHROMIUM_path_rendering") || + fExtensions.has("GL_NV_path_rendering")))) { + if (!fFunctions.fCoverFillPath || + !fFunctions.fCoverFillPathInstanced || + !fFunctions.fCoverStrokePath || + !fFunctions.fCoverStrokePathInstanced || + !fFunctions.fDeletePaths || + !fFunctions.fGenPaths || + !fFunctions.fIsPath || + !fFunctions.fPathCommands || + !fFunctions.fPathParameterf || + !fFunctions.fPathParameteri || + !fFunctions.fPathStencilFunc || + !fFunctions.fStencilFillPath || + !fFunctions.fStencilFillPathInstanced || + !fFunctions.fStencilStrokePath || + !fFunctions.fStencilStrokePathInstanced) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_CHROMIUM_path_rendering")))) { + if (!fFunctions.fBindFragmentInputLocation) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + fExtensions.has("GL_NV_framebuffer_mixed_samples"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_CHROMIUM_framebuffer_mixed_samples") || + fExtensions.has("GL_NV_framebuffer_mixed_samples")))) { + if (!fFunctions.fCoverageModulation) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_KHR_debug"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_KHR_debug")))) { + if (!fFunctions.fDebugMessageCallback || + !fFunctions.fDebugMessageControl || + !fFunctions.fDebugMessageInsert || + !fFunctions.fGetDebugMessageLog || + !fFunctions.fObjectLabel || + !fFunctions.fPopDebugGroup || + !fFunctions.fPushDebugGroup) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_CHROMIUM_bind_uniform_location")))) { + if (!fFunctions.fBindUniformLocation) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + fExtensions.has("GL_EXT_window_rectangles"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + fExtensions.has("GL_EXT_window_rectangles")))) { + if (!fFunctions.fWindowRectangles) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,2)) || + fExtensions.has("GL_ARB_sync"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_APPLE_sync"))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fClientWaitSync || + !fFunctions.fDeleteSync || + !fFunctions.fFenceSync || + !fFunctions.fIsSync || + !fFunctions.fWaitSync) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,2)) || + fExtensions.has("GL_ARB_internalformat_query"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0))))) { + if (!fFunctions.fGetInternalformativ) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,1)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_OES_get_program_binary")))) { + if (!fFunctions.fGetProgramBinary || + !fFunctions.fProgramBinary) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,1)))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0))))) { + if (!fFunctions.fProgramParameteri) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,2)) || + fExtensions.has("GL_ARB_sampler_objects"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fBindSampler || + !fFunctions.fDeleteSamplers || + !fFunctions.fGenSamplers || + !fFunctions.fSamplerParameteri || + !fFunctions.fSamplerParameteriv) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard)) { + if (!fFunctions.fGetQueryObjectiv) { + RETURN_FALSE_INTERFACE; + } + } + + if (GR_IS_GR_GL(fStandard) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)) || + fExtensions.has("GL_EXT_occlusion_query_boolean")))) { +#if GR_TEST_UTILS + if (!fFunctions.fBeginQuery || + !fFunctions.fDeleteQueries || + !fFunctions.fEndQuery || + !fFunctions.fGenQueries || + !fFunctions.fGetQueryObjectuiv || + !fFunctions.fGetQueryiv) { + RETURN_FALSE_INTERFACE; + } +#endif + // all functions were marked optional or test_only + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,3)) || + fExtensions.has("GL_ARB_timer_query") || + fExtensions.has("GL_EXT_timer_query")))) { + if (!fFunctions.fGetQueryObjecti64v || + !fFunctions.fGetQueryObjectui64v) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(3,3)) || + fExtensions.has("GL_ARB_timer_query")))) { + if (!fFunctions.fQueryCounter) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_ARB_invalidate_subdata")))) { + if (!fFunctions.fInvalidateBufferData || + !fFunctions.fInvalidateBufferSubData || + !fFunctions.fInvalidateTexImage || + !fFunctions.fInvalidateTexSubImage) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_ARB_invalidate_subdata"))) || + (GR_IS_GR_GL_ES(fStandard) && ( + (glVer >= GR_GL_VER(3,0)))) || + (GR_IS_GR_WEBGL(fStandard) && ( + (glVer >= GR_GL_VER(2,0))))) { + if (!fFunctions.fInvalidateFramebuffer || + !fFunctions.fInvalidateSubFramebuffer) { + RETURN_FALSE_INTERFACE; + } + } + + if ((GR_IS_GR_GL(fStandard) && ( + (glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_ARB_ES2_compatibility"))) || + GR_IS_GR_GL_ES(fStandard) || + GR_IS_GR_WEBGL(fStandard)) { + if (!fFunctions.fGetShaderPrecisionFormat) { + RETURN_FALSE_INTERFACE; + } + } + + + // End autogenerated content + return true; +} + +#if GR_TEST_UTILS + +void GrGLInterface::abandon() const { + const_cast(this)->fFunctions = GrGLInterface::Functions(); +} + +#endif // GR_TEST_UTILS diff --git a/gfx/skia/skia/src/gpu/gl/GrGLMakeNativeInterface_none.cpp b/gfx/skia/skia/src/gpu/gl/GrGLMakeNativeInterface_none.cpp new file mode 100644 index 0000000000..d4ac8bcfe4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLMakeNativeInterface_none.cpp @@ -0,0 +1,12 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/gl/GrGLInterface.h" + +sk_sp GrGLMakeNativeInterface() { return nullptr; } + +const GrGLInterface* GrGLCreateNativeInterface() { return nullptr; } diff --git a/gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.cpp b/gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.cpp new file mode 100644 index 0000000000..217fdb0a28 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLOpsRenderPass.h" + +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRenderTargetPriv.h" + +void GrGLOpsRenderPass::set(GrRenderTarget* rt, const SkIRect& contentBounds, + GrSurfaceOrigin origin, const LoadAndStoreInfo& colorInfo, + const StencilLoadAndStoreInfo& stencilInfo) { + SkASSERT(fGpu); + SkASSERT(!fRenderTarget); + SkASSERT(fGpu == rt->getContext()->priv().getGpu()); + + this->INHERITED::set(rt, origin); + fContentBounds = contentBounds; + fColorLoadAndStoreInfo = colorInfo; + fStencilLoadAndStoreInfo = stencilInfo; +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.h b/gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.h new file mode 100644 index 0000000000..42702008d3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLOpsRenderPass.h @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLOpsRenderPass_DEFINED +#define GrGLOpsRenderPass_DEFINED + +#include "src/gpu/GrOpsRenderPass.h" + +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLRenderTarget.h" + +class GrGLGpu; +class GrGLRenderTarget; + +class GrGLOpsRenderPass : public GrOpsRenderPass { +/** + * We do not actually buffer up draws or do any work in the this class for GL. Instead commands + * are immediately sent to the gpu to execute. Thus all the commands in this class are simply + * pass through functions to corresponding calls in the GrGLGpu class. + */ +public: + GrGLOpsRenderPass(GrGLGpu* gpu) : fGpu(gpu) {} + + void begin() override { + fGpu->beginCommandBuffer(fRenderTarget, fContentBounds, fOrigin, fColorLoadAndStoreInfo, + fStencilLoadAndStoreInfo); + } + + void end() override { + fGpu->endCommandBuffer(fRenderTarget, fColorLoadAndStoreInfo, fStencilLoadAndStoreInfo); + } + + void insertEventMarker(const char* msg) override { + fGpu->insertEventMarker(msg); + } + + void inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) override { + state->doUpload(upload); + } + + void set(GrRenderTarget*, const SkIRect& contentBounds, GrSurfaceOrigin, + const LoadAndStoreInfo&, const StencilLoadAndStoreInfo&); + + void reset() { + fRenderTarget = nullptr; + } + +private: + GrGpu* gpu() override { return fGpu; } + + void onDraw(const GrProgramInfo& programInfo, const GrMesh mesh[], int meshCount, + const SkRect& bounds) override { + fGpu->draw(fRenderTarget, programInfo, mesh, meshCount); + } + + void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override { + fGpu->clear(clip, color, fRenderTarget, fOrigin); + } + + void onClearStencilClip(const GrFixedClip& clip, bool insideStencilMask) override { + fGpu->clearStencilClip(clip, insideStencilMask, fRenderTarget, fOrigin); + } + + GrGLGpu* fGpu; + SkIRect fContentBounds; + LoadAndStoreInfo fColorLoadAndStoreInfo; + StencilLoadAndStoreInfo fStencilLoadAndStoreInfo; + + typedef GrOpsRenderPass INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/gl/GrGLPath.cpp b/gfx/skia/skia/src/gpu/gl/GrGLPath.cpp new file mode 100644 index 0000000000..e001bf93a1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLPath.cpp @@ -0,0 +1,347 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrStyle.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLPath.h" +#include "src/gpu/gl/GrGLPathRendering.h" + +namespace { +inline GrGLubyte verb_to_gl_path_cmd(SkPath::Verb verb) { + static const GrGLubyte gTable[] = { + GR_GL_MOVE_TO, + GR_GL_LINE_TO, + GR_GL_QUADRATIC_CURVE_TO, + GR_GL_CONIC_CURVE_TO, + GR_GL_CUBIC_CURVE_TO, + GR_GL_CLOSE_PATH, + }; + GR_STATIC_ASSERT(0 == SkPath::kMove_Verb); + GR_STATIC_ASSERT(1 == SkPath::kLine_Verb); + GR_STATIC_ASSERT(2 == SkPath::kQuad_Verb); + GR_STATIC_ASSERT(3 == SkPath::kConic_Verb); + GR_STATIC_ASSERT(4 == SkPath::kCubic_Verb); + GR_STATIC_ASSERT(5 == SkPath::kClose_Verb); + + SkASSERT(verb >= 0 && (size_t)verb < SK_ARRAY_COUNT(gTable)); + return gTable[verb]; +} + +#ifdef SK_DEBUG +inline int num_coords(SkPath::Verb verb) { + static const int gTable[] = { + 2, // move + 2, // line + 4, // quad + 5, // conic + 6, // cubic + 0, // close + }; + GR_STATIC_ASSERT(0 == SkPath::kMove_Verb); + GR_STATIC_ASSERT(1 == SkPath::kLine_Verb); + GR_STATIC_ASSERT(2 == SkPath::kQuad_Verb); + GR_STATIC_ASSERT(3 == SkPath::kConic_Verb); + GR_STATIC_ASSERT(4 == SkPath::kCubic_Verb); + GR_STATIC_ASSERT(5 == SkPath::kClose_Verb); + + SkASSERT(verb >= 0 && (size_t)verb < SK_ARRAY_COUNT(gTable)); + return gTable[verb]; +} +#endif + +inline GrGLenum join_to_gl_join(SkPaint::Join join) { + static GrGLenum gSkJoinsToGrGLJoins[] = { + GR_GL_MITER_REVERT, + GR_GL_ROUND, + GR_GL_BEVEL + }; + return gSkJoinsToGrGLJoins[join]; + GR_STATIC_ASSERT(0 == SkPaint::kMiter_Join); + GR_STATIC_ASSERT(1 == SkPaint::kRound_Join); + GR_STATIC_ASSERT(2 == SkPaint::kBevel_Join); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gSkJoinsToGrGLJoins) == SkPaint::kJoinCount); +} + +inline GrGLenum cap_to_gl_cap(SkPaint::Cap cap) { + static GrGLenum gSkCapsToGrGLCaps[] = { + GR_GL_FLAT, + GR_GL_ROUND, + GR_GL_SQUARE + }; + return gSkCapsToGrGLCaps[cap]; + GR_STATIC_ASSERT(0 == SkPaint::kButt_Cap); + GR_STATIC_ASSERT(1 == SkPaint::kRound_Cap); + GR_STATIC_ASSERT(2 == SkPaint::kSquare_Cap); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gSkCapsToGrGLCaps) == SkPaint::kCapCount); +} + +#ifdef SK_DEBUG +inline void verify_floats(const float* floats, int count) { + for (int i = 0; i < count; ++i) { + SkASSERT(!SkScalarIsNaN(SkFloatToScalar(floats[i]))); + } +} +#endif + +inline void points_to_coords(const SkPoint points[], size_t first_point, size_t amount, + GrGLfloat coords[]) { + for (size_t i = 0; i < amount; ++i) { + coords[i * 2] = SkScalarToFloat(points[first_point + i].fX); + coords[i * 2 + 1] = SkScalarToFloat(points[first_point + i].fY); + } +} + +template +inline bool init_path_object_for_general_path(GrGLGpu* gpu, GrGLuint pathID, + const SkPath& skPath) { + SkDEBUGCODE(int numCoords = 0); + int verbCnt = skPath.countVerbs(); + int pointCnt = skPath.countPoints(); + int minCoordCnt = pointCnt * 2; + + SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt); + SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt); + bool lastVerbWasMove = true; // A path with just "close;" means "moveto(0,0); close;" + SkPoint points[4]; + SkPath::RawIter iter(skPath); + SkPath::Verb verb; + while ((verb = iter.next(points)) != SkPath::kDone_Verb) { + pathCommands.push_back(verb_to_gl_path_cmd(verb)); + GrGLfloat coords[6]; + int coordsForVerb; + switch (verb) { + case SkPath::kMove_Verb: + if (checkForDegenerates) { + lastVerbWasMove = true; + } + points_to_coords(points, 0, 1, coords); + coordsForVerb = 2; + break; + case SkPath::kLine_Verb: + if (checkForDegenerates) { + if (SkPath::IsLineDegenerate(points[0], points[1], true)) { + return false; + } + lastVerbWasMove = false; + } + + points_to_coords(points, 1, 1, coords); + coordsForVerb = 2; + break; + case SkPath::kConic_Verb: + if (checkForDegenerates) { + if (SkPath::IsQuadDegenerate(points[0], points[1], points[2], true)) { + return false; + } + lastVerbWasMove = false; + } + points_to_coords(points, 1, 2, coords); + coords[4] = SkScalarToFloat(iter.conicWeight()); + coordsForVerb = 5; + break; + case SkPath::kQuad_Verb: + if (checkForDegenerates) { + if (SkPath::IsQuadDegenerate(points[0], points[1], points[2], true)) { + return false; + } + lastVerbWasMove = false; + } + points_to_coords(points, 1, 2, coords); + coordsForVerb = 4; + break; + case SkPath::kCubic_Verb: + if (checkForDegenerates) { + if (SkPath::IsCubicDegenerate(points[0], points[1], points[2], points[3], + true)) { + return false; + } + lastVerbWasMove = false; + } + points_to_coords(points, 1, 3, coords); + coordsForVerb = 6; + break; + case SkPath::kClose_Verb: + if (checkForDegenerates) { + if (lastVerbWasMove) { + // Interpret "move(x,y);close;" as "move(x,y);lineto(x,y);close;". + // which produces a degenerate segment. + return false; + } + } + continue; + default: + SkASSERT(false); // Not reached. + continue; + } + SkDEBUGCODE(numCoords += num_coords(verb)); + SkDEBUGCODE(verify_floats(coords, coordsForVerb)); + pathCoords.push_back_n(coordsForVerb, coords); + } + SkASSERT(verbCnt == pathCommands.count()); + SkASSERT(numCoords == pathCoords.count()); + + GR_GL_CALL(gpu->glInterface(), + PathCommands(pathID, pathCommands.count(), pathCommands.begin(), + pathCoords.count(), GR_GL_FLOAT, pathCoords.begin())); + return true; +} + +/* + * For now paths only natively support winding and even odd fill types + */ +static GrPathRendering::FillType convert_skpath_filltype(SkPath::FillType fill) { + switch (fill) { + default: + SK_ABORT("Incomplete Switch\n"); + case SkPath::kWinding_FillType: + case SkPath::kInverseWinding_FillType: + return GrPathRendering::kWinding_FillType; + case SkPath::kEvenOdd_FillType: + case SkPath::kInverseEvenOdd_FillType: + return GrPathRendering::kEvenOdd_FillType; + } +} + +} // namespace + +bool GrGLPath::InitPathObjectPathDataCheckingDegenerates(GrGLGpu* gpu, GrGLuint pathID, + const SkPath& skPath) { + return init_path_object_for_general_path(gpu, pathID, skPath); +} + +void GrGLPath::InitPathObjectPathData(GrGLGpu* gpu, + GrGLuint pathID, + const SkPath& skPath) { + SkASSERT(!skPath.isEmpty()); + +#if 1 // SK_SCALAR_IS_FLOAT + // This branch does type punning, converting SkPoint* to GrGLfloat*. + if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) { + int verbCnt = skPath.countVerbs(); + int pointCnt = skPath.countPoints(); + int coordCnt = pointCnt * 2; + SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt); + SkSTArray<16, GrGLfloat, true> pathCoords(coordCnt); + + static_assert(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, "sk_point_not_two_floats"); + + pathCommands.resize_back(verbCnt); + pathCoords.resize_back(coordCnt); + skPath.getPoints(reinterpret_cast(&pathCoords[0]), pointCnt); + skPath.getVerbs(&pathCommands[0], verbCnt); + + SkDEBUGCODE(int verbCoordCnt = 0); + for (int i = 0; i < verbCnt; ++i) { + SkPath::Verb v = static_cast(pathCommands[i]); + pathCommands[i] = verb_to_gl_path_cmd(v); + SkDEBUGCODE(verbCoordCnt += num_coords(v)); + } + SkASSERT(verbCnt == pathCommands.count()); + SkASSERT(verbCoordCnt == pathCoords.count()); + SkDEBUGCODE(verify_floats(&pathCoords[0], pathCoords.count())); + GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0], + pathCoords.count(), GR_GL_FLOAT, + &pathCoords[0])); + return; + } +#endif + SkAssertResult(init_path_object_for_general_path(gpu, pathID, skPath)); +} + +void GrGLPath::InitPathObjectStroke(GrGLGpu* gpu, GrGLuint pathID, const SkStrokeRec& stroke) { + SkASSERT(!stroke.isHairlineStyle()); + GR_GL_CALL(gpu->glInterface(), + PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth()))); + GR_GL_CALL(gpu->glInterface(), + PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter()))); + GrGLenum join = join_to_gl_join(stroke.getJoin()); + GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join)); + GrGLenum cap = cap_to_gl_cap(stroke.getCap()); + GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap)); + GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f)); +} + +void GrGLPath::InitPathObjectEmptyPath(GrGLGpu* gpu, GrGLuint pathID) { + GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, nullptr, 0, GR_GL_FLOAT, nullptr)); +} + +GrGLPath::GrGLPath(GrGLGpu* gpu, const SkPath& origSkPath, const GrStyle& style) + : INHERITED(gpu, origSkPath, style), + fPathID(gpu->glPathRendering()->genPaths(1)) { + + if (origSkPath.isEmpty()) { + InitPathObjectEmptyPath(gpu, fPathID); + fShouldStroke = false; + fShouldFill = false; + } else { + const SkPath* skPath = &origSkPath; + SkTLazy tmpPath; + SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); + + if (style.pathEffect()) { + // Skia stroking and NVPR stroking differ with respect to dashing + // pattern. + // Convert a dashing (or other path effect) to either a stroke or a fill. + if (style.applyPathEffectToPath(tmpPath.init(), &stroke, *skPath, SK_Scalar1)) { + skPath = tmpPath.get(); + } + } else { + stroke = style.strokeRec(); + } + + bool didInit = false; + if (stroke.needToApply() && stroke.getCap() != SkPaint::kButt_Cap) { + // Skia stroking and NVPR stroking differ with respect to stroking + // end caps of empty subpaths. + // Convert stroke to fill if path contains empty subpaths. + didInit = InitPathObjectPathDataCheckingDegenerates(gpu, fPathID, *skPath); + if (!didInit) { + if (!tmpPath.isValid()) { + tmpPath.init(); + } + SkAssertResult(stroke.applyToPath(tmpPath.get(), *skPath)); + skPath = tmpPath.get(); + stroke.setFillStyle(); + } + } + + if (!didInit) { + InitPathObjectPathData(gpu, fPathID, *skPath); + } + + fShouldStroke = stroke.needToApply(); + fShouldFill = stroke.isFillStyle() || + stroke.getStyle() == SkStrokeRec::kStrokeAndFill_Style; + + fFillType = convert_skpath_filltype(skPath->getFillType()); + fBounds = skPath->getBounds(); + SkScalar radius = stroke.getInflationRadius(); + fBounds.outset(radius, radius); + if (fShouldStroke) { + InitPathObjectStroke(gpu, fPathID, stroke); + } + } + + this->registerWithCache(SkBudgeted::kYes); +} + +void GrGLPath::onRelease() { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + if (0 != fPathID) { + static_cast(this->getGpu())->glPathRendering()->deletePaths(fPathID, 1); + fPathID = 0; + } + + INHERITED::onRelease(); +} + +void GrGLPath::onAbandon() { + fPathID = 0; + + INHERITED::onAbandon(); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLPath.h b/gfx/skia/skia/src/gpu/gl/GrGLPath.h new file mode 100644 index 0000000000..905012057c --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLPath.h @@ -0,0 +1,56 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLPath_DEFINED +#define GrGLPath_DEFINED + +#include "include/gpu/gl/GrGLTypes.h" +#include "src/gpu/GrPath.h" + +class GrGLGpu; +class GrStyle; + +/** + * Currently this represents a path built using GL_NV_path_rendering. If we + * support other GL path extensions then this would have to have a type enum + * and/or be subclassed. + */ + +class GrGLPath : public GrPath { +public: + static bool InitPathObjectPathDataCheckingDegenerates(GrGLGpu*, + GrGLuint pathID, + const SkPath&); + static void InitPathObjectPathData(GrGLGpu*, + GrGLuint pathID, + const SkPath&); + static void InitPathObjectStroke(GrGLGpu*, GrGLuint pathID, const SkStrokeRec&); + + static void InitPathObjectEmptyPath(GrGLGpu*, GrGLuint pathID); + + + GrGLPath(GrGLGpu*, const SkPath&, const GrStyle&); + GrGLuint pathID() const { return fPathID; } + + bool shouldStroke() const { return fShouldStroke; } + bool shouldFill() const { return fShouldFill; } +protected: + void onRelease() override; + void onAbandon() override; + +private: + // TODO: Figure out how to get an approximate size of the path in Gpu memory. + size_t onGpuMemorySize() const override { return 100; } + + GrGLuint fPathID; + bool fShouldStroke; + bool fShouldFill; + + typedef GrPath INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLPathRendering.cpp b/gfx/skia/skia/src/gpu/gl/GrGLPathRendering.cpp new file mode 100644 index 0000000000..abcae33067 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLPathRendering.cpp @@ -0,0 +1,266 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLPathRendering.h" +#include "src/gpu/gl/GrGLUtil.h" + +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/gl/GrGLPath.h" +#include "src/gpu/gl/GrGLPathRendering.h" + +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" + +#define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X) +#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->gpu()->glInterface(), RET, X) + +// Number of paths to allocate per glGenPaths call. The call can be overly slow on command buffer GL +// implementation. The call has a result value, and thus waiting for the call completion is needed. +static const GrGLsizei kPathIDPreallocationAmount = 65536; + +GR_STATIC_ASSERT(0 == GrPathRendering::kNone_PathTransformType); +GR_STATIC_ASSERT(1 == GrPathRendering::kTranslateX_PathTransformType); +GR_STATIC_ASSERT(2 == GrPathRendering::kTranslateY_PathTransformType); +GR_STATIC_ASSERT(3 == GrPathRendering::kTranslate_PathTransformType); +GR_STATIC_ASSERT(4 == GrPathRendering::kAffine_PathTransformType); +GR_STATIC_ASSERT(GrPathRendering::kAffine_PathTransformType == GrPathRendering::kLast_PathTransformType); + +#ifdef SK_DEBUG + +static void verify_floats(const float* floats, int count) { + for (int i = 0; i < count; ++i) { + SkASSERT(!SkScalarIsNaN(SkFloatToScalar(floats[i]))); + } +} +#endif + +static GrGLenum gr_stencil_op_to_gl_path_rendering_fill_mode(GrStencilOp op) { + switch (op) { + default: + SK_ABORT("Unexpected path fill."); + /* fallthrough */ + case GrStencilOp::kIncWrap: + return GR_GL_COUNT_UP; + case GrStencilOp::kInvert: + return GR_GL_INVERT; + } +} + +GrGLPathRendering::GrGLPathRendering(GrGLGpu* gpu) + : GrPathRendering(gpu) + , fPreallocatedPathCount(0) { + const GrGLInterface* glInterface = gpu->glInterface(); + fCaps.bindFragmentInputSupport = (bool)glInterface->fFunctions.fBindFragmentInputLocation; +} + +GrGLPathRendering::~GrGLPathRendering() { + if (fPreallocatedPathCount > 0) { + this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount); + } +} + +void GrGLPathRendering::disconnect(GrGpu::DisconnectType type) { + if (GrGpu::DisconnectType::kCleanup == type) { + this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount); + } + fPreallocatedPathCount = 0; +} + +void GrGLPathRendering::resetContext() { + fHWProjectionMatrixState.invalidate(); + // we don't use the model view matrix. + GL_CALL(MatrixLoadIdentity(GR_GL_PATH_MODELVIEW)); + + fHWPathStencilSettings.invalidate(); +} + +sk_sp GrGLPathRendering::createPath(const SkPath& inPath, const GrStyle& style) { + return sk_make_sp(this->gpu(), inPath, style); +} + +void GrGLPathRendering::onStencilPath(const StencilPathArgs& args, const GrPath* path) { + GrGLGpu* gpu = this->gpu(); + SkASSERT(gpu->caps()->shaderCaps()->pathRenderingSupport()); + gpu->flushColorWrite(false); + + GrGLRenderTarget* rt = static_cast(args.fProxy->peekRenderTarget()); + SkISize size = SkISize::Make(rt->width(), rt->height()); + this->setProjectionMatrix(*args.fViewMatrix, size, args.fProxy->origin()); + gpu->flushScissor(*args.fScissor, rt->width(), rt->height(), args.fProxy->origin()); + gpu->flushHWAAState(rt, args.fUseHWAA); + gpu->flushRenderTarget(rt); + + const GrGLPath* glPath = static_cast(path); + + this->flushPathStencilSettings(*args.fStencil); + + GrGLenum fillMode = + gr_stencil_op_to_gl_path_rendering_fill_mode(fHWPathStencilSettings.frontAndBack().fPassOp); + GrGLint writeMask = fHWPathStencilSettings.frontAndBack().fWriteMask; + + if (glPath->shouldFill()) { + GL_CALL(StencilFillPath(glPath->pathID(), fillMode, writeMask)); + } + if (glPath->shouldStroke()) { + GL_CALL(StencilStrokePath(glPath->pathID(), 0xffff, writeMask)); + } +} + +void GrGLPathRendering::onDrawPath(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + const GrStencilSettings& stencilPassSettings, + const GrPath* path) { + if (!this->gpu()->flushGLState(renderTarget, programInfo, GrPrimitiveType::kPath)) { + return; + } + + const GrGLPath* glPath = static_cast(path); + + this->flushPathStencilSettings(stencilPassSettings); + + GrGLenum fillMode = + gr_stencil_op_to_gl_path_rendering_fill_mode(fHWPathStencilSettings.frontAndBack().fPassOp); + GrGLint writeMask = fHWPathStencilSettings.frontAndBack().fWriteMask; + + if (glPath->shouldStroke()) { + if (glPath->shouldFill()) { + GL_CALL(StencilFillPath(glPath->pathID(), fillMode, writeMask)); + } + GL_CALL(StencilThenCoverStrokePath(glPath->pathID(), 0xffff, writeMask, + GR_GL_BOUNDING_BOX)); + } else { + GL_CALL(StencilThenCoverFillPath(glPath->pathID(), fillMode, writeMask, + GR_GL_BOUNDING_BOX)); + } +} + +void GrGLPathRendering::setProgramPathFragmentInputTransform(GrGLuint program, GrGLint location, + GrGLenum genMode, GrGLint components, + const SkMatrix& matrix) { + float coefficients[3 * 3]; + SkASSERT(components >= 1 && components <= 3); + + coefficients[0] = SkScalarToFloat(matrix[SkMatrix::kMScaleX]); + coefficients[1] = SkScalarToFloat(matrix[SkMatrix::kMSkewX]); + coefficients[2] = SkScalarToFloat(matrix[SkMatrix::kMTransX]); + + if (components >= 2) { + coefficients[3] = SkScalarToFloat(matrix[SkMatrix::kMSkewY]); + coefficients[4] = SkScalarToFloat(matrix[SkMatrix::kMScaleY]); + coefficients[5] = SkScalarToFloat(matrix[SkMatrix::kMTransY]); + } + + if (components >= 3) { + coefficients[6] = SkScalarToFloat(matrix[SkMatrix::kMPersp0]); + coefficients[7] = SkScalarToFloat(matrix[SkMatrix::kMPersp1]); + coefficients[8] = SkScalarToFloat(matrix[SkMatrix::kMPersp2]); + } + SkDEBUGCODE(verify_floats(coefficients, components * 3)); + + GL_CALL(ProgramPathFragmentInputGen(program, location, genMode, components, coefficients)); +} + +void GrGLPathRendering::setProjectionMatrix(const SkMatrix& matrix, + const SkISize& renderTargetSize, + GrSurfaceOrigin renderTargetOrigin) { + + SkASSERT(this->gpu()->glCaps().shaderCaps()->pathRenderingSupport()); + + if (renderTargetOrigin == fHWProjectionMatrixState.fRenderTargetOrigin && + renderTargetSize == fHWProjectionMatrixState.fRenderTargetSize && + matrix.cheapEqualTo(fHWProjectionMatrixState.fViewMatrix)) { + return; + } + + fHWProjectionMatrixState.fViewMatrix = matrix; + fHWProjectionMatrixState.fRenderTargetSize = renderTargetSize; + fHWProjectionMatrixState.fRenderTargetOrigin = renderTargetOrigin; + + float glMatrix[4 * 4]; + fHWProjectionMatrixState.getRTAdjustedGLMatrix<4>(glMatrix); + SkDEBUGCODE(verify_floats(glMatrix, SK_ARRAY_COUNT(glMatrix))); + GL_CALL(MatrixLoadf(GR_GL_PATH_PROJECTION, glMatrix)); +} + +GrGLuint GrGLPathRendering::genPaths(GrGLsizei range) { + SkASSERT(range > 0); + GrGLuint firstID; + if (fPreallocatedPathCount >= range) { + firstID = fFirstPreallocatedPathID; + fPreallocatedPathCount -= range; + fFirstPreallocatedPathID += range; + return firstID; + } + // Allocate range + the amount to fill up preallocation amount. If succeed, either join with + // the existing preallocation range or delete the existing and use the new (potentially partial) + // preallocation range. + GrGLsizei allocAmount = range + (kPathIDPreallocationAmount - fPreallocatedPathCount); + if (allocAmount >= range) { + GL_CALL_RET(firstID, GenPaths(allocAmount)); + + if (firstID != 0) { + if (fPreallocatedPathCount > 0 && + firstID == fFirstPreallocatedPathID + fPreallocatedPathCount) { + firstID = fFirstPreallocatedPathID; + fPreallocatedPathCount += allocAmount - range; + fFirstPreallocatedPathID += range; + return firstID; + } + + if (allocAmount > range) { + if (fPreallocatedPathCount > 0) { + this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount); + } + fFirstPreallocatedPathID = firstID + range; + fPreallocatedPathCount = allocAmount - range; + } + // Special case: if allocAmount == range, we have full preallocated range. + return firstID; + } + } + // Failed to allocate with preallocation. Remove existing preallocation and try to allocate just + // the range. + if (fPreallocatedPathCount > 0) { + this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount); + fPreallocatedPathCount = 0; + } + + GL_CALL_RET(firstID, GenPaths(range)); + if (firstID == 0) { + SkDebugf("Warning: Failed to allocate path\n"); + } + return firstID; +} + +void GrGLPathRendering::deletePaths(GrGLuint path, GrGLsizei range) { + GL_CALL(DeletePaths(path, range)); +} + +void GrGLPathRendering::flushPathStencilSettings(const GrStencilSettings& stencilSettings) { + SkASSERT(!stencilSettings.isTwoSided()); + if (fHWPathStencilSettings != stencilSettings) { + SkASSERT(stencilSettings.isValid()); + // Just the func, ref, and mask is set here. The op and write mask are params to the call + // that draws the path to the SB (glStencilFillPath) + uint16_t ref = stencilSettings.frontAndBack().fRef; + GrStencilTest test = stencilSettings.frontAndBack().fTest; + uint16_t testMask = stencilSettings.frontAndBack().fTestMask; + + if (!fHWPathStencilSettings.isValid() || + ref != fHWPathStencilSettings.frontAndBack().fRef || + test != fHWPathStencilSettings.frontAndBack().fTest || + testMask != fHWPathStencilSettings.frontAndBack().fTestMask) { + GL_CALL(PathStencilFunc(GrToGLStencilFunc(test), ref, testMask)); + } + fHWPathStencilSettings = stencilSettings; + } +} + +inline GrGLGpu* GrGLPathRendering::gpu() { + return static_cast(fGpu); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLPathRendering.h b/gfx/skia/skia/src/gpu/gl/GrGLPathRendering.h new file mode 100644 index 0000000000..14284b4888 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLPathRendering.h @@ -0,0 +1,121 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLPathRendering_DEFINED +#define GrGLPathRendering_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/gl/GrGLTypes.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/glsl/GrGLSLUtil.h" + +class GrGLNameAllocator; +class GrGLGpu; +class GrStyle; + +/** + * This class wraps the NV_path_rendering extension and manages its various + * API versions. If a method is not present in the GrGLInterface of the GrGLGpu + * (because the driver version is old), it tries to provide a backup + * implementation. But if a backup implementation is not practical, it marks the + * method as not supported. + */ +class GrGLPathRendering : public GrPathRendering { +public: + /** + * Create a new GrGLPathRendering object from a given GrGLGpu. + */ + GrGLPathRendering(GrGLGpu* gpu); + ~GrGLPathRendering() override; + + // GrPathRendering implementations. + sk_sp createPath(const SkPath&, const GrStyle&) override; + + /* Called when the 3D context state is unknown. */ + void resetContext(); + + /** + * Called when the context either is about to be lost or is lost. DisconnectType indicates + * whether GPU resources should be cleaned up or abandoned when this is called. + */ + void disconnect(GrGpu::DisconnectType); + + bool shouldBindFragmentInputs() const { + return fCaps.bindFragmentInputSupport; + } + + // Functions for "separable shader" texturing support. + void setProgramPathFragmentInputTransform(GrGLuint program, GrGLint location, + GrGLenum genMode, GrGLint components, + const SkMatrix&); + + /* Sets the projection matrix for path rendering */ + void setProjectionMatrix(const SkMatrix& matrix, + const SkISize& renderTargetSize, + GrSurfaceOrigin renderTargetOrigin); + + GrGLuint genPaths(GrGLsizei range); + GrGLvoid deletePaths(GrGLuint path, GrGLsizei range); + +protected: + void onStencilPath(const StencilPathArgs&, const GrPath*) override; + void onDrawPath(GrRenderTarget*, const GrProgramInfo&, const GrStencilSettings&, + const GrPath*) override; + +private: + /** + * Mark certain functionality as not supported. + */ + struct Caps { + bool bindFragmentInputSupport : 1; + }; + + void flushPathStencilSettings(const GrStencilSettings&); + + struct MatrixState { + SkMatrix fViewMatrix; + SkISize fRenderTargetSize; + GrSurfaceOrigin fRenderTargetOrigin; + + MatrixState() { this->invalidate(); } + void invalidate() { + fViewMatrix = SkMatrix::InvalidMatrix(); + fRenderTargetSize.fWidth = -1; + fRenderTargetSize.fHeight = -1; + fRenderTargetOrigin = (GrSurfaceOrigin) -1; + } + + /** + * Gets a matrix that goes from local coordinates to GL normalized device coords. + */ + template void getRTAdjustedGLMatrix(float* destMatrix) { + SkMatrix combined; + if (kBottomLeft_GrSurfaceOrigin == fRenderTargetOrigin) { + combined.setAll(SkIntToScalar(2) / fRenderTargetSize.fWidth, 0, -SK_Scalar1, + 0, -SkIntToScalar(2) / fRenderTargetSize.fHeight, SK_Scalar1, + 0, 0, 1); + } else { + combined.setAll(SkIntToScalar(2) / fRenderTargetSize.fWidth, 0, -SK_Scalar1, + 0, SkIntToScalar(2) / fRenderTargetSize.fHeight, -SK_Scalar1, + 0, 0, 1); + } + combined.preConcat(fViewMatrix); + GrGLSLGetMatrix(destMatrix, combined); + } + }; + GrGLGpu* gpu(); + + GrGLuint fFirstPreallocatedPathID; + GrGLsizei fPreallocatedPathCount; + MatrixState fHWProjectionMatrixState; + GrStencilSettings fHWPathStencilSettings; + Caps fCaps; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLProgram.cpp b/gfx/skia/skia/src/gpu/gl/GrGLProgram.cpp new file mode 100644 index 0000000000..74cf5392fa --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLProgram.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrPathProcessor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/gl/GrGLBuffer.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLPathRendering.h" +#include "src/gpu/gl/GrGLProgram.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +#define GL_CALL(X) GR_GL_CALL(fGpu->glInterface(), X) +#define GL_CALL_RET(R, X) GR_GL_CALL_RET(fGpu->glInterface(), R, X) + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +GrGLProgram::GrGLProgram( + GrGLGpu* gpu, + const GrGLSLBuiltinUniformHandles& builtinUniforms, + GrGLuint programID, + const UniformInfoArray& uniforms, + const UniformInfoArray& textureSamplers, + const VaryingInfoArray& pathProcVaryings, + std::unique_ptr geometryProcessor, + std::unique_ptr xferProcessor, + std::unique_ptr[]> fragmentProcessors, + int fragmentProcessorCnt, + std::unique_ptr attributes, + int vertexAttributeCnt, + int instanceAttributeCnt, + int vertexStride, + int instanceStride) + : fBuiltinUniformHandles(builtinUniforms) + , fProgramID(programID) + , fPrimitiveProcessor(std::move(geometryProcessor)) + , fXferProcessor(std::move(xferProcessor)) + , fFragmentProcessors(std::move(fragmentProcessors)) + , fFragmentProcessorCnt(fragmentProcessorCnt) + , fAttributes(std::move(attributes)) + , fVertexAttributeCnt(vertexAttributeCnt) + , fInstanceAttributeCnt(instanceAttributeCnt) + , fVertexStride(vertexStride) + , fInstanceStride(instanceStride) + , fGpu(gpu) + , fProgramDataManager(gpu, programID, uniforms, pathProcVaryings) + , fNumTextureSamplers(textureSamplers.count()) { + // Assign texture units to sampler uniforms one time up front. + GL_CALL(UseProgram(fProgramID)); + fProgramDataManager.setSamplerUniforms(textureSamplers, 0); +} + +GrGLProgram::~GrGLProgram() { + if (fProgramID) { + GL_CALL(DeleteProgram(fProgramID)); + } +} + +void GrGLProgram::abandon() { + fProgramID = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrGLProgram::updateUniformsAndTextureBindings(const GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo) { + + this->setRenderTargetState(renderTarget, programInfo.origin(), programInfo.primProc()); + + // we set the textures, and uniforms for installed processors in a generic way, but subclasses + // of GLProgram determine how to set coord transforms + + // We must bind to texture units in the same order in which we set the uniforms in + // GrGLProgramDataManager. That is, we bind textures for processors in this order: + // primProc, fragProcs, XP. + fPrimitiveProcessor->setData(fProgramDataManager, programInfo.primProc(), + GrFragmentProcessor::CoordTransformIter(programInfo.pipeline())); + if (programInfo.hasFixedPrimProcTextures()) { + this->updatePrimitiveProcessorTextureBindings(programInfo.primProc(), + programInfo.fixedPrimProcTextures()); + } + int nextTexSamplerIdx = programInfo.primProc().numTextureSamplers(); + + this->setFragmentData(programInfo.pipeline(), &nextTexSamplerIdx); + + const GrXferProcessor& xp = programInfo.pipeline().getXferProcessor(); + SkIPoint offset; + GrTexture* dstTexture = programInfo.pipeline().peekDstTexture(&offset); + + fXferProcessor->setData(fProgramDataManager, xp, dstTexture, offset); + if (dstTexture) { + fGpu->bindTexture(nextTexSamplerIdx++, GrSamplerState::ClampNearest(), + programInfo.pipeline().dstTextureProxy()->textureSwizzle(), + static_cast(dstTexture)); + } + SkASSERT(nextTexSamplerIdx == fNumTextureSamplers); +} + +void GrGLProgram::updatePrimitiveProcessorTextureBindings(const GrPrimitiveProcessor& primProc, + const GrTextureProxy* const proxies[]) { + for (int i = 0; i < primProc.numTextureSamplers(); ++i) { + auto* tex = static_cast(proxies[i]->peekTexture()); + fGpu->bindTexture(i, primProc.textureSampler(i).samplerState(), + primProc.textureSampler(i).swizzle(), tex); + } +} + +void GrGLProgram::setFragmentData(const GrPipeline& pipeline, int* nextTexSamplerIdx) { + GrFragmentProcessor::Iter iter(pipeline); + GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt); + const GrFragmentProcessor* fp = iter.next(); + GrGLSLFragmentProcessor* glslFP = glslIter.next(); + while (fp && glslFP) { + glslFP->setData(fProgramDataManager, *fp); + for (int i = 0; i < fp->numTextureSamplers(); ++i) { + const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i); + fGpu->bindTexture((*nextTexSamplerIdx)++, sampler.samplerState(), sampler.swizzle(), + static_cast(sampler.peekTexture())); + } + fp = iter.next(); + glslFP = glslIter.next(); + } + SkASSERT(!fp && !glslFP); +} + +void GrGLProgram::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin, + const GrPrimitiveProcessor& primProc) { + // Load the RT size uniforms if they are needed + if (fBuiltinUniformHandles.fRTWidthUni.isValid() && + fRenderTargetState.fRenderTargetSize.fWidth != rt->width()) { + fProgramDataManager.set1f(fBuiltinUniformHandles.fRTWidthUni, SkIntToScalar(rt->width())); + } + if (fBuiltinUniformHandles.fRTHeightUni.isValid() && + fRenderTargetState.fRenderTargetSize.fHeight != rt->height()) { + fProgramDataManager.set1f(fBuiltinUniformHandles.fRTHeightUni, SkIntToScalar(rt->height())); + } + + // set RT adjustment + SkISize size; + size.set(rt->width(), rt->height()); + if (!primProc.isPathRendering()) { + if (fRenderTargetState.fRenderTargetOrigin != origin || + fRenderTargetState.fRenderTargetSize != size) { + fRenderTargetState.fRenderTargetSize = size; + fRenderTargetState.fRenderTargetOrigin = origin; + + float rtAdjustmentVec[4]; + fRenderTargetState.getRTAdjustmentVec(rtAdjustmentVec); + fProgramDataManager.set4fv(fBuiltinUniformHandles.fRTAdjustmentUni, 1, rtAdjustmentVec); + } + } else { + SkASSERT(fGpu->glCaps().shaderCaps()->pathRenderingSupport()); + const GrPathProcessor& pathProc = primProc.cast(); + fGpu->glPathRendering()->setProjectionMatrix(pathProc.viewMatrix(), + size, origin); + } +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLProgram.h b/gfx/skia/skia/src/gpu/gl/GrGLProgram.h new file mode 100644 index 0000000000..cf2e2aab6e --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLProgram.h @@ -0,0 +1,174 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLProgram_DEFINED +#define GrGLProgram_DEFINED + +#include "src/gpu/gl/GrGLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrGLSLFragmentProcessor; +class GrGLSLPrimitiveProcessor; +class GrGLSLXferProcessor; +class GrPipeline; +class GrPrimitiveProcessor; +class GrProgramInfo; +class GrRenderTarget; +class GrTextureProxy; + +/** + * This class manages a GPU program and records per-program information. It also records the vertex + * and instance attribute layouts that are to be used with the program. + */ +class GrGLProgram : public SkRefCnt { +public: + /** + * This class has its own Attribute representation as it does not need the name and we don't + * want to worry about copying the name string to memory with life time of GrGLProgram. + * Additionally, these store the attribute location. + */ + struct Attribute { + GrVertexAttribType fCPUType; + GrSLType fGPUType; + size_t fOffset; + GrGLint fLocation; + }; + + using UniformHandle = GrGLSLProgramDataManager::UniformHandle; + using UniformInfoArray = GrGLProgramDataManager::UniformInfoArray; + using VaryingInfoArray = GrGLProgramDataManager::VaryingInfoArray; + + /** + * The attribute array consists of vertexAttributeCnt + instanceAttributeCnt elements with + * the vertex attributes preceding the instance attributes. + */ + GrGLProgram(GrGLGpu*, + const GrGLSLBuiltinUniformHandles&, + GrGLuint programID, + const UniformInfoArray& uniforms, + const UniformInfoArray& textureSamplers, + const VaryingInfoArray&, // used for NVPR only currently + std::unique_ptr geometryProcessor, + std::unique_ptr xferProcessor, + std::unique_ptr[]> fragmentProcessors, + int fragmentProcessorCnt, + std::unique_ptr, + int vertexAttributeCnt, + int instanceAttributeCnt, + int vertexStride, + int instanceStride); + + ~GrGLProgram(); + + /** + * Call to abandon GL objects owned by this program. + */ + void abandon(); + + /** + * Gets the GL program ID for this program. + */ + GrGLuint programID() const { return fProgramID; } + + /** + * We use the RT's size and origin to adjust from Skia device space to OpenGL normalized device + * space and to make device space positions have the correct origin for processors that require + * them. + */ + struct RenderTargetState { + SkISize fRenderTargetSize; + GrSurfaceOrigin fRenderTargetOrigin; + + RenderTargetState() { this->invalidate(); } + void invalidate() { + fRenderTargetSize.fWidth = -1; + fRenderTargetSize.fHeight = -1; + fRenderTargetOrigin = (GrSurfaceOrigin) -1; + } + + /** + * Gets a float4 that adjusts the position from Skia device coords to GL's normalized device + * coords. Assuming the transformed position, pos, is a homogeneous float3, the vec, v, is + * applied as such: + * pos.x = dot(v.xy, pos.xz) + * pos.y = dot(v.zw, pos.yz) + */ + void getRTAdjustmentVec(float* destVec) { + destVec[0] = 2.f / fRenderTargetSize.fWidth; + destVec[1] = -1.f; + if (kBottomLeft_GrSurfaceOrigin == fRenderTargetOrigin) { + destVec[2] = -2.f / fRenderTargetSize.fHeight; + destVec[3] = 1.f; + } else { + destVec[2] = 2.f / fRenderTargetSize.fHeight; + destVec[3] = -1.f; + } + } + }; + + /** + * This function uploads uniforms, calls each GrGLSL*Processor's setData. It binds all fragment + * processor textures. Primitive process textures can be bound using this function or by + * calling updatePrimitiveProcessorTextureBindings. + * + * It is the caller's responsibility to ensure the program is bound before calling. + */ + void updateUniformsAndTextureBindings(const GrRenderTarget*, const GrProgramInfo&); + + void updatePrimitiveProcessorTextureBindings(const GrPrimitiveProcessor&, + const GrTextureProxy* const[]); + + int vertexStride() const { return fVertexStride; } + int instanceStride() const { return fInstanceStride; } + + int numVertexAttributes() const { return fVertexAttributeCnt; } + const Attribute& vertexAttribute(int i) const { + SkASSERT(i >= 0 && i < fVertexAttributeCnt); + return fAttributes[i]; + } + + int numInstanceAttributes() const { return fInstanceAttributeCnt; } + const Attribute& instanceAttribute(int i) const { + SkASSERT(i >= 0 && i < fInstanceAttributeCnt); + return fAttributes[i + fVertexAttributeCnt]; + } + +private: + // A helper to loop over effects, set the transforms (via subclass) and bind textures + void setFragmentData(const GrPipeline&, int* nextTexSamplerIdx); + + // Helper for setData() that sets the view matrix and loads the render target height uniform + void setRenderTargetState(const GrRenderTarget*, GrSurfaceOrigin, const GrPrimitiveProcessor&); + + // these reflect the current values of uniforms (GL uniform values travel with program) + RenderTargetState fRenderTargetState; + GrGLSLBuiltinUniformHandles fBuiltinUniformHandles; + GrGLuint fProgramID; + + // the installed effects + std::unique_ptr fPrimitiveProcessor; + std::unique_ptr fXferProcessor; + std::unique_ptr[]> fFragmentProcessors; + int fFragmentProcessorCnt; + + std::unique_ptr fAttributes; + int fVertexAttributeCnt; + int fInstanceAttributeCnt; + int fVertexStride; + int fInstanceStride; + + GrGLGpu* fGpu; + GrGLProgramDataManager fProgramDataManager; + + int fNumTextureSamplers; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.cpp b/gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.cpp new file mode 100644 index 0000000000..15d9d36eee --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.cpp @@ -0,0 +1,317 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +#define ASSERT_ARRAY_UPLOAD_IN_BOUNDS(UNI, COUNT) \ + SkASSERT((COUNT) <= (UNI).fArrayCount || \ + (1 == (COUNT) && GrShaderVar::kNonArray == (UNI).fArrayCount)) + +GrGLProgramDataManager::GrGLProgramDataManager(GrGLGpu* gpu, GrGLuint programID, + const UniformInfoArray& uniforms, + const VaryingInfoArray& pathProcVaryings) + : fGpu(gpu) + , fProgramID(programID) { + int count = uniforms.count(); + fUniforms.push_back_n(count); + for (int i = 0; i < count; i++) { + Uniform& uniform = fUniforms[i]; + const UniformInfo& builderUniform = uniforms[i]; + SkASSERT(GrShaderVar::kNonArray == builderUniform.fVariable.getArrayCount() || + builderUniform.fVariable.getArrayCount() > 0); + SkDEBUGCODE( + uniform.fArrayCount = builderUniform.fVariable.getArrayCount(); + uniform.fType = builderUniform.fVariable.getType(); + ) + uniform.fLocation = builderUniform.fLocation; + } + + // NVPR programs have separable varyings + count = pathProcVaryings.count(); + fPathProcVaryings.push_back_n(count); + for (int i = 0; i < count; i++) { + SkASSERT(fGpu->glCaps().shaderCaps()->pathRenderingSupport()); + PathProcVarying& pathProcVarying = fPathProcVaryings[i]; + const VaryingInfo& builderPathProcVarying = pathProcVaryings[i]; + SkASSERT(GrShaderVar::kNonArray == builderPathProcVarying.fVariable.getArrayCount() || + builderPathProcVarying.fVariable.getArrayCount() > 0); + SkDEBUGCODE( + pathProcVarying.fArrayCount = builderPathProcVarying.fVariable.getArrayCount(); + pathProcVarying.fType = builderPathProcVarying.fVariable.getType(); + ) + pathProcVarying.fLocation = builderPathProcVarying.fLocation; + } +} + +void GrGLProgramDataManager::setSamplerUniforms(const UniformInfoArray& samplers, + int startUnit) const { + for (int i = 0; i < samplers.count(); ++i) { + const UniformInfo& sampler = samplers[i]; + SkASSERT(sampler.fVisibility); + if (kUnusedUniform != sampler.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform1i(sampler.fLocation, i + startUnit)); + } + } +} + +void GrGLProgramDataManager::set1i(UniformHandle u, int32_t i) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt_GrSLType || uni.fType == kShort_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform1i(uni.fLocation, i)); + } +} + +void GrGLProgramDataManager::set1iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt_GrSLType || uni.fType == kShort_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform1iv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set1f(UniformHandle u, float v0) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat_GrSLType || uni.fType == kHalf_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform1f(uni.fLocation, v0)); + } +} + +void GrGLProgramDataManager::set1fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat_GrSLType || uni.fType == kHalf_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + // This assert fires in some instances of the two-pt gradient for its VSParams. + // Once the uniform manager is responsible for inserting the duplicate uniform + // arrays in VS and FS driver bug workaround, this can be enabled. + // this->printUni(uni); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform1fv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set2i(UniformHandle u, int32_t i0, int32_t i1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt2_GrSLType || uni.fType == kShort2_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform2i(uni.fLocation, i0, i1)); + } +} + +void GrGLProgramDataManager::set2iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt2_GrSLType || uni.fType == kShort2_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform2iv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set2f(UniformHandle u, float v0, float v1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2_GrSLType || uni.fType == kHalf2_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform2f(uni.fLocation, v0, v1)); + } +} + +void GrGLProgramDataManager::set2fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2_GrSLType || uni.fType == kHalf2_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform2fv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set3i(UniformHandle u, int32_t i0, int32_t i1, int32_t i2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt3_GrSLType || uni.fType == kShort3_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform3i(uni.fLocation, i0, i1, i2)); + } +} + +void GrGLProgramDataManager::set3iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt3_GrSLType || uni.fType == kShort3_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform3iv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set3f(UniformHandle u, float v0, float v1, float v2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat3_GrSLType || uni.fType == kHalf3_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform3f(uni.fLocation, v0, v1, v2)); + } +} + +void GrGLProgramDataManager::set3fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat3_GrSLType || uni.fType == kHalf3_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform3fv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set4i(UniformHandle u, + int32_t i0, + int32_t i1, + int32_t i2, + int32_t i3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt4_GrSLType || uni.fType == kShort4_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform4i(uni.fLocation, i0, i1, i2, i3)); + } +} + +void GrGLProgramDataManager::set4iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt4_GrSLType || uni.fType == kShort4_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform4iv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::set4f(UniformHandle u, + float v0, + float v1, + float v2, + float v3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat4_GrSLType || uni.fType == kHalf4_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform4f(uni.fLocation, v0, v1, v2, v3)); + } +} + +void GrGLProgramDataManager::set4fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat4_GrSLType || uni.fType == kHalf4_GrSLType); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + GR_GL_CALL(fGpu->glInterface(), Uniform4fv(uni.fLocation, arrayCount, v)); + } +} + +void GrGLProgramDataManager::setMatrix2f(UniformHandle u, const float matrix[]) const { + this->setMatrices<2>(u, 1, matrix); +} + +void GrGLProgramDataManager::setMatrix3f(UniformHandle u, const float matrix[]) const { + this->setMatrices<3>(u, 1, matrix); +} + +void GrGLProgramDataManager::setMatrix4f(UniformHandle u, const float matrix[]) const { + this->setMatrices<4>(u, 1, matrix); +} + +void GrGLProgramDataManager::setMatrix2fv(UniformHandle u, int arrayCount, const float m[]) const { + this->setMatrices<2>(u, arrayCount, m); +} + +void GrGLProgramDataManager::setMatrix3fv(UniformHandle u, int arrayCount, const float m[]) const { + this->setMatrices<3>(u, arrayCount, m); +} + +void GrGLProgramDataManager::setMatrix4fv(UniformHandle u, int arrayCount, const float m[]) const { + this->setMatrices<4>(u, arrayCount, m); +} + +template struct set_uniform_matrix; + +template inline void GrGLProgramDataManager::setMatrices(UniformHandle u, + int arrayCount, + const float matrices[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2x2_GrSLType + (N - 2) || + uni.fType == kHalf2x2_GrSLType + (N - 2)); + SkASSERT(arrayCount > 0); + ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, arrayCount); + if (kUnusedUniform != uni.fLocation) { + set_uniform_matrix::set(fGpu->glInterface(), uni.fLocation, arrayCount, matrices); + } +} + +template<> struct set_uniform_matrix<2> { + inline static void set(const GrGLInterface* gli, const GrGLint loc, int cnt, const float m[]) { + GR_GL_CALL(gli, UniformMatrix2fv(loc, cnt, false, m)); + } +}; + +template<> struct set_uniform_matrix<3> { + inline static void set(const GrGLInterface* gli, const GrGLint loc, int cnt, const float m[]) { + GR_GL_CALL(gli, UniformMatrix3fv(loc, cnt, false, m)); + } +}; + +template<> struct set_uniform_matrix<4> { + inline static void set(const GrGLInterface* gli, const GrGLint loc, int cnt, const float m[]) { + GR_GL_CALL(gli, UniformMatrix4fv(loc, cnt, false, m)); + } +}; + +void GrGLProgramDataManager::setPathFragmentInputTransform(VaryingHandle u, + int components, + const SkMatrix& matrix) const { + SkASSERT(fGpu->glCaps().shaderCaps()->pathRenderingSupport()); + const PathProcVarying& fragmentInput = fPathProcVaryings[u.toIndex()]; + + SkASSERT((components == 2 && (fragmentInput.fType == kFloat2_GrSLType || + fragmentInput.fType == kHalf2_GrSLType)) || + (components == 3 && (fragmentInput.fType == kFloat3_GrSLType || + fragmentInput.fType == kHalf3_GrSLType))); + + fGpu->glPathRendering()->setProgramPathFragmentInputTransform(fProgramID, + fragmentInput.fLocation, + GR_GL_OBJECT_LINEAR, + components, + matrix); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.h b/gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.h new file mode 100644 index 0000000000..3a67fe07cd --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLProgramDataManager.h @@ -0,0 +1,117 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLProgramDataManager_DEFINED +#define GrGLProgramDataManager_DEFINED + +#include "include/gpu/gl/GrGLTypes.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" + +#include "include/private/SkTArray.h" + +class GrGLGpu; +class SkMatrix; +class GrGLProgram; + +/** Manages the resources used by a shader program. + * The resources are objects the program uses to communicate with the + * application code. + */ +class GrGLProgramDataManager : public GrGLSLProgramDataManager { +public: + struct UniformInfo { + GrShaderVar fVariable; + uint32_t fVisibility; + GrGLint fLocation; + }; + + struct VaryingInfo { + GrShaderVar fVariable; + GrGLint fLocation; + }; + + // This uses an allocator rather than array so that the GrShaderVars don't move in memory + // after they are inserted. Users of GrGLShaderBuilder get refs to the vars and ptrs to their + // name strings. Otherwise, we'd have to hand out copies. + typedef GrTAllocator UniformInfoArray; + typedef GrTAllocator VaryingInfoArray; + + GrGLProgramDataManager(GrGLGpu*, GrGLuint programID, const UniformInfoArray&, + const VaryingInfoArray&); + + void setSamplerUniforms(const UniformInfoArray& samplers, int startUnit) const; + + /** Functions for uploading uniform values. The varities ending in v can be used to upload to an + * array of uniforms. arrayCount must be <= the array count of the uniform. + */ + void set1i(UniformHandle, int32_t) const override; + void set1iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set1f(UniformHandle, float v0) const override; + void set1fv(UniformHandle, int arrayCount, const float v[]) const override; + void set2i(UniformHandle, int32_t, int32_t) const override; + void set2iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set2f(UniformHandle, float, float) const override; + void set2fv(UniformHandle, int arrayCount, const float v[]) const override; + void set3i(UniformHandle, int32_t, int32_t, int32_t) const override; + void set3iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set3f(UniformHandle, float, float, float) const override; + void set3fv(UniformHandle, int arrayCount, const float v[]) const override; + void set4i(UniformHandle, int32_t, int32_t, int32_t, int32_t) const override; + void set4iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set4f(UniformHandle, float, float, float, float) const override; + void set4fv(UniformHandle, int arrayCount, const float v[]) const override; + // matrices are column-major, the first three upload a single matrix, the latter three upload + // arrayCount matrices into a uniform array. + void setMatrix2f(UniformHandle, const float matrix[]) const override; + void setMatrix3f(UniformHandle, const float matrix[]) const override; + void setMatrix4f(UniformHandle, const float matrix[]) const override; + void setMatrix2fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix3fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix4fv(UniformHandle, int arrayCount, const float matrices[]) const override; + + // for nvpr only + void setPathFragmentInputTransform(VaryingHandle u, int components, + const SkMatrix& matrix) const override; + +private: + enum { + kUnusedUniform = -1, + }; + + struct Uniform { + GrGLint fLocation; +#ifdef SK_DEBUG + GrSLType fType; + int fArrayCount; +#endif + }; + + enum { + kUnusedPathProcVarying = -1, + }; + struct PathProcVarying { + GrGLint fLocation; + SkDEBUGCODE( + GrSLType fType; + int fArrayCount; + ); + }; + + template inline void setMatrices(UniformHandle, int arrayCount, + const float matrices[]) const; + + SkTArray fUniforms; + SkTArray fPathProcVaryings; + GrGLGpu* fGpu; + GrGLuint fProgramID; + + typedef GrGLSLProgramDataManager INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.cpp b/gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.cpp new file mode 100644 index 0000000000..0442945f07 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTraceMemoryDump.h" +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLRenderTarget.h" +#include "src/gpu/gl/GrGLUtil.h" + +#define GPUGL static_cast(this->getGpu()) +#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X) + +// Because this class is virtually derived from GrSurface we must explicitly call its constructor. +// Constructor for wrapped render targets. +GrGLRenderTarget::GrGLRenderTarget(GrGLGpu* gpu, + const SkISize& size, + GrGLFormat format, + GrPixelConfig config, + int sampleCount, + const IDs& ids, + GrGLStencilAttachment* stencil) + : GrSurface(gpu, size, config, GrProtected::kNo) + , INHERITED(gpu, size, config, sampleCount, GrProtected::kNo, stencil) { + this->setFlags(gpu->glCaps(), ids); + this->init(format, ids); + this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +GrGLRenderTarget::GrGLRenderTarget(GrGLGpu* gpu, + const SkISize& size, + GrGLFormat format, + GrPixelConfig config, + int sampleCount, + const IDs& ids) + : GrSurface(gpu, size, config, GrProtected::kNo) + , INHERITED(gpu, size, config, sampleCount, GrProtected::kNo) { + this->setFlags(gpu->glCaps(), ids); + this->init(format, ids); +} + +inline void GrGLRenderTarget::setFlags(const GrGLCaps& glCaps, const IDs& idDesc) { + if (!idDesc.fRTFBOID) { + this->setGLRTFBOIDIs0(); + } +} + +void GrGLRenderTarget::init(GrGLFormat format, const IDs& idDesc) { + fRTFBOID = idDesc.fRTFBOID; + fTexFBOID = idDesc.fTexFBOID; + fMSColorRenderbufferID = idDesc.fMSColorRenderbufferID; + fRTFBOOwnership = idDesc.fRTFBOOwnership; + fRTFormat = format; + fNumSamplesOwnedPerPixel = this->totalSamples(); +} + +sk_sp GrGLRenderTarget::MakeWrapped(GrGLGpu* gpu, + const SkISize& size, + GrGLFormat format, + GrPixelConfig config, + int sampleCount, + const IDs& idDesc, + int stencilBits) { + GrGLStencilAttachment* sb = nullptr; + if (stencilBits) { + GrGLStencilAttachment::IDDesc sbDesc; + GrGLStencilAttachment::Format format; + format.fInternalFormat = GrGLStencilAttachment::kUnknownInternalFormat; + format.fPacked = false; + format.fStencilBits = stencilBits; + format.fTotalBits = stencilBits; + // Ownership of sb is passed to the GrRenderTarget so doesn't need to be deleted + sb = new GrGLStencilAttachment(gpu, sbDesc, size.width(), size.height(), sampleCount, + format); + } + return sk_sp( + new GrGLRenderTarget(gpu, size, format, config, sampleCount, idDesc, sb)); +} + +GrBackendRenderTarget GrGLRenderTarget::getBackendRenderTarget() const { + GrGLFramebufferInfo fbi; + fbi.fFBOID = fRTFBOID; + fbi.fFormat = GrGLFormatToEnum(this->format()); + int numStencilBits = 0; + if (GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment()) { + numStencilBits = stencil->bits(); + } + + return GrBackendRenderTarget( + this->width(), this->height(), this->numSamples(), numStencilBits, fbi); +} + +GrBackendFormat GrGLRenderTarget::backendFormat() const { + // We should never have a GrGLRenderTarget (even a textureable one with a target that is not + // texture 2D. + return GrBackendFormat::MakeGL(GrGLFormatToEnum(fRTFormat), GR_GL_TEXTURE_2D); +} + +size_t GrGLRenderTarget::onGpuMemorySize() const { + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + fNumSamplesOwnedPerPixel, GrMipMapped::kNo); +} + +bool GrGLRenderTarget::completeStencilAttachment() { + GrGLGpu* gpu = this->getGLGpu(); + const GrGLInterface* interface = gpu->glInterface(); + GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment(); + if (nullptr == stencil) { + GR_GL_CALL(interface, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + GR_GL_CALL(interface, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); +#ifdef SK_DEBUG + if (kChromium_GrGLDriver != gpu->glContext().driver()) { + // This check can cause problems in Chromium if the context has been asynchronously + // abandoned (see skbug.com/5200) + GrGLenum status; + GR_GL_CALL_RET(interface, status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER)); + SkASSERT(GR_GL_FRAMEBUFFER_COMPLETE == status); + } +#endif + return true; + } else { + const GrGLStencilAttachment* glStencil = static_cast(stencil); + GrGLuint rb = glStencil->renderbufferID(); + + gpu->invalidateBoundRenderTarget(); + gpu->bindFramebuffer(GR_GL_FRAMEBUFFER, this->renderFBOID()); + GR_GL_CALL(interface, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_STENCIL_ATTACHMENT, + GR_GL_RENDERBUFFER, rb)); + if (glStencil->format().fPacked) { + GR_GL_CALL(interface, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, rb)); + } else { + GR_GL_CALL(interface, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER, + GR_GL_DEPTH_ATTACHMENT, + GR_GL_RENDERBUFFER, 0)); + } + + +#ifdef SK_DEBUG + if (kChromium_GrGLDriver != gpu->glContext().driver()) { + // This check can cause problems in Chromium if the context has been asynchronously + // abandoned (see skbug.com/5200) + GrGLenum status; + GR_GL_CALL_RET(interface, status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER)); + SkASSERT(GR_GL_FRAMEBUFFER_COMPLETE == status); + } +#endif + return true; + } +} + +void GrGLRenderTarget::onRelease() { + if (GrBackendObjectOwnership::kBorrowed != fRTFBOOwnership) { + GrGLGpu* gpu = this->getGLGpu(); + if (fTexFBOID) { + gpu->deleteFramebuffer(fTexFBOID); + } + if (fRTFBOID && fRTFBOID != fTexFBOID) { + gpu->deleteFramebuffer(fRTFBOID); + } + if (fMSColorRenderbufferID) { + GL_CALL(DeleteRenderbuffers(1, &fMSColorRenderbufferID)); + } + } + fRTFBOID = 0; + fTexFBOID = 0; + fMSColorRenderbufferID = 0; + INHERITED::onRelease(); +} + +void GrGLRenderTarget::onAbandon() { + fRTFBOID = 0; + fTexFBOID = 0; + fMSColorRenderbufferID = 0; + INHERITED::onAbandon(); +} + +GrGLGpu* GrGLRenderTarget::getGLGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +bool GrGLRenderTarget::canAttemptStencilAttachment() const { + if (this->getGpu()->getContext()->priv().caps()->avoidStencilBuffers()) { + return false; + } + + // Only modify the FBO's attachments if we have created the FBO. Public APIs do not currently + // allow for borrowed FBO ownership, so we can safely assume that if an object is owned, + // Skia created it. + return this->fRTFBOOwnership == GrBackendObjectOwnership::kOwned; +} + +void GrGLRenderTarget::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + // Don't check this->fRefsWrappedObjects, as we might be the base of a GrGLTextureRenderTarget + // which is multiply inherited from both ourselves and a texture. In these cases, one part + // (texture, rt) may be wrapped, while the other is owned by Skia. + bool refsWrappedRenderTargetObjects = + this->fRTFBOOwnership == GrBackendObjectOwnership::kBorrowed; + if (refsWrappedRenderTargetObjects && !traceMemoryDump->shouldDumpWrappedObjects()) { + return; + } + + // Don't log the framebuffer, as the framebuffer itself doesn't contribute to meaningful + // memory usage. It is always a wrapper around either: + // - a texture, which is owned elsewhere, and will be dumped there + // - a renderbuffer, which will be dumped below. + + // Log any renderbuffer's contribution to memory. + if (fMSColorRenderbufferID) { + const GrCaps& caps = *this->getGpu()->caps(); + size_t size = GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), + this->height(), this->msaaSamples(), GrMipMapped::kNo); + + // Due to this resource having both a texture and a renderbuffer component, dump as + // skia/gpu_resources/resource_#/renderbuffer + SkString resourceName = this->getResourceName(); + resourceName.append("/renderbuffer"); + + this->dumpMemoryStatisticsPriv(traceMemoryDump, resourceName, "RenderTarget", size); + + SkString renderbuffer_id; + renderbuffer_id.appendU32(fMSColorRenderbufferID); + traceMemoryDump->setMemoryBacking(resourceName.c_str(), "gl_renderbuffer", + renderbuffer_id.c_str()); + } +} + +int GrGLRenderTarget::msaaSamples() const { + if (fTexFBOID == kUnresolvableFBOID || fTexFBOID != fRTFBOID) { + // If the render target's FBO is external (fTexFBOID == kUnresolvableFBOID), or if we own + // the render target's FBO (fTexFBOID == fRTFBOID) then we use the provided sample count. + return this->numSamples(); + } + + // When fTexFBOID == fRTFBOID, we either are not using MSAA, or MSAA is auto resolving, so use + // 0 for the sample count. + return 0; +} + +int GrGLRenderTarget::totalSamples() const { + int total_samples = this->msaaSamples(); + + if (fTexFBOID != kUnresolvableFBOID) { + // If we own the resolve buffer then that is one more sample per pixel. + total_samples += 1; + } + + return total_samples; +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.h b/gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.h new file mode 100644 index 0000000000..5349e536db --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLRenderTarget.h @@ -0,0 +1,114 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLRenderTarget_DEFINED +#define GrGLRenderTarget_DEFINED + +#include "include/core/SkScalar.h" +#include "include/gpu/GrBackendSurface.h" +#include "src/gpu/GrRenderTarget.h" + +class GrGLCaps; +class GrGLGpu; +class GrGLStencilAttachment; + +class GrGLRenderTarget : public GrRenderTarget { +public: + bool alwaysClearStencil() const override { return 0 == fRTFBOID; } + + // set fTexFBOID to this value to indicate that it is multisampled but + // Gr doesn't know how to resolve it. + enum { kUnresolvableFBOID = 0 }; + + struct IDs { + GrGLuint fRTFBOID; + GrBackendObjectOwnership fRTFBOOwnership; + GrGLuint fTexFBOID; + GrGLuint fMSColorRenderbufferID; + }; + + static sk_sp MakeWrapped(GrGLGpu*, + const SkISize&, + GrGLFormat, + GrPixelConfig, + int sampleCount, + const IDs&, + int stencilBits); + + // The following two functions return the same ID when a texture/render target is not + // multisampled, and different IDs when it is multisampled. + // FBO ID used to render into + GrGLuint renderFBOID() const { return fRTFBOID; } + // FBO ID that has texture ID attached. + GrGLuint textureFBOID() const { return fTexFBOID; } + + GrBackendRenderTarget getBackendRenderTarget() const override; + + GrBackendFormat backendFormat() const override; + + bool canAttemptStencilAttachment() const override; + + // GrGLRenderTarget overrides dumpMemoryStatistics so it can log its texture and renderbuffer + // components seperately. + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override; + + GrGLFormat format() const { return fRTFormat; } + +protected: + // Constructor for subclasses. + GrGLRenderTarget(GrGLGpu*, + const SkISize&, + GrGLFormat, + GrPixelConfig, + int sampleCount, + const IDs&); + + void init(GrGLFormat, const IDs&); + + void onAbandon() override; + void onRelease() override; + + int numSamplesOwnedPerPixel() const { return fNumSamplesOwnedPerPixel; } + +private: + // Constructor for instances wrapping backend objects. + GrGLRenderTarget(GrGLGpu*, + const SkISize&, + GrGLFormat, + GrPixelConfig, + int sampleCount, + const IDs&, + GrGLStencilAttachment*); + + void setFlags(const GrGLCaps&, const IDs&); + + GrGLGpu* getGLGpu() const; + bool completeStencilAttachment() override; + + size_t onGpuMemorySize() const override; + + int msaaSamples() const; + // The number total number of samples, including both MSAA and resolve texture samples. + int totalSamples() const; + + GrGLuint fRTFBOID; + GrGLuint fTexFBOID; + GrGLuint fMSColorRenderbufferID; + GrGLFormat fRTFormat; + + GrBackendObjectOwnership fRTFBOOwnership; + + // The RenderTarget needs to be able to report its VRAM footprint even after abandon and + // release have potentially zeroed out the IDs (e.g., so the cache can reset itself). Since + // the IDs are just required for the computation in totalSamples we cache that result here. + int fNumSamplesOwnedPerPixel; + + typedef GrRenderTarget INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLSemaphore.cpp b/gfx/skia/skia/src/gpu/gl/GrGLSemaphore.cpp new file mode 100644 index 0000000000..d3a224ee1b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLSemaphore.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLSemaphore.h" + +#include "src/gpu/gl/GrGLGpu.h" + +GrGLSemaphore::GrGLSemaphore(GrGLGpu* gpu, bool isOwned) + : INHERITED(gpu), fSync(0), fIsOwned(isOwned) { + isOwned ? this->registerWithCache(SkBudgeted::kNo) + : this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +void GrGLSemaphore::onRelease() { + if (fSync && fIsOwned) { + static_cast(this->getGpu())->deleteSync(fSync); + } + fSync = 0; + INHERITED::onRelease(); +} + +void GrGLSemaphore::onAbandon() { + fSync = 0; + INHERITED::onAbandon(); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLSemaphore.h b/gfx/skia/skia/src/gpu/gl/GrGLSemaphore.h new file mode 100644 index 0000000000..aff3b73248 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLSemaphore.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSemaphore_DEFINED +#define GrGLSemaphore_DEFINED + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrSemaphore.h" + +class GrGLGpu; + +class GrGLSemaphore : public GrSemaphore { +public: + static sk_sp Make(GrGLGpu* gpu, bool isOwned) { + return sk_sp(new GrGLSemaphore(gpu, isOwned)); + } + + static sk_sp MakeWrapped(GrGLGpu* gpu, + GrGLsync sync, + GrWrapOwnership ownership) { + auto sema = sk_sp(new GrGLSemaphore(gpu, + kBorrow_GrWrapOwnership != ownership)); + sema->setSync(sync); + return sema; + } + + GrGLsync sync() const { return fSync; } + void setSync(const GrGLsync& sync) { fSync = sync; } + + GrBackendSemaphore backendSemaphore() const override { + GrBackendSemaphore backendSemaphore; + backendSemaphore.initGL(fSync); + return backendSemaphore; + } + +private: + GrGLSemaphore(GrGLGpu* gpu, bool isOwned); + + void onRelease() override; + void onAbandon() override; + + GrGLsync fSync; + bool fIsOwned; + + typedef GrSemaphore INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.cpp b/gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.cpp new file mode 100644 index 0000000000..4b749c6f3e --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkTraceMemoryDump.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLStencilAttachment.h" + +size_t GrGLStencilAttachment::onGpuMemorySize() const { + uint64_t size = this->width(); + size *= this->height(); + size *= fFormat.fTotalBits; + size *= this->numSamples(); + return static_cast(size / 8); +} + +void GrGLStencilAttachment::onRelease() { + if (0 != fRenderbufferID) { + GrGLGpu* gpuGL = (GrGLGpu*) this->getGpu(); + const GrGLInterface* gl = gpuGL->glInterface(); + GR_GL_CALL(gl, DeleteRenderbuffers(1, &fRenderbufferID)); + fRenderbufferID = 0; + } + + INHERITED::onRelease(); +} + +void GrGLStencilAttachment::onAbandon() { + fRenderbufferID = 0; + + INHERITED::onAbandon(); +} + +void GrGLStencilAttachment::setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, + const SkString& dumpName) const { + SkString renderbuffer_id; + renderbuffer_id.appendU32(this->renderbufferID()); + traceMemoryDump->setMemoryBacking(dumpName.c_str(), "gl_renderbuffer", + renderbuffer_id.c_str()); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.h b/gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.h new file mode 100644 index 0000000000..5be4954d05 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLStencilAttachment.h @@ -0,0 +1,67 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLStencilAttachment_DEFINED +#define GrGLStencilAttachment_DEFINED + +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/GrStencilAttachment.h" + +class GrGLStencilAttachment : public GrStencilAttachment { +public: + static const GrGLenum kUnknownInternalFormat = ~0U; + static const GrGLuint kUnknownBitCount = ~0U; + struct Format { + GrGLenum fInternalFormat; + GrGLuint fStencilBits; + GrGLuint fTotalBits; + bool fPacked; + }; + + struct IDDesc { + IDDesc() : fRenderbufferID(0) {} + GrGLuint fRenderbufferID; + }; + + GrGLStencilAttachment(GrGpu* gpu, + const IDDesc& idDesc, + int width, int height, + int sampleCnt, + const Format& format) + : GrStencilAttachment(gpu, width, height, format.fStencilBits, sampleCnt) + , fFormat(format) + , fRenderbufferID(idDesc.fRenderbufferID) { + this->registerWithCache(SkBudgeted::kYes); + } + + GrGLuint renderbufferID() const { + return fRenderbufferID; + } + + const Format& format() const { return fFormat; } + +protected: + // overrides of GrResource + void onRelease() override; + void onAbandon() override; + void setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, + const SkString& dumpName) const override; + +private: + size_t onGpuMemorySize() const override; + + Format fFormat; + // may be zero for external SBs associated with external RTs + // (we don't require the client to give us the id, just tell + // us how many bits of stencil there are). + GrGLuint fRenderbufferID; + + typedef GrStencilAttachment INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLTexture.cpp b/gfx/skia/skia/src/gpu/gl/GrGLTexture.cpp new file mode 100644 index 0000000000..a5a1a3708b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLTexture.cpp @@ -0,0 +1,174 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTraceMemoryDump.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLTexture.h" + +#define GPUGL static_cast(this->getGpu()) +#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X) + +GrTextureType GrGLTexture::TextureTypeFromTarget(GrGLenum target) { + switch (target) { + case GR_GL_TEXTURE_2D: + return GrTextureType::k2D; + case GR_GL_TEXTURE_RECTANGLE: + return GrTextureType::kRectangle; + case GR_GL_TEXTURE_EXTERNAL: + return GrTextureType::kExternal; + } + SK_ABORT("Unexpected texture target"); +} + +static inline GrGLenum target_from_texture_type(GrTextureType type) { + switch (type) { + case GrTextureType::k2D: + return GR_GL_TEXTURE_2D; + case GrTextureType::kRectangle: + return GR_GL_TEXTURE_RECTANGLE; + case GrTextureType::kExternal: + return GR_GL_TEXTURE_EXTERNAL; + default: + SK_ABORT("Unexpected texture target"); + } + SK_ABORT("Unexpected texture type"); +} + +// Because this class is virtually derived from GrSurface we must explicitly call its constructor. +GrGLTexture::GrGLTexture(GrGLGpu* gpu, SkBudgeted budgeted, const Desc& desc, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, desc.fSize, desc.fConfig, GrProtected::kNo) + , INHERITED(gpu, desc.fSize, desc.fConfig, GrProtected::kNo, + TextureTypeFromTarget(desc.fTarget), mipMapsStatus) + , fParameters(sk_make_sp()) { + this->init(desc); + this->registerWithCache(budgeted); + if (GrGLFormatIsCompressed(desc.fFormat)) { + this->setReadOnly(); + } +} + +GrGLTexture::GrGLTexture(GrGLGpu* gpu, const Desc& desc, GrMipMapsStatus mipMapsStatus, + sk_sp parameters, GrWrapCacheable cacheable, + GrIOType ioType) + : GrSurface(gpu, desc.fSize, desc.fConfig, GrProtected::kNo) + , INHERITED(gpu, desc.fSize, desc.fConfig, GrProtected::kNo, + TextureTypeFromTarget(desc.fTarget), mipMapsStatus) + , fParameters(std::move(parameters)) { + SkASSERT(fParameters); + this->init(desc); + this->registerWithCacheWrapped(cacheable); + if (ioType == kRead_GrIOType) { + this->setReadOnly(); + } +} + +GrGLTexture::GrGLTexture(GrGLGpu* gpu, const Desc& desc, sk_sp parameters, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, desc.fSize, desc.fConfig, GrProtected::kNo) + , INHERITED(gpu, desc.fSize, desc.fConfig, GrProtected::kNo, + TextureTypeFromTarget(desc.fTarget), mipMapsStatus) { + SkASSERT(parameters || desc.fOwnership == GrBackendObjectOwnership::kOwned); + fParameters = parameters ? std::move(parameters) : sk_make_sp(); + this->init(desc); +} + +void GrGLTexture::init(const Desc& desc) { + SkASSERT(0 != desc.fID); + SkASSERT(GrGLFormat::kUnknown != desc.fFormat); + fID = desc.fID; + fFormat = desc.fFormat; + fTextureIDOwnership = desc.fOwnership; +} + +GrGLenum GrGLTexture::target() const { + return target_from_texture_type(this->texturePriv().textureType()); +} + +void GrGLTexture::onRelease() { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + if (fID) { + if (GrBackendObjectOwnership::kBorrowed != fTextureIDOwnership) { + GL_CALL(DeleteTextures(1, &fID)); + } + fID = 0; + } + INHERITED::onRelease(); +} + +void GrGLTexture::onAbandon() { + fID = 0; + INHERITED::onAbandon(); +} + +GrBackendTexture GrGLTexture::getBackendTexture() const { + GrGLTextureInfo info; + info.fTarget = target_from_texture_type(this->texturePriv().textureType()); + info.fID = fID; + info.fFormat = GrGLFormatToEnum(fFormat); + return GrBackendTexture(this->width(), this->height(), this->texturePriv().mipMapped(), info, + fParameters); +} + +GrBackendFormat GrGLTexture::backendFormat() const { + return GrBackendFormat::MakeGL(GrGLFormatToEnum(fFormat), + target_from_texture_type(this->texturePriv().textureType())); +} + +sk_sp GrGLTexture::MakeWrapped(GrGLGpu* gpu, + GrMipMapsStatus mipMapsStatus, + const Desc& desc, + sk_sp parameters, + GrWrapCacheable cacheable, + GrIOType ioType) { + return sk_sp( + new GrGLTexture(gpu, desc, mipMapsStatus, std::move(parameters), cacheable, ioType)); +} + +bool GrGLTexture::onStealBackendTexture(GrBackendTexture* backendTexture, + SkImage::BackendTextureReleaseProc* releaseProc) { + *backendTexture = this->getBackendTexture(); + // Set the release proc to a no-op function. GL doesn't require any special cleanup. + *releaseProc = [](GrBackendTexture){}; + + // It's important that we only abandon this texture's objects, not subclass objects such as + // those held by GrGLTextureRenderTarget. Those objects are not being stolen and need to be + // cleaned up by us. + this->GrGLTexture::onAbandon(); + return true; +} + +void GrGLTexture::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + // Don't check this->fRefsWrappedObjects, as we might be the base of a GrGLTextureRenderTarget + // which is multiply inherited from both ourselves and a texture. In these cases, one part + // (texture, rt) may be wrapped, while the other is owned by Skia. + bool refsWrappedTextureObjects = + this->fTextureIDOwnership == GrBackendObjectOwnership::kBorrowed; + if (refsWrappedTextureObjects && !traceMemoryDump->shouldDumpWrappedObjects()) { + return; + } + + // Dump as skia/gpu_resources/resource_#/texture, to avoid conflicts in the + // GrGLTextureRenderTarget case, where multiple things may dump to the same resource. This + // has no downside in the normal case. + SkString resourceName = this->getResourceName(); + resourceName.append("/texture"); + + // As we are only dumping our texture memory (not any additional memory tracked by classes + // which may inherit from us), specifically call GrGLTexture::gpuMemorySize to avoid + // hitting an override. + this->dumpMemoryStatisticsPriv(traceMemoryDump, resourceName, "Texture", + GrGLTexture::gpuMemorySize()); + + SkString texture_id; + texture_id.appendU32(this->textureID()); + traceMemoryDump->setMemoryBacking(resourceName.c_str(), "gl_texture", texture_id.c_str()); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLTexture.h b/gfx/skia/skia/src/gpu/gl/GrGLTexture.h new file mode 100644 index 0000000000..1474ec7aa1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLTexture.h @@ -0,0 +1,91 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLTexture_DEFINED +#define GrGLTexture_DEFINED + +#include "include/gpu/GrTexture.h" +#include "include/private/GrGLTypesPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/gl/GrGLUtil.h" + +class GrGLGpu; + +class GrGLTexture : public GrTexture { +public: + struct Desc { + SkISize fSize = {-1, -1}; + GrGLenum fTarget = 0; + GrGLuint fID = 0; + GrGLFormat fFormat = GrGLFormat::kUnknown; + GrPixelConfig fConfig = kUnknown_GrPixelConfig; + GrBackendObjectOwnership fOwnership = GrBackendObjectOwnership::kOwned; + }; + + static GrTextureType TextureTypeFromTarget(GrGLenum textureTarget); + + GrGLTexture(GrGLGpu*, SkBudgeted, const Desc&, GrMipMapsStatus); + + ~GrGLTexture() override {} + + GrBackendTexture getBackendTexture() const override; + + GrBackendFormat backendFormat() const override; + + // TODO: Remove once clients are no longer calling this. + void textureParamsModified() override { fParameters->invalidate(); } + + GrGLTextureParameters* parameters() { return fParameters.get(); } + + GrGLuint textureID() const { return fID; } + + GrGLenum target() const; + + GrGLFormat format() const { return fFormat; } + + bool hasBaseLevelBeenBoundToFBO() const { return fBaseLevelHasBeenBoundToFBO; } + void baseLevelWasBoundToFBO() { fBaseLevelHasBeenBoundToFBO = true; } + + static sk_sp MakeWrapped(GrGLGpu*, + GrMipMapsStatus, + const Desc&, + sk_sp, + GrWrapCacheable, GrIOType); + + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override; + +protected: + // Constructor for subclasses. + GrGLTexture(GrGLGpu*, const Desc&, sk_sp, GrMipMapsStatus); + + // Constructor for instances wrapping backend objects. + GrGLTexture(GrGLGpu*, + const Desc&, + GrMipMapsStatus, + sk_sp, + GrWrapCacheable, + GrIOType); + + void init(const Desc&); + + void onAbandon() override; + void onRelease() override; + + bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override; + +private: + sk_sp fParameters; + GrGLuint fID; + GrGLFormat fFormat; + GrBackendObjectOwnership fTextureIDOwnership; + bool fBaseLevelHasBeenBoundToFBO = false; + + typedef GrTexture INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.cpp b/gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.cpp new file mode 100644 index 0000000000..38bc4ef4c7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTraceMemoryDump.h" +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLTextureRenderTarget.h" + +GrGLTextureRenderTarget::GrGLTextureRenderTarget(GrGLGpu* gpu, + SkBudgeted budgeted, + int sampleCount, + const GrGLTexture::Desc& texDesc, + const GrGLRenderTarget::IDs& rtIDs, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, texDesc.fSize, texDesc.fConfig, GrProtected::kNo) + , GrGLTexture(gpu, texDesc, nullptr, mipMapsStatus) + , GrGLRenderTarget(gpu, texDesc.fSize, texDesc.fFormat, texDesc.fConfig, sampleCount, + rtIDs) { + this->registerWithCache(budgeted); +} + +GrGLTextureRenderTarget::GrGLTextureRenderTarget(GrGLGpu* gpu, + int sampleCount, + const GrGLTexture::Desc& texDesc, + sk_sp parameters, + const GrGLRenderTarget::IDs& rtIDs, + GrWrapCacheable cacheable, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, texDesc.fSize, texDesc.fConfig, GrProtected::kNo) + , GrGLTexture(gpu, texDesc, std::move(parameters), mipMapsStatus) + , GrGLRenderTarget(gpu, texDesc.fSize, texDesc.fFormat, texDesc.fConfig, sampleCount, + rtIDs) { + this->registerWithCacheWrapped(cacheable); +} + +void GrGLTextureRenderTarget::dumpMemoryStatistics( + SkTraceMemoryDump* traceMemoryDump) const { +#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK + // Delegate to the base classes + GrGLRenderTarget::dumpMemoryStatistics(traceMemoryDump); + GrGLTexture::dumpMemoryStatistics(traceMemoryDump); +#else + SkString resourceName = this->getResourceName(); + resourceName.append("/texture_renderbuffer"); + this->dumpMemoryStatisticsPriv(traceMemoryDump, resourceName, "RenderTarget", + this->gpuMemorySize()); +#endif +} + +bool GrGLTextureRenderTarget::canAttemptStencilAttachment() const { + // The RT FBO of GrGLTextureRenderTarget is never created from a + // wrapped FBO, so we only care about the flag. + return !this->getGpu()->getContext()->priv().caps()->avoidStencilBuffers(); +} + +sk_sp GrGLTextureRenderTarget::MakeWrapped( + GrGLGpu* gpu, + int sampleCount, + const GrGLTexture::Desc& texDesc, + sk_sp parameters, + const GrGLRenderTarget::IDs& rtIDs, + GrWrapCacheable cacheable, + GrMipMapsStatus mipMapsStatus) { + return sk_sp(new GrGLTextureRenderTarget( + gpu, sampleCount, texDesc, std::move(parameters), rtIDs, cacheable, mipMapsStatus)); +} + +size_t GrGLTextureRenderTarget::onGpuMemorySize() const { + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + this->numSamplesOwnedPerPixel(), this->texturePriv().mipMapped()); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.h b/gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.h new file mode 100644 index 0000000000..96f1fefe8a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLTextureRenderTarget.h @@ -0,0 +1,79 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGLTextureRenderTarget_DEFINED +#define GrGLTextureRenderTarget_DEFINED + +#include "src/gpu/gl/GrGLRenderTarget.h" +#include "src/gpu/gl/GrGLTexture.h" + +class GrGLGpu; + +#ifdef SK_BUILD_FOR_WIN +// Windows gives bogus warnings about inheriting asTexture/asRenderTarget via dominance. +#pragma warning(push) +#pragma warning(disable: 4250) +#endif + +class GrGLTextureRenderTarget : public GrGLTexture, public GrGLRenderTarget { +public: + // We're virtually derived from GrSurface (via both GrGLTexture and GrGLRenderTarget) so its + // constructor must be explicitly called. + GrGLTextureRenderTarget(GrGLGpu* gpu, + SkBudgeted budgeted, + int sampleCount, + const GrGLTexture::Desc& texDesc, + const GrGLRenderTarget::IDs&, + GrMipMapsStatus); + + bool canAttemptStencilAttachment() const override; + + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override; + + static sk_sp MakeWrapped(GrGLGpu* gpu, + int sampleCount, + const GrGLTexture::Desc&, + sk_sp, + const GrGLRenderTarget::IDs&, + GrWrapCacheable, + GrMipMapsStatus); + + GrBackendFormat backendFormat() const override { + // It doesn't matter if we take the texture or render target path, so just pick texture. + return GrGLTexture::backendFormat(); + } + +protected: + void onAbandon() override { + GrGLRenderTarget::onAbandon(); + GrGLTexture::onAbandon(); + } + + void onRelease() override { + GrGLRenderTarget::onRelease(); + GrGLTexture::onRelease(); + } + +private: + // Constructor for instances wrapping backend objects. + GrGLTextureRenderTarget(GrGLGpu* gpu, + int sampleCount, + const GrGLTexture::Desc& texDesc, + sk_sp parameters, + const GrGLRenderTarget::IDs& ids, + GrWrapCacheable, + GrMipMapsStatus); + + size_t onGpuMemorySize() const override; +}; + +#ifdef SK_BUILD_FOR_WIN +#pragma warning(pop) +#endif + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLTypesPriv.cpp b/gfx/skia/skia/src/gpu/gl/GrGLTypesPriv.cpp new file mode 100644 index 0000000000..8c66fa558a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLTypesPriv.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkScalar.h" +#include "include/private/GrGLTypesPriv.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/gl/GrGLDefines.h" + +GrGLTextureParameters::SamplerOverriddenState::SamplerOverriddenState() + // These are the OpenGL defaults. + : fMinFilter(GR_GL_NEAREST_MIPMAP_LINEAR) + , fMagFilter(GR_GL_LINEAR) + , fWrapS(GR_GL_REPEAT) + , fWrapT(GR_GL_REPEAT) + , fMinLOD(-1000.f) + , fMaxLOD(1000.f) + , fBorderColorInvalid(false) {} + +void GrGLTextureParameters::SamplerOverriddenState::invalidate() { + fMinFilter = ~0U; + fMagFilter = ~0U; + fWrapS = ~0U; + fWrapT = ~0U; + fMinLOD = SK_ScalarNaN; + fMaxLOD = SK_ScalarNaN; + fBorderColorInvalid = true; +} + +GrGLTextureParameters::NonsamplerState::NonsamplerState() + // These are the OpenGL defaults. + : fSwizzleKey(GrSwizzle::RGBA().asKey()), fBaseMipMapLevel(0), fMaxMipMapLevel(1000) {} + +void GrGLTextureParameters::NonsamplerState::invalidate() { + fSwizzleKey = ~0U; + fBaseMipMapLevel = ~0; + fMaxMipMapLevel = ~0; +} + +void GrGLTextureParameters::invalidate() { + fSamplerOverriddenState.invalidate(); + fNonsamplerState.invalidate(); +} + +void GrGLTextureParameters::set(const SamplerOverriddenState* samplerState, + const NonsamplerState& nonsamplerState, + ResetTimestamp currTimestamp) { + if (samplerState) { + fSamplerOverriddenState = *samplerState; + } + fNonsamplerState = nonsamplerState; + fResetTimestamp = currTimestamp; +} + +void GrGLBackendTextureInfo::assign(const GrGLBackendTextureInfo& that, bool thisIsValid) { + fInfo = that.fInfo; + SkSafeRef(that.fParams); + if (thisIsValid) { + SkSafeUnref(fParams); + } + fParams = that.fParams; +} + +void GrGLBackendTextureInfo::cleanup() { SkSafeUnref(fParams); } diff --git a/gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.cpp b/gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.cpp new file mode 100644 index 0000000000..56ed244dbf --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLUniformHandler.h" + +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/gl/GrGLCaps.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/builders/GrGLProgramBuilder.h" +#include "src/sksl/SkSLCompiler.h" + +#define GL_CALL(X) GR_GL_CALL(this->glGpu()->glInterface(), X) +#define GL_CALL_RET(R, X) GR_GL_CALL_RET(this->glGpu()->glInterface(), R, X) + +bool valid_name(const char* name) { + // disallow unknown names that start with "sk_" + if (!strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX))) { + return !strcmp(name, SkSL::Compiler::RTADJUST_NAME); + } + return true; +} + +GrGLSLUniformHandler::UniformHandle GrGLUniformHandler::internalAddUniformArray( + uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) { + SkASSERT(name && strlen(name)); + SkASSERT(valid_name(name)); + SkASSERT(0 != visibility); + + UniformInfo& uni = fUniforms.push_back(); + uni.fVariable.setType(type); + uni.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier); + // TODO this is a bit hacky, lets think of a better way. Basically we need to be able to use + // the uniform view matrix name in the GP, and the GP is immutable so it has to tell the PB + // exactly what name it wants to use for the uniform view matrix. If we prefix anythings, then + // the names will mismatch. I think the correct solution is to have all GPs which need the + // uniform view matrix, they should upload the view matrix in their setData along with regular + // uniforms. + char prefix = 'u'; + if ('u' == name[0] || !strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX))) { + prefix = '\0'; + } + fProgramBuilder->nameVariable(uni.fVariable.accessName(), prefix, name, mangleName); + uni.fVariable.setArrayCount(arrayCount); + uni.fVisibility = visibility; + uni.fLocation = -1; + + if (outName) { + *outName = uni.fVariable.c_str(); + } + return GrGLSLUniformHandler::UniformHandle(fUniforms.count() - 1); +} + +GrGLSLUniformHandler::SamplerHandle GrGLUniformHandler::addSampler(const GrTextureProxy* texture, + const GrSamplerState&, + const GrSwizzle& swizzle, + const char* name, + const GrShaderCaps* shaderCaps) { + SkASSERT(name && strlen(name)); + + SkString mangleName; + char prefix = 'u'; + fProgramBuilder->nameVariable(&mangleName, prefix, name, true); + + GrTextureType type = texture->textureType(); + + UniformInfo& sampler = fSamplers.push_back(); + sampler.fVariable.setType(GrSLCombinedSamplerTypeForTextureType(type)); + sampler.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier); + sampler.fVariable.setName(mangleName); + sampler.fLocation = -1; + sampler.fVisibility = kFragment_GrShaderFlag; + if (shaderCaps->textureSwizzleAppliedInShader()) { + fSamplerSwizzles.push_back(swizzle); + SkASSERT(fSamplers.count() == fSamplerSwizzles.count()); + } + return GrGLSLUniformHandler::SamplerHandle(fSamplers.count() - 1); +} + +void GrGLUniformHandler::appendUniformDecls(GrShaderFlags visibility, SkString* out) const { + for (int i = 0; i < fUniforms.count(); ++i) { + if (fUniforms[i].fVisibility & visibility) { + fUniforms[i].fVariable.appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";"); + } + } + for (int i = 0; i < fSamplers.count(); ++i) { + if (fSamplers[i].fVisibility & visibility) { + fSamplers[i].fVariable.appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";\n"); + } + } +} + +void GrGLUniformHandler::bindUniformLocations(GrGLuint programID, const GrGLCaps& caps) { + if (caps.bindUniformLocationSupport()) { + int currUniform = 0; + for (int i = 0; i < fUniforms.count(); ++i, ++currUniform) { + GL_CALL(BindUniformLocation(programID, currUniform, fUniforms[i].fVariable.c_str())); + fUniforms[i].fLocation = currUniform; + } + for (int i = 0; i < fSamplers.count(); ++i, ++currUniform) { + GL_CALL(BindUniformLocation(programID, currUniform, fSamplers[i].fVariable.c_str())); + fSamplers[i].fLocation = currUniform; + } + } +} + +void GrGLUniformHandler::getUniformLocations(GrGLuint programID, const GrGLCaps& caps, bool force) { + if (!caps.bindUniformLocationSupport() || force) { + int count = fUniforms.count(); + for (int i = 0; i < count; ++i) { + GrGLint location; + GL_CALL_RET(location, GetUniformLocation(programID, fUniforms[i].fVariable.c_str())); + fUniforms[i].fLocation = location; + } + for (int i = 0; i < fSamplers.count(); ++i) { + GrGLint location; + GL_CALL_RET(location, GetUniformLocation(programID, fSamplers[i].fVariable.c_str())); + fSamplers[i].fLocation = location; + } + } +} + +const GrGLGpu* GrGLUniformHandler::glGpu() const { + GrGLProgramBuilder* glPB = (GrGLProgramBuilder*) fProgramBuilder; + return glPB->gpu(); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.h b/gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.h new file mode 100644 index 0000000000..f577755e3a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLUniformHandler.h @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLUniformHandler_DEFINED +#define GrGLUniformHandler_DEFINED + +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +#include "src/gpu/gl/GrGLProgramDataManager.h" + +class GrGLCaps; + +class GrGLUniformHandler : public GrGLSLUniformHandler { +public: + static const int kUniformsPerBlock = 8; + + const GrShaderVar& getUniformVariable(UniformHandle u) const override { + return fUniforms[u.toIndex()].fVariable; + } + + const char* getUniformCStr(UniformHandle u) const override { + return this->getUniformVariable(u).c_str(); + } +private: + explicit GrGLUniformHandler(GrGLSLProgramBuilder* program) + : INHERITED(program) + , fUniforms(kUniformsPerBlock) + , fSamplers(kUniformsPerBlock) {} + + UniformHandle internalAddUniformArray(uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) override; + + void updateUniformVisibility(UniformHandle u, uint32_t visibility) override { + fUniforms[u.toIndex()].fVisibility |= visibility; + } + + SamplerHandle addSampler(const GrTextureProxy*, const GrSamplerState&, const GrSwizzle&, + const char* name, const GrShaderCaps*) override; + + const char* samplerVariable(SamplerHandle handle) const override { + return fSamplers[handle.toIndex()].fVariable.c_str(); + } + + GrSwizzle samplerSwizzle(SamplerHandle handle) const override { + return fSamplerSwizzles[handle.toIndex()]; + } + + void appendUniformDecls(GrShaderFlags visibility, SkString*) const override; + + // Manually set uniform locations for all our uniforms. + void bindUniformLocations(GrGLuint programID, const GrGLCaps& caps); + + // Updates the loction of the Uniforms if we cannot bind uniform locations manually + void getUniformLocations(GrGLuint programID, const GrGLCaps& caps, bool force); + + const GrGLGpu* glGpu() const; + + typedef GrGLProgramDataManager::UniformInfo UniformInfo; + typedef GrGLProgramDataManager::UniformInfoArray UniformInfoArray; + + UniformInfoArray fUniforms; + UniformInfoArray fSamplers; + SkTArray fSamplerSwizzles; + + friend class GrGLProgramBuilder; + + typedef GrGLSLUniformHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLUtil.cpp b/gfx/skia/skia/src/gpu/gl/GrGLUtil.cpp new file mode 100644 index 0000000000..9a79f6da38 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLUtil.cpp @@ -0,0 +1,658 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/core/SkMatrix.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/gl/GrGLUtil.h" +#include + +void GrGLClearErr(const GrGLInterface* gl) { + while (GR_GL_NO_ERROR != gl->fFunctions.fGetError()) {} +} + +namespace { +const char *get_error_string(uint32_t err) { + switch (err) { + case GR_GL_NO_ERROR: + return ""; + case GR_GL_INVALID_ENUM: + return "Invalid Enum"; + case GR_GL_INVALID_VALUE: + return "Invalid Value"; + case GR_GL_INVALID_OPERATION: + return "Invalid Operation"; + case GR_GL_OUT_OF_MEMORY: + return "Out of Memory"; + case GR_GL_CONTEXT_LOST: + return "Context Lost"; + } + return "Unknown"; +} +} + +void GrGLCheckErr(const GrGLInterface* gl, + const char* location, + const char* call) { + uint32_t err = GR_GL_GET_ERROR(gl); + if (GR_GL_NO_ERROR != err) { + SkDebugf("---- glGetError 0x%x(%s)", err, get_error_string(err)); + if (location) { + SkDebugf(" at\n\t%s", location); + } + if (call) { + SkDebugf("\n\t\t%s", call); + } + SkDebugf("\n"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#if GR_GL_LOG_CALLS + bool gLogCallsGL = !!(GR_GL_LOG_CALLS_START); +#endif + +#if GR_GL_CHECK_ERROR + bool gCheckErrorGL = !!(GR_GL_CHECK_ERROR_START); +#endif + +/////////////////////////////////////////////////////////////////////////////// + +GrGLStandard GrGLGetStandardInUseFromString(const char* versionString) { + if (nullptr == versionString) { + SkDebugf("nullptr GL version string."); + return kNone_GrGLStandard; + } + + int major, minor; + + // check for desktop + int n = sscanf(versionString, "%d.%d", &major, &minor); + if (2 == n) { + return kGL_GrGLStandard; + } + + // WebGL might look like "OpenGL ES 2.0 (WebGL 1.0 (OpenGL ES 2.0 Chromium))" + int esMajor, esMinor; + n = sscanf(versionString, "OpenGL ES %d.%d (WebGL %d.%d", &esMajor, &esMinor, &major, &minor); + if (4 == n) { + return kWebGL_GrGLStandard; + } + + // check for ES 1 + char profile[2]; + n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1, &major, &minor); + if (4 == n) { + // we no longer support ES1. + return kNone_GrGLStandard; + } + + // check for ES2 + n = sscanf(versionString, "OpenGL ES %d.%d", &major, &minor); + if (2 == n) { + return kGLES_GrGLStandard; + } + return kNone_GrGLStandard; +} + +void GrGLGetDriverInfo(GrGLStandard standard, + GrGLVendor vendor, + const char* rendererString, + const char* versionString, + GrGLDriver* outDriver, + GrGLDriverVersion* outVersion) { + int major, minor, rev, driverMajor, driverMinor, driverPoint; + + *outDriver = kUnknown_GrGLDriver; + *outVersion = GR_GL_DRIVER_UNKNOWN_VER; + // These null checks are for test GL contexts that return nullptr in their + // glGetString implementation. + if (!rendererString) { + rendererString = ""; + } + if (!versionString) { + versionString = ""; + } + + static const char kChromium[] = "Chromium"; + char suffix[SK_ARRAY_COUNT(kChromium)]; + if (0 == strcmp(rendererString, kChromium) || + (3 == sscanf(versionString, "OpenGL ES %d.%d %8s", &major, &minor, suffix) && + 0 == strcmp(kChromium, suffix))) { + *outDriver = kChromium_GrGLDriver; + return; + } + + if (GR_IS_GR_GL(standard)) { + if (kNVIDIA_GrGLVendor == vendor) { + *outDriver = kNVIDIA_GrGLDriver; + int n = sscanf(versionString, "%d.%d.%d NVIDIA %d.%d", + &major, &minor, &rev, &driverMajor, &driverMinor); + // Some older NVIDIA drivers don't report the driver version. + if (5 == n) { + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0); + } + return; + } + int n = sscanf(versionString, "%d.%d Mesa %d.%d", + &major, &minor, &driverMajor, &driverMinor); + if (4 != n) { + n = sscanf(versionString, "%d.%d (Core Profile) Mesa %d.%d", + &major, &minor, &driverMajor, &driverMinor); + } + if (4 == n) { + *outDriver = kMesa_GrGLDriver; + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0); + return; + } + } else if (GR_IS_GR_GL_ES(standard)) { + if (kNVIDIA_GrGLVendor == vendor) { + *outDriver = kNVIDIA_GrGLDriver; + int n = sscanf(versionString, "OpenGL ES %d.%d NVIDIA %d.%d", + &major, &minor, &driverMajor, &driverMinor); + // Some older NVIDIA drivers don't report the driver version. + if (4 == n) { + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0); + } + return; + } + + int n = sscanf(versionString, "OpenGL ES %d.%d Mesa %d.%d", + &major, &minor, &driverMajor, &driverMinor); + if (4 == n) { + *outDriver = kMesa_GrGLDriver; + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0); + return; + } + if (0 == strncmp("ANGLE", rendererString, 5)) { + *outDriver = kANGLE_GrGLDriver; + n = sscanf(versionString, "OpenGL ES %d.%d (ANGLE %d.%d", &major, &minor, &driverMajor, + &driverMinor); + if (4 == n) { + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0); + } + return; + } + } + + if (kGoogle_GrGLVendor == vendor) { + // Swiftshader is the only Google vendor at the moment + *outDriver = kSwiftShader_GrGLDriver; + + // Swiftshader has a strange version string: w.x.y.z Going to arbitrarily ignore + // y and assume w,x and z are major, minor, point. + // As of writing, version is 4.0.0.6 + int n = sscanf(versionString, "OpenGL ES %d.%d SwiftShader %d.%d.0.%d", &major, &minor, + &driverMajor, &driverMinor, &driverPoint); + if (5 == n) { + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, driverPoint); + } + return; + } + + if (kIntel_GrGLVendor == vendor) { + // We presume we're on the Intel driver since it hasn't identified itself as Mesa. + *outDriver = kIntel_GrGLDriver; + + //This is how the macOS version strings are structured. This might be different on different + // OSes. + int n = sscanf(versionString, "%d.%d INTEL-%d.%d.%d", &major, &minor, &driverMajor, + &driverMinor, &driverPoint); + if (5 == n) { + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, driverPoint); + } + } + + if (kQualcomm_GrGLVendor == vendor) { + *outDriver = kQualcomm_GrGLDriver; + int n = sscanf(versionString, "OpenGL ES %d.%d V@%d.%d", &major, &minor, &driverMajor, + &driverMinor); + if (4 == n) { + *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0); + } + return; + } + static constexpr char kEmulatorPrefix[] = "Android Emulator OpenGL ES Translator"; + if (0 == strncmp(kEmulatorPrefix, rendererString, strlen(kEmulatorPrefix))) { + *outDriver = kAndroidEmulator_GrGLDriver; + } +} + +GrGLVersion GrGLGetVersionFromString(const char* versionString) { + if (nullptr == versionString) { + SkDebugf("nullptr GL version string."); + return GR_GL_INVALID_VER; + } + + int major, minor; + + // check for mesa + int mesaMajor, mesaMinor; + int n = sscanf(versionString, "%d.%d Mesa %d.%d", &major, &minor, &mesaMajor, &mesaMinor); + if (4 == n) { + return GR_GL_VER(major, minor); + } + + n = sscanf(versionString, "%d.%d", &major, &minor); + if (2 == n) { + return GR_GL_VER(major, minor); + } + + // WebGL might look like "OpenGL ES 2.0 (WebGL 1.0 (OpenGL ES 2.0 Chromium))" + int esMajor, esMinor; + n = sscanf(versionString, "OpenGL ES %d.%d (WebGL %d.%d", &esMajor, &esMinor, &major, &minor); + if (4 == n) { + return GR_GL_VER(major, minor); + } + + char profile[2]; + n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1, + &major, &minor); + if (4 == n) { + return GR_GL_VER(major, minor); + } + + n = sscanf(versionString, "OpenGL ES %d.%d", &major, &minor); + if (2 == n) { + return GR_GL_VER(major, minor); + } + + return GR_GL_INVALID_VER; +} + +GrGLSLVersion GrGLGetGLSLVersionFromString(const char* versionString) { + if (nullptr == versionString) { + SkDebugf("nullptr GLSL version string."); + return GR_GLSL_INVALID_VER; + } + + int major, minor; + + int n = sscanf(versionString, "%d.%d", &major, &minor); + if (2 == n) { + return GR_GLSL_VER(major, minor); + } + + n = sscanf(versionString, "OpenGL ES GLSL ES %d.%d", &major, &minor); + if (2 == n) { + return GR_GLSL_VER(major, minor); + } + +#ifdef SK_BUILD_FOR_ANDROID + // android hack until the gpu vender updates their drivers + n = sscanf(versionString, "OpenGL ES GLSL %d.%d", &major, &minor); + if (2 == n) { + return GR_GLSL_VER(major, minor); + } +#endif + + return GR_GLSL_INVALID_VER; +} + +GrGLVendor GrGLGetVendorFromString(const char* vendorString) { + if (vendorString) { + if (0 == strcmp(vendorString, "ARM")) { + return kARM_GrGLVendor; + } + if (0 == strcmp(vendorString, "Google Inc.")) { + return kGoogle_GrGLVendor; + } + if (0 == strcmp(vendorString, "Imagination Technologies")) { + return kImagination_GrGLVendor; + } + if (0 == strncmp(vendorString, "Intel ", 6) || 0 == strcmp(vendorString, "Intel")) { + return kIntel_GrGLVendor; + } + if (0 == strcmp(vendorString, "Qualcomm")) { + return kQualcomm_GrGLVendor; + } + if (0 == strcmp(vendorString, "NVIDIA Corporation")) { + return kNVIDIA_GrGLVendor; + } + if (0 == strcmp(vendorString, "ATI Technologies Inc.")) { + return kATI_GrGLVendor; + } + } + return kOther_GrGLVendor; +} + +static bool is_renderer_angle(const char* rendererString) { + static constexpr char kHeader[] = "ANGLE "; + static constexpr size_t kHeaderLength = SK_ARRAY_COUNT(kHeader) - 1; + return rendererString && 0 == strncmp(rendererString, kHeader, kHeaderLength); +} + +GrGLRenderer GrGLGetRendererFromStrings(const char* rendererString, + const GrGLExtensions& extensions) { + if (rendererString) { + static const char kTegraStr[] = "NVIDIA Tegra"; + if (0 == strncmp(rendererString, kTegraStr, SK_ARRAY_COUNT(kTegraStr) - 1)) { + // Tegra strings are not very descriptive. We distinguish between the modern and legacy + // architectures by the presence of NV_path_rendering. + return extensions.has("GL_NV_path_rendering") ? kTegra_GrGLRenderer + : kTegra_PreK1_GrGLRenderer; + } + int lastDigit; + int n = sscanf(rendererString, "PowerVR SGX 54%d", &lastDigit); + if (1 == n && lastDigit >= 0 && lastDigit <= 9) { + return kPowerVR54x_GrGLRenderer; + } + // certain iOS devices also use PowerVR54x GPUs + static const char kAppleA4Str[] = "Apple A4"; + static const char kAppleA5Str[] = "Apple A5"; + static const char kAppleA6Str[] = "Apple A6"; + if (0 == strncmp(rendererString, kAppleA4Str, + SK_ARRAY_COUNT(kAppleA4Str)-1) || + 0 == strncmp(rendererString, kAppleA5Str, + SK_ARRAY_COUNT(kAppleA5Str)-1) || + 0 == strncmp(rendererString, kAppleA6Str, + SK_ARRAY_COUNT(kAppleA6Str)-1)) { + return kPowerVR54x_GrGLRenderer; + } + static const char kPowerVRRogueStr[] = "PowerVR Rogue"; + static const char kAppleA7Str[] = "Apple A7"; + static const char kAppleA8Str[] = "Apple A8"; + if (0 == strncmp(rendererString, kPowerVRRogueStr, + SK_ARRAY_COUNT(kPowerVRRogueStr)-1) || + 0 == strncmp(rendererString, kAppleA7Str, + SK_ARRAY_COUNT(kAppleA7Str)-1) || + 0 == strncmp(rendererString, kAppleA8Str, + SK_ARRAY_COUNT(kAppleA8Str)-1)) { + return kPowerVRRogue_GrGLRenderer; + } + int adrenoNumber; + n = sscanf(rendererString, "Adreno (TM) %d", &adrenoNumber); + if (1 == n) { + if (adrenoNumber >= 300) { + if (adrenoNumber < 400) { + return kAdreno3xx_GrGLRenderer; + } + if (adrenoNumber < 500) { + return adrenoNumber >= 430 + ? kAdreno430_GrGLRenderer : kAdreno4xx_other_GrGLRenderer; + } + if (adrenoNumber < 600) { + return kAdreno5xx_GrGLRenderer; + } + } + } + if (0 == strcmp("Google SwiftShader", rendererString)) { + return kGoogleSwiftShader_GrGLRenderer; + } + + if (const char* intelString = strstr(rendererString, "Intel")) { + // These generic strings seem to always come from Haswell: Iris 5100 or Iris Pro 5200 + if (0 == strcmp("Intel Iris OpenGL Engine", intelString) || + 0 == strcmp("Intel Iris Pro OpenGL Engine", intelString)) { + return kIntelHaswell_GrGLRenderer; + } + if (strstr(intelString, "Sandybridge")) { + return kIntelSandyBridge_GrGLRenderer; + } + if (strstr(intelString, "Bay Trail")) { + return kIntelValleyView_GrGLRenderer; + } + // There are many possible intervening strings here: + // 'Intel(R)' is a common prefix + // 'Iris' may appear, followed by '(R)' or '(TM)' + // 'Iris' can then be followed by 'Graphics', 'Pro Graphics', or 'Plus Graphics' + // If 'Iris' isn't there, we might have 'HD Graphics' or 'UHD Graphics' + // + // In all cases, though, we end with 'Graphics ', an optional 'P', and a number, + // so just skip to that and handle two cases: + if (const char* intelGfxString = strstr(intelString, "Graphics")) { + int intelNumber; + if (sscanf(intelGfxString, "Graphics %d", &intelNumber) || + sscanf(intelGfxString, "Graphics P%d", &intelNumber)) { + + if (intelNumber == 2000 || intelNumber == 3000) { + return kIntelSandyBridge_GrGLRenderer; + } + if (intelNumber == 2500 || intelNumber == 4000) { + return kIntelIvyBridge_GrGLRenderer; + } + if (intelNumber >= 4200 && intelNumber <= 5200) { + return kIntelHaswell_GrGLRenderer; + } + if (intelNumber >= 400 && intelNumber <= 405) { + return kIntelCherryView_GrGLRenderer; + } + if (intelNumber >= 5300 && intelNumber <= 6300) { + return kIntelBroadwell_GrGLRenderer; + } + if (intelNumber >= 500 && intelNumber <= 505) { + return kIntelApolloLake_GrGLRenderer; + } + if (intelNumber >= 510 && intelNumber <= 580) { + return kIntelSkyLake_GrGLRenderer; + } + if (intelNumber >= 600 && intelNumber <= 605) { + return kIntelGeminiLake_GrGLRenderer; + } + // 610 and 630 are reused from KabyLake to CoffeeLake. The CoffeeLake variants + // are "UHD Graphics", while the KabyLake ones are "HD Graphics" + if (intelNumber == 610 || intelNumber == 630) { + return strstr(intelString, "UHD") ? kIntelCoffeeLake_GrGLRenderer + : kIntelKabyLake_GrGLRenderer; + } + if (intelNumber >= 610 && intelNumber <= 650) { + return kIntelKabyLake_GrGLRenderer; + } + if (intelNumber == 655) { + return kIntelCoffeeLake_GrGLRenderer; + } + if (intelNumber >= 910 && intelNumber <= 950) { + return kIntelIceLake_GrGLRenderer; + } + } + } + } + + // The AMD string can have a somewhat arbitrary preamble (see skbug.com/7195) + if (const char* amdString = strstr(rendererString, "Radeon")) { + char amdGeneration, amdTier, amdRevision; + n = sscanf(amdString, "Radeon (TM) R9 M%c%c%c", + &amdGeneration, &amdTier, &amdRevision); + if (3 == n) { + if ('4' == amdGeneration) { + return kAMDRadeonR9M4xx_GrGLRenderer; + } + } + + char amd0, amd1, amd2; + n = sscanf(amdString, "Radeon HD 7%c%c%c Series", &amd0, &amd1, &amd2); + if (3 == n) { + return kAMDRadeonHD7xxx_GrGLRenderer; + } + } + + if (strstr(rendererString, "llvmpipe")) { + return kGalliumLLVM_GrGLRenderer; + } + static const char kMaliTStr[] = "Mali-T"; + if (0 == strncmp(rendererString, kMaliTStr, SK_ARRAY_COUNT(kMaliTStr) - 1)) { + return kMaliT_GrGLRenderer; + } + int mali400Num; + if (1 == sscanf(rendererString, "Mali-%d", &mali400Num) && mali400Num >= 400 && + mali400Num < 500) { + return kMali4xx_GrGLRenderer; + } + if (is_renderer_angle(rendererString)) { + return kANGLE_GrGLRenderer; + } + } + return kOther_GrGLRenderer; +} + +void GrGLGetANGLEInfoFromString(const char* rendererString, GrGLANGLEBackend* backend, + GrGLANGLEVendor* vendor, GrGLANGLERenderer* renderer) { + *backend = GrGLANGLEBackend::kUnknown; + *vendor = GrGLANGLEVendor::kUnknown; + *renderer = GrGLANGLERenderer::kUnknown; + if (!is_renderer_angle(rendererString)) { + return; + } + if (strstr(rendererString, "Intel")) { + *vendor = GrGLANGLEVendor::kIntel; + + const char* modelStr; + int modelNumber; + if ((modelStr = strstr(rendererString, "HD Graphics")) && + (1 == sscanf(modelStr, "HD Graphics %i", &modelNumber) || + 1 == sscanf(modelStr, "HD Graphics P%i", &modelNumber))) { + switch (modelNumber) { + case 2000: + case 3000: + *renderer = GrGLANGLERenderer::kSandyBridge; + break; + case 4000: + case 2500: + *renderer = GrGLANGLERenderer::kIvyBridge; + break; + case 510: + case 515: + case 520: + case 530: + *renderer = GrGLANGLERenderer::kSkylake; + break; + } + } else if ((modelStr = strstr(rendererString, "Iris")) && + (1 == sscanf(modelStr, "Iris(TM) Graphics %i", &modelNumber) || + 1 == sscanf(modelStr, "Iris(TM) Pro Graphics %i", &modelNumber) || + 1 == sscanf(modelStr, "Iris(TM) Pro Graphics P%i", &modelNumber))) { + switch (modelNumber) { + case 540: + case 550: + case 555: + case 580: + *renderer = GrGLANGLERenderer::kSkylake; + break; + } + } + } + if (strstr(rendererString, "Direct3D11")) { + *backend = GrGLANGLEBackend::kD3D11; + } else if (strstr(rendererString, "Direct3D9")) { + *backend = GrGLANGLEBackend::kD3D9; + } else if (strstr(rendererString, "OpenGL")) { + *backend = GrGLANGLEBackend::kOpenGL; + } +} + +GrGLVersion GrGLGetVersion(const GrGLInterface* gl) { + const GrGLubyte* v; + GR_GL_CALL_RET(gl, v, GetString(GR_GL_VERSION)); + return GrGLGetVersionFromString((const char*) v); +} + +GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface* gl) { + const GrGLubyte* v; + GR_GL_CALL_RET(gl, v, GetString(GR_GL_SHADING_LANGUAGE_VERSION)); + return GrGLGetGLSLVersionFromString((const char*) v); +} + +GrGLVendor GrGLGetVendor(const GrGLInterface* gl) { + const GrGLubyte* v; + GR_GL_CALL_RET(gl, v, GetString(GR_GL_VENDOR)); + return GrGLGetVendorFromString((const char*) v); +} + +GrGLRenderer GrGLGetRenderer(const GrGLInterface* gl) { + const GrGLubyte* rendererString; + GR_GL_CALL_RET(gl, rendererString, GetString(GR_GL_RENDERER)); + + return GrGLGetRendererFromStrings((const char*)rendererString, gl->fExtensions); +} + +GrGLenum GrToGLStencilFunc(GrStencilTest test) { + static const GrGLenum gTable[kGrStencilTestCount] = { + GR_GL_ALWAYS, // kAlways + GR_GL_NEVER, // kNever + GR_GL_GREATER, // kGreater + GR_GL_GEQUAL, // kGEqual + GR_GL_LESS, // kLess + GR_GL_LEQUAL, // kLEqual + GR_GL_EQUAL, // kEqual + GR_GL_NOTEQUAL, // kNotEqual + }; + GR_STATIC_ASSERT(0 == (int)GrStencilTest::kAlways); + GR_STATIC_ASSERT(1 == (int)GrStencilTest::kNever); + GR_STATIC_ASSERT(2 == (int)GrStencilTest::kGreater); + GR_STATIC_ASSERT(3 == (int)GrStencilTest::kGEqual); + GR_STATIC_ASSERT(4 == (int)GrStencilTest::kLess); + GR_STATIC_ASSERT(5 == (int)GrStencilTest::kLEqual); + GR_STATIC_ASSERT(6 == (int)GrStencilTest::kEqual); + GR_STATIC_ASSERT(7 == (int)GrStencilTest::kNotEqual); + SkASSERT(test < (GrStencilTest)kGrStencilTestCount); + + return gTable[(int)test]; +} + +bool GrGLFormatIsCompressed(GrGLFormat format) { + switch (format) { + case GrGLFormat::kCOMPRESSED_RGB8_ETC2: + case GrGLFormat::kCOMPRESSED_ETC1_RGB8: + return true; + + case GrGLFormat::kRGBA8: + case GrGLFormat::kR8: + case GrGLFormat::kALPHA8: + case GrGLFormat::kLUMINANCE8: + case GrGLFormat::kBGRA8: + case GrGLFormat::kRGB565: + case GrGLFormat::kRGBA16F: + case GrGLFormat::kR16F: + case GrGLFormat::kLUMINANCE16F: + case GrGLFormat::kRGB8: + case GrGLFormat::kRG8: + case GrGLFormat::kRGB10_A2: + case GrGLFormat::kRGBA4: + case GrGLFormat::kSRGB8_ALPHA8: + case GrGLFormat::kR16: + case GrGLFormat::kRG16: + case GrGLFormat::kRGBA16: + case GrGLFormat::kRG16F: + case GrGLFormat::kUnknown: + return false; + } + SkUNREACHABLE; +} + +bool GrGLFormatToCompressionType(GrGLFormat format, SkImage::CompressionType* compressionType) { + switch (format) { + case GrGLFormat::kCOMPRESSED_RGB8_ETC2: + case GrGLFormat::kCOMPRESSED_ETC1_RGB8: + *compressionType = SkImage::kETC1_CompressionType; + return true; + + case GrGLFormat::kRGBA8: + case GrGLFormat::kR8: + case GrGLFormat::kALPHA8: + case GrGLFormat::kLUMINANCE8: + case GrGLFormat::kBGRA8: + case GrGLFormat::kRGB565: + case GrGLFormat::kRGBA16F: + case GrGLFormat::kR16F: + case GrGLFormat::kLUMINANCE16F: + case GrGLFormat::kRGB8: + case GrGLFormat::kRG8: + case GrGLFormat::kRGB10_A2: + case GrGLFormat::kRGBA4: + case GrGLFormat::kSRGB8_ALPHA8: + case GrGLFormat::kR16: + case GrGLFormat::kRG16: + case GrGLFormat::kRGBA16: + case GrGLFormat::kRG16F: + case GrGLFormat::kUnknown: + return false; + } + SkUNREACHABLE; +} + diff --git a/gfx/skia/skia/src/gpu/gl/GrGLUtil.h b/gfx/skia/skia/src/gpu/gl/GrGLUtil.h new file mode 100644 index 0000000000..35d9ad83bd --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLUtil.h @@ -0,0 +1,377 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLUtil_DEFINED +#define GrGLUtil_DEFINED + +#include "include/gpu/gl/GrGLInterface.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/gl/GrGLDefines.h" + +class SkMatrix; + +//////////////////////////////////////////////////////////////////////////////// + +typedef uint32_t GrGLVersion; +typedef uint32_t GrGLSLVersion; +typedef uint64_t GrGLDriverVersion; + +#define GR_GL_VER(major, minor) ((static_cast(major) << 16) | \ + static_cast(minor)) +#define GR_GLSL_VER(major, minor) ((static_cast(major) << 16) | \ + static_cast(minor)) +#define GR_GL_DRIVER_VER(major, minor, point) ((static_cast(major) << 32) | \ + (static_cast(minor) << 16) | \ + static_cast(point)) + +#define GR_GL_MAJOR_VER(version) (static_cast(version) >> 16) +#define GR_GL_MINOR_VER(version) (static_cast(version) & 0xFFFF) + +#define GR_GL_INVALID_VER GR_GL_VER(0, 0) +#define GR_GLSL_INVALID_VER GR_GLSL_VER(0, 0) +#define GR_GL_DRIVER_UNKNOWN_VER GR_GL_DRIVER_VER(0, 0, 0) + +/** + * The Vendor and Renderer enum values are lazily updated as required. + */ +enum GrGLVendor { + kARM_GrGLVendor, + kGoogle_GrGLVendor, + kImagination_GrGLVendor, + kIntel_GrGLVendor, + kQualcomm_GrGLVendor, + kNVIDIA_GrGLVendor, + kATI_GrGLVendor, + + kOther_GrGLVendor +}; + +enum GrGLRenderer { + kTegra_PreK1_GrGLRenderer, // Legacy Tegra architecture (pre-K1). + kTegra_GrGLRenderer, // Tegra with the same architecture as NVIDIA desktop GPUs (K1+). + kPowerVR54x_GrGLRenderer, + kPowerVRRogue_GrGLRenderer, + kAdreno3xx_GrGLRenderer, + kAdreno430_GrGLRenderer, + kAdreno4xx_other_GrGLRenderer, + kAdreno5xx_GrGLRenderer, + kGoogleSwiftShader_GrGLRenderer, + + /** Intel GPU families, ordered by generation **/ + // 6th gen + kIntelSandyBridge_GrGLRenderer, + + // 7th gen + kIntelIvyBridge_GrGLRenderer, + kIntelValleyView_GrGLRenderer, // aka BayTrail + kIntelHaswell_GrGLRenderer, + + // 8th gen + kIntelCherryView_GrGLRenderer, // aka Braswell + kIntelBroadwell_GrGLRenderer, + + // 9th gen + kIntelApolloLake_GrGLRenderer, + kIntelSkyLake_GrGLRenderer, + kIntelGeminiLake_GrGLRenderer, + kIntelKabyLake_GrGLRenderer, + kIntelCoffeeLake_GrGLRenderer, + + // 11th gen + kIntelIceLake_GrGLRenderer, + + kGalliumLLVM_GrGLRenderer, + kMali4xx_GrGLRenderer, + /** T-6xx, T-7xx, or T-8xx */ + kMaliT_GrGLRenderer, + kANGLE_GrGLRenderer, + + kAMDRadeonHD7xxx_GrGLRenderer, // AMD Radeon HD 7000 Series + kAMDRadeonR9M4xx_GrGLRenderer, // AMD Radeon R9 M400 Series + + kOther_GrGLRenderer +}; + +enum GrGLDriver { + kMesa_GrGLDriver, + kChromium_GrGLDriver, + kNVIDIA_GrGLDriver, + kIntel_GrGLDriver, + kANGLE_GrGLDriver, + kSwiftShader_GrGLDriver, + kQualcomm_GrGLDriver, + kAndroidEmulator_GrGLDriver, + kUnknown_GrGLDriver +}; + +enum class GrGLANGLEBackend { + kUnknown, + kD3D9, + kD3D11, + kOpenGL +}; + +enum class GrGLANGLEVendor { + kUnknown, + kIntel +}; + +enum class GrGLANGLERenderer { + kUnknown, + kSandyBridge, + kIvyBridge, + kSkylake +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Some drivers want the var-int arg to be zero-initialized on input. + */ +#define GR_GL_INIT_ZERO 0 +#define GR_GL_GetIntegerv(gl, e, p) \ + do { \ + *(p) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetIntegerv(e, p)); \ + } while (0) + +#define GR_GL_GetFramebufferAttachmentParameteriv(gl, t, a, pname, p) \ + do { \ + *(p) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetFramebufferAttachmentParameteriv(t, a, pname, p)); \ + } while (0) + +#define GR_GL_GetInternalformativ(gl, t, f, n, s, p) \ + do { \ + *(p) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetInternalformativ(t, f, n, s, p)); \ + } while (0) + +#define GR_GL_GetNamedFramebufferAttachmentParameteriv(gl, fb, a, pname, p) \ + do { \ + *(p) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetNamedFramebufferAttachmentParameteriv(fb, a, pname, p)); \ + } while (0) + +#define GR_GL_GetRenderbufferParameteriv(gl, t, pname, p) \ + do { \ + *(p) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetRenderbufferParameteriv(t, pname, p)); \ + } while (0) + +#define GR_GL_GetTexLevelParameteriv(gl, t, l, pname, p) \ + do { \ + *(p) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetTexLevelParameteriv(t, l, pname, p)); \ + } while (0) + +#define GR_GL_GetShaderPrecisionFormat(gl, st, pt, range, precision) \ + do { \ + (range)[0] = GR_GL_INIT_ZERO; \ + (range)[1] = GR_GL_INIT_ZERO; \ + (*precision) = GR_GL_INIT_ZERO; \ + GR_GL_CALL(gl, GetShaderPrecisionFormat(st, pt, range, precision)); \ + } while (0) + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Helpers for glGetString() + */ + +// these variants assume caller already has a string from glGetString() +GrGLVersion GrGLGetVersionFromString(const char* versionString); +GrGLStandard GrGLGetStandardInUseFromString(const char* versionString); +GrGLSLVersion GrGLGetGLSLVersionFromString(const char* versionString); +GrGLVendor GrGLGetVendorFromString(const char* vendorString); +GrGLRenderer GrGLGetRendererFromStrings(const char* rendererString, const GrGLExtensions&); +void GrGLGetANGLEInfoFromString(const char* rendererString, GrGLANGLEBackend*, + GrGLANGLEVendor*, GrGLANGLERenderer*); + +void GrGLGetDriverInfo(GrGLStandard standard, + GrGLVendor vendor, + const char* rendererString, + const char* versionString, + GrGLDriver* outDriver, + GrGLDriverVersion* outVersion); + +// these variants call glGetString() +GrGLVersion GrGLGetVersion(const GrGLInterface*); +GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface*); +GrGLVendor GrGLGetVendor(const GrGLInterface*); +GrGLRenderer GrGLGetRenderer(const GrGLInterface*); + +/** + * Helpers for glGetError() + */ + +void GrGLCheckErr(const GrGLInterface* gl, + const char* location, + const char* call); + +void GrGLClearErr(const GrGLInterface* gl); + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Macros for using GrGLInterface to make GL calls + */ + +// internal macro to conditionally call glGetError based on compile-time and +// run-time flags. +#if GR_GL_CHECK_ERROR + extern bool gCheckErrorGL; + #define GR_GL_CHECK_ERROR_IMPL(IFACE, X) \ + if (gCheckErrorGL) \ + GrGLCheckErr(IFACE, GR_FILE_AND_LINE_STR, #X) +#else + #define GR_GL_CHECK_ERROR_IMPL(IFACE, X) +#endif + +// internal macro to conditionally log the gl call using SkDebugf based on +// compile-time and run-time flags. +#if GR_GL_LOG_CALLS + extern bool gLogCallsGL; + #define GR_GL_LOG_CALLS_IMPL(X) \ + if (gLogCallsGL) \ + SkDebugf(GR_FILE_AND_LINE_STR "GL: " #X "\n") +#else + #define GR_GL_LOG_CALLS_IMPL(X) +#endif + +// makes a GL call on the interface and does any error checking and logging +#define GR_GL_CALL(IFACE, X) \ + do { \ + GR_GL_CALL_NOERRCHECK(IFACE, X); \ + GR_GL_CHECK_ERROR_IMPL(IFACE, X); \ + } while (false) + +// Variant of above that always skips the error check. This is useful when +// the caller wants to do its own glGetError() call and examine the error value. +#define GR_GL_CALL_NOERRCHECK(IFACE, X) \ + do { \ + (IFACE)->fFunctions.f##X; \ + GR_GL_LOG_CALLS_IMPL(X); \ + } while (false) + +// same as GR_GL_CALL but stores the return value of the gl call in RET +#define GR_GL_CALL_RET(IFACE, RET, X) \ + do { \ + GR_GL_CALL_RET_NOERRCHECK(IFACE, RET, X); \ + GR_GL_CHECK_ERROR_IMPL(IFACE, X); \ + } while (false) + +// same as GR_GL_CALL_RET but always skips the error check. +#define GR_GL_CALL_RET_NOERRCHECK(IFACE, RET, X) \ + do { \ + (RET) = (IFACE)->fFunctions.f##X; \ + GR_GL_LOG_CALLS_IMPL(X); \ + } while (false) + +// call glGetError without doing a redundant error check or logging. +#define GR_GL_GET_ERROR(IFACE) (IFACE)->fFunctions.fGetError() + +static constexpr GrGLFormat GrGLFormatFromGLEnum(GrGLenum glFormat) { + switch (glFormat) { + case GR_GL_RGBA8: return GrGLFormat::kRGBA8; + case GR_GL_R8: return GrGLFormat::kR8; + case GR_GL_ALPHA8: return GrGLFormat::kALPHA8; + case GR_GL_LUMINANCE8: return GrGLFormat::kLUMINANCE8; + case GR_GL_BGRA8: return GrGLFormat::kBGRA8; + case GR_GL_RGB565: return GrGLFormat::kRGB565; + case GR_GL_RGBA16F: return GrGLFormat::kRGBA16F; + case GR_GL_LUMINANCE16F: return GrGLFormat::kLUMINANCE16F; + case GR_GL_R16F: return GrGLFormat::kR16F; + case GR_GL_RGB8: return GrGLFormat::kRGB8; + case GR_GL_RG8: return GrGLFormat::kRG8; + case GR_GL_RGB10_A2: return GrGLFormat::kRGB10_A2; + case GR_GL_RGBA4: return GrGLFormat::kRGBA4; + case GR_GL_SRGB8_ALPHA8: return GrGLFormat::kSRGB8_ALPHA8; + case GR_GL_COMPRESSED_RGB8_ETC2: return GrGLFormat::kCOMPRESSED_RGB8_ETC2; + case GR_GL_COMPRESSED_ETC1_RGB8: return GrGLFormat::kCOMPRESSED_ETC1_RGB8; + case GR_GL_R16: return GrGLFormat::kR16; + case GR_GL_RG16: return GrGLFormat::kRG16; + case GR_GL_RGBA16: return GrGLFormat::kRGBA16; + case GR_GL_RG16F: return GrGLFormat::kRG16F; + + default: return GrGLFormat::kUnknown; + } +} + +/** Returns either the sized internal format or compressed internal format of the GrGLFormat. */ +static constexpr GrGLenum GrGLFormatToEnum(GrGLFormat format) { + switch (format) { + case GrGLFormat::kRGBA8: return GR_GL_RGBA8; + case GrGLFormat::kR8: return GR_GL_R8; + case GrGLFormat::kALPHA8: return GR_GL_ALPHA8; + case GrGLFormat::kLUMINANCE8: return GR_GL_LUMINANCE8; + case GrGLFormat::kBGRA8: return GR_GL_BGRA8; + case GrGLFormat::kRGB565: return GR_GL_RGB565; + case GrGLFormat::kRGBA16F: return GR_GL_RGBA16F; + case GrGLFormat::kLUMINANCE16F: return GR_GL_LUMINANCE16F; + case GrGLFormat::kR16F: return GR_GL_R16F; + case GrGLFormat::kRGB8: return GR_GL_RGB8; + case GrGLFormat::kRG8: return GR_GL_RG8; + case GrGLFormat::kRGB10_A2: return GR_GL_RGB10_A2; + case GrGLFormat::kRGBA4: return GR_GL_RGBA4; + case GrGLFormat::kSRGB8_ALPHA8: return GR_GL_SRGB8_ALPHA8; + case GrGLFormat::kCOMPRESSED_RGB8_ETC2: return GR_GL_COMPRESSED_RGB8_ETC2; + case GrGLFormat::kCOMPRESSED_ETC1_RGB8: return GR_GL_COMPRESSED_ETC1_RGB8; + case GrGLFormat::kR16: return GR_GL_R16; + case GrGLFormat::kRG16: return GR_GL_RG16; + case GrGLFormat::kRGBA16: return GR_GL_RGBA16; + case GrGLFormat::kRG16F: return GR_GL_RG16F; + case GrGLFormat::kUnknown: return 0; + } + SkUNREACHABLE; +} + +#if GR_TEST_UTILS +static constexpr const char* GrGLFormatToStr(GrGLenum glFormat) { + switch (glFormat) { + case GR_GL_RGBA8: return "RGBA8"; + case GR_GL_R8: return "R8"; + case GR_GL_ALPHA8: return "ALPHA8"; + case GR_GL_LUMINANCE8: return "LUMINANCE8"; + case GR_GL_BGRA8: return "BGRA8"; + case GR_GL_RGB565: return "RGB565"; + case GR_GL_RGBA16F: return "RGBA16F"; + case GR_GL_LUMINANCE16F: return "LUMINANCE16F"; + case GR_GL_R16F: return "R16F"; + case GR_GL_RGB8: return "RGB8"; + case GR_GL_RG8: return "RG8"; + case GR_GL_RGB10_A2: return "RGB10_A2"; + case GR_GL_RGBA4: return "RGBA4"; + case GR_GL_RGBA32F: return "RGBA32F"; + case GR_GL_SRGB8_ALPHA8: return "SRGB8_ALPHA8"; + case GR_GL_COMPRESSED_RGB8_ETC2: return "ETC2"; + case GR_GL_COMPRESSED_ETC1_RGB8: return "ETC1"; + case GR_GL_R16: return "R16"; + case GR_GL_RG16: return "RG16"; + case GR_GL_RGBA16: return "RGBA16"; + case GR_GL_RG16F: return "RG16F"; + + default: return "Unknown"; + } +} +#endif + +GrGLenum GrToGLStencilFunc(GrStencilTest test); + +/** + * Returns true if the format is compressed. + */ +bool GrGLFormatIsCompressed(GrGLFormat); + +/** + * Maps a GrGLFormat into the CompressionType enum if appropriate. + */ +bool GrGLFormatToCompressionType(GrGLFormat, SkImage::CompressionType*); + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.cpp b/gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.cpp new file mode 100644 index 0000000000..76ab1285d1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/GrGLVaryingHandler.h" + +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/builders/GrGLProgramBuilder.h" + + +GrGLSLVaryingHandler::VaryingHandle GrGLVaryingHandler::addPathProcessingVarying( + const char* name, + GrGLSLVarying* v) { +#ifdef SK_DEBUG + GrGLProgramBuilder* glPB = (GrGLProgramBuilder*) fProgramBuilder; + // This call is not used for non-NVPR backends. + SkASSERT(glPB->gpu()->glCaps().shaderCaps()->pathRenderingSupport() && + fProgramBuilder->fProgramInfo.isNVPR()); +#endif + this->addVarying(name, v); + auto varyingInfo = fPathProcVaryingInfos.push_back(); + varyingInfo.fLocation = fPathProcVaryingInfos.count() - 1; + return VaryingHandle(varyingInfo.fLocation); +} + +void GrGLVaryingHandler::onFinalize() { + SkASSERT(fPathProcVaryingInfos.empty() || fPathProcVaryingInfos.count() == fFragInputs.count()); + for (int i = 0; i < fPathProcVaryingInfos.count(); ++i) { + fPathProcVaryingInfos[i].fVariable = fFragInputs[i]; + } +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.h b/gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.h new file mode 100644 index 0000000000..da35e66b04 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLVaryingHandler.h @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLVaryingHandler_DEFINED +#define GrGLVaryingHandler_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/gl/GrGLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +class GrGLVaryingHandler : public GrGLSLVaryingHandler { +public: + GrGLVaryingHandler(GrGLSLProgramBuilder* program) + : INHERITED(program), + fPathProcVaryingInfos(kVaryingsPerBlock) {} + + // This function is used by the NVPR PathProcessor to add a varying directly into the fragment + // shader since there is no vertex shader. + VaryingHandle addPathProcessingVarying(const char* name, GrGLSLVarying*); + +private: + void onFinalize() override; + + GrGLProgramDataManager::VaryingInfoArray fPathProcVaryingInfos; + + friend class GrGLProgramBuilder; + + typedef GrGLSLVaryingHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/GrGLVertexArray.cpp b/gfx/skia/skia/src/gpu/gl/GrGLVertexArray.cpp new file mode 100644 index 0000000000..cfac573241 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLVertexArray.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrCpuBuffer.h" +#include "src/gpu/gl/GrGLBuffer.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLVertexArray.h" + +struct AttribLayout { + bool fNormalized; // Only used by floating point types. + uint8_t fCount; + uint16_t fType; +}; + +GR_STATIC_ASSERT(4 == sizeof(AttribLayout)); + +static AttribLayout attrib_layout(GrVertexAttribType type) { + switch (type) { + case kFloat_GrVertexAttribType: + return {false, 1, GR_GL_FLOAT}; + case kFloat2_GrVertexAttribType: + return {false, 2, GR_GL_FLOAT}; + case kFloat3_GrVertexAttribType: + return {false, 3, GR_GL_FLOAT}; + case kFloat4_GrVertexAttribType: + return {false, 4, GR_GL_FLOAT}; + case kHalf_GrVertexAttribType: + return {false, 1, GR_GL_HALF_FLOAT}; + case kHalf2_GrVertexAttribType: + return {false, 2, GR_GL_HALF_FLOAT}; + case kHalf3_GrVertexAttribType: + return {false, 3, GR_GL_HALF_FLOAT}; + case kHalf4_GrVertexAttribType: + return {false, 4, GR_GL_HALF_FLOAT}; + case kInt2_GrVertexAttribType: + return {false, 2, GR_GL_INT}; + case kInt3_GrVertexAttribType: + return {false, 3, GR_GL_INT}; + case kInt4_GrVertexAttribType: + return {false, 4, GR_GL_INT}; + case kByte_GrVertexAttribType: + return {false, 1, GR_GL_BYTE}; + case kByte2_GrVertexAttribType: + return {false, 2, GR_GL_BYTE}; + case kByte3_GrVertexAttribType: + return {false, 3, GR_GL_BYTE}; + case kByte4_GrVertexAttribType: + return {false, 4, GR_GL_BYTE}; + case kUByte_GrVertexAttribType: + return {false, 1, GR_GL_UNSIGNED_BYTE}; + case kUByte2_GrVertexAttribType: + return {false, 2, GR_GL_UNSIGNED_BYTE}; + case kUByte3_GrVertexAttribType: + return {false, 3, GR_GL_UNSIGNED_BYTE}; + case kUByte4_GrVertexAttribType: + return {false, 4, GR_GL_UNSIGNED_BYTE}; + case kUByte_norm_GrVertexAttribType: + return {true, 1, GR_GL_UNSIGNED_BYTE}; + case kUByte4_norm_GrVertexAttribType: + return {true, 4, GR_GL_UNSIGNED_BYTE}; + case kShort2_GrVertexAttribType: + return {false, 2, GR_GL_SHORT}; + case kShort4_GrVertexAttribType: + return {false, 4, GR_GL_SHORT}; + case kUShort2_GrVertexAttribType: + return {false, 2, GR_GL_UNSIGNED_SHORT}; + case kUShort2_norm_GrVertexAttribType: + return {true, 2, GR_GL_UNSIGNED_SHORT}; + case kInt_GrVertexAttribType: + return {false, 1, GR_GL_INT}; + case kUint_GrVertexAttribType: + return {false, 1, GR_GL_UNSIGNED_INT}; + case kUShort_norm_GrVertexAttribType: + return {true, 1, GR_GL_UNSIGNED_SHORT}; + case kUShort4_norm_GrVertexAttribType: + return {true, 4, GR_GL_UNSIGNED_SHORT}; + } + SK_ABORT("Unknown vertex attrib type"); +}; + +void GrGLAttribArrayState::set(GrGLGpu* gpu, + int index, + const GrBuffer* vertexBuffer, + GrVertexAttribType cpuType, + GrSLType gpuType, + GrGLsizei stride, + size_t offsetInBytes, + int divisor) { + SkASSERT(index >= 0 && index < fAttribArrayStates.count()); + SkASSERT(0 == divisor || gpu->caps()->instanceAttribSupport()); + AttribArrayState* array = &fAttribArrayStates[index]; + const char* offsetAsPtr; + bool bufferChanged = false; + if (vertexBuffer->isCpuBuffer()) { + if (!array->fUsingCpuBuffer) { + bufferChanged = true; + array->fUsingCpuBuffer = true; + } + offsetAsPtr = static_cast(vertexBuffer)->data() + offsetInBytes; + } else { + auto gpuBuffer = static_cast(vertexBuffer); + if (array->fUsingCpuBuffer || array->fVertexBufferUniqueID != gpuBuffer->uniqueID()) { + bufferChanged = true; + array->fVertexBufferUniqueID = gpuBuffer->uniqueID(); + } + offsetAsPtr = reinterpret_cast(offsetInBytes); + } + if (bufferChanged || + array->fCPUType != cpuType || + array->fGPUType != gpuType || + array->fStride != stride || + array->fOffset != offsetAsPtr) { + // We always have to call this if we're going to change the array pointer. 'array' is + // tracking the last buffer used to setup attrib pointers, not the last buffer bound. + // GrGLGpu will avoid redundant binds. + gpu->bindBuffer(GrGpuBufferType::kVertex, vertexBuffer); + const AttribLayout& layout = attrib_layout(cpuType); + if (GrSLTypeIsFloatType(gpuType)) { + GR_GL_CALL(gpu->glInterface(), VertexAttribPointer(index, + layout.fCount, + layout.fType, + layout.fNormalized, + stride, + offsetAsPtr)); + } else { + SkASSERT(gpu->caps()->shaderCaps()->integerSupport()); + SkASSERT(!layout.fNormalized); + GR_GL_CALL(gpu->glInterface(), VertexAttribIPointer(index, + layout.fCount, + layout.fType, + stride, + offsetAsPtr)); + } + array->fCPUType = cpuType; + array->fGPUType = gpuType; + array->fStride = stride; + array->fOffset = offsetAsPtr; + } + if (gpu->caps()->instanceAttribSupport() && array->fDivisor != divisor) { + SkASSERT(0 == divisor || 1 == divisor); // not necessarily a requirement but what we expect. + GR_GL_CALL(gpu->glInterface(), VertexAttribDivisor(index, divisor)); + array->fDivisor = divisor; + } +} + +void GrGLAttribArrayState::enableVertexArrays(const GrGLGpu* gpu, int enabledCount, + GrPrimitiveRestart enablePrimitiveRestart) { + SkASSERT(enabledCount <= fAttribArrayStates.count()); + + if (!fEnableStateIsValid || enabledCount != fNumEnabledArrays) { + int firstIdxToEnable = fEnableStateIsValid ? fNumEnabledArrays : 0; + for (int i = firstIdxToEnable; i < enabledCount; ++i) { + GR_GL_CALL(gpu->glInterface(), EnableVertexAttribArray(i)); + } + + int endIdxToDisable = fEnableStateIsValid ? fNumEnabledArrays : fAttribArrayStates.count(); + for (int i = enabledCount; i < endIdxToDisable; ++i) { + GR_GL_CALL(gpu->glInterface(), DisableVertexAttribArray(i)); + } + + fNumEnabledArrays = enabledCount; + } + + SkASSERT(GrPrimitiveRestart::kNo == enablePrimitiveRestart || + gpu->caps()->usePrimitiveRestart()); + + if (gpu->caps()->usePrimitiveRestart() && + (!fEnableStateIsValid || enablePrimitiveRestart != fPrimitiveRestartEnabled)) { + if (GrPrimitiveRestart::kYes == enablePrimitiveRestart) { + GR_GL_CALL(gpu->glInterface(), Enable(GR_GL_PRIMITIVE_RESTART_FIXED_INDEX)); + } else { + GR_GL_CALL(gpu->glInterface(), Disable(GR_GL_PRIMITIVE_RESTART_FIXED_INDEX)); + } + + fPrimitiveRestartEnabled = enablePrimitiveRestart; + } + + fEnableStateIsValid = true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +GrGLVertexArray::GrGLVertexArray(GrGLint id, int attribCount) + : fID(id) + , fAttribArrays(attribCount) + , fIndexBufferUniqueID(SK_InvalidUniqueID) { +} + +GrGLAttribArrayState* GrGLVertexArray::bind(GrGLGpu* gpu) { + if (0 == fID) { + return nullptr; + } + gpu->bindVertexArray(fID); + return &fAttribArrays; +} + +GrGLAttribArrayState* GrGLVertexArray::bindWithIndexBuffer(GrGLGpu* gpu, const GrBuffer* ibuff) { + GrGLAttribArrayState* state = this->bind(gpu); + if (!state) { + return nullptr; + } + if (ibuff->isCpuBuffer()) { + GR_GL_CALL(gpu->glInterface(), BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, 0)); + } else { + const GrGLBuffer* glBuffer = static_cast(ibuff); + if (fIndexBufferUniqueID != glBuffer->uniqueID()) { + const GrGLBuffer* glBuffer = static_cast(ibuff); + GR_GL_CALL(gpu->glInterface(), + BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, glBuffer->bufferID())); + fIndexBufferUniqueID = glBuffer->uniqueID(); + } + } + return state; +} + +void GrGLVertexArray::invalidateCachedState() { + fAttribArrays.invalidate(); + fIndexBufferUniqueID.makeInvalid(); +} diff --git a/gfx/skia/skia/src/gpu/gl/GrGLVertexArray.h b/gfx/skia/skia/src/gpu/gl/GrGLVertexArray.h new file mode 100644 index 0000000000..a253379938 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/GrGLVertexArray.h @@ -0,0 +1,127 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLVertexArray_DEFINED +#define GrGLVertexArray_DEFINED + +#include "include/gpu/GrGpuResource.h" +#include "include/gpu/gl/GrGLTypes.h" +#include "include/private/GrTypesPriv.h" +#include "include/private/SkTArray.h" +#include "src/gpu/gl/GrGLDefines.h" + +class GrBuffer; +class GrGLGpu; + +/** + * This sets and tracks the vertex attribute array state. It is used internally by GrGLVertexArray + * (below) but is separate because it is also used to track the state of vertex array object 0. + */ +class GrGLAttribArrayState { +public: + explicit GrGLAttribArrayState(int arrayCount = 0) { + this->resize(arrayCount); + } + + void resize(int newCount) { + fAttribArrayStates.resize_back(newCount); + this->invalidate(); + } + + /** + * This function enables and sets vertex attrib state for the specified attrib index. It is + * assumed that the GrGLAttribArrayState is tracking the state of the currently bound vertex + * array object. + */ + void set(GrGLGpu*, + int attribIndex, + const GrBuffer* vertexBuffer, + GrVertexAttribType cpuType, + GrSLType gpuType, + GrGLsizei stride, + size_t offsetInBytes, + int divisor = 0); + + /** + * This function enables the first 'enabledCount' vertex arrays and disables the rest. + */ + void enableVertexArrays(const GrGLGpu*, int enabledCount, + GrPrimitiveRestart = GrPrimitiveRestart::kNo); + + void invalidate() { + int count = fAttribArrayStates.count(); + for (int i = 0; i < count; ++i) { + fAttribArrayStates[i].invalidate(); + } + fEnableStateIsValid = false; + } + + /** + * The number of attrib arrays that this object is configured to track. + */ + int count() const { return fAttribArrayStates.count(); } + +private: + static constexpr int kInvalidDivisor = -1; + + /** + * Tracks the state of glVertexAttribArray for an attribute index. + */ + struct AttribArrayState { + void invalidate() { + fVertexBufferUniqueID.makeInvalid(); + fDivisor = kInvalidDivisor; + fUsingCpuBuffer = false; + } + + GrGpuResource::UniqueID fVertexBufferUniqueID; + bool fUsingCpuBuffer; + GrVertexAttribType fCPUType; + GrSLType fGPUType; + GrGLsizei fStride; + const GrGLvoid* fOffset; + int fDivisor; + }; + + SkSTArray<16, AttribArrayState, true> fAttribArrayStates; + int fNumEnabledArrays; + GrPrimitiveRestart fPrimitiveRestartEnabled; + bool fEnableStateIsValid = false; +}; + +/** + * This class represents an OpenGL vertex array object. It manages the lifetime of the vertex array + * and is used to track the state of the vertex array to avoid redundant GL calls. + */ +class GrGLVertexArray { +public: + GrGLVertexArray(GrGLint id, int attribCount); + + /** + * Binds this vertex array. If the ID has been deleted or abandoned then nullptr is returned. + * Otherwise, the GrGLAttribArrayState that is tracking this vertex array's attrib bindings is + * returned. + */ + GrGLAttribArrayState* bind(GrGLGpu*); + + /** + * This is a version of the above function that also binds an index buffer to the vertex + * array object. + */ + GrGLAttribArrayState* bindWithIndexBuffer(GrGLGpu* gpu, const GrBuffer* indexBuffer); + + GrGLuint arrayID() const { return fID; } + + void invalidateCachedState(); + +private: + GrGLuint fID; + GrGLAttribArrayState fAttribArrays; + GrGpuResource::UniqueID fIndexBufferUniqueID; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/android/GrGLMakeNativeInterface_android.cpp b/gfx/skia/skia/src/gpu/gl/android/GrGLMakeNativeInterface_android.cpp new file mode 100644 index 0000000000..5e5d4c2ebe --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/android/GrGLMakeNativeInterface_android.cpp @@ -0,0 +1,7 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "../egl/GrGLMakeNativeInterface_egl.cpp" diff --git a/gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.cpp new file mode 100644 index 0000000000..3ce3267fdd --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.cpp @@ -0,0 +1,609 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gl/builders/GrGLProgramBuilder.h" + +#include "include/gpu/GrContext.h" +#include "src/core/SkATrace.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkReader32.h" +#include "src/core/SkTraceEvent.h" +#include "src/core/SkWriter32.h" +#include "src/gpu/GrAutoLocaleSetter.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrPersistentCacheUtils.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrShaderUtils.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/GrGLProgram.h" +#include "src/gpu/gl/builders/GrGLProgramBuilder.h" +#include "src/gpu/gl/builders/GrGLShaderStringBuilder.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +#define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X) +#define GL_CALL_RET(R, X) GR_GL_CALL_RET(this->gpu()->glInterface(), R, X) + +static void cleanup_shaders(GrGLGpu* gpu, const SkTDArray& shaderIDs) { + for (int i = 0; i < shaderIDs.count(); ++i) { + GR_GL_CALL(gpu->glInterface(), DeleteShader(shaderIDs[i])); + } +} + +static void cleanup_program(GrGLGpu* gpu, GrGLuint programID, + const SkTDArray& shaderIDs) { + GR_GL_CALL(gpu->glInterface(), DeleteProgram(programID)); + cleanup_shaders(gpu, shaderIDs); +} + +GrGLProgram* GrGLProgramBuilder::CreateProgram(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrProgramDesc* desc, + GrGLGpu* gpu, + const GrGLPrecompiledProgram* precompiledProgram) { + ATRACE_ANDROID_FRAMEWORK("Shader Compile"); + GrAutoLocaleSetter als("C"); + + // create a builder. This will be handed off to effects so they can use it to add + // uniforms, varyings, textures, etc + GrGLProgramBuilder builder(gpu, renderTarget, programInfo, desc); + + auto persistentCache = gpu->getContext()->priv().getPersistentCache(); + if (persistentCache && !precompiledProgram) { + sk_sp key = SkData::MakeWithoutCopy(desc->asKey(), desc->keyLength()); + builder.fCached = persistentCache->load(*key); + // the eventual end goal is to completely skip emitAndInstallProcs on a cache hit, but it's + // doing necessary setup in addition to generating the SkSL code. Currently we are only able + // to skip the SkSL->GLSL step on a cache hit. + } + if (!builder.emitAndInstallProcs()) { + return nullptr; + } + return builder.finalize(precompiledProgram); +} + +///////////////////////////////////////////////////////////////////////////// + +GrGLProgramBuilder::GrGLProgramBuilder(GrGLGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrProgramDesc* desc) + : INHERITED(renderTarget, programInfo, desc) + , fGpu(gpu) + , fVaryingHandler(this) + , fUniformHandler(this) + , fVertexAttributeCnt(0) + , fInstanceAttributeCnt(0) + , fVertexStride(0) + , fInstanceStride(0) {} + +const GrCaps* GrGLProgramBuilder::caps() const { + return fGpu->caps(); +} + +bool GrGLProgramBuilder::compileAndAttachShaders(const SkSL::String& glsl, + GrGLuint programId, + GrGLenum type, + SkTDArray* shaderIds, + GrContextOptions::ShaderErrorHandler* errHandler) { + GrGLGpu* gpu = this->gpu(); + GrGLuint shaderId = GrGLCompileAndAttachShader(gpu->glContext(), + programId, + type, + glsl, + gpu->stats(), + errHandler); + if (!shaderId) { + return false; + } + + *shaderIds->append() = shaderId; + return true; +} + +void GrGLProgramBuilder::computeCountsAndStrides(GrGLuint programID, + const GrPrimitiveProcessor& primProc, + bool bindAttribLocations) { + fVertexAttributeCnt = primProc.numVertexAttributes(); + fInstanceAttributeCnt = primProc.numInstanceAttributes(); + fAttributes.reset( + new GrGLProgram::Attribute[fVertexAttributeCnt + fInstanceAttributeCnt]); + auto addAttr = [&](int i, const auto& a, size_t* stride) { + fAttributes[i].fCPUType = a.cpuType(); + fAttributes[i].fGPUType = a.gpuType(); + fAttributes[i].fOffset = *stride; + *stride += a.sizeAlign4(); + fAttributes[i].fLocation = i; + if (bindAttribLocations) { + GL_CALL(BindAttribLocation(programID, i, a.name())); + } + }; + fVertexStride = 0; + int i = 0; + for (const auto& attr : primProc.vertexAttributes()) { + addAttr(i++, attr, &fVertexStride); + } + SkASSERT(fVertexStride == primProc.vertexStride()); + fInstanceStride = 0; + for (const auto& attr : primProc.instanceAttributes()) { + addAttr(i++, attr, &fInstanceStride); + } + SkASSERT(fInstanceStride == primProc.instanceStride()); +} + +void GrGLProgramBuilder::addInputVars(const SkSL::Program::Inputs& inputs) { + if (inputs.fRTWidth) { + this->addRTWidthUniform(SKSL_RTWIDTH_NAME); + } + if (inputs.fRTHeight) { + this->addRTHeightUniform(SKSL_RTHEIGHT_NAME); + } +} + +static constexpr SkFourByteTag kSKSL_Tag = SkSetFourByteTag('S', 'K', 'S', 'L'); +static constexpr SkFourByteTag kGLSL_Tag = SkSetFourByteTag('G', 'L', 'S', 'L'); +static constexpr SkFourByteTag kGLPB_Tag = SkSetFourByteTag('G', 'L', 'P', 'B'); + +void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID, + const SkSL::String shaders[], bool isSkSL, + SkSL::Program::Settings* settings) { + if (!this->gpu()->getContext()->priv().getPersistentCache()) { + return; + } + sk_sp key = SkData::MakeWithoutCopy(this->desc()->asKey(), this->desc()->keyLength()); + if (fGpu->glCaps().programBinarySupport()) { + // binary cache + GrGLsizei length = 0; + GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length)); + if (length > 0) { + SkWriter32 writer; + writer.write32(kGLPB_Tag); + + writer.writePad(&inputs, sizeof(inputs)); + writer.write32(length); + + void* binary = writer.reservePad(length); + GrGLenum binaryFormat; + GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary)); + writer.write32(binaryFormat); + + auto data = writer.snapshotAsData(); + this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data); + } + } else { + // source cache, plus metadata to allow for a complete precompile + GrPersistentCacheUtils::ShaderMetadata meta; + meta.fSettings = settings; + meta.fHasCustomColorOutput = fFS.hasCustomColorOutput(); + meta.fHasSecondaryColorOutput = fFS.hasSecondaryOutput(); + for (const auto& attr : this->primitiveProcessor().vertexAttributes()) { + meta.fAttributeNames.emplace_back(attr.name()); + } + for (const auto& attr : this->primitiveProcessor().instanceAttributes()) { + meta.fAttributeNames.emplace_back(attr.name()); + } + + auto data = GrPersistentCacheUtils::PackCachedShaders(isSkSL ? kSKSL_Tag : kGLSL_Tag, + shaders, &inputs, 1, &meta); + this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data); + } +} + +GrGLProgram* GrGLProgramBuilder::finalize(const GrGLPrecompiledProgram* precompiledProgram) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + // verify we can get a program id + GrGLuint programID; + if (precompiledProgram) { + programID = precompiledProgram->fProgramID; + } else { + GL_CALL_RET(programID, CreateProgram()); + } + if (0 == programID) { + return nullptr; + } + + if (this->gpu()->glCaps().programBinarySupport() && + this->gpu()->glCaps().programParameterSupport() && + this->gpu()->getContext()->priv().getPersistentCache() && + !precompiledProgram) { + GL_CALL(ProgramParameteri(programID, GR_GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GR_GL_TRUE)); + } + + this->finalizeShaders(); + + // compile shaders and bind attributes / uniforms + auto errorHandler = this->gpu()->getContext()->priv().getShaderErrorHandler(); + const GrPrimitiveProcessor& primProc = this->primitiveProcessor(); + SkSL::Program::Settings settings; + settings.fCaps = this->gpu()->glCaps().shaderCaps(); + settings.fFlipY = this->origin() != kTopLeft_GrSurfaceOrigin; + settings.fSharpenTextures = + this->gpu()->getContext()->priv().options().fSharpenMipmappedTextures; + settings.fFragColorIsInOut = this->fragColorIsInOut(); + + SkSL::Program::Inputs inputs; + SkTDArray shadersToDelete; + // Calling GetProgramiv is expensive in Chromium. Assume success in release builds. + bool checkLinked = kChromium_GrGLDriver != fGpu->ctxInfo().driver(); +#ifdef SK_DEBUG + checkLinked = true; +#endif + bool cached = fCached.get() != nullptr; + bool usedProgramBinaries = false; + SkSL::String glsl[kGrShaderTypeCount]; + SkSL::String* sksl[kGrShaderTypeCount] = { + &fVS.fCompilerString, + &fGS.fCompilerString, + &fFS.fCompilerString, + }; + SkSL::String cached_sksl[kGrShaderTypeCount]; + if (precompiledProgram) { + // This is very similar to when we get program binaries. We even set that flag, as it's + // used to prevent other compile work later, and to force re-querying uniform locations. + this->addInputVars(precompiledProgram->fInputs); + this->computeCountsAndStrides(programID, primProc, false); + usedProgramBinaries = true; + } else if (cached) { + SkReader32 reader(fCached->data(), fCached->size()); + SkFourByteTag shaderType = reader.readU32(); + + switch (shaderType) { + case kGLPB_Tag: { + // Program binary cache hit. We may opt not to use this if we don't trust program + // binaries on this driver + if (!fGpu->glCaps().programBinarySupport()) { + cached = false; + break; + } + reader.read(&inputs, sizeof(inputs)); + GrGLsizei length = reader.readInt(); + const void* binary = reader.skip(length); + GrGLenum binaryFormat = reader.readU32(); + GrGLClearErr(this->gpu()->glInterface()); + GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(), + ProgramBinary(programID, binaryFormat, + const_cast(binary), length)); + if (GR_GL_GET_ERROR(this->gpu()->glInterface()) == GR_GL_NO_ERROR) { + if (checkLinked) { + cached = this->checkLinkStatus(programID, errorHandler, nullptr, nullptr); + } + if (cached) { + this->addInputVars(inputs); + this->computeCountsAndStrides(programID, primProc, false); + } + } else { + cached = false; + } + usedProgramBinaries = cached; + break; + } + + case kGLSL_Tag: + // Source cache hit, we don't need to compile the SkSL->GLSL + GrPersistentCacheUtils::UnpackCachedShaders(&reader, glsl, &inputs, 1); + break; + + case kSKSL_Tag: + // SkSL cache hit, this should only happen in tools overriding the generated SkSL + GrPersistentCacheUtils::UnpackCachedShaders(&reader, cached_sksl, &inputs, 1); + for (int i = 0; i < kGrShaderTypeCount; ++i) { + sksl[i] = &cached_sksl[i]; + } + break; + } + } + if (!usedProgramBinaries) { + // Either a cache miss, or we got something other than binaries from the cache + + /* + Fragment Shader + */ + if (glsl[kFragment_GrShaderType].empty()) { + // Don't have cached GLSL, need to compile SkSL->GLSL + if (fFS.fForceHighPrecision) { + settings.fForceHighPrecision = true; + } + std::unique_ptr fs = GrSkSLtoGLSL(gpu()->glContext(), + SkSL::Program::kFragment_Kind, + *sksl[kFragment_GrShaderType], + settings, + &glsl[kFragment_GrShaderType], + errorHandler); + if (!fs) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + inputs = fs->fInputs; + } + + this->addInputVars(inputs); + if (!this->compileAndAttachShaders(glsl[kFragment_GrShaderType], programID, + GR_GL_FRAGMENT_SHADER, &shadersToDelete, errorHandler)) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + + /* + Vertex Shader + */ + if (glsl[kVertex_GrShaderType].empty()) { + // Don't have cached GLSL, need to compile SkSL->GLSL + std::unique_ptr vs = GrSkSLtoGLSL(gpu()->glContext(), + SkSL::Program::kVertex_Kind, + *sksl[kVertex_GrShaderType], + settings, + &glsl[kVertex_GrShaderType], + errorHandler); + if (!vs) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + } + if (!this->compileAndAttachShaders(glsl[kVertex_GrShaderType], programID, + GR_GL_VERTEX_SHADER, &shadersToDelete, errorHandler)) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + + // This also binds vertex attribute locations. NVPR doesn't really use vertices, + // even though it requires a vertex shader in the program. + if (!primProc.isPathRendering()) { + this->computeCountsAndStrides(programID, primProc, true); + } + + /* + Geometry Shader + */ + if (primProc.willUseGeoShader()) { + if (glsl[kGeometry_GrShaderType].empty()) { + // Don't have cached GLSL, need to compile SkSL->GLSL + std::unique_ptr gs; + gs = GrSkSLtoGLSL(gpu()->glContext(), + SkSL::Program::kGeometry_Kind, + *sksl[kGeometry_GrShaderType], + settings, + &glsl[kGeometry_GrShaderType], + errorHandler); + if (!gs) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + } + if (!this->compileAndAttachShaders(glsl[kGeometry_GrShaderType], programID, + GR_GL_GEOMETRY_SHADER, &shadersToDelete, + errorHandler)) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + } + this->bindProgramResourceLocations(programID); + + GL_CALL(LinkProgram(programID)); + if (checkLinked) { + if (!this->checkLinkStatus(programID, errorHandler, sksl, glsl)) { + cleanup_program(fGpu, programID, shadersToDelete); + return nullptr; + } + } + } + this->resolveProgramResourceLocations(programID, usedProgramBinaries); + + cleanup_shaders(fGpu, shadersToDelete); + + // With ANGLE, we can't cache path-rendering programs. We use ProgramPathFragmentInputGen, + // and ANGLE's deserialized program state doesn't restore enough state to handle that. + // The native NVIDIA drivers do, but this is such an edge case that it's easier to just + // black-list caching these programs in all cases. See: anglebug.com/3619 + // We also can't cache SkSL or GLSL if we were given a precompiled program, but there's not + // much point in doing so. + if (!cached && !primProc.isPathRendering() && !precompiledProgram) { + bool isSkSL = false; + if (fGpu->getContext()->priv().options().fShaderCacheStrategy == + GrContextOptions::ShaderCacheStrategy::kSkSL) { + for (int i = 0; i < kGrShaderTypeCount; ++i) { + glsl[i] = GrShaderUtils::PrettyPrint(*sksl[i]); + } + isSkSL = true; + } + this->storeShaderInCache(inputs, programID, glsl, isSkSL, &settings); + } + return this->createProgram(programID); +} + +void GrGLProgramBuilder::bindProgramResourceLocations(GrGLuint programID) { + fUniformHandler.bindUniformLocations(programID, fGpu->glCaps()); + + const GrGLCaps& caps = this->gpu()->glCaps(); + if (fFS.hasCustomColorOutput() && caps.bindFragDataLocationSupport()) { + GL_CALL(BindFragDataLocation(programID, 0, + GrGLSLFragmentShaderBuilder::DeclaredColorOutputName())); + } + if (fFS.hasSecondaryOutput() && caps.shaderCaps()->mustDeclareFragmentShaderOutput()) { + GL_CALL(BindFragDataLocationIndexed(programID, 0, 1, + GrGLSLFragmentShaderBuilder::DeclaredSecondaryColorOutputName())); + } + + // handle NVPR separable varyings + if (!fGpu->glCaps().shaderCaps()->pathRenderingSupport() || + !fGpu->glPathRendering()->shouldBindFragmentInputs()) { + return; + } + int count = fVaryingHandler.fPathProcVaryingInfos.count(); + for (int i = 0; i < count; ++i) { + GL_CALL(BindFragmentInputLocation(programID, i, + fVaryingHandler.fPathProcVaryingInfos[i].fVariable.c_str())); + fVaryingHandler.fPathProcVaryingInfos[i].fLocation = i; + } +} + +bool GrGLProgramBuilder::checkLinkStatus(GrGLuint programID, + GrContextOptions::ShaderErrorHandler* errorHandler, + SkSL::String* sksl[], const SkSL::String glsl[]) { + GrGLint linked = GR_GL_INIT_ZERO; + GL_CALL(GetProgramiv(programID, GR_GL_LINK_STATUS, &linked)); + if (!linked) { + SkSL::String allShaders; + if (sksl) { + allShaders.appendf("// Vertex SKSL\n%s\n", sksl[kVertex_GrShaderType]->c_str()); + if (!sksl[kGeometry_GrShaderType]->empty()) { + allShaders.appendf("// Geometry SKSL\n%s\n", sksl[kGeometry_GrShaderType]->c_str()); + } + allShaders.appendf("// Fragment SKSL\n%s\n", sksl[kFragment_GrShaderType]->c_str()); + } + if (glsl) { + allShaders.appendf("// Vertex GLSL\n%s\n", glsl[kVertex_GrShaderType].c_str()); + if (!glsl[kGeometry_GrShaderType].empty()) { + allShaders.appendf("// Geometry GLSL\n%s\n", glsl[kGeometry_GrShaderType].c_str()); + } + allShaders.appendf("// Fragment GLSL\n%s\n", glsl[kFragment_GrShaderType].c_str()); + } + GrGLint infoLen = GR_GL_INIT_ZERO; + GL_CALL(GetProgramiv(programID, GR_GL_INFO_LOG_LENGTH, &infoLen)); + SkAutoMalloc log(sizeof(char)*(infoLen+1)); // outside if for debugger + if (infoLen > 0) { + // retrieve length even though we don't need it to workaround + // bug in chrome cmd buffer param validation. + GrGLsizei length = GR_GL_INIT_ZERO; + GL_CALL(GetProgramInfoLog(programID, infoLen+1, &length, (char*)log.get())); + } + errorHandler->compileError(allShaders.c_str(), infoLen > 0 ? (const char*)log.get() : ""); + } + return SkToBool(linked); +} + +void GrGLProgramBuilder::resolveProgramResourceLocations(GrGLuint programID, bool force) { + fUniformHandler.getUniformLocations(programID, fGpu->glCaps(), force); + + // handle NVPR separable varyings + if (!fGpu->glCaps().shaderCaps()->pathRenderingSupport() || + fGpu->glPathRendering()->shouldBindFragmentInputs()) { + return; + } + int count = fVaryingHandler.fPathProcVaryingInfos.count(); + for (int i = 0; i < count; ++i) { + GrGLint location; + GL_CALL_RET(location, GetProgramResourceLocation( + programID, + GR_GL_FRAGMENT_INPUT, + fVaryingHandler.fPathProcVaryingInfos[i].fVariable.c_str())); + fVaryingHandler.fPathProcVaryingInfos[i].fLocation = location; + } +} + +GrGLProgram* GrGLProgramBuilder::createProgram(GrGLuint programID) { + return new GrGLProgram(fGpu, + fUniformHandles, + programID, + fUniformHandler.fUniforms, + fUniformHandler.fSamplers, + fVaryingHandler.fPathProcVaryingInfos, + std::move(fGeometryProcessor), + std::move(fXferProcessor), + std::move(fFragmentProcessors), + fFragmentProcessorCnt, + std::move(fAttributes), + fVertexAttributeCnt, + fInstanceAttributeCnt, + fVertexStride, + fInstanceStride); +} + +bool GrGLProgramBuilder::PrecompileProgram(GrGLPrecompiledProgram* precompiledProgram, + GrGLGpu* gpu, + const SkData& cachedData) { + SkReader32 reader(cachedData.data(), cachedData.size()); + SkFourByteTag shaderType = reader.readU32(); + if (shaderType != kSKSL_Tag) { + // TODO: Support GLSL, and maybe even program binaries, too? + return false; + } + + const GrGLInterface* gl = gpu->glInterface(); + auto errorHandler = gpu->getContext()->priv().getShaderErrorHandler(); + GrGLuint programID; + GR_GL_CALL_RET(gl, programID, CreateProgram()); + if (0 == programID) { + return false; + } + + SkTDArray shadersToDelete; + + SkSL::Program::Settings settings; + const GrGLCaps& caps = gpu->glCaps(); + settings.fCaps = caps.shaderCaps(); + settings.fSharpenTextures = gpu->getContext()->priv().options().fSharpenMipmappedTextures; + GrPersistentCacheUtils::ShaderMetadata meta; + meta.fSettings = &settings; + + SkSL::String shaders[kGrShaderTypeCount]; + SkSL::Program::Inputs inputs; + GrPersistentCacheUtils::UnpackCachedShaders(&reader, shaders, &inputs, 1, &meta); + + auto compileShader = [&](SkSL::Program::Kind kind, const SkSL::String& sksl, GrGLenum type) { + SkSL::String glsl; + auto program = GrSkSLtoGLSL(gpu->glContext(), kind, sksl, settings, &glsl, errorHandler); + if (!program) { + return false; + } + + if (GrGLuint shaderID = GrGLCompileAndAttachShader(gpu->glContext(), programID, type, glsl, + gpu->stats(), errorHandler)) { + shadersToDelete.push_back(shaderID); + return true; + } else { + return false; + } + }; + + if (!compileShader(SkSL::Program::kFragment_Kind, + shaders[kFragment_GrShaderType], + GR_GL_FRAGMENT_SHADER) || + !compileShader(SkSL::Program::kVertex_Kind, + shaders[kVertex_GrShaderType], + GR_GL_VERTEX_SHADER) || + (!shaders[kGeometry_GrShaderType].empty() && + !compileShader(SkSL::Program::kGeometry_Kind, + shaders[kGeometry_GrShaderType], + GR_GL_GEOMETRY_SHADER))) { + cleanup_program(gpu, programID, shadersToDelete); + return false; + } + + for (int i = 0; i < meta.fAttributeNames.count(); ++i) { + GR_GL_CALL(gpu->glInterface(), BindAttribLocation(programID, i, + meta.fAttributeNames[i].c_str())); + } + + if (meta.fHasCustomColorOutput && caps.bindFragDataLocationSupport()) { + GR_GL_CALL(gpu->glInterface(), BindFragDataLocation(programID, 0, + GrGLSLFragmentShaderBuilder::DeclaredColorOutputName())); + } + if (meta.fHasSecondaryColorOutput && caps.shaderCaps()->mustDeclareFragmentShaderOutput()) { + GR_GL_CALL(gpu->glInterface(), BindFragDataLocationIndexed(programID, 0, 1, + GrGLSLFragmentShaderBuilder::DeclaredSecondaryColorOutputName())); + } + + GR_GL_CALL(gpu->glInterface(), LinkProgram(programID)); + GrGLint linked = GR_GL_INIT_ZERO; + GR_GL_CALL(gpu->glInterface(), GetProgramiv(programID, GR_GL_LINK_STATUS, &linked)); + if (!linked) { + cleanup_program(gpu, programID, shadersToDelete); + return false; + } + + cleanup_shaders(gpu, shadersToDelete); + + precompiledProgram->fProgramID = programID; + precompiledProgram->fInputs = inputs; + return true; +} diff --git a/gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.h b/gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.h new file mode 100644 index 0000000000..800c64a687 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/builders/GrGLProgramBuilder.h @@ -0,0 +1,108 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLProgramBuilder_DEFINED +#define GrGLProgramBuilder_DEFINED + +#include "src/gpu/GrPipeline.h" +#include "src/gpu/gl/GrGLProgram.h" +#include "src/gpu/gl/GrGLProgramDataManager.h" +#include "src/gpu/gl/GrGLUniformHandler.h" +#include "src/gpu/gl/GrGLVaryingHandler.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/sksl/ir/SkSLProgram.h" + +class GrFragmentProcessor; +class GrGLContextInfo; +class GrProgramDesc; +class GrGLSLShaderBuilder; +class GrShaderCaps; + +struct GrGLPrecompiledProgram { + GrGLPrecompiledProgram(GrGLuint programID = 0, + SkSL::Program::Inputs inputs = SkSL::Program::Inputs()) + : fProgramID(programID) + , fInputs(inputs) {} + + GrGLuint fProgramID; + SkSL::Program::Inputs fInputs; +}; + +class GrGLProgramBuilder : public GrGLSLProgramBuilder { +public: + /** Generates a shader program. + * + * The program implements what is specified in the stages given as input. + * After successful generation, the builder result objects are available + * to be used. + * This function may modify the GrProgramDesc by setting the surface origin + * key to 0 (unspecified) if it turns out the program does not care about + * the surface origin. + * If a GL program has already been created, the program ID and inputs can + * be supplied to skip the shader compilation. + * @return true if generation was successful. + */ + static GrGLProgram* CreateProgram(GrRenderTarget*, + const GrProgramInfo&, + GrProgramDesc*, + GrGLGpu*, + const GrGLPrecompiledProgram* = nullptr); + + static bool PrecompileProgram(GrGLPrecompiledProgram*, GrGLGpu*, const SkData&); + + const GrCaps* caps() const override; + + GrGLGpu* gpu() const { return fGpu; } + +private: + GrGLProgramBuilder(GrGLGpu*, GrRenderTarget*, const GrProgramInfo&, GrProgramDesc*); + + void addInputVars(const SkSL::Program::Inputs& inputs); + bool compileAndAttachShaders(const SkSL::String& glsl, + GrGLuint programId, + GrGLenum type, + SkTDArray* shaderIds, + GrContextOptions::ShaderErrorHandler* errorHandler); + + void computeCountsAndStrides(GrGLuint programID, const GrPrimitiveProcessor& primProc, + bool bindAttribLocations); + void storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID, + const SkSL::String shaders[], bool isSkSL, + SkSL::Program::Settings* settings); + GrGLProgram* finalize(const GrGLPrecompiledProgram*); + void bindProgramResourceLocations(GrGLuint programID); + bool checkLinkStatus(GrGLuint programID, GrContextOptions::ShaderErrorHandler* errorHandler, + SkSL::String* sksl[], const SkSL::String glsl[]); + void resolveProgramResourceLocations(GrGLuint programID, bool force); + + // Subclasses create different programs + GrGLProgram* createProgram(GrGLuint programID); + + GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; } + const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; } + GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; } + + GrGLGpu* fGpu; + GrGLVaryingHandler fVaryingHandler; + GrGLUniformHandler fUniformHandler; + + std::unique_ptr fAttributes; + int fVertexAttributeCnt; + int fInstanceAttributeCnt; + size_t fVertexStride; + size_t fInstanceStride; + + // shader pulled from cache. Data is organized as: + // SkSL::Program::Inputs inputs + // int binaryFormat + // (all remaining bytes) char[] binary + sk_sp fCached; + + typedef GrGLSLProgramBuilder INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp b/gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp new file mode 100644 index 0000000000..53b03eec62 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkAutoMalloc.h" +#include "src/gpu/GrShaderUtils.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/gl/builders/GrGLShaderStringBuilder.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLGLSLCodeGenerator.h" +#include "src/sksl/ir/SkSLProgram.h" + +// Print the source code for all shaders generated. +static const bool gPrintSKSL = false; +static const bool gPrintGLSL = false; + +void print_shader_banner(SkSL::Program::Kind programKind) { + const char* typeName = "Unknown"; + switch (programKind) { + case SkSL::Program::kVertex_Kind: typeName = "Vertex"; break; + case SkSL::Program::kGeometry_Kind: typeName = "Geometry"; break; + case SkSL::Program::kFragment_Kind: typeName = "Fragment"; break; + default: break; + } + SkDebugf("---- %s shader ----------------------------------------------------\n", typeName); +} + +std::unique_ptr GrSkSLtoGLSL(const GrGLContext& context, + SkSL::Program::Kind programKind, + const SkSL::String& sksl, + const SkSL::Program::Settings& settings, + SkSL::String* glsl, + GrContextOptions::ShaderErrorHandler* errorHandler) { + SkSL::Compiler* compiler = context.compiler(); + std::unique_ptr program; +#ifdef SK_DEBUG + SkSL::String src = GrShaderUtils::PrettyPrint(sksl); +#else + const SkSL::String& src = sksl; +#endif + program = compiler->convertProgram(programKind, src, settings); + if (!program || !compiler->toGLSL(*program, glsl)) { + errorHandler->compileError(src.c_str(), compiler->errorText().c_str()); + return nullptr; + } + + if (gPrintSKSL || gPrintGLSL) { + print_shader_banner(programKind); + if (gPrintSKSL) { + GrShaderUtils::PrintLineByLine("SKSL:", GrShaderUtils::PrettyPrint(sksl)); + } + if (gPrintGLSL) { + GrShaderUtils::PrintLineByLine("GLSL:", GrShaderUtils::PrettyPrint(*glsl)); + } + } + + return program; +} + +GrGLuint GrGLCompileAndAttachShader(const GrGLContext& glCtx, + GrGLuint programId, + GrGLenum type, + const SkSL::String& glsl, + GrGpu::Stats* stats, + GrContextOptions::ShaderErrorHandler* errorHandler) { + const GrGLInterface* gli = glCtx.interface(); + + // Specify GLSL source to the driver. + GrGLuint shaderId; + GR_GL_CALL_RET(gli, shaderId, CreateShader(type)); + if (0 == shaderId) { + return 0; + } + const GrGLchar* source = glsl.c_str(); + GrGLint sourceLength = glsl.size(); + GR_GL_CALL(gli, ShaderSource(shaderId, 1, &source, &sourceLength)); + + stats->incShaderCompilations(); + GR_GL_CALL(gli, CompileShader(shaderId)); + + // Calling GetShaderiv in Chromium is quite expensive. Assume success in release builds. + bool checkCompiled = kChromium_GrGLDriver != glCtx.driver(); +#ifdef SK_DEBUG + checkCompiled = true; +#endif + if (checkCompiled) { + GrGLint compiled = GR_GL_INIT_ZERO; + GR_GL_CALL(gli, GetShaderiv(shaderId, GR_GL_COMPILE_STATUS, &compiled)); + + if (!compiled) { + GrGLint infoLen = GR_GL_INIT_ZERO; + GR_GL_CALL(gli, GetShaderiv(shaderId, GR_GL_INFO_LOG_LENGTH, &infoLen)); + SkAutoMalloc log(sizeof(char)*(infoLen+1)); // outside if for debugger + if (infoLen > 0) { + // retrieve length even though we don't need it to workaround bug in Chromium cmd + // buffer param validation. + GrGLsizei length = GR_GL_INIT_ZERO; + GR_GL_CALL(gli, GetShaderInfoLog(shaderId, infoLen+1, &length, (char*)log.get())); + } + errorHandler->compileError(glsl.c_str(), infoLen > 0 ? (const char*)log.get() : ""); + GR_GL_CALL(gli, DeleteShader(shaderId)); + return 0; + } + } + + // Attach the shader, but defer deletion until after we have linked the program. + // This works around a bug in the Android emulator's GLES2 wrapper which + // will immediately delete the shader object and free its memory even though it's + // attached to a program, which then causes glLinkProgram to fail. + GR_GL_CALL(gli, AttachShader(programId, shaderId)); + return shaderId; +} diff --git a/gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.h b/gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.h new file mode 100644 index 0000000000..12bebc3cfe --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.h @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLShaderStringBuilder_DEFINED +#define GrGLShaderStringBuilder_DEFINED + +#include "include/core/SkTypes.h" +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/gl/GrGLContext.h" +#include "src/sksl/SkSLGLSLCodeGenerator.h" + +std::unique_ptr GrSkSLtoGLSL(const GrGLContext& context, + SkSL::Program::Kind programKind, + const SkSL::String& sksl, + const SkSL::Program::Settings& settings, + SkSL::String* glsl, + GrContextOptions::ShaderErrorHandler* errorHandler); + +GrGLuint GrGLCompileAndAttachShader(const GrGLContext& glCtx, + GrGLuint programId, + GrGLenum type, + const SkSL::String& glsl, + GrGpu::Stats*, + GrContextOptions::ShaderErrorHandler* errorHandler); + +#endif diff --git a/gfx/skia/skia/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp b/gfx/skia/skia/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp new file mode 100644 index 0000000000..3af6d84ecd --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#include +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif +#include + +static GrGLFuncPtr egl_get_gl_proc(void* ctx, const char name[]) { + SkASSERT(nullptr == ctx); + // https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_get_all_proc_addresses.txt + // eglGetProcAddress() is not guaranteed to support the querying of non-extension EGL functions. + #define M(X) if (0 == strcmp(#X, name)) { return (GrGLFuncPtr) X; } + M(eglGetCurrentDisplay) + M(eglQueryString) + M(glActiveTexture) + M(glAttachShader) + M(glBindAttribLocation) + M(glBindBuffer) + M(glBindFramebuffer) + M(glBindRenderbuffer) + M(glBindTexture) + M(glBlendColor) + M(glBlendEquation) + M(glBlendFunc) + M(glBufferData) + M(glBufferSubData) + M(glCheckFramebufferStatus) + M(glClear) + M(glClearColor) + M(glClearStencil) + M(glColorMask) + M(glCompileShader) + M(glCompressedTexImage2D) + M(glCompressedTexSubImage2D) + M(glCopyTexSubImage2D) + M(glCreateProgram) + M(glCreateShader) + M(glCullFace) + M(glDeleteBuffers) + M(glDeleteFramebuffers) + M(glDeleteProgram) + M(glDeleteRenderbuffers) + M(glDeleteShader) + M(glDeleteTextures) + M(glDepthMask) + M(glDisable) + M(glDisableVertexAttribArray) + M(glDrawArrays) + M(glDrawElements) + M(glEnable) + M(glEnableVertexAttribArray) + M(glFinish) + M(glFlush) + M(glFramebufferRenderbuffer) + M(glFramebufferTexture2D) + M(glFrontFace) + M(glGenBuffers) + M(glGenFramebuffers) + M(glGenRenderbuffers) + M(glGenTextures) + M(glGenerateMipmap) + M(glGetBufferParameteriv) + M(glGetError) + M(glGetFramebufferAttachmentParameteriv) + M(glGetIntegerv) + M(glGetProgramInfoLog) + M(glGetProgramiv) + M(glGetRenderbufferParameteriv) + M(glGetShaderInfoLog) + M(glGetShaderPrecisionFormat) + M(glGetShaderiv) + M(glGetString) + M(glGetUniformLocation) + M(glIsTexture) + M(glLineWidth) + M(glLinkProgram) + M(glPixelStorei) + M(glReadPixels) + M(glRenderbufferStorage) + M(glScissor) + M(glShaderSource) + M(glStencilFunc) + M(glStencilFuncSeparate) + M(glStencilMask) + M(glStencilMaskSeparate) + M(glStencilOp) + M(glStencilOpSeparate) + M(glTexImage2D) + M(glTexParameterf) + M(glTexParameterfv) + M(glTexParameteri) + M(glTexParameteriv) + M(glTexSubImage2D) + M(glUniform1f) + M(glUniform1fv) + M(glUniform1i) + M(glUniform1iv) + M(glUniform2f) + M(glUniform2fv) + M(glUniform2i) + M(glUniform2iv) + M(glUniform3f) + M(glUniform3fv) + M(glUniform3i) + M(glUniform3iv) + M(glUniform4f) + M(glUniform4fv) + M(glUniform4i) + M(glUniform4iv) + M(glUniformMatrix2fv) + M(glUniformMatrix3fv) + M(glUniformMatrix4fv) + M(glUseProgram) + M(glVertexAttrib1f) + M(glVertexAttrib2fv) + M(glVertexAttrib3fv) + M(glVertexAttrib4fv) + M(glVertexAttribPointer) + M(glViewport) + #undef M + return eglGetProcAddress(name); +} + +sk_sp GrGLMakeNativeInterface() { + return GrGLMakeAssembledInterface(nullptr, egl_get_gl_proc); +} + +const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); } diff --git a/gfx/skia/skia/src/gpu/gl/glfw/GrGLMakeNativeInterface_glfw.cpp b/gfx/skia/skia/src/gpu/gl/glfw/GrGLMakeNativeInterface_glfw.cpp new file mode 100644 index 0000000000..a2579688d8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/glfw/GrGLMakeNativeInterface_glfw.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#include + +static GrGLFuncPtr glfw_get(void* ctx, const char name[]) { + SkASSERT(nullptr == ctx); + SkASSERT(glfwGetCurrentContext()); + return glfwGetProcAddress(name); +} + +sk_sp GrGLMakeNativeInterface() { + if (nullptr == glfwGetCurrentContext()) { + return nullptr; + } + + return GrGLMakeAssembledInterface(nullptr, glfw_get); +} + +const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); } diff --git a/gfx/skia/skia/src/gpu/gl/glx/GrGLMakeNativeInterface_glx.cpp b/gfx/skia/skia/src/gpu/gl/glx/GrGLMakeNativeInterface_glx.cpp new file mode 100644 index 0000000000..20187eb1d3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/glx/GrGLMakeNativeInterface_glx.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +// Define this to get a prototype for glXGetProcAddress on some systems +#define GLX_GLXEXT_PROTOTYPES 1 +#include + +static GrGLFuncPtr glx_get(void* ctx, const char name[]) { + // Avoid calling glXGetProcAddress() for EGL procs. + // We don't expect it to ever succeed, but somtimes it returns non-null anyway. + if (0 == strncmp(name, "egl", 3)) { + return nullptr; + } + + SkASSERT(nullptr == ctx); + SkASSERT(glXGetCurrentContext()); + return glXGetProcAddress(reinterpret_cast(name)); +} + +sk_sp GrGLMakeNativeInterface() { + if (nullptr == glXGetCurrentContext()) { + return nullptr; + } + + return GrGLMakeAssembledInterface(nullptr, glx_get); +} + +const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); } diff --git a/gfx/skia/skia/src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp b/gfx/skia/skia/src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp new file mode 100644 index 0000000000..10e37d2b84 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "include/gpu/gl/GrGLInterface.h" +#include + +class GLLoader { +public: + GLLoader() { + fLibrary = dlopen( + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", + RTLD_LAZY); + } + + ~GLLoader() { + if (fLibrary) { + dlclose(fLibrary); + } + } + + void* handle() const { + return nullptr == fLibrary ? RTLD_DEFAULT : fLibrary; + } + +private: + void* fLibrary; +}; + +class GLProcGetter { +public: + GLProcGetter() {} + + GrGLFuncPtr getProc(const char name[]) const { + return (GrGLFuncPtr) dlsym(fLoader.handle(), name); + } + +private: + GLLoader fLoader; +}; + +static GrGLFuncPtr ios_get_gl_proc(void* ctx, const char name[]) { + SkASSERT(ctx); + const GLProcGetter* getter = (const GLProcGetter*) ctx; + return getter->getProc(name); +} + +sk_sp GrGLMakeNativeInterface() { + GLProcGetter getter; + return GrGLMakeAssembledGLESInterface(&getter, ios_get_gl_proc); +} + +const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); } diff --git a/gfx/skia/skia/src/gpu/gl/mac/GrGLMakeNativeInterface_mac.cpp b/gfx/skia/skia/src/gpu/gl/mac/GrGLMakeNativeInterface_mac.cpp new file mode 100644 index 0000000000..5a5c9e088a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/mac/GrGLMakeNativeInterface_mac.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_MAC) + + +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "include/gpu/gl/GrGLInterface.h" + +#include + +class GLLoader { +public: + GLLoader() { + fLibrary = dlopen( + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", + RTLD_LAZY); + } + + ~GLLoader() { + if (fLibrary) { + dlclose(fLibrary); + } + } + + void* handle() const { + return nullptr == fLibrary ? RTLD_DEFAULT : fLibrary; + } + +private: + void* fLibrary; +}; + +class GLProcGetter { +public: + GLProcGetter() {} + + GrGLFuncPtr getProc(const char name[]) const { + return (GrGLFuncPtr) dlsym(fLoader.handle(), name); + } + +private: + GLLoader fLoader; +}; + +static GrGLFuncPtr mac_get_gl_proc(void* ctx, const char name[]) { + SkASSERT(ctx); + const GLProcGetter* getter = (const GLProcGetter*) ctx; + return getter->getProc(name); +} + +sk_sp GrGLMakeNativeInterface() { + GLProcGetter getter; + return GrGLMakeAssembledGLInterface(&getter, mac_get_gl_proc); +} + +const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); } + +#endif//defined(SK_BUILD_FOR_MAC) diff --git a/gfx/skia/skia/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp b/gfx/skia/skia/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp new file mode 100644 index 0000000000..e8d43a76f6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "src/core/SkLeanWindows.h" + +#include "include/gpu/gl/GrGLAssembleInterface.h" +#include "include/gpu/gl/GrGLInterface.h" +#include "src/gpu/gl/GrGLUtil.h" + +#if defined(_M_ARM64) + +sk_sp GrGLMakeNativeInterface() { return nullptr; } + +#else + +class AutoLibraryUnload { +public: + AutoLibraryUnload(const char* moduleName) { + fModule = LoadLibraryA(moduleName); + } + ~AutoLibraryUnload() { + if (fModule) { + FreeLibrary(fModule); + } + } + HMODULE get() const { return fModule; } + +private: + HMODULE fModule; +}; + +class GLProcGetter { +public: + GLProcGetter() : fGLLib("opengl32.dll") {} + + bool isInitialized() const { return SkToBool(fGLLib.get()); } + + GrGLFuncPtr getProc(const char name[]) const { + GrGLFuncPtr proc; + if ((proc = (GrGLFuncPtr) GetProcAddress(fGLLib.get(), name))) { + return proc; + } + if ((proc = (GrGLFuncPtr) wglGetProcAddress(name))) { + return proc; + } + return nullptr; + } + +private: + AutoLibraryUnload fGLLib; +}; + +static GrGLFuncPtr win_get_gl_proc(void* ctx, const char name[]) { + SkASSERT(ctx); + SkASSERT(wglGetCurrentContext()); + const GLProcGetter* getter = (const GLProcGetter*) ctx; + return getter->getProc(name); +} + +/* + * Windows makes the GL funcs all be __stdcall instead of __cdecl :( + * This implementation will only work if GR_GL_FUNCTION_TYPE is __stdcall. + * Otherwise, a springboard would be needed that hides the calling convention. + */ +sk_sp GrGLMakeNativeInterface() { + if (nullptr == wglGetCurrentContext()) { + return nullptr; + } + + GLProcGetter getter; + if (!getter.isInitialized()) { + return nullptr; + } + + GrGLGetStringFn* getString = (GrGLGetStringFn*)getter.getProc("glGetString"); + if (nullptr == getString) { + return nullptr; + } + const char* verStr = reinterpret_cast(getString(GR_GL_VERSION)); + GrGLStandard standard = GrGLGetStandardInUseFromString(verStr); + + if (GR_IS_GR_GL_ES(standard)) { + return GrGLMakeAssembledGLESInterface(&getter, win_get_gl_proc); + } else if (GR_IS_GR_GL(standard)) { + return GrGLMakeAssembledGLInterface(&getter, win_get_gl_proc); + } + return nullptr; +} + +#endif // ARM64 + +const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); } + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSL.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSL.cpp new file mode 100644 index 0000000000..afa9159bb0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSL.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/glsl/GrGLSL.h" + +const char* GrGLSLTypeString(GrSLType t) { + switch (t) { + case kVoid_GrSLType: + return "void"; + case kHalf_GrSLType: + return "half"; + case kHalf2_GrSLType: + return "half2"; + case kHalf3_GrSLType: + return "half3"; + case kHalf4_GrSLType: + return "half4"; + case kFloat_GrSLType: + return "float"; + case kFloat2_GrSLType: + return "float2"; + case kFloat3_GrSLType: + return "float3"; + case kFloat4_GrSLType: + return "float4"; + case kUint2_GrSLType: + return "uint2"; + case kInt2_GrSLType: + return "int2"; + case kInt3_GrSLType: + return "int3"; + case kInt4_GrSLType: + return "int4"; + case kFloat2x2_GrSLType: + return "float2x2"; + case kFloat3x3_GrSLType: + return "float3x3"; + case kFloat4x4_GrSLType: + return "float4x4"; + case kHalf2x2_GrSLType: + return "half2x2"; + case kHalf3x3_GrSLType: + return "half3x3"; + case kHalf4x4_GrSLType: + return "half4x4"; + case kTexture2DSampler_GrSLType: + return "sampler2D"; + case kTextureExternalSampler_GrSLType: + return "samplerExternalOES"; + case kTexture2DRectSampler_GrSLType: + return "sampler2DRect"; + case kBool_GrSLType: + return "bool"; + case kInt_GrSLType: + return "int"; + case kUint_GrSLType: + return "uint"; + case kShort_GrSLType: + return "short"; + case kShort2_GrSLType: + return "short2"; + case kShort3_GrSLType: + return "short3"; + case kShort4_GrSLType: + return "short4"; + case kUShort_GrSLType: + return "ushort"; + case kUShort2_GrSLType: + return "ushort2"; + case kUShort3_GrSLType: + return "ushort3"; + case kUShort4_GrSLType: + return "ushort4"; + case kByte_GrSLType: + return "byte"; + case kByte2_GrSLType: + return "byte2"; + case kByte3_GrSLType: + return "byte3"; + case kByte4_GrSLType: + return "byte4"; + case kUByte_GrSLType: + return "ubyte"; + case kUByte2_GrSLType: + return "ubyte2"; + case kUByte3_GrSLType: + return "ubyte3"; + case kUByte4_GrSLType: + return "ubyte4"; + case kTexture2D_GrSLType: + return "texture2D"; + case kSampler_GrSLType: + return "sampler"; + } + SK_ABORT("Unknown shader var type."); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSL.h b/gfx/skia/skia/src/gpu/glsl/GrGLSL.h new file mode 100644 index 0000000000..0bee458b6e --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSL.h @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSL_DEFINED +#define GrGLSL_DEFINED + +#include "include/private/GrTypesPriv.h" + +class GrShaderCaps; + +// Limited set of GLSL versions we build shaders for. Caller should round +// down the GLSL version to one of these enums. +enum GrGLSLGeneration { + /** + * Desktop GLSL 1.10 and ES2 shading language (based on desktop GLSL 1.20) + */ + k110_GrGLSLGeneration, + /** + * Desktop GLSL 1.30 + */ + k130_GrGLSLGeneration, + /** + * Desktop GLSL 1.40 + */ + k140_GrGLSLGeneration, + /** + * Desktop GLSL 1.50 + */ + k150_GrGLSLGeneration, + /** + * Desktop GLSL 3.30, and ES GLSL 3.00 + */ + k330_GrGLSLGeneration, + /** + * Desktop GLSL 4.00 + */ + k400_GrGLSLGeneration, + /** + * Desktop GLSL 4.20 + */ + k420_GrGLSLGeneration, + /** + * ES GLSL 3.10 only TODO Make GLSLCap objects to make this more granular + */ + k310es_GrGLSLGeneration, + /** + * ES GLSL 3.20 + */ + k320es_GrGLSLGeneration, +}; + +const char* GrGLSLTypeString(GrSLType); + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.cpp new file mode 100644 index 0000000000..2d1e93c777 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.cpp @@ -0,0 +1,510 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkBlendModePriv.h" +#include "src/gpu/glsl/GrGLSLBlend.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" + +////////////////////////////////////////////////////////////////////////////// +// Advanced (non-coeff) blend helpers +////////////////////////////////////////////////////////////////////////////// + +static void hard_light(GrGLSLFragmentBuilder* fsBuilder, + const char* final, + const char* src, + const char* dst) { + static const char kComponents[] = { 'r', 'g', 'b' }; + for (size_t i = 0; i < SK_ARRAY_COUNT(kComponents); ++i) { + char component = kComponents[i]; + fsBuilder->codeAppendf("if (2.0 * %s.%c <= %s.a) {", src, component, src); + fsBuilder->codeAppendf("%s.%c = 2.0 * %s.%c * %s.%c;", + final, component, src, component, dst, component); + fsBuilder->codeAppend("} else {"); + fsBuilder->codeAppendf("%s.%c = %s.a * %s.a - 2.0 * (%s.a - %s.%c) * (%s.a - %s.%c);", + final, component, src, dst, dst, dst, component, src, src, + component); + fsBuilder->codeAppend("}"); + } + fsBuilder->codeAppendf("%s.rgb += %s.rgb * (1.0 - %s.a) + %s.rgb * (1.0 - %s.a);", + final, src, dst, dst, src); +} + +// Does one component of color-dodge +static void color_dodge_component(GrGLSLFragmentBuilder* fsBuilder, + const char* final, + const char* src, + const char* dst, + const char component) { + const char* divisorGuard = ""; + const GrShaderCaps* shaderCaps = fsBuilder->getProgramBuilder()->shaderCaps(); + if (shaderCaps->mustGuardDivisionEvenAfterExplicitZeroCheck()) { + divisorGuard = "+ 0.00000001"; + } + + fsBuilder->codeAppendf("if (0.0 == %s.%c) {", dst, component); + fsBuilder->codeAppendf("%s.%c = %s.%c * (1.0 - %s.a);", + final, component, src, component, dst); + fsBuilder->codeAppend("} else {"); + fsBuilder->codeAppendf("half d = %s.a - %s.%c;", src, src, component); + fsBuilder->codeAppend("if (0.0 == d) {"); + fsBuilder->codeAppendf("%s.%c = %s.a * %s.a + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);", + final, component, src, dst, src, component, dst, dst, component, + src); + fsBuilder->codeAppend("} else {"); + fsBuilder->codeAppendf("d = min(%s.a, %s.%c * %s.a / (d %s));", + dst, dst, component, src, divisorGuard); + fsBuilder->codeAppendf("%s.%c = d * %s.a + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);", + final, component, src, src, component, dst, dst, component, src); + fsBuilder->codeAppend("}"); + fsBuilder->codeAppend("}"); +} + +// Does one component of color-burn +static void color_burn_component(GrGLSLFragmentBuilder* fsBuilder, + const char* final, + const char* src, + const char* dst, + const char component) { + const char* divisorGuard = ""; + const GrShaderCaps* shaderCaps = fsBuilder->getProgramBuilder()->shaderCaps(); + if (shaderCaps->mustGuardDivisionEvenAfterExplicitZeroCheck()) { + divisorGuard = "+ 0.00000001"; + } + + fsBuilder->codeAppendf("if (%s.a == %s.%c) {", dst, dst, component); + fsBuilder->codeAppendf("%s.%c = %s.a * %s.a + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);", + final, component, src, dst, src, component, dst, dst, component, + src); + fsBuilder->codeAppendf("} else if (0.0 == %s.%c) {", src, component); + fsBuilder->codeAppendf("%s.%c = %s.%c * (1.0 - %s.a);", + final, component, dst, component, src); + fsBuilder->codeAppend("} else {"); + fsBuilder->codeAppendf("half d = max(0.0, %s.a - (%s.a - %s.%c) * %s.a / (%s.%c %s));", + dst, dst, dst, component, src, src, component, divisorGuard); + fsBuilder->codeAppendf("%s.%c = %s.a * d + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);", + final, component, src, src, component, dst, dst, component, src); + fsBuilder->codeAppend("}"); +} + +// Does one component of soft-light. Caller should have already checked that dst alpha > 0. +static void soft_light_component_pos_dst_alpha(GrGLSLFragmentBuilder* fsBuilder, + const char* final, + const char* src, + const char* dst, + const char component) { + const char* divisorGuard = ""; + const GrShaderCaps* shaderCaps = fsBuilder->getProgramBuilder()->shaderCaps(); + if (shaderCaps->mustGuardDivisionEvenAfterExplicitZeroCheck()) { + divisorGuard = "+ 0.00000001"; + } + + // if (2S < Sa) + fsBuilder->codeAppendf("if (2.0 * %s.%c <= %s.a) {", src, component, src); + // (D^2 (Sa-2 S))/Da+(1-Da) S+D (-Sa+2 S+1) + fsBuilder->codeAppendf("%s.%c = (%s.%c*%s.%c*(%s.a - 2.0*%s.%c)) / (%s.a %s) +" + "(1.0 - %s.a) * %s.%c + %s.%c*(-%s.a + 2.0*%s.%c + 1.0);", + final, component, dst, component, dst, component, src, src, + component, dst, divisorGuard, dst, src, component, dst, component, src, + src, component); + // else if (4D < Da) + fsBuilder->codeAppendf("} else if (4.0 * %s.%c <= %s.a) {", + dst, component, dst); + fsBuilder->codeAppendf("half DSqd = %s.%c * %s.%c;", + dst, component, dst, component); + fsBuilder->codeAppendf("half DCub = DSqd * %s.%c;", dst, component); + fsBuilder->codeAppendf("half DaSqd = %s.a * %s.a;", dst, dst); + fsBuilder->codeAppendf("half DaCub = DaSqd * %s.a;", dst); + // (Da^3 (-S)+Da^2 (S-D (3 Sa-6 S-1))+12 Da D^2 (Sa-2 S)-16 D^3 (Sa-2 S))/Da^2 + fsBuilder->codeAppendf("%s.%c =" + "(DaSqd*(%s.%c - %s.%c * (3.0*%s.a - 6.0*%s.%c - 1.0)) +" + " 12.0*%s.a*DSqd*(%s.a - 2.0*%s.%c) - 16.0*DCub * (%s.a - 2.0*%s.%c) -" + " DaCub*%s.%c) / (DaSqd %s);", + final, component, src, component, dst, component, + src, src, component, dst, src, src, component, src, src, + component, src, component, divisorGuard); + fsBuilder->codeAppendf("} else {"); + // -sqrt(Da * D) (Sa-2 S)-Da S+D (Sa-2 S+1)+S + fsBuilder->codeAppendf("%s.%c = %s.%c*(%s.a - 2.0*%s.%c + 1.0) + %s.%c -" + " sqrt(%s.a*%s.%c)*(%s.a - 2.0*%s.%c) - %s.a*%s.%c;", + final, component, dst, component, src, src, component, src, component, + dst, dst, component, src, src, component, dst, src, component); + fsBuilder->codeAppendf("}"); +} + +// Adds a function that takes two colors and an alpha as input. It produces a color with the +// hue and saturation of the first color, the luminosity of the second color, and the input +// alpha. It has this signature: +// float3 set_luminance(float3 hueSatColor, float alpha, float3 lumColor). +static void add_lum_function(GrGLSLFragmentBuilder* fsBuilder, SkString* setLumFunction) { + // Emit a helper that gets the luminance of a color. + SkString getFunction; + GrShaderVar getLumArgs[] = { + GrShaderVar("color", kHalf3_GrSLType), + }; + SkString getLumBody("return dot(half3(0.3, 0.59, 0.11), color);"); + fsBuilder->emitFunction(kHalf_GrSLType, + "luminance", + SK_ARRAY_COUNT(getLumArgs), getLumArgs, + getLumBody.c_str(), + &getFunction); + + // Emit the set luminance function. + GrShaderVar setLumArgs[] = { + GrShaderVar("hueSat", kHalf3_GrSLType), + GrShaderVar("alpha", kHalf_GrSLType), + GrShaderVar("lumColor", kHalf3_GrSLType), + }; + SkString setLumBody; + setLumBody.printf("half outLum = %s(lumColor);", getFunction.c_str()); + setLumBody.appendf("half3 outColor = outLum - %s(hueSat) + hueSat;", getFunction.c_str()); + setLumBody.append("half minComp = min(min(outColor.r, outColor.g), outColor.b);" + "half maxComp = max(max(outColor.r, outColor.g), outColor.b);" + "if (minComp < 0.0 && outLum != minComp) {" + "outColor = outLum + ((outColor - half3(outLum, outLum, outLum)) * outLum) /" + "(outLum - minComp);" + "}" + "if (maxComp > alpha && maxComp != outLum) {" + "outColor = outLum +" + "((outColor - half3(outLum, outLum, outLum)) * (alpha - outLum)) /" + "(maxComp - outLum);" + "}" + "return outColor;"); + fsBuilder->emitFunction(kHalf3_GrSLType, + "set_luminance", + SK_ARRAY_COUNT(setLumArgs), setLumArgs, + setLumBody.c_str(), + setLumFunction); +} + +// Adds a function that creates a color with the hue and luminosity of one input color and +// the saturation of another color. It will have this signature: +// float set_saturation(float3 hueLumColor, float3 satColor) +static void add_sat_function(GrGLSLFragmentBuilder* fsBuilder, SkString* setSatFunction) { + // Emit a helper that gets the saturation of a color + SkString getFunction; + GrShaderVar getSatArgs[] = { GrShaderVar("color", kHalf3_GrSLType) }; + SkString getSatBody; + getSatBody.printf("return max(max(color.r, color.g), color.b) - " + "min(min(color.r, color.g), color.b);"); + fsBuilder->emitFunction(kHalf_GrSLType, + "saturation", + SK_ARRAY_COUNT(getSatArgs), getSatArgs, + getSatBody.c_str(), + &getFunction); + + // Emit a helper that sets the saturation given sorted input channels. This used + // to use inout params for min, mid, and max components but that seems to cause + // problems on PowerVR drivers. So instead it returns a float3 where r, g ,b are the + // adjusted min, mid, and max inputs, respectively. + SkString helperFunction; + GrShaderVar helperArgs[] = { + GrShaderVar("minComp", kHalf_GrSLType), + GrShaderVar("midComp", kHalf_GrSLType), + GrShaderVar("maxComp", kHalf_GrSLType), + GrShaderVar("sat", kHalf_GrSLType), + }; + static const char kHelperBody[] = "if (minComp < maxComp) {" + "half3 result;" + "result.r = 0.0;" + "result.g = sat * (midComp - minComp) / (maxComp - minComp);" + "result.b = sat;" + "return result;" + "} else {" + "return half3(0, 0, 0);" + "}"; + fsBuilder->emitFunction(kHalf3_GrSLType, + "set_saturation_helper", + SK_ARRAY_COUNT(helperArgs), helperArgs, + kHelperBody, + &helperFunction); + + GrShaderVar setSatArgs[] = { + GrShaderVar("hueLumColor", kHalf3_GrSLType), + GrShaderVar("satColor", kHalf3_GrSLType), + }; + const char* helpFunc = helperFunction.c_str(); + SkString setSatBody; + setSatBody.appendf("half sat = %s(satColor);" + "if (hueLumColor.r <= hueLumColor.g) {" + "if (hueLumColor.g <= hueLumColor.b) {" + "hueLumColor.rgb = %s(hueLumColor.r, hueLumColor.g, hueLumColor.b, sat);" + "} else if (hueLumColor.r <= hueLumColor.b) {" + "hueLumColor.rbg = %s(hueLumColor.r, hueLumColor.b, hueLumColor.g, sat);" + "} else {" + "hueLumColor.brg = %s(hueLumColor.b, hueLumColor.r, hueLumColor.g, sat);" + "}" + "} else if (hueLumColor.r <= hueLumColor.b) {" + "hueLumColor.grb = %s(hueLumColor.g, hueLumColor.r, hueLumColor.b, sat);" + "} else if (hueLumColor.g <= hueLumColor.b) {" + "hueLumColor.gbr = %s(hueLumColor.g, hueLumColor.b, hueLumColor.r, sat);" + "} else {" + "hueLumColor.bgr = %s(hueLumColor.b, hueLumColor.g, hueLumColor.r, sat);" + "}" + "return hueLumColor;", + getFunction.c_str(), helpFunc, helpFunc, helpFunc, helpFunc, + helpFunc, helpFunc); + fsBuilder->emitFunction(kHalf3_GrSLType, + "set_saturation", + SK_ARRAY_COUNT(setSatArgs), setSatArgs, + setSatBody.c_str(), + setSatFunction); +} + +static void emit_advanced_xfermode_code(GrGLSLFragmentBuilder* fsBuilder, const char* srcColor, + const char* dstColor, const char* outputColor, + SkBlendMode mode) { + SkASSERT(srcColor); + SkASSERT(dstColor); + SkASSERT(outputColor); + // These all perform src-over on the alpha channel. + fsBuilder->codeAppendf("%s.a = %s.a + (1.0 - %s.a) * %s.a;", + outputColor, srcColor, srcColor, dstColor); + + switch (mode) { + case SkBlendMode::kOverlay: + // Overlay is Hard-Light with the src and dst reversed + hard_light(fsBuilder, outputColor, dstColor, srcColor); + break; + case SkBlendMode::kDarken: + fsBuilder->codeAppendf("%s.rgb = min((1.0 - %s.a) * %s.rgb + %s.rgb, " + "(1.0 - %s.a) * %s.rgb + %s.rgb);", + outputColor, + srcColor, dstColor, srcColor, + dstColor, srcColor, dstColor); + break; + case SkBlendMode::kLighten: + fsBuilder->codeAppendf("%s.rgb = max((1.0 - %s.a) * %s.rgb + %s.rgb, " + "(1.0 - %s.a) * %s.rgb + %s.rgb);", + outputColor, + srcColor, dstColor, srcColor, + dstColor, srcColor, dstColor); + break; + case SkBlendMode::kColorDodge: + color_dodge_component(fsBuilder, outputColor, srcColor, dstColor, 'r'); + color_dodge_component(fsBuilder, outputColor, srcColor, dstColor, 'g'); + color_dodge_component(fsBuilder, outputColor, srcColor, dstColor, 'b'); + break; + case SkBlendMode::kColorBurn: + color_burn_component(fsBuilder, outputColor, srcColor, dstColor, 'r'); + color_burn_component(fsBuilder, outputColor, srcColor, dstColor, 'g'); + color_burn_component(fsBuilder, outputColor, srcColor, dstColor, 'b'); + break; + case SkBlendMode::kHardLight: + hard_light(fsBuilder, outputColor, srcColor, dstColor); + break; + case SkBlendMode::kSoftLight: + fsBuilder->codeAppendf("if (0.0 == %s.a) {", dstColor); + fsBuilder->codeAppendf("%s.rgba = %s;", outputColor, srcColor); + fsBuilder->codeAppendf("} else {"); + soft_light_component_pos_dst_alpha(fsBuilder, outputColor, srcColor, dstColor, 'r'); + soft_light_component_pos_dst_alpha(fsBuilder, outputColor, srcColor, dstColor, 'g'); + soft_light_component_pos_dst_alpha(fsBuilder, outputColor, srcColor, dstColor, 'b'); + fsBuilder->codeAppendf("}"); + break; + case SkBlendMode::kDifference: + fsBuilder->codeAppendf("%s.rgb = %s.rgb + %s.rgb -" + "2.0 * min(%s.rgb * %s.a, %s.rgb * %s.a);", + outputColor, srcColor, dstColor, srcColor, dstColor, + dstColor, srcColor); + break; + case SkBlendMode::kExclusion: + fsBuilder->codeAppendf("%s.rgb = %s.rgb + %s.rgb - " + "2.0 * %s.rgb * %s.rgb;", + outputColor, dstColor, srcColor, dstColor, srcColor); + break; + case SkBlendMode::kMultiply: + fsBuilder->codeAppendf("%s.rgb = (1.0 - %s.a) * %s.rgb + " + "(1.0 - %s.a) * %s.rgb + " + "%s.rgb * %s.rgb;", + outputColor, srcColor, dstColor, dstColor, srcColor, + srcColor, dstColor); + break; + case SkBlendMode::kHue: { + // SetLum(SetSat(S * Da, Sat(D * Sa)), Sa*Da, D*Sa) + (1 - Sa) * D + (1 - Da) * S + SkString setSat, setLum; + add_sat_function(fsBuilder, &setSat); + add_lum_function(fsBuilder, &setLum); + fsBuilder->codeAppendf("half4 dstSrcAlpha = %s * %s.a;", + dstColor, srcColor); + fsBuilder->codeAppendf("%s.rgb = %s(%s(%s.rgb * %s.a, dstSrcAlpha.rgb)," + "dstSrcAlpha.a, dstSrcAlpha.rgb);", + outputColor, setLum.c_str(), setSat.c_str(), srcColor, + dstColor); + fsBuilder->codeAppendf("%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;", + outputColor, srcColor, dstColor, dstColor, srcColor); + break; + } + case SkBlendMode::kSaturation: { + // SetLum(SetSat(D * Sa, Sat(S * Da)), Sa*Da, D*Sa)) + (1 - Sa) * D + (1 - Da) * S + SkString setSat, setLum; + add_sat_function(fsBuilder, &setSat); + add_lum_function(fsBuilder, &setLum); + fsBuilder->codeAppendf("half4 dstSrcAlpha = %s * %s.a;", + dstColor, srcColor); + fsBuilder->codeAppendf("%s.rgb = %s(%s(dstSrcAlpha.rgb, %s.rgb * %s.a)," + "dstSrcAlpha.a, dstSrcAlpha.rgb);", + outputColor, setLum.c_str(), setSat.c_str(), srcColor, + dstColor); + fsBuilder->codeAppendf("%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;", + outputColor, srcColor, dstColor, dstColor, srcColor); + break; + } + case SkBlendMode::kColor: { + // SetLum(S * Da, Sa* Da, D * Sa) + (1 - Sa) * D + (1 - Da) * S + SkString setLum; + add_lum_function(fsBuilder, &setLum); + fsBuilder->codeAppendf("half4 srcDstAlpha = %s * %s.a;", + srcColor, dstColor); + fsBuilder->codeAppendf("%s.rgb = %s(srcDstAlpha.rgb, srcDstAlpha.a, %s.rgb * %s.a);", + outputColor, setLum.c_str(), dstColor, srcColor); + fsBuilder->codeAppendf("%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;", + outputColor, srcColor, dstColor, dstColor, srcColor); + break; + } + case SkBlendMode::kLuminosity: { + // SetLum(D * Sa, Sa* Da, S * Da) + (1 - Sa) * D + (1 - Da) * S + SkString setLum; + add_lum_function(fsBuilder, &setLum); + fsBuilder->codeAppendf("half4 srcDstAlpha = %s * %s.a;", + srcColor, dstColor); + fsBuilder->codeAppendf("%s.rgb = %s(%s.rgb * %s.a, srcDstAlpha.a, srcDstAlpha.rgb);", + outputColor, setLum.c_str(), dstColor, srcColor); + fsBuilder->codeAppendf("%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;", + outputColor, srcColor, dstColor, dstColor, srcColor); + break; + } + default: + SK_ABORT("Unknown Custom Xfer mode."); + break; + } +} + +////////////////////////////////////////////////////////////////////////////// +// Porter-Duff blend helper +////////////////////////////////////////////////////////////////////////////// + +static bool append_porterduff_term(GrGLSLFragmentBuilder* fsBuilder, SkBlendModeCoeff coeff, + const char* colorName, const char* srcColorName, + const char* dstColorName, bool hasPrevious) { + if (SkBlendModeCoeff::kZero == coeff) { + return hasPrevious; + } else { + if (hasPrevious) { + fsBuilder->codeAppend(" + "); + } + fsBuilder->codeAppendf("%s", colorName); + switch (coeff) { + case SkBlendModeCoeff::kOne: + break; + case SkBlendModeCoeff::kSC: + fsBuilder->codeAppendf(" * %s", srcColorName); + break; + case SkBlendModeCoeff::kISC: + fsBuilder->codeAppendf(" * (half4(1.0) - %s)", srcColorName); + break; + case SkBlendModeCoeff::kDC: + fsBuilder->codeAppendf(" * %s", dstColorName); + break; + case SkBlendModeCoeff::kIDC: + fsBuilder->codeAppendf(" * (half4(1.0) - %s)", dstColorName); + break; + case SkBlendModeCoeff::kSA: + fsBuilder->codeAppendf(" * %s.a", srcColorName); + break; + case SkBlendModeCoeff::kISA: + fsBuilder->codeAppendf(" * (1.0 - %s.a)", srcColorName); + break; + case SkBlendModeCoeff::kDA: + fsBuilder->codeAppendf(" * %s.a", dstColorName); + break; + case SkBlendModeCoeff::kIDA: + fsBuilder->codeAppendf(" * (1.0 - %s.a)", dstColorName); + break; + default: + SK_ABORT("Unsupported Blend Coeff"); + } + return true; + } +} + +////////////////////////////////////////////////////////////////////////////// + +void GrGLSLBlend::AppendMode(GrGLSLFragmentBuilder* fsBuilder, const char* srcColor, + const char* dstColor, const char* outColor, + SkBlendMode mode) { + + SkBlendModeCoeff srcCoeff, dstCoeff; + if (SkBlendMode_AsCoeff(mode, &srcCoeff, &dstCoeff)) { + // The only coeff mode that can go out of range is plus. + bool clamp = mode == SkBlendMode::kPlus; + + fsBuilder->codeAppendf("%s = ", outColor); + if (clamp) { + fsBuilder->codeAppend("clamp("); + } + // append src blend + bool didAppend = append_porterduff_term(fsBuilder, srcCoeff, srcColor, srcColor, dstColor, + false); + // append dst blend + if(!append_porterduff_term(fsBuilder, dstCoeff, dstColor, srcColor, dstColor, didAppend)) { + fsBuilder->codeAppend("half4(0, 0, 0, 0)"); + } + if (clamp) { + fsBuilder->codeAppend(", 0, 1);"); + } + fsBuilder->codeAppend(";"); + } else { + emit_advanced_xfermode_code(fsBuilder, srcColor, dstColor, outColor, mode); + } +} + +void GrGLSLBlend::AppendRegionOp(GrGLSLFragmentBuilder* fsBuilder, const char* srcColor, + const char* dstColor, const char* outColor, + SkRegion::Op regionOp) { + SkBlendModeCoeff srcCoeff, dstCoeff; + switch (regionOp) { + case SkRegion::kReplace_Op: + srcCoeff = SkBlendModeCoeff::kOne; + dstCoeff = SkBlendModeCoeff::kZero; + break; + case SkRegion::kIntersect_Op: + srcCoeff = SkBlendModeCoeff::kDC; + dstCoeff = SkBlendModeCoeff::kZero; + break; + case SkRegion::kUnion_Op: + srcCoeff = SkBlendModeCoeff::kOne; + dstCoeff = SkBlendModeCoeff::kISC; + break; + case SkRegion::kXOR_Op: + srcCoeff = SkBlendModeCoeff::kIDC; + dstCoeff = SkBlendModeCoeff::kISC; + break; + case SkRegion::kDifference_Op: + srcCoeff = SkBlendModeCoeff::kZero; + dstCoeff = SkBlendModeCoeff::kISC; + break; + case SkRegion::kReverseDifference_Op: + srcCoeff = SkBlendModeCoeff::kIDC; + dstCoeff = SkBlendModeCoeff::kZero; + break; + default: + SK_ABORT("Unsupported Op"); + // We should never get here but to make compiler happy + srcCoeff = SkBlendModeCoeff::kZero; + dstCoeff = SkBlendModeCoeff::kZero; + } + fsBuilder->codeAppendf("%s = ", outColor); + // append src blend + bool didAppend = append_porterduff_term(fsBuilder, srcCoeff, srcColor, srcColor, dstColor, + false); + // append dst blend + if(!append_porterduff_term(fsBuilder, dstCoeff, dstColor, srcColor, dstColor, didAppend)) { + fsBuilder->codeAppend("half4(0, 0, 0, 0)"); + } + fsBuilder->codeAppend(";"); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.h new file mode 100644 index 0000000000..69b821e158 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLBlend.h @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLBlend_DEFINED +#define GrGLBlend_DEFINED + +#include "include/core/SkBlendMode.h" +#include "include/core/SkRegion.h" + +class GrGLSLFragmentBuilder; + +namespace GrGLSLBlend { + /* + * Appends GLSL code to fsBuilder that assigns a specified blend of the srcColor and dstColor + * variables to the outColor variable. + */ + void AppendMode(GrGLSLFragmentBuilder* fsBuilder, const char* srcColor, + const char* dstColor, const char* outColor, SkBlendMode mode); + + void AppendRegionOp(GrGLSLFragmentBuilder* fsBuilder, const char* srcColor, + const char* dstColor, const char* outColor, SkRegion::Op regionOp); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h new file mode 100644 index 0000000000..1e68897b1b --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLColorSpaceXformHelper_DEFINED +#define GrGLSLColorSpaceXformHelper_DEFINED + +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +/** + * Helper class to assist with using GrColorSpaceXform within an FP. This manages all of the + * uniforms needed, and can be passed to shader builder functions to automatically generate the + * correct color space transformation code. + */ +class GrGLSLColorSpaceXformHelper : public SkNoncopyable { +public: + GrGLSLColorSpaceXformHelper() { + memset(&fFlags, 0, sizeof(fFlags)); + } + + void emitCode(GrGLSLUniformHandler* uniformHandler, const GrColorSpaceXform* colorSpaceXform, + uint32_t visibility = kFragment_GrShaderFlag) { + SkASSERT(uniformHandler); + if (colorSpaceXform) { + fFlags = colorSpaceXform->fSteps.flags; + if (this->applySrcTF()) { + fSrcTFVar = uniformHandler->addUniformArray(visibility, kHalf_GrSLType, + "SrcTF", kNumTransferFnCoeffs); + fSrcTFKind = classify_transfer_fn(colorSpaceXform->fSteps.srcTF); + } + if (this->applyGamutXform()) { + fGamutXformVar = uniformHandler->addUniform(visibility, kHalf3x3_GrSLType, + "ColorXform"); + } + if (this->applyDstTF()) { + fDstTFVar = uniformHandler->addUniformArray(visibility, kHalf_GrSLType, + "DstTF", kNumTransferFnCoeffs); + fDstTFKind = classify_transfer_fn(colorSpaceXform->fSteps.dstTFInv); + } + } + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrColorSpaceXform* colorSpaceXform) { + if (this->applySrcTF()) { + pdman.set1fv(fSrcTFVar, kNumTransferFnCoeffs, &colorSpaceXform->fSteps.srcTF.g); + } + if (this->applyGamutXform()) { + pdman.setMatrix3f(fGamutXformVar, colorSpaceXform->fSteps.src_to_dst_matrix); + } + if (this->applyDstTF()) { + pdman.set1fv(fDstTFVar, kNumTransferFnCoeffs, &colorSpaceXform->fSteps.dstTFInv.g); + } + } + + bool isNoop() const { return (0 == fFlags.mask()); } + + bool applyUnpremul() const { return fFlags.unpremul; } + bool applySrcTF() const { return fFlags.linearize; } + bool applyGamutXform() const { return fFlags.gamut_transform; } + bool applyDstTF() const { return fFlags.encode; } + bool applyPremul() const { return fFlags.premul; } + + TFKind srcTFKind() const { return fSrcTFKind; } + TFKind dstTFKind() const { return fDstTFKind; } + + GrGLSLProgramDataManager::UniformHandle srcTFUniform() const { return fSrcTFVar; } + GrGLSLProgramDataManager::UniformHandle gamutXformUniform() const { return fGamutXformVar; } + GrGLSLProgramDataManager::UniformHandle dstTFUniform() const { return fDstTFVar; } + +private: + static const int kNumTransferFnCoeffs = 7; + + GrGLSLProgramDataManager::UniformHandle fSrcTFVar; + GrGLSLProgramDataManager::UniformHandle fGamutXformVar; + GrGLSLProgramDataManager::UniformHandle fDstTFVar; + SkColorSpaceXformSteps::Flags fFlags; + TFKind fSrcTFKind; + TFKind fDstTFKind; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.cpp new file mode 100644 index 0000000000..e5e97deccf --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +void GrGLSLFragmentProcessor::setData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + this->onSetData(pdman, processor); +} + +void GrGLSLFragmentProcessor::invokeChild(int childIndex, const char* inputColor, EmitArgs& args, + SkSL::String skslCoords) { + while (childIndex >= (int) fFunctionNames.size()) { + fFunctionNames.emplace_back(); + } + this->internalInvokeChild(childIndex, inputColor, args.fOutputColor, args, skslCoords); +} + +void GrGLSLFragmentProcessor::writeChildCall(GrGLSLFPFragmentBuilder* fragBuilder, int childIndex, + TransformedCoordVars coordVars, const char* inputColor, + const char* outputColor, EmitArgs& args, + SkSL::String skslCoords) { + std::vector coordParams; + for (int i = 0; i < coordVars.count(); ++i) { + coordParams.push_back(fragBuilder->ensureCoords2D(coordVars[i].fVaryingPoint)); + } + // if the fragment processor is invoked with overridden coordinates, it must *always* be invoked + // with overridden coords + SkASSERT(args.fFp.computeLocalCoordsInVertexShader() == (skslCoords.length() == 0)); + fragBuilder->codeAppendf("%s = %s(%s", outputColor, fFunctionNames[childIndex].c_str(), + inputColor ? inputColor : "half4(1)"); + if (skslCoords.length()) { + fragBuilder->codeAppendf(", %s", skslCoords.c_str()); + } + fragBuilder->codeAppend(");\n"); +} + +void GrGLSLFragmentProcessor::invokeChild(int childIndex, const char* inputColor, + SkString* outputColor, EmitArgs& args, + SkSL::String skslCoords) { + SkASSERT(outputColor); + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + outputColor->append(fragBuilder->getMangleString()); + fragBuilder->codeAppendf("half4 %s;", outputColor->c_str()); + while (childIndex >= (int) fFunctionNames.size()) { + fFunctionNames.emplace_back(); + } + if (!args.fFp.computeLocalCoordsInVertexShader() && skslCoords.length() == 0) { + skslCoords = "_coords"; + } + if (fFunctionNames[childIndex].size() == 0) { + this->internalInvokeChild(childIndex, inputColor, outputColor->c_str(), args, skslCoords); + } else { + const GrFragmentProcessor& childProc = args.fFp.childProcessor(childIndex); + + TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex); + TextureSamplers textureSamplers = args.fTexSamplers.childInputs(childIndex); + EmitArgs childArgs(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + childProc, + outputColor->c_str(), + "_input", + coordVars, + textureSamplers); + this->writeChildCall(fragBuilder, childIndex, coordVars, inputColor, outputColor->c_str(), + childArgs, skslCoords); + } +} + +void GrGLSLFragmentProcessor::internalInvokeChild(int childIndex, const char* inputColor, + const char* outputColor, EmitArgs& args, + SkSL::String skslCoords) { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + fragBuilder->onBeforeChildProcEmitCode(); // call first so mangleString is updated + + // Prepare a mangled input color variable if the default is not used, + // inputName remains the empty string if no variable is needed. + SkString inputName; + if (inputColor&& strcmp("half4(1.0)", inputColor) != 0 && strcmp("half4(1)", inputColor) != 0) { + // The input name is based off of the current mangle string, and + // since this is called after onBeforeChildProcEmitCode(), it will be + // unique to the child processor (exactly what we want for its input). + inputName.appendf("_childInput%s", fragBuilder->getMangleString().c_str()); + fragBuilder->codeAppendf("half4 %s = %s;", inputName.c_str(), inputColor); + } + + const GrFragmentProcessor& childProc = args.fFp.childProcessor(childIndex); + TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex); + TextureSamplers textureSamplers = args.fTexSamplers.childInputs(childIndex); + + EmitArgs childArgs(fragBuilder, + args.fUniformHandler, + args.fShaderCaps, + childProc, + outputColor, + "_input", + coordVars, + textureSamplers); + fFunctionNames[childIndex] = fragBuilder->writeProcessorFunction( + this->childProcessor(childIndex), + childArgs); + this->writeChildCall(fragBuilder, childIndex, coordVars, inputColor, outputColor, childArgs, + skslCoords); + fragBuilder->onAfterChildProcEmitCode(); +} + +////////////////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrGLSLFragmentProcessor::Iter::next() { + if (fFPStack.empty()) { + return nullptr; + } + GrGLSLFragmentProcessor* back = fFPStack.back(); + fFPStack.pop_back(); + for (int i = back->numChildProcessors() - 1; i >= 0; --i) { + fFPStack.push_back(back->childProcessor(i)); + } + return back; +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.h new file mode 100644 index 0000000000..ad441d8efc --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentProcessor.h @@ -0,0 +1,211 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLFragmentProcessor_DEFINED +#define GrGLSLFragmentProcessor_DEFINED + +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/sksl/SkSLString.h" + +class GrProcessor; +class GrProcessorKeyBuilder; +class GrGLSLFPBuilder; +class GrGLSLFPFragmentBuilder; + +class GrGLSLFragmentProcessor { +public: + GrGLSLFragmentProcessor() {} + + virtual ~GrGLSLFragmentProcessor() { + for (int i = 0; i < fChildProcessors.count(); ++i) { + delete fChildProcessors[i]; + } + } + + using UniformHandle = GrGLSLUniformHandler::UniformHandle; + using SamplerHandle = GrGLSLUniformHandler::SamplerHandle; + +private: + /** + * This class allows the shader builder to provide each GrGLSLFragmentProcesor with an array of + * generated variables where each generated variable corresponds to an element of an array on + * the GrFragmentProcessor that generated the GLSLFP. For example, this is used to provide a + * variable holding transformed coords for each GrCoordTransform owned by the FP. + */ + template + class BuilderInputProvider { + public: + BuilderInputProvider(const GrFragmentProcessor* fp, const T* ts) : fFP(fp) , fTs(ts) {} + + const T& operator[] (int i) const { + SkASSERT(i >= 0 && i < (fFP->*COUNT)()); + return fTs[i]; + } + + int count() const { return (fFP->*COUNT)(); } + + BuilderInputProvider childInputs(int childIdx) const { + const GrFragmentProcessor* child = &fFP->childProcessor(childIdx); + GrFragmentProcessor::Iter iter(fFP); + int numToSkip = 0; + while (true) { + const GrFragmentProcessor* fp = iter.next(); + if (fp == child) { + return BuilderInputProvider(child, fTs + numToSkip); + } + numToSkip += (fp->*COUNT)(); + } + } + + private: + const GrFragmentProcessor* fFP; + const T* fTs; + }; + +public: + using TransformedCoordVars = BuilderInputProvider; + using TextureSamplers = + BuilderInputProvider; + + /** Called when the program stage should insert its code into the shaders. The code in each + shader will be in its own block ({}) and so locally scoped names will not collide across + stages. + + @param fragBuilder Interface used to emit code in the shaders. + @param fp The processor that generated this program stage. + @param key The key that was computed by GenKey() from the generating + GrProcessor. + @param outputColor A predefined half4 in the FS in which the stage should place its + output color (or coverage). + @param inputColor A half4 that holds the input color to the stage in the FS. This may + be nullptr in which case the fInputColor is set to "half4(1.0)" + (solid white) so this is guaranteed non-null. + TODO: Better system for communicating optimization info + (e.g. input color is solid white, trans black, known to be opaque, + etc.) that allows the processor to communicate back similar known + info about its output. + @param transformedCoords Fragment shader variables containing the coords computed using + each of the GrFragmentProcessor's GrCoordTransforms. + @param texSamplers Contains one entry for each TextureSampler of the GrProcessor. + These can be passed to the builder to emit texture reads in the + generated code. + */ + struct EmitArgs { + EmitArgs(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* caps, + const GrFragmentProcessor& fp, + const char* outputColor, + const char* inputColor, + const TransformedCoordVars& transformedCoordVars, + const TextureSamplers& textureSamplers) + : fFragBuilder(fragBuilder) + , fUniformHandler(uniformHandler) + , fShaderCaps(caps) + , fFp(fp) + , fOutputColor(outputColor) + , fInputColor(inputColor ? inputColor : "half4(1.0)") + , fTransformedCoords(transformedCoordVars) + , fTexSamplers(textureSamplers) {} + GrGLSLFPFragmentBuilder* fFragBuilder; + GrGLSLUniformHandler* fUniformHandler; + const GrShaderCaps* fShaderCaps; + const GrFragmentProcessor& fFp; + const char* fOutputColor; + const char* fInputColor; + const TransformedCoordVars& fTransformedCoords; + const TextureSamplers& fTexSamplers; + }; + + virtual void emitCode(EmitArgs&) = 0; + + // This does not recurse to any attached child processors. Recursing the entire processor tree + // is the responsibility of the caller. + void setData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor); + + int numChildProcessors() const { return fChildProcessors.count(); } + + GrGLSLFragmentProcessor* childProcessor(int index) { + return fChildProcessors[index]; + } + + // Invoke the child with the default input color (solid white) + inline void invokeChild(int childIndex, SkString* outputColor, EmitArgs& parentArgs, + SkSL::String skslCoords = "") { + this->invokeChild(childIndex, nullptr, outputColor, parentArgs, skslCoords); + } + + /** Invokes a child proc in its own scope. Pass in the parent's EmitArgs and invokeChild will + * automatically extract the coords and samplers of that child and pass them on to the child's + * emitCode(). Also, any uniforms or functions emitted by the child will have their names + * mangled to prevent redefinitions. The output color name is also mangled therefore in an + * in/out param. It will be declared in mangled form by invokeChild(). It is legal to pass + * nullptr as inputColor, since all fragment processors are required to work without an input + * color. + */ + void invokeChild(int childIndex, const char* inputColor, SkString* outputColor, + EmitArgs& parentArgs, SkSL::String skslCoords = ""); + + // Use the parent's output color to hold child's output, and use the + // default input color of solid white + inline void invokeChild(int childIndex, EmitArgs& args, SkSL::String skslCoords = "") { + // null pointer cast required to disambiguate the function call + this->invokeChild(childIndex, (const char*) nullptr, args, skslCoords); + } + + /** Variation that uses the parent's output color variable to hold the child's output.*/ + void invokeChild(int childIndex, const char* inputColor, EmitArgs& parentArgs, + SkSL::String skslCoords = ""); + + /** + * Pre-order traversal of a GLSLFP hierarchy, or of multiple trees with roots in an array of + * GLSLFPS. This agrees with the traversal order of GrFragmentProcessor::Iter + */ + class Iter : public SkNoncopyable { + public: + explicit Iter(GrGLSLFragmentProcessor* fp) { fFPStack.push_back(fp); } + explicit Iter(std::unique_ptr fps[], int cnt) { + for (int i = cnt - 1; i >= 0; --i) { + fFPStack.push_back(fps[i].get()); + } + } + GrGLSLFragmentProcessor* next(); + + private: + SkSTArray<4, GrGLSLFragmentProcessor*, true> fFPStack; + }; + +protected: + /** A GrGLSLFragmentProcessor instance can be reused with any GrFragmentProcessor that produces + the same stage key; this function reads data from a GrFragmentProcessor and uploads any + uniform variables required by the shaders created in emitCode(). The GrFragmentProcessor + parameter is guaranteed to be of the same type that created this GrGLSLFragmentProcessor and + to have an identical processor key as the one that created this GrGLSLFragmentProcessor. */ + virtual void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) {} + +private: + void writeChildCall(GrGLSLFPFragmentBuilder* fragBuilder, int childIndex, + TransformedCoordVars coordVars, const char* inputColor, + const char* outputColor, EmitArgs& args, + SkSL::String skslCoords); + + void internalInvokeChild(int, const char*, const char*, EmitArgs&, SkSL::String); + + // one per child; either not present or empty string if not yet emitted + SkTArray fFunctionNames; + + SkTArray fChildProcessors; + + friend class GrFragmentProcessor; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp new file mode 100644 index 0000000000..85dc827f73 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp @@ -0,0 +1,322 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/gl/GrGLGpu.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +const char* GrGLSLFragmentShaderBuilder::kDstColorName = "_dstColor"; + +static const char* specific_layout_qualifier_name(GrBlendEquation equation) { + SkASSERT(GrBlendEquationIsAdvanced(equation)); + + static const char* kLayoutQualifierNames[] = { + "blend_support_screen", + "blend_support_overlay", + "blend_support_darken", + "blend_support_lighten", + "blend_support_colordodge", + "blend_support_colorburn", + "blend_support_hardlight", + "blend_support_softlight", + "blend_support_difference", + "blend_support_exclusion", + "blend_support_multiply", + "blend_support_hsl_hue", + "blend_support_hsl_saturation", + "blend_support_hsl_color", + "blend_support_hsl_luminosity" + }; + return kLayoutQualifierNames[equation - kFirstAdvancedGrBlendEquation]; + + GR_STATIC_ASSERT(0 == kScreen_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(1 == kOverlay_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(2 == kDarken_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(3 == kLighten_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(4 == kColorDodge_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(5 == kColorBurn_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(6 == kHardLight_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(7 == kSoftLight_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(8 == kDifference_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(9 == kExclusion_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(10 == kMultiply_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(11 == kHSLHue_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(12 == kHSLSaturation_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(13 == kHSLColor_GrBlendEquation - kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(14 == kHSLLuminosity_GrBlendEquation - kFirstAdvancedGrBlendEquation); + // There's an illegal GrBlendEquation at the end there, hence the -1. + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kLayoutQualifierNames) == + kGrBlendEquationCnt - kFirstAdvancedGrBlendEquation - 1); +} + +uint8_t GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(GrSurfaceOrigin origin) { + SkASSERT(kTopLeft_GrSurfaceOrigin == origin || kBottomLeft_GrSurfaceOrigin == origin); + return origin + 1; + + GR_STATIC_ASSERT(0 == kTopLeft_GrSurfaceOrigin); + GR_STATIC_ASSERT(1 == kBottomLeft_GrSurfaceOrigin); +} + +GrGLSLFragmentShaderBuilder::GrGLSLFragmentShaderBuilder(GrGLSLProgramBuilder* program) + : GrGLSLFragmentBuilder(program) { + fSubstageIndices.push_back(0); +} + +SkString GrGLSLFragmentShaderBuilder::ensureCoords2D(const GrShaderVar& coords) { + if (kFloat3_GrSLType != coords.getType() && kHalf3_GrSLType != coords.getType()) { + SkASSERT(kFloat2_GrSLType == coords.getType() || kHalf2_GrSLType == coords.getType()); + return coords.getName(); + } + + SkString coords2D; + coords2D.printf("%s_ensure2D", coords.c_str()); + this->codeAppendf("\tfloat2 %s = %s.xy / %s.z;", coords2D.c_str(), coords.c_str(), + coords.c_str()); + return coords2D; +} + +const char* GrGLSLFragmentShaderBuilder::sampleOffsets() { + SkASSERT(CustomFeatures::kSampleLocations & fProgramBuilder->processorFeatures()); + SkDEBUGCODE(fUsedProcessorFeaturesThisStage_DebugOnly |= CustomFeatures::kSampleLocations); + SkDEBUGCODE(fUsedProcessorFeaturesAllStages_DebugOnly |= CustomFeatures::kSampleLocations); + return "_sampleOffsets"; +} + +void GrGLSLFragmentShaderBuilder::maskOffMultisampleCoverage( + const char* mask, ScopeFlags scopeFlags) { + const GrShaderCaps& shaderCaps = *fProgramBuilder->shaderCaps(); + if (!shaderCaps.sampleVariablesSupport() && !shaderCaps.sampleVariablesStencilSupport()) { + SkDEBUGFAIL("Attempted to mask sample coverage without support."); + return; + } + if (const char* extension = shaderCaps.sampleVariablesExtensionString()) { + this->addFeature(1 << kSampleVariables_GLSLPrivateFeature, extension); + } + + if (!fHasModifiedSampleMask) { + fHasModifiedSampleMask = true; + if (ScopeFlags::kTopLevel != scopeFlags) { + this->codePrependf("gl_SampleMask[0] = ~0;"); + } + if (!(ScopeFlags::kInsideLoop & scopeFlags)) { + this->codeAppendf("gl_SampleMask[0] = (%s);", mask); + return; + } + } + + this->codeAppendf("gl_SampleMask[0] &= (%s);", mask); +} + +void GrGLSLFragmentShaderBuilder::applyFnToMultisampleMask( + const char* fn, const char* grad, ScopeFlags scopeFlags) { + SkASSERT(CustomFeatures::kSampleLocations & fProgramBuilder->processorFeatures()); + SkDEBUGCODE(fUsedProcessorFeaturesThisStage_DebugOnly |= CustomFeatures::kSampleLocations); + SkDEBUGCODE(fUsedProcessorFeaturesAllStages_DebugOnly |= CustomFeatures::kSampleLocations); + + int sampleCnt = fProgramBuilder->effectiveSampleCnt(); + SkASSERT(sampleCnt > 1); + + this->codeAppendf("{"); + + if (!grad) { + SkASSERT(fProgramBuilder->shaderCaps()->shaderDerivativeSupport()); + // In order to use HW derivatives, our neighbors within the same primitive must also be + // executing the same code. A per-pixel branch makes this pre-condition impossible to + // fulfill. + SkASSERT(!(ScopeFlags::kInsidePerPixelBranch & scopeFlags)); + this->codeAppendf("float2 grad = float2(dFdx(%s), dFdy(%s));", fn, fn); + this->codeAppendf("float fnwidth = fwidth(%s);", fn); + grad = "grad"; + } else { + this->codeAppendf("float fnwidth = abs(%s.x) + abs(%s.y);", grad, grad); + } + + this->codeAppendf("int mask = 0;"); + this->codeAppendf("if (%s*2 < fnwidth) {", fn); // Are ANY samples inside the implicit fn? + this->codeAppendf( "if (%s*-2 >= fnwidth) {", fn); // Are ALL samples inside the implicit? + this->codeAppendf( "mask = ~0;"); + this->codeAppendf( "} else for (int i = 0; i < %i; ++i) {", sampleCnt); + this->codeAppendf( "float fnsample = dot(%s, _sampleOffsets[i]) + %s;", grad, fn); + this->codeAppendf( "if (fnsample < 0) {"); + this->codeAppendf( "mask |= (1 << i);"); + this->codeAppendf( "}"); + this->codeAppendf( "}"); + this->codeAppendf("}"); + this->maskOffMultisampleCoverage("mask", scopeFlags); + + this->codeAppendf("}"); +} + +SkString GrGLSLFPFragmentBuilder::writeProcessorFunction(GrGLSLFragmentProcessor* fp, + GrGLSLFragmentProcessor::EmitArgs& args) { + this->onBeforeChildProcEmitCode(); + this->nextStage(); + if (!args.fFp.computeLocalCoordsInVertexShader() && args.fTransformedCoords.count() > 0) { + // we currently only support overriding a single coordinate pair + SkASSERT(args.fTransformedCoords.count() == 1); + const GrGLSLProgramDataManager::UniformHandle& mat = + args.fTransformedCoords[0].fUniformMatrix; + if (mat.isValid()) { + args.fUniformHandler->updateUniformVisibility(mat, kFragment_GrShaderFlag); + this->codeAppendf("_coords = (float3(_coords, 1) * %s).xy;\n", + args.fTransformedCoords[0].fMatrixCode.c_str()); + } + } + this->codeAppendf("half4 %s;\n", args.fOutputColor); + fp->emitCode(args); + this->codeAppendf("return %s;\n", args.fOutputColor); + GrShaderVar params[] = { GrShaderVar(args.fInputColor, kHalf4_GrSLType), + GrShaderVar("_coords", kFloat2_GrSLType) }; + SkString result; + this->emitFunction(kHalf4_GrSLType, + "stage", + args.fFp.computeLocalCoordsInVertexShader() ? 1 : 2, + params, + this->code().c_str(), + &result); + this->deleteStage(); + this->onAfterChildProcEmitCode(); + return result; +} + +const char* GrGLSLFragmentShaderBuilder::dstColor() { + SkDEBUGCODE(fHasReadDstColorThisStage_DebugOnly = true;) + + const GrShaderCaps* shaderCaps = fProgramBuilder->shaderCaps(); + if (shaderCaps->fbFetchSupport()) { + this->addFeature(1 << kFramebufferFetch_GLSLPrivateFeature, + shaderCaps->fbFetchExtensionString()); + + // Some versions of this extension string require declaring custom color output on ES 3.0+ + const char* fbFetchColorName = "sk_LastFragColor"; + if (shaderCaps->fbFetchNeedsCustomOutput()) { + this->enableCustomOutput(); + fOutputs[fCustomColorOutputIndex].setTypeModifier(GrShaderVar::kInOut_TypeModifier); + fbFetchColorName = DeclaredColorOutputName(); + // Set the dstColor to an intermediate variable so we don't override it with the output + this->codeAppendf("half4 %s = %s;", kDstColorName, fbFetchColorName); + } else { + return fbFetchColorName; + } + } + return kDstColorName; +} + +void GrGLSLFragmentShaderBuilder::enableAdvancedBlendEquationIfNeeded(GrBlendEquation equation) { + SkASSERT(GrBlendEquationIsAdvanced(equation)); + + const GrShaderCaps& caps = *fProgramBuilder->shaderCaps(); + if (!caps.mustEnableAdvBlendEqs()) { + return; + } + + this->addFeature(1 << kBlendEquationAdvanced_GLSLPrivateFeature, + "GL_KHR_blend_equation_advanced"); + if (caps.mustEnableSpecificAdvBlendEqs()) { + this->addLayoutQualifier(specific_layout_qualifier_name(equation), kOut_InterfaceQualifier); + } else { + this->addLayoutQualifier("blend_support_all_equations", kOut_InterfaceQualifier); + } +} + +void GrGLSLFragmentShaderBuilder::enableCustomOutput() { + if (!fHasCustomColorOutput) { + fHasCustomColorOutput = true; + fCustomColorOutputIndex = fOutputs.count(); + fOutputs.push_back().set(kHalf4_GrSLType, DeclaredColorOutputName(), + GrShaderVar::kOut_TypeModifier); + fProgramBuilder->finalizeFragmentOutputColor(fOutputs.back()); + } +} + +void GrGLSLFragmentShaderBuilder::enableSecondaryOutput() { + SkASSERT(!fHasSecondaryOutput); + fHasSecondaryOutput = true; + const GrShaderCaps& caps = *fProgramBuilder->shaderCaps(); + if (const char* extension = caps.secondaryOutputExtensionString()) { + this->addFeature(1 << kBlendFuncExtended_GLSLPrivateFeature, extension); + } + + // If the primary output is declared, we must declare also the secondary output + // and vice versa, since it is not allowed to use a built-in gl_FragColor and a custom + // output. The condition also co-incides with the condition in whici GLES SL 2.0 + // requires the built-in gl_SecondaryFragColorEXT, where as 3.0 requires a custom output. + if (caps.mustDeclareFragmentShaderOutput()) { + fOutputs.push_back().set(kHalf4_GrSLType, DeclaredSecondaryColorOutputName(), + GrShaderVar::kOut_TypeModifier); + fProgramBuilder->finalizeFragmentSecondaryColor(fOutputs.back()); + } +} + +const char* GrGLSLFragmentShaderBuilder::getPrimaryColorOutputName() const { + return fHasCustomColorOutput ? DeclaredColorOutputName() : "sk_FragColor"; +} + +bool GrGLSLFragmentShaderBuilder::primaryColorOutputIsInOut() const { + return fHasCustomColorOutput && + fOutputs[fCustomColorOutputIndex].getTypeModifier() == GrShaderVar::kInOut_TypeModifier; +} + +void GrGLSLFragmentBuilder::declAppendf(const char* fmt, ...) { + va_list argp; + va_start(argp, fmt); + inputs().appendVAList(fmt, argp); + va_end(argp); +} + +const char* GrGLSLFragmentShaderBuilder::getSecondaryColorOutputName() const { + if (this->hasSecondaryOutput()) { + return (fProgramBuilder->shaderCaps()->mustDeclareFragmentShaderOutput()) + ? DeclaredSecondaryColorOutputName() + : "gl_SecondaryFragColorEXT"; + } + return nullptr; +} + +GrSurfaceOrigin GrGLSLFragmentShaderBuilder::getSurfaceOrigin() const { + return fProgramBuilder->origin(); +} + +void GrGLSLFragmentShaderBuilder::onFinalize() { + SkASSERT(fProgramBuilder->processorFeatures() == fUsedProcessorFeaturesAllStages_DebugOnly); + + if (CustomFeatures::kSampleLocations & fProgramBuilder->processorFeatures()) { + const SkTArray& sampleLocations = fProgramBuilder->getSampleLocations(); + this->definitions().append("const float2 _sampleOffsets[] = float2[]("); + for (int i = 0; i < sampleLocations.count(); ++i) { + SkPoint offset = sampleLocations[i] - SkPoint::Make(.5f, .5f); + if (kBottomLeft_GrSurfaceOrigin == this->getSurfaceOrigin()) { + offset.fY = -offset.fY; + } + this->definitions().appendf("float2(%f, %f)", offset.x(), offset.y()); + this->definitions().append((i + 1 != sampleLocations.count()) ? ", " : ");"); + } + } + + fProgramBuilder->varyingHandler()->getFragDecls(&this->inputs(), &this->outputs()); +} + +void GrGLSLFragmentShaderBuilder::onBeforeChildProcEmitCode() { + SkASSERT(fSubstageIndices.count() >= 1); + fSubstageIndices.push_back(0); + // second-to-last value in the fSubstageIndices stack is the index of the child proc + // at that level which is currently emitting code. + fMangleString.appendf("_c%d", fSubstageIndices[fSubstageIndices.count() - 2]); +} + +void GrGLSLFragmentShaderBuilder::onAfterChildProcEmitCode() { + SkASSERT(fSubstageIndices.count() >= 2); + fSubstageIndices.pop_back(); + fSubstageIndices.back()++; + int removeAt = fMangleString.findLastOf('_'); + fMangleString.remove(removeAt, fMangleString.size() - removeAt); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h new file mode 100644 index 0000000000..6d5bfe862c --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h @@ -0,0 +1,224 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLFragmentShaderBuilder_DEFINED +#define GrGLSLFragmentShaderBuilder_DEFINED + +#include "src/gpu/GrBlend.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLShaderBuilder.h" + +class GrRenderTarget; +class GrGLSLVarying; + +/* + * This base class encapsulates the common functionality which all processors use to build fragment + * shaders. + */ +class GrGLSLFragmentBuilder : public GrGLSLShaderBuilder { +public: + GrGLSLFragmentBuilder(GrGLSLProgramBuilder* program) : INHERITED(program) {} + virtual ~GrGLSLFragmentBuilder() {} + + /** + * This returns a variable name to access the 2D, perspective correct version of the coords in + * the fragment shader. The passed in coordinates must either be of type kHalf2 or kHalf3. If + * the coordinates are 3-dimensional, it a perspective divide into is emitted into the + * fragment shader (xy / z) to convert them to 2D. + */ + virtual SkString ensureCoords2D(const GrShaderVar&) = 0; + + // TODO: remove this method. + void declAppendf(const char* fmt, ...); + +private: + typedef GrGLSLShaderBuilder INHERITED; +}; + +/* + * This class is used by fragment processors to build their fragment code. + */ +class GrGLSLFPFragmentBuilder : virtual public GrGLSLFragmentBuilder { +public: + /** Appease the compiler; the derived class initializes GrGLSLFragmentBuilder. */ + GrGLSLFPFragmentBuilder() : GrGLSLFragmentBuilder(nullptr) {} + + /** + * Returns the variable name that holds the array of sample offsets from pixel center to each + * sample location. Before this is called, a processor must have advertised that it will use + * CustomFeatures::kSampleLocations. + */ + virtual const char* sampleOffsets() = 0; + + enum class ScopeFlags { + // Every fragment will always execute this code, and will do it exactly once. + kTopLevel = 0, + // Either all fragments in a given primitive, or none, will execute this code. + kInsidePerPrimitiveBranch = (1 << 0), + // Any given fragment may or may not execute this code. + kInsidePerPixelBranch = (1 << 1), + // This code will be executed more than once. + kInsideLoop = (1 << 2) + }; + + /** + * Subtracts multisample coverage by AND-ing the sample mask with the provided "mask". + * Sample N corresponds to bit "1 << N". + * + * If the given scope is "kTopLevel" and the sample mask has not yet been modified, this method + * assigns the sample mask in place rather than pre-initializing it to ~0 then AND-ing it. + * + * Requires MSAA and GLSL support for sample variables. + */ + virtual void maskOffMultisampleCoverage(const char* mask, ScopeFlags) = 0; + + /** + * Turns off coverage at each sample where the implicit function fn > 0. + * + * The provided "fn" value represents the implicit function at pixel center. We then approximate + * the implicit at each sample by riding the gradient, "grad", linearly from pixel center to + * each sample location. + * + * If "grad" is null, we approximate the gradient using HW derivatives. + * + * Requires MSAA and GLSL support for sample variables. Also requires HW derivatives if not + * providing a gradient. + */ + virtual void applyFnToMultisampleMask(const char* fn, const char* grad, ScopeFlags) = 0; + + /** + * Fragment procs with child procs should call these functions before/after calling emitCode + * on a child proc. + */ + virtual void onBeforeChildProcEmitCode() = 0; + virtual void onAfterChildProcEmitCode() = 0; + + virtual SkString writeProcessorFunction(GrGLSLFragmentProcessor* fp, + GrGLSLFragmentProcessor::EmitArgs& args); + + virtual const SkString& getMangleString() const = 0; + + virtual void forceHighPrecision() = 0; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrGLSLFPFragmentBuilder::ScopeFlags); + +/* + * This class is used by Xfer processors to build their fragment code. + */ +class GrGLSLXPFragmentBuilder : virtual public GrGLSLFragmentBuilder { +public: + /** Appease the compiler; the derived class initializes GrGLSLFragmentBuilder. */ + GrGLSLXPFragmentBuilder() : GrGLSLFragmentBuilder(nullptr) {} + + virtual bool hasCustomColorOutput() const = 0; + virtual bool hasSecondaryOutput() const = 0; + + /** Returns the variable name that holds the color of the destination pixel. This may be nullptr + * if no effect advertised that it will read the destination. */ + virtual const char* dstColor() = 0; + + /** Adds any necessary layout qualifiers in order to legalize the supplied blend equation with + this shader. It is only legal to call this method with an advanced blend equation, and only + if these equations are supported. */ + virtual void enableAdvancedBlendEquationIfNeeded(GrBlendEquation) = 0; +}; + +/* + * This class implements the various fragment builder interfaces. + */ +class GrGLSLFragmentShaderBuilder : public GrGLSLFPFragmentBuilder, public GrGLSLXPFragmentBuilder { +public: + /** Returns a nonzero key for a surface's origin. This should only be called if a processor will + use the fragment position and/or sample locations. */ + static uint8_t KeyForSurfaceOrigin(GrSurfaceOrigin); + + GrGLSLFragmentShaderBuilder(GrGLSLProgramBuilder* program); + + // Shared GrGLSLFragmentBuilder interface. + virtual SkString ensureCoords2D(const GrShaderVar&) override; + + // GrGLSLFPFragmentBuilder interface. + const char* sampleOffsets() override; + void maskOffMultisampleCoverage(const char* mask, ScopeFlags) override; + void applyFnToMultisampleMask(const char* fn, const char* grad, ScopeFlags) override; + const SkString& getMangleString() const override { return fMangleString; } + void onBeforeChildProcEmitCode() override; + void onAfterChildProcEmitCode() override; + void forceHighPrecision() override { fForceHighPrecision = true; } + + // GrGLSLXPFragmentBuilder interface. + bool hasCustomColorOutput() const override { return fHasCustomColorOutput; } + bool hasSecondaryOutput() const override { return fHasSecondaryOutput; } + const char* dstColor() override; + void enableAdvancedBlendEquationIfNeeded(GrBlendEquation) override; + +private: + using CustomFeatures = GrProcessor::CustomFeatures; + + // Private public interface, used by GrGLProgramBuilder to build a fragment shader + void enableCustomOutput(); + void enableSecondaryOutput(); + const char* getPrimaryColorOutputName() const; + const char* getSecondaryColorOutputName() const; + bool primaryColorOutputIsInOut() const; + +#ifdef SK_DEBUG + // As GLSLProcessors emit code, there are some conditions we need to verify. We use the below + // state to track this. The reset call is called per processor emitted. + bool fHasReadDstColorThisStage_DebugOnly = false; + CustomFeatures fUsedProcessorFeaturesThisStage_DebugOnly = CustomFeatures::kNone; + CustomFeatures fUsedProcessorFeaturesAllStages_DebugOnly = CustomFeatures::kNone; + + void debugOnly_resetPerStageVerification() { + fHasReadDstColorThisStage_DebugOnly = false; + fUsedProcessorFeaturesThisStage_DebugOnly = CustomFeatures::kNone; + } +#endif + + static const char* DeclaredColorOutputName() { return "sk_FragColor"; } + static const char* DeclaredSecondaryColorOutputName() { return "fsSecondaryColorOut"; } + + GrSurfaceOrigin getSurfaceOrigin() const; + + void onFinalize() override; + + static const char* kDstColorName; + + /* + * State that tracks which child proc in the proc tree is currently emitting code. This is + * used to update the fMangleString, which is used to mangle the names of uniforms and functions + * emitted by the proc. fSubstageIndices is a stack: its count indicates how many levels deep + * we are in the tree, and its second-to-last value is the index of the child proc at that + * level which is currently emitting code. For example, if fSubstageIndices = [3, 1, 2, 0], that + * means we're currently emitting code for the base proc's 3rd child's 1st child's 2nd child. + */ + SkTArray fSubstageIndices; + + /* + * The mangle string is used to mangle the names of uniforms/functions emitted by the child + * procs so no duplicate uniforms/functions appear in the generated shader program. The mangle + * string is simply based on fSubstageIndices. For example, if fSubstageIndices = [3, 1, 2, 0], + * then the manglestring will be "_c3_c1_c2", and any uniform/function emitted by that proc will + * have "_c3_c1_c2" appended to its name, which can be interpreted as "base proc's 3rd child's + * 1st child's 2nd child". + */ + SkString fMangleString; + + bool fSetupFragPosition = false; + bool fHasCustomColorOutput = false; + int fCustomColorOutputIndex = -1; + bool fHasSecondaryOutput = false; + bool fHasModifiedSampleMask = false; + bool fForceHighPrecision = false; + + friend class GrGLSLProgramBuilder; + friend class GrGLProgramBuilder; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.cpp new file mode 100644 index 0000000000..d99239ebb9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +void GrGLSLGeometryProcessor::emitCode(EmitArgs& args) { + GrGPArgs gpArgs; + this->onEmitCode(args, &gpArgs); + + GrGLSLVertexBuilder* vBuilder = args.fVertBuilder; + if (!args.fGP.willUseGeoShader()) { + // Emit the vertex position to the hardware in the normalized window coordinates it expects. + SkASSERT(kFloat2_GrSLType == gpArgs.fPositionVar.getType() || + kFloat3_GrSLType == gpArgs.fPositionVar.getType()); + vBuilder->emitNormalizedSkPosition(gpArgs.fPositionVar.c_str(), args.fRTAdjustName, + gpArgs.fPositionVar.getType()); + if (kFloat2_GrSLType == gpArgs.fPositionVar.getType()) { + args.fVaryingHandler->setNoPerspective(); + } + } else { + // Since we have a geometry shader, leave the vertex position in Skia device space for now. + // The geometry Shader will operate in device space, and then convert the final positions to + // normalized hardware window coordinates under the hood, once everything else has finished. + // The subclass must call setNoPerspective on the varying handler, if applicable. + vBuilder->codeAppendf("sk_Position = float4(%s", gpArgs.fPositionVar.c_str()); + switch (gpArgs.fPositionVar.getType()) { + case kFloat_GrSLType: + vBuilder->codeAppend(", 0"); // fallthru. + case kFloat2_GrSLType: + vBuilder->codeAppend(", 0"); // fallthru. + case kFloat3_GrSLType: + vBuilder->codeAppend(", 1"); // fallthru. + case kFloat4_GrSLType: + vBuilder->codeAppend(");"); + break; + default: + SK_ABORT("Invalid position var type"); + break; + } + } +} + +void GrGLSLGeometryProcessor::emitTransforms(GrGLSLVertexBuilder* vb, + GrGLSLVaryingHandler* varyingHandler, + GrGLSLUniformHandler* uniformHandler, + const GrShaderVar& localCoordsVar, + const SkMatrix& localMatrix, + FPCoordTransformHandler* handler) { + SkASSERT(GrSLTypeIsFloatType(localCoordsVar.getType())); + SkASSERT(2 == GrSLTypeVecLength(localCoordsVar.getType()) || + 3 == GrSLTypeVecLength(localCoordsVar.getType())); + + bool threeComponentLocalCoords = 3 == GrSLTypeVecLength(localCoordsVar.getType()); + SkString localCoords; + if (threeComponentLocalCoords) { + localCoords = localCoordsVar.getName(); + } else { + localCoords.printf("float3(%s, 1)", localCoordsVar.c_str()); + } + int i = 0; + while (const GrCoordTransform* coordTransform = handler->nextCoordTransform()) { + SkString strUniName; + strUniName.printf("CoordTransformMatrix_%d", i); + const char* uniName; + fInstalledTransforms.push_back().fHandle = uniformHandler->addUniform(kVertex_GrShaderFlag, + kFloat3x3_GrSLType, + strUniName.c_str(), + &uniName).toIndex(); + GrSLType varyingType = kFloat2_GrSLType; + if (localMatrix.hasPerspective() || coordTransform->getMatrix().hasPerspective() + || threeComponentLocalCoords) { + varyingType = kFloat3_GrSLType; + } + SkString strVaryingName; + strVaryingName.printf("TransformedCoords_%d", i); + GrGLSLVarying v(varyingType); + if (coordTransform->computeInVertexShader()) { + varyingHandler->addVarying(strVaryingName.c_str(), &v); + + if (kFloat2_GrSLType == varyingType) { + vb->codeAppendf("%s = (%s * %s).xy;", v.vsOut(), uniName, localCoords.c_str()); + } else { + vb->codeAppendf("%s = %s * %s;", v.vsOut(), uniName, localCoords.c_str()); + } + } + handler->specifyCoordsForCurrCoordTransform(SkString(uniName), + fInstalledTransforms.back().fHandle, + GrShaderVar(SkString(v.fsIn()), varyingType)); + ++i; + } +} + +void GrGLSLGeometryProcessor::setTransformDataHelper(const SkMatrix& localMatrix, + const GrGLSLProgramDataManager& pdman, + FPCoordTransformIter* transformIter) { + int i = 0; + while (const GrCoordTransform* coordTransform = transformIter->next()) { + const SkMatrix& m = GetTransformMatrix(localMatrix, *coordTransform); + if (!fInstalledTransforms[i].fCurrentValue.cheapEqualTo(m)) { + pdman.setSkMatrix(fInstalledTransforms[i].fHandle.toIndex(), m); + fInstalledTransforms[i].fCurrentValue = m; + } + ++i; + } + SkASSERT(i == fInstalledTransforms.count()); +} + +void GrGLSLGeometryProcessor::writeOutputPosition(GrGLSLVertexBuilder* vertBuilder, + GrGPArgs* gpArgs, + const char* posName) { + gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos2"); + vertBuilder->codeAppendf("float2 %s = %s;", gpArgs->fPositionVar.c_str(), posName); +} + +void GrGLSLGeometryProcessor::writeOutputPosition(GrGLSLVertexBuilder* vertBuilder, + GrGLSLUniformHandler* uniformHandler, + GrGPArgs* gpArgs, + const char* posName, + const SkMatrix& mat, + UniformHandle* viewMatrixUniform) { + if (mat.isIdentity()) { + gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos2"); + vertBuilder->codeAppendf("float2 %s = %s;", gpArgs->fPositionVar.c_str(), posName); + } else { + const char* viewMatrixName; + *viewMatrixUniform = uniformHandler->addUniform(kVertex_GrShaderFlag, + kFloat3x3_GrSLType, + "uViewM", + &viewMatrixName); + if (!mat.hasPerspective()) { + gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos2"); + vertBuilder->codeAppendf("float2 %s = (%s * float3(%s, 1)).xy;", + gpArgs->fPositionVar.c_str(), viewMatrixName, posName); + } else { + gpArgs->fPositionVar.set(kFloat3_GrSLType, "pos3"); + vertBuilder->codeAppendf("float3 %s = %s * float3(%s, 1);", + gpArgs->fPositionVar.c_str(), viewMatrixName, posName); + } + } +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.h new file mode 100644 index 0000000000..eb34412687 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLGeometryProcessor.h @@ -0,0 +1,95 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLGeometryProcessor_DEFINED +#define GrGLSLGeometryProcessor_DEFINED + +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" + +class GrGLSLGPBuilder; + +/** + * If a GL effect needs a GrGLFullShaderBuilder* object to emit vertex code, then it must inherit + * from this class. Since paths don't have vertices, this class is only meant to be used internally + * by skia, for special cases. + */ +class GrGLSLGeometryProcessor : public GrGLSLPrimitiveProcessor { +public: + /* Any general emit code goes in the base class emitCode. Subclasses override onEmitCode */ + void emitCode(EmitArgs&) final; + +protected: + // A helper which subclasses can use to upload coord transform matrices in setData(). + void setTransformDataHelper(const SkMatrix& localMatrix, + const GrGLSLProgramDataManager& pdman, + FPCoordTransformIter*); + + // Emit transformed local coords from the vertex shader as a uniform matrix and varying per + // coord-transform. localCoordsVar must be a 2- or 3-component vector. If it is 3 then it is + // assumed to be a 2D homogeneous coordinate. + void emitTransforms(GrGLSLVertexBuilder*, + GrGLSLVaryingHandler*, + GrGLSLUniformHandler*, + const GrShaderVar& localCoordsVar, + const SkMatrix& localMatrix, + FPCoordTransformHandler*); + + // Version of above that assumes identity for the local matrix. + void emitTransforms(GrGLSLVertexBuilder* vb, + GrGLSLVaryingHandler* varyingHandler, + GrGLSLUniformHandler* uniformHandler, + const GrShaderVar& localCoordsVar, + FPCoordTransformHandler* handler) { + this->emitTransforms(vb, varyingHandler, uniformHandler, localCoordsVar, SkMatrix::I(), + handler); + } + + struct GrGPArgs { + // Used to specify the output variable used by the GP to store its device position. It can + // either be a float2 or a float3 (in order to handle perspective). The subclass sets this + // in its onEmitCode(). + GrShaderVar fPositionVar; + }; + + // Helpers for adding code to write the transformed vertex position. The first simple version + // just writes a variable named by 'posName' into the position output variable with the + // assumption that the position is 2D. The second version transforms the input position by a + // view matrix and the output variable is 2D or 3D depending on whether the view matrix is + // perspective. Both versions declare the output position variable and will set + // GrGPArgs::fPositionVar. + void writeOutputPosition(GrGLSLVertexBuilder*, GrGPArgs*, const char* posName); + void writeOutputPosition(GrGLSLVertexBuilder*, + GrGLSLUniformHandler* uniformHandler, + GrGPArgs*, + const char* posName, + const SkMatrix& mat, + UniformHandle* viewMatrixUniform); + + static uint32_t ComputePosKey(const SkMatrix& mat) { + if (mat.isIdentity()) { + return 0x0; + } else if (!mat.hasPerspective()) { + return 0x01; + } else { + return 0x02; + } + } + +private: + virtual void onEmitCode(EmitArgs&, GrGPArgs*) = 0; + + struct TransformUniform { + UniformHandle fHandle; + SkMatrix fCurrentValue = SkMatrix::InvalidMatrix(); + }; + + SkTArray fInstalledTransforms; + + typedef GrGLSLPrimitiveProcessor INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp new file mode 100644 index 0000000000..9d35c44960 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +SkMatrix GrGLSLPrimitiveProcessor::GetTransformMatrix(const SkMatrix& localMatrix, + const GrCoordTransform& coordTransform) { + SkMatrix combined; + combined.setConcat(coordTransform.getMatrix(), localMatrix); + if (coordTransform.normalize()) { + combined.postIDiv(coordTransform.peekTexture()->width(), + coordTransform.peekTexture()->height()); + } + + if (coordTransform.reverseY()) { + if (coordTransform.normalize()) { + // combined.postScale(1,-1); + // combined.postTranslate(0,1); + combined.set(SkMatrix::kMSkewY, + combined[SkMatrix::kMPersp0] - combined[SkMatrix::kMSkewY]); + combined.set(SkMatrix::kMScaleY, + combined[SkMatrix::kMPersp1] - combined[SkMatrix::kMScaleY]); + combined.set(SkMatrix::kMTransY, + combined[SkMatrix::kMPersp2] - combined[SkMatrix::kMTransY]); + } else { + // combined.postScale(1, -1); + // combined.postTranslate(0,1); + SkScalar h = coordTransform.peekTexture()->height(); + combined.set(SkMatrix::kMSkewY, + h * combined[SkMatrix::kMPersp0] - combined[SkMatrix::kMSkewY]); + combined.set(SkMatrix::kMScaleY, + h * combined[SkMatrix::kMPersp1] - combined[SkMatrix::kMScaleY]); + combined.set(SkMatrix::kMTransY, + h * combined[SkMatrix::kMPersp2] - combined[SkMatrix::kMTransY]); + } + } + return combined; +} + +void GrGLSLPrimitiveProcessor::setupUniformColor(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const char* outputName, + UniformHandle* colorUniform) { + SkASSERT(colorUniform); + const char* stagedLocalVarName; + *colorUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, + "Color", + &stagedLocalVarName); + fragBuilder->codeAppendf("%s = %s;", outputName, stagedLocalVarName); + if (fragBuilder->getProgramBuilder()->shaderCaps()->mustObfuscateUniformColor()) { + fragBuilder->codeAppendf("%s = max(%s, half4(0, 0, 0, 0));", outputName, outputName); + } +} + +////////////////////////////////////////////////////////////////////////////// + +const GrCoordTransform* GrGLSLPrimitiveProcessor::FPCoordTransformHandler::nextCoordTransform() { +#ifdef SK_DEBUG + SkASSERT(nullptr == fCurr || fAddedCoord); + fAddedCoord = false; + fCurr = fIter.next(); + return fCurr; +#else + return fIter.next(); +#endif +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.h new file mode 100644 index 0000000000..12fb74f114 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLPrimitiveProcessor.h @@ -0,0 +1,151 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLPrimitiveProcessor_DEFINED +#define GrGLSLPrimitiveProcessor_DEFINED + +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrPrimitiveProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrPrimitiveProcessor; +class GrGLSLFPFragmentBuilder; +class GrGLSLGeometryBuilder; +class GrGLSLGPBuilder; +class GrGLSLVaryingHandler; +class GrGLSLVertexBuilder; +class GrShaderCaps; + +class GrGLSLPrimitiveProcessor { +public: + using UniformHandle = GrGLSLProgramDataManager::UniformHandle; + using SamplerHandle = GrGLSLUniformHandler::SamplerHandle; + using FPCoordTransformIter = GrFragmentProcessor::CoordTransformIter; + + struct TransformVar { + TransformVar() = default; + + TransformVar(SkString matrixCode, UniformHandle uniformMatrix, GrShaderVar varyingPoint) + : fMatrixCode(std::move(matrixCode)) + , fUniformMatrix(uniformMatrix) + , fVaryingPoint(varyingPoint) {} + + // a string of SkSL code which resolves to the transformation matrix + SkString fMatrixCode; + // the variable containing the matrix, if any, otherwise an invalid handle + UniformHandle fUniformMatrix; + // the transformed coordinate output by the vertex shader and consumed by the fragment + // shader + GrShaderVar fVaryingPoint; + }; + + + virtual ~GrGLSLPrimitiveProcessor() {} + + /** + * This class provides access to the GrCoordTransforms across all GrFragmentProcessors in a + * GrPipeline. It is also used by the primitive processor to specify the fragment shader + * variable that will hold the transformed coords for each GrCoordTransform. It is required that + * the primitive processor iterate over each coord transform and insert a shader var result for + * each. The GrGLSLFragmentProcessors will reference these variables in their fragment code. + */ + class FPCoordTransformHandler : public SkNoncopyable { + public: + FPCoordTransformHandler(const GrPipeline& pipeline, + SkTArray* transformedCoordVars) + : fIter(pipeline) + , fTransformedCoordVars(transformedCoordVars) {} + + ~FPCoordTransformHandler() { SkASSERT(!this->nextCoordTransform());} + + const GrCoordTransform* nextCoordTransform(); + + // 'args' are constructor params to GrShaderVar. + template + void specifyCoordsForCurrCoordTransform(Args&&... args) { + SkASSERT(!fAddedCoord); + fTransformedCoordVars->emplace_back(std::forward(args)...); + SkDEBUGCODE(fAddedCoord = true;) + } + + private: + GrFragmentProcessor::CoordTransformIter fIter; + SkDEBUGCODE(bool fAddedCoord = false;) + SkDEBUGCODE(const GrCoordTransform* fCurr = nullptr;) + SkTArray* fTransformedCoordVars; + }; + + struct EmitArgs { + EmitArgs(GrGLSLVertexBuilder* vertBuilder, + GrGLSLGeometryBuilder* geomBuilder, + GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLVaryingHandler* varyingHandler, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* caps, + const GrPrimitiveProcessor& gp, + const char* outputColor, + const char* outputCoverage, + const char* rtAdjustName, + const SamplerHandle* texSamplers, + FPCoordTransformHandler* transformHandler) + : fVertBuilder(vertBuilder) + , fGeomBuilder(geomBuilder) + , fFragBuilder(fragBuilder) + , fVaryingHandler(varyingHandler) + , fUniformHandler(uniformHandler) + , fShaderCaps(caps) + , fGP(gp) + , fOutputColor(outputColor) + , fOutputCoverage(outputCoverage) + , fRTAdjustName(rtAdjustName) + , fTexSamplers(texSamplers) + , fFPCoordTransformHandler(transformHandler) {} + GrGLSLVertexBuilder* fVertBuilder; + GrGLSLGeometryBuilder* fGeomBuilder; + GrGLSLFPFragmentBuilder* fFragBuilder; + GrGLSLVaryingHandler* fVaryingHandler; + GrGLSLUniformHandler* fUniformHandler; + const GrShaderCaps* fShaderCaps; + const GrPrimitiveProcessor& fGP; + const char* fOutputColor; + const char* fOutputCoverage; + const char* fRTAdjustName; + const SamplerHandle* fTexSamplers; + FPCoordTransformHandler* fFPCoordTransformHandler; + }; + + /** + * This is similar to emitCode() in the base class, except it takes a full shader builder. + * This allows the effect subclass to emit vertex code. + */ + virtual void emitCode(EmitArgs&) = 0; + + /** + * A GrGLSLPrimitiveProcessor instance can be reused with any GrGLSLPrimitiveProcessor that + * produces the same stage key; this function reads data from a GrGLSLPrimitiveProcessor and + * uploads any uniform variables required by the shaders created in emitCode(). The + * GrPrimitiveProcessor parameter is guaranteed to be of the same type and to have an + * identical processor key as the GrPrimitiveProcessor that created this + * GrGLSLPrimitiveProcessor. + * The subclass may use the transform iterator to perform any setup required for the particular + * set of fp transform matrices, such as uploading via uniforms. The iterator will iterate over + * the transforms in the same order as the TransformHandler passed to emitCode. + */ + virtual void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&&) = 0; + + static SkMatrix GetTransformMatrix(const SkMatrix& localMatrix, const GrCoordTransform&); + +protected: + void setupUniformColor(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const char* outputName, + UniformHandle* colorUniform); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.cpp new file mode 100644 index 0000000000..8cdf43dfa2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.cpp @@ -0,0 +1,372 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" +#include "src/sksl/SkSLCompiler.h" + +const int GrGLSLProgramBuilder::kVarsPerBlock = 8; + +GrGLSLProgramBuilder::GrGLSLProgramBuilder(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + const GrProgramDesc* desc) + : fVS(this) + , fGS(this) + , fFS(this) + , fStageIndex(-1) + , fRenderTarget(renderTarget) + , fProgramInfo(programInfo) + , fDesc(desc) + , fGeometryProcessor(nullptr) + , fXferProcessor(nullptr) + , fNumFragmentSamplers(0) {} + +void GrGLSLProgramBuilder::addFeature(GrShaderFlags shaders, + uint32_t featureBit, + const char* extensionName) { + if (shaders & kVertex_GrShaderFlag) { + fVS.addFeature(featureBit, extensionName); + } + if (shaders & kGeometry_GrShaderFlag) { + SkASSERT(this->primitiveProcessor().willUseGeoShader()); + fGS.addFeature(featureBit, extensionName); + } + if (shaders & kFragment_GrShaderFlag) { + fFS.addFeature(featureBit, extensionName); + } +} + +bool GrGLSLProgramBuilder::emitAndInstallProcs() { + // First we loop over all of the installed processors and collect coord transforms. These will + // be sent to the GrGLSLPrimitiveProcessor in its emitCode function + SkString inputColor; + SkString inputCoverage; + this->emitAndInstallPrimProc(&inputColor, &inputCoverage); + this->emitAndInstallFragProcs(&inputColor, &inputCoverage); + this->emitAndInstallXferProc(inputColor, inputCoverage); + + return this->checkSamplerCounts(); +} + +void GrGLSLProgramBuilder::emitAndInstallPrimProc(SkString* outputColor, + SkString* outputCoverage) { + const GrPrimitiveProcessor& proc = this->primitiveProcessor(); + + // Because all the texture properties must be consistent between all the dynamic and fixed + // primProc proxies, we just deal w/ the first set of dynamic proxies or the set of fixed + // proxies here. + const GrTextureProxy* const* primProcProxies = nullptr; + if (fProgramInfo.hasDynamicPrimProcTextures()) { + primProcProxies = fProgramInfo.dynamicPrimProcTextures(0); + } else if (fProgramInfo.hasFixedPrimProcTextures()) { + primProcProxies = fProgramInfo.fixedPrimProcTextures(); + } + + // Program builders have a bit of state we need to clear with each effect + AutoStageAdvance adv(this); + this->nameExpression(outputColor, "outputColor"); + this->nameExpression(outputCoverage, "outputCoverage"); + + SkASSERT(!fUniformHandles.fRTAdjustmentUni.isValid()); + GrShaderFlags rtAdjustVisibility; + if (proc.willUseGeoShader()) { + rtAdjustVisibility = kGeometry_GrShaderFlag; + } else { + rtAdjustVisibility = kVertex_GrShaderFlag; + } + fUniformHandles.fRTAdjustmentUni = this->uniformHandler()->addUniform( + rtAdjustVisibility, + kFloat4_GrSLType, + SkSL::Compiler::RTADJUST_NAME); + const char* rtAdjustName = + this->uniformHandler()->getUniformCStr(fUniformHandles.fRTAdjustmentUni); + + // Enclose custom code in a block to avoid namespace conflicts + SkString openBrace; + openBrace.printf("{ // Stage %d, %s\n", fStageIndex, proc.name()); + fFS.codeAppend(openBrace.c_str()); + fVS.codeAppendf("// Primitive Processor %s\n", proc.name()); + + SkASSERT(!fGeometryProcessor); + fGeometryProcessor.reset(proc.createGLSLInstance(*this->shaderCaps())); + + SkAutoSTMalloc<4, SamplerHandle> texSamplers(proc.numTextureSamplers()); + for (int i = 0; i < proc.numTextureSamplers(); ++i) { + SkString name; + name.printf("TextureSampler_%d", i); + const auto& sampler = proc.textureSampler(i); + SkASSERT(sampler.textureType() == primProcProxies[i]->textureType()); + texSamplers[i] = this->emitSampler(primProcProxies[i], + sampler.samplerState(), + sampler.swizzle(), + name.c_str()); + } + + GrGLSLPrimitiveProcessor::FPCoordTransformHandler transformHandler(this->pipeline(), + &fTransformedCoordVars); + GrGLSLGeometryProcessor::EmitArgs args(&fVS, + proc.willUseGeoShader() ? &fGS : nullptr, + &fFS, + this->varyingHandler(), + this->uniformHandler(), + this->shaderCaps(), + proc, + outputColor->c_str(), + outputCoverage->c_str(), + rtAdjustName, + texSamplers.get(), + &transformHandler); + fGeometryProcessor->emitCode(args); + + // We have to check that effects and the code they emit are consistent, ie if an effect + // asks for dst color, then the emit code needs to follow suit + SkDEBUGCODE(verify(proc);) + + fFS.codeAppend("}"); +} + +void GrGLSLProgramBuilder::emitAndInstallFragProcs(SkString* color, SkString* coverage) { + int transformedCoordVarsIdx = 0; + SkString** inOut = &color; + SkSTArray<8, std::unique_ptr> glslFragmentProcessors; + for (int i = 0; i < this->pipeline().numFragmentProcessors(); ++i) { + if (i == this->pipeline().numColorFragmentProcessors()) { + inOut = &coverage; + } + SkString output; + const GrFragmentProcessor& fp = this->pipeline().getFragmentProcessor(i); + output = this->emitAndInstallFragProc(fp, i, transformedCoordVarsIdx, **inOut, output, + &glslFragmentProcessors); + GrFragmentProcessor::Iter iter(&fp); + while (const GrFragmentProcessor* fp = iter.next()) { + transformedCoordVarsIdx += fp->numCoordTransforms(); + } + **inOut = output; + } + fFragmentProcessorCnt = glslFragmentProcessors.count(); + fFragmentProcessors.reset(new std::unique_ptr[fFragmentProcessorCnt]); + for (int i = 0; i < fFragmentProcessorCnt; ++i) { + fFragmentProcessors[i] = std::move(glslFragmentProcessors[i]); + } +} + +// TODO Processors cannot output zeros because an empty string is all 1s +// the fix is to allow effects to take the SkString directly +SkString GrGLSLProgramBuilder::emitAndInstallFragProc( + const GrFragmentProcessor& fp, + int index, + int transformedCoordVarsIdx, + const SkString& input, + SkString output, + SkTArray>* glslFragmentProcessors) { + SkASSERT(input.size()); + // Program builders have a bit of state we need to clear with each effect + AutoStageAdvance adv(this); + this->nameExpression(&output, "output"); + + // Enclose custom code in a block to avoid namespace conflicts + SkString openBrace; + openBrace.printf("{ // Stage %d, %s\n", fStageIndex, fp.name()); + fFS.codeAppend(openBrace.c_str()); + + GrGLSLFragmentProcessor* fragProc = fp.createGLSLInstance(); + + SkSTArray<4, SamplerHandle> texSamplers; + GrFragmentProcessor::Iter fpIter(&fp); + int samplerIdx = 0; + while (const auto* subFP = fpIter.next()) { + for (int i = 0; i < subFP->numTextureSamplers(); ++i) { + SkString name; + name.printf("TextureSampler_%d", samplerIdx++); + const auto& sampler = subFP->textureSampler(i); + texSamplers.emplace_back(this->emitSampler(sampler.proxy(), + sampler.samplerState(), + sampler.swizzle(), + name.c_str())); + } + } + + const GrGLSLPrimitiveProcessor::TransformVar* coordVars = fTransformedCoordVars.begin() + + transformedCoordVarsIdx; + GrGLSLFragmentProcessor::TransformedCoordVars coords(&fp, coordVars); + GrGLSLFragmentProcessor::TextureSamplers textureSamplers(&fp, texSamplers.begin()); + GrGLSLFragmentProcessor::EmitArgs args(&fFS, + this->uniformHandler(), + this->shaderCaps(), + fp, + output.c_str(), + input.c_str(), + coords, + textureSamplers); + + fragProc->emitCode(args); + + // We have to check that effects and the code they emit are consistent, ie if an effect + // asks for dst color, then the emit code needs to follow suit + SkDEBUGCODE(verify(fp);) + glslFragmentProcessors->emplace_back(fragProc); + + fFS.codeAppend("}"); + return output; +} + +void GrGLSLProgramBuilder::emitAndInstallXferProc(const SkString& colorIn, + const SkString& coverageIn) { + // Program builders have a bit of state we need to clear with each effect + AutoStageAdvance adv(this); + + SkASSERT(!fXferProcessor); + const GrXferProcessor& xp = this->pipeline().getXferProcessor(); + fXferProcessor.reset(xp.createGLSLInstance()); + + // Enable dual source secondary output if we have one + if (xp.hasSecondaryOutput()) { + fFS.enableSecondaryOutput(); + } + + if (this->shaderCaps()->mustDeclareFragmentShaderOutput()) { + fFS.enableCustomOutput(); + } + + SkString openBrace; + openBrace.printf("{ // Xfer Processor: %s\n", xp.name()); + fFS.codeAppend(openBrace.c_str()); + + SamplerHandle dstTextureSamplerHandle; + GrSurfaceOrigin dstTextureOrigin = kTopLeft_GrSurfaceOrigin; + + if (GrTextureProxy* dstTextureProxy = this->pipeline().dstTextureProxy()) { + // GrProcessor::TextureSampler sampler(dstTexture); + const GrSwizzle& swizzle = dstTextureProxy->textureSwizzle(); + dstTextureSamplerHandle = this->emitSampler(dstTextureProxy, GrSamplerState(), + swizzle, "DstTextureSampler"); + dstTextureOrigin = dstTextureProxy->origin(); + SkASSERT(dstTextureProxy->textureType() != GrTextureType::kExternal); + } + + SkString finalInColor = colorIn.size() ? colorIn : SkString("float4(1)"); + + GrGLSLXferProcessor::EmitArgs args(&fFS, + this->uniformHandler(), + this->shaderCaps(), + xp, + finalInColor.c_str(), + coverageIn.size() ? coverageIn.c_str() : "float4(1)", + fFS.getPrimaryColorOutputName(), + fFS.getSecondaryColorOutputName(), + dstTextureSamplerHandle, + dstTextureOrigin, + this->pipeline().outputSwizzle()); + fXferProcessor->emitCode(args); + + // We have to check that effects and the code they emit are consistent, ie if an effect + // asks for dst color, then the emit code needs to follow suit + SkDEBUGCODE(verify(xp);) + fFS.codeAppend("}"); +} + +GrGLSLProgramBuilder::SamplerHandle GrGLSLProgramBuilder::emitSampler(const GrTextureProxy* texture, + const GrSamplerState& state, + const GrSwizzle& swizzle, + const char* name) { + ++fNumFragmentSamplers; + return this->uniformHandler()->addSampler(texture, state, swizzle, name, this->shaderCaps()); +} + +bool GrGLSLProgramBuilder::checkSamplerCounts() { + const GrShaderCaps& shaderCaps = *this->shaderCaps(); + if (fNumFragmentSamplers > shaderCaps.maxFragmentSamplers()) { + GrCapsDebugf(this->caps(), "Program would use too many fragment samplers\n"); + return false; + } + return true; +} + +#ifdef SK_DEBUG +void GrGLSLProgramBuilder::verify(const GrPrimitiveProcessor& gp) { + SkASSERT(!fFS.fHasReadDstColorThisStage_DebugOnly); + SkASSERT(fFS.fUsedProcessorFeaturesThisStage_DebugOnly == gp.requestedFeatures()); +} + +void GrGLSLProgramBuilder::verify(const GrFragmentProcessor& fp) { + SkASSERT(!fFS.fHasReadDstColorThisStage_DebugOnly); + SkASSERT(fFS.fUsedProcessorFeaturesThisStage_DebugOnly == fp.requestedFeatures()); +} + +void GrGLSLProgramBuilder::verify(const GrXferProcessor& xp) { + SkASSERT(xp.willReadDstColor() == fFS.fHasReadDstColorThisStage_DebugOnly); + SkASSERT(fFS.fUsedProcessorFeaturesThisStage_DebugOnly == xp.requestedFeatures()); +} +#endif + +void GrGLSLProgramBuilder::nameVariable(SkString* out, char prefix, const char* name, bool mangle) { + if ('\0' == prefix) { + *out = name; + } else { + out->printf("%c%s", prefix, name); + } + if (mangle) { + if (out->endsWith('_')) { + // Names containing "__" are reserved. + out->append("x"); + } + out->appendf("_Stage%d%s", fStageIndex, fFS.getMangleString().c_str()); + } +} + +void GrGLSLProgramBuilder::nameExpression(SkString* output, const char* baseName) { + // create var to hold stage result. If we already have a valid output name, just use that + // otherwise create a new mangled one. This name is only valid if we are reordering stages + // and have to tell stage exactly where to put its output. + SkString outName; + if (output->size()) { + outName = output->c_str(); + } else { + this->nameVariable(&outName, '\0', baseName); + } + fFS.codeAppendf("half4 %s;", outName.c_str()); + *output = outName; +} + +void GrGLSLProgramBuilder::appendUniformDecls(GrShaderFlags visibility, SkString* out) const { + this->uniformHandler()->appendUniformDecls(visibility, out); +} + +void GrGLSLProgramBuilder::addRTWidthUniform(const char* name) { + SkASSERT(!fUniformHandles.fRTWidthUni.isValid()); + GrGLSLUniformHandler* uniformHandler = this->uniformHandler(); + fUniformHandles.fRTWidthUni = + uniformHandler->internalAddUniformArray(kFragment_GrShaderFlag, kHalf_GrSLType, name, + false, 0, nullptr); +} + +void GrGLSLProgramBuilder::addRTHeightUniform(const char* name) { + SkASSERT(!fUniformHandles.fRTHeightUni.isValid()); + GrGLSLUniformHandler* uniformHandler = this->uniformHandler(); + fUniformHandles.fRTHeightUni = + uniformHandler->internalAddUniformArray(kFragment_GrShaderFlag, kHalf_GrSLType, name, + false, 0, nullptr); +} + +void GrGLSLProgramBuilder::finalizeShaders() { + this->varyingHandler()->finalize(); + fVS.finalize(kVertex_GrShaderFlag); + if (this->primitiveProcessor().willUseGeoShader()) { + SkASSERT(this->shaderCaps()->geometryShaderSupport()); + fGS.finalize(kGeometry_GrShaderFlag); + } + fFS.finalize(kFragment_GrShaderFlag); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.h new file mode 100644 index 0000000000..dc63aa82aa --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramBuilder.h @@ -0,0 +1,181 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLProgramBuilder_DEFINED +#define GrGLSLProgramBuilder_DEFINED + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +class GrShaderVar; +class GrGLSLVaryingHandler; +class SkString; +class GrShaderCaps; + +class GrGLSLProgramBuilder { +public: + using UniformHandle = GrGLSLUniformHandler::UniformHandle; + using SamplerHandle = GrGLSLUniformHandler::SamplerHandle; + + virtual ~GrGLSLProgramBuilder() {} + + virtual const GrCaps* caps() const = 0; + const GrShaderCaps* shaderCaps() const { return this->caps()->shaderCaps(); } + + int numSamples() const { return fProgramInfo.numSamples(); } + GrSurfaceOrigin origin() const { return fProgramInfo.origin(); } + const GrPipeline& pipeline() const { return fProgramInfo.pipeline(); } + const GrPrimitiveProcessor& primitiveProcessor() const { return fProgramInfo.primProc(); } + GrProcessor::CustomFeatures processorFeatures() const { + return fProgramInfo.requestedFeatures(); + } + bool snapVerticesToPixelCenters() const { + return fProgramInfo.pipeline().snapVerticesToPixelCenters(); + } + // TODO: remove this usage of the descriptor's header + bool hasPointSize() const { return fDesc->hasPointSize(); } + + // TODO: stop passing in the renderTarget for just the sampleLocations + int effectiveSampleCnt() const { + SkASSERT(GrProcessor::CustomFeatures::kSampleLocations & fProgramInfo.requestedFeatures()); + return fRenderTarget->renderTargetPriv().getSampleLocations().count(); + } + const SkTArray& getSampleLocations() const { + return fRenderTarget->renderTargetPriv().getSampleLocations(); + } + + const GrProgramDesc* desc() const { return fDesc; } + + void appendUniformDecls(GrShaderFlags visibility, SkString*) const; + + const char* samplerVariable(SamplerHandle handle) const { + return this->uniformHandler()->samplerVariable(handle); + } + + GrSwizzle samplerSwizzle(SamplerHandle handle) const { + if (this->caps()->shaderCaps()->textureSwizzleAppliedInShader()) { + return this->uniformHandler()->samplerSwizzle(handle); + } + return GrSwizzle::RGBA(); + } + + // Used to add a uniform for the RenderTarget width (used for sk_Width) without mangling + // the name of the uniform inside of a stage. + void addRTWidthUniform(const char* name); + + // Used to add a uniform for the RenderTarget height (used for sk_Height and frag position) + // without mangling the name of the uniform inside of a stage. + void addRTHeightUniform(const char* name); + + // Generates a name for a variable. The generated string will be name prefixed by the prefix + // char (unless the prefix is '\0'). It also will mangle the name to be stage-specific unless + // explicitly asked not to. + void nameVariable(SkString* out, char prefix, const char* name, bool mangle = true); + + virtual GrGLSLUniformHandler* uniformHandler() = 0; + virtual const GrGLSLUniformHandler* uniformHandler() const = 0; + virtual GrGLSLVaryingHandler* varyingHandler() = 0; + + // Used for backend customization of the output color and secondary color variables from the + // fragment processor. Only used if the outputs are explicitly declared in the shaders + virtual void finalizeFragmentOutputColor(GrShaderVar& outputColor) {} + virtual void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) {} + + // number of each input/output type in a single allocation block, used by many builders + static const int kVarsPerBlock; + + GrGLSLVertexBuilder fVS; + GrGLSLGeometryBuilder fGS; + GrGLSLFragmentShaderBuilder fFS; + + int fStageIndex; + + const GrRenderTarget* fRenderTarget; // TODO: remove this + const GrProgramInfo& fProgramInfo; + + const GrProgramDesc* fDesc; + + GrGLSLBuiltinUniformHandles fUniformHandles; + + std::unique_ptr fGeometryProcessor; + std::unique_ptr fXferProcessor; + std::unique_ptr[]> fFragmentProcessors; + int fFragmentProcessorCnt; + +protected: + explicit GrGLSLProgramBuilder(GrRenderTarget*, const GrProgramInfo&, const GrProgramDesc*); + + void addFeature(GrShaderFlags shaders, uint32_t featureBit, const char* extensionName); + + bool emitAndInstallProcs(); + + void finalizeShaders(); + + bool fragColorIsInOut() const { return fFS.primaryColorOutputIsInOut(); } + +private: + // reset is called by program creator between each processor's emit code. It increments the + // stage offset for variable name mangling, and also ensures verfication variables in the + // fragment shader are cleared. + void reset() { + this->addStage(); + SkDEBUGCODE(fFS.debugOnly_resetPerStageVerification();) + } + void addStage() { fStageIndex++; } + + class AutoStageAdvance { + public: + AutoStageAdvance(GrGLSLProgramBuilder* pb) + : fPB(pb) { + fPB->reset(); + // Each output to the fragment processor gets its own code section + fPB->fFS.nextStage(); + } + ~AutoStageAdvance() {} + private: + GrGLSLProgramBuilder* fPB; + }; + + // Generates a possibly mangled name for a stage variable and writes it to the fragment shader. + void nameExpression(SkString*, const char* baseName); + + void emitAndInstallPrimProc(SkString* outputColor, SkString* outputCoverage); + void emitAndInstallFragProcs(SkString* colorInOut, SkString* coverageInOut); + SkString emitAndInstallFragProc(const GrFragmentProcessor&, + int index, + int transformedCoordVarsIdx, + const SkString& input, + SkString output, + SkTArray>*); + void emitAndInstallXferProc(const SkString& colorIn, const SkString& coverageIn); + SamplerHandle emitSampler(const GrTextureProxy*, const GrSamplerState&, const GrSwizzle&, + const char* name); + bool checkSamplerCounts(); + +#ifdef SK_DEBUG + void verify(const GrPrimitiveProcessor&); + void verify(const GrFragmentProcessor&); + void verify(const GrXferProcessor&); +#endif + + // These are used to check that we don't excede the allowable number of resources in a shader. + int fNumFragmentSamplers; + SkSTArray<4, GrGLSLPrimitiveProcessor::TransformVar> fTransformedCoordVars; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.cpp new file mode 100644 index 0000000000..395e325f9a --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" + +#include "include/core/SkMatrix.h" +#include "include/core/SkMatrix44.h" + +void GrGLSLProgramDataManager::setSkMatrix(UniformHandle u, const SkMatrix& matrix) const { + float mt[] = { + matrix.get(SkMatrix::kMScaleX), + matrix.get(SkMatrix::kMSkewY), + matrix.get(SkMatrix::kMPersp0), + matrix.get(SkMatrix::kMSkewX), + matrix.get(SkMatrix::kMScaleY), + matrix.get(SkMatrix::kMPersp1), + matrix.get(SkMatrix::kMTransX), + matrix.get(SkMatrix::kMTransY), + matrix.get(SkMatrix::kMPersp2), + }; + this->setMatrix3f(u, mt); +} + +void GrGLSLProgramDataManager::setSkMatrix44(UniformHandle u, const SkMatrix44& matrix) const { + float mt[16]; + matrix.asColMajorf(mt); + this->setMatrix4f(u, mt); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.h new file mode 100644 index 0000000000..5db4ae5eca --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLProgramDataManager.h @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLProgramDataManager_DEFINED +#define GrGLSLProgramDataManager_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkNoncopyable.h" +#include "src/gpu/GrResourceHandle.h" + +class SkMatrix; +class SkMatrix44; + +/** Manages the resources used by a shader program. + * The resources are objects the program uses to communicate with the + * application code. + */ +class GrGLSLProgramDataManager : SkNoncopyable { +public: + GR_DEFINE_RESOURCE_HANDLE_CLASS(UniformHandle); + + virtual ~GrGLSLProgramDataManager() {} + + /** Functions for uploading uniform values. The varities ending in v can be used to upload to an + * array of uniforms. arrayCount must be <= the array count of the uniform. + */ + virtual void set1i(UniformHandle, int32_t) const = 0; + virtual void set1iv(UniformHandle, int arrayCount, const int v[]) const = 0; + virtual void set1f(UniformHandle, float v0) const = 0; + virtual void set1fv(UniformHandle, int arrayCount, const float v[]) const = 0; + virtual void set2i(UniformHandle, int32_t, int32_t) const = 0; + virtual void set2iv(UniformHandle, int arrayCount, const int v[]) const = 0; + virtual void set2f(UniformHandle, float, float) const = 0; + virtual void set2fv(UniformHandle, int arrayCount, const float v[]) const = 0; + virtual void set3i(UniformHandle, int32_t, int32_t, int32_t) const = 0; + virtual void set3iv(UniformHandle, int arrayCount, const int v[]) const = 0; + virtual void set3f(UniformHandle, float, float, float) const = 0; + virtual void set3fv(UniformHandle, int arrayCount, const float v[]) const = 0; + virtual void set4i(UniformHandle, int32_t, int32_t, int32_t, int32_t) const = 0; + virtual void set4iv(UniformHandle, int arrayCount, const int v[]) const = 0; + virtual void set4f(UniformHandle, float, float, float, float) const = 0; + virtual void set4fv(UniformHandle, int arrayCount, const float v[]) const = 0; + // matrices are column-major, the first three upload a single matrix, the latter three upload + // arrayCount matrices into a uniform array. + virtual void setMatrix2f(UniformHandle, const float matrix[]) const = 0; + virtual void setMatrix3f(UniformHandle, const float matrix[]) const = 0; + virtual void setMatrix4f(UniformHandle, const float matrix[]) const = 0; + virtual void setMatrix2fv(UniformHandle, int arrayCount, const float matrices[]) const = 0; + virtual void setMatrix3fv(UniformHandle, int arrayCount, const float matrices[]) const = 0; + virtual void setMatrix4fv(UniformHandle, int arrayCount, const float matrices[]) const = 0; + + // convenience method for uploading a SkMatrix to a 3x3 matrix uniform + void setSkMatrix(UniformHandle, const SkMatrix&) const; + // convenience method for uploading a SkMatrix to a 4x4 matrix uniform + void setSkMatrix44(UniformHandle, const SkMatrix44&) const; + + // for nvpr only + GR_DEFINE_RESOURCE_HANDLE_CLASS(VaryingHandle); + virtual void setPathFragmentInputTransform(VaryingHandle u, int components, + const SkMatrix& matrix) const = 0; + +protected: + GrGLSLProgramDataManager() {} + +private: + typedef SkNoncopyable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.cpp new file mode 100644 index 0000000000..7675744bc5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.cpp @@ -0,0 +1,278 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLShaderBuilder.h" + +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" + +GrGLSLShaderBuilder::GrGLSLShaderBuilder(GrGLSLProgramBuilder* program) + : fProgramBuilder(program) + , fInputs(GrGLSLProgramBuilder::kVarsPerBlock) + , fOutputs(GrGLSLProgramBuilder::kVarsPerBlock) + , fFeaturesAddedMask(0) + , fCodeIndex(kCode) + , fFinalized(false) { + // We push back some dummy pointers which will later become our header + for (int i = 0; i <= kCode; i++) { + fShaderStrings.push_back(); + } + + this->main() = "void main() {"; +} + +void GrGLSLShaderBuilder::declAppend(const GrShaderVar& var) { + SkString tempDecl; + var.appendDecl(fProgramBuilder->shaderCaps(), &tempDecl); + this->codeAppendf("%s;", tempDecl.c_str()); +} + +void GrGLSLShaderBuilder::declareGlobal(const GrShaderVar& v) { + v.appendDecl(this->getProgramBuilder()->shaderCaps(), &this->definitions()); + this->definitions().append(";"); +} + +void GrGLSLShaderBuilder::emitFunction(GrSLType returnType, + const char* name, + int argCnt, + const GrShaderVar* args, + const char* body, + SkString* outName) { + this->functions().append(GrGLSLTypeString(returnType)); + fProgramBuilder->nameVariable(outName, '\0', name); + this->functions().appendf(" %s", outName->c_str()); + this->functions().append("("); + for (int i = 0; i < argCnt; ++i) { + args[i].appendDecl(fProgramBuilder->shaderCaps(), &this->functions()); + if (i < argCnt - 1) { + this->functions().append(", "); + } + } + this->functions().append(") {\n"); + this->functions().append(body); + this->functions().append("}\n\n"); +} + +static inline void append_texture_swizzle(SkString* out, GrSwizzle swizzle) { + if (swizzle != GrSwizzle::RGBA()) { + out->appendf(".%s", swizzle.c_str()); + } +} + +void GrGLSLShaderBuilder::appendTextureLookup(SkString* out, + SamplerHandle samplerHandle, + const char* coordName, + GrSLType varyingType) const { + const char* sampler = fProgramBuilder->samplerVariable(samplerHandle); + out->appendf("sample(%s, %s)", sampler, coordName); + append_texture_swizzle(out, fProgramBuilder->samplerSwizzle(samplerHandle)); +} + +void GrGLSLShaderBuilder::appendTextureLookup(SamplerHandle samplerHandle, + const char* coordName, + GrSLType varyingType, + GrGLSLColorSpaceXformHelper* colorXformHelper) { + SkString lookup; + this->appendTextureLookup(&lookup, samplerHandle, coordName, varyingType); + this->appendColorGamutXform(lookup.c_str(), colorXformHelper); +} + +void GrGLSLShaderBuilder::appendTextureLookupAndModulate( + const char* modulation, + SamplerHandle samplerHandle, + const char* coordName, + GrSLType varyingType, + GrGLSLColorSpaceXformHelper* colorXformHelper) { + SkString lookup; + this->appendTextureLookup(&lookup, samplerHandle, coordName, varyingType); + this->appendColorGamutXform(lookup.c_str(), colorXformHelper); + if (modulation) { + this->codeAppendf(" * %s", modulation); + } +} + +void GrGLSLShaderBuilder::appendColorGamutXform(SkString* out, + const char* srcColor, + GrGLSLColorSpaceXformHelper* colorXformHelper) { + if (!colorXformHelper || colorXformHelper->isNoop()) { + *out = srcColor; + return; + } + + GrGLSLUniformHandler* uniformHandler = fProgramBuilder->uniformHandler(); + + // We define up to three helper functions, to keep things clearer. One for the source transfer + // function, one for the (inverse) destination transfer function, and one for the gamut xform. + // Any combination of these may be present, although some configurations are much more likely. + + auto emitTFFunc = [=](const char* name, GrGLSLProgramDataManager::UniformHandle uniform, + TFKind kind) { + const GrShaderVar gTFArgs[] = { GrShaderVar("x", kHalf_GrSLType) }; + const char* coeffs = uniformHandler->getUniformCStr(uniform); + SkString body; + // Temporaries to make evaluation line readable. We always use the sRGBish names, so the + // PQ and HLG math is confusing. + body.appendf("half G = %s[0];", coeffs); + body.appendf("half A = %s[1];", coeffs); + body.appendf("half B = %s[2];", coeffs); + body.appendf("half C = %s[3];", coeffs); + body.appendf("half D = %s[4];", coeffs); + body.appendf("half E = %s[5];", coeffs); + body.appendf("half F = %s[6];", coeffs); + body.append("half s = sign(x);"); + body.append("x = abs(x);"); + switch (kind) { + case TFKind::sRGBish_TF: + body.append("x = (x < D) ? (C * x) + F : pow(A * x + B, G) + E;"); + break; + case TFKind::PQish_TF: + body.append("x = pow(max(A + B * pow(x, C), 0) / (D + E * pow(x, C)), F);"); + break; + case TFKind::HLGish_TF: + body.append("x = (x*A <= 1) ? pow(x*A, B) : exp((x-E)*C) + D;"); + break; + case TFKind::HLGinvish_TF: + body.append("x = (x <= 1) ? A * pow(x, B) : C * log(x - D) + E;"); + break; + default: + SkASSERT(false); + break; + } + body.append("return s * x;"); + SkString funcName; + this->emitFunction(kHalf_GrSLType, name, SK_ARRAY_COUNT(gTFArgs), gTFArgs, body.c_str(), + &funcName); + return funcName; + }; + + SkString srcTFFuncName; + if (colorXformHelper->applySrcTF()) { + srcTFFuncName = emitTFFunc("src_tf", colorXformHelper->srcTFUniform(), + colorXformHelper->srcTFKind()); + } + + SkString dstTFFuncName; + if (colorXformHelper->applyDstTF()) { + dstTFFuncName = emitTFFunc("dst_tf", colorXformHelper->dstTFUniform(), + colorXformHelper->dstTFKind()); + } + + SkString gamutXformFuncName; + if (colorXformHelper->applyGamutXform()) { + const GrShaderVar gGamutXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) }; + const char* xform = uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform()); + SkString body; + body.appendf("color.rgb = (%s * color.rgb);", xform); + body.append("return color;"); + this->emitFunction(kHalf4_GrSLType, "gamut_xform", SK_ARRAY_COUNT(gGamutXformArgs), + gGamutXformArgs, body.c_str(), &gamutXformFuncName); + } + + // Now define a wrapper function that applies all the intermediate steps + { + const GrShaderVar gColorXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) }; + SkString body; + if (colorXformHelper->applyUnpremul()) { + body.append("half nonZeroAlpha = max(color.a, 0.0001);"); + body.append("color = half4(color.rgb / nonZeroAlpha, nonZeroAlpha);"); + } + if (colorXformHelper->applySrcTF()) { + body.appendf("color.r = %s(color.r);", srcTFFuncName.c_str()); + body.appendf("color.g = %s(color.g);", srcTFFuncName.c_str()); + body.appendf("color.b = %s(color.b);", srcTFFuncName.c_str()); + } + if (colorXformHelper->applyGamutXform()) { + body.appendf("color = %s(color);", gamutXformFuncName.c_str()); + } + if (colorXformHelper->applyDstTF()) { + body.appendf("color.r = %s(color.r);", dstTFFuncName.c_str()); + body.appendf("color.g = %s(color.g);", dstTFFuncName.c_str()); + body.appendf("color.b = %s(color.b);", dstTFFuncName.c_str()); + } + if (colorXformHelper->applyPremul()) { + body.append("color.rgb *= color.a;"); + } + body.append("return color;"); + SkString colorXformFuncName; + this->emitFunction(kHalf4_GrSLType, "color_xform", SK_ARRAY_COUNT(gColorXformArgs), + gColorXformArgs, body.c_str(), &colorXformFuncName); + out->appendf("%s(%s)", colorXformFuncName.c_str(), srcColor); + } +} + +void GrGLSLShaderBuilder::appendColorGamutXform(const char* srcColor, + GrGLSLColorSpaceXformHelper* colorXformHelper) { + SkString xform; + this->appendColorGamutXform(&xform, srcColor, colorXformHelper); + this->codeAppend(xform.c_str()); +} + +bool GrGLSLShaderBuilder::addFeature(uint32_t featureBit, const char* extensionName) { + if (featureBit & fFeaturesAddedMask) { + return false; + } + this->extensions().appendf("#extension %s: require\n", extensionName); + fFeaturesAddedMask |= featureBit; + return true; +} + +void GrGLSLShaderBuilder::appendDecls(const VarArray& vars, SkString* out) const { + for (int i = 0; i < vars.count(); ++i) { + vars[i].appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";\n"); + } +} + +void GrGLSLShaderBuilder::addLayoutQualifier(const char* param, InterfaceQualifier interface) { + SkASSERT(fProgramBuilder->shaderCaps()->generation() >= k330_GrGLSLGeneration || + fProgramBuilder->shaderCaps()->mustEnableAdvBlendEqs()); + fLayoutParams[interface].push_back() = param; +} + +void GrGLSLShaderBuilder::compileAndAppendLayoutQualifiers() { + static const char* interfaceQualifierNames[] = { + "in", + "out" + }; + + for (int interface = 0; interface <= kLastInterfaceQualifier; ++interface) { + const SkTArray& params = fLayoutParams[interface]; + if (params.empty()) { + continue; + } + this->layoutQualifiers().appendf("layout(%s", params[0].c_str()); + for (int i = 1; i < params.count(); ++i) { + this->layoutQualifiers().appendf(", %s", params[i].c_str()); + } + this->layoutQualifiers().appendf(") %s;\n", interfaceQualifierNames[interface]); + } + + GR_STATIC_ASSERT(0 == GrGLSLShaderBuilder::kIn_InterfaceQualifier); + GR_STATIC_ASSERT(1 == GrGLSLShaderBuilder::kOut_InterfaceQualifier); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(interfaceQualifierNames) == kLastInterfaceQualifier + 1); +} + +void GrGLSLShaderBuilder::finalize(uint32_t visibility) { + SkASSERT(!fFinalized); + this->compileAndAppendLayoutQualifiers(); + SkASSERT(visibility); + fProgramBuilder->appendUniformDecls((GrShaderFlags) visibility, &this->uniforms()); + this->appendDecls(fInputs, &this->inputs()); + this->appendDecls(fOutputs, &this->outputs()); + this->onFinalize(); + // append the 'footer' to code + this->code().append("}"); + + for (int i = 0; i <= fCodeIndex; i++) { + fCompilerString.append(fShaderStrings[i].c_str(), fShaderStrings[i].size()); + } + + fFinalized = true; +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.h new file mode 100644 index 0000000000..9d3cc48cbc --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLShaderBuilder.h @@ -0,0 +1,257 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLShaderBuilder_DEFINED +#define GrGLSLShaderBuilder_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/sksl/SkSLString.h" + +#include + +class GrGLSLColorSpaceXformHelper; + +/** + base class for all shaders builders +*/ +class GrGLSLShaderBuilder { +public: + GrGLSLShaderBuilder(GrGLSLProgramBuilder* program); + virtual ~GrGLSLShaderBuilder() {} + + using SamplerHandle = GrGLSLUniformHandler::SamplerHandle; + + /** Appends a 2D texture sample with projection if necessary. coordType must either be Vec2f or + Vec3f. The latter is interpreted as projective texture coords. The vec length and swizzle + order of the result depends on the GrProcessor::TextureSampler associated with the + SamplerHandle. + */ + void appendTextureLookup(SkString* out, + SamplerHandle, + const char* coordName, + GrSLType coordType = kHalf2_GrSLType) const; + + /** Version of above that appends the result to the shader code instead.*/ + void appendTextureLookup(SamplerHandle, + const char* coordName, + GrSLType coordType = kHalf2_GrSLType, + GrGLSLColorSpaceXformHelper* colorXformHelper = nullptr); + + + /** Does the work of appendTextureLookup and modulates the result by modulation. The result is + always a half4. modulation and the swizzle specified by SamplerHandle must both be + half4 or half. If modulation is "" or nullptr it this function acts as though + appendTextureLookup were called. */ + void appendTextureLookupAndModulate(const char* modulation, + SamplerHandle, + const char* coordName, + GrSLType coordType = kHalf2_GrSLType, + GrGLSLColorSpaceXformHelper* colorXformHelper = nullptr); + + /** Adds a helper function to facilitate color gamut transformation, and produces code that + returns the srcColor transformed into a new gamut (via multiplication by the xform from + colorXformHelper). Premultiplied sources are also handled correctly (colorXformHelper + determines if the source is premultipled or not). */ + void appendColorGamutXform(SkString* out, const char* srcColor, + GrGLSLColorSpaceXformHelper* colorXformHelper); + + /** Version of above that appends the result to the shader code instead. */ + void appendColorGamutXform(const char* srcColor, GrGLSLColorSpaceXformHelper* colorXformHelper); + + /** + * Adds a constant declaration to the top of the shader. + */ + void defineConstant(const char* type, const char* name, const char* value) { + this->definitions().appendf("const %s %s = %s;\n", type, name, value); + } + + void defineConstant(const char* name, int value) { + this->definitions().appendf("const int %s = %i;\n", name, value); + } + + void defineConstant(const char* name, float value) { + this->definitions().appendf("const float %s = %f;\n", name, value); + } + + void defineConstantf(const char* type, const char* name, const char* fmt, ...) { + this->definitions().appendf("const %s %s = ", type, name); + va_list args; + va_start(args, fmt); + this->definitions().appendVAList(fmt, args); + va_end(args); + this->definitions().append(";\n"); + } + + void declareGlobal(const GrShaderVar&); + + /** + * Called by GrGLSLProcessors to add code to one of the shaders. + */ + void codeAppendf(const char format[], ...) SK_PRINTF_LIKE(2, 3) { + va_list args; + va_start(args, format); + this->code().appendVAList(format, args); + va_end(args); + } + + void codeAppend(const char* str) { this->code().append(str); } + + void codeAppend(const char* str, size_t length) { this->code().append(str, length); } + + void codePrependf(const char format[], ...) SK_PRINTF_LIKE(2, 3) { + va_list args; + va_start(args, format); + this->code().prependVAList(format, args); + va_end(args); + } + + /** + * Appends a variable declaration to one of the shaders + */ + void declAppend(const GrShaderVar& var); + + /** Emits a helper function outside of main() in the fragment shader. */ + void emitFunction(GrSLType returnType, + const char* name, + int argCnt, + const GrShaderVar* args, + const char* body, + SkString* outName); + + /* + * Combines the various parts of the shader to create a single finalized shader string. + */ + void finalize(uint32_t visibility); + + /* + * Get parent builder for adding uniforms + */ + GrGLSLProgramBuilder* getProgramBuilder() { return fProgramBuilder; } + + /** + * Helper for begining and ending a block in the shader code. + */ + class ShaderBlock { + public: + ShaderBlock(GrGLSLShaderBuilder* builder) : fBuilder(builder) { + SkASSERT(builder); + fBuilder->codeAppend("{"); + } + + ~ShaderBlock() { + fBuilder->codeAppend("}"); + } + private: + GrGLSLShaderBuilder* fBuilder; + }; + +protected: + typedef GrTAllocator VarArray; + void appendDecls(const VarArray& vars, SkString* out) const; + + /** + * Features that should only be enabled internally by the builders. + */ + enum GLSLPrivateFeature { + kFragCoordConventions_GLSLPrivateFeature, + kBlendEquationAdvanced_GLSLPrivateFeature, + kBlendFuncExtended_GLSLPrivateFeature, + kFramebufferFetch_GLSLPrivateFeature, + kNoPerspectiveInterpolation_GLSLPrivateFeature, + kSampleVariables_GLSLPrivateFeature, + kLastGLSLPrivateFeature = kSampleVariables_GLSLPrivateFeature + }; + + /* + * A general function which enables an extension in a shader if the feature bit is not present + * + * @return true if the feature bit was not yet present, false otherwise. + */ + bool addFeature(uint32_t featureBit, const char* extensionName); + + enum InterfaceQualifier { + kIn_InterfaceQualifier, + kOut_InterfaceQualifier, + kLastInterfaceQualifier = kOut_InterfaceQualifier + }; + + /* + * A low level function to build default layout qualifiers. + * + * e.g. layout(param1, param2, ...) out; + * + * GLSL allows default layout qualifiers for in, out, and uniform. + */ + void addLayoutQualifier(const char* param, InterfaceQualifier); + + void compileAndAppendLayoutQualifiers(); + + void nextStage() { + fShaderStrings.push_back(); + fCodeIndex++; + } + + void deleteStage() { + fShaderStrings.pop_back(); + fCodeIndex--; + } + + SkString& extensions() { return fShaderStrings[kExtensions]; } + SkString& definitions() { return fShaderStrings[kDefinitions]; } + SkString& precisionQualifier() { return fShaderStrings[kPrecisionQualifier]; } + SkString& layoutQualifiers() { return fShaderStrings[kLayoutQualifiers]; } + SkString& uniforms() { return fShaderStrings[kUniforms]; } + SkString& inputs() { return fShaderStrings[kInputs]; } + SkString& outputs() { return fShaderStrings[kOutputs]; } + SkString& functions() { return fShaderStrings[kFunctions]; } + SkString& main() { return fShaderStrings[kMain]; } + SkString& code() { return fShaderStrings[fCodeIndex]; } + + virtual void onFinalize() = 0; + + enum { + kExtensions, + kDefinitions, + kPrecisionQualifier, + kLayoutQualifiers, + kUniforms, + kInputs, + kOutputs, + kFunctions, + kMain, + kCode, + + kPrealloc = kCode + 6, // 6 == Reasonable upper bound on number of processor stages + }; + + GrGLSLProgramBuilder* fProgramBuilder; + SkSL::String fCompilerString; + SkSTArray fShaderStrings; + SkString fCode; + SkString fFunctions; + SkString fExtensions; + + VarArray fInputs; + VarArray fOutputs; + uint32_t fFeaturesAddedMask; + SkSTArray<1, SkString> fLayoutParams[kLastInterfaceQualifier + 1]; + int fCodeIndex; + bool fFinalized; + + friend class GrCCCoverageProcessor; // to access code(). + friend class GrGLSLProgramBuilder; + friend class GrGLProgramBuilder; + friend class GrDawnProgramBuilder; + friend class GrGLSLVaryingHandler; // to access noperspective interpolation feature. + friend class GrGLPathProgramBuilder; // to access fInputs. + friend class GrVkPipelineStateBuilder; + friend class GrMtlPipelineStateBuilder; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLUniformHandler.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLUniformHandler.h new file mode 100644 index 0000000000..f27d961a95 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLUniformHandler.h @@ -0,0 +1,102 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLUniformHandler_DEFINED +#define GrGLSLUniformHandler_DEFINED + +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/GrSwizzle.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" + +// variable names beginning with this prefix will not be mangled +#define GR_NO_MANGLE_PREFIX "sk_" + +class GrGLSLProgramBuilder; +class GrSamplerState; +class GrTextureProxy; + +// Handles for program uniforms (other than per-effect uniforms) +struct GrGLSLBuiltinUniformHandles { + GrGLSLProgramDataManager::UniformHandle fRTAdjustmentUni; + // Render target width, used to implement sk_Width + GrGLSLProgramDataManager::UniformHandle fRTWidthUni; + // Render target height, used to implement sk_Height and to calculate sk_FragCoord when + // origin_upper_left is not supported. + GrGLSLProgramDataManager::UniformHandle fRTHeightUni; +}; + +class GrGLSLUniformHandler { +public: + virtual ~GrGLSLUniformHandler() {} + + using UniformHandle = GrGLSLProgramDataManager::UniformHandle; + + GR_DEFINE_RESOURCE_HANDLE_CLASS(SamplerHandle); + + /** Add a uniform variable to the current program, that has visibility in one or more shaders. + visibility is a bitfield of GrShaderFlag values indicating from which shaders the uniform + should be accessible. At least one bit must be set. Geometry shader uniforms are not + supported at this time. The actual uniform name will be mangled. If outName is not nullptr + then it will refer to the final uniform name after return. Use the addUniformArray variant + to add an array of uniforms. */ + UniformHandle addUniform(uint32_t visibility, + GrSLType type, + const char* name, + const char** outName = nullptr) { + SkASSERT(!GrSLTypeIsCombinedSamplerType(type)); + return this->addUniformArray(visibility, type, name, 0, outName); + } + + UniformHandle addUniformArray(uint32_t visibility, + GrSLType type, + const char* name, + int arrayCount, + const char** outName = nullptr) { + SkASSERT(!GrSLTypeIsCombinedSamplerType(type)); + bool mangle = strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX)); + return this->internalAddUniformArray(visibility, type, name, mangle, arrayCount, outName); + } + + virtual const GrShaderVar& getUniformVariable(UniformHandle u) const = 0; + + /** + * 'Or's the visibility parameter with the current uniform visibililty. + */ + virtual void updateUniformVisibility(UniformHandle u, uint32_t visibility) = 0; + + /** + * Shortcut for getUniformVariable(u).c_str() + */ + virtual const char* getUniformCStr(UniformHandle u) const = 0; + +protected: + explicit GrGLSLUniformHandler(GrGLSLProgramBuilder* program) : fProgramBuilder(program) {} + + // This is not owned by the class + GrGLSLProgramBuilder* fProgramBuilder; + +private: + virtual const char * samplerVariable(SamplerHandle) const = 0; + // Only called if GrShaderCaps(:textureSwizzleAppliedInShader() == true. + virtual GrSwizzle samplerSwizzle(SamplerHandle) const = 0; + + virtual SamplerHandle addSampler(const GrTextureProxy*, const GrSamplerState&, const GrSwizzle&, + const char* name, const GrShaderCaps*) = 0; + + virtual UniformHandle internalAddUniformArray(uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) = 0; + + virtual void appendUniformDecls(GrShaderFlags visibility, SkString*) const = 0; + + friend class GrGLSLProgramBuilder; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.cpp new file mode 100644 index 0000000000..393947a016 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.cpp @@ -0,0 +1,52 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/core/SkMatrix.h" +#include "src/gpu/glsl/GrGLSLUtil.h" + +template<> void GrGLSLGetMatrix<3>(float* dest, const SkMatrix& src) { + // Col 0 + dest[0] = SkScalarToFloat(src[SkMatrix::kMScaleX]); + dest[1] = SkScalarToFloat(src[SkMatrix::kMSkewY]); + dest[2] = SkScalarToFloat(src[SkMatrix::kMPersp0]); + + // Col 1 + dest[3] = SkScalarToFloat(src[SkMatrix::kMSkewX]); + dest[4] = SkScalarToFloat(src[SkMatrix::kMScaleY]); + dest[5] = SkScalarToFloat(src[SkMatrix::kMPersp1]); + + // Col 2 + dest[6] = SkScalarToFloat(src[SkMatrix::kMTransX]); + dest[7] = SkScalarToFloat(src[SkMatrix::kMTransY]); + dest[8] = SkScalarToFloat(src[SkMatrix::kMPersp2]); +} + +template<> void GrGLSLGetMatrix<4>(float* dest, const SkMatrix& src) { + // Col 0 + dest[0] = SkScalarToFloat(src[SkMatrix::kMScaleX]); + dest[1] = SkScalarToFloat(src[SkMatrix::kMSkewY]); + dest[2] = 0; + dest[3] = SkScalarToFloat(src[SkMatrix::kMPersp0]); + + // Col 1 + dest[4] = SkScalarToFloat(src[SkMatrix::kMSkewX]); + dest[5] = SkScalarToFloat(src[SkMatrix::kMScaleY]); + dest[6] = 0; + dest[7] = SkScalarToFloat(src[SkMatrix::kMPersp1]); + + // Col 2 + dest[8] = 0; + dest[9] = 0; + dest[10] = 1; + dest[11] = 0; + + // Col 3 + dest[12] = SkScalarToFloat(src[SkMatrix::kMTransX]); + dest[13] = SkScalarToFloat(src[SkMatrix::kMTransY]); + dest[14] = 0; + dest[15] = SkScalarToFloat(src[SkMatrix::kMPersp2]); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.h new file mode 100644 index 0000000000..0d2b7e742f --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLUtil.h @@ -0,0 +1,19 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrGLSLUtil_DEFINED +#define GrGLSLUtil_DEFINED + +class SkMatrix; + +/** + * Helper for converting SkMatrix to a column-major float array. We assume that all GLSL backends + * use a column major representation for matrices. + */ +template void GrGLSLGetMatrix(float* dest, const SkMatrix& src); + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.cpp new file mode 100644 index 0000000000..ce086a9082 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +void GrGLSLVaryingHandler::addPassThroughAttribute(const GrGeometryProcessor::Attribute& input, + const char* output, + Interpolation interpolation) { + SkASSERT(input.isInitialized()); + SkASSERT(!fProgramBuilder->primitiveProcessor().willUseGeoShader()); + GrGLSLVarying v(input.gpuType()); + this->addVarying(input.name(), &v, interpolation); + fProgramBuilder->fVS.codeAppendf("%s = %s;", v.vsOut(), input.name()); + fProgramBuilder->fFS.codeAppendf("%s = %s;", output, v.fsIn()); +} + +static bool use_flat_interpolation(GrGLSLVaryingHandler::Interpolation interpolation, + const GrShaderCaps& shaderCaps) { + switch (interpolation) { + using Interpolation = GrGLSLVaryingHandler::Interpolation; + case Interpolation::kInterpolated: + return false; + case Interpolation::kCanBeFlat: + SkASSERT(!shaderCaps.preferFlatInterpolation() || + shaderCaps.flatInterpolationSupport()); + return shaderCaps.preferFlatInterpolation(); + case Interpolation::kMustBeFlat: + SkASSERT(shaderCaps.flatInterpolationSupport()); + return true; + } + SK_ABORT("Invalid interpolation"); +} + +void GrGLSLVaryingHandler::addVarying(const char* name, GrGLSLVarying* varying, + Interpolation interpolation) { + SkASSERT(GrSLTypeIsFloatType(varying->type()) || Interpolation::kMustBeFlat == interpolation); + bool willUseGeoShader = fProgramBuilder->primitiveProcessor().willUseGeoShader(); + VaryingInfo& v = fVaryings.push_back(); + + SkASSERT(varying); + SkASSERT(kVoid_GrSLType != varying->fType); + v.fType = varying->fType; + v.fIsFlat = use_flat_interpolation(interpolation, *fProgramBuilder->shaderCaps()); + fProgramBuilder->nameVariable(&v.fVsOut, 'v', name); + v.fVisibility = kNone_GrShaderFlags; + if (varying->isInVertexShader()) { + varying->fVsOut = v.fVsOut.c_str(); + v.fVisibility |= kVertex_GrShaderFlag; + } + if (willUseGeoShader) { + fProgramBuilder->nameVariable(&v.fGsOut, 'g', name); + varying->fGsIn = v.fVsOut.c_str(); + varying->fGsOut = v.fGsOut.c_str(); + v.fVisibility |= kGeometry_GrShaderFlag; + } + if (varying->isInFragmentShader()) { + varying->fFsIn = (willUseGeoShader ? v.fGsOut : v.fVsOut).c_str(); + v.fVisibility |= kFragment_GrShaderFlag; + } +} + +void GrGLSLVaryingHandler::emitAttributes(const GrGeometryProcessor& gp) { + for (const auto& attr : gp.vertexAttributes()) { + this->addAttribute(attr.asShaderVar()); + } + for (const auto& attr : gp.instanceAttributes()) { + this->addAttribute(attr.asShaderVar()); + } +} + +void GrGLSLVaryingHandler::addAttribute(const GrShaderVar& var) { + SkASSERT(GrShaderVar::kIn_TypeModifier == var.getTypeModifier()); + for (int j = 0; j < fVertexInputs.count(); ++j) { + const GrShaderVar& attr = fVertexInputs[j]; + // if attribute already added, don't add it again + if (attr.getName().equals(var.getName())) { + return; + } + } + fVertexInputs.push_back(var); +} + +void GrGLSLVaryingHandler::setNoPerspective() { + const GrShaderCaps& caps = *fProgramBuilder->shaderCaps(); + if (!caps.noperspectiveInterpolationSupport()) { + return; + } + if (const char* extension = caps.noperspectiveInterpolationExtensionString()) { + int bit = 1 << GrGLSLFragmentBuilder::kNoPerspectiveInterpolation_GLSLPrivateFeature; + fProgramBuilder->fVS.addFeature(bit, extension); + if (fProgramBuilder->primitiveProcessor().willUseGeoShader()) { + fProgramBuilder->fGS.addFeature(bit, extension); + } + fProgramBuilder->fFS.addFeature(bit, extension); + } + fDefaultInterpolationModifier = "noperspective"; +} + +void GrGLSLVaryingHandler::finalize() { + for (int i = 0; i < fVaryings.count(); ++i) { + const VaryingInfo& v = this->fVaryings[i]; + const char* modifier = v.fIsFlat ? "flat" : fDefaultInterpolationModifier; + if (v.fVisibility & kVertex_GrShaderFlag) { + fVertexOutputs.push_back().set(v.fType, v.fVsOut, GrShaderVar::kOut_TypeModifier, + nullptr, modifier); + if (v.fVisibility & kGeometry_GrShaderFlag) { + fGeomInputs.push_back().set(v.fType, v.fVsOut, GrShaderVar::kUnsizedArray, + GrShaderVar::kIn_TypeModifier, nullptr, modifier); + } + } + if (v.fVisibility & kFragment_GrShaderFlag) { + const char* fsIn = v.fVsOut.c_str(); + if (v.fVisibility & kGeometry_GrShaderFlag) { + fGeomOutputs.push_back().set(v.fType, v.fGsOut, GrShaderVar::kOut_TypeModifier, + nullptr, modifier); + fsIn = v.fGsOut.c_str(); + } + fFragInputs.push_back().set(v.fType, fsIn, GrShaderVar::kIn_TypeModifier, nullptr, + modifier); + } + } + this->onFinalize(); +} + +void GrGLSLVaryingHandler::appendDecls(const VarArray& vars, SkString* out) const { + for (int i = 0; i < vars.count(); ++i) { + vars[i].appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";"); + } +} + +void GrGLSLVaryingHandler::getVertexDecls(SkString* inputDecls, SkString* outputDecls) const { + this->appendDecls(fVertexInputs, inputDecls); + this->appendDecls(fVertexOutputs, outputDecls); +} + +void GrGLSLVaryingHandler::getGeomDecls(SkString* inputDecls, SkString* outputDecls) const { + this->appendDecls(fGeomInputs, inputDecls); + this->appendDecls(fGeomOutputs, outputDecls); +} + +void GrGLSLVaryingHandler::getFragDecls(SkString* inputDecls, SkString* outputDecls) const { + // We should not have any outputs in the fragment shader when using version 1.10 + SkASSERT(k110_GrGLSLGeneration != fProgramBuilder->shaderCaps()->generation() || + fFragOutputs.empty()); + this->appendDecls(fFragInputs, inputDecls); + this->appendDecls(fFragOutputs, outputDecls); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.h new file mode 100644 index 0000000000..3294e72a90 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLVarying.h @@ -0,0 +1,180 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLVarying_DEFINED +#define GrGLSLVarying_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" + +class GrGLSLProgramBuilder; + +#ifdef SK_DEBUG +static bool is_matrix(GrSLType type) { + switch (type) { + case kFloat2x2_GrSLType: + case kFloat3x3_GrSLType: + case kFloat4x4_GrSLType: + case kHalf2x2_GrSLType: + case kHalf3x3_GrSLType: + case kHalf4x4_GrSLType: + return true; + default: + return false; + } +} +#endif + +class GrGLSLVarying { +public: + enum class Scope { + kVertToFrag, + kVertToGeo, + kGeoToFrag + }; + + GrGLSLVarying() = default; + GrGLSLVarying(GrSLType type, Scope scope = Scope::kVertToFrag) + : fType(type) + , fScope(scope) { + // Metal doesn't support varying matrices, so we disallow them everywhere for consistency + SkASSERT(!is_matrix(type)); + } + + void reset(GrSLType type, Scope scope = Scope::kVertToFrag) { + // Metal doesn't support varying matrices, so we disallow them everywhere for consistency + SkASSERT(!is_matrix(type)); + *this = GrGLSLVarying(); + fType = type; + fScope = scope; + } + + GrSLType type() const { return fType; } + Scope scope() const { return fScope; } + bool isInVertexShader() const { return Scope::kGeoToFrag != fScope; } + bool isInFragmentShader() const { return Scope::kVertToGeo != fScope; } + + const char* vsOut() const { SkASSERT(this->isInVertexShader()); return fVsOut; } + const char* gsIn() const { return fGsIn; } + const char* gsOut() const { return fGsOut; } + const char* fsIn() const { SkASSERT(this->isInFragmentShader()); return fFsIn; } + +private: + GrSLType fType = kVoid_GrSLType; + Scope fScope = Scope::kVertToFrag; + const char* fVsOut = nullptr; + const char* fGsIn = nullptr; + const char* fGsOut = nullptr; + const char* fFsIn = nullptr; + + friend class GrGLSLVaryingHandler; +}; + +static const int kVaryingsPerBlock = 8; + +class GrGLSLVaryingHandler { +public: + explicit GrGLSLVaryingHandler(GrGLSLProgramBuilder* program) + : fVaryings(kVaryingsPerBlock) + , fVertexInputs(kVaryingsPerBlock) + , fVertexOutputs(kVaryingsPerBlock) + , fGeomInputs(kVaryingsPerBlock) + , fGeomOutputs(kVaryingsPerBlock) + , fFragInputs(kVaryingsPerBlock) + , fFragOutputs(kVaryingsPerBlock) + , fProgramBuilder(program) + , fDefaultInterpolationModifier(nullptr) {} + + virtual ~GrGLSLVaryingHandler() {} + + /* + * Notifies the varying handler that this shader will never emit geometry in perspective and + * therefore does not require perspective-correct interpolation. When supported, this allows + * varyings to use the "noperspective" keyword, which means the GPU can use cheaper math for + * interpolation. + */ + void setNoPerspective(); + + enum class Interpolation { + kInterpolated, + kCanBeFlat, // Use "flat" if it will be faster. + kMustBeFlat // Use "flat" even if it is known to be slow. + }; + + /* + * addVarying allows fine grained control for setting up varyings between stages. Calling this + * function will make sure all necessary decls are setup for the client. The client however is + * responsible for setting up all shader code (e.g "vOut = vIn;") If you just need to take an + * attribute and pass it through to an output value in a fragment shader, use + * addPassThroughAttribute. + * TODO convert most uses of addVarying to addPassThroughAttribute + */ + void addVarying(const char* name, GrGLSLVarying* varying, + Interpolation = Interpolation::kInterpolated); + + /* + * The GP can use these calls to pass an attribute through all shaders directly to 'output' in + * the fragment shader. Though these calls affect both the vertex shader and fragment shader, + * they expect 'output' to be defined in the fragment shader before the call is made. If there + * is a geometry shader, we will simply take the value of the varying from the first vertex and + * that will be set as the output varying for all emitted vertices. + * TODO it might be nicer behavior to have a flag to declare output inside these calls + */ + void addPassThroughAttribute(const GrGeometryProcessor::Attribute&, const char* output, + Interpolation = Interpolation::kInterpolated); + + void emitAttributes(const GrGeometryProcessor& gp); + + // This should be called once all attributes and varyings have been added to the + // GrGLSLVaryingHanlder and before getting/adding any of the declarations to the shaders. + void finalize(); + + void getVertexDecls(SkString* inputDecls, SkString* outputDecls) const; + void getGeomDecls(SkString* inputDecls, SkString* outputDecls) const; + void getFragDecls(SkString* inputDecls, SkString* outputDecls) const; + +protected: + struct VaryingInfo { + GrSLType fType; + bool fIsFlat; + SkString fVsOut; + SkString fGsOut; + GrShaderFlags fVisibility; + }; + + typedef GrTAllocator VaryingList; + typedef GrTAllocator VarArray; + typedef GrGLSLProgramDataManager::VaryingHandle VaryingHandle; + + VaryingList fVaryings; + VarArray fVertexInputs; + VarArray fVertexOutputs; + VarArray fGeomInputs; + VarArray fGeomOutputs; + VarArray fFragInputs; + VarArray fFragOutputs; + + // This is not owned by the class + GrGLSLProgramBuilder* fProgramBuilder; + +private: + void addAttribute(const GrShaderVar& var); + + virtual void onFinalize() = 0; + + // helper function for get*Decls + void appendDecls(const VarArray& vars, SkString* out) const; + + const char* fDefaultInterpolationModifier; + + friend class GrGLSLProgramBuilder; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp new file mode 100644 index 0000000000..ccec08db8b --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +#include "include/gpu/GrTypes.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/glsl/GrGLSLVarying.h" + +void GrGLSLVertexGeoBuilder::emitNormalizedSkPosition(SkString* out, const char* devPos, + const char* rtAdjustName, + GrSLType devPosType) { + if (this->getProgramBuilder()->snapVerticesToPixelCenters()) { + if (kFloat3_GrSLType == devPosType) { + const char* p = devPos; + out->appendf("{float2 _posTmp = float2(%s.x/%s.z, %s.y/%s.z);", p, p, p, p); + } else { + SkASSERT(kFloat2_GrSLType == devPosType); + out->appendf("{float2 _posTmp = %s;", devPos); + } + out->appendf("_posTmp = floor(_posTmp) + half2(0.5, 0.5);" + "sk_Position = float4(_posTmp, 0, 1);}"); + } else if (kFloat3_GrSLType == devPosType) { + out->appendf("sk_Position = float4(%s.x , %s.y, 0, %s.z);", + devPos, devPos, devPos); + } else { + SkASSERT(kFloat2_GrSLType == devPosType); + out->appendf("sk_Position = float4(%s.x , %s.y, 0, 1);", + devPos, devPos); + } +} + +void GrGLSLVertexBuilder::onFinalize() { + // We could have the GrGeometryProcessor do this, but its just easier to have it performed + // here. If we ever need to set variable pointsize, then we can reinvestigate. + if (this->getProgramBuilder()->hasPointSize()) { + this->codeAppend("sk_PointSize = 1.0;"); + } + fProgramBuilder->varyingHandler()->getVertexDecls(&this->inputs(), &this->outputs()); +} + +static const char* input_type_name(GrGLSLGeometryBuilder::InputType in) { + using InputType = GrGLSLGeometryBuilder::InputType; + switch (in) { + case InputType::kPoints: return "points"; + case InputType::kLines: return "lines"; + case InputType::kTriangles: return "triangles"; + } + SK_ABORT("invalid input type"); +} + +static const char* output_type_name(GrGLSLGeometryBuilder::OutputType out) { + using OutputType = GrGLSLGeometryBuilder::OutputType; + switch (out) { + case OutputType::kPoints: return "points"; + case OutputType::kLineStrip: return "line_strip"; + case OutputType::kTriangleStrip: return "triangle_strip"; + } + SK_ABORT("invalid output type"); +} + +void GrGLSLGeometryBuilder::configure(InputType inputType, OutputType outputType, int maxVertices, + int numInvocations) { + SkASSERT(!this->isConfigured()); + fNumInvocations = numInvocations; + this->addLayoutQualifier(input_type_name(inputType), kIn_InterfaceQualifier); + this->addLayoutQualifier(SkStringPrintf("invocations = %i", numInvocations).c_str(), + kIn_InterfaceQualifier); + this->addLayoutQualifier(output_type_name(outputType), kOut_InterfaceQualifier); + this->addLayoutQualifier(SkStringPrintf("max_vertices = %i", maxVertices).c_str(), + kOut_InterfaceQualifier); +} + +void GrGLSLGeometryBuilder::emitVertex(SkString* out, const char* devPos, const char* rtAdjustName, + GrSLType devPosType) { + this->emitNormalizedSkPosition(out, devPos, rtAdjustName, devPosType); + out->append("EmitVertex();"); +} + +void GrGLSLGeometryBuilder::endPrimitive() { + this->codeAppend("EndPrimitive();"); +} + +void GrGLSLGeometryBuilder::onFinalize() { + SkASSERT(this->isConfigured()); + fProgramBuilder->varyingHandler()->getGeomDecls(&this->inputs(), &this->outputs()); +} diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.h new file mode 100644 index 0000000000..914dbfa837 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.h @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLVertexGeoBuilder_DEFINED +#define GrGLSLVertexGeoBuilder_DEFINED + +#include "src/gpu/glsl/GrGLSLShaderBuilder.h" + +/** + * Base class for vertex and geometry shader builders. This is the stage that computes input + * geometry for the rasterizer. + */ +class GrGLSLVertexGeoBuilder : public GrGLSLShaderBuilder { +protected: + GrGLSLVertexGeoBuilder(GrGLSLProgramBuilder* program) : INHERITED(program) {} + + void emitNormalizedSkPosition(const char* devPos, const char* rtAdjustName, + GrSLType devPosType = GrSLType::kFloat2_GrSLType) { + this->emitNormalizedSkPosition(&this->code(), devPos, rtAdjustName, devPosType); + } + + void emitNormalizedSkPosition(SkString* out, const char* devPos, const char* rtAdjustName, + GrSLType devPosType = GrSLType::kFloat2_GrSLType); + + friend class GrGLSLGeometryProcessor; + + typedef GrGLSLShaderBuilder INHERITED; +}; + + +class GrGLSLVertexBuilder : public GrGLSLVertexGeoBuilder { +public: + GrGLSLVertexBuilder(GrGLSLProgramBuilder* program) : INHERITED(program) {} + +private: + void onFinalize() override; + + friend class GrGLProgramBuilder; + + typedef GrGLSLVertexGeoBuilder INHERITED; +}; + + +class GrGLSLGeometryBuilder : public GrGLSLVertexGeoBuilder { +public: + GrGLSLGeometryBuilder(GrGLSLProgramBuilder* program) : INHERITED(program) {} + + enum class InputType { + kPoints, + kLines, + kTriangles, + }; + + enum class OutputType { + kPoints, + kLineStrip, + kTriangleStrip + }; + + void configure(InputType, OutputType, int maxVertices, int numInvocations = 1); + bool isConfigured() const { return fNumInvocations; } + + void emitVertex(const char* devPos, const char* rtAdjustName, + GrSLType devPosType = GrSLType::kFloat2_GrSLType) { + this->emitVertex(&this->code(), devPos, rtAdjustName, devPosType); + } + void emitVertex(SkString* out, const char* devPos, const char* rtAdjustName, + GrSLType devPosType = GrSLType::kFloat2_GrSLType); + + void endPrimitive(); + +private: + void onFinalize() override; + + int fNumInvocations = 0; + + friend class GrGLProgramBuilder; + + typedef GrGLSLVertexGeoBuilder INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.cpp b/gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.cpp new file mode 100644 index 0000000000..7a04f4bfd2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/glsl/GrGLSLXferProcessor.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrXferProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +// This is only called for cases where we are doing LCD coverage and not using in shader blending. +// For these cases we assume the the src alpha is 1, thus we can just use the max for the alpha +// coverage since src alpha will always be greater than or equal to dst alpha. +static void adjust_for_lcd_coverage(GrGLSLXPFragmentBuilder* fragBuilder, + const char* srcCoverage, + const GrXferProcessor& proc) { + if (srcCoverage && proc.isLCD()) { + fragBuilder->codeAppendf("%s.a = max(max(%s.r, %s.g), %s.b);", + srcCoverage, srcCoverage, srcCoverage, srcCoverage); + } +} + + +void GrGLSLXferProcessor::emitCode(const EmitArgs& args) { + if (!args.fXP.willReadDstColor()) { + adjust_for_lcd_coverage(args.fXPFragBuilder, args.fInputCoverage, args.fXP); + this->emitOutputsForBlendState(args); + } else { + GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + const char* dstColor = fragBuilder->dstColor(); + + bool needsLocalOutColor = false; + + if (args.fDstTextureSamplerHandle.isValid()) { + bool flipY = kBottomLeft_GrSurfaceOrigin == args.fDstTextureOrigin; + + if (args.fInputCoverage) { + // We don't think any shaders actually output negative coverage, but just as a + // safety check for floating point precision errors we compare with <= here. We just + // check the rgb values of the coverage since the alpha may not have been set when + // using lcd. If we are using single channel coverage alpha will equal to rgb + // anyways. + // + // The discard here also helps for batching text draws together which need to read + // from a dst copy for blends. Though this only helps the case where the outer + // bounding boxes of each letter overlap and not two actually parts of the text. + fragBuilder->codeAppendf("if (all(lessThanEqual(%s.rgb, half3(0)))) {" + " discard;" + "}", args.fInputCoverage); + } + + const char* dstTopLeftName; + const char* dstCoordScaleName; + + fDstTopLeftUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf2_GrSLType, + "DstTextureUpperLeft", + &dstTopLeftName); + fDstScaleUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf2_GrSLType, + "DstTextureCoordScale", + &dstCoordScaleName); + + fragBuilder->codeAppend("// Read color from copy of the destination.\n"); + fragBuilder->codeAppendf("half2 _dstTexCoord = (half2(sk_FragCoord.xy) - %s) * %s;", + dstTopLeftName, dstCoordScaleName); + + if (flipY) { + fragBuilder->codeAppend("_dstTexCoord.y = 1.0 - _dstTexCoord.y;"); + } + + fragBuilder->codeAppendf("half4 %s = ", dstColor); + fragBuilder->appendTextureLookup(args.fDstTextureSamplerHandle, "_dstTexCoord", + kHalf2_GrSLType); + fragBuilder->codeAppend(";"); + } else { + needsLocalOutColor = args.fShaderCaps->requiresLocalOutputColorForFBFetch(); + } + + const char* outColor = "_localColorOut"; + if (!needsLocalOutColor) { + outColor = args.fOutputPrimary; + } else { + fragBuilder->codeAppendf("half4 %s;", outColor); + } + + this->emitBlendCodeForDstRead(fragBuilder, + uniformHandler, + args.fInputColor, + args.fInputCoverage, + dstColor, + outColor, + args.fOutputSecondary, + args.fXP); + if (needsLocalOutColor) { + fragBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, outColor); + } + } + + // Swizzle the fragment shader outputs if necessary. + this->emitOutputSwizzle( + args.fXPFragBuilder, args.fOutputSwizzle, args.fOutputPrimary, args.fOutputSecondary); +} + +void GrGLSLXferProcessor::emitOutputSwizzle( + GrGLSLXPFragmentBuilder* x, const GrSwizzle& swizzle, const char* outColor, + const char* outColorSecondary) const { + if (GrSwizzle::RGBA() != swizzle) { + x->codeAppendf("%s = %s.%s;", outColor, outColor, swizzle.c_str()); + if (outColorSecondary) { + x->codeAppendf("%s = %s.%s;", outColorSecondary, outColorSecondary, swizzle.c_str()); + } + } +} + +void GrGLSLXferProcessor::setData(const GrGLSLProgramDataManager& pdm, const GrXferProcessor& xp, + const GrTexture* dstTexture, const SkIPoint& dstTextureOffset) { + if (dstTexture) { + if (fDstTopLeftUni.isValid()) { + pdm.set2f(fDstTopLeftUni, static_cast(dstTextureOffset.fX), + static_cast(dstTextureOffset.fY)); + pdm.set2f(fDstScaleUni, 1.f / dstTexture->width(), 1.f / dstTexture->height()); + } else { + SkASSERT(!fDstScaleUni.isValid()); + } + } else { + SkASSERT(!fDstTopLeftUni.isValid()); + SkASSERT(!fDstScaleUni.isValid()); + } + this->onSetData(pdm, xp); +} + +void GrGLSLXferProcessor::DefaultCoverageModulation(GrGLSLXPFragmentBuilder* fragBuilder, + const char* srcCoverage, + const char* dstColor, + const char* outColor, + const char* outColorSecondary, + const GrXferProcessor& proc) { + if (proc.dstReadUsesMixedSamples()) { + if (srcCoverage) { + // TODO: Once we are no longer using legacy mesh ops, it will not be possible to even + // create a mixed sample with lcd so we can uncomment the below assert. In practice + // today this never happens except for GLPrograms test which can make one. skia:6661 + // SkASSERT(!proc.isLCD()); + fragBuilder->codeAppendf("%s *= %s;", outColor, srcCoverage); + fragBuilder->codeAppendf("%s = %s;", outColorSecondary, srcCoverage); + } else { + fragBuilder->codeAppendf("%s = half4(1.0);", outColorSecondary); + } + } else if (srcCoverage) { + if (proc.isLCD()) { + fragBuilder->codeAppendf("half lerpRed = mix(%s.a, %s.a, %s.r);", + dstColor, outColor, srcCoverage); + fragBuilder->codeAppendf("half lerpBlue = mix(%s.a, %s.a, %s.g);", + dstColor, outColor, srcCoverage); + fragBuilder->codeAppendf("half lerpGreen = mix(%s.a, %s.a, %s.b);", + dstColor, outColor, srcCoverage); + } + fragBuilder->codeAppendf("%s = %s * %s + (half4(1.0) - %s) * %s;", + outColor, srcCoverage, outColor, srcCoverage, dstColor); + if (proc.isLCD()) { + fragBuilder->codeAppendf("%s.a = max(max(lerpRed, lerpBlue), lerpGreen);", outColor); + } + } +} + diff --git a/gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.h b/gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.h new file mode 100644 index 0000000000..7c935a1769 --- /dev/null +++ b/gfx/skia/skia/src/gpu/glsl/GrGLSLXferProcessor.h @@ -0,0 +1,124 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGLSLXferProcessor_DEFINED +#define GrGLSLXferProcessor_DEFINED + +#include "include/core/SkPoint.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +class GrXferProcessor; +class GrGLSLXPBuilder; +class GrGLSLXPFragmentBuilder; +class GrShaderCaps; +class GrTexture; + +class GrGLSLXferProcessor { +public: + GrGLSLXferProcessor() {} + virtual ~GrGLSLXferProcessor() {} + + using SamplerHandle = GrGLSLUniformHandler::SamplerHandle; + + struct EmitArgs { + EmitArgs(GrGLSLXPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* caps, + const GrXferProcessor& xp, + const char* inputColor, + const char* inputCoverage, + const char* outputPrimary, + const char* outputSecondary, + const SamplerHandle dstTextureSamplerHandle, + GrSurfaceOrigin dstTextureOrigin, + const GrSwizzle& outputSwizzle) + : fXPFragBuilder(fragBuilder) + , fUniformHandler(uniformHandler) + , fShaderCaps(caps) + , fXP(xp) + , fInputColor(inputColor ? inputColor : "half4(1.0)") + , fInputCoverage(inputCoverage) + , fOutputPrimary(outputPrimary) + , fOutputSecondary(outputSecondary) + , fDstTextureSamplerHandle(dstTextureSamplerHandle) + , fDstTextureOrigin(dstTextureOrigin) + , fOutputSwizzle(outputSwizzle) { + } + GrGLSLXPFragmentBuilder* fXPFragBuilder; + GrGLSLUniformHandler* fUniformHandler; + const GrShaderCaps* fShaderCaps; + const GrXferProcessor& fXP; + const char* fInputColor; + const char* fInputCoverage; + const char* fOutputPrimary; + const char* fOutputSecondary; + const SamplerHandle fDstTextureSamplerHandle; + GrSurfaceOrigin fDstTextureOrigin; + GrSwizzle fOutputSwizzle; + }; + /** + * This is similar to emitCode() in the base class, except it takes a full shader builder. + * This allows the effect subclass to emit vertex code. + */ + void emitCode(const EmitArgs&); + + /** A GrGLSLXferProcessor instance can be reused with any GrGLSLXferProcessor that produces + the same stage key; this function reads data from a GrGLSLXferProcessor and uploads any + uniform variables required by the shaders created in emitCode(). The GrXferProcessor + parameter is guaranteed to be of the same type that created this GrGLSLXferProcessor and + to have an identical processor key as the one that created this GrGLSLXferProcessor. This + function calls onSetData on the subclass of GrGLSLXferProcessor + */ + void setData(const GrGLSLProgramDataManager& pdm, const GrXferProcessor& xp, + const GrTexture* dstTexture, const SkIPoint& dstTextureOffset); + +protected: + static void DefaultCoverageModulation(GrGLSLXPFragmentBuilder* fragBuilder, + const char* srcCoverage, + const char* dstColor, + const char* outColor, + const char* outColorSecondary, + const GrXferProcessor& proc); + +private: + /** + * Called by emitCode() when the XP will not be performing a dst read. This method is + * responsible for both blending and coverage. A subclass only needs to implement this method if + * it can construct a GrXferProcessor that will not read the dst color. + */ + virtual void emitOutputsForBlendState(const EmitArgs&) { + SK_ABORT("emitOutputsForBlendState not implemented."); + } + + /** + * Called by emitCode() when the XP will perform a dst read. This method only needs to supply + * the blending logic. The base class applies coverage. A subclass only needs to implement this + * method if it can construct a GrXferProcessor that reads the dst color. + */ + virtual void emitBlendCodeForDstRead(GrGLSLXPFragmentBuilder*, + GrGLSLUniformHandler*, + const char* srcColor, + const char* srcCoverage, + const char* dstColor, + const char* outColor, + const char* outColorSecondary, + const GrXferProcessor&) { + SK_ABORT("emitBlendCodeForDstRead not implemented."); + } + + virtual void emitOutputSwizzle(GrGLSLXPFragmentBuilder*, + const GrSwizzle&, + const char* outColor, + const char* outColorSecondary) const; + + virtual void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) = 0; + + GrGLSLProgramDataManager::UniformHandle fDstTopLeftUni; + GrGLSLProgramDataManager::UniformHandle fDstScaleUni; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gpu_workaround_list.txt b/gfx/skia/skia/src/gpu/gpu_workaround_list.txt new file mode 100644 index 0000000000..b58ce5d94b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gpu_workaround_list.txt @@ -0,0 +1,17 @@ +add_and_true_to_loop_condition +disable_blend_equation_advanced +disable_discard_framebuffer +disable_texture_storage +disallow_large_instanced_draw +emulate_abs_int_function +flush_on_framebuffer_change +gl_clear_broken +max_fragment_uniform_vectors_32 +max_msaa_sample_count_4 +max_texture_size_limit_4096 +pack_parameters_workaround_with_pack_buffer +remove_pow_with_constant_exponent +restore_scissor_on_fbo_change +rewrite_do_while_loops +unbind_attachments_on_bound_render_fbo_delete +unfold_short_circuit_as_ternary_operation diff --git a/gfx/skia/skia/src/gpu/gradients/GrClampedGradientEffect.fp b/gfx/skia/skia/src/gpu/gradients/GrClampedGradientEffect.fp new file mode 100644 index 0000000000..69d52f50e0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrClampedGradientEffect.fp @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// This master effect implements clamping on the layout coordinate and requires specifying the +// border colors that are used when outside the clamped boundary. Gradients with the +// SkShader::kClamp_TileMode should use the colors at their first and last stop (after adding dummy +// stops for t=0,t=1) as the border color. This will automatically replicate the edge color, even if +// when there is a hard stop. +// +// The SkShader::kDecal_TileMode can be produced by specifying transparent black as the border +// colors, regardless of the gradient's stop colors. + +in fragmentProcessor colorizer; +in fragmentProcessor gradLayout; + +layout(ctype=SkPMColor4f, tracked) in uniform half4 leftBorderColor; // t < 0.0 +layout(ctype=SkPMColor4f, tracked) in uniform half4 rightBorderColor; // t > 1.0 + +layout(key) in bool makePremul; +// Trust the creator that this matches the color spec of the gradient +in bool colorsAreOpaque; + +void main() { + half4 t = sample(gradLayout); + // If t.x is below 0, use the left border color without invoking the child processor. If any t.x + // is above 1, use the right border color. Otherwise, t is in the [0, 1] range assumed by the + // colorizer FP, so delegate to the child processor. + if (!gradLayout.preservesOpaqueInput && t.y < 0) { + // layout has rejected this fragment (rely on sksl to remove this branch if the layout FP + // preserves opacity is false) + sk_OutColor = half4(0); + } else if (t.x < 0) { + sk_OutColor = leftBorderColor; + } else if (t.x > 1.0) { + sk_OutColor = rightBorderColor; + } else { + sk_OutColor = sample(colorizer, t); + } + + @if(makePremul) { + sk_OutColor.xyz *= sk_OutColor.w; + } +} + +////////////////////////////////////////////////////////////////////////////// + +// If the layout does not preserve opacity, remove the opaque optimization, +// but otherwise respect the provided color opacity state (which should take +// into account the opacity of the border colors). +@optimizationFlags { + kCompatibleWithCoverageAsAlpha_OptimizationFlag | + (colorsAreOpaque && gradLayout->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags) +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrDualIntervalGradientColorizer.fp b/gfx/skia/skia/src/gpu/gradients/GrDualIntervalGradientColorizer.fp new file mode 100644 index 0000000000..602b075903 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrDualIntervalGradientColorizer.fp @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Models two intervals (so 4 colors), that are connected at a specific threshold point. + +// Bias and scale for 0 to threshold +layout(ctype=SkPMColor4f, tracked) in uniform float4 scale01; +layout(ctype=SkPMColor4f, tracked) in uniform float4 bias01; + +// Bias and scale for threshold to 1 +layout(ctype=SkPMColor4f, tracked) in uniform float4 scale23; +layout(ctype=SkPMColor4f, tracked) in uniform float4 bias23; + +layout(tracked) in uniform half threshold; + +void main() { + half t = sk_InColor.x; + + float4 scale, bias; + if (t < threshold) { + scale = scale01; + bias = bias01; + } else { + scale = scale23; + bias = bias23; + } + + sk_OutColor = half4(t * scale + bias); +} + +////////////////////////////////////////////////////////////////////////////// + +@make { + static std::unique_ptr Make(const SkPMColor4f& c0, const SkPMColor4f& c1, + const SkPMColor4f& c2, const SkPMColor4f& c3, + float threshold); +} + +@cppEnd { + std::unique_ptr GrDualIntervalGradientColorizer::Make( + const SkPMColor4f& c0, const SkPMColor4f& c1, const SkPMColor4f& c2, const SkPMColor4f& c3, float threshold) { + // Derive scale and biases from the 4 colors and threshold + auto vc0 = Sk4f::Load(c0.vec()); + auto vc1 = Sk4f::Load(c1.vec()); + auto scale01 = (vc1 - vc0) / threshold; + // bias01 = c0 + + auto vc2 = Sk4f::Load(c2.vec()); + auto vc3 = Sk4f::Load(c3.vec()); + auto scale23 = (vc3 - vc2) / (1 - threshold); + auto bias23 = vc2 - threshold * scale23; + + return std::unique_ptr(new GrDualIntervalGradientColorizer( + { scale01[0], scale01[1], scale01[2], scale01[3] }, c0, + { scale23[0], scale23[1], scale23[2], scale23[3] }, + { bias23[0], bias23[1], bias23[2], bias23[3] }, threshold)); + } +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.cpp b/gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.cpp new file mode 100644 index 0000000000..fdbc0fcc11 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.cpp @@ -0,0 +1,249 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/gpu/gradients/GrGradientBitmapCache.h" + +#include "include/private/SkFloatBits.h" +#include "include/private/SkHalf.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTemplates.h" + +#include + +struct GrGradientBitmapCache::Entry { + Entry* fPrev; + Entry* fNext; + + void* fBuffer; + size_t fSize; + SkBitmap fBitmap; + + Entry(const void* buffer, size_t size, const SkBitmap& bm) + : fPrev(nullptr), + fNext(nullptr), + fBitmap(bm) { + fBuffer = sk_malloc_throw(size); + fSize = size; + memcpy(fBuffer, buffer, size); + } + + ~Entry() { sk_free(fBuffer); } + + bool equals(const void* buffer, size_t size) const { + return (fSize == size) && !memcmp(fBuffer, buffer, size); + } +}; + +GrGradientBitmapCache::GrGradientBitmapCache(int max, int res) + : fMaxEntries(max) + , fResolution(res) { + fEntryCount = 0; + fHead = fTail = nullptr; + + this->validate(); +} + +GrGradientBitmapCache::~GrGradientBitmapCache() { + this->validate(); + + Entry* entry = fHead; + while (entry) { + Entry* next = entry->fNext; + delete entry; + entry = next; + } +} + +GrGradientBitmapCache::Entry* GrGradientBitmapCache::release(Entry* entry) const { + if (entry->fPrev) { + SkASSERT(fHead != entry); + entry->fPrev->fNext = entry->fNext; + } else { + SkASSERT(fHead == entry); + fHead = entry->fNext; + } + if (entry->fNext) { + SkASSERT(fTail != entry); + entry->fNext->fPrev = entry->fPrev; + } else { + SkASSERT(fTail == entry); + fTail = entry->fPrev; + } + return entry; +} + +void GrGradientBitmapCache::attachToHead(Entry* entry) const { + entry->fPrev = nullptr; + entry->fNext = fHead; + if (fHead) { + fHead->fPrev = entry; + } else { + fTail = entry; + } + fHead = entry; +} + +bool GrGradientBitmapCache::find(const void* buffer, size_t size, SkBitmap* bm) const { + AutoValidate av(this); + + Entry* entry = fHead; + while (entry) { + if (entry->equals(buffer, size)) { + if (bm) { + *bm = entry->fBitmap; + } + // move to the head of our list, so we purge it last + this->release(entry); + this->attachToHead(entry); + return true; + } + entry = entry->fNext; + } + return false; +} + +void GrGradientBitmapCache::add(const void* buffer, size_t len, const SkBitmap& bm) { + AutoValidate av(this); + + if (fEntryCount == fMaxEntries) { + SkASSERT(fTail); + delete this->release(fTail); + fEntryCount -= 1; + } + + Entry* entry = new Entry(buffer, len, bm); + this->attachToHead(entry); + fEntryCount += 1; +} + +/////////////////////////////////////////////////////////////////////////////// + + +void GrGradientBitmapCache::fillGradient(const SkPMColor4f* colors, const SkScalar* positions, + int count, SkColorType colorType, SkBitmap* bitmap) { + SkHalf* pixelsF16 = reinterpret_cast(bitmap->getPixels()); + uint32_t* pixels32 = reinterpret_cast(bitmap->getPixels()); + + typedef std::function pixelWriteFn_t; + + pixelWriteFn_t writeF16Pixel = [&](const Sk4f& x, int index) { + Sk4h c = SkFloatToHalf_finite_ftz(x); + pixelsF16[4*index+0] = c[0]; + pixelsF16[4*index+1] = c[1]; + pixelsF16[4*index+2] = c[2]; + pixelsF16[4*index+3] = c[3]; + }; + pixelWriteFn_t write8888Pixel = [&](const Sk4f& c, int index) { + pixels32[index] = Sk4f_toL32(c); + }; + + pixelWriteFn_t writePixel = + (colorType == kRGBA_F16_SkColorType) ? writeF16Pixel : write8888Pixel; + + int prevIndex = 0; + for (int i = 1; i < count; i++) { + // Historically, stops have been mapped to [0, 256], with 256 then nudged to the next + // smaller value, then truncate for the texture index. This seems to produce the best + // results for some common distributions, so we preserve the behavior. + int nextIndex = SkTMin(positions[i] * fResolution, + SkIntToScalar(fResolution - 1)); + + if (nextIndex > prevIndex) { + Sk4f c0 = Sk4f::Load(colors[i - 1].vec()), + c1 = Sk4f::Load(colors[i ].vec()); + + Sk4f step = Sk4f(1.0f / static_cast(nextIndex - prevIndex)); + Sk4f delta = (c1 - c0) * step; + + for (int curIndex = prevIndex; curIndex <= nextIndex; ++curIndex) { + writePixel(c0, curIndex); + c0 += delta; + } + } + prevIndex = nextIndex; + } + SkASSERT(prevIndex == fResolution - 1); +} + +void GrGradientBitmapCache::getGradient(const SkPMColor4f* colors, const SkScalar* positions, + int count, SkColorType colorType, SkAlphaType alphaType, SkBitmap* bitmap) { + // build our key: [numColors + colors[] + positions[] + alphaType + colorType ] + static_assert(sizeof(SkPMColor4f) % sizeof(int32_t) == 0, ""); + const int colorsAsIntCount = count * sizeof(SkPMColor4f) / sizeof(int32_t); + int keyCount = 1 + colorsAsIntCount + 1 + 1; + if (count > 2) { + keyCount += count - 1; + } + + SkAutoSTMalloc<64, int32_t> storage(keyCount); + int32_t* buffer = storage.get(); + + *buffer++ = count; + memcpy(buffer, colors, count * sizeof(SkPMColor4f)); + buffer += colorsAsIntCount; + if (count > 2) { + for (int i = 1; i < count; i++) { + *buffer++ = SkFloat2Bits(positions[i]); + } + } + *buffer++ = static_cast(alphaType); + *buffer++ = static_cast(colorType); + SkASSERT(buffer - storage.get() == keyCount); + + /////////////////////////////////// + + // acquire lock for checking/adding to cache + SkAutoMutexExclusive ama(fMutex); + size_t size = keyCount * sizeof(int32_t); + if (!this->find(storage.get(), size, bitmap)) { + SkImageInfo info = SkImageInfo::Make(fResolution, 1, colorType, alphaType); + bitmap->allocPixels(info); + GrGradientBitmapCache::fillGradient(colors, positions, count, colorType, bitmap); + bitmap->setImmutable(); + this->add(storage.get(), size, *bitmap); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +void GrGradientBitmapCache::validate() const { + SkASSERT(fEntryCount >= 0 && fEntryCount <= fMaxEntries); + + if (fEntryCount > 0) { + SkASSERT(nullptr == fHead->fPrev); + SkASSERT(nullptr == fTail->fNext); + + if (fEntryCount == 1) { + SkASSERT(fHead == fTail); + } else { + SkASSERT(fHead != fTail); + } + + Entry* entry = fHead; + int count = 0; + while (entry) { + count += 1; + entry = entry->fNext; + } + SkASSERT(count == fEntryCount); + + entry = fTail; + while (entry) { + count -= 1; + entry = entry->fPrev; + } + SkASSERT(0 == count); + } else { + SkASSERT(nullptr == fHead); + SkASSERT(nullptr == fTail); + } +} + +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.h b/gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.h new file mode 100644 index 0000000000..d4c8cef1f4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrGradientBitmapCache.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGradientBitmapCache_DEFINED +#define GrGradientBitmapCache_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMutex.h" +#include "include/private/SkNoncopyable.h" + +class GrGradientBitmapCache : SkNoncopyable { +public: + GrGradientBitmapCache(int maxEntries, int resolution); + ~GrGradientBitmapCache(); + + // Assumes colors are compatible with the specified alphaType (e.g. if it's premul then colors + // are already premultiplied). Thread safe. + void getGradient(const SkPMColor4f* colors, const SkScalar* positions, int count, + SkColorType colorType, SkAlphaType alphaType, SkBitmap* bitmap); + +private: + SkMutex fMutex; + + int fEntryCount; + const int fMaxEntries; + const int fResolution; + + struct Entry; + mutable Entry* fHead; + mutable Entry* fTail; + + inline Entry* release(Entry*) const; + inline void attachToHead(Entry*) const; + + bool find(const void* buffer, size_t len, SkBitmap*) const; + void add(const void* buffer, size_t len, const SkBitmap&); + + void fillGradient(const SkPMColor4f* colors, const SkScalar* positions, int count, + SkColorType colorType, SkBitmap* bitmap); + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate : SkNoncopyable { + public: + AutoValidate(const GrGradientBitmapCache* bc) : fBC(bc) { bc->validate(); } + ~AutoValidate() { fBC->validate(); } + private: + const GrGradientBitmapCache* fBC; + }; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/GrGradientShader.cpp b/gfx/skia/skia/src/gpu/gradients/GrGradientShader.cpp new file mode 100644 index 0000000000..a7946522a3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrGradientShader.cpp @@ -0,0 +1,308 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/gradients/GrGradientShader.h" + +#include "src/gpu/gradients/generated/GrClampedGradientEffect.h" +#include "src/gpu/gradients/generated/GrTiledGradientEffect.h" + +#include "src/gpu/gradients/generated/GrLinearGradientLayout.h" +#include "src/gpu/gradients/generated/GrRadialGradientLayout.h" +#include "src/gpu/gradients/generated/GrSweepGradientLayout.h" +#include "src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h" + +#include "src/gpu/gradients/GrGradientBitmapCache.h" +#include "src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h" +#include "src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h" +#include "src/gpu/gradients/generated/GrTextureGradientColorizer.h" +#include "src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" + +// Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to +// use the textured gradient +static const SkScalar kLowPrecisionIntervalLimit = 0.01f; + +// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp. +static const int kMaxNumCachedGradientBitmaps = 32; +static const int kGradientTextureSize = 256; + +// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for +// MakeColorizer to transparently take care of hard stops at the end points of the gradient. +static std::unique_ptr make_textured_colorizer(const SkPMColor4f* colors, + const SkScalar* positions, int count, bool premul, const GrFPArgs& args) { + static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize); + + // Use 8888 or F16, depending on the destination config. + // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102? + SkColorType colorType = kRGBA_8888_SkColorType; + if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) { + auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat( + GrColorType::kRGBA_F16, GrRenderable::kNo); + if (f16Format.isValid()) { + colorType = kRGBA_F16_SkColorType; + } + } + SkAlphaType alphaType = premul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + + SkBitmap bitmap; + gCache.getGradient(colors, positions, count, colorType, alphaType, &bitmap); + SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width())); + SkASSERT(bitmap.isImmutable()); + + sk_sp proxy = GrMakeCachedBitmapProxy( + args.fContext->priv().proxyProvider(), bitmap); + if (proxy == nullptr) { + SkDebugf("Gradient won't draw. Could not create texture."); + return nullptr; + } + + return GrTextureGradientColorizer::Make(std::move(proxy)); +} + +// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent +// the gradient. +static std::unique_ptr make_colorizer(const SkPMColor4f* colors, + const SkScalar* positions, int count, bool premul, const GrFPArgs& args) { + // If there are hard stops at the beginning or end, the first and/or last color should be + // ignored by the colorizer since it should only be used in a clamped border color. By detecting + // and removing these stops at the beginning, it makes optimizing the remaining color stops + // simpler. + + // SkGradientShaderBase guarantees that pos[0] == 0 by adding a dummy + bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]); + // The same is true for pos[end] == 1 + bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]); + + int offset = 0; + if (bottomHardStop) { + offset += 1; + count--; + } + if (topHardStop) { + count--; + } + + // Two remaining colors means a single interval from 0 to 1 + // (but it may have originally been a 3 or 4 color gradient with 1-2 hard stops at the ends) + if (count == 2) { + return GrSingleIntervalGradientColorizer::Make(colors[offset], colors[offset + 1]); + } + + // Do an early test for the texture fallback to skip all of the other tests for specific + // analytic support of the gradient (and compatibility with the hardware), when it's definitely + // impossible to use an analytic solution. + bool tryAnalyticColorizer = count <= GrUnrolledBinaryGradientColorizer::kMaxColorCount; + + // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become + // quite large when thresholds are close (but still outside the hardstop limit). If float isn't + // 32-bit, output can be incorrect if the thresholds are too close together. However, the + // analytic shaders are higher quality, so they can be used with lower precision hardware when + // the thresholds are not ill-conditioned. + const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps(); + if (!caps->floatIs32Bits() && tryAnalyticColorizer) { + // Could run into problems, check if thresholds are close together (with a limit of .01, so + // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit). + for (int i = offset; i < count - 1; i++) { + SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]); + if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) { + tryAnalyticColorizer = false; + break; + } + } + } + + if (tryAnalyticColorizer) { + if (count == 3) { + // Must be a dual interval gradient, where the middle point is at offset+1 and the two + // intervals share the middle color stop. + return GrDualIntervalGradientColorizer::Make(colors[offset], colors[offset + 1], + colors[offset + 1], colors[offset + 2], + positions[offset + 1]); + } else if (count == 4 && SkScalarNearlyEqual(positions[offset + 1], + positions[offset + 2])) { + // Two separate intervals that join at the same threshold position + return GrDualIntervalGradientColorizer::Make(colors[offset], colors[offset + 1], + colors[offset + 2], colors[offset + 3], + positions[offset + 1]); + } + + // The single and dual intervals are a specialized case of the unrolled binary search + // colorizer which can analytically render gradients of up to 8 intervals (up to 9 or 16 + // colors depending on how many hard stops are inserted). + std::unique_ptr unrolled = GrUnrolledBinaryGradientColorizer::Make( + colors + offset, positions + offset, count); + if (unrolled) { + return unrolled; + } + } + + // Otherwise fall back to a rasterized gradient sampled by a texture, which can handle + // arbitrary gradients (the only downside being sampling resolution). + return make_textured_colorizer(colors + offset, positions + offset, count, premul, args); +} + +// Combines the colorizer and layout with an appropriately configured master effect based on the +// gradient's tile mode +static std::unique_ptr make_gradient(const SkGradientShaderBase& shader, + const GrFPArgs& args, std::unique_ptr layout) { + // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned + // null. + if (layout == nullptr) { + return nullptr; + } + + // Convert all colors into destination space and into SkPMColor4fs, and handle + // premul issues depending on the interpolation mode + bool inputPremul = shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag; + bool allOpaque = true; + SkAutoSTMalloc<4, SkPMColor4f> colors(shader.fColorCount); + SkColor4fXformer xformedColors(shader.fOrigColors4f, shader.fColorCount, + shader.fColorSpace.get(), args.fDstColorInfo->colorSpace()); + for (int i = 0; i < shader.fColorCount; i++) { + const SkColor4f& upmColor = xformedColors.fColors[i]; + colors[i] = inputPremul ? upmColor.premul() + : SkPMColor4f{ upmColor.fR, upmColor.fG, upmColor.fB, upmColor.fA }; + if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) { + allOpaque = false; + } + } + + // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos() + // implementation performs a branch for every position index. Since the shader conversion + // requires lots of position tests, calculate all of the positions up front if needed. + SkTArray implicitPos; + SkScalar* positions; + if (shader.fOrigPos) { + positions = shader.fOrigPos; + } else { + implicitPos.reserve(shader.fColorCount); + SkScalar posScale = SK_Scalar1 / (shader.fColorCount - 1); + for (int i = 0 ; i < shader.fColorCount; i++) { + implicitPos.push_back(SkIntToScalar(i) * posScale); + } + positions = implicitPos.begin(); + } + + // All gradients are colorized the same way, regardless of layout + std::unique_ptr colorizer = make_colorizer( + colors.get(), positions, shader.fColorCount, inputPremul, args); + if (colorizer == nullptr) { + return nullptr; + } + + // The master effect has to export premul colors, but under certain conditions it doesn't need + // to do anything to achieve that: i.e. its interpolating already premul colors (inputPremul) + // or all the colors have a = 1, in which case premul is a no op. Note that this allOpaque + // check is more permissive than SkGradientShaderBase's isOpaque(), since we can optimize away + // the make-premul op for two point conical gradients (which report false for isOpaque). + bool makePremul = !inputPremul && !allOpaque; + + // All tile modes are supported (unless something was added to SkShader) + std::unique_ptr master; + switch(shader.getTileMode()) { + case SkTileMode::kRepeat: + master = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout), + /* mirror */ false, makePremul, allOpaque); + break; + case SkTileMode::kMirror: + master = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout), + /* mirror */ true, makePremul, allOpaque); + break; + case SkTileMode::kClamp: + // For the clamped mode, the border colors are the first and last colors, corresponding + // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as + // appropriate. If there is a hard stop, this grabs the expected outer colors for the + // border. + master = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout), + colors[0], colors[shader.fColorCount - 1], makePremul, allOpaque); + break; + case SkTileMode::kDecal: + // Even if the gradient colors are opaque, the decal borders are transparent so + // disable that optimization + master = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout), + SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT, + makePremul, /* colorsAreOpaque */ false); + break; + } + + if (master == nullptr) { + // Unexpected tile mode + return nullptr; + } + if (args.fInputColorIsOpaque) { + return GrFragmentProcessor::OverrideInput(std::move(master), SK_PMColor4fWHITE, false); + } + return GrFragmentProcessor::MulChildByInputAlpha(std::move(master)); +} + +namespace GrGradientShader { + +std::unique_ptr MakeLinear(const SkLinearGradient& shader, + const GrFPArgs& args) { + return make_gradient(shader, args, GrLinearGradientLayout::Make(shader, args)); +} + +std::unique_ptr MakeRadial(const SkRadialGradient& shader, + const GrFPArgs& args) { + return make_gradient(shader,args, GrRadialGradientLayout::Make(shader, args)); +} + +std::unique_ptr MakeSweep(const SkSweepGradient& shader, + const GrFPArgs& args) { + return make_gradient(shader,args, GrSweepGradientLayout::Make(shader, args)); +} + +std::unique_ptr MakeConical(const SkTwoPointConicalGradient& shader, + const GrFPArgs& args) { + return make_gradient(shader, args, GrTwoPointConicalGradientLayout::Make(shader, args)); +} + +#if GR_TEST_UTILS +RandomParams::RandomParams(SkRandom* random) { + // Set color count to min of 2 so that we don't trigger the const color optimization and make + // a non-gradient processor. + fColorCount = random->nextRangeU(2, kMaxRandomGradientColors); + fUseColors4f = random->nextBool(); + + // if one color, omit stops, otherwise randomly decide whether or not to + if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) { + fStops = nullptr; + } else { + fStops = fStopStorage; + } + + // if using SkColor4f, attach a random (possibly null) color space (with linear gamma) + if (fUseColors4f) { + fColorSpace = GrTest::TestColorSpace(random); + } + + SkScalar stop = 0.f; + for (int i = 0; i < fColorCount; ++i) { + if (fUseColors4f) { + fColors4f[i].fR = random->nextUScalar1(); + fColors4f[i].fG = random->nextUScalar1(); + fColors4f[i].fB = random->nextUScalar1(); + fColors4f[i].fA = random->nextUScalar1(); + } else { + fColors[i] = random->nextU(); + } + if (fStops) { + fStops[i] = stop; + stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f; + } + } + fTileMode = static_cast(random->nextULessThan(kSkTileModeCount)); +} +#endif + +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrGradientShader.h b/gfx/skia/skia/src/gpu/gradients/GrGradientShader.h new file mode 100644 index 0000000000..c8710e7bf4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrGradientShader.h @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrGradientShader_DEFINE +#define GrGradientShader_DEFINE + +#include "src/gpu/GrFPArgs.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/shaders/gradients/SkGradientShaderPriv.h" +#include "src/shaders/gradients/SkLinearGradient.h" +#include "src/shaders/gradients/SkRadialGradient.h" +#include "src/shaders/gradients/SkSweepGradient.h" +#include "src/shaders/gradients/SkTwoPointConicalGradient.h" + +#if GR_TEST_UTILS +#include "include/utils/SkRandom.h" +#endif + +namespace GrGradientShader { + std::unique_ptr MakeLinear(const SkLinearGradient& shader, + const GrFPArgs& args); + + std::unique_ptr MakeRadial(const SkRadialGradient& shader, + const GrFPArgs& args); + + std::unique_ptr MakeSweep(const SkSweepGradient& shader, + const GrFPArgs& args); + + std::unique_ptr MakeConical(const SkTwoPointConicalGradient& shader, + const GrFPArgs& args); + +#if GR_TEST_UTILS + /** Helper struct that stores (and populates) parameters to construct a random gradient. + If fUseColors4f is true, then the SkColor4f factory should be called, with fColors4f and + fColorSpace. Otherwise, the SkColor factory should be called, with fColors. fColorCount + will be the number of color stops in either case, and fColors and fStops can be passed to + the gradient factory. (The constructor may decide not to use stops, in which case fStops + will be nullptr). */ + struct RandomParams { + static constexpr int kMaxRandomGradientColors = 5; + + // Should be of similar magnitude to the draw area of the tests so that the gradient + // sampling is done at an appropriate scale. + static constexpr SkScalar kGradientScale = 256.0f; + + RandomParams(SkRandom* r); + + bool fUseColors4f; + SkColor fColors[kMaxRandomGradientColors]; + SkColor4f fColors4f[kMaxRandomGradientColors]; + sk_sp fColorSpace; + SkScalar fStopStorage[kMaxRandomGradientColors]; + SkTileMode fTileMode; + int fColorCount; + SkScalar* fStops; + }; +#endif + +} + +#endif // GrGradientShader_DEFINE diff --git a/gfx/skia/skia/src/gpu/gradients/GrLinearGradientLayout.fp b/gfx/skia/skia/src/gpu/gradients/GrLinearGradientLayout.fp new file mode 100644 index 0000000000..1b66dc9d6f --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrLinearGradientLayout.fp @@ -0,0 +1,74 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in half4x4 gradientMatrix; + +@coordTransform { + gradientMatrix +} + +void main() { + // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically + // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can + // we can get slightly different interpolated t values along the column/row. By adding the delta + // we will consistently get the color to the "right" of the stop. Of course if the hard stop + // falls at X.5 - delta then we still could get inconsistent results, but that is much less + // likely. crbug.com/938592 + // If/when we add filtering of the gradient this can be removed. + half t = half(sk_TransformedCoords2D[0].x) + 0.00001; + sk_OutColor = half4(t, 1, 0, 0); // y = 1 for always valid +} + +////////////////////////////////////////////////////////////////////////////// + +@header { + #include "src/gpu/gradients/GrGradientShader.h" + #include "src/shaders/gradients/SkLinearGradient.h" +} + +// The linear gradient never rejects a pixel so it doesn't change opacity +@optimizationFlags { + kPreservesOpaqueInput_OptimizationFlag +} + +@make { + static std::unique_ptr Make(const SkLinearGradient& gradient, + const GrFPArgs& args); +} + +@cppEnd { + std::unique_ptr GrLinearGradientLayout::Make( + const SkLinearGradient& grad, const GrFPArgs& args) { + SkMatrix matrix; + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + matrix.postConcat(grad.getGradientMatrix()); + return std::unique_ptr(new GrLinearGradientLayout(matrix)); + } +} + +////////////////////////////////////////////////////////////////////////////// + +@test(d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + SkPoint points[] = {{d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}, + {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}}; + + GrGradientShader::RandomParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeLinear(points, params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeLinear(points, params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + std::unique_ptr fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrRadialGradientLayout.fp b/gfx/skia/skia/src/gpu/gradients/GrRadialGradientLayout.fp new file mode 100644 index 0000000000..47b53cabd9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrRadialGradientLayout.fp @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in half4x4 gradientMatrix; + +@coordTransform { + gradientMatrix +} + +void main() { + half t = half(length(sk_TransformedCoords2D[0])); + sk_OutColor = half4(t, 1, 0, 0); // y = 1 for always valid +} + +////////////////////////////////////////////////////////////////////////////// + +@header { + #include "src/gpu/gradients/GrGradientShader.h" + #include "src/shaders/gradients/SkRadialGradient.h" +} + +// The radial gradient never rejects a pixel so it doesn't change opacity +@optimizationFlags { + kPreservesOpaqueInput_OptimizationFlag +} + +@make { + static std::unique_ptr Make(const SkRadialGradient& gradient, + const GrFPArgs& args); +} + +@cppEnd { + std::unique_ptr GrRadialGradientLayout::Make( + const SkRadialGradient& grad, const GrFPArgs& args) { + SkMatrix matrix; + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + matrix.postConcat(grad.getGradientMatrix()); + return std::unique_ptr(new GrRadialGradientLayout(matrix)); + } +} + +////////////////////////////////////////////////////////////////////////////// + +@test(d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + std::unique_ptr fp; + GrTest::TestAsFPArgs asFPArgs(d); + do { + GrGradientShader::RandomParams params(d->fRandom); + SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + SkScalar radius = d->fRandom->nextRangeScalar(0.0f, scale); + sk_sp shader = params.fUseColors4f + ? SkGradientShader::MakeRadial(center, radius, params.fColors4f, + params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) + : SkGradientShader::MakeRadial(center, radius, params.fColors, + params.fStops, params.fColorCount, + params.fTileMode); + // Degenerate params can create an Empty (non-null) shader, where fp will be nullptr + fp = shader ? as_SB(shader)->asFragmentProcessor(asFPArgs.args()) : nullptr; + } while (!fp); + return fp; +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrSingleIntervalGradientColorizer.fp b/gfx/skia/skia/src/gpu/gradients/GrSingleIntervalGradientColorizer.fp new file mode 100644 index 0000000000..621bd9731b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrSingleIntervalGradientColorizer.fp @@ -0,0 +1,21 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// This only supports a 2-color single interval so it is a simple linear interpolation between the +// two end points based on t. But it serves as a good test for connecting all of the plumbing into a +// functional gradient shader. + +layout(ctype=SkPMColor4f, tracked) in uniform half4 start; +layout(ctype=SkPMColor4f, tracked) in uniform half4 end; + +void main() { + half t = sk_InColor.x; + + // Clamping and/or wrapping was already handled by the parent shader so the output color is a + // simple lerp. + sk_OutColor = (1 - t) * start + t * end; +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrSweepGradientLayout.fp b/gfx/skia/skia/src/gpu/gradients/GrSweepGradientLayout.fp new file mode 100644 index 0000000000..09c1e6098b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrSweepGradientLayout.fp @@ -0,0 +1,83 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +in half4x4 gradientMatrix; + +layout(tracked) in uniform half bias; +layout(tracked) in uniform half scale; + +@coordTransform { + gradientMatrix +} + +void main() { + // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is + // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2 + // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the + // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and + // using atan instead. + half angle; + if (sk_Caps.atan2ImplementedAsAtanYOverX) { + angle = half(2 * atan(-sk_TransformedCoords2D[0].y, + length(sk_TransformedCoords2D[0]) - sk_TransformedCoords2D[0].x)); + } else { + angle = half(atan(-sk_TransformedCoords2D[0].y, -sk_TransformedCoords2D[0].x)); + } + + // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi] + half t = (angle * 0.1591549430918 + 0.5 + bias) * scale; + sk_OutColor = half4(t, 1, 0, 0); // y = 1 for always valid +} + +////////////////////////////////////////////////////////////////////////////// + +@header { + #include "src/gpu/gradients/GrGradientShader.h" + #include "src/shaders/gradients/SkSweepGradient.h" +} + +// The sweep gradient never rejects a pixel so it doesn't change opacity +@optimizationFlags { + kPreservesOpaqueInput_OptimizationFlag +} + +@make { + static std::unique_ptr Make(const SkSweepGradient& gradient, + const GrFPArgs& args); +} + +@cppEnd { + std::unique_ptr GrSweepGradientLayout::Make( + const SkSweepGradient& grad, const GrFPArgs& args) { + SkMatrix matrix; + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + matrix.postConcat(grad.getGradientMatrix()); + return std::unique_ptr(new GrSweepGradientLayout( + matrix, grad.getTBias(), grad.getTScale())); + } +} + +////////////////////////////////////////////////////////////////////////////// + +@test(d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + + GrGradientShader::RandomParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors4f, params.fColorSpace, + params.fStops, params.fColorCount) : + SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors, + params.fStops, params.fColorCount); + GrTest::TestAsFPArgs asFPArgs(d); + std::unique_ptr fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrTextureGradientColorizer.fp b/gfx/skia/skia/src/gpu/gradients/GrTextureGradientColorizer.fp new file mode 100644 index 0000000000..c3265bcb3a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrTextureGradientColorizer.fp @@ -0,0 +1,18 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Should have height = 1px, horizontal axis represents t = 0 to 1 +in uniform sampler2D gradient; + +@samplerParams(gradient) { + GrSamplerState::ClampBilerp() +} + +void main() { + half2 coord = half2(sk_InColor.x, 0.5); + sk_OutColor = sample(gradient, coord); +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrTiledGradientEffect.fp b/gfx/skia/skia/src/gpu/gradients/GrTiledGradientEffect.fp new file mode 100644 index 0000000000..aba5b6c74c --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrTiledGradientEffect.fp @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Provides tiling for the repeat or mirror modes. + +in fragmentProcessor colorizer; +in fragmentProcessor gradLayout; + +layout(key) in bool mirror; +layout(key) in bool makePremul; +// Trust the creator that this matches the color spec of the gradient +in bool colorsAreOpaque; + +void main() { + half4 t = sample(gradLayout); + + if (!gradLayout.preservesOpaqueInput && t.y < 0) { + // layout has rejected this fragment (rely on sksl to remove this branch if the layout FP + // preserves opacity is false) + sk_OutColor = half4(0); + } else { + @if(mirror) { + half t_1 = t.x - 1; + half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1; + if (sk_Caps.mustDoOpBetweenFloorAndAbs) { + // At this point the expected value of tiled_t should between -1 and 1, so this + // clamp has no effect other than to break up the floor and abs calls and make sure + // the compiler doesn't merge them back together. + tiled_t = clamp(tiled_t, -1, 1); + } + t.x = abs(tiled_t); + } else { + // Simple repeat mode + t.x = fract(t.x); + } + + // t.x has been tiled (repeat or mirrored), but pass through remaining 3 components + // unmodified. + sk_OutColor = sample(colorizer, t); + } + + @if (makePremul) { + sk_OutColor.xyz *= sk_OutColor.w; + } +} + +////////////////////////////////////////////////////////////////////////////// + +// If the layout does not preserve opacity, remove the opaque optimization, +// but otherwise respect the provided color opacity state. +@optimizationFlags { + kCompatibleWithCoverageAsAlpha_OptimizationFlag | + (colorsAreOpaque && gradLayout->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags) +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp b/gfx/skia/skia/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp new file mode 100644 index 0000000000..319221359a --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp @@ -0,0 +1,302 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Equivalent to SkTwoPointConicalGradient::Type +enum class Type { + kRadial, kStrip, kFocal +}; + +in half4x4 gradientMatrix; + +layout(key) in Type type; +layout(key) in bool isRadiusIncreasing; + +// Focal-specific optimizations +layout(key) in bool isFocalOnCircle; +layout(key) in bool isWellBehaved; +layout(key) in bool isSwapped; +layout(key) in bool isNativelyFocal; + +// focalParams is interpreted differently depending on if type is focal or degenerate when +// degenerate, focalParams = (r0, r0^2), so strips will use .y and kRadial will use .x when focal, +// focalParams = (1/r1, focalX = r0/(r0-r1)) The correct parameters are calculated once in Make for +// each FP +layout(tracked) in uniform half2 focalParams; + +@coordTransform { + gradientMatrix +} + +void main() { + // p typed as a float2 is intentional; while a half2 is adequate for most normal cases in the + // two point conic gradient's coordinate system, when the gradient is composed with a local + // perspective matrix, certain out-of-bounds regions become ill behaved on mobile devices. + // On desktops, they are properly clamped after the fact, but on many Adreno GPUs the + // calculations of t and x_t below overflow and produce an incorrect interpolant (which then + // renders the wrong border color sporadically). Increasing precition alleviates that issue. + float2 p = sk_TransformedCoords2D[0]; + float t = -1; + half v = 1; // validation flag, set to negative to discard fragment later + + @switch(type) { + case Type::kStrip: { + half r0_2 = focalParams.y; + t = r0_2 - p.y * p.y; + if (t >= 0) { + t = p.x + sqrt(t); + } else { + v = -1; + } + } + break; + case Type::kRadial: { + half r0 = focalParams.x; + @if(isRadiusIncreasing) { + t = length(p) - r0; + } else { + t = -length(p) - r0; + } + } + break; + case Type::kFocal: { + half invR1 = focalParams.x; + half fx = focalParams.y; + + float x_t = -1; + @if (isFocalOnCircle) { + x_t = dot(p, p) / p.x; + } else if (isWellBehaved) { + x_t = length(p) - p.x * invR1; + } else { + float temp = p.x * p.x - p.y * p.y; + + // Only do sqrt if temp >= 0; this is significantly slower than checking temp >= 0 + // in the if statement that checks r(t) >= 0. But GPU may break if we sqrt a + // negative float. (Although I havevn't observed that on any devices so far, and the + // old approach also does sqrt negative value without a check.) If the performance + // is really critical, maybe we should just compute the area where temp and x_t are + // always valid and drop all these ifs. + if (temp >= 0) { + @if(isSwapped || !isRadiusIncreasing) { + x_t = -sqrt(temp) - p.x * invR1; + } else { + x_t = sqrt(temp) - p.x * invR1; + } + } + } + + // The final calculation of t from x_t has lots of static optimizations but only do them + // when x_t is positive (which can be assumed true if isWellBehaved is true) + @if (!isWellBehaved) { + // This will still calculate t even though it will be ignored later in the pipeline + // to avoid a branch + if (x_t <= 0.0) { + v = -1; + } + } + @if (isRadiusIncreasing) { + @if (isNativelyFocal) { + t = x_t; + } else { + t = x_t + fx; + } + } else { + @if (isNativelyFocal) { + t = -x_t; + } else { + t = -x_t + fx; + } + } + + @if(isSwapped) { + t = 1 - t; + } + } + break; + } + + sk_OutColor = half4(half(t), v, 0, 0); +} + +////////////////////////////////////////////////////////////////////////////// + +@header { + #include "src/gpu/gradients/GrGradientShader.h" + #include "src/shaders/gradients/SkTwoPointConicalGradient.h" +} + +// The 2 point conical gradient can reject a pixel so it does change opacity +// even if the input was opaque, so disable that optimization +@optimizationFlags { + kNone_OptimizationFlags +} + +@make { + static std::unique_ptr Make(const SkTwoPointConicalGradient& gradient, + const GrFPArgs& args); +} + +@cppEnd { + // .fp files do not let you reference outside enum definitions, so we have to explicitly map + // between the two compatible enum defs + GrTwoPointConicalGradientLayout::Type convert_type( + SkTwoPointConicalGradient::Type type) { + switch(type) { + case SkTwoPointConicalGradient::Type::kRadial: + return GrTwoPointConicalGradientLayout::Type::kRadial; + case SkTwoPointConicalGradient::Type::kStrip: + return GrTwoPointConicalGradientLayout::Type::kStrip; + case SkTwoPointConicalGradient::Type::kFocal: + return GrTwoPointConicalGradientLayout::Type::kFocal; + } + SkDEBUGFAIL("Should not be reachable"); + return GrTwoPointConicalGradientLayout::Type::kRadial; + } + + std::unique_ptr GrTwoPointConicalGradientLayout::Make( + const SkTwoPointConicalGradient& grad, const GrFPArgs& args) { + GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType()); + + // The focalData struct is only valid if isFocal is true + const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData(); + bool isFocal = grType == Type::kFocal; + + // Calculate optimization switches from gradient specification + bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle(); + bool isWellBehaved = isFocal && focalData.isWellBehaved(); + bool isSwapped = isFocal && focalData.isSwapped(); + bool isNativelyFocal = isFocal && focalData.isNativelyFocal(); + + // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix. + // However, all types start with the total inverse local matrix calculated from the shader + // and args + bool isRadiusIncreasing; + SkPoint focalParams; // really just a 2D tuple + SkMatrix matrix; + + // Initialize the base matrix + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + + if (isFocal) { + isRadiusIncreasing = (1 - focalData.fFocalX) > 0; + + focalParams.set(1.0 / focalData.fR1, focalData.fFocalX); + + matrix.postConcat(grad.getGradientMatrix()); + } else if (grType == Type::kRadial) { + SkScalar dr = grad.getDiffRadius(); + isRadiusIncreasing = dr >= 0; + + SkScalar r0 = grad.getStartRadius() / dr; + focalParams.set(r0, r0 * r0); + + + // GPU radial matrix is different from the original matrix, since we map the diff radius + // to have |dr| = 1, so manually compute the final gradient matrix here. + + // Map center to (0, 0) + matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY); + + // scale |diffRadius| to 1 + matrix.postScale(1 / dr, 1 / dr); + } else { // kStrip + isRadiusIncreasing = false; // kStrip doesn't use this flag + + SkScalar r0 = grad.getStartRadius() / grad.getCenterX1(); + focalParams.set(r0, r0 * r0); + + + matrix.postConcat(grad.getGradientMatrix()); + } + + + return std::unique_ptr(new GrTwoPointConicalGradientLayout( + matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, + isSwapped, isNativelyFocal, focalParams)); + } +} + +////////////////////////////////////////////////////////////////////////////// + +@test(d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + SkScalar offset = scale / 32.0f; + + SkPoint center1 = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + SkPoint center2 = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + SkScalar radius1 = d->fRandom->nextRangeScalar(0.0f, scale); + SkScalar radius2 = d->fRandom->nextRangeScalar(0.0f, scale); + + constexpr int kTestTypeMask = (1 << 2) - 1, + kTestNativelyFocalBit = (1 << 2), + kTestFocalOnCircleBit = (1 << 3), + kTestSwappedBit = (1 << 4); + // We won't treat isWellDefined and isRadiusIncreasing specially because they + // should have high probability to be turned on and off as we're getting random + // radii and centers. + + int mask = d->fRandom->nextU(); + int type = mask & kTestTypeMask; + if (type == static_cast(Type::kRadial)) { + center2 = center1; + // Make sure that the radii are different + if (SkScalarNearlyZero(radius1 - radius2)) { + radius2 += offset; + } + } else if (type == static_cast(Type::kStrip)) { + radius1 = SkTMax(radius1, .1f); // Make sure that the radius is non-zero + radius2 = radius1; + // Make sure that the centers are different + if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { + center2.fX += offset; + } + } else { // kFocal_Type + // Make sure that the centers are different + if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { + center2.fX += offset; + } + + if (kTestNativelyFocalBit & mask) { + radius1 = 0; + } + if (kTestFocalOnCircleBit & mask) { + radius2 = radius1 + SkPoint::Distance(center1, center2); + } + if (kTestSwappedBit & mask) { + std::swap(radius1, radius2); + radius2 = 0; + } + + // Make sure that the radii are different + if (SkScalarNearlyZero(radius1 - radius2)) { + radius2 += offset; + } + } + + if (SkScalarNearlyZero(radius1 - radius2) && + SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { + radius2 += offset; // make sure that we're not degenerated + } + + GrGradientShader::RandomParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + std::unique_ptr fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + + GrAlwaysAssert(fp); + return fp; +} diff --git a/gfx/skia/skia/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp b/gfx/skia/skia/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp new file mode 100644 index 0000000000..d5d7053498 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp @@ -0,0 +1,188 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Unrolled gradient code supporting up to 8 intervals that produces code +// targeting a specific interval count. + +// Assumed to be between 1 and 8. +layout(key) in int intervalCount; + +// With the current hardstop detection threshold of 0.00024, the maximum scale and bias values +// will be on the order of 4k (since they divide by dt). That is well outside the precision +// capabilities of half floats, which can lead to inaccurate gradient calculations +layout(ctype=SkPMColor4f) in uniform float4 scale0_1; +layout(ctype=SkPMColor4f, when=intervalCount > 1) in uniform float4 scale2_3; +layout(ctype=SkPMColor4f, when=intervalCount > 2) in uniform float4 scale4_5; +layout(ctype=SkPMColor4f, when=intervalCount > 3) in uniform float4 scale6_7; +layout(ctype=SkPMColor4f, when=intervalCount > 4) in uniform float4 scale8_9; +layout(ctype=SkPMColor4f, when=intervalCount > 5) in uniform float4 scale10_11; +layout(ctype=SkPMColor4f, when=intervalCount > 6) in uniform float4 scale12_13; +layout(ctype=SkPMColor4f, when=intervalCount > 7) in uniform float4 scale14_15; + +layout(ctype=SkPMColor4f) in uniform float4 bias0_1; +layout(ctype=SkPMColor4f, when=intervalCount > 1) in uniform float4 bias2_3; +layout(ctype=SkPMColor4f, when=intervalCount > 2) in uniform float4 bias4_5; +layout(ctype=SkPMColor4f, when=intervalCount > 3) in uniform float4 bias6_7; +layout(ctype=SkPMColor4f, when=intervalCount > 4) in uniform float4 bias8_9; +layout(ctype=SkPMColor4f, when=intervalCount > 5) in uniform float4 bias10_11; +layout(ctype=SkPMColor4f, when=intervalCount > 6) in uniform float4 bias12_13; +layout(ctype=SkPMColor4f, when=intervalCount > 7) in uniform float4 bias14_15; + +// The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0, and t = +// 1) are packed into two half4's instead of having up to 7 separate scalar uniforms. For low +// interval counts, the extra components are ignored in the shader, but the uniform simplification +// is worth it. It is assumed thresholds are provided in increasing value, mapped as: +// - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2 +// - .y = boundary between (2,3) and (4,5) -> 3_4 +// - .z = boundary between (4,5) and (6,7) -> 5_6 +// - .w = boundary between (6,7) and (8,9) -> 7_8 +// - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10 +// - .y = boundary between (10,11) and (12,13) -> 11_12 +// - .z = boundary between (12,13) and (14,15) -> 13_14 +// - .w = unused +in uniform half4 thresholds1_7; +in uniform half4 thresholds9_13; + +void main() { + half t = sk_InColor.x; + + float4 scale, bias; + // Explicit binary search for the proper interval that t falls within. The interval count + // checks are converted into constant expressions in the C++ generated SkSL, which are then + // optimized to the minimal number of branches for the specific interval count. + + // thresholds1_7.w is mid point for intervals (0,7) and (8,15) + if (intervalCount <= 4 || t < thresholds1_7.w) { + // thresholds1_7.y is mid point for intervals (0,3) and (4,7) + if (intervalCount <= 2 || t < thresholds1_7.y) { + // thresholds1_7.x is mid point for intervals (0,1) and (2,3) + if (intervalCount <= 1 || t < thresholds1_7.x) { + scale = scale0_1; + bias = bias0_1; + } else { + scale = scale2_3; + bias = bias2_3; + } + } else { + // thresholds1_7.z is mid point for intervals (4,5) and (6,7) + if (intervalCount <= 3 || t < thresholds1_7.z) { + scale = scale4_5; + bias = bias4_5; + } else { + scale = scale6_7; + bias = bias6_7; + } + } + } else { + // thresholds9_13.y is mid point for intervals (8,11) and (12,15) + if (intervalCount <= 6 || t < thresholds9_13.y) { + // thresholds9_13.x is mid point for intervals (8,9) and (10,11) + if (intervalCount <= 5 || t < thresholds9_13.x) { + // interval 8-9 + scale = scale8_9; + bias = bias8_9; + } else { + // interval 10-11 + scale = scale10_11; + bias = bias10_11; + } + } else { + // thresholds9_13.z is mid point for intervals (12,13) and (14,15) + if (intervalCount <= 7 || t < thresholds9_13.z) { + // interval 12-13 + scale = scale12_13; + bias = bias12_13; + } else { + // interval 14-15 + scale = scale14_15; + bias = bias14_15; + } + } + } + + sk_OutColor = half4(t * scale + bias); +} + +////////////////////////////////////////////////////////////////////////////// + +@class { + static const int kMaxColorCount = 16; +} + +@make { + static std::unique_ptr Make(const SkPMColor4f* colors, + const SkScalar* positions, + int count); +} + +@cppEnd { + static const int kMaxIntervals = 8; + std::unique_ptr GrUnrolledBinaryGradientColorizer::Make( + const SkPMColor4f* colors, const SkScalar* positions, int count) { + // Depending on how the positions resolve into hard stops or regular stops, the number of + // intervals specified by the number of colors/positions can change. For instance, a plain + // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also + // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard + // stops has 16 colors. + + if (count > kMaxColorCount) { + // Definitely cannot represent this gradient configuration + return nullptr; + } + + // The raster implementation also uses scales and biases, but since they must be calculated + // after the dst color space is applied, it limits our ability to cache their values. + SkPMColor4f scales[kMaxIntervals]; + SkPMColor4f biases[kMaxIntervals]; + SkScalar thresholds[kMaxIntervals]; + + int intervalCount = 0; + + for (int i = 0; i < count - 1; i++) { + if (intervalCount >= kMaxIntervals) { + // Already reached kMaxIntervals, and haven't run out of color stops so this + // gradient cannot be represented by this shader. + return nullptr; + } + + SkScalar t0 = positions[i]; + SkScalar t1 = positions[i + 1]; + SkScalar dt = t1 - t0; + // If the interval is empty, skip to the next interval. This will automatically create + // distinct hard stop intervals as needed. It also protects against malformed gradients + // that have repeated hard stops at the very beginning that are effectively unreachable. + if (SkScalarNearlyZero(dt)) { + continue; + } + + auto c0 = Sk4f::Load(colors[i].vec()); + auto c1 = Sk4f::Load(colors[i + 1].vec()); + + auto scale = (c1 - c0) / dt; + auto bias = c0 - t0 * scale; + + scale.store(scales + intervalCount); + bias.store(biases + intervalCount); + thresholds[intervalCount] = t1; + intervalCount++; + } + + // For isEqual to make sense, set the unused values to something consistent + for (int i = intervalCount; i < kMaxIntervals; i++) { + scales[i] = SK_PMColor4fTRANSPARENT; + biases[i] = SK_PMColor4fTRANSPARENT; + thresholds[i] = 0.0; + } + + return std::unique_ptr(new GrUnrolledBinaryGradientColorizer( + intervalCount, scales[0], scales[1], scales[2], scales[3], scales[4], scales[5], + scales[6], scales[7], biases[0], biases[1], biases[2], biases[3], biases[4], + biases[5], biases[6], biases[7], + SkRect::MakeLTRB(thresholds[0], thresholds[1], thresholds[2], thresholds[3]), + SkRect::MakeLTRB(thresholds[4], thresholds[5], thresholds[6], 0.0))); + } +} diff --git a/gfx/skia/skia/src/gpu/gradients/README.md b/gfx/skia/skia/src/gpu/gradients/README.md new file mode 100644 index 0000000000..72ed05a2c9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/README.md @@ -0,0 +1,71 @@ +Gradients on the GPU +==================== + +Gradients can be thought of, at a very high level, as three pieces: + +1. A color interpolator that is one dimensional, returning a color for an input + within the range [0.0, 1.0]. This obfuscates the the definition of specific + color stops and how to wrap, tile, or clamp out of bound inputs. A color + interpolator will be named GrYGradientColorizer +2. A layout that converts from 2D geometry/position to the one dimensional + domain of the color interpolator. This is how a linear or radial gradient + distinguishes itself. When designing a new gradient, this is the component + that you have to implement. A layout will generally be named + GrXGradientLayout +3. A master effect that composes the layout and color interpolator together. It + is also responsible for implementing the clamping behavior that can be + abstracted away from both the layout and colorization. + + +GrClampedGradientEffect handles clamped and decal tile modes, while +GrTiledGradientEffect implements repeat and mirror tile modes. The +GrClampedGradientEffect requires border colors to be specified outside of its +colorizer child, but these border colors may be defined by the gradient color +stops. Both of these master effects delegate calculating a t interpolant to a +child process, perform their respective tile mode operations, and possibly +convert the tiled t value (guaranteed to be within 0 and 1) into an output +color using their child colorizer process. + +Because of how child processors are currently defined, where they have a single +half4 input and a single half4 output, their is a type mismatch between the 1D +t value and the 4D inputs/outputs of the layout and colorizer processes. For +now, the master effect assumes an untiled t is output in sk_OutColor.x by the +layout and it tiles solely off of that value. + +However, layouts can output a negative value in the y component to invalidate +the gradient location (currently on the two point conical gradient does this). +When invalidated, the master effect outputs transparent black and does not +invoke the child processor. Other than this condition, any value in y, z, or w +are passed into the colorizer unmodified. The colorizer should assume that the +valid tiled t value is in sk_InColor.x and can safely ignore y, z, and w. + +Currently there are color interpolators (colorizers) for analytic color cases +(evaluated directly on the GPU) and sampling a generated texture map. + +GrGradientShader provides static factory functions to create +GrFragmentProcessor graphs that reproduce a particular SkGradientShader. + +Optimization Flags +================== + +At an abstract level, gradient shaders are compatible with coverage as alpha +and, under certain conditions, preserve opacity when the inputs are opaque. To +reduce the amount of duplicate code and boilerplate, these optimization +decisions are implemented in the master effects and not in the colorizers. It +is assumed that all colorizer FPs will be compatible with coverage as alpha and +will preserve opacity if input colors are opaque. Since this is assumed by the +master effects, they do not need to report these optimizations or check input +opacity (this does mean if the colorizers are used independently from the +master effect shader that the reported flags might not be optimal, but since +that is unlikely, this convention really simplifies the colorizer +implementations). + +Unlike colorizers, which do not need to report any optimization flags, layout +FPs should report opacity preserving optimizations because they can impact the +opacity of a pixel outside of how the gradient would otherwise color it. +Layouts that potentially reject pixels (i.e. could output a negative y value) +must not report kPreservesOpaqueInput_OptimizationFlag. Layouts that never +reject a pixel should report kPreservesOpaqueInput_OptimizationFlag since the +master effects can optimize away checking if the layout rejects a pixel. + + diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.cpp new file mode 100644 index 0000000000..268fc6f9f8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrClampedGradientEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrClampedGradientEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLClampedGradientEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLClampedGradientEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrClampedGradientEffect& _outer = args.fFp.cast(); + (void)_outer; + auto leftBorderColor = _outer.leftBorderColor; + (void)leftBorderColor; + auto rightBorderColor = _outer.rightBorderColor; + (void)rightBorderColor; + auto makePremul = _outer.makePremul; + (void)makePremul; + auto colorsAreOpaque = _outer.colorsAreOpaque; + (void)colorsAreOpaque; + leftBorderColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, "leftBorderColor"); + rightBorderColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, "rightBorderColor"); + SkString _sample1099("_sample1099"); + this->invokeChild(_outer.gradLayout_index, &_sample1099, args); + fragBuilder->codeAppendf( + "half4 t = %s;\nif (!%s && t.y < 0.0) {\n %s = half4(0.0);\n} else if (t.x < " + "0.0) {\n %s = %s;\n} else if (t.x > 1.0) {\n %s = %s;\n} else {", + _sample1099.c_str(), + (_outer.childProcessor(_outer.gradLayout_index).preservesOpaqueInput() ? "true" + : "false"), + args.fOutputColor, args.fOutputColor, + args.fUniformHandler->getUniformCStr(leftBorderColorVar), args.fOutputColor, + args.fUniformHandler->getUniformCStr(rightBorderColorVar)); + SkString _input1767("t"); + SkString _sample1767("_sample1767"); + this->invokeChild(_outer.colorizer_index, _input1767.c_str(), &_sample1767, args); + fragBuilder->codeAppendf("\n %s = %s;\n}\n@if (%s) {\n %s.xyz *= %s.w;\n}\n", + args.fOutputColor, _sample1767.c_str(), + (_outer.makePremul ? "true" : "false"), args.fOutputColor, + args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrClampedGradientEffect& _outer = _proc.cast(); + { + const SkPMColor4f& leftBorderColorValue = _outer.leftBorderColor; + if (leftBorderColorPrev != leftBorderColorValue) { + leftBorderColorPrev = leftBorderColorValue; + pdman.set4fv(leftBorderColorVar, 1, leftBorderColorValue.vec()); + } + const SkPMColor4f& rightBorderColorValue = _outer.rightBorderColor; + if (rightBorderColorPrev != rightBorderColorValue) { + rightBorderColorPrev = rightBorderColorValue; + pdman.set4fv(rightBorderColorVar, 1, rightBorderColorValue.vec()); + } + } + } + SkPMColor4f leftBorderColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + SkPMColor4f rightBorderColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + UniformHandle leftBorderColorVar; + UniformHandle rightBorderColorVar; +}; +GrGLSLFragmentProcessor* GrClampedGradientEffect::onCreateGLSLInstance() const { + return new GrGLSLClampedGradientEffect(); +} +void GrClampedGradientEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)makePremul); +} +bool GrClampedGradientEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrClampedGradientEffect& that = other.cast(); + (void)that; + if (leftBorderColor != that.leftBorderColor) return false; + if (rightBorderColor != that.rightBorderColor) return false; + if (makePremul != that.makePremul) return false; + if (colorsAreOpaque != that.colorsAreOpaque) return false; + return true; +} +GrClampedGradientEffect::GrClampedGradientEffect(const GrClampedGradientEffect& src) + : INHERITED(kGrClampedGradientEffect_ClassID, src.optimizationFlags()) + , colorizer_index(src.colorizer_index) + , gradLayout_index(src.gradLayout_index) + , leftBorderColor(src.leftBorderColor) + , rightBorderColor(src.rightBorderColor) + , makePremul(src.makePremul) + , colorsAreOpaque(src.colorsAreOpaque) { + this->registerChildProcessor(src.childProcessor(colorizer_index).clone()); + this->registerChildProcessor(src.childProcessor(gradLayout_index).clone()); +} +std::unique_ptr GrClampedGradientEffect::clone() const { + return std::unique_ptr(new GrClampedGradientEffect(*this)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.h b/gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.h new file mode 100644 index 0000000000..115fe4a8a2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrClampedGradientEffect.h @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrClampedGradientEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrClampedGradientEffect_DEFINED +#define GrClampedGradientEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrClampedGradientEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make( + std::unique_ptr colorizer, + std::unique_ptr gradLayout, SkPMColor4f leftBorderColor, + SkPMColor4f rightBorderColor, bool makePremul, bool colorsAreOpaque) { + return std::unique_ptr(new GrClampedGradientEffect( + std::move(colorizer), std::move(gradLayout), leftBorderColor, rightBorderColor, + makePremul, colorsAreOpaque)); + } + GrClampedGradientEffect(const GrClampedGradientEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "ClampedGradientEffect"; } + int colorizer_index = -1; + int gradLayout_index = -1; + SkPMColor4f leftBorderColor; + SkPMColor4f rightBorderColor; + bool makePremul; + bool colorsAreOpaque; + +private: + GrClampedGradientEffect(std::unique_ptr colorizer, + std::unique_ptr gradLayout, + SkPMColor4f leftBorderColor, SkPMColor4f rightBorderColor, + bool makePremul, bool colorsAreOpaque) + : INHERITED(kGrClampedGradientEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag | + (colorsAreOpaque && gradLayout->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags)) + , leftBorderColor(leftBorderColor) + , rightBorderColor(rightBorderColor) + , makePremul(makePremul) + , colorsAreOpaque(colorsAreOpaque) { + SkASSERT(colorizer); + colorizer_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(colorizer)); + SkASSERT(gradLayout); + gradLayout_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(gradLayout)); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp new file mode 100644 index 0000000000..b7ce502d3f --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp @@ -0,0 +1,149 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrDualIntervalGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#include "GrDualIntervalGradientColorizer.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLDualIntervalGradientColorizer : public GrGLSLFragmentProcessor { +public: + GrGLSLDualIntervalGradientColorizer() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrDualIntervalGradientColorizer& _outer = + args.fFp.cast(); + (void)_outer; + auto scale01 = _outer.scale01; + (void)scale01; + auto bias01 = _outer.bias01; + (void)bias01; + auto scale23 = _outer.scale23; + (void)scale23; + auto bias23 = _outer.bias23; + (void)bias23; + auto threshold = _outer.threshold; + (void)threshold; + scale01Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale01"); + bias01Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias01"); + scale23Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale23"); + bias23Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias23"); + thresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, + "threshold"); + fragBuilder->codeAppendf( + "half t = %s.x;\nfloat4 scale, bias;\nif (t < %s) {\n scale = %s;\n bias = " + "%s;\n} else {\n scale = %s;\n bias = %s;\n}\n%s = half4(float(t) * scale + " + "bias);\n", + args.fInputColor, args.fUniformHandler->getUniformCStr(thresholdVar), + args.fUniformHandler->getUniformCStr(scale01Var), + args.fUniformHandler->getUniformCStr(bias01Var), + args.fUniformHandler->getUniformCStr(scale23Var), + args.fUniformHandler->getUniformCStr(bias23Var), args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrDualIntervalGradientColorizer& _outer = + _proc.cast(); + { + const SkPMColor4f& scale01Value = _outer.scale01; + if (scale01Prev != scale01Value) { + scale01Prev = scale01Value; + pdman.set4fv(scale01Var, 1, scale01Value.vec()); + } + const SkPMColor4f& bias01Value = _outer.bias01; + if (bias01Prev != bias01Value) { + bias01Prev = bias01Value; + pdman.set4fv(bias01Var, 1, bias01Value.vec()); + } + const SkPMColor4f& scale23Value = _outer.scale23; + if (scale23Prev != scale23Value) { + scale23Prev = scale23Value; + pdman.set4fv(scale23Var, 1, scale23Value.vec()); + } + const SkPMColor4f& bias23Value = _outer.bias23; + if (bias23Prev != bias23Value) { + bias23Prev = bias23Value; + pdman.set4fv(bias23Var, 1, bias23Value.vec()); + } + float thresholdValue = _outer.threshold; + if (thresholdPrev != thresholdValue) { + thresholdPrev = thresholdValue; + pdman.set1f(thresholdVar, thresholdValue); + } + } + } + SkPMColor4f scale01Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + SkPMColor4f bias01Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + SkPMColor4f scale23Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + SkPMColor4f bias23Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + float thresholdPrev = SK_FloatNaN; + UniformHandle scale01Var; + UniformHandle bias01Var; + UniformHandle scale23Var; + UniformHandle bias23Var; + UniformHandle thresholdVar; +}; +GrGLSLFragmentProcessor* GrDualIntervalGradientColorizer::onCreateGLSLInstance() const { + return new GrGLSLDualIntervalGradientColorizer(); +} +void GrDualIntervalGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrDualIntervalGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const { + const GrDualIntervalGradientColorizer& that = other.cast(); + (void)that; + if (scale01 != that.scale01) return false; + if (bias01 != that.bias01) return false; + if (scale23 != that.scale23) return false; + if (bias23 != that.bias23) return false; + if (threshold != that.threshold) return false; + return true; +} +GrDualIntervalGradientColorizer::GrDualIntervalGradientColorizer( + const GrDualIntervalGradientColorizer& src) + : INHERITED(kGrDualIntervalGradientColorizer_ClassID, src.optimizationFlags()) + , scale01(src.scale01) + , bias01(src.bias01) + , scale23(src.scale23) + , bias23(src.bias23) + , threshold(src.threshold) {} +std::unique_ptr GrDualIntervalGradientColorizer::clone() const { + return std::unique_ptr(new GrDualIntervalGradientColorizer(*this)); +} + +std::unique_ptr GrDualIntervalGradientColorizer::Make(const SkPMColor4f& c0, + const SkPMColor4f& c1, + const SkPMColor4f& c2, + const SkPMColor4f& c3, + float threshold) { + // Derive scale and biases from the 4 colors and threshold + auto vc0 = Sk4f::Load(c0.vec()); + auto vc1 = Sk4f::Load(c1.vec()); + auto scale01 = (vc1 - vc0) / threshold; + // bias01 = c0 + + auto vc2 = Sk4f::Load(c2.vec()); + auto vc3 = Sk4f::Load(c3.vec()); + auto scale23 = (vc3 - vc2) / (1 - threshold); + auto bias23 = vc2 - threshold * scale23; + + return std::unique_ptr(new GrDualIntervalGradientColorizer( + {scale01[0], scale01[1], scale01[2], scale01[3]}, c0, + {scale23[0], scale23[1], scale23[2], scale23[3]}, + {bias23[0], bias23[1], bias23[2], bias23[3]}, threshold)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h b/gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h new file mode 100644 index 0000000000..7c3e3b8b7b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrDualIntervalGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#ifndef GrDualIntervalGradientColorizer_DEFINED +#define GrDualIntervalGradientColorizer_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrDualIntervalGradientColorizer : public GrFragmentProcessor { +public: + static std::unique_ptr Make(const SkPMColor4f& c0, const SkPMColor4f& c1, + const SkPMColor4f& c2, const SkPMColor4f& c3, + float threshold); + GrDualIntervalGradientColorizer(const GrDualIntervalGradientColorizer& src); + std::unique_ptr clone() const override; + const char* name() const override { return "DualIntervalGradientColorizer"; } + SkPMColor4f scale01; + SkPMColor4f bias01; + SkPMColor4f scale23; + SkPMColor4f bias23; + float threshold; + +private: + GrDualIntervalGradientColorizer(SkPMColor4f scale01, SkPMColor4f bias01, SkPMColor4f scale23, + SkPMColor4f bias23, float threshold) + : INHERITED(kGrDualIntervalGradientColorizer_ClassID, kNone_OptimizationFlags) + , scale01(scale01) + , bias01(bias01) + , scale23(scale23) + , bias23(bias23) + , threshold(threshold) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.cpp new file mode 100644 index 0000000000..a20b0e5676 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrLinearGradientLayout.fp; do not modify. + **************************************************************************************************/ +#include "GrLinearGradientLayout.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLLinearGradientLayout : public GrGLSLFragmentProcessor { +public: + GrGLSLLinearGradientLayout() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrLinearGradientLayout& _outer = args.fFp.cast(); + (void)_outer; + auto gradientMatrix = _outer.gradientMatrix; + (void)gradientMatrix; + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf( + "half t = half(%s.x) + 9.9999997473787516e-06;\n%s = half4(t, 1.0, 0.0, 0.0);\n", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrLinearGradientLayout::onCreateGLSLInstance() const { + return new GrGLSLLinearGradientLayout(); +} +void GrLinearGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrLinearGradientLayout::onIsEqual(const GrFragmentProcessor& other) const { + const GrLinearGradientLayout& that = other.cast(); + (void)that; + if (gradientMatrix != that.gradientMatrix) return false; + return true; +} +GrLinearGradientLayout::GrLinearGradientLayout(const GrLinearGradientLayout& src) + : INHERITED(kGrLinearGradientLayout_ClassID, src.optimizationFlags()) + , fCoordTransform0(src.fCoordTransform0) + , gradientMatrix(src.gradientMatrix) { + this->addCoordTransform(&fCoordTransform0); +} +std::unique_ptr GrLinearGradientLayout::clone() const { + return std::unique_ptr(new GrLinearGradientLayout(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradientLayout); +#if GR_TEST_UTILS +std::unique_ptr GrLinearGradientLayout::TestCreate(GrProcessorTestData* d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + SkPoint points[] = { + {d->fRandom->nextRangeScalar(0.0f, scale), d->fRandom->nextRangeScalar(0.0f, scale)}, + {d->fRandom->nextRangeScalar(0.0f, scale), d->fRandom->nextRangeScalar(0.0f, scale)}}; + + GrGradientShader::RandomParams params(d->fRandom); + auto shader = params.fUseColors4f + ? SkGradientShader::MakeLinear(points, params.fColors4f, + params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) + : SkGradientShader::MakeLinear(points, params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + std::unique_ptr fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +std::unique_ptr GrLinearGradientLayout::Make(const SkLinearGradient& grad, + const GrFPArgs& args) { + SkMatrix matrix; + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + matrix.postConcat(grad.getGradientMatrix()); + return std::unique_ptr(new GrLinearGradientLayout(matrix)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.h b/gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.h new file mode 100644 index 0000000000..1f711d977b --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrLinearGradientLayout.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrLinearGradientLayout.fp; do not modify. + **************************************************************************************************/ +#ifndef GrLinearGradientLayout_DEFINED +#define GrLinearGradientLayout_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/gradients/GrGradientShader.h" +#include "src/shaders/gradients/SkLinearGradient.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrLinearGradientLayout : public GrFragmentProcessor { +public: + static std::unique_ptr Make(const SkLinearGradient& gradient, + const GrFPArgs& args); + GrLinearGradientLayout(const GrLinearGradientLayout& src); + std::unique_ptr clone() const override; + const char* name() const override { return "LinearGradientLayout"; } + GrCoordTransform fCoordTransform0; + SkMatrix44 gradientMatrix; + +private: + GrLinearGradientLayout(SkMatrix44 gradientMatrix) + : INHERITED(kGrLinearGradientLayout_ClassID, + (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag) + , fCoordTransform0(gradientMatrix) + , gradientMatrix(gradientMatrix) { + this->addCoordTransform(&fCoordTransform0); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.cpp new file mode 100644 index 0000000000..f1639438ac --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrRadialGradientLayout.fp; do not modify. + **************************************************************************************************/ +#include "GrRadialGradientLayout.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLRadialGradientLayout : public GrGLSLFragmentProcessor { +public: + GrGLSLRadialGradientLayout() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrRadialGradientLayout& _outer = args.fFp.cast(); + (void)_outer; + auto gradientMatrix = _outer.gradientMatrix; + (void)gradientMatrix; + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf("half t = half(length(%s));\n%s = half4(t, 1.0, 0.0, 0.0);\n", + _outer.computeLocalCoordsInVertexShader() + ? sk_TransformedCoords2D_0.c_str() + : "_coords", + args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrRadialGradientLayout::onCreateGLSLInstance() const { + return new GrGLSLRadialGradientLayout(); +} +void GrRadialGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrRadialGradientLayout::onIsEqual(const GrFragmentProcessor& other) const { + const GrRadialGradientLayout& that = other.cast(); + (void)that; + if (gradientMatrix != that.gradientMatrix) return false; + return true; +} +GrRadialGradientLayout::GrRadialGradientLayout(const GrRadialGradientLayout& src) + : INHERITED(kGrRadialGradientLayout_ClassID, src.optimizationFlags()) + , fCoordTransform0(src.fCoordTransform0) + , gradientMatrix(src.gradientMatrix) { + this->addCoordTransform(&fCoordTransform0); +} +std::unique_ptr GrRadialGradientLayout::clone() const { + return std::unique_ptr(new GrRadialGradientLayout(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradientLayout); +#if GR_TEST_UTILS +std::unique_ptr GrRadialGradientLayout::TestCreate(GrProcessorTestData* d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + std::unique_ptr fp; + GrTest::TestAsFPArgs asFPArgs(d); + do { + GrGradientShader::RandomParams params(d->fRandom); + SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + SkScalar radius = d->fRandom->nextRangeScalar(0.0f, scale); + sk_sp shader = + params.fUseColors4f + ? SkGradientShader::MakeRadial(center, radius, params.fColors4f, + params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) + : SkGradientShader::MakeRadial(center, radius, params.fColors, + params.fStops, params.fColorCount, + params.fTileMode); + // Degenerate params can create an Empty (non-null) shader, where fp will be nullptr + fp = shader ? as_SB(shader)->asFragmentProcessor(asFPArgs.args()) : nullptr; + } while (!fp); + return fp; +} +#endif + +std::unique_ptr GrRadialGradientLayout::Make(const SkRadialGradient& grad, + const GrFPArgs& args) { + SkMatrix matrix; + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + matrix.postConcat(grad.getGradientMatrix()); + return std::unique_ptr(new GrRadialGradientLayout(matrix)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.h b/gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.h new file mode 100644 index 0000000000..17a1b49ea9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrRadialGradientLayout.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrRadialGradientLayout.fp; do not modify. + **************************************************************************************************/ +#ifndef GrRadialGradientLayout_DEFINED +#define GrRadialGradientLayout_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/gradients/GrGradientShader.h" +#include "src/shaders/gradients/SkRadialGradient.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrRadialGradientLayout : public GrFragmentProcessor { +public: + static std::unique_ptr Make(const SkRadialGradient& gradient, + const GrFPArgs& args); + GrRadialGradientLayout(const GrRadialGradientLayout& src); + std::unique_ptr clone() const override; + const char* name() const override { return "RadialGradientLayout"; } + GrCoordTransform fCoordTransform0; + SkMatrix44 gradientMatrix; + +private: + GrRadialGradientLayout(SkMatrix44 gradientMatrix) + : INHERITED(kGrRadialGradientLayout_ClassID, + (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag) + , fCoordTransform0(gradientMatrix) + , gradientMatrix(gradientMatrix) { + this->addCoordTransform(&fCoordTransform0); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp new file mode 100644 index 0000000000..f4a3397c19 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSingleIntervalGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#include "GrSingleIntervalGradientColorizer.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLSingleIntervalGradientColorizer : public GrGLSLFragmentProcessor { +public: + GrGLSLSingleIntervalGradientColorizer() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrSingleIntervalGradientColorizer& _outer = + args.fFp.cast(); + (void)_outer; + auto start = _outer.start; + (void)start; + auto end = _outer.end; + (void)end; + startVar = + args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "start"); + endVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "end"); + fragBuilder->codeAppendf("half t = %s.x;\n%s = (1.0 - t) * %s + t * %s;\n", + args.fInputColor, args.fOutputColor, + args.fUniformHandler->getUniformCStr(startVar), + args.fUniformHandler->getUniformCStr(endVar)); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrSingleIntervalGradientColorizer& _outer = + _proc.cast(); + { + const SkPMColor4f& startValue = _outer.start; + if (startPrev != startValue) { + startPrev = startValue; + pdman.set4fv(startVar, 1, startValue.vec()); + } + const SkPMColor4f& endValue = _outer.end; + if (endPrev != endValue) { + endPrev = endValue; + pdman.set4fv(endVar, 1, endValue.vec()); + } + } + } + SkPMColor4f startPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + SkPMColor4f endPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}; + UniformHandle startVar; + UniformHandle endVar; +}; +GrGLSLFragmentProcessor* GrSingleIntervalGradientColorizer::onCreateGLSLInstance() const { + return new GrGLSLSingleIntervalGradientColorizer(); +} +void GrSingleIntervalGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrSingleIntervalGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const { + const GrSingleIntervalGradientColorizer& that = other.cast(); + (void)that; + if (start != that.start) return false; + if (end != that.end) return false; + return true; +} +GrSingleIntervalGradientColorizer::GrSingleIntervalGradientColorizer( + const GrSingleIntervalGradientColorizer& src) + : INHERITED(kGrSingleIntervalGradientColorizer_ClassID, src.optimizationFlags()) + , start(src.start) + , end(src.end) {} +std::unique_ptr GrSingleIntervalGradientColorizer::clone() const { + return std::unique_ptr(new GrSingleIntervalGradientColorizer(*this)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h b/gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h new file mode 100644 index 0000000000..f7b49dbd44 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSingleIntervalGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#ifndef GrSingleIntervalGradientColorizer_DEFINED +#define GrSingleIntervalGradientColorizer_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrSingleIntervalGradientColorizer : public GrFragmentProcessor { +public: + static std::unique_ptr Make(SkPMColor4f start, SkPMColor4f end) { + return std::unique_ptr( + new GrSingleIntervalGradientColorizer(start, end)); + } + GrSingleIntervalGradientColorizer(const GrSingleIntervalGradientColorizer& src); + std::unique_ptr clone() const override; + const char* name() const override { return "SingleIntervalGradientColorizer"; } + SkPMColor4f start; + SkPMColor4f end; + +private: + GrSingleIntervalGradientColorizer(SkPMColor4f start, SkPMColor4f end) + : INHERITED(kGrSingleIntervalGradientColorizer_ClassID, kNone_OptimizationFlags) + , start(start) + , end(end) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.cpp new file mode 100644 index 0000000000..6abcbb1780 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSweepGradientLayout.fp; do not modify. + **************************************************************************************************/ +#include "GrSweepGradientLayout.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLSweepGradientLayout : public GrGLSLFragmentProcessor { +public: + GrGLSLSweepGradientLayout() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrSweepGradientLayout& _outer = args.fFp.cast(); + (void)_outer; + auto gradientMatrix = _outer.gradientMatrix; + (void)gradientMatrix; + auto bias = _outer.bias; + (void)bias; + auto scale = _outer.scale; + (void)scale; + biasVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "bias"); + scaleVar = + args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "scale"); + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf( + "half angle;\nif (sk_Caps.atan2ImplementedAsAtanYOverX) {\n angle = half(2.0 * " + "atan(-%s.y, length(%s) - %s.x));\n} else {\n angle = half(atan(-%s.y, " + "-%s.x));\n}\nhalf t = ((angle * 0.15915493667125702 + 0.5) + %s) * %s;\n%s = " + "half4(t, 1.0, 0.0, 0.0);\n", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + args.fUniformHandler->getUniformCStr(biasVar), + args.fUniformHandler->getUniformCStr(scaleVar), args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrSweepGradientLayout& _outer = _proc.cast(); + { + float biasValue = _outer.bias; + if (biasPrev != biasValue) { + biasPrev = biasValue; + pdman.set1f(biasVar, biasValue); + } + float scaleValue = _outer.scale; + if (scalePrev != scaleValue) { + scalePrev = scaleValue; + pdman.set1f(scaleVar, scaleValue); + } + } + } + float biasPrev = SK_FloatNaN; + float scalePrev = SK_FloatNaN; + UniformHandle biasVar; + UniformHandle scaleVar; +}; +GrGLSLFragmentProcessor* GrSweepGradientLayout::onCreateGLSLInstance() const { + return new GrGLSLSweepGradientLayout(); +} +void GrSweepGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrSweepGradientLayout::onIsEqual(const GrFragmentProcessor& other) const { + const GrSweepGradientLayout& that = other.cast(); + (void)that; + if (gradientMatrix != that.gradientMatrix) return false; + if (bias != that.bias) return false; + if (scale != that.scale) return false; + return true; +} +GrSweepGradientLayout::GrSweepGradientLayout(const GrSweepGradientLayout& src) + : INHERITED(kGrSweepGradientLayout_ClassID, src.optimizationFlags()) + , fCoordTransform0(src.fCoordTransform0) + , gradientMatrix(src.gradientMatrix) + , bias(src.bias) + , scale(src.scale) { + this->addCoordTransform(&fCoordTransform0); +} +std::unique_ptr GrSweepGradientLayout::clone() const { + return std::unique_ptr(new GrSweepGradientLayout(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSweepGradientLayout); +#if GR_TEST_UTILS +std::unique_ptr GrSweepGradientLayout::TestCreate(GrProcessorTestData* d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + + GrGradientShader::RandomParams params(d->fRandom); + auto shader = params.fUseColors4f + ? SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors4f, + params.fColorSpace, params.fStops, + params.fColorCount) + : SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors, + params.fStops, params.fColorCount); + GrTest::TestAsFPArgs asFPArgs(d); + std::unique_ptr fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +std::unique_ptr GrSweepGradientLayout::Make(const SkSweepGradient& grad, + const GrFPArgs& args) { + SkMatrix matrix; + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + matrix.postConcat(grad.getGradientMatrix()); + return std::unique_ptr( + new GrSweepGradientLayout(matrix, grad.getTBias(), grad.getTScale())); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.h b/gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.h new file mode 100644 index 0000000000..77036635c4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrSweepGradientLayout.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrSweepGradientLayout.fp; do not modify. + **************************************************************************************************/ +#ifndef GrSweepGradientLayout_DEFINED +#define GrSweepGradientLayout_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/gradients/GrGradientShader.h" +#include "src/shaders/gradients/SkSweepGradient.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrSweepGradientLayout : public GrFragmentProcessor { +public: + static std::unique_ptr Make(const SkSweepGradient& gradient, + const GrFPArgs& args); + GrSweepGradientLayout(const GrSweepGradientLayout& src); + std::unique_ptr clone() const override; + const char* name() const override { return "SweepGradientLayout"; } + GrCoordTransform fCoordTransform0; + SkMatrix44 gradientMatrix; + float bias; + float scale; + +private: + GrSweepGradientLayout(SkMatrix44 gradientMatrix, float bias, float scale) + : INHERITED(kGrSweepGradientLayout_ClassID, + (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag) + , fCoordTransform0(gradientMatrix) + , gradientMatrix(gradientMatrix) + , bias(bias) + , scale(scale) { + this->addCoordTransform(&fCoordTransform0); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp new file mode 100644 index 0000000000..466b407cf7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#include "GrTextureGradientColorizer.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLTextureGradientColorizer : public GrGLSLFragmentProcessor { +public: + GrGLSLTextureGradientColorizer() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrTextureGradientColorizer& _outer = args.fFp.cast(); + (void)_outer; + fragBuilder->codeAppendf( + "half2 coord = half2(%s.x, 0.5);\n%s = sample(%s, float2(coord)).%s;\n", + args.fInputColor, args.fOutputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrTextureGradientColorizer::onCreateGLSLInstance() const { + return new GrGLSLTextureGradientColorizer(); +} +void GrTextureGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrTextureGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const { + const GrTextureGradientColorizer& that = other.cast(); + (void)that; + if (gradient != that.gradient) return false; + return true; +} +GrTextureGradientColorizer::GrTextureGradientColorizer(const GrTextureGradientColorizer& src) + : INHERITED(kGrTextureGradientColorizer_ClassID, src.optimizationFlags()) + , gradient(src.gradient) { + this->setTextureSamplerCnt(1); +} +std::unique_ptr GrTextureGradientColorizer::clone() const { + return std::unique_ptr(new GrTextureGradientColorizer(*this)); +} +const GrFragmentProcessor::TextureSampler& GrTextureGradientColorizer::onTextureSampler( + int index) const { + return IthTextureSampler(index, gradient); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.h b/gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.h new file mode 100644 index 0000000000..e15558edde --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#ifndef GrTextureGradientColorizer_DEFINED +#define GrTextureGradientColorizer_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrTextureGradientColorizer : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp gradient) { + return std::unique_ptr(new GrTextureGradientColorizer(gradient)); + } + GrTextureGradientColorizer(const GrTextureGradientColorizer& src); + std::unique_ptr clone() const override; + const char* name() const override { return "TextureGradientColorizer"; } + TextureSampler gradient; + +private: + GrTextureGradientColorizer(sk_sp gradient) + : INHERITED(kGrTextureGradientColorizer_ClassID, kNone_OptimizationFlags) + , gradient(std::move(gradient), GrSamplerState::ClampBilerp()) { + this->setTextureSamplerCnt(1); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.cpp new file mode 100644 index 0000000000..9d0637feb6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTiledGradientEffect.fp; do not modify. + **************************************************************************************************/ +#include "GrTiledGradientEffect.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLTiledGradientEffect : public GrGLSLFragmentProcessor { +public: + GrGLSLTiledGradientEffect() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrTiledGradientEffect& _outer = args.fFp.cast(); + (void)_outer; + auto mirror = _outer.mirror; + (void)mirror; + auto makePremul = _outer.makePremul; + (void)makePremul; + auto colorsAreOpaque = _outer.colorsAreOpaque; + (void)colorsAreOpaque; + SkString _sample453("_sample453"); + this->invokeChild(_outer.gradLayout_index, &_sample453, args); + fragBuilder->codeAppendf( + "half4 t = %s;\nif (!%s && t.y < 0.0) {\n %s = half4(0.0);\n} else {\n @if " + "(%s) {\n half t_1 = t.x - 1.0;\n half tiled_t = (t_1 - 2.0 * " + "floor(t_1 * 0.5)) - 1.0;\n if (sk_Caps.mustDoOpBetweenFloorAndAbs) {\n " + " tiled_t = clamp(tiled_t, -1.0, 1.0);\n }\n t.x = " + "abs(tiled_t);\n } else {\n t.x = fract(t.x);\n }", + _sample453.c_str(), + (_outer.childProcessor(_outer.gradLayout_index).preservesOpaqueInput() ? "true" + : "false"), + args.fOutputColor, (_outer.mirror ? "true" : "false")); + SkString _input1464("t"); + SkString _sample1464("_sample1464"); + this->invokeChild(_outer.colorizer_index, _input1464.c_str(), &_sample1464, args); + fragBuilder->codeAppendf("\n %s = %s;\n}\n@if (%s) {\n %s.xyz *= %s.w;\n}\n", + args.fOutputColor, _sample1464.c_str(), + (_outer.makePremul ? "true" : "false"), args.fOutputColor, + args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrTiledGradientEffect::onCreateGLSLInstance() const { + return new GrGLSLTiledGradientEffect(); +} +void GrTiledGradientEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)mirror); + b->add32((int32_t)makePremul); +} +bool GrTiledGradientEffect::onIsEqual(const GrFragmentProcessor& other) const { + const GrTiledGradientEffect& that = other.cast(); + (void)that; + if (mirror != that.mirror) return false; + if (makePremul != that.makePremul) return false; + if (colorsAreOpaque != that.colorsAreOpaque) return false; + return true; +} +GrTiledGradientEffect::GrTiledGradientEffect(const GrTiledGradientEffect& src) + : INHERITED(kGrTiledGradientEffect_ClassID, src.optimizationFlags()) + , colorizer_index(src.colorizer_index) + , gradLayout_index(src.gradLayout_index) + , mirror(src.mirror) + , makePremul(src.makePremul) + , colorsAreOpaque(src.colorsAreOpaque) { + this->registerChildProcessor(src.childProcessor(colorizer_index).clone()); + this->registerChildProcessor(src.childProcessor(gradLayout_index).clone()); +} +std::unique_ptr GrTiledGradientEffect::clone() const { + return std::unique_ptr(new GrTiledGradientEffect(*this)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.h b/gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.h new file mode 100644 index 0000000000..8942804513 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrTiledGradientEffect.h @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTiledGradientEffect.fp; do not modify. + **************************************************************************************************/ +#ifndef GrTiledGradientEffect_DEFINED +#define GrTiledGradientEffect_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrTiledGradientEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make( + std::unique_ptr colorizer, + std::unique_ptr gradLayout, bool mirror, bool makePremul, + bool colorsAreOpaque) { + return std::unique_ptr(new GrTiledGradientEffect( + std::move(colorizer), std::move(gradLayout), mirror, makePremul, colorsAreOpaque)); + } + GrTiledGradientEffect(const GrTiledGradientEffect& src); + std::unique_ptr clone() const override; + const char* name() const override { return "TiledGradientEffect"; } + int colorizer_index = -1; + int gradLayout_index = -1; + bool mirror; + bool makePremul; + bool colorsAreOpaque; + +private: + GrTiledGradientEffect(std::unique_ptr colorizer, + std::unique_ptr gradLayout, bool mirror, + bool makePremul, bool colorsAreOpaque) + : INHERITED(kGrTiledGradientEffect_ClassID, + (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag | + (colorsAreOpaque && gradLayout->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags)) + , mirror(mirror) + , makePremul(makePremul) + , colorsAreOpaque(colorsAreOpaque) { + SkASSERT(colorizer); + colorizer_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(colorizer)); + SkASSERT(gradLayout); + gradLayout_index = this->numChildProcessors(); + this->registerChildProcessor(std::move(gradLayout)); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp new file mode 100644 index 0000000000..804a42c6d8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp @@ -0,0 +1,305 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify. + **************************************************************************************************/ +#include "GrTwoPointConicalGradientLayout.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLTwoPointConicalGradientLayout : public GrGLSLFragmentProcessor { +public: + GrGLSLTwoPointConicalGradientLayout() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrTwoPointConicalGradientLayout& _outer = + args.fFp.cast(); + (void)_outer; + auto gradientMatrix = _outer.gradientMatrix; + (void)gradientMatrix; + auto type = _outer.type; + (void)type; + auto isRadiusIncreasing = _outer.isRadiusIncreasing; + (void)isRadiusIncreasing; + auto isFocalOnCircle = _outer.isFocalOnCircle; + (void)isFocalOnCircle; + auto isWellBehaved = _outer.isWellBehaved; + (void)isWellBehaved; + auto isSwapped = _outer.isSwapped; + (void)isSwapped; + auto isNativelyFocal = _outer.isNativelyFocal; + (void)isNativelyFocal; + auto focalParams = _outer.focalParams; + (void)focalParams; + focalParamsVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "focalParams"); + SkString sk_TransformedCoords2D_0 = + fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + fragBuilder->codeAppendf( + "float2 p = %s;\nfloat t = -1.0;\nhalf v = 1.0;\n@switch (%d) {\n case 1:\n " + " {\n half r0_2 = %s.y;\n t = float(r0_2) - p.y * p.y;\n " + " if (t >= 0.0) {\n t = p.x + sqrt(t);\n } else " + "{\n v = -1.0;\n }\n }\n break;\n case " + "0:\n {\n half r0 = %s.x;\n @if (%s) {\n " + " t = length(p) - float(r0);\n } else {\n t = " + "-length(p) - float(r0);\n ", + _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() + : "_coords", + (int)_outer.type, args.fUniformHandler->getUniformCStr(focalParamsVar), + args.fUniformHandler->getUniformCStr(focalParamsVar), + (_outer.isRadiusIncreasing ? "true" : "false")); + fragBuilder->codeAppendf( + " }\n }\n break;\n case 2:\n {\n half invR1 " + "= %s.x;\n half fx = %s.y;\n float x_t = -1.0;\n " + "@if (%s) {\n x_t = dot(p, p) / p.x;\n } else if (%s) " + "{\n x_t = length(p) - p.x * float(invR1);\n } else {\n " + " float temp = p.x * p.x - p.y * p.y;\n if (temp >= " + "0.0) {\n @if (%s || !%s) {\n x_t = " + "-sqrt(temp) - p.x * float(invR1)", + args.fUniformHandler->getUniformCStr(focalParamsVar), + args.fUniformHandler->getUniformCStr(focalParamsVar), + (_outer.isFocalOnCircle ? "true" : "false"), + (_outer.isWellBehaved ? "true" : "false"), (_outer.isSwapped ? "true" : "false"), + (_outer.isRadiusIncreasing ? "true" : "false")); + fragBuilder->codeAppendf( + ";\n } else {\n x_t = sqrt(temp) - p.x * " + "float(invR1);\n }\n }\n }\n " + " @if (!%s) {\n if (x_t <= 0.0) {\n v = -1.0;\n " + " }\n }\n @if (%s) {\n @if (%s) " + "{\n t = x_t;\n } else {\n t " + "= x_t + float(fx);\n }\n } else {\n @if " + "(%s) {\n ", + (_outer.isWellBehaved ? "true" : "false"), + (_outer.isRadiusIncreasing ? "true" : "false"), + (_outer.isNativelyFocal ? "true" : "false"), + (_outer.isNativelyFocal ? "true" : "false")); + fragBuilder->codeAppendf( + " t = -x_t;\n } else {\n t = -x_t + " + "float(fx);\n }\n }\n @if (%s) {\n " + " t = 1.0 - t;\n }\n }\n break;\n}\n%s = " + "half4(half(t), v, 0.0, 0.0);\n", + (_outer.isSwapped ? "true" : "false"), args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrTwoPointConicalGradientLayout& _outer = + _proc.cast(); + { + const SkPoint& focalParamsValue = _outer.focalParams; + if (focalParamsPrev != focalParamsValue) { + focalParamsPrev = focalParamsValue; + pdman.set2f(focalParamsVar, focalParamsValue.fX, focalParamsValue.fY); + } + } + } + SkPoint focalParamsPrev = SkPoint::Make(SK_FloatNaN, SK_FloatNaN); + UniformHandle focalParamsVar; +}; +GrGLSLFragmentProcessor* GrTwoPointConicalGradientLayout::onCreateGLSLInstance() const { + return new GrGLSLTwoPointConicalGradientLayout(); +} +void GrTwoPointConicalGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)type); + b->add32((int32_t)isRadiusIncreasing); + b->add32((int32_t)isFocalOnCircle); + b->add32((int32_t)isWellBehaved); + b->add32((int32_t)isSwapped); + b->add32((int32_t)isNativelyFocal); +} +bool GrTwoPointConicalGradientLayout::onIsEqual(const GrFragmentProcessor& other) const { + const GrTwoPointConicalGradientLayout& that = other.cast(); + (void)that; + if (gradientMatrix != that.gradientMatrix) return false; + if (type != that.type) return false; + if (isRadiusIncreasing != that.isRadiusIncreasing) return false; + if (isFocalOnCircle != that.isFocalOnCircle) return false; + if (isWellBehaved != that.isWellBehaved) return false; + if (isSwapped != that.isSwapped) return false; + if (isNativelyFocal != that.isNativelyFocal) return false; + if (focalParams != that.focalParams) return false; + return true; +} +GrTwoPointConicalGradientLayout::GrTwoPointConicalGradientLayout( + const GrTwoPointConicalGradientLayout& src) + : INHERITED(kGrTwoPointConicalGradientLayout_ClassID, src.optimizationFlags()) + , fCoordTransform0(src.fCoordTransform0) + , gradientMatrix(src.gradientMatrix) + , type(src.type) + , isRadiusIncreasing(src.isRadiusIncreasing) + , isFocalOnCircle(src.isFocalOnCircle) + , isWellBehaved(src.isWellBehaved) + , isSwapped(src.isSwapped) + , isNativelyFocal(src.isNativelyFocal) + , focalParams(src.focalParams) { + this->addCoordTransform(&fCoordTransform0); +} +std::unique_ptr GrTwoPointConicalGradientLayout::clone() const { + return std::unique_ptr(new GrTwoPointConicalGradientLayout(*this)); +} +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTwoPointConicalGradientLayout); +#if GR_TEST_UTILS +std::unique_ptr GrTwoPointConicalGradientLayout::TestCreate( + GrProcessorTestData* d) { + SkScalar scale = GrGradientShader::RandomParams::kGradientScale; + SkScalar offset = scale / 32.0f; + + SkPoint center1 = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + SkPoint center2 = {d->fRandom->nextRangeScalar(0.0f, scale), + d->fRandom->nextRangeScalar(0.0f, scale)}; + SkScalar radius1 = d->fRandom->nextRangeScalar(0.0f, scale); + SkScalar radius2 = d->fRandom->nextRangeScalar(0.0f, scale); + + constexpr int kTestTypeMask = (1 << 2) - 1, kTestNativelyFocalBit = (1 << 2), + kTestFocalOnCircleBit = (1 << 3), kTestSwappedBit = (1 << 4); + // We won't treat isWellDefined and isRadiusIncreasing specially because they + // should have high probability to be turned on and off as we're getting random + // radii and centers. + + int mask = d->fRandom->nextU(); + int type = mask & kTestTypeMask; + if (type == static_cast(Type::kRadial)) { + center2 = center1; + // Make sure that the radii are different + if (SkScalarNearlyZero(radius1 - radius2)) { + radius2 += offset; + } + } else if (type == static_cast(Type::kStrip)) { + radius1 = SkTMax(radius1, .1f); // Make sure that the radius is non-zero + radius2 = radius1; + // Make sure that the centers are different + if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { + center2.fX += offset; + } + } else { // kFocal_Type + // Make sure that the centers are different + if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { + center2.fX += offset; + } + + if (kTestNativelyFocalBit & mask) { + radius1 = 0; + } + if (kTestFocalOnCircleBit & mask) { + radius2 = radius1 + SkPoint::Distance(center1, center2); + } + if (kTestSwappedBit & mask) { + std::swap(radius1, radius2); + radius2 = 0; + } + + // Make sure that the radii are different + if (SkScalarNearlyZero(radius1 - radius2)) { + radius2 += offset; + } + } + + if (SkScalarNearlyZero(radius1 - radius2) && + SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { + radius2 += offset; // make sure that we're not degenerated + } + + GrGradientShader::RandomParams params(d->fRandom); + auto shader = params.fUseColors4f + ? SkGradientShader::MakeTwoPointConical( + center1, radius1, center2, radius2, params.fColors4f, + params.fColorSpace, params.fStops, params.fColorCount, + params.fTileMode) + : SkGradientShader::MakeTwoPointConical( + center1, radius1, center2, radius2, params.fColors, + params.fStops, params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + std::unique_ptr fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + + GrAlwaysAssert(fp); + return fp; +} +#endif + +// .fp files do not let you reference outside enum definitions, so we have to explicitly map +// between the two compatible enum defs +GrTwoPointConicalGradientLayout::Type convert_type(SkTwoPointConicalGradient::Type type) { + switch (type) { + case SkTwoPointConicalGradient::Type::kRadial: + return GrTwoPointConicalGradientLayout::Type::kRadial; + case SkTwoPointConicalGradient::Type::kStrip: + return GrTwoPointConicalGradientLayout::Type::kStrip; + case SkTwoPointConicalGradient::Type::kFocal: + return GrTwoPointConicalGradientLayout::Type::kFocal; + } + SkDEBUGFAIL("Should not be reachable"); + return GrTwoPointConicalGradientLayout::Type::kRadial; +} + +std::unique_ptr GrTwoPointConicalGradientLayout::Make( + const SkTwoPointConicalGradient& grad, const GrFPArgs& args) { + GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType()); + + // The focalData struct is only valid if isFocal is true + const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData(); + bool isFocal = grType == Type::kFocal; + + // Calculate optimization switches from gradient specification + bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle(); + bool isWellBehaved = isFocal && focalData.isWellBehaved(); + bool isSwapped = isFocal && focalData.isSwapped(); + bool isNativelyFocal = isFocal && focalData.isNativelyFocal(); + + // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix. + // However, all types start with the total inverse local matrix calculated from the shader + // and args + bool isRadiusIncreasing; + SkPoint focalParams; // really just a 2D tuple + SkMatrix matrix; + + // Initialize the base matrix + if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + + if (isFocal) { + isRadiusIncreasing = (1 - focalData.fFocalX) > 0; + + focalParams.set(1.0 / focalData.fR1, focalData.fFocalX); + + matrix.postConcat(grad.getGradientMatrix()); + } else if (grType == Type::kRadial) { + SkScalar dr = grad.getDiffRadius(); + isRadiusIncreasing = dr >= 0; + + SkScalar r0 = grad.getStartRadius() / dr; + focalParams.set(r0, r0 * r0); + + // GPU radial matrix is different from the original matrix, since we map the diff radius + // to have |dr| = 1, so manually compute the final gradient matrix here. + + // Map center to (0, 0) + matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY); + + // scale |diffRadius| to 1 + matrix.postScale(1 / dr, 1 / dr); + } else { // kStrip + isRadiusIncreasing = false; // kStrip doesn't use this flag + + SkScalar r0 = grad.getStartRadius() / grad.getCenterX1(); + focalParams.set(r0, r0 * r0); + + matrix.postConcat(grad.getGradientMatrix()); + } + + return std::unique_ptr(new GrTwoPointConicalGradientLayout( + matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, isSwapped, + isNativelyFocal, focalParams)); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h b/gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h new file mode 100644 index 0000000000..878b8d2c54 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify. + **************************************************************************************************/ +#ifndef GrTwoPointConicalGradientLayout_DEFINED +#define GrTwoPointConicalGradientLayout_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/gradients/GrGradientShader.h" +#include "src/shaders/gradients/SkTwoPointConicalGradient.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrTwoPointConicalGradientLayout : public GrFragmentProcessor { +public: + enum class Type { kFocal = 2, kRadial = 0, kStrip = 1 }; + + static std::unique_ptr Make(const SkTwoPointConicalGradient& gradient, + const GrFPArgs& args); + GrTwoPointConicalGradientLayout(const GrTwoPointConicalGradientLayout& src); + std::unique_ptr clone() const override; + const char* name() const override { return "TwoPointConicalGradientLayout"; } + GrCoordTransform fCoordTransform0; + SkMatrix44 gradientMatrix; + Type type; + bool isRadiusIncreasing; + bool isFocalOnCircle; + bool isWellBehaved; + bool isSwapped; + bool isNativelyFocal; + SkPoint focalParams; + +private: + GrTwoPointConicalGradientLayout(SkMatrix44 gradientMatrix, Type type, bool isRadiusIncreasing, + bool isFocalOnCircle, bool isWellBehaved, bool isSwapped, + bool isNativelyFocal, SkPoint focalParams) + : INHERITED(kGrTwoPointConicalGradientLayout_ClassID, + (OptimizationFlags)kNone_OptimizationFlags) + , fCoordTransform0(gradientMatrix) + , gradientMatrix(gradientMatrix) + , type(type) + , isRadiusIncreasing(isRadiusIncreasing) + , isFocalOnCircle(isFocalOnCircle) + , isWellBehaved(isWellBehaved) + , isSwapped(isSwapped) + , isNativelyFocal(isNativelyFocal) + , focalParams(focalParams) { + this->addCoordTransform(&fCoordTransform0); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp b/gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp new file mode 100644 index 0000000000..3cf31b7c7d --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp @@ -0,0 +1,381 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrUnrolledBinaryGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#include "GrUnrolledBinaryGradientColorizer.h" + +#include "include/gpu/GrTexture.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/sksl/SkSLCPP.h" +#include "src/sksl/SkSLUtil.h" +class GrGLSLUnrolledBinaryGradientColorizer : public GrGLSLFragmentProcessor { +public: + GrGLSLUnrolledBinaryGradientColorizer() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrUnrolledBinaryGradientColorizer& _outer = + args.fFp.cast(); + (void)_outer; + auto intervalCount = _outer.intervalCount; + (void)intervalCount; + auto scale0_1 = _outer.scale0_1; + (void)scale0_1; + auto scale2_3 = _outer.scale2_3; + (void)scale2_3; + auto scale4_5 = _outer.scale4_5; + (void)scale4_5; + auto scale6_7 = _outer.scale6_7; + (void)scale6_7; + auto scale8_9 = _outer.scale8_9; + (void)scale8_9; + auto scale10_11 = _outer.scale10_11; + (void)scale10_11; + auto scale12_13 = _outer.scale12_13; + (void)scale12_13; + auto scale14_15 = _outer.scale14_15; + (void)scale14_15; + auto bias0_1 = _outer.bias0_1; + (void)bias0_1; + auto bias2_3 = _outer.bias2_3; + (void)bias2_3; + auto bias4_5 = _outer.bias4_5; + (void)bias4_5; + auto bias6_7 = _outer.bias6_7; + (void)bias6_7; + auto bias8_9 = _outer.bias8_9; + (void)bias8_9; + auto bias10_11 = _outer.bias10_11; + (void)bias10_11; + auto bias12_13 = _outer.bias12_13; + (void)bias12_13; + auto bias14_15 = _outer.bias14_15; + (void)bias14_15; + auto thresholds1_7 = _outer.thresholds1_7; + (void)thresholds1_7; + auto thresholds9_13 = _outer.thresholds9_13; + (void)thresholds9_13; + scale0_1Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale0_1"); + if (intervalCount > 1) { + scale2_3Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale2_3"); + } + if (intervalCount > 2) { + scale4_5Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale4_5"); + } + if (intervalCount > 3) { + scale6_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale6_7"); + } + if (intervalCount > 4) { + scale8_9Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "scale8_9"); + } + if (intervalCount > 5) { + scale10_11Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, "scale10_11"); + } + if (intervalCount > 6) { + scale12_13Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, "scale12_13"); + } + if (intervalCount > 7) { + scale14_15Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, "scale14_15"); + } + bias0_1Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias0_1"); + if (intervalCount > 1) { + bias2_3Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias2_3"); + } + if (intervalCount > 2) { + bias4_5Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias4_5"); + } + if (intervalCount > 3) { + bias6_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias6_7"); + } + if (intervalCount > 4) { + bias8_9Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, + "bias8_9"); + } + if (intervalCount > 5) { + bias10_11Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, "bias10_11"); + } + if (intervalCount > 6) { + bias12_13Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, "bias12_13"); + } + if (intervalCount > 7) { + bias14_15Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat4_GrSLType, "bias14_15"); + } + thresholds1_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, + "thresholds1_7"); + thresholds9_13Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, + kHalf4_GrSLType, "thresholds9_13"); + fragBuilder->codeAppendf( + "half t = %s.x;\nfloat4 scale, bias;\nif (%d <= 4 || t < %s.w) {\n if (%d <= 2 " + "|| t < %s.y) {\n if (%d <= 1 || t < %s.x) {\n scale = %s;\n " + " bias = %s;\n } else {\n scale = %s;\n bias = " + "%s;\n }\n } else {\n if (%d <= 3 || t < %s.z) {\n " + "scale = %s;\n bias = %s;\n } else {\n scale = %s;\n " + " bias = %s;\n }\n }\n} else {\n if (%d <= 6 || t < %s.y) " + "{\n if (%d <= 5 || t <", + args.fInputColor, _outer.intervalCount, + args.fUniformHandler->getUniformCStr(thresholds1_7Var), _outer.intervalCount, + args.fUniformHandler->getUniformCStr(thresholds1_7Var), _outer.intervalCount, + args.fUniformHandler->getUniformCStr(thresholds1_7Var), + args.fUniformHandler->getUniformCStr(scale0_1Var), + args.fUniformHandler->getUniformCStr(bias0_1Var), + scale2_3Var.isValid() ? args.fUniformHandler->getUniformCStr(scale2_3Var) + : "float4(0)", + bias2_3Var.isValid() ? args.fUniformHandler->getUniformCStr(bias2_3Var) + : "float4(0)", + _outer.intervalCount, args.fUniformHandler->getUniformCStr(thresholds1_7Var), + scale4_5Var.isValid() ? args.fUniformHandler->getUniformCStr(scale4_5Var) + : "float4(0)", + bias4_5Var.isValid() ? args.fUniformHandler->getUniformCStr(bias4_5Var) + : "float4(0)", + scale6_7Var.isValid() ? args.fUniformHandler->getUniformCStr(scale6_7Var) + : "float4(0)", + bias6_7Var.isValid() ? args.fUniformHandler->getUniformCStr(bias6_7Var) + : "float4(0)", + _outer.intervalCount, args.fUniformHandler->getUniformCStr(thresholds9_13Var), + _outer.intervalCount); + fragBuilder->codeAppendf( + " %s.x) {\n scale = %s;\n bias = %s;\n } else {\n " + " scale = %s;\n bias = %s;\n }\n } else {\n if " + "(%d <= 7 || t < %s.z) {\n scale = %s;\n bias = %s;\n " + "} else {\n scale = %s;\n bias = %s;\n }\n " + "}\n}\n%s = half4(float(t) * scale + bias);\n", + args.fUniformHandler->getUniformCStr(thresholds9_13Var), + scale8_9Var.isValid() ? args.fUniformHandler->getUniformCStr(scale8_9Var) + : "float4(0)", + bias8_9Var.isValid() ? args.fUniformHandler->getUniformCStr(bias8_9Var) + : "float4(0)", + scale10_11Var.isValid() ? args.fUniformHandler->getUniformCStr(scale10_11Var) + : "float4(0)", + bias10_11Var.isValid() ? args.fUniformHandler->getUniformCStr(bias10_11Var) + : "float4(0)", + _outer.intervalCount, args.fUniformHandler->getUniformCStr(thresholds9_13Var), + scale12_13Var.isValid() ? args.fUniformHandler->getUniformCStr(scale12_13Var) + : "float4(0)", + bias12_13Var.isValid() ? args.fUniformHandler->getUniformCStr(bias12_13Var) + : "float4(0)", + scale14_15Var.isValid() ? args.fUniformHandler->getUniformCStr(scale14_15Var) + : "float4(0)", + bias14_15Var.isValid() ? args.fUniformHandler->getUniformCStr(bias14_15Var) + : "float4(0)", + args.fOutputColor); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override { + const GrUnrolledBinaryGradientColorizer& _outer = + _proc.cast(); + { + pdman.set4fv(scale0_1Var, 1, (_outer.scale0_1).vec()); + if (scale2_3Var.isValid()) { + pdman.set4fv(scale2_3Var, 1, (_outer.scale2_3).vec()); + } + if (scale4_5Var.isValid()) { + pdman.set4fv(scale4_5Var, 1, (_outer.scale4_5).vec()); + } + if (scale6_7Var.isValid()) { + pdman.set4fv(scale6_7Var, 1, (_outer.scale6_7).vec()); + } + if (scale8_9Var.isValid()) { + pdman.set4fv(scale8_9Var, 1, (_outer.scale8_9).vec()); + } + if (scale10_11Var.isValid()) { + pdman.set4fv(scale10_11Var, 1, (_outer.scale10_11).vec()); + } + if (scale12_13Var.isValid()) { + pdman.set4fv(scale12_13Var, 1, (_outer.scale12_13).vec()); + } + if (scale14_15Var.isValid()) { + pdman.set4fv(scale14_15Var, 1, (_outer.scale14_15).vec()); + } + pdman.set4fv(bias0_1Var, 1, (_outer.bias0_1).vec()); + if (bias2_3Var.isValid()) { + pdman.set4fv(bias2_3Var, 1, (_outer.bias2_3).vec()); + } + if (bias4_5Var.isValid()) { + pdman.set4fv(bias4_5Var, 1, (_outer.bias4_5).vec()); + } + if (bias6_7Var.isValid()) { + pdman.set4fv(bias6_7Var, 1, (_outer.bias6_7).vec()); + } + if (bias8_9Var.isValid()) { + pdman.set4fv(bias8_9Var, 1, (_outer.bias8_9).vec()); + } + if (bias10_11Var.isValid()) { + pdman.set4fv(bias10_11Var, 1, (_outer.bias10_11).vec()); + } + if (bias12_13Var.isValid()) { + pdman.set4fv(bias12_13Var, 1, (_outer.bias12_13).vec()); + } + if (bias14_15Var.isValid()) { + pdman.set4fv(bias14_15Var, 1, (_outer.bias14_15).vec()); + } + pdman.set4fv(thresholds1_7Var, 1, + reinterpret_cast(&(_outer.thresholds1_7))); + pdman.set4fv(thresholds9_13Var, 1, + reinterpret_cast(&(_outer.thresholds9_13))); + } + } + UniformHandle scale0_1Var; + UniformHandle scale2_3Var; + UniformHandle scale4_5Var; + UniformHandle scale6_7Var; + UniformHandle scale8_9Var; + UniformHandle scale10_11Var; + UniformHandle scale12_13Var; + UniformHandle scale14_15Var; + UniformHandle bias0_1Var; + UniformHandle bias2_3Var; + UniformHandle bias4_5Var; + UniformHandle bias6_7Var; + UniformHandle bias8_9Var; + UniformHandle bias10_11Var; + UniformHandle bias12_13Var; + UniformHandle bias14_15Var; + UniformHandle thresholds1_7Var; + UniformHandle thresholds9_13Var; +}; +GrGLSLFragmentProcessor* GrUnrolledBinaryGradientColorizer::onCreateGLSLInstance() const { + return new GrGLSLUnrolledBinaryGradientColorizer(); +} +void GrUnrolledBinaryGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + b->add32((int32_t)intervalCount); +} +bool GrUnrolledBinaryGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const { + const GrUnrolledBinaryGradientColorizer& that = other.cast(); + (void)that; + if (intervalCount != that.intervalCount) return false; + if (scale0_1 != that.scale0_1) return false; + if (scale2_3 != that.scale2_3) return false; + if (scale4_5 != that.scale4_5) return false; + if (scale6_7 != that.scale6_7) return false; + if (scale8_9 != that.scale8_9) return false; + if (scale10_11 != that.scale10_11) return false; + if (scale12_13 != that.scale12_13) return false; + if (scale14_15 != that.scale14_15) return false; + if (bias0_1 != that.bias0_1) return false; + if (bias2_3 != that.bias2_3) return false; + if (bias4_5 != that.bias4_5) return false; + if (bias6_7 != that.bias6_7) return false; + if (bias8_9 != that.bias8_9) return false; + if (bias10_11 != that.bias10_11) return false; + if (bias12_13 != that.bias12_13) return false; + if (bias14_15 != that.bias14_15) return false; + if (thresholds1_7 != that.thresholds1_7) return false; + if (thresholds9_13 != that.thresholds9_13) return false; + return true; +} +GrUnrolledBinaryGradientColorizer::GrUnrolledBinaryGradientColorizer( + const GrUnrolledBinaryGradientColorizer& src) + : INHERITED(kGrUnrolledBinaryGradientColorizer_ClassID, src.optimizationFlags()) + , intervalCount(src.intervalCount) + , scale0_1(src.scale0_1) + , scale2_3(src.scale2_3) + , scale4_5(src.scale4_5) + , scale6_7(src.scale6_7) + , scale8_9(src.scale8_9) + , scale10_11(src.scale10_11) + , scale12_13(src.scale12_13) + , scale14_15(src.scale14_15) + , bias0_1(src.bias0_1) + , bias2_3(src.bias2_3) + , bias4_5(src.bias4_5) + , bias6_7(src.bias6_7) + , bias8_9(src.bias8_9) + , bias10_11(src.bias10_11) + , bias12_13(src.bias12_13) + , bias14_15(src.bias14_15) + , thresholds1_7(src.thresholds1_7) + , thresholds9_13(src.thresholds9_13) {} +std::unique_ptr GrUnrolledBinaryGradientColorizer::clone() const { + return std::unique_ptr(new GrUnrolledBinaryGradientColorizer(*this)); +} + +static const int kMaxIntervals = 8; +std::unique_ptr GrUnrolledBinaryGradientColorizer::Make( + const SkPMColor4f* colors, const SkScalar* positions, int count) { + // Depending on how the positions resolve into hard stops or regular stops, the number of + // intervals specified by the number of colors/positions can change. For instance, a plain + // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also + // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard + // stops has 16 colors. + + if (count > kMaxColorCount) { + // Definitely cannot represent this gradient configuration + return nullptr; + } + + // The raster implementation also uses scales and biases, but since they must be calculated + // after the dst color space is applied, it limits our ability to cache their values. + SkPMColor4f scales[kMaxIntervals]; + SkPMColor4f biases[kMaxIntervals]; + SkScalar thresholds[kMaxIntervals]; + + int intervalCount = 0; + + for (int i = 0; i < count - 1; i++) { + if (intervalCount >= kMaxIntervals) { + // Already reached kMaxIntervals, and haven't run out of color stops so this + // gradient cannot be represented by this shader. + return nullptr; + } + + SkScalar t0 = positions[i]; + SkScalar t1 = positions[i + 1]; + SkScalar dt = t1 - t0; + // If the interval is empty, skip to the next interval. This will automatically create + // distinct hard stop intervals as needed. It also protects against malformed gradients + // that have repeated hard stops at the very beginning that are effectively unreachable. + if (SkScalarNearlyZero(dt)) { + continue; + } + + auto c0 = Sk4f::Load(colors[i].vec()); + auto c1 = Sk4f::Load(colors[i + 1].vec()); + + auto scale = (c1 - c0) / dt; + auto bias = c0 - t0 * scale; + + scale.store(scales + intervalCount); + bias.store(biases + intervalCount); + thresholds[intervalCount] = t1; + intervalCount++; + } + + // For isEqual to make sense, set the unused values to something consistent + for (int i = intervalCount; i < kMaxIntervals; i++) { + scales[i] = SK_PMColor4fTRANSPARENT; + biases[i] = SK_PMColor4fTRANSPARENT; + thresholds[i] = 0.0; + } + + return std::unique_ptr(new GrUnrolledBinaryGradientColorizer( + intervalCount, scales[0], scales[1], scales[2], scales[3], scales[4], scales[5], + scales[6], scales[7], biases[0], biases[1], biases[2], biases[3], biases[4], biases[5], + biases[6], biases[7], + SkRect::MakeLTRB(thresholds[0], thresholds[1], thresholds[2], thresholds[3]), + SkRect::MakeLTRB(thresholds[4], thresholds[5], thresholds[6], 0.0))); +} diff --git a/gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h b/gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h new file mode 100644 index 0000000000..04a878bc40 --- /dev/null +++ b/gfx/skia/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h @@ -0,0 +1,93 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrUnrolledBinaryGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#ifndef GrUnrolledBinaryGradientColorizer_DEFINED +#define GrUnrolledBinaryGradientColorizer_DEFINED +#include "include/core/SkTypes.h" + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +class GrUnrolledBinaryGradientColorizer : public GrFragmentProcessor { +public: + static const int kMaxColorCount = 16; + + static std::unique_ptr Make(const SkPMColor4f* colors, + const SkScalar* positions, + int count); + GrUnrolledBinaryGradientColorizer(const GrUnrolledBinaryGradientColorizer& src); + std::unique_ptr clone() const override; + const char* name() const override { return "UnrolledBinaryGradientColorizer"; } + int32_t intervalCount; + SkPMColor4f scale0_1; + SkPMColor4f scale2_3; + SkPMColor4f scale4_5; + SkPMColor4f scale6_7; + SkPMColor4f scale8_9; + SkPMColor4f scale10_11; + SkPMColor4f scale12_13; + SkPMColor4f scale14_15; + SkPMColor4f bias0_1; + SkPMColor4f bias2_3; + SkPMColor4f bias4_5; + SkPMColor4f bias6_7; + SkPMColor4f bias8_9; + SkPMColor4f bias10_11; + SkPMColor4f bias12_13; + SkPMColor4f bias14_15; + SkRect thresholds1_7; + SkRect thresholds9_13; + +private: + GrUnrolledBinaryGradientColorizer(int32_t intervalCount, + SkPMColor4f scale0_1, + SkPMColor4f scale2_3, + SkPMColor4f scale4_5, + SkPMColor4f scale6_7, + SkPMColor4f scale8_9, + SkPMColor4f scale10_11, + SkPMColor4f scale12_13, + SkPMColor4f scale14_15, + SkPMColor4f bias0_1, + SkPMColor4f bias2_3, + SkPMColor4f bias4_5, + SkPMColor4f bias6_7, + SkPMColor4f bias8_9, + SkPMColor4f bias10_11, + SkPMColor4f bias12_13, + SkPMColor4f bias14_15, + SkRect thresholds1_7, + SkRect thresholds9_13) + : INHERITED(kGrUnrolledBinaryGradientColorizer_ClassID, kNone_OptimizationFlags) + , intervalCount(intervalCount) + , scale0_1(scale0_1) + , scale2_3(scale2_3) + , scale4_5(scale4_5) + , scale6_7(scale6_7) + , scale8_9(scale8_9) + , scale10_11(scale10_11) + , scale12_13(scale12_13) + , scale14_15(scale14_15) + , bias0_1(bias0_1) + , bias2_3(bias2_3) + , bias4_5(bias4_5) + , bias6_7(bias6_7) + , bias8_9(bias8_9) + , bias10_11(bias10_11) + , bias12_13(bias12_13) + , bias14_15(bias14_15) + , thresholds1_7(thresholds1_7) + , thresholds9_13(thresholds9_13) {} + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + typedef GrFragmentProcessor INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockBuffer.h b/gfx/skia/skia/src/gpu/mock/GrMockBuffer.h new file mode 100644 index 0000000000..17b56a0f02 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockBuffer.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMockBuffer_DEFINED +#define GrMockBuffer_DEFINED + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/mock/GrMockGpu.h" + +class GrMockBuffer : public GrGpuBuffer { +public: + GrMockBuffer(GrMockGpu* gpu, size_t sizeInBytes, GrGpuBufferType type, + GrAccessPattern accessPattern) + : INHERITED(gpu, sizeInBytes, type, accessPattern) { + this->registerWithCache(SkBudgeted::kYes); + } + +private: + void onMap() override { + if (GrCaps::kNone_MapFlags != this->getGpu()->caps()->mapBufferFlags()) { + fMapPtr = sk_malloc_throw(this->size()); + } + } + void onUnmap() override { sk_free(fMapPtr); } + bool onUpdateData(const void* src, size_t srcSizeInBytes) override { return true; } + + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockCaps.cpp b/gfx/skia/skia/src/gpu/mock/GrMockCaps.cpp new file mode 100644 index 0000000000..8d798faf3b --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockCaps.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mock/GrMockCaps.h" + +#if GR_TEST_UTILS +std::vector GrMockCaps::getTestingCombinations() const { + // TODO: need to add compressed formats to this list + std::vector combos = { + { GrColorType::kAlpha_8, GrBackendFormat::MakeMock(GrColorType::kAlpha_8) }, + { GrColorType::kBGR_565, GrBackendFormat::MakeMock(GrColorType::kBGR_565) }, + { GrColorType::kABGR_4444, GrBackendFormat::MakeMock(GrColorType::kABGR_4444) }, + { GrColorType::kRGBA_8888, GrBackendFormat::MakeMock(GrColorType::kRGBA_8888) }, + { GrColorType::kRGBA_8888_SRGB, GrBackendFormat::MakeMock(GrColorType::kRGBA_8888_SRGB) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeMock(GrColorType::kRGB_888x) }, + { GrColorType::kRG_88, GrBackendFormat::MakeMock(GrColorType::kRG_88) }, + { GrColorType::kBGRA_8888, GrBackendFormat::MakeMock(GrColorType::kBGRA_8888) }, + { GrColorType::kRGBA_1010102, GrBackendFormat::MakeMock(GrColorType::kRGBA_1010102) }, + { GrColorType::kGray_8, GrBackendFormat::MakeMock(GrColorType::kGray_8) }, + { GrColorType::kAlpha_F16, GrBackendFormat::MakeMock(GrColorType::kAlpha_F16) }, + { GrColorType::kRGBA_F16, GrBackendFormat::MakeMock(GrColorType::kRGBA_F16) }, + { GrColorType::kRGBA_F16_Clamped,GrBackendFormat::MakeMock(GrColorType::kRGBA_F16_Clamped)}, + { GrColorType::kAlpha_16, GrBackendFormat::MakeMock(GrColorType::kAlpha_16) }, + { GrColorType::kRG_1616, GrBackendFormat::MakeMock(GrColorType::kRG_1616) }, + { GrColorType::kRGBA_16161616, GrBackendFormat::MakeMock(GrColorType::kRGBA_16161616) }, + { GrColorType::kRG_F16, GrBackendFormat::MakeMock(GrColorType::kRG_F16) }, + }; + +#ifdef SK_DEBUG + for (auto combo : combos) { + SkASSERT(this->onAreColorTypeAndFormatCompatible(combo.fColorType, combo.fFormat)); + } +#endif + + return combos; +} +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockCaps.h b/gfx/skia/skia/src/gpu/mock/GrMockCaps.h new file mode 100644 index 0000000000..457236c238 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockCaps.h @@ -0,0 +1,186 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMockCaps_DEFINED +#define GrMockCaps_DEFINED + +#include "include/gpu/mock/GrMockTypes.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/SkGr.h" + +class GrMockCaps : public GrCaps { +public: + GrMockCaps(const GrContextOptions& contextOptions, const GrMockOptions& options) + : INHERITED(contextOptions), fOptions(options) { + fMipMapSupport = options.fMipMapSupport; + fInstanceAttribSupport = options.fInstanceAttribSupport; + fHalfFloatVertexAttributeSupport = options.fHalfFloatVertexAttributeSupport; + fMapBufferFlags = options.fMapBufferFlags; + fBufferMapThreshold = SK_MaxS32; // Overridable in GrContextOptions. + fMaxTextureSize = options.fMaxTextureSize; + fMaxRenderTargetSize = SkTMin(options.fMaxRenderTargetSize, fMaxTextureSize); + fMaxPreferredRenderTargetSize = fMaxRenderTargetSize; + fMaxVertexAttributes = options.fMaxVertexAttributes; + fSampleLocationsSupport = true; + + fShaderCaps.reset(new GrShaderCaps(contextOptions)); + fShaderCaps->fGeometryShaderSupport = options.fGeometryShaderSupport; + fShaderCaps->fIntegerSupport = options.fIntegerSupport; + fShaderCaps->fFlatInterpolationSupport = options.fFlatInterpolationSupport; + fShaderCaps->fMaxFragmentSamplers = options.fMaxFragmentSamplers; + fShaderCaps->fShaderDerivativeSupport = options.fShaderDerivativeSupport; + fShaderCaps->fDualSourceBlendingSupport = options.fDualSourceBlendingSupport; + fShaderCaps->fSampleVariablesSupport = true; + fShaderCaps->fSampleVariablesStencilSupport = true; + + this->applyOptionsOverrides(contextOptions); + } + + bool isFormatSRGB(const GrBackendFormat& format) const override { + auto ct = format.asMockColorType(); + return GrGetColorTypeDesc(ct).encoding() == GrColorTypeEncoding::kSRGBUnorm; + } + + // Mock caps doesn't support any compressed formats right now + bool isFormatCompressed(const GrBackendFormat&, + SkImage::CompressionType* compressionType = nullptr) const override { + return false; + } + + bool isFormatTexturableAndUploadable(GrColorType, + const GrBackendFormat& format) const override { + return this->isFormatTexturable(format); + } + bool isFormatTexturable(const GrBackendFormat& format) const override { + auto index = static_cast(format.asMockColorType()); + return fOptions.fConfigOptions[index].fTexturable; + } + + bool isFormatCopyable(const GrBackendFormat& format) const override { + return false; + } + + bool isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount = 1) const override { + // Currently we don't allow RGB_888X to be renderable because we don't have a way to + // handle blends that reference dst alpha when the values in the dst alpha channel are + // uninitialized. + if (ct == GrColorType::kRGB_888x) { + return false; + } + return this->isFormatRenderable(format, sampleCount); + } + + bool isFormatRenderable(const GrBackendFormat& format, int sampleCount) const override { + return sampleCount <= this->maxRenderTargetSampleCount(format.asMockColorType()); + } + + int getRenderTargetSampleCount(int requestCount, GrColorType ct) const { + requestCount = SkTMax(requestCount, 1); + + switch (fOptions.fConfigOptions[(int)ct].fRenderability) { + case GrMockOptions::ConfigOptions::Renderability::kNo: + return 0; + case GrMockOptions::ConfigOptions::Renderability::kNonMSAA: + return requestCount > 1 ? 0 : 1; + case GrMockOptions::ConfigOptions::Renderability::kMSAA: + return requestCount > kMaxSampleCnt ? 0 : GrNextPow2(requestCount); + } + return 0; + } + + int getRenderTargetSampleCount(int requestCount, + const GrBackendFormat& format) const override { + return this->getRenderTargetSampleCount(requestCount, format.asMockColorType()); + } + + int maxRenderTargetSampleCount(GrColorType ct) const { + switch (fOptions.fConfigOptions[(int)ct].fRenderability) { + case GrMockOptions::ConfigOptions::Renderability::kNo: + return 0; + case GrMockOptions::ConfigOptions::Renderability::kNonMSAA: + return 1; + case GrMockOptions::ConfigOptions::Renderability::kMSAA: + return kMaxSampleCnt; + } + return 0; + } + + int maxRenderTargetSampleCount(const GrBackendFormat& format) const override { + return this->maxRenderTargetSampleCount(format.asMockColorType()); + } + + size_t bytesPerPixel(const GrBackendFormat& format) const override { + return GrColorTypeBytesPerPixel(format.asMockColorType()); + } + + SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const override { + return {surfaceColorType, 1}; + } + + SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const override { + return SurfaceReadPixelsSupport::kSupported; + } + + GrColorType getYUVAColorTypeFromBackendFormat(const GrBackendFormat& format, + bool isAlphaChannel) const override { + return format.asMockColorType(); + } + + GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override { + return {}; + } + + GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override { + return GrSwizzle(); + } + GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override { + return GrSwizzle(); + } + +#if GR_TEST_UTILS + std::vector getTestingCombinations() const override; +#endif + +private: + bool onSurfaceSupportsWritePixels(const GrSurface*) const override { return true; } + bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const override { + return true; + } + GrBackendFormat onGetDefaultBackendFormat(GrColorType ct, GrRenderable) const override { + return GrBackendFormat::MakeMock(ct); + } + + GrPixelConfig onGetConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType) const override { + return GrColorTypeToPixelConfig(format.asMockColorType()); + } + + bool onAreColorTypeAndFormatCompatible(GrColorType ct, + const GrBackendFormat& format) const override { + if (ct == GrColorType::kUnknown) { + return false; + } + + return ct == format.asMockColorType(); + } + + SupportedRead onSupportedReadPixelsColorType(GrColorType srcColorType, const GrBackendFormat&, + GrColorType) const override { + return SupportedRead{srcColorType, 1}; + } + + static const int kMaxSampleCnt = 16; + + GrMockOptions fOptions; + typedef GrCaps INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockGpu.cpp b/gfx/skia/skia/src/gpu/mock/GrMockGpu.cpp new file mode 100644 index 0000000000..0825d86ecb --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockGpu.cpp @@ -0,0 +1,310 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mock/GrMockBuffer.h" +#include "src/gpu/mock/GrMockCaps.h" +#include "src/gpu/mock/GrMockGpu.h" +#include "src/gpu/mock/GrMockOpsRenderPass.h" +#include "src/gpu/mock/GrMockStencilAttachment.h" +#include "src/gpu/mock/GrMockTexture.h" +#include + +int GrMockGpu::NextInternalTextureID() { + static std::atomic nextID{1}; + int id; + do { + id = nextID.fetch_add(1); + } while (0 == id); // Reserve 0 for an invalid ID. + return id; +} + +int GrMockGpu::NextExternalTextureID() { + // We use negative ints for the "testing only external textures" so they can easily be + // identified when debugging. + static std::atomic nextID{-1}; + return nextID--; +} + +int GrMockGpu::NextInternalRenderTargetID() { + // We start off with large numbers to differentiate from texture IDs, even though they're + // technically in a different space. + static std::atomic nextID{SK_MaxS32}; + return nextID--; +} + +int GrMockGpu::NextExternalRenderTargetID() { + // We use large negative ints for the "testing only external render targets" so they can easily + // be identified when debugging. + static std::atomic nextID{SK_MinS32}; + return nextID++; +} + +sk_sp GrMockGpu::Make(const GrMockOptions* mockOptions, + const GrContextOptions& contextOptions, GrContext* context) { + static const GrMockOptions kDefaultOptions = GrMockOptions(); + if (!mockOptions) { + mockOptions = &kDefaultOptions; + } + return sk_sp(new GrMockGpu(context, *mockOptions, contextOptions)); +} + +GrOpsRenderPass* GrMockGpu::getOpsRenderPass( + GrRenderTarget* rt, GrSurfaceOrigin origin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) { + return new GrMockOpsRenderPass(this, rt, origin, colorInfo); +} + +void GrMockGpu::submit(GrOpsRenderPass* renderPass) { + for (int i = 0; i < static_cast(renderPass)->numDraws(); ++i) { + fStats.incNumDraws(); + } + delete renderPass; +} + +GrMockGpu::GrMockGpu(GrContext* context, const GrMockOptions& options, + const GrContextOptions& contextOptions) + : INHERITED(context) + , fMockOptions(options) { + fCaps.reset(new GrMockCaps(contextOptions, options)); +} + +void GrMockGpu::querySampleLocations(GrRenderTarget* rt, SkTArray* sampleLocations) { + sampleLocations->reset(); + int numRemainingSamples = rt->numSamples(); + while (numRemainingSamples > 0) { + // Use standard D3D sample locations. + switch (numRemainingSamples) { + case 0: + case 1: + sampleLocations->push_back().set(.5, .5); + break; + case 2: + sampleLocations->push_back().set(.75, .75); + sampleLocations->push_back().set(.25, .25); + break; + case 3: + case 4: + sampleLocations->push_back().set(.375, .125); + sampleLocations->push_back().set(.875, .375); + sampleLocations->push_back().set(.125, .625); + sampleLocations->push_back().set(.625, .875); + break; + case 5: + case 6: + case 7: + case 8: + sampleLocations->push_back().set(.5625, .3125); + sampleLocations->push_back().set(.4375, .6875); + sampleLocations->push_back().set(.8125, .5625); + sampleLocations->push_back().set(.3125, .1875); + sampleLocations->push_back().set(.1875, .8125); + sampleLocations->push_back().set(.0625, .4375); + sampleLocations->push_back().set(.6875, .4375); + sampleLocations->push_back().set(.4375, .0625); + break; + default: + sampleLocations->push_back().set(.5625, .5625); + sampleLocations->push_back().set(.4375, .3125); + sampleLocations->push_back().set(.3125, .6250); + sampleLocations->push_back().set(.2500, .4375); + sampleLocations->push_back().set(.1875, .3750); + sampleLocations->push_back().set(.6250, .8125); + sampleLocations->push_back().set(.8125, .6875); + sampleLocations->push_back().set(.6875, .1875); + sampleLocations->push_back().set(.3750, .8750); + sampleLocations->push_back().set(.5000, .0625); + sampleLocations->push_back().set(.2500, .1250); + sampleLocations->push_back().set(.1250, .2500); + sampleLocations->push_back().set(.0000, .5000); + sampleLocations->push_back().set(.4375, .2500); + sampleLocations->push_back().set(.8750, .4375); + sampleLocations->push_back().set(.0625, .0000); + break; + } + numRemainingSamples = rt->numSamples() - sampleLocations->count(); + } +} + +sk_sp GrMockGpu::onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + int mipLevelCount, + uint32_t levelClearMask) { + if (fMockOptions.fFailTextureAllocations) { + return nullptr; + } + + GrColorType ct = format.asMockColorType(); + SkASSERT(ct != GrColorType::kUnknown); + + GrMipMapsStatus mipMapsStatus = + mipLevelCount > 1 ? GrMipMapsStatus::kDirty : GrMipMapsStatus::kNotAllocated; + GrMockTextureInfo texInfo(ct, NextInternalTextureID()); + if (renderable == GrRenderable::kYes) { + GrMockRenderTargetInfo rtInfo(ct, NextInternalRenderTargetID()); + return sk_sp(new GrMockTextureRenderTarget(this, budgeted, desc, + renderTargetSampleCnt, isProtected, + mipMapsStatus, texInfo, rtInfo)); + } + return sk_sp( + new GrMockTexture(this, budgeted, desc, isProtected, mipMapsStatus, texInfo)); +} + +sk_sp GrMockGpu::onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType compressionType, + SkBudgeted budgeted, const void* data) { + return nullptr; +} + +sk_sp GrMockGpu::onWrapBackendTexture(const GrBackendTexture& tex, GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable wrapType, GrIOType ioType) { + GrMockTextureInfo texInfo; + SkAssertResult(tex.getMockTextureInfo(&texInfo)); + + SkASSERT(colorType == texInfo.fColorType); + GrSurfaceDesc desc; + desc.fWidth = tex.width(); + desc.fHeight = tex.height(); + desc.fConfig = texInfo.pixelConfig(); + + GrMipMapsStatus mipMapsStatus = tex.hasMipMaps() ? GrMipMapsStatus::kValid + : GrMipMapsStatus::kNotAllocated; + auto isProtected = GrProtected(tex.isProtected()); + return sk_sp( + new GrMockTexture(this, desc, isProtected, mipMapsStatus, texInfo, wrapType, ioType)); +} + +sk_sp GrMockGpu::onWrapRenderableBackendTexture(const GrBackendTexture& tex, + int sampleCnt, + GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable) { + GrMockTextureInfo texInfo; + SkAssertResult(tex.getMockTextureInfo(&texInfo)); + + SkASSERT(colorType == texInfo.fColorType); + GrSurfaceDesc desc; + desc.fWidth = tex.width(); + desc.fHeight = tex.height(); + desc.fConfig = texInfo.pixelConfig(); + + GrMipMapsStatus mipMapsStatus = + tex.hasMipMaps() ? GrMipMapsStatus::kValid : GrMipMapsStatus::kNotAllocated; + + // The client gave us the texture ID but we supply the render target ID. + GrMockRenderTargetInfo rtInfo(texInfo.fColorType, NextInternalRenderTargetID()); + + auto isProtected = GrProtected(tex.isProtected()); + return sk_sp(new GrMockTextureRenderTarget( + this, desc, sampleCnt, isProtected, mipMapsStatus, texInfo, rtInfo, cacheable)); +} + +sk_sp GrMockGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& rt, + GrColorType colorType) { + GrMockRenderTargetInfo info; + SkAssertResult(rt.getMockRenderTargetInfo(&info)); + + SkASSERT(colorType == info.colorType()); + GrSurfaceDesc desc; + desc.fWidth = rt.width(); + desc.fHeight = rt.height(); + desc.fConfig = info.pixelConfig(); + + auto isProtected = GrProtected(rt.isProtected()); + return sk_sp(new GrMockRenderTarget(this, GrMockRenderTarget::kWrapped, desc, + rt.sampleCnt(), isProtected, info)); +} + +sk_sp GrMockGpu::onWrapBackendTextureAsRenderTarget(const GrBackendTexture& tex, + int sampleCnt, + GrColorType colorType) { + GrMockTextureInfo texInfo; + SkAssertResult(tex.getMockTextureInfo(&texInfo)); + + SkASSERT(colorType == texInfo.fColorType); + GrSurfaceDesc desc; + desc.fWidth = tex.width(); + desc.fHeight = tex.height(); + desc.fConfig = texInfo.pixelConfig(); + + // The client gave us the texture ID but we supply the render target ID. + GrMockRenderTargetInfo rtInfo(texInfo.fColorType, NextInternalRenderTargetID()); + + auto isProtected = GrProtected(tex.isProtected()); + return sk_sp(new GrMockRenderTarget(this, GrMockRenderTarget::kWrapped, desc, + sampleCnt, isProtected, rtInfo)); +} + +sk_sp GrMockGpu::onCreateBuffer(size_t sizeInBytes, GrGpuBufferType type, + GrAccessPattern accessPattern, const void*) { + return sk_sp(new GrMockBuffer(this, sizeInBytes, type, accessPattern)); +} + +GrStencilAttachment* GrMockGpu::createStencilAttachmentForRenderTarget( + const GrRenderTarget* rt, int width, int height, int numStencilSamples) { + SkASSERT(numStencilSamples == rt->numSamples()); + static constexpr int kBits = 8; + fStats.incStencilAttachmentCreates(); + return new GrMockStencilAttachment(this, width, height, kBits, rt->numSamples()); +} + +GrBackendTexture GrMockGpu::onCreateBackendTexture(int w, int h, + const GrBackendFormat& format, + GrMipMapped mipMapped, + GrRenderable /* renderable */, + const SkPixmap /*srcData*/[], + int /*numMipLevels*/, + const SkColor4f* /* color */, + GrProtected /* isProtected */) { + auto colorType = format.asMockColorType(); + if (!this->caps()->isFormatTexturable(format)) { + return GrBackendTexture(); // invalid + } + + GrMockTextureInfo info(colorType, NextExternalTextureID()); + + fOutstandingTestingOnlyTextureIDs.add(info.fID); + return GrBackendTexture(w, h, mipMapped, info); +} + +void GrMockGpu::deleteBackendTexture(const GrBackendTexture& tex) { + SkASSERT(GrBackendApi::kMock == tex.backend()); + + GrMockTextureInfo info; + if (tex.getMockTextureInfo(&info)) { + fOutstandingTestingOnlyTextureIDs.remove(info.fID); + } +} + +#if GR_TEST_UTILS +bool GrMockGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const { + SkASSERT(GrBackendApi::kMock == tex.backend()); + + GrMockTextureInfo info; + if (!tex.getMockTextureInfo(&info)) { + return false; + } + + return fOutstandingTestingOnlyTextureIDs.contains(info.fID); +} + +GrBackendRenderTarget GrMockGpu::createTestingOnlyBackendRenderTarget(int w, int h, + GrColorType colorType) { + GrMockRenderTargetInfo info(colorType, NextExternalRenderTargetID()); + static constexpr int kSampleCnt = 1; + static constexpr int kStencilBits = 8; + return GrBackendRenderTarget(w, h, kSampleCnt, kStencilBits, info); +} + +void GrMockGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) {} +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockGpu.h b/gfx/skia/skia/src/gpu/mock/GrMockGpu.h new file mode 100644 index 0000000000..bbafae0270 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockGpu.h @@ -0,0 +1,160 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMockGpu_DEFINED +#define GrMockGpu_DEFINED + +#include "include/gpu/GrTexture.h" +#include "include/private/SkTHash.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrSemaphore.h" + +class GrMockOpsRenderPass; +struct GrMockOptions; +class GrPipeline; + +class GrMockGpu : public GrGpu { +public: + static sk_sp Make(const GrMockOptions*, const GrContextOptions&, GrContext*); + + ~GrMockGpu() override {} + + GrOpsRenderPass* getOpsRenderPass( + GrRenderTarget*, GrSurfaceOrigin, const SkIRect&, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) override; + + GrFence SK_WARN_UNUSED_RESULT insertFence() override { return 0; } + bool waitFence(GrFence, uint64_t) override { return true; } + void deleteFence(GrFence) const override {} + + sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned) override { + return nullptr; + } + sk_sp wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) override { return nullptr; } + void insertSemaphore(sk_sp semaphore) override {} + void waitSemaphore(sk_sp semaphore) override {} + sk_sp prepareTextureForCrossContextUsage(GrTexture*) override { return nullptr; } + + void submit(GrOpsRenderPass* renderPass) override; + + void checkFinishProcs() override {} + +private: + GrMockGpu(GrContext* context, const GrMockOptions&, const GrContextOptions&); + + void onResetContext(uint32_t resetBits) override {} + + void querySampleLocations(GrRenderTarget*, SkTArray* sampleLocations) override; + + void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {} + + sk_sp onCreateTexture(const GrSurfaceDesc&, + const GrBackendFormat&, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted, + GrProtected, + int mipLevelCount, + uint32_t levelClearMask) override; + + sk_sp onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, + const void* data) override; + + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrColorType, GrWrapOwnership, + GrWrapCacheable, GrIOType) override; + + sk_sp onWrapRenderableBackendTexture(const GrBackendTexture&, + int sampleCnt, + GrColorType, + GrWrapOwnership, + GrWrapCacheable) override; + + sk_sp onWrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType) override; + + sk_sp onWrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, GrColorType) override; + + sk_sp onCreateBuffer(size_t sizeInBytes, GrGpuBufferType, GrAccessPattern, + const void*) override; + + bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) override { + return true; + } + + bool onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) override { + return true; + } + + bool onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) override { + return true; + } + bool onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) override { + return true; + } + bool onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) override { + return true; + } + + bool onRegenerateMipMapLevels(GrTexture*) override { return true; } + + void onResolveRenderTarget(GrRenderTarget* target, const SkIRect&, GrSurfaceOrigin, + ForExternalIO) override {} + + void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info, const GrPrepareForExternalIORequests&) override { + if (info.fFinishedProc) { + info.fFinishedProc(info.fFinishedContext); + } + } + + GrStencilAttachment* createStencilAttachmentForRenderTarget( + const GrRenderTarget*, int width, int height, int numStencilSamples) override; + GrBackendTexture onCreateBackendTexture(int w, int h, const GrBackendFormat&, + GrMipMapped, GrRenderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected) override; + void deleteBackendTexture(const GrBackendTexture&) override; + +#if GR_TEST_UTILS + bool isTestingOnlyBackendTexture(const GrBackendTexture&) const override; + + GrBackendRenderTarget createTestingOnlyBackendRenderTarget(int w, int h, GrColorType) override; + void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) override; + + void testingOnly_flushGpuAndSync() override {} +#endif + + const GrMockOptions fMockOptions; + + static int NextInternalTextureID(); + static int NextExternalTextureID(); + static int NextInternalRenderTargetID(); + static int NextExternalRenderTargetID(); + + SkTHashSet fOutstandingTestingOnlyTextureIDs; + + typedef GrGpu INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockOpsRenderPass.h b/gfx/skia/skia/src/gpu/mock/GrMockOpsRenderPass.h new file mode 100644 index 0000000000..659dfbaa41 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockOpsRenderPass.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMockOpsRenderPass_DEFINED +#define GrMockOpsRenderPass_DEFINED + +#include "src/gpu/GrOpsRenderPass.h" + +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/mock/GrMockGpu.h" + +class GrMockOpsRenderPass : public GrOpsRenderPass { +public: + GrMockOpsRenderPass(GrMockGpu* gpu, GrRenderTarget* rt, GrSurfaceOrigin origin, + LoadAndStoreInfo colorInfo) + : INHERITED(rt, origin) + , fGpu(gpu) + , fColorLoadOp(colorInfo.fLoadOp) { + } + + GrGpu* gpu() override { return fGpu; } + void inlineUpload(GrOpFlushState*, GrDeferredTextureUploadFn&) override {} + void insertEventMarker(const char*) override {} + void begin() override { + if (GrLoadOp::kClear == fColorLoadOp) { + this->markRenderTargetDirty(); + } + } + void end() override {} + + int numDraws() const { return fNumDraws; } + +private: + void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount, + const SkRect& bounds) override { + this->markRenderTargetDirty(); + ++fNumDraws; + } + void onClear(const GrFixedClip&, const SkPMColor4f&) override { + this->markRenderTargetDirty(); + } + void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) override {} + + void markRenderTargetDirty() { + if (auto* tex = fRenderTarget->asTexture()) { + tex->texturePriv().markMipMapsDirty(); + } + } + + GrMockGpu* fGpu; + GrLoadOp fColorLoadOp; + int fNumDraws = 0; + + typedef GrOpsRenderPass INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockStencilAttachment.h b/gfx/skia/skia/src/gpu/mock/GrMockStencilAttachment.h new file mode 100644 index 0000000000..a05894447a --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockStencilAttachment.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMockStencilAttachment_DEFINED +#define GrMockStencilAttachment_DEFINED + +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/mock/GrMockGpu.h" + +class GrMockStencilAttachment : public GrStencilAttachment { +public: + GrMockStencilAttachment(GrMockGpu* gpu, int width, int height, int bits, int sampleCnt) + : INHERITED(gpu, width, height, bits, sampleCnt) { + this->registerWithCache(SkBudgeted::kYes); + } + +private: + size_t onGpuMemorySize() const override { + return SkTMax(1, (int)(this->bits() / sizeof(char))) * this->width() * this->height(); + } + + typedef GrStencilAttachment INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockTexture.h b/gfx/skia/skia/src/gpu/mock/GrMockTexture.h new file mode 100644 index 0000000000..21d0634fe3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockTexture.h @@ -0,0 +1,199 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrMockTexture_DEFINED +#define GrMockTexture_DEFINED + +#include "include/gpu/GrTexture.h" +#include "include/gpu/mock/GrMockTypes.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/mock/GrMockGpu.h" + +class GrMockTexture : public GrTexture { +public: + GrMockTexture(GrMockGpu* gpu, SkBudgeted budgeted, const GrSurfaceDesc& desc, + GrProtected isProtected, GrMipMapsStatus mipMapsStatus, + const GrMockTextureInfo& info) + : GrMockTexture(gpu, desc, isProtected, mipMapsStatus, info) { + this->registerWithCache(budgeted); + } + + GrMockTexture(GrMockGpu* gpu, const GrSurfaceDesc& desc, GrProtected isProtected, + GrMipMapsStatus mipMapsStatus, const GrMockTextureInfo& info, + GrWrapCacheable cacheable, GrIOType ioType) + : GrMockTexture(gpu, desc, isProtected, mipMapsStatus, info) { + if (ioType == kRead_GrIOType) { + this->setReadOnly(); + } + this->registerWithCacheWrapped(cacheable); + } + + ~GrMockTexture() override {} + + GrBackendTexture getBackendTexture() const override { + return GrBackendTexture(this->width(), this->height(), this->texturePriv().mipMapped(), + fInfo); + } + + GrBackendFormat backendFormat() const override { + return fInfo.getBackendFormat(); + } + + void textureParamsModified() override {} + +protected: + // constructor for subclasses + GrMockTexture(GrMockGpu* gpu, const GrSurfaceDesc& desc, GrProtected isProtected, + GrMipMapsStatus mipMapsStatus, const GrMockTextureInfo& info) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected, + GrTextureType::k2D, mipMapsStatus) + , fInfo(info) {} + + void onRelease() override { + INHERITED::onRelease(); + } + + void onAbandon() override { + INHERITED::onAbandon(); + } + + bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override { + return false; + } + +private: + GrMockTextureInfo fInfo; + + typedef GrTexture INHERITED; +}; + +class GrMockRenderTarget : public GrRenderTarget { +public: + GrMockRenderTarget(GrMockGpu* gpu, SkBudgeted budgeted, const GrSurfaceDesc& desc, + int sampleCnt, GrProtected isProtected, const GrMockRenderTargetInfo& info) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, isProtected) + , fInfo(info) { + this->registerWithCache(budgeted); + } + + enum Wrapped { kWrapped }; + GrMockRenderTarget(GrMockGpu* gpu, Wrapped, const GrSurfaceDesc& desc, int sampleCnt, + GrProtected isProtected, const GrMockRenderTargetInfo& info) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, isProtected) + , fInfo(info) { + this->registerWithCacheWrapped(GrWrapCacheable::kNo); + } + + bool canAttemptStencilAttachment() const override { return true; } + bool completeStencilAttachment() override { return true; } + + size_t onGpuMemorySize() const override { + int numColorSamples = this->numSamples(); + if (numColorSamples > 1) { + // Add one to account for the resolve buffer. + ++numColorSamples; + } + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numColorSamples, GrMipMapped::kNo); + } + + GrBackendRenderTarget getBackendRenderTarget() const override { + int numStencilBits = 0; + if (GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment()) { + numStencilBits = stencil->bits(); + } + return {this->width(), this->height(), this->numSamples(), numStencilBits, fInfo}; + } + + GrBackendFormat backendFormat() const override { + return fInfo.getBackendFormat(); + } + +protected: + // constructor for subclasses + GrMockRenderTarget(GrMockGpu* gpu, const GrSurfaceDesc& desc, int sampleCnt, + GrProtected isProtected, const GrMockRenderTargetInfo& info) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, isProtected) + , fInfo(info) {} + +private: + GrMockRenderTargetInfo fInfo; + + typedef GrRenderTarget INHERITED; +}; + +class GrMockTextureRenderTarget : public GrMockTexture, public GrMockRenderTarget { +public: + // Internally created. + GrMockTextureRenderTarget(GrMockGpu* gpu, SkBudgeted budgeted, const GrSurfaceDesc& desc, + int sampleCnt, GrProtected isProtected, GrMipMapsStatus mipMapsStatus, + const GrMockTextureInfo& texInfo, + const GrMockRenderTargetInfo& rtInfo) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected) + , GrMockTexture(gpu, desc, isProtected, mipMapsStatus, texInfo) + , GrMockRenderTarget(gpu, desc, sampleCnt, isProtected, rtInfo) { + this->registerWithCache(budgeted); + } + + // Renderable wrapped backend texture. + GrMockTextureRenderTarget(GrMockGpu* gpu, const GrSurfaceDesc& desc, int sampleCnt, + GrProtected isProtected, GrMipMapsStatus mipMapsStatus, + const GrMockTextureInfo& texInfo, + const GrMockRenderTargetInfo& rtInfo, GrWrapCacheable cacheble) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, isProtected) + , GrMockTexture(gpu, desc, isProtected, mipMapsStatus, texInfo) + , GrMockRenderTarget(gpu, desc, sampleCnt, isProtected, rtInfo) { + this->registerWithCacheWrapped(cacheble); + } + + GrTexture* asTexture() override { return this; } + GrRenderTarget* asRenderTarget() override { return this; } + const GrTexture* asTexture() const override { return this; } + const GrRenderTarget* asRenderTarget() const override { return this; } + + GrBackendFormat backendFormat() const override { + return GrMockTexture::backendFormat(); + } + +protected: + // This avoids an inherits via dominance warning on MSVC. + void willRemoveLastRef() override { GrTexture::willRemoveLastRef(); } + +private: + void onAbandon() override { + GrRenderTarget::onAbandon(); + GrMockTexture::onAbandon(); + } + + void onRelease() override { + GrRenderTarget::onRelease(); + GrMockTexture::onRelease(); + } + + size_t onGpuMemorySize() const override { + int numColorSamples = this->numSamples(); + if (numColorSamples > 1) { + // Add one to account for the resolve buffer. + ++numColorSamples; + } + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numColorSamples, this->texturePriv().mipMapped()); + } + + // This avoids an inherits via dominance warning on MSVC. + void computeScratchKey(GrScratchKey* key) const override { GrTexture::computeScratchKey(key); } +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mock/GrMockTypes.cpp b/gfx/skia/skia/src/gpu/mock/GrMockTypes.cpp new file mode 100644 index 0000000000..836fe8a1d0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mock/GrMockTypes.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/mock/GrMockTypes.h" + +#include "include/gpu/GrBackendSurface.h" + +GrBackendFormat GrMockRenderTargetInfo::getBackendFormat() const { + return GrBackendFormat::MakeMock(fColorType); +} + +GrBackendFormat GrMockTextureInfo::getBackendFormat() const { + return GrBackendFormat::MakeMock(fColorType); +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.h b/gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.h new file mode 100644 index 0000000000..7f11d788d0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlBuffer_DEFINED +#define GrMtlBuffer_DEFINED + +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/mtl/GrMtlUniformHandler.h" + +#import + +class GrMtlCaps; +class GrMtlGpu; + +class GrMtlBuffer: public GrGpuBuffer { +public: + static sk_sp Make(GrMtlGpu*, size_t size, GrGpuBufferType intendedType, + GrAccessPattern, const void* data = nullptr); + + ~GrMtlBuffer() override; + + id mtlBuffer() const { return fMtlBuffer; } + size_t offset() const { return fOffset; } + void bind(); // for initial binding of XferGpuToCpu buffers + +protected: + GrMtlBuffer(GrMtlGpu*, size_t size, GrGpuBufferType intendedType, GrAccessPattern); + + void onAbandon() override; + void onRelease() override; + +private: + GrMtlGpu* mtlGpu() const; + + void onMap() override; + void onUnmap() override; + bool onUpdateData(const void* src, size_t srcSizeInBytes) override; + + void internalMap(size_t sizeInBytes); + void internalUnmap(size_t sizeInBytes); + +#ifdef SK_DEBUG + void validate() const; +#endif + + bool fIsDynamic; + id fMtlBuffer; + size_t fOffset; // offset into shared buffer for dynamic buffers + id fMappedBuffer; // buffer used by static buffers for uploads + + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.mm new file mode 100644 index 0000000000..fe585fcf50 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlBuffer.mm @@ -0,0 +1,188 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/mtl/GrMtlBuffer.h" +#include "src/gpu/mtl/GrMtlCommandBuffer.h" +#include "src/gpu/mtl/GrMtlGpu.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +#ifdef SK_DEBUG +#define VALIDATE() this->validate() +#else +#define VALIDATE() do {} while(false) +#endif + +sk_sp GrMtlBuffer::Make(GrMtlGpu* gpu, size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern, const void* data) { + sk_sp buffer(new GrMtlBuffer(gpu, size, intendedType, accessPattern)); + if (data && !buffer->onUpdateData(data, size)) { + return nullptr; + } + return buffer; +} + +GrMtlBuffer::GrMtlBuffer(GrMtlGpu* gpu, size_t size, GrGpuBufferType intendedType, + GrAccessPattern accessPattern) + : INHERITED(gpu, size, intendedType, accessPattern) + , fIsDynamic(accessPattern != kStatic_GrAccessPattern) + , fOffset(0) { + // In most cases, we'll allocate dynamic buffers when we map them, below. + if (!fIsDynamic) { + NSUInteger options = 0; + if (@available(macOS 10.11, iOS 9.0, *)) { + options |= MTLResourceStorageModePrivate; + } + fMtlBuffer = size == 0 ? nil : + [gpu->device() newBufferWithLength: size + options: options]; + } + this->registerWithCache(SkBudgeted::kYes); + VALIDATE(); +} + +GrMtlBuffer::~GrMtlBuffer() { + SkASSERT(fMtlBuffer == nil); + SkASSERT(fMappedBuffer == nil); + SkASSERT(fMapPtr == nullptr); +} + +void GrMtlBuffer::bind() { + SkASSERT(fIsDynamic && GrGpuBufferType::kXferGpuToCpu == this->intendedType()); + fMtlBuffer = this->mtlGpu()->resourceProvider().getDynamicBuffer(this->size(), &fOffset); +} + +bool GrMtlBuffer::onUpdateData(const void* src, size_t srcInBytes) { + if (!fIsDynamic) { + if (fMtlBuffer == nil) { + return false; + } + if (srcInBytes > fMtlBuffer.length) { + return false; + } + } + VALIDATE(); + + this->internalMap(srcInBytes); + if (fMapPtr == nil) { + return false; + } + SkASSERT(fMappedBuffer); + if (!fIsDynamic) { + SkASSERT(srcInBytes == fMappedBuffer.length); + } + memcpy(fMapPtr, src, srcInBytes); + this->internalUnmap(srcInBytes); + + VALIDATE(); + return true; +} + +inline GrMtlGpu* GrMtlBuffer::mtlGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +void GrMtlBuffer::onAbandon() { + fMtlBuffer = nil; + fMappedBuffer = nil; + fMapPtr = nullptr; + VALIDATE(); + INHERITED::onAbandon(); +} + +void GrMtlBuffer::onRelease() { + if (!this->wasDestroyed()) { + VALIDATE(); + fMtlBuffer = nil; + fMappedBuffer = nil; + fMapPtr = nullptr; + VALIDATE(); + } + INHERITED::onRelease(); +} + +void GrMtlBuffer::internalMap(size_t sizeInBytes) { + if (this->wasDestroyed()) { + return; + } + VALIDATE(); + SkASSERT(!this->isMapped()); + if (fIsDynamic) { + if (GrGpuBufferType::kXferGpuToCpu != this->intendedType()) { + fMtlBuffer = this->mtlGpu()->resourceProvider().getDynamicBuffer(sizeInBytes, &fOffset); + } + fMappedBuffer = fMtlBuffer; + fMapPtr = static_cast(fMtlBuffer.contents) + fOffset; + } else { + SkASSERT(fMtlBuffer); + SkASSERT(fMappedBuffer == nil); + NSUInteger options = 0; + if (@available(macOS 10.11, iOS 9.0, *)) { + options |= MTLResourceStorageModeShared; + } + fMappedBuffer = + [this->mtlGpu()->device() newBufferWithLength: sizeInBytes + options: options]; + fMapPtr = fMappedBuffer.contents; + } + VALIDATE(); +} + +void GrMtlBuffer::internalUnmap(size_t sizeInBytes) { + SkASSERT(fMtlBuffer); + if (this->wasDestroyed()) { + return; + } + VALIDATE(); + SkASSERT(this->isMapped()); + if (fMtlBuffer == nil) { + fMappedBuffer = nil; + fMapPtr = nullptr; + return; + } + if (fIsDynamic) { +#ifdef SK_BUILD_FOR_MAC + // TODO: need to make sure offset and size have valid alignments. + [fMtlBuffer didModifyRange: NSMakeRange(fOffset, sizeInBytes)]; +#endif + } else { + GrMtlCommandBuffer* cmdBuffer = this->mtlGpu()->commandBuffer(); + id blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); + [blitCmdEncoder copyFromBuffer: fMappedBuffer + sourceOffset: 0 + toBuffer: fMtlBuffer + destinationOffset: 0 + size: sizeInBytes]; + } + fMappedBuffer = nil; + fMapPtr = nullptr; +} + +void GrMtlBuffer::onMap() { + this->internalMap(this->size()); +} + +void GrMtlBuffer::onUnmap() { + this->internalUnmap(this->size()); +} + +#ifdef SK_DEBUG +void GrMtlBuffer::validate() const { + SkASSERT(fMtlBuffer == nil || + this->intendedType() == GrGpuBufferType::kVertex || + this->intendedType() == GrGpuBufferType::kIndex || + this->intendedType() == GrGpuBufferType::kXferCpuToGpu || + this->intendedType() == GrGpuBufferType::kXferGpuToCpu); + SkASSERT(fMappedBuffer == nil || fMtlBuffer == nil || + fMappedBuffer.length <= fMtlBuffer.length); +} +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlCaps.h b/gfx/skia/skia/src/gpu/mtl/GrMtlCaps.h new file mode 100644 index 0000000000..6ffd3c3225 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlCaps.h @@ -0,0 +1,188 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlCaps_DEFINED +#define GrMtlCaps_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/mtl/GrMtlStencilAttachment.h" + +#import + +class GrShaderCaps; + +/** + * Stores some capabilities of a Mtl backend. + */ +class GrMtlCaps : public GrCaps { +public: + typedef GrMtlStencilAttachment::Format StencilFormat; + + GrMtlCaps(const GrContextOptions& contextOptions, id device, + MTLFeatureSet featureSet); + + bool isFormatSRGB(const GrBackendFormat&) const override; + bool isFormatCompressed(const GrBackendFormat&, + SkImage::CompressionType* compressionType = nullptr) const override; + + bool isFormatTexturableAndUploadable(GrColorType, const GrBackendFormat&) const override; + bool isFormatTexturable(const GrBackendFormat&) const override; + bool isFormatTexturable(MTLPixelFormat) const; + + bool isFormatCopyable(const GrBackendFormat&) const override { return true; } + + bool isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount = 1) const override; + bool isFormatRenderable(const GrBackendFormat& format, int sampleCount) const override; + bool isFormatRenderable(MTLPixelFormat, int sampleCount) const; + + int getRenderTargetSampleCount(int requestedCount, const GrBackendFormat&) const override; + int getRenderTargetSampleCount(int requestedCount, MTLPixelFormat) const; + + int maxRenderTargetSampleCount(const GrBackendFormat&) const override; + int maxRenderTargetSampleCount(MTLPixelFormat) const; + + size_t bytesPerPixel(const GrBackendFormat&) const override; + size_t bytesPerPixel(MTLPixelFormat) const; + + SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const override; + + SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const override { + return SurfaceReadPixelsSupport::kSupported; + } + + /** + * Returns both a supported and most prefered stencil format to use in draws. + */ + const StencilFormat& preferredStencilFormat() const { + return fPreferredStencilFormat; + } + + bool canCopyAsBlit(MTLPixelFormat dstFormat, int dstSampleCount, MTLPixelFormat srcFormat, + int srcSampleCount, const SkIRect& srcRect, const SkIPoint& dstPoint, + bool areDstSrcSameObj) const; + + bool canCopyAsResolve(GrSurface* dst, int dstSampleCount, GrSurface* src, int srcSampleCount, + const SkIRect& srcRect, const SkIPoint& dstPoint) const; + + GrColorType getYUVAColorTypeFromBackendFormat(const GrBackendFormat&, + bool isAlphaChannel) const override; + + GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override; + + MTLPixelFormat getFormatFromColorType(GrColorType colorType) const { + int idx = static_cast(colorType); + return fColorTypeToFormatTable[idx]; + } + + GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override; + GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override; + +#if GR_TEST_UTILS + std::vector getTestingCombinations() const override; +#endif + +private: + void initFeatureSet(MTLFeatureSet featureSet); + + void initStencilFormat(const id device); + + void initGrCaps(const id device); + void initShaderCaps(); + + void initFormatTable(); + + bool onSurfaceSupportsWritePixels(const GrSurface*) const override; + bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const override; + GrBackendFormat onGetDefaultBackendFormat(GrColorType, GrRenderable) const override; + GrPixelConfig onGetConfigFromBackendFormat(const GrBackendFormat&, GrColorType) const override; + bool onAreColorTypeAndFormatCompatible(GrColorType, const GrBackendFormat&) const override; + + SupportedRead onSupportedReadPixelsColorType(GrColorType, const GrBackendFormat&, + GrColorType) const override; + + // ColorTypeInfo for a specific format + struct ColorTypeInfo { + GrColorType fColorType = GrColorType::kUnknown; + enum { + kUploadData_Flag = 0x1, + // Does Ganesh itself support rendering to this colorType & format pair. Renderability + // still additionally depends on if the format itself is renderable. + kRenderable_Flag = 0x2, + }; + uint32_t fFlags = 0; + + GrSwizzle fTextureSwizzle; + GrSwizzle fOutputSwizzle; + }; + + struct FormatInfo { + uint32_t colorTypeFlags(GrColorType colorType) const { + for (int i = 0; i < fColorTypeInfoCount; ++i) { + if (fColorTypeInfos[i].fColorType == colorType) { + return fColorTypeInfos[i].fFlags; + } + } + return 0; + } + + enum { + kTexturable_Flag = 0x1, + kRenderable_Flag = 0x2, // Color attachment and blendable + kMSAA_Flag = 0x4, + kResolve_Flag = 0x8, + }; + static const uint16_t kAllFlags = kTexturable_Flag | kRenderable_Flag | + kMSAA_Flag | kResolve_Flag; + + uint16_t fFlags = 0; + + // This value is only valid for regular formats. Compressed formats will be 0. + size_t fBytesPerPixel = 0; + + std::unique_ptr fColorTypeInfos; + int fColorTypeInfoCount = 0; + }; +#ifdef SK_BUILD_FOR_IOS + static constexpr size_t kNumMtlFormats = 17; +#else + static constexpr size_t kNumMtlFormats = 14; +#endif + static size_t GetFormatIndex(MTLPixelFormat); + FormatInfo fFormatTable[kNumMtlFormats]; + + const FormatInfo& getFormatInfo(const MTLPixelFormat pixelFormat) const { + size_t index = GetFormatIndex(pixelFormat); + return fFormatTable[index]; + } + + MTLPixelFormat fColorTypeToFormatTable[kGrColorTypeCnt]; + void setColorType(GrColorType, std::initializer_list formats); + + enum class Platform { + kMac, + kIOS + }; + bool isMac() { return Platform::kMac == fPlatform; } + bool isIOS() { return Platform::kIOS == fPlatform; } + + Platform fPlatform; + int fFamilyGroup; + int fVersion; + + SkTDArray fSampleCounts; + + StencilFormat fPreferredStencilFormat; + + typedef GrCaps INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlCaps.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlCaps.mm new file mode 100644 index 0000000000..7511f26366 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlCaps.mm @@ -0,0 +1,1107 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlCaps.h" + +#include "include/core/SkRect.h" +#include "include/gpu/GrBackendSurface.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlCaps::GrMtlCaps(const GrContextOptions& contextOptions, const id device, + MTLFeatureSet featureSet) + : INHERITED(contextOptions) { + fShaderCaps.reset(new GrShaderCaps(contextOptions)); + + this->initFeatureSet(featureSet); + this->initGrCaps(device); + this->initShaderCaps(); + this->initFormatTable(); + this->initStencilFormat(device); + + this->applyOptionsOverrides(contextOptions); + fShaderCaps->applyOptionsOverrides(contextOptions); + + // The following are disabled due to the unfinished Metal backend, not because Metal itself + // doesn't support it. + fCrossContextTextureSupport = false; // GrMtlGpu::prepareTextureForCrossContextUsage() not impl +} + +void GrMtlCaps::initFeatureSet(MTLFeatureSet featureSet) { + // Mac OSX +#ifdef SK_BUILD_FOR_MAC + if (@available(macOS 10.12, *)) { + if (MTLFeatureSet_OSX_GPUFamily1_v2 == featureSet) { + fPlatform = Platform::kMac; + fFamilyGroup = 1; + fVersion = 2; + return; + } + } + if (MTLFeatureSet_OSX_GPUFamily1_v1 == featureSet) { + fPlatform = Platform::kMac; + fFamilyGroup = 1; + fVersion = 1; + return; + } +#endif + + // iOS Family group 3 +#ifdef SK_BUILD_FOR_IOS + if (@available(iOS 10.0, *)) { + if (MTLFeatureSet_iOS_GPUFamily3_v2 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 3; + fVersion = 2; + return; + } + } + if (@available(iOS 9.0, *)) { + if (MTLFeatureSet_iOS_GPUFamily3_v1 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 3; + fVersion = 1; + return; + } + } + + // iOS Family group 2 + if (@available(iOS 10.0, *)) { + if (MTLFeatureSet_iOS_GPUFamily2_v3 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 2; + fVersion = 3; + return; + } + } + if (@available(iOS 9.0, *)) { + if (MTLFeatureSet_iOS_GPUFamily2_v2 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 2; + fVersion = 2; + return; + } + } + if (MTLFeatureSet_iOS_GPUFamily2_v1 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 2; + fVersion = 1; + return; + } + + // iOS Family group 1 + if (@available(iOS 10.0, *)) { + if (MTLFeatureSet_iOS_GPUFamily1_v3 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 1; + fVersion = 3; + return; + } + } + if (@available(iOS 9.0, *)) { + if (MTLFeatureSet_iOS_GPUFamily1_v2 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 1; + fVersion = 2; + return; + } + } + if (MTLFeatureSet_iOS_GPUFamily1_v1 == featureSet) { + fPlatform = Platform::kIOS; + fFamilyGroup = 1; + fVersion = 1; + return; + } +#endif + // No supported feature sets were found + SK_ABORT("Requested an unsupported feature set"); +} + +bool GrMtlCaps::canCopyAsBlit(MTLPixelFormat dstFormat, int dstSampleCount, + MTLPixelFormat srcFormat, int srcSampleCount, + const SkIRect& srcRect, const SkIPoint& dstPoint, + bool areDstSrcSameObj) const { + if (!dstFormat || dstFormat != srcFormat) { + return false; + } + if ((dstSampleCount > 1 || srcSampleCount > 1) && (dstSampleCount != srcSampleCount)) { + return false; + } + if (areDstSrcSameObj) { + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.x(), dstPoint.y(), + srcRect.width(), srcRect.height()); + if (dstRect.intersect(srcRect)) { + return false; + } + } + return true; +} + +bool GrMtlCaps::canCopyAsResolve(GrSurface* dst, int dstSampleCount, + GrSurface* src, int srcSampleCount, + const SkIRect& srcRect, const SkIPoint& dstPoint) const { + if (dst == src) { + return false; + } + if (dst->backendFormat() != src->backendFormat()) { + return false; + } + if (dstSampleCount > 1 || srcSampleCount == 1 || !src->asRenderTarget()) { + return false; + } + + // TODO: Support copying subrectangles + if (dstPoint != SkIPoint::Make(0, 0)) { + return false; + } + if (srcRect != SkIRect::MakeXYWH(0, 0, src->width(), src->height())) { + return false; + } + + return true; +} + +bool GrMtlCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const { + int dstSampleCnt = 0; + int srcSampleCnt = 0; + if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) { + dstSampleCnt = rtProxy->numSamples(); + } + if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) { + srcSampleCnt = rtProxy->numSamples(); + } + SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy())); + SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy())); + + return this->canCopyAsBlit(GrBackendFormatAsMTLPixelFormat(dst->backendFormat()), dstSampleCnt, + GrBackendFormatAsMTLPixelFormat(src->backendFormat()), srcSampleCnt, + srcRect, dstPoint, dst == src); +} + +void GrMtlCaps::initGrCaps(const id device) { + // Max vertex attribs is the same on all devices + fMaxVertexAttributes = 31; + + // Metal does not support scissor + clear + fPerformPartialClearsAsDraws = true; + + // We always copy in/out of a transfer buffer so it's trivial to support row bytes. + fReadPixelsRowBytesSupport = true; + fWritePixelsRowBytesSupport = true; + + // RenderTarget and Texture size + if (this->isMac()) { + fMaxRenderTargetSize = 16384; + } else { + if (3 == fFamilyGroup) { + fMaxRenderTargetSize = 16384; + } else { + // Family group 1 and 2 support 8192 for version 2 and above, 4096 for v1 + if (1 == fVersion) { + fMaxRenderTargetSize = 4096; + } else { + fMaxRenderTargetSize = 8192; + } + } + } + fMaxPreferredRenderTargetSize = fMaxRenderTargetSize; + fMaxTextureSize = fMaxRenderTargetSize; + + // Init sample counts. All devices support 1 (i.e. 0 in skia). + fSampleCounts.push_back(1); + if (@available(iOS 9.0, *)) { + for (auto sampleCnt : {2, 4, 8}) { + if ([device supportsTextureSampleCount:sampleCnt]) { + fSampleCounts.push_back(sampleCnt); + } + } + } + + // Clamp to border is supported on Mac 10.12 and higher. It is not supported on iOS. + fClampToBorderSupport = false; +#ifdef SK_BUILD_FOR_MAC + if (@available(macOS 10.12, *)) { + fClampToBorderSupport = true; + } +#endif + + // Starting with the assumption that there isn't a reason to not map small buffers. + fBufferMapThreshold = 0; + + // Buffers are always fully mapped. + fMapBufferFlags = kCanMap_MapFlag | kAsyncRead_MapFlag; + + fOversizedStencilSupport = true; + + fMipMapSupport = true; // always available in Metal + fNPOTTextureTileSupport = true; // always available in Metal + + fReuseScratchTextures = true; // Assuming this okay + + fTransferBufferSupport = true; + + fTextureBarrierSupport = false; // Need to figure out if we can do this + + fSampleLocationsSupport = false; + fMultisampleDisableSupport = false; + + if (@available(macOS 10.11, iOS 9.0, *)) { + if (this->isMac() || 3 == fFamilyGroup) { + fInstanceAttribSupport = true; + } + } + + fMixedSamplesSupport = false; + fGpuTracingSupport = false; + + fFenceSyncSupport = true; + bool supportsMTLEvent = false; + if (@available(macOS 10.14, iOS 12.0, *)) { + supportsMTLEvent = true; + } + fSemaphoreSupport = supportsMTLEvent; + + fCrossContextTextureSupport = false; + fHalfFloatVertexAttributeSupport = true; +} + +static bool format_is_srgb(MTLPixelFormat format) { + switch (format) { + case MTLPixelFormatRGBA8Unorm_sRGB: + case MTLPixelFormatBGRA8Unorm_sRGB: + return true; + default: + return false; + } +} + +bool GrMtlCaps::isFormatSRGB(const GrBackendFormat& format) const { + return format_is_srgb(GrBackendFormatAsMTLPixelFormat(format)); +} + +bool GrMtlCaps::isFormatCompressed(const GrBackendFormat& format, + SkImage::CompressionType* compressionType) const { +#ifdef SK_BUILD_FOR_MAC + return false; +#else + SkImage::CompressionType dummyType; + SkImage::CompressionType* compressionTypePtr = compressionType ? compressionType : &dummyType; + + switch (GrBackendFormatAsMTLPixelFormat(format)) { + case MTLPixelFormatETC2_RGB8: + // ETC2 uses the same compression layout as ETC1 + *compressionTypePtr = SkImage::kETC1_CompressionType; + return true; + default: + return false; + } +#endif +} + +bool GrMtlCaps::isFormatTexturableAndUploadable(GrColorType ct, + const GrBackendFormat& format) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + + uint32_t ctFlags = this->getFormatInfo(mtlFormat).colorTypeFlags(ct); + return this->isFormatTexturable(mtlFormat) && + SkToBool(ctFlags & ColorTypeInfo::kUploadData_Flag); +} + +bool GrMtlCaps::isFormatTexturable(const GrBackendFormat& format) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + return this->isFormatTexturable(mtlFormat); +} + +bool GrMtlCaps::isFormatTexturable(MTLPixelFormat format) const { + const FormatInfo& formatInfo = this->getFormatInfo(format); + return SkToBool(FormatInfo::kTexturable_Flag && formatInfo.fFlags); +} + +bool GrMtlCaps::isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount) const { + if (!this->isFormatRenderable(format, sampleCount)) { + return false; + } + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + SkASSERT(mtlFormat != MTLPixelFormatInvalid); + const auto& info = this->getFormatInfo(mtlFormat); + if (!SkToBool(info.colorTypeFlags(ct) & ColorTypeInfo::kRenderable_Flag)) { + return false; + } + return true; +} + +bool GrMtlCaps::isFormatRenderable(const GrBackendFormat& format, int sampleCount) const { + return this->isFormatRenderable(GrBackendFormatAsMTLPixelFormat(format), sampleCount); +} + +bool GrMtlCaps::isFormatRenderable(MTLPixelFormat format, int sampleCount) const { + return sampleCount <= this->maxRenderTargetSampleCount(format); +} + +int GrMtlCaps::maxRenderTargetSampleCount(const GrBackendFormat& format) const { + return this->maxRenderTargetSampleCount(GrBackendFormatAsMTLPixelFormat(format)); +} + +int GrMtlCaps::maxRenderTargetSampleCount(MTLPixelFormat format) const { + const FormatInfo& formatInfo = this->getFormatInfo(format); + if (formatInfo.fFlags & FormatInfo::kMSAA_Flag) { + return fSampleCounts[fSampleCounts.count() - 1]; + } else if (formatInfo.fFlags & FormatInfo::kRenderable_Flag) { + return 1; + } + return 0; +} + +int GrMtlCaps::getRenderTargetSampleCount(int requestedCount, + const GrBackendFormat& format) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + + return this->getRenderTargetSampleCount(requestedCount, mtlFormat); +} + +int GrMtlCaps::getRenderTargetSampleCount(int requestedCount, MTLPixelFormat format) const { + requestedCount = SkTMax(requestedCount, 1); + const FormatInfo& formatInfo = this->getFormatInfo(format); + if (!(formatInfo.fFlags & FormatInfo::kRenderable_Flag)) { + return 0; + } + if (formatInfo.fFlags & FormatInfo::kMSAA_Flag) { + int count = fSampleCounts.count(); + for (int i = 0; i < count; ++i) { + if (fSampleCounts[i] >= requestedCount) { + return fSampleCounts[i]; + } + } + } + return 1 == requestedCount ? 1 : 0; +} + +size_t GrMtlCaps::bytesPerPixel(const GrBackendFormat& format) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + return this->bytesPerPixel(mtlFormat); +} + +size_t GrMtlCaps::bytesPerPixel(MTLPixelFormat format) const { + return this->getFormatInfo(format).fBytesPerPixel; +} + +void GrMtlCaps::initShaderCaps() { + GrShaderCaps* shaderCaps = fShaderCaps.get(); + + // Setting this true with the assumption that this cap will eventually mean we support varying + // precisions and not just via modifiers. + shaderCaps->fUsesPrecisionModifiers = true; + shaderCaps->fFlatInterpolationSupport = true; + // We haven't yet tested that using flat attributes perform well. + shaderCaps->fPreferFlatInterpolation = true; + + shaderCaps->fShaderDerivativeSupport = true; + shaderCaps->fGeometryShaderSupport = false; + + if (@available(macOS 10.12, iOS 11.0, *)) { + shaderCaps->fDualSourceBlendingSupport = true; + } else { + shaderCaps->fDualSourceBlendingSupport = false; + } + + // TODO: Re-enable this once skbug:8720 is fixed. Will also need to remove asserts in + // GrMtlPipelineStateBuilder which assert we aren't using this feature. +#if 0 + if (this->isIOS()) { + shaderCaps->fFBFetchSupport = true; + shaderCaps->fFBFetchNeedsCustomOutput = true; // ?? + shaderCaps->fFBFetchColorName = ""; // Somehow add [[color(0)]] to arguments to frag shader + } +#endif + shaderCaps->fDstReadInShaderSupport = shaderCaps->fFBFetchSupport; + + shaderCaps->fIntegerSupport = true; + shaderCaps->fVertexIDSupport = false; + + // Metal uses IEEE float and half floats so assuming those values here. + shaderCaps->fFloatIs32Bits = true; + shaderCaps->fHalfIs32Bits = false; + + shaderCaps->fMaxFragmentSamplers = 16; +} + +// These are all the valid MTLPixelFormats that we support in Skia. They are roughly ordered from +// most frequently used to least to improve look up times in arrays. +static constexpr MTLPixelFormat kMtlFormats[] = { + MTLPixelFormatRGBA8Unorm, + MTLPixelFormatR8Unorm, + MTLPixelFormatA8Unorm, + MTLPixelFormatBGRA8Unorm, +#ifdef SK_BUILD_FOR_IOS + MTLPixelFormatB5G6R5Unorm, +#endif + MTLPixelFormatRGBA16Float, + MTLPixelFormatR16Float, + MTLPixelFormatRG8Unorm, + MTLPixelFormatRGB10A2Unorm, +#ifdef SK_BUILD_FOR_IOS + MTLPixelFormatABGR4Unorm, +#endif + MTLPixelFormatRGBA8Unorm_sRGB, + MTLPixelFormatR16Unorm, + MTLPixelFormatRG16Unorm, +#ifdef SK_BUILD_FOR_IOS + MTLPixelFormatETC2_RGB8, +#endif + MTLPixelFormatRGBA16Unorm, + MTLPixelFormatRG16Float, + + MTLPixelFormatInvalid, +}; + +void GrMtlCaps::setColorType(GrColorType colorType, std::initializer_list formats) { +#ifdef SK_DEBUG + for (size_t i = 0; i < kNumMtlFormats; ++i) { + const auto& formatInfo = fFormatTable[i]; + for (int j = 0; j < formatInfo.fColorTypeInfoCount; ++j) { + const auto& ctInfo = formatInfo.fColorTypeInfos[j]; + if (ctInfo.fColorType == colorType) { + bool found = false; + for (auto it = formats.begin(); it != formats.end(); ++it) { + if (kMtlFormats[i] == *it) { + found = true; + } + } + SkASSERT(found); + } + } + } +#endif + int idx = static_cast(colorType); + for (auto it = formats.begin(); it != formats.end(); ++it) { + const auto& info = this->getFormatInfo(*it); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + if (info.fColorTypeInfos[i].fColorType == colorType) { + fColorTypeToFormatTable[idx] = *it; + return; + } + } + } +} + +size_t GrMtlCaps::GetFormatIndex(MTLPixelFormat pixelFormat) { + static_assert(SK_ARRAY_COUNT(kMtlFormats) == GrMtlCaps::kNumMtlFormats, + "Size of kMtlFormats array must match static value in header"); + for (size_t i = 0; i < GrMtlCaps::kNumMtlFormats; ++i) { + if (kMtlFormats[i] == pixelFormat) { + return i; + } + } + SK_ABORT("Invalid MTLPixelFormat"); +} + +void GrMtlCaps::initFormatTable() { + FormatInfo* info; + + // Format: R8Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatR8Unorm)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 1; + info->fColorTypeInfoCount = 2; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: R8Unorm, Surface: kAlpha_8 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + } + // Format: R8Unorm, Surface: kGray_8 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kGray_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle("rrr1"); + } + } + + // Format: A8Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatA8Unorm)]; + info->fFlags = FormatInfo::kTexturable_Flag; + info->fBytesPerPixel = 1; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: A8Unorm, Surface: kAlpha_8 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_8; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::AAAA(); + } + } + +#ifdef SK_BUILD_FOR_IOS + // Format: B5G6R5Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatB5G6R5Unorm)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 2; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: B5G6R5Unorm, Surface: kBGR_565 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kBGR_565; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: ABGR4Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatABGR4Unorm)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 2; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: ABGR4Unorm, Surface: kABGR_4444 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kABGR_4444; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } +#endif + + // Format: RGBA8Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA8Unorm)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 4; + info->fColorTypeInfoCount = 2; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA8Unorm, Surface: kRGBA_8888 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_8888; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + // Format: RGBA8Unorm, Surface: kRGB_888x + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGB_888x; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RGB1(); + } + } + + // Format: RG8Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRG8Unorm)]; + info->fFlags = FormatInfo::kTexturable_Flag; + info->fBytesPerPixel = 2; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RG8Unorm, Surface: kRG_88 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRG_88; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: BGRA8Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatBGRA8Unorm)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 4; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: BGRA8Unorm, Surface: kBGRA_8888 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kBGRA_8888; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: RGBA8Unorm_sRGB + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA8Unorm_sRGB)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 4; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA8Unorm_sRGB, Surface: kRGBA_8888_SRGB + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_8888_SRGB; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: RGB10A2Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGB10A2Unorm)]; + if (this->isMac() || fFamilyGroup >= 3) { + info->fFlags = FormatInfo::kAllFlags; + } else { + info->fFlags = FormatInfo::kTexturable_Flag; + } + info->fBytesPerPixel = 4; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGB10A2Unorm, Surface: kRGBA_1010102 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_1010102; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: R16Float + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatR16Float)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 2; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: R16Float, Surface: kAlpha_F16 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_F16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + } + } + + // Format: RGBA16Float + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA16Float)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 8; + info->fColorTypeInfoCount = 2; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA16Float, Surface: kRGBA_F16 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_F16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + // Format: RGBA16Float, Surface: kRGBA_F16_Clamped + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_F16_Clamped; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: R16Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatR16Unorm)]; + if (this->isMac()) { + info->fFlags = FormatInfo::kAllFlags; + } else { + info->fFlags = FormatInfo::kTexturable_Flag | FormatInfo::kRenderable_Flag; + } + info->fBytesPerPixel = 2; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: R16Unorm, Surface: kAlpha_16 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kAlpha_16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + } + } + + // Format: RG16Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRG16Unorm)]; + if (this->isMac()) { + info->fFlags = FormatInfo::kAllFlags; + } else { + info->fFlags = FormatInfo::kTexturable_Flag | FormatInfo::kRenderable_Flag; + } + info->fBytesPerPixel = 4; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RG16Unorm, Surface: kRG_1616 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRG_1616; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + +#ifdef SK_BUILD_FOR_IOS + // ETC2_RGB8 + info = &fFormatTable[GetFormatIndex(MTLPixelFormatETC2_RGB8)]; + info->fFlags = FormatInfo::kTexturable_Flag; + // NO supported colorTypes +#endif + + // Format: RGBA16Unorm + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA16Unorm)]; + if (this->isMac()) { + info->fFlags = FormatInfo::kAllFlags; + } else { + info->fFlags = FormatInfo::kTexturable_Flag | FormatInfo::kRenderable_Flag; + } + info->fBytesPerPixel = 8; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RGBA16Unorm, Surface: kRGBA_16161616 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRGBA_16161616; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + // Format: RG16Float + { + info = &fFormatTable[GetFormatIndex(MTLPixelFormatRG16Float)]; + info->fFlags = FormatInfo::kAllFlags; + info->fBytesPerPixel = 4; + info->fColorTypeInfoCount = 1; + info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: RG16Float, Surface: kRG_F16 + { + auto& ctInfo = info->fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = GrColorType::kRG_F16; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Map GrColorTypes (used for creating GrSurfaces) to MTLPixelFormats. The order in which the + // formats are passed into the setColorType function indicates the priority in selecting which + // format we use for a given GrcolorType. + + std::fill_n(fColorTypeToFormatTable, kGrColorTypeCnt, MTLPixelFormatInvalid); + + this->setColorType(GrColorType::kAlpha_8, { MTLPixelFormatR8Unorm, + MTLPixelFormatA8Unorm }); +#ifdef SK_BUILD_FOR_IOS + this->setColorType(GrColorType::kBGR_565, { MTLPixelFormatB5G6R5Unorm }); + this->setColorType(GrColorType::kABGR_4444, { MTLPixelFormatABGR4Unorm }); +#endif + this->setColorType(GrColorType::kRGBA_8888, { MTLPixelFormatRGBA8Unorm }); + this->setColorType(GrColorType::kRGBA_8888_SRGB, { MTLPixelFormatRGBA8Unorm_sRGB }); + this->setColorType(GrColorType::kRGB_888x, { MTLPixelFormatRGBA8Unorm }); + this->setColorType(GrColorType::kRG_88, { MTLPixelFormatRG8Unorm }); + this->setColorType(GrColorType::kBGRA_8888, { MTLPixelFormatBGRA8Unorm }); + this->setColorType(GrColorType::kRGBA_1010102, { MTLPixelFormatRGB10A2Unorm }); + this->setColorType(GrColorType::kGray_8, { MTLPixelFormatR8Unorm }); + this->setColorType(GrColorType::kAlpha_F16, { MTLPixelFormatR16Float }); + this->setColorType(GrColorType::kRGBA_F16, { MTLPixelFormatRGBA16Float }); + this->setColorType(GrColorType::kRGBA_F16_Clamped, { MTLPixelFormatRGBA16Float }); + this->setColorType(GrColorType::kAlpha_16, { MTLPixelFormatR16Unorm }); + this->setColorType(GrColorType::kRG_1616, { MTLPixelFormatRG16Unorm }); + this->setColorType(GrColorType::kRGBA_16161616, { MTLPixelFormatRGBA16Unorm }); + this->setColorType(GrColorType::kRG_F16, { MTLPixelFormatRG16Float }); +} + +void GrMtlCaps::initStencilFormat(id physDev) { + fPreferredStencilFormat = StencilFormat{ MTLPixelFormatStencil8, 8, 8, true }; +} + +bool GrMtlCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const { + if (auto rt = surface->asRenderTarget()) { + return rt->numSamples() <= 1 && SkToBool(surface->asTexture()); + } + return true; +} + +static constexpr GrPixelConfig validate_sized_format(GrMTLPixelFormat grFormat, GrColorType ct) { + MTLPixelFormat format = static_cast(grFormat); + switch (ct) { + case GrColorType::kUnknown: + return kUnknown_GrPixelConfig; + case GrColorType::kAlpha_8: + if (MTLPixelFormatA8Unorm == format) { + return kAlpha_8_as_Alpha_GrPixelConfig; + } else if (MTLPixelFormatR8Unorm == format) { + return kAlpha_8_as_Red_GrPixelConfig; + } + break; +#ifdef SK_BUILD_FOR_MAC + case GrColorType::kBGR_565: + case GrColorType::kABGR_4444: + return kUnknown_GrPixelConfig; +#else + case GrColorType::kBGR_565: + if (MTLPixelFormatB5G6R5Unorm == format) { + return kRGB_565_GrPixelConfig; + } + break; + case GrColorType::kABGR_4444: + if (MTLPixelFormatABGR4Unorm == format) { + return kRGBA_4444_GrPixelConfig; + } + break; +#endif + case GrColorType::kRGBA_8888: + if (MTLPixelFormatRGBA8Unorm == format) { + return kRGBA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGBA_8888_SRGB: + if (MTLPixelFormatRGBA8Unorm_sRGB == format) { + return kSRGBA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGB_888x: + if (MTLPixelFormatRGBA8Unorm == format) { + return kRGB_888X_GrPixelConfig; + } +#ifdef SK_BUILD_FOR_IOS + else if (MTLPixelFormatETC2_RGB8 == format) { + return kRGB_ETC1_GrPixelConfig; + } +#endif + break; + case GrColorType::kRG_88: + if (MTLPixelFormatRG8Unorm == format) { + return kRG_88_GrPixelConfig; + } + break; + case GrColorType::kBGRA_8888: + if (MTLPixelFormatBGRA8Unorm == format) { + return kBGRA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGBA_1010102: + if (MTLPixelFormatRGB10A2Unorm == format) { + return kRGBA_1010102_GrPixelConfig; + } + break; + case GrColorType::kGray_8: + if (MTLPixelFormatR8Unorm == format) { + return kGray_8_as_Red_GrPixelConfig; + } + break; + case GrColorType::kAlpha_F16: + if (MTLPixelFormatR16Float == format) { + return kAlpha_half_as_Red_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F16: + if (MTLPixelFormatRGBA16Float == format) { + return kRGBA_half_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F16_Clamped: + if (MTLPixelFormatRGBA16Float == format) { + return kRGBA_half_Clamped_GrPixelConfig; + } + break; + case GrColorType::kAlpha_16: + if (MTLPixelFormatR16Unorm == format) { + return kAlpha_16_GrPixelConfig; + } + break; + case GrColorType::kRG_1616: + if (MTLPixelFormatRG16Unorm == format) { + return kRG_1616_GrPixelConfig; + } + break; + case GrColorType::kRGBA_16161616: + if (MTLPixelFormatRGBA16Unorm == format) { + return kRGBA_16161616_GrPixelConfig; + } + break; + case GrColorType::kRG_F16: + if (MTLPixelFormatRG16Float == format) { + return kRG_half_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F32: + case GrColorType::kAlpha_8xxx: + case GrColorType::kAlpha_F32xxx: + case GrColorType::kGray_8xxx: + return kUnknown_GrPixelConfig; + } + SkUNREACHABLE; +} + +bool GrMtlCaps::onAreColorTypeAndFormatCompatible(GrColorType ct, + const GrBackendFormat& format) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + const auto& info = this->getFormatInfo(mtlFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + if (info.fColorTypeInfos[i].fColorType == ct) { + return true; + } + } + return false; +} + +GrPixelConfig GrMtlCaps::onGetConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType ct) const { + return validate_sized_format(GrBackendFormatAsMTLPixelFormat(format), ct); +} + +GrColorType GrMtlCaps::getYUVAColorTypeFromBackendFormat(const GrBackendFormat& format, + bool isAlphaChannel) const { + switch (GrBackendFormatAsMTLPixelFormat(format)) { + case MTLPixelFormatA8Unorm: // fall through + case MTLPixelFormatR8Unorm: return isAlphaChannel ? GrColorType::kAlpha_8 + : GrColorType::kGray_8; + case MTLPixelFormatRG8Unorm: return GrColorType::kRG_88; + case MTLPixelFormatRGBA8Unorm: return GrColorType::kRGBA_8888; + case MTLPixelFormatBGRA8Unorm: return GrColorType::kBGRA_8888; + case MTLPixelFormatRGB10A2Unorm: return GrColorType::kRGBA_1010102; + case MTLPixelFormatR16Unorm: return GrColorType::kAlpha_16; + case MTLPixelFormatR16Float: return GrColorType::kAlpha_F16; + case MTLPixelFormatRG16Unorm: return GrColorType::kRG_1616; + case MTLPixelFormatRGBA16Unorm: return GrColorType::kRGBA_16161616; + case MTLPixelFormatRG16Float: return GrColorType::kRG_F16; + default: return GrColorType::kUnknown; + } +} + +GrBackendFormat GrMtlCaps::onGetDefaultBackendFormat(GrColorType ct, + GrRenderable renderable) const { + MTLPixelFormat format = this->getFormatFromColorType(ct); + if (!format) { + return GrBackendFormat(); + } + return GrBackendFormat::MakeMtl(format); +} + +GrBackendFormat GrMtlCaps::getBackendFormatFromCompressionType( + SkImage::CompressionType compressionType) const { + switch (compressionType) { + case SkImage::kETC1_CompressionType: +#ifdef SK_BUILD_FOR_MAC + return {}; +#else + return GrBackendFormat::MakeMtl(MTLPixelFormatETC2_RGB8); +#endif + } + SK_ABORT("Invalid compression type"); +} + +GrSwizzle GrMtlCaps::getTextureSwizzle(const GrBackendFormat& format, GrColorType colorType) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + SkASSERT(mtlFormat != MTLPixelFormatInvalid); + const auto& info = this->getFormatInfo(mtlFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == colorType) { + return ctInfo.fTextureSwizzle; + } + } + return GrSwizzle::RGBA(); +} +GrSwizzle GrMtlCaps::getOutputSwizzle(const GrBackendFormat& format, GrColorType colorType) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + SkASSERT(mtlFormat != MTLPixelFormatInvalid); + const auto& info = this->getFormatInfo(mtlFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == colorType) { + return ctInfo.fOutputSwizzle; + } + } + return GrSwizzle::RGBA(); +} + +GrCaps::SupportedWrite GrMtlCaps::supportedWritePixelsColorType( + GrColorType surfaceColorType, const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const { + // Metal requires the destination offset for copyFromTexture to be a multiple of the textures + // pixels size. + size_t offsetAlignment = GrColorTypeBytesPerPixel(surfaceColorType); + + const auto& info = this->getFormatInfo(GrBackendFormatAsMTLPixelFormat(surfaceFormat)); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == surfaceColorType) { + return {surfaceColorType, offsetAlignment}; + } + } + return {GrColorType::kUnknown, 0}; +} + +GrCaps::SupportedRead GrMtlCaps::onSupportedReadPixelsColorType( + GrColorType srcColorType, const GrBackendFormat& srcBackendFormat, + GrColorType dstColorType) const { + MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(srcBackendFormat); + + // Metal requires the destination offset for copyFromTexture to be a multiple of the textures + // pixels size. + size_t offsetAlignment = GrColorTypeBytesPerPixel(srcColorType); + + const auto& info = this->getFormatInfo(mtlFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == srcColorType) { + return {srcColorType, offsetAlignment}; + } + } + return {GrColorType::kUnknown, 0}; +} + +#if GR_TEST_UTILS +std::vector GrMtlCaps::getTestingCombinations() const { + std::vector combos = { + { GrColorType::kAlpha_8, GrBackendFormat::MakeMtl(MTLPixelFormatA8Unorm) }, + { GrColorType::kAlpha_8, GrBackendFormat::MakeMtl(MTLPixelFormatR8Unorm) }, +#ifdef SK_BUILD_FOR_IOS + { GrColorType::kBGR_565, GrBackendFormat::MakeMtl(MTLPixelFormatB5G6R5Unorm) }, + { GrColorType::kABGR_4444, GrBackendFormat::MakeMtl(MTLPixelFormatABGR4Unorm) }, +#endif + { GrColorType::kRGBA_8888, GrBackendFormat::MakeMtl(MTLPixelFormatRGBA8Unorm) }, + { GrColorType::kRGBA_8888_SRGB, GrBackendFormat::MakeMtl(MTLPixelFormatRGBA8Unorm_sRGB) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeMtl(MTLPixelFormatRGBA8Unorm) }, +#ifdef SK_BUILD_FOR_IOS + { GrColorType::kRGB_888x, GrBackendFormat::MakeMtl(MTLPixelFormatETC2_RGB8) }, +#endif + { GrColorType::kRG_88, GrBackendFormat::MakeMtl(MTLPixelFormatRG8Unorm) }, + { GrColorType::kBGRA_8888, GrBackendFormat::MakeMtl(MTLPixelFormatBGRA8Unorm) }, + { GrColorType::kRGBA_1010102, GrBackendFormat::MakeMtl(MTLPixelFormatRGB10A2Unorm) }, + { GrColorType::kGray_8, GrBackendFormat::MakeMtl(MTLPixelFormatR8Unorm) }, + { GrColorType::kAlpha_F16, GrBackendFormat::MakeMtl(MTLPixelFormatR16Float) }, + { GrColorType::kRGBA_F16, GrBackendFormat::MakeMtl(MTLPixelFormatRGBA16Float) }, + { GrColorType::kRGBA_F16_Clamped, GrBackendFormat::MakeMtl(MTLPixelFormatRGBA16Float) }, + { GrColorType::kAlpha_16, GrBackendFormat::MakeMtl(MTLPixelFormatR16Unorm) }, + { GrColorType::kRG_1616, GrBackendFormat::MakeMtl(MTLPixelFormatRG16Unorm) }, + { GrColorType::kRGBA_16161616, GrBackendFormat::MakeMtl(MTLPixelFormatRGBA16Unorm) }, + { GrColorType::kRG_F16, GrBackendFormat::MakeMtl(MTLPixelFormatRG16Float) }, + }; + + return combos; +} +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.h b/gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.h new file mode 100644 index 0000000000..6626c7d266 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.h @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlCommandBuffer_DEFINED +#define GrMtlCommandBuffer_DEFINED + +#import + +#include "include/core/SkRefCnt.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +class GrMtlGpu; +class GrMtlPipelineState; +class GrMtlOpsRenderPass; + +class GrMtlCommandBuffer { +public: + static GrMtlCommandBuffer* Create(id queue); + ~GrMtlCommandBuffer(); + + void commit(bool waitUntilCompleted); + + id getBlitCommandEncoder(); + id getRenderCommandEncoder(MTLRenderPassDescriptor*, + const GrMtlPipelineState*, + GrMtlOpsRenderPass* opsRenderPass); + + void addCompletedHandler(MTLCommandBufferHandler block) { + [fCmdBuffer addCompletedHandler:block]; + } + + void encodeSignalEvent(id, uint64_t value) API_AVAILABLE(macos(10.14), ios(12.0)); + void encodeWaitForEvent(id, uint64_t value) API_AVAILABLE(macos(10.14), ios(12.0)); + +private: + GrMtlCommandBuffer(id cmdBuffer) + : fCmdBuffer(cmdBuffer) + , fPreviousRenderPassDescriptor(nil) {} + + void endAllEncoding(); + + id fCmdBuffer; + id fActiveBlitCommandEncoder; + id fActiveRenderCommandEncoder; + MTLRenderPassDescriptor* fPreviousRenderPassDescriptor; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.mm new file mode 100644 index 0000000000..85b53281f5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlCommandBuffer.mm @@ -0,0 +1,138 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlCommandBuffer.h" + +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlOpsRenderPass.h" +#include "src/gpu/mtl/GrMtlPipelineState.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlCommandBuffer* GrMtlCommandBuffer::Create(id queue) { + id mtlCommandBuffer; + mtlCommandBuffer = [queue commandBuffer]; + if (nil == mtlCommandBuffer) { + return nullptr; + } + + mtlCommandBuffer.label = @"GrMtlCommandBuffer::Create"; + + return new GrMtlCommandBuffer(mtlCommandBuffer); +} + +GrMtlCommandBuffer::~GrMtlCommandBuffer() { + this->endAllEncoding(); + fCmdBuffer = nil; +} + +id GrMtlCommandBuffer::getBlitCommandEncoder() { + if (nil != fActiveRenderCommandEncoder) { + [fActiveRenderCommandEncoder endEncoding]; + fActiveRenderCommandEncoder = nil; + } + + if (nil == fActiveBlitCommandEncoder) { + fActiveBlitCommandEncoder = [fCmdBuffer blitCommandEncoder]; + } + fPreviousRenderPassDescriptor = nil; + + return fActiveBlitCommandEncoder; +} + +static bool compatible(const MTLRenderPassAttachmentDescriptor* first, + const MTLRenderPassAttachmentDescriptor* second, + const GrMtlPipelineState* pipelineState) { + // Check to see if the previous descriptor is compatible with the new one. + // They are compatible if: + // * they share the same rendertargets + // * the first's store actions are either Store or DontCare + // * the second's load actions are either Load or DontCare + // * the second doesn't sample from any rendertargets in the first + bool renderTargetsMatch = (first.texture == second.texture); + bool storeActionsValid = first.storeAction == MTLStoreActionStore || + first.storeAction == MTLStoreActionDontCare; + bool loadActionsValid = second.loadAction == MTLLoadActionLoad || + second.loadAction == MTLLoadActionDontCare; + bool secondDoesntSampleFirst = (!pipelineState || + pipelineState->doesntSampleAttachment(first)) && + second.storeAction != MTLStoreActionMultisampleResolve; + + return renderTargetsMatch && + (nil == first.texture || + (storeActionsValid && loadActionsValid && secondDoesntSampleFirst)); +} + +id GrMtlCommandBuffer::getRenderCommandEncoder( + MTLRenderPassDescriptor* descriptor, const GrMtlPipelineState* pipelineState, + GrMtlOpsRenderPass* opsRenderPass) { + if (nil != fPreviousRenderPassDescriptor) { + if (compatible(fPreviousRenderPassDescriptor.colorAttachments[0], + descriptor.colorAttachments[0], pipelineState) && + compatible(fPreviousRenderPassDescriptor.stencilAttachment, + descriptor.stencilAttachment, pipelineState)) { + return fActiveRenderCommandEncoder; + } + } + + this->endAllEncoding(); + fActiveRenderCommandEncoder = [fCmdBuffer renderCommandEncoderWithDescriptor:descriptor]; + if (opsRenderPass) { + opsRenderPass->initRenderState(fActiveRenderCommandEncoder); + } + fPreviousRenderPassDescriptor = descriptor; + + return fActiveRenderCommandEncoder; +} + +void GrMtlCommandBuffer::commit(bool waitUntilCompleted) { + this->endAllEncoding(); + [fCmdBuffer commit]; + if (waitUntilCompleted) { + [fCmdBuffer waitUntilCompleted]; + } + + if (MTLCommandBufferStatusError == fCmdBuffer.status) { + NSString* description = fCmdBuffer.error.localizedDescription; + const char* errorString = [description UTF8String]; + SkDebugf("Error submitting command buffer: %s\n", errorString); + } + + fCmdBuffer = nil; +} + +void GrMtlCommandBuffer::endAllEncoding() { + if (nil != fActiveRenderCommandEncoder) { + [fActiveRenderCommandEncoder endEncoding]; + fActiveRenderCommandEncoder = nil; + fPreviousRenderPassDescriptor = nil; + } + if (nil != fActiveBlitCommandEncoder) { + [fActiveBlitCommandEncoder endEncoding]; + fActiveBlitCommandEncoder = nil; + } +} + +void GrMtlCommandBuffer::encodeSignalEvent(id event, uint64_t eventValue) { + SkASSERT(fCmdBuffer); + this->endAllEncoding(); // ensure we don't have any active command encoders + if (@available(macOS 10.14, iOS 12.0, *)) { + [fCmdBuffer encodeSignalEvent:event value:eventValue]; + } +} + +void GrMtlCommandBuffer::encodeWaitForEvent(id event, uint64_t eventValue) { + SkASSERT(fCmdBuffer); + this->endAllEncoding(); // ensure we don't have any active command encoders + // TODO: not sure if needed but probably + if (@available(macOS 10.14, iOS 12.0, *)) { + [fCmdBuffer encodeWaitForEvent:event value:eventValue]; + } +} + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlCppUtil.h b/gfx/skia/skia/src/gpu/mtl/GrMtlCppUtil.h new file mode 100644 index 0000000000..47c6eb50c1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlCppUtil.h @@ -0,0 +1,22 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlCppUtil_DEFINED +#define GrMtlCppUtil_DEFINED + +#include "include/gpu/mtl/GrMtlTypes.h" + +// Utilities that can be used from cpp files (rather than .mm). + +GrMTLPixelFormat GrGetMTLPixelFormatFromMtlTextureInfo(const GrMtlTextureInfo&); + +#if GR_TEST_UTILS +const char* GrMtlFormatToStr(GrMTLPixelFormat mtlFormat); +bool GrMtlFormatIsBGRA(GrMTLPixelFormat mtlFormat); +#endif + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.h b/gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.h new file mode 100644 index 0000000000..0f61af877e --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.h @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlDepthStencil_DEFINED +#define GrMtlDepthStencil_DEFINED + +#import + +#include "include/gpu/GrTypes.h" +#include "src/core/SkOpts.h" +#include + +class GrMtlGpu; +class GrStencilSettings; + +// A wrapper for a MTLDepthStencilState object with caching support. +class GrMtlDepthStencil : public SkRefCnt { +public: + static GrMtlDepthStencil* Create(const GrMtlGpu*, const GrStencilSettings&, GrSurfaceOrigin); + + ~GrMtlDepthStencil() { fMtlDepthStencilState = nil; } + + id mtlDepthStencil() const { return fMtlDepthStencilState; } + + struct Key { + struct Face { + uint32_t fReadMask; + uint32_t fWriteMask; + uint32_t fOps; + }; + Face fFront; + Face fBack; + + bool operator==(const Key& that) const { + return this->fFront.fReadMask == that.fFront.fReadMask && + this->fFront.fWriteMask == that.fFront.fWriteMask && + this->fFront.fOps == that.fFront.fOps && + this->fBack.fReadMask == that.fBack.fReadMask && + this->fBack.fWriteMask == that.fBack.fWriteMask && + this->fBack.fOps == that.fBack.fOps; + } + }; + + // Helpers for hashing GrMtlSampler + static Key GenerateKey(const GrStencilSettings&, GrSurfaceOrigin); + + static const Key& GetKey(const GrMtlDepthStencil& depthStencil) { return depthStencil.fKey; } + static uint32_t Hash(const Key& key) { + return SkOpts::hash(reinterpret_cast(&key), sizeof(Key)); + } + +private: + GrMtlDepthStencil(id mtlDepthStencilState, Key key) + : fMtlDepthStencilState(mtlDepthStencilState) + , fKey(key) {} + + id fMtlDepthStencilState; + Key fKey; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.mm new file mode 100644 index 0000000000..7434772531 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlDepthStencil.mm @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/mtl/GrMtlDepthStencil.h" +#include "src/gpu/mtl/GrMtlGpu.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +MTLStencilOperation skia_stencil_op_to_mtl(GrStencilOp op) { + switch (op) { + case GrStencilOp::kKeep: + return MTLStencilOperationKeep; + case GrStencilOp::kZero: + return MTLStencilOperationZero; + case GrStencilOp::kReplace: + return MTLStencilOperationReplace; + case GrStencilOp::kInvert: + return MTLStencilOperationInvert; + case GrStencilOp::kIncWrap: + return MTLStencilOperationIncrementWrap; + case GrStencilOp::kDecWrap: + return MTLStencilOperationDecrementWrap; + case GrStencilOp::kIncClamp: + return MTLStencilOperationIncrementClamp; + case GrStencilOp::kDecClamp: + return MTLStencilOperationDecrementClamp; + } +} + +MTLStencilDescriptor* skia_stencil_to_mtl(GrStencilSettings::Face face) { + MTLStencilDescriptor* result = [[MTLStencilDescriptor alloc] init]; + switch (face.fTest) { + case GrStencilTest::kAlways: + result.stencilCompareFunction = MTLCompareFunctionAlways; + break; + case GrStencilTest::kNever: + result.stencilCompareFunction = MTLCompareFunctionNever; + break; + case GrStencilTest::kGreater: + result.stencilCompareFunction = MTLCompareFunctionGreater; + break; + case GrStencilTest::kGEqual: + result.stencilCompareFunction = MTLCompareFunctionGreaterEqual; + break; + case GrStencilTest::kLess: + result.stencilCompareFunction = MTLCompareFunctionLess; + break; + case GrStencilTest::kLEqual: + result.stencilCompareFunction = MTLCompareFunctionLessEqual; + break; + case GrStencilTest::kEqual: + result.stencilCompareFunction = MTLCompareFunctionEqual; + break; + case GrStencilTest::kNotEqual: + result.stencilCompareFunction = MTLCompareFunctionNotEqual; + break; + } + result.readMask = face.fTestMask; + result.writeMask = face.fWriteMask; + result.depthStencilPassOperation = skia_stencil_op_to_mtl(face.fPassOp); + result.stencilFailureOperation = skia_stencil_op_to_mtl(face.fFailOp); + return result; +} + +GrMtlDepthStencil* GrMtlDepthStencil::Create(const GrMtlGpu* gpu, + const GrStencilSettings& stencil, + GrSurfaceOrigin origin) { + MTLDepthStencilDescriptor* desc = [[MTLDepthStencilDescriptor alloc] init]; + if (!stencil.isDisabled()) { + if (stencil.isTwoSided()) { + desc.frontFaceStencil = skia_stencil_to_mtl(stencil.front(origin)); + desc.backFaceStencil = skia_stencil_to_mtl(stencil.back(origin)); + } + else { + desc.frontFaceStencil = skia_stencil_to_mtl(stencil.frontAndBack()); + desc.backFaceStencil = desc.frontFaceStencil; + } + } + + return new GrMtlDepthStencil([gpu->device() newDepthStencilStateWithDescriptor: desc], + GenerateKey(stencil, origin)); +} + +void skia_stencil_to_key(GrStencilSettings::Face face, GrMtlDepthStencil::Key::Face* faceKey) { + const int kPassOpShift = 3; + const int kFailOpShift = 6; + + faceKey->fReadMask = face.fTestMask; + faceKey->fWriteMask = face.fWriteMask; + + SkASSERT(static_cast(face.fTest) <= 7); + faceKey->fOps = static_cast(face.fTest); + + SkASSERT(static_cast(face.fPassOp) <= 7); + faceKey->fOps |= (static_cast(face.fPassOp) << kPassOpShift); + + SkASSERT(static_cast(face.fFailOp) <= 7); + faceKey->fOps |= (static_cast(face.fFailOp) << kFailOpShift); +} + +GrMtlDepthStencil::Key GrMtlDepthStencil::GenerateKey(const GrStencilSettings& stencil, + GrSurfaceOrigin origin) { + Key depthStencilKey; + + if (stencil.isDisabled()) { + memset(&depthStencilKey, 0, sizeof(Key)); + } else { + if (stencil.isTwoSided()) { + skia_stencil_to_key(stencil.front(origin), &depthStencilKey.fFront); + skia_stencil_to_key(stencil.back(origin), &depthStencilKey.fBack); + } + else { + skia_stencil_to_key(stencil.frontAndBack(), &depthStencilKey.fFront); + memcpy(&depthStencilKey.fBack, &depthStencilKey.fFront, sizeof(Key::Face)); + } + } + + return depthStencilKey; +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlGpu.h b/gfx/skia/skia/src/gpu/mtl/GrMtlGpu.h new file mode 100644 index 0000000000..7fd9134afd --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlGpu.h @@ -0,0 +1,238 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlGpu_DEFINED +#define GrMtlGpu_DEFINED + +#include +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrSemaphore.h" + +#include "src/gpu/mtl/GrMtlCaps.h" +#include "src/gpu/mtl/GrMtlResourceProvider.h" +#include "src/gpu/mtl/GrMtlStencilAttachment.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#import + +class GrMtlOpsRenderPass; +class GrMtlTexture; +class GrSemaphore; +struct GrMtlBackendContext; +class GrMtlCommandBuffer; + +namespace SkSL { + class Compiler; +} + +class GrMtlGpu : public GrGpu { +public: + static sk_sp Make(GrContext* context, const GrContextOptions& options, + id device, id queue); + ~GrMtlGpu() override; + + void disconnect(DisconnectType) override; + + const GrMtlCaps& mtlCaps() const { return *fMtlCaps.get(); } + + id device() const { return fDevice; } + + GrMtlResourceProvider& resourceProvider() { return fResourceProvider; } + + GrMtlCommandBuffer* commandBuffer(); + + enum SyncQueue { + kForce_SyncQueue, + kSkip_SyncQueue + }; + + // Commits the current command buffer to the queue and then creates a new command buffer. If + // sync is set to kForce_SyncQueue, the function will wait for all work in the committed + // command buffer to finish before returning. + void submitCommandBuffer(SyncQueue sync); + + void deleteBackendTexture(const GrBackendTexture&) override; + +#if GR_TEST_UTILS + bool isTestingOnlyBackendTexture(const GrBackendTexture&) const override; + + GrBackendRenderTarget createTestingOnlyBackendRenderTarget(int w, int h, GrColorType) override; + void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) override; + + void testingOnly_flushGpuAndSync() override; +#endif + + void copySurfaceAsResolve(GrSurface* dst, GrSurface* src); + + void copySurfaceAsBlit(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + bool onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) override; + + GrOpsRenderPass* getOpsRenderPass( + GrRenderTarget*, GrSurfaceOrigin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) override; + + SkSL::Compiler* shaderCompiler() const { return fCompiler.get(); } + + void submit(GrOpsRenderPass* renderPass) override; + + GrFence SK_WARN_UNUSED_RESULT insertFence() override; + bool waitFence(GrFence, uint64_t) override; + void deleteFence(GrFence) const override; + + sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned) override; + sk_sp wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) override; + void insertSemaphore(sk_sp semaphore) override; + void waitSemaphore(sk_sp semaphore) override; + void checkFinishProcs() override; + sk_sp prepareTextureForCrossContextUsage(GrTexture*) override { return nullptr; } + + // When the Metal backend actually uses indirect command buffers, this function will actually do + // what it says. For now, every command is encoded directly into the primary command buffer, so + // this function is pretty useless, except for indicating that a render target has been drawn + // to. + void submitIndirectCommandBuffer(GrSurface* surface, GrSurfaceOrigin origin, + const SkIRect* bounds) { + this->didWriteToSurface(surface, origin, bounds); + } + +private: + GrMtlGpu(GrContext* context, const GrContextOptions& options, + id device, id queue, MTLFeatureSet featureSet); + + void destroyResources(); + + void onResetContext(uint32_t resetBits) override {} + + void querySampleLocations(GrRenderTarget*, SkTArray*) override { + SkASSERT(!this->caps()->sampleLocationsSupport()); + SK_ABORT("Sample locations not yet implemented for Metal."); + } + + void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {} + + GrBackendTexture onCreateBackendTexture(int w, int h, const GrBackendFormat&, + GrMipMapped, GrRenderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected) override; + + sk_sp onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected, + int mipLevelCount, + uint32_t levelClearMask) override; + sk_sp onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, + const void* data) override; + + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrColorType, + GrWrapOwnership, GrWrapCacheable, GrIOType) override; + + sk_sp onWrapRenderableBackendTexture(const GrBackendTexture&, int sampleCnt, + GrColorType, GrWrapOwnership, + GrWrapCacheable) override; + + sk_sp onWrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType) override; + + sk_sp onWrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, + GrColorType) override; + + sk_sp onCreateBuffer(size_t, GrGpuBufferType, GrAccessPattern, + const void*) override; + + bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, void* buffer, + size_t rowBytes) override; + + bool onWritePixels(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + const GrMipLevel[], int mipLevelCount, + bool prepForTexSampling) override; + + bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, GrGpuBuffer*, + size_t offset, size_t rowBytes) override; + bool onTransferPixelsFrom(GrSurface*, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer*, size_t offset) override; + + bool onRegenerateMipMapLevels(GrTexture*) override; + + void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin resolveOrigin, ForExternalIO) override; + + void resolveTexture(id colorTexture, id resolveTexture); + + void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo& info, const GrPrepareForExternalIORequests&) override; + + // Function that uploads data onto textures with private storage mode (GPU access only). + bool uploadToTexture(GrMtlTexture* tex, int left, int top, int width, int height, + GrColorType dataColorType, const GrMipLevel texels[], int mipLevels); + // Function that fills texture levels with transparent black based on levelMask. + bool clearTexture(GrMtlTexture*, GrColorType, uint32_t levelMask); + bool readOrTransferPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType dstColorType, id transferBuffer, size_t offset, + size_t imageBytes, size_t rowBytes); + + GrStencilAttachment* createStencilAttachmentForRenderTarget( + const GrRenderTarget*, int width, int height, int numStencilSamples) override; + + bool createMtlTextureForBackendSurface(MTLPixelFormat, + int w, int h, bool texturable, + bool renderable, GrMipMapped, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrMtlTextureInfo*); + +#if GR_TEST_UTILS + void testingOnly_startCapture() override; + void testingOnly_endCapture() override; +#endif + + sk_sp fMtlCaps; + + id fDevice; + id fQueue; + + GrMtlCommandBuffer* fCmdBuffer; + + std::unique_ptr fCompiler; + + GrMtlResourceProvider fResourceProvider; + + // For FenceSync + id fSharedEvent API_AVAILABLE(macos(10.14), ios(12.0)); + MTLSharedEventListener* fSharedEventListener API_AVAILABLE(macos(10.14), ios(12.0)); + uint64_t fLatestEvent; + + bool fDisconnected; + + struct FinishCallback { + GrGpuFinishedProc fCallback; + GrGpuFinishedContext fContext; + GrFence fFence; + }; + std::list fFinishCallbacks; + + typedef GrGpu INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlGpu.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlGpu.mm new file mode 100644 index 0000000000..8e102306ae --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlGpu.mm @@ -0,0 +1,1375 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlGpu.h" + +#include "src/core/SkConvertPixels.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/mtl/GrMtlBuffer.h" +#include "src/gpu/mtl/GrMtlCommandBuffer.h" +#include "src/gpu/mtl/GrMtlOpsRenderPass.h" +#include "src/gpu/mtl/GrMtlSemaphore.h" +#include "src/gpu/mtl/GrMtlTexture.h" +#include "src/gpu/mtl/GrMtlTextureRenderTarget.h" +#include "src/gpu/mtl/GrMtlUtil.h" +#include "src/sksl/SkSLCompiler.h" + +#import + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +static bool get_feature_set(id device, MTLFeatureSet* featureSet) { + // Mac OSX +#ifdef SK_BUILD_FOR_MAC + if (@available(macOS 10.12, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_OSX_GPUFamily1_v2]) { + *featureSet = MTLFeatureSet_OSX_GPUFamily1_v2; + return true; + } + } + if ([device supportsFeatureSet:MTLFeatureSet_OSX_GPUFamily1_v1]) { + *featureSet = MTLFeatureSet_OSX_GPUFamily1_v1; + return true; + } +#endif + + // iOS Family group 3 +#ifdef SK_BUILD_FOR_IOS + if (@available(iOS 10.0, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v2]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily3_v2; + return true; + } + } + if (@available(iOS 9.0, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily3_v1; + return true; + } + } + + // iOS Family group 2 + if (@available(iOS 10.0, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v3]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily2_v3; + return true; + } + } + if (@available(iOS 9.0, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily2_v2; + return true; + } + } + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily2_v1; + return true; + } + + // iOS Family group 1 + if (@available(iOS 10.0, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v3]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily1_v3; + return true; + } + } + if (@available(iOS 9.0, *)) { + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily1_v2; + return true; + } + } + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v1]) { + *featureSet = MTLFeatureSet_iOS_GPUFamily1_v1; + return true; + } +#endif + // No supported feature sets were found + return false; +} + +sk_sp GrMtlGpu::Make(GrContext* context, const GrContextOptions& options, + id device, id queue) { + if (!device || !queue) { + return nullptr; + } + MTLFeatureSet featureSet; + if (!get_feature_set(device, &featureSet)) { + return nullptr; + } + return sk_sp(new GrMtlGpu(context, options, device, queue, featureSet)); +} + +GrMtlGpu::GrMtlGpu(GrContext* context, const GrContextOptions& options, + id device, id queue, MTLFeatureSet featureSet) + : INHERITED(context) + , fDevice(device) + , fQueue(queue) + , fCmdBuffer(nullptr) + , fCompiler(new SkSL::Compiler()) + , fResourceProvider(this) + , fDisconnected(false) { + fMtlCaps.reset(new GrMtlCaps(options, fDevice, featureSet)); + fCaps = fMtlCaps; + if (@available(macOS 10.14, iOS 12.0, *)) { + if (fMtlCaps->fenceSyncSupport()) { + fSharedEvent = [fDevice newSharedEvent]; + dispatch_queue_t dispatchQ = dispatch_queue_create("MTLFenceSync", NULL); + fSharedEventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:dispatchQ]; + fLatestEvent = 0; + } + } +} + +GrMtlGpu::~GrMtlGpu() { + if (!fDisconnected) { + this->destroyResources(); + } +} + +void GrMtlGpu::disconnect(DisconnectType type) { + INHERITED::disconnect(type); + + if (DisconnectType::kCleanup == type) { + this->destroyResources(); + } else { + delete fCmdBuffer; + fCmdBuffer = nullptr; + + fResourceProvider.destroyResources(); + + fQueue = nil; + fDevice = nil; + + fDisconnected = true; + } +} + +void GrMtlGpu::destroyResources() { + // Will implicitly delete the command buffer + this->submitCommandBuffer(SyncQueue::kForce_SyncQueue); + fResourceProvider.destroyResources(); + + fQueue = nil; + fDevice = nil; +} + +GrOpsRenderPass* GrMtlGpu::getOpsRenderPass( + GrRenderTarget* renderTarget, GrSurfaceOrigin origin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, + const SkTArray& sampledProxies) { + return new GrMtlOpsRenderPass(this, renderTarget, origin, colorInfo, stencilInfo); +} + +void GrMtlGpu::submit(GrOpsRenderPass* renderPass) { + GrMtlOpsRenderPass* mtlRenderPass = reinterpret_cast(renderPass); + mtlRenderPass->submit(); + delete renderPass; +} + +GrMtlCommandBuffer* GrMtlGpu::commandBuffer() { + if (!fCmdBuffer) { + fCmdBuffer = GrMtlCommandBuffer::Create(fQueue); + } + return fCmdBuffer; +} + +void GrMtlGpu::submitCommandBuffer(SyncQueue sync) { + if (fCmdBuffer) { + fResourceProvider.addBufferCompletionHandler(fCmdBuffer); + fCmdBuffer->commit(SyncQueue::kForce_SyncQueue == sync); + delete fCmdBuffer; + fCmdBuffer = nullptr; + } +} + +void GrMtlGpu::onFinishFlush(GrSurfaceProxy*[], int, SkSurface::BackendSurfaceAccess, + const GrFlushInfo& info, const GrPrepareForExternalIORequests&) { + bool forceSync = SkToBool(info.fFlags & kSyncCpu_GrFlushFlag) || + (info.fFinishedProc && !this->mtlCaps().fenceSyncSupport()); + // TODO: do we care about info.fSemaphore? + if (forceSync) { + this->submitCommandBuffer(kForce_SyncQueue); + // After a forced sync everything previously sent to the GPU is done. + for (const auto& cb : fFinishCallbacks) { + cb.fCallback(cb.fContext); + this->deleteFence(cb.fFence); + } + fFinishCallbacks.clear(); + if (info.fFinishedProc) { + info.fFinishedProc(info.fFinishedContext); + } + } else { + if (info.fFinishedProc) { + FinishCallback callback; + callback.fCallback = info.fFinishedProc; + callback.fContext = info.fFinishedContext; + callback.fFence = this->insertFence(); + fFinishCallbacks.push_back(callback); + } + this->submitCommandBuffer(kSkip_SyncQueue); + } +} + +void GrMtlGpu::checkFinishProcs() { + // Bail after the first unfinished sync since we expect they signal in the order inserted. + while (!fFinishCallbacks.empty() && this->waitFence(fFinishCallbacks.front().fFence, + /* timeout = */ 0)) { + fFinishCallbacks.front().fCallback(fFinishCallbacks.front().fContext); + this->deleteFence(fFinishCallbacks.front().fFence); + fFinishCallbacks.pop_front(); + } +} + +sk_sp GrMtlGpu::onCreateBuffer(size_t size, GrGpuBufferType type, + GrAccessPattern accessPattern, const void* data) { + return GrMtlBuffer::Make(this, size, type, accessPattern, data); +} + +static bool check_max_blit_width(int widthInPixels) { + if (widthInPixels > 32767) { + SkASSERT(false); // surfaces should not be this wide anyway + return false; + } + return true; +} + +bool GrMtlGpu::uploadToTexture(GrMtlTexture* tex, int left, int top, int width, int height, + GrColorType dataColorType, const GrMipLevel texels[], + int mipLevelCount) { + SkASSERT(this->caps()->isFormatTexturable(tex->backendFormat())); + // The assumption is either that we have no mipmaps, or that our rect is the entire texture + SkASSERT(1 == mipLevelCount || + (0 == left && 0 == top && width == tex->width() && height == tex->height())); + + // We assume that if the texture has mip levels, we either upload to all the levels or just the + // first. + SkASSERT(1 == mipLevelCount || mipLevelCount == (tex->texturePriv().maxMipMapLevel() + 1)); + + if (!check_max_blit_width(width)) { + return false; + } + if (width == 0 || height == 0) { + return false; + } + if (GrPixelConfigToColorType(tex->config()) != dataColorType) { + return false; + } + + id mtlTexture = tex->mtlTexture(); + SkASSERT(mtlTexture); + // Either upload only the first miplevel or all miplevels + SkASSERT(1 == mipLevelCount || mipLevelCount == (int)mtlTexture.mipmapLevelCount); + + if (1 == mipLevelCount && !texels[0].fPixels) { + return true; // no data to upload + } + + for (int i = 0; i < mipLevelCount; ++i) { + // We do not allow any gaps in the mip data + if (!texels[i].fPixels) { + return false; + } + } + + size_t bpp = GrColorTypeBytesPerPixel(dataColorType); + + SkTArray individualMipOffsets(mipLevelCount); + size_t combinedBufferSize = GrComputeTightCombinedBufferSize(bpp, width, height, + &individualMipOffsets, + mipLevelCount); + SkASSERT(combinedBufferSize); + + size_t bufferOffset; + id transferBuffer = this->resourceProvider().getDynamicBuffer(combinedBufferSize, + &bufferOffset); + if (!transferBuffer) { + return false; + } + char* buffer = (char*) transferBuffer.contents + bufferOffset; + + int currentWidth = width; + int currentHeight = height; + int layerHeight = tex->height(); + MTLOrigin origin = MTLOriginMake(left, top, 0); + + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + if (texels[currentMipLevel].fPixels) { + SkASSERT(1 == mipLevelCount || currentHeight == layerHeight); + const size_t trimRowBytes = currentWidth * bpp; + const size_t rowBytes = texels[currentMipLevel].fRowBytes; + + // copy data into the buffer, skipping any trailing bytes + char* dst = buffer + individualMipOffsets[currentMipLevel]; + const char* src = (const char*)texels[currentMipLevel].fPixels; + SkRectMemcpy(dst, trimRowBytes, src, rowBytes, trimRowBytes, currentHeight); + + [blitCmdEncoder copyFromBuffer: transferBuffer + sourceOffset: bufferOffset + individualMipOffsets[currentMipLevel] + sourceBytesPerRow: trimRowBytes + sourceBytesPerImage: trimRowBytes*currentHeight + sourceSize: MTLSizeMake(currentWidth, currentHeight, 1) + toTexture: mtlTexture + destinationSlice: 0 + destinationLevel: currentMipLevel + destinationOrigin: origin]; + } + currentWidth = SkTMax(1, currentWidth/2); + currentHeight = SkTMax(1, currentHeight/2); + layerHeight = currentHeight; + } +#ifdef SK_BUILD_FOR_MAC + [transferBuffer didModifyRange: NSMakeRange(bufferOffset, combinedBufferSize)]; +#endif + + if (mipLevelCount < (int) tex->mtlTexture().mipmapLevelCount) { + tex->texturePriv().markMipMapsDirty(); + } + + return true; +} + +bool GrMtlGpu::clearTexture(GrMtlTexture* tex, GrColorType dataColorType, uint32_t levelMask) { + SkASSERT(this->caps()->isFormatTexturableAndUploadable(dataColorType, tex->backendFormat())); + + if (!levelMask) { + return true; + } + + id mtlTexture = tex->mtlTexture(); + SkASSERT(mtlTexture); + // Either upload only the first miplevel or all miplevels + int mipLevelCount = (int)mtlTexture.mipmapLevelCount; + + // TODO: implement some way of reusing transfer buffers? + size_t bpp = GrColorTypeBytesPerPixel(dataColorType); + + SkTArray individualMipOffsets(mipLevelCount); + size_t combinedBufferSize = 0; + int currentWidth = tex->width(); + int currentHeight = tex->height(); + + // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image + // config. This works with the assumption that the bytes in pixel config is always a power of 2. + // TODO: can we just copy from a single buffer the size of the largest cleared level w/o a perf + // penalty? + SkASSERT((bpp & (bpp - 1)) == 0); + const size_t alignmentMask = 0x3 | (bpp - 1); + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + if (levelMask & (1 << currentMipLevel)) { + const size_t trimmedSize = currentWidth * bpp * currentHeight; + const size_t alignmentDiff = combinedBufferSize & alignmentMask; + if (alignmentDiff != 0) { + combinedBufferSize += alignmentMask - alignmentDiff + 1; + } + individualMipOffsets.push_back(combinedBufferSize); + combinedBufferSize += trimmedSize; + } + currentWidth = SkTMax(1, currentWidth/2); + currentHeight = SkTMax(1, currentHeight/2); + } + SkASSERT(combinedBufferSize > 0 && !individualMipOffsets.empty()); + + // TODO: Create GrMtlTransferBuffer + NSUInteger options = 0; + if (@available(macOS 10.11, iOS 9.0, *)) { + options |= MTLResourceStorageModePrivate; + } + id transferBuffer = [fDevice newBufferWithLength: combinedBufferSize + options: options]; + if (nil == transferBuffer) { + return false; + } + + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + // clear the buffer to transparent black + NSRange clearRange; + clearRange.location = 0; + clearRange.length = combinedBufferSize; + [blitCmdEncoder fillBuffer: transferBuffer + range: clearRange + value: 0]; + + // now copy buffer to texture + currentWidth = tex->width(); + currentHeight = tex->height(); + MTLOrigin origin = MTLOriginMake(0, 0, 0); + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + if (levelMask & (1 << currentMipLevel)) { + const size_t rowBytes = currentWidth * bpp; + + [blitCmdEncoder copyFromBuffer: transferBuffer + sourceOffset: individualMipOffsets[currentMipLevel] + sourceBytesPerRow: rowBytes + sourceBytesPerImage: rowBytes * currentHeight + sourceSize: MTLSizeMake(currentWidth, currentHeight, 1) + toTexture: mtlTexture + destinationSlice: 0 + destinationLevel: currentMipLevel + destinationOrigin: origin]; + } + currentWidth = SkTMax(1, currentWidth/2); + currentHeight = SkTMax(1, currentHeight/2); + } + + if (mipLevelCount < (int) tex->mtlTexture().mipmapLevelCount) { + tex->texturePriv().markMipMapsDirty(); + } + + return true; +} + +GrStencilAttachment* GrMtlGpu::createStencilAttachmentForRenderTarget( + const GrRenderTarget* rt, int width, int height, int numStencilSamples) { + SkASSERT(numStencilSamples == rt->numSamples()); + SkASSERT(width >= rt->width()); + SkASSERT(height >= rt->height()); + + int samples = rt->numSamples(); + + const GrMtlCaps::StencilFormat& sFmt = this->mtlCaps().preferredStencilFormat(); + + GrMtlStencilAttachment* stencil(GrMtlStencilAttachment::Create(this, + width, + height, + samples, + sFmt)); + fStats.incStencilAttachmentCreates(); + return stencil; +} + +sk_sp GrMtlGpu::onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + int mipLevelCount, + uint32_t levelClearMask) { + // We don't support protected textures in Metal. + if (isProtected == GrProtected::kYes) { + return nullptr; + } + SkASSERT(mipLevelCount > 0); + + MTLPixelFormat mtlPixelFormat = GrBackendFormatAsMTLPixelFormat(format); + SkASSERT(mtlPixelFormat != MTLPixelFormatInvalid); + SkASSERT(!this->caps()->isFormatCompressed(format)); + + sk_sp tex; + // This TexDesc refers to the texture that will be read by the client. Thus even if msaa is + // requested, this TexDesc describes the resolved texture. Therefore we always have samples + // set to 1. + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.pixelFormat = mtlPixelFormat; + texDesc.width = desc.fWidth; + texDesc.height = desc.fHeight; + texDesc.depth = 1; + texDesc.mipmapLevelCount = mipLevelCount; + texDesc.sampleCount = 1; + texDesc.arrayLength = 1; + // Make all textures have private gpu only access. We can use transfer buffers or textures + // to copy to them. + if (@available(macOS 10.11, iOS 9.0, *)) { + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead; + texDesc.usage |= (renderable == GrRenderable::kYes) ? MTLTextureUsageRenderTarget : 0; + } + + GrMipMapsStatus mipMapsStatus = + mipLevelCount > 1 ? GrMipMapsStatus::kDirty : GrMipMapsStatus::kNotAllocated; + if (renderable == GrRenderable::kYes) { + tex = GrMtlTextureRenderTarget::MakeNewTextureRenderTarget(this, budgeted, + desc, renderTargetSampleCnt, + texDesc, mipMapsStatus); + } else { + tex = GrMtlTexture::MakeNewTexture(this, budgeted, desc, texDesc, mipMapsStatus); + } + + if (!tex) { + return nullptr; + } + + if (levelClearMask) { + auto colorType = GrPixelConfigToColorType(desc.fConfig); + this->clearTexture(tex.get(), colorType, levelClearMask); + } + + return tex; +} + +sk_sp GrMtlGpu::onCreateCompressedTexture(int width, int height, + const GrBackendFormat& format, + SkImage::CompressionType compressionType, + SkBudgeted budgeted, const void* data) { + SkASSERT(this->caps()->isFormatTexturable(format)); + SkASSERT(data); + + if (!check_max_blit_width(width)) { + return nullptr; + } + + MTLPixelFormat mtlPixelFormat = GrBackendFormatAsMTLPixelFormat(format); + + // This TexDesc refers to the texture that will be read by the client. Thus even if msaa is + // requested, this TexDesc describes the resolved texture. Therefore we always have samples + // set to 1. + // Compressed textures with MIP levels or multiple samples are not supported as of now. + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.pixelFormat = mtlPixelFormat; + texDesc.width = width; + texDesc.height = height; + texDesc.depth = 1; + texDesc.mipmapLevelCount = 1; + texDesc.sampleCount = 1; + texDesc.arrayLength = 1; + // Make all textures have private gpu only access. We can use transfer buffers or textures + // to copy to them. + if (@available(macOS 10.11, iOS 9.0, *)) { + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead; + } + + GrSurfaceDesc desc; + desc.fConfig = GrCompressionTypePixelConfig(compressionType); + desc.fWidth = width; + desc.fHeight = height; + auto tex = GrMtlTexture::MakeNewTexture(this, budgeted, desc, texDesc, + GrMipMapsStatus::kNotAllocated); + if (!tex) { + return nullptr; + } + + // Upload to texture + id mtlTexture = tex->mtlTexture(); + SkASSERT(mtlTexture); + + SkImage::CompressionType textureCompressionType; + if (!GrMtlFormatToCompressionType(mtlTexture.pixelFormat, &textureCompressionType) || + textureCompressionType != compressionType) { + return nullptr; + } + + size_t dataSize = GrCompressedDataSize(compressionType, width, height); + SkASSERT(dataSize); + + size_t bufferOffset; + id transferBuffer = this->resourceProvider().getDynamicBuffer(dataSize, + &bufferOffset); + if (!transferBuffer) { + return nullptr; + } + char* buffer = (char*) transferBuffer.contents + bufferOffset; + + MTLOrigin origin = MTLOriginMake(0, 0, 0); + + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + const size_t rowBytes = GrCompressedRowBytes(compressionType, width); + + // copy data into the buffer, skipping any trailing bytes + memcpy(buffer, data, dataSize); + [blitCmdEncoder copyFromBuffer: transferBuffer + sourceOffset: bufferOffset + sourceBytesPerRow: rowBytes + sourceBytesPerImage: dataSize + sourceSize: MTLSizeMake(width, height, 1) + toTexture: mtlTexture + destinationSlice: 0 + destinationLevel: 0 + destinationOrigin: origin]; +#ifdef SK_BUILD_FOR_MAC + [transferBuffer didModifyRange: NSMakeRange(bufferOffset, dataSize)]; +#endif + + return tex; +} + +static id get_texture_from_backend(const GrBackendTexture& backendTex) { + GrMtlTextureInfo textureInfo; + if (!backendTex.getMtlTextureInfo(&textureInfo)) { + return nil; + } + return GrGetMTLTexture(textureInfo.fTexture.get()); +} + +static id get_texture_from_backend(const GrBackendRenderTarget& backendRT) { + GrMtlTextureInfo textureInfo; + if (!backendRT.getMtlTextureInfo(&textureInfo)) { + return nil; + } + return GrGetMTLTexture(textureInfo.fTexture.get()); +} + +static inline void init_surface_desc(GrSurfaceDesc* surfaceDesc, id mtlTexture, + GrRenderable renderable, GrPixelConfig config) { + if (@available(macOS 10.11, iOS 9.0, *)) { + if (renderable == GrRenderable::kYes) { + SkASSERT(MTLTextureUsageRenderTarget & mtlTexture.usage); + } + } + surfaceDesc->fWidth = mtlTexture.width; + surfaceDesc->fHeight = mtlTexture.height; + surfaceDesc->fConfig = config; +} + +sk_sp GrMtlGpu::onWrapBackendTexture(const GrBackendTexture& backendTex, + GrColorType grColorType, + GrWrapOwnership, + GrWrapCacheable cacheable, GrIOType ioType) { + id mtlTexture = get_texture_from_backend(backendTex); + if (!mtlTexture) { + return nullptr; + } + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendTex.getBackendFormat(), + grColorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc surfDesc; + init_surface_desc(&surfDesc, mtlTexture, GrRenderable::kNo, config); + + return GrMtlTexture::MakeWrappedTexture(this, surfDesc, mtlTexture, cacheable, ioType); +} + +sk_sp GrMtlGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex, + int sampleCnt, + GrColorType colorType, + GrWrapOwnership, + GrWrapCacheable cacheable) { + id mtlTexture = get_texture_from_backend(backendTex); + if (!mtlTexture) { + return nullptr; + } + + const GrMtlCaps& caps = this->mtlCaps(); + + MTLPixelFormat format = mtlTexture.pixelFormat; + if (!caps.isFormatRenderable(format, sampleCnt)) { + return nullptr; + } + + GrPixelConfig config = caps.getConfigFromBackendFormat(backendTex.getBackendFormat(), + colorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc surfDesc; + init_surface_desc(&surfDesc, mtlTexture, GrRenderable::kYes, config); + + sampleCnt = caps.getRenderTargetSampleCount(sampleCnt, format); + SkASSERT(sampleCnt); + + return GrMtlTextureRenderTarget::MakeWrappedTextureRenderTarget(this, surfDesc, sampleCnt, + mtlTexture, cacheable); +} + +sk_sp GrMtlGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& backendRT, + GrColorType grColorType) { + // TODO: Revisit this when the Metal backend is completed. It may support MSAA render targets. + if (backendRT.sampleCnt() > 1) { + return nullptr; + } + id mtlTexture = get_texture_from_backend(backendRT); + if (!mtlTexture) { + return nullptr; + } + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendRT.getBackendFormat(), + grColorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc surfDesc; + init_surface_desc(&surfDesc, mtlTexture, GrRenderable::kYes, config); + + return GrMtlRenderTarget::MakeWrappedRenderTarget(this, surfDesc, backendRT.sampleCnt(), + mtlTexture); +} + +sk_sp GrMtlGpu::onWrapBackendTextureAsRenderTarget( + const GrBackendTexture& backendTex, int sampleCnt, GrColorType grColorType) { + id mtlTexture = get_texture_from_backend(backendTex); + if (!mtlTexture) { + return nullptr; + } + + MTLPixelFormat format = mtlTexture.pixelFormat; + if (!this->mtlCaps().isFormatRenderable(format, sampleCnt)) { + return nullptr; + } + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendTex.getBackendFormat(), + grColorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc surfDesc; + init_surface_desc(&surfDesc, mtlTexture, GrRenderable::kYes, config); + sampleCnt = this->mtlCaps().getRenderTargetSampleCount(sampleCnt, format); + if (!sampleCnt) { + return nullptr; + } + + return GrMtlRenderTarget::MakeWrappedRenderTarget(this, surfDesc, sampleCnt, mtlTexture); +} + +bool GrMtlGpu::onRegenerateMipMapLevels(GrTexture* texture) { + GrMtlTexture* grMtlTexture = static_cast(texture); + id mtlTexture = grMtlTexture->mtlTexture(); + + // Automatic mipmap generation is only supported by color-renderable formats + if (!fMtlCaps->isFormatRenderable(mtlTexture.pixelFormat, 1) && + // We have pixel configs marked as textureable-only that use RGBA8 as the internal format + MTLPixelFormatRGBA8Unorm != mtlTexture.pixelFormat) { + return false; + } + + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + [blitCmdEncoder generateMipmapsForTexture: mtlTexture]; + + return true; +} + +static GrPixelConfig mtl_format_to_pixelconfig(MTLPixelFormat format) { + switch(format) { + case MTLPixelFormatA8Unorm: return kAlpha_8_GrPixelConfig; + case MTLPixelFormatR8Unorm: return kAlpha_8_GrPixelConfig; + +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatB5G6R5Unorm: return kRGB_565_GrPixelConfig; + case MTLPixelFormatABGR4Unorm: return kRGBA_4444_GrPixelConfig; +#endif + case MTLPixelFormatRGBA8Unorm: return kRGBA_8888_GrPixelConfig; + case MTLPixelFormatRGBA8Unorm_sRGB: return kSRGBA_8888_GrPixelConfig; + +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatETC2_RGB8: return kRGB_ETC1_GrPixelConfig; +#endif + case MTLPixelFormatRG8Unorm: return kRG_88_GrPixelConfig; + case MTLPixelFormatBGRA8Unorm: return kBGRA_8888_GrPixelConfig; + case MTLPixelFormatRGB10A2Unorm: return kRGBA_1010102_GrPixelConfig; + case MTLPixelFormatR16Float: return kAlpha_half_GrPixelConfig; + case MTLPixelFormatRGBA16Float: return kRGBA_half_GrPixelConfig; + case MTLPixelFormatR16Unorm: return kAlpha_16_GrPixelConfig; + case MTLPixelFormatRG16Unorm: return kRG_1616_GrPixelConfig; + case MTLPixelFormatRGBA16Unorm: return kRGBA_16161616_GrPixelConfig; + case MTLPixelFormatRG16Float: return kRG_half_GrPixelConfig; + default: return kUnknown_GrPixelConfig; + } + + SkUNREACHABLE; +} + +void copy_src_data(char* dst, size_t bytesPerPixel, const SkTArray& individualMipOffsets, + const SkPixmap srcData[], int numMipLevels, size_t bufferSize) { + SkASSERT(srcData && numMipLevels); + SkASSERT(individualMipOffsets.count() == numMipLevels); + + for (int level = 0; level < numMipLevels; ++level) { + const size_t trimRB = srcData[level].width() * bytesPerPixel; + SkASSERT(individualMipOffsets[level] + trimRB * srcData[level].height() <= bufferSize); + SkRectMemcpy(dst + individualMipOffsets[level], trimRB, + srcData[level].addr(), srcData[level].rowBytes(), + trimRB, srcData[level].height()); + } +} + +bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, + int w, int h, bool texturable, + bool renderable, GrMipMapped mipMapped, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrMtlTextureInfo* info) { + SkASSERT(texturable || renderable); + if (!texturable) { + SkASSERT(GrMipMapped::kNo == mipMapped); + SkASSERT(!srcData && !numMipLevels); + } + +#ifdef SK_BUILD_FOR_IOS + // Compressed formats go through onCreateCompressedBackendTexture + SkASSERT(!GrMtlFormatIsCompressed(format)); +#endif + + if (texturable && !fMtlCaps->isFormatTexturable(format)) { + return false; + } + if (renderable && !fMtlCaps->isFormatRenderable(format, 1)) { + return false; + } + + if (!check_max_blit_width(w)) { + return false; + } + + int mipLevelCount = 1; + if (srcData) { + SkASSERT(numMipLevels > 0); + mipLevelCount = numMipLevels; + } else if (GrMipMapped::kYes == mipMapped) { + mipLevelCount = SkMipMap::ComputeLevelCount(w, h) + 1; + } + + bool mipmapped = mipMapped == GrMipMapped::kYes ? true : false; + MTLTextureDescriptor* desc = + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format + width: w + height: h + mipmapped: mipmapped]; + if (@available(macOS 10.11, iOS 9.0, *)) { + desc.storageMode = MTLStorageModePrivate; + desc.usage = texturable ? MTLTextureUsageShaderRead : 0; + desc.usage |= renderable ? MTLTextureUsageRenderTarget : 0; + } + id testTexture = [fDevice newTextureWithDescriptor: desc]; + + if (!srcData && !color) { + info->fTexture.reset(GrRetainPtrFromId(testTexture)); + + return true; + } + + // Create the transfer buffer + size_t bytesPerPixel = fMtlCaps->bytesPerPixel(format); + + SkTArray individualMipOffsets(mipLevelCount); + size_t combinedBufferSize = GrComputeTightCombinedBufferSize(bytesPerPixel, w, h, + &individualMipOffsets, + mipLevelCount); + + NSUInteger options = 0; // TODO: consider other options here + if (@available(macOS 10.11, iOS 9.0, *)) { +#ifdef SK_BUILD_FOR_MAC + options |= MTLResourceStorageModeManaged; +#else + options |= MTLResourceStorageModeShared; +#endif + } + + id transferBuffer = [fDevice newBufferWithLength: combinedBufferSize + options: options]; + if (nil == transferBuffer) { + return false; + } + + char* buffer = (char*) transferBuffer.contents; + + // Fill buffer with data + if (srcData) { + copy_src_data(buffer, bytesPerPixel, individualMipOffsets, + srcData, numMipLevels, combinedBufferSize); + } else if (color) { + GrPixelConfig config = mtl_format_to_pixelconfig(format); + auto colorType = GrPixelConfigToColorType(config); + SkASSERT(kUnknown_GrPixelConfig != config); + GrFillInData(colorType, w, h, individualMipOffsets, buffer, *color); + } + + // Transfer buffer contents to texture + int currentWidth = w; + int currentHeight = h; + MTLOrigin origin = MTLOriginMake(0, 0, 0); + + id cmdBuffer = [fQueue commandBuffer]; + id blitCmdEncoder = [cmdBuffer blitCommandEncoder]; + + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + size_t trimRowBytes = currentWidth * bytesPerPixel; + size_t levelSize = trimRowBytes*currentHeight; + + // TODO: can this all be done in one go? + [blitCmdEncoder copyFromBuffer: transferBuffer + sourceOffset: individualMipOffsets[currentMipLevel] + sourceBytesPerRow: trimRowBytes + sourceBytesPerImage: levelSize + sourceSize: MTLSizeMake(currentWidth, currentHeight, 1) + toTexture: testTexture + destinationSlice: 0 + destinationLevel: currentMipLevel + destinationOrigin: origin]; + + currentWidth = SkTMax(1, currentWidth/2); + currentHeight = SkTMax(1, currentHeight/2); + } +#ifdef SK_BUILD_FOR_MAC + [transferBuffer didModifyRange: NSMakeRange(0, combinedBufferSize)]; +#endif + + [blitCmdEncoder endEncoding]; + [cmdBuffer commit]; + [cmdBuffer waitUntilCompleted]; + transferBuffer = nil; + + info->fTexture.reset(GrRetainPtrFromId(testTexture)); + + return true; +} + +GrBackendTexture GrMtlGpu::onCreateBackendTexture(int w, int h, + const GrBackendFormat& format, + GrMipMapped mipMapped, + GrRenderable renderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, + GrProtected isProtected) { + SkDEBUGCODE(const GrMtlCaps& caps = this->mtlCaps();) + + // GrGpu::createBackendTexture should've ensured these conditions + SkASSERT(w >= 1 && w <= caps.maxTextureSize() && h >= 1 && h <= caps.maxTextureSize()); + SkASSERT(GrGpu::MipMapsAreCorrect(w, h, mipMapped, srcData, numMipLevels)); + SkASSERT(mipMapped == GrMipMapped::kNo || caps.mipMapSupport()); + + const MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + GrMtlTextureInfo info; + if (!this->createMtlTextureForBackendSurface(mtlFormat, + w, h, true, + GrRenderable::kYes == renderable, mipMapped, + srcData, numMipLevels, color, &info)) { + return {}; + } + + GrBackendTexture backendTex(w, h, mipMapped, info); + return backendTex; +} + +void GrMtlGpu::deleteBackendTexture(const GrBackendTexture& tex) { + SkASSERT(GrBackendApi::kMetal == tex.backend()); + // Nothing to do here, will get cleaned up when the GrBackendTexture object goes away +} + +#if GR_TEST_UTILS +bool GrMtlGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const { + SkASSERT(GrBackendApi::kMetal == tex.backend()); + + GrMtlTextureInfo info; + if (!tex.getMtlTextureInfo(&info)) { + return false; + } + id mtlTexture = GrGetMTLTexture(info.fTexture.get()); + if (!mtlTexture) { + return false; + } + if (@available(macOS 10.11, iOS 9.0, *)) { + return mtlTexture.usage & MTLTextureUsageShaderRead; + } else { + return true; // best we can do + } +} + +GrBackendRenderTarget GrMtlGpu::createTestingOnlyBackendRenderTarget(int w, int h, GrColorType ct) { + if (w > this->caps()->maxRenderTargetSize() || h > this->caps()->maxRenderTargetSize()) { + return GrBackendRenderTarget(); + } + + GrPixelConfig config = GrColorTypeToPixelConfig(ct); + + MTLPixelFormat format; + if (!GrPixelConfigToMTLFormat(config, &format)) { + return GrBackendRenderTarget(); + } + + GrMtlTextureInfo info; + if (!this->createMtlTextureForBackendSurface(format, w, h, false, true, + GrMipMapped::kNo, nullptr, 0, nullptr, &info)) { + return {}; + } + + GrBackendRenderTarget backendRT(w, h, 1, info); + return backendRT; +} + +void GrMtlGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget& rt) { + SkASSERT(GrBackendApi::kMetal == rt.backend()); + + GrMtlTextureInfo info; + if (rt.getMtlTextureInfo(&info)) { + this->testingOnly_flushGpuAndSync(); + // Nothing else to do here, will get cleaned up when the GrBackendRenderTarget + // is deleted. + } +} + +void GrMtlGpu::testingOnly_flushGpuAndSync() { + this->submitCommandBuffer(kForce_SyncQueue); +} +#endif // GR_TEST_UTILS + +static int get_surface_sample_cnt(GrSurface* surf) { + if (const GrRenderTarget* rt = surf->asRenderTarget()) { + return rt->numSamples(); + } + return 0; +} + +void GrMtlGpu::copySurfaceAsResolve(GrSurface* dst, GrSurface* src) { + // TODO: Add support for subrectangles + GrMtlRenderTarget* srcRT = static_cast(src->asRenderTarget()); + GrRenderTarget* dstRT = dst->asRenderTarget(); + id dstTexture; + if (dstRT) { + GrMtlRenderTarget* mtlRT = static_cast(dstRT); + dstTexture = mtlRT->mtlColorTexture(); + } else { + SkASSERT(dst->asTexture()); + dstTexture = static_cast(dst->asTexture())->mtlTexture(); + } + + this->resolveTexture(dstTexture, srcRT->mtlColorTexture()); +} + +void GrMtlGpu::copySurfaceAsBlit(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + id dstTex = GrGetMTLTextureFromSurface(dst); + id srcTex = GrGetMTLTextureFromSurface(src); + +#ifdef SK_DEBUG + int dstSampleCnt = get_surface_sample_cnt(dst); + int srcSampleCnt = get_surface_sample_cnt(src); + SkASSERT(this->mtlCaps().canCopyAsBlit(dstTex.pixelFormat, dstSampleCnt, srcTex.pixelFormat, + srcSampleCnt, srcRect, dstPoint, dst == src)); +#endif + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + [blitCmdEncoder copyFromTexture: srcTex + sourceSlice: 0 + sourceLevel: 0 + sourceOrigin: MTLOriginMake(srcRect.x(), srcRect.y(), 0) + sourceSize: MTLSizeMake(srcRect.width(), srcRect.height(), 1) + toTexture: dstTex + destinationSlice: 0 + destinationLevel: 0 + destinationOrigin: MTLOriginMake(dstPoint.fX, dstPoint.fY, 0)]; +} + +bool GrMtlGpu::onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + SkASSERT(!src->isProtected() && !dst->isProtected()); + + MTLPixelFormat dstFormat = GrBackendFormatAsMTLPixelFormat(dst->backendFormat()); + MTLPixelFormat srcFormat = GrBackendFormatAsMTLPixelFormat(src->backendFormat()); + + int dstSampleCnt = get_surface_sample_cnt(dst); + int srcSampleCnt = get_surface_sample_cnt(src); + + bool success = false; + if (this->mtlCaps().canCopyAsResolve(dst, dstSampleCnt, src, srcSampleCnt, srcRect, dstPoint)) { + this->copySurfaceAsResolve(dst, src); + success = true; + } else if (this->mtlCaps().canCopyAsBlit(dstFormat, dstSampleCnt, srcFormat, srcSampleCnt, + srcRect, dstPoint, dst == src)) { + this->copySurfaceAsBlit(dst, src, srcRect, dstPoint); + success = true; + } + if (success) { + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.x(), dstPoint.y(), + srcRect.width(), srcRect.height()); + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); + } + return success; +} + +bool GrMtlGpu::onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) { + GrMtlTexture* mtlTexture = static_cast(surface->asTexture()); + // TODO: In principle we should be able to support pure rendertargets as well, but + // until we find a use case we'll only support texture rendertargets. + if (!mtlTexture) { + return false; + } + if (!mipLevelCount) { + return false; + } +#ifdef SK_DEBUG + for (int i = 0; i < mipLevelCount; i++) { + SkASSERT(texels[i].fPixels); + } +#endif + return this->uploadToTexture(mtlTexture, left, top, width, height, srcColorType, texels, + mipLevelCount); +} + +bool GrMtlGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) { + SkASSERT(surface); + + if (surfaceColorType != dstColorType) { + return false; + } + + int bpp = GrColorTypeBytesPerPixel(dstColorType); + size_t transBufferRowBytes = bpp * width; + size_t transBufferImageBytes = transBufferRowBytes * height; + + // TODO: implement some way of reusing buffers instead of making a new one every time. + NSUInteger options = 0; + if (@available(macOS 10.11, iOS 9.0, *)) { +#ifdef SK_BUILD_FOR_MAC + options |= MTLResourceStorageModeManaged; +#else + options |= MTLResourceStorageModeShared; +#endif + } + + id transferBuffer = [fDevice newBufferWithLength: transBufferImageBytes + options: options]; + + if (!this->readOrTransferPixels(surface, left, top, width, height, dstColorType, transferBuffer, + 0, transBufferImageBytes, transBufferRowBytes)) { + return false; + } + this->submitCommandBuffer(kForce_SyncQueue); + + const void* mappedMemory = transferBuffer.contents; + + SkRectMemcpy(buffer, rowBytes, mappedMemory, transBufferRowBytes, transBufferRowBytes, height); + + return true; +} + +bool GrMtlGpu::onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) { + SkASSERT(texture); + SkASSERT(transferBuffer); + if (textureColorType != bufferColorType || + GrPixelConfigToColorType(texture->config()) != bufferColorType) { + return false; + } + + GrMtlTexture* grMtlTexture = static_cast(texture); + id mtlTexture = grMtlTexture->mtlTexture(); + SkASSERT(mtlTexture); + + GrMtlBuffer* grMtlBuffer = static_cast(transferBuffer); + id mtlBuffer = grMtlBuffer->mtlBuffer(); + SkASSERT(mtlBuffer); + + size_t bpp = GrColorTypeBytesPerPixel(bufferColorType); + if (offset % bpp) { + return false; + } + MTLOrigin origin = MTLOriginMake(left, top, 0); + + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + [blitCmdEncoder copyFromBuffer: mtlBuffer + sourceOffset: offset + grMtlBuffer->offset() + sourceBytesPerRow: rowBytes + sourceBytesPerImage: rowBytes*height + sourceSize: MTLSizeMake(width, height, 1) + toTexture: mtlTexture + destinationSlice: 0 + destinationLevel: 0 + destinationOrigin: origin]; + + return true; +} + +bool GrMtlGpu::onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) { + SkASSERT(surface); + SkASSERT(transferBuffer); + + if (surfaceColorType != bufferColorType) { + return false; + } + + // Metal only supports offsets that are aligned to a pixel. + int bpp = GrColorTypeBytesPerPixel(bufferColorType); + if (offset % bpp) { + return false; + } + + GrMtlBuffer* grMtlBuffer = static_cast(transferBuffer); + grMtlBuffer->bind(); + + size_t transBufferRowBytes = bpp * width; + size_t transBufferImageBytes = transBufferRowBytes * height; + + return this->readOrTransferPixels(surface, left, top, width, height, bufferColorType, + grMtlBuffer->mtlBuffer(), offset + grMtlBuffer->offset(), + transBufferImageBytes, transBufferRowBytes); +} + +bool GrMtlGpu::readOrTransferPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType dstColorType, id transferBuffer, + size_t offset, size_t imageBytes, size_t rowBytes) { + if (!check_max_blit_width(width)) { + return false; + } + if (GrPixelConfigToColorType(surface->config()) != dstColorType) { + return false; + } + + id mtlTexture; + if (GrMtlRenderTarget* rt = static_cast(surface->asRenderTarget())) { + if (rt->numSamples() > 1) { + SkASSERT(rt->requiresManualMSAAResolve()); // msaa-render-to-texture not yet supported. + mtlTexture = rt->mtlResolveTexture(); + } else { + SkASSERT(!rt->requiresManualMSAAResolve()); + mtlTexture = rt->mtlColorTexture(); + } + } else if (GrMtlTexture* texture = static_cast(surface->asTexture())) { + mtlTexture = texture->mtlTexture(); + } + if (!mtlTexture) { + return false; + } + + id blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder(); + [blitCmdEncoder copyFromTexture: mtlTexture + sourceSlice: 0 + sourceLevel: 0 + sourceOrigin: MTLOriginMake(left, top, 0) + sourceSize: MTLSizeMake(width, height, 1) + toBuffer: transferBuffer + destinationOffset: offset + destinationBytesPerRow: rowBytes + destinationBytesPerImage: imageBytes]; +#ifdef SK_BUILD_FOR_MAC + // Sync GPU data back to the CPU + [blitCmdEncoder synchronizeResource: transferBuffer]; +#endif + + return true; +} + +GrFence SK_WARN_UNUSED_RESULT GrMtlGpu::insertFence() { + GrMtlCommandBuffer* cmdBuffer = this->commandBuffer(); + if (@available(macOS 10.14, iOS 12.0, *)) { + ++fLatestEvent; + cmdBuffer->encodeSignalEvent(fSharedEvent, fLatestEvent); + + return fLatestEvent; + } + // If MTLSharedEvent isn't available, we create a semaphore and signal it + // within the current command buffer's completion handler. + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + cmdBuffer->addCompletedHandler(^(id commandBuffer) { + dispatch_semaphore_signal(semaphore); + }); + + const void* cfFence = (__bridge_retained const void*) semaphore; + return (GrFence) cfFence; +} + +bool GrMtlGpu::waitFence(GrFence fence, uint64_t timeout) { + dispatch_semaphore_t semaphore; + if (@available(macOS 10.14, iOS 12.0, *)) { + semaphore = dispatch_semaphore_create(0); + + // Add listener for this particular value or greater + __block dispatch_semaphore_t block_sema = semaphore; + [fSharedEvent notifyListener: fSharedEventListener + atValue: fence + block: ^(id sharedEvent, uint64_t value) { + dispatch_semaphore_signal(block_sema); + }]; + + } else { + const void* cfFence = (const void*) fence; + semaphore = (__bridge dispatch_semaphore_t)cfFence; + } + long result = dispatch_semaphore_wait(semaphore, timeout); + + return !result; +} + +void GrMtlGpu::deleteFence(GrFence fence) const { + if (@available(macOS 10.14, iOS 12.0, *)) { + // nothing to delete + } else { + const void* cfFence = (const void*) fence; + // In this case it's easier to release in CoreFoundation than depend on ARC + CFRelease(cfFence); + } +} + +sk_sp SK_WARN_UNUSED_RESULT GrMtlGpu::makeSemaphore(bool isOwned) { + SkASSERT(this->caps()->semaphoreSupport()); + return GrMtlSemaphore::Make(this, isOwned); +} + +sk_sp GrMtlGpu::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) { + SkASSERT(this->caps()->semaphoreSupport()); + return GrMtlSemaphore::MakeWrapped(this, semaphore.mtlSemaphore(), semaphore.mtlValue(), + ownership); +} + +void GrMtlGpu::insertSemaphore(sk_sp semaphore) { + if (@available(macOS 10.14, iOS 12.0, *)) { + GrMtlSemaphore* mtlSem = static_cast(semaphore.get()); + + this->commandBuffer()->encodeSignalEvent(mtlSem->event(), mtlSem->value()); + } +} + +void GrMtlGpu::waitSemaphore(sk_sp semaphore) { + if (@available(macOS 10.14, iOS 12.0, *)) { + GrMtlSemaphore* mtlSem = static_cast(semaphore.get()); + + this->commandBuffer()->encodeWaitForEvent(mtlSem->event(), mtlSem->value()); + } +} + +void GrMtlGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect&, GrSurfaceOrigin, + ForExternalIO forExternalIO) { + this->resolveTexture(static_cast(target)->mtlResolveTexture(), + static_cast(target)->mtlColorTexture()); + + if (ForExternalIO::kYes == forExternalIO) { + // This resolve is called when we are preparing an msaa surface for external I/O. It is + // called after flushing, so we need to make sure we submit the command buffer after + // doing the resolve so that the resolve actually happens. + this->submitCommandBuffer(kSkip_SyncQueue); + } +} + +void GrMtlGpu::resolveTexture(id resolveTexture, id colorTexture) { + auto renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDesc.colorAttachments[0].texture = colorTexture; + renderPassDesc.colorAttachments[0].slice = 0; + renderPassDesc.colorAttachments[0].level = 0; + renderPassDesc.colorAttachments[0].resolveTexture = resolveTexture; + renderPassDesc.colorAttachments[0].slice = 0; + renderPassDesc.colorAttachments[0].level = 0; + renderPassDesc.colorAttachments[0].loadAction = MTLLoadActionLoad; + renderPassDesc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve; + + id cmdEncoder = + this->commandBuffer()->getRenderCommandEncoder(renderPassDesc, nullptr, nullptr); + SkASSERT(nil != cmdEncoder); + cmdEncoder.label = @"resolveTexture"; +} + +#if GR_TEST_UTILS +void GrMtlGpu::testingOnly_startCapture() { + if (@available(macOS 10.13, iOS 11.0, *)) { + // TODO: add Metal 3 interface as well + MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; + [captureManager startCaptureWithDevice: fDevice]; + } +} + +void GrMtlGpu::testingOnly_endCapture() { + if (@available(macOS 10.13, iOS 11.0, *)) { + MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; + [captureManager stopCapture]; + } +} +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.h b/gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.h new file mode 100644 index 0000000000..702bf29d80 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.h @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlOpsRenderPass_DEFINED +#define GrMtlOpsRenderPass_DEFINED + +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/mtl/GrMtlGpu.h" + +#import + +typedef uint32_t GrColor; +class GrMtlBuffer; +class GrMtlPipelineState; +class GrMtlRenderTarget; + +class GrMtlOpsRenderPass : public GrOpsRenderPass, private GrMesh::SendToGpuImpl { +public: + GrMtlOpsRenderPass(GrMtlGpu* gpu, GrRenderTarget* rt, GrSurfaceOrigin origin, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo); + + ~GrMtlOpsRenderPass() override; + + void begin() override {} + void end() override {} + + void insertEventMarker(const char* msg) override {} + + void initRenderState(id); + + void inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) override { + // TODO: this could be more efficient + state->doUpload(upload); + } + void submit(); + +private: + GrGpu* gpu() override { return fGpu; } + + GrMtlPipelineState* prepareDrawState(const GrProgramInfo&, GrPrimitiveType); + + void onDraw(const GrProgramInfo& programInfo, + const GrMesh mesh[], + int meshCount, + const SkRect& bounds) override; + + void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override; + + void onClearStencilClip(const GrFixedClip& clip, bool insideStencilMask) override; + + void setupRenderPass(const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo); + + void bindGeometry(const GrBuffer* vertexBuffer, size_t vertexOffset, + const GrBuffer* instanceBuffer); + + // GrMesh::SendToGpuImpl methods. These issue the actual Metal draw commands. + // Marked final as a hint to the compiler to not use virtual dispatch. + void sendMeshToGpu(GrPrimitiveType primType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex) final; + + void sendIndexedMeshToGpu(GrPrimitiveType primType, const GrBuffer* indexBuffer, int indexCount, + int baseIndex, uint16_t /*minIndexValue*/, uint16_t /*maxIndexValue*/, + const GrBuffer* vertexBuffer, int baseVertex, + GrPrimitiveRestart restart) final; + + void sendInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex, const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance) final; + + void sendIndexedInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* indexBuffer, int indexCount, + int baseIndex, const GrBuffer* vertexBuffer, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance, GrPrimitiveRestart) final; + + void setVertexBuffer(id, const GrMtlBuffer*, size_t offset, + size_t index); + void resetBufferBindings(); + void precreateCmdEncoder(); + + GrMtlGpu* fGpu; + + id fActiveRenderCmdEncoder; + MTLRenderPassDescriptor* fRenderPassDesc; + SkRect fBounds; + size_t fCurrentVertexStride; + + static constexpr size_t kNumBindings = GrMtlUniformHandler::kLastUniformBinding + 3; + struct { + id fBuffer; + size_t fOffset; + } fBufferBindings[kNumBindings]; + + typedef GrOpsRenderPass INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm new file mode 100644 index 0000000000..f587492f77 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm @@ -0,0 +1,420 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlOpsRenderPass.h" + +#include "src/gpu/GrColor.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/mtl/GrMtlCommandBuffer.h" +#include "src/gpu/mtl/GrMtlPipelineState.h" +#include "src/gpu/mtl/GrMtlPipelineStateBuilder.h" +#include "src/gpu/mtl/GrMtlRenderTarget.h" +#include "src/gpu/mtl/GrMtlTexture.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlOpsRenderPass::GrMtlOpsRenderPass(GrMtlGpu* gpu, GrRenderTarget* rt, GrSurfaceOrigin origin, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo) + : INHERITED(rt, origin) + , fGpu(gpu) { + this->setupRenderPass(colorInfo, stencilInfo); +} + +GrMtlOpsRenderPass::~GrMtlOpsRenderPass() { + SkASSERT(nil == fActiveRenderCmdEncoder); +} + +void GrMtlOpsRenderPass::precreateCmdEncoder() { + // For clears, we may not have an associated draw. So we prepare a cmdEncoder that + // will be submitted whether there's a draw or not. + SkASSERT(nil == fActiveRenderCmdEncoder); + + SkDEBUGCODE(id cmdEncoder =) + fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc, nullptr, this); + SkASSERT(nil != cmdEncoder); +} + +void GrMtlOpsRenderPass::submit() { + if (!fRenderTarget) { + return; + } + SkIRect iBounds; + fBounds.roundOut(&iBounds); + fGpu->submitIndirectCommandBuffer(fRenderTarget, fOrigin, &iBounds); +} + +GrMtlPipelineState* GrMtlOpsRenderPass::prepareDrawState(const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType) { + // TODO: resolve textures and regenerate mipmaps as needed + + GrMtlPipelineState* pipelineState = + fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget, + programInfo, + primitiveType); + if (!pipelineState) { + return nullptr; + } + + pipelineState->setData(fRenderTarget, programInfo); + fCurrentVertexStride = programInfo.primProc().vertexStride(); + + return pipelineState; +} + +void GrMtlOpsRenderPass::onDraw(const GrProgramInfo& programInfo, + const GrMesh meshes[], + int meshCount, + const SkRect& bounds) { + + SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw + + GrPrimitiveType primitiveType = meshes[0].primitiveType(); + GrMtlPipelineState* pipelineState = this->prepareDrawState(programInfo, primitiveType); + if (!pipelineState) { + return; + } + + SkASSERT(nil == fActiveRenderCmdEncoder); + fActiveRenderCmdEncoder = fGpu->commandBuffer()->getRenderCommandEncoder( + fRenderPassDesc, pipelineState, this); + SkASSERT(fActiveRenderCmdEncoder); + + [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()]; + pipelineState->setDrawState(fActiveRenderCmdEncoder, + programInfo.pipeline().outputSwizzle(), + programInfo.pipeline().getXferProcessor()); + + bool hasDynamicScissors = programInfo.hasDynamicScissors(); + + if (!programInfo.pipeline().isScissorEnabled()) { + GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, + fRenderTarget, fOrigin, + SkIRect::MakeWH(fRenderTarget->width(), + fRenderTarget->height())); + } else if (!hasDynamicScissors) { + SkASSERT(programInfo.hasFixedScissor()); + + GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, + fRenderTarget, fOrigin, + programInfo.fixedScissor()); + } + + for (int i = 0; i < meshCount; ++i) { + const GrMesh& mesh = meshes[i]; + SkASSERT(nil != fActiveRenderCmdEncoder); + if (mesh.primitiveType() != primitiveType) { + SkDEBUGCODE(pipelineState = nullptr); + primitiveType = mesh.primitiveType(); + pipelineState = this->prepareDrawState(programInfo, primitiveType); + if (!pipelineState) { + return; + } + + [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()]; + pipelineState->setDrawState(fActiveRenderCmdEncoder, + programInfo.pipeline().outputSwizzle(), + programInfo.pipeline().getXferProcessor()); + } + + if (hasDynamicScissors) { + GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, fRenderTarget, + fOrigin, + programInfo.dynamicScissor(i)); + } + + mesh.sendToGpu(this); + } + + fActiveRenderCmdEncoder = nil; + fBounds.join(bounds); +} + +void GrMtlOpsRenderPass::onClear(const GrFixedClip& clip, const SkPMColor4f& color) { + // We should never end up here since all clears should either be done as draws or load ops in + // metal. If we hit this assert then we missed a chance to set a load op on the + // GrRenderTargetContext level. + SkASSERT(false); +} + +void GrMtlOpsRenderPass::onClearStencilClip(const GrFixedClip& clip, bool insideStencilMask) { + SkASSERT(!clip.hasWindowRectangles()); + + GrStencilAttachment* sb = fRenderTarget->renderTargetPriv().getStencilAttachment(); + // this should only be called internally when we know we have a + // stencil buffer. + SkASSERT(sb); + int stencilBitCount = sb->bits(); + + // The contract with the callers does not guarantee that we preserve all bits in the stencil + // during this clear. Thus we will clear the entire stencil to the desired value. + if (insideStencilMask) { + fRenderPassDesc.stencilAttachment.clearStencil = (1 << (stencilBitCount - 1)); + } else { + fRenderPassDesc.stencilAttachment.clearStencil = 0; + } + + fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionClear; + this->precreateCmdEncoder(); + fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionLoad; +} + +void GrMtlOpsRenderPass::initRenderState(id encoder) { + [encoder pushDebugGroup:@"initRenderState"]; + [encoder setFrontFacingWinding:MTLWindingCounterClockwise]; + // Strictly speaking we shouldn't have to set this, as the default viewport is the size of + // the drawable used to generate the renderCommandEncoder -- but just in case. + MTLViewport viewport = { 0.0, 0.0, + (double) fRenderTarget->width(), (double) fRenderTarget->height(), + 0.0, 1.0 }; + [encoder setViewport:viewport]; + this->resetBufferBindings(); + [encoder popDebugGroup]; +} + +void GrMtlOpsRenderPass::setupRenderPass( + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo) { + const static MTLLoadAction mtlLoadAction[] { + MTLLoadActionLoad, + MTLLoadActionClear, + MTLLoadActionDontCare + }; + GR_STATIC_ASSERT((int)GrLoadOp::kLoad == 0); + GR_STATIC_ASSERT((int)GrLoadOp::kClear == 1); + GR_STATIC_ASSERT((int)GrLoadOp::kDiscard == 2); + SkASSERT(colorInfo.fLoadOp <= GrLoadOp::kDiscard); + SkASSERT(stencilInfo.fLoadOp <= GrLoadOp::kDiscard); + + const static MTLStoreAction mtlStoreAction[] { + MTLStoreActionStore, + MTLStoreActionDontCare + }; + GR_STATIC_ASSERT((int)GrStoreOp::kStore == 0); + GR_STATIC_ASSERT((int)GrStoreOp::kDiscard == 1); + SkASSERT(colorInfo.fStoreOp <= GrStoreOp::kDiscard); + SkASSERT(stencilInfo.fStoreOp <= GrStoreOp::kDiscard); + + auto renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDesc.colorAttachments[0].texture = + static_cast(fRenderTarget)->mtlColorTexture(); + renderPassDesc.colorAttachments[0].slice = 0; + renderPassDesc.colorAttachments[0].level = 0; + const SkPMColor4f& clearColor = colorInfo.fClearColor; + renderPassDesc.colorAttachments[0].clearColor = + MTLClearColorMake(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + renderPassDesc.colorAttachments[0].loadAction = + mtlLoadAction[static_cast(colorInfo.fLoadOp)]; + renderPassDesc.colorAttachments[0].storeAction = + mtlStoreAction[static_cast(colorInfo.fStoreOp)]; + + const GrMtlStencilAttachment* stencil = static_cast( + fRenderTarget->renderTargetPriv().getStencilAttachment()); + if (stencil) { + renderPassDesc.stencilAttachment.texture = stencil->stencilView(); + } + renderPassDesc.stencilAttachment.clearStencil = 0; + renderPassDesc.stencilAttachment.loadAction = + mtlLoadAction[static_cast(stencilInfo.fLoadOp)]; + renderPassDesc.stencilAttachment.storeAction = + mtlStoreAction[static_cast(stencilInfo.fStoreOp)]; + + fRenderPassDesc = renderPassDesc; + + // Manage initial clears + if (colorInfo.fLoadOp == GrLoadOp::kClear || stencilInfo.fLoadOp == GrLoadOp::kClear) { + fBounds = SkRect::MakeWH(fRenderTarget->width(), + fRenderTarget->height()); + this->precreateCmdEncoder(); + if (colorInfo.fLoadOp == GrLoadOp::kClear) { + fRenderPassDesc.colorAttachments[0].loadAction = MTLLoadActionLoad; + } + if (stencilInfo.fLoadOp == GrLoadOp::kClear) { + fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionLoad; + } + } else { + fBounds.setEmpty(); + } +} + +static MTLPrimitiveType gr_to_mtl_primitive(GrPrimitiveType primitiveType) { + const static MTLPrimitiveType mtlPrimitiveType[] { + MTLPrimitiveTypeTriangle, + MTLPrimitiveTypeTriangleStrip, + MTLPrimitiveTypePoint, + MTLPrimitiveTypeLine, + MTLPrimitiveTypeLineStrip + }; + GR_STATIC_ASSERT((int)GrPrimitiveType::kTriangles == 0); + GR_STATIC_ASSERT((int)GrPrimitiveType::kTriangleStrip == 1); + GR_STATIC_ASSERT((int)GrPrimitiveType::kPoints == 2); + GR_STATIC_ASSERT((int)GrPrimitiveType::kLines == 3); + GR_STATIC_ASSERT((int)GrPrimitiveType::kLineStrip == 4); + + SkASSERT(primitiveType <= GrPrimitiveType::kLineStrip); + return mtlPrimitiveType[static_cast(primitiveType)]; +} + +void GrMtlOpsRenderPass::bindGeometry(const GrBuffer* vertexBuffer, + size_t vertexOffset, + const GrBuffer* instanceBuffer) { + size_t bufferIndex = GrMtlUniformHandler::kLastUniformBinding + 1; + if (vertexBuffer) { + SkASSERT(!vertexBuffer->isCpuBuffer()); + SkASSERT(!static_cast(vertexBuffer)->isMapped()); + + const GrMtlBuffer* grMtlBuffer = static_cast(vertexBuffer); + this->setVertexBuffer(fActiveRenderCmdEncoder, grMtlBuffer, vertexOffset, bufferIndex++); + } + if (instanceBuffer) { + SkASSERT(!instanceBuffer->isCpuBuffer()); + SkASSERT(!static_cast(instanceBuffer)->isMapped()); + + const GrMtlBuffer* grMtlBuffer = static_cast(instanceBuffer); + this->setVertexBuffer(fActiveRenderCmdEncoder, grMtlBuffer, 0, bufferIndex++); + } +} + +void GrMtlOpsRenderPass::sendMeshToGpu(GrPrimitiveType primitiveType, + const GrBuffer* vertexBuffer, + int vertexCount, + int baseVertex) { + this->bindGeometry(vertexBuffer, 0, nullptr); + + [fActiveRenderCmdEncoder drawPrimitives:gr_to_mtl_primitive(primitiveType) + vertexStart:baseVertex + vertexCount:vertexCount]; +} + +void GrMtlOpsRenderPass::sendIndexedMeshToGpu(GrPrimitiveType primitiveType, + const GrBuffer* indexBuffer, + int indexCount, + int baseIndex, + uint16_t /*minIndexValue*/, + uint16_t /*maxIndexValue*/, + const GrBuffer* vertexBuffer, + int baseVertex, + GrPrimitiveRestart restart) { + this->bindGeometry(vertexBuffer, fCurrentVertexStride*baseVertex, nullptr); + + id mtlIndexBuffer = nil; + if (indexBuffer) { + SkASSERT(!indexBuffer->isCpuBuffer()); + SkASSERT(!static_cast(indexBuffer)->isMapped()); + + mtlIndexBuffer = static_cast(indexBuffer)->mtlBuffer(); + SkASSERT(mtlIndexBuffer); + } + + SkASSERT(restart == GrPrimitiveRestart::kNo); + size_t indexOffset = static_cast(indexBuffer)->offset() + + sizeof(uint16_t) * baseIndex; + [fActiveRenderCmdEncoder drawIndexedPrimitives:gr_to_mtl_primitive(primitiveType) + indexCount:indexCount + indexType:MTLIndexTypeUInt16 + indexBuffer:mtlIndexBuffer + indexBufferOffset:indexOffset]; + fGpu->stats()->incNumDraws(); +} + +void GrMtlOpsRenderPass::sendInstancedMeshToGpu(GrPrimitiveType primitiveType, + const GrBuffer* vertexBuffer, + int vertexCount, + int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, + int baseInstance) { + this->bindGeometry(vertexBuffer, 0, instanceBuffer); + + if (@available(macOS 10.11, iOS 9.0, *)) { + [fActiveRenderCmdEncoder drawPrimitives:gr_to_mtl_primitive(primitiveType) + vertexStart:baseVertex + vertexCount:vertexCount + instanceCount:instanceCount + baseInstance:baseInstance]; + } else { + SkASSERT(false); + } +} + +void GrMtlOpsRenderPass::sendIndexedInstancedMeshToGpu(GrPrimitiveType primitiveType, + const GrBuffer* indexBuffer, + int indexCount, + int baseIndex, + const GrBuffer* vertexBuffer, + int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, + int baseInstance, + GrPrimitiveRestart restart) { + this->bindGeometry(vertexBuffer, 0, instanceBuffer); + + id mtlIndexBuffer = nil; + if (indexBuffer) { + SkASSERT(!indexBuffer->isCpuBuffer()); + SkASSERT(!static_cast(indexBuffer)->isMapped()); + + mtlIndexBuffer = static_cast(indexBuffer)->mtlBuffer(); + SkASSERT(mtlIndexBuffer); + } + + SkASSERT(restart == GrPrimitiveRestart::kNo); + size_t indexOffset = static_cast(indexBuffer)->offset() + + sizeof(uint16_t) * baseIndex; + + if (@available(macOS 10.11, iOS 9.0, *)) { + [fActiveRenderCmdEncoder drawIndexedPrimitives:gr_to_mtl_primitive(primitiveType) + indexCount:indexCount + indexType:MTLIndexTypeUInt16 + indexBuffer:mtlIndexBuffer + indexBufferOffset:indexOffset + instanceCount:instanceCount + baseVertex:baseVertex + baseInstance:baseInstance]; + } else { + SkASSERT(false); + } + fGpu->stats()->incNumDraws(); +} + +void GrMtlOpsRenderPass::setVertexBuffer(id encoder, + const GrMtlBuffer* buffer, + size_t vertexOffset, + size_t index) { + SkASSERT(index < 4); + id mtlVertexBuffer = buffer->mtlBuffer(); + SkASSERT(mtlVertexBuffer); + // Apple recommends using setVertexBufferOffset: when changing the offset + // for a currently bound vertex buffer, rather than setVertexBuffer: + size_t offset = buffer->offset() + vertexOffset; + if (fBufferBindings[index].fBuffer != mtlVertexBuffer) { + [encoder setVertexBuffer: mtlVertexBuffer + offset: offset + atIndex: index]; + fBufferBindings[index].fBuffer = mtlVertexBuffer; + fBufferBindings[index].fOffset = offset; + } else if (fBufferBindings[index].fOffset != offset) { + if (@available(macOS 10.11, iOS 8.3, *)) { + [encoder setVertexBufferOffset: offset + atIndex: index]; + } else { + [encoder setVertexBuffer: mtlVertexBuffer + offset: offset + atIndex: index]; + } + fBufferBindings[index].fOffset = offset; + } +} + +void GrMtlOpsRenderPass::resetBufferBindings() { + for (size_t i = 0; i < kNumBindings; ++i) { + fBufferBindings[i].fBuffer = nil; + } +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.h b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.h new file mode 100644 index 0000000000..e71aee2611 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.h @@ -0,0 +1,133 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlPipelineState_DEFINED +#define GrMtlPipelineState_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/mtl/GrMtlBuffer.h" +#include "src/gpu/mtl/GrMtlPipelineStateDataManager.h" + +#import + +class GrMtlGpu; +class GrMtlPipelineStateDataManager; +class GrMtlSampler; +class GrMtlTexture; +class GrPipeline; + +/** + * Wraps a MTLRenderPipelineState object and also contains more info about the pipeline as needed + * by Ganesh + */ +class GrMtlPipelineState { +public: + using UniformInfoArray = GrMtlPipelineStateDataManager::UniformInfoArray; + using UniformHandle = GrGLSLProgramDataManager::UniformHandle; + + GrMtlPipelineState( + GrMtlGpu* gpu, + id pipelineState, + MTLPixelFormat pixelFormat, + const GrGLSLBuiltinUniformHandles& builtinUniformHandles, + const UniformInfoArray& uniforms, + uint32_t uniformBufferSize, + uint32_t numSamplers, + std::unique_ptr geometryProcessor, + std::unique_ptr xferPRocessor, + std::unique_ptr[]> fragmentProcessors, + int fFragmentProcessorCnt); + + id mtlPipelineState() { return fPipelineState; } + + void setData(const GrRenderTarget*, const GrProgramInfo&); + + void setDrawState(id, const GrSwizzle& outputSwizzle, + const GrXferProcessor&); + + static void SetDynamicScissorRectState(id renderCmdEncoder, + const GrRenderTarget* renderTarget, + GrSurfaceOrigin rtOrigin, + SkIRect scissorRect); + + bool doesntSampleAttachment(const MTLRenderPassAttachmentDescriptor*) const; + +private: + /** + * We use the RT's size and origin to adjust from Skia device space to Metal normalized device + * space and to make device space positions have the correct origin for processors that require + * them. + */ + struct RenderTargetState { + SkISize fRenderTargetSize; + GrSurfaceOrigin fRenderTargetOrigin; + + RenderTargetState() { this->invalidate(); } + void invalidate() { + fRenderTargetSize.fWidth = -1; + fRenderTargetSize.fHeight = -1; + fRenderTargetOrigin = (GrSurfaceOrigin)-1; + } + + /** + * Gets a float4 that adjusts the position from Skia device coords to Metals normalized + * device coords. Assuming the transformed position, pos, is a homogeneous float3, the vec, + * v, is applied as such: + * pos.x = dot(v.xy, pos.xz) + * pos.y = dot(v.zw, pos.yz) + */ + void getRTAdjustmentVec(float* destVec) { + destVec[0] = 2.f / fRenderTargetSize.fWidth; + destVec[1] = -1.f; + if (kBottomLeft_GrSurfaceOrigin == fRenderTargetOrigin) { + destVec[2] = -2.f / fRenderTargetSize.fHeight; + destVec[3] = 1.f; + } else { + destVec[2] = 2.f / fRenderTargetSize.fHeight; + destVec[3] = -1.f; + } + } + }; + + void setRenderTargetState(const GrRenderTarget*, GrSurfaceOrigin); + + void bind(id); + + void setBlendConstants(id, const GrSwizzle&, const GrXferProcessor&); + + void setDepthStencilState(id renderCmdEncoder); + + struct SamplerBindings { + GrMtlSampler* fSampler; + id fTexture; + + SamplerBindings(const GrSamplerState& state, GrTexture* texture, GrMtlGpu*); + }; + + GrMtlGpu* fGpu; + id fPipelineState; + MTLPixelFormat fPixelFormat; + + RenderTargetState fRenderTargetState; + GrGLSLBuiltinUniformHandles fBuiltinUniformHandles; + + GrStencilSettings fStencil; + + int fNumSamplers; + SkTArray fSamplerBindings; + + std::unique_ptr fGeometryProcessor; + std::unique_ptr fXferProcessor; + std::unique_ptr[]> fFragmentProcessors; + int fFragmentProcessorCnt; + + GrMtlPipelineStateDataManager fDataManager; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.mm new file mode 100644 index 0000000000..83af603731 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineState.mm @@ -0,0 +1,248 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlPipelineState.h" + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" +#include "src/gpu/mtl/GrMtlBuffer.h" +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlTexture.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlPipelineState::SamplerBindings::SamplerBindings(const GrSamplerState& state, + GrTexture* texture, + GrMtlGpu* gpu) + : fTexture(static_cast(texture)->mtlTexture()) { + fSampler = gpu->resourceProvider().findOrCreateCompatibleSampler(state); +} + +GrMtlPipelineState::GrMtlPipelineState( + GrMtlGpu* gpu, + id pipelineState, + MTLPixelFormat pixelFormat, + const GrGLSLBuiltinUniformHandles& builtinUniformHandles, + const UniformInfoArray& uniforms, + uint32_t uniformBufferSize, + uint32_t numSamplers, + std::unique_ptr geometryProcessor, + std::unique_ptr xferProcessor, + std::unique_ptr[]> fragmentProcessors, + int fragmentProcessorCnt) + : fGpu(gpu) + , fPipelineState(pipelineState) + , fPixelFormat(pixelFormat) + , fBuiltinUniformHandles(builtinUniformHandles) + , fNumSamplers(numSamplers) + , fGeometryProcessor(std::move(geometryProcessor)) + , fXferProcessor(std::move(xferProcessor)) + , fFragmentProcessors(std::move(fragmentProcessors)) + , fFragmentProcessorCnt(fragmentProcessorCnt) + , fDataManager(uniforms, uniformBufferSize) { + (void) fPixelFormat; // Suppress unused-var warning. +} + +void GrMtlPipelineState::setData(const GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo) { + + // Note: the Metal backend currently only supports fixed primProc textures + SkASSERT(!programInfo.hasDynamicPrimProcTextures()); + auto proxies = programInfo.hasFixedPrimProcTextures() ? programInfo.fixedPrimProcTextures() + : nullptr; + + this->setRenderTargetState(renderTarget, programInfo.origin()); + fGeometryProcessor->setData(fDataManager, programInfo.primProc(), + GrFragmentProcessor::CoordTransformIter(programInfo.pipeline())); + fSamplerBindings.reset(); + for (int i = 0; i < programInfo.primProc().numTextureSamplers(); ++i) { + const auto& sampler = programInfo.primProc().textureSampler(i); + auto texture = static_cast(proxies[i]->peekTexture()); + fSamplerBindings.emplace_back(sampler.samplerState(), texture, fGpu); + } + + GrFragmentProcessor::Iter iter(programInfo.pipeline()); + GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt); + const GrFragmentProcessor* fp = iter.next(); + GrGLSLFragmentProcessor* glslFP = glslIter.next(); + while (fp && glslFP) { + glslFP->setData(fDataManager, *fp); + for (int i = 0; i < fp->numTextureSamplers(); ++i) { + const auto& sampler = fp->textureSampler(i); + fSamplerBindings.emplace_back(sampler.samplerState(), sampler.peekTexture(), fGpu); + } + fp = iter.next(); + glslFP = glslIter.next(); + } + SkASSERT(!fp && !glslFP); + + { + SkIPoint offset; + GrTexture* dstTexture = programInfo.pipeline().peekDstTexture(&offset); + + fXferProcessor->setData(fDataManager, programInfo.pipeline().getXferProcessor(), + dstTexture, offset); + } + + if (GrTextureProxy* dstTextureProxy = programInfo.pipeline().dstTextureProxy()) { + fSamplerBindings.emplace_back(GrSamplerState::ClampNearest(), + dstTextureProxy->peekTexture(), + fGpu); + } + + SkASSERT(fNumSamplers == fSamplerBindings.count()); + fDataManager.resetDirtyBits(); + + if (programInfo.pipeline().isStencilEnabled()) { + SkASSERT(renderTarget->renderTargetPriv().getStencilAttachment()); + fStencil.reset(*programInfo.pipeline().getUserStencil(), + programInfo.pipeline().hasStencilClip(), + renderTarget->renderTargetPriv().numStencilBits()); + } +} + +void GrMtlPipelineState::setDrawState(id renderCmdEncoder, + const GrSwizzle& outputSwizzle, + const GrXferProcessor& xferProcessor) { + [renderCmdEncoder pushDebugGroup:@"setDrawState"]; + this->bind(renderCmdEncoder); + this->setBlendConstants(renderCmdEncoder, outputSwizzle, xferProcessor); + this->setDepthStencilState(renderCmdEncoder); + [renderCmdEncoder popDebugGroup]; +} + +void GrMtlPipelineState::bind(id renderCmdEncoder) { + fDataManager.uploadAndBindUniformBuffers(fGpu, renderCmdEncoder); + + SkASSERT(fNumSamplers == fSamplerBindings.count()); + for (int index = 0; index < fNumSamplers; ++index) { + [renderCmdEncoder setFragmentTexture: fSamplerBindings[index].fTexture + atIndex: index]; + [renderCmdEncoder setFragmentSamplerState: fSamplerBindings[index].fSampler->mtlSampler() + atIndex: index]; + } +} + +void GrMtlPipelineState::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin) { + // Load the RT height uniform if it is needed to y-flip gl_FragCoord. + if (fBuiltinUniformHandles.fRTHeightUni.isValid() && + fRenderTargetState.fRenderTargetSize.fHeight != rt->height()) { + fDataManager.set1f(fBuiltinUniformHandles.fRTHeightUni, SkIntToScalar(rt->height())); + } + + // set RT adjustment + SkISize size; + size.set(rt->width(), rt->height()); + SkASSERT(fBuiltinUniformHandles.fRTAdjustmentUni.isValid()); + if (fRenderTargetState.fRenderTargetOrigin != origin || + fRenderTargetState.fRenderTargetSize != size) { + fRenderTargetState.fRenderTargetSize = size; + fRenderTargetState.fRenderTargetOrigin = origin; + + float rtAdjustmentVec[4]; + fRenderTargetState.getRTAdjustmentVec(rtAdjustmentVec); + fDataManager.set4fv(fBuiltinUniformHandles.fRTAdjustmentUni, 1, rtAdjustmentVec); + } +} + +static bool blend_coeff_refs_constant(GrBlendCoeff coeff) { + switch (coeff) { + case kConstC_GrBlendCoeff: + case kIConstC_GrBlendCoeff: + case kConstA_GrBlendCoeff: + case kIConstA_GrBlendCoeff: + return true; + default: + return false; + } +} + +void GrMtlPipelineState::setBlendConstants(id renderCmdEncoder, + const GrSwizzle& swizzle, + const GrXferProcessor& xferProcessor) { + if (!renderCmdEncoder) { + return; + } + + const GrXferProcessor::BlendInfo& blendInfo = xferProcessor.getBlendInfo(); + GrBlendCoeff srcCoeff = blendInfo.fSrcBlend; + GrBlendCoeff dstCoeff = blendInfo.fDstBlend; + if (blend_coeff_refs_constant(srcCoeff) || blend_coeff_refs_constant(dstCoeff)) { + // Swizzle the blend to match what the shader will output. + SkPMColor4f blendConst = swizzle.applyTo(blendInfo.fBlendConstant); + + [renderCmdEncoder setBlendColorRed: blendConst.fR + green: blendConst.fG + blue: blendConst.fB + alpha: blendConst.fA]; + } +} + +void GrMtlPipelineState::setDepthStencilState(id renderCmdEncoder) { + const GrSurfaceOrigin& origin = fRenderTargetState.fRenderTargetOrigin; + GrMtlDepthStencil* state = + fGpu->resourceProvider().findOrCreateCompatibleDepthStencilState(fStencil, origin); + if (!fStencil.isDisabled()) { + if (fStencil.isTwoSided()) { + if (@available(macOS 10.11, iOS 9.0, *)) { + [renderCmdEncoder setStencilFrontReferenceValue:fStencil.front(origin).fRef + backReferenceValue:fStencil.back(origin).fRef]; + } else { + // Two-sided stencil not supported on older versions of iOS + // TODO: Find a way to recover from this + SkASSERT(false); + } + } else { + [renderCmdEncoder setStencilReferenceValue:fStencil.frontAndBack().fRef]; + } + } + [renderCmdEncoder setDepthStencilState:state->mtlDepthStencil()]; +} + +void GrMtlPipelineState::SetDynamicScissorRectState(id renderCmdEncoder, + const GrRenderTarget* renderTarget, + GrSurfaceOrigin rtOrigin, + SkIRect scissorRect) { + if (!scissorRect.intersect(SkIRect::MakeWH(renderTarget->width(), renderTarget->height()))) { + scissorRect.setEmpty(); + } + + MTLScissorRect scissor; + scissor.x = scissorRect.fLeft; + scissor.width = scissorRect.width(); + if (kTopLeft_GrSurfaceOrigin == rtOrigin) { + scissor.y = scissorRect.fTop; + } else { + SkASSERT(kBottomLeft_GrSurfaceOrigin == rtOrigin); + scissor.y = renderTarget->height() - scissorRect.fBottom; + } + scissor.height = scissorRect.height(); + + SkASSERT(scissor.x >= 0); + SkASSERT(scissor.y >= 0); + + [renderCmdEncoder setScissorRect: scissor]; +} + +bool GrMtlPipelineState::doesntSampleAttachment( + const MTLRenderPassAttachmentDescriptor* attachment) const { + for (int i = 0; i < fSamplerBindings.count(); ++i) { + if (attachment.texture == fSamplerBindings[i].fTexture) { + return false; + } + } + return true; +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h new file mode 100644 index 0000000000..fda1e6583d --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlPipelineStateBuilder_DEFINED +#define GrMtlPipelineStateBuilder_DEFINED + +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/mtl/GrMtlUniformHandler.h" +#include "src/gpu/mtl/GrMtlVaryingHandler.h" +#include "src/sksl/SkSLCompiler.h" + +#import + +class GrProgramInfo; +class GrMtlGpu; +class GrMtlPipelineState; + +class GrMtlPipelineStateBuilder : public GrGLSLProgramBuilder { +public: + /** + * For Metal we want to cache the entire pipeline for reuse of draws. The Desc here holds all + * the information needed to differentiate one pipeline from another. + * + * The GrProgramDesc contains all the information need to create the actual shaders for the + * pipeline. + * + * For Metal we need to add to the GrProgramDesc to include the rest of the state on the + * pipeline. This includes blending information and primitive type. The pipeline is immutable + * so any remaining dynamic state is set via the MtlRenderCmdEncoder. + */ + class Desc : public GrProgramDesc { + public: + static bool Build(Desc*, GrRenderTarget*, + const GrProgramInfo&, GrPrimitiveType, GrMtlGpu* gpu); + + size_t shaderKeyLength() const { return fShaderKeyLength; } + + private: + size_t fShaderKeyLength; + + typedef GrProgramDesc INHERITED; + }; + + /** Generates a pipeline state. + * + * The GrMtlPipelineState implements what is specified in the GrPipeline and + * GrPrimitiveProcessor as input. After successful generation, the builder result objects are + * available to be used. This function may modify the program key by setting the surface origin + * key to 0 (unspecified) if it turns out the program does not care about the surface origin. + * @return true if generation was successful. + */ + static GrMtlPipelineState* CreatePipelineState(GrMtlGpu*, + GrRenderTarget*, + const GrProgramInfo&, + Desc*); + +private: + GrMtlPipelineStateBuilder(GrMtlGpu*, GrRenderTarget*, const GrProgramInfo&, GrProgramDesc*); + + GrMtlPipelineState* finalize(GrRenderTarget*, const GrProgramInfo&, Desc*); + + const GrCaps* caps() const override; + + void finalizeFragmentOutputColor(GrShaderVar& outputColor) override; + + void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) override; + + id createMtlShaderLibrary(const GrGLSLShaderBuilder& builder, + SkSL::Program::Kind kind, + const SkSL::Program::Settings& settings, + GrProgramDesc* desc); + + GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; } + const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; } + GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; } + + GrMtlGpu* fGpu; + GrMtlUniformHandler fUniformHandler; + GrMtlVaryingHandler fVaryingHandler; + + typedef GrGLSLProgramBuilder INHERITED; +}; +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm new file mode 100644 index 0000000000..090c41b9de --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm @@ -0,0 +1,475 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlPipelineStateBuilder.h" + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" + +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlPipelineState.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#include "src/gpu/GrRenderTargetPriv.h" + +#import + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(GrMtlGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + Desc* desc) { + GrMtlPipelineStateBuilder builder(gpu, renderTarget, programInfo, desc); + + if (!builder.emitAndInstallProcs()) { + return nullptr; + } + return builder.finalize(renderTarget, programInfo, desc); +} + +GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrMtlGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrProgramDesc* desc) + : INHERITED(renderTarget, programInfo, desc) + , fGpu(gpu) + , fUniformHandler(this) + , fVaryingHandler(this) { +} + +const GrCaps* GrMtlPipelineStateBuilder::caps() const { + return fGpu->caps(); +} + +void GrMtlPipelineStateBuilder::finalizeFragmentOutputColor(GrShaderVar& outputColor) { + outputColor.addLayoutQualifier("location = 0, index = 0"); +} + +void GrMtlPipelineStateBuilder::finalizeFragmentSecondaryColor(GrShaderVar& outputColor) { + outputColor.addLayoutQualifier("location = 0, index = 1"); +} + +id GrMtlPipelineStateBuilder::createMtlShaderLibrary( + const GrGLSLShaderBuilder& builder, + SkSL::Program::Kind kind, + const SkSL::Program::Settings& settings, + GrProgramDesc* desc) { + SkSL::Program::Inputs inputs; + id shaderLibrary = GrCompileMtlShaderLibrary(fGpu, builder.fCompilerString.c_str(), + kind, settings, &inputs); + if (shaderLibrary == nil) { + return nil; + } + if (inputs.fRTHeight) { + this->addRTHeightUniform(SKSL_RTHEIGHT_NAME); + } + return shaderLibrary; +} + +static inline MTLVertexFormat attribute_type_to_mtlformat(GrVertexAttribType type) { + switch (type) { + case kFloat_GrVertexAttribType: + return MTLVertexFormatFloat; + case kFloat2_GrVertexAttribType: + return MTLVertexFormatFloat2; + case kFloat3_GrVertexAttribType: + return MTLVertexFormatFloat3; + case kFloat4_GrVertexAttribType: + return MTLVertexFormatFloat4; + case kHalf_GrVertexAttribType: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatHalf; + } else { + return MTLVertexFormatInvalid; + } + case kHalf2_GrVertexAttribType: + return MTLVertexFormatHalf2; + case kHalf3_GrVertexAttribType: + return MTLVertexFormatHalf3; + case kHalf4_GrVertexAttribType: + return MTLVertexFormatHalf4; + case kInt2_GrVertexAttribType: + return MTLVertexFormatInt2; + case kInt3_GrVertexAttribType: + return MTLVertexFormatInt3; + case kInt4_GrVertexAttribType: + return MTLVertexFormatInt4; + case kByte_GrVertexAttribType: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatChar; + } else { + return MTLVertexFormatInvalid; + } + case kByte2_GrVertexAttribType: + return MTLVertexFormatChar2; + case kByte3_GrVertexAttribType: + return MTLVertexFormatChar3; + case kByte4_GrVertexAttribType: + return MTLVertexFormatChar4; + case kUByte_GrVertexAttribType: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatUChar; + } else { + return MTLVertexFormatInvalid; + } + case kUByte2_GrVertexAttribType: + return MTLVertexFormatUChar2; + case kUByte3_GrVertexAttribType: + return MTLVertexFormatUChar3; + case kUByte4_GrVertexAttribType: + return MTLVertexFormatUChar4; + case kUByte_norm_GrVertexAttribType: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatUCharNormalized; + } else { + return MTLVertexFormatInvalid; + } + case kUByte4_norm_GrVertexAttribType: + return MTLVertexFormatUChar4Normalized; + case kShort2_GrVertexAttribType: + return MTLVertexFormatShort2; + case kShort4_GrVertexAttribType: + return MTLVertexFormatShort4; + case kUShort2_GrVertexAttribType: + return MTLVertexFormatUShort2; + case kUShort2_norm_GrVertexAttribType: + return MTLVertexFormatUShort2Normalized; + case kInt_GrVertexAttribType: + return MTLVertexFormatInt; + case kUint_GrVertexAttribType: + return MTLVertexFormatUInt; + case kUShort_norm_GrVertexAttribType: + if (@available(macOS 10.13, iOS 11.0, *)) { + return MTLVertexFormatUShortNormalized; + } else { + return MTLVertexFormatInvalid; + } + case kUShort4_norm_GrVertexAttribType: + return MTLVertexFormatUShort4Normalized; + } + SK_ABORT("Unknown vertex attribute type"); +} + +static MTLVertexDescriptor* create_vertex_descriptor(const GrPrimitiveProcessor& primProc) { + uint32_t vertexBinding = 0, instanceBinding = 0; + + int nextBinding = GrMtlUniformHandler::kLastUniformBinding + 1; + if (primProc.hasVertexAttributes()) { + vertexBinding = nextBinding++; + } + + if (primProc.hasInstanceAttributes()) { + instanceBinding = nextBinding; + } + + auto vertexDescriptor = [[MTLVertexDescriptor alloc] init]; + int attributeIndex = 0; + + int vertexAttributeCount = primProc.numVertexAttributes(); + size_t vertexAttributeOffset = 0; + for (const auto& attribute : primProc.vertexAttributes()) { + MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex]; + mtlAttribute.format = attribute_type_to_mtlformat(attribute.cpuType()); + SkASSERT(MTLVertexFormatInvalid != mtlAttribute.format); + mtlAttribute.offset = vertexAttributeOffset; + mtlAttribute.bufferIndex = vertexBinding; + + vertexAttributeOffset += attribute.sizeAlign4(); + attributeIndex++; + } + SkASSERT(vertexAttributeOffset == primProc.vertexStride()); + + if (vertexAttributeCount) { + MTLVertexBufferLayoutDescriptor* vertexBufferLayout = + vertexDescriptor.layouts[vertexBinding]; + vertexBufferLayout.stepFunction = MTLVertexStepFunctionPerVertex; + vertexBufferLayout.stepRate = 1; + vertexBufferLayout.stride = vertexAttributeOffset; + } + + int instanceAttributeCount = primProc.numInstanceAttributes(); + size_t instanceAttributeOffset = 0; + for (const auto& attribute : primProc.instanceAttributes()) { + MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex]; + mtlAttribute.format = attribute_type_to_mtlformat(attribute.cpuType()); + mtlAttribute.offset = instanceAttributeOffset; + mtlAttribute.bufferIndex = instanceBinding; + + instanceAttributeOffset += attribute.sizeAlign4(); + attributeIndex++; + } + SkASSERT(instanceAttributeOffset == primProc.instanceStride()); + + if (instanceAttributeCount) { + MTLVertexBufferLayoutDescriptor* instanceBufferLayout = + vertexDescriptor.layouts[instanceBinding]; + instanceBufferLayout.stepFunction = MTLVertexStepFunctionPerInstance; + instanceBufferLayout.stepRate = 1; + instanceBufferLayout.stride = instanceAttributeOffset; + } + return vertexDescriptor; +} + +static MTLBlendFactor blend_coeff_to_mtl_blend(GrBlendCoeff coeff) { + switch (coeff) { + case kZero_GrBlendCoeff: + return MTLBlendFactorZero; + case kOne_GrBlendCoeff: + return MTLBlendFactorOne; + case kSC_GrBlendCoeff: + return MTLBlendFactorSourceColor; + case kISC_GrBlendCoeff: + return MTLBlendFactorOneMinusSourceColor; + case kDC_GrBlendCoeff: + return MTLBlendFactorDestinationColor; + case kIDC_GrBlendCoeff: + return MTLBlendFactorOneMinusDestinationColor; + case kSA_GrBlendCoeff: + return MTLBlendFactorSourceAlpha; + case kISA_GrBlendCoeff: + return MTLBlendFactorOneMinusSourceAlpha; + case kDA_GrBlendCoeff: + return MTLBlendFactorDestinationAlpha; + case kIDA_GrBlendCoeff: + return MTLBlendFactorOneMinusDestinationAlpha; + case kConstC_GrBlendCoeff: + return MTLBlendFactorBlendColor; + case kIConstC_GrBlendCoeff: + return MTLBlendFactorOneMinusBlendColor; + case kConstA_GrBlendCoeff: + return MTLBlendFactorBlendAlpha; + case kIConstA_GrBlendCoeff: + return MTLBlendFactorOneMinusBlendAlpha; + case kS2C_GrBlendCoeff: + if (@available(macOS 10.12, iOS 11.0, *)) { + return MTLBlendFactorSource1Color; + } else { + return MTLBlendFactorZero; + } + case kIS2C_GrBlendCoeff: + if (@available(macOS 10.12, iOS 11.0, *)) { + return MTLBlendFactorOneMinusSource1Color; + } else { + return MTLBlendFactorZero; + } + case kS2A_GrBlendCoeff: + if (@available(macOS 10.12, iOS 11.0, *)) { + return MTLBlendFactorSource1Alpha; + } else { + return MTLBlendFactorZero; + } + case kIS2A_GrBlendCoeff: + if (@available(macOS 10.12, iOS 11.0, *)) { + return MTLBlendFactorOneMinusSource1Alpha; + } else { + return MTLBlendFactorZero; + } + case kIllegal_GrBlendCoeff: + return MTLBlendFactorZero; + } + + SK_ABORT("Unknown blend coefficient"); +} + +static MTLBlendOperation blend_equation_to_mtl_blend_op(GrBlendEquation equation) { + static const MTLBlendOperation gTable[] = { + MTLBlendOperationAdd, // kAdd_GrBlendEquation + MTLBlendOperationSubtract, // kSubtract_GrBlendEquation + MTLBlendOperationReverseSubtract, // kReverseSubtract_GrBlendEquation + }; + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gTable) == kFirstAdvancedGrBlendEquation); + GR_STATIC_ASSERT(0 == kAdd_GrBlendEquation); + GR_STATIC_ASSERT(1 == kSubtract_GrBlendEquation); + GR_STATIC_ASSERT(2 == kReverseSubtract_GrBlendEquation); + + SkASSERT((unsigned)equation < kGrBlendEquationCnt); + return gTable[equation]; +} + +static MTLRenderPipelineColorAttachmentDescriptor* create_color_attachment( + GrPixelConfig config, const GrPipeline& pipeline) { + auto mtlColorAttachment = [[MTLRenderPipelineColorAttachmentDescriptor alloc] init]; + + // pixel format + MTLPixelFormat format; + SkAssertResult(GrPixelConfigToMTLFormat(config, &format)); + mtlColorAttachment.pixelFormat = format; + + // blending + const GrXferProcessor::BlendInfo& blendInfo = pipeline.getXferProcessor().getBlendInfo(); + + GrBlendEquation equation = blendInfo.fEquation; + GrBlendCoeff srcCoeff = blendInfo.fSrcBlend; + GrBlendCoeff dstCoeff = blendInfo.fDstBlend; + bool blendOff = (kAdd_GrBlendEquation == equation || kSubtract_GrBlendEquation == equation) && + kOne_GrBlendCoeff == srcCoeff && kZero_GrBlendCoeff == dstCoeff; + + mtlColorAttachment.blendingEnabled = !blendOff; + if (!blendOff) { + mtlColorAttachment.sourceRGBBlendFactor = blend_coeff_to_mtl_blend(srcCoeff); + mtlColorAttachment.destinationRGBBlendFactor = blend_coeff_to_mtl_blend(dstCoeff); + mtlColorAttachment.rgbBlendOperation = blend_equation_to_mtl_blend_op(equation); + mtlColorAttachment.sourceAlphaBlendFactor = blend_coeff_to_mtl_blend(srcCoeff); + mtlColorAttachment.destinationAlphaBlendFactor = blend_coeff_to_mtl_blend(dstCoeff); + mtlColorAttachment.alphaBlendOperation = blend_equation_to_mtl_blend_op(equation); + } + + if (!blendInfo.fWriteColor) { + mtlColorAttachment.writeMask = MTLColorWriteMaskNone; + } else { + mtlColorAttachment.writeMask = MTLColorWriteMaskAll; + } + return mtlColorAttachment; +} + +uint32_t buffer_size(uint32_t offset, uint32_t maxAlignment) { + // Metal expects the buffer to be padded at the end according to the alignment + // of the largest element in the buffer. + uint32_t offsetDiff = offset & maxAlignment; + if (offsetDiff != 0) { + offsetDiff = maxAlignment - offsetDiff + 1; + } + return offset + offsetDiff; +} + +GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + Desc* desc) { + auto pipelineDescriptor = [MTLRenderPipelineDescriptor new]; + + fVS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n"); + fFS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n"); + fVS.extensions().appendf("#extension GL_ARB_shading_language_420pack : enable\n"); + fFS.extensions().appendf("#extension GL_ARB_shading_language_420pack : enable\n"); + + this->finalizeShaders(); + + SkSL::Program::Settings settings; + settings.fCaps = this->caps()->shaderCaps(); + settings.fFlipY = this->origin() != kTopLeft_GrSurfaceOrigin; + settings.fSharpenTextures = fGpu->getContext()->priv().options().fSharpenMipmappedTextures; + SkASSERT(!this->fragColorIsInOut()); + + // TODO: Store shaders in cache + id vertexLibrary = nil; + id fragmentLibrary = nil; + vertexLibrary = this->createMtlShaderLibrary(fVS, + SkSL::Program::kVertex_Kind, + settings, + desc); + fragmentLibrary = this->createMtlShaderLibrary(fFS, + SkSL::Program::kFragment_Kind, + settings, + desc); + SkASSERT(!this->primitiveProcessor().willUseGeoShader()); + + if (!vertexLibrary || !fragmentLibrary) { + return nullptr; + } + + id vertexFunction = [vertexLibrary newFunctionWithName: @"vertexMain"]; + id fragmentFunction = [fragmentLibrary newFunctionWithName: @"fragmentMain"]; + + if (vertexFunction == nil) { + SkDebugf("Couldn't find vertexMain() in library\n"); + return nullptr; + } + if (fragmentFunction == nil) { + SkDebugf("Couldn't find fragmentMain() in library\n"); + return nullptr; + } + + pipelineDescriptor.vertexFunction = vertexFunction; + pipelineDescriptor.fragmentFunction = fragmentFunction; + pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(programInfo.primProc()); + pipelineDescriptor.colorAttachments[0] = create_color_attachment(renderTarget->config(), + programInfo.pipeline()); + pipelineDescriptor.sampleCount = renderTarget->numSamples(); + bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment()); + GrMtlCaps* mtlCaps = (GrMtlCaps*)this->caps(); + pipelineDescriptor.stencilAttachmentPixelFormat = + hasStencilAttachment ? mtlCaps->preferredStencilFormat().fInternalFormat + : MTLPixelFormatInvalid; + + SkASSERT(pipelineDescriptor.vertexFunction); + SkASSERT(pipelineDescriptor.fragmentFunction); + SkASSERT(pipelineDescriptor.vertexDescriptor); + SkASSERT(pipelineDescriptor.colorAttachments[0]); + +#if defined(SK_BUILD_FOR_MAC) && defined(GR_USE_COMPLETION_HANDLER) + bool timedout; + id pipelineState = GrMtlNewRenderPipelineStateWithDescriptor( + fGpu->device(), pipelineDescriptor, &timedout); + if (timedout) { + // try a second time + pipelineState = GrMtlNewRenderPipelineStateWithDescriptor( + fGpu->device(), pipelineDescriptor, &timedout); + } + if (!pipelineState) { + return nullptr; + } +#else + NSError* error = nil; + id pipelineState = + [fGpu->device() newRenderPipelineStateWithDescriptor: pipelineDescriptor + error: &error]; + if (error) { + SkDebugf("Error creating pipeline: %s\n", + [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]); + return nullptr; + } +#endif + + uint32_t bufferSize = buffer_size(fUniformHandler.fCurrentUBOOffset, + fUniformHandler.fCurrentUBOMaxAlignment); + return new GrMtlPipelineState(fGpu, + pipelineState, + pipelineDescriptor.colorAttachments[0].pixelFormat, + fUniformHandles, + fUniformHandler.fUniforms, + bufferSize, + (uint32_t)fUniformHandler.numSamplers(), + std::move(fGeometryProcessor), + std::move(fXferProcessor), + std::move(fFragmentProcessors), + fFragmentProcessorCnt); +} + +////////////////////////////////////////////////////////////////////////////// + +bool GrMtlPipelineStateBuilder::Desc::Build(Desc* desc, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + GrMtlGpu* gpu) { + if (!GrProgramDesc::Build(desc, renderTarget, programInfo, primitiveType, gpu)) { + return false; + } + + GrProcessorKeyBuilder b(&desc->key()); + + int keyLength = desc->key().count(); + SkASSERT(0 == (keyLength % 4)); + desc->fShaderKeyLength = SkToU32(keyLength); + + b.add32(renderTarget->config()); + b.add32(renderTarget->numSamples()); + bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment()); + b.add32(hasStencilAttachment ? gpu->mtlCaps().preferredStencilFormat().fInternalFormat + : MTLPixelFormatInvalid); + b.add32((uint32_t)programInfo.pipeline().isStencilEnabled()); + // Stencil samples don't seem to be tracked in the MTLRenderPipeline + + b.add32(programInfo.pipeline().getBlendInfoKey()); + + b.add32((uint32_t)primitiveType); + + return true; +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.h b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.h new file mode 100644 index 0000000000..b3a3dac7c6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlPipelineStateDataManager_DEFINED +#define GrMtlPipelineStateDataManager_DEFINED + +#include "src/core/SkAutoMalloc.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/mtl/GrMtlUniformHandler.h" + +#import + +class GrMtlBuffer; +class GrMtlGpu; + +class GrMtlPipelineStateDataManager : public GrGLSLProgramDataManager { +public: + typedef GrMtlUniformHandler::UniformInfoArray UniformInfoArray; + + GrMtlPipelineStateDataManager(const UniformInfoArray&, + uint32_t uniformSize); + + void set1i(UniformHandle, int32_t) const override; + void set1iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set1f(UniformHandle, float v0) const override; + void set1fv(UniformHandle, int arrayCount, const float v[]) const override; + void set2i(UniformHandle, int32_t, int32_t) const override; + void set2iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set2f(UniformHandle, float, float) const override; + void set2fv(UniformHandle, int arrayCount, const float v[]) const override; + void set3i(UniformHandle, int32_t, int32_t, int32_t) const override; + void set3iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set3f(UniformHandle, float, float, float) const override; + void set3fv(UniformHandle, int arrayCount, const float v[]) const override; + void set4i(UniformHandle, int32_t, int32_t, int32_t, int32_t) const override; + void set4iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set4f(UniformHandle, float, float, float, float) const override; + void set4fv(UniformHandle, int arrayCount, const float v[]) const override; + // matrices are column-major, the first two upload a single matrix, the latter two upload + // arrayCount matrices into a uniform array. + void setMatrix2f(UniformHandle, const float matrix[]) const override; + void setMatrix3f(UniformHandle, const float matrix[]) const override; + void setMatrix4f(UniformHandle, const float matrix[]) const override; + void setMatrix2fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix3fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix4fv(UniformHandle, int arrayCount, const float matrices[]) const override; + + // for nvpr only + void setPathFragmentInputTransform(VaryingHandle u, int components, + const SkMatrix& matrix) const override { + SK_ABORT("Only supported in NVPR, which is not in Metal"); + } + + void uploadAndBindUniformBuffers(GrMtlGpu* gpu, + id renderCmdEncoder) const; + void resetDirtyBits(); + +private: + struct Uniform { + uint32_t fOffset; + SkDEBUGCODE( + GrSLType fType; + int fArrayCount; + ); + }; + + template inline void setMatrices(UniformHandle, int arrayCount, + const float matrices[]) const; + + void* getBufferPtrAndMarkDirty(const Uniform& uni) const; + + uint32_t fUniformSize; + + SkTArray fUniforms; + + mutable SkAutoMalloc fUniformData; + mutable bool fUniformsDirty; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.mm new file mode 100644 index 0000000000..1f2af241ad --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.mm @@ -0,0 +1,362 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlPipelineStateDataManager.h" + +#include "src/gpu/mtl/GrMtlBuffer.h" +#include "src/gpu/mtl/GrMtlGpu.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlPipelineStateDataManager::GrMtlPipelineStateDataManager(const UniformInfoArray& uniforms, + uint32_t uniformSize) + : fUniformSize(uniformSize) + , fUniformsDirty(false) { + fUniformData.reset(uniformSize); + int count = uniforms.count(); + fUniforms.push_back_n(count); + // We must add uniforms in same order is the UniformInfoArray so that UniformHandles already + // owned by other objects will still match up here. + for (int i = 0; i < count; i++) { + Uniform& uniform = fUniforms[i]; + const GrMtlUniformHandler::UniformInfo uniformInfo = uniforms[i]; + SkASSERT(GrShaderVar::kNonArray == uniformInfo.fVariable.getArrayCount() || + uniformInfo.fVariable.getArrayCount() > 0); + SkDEBUGCODE( + uniform.fArrayCount = uniformInfo.fVariable.getArrayCount(); + uniform.fType = uniformInfo.fVariable.getType(); + ); + uniform.fOffset = uniformInfo.fUBOffset; + } +} + +void* GrMtlPipelineStateDataManager::getBufferPtrAndMarkDirty(const Uniform& uni) const { + fUniformsDirty = true; + return static_cast(fUniformData.get())+uni.fOffset; +} + +void GrMtlPipelineStateDataManager::set1i(UniformHandle u, int32_t i) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt_GrSLType || uni.fType == kShort_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + memcpy(buffer, &i, sizeof(int32_t)); +} + +void GrMtlPipelineStateDataManager::set1iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt_GrSLType || uni.fType == kShort_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[i]; + memcpy(buffer, curVec, sizeof(int32_t)); + buffer = static_cast(buffer) + 4*sizeof(int32_t); + } +} + +void GrMtlPipelineStateDataManager::set1f(UniformHandle u, float v0) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat_GrSLType || uni.fType == kHalf_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + memcpy(buffer, &v0, sizeof(float)); +} + +void GrMtlPipelineStateDataManager::set1fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat_GrSLType || uni.fType == kHalf_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + memcpy(buffer, v, arrayCount * sizeof(float)); +} + +void GrMtlPipelineStateDataManager::set2i(UniformHandle u, int32_t i0, int32_t i1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt2_GrSLType || uni.fType == kShort2_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[2] = { i0, i1 }; + memcpy(buffer, v, 2 * sizeof(int32_t)); +} + +void GrMtlPipelineStateDataManager::set2iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt2_GrSLType || uni.fType == kShort2_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + memcpy(buffer, v, arrayCount*sizeof(int32_t)); +} + +void GrMtlPipelineStateDataManager::set2f(UniformHandle u, float v0, float v1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2_GrSLType || uni.fType == kHalf2_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + float v[2] = { v0, v1 }; + memcpy(buffer, v, 2 * sizeof(float)); +} + +void GrMtlPipelineStateDataManager::set2fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2_GrSLType || uni.fType == kHalf2_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + memcpy(buffer, v, arrayCount * 2 * sizeof(float)); +} + +void GrMtlPipelineStateDataManager::set3i(UniformHandle u, + int32_t i0, + int32_t i1, + int32_t i2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt3_GrSLType || uni.fType == kShort3_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[3] = { i0, i1, i2 }; + memcpy(buffer, v, 3 * sizeof(int32_t)); +} + +void GrMtlPipelineStateDataManager::set3iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt3_GrSLType || uni.fType == kShort3_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[3 * i]; + memcpy(buffer, curVec, 3 * sizeof(int32_t)); + buffer = static_cast(buffer) + 4*sizeof(int32_t); + } +} + +void GrMtlPipelineStateDataManager::set3f(UniformHandle u, float v0, float v1, float v2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat3_GrSLType || uni.fType == kHalf3_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + float v[3] = { v0, v1, v2 }; + memcpy(buffer, v, 3 * sizeof(float)); +} + +void GrMtlPipelineStateDataManager::set3fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat3_GrSLType || uni.fType == kHalf3_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[3 * i]; + memcpy(buffer, curVec, 3 * sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrMtlPipelineStateDataManager::set4i(UniformHandle u, + int32_t i0, + int32_t i1, + int32_t i2, + int32_t i3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt4_GrSLType || uni.fType == kShort4_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[4] = { i0, i1, i2, i3 }; + memcpy(buffer, v, 4 * sizeof(int32_t)); +} + +void GrMtlPipelineStateDataManager::set4iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt4_GrSLType || uni.fType == kShort4_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + memcpy(buffer, v, arrayCount * 4 * sizeof(int32_t)); +} + +void GrMtlPipelineStateDataManager::set4f(UniformHandle u, + float v0, + float v1, + float v2, + float v3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat4_GrSLType || uni.fType == kHalf4_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + float v[4] = { v0, v1, v2, v3 }; + memcpy(buffer, v, 4 * sizeof(float)); +} + +void GrMtlPipelineStateDataManager::set4fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat4_GrSLType || uni.fType == kHalf4_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + memcpy(buffer, v, arrayCount * 4 * sizeof(float)); +} + +void GrMtlPipelineStateDataManager::setMatrix2f(UniformHandle u, const float matrix[]) const { + this->setMatrices<2>(u, 1, matrix); +} + +void GrMtlPipelineStateDataManager::setMatrix2fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<2>(u, arrayCount, m); +} + +void GrMtlPipelineStateDataManager::setMatrix3f(UniformHandle u, const float matrix[]) const { + this->setMatrices<3>(u, 1, matrix); +} + +void GrMtlPipelineStateDataManager::setMatrix3fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<3>(u, arrayCount, m); +} + +void GrMtlPipelineStateDataManager::setMatrix4f(UniformHandle u, const float matrix[]) const { + this->setMatrices<4>(u, 1, matrix); +} + +void GrMtlPipelineStateDataManager::setMatrix4fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<4>(u, arrayCount, m); +} + +template struct set_uniform_matrix; + +template inline void GrMtlPipelineStateDataManager::setMatrices( + UniformHandle u, + int arrayCount, + const float matrices[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2x2_GrSLType + (N - 2) || + uni.fType == kHalf2x2_GrSLType + (N - 2)); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + fUniformsDirty = true; + set_uniform_matrix::set(fUniformData.get(), uni.fOffset, arrayCount, matrices); +} + +template<> struct set_uniform_matrix<2> { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + memcpy(buffer, matrices, count * 4 * sizeof(float)); + } +}; + +template<> struct set_uniform_matrix<3> { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + for (int i = 0; i < count; ++i) { + const float* matrix = &matrices[3 * 3 * i]; + for (int j = 0; j < 3; ++j) { + memcpy(buffer, &matrix[j * 3], 3 * sizeof(float)); + buffer = static_cast(buffer) + 4 * sizeof(float); + } + } + } +}; + +template<> struct set_uniform_matrix<4> { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + memcpy(buffer, matrices, count * 16 * sizeof(float)); + } +}; + +void GrMtlPipelineStateDataManager::uploadAndBindUniformBuffers( + GrMtlGpu* gpu, + id renderCmdEncoder) const { + if (fUniformSize && fUniformsDirty) { + SkASSERT(fUniformSize < 4*1024); + if (@available(macOS 10.11, iOS 8.3, *)) { + [renderCmdEncoder setVertexBytes: fUniformData.get() + length: fUniformSize + atIndex: GrMtlUniformHandler::kUniformBinding]; + [renderCmdEncoder setFragmentBytes: fUniformData.get() + length: fUniformSize + atIndex: GrMtlUniformHandler::kUniformBinding]; + } else { + size_t bufferOffset; + id uniformBuffer = gpu->resourceProvider().getDynamicBuffer( + fUniformSize, &bufferOffset); + SkASSERT(uniformBuffer); + char* bufferData = (char*) uniformBuffer.contents + bufferOffset; + memcpy(bufferData, fUniformData.get(), fUniformSize); + [renderCmdEncoder setVertexBuffer: uniformBuffer + offset: bufferOffset + atIndex: GrMtlUniformHandler::kUniformBinding]; + [renderCmdEncoder setFragmentBuffer: uniformBuffer + offset: bufferOffset + atIndex: GrMtlUniformHandler::kUniformBinding]; + } + fUniformsDirty = false; + } +} + +void GrMtlPipelineStateDataManager::resetDirtyBits() { + fUniformsDirty = true; +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.h b/gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.h new file mode 100644 index 0000000000..4ae34d75ae --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.h @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlRenderTarget_DEFINED +#define GrMtlRenderTarget_DEFINED + +#include "src/gpu/GrRenderTarget.h" + +#include "include/gpu/GrBackendSurface.h" +#include "src/gpu/GrGpu.h" + +#import + +class GrMtlGpu; + +class GrMtlRenderTarget: public GrRenderTarget { +public: + static sk_sp MakeWrappedRenderTarget(GrMtlGpu*, + const GrSurfaceDesc&, + int sampleCnt, + id); + + ~GrMtlRenderTarget() override; + + bool canAttemptStencilAttachment() const override { + return true; + } + + id mtlColorTexture() const { return fColorTexture; } + id mtlResolveTexture() const { return fResolveTexture; } + + GrBackendRenderTarget getBackendRenderTarget() const override; + + GrBackendFormat backendFormat() const override; + +protected: + GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture); + + GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id colorTexture); + + GrMtlGpu* getMtlGpu() const; + + void onAbandon() override; + void onRelease() override; + + // This accounts for the texture's memory and any MSAA renderbuffer's memory. + size_t onGpuMemorySize() const override { + int numColorSamples = this->numSamples(); + // TODO: When used as render targets certain formats may actually have a larger size than + // the base format size. Check to make sure we are reporting the correct value here. + // The plus 1 is to account for the resolve texture or if not using msaa the RT itself + if (numColorSamples > 1) { + ++numColorSamples; + } + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numColorSamples, GrMipMapped::kNo); + } + + id fColorTexture; + id fResolveTexture; + +private: + // Extra param to disambiguate from constructor used by subclasses. + enum Wrapped { kWrapped }; + GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture, + Wrapped); + GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id colorTexture, + Wrapped); + + bool completeStencilAttachment() override; + + typedef GrRenderTarget INHERITED; +}; + + +#endif + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.mm new file mode 100644 index 0000000000..b01066fec9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlRenderTarget.mm @@ -0,0 +1,146 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlRenderTarget.h" + +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +// Called for wrapped non-texture render targets. +GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture, + Wrapped) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrRenderTarget( + gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, GrProtected::kNo) + , fColorTexture(colorTexture) + , fResolveTexture(resolveTexture) { + SkASSERT(sampleCnt > 1); + this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id colorTexture, + Wrapped) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, 1, GrProtected::kNo) + , fColorTexture(colorTexture) + , fResolveTexture(nil) { + this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +// Called by subclass constructors. +GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrRenderTarget( + gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, GrProtected::kNo) + , fColorTexture(colorTexture) + , fResolveTexture(resolveTexture) { + SkASSERT(sampleCnt > 1); +} + +GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id colorTexture) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, 1, GrProtected::kNo) + , fColorTexture(colorTexture) + , fResolveTexture(nil) {} + +sk_sp GrMtlRenderTarget::MakeWrappedRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id texture) { + SkASSERT(nil != texture); + SkASSERT(1 == texture.mipmapLevelCount); + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT(MTLTextureUsageRenderTarget & texture.usage); + } + + GrMtlRenderTarget* mtlRT; + if (sampleCnt > 1) { + MTLPixelFormat format; + if (!GrPixelConfigToMTLFormat(desc.fConfig, &format)) { + return nullptr; + } + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2DMultisample; + texDesc.pixelFormat = format; + texDesc.width = desc.fWidth; + texDesc.height = desc.fHeight; + texDesc.depth = 1; + texDesc.mipmapLevelCount = 1; + texDesc.sampleCount = sampleCnt; + texDesc.arrayLength = 1; + if (@available(macOS 10.11, iOS 9.0, *)) { + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; + } + + id colorTexture = [gpu->device() newTextureWithDescriptor:texDesc]; + if (!colorTexture) { + return nullptr; + } + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT((MTLTextureUsageShaderRead|MTLTextureUsageRenderTarget) & colorTexture.usage); + } + mtlRT = new GrMtlRenderTarget(gpu, desc, sampleCnt, colorTexture, texture, kWrapped); + } else { + mtlRT = new GrMtlRenderTarget(gpu, desc, texture, kWrapped); + } + + return sk_sp(mtlRT); +} + +GrMtlRenderTarget::~GrMtlRenderTarget() { + SkASSERT(nil == fColorTexture); + SkASSERT(nil == fResolveTexture); +} + +GrBackendRenderTarget GrMtlRenderTarget::getBackendRenderTarget() const { + GrMtlTextureInfo info; + info.fTexture.reset(GrRetainPtrFromId(fColorTexture)); + return GrBackendRenderTarget(this->width(), this->height(), fColorTexture.sampleCount, info); +} + +GrBackendFormat GrMtlRenderTarget::backendFormat() const { + return GrBackendFormat::MakeMtl(fColorTexture.pixelFormat); +} + +GrMtlGpu* GrMtlRenderTarget::getMtlGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +void GrMtlRenderTarget::onAbandon() { + fColorTexture = nil; + fResolveTexture = nil; + INHERITED::onAbandon(); +} + +void GrMtlRenderTarget::onRelease() { + fColorTexture = nil; + fResolveTexture = nil; + INHERITED::onRelease(); +} + +bool GrMtlRenderTarget::completeStencilAttachment() { + return true; +} + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.h b/gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.h new file mode 100644 index 0000000000..0b8d2194e5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.h @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. +*/ + +#ifndef GrMtlResourceProvider_DEFINED +#define GrMtlResourceProvider_DEFINED + +#include "include/private/SkSpinlock.h" +#include "include/private/SkTArray.h" +#include "src/core/SkLRUCache.h" +#include "src/gpu/mtl/GrMtlDepthStencil.h" +#include "src/gpu/mtl/GrMtlPipelineStateBuilder.h" +#include "src/gpu/mtl/GrMtlSampler.h" + +#import + +class GrMtlGpu; +class GrMtlCommandBuffer; + +class GrMtlResourceProvider { +public: + GrMtlResourceProvider(GrMtlGpu* gpu); + + GrMtlPipelineState* findOrCreateCompatiblePipelineState(GrRenderTarget*, + const GrProgramInfo&, + GrPrimitiveType); + + // Finds or creates a compatible MTLDepthStencilState based on the GrStencilSettings. + GrMtlDepthStencil* findOrCreateCompatibleDepthStencilState(const GrStencilSettings&, + GrSurfaceOrigin); + + // Finds or creates a compatible MTLSamplerState based on the GrSamplerState. + GrMtlSampler* findOrCreateCompatibleSampler(const GrSamplerState&); + + id getDynamicBuffer(size_t size, size_t* offset); + void addBufferCompletionHandler(GrMtlCommandBuffer* cmdBuffer); + + // Destroy any cached resources. To be called before releasing the MtlDevice. + void destroyResources(); + +private: +#ifdef SK_DEBUG +#define GR_PIPELINE_STATE_CACHE_STATS +#endif + + class PipelineStateCache : public ::SkNoncopyable { + public: + PipelineStateCache(GrMtlGpu* gpu); + ~PipelineStateCache(); + + void release(); + GrMtlPipelineState* refPipelineState(GrRenderTarget*, const GrProgramInfo&, + GrPrimitiveType); + + private: + struct Entry; + + struct DescHash { + uint32_t operator()(const GrProgramDesc& desc) const { + return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0); + } + }; + + SkLRUCache, DescHash> fMap; + + GrMtlGpu* fGpu; + +#ifdef GR_PIPELINE_STATE_CACHE_STATS + int fTotalRequests; + int fCacheMisses; +#endif + }; + + // Buffer allocator + class BufferSuballocator : public SkRefCnt { + public: + BufferSuballocator(id device, size_t size); + ~BufferSuballocator() { + fBuffer = nil; + fTotalSize = 0; + } + + id getAllocation(size_t size, size_t* offset); + void addCompletionHandler(GrMtlCommandBuffer* cmdBuffer); + size_t size() { return fTotalSize; } + + private: + id fBuffer; + size_t fTotalSize; + size_t fHead SK_GUARDED_BY(fMutex); // where we start allocating + size_t fTail SK_GUARDED_BY(fMutex); // where we start deallocating + SkSpinlock fMutex; + }; + static constexpr size_t kBufferSuballocatorStartSize = 1024*1024; + + GrMtlGpu* fGpu; + + // Cache of GrMtlPipelineStates + std::unique_ptr fPipelineStateCache; + + SkTDynamicHash fSamplers; + SkTDynamicHash fDepthStencilStates; + + // This is ref-counted because we might delete the GrContext before the command buffer + // finishes. The completion handler will retain a reference to this so it won't get + // deleted along with the GrContext. + sk_sp fBufferSuballocator; + size_t fBufferSuballocatorMaxSize; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.mm new file mode 100644 index 0000000000..9774d2677f --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlResourceProvider.mm @@ -0,0 +1,290 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlResourceProvider.h" + +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/mtl/GrMtlCommandBuffer.h" +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlPipelineState.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#include "src/sksl/SkSLCompiler.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlResourceProvider::GrMtlResourceProvider(GrMtlGpu* gpu) + : fGpu(gpu) { + fPipelineStateCache.reset(new PipelineStateCache(gpu)); + fBufferSuballocator.reset(new BufferSuballocator(gpu->device(), kBufferSuballocatorStartSize)); + // TODO: maxBufferLength seems like a reasonable metric to determine fBufferSuballocatorMaxSize + // but may need tuning. Might also need a GrContextOption to let the client set this. +#ifdef SK_BUILD_FOR_MAC + int64_t maxBufferLength = 1024*1024*1024; +#else + int64_t maxBufferLength = 256*1024*1024; +#endif + if (@available(iOS 12, macOS 10.14, *)) { + maxBufferLength = gpu->device().maxBufferLength; + } + fBufferSuballocatorMaxSize = maxBufferLength/16; +} + +GrMtlPipelineState* GrMtlResourceProvider::findOrCreateCompatiblePipelineState( + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType) { + return fPipelineStateCache->refPipelineState(renderTarget, programInfo, primitiveType); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +GrMtlDepthStencil* GrMtlResourceProvider::findOrCreateCompatibleDepthStencilState( + const GrStencilSettings& stencil, GrSurfaceOrigin origin) { + GrMtlDepthStencil* depthStencilState; + GrMtlDepthStencil::Key key = GrMtlDepthStencil::GenerateKey(stencil, origin); + depthStencilState = fDepthStencilStates.find(key); + if (!depthStencilState) { + depthStencilState = GrMtlDepthStencil::Create(fGpu, stencil, origin); + fDepthStencilStates.add(depthStencilState); + } + SkASSERT(depthStencilState); + return depthStencilState; +} + +GrMtlSampler* GrMtlResourceProvider::findOrCreateCompatibleSampler(const GrSamplerState& params) { + GrMtlSampler* sampler; + sampler = fSamplers.find(GrMtlSampler::GenerateKey(params)); + if (!sampler) { + sampler = GrMtlSampler::Create(fGpu, params); + fSamplers.add(sampler); + } + SkASSERT(sampler); + return sampler; +} + +void GrMtlResourceProvider::destroyResources() { + // Iterate through all stored GrMtlSamplers and unref them before resetting the hash. + SkTDynamicHash::Iter samplerIter(&fSamplers); + for (; !samplerIter.done(); ++samplerIter) { + (*samplerIter).unref(); + } + fSamplers.reset(); + + // Iterate through all stored GrMtlDepthStencils and unref them before resetting the hash. + SkTDynamicHash::Iter dsIter(&fDepthStencilStates); + for (; !dsIter.done(); ++dsIter) { + (*dsIter).unref(); + } + fDepthStencilStates.reset(); + + fPipelineStateCache->release(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef GR_PIPELINE_STATE_CACHE_STATS +// Display pipeline state cache usage +static const bool c_DisplayMtlPipelineCache{false}; +#endif + +struct GrMtlResourceProvider::PipelineStateCache::Entry { + Entry(GrMtlGpu* gpu, GrMtlPipelineState* pipelineState) + : fGpu(gpu) + , fPipelineState(pipelineState) {} + + GrMtlGpu* fGpu; + std::unique_ptr fPipelineState; +}; + +GrMtlResourceProvider::PipelineStateCache::PipelineStateCache(GrMtlGpu* gpu) + : fMap(gpu->getContext()->priv().options().fRuntimeProgramCacheSize) + , fGpu(gpu) +#ifdef GR_PIPELINE_STATE_CACHE_STATS + , fTotalRequests(0) + , fCacheMisses(0) +#endif +{} + +GrMtlResourceProvider::PipelineStateCache::~PipelineStateCache() { + SkASSERT(0 == fMap.count()); + // dump stats +#ifdef GR_PIPELINE_STATE_CACHE_STATS + if (c_DisplayMtlPipelineCache) { + SkDebugf("--- Pipeline State Cache ---\n"); + SkDebugf("Total requests: %d\n", fTotalRequests); + SkDebugf("Cache misses: %d\n", fCacheMisses); + SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? + 100.f * fCacheMisses / fTotalRequests : + 0.f); + SkDebugf("---------------------\n"); + } +#endif +} + +void GrMtlResourceProvider::PipelineStateCache::release() { + fMap.reset(); +} + +GrMtlPipelineState* GrMtlResourceProvider::PipelineStateCache::refPipelineState( + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primType) { +#ifdef GR_PIPELINE_STATE_CACHE_STATS + ++fTotalRequests; +#endif + + // TODO: unify GL, VK and Mtl + // Get GrMtlProgramDesc + GrMtlPipelineStateBuilder::Desc desc; + if (!GrMtlPipelineStateBuilder::Desc::Build(&desc, renderTarget, programInfo, primType, fGpu)) { + GrCapsDebugf(fGpu->caps(), "Failed to build mtl program descriptor!\n"); + return nullptr; + } + + std::unique_ptr* entry = fMap.find(desc); + if (!entry) { +#ifdef GR_PIPELINE_STATE_CACHE_STATS + ++fCacheMisses; +#endif + GrMtlPipelineState* pipelineState(GrMtlPipelineStateBuilder::CreatePipelineState( + fGpu, renderTarget, programInfo, &desc)); + if (!pipelineState) { + return nullptr; + } + entry = fMap.insert(desc, std::unique_ptr(new Entry(fGpu, pipelineState))); + return (*entry)->fPipelineState.get(); + } + return (*entry)->fPipelineState.get(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +static id alloc_dynamic_buffer(id device, size_t size) { + NSUInteger options = 0; + if (@available(macOS 10.11, iOS 9.0, *)) { +#ifdef SK_BUILD_FOR_MAC + options |= MTLResourceStorageModeManaged; +#else + options |= MTLResourceStorageModeShared; +#endif + } + return [device newBufferWithLength: size + options: options]; + +} + +// The idea here is that we create a ring buffer which is used for all dynamic allocations +// below a certain size. When a dynamic GrMtlBuffer is mapped, it grabs a portion of this +// buffer and uses it. On a subsequent map it will grab a different portion of the buffer. +// This prevents the buffer from overwriting itself before it's submitted to the command +// stream. + +GrMtlResourceProvider::BufferSuballocator::BufferSuballocator(id device, size_t size) + : fBuffer(alloc_dynamic_buffer(device, size)) + , fTotalSize(size) + , fHead(0) + , fTail(0) { + // We increment fHead and fTail without bound and let overflow handle any wrapping. + // Because of this, size needs to be a power of two. + SkASSERT(SkIsPow2(size)); +} + +id GrMtlResourceProvider::BufferSuballocator::getAllocation(size_t size, + size_t* offset) { + // capture current state locally (because fTail could be overwritten by the completion handler) + size_t head, tail; + SkAutoSpinlock lock(fMutex); + head = fHead; + tail = fTail; + + // The head and tail indices increment without bound, wrapping with overflow, + // so we need to mod them down to the actual bounds of the allocation to determine + // which blocks are available. + size_t modHead = head & (fTotalSize - 1); + size_t modTail = tail & (fTotalSize - 1); + + bool full = (head != tail && modHead == modTail); + + + // We don't want large allocations to eat up this buffer, so we allocate them separately. + if (full || size > fTotalSize/2) { + return nil; + } + + // case 1: free space lies at the beginning and/or the end of the buffer + if (modHead >= modTail) { + // check for room at the end + if (fTotalSize - modHead < size) { + // no room at the end, check the beginning + if (modTail < size) { + // no room at the beginning + return nil; + } + // we are going to allocate from the beginning, adjust head to '0' position + head += fTotalSize - modHead; + modHead = 0; + } + // case 2: free space lies in the middle of the buffer, check for room there + } else if (modTail - modHead < size) { + // no room in the middle + return nil; + } + + *offset = modHead; + // We're not sure what the usage of the next allocation will be -- + // to be safe we'll use 16 byte alignment. + fHead = GrSizeAlignUp(head + size, 16); + return fBuffer; +} + +void GrMtlResourceProvider::BufferSuballocator::addCompletionHandler( + GrMtlCommandBuffer* cmdBuffer) { + this->ref(); + SkAutoSpinlock lock(fMutex); + size_t newTail = fHead; + cmdBuffer->addCompletedHandler(^(id commandBuffer) { + // Make sure SkAutoSpinlock goes out of scope before + // the BufferSuballocator is potentially deleted. + { + SkAutoSpinlock lock(fMutex); + fTail = newTail; + } + this->unref(); + }); +} + +id GrMtlResourceProvider::getDynamicBuffer(size_t size, size_t* offset) { + id buffer = fBufferSuballocator->getAllocation(size, offset); + if (buffer) { + return buffer; + } + + // Try to grow allocation (old allocation will age out). + // We grow up to a maximum size, and only grow if the requested allocation will + // fit into half of the new buffer (to prevent very large transient buffers forcing + // growth when they'll never fit anyway). + if (fBufferSuballocator->size() < fBufferSuballocatorMaxSize && + size <= fBufferSuballocator->size()) { + fBufferSuballocator.reset(new BufferSuballocator(fGpu->device(), + 2*fBufferSuballocator->size())); + id buffer = fBufferSuballocator->getAllocation(size, offset); + if (buffer) { + return buffer; + } + } + + *offset = 0; + return alloc_dynamic_buffer(fGpu->device(), size); +} + +void GrMtlResourceProvider::addBufferCompletionHandler(GrMtlCommandBuffer* cmdBuffer) { + fBufferSuballocator->addCompletionHandler(cmdBuffer); +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlSampler.h b/gfx/skia/skia/src/gpu/mtl/GrMtlSampler.h new file mode 100644 index 0000000000..50d9df9b46 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlSampler.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlSampler_DEFINED +#define GrMtlSampler_DEFINED + +#import + +#include "src/core/SkOpts.h" +#include + +class GrSamplerState; +class GrMtlGpu; + +// A wrapper for a MTLSamplerState object with caching support. +class GrMtlSampler : public SkRefCnt { +public: + static GrMtlSampler* Create(const GrMtlGpu* gpu, const GrSamplerState&); + ~GrMtlSampler() { fMtlSamplerState = nil; } + + id mtlSampler() const { return fMtlSamplerState; } + + typedef uint32_t Key; + + // Helpers for hashing GrMtlSampler + static Key GenerateKey(const GrSamplerState&); + + static const Key& GetKey(const GrMtlSampler& sampler) { return sampler.fKey; } + static uint32_t Hash(const Key& key) { + return SkOpts::hash(reinterpret_cast(&key), sizeof(Key)); + } + +private: + GrMtlSampler(id mtlSamplerState, Key key) + : fMtlSamplerState(mtlSamplerState) + , fKey(key) {} + + id fMtlSamplerState; + Key fKey; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlSampler.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlSampler.mm new file mode 100644 index 0000000000..2d7344eff7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlSampler.mm @@ -0,0 +1,78 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlSampler.h" + +#include "src/gpu/mtl/GrMtlGpu.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +static inline MTLSamplerAddressMode wrap_mode_to_mtl_sampler_address( + GrSamplerState::WrapMode wrapMode, const GrCaps& caps) { + switch (wrapMode) { + case GrSamplerState::WrapMode::kClamp: + return MTLSamplerAddressModeClampToEdge; + case GrSamplerState::WrapMode::kRepeat: + return MTLSamplerAddressModeRepeat; + case GrSamplerState::WrapMode::kMirrorRepeat: + return MTLSamplerAddressModeMirrorRepeat; + case GrSamplerState::WrapMode::kClampToBorder: + // Must guard the reference to the clamp to border address mode by macro since iOS or + // older MacOS builds will fail if it's referenced, even if other code makes sure it's + // never used. +#ifdef SK_BUILD_FOR_MAC + if (@available(macOS 10.12, *)) { + SkASSERT(caps.clampToBorderSupport()); + return MTLSamplerAddressModeClampToBorderColor; + } else +#endif + { + SkASSERT(false); + return MTLSamplerAddressModeClampToEdge; + } + } + SK_ABORT("Unknown wrap mode."); +} + +GrMtlSampler* GrMtlSampler::Create(const GrMtlGpu* gpu, const GrSamplerState& samplerState) { + static MTLSamplerMinMagFilter mtlMinMagFilterModes[] = { + MTLSamplerMinMagFilterNearest, + MTLSamplerMinMagFilterLinear, + MTLSamplerMinMagFilterLinear + }; + + GR_STATIC_ASSERT((int)GrSamplerState::Filter::kNearest == 0); + GR_STATIC_ASSERT((int)GrSamplerState::Filter::kBilerp == 1); + GR_STATIC_ASSERT((int)GrSamplerState::Filter::kMipMap == 2); + + auto samplerDesc = [[MTLSamplerDescriptor alloc] init]; + samplerDesc.rAddressMode = MTLSamplerAddressModeClampToEdge; + samplerDesc.sAddressMode = wrap_mode_to_mtl_sampler_address(samplerState.wrapModeX(), + gpu->mtlCaps()); + samplerDesc.tAddressMode = wrap_mode_to_mtl_sampler_address(samplerState.wrapModeY(), + gpu->mtlCaps()); + samplerDesc.magFilter = mtlMinMagFilterModes[static_cast(samplerState.filter())]; + samplerDesc.minFilter = mtlMinMagFilterModes[static_cast(samplerState.filter())]; + samplerDesc.mipFilter = MTLSamplerMipFilterLinear; + samplerDesc.lodMinClamp = 0.0f; + bool useMipMaps = GrSamplerState::Filter::kMipMap == samplerState.filter(); + samplerDesc.lodMaxClamp = !useMipMaps ? 0.0f : 10000.0f; + samplerDesc.maxAnisotropy = 1.0f; + samplerDesc.normalizedCoordinates = true; + if (@available(macOS 10.11, iOS 9.0, *)) { + samplerDesc.compareFunction = MTLCompareFunctionNever; + } + + return new GrMtlSampler([gpu->device() newSamplerStateWithDescriptor: samplerDesc], + GenerateKey(samplerState)); +} + +GrMtlSampler::Key GrMtlSampler::GenerateKey(const GrSamplerState& samplerState) { + return GrSamplerState::GenerateKey(samplerState); +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.h b/gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.h new file mode 100644 index 0000000000..5a5ad9ce26 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlSemaphore_DEFINED +#define GrMtlSemaphore_DEFINED + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#include + +class GrMtlGpu; + +class GrMtlSemaphore : public GrSemaphore { +public: + static sk_sp Make(GrMtlGpu* gpu, bool isOwned); + + static sk_sp MakeWrapped(GrMtlGpu* gpu, + GrMTLHandle event, + uint64_t value, + GrWrapOwnership ownership); + + id event() const API_AVAILABLE(macos(10.14), ios(12.0)) { return fEvent; } + uint64_t value() const { return fValue; } + + GrBackendSemaphore backendSemaphore() const override; + +private: + GrMtlSemaphore(GrMtlGpu* gpu, id event, + uint64_t value, bool isOwned) API_AVAILABLE(macos(10.14), ios(12.0)); + + void onRelease() override; + void onAbandon() override; + + id fEvent API_AVAILABLE(macos(10.14), ios(12.0)); + uint64_t fValue; + + typedef GrSemaphore INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.mm new file mode 100644 index 0000000000..3eb31ac503 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlSemaphore.mm @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlSemaphore.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +sk_sp GrMtlSemaphore::Make(GrMtlGpu* gpu, bool isOwned) { + if (@available(macOS 10.14, iOS 12.0, *)) { + id event = [gpu->device() newEvent]; + uint64_t value = 1; // seems like a reasonable starting point + return sk_sp(new GrMtlSemaphore(gpu, event, value, isOwned)); + } else { + return nullptr; + } +} + +sk_sp GrMtlSemaphore::MakeWrapped(GrMtlGpu* gpu, + GrMTLHandle event, + uint64_t value, + GrWrapOwnership ownership) { + // The GrMtlSemaphore will have strong ownership at this point. + // The GrMTLHandle will subsequently only have weak ownership. + if (@available(macOS 10.14, iOS 12.0, *)) { + id mtlEvent = (__bridge_transfer id)event; + auto sema = sk_sp(new GrMtlSemaphore(gpu, mtlEvent, value, + kBorrow_GrWrapOwnership != ownership)); + return sema; + } else { + return nullptr; + } +} + +GrMtlSemaphore::GrMtlSemaphore(GrMtlGpu* gpu, id event, uint64_t value, bool isOwned) + : INHERITED(gpu), fEvent(event), fValue(value) { + isOwned ? this->registerWithCache(SkBudgeted::kNo) + : this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +void GrMtlSemaphore::onRelease() { + if (@available(macOS 10.14, iOS 12.0, *)) { + fEvent = nil; + } + INHERITED::onRelease(); +} + +void GrMtlSemaphore::onAbandon() { + if (@available(macOS 10.14, iOS 12.0, *)) { + fEvent = nil; + } + INHERITED::onAbandon(); +} + +GrBackendSemaphore GrMtlSemaphore::backendSemaphore() const { + GrBackendSemaphore backendSemaphore; + // The GrMtlSemaphore and the GrBackendSemaphore will have strong ownership at this point. + // Whoever uses the GrBackendSemaphore will subsquently steal this ref (see MakeWrapped, above). + if (@available(macOS 10.14, iOS 12.0, *)) { + GrMTLHandle handle = (__bridge_retained GrMTLHandle)(fEvent); + backendSemaphore.initMetal(handle, fValue); + } + return backendSemaphore; +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.h b/gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.h new file mode 100644 index 0000000000..5387b06532 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.h @@ -0,0 +1,54 @@ +/* +* Copyright 2018 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrMtlStencil_DEFINED +#define GrMtlStencil_DEFINED + +#include "src/gpu/GrStencilAttachment.h" + +#import + +class GrMtlImageView; +class GrMtlGpu; + +class GrMtlStencilAttachment : public GrStencilAttachment { +public: + struct Format { + MTLPixelFormat fInternalFormat; + int fStencilBits; + int fTotalBits; + bool fPacked; + }; + + static GrMtlStencilAttachment* Create(GrMtlGpu* gpu, int width, int height, + int sampleCnt, const Format& format); + + ~GrMtlStencilAttachment() override; + + MTLPixelFormat mtlFormat() const { return fFormat.fInternalFormat; } + + id stencilView() const { return fStencilView; } + +protected: + void onRelease() override; + void onAbandon() override; + +private: + size_t onGpuMemorySize() const override; + + GrMtlStencilAttachment(GrMtlGpu* gpu, + const Format& format, + const id stencilView); + + GrMtlGpu* getMtlGpu() const; + + Format fFormat; + + id fStencilView; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.mm new file mode 100644 index 0000000000..01ed2c245f --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlStencilAttachment.mm @@ -0,0 +1,72 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlStencilAttachment::GrMtlStencilAttachment(GrMtlGpu* gpu, + const Format& format, + const id stencilView) + : GrStencilAttachment(gpu, stencilView.width, stencilView.height, format.fStencilBits, + stencilView.sampleCount) + , fFormat(format) + , fStencilView(stencilView) { + this->registerWithCache(SkBudgeted::kYes); +} + +GrMtlStencilAttachment* GrMtlStencilAttachment::Create(GrMtlGpu* gpu, + int width, + int height, + int sampleCnt, + const Format& format) { + MTLTextureDescriptor* desc = + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.fInternalFormat + width:width + height:height + mipmapped:NO]; + if (@available(macOS 10.11, iOS 9.0, *)) { + desc.storageMode = MTLStorageModePrivate; + desc.usage = MTLTextureUsageRenderTarget; + } + desc.sampleCount = sampleCnt; + if (sampleCnt > 1) { + desc.textureType = MTLTextureType2DMultisample; + } + return new GrMtlStencilAttachment(gpu, format, [gpu->device() newTextureWithDescriptor:desc]); +} + +GrMtlStencilAttachment::~GrMtlStencilAttachment() { + // should have been released or abandoned first + SkASSERT(!fStencilView); +} + +size_t GrMtlStencilAttachment::onGpuMemorySize() const { + uint64_t size = this->width(); + size *= this->height(); + size *= fFormat.fTotalBits; + size *= this->numSamples(); + return static_cast(size / 8); +} + +void GrMtlStencilAttachment::onRelease() { + fStencilView = nullptr; + GrStencilAttachment::onRelease(); +} + +void GrMtlStencilAttachment::onAbandon() { + fStencilView = nullptr; + GrStencilAttachment::onAbandon(); +} + +GrMtlGpu* GrMtlStencilAttachment::getMtlGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlTexture.h b/gfx/skia/skia/src/gpu/mtl/GrMtlTexture.h new file mode 100644 index 0000000000..c81aef0ebb --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlTexture.h @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlTexture_DEFINED +#define GrMtlTexture_DEFINED + +#include "include/gpu/GrTexture.h" + +#import + +class GrMtlGpu; + +class GrMtlTexture : public GrTexture { +public: + static sk_sp MakeNewTexture(GrMtlGpu*, SkBudgeted budgeted, + const GrSurfaceDesc&, + MTLTextureDescriptor*, + GrMipMapsStatus); + + static sk_sp MakeWrappedTexture(GrMtlGpu*, const GrSurfaceDesc&, id, + GrWrapCacheable, GrIOType); + + ~GrMtlTexture() override; + + id mtlTexture() const { return fTexture; } + + GrBackendTexture getBackendTexture() const override; + + GrBackendFormat backendFormat() const override; + + void textureParamsModified() override {} + + bool reallocForMipmap(GrMtlGpu* gpu, uint32_t mipLevels); + +protected: + GrMtlTexture(GrMtlGpu*, const GrSurfaceDesc&, id, GrMipMapsStatus); + + GrMtlGpu* getMtlGpu() const; + + void onAbandon() override { + fTexture = nil; + INHERITED::onAbandon(); + } + void onRelease() override { + fTexture = nil; + INHERITED::onRelease(); + } + + bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override { + return false; + } + +private: + enum Wrapped { kWrapped }; + + GrMtlTexture(GrMtlGpu*, SkBudgeted, const GrSurfaceDesc&, id, + GrMipMapsStatus); + + GrMtlTexture(GrMtlGpu*, Wrapped, const GrSurfaceDesc&, id, GrMipMapsStatus, + GrWrapCacheable, GrIOType); + + id fTexture; + + typedef GrTexture INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlTexture.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlTexture.mm new file mode 100644 index 0000000000..be9736fced --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlTexture.mm @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlTexture.h" + +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + id texture, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo, + GrTextureType::k2D, mipMapsStatus) + , fTexture(texture) { + SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == texture.mipmapLevelCount)); + this->registerWithCache(budgeted); + if (GrMtlFormatIsCompressed(texture.pixelFormat)) { + this->setReadOnly(); + } +} + +GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu, + Wrapped, + const GrSurfaceDesc& desc, + id texture, + GrMipMapsStatus mipMapsStatus, + GrWrapCacheable cacheable, + GrIOType ioType) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo, + GrTextureType::k2D, mipMapsStatus) + , fTexture(texture) { + SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == texture.mipmapLevelCount)); + if (ioType == kRead_GrIOType) { + this->setReadOnly(); + } + this->registerWithCacheWrapped(cacheable); +} + +GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id texture, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo, + GrTextureType::k2D, mipMapsStatus) + , fTexture(texture) { + SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == texture.mipmapLevelCount)); +} + +sk_sp GrMtlTexture::MakeNewTexture(GrMtlGpu* gpu, SkBudgeted budgeted, + const GrSurfaceDesc& desc, + MTLTextureDescriptor* texDesc, + GrMipMapsStatus mipMapsStatus) { + id texture = [gpu->device() newTextureWithDescriptor:texDesc]; + if (!texture) { + return nullptr; + } + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT(MTLTextureUsageShaderRead & texture.usage); + } + return sk_sp(new GrMtlTexture(gpu, budgeted, desc, texture, mipMapsStatus)); +} + +sk_sp GrMtlTexture::MakeWrappedTexture(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id texture, + GrWrapCacheable cacheable, + GrIOType ioType) { + SkASSERT(nil != texture); + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT(MTLTextureUsageShaderRead & texture.usage); + } + GrMipMapsStatus mipMapsStatus = texture.mipmapLevelCount > 1 ? GrMipMapsStatus::kValid + : GrMipMapsStatus::kNotAllocated; + return sk_sp(new GrMtlTexture(gpu, kWrapped, desc, texture, mipMapsStatus, + cacheable, ioType)); +} + +GrMtlTexture::~GrMtlTexture() { + SkASSERT(nil == fTexture); +} + +GrMtlGpu* GrMtlTexture::getMtlGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +GrBackendTexture GrMtlTexture::getBackendTexture() const { + GrMipMapped mipMapped = fTexture.mipmapLevelCount > 1 ? GrMipMapped::kYes + : GrMipMapped::kNo; + GrMtlTextureInfo info; + info.fTexture.reset(GrRetainPtrFromId(fTexture)); + return GrBackendTexture(this->width(), this->height(), mipMapped, info); +} + +GrBackendFormat GrMtlTexture::backendFormat() const { + return GrBackendFormat::MakeMtl(fTexture.pixelFormat); +} + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h b/gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h new file mode 100644 index 0000000000..18656e118f --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h @@ -0,0 +1,86 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlTextureRenderTarget_DEFINED +#define GrMtlTextureRenderTarget_DEFINED + +#include "src/gpu/mtl/GrMtlRenderTarget.h" +#include "src/gpu/mtl/GrMtlTexture.h" + +class GrMtlTextureRenderTarget: public GrMtlTexture, public GrMtlRenderTarget { +public: + static sk_sp MakeNewTextureRenderTarget(GrMtlGpu*, + SkBudgeted, + const GrSurfaceDesc&, + int sampleCnt, + MTLTextureDescriptor*, + GrMipMapsStatus); + + static sk_sp MakeWrappedTextureRenderTarget(GrMtlGpu*, + const GrSurfaceDesc&, + int sampleCnt, + id, + GrWrapCacheable); + GrBackendFormat backendFormat() const override { + return GrMtlTexture::backendFormat(); + } + +protected: + void onAbandon() override { + GrMtlRenderTarget::onAbandon(); + GrMtlTexture::onAbandon(); + } + + void onRelease() override { + GrMtlRenderTarget::onRelease(); + GrMtlTexture::onRelease(); + } + +private: + GrMtlTextureRenderTarget(GrMtlGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture, + GrMipMapsStatus); + + GrMtlTextureRenderTarget(GrMtlGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + id colorTexture, + GrMipMapsStatus); + + GrMtlTextureRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture, + GrMipMapsStatus, + GrWrapCacheable cacheable); + + GrMtlTextureRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id colorTexture, + GrMipMapsStatus, + GrWrapCacheable cacheable); + + size_t onGpuMemorySize() const override { + // TODO: When used as render targets certain formats may actually have a larger size than + // the base format size. Check to make sure we are reporting the correct value here. + // The plus 1 is to account for the resolve texture or if not using msaa the RT itself + int numColorSamples = this->numSamples(); + if (numColorSamples > 1) { + ++numColorSamples; + } + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numColorSamples, GrMipMapped::kNo); + } +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm new file mode 100644 index 0000000000..9bdaa82ac7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm @@ -0,0 +1,144 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlTextureRenderTarget.h" +#include "src/gpu/mtl/GrMtlUtil.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrMtlTexture(gpu, desc, resolveTexture, mipMapsStatus) + , GrMtlRenderTarget(gpu, desc, sampleCnt, colorTexture, resolveTexture) { + this->registerWithCache(budgeted); +} + +GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + id colorTexture, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrMtlTexture(gpu, desc, colorTexture, mipMapsStatus) + , GrMtlRenderTarget(gpu, desc, colorTexture) { + this->registerWithCache(budgeted); +} + +GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id colorTexture, + id resolveTexture, + GrMipMapsStatus mipMapsStatus, + GrWrapCacheable cacheable) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrMtlTexture(gpu, desc, resolveTexture, mipMapsStatus) + , GrMtlRenderTarget(gpu, desc, sampleCnt, colorTexture, resolveTexture) { + this->registerWithCacheWrapped(cacheable); +} + +GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + id colorTexture, + GrMipMapsStatus mipMapsStatus, + GrWrapCacheable cacheable) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, GrProtected::kNo) + , GrMtlTexture(gpu, desc, colorTexture, mipMapsStatus) + , GrMtlRenderTarget(gpu, desc, colorTexture) { + this->registerWithCacheWrapped(cacheable); +} + +id create_msaa_texture(GrMtlGpu* gpu, const GrSurfaceDesc& desc, int sampleCnt) { + MTLPixelFormat format; + if (!GrPixelConfigToMTLFormat(desc.fConfig, &format)) { + return nullptr; + } + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2DMultisample; + texDesc.pixelFormat = format; + texDesc.width = desc.fWidth; + texDesc.height = desc.fHeight; + texDesc.depth = 1; + texDesc.mipmapLevelCount = 1; + texDesc.sampleCount = sampleCnt; + texDesc.arrayLength = 1; + if (@available(macOS 10.11, iOS 9.0, *)) { + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; + } + + return [gpu->device() newTextureWithDescriptor:texDesc]; +} + +sk_sp GrMtlTextureRenderTarget::MakeNewTextureRenderTarget( + GrMtlGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + int sampleCnt, + MTLTextureDescriptor* texDesc, + GrMipMapsStatus mipMapsStatus) { + id texture = [gpu->device() newTextureWithDescriptor:texDesc]; + if (!texture) { + return nullptr; + } + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & texture.usage); + } + + if (sampleCnt > 1) { + id colorTexture = create_msaa_texture(gpu, desc, sampleCnt); + if (!colorTexture) { + return nullptr; + } + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT((MTLTextureUsageShaderRead|MTLTextureUsageRenderTarget) & colorTexture.usage); + } + return sk_sp(new GrMtlTextureRenderTarget( + gpu, budgeted, desc, sampleCnt, colorTexture, texture, mipMapsStatus)); + } else { + return sk_sp( + new GrMtlTextureRenderTarget(gpu, budgeted, desc, texture, mipMapsStatus)); + } +} + +sk_sp GrMtlTextureRenderTarget::MakeWrappedTextureRenderTarget( + GrMtlGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + id texture, + GrWrapCacheable cacheable) { + SkASSERT(nil != texture); + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & texture.usage); + } + GrMipMapsStatus mipMapsStatus = texture.mipmapLevelCount > 1 + ? GrMipMapsStatus::kDirty + : GrMipMapsStatus::kNotAllocated; + if (sampleCnt > 1) { + id colorTexture = create_msaa_texture(gpu, desc, sampleCnt); + if (!colorTexture) { + return nullptr; + } + if (@available(macOS 10.11, iOS 9.0, *)) { + SkASSERT((MTLTextureUsageShaderRead|MTLTextureUsageRenderTarget) & colorTexture.usage); + } + return sk_sp(new GrMtlTextureRenderTarget( + gpu, desc, sampleCnt, colorTexture, texture, mipMapsStatus, cacheable)); + } else { + return sk_sp( + new GrMtlTextureRenderTarget(gpu, desc, texture, mipMapsStatus, cacheable)); + } +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.h b/gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.h new file mode 100644 index 0000000000..ac0cd61e9e --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlTrampoline_DEFINED +#define GrMtlTrampoline_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/GrTypes.h" + +class GrContext; +class GrGpu; +struct GrContextOptions; + +/* + * This class is used to hold functions which trampoline from the Ganesh cpp code to the GrMtl + * objective-c files. + */ +class GrMtlTrampoline { +public: + static sk_sp MakeGpu(GrContext*, const GrContextOptions&, void* device, void* queue); +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.mm new file mode 100644 index 0000000000..85f67b061c --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlTrampoline.mm @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlTrampoline.h" + +#include "src/gpu/mtl/GrMtlGpu.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +sk_sp GrMtlTrampoline::MakeGpu(GrContext* context, + const GrContextOptions& options, + void* device, + void* queue) { + return GrMtlGpu::Make(context, + options, + (__bridge id)device, + (__bridge id)queue); +} + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.h b/gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.h new file mode 100644 index 0000000000..bcf15a05f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.h @@ -0,0 +1,99 @@ +/* +* Copyright 2018 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrMtlUniformHandler_DEFINED +#define GrMtlUniformHandler_DEFINED + +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +// TODO: this class is basically copy and pasted from GrVkUniformHandler so that we can have +// some shaders working. The SkSL Metal code generator was written to work with GLSL generated for +// the Ganesh Vulkan backend, so it should all work. There might be better ways to do things in +// Metal and/or some Vulkan GLSLisms left in. +class GrMtlUniformHandler : public GrGLSLUniformHandler { +public: + static const int kUniformsPerBlock = 8; + + enum { + kUniformBinding = 0, + kLastUniformBinding = kUniformBinding, + }; + + // fUBOffset is only valid if the GrSLType of the fVariable is not a sampler + struct UniformInfo { + GrShaderVar fVariable; + uint32_t fVisibility; + uint32_t fUBOffset; + }; + typedef GrTAllocator UniformInfoArray; + + const GrShaderVar& getUniformVariable(UniformHandle u) const override { + return fUniforms[u.toIndex()].fVariable; + } + + const char* getUniformCStr(UniformHandle u) const override { + return this->getUniformVariable(u).c_str(); + } + +private: + explicit GrMtlUniformHandler(GrGLSLProgramBuilder* program) + : INHERITED(program) + , fUniforms(kUniformsPerBlock) + , fSamplers(kUniformsPerBlock) + , fCurrentUBOOffset(0) + , fCurrentUBOMaxAlignment(0x0) { + } + + UniformHandle internalAddUniformArray(uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) override; + + void updateUniformVisibility(UniformHandle u, uint32_t visibility) override { + fUniforms[u.toIndex()].fVisibility |= visibility; + } + + SamplerHandle addSampler(const GrTextureProxy*, + const GrSamplerState&, + const GrSwizzle&, + const char* name, + const GrShaderCaps*) override; + + int numSamplers() const { return fSamplers.count(); } + const char* samplerVariable(SamplerHandle handle) const override { + return fSamplers[handle.toIndex()].fVariable.c_str(); + } + GrSwizzle samplerSwizzle(SamplerHandle handle) const override { + return fSamplerSwizzles[handle.toIndex()]; + } + uint32_t samplerVisibility(SamplerHandle handle) const { + return fSamplers[handle.toIndex()].fVisibility; + } + + void appendUniformDecls(GrShaderFlags, SkString*) const override; + + const UniformInfo& getUniformInfo(UniformHandle u) const { + return fUniforms[u.toIndex()]; + } + + UniformInfoArray fUniforms; + UniformInfoArray fSamplers; + SkTArray fSamplerSwizzles; + + uint32_t fCurrentUBOOffset; + uint32_t fCurrentUBOMaxAlignment; + + friend class GrMtlPipelineStateBuilder; + + typedef GrGLSLUniformHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.mm new file mode 100644 index 0000000000..89e1263eaf --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlUniformHandler.mm @@ -0,0 +1,310 @@ +/* +* Copyright 2018 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/gpu/GrTexture.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/mtl/GrMtlUniformHandler.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +// TODO: this class is basically copy and pasted from GrVklUniformHandler so that we can have +// some shaders working. The SkSL Metal code generator was written to work with GLSL generated for +// the Ganesh Vulkan backend, so it should all work. There might be better ways to do things in +// Metal and/or some Vulkan GLSLisms left in. + +// To determine whether a current offset is aligned, we can just 'and' the lowest bits with the +// alignment mask. A value of 0 means aligned, any other value is how many bytes past alignment we +// are. This works since all alignments are powers of 2. The mask is always (alignment - 1). +static uint32_t grsltype_to_alignment_mask(GrSLType type) { + switch(type) { + case kByte_GrSLType: // fall through + case kUByte_GrSLType: + return 0x0; + case kByte2_GrSLType: // fall through + case kUByte2_GrSLType: + return 0x1; + case kByte3_GrSLType: // fall through + case kByte4_GrSLType: + case kUByte3_GrSLType: + case kUByte4_GrSLType: + return 0x3; + case kShort_GrSLType: // fall through + case kUShort_GrSLType: + return 0x1; + case kShort2_GrSLType: // fall through + case kUShort2_GrSLType: + return 0x3; + case kShort3_GrSLType: // fall through + case kShort4_GrSLType: + case kUShort3_GrSLType: + case kUShort4_GrSLType: + return 0x7; + case kInt_GrSLType: + case kUint_GrSLType: + return 0x3; + case kHalf_GrSLType: // fall through + case kFloat_GrSLType: + return 0x3; + case kHalf2_GrSLType: // fall through + case kFloat2_GrSLType: + return 0x7; + case kHalf3_GrSLType: // fall through + case kFloat3_GrSLType: + return 0xF; + case kHalf4_GrSLType: // fall through + case kFloat4_GrSLType: + return 0xF; + case kUint2_GrSLType: + return 0x7; + case kInt2_GrSLType: + return 0x7; + case kInt3_GrSLType: + return 0xF; + case kInt4_GrSLType: + return 0xF; + case kHalf2x2_GrSLType: // fall through + case kFloat2x2_GrSLType: + return 0x7; + case kHalf3x3_GrSLType: // fall through + case kFloat3x3_GrSLType: + return 0xF; + case kHalf4x4_GrSLType: // fall through + case kFloat4x4_GrSLType: + return 0xF; + + // This query is only valid for certain types. + case kVoid_GrSLType: + case kBool_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kSampler_GrSLType: + case kTexture2D_GrSLType: + break; + } + SK_ABORT("Unexpected type"); +} + +/** Returns the size in bytes taken up in Metal buffers for GrSLTypes. */ +static inline uint32_t grsltype_to_mtl_size(GrSLType type) { + switch(type) { + case kByte_GrSLType: + return sizeof(int8_t); + case kByte2_GrSLType: + return 2 * sizeof(int8_t); + case kByte3_GrSLType: + return 4 * sizeof(int8_t); + case kByte4_GrSLType: + return 4 * sizeof(int8_t); + case kUByte_GrSLType: + return sizeof(uint8_t); + case kUByte2_GrSLType: + return 2 * sizeof(uint8_t); + case kUByte3_GrSLType: + return 4 * sizeof(uint8_t); + case kUByte4_GrSLType: + return 4 * sizeof(uint8_t); + case kShort_GrSLType: + return sizeof(int16_t); + case kShort2_GrSLType: + return 2 * sizeof(int16_t); + case kShort3_GrSLType: + return 4 * sizeof(int16_t); + case kShort4_GrSLType: + return 4 * sizeof(int16_t); + case kUShort_GrSLType: + return sizeof(uint16_t); + case kUShort2_GrSLType: + return 2 * sizeof(uint16_t); + case kUShort3_GrSLType: + return 4 * sizeof(uint16_t); + case kUShort4_GrSLType: + return 4 * sizeof(uint16_t); + case kInt_GrSLType: + return sizeof(int32_t); + case kUint_GrSLType: + return sizeof(int32_t); + case kHalf_GrSLType: // fall through + case kFloat_GrSLType: + return sizeof(float); + case kHalf2_GrSLType: // fall through + case kFloat2_GrSLType: + return 2 * sizeof(float); + case kHalf3_GrSLType: // fall through + case kFloat3_GrSLType: + return 4 * sizeof(float); + case kHalf4_GrSLType: // fall through + case kFloat4_GrSLType: + return 4 * sizeof(float); + case kUint2_GrSLType: + return 2 * sizeof(uint32_t); + case kInt2_GrSLType: + return 2 * sizeof(int32_t); + case kInt3_GrSLType: + return 4 * sizeof(int32_t); + case kInt4_GrSLType: + return 4 * sizeof(int32_t); + case kHalf2x2_GrSLType: // fall through + case kFloat2x2_GrSLType: + return 4 * sizeof(float); + case kHalf3x3_GrSLType: // fall through + case kFloat3x3_GrSLType: + return 12 * sizeof(float); + case kHalf4x4_GrSLType: // fall through + case kFloat4x4_GrSLType: + return 16 * sizeof(float); + + // This query is only valid for certain types. + case kVoid_GrSLType: + case kBool_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kSampler_GrSLType: + case kTexture2D_GrSLType: + break; + } + SK_ABORT("Unexpected type"); +} + +// Given the current offset into the ubo, calculate the offset for the uniform we're trying to add +// taking into consideration all alignment requirements. The uniformOffset is set to the offset for +// the new uniform, and currentOffset is updated to be the offset to the end of the new uniform. +static void get_ubo_aligned_offset(uint32_t* uniformOffset, + uint32_t* currentOffset, + uint32_t* maxAlignment, + GrSLType type, + int arrayCount) { + uint32_t alignmentMask = grsltype_to_alignment_mask(type); + if (alignmentMask > *maxAlignment) { + *maxAlignment = alignmentMask; + } + uint32_t offsetDiff = *currentOffset & alignmentMask; + if (offsetDiff != 0) { + offsetDiff = alignmentMask - offsetDiff + 1; + } + *uniformOffset = *currentOffset + offsetDiff; + SkASSERT(sizeof(float) == 4); + if (arrayCount) { + *currentOffset = *uniformOffset + grsltype_to_mtl_size(type) * arrayCount; + } else { + *currentOffset = *uniformOffset + grsltype_to_mtl_size(type); + } +} + +GrGLSLUniformHandler::UniformHandle GrMtlUniformHandler::internalAddUniformArray( + uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) { + SkASSERT(name && strlen(name)); + GrSLTypeIsFloatType(type); + + UniformInfo& uni = fUniforms.push_back(); + uni.fVariable.setType(type); + // TODO this is a bit hacky, lets think of a better way. Basically we need to be able to use + // the uniform view matrix name in the GP, and the GP is immutable so it has to tell the PB + // exactly what name it wants to use for the uniform view matrix. If we prefix anythings, then + // the names will mismatch. I think the correct solution is to have all GPs which need the + // uniform view matrix, they should upload the view matrix in their setData along with regular + // uniforms. + char prefix = 'u'; + if ('u' == name[0] || !strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX))) { + prefix = '\0'; + } + fProgramBuilder->nameVariable(uni.fVariable.accessName(), prefix, name, mangleName); + uni.fVariable.setArrayCount(arrayCount); + uni.fVisibility = kFragment_GrShaderFlag | kVertex_GrShaderFlag; + // When outputing the GLSL, only the outer uniform block will get the Uniform modifier. Thus + // we set the modifier to none for all uniforms declared inside the block. + uni.fVariable.setTypeModifier(GrShaderVar::kNone_TypeModifier); + + get_ubo_aligned_offset(&uni.fUBOffset, &fCurrentUBOOffset, &fCurrentUBOMaxAlignment, type, + arrayCount); + + SkString layoutQualifier; + layoutQualifier.appendf("offset=%d", uni.fUBOffset); + uni.fVariable.addLayoutQualifier(layoutQualifier.c_str()); + + if (outName) { + *outName = uni.fVariable.c_str(); + } + + return GrGLSLUniformHandler::UniformHandle(fUniforms.count() - 1); +} + +GrGLSLUniformHandler::SamplerHandle GrMtlUniformHandler::addSampler(const GrTextureProxy* texture, + const GrSamplerState&, + const GrSwizzle& swizzle, + const char* name, + const GrShaderCaps* caps) { + SkASSERT(name && strlen(name)); + SkString mangleName; + char prefix = 'u'; + fProgramBuilder->nameVariable(&mangleName, prefix, name, true); + + GrTextureType type = texture->textureType(); + + UniformInfo& info = fSamplers.push_back(); + info.fVariable.setType(GrSLCombinedSamplerTypeForTextureType(type)); + info.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier); + info.fVariable.setName(mangleName); + SkString layoutQualifier; + layoutQualifier.appendf("binding=%d", fSamplers.count() - 1); + info.fVariable.addLayoutQualifier(layoutQualifier.c_str()); + info.fVisibility = kFragment_GrShaderFlag; + info.fUBOffset = 0; + SkASSERT(caps->textureSwizzleAppliedInShader()); + fSamplerSwizzles.push_back(swizzle); + SkASSERT(fSamplerSwizzles.count() == fSamplers.count()); + return GrGLSLUniformHandler::SamplerHandle(fSamplers.count() - 1); +} + +void GrMtlUniformHandler::appendUniformDecls(GrShaderFlags visibility, SkString* out) const { + for (int i = 0; i < fSamplers.count(); ++i) { + const UniformInfo& sampler = fSamplers[i]; + SkASSERT(sampler.fVariable.getType() == kTexture2DSampler_GrSLType); + if (visibility == sampler.fVisibility) { + sampler.fVariable.appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";\n"); + } + } + +#ifdef SK_DEBUG + bool firstOffsetCheck = false; + for (int i = 0; i < fUniforms.count(); ++i) { + const UniformInfo& localUniform = fUniforms[i]; + if (!firstOffsetCheck) { + // Check to make sure we are starting our offset at 0 so the offset qualifier we + // set on each variable in the uniform block is valid. + SkASSERT(0 == localUniform.fUBOffset); + firstOffsetCheck = true; + } + } +#endif + + SkString uniformsString; + for (int i = 0; i < fUniforms.count(); ++i) { + const UniformInfo& localUniform = fUniforms[i]; + if (visibility & localUniform.fVisibility) { + if (GrSLTypeIsFloatType(localUniform.fVariable.getType())) { + localUniform.fVariable.appendDecl(fProgramBuilder->shaderCaps(), &uniformsString); + uniformsString.append(";\n"); + } + } + } + + if (!uniformsString.isEmpty()) { + out->appendf("layout (binding=%d) uniform uniformBuffer\n{\n", kUniformBinding); + out->appendf("%s\n};\n", uniformsString.c_str()); + } +} diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlUtil.h b/gfx/skia/skia/src/gpu/mtl/GrMtlUtil.h new file mode 100644 index 0000000000..171b87d7ce --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlUtil.h @@ -0,0 +1,108 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMtlUtil_DEFINED +#define GrMtlUtil_DEFINED + +#import + +#include "include/gpu/GrBackendSurface.h" +#include "include/private/GrTypesPriv.h" +#include "src/sksl/ir/SkSLProgram.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +#if defined(SK_BUILD_FOR_MAC) +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101400 +#error Must use at least 10.14 SDK to build Metal backend for MacOS +#endif +#else +#if __IPHONE_OS_VERSION_MAX_ALLOWED < 120000 && __TV_OS_VERSION_MAX_ALLOWED < 120000 +#error Must use at least 12.00 SDK to build Metal backend for iOS +#endif +#endif + +class GrMtlGpu; +class GrSurface; + +/** + * Returns the Metal texture format for the given GrPixelConfig + */ +bool GrPixelConfigToMTLFormat(GrPixelConfig config, MTLPixelFormat* format); + +/** + * Returns a id to the MTLTexture pointed at by the const void*. + */ +SK_ALWAYS_INLINE id GrGetMTLTexture(const void* mtlTexture) { + return (__bridge id)mtlTexture; +} + +/** + * Returns a const void* to whatever the id object is pointing to. + */ +SK_ALWAYS_INLINE const void* GrGetPtrFromId(id idObject) { + return (__bridge const void*)idObject; +} + +/** + * Returns a const void* to whatever the id object is pointing to. + * Will call CFRetain on the object. + */ +SK_ALWAYS_INLINE const void* GrRetainPtrFromId(id idObject) { + return (__bridge_retained const void*)idObject; +} + + +/** + * Returns a MTLTextureDescriptor which describes the MTLTexture. Useful when creating a duplicate + * MTLTexture without the same storage allocation. + */ +MTLTextureDescriptor* GrGetMTLTextureDescriptor(id mtlTexture); + +/** + * Returns a compiled MTLLibrary created from MSL code generated by SkSLC + */ +id GrCompileMtlShaderLibrary(const GrMtlGpu* gpu, + const char* shaderString, + SkSL::Program::Kind kind, + const SkSL::Program::Settings& settings, + SkSL::Program::Inputs* outInputs); + +/** + * Replacement for newLibraryWithSource:options:error that has a timeout. + */ +id GrMtlNewLibraryWithSource(id, NSString* mslCode, + MTLCompileOptions*, bool* timedout); + +/** + * Replacement for newRenderPipelineStateWithDescriptor:error that has a timeout. + */ +id GrMtlNewRenderPipelineStateWithDescriptor( + id, MTLRenderPipelineDescriptor*, bool* timedout); + +/** + * Returns a MTLTexture corresponding to the GrSurface. + */ +id GrGetMTLTextureFromSurface(GrSurface* surface); + +static inline MTLPixelFormat GrBackendFormatAsMTLPixelFormat(const GrBackendFormat& format) { + return static_cast(format.asMtlFormat()); +} + +/** + * Returns true if the format is compressed. + */ +bool GrMtlFormatIsCompressed(MTLPixelFormat mtlFormat); + +/** + * Maps a MTLPixelFormat into the CompressionType enum if applicable. + */ +bool GrMtlFormatToCompressionType(MTLPixelFormat mtlFormat, + SkImage::CompressionType* compressionType); +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlUtil.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlUtil.mm new file mode 100644 index 0000000000..eccf435074 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlUtil.mm @@ -0,0 +1,351 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlUtil.h" + +#include "include/gpu/GrSurface.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/mtl/GrMtlGpu.h" +#include "src/gpu/mtl/GrMtlRenderTarget.h" +#include "src/gpu/mtl/GrMtlTexture.h" +#include "src/sksl/SkSLCompiler.h" + +#import + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +#define PRINT_MSL 0 // print out the MSL code generated + +bool GrPixelConfigToMTLFormat(GrPixelConfig config, MTLPixelFormat* format) { + MTLPixelFormat dontCare; + if (!format) { + format = &dontCare; + } + + switch (config) { + case kUnknown_GrPixelConfig: + return false; + case kRGBA_8888_GrPixelConfig: + *format = MTLPixelFormatRGBA8Unorm; + return true; + case kRGB_888_GrPixelConfig: + *format = MTLPixelFormatRGBA8Unorm; + return true; + case kRGB_888X_GrPixelConfig: + *format = MTLPixelFormatRGBA8Unorm; + return true; + case kRG_88_GrPixelConfig: + *format = MTLPixelFormatRG8Unorm; + return true; + case kBGRA_8888_GrPixelConfig: + *format = MTLPixelFormatBGRA8Unorm; + return true; + case kSRGBA_8888_GrPixelConfig: + *format = MTLPixelFormatRGBA8Unorm_sRGB; + return true; + case kRGBA_1010102_GrPixelConfig: + *format = MTLPixelFormatRGB10A2Unorm; + return true; + case kRGB_565_GrPixelConfig: +#ifdef SK_BUILD_FOR_IOS + *format = MTLPixelFormatB5G6R5Unorm; + return true; +#else + return false; +#endif + case kRGBA_4444_GrPixelConfig: +#ifdef SK_BUILD_FOR_IOS + *format = MTLPixelFormatABGR4Unorm; + return true; +#else + return false; +#endif + case kAlpha_8_GrPixelConfig: // fall through + case kAlpha_8_as_Red_GrPixelConfig: + *format = MTLPixelFormatR8Unorm; + return true; + case kAlpha_8_as_Alpha_GrPixelConfig: + *format = MTLPixelFormatA8Unorm; + return true; + case kGray_8_GrPixelConfig: // fall through + case kGray_8_as_Red_GrPixelConfig: + *format = MTLPixelFormatR8Unorm; + return true; + case kGray_8_as_Lum_GrPixelConfig: + return false; + case kRGBA_half_GrPixelConfig: + *format = MTLPixelFormatRGBA16Float; + return true; + case kRGBA_half_Clamped_GrPixelConfig: + *format = MTLPixelFormatRGBA16Float; + return true; + case kAlpha_half_GrPixelConfig: // fall through + case kAlpha_half_as_Red_GrPixelConfig: + *format = MTLPixelFormatR16Float; + return true; + case kAlpha_half_as_Lum_GrPixelConfig: + return false; + case kRGB_ETC1_GrPixelConfig: +#ifdef SK_BUILD_FOR_IOS + *format = MTLPixelFormatETC2_RGB8; + return true; +#else + return false; +#endif + case kAlpha_16_GrPixelConfig: + *format = MTLPixelFormatR16Unorm; + return true; + case kRG_1616_GrPixelConfig: + *format = MTLPixelFormatRG16Unorm; + return true; + case kRGBA_16161616_GrPixelConfig: + *format = MTLPixelFormatRGBA16Unorm; + return true; + case kRG_half_GrPixelConfig: + *format = MTLPixelFormatRG16Float; + return true; + } + SK_ABORT("Unexpected config"); +} + +MTLTextureDescriptor* GrGetMTLTextureDescriptor(id mtlTexture) { + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = mtlTexture.textureType; + texDesc.pixelFormat = mtlTexture.pixelFormat; + texDesc.width = mtlTexture.width; + texDesc.height = mtlTexture.height; + texDesc.depth = mtlTexture.depth; + texDesc.mipmapLevelCount = mtlTexture.mipmapLevelCount; + texDesc.arrayLength = mtlTexture.arrayLength; + texDesc.sampleCount = mtlTexture.sampleCount; + if (@available(macOS 10.11, iOS 9.0, *)) { + texDesc.usage = mtlTexture.usage; + } + return texDesc; +} + +#if PRINT_MSL +void print_msl(const char* source) { + SkTArray lines; + SkStrSplit(source, "\n", kStrict_SkStrSplitMode, &lines); + for (int i = 0; i < lines.count(); i++) { + SkString& line = lines[i]; + line.prependf("%4i\t", i + 1); + SkDebugf("%s\n", line.c_str()); + } +} +#endif + +id GrCompileMtlShaderLibrary(const GrMtlGpu* gpu, + const char* shaderString, + SkSL::Program::Kind kind, + const SkSL::Program::Settings& settings, + SkSL::Program::Inputs* outInputs) { + std::unique_ptr program = + gpu->shaderCompiler()->convertProgram(kind, + SkSL::String(shaderString), + settings); + + if (!program) { + SkDebugf("SkSL error:\n%s\n", gpu->shaderCompiler()->errorText().c_str()); + SkASSERT(false); + return nil; + } + + *outInputs = program->fInputs; + SkSL::String code; + if (!gpu->shaderCompiler()->toMetal(*program, &code)) { + SkDebugf("%s\n", gpu->shaderCompiler()->errorText().c_str()); + SkASSERT(false); + return nil; + } + NSString* mtlCode = [[NSString alloc] initWithCString: code.c_str() + encoding: NSASCIIStringEncoding]; +#if PRINT_MSL + print_msl([mtlCode cStringUsingEncoding: NSASCIIStringEncoding]); +#endif + + MTLCompileOptions* defaultOptions = [[MTLCompileOptions alloc] init]; +#if defined(SK_BUILD_FOR_MAC) && defined(GR_USE_COMPLETION_HANDLER) + bool timedout; + id compiledLibrary = GrMtlNewLibraryWithSource(gpu->device(), mtlCode, + defaultOptions, &timedout); + if (timedout) { + // try again + compiledLibrary = GrMtlNewLibraryWithSource(gpu->device(), mtlCode, + defaultOptions, &timedout); + } +#else + NSError* error = nil; + id compiledLibrary = [gpu->device() newLibraryWithSource: mtlCode + options: defaultOptions + error: &error]; + if (error) { + SkDebugf("Error compiling MSL shader: %s\n%s\n", + code.c_str(), + [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]); + return nil; + } +#endif + return compiledLibrary; +} + +id GrMtlNewLibraryWithSource(id device, NSString* mslCode, + MTLCompileOptions* options, bool* timedout) { + dispatch_semaphore_t compilerSemaphore = dispatch_semaphore_create(0); + + __block dispatch_semaphore_t semaphore = compilerSemaphore; + __block id compiledLibrary; + [device newLibraryWithSource: mslCode + options: options + completionHandler: + ^(id library, NSError* error) { + if (error) { + SkDebugf("Error compiling MSL shader: %s\n%s\n", + mslCode, + [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]); + } + compiledLibrary = library; + dispatch_semaphore_signal(semaphore); + } + ]; + + // Wait 100 ms for the compiler + if (dispatch_semaphore_wait(compilerSemaphore, dispatch_time(DISPATCH_TIME_NOW, 100000))) { + SkDebugf("Timeout compiling MSL shader\n"); + *timedout = true; + return nil; + } + + *timedout = false; + return compiledLibrary; +} + +id GrMtlNewRenderPipelineStateWithDescriptor( + id device, MTLRenderPipelineDescriptor* pipelineDescriptor, bool* timedout) { + dispatch_semaphore_t pipelineSemaphore = dispatch_semaphore_create(0); + + __block dispatch_semaphore_t semaphore = pipelineSemaphore; + __block id pipelineState; + [device newRenderPipelineStateWithDescriptor: pipelineDescriptor + completionHandler: + ^(id state, NSError* error) { + if (error) { + SkDebugf("Error creating pipeline: %s\n", + [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]); + } + pipelineState = state; + dispatch_semaphore_signal(semaphore); + } + ]; + + // Wait 500 ms for pipeline creation + if (dispatch_semaphore_wait(pipelineSemaphore, dispatch_time(DISPATCH_TIME_NOW, 500000))) { + SkDebugf("Timeout creating pipeline.\n"); + *timedout = true; + return nil; + } + + *timedout = false; + return pipelineState; +} + +id GrGetMTLTextureFromSurface(GrSurface* surface) { + id mtlTexture = nil; + + GrMtlRenderTarget* renderTarget = static_cast(surface->asRenderTarget()); + GrMtlTexture* texture; + if (renderTarget) { + // We should not be using this for multisampled rendertargets + if (renderTarget->numSamples() > 1) { + SkASSERT(false); + return nil; + } + mtlTexture = renderTarget->mtlColorTexture(); + } else { + texture = static_cast(surface->asTexture()); + if (texture) { + mtlTexture = texture->mtlTexture(); + } + } + return mtlTexture; +} + + +////////////////////////////////////////////////////////////////////////////// +// CPP Utils + +GrMTLPixelFormat GrGetMTLPixelFormatFromMtlTextureInfo(const GrMtlTextureInfo& info) { + id mtlTexture = GrGetMTLTexture(info.fTexture.get()); + return static_cast(mtlTexture.pixelFormat); +} + +bool GrMtlFormatIsCompressed(MTLPixelFormat mtlFormat) { + switch (mtlFormat) { +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatETC2_RGB8: + return true; +#endif + default: + return false; + } +} + +bool GrMtlFormatToCompressionType(MTLPixelFormat mtlFormat, + SkImage::CompressionType* compressionType) { + switch (mtlFormat) { +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatETC2_RGB8: + *compressionType = SkImage::kETC1_CompressionType; + return true; +#endif + default: + return false; + } +} + +#if GR_TEST_UTILS +bool GrMtlFormatIsBGRA(GrMTLPixelFormat mtlFormat) { + return mtlFormat == MTLPixelFormatBGRA8Unorm; +} + +const char* GrMtlFormatToStr(GrMTLPixelFormat mtlFormat) { + switch (mtlFormat) { + case MTLPixelFormatInvalid: return "Invalid"; + case MTLPixelFormatRGBA8Unorm: return "RGBA8Unorm"; + case MTLPixelFormatR8Unorm: return "R8Unorm"; + case MTLPixelFormatA8Unorm: return "A8Unorm"; + case MTLPixelFormatBGRA8Unorm: return "BGRA8Unorm"; +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatB5G6R5Unorm: return "B5G6R5Unorm"; +#endif + case MTLPixelFormatRGBA16Float: return "RGBA16Float"; + case MTLPixelFormatR16Float: return "R16Float"; + case MTLPixelFormatRG8Unorm: return "RG8Unorm"; + case MTLPixelFormatRGB10A2Unorm: return "RGB10A2Unorm"; +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatABGR4Unorm: return "ABGR4Unorm"; +#endif + case MTLPixelFormatRGBA8Unorm_sRGB: return "RGBA8Unorm_sRGB"; + case MTLPixelFormatR16Unorm: return "R16Unorm"; + case MTLPixelFormatRG16Unorm: return "RG16Unorm"; +#ifdef SK_BUILD_FOR_IOS + case MTLPixelFormatETC2_RGB8: return "ETC2_RGB8"; +#endif + case MTLPixelFormatRGBA16Unorm: return "RGBA16Unorm"; + case MTLPixelFormatRG16Float: return "RG16Float"; + + default: return "Unknown"; + } +} + +#endif + + + diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.h b/gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.h new file mode 100644 index 0000000000..80cd584836 --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.h @@ -0,0 +1,27 @@ +/* +* Copyright 2018 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrMtlVaryingHandler_DEFINED +#define GrMtlVaryingHandler_DEFINED + +#include "src/gpu/glsl/GrGLSLVarying.h" + +class GrMtlVaryingHandler : public GrGLSLVaryingHandler { +public: + GrMtlVaryingHandler(GrGLSLProgramBuilder* program) : INHERITED(program) {} + + typedef GrGLSLVaryingHandler::VarArray VarArray; + +private: + void onFinalize() override; + + friend class GrMtlPipelineStateBuilder; + + typedef GrGLSLVaryingHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.mm b/gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.mm new file mode 100644 index 0000000000..c01e987d1d --- /dev/null +++ b/gfx/skia/skia/src/gpu/mtl/GrMtlVaryingHandler.mm @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/mtl/GrMtlVaryingHandler.h" + +#if !__has_feature(objc_arc) +#error This file must be compiled with Arc. Use -fobjc-arc flag +#endif + +static void finalize_helper(GrMtlVaryingHandler::VarArray& vars) { + int locationIndex; + int componentCount = 0; + for (locationIndex = 0; locationIndex < vars.count(); locationIndex++) { + GrShaderVar& var = vars[locationIndex]; + // Metal only allows scalars (including bool and char) and vectors as varyings + SkASSERT(GrSLTypeVecLength(var.getType()) != -1); + componentCount += GrSLTypeVecLength(var.getType()); + + SkString location; + location.appendf("location = %d", locationIndex); + var.addLayoutQualifier(location.c_str()); + } + // The max number of inputs is 60 for iOS and 32 for macOS. The max number of components is 60 + // for iOS and 128 for macOS. To be conservative, we are going to assert that we have less than + // 32 varyings and less than 60 components across all varyings. If we hit this assert, we can + // implement a function in GrMtlCaps to be less conservative. + SkASSERT(locationIndex <= 32); + SkASSERT(componentCount <= 60); +} + +void GrMtlVaryingHandler::onFinalize() { + finalize_helper(fVertexInputs); + finalize_helper(fVertexOutputs); + finalize_helper(fGeomInputs); + finalize_helper(fGeomOutputs); + finalize_helper(fFragInputs); + finalize_helper(fFragOutputs); +} diff --git a/gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.cpp new file mode 100644 index 0000000000..8906d29a5e --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.cpp @@ -0,0 +1,880 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkPointPriv.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/geometry/GrPathUtils.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" +#include "src/gpu/ops/GrAAConvexPathRenderer.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +GrAAConvexPathRenderer::GrAAConvexPathRenderer() { +} + +struct Segment { + enum { + // These enum values are assumed in member functions below. + kLine = 0, + kQuad = 1, + } fType; + + // line uses one pt, quad uses 2 pts + SkPoint fPts[2]; + // normal to edge ending at each pt + SkVector fNorms[2]; + // is the corner where the previous segment meets this segment + // sharp. If so, fMid is a normalized bisector facing outward. + SkVector fMid; + + int countPoints() { + GR_STATIC_ASSERT(0 == kLine && 1 == kQuad); + return fType + 1; + } + const SkPoint& endPt() const { + GR_STATIC_ASSERT(0 == kLine && 1 == kQuad); + return fPts[fType]; + } + const SkPoint& endNorm() const { + GR_STATIC_ASSERT(0 == kLine && 1 == kQuad); + return fNorms[fType]; + } +}; + +typedef SkTArray SegmentArray; + +static bool center_of_mass(const SegmentArray& segments, SkPoint* c) { + SkScalar area = 0; + SkPoint center = {0, 0}; + int count = segments.count(); + SkPoint p0 = {0, 0}; + if (count > 2) { + // We translate the polygon so that the first point is at the origin. + // This avoids some precision issues with small area polygons far away + // from the origin. + p0 = segments[0].endPt(); + SkPoint pi; + SkPoint pj; + // the first and last iteration of the below loop would compute + // zeros since the starting / ending point is (0,0). So instead we start + // at i=1 and make the last iteration i=count-2. + pj = segments[1].endPt() - p0; + for (int i = 1; i < count - 1; ++i) { + pi = pj; + pj = segments[i + 1].endPt() - p0; + + SkScalar t = SkPoint::CrossProduct(pi, pj); + area += t; + center.fX += (pi.fX + pj.fX) * t; + center.fY += (pi.fY + pj.fY) * t; + } + } + + // If the poly has no area then we instead return the average of + // its points. + if (SkScalarNearlyZero(area)) { + SkPoint avg; + avg.set(0, 0); + for (int i = 0; i < count; ++i) { + const SkPoint& pt = segments[i].endPt(); + avg.fX += pt.fX; + avg.fY += pt.fY; + } + SkScalar denom = SK_Scalar1 / count; + avg.scale(denom); + *c = avg; + } else { + area *= 3; + area = SkScalarInvert(area); + center.scale(area); + // undo the translate of p0 to the origin. + *c = center + p0; + } + return !SkScalarIsNaN(c->fX) && !SkScalarIsNaN(c->fY) && c->isFinite(); +} + +static bool compute_vectors(SegmentArray* segments, + SkPoint* fanPt, + SkPathPriv::FirstDirection dir, + int* vCount, + int* iCount) { + if (!center_of_mass(*segments, fanPt)) { + return false; + } + int count = segments->count(); + + // Make the normals point towards the outside + SkPointPriv::Side normSide; + if (dir == SkPathPriv::kCCW_FirstDirection) { + normSide = SkPointPriv::kRight_Side; + } else { + normSide = SkPointPriv::kLeft_Side; + } + + int64_t vCount64 = 0; + int64_t iCount64 = 0; + // compute normals at all points + for (int a = 0; a < count; ++a) { + Segment& sega = (*segments)[a]; + int b = (a + 1) % count; + Segment& segb = (*segments)[b]; + + const SkPoint* prevPt = &sega.endPt(); + int n = segb.countPoints(); + for (int p = 0; p < n; ++p) { + segb.fNorms[p] = segb.fPts[p] - *prevPt; + segb.fNorms[p].normalize(); + segb.fNorms[p] = SkPointPriv::MakeOrthog(segb.fNorms[p], normSide); + prevPt = &segb.fPts[p]; + } + if (Segment::kLine == segb.fType) { + vCount64 += 5; + iCount64 += 9; + } else { + vCount64 += 6; + iCount64 += 12; + } + } + + // compute mid-vectors where segments meet. TODO: Detect shallow corners + // and leave out the wedges and close gaps by stitching segments together. + for (int a = 0; a < count; ++a) { + const Segment& sega = (*segments)[a]; + int b = (a + 1) % count; + Segment& segb = (*segments)[b]; + segb.fMid = segb.fNorms[0] + sega.endNorm(); + segb.fMid.normalize(); + // corner wedges + vCount64 += 4; + iCount64 += 6; + } + if (vCount64 > SK_MaxS32 || iCount64 > SK_MaxS32) { + return false; + } + *vCount = vCount64; + *iCount = iCount64; + return true; +} + +struct DegenerateTestData { + DegenerateTestData() { fStage = kInitial; } + bool isDegenerate() const { return kNonDegenerate != fStage; } + enum { + kInitial, + kPoint, + kLine, + kNonDegenerate + } fStage; + SkPoint fFirstPoint; + SkVector fLineNormal; + SkScalar fLineC; +}; + +static const SkScalar kClose = (SK_Scalar1 / 16); +static const SkScalar kCloseSqd = kClose * kClose; + +static void update_degenerate_test(DegenerateTestData* data, const SkPoint& pt) { + switch (data->fStage) { + case DegenerateTestData::kInitial: + data->fFirstPoint = pt; + data->fStage = DegenerateTestData::kPoint; + break; + case DegenerateTestData::kPoint: + if (SkPointPriv::DistanceToSqd(pt, data->fFirstPoint) > kCloseSqd) { + data->fLineNormal = pt - data->fFirstPoint; + data->fLineNormal.normalize(); + data->fLineNormal = SkPointPriv::MakeOrthog(data->fLineNormal); + data->fLineC = -data->fLineNormal.dot(data->fFirstPoint); + data->fStage = DegenerateTestData::kLine; + } + break; + case DegenerateTestData::kLine: + if (SkScalarAbs(data->fLineNormal.dot(pt) + data->fLineC) > kClose) { + data->fStage = DegenerateTestData::kNonDegenerate; + } + case DegenerateTestData::kNonDegenerate: + break; + default: + SK_ABORT("Unexpected degenerate test stage."); + } +} + +static inline bool get_direction(const SkPath& path, const SkMatrix& m, + SkPathPriv::FirstDirection* dir) { + // At this point, we've already returned true from canDraw(), which checked that the path's + // direction could be determined, so this should just be fetching the cached direction. + // However, if perspective is involved, we're operating on a transformed path, which may no + // longer have a computable direction. + if (!SkPathPriv::CheapComputeFirstDirection(path, dir)) { + return false; + } + + // check whether m reverses the orientation + SkASSERT(!m.hasPerspective()); + SkScalar det2x2 = m.get(SkMatrix::kMScaleX) * m.get(SkMatrix::kMScaleY) - + m.get(SkMatrix::kMSkewX) * m.get(SkMatrix::kMSkewY); + if (det2x2 < 0) { + *dir = SkPathPriv::OppositeFirstDirection(*dir); + } + + return true; +} + +static inline void add_line_to_segment(const SkPoint& pt, + SegmentArray* segments) { + segments->push_back(); + segments->back().fType = Segment::kLine; + segments->back().fPts[0] = pt; +} + +static inline void add_quad_segment(const SkPoint pts[3], + SegmentArray* segments) { + if (SkPointPriv::DistanceToLineSegmentBetweenSqd(pts[1], pts[0], pts[2]) < kCloseSqd) { + if (pts[0] != pts[2]) { + add_line_to_segment(pts[2], segments); + } + } else { + segments->push_back(); + segments->back().fType = Segment::kQuad; + segments->back().fPts[0] = pts[1]; + segments->back().fPts[1] = pts[2]; + } +} + +static inline void add_cubic_segments(const SkPoint pts[4], + SkPathPriv::FirstDirection dir, + SegmentArray* segments) { + SkSTArray<15, SkPoint, true> quads; + GrPathUtils::convertCubicToQuadsConstrainToTangents(pts, SK_Scalar1, dir, &quads); + int count = quads.count(); + for (int q = 0; q < count; q += 3) { + add_quad_segment(&quads[q], segments); + } +} + +static bool get_segments(const SkPath& path, + const SkMatrix& m, + SegmentArray* segments, + SkPoint* fanPt, + int* vCount, + int* iCount) { + SkPath::Iter iter(path, true); + // This renderer over-emphasizes very thin path regions. We use the distance + // to the path from the sample to compute coverage. Every pixel intersected + // by the path will be hit and the maximum distance is sqrt(2)/2. We don't + // notice that the sample may be close to a very thin area of the path and + // thus should be very light. This is particularly egregious for degenerate + // line paths. We detect paths that are very close to a line (zero area) and + // draw nothing. + DegenerateTestData degenerateData; + SkPathPriv::FirstDirection dir; + if (!get_direction(path, m, &dir)) { + return false; + } + + for (;;) { + SkPoint pts[4]; + SkPath::Verb verb = iter.next(pts); + switch (verb) { + case SkPath::kMove_Verb: + m.mapPoints(pts, 1); + update_degenerate_test(°enerateData, pts[0]); + break; + case SkPath::kLine_Verb: { + if (!SkPathPriv::AllPointsEq(pts, 2)) { + m.mapPoints(&pts[1], 1); + update_degenerate_test(°enerateData, pts[1]); + add_line_to_segment(pts[1], segments); + } + break; + } + case SkPath::kQuad_Verb: + if (!SkPathPriv::AllPointsEq(pts, 3)) { + m.mapPoints(pts, 3); + update_degenerate_test(°enerateData, pts[1]); + update_degenerate_test(°enerateData, pts[2]); + add_quad_segment(pts, segments); + } + break; + case SkPath::kConic_Verb: { + if (!SkPathPriv::AllPointsEq(pts, 3)) { + m.mapPoints(pts, 3); + SkScalar weight = iter.conicWeight(); + SkAutoConicToQuads converter; + const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f); + for (int i = 0; i < converter.countQuads(); ++i) { + update_degenerate_test(°enerateData, quadPts[2*i + 1]); + update_degenerate_test(°enerateData, quadPts[2*i + 2]); + add_quad_segment(quadPts + 2*i, segments); + } + } + break; + } + case SkPath::kCubic_Verb: { + if (!SkPathPriv::AllPointsEq(pts, 4)) { + m.mapPoints(pts, 4); + update_degenerate_test(°enerateData, pts[1]); + update_degenerate_test(°enerateData, pts[2]); + update_degenerate_test(°enerateData, pts[3]); + add_cubic_segments(pts, dir, segments); + } + break; + } + case SkPath::kDone_Verb: + if (degenerateData.isDegenerate()) { + return false; + } else { + return compute_vectors(segments, fanPt, dir, vCount, iCount); + } + default: + break; + } + } +} + +struct Draw { + Draw() : fVertexCnt(0), fIndexCnt(0) {} + int fVertexCnt; + int fIndexCnt; +}; + +typedef SkTArray DrawArray; + +static void create_vertices(const SegmentArray& segments, + const SkPoint& fanPt, + const GrVertexColor& color, + DrawArray* draws, + GrVertexWriter& verts, + uint16_t* idxs, + size_t vertexStride) { + Draw* draw = &draws->push_back(); + // alias just to make vert/index assignments easier to read. + int* v = &draw->fVertexCnt; + int* i = &draw->fIndexCnt; + const size_t uvOffset = sizeof(SkPoint) + color.size(); + + int count = segments.count(); + for (int a = 0; a < count; ++a) { + const Segment& sega = segments[a]; + int b = (a + 1) % count; + const Segment& segb = segments[b]; + + // Check whether adding the verts for this segment to the current draw would cause index + // values to overflow. + int vCount = 4; + if (Segment::kLine == segb.fType) { + vCount += 5; + } else { + vCount += 6; + } + if (draw->fVertexCnt + vCount > (1 << 16)) { + idxs += *i; + draw = &draws->push_back(); + v = &draw->fVertexCnt; + i = &draw->fIndexCnt; + } + + const SkScalar negOneDists[2] = { -SK_Scalar1, -SK_Scalar1 }; + + // FIXME: These tris are inset in the 1 unit arc around the corner + SkPoint p0 = sega.endPt(); + // Position, Color, UV, D0, D1 + verts.write(p0, color, SkPoint{0, 0}, negOneDists); + verts.write(p0 + sega.endNorm(), color, SkPoint{0, -SK_Scalar1}, negOneDists); + verts.write(p0 + segb.fMid, color, SkPoint{0, -SK_Scalar1}, negOneDists); + verts.write(p0 + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists); + + idxs[*i + 0] = *v + 0; + idxs[*i + 1] = *v + 2; + idxs[*i + 2] = *v + 1; + idxs[*i + 3] = *v + 0; + idxs[*i + 4] = *v + 3; + idxs[*i + 5] = *v + 2; + + *v += 4; + *i += 6; + + if (Segment::kLine == segb.fType) { + // we draw the line edge as a degenerate quad (u is 0, v is the + // signed distance to the edge) + SkPoint v1Pos = sega.endPt(); + SkPoint v2Pos = segb.fPts[0]; + SkScalar dist = SkPointPriv::DistanceToLineBetween(fanPt, v1Pos, v2Pos); + + verts.write(fanPt, color, SkPoint{0, dist}, negOneDists); + verts.write(v1Pos, color, SkPoint{0, 0}, negOneDists); + verts.write(v2Pos, color, SkPoint{0, 0}, negOneDists); + verts.write(v1Pos + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists); + verts.write(v2Pos + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists); + + idxs[*i + 0] = *v + 3; + idxs[*i + 1] = *v + 1; + idxs[*i + 2] = *v + 2; + + idxs[*i + 3] = *v + 4; + idxs[*i + 4] = *v + 3; + idxs[*i + 5] = *v + 2; + + *i += 6; + + // Draw the interior fan if it exists. + // TODO: Detect and combine colinear segments. This will ensure we catch every case + // with no interior, and that the resulting shared edge uses the same endpoints. + if (count >= 3) { + idxs[*i + 0] = *v + 0; + idxs[*i + 1] = *v + 2; + idxs[*i + 2] = *v + 1; + + *i += 3; + } + + *v += 5; + } else { + void* quadVertsBegin = verts.fPtr; + + SkPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]}; + + SkScalar c0 = segb.fNorms[0].dot(qpts[0]); + SkScalar c1 = segb.fNorms[1].dot(qpts[2]); + GrVertexWriter::Skip skipUVs; + + verts.write(fanPt, + color, skipUVs, + -segb.fNorms[0].dot(fanPt) + c0, + -segb.fNorms[1].dot(fanPt) + c1); + + verts.write(qpts[0], + color, skipUVs, + 0.0f, + -segb.fNorms[1].dot(qpts[0]) + c1); + + verts.write(qpts[2], + color, skipUVs, + -segb.fNorms[0].dot(qpts[2]) + c0, + 0.0f); + + verts.write(qpts[0] + segb.fNorms[0], + color, skipUVs, + -SK_ScalarMax/100, + -SK_ScalarMax/100); + + verts.write(qpts[2] + segb.fNorms[1], + color, skipUVs, + -SK_ScalarMax/100, + -SK_ScalarMax/100); + + SkVector midVec = segb.fNorms[0] + segb.fNorms[1]; + midVec.normalize(); + + verts.write(qpts[1] + midVec, + color, skipUVs, + -SK_ScalarMax/100, + -SK_ScalarMax/100); + + GrPathUtils::QuadUVMatrix toUV(qpts); + toUV.apply(quadVertsBegin, 6, vertexStride, uvOffset); + + idxs[*i + 0] = *v + 3; + idxs[*i + 1] = *v + 1; + idxs[*i + 2] = *v + 2; + idxs[*i + 3] = *v + 4; + idxs[*i + 4] = *v + 3; + idxs[*i + 5] = *v + 2; + + idxs[*i + 6] = *v + 5; + idxs[*i + 7] = *v + 3; + idxs[*i + 8] = *v + 4; + + *i += 9; + + // Draw the interior fan if it exists. + // TODO: Detect and combine colinear segments. This will ensure we catch every case + // with no interior, and that the resulting shared edge uses the same endpoints. + if (count >= 3) { + idxs[*i + 0] = *v + 0; + idxs[*i + 1] = *v + 2; + idxs[*i + 2] = *v + 1; + + *i += 3; + } + + *v += 6; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +/* + * Quadratic specified by 0=u^2-v canonical coords. u and v are the first + * two components of the vertex attribute. Coverage is based on signed + * distance with negative being inside, positive outside. The edge is specified in + * window space (y-down). If either the third or fourth component of the interpolated + * vertex coord is > 0 then the pixel is considered outside the edge. This is used to + * attempt to trim to a portion of the infinite quad. + * Requires shader derivative instruction support. + */ + +class QuadEdgeEffect : public GrGeometryProcessor { +public: + static sk_sp Make(const SkMatrix& localMatrix, bool usesLocalCoords, + bool wideColor) { + return sk_sp( + new QuadEdgeEffect(localMatrix, usesLocalCoords, wideColor)); + } + + ~QuadEdgeEffect() override {} + + const char* name() const override { return "QuadEdge"; } + + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + GLSLProcessor() {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const QuadEdgeEffect& qe = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(qe); + + GrGLSLVarying v(kHalf4_GrSLType); + varyingHandler->addVarying("QuadEdge", &v); + vertBuilder->codeAppendf("%s = %s;", v.vsOut(), qe.fInQuadEdge.name()); + + // Setup pass through color + varyingHandler->addPassThroughAttribute(qe.fInColor, args.fOutputColor); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, qe.fInPosition.name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + qe.fInPosition.asShaderVar(), + qe.fLocalMatrix, + args.fFPCoordTransformHandler); + + fragBuilder->codeAppendf("half edgeAlpha;"); + + // keep the derivative instructions outside the conditional + fragBuilder->codeAppendf("half2 duvdx = half2(dFdx(%s.xy));", v.fsIn()); + fragBuilder->codeAppendf("half2 duvdy = half2(dFdy(%s.xy));", v.fsIn()); + fragBuilder->codeAppendf("if (%s.z > 0.0 && %s.w > 0.0) {", v.fsIn(), v.fsIn()); + // today we know z and w are in device space. We could use derivatives + fragBuilder->codeAppendf("edgeAlpha = min(min(%s.z, %s.w) + 0.5, 1.0);", v.fsIn(), + v.fsIn()); + fragBuilder->codeAppendf ("} else {"); + fragBuilder->codeAppendf("half2 gF = half2(2.0*%s.x*duvdx.x - duvdx.y," + " 2.0*%s.x*duvdy.x - duvdy.y);", + v.fsIn(), v.fsIn()); + fragBuilder->codeAppendf("edgeAlpha = (%s.x*%s.x - %s.y);", v.fsIn(), v.fsIn(), + v.fsIn()); + fragBuilder->codeAppendf("edgeAlpha = " + "saturate(0.5 - edgeAlpha / length(gF));}"); + + fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage); + } + + static inline void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const QuadEdgeEffect& qee = gp.cast(); + b->add32(SkToBool(qee.fUsesLocalCoords && qee.fLocalMatrix.hasPerspective())); + } + + void setData(const GrGLSLProgramDataManager& pdman, + const GrPrimitiveProcessor& gp, + FPCoordTransformIter&& transformIter) override { + const QuadEdgeEffect& qe = gp.cast(); + this->setTransformDataHelper(qe.fLocalMatrix, pdman, &transformIter); + } + + private: + typedef GrGLSLGeometryProcessor INHERITED; + }; + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLProcessor::GenKey(*this, caps, b); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new GLSLProcessor(); + } + +private: + QuadEdgeEffect(const SkMatrix& localMatrix, bool usesLocalCoords, bool wideColor) + : INHERITED(kQuadEdgeEffect_ClassID) + , fLocalMatrix(localMatrix) + , fUsesLocalCoords(usesLocalCoords) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = MakeColorAttribute("inColor", wideColor); + fInQuadEdge = {"inQuadEdge", kFloat4_GrVertexAttribType, kHalf4_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); + } + + Attribute fInPosition; + Attribute fInColor; + Attribute fInQuadEdge; + + SkMatrix fLocalMatrix; + bool fUsesLocalCoords; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(QuadEdgeEffect); + +#if GR_TEST_UTILS +sk_sp QuadEdgeEffect::TestCreate(GrProcessorTestData* d) { + // Doesn't work without derivative instructions. + return d->caps()->shaderCaps()->shaderDerivativeSupport() + ? QuadEdgeEffect::Make(GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool(), + d->fRandom->nextBool()) + : nullptr; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +GrPathRenderer::CanDrawPath +GrAAConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + // This check requires convexity and known direction, since the direction is used to build + // the geometry segments. Degenerate convex paths will fall through to some other path renderer. + if (args.fCaps->shaderCaps()->shaderDerivativeSupport() && + (GrAAType::kCoverage == args.fAAType) && args.fShape->style().isSimpleFill() && + !args.fShape->inverseFilled() && args.fShape->knownToBeConvex() && + args.fShape->knownDirection()) { + return CanDrawPath::kYes; + } + return CanDrawPath::kNo; +} + +namespace { + +class AAConvexPathOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkPath& path, + const GrUserStencilSettings* stencilSettings) { + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, path, + stencilSettings); + } + + AAConvexPathOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const SkPath& path, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) { + fPaths.emplace_back(PathData{viewMatrix, path, color}); + this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes, + IsHairline::kNo); + } + + const char* name() const override { return "AAConvexPathOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf("Count: %d\n", fPaths.count()); + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, &fPaths.back().fColor, &fWideColor); + } + +private: + void onPrepareDraws(Target* target) override { + int instanceCount = fPaths.count(); + + SkMatrix invert; + if (fHelper.usesLocalCoords() && !fPaths.back().fViewMatrix.invert(&invert)) { + return; + } + + // Setup GrGeometryProcessor + sk_sp quadProcessor( + QuadEdgeEffect::Make(invert, fHelper.usesLocalCoords(), fWideColor)); + const size_t kVertexStride = quadProcessor->vertexStride(); + + // TODO generate all segments for all paths and use one vertex buffer + for (int i = 0; i < instanceCount; i++) { + const PathData& args = fPaths[i]; + + // We use the fact that SkPath::transform path does subdivision based on + // perspective. Otherwise, we apply the view matrix when copying to the + // segment representation. + const SkMatrix* viewMatrix = &args.fViewMatrix; + + // We avoid initializing the path unless we have to + const SkPath* pathPtr = &args.fPath; + SkTLazy tmpPath; + if (viewMatrix->hasPerspective()) { + SkPath* tmpPathPtr = tmpPath.init(*pathPtr); + tmpPathPtr->setIsVolatile(true); + tmpPathPtr->transform(*viewMatrix); + viewMatrix = &SkMatrix::I(); + pathPtr = tmpPathPtr; + } + + int vertexCount; + int indexCount; + enum { + kPreallocSegmentCnt = 512 / sizeof(Segment), + kPreallocDrawCnt = 4, + }; + SkSTArray segments; + SkPoint fanPt; + + if (!get_segments(*pathPtr, *viewMatrix, &segments, &fanPt, &vertexCount, + &indexCount)) { + continue; + } + + sk_sp vertexBuffer; + int firstVertex; + + GrVertexWriter verts{target->makeVertexSpace(kVertexStride, vertexCount, + &vertexBuffer, &firstVertex)}; + + if (!verts.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + sk_sp indexBuffer; + int firstIndex; + + uint16_t *idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex); + if (!idxs) { + SkDebugf("Could not allocate indices\n"); + return; + } + + SkSTArray draws; + GrVertexColor color(args.fColor, fWideColor); + create_vertices(segments, fanPt, color, &draws, verts, idxs, kVertexStride); + + GrMesh* meshes = target->allocMeshes(draws.count()); + for (int j = 0; j < draws.count(); ++j) { + const Draw& draw = draws[j]; + meshes[j].setPrimitiveType(GrPrimitiveType::kTriangles); + meshes[j].setIndexed(indexBuffer, draw.fIndexCnt, firstIndex, 0, + draw.fVertexCnt - 1, GrPrimitiveRestart::kNo); + meshes[j].setVertexData(vertexBuffer, firstVertex); + firstIndex += draw.fIndexCnt; + firstVertex += draw.fVertexCnt; + } + target->recordDraw(quadProcessor, meshes, draws.count()); + } + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + AAConvexPathOp* that = t->cast(); + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + if (fHelper.usesLocalCoords() && + !fPaths[0].fViewMatrix.cheapEqualTo(that->fPaths[0].fViewMatrix)) { + return CombineResult::kCannotCombine; + } + + fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + struct PathData { + SkMatrix fViewMatrix; + SkPath fPath; + SkPMColor4f fColor; + }; + + Helper fHelper; + SkSTArray<1, PathData, true> fPaths; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +bool GrAAConvexPathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrAAConvexPathRenderer::onDrawPath"); + SkASSERT(args.fRenderTargetContext->numSamples() <= 1); + SkASSERT(!args.fShape->isEmpty()); + + SkPath path; + args.fShape->asPath(&path); + + std::unique_ptr op = AAConvexPathOp::Make(args.fContext, std::move(args.fPaint), + *args.fViewMatrix, + path, args.fUserStencilSettings); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(AAConvexPathOp) { + SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); + SkPath path = GrTest::TestPathConvex(random); + const GrUserStencilSettings* stencilSettings = GrGetRandomStencil(random, context); + return AAConvexPathOp::Make(context, std::move(paint), viewMatrix, path, stencilSettings); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.h new file mode 100644 index 0000000000..b6b01dc725 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAAConvexPathRenderer.h @@ -0,0 +1,23 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAAConvexPathRenderer_DEFINED +#define GrAAConvexPathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +class GrAAConvexPathRenderer : public GrPathRenderer { +public: + GrAAConvexPathRenderer(); + +private: + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.cpp b/gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.cpp new file mode 100644 index 0000000000..9952491b64 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.cpp @@ -0,0 +1,1108 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "include/core/SkString.h" +#include "src/gpu/geometry/GrPathUtils.h" +#include "src/gpu/ops/GrAAConvexTessellator.h" + +// Next steps: +// add an interactive sample app slide +// add debug check that all points are suitably far apart +// test more degenerate cases + +// The tolerance for fusing vertices and eliminating colinear lines (It is in device space). +static const SkScalar kClose = (SK_Scalar1 / 16); +static const SkScalar kCloseSqd = kClose * kClose; + +// tesselation tolerance values, in device space pixels +static const SkScalar kQuadTolerance = 0.2f; +static const SkScalar kCubicTolerance = 0.2f; +static const SkScalar kConicTolerance = 0.25f; + +// dot product below which we use a round cap between curve segments +static const SkScalar kRoundCapThreshold = 0.8f; + +// dot product above which we consider two adjacent curves to be part of the "same" curve +static const SkScalar kCurveConnectionThreshold = 0.8f; + +static bool intersect(const SkPoint& p0, const SkPoint& n0, + const SkPoint& p1, const SkPoint& n1, + SkScalar* t) { + const SkPoint v = p1 - p0; + SkScalar perpDot = n0.fX * n1.fY - n0.fY * n1.fX; + if (SkScalarNearlyZero(perpDot)) { + return false; + } + *t = (v.fX * n1.fY - v.fY * n1.fX) / perpDot; + SkASSERT(SkScalarIsFinite(*t)); + return true; +} + +// This is a special case version of intersect where we have the vector +// perpendicular to the second line rather than the vector parallel to it. +static SkScalar perp_intersect(const SkPoint& p0, const SkPoint& n0, + const SkPoint& p1, const SkPoint& perp) { + const SkPoint v = p1 - p0; + SkScalar perpDot = n0.dot(perp); + return v.dot(perp) / perpDot; +} + +static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) { + SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1); + return distSq < kCloseSqd; +} + +static bool points_are_colinear_and_b_is_middle(const SkPoint& a, const SkPoint& b, + const SkPoint& c) { + // 'area' is twice the area of the triangle with corners a, b, and c. + SkScalar area = a.fX * (b.fY - c.fY) + b.fX * (c.fY - a.fY) + c.fX * (a.fY - b.fY); + if (SkScalarAbs(area) >= 2 * kCloseSqd) { + return false; + } + return (a - b).dot(b - c) >= 0; +} + +int GrAAConvexTessellator::addPt(const SkPoint& pt, + SkScalar depth, + SkScalar coverage, + bool movable, + CurveState curve) { + SkASSERT(pt.isFinite()); + this->validate(); + + int index = fPts.count(); + *fPts.push() = pt; + *fCoverages.push() = coverage; + *fMovable.push() = movable; + *fCurveState.push() = curve; + + this->validate(); + return index; +} + +void GrAAConvexTessellator::popLastPt() { + this->validate(); + + fPts.pop(); + fCoverages.pop(); + fMovable.pop(); + fCurveState.pop(); + + this->validate(); +} + +void GrAAConvexTessellator::popFirstPtShuffle() { + this->validate(); + + fPts.removeShuffle(0); + fCoverages.removeShuffle(0); + fMovable.removeShuffle(0); + fCurveState.removeShuffle(0); + + this->validate(); +} + +void GrAAConvexTessellator::updatePt(int index, + const SkPoint& pt, + SkScalar depth, + SkScalar coverage) { + this->validate(); + SkASSERT(fMovable[index]); + + fPts[index] = pt; + fCoverages[index] = coverage; +} + +void GrAAConvexTessellator::addTri(int i0, int i1, int i2) { + if (i0 == i1 || i1 == i2 || i2 == i0) { + return; + } + + *fIndices.push() = i0; + *fIndices.push() = i1; + *fIndices.push() = i2; +} + +void GrAAConvexTessellator::rewind() { + fPts.rewind(); + fCoverages.rewind(); + fMovable.rewind(); + fIndices.rewind(); + fNorms.rewind(); + fCurveState.rewind(); + fInitialRing.rewind(); + fCandidateVerts.rewind(); +#if GR_AA_CONVEX_TESSELLATOR_VIZ + fRings.rewind(); // TODO: leak in this case! +#else + fRings[0].rewind(); + fRings[1].rewind(); +#endif +} + +void GrAAConvexTessellator::computeNormals() { + auto normalToVector = [this](SkVector v) { + SkVector n = SkPointPriv::MakeOrthog(v, fSide); + SkAssertResult(n.normalize()); + SkASSERT(SkScalarNearlyEqual(1.0f, n.length())); + return n; + }; + + // Check the cross product of the final trio + fNorms.append(fPts.count()); + fNorms[0] = fPts[1] - fPts[0]; + fNorms.top() = fPts[0] - fPts.top(); + SkScalar cross = SkPoint::CrossProduct(fNorms[0], fNorms.top()); + fSide = (cross > 0.0f) ? SkPointPriv::kRight_Side : SkPointPriv::kLeft_Side; + fNorms[0] = normalToVector(fNorms[0]); + for (int cur = 1; cur < fNorms.count() - 1; ++cur) { + fNorms[cur] = normalToVector(fPts[cur + 1] - fPts[cur]); + } + fNorms.top() = normalToVector(fNorms.top()); +} + +void GrAAConvexTessellator::computeBisectors() { + fBisectors.setCount(fNorms.count()); + + int prev = fBisectors.count() - 1; + for (int cur = 0; cur < fBisectors.count(); prev = cur, ++cur) { + fBisectors[cur] = fNorms[cur] + fNorms[prev]; + if (!fBisectors[cur].normalize()) { + fBisectors[cur] = SkPointPriv::MakeOrthog(fNorms[cur], (SkPointPriv::Side)-fSide) + + SkPointPriv::MakeOrthog(fNorms[prev], fSide); + SkAssertResult(fBisectors[cur].normalize()); + } else { + fBisectors[cur].negate(); // make the bisector face in + } + if (fCurveState[prev] == kIndeterminate_CurveState) { + if (fCurveState[cur] == kSharp_CurveState) { + fCurveState[prev] = kSharp_CurveState; + } else { + if (SkScalarAbs(fNorms[cur].dot(fNorms[prev])) > kCurveConnectionThreshold) { + fCurveState[prev] = kCurve_CurveState; + fCurveState[cur] = kCurve_CurveState; + } else { + fCurveState[prev] = kSharp_CurveState; + fCurveState[cur] = kSharp_CurveState; + } + } + } + + SkASSERT(SkScalarNearlyEqual(1.0f, fBisectors[cur].length())); + } +} + +// Create as many rings as we need to (up to a predefined limit) to reach the specified target +// depth. If we are in fill mode, the final ring will automatically be fanned. +bool GrAAConvexTessellator::createInsetRings(Ring& previousRing, SkScalar initialDepth, + SkScalar initialCoverage, SkScalar targetDepth, + SkScalar targetCoverage, Ring** finalRing) { + static const int kMaxNumRings = 8; + + if (previousRing.numPts() < 3) { + return false; + } + Ring* currentRing = &previousRing; + int i; + for (i = 0; i < kMaxNumRings; ++i) { + Ring* nextRing = this->getNextRing(currentRing); + SkASSERT(nextRing != currentRing); + + bool done = this->createInsetRing(*currentRing, nextRing, initialDepth, initialCoverage, + targetDepth, targetCoverage, i == 0); + currentRing = nextRing; + if (done) { + break; + } + currentRing->init(*this); + } + + if (kMaxNumRings == i) { + // Bail if we've exceeded the amount of time we want to throw at this. + this->terminate(*currentRing); + return false; + } + bool done = currentRing->numPts() >= 3; + if (done) { + currentRing->init(*this); + } + *finalRing = currentRing; + return done; +} + +// The general idea here is to, conceptually, start with the original polygon and slide +// the vertices along the bisectors until the first intersection. At that +// point two of the edges collapse and the process repeats on the new polygon. +// The polygon state is captured in the Ring class while the GrAAConvexTessellator +// controls the iteration. The CandidateVerts holds the formative points for the +// next ring. +bool GrAAConvexTessellator::tessellate(const SkMatrix& m, const SkPath& path) { + if (!this->extractFromPath(m, path)) { + return false; + } + + SkScalar coverage = 1.0f; + SkScalar scaleFactor = 0.0f; + + if (SkStrokeRec::kStrokeAndFill_Style == fStyle) { + SkASSERT(m.isSimilarity()); + scaleFactor = m.getMaxScale(); // x and y scale are the same + SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth; + Ring outerStrokeAndAARing; + this->createOuterRing(fInitialRing, + effectiveStrokeWidth / 2 + kAntialiasingRadius, 0.0, + &outerStrokeAndAARing); + + // discard all the triangles added between the originating ring and the new outer ring + fIndices.rewind(); + + outerStrokeAndAARing.init(*this); + + outerStrokeAndAARing.makeOriginalRing(); + + // Add the outer stroke ring's normals to the originating ring's normals + // so it can also act as an originating ring + fNorms.setCount(fNorms.count() + outerStrokeAndAARing.numPts()); + for (int i = 0; i < outerStrokeAndAARing.numPts(); ++i) { + SkASSERT(outerStrokeAndAARing.index(i) < fNorms.count()); + fNorms[outerStrokeAndAARing.index(i)] = outerStrokeAndAARing.norm(i); + } + + // the bisectors are only needed for the computation of the outer ring + fBisectors.rewind(); + + Ring* insetAARing; + this->createInsetRings(outerStrokeAndAARing, + 0.0f, 0.0f, 2*kAntialiasingRadius, 1.0f, + &insetAARing); + + SkDEBUGCODE(this->validate();) + return true; + } + + if (SkStrokeRec::kStroke_Style == fStyle) { + SkASSERT(fStrokeWidth >= 0.0f); + SkASSERT(m.isSimilarity()); + scaleFactor = m.getMaxScale(); // x and y scale are the same + SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth; + Ring outerStrokeRing; + this->createOuterRing(fInitialRing, effectiveStrokeWidth / 2 - kAntialiasingRadius, + coverage, &outerStrokeRing); + outerStrokeRing.init(*this); + Ring outerAARing; + this->createOuterRing(outerStrokeRing, kAntialiasingRadius * 2, 0.0f, &outerAARing); + } else { + Ring outerAARing; + this->createOuterRing(fInitialRing, kAntialiasingRadius, 0.0f, &outerAARing); + } + + // the bisectors are only needed for the computation of the outer ring + fBisectors.rewind(); + if (SkStrokeRec::kStroke_Style == fStyle && fInitialRing.numPts() > 2) { + SkASSERT(fStrokeWidth >= 0.0f); + SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth; + Ring* insetStrokeRing; + SkScalar strokeDepth = effectiveStrokeWidth / 2 - kAntialiasingRadius; + if (this->createInsetRings(fInitialRing, 0.0f, coverage, strokeDepth, coverage, + &insetStrokeRing)) { + Ring* insetAARing; + this->createInsetRings(*insetStrokeRing, strokeDepth, coverage, strokeDepth + + kAntialiasingRadius * 2, 0.0f, &insetAARing); + } + } else { + Ring* insetAARing; + this->createInsetRings(fInitialRing, 0.0f, 0.5f, kAntialiasingRadius, 1.0f, &insetAARing); + } + + SkDEBUGCODE(this->validate();) + return true; +} + +SkScalar GrAAConvexTessellator::computeDepthFromEdge(int edgeIdx, const SkPoint& p) const { + SkASSERT(edgeIdx < fNorms.count()); + + SkPoint v = p - fPts[edgeIdx]; + SkScalar depth = -fNorms[edgeIdx].dot(v); + return depth; +} + +// Find a point that is 'desiredDepth' away from the 'edgeIdx'-th edge and lies +// along the 'bisector' from the 'startIdx'-th point. +bool GrAAConvexTessellator::computePtAlongBisector(int startIdx, + const SkVector& bisector, + int edgeIdx, + SkScalar desiredDepth, + SkPoint* result) const { + const SkPoint& norm = fNorms[edgeIdx]; + + // First find the point where the edge and the bisector intersect + SkPoint newP; + + SkScalar t = perp_intersect(fPts[startIdx], bisector, fPts[edgeIdx], norm); + if (SkScalarNearlyEqual(t, 0.0f)) { + // the start point was one of the original ring points + SkASSERT(startIdx < fPts.count()); + newP = fPts[startIdx]; + } else if (t < 0.0f) { + newP = bisector; + newP.scale(t); + newP += fPts[startIdx]; + } else { + return false; + } + + // Then offset along the bisector from that point the correct distance + SkScalar dot = bisector.dot(norm); + t = -desiredDepth / dot; + *result = bisector; + result->scale(t); + *result += newP; + + return true; +} + +bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& path) { + SkASSERT(SkPath::kConvex_Convexity == path.getConvexity()); + + SkRect bounds = path.getBounds(); + m.mapRect(&bounds); + if (!bounds.isFinite()) { + // We could do something smarter here like clip the path based on the bounds of the dst. + // We'd have to be careful about strokes to ensure we don't draw something wrong. + return false; + } + + // Outer ring: 3*numPts + // Middle ring: numPts + // Presumptive inner ring: numPts + this->reservePts(5*path.countPoints()); + // Outer ring: 12*numPts + // Middle ring: 0 + // Presumptive inner ring: 6*numPts + 6 + fIndices.setReserve(18*path.countPoints() + 6); + + // TODO: is there a faster way to extract the points from the path? Perhaps + // get all the points via a new entry point, transform them all in bulk + // and then walk them to find duplicates? + SkPathEdgeIter iter(path); + while (auto e = iter.next()) { + switch (e.fEdge) { + case SkPathEdgeIter::Edge::kLine: + if (!SkPathPriv::AllPointsEq(e.fPts, 2)) { + this->lineTo(m, e.fPts[1], kSharp_CurveState); + } + break; + case SkPathEdgeIter::Edge::kQuad: + if (!SkPathPriv::AllPointsEq(e.fPts, 3)) { + this->quadTo(m, e.fPts); + } + break; + case SkPathEdgeIter::Edge::kCubic: + if (!SkPathPriv::AllPointsEq(e.fPts, 4)) { + this->cubicTo(m, e.fPts); + } + break; + case SkPathEdgeIter::Edge::kConic: + if (!SkPathPriv::AllPointsEq(e.fPts, 3)) { + this->conicTo(m, e.fPts, iter.conicWeight()); + } + break; + } + } + + if (this->numPts() < 2) { + return false; + } + + // check if last point is a duplicate of the first point. If so, remove it. + if (duplicate_pt(fPts[this->numPts()-1], fPts[0])) { + this->popLastPt(); + } + + // Remove any lingering colinear points where the path wraps around + bool noRemovalsToDo = false; + while (!noRemovalsToDo && this->numPts() >= 3) { + if (points_are_colinear_and_b_is_middle(fPts[fPts.count() - 2], fPts.top(), fPts[0])) { + this->popLastPt(); + } else if (points_are_colinear_and_b_is_middle(fPts.top(), fPts[0], fPts[1])) { + this->popFirstPtShuffle(); + } else { + noRemovalsToDo = true; + } + } + + // Compute the normals and bisectors. + SkASSERT(fNorms.empty()); + if (this->numPts() >= 3) { + this->computeNormals(); + this->computeBisectors(); + } else if (this->numPts() == 2) { + // We've got two points, so we're degenerate. + if (fStyle == SkStrokeRec::kFill_Style) { + // it's a fill, so we don't need to worry about degenerate paths + return false; + } + // For stroking, we still need to process the degenerate path, so fix it up + fSide = SkPointPriv::kLeft_Side; + + fNorms.append(2); + fNorms[0] = SkPointPriv::MakeOrthog(fPts[1] - fPts[0], fSide); + fNorms[0].normalize(); + fNorms[1] = -fNorms[0]; + SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[0].length())); + // we won't actually use the bisectors, so just push zeroes + fBisectors.push_back(SkPoint::Make(0.0, 0.0)); + fBisectors.push_back(SkPoint::Make(0.0, 0.0)); + } else { + return false; + } + + fCandidateVerts.setReserve(this->numPts()); + fInitialRing.setReserve(this->numPts()); + for (int i = 0; i < this->numPts(); ++i) { + fInitialRing.addIdx(i, i); + } + fInitialRing.init(fNorms, fBisectors); + + this->validate(); + return true; +} + +GrAAConvexTessellator::Ring* GrAAConvexTessellator::getNextRing(Ring* lastRing) { +#if GR_AA_CONVEX_TESSELLATOR_VIZ + Ring* ring = *fRings.push() = new Ring; + ring->setReserve(fInitialRing.numPts()); + ring->rewind(); + return ring; +#else + // Flip flop back and forth between fRings[0] & fRings[1] + int nextRing = (lastRing == &fRings[0]) ? 1 : 0; + fRings[nextRing].setReserve(fInitialRing.numPts()); + fRings[nextRing].rewind(); + return &fRings[nextRing]; +#endif +} + +void GrAAConvexTessellator::fanRing(const Ring& ring) { + // fan out from point 0 + int startIdx = ring.index(0); + for (int cur = ring.numPts() - 2; cur >= 0; --cur) { + this->addTri(startIdx, ring.index(cur), ring.index(cur + 1)); + } +} + +void GrAAConvexTessellator::createOuterRing(const Ring& previousRing, SkScalar outset, + SkScalar coverage, Ring* nextRing) { + const int numPts = previousRing.numPts(); + if (numPts == 0) { + return; + } + + int prev = numPts - 1; + int lastPerpIdx = -1, firstPerpIdx = -1; + + const SkScalar outsetSq = outset * outset; + SkScalar miterLimitSq = outset * fMiterLimit; + miterLimitSq = miterLimitSq * miterLimitSq; + for (int cur = 0; cur < numPts; ++cur) { + int originalIdx = previousRing.index(cur); + // For each vertex of the original polygon we add at least two points to the + // outset polygon - one extending perpendicular to each impinging edge. Connecting these + // two points yields a bevel join. We need one additional point for a mitered join, and + // a round join requires one or more points depending upon curvature. + + // The perpendicular point for the last edge + SkPoint normal1 = previousRing.norm(prev); + SkPoint perp1 = normal1; + perp1.scale(outset); + perp1 += this->point(originalIdx); + + // The perpendicular point for the next edge. + SkPoint normal2 = previousRing.norm(cur); + SkPoint perp2 = normal2; + perp2.scale(outset); + perp2 += fPts[originalIdx]; + + CurveState curve = fCurveState[originalIdx]; + + // We know it isn't a duplicate of the prior point (since it and this + // one are just perpendicular offsets from the non-merged polygon points) + int perp1Idx = this->addPt(perp1, -outset, coverage, false, curve); + nextRing->addIdx(perp1Idx, originalIdx); + + int perp2Idx; + // For very shallow angles all the corner points could fuse. + if (duplicate_pt(perp2, this->point(perp1Idx))) { + perp2Idx = perp1Idx; + } else { + perp2Idx = this->addPt(perp2, -outset, coverage, false, curve); + } + + if (perp2Idx != perp1Idx) { + if (curve == kCurve_CurveState) { + // bevel or round depending upon curvature + SkScalar dotProd = normal1.dot(normal2); + if (dotProd < kRoundCapThreshold) { + // Currently we "round" by creating a single extra point, which produces + // good results for common cases. For thick strokes with high curvature, we will + // need to add more points; for the time being we simply fall back to software + // rendering for thick strokes. + SkPoint miter = previousRing.bisector(cur); + miter.setLength(-outset); + miter += fPts[originalIdx]; + + // For very shallow angles all the corner points could fuse + if (!duplicate_pt(miter, this->point(perp1Idx))) { + int miterIdx; + miterIdx = this->addPt(miter, -outset, coverage, false, kSharp_CurveState); + nextRing->addIdx(miterIdx, originalIdx); + // The two triangles for the corner + this->addTri(originalIdx, perp1Idx, miterIdx); + this->addTri(originalIdx, miterIdx, perp2Idx); + } + } else { + this->addTri(originalIdx, perp1Idx, perp2Idx); + } + } else { + switch (fJoin) { + case SkPaint::Join::kMiter_Join: { + // The bisector outset point + SkPoint miter = previousRing.bisector(cur); + SkScalar dotProd = normal1.dot(normal2); + // The max is because this could go slightly negative if precision causes + // us to become slightly concave. + SkScalar sinHalfAngleSq = SkTMax(SkScalarHalf(SK_Scalar1 + dotProd), 0.f); + SkScalar lengthSq = sk_ieee_float_divide(outsetSq, sinHalfAngleSq); + if (lengthSq > miterLimitSq) { + // just bevel it + this->addTri(originalIdx, perp1Idx, perp2Idx); + break; + } + miter.setLength(-SkScalarSqrt(lengthSq)); + miter += fPts[originalIdx]; + + // For very shallow angles all the corner points could fuse + if (!duplicate_pt(miter, this->point(perp1Idx))) { + int miterIdx; + miterIdx = this->addPt(miter, -outset, coverage, false, + kSharp_CurveState); + nextRing->addIdx(miterIdx, originalIdx); + // The two triangles for the corner + this->addTri(originalIdx, perp1Idx, miterIdx); + this->addTri(originalIdx, miterIdx, perp2Idx); + } else { + // ignore the miter point as it's so close to perp1/perp2 and simply + // bevel. + this->addTri(originalIdx, perp1Idx, perp2Idx); + } + break; + } + case SkPaint::Join::kBevel_Join: + this->addTri(originalIdx, perp1Idx, perp2Idx); + break; + default: + // kRound_Join is unsupported for now. GrAALinearizingConvexPathRenderer is + // only willing to draw mitered or beveled, so we should never get here. + SkASSERT(false); + } + } + + nextRing->addIdx(perp2Idx, originalIdx); + } + + if (0 == cur) { + // Store the index of the first perpendicular point to finish up + firstPerpIdx = perp1Idx; + SkASSERT(-1 == lastPerpIdx); + } else { + // The triangles for the previous edge + int prevIdx = previousRing.index(prev); + this->addTri(prevIdx, perp1Idx, originalIdx); + this->addTri(prevIdx, lastPerpIdx, perp1Idx); + } + + // Track the last perpendicular outset point so we can construct the + // trailing edge triangles. + lastPerpIdx = perp2Idx; + prev = cur; + } + + // pick up the final edge rect + int lastIdx = previousRing.index(numPts - 1); + this->addTri(lastIdx, firstPerpIdx, previousRing.index(0)); + this->addTri(lastIdx, lastPerpIdx, firstPerpIdx); + + this->validate(); +} + +// Something went wrong in the creation of the next ring. If we're filling the shape, just go ahead +// and fan it. +void GrAAConvexTessellator::terminate(const Ring& ring) { + if (fStyle != SkStrokeRec::kStroke_Style && ring.numPts() > 0) { + this->fanRing(ring); + } +} + +static SkScalar compute_coverage(SkScalar depth, SkScalar initialDepth, SkScalar initialCoverage, + SkScalar targetDepth, SkScalar targetCoverage) { + if (SkScalarNearlyEqual(initialDepth, targetDepth)) { + return targetCoverage; + } + SkScalar result = (depth - initialDepth) / (targetDepth - initialDepth) * + (targetCoverage - initialCoverage) + initialCoverage; + return SkScalarClampMax(result, 1.0f); +} + +// return true when processing is complete +bool GrAAConvexTessellator::createInsetRing(const Ring& lastRing, Ring* nextRing, + SkScalar initialDepth, SkScalar initialCoverage, + SkScalar targetDepth, SkScalar targetCoverage, + bool forceNew) { + bool done = false; + + fCandidateVerts.rewind(); + + // Loop through all the points in the ring and find the intersection with the smallest depth + SkScalar minDist = SK_ScalarMax, minT = 0.0f; + int minEdgeIdx = -1; + + for (int cur = 0; cur < lastRing.numPts(); ++cur) { + int next = (cur + 1) % lastRing.numPts(); + + SkScalar t; + bool result = intersect(this->point(lastRing.index(cur)), lastRing.bisector(cur), + this->point(lastRing.index(next)), lastRing.bisector(next), + &t); + // The bisectors may be parallel (!result) or the previous ring may have become slightly + // concave due to accumulated error (t <= 0). + if (!result || t <= 0) { + continue; + } + SkScalar dist = -t * lastRing.norm(cur).dot(lastRing.bisector(cur)); + + if (minDist > dist) { + minDist = dist; + minT = t; + minEdgeIdx = cur; + } + } + + if (minEdgeIdx == -1) { + return false; + } + SkPoint newPt = lastRing.bisector(minEdgeIdx); + newPt.scale(minT); + newPt += this->point(lastRing.index(minEdgeIdx)); + + SkScalar depth = this->computeDepthFromEdge(lastRing.origEdgeID(minEdgeIdx), newPt); + if (depth >= targetDepth) { + // None of the bisectors intersect before reaching the desired depth. + // Just step them all to the desired depth + depth = targetDepth; + done = true; + } + + // 'dst' stores where each point in the last ring maps to/transforms into + // in the next ring. + SkTDArray dst; + dst.setCount(lastRing.numPts()); + + // Create the first point (who compares with no one) + if (!this->computePtAlongBisector(lastRing.index(0), + lastRing.bisector(0), + lastRing.origEdgeID(0), + depth, &newPt)) { + this->terminate(lastRing); + return true; + } + dst[0] = fCandidateVerts.addNewPt(newPt, + lastRing.index(0), lastRing.origEdgeID(0), + !this->movable(lastRing.index(0))); + + // Handle the middle points (who only compare with the prior point) + for (int cur = 1; cur < lastRing.numPts()-1; ++cur) { + if (!this->computePtAlongBisector(lastRing.index(cur), + lastRing.bisector(cur), + lastRing.origEdgeID(cur), + depth, &newPt)) { + this->terminate(lastRing); + return true; + } + if (!duplicate_pt(newPt, fCandidateVerts.lastPoint())) { + dst[cur] = fCandidateVerts.addNewPt(newPt, + lastRing.index(cur), lastRing.origEdgeID(cur), + !this->movable(lastRing.index(cur))); + } else { + dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur)); + } + } + + // Check on the last point (handling the wrap around) + int cur = lastRing.numPts()-1; + if (!this->computePtAlongBisector(lastRing.index(cur), + lastRing.bisector(cur), + lastRing.origEdgeID(cur), + depth, &newPt)) { + this->terminate(lastRing); + return true; + } + bool dupPrev = duplicate_pt(newPt, fCandidateVerts.lastPoint()); + bool dupNext = duplicate_pt(newPt, fCandidateVerts.firstPoint()); + + if (!dupPrev && !dupNext) { + dst[cur] = fCandidateVerts.addNewPt(newPt, + lastRing.index(cur), lastRing.origEdgeID(cur), + !this->movable(lastRing.index(cur))); + } else if (dupPrev && !dupNext) { + dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur)); + } else if (!dupPrev && dupNext) { + dst[cur] = fCandidateVerts.fuseWithNext(); + } else { + bool dupPrevVsNext = duplicate_pt(fCandidateVerts.firstPoint(), fCandidateVerts.lastPoint()); + + if (!dupPrevVsNext) { + dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur)); + } else { + const int fused = fCandidateVerts.fuseWithBoth(); + dst[cur] = fused; + const int targetIdx = dst[cur - 1]; + for (int i = cur - 1; i >= 0 && dst[i] == targetIdx; i--) { + dst[i] = fused; + } + } + } + + // Fold the new ring's points into the global pool + for (int i = 0; i < fCandidateVerts.numPts(); ++i) { + int newIdx; + if (fCandidateVerts.needsToBeNew(i) || forceNew) { + // if the originating index is still valid then this point wasn't + // fused (and is thus movable) + SkScalar coverage = compute_coverage(depth, initialDepth, initialCoverage, + targetDepth, targetCoverage); + newIdx = this->addPt(fCandidateVerts.point(i), depth, coverage, + fCandidateVerts.originatingIdx(i) != -1, kSharp_CurveState); + } else { + SkASSERT(fCandidateVerts.originatingIdx(i) != -1); + this->updatePt(fCandidateVerts.originatingIdx(i), fCandidateVerts.point(i), depth, + targetCoverage); + newIdx = fCandidateVerts.originatingIdx(i); + } + + nextRing->addIdx(newIdx, fCandidateVerts.origEdge(i)); + } + + // 'dst' currently has indices into the ring. Remap these to be indices + // into the global pool since the triangulation operates in that space. + for (int i = 0; i < dst.count(); ++i) { + dst[i] = nextRing->index(dst[i]); + } + + for (int i = 0; i < lastRing.numPts(); ++i) { + int next = (i + 1) % lastRing.numPts(); + + this->addTri(lastRing.index(i), lastRing.index(next), dst[next]); + this->addTri(lastRing.index(i), dst[next], dst[i]); + } + + if (done && fStyle != SkStrokeRec::kStroke_Style) { + // fill or stroke-and-fill + this->fanRing(*nextRing); + } + + if (nextRing->numPts() < 3) { + done = true; + } + return done; +} + +void GrAAConvexTessellator::validate() const { + SkASSERT(fPts.count() == fMovable.count()); + SkASSERT(fPts.count() == fCoverages.count()); + SkASSERT(fPts.count() == fCurveState.count()); + SkASSERT(0 == (fIndices.count() % 3)); + SkASSERT(!fBisectors.count() || fBisectors.count() == fNorms.count()); +} + +////////////////////////////////////////////////////////////////////////////// +void GrAAConvexTessellator::Ring::init(const GrAAConvexTessellator& tess) { + this->computeNormals(tess); + this->computeBisectors(tess); +} + +void GrAAConvexTessellator::Ring::init(const SkTDArray& norms, + const SkTDArray& bisectors) { + for (int i = 0; i < fPts.count(); ++i) { + fPts[i].fNorm = norms[i]; + fPts[i].fBisector = bisectors[i]; + } +} + +// Compute the outward facing normal at each vertex. +void GrAAConvexTessellator::Ring::computeNormals(const GrAAConvexTessellator& tess) { + for (int cur = 0; cur < fPts.count(); ++cur) { + int next = (cur + 1) % fPts.count(); + + fPts[cur].fNorm = tess.point(fPts[next].fIndex) - tess.point(fPts[cur].fIndex); + SkPoint::Normalize(&fPts[cur].fNorm); + fPts[cur].fNorm = SkPointPriv::MakeOrthog(fPts[cur].fNorm, tess.side()); + } +} + +void GrAAConvexTessellator::Ring::computeBisectors(const GrAAConvexTessellator& tess) { + int prev = fPts.count() - 1; + for (int cur = 0; cur < fPts.count(); prev = cur, ++cur) { + fPts[cur].fBisector = fPts[cur].fNorm + fPts[prev].fNorm; + if (!fPts[cur].fBisector.normalize()) { + fPts[cur].fBisector = + SkPointPriv::MakeOrthog(fPts[cur].fNorm, (SkPointPriv::Side)-tess.side()) + + SkPointPriv::MakeOrthog(fPts[prev].fNorm, tess.side()); + SkAssertResult(fPts[cur].fBisector.normalize()); + } else { + fPts[cur].fBisector.negate(); // make the bisector face in + } + } +} + +////////////////////////////////////////////////////////////////////////////// +#ifdef SK_DEBUG +// Is this ring convex? +bool GrAAConvexTessellator::Ring::isConvex(const GrAAConvexTessellator& tess) const { + if (fPts.count() < 3) { + return true; + } + + SkPoint prev = tess.point(fPts[0].fIndex) - tess.point(fPts.top().fIndex); + SkPoint cur = tess.point(fPts[1].fIndex) - tess.point(fPts[0].fIndex); + SkScalar minDot = prev.fX * cur.fY - prev.fY * cur.fX; + SkScalar maxDot = minDot; + + prev = cur; + for (int i = 1; i < fPts.count(); ++i) { + int next = (i + 1) % fPts.count(); + + cur = tess.point(fPts[next].fIndex) - tess.point(fPts[i].fIndex); + SkScalar dot = prev.fX * cur.fY - prev.fY * cur.fX; + + minDot = SkMinScalar(minDot, dot); + maxDot = SkMaxScalar(maxDot, dot); + + prev = cur; + } + + if (SkScalarNearlyEqual(maxDot, 0.0f, 0.005f)) { + maxDot = 0; + } + if (SkScalarNearlyEqual(minDot, 0.0f, 0.005f)) { + minDot = 0; + } + return (maxDot >= 0.0f) == (minDot >= 0.0f); +} + +#endif + +void GrAAConvexTessellator::lineTo(const SkPoint& p, CurveState curve) { + if (this->numPts() > 0 && duplicate_pt(p, this->lastPoint())) { + return; + } + + if (this->numPts() >= 2 && + points_are_colinear_and_b_is_middle(fPts[fPts.count() - 2], fPts.top(), p)) { + // The old last point is on the line from the second to last to the new point + this->popLastPt(); + // double-check that the new last point is not a duplicate of the new point. In an ideal + // world this wouldn't be necessary (since it's only possible for non-convex paths), but + // floating point precision issues mean it can actually happen on paths that were + // determined to be convex. + if (duplicate_pt(p, this->lastPoint())) { + return; + } + } + SkScalar initialRingCoverage = (SkStrokeRec::kFill_Style == fStyle) ? 0.5f : 1.0f; + this->addPt(p, 0.0f, initialRingCoverage, false, curve); +} + +void GrAAConvexTessellator::lineTo(const SkMatrix& m, const SkPoint& p, CurveState curve) { + this->lineTo(m.mapXY(p.fX, p.fY), curve); +} + +void GrAAConvexTessellator::quadTo(const SkPoint pts[3]) { + int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance); + fPointBuffer.setCount(maxCount); + SkPoint* target = fPointBuffer.begin(); + int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2], + kQuadTolerance, &target, maxCount); + fPointBuffer.setCount(count); + for (int i = 0; i < count - 1; i++) { + this->lineTo(fPointBuffer[i], kCurve_CurveState); + } + this->lineTo(fPointBuffer[count - 1], kIndeterminate_CurveState); +} + +void GrAAConvexTessellator::quadTo(const SkMatrix& m, const SkPoint srcPts[3]) { + SkPoint pts[3]; + m.mapPoints(pts, srcPts, 3); + this->quadTo(pts); +} + +void GrAAConvexTessellator::cubicTo(const SkMatrix& m, const SkPoint srcPts[4]) { + SkPoint pts[4]; + m.mapPoints(pts, srcPts, 4); + int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance); + fPointBuffer.setCount(maxCount); + SkPoint* target = fPointBuffer.begin(); + int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3], + kCubicTolerance, &target, maxCount); + fPointBuffer.setCount(count); + for (int i = 0; i < count - 1; i++) { + this->lineTo(fPointBuffer[i], kCurve_CurveState); + } + this->lineTo(fPointBuffer[count - 1], kIndeterminate_CurveState); +} + +// include down here to avoid compilation errors caused by "-" overload in SkGeometry.h +#include "src/core/SkGeometry.h" + +void GrAAConvexTessellator::conicTo(const SkMatrix& m, const SkPoint srcPts[3], SkScalar w) { + SkPoint pts[3]; + m.mapPoints(pts, srcPts, 3); + SkAutoConicToQuads quadder; + const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance); + SkPoint lastPoint = *(quads++); + int count = quadder.countQuads(); + for (int i = 0; i < count; ++i) { + SkPoint quadPts[3]; + quadPts[0] = lastPoint; + quadPts[1] = quads[0]; + quadPts[2] = i == count - 1 ? pts[2] : quads[1]; + this->quadTo(quadPts); + lastPoint = quadPts[2]; + quads += 2; + } +} + +////////////////////////////////////////////////////////////////////////////// +#if GR_AA_CONVEX_TESSELLATOR_VIZ +static const SkScalar kPointRadius = 0.02f; +static const SkScalar kArrowStrokeWidth = 0.0f; +static const SkScalar kArrowLength = 0.2f; +static const SkScalar kEdgeTextSize = 0.1f; +static const SkScalar kPointTextSize = 0.02f; + +static void draw_point(SkCanvas* canvas, const SkPoint& p, SkScalar paramValue, bool stroke) { + SkPaint paint; + SkASSERT(paramValue <= 1.0f); + int gs = int(255*paramValue); + paint.setARGB(255, gs, gs, gs); + + canvas->drawCircle(p.fX, p.fY, kPointRadius, paint); + + if (stroke) { + SkPaint stroke; + stroke.setColor(SK_ColorYELLOW); + stroke.setStyle(SkPaint::kStroke_Style); + stroke.setStrokeWidth(kPointRadius/3.0f); + canvas->drawCircle(p.fX, p.fY, kPointRadius, stroke); + } +} + +static void draw_line(SkCanvas* canvas, const SkPoint& p0, const SkPoint& p1, SkColor color) { + SkPaint p; + p.setColor(color); + + canvas->drawLine(p0.fX, p0.fY, p1.fX, p1.fY, p); +} + +static void draw_arrow(SkCanvas*canvas, const SkPoint& p, const SkPoint &n, + SkScalar len, SkColor color) { + SkPaint paint; + paint.setColor(color); + paint.setStrokeWidth(kArrowStrokeWidth); + paint.setStyle(SkPaint::kStroke_Style); + + canvas->drawLine(p.fX, p.fY, + p.fX + len * n.fX, p.fY + len * n.fY, + paint); +} + +void GrAAConvexTessellator::Ring::draw(SkCanvas* canvas, const GrAAConvexTessellator& tess) const { + SkPaint paint; + paint.setTextSize(kEdgeTextSize); + + for (int cur = 0; cur < fPts.count(); ++cur) { + int next = (cur + 1) % fPts.count(); + + draw_line(canvas, + tess.point(fPts[cur].fIndex), + tess.point(fPts[next].fIndex), + SK_ColorGREEN); + + SkPoint mid = tess.point(fPts[cur].fIndex) + tess.point(fPts[next].fIndex); + mid.scale(0.5f); + + if (fPts.count()) { + draw_arrow(canvas, mid, fPts[cur].fNorm, kArrowLength, SK_ColorRED); + mid.fX += (kArrowLength/2) * fPts[cur].fNorm.fX; + mid.fY += (kArrowLength/2) * fPts[cur].fNorm.fY; + } + + SkString num; + num.printf("%d", this->origEdgeID(cur)); + canvas->drawString(num, mid.fX, mid.fY, paint); + + if (fPts.count()) { + draw_arrow(canvas, tess.point(fPts[cur].fIndex), fPts[cur].fBisector, + kArrowLength, SK_ColorBLUE); + } + } +} + +void GrAAConvexTessellator::draw(SkCanvas* canvas) const { + for (int i = 0; i < fIndices.count(); i += 3) { + SkASSERT(fIndices[i] < this->numPts()) ; + SkASSERT(fIndices[i+1] < this->numPts()) ; + SkASSERT(fIndices[i+2] < this->numPts()) ; + + draw_line(canvas, + this->point(this->fIndices[i]), this->point(this->fIndices[i+1]), + SK_ColorBLACK); + draw_line(canvas, + this->point(this->fIndices[i+1]), this->point(this->fIndices[i+2]), + SK_ColorBLACK); + draw_line(canvas, + this->point(this->fIndices[i+2]), this->point(this->fIndices[i]), + SK_ColorBLACK); + } + + fInitialRing.draw(canvas, *this); + for (int i = 0; i < fRings.count(); ++i) { + fRings[i]->draw(canvas, *this); + } + + for (int i = 0; i < this->numPts(); ++i) { + draw_point(canvas, + this->point(i), 0.5f + (this->depth(i)/(2 * kAntialiasingRadius)), + !this->movable(i)); + + SkPaint paint; + paint.setTextSize(kPointTextSize); + if (this->depth(i) <= -kAntialiasingRadius) { + paint.setColor(SK_ColorWHITE); + } + + SkString num; + num.printf("%d", i); + canvas->drawString(num, + this->point(i).fX, this->point(i).fY+(kPointRadius/2.0f), + paint); + } +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.h b/gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.h new file mode 100644 index 0000000000..b4edd0c8a4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAAConvexTessellator.h @@ -0,0 +1,292 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAAConvexTessellator_DEFINED +#define GrAAConvexTessellator_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkPaint.h" +#include "include/core/SkScalar.h" +#include "include/core/SkStrokeRec.h" +#include "include/private/SkTDArray.h" +#include "src/core/SkPointPriv.h" + +class SkCanvas; +class SkMatrix; +class SkPath; + +//#define GR_AA_CONVEX_TESSELLATOR_VIZ 1 + +// device space distance which we inset / outset points in order to create the soft antialiased edge +static const SkScalar kAntialiasingRadius = 0.5f; + +class GrAAConvexTessellator; + +// The AAConvexTessellator holds the global pool of points and the triangulation +// that connects them. It also drives the tessellation process. +// The outward facing normals of the original polygon are stored (in 'fNorms') to service +// computeDepthFromEdge requests. +class GrAAConvexTessellator { +public: + GrAAConvexTessellator(SkStrokeRec::Style style = SkStrokeRec::kFill_Style, + SkScalar strokeWidth = -1.0f, + SkPaint::Join join = SkPaint::Join::kBevel_Join, + SkScalar miterLimit = 0.0f) + : fSide(SkPointPriv::kOn_Side) + , fStrokeWidth(strokeWidth) + , fStyle(style) + , fJoin(join) + , fMiterLimit(miterLimit) { + } + + SkPointPriv::Side side() const { return fSide; } + + bool tessellate(const SkMatrix& m, const SkPath& path); + + // The next five should only be called after tessellate to extract the result + int numPts() const { return fPts.count(); } + int numIndices() const { return fIndices.count(); } + + const SkPoint& lastPoint() const { return fPts.top(); } + const SkPoint& point(int index) const { return fPts[index]; } + int index(int index) const { return fIndices[index]; } + SkScalar coverage(int index) const { return fCoverages[index]; } + +#if GR_AA_CONVEX_TESSELLATOR_VIZ + void draw(SkCanvas* canvas) const; +#endif + + // The tessellator can be reused for multiple paths by rewinding in between + void rewind(); + +private: + // CandidateVerts holds the vertices for the next ring while they are + // being generated. Its main function is to de-dup the points. + class CandidateVerts { + public: + void setReserve(int numPts) { fPts.setReserve(numPts); } + void rewind() { fPts.rewind(); } + + int numPts() const { return fPts.count(); } + + const SkPoint& lastPoint() const { return fPts.top().fPt; } + const SkPoint& firstPoint() const { return fPts[0].fPt; } + const SkPoint& point(int index) const { return fPts[index].fPt; } + + int originatingIdx(int index) const { return fPts[index].fOriginatingIdx; } + int origEdge(int index) const { return fPts[index].fOrigEdgeId; } + bool needsToBeNew(int index) const { return fPts[index].fNeedsToBeNew; } + + int addNewPt(const SkPoint& newPt, int originatingIdx, int origEdge, bool needsToBeNew) { + struct PointData* pt = fPts.push(); + pt->fPt = newPt; + pt->fOrigEdgeId = origEdge; + pt->fOriginatingIdx = originatingIdx; + pt->fNeedsToBeNew = needsToBeNew; + return fPts.count() - 1; + } + + int fuseWithPrior(int origEdgeId) { + fPts.top().fOrigEdgeId = origEdgeId; + fPts.top().fOriginatingIdx = -1; + fPts.top().fNeedsToBeNew = true; + return fPts.count() - 1; + } + + int fuseWithNext() { + fPts[0].fOriginatingIdx = -1; + fPts[0].fNeedsToBeNew = true; + return 0; + } + + int fuseWithBoth() { + if (fPts.count() > 1) { + fPts.pop(); + } + + fPts[0].fOriginatingIdx = -1; + fPts[0].fNeedsToBeNew = true; + return 0; + } + + private: + struct PointData { + SkPoint fPt; + int fOriginatingIdx; + int fOrigEdgeId; + bool fNeedsToBeNew; + }; + + SkTDArray fPts; + }; + + // The Ring holds a set of indices into the global pool that together define + // a single polygon inset. + class Ring { + public: + void setReserve(int numPts) { fPts.setReserve(numPts); } + void rewind() { fPts.rewind(); } + + int numPts() const { return fPts.count(); } + + void addIdx(int index, int origEdgeId) { + struct PointData* pt = fPts.push(); + pt->fIndex = index; + pt->fOrigEdgeId = origEdgeId; + } + + // Upgrade this ring so that it can behave like an originating ring + void makeOriginalRing() { + for (int i = 0; i < fPts.count(); ++i) { + fPts[i].fOrigEdgeId = fPts[i].fIndex; + } + } + + // init should be called after all the indices have been added (via addIdx) + void init(const GrAAConvexTessellator& tess); + void init(const SkTDArray& norms, const SkTDArray& bisectors); + + const SkPoint& norm(int index) const { return fPts[index].fNorm; } + const SkPoint& bisector(int index) const { return fPts[index].fBisector; } + int index(int index) const { return fPts[index].fIndex; } + int origEdgeID(int index) const { return fPts[index].fOrigEdgeId; } + void setOrigEdgeId(int index, int id) { fPts[index].fOrigEdgeId = id; } + + #if GR_AA_CONVEX_TESSELLATOR_VIZ + void draw(SkCanvas* canvas, const GrAAConvexTessellator& tess) const; + #endif + + private: + void computeNormals(const GrAAConvexTessellator& result); + void computeBisectors(const GrAAConvexTessellator& tess); + + SkDEBUGCODE(bool isConvex(const GrAAConvexTessellator& tess) const;) + + struct PointData { + SkPoint fNorm; + SkPoint fBisector; + int fIndex; + int fOrigEdgeId; + }; + + SkTDArray fPts; + }; + + // Represents whether a given point is within a curve. A point is inside a curve only if it is + // an interior point within a quad, cubic, or conic, or if it is the endpoint of a quad, cubic, + // or conic with another curve meeting it at (more or less) the same angle. + enum CurveState { + // point is a sharp vertex + kSharp_CurveState, + // endpoint of a curve with the other side's curvature not yet determined + kIndeterminate_CurveState, + // point is in the interior of a curve + kCurve_CurveState + }; + + bool movable(int index) const { return fMovable[index]; } + + // Movable points are those that can be slid along their bisector. + // Basically, a point is immovable if it is part of the original + // polygon or it results from the fusing of two bisectors. + int addPt(const SkPoint& pt, SkScalar depth, SkScalar coverage, bool movable, CurveState curve); + void popLastPt(); + void popFirstPtShuffle(); + + void updatePt(int index, const SkPoint& pt, SkScalar depth, SkScalar coverage); + + void addTri(int i0, int i1, int i2); + + void reservePts(int count) { + fPts.setReserve(count); + fCoverages.setReserve(count); + fMovable.setReserve(count); + } + + SkScalar computeDepthFromEdge(int edgeIdx, const SkPoint& p) const; + + bool computePtAlongBisector(int startIdx, const SkPoint& bisector, + int edgeIdx, SkScalar desiredDepth, + SkPoint* result) const; + + void lineTo(const SkPoint& p, CurveState curve); + + void lineTo(const SkMatrix& m, const SkPoint& p, CurveState curve); + + void quadTo(const SkPoint pts[3]); + + void quadTo(const SkMatrix& m, const SkPoint pts[3]); + + void cubicTo(const SkMatrix& m, const SkPoint pts[4]); + + void conicTo(const SkMatrix& m, const SkPoint pts[3], SkScalar w); + + void terminate(const Ring& lastRing); + + // return false on failure/degenerate path + bool extractFromPath(const SkMatrix& m, const SkPath& path); + void computeBisectors(); + void computeNormals(); + + void fanRing(const Ring& ring); + + Ring* getNextRing(Ring* lastRing); + + void createOuterRing(const Ring& previousRing, SkScalar outset, SkScalar coverage, + Ring* nextRing); + + bool createInsetRings(Ring& previousRing, SkScalar initialDepth, SkScalar initialCoverage, + SkScalar targetDepth, SkScalar targetCoverage, Ring** finalRing); + + bool createInsetRing(const Ring& lastRing, Ring* nextRing, + SkScalar initialDepth, SkScalar initialCoverage, SkScalar targetDepth, + SkScalar targetCoverage, bool forceNew); + + void validate() const; + + // fPts, fCoverages, fMovable & fCurveState should always have the same # of elements + SkTDArray fPts; + SkTDArray fCoverages; + // movable points are those that can be slid further along their bisector + SkTDArray fMovable; + // Tracks whether a given point is interior to a curve. Such points are + // assumed to have shallow curvature. + SkTDArray fCurveState; + + // The outward facing normals for the original polygon + SkTDArray fNorms; + // The inward facing bisector at each point in the original polygon. Only + // needed for exterior ring creation and then handed off to the initial ring. + SkTDArray fBisectors; + + SkPointPriv::Side fSide; // winding of the original polygon + + // The triangulation of the points + SkTDArray fIndices; + + Ring fInitialRing; +#if GR_AA_CONVEX_TESSELLATOR_VIZ + // When visualizing save all the rings + SkTDArray fRings; +#else + Ring fRings[2]; +#endif + CandidateVerts fCandidateVerts; + + // the stroke width is only used for stroke or stroke-and-fill styles + SkScalar fStrokeWidth; + SkStrokeRec::Style fStyle; + + SkPaint::Join fJoin; + + SkScalar fMiterLimit; + + SkTDArray fPointBuffer; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.cpp new file mode 100644 index 0000000000..f78c6722b4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.cpp @@ -0,0 +1,1106 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPoint3.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkStroke.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrBuffer.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/effects/GrBezierEffect.h" +#include "src/gpu/geometry/GrPathUtils.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrAAHairLinePathRenderer.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true> + +// quadratics are rendered as 5-sided polys in order to bound the +// AA stroke around the center-curve. See comments in push_quad_index_buffer and +// bloat_quad. Quadratics and conics share an index buffer + +// lines are rendered as: +// *______________* +// |\ -_______ /| +// | \ \ / | +// | *--------* | +// | / ______/ \ | +// */_-__________\* +// For: 6 vertices and 18 indices (for 6 triangles) + +// Each quadratic is rendered as a five sided polygon. This poly bounds +// the quadratic's bounding triangle but has been expanded so that the +// 1-pixel wide area around the curve is inside the poly. +// If a,b,c are the original control points then the poly a0,b0,c0,c1,a1 +// that is rendered would look like this: +// b0 +// b +// +// a0 c0 +// a c +// a1 c1 +// Each is drawn as three triangles ((a0,a1,b0), (b0,c1,c0), (a1,c1,b0)) +// specified by these 9 indices: +static const uint16_t kQuadIdxBufPattern[] = { + 0, 1, 2, + 2, 4, 3, + 1, 4, 2 +}; + +static const int kIdxsPerQuad = SK_ARRAY_COUNT(kQuadIdxBufPattern); +static const int kQuadNumVertices = 5; +static const int kQuadsNumInIdxBuffer = 256; +GR_DECLARE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey); + +static sk_sp get_quads_index_buffer(GrResourceProvider* resourceProvider) { + GR_DEFINE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey); + return resourceProvider->findOrCreatePatternedIndexBuffer( + kQuadIdxBufPattern, kIdxsPerQuad, kQuadsNumInIdxBuffer, kQuadNumVertices, + gQuadsIndexBufferKey); +} + + +// Each line segment is rendered as two quads and two triangles. +// p0 and p1 have alpha = 1 while all other points have alpha = 0. +// The four external points are offset 1 pixel perpendicular to the +// line and half a pixel parallel to the line. +// +// p4 p5 +// p0 p1 +// p2 p3 +// +// Each is drawn as six triangles specified by these 18 indices: + +static const uint16_t kLineSegIdxBufPattern[] = { + 0, 1, 3, + 0, 3, 2, + 0, 4, 5, + 0, 5, 1, + 0, 2, 4, + 1, 5, 3 +}; + +static const int kIdxsPerLineSeg = SK_ARRAY_COUNT(kLineSegIdxBufPattern); +static const int kLineSegNumVertices = 6; +static const int kLineSegsNumInIdxBuffer = 256; + +GR_DECLARE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey); + +static sk_sp get_lines_index_buffer(GrResourceProvider* resourceProvider) { + GR_DEFINE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey); + return resourceProvider->findOrCreatePatternedIndexBuffer( + kLineSegIdxBufPattern, kIdxsPerLineSeg, kLineSegsNumInIdxBuffer, kLineSegNumVertices, + gLinesIndexBufferKey); +} + +// Takes 178th time of logf on Z600 / VC2010 +static int get_float_exp(float x) { + GR_STATIC_ASSERT(sizeof(int) == sizeof(float)); +#ifdef SK_DEBUG + static bool tested; + if (!tested) { + tested = true; + SkASSERT(get_float_exp(0.25f) == -2); + SkASSERT(get_float_exp(0.3f) == -2); + SkASSERT(get_float_exp(0.5f) == -1); + SkASSERT(get_float_exp(1.f) == 0); + SkASSERT(get_float_exp(2.f) == 1); + SkASSERT(get_float_exp(2.5f) == 1); + SkASSERT(get_float_exp(8.f) == 3); + SkASSERT(get_float_exp(100.f) == 6); + SkASSERT(get_float_exp(1000.f) == 9); + SkASSERT(get_float_exp(1024.f) == 10); + SkASSERT(get_float_exp(3000000.f) == 21); + } +#endif + const int* iptr = (const int*)&x; + return (((*iptr) & 0x7f800000) >> 23) - 127; +} + +// Uses the max curvature function for quads to estimate +// where to chop the conic. If the max curvature is not +// found along the curve segment it will return 1 and +// dst[0] is the original conic. If it returns 2 the dst[0] +// and dst[1] are the two new conics. +static int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) { + SkScalar t = SkFindQuadMaxCurvature(src); + if (t == 0 || t == 1) { + if (dst) { + dst[0].set(src, weight); + } + return 1; + } else { + if (dst) { + SkConic conic; + conic.set(src, weight); + if (!conic.chopAt(t, dst)) { + dst[0].set(src, weight); + return 1; + } + } + return 2; + } +} + +// Calls split_conic on the entire conic and then once more on each subsection. +// Most cases will result in either 1 conic (chop point is not within t range) +// or 3 points (split once and then one subsection is split again). +static int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) { + SkConic dstTemp[2]; + int conicCnt = split_conic(src, dstTemp, weight); + if (2 == conicCnt) { + int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW); + conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW); + } else { + dst[0] = dstTemp[0]; + } + return conicCnt; +} + +// returns 0 if quad/conic is degen or close to it +// in this case approx the path with lines +// otherwise returns 1 +static int is_degen_quad_or_conic(const SkPoint p[3], SkScalar* dsqd) { + static const SkScalar gDegenerateToLineTol = GrPathUtils::kDefaultTolerance; + static const SkScalar gDegenerateToLineTolSqd = + gDegenerateToLineTol * gDegenerateToLineTol; + + if (SkPointPriv::DistanceToSqd(p[0], p[1]) < gDegenerateToLineTolSqd || + SkPointPriv::DistanceToSqd(p[1], p[2]) < gDegenerateToLineTolSqd) { + return 1; + } + + *dsqd = SkPointPriv::DistanceToLineBetweenSqd(p[1], p[0], p[2]); + if (*dsqd < gDegenerateToLineTolSqd) { + return 1; + } + + if (SkPointPriv::DistanceToLineBetweenSqd(p[2], p[1], p[0]) < gDegenerateToLineTolSqd) { + return 1; + } + return 0; +} + +static int is_degen_quad_or_conic(const SkPoint p[3]) { + SkScalar dsqd; + return is_degen_quad_or_conic(p, &dsqd); +} + +// we subdivide the quads to avoid huge overfill +// if it returns -1 then should be drawn as lines +static int num_quad_subdivs(const SkPoint p[3]) { + SkScalar dsqd; + if (is_degen_quad_or_conic(p, &dsqd)) { + return -1; + } + + // tolerance of triangle height in pixels + // tuned on windows Quadro FX 380 / Z600 + // trade off of fill vs cpu time on verts + // maybe different when do this using gpu (geo or tess shaders) + static const SkScalar gSubdivTol = 175 * SK_Scalar1; + + if (dsqd <= gSubdivTol * gSubdivTol) { + return 0; + } else { + static const int kMaxSub = 4; + // subdividing the quad reduces d by 4. so we want x = log4(d/tol) + // = log4(d*d/tol*tol)/2 + // = log2(d*d/tol*tol) + + // +1 since we're ignoring the mantissa contribution. + int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1; + log = SkTMin(SkTMax(0, log), kMaxSub); + return log; + } +} + +/** + * Generates the lines and quads to be rendered. Lines are always recorded in + * device space. We will do a device space bloat to account for the 1pixel + * thickness. + * Quads are recorded in device space unless m contains + * perspective, then in they are in src space. We do this because we will + * subdivide large quads to reduce over-fill. This subdivision has to be + * performed before applying the perspective matrix. + */ +static int gather_lines_and_quads(const SkPath& path, + const SkMatrix& m, + const SkIRect& devClipBounds, + SkScalar capLength, + bool convertConicsToQuads, + GrAAHairLinePathRenderer::PtArray* lines, + GrAAHairLinePathRenderer::PtArray* quads, + GrAAHairLinePathRenderer::PtArray* conics, + GrAAHairLinePathRenderer::IntArray* quadSubdivCnts, + GrAAHairLinePathRenderer::FloatArray* conicWeights) { + SkPath::Iter iter(path, false); + + int totalQuadCount = 0; + SkRect bounds; + SkIRect ibounds; + + bool persp = m.hasPerspective(); + + // Whenever a degenerate, zero-length contour is encountered, this code will insert a + // 'capLength' x-aligned line segment. Since this is rendering hairlines it is hoped this will + // suffice for AA square & circle capping. + int verbsInContour = 0; // Does not count moves + bool seenZeroLengthVerb = false; + SkPoint zeroVerbPt; + + // Adds a quad that has already been chopped to the list and checks for quads that are close to + // lines. Also does a bounding box check. It takes points that are in src space and device + // space. The src points are only required if the view matrix has perspective. + auto addChoppedQuad = [&](const SkPoint srcPts[3], const SkPoint devPts[4], + bool isContourStart) { + SkRect bounds; + SkIRect ibounds; + bounds.setBounds(devPts, 3); + bounds.outset(SK_Scalar1, SK_Scalar1); + bounds.roundOut(&ibounds); + // We only need the src space space pts when not in perspective. + SkASSERT(srcPts || !persp); + if (SkIRect::Intersects(devClipBounds, ibounds)) { + int subdiv = num_quad_subdivs(devPts); + SkASSERT(subdiv >= -1); + if (-1 == subdiv) { + SkPoint* pts = lines->push_back_n(4); + pts[0] = devPts[0]; + pts[1] = devPts[1]; + pts[2] = devPts[1]; + pts[3] = devPts[2]; + if (isContourStart && pts[0] == pts[1] && pts[2] == pts[3]) { + seenZeroLengthVerb = true; + zeroVerbPt = pts[0]; + } + } else { + // when in perspective keep quads in src space + const SkPoint* qPts = persp ? srcPts : devPts; + SkPoint* pts = quads->push_back_n(3); + pts[0] = qPts[0]; + pts[1] = qPts[1]; + pts[2] = qPts[2]; + quadSubdivCnts->push_back() = subdiv; + totalQuadCount += 1 << subdiv; + } + } + }; + + // Applies the view matrix to quad src points and calls the above helper. + auto addSrcChoppedQuad = [&](const SkPoint srcSpaceQuadPts[3], bool isContourStart) { + SkPoint devPts[3]; + m.mapPoints(devPts, srcSpaceQuadPts, 3); + addChoppedQuad(srcSpaceQuadPts, devPts, isContourStart); + }; + + for (;;) { + SkPoint pathPts[4]; + SkPath::Verb verb = iter.next(pathPts); + switch (verb) { + case SkPath::kConic_Verb: + if (convertConicsToQuads) { + SkScalar weight = iter.conicWeight(); + SkAutoConicToQuads converter; + const SkPoint* quadPts = converter.computeQuads(pathPts, weight, 0.25f); + for (int i = 0; i < converter.countQuads(); ++i) { + addSrcChoppedQuad(quadPts + 2 * i, !verbsInContour && 0 == i); + } + } else { + SkConic dst[4]; + // We chop the conics to create tighter clipping to hide error + // that appears near max curvature of very thin conics. Thin + // hyperbolas with high weight still show error. + int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); + for (int i = 0; i < conicCnt; ++i) { + SkPoint devPts[4]; + SkPoint* chopPnts = dst[i].fPts; + m.mapPoints(devPts, chopPnts, 3); + bounds.setBounds(devPts, 3); + bounds.outset(SK_Scalar1, SK_Scalar1); + bounds.roundOut(&ibounds); + if (SkIRect::Intersects(devClipBounds, ibounds)) { + if (is_degen_quad_or_conic(devPts)) { + SkPoint* pts = lines->push_back_n(4); + pts[0] = devPts[0]; + pts[1] = devPts[1]; + pts[2] = devPts[1]; + pts[3] = devPts[2]; + if (verbsInContour == 0 && i == 0 && pts[0] == pts[1] && + pts[2] == pts[3]) { + seenZeroLengthVerb = true; + zeroVerbPt = pts[0]; + } + } else { + // when in perspective keep conics in src space + SkPoint* cPts = persp ? chopPnts : devPts; + SkPoint* pts = conics->push_back_n(3); + pts[0] = cPts[0]; + pts[1] = cPts[1]; + pts[2] = cPts[2]; + conicWeights->push_back() = dst[i].fW; + } + } + } + } + verbsInContour++; + break; + case SkPath::kMove_Verb: + // New contour (and last one was unclosed). If it was just a zero length drawing + // operation, and we're supposed to draw caps, then add a tiny line. + if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) { + SkPoint* pts = lines->push_back_n(2); + pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); + pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); + } + verbsInContour = 0; + seenZeroLengthVerb = false; + break; + case SkPath::kLine_Verb: { + SkPoint devPts[2]; + m.mapPoints(devPts, pathPts, 2); + bounds.setBounds(devPts, 2); + bounds.outset(SK_Scalar1, SK_Scalar1); + bounds.roundOut(&ibounds); + if (SkIRect::Intersects(devClipBounds, ibounds)) { + SkPoint* pts = lines->push_back_n(2); + pts[0] = devPts[0]; + pts[1] = devPts[1]; + if (verbsInContour == 0 && pts[0] == pts[1]) { + seenZeroLengthVerb = true; + zeroVerbPt = pts[0]; + } + } + verbsInContour++; + break; + } + case SkPath::kQuad_Verb: { + SkPoint choppedPts[5]; + // Chopping the quad helps when the quad is either degenerate or nearly degenerate. + // When it is degenerate it allows the approximation with lines to work since the + // chop point (if there is one) will be at the parabola's vertex. In the nearly + // degenerate the QuadUVMatrix computed for the points is almost singular which + // can cause rendering artifacts. + int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); + for (int i = 0; i < n; ++i) { + addSrcChoppedQuad(choppedPts + i * 2, !verbsInContour && 0 == i); + } + verbsInContour++; + break; + } + case SkPath::kCubic_Verb: { + SkPoint devPts[4]; + m.mapPoints(devPts, pathPts, 4); + bounds.setBounds(devPts, 4); + bounds.outset(SK_Scalar1, SK_Scalar1); + bounds.roundOut(&ibounds); + if (SkIRect::Intersects(devClipBounds, ibounds)) { + PREALLOC_PTARRAY(32) q; + // We convert cubics to quadratics (for now). + // In perspective have to do conversion in src space. + if (persp) { + SkScalar tolScale = + GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds()); + GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q); + } else { + GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q); + } + for (int i = 0; i < q.count(); i += 3) { + if (persp) { + addSrcChoppedQuad(&q[i], !verbsInContour && 0 == i); + } else { + addChoppedQuad(nullptr, &q[i], !verbsInContour && 0 == i); + } + } + } + verbsInContour++; + break; + } + case SkPath::kClose_Verb: + // Contour is closed, so we don't need to grow the starting line, unless it's + // *just* a zero length subpath. (SVG Spec 11.4, 'stroke'). + if (capLength > 0) { + if (seenZeroLengthVerb && verbsInContour == 1) { + SkPoint* pts = lines->push_back_n(2); + pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); + pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); + } else if (verbsInContour == 0) { + // Contour was (moveTo, close). Add a line. + SkPoint devPts[2]; + m.mapPoints(devPts, pathPts, 1); + devPts[1] = devPts[0]; + bounds.setBounds(devPts, 2); + bounds.outset(SK_Scalar1, SK_Scalar1); + bounds.roundOut(&ibounds); + if (SkIRect::Intersects(devClipBounds, ibounds)) { + SkPoint* pts = lines->push_back_n(2); + pts[0] = SkPoint::Make(devPts[0].fX - capLength, devPts[0].fY); + pts[1] = SkPoint::Make(devPts[1].fX + capLength, devPts[1].fY); + } + } + } + break; + case SkPath::kDone_Verb: + if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) { + // Path ended with a dangling (moveTo, line|quad|etc). If the final verb is + // degenerate, we need to draw a line. + SkPoint* pts = lines->push_back_n(2); + pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); + pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); + } + return totalQuadCount; + } + } +} + +struct LineVertex { + SkPoint fPos; + float fCoverage; +}; + +struct BezierVertex { + SkPoint fPos; + union { + struct { + SkScalar fKLM[3]; + } fConic; + SkVector fQuadCoord; + struct { + SkScalar fBogus[4]; + }; + }; +}; + +GR_STATIC_ASSERT(sizeof(BezierVertex) == 3 * sizeof(SkPoint)); + +static void intersect_lines(const SkPoint& ptA, const SkVector& normA, + const SkPoint& ptB, const SkVector& normB, + SkPoint* result) { + + SkScalar lineAW = -normA.dot(ptA); + SkScalar lineBW = -normB.dot(ptB); + + SkScalar wInv = normA.fX * normB.fY - normA.fY * normB.fX; + wInv = SkScalarInvert(wInv); + if (!SkScalarIsFinite(wInv)) { + // lines are parallel, pick the point in between + *result = (ptA + ptB)*SK_ScalarHalf; + *result += normA; + } else { + result->fX = normA.fY * lineBW - lineAW * normB.fY; + result->fX *= wInv; + + result->fY = lineAW * normB.fX - normA.fX * lineBW; + result->fY *= wInv; + } +} + +static void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kQuadNumVertices]) { + // this should be in the src space, not dev coords, when we have perspective + GrPathUtils::QuadUVMatrix DevToUV(qpts); + DevToUV.apply(verts, kQuadNumVertices, sizeof(BezierVertex), sizeof(SkPoint)); +} + +static void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice, + const SkMatrix* toSrc, BezierVertex verts[kQuadNumVertices]) { + SkASSERT(!toDevice == !toSrc); + // original quad is specified by tri a,b,c + SkPoint a = qpts[0]; + SkPoint b = qpts[1]; + SkPoint c = qpts[2]; + + if (toDevice) { + toDevice->mapPoints(&a, 1); + toDevice->mapPoints(&b, 1); + toDevice->mapPoints(&c, 1); + } + // make a new poly where we replace a and c by a 1-pixel wide edges orthog + // to edges ab and bc: + // + // before | after + // | b0 + // b | + // | + // | a0 c0 + // a c | a1 c1 + // + // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c, + // respectively. + BezierVertex& a0 = verts[0]; + BezierVertex& a1 = verts[1]; + BezierVertex& b0 = verts[2]; + BezierVertex& c0 = verts[3]; + BezierVertex& c1 = verts[4]; + + SkVector ab = b; + ab -= a; + SkVector ac = c; + ac -= a; + SkVector cb = b; + cb -= c; + + // After the transform we might have a line, try to do something reasonable + if (toDevice && SkPointPriv::LengthSqd(ab) <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { + ab = cb; + } + if (toDevice && SkPointPriv::LengthSqd(cb) <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { + cb = ab; + } + + // We should have already handled degenerates + SkASSERT(toDevice || (ab.length() > 0 && cb.length() > 0)); + + ab.normalize(); + SkVector abN = SkPointPriv::MakeOrthog(ab, SkPointPriv::kLeft_Side); + if (abN.dot(ac) > 0) { + abN.negate(); + } + + cb.normalize(); + SkVector cbN = SkPointPriv::MakeOrthog(cb, SkPointPriv::kLeft_Side); + if (cbN.dot(ac) < 0) { + cbN.negate(); + } + + a0.fPos = a; + a0.fPos += abN; + a1.fPos = a; + a1.fPos -= abN; + + if (toDevice && SkPointPriv::LengthSqd(ac) <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { + c = b; + } + c0.fPos = c; + c0.fPos += cbN; + c1.fPos = c; + c1.fPos -= cbN; + + intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos); + + if (toSrc) { + SkMatrixPriv::MapPointsWithStride(*toSrc, &verts[0].fPos, sizeof(BezierVertex), + kQuadNumVertices); + } +} + +// Equations based off of Loop-Blinn Quadratic GPU Rendering +// Input Parametric: +// P(t) = (P0*(1-t)^2 + 2*w*P1*t*(1-t) + P2*t^2) / (1-t)^2 + 2*w*t*(1-t) + t^2) +// Output Implicit: +// f(x, y, w) = f(P) = K^2 - LM +// K = dot(k, P), L = dot(l, P), M = dot(m, P) +// k, l, m are calculated in function GrPathUtils::getConicKLM +static void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kQuadNumVertices], + const SkScalar weight) { + SkMatrix klm; + + GrPathUtils::getConicKLM(p, weight, &klm); + + for (int i = 0; i < kQuadNumVertices; ++i) { + const SkPoint3 pt3 = {verts[i].fPos.x(), verts[i].fPos.y(), 1.f}; + klm.mapHomogeneousPoints((SkPoint3* ) verts[i].fConic.fKLM, &pt3, 1); + } +} + +static void add_conics(const SkPoint p[3], + const SkScalar weight, + const SkMatrix* toDevice, + const SkMatrix* toSrc, + BezierVertex** vert) { + bloat_quad(p, toDevice, toSrc, *vert); + set_conic_coeffs(p, *vert, weight); + *vert += kQuadNumVertices; +} + +static void add_quads(const SkPoint p[3], + int subdiv, + const SkMatrix* toDevice, + const SkMatrix* toSrc, + BezierVertex** vert) { + SkASSERT(subdiv >= 0); + if (subdiv) { + SkPoint newP[5]; + SkChopQuadAtHalf(p, newP); + add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert); + add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert); + } else { + bloat_quad(p, toDevice, toSrc, *vert); + set_uv_quad(p, *vert); + *vert += kQuadNumVertices; + } +} + +static void add_line(const SkPoint p[2], + const SkMatrix* toSrc, + uint8_t coverage, + LineVertex** vert) { + const SkPoint& a = p[0]; + const SkPoint& b = p[1]; + + SkVector ortho, vec = b; + vec -= a; + + SkScalar lengthSqd = SkPointPriv::LengthSqd(vec); + + if (vec.setLength(SK_ScalarHalf)) { + // Create a vector orthogonal to 'vec' and of unit length + ortho.fX = 2.0f * vec.fY; + ortho.fY = -2.0f * vec.fX; + + float floatCoverage = GrNormalizeByteToFloat(coverage); + + if (lengthSqd >= 1.0f) { + // Relative to points a and b: + // The inner vertices are inset half a pixel along the line a,b + (*vert)[0].fPos = a + vec; + (*vert)[0].fCoverage = floatCoverage; + (*vert)[1].fPos = b - vec; + (*vert)[1].fCoverage = floatCoverage; + } else { + // The inner vertices are inset a distance of length(a,b) from the outer edge of + // geometry. For the "a" inset this is the same as insetting from b by half a pixel. + // The coverage is then modulated by the length. This gives us the correct + // coverage for rects shorter than a pixel as they get translated subpixel amounts + // inside of a pixel. + SkScalar length = SkScalarSqrt(lengthSqd); + (*vert)[0].fPos = b - vec; + (*vert)[0].fCoverage = floatCoverage * length; + (*vert)[1].fPos = a + vec; + (*vert)[1].fCoverage = floatCoverage * length; + } + // Relative to points a and b: + // The outer vertices are outset half a pixel along the line a,b and then a whole pixel + // orthogonally. + (*vert)[2].fPos = a - vec + ortho; + (*vert)[2].fCoverage = 0; + (*vert)[3].fPos = b + vec + ortho; + (*vert)[3].fCoverage = 0; + (*vert)[4].fPos = a - vec - ortho; + (*vert)[4].fCoverage = 0; + (*vert)[5].fPos = b + vec - ortho; + (*vert)[5].fCoverage = 0; + + if (toSrc) { + SkMatrixPriv::MapPointsWithStride(*toSrc, &(*vert)->fPos, sizeof(LineVertex), + kLineSegNumVertices); + } + } else { + // just make it degenerate and likely offscreen + for (int i = 0; i < kLineSegNumVertices; ++i) { + (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax); + } + } + + *vert += kLineSegNumVertices; +} + +/////////////////////////////////////////////////////////////////////////////// + +GrPathRenderer::CanDrawPath +GrAAHairLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + if (GrAAType::kCoverage != args.fAAType) { + return CanDrawPath::kNo; + } + + if (!IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr)) { + return CanDrawPath::kNo; + } + + // We don't currently handle dashing in this class though perhaps we should. + if (args.fShape->style().pathEffect()) { + return CanDrawPath::kNo; + } + + if (SkPath::kLine_SegmentMask == args.fShape->segmentMask() || + args.fCaps->shaderCaps()->shaderDerivativeSupport()) { + return CanDrawPath::kYes; + } + + return CanDrawPath::kNo; +} + +template +bool check_bounds(const SkMatrix& viewMatrix, const SkRect& devBounds, void* vertices, int vCount) +{ + SkRect tolDevBounds = devBounds; + // The bounds ought to be tight, but in perspective the below code runs the verts + // through the view matrix to get back to dev coords, which can introduce imprecision. + if (viewMatrix.hasPerspective()) { + tolDevBounds.outset(SK_Scalar1 / 1000, SK_Scalar1 / 1000); + } else { + // Non-persp matrices cause this path renderer to draw in device space. + SkASSERT(viewMatrix.isIdentity()); + } + SkRect actualBounds; + + VertexType* verts = reinterpret_cast(vertices); + bool first = true; + for (int i = 0; i < vCount; ++i) { + SkPoint pos = verts[i].fPos; + // This is a hack to workaround the fact that we move some degenerate segments offscreen. + if (SK_ScalarMax == pos.fX) { + continue; + } + viewMatrix.mapPoints(&pos, 1); + if (first) { + actualBounds.setLTRB(pos.fX, pos.fY, pos.fX, pos.fY); + first = false; + } else { + SkRectPriv::GrowToInclude(&actualBounds, pos); + } + } + if (!first) { + return tolDevBounds.contains(actualBounds); + } + + return true; +} + +namespace { + +class AAHairlineOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkPath& path, + const GrStyle& style, + const SkIRect& devClipBounds, + const GrUserStencilSettings* stencilSettings) { + SkScalar hairlineCoverage; + uint8_t newCoverage = 0xff; + if (GrPathRenderer::IsStrokeHairlineOrEquivalent(style, viewMatrix, &hairlineCoverage)) { + newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); + } + + const SkStrokeRec& stroke = style.strokeRec(); + SkScalar capLength = SkPaint::kButt_Cap != stroke.getCap() ? hairlineCoverage * 0.5f : 0.0f; + + return Helper::FactoryHelper(context, std::move(paint), newCoverage, + viewMatrix, path, + devClipBounds, capLength, stencilSettings); + } + + AAHairlineOp(const Helper::MakeArgs& helperArgs, + const SkPMColor4f& color, + uint8_t coverage, + const SkMatrix& viewMatrix, + const SkPath& path, + SkIRect devClipBounds, + SkScalar capLength, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) + , fColor(color) + , fCoverage(coverage) { + fPaths.emplace_back(PathData{viewMatrix, path, devClipBounds, capLength}); + + this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes, + IsHairline::kYes); + } + + const char* name() const override { return "AAHairlineOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf("Color: 0x%08x Coverage: 0x%02x, Count: %d\n", fColor.toBytes_RGBA(), + fCoverage, fPaths.count()); + string += INHERITED::dumpInfo(); + string += fHelper.dumpInfo(); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + // This Op uses uniform (not vertex) color, so doesn't need to track wide color. + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, &fColor, + nullptr); + } + +private: + void onPrepareDraws(Target*) override; + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + typedef SkTArray PtArray; + typedef SkTArray IntArray; + typedef SkTArray FloatArray; + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + AAHairlineOp* that = t->cast(); + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (this->viewMatrix().hasPerspective() != that->viewMatrix().hasPerspective()) { + return CombineResult::kCannotCombine; + } + + // We go to identity if we don't have perspective + if (this->viewMatrix().hasPerspective() && + !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + // TODO we can actually combine hairlines if they are the same color in a kind of bulk + // method but we haven't implemented this yet + // TODO investigate going to vertex color and coverage? + if (this->coverage() != that->coverage()) { + return CombineResult::kCannotCombine; + } + + if (this->color() != that->color()) { + return CombineResult::kCannotCombine; + } + + if (fHelper.usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); + return CombineResult::kMerged; + } + + const SkPMColor4f& color() const { return fColor; } + uint8_t coverage() const { return fCoverage; } + const SkMatrix& viewMatrix() const { return fPaths[0].fViewMatrix; } + + struct PathData { + SkMatrix fViewMatrix; + SkPath fPath; + SkIRect fDevClipBounds; + SkScalar fCapLength; + }; + + SkSTArray<1, PathData, true> fPaths; + Helper fHelper; + SkPMColor4f fColor; + uint8_t fCoverage; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +void AAHairlineOp::onPrepareDraws(Target* target) { + // Setup the viewmatrix and localmatrix for the GrGeometryProcessor. + SkMatrix invert; + if (!this->viewMatrix().invert(&invert)) { + return; + } + + // we will transform to identity space if the viewmatrix does not have perspective + bool hasPerspective = this->viewMatrix().hasPerspective(); + const SkMatrix* geometryProcessorViewM = &SkMatrix::I(); + const SkMatrix* geometryProcessorLocalM = &invert; + const SkMatrix* toDevice = nullptr; + const SkMatrix* toSrc = nullptr; + if (hasPerspective) { + geometryProcessorViewM = &this->viewMatrix(); + geometryProcessorLocalM = &SkMatrix::I(); + toDevice = &this->viewMatrix(); + toSrc = &invert; + } + + // This is hand inlined for maximum performance. + PREALLOC_PTARRAY(128) lines; + PREALLOC_PTARRAY(128) quads; + PREALLOC_PTARRAY(128) conics; + IntArray qSubdivs; + FloatArray cWeights; + int quadCount = 0; + + int instanceCount = fPaths.count(); + bool convertConicsToQuads = !target->caps().shaderCaps()->floatIs32Bits(); + for (int i = 0; i < instanceCount; i++) { + const PathData& args = fPaths[i]; + quadCount += gather_lines_and_quads(args.fPath, args.fViewMatrix, args.fDevClipBounds, + args.fCapLength, convertConicsToQuads, &lines, &quads, + &conics, &qSubdivs, &cWeights); + } + + int lineCount = lines.count() / 2; + int conicCount = conics.count() / 3; + int quadAndConicCount = conicCount + quadCount; + + static constexpr int kMaxLines = SK_MaxS32 / kLineSegNumVertices; + static constexpr int kMaxQuadsAndConics = SK_MaxS32 / kQuadNumVertices; + if (lineCount > kMaxLines || quadAndConicCount > kMaxQuadsAndConics) { + return; + } + + // do lines first + if (lineCount) { + sk_sp lineGP; + { + using namespace GrDefaultGeoProcFactory; + + Color color(this->color()); + LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type + : LocalCoords::kUnused_Type); + localCoords.fMatrix = geometryProcessorLocalM; + lineGP = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), + color, Coverage::kAttribute_Type, localCoords, + *geometryProcessorViewM); + } + + sk_sp linesIndexBuffer = get_lines_index_buffer(target->resourceProvider()); + + sk_sp vertexBuffer; + int firstVertex; + + SkASSERT(sizeof(LineVertex) == lineGP->vertexStride()); + int vertexCount = kLineSegNumVertices * lineCount; + LineVertex* verts = reinterpret_cast(target->makeVertexSpace( + sizeof(LineVertex), vertexCount, &vertexBuffer, &firstVertex)); + + if (!verts|| !linesIndexBuffer) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + for (int i = 0; i < lineCount; ++i) { + add_line(&lines[2*i], toSrc, this->coverage(), &verts); + } + + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexedPatterned(std::move(linesIndexBuffer), kIdxsPerLineSeg, kLineSegNumVertices, + lineCount, kLineSegsNumInIdxBuffer); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(lineGP), mesh); + } + + if (quadCount || conicCount) { + sk_sp quadGP(GrQuadEffect::Make(this->color(), + *geometryProcessorViewM, + GrClipEdgeType::kHairlineAA, + target->caps(), + *geometryProcessorLocalM, + fHelper.usesLocalCoords(), + this->coverage())); + + sk_sp conicGP(GrConicEffect::Make(this->color(), + *geometryProcessorViewM, + GrClipEdgeType::kHairlineAA, + target->caps(), + *geometryProcessorLocalM, + fHelper.usesLocalCoords(), + this->coverage())); + + sk_sp vertexBuffer; + int firstVertex; + + sk_sp quadsIndexBuffer = get_quads_index_buffer(target->resourceProvider()); + + SkASSERT(sizeof(BezierVertex) == quadGP->vertexStride()); + SkASSERT(sizeof(BezierVertex) == conicGP->vertexStride()); + int vertexCount = kQuadNumVertices * quadAndConicCount; + void* vertices = target->makeVertexSpace(sizeof(BezierVertex), vertexCount, &vertexBuffer, + &firstVertex); + + if (!vertices || !quadsIndexBuffer) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + // Setup vertices + BezierVertex* bezVerts = reinterpret_cast(vertices); + + int unsubdivQuadCnt = quads.count() / 3; + for (int i = 0; i < unsubdivQuadCnt; ++i) { + SkASSERT(qSubdivs[i] >= 0); + add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &bezVerts); + } + + // Start Conics + for (int i = 0; i < conicCount; ++i) { + add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &bezVerts); + } + + if (quadCount > 0) { + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexedPatterned(quadsIndexBuffer, kIdxsPerQuad, kQuadNumVertices, quadCount, + kQuadsNumInIdxBuffer); + mesh->setVertexData(vertexBuffer, firstVertex); + target->recordDraw(std::move(quadGP), mesh); + firstVertex += quadCount * kQuadNumVertices; + } + + if (conicCount > 0) { + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexedPatterned(std::move(quadsIndexBuffer), kIdxsPerQuad, kQuadNumVertices, + conicCount, kQuadsNumInIdxBuffer); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(conicGP), mesh); + } + } +} + +void AAHairlineOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); +} + +bool GrAAHairLinePathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrAAHairlinePathRenderer::onDrawPath"); + SkASSERT(args.fRenderTargetContext->numSamples() <= 1); + + SkIRect devClipBounds; + args.fClip->getConservativeBounds(args.fRenderTargetContext->width(), + args.fRenderTargetContext->height(), + &devClipBounds); + SkPath path; + args.fShape->asPath(&path); + std::unique_ptr op = + AAHairlineOp::Make(args.fContext, std::move(args.fPaint), *args.fViewMatrix, path, + args.fShape->style(), devClipBounds, args.fUserStencilSettings); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(AAHairlineOp) { + SkMatrix viewMatrix = GrTest::TestMatrix(random); + SkPath path = GrTest::TestPath(random); + SkIRect devClipBounds; + devClipBounds.setEmpty(); + return AAHairlineOp::Make(context, std::move(paint), viewMatrix, path, + GrStyle::SimpleHairline(), devClipBounds, + GrGetRandomStencil(random, context)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.h new file mode 100644 index 0000000000..af78faa937 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAAHairLinePathRenderer.h @@ -0,0 +1,30 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAAHairLinePathRenderer_DEFINED +#define GrAAHairLinePathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +class GrAAHairLinePathRenderer : public GrPathRenderer { +public: + GrAAHairLinePathRenderer() {} + + typedef SkTArray PtArray; + typedef SkTArray IntArray; + typedef SkTArray FloatArray; + +private: + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; + + typedef GrPathRenderer INHERITED; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp new file mode 100644 index 0000000000..d0cbb41013 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp @@ -0,0 +1,394 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPathPriv.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/geometry/GrPathUtils.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/ops/GrAAConvexTessellator.h" +#include "src/gpu/ops/GrAALinearizingConvexPathRenderer.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +static const int DEFAULT_BUFFER_SIZE = 100; + +// The thicker the stroke, the harder it is to produce high-quality results using tessellation. For +// the time being, we simply drop back to software rendering above this stroke width. +static const SkScalar kMaxStrokeWidth = 20.0; + +GrAALinearizingConvexPathRenderer::GrAALinearizingConvexPathRenderer() { +} + +/////////////////////////////////////////////////////////////////////////////// + +GrPathRenderer::CanDrawPath +GrAALinearizingConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + if (GrAAType::kCoverage != args.fAAType) { + return CanDrawPath::kNo; + } + if (!args.fShape->knownToBeConvex()) { + return CanDrawPath::kNo; + } + if (args.fShape->style().pathEffect()) { + return CanDrawPath::kNo; + } + if (args.fShape->inverseFilled()) { + return CanDrawPath::kNo; + } + if (args.fShape->bounds().width() <= 0 && args.fShape->bounds().height() <= 0) { + // Stroked zero length lines should draw, but this PR doesn't handle that case + return CanDrawPath::kNo; + } + const SkStrokeRec& stroke = args.fShape->style().strokeRec(); + + if (stroke.getStyle() == SkStrokeRec::kStroke_Style || + stroke.getStyle() == SkStrokeRec::kStrokeAndFill_Style) { + if (!args.fViewMatrix->isSimilarity()) { + return CanDrawPath::kNo; + } + SkScalar strokeWidth = args.fViewMatrix->getMaxScale() * stroke.getWidth(); + if (strokeWidth < 1.0f && stroke.getStyle() == SkStrokeRec::kStroke_Style) { + return CanDrawPath::kNo; + } + if (strokeWidth > kMaxStrokeWidth || + !args.fShape->knownToBeClosed() || + stroke.getJoin() == SkPaint::Join::kRound_Join) { + return CanDrawPath::kNo; + } + return CanDrawPath::kYes; + } + if (stroke.getStyle() != SkStrokeRec::kFill_Style) { + return CanDrawPath::kNo; + } + return CanDrawPath::kYes; +} + +// extract the result vertices and indices from the GrAAConvexTessellator +static void extract_verts(const GrAAConvexTessellator& tess, + void* vertData, + const GrVertexColor& color, + uint16_t firstIndex, + uint16_t* idxs) { + GrVertexWriter verts{vertData}; + for (int i = 0; i < tess.numPts(); ++i) { + verts.write(tess.point(i), color, tess.coverage(i)); + } + + for (int i = 0; i < tess.numIndices(); ++i) { + idxs[i] = tess.index(i) + firstIndex; + } +} + +static sk_sp create_lines_only_gp(const GrShaderCaps* shaderCaps, + bool tweakAlphaForCoverage, + const SkMatrix& viewMatrix, + bool usesLocalCoords, + bool wideColor) { + using namespace GrDefaultGeoProcFactory; + + Coverage::Type coverageType = + tweakAlphaForCoverage ? Coverage::kAttributeTweakAlpha_Type : Coverage::kAttribute_Type; + LocalCoords::Type localCoordsType = + usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type; + Color::Type colorType = + wideColor ? Color::kPremulWideColorAttribute_Type : Color::kPremulGrColorAttribute_Type; + + return MakeForDeviceSpace(shaderCaps, colorType, coverageType, localCoordsType, viewMatrix); +} + +namespace { + +class AAFlatteningConvexPathOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkPath& path, + SkScalar strokeWidth, + SkStrokeRec::Style style, + SkPaint::Join join, + SkScalar miterLimit, + const GrUserStencilSettings* stencilSettings) { + return Helper::FactoryHelper(context, std::move(paint), + viewMatrix, path, + strokeWidth, style, join, miterLimit, + stencilSettings); + } + + AAFlatteningConvexPathOp(const Helper::MakeArgs& helperArgs, + const SkPMColor4f& color, + const SkMatrix& viewMatrix, + const SkPath& path, + SkScalar strokeWidth, + SkStrokeRec::Style style, + SkPaint::Join join, + SkScalar miterLimit, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) { + fPaths.emplace_back( + PathData{color, viewMatrix, path, strokeWidth, style, join, miterLimit}); + + // compute bounds + SkRect bounds = path.getBounds(); + SkScalar w = strokeWidth; + if (w > 0) { + w /= 2; + SkScalar maxScale = viewMatrix.getMaxScale(); + // We should not have a perspective matrix, thus we should have a valid scale. + SkASSERT(maxScale != -1); + if (SkPaint::kMiter_Join == join && w * maxScale > 1.f) { + w *= miterLimit; + } + bounds.outset(w, w); + } + this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kYes, IsHairline::kNo); + } + + const char* name() const override { return "AAFlatteningConvexPathOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (const auto& path : fPaths) { + string.appendf( + "Color: 0x%08x, StrokeWidth: %.2f, Style: %d, Join: %d, " + "MiterLimit: %.2f\n", + path.fColor.toBytes_RGBA(), path.fStrokeWidth, path.fStyle, path.fJoin, + path.fMiterLimit); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, &fPaths.back().fColor, &fWideColor); + } + +private: + void recordDraw(Target* target, sk_sp gp, int vertexCount, + size_t vertexStride, void* vertices, int indexCount, uint16_t* indices) const { + if (vertexCount == 0 || indexCount == 0) { + return; + } + sk_sp vertexBuffer; + int firstVertex; + void* verts = target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, + &firstVertex); + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return; + } + memcpy(verts, vertices, vertexCount * vertexStride); + + sk_sp indexBuffer; + int firstIndex; + uint16_t* idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex); + if (!idxs) { + SkDebugf("Could not allocate indices\n"); + return; + } + memcpy(idxs, indices, indexCount * sizeof(uint16_t)); + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexed(std::move(indexBuffer), indexCount, firstIndex, 0, vertexCount - 1, + GrPrimitiveRestart::kNo); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onPrepareDraws(Target* target) override { + // Setup GrGeometryProcessor + sk_sp gp(create_lines_only_gp(target->caps().shaderCaps(), + fHelper.compatibleWithCoverageAsAlpha(), + this->viewMatrix(), + fHelper.usesLocalCoords(), + fWideColor)); + if (!gp) { + SkDebugf("Couldn't create a GrGeometryProcessor\n"); + return; + } + + size_t vertexStride = gp->vertexStride(); + int instanceCount = fPaths.count(); + + int64_t vertexCount = 0; + int64_t indexCount = 0; + int64_t maxVertices = DEFAULT_BUFFER_SIZE; + int64_t maxIndices = DEFAULT_BUFFER_SIZE; + uint8_t* vertices = (uint8_t*) sk_malloc_throw(maxVertices * vertexStride); + uint16_t* indices = (uint16_t*) sk_malloc_throw(maxIndices * sizeof(uint16_t)); + for (int i = 0; i < instanceCount; i++) { + const PathData& args = fPaths[i]; + GrAAConvexTessellator tess(args.fStyle, args.fStrokeWidth, + args.fJoin, args.fMiterLimit); + + if (!tess.tessellate(args.fViewMatrix, args.fPath)) { + continue; + } + + int currentVertices = tess.numPts(); + if (vertexCount + currentVertices > static_cast(UINT16_MAX)) { + // if we added the current instance, we would overflow the indices we can store in a + // uint16_t. Draw what we've got so far and reset. + this->recordDraw( + target, gp, vertexCount, vertexStride, vertices, indexCount, indices); + vertexCount = 0; + indexCount = 0; + } + if (vertexCount + currentVertices > maxVertices) { + maxVertices = SkTMax(vertexCount + currentVertices, maxVertices * 2); + if (maxVertices * vertexStride > SK_MaxS32) { + sk_free(vertices); + sk_free(indices); + return; + } + vertices = (uint8_t*) sk_realloc_throw(vertices, maxVertices * vertexStride); + } + int currentIndices = tess.numIndices(); + if (indexCount + currentIndices > maxIndices) { + maxIndices = SkTMax(indexCount + currentIndices, maxIndices * 2); + if (maxIndices * sizeof(uint16_t) > SK_MaxS32) { + sk_free(vertices); + sk_free(indices); + return; + } + indices = (uint16_t*) sk_realloc_throw(indices, maxIndices * sizeof(uint16_t)); + } + + extract_verts(tess, vertices + vertexStride * vertexCount, + GrVertexColor(args.fColor, fWideColor), vertexCount, + indices + indexCount); + vertexCount += currentVertices; + indexCount += currentIndices; + } + if (vertexCount <= SK_MaxS32 && indexCount <= SK_MaxS32) { + this->recordDraw(target, std::move(gp), vertexCount, vertexStride, vertices, indexCount, + indices); + } + sk_free(vertices); + sk_free(indices); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + AAFlatteningConvexPathOp* that = t->cast(); + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + const SkMatrix& viewMatrix() const { return fPaths[0].fViewMatrix; } + + struct PathData { + SkPMColor4f fColor; + SkMatrix fViewMatrix; + SkPath fPath; + SkScalar fStrokeWidth; + SkStrokeRec::Style fStyle; + SkPaint::Join fJoin; + SkScalar fMiterLimit; + }; + + SkSTArray<1, PathData, true> fPaths; + Helper fHelper; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +bool GrAALinearizingConvexPathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrAALinearizingConvexPathRenderer::onDrawPath"); + SkASSERT(args.fRenderTargetContext->numSamples() <= 1); + SkASSERT(!args.fShape->isEmpty()); + SkASSERT(!args.fShape->style().pathEffect()); + + SkPath path; + args.fShape->asPath(&path); + bool fill = args.fShape->style().isSimpleFill(); + const SkStrokeRec& stroke = args.fShape->style().strokeRec(); + SkScalar strokeWidth = fill ? -1.0f : stroke.getWidth(); + SkPaint::Join join = fill ? SkPaint::Join::kMiter_Join : stroke.getJoin(); + SkScalar miterLimit = stroke.getMiter(); + + std::unique_ptr op = AAFlatteningConvexPathOp::Make( + args.fContext, std::move(args.fPaint), *args.fViewMatrix, path, strokeWidth, + stroke.getStyle(), join, miterLimit, args.fUserStencilSettings); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(AAFlatteningConvexPathOp) { + SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); + SkPath path = GrTest::TestPathConvex(random); + + SkStrokeRec::Style styles[3] = { SkStrokeRec::kFill_Style, + SkStrokeRec::kStroke_Style, + SkStrokeRec::kStrokeAndFill_Style }; + + SkStrokeRec::Style style = styles[random->nextU() % 3]; + + SkScalar strokeWidth = -1.f; + SkPaint::Join join = SkPaint::kMiter_Join; + SkScalar miterLimit = 0.5f; + + if (SkStrokeRec::kFill_Style != style) { + strokeWidth = random->nextRangeF(1.0f, 10.0f); + if (random->nextBool()) { + join = SkPaint::kMiter_Join; + } else { + join = SkPaint::kBevel_Join; + } + miterLimit = random->nextRangeF(0.5f, 2.0f); + } + const GrUserStencilSettings* stencilSettings = GrGetRandomStencil(random, context); + return AAFlatteningConvexPathOp::Make(context, std::move(paint), viewMatrix, path, strokeWidth, + style, join, miterLimit, stencilSettings); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.h new file mode 100644 index 0000000000..b86f6fc273 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAALinearizingConvexPathRenderer.h @@ -0,0 +1,23 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAALinearizingConvexPathRenderer_DEFINED +#define GrAALinearizingConvexPathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +class GrAALinearizingConvexPathRenderer : public GrPathRenderer { +public: + GrAALinearizingConvexPathRenderer(); + +private: + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.cpp b/gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.cpp new file mode 100644 index 0000000000..bcf4670395 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.cpp @@ -0,0 +1,576 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrAtlasTextOp.h" + +#include "include/core/SkPoint3.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkStrikeCache.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/effects/GrBitmapTextGeoProc.h" +#include "src/gpu/effects/GrDistanceFieldGeoProc.h" +#include "src/gpu/text/GrAtlasManager.h" +#include "src/gpu/text/GrStrikeCache.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrAtlasTextOp::MakeBitmap(GrRecordingContext* context, + GrPaint&& paint, + GrMaskFormat maskFormat, + int glyphCount, + bool needsTransform) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + std::unique_ptr op = pool->allocate(std::move(paint)); + + switch (maskFormat) { + case kA8_GrMaskFormat: + op->fMaskType = kGrayscaleCoverageMask_MaskType; + break; + case kA565_GrMaskFormat: + op->fMaskType = kLCDCoverageMask_MaskType; + break; + case kARGB_GrMaskFormat: + op->fMaskType = kColorBitmapMask_MaskType; + break; + } + op->fNumGlyphs = glyphCount; + op->fGeoCount = 1; + op->fLuminanceColor = 0; + op->fNeedsGlyphTransform = needsTransform; + return op; + } + +std::unique_ptr GrAtlasTextOp::MakeDistanceField( + GrRecordingContext* context, + GrPaint&& paint, + int glyphCount, + const GrDistanceFieldAdjustTable* distanceAdjustTable, + bool useGammaCorrectDistanceTable, + SkColor luminanceColor, + const SkSurfaceProps& props, + bool isAntiAliased, + bool useLCD) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + std::unique_ptr op = pool->allocate(std::move(paint)); + + bool isBGR = SkPixelGeometryIsBGR(props.pixelGeometry()); + bool isLCD = useLCD && SkPixelGeometryIsH(props.pixelGeometry()); + op->fMaskType = !isAntiAliased ? kAliasedDistanceField_MaskType + : isLCD ? (isBGR ? kLCDBGRDistanceField_MaskType + : kLCDDistanceField_MaskType) + : kGrayscaleDistanceField_MaskType; + op->fDistanceAdjustTable.reset(SkRef(distanceAdjustTable)); + op->fUseGammaCorrectDistanceTable = useGammaCorrectDistanceTable; + op->fLuminanceColor = luminanceColor; + op->fNumGlyphs = glyphCount; + op->fGeoCount = 1; + return op; + } + +static const int kDistanceAdjustLumShift = 5; + +void GrAtlasTextOp::init() { + const Geometry& geo = fGeoData[0]; + if (this->usesDistanceFields()) { + bool isLCD = this->isLCD(); + + const SkMatrix& viewMatrix = geo.fViewMatrix; + + fDFGPFlags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; + fDFGPFlags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0; + fDFGPFlags |= viewMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0; + fDFGPFlags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0; + fDFGPFlags |= (kAliasedDistanceField_MaskType == fMaskType) + ? kAliased_DistanceFieldEffectFlag + : 0; + + if (isLCD) { + fDFGPFlags |= kUseLCD_DistanceFieldEffectFlag; + fDFGPFlags |= + (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0; + } + + fNeedsGlyphTransform = true; + } + + SkRect bounds; + geo.fBlob->computeSubRunBounds(&bounds, geo.fRun, geo.fSubRun, geo.fViewMatrix, geo.fX, geo.fY, + fNeedsGlyphTransform); + // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds + // we treat this as a set of non-AA rects rendered with a texture. + this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo); +} + +void GrAtlasTextOp::visitProxies(const VisitProxyFunc& func) const { + fProcessors.visitProxies(func); +} + +#ifdef SK_DEBUG +SkString GrAtlasTextOp::dumpInfo() const { + SkString str; + + for (int i = 0; i < fGeoCount; ++i) { + str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f Runs: %d\n", + i, + fGeoData[i].fColor.toBytes_RGBA(), + fGeoData[i].fX, + fGeoData[i].fY, + fGeoData[i].fBlob->runCountLimit()); + } + + str += fProcessors.dumpProcessors(); + str += INHERITED::dumpInfo(); + return str; +} +#endif + +GrDrawOp::FixedFunctionFlags GrAtlasTextOp::fixedFunctionFlags() const { + return FixedFunctionFlags::kNone; +} + +GrProcessorSet::Analysis GrAtlasTextOp::finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) { + GrProcessorAnalysisCoverage coverage; + GrProcessorAnalysisColor color; + if (kColorBitmapMask_MaskType == fMaskType) { + color.setToUnknown(); + } else { + color.setToConstant(this->color()); + } + switch (fMaskType) { + case kGrayscaleCoverageMask_MaskType: + case kAliasedDistanceField_MaskType: + case kGrayscaleDistanceField_MaskType: + coverage = GrProcessorAnalysisCoverage::kSingleChannel; + break; + case kLCDCoverageMask_MaskType: + case kLCDDistanceField_MaskType: + case kLCDBGRDistanceField_MaskType: + coverage = GrProcessorAnalysisCoverage::kLCD; + break; + case kColorBitmapMask_MaskType: + coverage = GrProcessorAnalysisCoverage::kNone; + break; + } + auto analysis = fProcessors.finalize( + color, coverage, clip, &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, + clampType, &fGeoData[0].fColor); + fUsesLocalCoords = analysis.usesLocalCoords(); + return analysis; +} + +static void clip_quads(const SkIRect& clipRect, char* currVertex, const char* blobVertices, + size_t vertexStride, int glyphCount) { + for (int i = 0; i < glyphCount; ++i) { + const SkPoint* blobPositionLT = reinterpret_cast(blobVertices); + const SkPoint* blobPositionRB = + reinterpret_cast(blobVertices + 3 * vertexStride); + + // positions for bitmap glyphs are pixel boundary aligned + SkIRect positionRect = SkIRect::MakeLTRB(SkScalarRoundToInt(blobPositionLT->fX), + SkScalarRoundToInt(blobPositionLT->fY), + SkScalarRoundToInt(blobPositionRB->fX), + SkScalarRoundToInt(blobPositionRB->fY)); + if (clipRect.contains(positionRect)) { + memcpy(currVertex, blobVertices, 4 * vertexStride); + currVertex += 4 * vertexStride; + } else { + // Pull out some more data that we'll need. + // In the LCD case the color will be garbage, but we'll overwrite it with the texcoords + // and it avoids a lot of conditionals. + auto color = *reinterpret_cast(blobVertices + sizeof(SkPoint)); + size_t coordOffset = vertexStride - 2*sizeof(uint16_t); + auto* blobCoordsLT = reinterpret_cast(blobVertices + coordOffset); + auto* blobCoordsRB = reinterpret_cast(blobVertices + 3 * vertexStride + + coordOffset); + // Pull out the texel coordinates and texture index bits + uint16_t coordsRectL = blobCoordsLT[0] >> 1; + uint16_t coordsRectT = blobCoordsLT[1] >> 1; + uint16_t coordsRectR = blobCoordsRB[0] >> 1; + uint16_t coordsRectB = blobCoordsRB[1] >> 1; + uint16_t pageIndexX = blobCoordsLT[0] & 0x1; + uint16_t pageIndexY = blobCoordsLT[1] & 0x1; + + int positionRectWidth = positionRect.width(); + int positionRectHeight = positionRect.height(); + SkASSERT(positionRectWidth == (coordsRectR - coordsRectL)); + SkASSERT(positionRectHeight == (coordsRectB - coordsRectT)); + + // Clip position and texCoords to the clipRect + unsigned int delta; + delta = SkTMin(SkTMax(clipRect.fLeft - positionRect.fLeft, 0), positionRectWidth); + coordsRectL += delta; + positionRect.fLeft += delta; + + delta = SkTMin(SkTMax(clipRect.fTop - positionRect.fTop, 0), positionRectHeight); + coordsRectT += delta; + positionRect.fTop += delta; + + delta = SkTMin(SkTMax(positionRect.fRight - clipRect.fRight, 0), positionRectWidth); + coordsRectR -= delta; + positionRect.fRight -= delta; + + delta = SkTMin(SkTMax(positionRect.fBottom - clipRect.fBottom, 0), positionRectHeight); + coordsRectB -= delta; + positionRect.fBottom -= delta; + + // Repack texel coordinates and index + coordsRectL = coordsRectL << 1 | pageIndexX; + coordsRectT = coordsRectT << 1 | pageIndexY; + coordsRectR = coordsRectR << 1 | pageIndexX; + coordsRectB = coordsRectB << 1 | pageIndexY; + + // Set new positions and coords + SkPoint* currPosition = reinterpret_cast(currVertex); + currPosition->fX = positionRect.fLeft; + currPosition->fY = positionRect.fTop; + *(reinterpret_cast(currVertex + sizeof(SkPoint))) = color; + uint16_t* currCoords = reinterpret_cast(currVertex + coordOffset); + currCoords[0] = coordsRectL; + currCoords[1] = coordsRectT; + currVertex += vertexStride; + + currPosition = reinterpret_cast(currVertex); + currPosition->fX = positionRect.fLeft; + currPosition->fY = positionRect.fBottom; + *(reinterpret_cast(currVertex + sizeof(SkPoint))) = color; + currCoords = reinterpret_cast(currVertex + coordOffset); + currCoords[0] = coordsRectL; + currCoords[1] = coordsRectB; + currVertex += vertexStride; + + currPosition = reinterpret_cast(currVertex); + currPosition->fX = positionRect.fRight; + currPosition->fY = positionRect.fTop; + *(reinterpret_cast(currVertex + sizeof(SkPoint))) = color; + currCoords = reinterpret_cast(currVertex + coordOffset); + currCoords[0] = coordsRectR; + currCoords[1] = coordsRectT; + currVertex += vertexStride; + + currPosition = reinterpret_cast(currVertex); + currPosition->fX = positionRect.fRight; + currPosition->fY = positionRect.fBottom; + *(reinterpret_cast(currVertex + sizeof(SkPoint))) = color; + currCoords = reinterpret_cast(currVertex + coordOffset); + currCoords[0] = coordsRectR; + currCoords[1] = coordsRectB; + currVertex += vertexStride; + } + + blobVertices += 4 * vertexStride; + } +} + +void GrAtlasTextOp::onPrepareDraws(Target* target) { + auto resourceProvider = target->resourceProvider(); + + // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix. + // TODO actually only invert if we don't have RGBA + SkMatrix localMatrix; + if (this->usesLocalCoords() && !fGeoData[0].fViewMatrix.invert(&localMatrix)) { + return; + } + + GrAtlasManager* atlasManager = target->atlasManager(); + GrStrikeCache* glyphCache = target->glyphCache(); + + GrMaskFormat maskFormat = this->maskFormat(); + + unsigned int numActiveProxies; + const sk_sp* proxies = atlasManager->getProxies(maskFormat, &numActiveProxies); + if (!proxies) { + SkDebugf("Could not allocate backing texture for atlas\n"); + return; + } + SkASSERT(proxies[0]); + + static constexpr int kMaxTextures = GrBitmapTextGeoProc::kMaxTextures; + GR_STATIC_ASSERT(GrDistanceFieldA8TextGeoProc::kMaxTextures == kMaxTextures); + GR_STATIC_ASSERT(GrDistanceFieldLCDTextGeoProc::kMaxTextures == kMaxTextures); + + auto fixedDynamicState = target->makeFixedDynamicState(kMaxTextures); + for (unsigned i = 0; i < numActiveProxies; ++i) { + fixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get(); + // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the proxies + // don't get added during the visitProxies call. Thus we add them here. + target->sampledProxyArray()->push_back(proxies[i].get()); + } + + FlushInfo flushInfo; + flushInfo.fFixedDynamicState = fixedDynamicState; + + bool vmPerspective = fGeoData[0].fViewMatrix.hasPerspective(); + if (this->usesDistanceFields()) { + flushInfo.fGeometryProcessor = this->setupDfProcessor(*target->caps().shaderCaps(), + proxies, numActiveProxies); + } else { + GrSamplerState samplerState = fNeedsGlyphTransform ? GrSamplerState::ClampBilerp() + : GrSamplerState::ClampNearest(); + flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make( + *target->caps().shaderCaps(), this->color(), false, proxies, numActiveProxies, + samplerState, maskFormat, localMatrix, vmPerspective); + } + + flushInfo.fGlyphsToFlush = 0; + size_t vertexStride = flushInfo.fGeometryProcessor->vertexStride(); + + int glyphCount = this->numGlyphs(); + + void* vertices = target->makeVertexSpace(vertexStride, glyphCount * kVerticesPerGlyph, + &flushInfo.fVertexBuffer, &flushInfo.fVertexOffset); + flushInfo.fIndexBuffer = resourceProvider->refQuadIndexBuffer(); + if (!vertices || !flushInfo.fVertexBuffer) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + char* currVertex = reinterpret_cast(vertices); + + SkExclusiveStrikePtr autoGlyphCache; + // each of these is a SubRun + for (int i = 0; i < fGeoCount; i++) { + const Geometry& args = fGeoData[i]; + Blob* blob = args.fBlob; + // TODO4F: Preserve float colors + GrTextBlob::VertexRegenerator regenerator( + resourceProvider, blob, args.fRun, args.fSubRun, args.fViewMatrix, args.fX, args.fY, + args.fColor.toBytes_RGBA(), target->deferredUploadTarget(), glyphCache, + atlasManager, &autoGlyphCache); + bool done = false; + while (!done) { + GrTextBlob::VertexRegenerator::Result result; + if (!regenerator.regenerate(&result)) { + break; + } + done = result.fFinished; + + // Copy regenerated vertices from the blob to our vertex buffer. + size_t vertexBytes = result.fGlyphsRegenerated * kVerticesPerGlyph * vertexStride; + if (args.fClipRect.isEmpty()) { + memcpy(currVertex, result.fFirstVertex, vertexBytes); + } else { + SkASSERT(!vmPerspective); + clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride, + result.fGlyphsRegenerated); + } + if (fNeedsGlyphTransform && !args.fViewMatrix.isIdentity()) { + // We always do the distance field view matrix transformation after copying rather + // than during blob vertex generation time in the blob as handling successive + // arbitrary transformations would be complicated and accumulate error. + if (args.fViewMatrix.hasPerspective()) { + auto* pos = reinterpret_cast(currVertex); + SkMatrixPriv::MapHomogeneousPointsWithStride( + args.fViewMatrix, pos, vertexStride, pos, vertexStride, + result.fGlyphsRegenerated * kVerticesPerGlyph); + } else { + auto* pos = reinterpret_cast(currVertex); + SkMatrixPriv::MapPointsWithStride( + args.fViewMatrix, pos, vertexStride, + result.fGlyphsRegenerated * kVerticesPerGlyph); + } + } + flushInfo.fGlyphsToFlush += result.fGlyphsRegenerated; + if (!result.fFinished) { + this->flush(target, &flushInfo); + } + currVertex += vertexBytes; + } + } + this->flush(target, &flushInfo); +} + +void GrAtlasTextOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + flushState->executeDrawsAndUploadsForMeshDrawOp( + this, chainBounds, std::move(fProcessors), GrPipeline::InputFlags::kNone); +} + +void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const { + if (!flushInfo->fGlyphsToFlush) { + return; + } + + auto atlasManager = target->atlasManager(); + + GrGeometryProcessor* gp = flushInfo->fGeometryProcessor.get(); + GrMaskFormat maskFormat = this->maskFormat(); + + unsigned int numActiveProxies; + const sk_sp* proxies = atlasManager->getProxies(maskFormat, &numActiveProxies); + SkASSERT(proxies); + if (gp->numTextureSamplers() != (int) numActiveProxies) { + // During preparation the number of atlas pages has increased. + // Update the proxies used in the GP to match. + for (unsigned i = gp->numTextureSamplers(); i < numActiveProxies; ++i) { + flushInfo->fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get(); + // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the + // proxies don't get added during the visitProxies call. Thus we add them here. + target->sampledProxyArray()->push_back(proxies[i].get()); + } + if (this->usesDistanceFields()) { + if (this->isLCD()) { + reinterpret_cast(gp)->addNewProxies( + proxies, numActiveProxies, GrSamplerState::ClampBilerp()); + } else { + reinterpret_cast(gp)->addNewProxies( + proxies, numActiveProxies, GrSamplerState::ClampBilerp()); + } + } else { + GrSamplerState samplerState = fNeedsGlyphTransform ? GrSamplerState::ClampBilerp() + : GrSamplerState::ClampNearest(); + reinterpret_cast(gp)->addNewProxies(proxies, numActiveProxies, + samplerState); + } + } + int maxGlyphsPerDraw = static_cast(flushInfo->fIndexBuffer->size() / sizeof(uint16_t) / 6); + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexedPatterned(flushInfo->fIndexBuffer, kIndicesPerGlyph, kVerticesPerGlyph, + flushInfo->fGlyphsToFlush, maxGlyphsPerDraw); + mesh->setVertexData(flushInfo->fVertexBuffer, flushInfo->fVertexOffset); + target->recordDraw( + flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fFixedDynamicState, nullptr); + flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush; + flushInfo->fGlyphsToFlush = 0; +} + +GrOp::CombineResult GrAtlasTextOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) { + GrAtlasTextOp* that = t->cast(); + if (fProcessors != that->fProcessors) { + return CombineResult::kCannotCombine; + } + + if (fMaskType != that->fMaskType) { + return CombineResult::kCannotCombine; + } + + const SkMatrix& thisFirstMatrix = fGeoData[0].fViewMatrix; + const SkMatrix& thatFirstMatrix = that->fGeoData[0].fViewMatrix; + + if (this->usesLocalCoords() && !thisFirstMatrix.cheapEqualTo(thatFirstMatrix)) { + return CombineResult::kCannotCombine; + } + + if (fNeedsGlyphTransform != that->fNeedsGlyphTransform) { + return CombineResult::kCannotCombine; + } + + if (fNeedsGlyphTransform && + (thisFirstMatrix.hasPerspective() != thatFirstMatrix.hasPerspective())) { + return CombineResult::kCannotCombine; + } + + if (this->usesDistanceFields()) { + if (fDFGPFlags != that->fDFGPFlags) { + return CombineResult::kCannotCombine; + } + + if (fLuminanceColor != that->fLuminanceColor) { + return CombineResult::kCannotCombine; + } + } else { + if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) { + return CombineResult::kCannotCombine; + } + } + + // Keep the batch vertex buffer size below 32K so we don't have to create a special one + // We use the largest possible vertex size for this + static const int kVertexSize = sizeof(SkPoint) + sizeof(SkColor) + 2 * sizeof(uint16_t); + static const int kMaxGlyphs = 32768 / (kVerticesPerGlyph * kVertexSize); + if (this->fNumGlyphs + that->fNumGlyphs > kMaxGlyphs) { + return CombineResult::kCannotCombine; + } + + fNumGlyphs += that->numGlyphs(); + + // Reallocate space for geo data if necessary and then import that geo's data. + int newGeoCount = that->fGeoCount + fGeoCount; + + // We reallocate at a rate of 1.5x to try to get better total memory usage + if (newGeoCount > fGeoDataAllocSize) { + int newAllocSize = fGeoDataAllocSize + fGeoDataAllocSize / 2; + while (newAllocSize < newGeoCount) { + newAllocSize += newAllocSize / 2; + } + fGeoData.realloc(newAllocSize); + fGeoDataAllocSize = newAllocSize; + } + + // We steal the ref on the blobs from the other AtlasTextOp and set its count to 0 so that + // it doesn't try to unref them. + memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry)); +#ifdef SK_DEBUG + for (int i = 0; i < that->fGeoCount; ++i) { + that->fGeoData.get()[i].fBlob = (Blob*)0x1; + } +#endif + that->fGeoCount = 0; + fGeoCount = newGeoCount; + + return CombineResult::kMerged; +} + +// TODO trying to figure out why lcd is so whack +// (see comments in GrTextContext::ComputeCanonicalColor) +sk_sp GrAtlasTextOp::setupDfProcessor(const GrShaderCaps& caps, + const sk_sp* proxies, + unsigned int numActiveProxies) const { + bool isLCD = this->isLCD(); + + SkMatrix localMatrix = SkMatrix::I(); + if (this->usesLocalCoords()) { + // If this fails we'll just use I(). + bool result = fGeoData[0].fViewMatrix.invert(&localMatrix); + (void)result; + } + + // see if we need to create a new effect + if (isLCD) { + float redCorrection = fDistanceAdjustTable->getAdjustment( + SkColorGetR(fLuminanceColor) >> kDistanceAdjustLumShift, + fUseGammaCorrectDistanceTable); + float greenCorrection = fDistanceAdjustTable->getAdjustment( + SkColorGetG(fLuminanceColor) >> kDistanceAdjustLumShift, + fUseGammaCorrectDistanceTable); + float blueCorrection = fDistanceAdjustTable->getAdjustment( + SkColorGetB(fLuminanceColor) >> kDistanceAdjustLumShift, + fUseGammaCorrectDistanceTable); + GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust = + GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make( + redCorrection, greenCorrection, blueCorrection); + return GrDistanceFieldLCDTextGeoProc::Make(caps, proxies, numActiveProxies, + GrSamplerState::ClampBilerp(), widthAdjust, + fDFGPFlags, localMatrix); + } else { +#ifdef SK_GAMMA_APPLY_TO_A8 + float correction = 0; + if (kAliasedDistanceField_MaskType != fMaskType) { + U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, + fLuminanceColor); + correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift, + fUseGammaCorrectDistanceTable); + } + return GrDistanceFieldA8TextGeoProc::Make(caps, proxies, numActiveProxies, + GrSamplerState::ClampBilerp(), + correction, fDFGPFlags, localMatrix); +#else + return GrDistanceFieldA8TextGeoProc::Make(caps, proxies, numActiveProxies, + GrSamplerState::ClampBilerp(), + fDFGPFlags, localMatrix); +#endif + } +} + diff --git a/gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.h b/gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.h new file mode 100644 index 0000000000..6b821e605e --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrAtlasTextOp.h @@ -0,0 +1,179 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAtlasTextOp_DEFINED +#define GrAtlasTextOp_DEFINED + +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/text/GrDistanceFieldAdjustTable.h" +#include "src/gpu/text/GrTextBlob.h" + +class GrRecordingContext; +class SkAtlasTextTarget; + +class GrAtlasTextOp final : public GrMeshDrawOp { +public: + DEFINE_OP_CLASS_ID + + ~GrAtlasTextOp() override { + for (int i = 0; i < fGeoCount; i++) { + fGeoData[i].fBlob->unref(); + } + } + + static const int kVerticesPerGlyph = GrTextBlob::kVerticesPerGlyph; + static const int kIndicesPerGlyph = 6; + + typedef GrTextBlob Blob; + struct Geometry { + SkMatrix fViewMatrix; + SkIRect fClipRect; + Blob* fBlob; + SkScalar fX; + SkScalar fY; + uint16_t fRun; + uint16_t fSubRun; + SkPMColor4f fColor; + }; + + static std::unique_ptr MakeBitmap(GrRecordingContext*, + GrPaint&&, + GrMaskFormat, + int glyphCount, + bool needsTransform); + + static std::unique_ptr MakeDistanceField( + GrRecordingContext*, + GrPaint&&, + int glyphCount, + const GrDistanceFieldAdjustTable*, + bool useGammaCorrectDistanceTable, + SkColor luminanceColor, + const SkSurfaceProps&, + bool isAntiAliased, + bool useLCD); + + // To avoid even the initial copy of the struct, we have a getter for the first item which + // is used to seed the op with its initial geometry. After seeding, the client should call + // init() so the op can initialize itself + Geometry& geometry() { return fGeoData[0]; } + + /** Called after this->geometry() has been configured. */ + void init(); + + const char* name() const override { return "AtlasTextOp"; } + + void visitProxies(const VisitProxyFunc& func) const override; + +#ifdef SK_DEBUG + SkString dumpInfo() const override; +#endif + + FixedFunctionFlags fixedFunctionFlags() const override; + + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override; + + enum MaskType { + kGrayscaleCoverageMask_MaskType, + kLCDCoverageMask_MaskType, + kColorBitmapMask_MaskType, + kAliasedDistanceField_MaskType, + kGrayscaleDistanceField_MaskType, + kLCDDistanceField_MaskType, + kLCDBGRDistanceField_MaskType, + }; + + MaskType maskType() const { return fMaskType; } + + void finalizeForTextTarget(uint32_t color, const GrCaps&); + void executeForTextTarget(SkAtlasTextTarget*); + +private: + friend class GrOpMemoryPool; // for ctor + + // The minimum number of Geometry we will try to allocate. + static constexpr auto kMinGeometryAllocated = 12; + + GrAtlasTextOp(GrPaint&& paint) + : INHERITED(ClassID()) + , fGeoDataAllocSize(kMinGeometryAllocated) + , fProcessors(std::move(paint)) {} + + struct FlushInfo { + sk_sp fVertexBuffer; + sk_sp fIndexBuffer; + sk_sp fGeometryProcessor; + GrPipeline::FixedDynamicState* fFixedDynamicState; + int fGlyphsToFlush; + int fVertexOffset; + }; + + void onPrepareDraws(Target*) override; + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + GrMaskFormat maskFormat() const { + switch (fMaskType) { + case kLCDCoverageMask_MaskType: + return kA565_GrMaskFormat; + case kColorBitmapMask_MaskType: + return kARGB_GrMaskFormat; + case kGrayscaleCoverageMask_MaskType: + case kAliasedDistanceField_MaskType: + case kGrayscaleDistanceField_MaskType: + case kLCDDistanceField_MaskType: + case kLCDBGRDistanceField_MaskType: + return kA8_GrMaskFormat; + } + return kA8_GrMaskFormat; // suppress warning + } + + bool usesDistanceFields() const { + return kAliasedDistanceField_MaskType == fMaskType || + kGrayscaleDistanceField_MaskType == fMaskType || + kLCDDistanceField_MaskType == fMaskType || + kLCDBGRDistanceField_MaskType == fMaskType; + } + + bool isLCD() const { + return kLCDCoverageMask_MaskType == fMaskType || + kLCDDistanceField_MaskType == fMaskType || + kLCDBGRDistanceField_MaskType == fMaskType; + } + + inline void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const; + + const SkPMColor4f& color() const { SkASSERT(fGeoCount > 0); return fGeoData[0].fColor; } + bool usesLocalCoords() const { return fUsesLocalCoords; } + int numGlyphs() const { return fNumGlyphs; } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override; + + sk_sp setupDfProcessor(const GrShaderCaps& caps, + const sk_sp* proxies, + unsigned int numActiveProxies) const; + + SkAutoSTMalloc fGeoData; + int fGeoDataAllocSize; + GrProcessorSet fProcessors; + struct { + uint32_t fUsesLocalCoords : 1; + uint32_t fUseGammaCorrectDistanceTable : 1; + uint32_t fNeedsGlyphTransform : 1; + }; + int fGeoCount; + int fNumGlyphs; + MaskType fMaskType; + // Distance field properties + sk_sp fDistanceAdjustTable; + SkColor fLuminanceColor; + uint32_t fDFGPFlags = 0; + + typedef GrMeshDrawOp INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrClearOp.cpp b/gfx/skia/skia/src/gpu/ops/GrClearOp.cpp new file mode 100644 index 0000000000..b6dcab3570 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrClearOp.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrClearOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" + +std::unique_ptr GrClearOp::Make(GrRecordingContext* context, + const GrFixedClip& clip, + const SkPMColor4f& color, + GrSurfaceProxy* dstProxy) { + const SkIRect rect = SkIRect::MakeWH(dstProxy->width(), dstProxy->height()); + if (clip.scissorEnabled() && !SkIRect::Intersects(clip.scissorRect(), rect)) { + return nullptr; + } + + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(clip, color, dstProxy); +} + +std::unique_ptr GrClearOp::Make(GrRecordingContext* context, + const SkIRect& rect, + const SkPMColor4f& color, + bool fullScreen) { + SkASSERT(fullScreen || !rect.isEmpty()); + + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(rect, color, fullScreen); +} + +GrClearOp::GrClearOp(const GrFixedClip& clip, const SkPMColor4f& color, GrSurfaceProxy* proxy) + : INHERITED(ClassID()) + , fClip(clip) + , fColor(color) { + const SkIRect rtRect = SkIRect::MakeWH(proxy->width(), proxy->height()); + if (fClip.scissorEnabled()) { + // Don't let scissors extend outside the RT. This may improve op combining. + if (!fClip.intersect(rtRect)) { + SkASSERT(0); // should be caught upstream + fClip = GrFixedClip(SkIRect::MakeEmpty()); + } + + if (GrProxyProvider::IsFunctionallyExact(proxy) && fClip.scissorRect() == rtRect) { + fClip.disableScissor(); + } + } + this->setBounds(SkRect::Make(fClip.scissorEnabled() ? fClip.scissorRect() : rtRect), + HasAABloat::kNo, IsHairline::kNo); +} + +void GrClearOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { + SkASSERT(state->opsRenderPass()); + state->opsRenderPass()->clear(fClip, fColor); +} diff --git a/gfx/skia/skia/src/gpu/ops/GrClearOp.h b/gfx/skia/skia/src/gpu/ops/GrClearOp.h new file mode 100644 index 0000000000..c64bcc7157 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrClearOp.h @@ -0,0 +1,103 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrClearOp_DEFINED +#define GrClearOp_DEFINED + +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/ops/GrOp.h" + +class GrOpFlushState; +class GrRecordingContext; + +class GrClearOp final : public GrOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + const GrFixedClip& clip, + const SkPMColor4f& color, + GrSurfaceProxy* dstProxy); + + static std::unique_ptr Make(GrRecordingContext* context, + const SkIRect& rect, + const SkPMColor4f& color, + bool fullScreen); + + const char* name() const override { return "Clear"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.append(INHERITED::dumpInfo()); + string.appendf("Scissor [ "); + if (fClip.scissorEnabled()) { + const SkIRect& r = fClip.scissorRect(); + string.appendf("L: %d, T: %d, R: %d, B: %d", r.fLeft, r.fTop, r.fRight, r.fBottom); + } else { + string.append("disabled"); + } + string.appendf("], Color: 0x%08x\n", fColor.toBytes_RGBA()); + return string; + } +#endif + + const SkPMColor4f& color() const { return fColor; } + void setColor(const SkPMColor4f& color) { fColor = color; } + +private: + friend class GrOpMemoryPool; // for ctors + + GrClearOp(const GrFixedClip& clip, const SkPMColor4f& color, GrSurfaceProxy* proxy); + + GrClearOp(const SkIRect& rect, const SkPMColor4f& color, bool fullScreen) + : INHERITED(ClassID()) + , fClip(GrFixedClip(rect)) + , fColor(color) { + + if (fullScreen) { + fClip.disableScissor(); + } + this->setBounds(SkRect::Make(rect), HasAABloat::kNo, IsHairline::kNo); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + // This could be much more complicated. Currently we look at cases where the new clear + // contains the old clear, or when the new clear is a subset of the old clear and is the + // same color. + GrClearOp* cb = t->cast(); + if (fClip.windowRectsState() != cb->fClip.windowRectsState()) { + return CombineResult::kCannotCombine; + } + if (cb->contains(this)) { + fClip = cb->fClip; + fColor = cb->fColor; + return CombineResult::kMerged; + } else if (cb->fColor == fColor && this->contains(cb)) { + return CombineResult::kMerged; + } + return CombineResult::kCannotCombine; + } + + bool contains(const GrClearOp* that) const { + // The constructor ensures that scissor gets disabled on any clip that fills the entire RT. + return !fClip.scissorEnabled() || + (that->fClip.scissorEnabled() && + fClip.scissorRect().contains(that->fClip.scissorRect())); + } + + void onPrepare(GrOpFlushState*) override {} + + void onExecute(GrOpFlushState* state, const SkRect& chainBounds) override; + + GrFixedClip fClip; + SkPMColor4f fColor; + + typedef GrOp INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.cpp b/gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.cpp new file mode 100644 index 0000000000..7ca4063b96 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrClearStencilClipOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrRecordingContextPriv.h" + +std::unique_ptr GrClearStencilClipOp::Make(GrRecordingContext* context, + const GrFixedClip& clip, + bool insideStencilMask, + GrRenderTargetProxy* proxy) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(clip, insideStencilMask, proxy); +} + +void GrClearStencilClipOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { + SkASSERT(state->opsRenderPass()); + state->opsRenderPass()->clearStencilClip(fClip, fInsideStencilMask); +} diff --git a/gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.h b/gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.h new file mode 100644 index 0000000000..0a4a7403f6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrClearStencilClipOp.h @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrClearStencilClipOp_DEFINED +#define GrClearStencilClipOp_DEFINED + +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/ops/GrOp.h" + +class GrOpFlushState; +class GrRecordingContext; + +class GrClearStencilClipOp final : public GrOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + const GrFixedClip& clip, + bool insideStencilMask, + GrRenderTargetProxy* proxy); + + const char* name() const override { return "ClearStencilClip"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string("Scissor ["); + if (fClip.scissorEnabled()) { + const SkIRect& r = fClip.scissorRect(); + string.appendf("L: %d, T: %d, R: %d, B: %d", r.fLeft, r.fTop, r.fRight, r.fBottom); + } else { + string.append("disabled"); + } + string.appendf("], insideMask: %s\n", fInsideStencilMask ? "true" : "false"); + string.append(INHERITED::dumpInfo()); + return string; + } +#endif + +private: + friend class GrOpMemoryPool; // for ctor + + GrClearStencilClipOp(const GrFixedClip& clip, bool insideStencilMask, + GrRenderTargetProxy* proxy) + : INHERITED(ClassID()) + , fClip(clip) + , fInsideStencilMask(insideStencilMask) { + const SkRect& bounds = fClip.scissorEnabled() + ? SkRect::Make(fClip.scissorRect()) + : SkRect::MakeIWH(proxy->width(), proxy->height()); + this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo); + } + + void onPrepare(GrOpFlushState*) override {} + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + const GrFixedClip fClip; + const bool fInsideStencilMask; + + typedef GrOp INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.cpp new file mode 100644 index 0000000000..f15309ccb0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrDashLinePathRenderer.h" +#include "src/gpu/ops/GrDashOp.h" +#include "src/gpu/ops/GrMeshDrawOp.h" + +GrPathRenderer::CanDrawPath +GrDashLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + SkPoint pts[2]; + bool inverted; + if (args.fShape->style().isDashed() && args.fShape->asLine(pts, &inverted)) { + // We should never have an inverse dashed case. + SkASSERT(!inverted); + if (!GrDashOp::CanDrawDashLine(pts, args.fShape->style(), *args.fViewMatrix)) { + return CanDrawPath::kNo; + } + return CanDrawPath::kYes; + } + return CanDrawPath::kNo; +} + +bool GrDashLinePathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrDashLinePathRenderer::onDrawPath"); + GrDashOp::AAMode aaMode; + switch (args.fAAType) { + case GrAAType::kNone: + aaMode = GrDashOp::AAMode::kNone; + break; + case GrAAType::kMSAA: + // In this mode we will use aa between dashes but the outer border uses MSAA. Otherwise, + // we can wind up with external edges antialiased and internal edges unantialiased. + aaMode = GrDashOp::AAMode::kCoverageWithMSAA; + break; + case GrAAType::kCoverage: + aaMode = GrDashOp::AAMode::kCoverage; + break; + } + SkPoint pts[2]; + SkAssertResult(args.fShape->asLine(pts, nullptr)); + std::unique_ptr op = + GrDashOp::MakeDashLineOp(args.fContext, std::move(args.fPaint), *args.fViewMatrix, pts, + aaMode, args.fShape->style(), args.fUserStencilSettings); + if (!op) { + return false; + } + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + return true; +} diff --git a/gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.h new file mode 100644 index 0000000000..44342420ee --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDashLinePathRenderer.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDashLinePathRenderer_DEFINED +#define GrDashLinePathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +class GrGpu; + +class GrDashLinePathRenderer : public GrPathRenderer { +private: + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + StencilSupport onGetStencilSupport(const GrShape&) const override { + return kNoSupport_StencilSupport; + } + + bool onDrawPath(const DrawPathArgs&) override; + + sk_sp fGpu; + typedef GrPathRenderer INHERITED; +}; + + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDashOp.cpp b/gfx/skia/skia/src/gpu/ops/GrDashOp.cpp new file mode 100644 index 0000000000..ceba8e6d06 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDashOp.cpp @@ -0,0 +1,1279 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkPointPriv.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" +#include "src/gpu/ops/GrDashOp.h" +#include "src/gpu/ops/GrMeshDrawOp.h" + +using AAMode = GrDashOp::AAMode; + +/////////////////////////////////////////////////////////////////////////////// + +// Returns whether or not the gpu can fast path the dash line effect. +bool GrDashOp::CanDrawDashLine(const SkPoint pts[2], const GrStyle& style, + const SkMatrix& viewMatrix) { + // Pts must be either horizontal or vertical in src space + if (pts[0].fX != pts[1].fX && pts[0].fY != pts[1].fY) { + return false; + } + + // May be able to relax this to include skew. As of now cannot do perspective + // because of the non uniform scaling of bloating a rect + if (!viewMatrix.preservesRightAngles()) { + return false; + } + + if (!style.isDashed() || 2 != style.dashIntervalCnt()) { + return false; + } + + const SkScalar* intervals = style.dashIntervals(); + if (0 == intervals[0] && 0 == intervals[1]) { + return false; + } + + SkPaint::Cap cap = style.strokeRec().getCap(); + if (SkPaint::kRound_Cap == cap) { + // Current we don't support round caps unless the on interval is zero + if (intervals[0] != 0.f) { + return false; + } + // If the width of the circle caps in greater than the off interval we will pick up unwanted + // segments of circles at the start and end of the dash line. + if (style.strokeRec().getWidth() > intervals[1]) { + return false; + } + } + + return true; +} + +static void calc_dash_scaling(SkScalar* parallelScale, SkScalar* perpScale, + const SkMatrix& viewMatrix, const SkPoint pts[2]) { + SkVector vecSrc = pts[1] - pts[0]; + if (pts[1] == pts[0]) { + vecSrc.set(1.0, 0.0); + } + SkScalar magSrc = vecSrc.length(); + SkScalar invSrc = magSrc ? SkScalarInvert(magSrc) : 0; + vecSrc.scale(invSrc); + + SkVector vecSrcPerp; + SkPointPriv::RotateCW(vecSrc, &vecSrcPerp); + viewMatrix.mapVectors(&vecSrc, 1); + viewMatrix.mapVectors(&vecSrcPerp, 1); + + // parallelScale tells how much to scale along the line parallel to the dash line + // perpScale tells how much to scale in the direction perpendicular to the dash line + *parallelScale = vecSrc.length(); + *perpScale = vecSrcPerp.length(); +} + +// calculates the rotation needed to aligned pts to the x axis with pts[0] < pts[1] +// Stores the rotation matrix in rotMatrix, and the mapped points in ptsRot +static void align_to_x_axis(const SkPoint pts[2], SkMatrix* rotMatrix, SkPoint ptsRot[2] = nullptr) { + SkVector vec = pts[1] - pts[0]; + if (pts[1] == pts[0]) { + vec.set(1.0, 0.0); + } + SkScalar mag = vec.length(); + SkScalar inv = mag ? SkScalarInvert(mag) : 0; + + vec.scale(inv); + rotMatrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); + if (ptsRot) { + rotMatrix->mapPoints(ptsRot, pts, 2); + // correction for numerical issues if map doesn't make ptsRot exactly horizontal + ptsRot[1].fY = pts[0].fY; + } +} + +// Assumes phase < sum of all intervals +static SkScalar calc_start_adjustment(const SkScalar intervals[2], SkScalar phase) { + SkASSERT(phase < intervals[0] + intervals[1]); + if (phase >= intervals[0] && phase != 0) { + SkScalar srcIntervalLen = intervals[0] + intervals[1]; + return srcIntervalLen - phase; + } + return 0; +} + +static SkScalar calc_end_adjustment(const SkScalar intervals[2], const SkPoint pts[2], + SkScalar phase, SkScalar* endingInt) { + if (pts[1].fX <= pts[0].fX) { + return 0; + } + SkScalar srcIntervalLen = intervals[0] + intervals[1]; + SkScalar totalLen = pts[1].fX - pts[0].fX; + SkScalar temp = totalLen / srcIntervalLen; + SkScalar numFullIntervals = SkScalarFloorToScalar(temp); + *endingInt = totalLen - numFullIntervals * srcIntervalLen + phase; + temp = *endingInt / srcIntervalLen; + *endingInt = *endingInt - SkScalarFloorToScalar(temp) * srcIntervalLen; + if (0 == *endingInt) { + *endingInt = srcIntervalLen; + } + if (*endingInt > intervals[0]) { + return *endingInt - intervals[0]; + } + return 0; +} + +enum DashCap { + kRound_DashCap, + kNonRound_DashCap, +}; + +static void setup_dashed_rect(const SkRect& rect, GrVertexWriter& vertices, const SkMatrix& matrix, + SkScalar offset, SkScalar bloatX, SkScalar bloatY, SkScalar len, + SkScalar stroke, SkScalar startInterval, SkScalar endInterval, + SkScalar strokeWidth, DashCap cap) { + SkScalar intervalLength = startInterval + endInterval; + SkRect dashRect = { offset - bloatX, -stroke - bloatY, + offset + len + bloatX, stroke + bloatY }; + + if (kRound_DashCap == cap) { + SkScalar radius = SkScalarHalf(strokeWidth) - 0.5f; + SkScalar centerX = SkScalarHalf(endInterval); + + vertices.writeQuad(GrQuad::MakeFromRect(rect, matrix), + GrVertexWriter::TriStripFromRect(dashRect), + intervalLength, + radius, + centerX); + } else { + SkASSERT(kNonRound_DashCap == cap); + SkScalar halfOffLen = SkScalarHalf(endInterval); + SkScalar halfStroke = SkScalarHalf(strokeWidth); + SkRect rectParam; + rectParam.setLTRB(halfOffLen + 0.5f, -halfStroke + 0.5f, + halfOffLen + startInterval - 0.5f, halfStroke - 0.5f); + + vertices.writeQuad(GrQuad::MakeFromRect(rect, matrix), + GrVertexWriter::TriStripFromRect(dashRect), + intervalLength, + rectParam); + } +} + +/** + * An GrGeometryProcessor that renders a dashed line. + * This GrGeometryProcessor is meant for dashed lines that only have a single on/off interval pair. + * Bounding geometry is rendered and the effect computes coverage based on the fragment's + * position relative to the dashed line. + */ +static sk_sp make_dash_gp(const SkPMColor4f&, + AAMode aaMode, + DashCap cap, + const SkMatrix& localMatrix, + bool usesLocalCoords); + +class DashOp final : public GrMeshDrawOp { +public: + DEFINE_OP_CLASS_ID + + struct LineData { + SkMatrix fViewMatrix; + SkMatrix fSrcRotInv; + SkPoint fPtsRot[2]; + SkScalar fSrcStrokeWidth; + SkScalar fPhase; + SkScalar fIntervals[2]; + SkScalar fParallelScale; + SkScalar fPerpendicularScale; + }; + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const LineData& geometry, + SkPaint::Cap cap, + AAMode aaMode, bool fullDash, + const GrUserStencilSettings* stencilSettings) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(std::move(paint), geometry, cap, + aaMode, fullDash, stencilSettings); + } + + const char* name() const override { return "DashOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fProcessorSet.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (const auto& geo : fLines) { + string.appendf("Pt0: [%.2f, %.2f], Pt1: [%.2f, %.2f], Width: %.2f, Ival0: %.2f, " + "Ival1 : %.2f, Phase: %.2f\n", + geo.fPtsRot[0].fX, geo.fPtsRot[0].fY, + geo.fPtsRot[1].fX, geo.fPtsRot[1].fY, + geo.fSrcStrokeWidth, + geo.fIntervals[0], + geo.fIntervals[1], + geo.fPhase); + } + string += fProcessorSet.dumpProcessors(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { + FixedFunctionFlags flags = FixedFunctionFlags::kNone; + if (AAMode::kCoverageWithMSAA == fAAMode) { + flags |= FixedFunctionFlags::kUsesHWAA; + } + if (fStencilSettings != &GrUserStencilSettings::kUnused) { + flags |= FixedFunctionFlags::kUsesStencil; + } + return flags; + } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + GrProcessorAnalysisCoverage coverage; + if (AAMode::kNone == fAAMode && !clip->numClipCoverageFragmentProcessors()) { + coverage = GrProcessorAnalysisCoverage::kNone; + } else { + coverage = GrProcessorAnalysisCoverage::kSingleChannel; + } + auto analysis = fProcessorSet.finalize( + fColor, coverage, clip, fStencilSettings, hasMixedSampledCoverage, caps, clampType, + &fColor); + fUsesLocalCoords = analysis.usesLocalCoords(); + return analysis; + } + +private: + friend class GrOpMemoryPool; // for ctor + + DashOp(GrPaint&& paint, const LineData& geometry, SkPaint::Cap cap, AAMode aaMode, + bool fullDash, const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()) + , fColor(paint.getColor4f()) + , fFullDash(fullDash) + , fCap(cap) + , fAAMode(aaMode) + , fProcessorSet(std::move(paint)) + , fStencilSettings(stencilSettings) { + fLines.push_back(geometry); + + // compute bounds + SkScalar halfStrokeWidth = 0.5f * geometry.fSrcStrokeWidth; + SkScalar xBloat = SkPaint::kButt_Cap == cap ? 0 : halfStrokeWidth; + SkRect bounds; + bounds.set(geometry.fPtsRot[0], geometry.fPtsRot[1]); + bounds.outset(xBloat, halfStrokeWidth); + + // Note, we actually create the combined matrix here, and save the work + SkMatrix& combinedMatrix = fLines[0].fSrcRotInv; + combinedMatrix.postConcat(geometry.fViewMatrix); + + IsHairline zeroArea = geometry.fSrcStrokeWidth ? IsHairline::kNo + : IsHairline::kYes; + HasAABloat aaBloat = (aaMode == AAMode::kNone) ? HasAABloat ::kNo : HasAABloat::kYes; + this->setTransformedBounds(bounds, combinedMatrix, aaBloat, zeroArea); + } + + struct DashDraw { + DashDraw(const LineData& geo) { + memcpy(fPtsRot, geo.fPtsRot, sizeof(geo.fPtsRot)); + memcpy(fIntervals, geo.fIntervals, sizeof(geo.fIntervals)); + fPhase = geo.fPhase; + } + SkPoint fPtsRot[2]; + SkScalar fIntervals[2]; + SkScalar fPhase; + SkScalar fStartOffset; + SkScalar fStrokeWidth; + SkScalar fLineLength; + SkScalar fHalfDevStroke; + SkScalar fDevBloatX; + SkScalar fDevBloatY; + bool fLineDone; + bool fHasStartRect; + bool fHasEndRect; + }; + + void onPrepareDraws(Target* target) override { + int instanceCount = fLines.count(); + SkPaint::Cap cap = this->cap(); + bool isRoundCap = SkPaint::kRound_Cap == cap; + DashCap capType = isRoundCap ? kRound_DashCap : kNonRound_DashCap; + + sk_sp gp; + if (this->fullDash()) { + gp = make_dash_gp(this->color(), this->aaMode(), capType, this->viewMatrix(), + fUsesLocalCoords); + } else { + // Set up the vertex data for the line and start/end dashes + using namespace GrDefaultGeoProcFactory; + Color color(this->color()); + LocalCoords::Type localCoordsType = + fUsesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type; + gp = MakeForDeviceSpace(target->caps().shaderCaps(), + color, + Coverage::kSolid_Type, + localCoordsType, + this->viewMatrix()); + } + + if (!gp) { + SkDebugf("Could not create GrGeometryProcessor\n"); + return; + } + + // useAA here means Edge AA or MSAA + bool useAA = this->aaMode() != AAMode::kNone; + bool fullDash = this->fullDash(); + + // We do two passes over all of the dashes. First we setup the start, end, and bounds, + // rectangles. We preserve all of this work in the rects / draws arrays below. Then we + // iterate again over these decomposed dashes to generate vertices + static const int kNumStackDashes = 128; + SkSTArray rects; + SkSTArray draws; + + int totalRectCount = 0; + int rectOffset = 0; + rects.push_back_n(3 * instanceCount); + for (int i = 0; i < instanceCount; i++) { + const LineData& args = fLines[i]; + + DashDraw& draw = draws.push_back(args); + + bool hasCap = SkPaint::kButt_Cap != cap; + + // We always want to at least stroke out half a pixel on each side in device space + // so 0.5f / perpScale gives us this min in src space + SkScalar halfSrcStroke = + SkMaxScalar(args.fSrcStrokeWidth * 0.5f, 0.5f / args.fPerpendicularScale); + + SkScalar strokeAdj; + if (!hasCap) { + strokeAdj = 0.f; + } else { + strokeAdj = halfSrcStroke; + } + + SkScalar startAdj = 0; + + bool lineDone = false; + + // Too simplify the algorithm, we always push back rects for start and end rect. + // Otherwise we'd have to track start / end rects for each individual geometry + SkRect& bounds = rects[rectOffset++]; + SkRect& startRect = rects[rectOffset++]; + SkRect& endRect = rects[rectOffset++]; + + bool hasStartRect = false; + // If we are using AA, check to see if we are drawing a partial dash at the start. If so + // draw it separately here and adjust our start point accordingly + if (useAA) { + if (draw.fPhase > 0 && draw.fPhase < draw.fIntervals[0]) { + SkPoint startPts[2]; + startPts[0] = draw.fPtsRot[0]; + startPts[1].fY = startPts[0].fY; + startPts[1].fX = SkMinScalar(startPts[0].fX + draw.fIntervals[0] - draw.fPhase, + draw.fPtsRot[1].fX); + startRect.setBounds(startPts, 2); + startRect.outset(strokeAdj, halfSrcStroke); + + hasStartRect = true; + startAdj = draw.fIntervals[0] + draw.fIntervals[1] - draw.fPhase; + } + } + + // adjustments for start and end of bounding rect so we only draw dash intervals + // contained in the original line segment. + startAdj += calc_start_adjustment(draw.fIntervals, draw.fPhase); + if (startAdj != 0) { + draw.fPtsRot[0].fX += startAdj; + draw.fPhase = 0; + } + SkScalar endingInterval = 0; + SkScalar endAdj = calc_end_adjustment(draw.fIntervals, draw.fPtsRot, draw.fPhase, + &endingInterval); + draw.fPtsRot[1].fX -= endAdj; + if (draw.fPtsRot[0].fX >= draw.fPtsRot[1].fX) { + lineDone = true; + } + + bool hasEndRect = false; + // If we are using AA, check to see if we are drawing a partial dash at then end. If so + // draw it separately here and adjust our end point accordingly + if (useAA && !lineDone) { + // If we adjusted the end then we will not be drawing a partial dash at the end. + // If we didn't adjust the end point then we just need to make sure the ending + // dash isn't a full dash + if (0 == endAdj && endingInterval != draw.fIntervals[0]) { + SkPoint endPts[2]; + endPts[1] = draw.fPtsRot[1]; + endPts[0].fY = endPts[1].fY; + endPts[0].fX = endPts[1].fX - endingInterval; + + endRect.setBounds(endPts, 2); + endRect.outset(strokeAdj, halfSrcStroke); + + hasEndRect = true; + endAdj = endingInterval + draw.fIntervals[1]; + + draw.fPtsRot[1].fX -= endAdj; + if (draw.fPtsRot[0].fX >= draw.fPtsRot[1].fX) { + lineDone = true; + } + } + } + + if (draw.fPtsRot[0].fX == draw.fPtsRot[1].fX && + (0 != endAdj || 0 == startAdj) && + hasCap) { + // At this point the fPtsRot[0]/[1] represent the start and end of the inner rect of + // dashes that we want to draw. The only way they can be equal is if the on interval + // is zero (or an edge case if the end of line ends at a full off interval, but this + // is handled as well). Thus if the on interval is zero then we need to draw a cap + // at this position if the stroke has caps. The spec says we only draw this point if + // point lies between [start of line, end of line). Thus we check if we are at the + // end (but not the start), and if so we don't draw the cap. + lineDone = false; + } + + if (startAdj != 0) { + draw.fPhase = 0; + } + + // Change the dashing info from src space into device space + SkScalar* devIntervals = draw.fIntervals; + devIntervals[0] = draw.fIntervals[0] * args.fParallelScale; + devIntervals[1] = draw.fIntervals[1] * args.fParallelScale; + SkScalar devPhase = draw.fPhase * args.fParallelScale; + SkScalar strokeWidth = args.fSrcStrokeWidth * args.fPerpendicularScale; + + if ((strokeWidth < 1.f && useAA) || 0.f == strokeWidth) { + strokeWidth = 1.f; + } + + SkScalar halfDevStroke = strokeWidth * 0.5f; + + if (SkPaint::kSquare_Cap == cap) { + // add cap to on interval and remove from off interval + devIntervals[0] += strokeWidth; + devIntervals[1] -= strokeWidth; + } + SkScalar startOffset = devIntervals[1] * 0.5f + devPhase; + + // For EdgeAA, we bloat in X & Y for both square and round caps. + // For MSAA, we don't bloat at all for square caps, and bloat in Y only for round caps. + SkScalar devBloatX = this->aaMode() == AAMode::kCoverage ? 0.5f : 0.0f; + SkScalar devBloatY; + if (SkPaint::kRound_Cap == cap && this->aaMode() == AAMode::kCoverageWithMSAA) { + devBloatY = 0.5f; + } else { + devBloatY = devBloatX; + } + + SkScalar bloatX = devBloatX / args.fParallelScale; + SkScalar bloatY = devBloatY / args.fPerpendicularScale; + + if (devIntervals[1] <= 0.f && useAA) { + // Case when we end up drawing a solid AA rect + // Reset the start rect to draw this single solid rect + // but it requires to upload a new intervals uniform so we can mimic + // one giant dash + draw.fPtsRot[0].fX -= hasStartRect ? startAdj : 0; + draw.fPtsRot[1].fX += hasEndRect ? endAdj : 0; + startRect.setBounds(draw.fPtsRot, 2); + startRect.outset(strokeAdj, halfSrcStroke); + hasStartRect = true; + hasEndRect = false; + lineDone = true; + + SkPoint devicePts[2]; + args.fViewMatrix.mapPoints(devicePts, draw.fPtsRot, 2); + SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); + if (hasCap) { + lineLength += 2.f * halfDevStroke; + } + devIntervals[0] = lineLength; + } + + totalRectCount += !lineDone ? 1 : 0; + totalRectCount += hasStartRect ? 1 : 0; + totalRectCount += hasEndRect ? 1 : 0; + + if (SkPaint::kRound_Cap == cap && 0 != args.fSrcStrokeWidth) { + // need to adjust this for round caps to correctly set the dashPos attrib on + // vertices + startOffset -= halfDevStroke; + } + + if (!lineDone) { + SkPoint devicePts[2]; + args.fViewMatrix.mapPoints(devicePts, draw.fPtsRot, 2); + draw.fLineLength = SkPoint::Distance(devicePts[0], devicePts[1]); + if (hasCap) { + draw.fLineLength += 2.f * halfDevStroke; + } + + bounds.setLTRB(draw.fPtsRot[0].fX, draw.fPtsRot[0].fY, + draw.fPtsRot[1].fX, draw.fPtsRot[1].fY); + bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke); + } + + if (hasStartRect) { + SkASSERT(useAA); // so that we know bloatX and bloatY have been set + startRect.outset(bloatX, bloatY); + } + + if (hasEndRect) { + SkASSERT(useAA); // so that we know bloatX and bloatY have been set + endRect.outset(bloatX, bloatY); + } + + draw.fStartOffset = startOffset; + draw.fDevBloatX = devBloatX; + draw.fDevBloatY = devBloatY; + draw.fHalfDevStroke = halfDevStroke; + draw.fStrokeWidth = strokeWidth; + draw.fHasStartRect = hasStartRect; + draw.fLineDone = lineDone; + draw.fHasEndRect = hasEndRect; + } + + if (!totalRectCount) { + return; + } + + QuadHelper helper(target, gp->vertexStride(), totalRectCount); + GrVertexWriter vertices{ helper.vertices() }; + if (!vertices.fPtr) { + return; + } + + int rectIndex = 0; + for (int i = 0; i < instanceCount; i++) { + const LineData& geom = fLines[i]; + + if (!draws[i].fLineDone) { + if (fullDash) { + setup_dashed_rect( + rects[rectIndex], vertices, geom.fSrcRotInv, + draws[i].fStartOffset, draws[i].fDevBloatX, draws[i].fDevBloatY, + draws[i].fLineLength, draws[i].fHalfDevStroke, draws[i].fIntervals[0], + draws[i].fIntervals[1], draws[i].fStrokeWidth, capType); + } else { + vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv)); + } + } + rectIndex++; + + if (draws[i].fHasStartRect) { + if (fullDash) { + setup_dashed_rect( + rects[rectIndex], vertices, geom.fSrcRotInv, + draws[i].fStartOffset, draws[i].fDevBloatX, draws[i].fDevBloatY, + draws[i].fIntervals[0], draws[i].fHalfDevStroke, draws[i].fIntervals[0], + draws[i].fIntervals[1], draws[i].fStrokeWidth, capType); + } else { + vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv)); + } + } + rectIndex++; + + if (draws[i].fHasEndRect) { + if (fullDash) { + setup_dashed_rect( + rects[rectIndex], vertices, geom.fSrcRotInv, + draws[i].fStartOffset, draws[i].fDevBloatX, draws[i].fDevBloatY, + draws[i].fIntervals[0], draws[i].fHalfDevStroke, draws[i].fIntervals[0], + draws[i].fIntervals[1], draws[i].fStrokeWidth, capType); + } else { + vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv)); + } + } + rectIndex++; + } + helper.recordDraw(target, std::move(gp)); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + auto pipelineFlags = GrPipeline::InputFlags::kNone; + if (AAMode::kCoverageWithMSAA == fAAMode) { + pipelineFlags |= GrPipeline::InputFlags::kHWAntialias; + } + flushState->executeDrawsAndUploadsForMeshDrawOp( + this, chainBounds, std::move(fProcessorSet), pipelineFlags, fStencilSettings); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + DashOp* that = t->cast(); + if (fProcessorSet != that->fProcessorSet) { + return CombineResult::kCannotCombine; + } + + if (this->aaMode() != that->aaMode()) { + return CombineResult::kCannotCombine; + } + + if (this->fullDash() != that->fullDash()) { + return CombineResult::kCannotCombine; + } + + if (this->cap() != that->cap()) { + return CombineResult::kCannotCombine; + } + + // TODO vertex color + if (this->color() != that->color()) { + return CombineResult::kCannotCombine; + } + + if (fUsesLocalCoords && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + fLines.push_back_n(that->fLines.count(), that->fLines.begin()); + return CombineResult::kMerged; + } + + const SkPMColor4f& color() const { return fColor; } + const SkMatrix& viewMatrix() const { return fLines[0].fViewMatrix; } + AAMode aaMode() const { return fAAMode; } + bool fullDash() const { return fFullDash; } + SkPaint::Cap cap() const { return fCap; } + + static const int kVertsPerDash = 4; + static const int kIndicesPerDash = 6; + + SkSTArray<1, LineData, true> fLines; + SkPMColor4f fColor; + bool fUsesLocalCoords : 1; + bool fFullDash : 1; + // We use 3 bits for this 3-value enum because MSVS makes the underlying types signed. + SkPaint::Cap fCap : 3; + AAMode fAAMode; + GrProcessorSet fProcessorSet; + const GrUserStencilSettings* fStencilSettings; + + typedef GrMeshDrawOp INHERITED; +}; + +std::unique_ptr GrDashOp::MakeDashLineOp(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkPoint pts[2], + AAMode aaMode, + const GrStyle& style, + const GrUserStencilSettings* stencilSettings) { + SkASSERT(GrDashOp::CanDrawDashLine(pts, style, viewMatrix)); + const SkScalar* intervals = style.dashIntervals(); + SkScalar phase = style.dashPhase(); + + SkPaint::Cap cap = style.strokeRec().getCap(); + + DashOp::LineData lineData; + lineData.fSrcStrokeWidth = style.strokeRec().getWidth(); + + // the phase should be normalized to be [0, sum of all intervals) + SkASSERT(phase >= 0 && phase < intervals[0] + intervals[1]); + + // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[1].fX + if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) { + SkMatrix rotMatrix; + align_to_x_axis(pts, &rotMatrix, lineData.fPtsRot); + if (!rotMatrix.invert(&lineData.fSrcRotInv)) { + SkDebugf("Failed to create invertible rotation matrix!\n"); + return nullptr; + } + } else { + lineData.fSrcRotInv.reset(); + memcpy(lineData.fPtsRot, pts, 2 * sizeof(SkPoint)); + } + + // Scale corrections of intervals and stroke from view matrix + calc_dash_scaling(&lineData.fParallelScale, &lineData.fPerpendicularScale, viewMatrix, + lineData.fPtsRot); + if (SkScalarNearlyZero(lineData.fParallelScale) || + SkScalarNearlyZero(lineData.fPerpendicularScale)) { + return nullptr; + } + + SkScalar offInterval = intervals[1] * lineData.fParallelScale; + SkScalar strokeWidth = lineData.fSrcStrokeWidth * lineData.fPerpendicularScale; + + if (SkPaint::kSquare_Cap == cap && 0 != lineData.fSrcStrokeWidth) { + // add cap to on interveal and remove from off interval + offInterval -= strokeWidth; + } + + // TODO we can do a real rect call if not using fulldash(ie no off interval, not using AA) + bool fullDash = offInterval > 0.f || aaMode != AAMode::kNone; + + lineData.fViewMatrix = viewMatrix; + lineData.fPhase = phase; + lineData.fIntervals[0] = intervals[0]; + lineData.fIntervals[1] = intervals[1]; + + return DashOp::Make(context, std::move(paint), lineData, cap, aaMode, fullDash, + stencilSettings); +} + +////////////////////////////////////////////////////////////////////////////// + +class GLDashingCircleEffect; + +/* + * This effect will draw a dotted line (defined as a dashed lined with round caps and no on + * interval). The radius of the dots is given by the strokeWidth and the spacing by the DashInfo. + * Both of the previous two parameters are in device space. This effect also requires the setting of + * a float2 vertex attribute for the the four corners of the bounding rect. This attribute is the + * "dash position" of each vertex. In other words it is the vertex coords (in device space) if we + * transform the line to be horizontal, with the start of line at the origin then shifted to the + * right by half the off interval. The line then goes in the positive x direction. + */ +class DashingCircleEffect : public GrGeometryProcessor { +public: + typedef SkPathEffect::DashInfo DashInfo; + + static sk_sp Make(const SkPMColor4f&, + AAMode aaMode, + const SkMatrix& localMatrix, + bool usesLocalCoords); + + const char* name() const override { return "DashingCircleEffect"; } + + AAMode aaMode() const { return fAAMode; } + + const SkPMColor4f& color() const { return fColor; } + + const SkMatrix& localMatrix() const { return fLocalMatrix; } + + bool usesLocalCoords() const { return fUsesLocalCoords; } + + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + DashingCircleEffect(const SkPMColor4f&, AAMode aaMode, const SkMatrix& localMatrix, + bool usesLocalCoords); + + SkPMColor4f fColor; + SkMatrix fLocalMatrix; + bool fUsesLocalCoords; + AAMode fAAMode; + + Attribute fInPosition; + Attribute fInDashParams; + Attribute fInCircleParams; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + friend class GLDashingCircleEffect; + typedef GrGeometryProcessor INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +class GLDashingCircleEffect : public GrGLSLGeometryProcessor { +public: + GLDashingCircleEffect(); + + void onEmitCode(EmitArgs&, GrGPArgs*) override; + + static inline void GenKey(const GrGeometryProcessor&, + const GrShaderCaps&, + GrProcessorKeyBuilder*); + + void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&& transformIter) override; +private: + UniformHandle fParamUniform; + UniformHandle fColorUniform; + SkPMColor4f fColor; + SkScalar fPrevRadius; + SkScalar fPrevCenterX; + SkScalar fPrevIntervalLength; + typedef GrGLSLGeometryProcessor INHERITED; +}; + +GLDashingCircleEffect::GLDashingCircleEffect() { + fColor = SK_PMColor4fILLEGAL; + fPrevRadius = SK_ScalarMin; + fPrevCenterX = SK_ScalarMin; + fPrevIntervalLength = SK_ScalarMax; +} + +void GLDashingCircleEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + const DashingCircleEffect& dce = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(dce); + + // XY are dashPos, Z is dashInterval + GrGLSLVarying dashParams(kHalf3_GrSLType); + varyingHandler->addVarying("DashParam", &dashParams); + vertBuilder->codeAppendf("%s = %s;", dashParams.vsOut(), dce.fInDashParams.name()); + + // x refers to circle radius - 0.5, y refers to cicle's center x coord + GrGLSLVarying circleParams(kHalf2_GrSLType); + varyingHandler->addVarying("CircleParams", &circleParams); + vertBuilder->codeAppendf("%s = %s;", circleParams.vsOut(), dce.fInCircleParams.name()); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // Setup pass through color + this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform); + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, dce.fInPosition.name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + dce.fInPosition.asShaderVar(), + dce.localMatrix(), + args.fFPCoordTransformHandler); + + // transforms all points so that we can compare them to our test circle + fragBuilder->codeAppendf("half xShifted = half(%s.x - floor(%s.x / %s.z) * %s.z);", + dashParams.fsIn(), dashParams.fsIn(), dashParams.fsIn(), + dashParams.fsIn()); + fragBuilder->codeAppendf("half2 fragPosShifted = half2(xShifted, half(%s.y));", + dashParams.fsIn()); + fragBuilder->codeAppendf("half2 center = half2(%s.y, 0.0);", circleParams.fsIn()); + fragBuilder->codeAppend("half dist = length(center - fragPosShifted);"); + if (dce.aaMode() != AAMode::kNone) { + fragBuilder->codeAppendf("half diff = dist - %s.x;", circleParams.fsIn()); + fragBuilder->codeAppend("diff = 1.0 - diff;"); + fragBuilder->codeAppend("half alpha = saturate(diff);"); + } else { + fragBuilder->codeAppendf("half alpha = 1.0;"); + fragBuilder->codeAppendf("alpha *= dist < %s.x + 0.5 ? 1.0 : 0.0;", circleParams.fsIn()); + } + fragBuilder->codeAppendf("%s = half4(alpha);", args.fOutputCoverage); +} + +void GLDashingCircleEffect::setData(const GrGLSLProgramDataManager& pdman, + const GrPrimitiveProcessor& processor, + FPCoordTransformIter&& transformIter) { + const DashingCircleEffect& dce = processor.cast(); + if (dce.color() != fColor) { + pdman.set4fv(fColorUniform, 1, dce.color().vec()); + fColor = dce.color(); + } + this->setTransformDataHelper(dce.localMatrix(), pdman, &transformIter); +} + +void GLDashingCircleEffect::GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const DashingCircleEffect& dce = gp.cast(); + uint32_t key = 0; + key |= dce.usesLocalCoords() && dce.localMatrix().hasPerspective() ? 0x1 : 0x0; + key |= static_cast(dce.aaMode()) << 1; + b->add32(key); +} + +////////////////////////////////////////////////////////////////////////////// + +sk_sp DashingCircleEffect::Make(const SkPMColor4f& color, + AAMode aaMode, + const SkMatrix& localMatrix, + bool usesLocalCoords) { + return sk_sp( + new DashingCircleEffect(color, aaMode, localMatrix, usesLocalCoords)); +} + +void DashingCircleEffect::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLDashingCircleEffect::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* DashingCircleEffect::createGLSLInstance(const GrShaderCaps&) const { + return new GLDashingCircleEffect(); +} + +DashingCircleEffect::DashingCircleEffect(const SkPMColor4f& color, + AAMode aaMode, + const SkMatrix& localMatrix, + bool usesLocalCoords) + : INHERITED(kDashingCircleEffect_ClassID) + , fColor(color) + , fLocalMatrix(localMatrix) + , fUsesLocalCoords(usesLocalCoords) + , fAAMode(aaMode) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInDashParams = {"inDashParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; + fInCircleParams = {"inCircleParams", kFloat2_GrVertexAttribType, kHalf2_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); +} + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingCircleEffect); + +#if GR_TEST_UTILS +sk_sp DashingCircleEffect::TestCreate(GrProcessorTestData* d) { + AAMode aaMode = static_cast(d->fRandom->nextULessThan(GrDashOp::kAAModeCnt)); + return DashingCircleEffect::Make(SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)), + aaMode, GrTest::TestMatrix(d->fRandom), + d->fRandom->nextBool()); +} +#endif + +////////////////////////////////////////////////////////////////////////////// + +class GLDashingLineEffect; + +/* + * This effect will draw a dashed line. The width of the dash is given by the strokeWidth and the + * length and spacing by the DashInfo. Both of the previous two parameters are in device space. + * This effect also requires the setting of a float2 vertex attribute for the the four corners of the + * bounding rect. This attribute is the "dash position" of each vertex. In other words it is the + * vertex coords (in device space) if we transform the line to be horizontal, with the start of + * line at the origin then shifted to the right by half the off interval. The line then goes in the + * positive x direction. + */ +class DashingLineEffect : public GrGeometryProcessor { +public: + typedef SkPathEffect::DashInfo DashInfo; + + static sk_sp Make(const SkPMColor4f&, + AAMode aaMode, + const SkMatrix& localMatrix, + bool usesLocalCoords); + + const char* name() const override { return "DashingEffect"; } + + AAMode aaMode() const { return fAAMode; } + + const SkPMColor4f& color() const { return fColor; } + + const SkMatrix& localMatrix() const { return fLocalMatrix; } + + bool usesLocalCoords() const { return fUsesLocalCoords; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override; + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + DashingLineEffect(const SkPMColor4f&, AAMode aaMode, const SkMatrix& localMatrix, + bool usesLocalCoords); + + SkPMColor4f fColor; + SkMatrix fLocalMatrix; + bool fUsesLocalCoords; + AAMode fAAMode; + + Attribute fInPosition; + Attribute fInDashParams; + Attribute fInRect; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + friend class GLDashingLineEffect; + + typedef GrGeometryProcessor INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +class GLDashingLineEffect : public GrGLSLGeometryProcessor { +public: + GLDashingLineEffect(); + + void onEmitCode(EmitArgs&, GrGPArgs*) override; + + static inline void GenKey(const GrGeometryProcessor&, + const GrShaderCaps&, + GrProcessorKeyBuilder*); + + void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&, + FPCoordTransformIter&& iter) override; + +private: + SkPMColor4f fColor; + UniformHandle fColorUniform; + typedef GrGLSLGeometryProcessor INHERITED; +}; + +GLDashingLineEffect::GLDashingLineEffect() : fColor(SK_PMColor4fILLEGAL) {} + +void GLDashingLineEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + const DashingLineEffect& de = args.fGP.cast(); + + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(de); + + // XY refers to dashPos, Z is the dash interval length + GrGLSLVarying inDashParams(kFloat3_GrSLType); + varyingHandler->addVarying("DashParams", &inDashParams); + vertBuilder->codeAppendf("%s = %s;", inDashParams.vsOut(), de.fInDashParams.name()); + + // The rect uniform's xyzw refer to (left + 0.5, top + 0.5, right - 0.5, bottom - 0.5), + // respectively. + GrGLSLVarying inRectParams(kFloat4_GrSLType); + varyingHandler->addVarying("RectParams", &inRectParams); + vertBuilder->codeAppendf("%s = %s;", inRectParams.vsOut(), de.fInRect.name()); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // Setup pass through color + this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform); + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, de.fInPosition.name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + de.fInPosition.asShaderVar(), + de.localMatrix(), + args.fFPCoordTransformHandler); + + // transforms all points so that we can compare them to our test rect + fragBuilder->codeAppendf("half xShifted = half(%s.x - floor(%s.x / %s.z) * %s.z);", + inDashParams.fsIn(), inDashParams.fsIn(), inDashParams.fsIn(), + inDashParams.fsIn()); + fragBuilder->codeAppendf("half2 fragPosShifted = half2(xShifted, half(%s.y));", + inDashParams.fsIn()); + if (de.aaMode() == AAMode::kCoverage) { + // The amount of coverage removed in x and y by the edges is computed as a pair of negative + // numbers, xSub and ySub. + fragBuilder->codeAppend("half xSub, ySub;"); + fragBuilder->codeAppendf("xSub = half(min(fragPosShifted.x - %s.x, 0.0));", + inRectParams.fsIn()); + fragBuilder->codeAppendf("xSub += half(min(%s.z - fragPosShifted.x, 0.0));", + inRectParams.fsIn()); + fragBuilder->codeAppendf("ySub = half(min(fragPosShifted.y - %s.y, 0.0));", + inRectParams.fsIn()); + fragBuilder->codeAppendf("ySub += half(min(%s.w - fragPosShifted.y, 0.0));", + inRectParams.fsIn()); + // Now compute coverage in x and y and multiply them to get the fraction of the pixel + // covered. + fragBuilder->codeAppendf( + "half alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));"); + } else if (de.aaMode() == AAMode::kCoverageWithMSAA) { + // For MSAA, we don't modulate the alpha by the Y distance, since MSAA coverage will handle + // AA on the the top and bottom edges. The shader is only responsible for intra-dash alpha. + fragBuilder->codeAppend("half xSub;"); + fragBuilder->codeAppendf("xSub = half(min(fragPosShifted.x - %s.x, 0.0));", + inRectParams.fsIn()); + fragBuilder->codeAppendf("xSub += half(min(%s.z - fragPosShifted.x, 0.0));", + inRectParams.fsIn()); + // Now compute coverage in x to get the fraction of the pixel covered. + fragBuilder->codeAppendf("half alpha = (1.0 + max(xSub, -1.0));"); + } else { + // Assuming the bounding geometry is tight so no need to check y values + fragBuilder->codeAppendf("half alpha = 1.0;"); + fragBuilder->codeAppendf("alpha *= (fragPosShifted.x - %s.x) > -0.5 ? 1.0 : 0.0;", + inRectParams.fsIn()); + fragBuilder->codeAppendf("alpha *= (%s.z - fragPosShifted.x) >= -0.5 ? 1.0 : 0.0;", + inRectParams.fsIn()); + } + fragBuilder->codeAppendf("%s = half4(alpha);", args.fOutputCoverage); +} + +void GLDashingLineEffect::setData(const GrGLSLProgramDataManager& pdman, + const GrPrimitiveProcessor& processor, + FPCoordTransformIter&& transformIter) { + const DashingLineEffect& de = processor.cast(); + if (de.color() != fColor) { + pdman.set4fv(fColorUniform, 1, de.color().vec()); + fColor = de.color(); + } + this->setTransformDataHelper(de.localMatrix(), pdman, &transformIter); +} + +void GLDashingLineEffect::GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const DashingLineEffect& de = gp.cast(); + uint32_t key = 0; + key |= de.usesLocalCoords() && de.localMatrix().hasPerspective() ? 0x1 : 0x0; + key |= static_cast(de.aaMode()) << 8; + b->add32(key); +} + +////////////////////////////////////////////////////////////////////////////// + +sk_sp DashingLineEffect::Make(const SkPMColor4f& color, + AAMode aaMode, + const SkMatrix& localMatrix, + bool usesLocalCoords) { + return sk_sp( + new DashingLineEffect(color, aaMode, localMatrix, usesLocalCoords)); +} + +void DashingLineEffect::getGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GLDashingLineEffect::GenKey(*this, caps, b); +} + +GrGLSLPrimitiveProcessor* DashingLineEffect::createGLSLInstance(const GrShaderCaps&) const { + return new GLDashingLineEffect(); +} + +DashingLineEffect::DashingLineEffect(const SkPMColor4f& color, + AAMode aaMode, + const SkMatrix& localMatrix, + bool usesLocalCoords) + : INHERITED(kDashingLineEffect_ClassID) + , fColor(color) + , fLocalMatrix(localMatrix) + , fUsesLocalCoords(usesLocalCoords) + , fAAMode(aaMode) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInDashParams = {"inDashParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; + fInRect = {"inRect", kFloat4_GrVertexAttribType, kHalf4_GrSLType}; + this->setVertexAttributes(&fInPosition, 3); +} + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingLineEffect); + +#if GR_TEST_UTILS +sk_sp DashingLineEffect::TestCreate(GrProcessorTestData* d) { + AAMode aaMode = static_cast(d->fRandom->nextULessThan(GrDashOp::kAAModeCnt)); + return DashingLineEffect::Make(SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)), + aaMode, GrTest::TestMatrix(d->fRandom), + d->fRandom->nextBool()); +} + +#endif +////////////////////////////////////////////////////////////////////////////// + +static sk_sp make_dash_gp(const SkPMColor4f& color, + AAMode aaMode, + DashCap cap, + const SkMatrix& viewMatrix, + bool usesLocalCoords) { + SkMatrix invert; + if (usesLocalCoords && !viewMatrix.invert(&invert)) { + SkDebugf("Failed to invert\n"); + return nullptr; + } + + switch (cap) { + case kRound_DashCap: + return DashingCircleEffect::Make(color, aaMode, invert, usesLocalCoords); + case kNonRound_DashCap: + return DashingLineEffect::Make(color, aaMode, invert, usesLocalCoords); + } + return nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(DashOp) { + SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); + AAMode aaMode; + do { + aaMode = static_cast(random->nextULessThan(GrDashOp::kAAModeCnt)); + } while (AAMode::kCoverageWithMSAA == aaMode && numSamples <= 1); + + // We can only dash either horizontal or vertical lines + SkPoint pts[2]; + if (random->nextBool()) { + // vertical + pts[0].fX = 1.f; + pts[0].fY = random->nextF() * 10.f; + pts[1].fX = 1.f; + pts[1].fY = random->nextF() * 10.f; + } else { + // horizontal + pts[0].fX = random->nextF() * 10.f; + pts[0].fY = 1.f; + pts[1].fX = random->nextF() * 10.f; + pts[1].fY = 1.f; + } + + // pick random cap + SkPaint::Cap cap = SkPaint::Cap(random->nextULessThan(SkPaint::kCapCount)); + + SkScalar intervals[2]; + + // We can only dash with the following intervals + enum Intervals { + kOpenOpen_Intervals , + kOpenClose_Intervals, + kCloseOpen_Intervals, + }; + + Intervals intervalType = SkPaint::kRound_Cap == cap ? + kOpenClose_Intervals : + Intervals(random->nextULessThan(kCloseOpen_Intervals + 1)); + static const SkScalar kIntervalMin = 0.1f; + static const SkScalar kIntervalMinCircles = 1.f; // Must be >= to stroke width + static const SkScalar kIntervalMax = 10.f; + switch (intervalType) { + case kOpenOpen_Intervals: + intervals[0] = random->nextRangeScalar(kIntervalMin, kIntervalMax); + intervals[1] = random->nextRangeScalar(kIntervalMin, kIntervalMax); + break; + case kOpenClose_Intervals: { + intervals[0] = 0.f; + SkScalar min = SkPaint::kRound_Cap == cap ? kIntervalMinCircles : kIntervalMin; + intervals[1] = random->nextRangeScalar(min, kIntervalMax); + break; + } + case kCloseOpen_Intervals: + intervals[0] = random->nextRangeScalar(kIntervalMin, kIntervalMax); + intervals[1] = 0.f; + break; + + } + + // phase is 0 < sum (i0, i1) + SkScalar phase = random->nextRangeScalar(0, intervals[0] + intervals[1]); + + SkPaint p; + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(SkIntToScalar(1)); + p.setStrokeCap(cap); + p.setPathEffect(GrTest::TestDashPathEffect::Make(intervals, 2, phase)); + + GrStyle style(p); + + return GrDashOp::MakeDashLineOp(context, std::move(paint), viewMatrix, pts, aaMode, style, + GrGetRandomStencil(random, context)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDashOp.h b/gfx/skia/skia/src/gpu/ops/GrDashOp.h new file mode 100644 index 0000000000..db16a713fa --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDashOp.h @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDashOp_DEFINED +#define GrDashOp_DEFINED + +#include "include/core/SkPathEffect.h" +#include "include/gpu/GrTypes.h" + +class GrDrawOp; +class GrPaint; +class GrRecordingContext; +class GrStyle; +struct GrUserStencilSettings; + +namespace GrDashOp { +enum class AAMode { + kNone, + kCoverage, + kCoverageWithMSAA, +}; +static const int kAAModeCnt = static_cast(AAMode::kCoverageWithMSAA) + 1; + +std::unique_ptr MakeDashLineOp(GrRecordingContext*, + GrPaint&&, + const SkMatrix& viewMatrix, + const SkPoint pts[2], + AAMode, + const GrStyle& style, + const GrUserStencilSettings*); +bool CanDrawDashLine(const SkPoint pts[2], const GrStyle& style, const SkMatrix& viewMatrix); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.cpp b/gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.cpp new file mode 100644 index 0000000000..8c255cd45c --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrDebugMarkerOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrRecordingContextPriv.h" + +std::unique_ptr GrDebugMarkerOp::Make(GrRecordingContext* context, + GrRenderTargetProxy* proxy, + const SkString& str) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(proxy, str); +} + +void GrDebugMarkerOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { + //SkDebugf("%s\n", fStr.c_str()); + if (state->caps().gpuTracingSupport()) { + state->opsRenderPass()->insertEventMarker(fStr.c_str()); + } +} diff --git a/gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.h b/gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.h new file mode 100644 index 0000000000..fd7eb0cbdd --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDebugMarkerOp.h @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDebugMarkerOp_DEFINED +#define GrDebugMarkerOp_DEFINED + +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/ops/GrOp.h" + +class GrOpFlushState; +class GrRecordingContext; + +class GrDebugMarkerOp final : public GrOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext*, + GrRenderTargetProxy*, + const SkString&); + + const char* name() const override { return "DebugMarker"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.append(INHERITED::dumpInfo()); + return string; + } +#endif + +private: + friend class GrOpMemoryPool; // for ctor + + GrDebugMarkerOp(GrRenderTargetProxy* proxy, const SkString& str) + : INHERITED(ClassID()) + , fStr(str) { + // Make this cover the whole screen so it can't be reordered around + this->makeFullScreen(proxy); + } + + void onPrepare(GrOpFlushState*) override {} + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + SkString fStr; + + typedef GrOp INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.cpp new file mode 100644 index 0000000000..535b3eee42 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.cpp @@ -0,0 +1,714 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrDefaultPathRenderer.h" + +#include "include/core/SkString.h" +#include "include/core/SkStrokeRec.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrSurfaceContextPriv.h" +#include "src/gpu/geometry/GrPathUtils.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +GrDefaultPathRenderer::GrDefaultPathRenderer() { +} + +//////////////////////////////////////////////////////////////////////////////// +// Helpers for drawPath + +#define STENCIL_OFF 0 // Always disable stencil (even when needed) + +static inline bool single_pass_shape(const GrShape& shape) { +#if STENCIL_OFF + return true; +#else + // Inverse fill is always two pass. + if (shape.inverseFilled()) { + return false; + } + // This path renderer only accepts simple fill paths or stroke paths that are either hairline + // or have a stroke width small enough to treat as hairline. Hairline paths are always single + // pass. Filled paths are single pass if they're convex. + if (shape.style().isSimpleFill()) { + return shape.knownToBeConvex(); + } + return true; +#endif +} + +GrPathRenderer::StencilSupport +GrDefaultPathRenderer::onGetStencilSupport(const GrShape& shape) const { + if (single_pass_shape(shape)) { + return GrPathRenderer::kNoRestriction_StencilSupport; + } else { + return GrPathRenderer::kStencilOnly_StencilSupport; + } +} + +namespace { + +class PathGeoBuilder { +public: + PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawOp::Target* target, + sk_sp geometryProcessor) + : fPrimitiveType(primitiveType) + , fTarget(target) + , fVertexStride(sizeof(SkPoint)) + , fGeometryProcessor(std::move(geometryProcessor)) + , fFirstIndex(0) + , fIndicesInChunk(0) + , fIndices(nullptr) { + this->allocNewBuffers(); + } + + ~PathGeoBuilder() { + this->emitMeshAndPutBackReserve(); + } + + /** + * Path verbs + */ + void moveTo(const SkPoint& p) { + needSpace(1); + + fSubpathIndexStart = this->currentIndex(); + *(fCurVert++) = p; + } + + void addLine(const SkPoint& p) { + needSpace(1, this->indexScale()); + + if (this->isIndexed()) { + uint16_t prevIdx = this->currentIndex() - 1; + appendCountourEdgeIndices(prevIdx); + } + *(fCurVert++) = p; + } + + void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { + this->needSpace(GrPathUtils::kMaxPointsPerCurve, + GrPathUtils::kMaxPointsPerCurve * this->indexScale()); + + // First pt of quad is the pt we ended on in previous step + uint16_t firstQPtIdx = this->currentIndex() - 1; + uint16_t numPts = (uint16_t)GrPathUtils::generateQuadraticPoints( + pts[0], pts[1], pts[2], srcSpaceTolSqd, &fCurVert, + GrPathUtils::quadraticPointCount(pts, srcSpaceTol)); + if (this->isIndexed()) { + for (uint16_t i = 0; i < numPts; ++i) { + appendCountourEdgeIndices(firstQPtIdx + i); + } + } + } + + void addConic(SkScalar weight, const SkPoint pts[], SkScalar srcSpaceTolSqd, + SkScalar srcSpaceTol) { + SkAutoConicToQuads converter; + const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol); + for (int i = 0; i < converter.countQuads(); ++i) { + this->addQuad(quadPts + i * 2, srcSpaceTolSqd, srcSpaceTol); + } + } + + void addCubic(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { + this->needSpace(GrPathUtils::kMaxPointsPerCurve, + GrPathUtils::kMaxPointsPerCurve * this->indexScale()); + + // First pt of cubic is the pt we ended on in previous step + uint16_t firstCPtIdx = this->currentIndex() - 1; + uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints( + pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &fCurVert, + GrPathUtils::cubicPointCount(pts, srcSpaceTol)); + if (this->isIndexed()) { + for (uint16_t i = 0; i < numPts; ++i) { + appendCountourEdgeIndices(firstCPtIdx + i); + } + } + } + + void addPath(const SkPath& path, SkScalar srcSpaceTol) { + SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol; + + SkPath::Iter iter(path, false); + SkPoint pts[4]; + + bool done = false; + while (!done) { + SkPath::Verb verb = iter.next(pts); + switch (verb) { + case SkPath::kMove_Verb: + this->moveTo(pts[0]); + break; + case SkPath::kLine_Verb: + this->addLine(pts[1]); + break; + case SkPath::kConic_Verb: + this->addConic(iter.conicWeight(), pts, srcSpaceTolSqd, srcSpaceTol); + break; + case SkPath::kQuad_Verb: + this->addQuad(pts, srcSpaceTolSqd, srcSpaceTol); + break; + case SkPath::kCubic_Verb: + this->addCubic(pts, srcSpaceTolSqd, srcSpaceTol); + break; + case SkPath::kClose_Verb: + break; + case SkPath::kDone_Verb: + done = true; + } + } + } + + static bool PathHasMultipleSubpaths(const SkPath& path) { + bool first = true; + + SkPath::Iter iter(path, false); + SkPath::Verb verb; + + SkPoint pts[4]; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + if (SkPath::kMove_Verb == verb && !first) { + return true; + } + first = false; + } + return false; + } + +private: + /** + * Derived properties + * TODO: Cache some of these for better performance, rather than re-computing? + */ + bool isIndexed() const { + return GrPrimitiveType::kLines == fPrimitiveType || + GrPrimitiveType::kTriangles == fPrimitiveType; + } + bool isHairline() const { + return GrPrimitiveType::kLines == fPrimitiveType || + GrPrimitiveType::kLineStrip == fPrimitiveType; + } + int indexScale() const { + switch (fPrimitiveType) { + case GrPrimitiveType::kLines: + return 2; + case GrPrimitiveType::kTriangles: + return 3; + default: + return 0; + } + } + + uint16_t currentIndex() const { return fCurVert - fVertices; } + + // Allocate vertex and (possibly) index buffers + void allocNewBuffers() { + // Ensure that we always get enough verts for a worst-case quad/cubic, plus leftover points + // from previous mesh piece (up to two verts to continue fanning). If we can't get that + // many, ask for a much larger number. This needs to be fairly big to handle quads/cubics, + // which have a worst-case of 1k points. + static const int kMinVerticesPerChunk = GrPathUtils::kMaxPointsPerCurve + 2; + static const int kFallbackVerticesPerChunk = 16384; + + fVertices = static_cast(fTarget->makeVertexSpaceAtLeast(fVertexStride, + kMinVerticesPerChunk, + kFallbackVerticesPerChunk, + &fVertexBuffer, + &fFirstVertex, + &fVerticesInChunk)); + + if (this->isIndexed()) { + // Similar to above: Ensure we get enough indices for one worst-case quad/cubic. + // No extra indices are needed for stitching, though. If we can't get that many, ask + // for enough to match our large vertex request. + const int kMinIndicesPerChunk = GrPathUtils::kMaxPointsPerCurve * this->indexScale(); + const int kFallbackIndicesPerChunk = kFallbackVerticesPerChunk * this->indexScale(); + + fIndices = fTarget->makeIndexSpaceAtLeast(kMinIndicesPerChunk, kFallbackIndicesPerChunk, + &fIndexBuffer, &fFirstIndex, + &fIndicesInChunk); + } + + fCurVert = fVertices; + fCurIdx = fIndices; + fSubpathIndexStart = 0; + } + + void appendCountourEdgeIndices(uint16_t edgeV0Idx) { + // When drawing lines we're appending line segments along the countour. When applying the + // other fill rules we're drawing triangle fans around the start of the current (sub)path. + if (!this->isHairline()) { + *(fCurIdx++) = fSubpathIndexStart; + } + *(fCurIdx++) = edgeV0Idx; + *(fCurIdx++) = edgeV0Idx + 1; + } + + // Emits a single draw with all accumulated vertex/index data + void emitMeshAndPutBackReserve() { + int vertexCount = fCurVert - fVertices; + int indexCount = fCurIdx - fIndices; + SkASSERT(vertexCount <= fVerticesInChunk); + SkASSERT(indexCount <= fIndicesInChunk); + + if (this->isIndexed() ? SkToBool(indexCount) : SkToBool(vertexCount)) { + GrMesh* mesh = fTarget->allocMesh(fPrimitiveType); + if (!this->isIndexed()) { + mesh->setNonIndexedNonInstanced(vertexCount); + } else { + mesh->setIndexed(std::move(fIndexBuffer), indexCount, fFirstIndex, 0, + vertexCount - 1, GrPrimitiveRestart::kNo); + } + mesh->setVertexData(std::move(fVertexBuffer), fFirstVertex); + fTarget->recordDraw(fGeometryProcessor, mesh); + } + + fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount)); + fTarget->putBackVertices((size_t)(fVerticesInChunk - vertexCount), fVertexStride); + } + + void needSpace(int vertsNeeded, int indicesNeeded = 0) { + if (fCurVert + vertsNeeded > fVertices + fVerticesInChunk || + fCurIdx + indicesNeeded > fIndices + fIndicesInChunk) { + // We are about to run out of space (possibly) + + // To maintain continuity, we need to remember one or two points from the current mesh. + // Lines only need the last point, fills need the first point from the current contour. + // We always grab both here, and append the ones we need at the end of this process. + SkPoint lastPt = *(fCurVert - 1); + SkASSERT(fSubpathIndexStart < fVerticesInChunk); + SkPoint subpathStartPt = fVertices[fSubpathIndexStart]; + + // Draw the mesh we've accumulated, and put back any unused space + this->emitMeshAndPutBackReserve(); + + // Get new buffers + this->allocNewBuffers(); + + // Append copies of the points we saved so the two meshes will weld properly + if (!this->isHairline()) { + *(fCurVert++) = subpathStartPt; + } + *(fCurVert++) = lastPt; + } + } + + GrPrimitiveType fPrimitiveType; + GrMeshDrawOp::Target* fTarget; + size_t fVertexStride; + sk_sp fGeometryProcessor; + + sk_sp fVertexBuffer; + int fFirstVertex; + int fVerticesInChunk; + SkPoint* fVertices; + SkPoint* fCurVert; + + sk_sp fIndexBuffer; + int fFirstIndex; + int fIndicesInChunk; + uint16_t* fIndices; + uint16_t* fCurIdx; + uint16_t fSubpathIndexStart; +}; + +class DefaultPathOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkPath& path, + SkScalar tolerance, + uint8_t coverage, + const SkMatrix& viewMatrix, + bool isHairline, + GrAAType aaType, + const SkRect& devBounds, + const GrUserStencilSettings* stencilSettings) { + return Helper::FactoryHelper(context, std::move(paint), path, tolerance, + coverage, viewMatrix, isHairline, aaType, + devBounds, stencilSettings); + } + + const char* name() const override { return "DefaultPathOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf("Color: 0x%08x Count: %d\n", fColor.toBytes_RGBA(), fPaths.count()); + for (const auto& path : fPaths) { + string.appendf("Tolerance: %.2f\n", path.fTolerance); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + DefaultPathOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, const SkPath& path, + SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline, + GrAAType aaType, const SkRect& devBounds, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()) + , fHelper(helperArgs, aaType, stencilSettings) + , fColor(color) + , fCoverage(coverage) + , fViewMatrix(viewMatrix) + , fIsHairline(isHairline) { + fPaths.emplace_back(PathData{path, tolerance}); + + HasAABloat aaBloat = (aaType == GrAAType::kNone) ? HasAABloat ::kNo : HasAABloat::kYes; + this->setBounds(devBounds, aaBloat, + isHairline ? IsHairline::kYes : IsHairline::kNo); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + GrProcessorAnalysisCoverage gpCoverage = + this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone + : GrProcessorAnalysisCoverage::kSingleChannel; + // This Op uses uniform (not vertex) color, so doesn't need to track wide color. + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, gpCoverage, &fColor, nullptr); + } + +private: + void onPrepareDraws(Target* target) override { + sk_sp gp; + { + using namespace GrDefaultGeoProcFactory; + Color color(this->color()); + Coverage coverage(this->coverage()); + LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type + : LocalCoords::kUnused_Type); + gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), + color, + coverage, + localCoords, + this->viewMatrix()); + } + + SkASSERT(gp->vertexStride() == sizeof(SkPoint)); + + int instanceCount = fPaths.count(); + + // We avoid indices when we have a single hairline contour. + bool isIndexed = !this->isHairline() || instanceCount > 1 || + PathGeoBuilder::PathHasMultipleSubpaths(fPaths[0].fPath); + + // determine primitiveType + GrPrimitiveType primitiveType; + if (this->isHairline()) { + primitiveType = isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip; + } else { + primitiveType = GrPrimitiveType::kTriangles; + } + PathGeoBuilder pathGeoBuilder(primitiveType, target, std::move(gp)); + + // fill buffers + for (int i = 0; i < instanceCount; i++) { + const PathData& args = fPaths[i]; + pathGeoBuilder.addPath(args.fPath, args.fTolerance); + } + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + DefaultPathOp* that = t->cast(); + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (this->color() != that->color()) { + return CombineResult::kCannotCombine; + } + + if (this->coverage() != that->coverage()) { + return CombineResult::kCannotCombine; + } + + if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + if (this->isHairline() != that->isHairline()) { + return CombineResult::kCannotCombine; + } + + fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); + return CombineResult::kMerged; + } + + const SkPMColor4f& color() const { return fColor; } + uint8_t coverage() const { return fCoverage; } + const SkMatrix& viewMatrix() const { return fViewMatrix; } + bool isHairline() const { return fIsHairline; } + + struct PathData { + SkPath fPath; + SkScalar fTolerance; + }; + + SkSTArray<1, PathData, true> fPaths; + Helper fHelper; + SkPMColor4f fColor; + uint8_t fCoverage; + SkMatrix fViewMatrix; + bool fIsHairline; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext, + GrPaint&& paint, + GrAAType aaType, + const GrUserStencilSettings& userStencilSettings, + const GrClip& clip, + const SkMatrix& viewMatrix, + const GrShape& shape, + bool stencilOnly) { + auto context = renderTargetContext->surfPriv().getContext(); + + SkASSERT(GrAAType::kCoverage != aaType); + SkPath path; + shape.asPath(&path); + + SkScalar hairlineCoverage; + uint8_t newCoverage = 0xff; + bool isHairline = false; + if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) { + newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); + isHairline = true; + } else { + SkASSERT(shape.style().isSimpleFill()); + } + + int passCount = 0; + const GrUserStencilSettings* passes[2]; + bool reverse = false; + bool lastPassIsBounds; + + if (isHairline) { + passCount = 1; + if (stencilOnly) { + passes[0] = &gDirectToStencil; + } else { + passes[0] = &userStencilSettings; + } + lastPassIsBounds = false; + } else { + if (single_pass_shape(shape)) { + passCount = 1; + if (stencilOnly) { + passes[0] = &gDirectToStencil; + } else { + passes[0] = &userStencilSettings; + } + lastPassIsBounds = false; + } else { + switch (path.getFillType()) { + case SkPath::kInverseEvenOdd_FillType: + reverse = true; + // fallthrough + case SkPath::kEvenOdd_FillType: + passes[0] = &gEOStencilPass; + if (stencilOnly) { + passCount = 1; + lastPassIsBounds = false; + } else { + passCount = 2; + lastPassIsBounds = true; + if (reverse) { + passes[1] = &gInvEOColorPass; + } else { + passes[1] = &gEOColorPass; + } + } + break; + + case SkPath::kInverseWinding_FillType: + reverse = true; + // fallthrough + case SkPath::kWinding_FillType: + passes[0] = &gWindStencilPass; + passCount = 2; + if (stencilOnly) { + lastPassIsBounds = false; + --passCount; + } else { + lastPassIsBounds = true; + if (reverse) { + passes[passCount-1] = &gInvWindColorPass; + } else { + passes[passCount-1] = &gWindColorPass; + } + } + break; + default: + SkDEBUGFAIL("Unknown path fFill!"); + return false; + } + } + } + + SkScalar tol = GrPathUtils::kDefaultTolerance; + SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds()); + + SkRect devBounds; + GetPathDevBounds(path, + renderTargetContext->asRenderTargetProxy()->worstCaseWidth(), + renderTargetContext->asRenderTargetProxy()->worstCaseHeight(), + viewMatrix, &devBounds); + + for (int p = 0; p < passCount; ++p) { + if (lastPassIsBounds && (p == passCount-1)) { + SkRect bounds; + SkMatrix localMatrix = SkMatrix::I(); + if (reverse) { + // draw over the dev bounds (which will be the whole dst surface for inv fill). + bounds = devBounds; + SkMatrix vmi; + // mapRect through persp matrix may not be correct + if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { + vmi.mapRect(&bounds); + } else { + if (!viewMatrix.invert(&localMatrix)) { + return false; + } + } + } else { + bounds = path.getBounds(); + } + const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : + viewMatrix; + // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start + assert_alive(paint); + renderTargetContext->priv().stencilRect(clip, passes[p], std::move(paint), + GrAA(aaType == GrAAType::kMSAA), viewM, bounds, &localMatrix); + } else { + bool stencilPass = stencilOnly || passCount > 1; + std::unique_ptr op; + if (stencilPass) { + GrPaint stencilPaint; + stencilPaint.setXPFactory(GrDisableColorXPFactory::Get()); + op = DefaultPathOp::Make(context, std::move(stencilPaint), path, srcSpaceTol, + newCoverage, viewMatrix, isHairline, aaType, devBounds, + passes[p]); + } else { + assert_alive(paint); + op = DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, newCoverage, + viewMatrix, isHairline, aaType, devBounds, passes[p]); + } + renderTargetContext->addDrawOp(clip, std::move(op)); + } + } + return true; +} + +GrPathRenderer::CanDrawPath +GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + bool isHairline = IsStrokeHairlineOrEquivalent( + args.fShape->style(), *args.fViewMatrix, nullptr); + // If we aren't a single_pass_shape or hairline, we require stencil buffers. + if (!(single_pass_shape(*args.fShape) || isHairline) && + (args.fCaps->avoidStencilBuffers() || args.fTargetIsWrappedVkSecondaryCB)) { + return CanDrawPath::kNo; + } + // If antialiasing is required, we only support MSAA. + if (GrAAType::kNone != args.fAAType && GrAAType::kMSAA != args.fAAType) { + return CanDrawPath::kNo; + } + // This can draw any path with any simple fill style. + if (!args.fShape->style().isSimpleFill() && !isHairline) { + return CanDrawPath::kNo; + } + // This is the fallback renderer for when a path is too complicated for the others to draw. + return CanDrawPath::kAsBackup; +} + +bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrDefaultPathRenderer::onDrawPath"); + GrAAType aaType = (GrAAType::kNone != args.fAAType) ? GrAAType::kMSAA : GrAAType::kNone; + + return this->internalDrawPath( + args.fRenderTargetContext, std::move(args.fPaint), aaType, *args.fUserStencilSettings, + *args.fClip, *args.fViewMatrix, *args.fShape, false); +} + +void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrDefaultPathRenderer::onStencilPath"); + SkASSERT(!args.fShape->inverseFilled()); + + GrPaint paint; + paint.setXPFactory(GrDisableColorXPFactory::Get()); + + auto aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone; + + this->internalDrawPath( + args.fRenderTargetContext, std::move(paint), aaType, GrUserStencilSettings::kUnused, + *args.fClip, *args.fViewMatrix, *args.fShape, true); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(DefaultPathOp) { + SkMatrix viewMatrix = GrTest::TestMatrix(random); + + // For now just hairlines because the other types of draws require two ops. + // TODO we should figure out a way to combine the stencil and cover steps into one op. + GrStyle style(SkStrokeRec::kHairline_InitStyle); + SkPath path = GrTest::TestPath(random); + + // Compute srcSpaceTol + SkRect bounds = path.getBounds(); + SkScalar tol = GrPathUtils::kDefaultTolerance; + SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds); + + viewMatrix.mapRect(&bounds); + uint8_t coverage = GrRandomCoverage(random); + GrAAType aaType = GrAAType::kNone; + if (numSamples > 1 && random->nextBool()) { + aaType = GrAAType::kMSAA; + } + return DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, coverage, viewMatrix, + true, aaType, bounds, GrGetRandomStencil(random, context)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.h new file mode 100644 index 0000000000..871a8c64f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDefaultPathRenderer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDefaultPathRenderer_DEFINED +#define GrDefaultPathRenderer_DEFINED + +#include "include/core/SkTypes.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/ops/GrPathStencilSettings.h" + +/** + * Subclass that renders the path using the stencil buffer to resolve fill rules + * (e.g. winding, even-odd) + */ +class GrDefaultPathRenderer : public GrPathRenderer { +public: + GrDefaultPathRenderer(); + +private: + StencilSupport onGetStencilSupport(const GrShape&) const override; + + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; + + void onStencilPath(const StencilPathArgs&) override; + + bool internalDrawPath(GrRenderTargetContext*, + GrPaint&&, + GrAAType, + const GrUserStencilSettings&, + const GrClip&, + const SkMatrix& viewMatrix, + const GrShape&, + bool stencilOnly); + + typedef GrPathRenderer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.cpp b/gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.cpp new file mode 100644 index 0000000000..417e2b4d17 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.cpp @@ -0,0 +1,346 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrDrawAtlasOp.h" + +#include "include/core/SkRSXform.h" +#include "include/private/GrRecordingContext.h" +#include "include/utils/SkRandom.h" +#include "src/core/SkRectPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +namespace { + +class DrawAtlasOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + DrawAtlasOp(const Helper::MakeArgs&, const SkPMColor4f& color, + const SkMatrix& viewMatrix, GrAAType, int spriteCount, const SkRSXform* xforms, + const SkRect* rects, const SkColor* colors); + + const char* name() const override { return "DrawAtlasOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override; +#endif + + FixedFunctionFlags fixedFunctionFlags() const override; + + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override; + +private: + void onPrepareDraws(Target*) override; + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + const SkPMColor4f& color() const { return fColor; } + const SkMatrix& viewMatrix() const { return fViewMatrix; } + bool hasColors() const { return fHasColors; } + int quadCount() const { return fQuadCount; } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override; + + struct Geometry { + SkPMColor4f fColor; + SkTArray fVerts; + }; + + SkSTArray<1, Geometry, true> fGeoData; + Helper fHelper; + SkMatrix fViewMatrix; + SkPMColor4f fColor; + int fQuadCount; + bool fHasColors; + + typedef GrMeshDrawOp INHERITED; +}; + +static sk_sp make_gp(const GrShaderCaps* shaderCaps, + bool hasColors, + const SkPMColor4f& color, + const SkMatrix& viewMatrix) { + using namespace GrDefaultGeoProcFactory; + Color gpColor(color); + if (hasColors) { + gpColor.fType = Color::kPremulGrColorAttribute_Type; + } + + return GrDefaultGeoProcFactory::Make(shaderCaps, gpColor, Coverage::kSolid_Type, + LocalCoords::kHasExplicit_Type, viewMatrix); +} + +DrawAtlasOp::DrawAtlasOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, GrAAType aaType, int spriteCount, + const SkRSXform* xforms, const SkRect* rects, const SkColor* colors) + : INHERITED(ClassID()), fHelper(helperArgs, aaType), fColor(color) { + SkASSERT(xforms); + SkASSERT(rects); + + fViewMatrix = viewMatrix; + Geometry& installedGeo = fGeoData.push_back(); + installedGeo.fColor = color; + + // Figure out stride and offsets + // Order within the vertex is: position [color] texCoord + size_t texOffset = sizeof(SkPoint); + size_t vertexStride = 2 * sizeof(SkPoint); + fHasColors = SkToBool(colors); + if (colors) { + texOffset += sizeof(GrColor); + vertexStride += sizeof(GrColor); + } + + // Compute buffer size and alloc buffer + fQuadCount = spriteCount; + int allocSize = static_cast(4 * vertexStride * spriteCount); + installedGeo.fVerts.reset(allocSize); + uint8_t* currVertex = installedGeo.fVerts.begin(); + + SkRect bounds = SkRectPriv::MakeLargestInverted(); + // TODO4F: Preserve float colors + int paintAlpha = GrColorUnpackA(installedGeo.fColor.toBytes_RGBA()); + for (int spriteIndex = 0; spriteIndex < spriteCount; ++spriteIndex) { + // Transform rect + SkPoint strip[4]; + const SkRect& currRect = rects[spriteIndex]; + xforms[spriteIndex].toTriStrip(currRect.width(), currRect.height(), strip); + + // Copy colors if necessary + if (colors) { + // convert to GrColor + SkColor color = colors[spriteIndex]; + if (paintAlpha != 255) { + color = SkColorSetA(color, SkMulDiv255Round(SkColorGetA(color), paintAlpha)); + } + GrColor grColor = SkColorToPremulGrColor(color); + + *(reinterpret_cast(currVertex + sizeof(SkPoint))) = grColor; + *(reinterpret_cast(currVertex + vertexStride + sizeof(SkPoint))) = grColor; + *(reinterpret_cast(currVertex + 2 * vertexStride + sizeof(SkPoint))) = + grColor; + *(reinterpret_cast(currVertex + 3 * vertexStride + sizeof(SkPoint))) = + grColor; + } + + // Copy position and uv to verts + *(reinterpret_cast(currVertex)) = strip[0]; + *(reinterpret_cast(currVertex + texOffset)) = + SkPoint::Make(currRect.fLeft, currRect.fTop); + SkRectPriv::GrowToInclude(&bounds, strip[0]); + currVertex += vertexStride; + + *(reinterpret_cast(currVertex)) = strip[1]; + *(reinterpret_cast(currVertex + texOffset)) = + SkPoint::Make(currRect.fLeft, currRect.fBottom); + SkRectPriv::GrowToInclude(&bounds, strip[1]); + currVertex += vertexStride; + + *(reinterpret_cast(currVertex)) = strip[2]; + *(reinterpret_cast(currVertex + texOffset)) = + SkPoint::Make(currRect.fRight, currRect.fTop); + SkRectPriv::GrowToInclude(&bounds, strip[2]); + currVertex += vertexStride; + + *(reinterpret_cast(currVertex)) = strip[3]; + *(reinterpret_cast(currVertex + texOffset)) = + SkPoint::Make(currRect.fRight, currRect.fBottom); + SkRectPriv::GrowToInclude(&bounds, strip[3]); + currVertex += vertexStride; + } + + this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kNo, IsHairline::kNo); +} + +#ifdef SK_DEBUG +SkString DrawAtlasOp::dumpInfo() const { + SkString string; + for (const auto& geo : fGeoData) { + string.appendf("Color: 0x%08x, Quads: %d\n", geo.fColor.toBytes_RGBA(), + geo.fVerts.count() / 4); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; +} +#endif + +void DrawAtlasOp::onPrepareDraws(Target* target) { + // Setup geometry processor + sk_sp gp(make_gp(target->caps().shaderCaps(), + this->hasColors(), + this->color(), + this->viewMatrix())); + + int instanceCount = fGeoData.count(); + size_t vertexStride = gp->vertexStride(); + + int numQuads = this->quadCount(); + QuadHelper helper(target, vertexStride, numQuads); + void* verts = helper.vertices(); + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + uint8_t* vertPtr = reinterpret_cast(verts); + for (int i = 0; i < instanceCount; i++) { + const Geometry& args = fGeoData[i]; + + size_t allocSize = args.fVerts.count(); + memcpy(vertPtr, args.fVerts.begin(), allocSize); + vertPtr += allocSize; + } + helper.recordDraw(target, std::move(gp)); +} + +void DrawAtlasOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); +} + +GrOp::CombineResult DrawAtlasOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) { + DrawAtlasOp* that = t->cast(); + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + // We currently use a uniform viewmatrix for this op. + if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + if (this->hasColors() != that->hasColors()) { + return CombineResult::kCannotCombine; + } + + if (!this->hasColors() && this->color() != that->color()) { + return CombineResult::kCannotCombine; + } + + fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); + fQuadCount += that->quadCount(); + + return CombineResult::kMerged; +} + +GrDrawOp::FixedFunctionFlags DrawAtlasOp::fixedFunctionFlags() const { + return fHelper.fixedFunctionFlags(); +} + +GrProcessorSet::Analysis DrawAtlasOp::finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) { + GrProcessorAnalysisColor gpColor; + if (this->hasColors()) { + gpColor.setToUnknown(); + } else { + gpColor.setToConstant(fColor); + } + auto result = fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kNone, &gpColor); + if (gpColor.isConstant(&fColor)) { + fHasColors = false; + } + return result; +} + +} // anonymous namespace + +std::unique_ptr GrDrawAtlasOp::Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + GrAAType aaType, + int spriteCount, + const SkRSXform* xforms, + const SkRect* rects, + const SkColor* colors) { + return GrSimpleMeshDrawOpHelper::FactoryHelper(context, std::move(paint), + viewMatrix, aaType, + spriteCount, xforms, + rects, colors); +} + +#if GR_TEST_UTILS + +static SkRSXform random_xform(SkRandom* random) { + static const SkScalar kMinExtent = -100.f; + static const SkScalar kMaxExtent = 100.f; + static const SkScalar kMinScale = 0.1f; + static const SkScalar kMaxScale = 100.f; + static const SkScalar kMinRotate = -SK_ScalarPI; + static const SkScalar kMaxRotate = SK_ScalarPI; + + SkRSXform xform = SkRSXform::MakeFromRadians(random->nextRangeScalar(kMinScale, kMaxScale), + random->nextRangeScalar(kMinRotate, kMaxRotate), + random->nextRangeScalar(kMinExtent, kMaxExtent), + random->nextRangeScalar(kMinExtent, kMaxExtent), + random->nextRangeScalar(kMinExtent, kMaxExtent), + random->nextRangeScalar(kMinExtent, kMaxExtent)); + return xform; +} + +static SkRect random_texRect(SkRandom* random) { + static const SkScalar kMinCoord = 0.0f; + static const SkScalar kMaxCoord = 1024.f; + + SkRect texRect = SkRect::MakeLTRB(random->nextRangeScalar(kMinCoord, kMaxCoord), + random->nextRangeScalar(kMinCoord, kMaxCoord), + random->nextRangeScalar(kMinCoord, kMaxCoord), + random->nextRangeScalar(kMinCoord, kMaxCoord)); + texRect.sort(); + return texRect; +} + +static void randomize_params(uint32_t count, SkRandom* random, SkTArray* xforms, + SkTArray* texRects, SkTArray* colors, + bool hasColors) { + for (uint32_t v = 0; v < count; v++) { + xforms->push_back(random_xform(random)); + texRects->push_back(random_texRect(random)); + if (hasColors) { + colors->push_back(GrRandomColor(random)); + } + } +} + +GR_DRAW_OP_TEST_DEFINE(DrawAtlasOp) { + uint32_t spriteCount = random->nextRangeU(1, 100); + + SkTArray xforms(spriteCount); + SkTArray texRects(spriteCount); + SkTArray colors; + + bool hasColors = random->nextBool(); + + randomize_params(spriteCount, random, &xforms, &texRects, &colors, hasColors); + + SkMatrix viewMatrix = GrTest::TestMatrix(random); + GrAAType aaType = GrAAType::kNone; + if (numSamples > 1 && random->nextBool()) { + aaType = GrAAType::kMSAA; + } + + return GrDrawAtlasOp::Make(context, std::move(paint), viewMatrix, aaType, spriteCount, + xforms.begin(), texRects.begin(), + hasColors ? colors.begin() : nullptr); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.h b/gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.h new file mode 100644 index 0000000000..425dba41b3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawAtlasOp.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawAtlasOp_DEFINED +#define GrDrawAtlasOp_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/GrTypesPriv.h" + +class GrDrawOp; +class GrPaint; +class GrRecordingContext; +class SkMatrix; + +namespace GrDrawAtlasOp { + std::unique_ptr Make(GrRecordingContext*, + GrPaint&&, + const SkMatrix& viewMatrix, + GrAAType, + int spriteCount, + const SkRSXform* xforms, + const SkRect* rects, + const SkColor* colors); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawOp.h b/gfx/skia/skia/src/gpu/ops/GrDrawOp.h new file mode 100644 index 0000000000..a9e97520f5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawOp.h @@ -0,0 +1,63 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawOp_DEFINED +#define GrDrawOp_DEFINED + +#include +#include "src/gpu/GrDeferredUpload.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/ops/GrOp.h" + +class GrAppliedClip; + +/** + * Base class for GrOps that draw. These ops can draw into an op list's GrRenderTarget. + */ +class GrDrawOp : public GrOp { +public: + GrDrawOp(uint32_t classID) : INHERITED(classID) {} + + /** + * This information is required to determine how to compute a GrAppliedClip from a GrClip for + * this op. + */ + enum class FixedFunctionFlags : uint32_t { + kNone = 0x0, + /** Indices that the op will enable MSAA or mixed samples rendering. */ + kUsesHWAA = 0x1, + /** Indices that the op reads and/or writes the stencil buffer */ + kUsesStencil = 0x2, + }; + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(FixedFunctionFlags); + virtual FixedFunctionFlags fixedFunctionFlags() const = 0; + + /** + * This is called after the GrAppliedClip has been computed and just prior to recording the op + * or combining it with a previously recorded op. The op should convert any proxies or resources + * it owns to "pending io" status so that resource allocation can be more optimal. Additionally, + * at this time the op must report whether a copy of the destination (or destination texture + * itself) needs to be provided to the GrXferProcessor when this op executes. + */ + virtual GrProcessorSet::Analysis finalize( + const GrCaps&, const GrAppliedClip*, bool hasMixedSampledCoverage, GrClampType) = 0; + +#ifdef SK_DEBUG + bool fAddDrawOpCalled = false; + + void validate() const override { + SkASSERT(fAddDrawOpCalled); + } +#endif + +private: + typedef GrOp INHERITED; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrDrawOp::FixedFunctionFlags); + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawPathOp.cpp b/gfx/skia/skia/src/gpu/ops/GrDrawPathOp.cpp new file mode 100644 index 0000000000..526ebc03d5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawPathOp.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrRecordingContext.h" +#include "include/private/SkTemplates.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/ops/GrDrawPathOp.h" + +static constexpr GrUserStencilSettings kCoverPass{ + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kKeep, + 0xffff>() +}; + +GrDrawPathOpBase::GrDrawPathOpBase(uint32_t classID, const SkMatrix& viewMatrix, GrPaint&& paint, + GrPathRendering::FillType fill, GrAA aa) + : INHERITED(classID) + , fViewMatrix(viewMatrix) + , fInputColor(paint.getColor4f()) + , fFillType(fill) + , fDoAA(GrAA::kYes == aa) + , fProcessorSet(std::move(paint)) {} + +#ifdef SK_DEBUG +SkString GrDrawPathOp::dumpInfo() const { + SkString string; + string.printf("PATH: 0x%p", fPath.get()); + string.append(INHERITED::dumpInfo()); + return string; +} +#endif + +GrPipeline::InitArgs GrDrawPathOpBase::pipelineInitArgs(const GrOpFlushState& state) { + GrPipeline::InitArgs args; + if (fDoAA) { + args.fInputFlags |= GrPipeline::InputFlags::kHWAntialias; + } + args.fUserStencil = &kCoverPass; + args.fCaps = &state.caps(); + args.fDstProxy = state.drawOpArgs().dstProxy(); + args.fOutputSwizzle = state.drawOpArgs().outputSwizzle(); + return args; +} + +const GrProcessorSet::Analysis& GrDrawPathOpBase::doProcessorAnalysis( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) { + fAnalysis = fProcessorSet.finalize( + fInputColor, GrProcessorAnalysisCoverage::kNone, clip, &kCoverPass, + hasMixedSampledCoverage, caps, clampType, &fInputColor); + return fAnalysis; +} + +////////////////////////////////////////////////////////////////////////////// + +void init_stencil_pass_settings(const GrOpFlushState& flushState, + GrPathRendering::FillType fillType, GrStencilSettings* stencil) { + const GrAppliedClip* appliedClip = flushState.drawOpArgs().appliedClip(); + bool stencilClip = appliedClip && appliedClip->hasStencilClip(); + stencil->reset(GrPathRendering::GetStencilPassSettings(fillType), stencilClip, + flushState.drawOpArgs().renderTarget()->renderTargetPriv().numStencilBits()); +} + +////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrDrawPathOp::Make(GrRecordingContext* context, + const SkMatrix& viewMatrix, + GrPaint&& paint, + GrAA aa, + sk_sp path) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(viewMatrix, std::move(paint), aa, std::move(path)); +} + +void GrDrawPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { + GrAppliedClip appliedClip = state->detachAppliedClip(); + GrPipeline::FixedDynamicState fixedDynamicState(appliedClip.scissorState().rect()); + GrPipeline pipeline(this->pipelineInitArgs(*state), this->detachProcessors(), + std::move(appliedClip)); + sk_sp pathProc(GrPathProcessor::Create(this->color(), this->viewMatrix())); + + GrProgramInfo programInfo(state->drawOpArgs().numSamples(), + state->drawOpArgs().origin(), + pipeline, + *pathProc, + &fixedDynamicState, + nullptr, 0); + + GrStencilSettings stencil; + init_stencil_pass_settings(*state, this->fillType(), &stencil); + state->gpu()->pathRendering()->drawPath(state->drawOpArgs().renderTarget(), + programInfo, stencil, fPath.get()); +} + +////////////////////////////////////////////////////////////////////////////// + +inline void pre_translate_transform_values(const float* xforms, + GrPathRendering::PathTransformType type, int count, + SkScalar x, SkScalar y, float* dst) { + if (0 == x && 0 == y) { + memcpy(dst, xforms, count * GrPathRendering::PathTransformSize(type) * sizeof(float)); + return; + } + switch (type) { + case GrPathRendering::kNone_PathTransformType: + SK_ABORT("Cannot pre-translate kNone_PathTransformType."); + break; + case GrPathRendering::kTranslateX_PathTransformType: + SkASSERT(0 == y); + for (int i = 0; i < count; i++) { + dst[i] = xforms[i] + x; + } + break; + case GrPathRendering::kTranslateY_PathTransformType: + SkASSERT(0 == x); + for (int i = 0; i < count; i++) { + dst[i] = xforms[i] + y; + } + break; + case GrPathRendering::kTranslate_PathTransformType: + for (int i = 0; i < 2 * count; i += 2) { + dst[i] = xforms[i] + x; + dst[i + 1] = xforms[i + 1] + y; + } + break; + case GrPathRendering::kAffine_PathTransformType: + for (int i = 0; i < 6 * count; i += 6) { + dst[i] = xforms[i]; + dst[i + 1] = xforms[i + 1]; + dst[i + 2] = xforms[i] * x + xforms[i + 1] * y + xforms[i + 2]; + dst[i + 3] = xforms[i + 3]; + dst[i + 4] = xforms[i + 4]; + dst[i + 5] = xforms[i + 3] * x + xforms[i + 4] * y + xforms[i + 5]; + } + break; + default: + SK_ABORT("Unknown transform type."); + break; + } +} diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawPathOp.h b/gfx/skia/skia/src/gpu/ops/GrDrawPathOp.h new file mode 100644 index 0000000000..f40c2b96b9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawPathOp.h @@ -0,0 +1,100 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawPathOp_DEFINED +#define GrDrawPathOp_DEFINED + +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrPath.h" +#include "src/gpu/GrPathProcessor.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrProcessorSet.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/ops/GrDrawOp.h" + +class GrPaint; +class GrRecordingContext; + +class GrDrawPathOpBase : public GrDrawOp { +protected: + GrDrawPathOpBase(uint32_t classID, const SkMatrix& viewMatrix, GrPaint&&, + GrPathRendering::FillType, GrAA); + + FixedFunctionFlags fixedFunctionFlags() const override { + return (fDoAA) + ? FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil + : FixedFunctionFlags::kUsesStencil; + } + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + return this->doProcessorAnalysis(caps, clip, hasMixedSampledCoverage, clampType); + } + + void visitProxies(const VisitProxyFunc& func) const override { + fProcessorSet.visitProxies(func); + } + +protected: + const SkMatrix& viewMatrix() const { return fViewMatrix; } + const SkPMColor4f& color() const { return fInputColor; } + GrPathRendering::FillType fillType() const { return fFillType; } + const GrProcessorSet& processors() const { return fProcessorSet; } + GrProcessorSet detachProcessors() { return std::move(fProcessorSet); } + inline GrPipeline::InitArgs pipelineInitArgs(const GrOpFlushState&); + const GrProcessorSet::Analysis& doProcessorAnalysis( + const GrCaps&, const GrAppliedClip*, bool hasMixedSampledCoverage, GrClampType); + const GrProcessorSet::Analysis& processorAnalysis() const { + SkASSERT(fAnalysis.isInitialized()); + return fAnalysis; + } + +private: + void onPrepare(GrOpFlushState*) final {} + + SkMatrix fViewMatrix; + SkPMColor4f fInputColor; + GrProcessorSet::Analysis fAnalysis; + GrPathRendering::FillType fFillType; + bool fDoAA; + GrProcessorSet fProcessorSet; + + typedef GrDrawOp INHERITED; +}; + +class GrDrawPathOp final : public GrDrawPathOpBase { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make( + GrRecordingContext*, const SkMatrix& viewMatrix, GrPaint&&, GrAA, sk_sp); + + const char* name() const override { return "DrawPath"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override; +#endif + +private: + friend class GrOpMemoryPool; // for ctor + + GrDrawPathOp(const SkMatrix& viewMatrix, GrPaint&& paint, GrAA aa, sk_sp path) + : GrDrawPathOpBase( + ClassID(), viewMatrix, std::move(paint), path->getFillType(), aa) + , fPath(std::move(path)) { + this->setTransformedBounds(fPath->getBounds(), viewMatrix, HasAABloat::kNo, + IsHairline::kNo); + } + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + sk_sp fPath; + + typedef GrDrawPathOpBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.cpp b/gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.cpp new file mode 100644 index 0000000000..4b311b5207 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.cpp @@ -0,0 +1,708 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkRectPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/ops/GrDrawVerticesOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +namespace { + +class DrawVerticesOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + DrawVerticesOp(const Helper::MakeArgs&, const SkPMColor4f&, sk_sp, + const SkVertices::Bone bones[], int boneCount, GrPrimitiveType, GrAAType, + sk_sp, const SkMatrix& viewMatrix); + + const char* name() const override { return "DrawVerticesOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override; +#endif + + FixedFunctionFlags fixedFunctionFlags() const override; + + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override; + +private: + enum class ColorArrayType { + kPremulGrColor, + kSkColor, + }; + + void onPrepareDraws(Target*) override; + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + void drawVolatile(Target*); + void drawNonVolatile(Target*); + + void fillBuffers(bool hasColorAttribute, + bool hasLocalCoordsAttribute, + size_t vertexStride, + void* verts, + uint16_t* indices) const; + + void drawVertices(Target*, + sk_sp, + sk_sp vertexBuffer, + int firstVertex, + sk_sp indexBuffer, + int firstIndex); + + sk_sp makeGP(const GrShaderCaps* shaderCaps, + bool* hasColorAttribute, + bool* hasLocalCoordAttribute) const; + + GrPrimitiveType primitiveType() const { return fPrimitiveType; } + bool combinablePrimitive() const { + return GrPrimitiveType::kTriangles == fPrimitiveType || + GrPrimitiveType::kLines == fPrimitiveType || + GrPrimitiveType::kPoints == fPrimitiveType; + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override; + + struct Mesh { + SkPMColor4f fColor; // Used if this->hasPerVertexColors() is false. + sk_sp fVertices; + SkMatrix fViewMatrix; + bool fIgnoreTexCoords; + bool fIgnoreColors; + + bool hasExplicitLocalCoords() const { + return fVertices->hasTexCoords() && !fIgnoreTexCoords; + } + + bool hasPerVertexColors() const { + return fVertices->hasColors() && !fIgnoreColors; + } + }; + + bool isIndexed() const { + // Consistency enforced in onCombineIfPossible. + return fMeshes[0].fVertices->hasIndices(); + } + + bool requiresPerVertexColors() const { + return SkToBool(kRequiresPerVertexColors_Flag & fFlags); + } + + bool anyMeshHasExplicitLocalCoords() const { + return SkToBool(kAnyMeshHasExplicitLocalCoords_Flag & fFlags); + } + + bool hasMultipleViewMatrices() const { + return SkToBool(kHasMultipleViewMatrices_Flag & fFlags); + } + + enum Flags { + kRequiresPerVertexColors_Flag = 0x1, + kAnyMeshHasExplicitLocalCoords_Flag = 0x2, + kHasMultipleViewMatrices_Flag = 0x4, + }; + + Helper fHelper; + SkSTArray<1, Mesh, true> fMeshes; + // GrPrimitiveType is more expressive than fVertices.mode() so it is used instead and we ignore + // the SkVertices mode (though fPrimitiveType may have been inferred from it). + GrPrimitiveType fPrimitiveType; + uint32_t fFlags; + int fVertexCount; + int fIndexCount; + ColorArrayType fColorArrayType; + sk_sp fColorSpaceXform; + + typedef GrMeshDrawOp INHERITED; +}; + +DrawVerticesOp::DrawVerticesOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + sk_sp vertices, const SkVertices::Bone bones[], + int boneCount, GrPrimitiveType primitiveType, GrAAType aaType, + sk_sp colorSpaceXform, + const SkMatrix& viewMatrix) + : INHERITED(ClassID()) + , fHelper(helperArgs, aaType) + , fPrimitiveType(primitiveType) + , fColorSpaceXform(std::move(colorSpaceXform)) { + SkASSERT(vertices); + + fVertexCount = vertices->vertexCount(); + fIndexCount = vertices->indexCount(); + fColorArrayType = vertices->hasColors() ? ColorArrayType::kSkColor + : ColorArrayType::kPremulGrColor; + + Mesh& mesh = fMeshes.push_back(); + mesh.fColor = color; + mesh.fViewMatrix = viewMatrix; + mesh.fVertices = std::move(vertices); + mesh.fIgnoreTexCoords = false; + mesh.fIgnoreColors = false; + + if (mesh.fVertices->hasBones() && bones) { + // Perform the transformations on the CPU instead of the GPU. + mesh.fVertices = mesh.fVertices->applyBones(bones, boneCount); + } else { + SkASSERT(!bones || boneCount == 1); + } + + fFlags = 0; + if (mesh.hasPerVertexColors()) { + fFlags |= kRequiresPerVertexColors_Flag; + } + if (mesh.hasExplicitLocalCoords()) { + fFlags |= kAnyMeshHasExplicitLocalCoords_Flag; + } + + // Special case for meshes with a world transform but no bone weights. + // These will be considered normal vertices draws without bones. + if (!mesh.fVertices->hasBones() && boneCount == 1) { + SkMatrix worldTransform; + worldTransform.setAffine(bones[0].values); + mesh.fViewMatrix.preConcat(worldTransform); + } + + IsHairline zeroArea; + if (GrIsPrimTypeLines(primitiveType) || GrPrimitiveType::kPoints == primitiveType) { + zeroArea = IsHairline::kYes; + } else { + zeroArea = IsHairline::kNo; + } + + this->setTransformedBounds(mesh.fVertices->bounds(), + mesh.fViewMatrix, + HasAABloat::kNo, + zeroArea); +} + +#ifdef SK_DEBUG +SkString DrawVerticesOp::dumpInfo() const { + SkString string; + string.appendf("PrimType: %d, MeshCount %d, VCount: %d, ICount: %d\n", (int)fPrimitiveType, + fMeshes.count(), fVertexCount, fIndexCount); + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; +} +#endif + +GrDrawOp::FixedFunctionFlags DrawVerticesOp::fixedFunctionFlags() const { + return fHelper.fixedFunctionFlags(); +} + +GrProcessorSet::Analysis DrawVerticesOp::finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) { + GrProcessorAnalysisColor gpColor; + if (this->requiresPerVertexColors()) { + gpColor.setToUnknown(); + } else { + gpColor.setToConstant(fMeshes.front().fColor); + } + auto result = fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kNone, &gpColor); + if (gpColor.isConstant(&fMeshes.front().fColor)) { + fMeshes.front().fIgnoreColors = true; + fFlags &= ~kRequiresPerVertexColors_Flag; + fColorArrayType = ColorArrayType::kPremulGrColor; + } + if (!fHelper.usesLocalCoords()) { + fMeshes[0].fIgnoreTexCoords = true; + fFlags &= ~kAnyMeshHasExplicitLocalCoords_Flag; + } + return result; +} + +sk_sp DrawVerticesOp::makeGP(const GrShaderCaps* shaderCaps, + bool* hasColorAttribute, + bool* hasLocalCoordAttribute) const { + using namespace GrDefaultGeoProcFactory; + LocalCoords::Type localCoordsType; + if (fHelper.usesLocalCoords()) { + // If we have multiple view matrices we will transform the positions into device space. We + // must then also provide untransformed positions as local coords. + if (this->anyMeshHasExplicitLocalCoords() || this->hasMultipleViewMatrices()) { + *hasLocalCoordAttribute = true; + localCoordsType = LocalCoords::kHasExplicit_Type; + } else { + *hasLocalCoordAttribute = false; + localCoordsType = LocalCoords::kUsePosition_Type; + } + } else { + localCoordsType = LocalCoords::kUnused_Type; + *hasLocalCoordAttribute = false; + } + + Color color(fMeshes[0].fColor); + if (this->requiresPerVertexColors()) { + if (fColorArrayType == ColorArrayType::kPremulGrColor) { + color.fType = Color::kPremulGrColorAttribute_Type; + } else { + color.fType = Color::kUnpremulSkColorAttribute_Type; + color.fColorSpaceXform = fColorSpaceXform; + } + *hasColorAttribute = true; + } else { + *hasColorAttribute = false; + } + + const SkMatrix& vm = this->hasMultipleViewMatrices() ? SkMatrix::I() : fMeshes[0].fViewMatrix; + + return GrDefaultGeoProcFactory::Make(shaderCaps, + color, + Coverage::kSolid_Type, + localCoordsType, + vm); +} + +void DrawVerticesOp::onPrepareDraws(Target* target) { + bool hasMapBufferSupport = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags(); + if (fMeshes[0].fVertices->isVolatile() || !hasMapBufferSupport) { + this->drawVolatile(target); + } else { + this->drawNonVolatile(target); + } +} + +void DrawVerticesOp::drawVolatile(Target* target) { + bool hasColorAttribute; + bool hasLocalCoordsAttribute; + sk_sp gp = this->makeGP(target->caps().shaderCaps(), + &hasColorAttribute, + &hasLocalCoordsAttribute); + + // Allocate buffers. + size_t vertexStride = gp->vertexStride(); + sk_sp vertexBuffer; + int firstVertex = 0; + void* verts = target->makeVertexSpace(vertexStride, fVertexCount, &vertexBuffer, &firstVertex); + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + sk_sp indexBuffer; + int firstIndex = 0; + uint16_t* indices = nullptr; + if (this->isIndexed()) { + indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); + if (!indices) { + SkDebugf("Could not allocate indices\n"); + return; + } + } + + // Fill the buffers. + this->fillBuffers(hasColorAttribute, + hasLocalCoordsAttribute, + vertexStride, + verts, + indices); + + // Draw the vertices. + this->drawVertices(target, std::move(gp), std::move(vertexBuffer), firstVertex, indexBuffer, + firstIndex); +} + +void DrawVerticesOp::drawNonVolatile(Target* target) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + + bool hasColorAttribute; + bool hasLocalCoordsAttribute; + sk_sp gp = this->makeGP(target->caps().shaderCaps(), + &hasColorAttribute, + &hasLocalCoordsAttribute); + + SkASSERT(fMeshes.count() == 1); // Non-volatile meshes should never combine. + + // Get the resource provider. + GrResourceProvider* rp = target->resourceProvider(); + + // Generate keys for the buffers. + GrUniqueKey vertexKey, indexKey; + GrUniqueKey::Builder vertexKeyBuilder(&vertexKey, kDomain, 2); + GrUniqueKey::Builder indexKeyBuilder(&indexKey, kDomain, 2); + vertexKeyBuilder[0] = indexKeyBuilder[0] = fMeshes[0].fVertices->uniqueID(); + vertexKeyBuilder[1] = 0; + indexKeyBuilder[1] = 1; + vertexKeyBuilder.finish(); + indexKeyBuilder.finish(); + + // Try to grab data from the cache. + sk_sp vertexBuffer = rp->findByUniqueKey(vertexKey); + sk_sp indexBuffer = + this->isIndexed() ? rp->findByUniqueKey(indexKey) : nullptr; + + // Draw using the cached buffers if possible. + if (vertexBuffer && (!this->isIndexed() || indexBuffer)) { + this->drawVertices(target, std::move(gp), std::move(vertexBuffer), 0, + std::move(indexBuffer), 0); + return; + } + + // Allocate vertex buffer. + size_t vertexStride = gp->vertexStride(); + vertexBuffer = rp->createBuffer( + fVertexCount * vertexStride, GrGpuBufferType::kVertex, kStatic_GrAccessPattern); + void* verts = vertexBuffer ? vertexBuffer->map() : nullptr; + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + // Allocate index buffer. + uint16_t* indices = nullptr; + if (this->isIndexed()) { + indexBuffer = rp->createBuffer( + fIndexCount * sizeof(uint16_t), GrGpuBufferType::kIndex, kStatic_GrAccessPattern); + indices = indexBuffer ? static_cast(indexBuffer->map()) : nullptr; + if (!indices) { + SkDebugf("Could not allocate indices\n"); + return; + } + } + + // Fill the buffers. + this->fillBuffers(hasColorAttribute, + hasLocalCoordsAttribute, + vertexStride, + verts, + indices); + + // Unmap the buffers. + vertexBuffer->unmap(); + if (indexBuffer) { + indexBuffer->unmap(); + } + + // Cache the buffers. + rp->assignUniqueKeyToResource(vertexKey, vertexBuffer.get()); + rp->assignUniqueKeyToResource(indexKey, indexBuffer.get()); + + // Draw the vertices. + this->drawVertices(target, std::move(gp), std::move(vertexBuffer), 0, std::move(indexBuffer), + 0); +} + +void DrawVerticesOp::fillBuffers(bool hasColorAttribute, + bool hasLocalCoordsAttribute, + size_t vertexStride, + void* verts, + uint16_t* indices) const { + int instanceCount = fMeshes.count(); + + // Copy data into the buffers. + int vertexOffset = 0; + // We have a fast case below for uploading the vertex data when the matrix is translate + // only and there are colors but not local coords. + bool fastAttrs = hasColorAttribute && !hasLocalCoordsAttribute; + for (int i = 0; i < instanceCount; i++) { + // Get each mesh. + const Mesh& mesh = fMeshes[i]; + + // Copy data into the index buffer. + if (indices) { + int indexCount = mesh.fVertices->indexCount(); + for (int j = 0; j < indexCount; ++j) { + *indices++ = mesh.fVertices->indices()[j] + vertexOffset; + } + } + + // Copy data into the vertex buffer. + int vertexCount = mesh.fVertices->vertexCount(); + const SkPoint* positions = mesh.fVertices->positions(); + const SkColor* colors = mesh.fVertices->colors(); + const SkPoint* localCoords = mesh.fVertices->texCoords(); + bool fastMesh = (!this->hasMultipleViewMatrices() || + mesh.fViewMatrix.getType() <= SkMatrix::kTranslate_Mask) && + mesh.hasPerVertexColors(); + if (fastAttrs && fastMesh) { + // Fast case. + struct V { + SkPoint fPos; + uint32_t fColor; + }; + SkASSERT(sizeof(V) == vertexStride); + V* v = (V*)verts; + Sk2f t(0, 0); + if (this->hasMultipleViewMatrices()) { + t = Sk2f(mesh.fViewMatrix.getTranslateX(), mesh.fViewMatrix.getTranslateY()); + } + for (int j = 0; j < vertexCount; ++j) { + Sk2f p = Sk2f::Load(positions++) + t; + p.store(&v[j].fPos); + v[j].fColor = colors[j]; + } + verts = v + vertexCount; + } else { + // Normal case. + static constexpr size_t kColorOffset = sizeof(SkPoint); + size_t offset = kColorOffset; + if (hasColorAttribute) { + offset += sizeof(uint32_t); + } + size_t localCoordOffset = offset; + if (hasLocalCoordsAttribute) { + offset += sizeof(SkPoint); + } + + // TODO4F: Preserve float colors + GrColor color = mesh.fColor.toBytes_RGBA(); + + for (int j = 0; j < vertexCount; ++j) { + if (this->hasMultipleViewMatrices()) { + mesh.fViewMatrix.mapPoints(((SkPoint*)verts), &positions[j], 1); + } else { + *((SkPoint*)verts) = positions[j]; + } + if (hasColorAttribute) { + if (mesh.hasPerVertexColors()) { + *(uint32_t*)((intptr_t)verts + kColorOffset) = colors[j]; + } else { + *(uint32_t*)((intptr_t)verts + kColorOffset) = color; + } + } + if (hasLocalCoordsAttribute) { + if (mesh.hasExplicitLocalCoords()) { + *(SkPoint*)((intptr_t)verts + localCoordOffset) = localCoords[j]; + } else { + *(SkPoint*)((intptr_t)verts + localCoordOffset) = positions[j]; + } + } + verts = (void*)((intptr_t)verts + vertexStride); + } + } + vertexOffset += vertexCount; + } +} + +void DrawVerticesOp::drawVertices(Target* target, + sk_sp gp, + sk_sp vertexBuffer, + int firstVertex, + sk_sp indexBuffer, + int firstIndex) { + GrMesh* mesh = target->allocMesh(this->primitiveType()); + if (this->isIndexed()) { + mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertexCount - 1, + GrPrimitiveRestart::kNo); + } else { + mesh->setNonIndexedNonInstanced(fVertexCount); + } + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); +} + +void DrawVerticesOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); +} + +GrOp::CombineResult DrawVerticesOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) { + DrawVerticesOp* that = t->cast(); + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + // Non-volatile meshes cannot batch, because if a non-volatile mesh batches with another mesh, + // then on the next frame, if that non-volatile mesh is drawn, it will draw the other mesh + // that was saved in its vertex buffer, which is not necessarily there anymore. + if (!this->fMeshes[0].fVertices->isVolatile() || !that->fMeshes[0].fVertices->isVolatile()) { + return CombineResult::kCannotCombine; + } + + if (!this->combinablePrimitive() || this->primitiveType() != that->primitiveType()) { + return CombineResult::kCannotCombine; + } + + if (fMeshes[0].fVertices->hasIndices() != that->fMeshes[0].fVertices->hasIndices()) { + return CombineResult::kCannotCombine; + } + + if (fColorArrayType != that->fColorArrayType) { + return CombineResult::kCannotCombine; + } + + if (fVertexCount + that->fVertexCount > SkTo(UINT16_MAX)) { + return CombineResult::kCannotCombine; + } + + // NOTE: For SkColor vertex colors, the source color space is always sRGB, and the destination + // gamut is determined by the render target context. A mis-match should be impossible. + SkASSERT(GrColorSpaceXform::Equals(fColorSpaceXform.get(), that->fColorSpaceXform.get())); + + // If either op required explicit local coords or per-vertex colors the combined mesh does. Same + // with multiple view matrices. + fFlags |= that->fFlags; + + if (!this->requiresPerVertexColors() && this->fMeshes[0].fColor != that->fMeshes[0].fColor) { + fFlags |= kRequiresPerVertexColors_Flag; + } + // Check whether we are about to acquire a mesh with a different view matrix. + if (!this->hasMultipleViewMatrices() && + !this->fMeshes[0].fViewMatrix.cheapEqualTo(that->fMeshes[0].fViewMatrix)) { + fFlags |= kHasMultipleViewMatrices_Flag; + } + + fMeshes.push_back_n(that->fMeshes.count(), that->fMeshes.begin()); + fVertexCount += that->fVertexCount; + fIndexCount += that->fIndexCount; + + return CombineResult::kMerged; +} + +} // anonymous namespace + +std::unique_ptr GrDrawVerticesOp::Make(GrRecordingContext* context, + GrPaint&& paint, + sk_sp vertices, + const SkVertices::Bone bones[], + int boneCount, + const SkMatrix& viewMatrix, + GrAAType aaType, + sk_sp colorSpaceXform, + GrPrimitiveType* overridePrimType) { + SkASSERT(vertices); + GrPrimitiveType primType = overridePrimType ? *overridePrimType + : SkVertexModeToGrPrimitiveType(vertices->mode()); + return GrSimpleMeshDrawOpHelper::FactoryHelper(context, std::move(paint), + std::move(vertices), + bones, boneCount, + primType, aaType, + std::move(colorSpaceXform), + viewMatrix); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +#include "src/gpu/GrDrawOpTest.h" + +static uint32_t seed_vertices(GrPrimitiveType type) { + switch (type) { + case GrPrimitiveType::kTriangles: + case GrPrimitiveType::kTriangleStrip: + return 3; + case GrPrimitiveType::kPoints: + return 1; + case GrPrimitiveType::kLines: + case GrPrimitiveType::kLineStrip: + return 2; + case GrPrimitiveType::kPath: + SkASSERT(0); + return 0; + } + SK_ABORT("Incomplete switch\n"); +} + +static uint32_t primitive_vertices(GrPrimitiveType type) { + switch (type) { + case GrPrimitiveType::kTriangles: + return 3; + case GrPrimitiveType::kLines: + return 2; + case GrPrimitiveType::kTriangleStrip: + case GrPrimitiveType::kPoints: + case GrPrimitiveType::kLineStrip: + return 1; + case GrPrimitiveType::kPath: + SkASSERT(0); + return 0; + } + SK_ABORT("Incomplete switch\n"); +} + +static SkPoint random_point(SkRandom* random, SkScalar min, SkScalar max) { + SkPoint p; + p.fX = random->nextRangeScalar(min, max); + p.fY = random->nextRangeScalar(min, max); + return p; +} + +static void randomize_params(size_t count, size_t maxVertex, SkScalar min, SkScalar max, + SkRandom* random, SkTArray* positions, + SkTArray* texCoords, bool hasTexCoords, + SkTArray* colors, bool hasColors, + SkTArray* indices, bool hasIndices) { + for (uint32_t v = 0; v < count; v++) { + positions->push_back(random_point(random, min, max)); + if (hasTexCoords) { + texCoords->push_back(random_point(random, min, max)); + } + if (hasColors) { + colors->push_back(GrRandomColor(random)); + } + if (hasIndices) { + SkASSERT(maxVertex <= UINT16_MAX); + indices->push_back(random->nextULessThan((uint16_t)maxVertex)); + } + } +} + +GR_DRAW_OP_TEST_DEFINE(DrawVerticesOp) { + GrPrimitiveType type; + do { + type = GrPrimitiveType(random->nextULessThan(kNumGrPrimitiveTypes)); + } while (type == GrPrimitiveType::kPath); + + uint32_t primitiveCount = random->nextRangeU(1, 100); + + // TODO make 'sensible' indexbuffers + SkTArray positions; + SkTArray texCoords; + SkTArray colors; + SkTArray indices; + + bool hasTexCoords = random->nextBool(); + bool hasIndices = random->nextBool(); + bool hasColors = random->nextBool(); + + uint32_t vertexCount = seed_vertices(type) + (primitiveCount - 1) * primitive_vertices(type); + + static const SkScalar kMinVertExtent = -100.f; + static const SkScalar kMaxVertExtent = 100.f; + randomize_params(seed_vertices(type), vertexCount, kMinVertExtent, kMaxVertExtent, random, + &positions, &texCoords, hasTexCoords, &colors, hasColors, &indices, + hasIndices); + + for (uint32_t i = 1; i < primitiveCount; i++) { + randomize_params(primitive_vertices(type), vertexCount, kMinVertExtent, kMaxVertExtent, + random, &positions, &texCoords, hasTexCoords, &colors, hasColors, &indices, + hasIndices); + } + + SkMatrix viewMatrix = GrTest::TestMatrix(random); + + sk_sp colorSpaceXform = GrTest::TestColorXform(random); + + static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode; + sk_sp vertices = SkVertices::MakeCopy(kIgnoredMode, vertexCount, positions.begin(), + texCoords.begin(), colors.begin(), + hasIndices ? indices.count() : 0, + indices.begin()); + GrAAType aaType = GrAAType::kNone; + if (numSamples > 1 && random->nextBool()) { + aaType = GrAAType::kMSAA; + } + return GrDrawVerticesOp::Make(context, std::move(paint), std::move(vertices), nullptr, 0, + viewMatrix, aaType, std::move(colorSpaceXform), &type); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.h b/gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.h new file mode 100644 index 0000000000..2f146b8d91 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawVerticesOp.h @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawVerticesOp_DEFINED +#define GrDrawVerticesOp_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkVertices.h" +#include "include/private/GrTypesPriv.h" + +class GrColorSpaceXform; +class GrDrawOp; +class GrPaint; +class GrRecordingContext; + +namespace GrDrawVerticesOp { + + /** + * Draw a SkVertices. The GrPaint param's color is used if the vertices lack per-vertex color. + * If the vertices lack local coords then the vertex positions are used as local coords. The + * primitive type drawn is derived from the SkVertices object, unless overridePrimType is + * specified. + */ + std::unique_ptr Make(GrRecordingContext*, + GrPaint&&, + sk_sp, + const SkVertices::Bone bones[], + int boneCount, + const SkMatrix& viewMatrix, + GrAAType, + sk_sp, + GrPrimitiveType* overridePrimType = nullptr); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawableOp.cpp b/gfx/skia/skia/src/gpu/ops/GrDrawableOp.cpp new file mode 100644 index 0000000000..d50c26b0b5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawableOp.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrDrawableOp.h" + +#include "include/core/SkDrawable.h" +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrRecordingContextPriv.h" + +std::unique_ptr GrDrawableOp::Make( + GrRecordingContext* context, std::unique_ptr drawable, + const SkRect& bounds) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + return pool->allocate(std::move(drawable), bounds); +} + +GrDrawableOp::GrDrawableOp(std::unique_ptr drawable, + const SkRect& bounds) + : INHERITED(ClassID()) + , fDrawable(std::move(drawable)) { + this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo); +} + +void GrDrawableOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { + SkASSERT(state->opsRenderPass()); + state->opsRenderPass()->executeDrawable(std::move(fDrawable)); +} diff --git a/gfx/skia/skia/src/gpu/ops/GrDrawableOp.h b/gfx/skia/skia/src/gpu/ops/GrDrawableOp.h new file mode 100644 index 0000000000..b5b71a70d0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrDrawableOp.h @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDrawableOp_DEFINED +#define GrDrawableOp_DEFINED + +#include "src/gpu/ops/GrOp.h" + +#include "include/core/SkDrawable.h" +#include "include/core/SkMatrix.h" +#include "src/gpu/GrSemaphore.h" + +class GrRecordingContext; + +class GrDrawableOp final : public GrOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext*, + std::unique_ptr drawable, + const SkRect& bounds); + + const char* name() const override { return "Drawable"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + return INHERITED::dumpInfo(); + } +#endif + +private: + friend class GrOpMemoryPool; // for ctor + + GrDrawableOp(std::unique_ptr, const SkRect& bounds); + + CombineResult onCombineIfPossible(GrOp* that, const GrCaps& caps) override { + return CombineResult::kCannotCombine; + } + void onPrepare(GrOpFlushState*) override {} + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + std::unique_ptr fDrawable; + + typedef GrOp INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/ops/GrFillRRectOp.cpp b/gfx/skia/skia/src/gpu/ops/GrFillRRectOp.cpp new file mode 100644 index 0000000000..6b3c023ff6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrFillRRectOp.cpp @@ -0,0 +1,826 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrFillRRectOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkRRectPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrProgramInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +// Hardware derivatives are not always accurate enough for highly elliptical corners. This method +// checks to make sure the corners will still all look good if we use HW derivatives. +static bool can_use_hw_derivatives_with_coverage( + const GrShaderCaps&, const SkMatrix&, const SkRRect&); + +std::unique_ptr GrFillRRectOp::Make( + GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect, + const GrCaps& caps, GrPaint&& paint) { + if (!caps.instanceAttribSupport()) { + return nullptr; + } + + Flags flags = Flags::kNone; + if (GrAAType::kCoverage == aaType) { + // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we + // already use HW derivatives. The only trick will be adjusting the AA outset to account for + // perspective. (i.e., outset = 0.5 * z.) + if (viewMatrix.hasPerspective()) { + return nullptr; + } + if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) { + // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms + // in coverage mode. We use them as long as the approximation will be accurate enough. + flags |= Flags::kUseHWDerivatives; + } + } else { + if (GrAAType::kMSAA == aaType) { + if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleVariablesSupport()) { + return nullptr; + } + } + if (viewMatrix.hasPerspective()) { + // HW derivatives are consistently slower on all platforms in sample mask mode. We + // therefore only use them when there is perspective, since then we can't interpolate + // the symbolic screen-space gradient. + flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective; + } + } + + // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space. + float l = rrect.rect().left(), r = rrect.rect().right(), + t = rrect.rect().top(), b = rrect.rect().bottom(); + SkMatrix m; + // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b]. + m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2); + // Map to device space. + m.postConcat(viewMatrix); + + SkRect devBounds; + if (!(flags & Flags::kHasPerspective)) { + // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's + // device-space quad, it's quite simple to find the bounding rectangle: + devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0); + devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()), + SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY())); + } else { + viewMatrix.mapRect(&devBounds, rrect.rect()); + } + + if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) { + // We are on a platform that prefers fine triangles instead of using the sample mask. See if + // the round rect is large enough that it will be faster for us to send it off to the + // default path renderer instead. The 200x200 threshold was arrived at using the + // "shapes_rrect" benchmark on an ARM Galaxy S9. + if (devBounds.height() * devBounds.width() > 200 * 200) { + return nullptr; + } + } + + GrOpMemoryPool* pool = ctx->priv().opMemoryPool(); + return pool->allocate(aaType, rrect, flags, m, std::move(paint), devBounds); +} + +GrFillRRectOp::GrFillRRectOp( + GrAAType aaType, const SkRRect& rrect, Flags flags, + const SkMatrix& totalShapeMatrix, GrPaint&& paint, const SkRect& devBounds) + : GrDrawOp(ClassID()) + , fAAType(aaType) + , fOriginalColor(paint.getColor4f()) + , fLocalRect(rrect.rect()) + , fFlags(flags) + , fProcessors(std::move(paint)) { + SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective()); + this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsHairline::kNo); + + // Write the matrix attribs. + const SkMatrix& m = totalShapeMatrix; + if (!(fFlags & Flags::kHasPerspective)) { + // Affine 2D transformation (float2x2 plus float2 translate). + SkASSERT(!m.hasPerspective()); + this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()); + this->writeInstanceData(m.getTranslateX(), m.getTranslateY()); + } else { + // Perspective float3x3 transformation matrix. + SkASSERT(m.hasPerspective()); + m.get9(this->appendInstanceData(9)); + } + + // Convert the radii to [-1, -1, +1, +1] space and write their attribs. + Sk4f radiiX, radiiY; + Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY); + (radiiX * (2/rrect.width())).store(this->appendInstanceData(4)); + (radiiY * (2/rrect.height())).store(this->appendInstanceData(4)); + + // We will write the color and local rect attribs during finalize(). +} + +GrProcessorSet::Analysis GrFillRRectOp::finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) { + SkASSERT(1 == fInstanceCount); + + SkPMColor4f overrideColor; + const GrProcessorSet::Analysis& analysis = fProcessors.finalize( + + fOriginalColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, + &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType, + &overrideColor); + + // Finish writing the instance attribs. + SkPMColor4f finalColor = analysis.inputColorIsOverridden() ? overrideColor : fOriginalColor; + if (!SkPMColor4fFitsInBytes(finalColor)) { + fFlags |= Flags::kWideColor; + uint32_t halfColor[2]; + SkFloatToHalf_finite_ftz(Sk4f::Load(finalColor.vec())).store(&halfColor); + this->writeInstanceData(halfColor[0], halfColor[1]); + } else { + this->writeInstanceData(finalColor.toBytes_RGBA()); + } + + if (analysis.usesLocalCoords()) { + this->writeInstanceData(fLocalRect); + fFlags |= Flags::kHasLocalCoords; + } + fInstanceStride = fInstanceData.count(); + + return analysis; +} + +GrDrawOp::CombineResult GrFillRRectOp::onCombineIfPossible(GrOp* op, const GrCaps&) { + const auto& that = *op->cast(); + if (fFlags != that.fFlags || fProcessors != that.fProcessors || + fInstanceData.count() > std::numeric_limits::max() - that.fInstanceData.count()) { + return CombineResult::kCannotCombine; + } + + fInstanceData.push_back_n(that.fInstanceData.count(), that.fInstanceData.begin()); + fInstanceCount += that.fInstanceCount; + SkASSERT(fInstanceStride == that.fInstanceStride); + return CombineResult::kMerged; +} + +class GrFillRRectOp::Processor : public GrGeometryProcessor { +public: + Processor(GrAAType aaType, Flags flags) + : GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID) + , fAAType(aaType) + , fFlags(flags) { + int numVertexAttribs = (GrAAType::kCoverage == fAAType) ? 3 : 2; + this->setVertexAttributes(kVertexAttribs, numVertexAttribs); + + if (!(flags & Flags::kHasPerspective)) { + // Affine 2D transformation (float2x2 plus float2 translate). + fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + fInstanceAttribs.emplace_back( + "translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType); + } else { + // Perspective float3x3 transformation matrix. + fInstanceAttribs.emplace_back("persp_x", kFloat3_GrVertexAttribType, kFloat3_GrSLType); + fInstanceAttribs.emplace_back("persp_y", kFloat3_GrVertexAttribType, kFloat3_GrSLType); + fInstanceAttribs.emplace_back("persp_z", kFloat3_GrVertexAttribType, kFloat3_GrSLType); + } + fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + fColorAttrib = &fInstanceAttribs.push_back( + MakeColorAttribute("color", (flags & Flags::kWideColor))); + if (fFlags & Flags::kHasLocalCoords) { + fInstanceAttribs.emplace_back( + "local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType); + } + this->setInstanceAttributes(fInstanceAttribs.begin(), fInstanceAttribs.count()); + + if (GrAAType::kMSAA == fAAType) { + this->setWillUseCustomFeature(CustomFeatures::kSampleLocations); + } + } + + const char* name() const override { return "GrFillRRectOp::Processor"; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + b->add32(((uint32_t)fFlags << 16) | (uint32_t)fAAType); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + static constexpr Attribute kVertexAttribs[] = { + {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, + // Coverage only. + {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}}; + + const GrAAType fAAType; + const Flags fFlags; + + SkSTArray<6, Attribute> fInstanceAttribs; + const Attribute* fColorAttrib; + + class CoverageImpl; + class MSAAImpl; +}; + +constexpr GrPrimitiveProcessor::Attribute GrFillRRectOp::Processor::kVertexAttribs[]; + +// Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear +// coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal +// edges. The Vertex struct tells the shader where to place its vertex within a normalized +// ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode. +struct CoverageVertex { + std::array fRadiiSelector; + std::array fCorner; + std::array fRadiusOutset; + std::array fAABloatDirection; + float fCoverage; + float fIsLinearCoverage; +}; + +// This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices +// of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than +// rectangles. +static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2); + +static constexpr CoverageVertex kCoverageVertexData[] = { + // Left inset edge. + {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{+1,0}}, 1, 1}, + {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{+1,0}}, 1, 1}, + + // Top inset edge. + {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,+1}}, 1, 1}, + {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,+1}}, 1, 1}, + + // Right inset edge. + {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{-1,0}}, 1, 1}, + {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{-1,0}}, 1, 1}, + + // Bottom inset edge. + {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,-1}}, 1, 1}, + {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,-1}}, 1, 1}, + + + // Left outset edge. + {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{-1,0}}, 0, 1}, + {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{-1,0}}, 0, 1}, + + // Top outset edge. + {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,-1}}, 0, 1}, + {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,-1}}, 0, 1}, + + // Right outset edge. + {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{+1,0}}, 0, 1}, + {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{+1,0}}, 0, 1}, + + // Bottom outset edge. + {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,+1}}, 0, 1}, + {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,+1}}, 0, 1}, + + + // Top-left corner. + {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{-1, 0}}, 0, 0}, + {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{+1, 0}}, 1, 0}, + {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,+1}}, 1, 0}, + {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,-1}}, 0, 0}, + {{{1,0,0,0}}, {{-1,-1}}, {{+kOctoOffset,0}}, {{-1,-1}}, 0, 0}, + {{{1,0,0,0}}, {{-1,-1}}, {{0,+kOctoOffset}}, {{-1,-1}}, 0, 0}, + + // Top-right corner. + {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,-1}}, 0, 0}, + {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,+1}}, 1, 0}, + {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{-1, 0}}, 1, 0}, + {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{+1, 0}}, 0, 0}, + {{{0,1,0,0}}, {{+1,-1}}, {{0,+kOctoOffset}}, {{+1,-1}}, 0, 0}, + {{{0,1,0,0}}, {{+1,-1}}, {{-kOctoOffset,0}}, {{+1,-1}}, 0, 0}, + + // Bottom-right corner. + {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{+1, 0}}, 0, 0}, + {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{-1, 0}}, 1, 0}, + {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,-1}}, 1, 0}, + {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,+1}}, 0, 0}, + {{{0,0,1,0}}, {{+1,+1}}, {{-kOctoOffset,0}}, {{+1,+1}}, 0, 0}, + {{{0,0,1,0}}, {{+1,+1}}, {{0,-kOctoOffset}}, {{+1,+1}}, 0, 0}, + + // Bottom-left corner. + {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,+1}}, 0, 0}, + {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,-1}}, 1, 0}, + {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{+1, 0}}, 1, 0}, + {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{-1, 0}}, 0, 0}, + {{{0,0,0,1}}, {{-1,+1}}, {{0,-kOctoOffset}}, {{-1,+1}}, 0, 0}, + {{{0,0,0,1}}, {{-1,+1}}, {{+kOctoOffset,0}}, {{-1,+1}}, 0, 0}}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey); + +static constexpr uint16_t kCoverageIndexData[] = { + // Inset octagon (solid coverage). + 0, 1, 7, + 1, 2, 7, + 7, 2, 6, + 2, 3, 6, + 6, 3, 5, + 3, 4, 5, + + // AA borders (linear coverage). + 0, 1, 8, 1, 9, 8, + 2, 3, 10, 3, 11, 10, + 4, 5, 12, 5, 13, 12, + 6, 7, 14, 7, 15, 14, + + // Top-left arc. + 16, 17, 21, + 17, 21, 18, + 21, 18, 20, + 18, 20, 19, + + // Top-right arc. + 22, 23, 27, + 23, 27, 24, + 27, 24, 26, + 24, 26, 25, + + // Bottom-right arc. + 28, 29, 33, + 29, 33, 30, + 33, 30, 32, + 30, 32, 31, + + // Bottom-left arc. + 34, 35, 39, + 35, 39, 36, + 39, 36, 38, + 36, 38, 37}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey); + + +// Our MSAA geometry consists of an inset octagon with full sample mask coverage, circumscribed +// by a larger octagon that modifies the sample mask for the arc at each corresponding corner. +struct MSAAVertex { + std::array fRadiiSelector; + std::array fCorner; + std::array fRadiusOutset; +}; + +static constexpr MSAAVertex kMSAAVertexData[] = { + // Left edge. (Negative radii selector indicates this is not an arc section.) + {{{0,0,0,-1}}, {{-1,+1}}, {{0,-1}}}, + {{{-1,0,0,0}}, {{-1,-1}}, {{0,+1}}}, + + // Top edge. + {{{-1,0,0,0}}, {{-1,-1}}, {{+1,0}}}, + {{{0,-1,0,0}}, {{+1,-1}}, {{-1,0}}}, + + // Right edge. + {{{0,-1,0,0}}, {{+1,-1}}, {{0,+1}}}, + {{{0,0,-1,0}}, {{+1,+1}}, {{0,-1}}}, + + // Bottom edge. + {{{0,0,-1,0}}, {{+1,+1}}, {{-1,0}}}, + {{{0,0,0,-1}}, {{-1,+1}}, {{+1,0}}}, + + // Top-left corner. + {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}}, + {{{1,0,0,0}}, {{-1,-1}}, {{0,+kOctoOffset}}}, + {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}}, + {{{1,0,0,0}}, {{-1,-1}}, {{+kOctoOffset,0}}}, + + // Top-right corner. + {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}}, + {{{0,1,0,0}}, {{+1,-1}}, {{-kOctoOffset,0}}}, + {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}}, + {{{0,1,0,0}}, {{+1,-1}}, {{0,+kOctoOffset}}}, + + // Bottom-right corner. + {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}}, + {{{0,0,1,0}}, {{+1,+1}}, {{0,-kOctoOffset}}}, + {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}}, + {{{0,0,1,0}}, {{+1,+1}}, {{-kOctoOffset,0}}}, + + // Bottom-left corner. + {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}}, + {{{0,0,0,1}}, {{-1,+1}}, {{+kOctoOffset,0}}}, + {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}}, + {{{0,0,0,1}}, {{-1,+1}}, {{0,-kOctoOffset}}}}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey); + +static constexpr uint16_t kMSAAIndexData[] = { + // Inset octagon. (Full sample mask.) + 0, 1, 2, + 0, 2, 3, + 0, 3, 6, + 3, 4, 5, + 3, 5, 6, + 6, 7, 0, + + // Top-left arc. (Sample mask is set to the arc.) + 8, 9, 10, + 9, 11, 10, + + // Top-right arc. + 12, 13, 14, + 13, 15, 14, + + // Bottom-right arc. + 16, 17, 18, + 17, 19, 18, + + // Bottom-left arc. + 20, 21, 22, + 21, 23, 22}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey); + +void GrFillRRectOp::onPrepare(GrOpFlushState* flushState) { + if (void* instanceData = flushState->makeVertexSpace(fInstanceStride, fInstanceCount, + &fInstanceBuffer, &fBaseInstance)) { + SkASSERT(fInstanceStride * fInstanceCount == fInstanceData.count()); + memcpy(instanceData, fInstanceData.begin(), fInstanceData.count()); + } + + if (GrAAType::kCoverage == fAAType) { + GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey); + + fIndexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( + GrGpuBufferType::kIndex, sizeof(kCoverageIndexData), kCoverageIndexData, + gCoverageIndexBufferKey); + + GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey); + + fVertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( + GrGpuBufferType::kVertex, sizeof(kCoverageVertexData), kCoverageVertexData, + gCoverageVertexBufferKey); + + fIndexCount = SK_ARRAY_COUNT(kCoverageIndexData); + } else { + GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey); + + fIndexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( + GrGpuBufferType::kIndex, sizeof(kMSAAIndexData), kMSAAIndexData, + gMSAAIndexBufferKey); + + GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey); + + fVertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( + GrGpuBufferType::kVertex, sizeof(kMSAAVertexData), kMSAAVertexData, + gMSAAVertexBufferKey); + + fIndexCount = SK_ARRAY_COUNT(kMSAAIndexData); + } +} + +class GrFillRRectOp::Processor::CoverageImpl : public GrGLSLGeometryProcessor { + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const auto& proc = args.fGP.cast(); + bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives); + + SkASSERT(proc.vertexStride() == sizeof(CoverageVertex)); + + GrGLSLVaryingHandler* varyings = args.fVaryingHandler; + varyings->emitAttributes(proc); + varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor, + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + + // Emit the vertex shader. + GrGLSLVertexBuilder* v = args.fVertBuilder; + + // Unpack vertex attribs. + v->codeAppend("float2 corner = corner_and_radius_outsets.xy;"); + v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;"); + v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;"); + v->codeAppend("float coverage = aa_bloat_and_coverage.z;"); + v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;"); + + // Find the amount to bloat each edge for AA (in source space). + v->codeAppend("float2 pixellength = inversesqrt(" + "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));"); + v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;"); + v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + " + "abs(normalized_axis_dirs.zw));"); + v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;"); + + // Identify our radii. + v->codeAppend("float4 radii_and_neighbors = radii_selector" + "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);"); + v->codeAppend("float2 radii = radii_and_neighbors.xy;"); + v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;"); + + v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {"); + // The rrect is more narrow than an AA coverage ramp. We can't draw as-is + // or else opposite AA borders will overlap. Instead, fudge the size up to + // the width of a coverage ramp, and then reduce total coverage to make + // the rect appear more thin. + v->codeAppend( "corner = max(abs(corner), aa_bloatradius) * sign(corner);"); + v->codeAppend( "coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1);"); + // Set radii to zero to ensure we take the "linear coverage" codepath. + // (The "coverage" variable only has effect in the linear codepath.) + v->codeAppend( "radii = float2(0);"); + v->codeAppend("}"); + + v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.25))) {"); + // The radii are very small. Demote this arc to a sharp 90 degree corner. + v->codeAppend( "radii = aa_bloatradius;"); + // Snap octagon vertices to the corner of the bounding box. + v->codeAppend( "radius_outset = floor(abs(radius_outset)) * radius_outset;"); + v->codeAppend( "is_linear_coverage = 1;"); + v->codeAppend("} else {"); + // Don't let radii get smaller than a pixel. + v->codeAppend( "radii = clamp(radii, pixellength, 2 - pixellength);"); + v->codeAppend( "neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength);"); + // Don't let neighboring radii get closer together than 1/16 pixel. + v->codeAppend( "float2 spacing = 2 - radii - neighbor_radii;"); + v->codeAppend( "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));"); + v->codeAppend( "radii -= extra_pad * .5;"); + v->codeAppend("}"); + + // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in + // normalized [-1,-1,+1,+1] space. + v->codeAppend("float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;"); + v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;"); + + // Emit transforms. + GrShaderVar localCoord("", kFloat2_GrSLType); + if (proc.fFlags & Flags::kHasLocalCoords) { + v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + " + "local_rect.zw * (1 + vertexpos)) * .5;"); + localCoord.set(kFloat2_GrSLType, "localcoord"); + } + this->emitTransforms(v, varyings, args.fUniformHandler, localCoord, + args.fFPCoordTransformHandler); + + // Transform to device space. + SkASSERT(!(proc.fFlags & Flags::kHasPerspective)); + v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);"); + v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;"); + gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord"); + + // Setup interpolants for coverage. + GrGLSLVarying arcCoord(useHWDerivatives ? kFloat2_GrSLType : kFloat4_GrSLType); + varyings->addVarying("arccoord", &arcCoord); + v->codeAppend("if (0 != is_linear_coverage) {"); + // We are a non-corner piece: Set x=0 to indicate built-in coverage, and + // interpolate linear coverage across y. + v->codeAppendf( "%s.xy = float2(0, coverage);", arcCoord.vsOut()); + v->codeAppend("} else {"); + // Find the normalized arc coordinates for our corner ellipse. + // (i.e., the coordinate system where x^2 + y^2 == 1). + v->codeAppend( "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;"); + // We are a corner piece: Interpolate the arc coordinates for coverage. + // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0 + // instructs the fragment shader to use linear coverage). + v->codeAppendf( "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut()); + if (!useHWDerivatives) { + // The gradient is order-1: Interpolate it across arccoord.zw. + v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);"); + v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut()); + } + v->codeAppend("}"); + + // Emit the fragment shader. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + + f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn()); + f->codeAppendf("half coverage;"); + f->codeAppendf("if (0 == x_plus_1) {"); + f->codeAppendf( "coverage = half(y);"); // We are a non-arc pixel (linear coverage). + f->codeAppendf("} else {"); + f->codeAppendf( "float fn = x_plus_1 * (x_plus_1 - 2);"); // fn = (x+1)*(x-1) = x^2-1 + f->codeAppendf( "fn = fma(y,y, fn);"); // fn = x^2 + y^2 - 1 + if (useHWDerivatives) { + f->codeAppendf("float fnwidth = fwidth(fn);"); + } else { + // The gradient is interpolated across arccoord.zw. + f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn()); + f->codeAppendf("float fnwidth = abs(gx) + abs(gy);"); + } + f->codeAppendf( "half d = half(fn/fnwidth);"); + f->codeAppendf( "coverage = clamp(.5 - d, 0, 1);"); + f->codeAppendf("}"); + f->codeAppendf("%s = half4(coverage);", args.fOutputCoverage); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, + FPCoordTransformIter&& transformIter) override { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } +}; + + +class GrFillRRectOp::Processor::MSAAImpl : public GrGLSLGeometryProcessor { + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const auto& proc = args.fGP.cast(); + bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives); + bool hasPerspective = (proc.fFlags & Flags::kHasPerspective); + bool hasLocalCoords = (proc.fFlags & Flags::kHasLocalCoords); + SkASSERT(useHWDerivatives == hasPerspective); + + SkASSERT(proc.vertexStride() == sizeof(MSAAVertex)); + + // Emit the vertex shader. + GrGLSLVertexBuilder* v = args.fVertBuilder; + + GrGLSLVaryingHandler* varyings = args.fVaryingHandler; + varyings->emitAttributes(proc); + varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor, + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + + // Unpack vertex attribs. + v->codeAppendf("float2 corner = corner_and_radius_outsets.xy;"); + v->codeAppendf("float2 radius_outset = corner_and_radius_outsets.zw;"); + + // Identify our radii. + v->codeAppend("float2 radii;"); + v->codeAppend("radii.x = dot(radii_selector, radii_x);"); + v->codeAppend("radii.y = dot(radii_selector, radii_y);"); + v->codeAppendf("bool is_arc_section = (radii.x > 0);"); + v->codeAppendf("radii = abs(radii);"); + + // Find our vertex position, adjusted for radii. Our rect is drawn in normalized + // [-1,-1,+1,+1] space. + v->codeAppend("float2 vertexpos = corner + radius_outset * radii;"); + + // Emit transforms. + GrShaderVar localCoord("", kFloat2_GrSLType); + if (hasLocalCoords) { + v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + " + "local_rect.zw * (1 + vertexpos)) * .5;"); + localCoord.set(kFloat2_GrSLType, "localcoord"); + } + this->emitTransforms(v, varyings, args.fUniformHandler, localCoord, + args.fFPCoordTransformHandler); + + // Transform to device space. + if (!hasPerspective) { + v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);"); + v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;"); + gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord"); + } else { + v->codeAppend("float3x3 persp_matrix = float3x3(persp_x, persp_y, persp_z);"); + v->codeAppend("float3 devcoord = float3(vertexpos, 1) * persp_matrix;"); + gpArgs->fPositionVar.set(kFloat3_GrSLType, "devcoord"); + } + + // Determine normalized arc coordinates for the implicit function. + GrGLSLVarying arcCoord((useHWDerivatives) ? kFloat2_GrSLType : kFloat4_GrSLType); + varyings->addVarying("arccoord", &arcCoord); + v->codeAppendf("if (is_arc_section) {"); + v->codeAppendf( "%s.xy = 1 - abs(radius_outset);", arcCoord.vsOut()); + if (!useHWDerivatives) { + // The gradient is order-1: Interpolate it across arccoord.zw. + // This doesn't work with perspective. + SkASSERT(!hasPerspective); + v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);"); + v->codeAppendf("%s.zw = derivatives * (%s.xy/radii * corner * 2);", + arcCoord.vsOut(), arcCoord.vsOut()); + } + v->codeAppendf("} else {"); + if (useHWDerivatives) { + v->codeAppendf("%s = float2(0);", arcCoord.vsOut()); + } else { + v->codeAppendf("%s = float4(0);", arcCoord.vsOut()); + } + v->codeAppendf("}"); + + // Emit the fragment shader. + GrGLSLFPFragmentBuilder* f = args.fFragBuilder; + + f->codeAppendf("%s = half4(1);", args.fOutputCoverage); + + // If x,y == 0, then we are drawing a triangle that does not track an arc. + f->codeAppendf("if (float2(0) != %s.xy) {", arcCoord.fsIn()); + f->codeAppendf( "float fn = dot(%s.xy, %s.xy) - 1;", arcCoord.fsIn(), arcCoord.fsIn()); + if (GrAAType::kMSAA == proc.fAAType) { + using ScopeFlags = GrGLSLFPFragmentBuilder::ScopeFlags; + if (!useHWDerivatives) { + f->codeAppendf("float2 grad = %s.zw;", arcCoord.fsIn()); + f->applyFnToMultisampleMask("fn", "grad", ScopeFlags::kInsidePerPrimitiveBranch); + } else { + f->applyFnToMultisampleMask("fn", nullptr, ScopeFlags::kInsidePerPrimitiveBranch); + } + } else { + f->codeAppendf("if (fn > 0) {"); + f->codeAppendf( "%s = half4(0);", args.fOutputCoverage); + f->codeAppendf("}"); + } + f->codeAppendf("}"); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, + FPCoordTransformIter&& transformIter) override { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } +}; + +GrGLSLPrimitiveProcessor* GrFillRRectOp::Processor::createGLSLInstance( + const GrShaderCaps&) const { + if (GrAAType::kCoverage != fAAType) { + return new MSAAImpl(); + } + return new CoverageImpl(); +} + +void GrFillRRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + if (!fInstanceBuffer || !fIndexBuffer || !fVertexBuffer) { + return; // Setup failed. + } + + Processor* proc = flushState->allocator()->make(fAAType, fFlags); + SkASSERT(proc->instanceStride() == (size_t)fInstanceStride); + + GrPipeline::InitArgs initArgs; + if (GrAAType::kMSAA == fAAType) { + initArgs.fInputFlags = GrPipeline::InputFlags::kHWAntialias; + } + initArgs.fCaps = &flushState->caps(); + initArgs.fDstProxy = flushState->drawOpArgs().dstProxy(); + initArgs.fOutputSwizzle = flushState->drawOpArgs().outputSwizzle(); + auto clip = flushState->detachAppliedClip(); + GrPipeline::FixedDynamicState* fixedDynamicState = + flushState->allocator()->make(clip.scissorState().rect()); + GrPipeline* pipeline = flushState->allocator()->make(initArgs, + std::move(fProcessors), + std::move(clip)); + + GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(), + flushState->drawOpArgs().origin(), + *pipeline, + *proc, + fixedDynamicState, + nullptr, 0); + + GrMesh* mesh = flushState->allocator()->make(GrPrimitiveType::kTriangles); + mesh->setIndexedInstanced( + std::move(fIndexBuffer), fIndexCount, std::move(fInstanceBuffer), fInstanceCount, + fBaseInstance, GrPrimitiveRestart::kNo); + mesh->setVertexData(std::move(fVertexBuffer)); + flushState->opsRenderPass()->draw(programInfo, mesh, 1, this->bounds()); + fIndexCount = 0; +} + +// Will the given corner look good if we use HW derivatives? +static bool can_use_hw_derivatives_with_coverage(const Sk2f& devScale, const Sk2f& cornerRadii) { + Sk2f devRadii = devScale * cornerRadii; + if (devRadii[1] < devRadii[0]) { + devRadii = SkNx_shuffle<1,0>(devRadii); + } + float minDevRadius = SkTMax(devRadii[0], 1.f); // Shader clamps radius at a minimum of 1. + // Is the gradient smooth enough for this corner look ok if we use hardware derivatives? + // This threshold was arrived at subjevtively on an NVIDIA chip. + return minDevRadius * minDevRadius * 5 > devRadii[1]; +} + +static bool can_use_hw_derivatives_with_coverage( + const Sk2f& devScale, const SkVector& cornerRadii) { + return can_use_hw_derivatives_with_coverage(devScale, Sk2f::Load(&cornerRadii)); +} + +// Will the given round rect look good if we use HW derivatives? +static bool can_use_hw_derivatives_with_coverage( + const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, const SkRRect& rrect) { + if (!shaderCaps.shaderDerivativeSupport()) { + return false; + } + + Sk2f x = Sk2f(viewMatrix.getScaleX(), viewMatrix.getSkewX()); + Sk2f y = Sk2f(viewMatrix.getSkewY(), viewMatrix.getScaleY()); + Sk2f devScale = (x*x + y*y).sqrt(); + switch (rrect.getType()) { + case SkRRect::kEmpty_Type: + case SkRRect::kRect_Type: + return true; + + case SkRRect::kOval_Type: + case SkRRect::kSimple_Type: + return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii()); + + case SkRRect::kNinePatch_Type: { + Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect)); + Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2); + Sk2f minRadii = Sk2f::Min(r0, r1); + Sk2f maxRadii = Sk2f::Max(r0, r1); + return can_use_hw_derivatives_with_coverage(devScale, Sk2f(minRadii[0], maxRadii[1])) && + can_use_hw_derivatives_with_coverage(devScale, Sk2f(maxRadii[0], minRadii[1])); + } + + case SkRRect::kComplex_Type: { + for (int i = 0; i < 4; ++i) { + auto corner = static_cast(i); + if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) { + return false; + } + } + return true; + } + } + SK_ABORT("Invalid round rect type."); +} diff --git a/gfx/skia/skia/src/gpu/ops/GrFillRRectOp.h b/gfx/skia/skia/src/gpu/ops/GrFillRRectOp.h new file mode 100644 index 0000000000..c3e759947a --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrFillRRectOp.h @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrFillRRectOp_DEFINED +#define GrFillRRectOp_DEFINED + +#include "src/gpu/ops/GrDrawOp.h" + +class GrRecordingContext; + +class GrFillRRectOp : public GrDrawOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make( + GrRecordingContext*, GrAAType, const SkMatrix& viewMatrix, const SkRRect&, + const GrCaps&, GrPaint&&); + + const char* name() const override { return "GrFillRRectOp"; } + FixedFunctionFlags fixedFunctionFlags() const override { + return (GrAAType::kMSAA == fAAType) + ? FixedFunctionFlags::kUsesHWAA + : FixedFunctionFlags::kNone; + } + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override; + CombineResult onCombineIfPossible(GrOp*, const GrCaps&) override; + void visitProxies(const VisitProxyFunc& fn) const override { + fProcessors.visitProxies(fn); + } + void onPrepare(GrOpFlushState*) override; + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + +private: + enum class Flags { + kNone = 0, + kUseHWDerivatives = 1 << 0, + kHasPerspective = 1 << 1, + kHasLocalCoords = 1 << 2, + kWideColor = 1 << 3 + }; + + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags); + + class Processor; + + GrFillRRectOp(GrAAType, const SkRRect&, Flags, const SkMatrix& totalShapeMatrix, + GrPaint&&, const SkRect& devBounds); + + // These methods are used to append data of various POD types to our internal array of instance + // data. The actual layout of the instance buffer can vary from Op to Op. + template inline T* appendInstanceData(int count) { + static_assert(std::is_pod::value, ""); + static_assert(4 == alignof(T), ""); + return reinterpret_cast(fInstanceData.push_back_n(sizeof(T) * count)); + } + + template + inline void writeInstanceData(const T& val, const Args&... remainder) { + memcpy(this->appendInstanceData(1), &val, sizeof(T)); + this->writeInstanceData(remainder...); + } + + void writeInstanceData() {} // Halt condition. + + const GrAAType fAAType; + const SkPMColor4f fOriginalColor; + const SkRect fLocalRect; + Flags fFlags; + GrProcessorSet fProcessors; + + SkSTArray fInstanceData; + int fInstanceCount = 1; + int fInstanceStride = 0; + + sk_sp fInstanceBuffer; + sk_sp fVertexBuffer; + sk_sp fIndexBuffer; + int fBaseInstance; + int fIndexCount = 0; + + friend class GrOpMemoryPool; +}; + +GR_MAKE_BITFIELD_CLASS_OPS(GrFillRRectOp::Flags) + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrFillRectOp.cpp b/gfx/skia/skia/src/gpu/ops/GrFillRectOp.cpp new file mode 100644 index 0000000000..560adc0a97 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrFillRectOp.cpp @@ -0,0 +1,447 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrFillRectOp.h" + +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrPaint.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/geometry/GrQuadBuffer.h" +#include "src/gpu/geometry/GrQuadUtils.h" +#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrQuadPerEdgeAA.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +namespace { + +using VertexSpec = GrQuadPerEdgeAA::VertexSpec; +using ColorType = GrQuadPerEdgeAA::ColorType; + +#ifdef SK_DEBUG +static SkString dump_quad_info(int index, const GrQuad& deviceQuad, + const GrQuad& localQuad, const SkPMColor4f& color, + GrQuadAAFlags aaFlags) { + SkString str; + str.appendf("%d: Color: [%.2f, %.2f, %.2f, %.2f], Edge AA: l%u_t%u_r%u_b%u, \n" + " device quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), " + "(%.2f, %.2f, %.2f)],\n" + " local quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), " + "(%.2f, %.2f, %.2f)]\n", + index, color.fR, color.fG, color.fB, color.fA, + (uint32_t) (aaFlags & GrQuadAAFlags::kLeft), + (uint32_t) (aaFlags & GrQuadAAFlags::kTop), + (uint32_t) (aaFlags & GrQuadAAFlags::kRight), + (uint32_t) (aaFlags & GrQuadAAFlags::kBottom), + deviceQuad.x(0), deviceQuad.y(0), deviceQuad.w(0), + deviceQuad.x(1), deviceQuad.y(1), deviceQuad.w(1), + deviceQuad.x(2), deviceQuad.y(2), deviceQuad.w(2), + deviceQuad.x(3), deviceQuad.y(3), deviceQuad.w(3), + localQuad.x(0), localQuad.y(0), localQuad.w(0), + localQuad.x(1), localQuad.y(1), localQuad.w(1), + localQuad.x(2), localQuad.y(2), localQuad.w(2), + localQuad.x(3), localQuad.y(3), localQuad.w(3)); + return str; +} +#endif + +class FillRectOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + GrQuadAAFlags edgeAA, + const GrUserStencilSettings* stencilSettings, + const GrQuad& deviceQuad, + const GrQuad& localQuad) { + // Clean up deviations between aaType and edgeAA + GrQuadUtils::ResolveAAType(aaType, edgeAA, deviceQuad, &aaType, &edgeAA); + return Helper::FactoryHelper(context, std::move(paint), aaType, edgeAA, + stencilSettings, deviceQuad, localQuad); + } + + // aaType is passed to Helper in the initializer list, so incongruities between aaType and + // edgeFlags must be resolved prior to calling this constructor. + FillRectOp(Helper::MakeArgs args, SkPMColor4f paintColor, GrAAType aaType, + GrQuadAAFlags edgeFlags, const GrUserStencilSettings* stencil, + const GrQuad& deviceQuad, const GrQuad& localQuad) + : INHERITED(ClassID()) + , fHelper(args, aaType, stencil) + , fQuads(1, !fHelper.isTrivial()) { + // Conservatively keep track of the local coordinates; it may be that the paint doesn't + // need them after analysis is finished. If the paint is known to be solid up front they + // can be skipped entirely. + fQuads.append(deviceQuad, { paintColor, edgeFlags }, + fHelper.isTrivial() ? nullptr : &localQuad); + this->setBounds(deviceQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage), + IsHairline::kNo); + } + + const char* name() const override { return "FillRectOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + return fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + str.appendf("# draws: %u\n", fQuads.count()); + str.appendf("Device quad type: %u, local quad type: %u\n", + (uint32_t) fQuads.deviceQuadType(), (uint32_t) fQuads.localQuadType()); + str += fHelper.dumpInfo(); + int i = 0; + auto iter = fQuads.iterator(); + while(iter.next()) { + const ColorAndAA& info = iter.metadata(); + str += dump_quad_info(i, iter.deviceQuad(), iter.localQuad(), + info.fColor, info.fAAFlags); + i++; + } + str += INHERITED::dumpInfo(); + return str; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + // Initialize aggregate color analysis with the first quad's color (which always exists) + auto iter = fQuads.metadata(); + SkAssertResult(iter.next()); + GrProcessorAnalysisColor quadColors(iter->fColor); + // Then combine the colors of any additional quads (e.g. from MakeSet) + while(iter.next()) { + quadColors = GrProcessorAnalysisColor::Combine(quadColors, iter->fColor); + if (quadColors.isUnknown()) { + // No point in accumulating additional starting colors, combining cannot make it + // less unknown. + break; + } + } + + // If the AA type is coverage, it will be a single value per pixel; if it's not coverage AA + // then the coverage is always 1.0, so specify kNone for more optimal blending. + GrProcessorAnalysisCoverage coverage = fHelper.aaType() == GrAAType::kCoverage ? + GrProcessorAnalysisCoverage::kSingleChannel : + GrProcessorAnalysisCoverage::kNone; + auto result = fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, coverage, &quadColors); + // If there is a constant color after analysis, that means all of the quads should be set + // to the same color (even if they started out with different colors). + iter = fQuads.metadata(); + SkPMColor4f colorOverride; + if (quadColors.isConstant(&colorOverride)) { + fColorType = GrQuadPerEdgeAA::MinColorType(colorOverride, clampType, caps); + while(iter.next()) { + iter->fColor = colorOverride; + } + } else { + // Otherwise compute the color type needed as the max over all quads. + fColorType = ColorType::kNone; + while(iter.next()) { + fColorType = SkTMax(fColorType, + GrQuadPerEdgeAA::MinColorType(iter->fColor, clampType, caps)); + } + } + // Most SkShaders' FPs multiply their calculated color by the paint color or alpha. We want + // to use ColorType::kNone to optimize out that multiply. However, if there are no color + // FPs then were really writing a special shader for white rectangles and not saving any + // multiples. So in that case use bytes to avoid the extra shader (and possibly work around + // an ANGLE issue: crbug.com/942565). + if (fColorType == ColorType::kNone && !result.hasColorFragmentProcessor()) { + fColorType = ColorType::kByte; + } + + return result; + } + + FixedFunctionFlags fixedFunctionFlags() const override { + // Since the AA type of the whole primitive is kept consistent with the per edge AA flags + // the helper's fixed function flags are appropriate. + return fHelper.fixedFunctionFlags(); + } + + DEFINE_OP_CLASS_ID + +private: + // For GrFillRectOp::MakeSet's use of addQuad + friend std::unique_ptr GrFillRectOp::MakeSet( + GrRecordingContext*, + GrPaint&&, + GrAAType, const SkMatrix& viewMatrix, + const GrRenderTargetContext::QuadSetEntry quads[], int quadCount, + const GrUserStencilSettings*); + + void onPrepareDraws(Target* target) override { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + using Domain = GrQuadPerEdgeAA::Domain; + static constexpr SkRect kEmptyDomain = SkRect::MakeEmpty(); + + VertexSpec vertexSpec(fQuads.deviceQuadType(), fColorType, fQuads.localQuadType(), + fHelper.usesLocalCoords(), Domain::kNo, fHelper.aaType(), + fHelper.compatibleWithCoverageAsAlpha()); + // Make sure that if the op thought it was a solid color, the vertex spec does not use + // local coords. + SkASSERT(!fHelper.isTrivial() || !fHelper.usesLocalCoords()); + + sk_sp gp = GrQuadPerEdgeAA::MakeProcessor(vertexSpec); + size_t vertexSize = gp->vertexStride(); + + sk_sp vbuffer; + int vertexOffsetInBuffer = 0; + + // Fill the allocated vertex data + void* vdata = target->makeVertexSpace( + vertexSize, fQuads.count() * vertexSpec.verticesPerQuad(), + &vbuffer, &vertexOffsetInBuffer); + if (!vdata) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + // vertices pointer advances through vdata based on Tessellate's return value + void* vertices = vdata; + auto iter = fQuads.iterator(); + while(iter.next()) { + // All entries should have local coords, or no entries should have local coords, + // matching !helper.isTrivial() (which is more conservative than helper.usesLocalCoords) + SkASSERT(iter.isLocalValid() != fHelper.isTrivial()); + auto info = iter.metadata(); + vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, iter.deviceQuad(), + info.fColor, iter.localQuad(), kEmptyDomain, info.fAAFlags); + } + + // Configure the mesh for the vertex data + GrMesh* mesh = target->allocMeshes(1); + if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, mesh, vertexSpec, fQuads.count())) { + SkDebugf("Could not allocate indices\n"); + return; + } + mesh->setVertexData(std::move(vbuffer), vertexOffsetInBuffer); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + const auto* that = t->cast(); + + if ((fHelper.aaType() == GrAAType::kCoverage || + that->fHelper.aaType() == GrAAType::kCoverage) && + fQuads.count() + that->fQuads.count() > GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer) { + // This limit on batch size seems to help on Adreno devices + return CombineResult::kCannotCombine; + } + + // Unlike most users of the draw op helper, this op can merge none-aa and coverage-aa draw + // ops together, so pass true as the last argument. + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds(), true)) { + return CombineResult::kCannotCombine; + } + + // If the paints were compatible, the trivial/solid-color state should be the same + SkASSERT(fHelper.isTrivial() == that->fHelper.isTrivial()); + + // If the processor sets are compatible, the two ops are always compatible; it just needs to + // adjust the state of the op to be the more general quad and aa types of the two ops and + // then concatenate the per-quad data. + fColorType = SkTMax(fColorType, that->fColorType); + + // The helper stores the aa type, but isCompatible(with true arg) allows the two ops' aa + // types to be none and coverage, in which case this op's aa type must be lifted to coverage + // so that quads with no aa edges can be batched with quads that have some/all edges aa'ed. + if (fHelper.aaType() == GrAAType::kNone && that->fHelper.aaType() == GrAAType::kCoverage) { + fHelper.setAAType(GrAAType::kCoverage); + } + + fQuads.concat(that->fQuads); + return CombineResult::kMerged; + } + + // Similar to onCombineIfPossible, but adds a quad assuming its op would have been compatible. + // But since it's avoiding the op list management, it must update the op's bounds. This is only + // used with quad sets, which uses the same view matrix for each quad so this assumes that the + // device quad type of the new quad is the same as the op's. + void addQuad(const GrQuad& deviceQuad, const GrQuad& localQuad, + const SkPMColor4f& color, GrQuadAAFlags edgeAA, GrAAType aaType) { + // The new quad's aa type should be the same as the first quad's or none, except when the + // first quad's aa type was already downgraded to none, in which case the stored type must + // be lifted to back to the requested type. + if (aaType != fHelper.aaType()) { + if (aaType != GrAAType::kNone) { + // Original quad was downgraded to non-aa, lift back up to this quad's required type + SkASSERT(fHelper.aaType() == GrAAType::kNone); + fHelper.setAAType(aaType); + } + // else the new quad could have been downgraded but the other quads can't be, so don't + // reset the op's accumulated aa type. + } + + // Update the bounds and add the quad to this op's storage + SkRect newBounds = this->bounds(); + newBounds.joinPossiblyEmptyRect(deviceQuad.bounds()); + this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage), + IsHairline::kNo); + fQuads.append(deviceQuad, { color, edgeAA }, fHelper.isTrivial() ? nullptr : &localQuad); + } + + struct ColorAndAA { + SkPMColor4f fColor; + GrQuadAAFlags fAAFlags; + }; + + Helper fHelper; + GrQuadBuffer fQuads; + + ColorType fColorType; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +namespace GrFillRectOp { + +std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + GrQuadAAFlags aaFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const GrUserStencilSettings* stencil) { + return FillRectOp::Make(context, std::move(paint), aaType, aaFlags, stencil, + deviceQuad, localQuad); +} + +std::unique_ptr MakeNonAARect(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& view, + const SkRect& rect, + const GrUserStencilSettings* stencil) { + return FillRectOp::Make(context, std::move(paint), GrAAType::kNone, GrQuadAAFlags::kNone, + stencil, GrQuad::MakeFromRect(rect, view), GrQuad(rect)); +} + +std::unique_ptr MakeSet(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + const SkMatrix& viewMatrix, + const GrRenderTargetContext::QuadSetEntry quads[], + int cnt, + const GrUserStencilSettings* stencilSettings) { + // First make a draw op for the first quad in the set + SkASSERT(cnt > 0); + + paint.setColor4f(quads[0].fColor); + std::unique_ptr op = FillRectOp::Make(context, std::move(paint), aaType, + quads[0].fAAFlags, stencilSettings, + GrQuad::MakeFromRect(quads[0].fRect, viewMatrix), + GrQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix)); + auto* fillRects = op->cast(); + + // Accumulate remaining quads similar to onCombineIfPossible() without creating an op + for (int i = 1; i < cnt; ++i) { + GrQuad deviceQuad = GrQuad::MakeFromRect(quads[i].fRect, viewMatrix); + + GrAAType resolvedAA; + GrQuadAAFlags resolvedEdgeFlags; + GrQuadUtils::ResolveAAType(aaType, quads[i].fAAFlags, deviceQuad, + &resolvedAA, &resolvedEdgeFlags); + + fillRects->addQuad(deviceQuad, + GrQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix), + quads[i].fColor, resolvedEdgeFlags,resolvedAA); + } + + return op; +} + +} // namespace GrFillRectOp + +#if GR_TEST_UTILS + +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/SkGr.h" + +GR_DRAW_OP_TEST_DEFINE(FillRectOp) { + SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); + SkRect rect = GrTest::TestRect(random); + + GrAAType aaType = GrAAType::kNone; + if (random->nextBool()) { + aaType = (numSamples > 1) ? GrAAType::kMSAA : GrAAType::kCoverage; + } + const GrUserStencilSettings* stencil = random->nextBool() ? nullptr + : GrGetRandomStencil(random, context); + + GrQuadAAFlags aaFlags = GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone; + + if (random->nextBool()) { + if (random->nextBool()) { + if (random->nextBool()) { + // Local matrix with a set op + uint32_t extraQuadCt = random->nextRangeU(1, 4); + SkTArray quads(extraQuadCt + 1); + quads.push_back( + {rect, SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU())), + GrTest::TestMatrixInvertible(random), aaFlags}); + for (uint32_t i = 0; i < extraQuadCt; ++i) { + GrQuadAAFlags aaFlags = GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone; + + quads.push_back( + {GrTest::TestRect(random), + SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU())), + GrTest::TestMatrixInvertible(random), aaFlags}); + } + + return GrFillRectOp::MakeSet(context, std::move(paint), aaType, viewMatrix, + quads.begin(), quads.count(), stencil); + } else { + // Single local matrix + SkMatrix localMatrix = GrTest::TestMatrixInvertible(random); + return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, + GrQuad::MakeFromRect(rect, viewMatrix), + GrQuad::MakeFromRect(rect, localMatrix), stencil); + } + } else { + // Pass local rect directly + SkRect localRect = GrTest::TestRect(random); + return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, + GrQuad::MakeFromRect(rect, viewMatrix), + GrQuad(localRect), stencil); + } + } else { + // The simplest constructor + return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, + GrQuad::MakeFromRect(rect, viewMatrix), + GrQuad(rect), stencil); + } +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrFillRectOp.h b/gfx/skia/skia/src/gpu/ops/GrFillRectOp.h new file mode 100644 index 0000000000..768ed8bc78 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrFillRectOp.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrFillRectOp_DEFINED +#define GrFillRectOp_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrRenderTargetContext.h" + +class GrDrawOp; +class GrPaint; +class GrQuad; +class GrRecordingContext; +struct GrUserStencilSettings; +class SkMatrix; +struct SkRect; + +/** + * A set of factory functions for drawing filled rectangles either coverage-antialiased, or + * non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp factories, + * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then + * the paint is unmodified and may still be used. + */ +namespace GrFillRectOp { + +std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + GrQuadAAFlags aaFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const GrUserStencilSettings* stencil = nullptr); + +// Utility function to create a non-AA rect transformed by view. This is used commonly enough in +// testing and GMs that manage ops without going through GrRTC that it's worth the convenience. +std::unique_ptr MakeNonAARect(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& view, + const SkRect& rect, + const GrUserStencilSettings* stencil = nullptr); + +// Bulk API for drawing quads with a single op +// TODO(michaelludwig) - remove if the bulk API is not useful for SkiaRenderer +std::unique_ptr MakeSet(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + const SkMatrix& viewMatrix, + const GrRenderTargetContext::QuadSetEntry quads[], + int quadCount, + const GrUserStencilSettings* stencil = nullptr); + +} // namespace GrFillRectOp + +#endif // GrFillRectOp_DEFINED diff --git a/gfx/skia/skia/src/gpu/ops/GrLatticeOp.cpp b/gfx/skia/skia/src/gpu/ops/GrLatticeOp.cpp new file mode 100644 index 0000000000..adcfe8c0b8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrLatticeOp.cpp @@ -0,0 +1,479 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkRect.h" +#include "src/core/SkLatticeIter.h" +#include "src/core/SkMatrixPriv.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrResourceProviderPriv.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/ops/GrLatticeOp.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +namespace { + +class LatticeGP : public GrGeometryProcessor { +public: + static sk_sp Make(GrGpu* gpu, + const GrTextureProxy* proxy, + sk_sp csxf, + GrSamplerState::Filter filter, + bool wideColor) { + return sk_sp( + new LatticeGP(gpu, proxy, std::move(csxf), filter, wideColor)); + } + + const char* name() const override { return "LatticeGP"; } + + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { + b->add32(GrColorSpaceXform::XformKey(fColorSpaceXform.get())); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override { + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, + FPCoordTransformIter&& transformIter) override { + const auto& latticeGP = proc.cast(); + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + fColorSpaceXformHelper.setData(pdman, latticeGP.fColorSpaceXform.get()); + } + + private: + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + using Interpolation = GrGLSLVaryingHandler::Interpolation; + const auto& latticeGP = args.fGP.cast(); + fColorSpaceXformHelper.emitCode(args.fUniformHandler, + latticeGP.fColorSpaceXform.get()); + + args.fVaryingHandler->emitAttributes(latticeGP); + this->writeOutputPosition(args.fVertBuilder, gpArgs, latticeGP.fInPosition.name()); + this->emitTransforms(args.fVertBuilder, + args.fVaryingHandler, + args.fUniformHandler, + latticeGP.fInTextureCoords.asShaderVar(), + args.fFPCoordTransformHandler); + args.fFragBuilder->codeAppend("float2 textureCoords;"); + args.fVaryingHandler->addPassThroughAttribute(latticeGP.fInTextureCoords, + "textureCoords"); + args.fFragBuilder->codeAppend("float4 textureDomain;"); + args.fVaryingHandler->addPassThroughAttribute( + latticeGP.fInTextureDomain, "textureDomain", Interpolation::kCanBeFlat); + args.fVaryingHandler->addPassThroughAttribute(latticeGP.fInColor, + args.fOutputColor, + Interpolation::kCanBeFlat); + args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor); + args.fFragBuilder->appendTextureLookupAndModulate( + args.fOutputColor, + args.fTexSamplers[0], + "clamp(textureCoords, textureDomain.xy, textureDomain.zw)", + kFloat2_GrSLType, + &fColorSpaceXformHelper); + args.fFragBuilder->codeAppend(";"); + args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage); + } + GrGLSLColorSpaceXformHelper fColorSpaceXformHelper; + }; + return new GLSLProcessor; + } + +private: + LatticeGP(GrGpu* gpu, const GrTextureProxy* proxy, sk_sp csxf, + GrSamplerState::Filter filter, bool wideColor) + : INHERITED(kLatticeGP_ClassID), fColorSpaceXform(std::move(csxf)) { + + GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp, + filter); + uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram(samplerState, + proxy->backendFormat()); + + fSampler.reset(proxy->textureType(), samplerState, proxy->textureSwizzle(), + extraSamplerKey); + this->setTextureSamplerCnt(1); + fInPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInTextureDomain = {"textureDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + fInColor = MakeColorAttribute("color", wideColor); + this->setVertexAttributes(&fInPosition, 4); + } + + const TextureSampler& onTextureSampler(int) const override { return fSampler; } + + Attribute fInPosition; + Attribute fInTextureCoords; + Attribute fInTextureDomain; + Attribute fInColor; + + sk_sp fColorSpaceXform; + TextureSampler fSampler; + + typedef GrGeometryProcessor INHERITED; +}; + +class NonAALatticeOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + static const int kVertsPerRect = 4; + static const int kIndicesPerRect = 6; + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + sk_sp proxy, + GrColorType srcColorType, + sk_sp colorSpaceXForm, + GrSamplerState::Filter filter, + std::unique_ptr iter, + const SkRect& dst) { + SkASSERT(proxy); + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, + std::move(proxy), srcColorType, + std::move(colorSpaceXForm), filter, + std::move(iter), dst); + } + + NonAALatticeOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, sk_sp proxy, + GrColorType srcColorType, sk_sp colorSpaceXform, + GrSamplerState::Filter filter, std::unique_ptr iter, + const SkRect& dst) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kNone) + , fProxy(std::move(proxy)) + , fSrcColorType(srcColorType) + , fColorSpaceXform(std::move(colorSpaceXform)) + , fFilter(filter) { + Patch& patch = fPatches.push_back(); + patch.fViewMatrix = viewMatrix; + patch.fColor = color; + patch.fIter = std::move(iter); + patch.fDst = dst; + + // setup bounds + this->setTransformedBounds(patch.fDst, viewMatrix, HasAABloat::kNo, IsHairline::kNo); + } + + const char* name() const override { return "NonAALatticeOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + bool mipped = (GrSamplerState::Filter::kMipMap == fFilter); + func(fProxy.get(), GrMipMapped(mipped)); + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + + for (int i = 0; i < fPatches.count(); ++i) { + str.appendf("%d: Color: 0x%08x Dst [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i, + fPatches[i].fColor.toBytes_RGBA(), fPatches[i].fDst.fLeft, + fPatches[i].fDst.fTop, fPatches[i].fDst.fRight, fPatches[i].fDst.fBottom); + } + + str += fHelper.dumpInfo(); + str += INHERITED::dumpInfo(); + return str; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + auto opaque = fPatches[0].fColor.isOpaque() && !GrColorTypeHasAlpha(fSrcColorType) + ? GrProcessorAnalysisColor::Opaque::kYes + : GrProcessorAnalysisColor::Opaque::kNo; + auto analysisColor = GrProcessorAnalysisColor(opaque); + auto result = fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, GrProcessorAnalysisCoverage::kNone, + &analysisColor); + analysisColor.isConstant(&fPatches[0].fColor); + fWideColor = SkPMColor4fNeedsWideColor(fPatches[0].fColor, clampType, caps); + return result; + } + +private: + void onPrepareDraws(Target* target) override { + GrGpu* gpu = target->resourceProvider()->priv().gpu(); + auto gp = LatticeGP::Make(gpu, fProxy.get(), fColorSpaceXform, fFilter, fWideColor); + if (!gp) { + SkDebugf("Couldn't create GrGeometryProcessor\n"); + return; + } + + int patchCnt = fPatches.count(); + int numRects = 0; + for (int i = 0; i < patchCnt; i++) { + numRects += fPatches[i].fIter->numRectsToDraw(); + } + + if (!numRects) { + return; + } + + const size_t kVertexStride = gp->vertexStride(); + sk_sp indexBuffer = target->resourceProvider()->refQuadIndexBuffer(); + if (!indexBuffer) { + SkDebugf("Could not allocate indices\n"); + return; + } + PatternHelper helper(target, GrPrimitiveType::kTriangles, kVertexStride, + std::move(indexBuffer), kVertsPerRect, kIndicesPerRect, numRects); + GrVertexWriter vertices{helper.vertices()}; + if (!vertices.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + for (int i = 0; i < patchCnt; i++) { + const Patch& patch = fPatches[i]; + + GrVertexColor patchColor(patch.fColor, fWideColor); + + // Apply the view matrix here if it is scale-translate. Otherwise, we need to + // wait until we've created the dst rects. + bool isScaleTranslate = patch.fViewMatrix.isScaleTranslate(); + if (isScaleTranslate) { + patch.fIter->mapDstScaleTranslate(patch.fViewMatrix); + } + + SkIRect srcR; + SkRect dstR; + SkPoint* patchPositions = reinterpret_cast(vertices.fPtr); + Sk4f scales(1.f / fProxy->width(), 1.f / fProxy->height(), + 1.f / fProxy->width(), 1.f / fProxy->height()); + static const Sk4f kDomainOffsets(0.5f, 0.5f, -0.5f, -0.5f); + static const Sk4f kFlipOffsets(0.f, 1.f, 0.f, 1.f); + static const Sk4f kFlipMuls(1.f, -1.f, 1.f, -1.f); + while (patch.fIter->next(&srcR, &dstR)) { + Sk4f coords(SkIntToScalar(srcR.fLeft), SkIntToScalar(srcR.fTop), + SkIntToScalar(srcR.fRight), SkIntToScalar(srcR.fBottom)); + Sk4f domain = coords + kDomainOffsets; + coords *= scales; + domain *= scales; + if (fProxy->origin() == kBottomLeft_GrSurfaceOrigin) { + coords = kFlipMuls * coords + kFlipOffsets; + domain = SkNx_shuffle<0, 3, 2, 1>(kFlipMuls * domain + kFlipOffsets); + } + SkRect texDomain; + SkRect texCoords; + domain.store(&texDomain); + coords.store(&texCoords); + + vertices.writeQuad(GrVertexWriter::TriStripFromRect(dstR), + GrVertexWriter::TriStripFromRect(texCoords), + texDomain, + patchColor); + } + + // If we didn't handle it above, apply the matrix here. + if (!isScaleTranslate) { + SkMatrixPriv::MapPointsWithStride(patch.fViewMatrix, patchPositions, kVertexStride, + kVertsPerRect * patch.fIter->numRectsToDraw()); + } + } + auto fixedDynamicState = target->makeFixedDynamicState(1); + fixedDynamicState->fPrimitiveProcessorTextures[0] = fProxy.get(); + helper.recordDraw(target, std::move(gp), fixedDynamicState); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + NonAALatticeOp* that = t->cast(); + if (fProxy != that->fProxy) { + return CombineResult::kCannotCombine; + } + if (fFilter != that->fFilter) { + return CombineResult::kCannotCombine; + } + if (GrColorSpaceXform::Equals(fColorSpaceXform.get(), that->fColorSpaceXform.get())) { + return CombineResult::kCannotCombine; + } + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + fPatches.move_back_n(that->fPatches.count(), that->fPatches.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + struct Patch { + SkMatrix fViewMatrix; + std::unique_ptr fIter; + SkRect fDst; + SkPMColor4f fColor; + }; + + Helper fHelper; + SkSTArray<1, Patch, true> fPatches; + sk_sp fProxy; + GrColorType fSrcColorType; + sk_sp fColorSpaceXform; + GrSamplerState::Filter fFilter; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +namespace GrLatticeOp { +std::unique_ptr MakeNonAA(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + sk_sp proxy, + GrColorType srcColorType, + sk_sp colorSpaceXform, + GrSamplerState::Filter filter, + std::unique_ptr iter, + const SkRect& dst) { + return NonAALatticeOp::Make(context, std::move(paint), viewMatrix, std::move(proxy), + srcColorType, std::move(colorSpaceXform), filter, std::move(iter), + dst); +} +}; + +#if GR_TEST_UTILS +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" + +/** Randomly divides subset into count divs. */ +static void init_random_divs(int divs[], int count, int subsetStart, int subsetStop, + SkRandom* random) { + // Rules for lattice divs: Must be strictly increasing and in the range + // [subsetStart, subsetStop). + // Not terribly efficient alg for generating random divs: + // 1) Start with minimum legal pixels between each div. + // 2) Randomly assign the remaining pixels of the subset to divs. + // 3) Convert from pixel counts to div offsets. + + // 1) Initially each divs[i] represents the number of pixels between + // div i-1 and i. The initial div is allowed to be at subsetStart. There + // must be one pixel spacing between subsequent divs. + divs[0] = 0; + for (int i = 1; i < count; ++i) { + divs[i] = 1; + } + // 2) Assign the remaining subset pixels to fall + int subsetLength = subsetStop - subsetStart; + for (int i = 0; i < subsetLength - count; ++i) { + // +1 because count divs means count+1 intervals. + int entry = random->nextULessThan(count + 1); + // We don't have an entry to to store the count after the last div + if (entry < count) { + divs[entry]++; + } + } + // 3) Now convert the counts between divs to pixel indices, incorporating the subset's offset. + int offset = subsetStart; + for (int i = 0; i < count; ++i) { + divs[i] += offset; + offset = divs[i]; + } +} + +GR_DRAW_OP_TEST_DEFINE(NonAALatticeOp) { + SkCanvas::Lattice lattice; + // We loop because our random lattice code can produce an invalid lattice in the case where + // there is a single div separator in both x and y and both are aligned with the left and top + // edge of the image subset, respectively. + std::unique_ptr xdivs; + std::unique_ptr ydivs; + std::unique_ptr flags; + std::unique_ptr colors; + SkIRect subset; + GrSurfaceDesc desc; + desc.fConfig = kRGBA_8888_GrPixelConfig; + desc.fWidth = random->nextRangeU(1, 1000); + desc.fHeight = random->nextRangeU(1, 1000); + GrSurfaceOrigin origin = random->nextBool() ? kTopLeft_GrSurfaceOrigin + : kBottomLeft_GrSurfaceOrigin; + const GrBackendFormat format = + context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, + GrRenderable::kNo); + auto proxy = context->priv().proxyProvider()->createProxy(format, + desc, + GrRenderable::kNo, + 1, + origin, + GrMipMapped::kNo, + SkBackingFit::kExact, + SkBudgeted::kYes, + GrProtected::kNo); + + do { + if (random->nextBool()) { + subset.fLeft = random->nextULessThan(desc.fWidth); + subset.fRight = random->nextRangeU(subset.fLeft + 1, desc.fWidth); + subset.fTop = random->nextULessThan(desc.fHeight); + subset.fBottom = random->nextRangeU(subset.fTop + 1, desc.fHeight); + } else { + subset.setXYWH(0, 0, desc.fWidth, desc.fHeight); + } + // SkCanvas::Lattice allows bounds to be null. However, SkCanvas creates a temp Lattice with + // a non-null bounds before creating a SkLatticeIter since SkLatticeIter requires a bounds. + lattice.fBounds = ⊂ + lattice.fXCount = random->nextRangeU(1, subset.width()); + lattice.fYCount = random->nextRangeU(1, subset.height()); + xdivs.reset(new int[lattice.fXCount]); + ydivs.reset(new int[lattice.fYCount]); + init_random_divs(xdivs.get(), lattice.fXCount, subset.fLeft, subset.fRight, random); + init_random_divs(ydivs.get(), lattice.fYCount, subset.fTop, subset.fBottom, random); + lattice.fXDivs = xdivs.get(); + lattice.fYDivs = ydivs.get(); + bool hasFlags = random->nextBool(); + if (hasFlags) { + int n = (lattice.fXCount + 1) * (lattice.fYCount + 1); + flags.reset(new SkCanvas::Lattice::RectType[n]); + colors.reset(new SkColor[n]); + for (int i = 0; i < n; ++i) { + flags[i] = random->nextBool() ? SkCanvas::Lattice::kTransparent + : SkCanvas::Lattice::kDefault; + } + lattice.fRectTypes = flags.get(); + lattice.fColors = colors.get(); + } else { + lattice.fRectTypes = nullptr; + lattice.fColors = nullptr; + } + } while (!SkLatticeIter::Valid(desc.fWidth, desc.fHeight, lattice)); + SkRect dst; + dst.fLeft = random->nextRangeScalar(-2000.5f, 1000.f); + dst.fTop = random->nextRangeScalar(-2000.5f, 1000.f); + dst.fRight = dst.fLeft + random->nextRangeScalar(0.5f, 1000.f); + dst.fBottom = dst.fTop + random->nextRangeScalar(0.5f, 1000.f); + std::unique_ptr iter(new SkLatticeIter(lattice, dst)); + SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); + auto csxf = GrTest::TestColorXform(random); + GrSamplerState::Filter filter = + random->nextBool() ? GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kBilerp; + return NonAALatticeOp::Make(context, std::move(paint), viewMatrix, std::move(proxy), + GrColorType::kRGBA_8888, std::move(csxf), filter, std::move(iter), + dst); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrLatticeOp.h b/gfx/skia/skia/src/gpu/ops/GrLatticeOp.h new file mode 100644 index 0000000000..1ea57258a7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrLatticeOp.h @@ -0,0 +1,36 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GLatticeOp_DEFINED +#define GLatticeOp_DEFINED + +#include +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrSamplerState.h" + +class GrColorSpaceXform; +class GrDrawOp; +class GrPaint; +class SkLatticeIter; +class GrRecordingContext; +class GrTextureProxy; +class SkMatrix; +struct SkRect; + +namespace GrLatticeOp { +std::unique_ptr MakeNonAA(GrRecordingContext*, + GrPaint&&, + const SkMatrix& viewMatrix, + sk_sp, + GrColorType srcColorType, + sk_sp, + GrSamplerState::Filter, + std::unique_ptr, + const SkRect& dst); +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.cpp b/gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.cpp new file mode 100644 index 0000000000..0ba8e43ce7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrMeshDrawOp.h" + +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrOpsRenderPass.h" +#include "src/gpu/GrResourceProvider.h" + +GrMeshDrawOp::GrMeshDrawOp(uint32_t classID) : INHERITED(classID) {} + +void GrMeshDrawOp::onPrepare(GrOpFlushState* state) { this->onPrepareDraws(state); } + +////////////////////////////////////////////////////////////////////////////// + +GrMeshDrawOp::PatternHelper::PatternHelper(Target* target, GrPrimitiveType primitiveType, + size_t vertexStride, sk_sp indexBuffer, + int verticesPerRepetition, int indicesPerRepetition, + int repeatCount) { + this->init(target, primitiveType, vertexStride, std::move(indexBuffer), verticesPerRepetition, + indicesPerRepetition, repeatCount); +} + +void GrMeshDrawOp::PatternHelper::init(Target* target, GrPrimitiveType primitiveType, + size_t vertexStride, sk_sp indexBuffer, + int verticesPerRepetition, int indicesPerRepetition, + int repeatCount) { + SkASSERT(target); + if (!indexBuffer) { + return; + } + sk_sp vertexBuffer; + int firstVertex; + int vertexCount = verticesPerRepetition * repeatCount; + fVertices = target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex); + if (!fVertices) { + SkDebugf("Vertices could not be allocated for patterned rendering."); + return; + } + SkASSERT(vertexBuffer); + size_t ibSize = indexBuffer->size(); + int maxRepetitions = static_cast(ibSize / (sizeof(uint16_t) * indicesPerRepetition)); + fMesh = target->allocMesh(primitiveType); + fMesh->setIndexedPatterned(std::move(indexBuffer), indicesPerRepetition, verticesPerRepetition, + repeatCount, maxRepetitions); + fMesh->setVertexData(std::move(vertexBuffer), firstVertex); +} + +void GrMeshDrawOp::PatternHelper::recordDraw( + Target* target, sk_sp gp) const { + target->recordDraw(std::move(gp), fMesh); +} + +void GrMeshDrawOp::PatternHelper::recordDraw( + Target* target, sk_sp gp, + const GrPipeline::FixedDynamicState* fixedDynamicState) const { + target->recordDraw(std::move(gp), fMesh, 1, fixedDynamicState, nullptr); +} + +////////////////////////////////////////////////////////////////////////////// + +GrMeshDrawOp::QuadHelper::QuadHelper(Target* target, size_t vertexStride, int quadsToDraw) { + sk_sp quadIndexBuffer = target->resourceProvider()->refQuadIndexBuffer(); + if (!quadIndexBuffer) { + SkDebugf("Could not get quad index buffer."); + return; + } + this->init(target, GrPrimitiveType::kTriangles, vertexStride, std::move(quadIndexBuffer), + kVerticesPerQuad, kIndicesPerQuad, quadsToDraw); +} + +////////////////////////////////////////////////////////////////////////////// + +GrPipeline::DynamicStateArrays* GrMeshDrawOp::Target::allocDynamicStateArrays( + int numMeshes, int numPrimitiveProcessorTextures, bool allocScissors) { + auto result = this->allocator()->make(); + if (allocScissors) { + result->fScissorRects = this->allocator()->makeArray(numMeshes); + } + if (numPrimitiveProcessorTextures) { + result->fPrimitiveProcessorTextures = + this->allocator()->makeArrayDefault( + numPrimitiveProcessorTextures * numMeshes); + } + return result; +} + +GrPipeline::FixedDynamicState* GrMeshDrawOp::Target::makeFixedDynamicState( + int numPrimProcTextures) { + const GrAppliedClip* clip = this->appliedClip(); + if ((clip && clip->scissorState().enabled()) || numPrimProcTextures) { + const SkIRect& scissor = (clip) ? clip->scissorState().rect() : SkIRect::MakeEmpty(); + auto fixedDynamicState = + this->allocator()->make(scissor); + if (numPrimProcTextures) { + fixedDynamicState->fPrimitiveProcessorTextures = + this->allocator()->makeArrayDefault(numPrimProcTextures); + } + return fixedDynamicState; + } + return nullptr; +} diff --git a/gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.h b/gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.h new file mode 100644 index 0000000000..6bdb63768a --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrMeshDrawOp.h @@ -0,0 +1,181 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrMeshDrawOp_DEFINED +#define GrMeshDrawOp_DEFINED + +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/ops/GrDrawOp.h" +#include + +class GrAtlasManager; +class GrCaps; +class GrStrikeCache; +class GrOpFlushState; + +/** + * Base class for mesh-drawing GrDrawOps. + */ +class GrMeshDrawOp : public GrDrawOp { +public: + /** Abstract interface that represents a destination for a GrMeshDrawOp. */ + class Target; + +protected: + GrMeshDrawOp(uint32_t classID); + + /** Helper for rendering repeating meshes using a patterned index buffer. This class creates the + space for the vertices and flushes the draws to the GrMeshDrawOp::Target. */ + class PatternHelper { + public: + PatternHelper(Target*, GrPrimitiveType, size_t vertexStride, + sk_sp indexBuffer, int verticesPerRepetition, + int indicesPerRepetition, int repeatCount); + + /** Called to issue draws to the GrMeshDrawOp::Target.*/ + void recordDraw(Target*, sk_sp) const; + void recordDraw(Target*, sk_sp, + const GrPipeline::FixedDynamicState*) const; + + void* vertices() const { return fVertices; } + + protected: + PatternHelper() = default; + void init(Target*, GrPrimitiveType, size_t vertexStride, sk_sp indexBuffer, + int verticesPerRepetition, int indicesPerRepetition, int repeatCount); + + private: + void* fVertices = nullptr; + GrMesh* fMesh = nullptr; + }; + + static const int kVerticesPerQuad = 4; + static const int kIndicesPerQuad = 6; + + /** A specialization of InstanceHelper for quad rendering. */ + class QuadHelper : private PatternHelper { + public: + QuadHelper() = delete; + QuadHelper(Target* target, size_t vertexStride, int quadsToDraw); + + using PatternHelper::recordDraw; + using PatternHelper::vertices; + + private: + typedef PatternHelper INHERITED; + }; + +private: + void onPrePrepare(GrRecordingContext* context) final { this->onPrePrepareDraws(context); } + void onPrepare(GrOpFlushState* state) final; + + // Only the GrTextureOp currently overrides this virtual + virtual void onPrePrepareDraws(GrRecordingContext*) {} + + virtual void onPrepareDraws(Target*) = 0; + typedef GrDrawOp INHERITED; +}; + +class GrMeshDrawOp::Target { +public: + virtual ~Target() {} + + /** Adds a draw of a mesh. */ + virtual void recordDraw( + sk_sp, const GrMesh[], int meshCnt, + const GrPipeline::FixedDynamicState*, const GrPipeline::DynamicStateArrays*) = 0; + + /** + * Helper for drawing GrMesh(es) with zero primProc textures and no dynamic state besides the + * scissor clip. + */ + void recordDraw(sk_sp gp, const GrMesh meshes[], int meshCnt = 1) { + static constexpr int kZeroPrimProcTextures = 0; + auto fixedDynamicState = this->makeFixedDynamicState(kZeroPrimProcTextures); + this->recordDraw(std::move(gp), meshes, meshCnt, fixedDynamicState, nullptr); + } + + /** + * Makes space for vertex data. The returned pointer is the location where vertex data + * should be written. On return the buffer that will hold the data as well as an offset into + * the buffer (in 'vertexSize' units) where the data will be placed. + */ + virtual void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp*, + int* startVertex) = 0; + + /** + * Makes space for index data. The returned pointer is the location where index data + * should be written. On return the buffer that will hold the data as well as an offset into + * the buffer (in uint16_t units) where the data will be placed. + */ + virtual uint16_t* makeIndexSpace(int indexCount, sk_sp*, int* startIndex) = 0; + + /** + * This is similar to makeVertexSpace. It allows the caller to use up to 'actualVertexCount' + * vertices in the returned pointer, which may exceed 'minVertexCount'. + * 'fallbackVertexCount' is the maximum number of vertices that should be allocated if a new + * buffer is allocated on behalf of this request. + */ + virtual void* makeVertexSpaceAtLeast(size_t vertexSize, int minVertexCount, + int fallbackVertexCount, sk_sp*, + int* startVertex, int* actualVertexCount) = 0; + + /** + * This is similar to makeIndexSpace. It allows the caller to use up to 'actualIndexCount' + * indices in the returned pointer, which may exceed 'minIndexCount'. + * 'fallbackIndexCount' is the maximum number of indices that should be allocated if a new + * buffer is allocated on behalf of this request. + */ + virtual uint16_t* makeIndexSpaceAtLeast(int minIndexCount, int fallbackIndexCount, + sk_sp*, int* startIndex, + int* actualIndexCount) = 0; + + /** Helpers for ops which over-allocate and then return excess data to the pool. */ + virtual void putBackIndices(int indices) = 0; + virtual void putBackVertices(int vertices, size_t vertexStride) = 0; + + GrMesh* allocMesh(GrPrimitiveType primitiveType) { + return this->allocator()->make(primitiveType); + } + + GrMesh* allocMeshes(int n) { return this->allocator()->makeArray(n); } + + GrPipeline::DynamicStateArrays* allocDynamicStateArrays(int numMeshes, + int numPrimitiveProcessorTextures, + bool allocScissors); + + GrPipeline::FixedDynamicState* makeFixedDynamicState(int numPrimitiveProcessorTextures); + + virtual GrRenderTargetProxy* proxy() const = 0; + + virtual const GrAppliedClip* appliedClip() = 0; + virtual GrAppliedClip detachAppliedClip() = 0; + + virtual const GrXferProcessor::DstProxy& dstProxy() const = 0; + + virtual GrResourceProvider* resourceProvider() const = 0; + uint32_t contextUniqueID() const { return this->resourceProvider()->contextUniqueID(); } + + virtual GrStrikeCache* glyphCache() const = 0; + virtual GrAtlasManager* atlasManager() const = 0; + + // This should be called during onPrepare of a GrOp. The caller should add any proxies to the + // array it will use that it did not access during a call to visitProxies. This is usually the + // case for atlases. + virtual SkTArray* sampledProxyArray() = 0; + + virtual const GrCaps& caps() const = 0; + + virtual GrDeferredUploadTarget* deferredUploadTarget() = 0; + +private: + virtual SkArenaAlloc* allocator() = 0; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrOp.cpp b/gfx/skia/skia/src/gpu/ops/GrOp.cpp new file mode 100644 index 0000000000..30e2e44896 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrOp.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrOp.h" + +std::atomic GrOp::gCurrOpClassID {GrOp::kIllegalOpID + 1}; +std::atomic GrOp::gCurrOpUniqueID{GrOp::kIllegalOpID + 1}; + +#ifdef SK_DEBUG +void* GrOp::operator new(size_t size) { + // All GrOp-derived class should be allocated in a GrMemoryPool + SkASSERT(0); + return ::operator new(size); +} + +void GrOp::operator delete(void* target) { + // All GrOp-derived class should be released from their owning GrMemoryPool + SkASSERT(0); + ::operator delete(target); +} +#endif + +GrOp::GrOp(uint32_t classID) : fClassID(classID) { + SkASSERT(classID == SkToU32(fClassID)); + SkASSERT(classID); + SkDEBUGCODE(fBoundsFlags = kUninitialized_BoundsFlag); +} + +GrOp::CombineResult GrOp::combineIfPossible(GrOp* that, const GrCaps& caps) { + SkASSERT(this != that); + if (this->classID() != that->classID()) { + return CombineResult::kCannotCombine; + } + auto result = this->onCombineIfPossible(that, caps); + if (result == CombineResult::kMerged) { + this->joinBounds(*that); + } + return result; +} + +void GrOp::chainConcat(std::unique_ptr next) { + SkASSERT(next); + SkASSERT(this->classID() == next->classID()); + SkASSERT(this->isChainTail()); + SkASSERT(next->isChainHead()); + fNextInChain = std::move(next); + fNextInChain->fPrevInChain = this; +} + +std::unique_ptr GrOp::cutChain() { + if (fNextInChain) { + fNextInChain->fPrevInChain = nullptr; + return std::move(fNextInChain); + } + return nullptr; +} + +#ifdef SK_DEBUG +void GrOp::validateChain(GrOp* expectedTail) const { + SkASSERT(this->isChainHead()); + uint32_t classID = this->classID(); + const GrOp* op = this; + while (op) { + SkASSERT(op == this || (op->prevInChain() && op->prevInChain()->nextInChain() == op)); + SkASSERT(classID == op->classID()); + if (op->nextInChain()) { + SkASSERT(op->nextInChain()->prevInChain() == op); + SkASSERT(op != expectedTail); + } else { + SkASSERT(!expectedTail || op == expectedTail); + } + op = op->nextInChain(); + } +} +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrOp.h b/gfx/skia/skia/src/gpu/ops/GrOp.h new file mode 100644 index 0000000000..ac738f9af5 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrOp.h @@ -0,0 +1,337 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOp_DEFINED +#define GrOp_DEFINED + +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" +#include "include/core/SkString.h" +#include "include/gpu/GrGpuResource.h" +#include "src/gpu/GrNonAtomicRef.h" +#include "src/gpu/GrTracing.h" +#include "src/gpu/GrXferProcessor.h" +#include +#include + +class GrCaps; +class GrOpFlushState; +class GrOpsRenderPass; + +/** + * GrOp is the base class for all Ganesh deferred GPU operations. To facilitate reordering and to + * minimize draw calls, Ganesh does not generate geometry inline with draw calls. Instead, it + * captures the arguments to the draw and then generates the geometry when flushing. This gives GrOp + * subclasses complete freedom to decide how/when to combine in order to produce fewer draw calls + * and minimize state changes. + * + * Ops of the same subclass may be merged or chained using combineIfPossible. When two ops merge, + * one takes on the union of the data and the other is left empty. The merged op becomes responsible + * for drawing the data from both the original ops. When ops are chained each op maintains its own + * data but they are linked in a list and the head op becomes responsible for executing the work for + * the chain. + * + * It is required that chainability is transitive. Moreover, if op A is able to merge with B then + * it must be the case that any op that can chain with A will either merge or chain with any op + * that can chain to B. + * + * The bounds of the op must contain all the vertices in device space *irrespective* of the clip. + * The bounds are used in determining which clip elements must be applied and thus the bounds cannot + * in turn depend upon the clip. + */ +#define GR_OP_SPEW 0 +#if GR_OP_SPEW + #define GrOP_SPEW(code) code + #define GrOP_INFO(...) SkDebugf(__VA_ARGS__) +#else + #define GrOP_SPEW(code) + #define GrOP_INFO(...) +#endif + +// Print out op information at flush time +#define GR_FLUSH_TIME_OP_SPEW 0 + +// A helper macro to generate a class static id +#define DEFINE_OP_CLASS_ID \ + static uint32_t ClassID() { \ + static uint32_t kClassID = GenOpClassID(); \ + return kClassID; \ + } + +class GrOp : private SkNoncopyable { +public: + virtual ~GrOp() = default; + + virtual const char* name() const = 0; + + using VisitProxyFunc = std::function; + + virtual void visitProxies(const VisitProxyFunc&) const { + // This default implementation assumes the op has no proxies + } + + enum class CombineResult { + /** + * The op that combineIfPossible was called on now represents its own work plus that of + * the passed op. The passed op should be destroyed without being flushed. Currently it + * is not legal to merge an op passed to combineIfPossible() the passed op is already in a + * chain (though the op on which combineIfPossible() was called may be). + */ + kMerged, + /** + * The caller *may* (but is not required) to chain these ops together. If they are chained + * then prepare() and execute() will be called on the head op but not the other ops in the + * chain. The head op will prepare and execute on behalf of all the ops in the chain. + */ + kMayChain, + /** + * The ops cannot be combined. + */ + kCannotCombine + }; + + CombineResult combineIfPossible(GrOp* that, const GrCaps& caps); + + const SkRect& bounds() const { + SkASSERT(kUninitialized_BoundsFlag != fBoundsFlags); + return fBounds; + } + + void setClippedBounds(const SkRect& clippedBounds) { + fBounds = clippedBounds; + // The clipped bounds already incorporate any effect of the bounds flags. + fBoundsFlags = 0; + } + + bool hasAABloat() const { + SkASSERT(fBoundsFlags != kUninitialized_BoundsFlag); + return SkToBool(fBoundsFlags & kAABloat_BoundsFlag); + } + + bool hasZeroArea() const { + SkASSERT(fBoundsFlags != kUninitialized_BoundsFlag); + return SkToBool(fBoundsFlags & kZeroArea_BoundsFlag); + } + +#ifdef SK_DEBUG + // All GrOp-derived classes should be allocated in and deleted from a GrMemoryPool + void* operator new(size_t size); + void operator delete(void* target); + + void* operator new(size_t size, void* placement) { + return ::operator new(size, placement); + } + void operator delete(void* target, void* placement) { + ::operator delete(target, placement); + } +#endif + + /** + * Helper for safely down-casting to a GrOp subclass + */ + template const T& cast() const { + SkASSERT(T::ClassID() == this->classID()); + return *static_cast(this); + } + + template T* cast() { + SkASSERT(T::ClassID() == this->classID()); + return static_cast(this); + } + + uint32_t classID() const { SkASSERT(kIllegalOpID != fClassID); return fClassID; } + + // We lazily initialize the uniqueID because currently the only user is GrAuditTrail + uint32_t uniqueID() const { + if (kIllegalOpID == fUniqueID) { + fUniqueID = GenOpID(); + } + return fUniqueID; + } + + /** + * This can optionally be called before 'prepare' (but after sorting). Each op that overrides + * onPrePrepare must be prepared to handle both cases (when onPrePrepare has been called + * ahead of time and when it has not been called). + */ + void prePrepare(GrRecordingContext* context) { this->onPrePrepare(context); } + + /** + * Called prior to executing. The op should perform any resource creation or data transfers + * necessary before execute() is called. + */ + void prepare(GrOpFlushState* state) { this->onPrepare(state); } + + /** Issues the op's commands to GrGpu. */ + void execute(GrOpFlushState* state, const SkRect& chainBounds) { + TRACE_EVENT0("skia.gpu", name()); + this->onExecute(state, chainBounds); + } + + /** Used for spewing information about ops when debugging. */ +#ifdef SK_DEBUG + virtual SkString dumpInfo() const { + SkString string; + string.appendf("OpBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", + fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom); + return string; + } +#else + SkString dumpInfo() const { return SkString(""); } +#endif + + /** + * A helper for iterating over an op chain in a range for loop that also downcasts to a GrOp + * subclass. E.g.: + * for (MyOpSubClass& op : ChainRange(this)) { + * // ... + * } + */ + template class ChainRange { + private: + class Iter { + public: + explicit Iter(const OpSubclass* head) : fCurr(head) {} + inline Iter& operator++() { + return *this = Iter(static_cast(fCurr->nextInChain())); + } + const OpSubclass& operator*() const { return *fCurr; } + bool operator!=(const Iter& that) const { return fCurr != that.fCurr; } + + private: + const OpSubclass* fCurr; + }; + const OpSubclass* fHead; + + public: + explicit ChainRange(const OpSubclass* head) : fHead(head) {} + Iter begin() { return Iter(fHead); } + Iter end() { return Iter(nullptr); } + }; + + /** + * Concatenates two op chains. This op must be a tail and the passed op must be a head. The ops + * must be of the same subclass. + */ + void chainConcat(std::unique_ptr); + /** Returns true if this is the head of a chain (including a length 1 chain). */ + bool isChainHead() const { return !fPrevInChain; } + /** Returns true if this is the tail of a chain (including a length 1 chain). */ + bool isChainTail() const { return !fNextInChain; } + /** The next op in the chain. */ + GrOp* nextInChain() const { return fNextInChain.get(); } + /** The previous op in the chain. */ + GrOp* prevInChain() const { return fPrevInChain; } + /** + * Cuts the chain after this op. The returned op is the op that was previously next in the + * chain or null if this was already a tail. + */ + std::unique_ptr cutChain(); + SkDEBUGCODE(void validateChain(GrOp* expectedTail = nullptr) const); + +#ifdef SK_DEBUG + virtual void validate() const {} +#endif + +protected: + GrOp(uint32_t classID); + + /** + * Indicates that the op will produce geometry that extends beyond its bounds for the + * purpose of ensuring that the fragment shader runs on partially covered pixels for + * non-MSAA antialiasing. + */ + enum class HasAABloat : bool { + kNo = false, + kYes = true + }; + /** + * Indicates that the geometry being drawn in a hairline stroke. A point that is drawn in device + * space is also considered a hairline. + */ + enum class IsHairline : bool { + kNo = false, + kYes = true + }; + + void setBounds(const SkRect& newBounds, HasAABloat aabloat, IsHairline zeroArea) { + fBounds = newBounds; + this->setBoundsFlags(aabloat, zeroArea); + } + void setTransformedBounds(const SkRect& srcBounds, const SkMatrix& m, + HasAABloat aabloat, IsHairline zeroArea) { + m.mapRect(&fBounds, srcBounds); + this->setBoundsFlags(aabloat, zeroArea); + } + void makeFullScreen(GrSurfaceProxy* proxy) { + this->setBounds(SkRect::MakeIWH(proxy->width(), proxy->height()), + HasAABloat::kNo, IsHairline::kNo); + } + + static uint32_t GenOpClassID() { return GenID(&gCurrOpClassID); } + +private: + void joinBounds(const GrOp& that) { + if (that.hasAABloat()) { + fBoundsFlags |= kAABloat_BoundsFlag; + } + if (that.hasZeroArea()) { + fBoundsFlags |= kZeroArea_BoundsFlag; + } + return fBounds.joinPossiblyEmptyRect(that.fBounds); + } + + virtual CombineResult onCombineIfPossible(GrOp*, const GrCaps&) { + return CombineResult::kCannotCombine; + } + + // Only GrMeshDrawOp currently overrides this virtual + virtual void onPrePrepare(GrRecordingContext*) {} + virtual void onPrepare(GrOpFlushState*) = 0; + // If this op is chained then chainBounds is the union of the bounds of all ops in the chain. + // Otherwise, this op's bounds. + virtual void onExecute(GrOpFlushState*, const SkRect& chainBounds) = 0; + + static uint32_t GenID(std::atomic* idCounter) { + uint32_t id = (*idCounter)++; + if (id == 0) { + SK_ABORT("This should never wrap as it should only be called once for each GrOp " + "subclass."); + } + return id; + } + + void setBoundsFlags(HasAABloat aabloat, IsHairline zeroArea) { + fBoundsFlags = 0; + fBoundsFlags |= (HasAABloat::kYes == aabloat) ? kAABloat_BoundsFlag : 0; + fBoundsFlags |= (IsHairline ::kYes == zeroArea) ? kZeroArea_BoundsFlag : 0; + } + + enum { + kIllegalOpID = 0, + }; + + enum BoundsFlags { + kAABloat_BoundsFlag = 0x1, + kZeroArea_BoundsFlag = 0x2, + SkDEBUGCODE(kUninitialized_BoundsFlag = 0x4) + }; + + std::unique_ptr fNextInChain; + GrOp* fPrevInChain = nullptr; + const uint16_t fClassID; + uint16_t fBoundsFlags; + + static uint32_t GenOpID() { return GenID(&gCurrOpUniqueID); } + mutable uint32_t fUniqueID = SK_InvalidUniqueID; + SkRect fBounds; + + static std::atomic gCurrOpUniqueID; + static std::atomic gCurrOpClassID; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.cpp b/gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.cpp new file mode 100644 index 0000000000..b32758150f --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.cpp @@ -0,0 +1,3266 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStrokeRec.h" +#include "src/core/SkRRectPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/glsl/GrGLSLUtil.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrOvalOpFactory.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +#include + +namespace { + +static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); } + +// Produces TriStrip vertex data for an origin-centered rectangle from [-x, -y] to [x, y] +static inline GrVertexWriter::TriStrip origin_centered_tri_strip(float x, float y) { + return GrVertexWriter::TriStrip{ -x, -y, x, y }; +}; + +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * The output of this effect is a modulation of the input color and coverage for a circle. It + * operates in a space normalized by the circle radius (outer radius in the case of a stroke) + * with origin at the circle center. Three vertex attributes are used: + * vec2f : position in device space of the bounding geometry vertices + * vec4ub: color + * vec4f : (p.xy, outerRad, innerRad) + * p is the position in the normalized space. + * outerRad is the outerRadius in device space. + * innerRad is the innerRadius in normalized space (ignored if not stroking). + * Additional clip planes are supported for rendering circular arcs. The additional planes are + * either intersected or unioned together. Up to three planes are supported (an initial plane, + * a plane intersected with the initial plane, and a plane unioned with the first two). Only two + * are useful for any given arc, but having all three in one instance allows combining different + * types of arcs. + * Round caps for stroking are allowed as well. The caps are specified as two circle center points + * in the same space as p.xy. + */ + +class CircleGeometryProcessor : public GrGeometryProcessor { +public: + CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane, + bool roundCaps, bool wideColor, const SkMatrix& localMatrix) + : INHERITED(kCircleGeometryProcessor_ClassID) + , fLocalMatrix(localMatrix) + , fStroke(stroke) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = MakeColorAttribute("inColor", wideColor); + fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + + if (clipPlane) { + fInClipPlane = {"inClipPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; + } + if (isectPlane) { + fInIsectPlane = {"inIsectPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; + } + if (unionPlane) { + fInUnionPlane = {"inUnionPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; + } + if (roundCaps) { + SkASSERT(stroke); + SkASSERT(clipPlane); + fInRoundCapCenters = + {"inRoundCapCenters", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + } + this->setVertexAttributes(&fInPosition, 7); + } + + ~CircleGeometryProcessor() override {} + + const char* name() const override { return "CircleEdge"; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLProcessor::GenKey(*this, caps, b); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new GLSLProcessor(); + } + +private: + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + GLSLProcessor() {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const CircleGeometryProcessor& cgp = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + // emit attributes + varyingHandler->emitAttributes(cgp); + fragBuilder->codeAppend("float4 circleEdge;"); + varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge, "circleEdge"); + if (cgp.fInClipPlane.isInitialized()) { + fragBuilder->codeAppend("half3 clipPlane;"); + varyingHandler->addPassThroughAttribute(cgp.fInClipPlane, "clipPlane"); + } + if (cgp.fInIsectPlane.isInitialized()) { + fragBuilder->codeAppend("half3 isectPlane;"); + varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane, "isectPlane"); + } + if (cgp.fInUnionPlane.isInitialized()) { + SkASSERT(cgp.fInClipPlane.isInitialized()); + fragBuilder->codeAppend("half3 unionPlane;"); + varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane, "unionPlane"); + } + GrGLSLVarying capRadius(kFloat_GrSLType); + if (cgp.fInRoundCapCenters.isInitialized()) { + fragBuilder->codeAppend("float4 roundCapCenters;"); + varyingHandler->addPassThroughAttribute(cgp.fInRoundCapCenters, "roundCapCenters"); + varyingHandler->addVarying("capRadius", &capRadius, + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + // This is the cap radius in normalized space where the outer radius is 1 and + // circledEdge.w is the normalized inner radius. + vertBuilder->codeAppendf("%s = (1.0 - %s.w) / 2.0;", capRadius.vsOut(), + cgp.fInCircleEdge.name()); + } + + // setup pass through color + varyingHandler->addPassThroughAttribute(cgp.fInColor, args.fOutputColor); + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, cgp.fInPosition.name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + cgp.fInPosition.asShaderVar(), + cgp.fLocalMatrix, + args.fFPCoordTransformHandler); + + fragBuilder->codeAppend("float d = length(circleEdge.xy);"); + fragBuilder->codeAppend("half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));"); + fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);"); + if (cgp.fStroke) { + fragBuilder->codeAppend( + "half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w));"); + fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);"); + fragBuilder->codeAppend("edgeAlpha *= innerAlpha;"); + } + + if (cgp.fInClipPlane.isInitialized()) { + fragBuilder->codeAppend( + "half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, " + "clipPlane.xy) + clipPlane.z));"); + if (cgp.fInIsectPlane.isInitialized()) { + fragBuilder->codeAppend( + "clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, " + "isectPlane.xy) + isectPlane.z));"); + } + if (cgp.fInUnionPlane.isInitialized()) { + fragBuilder->codeAppend( + "clip = saturate(clip + half(saturate(circleEdge.z * dot(circleEdge.xy," + " unionPlane.xy) + unionPlane.z)));"); + } + fragBuilder->codeAppend("edgeAlpha *= clip;"); + if (cgp.fInRoundCapCenters.isInitialized()) { + // We compute coverage of the round caps as circles at the butt caps produced + // by the clip planes. The inverse of the clip planes is applied so that there + // is no double counting. + fragBuilder->codeAppendf( + "half dcap1 = half(circleEdge.z * (%s - length(circleEdge.xy - " + " roundCapCenters.xy)));" + "half dcap2 = half(circleEdge.z * (%s - length(circleEdge.xy - " + " roundCapCenters.zw)));" + "half capAlpha = (1 - clip) * (max(dcap1, 0) + max(dcap2, 0));" + "edgeAlpha = min(edgeAlpha + capAlpha, 1.0);", + capRadius.fsIn(), capRadius.fsIn()); + } + } + fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage); + } + + static void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const CircleGeometryProcessor& cgp = gp.cast(); + uint16_t key; + key = cgp.fStroke ? 0x01 : 0x0; + key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0; + key |= cgp.fInClipPlane.isInitialized() ? 0x04 : 0x0; + key |= cgp.fInIsectPlane.isInitialized() ? 0x08 : 0x0; + key |= cgp.fInUnionPlane.isInitialized() ? 0x10 : 0x0; + key |= cgp.fInRoundCapCenters.isInitialized() ? 0x20 : 0x0; + b->add32(key); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + this->setTransformDataHelper(primProc.cast().fLocalMatrix, + pdman, &transformIter); + } + + private: + typedef GrGLSLGeometryProcessor INHERITED; + }; + + SkMatrix fLocalMatrix; + + Attribute fInPosition; + Attribute fInColor; + Attribute fInCircleEdge; + // Optional attributes. + Attribute fInClipPlane; + Attribute fInIsectPlane; + Attribute fInUnionPlane; + Attribute fInRoundCapCenters; + + bool fStroke; + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor); + +#if GR_TEST_UTILS +sk_sp CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) { + bool stroke = d->fRandom->nextBool(); + bool roundCaps = stroke ? d->fRandom->nextBool() : false; + bool wideColor = d->fRandom->nextBool(); + bool clipPlane = d->fRandom->nextBool(); + bool isectPlane = d->fRandom->nextBool(); + bool unionPlane = d->fRandom->nextBool(); + const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom); + return sk_sp(new CircleGeometryProcessor( + stroke, clipPlane, isectPlane, unionPlane, roundCaps, wideColor, matrix)); +} +#endif + +class ButtCapDashedCircleGeometryProcessor : public GrGeometryProcessor { +public: + ButtCapDashedCircleGeometryProcessor(bool wideColor, const SkMatrix& localMatrix) + : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID), fLocalMatrix(localMatrix) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = MakeColorAttribute("inColor", wideColor); + fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + fInDashParams = {"inDashParams", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + this->setVertexAttributes(&fInPosition, 4); + } + + ~ButtCapDashedCircleGeometryProcessor() override {} + + const char* name() const override { return "ButtCapDashedCircleGeometryProcessor"; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLProcessor::GenKey(*this, caps, b); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new GLSLProcessor(); + } + +private: + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + GLSLProcessor() {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const ButtCapDashedCircleGeometryProcessor& bcscgp = + args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + // emit attributes + varyingHandler->emitAttributes(bcscgp); + fragBuilder->codeAppend("float4 circleEdge;"); + varyingHandler->addPassThroughAttribute(bcscgp.fInCircleEdge, "circleEdge"); + + fragBuilder->codeAppend("float4 dashParams;"); + varyingHandler->addPassThroughAttribute( + bcscgp.fInDashParams, "dashParams", + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + GrGLSLVarying wrapDashes(kHalf4_GrSLType); + varyingHandler->addVarying("wrapDashes", &wrapDashes, + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + GrGLSLVarying lastIntervalLength(kHalf_GrSLType); + varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength, + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.fInDashParams.name()); + // Our fragment shader works in on/off intervals as specified by dashParams.xy: + // x = length of on interval, y = length of on + off. + // There are two other parameters in dashParams.zw: + // z = start angle in radians, w = phase offset in radians in range -y/2..y/2. + // Each interval has a "corresponding" dash which may be shifted partially or + // fully out of its interval by the phase. So there may be up to two "visual" + // dashes in an interval. + // When computing coverage in an interval we look at three dashes. These are the + // "corresponding" dashes from the current, previous, and next intervals. Any of these + // may be phase shifted into our interval or even when phase=0 they may be within half a + // pixel distance of a pixel center in the interval. + // When in the first interval we need to check the dash from the last interval. And + // similarly when in the last interval we need to check the dash from the first + // interval. When 2pi is not perfectly divisible dashParams.y this is a boundary case. + // We compute the dash begin/end angles in the vertex shader and apply them in the + // fragment shader when we detect we're in the first/last interval. + vertBuilder->codeAppend(R"( + // The two boundary dash intervals are stored in wrapDashes.xy and .zw and fed + // to the fragment shader as a varying. + float4 wrapDashes; + half lastIntervalLength = mod(6.28318530718, half(dashParams.y)); + // We can happen to be perfectly divisible. + if (0 == lastIntervalLength) { + lastIntervalLength = half(dashParams.y); + } + // Let 'l' be the last interval before reaching 2 pi. + // Based on the phase determine whether (l-1)th, l-th, or (l+1)th interval's + // "corresponding" dash appears in the l-th interval and is closest to the 0-th + // interval. + half offset = 0; + if (-dashParams.w >= lastIntervalLength) { + offset = half(-dashParams.y); + } else if (dashParams.w > dashParams.y - lastIntervalLength) { + offset = half(dashParams.y); + } + wrapDashes.x = -lastIntervalLength + offset - dashParams.w; + // The end of this dash may be beyond the 2 pi and therefore clipped. Hence the + // min. + wrapDashes.y = min(wrapDashes.x + dashParams.x, 0); + + // Based on the phase determine whether the -1st, 0th, or 1st interval's + // "corresponding" dash appears in the 0th interval and is closest to l. + offset = 0; + if (dashParams.w >= dashParams.x) { + offset = half(dashParams.y); + } else if (-dashParams.w > dashParams.y - dashParams.x) { + offset = half(-dashParams.y); + } + wrapDashes.z = lastIntervalLength + offset - dashParams.w; + wrapDashes.w = wrapDashes.z + dashParams.x; + // The start of the dash we're considering may be clipped by the start of the + // circle. + wrapDashes.z = max(wrapDashes.z, lastIntervalLength); + )"); + vertBuilder->codeAppendf("%s = half4(wrapDashes);", wrapDashes.vsOut()); + vertBuilder->codeAppendf("%s = lastIntervalLength;", lastIntervalLength.vsOut()); + fragBuilder->codeAppendf("half4 wrapDashes = %s;", wrapDashes.fsIn()); + fragBuilder->codeAppendf("half lastIntervalLength = %s;", lastIntervalLength.fsIn()); + + // setup pass through color + varyingHandler->addPassThroughAttribute( + bcscgp.fInColor, args.fOutputColor, + GrGLSLVaryingHandler::Interpolation::kCanBeFlat); + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition.name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + bcscgp.fInPosition.asShaderVar(), + bcscgp.fLocalMatrix, + args.fFPCoordTransformHandler); + GrShaderVar fnArgs[] = { + GrShaderVar("angleToEdge", kFloat_GrSLType), + GrShaderVar("diameter", kFloat_GrSLType), + }; + SkString fnName; + fragBuilder->emitFunction(kFloat_GrSLType, "coverage_from_dash_edge", + SK_ARRAY_COUNT(fnArgs), fnArgs, R"( + float linearDist; + angleToEdge = clamp(angleToEdge, -3.1415, 3.1415); + linearDist = diameter * sin(angleToEdge / 2); + return saturate(linearDist + 0.5); + )", + &fnName); + fragBuilder->codeAppend(R"( + float d = length(circleEdge.xy) * circleEdge.z; + + // Compute coverage from outer/inner edges of the stroke. + half distanceToOuterEdge = half(circleEdge.z - d); + half edgeAlpha = saturate(distanceToOuterEdge); + half distanceToInnerEdge = half(d - circleEdge.z * circleEdge.w); + half innerAlpha = saturate(distanceToInnerEdge); + edgeAlpha *= innerAlpha; + + half angleFromStart = half(atan(circleEdge.y, circleEdge.x) - dashParams.z); + angleFromStart = mod(angleFromStart, 6.28318530718); + float x = mod(angleFromStart, dashParams.y); + // Convert the radial distance from center to pixel into a diameter. + d *= 2; + half2 currDash = half2(half(-dashParams.w), half(dashParams.x) - + half(dashParams.w)); + half2 nextDash = half2(half(dashParams.y) - half(dashParams.w), + half(dashParams.y) + half(dashParams.x) - + half(dashParams.w)); + half2 prevDash = half2(half(-dashParams.y) - half(dashParams.w), + half(-dashParams.y) + half(dashParams.x) - + half(dashParams.w)); + half dashAlpha = 0; + )"); + fragBuilder->codeAppendf(R"( + if (angleFromStart - x + dashParams.y >= 6.28318530718) { + dashAlpha += half(%s(x - wrapDashes.z, d) * %s(wrapDashes.w - x, d)); + currDash.y = min(currDash.y, lastIntervalLength); + if (nextDash.x >= lastIntervalLength) { + // The next dash is outside the 0..2pi range, throw it away + nextDash.xy = half2(1000); + } else { + // Clip the end of the next dash to the end of the circle + nextDash.y = min(nextDash.y, lastIntervalLength); + } + } + )", fnName.c_str(), fnName.c_str()); + fragBuilder->codeAppendf(R"( + if (angleFromStart - x - dashParams.y < -0.01) { + dashAlpha += half(%s(x - wrapDashes.x, d) * %s(wrapDashes.y - x, d)); + currDash.x = max(currDash.x, 0); + if (prevDash.y <= 0) { + // The previous dash is outside the 0..2pi range, throw it away + prevDash.xy = half2(1000); + } else { + // Clip the start previous dash to the start of the circle + prevDash.x = max(prevDash.x, 0); + } + } + )", fnName.c_str(), fnName.c_str()); + fragBuilder->codeAppendf(R"( + dashAlpha += half(%s(x - currDash.x, d) * %s(currDash.y - x, d)); + dashAlpha += half(%s(x - nextDash.x, d) * %s(nextDash.y - x, d)); + dashAlpha += half(%s(x - prevDash.x, d) * %s(prevDash.y - x, d)); + dashAlpha = min(dashAlpha, 1); + edgeAlpha *= dashAlpha; + )", fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), + fnName.c_str()); + fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage); + } + + static void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const ButtCapDashedCircleGeometryProcessor& bcscgp = + gp.cast(); + b->add32(bcscgp.fLocalMatrix.hasPerspective()); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + this->setTransformDataHelper( + primProc.cast().fLocalMatrix, pdman, + &transformIter); + } + + private: + typedef GrGLSLGeometryProcessor INHERITED; + }; + + SkMatrix fLocalMatrix; + Attribute fInPosition; + Attribute fInColor; + Attribute fInCircleEdge; + Attribute fInDashParams; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +#if GR_TEST_UTILS +sk_sp ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) { + bool wideColor = d->fRandom->nextBool(); + const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom); + return sk_sp(new ButtCapDashedCircleGeometryProcessor(wideColor, matrix)); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +/** + * The output of this effect is a modulation of the input color and coverage for an axis-aligned + * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii, + * in both x and y directions. + * + * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0. + */ + +class EllipseGeometryProcessor : public GrGeometryProcessor { +public: + EllipseGeometryProcessor(bool stroke, bool wideColor, bool useScale, + const SkMatrix& localMatrix) + : INHERITED(kEllipseGeometryProcessor_ClassID) + , fLocalMatrix(localMatrix) + , fStroke(stroke) + , fUseScale(useScale) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = MakeColorAttribute("inColor", wideColor); + if (useScale) { + fInEllipseOffset = {"inEllipseOffset", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + } else { + fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + } + fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + this->setVertexAttributes(&fInPosition, 4); + } + + ~EllipseGeometryProcessor() override {} + + const char* name() const override { return "EllipseEdge"; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLProcessor::GenKey(*this, caps, b); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new GLSLProcessor(); + } + +private: + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + GLSLProcessor() {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const EllipseGeometryProcessor& egp = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(egp); + + GrSLType offsetType = egp.fUseScale ? kFloat3_GrSLType : kFloat2_GrSLType; + GrGLSLVarying ellipseOffsets(offsetType); + varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets); + vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(), + egp.fInEllipseOffset.name()); + + GrGLSLVarying ellipseRadii(kFloat4_GrSLType); + varyingHandler->addVarying("EllipseRadii", &ellipseRadii); + vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii.name()); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + // setup pass through color + varyingHandler->addPassThroughAttribute(egp.fInColor, args.fOutputColor); + + // Setup position + this->writeOutputPosition(vertBuilder, gpArgs, egp.fInPosition.name()); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + egp.fInPosition.asShaderVar(), + egp.fLocalMatrix, + args.fFPCoordTransformHandler); + // For stroked ellipses, we use the full ellipse equation (x^2/a^2 + y^2/b^2 = 1) + // to compute both the edges because we need two separate test equations for + // the single offset. + // For filled ellipses we can use a unit circle equation (x^2 + y^2 = 1), and warp + // the distance by the gradient, non-uniformly scaled by the inverse of the + // ellipse size. + + // On medium precision devices, we scale the denominator of the distance equation + // before taking the inverse square root to minimize the chance that we're dividing + // by zero, then we scale the result back. + + // for outer curve + fragBuilder->codeAppendf("float2 offset = %s.xy;", ellipseOffsets.fsIn()); + if (egp.fStroke) { + fragBuilder->codeAppendf("offset *= %s.xy;", ellipseRadii.fsIn()); + } + fragBuilder->codeAppend("float test = dot(offset, offset) - 1.0;"); + if (egp.fUseScale) { + fragBuilder->codeAppendf("float2 grad = 2.0*offset*(%s.z*%s.xy);", + ellipseOffsets.fsIn(), ellipseRadii.fsIn()); + } else { + fragBuilder->codeAppendf("float2 grad = 2.0*offset*%s.xy;", ellipseRadii.fsIn()); + } + fragBuilder->codeAppend("float grad_dot = dot(grad, grad);"); + + // avoid calling inversesqrt on zero. + if (args.fShaderCaps->floatIs32Bits()) { + fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);"); + } else { + fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); + } + if (egp.fUseScale) { + fragBuilder->codeAppendf("float invlen = %s.z*inversesqrt(grad_dot);", + ellipseOffsets.fsIn()); + } else { + fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);"); + } + fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);"); + + // for inner curve + if (egp.fStroke) { + fragBuilder->codeAppendf("offset = %s.xy*%s.zw;", ellipseOffsets.fsIn(), + ellipseRadii.fsIn()); + fragBuilder->codeAppend("test = dot(offset, offset) - 1.0;"); + if (egp.fUseScale) { + fragBuilder->codeAppendf("grad = 2.0*offset*(%s.z*%s.zw);", + ellipseOffsets.fsIn(), ellipseRadii.fsIn()); + } else { + fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn()); + } + fragBuilder->codeAppend("grad_dot = dot(grad, grad);"); + if (!args.fShaderCaps->floatIs32Bits()) { + fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); + } + if (egp.fUseScale) { + fragBuilder->codeAppendf("invlen = %s.z*inversesqrt(grad_dot);", + ellipseOffsets.fsIn()); + } else { + fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);"); + } + fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);"); + } + + fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage); + } + + static void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const EllipseGeometryProcessor& egp = gp.cast(); + uint16_t key = egp.fStroke ? 0x1 : 0x0; + key |= egp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0; + b->add32(key); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + const EllipseGeometryProcessor& egp = primProc.cast(); + this->setTransformDataHelper(egp.fLocalMatrix, pdman, &transformIter); + } + + private: + typedef GrGLSLGeometryProcessor INHERITED; + }; + + Attribute fInPosition; + Attribute fInColor; + Attribute fInEllipseOffset; + Attribute fInEllipseRadii; + + SkMatrix fLocalMatrix; + bool fStroke; + bool fUseScale; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor); + +#if GR_TEST_UTILS +sk_sp EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) { + return sk_sp( + new EllipseGeometryProcessor(d->fRandom->nextBool(), d->fRandom->nextBool(), + d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom))); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +/** + * The output of this effect is a modulation of the input color and coverage for an ellipse, + * specified as a 2D offset from center for both the outer and inner paths (if stroked). The + * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by + * using differentials. + * + * The result is device-independent and can be used with any affine matrix. + */ + +enum class DIEllipseStyle { kStroke = 0, kHairline, kFill }; + +class DIEllipseGeometryProcessor : public GrGeometryProcessor { +public: + DIEllipseGeometryProcessor(bool wideColor, bool useScale, const SkMatrix& viewMatrix, + DIEllipseStyle style) + : INHERITED(kDIEllipseGeometryProcessor_ClassID) + , fViewMatrix(viewMatrix) + , fUseScale(useScale) + , fStyle(style) { + fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fInColor = MakeColorAttribute("inColor", wideColor); + if (useScale) { + fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat3_GrVertexAttribType, + kFloat3_GrSLType}; + } else { + fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType, + kFloat2_GrSLType}; + } + fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + this->setVertexAttributes(&fInPosition, 4); + } + + ~DIEllipseGeometryProcessor() override {} + + const char* name() const override { return "DIEllipseEdge"; } + + void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLProcessor::GenKey(*this, caps, b); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { + return new GLSLProcessor(); + } + +private: + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + GLSLProcessor() : fViewMatrix(SkMatrix::InvalidMatrix()) {} + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const DIEllipseGeometryProcessor& diegp = args.fGP.cast(); + GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // emit attributes + varyingHandler->emitAttributes(diegp); + + GrSLType offsetType = (diegp.fUseScale) ? kFloat3_GrSLType : kFloat2_GrSLType; + GrGLSLVarying offsets0(offsetType); + varyingHandler->addVarying("EllipseOffsets0", &offsets0); + vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.fInEllipseOffsets0.name()); + + GrGLSLVarying offsets1(kFloat2_GrSLType); + varyingHandler->addVarying("EllipseOffsets1", &offsets1); + vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1.name()); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + varyingHandler->addPassThroughAttribute(diegp.fInColor, args.fOutputColor); + + // Setup position + this->writeOutputPosition(vertBuilder, + uniformHandler, + gpArgs, + diegp.fInPosition.name(), + diegp.fViewMatrix, + &fViewMatrixUniform); + + // emit transforms + this->emitTransforms(vertBuilder, + varyingHandler, + uniformHandler, + diegp.fInPosition.asShaderVar(), + args.fFPCoordTransformHandler); + + // for outer curve + fragBuilder->codeAppendf("float2 scaledOffset = %s.xy;", offsets0.fsIn()); + fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;"); + fragBuilder->codeAppendf("float2 duvdx = dFdx(%s.xy);", offsets0.fsIn()); + fragBuilder->codeAppendf("float2 duvdy = dFdy(%s.xy);", offsets0.fsIn()); + fragBuilder->codeAppendf( + "float2 grad = float2(%s.x*duvdx.x + %s.y*duvdx.y," + " %s.x*duvdy.x + %s.y*duvdy.y);", + offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn()); + if (diegp.fUseScale) { + fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn()); + } + + fragBuilder->codeAppend("float grad_dot = 4.0*dot(grad, grad);"); + // avoid calling inversesqrt on zero. + if (args.fShaderCaps->floatIs32Bits()) { + fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);"); + } else { + fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); + } + fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);"); + if (diegp.fUseScale) { + fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn()); + } + if (DIEllipseStyle::kHairline == diegp.fStyle) { + // can probably do this with one step + fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);"); + fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);"); + } else { + fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);"); + } + + // for inner curve + if (DIEllipseStyle::kStroke == diegp.fStyle) { + fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn()); + fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;"); + fragBuilder->codeAppendf("duvdx = float2(dFdx(%s));", offsets1.fsIn()); + fragBuilder->codeAppendf("duvdy = float2(dFdy(%s));", offsets1.fsIn()); + fragBuilder->codeAppendf( + "grad = float2(%s.x*duvdx.x + %s.y*duvdx.y," + " %s.x*duvdy.x + %s.y*duvdy.y);", + offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn()); + if (diegp.fUseScale) { + fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn()); + } + fragBuilder->codeAppend("grad_dot = 4.0*dot(grad, grad);"); + if (!args.fShaderCaps->floatIs32Bits()) { + fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); + } + fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);"); + if (diegp.fUseScale) { + fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn()); + } + fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);"); + } + + fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage); + } + + static void GenKey(const GrGeometryProcessor& gp, + const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const DIEllipseGeometryProcessor& diegp = gp.cast(); + uint16_t key = static_cast(diegp.fStyle); + key |= ComputePosKey(diegp.fViewMatrix) << 10; + b->add32(key); + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp, + FPCoordTransformIter&& transformIter) override { + const DIEllipseGeometryProcessor& diegp = gp.cast(); + + if (!diegp.fViewMatrix.isIdentity() && !fViewMatrix.cheapEqualTo(diegp.fViewMatrix)) { + fViewMatrix = diegp.fViewMatrix; + float viewMatrix[3 * 3]; + GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix); + pdman.setMatrix3f(fViewMatrixUniform, viewMatrix); + } + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + + private: + SkMatrix fViewMatrix; + UniformHandle fViewMatrixUniform; + + typedef GrGLSLGeometryProcessor INHERITED; + }; + + + Attribute fInPosition; + Attribute fInColor; + Attribute fInEllipseOffsets0; + Attribute fInEllipseOffsets1; + + SkMatrix fViewMatrix; + bool fUseScale; + DIEllipseStyle fStyle; + + GR_DECLARE_GEOMETRY_PROCESSOR_TEST + + typedef GrGeometryProcessor INHERITED; +}; + +GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor); + +#if GR_TEST_UTILS +sk_sp DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) { + return sk_sp(new DIEllipseGeometryProcessor( + d->fRandom->nextBool(), d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom), + (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2)))); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// We have two possible cases for geometry for a circle: + +// In the case of a normal fill, we draw geometry for the circle as an octagon. +static const uint16_t gFillCircleIndices[] = { + // enter the octagon + // clang-format off + 0, 1, 8, 1, 2, 8, + 2, 3, 8, 3, 4, 8, + 4, 5, 8, 5, 6, 8, + 6, 7, 8, 7, 0, 8 + // clang-format on +}; + +// For stroked circles, we use two nested octagons. +static const uint16_t gStrokeCircleIndices[] = { + // enter the octagon + // clang-format off + 0, 1, 9, 0, 9, 8, + 1, 2, 10, 1, 10, 9, + 2, 3, 11, 2, 11, 10, + 3, 4, 12, 3, 12, 11, + 4, 5, 13, 4, 13, 12, + 5, 6, 14, 5, 14, 13, + 6, 7, 15, 6, 15, 14, + 7, 0, 8, 7, 8, 15, + // clang-format on +}; + +// Normalized geometry for octagons that circumscribe and lie on a circle: + +static constexpr SkScalar kOctOffset = 0.41421356237f; // sqrt(2) - 1 +static constexpr SkPoint kOctagonOuter[] = { + SkPoint::Make(-kOctOffset, -1), + SkPoint::Make( kOctOffset, -1), + SkPoint::Make( 1, -kOctOffset), + SkPoint::Make( 1, kOctOffset), + SkPoint::Make( kOctOffset, 1), + SkPoint::Make(-kOctOffset, 1), + SkPoint::Make(-1, kOctOffset), + SkPoint::Make(-1, -kOctOffset), +}; + +// cosine and sine of pi/8 +static constexpr SkScalar kCosPi8 = 0.923579533f; +static constexpr SkScalar kSinPi8 = 0.382683432f; +static constexpr SkPoint kOctagonInner[] = { + SkPoint::Make(-kSinPi8, -kCosPi8), + SkPoint::Make( kSinPi8, -kCosPi8), + SkPoint::Make( kCosPi8, -kSinPi8), + SkPoint::Make( kCosPi8, kSinPi8), + SkPoint::Make( kSinPi8, kCosPi8), + SkPoint::Make(-kSinPi8, kCosPi8), + SkPoint::Make(-kCosPi8, kSinPi8), + SkPoint::Make(-kCosPi8, -kSinPi8), +}; + +static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices); +static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices); +static const int kVertsPerStrokeCircle = 16; +static const int kVertsPerFillCircle = 9; + +static int circle_type_to_vert_count(bool stroked) { + return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle; +} + +static int circle_type_to_index_count(bool stroked) { + return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle; +} + +static const uint16_t* circle_type_to_indices(bool stroked) { + return stroked ? gStrokeCircleIndices : gFillCircleIndices; +} + +/////////////////////////////////////////////////////////////////////////////// + +class CircleOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + /** Optional extra params to render a partial arc rather than a full circle. */ + struct ArcParams { + SkScalar fStartAngleRadians; + SkScalar fSweepAngleRadians; + bool fUseCenter; + }; + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + SkPoint center, + SkScalar radius, + const GrStyle& style, + const ArcParams* arcParams = nullptr) { + SkASSERT(circle_stays_circle(viewMatrix)); + if (style.hasPathEffect()) { + return nullptr; + } + const SkStrokeRec& stroke = style.strokeRec(); + SkStrokeRec::Style recStyle = stroke.getStyle(); + if (arcParams) { + // Arc support depends on the style. + switch (recStyle) { + case SkStrokeRec::kStrokeAndFill_Style: + // This produces a strange result that this op doesn't implement. + return nullptr; + case SkStrokeRec::kFill_Style: + // This supports all fills. + break; + case SkStrokeRec::kStroke_Style: + // Strokes that don't use the center point are supported with butt and round + // caps. + if (arcParams->fUseCenter || stroke.getCap() == SkPaint::kSquare_Cap) { + return nullptr; + } + break; + case SkStrokeRec::kHairline_Style: + // Hairline only supports butt cap. Round caps could be emulated by slightly + // extending the angle range if we ever care to. + if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) { + return nullptr; + } + break; + } + } + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, center, + radius, style, arcParams); + } + + CircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, const GrStyle& style, + const ArcParams* arcParams) + : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) { + const SkStrokeRec& stroke = style.strokeRec(); + SkStrokeRec::Style recStyle = stroke.getStyle(); + + fRoundCaps = false; + + viewMatrix.mapPoints(¢er, 1); + radius = viewMatrix.mapRadius(radius); + SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth()); + + bool isStrokeOnly = + SkStrokeRec::kStroke_Style == recStyle || SkStrokeRec::kHairline_Style == recStyle; + bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle; + + SkScalar innerRadius = -SK_ScalarHalf; + SkScalar outerRadius = radius; + SkScalar halfWidth = 0; + if (hasStroke) { + if (SkScalarNearlyZero(strokeWidth)) { + halfWidth = SK_ScalarHalf; + } else { + halfWidth = SkScalarHalf(strokeWidth); + } + + outerRadius += halfWidth; + if (isStrokeOnly) { + innerRadius = radius - halfWidth; + } + } + + // The radii are outset for two reasons. First, it allows the shader to simply perform + // simpler computation because the computed alpha is zero, rather than 50%, at the radius. + // Second, the outer radius is used to compute the verts of the bounding box that is + // rendered and the outset ensures the box will cover all partially covered by the circle. + outerRadius += SK_ScalarHalf; + innerRadius -= SK_ScalarHalf; + bool stroked = isStrokeOnly && innerRadius > 0.0f; + fViewMatrixIfUsingLocalCoords = viewMatrix; + + // This makes every point fully inside the intersection plane. + static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f}; + // This makes every point fully outside the union plane. + static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f}; + static constexpr SkPoint kUnusedRoundCaps[] = {{1e10f, 1e10f}, {1e10f, 1e10f}}; + SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius, + center.fX + outerRadius, center.fY + outerRadius); + if (arcParams) { + // The shader operates in a space where the circle is translated to be centered at the + // origin. Here we compute points on the unit circle at the starting and ending angles. + SkPoint startPoint, stopPoint; + startPoint.fY = SkScalarSin(arcParams->fStartAngleRadians); + startPoint.fX = SkScalarCos(arcParams->fStartAngleRadians); + SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians; + stopPoint.fY = SkScalarSin(endAngle); + stopPoint.fX = SkScalarCos(endAngle); + + // Adjust the start and end points based on the view matrix (to handle rotated arcs) + startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY); + stopPoint = viewMatrix.mapVector(stopPoint.fX, stopPoint.fY); + startPoint.normalize(); + stopPoint.normalize(); + + // If the matrix included scale (on one axis) we need to swap our start and end points + if ((viewMatrix.getScaleX() < 0) != (viewMatrix.getScaleY() < 0)) { + using std::swap; + swap(startPoint, stopPoint); + } + + fRoundCaps = style.strokeRec().getWidth() > 0 && + style.strokeRec().getCap() == SkPaint::kRound_Cap; + SkPoint roundCaps[2]; + if (fRoundCaps) { + // Compute the cap center points in the normalized space. + SkScalar midRadius = (innerRadius + outerRadius) / (2 * outerRadius); + roundCaps[0] = startPoint * midRadius; + roundCaps[1] = stopPoint * midRadius; + } else { + roundCaps[0] = kUnusedRoundCaps[0]; + roundCaps[1] = kUnusedRoundCaps[1]; + } + + // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against + // radial lines. We treat round caps the same way, but tack coverage of circles at the + // center of the butts. + // However, in both cases we have to be careful about the half-circle. + // case. In that case the two radial lines are equal and so that edge gets clipped + // twice. Since the shared edge goes through the center we fall back on the !useCenter + // case. + auto absSweep = SkScalarAbs(arcParams->fSweepAngleRadians); + bool useCenter = (arcParams->fUseCenter || isStrokeOnly) && + !SkScalarNearlyEqual(absSweep, SK_ScalarPI); + if (useCenter) { + SkVector norm0 = {startPoint.fY, -startPoint.fX}; + SkVector norm1 = {stopPoint.fY, -stopPoint.fX}; + // This ensures that norm0 is always the clockwise plane, and norm1 is CCW. + if (arcParams->fSweepAngleRadians < 0) { + std::swap(norm0, norm1); + } + norm0.negate(); + fClipPlane = true; + if (absSweep > SK_ScalarPI) { + fCircles.emplace_back(Circle{ + color, + innerRadius, + outerRadius, + {norm0.fX, norm0.fY, 0.5f}, + {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, + {norm1.fX, norm1.fY, 0.5f}, + {roundCaps[0], roundCaps[1]}, + devBounds, + stroked}); + fClipPlaneIsect = false; + fClipPlaneUnion = true; + } else { + fCircles.emplace_back(Circle{ + color, + innerRadius, + outerRadius, + {norm0.fX, norm0.fY, 0.5f}, + {norm1.fX, norm1.fY, 0.5f}, + {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, + {roundCaps[0], roundCaps[1]}, + devBounds, + stroked}); + fClipPlaneIsect = true; + fClipPlaneUnion = false; + } + } else { + // We clip to a secant of the original circle. + startPoint.scale(radius); + stopPoint.scale(radius); + SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX}; + norm.normalize(); + if (arcParams->fSweepAngleRadians > 0) { + norm.negate(); + } + SkScalar d = -norm.dot(startPoint) + 0.5f; + + fCircles.emplace_back( + Circle{color, + innerRadius, + outerRadius, + {norm.fX, norm.fY, d}, + {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, + {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, + {roundCaps[0], roundCaps[1]}, + devBounds, + stroked}); + fClipPlane = true; + fClipPlaneIsect = false; + fClipPlaneUnion = false; + } + } else { + fCircles.emplace_back( + Circle{color, + innerRadius, + outerRadius, + {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, + {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, + {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, + {kUnusedRoundCaps[0], kUnusedRoundCaps[1]}, + devBounds, + stroked}); + fClipPlane = false; + fClipPlaneIsect = false; + fClipPlaneUnion = false; + } + // Use the original radius and stroke radius for the bounds so that it does not include the + // AA bloat. + radius += halfWidth; + this->setBounds( + {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius}, + HasAABloat::kYes, IsHairline::kNo); + fVertCount = circle_type_to_vert_count(stroked); + fIndexCount = circle_type_to_index_count(stroked); + fAllFill = !stroked; + } + + const char* name() const override { return "CircleOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (int i = 0; i < fCircles.count(); ++i) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," + "InnerRad: %.2f, OuterRad: %.2f\n", + fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft, + fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight, + fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius, + fCircles[i].fOuterRadius); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + SkPMColor4f* color = &fCircles.front().fColor; + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, color, + &fWideColor); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + +private: + void onPrepareDraws(Target* target) override { + SkMatrix localMatrix; + if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { + return; + } + + // Setup geometry processor + sk_sp gp(new CircleGeometryProcessor( + !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, fRoundCaps, fWideColor, + localMatrix)); + + sk_sp vertexBuffer; + int firstVertex; + GrVertexWriter vertices{target->makeVertexSpace(gp->vertexStride(), fVertCount, + &vertexBuffer, &firstVertex)}; + if (!vertices.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + sk_sp indexBuffer = nullptr; + int firstIndex = 0; + uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); + if (!indices) { + SkDebugf("Could not allocate indices\n"); + return; + } + + int currStartVertex = 0; + for (const auto& circle : fCircles) { + SkScalar innerRadius = circle.fInnerRadius; + SkScalar outerRadius = circle.fOuterRadius; + GrVertexColor color(circle.fColor, fWideColor); + const SkRect& bounds = circle.fDevBounds; + + // The inner radius in the vertex data must be specified in normalized space. + innerRadius = innerRadius / outerRadius; + SkPoint radii = { outerRadius, innerRadius }; + + SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY()); + SkScalar halfWidth = 0.5f * bounds.width(); + + SkVector geoClipPlane = { 0, 0 }; + SkScalar offsetClipDist = SK_Scalar1; + if (!circle.fStroked && fClipPlane && fClipPlaneIsect && + (circle.fClipPlane[0] * circle.fIsectPlane[0] + + circle.fClipPlane[1] * circle.fIsectPlane[1]) < 0.0f) { + // Acute arc. Clip the vertices to the perpendicular half-plane. We've constructed + // fClipPlane to be clockwise, and fISectPlane to be CCW, so we can can rotate them + // each 90 degrees to point "out", then average them. We back off by 1/2 pixel so + // the AA can extend just past the center of the circle. + geoClipPlane.set(circle.fClipPlane[1] - circle.fIsectPlane[1], + circle.fIsectPlane[0] - circle.fClipPlane[0]); + SkAssertResult(geoClipPlane.normalize()); + offsetClipDist = 0.5f / halfWidth; + } + + for (int i = 0; i < 8; ++i) { + // This clips the normalized offset to the half-plane we computed above. Then we + // compute the vertex position from this. + SkScalar dist = SkTMin(kOctagonOuter[i].dot(geoClipPlane) + offsetClipDist, 0.0f); + SkVector offset = kOctagonOuter[i] - geoClipPlane * dist; + vertices.write(center + offset * halfWidth, + color, + offset, + radii); + if (fClipPlane) { + vertices.write(circle.fClipPlane); + } + if (fClipPlaneIsect) { + vertices.write(circle.fIsectPlane); + } + if (fClipPlaneUnion) { + vertices.write(circle.fUnionPlane); + } + if (fRoundCaps) { + vertices.write(circle.fRoundCapCenters); + } + } + + if (circle.fStroked) { + // compute the inner ring + + for (int i = 0; i < 8; ++i) { + vertices.write(center + kOctagonInner[i] * circle.fInnerRadius, + color, + kOctagonInner[i] * innerRadius, + radii); + if (fClipPlane) { + vertices.write(circle.fClipPlane); + } + if (fClipPlaneIsect) { + vertices.write(circle.fIsectPlane); + } + if (fClipPlaneUnion) { + vertices.write(circle.fUnionPlane); + } + if (fRoundCaps) { + vertices.write(circle.fRoundCapCenters); + } + } + } else { + // filled + vertices.write(center, color, SkPoint::Make(0, 0), radii); + if (fClipPlane) { + vertices.write(circle.fClipPlane); + } + if (fClipPlaneIsect) { + vertices.write(circle.fIsectPlane); + } + if (fClipPlaneUnion) { + vertices.write(circle.fUnionPlane); + } + if (fRoundCaps) { + vertices.write(circle.fRoundCapCenters); + } + } + + const uint16_t* primIndices = circle_type_to_indices(circle.fStroked); + const int primIndexCount = circle_type_to_index_count(circle.fStroked); + for (int i = 0; i < primIndexCount; ++i) { + *indices++ = primIndices[i] + currStartVertex; + } + + currStartVertex += circle_type_to_vert_count(circle.fStroked); + } + + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, + GrPrimitiveRestart::kNo); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + CircleOp* that = t->cast(); + + // can only represent 65535 unique vertices with 16-bit indices + if (fVertCount + that->fVertCount > 65536) { + return CombineResult::kCannotCombine; + } + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (fHelper.usesLocalCoords() && + !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { + return CombineResult::kCannotCombine; + } + + // Because we've set up the ops that don't use the planes with noop values + // we can just accumulate used planes by later ops. + fClipPlane |= that->fClipPlane; + fClipPlaneIsect |= that->fClipPlaneIsect; + fClipPlaneUnion |= that->fClipPlaneUnion; + fRoundCaps |= that->fRoundCaps; + fWideColor |= that->fWideColor; + + fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin()); + fVertCount += that->fVertCount; + fIndexCount += that->fIndexCount; + fAllFill = fAllFill && that->fAllFill; + return CombineResult::kMerged; + } + + struct Circle { + SkPMColor4f fColor; + SkScalar fInnerRadius; + SkScalar fOuterRadius; + SkScalar fClipPlane[3]; + SkScalar fIsectPlane[3]; + SkScalar fUnionPlane[3]; + SkPoint fRoundCapCenters[2]; + SkRect fDevBounds; + bool fStroked; + }; + + SkMatrix fViewMatrixIfUsingLocalCoords; + Helper fHelper; + SkSTArray<1, Circle, true> fCircles; + int fVertCount; + int fIndexCount; + bool fAllFill; + bool fClipPlane; + bool fClipPlaneIsect; + bool fClipPlaneUnion; + bool fRoundCaps; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +class ButtCapDashedCircleOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + SkPoint center, + SkScalar radius, + SkScalar strokeWidth, + SkScalar startAngle, + SkScalar onAngle, + SkScalar offAngle, + SkScalar phaseAngle) { + SkASSERT(circle_stays_circle(viewMatrix)); + SkASSERT(strokeWidth < 2 * radius); + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, + center, radius, strokeWidth, startAngle, + onAngle, offAngle, phaseAngle); + } + + ButtCapDashedCircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, + SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle, + SkScalar offAngle, SkScalar phaseAngle) + : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) { + SkASSERT(circle_stays_circle(viewMatrix)); + viewMatrix.mapPoints(¢er, 1); + radius = viewMatrix.mapRadius(radius); + strokeWidth = viewMatrix.mapRadius(strokeWidth); + + // Determine the angle where the circle starts in device space and whether its orientation + // has been reversed. + SkVector start; + bool reflection; + if (!startAngle) { + start = {1, 0}; + } else { + start.fY = SkScalarSin(startAngle); + start.fX = SkScalarCos(startAngle); + } + viewMatrix.mapVectors(&start, 1); + startAngle = SkScalarATan2(start.fY, start.fX); + reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() - + viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0; + + auto totalAngle = onAngle + offAngle; + phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2; + + SkScalar halfWidth = 0; + if (SkScalarNearlyZero(strokeWidth)) { + halfWidth = SK_ScalarHalf; + } else { + halfWidth = SkScalarHalf(strokeWidth); + } + + SkScalar outerRadius = radius + halfWidth; + SkScalar innerRadius = radius - halfWidth; + + // The radii are outset for two reasons. First, it allows the shader to simply perform + // simpler computation because the computed alpha is zero, rather than 50%, at the radius. + // Second, the outer radius is used to compute the verts of the bounding box that is + // rendered and the outset ensures the box will cover all partially covered by the circle. + outerRadius += SK_ScalarHalf; + innerRadius -= SK_ScalarHalf; + fViewMatrixIfUsingLocalCoords = viewMatrix; + + SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius, + center.fX + outerRadius, center.fY + outerRadius); + + // We store whether there is a reflection as a negative total angle. + if (reflection) { + totalAngle = -totalAngle; + } + fCircles.push_back(Circle{ + color, + outerRadius, + innerRadius, + onAngle, + totalAngle, + startAngle, + phaseAngle, + devBounds + }); + // Use the original radius and stroke radius for the bounds so that it does not include the + // AA bloat. + radius += halfWidth; + this->setBounds( + {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius}, + HasAABloat::kYes, IsHairline::kNo); + fVertCount = circle_type_to_vert_count(true); + fIndexCount = circle_type_to_index_count(true); + } + + const char* name() const override { return "ButtCappedDashedCircleOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (int i = 0; i < fCircles.count(); ++i) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," + "InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, " + "Phase: %.2f\n", + fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft, + fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight, + fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius, + fCircles[i].fOuterRadius, fCircles[i].fOnAngle, fCircles[i].fTotalAngle, + fCircles[i].fPhaseAngle); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + SkPMColor4f* color = &fCircles.front().fColor; + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, color, + &fWideColor); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + +private: + void onPrepareDraws(Target* target) override { + SkMatrix localMatrix; + if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { + return; + } + + // Setup geometry processor + sk_sp gp(new ButtCapDashedCircleGeometryProcessor(fWideColor, + localMatrix)); + + sk_sp vertexBuffer; + int firstVertex; + GrVertexWriter vertices{target->makeVertexSpace(gp->vertexStride(), fVertCount, + &vertexBuffer, &firstVertex)}; + if (!vertices.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + sk_sp indexBuffer; + int firstIndex = 0; + uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); + if (!indices) { + SkDebugf("Could not allocate indices\n"); + return; + } + + int currStartVertex = 0; + for (const auto& circle : fCircles) { + // The inner radius in the vertex data must be specified in normalized space so that + // length() can be called with smaller values to avoid precision issues with half + // floats. + auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius; + const SkRect& bounds = circle.fDevBounds; + bool reflect = false; + struct { float onAngle, totalAngle, startAngle, phaseAngle; } dashParams = { + circle.fOnAngle, circle.fTotalAngle, circle.fStartAngle, circle.fPhaseAngle + }; + if (dashParams.totalAngle < 0) { + reflect = true; + dashParams.totalAngle = -dashParams.totalAngle; + dashParams.startAngle = -dashParams.startAngle; + } + + GrVertexColor color(circle.fColor, fWideColor); + + // The bounding geometry for the circle is composed of an outer bounding octagon and + // an inner bounded octagon. + + // Compute the vertices of the outer octagon. + SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY()); + SkScalar halfWidth = 0.5f * bounds.width(); + + auto reflectY = [=](const SkPoint& p) { + return SkPoint{ p.fX, reflect ? -p.fY : p.fY }; + }; + + for (int i = 0; i < 8; ++i) { + vertices.write(center + kOctagonOuter[i] * halfWidth, + color, + reflectY(kOctagonOuter[i]), + circle.fOuterRadius, + normInnerRadius, + dashParams); + } + + // Compute the vertices of the inner octagon. + for (int i = 0; i < 8; ++i) { + vertices.write(center + kOctagonInner[i] * circle.fInnerRadius, + color, + reflectY(kOctagonInner[i]) * normInnerRadius, + circle.fOuterRadius, + normInnerRadius, + dashParams); + } + + const uint16_t* primIndices = circle_type_to_indices(true); + const int primIndexCount = circle_type_to_index_count(true); + for (int i = 0; i < primIndexCount; ++i) { + *indices++ = primIndices[i] + currStartVertex; + } + + currStartVertex += circle_type_to_vert_count(true); + } + + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, + GrPrimitiveRestart::kNo); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + ButtCapDashedCircleOp* that = t->cast(); + + // can only represent 65535 unique vertices with 16-bit indices + if (fVertCount + that->fVertCount > 65536) { + return CombineResult::kCannotCombine; + } + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (fHelper.usesLocalCoords() && + !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { + return CombineResult::kCannotCombine; + } + + fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin()); + fVertCount += that->fVertCount; + fIndexCount += that->fIndexCount; + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + struct Circle { + SkPMColor4f fColor; + SkScalar fOuterRadius; + SkScalar fInnerRadius; + SkScalar fOnAngle; + SkScalar fTotalAngle; + SkScalar fStartAngle; + SkScalar fPhaseAngle; + SkRect fDevBounds; + }; + + SkMatrix fViewMatrixIfUsingLocalCoords; + Helper fHelper; + SkSTArray<1, Circle, true> fCircles; + int fVertCount; + int fIndexCount; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class EllipseOp : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + + struct DeviceSpaceParams { + SkPoint fCenter; + SkScalar fXRadius; + SkScalar fYRadius; + SkScalar fInnerXRadius; + SkScalar fInnerYRadius; + }; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& ellipse, + const SkStrokeRec& stroke) { + DeviceSpaceParams params; + // do any matrix crunching before we reset the draw state for device coords + params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); + viewMatrix.mapPoints(¶ms.fCenter, 1); + SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); + SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); + params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius + + viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius); + params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius + + viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius); + + // do (potentially) anisotropic mapping of stroke + SkVector scaledStroke; + SkScalar strokeWidth = stroke.getWidth(); + scaledStroke.fX = SkScalarAbs( + strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); + scaledStroke.fY = SkScalarAbs( + strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); + + SkStrokeRec::Style style = stroke.getStyle(); + bool isStrokeOnly = + SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; + bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; + + params.fInnerXRadius = 0; + params.fInnerYRadius = 0; + if (hasStroke) { + if (SkScalarNearlyZero(scaledStroke.length())) { + scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); + } else { + scaledStroke.scale(SK_ScalarHalf); + } + + // we only handle thick strokes for near-circular ellipses + if (scaledStroke.length() > SK_ScalarHalf && + (0.5f * params.fXRadius > params.fYRadius || + 0.5f * params.fYRadius > params.fXRadius)) { + return nullptr; + } + + // we don't handle it if curvature of the stroke is less than curvature of the ellipse + if (scaledStroke.fX * (params.fXRadius * params.fYRadius) < + (scaledStroke.fY * scaledStroke.fY) * params.fXRadius || + scaledStroke.fY * (params.fXRadius * params.fXRadius) < + (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) { + return nullptr; + } + + // this is legit only if scale & translation (which should be the case at the moment) + if (isStrokeOnly) { + params.fInnerXRadius = params.fXRadius - scaledStroke.fX; + params.fInnerYRadius = params.fYRadius - scaledStroke.fY; + } + + params.fXRadius += scaledStroke.fX; + params.fYRadius += scaledStroke.fY; + } + + // For large ovals with low precision floats, we fall back to the path renderer. + // To compute the AA at the edge we divide by the gradient, which is clamped to a + // minimum value to avoid divides by zero. With large ovals and low precision this + // leads to blurring at the edge of the oval. + const SkScalar kMaxOvalRadius = 16384; + if (!context->priv().caps()->shaderCaps()->floatIs32Bits() && + (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) { + return nullptr; + } + + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, + params, stroke); + } + + EllipseOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const DeviceSpaceParams& params, + const SkStrokeRec& stroke) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kCoverage) + , fUseScale(false) { + SkStrokeRec::Style style = stroke.getStyle(); + bool isStrokeOnly = + SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; + + fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius, + params.fInnerXRadius, params.fInnerYRadius, + SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius, + params.fCenter.fY - params.fYRadius, + params.fCenter.fX + params.fXRadius, + params.fCenter.fY + params.fYRadius)}); + + this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsHairline::kNo); + + // Outset bounds to include half-pixel width antialiasing. + fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf); + + fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0; + fViewMatrixIfUsingLocalCoords = viewMatrix; + } + + const char* name() const override { return "EllipseOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf("Stroked: %d\n", fStroked); + for (const auto& geo : fEllipses) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " + "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n", + geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop, + geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, + geo.fInnerXRadius, geo.fInnerYRadius); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + fUseScale = !caps.shaderCaps()->floatIs32Bits() && + !caps.shaderCaps()->hasLowFragmentPrecision(); + SkPMColor4f* color = &fEllipses.front().fColor; + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, color, + &fWideColor); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + +private: + void onPrepareDraws(Target* target) override { + SkMatrix localMatrix; + if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { + return; + } + + // Setup geometry processor + sk_sp gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale, + localMatrix)); + QuadHelper helper(target, gp->vertexStride(), fEllipses.count()); + GrVertexWriter verts{helper.vertices()}; + if (!verts.fPtr) { + return; + } + + for (const auto& ellipse : fEllipses) { + GrVertexColor color(ellipse.fColor, fWideColor); + SkScalar xRadius = ellipse.fXRadius; + SkScalar yRadius = ellipse.fYRadius; + + // Compute the reciprocals of the radii here to save time in the shader + struct { float xOuter, yOuter, xInner, yInner; } invRadii = { + SkScalarInvert(xRadius), + SkScalarInvert(yRadius), + SkScalarInvert(ellipse.fInnerXRadius), + SkScalarInvert(ellipse.fInnerYRadius) + }; + SkScalar xMaxOffset = xRadius + SK_ScalarHalf; + SkScalar yMaxOffset = yRadius + SK_ScalarHalf; + + if (!fStroked) { + // For filled ellipses we map a unit circle in the vertex attributes rather than + // computing an ellipse and modifying that distance, so we normalize to 1 + xMaxOffset /= xRadius; + yMaxOffset /= yRadius; + } + + // The inner radius in the vertex data must be specified in normalized space. + verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fDevBounds), + color, + origin_centered_tri_strip(xMaxOffset, yMaxOffset), + GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)), + invRadii); + } + helper.recordDraw(target, std::move(gp)); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + EllipseOp* that = t->cast(); + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (fStroked != that->fStroked) { + return CombineResult::kCannotCombine; + } + + if (fHelper.usesLocalCoords() && + !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { + return CombineResult::kCannotCombine; + } + + fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + struct Ellipse { + SkPMColor4f fColor; + SkScalar fXRadius; + SkScalar fYRadius; + SkScalar fInnerXRadius; + SkScalar fInnerYRadius; + SkRect fDevBounds; + }; + + SkMatrix fViewMatrixIfUsingLocalCoords; + Helper fHelper; + bool fStroked; + bool fWideColor; + bool fUseScale; + SkSTArray<1, Ellipse, true> fEllipses; + + typedef GrMeshDrawOp INHERITED; +}; + +///////////////////////////////////////////////////////////////////////////////////////////////// + +class DIEllipseOp : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + + struct DeviceSpaceParams { + SkPoint fCenter; + SkScalar fXRadius; + SkScalar fYRadius; + SkScalar fInnerXRadius; + SkScalar fInnerYRadius; + DIEllipseStyle fStyle; + }; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& ellipse, + const SkStrokeRec& stroke) { + DeviceSpaceParams params; + params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); + params.fXRadius = SkScalarHalf(ellipse.width()); + params.fYRadius = SkScalarHalf(ellipse.height()); + + SkStrokeRec::Style style = stroke.getStyle(); + params.fStyle = (SkStrokeRec::kStroke_Style == style) + ? DIEllipseStyle::kStroke + : (SkStrokeRec::kHairline_Style == style) + ? DIEllipseStyle::kHairline + : DIEllipseStyle::kFill; + + params.fInnerXRadius = 0; + params.fInnerYRadius = 0; + if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) { + SkScalar strokeWidth = stroke.getWidth(); + + if (SkScalarNearlyZero(strokeWidth)) { + strokeWidth = SK_ScalarHalf; + } else { + strokeWidth *= SK_ScalarHalf; + } + + // we only handle thick strokes for near-circular ellipses + if (strokeWidth > SK_ScalarHalf && + (SK_ScalarHalf * params.fXRadius > params.fYRadius || + SK_ScalarHalf * params.fYRadius > params.fXRadius)) { + return nullptr; + } + + // we don't handle it if curvature of the stroke is less than curvature of the ellipse + if (strokeWidth * (params.fYRadius * params.fYRadius) < + (strokeWidth * strokeWidth) * params.fXRadius) { + return nullptr; + } + if (strokeWidth * (params.fXRadius * params.fXRadius) < + (strokeWidth * strokeWidth) * params.fYRadius) { + return nullptr; + } + + // set inner radius (if needed) + if (SkStrokeRec::kStroke_Style == style) { + params.fInnerXRadius = params.fXRadius - strokeWidth; + params.fInnerYRadius = params.fYRadius - strokeWidth; + } + + params.fXRadius += strokeWidth; + params.fYRadius += strokeWidth; + } + + // For large ovals with low precision floats, we fall back to the path renderer. + // To compute the AA at the edge we divide by the gradient, which is clamped to a + // minimum value to avoid divides by zero. With large ovals and low precision this + // leads to blurring at the edge of the oval. + const SkScalar kMaxOvalRadius = 16384; + if (!context->priv().caps()->shaderCaps()->floatIs32Bits() && + (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) { + return nullptr; + } + + if (DIEllipseStyle::kStroke == params.fStyle && + (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) { + params.fStyle = DIEllipseStyle::kFill; + } + return Helper::FactoryHelper(context, std::move(paint), params, viewMatrix); + } + + DIEllipseOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const DeviceSpaceParams& params, const SkMatrix& viewMatrix) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kCoverage) + , fUseScale(false) { + // This expands the outer rect so that after CTM we end up with a half-pixel border + SkScalar a = viewMatrix[SkMatrix::kMScaleX]; + SkScalar b = viewMatrix[SkMatrix::kMSkewX]; + SkScalar c = viewMatrix[SkMatrix::kMSkewY]; + SkScalar d = viewMatrix[SkMatrix::kMScaleY]; + SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c); + SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d); + + fEllipses.emplace_back( + Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius, + params.fInnerYRadius, geoDx, geoDy, params.fStyle, + SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius - geoDx, + params.fCenter.fY - params.fYRadius - geoDy, + params.fCenter.fX + params.fXRadius + geoDx, + params.fCenter.fY + params.fYRadius + geoDy)}); + this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes, + IsHairline::kNo); + } + + const char* name() const override { return "DIEllipseOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (const auto& geo : fEllipses) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, " + "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, " + "GeoDY: %.2f\n", + geo.fColor.toBytes_RGBA(), geo.fBounds.fLeft, geo.fBounds.fTop, + geo.fBounds.fRight, geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius, + geo.fInnerXRadius, geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + fUseScale = !caps.shaderCaps()->floatIs32Bits() && + !caps.shaderCaps()->hasLowFragmentPrecision(); + SkPMColor4f* color = &fEllipses.front().fColor; + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, color, + &fWideColor); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + +private: + void onPrepareDraws(Target* target) override { + // Setup geometry processor + sk_sp gp( + new DIEllipseGeometryProcessor(fWideColor, fUseScale, this->viewMatrix(), + this->style())); + + QuadHelper helper(target, gp->vertexStride(), fEllipses.count()); + GrVertexWriter verts{helper.vertices()}; + if (!verts.fPtr) { + return; + } + + for (const auto& ellipse : fEllipses) { + GrVertexColor color(ellipse.fColor, fWideColor); + SkScalar xRadius = ellipse.fXRadius; + SkScalar yRadius = ellipse.fYRadius; + + // This adjusts the "radius" to include the half-pixel border + SkScalar offsetDx = ellipse.fGeoDx / xRadius; + SkScalar offsetDy = ellipse.fGeoDy / yRadius; + + // By default, constructed so that inner offset is (0, 0) for all points + SkScalar innerRatioX = -offsetDx; + SkScalar innerRatioY = -offsetDy; + + // ... unless we're stroked + if (DIEllipseStyle::kStroke == this->style()) { + innerRatioX = xRadius / ellipse.fInnerXRadius; + innerRatioY = yRadius / ellipse.fInnerYRadius; + } + + verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fBounds), + color, + origin_centered_tri_strip(1.0f + offsetDx, 1.0f + offsetDy), + GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)), + origin_centered_tri_strip(innerRatioX + offsetDx, + innerRatioY + offsetDy)); + } + helper.recordDraw(target, std::move(gp)); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + DIEllipseOp* that = t->cast(); + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (this->style() != that->style()) { + return CombineResult::kCannotCombine; + } + + // TODO rewrite to allow positioning on CPU + if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; } + DIEllipseStyle style() const { return fEllipses[0].fStyle; } + + struct Ellipse { + SkMatrix fViewMatrix; + SkPMColor4f fColor; + SkScalar fXRadius; + SkScalar fYRadius; + SkScalar fInnerXRadius; + SkScalar fInnerYRadius; + SkScalar fGeoDx; + SkScalar fGeoDy; + DIEllipseStyle fStyle; + SkRect fBounds; + }; + + Helper fHelper; + bool fWideColor; + bool fUseScale; + SkSTArray<1, Ellipse, true> fEllipses; + + typedef GrMeshDrawOp INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// We have three possible cases for geometry for a roundrect. +// +// In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch: +// ____________ +// |_|________|_| +// | | | | +// | | | | +// | | | | +// |_|________|_| +// |_|________|_| +// +// For strokes, we don't draw the center quad. +// +// For circular roundrects, in the case where the stroke width is greater than twice +// the corner radius (overstroke), we add additional geometry to mark out the rectangle +// in the center. The shared vertices are duplicated so we can set a different outer radius +// for the fill calculation. +// ____________ +// |_|________|_| +// | |\ ____ /| | +// | | | | | | +// | | |____| | | +// |_|/______\|_| +// |_|________|_| +// +// We don't draw the center quad from the fill rect in this case. +// +// For filled rrects that need to provide a distance vector we resuse the overstroke +// geometry but make the inner rect degenerate (either a point or a horizontal or +// vertical line). + +static const uint16_t gOverstrokeRRectIndices[] = { + // clang-format off + // overstroke quads + // we place this at the beginning so that we can skip these indices when rendering normally + 16, 17, 19, 16, 19, 18, + 19, 17, 23, 19, 23, 21, + 21, 23, 22, 21, 22, 20, + 22, 16, 18, 22, 18, 20, + + // corners + 0, 1, 5, 0, 5, 4, + 2, 3, 7, 2, 7, 6, + 8, 9, 13, 8, 13, 12, + 10, 11, 15, 10, 15, 14, + + // edges + 1, 2, 6, 1, 6, 5, + 4, 5, 9, 4, 9, 8, + 6, 7, 11, 6, 11, 10, + 9, 10, 14, 9, 14, 13, + + // center + // we place this at the end so that we can ignore these indices when not rendering as filled + 5, 6, 10, 5, 10, 9, + // clang-format on +}; + +// fill and standard stroke indices skip the overstroke "ring" +static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4; + +// overstroke count is arraysize minus the center indices +static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6; +// fill count skips overstroke indices and includes center +static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6; +// stroke count is fill count minus center indices +static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6; +static const int kVertsPerStandardRRect = 16; +static const int kVertsPerOverstrokeRRect = 24; + +enum RRectType { + kFill_RRectType, + kStroke_RRectType, + kOverstroke_RRectType, +}; + +static int rrect_type_to_vert_count(RRectType type) { + switch (type) { + case kFill_RRectType: + case kStroke_RRectType: + return kVertsPerStandardRRect; + case kOverstroke_RRectType: + return kVertsPerOverstrokeRRect; + } + SK_ABORT("Invalid type"); +} + +static int rrect_type_to_index_count(RRectType type) { + switch (type) { + case kFill_RRectType: + return kIndicesPerFillRRect; + case kStroke_RRectType: + return kIndicesPerStrokeRRect; + case kOverstroke_RRectType: + return kIndicesPerOverstrokeRRect; + } + SK_ABORT("Invalid type"); +} + +static const uint16_t* rrect_type_to_indices(RRectType type) { + switch (type) { + case kFill_RRectType: + case kStroke_RRectType: + return gStandardRRectIndices; + case kOverstroke_RRectType: + return gOverstrokeRRectIndices; + } + SK_ABORT("Invalid type"); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// For distance computations in the interior of filled rrects we: +// +// add a interior degenerate (point or line) rect +// each vertex of that rect gets -outerRad as its radius +// this makes the computation of the distance to the outer edge be negative +// negative values are caught and then handled differently in the GP's onEmitCode +// each vertex is also given the normalized x & y distance from the interior rect's edge +// the GP takes the min of those depths +1 to get the normalized distance to the outer edge + +class CircularRRectOp : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates + // whether the rrect is only stroked or stroked and filled. + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& devRect, + float devRadius, + float devStrokeWidth, + bool strokeOnly) { + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, + devRect, devRadius, + devStrokeWidth, strokeOnly); + } + CircularRRectOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius, + float devStrokeWidth, bool strokeOnly) + : INHERITED(ClassID()) + , fViewMatrixIfUsingLocalCoords(viewMatrix) + , fHelper(helperArgs, GrAAType::kCoverage) { + SkRect bounds = devRect; + SkASSERT(!(devStrokeWidth <= 0 && strokeOnly)); + SkScalar innerRadius = 0.0f; + SkScalar outerRadius = devRadius; + SkScalar halfWidth = 0; + RRectType type = kFill_RRectType; + if (devStrokeWidth > 0) { + if (SkScalarNearlyZero(devStrokeWidth)) { + halfWidth = SK_ScalarHalf; + } else { + halfWidth = SkScalarHalf(devStrokeWidth); + } + + if (strokeOnly) { + // Outset stroke by 1/4 pixel + devStrokeWidth += 0.25f; + // If stroke is greater than width or height, this is still a fill + // Otherwise we compute stroke params + if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) { + innerRadius = devRadius - halfWidth; + type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType; + } + } + outerRadius += halfWidth; + bounds.outset(halfWidth, halfWidth); + } + + // The radii are outset for two reasons. First, it allows the shader to simply perform + // simpler computation because the computed alpha is zero, rather than 50%, at the radius. + // Second, the outer radius is used to compute the verts of the bounding box that is + // rendered and the outset ensures the box will cover all partially covered by the rrect + // corners. + outerRadius += SK_ScalarHalf; + innerRadius -= SK_ScalarHalf; + + this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo); + + // Expand the rect for aa to generate correct vertices. + bounds.outset(SK_ScalarHalf, SK_ScalarHalf); + + fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type}); + fVertCount = rrect_type_to_vert_count(type); + fIndexCount = rrect_type_to_index_count(type); + fAllFill = (kFill_RRectType == type); + } + + const char* name() const override { return "CircularRRectOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (int i = 0; i < fRRects.count(); ++i) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," + "InnerRad: %.2f, OuterRad: %.2f\n", + fRRects[i].fColor.toBytes_RGBA(), fRRects[i].fDevBounds.fLeft, + fRRects[i].fDevBounds.fTop, fRRects[i].fDevBounds.fRight, + fRRects[i].fDevBounds.fBottom, fRRects[i].fInnerRadius, + fRRects[i].fOuterRadius); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + SkPMColor4f* color = &fRRects.front().fColor; + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, color, + &fWideColor); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + +private: + static void FillInOverstrokeVerts(GrVertexWriter& verts, const SkRect& bounds, SkScalar smInset, + SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius, + SkScalar innerRadius, const GrVertexColor& color) { + SkASSERT(smInset < bigInset); + + // TL + verts.write(bounds.fLeft + smInset, bounds.fTop + smInset, + color, + xOffset, 0.0f, + outerRadius, innerRadius); + + // TR + verts.write(bounds.fRight - smInset, bounds.fTop + smInset, + color, + xOffset, 0.0f, + outerRadius, innerRadius); + + verts.write(bounds.fLeft + bigInset, bounds.fTop + bigInset, + color, + 0.0f, 0.0f, + outerRadius, innerRadius); + + verts.write(bounds.fRight - bigInset, bounds.fTop + bigInset, + color, + 0.0f, 0.0f, + outerRadius, innerRadius); + + verts.write(bounds.fLeft + bigInset, bounds.fBottom - bigInset, + color, + 0.0f, 0.0f, + outerRadius, innerRadius); + + verts.write(bounds.fRight - bigInset, bounds.fBottom - bigInset, + color, + 0.0f, 0.0f, + outerRadius, innerRadius); + + // BL + verts.write(bounds.fLeft + smInset, bounds.fBottom - smInset, + color, + xOffset, 0.0f, + outerRadius, innerRadius); + + // BR + verts.write(bounds.fRight - smInset, bounds.fBottom - smInset, + color, + xOffset, 0.0f, + outerRadius, innerRadius); + } + + void onPrepareDraws(Target* target) override { + // Invert the view matrix as a local matrix (if any other processors require coords). + SkMatrix localMatrix; + if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { + return; + } + + // Setup geometry processor + sk_sp gp( + new CircleGeometryProcessor(!fAllFill, false, false, false, false, fWideColor, + localMatrix)); + + sk_sp vertexBuffer; + int firstVertex; + + GrVertexWriter verts{target->makeVertexSpace(gp->vertexStride(), fVertCount, + &vertexBuffer, &firstVertex)}; + if (!verts.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + sk_sp indexBuffer; + int firstIndex = 0; + uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); + if (!indices) { + SkDebugf("Could not allocate indices\n"); + return; + } + + int currStartVertex = 0; + for (const auto& rrect : fRRects) { + GrVertexColor color(rrect.fColor, fWideColor); + SkScalar outerRadius = rrect.fOuterRadius; + const SkRect& bounds = rrect.fDevBounds; + + SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius, + bounds.fBottom - outerRadius, bounds.fBottom}; + + SkScalar yOuterRadii[4] = {-1, 0, 0, 1}; + // The inner radius in the vertex data must be specified in normalized space. + // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius. + SkScalar innerRadius = rrect.fType != kFill_RRectType + ? rrect.fInnerRadius / rrect.fOuterRadius + : -1.0f / rrect.fOuterRadius; + for (int i = 0; i < 4; ++i) { + verts.write(bounds.fLeft, yCoords[i], + color, + -1.0f, yOuterRadii[i], + outerRadius, innerRadius); + + verts.write(bounds.fLeft + outerRadius, yCoords[i], + color, + 0.0f, yOuterRadii[i], + outerRadius, innerRadius); + + verts.write(bounds.fRight - outerRadius, yCoords[i], + color, + 0.0f, yOuterRadii[i], + outerRadius, innerRadius); + + verts.write(bounds.fRight, yCoords[i], + color, + 1.0f, yOuterRadii[i], + outerRadius, innerRadius); + } + // Add the additional vertices for overstroked rrects. + // Effectively this is an additional stroked rrect, with its + // outer radius = outerRadius - innerRadius, and inner radius = 0. + // This will give us correct AA in the center and the correct + // distance to the outer edge. + // + // Also, the outer offset is a constant vector pointing to the right, which + // guarantees that the distance value along the outer rectangle is constant. + if (kOverstroke_RRectType == rrect.fType) { + SkASSERT(rrect.fInnerRadius <= 0.0f); + + SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius; + // this is the normalized distance from the outer rectangle of this + // geometry to the outer edge + SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius; + + FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset, + overstrokeOuterRadius, 0.0f, color); + } + + const uint16_t* primIndices = rrect_type_to_indices(rrect.fType); + const int primIndexCount = rrect_type_to_index_count(rrect.fType); + for (int i = 0; i < primIndexCount; ++i) { + *indices++ = primIndices[i] + currStartVertex; + } + + currStartVertex += rrect_type_to_vert_count(rrect.fType); + } + + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, + GrPrimitiveRestart::kNo); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + CircularRRectOp* that = t->cast(); + + // can only represent 65535 unique vertices with 16-bit indices + if (fVertCount + that->fVertCount > 65536) { + return CombineResult::kCannotCombine; + } + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (fHelper.usesLocalCoords() && + !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { + return CombineResult::kCannotCombine; + } + + fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin()); + fVertCount += that->fVertCount; + fIndexCount += that->fIndexCount; + fAllFill = fAllFill && that->fAllFill; + fWideColor = fWideColor || that->fWideColor; + return CombineResult::kMerged; + } + + struct RRect { + SkPMColor4f fColor; + SkScalar fInnerRadius; + SkScalar fOuterRadius; + SkRect fDevBounds; + RRectType fType; + }; + + SkMatrix fViewMatrixIfUsingLocalCoords; + Helper fHelper; + int fVertCount; + int fIndexCount; + bool fAllFill; + bool fWideColor; + SkSTArray<1, RRect, true> fRRects; + + typedef GrMeshDrawOp INHERITED; +}; + +static const int kNumRRectsInIndexBuffer = 256; + +GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey); +GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey); +static sk_sp get_rrect_index_buffer(RRectType type, + GrResourceProvider* resourceProvider) { + GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey); + GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey); + switch (type) { + case kFill_RRectType: + return resourceProvider->findOrCreatePatternedIndexBuffer( + gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer, + kVertsPerStandardRRect, gRRectOnlyIndexBufferKey); + case kStroke_RRectType: + return resourceProvider->findOrCreatePatternedIndexBuffer( + gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer, + kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey); + default: + SkASSERT(false); + return nullptr; + } +} + +class EllipticalRRectOp : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates + // whether the rrect is only stroked or stroked and filled. + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& devRect, + float devXRadius, + float devYRadius, + SkVector devStrokeWidths, + bool strokeOnly) { + SkASSERT(devXRadius >= 0.5); + SkASSERT(devYRadius >= 0.5); + SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0)); + SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0)); + if (devStrokeWidths.fX > 0) { + if (SkScalarNearlyZero(devStrokeWidths.length())) { + devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf); + } else { + devStrokeWidths.scale(SK_ScalarHalf); + } + + // we only handle thick strokes for near-circular ellipses + if (devStrokeWidths.length() > SK_ScalarHalf && + (SK_ScalarHalf * devXRadius > devYRadius || + SK_ScalarHalf * devYRadius > devXRadius)) { + return nullptr; + } + + // we don't handle it if curvature of the stroke is less than curvature of the ellipse + if (devStrokeWidths.fX * (devYRadius * devYRadius) < + (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) { + return nullptr; + } + if (devStrokeWidths.fY * (devXRadius * devXRadius) < + (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) { + return nullptr; + } + } + return Helper::FactoryHelper(context, std::move(paint), + viewMatrix, devRect, + devXRadius, devYRadius, devStrokeWidths, + strokeOnly); + } + + EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius, + float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kCoverage) + , fUseScale(false) { + SkScalar innerXRadius = 0.0f; + SkScalar innerYRadius = 0.0f; + SkRect bounds = devRect; + bool stroked = false; + if (devStrokeHalfWidths.fX > 0) { + // this is legit only if scale & translation (which should be the case at the moment) + if (strokeOnly) { + innerXRadius = devXRadius - devStrokeHalfWidths.fX; + innerYRadius = devYRadius - devStrokeHalfWidths.fY; + stroked = (innerXRadius >= 0 && innerYRadius >= 0); + } + + devXRadius += devStrokeHalfWidths.fX; + devYRadius += devStrokeHalfWidths.fY; + bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY); + } + + fStroked = stroked; + fViewMatrixIfUsingLocalCoords = viewMatrix; + this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo); + // Expand the rect for aa in order to generate the correct vertices. + bounds.outset(SK_ScalarHalf, SK_ScalarHalf); + fRRects.emplace_back( + RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds}); + } + + const char* name() const override { return "EllipticalRRectOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf("Stroked: %d\n", fStroked); + for (const auto& geo : fRRects) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " + "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n", + geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop, + geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, + geo.fInnerXRadius, geo.fInnerYRadius); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + fUseScale = !caps.shaderCaps()->floatIs32Bits(); + SkPMColor4f* color = &fRRects.front().fColor; + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, color, + &fWideColor); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + +private: + void onPrepareDraws(Target* target) override { + SkMatrix localMatrix; + if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { + return; + } + + // Setup geometry processor + sk_sp gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale, + localMatrix)); + + // drop out the middle quad if we're stroked + int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect; + sk_sp indexBuffer = get_rrect_index_buffer( + fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider()); + + if (!indexBuffer) { + SkDebugf("Could not allocate indices\n"); + return; + } + PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(), + std::move(indexBuffer), kVertsPerStandardRRect, indicesPerInstance, + fRRects.count()); + GrVertexWriter verts{helper.vertices()}; + if (!verts.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + for (const auto& rrect : fRRects) { + GrVertexColor color(rrect.fColor, fWideColor); + // Compute the reciprocals of the radii here to save time in the shader + float reciprocalRadii[4] = { + SkScalarInvert(rrect.fXRadius), + SkScalarInvert(rrect.fYRadius), + SkScalarInvert(rrect.fInnerXRadius), + SkScalarInvert(rrect.fInnerYRadius) + }; + + // Extend the radii out half a pixel to antialias. + SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf; + SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf; + + SkScalar xMaxOffset = xOuterRadius; + SkScalar yMaxOffset = yOuterRadius; + if (!fStroked) { + // For filled rrects we map a unit circle in the vertex attributes rather than + // computing an ellipse and modifying that distance, so we normalize to 1. + xMaxOffset /= rrect.fXRadius; + yMaxOffset /= rrect.fYRadius; + } + + const SkRect& bounds = rrect.fDevBounds; + + SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius, + bounds.fBottom - yOuterRadius, bounds.fBottom}; + SkScalar yOuterOffsets[4] = {yMaxOffset, + SK_ScalarNearlyZero, // we're using inversesqrt() in + // shader, so can't be exactly 0 + SK_ScalarNearlyZero, yMaxOffset}; + + auto maybeScale = GrVertexWriter::If(fUseScale, SkTMax(rrect.fXRadius, rrect.fYRadius)); + for (int i = 0; i < 4; ++i) { + verts.write(bounds.fLeft, yCoords[i], + color, + xMaxOffset, yOuterOffsets[i], + maybeScale, + reciprocalRadii); + + verts.write(bounds.fLeft + xOuterRadius, yCoords[i], + color, + SK_ScalarNearlyZero, yOuterOffsets[i], + maybeScale, + reciprocalRadii); + + verts.write(bounds.fRight - xOuterRadius, yCoords[i], + color, + SK_ScalarNearlyZero, yOuterOffsets[i], + maybeScale, + reciprocalRadii); + + verts.write(bounds.fRight, yCoords[i], + color, + xMaxOffset, yOuterOffsets[i], + maybeScale, + reciprocalRadii); + } + } + helper.recordDraw(target, std::move(gp)); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + EllipticalRRectOp* that = t->cast(); + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (fStroked != that->fStroked) { + return CombineResult::kCannotCombine; + } + + if (fHelper.usesLocalCoords() && + !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { + return CombineResult::kCannotCombine; + } + + fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin()); + fWideColor = fWideColor || that->fWideColor; + return CombineResult::kMerged; + } + + struct RRect { + SkPMColor4f fColor; + SkScalar fXRadius; + SkScalar fYRadius; + SkScalar fInnerXRadius; + SkScalar fInnerYRadius; + SkRect fDevBounds; + }; + + SkMatrix fViewMatrixIfUsingLocalCoords; + Helper fHelper; + bool fStroked; + bool fWideColor; + bool fUseScale; + SkSTArray<1, RRect, true> fRRects; + + typedef GrMeshDrawOp INHERITED; +}; + +std::unique_ptr GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRRect& rrect, + const SkStrokeRec& stroke, + const GrShaderCaps* shaderCaps) { + SkASSERT(viewMatrix.rectStaysRect()); + SkASSERT(viewMatrix.isSimilarity()); + SkASSERT(rrect.isSimple()); + SkASSERT(!rrect.isOval()); + SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY); + + // RRect ops only handle simple, but not too simple, rrects. + // Do any matrix crunching before we reset the draw state for device coords. + const SkRect& rrectBounds = rrect.getBounds(); + SkRect bounds; + viewMatrix.mapRect(&bounds, rrectBounds); + + SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX; + SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] + + viewMatrix[SkMatrix::kMSkewY])); + + // Do mapping of stroke. Use -1 to indicate fill-only draws. + SkScalar scaledStroke = -1; + SkScalar strokeWidth = stroke.getWidth(); + SkStrokeRec::Style style = stroke.getStyle(); + + bool isStrokeOnly = + SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; + bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; + + if (hasStroke) { + if (SkStrokeRec::kHairline_Style == style) { + scaledStroke = SK_Scalar1; + } else { + scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); + } + } + + // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on + // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine- + // patch will have fractional coverage. This only matters when the interior is actually filled. + // We could consider falling back to rect rendering here, since a tiny radius is + // indistinguishable from a square corner. + if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) { + return nullptr; + } + + return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius, + scaledStroke, isStrokeOnly); +} + +static std::unique_ptr make_rrect_op(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRRect& rrect, + const SkStrokeRec& stroke) { + SkASSERT(viewMatrix.rectStaysRect()); + SkASSERT(rrect.isSimple()); + SkASSERT(!rrect.isOval()); + + // RRect ops only handle simple, but not too simple, rrects. + // Do any matrix crunching before we reset the draw state for device coords. + const SkRect& rrectBounds = rrect.getBounds(); + SkRect bounds; + viewMatrix.mapRect(&bounds, rrectBounds); + + SkVector radii = SkRRectPriv::GetSimpleRadii(rrect); + SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX + + viewMatrix[SkMatrix::kMSkewY] * radii.fY); + SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX + + viewMatrix[SkMatrix::kMScaleY] * radii.fY); + + SkStrokeRec::Style style = stroke.getStyle(); + + // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws. + SkVector scaledStroke = {-1, -1}; + SkScalar strokeWidth = stroke.getWidth(); + + bool isStrokeOnly = + SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; + bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; + + if (hasStroke) { + if (SkStrokeRec::kHairline_Style == style) { + scaledStroke.set(1, 1); + } else { + scaledStroke.fX = SkScalarAbs( + strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); + scaledStroke.fY = SkScalarAbs( + strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); + } + + // if half of strokewidth is greater than radius, we don't handle that right now + if ((SK_ScalarHalf * scaledStroke.fX > xRadius || + SK_ScalarHalf * scaledStroke.fY > yRadius)) { + return nullptr; + } + } + + // The matrix may have a rotation by an odd multiple of 90 degrees. + if (viewMatrix.getScaleX() == 0) { + std::swap(xRadius, yRadius); + std::swap(scaledStroke.fX, scaledStroke.fY); + } + + // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on + // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine- + // patch will have fractional coverage. This only matters when the interior is actually filled. + // We could consider falling back to rect rendering here, since a tiny radius is + // indistinguishable from a square corner. + if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) { + return nullptr; + } + + // if the corners are circles, use the circle renderer + return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds, + xRadius, yRadius, scaledStroke, isStrokeOnly); +} + +std::unique_ptr GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRRect& rrect, + const SkStrokeRec& stroke, + const GrShaderCaps* shaderCaps) { + if (rrect.isOval()) { + return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(), + GrStyle(stroke, nullptr), shaderCaps); + } + + if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) { + return nullptr; + } + + return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke); +} + +/////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& oval, + const GrStyle& style, + const GrShaderCaps* shaderCaps) { + SkScalar width = oval.width(); + SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) && + circle_stays_circle(viewMatrix)); + + auto r = width / 2.f; + SkPoint center = { oval.centerX(), oval.centerY() }; + if (style.hasNonDashPathEffect()) { + return nullptr; + } else if (style.isDashed()) { + if (style.strokeRec().getCap() != SkPaint::kButt_Cap || + style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) { + return nullptr; + } + auto onInterval = style.dashIntervals()[0]; + auto offInterval = style.dashIntervals()[1]; + if (offInterval == 0) { + GrStyle strokeStyle(style.strokeRec(), nullptr); + return MakeOvalOp(context, std::move(paint), viewMatrix, oval, + strokeStyle, shaderCaps); + } else if (onInterval == 0) { + // There is nothing to draw but we have no way to indicate that here. + return nullptr; + } + auto angularOnInterval = onInterval / r; + auto angularOffInterval = offInterval / r; + auto phaseAngle = style.dashPhase() / r; + // Currently this function doesn't accept ovals with different start angles, though + // it could. + static const SkScalar kStartAngle = 0.f; + return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r, + style.strokeRec().getWidth(), kStartAngle, + angularOnInterval, angularOffInterval, phaseAngle); + } + return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style); +} + +std::unique_ptr GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& oval, + const GrStyle& style, + const GrShaderCaps* shaderCaps) { + // we can draw circles + SkScalar width = oval.width(); + if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) && + circle_stays_circle(viewMatrix)) { + return MakeCircleOp(context, std::move(paint), viewMatrix, oval, style, shaderCaps); + } + + if (style.pathEffect()) { + return nullptr; + } + + // prefer the device space ellipse op for batchability + if (viewMatrix.rectStaysRect()) { + return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec()); + } + + // Otherwise, if we have shader derivative support, render as device-independent + if (shaderCaps->shaderDerivativeSupport()) { + SkScalar a = viewMatrix[SkMatrix::kMScaleX]; + SkScalar b = viewMatrix[SkMatrix::kMSkewX]; + SkScalar c = viewMatrix[SkMatrix::kMSkewY]; + SkScalar d = viewMatrix[SkMatrix::kMScaleY]; + // Check for near-degenerate matrix + if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) { + return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval, + style.strokeRec()); + } + } + + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////// + +std::unique_ptr GrOvalOpFactory::MakeArcOp(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& oval, SkScalar startAngle, + SkScalar sweepAngle, bool useCenter, + const GrStyle& style, + const GrShaderCaps* shaderCaps) { + SkASSERT(!oval.isEmpty()); + SkASSERT(sweepAngle); + SkScalar width = oval.width(); + if (SkScalarAbs(sweepAngle) >= 360.f) { + return nullptr; + } + if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) { + return nullptr; + } + SkPoint center = {oval.centerX(), oval.centerY()}; + CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle), + useCenter}; + return CircleOp::Make(context, std::move(paint), viewMatrix, + center, width / 2.f, style, &arcParams); +} + +/////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(CircleOp) { + do { + SkScalar rotate = random->nextSScalar1() * 360.f; + SkScalar translateX = random->nextSScalar1() * 1000.f; + SkScalar translateY = random->nextSScalar1() * 1000.f; + SkScalar scale; + do { + scale = random->nextSScalar1() * 100.f; + } while (scale == 0); + SkMatrix viewMatrix; + viewMatrix.setRotate(rotate); + viewMatrix.postTranslate(translateX, translateY); + viewMatrix.postScale(scale, scale); + SkRect circle = GrTest::TestSquare(random); + SkPoint center = {circle.centerX(), circle.centerY()}; + SkScalar radius = circle.width() / 2.f; + SkStrokeRec stroke = GrTest::TestStrokeRec(random); + CircleOp::ArcParams arcParamsTmp; + const CircleOp::ArcParams* arcParams = nullptr; + if (random->nextBool()) { + arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2; + arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f; + arcParamsTmp.fUseCenter = random->nextBool(); + arcParams = &arcParamsTmp; + } + std::unique_ptr op = CircleOp::Make(context, std::move(paint), viewMatrix, + center, radius, + GrStyle(stroke, nullptr), arcParams); + if (op) { + return op; + } + assert_alive(paint); + } while (true); +} + +GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) { + SkScalar rotate = random->nextSScalar1() * 360.f; + SkScalar translateX = random->nextSScalar1() * 1000.f; + SkScalar translateY = random->nextSScalar1() * 1000.f; + SkScalar scale; + do { + scale = random->nextSScalar1() * 100.f; + } while (scale == 0); + SkMatrix viewMatrix; + viewMatrix.setRotate(rotate); + viewMatrix.postTranslate(translateX, translateY); + viewMatrix.postScale(scale, scale); + SkRect circle = GrTest::TestSquare(random); + SkPoint center = {circle.centerX(), circle.centerY()}; + SkScalar radius = circle.width() / 2.f; + SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius); + SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f); + SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f); + SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f); + SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f); + return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, + center, radius, strokeWidth, + startAngle, onAngle, offAngle, phase); +} + +GR_DRAW_OP_TEST_DEFINE(EllipseOp) { + SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); + SkRect ellipse = GrTest::TestSquare(random); + return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse, + GrTest::TestStrokeRec(random)); +} + +GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) { + SkMatrix viewMatrix = GrTest::TestMatrix(random); + SkRect ellipse = GrTest::TestSquare(random); + return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse, + GrTest::TestStrokeRec(random)); +} + +GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) { + do { + SkScalar rotate = random->nextSScalar1() * 360.f; + SkScalar translateX = random->nextSScalar1() * 1000.f; + SkScalar translateY = random->nextSScalar1() * 1000.f; + SkScalar scale; + do { + scale = random->nextSScalar1() * 100.f; + } while (scale == 0); + SkMatrix viewMatrix; + viewMatrix.setRotate(rotate); + viewMatrix.postTranslate(translateX, translateY); + viewMatrix.postScale(scale, scale); + SkRect rect = GrTest::TestRect(random); + SkScalar radius = random->nextRangeF(0.1f, 10.f); + SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius); + if (rrect.isOval()) { + continue; + } + std::unique_ptr op = + GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect, + GrTest::TestStrokeRec(random), nullptr); + if (op) { + return op; + } + assert_alive(paint); + } while (true); +} + +GR_DRAW_OP_TEST_DEFINE(RRectOp) { + SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); + const SkRRect& rrect = GrTest::TestRRectSimple(random); + return make_rrect_op(context, std::move(paint), viewMatrix, rrect, + GrTest::TestStrokeRec(random)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.h b/gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.h new file mode 100644 index 0000000000..de25fa18d3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrOvalOpFactory.h @@ -0,0 +1,68 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrOvalOpFactory_DEFINED +#define GrOvalOpFactory_DEFINED + +#include "include/core/SkRefCnt.h" +#include "src/gpu/GrColor.h" + +class GrDrawOp; +class GrPaint; +class GrRecordingContext; +class GrShaderCaps; +class GrStyle; +class SkMatrix; +struct SkRect; +class SkRRect; +class SkStrokeRec; + +/* + * This namespace wraps helper functions that draw ovals, rrects, and arcs (filled & stroked) + */ +class GrOvalOpFactory { +public: + static std::unique_ptr MakeCircleOp(GrRecordingContext*, + GrPaint&&, + const SkMatrix&, + const SkRect& oval, + const GrStyle& style, + const GrShaderCaps*); + + static std::unique_ptr MakeOvalOp(GrRecordingContext*, + GrPaint&&, + const SkMatrix&, + const SkRect& oval, + const GrStyle& style, + const GrShaderCaps*); + + static std::unique_ptr MakeCircularRRectOp(GrRecordingContext*, + GrPaint&&, + const SkMatrix&, + const SkRRect&, + const SkStrokeRec&, + const GrShaderCaps*); + + static std::unique_ptr MakeRRectOp(GrRecordingContext*, + GrPaint&&, + const SkMatrix&, + const SkRRect&, + const SkStrokeRec&, + const GrShaderCaps*); + + static std::unique_ptr MakeArcOp(GrRecordingContext*, + GrPaint&&, + const SkMatrix&, + const SkRect& oval, + SkScalar startAngle, + SkScalar sweepAngle, + bool useCenter, + const GrStyle&, + const GrShaderCaps*); +}; + +#endif // GrOvalOpFactory_DEFINED diff --git a/gfx/skia/skia/src/gpu/ops/GrPathStencilSettings.h b/gfx/skia/skia/src/gpu/ops/GrPathStencilSettings.h new file mode 100644 index 0000000000..8d221736ab --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrPathStencilSettings.h @@ -0,0 +1,96 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrPathStencilSettings_DEFINED +#define GrPathStencilSettings_DEFINED + +#include "src/gpu/GrUserStencilSettings.h" + +//////////////////////////////////////////////////////////////////////////////// +// Stencil rules for paths + +////// Even/Odd + +static constexpr GrUserStencilSettings gEOStencilPass( + GrUserStencilSettings::StaticInit< + 0xffff, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kInvert, + GrUserStencilOp::kKeep, + 0xffff>() +); + +// ok not to check clip b/c stencil pass only wrote inside clip +static constexpr GrUserStencilSettings gEOColorPass( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kNotEqual, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kZero, + 0xffff>() +); + +// have to check clip b/c outside clip will always be zero. +static constexpr GrUserStencilSettings gInvEOColorPass( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqualIfInClip, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kZero, + 0xffff>() +); + +////// Winding + +static constexpr GrUserStencilSettings gWindStencilPass ( + GrUserStencilSettings::StaticInitSeparate< + 0xffff, 0xffff, + GrUserStencilTest::kAlwaysIfInClip, GrUserStencilTest::kAlwaysIfInClip, + 0xffff, 0xffff, + GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap, + GrUserStencilOp::kKeep, GrUserStencilOp::kKeep, + 0xffff, 0xffff>() +); + +static constexpr GrUserStencilSettings gWindColorPass( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kLessIfInClip, // "0 < stencil" is equivalent to "0 != stencil". + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kZero, + 0xffff>() +); + +static constexpr GrUserStencilSettings gInvWindColorPass( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kEqualIfInClip, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kZero, + 0xffff>() +); + +////// Normal render to stencil + +// Sometimes the default path renderer can draw a path directly to the stencil +// buffer without having to first resolve the interior / exterior. +static constexpr GrUserStencilSettings gDirectToStencil( + GrUserStencilSettings::StaticInit< + 0x0000, + GrUserStencilTest::kAlwaysIfInClip, + 0xffff, + GrUserStencilOp::kZero, + GrUserStencilOp::kIncMaybeClamp, + 0xffff>() +); + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp b/gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp new file mode 100644 index 0000000000..27bd00eec0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp @@ -0,0 +1,1042 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrQuadPerEdgeAA.h" + +#include "include/private/SkNx.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +#define AI SK_ALWAYS_INLINE + +namespace { + +// Helper data types since there is a lot of information that needs to be passed around to +// avoid recalculation in the different procedures for tessellating an AA quad. + +using V4f = skvx::Vec<4, float>; +using M4f = skvx::Vec<4, int32_t>; + +struct Vertices { + // X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f + V4f fX, fY, fW; + // U, V, and R coordinates representing local quad. Ignored depending on uvrCount (0, 1, 2). + V4f fU, fV, fR; + int fUVRCount; +}; + +struct QuadMetadata { + // Normalized edge vectors of the device space quad, ordered L, B, T, R (i.e. nextCCW(x) - x). + V4f fDX, fDY; + // 1 / edge length of the device space quad + V4f fInvLengths; + // Edge mask (set to all 1s if aa flags is kAll), otherwise 1.f if edge was AA, 0.f if non-AA. + V4f fMask; +}; + +struct Edges { + // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR. + V4f fA, fB, fC; + // Whether or not the edge normals had to be flipped to preserve positive distance on the inside + bool fFlipped; +}; + +static constexpr float kTolerance = 1e-2f; +// True/false bit masks for initializing an M4f +static constexpr int32_t kTrue = ~0; +static constexpr int32_t kFalse = 0; + +static AI V4f fma(const V4f& f, const V4f& m, const V4f& a) { + return mad(f, m, a); +} + +// These rotate the points/edge values either clockwise or counterclockwise assuming tri strip +// order. +static AI V4f nextCW(const V4f& v) { + return skvx::shuffle<2, 0, 3, 1>(v); +} + +static AI V4f nextCCW(const V4f& v) { + return skvx::shuffle<1, 3, 0, 2>(v); +} + +// Replaces zero-length 'bad' edge vectors with the reversed opposite edge vector. +// e3 may be null if only 2D edges need to be corrected for. +static AI void correct_bad_edges(const M4f& bad, V4f* e1, V4f* e2, V4f* e3) { + if (any(bad)) { + // Want opposite edges, L B T R -> R T B L but with flipped sign to preserve winding + *e1 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e1), *e1); + *e2 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e2), *e2); + if (e3) { + *e3 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e3), *e3); + } + } +} + +// Replace 'bad' coordinates by rotating CCW to get the next point. c3 may be null for 2D points. +static AI void correct_bad_coords(const M4f& bad, V4f* c1, V4f* c2, V4f* c3) { + if (any(bad)) { + *c1 = if_then_else(bad, nextCCW(*c1), *c1); + *c2 = if_then_else(bad, nextCCW(*c2), *c2); + if (c3) { + *c3 = if_then_else(bad, nextCCW(*c3), *c3); + } + } +} + +static AI QuadMetadata get_metadata(const Vertices& vertices, GrQuadAAFlags aaFlags) { + V4f dx = nextCCW(vertices.fX) - vertices.fX; + V4f dy = nextCCW(vertices.fY) - vertices.fY; + V4f invLengths = rsqrt(fma(dx, dx, dy * dy)); + + V4f mask = aaFlags == GrQuadAAFlags::kAll ? V4f(1.f) : + V4f{(GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f, + (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f, + (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f, + (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f}; + return { dx * invLengths, dy * invLengths, invLengths, mask }; +} + +static AI Edges get_edge_equations(const QuadMetadata& metadata, const Vertices& vertices) { + V4f dx = metadata.fDX; + V4f dy = metadata.fDY; + // Correct for bad edges by copying adjacent edge information into the bad component + correct_bad_edges(metadata.fInvLengths >= 1.f / kTolerance, &dx, &dy, nullptr); + + V4f c = fma(dx, vertices.fY, -dy * vertices.fX); + // Make sure normals point into the shape + V4f test = fma(dy, nextCW(vertices.fX), fma(-dx, nextCW(vertices.fY), c)); + if (any(test < -kTolerance)) { + return {-dy, dx, -c, true}; + } else { + return {dy, -dx, c, false}; + } +} + +// Sets 'outset' to the magnitude of outset/inset to adjust each corner of a quad given the +// edge angles and lengths. If the quad is too small, has empty edges, or too sharp of angles, +// false is returned and the degenerate slow-path should be used. +static bool get_optimized_outset(const QuadMetadata& metadata, bool rectilinear, V4f* outset) { + if (rectilinear) { + *outset = 0.5f; + // Stay in the fast path as long as all edges are at least a pixel long (so 1/len <= 1) + return all(metadata.fInvLengths <= 1.f); + } + + if (any(metadata.fInvLengths >= 1.f / kTolerance)) { + // Have an empty edge from a degenerate quad, so there's no hope + return false; + } + + // The distance the point needs to move is 1/2sin(theta), where theta is the angle between the + // two edges at that point. cos(theta) is equal to dot(dxy, nextCW(dxy)) + V4f cosTheta = fma(metadata.fDX, nextCW(metadata.fDX), metadata.fDY * nextCW(metadata.fDY)); + // If the angle is too shallow between edges, go through the degenerate path, otherwise adding + // and subtracting very large vectors in almost opposite directions leads to float errors + if (any(abs(cosTheta) >= 0.9f)) { + return false; + } + *outset = 0.5f * rsqrt(1.f - cosTheta * cosTheta); // 1/2sin(theta) + + // When outsetting or insetting, the current edge's AA adds to the length: + // cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta)) + // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta)) + V4f halfTanTheta = -cosTheta * (*outset); // cos(pi - theta) = -cos(theta) + V4f edgeAdjust = metadata.fMask * (halfTanTheta + nextCCW(halfTanTheta)) + + nextCCW(metadata.fMask) * nextCCW(*outset) + + nextCW(metadata.fMask) * (*outset); + // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make edgeLen negative + // then use the slow path + V4f threshold = 0.1f - (1.f / metadata.fInvLengths); + return all(edgeAdjust > threshold) && all(edgeAdjust < -threshold); +} + +// Ignores the quad's fW, use outset_projected_vertices if it's known to need 3D. +static AI void outset_vertices(const V4f& outset, const QuadMetadata& metadata, Vertices* quad) { + // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on" + // both its points need to be moved along their other edge vectors. + auto maskedOutset = -outset * nextCW(metadata.fMask); + auto maskedOutsetCW = outset * metadata.fMask; + // x = x + outset * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff + quad->fX += fma(maskedOutsetCW, nextCW(metadata.fDX), maskedOutset * metadata.fDX); + quad->fY += fma(maskedOutsetCW, nextCW(metadata.fDY), maskedOutset * metadata.fDY); + if (quad->fUVRCount > 0) { + // We want to extend the texture coords by the same proportion as the positions. + maskedOutset *= metadata.fInvLengths; + maskedOutsetCW *= nextCW(metadata.fInvLengths); + V4f du = nextCCW(quad->fU) - quad->fU; + V4f dv = nextCCW(quad->fV) - quad->fV; + quad->fU += fma(maskedOutsetCW, nextCW(du), maskedOutset * du); + quad->fV += fma(maskedOutsetCW, nextCW(dv), maskedOutset * dv); + if (quad->fUVRCount == 3) { + V4f dr = nextCCW(quad->fR) - quad->fR; + quad->fR += fma(maskedOutsetCW, nextCW(dr), maskedOutset * dr); + } + } +} + +// Updates (x,y,w) to be at (x2d,y2d) once projected. Updates (u,v,r) to match if provided. +// Gracefully handles 2D content if *w holds all 1s. +static void outset_projected_vertices(const V4f& x2d, const V4f& y2d, + GrQuadAAFlags aaFlags, Vertices* quad) { + // Left to right, in device space, for each point + V4f e1x = skvx::shuffle<2, 3, 2, 3>(quad->fX) - skvx::shuffle<0, 1, 0, 1>(quad->fX); + V4f e1y = skvx::shuffle<2, 3, 2, 3>(quad->fY) - skvx::shuffle<0, 1, 0, 1>(quad->fY); + V4f e1w = skvx::shuffle<2, 3, 2, 3>(quad->fW) - skvx::shuffle<0, 1, 0, 1>(quad->fW); + correct_bad_edges(fma(e1x, e1x, e1y * e1y) < kTolerance * kTolerance, &e1x, &e1y, &e1w); + + // // Top to bottom, in device space, for each point + V4f e2x = skvx::shuffle<1, 1, 3, 3>(quad->fX) - skvx::shuffle<0, 0, 2, 2>(quad->fX); + V4f e2y = skvx::shuffle<1, 1, 3, 3>(quad->fY) - skvx::shuffle<0, 0, 2, 2>(quad->fY); + V4f e2w = skvx::shuffle<1, 1, 3, 3>(quad->fW) - skvx::shuffle<0, 0, 2, 2>(quad->fW); + correct_bad_edges(fma(e2x, e2x, e2y * e2y) < kTolerance * kTolerance, &e2x, &e2y, &e2w); + + // Can only move along e1 and e2 to reach the new 2D point, so we have + // x2d = (x + a*e1x + b*e2x) / (w + a*e1w + b*e2w) and + // y2d = (y + a*e1y + b*e2y) / (w + a*e1w + b*e2w) for some a, b + // This can be rewritten to a*c1x + b*c2x + c3x = 0; a * c1y + b*c2y + c3y = 0, where + // the cNx and cNy coefficients are: + V4f c1x = e1w * x2d - e1x; + V4f c1y = e1w * y2d - e1y; + V4f c2x = e2w * x2d - e2x; + V4f c2y = e2w * y2d - e2y; + V4f c3x = quad->fW * x2d - quad->fX; + V4f c3y = quad->fW * y2d - quad->fY; + + // Solve for a and b + V4f a, b, denom; + if (aaFlags == GrQuadAAFlags::kAll) { + // When every edge is outset/inset, each corner can use both edge vectors + denom = c1x * c2y - c2x * c1y; + a = (c2x * c3y - c3x * c2y) / denom; + b = (c3x * c1y - c1x * c3y) / denom; + } else { + // Force a or b to be 0 if that edge cannot be used due to non-AA + M4f aMask = M4f{(aaFlags & GrQuadAAFlags::kLeft) ? kTrue : kFalse, + (aaFlags & GrQuadAAFlags::kLeft) ? kTrue : kFalse, + (aaFlags & GrQuadAAFlags::kRight) ? kTrue : kFalse, + (aaFlags & GrQuadAAFlags::kRight) ? kTrue : kFalse}; + M4f bMask = M4f{(aaFlags & GrQuadAAFlags::kTop) ? kTrue : kFalse, + (aaFlags & GrQuadAAFlags::kBottom) ? kTrue : kFalse, + (aaFlags & GrQuadAAFlags::kTop) ? kTrue : kFalse, + (aaFlags & GrQuadAAFlags::kBottom) ? kTrue : kFalse}; + + // When aMask[i]&bMask[i], then a[i], b[i], denom[i] match the kAll case. + // When aMask[i]&!bMask[i], then b[i] = 0, a[i] = -c3x/c1x or -c3y/c1y, using better denom + // When !aMask[i]&bMask[i], then a[i] = 0, b[i] = -c3x/c2x or -c3y/c2y, "" + // When !aMask[i]&!bMask[i], then both a[i] = 0 and b[i] = 0 + M4f useC1x = abs(c1x) > abs(c1y); + M4f useC2x = abs(c2x) > abs(c2y); + + denom = if_then_else(aMask, + if_then_else(bMask, + c1x * c2y - c2x * c1y, /* A & B */ + if_then_else(useC1x, c1x, c1y)), /* A & !B */ + if_then_else(bMask, + if_then_else(useC2x, c2x, c2y), /* !A & B */ + V4f(1.f))); /* !A & !B */ + + a = if_then_else(aMask, + if_then_else(bMask, + c2x * c3y - c3x * c2y, /* A & B */ + if_then_else(useC1x, -c3x, -c3y)), /* A & !B */ + V4f(0.f)) / denom; /* !A */ + b = if_then_else(bMask, + if_then_else(aMask, + c3x * c1y - c1x * c3y, /* A & B */ + if_then_else(useC2x, -c3x, -c3y)), /* !A & B */ + V4f(0.f)) / denom; /* !B */ + } + + V4f newW = quad->fW + a * e1w + b * e2w; + // If newW < 0, scale a and b such that the point reaches the infinity plane instead of crossing + // This breaks orthogonality of inset/outsets, but GPUs don't handle negative Ws well so this + // is far less visually disturbing (likely not noticeable since it's at extreme perspective). + // The alternative correction (multiply xyw by -1) has the disadvantage of changing how local + // coordinates would be interpolated. + static const float kMinW = 1e-6f; + if (any(newW < 0.f)) { + V4f scale = if_then_else(newW < kMinW, (kMinW - quad->fW) / (newW - quad->fW), V4f(1.f)); + a *= scale; + b *= scale; + } + + quad->fX += a * e1x + b * e2x; + quad->fY += a * e1y + b * e2y; + quad->fW += a * e1w + b * e2w; + correct_bad_coords(abs(denom) < kTolerance, &quad->fX, &quad->fY, &quad->fW); + + if (quad->fUVRCount > 0) { + // Calculate R here so it can be corrected with U and V in case it's needed later + V4f e1u = skvx::shuffle<2, 3, 2, 3>(quad->fU) - skvx::shuffle<0, 1, 0, 1>(quad->fU); + V4f e1v = skvx::shuffle<2, 3, 2, 3>(quad->fV) - skvx::shuffle<0, 1, 0, 1>(quad->fV); + V4f e1r = skvx::shuffle<2, 3, 2, 3>(quad->fR) - skvx::shuffle<0, 1, 0, 1>(quad->fR); + correct_bad_edges(fma(e1u, e1u, e1v * e1v) < kTolerance * kTolerance, &e1u, &e1v, &e1r); + + V4f e2u = skvx::shuffle<1, 1, 3, 3>(quad->fU) - skvx::shuffle<0, 0, 2, 2>(quad->fU); + V4f e2v = skvx::shuffle<1, 1, 3, 3>(quad->fV) - skvx::shuffle<0, 0, 2, 2>(quad->fV); + V4f e2r = skvx::shuffle<1, 1, 3, 3>(quad->fR) - skvx::shuffle<0, 0, 2, 2>(quad->fR); + correct_bad_edges(fma(e2u, e2u, e2v * e2v) < kTolerance * kTolerance, &e2u, &e2v, &e2r); + + quad->fU += a * e1u + b * e2u; + quad->fV += a * e1v + b * e2v; + if (quad->fUVRCount == 3) { + quad->fR += a * e1r + b * e2r; + correct_bad_coords(abs(denom) < kTolerance, &quad->fU, &quad->fV, &quad->fR); + } else { + correct_bad_coords(abs(denom) < kTolerance, &quad->fU, &quad->fV, nullptr); + } + } +} + +static V4f degenerate_coverage(const V4f& px, const V4f& py, const Edges& edges) { + // Calculate distance of the 4 inset points (px, py) to the 4 edges + V4f d0 = fma(edges.fA[0], px, fma(edges.fB[0], py, edges.fC[0])); + V4f d1 = fma(edges.fA[1], px, fma(edges.fB[1], py, edges.fC[1])); + V4f d2 = fma(edges.fA[2], px, fma(edges.fB[2], py, edges.fC[2])); + V4f d3 = fma(edges.fA[3], px, fma(edges.fB[3], py, edges.fC[3])); + + // For each point, pretend that there's a rectangle that touches e0 and e3 on the horizontal + // axis, so its width is "approximately" d0 + d3, and it touches e1 and e2 on the vertical axis + // so its height is d1 + d2. Pin each of these dimensions to [0, 1] and approximate the coverage + // at each point as clamp(d0+d3, 0, 1) x clamp(d1+d2, 0, 1). For rectilinear quads this is an + // accurate calculation of its area clipped to an aligned pixel. For arbitrary quads it is not + // mathematically accurate but qualitatively provides a stable value proportional to the size of + // the shape. + V4f w = max(0.f, min(1.f, d0 + d3)); + V4f h = max(0.f, min(1.f, d1 + d2)); + return w * h; +} + +// Outsets or insets xs/ys in place. To be used when the interior is very small, edges are near +// parallel, or edges are very short/zero-length. Returns coverage for each vertex. +// Requires (dx, dy) to already be fixed for empty edges. +static V4f compute_degenerate_quad(GrQuadAAFlags aaFlags, const V4f& mask, const Edges& edges, + bool outset, Vertices* quad) { + // Move the edge 1/2 pixel in or out depending on 'outset'. + V4f oc = edges.fC + mask * (outset ? 0.5f : -0.5f); + + // There are 6 points that we care about to determine the final shape of the polygon, which + // are the intersections between (e0,e2), (e1,e0), (e2,e3), (e3,e1) (corresponding to the + // 4 corners), and (e1, e2), (e0, e3) (representing the intersections of opposite edges). + V4f denom = edges.fA * nextCW(edges.fB) - edges.fB * nextCW(edges.fA); + V4f px = (edges.fB * nextCW(oc) - oc * nextCW(edges.fB)) / denom; + V4f py = (oc * nextCW(edges.fA) - edges.fA * nextCW(oc)) / denom; + correct_bad_coords(abs(denom) < kTolerance, &px, &py, nullptr); + + // Calculate the signed distances from these 4 corners to the other two edges that did not + // define the intersection. So p(0) is compared to e3,e1, p(1) to e3,e2 , p(2) to e0,e1, and + // p(3) to e0,e2 + V4f dists1 = px * skvx::shuffle<3, 3, 0, 0>(edges.fA) + + py * skvx::shuffle<3, 3, 0, 0>(edges.fB) + + skvx::shuffle<3, 3, 0, 0>(oc); + V4f dists2 = px * skvx::shuffle<1, 2, 1, 2>(edges.fA) + + py * skvx::shuffle<1, 2, 1, 2>(edges.fB) + + skvx::shuffle<1, 2, 1, 2>(oc); + + // If all the distances are >= 0, the 4 corners form a valid quadrilateral, so use them as + // the 4 points. If any point is on the wrong side of both edges, the interior has collapsed + // and we need to use a central point to represent it. If all four points are only on the + // wrong side of 1 edge, one edge has crossed over another and we use a line to represent it. + // Otherwise, use a triangle that replaces the bad points with the intersections of + // (e1, e2) or (e0, e3) as needed. + M4f d1v0 = dists1 < kTolerance; + M4f d2v0 = dists2 < kTolerance; + M4f d1And2 = d1v0 & d2v0; + M4f d1Or2 = d1v0 | d2v0; + + V4f coverage; + if (!any(d1Or2)) { + // Every dists1 and dists2 >= kTolerance so it's not degenerate, use all 4 corners as-is + // and use full coverage + coverage = 1.f; + } else if (any(d1And2)) { + // A point failed against two edges, so reduce the shape to a single point, which we take as + // the center of the original quad to ensure it is contained in the intended geometry. Since + // it has collapsed, we know the shape cannot cover a pixel so update the coverage. + SkPoint center = {0.25f * (quad->fX[0] + quad->fX[1] + quad->fX[2] + quad->fX[3]), + 0.25f * (quad->fY[0] + quad->fY[1] + quad->fY[2] + quad->fY[3])}; + px = center.fX; + py = center.fY; + coverage = degenerate_coverage(px, py, edges); + } else if (all(d1Or2)) { + // Degenerates to a line. Compare p[2] and p[3] to edge 0. If they are on the wrong side, + // that means edge 0 and 3 crossed, and otherwise edge 1 and 2 crossed. + if (dists1[2] < kTolerance && dists1[3] < kTolerance) { + // Edges 0 and 3 have crossed over, so make the line from average of (p0,p2) and (p1,p3) + px = 0.5f * (skvx::shuffle<0, 1, 0, 1>(px) + skvx::shuffle<2, 3, 2, 3>(px)); + py = 0.5f * (skvx::shuffle<0, 1, 0, 1>(py) + skvx::shuffle<2, 3, 2, 3>(py)); + } else { + // Edges 1 and 2 have crossed over, so make the line from average of (p0,p1) and (p2,p3) + px = 0.5f * (skvx::shuffle<0, 0, 2, 2>(px) + skvx::shuffle<1, 1, 3, 3>(px)); + py = 0.5f * (skvx::shuffle<0, 0, 2, 2>(py) + skvx::shuffle<1, 1, 3, 3>(py)); + } + coverage = degenerate_coverage(px, py, edges); + } else { + // This turns into a triangle. Replace corners as needed with the intersections between + // (e0,e3) and (e1,e2), which must now be calculated + using V2f = skvx::Vec<2, float>; + V2f eDenom = skvx::shuffle<0, 1>(edges.fA) * skvx::shuffle<3, 2>(edges.fB) - + skvx::shuffle<0, 1>(edges.fB) * skvx::shuffle<3, 2>(edges.fA); + V2f ex = (skvx::shuffle<0, 1>(edges.fB) * skvx::shuffle<3, 2>(oc) - + skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(edges.fB)) / eDenom; + V2f ey = (skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(edges.fA) - + skvx::shuffle<0, 1>(edges.fA) * skvx::shuffle<3, 2>(oc)) / eDenom; + + if (SkScalarAbs(eDenom[0]) > kTolerance) { + px = if_then_else(d1v0, V4f(ex[0]), px); + py = if_then_else(d1v0, V4f(ey[0]), py); + } + if (SkScalarAbs(eDenom[1]) > kTolerance) { + px = if_then_else(d2v0, V4f(ex[1]), px); + py = if_then_else(d2v0, V4f(ey[1]), py); + } + + coverage = 1.f; + } + + outset_projected_vertices(px, py, aaFlags, quad); + return coverage; +} + +// Computes the vertices for the two nested quads used to create AA edges. The original single quad +// should be duplicated as input in 'inner' and 'outer', and the resulting quad frame will be +// stored in-place on return. Returns per-vertex coverage for the inner vertices. +static V4f compute_nested_quad_vertices(GrQuadAAFlags aaFlags, bool rectilinear, + Vertices* inner, Vertices* outer, SkRect* domain) { + SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3); + SkASSERT(outer->fUVRCount == inner->fUVRCount); + + QuadMetadata metadata = get_metadata(*inner, aaFlags); + + // Calculate domain first before updating vertices. It's only used when not rectilinear. + if (!rectilinear) { + SkASSERT(domain); + // The domain is the bounding box of the quad, outset by 0.5. Don't worry about edge masks + // since the FP only applies the domain on the exterior triangles, which are degenerate for + // non-AA edges. + domain->fLeft = min(outer->fX) - 0.5f; + domain->fRight = max(outer->fX) + 0.5f; + domain->fTop = min(outer->fY) - 0.5f; + domain->fBottom = max(outer->fY) + 0.5f; + } + + // When outsetting, we want the new edge to be .5px away from the old line, which means the + // corners may need to be adjusted by more than .5px if the matrix had sheer. This adjustment + // is only computed if there are no empty edges, and it may signal going through the slow path. + V4f outset = 0.5f; + if (get_optimized_outset(metadata, rectilinear, &outset)) { + // Since it's not subpixel, outsetting and insetting are trivial vector additions. + outset_vertices(outset, metadata, outer); + outset_vertices(-outset, metadata, inner); + return 1.f; + } + + // Only compute edge equations once since they are the same for inner and outer quads + Edges edges = get_edge_equations(metadata, *inner); + + // Calculate both outset and inset, returning the coverage reported for the inset, since the + // outset will always have 0.0f. + compute_degenerate_quad(aaFlags, metadata.fMask, edges, true, outer); + return compute_degenerate_quad(aaFlags, metadata.fMask, edges, false, inner); +} + +// Generalizes compute_nested_quad_vertices to extrapolate local coords such that after perspective +// division of the device coordinates, the original local coordinate value is at the original +// un-outset device position. +static V4f compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags, Vertices* inner, + Vertices* outer, SkRect* domain) { + SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3); + SkASSERT(outer->fUVRCount == inner->fUVRCount); + + // Calculate the projected 2D quad and use it to form projeccted inner/outer quads + V4f iw = 1.0f / inner->fW; + V4f x2d = inner->fX * iw; + V4f y2d = inner->fY * iw; + + Vertices inner2D = { x2d, y2d, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 }; // No uvr outsetting in 2D + Vertices outer2D = inner2D; + + V4f coverage = compute_nested_quad_vertices( + aaFlags, /* rect */ false, &inner2D, &outer2D, domain); + + // Now map from the 2D inset/outset back to 3D and update the local coordinates as well + outset_projected_vertices(inner2D.fX, inner2D.fY, aaFlags, inner); + outset_projected_vertices(outer2D.fX, outer2D.fY, aaFlags, outer); + + return coverage; +} + +// Writes four vertices in triangle strip order, including the additional data for local +// coordinates, geometry + texture domains, color, and coverage as needed to satisfy the vertex spec +static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec, + GrQuadPerEdgeAA::CoverageMode mode, const V4f& coverage, SkPMColor4f color4f, + const SkRect& geomDomain, const SkRect& texDomain, const Vertices& quad) { + static constexpr auto If = GrVertexWriter::If; + + for (int i = 0; i < 4; ++i) { + // save position, this is a float2 or float3 or float4 depending on the combination of + // perspective and coverage mode. + vb->write(quad.fX[i], quad.fY[i], + If(spec.deviceQuadType() == GrQuad::Type::kPerspective, quad.fW[i]), + If(mode == GrQuadPerEdgeAA::CoverageMode::kWithPosition, coverage[i])); + + // save color + if (spec.hasVertexColors()) { + bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf; + vb->write(GrVertexColor( + color4f * (mode == GrQuadPerEdgeAA::CoverageMode::kWithColor ? coverage[i] : 1.f), + wide)); + } + + // save local position + if (spec.hasLocalCoords()) { + vb->write(quad.fU[i], quad.fV[i], + If(spec.localQuadType() == GrQuad::Type::kPerspective, quad.fR[i])); + } + + // save the geometry domain + if (spec.requiresGeometryDomain()) { + vb->write(geomDomain); + } + + // save the texture domain + if (spec.hasDomain()) { + vb->write(texDomain); + } + } +} + +GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey); + +static const int kVertsPerAAFillRect = 8; +static const int kIndicesPerAAFillRect = 30; + +static sk_sp get_index_buffer(GrResourceProvider* resourceProvider) { + GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey); + + // clang-format off + static const uint16_t gFillAARectIdx[] = { + 0, 1, 2, 1, 3, 2, + 0, 4, 1, 4, 5, 1, + 0, 6, 4, 0, 2, 6, + 2, 3, 6, 3, 7, 6, + 1, 5, 3, 3, 5, 7, + }; + // clang-format on + + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect); + return resourceProvider->findOrCreatePatternedIndexBuffer( + gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer, + kVertsPerAAFillRect, gAAFillRectIndexBufferKey); +} + +} // anonymous namespace + +namespace GrQuadPerEdgeAA { + +// This is a more elaborate version of SkPMColor4fNeedsWideColor that allows "no color" for white +ColorType MinColorType(SkPMColor4f color, GrClampType clampType, const GrCaps& caps) { + if (color == SK_PMColor4fWHITE) { + return ColorType::kNone; + } else { + return SkPMColor4fNeedsWideColor(color, clampType, caps) ? ColorType::kHalf + : ColorType::kByte; + } +} + +////////////////// Tessellate Implementation + +void* Tessellate(void* vertices, const VertexSpec& spec, const GrQuad& deviceQuad, + const SkPMColor4f& color4f, const GrQuad& localQuad, const SkRect& domain, + GrQuadAAFlags aaFlags) { + SkASSERT(deviceQuad.quadType() <= spec.deviceQuadType()); + SkASSERT(!spec.hasLocalCoords() || localQuad.quadType() <= spec.localQuadType()); + + GrQuadPerEdgeAA::CoverageMode mode = spec.coverageMode(); + + // Load position data into V4fs (always x, y, and load w to avoid branching down the road) + Vertices outer; + outer.fX = deviceQuad.x4f(); + outer.fY = deviceQuad.y4f(); + outer.fW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective + + // Load local position data into V4fs (either none, just u,v or all three) + outer.fUVRCount = spec.localDimensionality(); + if (spec.hasLocalCoords()) { + outer.fU = localQuad.x4f(); + outer.fV = localQuad.y4f(); + outer.fR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective + } + + GrVertexWriter vb{vertices}; + if (spec.usesCoverageAA()) { + SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor); + // Must calculate two new quads, an outset and inset by .5 in projected device space, so + // duplicate the original quad for the inner space + Vertices inner = outer; + + SkRect geomDomain; + V4f maxCoverage = 1.f; + if (spec.deviceQuadType() == GrQuad::Type::kPerspective) { + // For perspective, send quads with all edges non-AA through the tessellation to ensure + // their corners are processed the same as adjacent quads. This approach relies on + // solving edge equations to reconstruct corners, which can create seams if an inner + // fully non-AA quad is not similarly processed. + maxCoverage = compute_nested_persp_quad_vertices(aaFlags, &inner, &outer, &geomDomain); + } else if (aaFlags != GrQuadAAFlags::kNone) { + // In 2D, the simpler corner math does not cause issues with seaming against non-AA + // inner quads. + maxCoverage = compute_nested_quad_vertices( + aaFlags, spec.deviceQuadType() <= GrQuad::Type::kRectilinear, &inner, &outer, + &geomDomain); + } else if (spec.requiresGeometryDomain()) { + // The quad itself wouldn't need a geometric domain, but the batch does, so set the + // domain to the bounds of the X/Y coords. Since it's non-AA, this won't actually be + // evaluated by the shader, but make sure not to upload uninitialized data. + geomDomain.fLeft = min(outer.fX); + geomDomain.fRight = max(outer.fX); + geomDomain.fTop = min(outer.fY); + geomDomain.fBottom = max(outer.fY); + } + + // Write two quads for inner and outer, inner will use the + write_quad(&vb, spec, mode, maxCoverage, color4f, geomDomain, domain, inner); + write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain, outer); + } else { + // No outsetting needed, just write a single quad with full coverage + SkASSERT(mode == CoverageMode::kNone && !spec.requiresGeometryDomain()); + write_quad(&vb, spec, mode, 1.f, color4f, SkRect::MakeEmpty(), domain, outer); + } + + return vb.fPtr; +} + +bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec, + int quadCount) { + if (spec.usesCoverageAA()) { + // AA quads use 8 vertices, basically nested rectangles + sk_sp ibuffer = get_index_buffer(target->resourceProvider()); + if (!ibuffer) { + return false; + } + + mesh->setPrimitiveType(GrPrimitiveType::kTriangles); + mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect, + quadCount, kNumAAQuadsInIndexBuffer); + } else { + // Non-AA quads use 4 vertices, and regular triangle strip layout + if (quadCount > 1) { + sk_sp ibuffer = target->resourceProvider()->refQuadIndexBuffer(); + if (!ibuffer) { + return false; + } + + mesh->setPrimitiveType(GrPrimitiveType::kTriangles); + mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount, + GrResourceProvider::QuadCountOfQuadBuffer()); + } else { + mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip); + mesh->setNonIndexedNonInstanced(4); + } + } + + return true; +} + +////////////////// VertexSpec Implementation + +int VertexSpec::deviceDimensionality() const { + return this->deviceQuadType() == GrQuad::Type::kPerspective ? 3 : 2; +} + +int VertexSpec::localDimensionality() const { + return fHasLocalCoords ? (this->localQuadType() == GrQuad::Type::kPerspective ? 3 : 2) : 0; +} + +CoverageMode VertexSpec::coverageMode() const { + if (this->usesCoverageAA()) { + if (this->compatibleWithCoverageAsAlpha() && this->hasVertexColors() && + !this->requiresGeometryDomain()) { + // Using a geometric domain acts as a second source of coverage and folding + // the original coverage into color makes it impossible to apply the color's + // alpha to the geometric domain's coverage when the original shape is clipped. + return CoverageMode::kWithColor; + } else { + return CoverageMode::kWithPosition; + } + } else { + return CoverageMode::kNone; + } +} + +// This needs to stay in sync w/ QuadPerEdgeAAGeometryProcessor::initializeAttrs +size_t VertexSpec::vertexSize() const { + bool needsPerspective = (this->deviceDimensionality() == 3); + CoverageMode coverageMode = this->coverageMode(); + + size_t count = 0; + + if (coverageMode == CoverageMode::kWithPosition) { + if (needsPerspective) { + count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); + } else { + count += GrVertexAttribTypeSize(kFloat2_GrVertexAttribType) + + GrVertexAttribTypeSize(kFloat_GrVertexAttribType); + } + } else { + if (needsPerspective) { + count += GrVertexAttribTypeSize(kFloat3_GrVertexAttribType); + } else { + count += GrVertexAttribTypeSize(kFloat2_GrVertexAttribType); + } + } + + if (this->requiresGeometryDomain()) { + count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); + } + + count += this->localDimensionality() * GrVertexAttribTypeSize(kFloat_GrVertexAttribType); + + if (ColorType::kByte == this->colorType()) { + count += GrVertexAttribTypeSize(kUByte4_norm_GrVertexAttribType); + } else if (ColorType::kHalf == this->colorType()) { + count += GrVertexAttribTypeSize(kHalf4_GrVertexAttribType); + } + + if (this->hasDomain()) { + count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); + } + + return count; +} + +////////////////// Geometry Processor Implementation + +class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor { +public: + using Saturate = GrTextureOp::Saturate; + + static sk_sp Make(const VertexSpec& spec) { + return sk_sp(new QuadPerEdgeAAGeometryProcessor(spec)); + } + + static sk_sp Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps, + GrTextureType textureType, + const GrSamplerState& samplerState, + const GrSwizzle& swizzle, uint32_t extraSamplerKey, + sk_sp textureColorSpaceXform, + Saturate saturate) { + return sk_sp(new QuadPerEdgeAAGeometryProcessor( + vertexSpec, caps, textureType, samplerState, swizzle, extraSamplerKey, + std::move(textureColorSpaceXform), saturate)); + } + + const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; } + + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { + // texturing, device-dimensions are single bit flags + uint32_t x = (fTexDomain.isInitialized() ? 0 : 0x1) + | (fSampler.isInitialized() ? 0 : 0x2) + | (fNeedsPerspective ? 0 : 0x4) + | (fSaturate == Saturate::kNo ? 0 : 0x8); + // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d + if (fLocalCoord.isInitialized()) { + x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 0x10 : 0x20; + } + // similar for colors, 00 for none, 01 for bytes, 10 for half-floats + if (fColor.isInitialized()) { + x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 0x40 : 0x80; + } + // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for + // position+geomdomain + SkASSERT(!fGeomDomain.isInitialized() || fCoverageMode == CoverageMode::kWithPosition); + if (fCoverageMode != CoverageMode::kNone) { + x |= fGeomDomain.isInitialized() + ? 0x300 + : (CoverageMode::kWithPosition == fCoverageMode ? 0x100 : 0x200); + } + + b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get())); + b->add32(x); + } + + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override { + class GLSLProcessor : public GrGLSLGeometryProcessor { + public: + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, + FPCoordTransformIter&& transformIter) override { + const auto& gp = proc.cast(); + if (gp.fLocalCoord.isInitialized()) { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get()); + } + + private: + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + using Interpolation = GrGLSLVaryingHandler::Interpolation; + + const auto& gp = args.fGP.cast(); + fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler, + gp.fTextureColorSpaceXform.get()); + + args.fVaryingHandler->emitAttributes(gp); + + if (gp.fCoverageMode == CoverageMode::kWithPosition) { + // Strip last channel from the vertex attribute to remove coverage and get the + // actual position + if (gp.fNeedsPerspective) { + args.fVertBuilder->codeAppendf("float3 position = %s.xyz;", + gp.fPosition.name()); + } else { + args.fVertBuilder->codeAppendf("float2 position = %s.xy;", + gp.fPosition.name()); + } + gpArgs->fPositionVar = {"position", + gp.fNeedsPerspective ? kFloat3_GrSLType + : kFloat2_GrSLType, + GrShaderVar::kNone_TypeModifier}; + } else { + // No coverage to eliminate + gpArgs->fPositionVar = gp.fPosition.asShaderVar(); + } + + // Handle local coordinates if they exist + if (gp.fLocalCoord.isInitialized()) { + // NOTE: If the only usage of local coordinates is for the inline texture fetch + // before FPs, then there are no registered FPCoordTransforms and this ends up + // emitting nothing, so there isn't a duplication of local coordinates + this->emitTransforms(args.fVertBuilder, + args.fVaryingHandler, + args.fUniformHandler, + gp.fLocalCoord.asShaderVar(), + args.fFPCoordTransformHandler); + } + + // Solid color before any texturing gets modulated in + if (gp.fColor.isInitialized()) { + SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective); + // The color cannot be flat if the varying coverage has been modulated into it + args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor, + gp.fCoverageMode == CoverageMode::kWithColor ? + Interpolation::kInterpolated : Interpolation::kCanBeFlat); + } else { + // Output color must be initialized to something + args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor); + } + + // If there is a texture, must also handle texture coordinates and reading from + // the texture in the fragment shader before continuing to fragment processors. + if (gp.fSampler.isInitialized()) { + // Texture coordinates clamped by the domain on the fragment shader; if the GP + // has a texture, it's guaranteed to have local coordinates + args.fFragBuilder->codeAppend("float2 texCoord;"); + if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) { + // Can't do a pass through since we need to perform perspective division + GrGLSLVarying v(gp.fLocalCoord.gpuType()); + args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v); + args.fVertBuilder->codeAppendf("%s = %s;", + v.vsOut(), gp.fLocalCoord.name()); + args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;", + v.fsIn(), v.fsIn()); + } else { + args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord"); + } + + // Clamp the now 2D localCoordName variable by the domain if it is provided + if (gp.fTexDomain.isInitialized()) { + args.fFragBuilder->codeAppend("float4 domain;"); + args.fVaryingHandler->addPassThroughAttribute(gp.fTexDomain, "domain", + Interpolation::kCanBeFlat); + args.fFragBuilder->codeAppend( + "texCoord = clamp(texCoord, domain.xy, domain.zw);"); + } + + // Now modulate the starting output color by the texture lookup + args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor); + args.fFragBuilder->appendTextureLookupAndModulate( + args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType, + &fTextureColorSpaceXformHelper); + args.fFragBuilder->codeAppend(";"); + if (gp.fSaturate == Saturate::kYes) { + args.fFragBuilder->codeAppendf("%s = saturate(%s);", + args.fOutputColor, args.fOutputColor); + } + } else { + // Saturate is only intended for use with a proxy to account for the fact + // that GrTextureOp skips SkPaint conversion, which normally handles this. + SkASSERT(gp.fSaturate == Saturate::kNo); + } + + // And lastly, output the coverage calculation code + if (gp.fCoverageMode == CoverageMode::kWithPosition) { + GrGLSLVarying coverage(kFloat_GrSLType); + args.fVaryingHandler->addVarying("coverage", &coverage); + if (gp.fNeedsPerspective) { + // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in + // the fragment shader to get screen-space linear coverage. + args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;", + coverage.vsOut(), gp.fPosition.name(), + gp.fPosition.name()); + args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;", + coverage.fsIn()); + } else { + args.fVertBuilder->codeAppendf("%s = %s;", + coverage.vsOut(), gp.fCoverage.name()); + args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn()); + } + + if (gp.fGeomDomain.isInitialized()) { + // Calculate distance from sk_FragCoord to the 4 edges of the domain + // and clamp them to (0, 1). Use the minimum of these and the original + // coverage. This only has to be done in the exterior triangles, the + // interior of the quad geometry can never be clipped by the domain box. + args.fFragBuilder->codeAppend("float4 geoDomain;"); + args.fVaryingHandler->addPassThroughAttribute(gp.fGeomDomain, "geoDomain", + Interpolation::kCanBeFlat); + args.fFragBuilder->codeAppend( + "if (coverage < 0.5) {" + " float4 dists4 = clamp(float4(1, 1, -1, -1) * " + "(sk_FragCoord.xyxy - geoDomain), 0, 1);" + " float2 dists2 = dists4.xy * dists4.zw;" + " coverage = min(coverage, dists2.x * dists2.y);" + "}"); + } + + args.fFragBuilder->codeAppendf("%s = half4(half(coverage));", + args.fOutputCoverage); + } else { + // Set coverage to 1, since it's either non-AA or the coverage was already + // folded into the output color + SkASSERT(!gp.fGeomDomain.isInitialized()); + args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage); + } + } + GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper; + }; + return new GLSLProcessor; + } + +private: + QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec) + : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID) + , fTextureColorSpaceXform(nullptr) { + SkASSERT(!spec.hasDomain()); + this->initializeAttrs(spec); + this->setTextureSamplerCnt(0); + } + + QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, + const GrShaderCaps& caps, + GrTextureType textureType, + const GrSamplerState& samplerState, + const GrSwizzle& swizzle, + uint32_t extraSamplerKey, + sk_sp textureColorSpaceXform, + Saturate saturate) + : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID) + , fSaturate(saturate) + , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) + , fSampler(textureType, samplerState, swizzle, extraSamplerKey) { + SkASSERT(spec.hasLocalCoords()); + this->initializeAttrs(spec); + this->setTextureSamplerCnt(1); + } + + // This needs to stay in sync w/ VertexSpec::vertexSize + void initializeAttrs(const VertexSpec& spec) { + fNeedsPerspective = spec.deviceDimensionality() == 3; + fCoverageMode = spec.coverageMode(); + + if (fCoverageMode == CoverageMode::kWithPosition) { + if (fNeedsPerspective) { + fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + } else { + fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + fCoverage = {"coverage", kFloat_GrVertexAttribType, kFloat_GrSLType}; + } + } else { + if (fNeedsPerspective) { + fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + } else { + fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + } + } + + // Need a geometry domain when the quads are AA and not rectilinear, since their AA + // outsetting can go beyond a half pixel. + if (spec.requiresGeometryDomain()) { + fGeomDomain = {"geomDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + } + + int localDim = spec.localDimensionality(); + if (localDim == 3) { + fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; + } else if (localDim == 2) { + fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + } // else localDim == 0 and attribute remains uninitialized + + if (ColorType::kByte == spec.colorType()) { + fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType}; + } else if (ColorType::kHalf == spec.colorType()) { + fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType}; + } + + if (spec.hasDomain()) { + fTexDomain = {"texDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; + } + + this->setVertexAttributes(&fPosition, 6); + } + + const TextureSampler& onTextureSampler(int) const override { return fSampler; } + + Attribute fPosition; // May contain coverage as last channel + Attribute fCoverage; // Used for non-perspective position to avoid Intel Metal issues + Attribute fColor; // May have coverage modulated in if the FPs support it + Attribute fLocalCoord; + Attribute fGeomDomain; // Screen-space bounding box on geometry+aa outset + Attribute fTexDomain; // Texture-space bounding box on local coords + + // The positions attribute may have coverage built into it, so float3 is an ambiguous type + // and may mean 2d with coverage, or 3d with no coverage + bool fNeedsPerspective; + // Should saturate() be called on the color? Only relevant when created with a texture. + Saturate fSaturate = Saturate::kNo; + CoverageMode fCoverageMode; + + // Color space will be null and fSampler.isInitialized() returns false when the GP is configured + // to skip texturing. + sk_sp fTextureColorSpaceXform; + TextureSampler fSampler; + + typedef GrGeometryProcessor INHERITED; +}; + +sk_sp MakeProcessor(const VertexSpec& spec) { + return QuadPerEdgeAAGeometryProcessor::Make(spec); +} + +sk_sp MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps, + GrTextureType textureType, + const GrSamplerState& samplerState, + const GrSwizzle& swizzle, uint32_t extraSamplerKey, + sk_sp textureColorSpaceXform, + Saturate saturate) { + return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, samplerState, swizzle, + extraSamplerKey, std::move(textureColorSpaceXform), + saturate); +} + +} // namespace GrQuadPerEdgeAA diff --git a/gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.h b/gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.h new file mode 100644 index 0000000000..06bb315efc --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrQuadPerEdgeAA.h @@ -0,0 +1,121 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrQuadPerEdgeAA_DEFINED +#define GrQuadPerEdgeAA_DEFINED + +#include "include/core/SkPoint.h" +#include "include/core/SkPoint3.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrTextureOp.h" + +class GrCaps; +class GrColorSpaceXform; +class GrShaderCaps; + +namespace GrQuadPerEdgeAA { + using Saturate = GrTextureOp::Saturate; + + enum class CoverageMode { kNone, kWithPosition, kWithColor }; + enum class Domain : bool { kNo = false, kYes = true }; + enum class ColorType { kNone, kByte, kHalf, kLast = kHalf }; + static const int kColorTypeCount = static_cast(ColorType::kLast) + 1; + + // Gets the minimum ColorType that can represent a color. + ColorType MinColorType(SkPMColor4f, GrClampType, const GrCaps&); + + // Specifies the vertex configuration for an op that renders per-edge AA quads. The vertex + // order (when enabled) is device position, color, local position, domain, aa edge equations. + // This order matches the constructor argument order of VertexSpec and is the order that + // GPAttributes maintains. If hasLocalCoords is false, then the local quad type can be ignored. + struct VertexSpec { + public: + VertexSpec(GrQuad::Type deviceQuadType, ColorType colorType, GrQuad::Type localQuadType, + bool hasLocalCoords, Domain domain, GrAAType aa, bool coverageAsAlpha) + : fDeviceQuadType(static_cast(deviceQuadType)) + , fLocalQuadType(static_cast(localQuadType)) + , fHasLocalCoords(hasLocalCoords) + , fColorType(static_cast(colorType)) + , fHasDomain(static_cast(domain)) + , fUsesCoverageAA(aa == GrAAType::kCoverage) + , fCompatibleWithCoverageAsAlpha(coverageAsAlpha) + , fRequiresGeometryDomain(aa == GrAAType::kCoverage && + deviceQuadType > GrQuad::Type::kRectilinear) { } + + GrQuad::Type deviceQuadType() const { return static_cast(fDeviceQuadType); } + GrQuad::Type localQuadType() const { return static_cast(fLocalQuadType); } + bool hasLocalCoords() const { return fHasLocalCoords; } + ColorType colorType() const { return static_cast(fColorType); } + bool hasVertexColors() const { return ColorType::kNone != this->colorType(); } + bool hasDomain() const { return fHasDomain; } + bool usesCoverageAA() const { return fUsesCoverageAA; } + bool compatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; } + bool requiresGeometryDomain() const { return fRequiresGeometryDomain; } + // Will always be 2 or 3 + int deviceDimensionality() const; + // Will always be 0 if hasLocalCoords is false, otherwise will be 2 or 3 + int localDimensionality() const; + + int verticesPerQuad() const { return fUsesCoverageAA ? 8 : 4; } + + CoverageMode coverageMode() const; + size_t vertexSize() const; + + private: + static_assert(GrQuad::kTypeCount <= 4, "GrQuad::Type doesn't fit in 2 bits"); + static_assert(kColorTypeCount <= 4, "Color doesn't fit in 2 bits"); + + unsigned fDeviceQuadType: 2; + unsigned fLocalQuadType: 2; + unsigned fHasLocalCoords: 1; + unsigned fColorType : 2; + unsigned fHasDomain: 1; + unsigned fUsesCoverageAA: 1; + unsigned fCompatibleWithCoverageAsAlpha: 1; + // The geometry domain serves to clip off pixels touched by quads with sharp corners that + // would otherwise exceed the miter limit for the AA-outset geometry. + unsigned fRequiresGeometryDomain: 1; + }; + + sk_sp MakeProcessor(const VertexSpec& spec); + + sk_sp MakeTexturedProcessor( + const VertexSpec& spec, const GrShaderCaps& caps, GrTextureType textureType, + const GrSamplerState& samplerState, const GrSwizzle& swizzle, uint32_t extraSamplerKey, + sk_sp textureColorSpaceXform, Saturate saturate); + + // Fill vertices with the vertex data needed to represent the given quad. The device position, + // local coords, vertex color, domain, and edge coefficients will be written and/or computed + // based on the configuration in the vertex spec; if that attribute is disabled in the spec, + // then its corresponding function argument is ignored. + // + // Tessellation is based on the quad type of the vertex spec, not the provided GrQuad's + // so that all quads in a batch are tessellated the same. + // + // Returns the advanced pointer in vertices. + void* Tessellate(void* vertices, const VertexSpec& spec, const GrQuad& deviceQuad, + const SkPMColor4f& color, const GrQuad& localQuad, const SkRect& domain, + GrQuadAAFlags aa); + + // The mesh will have its index data configured to meet the expectations of the Tessellate() + // function, but it the calling code must handle filling a vertex buffer via Tessellate() and + // then assigning it to the returned mesh. + // + // Returns false if the index data could not be allocated. + bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec, + int quadCount); + + static constexpr int kNumAAQuadsInIndexBuffer = 512; + +} // namespace GrQuadPerEdgeAA + +#endif // GrQuadPerEdgeAA_DEFINED diff --git a/gfx/skia/skia/src/gpu/ops/GrRegionOp.cpp b/gfx/skia/skia/src/gpu/ops/GrRegionOp.cpp new file mode 100644 index 0000000000..6d48a339fa --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrRegionOp.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrRegionOp.h" + +#include "include/core/SkRegion.h" +#include "src/core/SkMatrixPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +static const int kVertsPerInstance = 4; +static const int kIndicesPerInstance = 6; + +static sk_sp make_gp(const GrShaderCaps* shaderCaps, + const SkMatrix& viewMatrix, + bool wideColor) { + using namespace GrDefaultGeoProcFactory; + Color::Type colorType = + wideColor ? Color::kPremulWideColorAttribute_Type : Color::kPremulGrColorAttribute_Type; + return GrDefaultGeoProcFactory::Make(shaderCaps, colorType, Coverage::kSolid_Type, + LocalCoords::kUsePosition_Type, viewMatrix); +} + +namespace { + +class RegionOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRegion& region, + GrAAType aaType, + const GrUserStencilSettings* stencilSettings = nullptr) { + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, region, + aaType, stencilSettings); + } + + RegionOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const SkRegion& region, GrAAType aaType, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()) + , fHelper(helperArgs, aaType, stencilSettings) + , fViewMatrix(viewMatrix) { + RegionInfo& info = fRegions.push_back(); + info.fColor = color; + info.fRegion = region; + + SkRect bounds = SkRect::Make(region.getBounds()); + this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kNo, IsHairline::kNo); + } + + const char* name() const override { return "GrRegionOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + str.appendf("# combined: %d\n", fRegions.count()); + for (int i = 0; i < fRegions.count(); ++i) { + const RegionInfo& info = fRegions[i]; + str.appendf("%d: Color: 0x%08x, Region with %d rects\n", i, info.fColor.toBytes_RGBA(), + info.fRegion.computeRegionComplexity()); + } + str += fHelper.dumpInfo(); + str += INHERITED::dumpInfo(); + return str; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, GrProcessorAnalysisCoverage::kNone, + &fRegions[0].fColor, &fWideColor); + } + +private: + void onPrepareDraws(Target* target) override { + sk_sp gp = make_gp(target->caps().shaderCaps(), fViewMatrix, + fWideColor); + if (!gp) { + SkDebugf("Couldn't create GrGeometryProcessor\n"); + return; + } + + int numRegions = fRegions.count(); + int numRects = 0; + for (int i = 0; i < numRegions; i++) { + numRects += fRegions[i].fRegion.computeRegionComplexity(); + } + + if (!numRects) { + return; + } + sk_sp indexBuffer = target->resourceProvider()->refQuadIndexBuffer(); + if (!indexBuffer) { + SkDebugf("Could not allocate indices\n"); + return; + } + PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(), + std::move(indexBuffer), kVertsPerInstance, kIndicesPerInstance, + numRects); + GrVertexWriter vertices{helper.vertices()}; + if (!vertices.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + for (int i = 0; i < numRegions; i++) { + GrVertexColor color(fRegions[i].fColor, fWideColor); + SkRegion::Iterator iter(fRegions[i].fRegion); + while (!iter.done()) { + SkRect rect = SkRect::Make(iter.rect()); + vertices.writeQuad(GrVertexWriter::TriStripFromRect(rect), color); + iter.next(); + } + } + helper.recordDraw(target, std::move(gp)); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + RegionOp* that = t->cast(); + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (fViewMatrix != that->fViewMatrix) { + return CombineResult::kCannotCombine; + } + + fRegions.push_back_n(that->fRegions.count(), that->fRegions.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + struct RegionInfo { + SkPMColor4f fColor; + SkRegion fRegion; + }; + + Helper fHelper; + SkMatrix fViewMatrix; + SkSTArray<1, RegionInfo, true> fRegions; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +namespace GrRegionOp { + +std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRegion& region, + GrAAType aaType, + const GrUserStencilSettings* stencilSettings) { + if (aaType != GrAAType::kNone && aaType != GrAAType::kMSAA) { + return nullptr; + } + return RegionOp::Make(context, std::move(paint), viewMatrix, region, aaType, stencilSettings); +} +} + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(RegionOp) { + SkRegion region; + int n = random->nextULessThan(200); + for (int i = 0; i < n; ++i) { + SkIPoint center; + center.fX = random->nextULessThan(1000); + center.fY = random->nextULessThan(1000); + int w = random->nextRangeU(10, 1000); + int h = random->nextRangeU(10, 1000); + SkIRect rect = {center.fX - w / 2, center.fY - h / 2, center.fX + w / 2, center.fY + h / 2}; + SkRegion::Op op; + if (i == 0) { + op = SkRegion::kReplace_Op; + } else { + // Pick an other than replace. + GR_STATIC_ASSERT(SkRegion::kLastOp == SkRegion::kReplace_Op); + op = (SkRegion::Op)random->nextULessThan(SkRegion::kLastOp); + } + region.op(rect, op); + } + SkMatrix viewMatrix = GrTest::TestMatrix(random); + GrAAType aaType = GrAAType::kNone; + if (numSamples > 1 && random->nextBool()) { + aaType = GrAAType::kMSAA; + } + return RegionOp::Make(context, std::move(paint), viewMatrix, region, aaType, + GrGetRandomStencil(random, context)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrRegionOp.h b/gfx/skia/skia/src/gpu/ops/GrRegionOp.h new file mode 100644 index 0000000000..d7dc17c313 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrRegionOp.h @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrRegionOp_DEFINED +#define GrRegionOp_DEFINED + +#include "include/private/GrTypesPriv.h" + +class GrDrawOp; +class GrRecordingContext; +class SkMatrix; +class SkRegion; +class GrPaint; +struct GrUserStencilSettings; + +namespace GrRegionOp { +/** GrAAType must be kNone or kMSAA. */ +std::unique_ptr Make(GrRecordingContext*, + GrPaint&&, + const SkMatrix& viewMatrix, + const SkRegion&, + GrAAType, + const GrUserStencilSettings* stencilSettings = nullptr); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.cpp b/gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.cpp new file mode 100644 index 0000000000..546933bb8a --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.cpp @@ -0,0 +1,692 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrShadowRRectOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/core/SkRRectPriv.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/effects/GrShadowGeoProc.h" + +/////////////////////////////////////////////////////////////////////////////// +// Circle Data +// +// We have two possible cases for geometry for a circle: + +// In the case of a normal fill, we draw geometry for the circle as an octagon. +static const uint16_t gFillCircleIndices[] = { + // enter the octagon + // clang-format off + 0, 1, 8, 1, 2, 8, + 2, 3, 8, 3, 4, 8, + 4, 5, 8, 5, 6, 8, + 6, 7, 8, 7, 0, 8, + // clang-format on +}; + +// For stroked circles, we use two nested octagons. +static const uint16_t gStrokeCircleIndices[] = { + // enter the octagon + // clang-format off + 0, 1, 9, 0, 9, 8, + 1, 2, 10, 1, 10, 9, + 2, 3, 11, 2, 11, 10, + 3, 4, 12, 3, 12, 11, + 4, 5, 13, 4, 13, 12, + 5, 6, 14, 5, 14, 13, + 6, 7, 15, 6, 15, 14, + 7, 0, 8, 7, 8, 15, + // clang-format on +}; + +static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices); +static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices); +static const int kVertsPerStrokeCircle = 16; +static const int kVertsPerFillCircle = 9; + +static int circle_type_to_vert_count(bool stroked) { + return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle; +} + +static int circle_type_to_index_count(bool stroked) { + return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle; +} + +static const uint16_t* circle_type_to_indices(bool stroked) { + return stroked ? gStrokeCircleIndices : gFillCircleIndices; +} + +/////////////////////////////////////////////////////////////////////////////// +// RoundRect Data +// +// The geometry for a shadow roundrect is similar to a 9-patch: +// ____________ +// |_|________|_| +// | | | | +// | | | | +// | | | | +// |_|________|_| +// |_|________|_| +// +// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram +// shows the upper part of the upper left corner. The bottom triangle would similarly be split +// into two triangles.) +// ________ +// |\ \ | +// | \ \ | +// | \\ | +// | \| +// -------- +// +// The center of the fan handles the curve of the corner. For roundrects where the stroke width +// is greater than the corner radius, the outer triangles blend from the curve to the straight +// sides. Otherwise these triangles will be degenerate. +// +// In the case where the stroke width is greater than the corner radius and the +// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center. +// This rectangle extends the coverage values of the center edges of the 9-patch. +// ____________ +// |_|________|_| +// | |\ ____ /| | +// | | | | | | +// | | |____| | | +// |_|/______\|_| +// |_|________|_| +// +// For filled rrects we reuse the stroke geometry but add an additional quad to the center. + +static const uint16_t gRRectIndices[] = { + // clang-format off + // overstroke quads + // we place this at the beginning so that we can skip these indices when rendering as filled + 0, 6, 25, 0, 25, 24, + 6, 18, 27, 6, 27, 25, + 18, 12, 26, 18, 26, 27, + 12, 0, 24, 12, 24, 26, + + // corners + 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, + 6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7, + 12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13, + 18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23, + + // edges + 0, 5, 11, 0, 11, 6, + 6, 7, 19, 6, 19, 18, + 18, 23, 17, 18, 17, 12, + 12, 13, 1, 12, 1, 0, + + // fill quad + // we place this at the end so that we can skip these indices when rendering as stroked + 0, 6, 18, 0, 18, 12, + // clang-format on +}; + +// overstroke count +static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6; +// simple stroke count skips overstroke indices +static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6*4; +// fill count adds final quad to stroke count +static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6; +static const int kVertsPerStrokeRRect = 24; +static const int kVertsPerOverstrokeRRect = 28; +static const int kVertsPerFillRRect = 24; + +enum RRectType { + kFill_RRectType, + kStroke_RRectType, + kOverstroke_RRectType, +}; + +static int rrect_type_to_vert_count(RRectType type) { + switch (type) { + case kFill_RRectType: + return kVertsPerFillRRect; + case kStroke_RRectType: + return kVertsPerStrokeRRect; + case kOverstroke_RRectType: + return kVertsPerOverstrokeRRect; + } + SK_ABORT("Invalid type"); +} + +static int rrect_type_to_index_count(RRectType type) { + switch (type) { + case kFill_RRectType: + return kIndicesPerFillRRect; + case kStroke_RRectType: + return kIndicesPerStrokeRRect; + case kOverstroke_RRectType: + return kIndicesPerOverstrokeRRect; + } + SK_ABORT("Invalid type"); +} + +static const uint16_t* rrect_type_to_indices(RRectType type) { + switch (type) { + case kFill_RRectType: + case kStroke_RRectType: + return gRRectIndices + 6*4; + case kOverstroke_RRectType: + return gRRectIndices; + } + SK_ABORT("Invalid type"); +} + +/////////////////////////////////////////////////////////////////////////////// +namespace { + +class ShadowCircularRRectOp final : public GrMeshDrawOp { +public: + DEFINE_OP_CLASS_ID + + // An insetWidth > 1/2 rect width or height indicates a simple fill. + ShadowCircularRRectOp(GrColor color, const SkRect& devRect, + float devRadius, bool isCircle, float blurRadius, float insetWidth) + : INHERITED(ClassID()) { + SkRect bounds = devRect; + SkASSERT(insetWidth > 0); + SkScalar innerRadius = 0.0f; + SkScalar outerRadius = devRadius; + SkScalar umbraInset; + + RRectType type = kFill_RRectType; + if (isCircle) { + umbraInset = 0; + } else { + umbraInset = SkTMax(outerRadius, blurRadius); + } + + // If stroke is greater than width or height, this is still a fill, + // otherwise we compute stroke params. + if (isCircle) { + innerRadius = devRadius - insetWidth; + type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType; + } else { + if (insetWidth <= 0.5f*SkTMin(devRect.width(), devRect.height())) { + // We don't worry about a real inner radius, we just need to know if we + // need to create overstroke vertices. + innerRadius = SkTMax(insetWidth - umbraInset, 0.0f); + type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType; + } + } + + this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo); + + fGeoData.emplace_back(Geometry{color, outerRadius, umbraInset, innerRadius, + blurRadius, bounds, type, isCircle}); + if (isCircle) { + fVertCount = circle_type_to_vert_count(kStroke_RRectType == type); + fIndexCount = circle_type_to_index_count(kStroke_RRectType == type); + } else { + fVertCount = rrect_type_to_vert_count(type); + fIndexCount = rrect_type_to_index_count(type); + } + } + + const char* name() const override { return "ShadowCircularRRectOp"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (int i = 0; i < fGeoData.count(); ++i) { + string.appendf( + "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," + "OuterRad: %.2f, Umbra: %.2f, InnerRad: %.2f, BlurRad: %.2f\n", + fGeoData[i].fColor, fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop, + fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom, + fGeoData[i].fOuterRadius, fGeoData[i].fUmbraInset, + fGeoData[i].fInnerRadius, fGeoData[i].fBlurRadius); + } + string.append(INHERITED::dumpInfo()); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } + + GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, + bool hasMixedSampledCoverage, GrClampType) override { + return GrProcessorSet::EmptySetAnalysis(); + } + +private: + struct Geometry { + GrColor fColor; + SkScalar fOuterRadius; + SkScalar fUmbraInset; + SkScalar fInnerRadius; + SkScalar fBlurRadius; + SkRect fDevBounds; + RRectType fType; + bool fIsCircle; + }; + + struct CircleVertex { + SkPoint fPos; + GrColor fColor; + SkPoint fOffset; + SkScalar fDistanceCorrection; + }; + + void fillInCircleVerts(const Geometry& args, bool isStroked, CircleVertex** verts) const { + + GrColor color = args.fColor; + SkScalar outerRadius = args.fOuterRadius; + SkScalar innerRadius = args.fInnerRadius; + SkScalar blurRadius = args.fBlurRadius; + SkScalar distanceCorrection = outerRadius / blurRadius; + + const SkRect& bounds = args.fDevBounds; + + // The inner radius in the vertex data must be specified in normalized space. + innerRadius = innerRadius / outerRadius; + + SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY()); + SkScalar halfWidth = 0.5f * bounds.width(); + SkScalar octOffset = 0.41421356237f; // sqrt(2) - 1 + + (*verts)->fPos = center + SkPoint::Make(-octOffset * halfWidth, -halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-octOffset, -1); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(octOffset * halfWidth, -halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(octOffset, -1); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(halfWidth, -octOffset * halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(1, -octOffset); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(halfWidth, octOffset * halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(1, octOffset); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(octOffset * halfWidth, halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(octOffset, 1); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(-octOffset * halfWidth, halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-octOffset, 1); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(-halfWidth, octOffset * halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-1, octOffset); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(-halfWidth, -octOffset * halfWidth); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-1, -octOffset); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + if (isStroked) { + // compute the inner ring + + // cosine and sine of pi/8 + SkScalar c = 0.923579533f; + SkScalar s = 0.382683432f; + SkScalar r = args.fInnerRadius; + + (*verts)->fPos = center + SkPoint::Make(-s * r, -c * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-s * innerRadius, -c * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(s * r, -c * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(s * innerRadius, -c * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(c * r, -s * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(c * innerRadius, -s * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(c * r, s * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(c * innerRadius, s * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(s * r, c * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(s * innerRadius, c * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(-s * r, c * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-s * innerRadius, c * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(-c * r, s * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-c * innerRadius, s * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = center + SkPoint::Make(-c * r, -s * r); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(-c * innerRadius, -s * innerRadius); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + } else { + // filled + (*verts)->fPos = center; + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + } + } + + void fillInRRectVerts(const Geometry& args, CircleVertex** verts) const { + GrColor color = args.fColor; + SkScalar outerRadius = args.fOuterRadius; + + const SkRect& bounds = args.fDevBounds; + + SkScalar umbraInset = args.fUmbraInset; + SkScalar minDim = 0.5f*SkTMin(bounds.width(), bounds.height()); + if (umbraInset > minDim) { + umbraInset = minDim; + } + + SkScalar xInner[4] = { bounds.fLeft + umbraInset, bounds.fRight - umbraInset, + bounds.fLeft + umbraInset, bounds.fRight - umbraInset }; + SkScalar xMid[4] = { bounds.fLeft + outerRadius, bounds.fRight - outerRadius, + bounds.fLeft + outerRadius, bounds.fRight - outerRadius }; + SkScalar xOuter[4] = { bounds.fLeft, bounds.fRight, + bounds.fLeft, bounds.fRight }; + SkScalar yInner[4] = { bounds.fTop + umbraInset, bounds.fTop + umbraInset, + bounds.fBottom - umbraInset, bounds.fBottom - umbraInset }; + SkScalar yMid[4] = { bounds.fTop + outerRadius, bounds.fTop + outerRadius, + bounds.fBottom - outerRadius, bounds.fBottom - outerRadius }; + SkScalar yOuter[4] = { bounds.fTop, bounds.fTop, + bounds.fBottom, bounds.fBottom }; + + SkScalar blurRadius = args.fBlurRadius; + + // In the case where we have to inset more for the umbra, our two triangles in the + // corner get skewed to a diamond rather than a square. To correct for that, + // we also skew the vectors we send to the shader that help define the circle. + // By doing so, we end up with a quarter circle in the corner rather than the + // elliptical curve. + + // This is a bit magical, but it gives us the correct results at extrema: + // a) umbraInset == outerRadius produces an orthogonal vector + // b) outerRadius == 0 produces a diagonal vector + // And visually the corner looks correct. + SkVector outerVec = SkVector::Make(outerRadius - umbraInset, -outerRadius - umbraInset); + outerVec.normalize(); + // We want the circle edge to fall fractionally along the diagonal at + // (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset + // + // Setting the components of the diagonal offset to the following value will give us that. + SkScalar diagVal = umbraInset / (SK_ScalarSqrt2*(outerRadius - umbraInset) - outerRadius); + SkVector diagVec = SkVector::Make(diagVal, diagVal); + SkScalar distanceCorrection = umbraInset / blurRadius; + + // build corner by corner + for (int i = 0; i < 4; ++i) { + // inner point + (*verts)->fPos = SkPoint::Make(xInner[i], yInner[i]); + (*verts)->fColor = color; + (*verts)->fOffset = SkVector::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + // outer points + (*verts)->fPos = SkPoint::Make(xOuter[i], yInner[i]); + (*verts)->fColor = color; + (*verts)->fOffset = SkVector::Make(0, -1); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = SkPoint::Make(xOuter[i], yMid[i]); + (*verts)->fColor = color; + (*verts)->fOffset = outerVec; + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = SkPoint::Make(xOuter[i], yOuter[i]); + (*verts)->fColor = color; + (*verts)->fOffset = diagVec; + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = SkPoint::Make(xMid[i], yOuter[i]); + (*verts)->fColor = color; + (*verts)->fOffset = outerVec; + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + (*verts)->fPos = SkPoint::Make(xInner[i], yOuter[i]); + (*verts)->fColor = color; + (*verts)->fOffset = SkVector::Make(0, -1); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + } + + // Add the additional vertices for overstroked rrects. + // Effectively this is an additional stroked rrect, with its + // parameters equal to those in the center of the 9-patch. This will + // give constant values across this inner ring. + if (kOverstroke_RRectType == args.fType) { + SkASSERT(args.fInnerRadius > 0.0f); + + SkScalar inset = umbraInset + args.fInnerRadius; + + // TL + (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fTop + inset); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + // TR + (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fTop + inset); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + // BL + (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fBottom - inset); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + + // BR + (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fBottom - inset); + (*verts)->fColor = color; + (*verts)->fOffset = SkPoint::Make(0, 0); + (*verts)->fDistanceCorrection = distanceCorrection; + (*verts)++; + } + + } + + void onPrepareDraws(Target* target) override { + // Setup geometry processor + sk_sp gp = GrRRectShadowGeoProc::Make(); + + int instanceCount = fGeoData.count(); + SkASSERT(sizeof(CircleVertex) == gp->vertexStride()); + + sk_sp vertexBuffer; + int firstVertex; + CircleVertex* verts = (CircleVertex*)target->makeVertexSpace( + sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex); + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + sk_sp indexBuffer; + int firstIndex = 0; + uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); + if (!indices) { + SkDebugf("Could not allocate indices\n"); + return; + } + + int currStartVertex = 0; + for (int i = 0; i < instanceCount; i++) { + const Geometry& args = fGeoData[i]; + + if (args.fIsCircle) { + bool isStroked = SkToBool(kStroke_RRectType == args.fType); + this->fillInCircleVerts(args, isStroked, &verts); + + const uint16_t* primIndices = circle_type_to_indices(isStroked); + const int primIndexCount = circle_type_to_index_count(isStroked); + for (int i = 0; i < primIndexCount; ++i) { + *indices++ = primIndices[i] + currStartVertex; + } + + currStartVertex += circle_type_to_vert_count(isStroked); + + } else { + this->fillInRRectVerts(args, &verts); + + const uint16_t* primIndices = rrect_type_to_indices(args.fType); + const int primIndexCount = rrect_type_to_index_count(args.fType); + for (int i = 0; i < primIndexCount; ++i) { + *indices++ = primIndices[i] + currStartVertex; + } + + currStartVertex += rrect_type_to_vert_count(args.fType); + } + } + + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, + GrPrimitiveRestart::kNo); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + flushState->executeDrawsAndUploadsForMeshDrawOp( + this, chainBounds, GrProcessorSet::MakeEmptySet()); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + ShadowCircularRRectOp* that = t->cast(); + fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); + fVertCount += that->fVertCount; + fIndexCount += that->fIndexCount; + return CombineResult::kMerged; + } + + SkSTArray<1, Geometry, true> fGeoData; + int fVertCount; + int fIndexCount; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +/////////////////////////////////////////////////////////////////////////////// + +namespace GrShadowRRectOp { +std::unique_ptr Make(GrRecordingContext* context, + GrColor color, + const SkMatrix& viewMatrix, + const SkRRect& rrect, + SkScalar blurWidth, + SkScalar insetWidth) { + // Shadow rrect ops only handle simple circular rrects. + SkASSERT(viewMatrix.isSimilarity() && SkRRectPriv::EqualRadii(rrect)); + + // Do any matrix crunching before we reset the draw state for device coords. + const SkRect& rrectBounds = rrect.getBounds(); + SkRect bounds; + viewMatrix.mapRect(&bounds, rrectBounds); + + // Map radius and inset. As the matrix is a similarity matrix, this should be isotropic. + SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX; + SkScalar matrixFactor = viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewX]; + SkScalar scaledRadius = SkScalarAbs(radius*matrixFactor); + SkScalar scaledInsetWidth = SkScalarAbs(insetWidth*matrixFactor); + + if (scaledInsetWidth <= 0) { + return nullptr; + } + + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(color, bounds, + scaledRadius, + rrect.isOval(), + blurWidth, + scaledInsetWidth); +} +} + +/////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(ShadowRRectOp) { + // create a similarity matrix + SkScalar rotate = random->nextSScalar1() * 360.f; + SkScalar translateX = random->nextSScalar1() * 1000.f; + SkScalar translateY = random->nextSScalar1() * 1000.f; + SkScalar scale; + do { + scale = random->nextSScalar1() * 100.f; + } while (scale == 0); + SkMatrix viewMatrix; + viewMatrix.setRotate(rotate); + viewMatrix.postTranslate(translateX, translateY); + viewMatrix.postScale(scale, scale); + SkScalar insetWidth = random->nextSScalar1() * 72.f; + SkScalar blurWidth = random->nextSScalar1() * 72.f; + bool isCircle = random->nextBool(); + // This op doesn't use a full GrPaint, just a color. + GrColor color = paint.getColor4f().toBytes_RGBA(); + if (isCircle) { + SkRect circle = GrTest::TestSquare(random); + SkRRect rrect = SkRRect::MakeOval(circle); + return GrShadowRRectOp::Make(context, color, viewMatrix, rrect, blurWidth, insetWidth); + } else { + SkRRect rrect; + do { + // This may return a rrect with elliptical corners, which we don't support. + rrect = GrTest::TestRRectSimple(random); + } while (!SkRRectPriv::IsSimpleCircular(rrect)); + return GrShadowRRectOp::Make(context, color, viewMatrix, rrect, blurWidth, insetWidth); + } +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.h b/gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.h new file mode 100644 index 0000000000..c4d313dd69 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrShadowRRectOp.h @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrShadowRRectOp_DEFINED +#define GrShadowRRectOp_DEFINED + +#include +#include "src/gpu/GrColor.h" + +class GrDrawOp; +class GrRecordingContext; + +class SkMatrix; +class SkRRect; + +namespace GrShadowRRectOp { + +std::unique_ptr Make(GrRecordingContext*, + GrColor, + const SkMatrix& viewMatrix, + const SkRRect&, + SkScalar blurWidth, + SkScalar insetWidth); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp b/gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp new file mode 100644 index 0000000000..3c10b8bfd4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrProcessorSet.h" +#include "src/gpu/GrUserStencilSettings.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/geometry/GrRect.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +GrSimpleMeshDrawOpHelper::GrSimpleMeshDrawOpHelper(const MakeArgs& args, GrAAType aaType, + InputFlags inputFlags) + : fProcessors(args.fProcessorSet) + , fPipelineFlags((GrPipeline::InputFlags)inputFlags) + , fAAType((int)aaType) + , fUsesLocalCoords(false) + , fCompatibleWithCoverageAsAlpha(false) { + SkDEBUGCODE(fDidAnalysis = false); + SkDEBUGCODE(fMadePipeline = false); + if (GrAATypeIsHW(aaType)) { + fPipelineFlags |= GrPipeline::InputFlags::kHWAntialias; + } +} + +GrSimpleMeshDrawOpHelper::~GrSimpleMeshDrawOpHelper() { + if (fProcessors) { + fProcessors->~GrProcessorSet(); + } +} + +GrDrawOp::FixedFunctionFlags GrSimpleMeshDrawOpHelper::fixedFunctionFlags() const { + return GrAATypeIsHW((this->aaType())) ? GrDrawOp::FixedFunctionFlags::kUsesHWAA + : GrDrawOp::FixedFunctionFlags::kNone; +} + +static bool none_as_coverage_aa_compatible(GrAAType aa1, GrAAType aa2) { + return (aa1 == GrAAType::kNone && aa2 == GrAAType::kCoverage) || + (aa1 == GrAAType::kCoverage && aa2 == GrAAType::kNone); +} + +bool GrSimpleMeshDrawOpHelper::isCompatible(const GrSimpleMeshDrawOpHelper& that, + const GrCaps& caps, const SkRect& thisBounds, + const SkRect& thatBounds, bool noneAsCoverageAA) const { + if (SkToBool(fProcessors) != SkToBool(that.fProcessors)) { + return false; + } + if (fProcessors) { + if (*fProcessors != *that.fProcessors) { + return false; + } + } + bool result = fPipelineFlags == that.fPipelineFlags && (fAAType == that.fAAType || + (noneAsCoverageAA && none_as_coverage_aa_compatible(this->aaType(), that.aaType()))); + SkASSERT(!result || fCompatibleWithCoverageAsAlpha == that.fCompatibleWithCoverageAsAlpha); + SkASSERT(!result || fUsesLocalCoords == that.fUsesLocalCoords); + return result; +} + +GrProcessorSet::Analysis GrSimpleMeshDrawOpHelper::finalizeProcessors( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage, + SkPMColor4f* geometryColor, bool* wideColor) { + GrProcessorAnalysisColor color = *geometryColor; + auto result = this->finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, geometryCoverage, &color); + color.isConstant(geometryColor); + if (wideColor) { + *wideColor = SkPMColor4fNeedsWideColor(*geometryColor, clampType, caps); + } + return result; +} + +GrProcessorSet::Analysis GrSimpleMeshDrawOpHelper::finalizeProcessors( + const GrCaps& caps, const GrAppliedClip* clip, const GrUserStencilSettings* userStencil, + bool hasMixedSampledCoverage, GrClampType clampType, + GrProcessorAnalysisCoverage geometryCoverage, GrProcessorAnalysisColor* geometryColor) { + SkDEBUGCODE(fDidAnalysis = true); + GrProcessorSet::Analysis analysis; + if (fProcessors) { + GrProcessorAnalysisCoverage coverage = geometryCoverage; + if (GrProcessorAnalysisCoverage::kNone == coverage) { + coverage = clip->numClipCoverageFragmentProcessors() + ? GrProcessorAnalysisCoverage::kSingleChannel + : GrProcessorAnalysisCoverage::kNone; + } + SkPMColor4f overrideColor; + analysis = fProcessors->finalize(*geometryColor, coverage, clip, userStencil, + hasMixedSampledCoverage, caps, clampType, &overrideColor); + if (analysis.inputColorIsOverridden()) { + *geometryColor = overrideColor; + } + } else { + analysis = GrProcessorSet::EmptySetAnalysis(); + } + fUsesLocalCoords = analysis.usesLocalCoords(); + fCompatibleWithCoverageAsAlpha = analysis.isCompatibleWithCoverageAsAlpha(); + return analysis; +} + +void GrSimpleMeshDrawOpHelper::executeDrawsAndUploads( + const GrOp* op, GrOpFlushState* flushState, const SkRect& chainBounds) { + if (fProcessors) { + flushState->executeDrawsAndUploadsForMeshDrawOp( + op, chainBounds, std::move(*fProcessors), fPipelineFlags); + } else { + flushState->executeDrawsAndUploadsForMeshDrawOp( + op, chainBounds, GrProcessorSet::MakeEmptySet(), fPipelineFlags); + } +} + +#ifdef SK_DEBUG +static void dump_pipeline_flags(GrPipeline::InputFlags flags, SkString* result) { + if (GrPipeline::InputFlags::kNone != flags) { + if (flags & GrPipeline::InputFlags::kSnapVerticesToPixelCenters) { + result->append("Snap vertices to pixel center.\n"); + } + if (flags & GrPipeline::InputFlags::kHWAntialias) { + result->append("HW Antialiasing enabled.\n"); + } + return; + } + result->append("No pipeline flags\n"); +} + +SkString GrSimpleMeshDrawOpHelper::dumpInfo() const { + const GrProcessorSet& processors = fProcessors ? *fProcessors : GrProcessorSet::EmptySet(); + SkString result = processors.dumpProcessors(); + result.append("AA Type: "); + switch (this->aaType()) { + case GrAAType::kNone: + result.append(" none\n"); + break; + case GrAAType::kCoverage: + result.append(" coverage\n"); + break; + case GrAAType::kMSAA: + result.append(" msaa\n"); + break; + } + dump_pipeline_flags(fPipelineFlags, &result); + return result; +} +#endif + +GrSimpleMeshDrawOpHelperWithStencil::GrSimpleMeshDrawOpHelperWithStencil( + const MakeArgs& args, GrAAType aaType, const GrUserStencilSettings* stencilSettings, + InputFlags inputFlags) + : INHERITED(args, aaType, inputFlags) + , fStencilSettings(stencilSettings ? stencilSettings : &GrUserStencilSettings::kUnused) {} + +GrDrawOp::FixedFunctionFlags GrSimpleMeshDrawOpHelperWithStencil::fixedFunctionFlags() const { + GrDrawOp::FixedFunctionFlags flags = INHERITED::fixedFunctionFlags(); + if (fStencilSettings != &GrUserStencilSettings::kUnused) { + flags |= GrDrawOp::FixedFunctionFlags::kUsesStencil; + } + return flags; +} + +GrProcessorSet::Analysis GrSimpleMeshDrawOpHelperWithStencil::finalizeProcessors( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage, + SkPMColor4f* geometryColor, bool* wideColor) { + GrProcessorAnalysisColor color = *geometryColor; + auto result = this->finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, geometryCoverage, &color); + color.isConstant(geometryColor); + if (wideColor) { + *wideColor = SkPMColor4fNeedsWideColor(*geometryColor, clampType, caps); + } + return result; +} + +bool GrSimpleMeshDrawOpHelperWithStencil::isCompatible( + const GrSimpleMeshDrawOpHelperWithStencil& that, const GrCaps& caps, + const SkRect& thisBounds, const SkRect& thatBounds, bool noneAsCoverageAA) const { + return INHERITED::isCompatible(that, caps, thisBounds, thatBounds, noneAsCoverageAA) && + fStencilSettings == that.fStencilSettings; +} + +void GrSimpleMeshDrawOpHelperWithStencil::executeDrawsAndUploads( + const GrOp* op, GrOpFlushState* flushState, const SkRect& chainBounds) { + if (fProcessors) { + flushState->executeDrawsAndUploadsForMeshDrawOp( + op, chainBounds, std::move(*fProcessors), fPipelineFlags, fStencilSettings); + } else { + flushState->executeDrawsAndUploadsForMeshDrawOp( + op, chainBounds, GrProcessorSet::MakeEmptySet(), fPipelineFlags, fStencilSettings); + } +} + +#ifdef SK_DEBUG +SkString GrSimpleMeshDrawOpHelperWithStencil::dumpInfo() const { + SkString result = INHERITED::dumpInfo(); + result.appendf("Stencil settings: %s\n", (fStencilSettings ? "yes" : "no")); + return result; +} +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.h b/gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.h new file mode 100644 index 0000000000..35bf3cc86f --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.h @@ -0,0 +1,228 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSimpleMeshDrawOpHelper_DEFINED +#define GrSimpleMeshDrawOpHelper_DEFINED + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include + +struct SkRect; + +/** + * This class can be used to help implement simple mesh draw ops. It reduces the amount of + * boilerplate code to type and also provides a mechanism for optionally allocating space for a + * GrProcessorSet based on a GrPaint. It is intended to be used by ops that construct a single + * GrPipeline for a uniform primitive color and a GrPaint. + */ +class GrSimpleMeshDrawOpHelper { +public: + struct MakeArgs; + + /** + * This can be used by a Op class to perform allocation and initialization such that a + * GrProcessorSet (if required) is allocated as part of the the same allocation that as + * the Op instance. It requires that Op implements a constructor of the form: + * Op(MakeArgs, GrColor, OpArgs...) + * which is public or made accessible via 'friend'. + */ + template + static std::unique_ptr FactoryHelper(GrRecordingContext*, GrPaint&&, OpArgs...); + + // Here we allow callers to specify a subset of the GrPipeline::InputFlags upon creation. + enum class InputFlags : uint8_t { + kNone = 0, + kSnapVerticesToPixelCenters = (uint8_t)GrPipeline::InputFlags::kSnapVerticesToPixelCenters, + }; + GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(InputFlags); + + GrSimpleMeshDrawOpHelper(const MakeArgs&, GrAAType, InputFlags = InputFlags::kNone); + ~GrSimpleMeshDrawOpHelper(); + + GrSimpleMeshDrawOpHelper() = delete; + GrSimpleMeshDrawOpHelper(const GrSimpleMeshDrawOpHelper&) = delete; + GrSimpleMeshDrawOpHelper& operator=(const GrSimpleMeshDrawOpHelper&) = delete; + + GrDrawOp::FixedFunctionFlags fixedFunctionFlags() const; + + // noneAACompatibleWithCoverage should be set to true if the op can properly render a non-AA + // primitive merged into a coverage-based op. + bool isCompatible(const GrSimpleMeshDrawOpHelper& that, const GrCaps&, const SkRect& thisBounds, + const SkRect& thatBounds, bool noneAACompatibleWithCoverage = false) const; + + /** + * Finalizes the processor set and determines whether the destination must be provided + * to the fragment shader as a texture for blending. + * + * @param geometryCoverage Describes the coverage output of the op's geometry processor + * @param geometryColor An in/out param. As input this informs processor analysis about the + * color the op expects to output from its geometry processor. As output + * this may be set to a known color in which case the op must output this + * color from its geometry processor instead. + */ + GrProcessorSet::Analysis finalizeProcessors( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage, + GrProcessorAnalysisColor* geometryColor) { + return this->finalizeProcessors( + caps, clip, &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, clampType, + geometryCoverage, geometryColor); + } + + /** + * Version of above that can be used by ops that have a constant color geometry processor + * output. The op passes this color as 'geometryColor' and after return if 'geometryColor' has + * changed the op must override its geometry processor color output with the new color. + */ + GrProcessorSet::Analysis finalizeProcessors( + const GrCaps&, const GrAppliedClip*, bool hasMixedSampledCoverage, GrClampType, + GrProcessorAnalysisCoverage geometryCoverage, SkPMColor4f* geometryColor, + bool* wideColor); + + bool isTrivial() const { + return fProcessors == nullptr; + } + + bool usesLocalCoords() const { + SkASSERT(fDidAnalysis); + return fUsesLocalCoords; + } + + bool compatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; } + + struct MakeArgs { + private: + MakeArgs() = default; + + GrProcessorSet* fProcessorSet; + + friend class GrSimpleMeshDrawOpHelper; + }; + + void visitProxies(const GrOp::VisitProxyFunc& func) const { + if (fProcessors) { + fProcessors->visitProxies(func); + } + } + +#ifdef SK_DEBUG + SkString dumpInfo() const; +#endif + GrAAType aaType() const { return static_cast(fAAType); } + + void setAAType(GrAAType aaType) { + fAAType = static_cast(aaType); + } + + void executeDrawsAndUploads(const GrOp*, GrOpFlushState*, const SkRect& chainBounds); + +protected: + GrPipeline::InputFlags pipelineFlags() const { return fPipelineFlags; } + + GrProcessorSet::Analysis finalizeProcessors( + const GrCaps& caps, const GrAppliedClip*, const GrUserStencilSettings*, + bool hasMixedSampledCoverage, GrClampType, GrProcessorAnalysisCoverage geometryCoverage, + GrProcessorAnalysisColor* geometryColor); + + GrProcessorSet* fProcessors; + GrPipeline::InputFlags fPipelineFlags; + unsigned fAAType : 2; + unsigned fUsesLocalCoords : 1; + unsigned fCompatibleWithCoverageAsAlpha : 1; + SkDEBUGCODE(unsigned fMadePipeline : 1;) + SkDEBUGCODE(unsigned fDidAnalysis : 1;) +}; + +/** + * This class extends GrSimpleMeshDrawOpHelper to support an optional GrUserStencilSettings. This + * uses private inheritance because it non-virtually overrides methods in the base class and should + * never be used with a GrSimpleMeshDrawOpHelper pointer or reference. + */ +class GrSimpleMeshDrawOpHelperWithStencil : private GrSimpleMeshDrawOpHelper { +public: + using MakeArgs = GrSimpleMeshDrawOpHelper::MakeArgs; + using InputFlags = GrSimpleMeshDrawOpHelper::InputFlags; + + using GrSimpleMeshDrawOpHelper::visitProxies; + + // using declarations can't be templated, so this is a pass through function instead. + template + static std::unique_ptr FactoryHelper(GrRecordingContext* context, GrPaint&& paint, + OpArgs... opArgs) { + return GrSimpleMeshDrawOpHelper::FactoryHelper( + context, std::move(paint), std::forward(opArgs)...); + } + + GrSimpleMeshDrawOpHelperWithStencil(const MakeArgs&, GrAAType, const GrUserStencilSettings*, + InputFlags = InputFlags::kNone); + + GrDrawOp::FixedFunctionFlags fixedFunctionFlags() const; + + GrProcessorSet::Analysis finalizeProcessors( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage, + GrProcessorAnalysisColor* geometryColor) { + return this->INHERITED::finalizeProcessors( + caps, clip, fStencilSettings, hasMixedSampledCoverage, clampType, geometryCoverage, + geometryColor); + } + + GrProcessorSet::Analysis finalizeProcessors( + const GrCaps&, const GrAppliedClip*, bool hasMixedSampledCoverage, GrClampType, + GrProcessorAnalysisCoverage geometryCoverage, SkPMColor4f* geometryColor, bool* + wideColor); + + using GrSimpleMeshDrawOpHelper::aaType; + using GrSimpleMeshDrawOpHelper::setAAType; + using GrSimpleMeshDrawOpHelper::isTrivial; + using GrSimpleMeshDrawOpHelper::usesLocalCoords; + using GrSimpleMeshDrawOpHelper::compatibleWithCoverageAsAlpha; + + bool isCompatible(const GrSimpleMeshDrawOpHelperWithStencil& that, const GrCaps&, + const SkRect& thisBounds, const SkRect& thatBounds, + bool noneAACompatibleWithCoverage = false) const; + + void executeDrawsAndUploads(const GrOp*, GrOpFlushState*, const SkRect& chainBounds); + +#ifdef SK_DEBUG + SkString dumpInfo() const; +#endif + +private: + const GrUserStencilSettings* fStencilSettings; + typedef GrSimpleMeshDrawOpHelper INHERITED; +}; + +template +std::unique_ptr GrSimpleMeshDrawOpHelper::FactoryHelper(GrRecordingContext* context, + GrPaint&& paint, + OpArgs... opArgs) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + MakeArgs makeArgs; + + if (paint.isTrivial()) { + makeArgs.fProcessorSet = nullptr; + return pool->allocate(makeArgs, paint.getColor4f(), std::forward(opArgs)...); + } else { + char* mem = (char*) pool->allocate(sizeof(Op) + sizeof(GrProcessorSet)); + char* setMem = mem + sizeof(Op); + auto color = paint.getColor4f(); + makeArgs.fProcessorSet = new (setMem) GrProcessorSet(std::move(paint)); + return std::unique_ptr(new (mem) Op(makeArgs, color, + std::forward(opArgs)...)); + } +} + +GR_MAKE_BITFIELD_CLASS_OPS(GrSimpleMeshDrawOpHelper::InputFlags) + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.cpp new file mode 100644 index 0000000000..b65cb423ee --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.cpp @@ -0,0 +1,999 @@ +/* + * Copyright 2014 Google Inc. + * Copyright 2017 ARM Ltd. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrSmallPathRenderer.h" + +#include "include/core/SkPaint.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/core/SkDraw.h" +#include "src/core/SkPointPriv.h" +#include "src/core/SkRasterClip.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrBuffer.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDistanceFieldGenFromVector.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/effects/GrBitmapTextGeoProc.h" +#include "src/gpu/effects/GrDistanceFieldGeoProc.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +#define ATLAS_TEXTURE_WIDTH 2048 +#define ATLAS_TEXTURE_HEIGHT 2048 +#define PLOT_WIDTH 512 +#define PLOT_HEIGHT 256 + +#define NUM_PLOTS_X (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH) +#define NUM_PLOTS_Y (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT) + +#ifdef DF_PATH_TRACKING +static int g_NumCachedShapes = 0; +static int g_NumFreedShapes = 0; +#endif + +// mip levels +static const SkScalar kIdealMinMIP = 12; +static const SkScalar kMaxMIP = 162; + +static const SkScalar kMaxDim = 73; +static const SkScalar kMinSize = SK_ScalarHalf; +static const SkScalar kMaxSize = 2*kMaxMIP; + +class ShapeDataKey { +public: + ShapeDataKey() {} + ShapeDataKey(const ShapeDataKey& that) { *this = that; } + ShapeDataKey(const GrShape& shape, uint32_t dim) { this->set(shape, dim); } + ShapeDataKey(const GrShape& shape, const SkMatrix& ctm) { this->set(shape, ctm); } + + ShapeDataKey& operator=(const ShapeDataKey& that) { + fKey.reset(that.fKey.count()); + memcpy(fKey.get(), that.fKey.get(), fKey.count() * sizeof(uint32_t)); + return *this; + } + + // for SDF paths + void set(const GrShape& shape, uint32_t dim) { + // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any + // relevant styling information. + SkASSERT(shape.style().isSimpleFill()); + SkASSERT(shape.hasUnstyledKey()); + int shapeKeySize = shape.unstyledKeySize(); + fKey.reset(1 + shapeKeySize); + fKey[0] = dim; + shape.writeUnstyledKey(&fKey[1]); + } + + // for bitmap paths + void set(const GrShape& shape, const SkMatrix& ctm) { + // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any + // relevant styling information. + SkASSERT(shape.style().isSimpleFill()); + SkASSERT(shape.hasUnstyledKey()); + // We require the upper left 2x2 of the matrix to match exactly for a cache hit. + SkScalar sx = ctm.get(SkMatrix::kMScaleX); + SkScalar sy = ctm.get(SkMatrix::kMScaleY); + SkScalar kx = ctm.get(SkMatrix::kMSkewX); + SkScalar ky = ctm.get(SkMatrix::kMSkewY); + SkScalar tx = ctm.get(SkMatrix::kMTransX); + SkScalar ty = ctm.get(SkMatrix::kMTransY); + // Allow 8 bits each in x and y of subpixel positioning. + tx -= SkScalarFloorToScalar(tx); + ty -= SkScalarFloorToScalar(ty); + SkFixed fracX = SkScalarToFixed(tx) & 0x0000FF00; + SkFixed fracY = SkScalarToFixed(ty) & 0x0000FF00; + int shapeKeySize = shape.unstyledKeySize(); + fKey.reset(5 + shapeKeySize); + fKey[0] = SkFloat2Bits(sx); + fKey[1] = SkFloat2Bits(sy); + fKey[2] = SkFloat2Bits(kx); + fKey[3] = SkFloat2Bits(ky); + fKey[4] = fracX | (fracY >> 8); + shape.writeUnstyledKey(&fKey[5]); + } + + bool operator==(const ShapeDataKey& that) const { + return fKey.count() == that.fKey.count() && + 0 == memcmp(fKey.get(), that.fKey.get(), sizeof(uint32_t) * fKey.count()); + } + + int count32() const { return fKey.count(); } + const uint32_t* data() const { return fKey.get(); } + +private: + // The key is composed of the GrShape's key, and either the dimensions of the DF + // generated for the path (32x32 max, 64x64 max, 128x128 max) if an SDF image or + // the matrix for the path with only fractional translation. + SkAutoSTArray<24, uint32_t> fKey; +}; + +class ShapeData { +public: + ShapeDataKey fKey; + GrDrawOpAtlas::AtlasID fID; + SkRect fBounds; + GrIRect16 fTextureCoords; + SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeData); + + static inline const ShapeDataKey& GetKey(const ShapeData& data) { + return data.fKey; + } + + static inline uint32_t Hash(const ShapeDataKey& key) { + return SkOpts::hash(key.data(), sizeof(uint32_t) * key.count32()); + } +}; + + + +// Callback to clear out internal path cache when eviction occurs +void GrSmallPathRenderer::HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) { + GrSmallPathRenderer* dfpr = (GrSmallPathRenderer*)pr; + // remove any paths that use this plot + ShapeDataList::Iter iter; + iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart); + ShapeData* shapeData; + while ((shapeData = iter.get())) { + iter.next(); + if (id == shapeData->fID) { + dfpr->fShapeCache.remove(shapeData->fKey); + dfpr->fShapeList.remove(shapeData); + delete shapeData; +#ifdef DF_PATH_TRACKING + ++g_NumFreedPaths; +#endif + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +GrSmallPathRenderer::GrSmallPathRenderer() : fAtlas(nullptr) {} + +GrSmallPathRenderer::~GrSmallPathRenderer() { + ShapeDataList::Iter iter; + iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart); + ShapeData* shapeData; + while ((shapeData = iter.get())) { + iter.next(); + delete shapeData; + } + +#ifdef DF_PATH_TRACKING + SkDebugf("Cached shapes: %d, freed shapes: %d\n", g_NumCachedShapes, g_NumFreedShapes); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +GrPathRenderer::CanDrawPath GrSmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + if (!args.fCaps->shaderCaps()->shaderDerivativeSupport()) { + return CanDrawPath::kNo; + } + // If the shape has no key then we won't get any reuse. + if (!args.fShape->hasUnstyledKey()) { + return CanDrawPath::kNo; + } + // This only supports filled paths, however, the caller may apply the style to make a filled + // path and try again. + if (!args.fShape->style().isSimpleFill()) { + return CanDrawPath::kNo; + } + // This does non-inverse coverage-based antialiased fills. + if (GrAAType::kCoverage != args.fAAType) { + return CanDrawPath::kNo; + } + // TODO: Support inverse fill + if (args.fShape->inverseFilled()) { + return CanDrawPath::kNo; + } + + // Only support paths with bounds within kMaxDim by kMaxDim, + // scaled to have bounds within kMaxSize by kMaxSize. + // The goal is to accelerate rendering of lots of small paths that may be scaling. + SkScalar scaleFactors[2] = { 1, 1 }; + if (!args.fViewMatrix->hasPerspective() && !args.fViewMatrix->getMinMaxScales(scaleFactors)) { + return CanDrawPath::kNo; + } + SkRect bounds = args.fShape->styledBounds(); + SkScalar minDim = SkMinScalar(bounds.width(), bounds.height()); + SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height()); + SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]); + SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]); + if (maxDim > kMaxDim || kMinSize > minSize || maxSize > kMaxSize) { + return CanDrawPath::kNo; + } + + return CanDrawPath::kYes; +} + +//////////////////////////////////////////////////////////////////////////////// + +// padding around path bounds to allow for antialiased pixels +static const SkScalar kAntiAliasPad = 1.0f; + +class GrSmallPathRenderer::SmallPathOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + + using ShapeCache = SkTDynamicHash; + using ShapeDataList = GrSmallPathRenderer::ShapeDataList; + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const GrShape& shape, + const SkMatrix& viewMatrix, + GrDrawOpAtlas* atlas, + ShapeCache* shapeCache, + ShapeDataList* shapeList, + bool gammaCorrect, + const GrUserStencilSettings* stencilSettings) { + return Helper::FactoryHelper(context, std::move(paint), shape, viewMatrix, + atlas, shapeCache, shapeList, gammaCorrect, + stencilSettings); + } + + SmallPathOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color, const GrShape& shape, + const SkMatrix& viewMatrix, GrDrawOpAtlas* atlas, ShapeCache* shapeCache, + ShapeDataList* shapeList, bool gammaCorrect, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) { + SkASSERT(shape.hasUnstyledKey()); + // Compute bounds + this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsHairline::kNo); + +#if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) + fUsesDistanceField = true; +#else + // only use distance fields on desktop and Android framework to save space in the atlas + fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP; +#endif + // always use distance fields if in perspective + fUsesDistanceField = fUsesDistanceField || viewMatrix.hasPerspective(); + + fShapes.emplace_back(Entry{color, shape, viewMatrix}); + + fAtlas = atlas; + fShapeCache = shapeCache; + fShapeList = shapeList; + fGammaCorrect = gammaCorrect; + } + + const char* name() const override { return "SmallPathOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + + const sk_sp* proxies = fAtlas->getProxies(); + for (uint32_t i = 0; i < fAtlas->numActivePages(); ++i) { + SkASSERT(proxies[i]); + func(proxies[i].get(), GrMipMapped::kNo); + } + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (const auto& geo : fShapes) { + string.appendf("Color: 0x%08x\n", geo.fColor.toBytes_RGBA()); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, &fShapes.front().fColor, &fWideColor); + } + +private: + struct FlushInfo { + sk_sp fVertexBuffer; + sk_sp fIndexBuffer; + sk_sp fGeometryProcessor; + GrPipeline::FixedDynamicState* fFixedDynamicState; + int fVertexOffset; + int fInstancesToFlush; + }; + + void onPrepareDraws(Target* target) override { + int instanceCount = fShapes.count(); + + static constexpr int kMaxTextures = GrDistanceFieldPathGeoProc::kMaxTextures; + GR_STATIC_ASSERT(GrBitmapTextGeoProc::kMaxTextures == kMaxTextures); + + FlushInfo flushInfo; + flushInfo.fFixedDynamicState = target->makeFixedDynamicState(kMaxTextures); + int numActiveProxies = fAtlas->numActivePages(); + const auto proxies = fAtlas->getProxies(); + for (int i = 0; i < numActiveProxies; ++i) { + // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the + // proxies don't get added during the visitProxies call. Thus we add them here. + flushInfo.fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get(); + target->sampledProxyArray()->push_back(proxies[i].get()); + } + + // Setup GrGeometryProcessor + const SkMatrix& ctm = fShapes[0].fViewMatrix; + if (fUsesDistanceField) { + uint32_t flags = 0; + // Still need to key off of ctm to pick the right shader for the transformed quad + flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0; + flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; + flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0; + + const SkMatrix* matrix; + SkMatrix invert; + if (ctm.hasPerspective()) { + matrix = &ctm; + } else if (fHelper.usesLocalCoords()) { + if (!ctm.invert(&invert)) { + return; + } + matrix = &invert; + } else { + matrix = &SkMatrix::I(); + } + flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make( + *target->caps().shaderCaps(), *matrix, fWideColor, fAtlas->getProxies(), + fAtlas->numActivePages(), GrSamplerState::ClampBilerp(), flags); + } else { + SkMatrix invert; + if (fHelper.usesLocalCoords()) { + if (!ctm.invert(&invert)) { + return; + } + } + + flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make( + *target->caps().shaderCaps(), this->color(), fWideColor, fAtlas->getProxies(), + fAtlas->numActivePages(), GrSamplerState::ClampNearest(), kA8_GrMaskFormat, + invert, false); + } + + // allocate vertices + const size_t kVertexStride = flushInfo.fGeometryProcessor->vertexStride(); + + // We need to make sure we don't overflow a 32 bit int when we request space in the + // makeVertexSpace call below. + if (instanceCount > SK_MaxS32 / kVerticesPerQuad) { + return; + } + GrVertexWriter vertices{target->makeVertexSpace(kVertexStride, + kVerticesPerQuad * instanceCount, + &flushInfo.fVertexBuffer, + &flushInfo.fVertexOffset)}; + flushInfo.fIndexBuffer = target->resourceProvider()->refQuadIndexBuffer(); + if (!vertices.fPtr || !flushInfo.fIndexBuffer) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + flushInfo.fInstancesToFlush = 0; + for (int i = 0; i < instanceCount; i++) { + const Entry& args = fShapes[i]; + + ShapeData* shapeData; + if (fUsesDistanceField) { + // get mip level + SkScalar maxScale; + const SkRect& bounds = args.fShape.bounds(); + if (args.fViewMatrix.hasPerspective()) { + // approximate the scale since we can't get it from the matrix + SkRect xformedBounds; + args.fViewMatrix.mapRect(&xformedBounds, bounds); + maxScale = SkScalarAbs(SkTMax(xformedBounds.width() / bounds.width(), + xformedBounds.height() / bounds.height())); + } else { + maxScale = SkScalarAbs(args.fViewMatrix.getMaxScale()); + } + SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height()); + // We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.) + // In the majority of cases this will yield a crisper rendering. + SkScalar mipScale = 1.0f; + // Our mipscale is the maxScale clamped to the next highest power of 2 + if (maxScale <= SK_ScalarHalf) { + SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale))); + mipScale = SkScalarPow(2, -log); + } else if (maxScale > SK_Scalar1) { + SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale)); + mipScale = SkScalarPow(2, log); + } + SkASSERT(maxScale <= mipScale); + + SkScalar mipSize = mipScale*SkScalarAbs(maxDim); + // For sizes less than kIdealMinMIP we want to use as large a distance field as we can + // so we can preserve as much detail as possible. However, we can't scale down more + // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize + // just bigger than the ideal, and then scale down until we are no more than 4x the + // original mipsize. + if (mipSize < kIdealMinMIP) { + SkScalar newMipSize = mipSize; + do { + newMipSize *= 2; + } while (newMipSize < kIdealMinMIP); + while (newMipSize > 4 * mipSize) { + newMipSize *= 0.25f; + } + mipSize = newMipSize; + } + SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP); + + // check to see if df path is cached + ShapeDataKey key(args.fShape, SkScalarCeilToInt(desiredDimension)); + shapeData = fShapeCache->find(key); + if (nullptr == shapeData || !fAtlas->hasID(shapeData->fID)) { + // Remove the stale cache entry + if (shapeData) { + fShapeCache->remove(shapeData->fKey); + fShapeList->remove(shapeData); + delete shapeData; + } + SkScalar scale = desiredDimension / maxDim; + + shapeData = new ShapeData; + if (!this->addDFPathToAtlas(target, + &flushInfo, + fAtlas, + shapeData, + args.fShape, + SkScalarCeilToInt(desiredDimension), + scale)) { + delete shapeData; + continue; + } + } + } else { + // check to see if bitmap path is cached + ShapeDataKey key(args.fShape, args.fViewMatrix); + shapeData = fShapeCache->find(key); + if (nullptr == shapeData || !fAtlas->hasID(shapeData->fID)) { + // Remove the stale cache entry + if (shapeData) { + fShapeCache->remove(shapeData->fKey); + fShapeList->remove(shapeData); + delete shapeData; + } + + shapeData = new ShapeData; + if (!this->addBMPathToAtlas(target, + &flushInfo, + fAtlas, + shapeData, + args.fShape, + args.fViewMatrix)) { + delete shapeData; + continue; + } + } + } + + auto uploadTarget = target->deferredUploadTarget(); + fAtlas->setLastUseToken(shapeData->fID, uploadTarget->tokenTracker()->nextDrawToken()); + + this->writePathVertices(fAtlas, vertices, GrVertexColor(args.fColor, fWideColor), + args.fViewMatrix, shapeData); + flushInfo.fInstancesToFlush++; + } + + this->flush(target, &flushInfo); + } + + bool addToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas, + int width, int height, const void* image, + GrDrawOpAtlas::AtlasID* id, SkIPoint16* atlasLocation) const { + auto resourceProvider = target->resourceProvider(); + auto uploadTarget = target->deferredUploadTarget(); + + GrDrawOpAtlas::ErrorCode code = atlas->addToAtlas(resourceProvider, id, + uploadTarget, width, height, + image, atlasLocation); + if (GrDrawOpAtlas::ErrorCode::kError == code) { + return false; + } + + if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) { + this->flush(target, flushInfo); + + code = atlas->addToAtlas(resourceProvider, id, uploadTarget, width, height, + image, atlasLocation); + } + + return GrDrawOpAtlas::ErrorCode::kSucceeded == code; + } + + bool addDFPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, + GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape, + uint32_t dimension, SkScalar scale) const { + + const SkRect& bounds = shape.bounds(); + + // generate bounding rect for bitmap draw + SkRect scaledBounds = bounds; + // scale to mip level size + scaledBounds.fLeft *= scale; + scaledBounds.fTop *= scale; + scaledBounds.fRight *= scale; + scaledBounds.fBottom *= scale; + // subtract out integer portion of origin + // (SDF created will be placed with fractional offset burnt in) + SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft); + SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop); + scaledBounds.offset(-dx, -dy); + // get integer boundary + SkIRect devPathBounds; + scaledBounds.roundOut(&devPathBounds); + // pad to allow room for antialiasing + const int intPad = SkScalarCeilToInt(kAntiAliasPad); + // place devBounds at origin + int width = devPathBounds.width() + 2*intPad; + int height = devPathBounds.height() + 2*intPad; + devPathBounds = SkIRect::MakeWH(width, height); + SkScalar translateX = intPad - dx; + SkScalar translateY = intPad - dy; + + // draw path to bitmap + SkMatrix drawMatrix; + drawMatrix.setScale(scale, scale); + drawMatrix.postTranslate(translateX, translateY); + + SkASSERT(devPathBounds.fLeft == 0); + SkASSERT(devPathBounds.fTop == 0); + SkASSERT(devPathBounds.width() > 0); + SkASSERT(devPathBounds.height() > 0); + + // setup signed distance field storage + SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad); + width = dfBounds.width(); + height = dfBounds.height(); + // TODO We should really generate this directly into the plot somehow + SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char)); + + SkPath path; + shape.asPath(&path); +#ifndef SK_USE_LEGACY_DISTANCE_FIELDS + // Generate signed distance field directly from SkPath + bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(), + path, drawMatrix, + width, height, width * sizeof(unsigned char)); + if (!succeed) { +#endif + // setup bitmap backing + SkAutoPixmapStorage dst; + if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), + devPathBounds.height()))) { + return false; + } + sk_bzero(dst.writable_addr(), dst.computeByteSize()); + + // rasterize path + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setAntiAlias(true); + + SkDraw draw; + + SkRasterClip rasterClip; + rasterClip.setRect(devPathBounds); + draw.fRC = &rasterClip; + draw.fMatrix = &drawMatrix; + draw.fDst = dst; + + draw.drawPathCoverage(path, paint); + + // Generate signed distance field + SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(), + (const unsigned char*)dst.addr(), + dst.width(), dst.height(), dst.rowBytes()); +#ifndef SK_USE_LEGACY_DISTANCE_FIELDS + } +#endif + + // add to atlas + SkIPoint16 atlasLocation; + GrDrawOpAtlas::AtlasID id; + + if (!this->addToAtlas(target, flushInfo, atlas, + width, height, dfStorage.get(), &id, &atlasLocation)) { + return false; + } + + // add to cache + shapeData->fKey.set(shape, dimension); + shapeData->fID = id; + + shapeData->fBounds = SkRect::Make(devPathBounds); + shapeData->fBounds.offset(-translateX, -translateY); + shapeData->fBounds.fLeft /= scale; + shapeData->fBounds.fTop /= scale; + shapeData->fBounds.fRight /= scale; + shapeData->fBounds.fBottom /= scale; + + // We pack the 2bit page index in the low bit of the u and v texture coords + uint16_t pageIndex = GrDrawOpAtlas::GetPageIndexFromID(id); + SkASSERT(pageIndex < 4); + uint16_t uBit = (pageIndex >> 1) & 0x1; + uint16_t vBit = pageIndex & 0x1; + shapeData->fTextureCoords.set((atlasLocation.fX+SK_DistanceFieldPad) << 1 | uBit, + (atlasLocation.fY+SK_DistanceFieldPad) << 1 | vBit, + (atlasLocation.fX+SK_DistanceFieldPad+ + devPathBounds.width()) << 1 | uBit, + (atlasLocation.fY+SK_DistanceFieldPad+ + devPathBounds.height()) << 1 | vBit); + + fShapeCache->add(shapeData); + fShapeList->addToTail(shapeData); +#ifdef DF_PATH_TRACKING + ++g_NumCachedPaths; +#endif + return true; + } + + bool addBMPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, + GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape, + const SkMatrix& ctm) const { + const SkRect& bounds = shape.bounds(); + if (bounds.isEmpty()) { + return false; + } + SkMatrix drawMatrix(ctm); + SkScalar tx = ctm.getTranslateX(); + SkScalar ty = ctm.getTranslateY(); + tx -= SkScalarFloorToScalar(tx); + ty -= SkScalarFloorToScalar(ty); + drawMatrix.set(SkMatrix::kMTransX, tx); + drawMatrix.set(SkMatrix::kMTransY, ty); + SkRect shapeDevBounds; + drawMatrix.mapRect(&shapeDevBounds, bounds); + SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft); + SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop); + + // get integer boundary + SkIRect devPathBounds; + shapeDevBounds.roundOut(&devPathBounds); + // pad to allow room for antialiasing + const int intPad = SkScalarCeilToInt(kAntiAliasPad); + // place devBounds at origin + int width = devPathBounds.width() + 2 * intPad; + int height = devPathBounds.height() + 2 * intPad; + devPathBounds = SkIRect::MakeWH(width, height); + SkScalar translateX = intPad - dx; + SkScalar translateY = intPad - dy; + + SkASSERT(devPathBounds.fLeft == 0); + SkASSERT(devPathBounds.fTop == 0); + SkASSERT(devPathBounds.width() > 0); + SkASSERT(devPathBounds.height() > 0); + + SkPath path; + shape.asPath(&path); + // setup bitmap backing + SkAutoPixmapStorage dst; + if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), + devPathBounds.height()))) { + return false; + } + sk_bzero(dst.writable_addr(), dst.computeByteSize()); + + // rasterize path + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setAntiAlias(true); + + SkDraw draw; + + SkRasterClip rasterClip; + rasterClip.setRect(devPathBounds); + draw.fRC = &rasterClip; + drawMatrix.postTranslate(translateX, translateY); + draw.fMatrix = &drawMatrix; + draw.fDst = dst; + + draw.drawPathCoverage(path, paint); + + // add to atlas + SkIPoint16 atlasLocation; + GrDrawOpAtlas::AtlasID id; + + if (!this->addToAtlas(target, flushInfo, atlas, + dst.width(), dst.height(), dst.addr(), &id, &atlasLocation)) { + return false; + } + + // add to cache + shapeData->fKey.set(shape, ctm); + shapeData->fID = id; + + shapeData->fBounds = SkRect::Make(devPathBounds); + shapeData->fBounds.offset(-translateX, -translateY); + + // We pack the 2bit page index in the low bit of the u and v texture coords + uint16_t pageIndex = GrDrawOpAtlas::GetPageIndexFromID(id); + SkASSERT(pageIndex < 4); + uint16_t uBit = (pageIndex >> 1) & 0x1; + uint16_t vBit = pageIndex & 0x1; + shapeData->fTextureCoords.set(atlasLocation.fX << 1 | uBit, atlasLocation.fY << 1 | vBit, + (atlasLocation.fX+width) << 1 | uBit, + (atlasLocation.fY+height) << 1 | vBit); + + fShapeCache->add(shapeData); + fShapeList->addToTail(shapeData); +#ifdef DF_PATH_TRACKING + ++g_NumCachedPaths; +#endif + return true; + } + + void writePathVertices(GrDrawOpAtlas* atlas, + GrVertexWriter& vertices, + const GrVertexColor& color, + const SkMatrix& ctm, + const ShapeData* shapeData) const { + SkRect translatedBounds(shapeData->fBounds); + if (!fUsesDistanceField) { + translatedBounds.offset(SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransX)), + SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransY))); + } + + // set up texture coordinates + GrVertexWriter::TriStrip texCoords{ + (uint16_t)shapeData->fTextureCoords.fLeft, + (uint16_t)shapeData->fTextureCoords.fTop, + (uint16_t)shapeData->fTextureCoords.fRight, + (uint16_t)shapeData->fTextureCoords.fBottom + }; + + if (fUsesDistanceField && !ctm.hasPerspective()) { + vertices.writeQuad(GrQuad::MakeFromRect(translatedBounds, ctm), + color, + texCoords); + } else { + vertices.writeQuad(GrVertexWriter::TriStripFromRect(translatedBounds), + color, + texCoords); + } + } + + void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const { + GrGeometryProcessor* gp = flushInfo->fGeometryProcessor.get(); + int numAtlasTextures = SkToInt(fAtlas->numActivePages()); + auto proxies = fAtlas->getProxies(); + if (gp->numTextureSamplers() != numAtlasTextures) { + for (int i = gp->numTextureSamplers(); i < numAtlasTextures; ++i) { + flushInfo->fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get(); + // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the + // proxies don't get added during the visitProxies call. Thus we add them here. + target->sampledProxyArray()->push_back(proxies[i].get()); + } + // During preparation the number of atlas pages has increased. + // Update the proxies used in the GP to match. + if (fUsesDistanceField) { + reinterpret_cast(gp)->addNewProxies( + fAtlas->getProxies(), fAtlas->numActivePages(), GrSamplerState::ClampBilerp()); + } else { + reinterpret_cast(gp)->addNewProxies( + fAtlas->getProxies(), fAtlas->numActivePages(), GrSamplerState::ClampNearest()); + } + } + + if (flushInfo->fInstancesToFlush) { + GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles); + int maxInstancesPerDraw = + static_cast(flushInfo->fIndexBuffer->size() / sizeof(uint16_t) / 6); + mesh->setIndexedPatterned(flushInfo->fIndexBuffer, kIndicesPerQuad, kVerticesPerQuad, + flushInfo->fInstancesToFlush, maxInstancesPerDraw); + mesh->setVertexData(flushInfo->fVertexBuffer, flushInfo->fVertexOffset); + target->recordDraw( + flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fFixedDynamicState, nullptr); + flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush; + flushInfo->fInstancesToFlush = 0; + } + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + const SkPMColor4f& color() const { return fShapes[0].fColor; } + bool usesDistanceField() const { return fUsesDistanceField; } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + SmallPathOp* that = t->cast(); + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + if (this->usesDistanceField() != that->usesDistanceField()) { + return CombineResult::kCannotCombine; + } + + const SkMatrix& thisCtm = this->fShapes[0].fViewMatrix; + const SkMatrix& thatCtm = that->fShapes[0].fViewMatrix; + + if (thisCtm.hasPerspective() != thatCtm.hasPerspective()) { + return CombineResult::kCannotCombine; + } + + // We can position on the cpu unless we're in perspective, + // but also need to make sure local matrices are identical + if ((thisCtm.hasPerspective() || fHelper.usesLocalCoords()) && + !thisCtm.cheapEqualTo(thatCtm)) { + return CombineResult::kCannotCombine; + } + + // Depending on the ctm we may have a different shader for SDF paths + if (this->usesDistanceField()) { + if (thisCtm.isScaleTranslate() != thatCtm.isScaleTranslate() || + thisCtm.isSimilarity() != thatCtm.isSimilarity()) { + return CombineResult::kCannotCombine; + } + } + + fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; + } + + bool fUsesDistanceField; + + struct Entry { + SkPMColor4f fColor; + GrShape fShape; + SkMatrix fViewMatrix; + }; + + SkSTArray<1, Entry> fShapes; + Helper fHelper; + GrDrawOpAtlas* fAtlas; + ShapeCache* fShapeCache; + ShapeDataList* fShapeList; + bool fGammaCorrect; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +bool GrSmallPathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrSmallPathRenderer::onDrawPath"); + + // we've already bailed on inverse filled paths, so this is safe + SkASSERT(!args.fShape->isEmpty()); + SkASSERT(args.fShape->hasUnstyledKey()); + if (!fAtlas) { + const GrBackendFormat format = args.fContext->priv().caps()->getDefaultBackendFormat( + GrColorType::kAlpha_8, GrRenderable::kNo); + fAtlas = GrDrawOpAtlas::Make(args.fContext->priv().proxyProvider(), + format, + GrColorType::kAlpha_8, + ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT, + PLOT_WIDTH, PLOT_HEIGHT, + GrDrawOpAtlas::AllowMultitexturing::kYes, + &GrSmallPathRenderer::HandleEviction, + (void*)this); + if (!fAtlas) { + return false; + } + } + + std::unique_ptr op = SmallPathOp::Make( + args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, fAtlas.get(), + &fShapeCache, &fShapeList, args.fGammaCorrect, args.fUserStencilSettings); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +struct GrSmallPathRenderer::PathTestStruct { + PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {} + ~PathTestStruct() { this->reset(); } + + void reset() { + ShapeDataList::Iter iter; + iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart); + ShapeData* shapeData; + while ((shapeData = iter.get())) { + iter.next(); + fShapeList.remove(shapeData); + delete shapeData; + } + fAtlas = nullptr; + fShapeCache.reset(); + } + + static void HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) { + PathTestStruct* dfpr = (PathTestStruct*)pr; + // remove any paths that use this plot + ShapeDataList::Iter iter; + iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart); + ShapeData* shapeData; + while ((shapeData = iter.get())) { + iter.next(); + if (id == shapeData->fID) { + dfpr->fShapeCache.remove(shapeData->fKey); + dfpr->fShapeList.remove(shapeData); + delete shapeData; + } + } + } + + uint32_t fContextID; + std::unique_ptr fAtlas; + ShapeCache fShapeCache; + ShapeDataList fShapeList; +}; + +std::unique_ptr GrSmallPathRenderer::createOp_TestingOnly( + GrRecordingContext* context, + GrPaint&& paint, + const GrShape& shape, + const SkMatrix& viewMatrix, + GrDrawOpAtlas* atlas, + ShapeCache* shapeCache, + ShapeDataList* shapeList, + bool gammaCorrect, + const GrUserStencilSettings* stencil) { + + return GrSmallPathRenderer::SmallPathOp::Make(context, std::move(paint), shape, viewMatrix, + atlas, shapeCache, shapeList, gammaCorrect, + stencil); + +} + +GR_DRAW_OP_TEST_DEFINE(SmallPathOp) { + using PathTestStruct = GrSmallPathRenderer::PathTestStruct; + static PathTestStruct gTestStruct; + + if (context->priv().contextID() != gTestStruct.fContextID) { + gTestStruct.fContextID = context->priv().contextID(); + gTestStruct.reset(); + const GrBackendFormat format = context->priv().caps()->getDefaultBackendFormat( + GrColorType::kAlpha_8, GrRenderable::kNo); + gTestStruct.fAtlas = GrDrawOpAtlas::Make(context->priv().proxyProvider(), + format, GrColorType::kAlpha_8, + ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT, + PLOT_WIDTH, PLOT_HEIGHT, + GrDrawOpAtlas::AllowMultitexturing::kYes, + &PathTestStruct::HandleEviction, + (void*)&gTestStruct); + } + + SkMatrix viewMatrix = GrTest::TestMatrix(random); + bool gammaCorrect = random->nextBool(); + + // This path renderer only allows fill styles. + GrShape shape(GrTest::TestPath(random), GrStyle::SimpleFill()); + return GrSmallPathRenderer::createOp_TestingOnly( + context, + std::move(paint), shape, viewMatrix, + gTestStruct.fAtlas.get(), + &gTestStruct.fShapeCache, + &gTestStruct.fShapeList, + gammaCorrect, + GrGetRandomStencil(random, context)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.h new file mode 100644 index 0000000000..a916fc1d89 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrSmallPathRenderer.h @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSmallPathRenderer_DEFINED +#define GrSmallPathRenderer_DEFINED + +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrPathRenderer.h" +#include "src/gpu/geometry/GrRect.h" +#include "src/gpu/geometry/GrShape.h" + +#include "src/core/SkOpts.h" +#include "src/core/SkTDynamicHash.h" + +class GrRecordingContext; + +class ShapeData; +class ShapeDataKey; + +class GrSmallPathRenderer : public GrPathRenderer, public GrOnFlushCallbackObject { +public: + GrSmallPathRenderer(); + ~GrSmallPathRenderer() override; + + // GrOnFlushCallbackObject overrides + // + // Note: because this class is associated with a path renderer we want it to be removed from + // the list of active OnFlushBackkbackObjects in an freeGpuResources call (i.e., we accept the + // default retainOnFreeGpuResources implementation). + + void preFlush(GrOnFlushResourceProvider* onFlushRP, const uint32_t*, int) override { + if (fAtlas) { + fAtlas->instantiate(onFlushRP); + } + } + + void postFlush(GrDeferredUploadToken startTokenForNextFlush, + const uint32_t* /*opsTaskIDs*/, int /*numOpsTaskIDs*/) override { + if (fAtlas) { + fAtlas->compact(startTokenForNextFlush); + } + } + + using ShapeCache = SkTDynamicHash; + typedef SkTInternalLList ShapeDataList; + + static std::unique_ptr createOp_TestingOnly(GrRecordingContext*, + GrPaint&&, + const GrShape&, + const SkMatrix& viewMatrix, + GrDrawOpAtlas* atlas, + ShapeCache*, + ShapeDataList*, + bool gammaCorrect, + const GrUserStencilSettings*); + struct PathTestStruct; + +private: + class SmallPathOp; + + StencilSupport onGetStencilSupport(const GrShape&) const override { + return GrPathRenderer::kNoSupport_StencilSupport; + } + + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; + + static void HandleEviction(GrDrawOpAtlas::AtlasID, void*); + + std::unique_ptr fAtlas; + ShapeCache fShapeCache; + ShapeDataList fShapeList; + + typedef GrPathRenderer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp new file mode 100644 index 0000000000..c05b955d8e --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrPath.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrStencilClip.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrDrawPathOp.h" +#include "src/gpu/ops/GrStencilAndCoverPathRenderer.h" +#include "src/gpu/ops/GrStencilPathOp.h" + +GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrResourceProvider* resourceProvider, + const GrCaps& caps) { + if (caps.shaderCaps()->pathRenderingSupport() && !caps.avoidStencilBuffers()) { + return new GrStencilAndCoverPathRenderer(resourceProvider); + } else { + return nullptr; + } +} + +GrStencilAndCoverPathRenderer::GrStencilAndCoverPathRenderer(GrResourceProvider* resourceProvider) + : fResourceProvider(resourceProvider) { +} + +GrPathRenderer::CanDrawPath +GrStencilAndCoverPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + SkASSERT(!args.fTargetIsWrappedVkSecondaryCB); + // GrPath doesn't support hairline paths. An arbitrary path effect could produce a hairline + // path. + if (args.fShape->style().strokeRec().isHairlineStyle() || + args.fShape->style().hasNonDashPathEffect() || + args.fHasUserStencilSettings) { + return CanDrawPath::kNo; + } + if (GrAAType::kCoverage == args.fAAType && !args.fProxy->canUseMixedSamples(*args.fCaps)) { + // We rely on a mixed sampled stencil buffer to implement coverage AA. + return CanDrawPath::kNo; + } + return CanDrawPath::kYes; +} + +static sk_sp get_gr_path(GrResourceProvider* resourceProvider, const GrShape& shape) { + GrUniqueKey key; + bool isVolatile; + GrPath::ComputeKey(shape, &key, &isVolatile); + sk_sp path; + if (!isVolatile) { + path = resourceProvider->findByUniqueKey(key); + } + if (!path) { + SkPath skPath; + shape.asPath(&skPath); + path = resourceProvider->createPath(skPath, shape.style()); + if (!isVolatile) { + resourceProvider->assignUniqueKeyToResource(key, path.get()); + } + } else { +#ifdef SK_DEBUG + SkPath skPath; + shape.asPath(&skPath); + SkASSERT(path->isEqualTo(skPath, shape.style())); +#endif + } + return path; +} + +void GrStencilAndCoverPathRenderer::onStencilPath(const StencilPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrStencilAndCoverPathRenderer::onStencilPath"); + sk_sp p(get_gr_path(fResourceProvider, *args.fShape)); + args.fRenderTargetContext->priv().stencilPath( + *args.fClip, args.fDoStencilMSAA, *args.fViewMatrix, std::move(p)); +} + +bool GrStencilAndCoverPathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrStencilAndCoverPathRenderer::onDrawPath"); + SkASSERT(!args.fShape->style().strokeRec().isHairlineStyle()); + + const SkMatrix& viewMatrix = *args.fViewMatrix; + + bool doStencilMSAA = GrAAType::kNone != args.fAAType; + + sk_sp path(get_gr_path(fResourceProvider, *args.fShape)); + + if (args.fShape->inverseFilled()) { + SkMatrix vmi; + if (!viewMatrix.invert(&vmi)) { + return true; + } + + SkRect devBounds = SkRect::MakeIWH(args.fRenderTargetContext->width(), + args.fRenderTargetContext->height()); // Inverse fill. + + // fake inverse with a stencil and cover + GrAppliedClip appliedClip; + if (!args.fClip->apply( + args.fContext, args.fRenderTargetContext, doStencilMSAA, true, &appliedClip, + &devBounds)) { + return true; + } + GrStencilClip stencilClip(appliedClip.stencilStackID()); + if (appliedClip.scissorState().enabled()) { + stencilClip.fixedClip().setScissor(appliedClip.scissorState().rect()); + } + if (appliedClip.windowRectsState().enabled()) { + stencilClip.fixedClip().setWindowRectangles(appliedClip.windowRectsState().windows(), + appliedClip.windowRectsState().mode()); + } + // Just ignore the analytic FPs (if any) during the stencil pass. They will still clip the + // final draw and it is meaningless to multiply by coverage when drawing to stencil. + args.fRenderTargetContext->priv().stencilPath( + stencilClip, GrAA(doStencilMSAA), viewMatrix, std::move(path)); + + { + static constexpr GrUserStencilSettings kInvertedCoverPass( + GrUserStencilSettings::StaticInit< + 0x0000, + // We know our rect will hit pixels outside the clip and the user bits will + // be 0 outside the clip. So we can't just fill where the user bits are 0. We + // also need to check that the clip bit is set. + GrUserStencilTest::kEqualIfInClip, + 0xffff, + GrUserStencilOp::kKeep, + GrUserStencilOp::kZero, + 0xffff>() + ); + + SkRect coverBounds; + // mapRect through persp matrix may not be correct + if (!viewMatrix.hasPerspective()) { + vmi.mapRect(&coverBounds, devBounds); + // theoretically could set bloat = 0, instead leave it because of matrix inversion + // precision. + SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf; + coverBounds.outset(bloat, bloat); + } else { + coverBounds = devBounds; + } + const SkMatrix& coverMatrix = !viewMatrix.hasPerspective() ? viewMatrix : SkMatrix::I(); + const SkMatrix& localMatrix = !viewMatrix.hasPerspective() ? SkMatrix::I() : vmi; + + // We have to suppress enabling MSAA for mixed samples or we will get seams due to + // coverage modulation along the edge where two triangles making up the rect meet. + GrAA doStencilMSAA = GrAA::kNo; + if (GrAAType::kMSAA == args.fAAType) { + doStencilMSAA = GrAA::kYes; + } + args.fRenderTargetContext->priv().stencilRect( + *args.fClip, &kInvertedCoverPass, std::move(args.fPaint), doStencilMSAA, + coverMatrix, coverBounds, &localMatrix); + } + } else { + std::unique_ptr op = GrDrawPathOp::Make( + args.fContext, viewMatrix, std::move(args.fPaint), GrAA(doStencilMSAA), + std::move(path)); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + } + + return true; +} diff --git a/gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.h new file mode 100644 index 0000000000..755a1b63b7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrStencilAndCoverPathRenderer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBuiltInPathRenderer_DEFINED +#define GrBuiltInPathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +class GrGpu; +class GrResourceProvider; + +/** + * Uses GrGpu::stencilPath followed by a cover rectangle. This subclass doesn't apply AA; it relies + * on the target having MSAA if AA is desired. + */ +class GrStencilAndCoverPathRenderer : public GrPathRenderer { +public: + + static GrPathRenderer* Create(GrResourceProvider*, const GrCaps&); + + +private: + StencilSupport onGetStencilSupport(const GrShape&) const override { + return GrPathRenderer::kStencilOnly_StencilSupport; + } + + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + bool onDrawPath(const DrawPathArgs&) override; + + void onStencilPath(const StencilPathArgs&) override; + + GrStencilAndCoverPathRenderer(GrResourceProvider*); + + GrResourceProvider* fResourceProvider; + + typedef GrPathRenderer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrStencilPathOp.cpp b/gfx/skia/skia/src/gpu/ops/GrStencilPathOp.cpp new file mode 100644 index 0000000000..1cfd8ff04a --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrStencilPathOp.cpp @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrStencilPathOp.h" + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetPriv.h" + +std::unique_ptr GrStencilPathOp::Make(GrRecordingContext* context, + const SkMatrix& viewMatrix, + bool useHWAA, + bool hasStencilClip, + const GrScissorState& scissor, + sk_sp path) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + return pool->allocate(viewMatrix, useHWAA, + hasStencilClip, scissor, std::move(path)); +} + +void GrStencilPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { + GrRenderTarget* rt = state->drawOpArgs().renderTarget(); + SkASSERT(rt); + + int numStencilBits = rt->renderTargetPriv().numStencilBits(); + GrStencilSettings stencil(GrPathRendering::GetStencilPassSettings(fPath->getFillType()), + fHasStencilClip, numStencilBits); + + GrPathRendering::StencilPathArgs args(fUseHWAA, state->drawOpArgs().proxy(), + &fViewMatrix, &fScissor, &stencil); + state->gpu()->pathRendering()->stencilPath(args, fPath.get()); +} diff --git a/gfx/skia/skia/src/gpu/ops/GrStencilPathOp.h b/gfx/skia/skia/src/gpu/ops/GrStencilPathOp.h new file mode 100644 index 0000000000..2d4f37fee3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrStencilPathOp.h @@ -0,0 +1,72 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStencilPathOp_DEFINED +#define GrStencilPathOp_DEFINED + +#include "src/gpu/GrPath.h" +#include "src/gpu/GrPathRendering.h" +#include "src/gpu/GrScissorState.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/ops/GrOp.h" + +class GrOpFlushState; +class GrRecordingContext; + +class GrStencilPathOp final : public GrOp { +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + const SkMatrix& viewMatrix, + bool useHWAA, + bool hasStencilClip, + const GrScissorState& scissor, + sk_sp path); + + const char* name() const override { return "StencilPathOp"; } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.printf("Path: 0x%p, AA: %d", fPath.get(), fUseHWAA); + string.append(INHERITED::dumpInfo()); + return string; + } +#endif + +private: + friend class GrOpMemoryPool; // for ctor + + GrStencilPathOp(const SkMatrix& viewMatrix, + bool useHWAA, + bool hasStencilClip, + const GrScissorState& scissor, + sk_sp path) + : INHERITED(ClassID()) + , fViewMatrix(viewMatrix) + , fUseHWAA(useHWAA) + , fHasStencilClip(hasStencilClip) + , fScissor(scissor) + , fPath(std::move(path)) { + this->setBounds(fPath->getBounds(), HasAABloat::kNo, IsHairline::kNo); + } + + void onPrepare(GrOpFlushState*) override {} + + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + SkMatrix fViewMatrix; + bool fUseHWAA; + bool fHasStencilClip; + GrScissorState fScissor; + sk_sp fPath; + + typedef GrOp INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.cpp b/gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.cpp new file mode 100644 index 0000000000..439a7c7dcc --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.cpp @@ -0,0 +1,830 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrStrokeRectOp.h" + +#include "include/core/SkStrokeRec.h" +#include "include/private/GrResourceKey.h" +#include "include/utils/SkRandom.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrVertexWriter.h" +#include "src/gpu/ops/GrFillRectOp.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +namespace { + +// We support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter +// limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only +// supported when using an AA stroke. +inline static bool allowed_stroke(const SkStrokeRec& stroke, GrAA aa, bool* isMiter) { + SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style || + stroke.getStyle() == SkStrokeRec::kHairline_Style); + // For hairlines, make bevel and round joins appear the same as mitered ones. + if (!stroke.getWidth()) { + *isMiter = true; + return true; + } + if (stroke.getJoin() == SkPaint::kBevel_Join) { + *isMiter = false; + return aa == GrAA::kYes; // bevel only supported with AA + } + if (stroke.getJoin() == SkPaint::kMiter_Join) { + *isMiter = stroke.getMiter() >= SK_ScalarSqrt2; + // Supported under non-AA only if it remains mitered + return aa == GrAA::kYes || *isMiter; + } + return false; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Non-AA Stroking +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/* create a triangle strip that strokes the specified rect. There are 8 + unique vertices, but we repeat the last 2 to close up. Alternatively we + could use an indices array, and then only send 8 verts, but not sure that + would be faster. + */ +static void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) { + const SkScalar rad = SkScalarHalf(width); + + verts[0].set(rect.fLeft + rad, rect.fTop + rad); + verts[1].set(rect.fLeft - rad, rect.fTop - rad); + verts[2].set(rect.fRight - rad, rect.fTop + rad); + verts[3].set(rect.fRight + rad, rect.fTop - rad); + verts[4].set(rect.fRight - rad, rect.fBottom - rad); + verts[5].set(rect.fRight + rad, rect.fBottom + rad); + verts[6].set(rect.fLeft + rad, rect.fBottom - rad); + verts[7].set(rect.fLeft - rad, rect.fBottom + rad); + verts[8] = verts[0]; + verts[9] = verts[1]; + + // TODO: we should be catching this higher up the call stack and just draw a single + // non-AA rect + if (2*rad >= rect.width()) { + verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX(); + } + if (2*rad >= rect.height()) { + verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY(); + } +} + +class NonAAStrokeRectOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + const char* name() const override { return "NonAAStrokeRectOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf( + "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " + "StrokeWidth: %.2f\n", + fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom, + fStrokeWidth); + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkStrokeRec& stroke, + GrAAType aaType) { + bool isMiter; + if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) { + return nullptr; + } + Helper::InputFlags inputFlags = Helper::InputFlags::kNone; + // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of + // hairline rects. We jam all the vertices to pixel centers to avoid this, but not + // when MSAA is enabled because it can cause ugly artifacts. + if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) { + inputFlags |= Helper::InputFlags::kSnapVerticesToPixelCenters; + } + return Helper::FactoryHelper(context, std::move(paint), inputFlags, + viewMatrix, rect, + stroke, aaType); + } + + NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + Helper::InputFlags inputFlags, const SkMatrix& viewMatrix, const SkRect& rect, + const SkStrokeRec& stroke, GrAAType aaType) + : INHERITED(ClassID()), fHelper(helperArgs, aaType, inputFlags) { + fColor = color; + fViewMatrix = viewMatrix; + fRect = rect; + // Sort the rect for hairlines + fRect.sort(); + fStrokeWidth = stroke.getWidth(); + + SkScalar rad = SkScalarHalf(fStrokeWidth); + SkRect bounds = rect; + bounds.outset(rad, rad); + + // If our caller snaps to pixel centers then we have to round out the bounds + if (inputFlags & Helper::InputFlags::kSnapVerticesToPixelCenters) { + SkASSERT(!fStrokeWidth || aaType == GrAAType::kNone); + viewMatrix.mapRect(&bounds); + // We want to be consistent with how we snap non-aa lines. To match what we do in + // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a + // pixel to force us to pixel centers. + bounds.setLTRB(SkScalarFloorToScalar(bounds.fLeft), + SkScalarFloorToScalar(bounds.fTop), + SkScalarFloorToScalar(bounds.fRight), + SkScalarFloorToScalar(bounds.fBottom)); + bounds.offset(0.5f, 0.5f); + this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo); + } else { + HasAABloat aaBloat = (aaType == GrAAType::kNone) ? HasAABloat ::kNo : HasAABloat::kYes; + this->setTransformedBounds(bounds, fViewMatrix, aaBloat, + fStrokeWidth ? IsHairline::kNo : IsHairline::kYes); + } + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + // This Op uses uniform (not vertex) color, so doesn't need to track wide color. + return fHelper.finalizeProcessors(caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kNone, &fColor, nullptr); + } + +private: + void onPrepareDraws(Target* target) override { + sk_sp gp; + { + using namespace GrDefaultGeoProcFactory; + Color color(fColor); + LocalCoords::Type localCoordsType = fHelper.usesLocalCoords() + ? LocalCoords::kUsePosition_Type + : LocalCoords::kUnused_Type; + gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color, + Coverage::kSolid_Type, localCoordsType, + fViewMatrix); + } + + size_t kVertexStride = gp->vertexStride(); + int vertexCount = kVertsPerHairlineRect; + if (fStrokeWidth > 0) { + vertexCount = kVertsPerStrokeRect; + } + + sk_sp vertexBuffer; + int firstVertex; + + void* verts = + target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex); + + if (!verts) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + SkPoint* vertex = reinterpret_cast(verts); + + GrPrimitiveType primType; + if (fStrokeWidth > 0) { + primType = GrPrimitiveType::kTriangleStrip; + init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth); + } else { + // hairline + primType = GrPrimitiveType::kLineStrip; + vertex[0].set(fRect.fLeft, fRect.fTop); + vertex[1].set(fRect.fRight, fRect.fTop); + vertex[2].set(fRect.fRight, fRect.fBottom); + vertex[3].set(fRect.fLeft, fRect.fBottom); + vertex[4].set(fRect.fLeft, fRect.fTop); + } + + GrMesh* mesh = target->allocMesh(primType); + mesh->setNonIndexedNonInstanced(vertexCount); + mesh->setVertexData(std::move(vertexBuffer), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + // TODO: override onCombineIfPossible + + Helper fHelper; + SkPMColor4f fColor; + SkMatrix fViewMatrix; + SkRect fRect; + SkScalar fStrokeWidth; + + const static int kVertsPerHairlineRect = 5; + const static int kVertsPerStrokeRect = 10; + + typedef GrMeshDrawOp INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// AA Stroking +/////////////////////////////////////////////////////////////////////////////////////////////////// + +GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey); +GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey); + +static void compute_aa_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside, + bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect, + SkScalar strokeWidth, bool miterStroke) { + SkRect devRect; + viewMatrix.mapRect(&devRect, rect); + + SkVector devStrokeSize; + if (strokeWidth > 0) { + devStrokeSize.set(strokeWidth, strokeWidth); + viewMatrix.mapVectors(&devStrokeSize, 1); + devStrokeSize.setAbs(devStrokeSize); + } else { + devStrokeSize.set(SK_Scalar1, SK_Scalar1); + } + + const SkScalar dx = devStrokeSize.fX; + const SkScalar dy = devStrokeSize.fY; + const SkScalar rx = SkScalarHalf(dx); + const SkScalar ry = SkScalarHalf(dy); + + *devOutside = devRect; + *devOutsideAssist = devRect; + *devInside = devRect; + + devOutside->outset(rx, ry); + devInside->inset(rx, ry); + + // If we have a degenerate stroking rect(ie the stroke is larger than inner rect) then we + // make a degenerate inside rect to avoid double hitting. We will also jam all of the points + // together when we render these rects. + SkScalar spare; + { + SkScalar w = devRect.width() - dx; + SkScalar h = devRect.height() - dy; + spare = SkTMin(w, h); + } + + *isDegenerate = spare <= 0; + if (*isDegenerate) { + devInside->fLeft = devInside->fRight = devRect.centerX(); + devInside->fTop = devInside->fBottom = devRect.centerY(); + } + + // For bevel-stroke, use 2 SkRect instances(devOutside and devOutsideAssist) + // to draw the outside of the octagon. Because there are 8 vertices on the outer + // edge, while vertex number of inner edge is 4, the same as miter-stroke. + if (!miterStroke) { + devOutside->inset(0, ry); + devOutsideAssist->outset(0, ry); + } +} + +static sk_sp create_aa_stroke_rect_gp(const GrShaderCaps* shaderCaps, + bool tweakAlphaForCoverage, + const SkMatrix& viewMatrix, + bool usesLocalCoords, + bool wideColor) { + using namespace GrDefaultGeoProcFactory; + + Coverage::Type coverageType = + tweakAlphaForCoverage ? Coverage::kSolid_Type : Coverage::kAttribute_Type; + LocalCoords::Type localCoordsType = + usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type; + Color::Type colorType = + wideColor ? Color::kPremulWideColorAttribute_Type: Color::kPremulGrColorAttribute_Type; + + return MakeForDeviceSpace(shaderCaps, colorType, coverageType, localCoordsType, viewMatrix); +} + +class AAStrokeRectOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelper; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& devOutside, + const SkRect& devInside) { + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, + devOutside, devInside); + } + + AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const SkRect& devOutside, const SkRect& devInside) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kCoverage) + , fViewMatrix(viewMatrix) { + SkASSERT(!devOutside.isEmpty()); + SkASSERT(!devInside.isEmpty()); + + fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, false}); + this->setBounds(devOutside, HasAABloat::kYes, IsHairline::kNo); + fMiterStroke = true; + } + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkStrokeRec& stroke) { + bool isMiter; + if (!allowed_stroke(stroke, GrAA::kYes, &isMiter)) { + return nullptr; + } + return Helper::FactoryHelper(context, std::move(paint), viewMatrix, rect, + stroke, isMiter); + } + + AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, + const SkMatrix& viewMatrix, const SkRect& rect, const SkStrokeRec& stroke, + bool isMiter) + : INHERITED(ClassID()) + , fHelper(helperArgs, GrAAType::kCoverage) + , fViewMatrix(viewMatrix) { + fMiterStroke = isMiter; + RectInfo& info = fRects.push_back(); + compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside, + &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter); + info.fColor = color; + if (isMiter) { + this->setBounds(info.fDevOutside, HasAABloat::kYes, IsHairline::kNo); + } else { + // The outer polygon of the bevel stroke is an octagon specified by the points of a + // pair of overlapping rectangles where one is wide and the other is narrow. + SkRect bounds = info.fDevOutside; + bounds.joinPossiblyEmptyRect(info.fDevOutsideAssist); + this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo); + } + } + + const char* name() const override { return "AAStrokeRect"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + for (const auto& info : fRects) { + string.appendf( + "Color: 0x%08x, ORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " + "AssistORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " + "IRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], Degen: %d", + info.fColor.toBytes_RGBA(), info.fDevOutside.fLeft, info.fDevOutside.fTop, + info.fDevOutside.fRight, info.fDevOutside.fBottom, info.fDevOutsideAssist.fLeft, + info.fDevOutsideAssist.fTop, info.fDevOutsideAssist.fRight, + info.fDevOutsideAssist.fBottom, info.fDevInside.fLeft, info.fDevInside.fTop, + info.fDevInside.fRight, info.fDevInside.fBottom, info.fDegenerate); + } + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, + GrProcessorAnalysisCoverage::kSingleChannel, &fRects.back().fColor, &fWideColor); + } + +private: + void onPrepareDraws(Target*) override; + void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; + + static const int kMiterIndexCnt = 3 * 24; + static const int kMiterVertexCnt = 16; + static const int kNumMiterRectsInIndexBuffer = 256; + + static const int kBevelIndexCnt = 48 + 36 + 24; + static const int kBevelVertexCnt = 24; + static const int kNumBevelRectsInIndexBuffer = 256; + + static sk_sp GetIndexBuffer(GrResourceProvider*, bool miterStroke); + + const SkMatrix& viewMatrix() const { return fViewMatrix; } + bool miterStroke() const { return fMiterStroke; } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override; + + void generateAAStrokeRectGeometry(GrVertexWriter& vertices, + const SkPMColor4f& color, + bool wideColor, + const SkRect& devOutside, + const SkRect& devOutsideAssist, + const SkRect& devInside, + bool miterStroke, + bool degenerate, + bool tweakAlphaForCoverage) const; + + // TODO support AA rotated stroke rects by copying around view matrices + struct RectInfo { + SkPMColor4f fColor; + SkRect fDevOutside; + SkRect fDevOutsideAssist; + SkRect fDevInside; + bool fDegenerate; + }; + + Helper fHelper; + SkSTArray<1, RectInfo, true> fRects; + SkMatrix fViewMatrix; + bool fMiterStroke; + bool fWideColor; + + typedef GrMeshDrawOp INHERITED; +}; + +void AAStrokeRectOp::onPrepareDraws(Target* target) { + sk_sp gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(), + fHelper.compatibleWithCoverageAsAlpha(), + this->viewMatrix(), + fHelper.usesLocalCoords(), + fWideColor)); + if (!gp) { + SkDebugf("Couldn't create GrGeometryProcessor\n"); + return; + } + + int innerVertexNum = 4; + int outerVertexNum = this->miterStroke() ? 4 : 8; + int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2; + int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt; + int instanceCount = fRects.count(); + + sk_sp indexBuffer = + GetIndexBuffer(target->resourceProvider(), this->miterStroke()); + if (!indexBuffer) { + SkDebugf("Could not allocate indices\n"); + return; + } + PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(), + std::move(indexBuffer), verticesPerInstance, indicesPerInstance, + instanceCount); + GrVertexWriter vertices{ helper.vertices() }; + if (!vertices.fPtr) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + for (int i = 0; i < instanceCount; i++) { + const RectInfo& info = fRects[i]; + this->generateAAStrokeRectGeometry(vertices, + info.fColor, + fWideColor, + info.fDevOutside, + info.fDevOutsideAssist, + info.fDevInside, + fMiterStroke, + info.fDegenerate, + fHelper.compatibleWithCoverageAsAlpha()); + } + helper.recordDraw(target, std::move(gp)); +} + +void AAStrokeRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); +} + +sk_sp AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider, + bool miterStroke) { + if (miterStroke) { + // clang-format off + static const uint16_t gMiterIndices[] = { + 0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0, + 1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0, + 2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0, + 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0, + + 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4, + 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4, + 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4, + 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4, + + 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8, + 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8, + 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8, + 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8, + }; + // clang-format on + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gMiterIndices) == kMiterIndexCnt); + GR_DEFINE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey); + return resourceProvider->findOrCreatePatternedIndexBuffer( + gMiterIndices, kMiterIndexCnt, kNumMiterRectsInIndexBuffer, kMiterVertexCnt, + gMiterIndexBufferKey); + } else { + /** + * As in miter-stroke, index = a + b, and a is the current index, b is the shift + * from the first index. The index layout: + * outer AA line: 0~3, 4~7 + * outer edge: 8~11, 12~15 + * inner edge: 16~19 + * inner AA line: 20~23 + * Following comes a bevel-stroke rect and its indices: + * + * 4 7 + * ********************************* + * * ______________________________ * + * * / 12 15 \ * + * * / \ * + * 0 * |8 16_____________________19 11 | * 3 + * * | | | | * + * * | | **************** | | * + * * | | * 20 23 * | | * + * * | | * * | | * + * * | | * 21 22 * | | * + * * | | **************** | | * + * * | |____________________| | * + * 1 * |9 17 18 10| * 2 + * * \ / * + * * \13 __________________________14/ * + * * * + * ********************************** + * 5 6 + */ + // clang-format off + static const uint16_t gBevelIndices[] = { + // Draw outer AA, from outer AA line to outer edge, shift is 0. + 0 + 0, 1 + 0, 9 + 0, 9 + 0, 8 + 0, 0 + 0, + 1 + 0, 5 + 0, 13 + 0, 13 + 0, 9 + 0, 1 + 0, + 5 + 0, 6 + 0, 14 + 0, 14 + 0, 13 + 0, 5 + 0, + 6 + 0, 2 + 0, 10 + 0, 10 + 0, 14 + 0, 6 + 0, + 2 + 0, 3 + 0, 11 + 0, 11 + 0, 10 + 0, 2 + 0, + 3 + 0, 7 + 0, 15 + 0, 15 + 0, 11 + 0, 3 + 0, + 7 + 0, 4 + 0, 12 + 0, 12 + 0, 15 + 0, 7 + 0, + 4 + 0, 0 + 0, 8 + 0, 8 + 0, 12 + 0, 4 + 0, + + // Draw the stroke, from outer edge to inner edge, shift is 8. + 0 + 8, 1 + 8, 9 + 8, 9 + 8, 8 + 8, 0 + 8, + 1 + 8, 5 + 8, 9 + 8, + 5 + 8, 6 + 8, 10 + 8, 10 + 8, 9 + 8, 5 + 8, + 6 + 8, 2 + 8, 10 + 8, + 2 + 8, 3 + 8, 11 + 8, 11 + 8, 10 + 8, 2 + 8, + 3 + 8, 7 + 8, 11 + 8, + 7 + 8, 4 + 8, 8 + 8, 8 + 8, 11 + 8, 7 + 8, + 4 + 8, 0 + 8, 8 + 8, + + // Draw the inner AA, from inner edge to inner AA line, shift is 16. + 0 + 16, 1 + 16, 5 + 16, 5 + 16, 4 + 16, 0 + 16, + 1 + 16, 2 + 16, 6 + 16, 6 + 16, 5 + 16, 1 + 16, + 2 + 16, 3 + 16, 7 + 16, 7 + 16, 6 + 16, 2 + 16, + 3 + 16, 0 + 16, 4 + 16, 4 + 16, 7 + 16, 3 + 16, + }; + // clang-format on + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gBevelIndices) == kBevelIndexCnt); + + GR_DEFINE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey); + return resourceProvider->findOrCreatePatternedIndexBuffer( + gBevelIndices, kBevelIndexCnt, kNumBevelRectsInIndexBuffer, kBevelVertexCnt, + gBevelIndexBufferKey); + } +} + +GrOp::CombineResult AAStrokeRectOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) { + AAStrokeRectOp* that = t->cast(); + + if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { + return CombineResult::kCannotCombine; + } + + // TODO combine across miterstroke changes + if (this->miterStroke() != that->miterStroke()) { + return CombineResult::kCannotCombine; + } + + // We apply the viewmatrix to the rect points on the cpu. However, if the pipeline uses + // local coords then we won't be able to combine. TODO: Upload local coords as an attribute. + if (fHelper.usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return CombineResult::kCannotCombine; + } + + fRects.push_back_n(that->fRects.count(), that->fRects.begin()); + fWideColor |= that->fWideColor; + return CombineResult::kMerged; +} + +static void setup_scale(int* scale, SkScalar inset) { + if (inset < SK_ScalarHalf) { + *scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf)); + SkASSERT(*scale >= 0 && *scale <= 255); + } else { + *scale = 0xff; + } +} + +void AAStrokeRectOp::generateAAStrokeRectGeometry(GrVertexWriter& vertices, + const SkPMColor4f& color, + bool wideColor, + const SkRect& devOutside, + const SkRect& devOutsideAssist, + const SkRect& devInside, + bool miterStroke, + bool degenerate, + bool tweakAlphaForCoverage) const { + // We create vertices for four nested rectangles. There are two ramps from 0 to full + // coverage, one on the exterior of the stroke and the other on the interior. + + // TODO: this only really works if the X & Y margins are the same all around + // the rect (or if they are all >= 1.0). + SkScalar inset; + if (!degenerate) { + inset = SkMinScalar(SK_Scalar1, devOutside.fRight - devInside.fRight); + inset = SkMinScalar(inset, devInside.fLeft - devOutside.fLeft); + inset = SkMinScalar(inset, devInside.fTop - devOutside.fTop); + if (miterStroke) { + inset = SK_ScalarHalf * SkMinScalar(inset, devOutside.fBottom - devInside.fBottom); + } else { + inset = SK_ScalarHalf * + SkMinScalar(inset, devOutsideAssist.fBottom - devInside.fBottom); + } + SkASSERT(inset >= 0); + } else { + // TODO use real devRect here + inset = SkMinScalar(devOutside.width(), SK_Scalar1); + inset = SK_ScalarHalf * + SkMinScalar(inset, SkTMax(devOutside.height(), devOutsideAssist.height())); + } + + auto inset_fan = [](const SkRect& r, SkScalar dx, SkScalar dy) { + return GrVertexWriter::TriFanFromRect(r.makeInset(dx, dy)); + }; + + auto maybe_coverage = [tweakAlphaForCoverage](float coverage) { + return GrVertexWriter::If(!tweakAlphaForCoverage, coverage); + }; + + GrVertexColor outerColor(tweakAlphaForCoverage ? SK_PMColor4fTRANSPARENT : color, wideColor); + + // Outermost rect + vertices.writeQuad(inset_fan(devOutside, -SK_ScalarHalf, -SK_ScalarHalf), + outerColor, + maybe_coverage(0.0f)); + + if (!miterStroke) { + // Second outermost + vertices.writeQuad(inset_fan(devOutsideAssist, -SK_ScalarHalf, -SK_ScalarHalf), + outerColor, + maybe_coverage(0.0f)); + } + + // scale is the coverage for the the inner two rects. + int scale; + setup_scale(&scale, inset); + + float innerCoverage = GrNormalizeByteToFloat(scale); + SkPMColor4f scaledColor = color * innerCoverage; + GrVertexColor innerColor(tweakAlphaForCoverage ? scaledColor : color, wideColor); + + // Inner rect + vertices.writeQuad(inset_fan(devOutside, inset, inset), + innerColor, + maybe_coverage(innerCoverage)); + + if (!miterStroke) { + // Second inner + vertices.writeQuad(inset_fan(devOutsideAssist, inset, inset), + innerColor, + maybe_coverage(innerCoverage)); + } + + if (!degenerate) { + vertices.writeQuad(inset_fan(devInside, -inset, -inset), + innerColor, + maybe_coverage(innerCoverage)); + + // The innermost rect has 0 coverage... + vertices.writeQuad(inset_fan(devInside, SK_ScalarHalf, SK_ScalarHalf), + outerColor, + maybe_coverage(0.0f)); + } else { + // When the interior rect has become degenerate we smoosh to a single point + SkASSERT(devInside.fLeft == devInside.fRight && devInside.fTop == devInside.fBottom); + + vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside), + innerColor, + maybe_coverage(innerCoverage)); + + // ... unless we are degenerate, in which case we must apply the scaled coverage + vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside), + innerColor, + maybe_coverage(innerCoverage)); + } +} + +} // anonymous namespace + +namespace GrStrokeRectOp { + +std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkStrokeRec& stroke) { + if (aaType == GrAAType::kCoverage) { + // The AA op only supports axis-aligned rectangles + if (!viewMatrix.rectStaysRect()) { + return nullptr; + } + return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke); + } else { + return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType); + } +} + +std::unique_ptr MakeNested(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect rects[2]) { + SkASSERT(viewMatrix.rectStaysRect()); + SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty()); + + SkRect devOutside, devInside; + viewMatrix.mapRect(&devOutside, rects[0]); + viewMatrix.mapRect(&devInside, rects[1]); + if (devInside.isEmpty()) { + if (devOutside.isEmpty()) { + return nullptr; + } + return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, + GrQuadAAFlags::kAll, + GrQuad::MakeFromRect(rects[0], viewMatrix), + GrQuad(rects[0])); + } + + return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside, devInside); +} + +} // namespace GrStrokeRectOp + +#if GR_TEST_UTILS + +#include "src/gpu/GrDrawOpTest.h" + +GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) { + SkMatrix viewMatrix = GrTest::TestMatrix(random); + SkRect rect = GrTest::TestRect(random); + SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f; + SkPaint strokePaint; + strokePaint.setStrokeWidth(strokeWidth); + strokePaint.setStyle(SkPaint::kStroke_Style); + strokePaint.setStrokeJoin(SkPaint::kMiter_Join); + SkStrokeRec strokeRec(strokePaint); + GrAAType aaType = GrAAType::kNone; + if (numSamples > 1) { + aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone; + } + return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType); +} + +GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) { + bool miterStroke = random->nextBool(); + + // Create either a empty rect or a non-empty rect. + SkRect rect = + random->nextBool() ? SkRect::MakeXYWH(10, 10, 50, 40) : SkRect::MakeXYWH(6, 7, 0, 0); + SkScalar minDim = SkMinScalar(rect.width(), rect.height()); + SkScalar strokeWidth = random->nextUScalar1() * minDim; + + SkStrokeRec rec(SkStrokeRec::kFill_InitStyle); + rec.setStrokeStyle(strokeWidth); + rec.setStrokeParams(SkPaint::kButt_Cap, + miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f); + SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random); + return AAStrokeRectOp::Make(context, std::move(paint), matrix, rect, rec); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.h b/gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.h new file mode 100644 index 0000000000..6f319cc17d --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrStrokeRectOp.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStrokeRectOp_DEFINED +#define GrStrokeRectOp_DEFINED + +#include "include/private/GrTypesPriv.h" + +class GrDrawOp; +class GrPaint; +class GrRecordingContext; +class SkMatrix; +struct SkRect; +class SkStrokeRec; + +/** + * A set of factory functions for drawing stroked rectangles either coverage-antialiased, or + * non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp factories, + * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then + * the paint is unmodified and may still be used. + */ +namespace GrStrokeRectOp { + +std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + GrAAType aaType, + const SkMatrix& viewMatrix, + const SkRect& rect, + const SkStrokeRec& stroke); + +// rects[0] == outer rectangle, rects[1] == inner rectangle. Null return means there is nothing to +// draw rather than failure. The area between the rectangles will be filled by the paint, and it +// will be anti-aliased with coverage AA. viewMatrix.rectStaysRect() must be true. +std::unique_ptr MakeNested(GrRecordingContext* context, + GrPaint&& paint, + const SkMatrix& viewMatrix, + const SkRect rects[2]); + +} // namespace GrStrokeRectOp + +#endif // GrStrokeRectOp_DEFINED diff --git a/gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.cpp b/gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.cpp new file mode 100644 index 0000000000..8ec223d604 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.cpp @@ -0,0 +1,436 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/ops/GrTessellatingPathRenderer.h" +#include +#include "src/core/SkGeometry.h" +#include "src/gpu/GrAuditTrail.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrDefaultGeoProcFactory.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceCache.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/GrTessellator.h" +#include "src/gpu/geometry/GrPathUtils.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" + +#ifndef GR_AA_TESSELLATOR_MAX_VERB_COUNT +#define GR_AA_TESSELLATOR_MAX_VERB_COUNT 10 +#endif + +/* + * This path renderer tessellates the path into triangles using GrTessellator, uploads the + * triangles to a vertex buffer, and renders them with a single draw call. It can do screenspace + * antialiasing with a one-pixel coverage ramp. + */ +namespace { + +struct TessInfo { + SkScalar fTolerance; + int fCount; +}; + +// When the SkPathRef genID changes, invalidate a corresponding GrResource described by key. +class PathInvalidator : public SkPathRef::GenIDChangeListener { +public: + PathInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID) + : fMsg(key, contextUniqueID) {} + +private: + GrUniqueKeyInvalidatedMessage fMsg; + + void onChange() override { + SkMessageBus::Post(fMsg); + } +}; + +bool cache_match(GrGpuBuffer* vertexBuffer, SkScalar tol, int* actualCount) { + if (!vertexBuffer) { + return false; + } + const SkData* data = vertexBuffer->getUniqueKey().getCustomData(); + SkASSERT(data); + const TessInfo* info = static_cast(data->data()); + if (info->fTolerance == 0 || info->fTolerance < 3.0f * tol) { + *actualCount = info->fCount; + return true; + } + return false; +} + +class StaticVertexAllocator : public GrTessellator::VertexAllocator { +public: + StaticVertexAllocator(size_t stride, GrResourceProvider* resourceProvider, bool canMapVB) + : VertexAllocator(stride) + , fResourceProvider(resourceProvider) + , fCanMapVB(canMapVB) + , fVertices(nullptr) { + } + void* lock(int vertexCount) override { + size_t size = vertexCount * stride(); + fVertexBuffer = fResourceProvider->createBuffer(size, GrGpuBufferType::kVertex, + kStatic_GrAccessPattern); + if (!fVertexBuffer.get()) { + return nullptr; + } + if (fCanMapVB) { + fVertices = fVertexBuffer->map(); + } else { + fVertices = sk_malloc_throw(vertexCount * stride()); + } + return fVertices; + } + void unlock(int actualCount) override { + if (fCanMapVB) { + fVertexBuffer->unmap(); + } else { + fVertexBuffer->updateData(fVertices, actualCount * stride()); + sk_free(fVertices); + } + fVertices = nullptr; + } + sk_sp detachVertexBuffer() { return std::move(fVertexBuffer); } + +private: + sk_sp fVertexBuffer; + GrResourceProvider* fResourceProvider; + bool fCanMapVB; + void* fVertices; +}; + +class DynamicVertexAllocator : public GrTessellator::VertexAllocator { +public: + DynamicVertexAllocator(size_t stride, GrMeshDrawOp::Target* target) + : VertexAllocator(stride) + , fTarget(target) + , fVertexBuffer(nullptr) + , fVertices(nullptr) {} + void* lock(int vertexCount) override { + fVertexCount = vertexCount; + fVertices = fTarget->makeVertexSpace(stride(), vertexCount, &fVertexBuffer, &fFirstVertex); + return fVertices; + } + void unlock(int actualCount) override { + fTarget->putBackVertices(fVertexCount - actualCount, stride()); + fVertices = nullptr; + } + sk_sp detachVertexBuffer() const { return std::move(fVertexBuffer); } + int firstVertex() const { return fFirstVertex; } + +private: + GrMeshDrawOp::Target* fTarget; + sk_sp fVertexBuffer; + int fVertexCount; + int fFirstVertex; + void* fVertices; +}; + +} // namespace + +GrTessellatingPathRenderer::GrTessellatingPathRenderer() + : fMaxVerbCount(GR_AA_TESSELLATOR_MAX_VERB_COUNT) { +} + +GrPathRenderer::CanDrawPath +GrTessellatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { + // This path renderer can draw fill styles, and can do screenspace antialiasing via a + // one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex + // ones to simpler algorithms. We pass on paths that have styles, though they may come back + // around after applying the styling information to the geometry to create a filled path. + if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) { + return CanDrawPath::kNo; + } + switch (args.fAAType) { + case GrAAType::kNone: + case GrAAType::kMSAA: + // Prefer MSAA, if any antialiasing. In the non-analytic-AA case, We skip paths that + // don't have a key since the real advantage of this path renderer comes from caching + // the tessellated geometry. + if (!args.fShape->hasUnstyledKey()) { + return CanDrawPath::kNo; + } + break; + case GrAAType::kCoverage: + // Use analytic AA if we don't have MSAA. In this case, we do not cache, so we accept + // paths without keys. + SkPath path; + args.fShape->asPath(&path); + if (path.countVerbs() > fMaxVerbCount) { + return CanDrawPath::kNo; + } + break; + } + return CanDrawPath::kYes; +} + +namespace { + +class TessellatingPathOp final : public GrMeshDrawOp { +private: + using Helper = GrSimpleMeshDrawOpHelperWithStencil; + +public: + DEFINE_OP_CLASS_ID + + static std::unique_ptr Make(GrRecordingContext* context, + GrPaint&& paint, + const GrShape& shape, + const SkMatrix& viewMatrix, + SkIRect devClipBounds, + GrAAType aaType, + const GrUserStencilSettings* stencilSettings) { + return Helper::FactoryHelper(context, std::move(paint), shape, + viewMatrix, devClipBounds, + aaType, stencilSettings); + } + + const char* name() const override { return "TessellatingPathOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + fHelper.visitProxies(func); + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString string; + string.appendf("Color 0x%08x, aa: %d\n", fColor.toBytes_RGBA(), fAntiAlias); + string += fHelper.dumpInfo(); + string += INHERITED::dumpInfo(); + return string; + } +#endif + + TessellatingPathOp(Helper::MakeArgs helperArgs, + const SkPMColor4f& color, + const GrShape& shape, + const SkMatrix& viewMatrix, + const SkIRect& devClipBounds, + GrAAType aaType, + const GrUserStencilSettings* stencilSettings) + : INHERITED(ClassID()) + , fHelper(helperArgs, aaType, stencilSettings) + , fColor(color) + , fShape(shape) + , fViewMatrix(viewMatrix) + , fDevClipBounds(devClipBounds) + , fAntiAlias(GrAAType::kCoverage == aaType) { + SkRect devBounds; + viewMatrix.mapRect(&devBounds, shape.bounds()); + if (shape.inverseFilled()) { + // Because the clip bounds are used to add a contour for inverse fills, they must also + // include the path bounds. + devBounds.join(SkRect::Make(fDevClipBounds)); + } + this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo); + } + + FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, + GrClampType clampType) override { + GrProcessorAnalysisCoverage coverage = fAntiAlias + ? GrProcessorAnalysisCoverage::kSingleChannel + : GrProcessorAnalysisCoverage::kNone; + // This Op uses uniform (not vertex) color, so doesn't need to track wide color. + return fHelper.finalizeProcessors( + caps, clip, hasMixedSampledCoverage, clampType, coverage, &fColor, nullptr); + } + +private: + SkPath getPath() const { + SkASSERT(!fShape.style().applies()); + SkPath path; + fShape.asPath(&path); + return path; + } + + void draw(Target* target, sk_sp gp, size_t vertexStride) { + SkASSERT(!fAntiAlias); + GrResourceProvider* rp = target->resourceProvider(); + bool inverseFill = fShape.inverseFilled(); + // construct a cache key from the path's genID and the view matrix + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + static constexpr int kClipBoundsCnt = sizeof(fDevClipBounds) / sizeof(uint32_t); + int shapeKeyDataCnt = fShape.unstyledKeySize(); + SkASSERT(shapeKeyDataCnt >= 0); + GrUniqueKey::Builder builder(&key, kDomain, shapeKeyDataCnt + kClipBoundsCnt, "Path"); + fShape.writeUnstyledKey(&builder[0]); + // For inverse fills, the tessellation is dependent on clip bounds. + if (inverseFill) { + memcpy(&builder[shapeKeyDataCnt], &fDevClipBounds, sizeof(fDevClipBounds)); + } else { + memset(&builder[shapeKeyDataCnt], 0, sizeof(fDevClipBounds)); + } + builder.finish(); + sk_sp cachedVertexBuffer(rp->findByUniqueKey(key)); + int actualCount; + SkScalar tol = GrPathUtils::kDefaultTolerance; + tol = GrPathUtils::scaleToleranceToSrc(tol, fViewMatrix, fShape.bounds()); + if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) { + this->drawVertices(target, std::move(gp), std::move(cachedVertexBuffer), 0, + actualCount); + return; + } + + SkRect clipBounds = SkRect::Make(fDevClipBounds); + + SkMatrix vmi; + if (!fViewMatrix.invert(&vmi)) { + return; + } + vmi.mapRect(&clipBounds); + bool isLinear; + bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags(); + StaticVertexAllocator allocator(vertexStride, rp, canMapVB); + int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator, false, + &isLinear); + if (count == 0) { + return; + } + sk_sp vb = allocator.detachVertexBuffer(); + TessInfo info; + info.fTolerance = isLinear ? 0 : tol; + info.fCount = count; + fShape.addGenIDChangeListener(sk_make_sp(key, target->contextUniqueID())); + key.setCustomData(SkData::MakeWithCopy(&info, sizeof(info))); + rp->assignUniqueKeyToResource(key, vb.get()); + + this->drawVertices(target, std::move(gp), std::move(vb), 0, count); + } + + void drawAA(Target* target, sk_sp gp, size_t vertexStride) { + SkASSERT(fAntiAlias); + SkPath path = getPath(); + if (path.isEmpty()) { + return; + } + SkRect clipBounds = SkRect::Make(fDevClipBounds); + path.transform(fViewMatrix); + SkScalar tol = GrPathUtils::kDefaultTolerance; + bool isLinear; + DynamicVertexAllocator allocator(vertexStride, target); + int count = GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator, true, + &isLinear); + if (count == 0) { + return; + } + this->drawVertices(target, std::move(gp), allocator.detachVertexBuffer(), + allocator.firstVertex(), count); + } + + void onPrepareDraws(Target* target) override { + sk_sp gp; + { + using namespace GrDefaultGeoProcFactory; + + Color color(fColor); + LocalCoords::Type localCoordsType = fHelper.usesLocalCoords() + ? LocalCoords::kUsePosition_Type + : LocalCoords::kUnused_Type; + Coverage::Type coverageType; + if (fAntiAlias) { + if (fHelper.compatibleWithCoverageAsAlpha()) { + coverageType = Coverage::kAttributeTweakAlpha_Type; + } else { + coverageType = Coverage::kAttribute_Type; + } + } else { + coverageType = Coverage::kSolid_Type; + } + if (fAntiAlias) { + gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(target->caps().shaderCaps(), + color, coverageType, + localCoordsType, fViewMatrix); + } else { + gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), + color, coverageType, localCoordsType, + fViewMatrix); + } + } + if (!gp.get()) { + return; + } + size_t vertexStride = gp->vertexStride(); + if (fAntiAlias) { + this->drawAA(target, std::move(gp), vertexStride); + } else { + this->draw(target, std::move(gp), vertexStride); + } + } + + void drawVertices(Target* target, sk_sp gp, sk_sp vb, + int firstVertex, int count) { + GrMesh* mesh = target->allocMesh(TESSELLATOR_WIREFRAME ? GrPrimitiveType::kLines + : GrPrimitiveType::kTriangles); + mesh->setNonIndexedNonInstanced(count); + mesh->setVertexData(std::move(vb), firstVertex); + target->recordDraw(std::move(gp), mesh); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + fHelper.executeDrawsAndUploads(this, flushState, chainBounds); + } + + Helper fHelper; + SkPMColor4f fColor; + GrShape fShape; + SkMatrix fViewMatrix; + SkIRect fDevClipBounds; + bool fAntiAlias; + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +bool GrTessellatingPathRenderer::onDrawPath(const DrawPathArgs& args) { + GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), + "GrTessellatingPathRenderer::onDrawPath"); + SkIRect clipBoundsI; + args.fClip->getConservativeBounds(args.fRenderTargetContext->width(), + args.fRenderTargetContext->height(), + &clipBoundsI); + std::unique_ptr op = TessellatingPathOp::Make( + args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, clipBoundsI, + args.fAAType, args.fUserStencilSettings); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +GR_DRAW_OP_TEST_DEFINE(TesselatingPathOp) { + SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); + SkPath path = GrTest::TestPath(random); + SkIRect devClipBounds = SkIRect::MakeLTRB( + random->nextU(), random->nextU(), random->nextU(), random->nextU()); + devClipBounds.sort(); + static constexpr GrAAType kAATypes[] = {GrAAType::kNone, GrAAType::kMSAA, GrAAType::kCoverage}; + GrAAType aaType; + do { + aaType = kAATypes[random->nextULessThan(SK_ARRAY_COUNT(kAATypes))]; + } while(GrAAType::kMSAA == aaType && numSamples <= 1); + GrStyle style; + do { + GrTest::TestStyle(random, &style); + } while (!style.isSimpleFill()); + GrShape shape(path, style); + return TessellatingPathOp::Make(context, std::move(paint), shape, viewMatrix, devClipBounds, + aaType, GrGetRandomStencil(random, context)); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.h b/gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.h new file mode 100644 index 0000000000..2ebcd79a2f --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrTessellatingPathRenderer.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTessellatingPathRenderer_DEFINED +#define GrTessellatingPathRenderer_DEFINED + +#include "src/gpu/GrPathRenderer.h" + +/** + * Subclass that renders the path by converting to screen-space trapezoids plus + * extra 1-pixel geometry for AA. + */ +class GrTessellatingPathRenderer : public GrPathRenderer { +public: + GrTessellatingPathRenderer(); +#if GR_TEST_UTILS + void setMaxVerbCount(int maxVerbCount) { fMaxVerbCount = maxVerbCount; } +#endif + +private: + CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override; + + StencilSupport onGetStencilSupport(const GrShape&) const override { + return GrPathRenderer::kNoSupport_StencilSupport; + } + + bool onDrawPath(const DrawPathArgs&) override; + int fMaxVerbCount; + + typedef GrPathRenderer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrTextureOp.cpp b/gfx/skia/skia/src/gpu/ops/GrTextureOp.cpp new file mode 100644 index 0000000000..bd4673e26a --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrTextureOp.cpp @@ -0,0 +1,817 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +#include "include/core/SkPoint.h" +#include "include/core/SkPoint3.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "include/private/SkFloatingPoint.h" +#include "include/private/SkTo.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkMatrixPriv.h" +#include "src/core/SkRectPriv.h" +#include "src/gpu/GrAppliedClip.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDrawOpTest.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrMemoryPool.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/GrResourceProviderPriv.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrTextureDomain.h" +#include "src/gpu/effects/generated/GrSaturateProcessor.h" +#include "src/gpu/geometry/GrQuad.h" +#include "src/gpu/geometry/GrQuadBuffer.h" +#include "src/gpu/geometry/GrQuadUtils.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/ops/GrFillRectOp.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/ops/GrQuadPerEdgeAA.h" +#include "src/gpu/ops/GrTextureOp.h" + +namespace { + +using Domain = GrQuadPerEdgeAA::Domain; +using VertexSpec = GrQuadPerEdgeAA::VertexSpec; +using ColorType = GrQuadPerEdgeAA::ColorType; + +// Extracts lengths of vertical and horizontal edges of axis-aligned quad. "width" is the edge +// between v0 and v2 (or v1 and v3), "height" is the edge between v0 and v1 (or v2 and v3). +static SkSize axis_aligned_quad_size(const GrQuad& quad) { + SkASSERT(quad.quadType() == GrQuad::Type::kAxisAligned); + // Simplification of regular edge length equation, since it's axis aligned and can avoid sqrt + float dw = sk_float_abs(quad.x(2) - quad.x(0)) + sk_float_abs(quad.y(2) - quad.y(0)); + float dh = sk_float_abs(quad.x(1) - quad.x(0)) + sk_float_abs(quad.y(1) - quad.y(0)); + return {dw, dh}; +} + +static bool filter_has_effect(const GrQuad& srcQuad, const GrQuad& dstQuad) { + // If not axis-aligned in src or dst, then always say it has an effect + if (srcQuad.quadType() != GrQuad::Type::kAxisAligned || + dstQuad.quadType() != GrQuad::Type::kAxisAligned) { + return true; + } + + SkRect srcRect; + SkRect dstRect; + if (srcQuad.asRect(&srcRect) && dstQuad.asRect(&dstRect)) { + // Disable filtering when there is no scaling (width and height are the same), and the + // top-left corners have the same fraction (so src and dst snap to the pixel grid + // identically). + SkASSERT(srcRect.isSorted()); + return srcRect.width() != dstRect.width() || srcRect.height() != dstRect.height() || + SkScalarFraction(srcRect.fLeft) != SkScalarFraction(dstRect.fLeft) || + SkScalarFraction(srcRect.fTop) != SkScalarFraction(dstRect.fTop); + } else { + // Although the quads are axis-aligned, the local coordinate system is transformed such + // that fractionally-aligned sample centers will not align with the device coordinate system + // So disable filtering when edges are the same length and both srcQuad and dstQuad + // 0th vertex is integer aligned. + if (SkScalarIsInt(srcQuad.x(0)) && SkScalarIsInt(srcQuad.y(0)) && + SkScalarIsInt(dstQuad.x(0)) && SkScalarIsInt(dstQuad.y(0))) { + // Extract edge lengths + SkSize srcSize = axis_aligned_quad_size(srcQuad); + SkSize dstSize = axis_aligned_quad_size(dstQuad); + return srcSize.fWidth != dstSize.fWidth || srcSize.fHeight != dstSize.fHeight; + } else { + return true; + } + } +} + +// if normalizing the domain then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass +// 1, 1, and height. +static void compute_domain(Domain domain, GrSamplerState::Filter filter, GrSurfaceOrigin origin, + const SkRect& domainRect, float iw, float ih, float h, SkRect* out) { + static constexpr SkRect kLargeRect = {-100000, -100000, 1000000, 1000000}; + if (domain == Domain::kNo) { + // Either the quad has no domain constraint and is batched with a domain constrained op + // (in which case we want a domain that doesn't restrict normalized tex coords), or the + // entire op doesn't use the domain, in which case the returned value is ignored. + *out = kLargeRect; + return; + } + + auto ltrb = Sk4f::Load(&domainRect); + if (filter == GrSamplerState::Filter::kBilerp) { + auto rblt = SkNx_shuffle<2, 3, 0, 1>(ltrb); + auto whwh = (rblt - ltrb).abs(); + auto c = (rblt + ltrb) * 0.5f; + static const Sk4f kOffsets = {0.5f, 0.5f, -0.5f, -0.5f}; + ltrb = (whwh < 1.f).thenElse(c, ltrb + kOffsets); + } + ltrb *= Sk4f(iw, ih, iw, ih); + if (origin == kBottomLeft_GrSurfaceOrigin) { + static const Sk4f kMul = {1.f, -1.f, 1.f, -1.f}; + const Sk4f kAdd = {0.f, h, 0.f, h}; + ltrb = SkNx_shuffle<0, 3, 2, 1>(kMul * ltrb + kAdd); + } + + ltrb.store(out); +} + +// Normalizes logical src coords and corrects for origin +static void compute_src_quad(GrSurfaceOrigin origin, const GrQuad& srcQuad, + float iw, float ih, float h, GrQuad* out) { + // The src quad should not have any perspective + SkASSERT(!srcQuad.hasPerspective() && !out->hasPerspective()); + skvx::Vec<4, float> xs = srcQuad.x4f() * iw; + skvx::Vec<4, float> ys = srcQuad.y4f() * ih; + if (origin == kBottomLeft_GrSurfaceOrigin) { + ys = h - ys; + } + xs.store(out->xs()); + ys.store(out->ys()); + out->setQuadType(srcQuad.quadType()); +} + +/** + * Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a + * the texture by color. The blend with the destination is always src-over. The edges are non-AA. + */ +class TextureOp final : public GrMeshDrawOp { +public: + static std::unique_ptr Make(GrRecordingContext* context, + sk_sp proxy, + sk_sp textureXform, + GrSamplerState::Filter filter, + const SkPMColor4f& color, + GrTextureOp::Saturate saturate, + GrAAType aaType, + GrQuadAAFlags aaFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain) { + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + return pool->allocate(std::move(proxy), std::move(textureXform), filter, color, + saturate, aaType, aaFlags, deviceQuad, localQuad, domain); + } + static std::unique_ptr Make(GrRecordingContext* context, + const GrRenderTargetContext::TextureSetEntry set[], + int cnt, + GrSamplerState::Filter filter, + GrTextureOp::Saturate saturate, + GrAAType aaType, + SkCanvas::SrcRectConstraint constraint, + const SkMatrix& viewMatrix, + sk_sp textureColorSpaceXform) { + size_t size = sizeof(TextureOp) + sizeof(Proxy) * (cnt - 1); + GrOpMemoryPool* pool = context->priv().opMemoryPool(); + void* mem = pool->allocate(size); + return std::unique_ptr(new (mem) TextureOp(set, cnt, filter, saturate, aaType, + constraint, viewMatrix, + std::move(textureColorSpaceXform))); + } + + ~TextureOp() override { + for (unsigned p = 0; p < fProxyCnt; ++p) { + fProxies[p].fProxy->unref(); + } + } + + const char* name() const override { return "TextureOp"; } + + void visitProxies(const VisitProxyFunc& func) const override { + for (unsigned p = 0; p < fProxyCnt; ++p) { + bool mipped = (GrSamplerState::Filter::kMipMap == this->filter()); + func(fProxies[p].fProxy, GrMipMapped(mipped)); + } + } + +#ifdef SK_DEBUG + SkString dumpInfo() const override { + SkString str; + str.appendf("# draws: %d\n", fQuads.count()); + auto iter = fQuads.iterator(); + for (unsigned p = 0; p < fProxyCnt; ++p) { + str.appendf("Proxy ID: %d, Filter: %d\n", fProxies[p].fProxy->uniqueID().asUInt(), + static_cast(fFilter)); + int i = 0; + while(i < fProxies[p].fQuadCnt && iter.next()) { + const GrQuad& quad = iter.deviceQuad(); + const GrQuad& uv = iter.localQuad(); + const ColorDomainAndAA& info = iter.metadata(); + str.appendf( + "%d: Color: 0x%08x, Domain(%d): [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n" + " UVs [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n" + " Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n", + i, info.fColor.toBytes_RGBA(), info.fHasDomain, info.fDomainRect.fLeft, + info.fDomainRect.fTop, info.fDomainRect.fRight, info.fDomainRect.fBottom, + quad.point(0).fX, quad.point(0).fY, quad.point(1).fX, quad.point(1).fY, + quad.point(2).fX, quad.point(2).fY, quad.point(3).fX, quad.point(3).fY, + uv.point(0).fX, uv.point(0).fY, uv.point(1).fX, uv.point(1).fY, + uv.point(2).fX, uv.point(2).fY, uv.point(3).fX, uv.point(3).fY); + + i++; + } + } + str += INHERITED::dumpInfo(); + return str; + } +#endif + + GrProcessorSet::Analysis finalize( + const GrCaps& caps, const GrAppliedClip*, bool hasMixedSampledCoverage, + GrClampType clampType) override { + fColorType = static_cast(ColorType::kNone); + auto iter = fQuads.metadata(); + while(iter.next()) { + auto colorType = GrQuadPerEdgeAA::MinColorType(iter->fColor, clampType, caps); + fColorType = SkTMax(fColorType, static_cast(colorType)); + } + return GrProcessorSet::EmptySetAnalysis(); + } + + FixedFunctionFlags fixedFunctionFlags() const override { + return this->aaType() == GrAAType::kMSAA ? FixedFunctionFlags::kUsesHWAA + : FixedFunctionFlags::kNone; + } + + DEFINE_OP_CLASS_ID + +private: + friend class ::GrOpMemoryPool; + + struct ColorDomainAndAA { + ColorDomainAndAA(const SkPMColor4f& color, const SkRect* domainRect, GrQuadAAFlags aaFlags) + : fColor(color) + , fDomainRect(domainRect ? *domainRect : SkRect::MakeEmpty()) + , fHasDomain(static_cast(domainRect ? Domain::kYes : Domain::kNo)) + , fAAFlags(static_cast(aaFlags)) { + SkASSERT(fAAFlags == static_cast(aaFlags)); + } + + SkPMColor4f fColor; + SkRect fDomainRect; + unsigned fHasDomain : 1; + unsigned fAAFlags : 4; + + Domain domain() const { return Domain(fHasDomain); } + GrQuadAAFlags aaFlags() const { return static_cast(fAAFlags); } + }; + struct Proxy { + GrTextureProxy* fProxy; + int fQuadCnt; + }; + + // dstQuad should be the geometry transformed by the view matrix. If domainRect + // is not null it will be used to apply the strict src rect constraint. + TextureOp(sk_sp proxy, + sk_sp textureColorSpaceXform, + GrSamplerState::Filter filter, + const SkPMColor4f& color, + GrTextureOp::Saturate saturate, + GrAAType aaType, + GrQuadAAFlags aaFlags, + const GrQuad& dstQuad, + const GrQuad& srcQuad, + const SkRect* domainRect) + : INHERITED(ClassID()) + , fQuads(1, true /* includes locals */) + , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) + , fSaturate(static_cast(saturate)) + , fFilter(static_cast(filter)) + , fPrePrepared(false) { + // Clean up disparities between the overall aa type and edge configuration and apply + // optimizations based on the rect and matrix when appropriate + GrQuadUtils::ResolveAAType(aaType, aaFlags, dstQuad, &aaType, &aaFlags); + fAAType = static_cast(aaType); + + // We expect our caller to have already caught this optimization. + SkASSERT(!domainRect || !domainRect->contains(proxy->getWorstCaseBoundsRect())); + + // We may have had a strict constraint with nearest filter solely due to possible AA bloat. + // If we don't have (or determined we don't need) coverage AA then we can skip using a + // domain. + if (domainRect && this->filter() == GrSamplerState::Filter::kNearest && + aaType != GrAAType::kCoverage) { + domainRect = nullptr; + } + + fQuads.append(dstQuad, {color, domainRect, aaFlags}, &srcQuad); + + fProxyCnt = 1; + fProxies[0] = {proxy.release(), 1}; + this->setBounds(dstQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage), + IsHairline::kNo); + fDomain = static_cast(domainRect != nullptr); + } + TextureOp(const GrRenderTargetContext::TextureSetEntry set[], + int cnt, + GrSamplerState::Filter filter, + GrTextureOp::Saturate saturate, + GrAAType aaType, + SkCanvas::SrcRectConstraint constraint, + const SkMatrix& viewMatrix, + sk_sp textureColorSpaceXform) + : INHERITED(ClassID()) + , fQuads(cnt, true /* includes locals */) + , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) + , fSaturate(static_cast(saturate)) + , fFilter(static_cast(filter)) + , fPrePrepared(false) { + fProxyCnt = SkToUInt(cnt); + SkRect bounds = SkRectPriv::MakeLargestInverted(); + GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects + bool mustFilter = false; + bool allOpaque = true; + Domain netDomain = Domain::kNo; + for (unsigned p = 0; p < fProxyCnt; ++p) { + fProxies[p].fProxy = SkRef(set[p].fProxy.get()); + fProxies[p].fQuadCnt = 1; + SkASSERT(fProxies[p].fProxy->textureType() == fProxies[0].fProxy->textureType()); + SkASSERT(fProxies[p].fProxy->config() == fProxies[0].fProxy->config()); + + SkMatrix ctm = viewMatrix; + if (set[p].fPreViewMatrix) { + ctm.preConcat(*set[p].fPreViewMatrix); + } + + // Use dstRect/srcRect unless dstClip is provided, in which case derive new source + // coordinates by mapping dstClipQuad by the dstRect to srcRect transform. + GrQuad quad, srcQuad; + if (set[p].fDstClipQuad) { + quad = GrQuad::MakeFromSkQuad(set[p].fDstClipQuad, ctm); + + SkPoint srcPts[4]; + GrMapRectPoints(set[p].fDstRect, set[p].fSrcRect, set[p].fDstClipQuad, srcPts, 4); + srcQuad = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I()); + } else { + quad = GrQuad::MakeFromRect(set[p].fDstRect, ctm); + srcQuad = GrQuad(set[p].fSrcRect); + } + + if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) { + mustFilter = filter_has_effect(srcQuad, quad); + } + + bounds.joinPossiblyEmptyRect(quad.bounds()); + GrQuadAAFlags aaFlags; + // Don't update the overall aaType, might be inappropriate for some of the quads + GrAAType aaForQuad; + GrQuadUtils::ResolveAAType(aaType, set[p].fAAFlags, quad, &aaForQuad, &aaFlags); + // Resolve sets aaForQuad to aaType or None, there is never a change between aa methods + SkASSERT(aaForQuad == GrAAType::kNone || aaForQuad == aaType); + if (overallAAType == GrAAType::kNone && aaForQuad != GrAAType::kNone) { + overallAAType = aaType; + } + + // Calculate metadata for the entry + const SkRect* domainForQuad = nullptr; + if (constraint == SkCanvas::kStrict_SrcRectConstraint) { + // Check (briefly) if the strict constraint is needed for this set entry + if (!set[p].fSrcRect.contains(fProxies[p].fProxy->getWorstCaseBoundsRect()) && + (mustFilter || aaForQuad == GrAAType::kCoverage)) { + // Can't rely on hardware clamping and the draw will access outer texels + // for AA and/or bilerp + netDomain = Domain::kYes; + domainForQuad = &set[p].fSrcRect; + } + } + float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f); + allOpaque &= (1.f == alpha); + SkPMColor4f color{alpha, alpha, alpha, alpha}; + fQuads.append(quad, {color, domainForQuad, aaFlags}, &srcQuad); + } + fAAType = static_cast(overallAAType); + if (!mustFilter) { + fFilter = static_cast(GrSamplerState::Filter::kNearest); + } + this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), + IsHairline::kNo); + fDomain = static_cast(netDomain); + } + + void tess(void* v, const VertexSpec& spec, const GrTextureProxy* proxy, + GrQuadBuffer::Iter* iter, int cnt) const { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + auto origin = proxy->origin(); + const auto* texture = proxy->peekTexture(); + float iw, ih, h; + if (proxy->textureType() == GrTextureType::kRectangle) { + iw = ih = 1.f; + h = texture->height(); + } else { + iw = 1.f / texture->width(); + ih = 1.f / texture->height(); + h = 1.f; + } + + int i = 0; + // Explicit ctor ensures ws are 1s, which compute_src_quad requires + GrQuad srcQuad(SkRect::MakeEmpty()); + SkRect domain; + while(i < cnt && iter->next()) { + SkASSERT(iter->isLocalValid()); + const ColorDomainAndAA& info = iter->metadata(); + // Must correct the texture coordinates and domain now that the real texture size + // is known + compute_src_quad(origin, iter->localQuad(), iw, ih, h, &srcQuad); + compute_domain(info.domain(), this->filter(), origin, info.fDomainRect, iw, ih, h, + &domain); + v = GrQuadPerEdgeAA::Tessellate(v, spec, iter->deviceQuad(), info.fColor, srcQuad, + domain, info.aaFlags()); + i++; + } + } + + void onPrePrepareDraws(GrRecordingContext* context) override { + SkASSERT(!fPrePrepared); + // Pull forward the tessellation of the quads to here + + //GrOpMemoryPool* pool = context->priv().opMemoryPool(); + + fPrePrepared = true; + } + +#ifdef SK_DEBUG + void validate() const override { + auto textureType = fProxies[0].fProxy->textureType(); + const GrSwizzle& swizzle = fProxies[0].fProxy->textureSwizzle(); + GrAAType aaType = this->aaType(); + + for (const auto& op : ChainRange(this)) { + for (unsigned p = 0; p < op.fProxyCnt; ++p) { + auto* proxy = op.fProxies[p].fProxy; + SkASSERT(proxy); + SkASSERT(proxy->textureType() == textureType); + SkASSERT(proxy->textureSwizzle() == swizzle); + } + + // Each individual op must be a single aaType. kCoverage and kNone ops can chain + // together but kMSAA ones do not. + if (aaType == GrAAType::kCoverage || aaType == GrAAType::kNone) { + SkASSERT(op.aaType() == GrAAType::kCoverage || op.aaType() == GrAAType::kNone); + } else { + SkASSERT(aaType == GrAAType::kMSAA && op.aaType() == GrAAType::kMSAA); + } + } + } +#endif + + VertexSpec characterize(int* numProxies, int* numTotalQuads) const { + GrQuad::Type quadType = GrQuad::Type::kAxisAligned; + ColorType colorType = ColorType::kNone; + GrQuad::Type srcQuadType = GrQuad::Type::kAxisAligned; + Domain domain = Domain::kNo; + GrAAType overallAAType = this->aaType(); + + *numProxies = 0; + *numTotalQuads = 0; + + for (const auto& op : ChainRange(this)) { + if (op.fQuads.deviceQuadType() > quadType) { + quadType = op.fQuads.deviceQuadType(); + } + if (op.fQuads.localQuadType() > srcQuadType) { + srcQuadType = op.fQuads.localQuadType(); + } + if (op.fDomain) { + domain = Domain::kYes; + } + colorType = SkTMax(colorType, static_cast(op.fColorType)); + *numProxies += op.fProxyCnt; + for (unsigned p = 0; p < op.fProxyCnt; ++p) { + *numTotalQuads += op.fProxies[p].fQuadCnt; + } + if (op.aaType() == GrAAType::kCoverage) { + overallAAType = GrAAType::kCoverage; + } + } + + return VertexSpec(quadType, colorType, srcQuadType, /* hasLocal */ true, domain, + overallAAType, /* alpha as coverage */ true); + } + + // onPrePrepareDraws may or may not have been called at this point + void onPrepareDraws(Target* target) override { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + + SkDEBUGCODE(this->validate();) + + int numProxies, numTotalQuads; + + const VertexSpec vertexSpec = this->characterize(&numProxies, &numTotalQuads); + + // We'll use a dynamic state array for the GP textures when there are multiple ops. + // Otherwise, we use fixed dynamic state to specify the single op's proxy. + GrPipeline::DynamicStateArrays* dynamicStateArrays = nullptr; + GrPipeline::FixedDynamicState* fixedDynamicState; + if (numProxies > 1) { + dynamicStateArrays = target->allocDynamicStateArrays(numProxies, 1, false); + fixedDynamicState = target->makeFixedDynamicState(0); + } else { + fixedDynamicState = target->makeFixedDynamicState(1); + fixedDynamicState->fPrimitiveProcessorTextures[0] = fProxies[0].fProxy; + } + + size_t vertexSize = vertexSpec.vertexSize(); + + GrMesh* meshes = target->allocMeshes(numProxies); + sk_sp vbuffer; + int vertexOffsetInBuffer = 0; + int numQuadVerticesLeft = numTotalQuads * vertexSpec.verticesPerQuad(); + int numAllocatedVertices = 0; + void* vdata = nullptr; + + int m = 0; + for (const auto& op : ChainRange(this)) { + auto iter = op.fQuads.iterator(); + for (unsigned p = 0; p < op.fProxyCnt; ++p) { + int quadCnt = op.fProxies[p].fQuadCnt; + auto* proxy = op.fProxies[p].fProxy; + int meshVertexCnt = quadCnt * vertexSpec.verticesPerQuad(); + if (numAllocatedVertices < meshVertexCnt) { + vdata = target->makeVertexSpaceAtLeast( + vertexSize, meshVertexCnt, numQuadVerticesLeft, &vbuffer, + &vertexOffsetInBuffer, &numAllocatedVertices); + SkASSERT(numAllocatedVertices <= numQuadVerticesLeft); + if (!vdata) { + SkDebugf("Could not allocate vertices\n"); + return; + } + } + SkASSERT(numAllocatedVertices >= meshVertexCnt); + + op.tess(vdata, vertexSpec, proxy, &iter, quadCnt); + + if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, &(meshes[m]), vertexSpec, + quadCnt)) { + SkDebugf("Could not allocate indices"); + return; + } + meshes[m].setVertexData(vbuffer, vertexOffsetInBuffer); + if (dynamicStateArrays) { + dynamicStateArrays->fPrimitiveProcessorTextures[m] = proxy; + } + ++m; + numAllocatedVertices -= meshVertexCnt; + numQuadVerticesLeft -= meshVertexCnt; + vertexOffsetInBuffer += meshVertexCnt; + vdata = reinterpret_cast(vdata) + vertexSize * meshVertexCnt; + } + // If quad counts per proxy were calculated correctly, the entire iterator should have + // been consumed. + SkASSERT(!iter.next()); + } + SkASSERT(!numQuadVerticesLeft); + SkASSERT(!numAllocatedVertices); + + sk_sp gp; + + { + auto textureType = fProxies[0].fProxy->textureType(); + const GrSwizzle& swizzle = fProxies[0].fProxy->textureSwizzle(); + + GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp, + this->filter()); + + auto saturate = static_cast(fSaturate); + + GrGpu* gpu = target->resourceProvider()->priv().gpu(); + uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram( + samplerState, fProxies[0].fProxy->backendFormat()); + + gp = GrQuadPerEdgeAA::MakeTexturedProcessor( + vertexSpec, *target->caps().shaderCaps(), textureType, samplerState, swizzle, + extraSamplerKey, std::move(fTextureColorSpaceXform), saturate); + + SkASSERT(vertexSize == gp->vertexStride()); + } + + target->recordDraw( + std::move(gp), meshes, numProxies, fixedDynamicState, dynamicStateArrays); + } + + void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { + auto pipelineFlags = (GrAAType::kMSAA == this->aaType()) + ? GrPipeline::InputFlags::kHWAntialias + : GrPipeline::InputFlags::kNone; + flushState->executeDrawsAndUploadsForMeshDrawOp( + this, chainBounds, GrProcessorSet::MakeEmptySet(), pipelineFlags); + } + + CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + const auto* that = t->cast(); + + if (fPrePrepared || that->fPrePrepared) { + // This should never happen (since only DDL recorded ops should be prePrepared) + // but, in any case, we should never combine ops that that been prePrepared + return CombineResult::kCannotCombine; + } + + if (fDomain != that->fDomain) { + // It is technically possible to combine operations across domain modes, but performance + // testing suggests it's better to make more draw calls where some take advantage of + // the more optimal shader path without coordinate clamping. + return CombineResult::kCannotCombine; + } + if (!GrColorSpaceXform::Equals(fTextureColorSpaceXform.get(), + that->fTextureColorSpaceXform.get())) { + return CombineResult::kCannotCombine; + } + bool upgradeToCoverageAAOnMerge = false; + if (this->aaType() != that->aaType()) { + if (!((this->aaType() == GrAAType::kCoverage && that->aaType() == GrAAType::kNone) || + (that->aaType() == GrAAType::kCoverage && this->aaType() == GrAAType::kNone))) { + return CombineResult::kCannotCombine; + } + upgradeToCoverageAAOnMerge = true; + } + if (fSaturate != that->fSaturate) { + return CombineResult::kCannotCombine; + } + if (fFilter != that->fFilter) { + return CombineResult::kCannotCombine; + } + auto thisProxy = fProxies[0].fProxy; + auto thatProxy = that->fProxies[0].fProxy; + if (fProxyCnt > 1 || that->fProxyCnt > 1 || + thisProxy->uniqueID() != thatProxy->uniqueID()) { + // We can't merge across different proxies. Check if 'this' can be chained with 'that'. + if (GrTextureProxy::ProxiesAreCompatibleAsDynamicState(thisProxy, thatProxy) && + caps.dynamicStateArrayGeometryProcessorTextureSupport()) { + return CombineResult::kMayChain; + } + return CombineResult::kCannotCombine; + } + + fDomain |= that->fDomain; + fColorType = SkTMax(fColorType, that->fColorType); + if (upgradeToCoverageAAOnMerge) { + fAAType = static_cast(GrAAType::kCoverage); + } + + // Concatenate quad lists together + fQuads.concat(that->fQuads); + fProxies[0].fQuadCnt += that->fQuads.count(); + + return CombineResult::kMerged; + } + + GrAAType aaType() const { return static_cast(fAAType); } + GrSamplerState::Filter filter() const { return static_cast(fFilter); } + + GrQuadBuffer fQuads; + sk_sp fTextureColorSpaceXform; + unsigned fSaturate : 1; + unsigned fFilter : 2; + unsigned fAAType : 2; + unsigned fDomain : 1; + unsigned fColorType : 2; + GR_STATIC_ASSERT(GrQuadPerEdgeAA::kColorTypeCount <= 4); + unsigned fPrePrepared : 1; + unsigned fProxyCnt : 32 - 7; + Proxy fProxies[1]; + + static_assert(GrQuad::kTypeCount <= 4, "GrQuad::Type does not fit in 2 bits"); + + typedef GrMeshDrawOp INHERITED; +}; + +} // anonymous namespace + +namespace GrTextureOp { + +std::unique_ptr Make(GrRecordingContext* context, + sk_sp proxy, + GrColorType srcColorType, + sk_sp textureXform, + GrSamplerState::Filter filter, + const SkPMColor4f& color, + Saturate saturate, + SkBlendMode blendMode, + GrAAType aaType, + GrQuadAAFlags aaFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain) { + // Apply optimizations that are valid whether or not using GrTextureOp or GrFillRectOp + if (domain && domain->contains(proxy->getWorstCaseBoundsRect())) { + // No need for a shader-based domain if hardware clamping achieves the same effect + domain = nullptr; + } + + if (filter != GrSamplerState::Filter::kNearest && !filter_has_effect(localQuad, deviceQuad)) { + filter = GrSamplerState::Filter::kNearest; + } + + if (blendMode == SkBlendMode::kSrcOver) { + return TextureOp::Make(context, std::move(proxy), std::move(textureXform), filter, color, + saturate, aaType, aaFlags, deviceQuad, localQuad, domain); + } else { + // Emulate complex blending using GrFillRectOp + GrPaint paint; + paint.setColor4f(color); + paint.setXPFactory(SkBlendMode_AsXPFactory(blendMode)); + + std::unique_ptr fp; + if (domain) { + // Update domain to match what GrTextureOp computes during tessellation, using top-left + // as the origin so that it doesn't depend on final texture size (which the FP handles + // later, as well as accounting for the true origin). + SkRect correctedDomain; + compute_domain(Domain::kYes, filter, kTopLeft_GrSurfaceOrigin, *domain, + 1.f, 1.f, proxy->height(), &correctedDomain); + fp = GrTextureDomainEffect::Make(std::move(proxy), srcColorType, SkMatrix::I(), + correctedDomain, GrTextureDomain::kClamp_Mode, filter); + } else { + fp = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, SkMatrix::I(), filter); + } + fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(textureXform)); + paint.addColorFragmentProcessor(std::move(fp)); + if (saturate == GrTextureOp::Saturate::kYes) { + paint.addColorFragmentProcessor(GrSaturateProcessor::Make()); + } + + return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, + deviceQuad, localQuad); + } +} + +std::unique_ptr MakeSet(GrRecordingContext* context, + const GrRenderTargetContext::TextureSetEntry set[], + int cnt, + GrSamplerState::Filter filter, + Saturate saturate, + GrAAType aaType, + SkCanvas::SrcRectConstraint constraint, + const SkMatrix& viewMatrix, + sk_sp textureColorSpaceXform) { + return TextureOp::Make(context, set, cnt, filter, saturate, aaType, constraint, viewMatrix, + std::move(textureColorSpaceXform)); +} + +} // namespace GrTextureOp + +#if GR_TEST_UTILS +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" + +GR_DRAW_OP_TEST_DEFINE(TextureOp) { + GrSurfaceDesc desc; + desc.fConfig = kRGBA_8888_GrPixelConfig; + desc.fHeight = random->nextULessThan(90) + 10; + desc.fWidth = random->nextULessThan(90) + 10; + auto origin = random->nextBool() ? kTopLeft_GrSurfaceOrigin : kBottomLeft_GrSurfaceOrigin; + GrMipMapped mipMapped = random->nextBool() ? GrMipMapped::kYes : GrMipMapped::kNo; + SkBackingFit fit = SkBackingFit::kExact; + if (mipMapped == GrMipMapped::kNo) { + fit = random->nextBool() ? SkBackingFit::kApprox : SkBackingFit::kExact; + } + const GrBackendFormat format = + context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, + GrRenderable::kNo); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + sk_sp proxy = proxyProvider->createProxy( + format, desc, GrRenderable::kNo, 1, origin, mipMapped, fit, SkBudgeted::kNo, + GrProtected::kNo, GrInternalSurfaceFlags::kNone); + + SkRect rect = GrTest::TestRect(random); + SkRect srcRect; + srcRect.fLeft = random->nextRangeScalar(0.f, proxy->width() / 2.f); + srcRect.fRight = random->nextRangeScalar(0.f, proxy->width()) + proxy->width() / 2.f; + srcRect.fTop = random->nextRangeScalar(0.f, proxy->height() / 2.f); + srcRect.fBottom = random->nextRangeScalar(0.f, proxy->height()) + proxy->height() / 2.f; + SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); + SkPMColor4f color = SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU())); + GrSamplerState::Filter filter = (GrSamplerState::Filter)random->nextULessThan( + static_cast(GrSamplerState::Filter::kMipMap) + 1); + while (mipMapped == GrMipMapped::kNo && filter == GrSamplerState::Filter::kMipMap) { + filter = (GrSamplerState::Filter)random->nextULessThan( + static_cast(GrSamplerState::Filter::kMipMap) + 1); + } + auto texXform = GrTest::TestColorXform(random); + GrAAType aaType = GrAAType::kNone; + if (random->nextBool()) { + aaType = (numSamples > 1) ? GrAAType::kMSAA : GrAAType::kCoverage; + } + GrQuadAAFlags aaFlags = GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone; + aaFlags |= random->nextBool() ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone; + bool useDomain = random->nextBool(); + auto saturate = random->nextBool() ? GrTextureOp::Saturate::kYes : GrTextureOp::Saturate::kNo; + return GrTextureOp::Make(context, std::move(proxy), GrColorType::kRGBA_8888, + std::move(texXform), filter, color, saturate, SkBlendMode::kSrcOver, + aaType, aaFlags, GrQuad::MakeFromRect(rect, viewMatrix), + GrQuad(srcRect), useDomain ? &srcRect : nullptr); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/ops/GrTextureOp.h b/gfx/skia/skia/src/gpu/ops/GrTextureOp.h new file mode 100644 index 0000000000..da7a6b4eb1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/ops/GrTextureOp.h @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef GrTextureOp_DEFINED +#define GrTextureOp_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkRefCnt.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSamplerState.h" + +class GrColorSpaceXform; +class GrDrawOp; +class GrTextureProxy; +struct SkRect; +class SkMatrix; + +namespace GrTextureOp { + +/** + * Controls whether saturate() is called after the texture is color-converted to ensure all + * color values are in 0..1 range. + */ +enum class Saturate : bool { kNo = false, kYes = true }; + +/** + * Creates an op that draws a sub-quadrilateral of a texture. The passed color is modulated by the + * texture's color. 'deviceQuad' specifies the device-space coordinates to draw, using 'localQuad' + * to map into the proxy's texture space. If non-null, 'domain' represents the boundary for the + * strict src rect constraint. If GrAAType is kCoverage then AA is applied to the edges + * indicated by GrQuadAAFlags. Otherwise, GrQuadAAFlags is ignored. + * + * This is functionally very similar to GrFillRectOp::Make, except that the GrPaint has been + * deconstructed into the texture, filter, modulating color, and blend mode. When blend mode is + * src over, this will return a GrFillRectOp with a paint that samples the proxy. + */ +std::unique_ptr Make(GrRecordingContext*, + sk_sp, + GrColorType srcColorType, + sk_sp, + GrSamplerState::Filter, + const SkPMColor4f&, + Saturate, + SkBlendMode, + GrAAType, + GrQuadAAFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain = nullptr); + +// Unlike the single-proxy factory, this only supports src-over blending. +std::unique_ptr MakeSet(GrRecordingContext*, + const GrRenderTargetContext::TextureSetEntry[], + int cnt, + GrSamplerState::Filter, + Saturate, + GrAAType, + SkCanvas::SrcRectConstraint, + const SkMatrix& viewMatrix, + sk_sp textureXform); + +} +#endif // GrTextureOp_DEFINED diff --git a/gfx/skia/skia/src/gpu/text/GrAtlasManager.cpp b/gfx/skia/skia/src/gpu/text/GrAtlasManager.cpp new file mode 100644 index 0000000000..29e32165e4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrAtlasManager.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/text/GrAtlasManager.h" + +#include "src/gpu/GrGlyph.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/text/GrStrikeCache.h" + +GrAtlasManager::GrAtlasManager(GrProxyProvider* proxyProvider, GrStrikeCache* glyphCache, + size_t maxTextureBytes, + GrDrawOpAtlas::AllowMultitexturing allowMultitexturing) + : fAllowMultitexturing{allowMultitexturing} + , fProxyProvider{proxyProvider} + , fCaps{fProxyProvider->refCaps()} + , fGlyphCache{glyphCache} + , fAtlasConfig{fCaps->maxTextureSize(), maxTextureBytes} { } + +GrAtlasManager::~GrAtlasManager() = default; + +static GrColorType mask_format_to_gr_color_type(GrMaskFormat format) { + switch (format) { + case kA8_GrMaskFormat: + return GrColorType::kAlpha_8; + case kA565_GrMaskFormat: + return GrColorType::kBGR_565; + case kARGB_GrMaskFormat: + return GrColorType::kRGBA_8888; + default: + SkDEBUGFAIL("unsupported GrMaskFormat"); + return GrColorType::kAlpha_8; + } +} + +void GrAtlasManager::freeAll() { + for (int i = 0; i < kMaskFormatCount; ++i) { + fAtlases[i] = nullptr; + } +} + +bool GrAtlasManager::hasGlyph(GrGlyph* glyph) { + SkASSERT(glyph); + return this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID); +} + +// add to texture atlas that matches this format +GrDrawOpAtlas::ErrorCode GrAtlasManager::addToAtlas( + GrResourceProvider* resourceProvider, + GrStrikeCache* glyphCache, + GrTextStrike* strike, GrDrawOpAtlas::AtlasID* id, + GrDeferredUploadTarget* target, GrMaskFormat format, + int width, int height, const void* image, SkIPoint16* loc) { + glyphCache->setStrikeToPreserve(strike); + return this->getAtlas(format)->addToAtlas(resourceProvider, id, target, width, height, + image, loc); +} + +void GrAtlasManager::addGlyphToBulkAndSetUseToken(GrDrawOpAtlas::BulkUseTokenUpdater* updater, + GrGlyph* glyph, + GrDeferredUploadToken token) { + SkASSERT(glyph); + if (updater->add(glyph->fID)) { + this->getAtlas(glyph->fMaskFormat)->setLastUseToken(glyph->fID, token); + } +} + +#ifdef SK_DEBUG +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrSurfaceContext.h" +#include "src/gpu/GrSurfaceProxy.h" +#include "src/gpu/GrTextureProxy.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkImageEncoder.h" +#include "include/core/SkStream.h" +#include + +/** + * Write the contents of the surface proxy to a PNG. Returns true if successful. + * @param filename Full path to desired file + */ +static bool save_pixels(GrContext* context, GrSurfaceProxy* sProxy, GrColorType colorType, + const char* filename) { + if (!sProxy) { + return false; + } + + SkImageInfo ii = SkImageInfo::Make(sProxy->width(), sProxy->height(), + kRGBA_8888_SkColorType, kPremul_SkAlphaType); + SkBitmap bm; + if (!bm.tryAllocPixels(ii)) { + return false; + } + + auto sContext = context->priv().makeWrappedSurfaceContext(sk_ref_sp(sProxy), colorType, + kUnknown_SkAlphaType); + if (!sContext || !sContext->asTextureProxy()) { + return false; + } + + bool result = sContext->readPixels(ii, bm.getPixels(), bm.rowBytes(), {0, 0}); + if (!result) { + SkDebugf("------ failed to read pixels for %s\n", filename); + return false; + } + + // remove any previous version of this file + remove(filename); + + SkFILEWStream file(filename); + if (!file.isValid()) { + SkDebugf("------ failed to create file: %s\n", filename); + remove(filename); // remove any partial file + return false; + } + + if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) { + SkDebugf("------ failed to encode %s\n", filename); + remove(filename); // remove any partial file + return false; + } + + return true; +} + +void GrAtlasManager::dump(GrContext* context) const { + static int gDumpCount = 0; + for (int i = 0; i < kMaskFormatCount; ++i) { + if (fAtlases[i]) { + const sk_sp* proxies = fAtlases[i]->getProxies(); + for (uint32_t pageIdx = 0; pageIdx < fAtlases[i]->numActivePages(); ++pageIdx) { + SkASSERT(proxies[pageIdx]); + SkString filename; +#ifdef SK_BUILD_FOR_ANDROID + filename.printf("/sdcard/fontcache_%d%d%d.png", gDumpCount, i, pageIdx); +#else + filename.printf("fontcache_%d%d%d.png", gDumpCount, i, pageIdx); +#endif + auto ct = mask_format_to_gr_color_type(AtlasIndexToMaskFormat(i)); + save_pixels(context, proxies[pageIdx].get(), ct, filename.c_str()); + } + } + } + ++gDumpCount; +} +#endif + +void GrAtlasManager::setAtlasSizesToMinimum_ForTesting() { + // Delete any old atlases. + // This should be safe to do as long as we are not in the middle of a flush. + for (int i = 0; i < kMaskFormatCount; i++) { + fAtlases[i] = nullptr; + } + + // Set all the atlas sizes to 1x1 plot each. + new (&fAtlasConfig) GrDrawOpAtlasConfig{}; +} + +bool GrAtlasManager::initAtlas(GrMaskFormat format) { + int index = MaskFormatToAtlasIndex(format); + if (fAtlases[index] == nullptr) { + GrColorType grColorType = mask_format_to_gr_color_type(format); + SkISize atlasDimensions = fAtlasConfig.atlasDimensions(format); + SkISize plotDimensions = fAtlasConfig.plotDimensions(format); + + const GrBackendFormat format = fCaps->getDefaultBackendFormat(grColorType, + GrRenderable::kNo); + + fAtlases[index] = GrDrawOpAtlas::Make( + fProxyProvider, format, grColorType, + atlasDimensions.width(), atlasDimensions.height(), + plotDimensions.width(), plotDimensions.height(), + fAllowMultitexturing, &GrStrikeCache::HandleEviction, fGlyphCache); + if (!fAtlases[index]) { + return false; + } + } + return true; +} diff --git a/gfx/skia/skia/src/gpu/text/GrAtlasManager.h b/gfx/skia/skia/src/gpu/text/GrAtlasManager.h new file mode 100644 index 0000000000..1799d8d6b1 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrAtlasManager.h @@ -0,0 +1,145 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAtlasManager_DEFINED +#define GrAtlasManager_DEFINED + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/GrOnFlushResourceProvider.h" +#include "src/gpu/GrProxyProvider.h" + +struct GrGlyph; +class GrTextStrike; + +////////////////////////////////////////////////////////////////////////////////////////////////// +/** The GrAtlasManager manages the lifetime of and access to GrDrawOpAtlases. + * It is only available at flush and only via the GrOpFlushState. + * + * This implies that all of the advanced atlasManager functionality (i.e., + * adding glyphs to the atlas) are only available at flush time. + */ +class GrAtlasManager : public GrOnFlushCallbackObject { +public: + GrAtlasManager(GrProxyProvider*, GrStrikeCache*, + size_t maxTextureBytes, GrDrawOpAtlas::AllowMultitexturing); + ~GrAtlasManager() override; + + // Change an expected 565 mask format to 8888 if 565 is not supported (will happen when using + // Metal on macOS). The actual conversion of the data is handled in get_packed_glyph_image() in + // GrStrikeCache.cpp + GrMaskFormat resolveMaskFormat(GrMaskFormat format) const { + if (kA565_GrMaskFormat == format && + !fProxyProvider->caps()->getDefaultBackendFormat(GrColorType::kBGR_565, + GrRenderable::kNo).isValid()) { + format = kARGB_GrMaskFormat; + } + return format; + } + + // if getProxies returns nullptr, the client must not try to use other functions on the + // GrStrikeCache which use the atlas. This function *must* be called first, before other + // functions which use the atlas. Note that we can have proxies available but none active + // (i.e., none instantiated). + const sk_sp* getProxies(GrMaskFormat format, unsigned int* numActiveProxies) { + format = this->resolveMaskFormat(format); + if (this->initAtlas(format)) { + *numActiveProxies = this->getAtlas(format)->numActivePages(); + return this->getAtlas(format)->getProxies(); + } + *numActiveProxies = 0; + return nullptr; + } + + void freeAll(); + + bool hasGlyph(GrGlyph* glyph); + + // To ensure the GrDrawOpAtlas does not evict the Glyph Mask from its texture backing store, + // the client must pass in the current op token along with the GrGlyph. + // A BulkUseTokenUpdater is used to manage bulk last use token updating in the Atlas. + // For convenience, this function will also set the use token for the current glyph if required + // NOTE: the bulk uploader is only valid if the subrun has a valid atlasGeneration + void addGlyphToBulkAndSetUseToken(GrDrawOpAtlas::BulkUseTokenUpdater*, GrGlyph*, + GrDeferredUploadToken); + + void setUseTokenBulk(const GrDrawOpAtlas::BulkUseTokenUpdater& updater, + GrDeferredUploadToken token, + GrMaskFormat format) { + this->getAtlas(format)->setLastUseTokenBulk(updater, token); + } + + // add to texture atlas that matches this format + GrDrawOpAtlas::ErrorCode addToAtlas( + GrResourceProvider*, GrStrikeCache*, GrTextStrike*, + GrDrawOpAtlas::AtlasID*, GrDeferredUploadTarget*, GrMaskFormat, + int width, int height, const void* image, SkIPoint16* loc); + + // Some clients may wish to verify the integrity of the texture backing store of the + // GrDrawOpAtlas. The atlasGeneration returned below is a monotonically increasing number which + // changes every time something is removed from the texture backing store. + uint64_t atlasGeneration(GrMaskFormat format) const { + return this->getAtlas(format)->atlasGeneration(); + } + + // GrOnFlushCallbackObject overrides + + void preFlush(GrOnFlushResourceProvider* onFlushRP, const uint32_t*, int) override { + for (int i = 0; i < kMaskFormatCount; ++i) { + if (fAtlases[i]) { + fAtlases[i]->instantiate(onFlushRP); + } + } + } + + void postFlush(GrDeferredUploadToken startTokenForNextFlush, + const uint32_t* opsTaskIDs, int numOpsTaskIDs) override { + for (int i = 0; i < kMaskFormatCount; ++i) { + if (fAtlases[i]) { + fAtlases[i]->compact(startTokenForNextFlush); + } + } + } + + // The AtlasGlyph cache always survives freeGpuResources so we want it to remain in the active + // OnFlushCallbackObject list + bool retainOnFreeGpuResources() override { return true; } + + /////////////////////////////////////////////////////////////////////////// + // Functions intended debug only +#ifdef SK_DEBUG + void dump(GrContext* context) const; +#endif + + void setAtlasSizesToMinimum_ForTesting(); + void setMaxPages_TestingOnly(uint32_t maxPages); + +private: + bool initAtlas(GrMaskFormat); + + // There is a 1:1 mapping between GrMaskFormats and atlas indices + static int MaskFormatToAtlasIndex(GrMaskFormat format) { return static_cast(format); } + static GrMaskFormat AtlasIndexToMaskFormat(int idx) { return static_cast(idx); } + + GrDrawOpAtlas* getAtlas(GrMaskFormat format) const { + format = this->resolveMaskFormat(format); + int atlasIndex = MaskFormatToAtlasIndex(format); + SkASSERT(fAtlases[atlasIndex]); + return fAtlases[atlasIndex].get(); + } + + GrDrawOpAtlas::AllowMultitexturing fAllowMultitexturing; + std::unique_ptr fAtlases[kMaskFormatCount]; + GrProxyProvider* fProxyProvider; + sk_sp fCaps; + GrStrikeCache* fGlyphCache; + GrDrawOpAtlasConfig fAtlasConfig; + + typedef GrOnFlushCallbackObject INHERITED; +}; + +#endif // GrAtlasManager_DEFINED diff --git a/gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.cpp b/gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.cpp new file mode 100644 index 0000000000..a760d6ef6e --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/text/GrDistanceFieldAdjustTable.h" + +#include "src/core/SkScalerContext.h" + +SkDEBUGCODE(static const int kExpectedDistanceAdjustTableSize = 8;) + +SkScalar* build_distance_adjust_table(SkScalar paintGamma, SkScalar deviceGamma) { + // This is used for an approximation of the mask gamma hack, used by raster and bitmap + // text. The mask gamma hack is based off of guessing what the blend color is going to + // be, and adjusting the mask so that when run through the linear blend will + // produce the value closest to the desired result. However, in practice this means + // that the 'adjusted' mask is just increasing or decreasing the coverage of + // the mask depending on what it is thought it will blit against. For black (on + // assumed white) this means that coverages are decreased (on a curve). For white (on + // assumed black) this means that coverages are increased (on a a curve). At + // middle (perceptual) gray (which could be blit against anything) the coverages + // remain the same. + // + // The idea here is that instead of determining the initial (real) coverage and + // then adjusting that coverage, we determine an adjusted coverage directly by + // essentially manipulating the geometry (in this case, the distance to the glyph + // edge). So for black (on assumed white) this thins a bit; for white (on + // assumed black) this fake bolds the geometry a bit. + // + // The distance adjustment is calculated by determining the actual coverage value which + // when fed into in the mask gamma table gives us an 'adjusted coverage' value of 0.5. This + // actual coverage value (assuming it's between 0 and 1) corresponds to a distance from the + // actual edge. So by subtracting this distance adjustment and computing without the + // the coverage adjustment we should get 0.5 coverage at the same point. + // + // This has several implications: + // For non-gray lcd smoothed text, each subpixel essentially is using a + // slightly different geometry. + // + // For black (on assumed white) this may not cover some pixels which were + // previously covered; however those pixels would have been only slightly + // covered and that slight coverage would have been decreased anyway. Also, some pixels + // which were previously fully covered may no longer be fully covered. + // + // For white (on assumed black) this may cover some pixels which weren't + // previously covered at all. + + int width, height; + size_t size; + +#ifdef SK_GAMMA_CONTRAST + SkScalar contrast = SK_GAMMA_CONTRAST; +#else + SkScalar contrast = 0.5f; +#endif + + size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma, + &width, &height); + + SkASSERT(kExpectedDistanceAdjustTableSize == height); + SkScalar* table = new SkScalar[height]; + + SkAutoTArray data((int)size); + if (!SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get())) { + // if no valid data is available simply do no adjustment + for (int row = 0; row < height; ++row) { + table[row] = 0; + } + return table; + } + + // find the inverse points where we cross 0.5 + // binsearch might be better, but we only need to do this once on creation + for (int row = 0; row < height; ++row) { + uint8_t* rowPtr = data.get() + row*width; + for (int col = 0; col < width - 1; ++col) { + if (rowPtr[col] <= 127 && rowPtr[col + 1] >= 128) { + // compute point where a mask value will give us a result of 0.5 + float interp = (127.5f - rowPtr[col]) / (rowPtr[col + 1] - rowPtr[col]); + float borderAlpha = (col + interp) / 255.f; + + // compute t value for that alpha + // this is an approximate inverse for smoothstep() + float t = borderAlpha*(borderAlpha*(4.0f*borderAlpha - 6.0f) + 5.0f) / 3.0f; + + // compute distance which gives us that t value + const float kDistanceFieldAAFactor = 0.65f; // should match SK_DistanceFieldAAFactor + float d = 2.0f*kDistanceFieldAAFactor*t - kDistanceFieldAAFactor; + + table[row] = d; + break; + } + } + } + + return table; +} + +void GrDistanceFieldAdjustTable::buildDistanceAdjustTables() { + fTable = build_distance_adjust_table(SK_GAMMA_EXPONENT, SK_GAMMA_EXPONENT); + fGammaCorrectTable = build_distance_adjust_table(SK_Scalar1, SK_Scalar1); +} diff --git a/gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.h b/gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.h new file mode 100644 index 0000000000..3b217b9780 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrDistanceFieldAdjustTable.h @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrDistanceFieldAdjustTable_DEFINED +#define GrDistanceFieldAdjustTable_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" + +// Distance field text needs this table to compute a value for use in the fragment shader. +// Because the GrTextContext can go out of scope before the final flush, this needs to be +// refcnted and malloced +struct GrDistanceFieldAdjustTable : public SkNVRefCnt { + GrDistanceFieldAdjustTable() { this->buildDistanceAdjustTables(); } + ~GrDistanceFieldAdjustTable() { + delete[] fTable; + delete[] fGammaCorrectTable; + } + + const SkScalar& getAdjustment(int i, bool useGammaCorrectTable) const { + return useGammaCorrectTable ? fGammaCorrectTable[i] : fTable[i]; + } + +private: + void buildDistanceAdjustTables(); + + SkScalar* fTable; + SkScalar* fGammaCorrectTable; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.cpp b/gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.cpp new file mode 100644 index 0000000000..7bcaed3fac --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkSafeMath.h" +#include "src/core/SkWriteBuffer.h" +#include "src/gpu/text/GrSDFMaskFilter.h" + +class GrSDFMaskFilterImpl : public SkMaskFilterBase { +public: + GrSDFMaskFilterImpl(); + + // overrides from SkMaskFilterBase + // This method is not exported to java. + SkMask::Format getFormat() const override; + // This method is not exported to java. + bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, + SkIPoint* margin) const override; + + void computeFastBounds(const SkRect&, SkRect*) const override; + +protected: + +private: + SK_FLATTENABLE_HOOKS(GrSDFMaskFilterImpl) + + typedef SkMaskFilter INHERITED; + friend void gr_register_sdf_maskfilter_createproc(); +}; + +/////////////////////////////////////////////////////////////////////////////// + +GrSDFMaskFilterImpl::GrSDFMaskFilterImpl() {} + +SkMask::Format GrSDFMaskFilterImpl::getFormat() const { + return SkMask::kSDF_Format; +} + +bool GrSDFMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, + const SkMatrix& matrix, SkIPoint* margin) const { + if (src.fFormat != SkMask::kA8_Format + && src.fFormat != SkMask::kBW_Format + && src.fFormat != SkMask::kLCD16_Format) { + return false; + } + + *dst = SkMask::PrepareDestination(SK_DistanceFieldPad, SK_DistanceFieldPad, src); + dst->fFormat = SkMask::kSDF_Format; + + if (margin) { + margin->set(SK_DistanceFieldPad, SK_DistanceFieldPad); + } + + if (src.fImage == nullptr) { + return true; + } + if (dst->fImage == nullptr) { + dst->fBounds.setEmpty(); + return false; + } + + if (src.fFormat == SkMask::kA8_Format) { + return SkGenerateDistanceFieldFromA8Image(dst->fImage, src.fImage, + src.fBounds.width(), src.fBounds.height(), + src.fRowBytes); + } else if (src.fFormat == SkMask::kLCD16_Format) { + return SkGenerateDistanceFieldFromLCD16Mask(dst->fImage, src.fImage, + src.fBounds.width(), src.fBounds.height(), + src.fRowBytes); + } else { + return SkGenerateDistanceFieldFromBWImage(dst->fImage, src.fImage, + src.fBounds.width(), src.fBounds.height(), + src.fRowBytes); + } +} + +void GrSDFMaskFilterImpl::computeFastBounds(const SkRect& src, + SkRect* dst) const { + dst->setLTRB(src.fLeft - SK_DistanceFieldPad, src.fTop - SK_DistanceFieldPad, + src.fRight + SK_DistanceFieldPad, src.fBottom + SK_DistanceFieldPad); +} + +sk_sp GrSDFMaskFilterImpl::CreateProc(SkReadBuffer& buffer) { + return GrSDFMaskFilter::Make(); +} + +void gr_register_sdf_maskfilter_createproc() { SK_REGISTER_FLATTENABLE(GrSDFMaskFilterImpl); } + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp GrSDFMaskFilter::Make() { + return sk_sp(new GrSDFMaskFilterImpl()); +} diff --git a/gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.h b/gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.h new file mode 100644 index 0000000000..6dfbaf062b --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrSDFMaskFilter.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrSDFMaskFilter_DEFINED +#define GrSDFMaskFilter_DEFINED + +#include "include/core/SkMaskFilter.h" + +/** \class GrSDFMaskFilter + + This mask filter converts an alpha mask to a signed distance field representation +*/ +class GrSDFMaskFilter : public SkMaskFilter { +public: + static sk_sp Make(); +}; + +extern void gr_register_sdf_maskfilter_createproc(); + +#endif diff --git a/gfx/skia/skia/src/gpu/text/GrStrikeCache.cpp b/gfx/skia/skia/src/gpu/text/GrStrikeCache.cpp new file mode 100644 index 0000000000..f445c65986 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrStrikeCache.cpp @@ -0,0 +1,244 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrDistanceFieldGenFromVector.h" +#include "src/gpu/text/GrAtlasManager.h" +#include "src/gpu/text/GrStrikeCache.h" + +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkDistanceFieldGen.h" + +GrStrikeCache::GrStrikeCache(const GrCaps* caps, size_t maxTextureBytes) + : fPreserveStrike(nullptr) + , f565Masks(SkMasks::CreateMasks({0xF800, 0x07E0, 0x001F, 0}, + GrMaskFormatBytesPerPixel(kA565_GrMaskFormat))) { } + +GrStrikeCache::~GrStrikeCache() { + StrikeHash::Iter iter(&fCache); + while (!iter.done()) { + (*iter).fIsAbandoned = true; + (*iter).unref(); + ++iter; + } +} + +void GrStrikeCache::freeAll() { + StrikeHash::Iter iter(&fCache); + while (!iter.done()) { + (*iter).fIsAbandoned = true; + (*iter).unref(); + ++iter; + } + fCache.rewind(); +} + +void GrStrikeCache::HandleEviction(GrDrawOpAtlas::AtlasID id, void* ptr) { + GrStrikeCache* grStrikeCache = reinterpret_cast(ptr); + + StrikeHash::Iter iter(&grStrikeCache->fCache); + for (; !iter.done(); ++iter) { + GrTextStrike* strike = &*iter; + strike->removeID(id); + + // clear out any empty strikes. We will preserve the strike whose call to addToAtlas + // triggered the eviction + if (strike != grStrikeCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) { + grStrikeCache->fCache.remove(GrTextStrike::GetKey(*strike)); + strike->fIsAbandoned = true; + strike->unref(); + } + } +} + +// expands each bit in a bitmask to 0 or ~0 of type INT_TYPE. Used to expand a BW glyph mask to +// A8, RGB565, or RGBA8888. +template +static void expand_bits(INT_TYPE* dst, + const uint8_t* src, + int width, + int height, + int dstRowBytes, + int srcRowBytes) { + for (int i = 0; i < height; ++i) { + int rowWritesLeft = width; + const uint8_t* s = src; + INT_TYPE* d = dst; + while (rowWritesLeft > 0) { + unsigned mask = *s++; + for (int i = 7; i >= 0 && rowWritesLeft; --i, --rowWritesLeft) { + *d++ = (mask & (1 << i)) ? (INT_TYPE)(~0UL) : 0; + } + } + dst = reinterpret_cast(reinterpret_cast(dst) + dstRowBytes); + src += srcRowBytes; + } +} + +static bool get_packed_glyph_image(SkStrike* cache, SkGlyph* glyph, int width, + int height, int dstRB, GrMaskFormat expectedMaskFormat, + void* dst, const SkMasks& masks) { + SkASSERT(glyph->width() == width); + SkASSERT(glyph->height() == height); + const void* src = cache->prepareImage(glyph); + if (src == nullptr) { + return false; + } + + // Convert if the glyph uses a 565 mask format since it is using LCD text rendering but the + // expected format is 8888 (will happen on macOS with Metal since that combination does not + // support 565). + if (kA565_GrMaskFormat == GrGlyph::FormatFromSkGlyph(glyph->maskFormat()) && + kARGB_GrMaskFormat == expectedMaskFormat) { + const int a565Bpp = GrMaskFormatBytesPerPixel(kA565_GrMaskFormat); + const int argbBpp = GrMaskFormatBytesPerPixel(kARGB_GrMaskFormat); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uint16_t color565 = 0; + memcpy(&color565, src, a565Bpp); + uint32_t colorRGBA = GrColorPackRGBA(masks.getRed(color565), + masks.getGreen(color565), + masks.getBlue(color565), + 0xFF); + memcpy(dst, &colorRGBA, argbBpp); + src = (char*)src + a565Bpp; + dst = (char*)dst + argbBpp; + } + } + return true; + } + + // crbug:510931 + // Retrieving the image from the cache can actually change the mask format. This case is very + // uncommon so for now we just draw a clear box for these glyphs. + if (GrGlyph::FormatFromSkGlyph(glyph->maskFormat()) != expectedMaskFormat) { + const int bpp = GrMaskFormatBytesPerPixel(expectedMaskFormat); + for (int y = 0; y < height; y++) { + sk_bzero(dst, width * bpp); + dst = (char*)dst + dstRB; + } + return true; + } + + int srcRB = glyph->rowBytes(); + // The windows font host sometimes has BW glyphs in a non-BW strike. So it is important here to + // check the glyph's format, not the strike's format, and to be able to convert to any of the + // GrMaskFormats. + if (glyph->maskFormat() == SkMask::kBW_Format) { + // expand bits to our mask type + const uint8_t* bits = reinterpret_cast(src); + switch (expectedMaskFormat) { + case kA8_GrMaskFormat:{ + uint8_t* bytes = reinterpret_cast(dst); + expand_bits(bytes, bits, width, height, dstRB, srcRB); + break; + } + case kA565_GrMaskFormat: { + uint16_t* rgb565 = reinterpret_cast(dst); + expand_bits(rgb565, bits, width, height, dstRB, srcRB); + break; + } + default: + SK_ABORT("Invalid GrMaskFormat"); + } + } else if (srcRB == dstRB) { + memcpy(dst, src, dstRB * height); + } else { + const int bbp = GrMaskFormatBytesPerPixel(expectedMaskFormat); + for (int y = 0; y < height; y++) { + memcpy(dst, src, width * bbp); + src = (const char*)src + srcRB; + dst = (char*)dst + dstRB; + } + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +/* + The text strike is specific to a given font/style/matrix setup, which is + represented by the GrHostFontScaler object we are given in getGlyph(). + + We map a 32bit glyphID to a GrGlyph record, which in turn points to a + atlas and a position within that texture. + */ + +GrTextStrike::GrTextStrike(const SkDescriptor& key) + : fFontScalerKey(key) {} + +void GrTextStrike::removeID(GrDrawOpAtlas::AtlasID id) { + SkTDynamicHash::Iter iter(&fCache); + while (!iter.done()) { + if (id == (*iter).fID) { + (*iter).fID = GrDrawOpAtlas::kInvalidAtlasID; + fAtlasedGlyphs--; + SkASSERT(fAtlasedGlyphs >= 0); + } + ++iter; + } +} + +GrDrawOpAtlas::ErrorCode GrTextStrike::addGlyphToAtlas( + GrResourceProvider* resourceProvider, + GrDeferredUploadTarget* target, + GrStrikeCache* glyphCache, + GrAtlasManager* fullAtlasManager, + GrGlyph* glyph, + SkStrike* skStrikeCache, + GrMaskFormat expectedMaskFormat, + bool isScaledGlyph) { + SkASSERT(glyph); + SkASSERT(skStrikeCache); + SkASSERT(fCache.find(glyph->fPackedID)); + + expectedMaskFormat = fullAtlasManager->resolveMaskFormat(expectedMaskFormat); + int bytesPerPixel = GrMaskFormatBytesPerPixel(expectedMaskFormat); + int width = glyph->width(); + int height = glyph->height(); + int rowBytes = width * bytesPerPixel; + + size_t size = glyph->fBounds.area() * bytesPerPixel; + bool isSDFGlyph = GrGlyph::kDistance_MaskStyle == glyph->maskStyle(); + bool addPad = isScaledGlyph && !isSDFGlyph; + if (addPad) { + width += 2; + rowBytes += 2*bytesPerPixel; + size += 2 * rowBytes; + height += 2; + size += 2 * (height + 2) * bytesPerPixel; + } + SkAutoSMalloc<1024> storage(size); + + SkGlyph* skGlyph = skStrikeCache->glyph(glyph->fPackedID); + void* dataPtr = storage.get(); + if (addPad) { + sk_bzero(dataPtr, size); + dataPtr = (char*)(dataPtr) + rowBytes + bytesPerPixel; + } + if (!get_packed_glyph_image(skStrikeCache, skGlyph, glyph->width(), glyph->height(), + rowBytes, expectedMaskFormat, + dataPtr, glyphCache->getMasks())) { + return GrDrawOpAtlas::ErrorCode::kError; + } + + GrDrawOpAtlas::ErrorCode result = fullAtlasManager->addToAtlas( + resourceProvider, glyphCache, this, + &glyph->fID, target, expectedMaskFormat, + width, height, + storage.get(), &glyph->fAtlasLocation); + if (GrDrawOpAtlas::ErrorCode::kSucceeded == result) { + if (addPad) { + glyph->fAtlasLocation.fX += 1; + glyph->fAtlasLocation.fY += 1; + } + SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != glyph->fID); + fAtlasedGlyphs++; + } + return result; +} diff --git a/gfx/skia/skia/src/gpu/text/GrStrikeCache.h b/gfx/skia/skia/src/gpu/text/GrStrikeCache.h new file mode 100644 index 0000000000..365e0a7868 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrStrikeCache.h @@ -0,0 +1,138 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStrikeCache_DEFINED +#define GrStrikeCache_DEFINED + +#include "src/codec/SkMasks.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkStrike.h" +#include "src/core/SkTDynamicHash.h" +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/GrGlyph.h" + +class GrAtlasManager; +class GrGpu; +class GrStrikeCache; + +/** + * The GrTextStrike manages a pool of CPU backing memory for GrGlyphs. This backing memory + * is indexed by a PackedID and SkStrike. The SkStrike is what actually creates the mask. + * The GrTextStrike may outlive the generating SkStrike. However, it retains a copy + * of it's SkDescriptor as a key to access (or regenerate) the SkStrike. GrTextStrikes are + * created by and owned by a GrStrikeCache. + */ +class GrTextStrike : public SkNVRefCnt { +public: + GrTextStrike(const SkDescriptor& fontScalerKey); + + GrGlyph* getGlyph(const SkGlyph& skGlyph) { + GrGlyph* grGlyph = fCache.find(skGlyph.getPackedID()); + if (grGlyph == nullptr) { + grGlyph = fAlloc.make(skGlyph); + fCache.add(grGlyph); + } + return grGlyph; + } + + // This variant of the above function is called by GrAtlasTextOp. At this point, it is possible + // that the maskformat of the glyph differs from what we expect. In these cases we will just + // draw a clear square. + // skbug:4143 crbug:510931 + GrGlyph* getGlyph(SkPackedGlyphID packed, SkStrike* skStrike) { + GrGlyph* grGlyph = fCache.find(packed); + if (grGlyph == nullptr) { + // We could return this to the caller, but in practice it adds code complexity for + // potentially little benefit(ie, if the glyph is not in our font cache, then its not + // in the atlas and we're going to be doing a texture upload anyways). + grGlyph = fAlloc.make(*skStrike->glyph(packed)); + fCache.add(grGlyph); + } + return grGlyph; + } + + // returns true if glyph successfully added to texture atlas, false otherwise. If the glyph's + // mask format has changed, then addGlyphToAtlas will draw a clear box. This will almost never + // happen. + // TODO we can handle some of these cases if we really want to, but the long term solution is to + // get the actual glyph image itself when we get the glyph metrics. + GrDrawOpAtlas::ErrorCode addGlyphToAtlas(GrResourceProvider*, GrDeferredUploadTarget*, + GrStrikeCache*, GrAtlasManager*, GrGlyph*, + SkStrike*, GrMaskFormat expectedMaskFormat, + bool isScaledGlyph); + + // testing + int countGlyphs() const { return fCache.count(); } + + // remove any references to this plot + void removeID(GrDrawOpAtlas::AtlasID); + + // If a TextStrike is abandoned by the cache, then the caller must get a new strike + bool isAbandoned() const { return fIsAbandoned; } + + static const SkDescriptor& GetKey(const GrTextStrike& strike) { + return *strike.fFontScalerKey.getDesc(); + } + + static uint32_t Hash(const SkDescriptor& desc) { return desc.getChecksum(); } + +private: + SkTDynamicHash fCache; + SkAutoDescriptor fFontScalerKey; + SkArenaAlloc fAlloc{512}; + + int fAtlasedGlyphs{0}; + bool fIsAbandoned{false}; + + friend class GrStrikeCache; +}; + +/** + * GrStrikeCache manages strikes which are indexed by a SkStrike. These strikes can then be + * used to generate individual Glyph Masks. + */ +class GrStrikeCache { +public: + GrStrikeCache(const GrCaps* caps, size_t maxTextureBytes); + ~GrStrikeCache(); + + void setStrikeToPreserve(GrTextStrike* strike) { fPreserveStrike = strike; } + + // The user of the cache may hold a long-lived ref to the returned strike. However, actions by + // another client of the cache may cause the strike to be purged while it is still reffed. + // Therefore, the caller must check GrTextStrike::isAbandoned() if there are other + // interactions with the cache since the strike was received. + sk_sp getStrike(const SkDescriptor& desc) { + sk_sp strike = sk_ref_sp(fCache.find(desc)); + if (!strike) { + strike = this->generateStrike(desc); + } + return strike; + } + + const SkMasks& getMasks() const { return *f565Masks; } + + void freeAll(); + + static void HandleEviction(GrDrawOpAtlas::AtlasID, void*); + +private: + sk_sp generateStrike(const SkDescriptor& desc) { + // 'fCache' get the construction ref + sk_sp strike = sk_ref_sp(new GrTextStrike(desc)); + fCache.add(strike.get()); + return strike; + } + + using StrikeHash = SkTDynamicHash; + + StrikeHash fCache; + GrTextStrike* fPreserveStrike; + std::unique_ptr f565Masks; +}; + +#endif // GrStrikeCache_DEFINED diff --git a/gfx/skia/skia/src/gpu/text/GrTextBlob.cpp b/gfx/skia/skia/src/gpu/text/GrTextBlob.cpp new file mode 100644 index 0000000000..095c6fa531 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextBlob.cpp @@ -0,0 +1,453 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/gpu/GrContext.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkPaintPriv.h" +#include "src/gpu/GrBlurUtils.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrStyle.h" +#include "src/gpu/geometry/GrShape.h" +#include "src/gpu/ops/GrAtlasTextOp.h" +#include "src/gpu/text/GrTextBlob.h" +#include "src/gpu/text/GrTextTarget.h" + +#include + +template static size_t sk_align(size_t s) { + return ((s + (N-1)) / N) * N; +} + +sk_sp GrTextBlob::Make(int glyphCount, + int runCount, + GrColor color, + GrStrikeCache* strikeCache) { + // We allocate size for the GrTextBlob itself, plus size for the vertices array, + // and size for the glyphIds array. + size_t verticesCount = glyphCount * kVerticesPerGlyph * kMaxVASize; + + size_t blobStart = 0; + size_t vertex = sk_align (blobStart + sizeof(GrTextBlob) * 1); + size_t glyphs = sk_align (vertex + sizeof(char) * verticesCount); + size_t runs = sk_align(glyphs + sizeof(GrGlyph*) * glyphCount); + size_t size = (runs + sizeof(GrTextBlob::Run) * runCount); + + void* allocation = ::operator new (size); + + if (CACHE_SANITY_CHECK) { + sk_bzero(allocation, size); + } + + sk_sp blob{new (allocation) GrTextBlob{strikeCache}}; + blob->fSize = size; + + // setup offsets for vertices / glyphs + blob->fVertices = SkTAddOffset(blob.get(), vertex); + blob->fGlyphs = SkTAddOffset(blob.get(), glyphs); + blob->fRuns = SkTAddOffset(blob.get(), runs); + + // Initialize runs + for (int i = 0; i < runCount; i++) { + new (&blob->fRuns[i]) GrTextBlob::Run{blob.get(), color}; + } + blob->fRunCountLimit = runCount; + return blob; +} + +void GrTextBlob::Run::setupFont(const SkStrikeSpec& strikeSpec) { + + if (fFallbackStrikeSpec != nullptr) { + *fFallbackStrikeSpec = strikeSpec; + } else { + fStrikeSpec = strikeSpec; + } +} + +void GrTextBlob::Run::appendPathGlyph(const SkPath& path, SkPoint position, + SkScalar scale, bool preTransformed) { + fPathGlyphs.push_back(PathGlyph(path, position.x(), position.y(), scale, preTransformed)); +} + +bool GrTextBlob::mustRegenerate(const SkPaint& paint, bool anyRunHasSubpixelPosition, + const SkMaskFilterBase::BlurRec& blurRec, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { + // If we have LCD text then our canonical color will be set to transparent, in this case we have + // to regenerate the blob on any color change + // We use the grPaint to get any color filter effects + if (fKey.fCanonicalColor == SK_ColorTRANSPARENT && + fLuminanceColor != SkPaintPriv::ComputeLuminanceColor(paint)) { + return true; + } + + if (fInitialViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) { + return true; + } + + /** This could be relaxed for blobs with only distance field glyphs. */ + if (fInitialViewMatrix.hasPerspective() && !fInitialViewMatrix.cheapEqualTo(viewMatrix)) { + return true; + } + + // We only cache one masked version + if (fKey.fHasBlur && + (fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle)) { + return true; + } + + // Similarly, we only cache one version for each style + if (fKey.fStyle != SkPaint::kFill_Style && + (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() || + fStrokeInfo.fMiterLimit != paint.getStrokeMiter() || + fStrokeInfo.fJoin != paint.getStrokeJoin())) { + return true; + } + + // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls + // for mixed blobs if this becomes an issue. + if (this->hasBitmap() && this->hasDistanceField()) { + // Identical viewmatrices and we can reuse in all cases + if (fInitialViewMatrix.cheapEqualTo(viewMatrix) && x == fInitialX && y == fInitialY) { + return false; + } + return true; + } + + if (this->hasBitmap()) { + if (fInitialViewMatrix.getScaleX() != viewMatrix.getScaleX() || + fInitialViewMatrix.getScaleY() != viewMatrix.getScaleY() || + fInitialViewMatrix.getSkewX() != viewMatrix.getSkewX() || + fInitialViewMatrix.getSkewY() != viewMatrix.getSkewY()) { + return true; + } + + // TODO(herb): this is not needed for full pixel glyph choice, but is needed to adjust + // the quads properly. Devise a system that regenerates the quads from original data + // using the transform to allow this to be used in general. + + // We can update the positions in the text blob without regenerating the whole + // blob, but only for integer translations. + // This cool bit of math will determine the necessary translation to apply to the + // already generated vertex coordinates to move them to the correct position. + // Figure out the translation in view space given a translation in source space. + SkScalar transX = viewMatrix.getTranslateX() + + viewMatrix.getScaleX() * (x - fInitialX) + + viewMatrix.getSkewX() * (y - fInitialY) - + fInitialViewMatrix.getTranslateX(); + SkScalar transY = viewMatrix.getTranslateY() + + viewMatrix.getSkewY() * (x - fInitialX) + + viewMatrix.getScaleY() * (y - fInitialY) - + fInitialViewMatrix.getTranslateY(); + if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) { + return true; + } + } else if (this->hasDistanceField()) { + // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different + // distance field being generated, so we have to regenerate in those cases + SkScalar newMaxScale = viewMatrix.getMaxScale(); + SkScalar oldMaxScale = fInitialViewMatrix.getMaxScale(); + SkScalar scaleAdjust = newMaxScale / oldMaxScale; + if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) { + return true; + } + } + + // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case + // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate + // the blob anyways at flush time, so no need to regenerate explicitly + return false; +} + +inline std::unique_ptr GrTextBlob::makeOp( + const SubRun& info, int glyphCount, uint16_t run, uint16_t subRun, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect, + const SkPaint& paint, const SkPMColor4f& filteredColor, const SkSurfaceProps& props, + const GrDistanceFieldAdjustTable* distanceAdjustTable, GrTextTarget* target) { + GrMaskFormat format = info.maskFormat(); + + GrPaint grPaint; + target->makeGrPaint(info.maskFormat(), paint, viewMatrix, &grPaint); + std::unique_ptr op; + if (info.drawAsDistanceFields()) { + // TODO: Can we be even smarter based on the dest transfer function? + op = GrAtlasTextOp::MakeDistanceField( + target->getContext(), std::move(grPaint), glyphCount, distanceAdjustTable, + target->colorInfo().isLinearlyBlended(), SkPaintPriv::ComputeLuminanceColor(paint), + props, info.isAntiAliased(), info.hasUseLCDText()); + } else { + op = GrAtlasTextOp::MakeBitmap(target->getContext(), std::move(grPaint), format, glyphCount, + info.needsTransform()); + } + GrAtlasTextOp::Geometry& geometry = op->geometry(); + geometry.fViewMatrix = viewMatrix; + geometry.fClipRect = clipRect; + geometry.fBlob = SkRef(this); + geometry.fRun = run; + geometry.fSubRun = subRun; + geometry.fColor = info.maskFormat() == kARGB_GrMaskFormat ? SK_PMColor4fWHITE : filteredColor; + geometry.fX = x; + geometry.fY = y; + op->init(); + return op; +} + +static void calculate_translation(bool applyVM, + const SkMatrix& newViewMatrix, SkScalar newX, SkScalar newY, + const SkMatrix& currentViewMatrix, SkScalar currentX, + SkScalar currentY, SkScalar* transX, SkScalar* transY) { + if (applyVM) { + *transX = newViewMatrix.getTranslateX() + + newViewMatrix.getScaleX() * (newX - currentX) + + newViewMatrix.getSkewX() * (newY - currentY) - + currentViewMatrix.getTranslateX(); + + *transY = newViewMatrix.getTranslateY() + + newViewMatrix.getSkewY() * (newX - currentX) + + newViewMatrix.getScaleY() * (newY - currentY) - + currentViewMatrix.getTranslateY(); + } else { + *transX = newX - currentX; + *transY = newY - currentY; + } +} + +void GrTextBlob::flush(GrTextTarget* target, const SkSurfaceProps& props, + const GrDistanceFieldAdjustTable* distanceAdjustTable, + const SkPaint& paint, const SkPMColor4f& filteredColor, const GrClip& clip, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { + + // GrTextBlob::makeOp only takes uint16_t values for run and subRun indices. + // Encountering something larger than this is highly unlikely, so we'll just not draw it. + int lastRun = SkTMin(fRunCountLimit, (1 << 16)) - 1; + // For each run in the GrTextBlob we're going to churn through all the glyphs. + // Each run is broken into a path part and a Mask / DFT / ARGB part. + for (int runIndex = 0; runIndex <= lastRun; runIndex++) { + + Run& run = fRuns[runIndex]; + + // first flush any path glyphs + if (run.fPathGlyphs.count()) { + SkPaint runPaint{paint}; + runPaint.setAntiAlias(run.fAntiAlias); + + for (int i = 0; i < run.fPathGlyphs.count(); i++) { + GrTextBlob::Run::PathGlyph& pathGlyph = run.fPathGlyphs[i]; + + SkMatrix ctm; + const SkPath* path = &pathGlyph.fPath; + + // TmpPath must be in the same scope as GrShape shape below. + SkTLazy tmpPath; + + // The glyph positions and glyph outlines are either in device space or in source + // space based on fPreTransformed. + if (!pathGlyph.fPreTransformed) { + // Positions and outlines are in source space. + + ctm = viewMatrix; + + SkMatrix pathMatrix = SkMatrix::MakeScale(pathGlyph.fScale, pathGlyph.fScale); + + // The origin for the blob may have changed, so figure out the delta. + SkVector originShift = SkPoint{x, y} - SkPoint{fInitialX, fInitialY}; + + // Shift the original glyph location in source space to the position of the new + // blob. + pathMatrix.postTranslate(originShift.x() + pathGlyph.fX, + originShift.y() + pathGlyph.fY); + + // If there are shaders, blurs or styles, the path must be scaled into source + // space independently of the CTM. This allows the CTM to be correct for the + // different effects. + GrStyle style(runPaint); + bool scalePath = runPaint.getShader() + || style.applies() + || runPaint.getMaskFilter(); + if (!scalePath) { + // Scale can be applied to CTM -- no effects. + + ctm.preConcat(pathMatrix); + } else { + // Scale the outline into source space. + + // Transform the path form the normalized outline to source space. This + // way the CTM will remain the same so it can be used by the effects. + SkPath* sourceOutline = tmpPath.init(); + path->transform(pathMatrix, sourceOutline); + sourceOutline->setIsVolatile(true); + path = sourceOutline; + } + + + } else { + // Positions and outlines are in device space. + + SkPoint originalOrigin = {fInitialX, fInitialY}; + fInitialViewMatrix.mapPoints(&originalOrigin, 1); + + SkPoint newOrigin = {x, y}; + viewMatrix.mapPoints(&newOrigin, 1); + + // The origin shift in device space. + SkPoint originShift = newOrigin - originalOrigin; + + // Shift the original glyph location in device space to the position of the + // new blob. + ctm = SkMatrix::MakeTrans(originShift.x() + pathGlyph.fX, + originShift.y() + pathGlyph.fY); + } + + // TODO: we are losing the mutability of the path here + GrShape shape(*path, paint); + + target->drawShape(clip, runPaint, ctm, shape); + } + } + + // then flush each subrun, if any + if (!run.fInitialized) { + continue; + } + + int lastSubRun = SkTMin(run.fSubRunInfo.count(), 1 << 16) - 1; + for (int subRun = 0; subRun <= lastSubRun; subRun++) { + const SubRun& info = run.fSubRunInfo[subRun]; + int glyphCount = info.glyphCount(); + if (0 == glyphCount) { + continue; + } + + bool skipClip = false; + bool submitOp = true; + SkIRect clipRect = SkIRect::MakeEmpty(); + SkRect rtBounds = SkRect::MakeWH(target->width(), target->height()); + SkRRect clipRRect; + GrAA aa; + // We can clip geometrically if we're not using SDFs or transformed glyphs, + // and we have an axis-aligned rectangular non-AA clip + if (!info.drawAsDistanceFields() && !info.needsTransform() && + clip.isRRect(rtBounds, &clipRRect, &aa) && + clipRRect.isRect() && GrAA::kNo == aa) { + skipClip = true; + // We only need to do clipping work if the subrun isn't contained by the clip + SkRect subRunBounds; + this->computeSubRunBounds(&subRunBounds, runIndex, subRun, viewMatrix, x, y, + false); + if (!clipRRect.getBounds().contains(subRunBounds)) { + // If the subrun is completely outside, don't add an op for it + if (!clipRRect.getBounds().intersects(subRunBounds)) { + submitOp = false; + } + else { + clipRRect.getBounds().round(&clipRect); + } + } + } + + if (submitOp) { + auto op = this->makeOp(info, glyphCount, runIndex, subRun, viewMatrix, x, y, + clipRect, paint, filteredColor, props, distanceAdjustTable, + target); + if (op) { + if (skipClip) { + target->addDrawOp(GrNoClip(), std::move(op)); + } + else { + target->addDrawOp(clip, std::move(op)); + } + } + } + } + + } +} + +std::unique_ptr GrTextBlob::test_makeOp( + int glyphCount, uint16_t run, uint16_t subRun, const SkMatrix& viewMatrix, + SkScalar x, SkScalar y, const SkPaint& paint, const SkPMColor4f& filteredColor, + const SkSurfaceProps& props, const GrDistanceFieldAdjustTable* distanceAdjustTable, + GrTextTarget* target) { + const GrTextBlob::SubRun& info = fRuns[run].fSubRunInfo[subRun]; + SkIRect emptyRect = SkIRect::MakeEmpty(); + return this->makeOp(info, glyphCount, run, subRun, viewMatrix, x, y, emptyRect, + paint, filteredColor, props, distanceAdjustTable, target); +} + +void GrTextBlob::AssertEqual(const GrTextBlob& l, const GrTextBlob& r) { + SkASSERT_RELEASE(l.fSize == r.fSize); + + SkASSERT_RELEASE(l.fBlurRec.fSigma == r.fBlurRec.fSigma); + SkASSERT_RELEASE(l.fBlurRec.fStyle == r.fBlurRec.fStyle); + + SkASSERT_RELEASE(l.fStrokeInfo.fFrameWidth == r.fStrokeInfo.fFrameWidth); + SkASSERT_RELEASE(l.fStrokeInfo.fMiterLimit == r.fStrokeInfo.fMiterLimit); + SkASSERT_RELEASE(l.fStrokeInfo.fJoin == r.fStrokeInfo.fJoin); + + SkASSERT_RELEASE(l.fKey == r.fKey); + //SkASSERT_RELEASE(l.fPaintColor == r.fPaintColor); // Colors might not actually be identical + SkASSERT_RELEASE(l.fMaxMinScale == r.fMaxMinScale); + SkASSERT_RELEASE(l.fMinMaxScale == r.fMinMaxScale); + SkASSERT_RELEASE(l.fTextType == r.fTextType); + + SkASSERT_RELEASE(l.fRunCountLimit == r.fRunCountLimit); + for (int i = 0; i < l.fRunCountLimit; i++) { + const Run& lRun = l.fRuns[i]; + const Run& rRun = r.fRuns[i]; + + SkASSERT_RELEASE(lRun.fStrikeSpec.descriptor() == rRun.fStrikeSpec.descriptor()); + + // color can be changed + //SkASSERT(lRun.fColor == rRun.fColor); + SkASSERT_RELEASE(lRun.fInitialized == rRun.fInitialized); + + SkASSERT_RELEASE(lRun.fSubRunInfo.count() == rRun.fSubRunInfo.count()); + for(int j = 0; j < lRun.fSubRunInfo.count(); j++) { + const SubRun& lSubRun = lRun.fSubRunInfo[j]; + const SubRun& rSubRun = rRun.fSubRunInfo[j]; + + // TODO we can do this check, but we have to apply the VM to the old vertex bounds + //SkASSERT_RELEASE(lSubRun.vertexBounds() == rSubRun.vertexBounds()); + + if (lSubRun.strike()) { + SkASSERT_RELEASE(rSubRun.strike()); + SkASSERT_RELEASE(GrTextStrike::GetKey(*lSubRun.strike()) == + GrTextStrike::GetKey(*rSubRun.strike())); + + } else { + SkASSERT_RELEASE(!rSubRun.strike()); + } + + SkASSERT_RELEASE(lSubRun.vertexStartIndex() == rSubRun.vertexStartIndex()); + SkASSERT_RELEASE(lSubRun.vertexEndIndex() == rSubRun.vertexEndIndex()); + SkASSERT_RELEASE(lSubRun.glyphStartIndex() == rSubRun.glyphStartIndex()); + SkASSERT_RELEASE(lSubRun.glyphEndIndex() == rSubRun.glyphEndIndex()); + SkASSERT_RELEASE(lSubRun.maskFormat() == rSubRun.maskFormat()); + SkASSERT_RELEASE(lSubRun.drawAsDistanceFields() == rSubRun.drawAsDistanceFields()); + SkASSERT_RELEASE(lSubRun.hasUseLCDText() == rSubRun.hasUseLCDText()); + } + + SkASSERT_RELEASE(lRun.fPathGlyphs.count() == rRun.fPathGlyphs.count()); + for (int i = 0; i < lRun.fPathGlyphs.count(); i++) { + const Run::PathGlyph& lPathGlyph = lRun.fPathGlyphs[i]; + const Run::PathGlyph& rPathGlyph = rRun.fPathGlyphs[i]; + + SkASSERT_RELEASE(lPathGlyph.fPath == rPathGlyph.fPath); + // We can't assert that these have the same translations + } + } +} + +void GrTextBlob::SubRun::computeTranslation(const SkMatrix& viewMatrix, + SkScalar x, SkScalar y, SkScalar* transX, + SkScalar* transY) { + // Don't use the matrix to translate on distance field for fallback subruns. + calculate_translation(!this->drawAsDistanceFields() && !this->isFallback(), viewMatrix, + x, y, fCurrentViewMatrix, fX, fY, transX, transY); + fCurrentViewMatrix = viewMatrix; + fX = x; + fY = y; +} diff --git a/gfx/skia/skia/src/gpu/text/GrTextBlob.h b/gfx/skia/skia/src/gpu/text/GrTextBlob.h new file mode 100644 index 0000000000..ddea88d15f --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextBlob.h @@ -0,0 +1,642 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextBlob_DEFINED +#define GrTextBlob_DEFINED + +#include "include/core/SkPathEffect.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkSurfaceProps.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRectPriv.h" +#include "src/core/SkStrikeCache.h" +#include "src/core/SkStrikeSpec.h" +#include "src/core/SkTInternalLList.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrDrawOpAtlas.h" +#include "src/gpu/text/GrStrikeCache.h" +#include "src/gpu/text/GrTextContext.h" +#include "src/gpu/text/GrTextTarget.h" + +class GrAtlasManager; +struct GrDistanceFieldAdjustTable; +struct GrGlyph; + +class SkTextBlob; +class SkTextBlobRunIterator; + +// With this flag enabled, the GrTextContext will, as a sanity check, regenerate every blob +// that comes in to verify the integrity of its cache +#define CACHE_SANITY_CHECK 0 + +/* + * A GrTextBlob contains a fully processed SkTextBlob, suitable for nearly immediate drawing + * on the GPU. These are initially created with valid positions and colors, but invalid + * texture coordinates. The GrTextBlob itself has a few Blob-wide properties, and also + * consists of a number of runs. Runs inside a blob are flushed individually so they can be + * reordered. + * + * The only thing(aside from a memcopy) required to flush a GrTextBlob is to ensure that + * the GrAtlas will not evict anything the Blob needs. + * + * Note: This struct should really be named GrCachedAtasTextBlob, but that is too verbose. + * + * *WARNING* If you add new fields to this struct, then you may need to to update AssertEqual + */ +class GrTextBlob : public SkNVRefCnt, public SkGlyphRunPainterInterface { + struct Run; +public: + SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrTextBlob); + + class VertexRegenerator; + + void generateFromGlyphRunList(const GrShaderCaps& shaderCaps, + const GrTextContext::Options& options, + const SkPaint& paint, + SkScalerContextFlags scalerContextFlags, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + const SkGlyphRunList& glyphRunList, + SkGlyphRunListPainter* glyphPainter); + + static sk_sp Make( + int glyphCount, + int runCount, + GrColor color, + GrStrikeCache* strikeCache); + + /** + * We currently force regeneration of a blob if old or new matrix differ in having perspective. + * If we ever change that then the key must contain the perspectiveness when there are distance + * fields as perspective distance field use 3 component vertex positions and non-perspective + * uses 2. + */ + struct Key { + Key() { + sk_bzero(this, sizeof(Key)); + } + uint32_t fUniqueID; + // Color may affect the gamma of the mask we generate, but in a fairly limited way. + // Each color is assigned to on of a fixed number of buckets based on its + // luminance. For each luminance bucket there is a "canonical color" that + // represents the bucket. This functionality is currently only supported for A8 + SkColor fCanonicalColor; + SkPaint::Style fStyle; + SkPixelGeometry fPixelGeometry; + bool fHasBlur; + uint32_t fScalerContextFlags; + + bool operator==(const Key& other) const { + return 0 == memcmp(this, &other, sizeof(Key)); + } + }; + + void setupKey(const GrTextBlob::Key& key, + const SkMaskFilterBase::BlurRec& blurRec, + const SkPaint& paint) { + fKey = key; + if (key.fHasBlur) { + fBlurRec = blurRec; + } + if (key.fStyle != SkPaint::kFill_Style) { + fStrokeInfo.fFrameWidth = paint.getStrokeWidth(); + fStrokeInfo.fMiterLimit = paint.getStrokeMiter(); + fStrokeInfo.fJoin = paint.getStrokeJoin(); + } + } + + static const Key& GetKey(const GrTextBlob& blob) { + return blob.fKey; + } + + static uint32_t Hash(const Key& key) { + return SkOpts::hash(&key, sizeof(Key)); + } + + void operator delete(void* p) { + ::operator delete(p); + } + + void* operator new(size_t) { + SK_ABORT("All blobs are created by placement new."); + } + + void* operator new(size_t, void* p) { return p; } + + bool hasDistanceField() const { return SkToBool(fTextType & kHasDistanceField_TextType); } + bool hasBitmap() const { return SkToBool(fTextType & kHasBitmap_TextType); } + void setHasDistanceField() { fTextType |= kHasDistanceField_TextType; } + void setHasBitmap() { fTextType |= kHasBitmap_TextType; } + + int runCountLimit() const { return fRunCountLimit; } + + Run* pushBackRun() { + SkASSERT(fRunCount < fRunCountLimit); + + // If there is more run, then connect up the subruns. + if (fRunCount > 0) { + SubRun& newRun = fRuns[fRunCount].fSubRunInfo.back(); + SubRun& lastRun = fRuns[fRunCount - 1].fSubRunInfo.back(); + newRun.setAsSuccessor(lastRun); + } + + fRunCount++; + return this->currentRun(); + } + + void setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax) { + // we init fMaxMinScale and fMinMaxScale in the constructor + fMaxMinScale = SkMaxScalar(scaledMin, fMaxMinScale); + fMinMaxScale = SkMinScalar(scaledMax, fMinMaxScale); + } + + static size_t GetVertexStride(GrMaskFormat maskFormat, bool hasWCoord) { + switch (maskFormat) { + case kA8_GrMaskFormat: + return hasWCoord ? kGrayTextDFPerspectiveVASize : kGrayTextVASize; + case kARGB_GrMaskFormat: + return hasWCoord ? kColorTextPerspectiveVASize : kColorTextVASize; + default: + SkASSERT(!hasWCoord); + return kLCDTextVASize; + } + } + + bool mustRegenerate(const SkPaint&, bool, const SkMaskFilterBase::BlurRec& blurRec, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y); + + void flush(GrTextTarget*, const SkSurfaceProps& props, + const GrDistanceFieldAdjustTable* distanceAdjustTable, + const SkPaint& paint, const SkPMColor4f& filteredColor, const GrClip& clip, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y); + + void computeSubRunBounds(SkRect* outBounds, int runIndex, int subRunIndex, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y, + bool needsGlyphTransform) { + // We don't yet position distance field text on the cpu, so we have to map the vertex bounds + // into device space. + // We handle vertex bounds differently for distance field text and bitmap text because + // the vertex bounds of bitmap text are in device space. If we are flushing multiple runs + // from one blob then we are going to pay the price here of mapping the rect for each run. + const Run& run = fRuns[runIndex]; + const SubRun& subRun = run.fSubRunInfo[subRunIndex]; + *outBounds = subRun.vertexBounds(); + if (needsGlyphTransform) { + // Distance field text is positioned with the (X,Y) as part of the glyph position, + // and currently the view matrix is applied on the GPU + outBounds->offset(x - fInitialX, y - fInitialY); + viewMatrix.mapRect(outBounds); + } else { + // Bitmap text is fully positioned on the CPU, and offset by an (X,Y) translate in + // device space. + SkMatrix boundsMatrix = fInitialViewMatrixInverse; + + boundsMatrix.postTranslate(-fInitialX, -fInitialY); + + boundsMatrix.postTranslate(x, y); + + boundsMatrix.postConcat(viewMatrix); + boundsMatrix.mapRect(outBounds); + + // Due to floating point numerical inaccuracies, we have to round out here + outBounds->roundOut(outBounds); + } + } + + // position + local coord + static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16); + static const size_t kColorTextPerspectiveVASize = sizeof(SkPoint3) + sizeof(SkIPoint16); + static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16); + static const size_t kGrayTextDFPerspectiveVASize = + sizeof(SkPoint3) + sizeof(GrColor) + sizeof(SkIPoint16); + static const size_t kLCDTextVASize = kGrayTextVASize; + static const size_t kMaxVASize = kGrayTextDFPerspectiveVASize; + static const int kVerticesPerGlyph = 4; + + static void AssertEqual(const GrTextBlob&, const GrTextBlob&); + + // The color here is the GrPaint color, and it is used to determine whether we + // have to regenerate LCD text blobs. + // We use this color vs the SkPaint color because it has the colorfilter applied. + void initReusableBlob(SkColor luminanceColor, const SkMatrix& viewMatrix, + SkScalar x, SkScalar y) { + fLuminanceColor = luminanceColor; + this->setupViewMatrix(viewMatrix, x, y); + } + + void initThrowawayBlob(const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { + this->setupViewMatrix(viewMatrix, x, y); + } + + const Key& key() const { return fKey; } + + size_t size() const { return fSize; } + + ~GrTextBlob() override { + for (int i = 0; i < fRunCountLimit; i++) { + fRuns[i].~Run(); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Internal test methods + std::unique_ptr test_makeOp(int glyphCount, uint16_t run, uint16_t subRun, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y, + const SkPaint& paint, const SkPMColor4f& filteredColor, + const SkSurfaceProps&, const GrDistanceFieldAdjustTable*, + GrTextTarget*); + +private: + GrTextBlob(GrStrikeCache* strikeCache) : fStrikeCache{strikeCache} { } + + // This function will only be called when we are generating a blob from scratch. We record the + // initial view matrix and initial offsets(x,y), because we record vertex bounds relative to + // these numbers. When blobs are reused with new matrices, we need to return to model space so + // we can update the vertex bounds appropriately. + void setupViewMatrix(const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { + fInitialViewMatrix = viewMatrix; + if (!viewMatrix.invert(&fInitialViewMatrixInverse)) { + fInitialViewMatrixInverse = SkMatrix::I(); + } + fInitialX = x; + fInitialY = y; + + // make sure all initial subruns have the correct VM and X/Y applied + for (int i = 0; i < fRunCountLimit; i++) { + fRuns[i].fSubRunInfo[0].init(fInitialViewMatrix, x, y); + } + } + + class SubRun { + public: + SubRun(Run* run, const SkStrikeSpec& strikeSpec, GrColor color) + : fColor{color} + , fRun{run} + , fStrikeSpec{strikeSpec} {} + + // When used with emplace_back, this constructs a SubRun from the last SubRun in an array. + //SubRun(SkSTArray<1, SubRun>* subRunList) + // : fColor{subRunList->fromBack(1).fColor} { } + + void appendGlyph(GrGlyph* glyph, SkRect dstRect); + + // TODO when this object is more internal, drop the privacy + void resetBulkUseToken() { fBulkUseToken.reset(); } + GrDrawOpAtlas::BulkUseTokenUpdater* bulkUseToken() { return &fBulkUseToken; } + void setStrike(sk_sp strike) { fStrike = std::move(strike); } + GrTextStrike* strike() const { return fStrike.get(); } + sk_sp refStrike() const { return fStrike; } + + void setAtlasGeneration(uint64_t atlasGeneration) { fAtlasGeneration = atlasGeneration;} + uint64_t atlasGeneration() const { return fAtlasGeneration; } + + size_t byteCount() const { return fVertexEndIndex - fVertexStartIndex; } + size_t vertexStartIndex() const { return fVertexStartIndex; } + size_t vertexEndIndex() const { return fVertexEndIndex; } + + uint32_t glyphCount() const { return fGlyphEndIndex - fGlyphStartIndex; } + uint32_t glyphStartIndex() const { return fGlyphStartIndex; } + uint32_t glyphEndIndex() const { return fGlyphEndIndex; } + void setColor(GrColor color) { fColor = color; } + GrColor color() const { return fColor; } + void setMaskFormat(GrMaskFormat format) { fMaskFormat = format; } + GrMaskFormat maskFormat() const { return fMaskFormat; } + + void setAsSuccessor(const SubRun& prev) { + fGlyphStartIndex = prev.glyphEndIndex(); + fGlyphEndIndex = fGlyphStartIndex; + + fVertexStartIndex = prev.vertexEndIndex(); + fVertexEndIndex = fVertexStartIndex; + + // copy over viewmatrix settings + this->init(prev.fCurrentViewMatrix, prev.fX, prev.fY); + } + + const SkRect& vertexBounds() const { return fVertexBounds; } + void joinGlyphBounds(const SkRect& glyphBounds) { + fVertexBounds.joinNonEmptyArg(glyphBounds); + } + + void init(const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { + fCurrentViewMatrix = viewMatrix; + fX = x; + fY = y; + } + + // This function assumes the translation will be applied before it is called again + void computeTranslation(const SkMatrix& viewMatrix, SkScalar x, SkScalar y, + SkScalar* transX, SkScalar* transY); + + // df properties + void setDrawAsDistanceFields() { fFlags.drawAsSdf = true; } + bool drawAsDistanceFields() const { return fFlags.drawAsSdf; } + void setUseLCDText(bool useLCDText) { fFlags.useLCDText = useLCDText; } + bool hasUseLCDText() const { return fFlags.useLCDText; } + void setAntiAliased(bool antiAliased) { fFlags.antiAliased = antiAliased; } + bool isAntiAliased() const { return fFlags.antiAliased; } + void setHasWCoord(bool hasW) { fFlags.hasWCoord = hasW; } + bool hasWCoord() const { return fFlags.hasWCoord; } + void setNeedsTransform(bool needsTransform) { fFlags.needsTransform = needsTransform; } + bool needsTransform() const { return fFlags.needsTransform; } + void setFallback() { fFlags.argbFallback = true; } + bool isFallback() { return fFlags.argbFallback; } + + const SkStrikeSpec& strikeSpec() const { return fStrikeSpec; } + + private: + GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken; + sk_sp fStrike; + SkMatrix fCurrentViewMatrix; + SkRect fVertexBounds = SkRectPriv::MakeLargestInverted(); + uint64_t fAtlasGeneration{GrDrawOpAtlas::kInvalidAtlasGeneration}; + size_t fVertexStartIndex{0}; + size_t fVertexEndIndex{0}; + uint32_t fGlyphStartIndex{0}; + uint32_t fGlyphEndIndex{0}; + SkScalar fX; + SkScalar fY; + GrColor fColor{GrColor_ILLEGAL}; + GrMaskFormat fMaskFormat{kA8_GrMaskFormat}; + struct { + bool drawAsSdf:1; + bool useLCDText:1; + bool antiAliased:1; + bool hasWCoord:1; + bool needsTransform:1; + bool argbFallback:1; + } fFlags{false, false, false, false, false, false}; + Run* const fRun; + const SkStrikeSpec& fStrikeSpec; + }; // SubRunInfo + + /* + * Each Run inside of the blob can have its texture coordinates regenerated if required. + * To determine if regeneration is necessary, fAtlasGeneration is used. If there have been + * any evictions inside of the atlas, then we will simply regenerate Runs. We could track + * this at a more fine grained level, but its not clear if this is worth it, as evictions + * should be fairly rare. + * + * One additional point, each run can contain glyphs with any of the three mask formats. + * We call these SubRuns. Because a subrun must be a contiguous range, we have to create + * a new subrun each time the mask format changes in a run. In theory, a run can have as + * many SubRuns as it has glyphs, ie if a run alternates between color emoji and A8. In + * practice, the vast majority of runs have only a single subrun. + * + * Finally, for runs where the entire thing is too large for the GrTextContext to + * handle, we have a bit to mark the run as flushable via rendering as paths or as scaled + * glyphs. It would be a bit expensive to figure out ahead of time whether or not a run + * can flush in this manner, so we always allocate vertices for the run, regardless of + * whether or not it is too large. The benefit of this strategy is that we can always reuse + * a blob allocation regardless of viewmatrix changes. We could store positions for these + * glyphs, however, it's not clear if this is a win because we'd still have to either go to the + * glyph cache to get the path at flush time, or hold onto the path in the cache, which + * would greatly increase the memory of these cached items. + */ + struct Run { + explicit Run(GrTextBlob* blob, GrColor color) + : fBlob{blob}, fColor{color} { + // To ensure we always have one subrun, we push back a fresh run here + fSubRunInfo.emplace_back(this, fStrikeSpec, color); + } + + // sets the last subrun of runIndex to use w values + void setSubRunHasW(bool hasWCoord) { + SubRun& subRun = this->fSubRunInfo.back(); + subRun.setHasWCoord(hasWCoord); + } + + // inits the override descriptor on the current run. All following subruns must use this + // descriptor + SubRun* initARGBFallback() { + fFallbackStrikeSpec.reset(new SkStrikeSpec{}); + // Push back a new subrun to fill and set the override descriptor + SubRun* subRun = this->pushBackSubRun(*fFallbackStrikeSpec, fColor); + subRun->setMaskFormat(kARGB_GrMaskFormat); + subRun->setFallback(); + return subRun; + } + + // Appends a glyph to the blob as a path only. + void appendPathGlyph( + const SkPath& path, SkPoint position, SkScalar scale, bool preTransformed); + + // Append a glyph to the sub run taking care to switch the glyph if needed. + void switchSubRunIfNeededAndAppendGlyph(GrGlyph* glyph, + const sk_sp& strike, + const SkRect& destRect, + bool needsTransform); + + // Used when the glyph in the cache has the CTM already applied, therefore no transform + // is needed during rendering. + void appendDeviceSpaceGlyph(const sk_sp& strike, + const SkGlyph& skGlyph, + SkPoint origin); + + // The glyph is oriented upright in the cache and needs to be transformed onto the screen. + void appendSourceSpaceGlyph(const sk_sp& strike, + const SkGlyph& skGlyph, + SkPoint origin, + SkScalar textScale); + + void setupFont(const SkStrikeSpec& strikeSpec); + + void setRunFontAntiAlias(bool aa) { + fAntiAlias = aa; + } + + // sets the last subrun of runIndex to use distance field text + void setSubRunHasDistanceFields(bool hasLCD, bool isAntiAlias, bool hasWCoord) { + SubRun& subRun = fSubRunInfo.back(); + subRun.setUseLCDText(hasLCD); + subRun.setAntiAliased(isAntiAlias); + subRun.setDrawAsDistanceFields(); + subRun.setHasWCoord(hasWCoord); + } + + SubRun* pushBackSubRun(const SkStrikeSpec& desc, GrColor color) { + // Forward glyph / vertex information to seed the new sub run + SubRun& newSubRun = fSubRunInfo.emplace_back(this, desc, color); + + const SubRun& prevSubRun = fSubRunInfo.fromBack(1); + + // Forward glyph / vertex information to seed the new sub run + newSubRun.setAsSuccessor(prevSubRun); + return &newSubRun; + } + + // Any glyphs that can't be rendered with the base or override descriptor + // are rendered as paths + struct PathGlyph { + PathGlyph(const SkPath& path, SkScalar x, SkScalar y, SkScalar scale, bool preXformed) + : fPath(path) + , fX(x) + , fY(y) + , fScale(scale) + , fPreTransformed(preXformed) {} + SkPath fPath; + SkScalar fX; + SkScalar fY; + SkScalar fScale; + bool fPreTransformed; + }; + + SkSTArray<1, SubRun> fSubRunInfo; + SkStrikeSpec fStrikeSpec; + + // Distance field text cannot draw coloremoji, and so has to fall back. However, + // though the distance field text and the coloremoji may share the same run, they + // will have different descriptors. If fFallbackStrikeSpec is non-nullptr, then it + // will be used in place of the run's descriptor to regen texture coords + std::unique_ptr fFallbackStrikeSpec; + + SkTArray fPathGlyphs; + + bool fAntiAlias{false}; // needed mainly for rendering paths + bool fInitialized{false}; + + GrTextBlob* const fBlob; + GrColor fColor; + }; // Run + + std::unique_ptr makeOp( + const SubRun& info, int glyphCount, uint16_t run, uint16_t subRun, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect, + const SkPaint& paint, const SkPMColor4f& filteredColor, const SkSurfaceProps&, + const GrDistanceFieldAdjustTable*, GrTextTarget*); + + // currentRun, startRun, and the process* calls are all used by the SkGlyphRunPainter, and + // live in SkGlyphRunPainter.cpp file. + Run* currentRun(); + + void startRun(const SkGlyphRun& glyphRun, bool useSDFT) override; + + void processDeviceMasks(SkSpan masks, + const SkStrikeSpec& strikeSpec) override; + + void processSourcePaths(SkSpan paths, + const SkStrikeSpec& strikeSpec) override; + + void processDevicePaths(SkSpan paths) override; + + void processSourceSDFT(SkSpan masks, + const SkStrikeSpec& strikeSpec, + const SkFont& runFont, + SkScalar minScale, + SkScalar maxScale, + bool hasWCoord) override; + + void processSourceFallback(SkSpan masks, + const SkStrikeSpec& strikeSpec, + bool hasW) override; + + void processDeviceFallback(SkSpan masks, + const SkStrikeSpec& strikeSpec) override; + + struct StrokeInfo { + SkScalar fFrameWidth; + SkScalar fMiterLimit; + SkPaint::Join fJoin; + }; + + enum TextType { + kHasDistanceField_TextType = 0x1, + kHasBitmap_TextType = 0x2, + }; + + // all glyph / vertex offsets are into these pools. + char* fVertices; + GrGlyph** fGlyphs; + Run* fRuns; + + // Lifetime: The GrStrikeCache is owned by and has the same lifetime as the GrRecordingContext. + // The GrRecordingContext also owns the GrTextBlob cache which owns this GrTextBlob. + GrStrikeCache* const fStrikeCache; + SkMaskFilterBase::BlurRec fBlurRec; + StrokeInfo fStrokeInfo; + Key fKey; + SkMatrix fInitialViewMatrix; + SkMatrix fInitialViewMatrixInverse; + size_t fSize; + SkColor fLuminanceColor; + SkScalar fInitialX; + SkScalar fInitialY; + + // We can reuse distance field text, but only if the new viewmatrix would not result in + // a mip change. Because there can be multiple runs in a blob, we track the overall + // maximum minimum scale, and minimum maximum scale, we can support before we need to regen + SkScalar fMaxMinScale{-SK_ScalarMax}; + SkScalar fMinMaxScale{SK_ScalarMax}; + int fRunCount{0}; + int fRunCountLimit; + uint8_t fTextType{0}; +}; + +/** + * Used to produce vertices for a subrun of a blob. The vertices are cached in the blob itself. + * This is invoked each time a sub run is drawn. It regenerates the vertex data as required either + * because of changes to the atlas or because of different draw parameters (e.g. color change). In + * rare cases the draw may have to interrupted and flushed in the middle of the sub run in order to + * free up atlas space. Thus, this generator is stateful and should be invoked in a loop until the + * entire sub run has been completed. + */ +class GrTextBlob::VertexRegenerator { +public: + /** + * Consecutive VertexRegenerators often use the same SkGlyphCache. If the same instance of + * SkAutoGlyphCache is reused then it can save the cost of multiple detach/attach operations of + * SkGlyphCache. + */ + VertexRegenerator(GrResourceProvider*, GrTextBlob*, int runIdx, int subRunIdx, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color, + GrDeferredUploadTarget*, GrStrikeCache*, GrAtlasManager*, + SkExclusiveStrikePtr*); + + struct Result { + /** + * Was regenerate() able to draw all the glyphs from the sub run? If not flush all glyph + * draws and call regenerate() again. + */ + bool fFinished = true; + + /** + * How many glyphs were regenerated. Will be equal to the sub run's glyph count if + * fType is kFinished. + */ + int fGlyphsRegenerated = 0; + + /** + * Pointer where the caller finds the first regenerated vertex. + */ + const char* fFirstVertex; + }; + + bool regenerate(Result*); + +private: + bool doRegen(Result*, bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs); + + GrResourceProvider* fResourceProvider; + const SkMatrix& fViewMatrix; + GrTextBlob* fBlob; + GrDeferredUploadTarget* fUploadTarget; + GrStrikeCache* fGlyphCache; + GrAtlasManager* fFullAtlasManager; + SkExclusiveStrikePtr* fLazyStrike; + SubRun* fSubRun; + GrColor fColor; + SkScalar fTransX; + SkScalar fTransY; + + uint32_t fRegenFlags = 0; + int fCurrGlyph = 0; + bool fBrokenRun = false; +}; + +#endif // GrTextBlob_DEFINED diff --git a/gfx/skia/skia/src/gpu/text/GrTextBlobCache.cpp b/gfx/skia/skia/src/gpu/text/GrTextBlobCache.cpp new file mode 100644 index 0000000000..c3a6860231 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextBlobCache.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/text/GrTextBlobCache.h" + +DECLARE_SKMESSAGEBUS_MESSAGE(GrTextBlobCache::PurgeBlobMessage) + +static inline bool SkShouldPostMessageToBus( + const GrTextBlobCache::PurgeBlobMessage& msg, uint32_t msgBusUniqueID) { + return msg.fContextID == msgBusUniqueID; +} + +GrTextBlobCache::~GrTextBlobCache() { + this->freeAll(); +} + +void GrTextBlobCache::freeAll() { + fBlobIDCache.foreach([this](uint32_t, BlobIDCacheEntry* entry) { + for (const auto& blob : entry->fBlobs) { + fBlobList.remove(blob.get()); + } + }); + + fBlobIDCache.reset(); + + fCurrentSize = 0; + + // There should be no allocations in the memory pool at this point + SkASSERT(fBlobList.isEmpty()); +} + +void GrTextBlobCache::PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID) { + SkASSERT(blobID != SK_InvalidGenID); + SkMessageBus::Post(PurgeBlobMessage(blobID, cacheID)); +} + +void GrTextBlobCache::purgeStaleBlobs() { + SkTArray msgs; + fPurgeBlobInbox.poll(&msgs); + + for (const auto& msg : msgs) { + auto* idEntry = fBlobIDCache.find(msg.fBlobID); + if (!idEntry) { + // no cache entries for id + continue; + } + + // remove all blob entries from the LRU list + for (const auto& blob : idEntry->fBlobs) { + fCurrentSize -= blob->size(); + fBlobList.remove(blob.get()); + } + + // drop the idEntry itself (unrefs all blobs) + fBlobIDCache.remove(msg.fBlobID); + } +} + +void GrTextBlobCache::checkPurge(GrTextBlob* blob) { + // First, purge all stale blob IDs. + this->purgeStaleBlobs(); + + // If we are still over budget, then unref until we are below budget again + if (fCurrentSize > fSizeBudget) { + BitmapBlobList::Iter iter; + iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart); + GrTextBlob* lruBlob = nullptr; + while (fCurrentSize > fSizeBudget && (lruBlob = iter.get()) && lruBlob != blob) { + // Backup the iterator before removing and unrefing the blob + iter.prev(); + + this->remove(lruBlob); + } + + // If we break out of the loop with lruBlob == blob, then we haven't purged enough + // use the call back and try to free some more. If we are still overbudget after this, + // then this single textblob is over our budget + if (blob && lruBlob == blob) { + (*fCallback)(fData); + } + +#ifdef SPEW_BUDGET_MESSAGE + if (fCurrentSize > fSizeBudget) { + SkDebugf("Single textblob is larger than our whole budget"); + } +#endif + } +} + + + diff --git a/gfx/skia/skia/src/gpu/text/GrTextBlobCache.h b/gfx/skia/skia/src/gpu/text/GrTextBlobCache.h new file mode 100644 index 0000000000..5479112085 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextBlobCache.h @@ -0,0 +1,191 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextBlobCache_DEFINED +#define GrTextBlobCache_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTHash.h" +#include "src/core/SkMessageBus.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/gpu/text/GrTextBlob.h" + +class GrTextBlobCache { +public: + /** + * The callback function used by the cache when it is still over budget after a purge. The + * passed in 'data' is the same 'data' handed to setOverbudgetCallback. + */ + typedef void (*PFOverBudgetCB)(void* data); + + GrTextBlobCache(PFOverBudgetCB cb, void* data, uint32_t uniqueID) + : fCallback(cb) + , fData(data) + , fSizeBudget(kDefaultBudget) + , fUniqueID(uniqueID) + , fPurgeBlobInbox(uniqueID) { + SkASSERT(cb && data); + } + ~GrTextBlobCache(); + + sk_sp makeBlob(const SkGlyphRunList& glyphRunList, + GrColor color, + GrStrikeCache* strikeCache) { + return GrTextBlob::Make( + glyphRunList.totalGlyphCount(), glyphRunList.size(), color, strikeCache); + } + + sk_sp makeCachedBlob(const SkGlyphRunList& glyphRunList, + const GrTextBlob::Key& key, + const SkMaskFilterBase::BlurRec& blurRec, + const SkPaint& paint, + GrColor color, + GrStrikeCache* strikeCache) { + sk_sp cacheBlob(makeBlob(glyphRunList, color, strikeCache)); + cacheBlob->setupKey(key, blurRec, paint); + this->add(cacheBlob); + glyphRunList.temporaryShuntBlobNotifyAddedToCache(fUniqueID); + return cacheBlob; + } + + sk_sp find(const GrTextBlob::Key& key) const { + const auto* idEntry = fBlobIDCache.find(key.fUniqueID); + return idEntry ? idEntry->find(key) : nullptr; + } + + void remove(GrTextBlob* blob) { + auto id = GrTextBlob::GetKey(*blob).fUniqueID; + auto* idEntry = fBlobIDCache.find(id); + SkASSERT(idEntry); + + fCurrentSize -= blob->size(); + fBlobList.remove(blob); + idEntry->removeBlob(blob); + if (idEntry->fBlobs.empty()) { + fBlobIDCache.remove(id); + } + } + + void makeMRU(GrTextBlob* blob) { + if (fBlobList.head() == blob) { + return; + } + + fBlobList.remove(blob); + fBlobList.addToHead(blob); + } + + void freeAll(); + + // TODO move to SkTextBlob + static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) { + SkTextBlobRunIterator itCounter(blob); + for (; !itCounter.done(); itCounter.next(), (*runCount)++) { + *glyphCount += itCounter.glyphCount(); + } + } + + void setBudget(size_t budget) { + fSizeBudget = budget; + this->checkPurge(); + } + + struct PurgeBlobMessage { + PurgeBlobMessage(uint32_t blobID, uint32_t contextUniqueID) + : fBlobID(blobID), fContextID(contextUniqueID) {} + + uint32_t fBlobID; + uint32_t fContextID; + }; + + static void PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID); + + void purgeStaleBlobs(); + + size_t usedBytes() const { return fCurrentSize; } + +private: + using BitmapBlobList = SkTInternalLList; + + struct BlobIDCacheEntry { + BlobIDCacheEntry() : fID(SK_InvalidGenID) {} + explicit BlobIDCacheEntry(uint32_t id) : fID(id) {} + + static uint32_t GetKey(const BlobIDCacheEntry& entry) { + return entry.fID; + } + + void addBlob(sk_sp blob) { + SkASSERT(blob); + SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID); + SkASSERT(!this->find(GrTextBlob::GetKey(*blob))); + + fBlobs.emplace_back(std::move(blob)); + } + + void removeBlob(GrTextBlob* blob) { + SkASSERT(blob); + SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID); + + auto index = this->findBlobIndex(GrTextBlob::GetKey(*blob)); + SkASSERT(index >= 0); + + fBlobs.removeShuffle(index); + } + + sk_sp find(const GrTextBlob::Key& key) const { + auto index = this->findBlobIndex(key); + return index < 0 ? nullptr : fBlobs[index]; + } + + int findBlobIndex(const GrTextBlob::Key& key) const{ + for (int i = 0; i < fBlobs.count(); ++i) { + if (GrTextBlob::GetKey(*fBlobs[i]) == key) { + return i; + } + } + return -1; + } + + uint32_t fID; + // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/ + // linear search is acceptable. If usage changes, we should re-evaluate this structure. + SkSTArray<1, sk_sp, true> fBlobs; + }; + + void add(sk_sp blob) { + auto id = GrTextBlob::GetKey(*blob).fUniqueID; + auto* idEntry = fBlobIDCache.find(id); + if (!idEntry) { + idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id)); + } + + // Safe to retain a raw ptr temporarily here, because the cache will hold a ref. + GrTextBlob* rawBlobPtr = blob.get(); + fBlobList.addToHead(rawBlobPtr); + fCurrentSize += blob->size(); + idEntry->addBlob(std::move(blob)); + + this->checkPurge(rawBlobPtr); + } + + void checkPurge(GrTextBlob* blob = nullptr); + + static const int kMinGrowthSize = 1 << 16; + static const int kDefaultBudget = 1 << 22; + BitmapBlobList fBlobList; + SkTHashMap fBlobIDCache; + PFOverBudgetCB fCallback; + void* fData; + size_t fSizeBudget; + size_t fCurrentSize{0}; + uint32_t fUniqueID; // unique id to use for messaging + SkMessageBus::Inbox fPurgeBlobInbox; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/text/GrTextBlobVertexRegenerator.cpp b/gfx/skia/skia/src/gpu/text/GrTextBlobVertexRegenerator.cpp new file mode 100644 index 0000000000..976beaf3d4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextBlobVertexRegenerator.cpp @@ -0,0 +1,287 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkDistanceFieldGen.h" +#include "src/gpu/ops/GrAtlasTextOp.h" +#include "src/gpu/text/GrAtlasManager.h" +#include "src/gpu/text/GrTextBlob.h" +#include "src/gpu/text/GrTextTarget.h" + +enum RegenMask { + kNoRegen = 0x0, + kRegenPos = 0x1, + kRegenCol = 0x2, + kRegenTex = 0x4, + kRegenGlyph = 0x8, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +static void regen_positions(char* vertex, size_t vertexStride, SkScalar transX, SkScalar transY) { + SkPoint* point = reinterpret_cast(vertex); + for (int i = 0; i < 4; ++i) { + point->fX += transX; + point->fY += transY; + point = SkTAddOffset(point, vertexStride); + } +} + +static void regen_colors(char* vertex, size_t vertexStride, GrColor color) { + // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color + // vertices, hence vertexStride - sizeof(SkIPoint16) + size_t colorOffset = vertexStride - sizeof(SkIPoint16) - sizeof(GrColor); + GrColor* vcolor = reinterpret_cast(vertex + colorOffset); + for (int i = 0; i < 4; ++i) { + *vcolor = color; + vcolor = SkTAddOffset(vcolor, vertexStride); + } +} + +static void regen_texcoords(char* vertex, size_t vertexStride, const GrGlyph* glyph, + bool useDistanceFields) { + // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color + // vertices, hence vertexStride - sizeof(SkIPoint16) + size_t texCoordOffset = vertexStride - sizeof(SkIPoint16); + + uint16_t u0, v0, u1, v1; + SkASSERT(glyph); + int width = glyph->fBounds.width(); + int height = glyph->fBounds.height(); + + if (useDistanceFields) { + u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset; + v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset; + u1 = u0 + width - 2 * SK_DistanceFieldInset; + v1 = v0 + height - 2 * SK_DistanceFieldInset; + } else { + u0 = glyph->fAtlasLocation.fX; + v0 = glyph->fAtlasLocation.fY; + u1 = u0 + width; + v1 = v0 + height; + } + // We pack the 2bit page index in the low bit of the u and v texture coords + uint32_t pageIndex = glyph->pageIndex(); + SkASSERT(pageIndex < 4); + uint16_t uBit = (pageIndex >> 1) & 0x1; + uint16_t vBit = pageIndex & 0x1; + u0 <<= 1; + u0 |= uBit; + v0 <<= 1; + v0 |= vBit; + u1 <<= 1; + u1 |= uBit; + v1 <<= 1; + v1 |= vBit; + + uint16_t* textureCoords = reinterpret_cast(vertex + texCoordOffset); + textureCoords[0] = u0; + textureCoords[1] = v0; + textureCoords = SkTAddOffset(textureCoords, vertexStride); + textureCoords[0] = u0; + textureCoords[1] = v1; + textureCoords = SkTAddOffset(textureCoords, vertexStride); + textureCoords[0] = u1; + textureCoords[1] = v0; + textureCoords = SkTAddOffset(textureCoords, vertexStride); + textureCoords[0] = u1; + textureCoords[1] = v1; + +#ifdef DISPLAY_PAGE_INDEX + // Enable this to visualize the page from which each glyph is being drawn. + // Green Red Magenta Cyan -> 0 1 2 3; Black -> error + GrColor hackColor; + switch (pageIndex) { + case 0: + hackColor = GrColorPackRGBA(0, 255, 0, 255); + break; + case 1: + hackColor = GrColorPackRGBA(255, 0, 0, 255);; + break; + case 2: + hackColor = GrColorPackRGBA(255, 0, 255, 255); + break; + case 3: + hackColor = GrColorPackRGBA(0, 255, 255, 255); + break; + default: + hackColor = GrColorPackRGBA(0, 0, 0, 255); + break; + } + regen_colors(vertex, vertexStride, hackColor); +#endif +} + +GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourceProvider, + GrTextBlob* blob, + int runIdx, int subRunIdx, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y, + GrColor color, + GrDeferredUploadTarget* uploadTarget, + GrStrikeCache* glyphCache, + GrAtlasManager* fullAtlasManager, + SkExclusiveStrikePtr* lazyStrike) + : fResourceProvider(resourceProvider) + , fViewMatrix(viewMatrix) + , fBlob(blob) + , fUploadTarget(uploadTarget) + , fGlyphCache(glyphCache) + , fFullAtlasManager(fullAtlasManager) + , fLazyStrike(lazyStrike) + , fSubRun(&blob->fRuns[runIdx].fSubRunInfo[subRunIdx]) + , fColor(color) { + // Compute translation if any + fSubRun->computeTranslation(fViewMatrix, x, y, &fTransX, &fTransY); + + // Because the GrStrikeCache may evict the strike a blob depends on using for + // generating its texture coords, we have to track whether or not the strike has + // been abandoned. If it hasn't been abandoned, then we can use the GrGlyph*s as is + // otherwise we have to get the new strike, and use that to get the correct glyphs. + // Because we do not have the packed ids, and thus can't look up our glyphs in the + // new strike, we instead keep our ref to the old strike and use the packed ids from + // it. These ids will still be valid as long as we hold the ref. When we are done + // updating our cache of the GrGlyph*s, we drop our ref on the old strike + if (fSubRun->strike()->isAbandoned()) { + fRegenFlags |= kRegenGlyph; + fRegenFlags |= kRegenTex; + } + if (kARGB_GrMaskFormat != fSubRun->maskFormat() && fSubRun->color() != color) { + fRegenFlags |= kRegenCol; + } + if (0.f != fTransX || 0.f != fTransY) { + fRegenFlags |= kRegenPos; + } +} + +bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Result* result, + bool regenPos, bool regenCol, bool regenTexCoords, + bool regenGlyphs) { + SkASSERT(!regenGlyphs || regenTexCoords); + sk_sp strike; + if (regenTexCoords) { + fSubRun->resetBulkUseToken(); + + const SkStrikeSpec& strikeSpec = fSubRun->strikeSpec(); + + if (!*fLazyStrike || (*fLazyStrike)->getDescriptor() != strikeSpec.descriptor()) { + *fLazyStrike = + strikeSpec.findOrCreateExclusiveStrike(SkStrikeCache::GlobalStrikeCache()); + } + + if (regenGlyphs) { + strike = strikeSpec.findOrCreateGrStrike(fGlyphCache); + } else { + strike = fSubRun->refStrike(); + } + } + + bool hasW = fSubRun->hasWCoord(); + auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW); + char* currVertex = fBlob->fVertices + fSubRun->vertexStartIndex() + + fCurrGlyph * kVerticesPerGlyph * vertexStride; + result->fFirstVertex = currVertex; + + for (int glyphIdx = fCurrGlyph; glyphIdx < (int)fSubRun->glyphCount(); glyphIdx++) { + GrGlyph* glyph = nullptr; + if (regenTexCoords) { + size_t glyphOffset = glyphIdx + fSubRun->glyphStartIndex(); + + if (regenGlyphs) { + // Get the id from the old glyph, and use the new strike to lookup + // the glyph. + SkPackedGlyphID id = fBlob->fGlyphs[glyphOffset]->fPackedID; + fBlob->fGlyphs[glyphOffset] = strike->getGlyph(id, fLazyStrike->get()); + SkASSERT(id == fBlob->fGlyphs[glyphOffset]->fPackedID); + } + glyph = fBlob->fGlyphs[glyphOffset]; + SkASSERT(glyph && glyph->fMaskFormat == fSubRun->maskFormat()); + + if (!fFullAtlasManager->hasGlyph(glyph)) { + GrDrawOpAtlas::ErrorCode code; + code = strike->addGlyphToAtlas(fResourceProvider, fUploadTarget, fGlyphCache, + fFullAtlasManager, glyph, + fLazyStrike->get(), fSubRun->maskFormat(), + fSubRun->needsTransform()); + if (GrDrawOpAtlas::ErrorCode::kError == code) { + // Something horrible has happened - drop the op + return false; + } + else if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) { + fBrokenRun = glyphIdx > 0; + result->fFinished = false; + return true; + } + } + auto tokenTracker = fUploadTarget->tokenTracker(); + fFullAtlasManager->addGlyphToBulkAndSetUseToken(fSubRun->bulkUseToken(), glyph, + tokenTracker->nextDrawToken()); + } + + if (regenPos) { + regen_positions(currVertex, vertexStride, fTransX, fTransY); + } + if (regenCol) { + regen_colors(currVertex, vertexStride, fColor); + } + if (regenTexCoords) { + regen_texcoords(currVertex, vertexStride, glyph, fSubRun->drawAsDistanceFields()); + } + + currVertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph; + ++result->fGlyphsRegenerated; + ++fCurrGlyph; + } + + // We may have changed the color so update it here + fSubRun->setColor(fColor); + if (regenTexCoords) { + if (regenGlyphs) { + fSubRun->setStrike(std::move(strike)); + } + fSubRun->setAtlasGeneration(fBrokenRun + ? GrDrawOpAtlas::kInvalidAtlasGeneration + : fFullAtlasManager->atlasGeneration(fSubRun->maskFormat())); + } else { + // For the non-texCoords case we need to ensure that we update the associated use tokens + fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(), + fUploadTarget->tokenTracker()->nextDrawToken(), + fSubRun->maskFormat()); + } + return true; +} + +bool GrTextBlob::VertexRegenerator::regenerate(GrTextBlob::VertexRegenerator::Result* result) { + uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat()); + // If regenerate() is called multiple times then the atlas gen may have changed. So we check + // this each time. + if (fSubRun->atlasGeneration() != currentAtlasGen) { + fRegenFlags |= kRegenTex; + } + + if (fRegenFlags) { + return this->doRegen(result, + fRegenFlags & kRegenPos, + fRegenFlags & kRegenCol, + fRegenFlags & kRegenTex, + fRegenFlags & kRegenGlyph); + } else { + bool hasW = fSubRun->hasWCoord(); + auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW); + result->fFinished = true; + result->fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph; + result->fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() + + fCurrGlyph * kVerticesPerGlyph * vertexStride; + fCurrGlyph = fSubRun->glyphCount(); + + // set use tokens for all of the glyphs in our subrun. This is only valid if we + // have a valid atlas generation + fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(), + fUploadTarget->tokenTracker()->nextDrawToken(), + fSubRun->maskFormat()); + return true; + } + SK_ABORT("Should not get here"); +} diff --git a/gfx/skia/skia/src/gpu/text/GrTextContext.cpp b/gfx/skia/skia/src/gpu/text/GrTextContext.cpp new file mode 100644 index 0000000000..f1ecef43b6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextContext.cpp @@ -0,0 +1,270 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/text/GrTextContext.h" + +#include "include/core/SkGraphics.h" +#include "include/gpu/GrContext.h" +#include "include/private/SkTo.h" +#include "src/core/SkDistanceFieldGen.h" +#include "src/core/SkDraw.h" +#include "src/core/SkDrawProcs.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkPaintPriv.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/ops/GrMeshDrawOp.h" +#include "src/gpu/text/GrSDFMaskFilter.h" +#include "src/gpu/text/GrTextBlobCache.h" + +// DF sizes and thresholds for usage of the small and medium sizes. For example, above +// kSmallDFFontLimit we will use the medium size. The large size is used up until the size at +// which we switch over to drawing as paths as controlled by Options. +static const int kSmallDFFontSize = 32; +static const int kSmallDFFontLimit = 32; +static const int kMediumDFFontSize = 72; +static const int kMediumDFFontLimit = 72; +static const int kLargeDFFontSize = 162; + +static const int kDefaultMinDistanceFieldFontSize = 18; +#ifdef SK_BUILD_FOR_ANDROID +static const int kDefaultMaxDistanceFieldFontSize = 384; +#else +static const int kDefaultMaxDistanceFieldFontSize = 2 * kLargeDFFontSize; +#endif + +GrTextContext::GrTextContext(const Options& options) + : fDistanceAdjustTable(new GrDistanceFieldAdjustTable), fOptions(options) { + SanitizeOptions(&fOptions); +} + +std::unique_ptr GrTextContext::Make(const Options& options) { + return std::unique_ptr(new GrTextContext(options)); +} + +SkColor GrTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) { + SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint); + if (lcd) { + // This is the correct computation, but there are tons of cases where LCD can be overridden. + // For now we just regenerate if any run in a textblob has LCD. + // TODO figure out where all of these overrides are and see if we can incorporate that logic + // at a higher level *OR* use sRGB + SkASSERT(false); + //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor); + } else { + // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have + // gamma corrected masks anyways, nor color + U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor), + SkColorGetG(canonicalColor), + SkColorGetB(canonicalColor)); + // reduce to our finite number of bits + canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum)); + } + return canonicalColor; +} + +SkScalerContextFlags GrTextContext::ComputeScalerContextFlags(const GrColorInfo& colorInfo) { + // If we're doing linear blending, then we can disable the gamma hacks. + // Otherwise, leave them on. In either case, we still want the contrast boost: + // TODO: Can we be even smarter about mask gamma based on the dest transfer function? + if (colorInfo.isLinearlyBlended()) { + return SkScalerContextFlags::kBoostContrast; + } else { + return SkScalerContextFlags::kFakeGammaAndBoostContrast; + } +} + +void GrTextContext::SanitizeOptions(Options* options) { + if (options->fMaxDistanceFieldFontSize < 0.f) { + options->fMaxDistanceFieldFontSize = kDefaultMaxDistanceFieldFontSize; + } + if (options->fMinDistanceFieldFontSize < 0.f) { + options->fMinDistanceFieldFontSize = kDefaultMinDistanceFieldFontSize; + } +} + +bool GrTextContext::CanDrawAsDistanceFields(const SkPaint& paint, const SkFont& font, + const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + bool contextSupportsDistanceFieldText, + const Options& options) { + // mask filters modify alpha, which doesn't translate well to distance + if (paint.getMaskFilter() || !contextSupportsDistanceFieldText) { + return false; + } + + // TODO: add some stroking support + if (paint.getStyle() != SkPaint::kFill_Style) { + return false; + } + + if (viewMatrix.hasPerspective()) { + if (!options.fDistanceFieldVerticesAlwaysHaveW) { + return false; + } + } else { + SkScalar maxScale = viewMatrix.getMaxScale(); + SkScalar scaledTextSize = maxScale * font.getSize(); + // Hinted text looks far better at small resolutions + // Scaling up beyond 2x yields undesireable artifacts + if (scaledTextSize < options.fMinDistanceFieldFontSize || + scaledTextSize > options.fMaxDistanceFieldFontSize) { + return false; + } + + bool useDFT = props.isUseDeviceIndependentFonts(); +#if SK_FORCE_DISTANCE_FIELD_TEXT + useDFT = true; +#endif + + if (!useDFT && scaledTextSize < kLargeDFFontSize) { + return false; + } + } + + return true; +} + +SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) { + SkScalar scaledTextSize = textSize; + + if (viewMatrix.hasPerspective()) { + // for perspective, we simply force to the medium size + // TODO: compute a size based on approximate screen area + scaledTextSize = kMediumDFFontLimit; + } else { + SkScalar maxScale = viewMatrix.getMaxScale(); + // if we have non-unity scale, we need to choose our base text size + // based on the SkPaint's text size multiplied by the max scale factor + // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? + if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { + scaledTextSize *= maxScale; + } + } + + return scaledTextSize; +} + +SkFont GrTextContext::InitDistanceFieldFont(const SkFont& font, + const SkMatrix& viewMatrix, + const Options& options, + SkScalar* textRatio) { + SkScalar textSize = font.getSize(); + SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix); + + SkFont dfFont{font}; + + if (scaledTextSize <= kSmallDFFontLimit) { + *textRatio = textSize / kSmallDFFontSize; + dfFont.setSize(SkIntToScalar(kSmallDFFontSize)); + } else if (scaledTextSize <= kMediumDFFontLimit) { + *textRatio = textSize / kMediumDFFontSize; + dfFont.setSize(SkIntToScalar(kMediumDFFontSize)); + } else { + *textRatio = textSize / kLargeDFFontSize; + dfFont.setSize(SkIntToScalar(kLargeDFFontSize)); + } + + dfFont.setEdging(SkFont::Edging::kAntiAlias); + dfFont.setForceAutoHinting(false); + dfFont.setHinting(SkFontHinting::kNormal); + + // The sub-pixel position will always happen when transforming to the screen. + dfFont.setSubpixel(false); + return dfFont; +} + +std::pair GrTextContext::InitDistanceFieldMinMaxScale( + SkScalar textSize, + const SkMatrix& viewMatrix, + const GrTextContext::Options& options) { + + SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix); + + // We have three sizes of distance field text, and within each size 'bucket' there is a floor + // and ceiling. A scale outside of this range would require regenerating the distance fields + SkScalar dfMaskScaleFloor; + SkScalar dfMaskScaleCeil; + if (scaledTextSize <= kSmallDFFontLimit) { + dfMaskScaleFloor = options.fMinDistanceFieldFontSize; + dfMaskScaleCeil = kSmallDFFontLimit; + } else if (scaledTextSize <= kMediumDFFontLimit) { + dfMaskScaleFloor = kSmallDFFontLimit; + dfMaskScaleCeil = kMediumDFFontLimit; + } else { + dfMaskScaleFloor = kMediumDFFontLimit; + dfMaskScaleCeil = options.fMaxDistanceFieldFontSize; + } + + // Because there can be multiple runs in the blob, we want the overall maxMinScale, and + // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale + // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can + // tolerate before we'd have to move to a large mip size. When we actually test these values + // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test + // against these values to decide if we can reuse or not(ie, will a given scale change our mip + // level) + SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil); + + return std::make_pair(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize); +} + +SkPaint GrTextContext::InitDistanceFieldPaint(const SkPaint& paint) { + SkPaint dfPaint{paint}; + dfPaint.setMaskFilter(GrSDFMaskFilter::Make()); + return dfPaint; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS + +#include "src/gpu/GrRenderTargetContext.h" + +GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) { + static uint32_t gContextID = SK_InvalidGenID; + static std::unique_ptr gTextContext; + static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType); + + if (context->priv().contextID() != gContextID) { + gContextID = context->priv().contextID(); + gTextContext = GrTextContext::Make(GrTextContext::Options()); + } + + // Setup dummy SkPaint / GrPaint / GrRenderTargetContext + auto rtc = context->priv().makeDeferredRenderTargetContext(SkBackingFit::kApprox, 1024, 1024, + GrColorType::kRGBA_8888, nullptr); + + SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); + + SkPaint skPaint; + skPaint.setColor(random->nextU()); + + SkFont font; + if (random->nextBool()) { + font.setEdging(SkFont::Edging::kSubpixelAntiAlias); + } else { + font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias); + } + font.setSubpixel(random->nextBool()); + + const char* text = "The quick brown fox jumps over the lazy dog."; + + // create some random x/y offsets, including negative offsets + static const int kMaxTrans = 1024; + int xPos = (random->nextU() % 2) * 2 - 1; + int yPos = (random->nextU() % 2) * 2 - 1; + int xInt = (random->nextU() % kMaxTrans) * xPos; + int yInt = (random->nextU() % kMaxTrans) * yPos; + + return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(), + skPaint, font, viewMatrix, text, xInt, yInt); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/text/GrTextContext.h b/gfx/skia/skia/src/gpu/text/GrTextContext.h new file mode 100644 index 0000000000..1cb973445c --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextContext.h @@ -0,0 +1,98 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextContext_DEFINED +#define GrTextContext_DEFINED + +#include "src/core/SkGlyphRun.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/text/GrDistanceFieldAdjustTable.h" +#include "src/gpu/text/GrTextTarget.h" + +#if GR_TEST_UTILS +#include "src/gpu/GrDrawOpTest.h" +#endif + +class GrDrawOp; +class GrRecordingContext; +class GrTextBlobCache; +class SkGlyph; +class GrTextBlob; + +/* + * Renders text using some kind of an atlas, ie BitmapText or DistanceField text + */ +class GrTextContext { +public: + struct Options { + /** + * Below this size (in device space) distance field text will not be used. Negative means + * use a default value. + */ + SkScalar fMinDistanceFieldFontSize = -1.f; + /** + * Above this size (in device space) distance field text will not be used and glyphs will + * be rendered from outline as individual paths. Negative means use a default value. + */ + SkScalar fMaxDistanceFieldFontSize = -1.f; + /** Forces all distance field vertices to use 3 components, not just when in perspective. */ + bool fDistanceFieldVerticesAlwaysHaveW = false; + }; + + static std::unique_ptr Make(const Options& options); + + void drawGlyphRunList(GrRecordingContext*, GrTextTarget*, const GrClip&, + const SkMatrix& viewMatrix, const SkSurfaceProps&, const SkGlyphRunList&); + + std::unique_ptr createOp_TestingOnly(GrRecordingContext*, + GrTextContext*, + GrRenderTargetContext*, + const SkPaint&, const SkFont&, + const SkMatrix& viewMatrix, + const char* text, + int x, + int y); + + static void SanitizeOptions(Options* options); + static bool CanDrawAsDistanceFields(const SkPaint&, const SkFont&, const SkMatrix& viewMatrix, + const SkSurfaceProps& props, + bool contextSupportsDistanceFieldText, + const Options& options); + + static SkFont InitDistanceFieldFont(const SkFont& font, + const SkMatrix& viewMatrix, + const Options& options, + SkScalar* textRatio); + + static SkPaint InitDistanceFieldPaint(const SkPaint& paint); + + static std::pair InitDistanceFieldMinMaxScale(SkScalar textSize, + const SkMatrix& viewMatrix, + const Options& options); + +private: + GrTextContext(const Options& options); + + // sets up the descriptor on the blob and returns a detached cache. Client must attach + static SkColor ComputeCanonicalColor(const SkPaint&, bool lcd); + // Determines if we need to use fake gamma (and contrast boost): + static SkScalerContextFlags ComputeScalerContextFlags(const GrColorInfo&); + + const GrDistanceFieldAdjustTable* dfAdjustTable() const { return fDistanceAdjustTable.get(); } + + sk_sp fDistanceAdjustTable; + + Options fOptions; + +#if GR_TEST_UTILS + static const SkScalerContextFlags kTextBlobOpScalerContextFlags = + SkScalerContextFlags::kFakeGammaAndBoostContrast; + GR_DRAW_OP_TEST_FRIEND(GrAtlasTextOp); +#endif +}; + +#endif // GrTextContext_DEFINED diff --git a/gfx/skia/skia/src/gpu/text/GrTextTarget.h b/gfx/skia/skia/src/gpu/text/GrTextTarget.h new file mode 100644 index 0000000000..8498d717c2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/text/GrTextTarget.h @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrTextTarget_DEFINED +#define GrTextTarget_DEFINED + +#include "include/core/SkPaint.h" +#include "src/gpu/GrColorInfo.h" + +class GrAtlasTextOp; +class GrClip; +class GrPaint; +class GrRecordingContext; +class GrShape; +class SkGlyphRunListPainter; +class SkMatrix; +struct SkIRect; + +class GrTextTarget { +public: + virtual ~GrTextTarget() = default; + + int width() const { return fWidth; } + + int height() const { return fHeight; } + + const GrColorInfo& colorInfo() const { return fColorInfo; } + + virtual void addDrawOp(const GrClip&, std::unique_ptr op) = 0; + + virtual void drawShape(const GrClip&, const SkPaint&, + const SkMatrix& viewMatrix, const GrShape&) = 0; + + virtual void makeGrPaint(GrMaskFormat, const SkPaint&, const SkMatrix& viewMatrix, + GrPaint*) = 0; + + virtual GrRecordingContext* getContext() = 0; + + virtual SkGlyphRunListPainter* glyphPainter() = 0; + +protected: + GrTextTarget(int width, int height, const GrColorInfo& colorInfo) + : fWidth(width), fHeight(height), fColorInfo(colorInfo) { + SkASSERT(kPremul_SkAlphaType == colorInfo.alphaType()); + } + +private: + int fWidth; + int fHeight; + const GrColorInfo& fColorInfo; +}; +#endif // GrTextTarget_DEFINED diff --git a/gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp b/gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp new file mode 100644 index 0000000000..65577bcba8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp @@ -0,0 +1,275 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkAMDMemoryAllocator.h" + +#include "src/core/SkTraceEvent.h" +#include "src/gpu/vk/GrVkInterface.h" +#include "src/gpu/vk/GrVkMemory.h" +#include "src/gpu/vk/GrVkUtil.h" + +GrVkAMDMemoryAllocator::GrVkAMDMemoryAllocator(VkPhysicalDevice physicalDevice, + VkDevice device, + sk_sp interface) + : fAllocator(VK_NULL_HANDLE) + , fInterface(std::move(interface)) + , fDevice(device) { +#define GR_COPY_FUNCTION(NAME) functions.vk##NAME = fInterface->fFunctions.f##NAME + + VmaVulkanFunctions functions; + GR_COPY_FUNCTION(GetPhysicalDeviceProperties); + GR_COPY_FUNCTION(GetPhysicalDeviceMemoryProperties); + GR_COPY_FUNCTION(AllocateMemory); + GR_COPY_FUNCTION(FreeMemory); + GR_COPY_FUNCTION(MapMemory); + GR_COPY_FUNCTION(UnmapMemory); + GR_COPY_FUNCTION(BindBufferMemory); + GR_COPY_FUNCTION(BindImageMemory); + GR_COPY_FUNCTION(GetBufferMemoryRequirements); + GR_COPY_FUNCTION(GetImageMemoryRequirements); + GR_COPY_FUNCTION(CreateBuffer); + GR_COPY_FUNCTION(DestroyBuffer); + GR_COPY_FUNCTION(CreateImage); + GR_COPY_FUNCTION(DestroyImage); + + // Skia current doesn't support VK_KHR_dedicated_allocation + functions.vkGetBufferMemoryRequirements2KHR = nullptr; + functions.vkGetImageMemoryRequirements2KHR = nullptr; + + VmaAllocatorCreateInfo info; + info.flags = 0; + info.physicalDevice = physicalDevice; + info.device = device; + // 4MB was picked for the size here by looking at memory usage of Android apps and runs of DM. + // It seems to be a good compromise of not wasting unused allocated space and not making too + // many small allocations. The AMD allocator will start making blocks at 1/8 the max size and + // builds up block size as needed before capping at the max set here. + info.preferredLargeHeapBlockSize = 4*1024*1024; + info.pAllocationCallbacks = nullptr; + info.pDeviceMemoryCallbacks = nullptr; + info.frameInUseCount = 0; + info.pHeapSizeLimit = nullptr; + info.pVulkanFunctions = &functions; + + vmaCreateAllocator(&info, &fAllocator); +} + +GrVkAMDMemoryAllocator::~GrVkAMDMemoryAllocator() { + vmaDestroyAllocator(fAllocator); + fAllocator = VK_NULL_HANDLE; +} + +bool GrVkAMDMemoryAllocator::allocateMemoryForImage(VkImage image, AllocationPropertyFlags flags, + GrVkBackendMemory* backendMemory) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + VmaAllocationCreateInfo info; + info.flags = 0; + info.usage = VMA_MEMORY_USAGE_UNKNOWN; + info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + info.preferredFlags = 0; + info.memoryTypeBits = 0; + info.pool = VK_NULL_HANDLE; + info.pUserData = nullptr; + + if (AllocationPropertyFlags::kDedicatedAllocation & flags) { + info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } + + if (AllocationPropertyFlags::kLazyAllocation & flags) { + info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; + } + + if (AllocationPropertyFlags::kProtected & flags) { + info.requiredFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT; + } + + VmaAllocation allocation; + VkResult result = vmaAllocateMemoryForImage(fAllocator, image, &info, &allocation, nullptr); + if (VK_SUCCESS != result) { + return false; + } + *backendMemory = (GrVkBackendMemory)allocation; + return true; +} + +bool GrVkAMDMemoryAllocator::allocateMemoryForBuffer(VkBuffer buffer, BufferUsage usage, + AllocationPropertyFlags flags, + GrVkBackendMemory* backendMemory) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + VmaAllocationCreateInfo info; + info.flags = 0; + info.usage = VMA_MEMORY_USAGE_UNKNOWN; + info.memoryTypeBits = 0; + info.pool = VK_NULL_HANDLE; + info.pUserData = nullptr; + + switch (usage) { + case BufferUsage::kGpuOnly: + info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + info.preferredFlags = 0; + break; + case BufferUsage::kCpuOnly: + info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + break; + case BufferUsage::kCpuWritesGpuReads: + // First attempt to try memory is also cached + info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + break; + case BufferUsage::kGpuWritesCpuReads: + info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + info.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + break; + } + + if (AllocationPropertyFlags::kDedicatedAllocation & flags) { + info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } + + if ((AllocationPropertyFlags::kLazyAllocation & flags) && BufferUsage::kGpuOnly == usage) { + info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; + } + + if (AllocationPropertyFlags::kPersistentlyMapped & flags) { + SkASSERT(BufferUsage::kGpuOnly != usage); + info.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; + } + + VmaAllocation allocation; + VkResult result = vmaAllocateMemoryForBuffer(fAllocator, buffer, &info, &allocation, nullptr); + if (VK_SUCCESS != result) { + if (usage == BufferUsage::kCpuWritesGpuReads) { + // We try again but this time drop the requirement for cached + info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + result = vmaAllocateMemoryForBuffer(fAllocator, buffer, &info, &allocation, nullptr); + } + } + if (VK_SUCCESS != result) { + return false; + } + + *backendMemory = (GrVkBackendMemory)allocation; + return true; +} + +void GrVkAMDMemoryAllocator::freeMemory(const GrVkBackendMemory& memoryHandle) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + const VmaAllocation allocation = (const VmaAllocation)memoryHandle; + vmaFreeMemory(fAllocator, allocation); +} + +void GrVkAMDMemoryAllocator::getAllocInfo(const GrVkBackendMemory& memoryHandle, + GrVkAlloc* alloc) const { + const VmaAllocation allocation = (const VmaAllocation)memoryHandle; + VmaAllocationInfo vmaInfo; + vmaGetAllocationInfo(fAllocator, allocation, &vmaInfo); + + VkMemoryPropertyFlags memFlags; + vmaGetMemoryTypeProperties(fAllocator, vmaInfo.memoryType, &memFlags); + + uint32_t flags = 0; + if (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT & memFlags) { + flags |= GrVkAlloc::kMappable_Flag; + } + if (!SkToBool(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT & memFlags)) { + flags |= GrVkAlloc::kNoncoherent_Flag; + } + + alloc->fMemory = vmaInfo.deviceMemory; + alloc->fOffset = vmaInfo.offset; + alloc->fSize = vmaInfo.size; + alloc->fFlags = flags; + alloc->fBackendMemory = memoryHandle; + + // TODO: Remove this hack once the AMD allocator is able to handle the alignment of noncoherent + // memory itself. + if (!SkToBool(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT & memFlags)) { + // This is a hack to say that the allocation size is actually larger than it is. This is to + // make sure when we are flushing and invalidating noncoherent memory we have a size that is + // aligned to the nonCoherentAtomSize. This is safe for three reasons. First the total size + // of the VkDeviceMemory we allocate will always be a multple of the max possible alignment + // (currently 256). Second all sub allocations are alignmed with an offset of 256. And + // finally the allocator we are using always maps the entire VkDeviceMemory so the range + // we'll be flushing/invalidating will be mapped. So our new fake allocation size will + // always fit into the VkDeviceMemory, will never push it into another suballocation, and + // will always be mapped when map is called. + const VkPhysicalDeviceProperties* devProps; + vmaGetPhysicalDeviceProperties(fAllocator, &devProps); + VkDeviceSize alignment = devProps->limits.nonCoherentAtomSize; + + alloc->fSize = (alloc->fSize + alignment - 1) & ~(alignment -1); + } +} + +void* GrVkAMDMemoryAllocator::mapMemory(const GrVkBackendMemory& memoryHandle) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + const VmaAllocation allocation = (const VmaAllocation)memoryHandle; + void* mapPtr; + vmaMapMemory(fAllocator, allocation, &mapPtr); + return mapPtr; +} + +void GrVkAMDMemoryAllocator::unmapMemory(const GrVkBackendMemory& memoryHandle) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + const VmaAllocation allocation = (const VmaAllocation)memoryHandle; + vmaUnmapMemory(fAllocator, allocation); +} + +void GrVkAMDMemoryAllocator::flushMappedMemory(const GrVkBackendMemory& memoryHandle, + VkDeviceSize offset, VkDeviceSize size) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + GrVkAlloc info; + this->getAllocInfo(memoryHandle, &info); + + if (GrVkAlloc::kNoncoherent_Flag & info.fFlags) { + // We need to store the nonCoherentAtomSize for non-coherent flush/invalidate alignment. + const VkPhysicalDeviceProperties* physDevProps; + vmaGetPhysicalDeviceProperties(fAllocator, &physDevProps); + VkDeviceSize alignment = physDevProps->limits.nonCoherentAtomSize; + + VkMappedMemoryRange mappedMemoryRange; + GrVkMemory::GetNonCoherentMappedMemoryRange(info, offset, size, alignment, + &mappedMemoryRange); + GR_VK_CALL(fInterface, FlushMappedMemoryRanges(fDevice, 1, &mappedMemoryRange)); + } +} + +void GrVkAMDMemoryAllocator::invalidateMappedMemory(const GrVkBackendMemory& memoryHandle, + VkDeviceSize offset, VkDeviceSize size) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + GrVkAlloc info; + this->getAllocInfo(memoryHandle, &info); + + if (GrVkAlloc::kNoncoherent_Flag & info.fFlags) { + // We need to store the nonCoherentAtomSize for non-coherent flush/invalidate alignment. + const VkPhysicalDeviceProperties* physDevProps; + vmaGetPhysicalDeviceProperties(fAllocator, &physDevProps); + VkDeviceSize alignment = physDevProps->limits.nonCoherentAtomSize; + + VkMappedMemoryRange mappedMemoryRange; + GrVkMemory::GetNonCoherentMappedMemoryRange(info, offset, size, alignment, + &mappedMemoryRange); + GR_VK_CALL(fInterface, InvalidateMappedMemoryRanges(fDevice, 1, &mappedMemoryRange)); + } +} + +uint64_t GrVkAMDMemoryAllocator::totalUsedMemory() const { + VmaStats stats; + vmaCalculateStats(fAllocator, &stats); + return stats.total.usedBytes; +} + +uint64_t GrVkAMDMemoryAllocator::totalAllocatedMemory() const { + VmaStats stats; + vmaCalculateStats(fAllocator, &stats); + return stats.total.usedBytes + stats.total.unusedBytes; +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h b/gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h new file mode 100644 index 0000000000..5d93dea63b --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkAMDMemoryAllocator_DEFINED +#define GrVkAMDMemoryAllocator_DEFINED + + +#include "include/gpu/vk/GrVkMemoryAllocator.h" + +#include "GrVulkanMemoryAllocator.h" + +struct GrVkInterface; + +class GrVkAMDMemoryAllocator : public GrVkMemoryAllocator { +public: + GrVkAMDMemoryAllocator(VkPhysicalDevice physicalDevice, VkDevice device, + sk_sp interface); + + ~GrVkAMDMemoryAllocator() override; + + bool allocateMemoryForImage(VkImage image, AllocationPropertyFlags flags, GrVkBackendMemory*) override; + + bool allocateMemoryForBuffer(VkBuffer buffer, BufferUsage usage, + AllocationPropertyFlags flags, GrVkBackendMemory*) override; + + void freeMemory(const GrVkBackendMemory&) override; + + void getAllocInfo(const GrVkBackendMemory&, GrVkAlloc*) const override; + + void* mapMemory(const GrVkBackendMemory&) override; + void unmapMemory(const GrVkBackendMemory&) override; + + void flushMappedMemory(const GrVkBackendMemory&, VkDeviceSize offset, + VkDeviceSize size) override; + void invalidateMappedMemory(const GrVkBackendMemory&, VkDeviceSize offset, + VkDeviceSize size) override; + + uint64_t totalUsedMemory() const override; + uint64_t totalAllocatedMemory() const override; + +private: + VmaAllocator fAllocator; + + // If a future version of the AMD allocator has helper functions for flushing and invalidating + // memory, then we won't need to save the GrVkInterface here since we won't need to make direct + // vulkan calls. + sk_sp fInterface; + VkDevice fDevice; + + typedef GrVkMemoryAllocator INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkBuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkBuffer.cpp new file mode 100644 index 0000000000..833dfdb5dc --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkBuffer.cpp @@ -0,0 +1,268 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkBuffer.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkMemory.h" +#include "src/gpu/vk/GrVkTransferBuffer.h" +#include "src/gpu/vk/GrVkUtil.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +#ifdef SK_DEBUG +#define VALIDATE() this->validate() +#else +#define VALIDATE() do {} while(false) +#endif + +const GrVkBuffer::Resource* GrVkBuffer::Create(const GrVkGpu* gpu, const Desc& desc) { + SkASSERT(!gpu->protectedContext() || (gpu->protectedContext() == desc.fDynamic)); + VkBuffer buffer; + GrVkAlloc alloc; + + // create the buffer object + VkBufferCreateInfo bufInfo; + memset(&bufInfo, 0, sizeof(VkBufferCreateInfo)); + bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufInfo.flags = 0; + bufInfo.size = desc.fSizeInBytes; + switch (desc.fType) { + case kVertex_Type: + bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + break; + case kIndex_Type: + bufInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + break; + case kUniform_Type: + bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + break; + case kCopyRead_Type: + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + break; + case kCopyWrite_Type: + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + break; + case kTexel_Type: + bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; + } + if (!desc.fDynamic) { + bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + } + + bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufInfo.queueFamilyIndexCount = 0; + bufInfo.pQueueFamilyIndices = nullptr; + + VkResult err; + err = VK_CALL(gpu, CreateBuffer(gpu->device(), &bufInfo, nullptr, &buffer)); + if (err) { + return nullptr; + } + + if (!GrVkMemory::AllocAndBindBufferMemory(gpu, + buffer, + desc.fType, + desc.fDynamic, + &alloc)) { + return nullptr; + } + + const GrVkBuffer::Resource* resource = new GrVkBuffer::Resource(buffer, alloc, desc.fType); + if (!resource) { + VK_CALL(gpu, DestroyBuffer(gpu->device(), buffer, nullptr)); + GrVkMemory::FreeBufferMemory(gpu, desc.fType, alloc); + return nullptr; + } + + return resource; +} + +void GrVkBuffer::addMemoryBarrier(const GrVkGpu* gpu, + VkAccessFlags srcAccessMask, + VkAccessFlags dstAccesMask, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion) const { + VkBufferMemoryBarrier bufferMemoryBarrier = { + VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, // sType + nullptr, // pNext + srcAccessMask, // srcAccessMask + dstAccesMask, // dstAccessMask + VK_QUEUE_FAMILY_IGNORED, // srcQueueFamilyIndex + VK_QUEUE_FAMILY_IGNORED, // dstQueueFamilyIndex + this->buffer(), // buffer + 0, // offset + fDesc.fSizeInBytes, // size + }; + + // TODO: restrict to area of buffer we're interested in + gpu->addBufferMemoryBarrier(this->resource(), srcStageMask, dstStageMask, byRegion, + &bufferMemoryBarrier); +} + +void GrVkBuffer::Resource::freeGPUData(GrVkGpu* gpu) const { + SkASSERT(fBuffer); + SkASSERT(fAlloc.fMemory); + VK_CALL(gpu, DestroyBuffer(gpu->device(), fBuffer, nullptr)); + GrVkMemory::FreeBufferMemory(gpu, fType, fAlloc); +} + +void GrVkBuffer::vkRelease(const GrVkGpu* gpu) { + VALIDATE(); + fResource->recycle(const_cast(gpu)); + fResource = nullptr; + if (!fDesc.fDynamic) { + delete[] (unsigned char*)fMapPtr; + } + fMapPtr = nullptr; + VALIDATE(); +} + +void GrVkBuffer::vkAbandon() { + fResource->unrefAndAbandon(); + fResource = nullptr; + if (!fDesc.fDynamic) { + delete[] (unsigned char*)fMapPtr; + } + fMapPtr = nullptr; + VALIDATE(); +} + +VkAccessFlags buffer_type_to_access_flags(GrVkBuffer::Type type) { + switch (type) { + case GrVkBuffer::kIndex_Type: + return VK_ACCESS_INDEX_READ_BIT; + case GrVkBuffer::kVertex_Type: + return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + default: + // This helper is only called for static buffers so we should only ever see index or + // vertex buffers types + SkASSERT(false); + return 0; + } +} + +void GrVkBuffer::internalMap(GrVkGpu* gpu, size_t size, bool* createdNewBuffer) { + VALIDATE(); + SkASSERT(!this->vkIsMapped()); + + if (!fResource->unique()) { + if (fDesc.fDynamic) { + // in use by the command buffer, so we need to create a new one + fResource->recycle(gpu); + fResource = this->createResource(gpu, fDesc); + if (createdNewBuffer) { + *createdNewBuffer = true; + } + } else { + SkASSERT(fMapPtr); + this->addMemoryBarrier(gpu, + buffer_type_to_access_flags(fDesc.fType), + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + } + } + + if (fDesc.fDynamic) { + const GrVkAlloc& alloc = this->alloc(); + SkASSERT(alloc.fSize > 0); + SkASSERT(alloc.fSize >= size); + SkASSERT(0 == fOffset); + + fMapPtr = GrVkMemory::MapAlloc(gpu, alloc); + } else { + if (!fMapPtr) { + fMapPtr = new unsigned char[this->size()]; + } + } + + VALIDATE(); +} + +void GrVkBuffer::copyCpuDataToGpuBuffer(GrVkGpu* gpu, const void* src, size_t size) { + SkASSERT(src); + // We should never call this method in protected contexts. + SkASSERT(!gpu->protectedContext()); + // The vulkan api restricts the use of vkCmdUpdateBuffer to updates that are less than or equal + // to 65536 bytes and a size the is 4 byte aligned. + if ((size <= 65536) && (0 == (size & 0x3)) && !gpu->vkCaps().avoidUpdateBuffers()) { + gpu->updateBuffer(this, src, this->offset(), size); + } else { + sk_sp transferBuffer = + GrVkTransferBuffer::Make(gpu, size, GrVkBuffer::kCopyRead_Type); + if (!transferBuffer) { + return; + } + + char* buffer = (char*) transferBuffer->map(); + memcpy (buffer, src, size); + transferBuffer->unmap(); + + gpu->copyBuffer(transferBuffer.get(), this, 0, this->offset(), size); + } + this->addMemoryBarrier(gpu, + VK_ACCESS_TRANSFER_WRITE_BIT, + buffer_type_to_access_flags(fDesc.fType), + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, + false); +} + +void GrVkBuffer::internalUnmap(GrVkGpu* gpu, size_t size) { + VALIDATE(); + SkASSERT(this->vkIsMapped()); + + if (fDesc.fDynamic) { + const GrVkAlloc& alloc = this->alloc(); + SkASSERT(alloc.fSize > 0); + SkASSERT(alloc.fSize >= size); + // We currently don't use fOffset + SkASSERT(0 == fOffset); + + GrVkMemory::FlushMappedAlloc(gpu, alloc, 0, size); + GrVkMemory::UnmapAlloc(gpu, alloc); + fMapPtr = nullptr; + } else { + SkASSERT(fMapPtr); + this->copyCpuDataToGpuBuffer(gpu, fMapPtr, size); + } +} + +bool GrVkBuffer::vkIsMapped() const { + VALIDATE(); + return SkToBool(fMapPtr); +} + +bool GrVkBuffer::vkUpdateData(GrVkGpu* gpu, const void* src, size_t srcSizeInBytes, + bool* createdNewBuffer) { + if (srcSizeInBytes > fDesc.fSizeInBytes) { + return false; + } + + if (fDesc.fDynamic) { + this->internalMap(gpu, srcSizeInBytes, createdNewBuffer); + if (!fMapPtr) { + return false; + } + + memcpy(fMapPtr, src, srcSizeInBytes); + this->internalUnmap(gpu, srcSizeInBytes); + } else { + this->copyCpuDataToGpuBuffer(gpu, src, srcSizeInBytes); + } + + + return true; +} + +void GrVkBuffer::validate() const { + SkASSERT(!fResource || kVertex_Type == fDesc.fType || kIndex_Type == fDesc.fType + || kTexel_Type == fDesc.fType || kCopyRead_Type == fDesc.fType + || kCopyWrite_Type == fDesc.fType || kUniform_Type == fDesc.fType); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkBuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkBuffer.h new file mode 100644 index 0000000000..e82613d0d2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkBuffer.h @@ -0,0 +1,122 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkBuffer_DEFINED +#define GrVkBuffer_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkGpu; + +/** + * This class serves as the base of GrVk*Buffer classes. It was written to avoid code + * duplication in those classes. + */ +class GrVkBuffer : public SkNoncopyable { +public: + virtual ~GrVkBuffer() { + // either release or abandon should have been called by the owner of this object. + SkASSERT(!fResource); + delete [] (unsigned char*)fMapPtr; + } + + VkBuffer buffer() const { return fResource->fBuffer; } + const GrVkAlloc& alloc() const { return fResource->fAlloc; } + const GrVkRecycledResource* resource() const { return fResource; } + size_t size() const { return fDesc.fSizeInBytes; } + VkDeviceSize offset() const { return fOffset; } + + void addMemoryBarrier(const GrVkGpu* gpu, + VkAccessFlags srcAccessMask, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion) const; + + enum Type { + kVertex_Type, + kIndex_Type, + kUniform_Type, + kTexel_Type, + kCopyRead_Type, + kCopyWrite_Type, + }; + +protected: + struct Desc { + size_t fSizeInBytes; + Type fType; // vertex buffer, index buffer, etc. + bool fDynamic; + }; + + class Resource : public GrVkRecycledResource { + public: + Resource(VkBuffer buf, const GrVkAlloc& alloc, Type type) + : INHERITED(), fBuffer(buf), fAlloc(alloc), fType(type) {} + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkBuffer: %d (%d refs)\n", fBuffer, this->getRefCnt()); + } +#endif + VkBuffer fBuffer; + GrVkAlloc fAlloc; + Type fType; + + private: + void freeGPUData(GrVkGpu* gpu) const override; + + void onRecycle(GrVkGpu* gpu) const override { this->unref(gpu); } + + typedef GrVkRecycledResource INHERITED; + }; + + // convenience routine for raw buffer creation + static const Resource* Create(const GrVkGpu* gpu, + const Desc& descriptor); + + GrVkBuffer(const Desc& desc, const GrVkBuffer::Resource* resource) + : fDesc(desc), fResource(resource), fOffset(0), fMapPtr(nullptr) { + } + + void* vkMap(GrVkGpu* gpu) { + this->internalMap(gpu, fDesc.fSizeInBytes); + return fMapPtr; + } + void vkUnmap(GrVkGpu* gpu) { this->internalUnmap(gpu, this->size()); } + + // If the caller passes in a non null createdNewBuffer, this function will set the bool to true + // if it creates a new VkBuffer to upload the data to. + bool vkUpdateData(GrVkGpu* gpu, const void* src, size_t srcSizeInBytes, + bool* createdNewBuffer = nullptr); + + void vkAbandon(); + void vkRelease(const GrVkGpu* gpu); + +private: + virtual const Resource* createResource(GrVkGpu* gpu, + const Desc& descriptor) { + return Create(gpu, descriptor); + } + + void internalMap(GrVkGpu* gpu, size_t size, bool* createdNewBuffer = nullptr); + void internalUnmap(GrVkGpu* gpu, size_t size); + void copyCpuDataToGpuBuffer(GrVkGpu* gpu, const void* srcData, size_t size); + + void validate() const; + bool vkIsMapped() const; + + Desc fDesc; + const Resource* fResource; + VkDeviceSize fOffset; + void* fMapPtr; + + typedef SkNoncopyable INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkBufferView.cpp b/gfx/skia/skia/src/gpu/vk/GrVkBufferView.cpp new file mode 100644 index 0000000000..f4ed9ea821 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkBufferView.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkBufferView.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkUtil.h" + +const GrVkBufferView* GrVkBufferView::Create(const GrVkGpu* gpu, VkBuffer buffer, VkFormat format, + VkDeviceSize offset, VkDeviceSize range) { + VkBufferView bufferView; + + // Create the VkBufferView + VkBufferViewCreateInfo viewInfo; + memset(&viewInfo, 0, sizeof(VkBufferViewCreateInfo)); + viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; + viewInfo.pNext = nullptr; + viewInfo.flags = 0; + viewInfo.buffer = buffer; + viewInfo.format = format; + viewInfo.offset = offset; + viewInfo.range = range; + + VkResult err = GR_VK_CALL(gpu->vkInterface(), CreateBufferView(gpu->device(), &viewInfo, + nullptr, &bufferView)); + if (err) { + return nullptr; + } + + return new GrVkBufferView(bufferView); +} + +void GrVkBufferView::freeGPUData(GrVkGpu* gpu) const { + GR_VK_CALL(gpu->vkInterface(), DestroyBufferView(gpu->device(), fBufferView, nullptr)); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkBufferView.h b/gfx/skia/skia/src/gpu/vk/GrVkBufferView.h new file mode 100644 index 0000000000..e6adf6b9a9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkBufferView.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkBufferView_DEFINED +#define GrVkBufferView_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkBufferView : public GrVkResource { +public: + static const GrVkBufferView* Create(const GrVkGpu* gpu, VkBuffer buffer, VkFormat format, + VkDeviceSize offset, VkDeviceSize range); + + VkBufferView bufferView() const { return fBufferView; } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkBufferView: %d (%d refs)\n", fBufferView, this->getRefCnt()); + } +#endif + +private: + GrVkBufferView(VkBufferView bufferView) : INHERITED(), fBufferView(bufferView) {} + + void freeGPUData(GrVkGpu* gpu) const override; + + VkBufferView fBufferView; + + typedef GrVkResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkCaps.cpp b/gfx/skia/skia/src/gpu/vk/GrVkCaps.cpp new file mode 100644 index 0000000000..36e2436a85 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkCaps.cpp @@ -0,0 +1,1747 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/vk/GrVkBackendContext.h" +#include "include/gpu/vk/GrVkExtensions.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetProxy.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrUtil.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/vk/GrVkCaps.h" +#include "src/gpu/vk/GrVkInterface.h" +#include "src/gpu/vk/GrVkTexture.h" +#include "src/gpu/vk/GrVkUniformHandler.h" +#include "src/gpu/vk/GrVkUtil.h" + +#ifdef SK_BUILD_FOR_ANDROID +#include +#endif + +GrVkCaps::GrVkCaps(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface, + VkPhysicalDevice physDev, const VkPhysicalDeviceFeatures2& features, + uint32_t instanceVersion, uint32_t physicalDeviceVersion, + const GrVkExtensions& extensions, GrProtected isProtected) + : INHERITED(contextOptions) { + /************************************************************************** + * GrCaps fields + **************************************************************************/ + fMipMapSupport = true; // always available in Vulkan + fNPOTTextureTileSupport = true; // always available in Vulkan + fReuseScratchTextures = true; //TODO: figure this out + fGpuTracingSupport = false; //TODO: figure this out + fOversizedStencilSupport = false; //TODO: figure this out + fInstanceAttribSupport = true; + + fSemaphoreSupport = true; // always available in Vulkan + fFenceSyncSupport = true; // always available in Vulkan + fCrossContextTextureSupport = true; + fHalfFloatVertexAttributeSupport = true; + + // We always copy in/out of a transfer buffer so it's trivial to support row bytes. + fReadPixelsRowBytesSupport = true; + fWritePixelsRowBytesSupport = true; + + fTransferBufferSupport = true; + + fMaxRenderTargetSize = 4096; // minimum required by spec + fMaxTextureSize = 4096; // minimum required by spec + + fDynamicStateArrayGeometryProcessorTextureSupport = true; + + fShaderCaps.reset(new GrShaderCaps(contextOptions)); + + this->init(contextOptions, vkInterface, physDev, features, physicalDeviceVersion, extensions, + isProtected); +} + +namespace { +/** + * This comes from section 37.1.6 of the Vulkan spec. Format is + * (|)__. + */ +enum class FormatCompatibilityClass { + k8_1_1, + k16_2_1, + k24_3_1, + k32_4_1, + k64_8_1, + kETC2_RGB_8_16, +}; +} // anonymous namespace + +static FormatCompatibilityClass format_compatibility_class(VkFormat format) { + switch (format) { + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_R8G8B8A8_SRGB: + case VK_FORMAT_R16G16_UNORM: + case VK_FORMAT_R16G16_SFLOAT: + return FormatCompatibilityClass::k32_4_1; + + case VK_FORMAT_R8_UNORM: + return FormatCompatibilityClass::k8_1_1; + + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_R16_SFLOAT: + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: + case VK_FORMAT_R16_UNORM: + return FormatCompatibilityClass::k16_2_1; + + case VK_FORMAT_R16G16B16A16_SFLOAT: + case VK_FORMAT_R16G16B16A16_UNORM: + return FormatCompatibilityClass::k64_8_1; + + case VK_FORMAT_R8G8B8_UNORM: + return FormatCompatibilityClass::k24_3_1; + + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + return FormatCompatibilityClass::kETC2_RGB_8_16; + + default: + SK_ABORT("Unsupported VkFormat"); + } +} + +bool GrVkCaps::canCopyImage(VkFormat dstFormat, int dstSampleCnt, bool dstHasYcbcr, + VkFormat srcFormat, int srcSampleCnt, bool srcHasYcbcr) const { + if ((dstSampleCnt > 1 || srcSampleCnt > 1) && dstSampleCnt != srcSampleCnt) { + return false; + } + + if (dstHasYcbcr || srcHasYcbcr) { + return false; + } + + // We require that all Vulkan GrSurfaces have been created with transfer_dst and transfer_src + // as image usage flags. + return format_compatibility_class(srcFormat) == format_compatibility_class(dstFormat); +} + +bool GrVkCaps::canCopyAsBlit(VkFormat dstFormat, int dstSampleCnt, bool dstIsLinear, + bool dstHasYcbcr, VkFormat srcFormat, int srcSampleCnt, + bool srcIsLinear, bool srcHasYcbcr) const { + // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src + // as image usage flags. + if (!this->formatCanBeDstofBlit(dstFormat, dstIsLinear) || + !this->formatCanBeSrcofBlit(srcFormat, srcIsLinear)) { + return false; + } + + // We cannot blit images that are multisampled. Will need to figure out if we can blit the + // resolved msaa though. + if (dstSampleCnt > 1 || srcSampleCnt > 1) { + return false; + } + + if (dstHasYcbcr || srcHasYcbcr) { + return false; + } + + return true; +} + +bool GrVkCaps::canCopyAsResolve(VkFormat dstFormat, int dstSampleCnt, bool dstHasYcbcr, + VkFormat srcFormat, int srcSampleCnt, bool srcHasYcbcr) const { + // The src surface must be multisampled. + if (srcSampleCnt <= 1) { + return false; + } + + // The dst must not be multisampled. + if (dstSampleCnt > 1) { + return false; + } + + // Surfaces must have the same format. + if (srcFormat != dstFormat) { + return false; + } + + if (dstHasYcbcr || srcHasYcbcr) { + return false; + } + + return true; +} + +bool GrVkCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const { + if (src->isProtected() && !dst->isProtected()) { + return false; + } + + // TODO: Figure out a way to track if we've wrapped a linear texture in a proxy (e.g. + // PromiseImage which won't get instantiated right away. Does this need a similar thing like the + // tracking of external or rectangle textures in GL? For now we don't create linear textures + // internally, and I don't believe anyone is wrapping them. + bool srcIsLinear = false; + bool dstIsLinear = false; + + int dstSampleCnt = 0; + int srcSampleCnt = 0; + if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) { + // Copying to or from render targets that wrap a secondary command buffer is not allowed + // since they would require us to know the VkImage, which we don't have, as well as need us + // to stop and start the VkRenderPass which we don't have access to. + if (rtProxy->wrapsVkSecondaryCB()) { + return false; + } + dstSampleCnt = rtProxy->numSamples(); + } + if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) { + // Copying to or from render targets that wrap a secondary command buffer is not allowed + // since they would require us to know the VkImage, which we don't have, as well as need us + // to stop and start the VkRenderPass which we don't have access to. + if (rtProxy->wrapsVkSecondaryCB()) { + return false; + } + srcSampleCnt = rtProxy->numSamples(); + } + SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy())); + SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy())); + + bool dstHasYcbcr = false; + if (auto ycbcr = dst->backendFormat().getVkYcbcrConversionInfo()) { + if (ycbcr->isValid()) { + dstHasYcbcr = true; + } + } + + bool srcHasYcbcr = false; + if (auto ycbcr = src->backendFormat().getVkYcbcrConversionInfo()) { + if (ycbcr->isValid()) { + srcHasYcbcr = true; + } + } + + VkFormat dstFormat, srcFormat; + SkAssertResult(dst->backendFormat().asVkFormat(&dstFormat)); + SkAssertResult(src->backendFormat().asVkFormat(&srcFormat)); + + return this->canCopyImage(dstFormat, dstSampleCnt, dstHasYcbcr, + srcFormat, srcSampleCnt, srcHasYcbcr) || + this->canCopyAsBlit(dstFormat, dstSampleCnt, dstIsLinear, dstHasYcbcr, + srcFormat, srcSampleCnt, srcIsLinear, srcHasYcbcr) || + this->canCopyAsResolve(dstFormat, dstSampleCnt, dstHasYcbcr, + srcFormat, srcSampleCnt, srcHasYcbcr); +} + +template T* get_extension_feature_struct(const VkPhysicalDeviceFeatures2& features, + VkStructureType type) { + // All Vulkan structs that could be part of the features chain will start with the + // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader + // so we can get access to the pNext for the next struct. + struct CommonVulkanHeader { + VkStructureType sType; + void* pNext; + }; + + void* pNext = features.pNext; + while (pNext) { + CommonVulkanHeader* header = static_cast(pNext); + if (header->sType == type) { + return static_cast(pNext); + } + pNext = header->pNext; + } + return nullptr; +} + +void GrVkCaps::init(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface, + VkPhysicalDevice physDev, const VkPhysicalDeviceFeatures2& features, + uint32_t physicalDeviceVersion, const GrVkExtensions& extensions, + GrProtected isProtected) { + VkPhysicalDeviceProperties properties; + GR_VK_CALL(vkInterface, GetPhysicalDeviceProperties(physDev, &properties)); + + VkPhysicalDeviceMemoryProperties memoryProperties; + GR_VK_CALL(vkInterface, GetPhysicalDeviceMemoryProperties(physDev, &memoryProperties)); + + SkASSERT(physicalDeviceVersion <= properties.apiVersion); + + if (extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1)) { + fSupportsSwapchain = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions.hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1)) { + fSupportsPhysicalDeviceProperties2 = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions.hasExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1)) { + fSupportsMemoryRequirements2 = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions.hasExtension(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, 1)) { + fSupportsBindMemory2 = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions.hasExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME, 1)) { + fSupportsMaintenance1 = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions.hasExtension(VK_KHR_MAINTENANCE2_EXTENSION_NAME, 1)) { + fSupportsMaintenance2 = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions.hasExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME, 1)) { + fSupportsMaintenance3 = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + (extensions.hasExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1) && + this->supportsMemoryRequirements2())) { + fSupportsDedicatedAllocation = true; + } + + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + (extensions.hasExtension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1) && + this->supportsPhysicalDeviceProperties2() && + extensions.hasExtension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, 1) && + this->supportsDedicatedAllocation())) { + fSupportsExternalMemory = true; + } + +#ifdef SK_BUILD_FOR_ANDROID + // Currently Adreno devices are not supporting the QUEUE_FAMILY_FOREIGN_EXTENSION, so until they + // do we don't explicitly require it here even the spec says it is required. + if (extensions.hasExtension( + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, 2) && + /* extensions.hasExtension(VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, 1) &&*/ + this->supportsExternalMemory() && + this->supportsBindMemory2()) { + fSupportsAndroidHWBExternalMemory = true; + fSupportsAHardwareBufferImages = true; + } +#endif + + auto ycbcrFeatures = + get_extension_feature_struct( + features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES); + if (ycbcrFeatures && ycbcrFeatures->samplerYcbcrConversion && + (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + (extensions.hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1) && + this->supportsMaintenance1() && this->supportsBindMemory2() && + this->supportsMemoryRequirements2() && this->supportsPhysicalDeviceProperties2()))) { + fSupportsYcbcrConversion = true; + } + + // We always push back the default GrVkYcbcrConversionInfo so that the case of no conversion + // will return a key of 0. + fYcbcrInfos.push_back(GrVkYcbcrConversionInfo()); + + if ((isProtected == GrProtected::kYes) && + (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0))) { + fSupportsProtectedMemory = true; + fAvoidUpdateBuffers = true; + fShouldAlwaysUseDedicatedImageMemory = true; + } + + this->initGrCaps(vkInterface, physDev, properties, memoryProperties, features, extensions); + this->initShaderCaps(properties, features); + + if (kQualcomm_VkVendor == properties.vendorID) { + // A "clear" load for the CCPR atlas runs faster on QC than a "discard" load followed by a + // scissored clear. + // On NVIDIA and Intel, the discard load followed by clear is faster. + // TODO: Evaluate on ARM, Imagination, and ATI. + fPreferFullscreenClears = true; + } + + if (kQualcomm_VkVendor == properties.vendorID || kARM_VkVendor == properties.vendorID) { + // On Qualcomm and ARM mapping a gpu buffer and doing both reads and writes to it is slow. + // Thus for index and vertex buffers we will force to use a cpu side buffer and then copy + // the whole buffer up to the gpu. + fBufferMapThreshold = SK_MaxS32; + } + + if (kQualcomm_VkVendor == properties.vendorID) { + // On Qualcomm it looks like using vkCmdUpdateBuffer is slower than using a transfer buffer + // even for small sizes. + fAvoidUpdateBuffers = true; + } + + if (kARM_VkVendor == properties.vendorID) { + // ARM seems to do better with more fine triangles as opposed to using the sample mask. + // (At least in our current round rect op.) + fPreferTrianglesOverSampleMask = true; + } + + this->initFormatTable(vkInterface, physDev, properties); + this->initStencilFormat(vkInterface, physDev); + + if (!contextOptions.fDisableDriverCorrectnessWorkarounds) { + this->applyDriverCorrectnessWorkarounds(properties); + } + + this->applyOptionsOverrides(contextOptions); + fShaderCaps->applyOptionsOverrides(contextOptions); +} + +void GrVkCaps::applyDriverCorrectnessWorkarounds(const VkPhysicalDeviceProperties& properties) { + if (kQualcomm_VkVendor == properties.vendorID) { + fMustDoCopiesFromOrigin = true; + // Transfer doesn't support this workaround. + fTransferBufferSupport = false; + } + +#if defined(SK_BUILD_FOR_WIN) + if (kNvidia_VkVendor == properties.vendorID || kIntel_VkVendor == properties.vendorID) { + fMustSleepOnTearDown = true; + } +#elif defined(SK_BUILD_FOR_ANDROID) + if (kImagination_VkVendor == properties.vendorID) { + fMustSleepOnTearDown = true; + } +#endif + +#if defined(SK_BUILD_FOR_ANDROID) + // Protected memory features have problems in Android P and earlier. + if (fSupportsProtectedMemory && (kQualcomm_VkVendor == properties.vendorID)) { + char androidAPIVersion[PROP_VALUE_MAX]; + int strLength = __system_property_get("ro.build.version.sdk", androidAPIVersion); + if (strLength == 0 || atoi(androidAPIVersion) <= 28) { + fSupportsProtectedMemory = false; + } + } +#endif + + // On Mali galaxy s7 we see lots of rendering issues when we suballocate VkImages. + if (kARM_VkVendor == properties.vendorID) { + fShouldAlwaysUseDedicatedImageMemory = true; + } + + // On Mali galaxy s7 and s9 we see lots of rendering issues with image filters dropping out when + // using only primary command buffers. + if (kARM_VkVendor == properties.vendorID) { + fPreferPrimaryOverSecondaryCommandBuffers = false; + } + + // On various devices, when calling vkCmdClearAttachments on a primary command buffer, it + // corrupts the bound buffers on the command buffer. As a workaround we invalidate our knowledge + // of bound buffers so that we will rebind them on the next draw. + if (kQualcomm_VkVendor == properties.vendorID || kAMD_VkVendor == properties.vendorID) { + fMustInvalidatePrimaryCmdBufferStateAfterClearAttachments = true; + } + + //////////////////////////////////////////////////////////////////////////// + // GrCaps workarounds + //////////////////////////////////////////////////////////////////////////// + + if (kARM_VkVendor == properties.vendorID) { + fInstanceAttribSupport = false; + fAvoidWritePixelsFastPath = true; // bugs.skia.org/8064 + } + + // AMD advertises support for MAX_UINT vertex input attributes, but in reality only supports 32. + if (kAMD_VkVendor == properties.vendorID) { + fMaxVertexAttributes = SkTMin(fMaxVertexAttributes, 32); + } + + //////////////////////////////////////////////////////////////////////////// + // GrShaderCaps workarounds + //////////////////////////////////////////////////////////////////////////// + + if (kImagination_VkVendor == properties.vendorID) { + fShaderCaps->fAtan2ImplementedAsAtanYOverX = true; + } +} + +int get_max_sample_count(VkSampleCountFlags flags) { + SkASSERT(flags & VK_SAMPLE_COUNT_1_BIT); + if (!(flags & VK_SAMPLE_COUNT_2_BIT)) { + return 0; + } + if (!(flags & VK_SAMPLE_COUNT_4_BIT)) { + return 2; + } + if (!(flags & VK_SAMPLE_COUNT_8_BIT)) { + return 4; + } + if (!(flags & VK_SAMPLE_COUNT_16_BIT)) { + return 8; + } + if (!(flags & VK_SAMPLE_COUNT_32_BIT)) { + return 16; + } + if (!(flags & VK_SAMPLE_COUNT_64_BIT)) { + return 32; + } + return 64; +} + +void GrVkCaps::initGrCaps(const GrVkInterface* vkInterface, + VkPhysicalDevice physDev, + const VkPhysicalDeviceProperties& properties, + const VkPhysicalDeviceMemoryProperties& memoryProperties, + const VkPhysicalDeviceFeatures2& features, + const GrVkExtensions& extensions) { + // So GPUs, like AMD, are reporting MAX_INT support vertex attributes. In general, there is no + // need for us ever to support that amount, and it makes tests which tests all the vertex + // attribs timeout looping over that many. For now, we'll cap this at 64 max and can raise it if + // we ever find that need. + static const uint32_t kMaxVertexAttributes = 64; + fMaxVertexAttributes = SkTMin(properties.limits.maxVertexInputAttributes, kMaxVertexAttributes); + + // We could actually query and get a max size for each config, however maxImageDimension2D will + // give the minimum max size across all configs. So for simplicity we will use that for now. + fMaxRenderTargetSize = SkTMin(properties.limits.maxImageDimension2D, (uint32_t)INT_MAX); + fMaxTextureSize = SkTMin(properties.limits.maxImageDimension2D, (uint32_t)INT_MAX); + if (fDriverBugWorkarounds.max_texture_size_limit_4096) { + fMaxTextureSize = SkTMin(fMaxTextureSize, 4096); + } + // Our render targets are always created with textures as the color + // attachment, hence this min: + fMaxRenderTargetSize = SkTMin(fMaxTextureSize, fMaxRenderTargetSize); + + // TODO: check if RT's larger than 4k incur a performance cost on ARM. + fMaxPreferredRenderTargetSize = fMaxRenderTargetSize; + + // Assuming since we will always map in the end to upload the data we might as well just map + // from the get go. There is no hard data to suggest this is faster or slower. + fBufferMapThreshold = 0; + + fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag | kAsyncRead_MapFlag; + + fOversizedStencilSupport = true; + + if (extensions.hasExtension(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, 2) && + this->supportsPhysicalDeviceProperties2()) { + + VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT blendProps; + blendProps.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT; + blendProps.pNext = nullptr; + + VkPhysicalDeviceProperties2 props; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &blendProps; + + GR_VK_CALL(vkInterface, GetPhysicalDeviceProperties2(physDev, &props)); + + if (blendProps.advancedBlendAllOperations == VK_TRUE) { + fShaderCaps->fAdvBlendEqInteraction = GrShaderCaps::kAutomatic_AdvBlendEqInteraction; + + auto blendFeatures = + get_extension_feature_struct( + features, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT); + if (blendFeatures && blendFeatures->advancedBlendCoherentOperations == VK_TRUE) { + fBlendEquationSupport = kAdvancedCoherent_BlendEquationSupport; + } else { + // TODO: Currently non coherent blends are not supported in our vulkan backend. They + // require us to support self dependencies in our render passes. + // fBlendEquationSupport = kAdvanced_BlendEquationSupport; + } + } + } +} + +void GrVkCaps::initShaderCaps(const VkPhysicalDeviceProperties& properties, + const VkPhysicalDeviceFeatures2& features) { + GrShaderCaps* shaderCaps = fShaderCaps.get(); + shaderCaps->fVersionDeclString = "#version 330\n"; + + // Vulkan is based off ES 3.0 so the following should all be supported + shaderCaps->fUsesPrecisionModifiers = true; + shaderCaps->fFlatInterpolationSupport = true; + // Flat interpolation appears to be slow on Qualcomm GPUs. This was tested in GL and is assumed + // to be true with Vulkan as well. + shaderCaps->fPreferFlatInterpolation = kQualcomm_VkVendor != properties.vendorID; + + // GrShaderCaps + + shaderCaps->fShaderDerivativeSupport = true; + + // FIXME: http://skbug.com/7733: Disable geometry shaders until Intel/Radeon GMs draw correctly. + // shaderCaps->fGeometryShaderSupport = + // shaderCaps->fGSInvocationsSupport = features.features.geometryShader; + + shaderCaps->fDualSourceBlendingSupport = features.features.dualSrcBlend; + + shaderCaps->fIntegerSupport = true; + shaderCaps->fVertexIDSupport = true; + shaderCaps->fFPManipulationSupport = true; + + // Assume the minimum precisions mandated by the SPIR-V spec. + shaderCaps->fFloatIs32Bits = true; + shaderCaps->fHalfIs32Bits = false; + + shaderCaps->fMaxFragmentSamplers = SkTMin( + SkTMin(properties.limits.maxPerStageDescriptorSampledImages, + properties.limits.maxPerStageDescriptorSamplers), + (uint32_t)INT_MAX); +} + +bool stencil_format_supported(const GrVkInterface* interface, + VkPhysicalDevice physDev, + VkFormat format) { + VkFormatProperties props; + memset(&props, 0, sizeof(VkFormatProperties)); + GR_VK_CALL(interface, GetPhysicalDeviceFormatProperties(physDev, format, &props)); + return SkToBool(VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT & props.optimalTilingFeatures); +} + +void GrVkCaps::initStencilFormat(const GrVkInterface* interface, VkPhysicalDevice physDev) { + // List of legal stencil formats (though perhaps not supported on + // the particular gpu/driver) from most preferred to least. We are guaranteed to have either + // VK_FORMAT_D24_UNORM_S8_UINT or VK_FORMAT_D32_SFLOAT_S8_UINT. VK_FORMAT_D32_SFLOAT_S8_UINT + // can optionally have 24 unused bits at the end so we assume the total bits is 64. + static const StencilFormat + // internal Format stencil bits total bits packed? + gS8 = { VK_FORMAT_S8_UINT, 8, 8, false }, + gD24S8 = { VK_FORMAT_D24_UNORM_S8_UINT, 8, 32, true }, + gD32S8 = { VK_FORMAT_D32_SFLOAT_S8_UINT, 8, 64, true }; + + if (stencil_format_supported(interface, physDev, VK_FORMAT_S8_UINT)) { + fPreferredStencilFormat = gS8; + } else if (stencil_format_supported(interface, physDev, VK_FORMAT_D24_UNORM_S8_UINT)) { + fPreferredStencilFormat = gD24S8; + } else { + SkASSERT(stencil_format_supported(interface, physDev, VK_FORMAT_D32_SFLOAT_S8_UINT)); + fPreferredStencilFormat = gD32S8; + } +} + +static bool format_is_srgb(VkFormat format) { + SkASSERT(GrVkFormatIsSupported(format)); + + switch (format) { + case VK_FORMAT_R8G8B8A8_SRGB: + return true; + default: + return false; + } +} + +// These are all the valid VkFormats that we support in Skia. They are roughly ordered from most +// frequently used to least to improve look up times in arrays. +static constexpr VkFormat kVkFormats[] = { + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8_UNORM, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_R5G6B5_UNORM_PACK16, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R16_SFLOAT, + VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8_UNORM, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_B4G4R4A4_UNORM_PACK16, + VK_FORMAT_R4G4B4A4_UNORM_PACK16, + VK_FORMAT_R8G8B8A8_SRGB, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, + VK_FORMAT_R16_UNORM, + VK_FORMAT_R16G16_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16_SFLOAT, +}; + +void GrVkCaps::setColorType(GrColorType colorType, std::initializer_list formats) { +#ifdef SK_DEBUG + for (size_t i = 0; i < kNumVkFormats; ++i) { + const auto& formatInfo = fFormatTable[i]; + for (int j = 0; j < formatInfo.fColorTypeInfoCount; ++j) { + const auto& ctInfo = formatInfo.fColorTypeInfos[j]; + if (ctInfo.fColorType == colorType && + !SkToBool(ctInfo.fFlags & ColorTypeInfo::kWrappedOnly_Flag)) { + bool found = false; + for (auto it = formats.begin(); it != formats.end(); ++it) { + if (kVkFormats[i] == *it) { + found = true; + } + } + SkASSERT(found); + } + } + } +#endif + int idx = static_cast(colorType); + for (auto it = formats.begin(); it != formats.end(); ++it) { + const auto& info = this->getFormatInfo(*it); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + if (info.fColorTypeInfos[i].fColorType == colorType) { + fColorTypeToFormatTable[idx] = *it; + return; + } + } + } +} + +const GrVkCaps::FormatInfo& GrVkCaps::getFormatInfo(VkFormat format) const { + GrVkCaps* nonConstThis = const_cast(this); + return nonConstThis->getFormatInfo(format); +} + +GrVkCaps::FormatInfo& GrVkCaps::getFormatInfo(VkFormat format) { + static_assert(SK_ARRAY_COUNT(kVkFormats) == GrVkCaps::kNumVkFormats, + "Size of VkFormats array must match static value in header"); + for (size_t i = 0; i < SK_ARRAY_COUNT(kVkFormats); ++i) { + if (kVkFormats[i] == format) { + return fFormatTable[i]; + } + } + static FormatInfo kInvalidFormat; + return kInvalidFormat; +} + +void GrVkCaps::initFormatTable(const GrVkInterface* interface, VkPhysicalDevice physDev, + const VkPhysicalDeviceProperties& properties) { + static_assert(SK_ARRAY_COUNT(kVkFormats) == GrVkCaps::kNumVkFormats, + "Size of VkFormats array must match static value in header"); + + std::fill_n(fColorTypeToFormatTable, kGrColorTypeCnt, VK_FORMAT_UNDEFINED); + + // Go through all the formats and init their support surface and data GrColorTypes. + // Format: VK_FORMAT_R8G8B8A8_UNORM + { + constexpr VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 4; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 2; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R8G8B8A8_UNORM, Surface: kRGBA_8888 + { + constexpr GrColorType ct = GrColorType::kRGBA_8888; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + // Format: VK_FORMAT_R8G8B8A8_UNORM, Surface: kRGB_888x + { + constexpr GrColorType ct = GrColorType::kRGB_888x; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RGB1(); + } + } + } + + // Format: VK_FORMAT_R8_UNORM + { + constexpr VkFormat format = VK_FORMAT_R8_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 1; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 2; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R8_UNORM, Surface: kAlpha_8 + { + constexpr GrColorType ct = GrColorType::kAlpha_8; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + } + // Format: VK_FORMAT_R8_UNORM, Surface: kGray_8 + { + constexpr GrColorType ct = GrColorType::kGray_8; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag; + ctInfo.fTextureSwizzle = GrSwizzle("rrr1"); + } + } + } + // Format: VK_FORMAT_B8G8R8A8_UNORM + { + constexpr VkFormat format = VK_FORMAT_B8G8R8A8_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 4; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_B8G8R8A8_UNORM, Surface: kBGRA_8888 + { + constexpr GrColorType ct = GrColorType::kBGRA_8888; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R5G6B5_UNORM_PACK16 + { + constexpr VkFormat format = VK_FORMAT_R5G6B5_UNORM_PACK16; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 2; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R5G6B5_UNORM_PACK16, Surface: kBGR_565 + { + constexpr GrColorType ct = GrColorType::kBGR_565; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R16G16B16A16_SFLOAT + { + constexpr VkFormat format = VK_FORMAT_R16G16B16A16_SFLOAT; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 8; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 2; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R16G16B16A16_SFLOAT, Surface: GrColorType::kRGBA_F16 + { + constexpr GrColorType ct = GrColorType::kRGBA_F16; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + // Format: VK_FORMAT_R16G16B16A16_SFLOAT, Surface: GrColorType::kRGBA_F16_Clamped + { + constexpr GrColorType ct = GrColorType::kRGBA_F16_Clamped; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R16_SFLOAT + { + constexpr VkFormat format = VK_FORMAT_R16_SFLOAT; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 2; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R16_SFLOAT, Surface: kAlpha_F16 + { + constexpr GrColorType ct = GrColorType::kAlpha_F16; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + } + } + } + // Format: VK_FORMAT_R8G8B8_UNORM + { + constexpr VkFormat format = VK_FORMAT_R8G8B8_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 3; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R8G8B8_UNORM, Surface: kRGB_888x + { + constexpr GrColorType ct = GrColorType::kRGB_888x; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R8G8_UNORM + { + constexpr VkFormat format = VK_FORMAT_R8G8_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 2; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R8G8_UNORM, Surface: kRG_88 + { + constexpr GrColorType ct = GrColorType::kRG_88; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_A2B10G10R10_UNORM_PACK32 + { + constexpr VkFormat format = VK_FORMAT_A2B10G10R10_UNORM_PACK32; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 4; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_A2B10G10R10_UNORM_PACK32, Surface: kRGBA_1010102 + { + constexpr GrColorType ct = GrColorType::kRGBA_1010102; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_B4G4R4A4_UNORM_PACK16 + { + constexpr VkFormat format = VK_FORMAT_B4G4R4A4_UNORM_PACK16; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 2; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_B4G4R4A4_UNORM_PACK16, Surface: kABGR_4444 + { + constexpr GrColorType ct = GrColorType::kABGR_4444; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::BGRA(); + ctInfo.fOutputSwizzle = GrSwizzle::BGRA(); + } + } + } + // Format: VK_FORMAT_R4G4B4A4_UNORM_PACK16 + { + constexpr VkFormat format = VK_FORMAT_R4G4B4A4_UNORM_PACK16; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 2; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R4G4B4A4_UNORM_PACK16, Surface: kABGR_4444 + { + constexpr GrColorType ct = GrColorType::kABGR_4444; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R8G8B8A8_SRGB + { + constexpr VkFormat format = VK_FORMAT_R8G8B8A8_SRGB; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 4; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R8G8B8A8_SRGB, Surface: kRGBA_8888_SRGB + { + constexpr GrColorType ct = GrColorType::kRGBA_8888_SRGB; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R16_UNORM + { + constexpr VkFormat format = VK_FORMAT_R16_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 2; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R16_UNORM, Surface: kAlpha_16 + { + constexpr GrColorType ct = GrColorType::kAlpha_16; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + ctInfo.fTextureSwizzle = GrSwizzle::RRRR(); + ctInfo.fOutputSwizzle = GrSwizzle::AAAA(); + } + } + } + // Format: VK_FORMAT_R16G16_UNORM + { + constexpr VkFormat format = VK_FORMAT_R16G16_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 4; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R16G16_UNORM, Surface: kRG_1616 + { + constexpr GrColorType ct = GrColorType::kRG_1616; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R16G16B16A16_UNORM + { + constexpr VkFormat format = VK_FORMAT_R16G16B16A16_UNORM; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 8; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R16G16B16A16_UNORM, Surface: kRGBA_16161616 + { + constexpr GrColorType ct = GrColorType::kRGBA_16161616; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_R16G16_SFLOAT + { + constexpr VkFormat format = VK_FORMAT_R16G16_SFLOAT; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 4; + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_R16G16_SFLOAT, Surface: kRG_F16 + { + constexpr GrColorType ct = GrColorType::kRG_F16; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag; + } + } + } + // Format: VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM + { + constexpr VkFormat format = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM; + auto& info = this->getFormatInfo(format); + // Currently we are just over estimating this value to be used in gpu size calculations even + // though the actually size is probably less. We should instead treat planar formats similar + // to compressed textures that go through their own special query for calculating size. + info.fBytesPerPixel = 3; + if (fSupportsYcbcrConversion) { + info.init(interface, physDev, properties, format); + } + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, Surface: kRGB_888x + { + constexpr GrColorType ct = GrColorType::kRGB_888x; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kWrappedOnly_Flag; + } + } + } + // Format: VK_FORMAT_G8_B8R8_2PLANE_420_UNORM + { + constexpr VkFormat format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; + auto& info = this->getFormatInfo(format); + // Currently we are just over estimating this value to be used in gpu size calculations even + // though the actually size is probably less. We should instead treat planar formats similar + // to compressed textures that go through their own special query for calculating size. + info.fBytesPerPixel = 3; + if (fSupportsYcbcrConversion) { + info.init(interface, physDev, properties, format); + } + if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) { + info.fColorTypeInfoCount = 1; + info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]()); + int ctIdx = 0; + // Format: VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, Surface: kRGB_888x + { + constexpr GrColorType ct = GrColorType::kRGB_888x; + auto& ctInfo = info.fColorTypeInfos[ctIdx++]; + ctInfo.fColorType = ct; + ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kWrappedOnly_Flag; + } + } + } + // Format: VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK + { + constexpr VkFormat format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; + auto& info = this->getFormatInfo(format); + info.init(interface, physDev, properties, format); + info.fBytesPerPixel = 0; + // No supported GrColorTypes. + } + + //////////////////////////////////////////////////////////////////////////// + // Map GrColorTypes (used for creating GrSurfaces) to VkFormats. The order in which the formats + // are passed into the setColorType function indicates the priority in selecting which format + // we use for a given GrcolorType. + + this->setColorType(GrColorType::kAlpha_8, { VK_FORMAT_R8_UNORM }); + this->setColorType(GrColorType::kBGR_565, { VK_FORMAT_R5G6B5_UNORM_PACK16 }); + this->setColorType(GrColorType::kABGR_4444, { VK_FORMAT_R4G4B4A4_UNORM_PACK16, + VK_FORMAT_B4G4R4A4_UNORM_PACK16 }); + this->setColorType(GrColorType::kRGBA_8888, { VK_FORMAT_R8G8B8A8_UNORM }); + this->setColorType(GrColorType::kRGBA_8888_SRGB, { VK_FORMAT_R8G8B8A8_SRGB }); + this->setColorType(GrColorType::kRGB_888x, { VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8B8A8_UNORM }); + this->setColorType(GrColorType::kRG_88, { VK_FORMAT_R8G8_UNORM }); + this->setColorType(GrColorType::kBGRA_8888, { VK_FORMAT_B8G8R8A8_UNORM }); + this->setColorType(GrColorType::kRGBA_1010102, { VK_FORMAT_A2B10G10R10_UNORM_PACK32 }); + this->setColorType(GrColorType::kGray_8, { VK_FORMAT_R8_UNORM }); + this->setColorType(GrColorType::kAlpha_F16, { VK_FORMAT_R16_SFLOAT }); + this->setColorType(GrColorType::kRGBA_F16, { VK_FORMAT_R16G16B16A16_SFLOAT }); + this->setColorType(GrColorType::kRGBA_F16_Clamped, { VK_FORMAT_R16G16B16A16_SFLOAT }); + this->setColorType(GrColorType::kAlpha_16, { VK_FORMAT_R16_UNORM }); + this->setColorType(GrColorType::kRG_1616, { VK_FORMAT_R16G16_UNORM }); + this->setColorType(GrColorType::kRGBA_16161616, { VK_FORMAT_R16G16B16A16_UNORM }); + this->setColorType(GrColorType::kRG_F16, { VK_FORMAT_R16G16_SFLOAT }); +} + +void GrVkCaps::FormatInfo::InitFormatFlags(VkFormatFeatureFlags vkFlags, uint16_t* flags) { + if (SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & vkFlags) && + SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT & vkFlags)) { + *flags = *flags | kTexturable_Flag; + + // Ganesh assumes that all renderable surfaces are also texturable + if (SkToBool(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT & vkFlags)) { + *flags = *flags | kRenderable_Flag; + } + } + + if (SkToBool(VK_FORMAT_FEATURE_BLIT_SRC_BIT & vkFlags)) { + *flags = *flags | kBlitSrc_Flag; + } + + if (SkToBool(VK_FORMAT_FEATURE_BLIT_DST_BIT & vkFlags)) { + *flags = *flags | kBlitDst_Flag; + } +} + +void GrVkCaps::FormatInfo::initSampleCounts(const GrVkInterface* interface, + VkPhysicalDevice physDev, + const VkPhysicalDeviceProperties& physProps, + VkFormat format) { + VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + VkImageFormatProperties properties; + GR_VK_CALL(interface, GetPhysicalDeviceImageFormatProperties(physDev, + format, + VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, + usage, + 0, // createFlags + &properties)); + VkSampleCountFlags flags = properties.sampleCounts; + if (flags & VK_SAMPLE_COUNT_1_BIT) { + fColorSampleCounts.push_back(1); + } + if (kImagination_VkVendor == physProps.vendorID) { + // MSAA does not work on imagination + return; + } + if (kIntel_VkVendor == physProps.vendorID) { + // MSAA doesn't work well on Intel GPUs chromium:527565, chromium:983926 + return; + } + if (flags & VK_SAMPLE_COUNT_2_BIT) { + fColorSampleCounts.push_back(2); + } + if (flags & VK_SAMPLE_COUNT_4_BIT) { + fColorSampleCounts.push_back(4); + } + if (flags & VK_SAMPLE_COUNT_8_BIT) { + fColorSampleCounts.push_back(8); + } + if (flags & VK_SAMPLE_COUNT_16_BIT) { + fColorSampleCounts.push_back(16); + } + if (flags & VK_SAMPLE_COUNT_32_BIT) { + fColorSampleCounts.push_back(32); + } + if (flags & VK_SAMPLE_COUNT_64_BIT) { + fColorSampleCounts.push_back(64); + } +} + +void GrVkCaps::FormatInfo::init(const GrVkInterface* interface, + VkPhysicalDevice physDev, + const VkPhysicalDeviceProperties& properties, + VkFormat format) { + VkFormatProperties props; + memset(&props, 0, sizeof(VkFormatProperties)); + GR_VK_CALL(interface, GetPhysicalDeviceFormatProperties(physDev, format, &props)); + InitFormatFlags(props.linearTilingFeatures, &fLinearFlags); + InitFormatFlags(props.optimalTilingFeatures, &fOptimalFlags); + if (fOptimalFlags & kRenderable_Flag) { + this->initSampleCounts(interface, physDev, properties, format); + } +} + +// For many checks in caps, we need to know whether the GrBackendFormat is external or not. If it is +// external the VkFormat will be VK_NULL_HANDLE which is not handled by our various format +// capability checks. +static bool backend_format_is_external(const GrBackendFormat& format) { + const GrVkYcbcrConversionInfo* ycbcrInfo = format.getVkYcbcrConversionInfo(); + SkASSERT(ycbcrInfo); + + // All external formats have a valid ycbcrInfo used for sampling and a non zero external format. + if (ycbcrInfo->isValid() && ycbcrInfo->fExternalFormat != 0) { +#ifdef SK_DEBUG + VkFormat vkFormat; + SkAssertResult(format.asVkFormat(&vkFormat)); + SkASSERT(vkFormat == VK_NULL_HANDLE); +#endif + return true; + } + return false; +} + +bool GrVkCaps::isFormatSRGB(const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + if (backend_format_is_external(format)) { + return false; + } + + return format_is_srgb(vkFormat); +} + +bool GrVkCaps::isFormatCompressed(const GrBackendFormat& format, + SkImage::CompressionType* compressionType) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + SkImage::CompressionType dummyType; + SkImage::CompressionType* compressionTypePtr = compressionType ? compressionType : &dummyType; + + switch (vkFormat) { + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + // ETC2 uses the same compression layout as ETC1 + *compressionTypePtr = SkImage::kETC1_CompressionType; + return true; + default: + return false; + } +} + +bool GrVkCaps::isFormatTexturableAndUploadable(GrColorType ct, + const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + + uint32_t ctFlags = this->getFormatInfo(vkFormat).colorTypeFlags(ct); + return this->isVkFormatTexturable(vkFormat) && + SkToBool(ctFlags & ColorTypeInfo::kUploadData_Flag); +} + +bool GrVkCaps::isFormatTexturable(const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + if (backend_format_is_external(format)) { + // We can always texture from an external format (assuming we have the ycbcr conversion + // info which we require to be passed in). + return true; + } + return this->isVkFormatTexturable(vkFormat); +} + +bool GrVkCaps::isVkFormatTexturable(VkFormat format) const { + const FormatInfo& info = this->getFormatInfo(format); + return SkToBool(FormatInfo::kTexturable_Flag & info.fOptimalFlags); +} + +bool GrVkCaps::isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount) const { + if (!this->isFormatRenderable(format, sampleCount)) { + return false; + } + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + const auto& info = this->getFormatInfo(vkFormat); + if (!SkToBool(info.colorTypeFlags(ct) & ColorTypeInfo::kRenderable_Flag)) { + return false; + } + return true; +} + +bool GrVkCaps::isFormatRenderable(const GrBackendFormat& format, int sampleCount) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + return this->isFormatRenderable(vkFormat, sampleCount); +} + +bool GrVkCaps::isFormatRenderable(VkFormat format, int sampleCount) const { + return sampleCount <= this->maxRenderTargetSampleCount(format); +} + +int GrVkCaps::getRenderTargetSampleCount(int requestedCount, + const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return 0; + } + + return this->getRenderTargetSampleCount(requestedCount, vkFormat); +} + +int GrVkCaps::getRenderTargetSampleCount(int requestedCount, VkFormat format) const { + requestedCount = SkTMax(1, requestedCount); + + const FormatInfo& info = this->getFormatInfo(format); + + int count = info.fColorSampleCounts.count(); + + if (!count) { + return 0; + } + + if (1 == requestedCount) { + SkASSERT(info.fColorSampleCounts.count() && info.fColorSampleCounts[0] == 1); + return 1; + } + + for (int i = 0; i < count; ++i) { + if (info.fColorSampleCounts[i] >= requestedCount) { + return info.fColorSampleCounts[i]; + } + } + return 0; +} + +int GrVkCaps::maxRenderTargetSampleCount(const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return 0; + } + return this->maxRenderTargetSampleCount(vkFormat); +} + +int GrVkCaps::maxRenderTargetSampleCount(VkFormat format) const { + const FormatInfo& info = this->getFormatInfo(format); + + const auto& table = info.fColorSampleCounts; + if (!table.count()) { + return 0; + } + return table[table.count() - 1]; +} + +size_t GrVkCaps::bytesPerPixel(const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return 0; + } + return this->bytesPerPixel(vkFormat); +} + +size_t GrVkCaps::bytesPerPixel(VkFormat format) const { + return this->getFormatInfo(format).fBytesPerPixel; +} + +static inline size_t align_to_4(size_t v) { + switch (v & 0b11) { + // v is already a multiple of 4. + case 0: return v; + // v is a multiple of 2 but not 4. + case 2: return 2 * v; + // v is not a multiple of 2. + default: return 4 * v; + } +} + +GrCaps::SupportedWrite GrVkCaps::supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const { + VkFormat vkFormat; + if (!surfaceFormat.asVkFormat(&vkFormat)) { + return {GrColorType::kUnknown, 0}; + } + + // We don't support the ability to upload to external formats or formats that require a ycbcr + // sampler. In general these types of formats are only used for sampling in a shader. + if (backend_format_is_external(surfaceFormat) || GrVkFormatNeedsYcbcrSampler(vkFormat)) { + return {GrColorType::kUnknown, 0}; + } + + // The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel. + size_t offsetAlignment = align_to_4(this->bytesPerPixel(vkFormat)); + + const auto& info = this->getFormatInfo(vkFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == surfaceColorType) { + return {surfaceColorType, offsetAlignment}; + } + } + return {GrColorType::kUnknown, 0}; +} + +GrCaps::SurfaceReadPixelsSupport GrVkCaps::surfaceSupportsReadPixels( + const GrSurface* surface) const { + if (surface->isProtected()) { + return SurfaceReadPixelsSupport::kUnsupported; + } + if (auto tex = static_cast(surface->asTexture())) { + // We can't directly read from a VkImage that has a ycbcr sampler. + if (tex->ycbcrConversionInfo().isValid()) { + return SurfaceReadPixelsSupport::kCopyToTexture2D; + } + // We can't directly read from a compressed format + SkImage::CompressionType compressionType; + if (GrVkFormatToCompressionType(tex->imageFormat(), &compressionType)) { + return SurfaceReadPixelsSupport::kCopyToTexture2D; + } + } + return SurfaceReadPixelsSupport::kSupported; +} + +bool GrVkCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const { + if (auto rt = surface->asRenderTarget()) { + return rt->numSamples() <= 1 && SkToBool(surface->asTexture()); + } + // We can't write to a texture that has a ycbcr sampler. + if (auto tex = static_cast(surface->asTexture())) { + // We can't directly read from a VkImage that has a ycbcr sampler. + if (tex->ycbcrConversionInfo().isValid()) { + return false; + } + } + return true; +} + +bool GrVkCaps::onAreColorTypeAndFormatCompatible(GrColorType ct, + const GrBackendFormat& format) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return false; + } + const GrVkYcbcrConversionInfo* ycbcrInfo = format.getVkYcbcrConversionInfo(); + SkASSERT(ycbcrInfo); + + if (ycbcrInfo->isValid() && !GrVkFormatNeedsYcbcrSampler(vkFormat)) { + // Format may be undefined for external images, which are required to have YCbCr conversion. + if (VK_FORMAT_UNDEFINED == vkFormat && ycbcrInfo->fExternalFormat != 0) { + return true; + } + return false; + } + + const auto& info = this->getFormatInfo(vkFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + if (info.fColorTypeInfos[i].fColorType == ct) { + return true; + } + } + return false; +} + +static GrPixelConfig validate_image_info(VkFormat format, GrColorType ct, bool hasYcbcrConversion) { + if (hasYcbcrConversion) { + if (GrVkFormatNeedsYcbcrSampler(format)) { + return kRGB_888X_GrPixelConfig; + } + + // Format may be undefined for external images, which are required to have YCbCr conversion. + if (VK_FORMAT_UNDEFINED == format) { + // We don't actually care what the color type or config are since we won't use those + // values for external textures. However, for read pixels we will draw to a non ycbcr + // texture of this config so we set RGBA here for that. + return kRGBA_8888_GrPixelConfig; + } + + return kUnknown_GrPixelConfig; + } + + if (VK_FORMAT_UNDEFINED == format) { + return kUnknown_GrPixelConfig; + } + + switch (ct) { + case GrColorType::kUnknown: + break; + case GrColorType::kAlpha_8: + if (VK_FORMAT_R8_UNORM == format) { + return kAlpha_8_as_Red_GrPixelConfig; + } + break; + case GrColorType::kBGR_565: + if (VK_FORMAT_R5G6B5_UNORM_PACK16 == format) { + return kRGB_565_GrPixelConfig; + } + break; + case GrColorType::kABGR_4444: + if (VK_FORMAT_B4G4R4A4_UNORM_PACK16 == format || + VK_FORMAT_R4G4B4A4_UNORM_PACK16 == format) { + return kRGBA_4444_GrPixelConfig; + } + break; + case GrColorType::kRGBA_8888: + if (VK_FORMAT_R8G8B8A8_UNORM == format) { + return kRGBA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGBA_8888_SRGB: + if (VK_FORMAT_R8G8B8A8_SRGB == format) { + return kSRGBA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGB_888x: + if (VK_FORMAT_R8G8B8_UNORM == format) { + return kRGB_888_GrPixelConfig; + } else if (VK_FORMAT_R8G8B8A8_UNORM == format) { + return kRGB_888X_GrPixelConfig; + } else if (VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK == format) { + return kRGB_ETC1_GrPixelConfig; + } + break; + case GrColorType::kRG_88: + if (VK_FORMAT_R8G8_UNORM == format) { + return kRG_88_GrPixelConfig; + } + break; + case GrColorType::kBGRA_8888: + if (VK_FORMAT_B8G8R8A8_UNORM == format) { + return kBGRA_8888_GrPixelConfig; + } + break; + case GrColorType::kRGBA_1010102: + if (VK_FORMAT_A2B10G10R10_UNORM_PACK32 == format) { + return kRGBA_1010102_GrPixelConfig; + } + break; + case GrColorType::kGray_8: + if (VK_FORMAT_R8_UNORM == format) { + return kGray_8_as_Red_GrPixelConfig; + } + break; + case GrColorType::kAlpha_F16: + if (VK_FORMAT_R16_SFLOAT == format) { + return kAlpha_half_as_Red_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F16: + if (VK_FORMAT_R16G16B16A16_SFLOAT == format) { + return kRGBA_half_GrPixelConfig; + } + break; + case GrColorType::kRGBA_F16_Clamped: + if (VK_FORMAT_R16G16B16A16_SFLOAT == format) { + return kRGBA_half_Clamped_GrPixelConfig; + } + break; + case GrColorType::kAlpha_16: + if (VK_FORMAT_R16_UNORM == format) { + return kAlpha_16_GrPixelConfig; + } + break; + case GrColorType::kRG_1616: + if (VK_FORMAT_R16G16_UNORM == format) { + return kRG_1616_GrPixelConfig; + } + break; + case GrColorType::kRGBA_16161616: + if (VK_FORMAT_R16G16B16A16_UNORM == format) { + return kRGBA_16161616_GrPixelConfig; + } + break; + case GrColorType::kRG_F16: + if (VK_FORMAT_R16G16_SFLOAT == format) { + return kRG_half_GrPixelConfig; + } + break; + // These have no equivalent: + case GrColorType::kRGBA_F32: + case GrColorType::kAlpha_8xxx: + case GrColorType::kAlpha_F32xxx: + case GrColorType::kGray_8xxx: + break; + } + + return kUnknown_GrPixelConfig; +} + +GrPixelConfig GrVkCaps::onGetConfigFromBackendFormat(const GrBackendFormat& format, + GrColorType ct) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return kUnknown_GrPixelConfig; + } + const GrVkYcbcrConversionInfo* ycbcrInfo = format.getVkYcbcrConversionInfo(); + SkASSERT(ycbcrInfo); + return validate_image_info(vkFormat, ct, ycbcrInfo->isValid()); +} + +GrColorType GrVkCaps::getYUVAColorTypeFromBackendFormat(const GrBackendFormat& format, + bool isAlphaChannel) const { + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + return GrColorType::kUnknown; + } + + switch (vkFormat) { + case VK_FORMAT_R8_UNORM: return isAlphaChannel ? GrColorType::kAlpha_8 + : GrColorType::kGray_8; + case VK_FORMAT_R8G8B8A8_UNORM: return GrColorType::kRGBA_8888; + case VK_FORMAT_R8G8B8_UNORM: return GrColorType::kRGB_888x; + case VK_FORMAT_R8G8_UNORM: return GrColorType::kRG_88; + case VK_FORMAT_B8G8R8A8_UNORM: return GrColorType::kBGRA_8888; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return GrColorType::kRGBA_1010102; + case VK_FORMAT_R16_UNORM: return GrColorType::kAlpha_16; + case VK_FORMAT_R16_SFLOAT: return GrColorType::kAlpha_F16; + case VK_FORMAT_R16G16_UNORM: return GrColorType::kRG_1616; + case VK_FORMAT_R16G16B16A16_UNORM: return GrColorType::kRGBA_16161616; + case VK_FORMAT_R16G16_SFLOAT: return GrColorType::kRG_F16; + default: return GrColorType::kUnknown; + } + + SkUNREACHABLE; +} + +GrBackendFormat GrVkCaps::onGetDefaultBackendFormat(GrColorType ct, + GrRenderable renderable) const { + VkFormat format = this->getFormatFromColorType(ct); + if (format == VK_FORMAT_UNDEFINED) { + return GrBackendFormat(); + } + return GrBackendFormat::MakeVk(format); +} + +GrBackendFormat GrVkCaps::getBackendFormatFromCompressionType( + SkImage::CompressionType compressionType) const { + switch (compressionType) { + case SkImage::kETC1_CompressionType: + return GrBackendFormat::MakeVk(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK); + } + SK_ABORT("Invalid compression type"); +} + +GrSwizzle GrVkCaps::getTextureSwizzle(const GrBackendFormat& format, GrColorType colorType) const { + VkFormat vkFormat; + SkAssertResult(format.asVkFormat(&vkFormat)); + const auto& info = this->getFormatInfo(vkFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == colorType) { + return ctInfo.fTextureSwizzle; + } + } + return GrSwizzle::RGBA(); +} + +GrSwizzle GrVkCaps::getOutputSwizzle(const GrBackendFormat& format, GrColorType colorType) const { + VkFormat vkFormat; + SkAssertResult(format.asVkFormat(&vkFormat)); + const auto& info = this->getFormatInfo(vkFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == colorType) { + return ctInfo.fOutputSwizzle; + } + } + return GrSwizzle::RGBA(); +} + +GrCaps::SupportedRead GrVkCaps::onSupportedReadPixelsColorType( + GrColorType srcColorType, const GrBackendFormat& srcBackendFormat, + GrColorType dstColorType) const { + VkFormat vkFormat; + if (!srcBackendFormat.asVkFormat(&vkFormat)) { + return {GrColorType::kUnknown, 0}; + } + + if (GrVkFormatNeedsYcbcrSampler(vkFormat)) { + return {GrColorType::kUnknown, 0}; + } + + // The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel. + size_t offsetAlignment = align_to_4(this->bytesPerPixel(vkFormat)); + + const auto& info = this->getFormatInfo(vkFormat); + for (int i = 0; i < info.fColorTypeInfoCount; ++i) { + const auto& ctInfo = info.fColorTypeInfos[i]; + if (ctInfo.fColorType == srcColorType) { + return {srcColorType, offsetAlignment}; + } + } + return {GrColorType::kUnknown, 0}; +} + +int GrVkCaps::getFragmentUniformBinding() const { + return GrVkUniformHandler::kUniformBinding; +} + +int GrVkCaps::getFragmentUniformSet() const { + return GrVkUniformHandler::kUniformBufferDescSet; +} + +#if GR_TEST_UTILS +std::vector GrVkCaps::getTestingCombinations() const { + std::vector combos = { + { GrColorType::kAlpha_8, GrBackendFormat::MakeVk(VK_FORMAT_R8_UNORM) }, + { GrColorType::kBGR_565, GrBackendFormat::MakeVk(VK_FORMAT_R5G6B5_UNORM_PACK16) }, + { GrColorType::kABGR_4444, GrBackendFormat::MakeVk(VK_FORMAT_R4G4B4A4_UNORM_PACK16)}, + { GrColorType::kABGR_4444, GrBackendFormat::MakeVk(VK_FORMAT_B4G4R4A4_UNORM_PACK16)}, + { GrColorType::kRGBA_8888, GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM) }, + { GrColorType::kRGBA_8888_SRGB, GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_SRGB) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM) }, + { GrColorType::kRGB_888x, GrBackendFormat::MakeVk(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK)}, + { GrColorType::kRG_88, GrBackendFormat::MakeVk(VK_FORMAT_R8G8_UNORM) }, + { GrColorType::kBGRA_8888, GrBackendFormat::MakeVk(VK_FORMAT_B8G8R8A8_UNORM) }, + { GrColorType::kRGBA_1010102, GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32)}, + { GrColorType::kGray_8, GrBackendFormat::MakeVk(VK_FORMAT_R8_UNORM) }, + { GrColorType::kAlpha_F16, GrBackendFormat::MakeVk(VK_FORMAT_R16_SFLOAT) }, + { GrColorType::kRGBA_F16, GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT) }, + { GrColorType::kRGBA_F16_Clamped, GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT) }, + { GrColorType::kAlpha_16, GrBackendFormat::MakeVk(VK_FORMAT_R16_UNORM) }, + { GrColorType::kRG_1616, GrBackendFormat::MakeVk(VK_FORMAT_R16G16_UNORM) }, + { GrColorType::kRGBA_16161616, GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_UNORM) }, + { GrColorType::kRG_F16, GrBackendFormat::MakeVk(VK_FORMAT_R16G16_SFLOAT) }, + }; + + return combos; +} +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkCaps.h b/gfx/skia/skia/src/gpu/vk/GrVkCaps.h new file mode 100644 index 0000000000..0b90035d60 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkCaps.h @@ -0,0 +1,321 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkCaps_DEFINED +#define GrVkCaps_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/vk/GrVkStencilAttachment.h" + +class GrShaderCaps; +class GrVkExtensions; +struct GrVkInterface; + +/** + * Stores some capabilities of a Vk backend. + */ +class GrVkCaps : public GrCaps { +public: + typedef GrVkStencilAttachment::Format StencilFormat; + + /** + * Creates a GrVkCaps that is set such that nothing is supported. The init function should + * be called to fill out the caps. + */ + GrVkCaps(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface, + VkPhysicalDevice device, const VkPhysicalDeviceFeatures2& features, + uint32_t instanceVersion, uint32_t physicalDeviceVersion, + const GrVkExtensions& extensions, GrProtected isProtected = GrProtected::kNo); + + bool isFormatSRGB(const GrBackendFormat&) const override; + bool isFormatCompressed(const GrBackendFormat&, + SkImage::CompressionType* compressionType = nullptr) const override; + + bool isFormatTexturableAndUploadable(GrColorType, const GrBackendFormat&) const override; + bool isFormatTexturable(const GrBackendFormat&) const override; + bool isVkFormatTexturable(VkFormat) const; + + bool isFormatCopyable(const GrBackendFormat&) const override { return true; } + + bool isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format, + int sampleCount = 1) const override; + bool isFormatRenderable(const GrBackendFormat& format, int sampleCount) const override; + bool isFormatRenderable(VkFormat, int sampleCount) const; + + int getRenderTargetSampleCount(int requestedCount, const GrBackendFormat&) const override; + int getRenderTargetSampleCount(int requestedCount, VkFormat) const; + + int maxRenderTargetSampleCount(const GrBackendFormat&) const override; + int maxRenderTargetSampleCount(VkFormat format) const; + + size_t bytesPerPixel(const GrBackendFormat&) const override; + size_t bytesPerPixel(VkFormat format) const; + + SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType, + const GrBackendFormat& surfaceFormat, + GrColorType srcColorType) const override; + + SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const override; + + bool isVkFormatTexturableLinearly(VkFormat format) const { + return SkToBool(FormatInfo::kTexturable_Flag & this->getFormatInfo(format).fLinearFlags); + } + + bool formatCanBeDstofBlit(VkFormat format, bool linearTiled) const { + const FormatInfo& info = this->getFormatInfo(format); + const uint16_t& flags = linearTiled ? info.fLinearFlags : info.fOptimalFlags; + return SkToBool(FormatInfo::kBlitDst_Flag & flags); + } + + bool formatCanBeSrcofBlit(VkFormat format, bool linearTiled) const { + const FormatInfo& info = this->getFormatInfo(format); + const uint16_t& flags = linearTiled ? info.fLinearFlags : info.fOptimalFlags; + return SkToBool(FormatInfo::kBlitSrc_Flag & flags); + } + + // On Adreno vulkan, they do not respect the imageOffset parameter at least in + // copyImageToBuffer. This flag says that we must do the copy starting from the origin always. + bool mustDoCopiesFromOrigin() const { + return fMustDoCopiesFromOrigin; + } + + // Sometimes calls to QueueWaitIdle return before actually signalling the fences + // on the command buffers even though they have completed. This causes an assert to fire when + // destroying the command buffers. Therefore we add a sleep to make sure the fence signals. + bool mustSleepOnTearDown() const { + return fMustSleepOnTearDown; + } + + // Returns true if we should always make dedicated allocations for VkImages. + bool shouldAlwaysUseDedicatedImageMemory() const { + return fShouldAlwaysUseDedicatedImageMemory; + } + + // Always use a transfer buffer instead of vkCmdUpdateBuffer to upload data to a VkBuffer. + bool avoidUpdateBuffers() const { + return fAvoidUpdateBuffers; + } + + /** + * Returns both a supported and most preferred stencil format to use in draws. + */ + const StencilFormat& preferredStencilFormat() const { + return fPreferredStencilFormat; + } + + // Returns whether the device supports VK_KHR_Swapchain. Internally Skia never uses any of the + // swapchain functions, but we may need to transition to and from the + // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR image layout, so we must know whether that layout is + // supported. + bool supportsSwapchain() const { return fSupportsSwapchain; } + + // Returns whether the device supports the ability to extend VkPhysicalDeviceProperties struct. + bool supportsPhysicalDeviceProperties2() const { return fSupportsPhysicalDeviceProperties2; } + // Returns whether the device supports the ability to extend VkMemoryRequirements struct. + bool supportsMemoryRequirements2() const { return fSupportsMemoryRequirements2; } + + // Returns whether the device supports the ability to extend the vkBindMemory call. + bool supportsBindMemory2() const { return fSupportsBindMemory2; } + + // Returns whether or not the device suports the various API maintenance fixes to Vulkan 1.0. In + // Vulkan 1.1 all these maintenance are part of the core spec. + bool supportsMaintenance1() const { return fSupportsMaintenance1; } + bool supportsMaintenance2() const { return fSupportsMaintenance2; } + bool supportsMaintenance3() const { return fSupportsMaintenance3; } + + // Returns true if the device supports passing in a flag to say we are using dedicated GPU when + // allocating memory. For some devices this allows them to return more optimized memory knowning + // they will never need to suballocate amonst multiple objects. + bool supportsDedicatedAllocation() const { return fSupportsDedicatedAllocation; } + + // Returns true if the device supports importing of external memory into Vulkan memory. + bool supportsExternalMemory() const { return fSupportsExternalMemory; } + // Returns true if the device supports importing Android hardware buffers into Vulkan memory. + bool supportsAndroidHWBExternalMemory() const { return fSupportsAndroidHWBExternalMemory; } + + // Returns true if it supports ycbcr conversion for samplers + bool supportsYcbcrConversion() const { return fSupportsYcbcrConversion; } + + // Returns true if the device supports protected memory. + bool supportsProtectedMemory() const { return fSupportsProtectedMemory; } + + // Returns whether we prefer to record draws directly into a primary command buffer. + bool preferPrimaryOverSecondaryCommandBuffers() const { + return fPreferPrimaryOverSecondaryCommandBuffers; + } + + bool mustInvalidatePrimaryCmdBufferStateAfterClearAttachments() const { + return fMustInvalidatePrimaryCmdBufferStateAfterClearAttachments; + } + + /** + * Helpers used by canCopySurface. In all cases if the SampleCnt parameter is zero that means + * the surface is not a render target, otherwise it is the number of samples in the render + * target. + */ + bool canCopyImage(VkFormat dstFormat, int dstSampleCnt, bool dstHasYcbcr, + VkFormat srcFormat, int srcSamplecnt, bool srcHasYcbcr) const; + + bool canCopyAsBlit(VkFormat dstConfig, int dstSampleCnt, bool dstIsLinear, bool dstHasYcbcr, + VkFormat srcConfig, int srcSampleCnt, bool srcIsLinear, + bool srcHasYcbcr) const; + + bool canCopyAsResolve(VkFormat dstConfig, int dstSampleCnt, bool dstHasYcbcr, + VkFormat srcConfig, int srcSamplecnt, bool srcHasYcbcr) const; + + GrColorType getYUVAColorTypeFromBackendFormat(const GrBackendFormat&, + bool isAlphaChannel) const override; + + GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override; + + VkFormat getFormatFromColorType(GrColorType colorType) const { + int idx = static_cast(colorType); + return fColorTypeToFormatTable[idx]; + } + + GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override; + GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override; + + int getFragmentUniformBinding() const; + int getFragmentUniformSet() const; + +#if GR_TEST_UTILS + std::vector getTestingCombinations() const override; +#endif + +private: + enum VkVendor { + kAMD_VkVendor = 4098, + kARM_VkVendor = 5045, + kImagination_VkVendor = 4112, + kIntel_VkVendor = 32902, + kNvidia_VkVendor = 4318, + kQualcomm_VkVendor = 20803, + }; + + void init(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface, + VkPhysicalDevice device, const VkPhysicalDeviceFeatures2&, + uint32_t physicalDeviceVersion, const GrVkExtensions&, GrProtected isProtected); + void initGrCaps(const GrVkInterface* vkInterface, + VkPhysicalDevice physDev, + const VkPhysicalDeviceProperties&, + const VkPhysicalDeviceMemoryProperties&, + const VkPhysicalDeviceFeatures2&, + const GrVkExtensions&); + void initShaderCaps(const VkPhysicalDeviceProperties&, const VkPhysicalDeviceFeatures2&); + + void initFormatTable(const GrVkInterface*, VkPhysicalDevice, const VkPhysicalDeviceProperties&); + void initStencilFormat(const GrVkInterface* iface, VkPhysicalDevice physDev); + + void applyDriverCorrectnessWorkarounds(const VkPhysicalDeviceProperties&); + + bool onSurfaceSupportsWritePixels(const GrSurface*) const override; + bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src, + const SkIRect& srcRect, const SkIPoint& dstPoint) const override; + GrBackendFormat onGetDefaultBackendFormat(GrColorType, GrRenderable) const override; + + GrPixelConfig onGetConfigFromBackendFormat(const GrBackendFormat&, GrColorType) const override; + bool onAreColorTypeAndFormatCompatible(GrColorType, const GrBackendFormat&) const override; + + SupportedRead onSupportedReadPixelsColorType(GrColorType, const GrBackendFormat&, + GrColorType) const override; + + // ColorTypeInfo for a specific format + struct ColorTypeInfo { + GrColorType fColorType = GrColorType::kUnknown; + enum { + kUploadData_Flag = 0x1, + // Does Ganesh itself support rendering to this colorType & format pair. Renderability + // still additionally depends on if the format itself is renderable. + kRenderable_Flag = 0x2, + // Indicates that this colorType is supported only if we are wrapping a texture with + // the given format and colorType. We do not allow creation with this pair. + kWrappedOnly_Flag = 0x4, + }; + uint32_t fFlags = 0; + + GrSwizzle fTextureSwizzle; + GrSwizzle fOutputSwizzle; + }; + + struct FormatInfo { + uint32_t colorTypeFlags(GrColorType colorType) const { + for (int i = 0; i < fColorTypeInfoCount; ++i) { + if (fColorTypeInfos[i].fColorType == colorType) { + return fColorTypeInfos[i].fFlags; + } + } + return 0; + } + + void init(const GrVkInterface*, VkPhysicalDevice, const VkPhysicalDeviceProperties&, + VkFormat); + static void InitFormatFlags(VkFormatFeatureFlags, uint16_t* flags); + void initSampleCounts(const GrVkInterface*, VkPhysicalDevice, + const VkPhysicalDeviceProperties&, VkFormat); + + enum { + kTexturable_Flag = 0x1, + kRenderable_Flag = 0x2, + kBlitSrc_Flag = 0x4, + kBlitDst_Flag = 0x8, + }; + + uint16_t fOptimalFlags = 0; + uint16_t fLinearFlags = 0; + + SkTDArray fColorSampleCounts; + // This value is only valid for regular formats. Compressed formats will be 0. + size_t fBytesPerPixel = 0; + + std::unique_ptr fColorTypeInfos; + int fColorTypeInfoCount = 0; + }; + static const size_t kNumVkFormats = 19; + FormatInfo fFormatTable[kNumVkFormats]; + + FormatInfo& getFormatInfo(VkFormat); + const FormatInfo& getFormatInfo(VkFormat) const; + + VkFormat fColorTypeToFormatTable[kGrColorTypeCnt]; + void setColorType(GrColorType, std::initializer_list formats); + + StencilFormat fPreferredStencilFormat; + + SkSTArray<1, GrVkYcbcrConversionInfo> fYcbcrInfos; + + bool fMustDoCopiesFromOrigin = false; + bool fMustSleepOnTearDown = false; + bool fShouldAlwaysUseDedicatedImageMemory = false; + + bool fAvoidUpdateBuffers = false; + + bool fSupportsSwapchain = false; + + bool fSupportsPhysicalDeviceProperties2 = false; + bool fSupportsMemoryRequirements2 = false; + bool fSupportsBindMemory2 = false; + bool fSupportsMaintenance1 = false; + bool fSupportsMaintenance2 = false; + bool fSupportsMaintenance3 = false; + + bool fSupportsDedicatedAllocation = false; + bool fSupportsExternalMemory = false; + bool fSupportsAndroidHWBExternalMemory = false; + + bool fSupportsYcbcrConversion = false; + + bool fSupportsProtectedMemory = false; + + bool fPreferPrimaryOverSecondaryCommandBuffers = true; + bool fMustInvalidatePrimaryCmdBufferStateAfterClearAttachments = false; + + typedef GrCaps INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.cpp new file mode 100644 index 0000000000..5f95ccfe88 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.cpp @@ -0,0 +1,942 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkCommandBuffer.h" + +#include "include/core/SkRect.h" +#include "src/gpu/vk/GrVkCommandPool.h" +#include "src/gpu/vk/GrVkFramebuffer.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImage.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkIndexBuffer.h" +#include "src/gpu/vk/GrVkPipeline.h" +#include "src/gpu/vk/GrVkPipelineState.h" +#include "src/gpu/vk/GrVkPipelineState.h" +#include "src/gpu/vk/GrVkRenderPass.h" +#include "src/gpu/vk/GrVkRenderTarget.h" +#include "src/gpu/vk/GrVkTransferBuffer.h" +#include "src/gpu/vk/GrVkUtil.h" +#include "src/gpu/vk/GrVkVertexBuffer.h" + +void GrVkCommandBuffer::invalidateState() { + for (auto& boundInputBuffer : fBoundInputBuffers) { + boundInputBuffer = VK_NULL_HANDLE; + } + fBoundIndexBuffer = VK_NULL_HANDLE; + + memset(&fCachedViewport, 0, sizeof(VkViewport)); + fCachedViewport.width = - 1.0f; // Viewport must have a width greater than 0 + + memset(&fCachedScissor, 0, sizeof(VkRect2D)); + fCachedScissor.offset.x = -1; // Scissor offset must be greater that 0 to be valid + + for (int i = 0; i < 4; ++i) { + fCachedBlendConstant[i] = -1.0; + } +} + +void GrVkCommandBuffer::freeGPUData(GrVkGpu* gpu) const { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(!fIsActive); + for (int i = 0; i < fTrackedResources.count(); ++i) { + fTrackedResources[i]->notifyRemovedFromCommandBuffer(); + fTrackedResources[i]->unref(gpu); + } + + for (int i = 0; i < fTrackedRecycledResources.count(); ++i) { + fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer(); + fTrackedRecycledResources[i]->recycle(const_cast(gpu)); + } + + if (!this->isWrapped()) { + GR_VK_CALL(gpu->vkInterface(), FreeCommandBuffers(gpu->device(), fCmdPool->vkCommandPool(), + 1, &fCmdBuffer)); + } + + this->onFreeGPUData(gpu); +} + +void GrVkCommandBuffer::abandonGPUData() const { + SkDEBUGCODE(fResourcesReleased = true;) + for (int i = 0; i < fTrackedResources.count(); ++i) { + fTrackedResources[i]->notifyRemovedFromCommandBuffer(); + fTrackedResources[i]->unrefAndAbandon(); + } + + for (int i = 0; i < fTrackedRecycledResources.count(); ++i) { + fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer(); + // We don't recycle resources when abandoning them. + fTrackedRecycledResources[i]->unrefAndAbandon(); + } + + this->onAbandonGPUData(); +} + +void GrVkCommandBuffer::releaseResources(GrVkGpu* gpu) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkDEBUGCODE(fResourcesReleased = true;) + SkASSERT(!fIsActive); + for (int i = 0; i < fTrackedResources.count(); ++i) { + fTrackedResources[i]->notifyRemovedFromCommandBuffer(); + fTrackedResources[i]->unref(gpu); + } + for (int i = 0; i < fTrackedRecycledResources.count(); ++i) { + fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer(); + fTrackedRecycledResources[i]->recycle(const_cast(gpu)); + } + + if (++fNumResets > kNumRewindResetsBeforeFullReset) { + fTrackedResources.reset(); + fTrackedRecycledResources.reset(); + fTrackedResources.setReserve(kInitialTrackedResourcesCount); + fTrackedRecycledResources.setReserve(kInitialTrackedResourcesCount); + fNumResets = 0; + } else { + fTrackedResources.rewind(); + fTrackedRecycledResources.rewind(); + } + + this->invalidateState(); + + this->onReleaseResources(gpu); +} + +//////////////////////////////////////////////////////////////////////////////// +// CommandBuffer commands +//////////////////////////////////////////////////////////////////////////////// + +void GrVkCommandBuffer::pipelineBarrier(const GrVkGpu* gpu, + const GrVkResource* resource, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + BarrierType barrierType, + void* barrier) { + SkASSERT(!this->isWrapped()); + SkASSERT(fIsActive); + // For images we can have barriers inside of render passes but they require us to add more + // support in subpasses which need self dependencies to have barriers inside them. Also, we can + // never have buffer barriers inside of a render pass. For now we will just assert that we are + // not in a render pass. + SkASSERT(!fActiveRenderPass); + + if (barrierType == kBufferMemory_BarrierType) { + const VkBufferMemoryBarrier* barrierPtr = reinterpret_cast(barrier); + fBufferBarriers.push_back(*barrierPtr); + } else { + SkASSERT(barrierType == kImageMemory_BarrierType); + const VkImageMemoryBarrier* barrierPtr = reinterpret_cast(barrier); + // We need to check if we are adding a pipeline barrier that covers part of the same + // subresource range as a barrier that is already in current batch. If it does, then we must + // submit the first batch because the vulkan spec does not define a specific ordering for + // barriers submitted in the same batch. + // TODO: Look if we can gain anything by merging barriers together instead of submitting + // the old ones. + for (int i = 0; i < fImageBarriers.count(); ++i) { + VkImageMemoryBarrier& currentBarrier = fImageBarriers[i]; + if (barrierPtr->image == currentBarrier.image) { + const VkImageSubresourceRange newRange = barrierPtr->subresourceRange; + const VkImageSubresourceRange oldRange = currentBarrier.subresourceRange; + SkASSERT(newRange.aspectMask == oldRange.aspectMask); + SkASSERT(newRange.baseArrayLayer == oldRange.baseArrayLayer); + SkASSERT(newRange.layerCount == oldRange.layerCount); + uint32_t newStart = newRange.baseMipLevel; + uint32_t newEnd = newRange.baseMipLevel + newRange.levelCount - 1; + uint32_t oldStart = oldRange.baseMipLevel; + uint32_t oldEnd = oldRange.baseMipLevel + oldRange.levelCount - 1; + if (SkTMax(newStart, oldStart) <= SkTMin(newEnd, oldEnd)) { + this->submitPipelineBarriers(gpu); + break; + } + } + } + fImageBarriers.push_back(*barrierPtr); + } + fBarriersByRegion |= byRegion; + + fSrcStageMask = fSrcStageMask | srcStageMask; + fDstStageMask = fDstStageMask | dstStageMask; + + fHasWork = true; + if (resource) { + this->addResource(resource); + } +} + +void GrVkCommandBuffer::submitPipelineBarriers(const GrVkGpu* gpu) { + SkASSERT(fIsActive); + + // Currently we never submit a pipeline barrier without at least one memory barrier. + if (fBufferBarriers.count() || fImageBarriers.count()) { + // For images we can have barriers inside of render passes but they require us to add more + // support in subpasses which need self dependencies to have barriers inside them. Also, we + // can never have buffer barriers inside of a render pass. For now we will just assert that + // we are not in a render pass. + SkASSERT(!fActiveRenderPass); + SkASSERT(!this->isWrapped()); + SkASSERT(fSrcStageMask && fDstStageMask); + + VkDependencyFlags dependencyFlags = fBarriersByRegion ? VK_DEPENDENCY_BY_REGION_BIT : 0; + GR_VK_CALL(gpu->vkInterface(), CmdPipelineBarrier( + fCmdBuffer, fSrcStageMask, fDstStageMask, dependencyFlags, 0, nullptr, + fBufferBarriers.count(), fBufferBarriers.begin(), + fImageBarriers.count(), fImageBarriers.begin())); + fBufferBarriers.reset(); + fImageBarriers.reset(); + fBarriersByRegion = false; + fSrcStageMask = 0; + fDstStageMask = 0; + } + SkASSERT(!fBufferBarriers.count()); + SkASSERT(!fImageBarriers.count()); + SkASSERT(!fBarriersByRegion); + SkASSERT(!fSrcStageMask); + SkASSERT(!fDstStageMask); +} + + +void GrVkCommandBuffer::bindInputBuffer(GrVkGpu* gpu, uint32_t binding, + const GrVkVertexBuffer* vbuffer) { + VkBuffer vkBuffer = vbuffer->buffer(); + SkASSERT(VK_NULL_HANDLE != vkBuffer); + SkASSERT(binding < kMaxInputBuffers); + // TODO: once vbuffer->offset() no longer always returns 0, we will need to track the offset + // to know if we can skip binding or not. + if (vkBuffer != fBoundInputBuffers[binding]) { + VkDeviceSize offset = vbuffer->offset(); + GR_VK_CALL(gpu->vkInterface(), CmdBindVertexBuffers(fCmdBuffer, + binding, + 1, + &vkBuffer, + &offset)); + fBoundInputBuffers[binding] = vkBuffer; + this->addResource(vbuffer->resource()); + } +} + +void GrVkCommandBuffer::bindIndexBuffer(GrVkGpu* gpu, const GrVkIndexBuffer* ibuffer) { + VkBuffer vkBuffer = ibuffer->buffer(); + SkASSERT(VK_NULL_HANDLE != vkBuffer); + // TODO: once ibuffer->offset() no longer always returns 0, we will need to track the offset + // to know if we can skip binding or not. + if (vkBuffer != fBoundIndexBuffer) { + GR_VK_CALL(gpu->vkInterface(), CmdBindIndexBuffer(fCmdBuffer, + vkBuffer, + ibuffer->offset(), + VK_INDEX_TYPE_UINT16)); + fBoundIndexBuffer = vkBuffer; + this->addResource(ibuffer->resource()); + } +} + +void GrVkCommandBuffer::clearAttachments(const GrVkGpu* gpu, + int numAttachments, + const VkClearAttachment* attachments, + int numRects, + const VkClearRect* clearRects) { + SkASSERT(fIsActive); + SkASSERT(fActiveRenderPass); + SkASSERT(numAttachments > 0); + SkASSERT(numRects > 0); + + this->addingWork(gpu); + +#ifdef SK_DEBUG + for (int i = 0; i < numAttachments; ++i) { + if (attachments[i].aspectMask == VK_IMAGE_ASPECT_COLOR_BIT) { + uint32_t testIndex; + SkAssertResult(fActiveRenderPass->colorAttachmentIndex(&testIndex)); + SkASSERT(testIndex == attachments[i].colorAttachment); + } + } +#endif + GR_VK_CALL(gpu->vkInterface(), CmdClearAttachments(fCmdBuffer, + numAttachments, + attachments, + numRects, + clearRects)); + if (gpu->vkCaps().mustInvalidatePrimaryCmdBufferStateAfterClearAttachments()) { + this->invalidateState(); + } +} + +void GrVkCommandBuffer::bindDescriptorSets(const GrVkGpu* gpu, + GrVkPipelineState* pipelineState, + VkPipelineLayout layout, + uint32_t firstSet, + uint32_t setCount, + const VkDescriptorSet* descriptorSets, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets) { + SkASSERT(fIsActive); + GR_VK_CALL(gpu->vkInterface(), CmdBindDescriptorSets(fCmdBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + layout, + firstSet, + setCount, + descriptorSets, + dynamicOffsetCount, + dynamicOffsets)); +} + +void GrVkCommandBuffer::bindPipeline(const GrVkGpu* gpu, const GrVkPipeline* pipeline) { + SkASSERT(fIsActive); + GR_VK_CALL(gpu->vkInterface(), CmdBindPipeline(fCmdBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline->pipeline())); + this->addResource(pipeline); +} + +void GrVkCommandBuffer::drawIndexed(const GrVkGpu* gpu, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance) { + SkASSERT(fIsActive); + SkASSERT(fActiveRenderPass); + this->addingWork(gpu); + GR_VK_CALL(gpu->vkInterface(), CmdDrawIndexed(fCmdBuffer, + indexCount, + instanceCount, + firstIndex, + vertexOffset, + firstInstance)); +} + +void GrVkCommandBuffer::draw(const GrVkGpu* gpu, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) { + SkASSERT(fIsActive); + SkASSERT(fActiveRenderPass); + this->addingWork(gpu); + GR_VK_CALL(gpu->vkInterface(), CmdDraw(fCmdBuffer, + vertexCount, + instanceCount, + firstVertex, + firstInstance)); +} + +void GrVkCommandBuffer::setViewport(const GrVkGpu* gpu, + uint32_t firstViewport, + uint32_t viewportCount, + const VkViewport* viewports) { + SkASSERT(fIsActive); + SkASSERT(1 == viewportCount); + if (memcmp(viewports, &fCachedViewport, sizeof(VkViewport))) { + GR_VK_CALL(gpu->vkInterface(), CmdSetViewport(fCmdBuffer, + firstViewport, + viewportCount, + viewports)); + fCachedViewport = viewports[0]; + } +} + +void GrVkCommandBuffer::setScissor(const GrVkGpu* gpu, + uint32_t firstScissor, + uint32_t scissorCount, + const VkRect2D* scissors) { + SkASSERT(fIsActive); + SkASSERT(1 == scissorCount); + if (memcmp(scissors, &fCachedScissor, sizeof(VkRect2D))) { + GR_VK_CALL(gpu->vkInterface(), CmdSetScissor(fCmdBuffer, + firstScissor, + scissorCount, + scissors)); + fCachedScissor = scissors[0]; + } +} + +void GrVkCommandBuffer::setBlendConstants(const GrVkGpu* gpu, + const float blendConstants[4]) { + SkASSERT(fIsActive); + if (memcmp(blendConstants, fCachedBlendConstant, 4 * sizeof(float))) { + GR_VK_CALL(gpu->vkInterface(), CmdSetBlendConstants(fCmdBuffer, blendConstants)); + memcpy(fCachedBlendConstant, blendConstants, 4 * sizeof(float)); + } +} + +void GrVkCommandBuffer::addingWork(const GrVkGpu* gpu) { + this->submitPipelineBarriers(gpu); + fHasWork = true; +} + +/////////////////////////////////////////////////////////////////////////////// +// PrimaryCommandBuffer +//////////////////////////////////////////////////////////////////////////////// +GrVkPrimaryCommandBuffer::~GrVkPrimaryCommandBuffer() { + // Should have ended any render pass we're in the middle of + SkASSERT(!fActiveRenderPass); +} + +GrVkPrimaryCommandBuffer* GrVkPrimaryCommandBuffer::Create(const GrVkGpu* gpu, + GrVkCommandPool* cmdPool) { + const VkCommandBufferAllocateInfo cmdInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, // sType + nullptr, // pNext + cmdPool->vkCommandPool(), // commandPool + VK_COMMAND_BUFFER_LEVEL_PRIMARY, // level + 1 // bufferCount + }; + + VkCommandBuffer cmdBuffer; + VkResult err = GR_VK_CALL(gpu->vkInterface(), AllocateCommandBuffers(gpu->device(), + &cmdInfo, + &cmdBuffer)); + if (err) { + return nullptr; + } + return new GrVkPrimaryCommandBuffer(cmdBuffer, cmdPool); +} + +void GrVkPrimaryCommandBuffer::begin(const GrVkGpu* gpu) { + SkASSERT(!fIsActive); + VkCommandBufferBeginInfo cmdBufferBeginInfo; + memset(&cmdBufferBeginInfo, 0, sizeof(VkCommandBufferBeginInfo)); + cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + cmdBufferBeginInfo.pNext = nullptr; + cmdBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + cmdBufferBeginInfo.pInheritanceInfo = nullptr; + + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), BeginCommandBuffer(fCmdBuffer, + &cmdBufferBeginInfo)); + fIsActive = true; +} + +void GrVkPrimaryCommandBuffer::end(GrVkGpu* gpu) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + + this->submitPipelineBarriers(gpu); + + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer)); + this->invalidateState(); + fIsActive = false; + fHasWork = false; +} + +void GrVkPrimaryCommandBuffer::beginRenderPass(const GrVkGpu* gpu, + const GrVkRenderPass* renderPass, + const VkClearValue clearValues[], + const GrVkRenderTarget& target, + const SkIRect& bounds, + bool forSecondaryCB) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + SkASSERT(renderPass->isCompatible(target)); + + this->addingWork(gpu); + + VkRenderPassBeginInfo beginInfo; + VkRect2D renderArea; + renderArea.offset = { bounds.fLeft , bounds.fTop }; + renderArea.extent = { (uint32_t)bounds.width(), (uint32_t)bounds.height() }; + + memset(&beginInfo, 0, sizeof(VkRenderPassBeginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + beginInfo.pNext = nullptr; + beginInfo.renderPass = renderPass->vkRenderPass(); + beginInfo.framebuffer = target.framebuffer()->framebuffer(); + beginInfo.renderArea = renderArea; + beginInfo.clearValueCount = renderPass->clearValueCount(); + beginInfo.pClearValues = clearValues; + + VkSubpassContents contents = forSecondaryCB ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS + : VK_SUBPASS_CONTENTS_INLINE; + + GR_VK_CALL(gpu->vkInterface(), CmdBeginRenderPass(fCmdBuffer, &beginInfo, contents)); + fActiveRenderPass = renderPass; + this->addResource(renderPass); + target.addResources(*this); +} + +void GrVkPrimaryCommandBuffer::endRenderPass(const GrVkGpu* gpu) { + SkASSERT(fIsActive); + SkASSERT(fActiveRenderPass); + this->addingWork(gpu); + GR_VK_CALL(gpu->vkInterface(), CmdEndRenderPass(fCmdBuffer)); + fActiveRenderPass = nullptr; +} + +void GrVkPrimaryCommandBuffer::executeCommands(const GrVkGpu* gpu, + std::unique_ptr buffer) { + // The Vulkan spec allows secondary command buffers to be executed on a primary command buffer + // if the command pools both were created from were created with the same queue family. However, + // we currently always create them from the same pool. + SkASSERT(buffer->commandPool() == fCmdPool); + SkASSERT(fIsActive); + SkASSERT(!buffer->fIsActive); + SkASSERT(fActiveRenderPass); + SkASSERT(fActiveRenderPass->isCompatible(*buffer->fActiveRenderPass)); + + this->addingWork(gpu); + + GR_VK_CALL(gpu->vkInterface(), CmdExecuteCommands(fCmdBuffer, 1, &buffer->fCmdBuffer)); + fSecondaryCommandBuffers.push_back(std::move(buffer)); + // When executing a secondary command buffer all state (besides render pass state) becomes + // invalidated and must be reset. This includes bound buffers, pipelines, dynamic state, etc. + this->invalidateState(); +} + +static void submit_to_queue(const GrVkInterface* interface, + VkQueue queue, + VkFence fence, + uint32_t waitCount, + const VkSemaphore* waitSemaphores, + const VkPipelineStageFlags* waitStages, + uint32_t commandBufferCount, + const VkCommandBuffer* commandBuffers, + uint32_t signalCount, + const VkSemaphore* signalSemaphores, + GrProtected protectedContext) { + VkProtectedSubmitInfo protectedSubmitInfo; + if (protectedContext == GrProtected::kYes) { + memset(&protectedSubmitInfo, 0, sizeof(VkProtectedSubmitInfo)); + protectedSubmitInfo.sType = VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO; + protectedSubmitInfo.pNext = nullptr; + protectedSubmitInfo.protectedSubmit = VK_TRUE; + } + + VkSubmitInfo submitInfo; + memset(&submitInfo, 0, sizeof(VkSubmitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.pNext = protectedContext == GrProtected::kYes ? &protectedSubmitInfo : nullptr; + submitInfo.waitSemaphoreCount = waitCount; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = commandBufferCount; + submitInfo.pCommandBuffers = commandBuffers; + submitInfo.signalSemaphoreCount = signalCount; + submitInfo.pSignalSemaphores = signalSemaphores; + GR_VK_CALL_ERRCHECK(interface, QueueSubmit(queue, 1, &submitInfo, fence)); +} + +void GrVkPrimaryCommandBuffer::submitToQueue( + const GrVkGpu* gpu, + VkQueue queue, + GrVkGpu::SyncQueue sync, + SkTArray& signalSemaphores, + SkTArray& waitSemaphores) { + SkASSERT(!fIsActive); + + VkResult err; + if (VK_NULL_HANDLE == fSubmitFence) { + VkFenceCreateInfo fenceInfo; + memset(&fenceInfo, 0, sizeof(VkFenceCreateInfo)); + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + err = GR_VK_CALL(gpu->vkInterface(), CreateFence(gpu->device(), &fenceInfo, nullptr, + &fSubmitFence)); + SkASSERT(!err); + } else { + GR_VK_CALL(gpu->vkInterface(), ResetFences(gpu->device(), 1, &fSubmitFence)); + } + + int signalCount = signalSemaphores.count(); + int waitCount = waitSemaphores.count(); + + if (0 == signalCount && 0 == waitCount) { + // This command buffer has no dependent semaphores so we can simply just submit it to the + // queue with no worries. + submit_to_queue(gpu->vkInterface(), queue, fSubmitFence, 0, nullptr, nullptr, 1, + &fCmdBuffer, 0, nullptr, + gpu->protectedContext() ? GrProtected::kYes : GrProtected::kNo); + } else { + SkTArray vkSignalSems(signalCount); + for (int i = 0; i < signalCount; ++i) { + if (signalSemaphores[i]->shouldSignal()) { + this->addResource(signalSemaphores[i]); + vkSignalSems.push_back(signalSemaphores[i]->semaphore()); + } + } + + SkTArray vkWaitSems(waitCount); + SkTArray vkWaitStages(waitCount); + for (int i = 0; i < waitCount; ++i) { + if (waitSemaphores[i]->shouldWait()) { + this->addResource(waitSemaphores[i]); + vkWaitSems.push_back(waitSemaphores[i]->semaphore()); + vkWaitStages.push_back(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + } + } + submit_to_queue(gpu->vkInterface(), queue, fSubmitFence, vkWaitSems.count(), + vkWaitSems.begin(), vkWaitStages.begin(), 1, &fCmdBuffer, + vkSignalSems.count(), vkSignalSems.begin(), + gpu->protectedContext() ? GrProtected::kYes : GrProtected::kNo); + for (int i = 0; i < signalCount; ++i) { + signalSemaphores[i]->markAsSignaled(); + } + for (int i = 0; i < waitCount; ++i) { + waitSemaphores[i]->markAsWaited(); + } + } + + if (GrVkGpu::kForce_SyncQueue == sync) { + err = GR_VK_CALL(gpu->vkInterface(), + WaitForFences(gpu->device(), 1, &fSubmitFence, true, UINT64_MAX)); + if (VK_TIMEOUT == err) { + SkDebugf("Fence failed to signal: %d\n", err); + SK_ABORT("failing"); + } + SkASSERT(!err); + + fFinishedProcs.reset(); + + // Destroy the fence + GR_VK_CALL(gpu->vkInterface(), DestroyFence(gpu->device(), fSubmitFence, nullptr)); + fSubmitFence = VK_NULL_HANDLE; + } +} + +bool GrVkPrimaryCommandBuffer::finished(const GrVkGpu* gpu) { + SkASSERT(!fIsActive); + if (VK_NULL_HANDLE == fSubmitFence) { + return true; + } + + VkResult err = GR_VK_CALL(gpu->vkInterface(), GetFenceStatus(gpu->device(), fSubmitFence)); + switch (err) { + case VK_SUCCESS: + return true; + + case VK_NOT_READY: + return false; + + default: + SkDebugf("Error getting fence status: %d\n", err); + SK_ABORT("failing"); + break; + } + + return false; +} + +void GrVkPrimaryCommandBuffer::addFinishedProc(sk_sp finishedProc) { + fFinishedProcs.push_back(std::move(finishedProc)); +} + +void GrVkPrimaryCommandBuffer::onReleaseResources(GrVkGpu* gpu) { + for (int i = 0; i < fSecondaryCommandBuffers.count(); ++i) { + fSecondaryCommandBuffers[i]->releaseResources(gpu); + } + fFinishedProcs.reset(); +} + +void GrVkPrimaryCommandBuffer::recycleSecondaryCommandBuffers(GrVkGpu* gpu) { + for (int i = 0; i < fSecondaryCommandBuffers.count(); ++i) { + SkASSERT(fSecondaryCommandBuffers[i]->commandPool() == fCmdPool); + fSecondaryCommandBuffers[i].release()->recycle(gpu); + } + fSecondaryCommandBuffers.reset(); +} + +void GrVkPrimaryCommandBuffer::copyImage(const GrVkGpu* gpu, + GrVkImage* srcImage, + VkImageLayout srcLayout, + GrVkImage* dstImage, + VkImageLayout dstLayout, + uint32_t copyRegionCount, + const VkImageCopy* copyRegions) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); + this->addResource(srcImage->resource()); + this->addResource(dstImage->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdCopyImage(fCmdBuffer, + srcImage->image(), + srcLayout, + dstImage->image(), + dstLayout, + copyRegionCount, + copyRegions)); +} + +void GrVkPrimaryCommandBuffer::blitImage(const GrVkGpu* gpu, + const GrVkResource* srcResource, + VkImage srcImage, + VkImageLayout srcLayout, + const GrVkResource* dstResource, + VkImage dstImage, + VkImageLayout dstLayout, + uint32_t blitRegionCount, + const VkImageBlit* blitRegions, + VkFilter filter) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); + this->addResource(srcResource); + this->addResource(dstResource); + GR_VK_CALL(gpu->vkInterface(), CmdBlitImage(fCmdBuffer, + srcImage, + srcLayout, + dstImage, + dstLayout, + blitRegionCount, + blitRegions, + filter)); +} + +void GrVkPrimaryCommandBuffer::blitImage(const GrVkGpu* gpu, + const GrVkImage& srcImage, + const GrVkImage& dstImage, + uint32_t blitRegionCount, + const VkImageBlit* blitRegions, + VkFilter filter) { + this->blitImage(gpu, + srcImage.resource(), + srcImage.image(), + srcImage.currentLayout(), + dstImage.resource(), + dstImage.image(), + dstImage.currentLayout(), + blitRegionCount, + blitRegions, + filter); +} + + +void GrVkPrimaryCommandBuffer::copyImageToBuffer(const GrVkGpu* gpu, + GrVkImage* srcImage, + VkImageLayout srcLayout, + GrVkTransferBuffer* dstBuffer, + uint32_t copyRegionCount, + const VkBufferImageCopy* copyRegions) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); + this->addResource(srcImage->resource()); + this->addResource(dstBuffer->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdCopyImageToBuffer(fCmdBuffer, + srcImage->image(), + srcLayout, + dstBuffer->buffer(), + copyRegionCount, + copyRegions)); +} + +void GrVkPrimaryCommandBuffer::copyBufferToImage(const GrVkGpu* gpu, + GrVkTransferBuffer* srcBuffer, + GrVkImage* dstImage, + VkImageLayout dstLayout, + uint32_t copyRegionCount, + const VkBufferImageCopy* copyRegions) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); + this->addResource(srcBuffer->resource()); + this->addResource(dstImage->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdCopyBufferToImage(fCmdBuffer, + srcBuffer->buffer(), + dstImage->image(), + dstLayout, + copyRegionCount, + copyRegions)); +} + + +void GrVkPrimaryCommandBuffer::copyBuffer(GrVkGpu* gpu, + GrVkBuffer* srcBuffer, + GrVkBuffer* dstBuffer, + uint32_t regionCount, + const VkBufferCopy* regions) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); +#ifdef SK_DEBUG + for (uint32_t i = 0; i < regionCount; ++i) { + const VkBufferCopy& region = regions[i]; + SkASSERT(region.size > 0); + SkASSERT(region.srcOffset < srcBuffer->size()); + SkASSERT(region.dstOffset < dstBuffer->size()); + SkASSERT(region.srcOffset + region.size <= srcBuffer->size()); + SkASSERT(region.dstOffset + region.size <= dstBuffer->size()); + } +#endif + this->addResource(srcBuffer->resource()); + this->addResource(dstBuffer->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdCopyBuffer(fCmdBuffer, + srcBuffer->buffer(), + dstBuffer->buffer(), + regionCount, + regions)); +} + +void GrVkPrimaryCommandBuffer::updateBuffer(GrVkGpu* gpu, + GrVkBuffer* dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize dataSize, + const void* data) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + SkASSERT(0 == (dstOffset & 0x03)); // four byte aligned + // TODO: handle larger transfer sizes + SkASSERT(dataSize <= 65536); + SkASSERT(0 == (dataSize & 0x03)); // four byte aligned + this->addingWork(gpu); + this->addResource(dstBuffer->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdUpdateBuffer(fCmdBuffer, + dstBuffer->buffer(), + dstOffset, + dataSize, + (const uint32_t*) data)); +} + +void GrVkPrimaryCommandBuffer::clearColorImage(const GrVkGpu* gpu, + GrVkImage* image, + const VkClearColorValue* color, + uint32_t subRangeCount, + const VkImageSubresourceRange* subRanges) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); + this->addResource(image->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdClearColorImage(fCmdBuffer, + image->image(), + image->currentLayout(), + color, + subRangeCount, + subRanges)); +} + +void GrVkPrimaryCommandBuffer::clearDepthStencilImage(const GrVkGpu* gpu, + GrVkImage* image, + const VkClearDepthStencilValue* color, + uint32_t subRangeCount, + const VkImageSubresourceRange* subRanges) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + this->addingWork(gpu); + this->addResource(image->resource()); + GR_VK_CALL(gpu->vkInterface(), CmdClearDepthStencilImage(fCmdBuffer, + image->image(), + image->currentLayout(), + color, + subRangeCount, + subRanges)); +} + +void GrVkPrimaryCommandBuffer::resolveImage(GrVkGpu* gpu, + const GrVkImage& srcImage, + const GrVkImage& dstImage, + uint32_t regionCount, + const VkImageResolve* regions) { + SkASSERT(fIsActive); + SkASSERT(!fActiveRenderPass); + + this->addingWork(gpu); + this->addResource(srcImage.resource()); + this->addResource(dstImage.resource()); + + GR_VK_CALL(gpu->vkInterface(), CmdResolveImage(fCmdBuffer, + srcImage.image(), + srcImage.currentLayout(), + dstImage.image(), + dstImage.currentLayout(), + regionCount, + regions)); +} + +void GrVkPrimaryCommandBuffer::onFreeGPUData(GrVkGpu* gpu) const { + SkASSERT(!fActiveRenderPass); + // Destroy the fence, if any + if (VK_NULL_HANDLE != fSubmitFence) { + GR_VK_CALL(gpu->vkInterface(), DestroyFence(gpu->device(), fSubmitFence, nullptr)); + } + for (const auto& buffer : fSecondaryCommandBuffers) { + buffer->freeGPUData(gpu); + } +} + +void GrVkPrimaryCommandBuffer::onAbandonGPUData() const { + SkASSERT(!fActiveRenderPass); + for (const auto& buffer : fSecondaryCommandBuffers) { + buffer->abandonGPUData(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// SecondaryCommandBuffer +//////////////////////////////////////////////////////////////////////////////// + +GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(const GrVkGpu* gpu, + GrVkCommandPool* cmdPool) { + SkASSERT(cmdPool); + const VkCommandBufferAllocateInfo cmdInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, // sType + nullptr, // pNext + cmdPool->vkCommandPool(), // commandPool + VK_COMMAND_BUFFER_LEVEL_SECONDARY, // level + 1 // bufferCount + }; + + VkCommandBuffer cmdBuffer; + VkResult err = GR_VK_CALL(gpu->vkInterface(), AllocateCommandBuffers(gpu->device(), + &cmdInfo, + &cmdBuffer)); + if (err) { + return nullptr; + } + return new GrVkSecondaryCommandBuffer(cmdBuffer, cmdPool); +} + +GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(VkCommandBuffer cmdBuffer) { + return new GrVkSecondaryCommandBuffer(cmdBuffer, nullptr); +} + +void GrVkSecondaryCommandBuffer::begin(const GrVkGpu* gpu, const GrVkFramebuffer* framebuffer, + const GrVkRenderPass* compatibleRenderPass) { + SkASSERT(!fIsActive); + SkASSERT(compatibleRenderPass); + fActiveRenderPass = compatibleRenderPass; + + if (!this->isWrapped()) { + VkCommandBufferInheritanceInfo inheritanceInfo; + memset(&inheritanceInfo, 0, sizeof(VkCommandBufferInheritanceInfo)); + inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; + inheritanceInfo.pNext = nullptr; + inheritanceInfo.renderPass = fActiveRenderPass->vkRenderPass(); + inheritanceInfo.subpass = 0; // Currently only using 1 subpass for each render pass + inheritanceInfo.framebuffer = framebuffer ? framebuffer->framebuffer() : VK_NULL_HANDLE; + inheritanceInfo.occlusionQueryEnable = false; + inheritanceInfo.queryFlags = 0; + inheritanceInfo.pipelineStatistics = 0; + + VkCommandBufferBeginInfo cmdBufferBeginInfo; + memset(&cmdBufferBeginInfo, 0, sizeof(VkCommandBufferBeginInfo)); + cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + cmdBufferBeginInfo.pNext = nullptr; + cmdBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + cmdBufferBeginInfo.pInheritanceInfo = &inheritanceInfo; + + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), BeginCommandBuffer(fCmdBuffer, + &cmdBufferBeginInfo)); + } + fIsActive = true; +} + +void GrVkSecondaryCommandBuffer::end(GrVkGpu* gpu) { + SkASSERT(fIsActive); + if (!this->isWrapped()) { + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer)); + } + this->invalidateState(); + fIsActive = false; + fHasWork = false; +} + +void GrVkSecondaryCommandBuffer::recycle(GrVkGpu* gpu) { + if (this->isWrapped()) { + this->freeGPUData(gpu); + delete this; + } else { + fCmdPool->recycleSecondaryCommandBuffer(this); + } +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.h new file mode 100644 index 0000000000..ec35c124f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkCommandBuffer.h @@ -0,0 +1,347 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkCommandBuffer_DEFINED +#define GrVkCommandBuffer_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkResource.h" +#include "src/gpu/vk/GrVkSemaphore.h" +#include "src/gpu/vk/GrVkUtil.h" + +class GrVkBuffer; +class GrVkFramebuffer; +class GrVkIndexBuffer; +class GrVkImage; +class GrVkPipeline; +class GrVkPipelineState; +class GrVkRenderPass; +class GrVkRenderTarget; +class GrVkTransferBuffer; +class GrVkVertexBuffer; + +class GrVkCommandBuffer { +public: + virtual ~GrVkCommandBuffer() {} + + void invalidateState(); + + //////////////////////////////////////////////////////////////////////////// + // CommandBuffer commands + //////////////////////////////////////////////////////////////////////////// + enum BarrierType { + kBufferMemory_BarrierType, + kImageMemory_BarrierType + }; + + void pipelineBarrier(const GrVkGpu* gpu, + const GrVkResource* resource, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + BarrierType barrierType, + void* barrier); + + void bindInputBuffer(GrVkGpu* gpu, uint32_t binding, const GrVkVertexBuffer* vbuffer); + + void bindIndexBuffer(GrVkGpu* gpu, const GrVkIndexBuffer* ibuffer); + + void bindPipeline(const GrVkGpu* gpu, const GrVkPipeline* pipeline); + + void bindDescriptorSets(const GrVkGpu* gpu, + GrVkPipelineState*, + VkPipelineLayout layout, + uint32_t firstSet, + uint32_t setCount, + const VkDescriptorSet* descriptorSets, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets); + + GrVkCommandPool* commandPool() { return fCmdPool; } + + void setViewport(const GrVkGpu* gpu, + uint32_t firstViewport, + uint32_t viewportCount, + const VkViewport* viewports); + + void setScissor(const GrVkGpu* gpu, + uint32_t firstScissor, + uint32_t scissorCount, + const VkRect2D* scissors); + + void setBlendConstants(const GrVkGpu* gpu, const float blendConstants[4]); + + // Commands that only work inside of a render pass + void clearAttachments(const GrVkGpu* gpu, + int numAttachments, + const VkClearAttachment* attachments, + int numRects, + const VkClearRect* clearRects); + + void drawIndexed(const GrVkGpu* gpu, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + + void draw(const GrVkGpu* gpu, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + + // Add ref-counted resource that will be tracked and released when this command buffer finishes + // execution + void addResource(const GrVkResource* resource) { + resource->ref(); + resource->notifyAddedToCommandBuffer(); + fTrackedResources.append(1, &resource); + } + + // Add ref-counted resource that will be tracked and released when this command buffer finishes + // execution. When it is released, it will signal that the resource can be recycled for reuse. + void addRecycledResource(const GrVkRecycledResource* resource) { + resource->ref(); + resource->notifyAddedToCommandBuffer(); + fTrackedRecycledResources.append(1, &resource); + } + + void releaseResources(GrVkGpu* gpu); + + void freeGPUData(GrVkGpu* gpu) const; + void abandonGPUData() const; + + bool hasWork() const { return fHasWork; } + +protected: + GrVkCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool, + const GrVkRenderPass* rp = nullptr) + : fIsActive(false) + , fActiveRenderPass(rp) + , fCmdBuffer(cmdBuffer) + , fCmdPool(cmdPool) + , fNumResets(0) { + fTrackedResources.setReserve(kInitialTrackedResourcesCount); + fTrackedRecycledResources.setReserve(kInitialTrackedResourcesCount); + this->invalidateState(); + } + + bool isWrapped() const { return fCmdPool == nullptr; } + + void addingWork(const GrVkGpu* gpu); + + void submitPipelineBarriers(const GrVkGpu* gpu); + + SkTDArray fTrackedResources; + SkTDArray fTrackedRecycledResources; + + // Tracks whether we are in the middle of a command buffer begin/end calls and thus can add + // new commands to the buffer; + bool fIsActive; + bool fHasWork = false; + + // Stores a pointer to the current active render pass (i.e. begin has been called but not + // end). A nullptr means there is no active render pass. The GrVKCommandBuffer does not own + // the render pass. + const GrVkRenderPass* fActiveRenderPass; + + VkCommandBuffer fCmdBuffer; + + // Raw pointer, not refcounted. The command pool controls the command buffer's lifespan, so + // it's guaranteed to outlive us. + GrVkCommandPool* fCmdPool; + +private: + static const int kInitialTrackedResourcesCount = 32; + + virtual void onReleaseResources(GrVkGpu* gpu) {} + virtual void onFreeGPUData(GrVkGpu* gpu) const = 0; + virtual void onAbandonGPUData() const = 0; + + static constexpr uint32_t kMaxInputBuffers = 2; + + VkBuffer fBoundInputBuffers[kMaxInputBuffers]; + VkBuffer fBoundIndexBuffer; + + // When resetting the command buffer, we remove the tracked resources from their arrays, and + // we prefer to not free all the memory every time so usually we just rewind. However, to avoid + // all arrays growing to the max size, after so many resets we'll do a full reset of the tracked + // resource arrays. + static const int kNumRewindResetsBeforeFullReset = 8; + int fNumResets; + + // Cached values used for dynamic state updates + VkViewport fCachedViewport; + VkRect2D fCachedScissor; + float fCachedBlendConstant[4]; + +#ifdef SK_DEBUG + mutable bool fResourcesReleased = false; +#endif + // Tracking of memory barriers so that we can submit them all in a batch together. + SkSTArray<4, VkBufferMemoryBarrier> fBufferBarriers; + SkSTArray<1, VkImageMemoryBarrier> fImageBarriers; + bool fBarriersByRegion = false; + VkPipelineStageFlags fSrcStageMask = 0; + VkPipelineStageFlags fDstStageMask = 0; +}; + +class GrVkSecondaryCommandBuffer; + +class GrVkPrimaryCommandBuffer : public GrVkCommandBuffer { +public: + ~GrVkPrimaryCommandBuffer() override; + + static GrVkPrimaryCommandBuffer* Create(const GrVkGpu* gpu, GrVkCommandPool* cmdPool); + + void begin(const GrVkGpu* gpu); + void end(GrVkGpu* gpu); + + // Begins render pass on this command buffer. The framebuffer from GrVkRenderTarget will be used + // in the render pass. + void beginRenderPass(const GrVkGpu* gpu, + const GrVkRenderPass* renderPass, + const VkClearValue clearValues[], + const GrVkRenderTarget& target, + const SkIRect& bounds, + bool forSecondaryCB); + void endRenderPass(const GrVkGpu* gpu); + + // Submits the SecondaryCommandBuffer into this command buffer. It is required that we are + // currently inside a render pass that is compatible with the one used to create the + // SecondaryCommandBuffer. + void executeCommands(const GrVkGpu* gpu, + std::unique_ptr secondaryBuffer); + + // Commands that only work outside of a render pass + void clearColorImage(const GrVkGpu* gpu, + GrVkImage* image, + const VkClearColorValue* color, + uint32_t subRangeCount, + const VkImageSubresourceRange* subRanges); + + void clearDepthStencilImage(const GrVkGpu* gpu, + GrVkImage* image, + const VkClearDepthStencilValue* color, + uint32_t subRangeCount, + const VkImageSubresourceRange* subRanges); + + void copyImage(const GrVkGpu* gpu, + GrVkImage* srcImage, + VkImageLayout srcLayout, + GrVkImage* dstImage, + VkImageLayout dstLayout, + uint32_t copyRegionCount, + const VkImageCopy* copyRegions); + + void blitImage(const GrVkGpu* gpu, + const GrVkResource* srcResource, + VkImage srcImage, + VkImageLayout srcLayout, + const GrVkResource* dstResource, + VkImage dstImage, + VkImageLayout dstLayout, + uint32_t blitRegionCount, + const VkImageBlit* blitRegions, + VkFilter filter); + + void blitImage(const GrVkGpu* gpu, + const GrVkImage& srcImage, + const GrVkImage& dstImage, + uint32_t blitRegionCount, + const VkImageBlit* blitRegions, + VkFilter filter); + + void copyImageToBuffer(const GrVkGpu* gpu, + GrVkImage* srcImage, + VkImageLayout srcLayout, + GrVkTransferBuffer* dstBuffer, + uint32_t copyRegionCount, + const VkBufferImageCopy* copyRegions); + + void copyBufferToImage(const GrVkGpu* gpu, + GrVkTransferBuffer* srcBuffer, + GrVkImage* dstImage, + VkImageLayout dstLayout, + uint32_t copyRegionCount, + const VkBufferImageCopy* copyRegions); + + void copyBuffer(GrVkGpu* gpu, + GrVkBuffer* srcBuffer, + GrVkBuffer* dstBuffer, + uint32_t regionCount, + const VkBufferCopy* regions); + + void updateBuffer(GrVkGpu* gpu, + GrVkBuffer* dstBuffer, + VkDeviceSize dstOffset, + VkDeviceSize dataSize, + const void* data); + + void resolveImage(GrVkGpu* gpu, + const GrVkImage& srcImage, + const GrVkImage& dstImage, + uint32_t regionCount, + const VkImageResolve* regions); + + void submitToQueue(const GrVkGpu* gpu, VkQueue queue, GrVkGpu::SyncQueue sync, + SkTArray& signalSemaphores, + SkTArray& waitSemaphores); + bool finished(const GrVkGpu* gpu); + + void addFinishedProc(sk_sp finishedProc); + + void recycleSecondaryCommandBuffers(GrVkGpu* gpu); + +private: + explicit GrVkPrimaryCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool) + : INHERITED(cmdBuffer, cmdPool) + , fSubmitFence(VK_NULL_HANDLE) {} + + void onFreeGPUData(GrVkGpu* gpu) const override; + + void onAbandonGPUData() const override; + + void onReleaseResources(GrVkGpu* gpu) override; + + SkTArray, true> fSecondaryCommandBuffers; + VkFence fSubmitFence; + SkTArray> fFinishedProcs; + + typedef GrVkCommandBuffer INHERITED; +}; + +class GrVkSecondaryCommandBuffer : public GrVkCommandBuffer { +public: + static GrVkSecondaryCommandBuffer* Create(const GrVkGpu* gpu, GrVkCommandPool* cmdPool); + // Used for wrapping an external secondary command buffer. + static GrVkSecondaryCommandBuffer* Create(VkCommandBuffer externalSecondaryCB); + + void begin(const GrVkGpu* gpu, const GrVkFramebuffer* framebuffer, + const GrVkRenderPass* compatibleRenderPass); + void end(GrVkGpu* gpu); + + void recycle(GrVkGpu* gpu); + + VkCommandBuffer vkCommandBuffer() { return fCmdBuffer; } + +private: + explicit GrVkSecondaryCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool) + : INHERITED(cmdBuffer, cmdPool) {} + + void onFreeGPUData(GrVkGpu* gpu) const override {} + + void onAbandonGPUData() const override {} + + friend class GrVkPrimaryCommandBuffer; + + typedef GrVkCommandBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkCommandPool.cpp b/gfx/skia/skia/src/gpu/vk/GrVkCommandPool.cpp new file mode 100644 index 0000000000..5dc5a94fdc --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkCommandPool.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkCommandPool.h" + +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkGpu.h" + +GrVkCommandPool* GrVkCommandPool::Create(const GrVkGpu* gpu) { + VkCommandPoolCreateFlags cmdPoolCreateFlags = + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + if (gpu->protectedContext()) { + cmdPoolCreateFlags |= VK_COMMAND_POOL_CREATE_PROTECTED_BIT; + } + + const VkCommandPoolCreateInfo cmdPoolInfo = { + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, // sType + nullptr, // pNext + cmdPoolCreateFlags, // CmdPoolCreateFlags + gpu->queueIndex(), // queueFamilyIndex + }; + VkCommandPool pool; + GR_VK_CALL_ERRCHECK( + gpu->vkInterface(), + CreateCommandPool(gpu->device(), &cmdPoolInfo, nullptr, &pool)); + return new GrVkCommandPool(gpu, pool); +} + +GrVkCommandPool::GrVkCommandPool(const GrVkGpu* gpu, VkCommandPool commandPool) + : fCommandPool(commandPool) { + fPrimaryCommandBuffer.reset(GrVkPrimaryCommandBuffer::Create(gpu, this)); +} + +std::unique_ptr GrVkCommandPool::findOrCreateSecondaryCommandBuffer( + GrVkGpu* gpu) { + std::unique_ptr result; + if (fAvailableSecondaryBuffers.count()) { + result = std::move(fAvailableSecondaryBuffers.back()); + fAvailableSecondaryBuffers.pop_back(); + } else{ + result.reset(GrVkSecondaryCommandBuffer::Create(gpu, this)); + } + return result; +} + +void GrVkCommandPool::recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* buffer) { + SkASSERT(buffer->commandPool() == this); + std::unique_ptr scb(buffer); + fAvailableSecondaryBuffers.push_back(std::move(scb)); +} + +void GrVkCommandPool::close() { + fOpen = false; +} + +void GrVkCommandPool::reset(GrVkGpu* gpu) { + SkASSERT(!fOpen); + fOpen = true; + fPrimaryCommandBuffer->recycleSecondaryCommandBuffers(gpu); + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), ResetCommandPool(gpu->device(), fCommandPool, 0)); +} + +void GrVkCommandPool::releaseResources(GrVkGpu* gpu) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(!fOpen); + fPrimaryCommandBuffer->releaseResources(gpu); +} + +void GrVkCommandPool::abandonGPUData() const { + fPrimaryCommandBuffer->abandonGPUData(); + for (const auto& buffer : fAvailableSecondaryBuffers) { + buffer->abandonGPUData(); + } +} + +void GrVkCommandPool::freeGPUData(GrVkGpu* gpu) const { + fPrimaryCommandBuffer->freeGPUData(gpu); + for (const auto& buffer : fAvailableSecondaryBuffers) { + buffer->freeGPUData(gpu); + } + if (fCommandPool != VK_NULL_HANDLE) { + GR_VK_CALL(gpu->vkInterface(), + DestroyCommandPool(gpu->device(), fCommandPool, nullptr)); + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkCommandPool.h b/gfx/skia/skia/src/gpu/vk/GrVkCommandPool.h new file mode 100644 index 0000000000..fd44d62e94 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkCommandPool.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkCommandPool_DEFINED +#define GrVkCommandPool_DEFINED + +#include "src/gpu/vk/GrVkInterface.h" +#include "src/gpu/vk/GrVkResource.h" +#include "src/gpu/vk/GrVkResourceProvider.h" + +class GrVkPrimaryCommandBuffer; +class GrVkSecondaryCommandBuffer; +class GrVkGpu; + +class GrVkCommandPool : public GrVkResource { +public: + static GrVkCommandPool* Create(const GrVkGpu* gpu); + + VkCommandPool vkCommandPool() const { + return fCommandPool; + } + + void reset(GrVkGpu* gpu); + + void releaseResources(GrVkGpu* gpu); + + GrVkPrimaryCommandBuffer* getPrimaryCommandBuffer() { return fPrimaryCommandBuffer.get(); } + + std::unique_ptr findOrCreateSecondaryCommandBuffer(GrVkGpu* gpu); + + void recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* buffer); + + // marks that we are finished with this command pool; it is not legal to continue creating or + // writing to command buffers in a closed pool + void close(); + + // returns true if close() has not been called + bool isOpen() const { return fOpen; } + +#ifdef SK_DEBUG + void dumpInfo() const override { + SkDebugf("GrVkCommandPool: %p (%d refs)\n", fCommandPool, this->getRefCnt()); + } +#endif + +private: + GrVkCommandPool() = delete; + + GrVkCommandPool(const GrVkGpu* gpu, VkCommandPool commandPool); + + void abandonGPUData() const override; + + void freeGPUData(GrVkGpu* gpu) const override; + + bool fOpen = true; + + VkCommandPool fCommandPool; + + std::unique_ptr fPrimaryCommandBuffer; + + // Array of available secondary command buffers that are not in flight + SkSTArray<4, std::unique_ptr, true> fAvailableSecondaryBuffers; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.cpp b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.cpp new file mode 100644 index 0000000000..16350bd3c8 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.cpp @@ -0,0 +1,51 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkDescriptorPool.h" + +#include "include/private/SkTemplates.h" +#include "src/gpu/vk/GrVkGpu.h" + + +GrVkDescriptorPool::GrVkDescriptorPool(const GrVkGpu* gpu, VkDescriptorType type, uint32_t count) + : INHERITED() + , fType (type) + , fCount(count) { + VkDescriptorPoolSize poolSize; + memset(&poolSize, 0, sizeof(VkDescriptorPoolSize)); + poolSize.descriptorCount = count; + poolSize.type = type; + + VkDescriptorPoolCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkDescriptorPoolCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + // This is an over/conservative estimate since each set may contain more than count descriptors. + createInfo.maxSets = count; + createInfo.poolSizeCount = 1; + createInfo.pPoolSizes = &poolSize; + + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), CreateDescriptorPool(gpu->device(), + &createInfo, + nullptr, + &fDescPool)); +} + +bool GrVkDescriptorPool::isCompatible(VkDescriptorType type, uint32_t count) const { + return fType == type && count <= fCount; +} + +void GrVkDescriptorPool::reset(const GrVkGpu* gpu) { + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), ResetDescriptorPool(gpu->device(), fDescPool, 0)); +} + +void GrVkDescriptorPool::freeGPUData(GrVkGpu* gpu) const { + // Destroying the VkDescriptorPool will automatically free and delete any VkDescriptorSets + // allocated from the pool. + GR_VK_CALL(gpu->vkInterface(), DestroyDescriptorPool(gpu->device(), fDescPool, nullptr)); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.h b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.h new file mode 100644 index 0000000000..db90e61a09 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorPool.h @@ -0,0 +1,50 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkDescriptorPool_DEFINED +#define GrVkDescriptorPool_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkGpu; + +/** + * We require that all descriptor sets are of a single descriptor type. We also use a pool to only + * make one type of descriptor set. Thus a single VkDescriptorPool will only allocated space for + * for one type of descriptor. + */ +class GrVkDescriptorPool : public GrVkResource { +public: + GrVkDescriptorPool(const GrVkGpu* gpu, VkDescriptorType type, uint32_t count); + + VkDescriptorPool descPool() const { return fDescPool; } + + void reset(const GrVkGpu* gpu); + + // Returns whether or not this descriptor pool could be used, assuming it gets fully reset and + // not in use by another draw, to support the requested type and count. + bool isCompatible(VkDescriptorType type, uint32_t count) const; + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkDescriptorPool: %d, type %d (%d refs)\n", fDescPool, fType, + this->getRefCnt()); + } +#endif + +private: + void freeGPUData(GrVkGpu* gpu) const override; + + VkDescriptorType fType; + uint32_t fCount; + VkDescriptorPool fDescPool; + + typedef GrVkResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.cpp b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.cpp new file mode 100644 index 0000000000..9de9a6778b --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.cpp @@ -0,0 +1,34 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkDescriptorSet.h" + +#include "src/gpu/vk/GrVkDescriptorPool.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkResourceProvider.h" + +GrVkDescriptorSet::GrVkDescriptorSet(VkDescriptorSet descSet, + GrVkDescriptorPool* pool, + GrVkDescriptorSetManager::Handle handle) + : fDescSet(descSet) + , fPool(pool) + , fHandle(handle) { + fPool->ref(); +} + +void GrVkDescriptorSet::freeGPUData(GrVkGpu* gpu) const { + fPool->unref(gpu); +} + +void GrVkDescriptorSet::onRecycle(GrVkGpu* gpu) const { + gpu->resourceProvider().recycleDescriptorSet(this, fHandle); +} + +void GrVkDescriptorSet::abandonGPUData() const { + fPool->unrefAndAbandon(); +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.h b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.h new file mode 100644 index 0000000000..d909511c45 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSet.h @@ -0,0 +1,44 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkDescriptorSet_DEFINED +#define GrVkDescriptorSet_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkDescriptorSetManager.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkDescriptorPool; +class GrVkGpu; + +class GrVkDescriptorSet : public GrVkRecycledResource { +public: + GrVkDescriptorSet(VkDescriptorSet descSet, + GrVkDescriptorPool* pool, + GrVkDescriptorSetManager::Handle handle); + + ~GrVkDescriptorSet() override {} + + VkDescriptorSet descriptorSet() const { return fDescSet; } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkDescriptorSet: %d (%d refs)\n", fDescSet, this->getRefCnt()); + } +#endif + +private: + void freeGPUData(GrVkGpu* gpu) const override; + void abandonGPUData() const override; + void onRecycle(GrVkGpu* gpu) const override; + + VkDescriptorSet fDescSet; + SkDEBUGCODE(mutable) GrVkDescriptorPool* fPool; + GrVkDescriptorSetManager::Handle fHandle; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.cpp b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.cpp new file mode 100644 index 0000000000..5869ae6911 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.cpp @@ -0,0 +1,336 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkDescriptorSetManager.h" + +#include "src/gpu/vk/GrVkDescriptorPool.h" +#include "src/gpu/vk/GrVkDescriptorSet.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkUniformHandler.h" + +#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS) +#include +#endif + +GrVkDescriptorSetManager* GrVkDescriptorSetManager::CreateUniformManager(GrVkGpu* gpu) { + SkSTArray<1, uint32_t> visibilities; + uint32_t stages = kVertex_GrShaderFlag | kFragment_GrShaderFlag; + if (gpu->vkCaps().shaderCaps()->geometryShaderSupport()) { + stages |= kGeometry_GrShaderFlag; + } + visibilities.push_back(stages); + + SkTArray samplers; + return new GrVkDescriptorSetManager(gpu, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, visibilities, + samplers); +} + +GrVkDescriptorSetManager* GrVkDescriptorSetManager::CreateSamplerManager( + GrVkGpu* gpu, VkDescriptorType type, const GrVkUniformHandler& uniformHandler) { + SkSTArray<4, uint32_t> visibilities; + SkSTArray<4, const GrVkSampler*> immutableSamplers; + SkASSERT(type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + for (int i = 0 ; i < uniformHandler.numSamplers(); ++i) { + visibilities.push_back(uniformHandler.samplerVisibility(i)); + immutableSamplers.push_back(uniformHandler.immutableSampler(i)); + } + return new GrVkDescriptorSetManager(gpu, type, visibilities, immutableSamplers); +} + +GrVkDescriptorSetManager* GrVkDescriptorSetManager::CreateSamplerManager( + GrVkGpu* gpu, VkDescriptorType type, const SkTArray& visibilities) { + SkSTArray<4, const GrVkSampler*> immutableSamplers; + SkASSERT(type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + for (int i = 0 ; i < visibilities.count(); ++i) { + immutableSamplers.push_back(nullptr); + } + return new GrVkDescriptorSetManager(gpu, type, visibilities, immutableSamplers); +} + +GrVkDescriptorSetManager::GrVkDescriptorSetManager( + GrVkGpu* gpu, VkDescriptorType type, + const SkTArray& visibilities, + const SkTArray& immutableSamplers) + : fPoolManager(type, gpu, visibilities, immutableSamplers) { +#ifdef SK_DEBUG + if (type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) { + SkASSERT(visibilities.count() == immutableSamplers.count()); + } else { + SkASSERT(immutableSamplers.count() == 0); + } +#endif + for (int i = 0; i < visibilities.count(); ++i) { + fBindingVisibilities.push_back(visibilities[i]); + } + for (int i = 0; i < immutableSamplers.count(); ++i) { + const GrVkSampler* sampler = immutableSamplers[i]; + if (sampler) { + sampler->ref(); + } + fImmutableSamplers.push_back(sampler); + } +} + +const GrVkDescriptorSet* GrVkDescriptorSetManager::getDescriptorSet(GrVkGpu* gpu, + const Handle& handle) { + const GrVkDescriptorSet* ds = nullptr; + int count = fFreeSets.count(); + if (count > 0) { + ds = fFreeSets[count - 1]; + fFreeSets.removeShuffle(count - 1); + } else { + VkDescriptorSet vkDS; + fPoolManager.getNewDescriptorSet(gpu, &vkDS); + + ds = new GrVkDescriptorSet(vkDS, fPoolManager.fPool, handle); + } + SkASSERT(ds); + return ds; +} + +void GrVkDescriptorSetManager::recycleDescriptorSet(const GrVkDescriptorSet* descSet) { + SkASSERT(descSet); + fFreeSets.push_back(descSet); +} + +void GrVkDescriptorSetManager::release(GrVkGpu* gpu) { + fPoolManager.freeGPUResources(gpu); + + for (int i = 0; i < fFreeSets.count(); ++i) { + fFreeSets[i]->unref(gpu); + } + fFreeSets.reset(); + + for (int i = 0; i < fImmutableSamplers.count(); ++i) { + if (fImmutableSamplers[i]) { + fImmutableSamplers[i]->unref(gpu); + } + } + fImmutableSamplers.reset(); +} + +void GrVkDescriptorSetManager::abandon() { + fPoolManager.abandonGPUResources(); + + for (int i = 0; i < fFreeSets.count(); ++i) { + fFreeSets[i]->unrefAndAbandon(); + } + fFreeSets.reset(); + + for (int i = 0; i < fImmutableSamplers.count(); ++i) { + if (fImmutableSamplers[i]) { + fImmutableSamplers[i]->unrefAndAbandon(); + } + } + fImmutableSamplers.reset(); +} + +bool GrVkDescriptorSetManager::isCompatible(VkDescriptorType type, + const GrVkUniformHandler* uniHandler) const { + SkASSERT(uniHandler); + if (type != fPoolManager.fDescType) { + return false; + } + + SkASSERT(type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + if (fBindingVisibilities.count() != uniHandler->numSamplers()) { + return false; + } + for (int i = 0; i < uniHandler->numSamplers(); ++i) { + if (uniHandler->samplerVisibility(i) != fBindingVisibilities[i] || + uniHandler->immutableSampler(i) != fImmutableSamplers[i]) { + return false; + } + } + return true; +} + +bool GrVkDescriptorSetManager::isCompatible(VkDescriptorType type, + const SkTArray& visibilities) const { + if (type != fPoolManager.fDescType) { + return false; + } + + if (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == type || + VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER == type) { + if (fBindingVisibilities.count() != visibilities.count()) { + return false; + } + for (int i = 0; i < visibilities.count(); ++i) { + if (visibilities[i] != fBindingVisibilities[i] || fImmutableSamplers[i] != nullptr) { + return false; + } + } + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +VkShaderStageFlags visibility_to_vk_stage_flags(uint32_t visibility) { + VkShaderStageFlags flags = 0; + + if (visibility & kVertex_GrShaderFlag) { + flags |= VK_SHADER_STAGE_VERTEX_BIT; + } + if (visibility & kGeometry_GrShaderFlag) { + flags |= VK_SHADER_STAGE_GEOMETRY_BIT; + } + if (visibility & kFragment_GrShaderFlag) { + flags |= VK_SHADER_STAGE_FRAGMENT_BIT; + } + return flags; +} + +GrVkDescriptorSetManager::DescriptorPoolManager::DescriptorPoolManager( + VkDescriptorType type, + GrVkGpu* gpu, + const SkTArray& visibilities, + const SkTArray& immutableSamplers) + : fDescType(type) + , fCurrentDescriptorCount(0) + , fPool(nullptr) { + + + if (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == type || + VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER == type) { + uint32_t numBindings = visibilities.count(); + std::unique_ptr dsSamplerBindings( + new VkDescriptorSetLayoutBinding[numBindings]); + for (uint32_t i = 0; i < numBindings; ++i) { + uint32_t visibility = visibilities[i]; + dsSamplerBindings[i].binding = i; + dsSamplerBindings[i].descriptorType = type; + dsSamplerBindings[i].descriptorCount = 1; + dsSamplerBindings[i].stageFlags = visibility_to_vk_stage_flags(visibility); + if (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == type) { + if (immutableSamplers[i]) { + dsSamplerBindings[i].pImmutableSamplers = immutableSamplers[i]->samplerPtr(); + } else { + dsSamplerBindings[i].pImmutableSamplers = nullptr; + } + } + } + + VkDescriptorSetLayoutCreateInfo dsSamplerLayoutCreateInfo; + memset(&dsSamplerLayoutCreateInfo, 0, sizeof(VkDescriptorSetLayoutCreateInfo)); + dsSamplerLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + dsSamplerLayoutCreateInfo.pNext = nullptr; + dsSamplerLayoutCreateInfo.flags = 0; + dsSamplerLayoutCreateInfo.bindingCount = numBindings; + // Setting to nullptr fixes an error in the param checker validation layer. Even though + // bindingCount is 0 (which is valid), it still tries to validate pBindings unless it is + // null. + dsSamplerLayoutCreateInfo.pBindings = numBindings ? dsSamplerBindings.get() : nullptr; + +#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS) + // skia:8713 + __lsan::ScopedDisabler lsanDisabler; +#endif + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), + CreateDescriptorSetLayout(gpu->device(), + &dsSamplerLayoutCreateInfo, + nullptr, + &fDescLayout)); + fDescCountPerSet = visibilities.count(); + } else { + SkASSERT(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER == type); + GR_STATIC_ASSERT(1 == kUniformDescPerSet); + SkASSERT(kUniformDescPerSet == visibilities.count()); + // Create Uniform Buffer Descriptor + VkDescriptorSetLayoutBinding dsUniBinding; + memset(&dsUniBinding, 0, sizeof(dsUniBinding)); + dsUniBinding.binding = GrVkUniformHandler::kUniformBinding; + dsUniBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + dsUniBinding.descriptorCount = 1; + dsUniBinding.stageFlags = visibility_to_vk_stage_flags(visibilities[0]); + dsUniBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo uniformLayoutCreateInfo; + memset(&uniformLayoutCreateInfo, 0, sizeof(VkDescriptorSetLayoutCreateInfo)); + uniformLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + uniformLayoutCreateInfo.pNext = nullptr; + uniformLayoutCreateInfo.flags = 0; + uniformLayoutCreateInfo.bindingCount = 1; + uniformLayoutCreateInfo.pBindings = &dsUniBinding; + +#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS) + // skia:8713 + __lsan::ScopedDisabler lsanDisabler; +#endif + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), CreateDescriptorSetLayout(gpu->device(), + &uniformLayoutCreateInfo, + nullptr, + &fDescLayout)); + fDescCountPerSet = kUniformDescPerSet; + } + + SkASSERT(fDescCountPerSet < kStartNumDescriptors); + fMaxDescriptors = kStartNumDescriptors; + SkASSERT(fMaxDescriptors > 0); + this->getNewPool(gpu); +} + +void GrVkDescriptorSetManager::DescriptorPoolManager::getNewPool(GrVkGpu* gpu) { + if (fPool) { + fPool->unref(gpu); + uint32_t newPoolSize = fMaxDescriptors + ((fMaxDescriptors + 1) >> 1); + if (newPoolSize < kMaxDescriptors) { + fMaxDescriptors = newPoolSize; + } else { + fMaxDescriptors = kMaxDescriptors; + } + + } + fPool = gpu->resourceProvider().findOrCreateCompatibleDescriptorPool(fDescType, + fMaxDescriptors); + SkASSERT(fPool); +} + +void GrVkDescriptorSetManager::DescriptorPoolManager::getNewDescriptorSet(GrVkGpu* gpu, + VkDescriptorSet* ds) { + if (!fMaxDescriptors) { + return; + } + fCurrentDescriptorCount += fDescCountPerSet; + if (fCurrentDescriptorCount > fMaxDescriptors) { + this->getNewPool(gpu); + fCurrentDescriptorCount = fDescCountPerSet; + } + + VkDescriptorSetAllocateInfo dsAllocateInfo; + memset(&dsAllocateInfo, 0, sizeof(VkDescriptorSetAllocateInfo)); + dsAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + dsAllocateInfo.pNext = nullptr; + dsAllocateInfo.descriptorPool = fPool->descPool(); + dsAllocateInfo.descriptorSetCount = 1; + dsAllocateInfo.pSetLayouts = &fDescLayout; + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), AllocateDescriptorSets(gpu->device(), + &dsAllocateInfo, + ds)); +} + +void GrVkDescriptorSetManager::DescriptorPoolManager::freeGPUResources(GrVkGpu* gpu) { + if (fDescLayout) { + GR_VK_CALL(gpu->vkInterface(), DestroyDescriptorSetLayout(gpu->device(), fDescLayout, + nullptr)); + fDescLayout = VK_NULL_HANDLE; + } + + if (fPool) { + fPool->unref(gpu); + fPool = nullptr; + } +} + +void GrVkDescriptorSetManager::DescriptorPoolManager::abandonGPUResources() { + fDescLayout = VK_NULL_HANDLE; + if (fPool) { + fPool->unrefAndAbandon(); + fPool = nullptr; + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.h b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.h new file mode 100644 index 0000000000..767ca33ec3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkDescriptorSetManager.h @@ -0,0 +1,97 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkDescriptorSetManager_DEFINED +#define GrVkDescriptorSetManager_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/SkTArray.h" +#include "src/gpu/GrResourceHandle.h" +#include "src/gpu/vk/GrVkDescriptorPool.h" +#include "src/gpu/vk/GrVkSampler.h" + +class GrVkDescriptorSet; +class GrVkGpu; +class GrVkUniformHandler; + +/** + * This class handles the allocation of descriptor sets for a given VkDescriptorSetLayout. It will + * try to reuse previously allocated descriptor sets if they are no longer in use by other objects. + */ +class GrVkDescriptorSetManager { +public: + GR_DEFINE_RESOURCE_HANDLE_CLASS(Handle); + + static GrVkDescriptorSetManager* CreateUniformManager(GrVkGpu* gpu); + static GrVkDescriptorSetManager* CreateSamplerManager(GrVkGpu* gpu, VkDescriptorType type, + const GrVkUniformHandler&); + static GrVkDescriptorSetManager* CreateSamplerManager(GrVkGpu* gpu, VkDescriptorType type, + const SkTArray& visibilities); + + ~GrVkDescriptorSetManager() {} + + void abandon(); + void release(GrVkGpu* gpu); + + VkDescriptorSetLayout layout() const { return fPoolManager.fDescLayout; } + + const GrVkDescriptorSet* getDescriptorSet(GrVkGpu* gpu, const Handle& handle); + + void recycleDescriptorSet(const GrVkDescriptorSet*); + + bool isCompatible(VkDescriptorType type, const GrVkUniformHandler*) const; + bool isCompatible(VkDescriptorType type, + const SkTArray& visibilities) const; + +private: + struct DescriptorPoolManager { + DescriptorPoolManager(VkDescriptorType type, GrVkGpu* gpu, + const SkTArray& visibilities, + const SkTArray& immutableSamplers); + + + ~DescriptorPoolManager() { + SkASSERT(!fDescLayout); + SkASSERT(!fPool); + } + + void getNewDescriptorSet(GrVkGpu* gpu, VkDescriptorSet* ds); + + void freeGPUResources(GrVkGpu* gpu); + void abandonGPUResources(); + + VkDescriptorSetLayout fDescLayout; + VkDescriptorType fDescType; + uint32_t fDescCountPerSet; + uint32_t fMaxDescriptors; + uint32_t fCurrentDescriptorCount; + GrVkDescriptorPool* fPool; + + private: + enum { + kUniformDescPerSet = 1, + kMaxDescriptors = 1024, + kStartNumDescriptors = 16, // must be less than kMaxUniformDescriptors + }; + + void getNewPool(GrVkGpu* gpu); + }; + + GrVkDescriptorSetManager(GrVkGpu* gpu, + VkDescriptorType, + const SkTArray& visibilities, + const SkTArray& immutableSamplers); + + + DescriptorPoolManager fPoolManager; + SkTArray fFreeSets; + SkSTArray<4, uint32_t> fBindingVisibilities; + SkSTArray<4, const GrVkSampler*> fImmutableSamplers; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkExtensions.cpp b/gfx/skia/skia/src/gpu/vk/GrVkExtensions.cpp new file mode 100644 index 0000000000..b9990e67b3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkExtensions.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/vk/GrVkExtensions.h" + +// Can remove this once we get rid of the extension flags. +#include "include/gpu/vk/GrVkBackendContext.h" + +#include "src/core/SkTSearch.h" +#include "src/core/SkTSort.h" + +// finds the index of ext in infos or a negative result if ext is not found. +static int find_info(const SkTArray& infos, const char ext[]) { + if (infos.empty()) { + return -1; + } + SkString extensionStr(ext); + GrVkExtensions::Info::Less less; + int idx = SkTSearch( + &infos.front(), infos.count(), extensionStr, sizeof(GrVkExtensions::Info), + less); + return idx; +} + +namespace { // This cannot be static because it is used as a template parameter. +inline bool extension_compare(const GrVkExtensions::Info& a, const GrVkExtensions::Info& b) { + return strcmp(a.fName.c_str(), b.fName.c_str()) < 0; +} +} + +void GrVkExtensions::init(GrVkGetProc getProc, + VkInstance instance, + VkPhysicalDevice physDev, + uint32_t instanceExtensionCount, + const char* const* instanceExtensions, + uint32_t deviceExtensionCount, + const char* const* deviceExtensions) { + SkTLessFunctionToFunctorAdaptor cmp; + + for (uint32_t i = 0; i < instanceExtensionCount; ++i) { + const char* extension = instanceExtensions[i]; + // if not already in the list, add it + if (find_info(fExtensions, extension) < 0) { + fExtensions.push_back() = Info(extension); + SkTQSort(&fExtensions.front(), &fExtensions.back(), cmp); + } + } + for (uint32_t i = 0; i < deviceExtensionCount; ++i) { + const char* extension = deviceExtensions[i]; + // if not already in the list, add it + if (find_info(fExtensions, extension) < 0) { + fExtensions.push_back() = Info(extension); + SkTQSort(&fExtensions.front(), &fExtensions.back(), cmp); + } + } + this->getSpecVersions(getProc, instance, physDev); +} + +#define GET_PROC(F, inst) \ + PFN_vk##F grVk##F = (PFN_vk ## F) getProc("vk" #F, inst, VK_NULL_HANDLE) + +void GrVkExtensions::getSpecVersions(GrVkGetProc getProc, VkInstance instance, + VkPhysicalDevice physDevice) { + // We grab all the extensions for the VkInstance and VkDevice so we can look up what spec + // version each of the supported extensions are. We do not grab the extensions for layers + // because we don't know what layers the client has enabled and in general we don't do anything + // special for those extensions. + + if (instance == VK_NULL_HANDLE) { + return; + } + GET_PROC(EnumerateInstanceExtensionProperties, VK_NULL_HANDLE); + SkASSERT(grVkEnumerateInstanceExtensionProperties); + + VkResult res; + // instance extensions + uint32_t extensionCount = 0; + res = grVkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + if (VK_SUCCESS != res) { + return; + } + VkExtensionProperties* extensions = new VkExtensionProperties[extensionCount]; + res = grVkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions); + if (VK_SUCCESS != res) { + delete[] extensions; + return; + } + for (uint32_t i = 0; i < extensionCount; ++i) { + int idx = find_info(fExtensions, extensions[i].extensionName); + if (idx >= 0) { + fExtensions[idx].fSpecVersion = extensions[i].specVersion; + } + } + delete[] extensions; + + if (physDevice == VK_NULL_HANDLE) { + return; + } + GET_PROC(EnumerateDeviceExtensionProperties, instance); + SkASSERT(grVkEnumerateDeviceExtensionProperties); + + // device extensions + extensionCount = 0; + res = grVkEnumerateDeviceExtensionProperties(physDevice, nullptr, &extensionCount, nullptr); + if (VK_SUCCESS != res) { + return; + } + extensions = new VkExtensionProperties[extensionCount]; + res = grVkEnumerateDeviceExtensionProperties(physDevice, nullptr, &extensionCount, extensions); + if (VK_SUCCESS != res) { + delete[] extensions; + return; + } + for (uint32_t i = 0; i < extensionCount; ++i) { + int idx = find_info(fExtensions, extensions[i].extensionName); + if (idx >= 0) { + fExtensions[idx].fSpecVersion = extensions[i].specVersion; + } + } + delete[] extensions; +} + +bool GrVkExtensions::hasExtension(const char ext[], uint32_t minVersion) const { + int idx = find_info(fExtensions, ext); + return idx >= 0 && fExtensions[idx].fSpecVersion >= minVersion; +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.cpp new file mode 100644 index 0000000000..a06d792d94 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.cpp @@ -0,0 +1,57 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkFramebuffer.h" + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkRenderPass.h" + +GrVkFramebuffer* GrVkFramebuffer::Create(GrVkGpu* gpu, + int width, int height, + const GrVkRenderPass* renderPass, + const GrVkImageView* colorAttachment, + const GrVkImageView* stencilAttachment) { + // At the very least we need a renderPass and a colorAttachment + SkASSERT(renderPass); + SkASSERT(colorAttachment); + + VkImageView attachments[3]; + attachments[0] = colorAttachment->imageView(); + int numAttachments = 1; + if (stencilAttachment) { + attachments[numAttachments++] = stencilAttachment->imageView(); + } + + VkFramebufferCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkFramebufferCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.renderPass = renderPass->vkRenderPass(); + createInfo.attachmentCount = numAttachments; + createInfo.pAttachments = attachments; + createInfo.width = width; + createInfo.height = height; + createInfo.layers = 1; + + VkFramebuffer framebuffer; + VkResult err = GR_VK_CALL(gpu->vkInterface(), CreateFramebuffer(gpu->device(), + &createInfo, + nullptr, + &framebuffer)); + if (err) { + return nullptr; + } + + return new GrVkFramebuffer(framebuffer); +} + +void GrVkFramebuffer::freeGPUData(GrVkGpu* gpu) const { + SkASSERT(fFramebuffer); + GR_VK_CALL(gpu->vkInterface(), DestroyFramebuffer(gpu->device(), fFramebuffer, nullptr)); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.h new file mode 100644 index 0000000000..1a97d5762a --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkFramebuffer.h @@ -0,0 +1,48 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkFramebuffer_DEFINED +#define GrVkFramebuffer_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkGpu; +class GrVkImageView; +class GrVkRenderPass; + +class GrVkFramebuffer : public GrVkResource { +public: + static GrVkFramebuffer* Create(GrVkGpu* gpu, + int width, int height, + const GrVkRenderPass* renderPass, + const GrVkImageView* colorAttachment, + const GrVkImageView* stencilAttachment); + + VkFramebuffer framebuffer() const { return fFramebuffer; } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkFramebuffer: %d (%d refs)\n", fFramebuffer, this->getRefCnt()); + } +#endif + +private: + GrVkFramebuffer(VkFramebuffer framebuffer) : INHERITED(), fFramebuffer(framebuffer) {} + + GrVkFramebuffer(const GrVkFramebuffer&); + GrVkFramebuffer& operator=(const GrVkFramebuffer&); + + void freeGPUData(GrVkGpu* gpu) const override; + + VkFramebuffer fFramebuffer; + + typedef GrVkResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkGpu.cpp b/gfx/skia/skia/src/gpu/vk/GrVkGpu.cpp new file mode 100644 index 0000000000..d9baea4d25 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkGpu.cpp @@ -0,0 +1,2558 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkGpu.h" + +#include "include/gpu/GrBackendSemaphore.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContextOptions.h" +#include "include/private/SkTo.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrGpuResourceCacheAccess.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrNativeRect.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/SkGpuDevice.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/vk/GrVkAMDMemoryAllocator.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkCommandPool.h" +#include "src/gpu/vk/GrVkImage.h" +#include "src/gpu/vk/GrVkIndexBuffer.h" +#include "src/gpu/vk/GrVkInterface.h" +#include "src/gpu/vk/GrVkMemory.h" +#include "src/gpu/vk/GrVkOpsRenderPass.h" +#include "src/gpu/vk/GrVkPipeline.h" +#include "src/gpu/vk/GrVkPipelineState.h" +#include "src/gpu/vk/GrVkRenderPass.h" +#include "src/gpu/vk/GrVkResourceProvider.h" +#include "src/gpu/vk/GrVkSemaphore.h" +#include "src/gpu/vk/GrVkTexture.h" +#include "src/gpu/vk/GrVkTextureRenderTarget.h" +#include "src/gpu/vk/GrVkTransferBuffer.h" +#include "src/gpu/vk/GrVkVertexBuffer.h" +#include "src/image/SkImage_Gpu.h" +#include "src/image/SkSurface_Gpu.h" +#include "src/sksl/SkSLCompiler.h" + +#include "include/gpu/vk/GrVkExtensions.h" +#include "include/gpu/vk/GrVkTypes.h" + +#include + +#if !defined(SK_BUILD_FOR_WIN) +#include +#endif // !defined(SK_BUILD_FOR_WIN) + +#if defined(SK_BUILD_FOR_WIN) && defined(SK_DEBUG) +#include "src/core/SkLeanWindows.h" +#endif + +#define VK_CALL(X) GR_VK_CALL(this->vkInterface(), X) +#define VK_CALL_RET(RET, X) GR_VK_CALL_RET(this->vkInterface(), RET, X) +#define VK_CALL_ERRCHECK(X) GR_VK_CALL_ERRCHECK(this->vkInterface(), X) + +sk_sp GrVkGpu::Make(const GrVkBackendContext& backendContext, + const GrContextOptions& options, GrContext* context) { + if (backendContext.fInstance == VK_NULL_HANDLE || + backendContext.fPhysicalDevice == VK_NULL_HANDLE || + backendContext.fDevice == VK_NULL_HANDLE || + backendContext.fQueue == VK_NULL_HANDLE) { + return nullptr; + } + if (!backendContext.fGetProc) { + return nullptr; + } + + PFN_vkEnumerateInstanceVersion localEnumerateInstanceVersion = + reinterpret_cast( + backendContext.fGetProc("vkEnumerateInstanceVersion", + VK_NULL_HANDLE, VK_NULL_HANDLE)); + uint32_t instanceVersion = 0; + if (!localEnumerateInstanceVersion) { + instanceVersion = VK_MAKE_VERSION(1, 0, 0); + } else { + VkResult err = localEnumerateInstanceVersion(&instanceVersion); + if (err) { + SkDebugf("Failed to enumerate instance version. Err: %d\n", err); + return nullptr; + } + } + + PFN_vkGetPhysicalDeviceProperties localGetPhysicalDeviceProperties = + reinterpret_cast( + backendContext.fGetProc("vkGetPhysicalDeviceProperties", + backendContext.fInstance, + VK_NULL_HANDLE)); + + if (!localGetPhysicalDeviceProperties) { + return nullptr; + } + VkPhysicalDeviceProperties physDeviceProperties; + localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &physDeviceProperties); + uint32_t physDevVersion = physDeviceProperties.apiVersion; + + uint32_t apiVersion = backendContext.fMaxAPIVersion ? backendContext.fMaxAPIVersion + : instanceVersion; + + instanceVersion = SkTMin(instanceVersion, apiVersion); + physDevVersion = SkTMin(physDevVersion, apiVersion); + + sk_sp interface; + + if (backendContext.fVkExtensions) { + interface.reset(new GrVkInterface(backendContext.fGetProc, + backendContext.fInstance, + backendContext.fDevice, + instanceVersion, + physDevVersion, + backendContext.fVkExtensions)); + if (!interface->validate(instanceVersion, physDevVersion, backendContext.fVkExtensions)) { + return nullptr; + } + } else { + GrVkExtensions extensions; + // The only extension flag that may effect the vulkan backend is the swapchain extension. We + // need to know if this is enabled to know if we can transition to a present layout when + // flushing a surface. + if (backendContext.fExtensions & kKHR_swapchain_GrVkExtensionFlag) { + const char* swapChainExtName = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + extensions.init(backendContext.fGetProc, backendContext.fInstance, + backendContext.fPhysicalDevice, 0, nullptr, 1, &swapChainExtName); + } + interface.reset(new GrVkInterface(backendContext.fGetProc, + backendContext.fInstance, + backendContext.fDevice, + instanceVersion, + physDevVersion, + &extensions)); + if (!interface->validate(instanceVersion, physDevVersion, &extensions)) { + return nullptr; + } + } + + sk_sp vkGpu(new GrVkGpu(context, options, backendContext, interface, + instanceVersion, physDevVersion)); + if (backendContext.fProtectedContext == GrProtected::kYes && + !vkGpu->vkCaps().supportsProtectedMemory()) { + return nullptr; + } + return vkGpu; +} + +//////////////////////////////////////////////////////////////////////////////// + +GrVkGpu::GrVkGpu(GrContext* context, const GrContextOptions& options, + const GrVkBackendContext& backendContext, sk_sp interface, + uint32_t instanceVersion, uint32_t physicalDeviceVersion) + : INHERITED(context) + , fInterface(std::move(interface)) + , fMemoryAllocator(backendContext.fMemoryAllocator) + , fInstance(backendContext.fInstance) + , fPhysicalDevice(backendContext.fPhysicalDevice) + , fDevice(backendContext.fDevice) + , fQueue(backendContext.fQueue) + , fQueueIndex(backendContext.fGraphicsQueueIndex) + , fResourceProvider(this) + , fDisconnected(false) + , fProtectedContext(backendContext.fProtectedContext) { + SkASSERT(!backendContext.fOwnsInstanceAndDevice); + + if (!fMemoryAllocator) { + // We were not given a memory allocator at creation + fMemoryAllocator.reset(new GrVkAMDMemoryAllocator(backendContext.fPhysicalDevice, + fDevice, fInterface)); + } + + fCompiler = new SkSL::Compiler(); + + if (backendContext.fDeviceFeatures2) { + fVkCaps.reset(new GrVkCaps(options, this->vkInterface(), backendContext.fPhysicalDevice, + *backendContext.fDeviceFeatures2, instanceVersion, + physicalDeviceVersion, + *backendContext.fVkExtensions, fProtectedContext)); + } else if (backendContext.fDeviceFeatures) { + VkPhysicalDeviceFeatures2 features2; + features2.pNext = nullptr; + features2.features = *backendContext.fDeviceFeatures; + fVkCaps.reset(new GrVkCaps(options, this->vkInterface(), backendContext.fPhysicalDevice, + features2, instanceVersion, physicalDeviceVersion, + *backendContext.fVkExtensions, fProtectedContext)); + } else { + VkPhysicalDeviceFeatures2 features; + memset(&features, 0, sizeof(VkPhysicalDeviceFeatures2)); + features.pNext = nullptr; + if (backendContext.fFeatures & kGeometryShader_GrVkFeatureFlag) { + features.features.geometryShader = true; + } + if (backendContext.fFeatures & kDualSrcBlend_GrVkFeatureFlag) { + features.features.dualSrcBlend = true; + } + if (backendContext.fFeatures & kSampleRateShading_GrVkFeatureFlag) { + features.features.sampleRateShading = true; + } + GrVkExtensions extensions; + // The only extension flag that may effect the vulkan backend is the swapchain extension. We + // need to know if this is enabled to know if we can transition to a present layout when + // flushing a surface. + if (backendContext.fExtensions & kKHR_swapchain_GrVkExtensionFlag) { + const char* swapChainExtName = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + extensions.init(backendContext.fGetProc, backendContext.fInstance, + backendContext.fPhysicalDevice, 0, nullptr, 1, &swapChainExtName); + } + fVkCaps.reset(new GrVkCaps(options, this->vkInterface(), backendContext.fPhysicalDevice, + features, instanceVersion, physicalDeviceVersion, extensions, + fProtectedContext)); + } + fCaps.reset(SkRef(fVkCaps.get())); + + VK_CALL(GetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &fPhysDevProps)); + VK_CALL(GetPhysicalDeviceMemoryProperties(backendContext.fPhysicalDevice, &fPhysDevMemProps)); + + fResourceProvider.init(); + + fCmdPool = fResourceProvider.findOrCreateCommandPool(); + fCurrentCmdBuffer = fCmdPool->getPrimaryCommandBuffer(); + SkASSERT(fCurrentCmdBuffer); + fCurrentCmdBuffer->begin(this); +} + +void GrVkGpu::destroyResources() { + if (fCmdPool) { + fCmdPool->getPrimaryCommandBuffer()->end(this); + fCmdPool->close(); + } + + // wait for all commands to finish + VkResult res = VK_CALL(QueueWaitIdle(fQueue)); + + // On windows, sometimes calls to QueueWaitIdle return before actually signalling the fences + // on the command buffers even though they have completed. This causes an assert to fire when + // destroying the command buffers. Currently this ony seems to happen on windows, so we add a + // sleep to make sure the fence signals. +#ifdef SK_DEBUG + if (this->vkCaps().mustSleepOnTearDown()) { +#if defined(SK_BUILD_FOR_WIN) + Sleep(10); // In milliseconds +#else + sleep(1); // In seconds +#endif + } +#endif + +#ifdef SK_DEBUG + SkASSERT(VK_SUCCESS == res || VK_ERROR_DEVICE_LOST == res); +#endif + + if (fCmdPool) { + fCmdPool->unref(this); + fCmdPool = nullptr; + } + + for (int i = 0; i < fSemaphoresToWaitOn.count(); ++i) { + fSemaphoresToWaitOn[i]->unref(this); + } + fSemaphoresToWaitOn.reset(); + + for (int i = 0; i < fSemaphoresToSignal.count(); ++i) { + fSemaphoresToSignal[i]->unref(this); + } + fSemaphoresToSignal.reset(); + + // must call this just before we destroy the command pool and VkDevice + fResourceProvider.destroyResources(VK_ERROR_DEVICE_LOST == res); + + fMemoryAllocator.reset(); + + fQueue = VK_NULL_HANDLE; + fDevice = VK_NULL_HANDLE; + fInstance = VK_NULL_HANDLE; +} + +GrVkGpu::~GrVkGpu() { + if (!fDisconnected) { + this->destroyResources(); + } + delete fCompiler; +} + + +void GrVkGpu::disconnect(DisconnectType type) { + INHERITED::disconnect(type); + if (!fDisconnected) { + if (DisconnectType::kCleanup == type) { + this->destroyResources(); + } else { + if (fCmdPool) { + fCmdPool->unrefAndAbandon(); + fCmdPool = nullptr; + } + for (int i = 0; i < fSemaphoresToWaitOn.count(); ++i) { + fSemaphoresToWaitOn[i]->unrefAndAbandon(); + } + for (int i = 0; i < fSemaphoresToSignal.count(); ++i) { + fSemaphoresToSignal[i]->unrefAndAbandon(); + } + + // must call this just before we destroy the command pool and VkDevice + fResourceProvider.abandonResources(); + + fMemoryAllocator.reset(); + } + fSemaphoresToWaitOn.reset(); + fSemaphoresToSignal.reset(); + fCurrentCmdBuffer = nullptr; + fDisconnected = true; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +GrOpsRenderPass* GrVkGpu::getOpsRenderPass( + GrRenderTarget* rt, GrSurfaceOrigin origin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, + const SkTArray& sampledProxies) { + if (!fCachedOpsRenderPass) { + fCachedOpsRenderPass.reset(new GrVkOpsRenderPass(this)); + } + + fCachedOpsRenderPass->set(rt, origin, bounds, colorInfo, stencilInfo, sampledProxies); + return fCachedOpsRenderPass.get(); +} + +void GrVkGpu::submitCommandBuffer(SyncQueue sync, GrGpuFinishedProc finishedProc, + GrGpuFinishedContext finishedContext) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(fCurrentCmdBuffer); + SkASSERT(!fCachedOpsRenderPass || !fCachedOpsRenderPass->isActive()); + + if (!fCurrentCmdBuffer->hasWork() && kForce_SyncQueue != sync && + !fSemaphoresToSignal.count() && !fSemaphoresToWaitOn.count()) { + SkASSERT(fDrawables.empty()); + fResourceProvider.checkCommandBuffers(); + if (finishedProc) { + fResourceProvider.addFinishedProcToActiveCommandBuffers(finishedProc, finishedContext); + } + return; + } + + fCurrentCmdBuffer->end(this); + fCmdPool->close(); + fCurrentCmdBuffer->submitToQueue(this, fQueue, sync, fSemaphoresToSignal, fSemaphoresToWaitOn); + + if (finishedProc) { + // Make sure this is called after closing the current command pool + fResourceProvider.addFinishedProcToActiveCommandBuffers(finishedProc, finishedContext); + } + + // We must delete and drawables that have been waitint till submit for us to destroy. + fDrawables.reset(); + + for (int i = 0; i < fSemaphoresToWaitOn.count(); ++i) { + fSemaphoresToWaitOn[i]->unref(this); + } + fSemaphoresToWaitOn.reset(); + for (int i = 0; i < fSemaphoresToSignal.count(); ++i) { + fSemaphoresToSignal[i]->unref(this); + } + fSemaphoresToSignal.reset(); + + // Release old command pool and create a new one + fCmdPool->unref(this); + fResourceProvider.checkCommandBuffers(); + fCmdPool = fResourceProvider.findOrCreateCommandPool(); + fCurrentCmdBuffer = fCmdPool->getPrimaryCommandBuffer(); + fCurrentCmdBuffer->begin(this); +} + +/////////////////////////////////////////////////////////////////////////////// +sk_sp GrVkGpu::onCreateBuffer(size_t size, GrGpuBufferType type, + GrAccessPattern accessPattern, const void* data) { + sk_sp buff; + switch (type) { + case GrGpuBufferType::kVertex: + SkASSERT(kDynamic_GrAccessPattern == accessPattern || + kStatic_GrAccessPattern == accessPattern); + buff = GrVkVertexBuffer::Make(this, size, kDynamic_GrAccessPattern == accessPattern); + break; + case GrGpuBufferType::kIndex: + SkASSERT(kDynamic_GrAccessPattern == accessPattern || + kStatic_GrAccessPattern == accessPattern); + buff = GrVkIndexBuffer::Make(this, size, kDynamic_GrAccessPattern == accessPattern); + break; + case GrGpuBufferType::kXferCpuToGpu: + SkASSERT(kDynamic_GrAccessPattern == accessPattern || + kStream_GrAccessPattern == accessPattern); + buff = GrVkTransferBuffer::Make(this, size, GrVkBuffer::kCopyRead_Type); + break; + case GrGpuBufferType::kXferGpuToCpu: + SkASSERT(kDynamic_GrAccessPattern == accessPattern || + kStream_GrAccessPattern == accessPattern); + buff = GrVkTransferBuffer::Make(this, size, GrVkBuffer::kCopyWrite_Type); + break; + default: + SK_ABORT("Unknown buffer type."); + } + if (data && buff) { + buff->updateData(data, size); + } + return buff; +} + +bool GrVkGpu::onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) { + GrVkTexture* vkTex = static_cast(surface->asTexture()); + if (!vkTex) { + return false; + } + + // Make sure we have at least the base level + if (!mipLevelCount || !texels[0].fPixels) { + return false; + } + + SkASSERT(!GrVkFormatIsCompressed(vkTex->imageFormat())); + bool success = false; + bool linearTiling = vkTex->isLinearTiled(); + if (linearTiling) { + if (mipLevelCount > 1) { + SkDebugf("Can't upload mipmap data to linear tiled texture"); + return false; + } + if (VK_IMAGE_LAYOUT_PREINITIALIZED != vkTex->currentLayout()) { + // Need to change the layout to general in order to perform a host write + vkTex->setImageLayout(this, + VK_IMAGE_LAYOUT_GENERAL, + VK_ACCESS_HOST_WRITE_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + false); + this->submitCommandBuffer(kForce_SyncQueue); + } + success = this->uploadTexDataLinear(vkTex, left, top, width, height, srcColorType, + texels[0].fPixels, texels[0].fRowBytes); + } else { + SkASSERT(mipLevelCount <= vkTex->texturePriv().maxMipMapLevel() + 1); + success = this->uploadTexDataOptimal(vkTex, left, top, width, height, srcColorType, texels, + mipLevelCount); + } + + if (prepForTexSampling) { + vkTex->setImageLayout(this, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + false); + } + + return success; +} + +bool GrVkGpu::onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t bufferOffset, + size_t rowBytes) { + // Vulkan only supports offsets that are both 4-byte aligned and aligned to a pixel. + if ((bufferOffset & 0x3) || (bufferOffset % GrColorTypeBytesPerPixel(bufferColorType))) { + return false; + } + GrVkTexture* vkTex = static_cast(texture); + if (!vkTex) { + return false; + } + + // Can't transfer compressed data + SkASSERT(!GrVkFormatIsCompressed(vkTex->imageFormat())); + + GrVkTransferBuffer* vkBuffer = static_cast(transferBuffer); + if (!vkBuffer) { + return false; + } + + SkDEBUGCODE( + SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); + SkIRect bounds = SkIRect::MakeWH(texture->width(), texture->height()); + SkASSERT(bounds.contains(subRect)); + ) + size_t bpp = GrColorTypeBytesPerPixel(bufferColorType); + + // Set up copy region + VkBufferImageCopy region; + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = bufferOffset; + region.bufferRowLength = (uint32_t)(rowBytes/bpp); + region.bufferImageHeight = 0; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.imageOffset = { left, top, 0 }; + region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 }; + + // Change layout of our target so it can be copied to + vkTex->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + // Copy the buffer to the image + fCurrentCmdBuffer->copyBufferToImage(this, + vkBuffer, + vkTex, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion); + + vkTex->texturePriv().markMipMapsDirty(); + return true; +} + +bool GrVkGpu::onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) { + SkASSERT(surface); + SkASSERT(transferBuffer); + if (fProtectedContext == GrProtected::kYes) { + return false; + } + + GrVkTransferBuffer* vkBuffer = static_cast(transferBuffer); + + GrVkImage* srcImage; + if (GrVkRenderTarget* rt = static_cast(surface->asRenderTarget())) { + // Reading from render targets that wrap a secondary command buffer is not allowed since + // it would require us to know the VkImage, which we don't have, as well as need us to + // stop and start the VkRenderPass which we don't have access to. + if (rt->wrapsSecondaryCommandBuffer()) { + return false; + } + srcImage = rt; + } else { + srcImage = static_cast(surface->asTexture()); + } + + // Set up copy region + VkBufferImageCopy region; + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = offset; + region.bufferRowLength = width; + region.bufferImageHeight = 0; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.imageOffset = { left, top, 0 }; + region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 }; + + srcImage->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + fCurrentCmdBuffer->copyImageToBuffer(this, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vkBuffer, 1, ®ion); + + // Make sure the copy to buffer has finished. + vkBuffer->addMemoryBarrier(this, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_HOST_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + false); + return true; +} + +void GrVkGpu::resolveImage(GrSurface* dst, GrVkRenderTarget* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + SkASSERT(dst); + SkASSERT(src && src->numSamples() > 1 && src->msaaImage()); + + VkImageResolve resolveInfo; + resolveInfo.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + resolveInfo.srcOffset = {srcRect.fLeft, srcRect.fTop, 0}; + resolveInfo.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + resolveInfo.dstOffset = {dstPoint.fX, dstPoint.fY, 0}; + resolveInfo.extent = {(uint32_t)srcRect.width(), (uint32_t)srcRect.height(), 1}; + + GrVkImage* dstImage; + GrRenderTarget* dstRT = dst->asRenderTarget(); + if (dstRT) { + GrVkRenderTarget* vkRT = static_cast(dstRT); + dstImage = vkRT; + } else { + SkASSERT(dst->asTexture()); + dstImage = static_cast(dst->asTexture()); + } + dstImage->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + src->msaaImage()->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + fCurrentCmdBuffer->resolveImage(this, *src->msaaImage(), *dstImage, 1, &resolveInfo); +} + +void GrVkGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin resolveOrigin, ForExternalIO forExternalIO) { + SkASSERT(target->numSamples() > 1); + GrVkRenderTarget* rt = static_cast(target); + SkASSERT(rt->msaaImage()); + + auto nativeResolveRect = GrNativeRect::MakeRelativeTo( + resolveOrigin, target->height(), resolveRect); + this->resolveImage(target, rt, nativeResolveRect.asSkIRect(), + SkIPoint::Make(nativeResolveRect.fX, nativeResolveRect.fY)); + + if (ForExternalIO::kYes == forExternalIO) { + // This resolve is called when we are preparing an msaa surface for external I/O. It is + // called after flushing, so we need to make sure we submit the command buffer after doing + // the resolve so that the resolve actually happens. + this->submitCommandBuffer(kSkip_SyncQueue); + } +} + +bool GrVkGpu::uploadTexDataLinear(GrVkTexture* tex, int left, int top, int width, int height, + GrColorType dataColorType, const void* data, size_t rowBytes) { + SkASSERT(data); + SkASSERT(tex->isLinearTiled()); + + SkDEBUGCODE( + SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); + SkIRect bounds = SkIRect::MakeWH(tex->width(), tex->height()); + SkASSERT(bounds.contains(subRect)); + ) + size_t bpp = GrColorTypeBytesPerPixel(dataColorType); + size_t trimRowBytes = width * bpp; + + SkASSERT(VK_IMAGE_LAYOUT_PREINITIALIZED == tex->currentLayout() || + VK_IMAGE_LAYOUT_GENERAL == tex->currentLayout()); + const VkImageSubresource subres = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, // mipLevel + 0, // arraySlice + }; + VkSubresourceLayout layout; + + const GrVkInterface* interface = this->vkInterface(); + + GR_VK_CALL(interface, GetImageSubresourceLayout(fDevice, + tex->image(), + &subres, + &layout)); + + const GrVkAlloc& alloc = tex->alloc(); + if (VK_NULL_HANDLE == alloc.fMemory) { + return false; + } + VkDeviceSize offset = top * layout.rowPitch + left * bpp; + VkDeviceSize size = height*layout.rowPitch; + SkASSERT(size + offset <= alloc.fSize); + void* mapPtr = GrVkMemory::MapAlloc(this, alloc); + if (!mapPtr) { + return false; + } + mapPtr = reinterpret_cast(mapPtr) + offset; + + SkRectMemcpy(mapPtr, static_cast(layout.rowPitch), data, rowBytes, trimRowBytes, + height); + + GrVkMemory::FlushMappedAlloc(this, alloc, offset, size); + GrVkMemory::UnmapAlloc(this, alloc); + + return true; +} + +bool GrVkGpu::uploadTexDataOptimal(GrVkTexture* tex, int left, int top, int width, int height, + GrColorType dataColorType, const GrMipLevel texels[], + int mipLevelCount) { + SkASSERT(!tex->isLinearTiled()); + // The assumption is either that we have no mipmaps, or that our rect is the entire texture + SkASSERT(1 == mipLevelCount || + (0 == left && 0 == top && width == tex->width() && height == tex->height())); + + // We assume that if the texture has mip levels, we either upload to all the levels or just the + // first. + SkASSERT(1 == mipLevelCount || mipLevelCount == (tex->texturePriv().maxMipMapLevel() + 1)); + + if (width == 0 || height == 0) { + return false; + } + + if (GrPixelConfigToColorType(tex->config()) != dataColorType) { + return false; + } + + // For RGB_888x src data we are uploading it first to an RGBA texture and then copying it to the + // dst RGB texture. Thus we do not upload mip levels for that. + if (dataColorType == GrColorType::kRGB_888x && tex->imageFormat() == VK_FORMAT_R8G8B8_UNORM) { + SkASSERT(tex->config() == kRGB_888_GrPixelConfig); + // First check that we'll be able to do the copy to the to the R8G8B8 image in the end via a + // blit or draw. + if (!this->vkCaps().formatCanBeDstofBlit(VK_FORMAT_R8G8B8_UNORM, tex->isLinearTiled()) && + !this->vkCaps().isFormatRenderable(VK_FORMAT_R8G8B8_UNORM, 1)) { + return false; + } + mipLevelCount = 1; + } + + SkASSERT(this->vkCaps().isVkFormatTexturable(tex->imageFormat())); + size_t bpp = GrColorTypeBytesPerPixel(dataColorType); + + // texels is const. + // But we may need to adjust the fPixels ptr based on the copyRect, or fRowBytes. + // Because of this we need to make a non-const shallow copy of texels. + SkAutoTMalloc texelsShallowCopy; + + texelsShallowCopy.reset(mipLevelCount); + memcpy(texelsShallowCopy.get(), texels, mipLevelCount*sizeof(GrMipLevel)); + + SkTArray individualMipOffsets(mipLevelCount); + individualMipOffsets.push_back(0); + size_t combinedBufferSize = width * bpp * height; + int currentWidth = width; + int currentHeight = height; + if (!texelsShallowCopy[0].fPixels) { + combinedBufferSize = 0; + } + + // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image + // config. This works with the assumption that the bytes in pixel config is always a power of 2. + SkASSERT((bpp & (bpp - 1)) == 0); + const size_t alignmentMask = 0x3 | (bpp - 1); + for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; currentMipLevel++) { + currentWidth = SkTMax(1, currentWidth/2); + currentHeight = SkTMax(1, currentHeight/2); + + if (texelsShallowCopy[currentMipLevel].fPixels) { + const size_t trimmedSize = currentWidth * bpp * currentHeight; + const size_t alignmentDiff = combinedBufferSize & alignmentMask; + if (alignmentDiff != 0) { + combinedBufferSize += alignmentMask - alignmentDiff + 1; + } + individualMipOffsets.push_back(combinedBufferSize); + combinedBufferSize += trimmedSize; + } else { + individualMipOffsets.push_back(0); + } + } + if (0 == combinedBufferSize) { + // We don't actually have any data to upload so just return success + return true; + } + + // allocate buffer to hold our mip data + sk_sp transferBuffer = + GrVkTransferBuffer::Make(this, combinedBufferSize, GrVkBuffer::kCopyRead_Type); + if (!transferBuffer) { + return false; + } + + int uploadLeft = left; + int uploadTop = top; + GrVkTexture* uploadTexture = tex; + // For uploading RGB_888x data to an R8G8B8_UNORM texture we must first upload the data to an + // R8G8B8A8_UNORM image and then copy it. + sk_sp copyTexture; + if (dataColorType == GrColorType::kRGB_888x && tex->imageFormat() == VK_FORMAT_R8G8B8_UNORM) { + bool dstHasYcbcr = tex->ycbcrConversionInfo().isValid(); + if (!this->vkCaps().canCopyAsBlit(tex->imageFormat(), 1, false, dstHasYcbcr, + VK_FORMAT_R8G8B8A8_UNORM, 1, false, false)) { + return false; + } + GrSurfaceDesc surfDesc; + surfDesc.fWidth = width; + surfDesc.fHeight = height; + surfDesc.fConfig = kRGBA_8888_GrPixelConfig; + + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = VK_FORMAT_R8G8B8A8_UNORM; + imageDesc.fWidth = width; + imageDesc.fHeight = height; + imageDesc.fLevels = 1; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fMemProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + copyTexture = GrVkTexture::MakeNewTexture(this, SkBudgeted::kYes, surfDesc, imageDesc, + GrMipMapsStatus::kNotAllocated); + if (!copyTexture) { + return false; + } + + uploadTexture = copyTexture.get(); + uploadLeft = 0; + uploadTop = 0; + } + + char* buffer = (char*) transferBuffer->map(); + SkTArray regions(mipLevelCount); + + currentWidth = width; + currentHeight = height; + int layerHeight = uploadTexture->height(); + for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + if (texelsShallowCopy[currentMipLevel].fPixels) { + SkASSERT(1 == mipLevelCount || currentHeight == layerHeight); + const size_t trimRowBytes = currentWidth * bpp; + const size_t rowBytes = texelsShallowCopy[currentMipLevel].fRowBytes; + + // copy data into the buffer, skipping the trailing bytes + char* dst = buffer + individualMipOffsets[currentMipLevel]; + const char* src = (const char*)texelsShallowCopy[currentMipLevel].fPixels; + SkRectMemcpy(dst, trimRowBytes, src, rowBytes, trimRowBytes, currentHeight); + + VkBufferImageCopy& region = regions.push_back(); + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = transferBuffer->offset() + individualMipOffsets[currentMipLevel]; + region.bufferRowLength = currentWidth; + region.bufferImageHeight = currentHeight; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, SkToU32(currentMipLevel), 0, 1 }; + region.imageOffset = {uploadLeft, uploadTop, 0}; + region.imageExtent = { (uint32_t)currentWidth, (uint32_t)currentHeight, 1 }; + } + currentWidth = SkTMax(1, currentWidth/2); + currentHeight = SkTMax(1, currentHeight/2); + layerHeight = currentHeight; + } + + // no need to flush non-coherent memory, unmap will do that for us + transferBuffer->unmap(); + + // Change layout of our target so it can be copied to + uploadTexture->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + // Copy the buffer to the image + fCurrentCmdBuffer->copyBufferToImage(this, + transferBuffer.get(), + uploadTexture, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + regions.count(), + regions.begin()); + + // If we copied the data into a temporary image first, copy that image into our main texture + // now. + if (copyTexture.get()) { + SkASSERT(dataColorType == GrColorType::kRGB_888x); + SkAssertResult(this->copySurface(tex, copyTexture.get(), SkIRect::MakeWH(width, height), + SkIPoint::Make(left, top))); + } + if (1 == mipLevelCount) { + tex->texturePriv().markMipMapsDirty(); + } + + return true; +} + +// It's probably possible to roll this into uploadTexDataOptimal, +// but for now it's easier to maintain as a separate entity. +bool GrVkGpu::uploadTexDataCompressed(GrVkTexture* tex, int left, int top, int width, int height, + SkImage::CompressionType compressionType, const void* data) { + SkASSERT(data); + SkASSERT(!tex->isLinearTiled()); + // For now the assumption is that our rect is the entire texture. + // Compressed textures are read-only so this should be a reasonable assumption. + SkASSERT(0 == left && 0 == top && width == tex->width() && height == tex->height()); + + if (width == 0 || height == 0) { + return false; + } + + SkImage::CompressionType textureCompressionType; + if (!GrVkFormatToCompressionType(tex->imageFormat(), &textureCompressionType) || + textureCompressionType != compressionType) { + return false; + } + + SkASSERT(this->vkCaps().isVkFormatTexturable(tex->imageFormat())); + + size_t dataSize = GrCompressedDataSize(compressionType, width, height); + + // allocate buffer to hold our mip data + sk_sp transferBuffer = + GrVkTransferBuffer::Make(this, dataSize, GrVkBuffer::kCopyRead_Type); + if (!transferBuffer) { + return false; + } + + int uploadLeft = left; + int uploadTop = top; + GrVkTexture* uploadTexture = tex; + + char* buffer = (char*)transferBuffer->map(); + + memcpy(buffer, data, dataSize); + + VkBufferImageCopy region; + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = transferBuffer->offset(); + region.bufferRowLength = width; + region.bufferImageHeight = height; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.imageOffset = { uploadLeft, uploadTop, 0 }; + region.imageExtent = { SkToU32(width), SkToU32(height), 1 }; + + // no need to flush non-coherent memory, unmap will do that for us + transferBuffer->unmap(); + + // Change layout of our target so it can be copied to + uploadTexture->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + // Copy the buffer to the image + fCurrentCmdBuffer->copyBufferToImage(this, + transferBuffer.get(), + uploadTexture, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +sk_sp GrVkGpu::onCreateTexture(const GrSurfaceDesc& desc, + const GrBackendFormat& format, + GrRenderable renderable, + int renderTargetSampleCnt, + SkBudgeted budgeted, + GrProtected isProtected, + int mipLevelCount, + uint32_t levelClearMask) { + VkFormat pixelFormat; + SkAssertResult(format.asVkFormat(&pixelFormat)); + SkASSERT(!GrVkFormatIsCompressed(pixelFormat)); + + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; + if (renderable == GrRenderable::kYes) { + usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + // For now we will set the VK_IMAGE_USAGE_TRANSFER_DESTINATION_BIT and + // VK_IMAGE_USAGE_TRANSFER_SOURCE_BIT on every texture since we do not know whether or not we + // will be using this texture in some copy or not. Also this assumes, as is the current case, + // that all render targets in vulkan are also textures. If we change this practice of setting + // both bits, we must make sure to set the destination bit if we are uploading srcData to the + // texture. + usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + // This ImageDesc refers to the texture that will be read by the client. Thus even if msaa is + // requested, this ImageDesc describes the resolved texture. Therefore we always have samples set + // to 1. + SkASSERT(mipLevelCount > 0); + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = pixelFormat; + imageDesc.fWidth = desc.fWidth; + imageDesc.fHeight = desc.fHeight; + imageDesc.fLevels = mipLevelCount; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fIsProtected = isProtected; + + GrMipMapsStatus mipMapsStatus = + mipLevelCount > 1 ? GrMipMapsStatus::kDirty : GrMipMapsStatus::kNotAllocated; + + sk_sp tex; + if (renderable == GrRenderable::kYes) { + tex = GrVkTextureRenderTarget::MakeNewTextureRenderTarget( + this, budgeted, desc, renderTargetSampleCnt, imageDesc, mipMapsStatus); + } else { + tex = GrVkTexture::MakeNewTexture(this, budgeted, desc, imageDesc, mipMapsStatus); + } + + if (!tex) { + return nullptr; + } + + if (levelClearMask) { + SkSTArray<1, VkImageSubresourceRange> ranges; + bool inRange = false; + for (uint32_t i = 0; i < tex->mipLevels(); ++i) { + if (levelClearMask & (1U << i)) { + if (inRange) { + ranges.back().levelCount++; + } else { + auto& range = ranges.push_back(); + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.baseArrayLayer = 0; + range.baseMipLevel = i; + range.layerCount = 1; + range.levelCount = 1; + inRange = true; + } + } else if (inRange) { + inRange = false; + } + } + SkASSERT(!ranges.empty()); + static constexpr VkClearColorValue kZeroClearColor = {}; + tex->setImageLayout(this, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, false); + this->currentCommandBuffer()->clearColorImage(this, tex.get(), &kZeroClearColor, + ranges.count(), ranges.begin()); + } + return tex; +} + +sk_sp GrVkGpu::onCreateCompressedTexture(int width, int height, + const GrBackendFormat& format, + SkImage::CompressionType compressionType, + SkBudgeted budgeted, const void* data) { + VkFormat pixelFormat; + if (!format.asVkFormat(&pixelFormat)) { + return nullptr; + } + + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; + + // For now we will set the VK_IMAGE_USAGE_TRANSFER_DESTINATION_BIT and + // VK_IMAGE_USAGE_TRANSFER_SOURCE_BIT on every texture since we do not know whether or not we + // will be using this texture in some copy or not. Also this assumes, as is the current case, + // that all render targets in vulkan are also textures. If we change this practice of setting + // both bits, we must make sure to set the destination bit if we are uploading srcData to the + // texture. + usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + // Compressed textures with MIP levels or multiple samples are not supported as of now. + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = pixelFormat; + imageDesc.fWidth = width; + imageDesc.fHeight = height; + imageDesc.fLevels = 1; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fIsProtected = GrProtected::kNo; + + GrSurfaceDesc desc; + desc.fConfig = GrCompressionTypePixelConfig(compressionType); + desc.fWidth = width; + desc.fHeight = height; + auto tex = GrVkTexture::MakeNewTexture(this, budgeted, desc, imageDesc, + GrMipMapsStatus::kNotAllocated); + if (!tex) { + return nullptr; + } + + if (!this->uploadTexDataCompressed(tex.get(), 0, 0, desc.fWidth, desc.fHeight, compressionType, + data)) { + return nullptr; + } + + return tex; +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkGpu::copyBuffer(GrVkBuffer* srcBuffer, GrVkBuffer* dstBuffer, VkDeviceSize srcOffset, + VkDeviceSize dstOffset, VkDeviceSize size) { + VkBufferCopy copyRegion; + copyRegion.srcOffset = srcOffset; + copyRegion.dstOffset = dstOffset; + copyRegion.size = size; + fCurrentCmdBuffer->copyBuffer(this, srcBuffer, dstBuffer, 1, ©Region); +} + +bool GrVkGpu::updateBuffer(GrVkBuffer* buffer, const void* src, + VkDeviceSize offset, VkDeviceSize size) { + // Update the buffer + fCurrentCmdBuffer->updateBuffer(this, buffer, offset, size, src); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +static bool check_image_info(const GrVkCaps& caps, + const GrVkImageInfo& info, + GrColorType colorType, + bool needsAllocation) { + if (VK_NULL_HANDLE == info.fImage) { + return false; + } + + if (VK_NULL_HANDLE == info.fAlloc.fMemory && needsAllocation) { + return false; + } + + if (info.fImageLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && !caps.supportsSwapchain()) { + return false; + } + + if (info.fYcbcrConversionInfo.isValid()) { + if (!caps.supportsYcbcrConversion()) { + return false; + } + if (info.fYcbcrConversionInfo.fExternalFormat != 0) { + return true; + } + } + + SkASSERT(GrVkFormatColorTypePairIsValid(info.fFormat, colorType)); + return true; +} + +static bool check_tex_image_info(const GrVkCaps& caps, const GrVkImageInfo& info) { + if (info.fYcbcrConversionInfo.isValid() && info.fYcbcrConversionInfo.fExternalFormat != 0) { + return true; + } + if (info.fImageTiling == VK_IMAGE_TILING_OPTIMAL) { + if (!caps.isVkFormatTexturable(info.fFormat)) { + return false; + } + } else { + SkASSERT(info.fImageTiling == VK_IMAGE_TILING_LINEAR); + if (!caps.isVkFormatTexturableLinearly(info.fFormat)) { + return false; + } + } + return true; +} + +static bool check_rt_image_info(const GrVkCaps& caps, const GrVkImageInfo& info, int sampleCnt) { + if (!caps.isFormatRenderable(info.fFormat, sampleCnt)) { + return false; + } + return true; +} + +sk_sp GrVkGpu::onWrapBackendTexture(const GrBackendTexture& backendTex, + GrColorType colorType, GrWrapOwnership ownership, + GrWrapCacheable cacheable, GrIOType ioType) { + GrVkImageInfo imageInfo; + if (!backendTex.getVkImageInfo(&imageInfo)) { + return nullptr; + } + + if (!check_image_info(this->vkCaps(), imageInfo, colorType, + kAdopt_GrWrapOwnership == ownership)) { + return nullptr; + } + if (!check_tex_image_info(this->vkCaps(), imageInfo)) { + return nullptr; + } + + if (backendTex.isProtected() && (fProtectedContext == GrProtected::kNo)) { + return nullptr; + } + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendTex.getBackendFormat(), + colorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc surfDesc; + surfDesc.fWidth = backendTex.width(); + surfDesc.fHeight = backendTex.height(); + surfDesc.fConfig = config; + + sk_sp layout = backendTex.getGrVkImageLayout(); + SkASSERT(layout); + return GrVkTexture::MakeWrappedTexture(this, surfDesc, ownership, cacheable, ioType, imageInfo, + std::move(layout)); +} + +sk_sp GrVkGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex, + int sampleCnt, + GrColorType colorType, + GrWrapOwnership ownership, + GrWrapCacheable cacheable) { + GrVkImageInfo imageInfo; + if (!backendTex.getVkImageInfo(&imageInfo)) { + return nullptr; + } + + if (!check_image_info(this->vkCaps(), imageInfo, colorType, + kAdopt_GrWrapOwnership == ownership)) { + return nullptr; + } + if (!check_tex_image_info(this->vkCaps(), imageInfo)) { + return nullptr; + } + if (!check_rt_image_info(this->vkCaps(), imageInfo, sampleCnt)) { + return nullptr; + } + + if (backendTex.isProtected() && (fProtectedContext == GrProtected::kNo)) { + return nullptr; + } + + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendTex.getBackendFormat(), + colorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc surfDesc; + surfDesc.fWidth = backendTex.width(); + surfDesc.fHeight = backendTex.height(); + surfDesc.fConfig = config; + sampleCnt = this->vkCaps().getRenderTargetSampleCount(sampleCnt, imageInfo.fFormat); + + sk_sp layout = backendTex.getGrVkImageLayout(); + SkASSERT(layout); + + return GrVkTextureRenderTarget::MakeWrappedTextureRenderTarget( + this, surfDesc, sampleCnt, ownership, cacheable, imageInfo, std::move(layout)); +} + +sk_sp GrVkGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& backendRT, + GrColorType colorType) { + // Currently the Vulkan backend does not support wrapping of msaa render targets directly. In + // general this is not an issue since swapchain images in vulkan are never multisampled. Thus if + // you want a multisampled RT it is best to wrap the swapchain images and then let Skia handle + // creating and owning the MSAA images. + if (backendRT.sampleCnt() > 1) { + return nullptr; + } + + GrVkImageInfo info; + if (!backendRT.getVkImageInfo(&info)) { + return nullptr; + } + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendRT.getBackendFormat(), + colorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + if (!check_image_info(this->vkCaps(), info, colorType, false)) { + return nullptr; + } + if (!check_rt_image_info(this->vkCaps(), info, backendRT.sampleCnt())) { + return nullptr; + } + + if (backendRT.isProtected() && (fProtectedContext == GrProtected::kNo)) { + return nullptr; + } + + GrSurfaceDesc desc; + desc.fWidth = backendRT.width(); + desc.fHeight = backendRT.height(); + desc.fConfig = config; + + sk_sp layout = backendRT.getGrVkImageLayout(); + + sk_sp tgt = + GrVkRenderTarget::MakeWrappedRenderTarget(this, desc, 1, info, std::move(layout)); + + // We don't allow the client to supply a premade stencil buffer. We always create one if needed. + SkASSERT(!backendRT.stencilBits()); + if (tgt) { + SkASSERT(tgt->canAttemptStencilAttachment()); + } + + return tgt; +} + +sk_sp GrVkGpu::onWrapBackendTextureAsRenderTarget(const GrBackendTexture& tex, + int sampleCnt, + GrColorType grColorType) { + + GrVkImageInfo imageInfo; + if (!tex.getVkImageInfo(&imageInfo)) { + return nullptr; + } + if (!check_image_info(this->vkCaps(), imageInfo, grColorType, false)) { + return nullptr; + } + if (!check_rt_image_info(this->vkCaps(), imageInfo, sampleCnt)) { + return nullptr; + } + + if (tex.isProtected() && (fProtectedContext == GrProtected::kNo)) { + return nullptr; + } + + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(tex.getBackendFormat(), + grColorType); + SkASSERT(kUnknown_GrPixelConfig != config); + + GrSurfaceDesc desc; + desc.fWidth = tex.width(); + desc.fHeight = tex.height(); + desc.fConfig = config; + + sampleCnt = this->vkCaps().getRenderTargetSampleCount(sampleCnt, imageInfo.fFormat); + if (!sampleCnt) { + return nullptr; + } + + sk_sp layout = tex.getGrVkImageLayout(); + SkASSERT(layout); + + return GrVkRenderTarget::MakeWrappedRenderTarget(this, desc, sampleCnt, imageInfo, + std::move(layout)); +} + +sk_sp GrVkGpu::onWrapVulkanSecondaryCBAsRenderTarget( + const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) { + int maxSize = this->caps()->maxTextureSize(); + if (imageInfo.width() > maxSize || imageInfo.height() > maxSize) { + return nullptr; + } + + GrBackendFormat backendFormat = GrBackendFormat::MakeVk(vkInfo.fFormat); + if (!backendFormat.isValid()) { + return nullptr; + } + int sampleCnt = this->vkCaps().getRenderTargetSampleCount(1, vkInfo.fFormat); + if (!sampleCnt) { + return nullptr; + } + + GrColorType grColorType = SkColorTypeToGrColorType(imageInfo.colorType()); + GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendFormat, grColorType); + if (config == kUnknown_GrPixelConfig) { + return nullptr; + } + + GrSurfaceDesc desc; + desc.fWidth = imageInfo.width(); + desc.fHeight = imageInfo.height(); + desc.fConfig = config; + + return GrVkRenderTarget::MakeSecondaryCBRenderTarget(this, desc, vkInfo); +} + +bool GrVkGpu::onRegenerateMipMapLevels(GrTexture* tex) { + auto* vkTex = static_cast(tex); + // don't do anything for linearly tiled textures (can't have mipmaps) + if (vkTex->isLinearTiled()) { + SkDebugf("Trying to create mipmap for linear tiled texture"); + return false; + } + SkASSERT(tex->texturePriv().textureType() == GrTextureType::k2D); + + // determine if we can blit to and from this format + const GrVkCaps& caps = this->vkCaps(); + if (!caps.formatCanBeDstofBlit(vkTex->imageFormat(), false) || + !caps.formatCanBeSrcofBlit(vkTex->imageFormat(), false) || + !caps.mipMapSupport()) { + return false; + } + + int width = tex->width(); + int height = tex->height(); + VkImageBlit blitRegion; + memset(&blitRegion, 0, sizeof(VkImageBlit)); + + // SkMipMap doesn't include the base level in the level count so we have to add 1 + uint32_t levelCount = SkMipMap::ComputeLevelCount(tex->width(), tex->height()) + 1; + SkASSERT(levelCount == vkTex->mipLevels()); + + // change layout of the layers so we can write to them. + vkTex->setImageLayout(this, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, false); + + // setup memory barrier + SkASSERT(GrVkFormatIsSupported(vkTex->imageFormat())); + VkImageMemoryBarrier imageMemoryBarrier = { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType + nullptr, // pNext + VK_ACCESS_TRANSFER_WRITE_BIT, // srcAccessMask + VK_ACCESS_TRANSFER_READ_BIT, // dstAccessMask + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // oldLayout + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, // newLayout + VK_QUEUE_FAMILY_IGNORED, // srcQueueFamilyIndex + VK_QUEUE_FAMILY_IGNORED, // dstQueueFamilyIndex + vkTex->image(), // image + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1} // subresourceRange + }; + + // Blit the miplevels + uint32_t mipLevel = 1; + while (mipLevel < levelCount) { + int prevWidth = width; + int prevHeight = height; + width = SkTMax(1, width / 2); + height = SkTMax(1, height / 2); + + imageMemoryBarrier.subresourceRange.baseMipLevel = mipLevel - 1; + this->addImageMemoryBarrier(vkTex->resource(), VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, false, &imageMemoryBarrier); + + blitRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, mipLevel - 1, 0, 1 }; + blitRegion.srcOffsets[0] = { 0, 0, 0 }; + blitRegion.srcOffsets[1] = { prevWidth, prevHeight, 1 }; + blitRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, mipLevel, 0, 1 }; + blitRegion.dstOffsets[0] = { 0, 0, 0 }; + blitRegion.dstOffsets[1] = { width, height, 1 }; + fCurrentCmdBuffer->blitImage(this, + vkTex->resource(), + vkTex->image(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vkTex->resource(), + vkTex->image(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &blitRegion, + VK_FILTER_LINEAR); + ++mipLevel; + } + if (levelCount > 1) { + // This barrier logically is not needed, but it changes the final level to the same layout + // as all the others, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL. This makes tracking of the + // layouts and future layout changes easier. The alternative here would be to track layout + // and memory accesses per layer which doesn't seem work it. + imageMemoryBarrier.subresourceRange.baseMipLevel = mipLevel - 1; + this->addImageMemoryBarrier(vkTex->resource(), VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, false, &imageMemoryBarrier); + vkTex->updateImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +GrStencilAttachment* GrVkGpu::createStencilAttachmentForRenderTarget( + const GrRenderTarget* rt, int width, int height, int numStencilSamples) { + SkASSERT(numStencilSamples == rt->numSamples()); + SkASSERT(width >= rt->width()); + SkASSERT(height >= rt->height()); + + int samples = rt->numSamples(); + + const GrVkCaps::StencilFormat& sFmt = this->vkCaps().preferredStencilFormat(); + + GrVkStencilAttachment* stencil(GrVkStencilAttachment::Create(this, + width, + height, + samples, + sFmt)); + fStats.incStencilAttachmentCreates(); + return stencil; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool copy_src_data(GrVkGpu* gpu, const GrVkAlloc& alloc, VkFormat vkFormat, + const SkTArray& individualMipOffsets, + const SkPixmap srcData[], int numMipLevels) { + SkASSERT(srcData && numMipLevels); + SkASSERT(!GrVkFormatIsCompressed(vkFormat)); + SkASSERT(individualMipOffsets.count() == numMipLevels); + + char* mapPtr = (char*) GrVkMemory::MapAlloc(gpu, alloc); + if (!mapPtr) { + return false; + } + size_t bytesPerPixel = gpu->vkCaps().bytesPerPixel(vkFormat); + + for (int level = 0; level < numMipLevels; ++level) { + const size_t trimRB = srcData[level].width() * bytesPerPixel; + SkASSERT(individualMipOffsets[level] + trimRB * srcData[level].height() <= alloc.fSize); + + SkRectMemcpy(mapPtr + individualMipOffsets[level], trimRB, + srcData[level].addr(), srcData[level].rowBytes(), + trimRB, srcData[level].height()); + } + + GrVkMemory::FlushMappedAlloc(gpu, alloc, 0, alloc.fSize); + GrVkMemory::UnmapAlloc(gpu, alloc); + return true; +} + +static void set_image_layout(const GrVkInterface* vkInterface, VkCommandBuffer cmdBuffer, + GrVkImageInfo* info, VkImageLayout newLayout, uint32_t mipLevels, + VkAccessFlags dstAccessMask, VkPipelineStageFlagBits dstStageMask) { + VkAccessFlags srcAccessMask = GrVkImage::LayoutToSrcAccessMask(info->fImageLayout); + VkPipelineStageFlags srcStageMask = GrVkImage::LayoutToPipelineSrcStageFlags( + info->fImageLayout); + + VkImageMemoryBarrier barrier; + memset(&barrier, 0, sizeof(VkImageMemoryBarrier)); + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = srcAccessMask; + barrier.dstAccessMask = dstAccessMask; + barrier.oldLayout = info->fImageLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = info->fImage; + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, mipLevels, 0, 1}; + GR_VK_CALL(vkInterface, CmdPipelineBarrier( + cmdBuffer, + srcStageMask, + dstStageMask, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier)); + info->fImageLayout = newLayout; +} + +bool GrVkGpu::createVkImageForBackendSurface(VkFormat vkFormat, int w, int h, bool texturable, + bool renderable, GrMipMapped mipMapped, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrVkImageInfo* info, + GrProtected isProtected) { + SkASSERT(texturable || renderable); + if (!texturable) { + SkASSERT(GrMipMapped::kNo == mipMapped); + SkASSERT(!srcData && !numMipLevels); + } + + // Compressed formats go through onCreateCompressedBackendTexture + SkASSERT(!GrVkFormatIsCompressed(vkFormat)); + + if (fProtectedContext != isProtected) { + return false; + } + + if (texturable && !fVkCaps->isVkFormatTexturable(vkFormat)) { + return false; + } + + if (renderable && !fVkCaps->isFormatRenderable(vkFormat, 1)) { + return false; + } + + VkImageUsageFlags usageFlags = 0; + usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + usageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (texturable) { + usageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT; + } + if (renderable) { + usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + // Figure out the number of mip levels. + uint32_t mipLevelCount = 1; + if (srcData) { + SkASSERT(numMipLevels > 0); + mipLevelCount = numMipLevels; + } else if (GrMipMapped::kYes == mipMapped) { + mipLevelCount = SkMipMap::ComputeLevelCount(w, h) + 1; + } + + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = vkFormat; + imageDesc.fWidth = w; + imageDesc.fHeight = h; + imageDesc.fLevels = mipLevelCount; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fMemProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + imageDesc.fIsProtected = fProtectedContext; + + if (!GrVkImage::InitImageInfo(this, imageDesc, info)) { + SkDebugf("Failed to init image info\n"); + return false; + } + + if (!srcData && !color) { + return true; + } + + // We need to declare these early so that we can delete them at the end outside of + // the if block. + GrVkAlloc bufferAlloc; + VkBuffer buffer = VK_NULL_HANDLE; + + VkResult err; + const VkCommandBufferAllocateInfo cmdInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, // sType + nullptr, // pNext + fCmdPool->vkCommandPool(), // commandPool + VK_COMMAND_BUFFER_LEVEL_PRIMARY, // level + 1 // bufferCount + }; + + VkCommandBuffer cmdBuffer; + err = VK_CALL(AllocateCommandBuffers(fDevice, &cmdInfo, &cmdBuffer)); + if (err) { + GrVkImage::DestroyImageInfo(this, info); + return false; + } + + VkCommandBufferBeginInfo cmdBufferBeginInfo; + memset(&cmdBufferBeginInfo, 0, sizeof(VkCommandBufferBeginInfo)); + cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + cmdBufferBeginInfo.pNext = nullptr; + cmdBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + cmdBufferBeginInfo.pInheritanceInfo = nullptr; + + err = VK_CALL(BeginCommandBuffer(cmdBuffer, &cmdBufferBeginInfo)); + SkASSERT(!err); + + // Set image layout and add barrier + set_image_layout(this->vkInterface(), cmdBuffer, info, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + mipLevelCount, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + if (srcData) { + size_t bytesPerPixel = fVkCaps->bytesPerPixel(vkFormat); + SkASSERT(w && h); + + SkTArray individualMipOffsets(mipLevelCount); + + size_t combinedBufferSize = GrComputeTightCombinedBufferSize(bytesPerPixel, w, h, + &individualMipOffsets, + mipLevelCount); + + VkBufferCreateInfo bufInfo; + memset(&bufInfo, 0, sizeof(VkBufferCreateInfo)); + bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufInfo.flags = fProtectedContext == GrProtected::kYes ? VK_BUFFER_CREATE_PROTECTED_BIT : 0; + bufInfo.size = combinedBufferSize; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufInfo.queueFamilyIndexCount = 0; + bufInfo.pQueueFamilyIndices = nullptr; + err = VK_CALL(CreateBuffer(fDevice, &bufInfo, nullptr, &buffer)); + + if (err) { + GrVkImage::DestroyImageInfo(this, info); + VK_CALL(EndCommandBuffer(cmdBuffer)); + VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer)); + return false; + } + + if (!GrVkMemory::AllocAndBindBufferMemory(this, buffer, GrVkBuffer::kCopyRead_Type, true, + &bufferAlloc)) { + GrVkImage::DestroyImageInfo(this, info); + VK_CALL(DestroyBuffer(fDevice, buffer, nullptr)); + VK_CALL(EndCommandBuffer(cmdBuffer)); + VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer)); + return false; + } + + bool result = copy_src_data(this, bufferAlloc, vkFormat, individualMipOffsets, + srcData, numMipLevels); + if (!result) { + GrVkImage::DestroyImageInfo(this, info); + GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc); + VK_CALL(DestroyBuffer(fDevice, buffer, nullptr)); + VK_CALL(EndCommandBuffer(cmdBuffer)); + VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer)); + return false; + } + + SkTArray regions(mipLevelCount); + + int currentWidth = w; + int currentHeight = h; + for (uint32_t currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { + // Submit copy command + VkBufferImageCopy& region = regions.push_back(); + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = individualMipOffsets[currentMipLevel]; + region.bufferRowLength = currentWidth; + region.bufferImageHeight = currentHeight; + region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, currentMipLevel, 0, 1}; + region.imageOffset = {0, 0, 0}; + region.imageExtent = {(uint32_t)currentWidth, (uint32_t)currentHeight, 1}; + currentWidth = SkTMax(1, currentWidth / 2); + currentHeight = SkTMax(1, currentHeight / 2); + } + + VK_CALL(CmdCopyBufferToImage(cmdBuffer, buffer, info->fImage, info->fImageLayout, + regions.count(), regions.begin())); + } else { + SkASSERT(color); + VkClearColorValue vkColor; + // If we ever support SINT or UINT formats this needs to be updated to use the int32 and + // uint32 union members in those cases. + vkColor.float32[0] = color->fR; + vkColor.float32[1] = color->fG; + vkColor.float32[2] = color->fB; + vkColor.float32[3] = color->fA; + VkImageSubresourceRange range; + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.baseArrayLayer = 0; + range.baseMipLevel = 0; + range.layerCount = 1; + range.levelCount = mipLevelCount; + VK_CALL(CmdClearColorImage(cmdBuffer, info->fImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + &vkColor, 1, &range)); + } + + if (!srcData && renderable) { + SkASSERT(color); + + // Change image layout to color-attachment-optimal since if we use this texture as a + // borrowed texture within Ganesh we are probably going to render to it + set_image_layout(this->vkInterface(), cmdBuffer, info, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, mipLevelCount, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + } else if (texturable) { + // Change image layout to shader read since if we use this texture as a borrowed + // texture within Ganesh we require that its layout be set to that + set_image_layout(this->vkInterface(), cmdBuffer, info, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, mipLevelCount, + VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + } + + // End CommandBuffer + err = VK_CALL(EndCommandBuffer(cmdBuffer)); + SkASSERT(!err); + + // Create Fence for queue + VkFenceCreateInfo fenceInfo; + memset(&fenceInfo, 0, sizeof(VkFenceCreateInfo)); + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.pNext = nullptr; + fenceInfo.flags = 0; + VkFence fence = VK_NULL_HANDLE; + + err = VK_CALL(CreateFence(fDevice, &fenceInfo, nullptr, &fence)); + SkASSERT(!err); + + VkProtectedSubmitInfo protectedSubmitInfo; + if (fProtectedContext == GrProtected::kYes) { + memset(&protectedSubmitInfo, 0, sizeof(VkProtectedSubmitInfo)); + protectedSubmitInfo.sType = VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO; + protectedSubmitInfo.pNext = nullptr; + protectedSubmitInfo.protectedSubmit = VK_TRUE; + } + + VkSubmitInfo submitInfo; + memset(&submitInfo, 0, sizeof(VkSubmitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.pNext = fProtectedContext == GrProtected::kYes ? &protectedSubmitInfo : nullptr; + submitInfo.waitSemaphoreCount = 0; + submitInfo.pWaitSemaphores = nullptr; + submitInfo.pWaitDstStageMask = 0; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cmdBuffer; + submitInfo.signalSemaphoreCount = 0; + submitInfo.pSignalSemaphores = nullptr; + err = VK_CALL(QueueSubmit(this->queue(), 1, &submitInfo, fence)); + SkASSERT(!err); + + err = VK_CALL(WaitForFences(this->device(), 1, &fence, VK_TRUE, UINT64_MAX)); + if (VK_TIMEOUT == err) { + GrVkImage::DestroyImageInfo(this, info); + if (buffer != VK_NULL_HANDLE) { // workaround for an older NVidia driver crash + GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc); + VK_CALL(DestroyBuffer(fDevice, buffer, nullptr)); + } + VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer)); + VK_CALL(DestroyFence(this->device(), fence, nullptr)); + SkDebugf("Fence failed to signal: %d\n", err); + SK_ABORT("failing"); + } + SkASSERT(!err); + + // Clean up transfer resources + if (buffer != VK_NULL_HANDLE) { // workaround for an older NVidia driver crash + GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc); + VK_CALL(DestroyBuffer(fDevice, buffer, nullptr)); + } + VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer)); + VK_CALL(DestroyFence(this->device(), fence, nullptr)); + + return true; +} + +GrBackendTexture GrVkGpu::onCreateBackendTexture(int w, int h, + const GrBackendFormat& format, + GrMipMapped mipMapped, + GrRenderable renderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected isProtected) { + this->handleDirtyContext(); + + const GrVkCaps& caps = this->vkCaps(); + + // GrGpu::createBackendTexture should've ensured these conditions + SkASSERT(w >= 1 && w <= caps.maxTextureSize() && h >= 1 && h <= caps.maxTextureSize()); + SkASSERT(GrGpu::MipMapsAreCorrect(w, h, mipMapped, srcData, numMipLevels)); + SkASSERT(mipMapped == GrMipMapped::kNo || caps.mipMapSupport()); + + if (fProtectedContext != isProtected) { + return GrBackendTexture(); + } + + VkFormat vkFormat; + if (!format.asVkFormat(&vkFormat)) { + SkDebugf("Could net get vkformat\n"); + return GrBackendTexture(); + } + + // TODO: move the texturability check up to GrGpu::createBackendTexture and just assert here + if (!caps.isVkFormatTexturable(vkFormat)) { + SkDebugf("Config is not texturable\n"); + return GrBackendTexture(); + } + + if (GrVkFormatNeedsYcbcrSampler(vkFormat)) { + SkDebugf("Can't create BackendTexture that requires Ycbcb sampler.\n"); + return GrBackendTexture(); + } + + GrVkImageInfo info; + if (!this->createVkImageForBackendSurface(vkFormat, w, h, true, + GrRenderable::kYes == renderable, mipMapped, + srcData, numMipLevels, color, &info, isProtected)) { + SkDebugf("Failed to create testing only image\n"); + return GrBackendTexture(); + } + + return GrBackendTexture(w, h, info); +} + +void GrVkGpu::deleteBackendTexture(const GrBackendTexture& tex) { + SkASSERT(GrBackendApi::kVulkan == tex.fBackend); + + GrVkImageInfo info; + if (tex.getVkImageInfo(&info)) { + GrVkImage::DestroyImageInfo(this, const_cast(&info)); + } +} + +#if GR_TEST_UTILS +bool GrVkGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const { + SkASSERT(GrBackendApi::kVulkan == tex.fBackend); + + GrVkImageInfo backend; + if (!tex.getVkImageInfo(&backend)) { + return false; + } + + if (backend.fImage && backend.fAlloc.fMemory) { + VkMemoryRequirements req; + memset(&req, 0, sizeof(req)); + GR_VK_CALL(this->vkInterface(), GetImageMemoryRequirements(fDevice, + backend.fImage, + &req)); + // TODO: find a better check + // This will probably fail with a different driver + return (req.size > 0) && (req.size <= 8192 * 8192); + } + + return false; +} + +GrBackendRenderTarget GrVkGpu::createTestingOnlyBackendRenderTarget(int w, int h, GrColorType ct) { + this->handleDirtyContext(); + + if (w > this->caps()->maxRenderTargetSize() || h > this->caps()->maxRenderTargetSize()) { + return GrBackendRenderTarget(); + } + + VkFormat vkFormat = this->vkCaps().getFormatFromColorType(ct); + + GrVkImageInfo info; + if (!this->createVkImageForBackendSurface(vkFormat, w, h, false, true, GrMipMapped::kNo, + nullptr, 0, &SkColors::kTransparent, &info, + GrProtected::kNo)) { + return {}; + } + + return GrBackendRenderTarget(w, h, 1, 0, info); +} + +void GrVkGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget& rt) { + SkASSERT(GrBackendApi::kVulkan == rt.fBackend); + + GrVkImageInfo info; + if (rt.getVkImageInfo(&info)) { + // something in the command buffer may still be using this, so force submit + this->submitCommandBuffer(kForce_SyncQueue); + GrVkImage::DestroyImageInfo(this, const_cast(&info)); + } +} + +void GrVkGpu::testingOnly_flushGpuAndSync() { + this->submitCommandBuffer(kForce_SyncQueue); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkGpu::addBufferMemoryBarrier(const GrVkResource* resource, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkBufferMemoryBarrier* barrier) const { + SkASSERT(fCurrentCmdBuffer); + SkASSERT(resource); + fCurrentCmdBuffer->pipelineBarrier(this, + resource, + srcStageMask, + dstStageMask, + byRegion, + GrVkCommandBuffer::kBufferMemory_BarrierType, + barrier); +} + +void GrVkGpu::addImageMemoryBarrier(const GrVkResource* resource, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkImageMemoryBarrier* barrier) const { + SkASSERT(fCurrentCmdBuffer); + SkASSERT(resource); + fCurrentCmdBuffer->pipelineBarrier(this, + resource, + srcStageMask, + dstStageMask, + byRegion, + GrVkCommandBuffer::kImageMemory_BarrierType, + barrier); +} + +void GrVkGpu::onFinishFlush(GrSurfaceProxy* proxies[], int n, + SkSurface::BackendSurfaceAccess access, const GrFlushInfo& info, + const GrPrepareForExternalIORequests& externalRequests) { + SkASSERT(n >= 0); + SkASSERT(!n || proxies); + // Submit the current command buffer to the Queue. Whether we inserted semaphores or not does + // not effect what we do here. + if (n && access == SkSurface::BackendSurfaceAccess::kPresent) { + GrVkImage* image; + for (int i = 0; i < n; ++i) { + SkASSERT(proxies[i]->isInstantiated()); + if (GrTexture* tex = proxies[i]->peekTexture()) { + image = static_cast(tex); + } else { + GrRenderTarget* rt = proxies[i]->peekRenderTarget(); + SkASSERT(rt); + image = static_cast(rt); + } + image->prepareForPresent(this); + } + } + + // Handle requests for preparing for external IO + for (int i = 0; i < externalRequests.fNumImages; ++i) { + SkImage* image = externalRequests.fImages[i]; + if (!image->isTextureBacked()) { + continue; + } + SkImage_GpuBase* gpuImage = static_cast(as_IB(image)); + sk_sp proxy = gpuImage->asTextureProxyRef(this->getContext()); + SkASSERT(proxy); + + if (!proxy->isInstantiated()) { + auto resourceProvider = this->getContext()->priv().resourceProvider(); + if (!proxy->instantiate(resourceProvider)) { + continue; + } + } + + GrTexture* tex = proxy->peekTexture(); + if (!tex) { + continue; + } + GrVkTexture* vkTex = static_cast(tex); + vkTex->prepareForExternal(this); + } + for (int i = 0; i < externalRequests.fNumSurfaces; ++i) { + SkSurface* surface = externalRequests.fSurfaces[i]; + if (!surface->getCanvas()->getGrContext()) { + continue; + } + SkSurface_Gpu* gpuSurface = static_cast(surface); + auto* rtc = gpuSurface->getDevice()->accessRenderTargetContext(); + sk_sp proxy = rtc->asRenderTargetProxyRef(); + if (!proxy->isInstantiated()) { + auto resourceProvider = this->getContext()->priv().resourceProvider(); + if (!proxy->instantiate(resourceProvider)) { + continue; + } + } + + GrRenderTarget* rt = proxy->peekRenderTarget(); + SkASSERT(rt); + GrVkRenderTarget* vkRT = static_cast(rt); + if (externalRequests.fPrepareSurfaceForPresent && + externalRequests.fPrepareSurfaceForPresent[i]) { + vkRT->prepareForPresent(this); + } else { + vkRT->prepareForExternal(this); + } + } + + if (info.fFlags & kSyncCpu_GrFlushFlag) { + this->submitCommandBuffer(kForce_SyncQueue, info.fFinishedProc, info.fFinishedContext); + } else { + this->submitCommandBuffer(kSkip_SyncQueue, info.fFinishedProc, info.fFinishedContext); + } +} + +static int get_surface_sample_cnt(GrSurface* surf) { + if (const GrRenderTarget* rt = surf->asRenderTarget()) { + return rt->numSamples(); + } + return 0; +} + +void GrVkGpu::copySurfaceAsCopyImage(GrSurface* dst, GrSurface* src, GrVkImage* dstImage, + GrVkImage* srcImage, const SkIRect& srcRect, + const SkIPoint& dstPoint) { +#ifdef SK_DEBUG + int dstSampleCnt = get_surface_sample_cnt(dst); + int srcSampleCnt = get_surface_sample_cnt(src); + bool dstHasYcbcr = dstImage->ycbcrConversionInfo().isValid(); + bool srcHasYcbcr = srcImage->ycbcrConversionInfo().isValid(); + VkFormat dstFormat = dstImage->imageFormat(); + VkFormat srcFormat; + SkAssertResult(dst->backendFormat().asVkFormat(&srcFormat)); + SkASSERT(this->vkCaps().canCopyImage(dstFormat, dstSampleCnt, dstHasYcbcr, + srcFormat, srcSampleCnt, srcHasYcbcr)); +#endif + if (src->isProtected() && !dst->isProtected()) { + SkDebugf("Can't copy from protected memory to non-protected"); + return; + } + + // These flags are for flushing/invalidating caches and for the dst image it doesn't matter if + // the cache is flushed since it is only being written to. + dstImage->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + srcImage->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + VkImageCopy copyRegion; + memset(©Region, 0, sizeof(VkImageCopy)); + copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.srcOffset = { srcRect.fLeft, srcRect.fTop, 0 }; + copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.dstOffset = { dstPoint.fX, dstPoint.fY, 0 }; + copyRegion.extent = { (uint32_t)srcRect.width(), (uint32_t)srcRect.height(), 1 }; + + fCurrentCmdBuffer->copyImage(this, + srcImage, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); + + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, + srcRect.width(), srcRect.height()); + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); +} + +void GrVkGpu::copySurfaceAsBlit(GrSurface* dst, GrSurface* src, GrVkImage* dstImage, + GrVkImage* srcImage, const SkIRect& srcRect, + const SkIPoint& dstPoint) { +#ifdef SK_DEBUG + int dstSampleCnt = get_surface_sample_cnt(dst); + int srcSampleCnt = get_surface_sample_cnt(src); + bool dstHasYcbcr = dstImage->ycbcrConversionInfo().isValid(); + bool srcHasYcbcr = srcImage->ycbcrConversionInfo().isValid(); + VkFormat dstFormat = dstImage->imageFormat(); + VkFormat srcFormat; + SkAssertResult(dst->backendFormat().asVkFormat(&srcFormat)); + SkASSERT(this->vkCaps().canCopyAsBlit(dstFormat, dstSampleCnt, dstImage->isLinearTiled(), + dstHasYcbcr, srcFormat, srcSampleCnt, + srcImage->isLinearTiled(), srcHasYcbcr)); + +#endif + if (src->isProtected() && !dst->isProtected()) { + SkDebugf("Can't copy from protected memory to non-protected"); + return; + } + + dstImage->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + srcImage->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + // Flip rect if necessary + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, srcRect.width(), + srcRect.height()); + + VkImageBlit blitRegion; + memset(&blitRegion, 0, sizeof(VkImageBlit)); + blitRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + blitRegion.srcOffsets[0] = { srcRect.fLeft, srcRect.fTop, 0 }; + blitRegion.srcOffsets[1] = { srcRect.fRight, srcRect.fBottom, 1 }; + blitRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + blitRegion.dstOffsets[0] = { dstRect.fLeft, dstRect.fTop, 0 }; + blitRegion.dstOffsets[1] = { dstRect.fRight, dstRect.fBottom, 1 }; + + fCurrentCmdBuffer->blitImage(this, + *srcImage, + *dstImage, + 1, + &blitRegion, + VK_FILTER_NEAREST); // We never scale so any filter works here + + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); +} + +void GrVkGpu::copySurfaceAsResolve(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { + if (src->isProtected() && !dst->isProtected()) { + SkDebugf("Can't copy from protected memory to non-protected"); + return; + } + GrVkRenderTarget* srcRT = static_cast(src->asRenderTarget()); + this->resolveImage(dst, srcRT, srcRect, dstPoint); + SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, + srcRect.width(), srcRect.height()); + // The rect is already in device space so we pass in kTopLeft so no flip is done. + this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect); +} + +bool GrVkGpu::onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) { +#ifdef SK_DEBUG + if (GrVkRenderTarget* srcRT = static_cast(src->asRenderTarget())) { + SkASSERT(!srcRT->wrapsSecondaryCommandBuffer()); + } + if (GrVkRenderTarget* dstRT = static_cast(dst->asRenderTarget())) { + SkASSERT(!dstRT->wrapsSecondaryCommandBuffer()); + } +#endif + if (src->isProtected() && !dst->isProtected()) { + SkDebugf("Can't copy from protected memory to non-protected"); + return false; + } + + int dstSampleCnt = get_surface_sample_cnt(dst); + int srcSampleCnt = get_surface_sample_cnt(src); + + GrVkImage* dstImage; + GrVkImage* srcImage; + GrRenderTarget* dstRT = dst->asRenderTarget(); + if (dstRT) { + GrVkRenderTarget* vkRT = static_cast(dstRT); + if (vkRT->wrapsSecondaryCommandBuffer()) { + return false; + } + dstImage = vkRT->numSamples() > 1 ? vkRT->msaaImage() : vkRT; + } else { + SkASSERT(dst->asTexture()); + dstImage = static_cast(dst->asTexture()); + } + GrRenderTarget* srcRT = src->asRenderTarget(); + if (srcRT) { + GrVkRenderTarget* vkRT = static_cast(srcRT); + srcImage = vkRT->numSamples() > 1 ? vkRT->msaaImage() : vkRT; + } else { + SkASSERT(src->asTexture()); + srcImage = static_cast(src->asTexture()); + } + + VkFormat dstFormat = dstImage->imageFormat(); + VkFormat srcFormat = srcImage->imageFormat(); + + bool dstHasYcbcr = dstImage->ycbcrConversionInfo().isValid(); + bool srcHasYcbcr = srcImage->ycbcrConversionInfo().isValid(); + + if (this->vkCaps().canCopyAsResolve(dstFormat, dstSampleCnt, dstHasYcbcr, + srcFormat, srcSampleCnt, srcHasYcbcr)) { + this->copySurfaceAsResolve(dst, src, srcRect, dstPoint); + return true; + } + + if (this->vkCaps().canCopyImage(dstFormat, dstSampleCnt, dstHasYcbcr, + srcFormat, srcSampleCnt, srcHasYcbcr)) { + this->copySurfaceAsCopyImage(dst, src, dstImage, srcImage, srcRect, dstPoint); + return true; + } + + if (this->vkCaps().canCopyAsBlit(dstFormat, dstSampleCnt, dstImage->isLinearTiled(), + dstHasYcbcr, srcFormat, srcSampleCnt, + srcImage->isLinearTiled(), srcHasYcbcr)) { + this->copySurfaceAsBlit(dst, src, dstImage, srcImage, srcRect, dstPoint); + return true; + } + + return false; +} + +bool GrVkGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) { + if (surface->isProtected()) { + return false; + } + + if (surfaceColorType != dstColorType) { + return false; + } + + GrVkImage* image = nullptr; + GrVkRenderTarget* rt = static_cast(surface->asRenderTarget()); + if (rt) { + // Reading from render targets that wrap a secondary command buffer is not allowed since + // it would require us to know the VkImage, which we don't have, as well as need us to + // stop and start the VkRenderPass which we don't have access to. + if (rt->wrapsSecondaryCommandBuffer()) { + return false; + } + image = rt; + } else { + image = static_cast(surface->asTexture()); + } + + if (!image) { + return false; + } + + // Skia's RGB_888x color type, which we map to the vulkan R8G8B8_UNORM, expects the data to be + // 32 bits, but the Vulkan format is only 24. So we first copy the surface into an R8G8B8A8 + // image and then do the read pixels from that. + sk_sp copySurface; + if (dstColorType == GrColorType::kRGB_888x && image->imageFormat() == VK_FORMAT_R8G8B8_UNORM) { + int srcSampleCount = 0; + if (rt) { + srcSampleCount = rt->numSamples(); + } + bool srcHasYcbcr = image->ycbcrConversionInfo().isValid(); + if (!this->vkCaps().canCopyAsBlit(VK_FORMAT_R8G8B8A8_UNORM, 1, false, false, + image->imageFormat(), srcSampleCount, + image->isLinearTiled(), srcHasYcbcr)) { + return false; + } + + // Make a new surface that is RGBA to copy the RGB surface into. + GrSurfaceDesc surfDesc; + surfDesc.fWidth = width; + surfDesc.fHeight = height; + surfDesc.fConfig = kRGBA_8888_GrPixelConfig; + + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = VK_FORMAT_R8G8B8A8_UNORM; + imageDesc.fWidth = width; + imageDesc.fHeight = height; + imageDesc.fLevels = 1; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fMemProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + copySurface = GrVkTextureRenderTarget::MakeNewTextureRenderTarget( + this, SkBudgeted::kYes, surfDesc, 1, imageDesc, GrMipMapsStatus::kNotAllocated); + if (!copySurface) { + return false; + } + + SkIRect srcRect = SkIRect::MakeXYWH(left, top, width, height); + SkAssertResult(this->copySurface(copySurface.get(), surface, srcRect, SkIPoint::Make(0,0))); + + top = 0; + left = 0; + dstColorType = GrColorType::kRGBA_8888; + image = copySurface.get(); + } + + // Change layout of our target so it can be used as copy + image->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + size_t bpp = GrColorTypeBytesPerPixel(dstColorType); + size_t tightRowBytes = bpp * width; + + VkBufferImageCopy region; + memset(®ion, 0, sizeof(VkBufferImageCopy)); + + bool copyFromOrigin = this->vkCaps().mustDoCopiesFromOrigin(); + if (copyFromOrigin) { + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { (uint32_t)(left + width), (uint32_t)(top + height), 1 }; + } else { + VkOffset3D offset = { left, top, 0 }; + region.imageOffset = offset; + region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 }; + } + + size_t transBufferRowBytes = bpp * region.imageExtent.width; + size_t imageRows = region.imageExtent.height; + auto transferBuffer = sk_sp( + static_cast(this->createBuffer(transBufferRowBytes * imageRows, + GrGpuBufferType::kXferGpuToCpu, + kStream_GrAccessPattern) + .release())); + + // Copy the image to a buffer so we can map it to cpu memory + region.bufferOffset = transferBuffer->offset(); + region.bufferRowLength = 0; // Forces RowLength to be width. We handle the rowBytes below. + region.bufferImageHeight = 0; // Forces height to be tightly packed. Only useful for 3d images. + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + + fCurrentCmdBuffer->copyImageToBuffer(this, + image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + transferBuffer.get(), + 1, + ®ion); + + // make sure the copy to buffer has finished + transferBuffer->addMemoryBarrier(this, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_HOST_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + false); + + // We need to submit the current command buffer to the Queue and make sure it finishes before + // we can copy the data out of the buffer. + this->submitCommandBuffer(kForce_SyncQueue); + void* mappedMemory = transferBuffer->map(); + const GrVkAlloc& transAlloc = transferBuffer->alloc(); + GrVkMemory::InvalidateMappedAlloc(this, transAlloc, 0, transAlloc.fSize); + + if (copyFromOrigin) { + uint32_t skipRows = region.imageExtent.height - height; + mappedMemory = (char*)mappedMemory + transBufferRowBytes * skipRows + bpp * left; + } + + SkRectMemcpy(buffer, rowBytes, mappedMemory, transBufferRowBytes, tightRowBytes, height); + + transferBuffer->unmap(); + return true; +} + +// The RenderArea bounds we pass into BeginRenderPass must have a start x value that is a multiple +// of the granularity. The width must also be a multiple of the granularity or eaqual to the width +// the the entire attachment. Similar requirements for the y and height components. +void adjust_bounds_to_granularity(SkIRect* dstBounds, const SkIRect& srcBounds, + const VkExtent2D& granularity, int maxWidth, int maxHeight) { + // Adjust Width + if ((0 != granularity.width && 1 != granularity.width)) { + // Start with the right side of rect so we know if we end up going pass the maxWidth. + int rightAdj = srcBounds.fRight % granularity.width; + if (rightAdj != 0) { + rightAdj = granularity.width - rightAdj; + } + dstBounds->fRight = srcBounds.fRight + rightAdj; + if (dstBounds->fRight > maxWidth) { + dstBounds->fRight = maxWidth; + dstBounds->fLeft = 0; + } else { + dstBounds->fLeft = srcBounds.fLeft - srcBounds.fLeft % granularity.width; + } + } else { + dstBounds->fLeft = srcBounds.fLeft; + dstBounds->fRight = srcBounds.fRight; + } + + // Adjust height + if ((0 != granularity.height && 1 != granularity.height)) { + // Start with the bottom side of rect so we know if we end up going pass the maxHeight. + int bottomAdj = srcBounds.fBottom % granularity.height; + if (bottomAdj != 0) { + bottomAdj = granularity.height - bottomAdj; + } + dstBounds->fBottom = srcBounds.fBottom + bottomAdj; + if (dstBounds->fBottom > maxHeight) { + dstBounds->fBottom = maxHeight; + dstBounds->fTop = 0; + } else { + dstBounds->fTop = srcBounds.fTop - srcBounds.fTop % granularity.height; + } + } else { + dstBounds->fTop = srcBounds.fTop; + dstBounds->fBottom = srcBounds.fBottom; + } +} + +void GrVkGpu::beginRenderPass(const GrVkRenderPass* renderPass, + const VkClearValue* colorClear, + GrVkRenderTarget* target, GrSurfaceOrigin origin, + const SkIRect& bounds, bool forSecondaryCB) { + SkASSERT (!target->wrapsSecondaryCommandBuffer()); + auto nativeBounds = GrNativeRect::MakeRelativeTo(origin, target->height(), bounds); + + // The bounds we use for the render pass should be of the granularity supported + // by the device. + const VkExtent2D& granularity = renderPass->granularity(); + SkIRect adjustedBounds; + if ((0 != granularity.width && 1 != granularity.width) || + (0 != granularity.height && 1 != granularity.height)) { + adjust_bounds_to_granularity(&adjustedBounds, nativeBounds.asSkIRect(), granularity, + target->width(), target->height()); + } else { + adjustedBounds = nativeBounds.asSkIRect(); + } + +#ifdef SK_DEBUG + uint32_t index; + bool result = renderPass->colorAttachmentIndex(&index); + SkASSERT(result && 0 == index); + result = renderPass->stencilAttachmentIndex(&index); + if (result) { + SkASSERT(1 == index); + } +#endif + VkClearValue clears[2]; + clears[0].color = colorClear->color; + clears[1].depthStencil.depth = 0.0f; + clears[1].depthStencil.stencil = 0; + + fCurrentCmdBuffer->beginRenderPass(this, renderPass, clears, *target, adjustedBounds, + forSecondaryCB); +} + +void GrVkGpu::endRenderPass(GrRenderTarget* target, GrSurfaceOrigin origin, + const SkIRect& bounds) { + fCurrentCmdBuffer->endRenderPass(this); + this->didWriteToSurface(target, origin, &bounds); +} + +void GrVkGpu::submitSecondaryCommandBuffer(std::unique_ptr buffer) { + fCurrentCmdBuffer->executeCommands(this, std::move(buffer)); +} + +void GrVkGpu::submit(GrOpsRenderPass* renderPass) { + SkASSERT(fCachedOpsRenderPass.get() == renderPass); + + fCachedOpsRenderPass->submit(); + fCachedOpsRenderPass->reset(); +} + +GrFence SK_WARN_UNUSED_RESULT GrVkGpu::insertFence() { + VkFenceCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkFenceCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + VkFence fence = VK_NULL_HANDLE; + + VK_CALL_ERRCHECK(CreateFence(this->device(), &createInfo, nullptr, &fence)); + VK_CALL(QueueSubmit(this->queue(), 0, nullptr, fence)); + + GR_STATIC_ASSERT(sizeof(GrFence) >= sizeof(VkFence)); + return (GrFence)fence; +} + +bool GrVkGpu::waitFence(GrFence fence, uint64_t timeout) { + SkASSERT(VK_NULL_HANDLE != (VkFence)fence); + + VkResult result = VK_CALL(WaitForFences(this->device(), 1, (VkFence*)&fence, VK_TRUE, timeout)); + return (VK_SUCCESS == result); +} + +void GrVkGpu::deleteFence(GrFence fence) const { + VK_CALL(DestroyFence(this->device(), (VkFence)fence, nullptr)); +} + +sk_sp SK_WARN_UNUSED_RESULT GrVkGpu::makeSemaphore(bool isOwned) { + return GrVkSemaphore::Make(this, isOwned); +} + +sk_sp GrVkGpu::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) { + return GrVkSemaphore::MakeWrapped(this, semaphore.vkSemaphore(), wrapType, ownership); +} + +void GrVkGpu::insertSemaphore(sk_sp semaphore) { + GrVkSemaphore* vkSem = static_cast(semaphore.get()); + + GrVkSemaphore::Resource* resource = vkSem->getResource(); + if (resource->shouldSignal()) { + resource->ref(); + fSemaphoresToSignal.push_back(resource); + } +} + +void GrVkGpu::waitSemaphore(sk_sp semaphore) { + GrVkSemaphore* vkSem = static_cast(semaphore.get()); + + GrVkSemaphore::Resource* resource = vkSem->getResource(); + if (resource->shouldWait()) { + resource->ref(); + fSemaphoresToWaitOn.push_back(resource); + } +} + +sk_sp GrVkGpu::prepareTextureForCrossContextUsage(GrTexture* texture) { + SkASSERT(texture); + GrVkTexture* vkTexture = static_cast(texture); + vkTexture->setImageLayout(this, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + false); + this->submitCommandBuffer(kSkip_SyncQueue); + + // The image layout change serves as a barrier, so no semaphore is needed. + // If we ever decide we need to return a semaphore here, we need to make sure GrVkSemaphore is + // thread safe so that only the first thread that tries to use the semaphore actually submits + // it. This additionally would also require thread safety in command buffer submissions to + // queues in general. + return nullptr; +} + +void GrVkGpu::addDrawable(std::unique_ptr drawable) { + fDrawables.emplace_back(std::move(drawable)); +} + +uint32_t GrVkGpu::getExtraSamplerKeyForProgram(const GrSamplerState& samplerState, + const GrBackendFormat& format) { + const GrVkYcbcrConversionInfo* ycbcrInfo = format.getVkYcbcrConversionInfo(); + SkASSERT(ycbcrInfo); + if (!ycbcrInfo->isValid()) { + return 0; + } + + const GrVkSampler* sampler = this->resourceProvider().findOrCreateCompatibleSampler( + samplerState, *ycbcrInfo); + + uint32_t result = sampler->uniqueID(); + + sampler->unref(this); + + return result; +} + +void GrVkGpu::storeVkPipelineCacheData() { + if (this->getContext()->priv().getPersistentCache()) { + this->resourceProvider().storePipelineCacheData(); + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkGpu.h b/gfx/skia/skia/src/gpu/vk/GrVkGpu.h new file mode 100644 index 0000000000..cdbb31c6c2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkGpu.h @@ -0,0 +1,313 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkGpu_DEFINED +#define GrVkGpu_DEFINED + +#include "include/gpu/vk/GrVkBackendContext.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/vk/GrVkCaps.h" +#include "src/gpu/vk/GrVkIndexBuffer.h" +#include "src/gpu/vk/GrVkMemory.h" +#include "src/gpu/vk/GrVkResourceProvider.h" +#include "src/gpu/vk/GrVkSemaphore.h" +#include "src/gpu/vk/GrVkUtil.h" +#include "src/gpu/vk/GrVkVertexBuffer.h" + +class GrPipeline; + +class GrVkBufferImpl; +class GrVkCommandPool; +class GrVkMemoryAllocator; +class GrVkPipeline; +class GrVkPipelineState; +class GrVkPrimaryCommandBuffer; +class GrVkOpsRenderPass; +class GrVkRenderPass; +class GrVkSecondaryCommandBuffer; +class GrVkTexture; +struct GrVkInterface; + +namespace SkSL { + class Compiler; +} + +class GrVkGpu : public GrGpu { +public: + static sk_sp Make(const GrVkBackendContext&, const GrContextOptions&, GrContext*); + + ~GrVkGpu() override; + + void disconnect(DisconnectType) override; + + const GrVkInterface* vkInterface() const { return fInterface.get(); } + const GrVkCaps& vkCaps() const { return *fVkCaps; } + + GrVkMemoryAllocator* memoryAllocator() const { return fMemoryAllocator.get(); } + + VkPhysicalDevice physicalDevice() const { return fPhysicalDevice; } + VkDevice device() const { return fDevice; } + VkQueue queue() const { return fQueue; } + uint32_t queueIndex() const { return fQueueIndex; } + GrVkCommandPool* cmdPool() const { return fCmdPool; } + const VkPhysicalDeviceProperties& physicalDeviceProperties() const { + return fPhysDevProps; + } + const VkPhysicalDeviceMemoryProperties& physicalDeviceMemoryProperties() const { + return fPhysDevMemProps; + } + bool protectedContext() const { return fProtectedContext == GrProtected::kYes; } + + GrVkResourceProvider& resourceProvider() { return fResourceProvider; } + + GrVkPrimaryCommandBuffer* currentCommandBuffer() { return fCurrentCmdBuffer; } + + enum SyncQueue { + kForce_SyncQueue, + kSkip_SyncQueue + }; + + void querySampleLocations(GrRenderTarget*, SkTArray*) override { + SkASSERT(!this->caps()->sampleLocationsSupport()); + SK_ABORT("Sample locations not yet implemented for Vulkan."); + } + + void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {} + + void deleteBackendTexture(const GrBackendTexture&) override; +#if GR_TEST_UTILS + bool isTestingOnlyBackendTexture(const GrBackendTexture&) const override; + + GrBackendRenderTarget createTestingOnlyBackendRenderTarget(int w, int h, GrColorType) override; + void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) override; + + void testingOnly_flushGpuAndSync() override; + + void resetShaderCacheForTesting() const override { + fResourceProvider.resetShaderCacheForTesting(); + } +#endif + + GrStencilAttachment* createStencilAttachmentForRenderTarget( + const GrRenderTarget*, int width, int height, int numStencilSamples) override; + + GrOpsRenderPass* getOpsRenderPass( + GrRenderTarget*, GrSurfaceOrigin, const SkIRect&, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies) override; + + void addBufferMemoryBarrier(const GrVkResource*, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkBufferMemoryBarrier* barrier) const; + void addImageMemoryBarrier(const GrVkResource*, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkImageMemoryBarrier* barrier) const; + + SkSL::Compiler* shaderCompiler() const { + return fCompiler; + } + + bool onRegenerateMipMapLevels(GrTexture* tex) override; + + void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect, + GrSurfaceOrigin resolveOrigin, ForExternalIO) override; + + void submitSecondaryCommandBuffer(std::unique_ptr); + + void submit(GrOpsRenderPass*) override; + + GrFence SK_WARN_UNUSED_RESULT insertFence() override; + bool waitFence(GrFence, uint64_t timeout) override; + void deleteFence(GrFence) const override; + + sk_sp SK_WARN_UNUSED_RESULT makeSemaphore(bool isOwned) override; + sk_sp wrapBackendSemaphore(const GrBackendSemaphore& semaphore, + GrResourceProvider::SemaphoreWrapType wrapType, + GrWrapOwnership ownership) override; + void insertSemaphore(sk_sp semaphore) override; + void waitSemaphore(sk_sp semaphore) override; + + // These match the definitions in SkDrawable, from whence they came + typedef void* SubmitContext; + typedef void (*SubmitProc)(SubmitContext submitContext); + + // Adds an SkDrawable::GpuDrawHandler that we will delete the next time we submit the primary + // command buffer to the gpu. + void addDrawable(std::unique_ptr drawable); + + void checkFinishProcs() override { fResourceProvider.checkCommandBuffers(); } + + sk_sp prepareTextureForCrossContextUsage(GrTexture*) override; + + void copyBuffer(GrVkBuffer* srcBuffer, GrVkBuffer* dstBuffer, VkDeviceSize srcOffset, + VkDeviceSize dstOffset, VkDeviceSize size); + bool updateBuffer(GrVkBuffer* buffer, const void* src, VkDeviceSize offset, VkDeviceSize size); + + uint32_t getExtraSamplerKeyForProgram(const GrSamplerState&, + const GrBackendFormat& format) override; + + enum PersistentCacheKeyType : uint32_t { + kShader_PersistentCacheKeyType = 0, + kPipelineCache_PersistentCacheKeyType = 1, + }; + + void storeVkPipelineCacheData() override; + + void beginRenderPass(const GrVkRenderPass*, + const VkClearValue* colorClear, + GrVkRenderTarget*, GrSurfaceOrigin, + const SkIRect& bounds, bool forSecondaryCB); + void endRenderPass(GrRenderTarget* target, GrSurfaceOrigin origin, const SkIRect& bounds); + +private: + GrVkGpu(GrContext*, const GrContextOptions&, const GrVkBackendContext&, + sk_sp, uint32_t instanceVersion, uint32_t physicalDeviceVersion); + + void onResetContext(uint32_t resetBits) override {} + + void destroyResources(); + + GrBackendTexture onCreateBackendTexture(int w, int h, const GrBackendFormat&, + GrMipMapped, GrRenderable, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrProtected) override; + sk_sp onCreateTexture(const GrSurfaceDesc&, + const GrBackendFormat& format, + GrRenderable, + int renderTargetSampleCnt, + SkBudgeted, + GrProtected, + int mipLevelCount, + uint32_t levelClearMask) override; + sk_sp onCreateCompressedTexture(int width, int height, const GrBackendFormat&, + SkImage::CompressionType, SkBudgeted, + const void* data) override; + + sk_sp onWrapBackendTexture(const GrBackendTexture&, GrColorType, GrWrapOwnership, + GrWrapCacheable, GrIOType) override; + sk_sp onWrapRenderableBackendTexture(const GrBackendTexture&, + int sampleCnt, + GrColorType colorType, + GrWrapOwnership, + GrWrapCacheable) override; + sk_sp onWrapBackendRenderTarget(const GrBackendRenderTarget&, + GrColorType) override; + + sk_sp onWrapBackendTextureAsRenderTarget(const GrBackendTexture&, + int sampleCnt, GrColorType) override; + + sk_sp onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&, + const GrVkDrawableInfo&) override; + + sk_sp onCreateBuffer(size_t size, GrGpuBufferType type, GrAccessPattern, + const void* data) override; + + bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, + size_t rowBytes) override; + + bool onWritePixels(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType srcColorType, + const GrMipLevel texels[], int mipLevelCount, + bool prepForTexSampling) override; + + bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, + GrColorType textureColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) override; + bool onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height, + GrColorType surfaceColorType, GrColorType bufferColorType, + GrGpuBuffer* transferBuffer, size_t offset) override; + + bool onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint) override; + + void onFinishFlush(GrSurfaceProxy*[], int, SkSurface::BackendSurfaceAccess access, + const GrFlushInfo&, const GrPrepareForExternalIORequests&) override; + + // Ends and submits the current command buffer to the queue and then creates a new command + // buffer and begins it. If sync is set to kForce_SyncQueue, the function will wait for all + // work in the queue to finish before returning. If this GrVkGpu object has any semaphores in + // fSemaphoreToSignal, we will add those signal semaphores to the submission of this command + // buffer. If this GrVkGpu object has any semaphores in fSemaphoresToWaitOn, we will add those + // wait semaphores to the submission of this command buffer. + void submitCommandBuffer(SyncQueue sync, GrGpuFinishedProc finishedProc = nullptr, + GrGpuFinishedContext finishedContext = nullptr); + + void copySurfaceAsCopyImage(GrSurface* dst, GrSurface* src, GrVkImage* dstImage, + GrVkImage* srcImage, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + void copySurfaceAsBlit(GrSurface* dst, GrSurface* src, GrVkImage* dstImage, GrVkImage* srcImage, + const SkIRect& srcRect, const SkIPoint& dstPoint); + + void copySurfaceAsResolve(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + // helpers for onCreateTexture and writeTexturePixels + bool uploadTexDataLinear(GrVkTexture* tex, int left, int top, int width, int height, + GrColorType colorType, const void* data, size_t rowBytes); + bool uploadTexDataOptimal(GrVkTexture* tex, int left, int top, int width, int height, + GrColorType colorType, const GrMipLevel texels[], int mipLevelCount); + bool uploadTexDataCompressed(GrVkTexture* tex, int left, int top, int width, int height, + SkImage::CompressionType, const void* data); + void resolveImage(GrSurface* dst, GrVkRenderTarget* src, const SkIRect& srcRect, + const SkIPoint& dstPoint); + + bool createVkImageForBackendSurface(VkFormat vkFormat, int w, int h, bool texturable, + bool renderable, GrMipMapped mipMapped, + const SkPixmap srcData[], int numMipLevels, + const SkColor4f* color, GrVkImageInfo* info, + GrProtected isProtected); + + sk_sp fInterface; + sk_sp fMemoryAllocator; + sk_sp fVkCaps; + + VkInstance fInstance; + VkPhysicalDevice fPhysicalDevice; + VkDevice fDevice; + VkQueue fQueue; // Must be Graphics queue + uint32_t fQueueIndex; + + // Created by GrVkGpu + GrVkResourceProvider fResourceProvider; + + GrVkCommandPool* fCmdPool; + + // just a raw pointer; object's lifespan is managed by fCmdPool + GrVkPrimaryCommandBuffer* fCurrentCmdBuffer; + + SkSTArray<1, GrVkSemaphore::Resource*> fSemaphoresToWaitOn; + SkSTArray<1, GrVkSemaphore::Resource*> fSemaphoresToSignal; + + SkTArray> fDrawables; + + VkPhysicalDeviceProperties fPhysDevProps; + VkPhysicalDeviceMemoryProperties fPhysDevMemProps; + + // compiler used for compiling sksl into spirv. We only want to create the compiler once since + // there is significant overhead to the first compile of any compiler. + SkSL::Compiler* fCompiler; + + // We need a bool to track whether or not we've already disconnected all the gpu resources from + // vulkan context. + bool fDisconnected; + + GrProtected fProtectedContext; + + std::unique_ptr fCachedOpsRenderPass; + + typedef GrGpu INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkImage.cpp b/gfx/skia/skia/src/gpu/vk/GrVkImage.cpp new file mode 100644 index 0000000000..2b05d00e41 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkImage.cpp @@ -0,0 +1,331 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImage.h" +#include "src/gpu/vk/GrVkMemory.h" +#include "src/gpu/vk/GrVkTexture.h" +#include "src/gpu/vk/GrVkUtil.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +VkPipelineStageFlags GrVkImage::LayoutToPipelineSrcStageFlags(const VkImageLayout layout) { + if (VK_IMAGE_LAYOUT_GENERAL == layout) { + return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout || + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } else if (VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout || + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + } else if (VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) { + return VK_PIPELINE_STAGE_HOST_BIT; + } else if (VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout) { + return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } + + SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout); + return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; +} + +VkAccessFlags GrVkImage::LayoutToSrcAccessMask(const VkImageLayout layout) { + // Currently we assume we will never being doing any explict shader writes (this doesn't include + // color attachment or depth/stencil writes). So we will ignore the + // VK_MEMORY_OUTPUT_SHADER_WRITE_BIT. + + // We can only directly access the host memory if we are in preinitialized or general layout, + // and the image is linear. + // TODO: Add check for linear here so we are not always adding host to general, and we should + // only be in preinitialized if we are linear + VkAccessFlags flags = 0; + if (VK_IMAGE_LAYOUT_GENERAL == layout) { + flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_HOST_READ_BIT; + } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) { + flags = VK_ACCESS_HOST_WRITE_BIT; + } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout) { + flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + } else if (VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout) { + flags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + } else if (VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) { + flags = VK_ACCESS_TRANSFER_WRITE_BIT; + } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout || + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout || + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout) { + // There are no writes that need to be made available + flags = 0; + } + return flags; +} + +VkImageAspectFlags vk_format_to_aspect_flags(VkFormat format) { + switch (format) { + case VK_FORMAT_S8_UINT: + return VK_IMAGE_ASPECT_STENCIL_BIT; + case VK_FORMAT_D24_UNORM_S8_UINT: // fallthrough + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + default: + return VK_IMAGE_ASPECT_COLOR_BIT; + } +} + +void GrVkImage::setImageLayout(const GrVkGpu* gpu, VkImageLayout newLayout, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, bool releaseFamilyQueue) { + SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED != newLayout && + VK_IMAGE_LAYOUT_PREINITIALIZED != newLayout); + VkImageLayout currentLayout = this->currentLayout(); + + if (releaseFamilyQueue && fInfo.fCurrentQueueFamily == fInitialQueueFamily && + newLayout == currentLayout) { + // We never transfered the image to this queue and we are releasing it so don't do anything. + return; + } + + // If the old and new layout are the same and the layout is a read only layout, there is no need + // to put in a barrier unless we also need to switch queues. + if (newLayout == currentLayout && !releaseFamilyQueue && + (fInfo.fCurrentQueueFamily == VK_QUEUE_FAMILY_IGNORED || + fInfo.fCurrentQueueFamily == gpu->queueIndex()) && + (VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == currentLayout || + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == currentLayout || + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == currentLayout)) { + return; + } + + VkAccessFlags srcAccessMask = GrVkImage::LayoutToSrcAccessMask(currentLayout); + VkPipelineStageFlags srcStageMask = GrVkImage::LayoutToPipelineSrcStageFlags(currentLayout); + + VkImageAspectFlags aspectFlags = vk_format_to_aspect_flags(fInfo.fFormat); + + uint32_t srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + uint32_t dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + if (fInfo.fCurrentQueueFamily != VK_QUEUE_FAMILY_IGNORED && + gpu->queueIndex() != fInfo.fCurrentQueueFamily) { + // The image still is owned by its original queue family and we need to transfer it into + // ours. + SkASSERT(!releaseFamilyQueue); + SkASSERT(fInfo.fCurrentQueueFamily == fInitialQueueFamily); + + srcQueueFamilyIndex = fInfo.fCurrentQueueFamily; + dstQueueFamilyIndex = gpu->queueIndex(); + fInfo.fCurrentQueueFamily = gpu->queueIndex(); + } else if (releaseFamilyQueue) { + // We are releasing the image so we must transfer the image back to its original queue + // family. + srcQueueFamilyIndex = fInfo.fCurrentQueueFamily; + dstQueueFamilyIndex = fInitialQueueFamily; + fInfo.fCurrentQueueFamily = fInitialQueueFamily; + } + + VkImageMemoryBarrier imageMemoryBarrier = { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType + nullptr, // pNext + srcAccessMask, // srcAccessMask + dstAccessMask, // dstAccessMask + currentLayout, // oldLayout + newLayout, // newLayout + srcQueueFamilyIndex, // srcQueueFamilyIndex + dstQueueFamilyIndex, // dstQueueFamilyIndex + fInfo.fImage, // image + { aspectFlags, 0, fInfo.fLevelCount, 0, 1 } // subresourceRange + }; + + gpu->addImageMemoryBarrier(this->resource(), srcStageMask, dstStageMask, byRegion, + &imageMemoryBarrier); + + this->updateImageLayout(newLayout); +} + +bool GrVkImage::InitImageInfo(const GrVkGpu* gpu, const ImageDesc& imageDesc, GrVkImageInfo* info) { + if (0 == imageDesc.fWidth || 0 == imageDesc.fHeight) { + return false; + } + if ((imageDesc.fIsProtected == GrProtected::kYes) && !gpu->vkCaps().supportsProtectedMemory()) { + return false; + } + VkImage image = VK_NULL_HANDLE; + GrVkAlloc alloc; + + bool isLinear = VK_IMAGE_TILING_LINEAR == imageDesc.fImageTiling; + VkImageLayout initialLayout = isLinear ? VK_IMAGE_LAYOUT_PREINITIALIZED + : VK_IMAGE_LAYOUT_UNDEFINED; + + // Create Image + VkSampleCountFlagBits vkSamples; + if (!GrSampleCountToVkSampleCount(imageDesc.fSamples, &vkSamples)) { + return false; + } + + SkASSERT(VK_IMAGE_TILING_OPTIMAL == imageDesc.fImageTiling || + VK_SAMPLE_COUNT_1_BIT == vkSamples); + + VkImageCreateFlags createflags = 0; + if (imageDesc.fIsProtected == GrProtected::kYes || gpu->protectedContext()) { + createflags |= VK_IMAGE_CREATE_PROTECTED_BIT; + } + const VkImageCreateInfo imageCreateInfo = { + VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType + nullptr, // pNext + createflags, // VkImageCreateFlags + imageDesc.fImageType, // VkImageType + imageDesc.fFormat, // VkFormat + { imageDesc.fWidth, imageDesc.fHeight, 1 }, // VkExtent3D + imageDesc.fLevels, // mipLevels + 1, // arrayLayers + vkSamples, // samples + imageDesc.fImageTiling, // VkImageTiling + imageDesc.fUsageFlags, // VkImageUsageFlags + VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode + 0, // queueFamilyCount + 0, // pQueueFamilyIndices + initialLayout // initialLayout + }; + + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), CreateImage(gpu->device(), &imageCreateInfo, nullptr, + &image)); + + if (!GrVkMemory::AllocAndBindImageMemory(gpu, image, isLinear, &alloc)) { + VK_CALL(gpu, DestroyImage(gpu->device(), image, nullptr)); + return false; + } + + info->fImage = image; + info->fAlloc = alloc; + info->fImageTiling = imageDesc.fImageTiling; + info->fImageLayout = initialLayout; + info->fFormat = imageDesc.fFormat; + info->fLevelCount = imageDesc.fLevels; + info->fCurrentQueueFamily = VK_QUEUE_FAMILY_IGNORED; + info->fProtected = + (createflags & VK_IMAGE_CREATE_PROTECTED_BIT) ? GrProtected::kYes : GrProtected::kNo; + return true; +} + +void GrVkImage::DestroyImageInfo(const GrVkGpu* gpu, GrVkImageInfo* info) { + VK_CALL(gpu, DestroyImage(gpu->device(), info->fImage, nullptr)); + bool isLinear = VK_IMAGE_TILING_LINEAR == info->fImageTiling; + GrVkMemory::FreeImageMemory(gpu, isLinear, info->fAlloc); +} + +GrVkImage::~GrVkImage() { + // should have been released or abandoned first + SkASSERT(!fResource); +} + +void GrVkImage::prepareForPresent(GrVkGpu* gpu) { + VkImageLayout layout = this->currentLayout(); + if (fInitialQueueFamily != VK_QUEUE_FAMILY_EXTERNAL && + fInitialQueueFamily != VK_QUEUE_FAMILY_FOREIGN_EXT) { + if (gpu->vkCaps().supportsSwapchain()) { + layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + } + } + this->setImageLayout(gpu, layout, 0, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, false, true); +} + +void GrVkImage::prepareForExternal(GrVkGpu* gpu) { + this->setImageLayout(gpu, this->currentLayout(), 0, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, false, + true); +} + +void GrVkImage::releaseImage(GrVkGpu* gpu) { + if (fInfo.fCurrentQueueFamily != fInitialQueueFamily) { + // The Vulkan spec is vague on what to put for the dstStageMask here. The spec for image + // memory barrier says the dstStageMask must not be zero. However, in the spec when it talks + // about family queue transfers it says the dstStageMask is ignored and should be set to + // zero. Assuming it really is ignored we set it to VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT here + // since it makes the Vulkan validation layers happy. + this->setImageLayout(gpu, this->currentLayout(), 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + false, true); + } + if (fResource) { + fResource->removeOwningTexture(); + fResource->unref(gpu); + fResource = nullptr; + } +} + +void GrVkImage::abandonImage() { + if (fResource) { + fResource->removeOwningTexture(); + fResource->unrefAndAbandon(); + fResource = nullptr; + } +} + +void GrVkImage::setResourceRelease(sk_sp releaseHelper) { + SkASSERT(fResource); + // Forward the release proc on to GrVkImage::Resource + fResource->setRelease(std::move(releaseHelper)); +} + +void GrVkImage::Resource::freeGPUData(GrVkGpu* gpu) const { + this->invokeReleaseProc(); + VK_CALL(gpu, DestroyImage(gpu->device(), fImage, nullptr)); + bool isLinear = (VK_IMAGE_TILING_LINEAR == fImageTiling); + GrVkMemory::FreeImageMemory(gpu, isLinear, fAlloc); +} + +void GrVkImage::Resource::addIdleProc(GrVkTexture* owningTexture, + sk_sp idleProc) const { + SkASSERT(!fOwningTexture || fOwningTexture == owningTexture); + fOwningTexture = owningTexture; + fIdleProcs.push_back(std::move(idleProc)); +} + +int GrVkImage::Resource::idleProcCnt() const { return fIdleProcs.count(); } + +sk_sp GrVkImage::Resource::idleProc(int i) const { return fIdleProcs[i]; } + +void GrVkImage::Resource::resetIdleProcs() const { fIdleProcs.reset(); } + +void GrVkImage::Resource::removeOwningTexture() const { fOwningTexture = nullptr; } + +void GrVkImage::Resource::notifyAddedToCommandBuffer() const { ++fNumCommandBufferOwners; } + +void GrVkImage::Resource::notifyRemovedFromCommandBuffer() const { + SkASSERT(fNumCommandBufferOwners); + if (--fNumCommandBufferOwners || !fIdleProcs.count()) { + return; + } + if (fOwningTexture) { + if (fOwningTexture->resourcePriv().hasRef()) { + // Wait for the texture to become idle in the cache to call the procs. + return; + } + fOwningTexture->callIdleProcsOnBehalfOfResource(); + } else { + fIdleProcs.reset(); + } +} + +void GrVkImage::BorrowedResource::freeGPUData(GrVkGpu* gpu) const { + this->invokeReleaseProc(); +} + +void GrVkImage::BorrowedResource::abandonGPUData() const { + this->invokeReleaseProc(); +} + +#if GR_TEST_UTILS +void GrVkImage::setCurrentQueueFamilyToGraphicsQueue(GrVkGpu* gpu) { + fInfo.fCurrentQueueFamily = gpu->queueIndex(); +} +#endif + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkImage.h b/gfx/skia/skia/src/gpu/vk/GrVkImage.h new file mode 100644 index 0000000000..8f448639c6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkImage.h @@ -0,0 +1,259 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkImage_DEFINED +#define GrVkImage_DEFINED + +#include "include/core/SkTypes.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrTexture.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/vk/GrVkImageLayout.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkGpu; +class GrVkTexture; + +class GrVkImage : SkNoncopyable { +private: + class Resource; + +public: + GrVkImage(const GrVkImageInfo& info, sk_sp layout, + GrBackendObjectOwnership ownership, bool forSecondaryCB = false) + : fInfo(info) + , fInitialQueueFamily(info.fCurrentQueueFamily) + , fLayout(std::move(layout)) + , fIsBorrowed(GrBackendObjectOwnership::kBorrowed == ownership) { + SkASSERT(fLayout->getImageLayout() == fInfo.fImageLayout); + if (forSecondaryCB) { + fResource = nullptr; + } else if (fIsBorrowed) { + fResource = new BorrowedResource(info.fImage, info.fAlloc, info.fImageTiling); + } else { + SkASSERT(VK_NULL_HANDLE != info.fAlloc.fMemory); + fResource = new Resource(info.fImage, info.fAlloc, info.fImageTiling); + } + } + virtual ~GrVkImage(); + + VkImage image() const { + // Should only be called when we have a real fResource object, i.e. never when being used as + // a RT in an external secondary command buffer. + SkASSERT(fResource); + return fInfo.fImage; + } + const GrVkAlloc& alloc() const { + // Should only be called when we have a real fResource object, i.e. never when being used as + // a RT in an external secondary command buffer. + SkASSERT(fResource); + return fInfo.fAlloc; + } + VkFormat imageFormat() const { return fInfo.fFormat; } + GrBackendFormat getBackendFormat() const { + if (fResource && this->ycbcrConversionInfo().isValid()) { + SkASSERT(this->imageFormat() == this->ycbcrConversionInfo().fFormat); + return GrBackendFormat::MakeVk(this->ycbcrConversionInfo()); + } + SkASSERT(this->imageFormat() != VK_FORMAT_UNDEFINED); + return GrBackendFormat::MakeVk(this->imageFormat()); + } + uint32_t mipLevels() const { return fInfo.fLevelCount; } + const GrVkYcbcrConversionInfo& ycbcrConversionInfo() const { + // Should only be called when we have a real fResource object, i.e. never when being used as + // a RT in an external secondary command buffer. + SkASSERT(fResource); + return fInfo.fYcbcrConversionInfo; + } + const Resource* resource() const { + SkASSERT(fResource); + return fResource; + } + bool isLinearTiled() const { + // Should only be called when we have a real fResource object, i.e. never when being used as + // a RT in an external secondary command buffer. + SkASSERT(fResource); + return SkToBool(VK_IMAGE_TILING_LINEAR == fInfo.fImageTiling); + } + bool isBorrowed() const { return fIsBorrowed; } + + sk_sp grVkImageLayout() const { return fLayout; } + + VkImageLayout currentLayout() const { + return fLayout->getImageLayout(); + } + + void setImageLayout(const GrVkGpu* gpu, + VkImageLayout newLayout, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + bool releaseFamilyQueue = false); + + // Returns the image to its original queue family and changes the layout to present if the queue + // family is not external or foreign. + void prepareForPresent(GrVkGpu* gpu); + + // Returns the image to its original queue family + void prepareForExternal(GrVkGpu* gpu); + + // This simply updates our tracking of the image layout and does not actually do any gpu work. + // This is only used for mip map generation where we are manually changing the layouts as we + // blit each layer, and then at the end need to update our tracking. + void updateImageLayout(VkImageLayout newLayout) { + // Should only be called when we have a real fResource object, i.e. never when being used as + // a RT in an external secondary command buffer. + SkASSERT(fResource); + fLayout->setImageLayout(newLayout); + } + + struct ImageDesc { + VkImageType fImageType; + VkFormat fFormat; + uint32_t fWidth; + uint32_t fHeight; + uint32_t fLevels; + uint32_t fSamples; + VkImageTiling fImageTiling; + VkImageUsageFlags fUsageFlags; + VkFlags fMemProps; + GrProtected fIsProtected; + + ImageDesc() + : fImageType(VK_IMAGE_TYPE_2D) + , fFormat(VK_FORMAT_UNDEFINED) + , fWidth(0) + , fHeight(0) + , fLevels(1) + , fSamples(1) + , fImageTiling(VK_IMAGE_TILING_OPTIMAL) + , fUsageFlags(0) + , fMemProps(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + , fIsProtected(GrProtected::kNo) {} + }; + + static bool InitImageInfo(const GrVkGpu* gpu, const ImageDesc& imageDesc, GrVkImageInfo*); + // Destroys the internal VkImage and VkDeviceMemory in the GrVkImageInfo + static void DestroyImageInfo(const GrVkGpu* gpu, GrVkImageInfo*); + + // These match the definitions in SkImage, for whence they came + typedef void* ReleaseCtx; + typedef void (*ReleaseProc)(ReleaseCtx); + + void setResourceRelease(sk_sp releaseHelper); + + // Helpers to use for setting the layout of the VkImage + static VkPipelineStageFlags LayoutToPipelineSrcStageFlags(const VkImageLayout layout); + static VkAccessFlags LayoutToSrcAccessMask(const VkImageLayout layout); + +#if GR_TEST_UTILS + void setCurrentQueueFamilyToGraphicsQueue(GrVkGpu* gpu); +#endif + +protected: + void releaseImage(GrVkGpu* gpu); + void abandonImage(); + bool hasResource() const { return fResource; } + + GrVkImageInfo fInfo; + uint32_t fInitialQueueFamily; + sk_sp fLayout; + bool fIsBorrowed; + +private: + class Resource : public GrVkResource { + public: + Resource() + : fImage(VK_NULL_HANDLE) { + fAlloc.fMemory = VK_NULL_HANDLE; + fAlloc.fOffset = 0; + } + + Resource(VkImage image, const GrVkAlloc& alloc, VkImageTiling tiling) + : fImage(image) + , fAlloc(alloc) + , fImageTiling(tiling) {} + + ~Resource() override { + SkASSERT(!fReleaseHelper); + } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkImage: %d (%d refs)\n", fImage, this->getRefCnt()); + } +#endif + void setRelease(sk_sp releaseHelper) { + fReleaseHelper = std::move(releaseHelper); + } + + /** + * These are used to coordinate calling the "finished" idle procs between the GrVkTexture + * and the Resource. If the GrVkTexture becomes purgeable and if there are no command + * buffers referring to the Resource then it calls the procs. Otherwise, the Resource calls + * them when the last command buffer reference goes away and the GrVkTexture is purgeable. + */ + void addIdleProc(GrVkTexture*, sk_sp) const; + int idleProcCnt() const; + sk_sp idleProc(int) const; + void resetIdleProcs() const; + void removeOwningTexture() const; + + /** + * We track how many outstanding references this Resource has in command buffers and + * when the count reaches zero we call the idle proc. + */ + void notifyAddedToCommandBuffer() const override; + void notifyRemovedFromCommandBuffer() const override; + bool isOwnedByCommandBuffer() const { return fNumCommandBufferOwners > 0; } + + protected: + mutable sk_sp fReleaseHelper; + + void invokeReleaseProc() const { + if (fReleaseHelper) { + // Depending on the ref count of fReleaseHelper this may or may not actually trigger + // the ReleaseProc to be called. + fReleaseHelper.reset(); + } + } + + private: + void freeGPUData(GrVkGpu* gpu) const override; + void abandonGPUData() const override { + this->invokeReleaseProc(); + SkASSERT(!fReleaseHelper); + } + + VkImage fImage; + GrVkAlloc fAlloc; + VkImageTiling fImageTiling; + mutable int fNumCommandBufferOwners = 0; + mutable SkTArray> fIdleProcs; + mutable GrVkTexture* fOwningTexture = nullptr; + + typedef GrVkResource INHERITED; + }; + + // for wrapped textures + class BorrowedResource : public Resource { + public: + BorrowedResource(VkImage image, const GrVkAlloc& alloc, VkImageTiling tiling) + : Resource(image, alloc, tiling) { + } + private: + void freeGPUData(GrVkGpu* gpu) const override; + void abandonGPUData() const override; + }; + + Resource* fResource; + + friend class GrVkRenderTarget; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkImageLayout.h b/gfx/skia/skia/src/gpu/vk/GrVkImageLayout.h new file mode 100644 index 0000000000..154c21fb36 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkImageLayout.h @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkImageLayout_DEFINED +#define GrVkImageLayout_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/gpu/vk/GrVkTypes.h" + +class GrVkImageLayout : public SkRefCnt { +public: + GrVkImageLayout(VkImageLayout layout) : fLayout(layout) {} + + void setImageLayout(VkImageLayout layout) { + // Defaulting to use std::memory_order_seq_cst + fLayout.store(layout); + } + + VkImageLayout getImageLayout() const { + // Defaulting to use std::memory_order_seq_cst + return fLayout.load(); + } + +private: + std::atomic fLayout; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkImageView.cpp b/gfx/skia/skia/src/gpu/vk/GrVkImageView.cpp new file mode 100644 index 0000000000..ce7b1c5458 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkImageView.cpp @@ -0,0 +1,77 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkSamplerYcbcrConversion.h" +#include "src/gpu/vk/GrVkUtil.h" + +const GrVkImageView* GrVkImageView::Create(GrVkGpu* gpu, VkImage image, VkFormat format, + Type viewType, uint32_t miplevels, + const GrVkYcbcrConversionInfo& ycbcrInfo) { + + void* pNext = nullptr; + VkSamplerYcbcrConversionInfo conversionInfo; + GrVkSamplerYcbcrConversion* ycbcrConversion = nullptr; + + if (ycbcrInfo.isValid()) { + SkASSERT(gpu->vkCaps().supportsYcbcrConversion() && format == ycbcrInfo.fFormat); + + ycbcrConversion = + gpu->resourceProvider().findOrCreateCompatibleSamplerYcbcrConversion(ycbcrInfo); + if (!ycbcrConversion) { + return nullptr; + } + + conversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO; + conversionInfo.pNext = nullptr; + conversionInfo.conversion = ycbcrConversion->ycbcrConversion(); + pNext = &conversionInfo; + } + + VkImageView imageView; + // Create the VkImageView + VkImageViewCreateInfo viewInfo = { + VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // sType + pNext, // pNext + 0, // flags + image, // image + VK_IMAGE_VIEW_TYPE_2D, // viewType + format, // format + { VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY }, // components + { VK_IMAGE_ASPECT_COLOR_BIT, 0, miplevels, 0, 1 }, // subresourceRange + }; + if (kStencil_Type == viewType) { + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + } + + VkResult err = GR_VK_CALL(gpu->vkInterface(), CreateImageView(gpu->device(), &viewInfo, + nullptr, &imageView)); + if (err) { + return nullptr; + } + + return new GrVkImageView(imageView, ycbcrConversion); +} + +void GrVkImageView::freeGPUData(GrVkGpu* gpu) const { + GR_VK_CALL(gpu->vkInterface(), DestroyImageView(gpu->device(), fImageView, nullptr)); + + if (fYcbcrConversion) { + fYcbcrConversion->unref(gpu); + } +} + +void GrVkImageView::abandonGPUData() const { + if (fYcbcrConversion) { + fYcbcrConversion->unrefAndAbandon(); + } +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkImageView.h b/gfx/skia/skia/src/gpu/vk/GrVkImageView.h new file mode 100644 index 0000000000..12f3b21820 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkImageView.h @@ -0,0 +1,53 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkImageView_DEFINED +#define GrVkImageView_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrVkSamplerYcbcrConversion; +struct GrVkYcbcrConversionInfo; + +class GrVkImageView : public GrVkResource { +public: + enum Type { + kColor_Type, + kStencil_Type + }; + + static const GrVkImageView* Create(GrVkGpu* gpu, VkImage image, VkFormat format, + Type viewType, uint32_t miplevels, + const GrVkYcbcrConversionInfo& ycbcrInfo); + + VkImageView imageView() const { return fImageView; } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkImageView: %d (%d refs)\n", fImageView, this->getRefCnt()); + } +#endif + +private: + GrVkImageView(VkImageView imageView, GrVkSamplerYcbcrConversion* ycbcrConversion) + : INHERITED(), fImageView(imageView), fYcbcrConversion(ycbcrConversion) {} + + GrVkImageView(const GrVkImageView&); + GrVkImageView& operator=(const GrVkImageView&); + + void freeGPUData(GrVkGpu* gpu) const override; + void abandonGPUData() const override; + + VkImageView fImageView; + GrVkSamplerYcbcrConversion* fYcbcrConversion; + + typedef GrVkResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.cpp new file mode 100644 index 0000000000..c8dfd94ffd --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkIndexBuffer.h" + +GrVkIndexBuffer::GrVkIndexBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkBuffer::Resource* bufferResource) + : INHERITED(gpu, desc.fSizeInBytes, GrGpuBufferType::kIndex, + desc.fDynamic ? kDynamic_GrAccessPattern : kStatic_GrAccessPattern) + , GrVkBuffer(desc, bufferResource) { + this->registerWithCache(SkBudgeted::kYes); +} + +sk_sp GrVkIndexBuffer::Make(GrVkGpu* gpu, size_t size, bool dynamic) { + GrVkBuffer::Desc desc; + desc.fDynamic = gpu->protectedContext() ? true : dynamic; + desc.fType = GrVkBuffer::kIndex_Type; + desc.fSizeInBytes = size; + + const GrVkBuffer::Resource* bufferResource = GrVkBuffer::Create(gpu, desc); + if (!bufferResource) { + return nullptr; + } + + + GrVkIndexBuffer* buffer = new GrVkIndexBuffer(gpu, desc, bufferResource); + if (!buffer) { + bufferResource->unref(gpu); + } + return sk_sp(buffer); +} + +void GrVkIndexBuffer::onRelease() { + if (!this->wasDestroyed()) { + this->vkRelease(this->getVkGpu()); + } + + INHERITED::onRelease(); +} + +void GrVkIndexBuffer::onAbandon() { + this->vkAbandon(); + INHERITED::onAbandon(); +} + +void GrVkIndexBuffer::onMap() { this->GrGpuBuffer::fMapPtr = this->vkMap(this->getVkGpu()); } + +void GrVkIndexBuffer::onUnmap() { this->vkUnmap(this->getVkGpu()); } + +bool GrVkIndexBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) { + return this->vkUpdateData(this->getVkGpu(), src, srcSizeInBytes); +} + +GrVkGpu* GrVkIndexBuffer::getVkGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.h new file mode 100644 index 0000000000..bc29d2b1a7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkIndexBuffer.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkIndexBuffer_DEFINED +#define GrVkIndexBuffer_DEFINED + +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/vk/GrVkBuffer.h" + +class GrVkGpu; + +class GrVkIndexBuffer : public GrGpuBuffer, public GrVkBuffer { +public: + static sk_sp Make(GrVkGpu* gpu, size_t size, bool dynamic); + +protected: + void onAbandon() override; + void onRelease() override; + +private: + GrVkIndexBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkBuffer::Resource* resource); + + void onMap() override; + void onUnmap() override; + bool onUpdateData(const void* src, size_t srcSizeInBytes) override; + + GrVkGpu* getVkGpu() const; + + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkInterface.cpp b/gfx/skia/skia/src/gpu/vk/GrVkInterface.cpp new file mode 100644 index 0000000000..acb7bcb82c --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkInterface.cpp @@ -0,0 +1,483 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/gpu/vk/GrVkBackendContext.h" +#include "include/gpu/vk/GrVkExtensions.h" +#include "src/gpu/vk/GrVkInterface.h" +#include "src/gpu/vk/GrVkUtil.h" + +#define ACQUIRE_PROC(name, instance, device) \ + fFunctions.f##name = reinterpret_cast(getProc("vk" #name, instance, device)) + +#define ACQUIRE_PROC_SUFFIX(name, suffix, instance, device) \ + fFunctions.f##name = \ + reinterpret_cast(getProc("vk" #name #suffix, instance, device)) + +GrVkInterface::GrVkInterface(GrVkGetProc getProc, + VkInstance instance, + VkDevice device, + uint32_t instanceVersion, + uint32_t physicalDeviceVersion, + const GrVkExtensions* extensions) { + if (getProc == nullptr) { + return; + } + // Global/Loader Procs. + ACQUIRE_PROC(CreateInstance, VK_NULL_HANDLE, VK_NULL_HANDLE); + ACQUIRE_PROC(EnumerateInstanceExtensionProperties, VK_NULL_HANDLE, VK_NULL_HANDLE); + ACQUIRE_PROC(EnumerateInstanceLayerProperties, VK_NULL_HANDLE, VK_NULL_HANDLE); + + // Instance Procs. + ACQUIRE_PROC(EnumeratePhysicalDevices, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceFeatures, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceFormatProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceImageFormatProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceQueueFamilyProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceMemoryProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceSparseImageFormatProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(DestroyInstance, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(CreateDevice, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(DestroyDevice, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(EnumerateDeviceExtensionProperties, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(EnumerateDeviceLayerProperties, instance, VK_NULL_HANDLE); + + // Device Procs. + ACQUIRE_PROC(GetDeviceQueue, VK_NULL_HANDLE, device); + ACQUIRE_PROC(QueueSubmit, VK_NULL_HANDLE, device); + ACQUIRE_PROC(QueueWaitIdle, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DeviceWaitIdle, VK_NULL_HANDLE, device); + ACQUIRE_PROC(AllocateMemory, VK_NULL_HANDLE, device); + ACQUIRE_PROC(FreeMemory, VK_NULL_HANDLE, device); + ACQUIRE_PROC(MapMemory, VK_NULL_HANDLE, device); + ACQUIRE_PROC(UnmapMemory, VK_NULL_HANDLE, device); + ACQUIRE_PROC(FlushMappedMemoryRanges, VK_NULL_HANDLE, device); + ACQUIRE_PROC(InvalidateMappedMemoryRanges, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetDeviceMemoryCommitment, VK_NULL_HANDLE, device); + ACQUIRE_PROC(BindBufferMemory, VK_NULL_HANDLE, device); + ACQUIRE_PROC(BindImageMemory, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetBufferMemoryRequirements, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetImageMemoryRequirements, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetImageSparseMemoryRequirements, VK_NULL_HANDLE, device); + ACQUIRE_PROC(QueueBindSparse, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateFence, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyFence, VK_NULL_HANDLE, device); + ACQUIRE_PROC(ResetFences, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetFenceStatus, VK_NULL_HANDLE, device); + ACQUIRE_PROC(WaitForFences, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateSemaphore, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroySemaphore, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateEvent, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyEvent, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetEventStatus, VK_NULL_HANDLE, device); + ACQUIRE_PROC(SetEvent, VK_NULL_HANDLE, device); + ACQUIRE_PROC(ResetEvent, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateQueryPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyQueryPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetQueryPoolResults, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateBufferView, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyBufferView, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetImageSubresourceLayout, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateImageView, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyImageView, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateShaderModule, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyShaderModule, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreatePipelineCache, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyPipelineCache, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetPipelineCacheData, VK_NULL_HANDLE, device); + ACQUIRE_PROC(MergePipelineCaches, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateGraphicsPipelines, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateComputePipelines, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyPipeline, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreatePipelineLayout, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyPipelineLayout, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateSampler, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroySampler, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateDescriptorSetLayout, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyDescriptorSetLayout, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateDescriptorPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyDescriptorPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(ResetDescriptorPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(AllocateDescriptorSets, VK_NULL_HANDLE, device); + ACQUIRE_PROC(FreeDescriptorSets, VK_NULL_HANDLE, device); + ACQUIRE_PROC(UpdateDescriptorSets, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateFramebuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyFramebuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateRenderPass, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyRenderPass, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetRenderAreaGranularity, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CreateCommandPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroyCommandPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(ResetCommandPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(AllocateCommandBuffers, VK_NULL_HANDLE, device); + ACQUIRE_PROC(FreeCommandBuffers, VK_NULL_HANDLE, device); + ACQUIRE_PROC(BeginCommandBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(EndCommandBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(ResetCommandBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBindPipeline, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetViewport, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetScissor, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetLineWidth, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetDepthBias, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetBlendConstants, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetDepthBounds, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetStencilCompareMask, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetStencilWriteMask, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetStencilReference, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBindDescriptorSets, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBindIndexBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBindVertexBuffers, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdDraw, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdDrawIndexed, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdDrawIndirect, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdDrawIndexedIndirect, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdDispatch, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdDispatchIndirect, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdCopyBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdCopyImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBlitImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdCopyBufferToImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdCopyImageToBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdUpdateBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdFillBuffer, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdClearColorImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdClearDepthStencilImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdClearAttachments, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdResolveImage, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdSetEvent, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdResetEvent, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdWaitEvents, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdPipelineBarrier, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBeginQuery, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdEndQuery, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdResetQueryPool, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdWriteTimestamp, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdCopyQueryPoolResults, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdPushConstants, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdBeginRenderPass, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdNextSubpass, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdEndRenderPass, VK_NULL_HANDLE, device); + ACQUIRE_PROC(CmdExecuteCommands, VK_NULL_HANDLE, device); + + // Functions for VK_KHR_get_physical_device_properties2 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(GetPhysicalDeviceFeatures2, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceProperties2, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceFormatProperties2, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceImageFormatProperties2, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceQueueFamilyProperties2, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceMemoryProperties2, instance, VK_NULL_HANDLE); + ACQUIRE_PROC(GetPhysicalDeviceSparseImageFormatProperties2, instance, VK_NULL_HANDLE); + } else if (extensions->hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + 1)) { + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceFeatures2, KHR, instance, VK_NULL_HANDLE); + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceProperties2, KHR, instance, VK_NULL_HANDLE); + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceFormatProperties2, KHR, instance, VK_NULL_HANDLE); + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceImageFormatProperties2, KHR, instance, VK_NULL_HANDLE); + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceQueueFamilyProperties2, KHR, instance, VK_NULL_HANDLE); + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceMemoryProperties2, KHR, instance, VK_NULL_HANDLE); + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceSparseImageFormatProperties2, KHR, instance, + VK_NULL_HANDLE); + } + + // Functions for VK_KHR_get_memory_requirements2 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(GetImageMemoryRequirements2, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetBufferMemoryRequirements2, VK_NULL_HANDLE, device); + ACQUIRE_PROC(GetImageSparseMemoryRequirements2, VK_NULL_HANDLE, device); + } else if (extensions->hasExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1)) { + ACQUIRE_PROC_SUFFIX(GetImageMemoryRequirements2, KHR, VK_NULL_HANDLE, device); + ACQUIRE_PROC_SUFFIX(GetBufferMemoryRequirements2, KHR, VK_NULL_HANDLE, device); + ACQUIRE_PROC_SUFFIX(GetImageSparseMemoryRequirements2, KHR, VK_NULL_HANDLE, device); + } + + // Functions for VK_KHR_bind_memory2 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(BindBufferMemory2, VK_NULL_HANDLE, device); + ACQUIRE_PROC(BindImageMemory2, VK_NULL_HANDLE, device); + } else if (extensions->hasExtension(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, 1)) { + ACQUIRE_PROC_SUFFIX(BindBufferMemory2, KHR, VK_NULL_HANDLE, device); + ACQUIRE_PROC_SUFFIX(BindImageMemory2, KHR, VK_NULL_HANDLE, device); + } + + // Functions for VK_KHR_maintenance1 or vulkan 1.1 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(TrimCommandPool, VK_NULL_HANDLE, device); + } else if (extensions->hasExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME, 1)) { + ACQUIRE_PROC_SUFFIX(TrimCommandPool, KHR, VK_NULL_HANDLE, device); + } + + // Functions for VK_KHR_maintenance3 or vulkan 1.1 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(GetDescriptorSetLayoutSupport, VK_NULL_HANDLE, device); + } else if (extensions->hasExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME, 1)) { + ACQUIRE_PROC_SUFFIX(GetDescriptorSetLayoutSupport, KHR, VK_NULL_HANDLE, device); + } + + // Functions for VK_KHR_external_memory_capabilities + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(GetPhysicalDeviceExternalBufferProperties, instance, VK_NULL_HANDLE); + } else if (extensions->hasExtension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1)) { + ACQUIRE_PROC_SUFFIX(GetPhysicalDeviceExternalBufferProperties, KHR, instance, + VK_NULL_HANDLE); + } + + // Functions for VK_KHR_sampler_ycbcr_conversion + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) { + ACQUIRE_PROC(CreateSamplerYcbcrConversion, VK_NULL_HANDLE, device); + ACQUIRE_PROC(DestroySamplerYcbcrConversion, VK_NULL_HANDLE, device); + } else if (extensions->hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1)) { + ACQUIRE_PROC_SUFFIX(CreateSamplerYcbcrConversion, KHR, VK_NULL_HANDLE, device); + ACQUIRE_PROC_SUFFIX(DestroySamplerYcbcrConversion, KHR, VK_NULL_HANDLE, device); + } + +#ifdef SK_BUILD_FOR_ANDROID + // Functions for VK_ANDROID_external_memory_android_hardware_buffer + if (extensions->hasExtension( + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, 2)) { + ACQUIRE_PROC_SUFFIX(GetAndroidHardwareBufferProperties, ANDROID, VK_NULL_HANDLE, device); + ACQUIRE_PROC_SUFFIX(GetMemoryAndroidHardwareBuffer, ANDROID, VK_NULL_HANDLE, device); + } +#endif + +} + +#ifdef SK_DEBUG + static int kIsDebug = 1; +#else + static int kIsDebug = 0; +#endif + +#define RETURN_FALSE_INTERFACE \ + if (kIsDebug) { SkDebugf("%s:%d GrVkInterface::validate() failed.\n", __FILE__, __LINE__); } \ + return false; + +bool GrVkInterface::validate(uint32_t instanceVersion, uint32_t physicalDeviceVersion, + const GrVkExtensions* extensions) const { + // functions that are always required + if (nullptr == fFunctions.fCreateInstance || + nullptr == fFunctions.fDestroyInstance || + nullptr == fFunctions.fEnumeratePhysicalDevices || + nullptr == fFunctions.fGetPhysicalDeviceFeatures || + nullptr == fFunctions.fGetPhysicalDeviceFormatProperties || + nullptr == fFunctions.fGetPhysicalDeviceImageFormatProperties || + nullptr == fFunctions.fGetPhysicalDeviceProperties || + nullptr == fFunctions.fGetPhysicalDeviceQueueFamilyProperties || + nullptr == fFunctions.fGetPhysicalDeviceMemoryProperties || + nullptr == fFunctions.fCreateDevice || + nullptr == fFunctions.fDestroyDevice || + nullptr == fFunctions.fEnumerateInstanceExtensionProperties || + nullptr == fFunctions.fEnumerateDeviceExtensionProperties || + nullptr == fFunctions.fEnumerateInstanceLayerProperties || + nullptr == fFunctions.fEnumerateDeviceLayerProperties || + nullptr == fFunctions.fGetDeviceQueue || + nullptr == fFunctions.fQueueSubmit || + nullptr == fFunctions.fQueueWaitIdle || + nullptr == fFunctions.fDeviceWaitIdle || + nullptr == fFunctions.fAllocateMemory || + nullptr == fFunctions.fFreeMemory || + nullptr == fFunctions.fMapMemory || + nullptr == fFunctions.fUnmapMemory || + nullptr == fFunctions.fFlushMappedMemoryRanges || + nullptr == fFunctions.fInvalidateMappedMemoryRanges || + nullptr == fFunctions.fGetDeviceMemoryCommitment || + nullptr == fFunctions.fBindBufferMemory || + nullptr == fFunctions.fBindImageMemory || + nullptr == fFunctions.fGetBufferMemoryRequirements || + nullptr == fFunctions.fGetImageMemoryRequirements || + nullptr == fFunctions.fGetImageSparseMemoryRequirements || + nullptr == fFunctions.fGetPhysicalDeviceSparseImageFormatProperties || + nullptr == fFunctions.fQueueBindSparse || + nullptr == fFunctions.fCreateFence || + nullptr == fFunctions.fDestroyFence || + nullptr == fFunctions.fResetFences || + nullptr == fFunctions.fGetFenceStatus || + nullptr == fFunctions.fWaitForFences || + nullptr == fFunctions.fCreateSemaphore || + nullptr == fFunctions.fDestroySemaphore || + nullptr == fFunctions.fCreateEvent || + nullptr == fFunctions.fDestroyEvent || + nullptr == fFunctions.fGetEventStatus || + nullptr == fFunctions.fSetEvent || + nullptr == fFunctions.fResetEvent || + nullptr == fFunctions.fCreateQueryPool || + nullptr == fFunctions.fDestroyQueryPool || + nullptr == fFunctions.fGetQueryPoolResults || + nullptr == fFunctions.fCreateBuffer || + nullptr == fFunctions.fDestroyBuffer || + nullptr == fFunctions.fCreateBufferView || + nullptr == fFunctions.fDestroyBufferView || + nullptr == fFunctions.fCreateImage || + nullptr == fFunctions.fDestroyImage || + nullptr == fFunctions.fGetImageSubresourceLayout || + nullptr == fFunctions.fCreateImageView || + nullptr == fFunctions.fDestroyImageView || + nullptr == fFunctions.fCreateShaderModule || + nullptr == fFunctions.fDestroyShaderModule || + nullptr == fFunctions.fCreatePipelineCache || + nullptr == fFunctions.fDestroyPipelineCache || + nullptr == fFunctions.fGetPipelineCacheData || + nullptr == fFunctions.fMergePipelineCaches || + nullptr == fFunctions.fCreateGraphicsPipelines || + nullptr == fFunctions.fCreateComputePipelines || + nullptr == fFunctions.fDestroyPipeline || + nullptr == fFunctions.fCreatePipelineLayout || + nullptr == fFunctions.fDestroyPipelineLayout || + nullptr == fFunctions.fCreateSampler || + nullptr == fFunctions.fDestroySampler || + nullptr == fFunctions.fCreateDescriptorSetLayout || + nullptr == fFunctions.fDestroyDescriptorSetLayout || + nullptr == fFunctions.fCreateDescriptorPool || + nullptr == fFunctions.fDestroyDescriptorPool || + nullptr == fFunctions.fResetDescriptorPool || + nullptr == fFunctions.fAllocateDescriptorSets || + nullptr == fFunctions.fFreeDescriptorSets || + nullptr == fFunctions.fUpdateDescriptorSets || + nullptr == fFunctions.fCreateFramebuffer || + nullptr == fFunctions.fDestroyFramebuffer || + nullptr == fFunctions.fCreateRenderPass || + nullptr == fFunctions.fDestroyRenderPass || + nullptr == fFunctions.fGetRenderAreaGranularity || + nullptr == fFunctions.fCreateCommandPool || + nullptr == fFunctions.fDestroyCommandPool || + nullptr == fFunctions.fResetCommandPool || + nullptr == fFunctions.fAllocateCommandBuffers || + nullptr == fFunctions.fFreeCommandBuffers || + nullptr == fFunctions.fBeginCommandBuffer || + nullptr == fFunctions.fEndCommandBuffer || + nullptr == fFunctions.fResetCommandBuffer || + nullptr == fFunctions.fCmdBindPipeline || + nullptr == fFunctions.fCmdSetViewport || + nullptr == fFunctions.fCmdSetScissor || + nullptr == fFunctions.fCmdSetLineWidth || + nullptr == fFunctions.fCmdSetDepthBias || + nullptr == fFunctions.fCmdSetBlendConstants || + nullptr == fFunctions.fCmdSetDepthBounds || + nullptr == fFunctions.fCmdSetStencilCompareMask || + nullptr == fFunctions.fCmdSetStencilWriteMask || + nullptr == fFunctions.fCmdSetStencilReference || + nullptr == fFunctions.fCmdBindDescriptorSets || + nullptr == fFunctions.fCmdBindIndexBuffer || + nullptr == fFunctions.fCmdBindVertexBuffers || + nullptr == fFunctions.fCmdDraw || + nullptr == fFunctions.fCmdDrawIndexed || + nullptr == fFunctions.fCmdDrawIndirect || + nullptr == fFunctions.fCmdDrawIndexedIndirect || + nullptr == fFunctions.fCmdDispatch || + nullptr == fFunctions.fCmdDispatchIndirect || + nullptr == fFunctions.fCmdCopyBuffer || + nullptr == fFunctions.fCmdCopyImage || + nullptr == fFunctions.fCmdBlitImage || + nullptr == fFunctions.fCmdCopyBufferToImage || + nullptr == fFunctions.fCmdCopyImageToBuffer || + nullptr == fFunctions.fCmdUpdateBuffer || + nullptr == fFunctions.fCmdFillBuffer || + nullptr == fFunctions.fCmdClearColorImage || + nullptr == fFunctions.fCmdClearDepthStencilImage || + nullptr == fFunctions.fCmdClearAttachments || + nullptr == fFunctions.fCmdResolveImage || + nullptr == fFunctions.fCmdSetEvent || + nullptr == fFunctions.fCmdResetEvent || + nullptr == fFunctions.fCmdWaitEvents || + nullptr == fFunctions.fCmdPipelineBarrier || + nullptr == fFunctions.fCmdBeginQuery || + nullptr == fFunctions.fCmdEndQuery || + nullptr == fFunctions.fCmdResetQueryPool || + nullptr == fFunctions.fCmdWriteTimestamp || + nullptr == fFunctions.fCmdCopyQueryPoolResults || + nullptr == fFunctions.fCmdPushConstants || + nullptr == fFunctions.fCmdBeginRenderPass || + nullptr == fFunctions.fCmdNextSubpass || + nullptr == fFunctions.fCmdEndRenderPass || + nullptr == fFunctions.fCmdExecuteCommands) { + RETURN_FALSE_INTERFACE + } + + // Functions for VK_KHR_get_physical_device_properties2 or vulkan 1.1 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fGetPhysicalDeviceFeatures2 || + nullptr == fFunctions.fGetPhysicalDeviceProperties2 || + nullptr == fFunctions.fGetPhysicalDeviceFormatProperties2 || + nullptr == fFunctions.fGetPhysicalDeviceImageFormatProperties2 || + nullptr == fFunctions.fGetPhysicalDeviceQueueFamilyProperties2 || + nullptr == fFunctions.fGetPhysicalDeviceMemoryProperties2 || + nullptr == fFunctions.fGetPhysicalDeviceSparseImageFormatProperties2) { + RETURN_FALSE_INTERFACE + } + } + + // Functions for VK_KHR_get_memory_requirements2 or vulkan 1.1 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fGetImageMemoryRequirements2 || + nullptr == fFunctions.fGetBufferMemoryRequirements2 || + nullptr == fFunctions.fGetImageSparseMemoryRequirements2) { + RETURN_FALSE_INTERFACE + } + } + + // Functions for VK_KHR_bind_memory2 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fBindBufferMemory2 || + nullptr == fFunctions.fBindImageMemory2) { + RETURN_FALSE_INTERFACE + } + } + + // Functions for VK_KHR_maintenance1 or vulkan 1.1 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fTrimCommandPool) { + RETURN_FALSE_INTERFACE + } + } + + // Functions for VK_KHR_maintenance3 or vulkan 1.1 + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fGetDescriptorSetLayoutSupport) { + RETURN_FALSE_INTERFACE + } + } + + // Functions for VK_KHR_external_memory_capabilities + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fGetPhysicalDeviceExternalBufferProperties) { + RETURN_FALSE_INTERFACE + } + } + + // Functions for VK_KHR_sampler_ycbcr_conversion + if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || + extensions->hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1)) { + if (nullptr == fFunctions.fCreateSamplerYcbcrConversion || + nullptr == fFunctions.fDestroySamplerYcbcrConversion) { + RETURN_FALSE_INTERFACE + } + } + +#ifdef SK_BUILD_FOR_ANDROID + // Functions for VK_ANDROID_external_memory_android_hardware_buffer + if (extensions->hasExtension( + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, 2)) { + if (nullptr == fFunctions.fGetAndroidHardwareBufferProperties || + nullptr == fFunctions.fGetMemoryAndroidHardwareBuffer) { + RETURN_FALSE_INTERFACE + } + } +#endif + + return true; +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkInterface.h b/gfx/skia/skia/src/gpu/vk/GrVkInterface.h new file mode 100644 index 0000000000..53360ba9c4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkInterface.h @@ -0,0 +1,235 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkInterface_DEFINED +#define GrVkInterface_DEFINED + +#include "include/core/SkRefCnt.h" + +#include "include/gpu/vk/GrVkBackendContext.h" +#include "include/gpu/vk/GrVkTypes.h" + +class GrVkExtensions; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * GrContext uses the following interface to make all calls into Vulkan. When a + * GrContext is created it is given a GrVkInterface. All functions that should be + * available based on the Vulkan's version must be non-NULL or GrContext creation + * will fail. This can be tested with the validate() method. + */ +struct GrVkInterface : public SkRefCnt { +private: + // simple wrapper class that exists only to initialize a pointer to NULL + template class VkPtr { + public: + VkPtr() : fPtr(NULL) {} + VkPtr operator=(FNPTR_TYPE ptr) { fPtr = ptr; return *this; } + operator FNPTR_TYPE() const { return fPtr; } + private: + FNPTR_TYPE fPtr; + }; + + typedef SkRefCnt INHERITED; + +public: + GrVkInterface(GrVkGetProc getProc, + VkInstance instance, + VkDevice device, + uint32_t instanceVersion, + uint32_t physicalDeviceVersion, + const GrVkExtensions*); + + // Validates that the GrVkInterface supports its advertised standard. This means the necessary + // function pointers have been initialized for Vulkan version. + bool validate(uint32_t instanceVersion, uint32_t physicalDeviceVersion, + const GrVkExtensions*) const; + + /** + * The function pointers are in a struct so that we can have a compiler generated assignment + * operator. + */ + struct Functions { + VkPtr fCreateInstance; + VkPtr fDestroyInstance; + VkPtr fEnumeratePhysicalDevices; + VkPtr fGetPhysicalDeviceFeatures; + VkPtr fGetPhysicalDeviceFormatProperties; + VkPtr fGetPhysicalDeviceImageFormatProperties; + VkPtr fGetPhysicalDeviceProperties; + VkPtr fGetPhysicalDeviceQueueFamilyProperties; + VkPtr fGetPhysicalDeviceMemoryProperties; + VkPtr fCreateDevice; + VkPtr fDestroyDevice; + VkPtr fEnumerateInstanceExtensionProperties; + VkPtr fEnumerateDeviceExtensionProperties; + VkPtr fEnumerateInstanceLayerProperties; + VkPtr fEnumerateDeviceLayerProperties; + VkPtr fGetDeviceQueue; + VkPtr fQueueSubmit; + VkPtr fQueueWaitIdle; + VkPtr fDeviceWaitIdle; + VkPtr fAllocateMemory; + VkPtr fFreeMemory; + VkPtr fMapMemory; + VkPtr fUnmapMemory; + VkPtr fFlushMappedMemoryRanges; + VkPtr fInvalidateMappedMemoryRanges; + VkPtr fGetDeviceMemoryCommitment; + VkPtr fBindBufferMemory; + VkPtr fBindImageMemory; + VkPtr fGetBufferMemoryRequirements; + VkPtr fGetImageMemoryRequirements; + VkPtr fGetImageSparseMemoryRequirements; + VkPtr fGetPhysicalDeviceSparseImageFormatProperties; + VkPtr fQueueBindSparse; + VkPtr fCreateFence; + VkPtr fDestroyFence; + VkPtr fResetFences; + VkPtr fGetFenceStatus; + VkPtr fWaitForFences; + VkPtr fCreateSemaphore; + VkPtr fDestroySemaphore; + VkPtr fCreateEvent; + VkPtr fDestroyEvent; + VkPtr fGetEventStatus; + VkPtr fSetEvent; + VkPtr fResetEvent; + VkPtr fCreateQueryPool; + VkPtr fDestroyQueryPool; + VkPtr fGetQueryPoolResults; + VkPtr fCreateBuffer; + VkPtr fDestroyBuffer; + VkPtr fCreateBufferView; + VkPtr fDestroyBufferView; + VkPtr fCreateImage; + VkPtr fDestroyImage; + VkPtr fGetImageSubresourceLayout; + VkPtr fCreateImageView; + VkPtr fDestroyImageView; + VkPtr fCreateShaderModule; + VkPtr fDestroyShaderModule; + VkPtr fCreatePipelineCache; + VkPtr fDestroyPipelineCache; + VkPtr fGetPipelineCacheData; + VkPtr fMergePipelineCaches; + VkPtr fCreateGraphicsPipelines; + VkPtr fCreateComputePipelines; + VkPtr fDestroyPipeline; + VkPtr fCreatePipelineLayout; + VkPtr fDestroyPipelineLayout; + VkPtr fCreateSampler; + VkPtr fDestroySampler; + VkPtr fCreateDescriptorSetLayout; + VkPtr fDestroyDescriptorSetLayout; + VkPtr fCreateDescriptorPool; + VkPtr fDestroyDescriptorPool; + VkPtr fResetDescriptorPool; + VkPtr fAllocateDescriptorSets; + VkPtr fFreeDescriptorSets; + VkPtr fUpdateDescriptorSets; + VkPtr fCreateFramebuffer; + VkPtr fDestroyFramebuffer; + VkPtr fCreateRenderPass; + VkPtr fDestroyRenderPass; + VkPtr fGetRenderAreaGranularity; + VkPtr fCreateCommandPool; + VkPtr fDestroyCommandPool; + VkPtr fResetCommandPool; + VkPtr fAllocateCommandBuffers; + VkPtr fFreeCommandBuffers; + VkPtr fBeginCommandBuffer; + VkPtr fEndCommandBuffer; + VkPtr fResetCommandBuffer; + VkPtr fCmdBindPipeline; + VkPtr fCmdSetViewport; + VkPtr fCmdSetScissor; + VkPtr fCmdSetLineWidth; + VkPtr fCmdSetDepthBias; + VkPtr fCmdSetBlendConstants; + VkPtr fCmdSetDepthBounds; + VkPtr fCmdSetStencilCompareMask; + VkPtr fCmdSetStencilWriteMask; + VkPtr fCmdSetStencilReference; + VkPtr fCmdBindDescriptorSets; + VkPtr fCmdBindIndexBuffer; + VkPtr fCmdBindVertexBuffers; + VkPtr fCmdDraw; + VkPtr fCmdDrawIndexed; + VkPtr fCmdDrawIndirect; + VkPtr fCmdDrawIndexedIndirect; + VkPtr fCmdDispatch; + VkPtr fCmdDispatchIndirect; + VkPtr fCmdCopyBuffer; + VkPtr fCmdCopyImage; + VkPtr fCmdBlitImage; + VkPtr fCmdCopyBufferToImage; + VkPtr fCmdCopyImageToBuffer; + VkPtr fCmdUpdateBuffer; + VkPtr fCmdFillBuffer; + VkPtr fCmdClearColorImage; + VkPtr fCmdClearDepthStencilImage; + VkPtr fCmdClearAttachments; + VkPtr fCmdResolveImage; + VkPtr fCmdSetEvent; + VkPtr fCmdResetEvent; + VkPtr fCmdWaitEvents; + VkPtr fCmdPipelineBarrier; + VkPtr fCmdBeginQuery; + VkPtr fCmdEndQuery; + VkPtr fCmdResetQueryPool; + VkPtr fCmdWriteTimestamp; + VkPtr fCmdCopyQueryPoolResults; + VkPtr fCmdPushConstants; + VkPtr fCmdBeginRenderPass; + VkPtr fCmdNextSubpass; + VkPtr fCmdEndRenderPass; + VkPtr fCmdExecuteCommands; + + // Functions for VK_KHR_get_physical_device_properties2 or vulkan 1.1 + VkPtr fGetPhysicalDeviceFeatures2; + VkPtr fGetPhysicalDeviceProperties2; + VkPtr fGetPhysicalDeviceFormatProperties2; + VkPtr fGetPhysicalDeviceImageFormatProperties2; + VkPtr fGetPhysicalDeviceQueueFamilyProperties2; + VkPtr fGetPhysicalDeviceMemoryProperties2; + VkPtr fGetPhysicalDeviceSparseImageFormatProperties2; + + // Functions for VK_KHR_get_memory_requirements2 or vulkan 1.1 + VkPtr fGetImageMemoryRequirements2; + VkPtr fGetBufferMemoryRequirements2; + VkPtr fGetImageSparseMemoryRequirements2; + + //Functions for VK_KHR_bind_memory2 + VkPtr fBindBufferMemory2; + VkPtr fBindImageMemory2; + + // Functions for VK_KHR_maintenance1 or vulkan 1.1 + VkPtr fTrimCommandPool; + + // Functions for VK_KHR_maintenance3 or vulkan 1.1 + VkPtr fGetDescriptorSetLayoutSupport; + + // Functions for VK_KHR_external_memory_capabilities + VkPtr fGetPhysicalDeviceExternalBufferProperties; + + // Functions for YCBCRConversion + VkPtr fCreateSamplerYcbcrConversion; + VkPtr fDestroySamplerYcbcrConversion; + +#ifdef SK_BUILD_FOR_ANDROID + // Functions for VK_ANDROID_external_memory_android_hardware_buffer + VkPtr fGetAndroidHardwareBufferProperties; + VkPtr fGetMemoryAndroidHardwareBuffer; +#endif + + + } fFunctions; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkMemory.cpp b/gfx/skia/skia/src/gpu/vk/GrVkMemory.cpp new file mode 100644 index 0000000000..ed14a643ec --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkMemory.cpp @@ -0,0 +1,230 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkMemory.h" + +#include "include/gpu/vk/GrVkMemoryAllocator.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkUtil.h" + +using AllocationPropertyFlags = GrVkMemoryAllocator::AllocationPropertyFlags; +using BufferUsage = GrVkMemoryAllocator::BufferUsage; + +static BufferUsage get_buffer_usage(GrVkBuffer::Type type, bool dynamic) { + switch (type) { + case GrVkBuffer::kVertex_Type: // fall through + case GrVkBuffer::kIndex_Type: // fall through + case GrVkBuffer::kTexel_Type: + return dynamic ? BufferUsage::kCpuWritesGpuReads : BufferUsage::kGpuOnly; + case GrVkBuffer::kUniform_Type: + SkASSERT(dynamic); + return BufferUsage::kCpuWritesGpuReads; + case GrVkBuffer::kCopyRead_Type: // fall through + case GrVkBuffer::kCopyWrite_Type: + return BufferUsage::kCpuOnly; + } + SK_ABORT("Invalid GrVkBuffer::Type"); +} + +bool GrVkMemory::AllocAndBindBufferMemory(const GrVkGpu* gpu, + VkBuffer buffer, + GrVkBuffer::Type type, + bool dynamic, + GrVkAlloc* alloc) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + GrVkBackendMemory memory = 0; + + GrVkMemoryAllocator::BufferUsage usage = get_buffer_usage(type, dynamic); + + AllocationPropertyFlags propFlags; + if (usage == GrVkMemoryAllocator::BufferUsage::kCpuWritesGpuReads) { + // In general it is always fine (and often better) to keep buffers always mapped. + // TODO: According to AMDs guide for the VulkanMemoryAllocator they suggest there are two + // cases when keeping it mapped can hurt. The first is when running on Win7 or Win8 (Win 10 + // is fine). In general, by the time Vulkan ships it is probably less likely to be running + // on non Win10 or newer machines. The second use case is if running on an AMD card and you + // are using the special GPU local and host mappable memory. However, in general we don't + // pick this memory as we've found it slower than using the cached host visible memory. In + // the future if we find the need to special case either of these two issues we can add + // checks for them here. + propFlags = AllocationPropertyFlags::kPersistentlyMapped; + } else { + propFlags = AllocationPropertyFlags::kNone; + } + + if (!allocator->allocateMemoryForBuffer(buffer, usage, propFlags, &memory)) { + return false; + } + allocator->getAllocInfo(memory, alloc); + + // Bind buffer + VkResult err = GR_VK_CALL(gpu->vkInterface(), BindBufferMemory(gpu->device(), buffer, + alloc->fMemory, + alloc->fOffset)); + if (err) { + FreeBufferMemory(gpu, type, *alloc); + return false; + } + + return true; +} + +void GrVkMemory::FreeBufferMemory(const GrVkGpu* gpu, GrVkBuffer::Type type, + const GrVkAlloc& alloc) { + if (alloc.fBackendMemory) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + allocator->freeMemory(alloc.fBackendMemory); + } else { + GR_VK_CALL(gpu->vkInterface(), FreeMemory(gpu->device(), alloc.fMemory, nullptr)); + } +} + +const VkDeviceSize kMaxSmallImageSize = 256 * 1024; + +bool GrVkMemory::AllocAndBindImageMemory(const GrVkGpu* gpu, + VkImage image, + bool linearTiling, + GrVkAlloc* alloc) { + SkASSERT(!linearTiling); + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + GrVkBackendMemory memory = 0; + + VkMemoryRequirements memReqs; + GR_VK_CALL(gpu->vkInterface(), GetImageMemoryRequirements(gpu->device(), image, &memReqs)); + + AllocationPropertyFlags propFlags; + if (memReqs.size > kMaxSmallImageSize || + gpu->vkCaps().shouldAlwaysUseDedicatedImageMemory()) { + propFlags = AllocationPropertyFlags::kDedicatedAllocation; + } else { + propFlags = AllocationPropertyFlags::kNone; + } + + if (gpu->protectedContext()) { + propFlags |= AllocationPropertyFlags::kProtected; + } + + if (!allocator->allocateMemoryForImage(image, propFlags, &memory)) { + return false; + } + allocator->getAllocInfo(memory, alloc); + + // Bind buffer + VkResult err = GR_VK_CALL(gpu->vkInterface(), BindImageMemory(gpu->device(), image, + alloc->fMemory, alloc->fOffset)); + if (err) { + FreeImageMemory(gpu, linearTiling, *alloc); + return false; + } + + return true; +} + +void GrVkMemory::FreeImageMemory(const GrVkGpu* gpu, bool linearTiling, + const GrVkAlloc& alloc) { + if (alloc.fBackendMemory) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + allocator->freeMemory(alloc.fBackendMemory); + } else { + GR_VK_CALL(gpu->vkInterface(), FreeMemory(gpu->device(), alloc.fMemory, nullptr)); + } +} + +void* GrVkMemory::MapAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc) { + SkASSERT(GrVkAlloc::kMappable_Flag & alloc.fFlags); +#ifdef SK_DEBUG + if (alloc.fFlags & GrVkAlloc::kNoncoherent_Flag) { + VkDeviceSize alignment = gpu->physicalDeviceProperties().limits.nonCoherentAtomSize; + SkASSERT(0 == (alloc.fOffset & (alignment-1))); + SkASSERT(0 == (alloc.fSize & (alignment-1))); + } +#endif + if (alloc.fBackendMemory) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + return allocator->mapMemory(alloc.fBackendMemory); + } + + void* mapPtr; + VkResult err = GR_VK_CALL(gpu->vkInterface(), MapMemory(gpu->device(), alloc.fMemory, + alloc.fOffset, + alloc.fSize, 0, &mapPtr)); + if (err) { + mapPtr = nullptr; + } + return mapPtr; +} + +void GrVkMemory::UnmapAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc) { + if (alloc.fBackendMemory) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + allocator->unmapMemory(alloc.fBackendMemory); + } else { + GR_VK_CALL(gpu->vkInterface(), UnmapMemory(gpu->device(), alloc.fMemory)); + } +} + +void GrVkMemory::GetNonCoherentMappedMemoryRange(const GrVkAlloc& alloc, VkDeviceSize offset, + VkDeviceSize size, VkDeviceSize alignment, + VkMappedMemoryRange* range) { + SkASSERT(alloc.fFlags & GrVkAlloc::kNoncoherent_Flag); + offset = offset + alloc.fOffset; + VkDeviceSize offsetDiff = offset & (alignment -1); + offset = offset - offsetDiff; + size = (size + alignment - 1) & ~(alignment - 1); +#ifdef SK_DEBUG + SkASSERT(offset >= alloc.fOffset); + SkASSERT(offset + size <= alloc.fOffset + alloc.fSize); + SkASSERT(0 == (offset & (alignment-1))); + SkASSERT(size > 0); + SkASSERT(0 == (size & (alignment-1))); +#endif + + memset(range, 0, sizeof(VkMappedMemoryRange)); + range->sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range->memory = alloc.fMemory; + range->offset = offset; + range->size = size; +} + +void GrVkMemory::FlushMappedAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc, VkDeviceSize offset, + VkDeviceSize size) { + if (alloc.fFlags & GrVkAlloc::kNoncoherent_Flag) { + SkASSERT(offset == 0); + SkASSERT(size <= alloc.fSize); + if (alloc.fBackendMemory) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + allocator->flushMappedMemory(alloc.fBackendMemory, offset, size); + } else { + VkDeviceSize alignment = gpu->physicalDeviceProperties().limits.nonCoherentAtomSize; + VkMappedMemoryRange mappedMemoryRange; + GrVkMemory::GetNonCoherentMappedMemoryRange(alloc, offset, size, alignment, + &mappedMemoryRange); + GR_VK_CALL(gpu->vkInterface(), FlushMappedMemoryRanges(gpu->device(), 1, + &mappedMemoryRange)); + } + } +} + +void GrVkMemory::InvalidateMappedAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc, + VkDeviceSize offset, VkDeviceSize size) { + if (alloc.fFlags & GrVkAlloc::kNoncoherent_Flag) { + SkASSERT(offset == 0); + SkASSERT(size <= alloc.fSize); + if (alloc.fBackendMemory) { + GrVkMemoryAllocator* allocator = gpu->memoryAllocator(); + allocator->invalidateMappedMemory(alloc.fBackendMemory, offset, size); + } else { + VkDeviceSize alignment = gpu->physicalDeviceProperties().limits.nonCoherentAtomSize; + VkMappedMemoryRange mappedMemoryRange; + GrVkMemory::GetNonCoherentMappedMemoryRange(alloc, offset, size, alignment, + &mappedMemoryRange); + GR_VK_CALL(gpu->vkInterface(), InvalidateMappedMemoryRanges(gpu->device(), 1, + &mappedMemoryRange)); + } + } +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkMemory.h b/gfx/skia/skia/src/gpu/vk/GrVkMemory.h new file mode 100644 index 0000000000..7244da053c --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkMemory.h @@ -0,0 +1,56 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkMemory_DEFINED +#define GrVkMemory_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/SkTArray.h" +#include "src/gpu/vk/GrVkBuffer.h" + +class GrVkGpu; + +namespace GrVkMemory { + /** + * Allocates vulkan device memory and binds it to the gpu's device for the given object. + * Returns true if allocation succeeded. + */ + bool AllocAndBindBufferMemory(const GrVkGpu* gpu, + VkBuffer buffer, + GrVkBuffer::Type type, + bool dynamic, + GrVkAlloc* alloc); + void FreeBufferMemory(const GrVkGpu* gpu, GrVkBuffer::Type type, const GrVkAlloc& alloc); + + bool AllocAndBindImageMemory(const GrVkGpu* gpu, + VkImage image, + bool linearTiling, + GrVkAlloc* alloc); + void FreeImageMemory(const GrVkGpu* gpu, bool linearTiling, const GrVkAlloc& alloc); + + // Maps the entire GrVkAlloc and returns a pointer to the start of the allocation. Underneath + // the hood, we may map more than the range of the GrVkAlloc (e.g. the entire VkDeviceMemory), + // but the pointer returned will always be to the start of the GrVkAlloc. The caller should also + // never assume more than the GrVkAlloc block has been mapped. + void* MapAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc); + void UnmapAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc); + + // For the Flush and Invalidate calls, the offset should be relative to the GrVkAlloc. Thus this + // will often be 0. The client does not need to make sure the offset and size are aligned to the + // nonCoherentAtomSize, the internal calls will handle that. + void FlushMappedAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc, VkDeviceSize offset, + VkDeviceSize size); + void InvalidateMappedAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc, VkDeviceSize offset, + VkDeviceSize size); + + // Helper for aligning and setting VkMappedMemoryRange for flushing/invalidating noncoherent + // memory. + void GetNonCoherentMappedMemoryRange(const GrVkAlloc&, VkDeviceSize offset, VkDeviceSize size, + VkDeviceSize alignment, VkMappedMemoryRange*); +} + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.cpp b/gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.cpp new file mode 100644 index 0000000000..c58c412409 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.cpp @@ -0,0 +1,662 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkOpsRenderPass.h" + +#include "include/core/SkDrawable.h" +#include "include/core/SkRect.h" +#include "include/gpu/GrBackendDrawableInfo.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrFixedClip.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkCommandPool.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkPipeline.h" +#include "src/gpu/vk/GrVkRenderPass.h" +#include "src/gpu/vk/GrVkRenderTarget.h" +#include "src/gpu/vk/GrVkResourceProvider.h" +#include "src/gpu/vk/GrVkSemaphore.h" +#include "src/gpu/vk/GrVkTexture.h" + +///////////////////////////////////////////////////////////////////////////// + +void get_vk_load_store_ops(GrLoadOp loadOpIn, GrStoreOp storeOpIn, + VkAttachmentLoadOp* loadOp, VkAttachmentStoreOp* storeOp) { + switch (loadOpIn) { + case GrLoadOp::kLoad: + *loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case GrLoadOp::kClear: + *loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case GrLoadOp::kDiscard: + *loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + default: + SK_ABORT("Invalid LoadOp"); + *loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + } + + switch (storeOpIn) { + case GrStoreOp::kStore: + *storeOp = VK_ATTACHMENT_STORE_OP_STORE; + break; + case GrStoreOp::kDiscard: + *storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + break; + default: + SK_ABORT("Invalid StoreOp"); + *storeOp = VK_ATTACHMENT_STORE_OP_STORE; + } +} + +GrVkOpsRenderPass::GrVkOpsRenderPass(GrVkGpu* gpu) : fGpu(gpu) {} + +void GrVkOpsRenderPass::init(const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, + const SkPMColor4f& clearColor) { + + VkAttachmentLoadOp loadOp; + VkAttachmentStoreOp storeOp; + get_vk_load_store_ops(colorInfo.fLoadOp, colorInfo.fStoreOp, + &loadOp, &storeOp); + GrVkRenderPass::LoadStoreOps vkColorOps(loadOp, storeOp); + + get_vk_load_store_ops(stencilInfo.fLoadOp, stencilInfo.fStoreOp, + &loadOp, &storeOp); + GrVkRenderPass::LoadStoreOps vkStencilOps(loadOp, storeOp); + + GrVkRenderTarget* vkRT = static_cast(fRenderTarget); + GrVkImage* targetImage = vkRT->msaaImage() ? vkRT->msaaImage() : vkRT; + + // Change layout of our render target so it can be used as the color attachment. + // TODO: If we know that we will never be blending or loading the attachment we could drop the + // VK_ACCESS_COLOR_ATTACHMENT_READ_BIT. + targetImage->setImageLayout(fGpu, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + false); + + // If we are using a stencil attachment we also need to update its layout + if (GrStencilAttachment* stencil = fRenderTarget->renderTargetPriv().getStencilAttachment()) { + GrVkStencilAttachment* vkStencil = (GrVkStencilAttachment*)stencil; + // We need the write and read access bits since we may load and store the stencil. + // The initial load happens in the VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT so we + // wait there. + vkStencil->setImageLayout(fGpu, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + false); + } + + const GrVkResourceProvider::CompatibleRPHandle& rpHandle = vkRT->compatibleRenderPassHandle(); + if (rpHandle.isValid()) { + fCurrentRenderPass = fGpu->resourceProvider().findRenderPass(rpHandle, + vkColorOps, + vkStencilOps); + } else { + fCurrentRenderPass = fGpu->resourceProvider().findRenderPass(*vkRT, + vkColorOps, + vkStencilOps); + } + SkASSERT(fCurrentRenderPass); + + VkClearValue vkClearColor; + vkClearColor.color.float32[0] = clearColor[0]; + vkClearColor.color.float32[1] = clearColor[1]; + vkClearColor.color.float32[2] = clearColor[2]; + vkClearColor.color.float32[3] = clearColor[3]; + + if (!fGpu->vkCaps().preferPrimaryOverSecondaryCommandBuffers()) { + fCurrentSecondaryCommandBuffer = fGpu->cmdPool()->findOrCreateSecondaryCommandBuffer(fGpu); + fCurrentSecondaryCommandBuffer->begin(fGpu, vkRT->framebuffer(), fCurrentRenderPass); + } + + fGpu->beginRenderPass(fCurrentRenderPass, &vkClearColor, vkRT, fOrigin, fBounds, + SkToBool(fCurrentSecondaryCommandBuffer)); +} + +void GrVkOpsRenderPass::initWrapped() { + GrVkRenderTarget* vkRT = static_cast(fRenderTarget); + SkASSERT(vkRT->wrapsSecondaryCommandBuffer()); + fCurrentRenderPass = vkRT->externalRenderPass(); + SkASSERT(fCurrentRenderPass); + fCurrentRenderPass->ref(); + + fCurrentSecondaryCommandBuffer.reset( + GrVkSecondaryCommandBuffer::Create(vkRT->getExternalSecondaryCommandBuffer())); + fCurrentSecondaryCommandBuffer->begin(fGpu, nullptr, fCurrentRenderPass); +} + +GrVkOpsRenderPass::~GrVkOpsRenderPass() { + this->reset(); +} + +GrGpu* GrVkOpsRenderPass::gpu() { return fGpu; } + +GrVkCommandBuffer* GrVkOpsRenderPass::currentCommandBuffer() { + if (fCurrentSecondaryCommandBuffer) { + return fCurrentSecondaryCommandBuffer.get(); + } + return fGpu->currentCommandBuffer(); +} + +void GrVkOpsRenderPass::end() { + if (fCurrentSecondaryCommandBuffer) { + fCurrentSecondaryCommandBuffer->end(fGpu); + } +} + +void GrVkOpsRenderPass::submit() { + if (!fRenderTarget) { + return; + } + + // We don't want to actually submit the secondary command buffer if it is wrapped. + if (this->wrapsSecondaryCommandBuffer()) { + return; + } + + if (fCurrentSecondaryCommandBuffer) { + fGpu->submitSecondaryCommandBuffer(std::move(fCurrentSecondaryCommandBuffer)); + } + fGpu->endRenderPass(fRenderTarget, fOrigin, fBounds); +} + +void GrVkOpsRenderPass::set(GrRenderTarget* rt, GrSurfaceOrigin origin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, + const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, + const SkTArray& sampledProxies) { + SkASSERT(!fRenderTarget); + SkASSERT(fGpu == rt->getContext()->priv().getGpu()); + +#ifdef SK_DEBUG + fIsActive = true; +#endif + + this->INHERITED::set(rt, origin); + + for (int i = 0; i < sampledProxies.count(); ++i) { + if (sampledProxies[i]->isInstantiated()) { + GrVkTexture* vkTex = static_cast(sampledProxies[i]->peekTexture()); + SkASSERT(vkTex); + vkTex->setImageLayout( + fGpu, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, false); + } + } + + SkASSERT(bounds.isEmpty() || SkIRect::MakeWH(rt->width(), rt->height()).contains(bounds)); + fBounds = bounds; + + if (this->wrapsSecondaryCommandBuffer()) { + this->initWrapped(); + return; + } + + this->init(colorInfo, stencilInfo, colorInfo.fClearColor); +} + +void GrVkOpsRenderPass::reset() { + if (fCurrentSecondaryCommandBuffer) { + fCurrentSecondaryCommandBuffer.release()->recycle(fGpu); + } + if (fCurrentRenderPass) { + fCurrentRenderPass->unref(fGpu); + fCurrentRenderPass = nullptr; + } + fCurrentCBIsEmpty = true; + + fRenderTarget = nullptr; + +#ifdef SK_DEBUG + fIsActive = false; +#endif +} + +bool GrVkOpsRenderPass::wrapsSecondaryCommandBuffer() const { + GrVkRenderTarget* vkRT = static_cast(fRenderTarget); + return vkRT->wrapsSecondaryCommandBuffer(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkOpsRenderPass::insertEventMarker(const char* msg) { + // TODO: does Vulkan have a correlate? +} + +void GrVkOpsRenderPass::onClearStencilClip(const GrFixedClip& clip, bool insideStencilMask) { + SkASSERT(!clip.hasWindowRectangles()); + + GrStencilAttachment* sb = fRenderTarget->renderTargetPriv().getStencilAttachment(); + // this should only be called internally when we know we have a + // stencil buffer. + SkASSERT(sb); + int stencilBitCount = sb->bits(); + + // The contract with the callers does not guarantee that we preserve all bits in the stencil + // during this clear. Thus we will clear the entire stencil to the desired value. + + VkClearDepthStencilValue vkStencilColor; + memset(&vkStencilColor, 0, sizeof(VkClearDepthStencilValue)); + if (insideStencilMask) { + vkStencilColor.stencil = (1 << (stencilBitCount - 1)); + } else { + vkStencilColor.stencil = 0; + } + + VkClearRect clearRect; + // Flip rect if necessary + SkIRect vkRect; + if (!clip.scissorEnabled()) { + vkRect.setXYWH(0, 0, fRenderTarget->width(), fRenderTarget->height()); + } else if (kBottomLeft_GrSurfaceOrigin != fOrigin) { + vkRect = clip.scissorRect(); + } else { + const SkIRect& scissor = clip.scissorRect(); + vkRect.setLTRB(scissor.fLeft, fRenderTarget->height() - scissor.fBottom, + scissor.fRight, fRenderTarget->height() - scissor.fTop); + } + + clearRect.rect.offset = { vkRect.fLeft, vkRect.fTop }; + clearRect.rect.extent = { (uint32_t)vkRect.width(), (uint32_t)vkRect.height() }; + + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + uint32_t stencilIndex; + SkAssertResult(fCurrentRenderPass->stencilAttachmentIndex(&stencilIndex)); + + VkClearAttachment attachment; + attachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + attachment.colorAttachment = 0; // this value shouldn't matter + attachment.clearValue.depthStencil = vkStencilColor; + + this->currentCommandBuffer()->clearAttachments(fGpu, 1, &attachment, 1, &clearRect); + fCurrentCBIsEmpty = false; +} + +void GrVkOpsRenderPass::onClear(const GrFixedClip& clip, const SkPMColor4f& color) { + // parent class should never let us get here with no RT + SkASSERT(!clip.hasWindowRectangles()); + + VkClearColorValue vkColor = {{color.fR, color.fG, color.fB, color.fA}}; + + // If we end up in a situation where we are calling clear without a scissior then in general it + // means we missed an opportunity higher up the stack to set the load op to be a clear. However, + // there are situations where higher up we couldn't discard the previous ops and set a clear + // load op (e.g. if we needed to execute a wait op). Thus we also have the empty check here. + // TODO: Make the waitOp a RenderTask instead so we can clear out the GrOpsTask for a clear. We + // can then reenable this assert assuming we can't get messed up by a waitOp. + //SkASSERT(!fCurrentCBIsEmpty || clip.scissorEnabled()); + + // We always do a sub rect clear with clearAttachments since we are inside a render pass + VkClearRect clearRect; + // Flip rect if necessary + SkIRect vkRect; + if (!clip.scissorEnabled()) { + vkRect.setXYWH(0, 0, fRenderTarget->width(), fRenderTarget->height()); + } else if (kBottomLeft_GrSurfaceOrigin != fOrigin) { + vkRect = clip.scissorRect(); + } else { + const SkIRect& scissor = clip.scissorRect(); + vkRect.setLTRB(scissor.fLeft, fRenderTarget->height() - scissor.fBottom, + scissor.fRight, fRenderTarget->height() - scissor.fTop); + } + clearRect.rect.offset = { vkRect.fLeft, vkRect.fTop }; + clearRect.rect.extent = { (uint32_t)vkRect.width(), (uint32_t)vkRect.height() }; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + uint32_t colorIndex; + SkAssertResult(fCurrentRenderPass->colorAttachmentIndex(&colorIndex)); + + VkClearAttachment attachment; + attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + attachment.colorAttachment = colorIndex; + attachment.clearValue.color = vkColor; + + this->currentCommandBuffer()->clearAttachments(fGpu, 1, &attachment, 1, &clearRect); + fCurrentCBIsEmpty = false; + return; +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkOpsRenderPass::addAdditionalRenderPass(bool mustUseSecondaryCommandBuffer) { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + GrVkRenderTarget* vkRT = static_cast(fRenderTarget); + + GrVkRenderPass::LoadStoreOps vkColorOps(VK_ATTACHMENT_LOAD_OP_LOAD, + VK_ATTACHMENT_STORE_OP_STORE); + GrVkRenderPass::LoadStoreOps vkStencilOps(VK_ATTACHMENT_LOAD_OP_LOAD, + VK_ATTACHMENT_STORE_OP_STORE); + + const GrVkResourceProvider::CompatibleRPHandle& rpHandle = + vkRT->compatibleRenderPassHandle(); + SkASSERT(fCurrentRenderPass); + fCurrentRenderPass->unref(fGpu); + if (rpHandle.isValid()) { + fCurrentRenderPass = fGpu->resourceProvider().findRenderPass(rpHandle, + vkColorOps, + vkStencilOps); + } else { + fCurrentRenderPass = fGpu->resourceProvider().findRenderPass(*vkRT, + vkColorOps, + vkStencilOps); + } + SkASSERT(fCurrentRenderPass); + + VkClearValue vkClearColor; + memset(&vkClearColor, 0, sizeof(VkClearValue)); + + if (!fGpu->vkCaps().preferPrimaryOverSecondaryCommandBuffers() || + mustUseSecondaryCommandBuffer) { + fCurrentSecondaryCommandBuffer = fGpu->cmdPool()->findOrCreateSecondaryCommandBuffer(fGpu); + fCurrentSecondaryCommandBuffer->begin(fGpu, vkRT->framebuffer(), fCurrentRenderPass); + } + + // We use the same fBounds as the whole GrVkOpsRenderPass since we have no way of tracking the + // bounds in GrOpsTask for parts before and after inline uploads separately. + fGpu->beginRenderPass(fCurrentRenderPass, &vkClearColor, vkRT, fOrigin, fBounds, + SkToBool(fCurrentSecondaryCommandBuffer)); +} + +void GrVkOpsRenderPass::inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) { + if (fCurrentSecondaryCommandBuffer) { + fCurrentSecondaryCommandBuffer->end(fGpu); + fGpu->submitSecondaryCommandBuffer(std::move(fCurrentSecondaryCommandBuffer)); + } + fGpu->endRenderPass(fRenderTarget, fOrigin, fBounds); + + // We pass in true here to signal that after the upload we need to set the upload textures + // layout back to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + state->doUpload(upload, true); + + this->addAdditionalRenderPass(false); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkOpsRenderPass::bindGeometry(const GrGpuBuffer* indexBuffer, + const GrGpuBuffer* vertexBuffer, + const GrGpuBuffer* instanceBuffer) { + GrVkCommandBuffer* currCmdBuf = this->currentCommandBuffer(); + // There is no need to put any memory barriers to make sure host writes have finished here. + // When a command buffer is submitted to a queue, there is an implicit memory barrier that + // occurs for all host writes. Additionally, BufferMemoryBarriers are not allowed inside of + // an active RenderPass. + + // Here our vertex and instance inputs need to match the same 0-based bindings they were + // assigned in GrVkPipeline. That is, vertex first (if any) followed by instance. + uint32_t binding = 0; + + if (vertexBuffer) { + SkASSERT(vertexBuffer); + SkASSERT(!vertexBuffer->isMapped()); + + currCmdBuf->bindInputBuffer(fGpu, binding++, + static_cast(vertexBuffer)); + } + + if (instanceBuffer) { + SkASSERT(instanceBuffer); + SkASSERT(!instanceBuffer->isMapped()); + + currCmdBuf->bindInputBuffer(fGpu, binding++, + static_cast(instanceBuffer)); + } + if (indexBuffer) { + SkASSERT(indexBuffer); + SkASSERT(!indexBuffer->isMapped()); + + currCmdBuf->bindIndexBuffer(fGpu, static_cast(indexBuffer)); + } +} + +GrVkPipelineState* GrVkOpsRenderPass::prepareDrawState( + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + const SkIRect& renderPassScissorRect) { + GrVkCommandBuffer* currentCB = this->currentCommandBuffer(); + SkASSERT(fCurrentRenderPass); + + VkRenderPass compatibleRenderPass = fCurrentRenderPass->vkRenderPass(); + + GrVkPipelineState* pipelineState = + fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget, + programInfo, + primitiveType, + compatibleRenderPass); + if (!pipelineState) { + return pipelineState; + } + + pipelineState->bindPipeline(fGpu, currentCB); + + // Both the 'programInfo' and this renderPass have an origin. Since they come from the + // same place (i.e., the target renderTargetProxy) that had best agree. + SkASSERT(programInfo.origin() == fOrigin); + + pipelineState->setAndBindUniforms(fGpu, fRenderTarget, programInfo, currentCB); + + // Check whether we need to bind textures between each GrMesh. If not we can bind them all now. + if (!programInfo.hasDynamicPrimProcTextures()) { + auto proxies = programInfo.hasFixedPrimProcTextures() ? programInfo.fixedPrimProcTextures() + : nullptr; + pipelineState->setAndBindTextures(fGpu, programInfo.primProc(), programInfo.pipeline(), + proxies, currentCB); + } + + if (!programInfo.pipeline().isScissorEnabled()) { + GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin, + renderPassScissorRect); + } else if (!programInfo.hasDynamicScissors()) { + SkASSERT(programInfo.hasFixedScissor()); + + SkIRect combinedScissorRect; + if (!combinedScissorRect.intersect(renderPassScissorRect, programInfo.fixedScissor())) { + combinedScissorRect = SkIRect::MakeEmpty(); + } + GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin, + combinedScissorRect); + } + GrVkPipeline::SetDynamicViewportState(fGpu, currentCB, fRenderTarget); + GrVkPipeline::SetDynamicBlendConstantState(fGpu, currentCB, + programInfo.pipeline().outputSwizzle(), + programInfo.pipeline().getXferProcessor()); + + return pipelineState; +} + +#ifdef SK_DEBUG +void check_sampled_texture(GrTexture* tex, GrRenderTarget* rt, GrVkGpu* gpu) { + SkASSERT(!tex->isProtected() || (rt->isProtected() && gpu->protectedContext())); + GrVkTexture* vkTex = static_cast(tex); + SkASSERT(vkTex->currentLayout() == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +} +#endif + + +void GrVkOpsRenderPass::onDraw(const GrProgramInfo& programInfo, + const GrMesh meshes[], int meshCount, + const SkRect& bounds) { + + SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw + +#ifdef SK_DEBUG + if (programInfo.hasDynamicPrimProcTextures()) { + for (int m = 0; m < meshCount; ++m) { + auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m); + + for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) { + auto texture = dynamicPrimProcTextures[s]->peekTexture(); + check_sampled_texture(texture, fRenderTarget, fGpu); + } + } + } else if (programInfo.hasFixedPrimProcTextures()) { + auto fixedPrimProcTextures = programInfo.fixedPrimProcTextures(); + + for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) { + auto texture = fixedPrimProcTextures[s]->peekTexture(); + check_sampled_texture(texture, fRenderTarget, fGpu); + } + } + + GrFragmentProcessor::Iter iter(programInfo.pipeline()); + while (const GrFragmentProcessor* fp = iter.next()) { + for (int i = 0; i < fp->numTextureSamplers(); ++i) { + const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i); + check_sampled_texture(sampler.peekTexture(), fRenderTarget, fGpu); + } + } + if (GrTexture* dstTexture = programInfo.pipeline().peekDstTexture()) { + check_sampled_texture(dstTexture, fRenderTarget, fGpu); + } + + // Both the 'programInfo' and this renderPass have an origin. Since they come from the + // same place (i.e., the target renderTargetProxy) that had best agree. + SkASSERT(programInfo.origin() == fOrigin); +#endif + + SkRect scissorRect = SkRect::Make(fBounds); + SkIRect renderPassScissorRect = SkIRect::MakeEmpty(); + if (scissorRect.intersect(bounds)) { + scissorRect.roundOut(&renderPassScissorRect); + } + + GrPrimitiveType primitiveType = meshes[0].primitiveType(); + GrVkPipelineState* pipelineState = this->prepareDrawState(programInfo, primitiveType, + renderPassScissorRect); + if (!pipelineState) { + return; + } + + bool hasDynamicScissors = programInfo.hasDynamicScissors(); + bool hasDynamicTextures = programInfo.hasDynamicPrimProcTextures(); + + for (int i = 0; i < meshCount; ++i) { + const GrMesh& mesh = meshes[i]; + if (mesh.primitiveType() != primitiveType) { + SkDEBUGCODE(pipelineState = nullptr); + primitiveType = mesh.primitiveType(); + pipelineState = this->prepareDrawState(programInfo, primitiveType, + renderPassScissorRect); + if (!pipelineState) { + return; + } + } + + if (hasDynamicScissors) { + SkIRect combinedScissorRect; + if (!combinedScissorRect.intersect(renderPassScissorRect, + programInfo.dynamicScissor(i))) { + combinedScissorRect = SkIRect::MakeEmpty(); + } + GrVkPipeline::SetDynamicScissorRectState(fGpu, this->currentCommandBuffer(), + fRenderTarget, fOrigin, + combinedScissorRect); + } + if (hasDynamicTextures) { + auto meshProxies = programInfo.dynamicPrimProcTextures(i); + pipelineState->setAndBindTextures(fGpu, programInfo.primProc(), programInfo.pipeline(), + meshProxies, this->currentCommandBuffer()); + } + SkASSERT(pipelineState); + mesh.sendToGpu(this); + } + + fCurrentCBIsEmpty = false; +} + +void GrVkOpsRenderPass::sendInstancedMeshToGpu(GrPrimitiveType, + const GrBuffer* vertexBuffer, + int vertexCount, + int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, + int baseInstance) { + SkASSERT(!vertexBuffer || !vertexBuffer->isCpuBuffer()); + SkASSERT(!instanceBuffer || !instanceBuffer->isCpuBuffer()); + auto gpuVertexBuffer = static_cast(vertexBuffer); + auto gpuInstanceBuffer = static_cast(instanceBuffer); + this->bindGeometry(nullptr, gpuVertexBuffer, gpuInstanceBuffer); + this->currentCommandBuffer()->draw(fGpu, vertexCount, instanceCount, baseVertex, baseInstance); + fGpu->stats()->incNumDraws(); +} + +void GrVkOpsRenderPass::sendIndexedInstancedMeshToGpu(GrPrimitiveType, + const GrBuffer* indexBuffer, + int indexCount, + int baseIndex, + const GrBuffer* vertexBuffer, + int baseVertex, + const GrBuffer* instanceBuffer, + int instanceCount, + int baseInstance, + GrPrimitiveRestart restart) { + SkASSERT(restart == GrPrimitiveRestart::kNo); + SkASSERT(!vertexBuffer || !vertexBuffer->isCpuBuffer()); + SkASSERT(!instanceBuffer || !instanceBuffer->isCpuBuffer()); + SkASSERT(!indexBuffer->isCpuBuffer()); + auto gpuIndexxBuffer = static_cast(indexBuffer); + auto gpuVertexBuffer = static_cast(vertexBuffer); + auto gpuInstanceBuffer = static_cast(instanceBuffer); + this->bindGeometry(gpuIndexxBuffer, gpuVertexBuffer, gpuInstanceBuffer); + this->currentCommandBuffer()->drawIndexed(fGpu, indexCount, instanceCount, + baseIndex, baseVertex, baseInstance); + fGpu->stats()->incNumDraws(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkOpsRenderPass::executeDrawable(std::unique_ptr drawable) { + GrVkRenderTarget* target = static_cast(fRenderTarget); + + GrVkImage* targetImage = target->msaaImage() ? target->msaaImage() : target; + + VkRect2D bounds; + bounds.offset = { 0, 0 }; + bounds.extent = { 0, 0 }; + + if (!fCurrentSecondaryCommandBuffer) { + fGpu->endRenderPass(fRenderTarget, fOrigin, fBounds); + this->addAdditionalRenderPass(true); + } + SkASSERT(fCurrentSecondaryCommandBuffer); + + GrVkDrawableInfo vkInfo; + vkInfo.fSecondaryCommandBuffer = fCurrentSecondaryCommandBuffer->vkCommandBuffer(); + vkInfo.fCompatibleRenderPass = fCurrentRenderPass->vkRenderPass(); + SkAssertResult(fCurrentRenderPass->colorAttachmentIndex(&vkInfo.fColorAttachmentIndex)); + vkInfo.fFormat = targetImage->imageFormat(); + vkInfo.fDrawBounds = &bounds; +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + vkInfo.fImage = targetImage->image(); +#else + vkInfo.fImage = VK_NULL_HANDLE; +#endif //SK_BUILD_FOR_ANDROID_FRAMEWORK + + GrBackendDrawableInfo info(vkInfo); + + // After we draw into the command buffer via the drawable, cached state we have may be invalid. + this->currentCommandBuffer()->invalidateState(); + // Also assume that the drawable produced output. + fCurrentCBIsEmpty = false; + + drawable->draw(info); + fGpu->addDrawable(std::move(drawable)); +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.h b/gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.h new file mode 100644 index 0000000000..65d6b5b464 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkOpsRenderPass.h @@ -0,0 +1,131 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkOpsRenderPass_DEFINED +#define GrVkOpsRenderPass_DEFINED + +#include "src/gpu/GrOpsRenderPass.h" + +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrMesh.h" +#include "src/gpu/GrTRecorder.h" +#include "src/gpu/vk/GrVkPipelineState.h" + +class GrVkGpu; +class GrVkImage; +class GrVkRenderPass; +class GrVkRenderTarget; +class GrVkSecondaryCommandBuffer; + +class GrVkOpsRenderPass : public GrOpsRenderPass, private GrMesh::SendToGpuImpl { +public: + GrVkOpsRenderPass(GrVkGpu*); + + ~GrVkOpsRenderPass() override; + + void begin() override { } + void end() override; + + void insertEventMarker(const char*) override; + + void inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) override; + + void executeDrawable(std::unique_ptr) override; + + void set(GrRenderTarget*, GrSurfaceOrigin, const SkIRect& bounds, + const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkTArray& sampledProxies); + void reset(); + + void submit(); + +#ifdef SK_DEBUG + bool isActive() const { return fIsActive; } +#endif + +private: + void init(const GrOpsRenderPass::LoadAndStoreInfo&, + const GrOpsRenderPass::StencilLoadAndStoreInfo&, + const SkPMColor4f& clearColor); + + // Called instead of init when we are drawing to a render target that already wraps a secondary + // command buffer. + void initWrapped(); + + bool wrapsSecondaryCommandBuffer() const; + + GrGpu* gpu() override; + + GrVkCommandBuffer* currentCommandBuffer(); + + // Bind vertex and index buffers + void bindGeometry(const GrGpuBuffer* indexBuffer, + const GrGpuBuffer* vertexBuffer, + const GrGpuBuffer* instanceBuffer); + + GrVkPipelineState* prepareDrawState(const GrProgramInfo&, GrPrimitiveType, + const SkIRect& renderPassScissorRect); + + void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount, + const SkRect& bounds) override; + + // GrMesh::SendToGpuImpl methods. These issue the actual Vulkan draw commands. + // Marked final as a hint to the compiler to not use virtual dispatch. + void sendMeshToGpu(GrPrimitiveType primType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex) final { + this->sendInstancedMeshToGpu(primType, vertexBuffer, vertexCount, baseVertex, nullptr, 1, + 0); + } + + void sendIndexedMeshToGpu(GrPrimitiveType primType, const GrBuffer* indexBuffer, int indexCount, + int baseIndex, uint16_t /*minIndexValue*/, uint16_t /*maxIndexValue*/, + const GrBuffer* vertexBuffer, int baseVertex, + GrPrimitiveRestart restart) final { + SkASSERT(restart == GrPrimitiveRestart::kNo); + this->sendIndexedInstancedMeshToGpu(primType, indexBuffer, indexCount, baseIndex, + vertexBuffer, baseVertex, nullptr, 1, 0, + GrPrimitiveRestart::kNo); + } + + void sendInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* vertexBuffer, int vertexCount, + int baseVertex, const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance) final; + + void sendIndexedInstancedMeshToGpu(GrPrimitiveType, const GrBuffer* indexBuffer, int indexCount, + int baseIndex, const GrBuffer* vertexBuffer, int baseVertex, + const GrBuffer* instanceBuffer, int instanceCount, + int baseInstance, GrPrimitiveRestart) final; + + void onClear(const GrFixedClip&, const SkPMColor4f& color) override; + + void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) override; + + void addAdditionalRenderPass(bool mustUseSecondaryCommandBuffer); + + std::unique_ptr fCurrentSecondaryCommandBuffer; + const GrVkRenderPass* fCurrentRenderPass; + bool fCurrentCBIsEmpty = true; + SkIRect fBounds; + GrVkGpu* fGpu; + +#ifdef SK_DEBUG + // When we are actively recording into the GrVkOpsRenderPass we set this flag to true. This + // then allows us to assert that we never submit a primary command buffer to the queue while in + // a recording state. This is needed since when we submit to the queue we change command pools + // and may trigger the old one to be reset, but a recording GrVkOpsRenderPass may still have + // a outstanding secondary command buffer allocated from that pool that we'll try to access + // after the pool as been reset. + bool fIsActive = false; +#endif + + typedef GrOpsRenderPass INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipeline.cpp b/gfx/skia/skia/src/gpu/vk/GrVkPipeline.cpp new file mode 100644 index 0000000000..6868ff5bab --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipeline.cpp @@ -0,0 +1,642 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/GrGeometryProcessor.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkPipeline.h" +#include "src/gpu/vk/GrVkRenderTarget.h" +#include "src/gpu/vk/GrVkUtil.h" + +#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS) +#include +#endif + +static inline VkFormat attrib_type_to_vkformat(GrVertexAttribType type) { + switch (type) { + case kFloat_GrVertexAttribType: + return VK_FORMAT_R32_SFLOAT; + case kFloat2_GrVertexAttribType: + return VK_FORMAT_R32G32_SFLOAT; + case kFloat3_GrVertexAttribType: + return VK_FORMAT_R32G32B32_SFLOAT; + case kFloat4_GrVertexAttribType: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case kHalf_GrVertexAttribType: + return VK_FORMAT_R16_SFLOAT; + case kHalf2_GrVertexAttribType: + return VK_FORMAT_R16G16_SFLOAT; + case kHalf3_GrVertexAttribType: + return VK_FORMAT_R16G16B16_SFLOAT; + case kHalf4_GrVertexAttribType: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case kInt2_GrVertexAttribType: + return VK_FORMAT_R32G32_SINT; + case kInt3_GrVertexAttribType: + return VK_FORMAT_R32G32B32_SINT; + case kInt4_GrVertexAttribType: + return VK_FORMAT_R32G32B32A32_SINT; + case kByte_GrVertexAttribType: + return VK_FORMAT_R8_SINT; + case kByte2_GrVertexAttribType: + return VK_FORMAT_R8G8_SINT; + case kByte3_GrVertexAttribType: + return VK_FORMAT_R8G8B8_SINT; + case kByte4_GrVertexAttribType: + return VK_FORMAT_R8G8B8A8_SINT; + case kUByte_GrVertexAttribType: + return VK_FORMAT_R8_UINT; + case kUByte2_GrVertexAttribType: + return VK_FORMAT_R8G8_UINT; + case kUByte3_GrVertexAttribType: + return VK_FORMAT_R8G8B8_UINT; + case kUByte4_GrVertexAttribType: + return VK_FORMAT_R8G8B8A8_UINT; + case kUByte_norm_GrVertexAttribType: + return VK_FORMAT_R8_UNORM; + case kUByte4_norm_GrVertexAttribType: + return VK_FORMAT_R8G8B8A8_UNORM; + case kShort2_GrVertexAttribType: + return VK_FORMAT_R16G16_SINT; + case kShort4_GrVertexAttribType: + return VK_FORMAT_R16G16B16A16_SINT; + case kUShort2_GrVertexAttribType: + return VK_FORMAT_R16G16_UINT; + case kUShort2_norm_GrVertexAttribType: + return VK_FORMAT_R16G16_UNORM; + case kInt_GrVertexAttribType: + return VK_FORMAT_R32_SINT; + case kUint_GrVertexAttribType: + return VK_FORMAT_R32_UINT; + case kUShort_norm_GrVertexAttribType: + return VK_FORMAT_R16_UNORM; + case kUShort4_norm_GrVertexAttribType: + return VK_FORMAT_R16G16B16A16_UNORM; + } + SK_ABORT("Unknown vertex attrib type"); +} + +static void setup_vertex_input_state(const GrPrimitiveProcessor& primProc, + VkPipelineVertexInputStateCreateInfo* vertexInputInfo, + SkSTArray<2, VkVertexInputBindingDescription, true>* bindingDescs, + VkVertexInputAttributeDescription* attributeDesc) { + uint32_t vertexBinding = 0, instanceBinding = 0; + + int nextBinding = bindingDescs->count(); + if (primProc.hasVertexAttributes()) { + vertexBinding = nextBinding++; + } + + if (primProc.hasInstanceAttributes()) { + instanceBinding = nextBinding; + } + + // setup attribute descriptions + int vaCount = primProc.numVertexAttributes(); + int attribIndex = 0; + size_t vertexAttributeOffset = 0; + for (const auto& attrib : primProc.vertexAttributes()) { + VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex]; + vkAttrib.location = attribIndex++; // for now assume location = attribIndex + vkAttrib.binding = vertexBinding; + vkAttrib.format = attrib_type_to_vkformat(attrib.cpuType()); + vkAttrib.offset = vertexAttributeOffset; + vertexAttributeOffset += attrib.sizeAlign4(); + } + SkASSERT(vertexAttributeOffset == primProc.vertexStride()); + + int iaCount = primProc.numInstanceAttributes(); + size_t instanceAttributeOffset = 0; + for (const auto& attrib : primProc.instanceAttributes()) { + VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex]; + vkAttrib.location = attribIndex++; // for now assume location = attribIndex + vkAttrib.binding = instanceBinding; + vkAttrib.format = attrib_type_to_vkformat(attrib.cpuType()); + vkAttrib.offset = instanceAttributeOffset; + instanceAttributeOffset += attrib.sizeAlign4(); + } + SkASSERT(instanceAttributeOffset == primProc.instanceStride()); + + if (primProc.hasVertexAttributes()) { + bindingDescs->push_back() = { + vertexBinding, + (uint32_t) vertexAttributeOffset, + VK_VERTEX_INPUT_RATE_VERTEX + }; + } + if (primProc.hasInstanceAttributes()) { + bindingDescs->push_back() = { + instanceBinding, + (uint32_t) instanceAttributeOffset, + VK_VERTEX_INPUT_RATE_INSTANCE + }; + } + + memset(vertexInputInfo, 0, sizeof(VkPipelineVertexInputStateCreateInfo)); + vertexInputInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo->pNext = nullptr; + vertexInputInfo->flags = 0; + vertexInputInfo->vertexBindingDescriptionCount = bindingDescs->count(); + vertexInputInfo->pVertexBindingDescriptions = bindingDescs->begin(); + vertexInputInfo->vertexAttributeDescriptionCount = vaCount + iaCount; + vertexInputInfo->pVertexAttributeDescriptions = attributeDesc; +} + +static VkPrimitiveTopology gr_primitive_type_to_vk_topology(GrPrimitiveType primitiveType) { + switch (primitiveType) { + case GrPrimitiveType::kTriangles: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case GrPrimitiveType::kTriangleStrip: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + case GrPrimitiveType::kPoints: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case GrPrimitiveType::kLines: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case GrPrimitiveType::kLineStrip: + return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case GrPrimitiveType::kPath: + SK_ABORT("Unsupported primitive type"); + } + SK_ABORT("invalid GrPrimitiveType"); +} + +static void setup_input_assembly_state(GrPrimitiveType primitiveType, + VkPipelineInputAssemblyStateCreateInfo* inputAssemblyInfo) { + memset(inputAssemblyInfo, 0, sizeof(VkPipelineInputAssemblyStateCreateInfo)); + inputAssemblyInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssemblyInfo->pNext = nullptr; + inputAssemblyInfo->flags = 0; + inputAssemblyInfo->primitiveRestartEnable = false; + inputAssemblyInfo->topology = gr_primitive_type_to_vk_topology(primitiveType); +} + + +static VkStencilOp stencil_op_to_vk_stencil_op(GrStencilOp op) { + static const VkStencilOp gTable[] = { + VK_STENCIL_OP_KEEP, // kKeep + VK_STENCIL_OP_ZERO, // kZero + VK_STENCIL_OP_REPLACE, // kReplace + VK_STENCIL_OP_INVERT, // kInvert + VK_STENCIL_OP_INCREMENT_AND_WRAP, // kIncWrap + VK_STENCIL_OP_DECREMENT_AND_WRAP, // kDecWrap + VK_STENCIL_OP_INCREMENT_AND_CLAMP, // kIncClamp + VK_STENCIL_OP_DECREMENT_AND_CLAMP, // kDecClamp + }; + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gTable) == kGrStencilOpCount); + GR_STATIC_ASSERT(0 == (int)GrStencilOp::kKeep); + GR_STATIC_ASSERT(1 == (int)GrStencilOp::kZero); + GR_STATIC_ASSERT(2 == (int)GrStencilOp::kReplace); + GR_STATIC_ASSERT(3 == (int)GrStencilOp::kInvert); + GR_STATIC_ASSERT(4 == (int)GrStencilOp::kIncWrap); + GR_STATIC_ASSERT(5 == (int)GrStencilOp::kDecWrap); + GR_STATIC_ASSERT(6 == (int)GrStencilOp::kIncClamp); + GR_STATIC_ASSERT(7 == (int)GrStencilOp::kDecClamp); + SkASSERT(op < (GrStencilOp)kGrStencilOpCount); + return gTable[(int)op]; +} + +static VkCompareOp stencil_func_to_vk_compare_op(GrStencilTest test) { + static const VkCompareOp gTable[] = { + VK_COMPARE_OP_ALWAYS, // kAlways + VK_COMPARE_OP_NEVER, // kNever + VK_COMPARE_OP_GREATER, // kGreater + VK_COMPARE_OP_GREATER_OR_EQUAL, // kGEqual + VK_COMPARE_OP_LESS, // kLess + VK_COMPARE_OP_LESS_OR_EQUAL, // kLEqual + VK_COMPARE_OP_EQUAL, // kEqual + VK_COMPARE_OP_NOT_EQUAL, // kNotEqual + }; + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gTable) == kGrStencilTestCount); + GR_STATIC_ASSERT(0 == (int)GrStencilTest::kAlways); + GR_STATIC_ASSERT(1 == (int)GrStencilTest::kNever); + GR_STATIC_ASSERT(2 == (int)GrStencilTest::kGreater); + GR_STATIC_ASSERT(3 == (int)GrStencilTest::kGEqual); + GR_STATIC_ASSERT(4 == (int)GrStencilTest::kLess); + GR_STATIC_ASSERT(5 == (int)GrStencilTest::kLEqual); + GR_STATIC_ASSERT(6 == (int)GrStencilTest::kEqual); + GR_STATIC_ASSERT(7 == (int)GrStencilTest::kNotEqual); + SkASSERT(test < (GrStencilTest)kGrStencilTestCount); + + return gTable[(int)test]; +} + +static void setup_stencil_op_state( + VkStencilOpState* opState, const GrStencilSettings::Face& stencilFace) { + opState->failOp = stencil_op_to_vk_stencil_op(stencilFace.fFailOp); + opState->passOp = stencil_op_to_vk_stencil_op(stencilFace.fPassOp); + opState->depthFailOp = opState->failOp; + opState->compareOp = stencil_func_to_vk_compare_op(stencilFace.fTest); + opState->compareMask = stencilFace.fTestMask; + opState->writeMask = stencilFace.fWriteMask; + opState->reference = stencilFace.fRef; +} + +static void setup_depth_stencil_state( + const GrStencilSettings& stencilSettings, GrSurfaceOrigin origin, + VkPipelineDepthStencilStateCreateInfo* stencilInfo) { + memset(stencilInfo, 0, sizeof(VkPipelineDepthStencilStateCreateInfo)); + stencilInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + stencilInfo->pNext = nullptr; + stencilInfo->flags = 0; + // set depth testing defaults + stencilInfo->depthTestEnable = VK_FALSE; + stencilInfo->depthWriteEnable = VK_FALSE; + stencilInfo->depthCompareOp = VK_COMPARE_OP_ALWAYS; + stencilInfo->depthBoundsTestEnable = VK_FALSE; + stencilInfo->stencilTestEnable = !stencilSettings.isDisabled(); + if (!stencilSettings.isDisabled()) { + if (!stencilSettings.isTwoSided()) { + setup_stencil_op_state(&stencilInfo->front, stencilSettings.frontAndBack()); + stencilInfo->back = stencilInfo->front; + } else { + setup_stencil_op_state(&stencilInfo->front, stencilSettings.front(origin)); + setup_stencil_op_state(&stencilInfo->back, stencilSettings.back(origin)); + } + } + stencilInfo->minDepthBounds = 0.0f; + stencilInfo->maxDepthBounds = 1.0f; +} + +static void setup_viewport_scissor_state(VkPipelineViewportStateCreateInfo* viewportInfo) { + memset(viewportInfo, 0, sizeof(VkPipelineViewportStateCreateInfo)); + viewportInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportInfo->pNext = nullptr; + viewportInfo->flags = 0; + + viewportInfo->viewportCount = 1; + viewportInfo->pViewports = nullptr; // This is set dynamically + + viewportInfo->scissorCount = 1; + viewportInfo->pScissors = nullptr; // This is set dynamically + + SkASSERT(viewportInfo->viewportCount == viewportInfo->scissorCount); +} + +static void setup_multisample_state(const GrProgramInfo& programInfo, + const GrCaps* caps, + VkPipelineMultisampleStateCreateInfo* multisampleInfo) { + memset(multisampleInfo, 0, sizeof(VkPipelineMultisampleStateCreateInfo)); + multisampleInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampleInfo->pNext = nullptr; + multisampleInfo->flags = 0; + SkAssertResult(GrSampleCountToVkSampleCount(programInfo.numSamples(), + &multisampleInfo->rasterizationSamples)); + multisampleInfo->sampleShadingEnable = VK_FALSE; + multisampleInfo->minSampleShading = 0.0f; + multisampleInfo->pSampleMask = nullptr; + multisampleInfo->alphaToCoverageEnable = VK_FALSE; + multisampleInfo->alphaToOneEnable = VK_FALSE; +} + +static VkBlendFactor blend_coeff_to_vk_blend(GrBlendCoeff coeff) { + static const VkBlendFactor gTable[] = { + VK_BLEND_FACTOR_ZERO, // kZero_GrBlendCoeff + VK_BLEND_FACTOR_ONE, // kOne_GrBlendCoeff + VK_BLEND_FACTOR_SRC_COLOR, // kSC_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, // kISC_GrBlendCoeff + VK_BLEND_FACTOR_DST_COLOR, // kDC_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, // kIDC_GrBlendCoeff + VK_BLEND_FACTOR_SRC_ALPHA, // kSA_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, // kISA_GrBlendCoeff + VK_BLEND_FACTOR_DST_ALPHA, // kDA_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, // kIDA_GrBlendCoeff + VK_BLEND_FACTOR_CONSTANT_COLOR, // kConstC_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, // kIConstC_GrBlendCoeff + VK_BLEND_FACTOR_CONSTANT_ALPHA, // kConstA_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA, // kIConstA_GrBlendCoeff + VK_BLEND_FACTOR_SRC1_COLOR, // kS2C_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, // kIS2C_GrBlendCoeff + VK_BLEND_FACTOR_SRC1_ALPHA, // kS2A_GrBlendCoeff + VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, // kIS2A_GrBlendCoeff + VK_BLEND_FACTOR_ZERO, // kIllegal_GrBlendCoeff + }; + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gTable) == kGrBlendCoeffCnt); + GR_STATIC_ASSERT(0 == kZero_GrBlendCoeff); + GR_STATIC_ASSERT(1 == kOne_GrBlendCoeff); + GR_STATIC_ASSERT(2 == kSC_GrBlendCoeff); + GR_STATIC_ASSERT(3 == kISC_GrBlendCoeff); + GR_STATIC_ASSERT(4 == kDC_GrBlendCoeff); + GR_STATIC_ASSERT(5 == kIDC_GrBlendCoeff); + GR_STATIC_ASSERT(6 == kSA_GrBlendCoeff); + GR_STATIC_ASSERT(7 == kISA_GrBlendCoeff); + GR_STATIC_ASSERT(8 == kDA_GrBlendCoeff); + GR_STATIC_ASSERT(9 == kIDA_GrBlendCoeff); + GR_STATIC_ASSERT(10 == kConstC_GrBlendCoeff); + GR_STATIC_ASSERT(11 == kIConstC_GrBlendCoeff); + GR_STATIC_ASSERT(12 == kConstA_GrBlendCoeff); + GR_STATIC_ASSERT(13 == kIConstA_GrBlendCoeff); + GR_STATIC_ASSERT(14 == kS2C_GrBlendCoeff); + GR_STATIC_ASSERT(15 == kIS2C_GrBlendCoeff); + GR_STATIC_ASSERT(16 == kS2A_GrBlendCoeff); + GR_STATIC_ASSERT(17 == kIS2A_GrBlendCoeff); + + SkASSERT((unsigned)coeff < kGrBlendCoeffCnt); + return gTable[coeff]; +} + + +static VkBlendOp blend_equation_to_vk_blend_op(GrBlendEquation equation) { + static const VkBlendOp gTable[] = { + // Basic blend ops + VK_BLEND_OP_ADD, + VK_BLEND_OP_SUBTRACT, + VK_BLEND_OP_REVERSE_SUBTRACT, + + // Advanced blend ops + VK_BLEND_OP_SCREEN_EXT, + VK_BLEND_OP_OVERLAY_EXT, + VK_BLEND_OP_DARKEN_EXT, + VK_BLEND_OP_LIGHTEN_EXT, + VK_BLEND_OP_COLORDODGE_EXT, + VK_BLEND_OP_COLORBURN_EXT, + VK_BLEND_OP_HARDLIGHT_EXT, + VK_BLEND_OP_SOFTLIGHT_EXT, + VK_BLEND_OP_DIFFERENCE_EXT, + VK_BLEND_OP_EXCLUSION_EXT, + VK_BLEND_OP_MULTIPLY_EXT, + VK_BLEND_OP_HSL_HUE_EXT, + VK_BLEND_OP_HSL_SATURATION_EXT, + VK_BLEND_OP_HSL_COLOR_EXT, + VK_BLEND_OP_HSL_LUMINOSITY_EXT, + + // Illegal. + VK_BLEND_OP_ADD, + }; + GR_STATIC_ASSERT(0 == kAdd_GrBlendEquation); + GR_STATIC_ASSERT(1 == kSubtract_GrBlendEquation); + GR_STATIC_ASSERT(2 == kReverseSubtract_GrBlendEquation); + GR_STATIC_ASSERT(3 == kScreen_GrBlendEquation); + GR_STATIC_ASSERT(4 == kOverlay_GrBlendEquation); + GR_STATIC_ASSERT(5 == kDarken_GrBlendEquation); + GR_STATIC_ASSERT(6 == kLighten_GrBlendEquation); + GR_STATIC_ASSERT(7 == kColorDodge_GrBlendEquation); + GR_STATIC_ASSERT(8 == kColorBurn_GrBlendEquation); + GR_STATIC_ASSERT(9 == kHardLight_GrBlendEquation); + GR_STATIC_ASSERT(10 == kSoftLight_GrBlendEquation); + GR_STATIC_ASSERT(11 == kDifference_GrBlendEquation); + GR_STATIC_ASSERT(12 == kExclusion_GrBlendEquation); + GR_STATIC_ASSERT(13 == kMultiply_GrBlendEquation); + GR_STATIC_ASSERT(14 == kHSLHue_GrBlendEquation); + GR_STATIC_ASSERT(15 == kHSLSaturation_GrBlendEquation); + GR_STATIC_ASSERT(16 == kHSLColor_GrBlendEquation); + GR_STATIC_ASSERT(17 == kHSLLuminosity_GrBlendEquation); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(gTable) == kGrBlendEquationCnt); + + SkASSERT((unsigned)equation < kGrBlendCoeffCnt); + return gTable[equation]; +} + +static bool blend_coeff_refs_constant(GrBlendCoeff coeff) { + static const bool gCoeffReferencesBlendConst[] = { + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + + // extended blend coeffs + false, + false, + false, + false, + + // Illegal + false, + }; + return gCoeffReferencesBlendConst[coeff]; + GR_STATIC_ASSERT(kGrBlendCoeffCnt == SK_ARRAY_COUNT(gCoeffReferencesBlendConst)); + // Individual enum asserts already made in blend_coeff_to_vk_blend +} + +static void setup_color_blend_state(const GrPipeline& pipeline, + VkPipelineColorBlendStateCreateInfo* colorBlendInfo, + VkPipelineColorBlendAttachmentState* attachmentState) { + const GrXferProcessor::BlendInfo& blendInfo = pipeline.getXferProcessor().getBlendInfo(); + + GrBlendEquation equation = blendInfo.fEquation; + GrBlendCoeff srcCoeff = blendInfo.fSrcBlend; + GrBlendCoeff dstCoeff = blendInfo.fDstBlend; + bool blendOff = (kAdd_GrBlendEquation == equation || kSubtract_GrBlendEquation == equation) && + kOne_GrBlendCoeff == srcCoeff && kZero_GrBlendCoeff == dstCoeff; + + memset(attachmentState, 0, sizeof(VkPipelineColorBlendAttachmentState)); + attachmentState->blendEnable = !blendOff; + if (!blendOff) { + attachmentState->srcColorBlendFactor = blend_coeff_to_vk_blend(srcCoeff); + attachmentState->dstColorBlendFactor = blend_coeff_to_vk_blend(dstCoeff); + attachmentState->colorBlendOp = blend_equation_to_vk_blend_op(equation); + attachmentState->srcAlphaBlendFactor = blend_coeff_to_vk_blend(srcCoeff); + attachmentState->dstAlphaBlendFactor = blend_coeff_to_vk_blend(dstCoeff); + attachmentState->alphaBlendOp = blend_equation_to_vk_blend_op(equation); + } + + if (!blendInfo.fWriteColor) { + attachmentState->colorWriteMask = 0; + } else { + attachmentState->colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + } + + memset(colorBlendInfo, 0, sizeof(VkPipelineColorBlendStateCreateInfo)); + colorBlendInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlendInfo->pNext = nullptr; + colorBlendInfo->flags = 0; + colorBlendInfo->logicOpEnable = VK_FALSE; + colorBlendInfo->attachmentCount = 1; + colorBlendInfo->pAttachments = attachmentState; + // colorBlendInfo->blendConstants is set dynamically +} + +static void setup_raster_state(const GrPipeline& pipeline, + const GrCaps* caps, + VkPipelineRasterizationStateCreateInfo* rasterInfo) { + memset(rasterInfo, 0, sizeof(VkPipelineRasterizationStateCreateInfo)); + rasterInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterInfo->pNext = nullptr; + rasterInfo->flags = 0; + rasterInfo->depthClampEnable = VK_FALSE; + rasterInfo->rasterizerDiscardEnable = VK_FALSE; + rasterInfo->polygonMode = caps->wireframeMode() ? VK_POLYGON_MODE_LINE + : VK_POLYGON_MODE_FILL; + rasterInfo->cullMode = VK_CULL_MODE_NONE; + rasterInfo->frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterInfo->depthBiasEnable = VK_FALSE; + rasterInfo->depthBiasConstantFactor = 0.0f; + rasterInfo->depthBiasClamp = 0.0f; + rasterInfo->depthBiasSlopeFactor = 0.0f; + rasterInfo->lineWidth = 1.0f; +} + +static void setup_dynamic_state(VkPipelineDynamicStateCreateInfo* dynamicInfo, + VkDynamicState* dynamicStates) { + memset(dynamicInfo, 0, sizeof(VkPipelineDynamicStateCreateInfo)); + dynamicInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicInfo->pNext = VK_NULL_HANDLE; + dynamicInfo->flags = 0; + dynamicStates[0] = VK_DYNAMIC_STATE_VIEWPORT; + dynamicStates[1] = VK_DYNAMIC_STATE_SCISSOR; + dynamicStates[2] = VK_DYNAMIC_STATE_BLEND_CONSTANTS; + dynamicInfo->dynamicStateCount = 3; + dynamicInfo->pDynamicStates = dynamicStates; +} + +GrVkPipeline* GrVkPipeline::Create( + GrVkGpu* gpu, + const GrProgramInfo& programInfo, + const GrStencilSettings& stencil, + VkPipelineShaderStageCreateInfo* shaderStageInfo, int shaderStageCount, + GrPrimitiveType primitiveType, VkRenderPass compatibleRenderPass, VkPipelineLayout layout, + VkPipelineCache cache) { + VkPipelineVertexInputStateCreateInfo vertexInputInfo; + SkSTArray<2, VkVertexInputBindingDescription, true> bindingDescs; + SkSTArray<16, VkVertexInputAttributeDescription> attributeDesc; + int totalAttributeCnt = programInfo.primProc().numVertexAttributes() + + programInfo.primProc().numInstanceAttributes(); + SkASSERT(totalAttributeCnt <= gpu->vkCaps().maxVertexAttributes()); + VkVertexInputAttributeDescription* pAttribs = attributeDesc.push_back_n(totalAttributeCnt); + setup_vertex_input_state(programInfo.primProc(), &vertexInputInfo, &bindingDescs, pAttribs); + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo; + setup_input_assembly_state(primitiveType, &inputAssemblyInfo); + + VkPipelineDepthStencilStateCreateInfo depthStencilInfo; + setup_depth_stencil_state(stencil, programInfo.origin(), &depthStencilInfo); + + VkPipelineViewportStateCreateInfo viewportInfo; + setup_viewport_scissor_state(&viewportInfo); + + VkPipelineMultisampleStateCreateInfo multisampleInfo; + setup_multisample_state(programInfo, gpu->caps(), &multisampleInfo); + + // We will only have one color attachment per pipeline. + VkPipelineColorBlendAttachmentState attachmentStates[1]; + VkPipelineColorBlendStateCreateInfo colorBlendInfo; + setup_color_blend_state(programInfo.pipeline(), &colorBlendInfo, attachmentStates); + + VkPipelineRasterizationStateCreateInfo rasterInfo; + setup_raster_state(programInfo.pipeline(), gpu->caps(), &rasterInfo); + + VkDynamicState dynamicStates[3]; + VkPipelineDynamicStateCreateInfo dynamicInfo; + setup_dynamic_state(&dynamicInfo, dynamicStates); + + VkGraphicsPipelineCreateInfo pipelineCreateInfo; + memset(&pipelineCreateInfo, 0, sizeof(VkGraphicsPipelineCreateInfo)); + pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineCreateInfo.pNext = nullptr; + pipelineCreateInfo.flags = 0; + pipelineCreateInfo.stageCount = shaderStageCount; + pipelineCreateInfo.pStages = shaderStageInfo; + pipelineCreateInfo.pVertexInputState = &vertexInputInfo; + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyInfo; + pipelineCreateInfo.pTessellationState = nullptr; + pipelineCreateInfo.pViewportState = &viewportInfo; + pipelineCreateInfo.pRasterizationState = &rasterInfo; + pipelineCreateInfo.pMultisampleState = &multisampleInfo; + pipelineCreateInfo.pDepthStencilState = &depthStencilInfo; + pipelineCreateInfo.pColorBlendState = &colorBlendInfo; + pipelineCreateInfo.pDynamicState = &dynamicInfo; + pipelineCreateInfo.layout = layout; + pipelineCreateInfo.renderPass = compatibleRenderPass; + pipelineCreateInfo.subpass = 0; + pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineCreateInfo.basePipelineIndex = -1; + + VkPipeline vkPipeline; + VkResult err; + { +#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS) + // skia:8712 + __lsan::ScopedDisabler lsanDisabler; +#endif + err = GR_VK_CALL(gpu->vkInterface(), CreateGraphicsPipelines(gpu->device(), + cache, 1, + &pipelineCreateInfo, + nullptr, &vkPipeline)); + } + if (err) { + SkDebugf("Failed to create pipeline. Error: %d\n", err); + return nullptr; + } + + return new GrVkPipeline(vkPipeline, layout); +} + +void GrVkPipeline::freeGPUData(GrVkGpu* gpu) const { + GR_VK_CALL(gpu->vkInterface(), DestroyPipeline(gpu->device(), fPipeline, nullptr)); + GR_VK_CALL(gpu->vkInterface(), DestroyPipelineLayout(gpu->device(), fPipelineLayout, nullptr)); +} + +void GrVkPipeline::SetDynamicScissorRectState(GrVkGpu* gpu, + GrVkCommandBuffer* cmdBuffer, + const GrRenderTarget* renderTarget, + GrSurfaceOrigin rtOrigin, + const SkIRect& scissorRect) { + SkASSERT(scissorRect.isEmpty() || + SkIRect::MakeWH(renderTarget->width(), renderTarget->height()).contains(scissorRect)); + + VkRect2D scissor; + scissor.offset.x = scissorRect.fLeft; + scissor.extent.width = scissorRect.width(); + if (kTopLeft_GrSurfaceOrigin == rtOrigin) { + scissor.offset.y = scissorRect.fTop; + } else { + SkASSERT(kBottomLeft_GrSurfaceOrigin == rtOrigin); + scissor.offset.y = renderTarget->height() - scissorRect.fBottom; + } + scissor.extent.height = scissorRect.height(); + + SkASSERT(scissor.offset.x >= 0); + SkASSERT(scissor.offset.y >= 0); + cmdBuffer->setScissor(gpu, 0, 1, &scissor); +} + +void GrVkPipeline::SetDynamicViewportState(GrVkGpu* gpu, + GrVkCommandBuffer* cmdBuffer, + const GrRenderTarget* renderTarget) { + // We always use one viewport the size of the RT + VkViewport viewport; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = SkIntToScalar(renderTarget->width()); + viewport.height = SkIntToScalar(renderTarget->height()); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + cmdBuffer->setViewport(gpu, 0, 1, &viewport); +} + +void GrVkPipeline::SetDynamicBlendConstantState(GrVkGpu* gpu, + GrVkCommandBuffer* cmdBuffer, + const GrSwizzle& swizzle, + const GrXferProcessor& xferProcessor) { + const GrXferProcessor::BlendInfo& blendInfo = xferProcessor.getBlendInfo(); + GrBlendCoeff srcCoeff = blendInfo.fSrcBlend; + GrBlendCoeff dstCoeff = blendInfo.fDstBlend; + float floatColors[4]; + if (blend_coeff_refs_constant(srcCoeff) || blend_coeff_refs_constant(dstCoeff)) { + // Swizzle the blend to match what the shader will output. + SkPMColor4f blendConst = swizzle.applyTo(blendInfo.fBlendConstant); + floatColors[0] = blendConst.fR; + floatColors[1] = blendConst.fG; + floatColors[2] = blendConst.fB; + floatColors[3] = blendConst.fA; + } else { + memset(floatColors, 0, 4 * sizeof(float)); + } + cmdBuffer->setBlendConstants(gpu, floatColors); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipeline.h b/gfx/skia/skia/src/gpu/vk/GrVkPipeline.h new file mode 100644 index 0000000000..ce33a49d72 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipeline.h @@ -0,0 +1,66 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkPipeline_DEFINED +#define GrVkPipeline_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/GrTypesPriv.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrPipeline; +class GrPrimitiveProcessor; +class GrRenderTarget; +class GrXferProcessor; +class GrStencilSettings; +class GrVkCommandBuffer; +class GrVkGpu; +class GrVkRenderPass; +struct SkIRect; + +class GrVkPipeline : public GrVkResource { +public: + static GrVkPipeline* Create(GrVkGpu*, + const GrProgramInfo&, + const GrStencilSettings&, + VkPipelineShaderStageCreateInfo* shaderStageInfo, + int shaderStageCount, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass, + VkPipelineLayout layout, + VkPipelineCache cache); + + VkPipeline pipeline() const { return fPipeline; } + VkPipelineLayout layout() const { return fPipelineLayout; } + + static void SetDynamicScissorRectState(GrVkGpu*, GrVkCommandBuffer*, const GrRenderTarget*, + GrSurfaceOrigin, const SkIRect& scissorRect); + static void SetDynamicViewportState(GrVkGpu*, GrVkCommandBuffer*, const GrRenderTarget*); + static void SetDynamicBlendConstantState(GrVkGpu*, GrVkCommandBuffer*, + const GrSwizzle& outputSwizzle, + const GrXferProcessor&); + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkPipeline: %d (%d refs)\n", fPipeline, this->getRefCnt()); + } +#endif + +protected: + GrVkPipeline(VkPipeline pipeline, VkPipelineLayout layout) + : INHERITED(), fPipeline(pipeline), fPipelineLayout(layout) {} + + VkPipeline fPipeline; + VkPipelineLayout fPipelineLayout; + +private: + void freeGPUData(GrVkGpu* gpu) const override; + + typedef GrVkResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineState.cpp b/gfx/skia/skia/src/gpu/vk/GrVkPipelineState.cpp new file mode 100644 index 0000000000..b48f15550e --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineState.cpp @@ -0,0 +1,337 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/gpu/GrContext.h" +#include "src/core/SkMipMap.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLXferProcessor.h" +#include "src/gpu/vk/GrVkBufferView.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkDescriptorPool.h" +#include "src/gpu/vk/GrVkDescriptorSet.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkMemory.h" +#include "src/gpu/vk/GrVkPipeline.h" +#include "src/gpu/vk/GrVkPipelineState.h" +#include "src/gpu/vk/GrVkSampler.h" +#include "src/gpu/vk/GrVkTexture.h" +#include "src/gpu/vk/GrVkUniformBuffer.h" + +GrVkPipelineState::GrVkPipelineState( + GrVkGpu* gpu, + GrVkPipeline* pipeline, + const GrVkDescriptorSetManager::Handle& samplerDSHandle, + const GrGLSLBuiltinUniformHandles& builtinUniformHandles, + const UniformInfoArray& uniforms, + uint32_t uniformSize, + const UniformInfoArray& samplers, + std::unique_ptr geometryProcessor, + std::unique_ptr xferProcessor, + std::unique_ptr[]> fragmentProcessors, + int fragmentProcessorCnt) + : fPipeline(pipeline) + , fUniformDescriptorSet(nullptr) + , fSamplerDescriptorSet(nullptr) + , fSamplerDSHandle(samplerDSHandle) + , fBuiltinUniformHandles(builtinUniformHandles) + , fGeometryProcessor(std::move(geometryProcessor)) + , fXferProcessor(std::move(xferProcessor)) + , fFragmentProcessors(std::move(fragmentProcessors)) + , fFragmentProcessorCnt(fragmentProcessorCnt) + , fDataManager(uniforms, uniformSize) { + fDescriptorSets[0] = VK_NULL_HANDLE; + fDescriptorSets[1] = VK_NULL_HANDLE; + fDescriptorSets[2] = VK_NULL_HANDLE; + + fUniformBuffer.reset(GrVkUniformBuffer::Create(gpu, uniformSize)); + + fNumSamplers = samplers.count(); + + for (int i = 0; i < fNumSamplers; ++i) { + // We store the immutable samplers here and take ownership of the ref from the + // GrVkUnformHandler. + fImmutableSamplers.push_back(samplers[i].fImmutableSampler); + } +} + +GrVkPipelineState::~GrVkPipelineState() { + // Must have freed all GPU resources before this is destroyed + SkASSERT(!fPipeline); +} + +void GrVkPipelineState::freeGPUResources(GrVkGpu* gpu) { + if (fPipeline) { + fPipeline->unref(gpu); + fPipeline = nullptr; + } + + if (fUniformBuffer) { + fUniformBuffer->release(gpu); + fUniformBuffer.reset(); + } + + if (fUniformDescriptorSet) { + fUniformDescriptorSet->recycle(const_cast(gpu)); + fUniformDescriptorSet = nullptr; + } + + if (fSamplerDescriptorSet) { + fSamplerDescriptorSet->recycle(const_cast(gpu)); + fSamplerDescriptorSet = nullptr; + } +} + +void GrVkPipelineState::abandonGPUResources() { + if (fPipeline) { + fPipeline->unrefAndAbandon(); + fPipeline = nullptr; + } + + if (fUniformBuffer) { + fUniformBuffer->abandon(); + fUniformBuffer.reset(); + } + + if (fUniformDescriptorSet) { + fUniformDescriptorSet->unrefAndAbandon(); + fUniformDescriptorSet = nullptr; + } + + if (fSamplerDescriptorSet) { + fSamplerDescriptorSet->unrefAndAbandon(); + fSamplerDescriptorSet = nullptr; + } +} + +void GrVkPipelineState::setAndBindUniforms(GrVkGpu* gpu, + const GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrVkCommandBuffer* commandBuffer) { + this->setRenderTargetState(renderTarget, programInfo.origin()); + + fGeometryProcessor->setData(fDataManager, programInfo.primProc(), + GrFragmentProcessor::CoordTransformIter(programInfo.pipeline())); + GrFragmentProcessor::Iter iter(programInfo.pipeline()); + GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt); + const GrFragmentProcessor* fp = iter.next(); + GrGLSLFragmentProcessor* glslFP = glslIter.next(); + while (fp && glslFP) { + glslFP->setData(fDataManager, *fp); + fp = iter.next(); + glslFP = glslIter.next(); + } + SkASSERT(!fp && !glslFP); + + { + SkIPoint offset; + GrTexture* dstTexture = programInfo.pipeline().peekDstTexture(&offset); + + fXferProcessor->setData(fDataManager, programInfo.pipeline().getXferProcessor(), + dstTexture, offset); + } + + // Get new descriptor set + if (fUniformBuffer) { + int uniformDSIdx = GrVkUniformHandler::kUniformBufferDescSet; + if (fDataManager.uploadUniformBuffers(gpu, fUniformBuffer.get()) || + !fUniformDescriptorSet) { + if (fUniformDescriptorSet) { + fUniformDescriptorSet->recycle(gpu); + } + fUniformDescriptorSet = gpu->resourceProvider().getUniformDescriptorSet(); + fDescriptorSets[uniformDSIdx] = fUniformDescriptorSet->descriptorSet(); + this->writeUniformBuffers(gpu); + } + commandBuffer->bindDescriptorSets(gpu, this, fPipeline->layout(), uniformDSIdx, 1, + &fDescriptorSets[uniformDSIdx], 0, nullptr); + if (fUniformDescriptorSet) { + commandBuffer->addRecycledResource(fUniformDescriptorSet); + } + if (fUniformBuffer) { + commandBuffer->addRecycledResource(fUniformBuffer->resource()); + } + } +} + +void GrVkPipelineState::setAndBindTextures(GrVkGpu* gpu, + const GrPrimitiveProcessor& primProc, + const GrPipeline& pipeline, + const GrTextureProxy* const primProcTextures[], + GrVkCommandBuffer* commandBuffer) { + SkASSERT(primProcTextures || !primProc.numTextureSamplers()); + + struct SamplerBindings { + GrSamplerState fState; + GrVkTexture* fTexture; + }; + SkAutoSTMalloc<8, SamplerBindings> samplerBindings(fNumSamplers); + int currTextureBinding = 0; + + fGeometryProcessor->setData(fDataManager, primProc, + GrFragmentProcessor::CoordTransformIter(pipeline)); + for (int i = 0; i < primProc.numTextureSamplers(); ++i) { + const auto& sampler = primProc.textureSampler(i); + auto texture = static_cast(primProcTextures[i]->peekTexture()); + samplerBindings[currTextureBinding++] = {sampler.samplerState(), texture}; + } + + GrFragmentProcessor::Iter iter(pipeline); + GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt); + const GrFragmentProcessor* fp = iter.next(); + GrGLSLFragmentProcessor* glslFP = glslIter.next(); + while (fp && glslFP) { + for (int i = 0; i < fp->numTextureSamplers(); ++i) { + const auto& sampler = fp->textureSampler(i); + samplerBindings[currTextureBinding++] = + {sampler.samplerState(), static_cast(sampler.peekTexture())}; + } + fp = iter.next(); + glslFP = glslIter.next(); + } + SkASSERT(!fp && !glslFP); + + if (GrTextureProxy* dstTextureProxy = pipeline.dstTextureProxy()) { + samplerBindings[currTextureBinding++] = { + GrSamplerState::ClampNearest(), + static_cast(dstTextureProxy->peekTexture())}; + } + + // Get new descriptor set + SkASSERT(fNumSamplers == currTextureBinding); + if (fNumSamplers) { + if (fSamplerDescriptorSet) { + fSamplerDescriptorSet->recycle(gpu); + } + fSamplerDescriptorSet = gpu->resourceProvider().getSamplerDescriptorSet(fSamplerDSHandle); + int samplerDSIdx = GrVkUniformHandler::kSamplerDescSet; + fDescriptorSets[samplerDSIdx] = fSamplerDescriptorSet->descriptorSet(); + for (int i = 0; i < fNumSamplers; ++i) { + const GrSamplerState& state = samplerBindings[i].fState; + GrVkTexture* texture = samplerBindings[i].fTexture; + + const GrVkImageView* textureView = texture->textureView(); + const GrVkSampler* sampler = nullptr; + if (fImmutableSamplers[i]) { + sampler = fImmutableSamplers[i]; + } else { + sampler = gpu->resourceProvider().findOrCreateCompatibleSampler( + state, texture->ycbcrConversionInfo()); + } + SkASSERT(sampler); + + VkDescriptorImageInfo imageInfo; + memset(&imageInfo, 0, sizeof(VkDescriptorImageInfo)); + imageInfo.sampler = sampler->sampler(); + imageInfo.imageView = textureView->imageView(); + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkWriteDescriptorSet writeInfo; + memset(&writeInfo, 0, sizeof(VkWriteDescriptorSet)); + writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeInfo.pNext = nullptr; + writeInfo.dstSet = fDescriptorSets[GrVkUniformHandler::kSamplerDescSet]; + writeInfo.dstBinding = i; + writeInfo.dstArrayElement = 0; + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeInfo.pImageInfo = &imageInfo; + writeInfo.pBufferInfo = nullptr; + writeInfo.pTexelBufferView = nullptr; + + GR_VK_CALL(gpu->vkInterface(), + UpdateDescriptorSets(gpu->device(), 1, &writeInfo, 0, nullptr)); + commandBuffer->addResource(sampler); + if (!fImmutableSamplers[i]) { + sampler->unref(gpu); + } + commandBuffer->addResource(samplerBindings[i].fTexture->textureView()); + commandBuffer->addResource(samplerBindings[i].fTexture->resource()); + } + + commandBuffer->bindDescriptorSets(gpu, this, fPipeline->layout(), samplerDSIdx, 1, + &fDescriptorSets[samplerDSIdx], 0, nullptr); + commandBuffer->addRecycledResource(fSamplerDescriptorSet); + } +} + +void set_uniform_descriptor_writes(VkWriteDescriptorSet* descriptorWrite, + VkDescriptorBufferInfo* bufferInfo, + const GrVkUniformBuffer* buffer, + VkDescriptorSet descriptorSet) { + + memset(bufferInfo, 0, sizeof(VkDescriptorBufferInfo)); + bufferInfo->buffer = buffer->buffer(); + bufferInfo->offset = buffer->offset(); + bufferInfo->range = buffer->size(); + + memset(descriptorWrite, 0, sizeof(VkWriteDescriptorSet)); + descriptorWrite->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite->pNext = nullptr; + descriptorWrite->dstSet = descriptorSet; + descriptorWrite->dstBinding = GrVkUniformHandler::kUniformBinding; + descriptorWrite->dstArrayElement = 0; + descriptorWrite->descriptorCount = 1; + descriptorWrite->descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite->pImageInfo = nullptr; + descriptorWrite->pBufferInfo = bufferInfo; + descriptorWrite->pTexelBufferView = nullptr; +} + +void GrVkPipelineState::writeUniformBuffers(const GrVkGpu* gpu) { + VkWriteDescriptorSet descriptorWrites[3]; + VkDescriptorBufferInfo bufferInfos[3]; + + uint32_t writeCount = 0; + + if (fUniformBuffer.get()) { + set_uniform_descriptor_writes(&descriptorWrites[writeCount], + &bufferInfos[writeCount], + fUniformBuffer.get(), + fDescriptorSets[GrVkUniformHandler::kUniformBufferDescSet]); + ++writeCount; + } + + if (writeCount) { + GR_VK_CALL(gpu->vkInterface(), UpdateDescriptorSets(gpu->device(), + writeCount, + descriptorWrites, + 0, nullptr)); + } +} + +void GrVkPipelineState::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin) { + + // Load the RT height uniform if it is needed to y-flip gl_FragCoord. + if (fBuiltinUniformHandles.fRTHeightUni.isValid() && + fRenderTargetState.fRenderTargetSize.fHeight != rt->height()) { + fDataManager.set1f(fBuiltinUniformHandles.fRTHeightUni, SkIntToScalar(rt->height())); + } + + // set RT adjustment + SkISize size; + size.set(rt->width(), rt->height()); + SkASSERT(fBuiltinUniformHandles.fRTAdjustmentUni.isValid()); + if (fRenderTargetState.fRenderTargetOrigin != origin || + fRenderTargetState.fRenderTargetSize != size) { + fRenderTargetState.fRenderTargetSize = size; + fRenderTargetState.fRenderTargetOrigin = origin; + + float rtAdjustmentVec[4]; + fRenderTargetState.getRTAdjustmentVec(rtAdjustmentVec); + fDataManager.set4fv(fBuiltinUniformHandles.fRTAdjustmentUni, 1, rtAdjustmentVec); + } +} + +void GrVkPipelineState::bindPipeline(const GrVkGpu* gpu, GrVkCommandBuffer* commandBuffer) { + commandBuffer->bindPipeline(gpu, fPipeline); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineState.h b/gfx/skia/skia/src/gpu/vk/GrVkPipelineState.h new file mode 100644 index 0000000000..331bb01f01 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineState.h @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrVkPipelineState_DEFINED +#define GrVkPipelineState_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/vk/GrVkDescriptorSetManager.h" +#include "src/gpu/vk/GrVkPipelineStateDataManager.h" + +class GrPipeline; +class GrStencilSettings; +class GrVkBufferView; +class GrVkCommandBuffer; +class GrVkDescriptorPool; +class GrVkDescriptorSet; +class GrVkGpu; +class GrVkImageView; +class GrVkPipeline; +class GrVkSampler; +class GrVkTexture; +class GrVkUniformBuffer; + +/** + * This class holds onto a GrVkPipeline object that we use for draws. Besides storing the acutal + * GrVkPipeline object, this class is also responsible handling all uniforms, descriptors, samplers, + * and other similar objects that are used along with the VkPipeline in the draw. This includes both + * allocating and freeing these objects, as well as updating their values. + */ +class GrVkPipelineState : public SkRefCnt { +public: + using UniformInfoArray = GrVkPipelineStateDataManager::UniformInfoArray; + using UniformHandle = GrGLSLProgramDataManager::UniformHandle; + + GrVkPipelineState( + GrVkGpu* gpu, + GrVkPipeline* pipeline, + const GrVkDescriptorSetManager::Handle& samplerDSHandle, + const GrGLSLBuiltinUniformHandles& builtinUniformHandles, + const UniformInfoArray& uniforms, + uint32_t uniformSize, + const UniformInfoArray& samplers, + std::unique_ptr geometryProcessor, + std::unique_ptr xferProcessor, + std::unique_ptr[]> fragmentProcessors, + int fFragmentProcessorCnt); + + ~GrVkPipelineState(); + + void setAndBindUniforms(GrVkGpu*, const GrRenderTarget*, const GrProgramInfo&, + GrVkCommandBuffer*); + /** + * This must be called after setAndBindUniforms() since that function invalidates texture + * bindings. + */ + void setAndBindTextures(GrVkGpu*, const GrPrimitiveProcessor&, const GrPipeline&, + const GrTextureProxy* const primitiveProcessorTextures[], + GrVkCommandBuffer*); + + void bindPipeline(const GrVkGpu* gpu, GrVkCommandBuffer* commandBuffer); + + void addUniformResources(GrVkCommandBuffer&, GrVkSampler*[], GrVkTexture*[], int numTextures); + + void freeGPUResources(GrVkGpu* gpu); + + void abandonGPUResources(); + +private: + void writeUniformBuffers(const GrVkGpu* gpu); + + /** + * We use the RT's size and origin to adjust from Skia device space to vulkan normalized device + * space and to make device space positions have the correct origin for processors that require + * them. + */ + struct RenderTargetState { + SkISize fRenderTargetSize; + GrSurfaceOrigin fRenderTargetOrigin; + + RenderTargetState() { this->invalidate(); } + void invalidate() { + fRenderTargetSize.fWidth = -1; + fRenderTargetSize.fHeight = -1; + fRenderTargetOrigin = (GrSurfaceOrigin)-1; + } + + /** + * Gets a float4 that adjusts the position from Skia device coords to Vulkans normalized device + * coords. Assuming the transformed position, pos, is a homogeneous float3, the vec, v, is + * applied as such: + * pos.x = dot(v.xy, pos.xz) + * pos.y = dot(v.zw, pos.yz) + */ + void getRTAdjustmentVec(float* destVec) { + destVec[0] = 2.f / fRenderTargetSize.fWidth; + destVec[1] = -1.f; + if (kBottomLeft_GrSurfaceOrigin == fRenderTargetOrigin) { + destVec[2] = -2.f / fRenderTargetSize.fHeight; + destVec[3] = 1.f; + } else { + destVec[2] = 2.f / fRenderTargetSize.fHeight; + destVec[3] = -1.f; + } + } + }; + + // Helper for setData() that sets the view matrix and loads the render target height uniform + void setRenderTargetState(const GrRenderTarget*, GrSurfaceOrigin); + + // GrVkResources + GrVkPipeline* fPipeline; + + // The DescriptorSets need to survive until the gpu has finished all draws that use them. + // However, they will only be freed by the descriptor pool. Thus by simply keeping the + // descriptor pool alive through the draw, the descritor sets will also stay alive. Thus we do + // not need a GrVkResource versions of VkDescriptorSet. We hold on to these in the + // GrVkPipelineState since we update the descriptor sets and bind them at separate times; + VkDescriptorSet fDescriptorSets[3]; + + const GrVkDescriptorSet* fUniformDescriptorSet; + const GrVkDescriptorSet* fSamplerDescriptorSet; + + const GrVkDescriptorSetManager::Handle fSamplerDSHandle; + + SkSTArray<4, const GrVkSampler*> fImmutableSamplers; + + std::unique_ptr fUniformBuffer; + + // Tracks the current render target uniforms stored in the vertex buffer. + RenderTargetState fRenderTargetState; + GrGLSLBuiltinUniformHandles fBuiltinUniformHandles; + + // Processors in the GrVkPipelineState + std::unique_ptr fGeometryProcessor; + std::unique_ptr fXferProcessor; + std::unique_ptr[]> fFragmentProcessors; + int fFragmentProcessorCnt; + + GrVkPipelineStateDataManager fDataManager; + + int fNumSamplers; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.cpp new file mode 100644 index 0000000000..4d595e7eca --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.cpp @@ -0,0 +1,353 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrPersistentCacheUtils.h" +#include "src/gpu/GrShaderCaps.h" +#include "src/gpu/GrShaderUtils.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/vk/GrVkDescriptorSetManager.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkPipelineStateBuilder.h" +#include "src/gpu/vk/GrVkRenderPass.h" +#include "src/gpu/vk/GrVkRenderTarget.h" + +typedef size_t shader_size; + +GrVkPipelineState* GrVkPipelineStateBuilder::CreatePipelineState( + GrVkGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + const GrStencilSettings& stencil, + GrPrimitiveType primitiveType, + Desc* desc, + VkRenderPass compatibleRenderPass) { + // create a builder. This will be handed off to effects so they can use it to add + // uniforms, varyings, textures, etc + GrVkPipelineStateBuilder builder(gpu, renderTarget, programInfo, desc); + + if (!builder.emitAndInstallProcs()) { + return nullptr; + } + + return builder.finalize(stencil, primitiveType, compatibleRenderPass, desc); +} + +GrVkPipelineStateBuilder::GrVkPipelineStateBuilder(GrVkGpu* gpu, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrProgramDesc* desc) + : INHERITED(renderTarget, programInfo, desc) + , fGpu(gpu) + , fVaryingHandler(this) + , fUniformHandler(this) {} + +const GrCaps* GrVkPipelineStateBuilder::caps() const { + return fGpu->caps(); +} + +void GrVkPipelineStateBuilder::finalizeFragmentOutputColor(GrShaderVar& outputColor) { + outputColor.addLayoutQualifier("location = 0, index = 0"); +} + +void GrVkPipelineStateBuilder::finalizeFragmentSecondaryColor(GrShaderVar& outputColor) { + outputColor.addLayoutQualifier("location = 0, index = 1"); +} + +bool GrVkPipelineStateBuilder::createVkShaderModule(VkShaderStageFlagBits stage, + const SkSL::String& sksl, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo, + const SkSL::Program::Settings& settings, + Desc* desc, + SkSL::String* outSPIRV, + SkSL::Program::Inputs* outInputs) { + if (!GrCompileVkShaderModule(fGpu, sksl, stage, shaderModule, + stageInfo, settings, outSPIRV, outInputs)) { + return false; + } + if (outInputs->fRTHeight) { + this->addRTHeightUniform(SKSL_RTHEIGHT_NAME); + } + return true; +} + +bool GrVkPipelineStateBuilder::installVkShaderModule(VkShaderStageFlagBits stage, + const GrGLSLShaderBuilder& builder, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo, + SkSL::String spirv, + SkSL::Program::Inputs inputs) { + if (!GrInstallVkShaderModule(fGpu, spirv, stage, shaderModule, stageInfo)) { + return false; + } + if (inputs.fRTHeight) { + this->addRTHeightUniform(SKSL_RTHEIGHT_NAME); + } + return true; +} + +static constexpr SkFourByteTag kSPIRV_Tag = SkSetFourByteTag('S', 'P', 'R', 'V'); +static constexpr SkFourByteTag kSKSL_Tag = SkSetFourByteTag('S', 'K', 'S', 'L'); + +int GrVkPipelineStateBuilder::loadShadersFromCache(SkReader32* cached, + VkShaderModule outShaderModules[], + VkPipelineShaderStageCreateInfo* outStageInfo) { + SkSL::String shaders[kGrShaderTypeCount]; + SkSL::Program::Inputs inputs[kGrShaderTypeCount]; + + GrPersistentCacheUtils::UnpackCachedShaders(cached, shaders, inputs, kGrShaderTypeCount); + + SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT, + fVS, + &outShaderModules[kVertex_GrShaderType], + &outStageInfo[0], + shaders[kVertex_GrShaderType], + inputs[kVertex_GrShaderType])); + + SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT, + fFS, + &outShaderModules[kFragment_GrShaderType], + &outStageInfo[1], + shaders[kFragment_GrShaderType], + inputs[kFragment_GrShaderType])); + + if (!shaders[kGeometry_GrShaderType].empty()) { + SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT, + fGS, + &outShaderModules[kGeometry_GrShaderType], + &outStageInfo[2], + shaders[kGeometry_GrShaderType], + inputs[kGeometry_GrShaderType])); + return 3; + } else { + return 2; + } +} + +void GrVkPipelineStateBuilder::storeShadersInCache(const SkSL::String shaders[], + const SkSL::Program::Inputs inputs[], + bool isSkSL) { + const Desc* desc = static_cast(this->desc()); + // Here we shear off the Vk-specific portion of the Desc in order to create the + // persistent key. This is bc Vk only caches the SPIRV code, not the fully compiled + // program, and that only depends on the base GrProgramDesc data. + sk_sp key = SkData::MakeWithoutCopy(desc->asKey(), desc->shaderKeyLength()); + sk_sp data = GrPersistentCacheUtils::PackCachedShaders(isSkSL ? kSKSL_Tag : kSPIRV_Tag, + shaders, + inputs, kGrShaderTypeCount); + this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data); +} + +GrVkPipelineState* GrVkPipelineStateBuilder::finalize(const GrStencilSettings& stencil, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass, + Desc* desc) { + VkDescriptorSetLayout dsLayout[2]; + VkPipelineLayout pipelineLayout; + VkShaderModule shaderModules[kGrShaderTypeCount] = { VK_NULL_HANDLE, + VK_NULL_HANDLE, + VK_NULL_HANDLE }; + + GrVkResourceProvider& resourceProvider = fGpu->resourceProvider(); + // These layouts are not owned by the PipelineStateBuilder and thus should not be destroyed + dsLayout[GrVkUniformHandler::kUniformBufferDescSet] = resourceProvider.getUniformDSLayout(); + + GrVkDescriptorSetManager::Handle samplerDSHandle; + resourceProvider.getSamplerDescriptorSetHandle(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + fUniformHandler, &samplerDSHandle); + dsLayout[GrVkUniformHandler::kSamplerDescSet] = + resourceProvider.getSamplerDSLayout(samplerDSHandle); + + // Create the VkPipelineLayout + VkPipelineLayoutCreateInfo layoutCreateInfo; + memset(&layoutCreateInfo, 0, sizeof(VkPipelineLayoutCreateFlags)); + layoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layoutCreateInfo.pNext = 0; + layoutCreateInfo.flags = 0; + layoutCreateInfo.setLayoutCount = 2; + layoutCreateInfo.pSetLayouts = dsLayout; + layoutCreateInfo.pushConstantRangeCount = 0; + layoutCreateInfo.pPushConstantRanges = nullptr; + + GR_VK_CALL_ERRCHECK(fGpu->vkInterface(), CreatePipelineLayout(fGpu->device(), + &layoutCreateInfo, + nullptr, + &pipelineLayout)); + + // We need to enable the following extensions so that the compiler can correctly make spir-v + // from our glsl shaders. + fVS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n"); + fFS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n"); + fVS.extensions().appendf("#extension GL_ARB_shading_language_420pack : enable\n"); + fFS.extensions().appendf("#extension GL_ARB_shading_language_420pack : enable\n"); + + this->finalizeShaders(); + + VkPipelineShaderStageCreateInfo shaderStageInfo[3]; + SkSL::Program::Settings settings; + settings.fCaps = this->caps()->shaderCaps(); + settings.fVkCaps = &this->gpu()->vkCaps(); + settings.fFlipY = this->origin() != kTopLeft_GrSurfaceOrigin; + settings.fSharpenTextures = + this->gpu()->getContext()->priv().options().fSharpenMipmappedTextures; + settings.fRTHeightOffset = fUniformHandler.getRTHeightOffset(); + SkASSERT(!this->fragColorIsInOut()); + + sk_sp cached; + SkReader32 reader; + SkFourByteTag shaderType = 0; + auto persistentCache = fGpu->getContext()->priv().getPersistentCache(); + if (persistentCache) { + // Here we shear off the Vk-specific portion of the Desc in order to create the + // persistent key. This is bc Vk only caches the SPIRV code, not the fully compiled + // program, and that only depends on the base GrProgramDesc data. + sk_sp key = SkData::MakeWithoutCopy(desc->asKey(), desc->shaderKeyLength()); + cached = persistentCache->load(*key); + if (cached) { + reader.setMemory(cached->data(), cached->size()); + shaderType = reader.readU32(); + } + } + + int numShaderStages = 0; + if (kSPIRV_Tag == shaderType) { + numShaderStages = this->loadShadersFromCache(&reader, shaderModules, shaderStageInfo); + } else { + numShaderStages = 2; // We always have at least vertex and fragment stages. + SkSL::String shaders[kGrShaderTypeCount]; + SkSL::Program::Inputs inputs[kGrShaderTypeCount]; + + SkSL::String* sksl[kGrShaderTypeCount] = { + &fVS.fCompilerString, + &fGS.fCompilerString, + &fFS.fCompilerString, + }; + SkSL::String cached_sksl[kGrShaderTypeCount]; + if (kSKSL_Tag == shaderType) { + GrPersistentCacheUtils::UnpackCachedShaders(&reader, cached_sksl, inputs, + kGrShaderTypeCount); + for (int i = 0; i < kGrShaderTypeCount; ++i) { + sksl[i] = &cached_sksl[i]; + } + } + + bool success = this->createVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT, + *sksl[kVertex_GrShaderType], + &shaderModules[kVertex_GrShaderType], + &shaderStageInfo[0], + settings, + desc, + &shaders[kVertex_GrShaderType], + &inputs[kVertex_GrShaderType]); + + success = success && this->createVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT, + *sksl[kFragment_GrShaderType], + &shaderModules[kFragment_GrShaderType], + &shaderStageInfo[1], + settings, + desc, + &shaders[kFragment_GrShaderType], + &inputs[kFragment_GrShaderType]); + + if (this->primitiveProcessor().willUseGeoShader()) { + success = success && this->createVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT, + *sksl[kGeometry_GrShaderType], + &shaderModules[kGeometry_GrShaderType], + &shaderStageInfo[2], + settings, + desc, + &shaders[kGeometry_GrShaderType], + &inputs[kGeometry_GrShaderType]); + ++numShaderStages; + } + + if (!success) { + for (int i = 0; i < kGrShaderTypeCount; ++i) { + if (shaderModules[i]) { + GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(), + shaderModules[i], nullptr)); + } + } + GR_VK_CALL(fGpu->vkInterface(), DestroyPipelineLayout(fGpu->device(), pipelineLayout, + nullptr)); + return nullptr; + } + + if (persistentCache && !cached) { + bool isSkSL = false; + if (fGpu->getContext()->priv().options().fShaderCacheStrategy == + GrContextOptions::ShaderCacheStrategy::kSkSL) { + for (int i = 0; i < kGrShaderTypeCount; ++i) { + shaders[i] = GrShaderUtils::PrettyPrint(*sksl[i]); + } + isSkSL = true; + } + this->storeShadersInCache(shaders, inputs, isSkSL); + } + } + GrVkPipeline* pipeline = resourceProvider.createPipeline(fProgramInfo, stencil, + shaderStageInfo, numShaderStages, primitiveType, compatibleRenderPass, pipelineLayout); + for (int i = 0; i < kGrShaderTypeCount; ++i) { + // This if check should not be needed since calling destroy on a VK_NULL_HANDLE is allowed. + // However this is causing a crash in certain drivers (e.g. NVidia). + if (shaderModules[i]) { + GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(), shaderModules[i], + nullptr)); + } + } + + if (!pipeline) { + GR_VK_CALL(fGpu->vkInterface(), DestroyPipelineLayout(fGpu->device(), pipelineLayout, + nullptr)); + return nullptr; + } + + return new GrVkPipelineState(fGpu, + pipeline, + samplerDSHandle, + fUniformHandles, + fUniformHandler.fUniforms, + fUniformHandler.fCurrentUBOOffset, + fUniformHandler.fSamplers, + std::move(fGeometryProcessor), + std::move(fXferProcessor), + std::move(fFragmentProcessors), + fFragmentProcessorCnt); +} + +////////////////////////////////////////////////////////////////////////////// + +bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc, + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + const GrStencilSettings& stencil, + GrPrimitiveType primitiveType, + GrVkGpu* gpu) { + if (!GrProgramDesc::Build(desc, renderTarget, programInfo, primitiveType, gpu)) { + return false; + } + + GrProcessorKeyBuilder b(&desc->key()); + + b.add32(GrVkGpu::kShader_PersistentCacheKeyType); + int keyLength = desc->key().count(); + SkASSERT(0 == (keyLength % 4)); + desc->fShaderKeyLength = SkToU32(keyLength); + + GrVkRenderTarget* vkRT = (GrVkRenderTarget*)renderTarget; + vkRT->simpleRenderPass()->genKey(&b); + + stencil.genKey(&b); + + b.add32(programInfo.pipeline().getBlendInfoKey()); + + b.add32((uint32_t)primitiveType); + + return true; +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.h b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.h new file mode 100644 index 0000000000..0ba7fc5001 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateBuilder.h @@ -0,0 +1,121 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkPipelineStateBuilder_DEFINED +#define GrVkPipelineStateBuilder_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrPipeline.h" +#include "src/gpu/GrProgramDesc.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/vk/GrVkPipelineState.h" +#include "src/gpu/vk/GrVkUniformHandler.h" +#include "src/gpu/vk/GrVkVaryingHandler.h" +#include "src/sksl/SkSLCompiler.h" + +class GrVkGpu; +class GrVkRenderPass; +class SkReader32; + +class GrVkPipelineStateBuilder : public GrGLSLProgramBuilder { +public: + /** + * For Vulkan we want to cache the entire VkPipeline for reuse of draws. The Desc here holds all + * the information needed to differentiate one pipeline from another. + * + * The GrProgramDesc contains all the information need to create the actual shaders for the + * pipeline. + * + * For Vulkan we need to add to the GrProgramDesc to include the rest of the state on the + * pipline. This includes stencil settings, blending information, render pass format, draw face + * information, and primitive type. Note that some state is set dynamically on the pipeline for + * each draw and thus is not included in this descriptor. This includes the viewport, scissor, + * and blend constant. + */ + class Desc : public GrProgramDesc { + public: + static bool Build(Desc*, + GrRenderTarget*, + const GrProgramInfo&, + const GrStencilSettings&, + GrPrimitiveType primitiveType, + GrVkGpu* gpu); + + size_t shaderKeyLength() const { return fShaderKeyLength; } + + private: + size_t fShaderKeyLength; + + typedef GrProgramDesc INHERITED; + }; + + /** Generates a pipeline state. + * + * The GrVkPipelineState implements what is specified in the GrPipeline and GrPrimitiveProcessor + * as input. After successful generation, the builder result objects are available to be used. + * This function may modify the program key by setting the surface origin key to 0 (unspecified) + * if it turns out the program does not care about the surface origin. + * @return true if generation was successful. + */ + static GrVkPipelineState* CreatePipelineState(GrVkGpu*, + GrRenderTarget*, + const GrProgramInfo&, + const GrStencilSettings&, + GrPrimitiveType, + Desc*, + VkRenderPass compatibleRenderPass); + + const GrCaps* caps() const override; + + GrVkGpu* gpu() const { return fGpu; } + + void finalizeFragmentOutputColor(GrShaderVar& outputColor) override; + void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) override; + +private: + GrVkPipelineStateBuilder(GrVkGpu*, GrRenderTarget*, const GrProgramInfo&, GrProgramDesc*); + + GrVkPipelineState* finalize(const GrStencilSettings&, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass, + Desc*); + + // returns number of shader stages + int loadShadersFromCache(SkReader32* cached, VkShaderModule outShaderModules[], + VkPipelineShaderStageCreateInfo* outStageInfo); + + void storeShadersInCache(const SkSL::String shaders[], const SkSL::Program::Inputs inputs[], + bool isSkSL); + + bool createVkShaderModule(VkShaderStageFlagBits stage, + const SkSL::String& sksl, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo, + const SkSL::Program::Settings& settings, + Desc* desc, + SkSL::String* outSPIRV, + SkSL::Program::Inputs* outInputs); + + bool installVkShaderModule(VkShaderStageFlagBits stage, + const GrGLSLShaderBuilder& builder, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo, + SkSL::String spirv, + SkSL::Program::Inputs inputs); + + GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; } + const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; } + GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; } + + GrVkGpu* fGpu; + GrVkVaryingHandler fVaryingHandler; + GrVkUniformHandler fUniformHandler; + + typedef GrGLSLProgramBuilder INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateCache.cpp b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateCache.cpp new file mode 100644 index 0000000000..da6a69f3f3 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateCache.cpp @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/gpu/GrContextOptions.h" +#include "src/core/SkOpts.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrProcessor.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/GrStencilSettings.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkPipelineState.h" +#include "src/gpu/vk/GrVkPipelineStateBuilder.h" +#include "src/gpu/vk/GrVkResourceProvider.h" + +#ifdef GR_PIPELINE_STATE_CACHE_STATS +// Display pipeline state cache usage +static const bool c_DisplayVkPipelineCache{false}; +#endif + +struct GrVkResourceProvider::PipelineStateCache::Entry { + Entry(GrVkGpu* gpu, GrVkPipelineState* pipelineState) + : fGpu(gpu) + , fPipelineState(pipelineState) {} + + ~Entry() { + if (fPipelineState) { + fPipelineState->freeGPUResources(fGpu); + } + } + + GrVkGpu* fGpu; + std::unique_ptr fPipelineState; +}; + +GrVkResourceProvider::PipelineStateCache::PipelineStateCache(GrVkGpu* gpu) + : fMap(gpu->getContext()->priv().options().fRuntimeProgramCacheSize) + , fGpu(gpu) +#ifdef GR_PIPELINE_STATE_CACHE_STATS + , fTotalRequests(0) + , fCacheMisses(0) +#endif +{} + +GrVkResourceProvider::PipelineStateCache::~PipelineStateCache() { + SkASSERT(0 == fMap.count()); + // dump stats +#ifdef GR_PIPELINE_STATE_CACHE_STATS + if (c_DisplayVkPipelineCache) { + SkDebugf("--- Pipeline State Cache ---\n"); + SkDebugf("Total requests: %d\n", fTotalRequests); + SkDebugf("Cache misses: %d\n", fCacheMisses); + SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? + 100.f * fCacheMisses / fTotalRequests : + 0.f); + SkDebugf("---------------------\n"); + } +#endif +} + +void GrVkResourceProvider::PipelineStateCache::abandon() { + fMap.foreach([](std::unique_ptr* e) { + (*e)->fPipelineState->abandonGPUResources(); + (*e)->fPipelineState = nullptr; + }); + fMap.reset(); +} + +void GrVkResourceProvider::PipelineStateCache::release() { + fMap.reset(); +} + +GrVkPipelineState* GrVkResourceProvider::PipelineStateCache::refPipelineState( + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass) { +#ifdef GR_PIPELINE_STATE_CACHE_STATS + ++fTotalRequests; +#endif + GrStencilSettings stencil; + if (programInfo.pipeline().isStencilEnabled()) { + // TODO: attach stencil and create settings during render target flush. + SkASSERT(renderTarget->renderTargetPriv().getStencilAttachment()); + stencil.reset(*programInfo.pipeline().getUserStencil(), + programInfo.pipeline().hasStencilClip(), + renderTarget->renderTargetPriv().numStencilBits()); + } + + // TODO: can this be unified between GL, Vk and Mtl? + // Get GrVkProgramDesc + GrVkPipelineStateBuilder::Desc desc; + if (!GrVkPipelineStateBuilder::Desc::Build(&desc, renderTarget, programInfo, stencil, + primitiveType, fGpu)) { + GrCapsDebugf(fGpu->caps(), "Failed to build vk program descriptor!\n"); + return nullptr; + } + + std::unique_ptr* entry = fMap.find(desc); + if (!entry) { +#ifdef GR_PIPELINE_STATE_CACHE_STATS + ++fCacheMisses; +#endif + GrVkPipelineState* pipelineState(GrVkPipelineStateBuilder::CreatePipelineState( + fGpu, renderTarget, programInfo, + stencil, primitiveType, &desc, compatibleRenderPass)); + if (!pipelineState) { + return nullptr; + } + entry = fMap.insert(desc, std::unique_ptr(new Entry(fGpu, pipelineState))); + return (*entry)->fPipelineState.get(); + } + return (*entry)->fPipelineState.get(); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.cpp b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.cpp new file mode 100644 index 0000000000..cbef926177 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.cpp @@ -0,0 +1,346 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkPipelineStateDataManager.h" + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkUniformBuffer.h" + +GrVkPipelineStateDataManager::GrVkPipelineStateDataManager(const UniformInfoArray& uniforms, + uint32_t uniformSize) + : fUniformSize(uniformSize) + , fUniformsDirty(false) { + fUniformData.reset(uniformSize); + int count = uniforms.count(); + fUniforms.push_back_n(count); + // We must add uniforms in same order is the UniformInfoArray so that UniformHandles already + // owned by other objects will still match up here. + for (int i = 0; i < count; i++) { + Uniform& uniform = fUniforms[i]; + const GrVkUniformHandler::UniformInfo uniformInfo = uniforms[i]; + SkASSERT(GrShaderVar::kNonArray == uniformInfo.fVariable.getArrayCount() || + uniformInfo.fVariable.getArrayCount() > 0); + SkDEBUGCODE( + uniform.fArrayCount = uniformInfo.fVariable.getArrayCount(); + uniform.fType = uniformInfo.fVariable.getType(); + ) + + uniform.fOffset = uniformInfo.fUBOffset; + } +} + +void* GrVkPipelineStateDataManager::getBufferPtrAndMarkDirty(const Uniform& uni) const { + fUniformsDirty = true; + return static_cast(fUniformData.get())+uni.fOffset; +} + +void GrVkPipelineStateDataManager::set1i(UniformHandle u, int32_t i) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt_GrSLType || uni.fType == kShort_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + memcpy(buffer, &i, sizeof(int32_t)); +} + +void GrVkPipelineStateDataManager::set1iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt_GrSLType || uni.fType == kShort_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[i]; + memcpy(buffer, curVec, sizeof(int32_t)); + buffer = static_cast(buffer) + 4*sizeof(int32_t); + } +} + +void GrVkPipelineStateDataManager::set1f(UniformHandle u, float v0) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat_GrSLType || uni.fType == kHalf_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + memcpy(buffer, &v0, sizeof(float)); +} + +void GrVkPipelineStateDataManager::set1fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat_GrSLType || uni.fType == kHalf_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[i]; + memcpy(buffer, curVec, sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrVkPipelineStateDataManager::set2i(UniformHandle u, int32_t i0, int32_t i1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt2_GrSLType || uni.fType == kShort2_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[2] = { i0, i1 }; + memcpy(buffer, v, 2 * sizeof(int32_t)); +} + +void GrVkPipelineStateDataManager::set2iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt2_GrSLType || uni.fType == kShort2_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[2 * i]; + memcpy(buffer, curVec, 2 * sizeof(int32_t)); + buffer = static_cast(buffer) + 4*sizeof(int32_t); + } +} + +void GrVkPipelineStateDataManager::set2f(UniformHandle u, float v0, float v1) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2_GrSLType || uni.fType == kHalf2_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + float v[2] = { v0, v1 }; + memcpy(buffer, v, 2 * sizeof(float)); +} + +void GrVkPipelineStateDataManager::set2fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2_GrSLType || uni.fType == kHalf2_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[2 * i]; + memcpy(buffer, curVec, 2 * sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrVkPipelineStateDataManager::set3i(UniformHandle u, + int32_t i0, + int32_t i1, + int32_t i2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt3_GrSLType || uni.fType == kShort3_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[3] = { i0, i1, i2 }; + memcpy(buffer, v, 3 * sizeof(int32_t)); +} + +void GrVkPipelineStateDataManager::set3iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt3_GrSLType || uni.fType == kShort3_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[3 * i]; + memcpy(buffer, curVec, 3 * sizeof(int32_t)); + buffer = static_cast(buffer) + 4*sizeof(int32_t); + } +} + +void GrVkPipelineStateDataManager::set3f(UniformHandle u, float v0, float v1, float v2) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat3_GrSLType || uni.fType == kHalf3_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + float v[3] = { v0, v1, v2 }; + memcpy(buffer, v, 3 * sizeof(float)); +} + +void GrVkPipelineStateDataManager::set3fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat3_GrSLType || uni.fType == kHalf3_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + for (int i = 0; i < arrayCount; ++i) { + const float* curVec = &v[3 * i]; + memcpy(buffer, curVec, 3 * sizeof(float)); + buffer = static_cast(buffer) + 4*sizeof(float); + } +} + +void GrVkPipelineStateDataManager::set4i(UniformHandle u, + int32_t i0, + int32_t i1, + int32_t i2, + int32_t i3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt4_GrSLType || uni.fType == kShort4_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + int32_t v[4] = { i0, i1, i2, i3 }; + memcpy(buffer, v, 4 * sizeof(int32_t)); +} + +void GrVkPipelineStateDataManager::set4iv(UniformHandle u, + int arrayCount, + const int32_t v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kInt4_GrSLType || uni.fType == kShort4_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(int32_t) == 4); + for (int i = 0; i < arrayCount; ++i) { + const int32_t* curVec = &v[4 * i]; + memcpy(buffer, curVec, 4 * sizeof(int32_t)); + buffer = static_cast(buffer) + 4*sizeof(int32_t); + } +} + +void GrVkPipelineStateDataManager::set4f(UniformHandle u, + float v0, + float v1, + float v2, + float v3) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat4_GrSLType || uni.fType == kHalf4_GrSLType); + SkASSERT(GrShaderVar::kNonArray == uni.fArrayCount); + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + float v[4] = { v0, v1, v2, v3 }; + memcpy(buffer, v, 4 * sizeof(float)); +} + +void GrVkPipelineStateDataManager::set4fv(UniformHandle u, + int arrayCount, + const float v[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat4_GrSLType || uni.fType == kHalf4_GrSLType); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = this->getBufferPtrAndMarkDirty(uni); + SkASSERT(sizeof(float) == 4); + memcpy(buffer, v, arrayCount * 4 * sizeof(float)); +} + +void GrVkPipelineStateDataManager::setMatrix2f(UniformHandle u, const float matrix[]) const { + this->setMatrices<2>(u, 1, matrix); +} + +void GrVkPipelineStateDataManager::setMatrix2fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<2>(u, arrayCount, m); +} + +void GrVkPipelineStateDataManager::setMatrix3f(UniformHandle u, const float matrix[]) const { + this->setMatrices<3>(u, 1, matrix); +} + +void GrVkPipelineStateDataManager::setMatrix3fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<3>(u, arrayCount, m); +} + +void GrVkPipelineStateDataManager::setMatrix4f(UniformHandle u, const float matrix[]) const { + this->setMatrices<4>(u, 1, matrix); +} + +void GrVkPipelineStateDataManager::setMatrix4fv(UniformHandle u, + int arrayCount, + const float m[]) const { + this->setMatrices<4>(u, arrayCount, m); +} + +template struct set_uniform_matrix; + +template inline void GrVkPipelineStateDataManager::setMatrices(UniformHandle u, + int arrayCount, + const float matrices[]) const { + const Uniform& uni = fUniforms[u.toIndex()]; + SkASSERT(uni.fType == kFloat2x2_GrSLType + (N - 2) || + uni.fType == kHalf2x2_GrSLType + (N - 2)); + SkASSERT(arrayCount > 0); + SkASSERT(arrayCount <= uni.fArrayCount || + (1 == arrayCount && GrShaderVar::kNonArray == uni.fArrayCount)); + + void* buffer = fUniformData.get(); + fUniformsDirty = true; + + set_uniform_matrix::set(buffer, uni.fOffset, arrayCount, matrices); +} + +template struct set_uniform_matrix { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + for (int i = 0; i < count; ++i) { + const float* matrix = &matrices[N * N * i]; + for (int j = 0; j < N; ++j) { + memcpy(buffer, &matrix[j * N], N * sizeof(float)); + buffer = static_cast(buffer) + 4 * sizeof(float); + } + } + } +}; + +template<> struct set_uniform_matrix<4> { + inline static void set(void* buffer, int uniformOffset, int count, const float matrices[]) { + GR_STATIC_ASSERT(sizeof(float) == 4); + buffer = static_cast(buffer) + uniformOffset; + memcpy(buffer, matrices, count * 16 * sizeof(float)); + } +}; + +bool GrVkPipelineStateDataManager::uploadUniformBuffers(GrVkGpu* gpu, + GrVkUniformBuffer* buffer) const { + bool updatedBuffer = false; + if (buffer && fUniformsDirty) { + SkAssertResult(buffer->updateData(gpu, fUniformData.get(), + fUniformSize, &updatedBuffer)); + fUniformsDirty = false; + } + + return updatedBuffer; +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.h b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.h new file mode 100644 index 0000000000..91d50924b6 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkPipelineStateDataManager.h @@ -0,0 +1,85 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkPipelineStateDataManager_DEFINED +#define GrVkPipelineStateDataManager_DEFINED + +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/core/SkAutoMalloc.h" +#include "src/gpu/vk/GrVkUniformHandler.h" + +class GrVkGpu; +class GrVkUniformBuffer; + +class GrVkPipelineStateDataManager : public GrGLSLProgramDataManager { +public: + typedef GrVkUniformHandler::UniformInfoArray UniformInfoArray; + + GrVkPipelineStateDataManager(const UniformInfoArray&, + uint32_t uniformSize); + + void set1i(UniformHandle, int32_t) const override; + void set1iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set1f(UniformHandle, float v0) const override; + void set1fv(UniformHandle, int arrayCount, const float v[]) const override; + void set2i(UniformHandle, int32_t, int32_t) const override; + void set2iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set2f(UniformHandle, float, float) const override; + void set2fv(UniformHandle, int arrayCount, const float v[]) const override; + void set3i(UniformHandle, int32_t, int32_t, int32_t) const override; + void set3iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set3f(UniformHandle, float, float, float) const override; + void set3fv(UniformHandle, int arrayCount, const float v[]) const override; + void set4i(UniformHandle, int32_t, int32_t, int32_t, int32_t) const override; + void set4iv(UniformHandle, int arrayCount, const int32_t v[]) const override; + void set4f(UniformHandle, float, float, float, float) const override; + void set4fv(UniformHandle, int arrayCount, const float v[]) const override; + // matrices are column-major, the first two upload a single matrix, the latter two upload + // arrayCount matrices into a uniform array. + void setMatrix2f(UniformHandle, const float matrix[]) const override; + void setMatrix3f(UniformHandle, const float matrix[]) const override; + void setMatrix4f(UniformHandle, const float matrix[]) const override; + void setMatrix2fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix3fv(UniformHandle, int arrayCount, const float matrices[]) const override; + void setMatrix4fv(UniformHandle, int arrayCount, const float matrices[]) const override; + + // for nvpr only + void setPathFragmentInputTransform(VaryingHandle u, int components, + const SkMatrix& matrix) const override { + SK_ABORT("Only supported in NVPR, which is not in vulkan"); + } + + // Returns true if either the geometry or fragment buffers needed to generate a new underlying + // VkBuffer object in order upload data. If true is returned, this is a signal to the caller + // that they will need to update the descriptor set that is using these buffers. + bool uploadUniformBuffers(GrVkGpu* gpu, + GrVkUniformBuffer* buffer) const; +private: + struct Uniform { + uint32_t fOffset; + SkDEBUGCODE( + GrSLType fType; + int fArrayCount; + ); + }; + + template inline void setMatrices(UniformHandle, int arrayCount, + const float matrices[]) const; + + void* getBufferPtrAndMarkDirty(const Uniform& uni) const; + + uint32_t fUniformSize; + + SkTArray fUniforms; + + mutable SkAutoMalloc fUniformData; + mutable bool fUniformsDirty; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkRenderPass.cpp b/gfx/skia/skia/src/gpu/vk/GrVkRenderPass.cpp new file mode 100644 index 0000000000..d73ae6149a --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkRenderPass.cpp @@ -0,0 +1,266 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkRenderPass.h" + +#include "src/gpu/GrProcessor.h" +#include "src/gpu/vk/GrVkFramebuffer.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkRenderTarget.h" +#include "src/gpu/vk/GrVkUtil.h" + +typedef GrVkRenderPass::AttachmentsDescriptor::AttachmentDesc AttachmentDesc; + +void setup_vk_attachment_description(VkAttachmentDescription* attachment, + const AttachmentDesc& desc, + VkImageLayout layout) { + attachment->flags = 0; + attachment->format = desc.fFormat; + SkAssertResult(GrSampleCountToVkSampleCount(desc.fSamples, &attachment->samples)); + switch (layout) { + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + attachment->loadOp = desc.fLoadStoreOps.fLoadOp; + attachment->storeOp = desc.fLoadStoreOps.fStoreOp; + attachment->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + break; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment->storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment->stencilLoadOp = desc.fLoadStoreOps.fLoadOp; + attachment->stencilStoreOp = desc.fLoadStoreOps.fStoreOp; + break; + default: + SK_ABORT("Unexpected attachment layout"); + } + + attachment->initialLayout = layout; + attachment->finalLayout = layout; +} + +void GrVkRenderPass::initSimple(const GrVkGpu* gpu, const GrVkRenderTarget& target) { + static const GrVkRenderPass::LoadStoreOps kBasicLoadStoreOps(VK_ATTACHMENT_LOAD_OP_LOAD, + VK_ATTACHMENT_STORE_OP_STORE); + + this->init(gpu, target, kBasicLoadStoreOps, kBasicLoadStoreOps); +} + +void GrVkRenderPass::init(const GrVkGpu* gpu, + const LoadStoreOps& colorOp, + const LoadStoreOps& stencilOp) { + uint32_t numAttachments = fAttachmentsDescriptor.fAttachmentCount; + // Attachment descriptions to be set on the render pass + SkTArray attachments(numAttachments); + attachments.reset(numAttachments); + memset(attachments.begin(), 0, numAttachments * sizeof(VkAttachmentDescription)); + + // Refs to attachments on the render pass (as described by teh VkAttachmentDescription above), + // that are used by the subpass. + VkAttachmentReference colorRef; + VkAttachmentReference stencilRef; + uint32_t currentAttachment = 0; + + // Go through each of the attachment types (color, stencil) and set the necessary + // on the various Vk structs. + VkSubpassDescription subpassDesc; + memset(&subpassDesc, 0, sizeof(VkSubpassDescription)); + subpassDesc.flags = 0; + subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDesc.inputAttachmentCount = 0; + subpassDesc.pInputAttachments = nullptr; + subpassDesc.pResolveAttachments = nullptr; + + if (fAttachmentFlags & kColor_AttachmentFlag) { + // set up color attachment + fAttachmentsDescriptor.fColor.fLoadStoreOps = colorOp; + setup_vk_attachment_description(&attachments[currentAttachment], + fAttachmentsDescriptor.fColor, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + // setup subpass use of attachment + colorRef.attachment = currentAttachment++; + colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + subpassDesc.colorAttachmentCount = 1; + + if (VK_ATTACHMENT_LOAD_OP_CLEAR == colorOp.fLoadOp) { + fClearValueCount = colorRef.attachment + 1; + } + } else { + // I don't think there should ever be a time where we don't have a color attachment + SkASSERT(false); + colorRef.attachment = VK_ATTACHMENT_UNUSED; + colorRef.layout = VK_IMAGE_LAYOUT_UNDEFINED; + subpassDesc.colorAttachmentCount = 0; + } + subpassDesc.pColorAttachments = &colorRef; + + if (fAttachmentFlags & kStencil_AttachmentFlag) { + // set up stencil attachment + fAttachmentsDescriptor.fStencil.fLoadStoreOps = stencilOp; + setup_vk_attachment_description(&attachments[currentAttachment], + fAttachmentsDescriptor.fStencil, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + // setup subpass use of attachment + stencilRef.attachment = currentAttachment++; + stencilRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + if (VK_ATTACHMENT_LOAD_OP_CLEAR == stencilOp.fLoadOp) { + fClearValueCount = SkTMax(fClearValueCount, stencilRef.attachment + 1); + } + } else { + stencilRef.attachment = VK_ATTACHMENT_UNUSED; + stencilRef.layout = VK_IMAGE_LAYOUT_UNDEFINED; + } + subpassDesc.pDepthStencilAttachment = &stencilRef; + + subpassDesc.preserveAttachmentCount = 0; + subpassDesc.pPreserveAttachments = nullptr; + + SkASSERT(numAttachments == currentAttachment); + + // Create the VkRenderPass compatible with the attachment descriptions above + VkRenderPassCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkRenderPassCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.attachmentCount = numAttachments; + createInfo.pAttachments = attachments.begin(); + createInfo.subpassCount = 1; + createInfo.pSubpasses = &subpassDesc; + createInfo.dependencyCount = 0; + createInfo.pDependencies = nullptr; + + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), CreateRenderPass(gpu->device(), + &createInfo, + nullptr, + &fRenderPass)); + + // Get granularity for this render pass + GR_VK_CALL(gpu->vkInterface(), GetRenderAreaGranularity(gpu->device(), + fRenderPass, + &fGranularity)); +} + +void GrVkRenderPass::init(const GrVkGpu* gpu, + const GrVkRenderPass& compatibleRenderPass, + const LoadStoreOps& colorOp, + const LoadStoreOps& stencilOp) { + fAttachmentFlags = compatibleRenderPass.fAttachmentFlags; + fAttachmentsDescriptor = compatibleRenderPass.fAttachmentsDescriptor; + this->init(gpu, colorOp, stencilOp); +} + +void GrVkRenderPass::init(const GrVkGpu* gpu, + const GrVkRenderTarget& target, + const LoadStoreOps& colorOp, + const LoadStoreOps& stencilOp) { + // Get attachment information from render target. This includes which attachments the render + // target has (color, stencil) and the attachments format and sample count. + target.getAttachmentsDescriptor(&fAttachmentsDescriptor, &fAttachmentFlags); + this->init(gpu, colorOp, stencilOp); +} + +void GrVkRenderPass::freeGPUData(GrVkGpu* gpu) const { + if (!(fAttachmentFlags & kExternal_AttachmentFlag)) { + GR_VK_CALL(gpu->vkInterface(), DestroyRenderPass(gpu->device(), fRenderPass, nullptr)); + } +} + +bool GrVkRenderPass::colorAttachmentIndex(uint32_t* index) const { + *index = fColorAttachmentIndex; + if ((fAttachmentFlags & kColor_AttachmentFlag) || + (fAttachmentFlags & kExternal_AttachmentFlag)) { + return true; + } + return false; +} + +// Works under the assumption that stencil attachment will always be after the color and resolve +// attachment. +bool GrVkRenderPass::stencilAttachmentIndex(uint32_t* index) const { + *index = 0; + if (fAttachmentFlags & kColor_AttachmentFlag) { + ++(*index); + } + if (fAttachmentFlags & kStencil_AttachmentFlag) { + return true; + } + return false; +} + +bool GrVkRenderPass::isCompatible(const AttachmentsDescriptor& desc, + const AttachmentFlags& flags) const { + SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag)); + if (flags != fAttachmentFlags) { + return false; + } + + if (fAttachmentFlags & kColor_AttachmentFlag) { + if (!fAttachmentsDescriptor.fColor.isCompatible(desc.fColor)) { + return false; + } + } + if (fAttachmentFlags & kStencil_AttachmentFlag) { + if (!fAttachmentsDescriptor.fStencil.isCompatible(desc.fStencil)) { + return false; + } + } + + return true; +} + +bool GrVkRenderPass::isCompatible(const GrVkRenderTarget& target) const { + SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag)); + AttachmentsDescriptor desc; + AttachmentFlags flags; + target.getAttachmentsDescriptor(&desc, &flags); + + return this->isCompatible(desc, flags); +} + +bool GrVkRenderPass::isCompatible(const GrVkRenderPass& renderPass) const { + SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag)); + return this->isCompatible(renderPass.fAttachmentsDescriptor, renderPass.fAttachmentFlags); +} + +bool GrVkRenderPass::isCompatibleExternalRP(VkRenderPass renderPass) const { + SkASSERT(fAttachmentFlags & kExternal_AttachmentFlag); + return fRenderPass == renderPass; +} + +bool GrVkRenderPass::equalLoadStoreOps(const LoadStoreOps& colorOps, + const LoadStoreOps& stencilOps) const { + SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag)); + if (fAttachmentFlags & kColor_AttachmentFlag) { + if (fAttachmentsDescriptor.fColor.fLoadStoreOps != colorOps) { + return false; + } + } + if (fAttachmentFlags & kStencil_AttachmentFlag) { + if (fAttachmentsDescriptor.fStencil.fLoadStoreOps != stencilOps) { + return false; + } + } + return true; +} + +void GrVkRenderPass::genKey(GrProcessorKeyBuilder* b) const { + b->add32(fAttachmentFlags); + if (fAttachmentFlags & kColor_AttachmentFlag) { + b->add32(fAttachmentsDescriptor.fColor.fFormat); + b->add32(fAttachmentsDescriptor.fColor.fSamples); + } + if (fAttachmentFlags & kStencil_AttachmentFlag) { + b->add32(fAttachmentsDescriptor.fStencil.fFormat); + b->add32(fAttachmentsDescriptor.fStencil.fSamples); + } + if (fAttachmentFlags & kExternal_AttachmentFlag) { + SkASSERT(!(fAttachmentFlags & ~kExternal_AttachmentFlag)); + uint64_t handle = (uint64_t)fRenderPass; + b->add32((uint32_t)(handle & 0xFFFFFFFF)); + b->add32((uint32_t)(handle>>32)); + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkRenderPass.h b/gfx/skia/skia/src/gpu/vk/GrVkRenderPass.h new file mode 100644 index 0000000000..725b1962cd --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkRenderPass.h @@ -0,0 +1,158 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkRenderPass_DEFINED +#define GrVkRenderPass_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrProcessorKeyBuilder; +class GrVkGpu; +class GrVkRenderTarget; + +class GrVkRenderPass : public GrVkResource { +public: + GrVkRenderPass() : INHERITED(), fRenderPass(VK_NULL_HANDLE), fClearValueCount(0) {} + + // Used when importing an external render pass. In this case we have to explicitly be told the + // color attachment index + explicit GrVkRenderPass(VkRenderPass renderPass, uint32_t colorAttachmentIndex) + : INHERITED() + , fRenderPass(renderPass) + , fAttachmentFlags(kExternal_AttachmentFlag) + , fClearValueCount(0) + , fColorAttachmentIndex(colorAttachmentIndex) {} + + struct LoadStoreOps { + VkAttachmentLoadOp fLoadOp; + VkAttachmentStoreOp fStoreOp; + + LoadStoreOps(VkAttachmentLoadOp loadOp, VkAttachmentStoreOp storeOp) + : fLoadOp(loadOp) + , fStoreOp(storeOp) {} + + bool operator==(const LoadStoreOps& right) const { + return fLoadOp == right.fLoadOp && fStoreOp == right.fStoreOp; + } + + bool operator!=(const LoadStoreOps& right) const { + return !(*this == right); + } + }; + + void initSimple(const GrVkGpu* gpu, const GrVkRenderTarget& target); + void init(const GrVkGpu* gpu, + const GrVkRenderTarget& target, + const LoadStoreOps& colorOp, + const LoadStoreOps& stencilOp); + + void init(const GrVkGpu* gpu, + const GrVkRenderPass& compatibleRenderPass, + const LoadStoreOps& colorOp, + const LoadStoreOps& stencilOp); + + struct AttachmentsDescriptor { + struct AttachmentDesc { + VkFormat fFormat; + int fSamples; + LoadStoreOps fLoadStoreOps; + + AttachmentDesc() + : fFormat(VK_FORMAT_UNDEFINED) + , fSamples(0) + , fLoadStoreOps(VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE) {} + bool operator==(const AttachmentDesc& right) const { + return (fFormat == right.fFormat && + fSamples == right.fSamples && + fLoadStoreOps == right.fLoadStoreOps); + } + bool operator!=(const AttachmentDesc& right) const { + return !(*this == right); + } + bool isCompatible(const AttachmentDesc& desc) const { + return (fFormat == desc.fFormat && fSamples == desc.fSamples); + } + }; + AttachmentDesc fColor; + AttachmentDesc fStencil; + uint32_t fAttachmentCount; + }; + + enum AttachmentFlags { + kColor_AttachmentFlag = 0x1, + kStencil_AttachmentFlag = 0x2, + // The external attachment flag signals that this render pass is imported from an external + // client. Since we don't know every attachment on the render pass we don't set any of the + // specific attachment flags when using external. However, the external render pass must + // at least have a color attachment. + kExternal_AttachmentFlag = 0x4, + }; + GR_DECL_BITFIELD_OPS_FRIENDS(AttachmentFlags); + + // The following return the index of the render pass attachment array for the given attachment. + // If the render pass does not have the given attachment it will return false and not set the + // index value. + bool colorAttachmentIndex(uint32_t* index) const; + bool stencilAttachmentIndex(uint32_t* index) const; + + // Returns whether or not the structure of a RenderTarget matches that of the VkRenderPass in + // this object. Specifically this compares that the number of attachments, format of + // attachments, and sample counts are all the same. This function is used in the creation of + // basic RenderPasses that can be used when creating a VkFrameBuffer object. + bool isCompatible(const GrVkRenderTarget& target) const; + + bool isCompatible(const GrVkRenderPass& renderPass) const; + + bool isCompatibleExternalRP(VkRenderPass) const; + + bool equalLoadStoreOps(const LoadStoreOps& colorOps, + const LoadStoreOps& stencilOps) const; + + VkRenderPass vkRenderPass() const { return fRenderPass; } + + const VkExtent2D& granularity() const { return fGranularity; } + + // Returns the number of clear colors needed to begin this render pass. Currently this will + // either only be 0 or 1 since we only ever clear the color attachment. + uint32_t clearValueCount() const { return fClearValueCount; } + + + void genKey(GrProcessorKeyBuilder* b) const; + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkRenderPass: %d (%d refs)\n", fRenderPass, this->getRefCnt()); + } +#endif + +private: + GrVkRenderPass(const GrVkRenderPass&); + + void init(const GrVkGpu* gpu, + const LoadStoreOps& colorOps, + const LoadStoreOps& stencilOps); + + bool isCompatible(const AttachmentsDescriptor&, const AttachmentFlags&) const; + + void freeGPUData(GrVkGpu* gpu) const override; + + VkRenderPass fRenderPass; + AttachmentFlags fAttachmentFlags; + AttachmentsDescriptor fAttachmentsDescriptor; + VkExtent2D fGranularity; + uint32_t fClearValueCount; + // For internally created render passes we assume the color attachment index is always 0. + uint32_t fColorAttachmentIndex = 0; + + typedef GrVkResource INHERITED; +}; + +GR_MAKE_BITFIELD_OPS(GrVkRenderPass::AttachmentFlags); + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.cpp b/gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.cpp new file mode 100644 index 0000000000..d7d53fb3b0 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.cpp @@ -0,0 +1,401 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkRenderTarget.h" + +#include "include/gpu/GrBackendSurface.h" +#include "src/gpu/GrRenderTargetPriv.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkFramebuffer.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkResourceProvider.h" +#include "src/gpu/vk/GrVkUtil.h" + +#include "include/gpu/vk/GrVkTypes.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +// We're virtually derived from GrSurface (via GrRenderTarget) so its +// constructor must be explicitly called. +GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed) + // for the moment we only support 1:1 color to stencil + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, info.fProtected) + , fColorAttachmentView(colorAttachmentView) + , fMSAAImage(new GrVkImage(msaaInfo, std::move(msaaLayout), + GrBackendObjectOwnership::kOwned)) + , fResolveAttachmentView(resolveAttachmentView) + , fFramebuffer(nullptr) + , fCachedSimpleRenderPass(nullptr) { + SkASSERT(info.fProtected == msaaInfo.fProtected); + SkASSERT(sampleCnt > 1); + this->createFramebuffer(gpu); + this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +// We're virtually derived from GrSurface (via GrRenderTarget) so its +// constructor must be explicitly called. +GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView, + GrBackendObjectOwnership ownership) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), ownership) + // for the moment we only support 1:1 color to stencil + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, sampleCnt, info.fProtected) + , fColorAttachmentView(colorAttachmentView) + , fMSAAImage( + new GrVkImage(msaaInfo, std::move(msaaLayout), GrBackendObjectOwnership::kOwned)) + , fResolveAttachmentView(resolveAttachmentView) + , fFramebuffer(nullptr) + , fCachedSimpleRenderPass(nullptr) { + SkASSERT(info.fProtected == msaaInfo.fProtected); + SkASSERT(sampleCnt > 1); + this->createFramebuffer(gpu); +} + +// We're virtually derived from GrSurface (via GrRenderTarget) so its +// constructor must be explicitly called. +GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* colorAttachmentView) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed) + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, 1, info.fProtected) + , fColorAttachmentView(colorAttachmentView) + , fMSAAImage(nullptr) + , fResolveAttachmentView(nullptr) + , fFramebuffer(nullptr) + , fCachedSimpleRenderPass(nullptr) { + this->createFramebuffer(gpu); + this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +// We're virtually derived from GrSurface (via GrRenderTarget) so its +// constructor must be explicitly called. +GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* colorAttachmentView, + GrBackendObjectOwnership ownership) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), ownership) + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, 1, info.fProtected) + , fColorAttachmentView(colorAttachmentView) + , fMSAAImage(nullptr) + , fResolveAttachmentView(nullptr) + , fFramebuffer(nullptr) + , fCachedSimpleRenderPass(nullptr) { + this->createFramebuffer(gpu); +} + +GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkRenderPass* renderPass, + VkCommandBuffer secondaryCommandBuffer) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed, true) + , GrRenderTarget(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, 1, info.fProtected) + , fColorAttachmentView(nullptr) + , fMSAAImage(nullptr) + , fResolveAttachmentView(nullptr) + , fFramebuffer(nullptr) + , fCachedSimpleRenderPass(renderPass) + , fSecondaryCommandBuffer(secondaryCommandBuffer) { + SkASSERT(fSecondaryCommandBuffer != VK_NULL_HANDLE); + this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +sk_sp GrVkRenderTarget::MakeWrappedRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout) { + SkASSERT(VK_NULL_HANDLE != info.fImage); + + SkASSERT(1 == info.fLevelCount); + VkFormat pixelFormat = info.fFormat; + + VkImage colorImage; + + // create msaa surface if necessary + GrVkImageInfo msInfo; + sk_sp msLayout; + const GrVkImageView* resolveAttachmentView = nullptr; + if (sampleCnt > 1) { + GrVkImage::ImageDesc msImageDesc; + msImageDesc.fImageType = VK_IMAGE_TYPE_2D; + msImageDesc.fFormat = pixelFormat; + msImageDesc.fWidth = desc.fWidth; + msImageDesc.fHeight = desc.fHeight; + msImageDesc.fLevels = 1; + msImageDesc.fSamples = sampleCnt; + msImageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + msImageDesc.fUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + msImageDesc.fMemProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + msImageDesc.fIsProtected = info.fProtected; + + if (!GrVkImage::InitImageInfo(gpu, msImageDesc, &msInfo)) { + return nullptr; + } + + // Set color attachment image + colorImage = msInfo.fImage; + + // Create Resolve attachment view + resolveAttachmentView = GrVkImageView::Create(gpu, info.fImage, pixelFormat, + GrVkImageView::kColor_Type, 1, + GrVkYcbcrConversionInfo()); + if (!resolveAttachmentView) { + GrVkImage::DestroyImageInfo(gpu, &msInfo); + return nullptr; + } + msLayout.reset(new GrVkImageLayout(msInfo.fImageLayout)); + } else { + // Set color attachment image + colorImage = info.fImage; + } + + // Get color attachment view + const GrVkImageView* colorAttachmentView = GrVkImageView::Create(gpu, colorImage, pixelFormat, + GrVkImageView::kColor_Type, 1, + GrVkYcbcrConversionInfo()); + if (!colorAttachmentView) { + if (sampleCnt > 1) { + resolveAttachmentView->unref(gpu); + GrVkImage::DestroyImageInfo(gpu, &msInfo); + } + return nullptr; + } + + GrVkRenderTarget* vkRT; + if (sampleCnt > 1) { + vkRT = new GrVkRenderTarget(gpu, desc, sampleCnt, info, std::move(layout), msInfo, + std::move(msLayout), colorAttachmentView, + resolveAttachmentView); + } else { + vkRT = new GrVkRenderTarget(gpu, desc, info, std::move(layout), colorAttachmentView); + } + + return sk_sp(vkRT); +} + +sk_sp GrVkRenderTarget::MakeSecondaryCBRenderTarget( + GrVkGpu* gpu, const GrSurfaceDesc& desc, const GrVkDrawableInfo& vkInfo) { + // We only set the few properties of the GrVkImageInfo that we know like layout and format. The + // others we keep at the default "null" values. + GrVkImageInfo info; + info.fImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + info.fFormat = vkInfo.fFormat; + + sk_sp layout(new GrVkImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)); + + const GrVkRenderPass* rp = + gpu->resourceProvider().findCompatibleExternalRenderPass(vkInfo.fCompatibleRenderPass, + vkInfo.fColorAttachmentIndex); + if (!rp) { + return nullptr; + } + + if (vkInfo.fSecondaryCommandBuffer == VK_NULL_HANDLE) { + return nullptr; + } + + GrVkRenderTarget* vkRT = new GrVkRenderTarget(gpu, desc, info, std::move(layout), rp, + vkInfo.fSecondaryCommandBuffer); + + return sk_sp(vkRT); +} + +bool GrVkRenderTarget::completeStencilAttachment() { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + this->createFramebuffer(this->getVkGpu()); + return true; +} + +void GrVkRenderTarget::createFramebuffer(GrVkGpu* gpu) { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + if (fFramebuffer) { + fFramebuffer->unref(gpu); + } + if (fCachedSimpleRenderPass) { + fCachedSimpleRenderPass->unref(gpu); + } + + // Vulkan requires us to create a compatible renderpass before we can create our framebuffer, + // so we use this to get a (cached) basic renderpass, only for creation. + fCachedSimpleRenderPass = + gpu->resourceProvider().findCompatibleRenderPass(*this, &fCompatibleRPHandle); + + // Stencil attachment view is stored in the base RT stencil attachment + const GrVkImageView* stencilView = this->stencilAttachmentView(); + fFramebuffer = GrVkFramebuffer::Create(gpu, this->width(), this->height(), + fCachedSimpleRenderPass, fColorAttachmentView, + stencilView); + SkASSERT(fFramebuffer); +} + +void GrVkRenderTarget::getAttachmentsDescriptor( + GrVkRenderPass::AttachmentsDescriptor* desc, + GrVkRenderPass::AttachmentFlags* attachmentFlags) const { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + desc->fColor.fFormat = this->imageFormat(); + desc->fColor.fSamples = this->numSamples(); + *attachmentFlags = GrVkRenderPass::kColor_AttachmentFlag; + uint32_t attachmentCount = 1; + + const GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment(); + if (stencil) { + const GrVkStencilAttachment* vkStencil = static_cast(stencil); + desc->fStencil.fFormat = vkStencil->vkFormat(); + desc->fStencil.fSamples = vkStencil->numSamples(); + // Currently in vulkan stencil and color attachments must all have same number of samples + SkASSERT(desc->fColor.fSamples == desc->fStencil.fSamples); + *attachmentFlags |= GrVkRenderPass::kStencil_AttachmentFlag; + ++attachmentCount; + } + desc->fAttachmentCount = attachmentCount; +} + +GrVkRenderTarget::~GrVkRenderTarget() { + // either release or abandon should have been called by the owner of this object. + SkASSERT(!fMSAAImage); + SkASSERT(!fResolveAttachmentView); + SkASSERT(!fColorAttachmentView); + SkASSERT(!fFramebuffer); + SkASSERT(!fCachedSimpleRenderPass); +} + +void GrVkRenderTarget::addResources(GrVkCommandBuffer& commandBuffer) const { + commandBuffer.addResource(this->framebuffer()); + commandBuffer.addResource(this->colorAttachmentView()); + commandBuffer.addResource(this->msaaImageResource() ? this->msaaImageResource() + : this->resource()); + if (this->stencilImageResource()) { + commandBuffer.addResource(this->stencilImageResource()); + commandBuffer.addResource(this->stencilAttachmentView()); + } +} + +void GrVkRenderTarget::releaseInternalObjects() { + GrVkGpu* gpu = this->getVkGpu(); + + if (fMSAAImage) { + fMSAAImage->releaseImage(gpu); + fMSAAImage.reset(); + } + + if (fResolveAttachmentView) { + fResolveAttachmentView->unref(gpu); + fResolveAttachmentView = nullptr; + } + if (fColorAttachmentView) { + fColorAttachmentView->unref(gpu); + fColorAttachmentView = nullptr; + } + if (fFramebuffer) { + fFramebuffer->unref(gpu); + fFramebuffer = nullptr; + } + if (fCachedSimpleRenderPass) { + fCachedSimpleRenderPass->unref(gpu); + fCachedSimpleRenderPass = nullptr; + } +} + +void GrVkRenderTarget::abandonInternalObjects() { + if (fMSAAImage) { + fMSAAImage->abandonImage(); + fMSAAImage.reset(); + } + + if (fResolveAttachmentView) { + fResolveAttachmentView->unrefAndAbandon(); + fResolveAttachmentView = nullptr; + } + if (fColorAttachmentView) { + fColorAttachmentView->unrefAndAbandon(); + fColorAttachmentView = nullptr; + } + if (fFramebuffer) { + fFramebuffer->unrefAndAbandon(); + fFramebuffer = nullptr; + } + if (fCachedSimpleRenderPass) { + fCachedSimpleRenderPass->unrefAndAbandon(); + fCachedSimpleRenderPass = nullptr; + } +} + +void GrVkRenderTarget::onRelease() { + this->releaseInternalObjects(); + this->releaseImage(this->getVkGpu()); + GrRenderTarget::onRelease(); +} + +void GrVkRenderTarget::onAbandon() { + this->abandonInternalObjects(); + this->abandonImage(); + GrRenderTarget::onAbandon(); +} + + +GrBackendRenderTarget GrVkRenderTarget::getBackendRenderTarget() const { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + return GrBackendRenderTarget(this->width(), this->height(), this->numSamples(), fInfo, + this->grVkImageLayout()); +} + +const GrVkResource* GrVkRenderTarget::stencilImageResource() const { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + const GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment(); + if (stencil) { + const GrVkStencilAttachment* vkStencil = static_cast(stencil); + return vkStencil->imageResource(); + } + + return nullptr; +} + +const GrVkImageView* GrVkRenderTarget::stencilAttachmentView() const { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + const GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment(); + if (stencil) { + const GrVkStencilAttachment* vkStencil = static_cast(stencil); + return vkStencil->stencilView(); + } + + return nullptr; +} + +GrVkGpu* GrVkRenderTarget::getVkGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.h b/gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.h new file mode 100644 index 0000000000..09bb3d6a35 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkRenderTarget.h @@ -0,0 +1,182 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrVkRenderTarget_DEFINED +#define GrVkRenderTarget_DEFINED + +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/vk/GrVkImage.h" + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkRenderPass.h" +#include "src/gpu/vk/GrVkResourceProvider.h" + +class GrVkCommandBuffer; +class GrVkFramebuffer; +class GrVkGpu; +class GrVkImageView; +class GrVkSecondaryCommandBuffer; +class GrVkStencilAttachment; + +struct GrVkImageInfo; + +#ifdef SK_BUILD_FOR_WIN +// Windows gives bogus warnings about inheriting asTexture/asRenderTarget via dominance. +#pragma warning(push) +#pragma warning(disable: 4250) +#endif + +class GrVkRenderTarget: public GrRenderTarget, public virtual GrVkImage { +public: + static sk_sp MakeWrappedRenderTarget(GrVkGpu*, const GrSurfaceDesc&, + int sampleCnt, const GrVkImageInfo&, + sk_sp); + + static sk_sp MakeSecondaryCBRenderTarget(GrVkGpu*, const GrSurfaceDesc&, + const GrVkDrawableInfo& vkInfo); + + ~GrVkRenderTarget() override; + + GrBackendFormat backendFormat() const override { return this->getBackendFormat(); } + + const GrVkFramebuffer* framebuffer() const { return fFramebuffer; } + const GrVkImageView* colorAttachmentView() const { return fColorAttachmentView; } + const GrVkResource* msaaImageResource() const { + if (fMSAAImage) { + return fMSAAImage->fResource; + } + return nullptr; + } + GrVkImage* msaaImage() { return fMSAAImage.get(); } + const GrVkImageView* resolveAttachmentView() const { return fResolveAttachmentView; } + const GrVkResource* stencilImageResource() const; + const GrVkImageView* stencilAttachmentView() const; + + const GrVkRenderPass* simpleRenderPass() const { return fCachedSimpleRenderPass; } + GrVkResourceProvider::CompatibleRPHandle compatibleRenderPassHandle() const { + SkASSERT(!this->wrapsSecondaryCommandBuffer()); + return fCompatibleRPHandle; + } + const GrVkRenderPass* externalRenderPass() const { + SkASSERT(this->wrapsSecondaryCommandBuffer()); + // We use the cached simple render pass to hold the external render pass. + return fCachedSimpleRenderPass; + } + + bool wrapsSecondaryCommandBuffer() const { return fSecondaryCommandBuffer != VK_NULL_HANDLE; } + VkCommandBuffer getExternalSecondaryCommandBuffer() const { + return fSecondaryCommandBuffer; + } + + bool canAttemptStencilAttachment() const override { + // We don't know the status of the stencil attachment for wrapped external secondary command + // buffers so we just assume we don't have one. + return !this->wrapsSecondaryCommandBuffer(); + } + + GrBackendRenderTarget getBackendRenderTarget() const override; + + void getAttachmentsDescriptor(GrVkRenderPass::AttachmentsDescriptor* desc, + GrVkRenderPass::AttachmentFlags* flags) const; + + void addResources(GrVkCommandBuffer& commandBuffer) const; + +protected: + GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView, + GrBackendObjectOwnership); + + GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* colorAttachmentView, + GrBackendObjectOwnership); + + GrVkGpu* getVkGpu() const; + + void onAbandon() override; + void onRelease() override; + + // This accounts for the texture's memory and any MSAA renderbuffer's memory. + size_t onGpuMemorySize() const override { + int numColorSamples = this->numSamples(); + if (numColorSamples > 1) { + // Add one to account for the resolved VkImage. + numColorSamples += 1; + } + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numColorSamples, GrMipMapped::kNo); + } + + void createFramebuffer(GrVkGpu* gpu); + + const GrVkImageView* fColorAttachmentView; + std::unique_ptr fMSAAImage; + const GrVkImageView* fResolveAttachmentView; + +private: + GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView); + + GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* colorAttachmentView); + + + GrVkRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkRenderPass* renderPass, + VkCommandBuffer secondaryCommandBuffer); + + bool completeStencilAttachment() override; + + // In Vulkan we call the release proc after we are finished with the underlying + // GrVkImage::Resource object (which occurs after the GPU has finished all work on it). + void onSetRelease(sk_sp releaseHelper) override { + // Forward the release proc on to GrVkImage + this->setResourceRelease(std::move(releaseHelper)); + } + + void releaseInternalObjects(); + void abandonInternalObjects(); + + const GrVkFramebuffer* fFramebuffer; + + // This is a cached pointer to a simple render pass. The render target should unref it + // once it is done with it. + const GrVkRenderPass* fCachedSimpleRenderPass; + // This is a handle to be used to quickly get compatible GrVkRenderPasses for this render target + GrVkResourceProvider::CompatibleRPHandle fCompatibleRPHandle; + + // If this render target wraps an external VkCommandBuffer, then this handle will be that + // VkCommandBuffer and not VK_NULL_HANDLE. In this case the render target will not be backed by + // an actual VkImage and will thus be limited in terms of what it can be used for. + VkCommandBuffer fSecondaryCommandBuffer = VK_NULL_HANDLE; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkResource.h b/gfx/skia/skia/src/gpu/vk/GrVkResource.h new file mode 100644 index 0000000000..7b9949ba1b --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkResource.h @@ -0,0 +1,243 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkResource_DEFINED +#define GrVkResource_DEFINED + + +#include "include/private/SkTHash.h" +#include "include/utils/SkRandom.h" +#include + +class GrVkGpu; + +// uncomment to enable tracing of resource refs +#ifdef SK_DEBUG +#define SK_TRACE_VK_RESOURCES +#endif + +/** \class GrVkResource + + GrVkResource is the base class for Vulkan resources that may be shared by multiple + objects. When an existing owner wants to share a reference, it calls ref(). + When an owner wants to release its reference, it calls unref(). When the + shared object's reference count goes to zero as the result of an unref() + call, its (virtual) destructor is called. It is an error for the + destructor to be called explicitly (or via the object going out of scope on + the stack or calling delete) if getRefCnt() > 1. + + This is nearly identical to SkRefCntBase. The exceptions are that unref() + takes a GrVkGpu, and any derived classes must implement freeGPUData() and + possibly abandonGPUData(). +*/ + +class GrVkResource : SkNoncopyable { +public: + // Simple refCount tracing, to ensure that everything ref'ed is unref'ed. +#ifdef SK_TRACE_VK_RESOURCES + struct Hash { + uint32_t operator()(const GrVkResource* const& r) const { + SkASSERT(r); + return r->fKey; + } + }; + + class Trace { + public: + ~Trace() { + fHashSet.foreach([](const GrVkResource* r) { + r->dumpInfo(); + }); + SkASSERT(0 == fHashSet.count()); + } + + void add(const GrVkResource* r) { + fHashSet.add(r); + } + + void remove(const GrVkResource* r) { + fHashSet.remove(r); + } + + private: + SkTHashSet fHashSet; + }; + + static std::atomic fKeyCounter; +#endif + + /** Default construct, initializing the reference count to 1. + */ + GrVkResource() : fRefCnt(1) { +#ifdef SK_TRACE_VK_RESOURCES + fKey = fKeyCounter.fetch_add(+1, std::memory_order_relaxed); + GetTrace()->add(this); +#endif + } + + /** Destruct, asserting that the reference count is 1. + */ + virtual ~GrVkResource() { +#ifdef SK_DEBUG + auto count = this->getRefCnt(); + SkASSERTF(count == 1, "fRefCnt was %d", count); + fRefCnt.store(0); // illegal value, to catch us if we reuse after delete +#endif + } + +#ifdef SK_DEBUG + /** Return the reference count. Use only for debugging. */ + int32_t getRefCnt() const { return fRefCnt.load(); } +#endif + + /** May return true if the caller is the only owner. + * Ensures that all previous owner's actions are complete. + */ + bool unique() const { + // The acquire barrier is only really needed if we return true. It + // prevents code conditioned on the result of unique() from running + // until previous owners are all totally done calling unref(). + return 1 == fRefCnt.load(std::memory_order_acquire); + } + + /** Increment the reference count. + Must be balanced by a call to unref() or unrefAndFreeResources(). + */ + void ref() const { + // No barrier required. + SkDEBUGCODE(int newRefCount = )fRefCnt.fetch_add(+1, std::memory_order_relaxed); + SkASSERT(newRefCount >= 1); + } + + /** Decrement the reference count. If the reference count is 1 before the + decrement, then delete the object. Note that if this is the case, then + the object needs to have been allocated via new, and not on the stack. + Any GPU data associated with this resource will be freed before it's deleted. + */ + void unref(GrVkGpu* gpu) const { + SkASSERT(gpu); + // A release here acts in place of all releases we "should" have been doing in ref(). + int newRefCount = fRefCnt.fetch_add(-1, std::memory_order_acq_rel); + SkASSERT(newRefCount >= 0); + if (newRefCount == 1) { + // Like unique(), the acquire is only needed on success, to make sure + // code in internal_dispose() doesn't happen before the decrement. + this->internal_dispose(gpu); + } + } + + /** Unref without freeing GPU data. Used only when we're abandoning the resource */ + void unrefAndAbandon() const { + SkASSERT(this->getRefCnt() > 0); + // A release here acts in place of all releases we "should" have been doing in ref(). + int newRefCount = fRefCnt.fetch_add(-1, std::memory_order_acq_rel); + SkASSERT(newRefCount >= 0); + if (newRefCount == 1) { + // Like unique(), the acquire is only needed on success, to make sure + // code in internal_dispose() doesn't happen before the decrement. + this->internal_dispose(); + } + } + + // Called every time this resource is added to a command buffer. + virtual void notifyAddedToCommandBuffer() const {} + // Called every time this resource is removed from a command buffer (typically because + // the command buffer finished execution on the GPU but also when the command buffer + // is abandoned.) + virtual void notifyRemovedFromCommandBuffer() const {} + +#ifdef SK_DEBUG + void validate() const { + SkASSERT(this->getRefCnt() > 0); + } +#endif + +#ifdef SK_TRACE_VK_RESOURCES + /** Output a human-readable dump of this resource's information + */ + virtual void dumpInfo() const = 0; +#endif + +private: +#ifdef SK_TRACE_VK_RESOURCES + static Trace* GetTrace() { + static Trace kTrace; + return &kTrace; + } +#endif + + /** Must be implemented by any subclasses. + * Deletes any Vk data associated with this resource + */ + virtual void freeGPUData(GrVkGpu* gpu) const = 0; + + /** + * Called from unrefAndAbandon. Resources should do any necessary cleanup without freeing + * underlying Vk objects. This must be overridden by subclasses that themselves store + * GrVkResources since those resource will need to be unrefed. + */ + virtual void abandonGPUData() const {} + + /** + * Called when the ref count goes to 0. Will free Vk resources. + */ + void internal_dispose(GrVkGpu* gpu) const { + this->freeGPUData(gpu); +#ifdef SK_TRACE_VK_RESOURCES + GetTrace()->remove(this); +#endif + +#ifdef SK_DEBUG + SkASSERT(0 == this->getRefCnt()); + fRefCnt.store(1); +#endif + delete this; + } + + /** + * Internal_dispose without freeing Vk resources. Used when we've lost context. + */ + void internal_dispose() const { + this->abandonGPUData(); +#ifdef SK_TRACE_VK_RESOURCES + GetTrace()->remove(this); +#endif + +#ifdef SK_DEBUG + SkASSERT(0 == this->getRefCnt()); + fRefCnt.store(1); +#endif + delete this; + } + + mutable std::atomic fRefCnt; +#ifdef SK_TRACE_VK_RESOURCES + uint32_t fKey; +#endif + + typedef SkNoncopyable INHERITED; +}; + +// This subclass allows for recycling +class GrVkRecycledResource : public GrVkResource { +public: + // When recycle is called and there is only one ref left on the resource, we will signal that + // the resource can be recycled for reuse. If the sublass (or whoever is managing this resource) + // decides not to recycle the objects, it is their responsibility to call unref on the object. + void recycle(GrVkGpu* gpu) const { + if (this->unique()) { + this->onRecycle(gpu); + } else { + this->unref(gpu); + } + } + +private: + virtual void onRecycle(GrVkGpu* gpu) const = 0; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.cpp b/gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.cpp new file mode 100644 index 0000000000..5f21dbd3fd --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.cpp @@ -0,0 +1,585 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkResourceProvider.h" + +#include "src/core/SkTaskGroup.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/vk/GrVkCommandBuffer.h" +#include "src/gpu/vk/GrVkCommandPool.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkPipeline.h" +#include "src/gpu/vk/GrVkRenderTarget.h" +#include "src/gpu/vk/GrVkUniformBuffer.h" +#include "src/gpu/vk/GrVkUtil.h" + +#ifdef SK_TRACE_VK_RESOURCES +std::atomic GrVkResource::fKeyCounter{0}; +#endif + +GrVkResourceProvider::GrVkResourceProvider(GrVkGpu* gpu) + : fGpu(gpu) + , fPipelineCache(VK_NULL_HANDLE) { + fPipelineStateCache = new PipelineStateCache(gpu); +} + +GrVkResourceProvider::~GrVkResourceProvider() { + SkASSERT(0 == fRenderPassArray.count()); + SkASSERT(0 == fExternalRenderPasses.count()); + SkASSERT(VK_NULL_HANDLE == fPipelineCache); + delete fPipelineStateCache; +} + +VkPipelineCache GrVkResourceProvider::pipelineCache() { + if (fPipelineCache == VK_NULL_HANDLE) { + VkPipelineCacheCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkPipelineCacheCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + + auto persistentCache = fGpu->getContext()->priv().getPersistentCache(); + sk_sp cached; + if (persistentCache) { + uint32_t key = GrVkGpu::kPipelineCache_PersistentCacheKeyType; + sk_sp keyData = SkData::MakeWithoutCopy(&key, sizeof(uint32_t)); + cached = persistentCache->load(*keyData); + } + bool usedCached = false; + if (cached) { + uint32_t* cacheHeader = (uint32_t*)cached->data(); + if (cacheHeader[1] == VK_PIPELINE_CACHE_HEADER_VERSION_ONE) { + // For version one of the header, the total header size is 16 bytes plus + // VK_UUID_SIZE bytes. See Section 9.6 (Pipeline Cache) in the vulkan spec to see + // the breakdown of these bytes. + SkASSERT(cacheHeader[0] == 16 + VK_UUID_SIZE); + const VkPhysicalDeviceProperties& devProps = fGpu->physicalDeviceProperties(); + const uint8_t* supportedPipelineCacheUUID = devProps.pipelineCacheUUID; + if (cacheHeader[2] == devProps.vendorID && cacheHeader[3] == devProps.deviceID && + !memcmp(&cacheHeader[4], supportedPipelineCacheUUID, VK_UUID_SIZE)) { + createInfo.initialDataSize = cached->size(); + createInfo.pInitialData = cached->data(); + usedCached = true; + } + } + } + if (!usedCached) { + createInfo.initialDataSize = 0; + createInfo.pInitialData = nullptr; + } + VkResult result = GR_VK_CALL(fGpu->vkInterface(), + CreatePipelineCache(fGpu->device(), &createInfo, nullptr, + &fPipelineCache)); + SkASSERT(VK_SUCCESS == result); + if (VK_SUCCESS != result) { + fPipelineCache = VK_NULL_HANDLE; + } + } + return fPipelineCache; +} + +void GrVkResourceProvider::init() { + // Init uniform descriptor objects + GrVkDescriptorSetManager* dsm = GrVkDescriptorSetManager::CreateUniformManager(fGpu); + fDescriptorSetManagers.emplace_back(dsm); + SkASSERT(1 == fDescriptorSetManagers.count()); + fUniformDSHandle = GrVkDescriptorSetManager::Handle(0); +} + +GrVkPipeline* GrVkResourceProvider::createPipeline(const GrProgramInfo& programInfo, + const GrStencilSettings& stencil, + VkPipelineShaderStageCreateInfo* shaderStageInfo, + int shaderStageCount, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass, + VkPipelineLayout layout) { + return GrVkPipeline::Create(fGpu, programInfo, stencil, shaderStageInfo, + shaderStageCount, primitiveType, compatibleRenderPass, + layout, this->pipelineCache()); +} + +// To create framebuffers, we first need to create a simple RenderPass that is +// only used for framebuffer creation. When we actually render we will create +// RenderPasses as needed that are compatible with the framebuffer. +const GrVkRenderPass* +GrVkResourceProvider::findCompatibleRenderPass(const GrVkRenderTarget& target, + CompatibleRPHandle* compatibleHandle) { + for (int i = 0; i < fRenderPassArray.count(); ++i) { + if (fRenderPassArray[i].isCompatible(target)) { + const GrVkRenderPass* renderPass = fRenderPassArray[i].getCompatibleRenderPass(); + renderPass->ref(); + if (compatibleHandle) { + *compatibleHandle = CompatibleRPHandle(i); + } + return renderPass; + } + } + + const GrVkRenderPass* renderPass = + fRenderPassArray.emplace_back(fGpu, target).getCompatibleRenderPass(); + renderPass->ref(); + + if (compatibleHandle) { + *compatibleHandle = CompatibleRPHandle(fRenderPassArray.count() - 1); + } + return renderPass; +} + +const GrVkRenderPass* +GrVkResourceProvider::findCompatibleRenderPass(const CompatibleRPHandle& compatibleHandle) { + SkASSERT(compatibleHandle.isValid() && compatibleHandle.toIndex() < fRenderPassArray.count()); + int index = compatibleHandle.toIndex(); + const GrVkRenderPass* renderPass = fRenderPassArray[index].getCompatibleRenderPass(); + renderPass->ref(); + return renderPass; +} + +const GrVkRenderPass* GrVkResourceProvider::findCompatibleExternalRenderPass( + VkRenderPass renderPass, uint32_t colorAttachmentIndex) { + for (int i = 0; i < fExternalRenderPasses.count(); ++i) { + if (fExternalRenderPasses[i]->isCompatibleExternalRP(renderPass)) { + fExternalRenderPasses[i]->ref(); +#ifdef SK_DEBUG + uint32_t cachedColorIndex; + SkASSERT(fExternalRenderPasses[i]->colorAttachmentIndex(&cachedColorIndex)); + SkASSERT(cachedColorIndex == colorAttachmentIndex); +#endif + return fExternalRenderPasses[i]; + } + } + + const GrVkRenderPass* newRenderPass = new GrVkRenderPass(renderPass, colorAttachmentIndex); + fExternalRenderPasses.push_back(newRenderPass); + newRenderPass->ref(); + return newRenderPass; +} + +const GrVkRenderPass* GrVkResourceProvider::findRenderPass( + const GrVkRenderTarget& target, + const GrVkRenderPass::LoadStoreOps& colorOps, + const GrVkRenderPass::LoadStoreOps& stencilOps, + CompatibleRPHandle* compatibleHandle) { + GrVkResourceProvider::CompatibleRPHandle tempRPHandle; + GrVkResourceProvider::CompatibleRPHandle* pRPHandle = compatibleHandle ? compatibleHandle + : &tempRPHandle; + *pRPHandle = target.compatibleRenderPassHandle(); + + // This will get us the handle to (and possible create) the compatible set for the specific + // GrVkRenderPass we are looking for. + this->findCompatibleRenderPass(target, compatibleHandle); + return this->findRenderPass(*pRPHandle, colorOps, stencilOps); +} + +const GrVkRenderPass* +GrVkResourceProvider::findRenderPass(const CompatibleRPHandle& compatibleHandle, + const GrVkRenderPass::LoadStoreOps& colorOps, + const GrVkRenderPass::LoadStoreOps& stencilOps) { + SkASSERT(compatibleHandle.isValid() && compatibleHandle.toIndex() < fRenderPassArray.count()); + CompatibleRenderPassSet& compatibleSet = fRenderPassArray[compatibleHandle.toIndex()]; + const GrVkRenderPass* renderPass = compatibleSet.getRenderPass(fGpu, + colorOps, + stencilOps); + renderPass->ref(); + return renderPass; +} + +GrVkDescriptorPool* GrVkResourceProvider::findOrCreateCompatibleDescriptorPool( + VkDescriptorType type, uint32_t count) { + return new GrVkDescriptorPool(fGpu, type, count); +} + +GrVkSampler* GrVkResourceProvider::findOrCreateCompatibleSampler( + const GrSamplerState& params, const GrVkYcbcrConversionInfo& ycbcrInfo) { + GrVkSampler* sampler = fSamplers.find(GrVkSampler::GenerateKey(params, ycbcrInfo)); + if (!sampler) { + sampler = GrVkSampler::Create(fGpu, params, ycbcrInfo); + if (!sampler) { + return nullptr; + } + fSamplers.add(sampler); + } + SkASSERT(sampler); + sampler->ref(); + return sampler; +} + +GrVkSamplerYcbcrConversion* GrVkResourceProvider::findOrCreateCompatibleSamplerYcbcrConversion( + const GrVkYcbcrConversionInfo& ycbcrInfo) { + GrVkSamplerYcbcrConversion* ycbcrConversion = + fYcbcrConversions.find(GrVkSamplerYcbcrConversion::GenerateKey(ycbcrInfo)); + if (!ycbcrConversion) { + ycbcrConversion = GrVkSamplerYcbcrConversion::Create(fGpu, ycbcrInfo); + if (!ycbcrConversion) { + return nullptr; + } + fYcbcrConversions.add(ycbcrConversion); + } + SkASSERT(ycbcrConversion); + ycbcrConversion->ref(); + return ycbcrConversion; +} + +GrVkPipelineState* GrVkResourceProvider::findOrCreateCompatiblePipelineState( + GrRenderTarget* renderTarget, + const GrProgramInfo& programInfo, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass) { + return fPipelineStateCache->refPipelineState(renderTarget, programInfo, + primitiveType, compatibleRenderPass); +} + +void GrVkResourceProvider::getSamplerDescriptorSetHandle(VkDescriptorType type, + const GrVkUniformHandler& uniformHandler, + GrVkDescriptorSetManager::Handle* handle) { + SkASSERT(handle); + SkASSERT(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == type || + VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER == type); + for (int i = 0; i < fDescriptorSetManagers.count(); ++i) { + if (fDescriptorSetManagers[i]->isCompatible(type, &uniformHandler)) { + *handle = GrVkDescriptorSetManager::Handle(i); + return; + } + } + + GrVkDescriptorSetManager* dsm = GrVkDescriptorSetManager::CreateSamplerManager(fGpu, type, + uniformHandler); + fDescriptorSetManagers.emplace_back(dsm); + *handle = GrVkDescriptorSetManager::Handle(fDescriptorSetManagers.count() - 1); +} + +void GrVkResourceProvider::getSamplerDescriptorSetHandle(VkDescriptorType type, + const SkTArray& visibilities, + GrVkDescriptorSetManager::Handle* handle) { + SkASSERT(handle); + SkASSERT(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == type || + VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER == type); + for (int i = 0; i < fDescriptorSetManagers.count(); ++i) { + if (fDescriptorSetManagers[i]->isCompatible(type, visibilities)) { + *handle = GrVkDescriptorSetManager::Handle(i); + return; + } + } + + GrVkDescriptorSetManager* dsm = GrVkDescriptorSetManager::CreateSamplerManager(fGpu, type, + visibilities); + fDescriptorSetManagers.emplace_back(dsm); + *handle = GrVkDescriptorSetManager::Handle(fDescriptorSetManagers.count() - 1); +} + +VkDescriptorSetLayout GrVkResourceProvider::getUniformDSLayout() const { + SkASSERT(fUniformDSHandle.isValid()); + return fDescriptorSetManagers[fUniformDSHandle.toIndex()]->layout(); +} + +VkDescriptorSetLayout GrVkResourceProvider::getSamplerDSLayout( + const GrVkDescriptorSetManager::Handle& handle) const { + SkASSERT(handle.isValid()); + return fDescriptorSetManagers[handle.toIndex()]->layout(); +} + +const GrVkDescriptorSet* GrVkResourceProvider::getUniformDescriptorSet() { + SkASSERT(fUniformDSHandle.isValid()); + return fDescriptorSetManagers[fUniformDSHandle.toIndex()]->getDescriptorSet(fGpu, + fUniformDSHandle); +} + +const GrVkDescriptorSet* GrVkResourceProvider::getSamplerDescriptorSet( + const GrVkDescriptorSetManager::Handle& handle) { + SkASSERT(handle.isValid()); + return fDescriptorSetManagers[handle.toIndex()]->getDescriptorSet(fGpu, handle); +} + +void GrVkResourceProvider::recycleDescriptorSet(const GrVkDescriptorSet* descSet, + const GrVkDescriptorSetManager::Handle& handle) { + SkASSERT(descSet); + SkASSERT(handle.isValid()); + int managerIdx = handle.toIndex(); + SkASSERT(managerIdx < fDescriptorSetManagers.count()); + fDescriptorSetManagers[managerIdx]->recycleDescriptorSet(descSet); +} + +GrVkCommandPool* GrVkResourceProvider::findOrCreateCommandPool() { + std::unique_lock lock(fBackgroundMutex); + GrVkCommandPool* result; + if (fAvailableCommandPools.count()) { + result = fAvailableCommandPools.back(); + fAvailableCommandPools.pop_back(); + } else { + result = GrVkCommandPool::Create(fGpu); + } + SkASSERT(result->unique()); + SkDEBUGCODE( + for (const GrVkCommandPool* pool : fActiveCommandPools) { + SkASSERT(pool != result); + } + for (const GrVkCommandPool* pool : fAvailableCommandPools) { + SkASSERT(pool != result); + } + ) + fActiveCommandPools.push_back(result); + result->ref(); + return result; +} + +void GrVkResourceProvider::checkCommandBuffers() { + for (int i = fActiveCommandPools.count() - 1; i >= 0; --i) { + GrVkCommandPool* pool = fActiveCommandPools[i]; + if (!pool->isOpen()) { + GrVkPrimaryCommandBuffer* buffer = pool->getPrimaryCommandBuffer(); + if (buffer->finished(fGpu)) { + fActiveCommandPools.removeShuffle(i); + this->backgroundReset(pool); + } + } + } +} + +void GrVkResourceProvider::addFinishedProcToActiveCommandBuffers( + GrGpuFinishedProc finishedProc, GrGpuFinishedContext finishedContext) { + sk_sp procRef(new GrRefCntedCallback(finishedProc, finishedContext)); + for (int i = 0; i < fActiveCommandPools.count(); ++i) { + GrVkCommandPool* pool = fActiveCommandPools[i]; + if (!pool->isOpen()) { + GrVkPrimaryCommandBuffer* buffer = pool->getPrimaryCommandBuffer(); + buffer->addFinishedProc(procRef); + } + } +} + +const GrVkResource* GrVkResourceProvider::findOrCreateStandardUniformBufferResource() { + const GrVkResource* resource = nullptr; + int count = fAvailableUniformBufferResources.count(); + if (count > 0) { + resource = fAvailableUniformBufferResources[count - 1]; + fAvailableUniformBufferResources.removeShuffle(count - 1); + } else { + resource = GrVkUniformBuffer::CreateResource(fGpu, GrVkUniformBuffer::kStandardSize); + } + return resource; +} + +void GrVkResourceProvider::recycleStandardUniformBufferResource(const GrVkResource* resource) { + fAvailableUniformBufferResources.push_back(resource); +} + +void GrVkResourceProvider::destroyResources(bool deviceLost) { + SkTaskGroup* taskGroup = fGpu->getContext()->priv().getTaskGroup(); + if (taskGroup) { + taskGroup->wait(); + } + + // loop over all render pass sets to make sure we destroy all the internal VkRenderPasses + for (int i = 0; i < fRenderPassArray.count(); ++i) { + fRenderPassArray[i].releaseResources(fGpu); + } + fRenderPassArray.reset(); + + for (int i = 0; i < fExternalRenderPasses.count(); ++i) { + fExternalRenderPasses[i]->unref(fGpu); + } + fExternalRenderPasses.reset(); + + // Iterate through all store GrVkSamplers and unref them before resetting the hash. + for (decltype(fSamplers)::Iter iter(&fSamplers); !iter.done(); ++iter) { + (*iter).unref(fGpu); + } + fSamplers.reset(); + + for (decltype(fYcbcrConversions)::Iter iter(&fYcbcrConversions); !iter.done(); ++iter) { + (*iter).unref(fGpu); + } + fYcbcrConversions.reset(); + + fPipelineStateCache->release(); + + GR_VK_CALL(fGpu->vkInterface(), DestroyPipelineCache(fGpu->device(), fPipelineCache, nullptr)); + fPipelineCache = VK_NULL_HANDLE; + + for (GrVkCommandPool* pool : fActiveCommandPools) { + SkASSERT(pool->unique()); + pool->unref(fGpu); + } + fActiveCommandPools.reset(); + + for (GrVkCommandPool* pool : fAvailableCommandPools) { + SkASSERT(pool->unique()); + pool->unref(fGpu); + } + fAvailableCommandPools.reset(); + + // We must release/destroy all command buffers and pipeline states before releasing the + // GrVkDescriptorSetManagers + for (int i = 0; i < fDescriptorSetManagers.count(); ++i) { + fDescriptorSetManagers[i]->release(fGpu); + } + fDescriptorSetManagers.reset(); + + // release our uniform buffers + for (int i = 0; i < fAvailableUniformBufferResources.count(); ++i) { + SkASSERT(fAvailableUniformBufferResources[i]->unique()); + fAvailableUniformBufferResources[i]->unref(fGpu); + } + fAvailableUniformBufferResources.reset(); +} + +void GrVkResourceProvider::abandonResources() { + SkTaskGroup* taskGroup = fGpu->getContext()->priv().getTaskGroup(); + if (taskGroup) { + taskGroup->wait(); + } + + // Abandon all command pools + for (int i = 0; i < fActiveCommandPools.count(); ++i) { + SkASSERT(fActiveCommandPools[i]->unique()); + fActiveCommandPools[i]->unrefAndAbandon(); + } + fActiveCommandPools.reset(); + for (int i = 0; i < fAvailableCommandPools.count(); ++i) { + SkASSERT(fAvailableCommandPools[i]->unique()); + fAvailableCommandPools[i]->unrefAndAbandon(); + } + fAvailableCommandPools.reset(); + + // loop over all render pass sets to make sure we destroy all the internal VkRenderPasses + for (int i = 0; i < fRenderPassArray.count(); ++i) { + fRenderPassArray[i].abandonResources(); + } + fRenderPassArray.reset(); + + for (int i = 0; i < fExternalRenderPasses.count(); ++i) { + fExternalRenderPasses[i]->unrefAndAbandon(); + } + fExternalRenderPasses.reset(); + + // Iterate through all store GrVkSamplers and unrefAndAbandon them before resetting the hash. + SkTDynamicHash::Iter iter(&fSamplers); + for (; !iter.done(); ++iter) { + (*iter).unrefAndAbandon(); + } + fSamplers.reset(); + + for (decltype(fYcbcrConversions)::Iter iter(&fYcbcrConversions); !iter.done(); ++iter) { + (*iter).unrefAndAbandon(); + } + fYcbcrConversions.reset(); + + fPipelineStateCache->abandon(); + + fPipelineCache = VK_NULL_HANDLE; + + // We must abandon all command buffers and pipeline states before abandoning the + // GrVkDescriptorSetManagers + for (int i = 0; i < fDescriptorSetManagers.count(); ++i) { + fDescriptorSetManagers[i]->abandon(); + } + fDescriptorSetManagers.reset(); + + // release our uniform buffers + for (int i = 0; i < fAvailableUniformBufferResources.count(); ++i) { + SkASSERT(fAvailableUniformBufferResources[i]->unique()); + fAvailableUniformBufferResources[i]->unrefAndAbandon(); + } + fAvailableUniformBufferResources.reset(); +} + +void GrVkResourceProvider::backgroundReset(GrVkCommandPool* pool) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(pool->unique()); + pool->releaseResources(fGpu); + SkTaskGroup* taskGroup = fGpu->getContext()->priv().getTaskGroup(); + if (taskGroup) { + taskGroup->add([this, pool]() { + this->reset(pool); + }); + } else { + this->reset(pool); + } +} + +void GrVkResourceProvider::reset(GrVkCommandPool* pool) { + TRACE_EVENT0("skia.gpu", TRACE_FUNC); + SkASSERT(pool->unique()); + pool->reset(fGpu); + std::unique_lock providerLock(fBackgroundMutex); + fAvailableCommandPools.push_back(pool); +} + +void GrVkResourceProvider::storePipelineCacheData() { + size_t dataSize = 0; + VkResult result = GR_VK_CALL(fGpu->vkInterface(), GetPipelineCacheData(fGpu->device(), + this->pipelineCache(), + &dataSize, nullptr)); + SkASSERT(result == VK_SUCCESS); + + std::unique_ptr data(new uint8_t[dataSize]); + + result = GR_VK_CALL(fGpu->vkInterface(), GetPipelineCacheData(fGpu->device(), + this->pipelineCache(), + &dataSize, + (void*)data.get())); + SkASSERT(result == VK_SUCCESS); + + uint32_t key = GrVkGpu::kPipelineCache_PersistentCacheKeyType; + sk_sp keyData = SkData::MakeWithoutCopy(&key, sizeof(uint32_t)); + + fGpu->getContext()->priv().getPersistentCache()->store( + *keyData, *SkData::MakeWithoutCopy(data.get(), dataSize)); +} + +//////////////////////////////////////////////////////////////////////////////// + +GrVkResourceProvider::CompatibleRenderPassSet::CompatibleRenderPassSet( + const GrVkGpu* gpu, + const GrVkRenderTarget& target) + : fLastReturnedIndex(0) { + fRenderPasses.emplace_back(new GrVkRenderPass()); + fRenderPasses[0]->initSimple(gpu, target); +} + +bool GrVkResourceProvider::CompatibleRenderPassSet::isCompatible( + const GrVkRenderTarget& target) const { + // The first GrVkRenderpass should always exists since we create the basic load store + // render pass on create + SkASSERT(fRenderPasses[0]); + return fRenderPasses[0]->isCompatible(target); +} + +GrVkRenderPass* GrVkResourceProvider::CompatibleRenderPassSet::getRenderPass( + const GrVkGpu* gpu, + const GrVkRenderPass::LoadStoreOps& colorOps, + const GrVkRenderPass::LoadStoreOps& stencilOps) { + for (int i = 0; i < fRenderPasses.count(); ++i) { + int idx = (i + fLastReturnedIndex) % fRenderPasses.count(); + if (fRenderPasses[idx]->equalLoadStoreOps(colorOps, stencilOps)) { + fLastReturnedIndex = idx; + return fRenderPasses[idx]; + } + } + GrVkRenderPass* renderPass = fRenderPasses.emplace_back(new GrVkRenderPass()); + renderPass->init(gpu, *this->getCompatibleRenderPass(), colorOps, stencilOps); + fLastReturnedIndex = fRenderPasses.count() - 1; + return renderPass; +} + +void GrVkResourceProvider::CompatibleRenderPassSet::releaseResources(GrVkGpu* gpu) { + for (int i = 0; i < fRenderPasses.count(); ++i) { + if (fRenderPasses[i]) { + fRenderPasses[i]->unref(gpu); + fRenderPasses[i] = nullptr; + } + } +} + +void GrVkResourceProvider::CompatibleRenderPassSet::abandonResources() { + for (int i = 0; i < fRenderPasses.count(); ++i) { + if (fRenderPasses[i]) { + fRenderPasses[i]->unrefAndAbandon(); + fRenderPasses[i] = nullptr; + } + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.h b/gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.h new file mode 100644 index 0000000000..44755d7a01 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkResourceProvider.h @@ -0,0 +1,284 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkResourceProvider_DEFINED +#define GrVkResourceProvider_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/SkTArray.h" +#include "src/core/SkLRUCache.h" +#include "src/core/SkTDynamicHash.h" +#include "src/core/SkTInternalLList.h" +#include "src/gpu/GrResourceHandle.h" +#include "src/gpu/vk/GrVkDescriptorPool.h" +#include "src/gpu/vk/GrVkDescriptorSetManager.h" +#include "src/gpu/vk/GrVkPipelineStateBuilder.h" +#include "src/gpu/vk/GrVkRenderPass.h" +#include "src/gpu/vk/GrVkResource.h" +#include "src/gpu/vk/GrVkSampler.h" +#include "src/gpu/vk/GrVkSamplerYcbcrConversion.h" +#include "src/gpu/vk/GrVkUtil.h" + +#include +#include + +class GrVkCommandPool; +class GrVkGpu; +class GrVkPipeline; +class GrVkPipelineState; +class GrVkPrimaryCommandBuffer; +class GrVkRenderTarget; +class GrVkSecondaryCommandBuffer; +class GrVkUniformHandler; + +class GrVkResourceProvider { +public: + GrVkResourceProvider(GrVkGpu* gpu); + ~GrVkResourceProvider(); + + // Set up any initial vk objects + void init(); + + GrVkPipeline* createPipeline(const GrProgramInfo&, + const GrStencilSettings& stencil, + VkPipelineShaderStageCreateInfo* shaderStageInfo, + int shaderStageCount, + GrPrimitiveType primitiveType, + VkRenderPass compatibleRenderPass, + VkPipelineLayout layout); + + GR_DEFINE_RESOURCE_HANDLE_CLASS(CompatibleRPHandle); + + // Finds or creates a simple render pass that matches the target, increments the refcount, + // and returns. The caller can optionally pass in a pointer to a CompatibleRPHandle. If this is + // non null it will be set to a handle that can be used in the furutre to quickly return a + // compatible GrVkRenderPasses without the need inspecting a GrVkRenderTarget. + const GrVkRenderPass* findCompatibleRenderPass(const GrVkRenderTarget& target, + CompatibleRPHandle* compatibleHandle = nullptr); + // The CompatibleRPHandle must be a valid handle previously set by a call to + // findCompatibleRenderPass(GrVkRenderTarget&, CompatibleRPHandle*). + const GrVkRenderPass* findCompatibleRenderPass(const CompatibleRPHandle& compatibleHandle); + + const GrVkRenderPass* findCompatibleExternalRenderPass(VkRenderPass, + uint32_t colorAttachmentIndex); + + // Finds or creates a render pass that matches the target and LoadStoreOps, increments the + // refcount, and returns. The caller can optionally pass in a pointer to a CompatibleRPHandle. + // If this is non null it will be set to a handle that can be used in the furutre to quickly + // return a GrVkRenderPasses without the need inspecting a GrVkRenderTarget. + const GrVkRenderPass* findRenderPass(const GrVkRenderTarget& target, + const GrVkRenderPass::LoadStoreOps& colorOps, + const GrVkRenderPass::LoadStoreOps& stencilOps, + CompatibleRPHandle* compatibleHandle = nullptr); + + // The CompatibleRPHandle must be a valid handle previously set by a call to findRenderPass or + // findCompatibleRenderPass. + const GrVkRenderPass* findRenderPass(const CompatibleRPHandle& compatibleHandle, + const GrVkRenderPass::LoadStoreOps& colorOps, + const GrVkRenderPass::LoadStoreOps& stencilOps); + + GrVkCommandPool* findOrCreateCommandPool(); + + void checkCommandBuffers(); + + // We must add the finishedProc to all active command buffers since we may have flushed work + // that the client cares about before they explicitly called flush and the GPU may reorder + // command execution. So we make sure all previously submitted work finishes before we call the + // finishedProc. + void addFinishedProcToActiveCommandBuffers(GrGpuFinishedProc finishedProc, + GrGpuFinishedContext finishedContext); + + // Finds or creates a compatible GrVkDescriptorPool for the requested type and count. + // The refcount is incremented and a pointer returned. + // TODO: Currently this will just create a descriptor pool without holding onto a ref itself + // so we currently do not reuse them. Rquires knowing if another draw is currently using + // the GrVkDescriptorPool, the ability to reset pools, and the ability to purge pools out + // of our cache of GrVkDescriptorPools. + GrVkDescriptorPool* findOrCreateCompatibleDescriptorPool(VkDescriptorType type, uint32_t count); + + // Finds or creates a compatible GrVkSampler based on the GrSamplerState and + // GrVkYcbcrConversionInfo. The refcount is incremented and a pointer returned. + GrVkSampler* findOrCreateCompatibleSampler(const GrSamplerState&, + const GrVkYcbcrConversionInfo& ycbcrInfo); + + // Finds or creates a compatible GrVkSamplerYcbcrConversion based on the GrSamplerState and + // GrVkYcbcrConversionInfo. The refcount is incremented and a pointer returned. + GrVkSamplerYcbcrConversion* findOrCreateCompatibleSamplerYcbcrConversion( + const GrVkYcbcrConversionInfo& ycbcrInfo); + + GrVkPipelineState* findOrCreateCompatiblePipelineState( + GrRenderTarget*, + const GrProgramInfo&, + GrPrimitiveType, + VkRenderPass compatibleRenderPass); + + void getSamplerDescriptorSetHandle(VkDescriptorType type, + const GrVkUniformHandler&, + GrVkDescriptorSetManager::Handle* handle); + void getSamplerDescriptorSetHandle(VkDescriptorType type, + const SkTArray& visibilities, + GrVkDescriptorSetManager::Handle* handle); + + // Returns the compatible VkDescriptorSetLayout to use for uniform buffers. The caller does not + // own the VkDescriptorSetLayout and thus should not delete it. This function should be used + // when the caller needs the layout to create a VkPipelineLayout. + VkDescriptorSetLayout getUniformDSLayout() const; + + // Returns the compatible VkDescriptorSetLayout to use for a specific sampler handle. The caller + // does not own the VkDescriptorSetLayout and thus should not delete it. This function should be + // used when the caller needs the layout to create a VkPipelineLayout. + VkDescriptorSetLayout getSamplerDSLayout(const GrVkDescriptorSetManager::Handle&) const; + + // Returns a GrVkDescriptorSet that can be used for uniform buffers. The GrVkDescriptorSet + // is already reffed for the caller. + const GrVkDescriptorSet* getUniformDescriptorSet(); + + // Returns a GrVkDescriptorSet that can be used for sampler descriptors that are compatible with + // the GrVkDescriptorSetManager::Handle passed in. The GrVkDescriptorSet is already reffed for + // the caller. + const GrVkDescriptorSet* getSamplerDescriptorSet(const GrVkDescriptorSetManager::Handle&); + + + // Signals that the descriptor set passed it, which is compatible with the passed in handle, + // can be reused by the next allocation request. + void recycleDescriptorSet(const GrVkDescriptorSet* descSet, + const GrVkDescriptorSetManager::Handle&); + + // Creates or finds free uniform buffer resources of size GrVkUniformBuffer::kStandardSize. + // Anything larger will need to be created and released by the client. + const GrVkResource* findOrCreateStandardUniformBufferResource(); + + // Signals that the resource passed to it (which should be a uniform buffer resource) + // can be reused by the next uniform buffer resource request. + void recycleStandardUniformBufferResource(const GrVkResource*); + + void storePipelineCacheData(); + + // Destroy any cached resources. To be called before destroying the VkDevice. + // The assumption is that all queues are idle and all command buffers are finished. + // For resource tracing to work properly, this should be called after unrefing all other + // resource usages. + // If deviceLost is true, then resources will not be checked to see if they've finished + // before deleting (see section 4.2.4 of the Vulkan spec). + void destroyResources(bool deviceLost); + + // Abandon any cached resources. To be used when the context/VkDevice is lost. + // For resource tracing to work properly, this should be called after unrefing all other + // resource usages. + void abandonResources(); + + void backgroundReset(GrVkCommandPool* pool); + + void reset(GrVkCommandPool* pool); + +#if GR_TEST_UTILS + void resetShaderCacheForTesting() const { fPipelineStateCache->release(); } +#endif + +private: + +#ifdef SK_DEBUG +#define GR_PIPELINE_STATE_CACHE_STATS +#endif + + class PipelineStateCache : public ::SkNoncopyable { + public: + PipelineStateCache(GrVkGpu* gpu); + ~PipelineStateCache(); + + void abandon(); + void release(); + GrVkPipelineState* refPipelineState(GrRenderTarget*, + const GrProgramInfo&, + GrPrimitiveType, + VkRenderPass compatibleRenderPass); + + private: + struct Entry; + + struct DescHash { + uint32_t operator()(const GrProgramDesc& desc) const { + return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0); + } + }; + + SkLRUCache, DescHash> fMap; + + GrVkGpu* fGpu; + +#ifdef GR_PIPELINE_STATE_CACHE_STATS + int fTotalRequests; + int fCacheMisses; +#endif + }; + + class CompatibleRenderPassSet { + public: + // This will always construct the basic load store render pass (all attachments load and + // store their data) so that there is at least one compatible VkRenderPass that can be used + // with this set. + CompatibleRenderPassSet(const GrVkGpu* gpu, const GrVkRenderTarget& target); + + bool isCompatible(const GrVkRenderTarget& target) const; + + GrVkRenderPass* getCompatibleRenderPass() const { + // The first GrVkRenderpass should always exist since we create the basic load store + // render pass on create + SkASSERT(fRenderPasses[0]); + return fRenderPasses[0]; + } + + GrVkRenderPass* getRenderPass(const GrVkGpu* gpu, + const GrVkRenderPass::LoadStoreOps& colorOps, + const GrVkRenderPass::LoadStoreOps& stencilOps); + + void releaseResources(GrVkGpu* gpu); + void abandonResources(); + + private: + SkSTArray<4, GrVkRenderPass*> fRenderPasses; + int fLastReturnedIndex; + }; + + VkPipelineCache pipelineCache(); + + GrVkGpu* fGpu; + + // Central cache for creating pipelines + VkPipelineCache fPipelineCache; + + SkSTArray<4, CompatibleRenderPassSet> fRenderPassArray; + + SkTArray fExternalRenderPasses; + + // Array of command pools that we are waiting on + SkSTArray<4, GrVkCommandPool*, true> fActiveCommandPools; + + // Array of available command pools that are not in flight + SkSTArray<4, GrVkCommandPool*, true> fAvailableCommandPools; + + // Array of available uniform buffer resources + SkSTArray<16, const GrVkResource*, true> fAvailableUniformBufferResources; + + // Stores GrVkSampler objects that we've already created so we can reuse them across multiple + // GrVkPipelineStates + SkTDynamicHash fSamplers; + + // Stores GrVkSamplerYcbcrConversion objects that we've already created so we can reuse them. + SkTDynamicHash fYcbcrConversions; + + // Cache of GrVkPipelineStates + PipelineStateCache* fPipelineStateCache; + + SkSTArray<4, std::unique_ptr> fDescriptorSetManagers; + + GrVkDescriptorSetManager::Handle fUniformDSHandle; + + std::recursive_mutex fBackgroundMutex; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSampler.cpp b/gfx/skia/skia/src/gpu/vk/GrVkSampler.cpp new file mode 100644 index 0000000000..5dc3544f00 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSampler.cpp @@ -0,0 +1,132 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkSampler.h" + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkSamplerYcbcrConversion.h" + +static inline VkSamplerAddressMode wrap_mode_to_vk_sampler_address( + GrSamplerState::WrapMode wrapMode) { + switch (wrapMode) { + case GrSamplerState::WrapMode::kClamp: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case GrSamplerState::WrapMode::kRepeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case GrSamplerState::WrapMode::kMirrorRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case GrSamplerState::WrapMode::kClampToBorder: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + } + SK_ABORT("Unknown wrap mode."); +} + +GrVkSampler* GrVkSampler::Create(GrVkGpu* gpu, const GrSamplerState& samplerState, + const GrVkYcbcrConversionInfo& ycbcrInfo) { + static VkFilter vkMinFilterModes[] = { + VK_FILTER_NEAREST, + VK_FILTER_LINEAR, + VK_FILTER_LINEAR + }; + static VkFilter vkMagFilterModes[] = { + VK_FILTER_NEAREST, + VK_FILTER_LINEAR, + VK_FILTER_LINEAR + }; + + VkSamplerCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkSamplerCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.magFilter = vkMagFilterModes[static_cast(samplerState.filter())]; + createInfo.minFilter = vkMinFilterModes[static_cast(samplerState.filter())]; + createInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + createInfo.addressModeU = wrap_mode_to_vk_sampler_address(samplerState.wrapModeX()); + createInfo.addressModeV = wrap_mode_to_vk_sampler_address(samplerState.wrapModeY()); + createInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; // Shouldn't matter + createInfo.mipLodBias = 0.0f; + createInfo.anisotropyEnable = VK_FALSE; + createInfo.maxAnisotropy = 1.0f; + createInfo.compareEnable = VK_FALSE; + createInfo.compareOp = VK_COMPARE_OP_NEVER; + // Vulkan doesn't have a direct mapping of GL's nearest or linear filters for minFilter since + // there is always a mipmapMode. To get the same effect as GL we can set minLod = maxLod = 0.0. + // This works since our min and mag filters are the same (this forces us to use mag on the 0 + // level mip). If the filters weren't the same we could set min = 0 and max = 0.25 to force + // the minFilter on mip level 0. + createInfo.minLod = 0.0f; + bool useMipMaps = GrSamplerState::Filter::kMipMap == samplerState.filter(); + createInfo.maxLod = !useMipMaps ? 0.0f : 10000.0f; + createInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + createInfo.unnormalizedCoordinates = VK_FALSE; + + VkSamplerYcbcrConversionInfo conversionInfo; + GrVkSamplerYcbcrConversion* ycbcrConversion = nullptr; + if (ycbcrInfo.isValid()) { + SkASSERT(gpu->vkCaps().supportsYcbcrConversion()); + + ycbcrConversion = + gpu->resourceProvider().findOrCreateCompatibleSamplerYcbcrConversion(ycbcrInfo); + if (!ycbcrConversion) { + return nullptr; + } + + conversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO; + conversionInfo.pNext = nullptr; + conversionInfo.conversion = ycbcrConversion->ycbcrConversion(); + + createInfo.pNext = &conversionInfo; + + VkFormatFeatureFlags flags = ycbcrInfo.fFormatFeatures; + if (!SkToBool(flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)) { + createInfo.magFilter = VK_FILTER_NEAREST; + createInfo.minFilter = VK_FILTER_NEAREST; + } else if ( + !(flags & + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT)) { + createInfo.magFilter = ycbcrInfo.fChromaFilter; + createInfo.minFilter = ycbcrInfo.fChromaFilter; + } + + // Required values when using ycbcr conversion + createInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + createInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + createInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + createInfo.anisotropyEnable = VK_FALSE; + createInfo.unnormalizedCoordinates = VK_FALSE; + } + + VkSampler sampler; + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), CreateSampler(gpu->device(), + &createInfo, + nullptr, + &sampler)); + + return new GrVkSampler(sampler, ycbcrConversion, GenerateKey(samplerState, ycbcrInfo)); +} + +void GrVkSampler::freeGPUData(GrVkGpu* gpu) const { + SkASSERT(fSampler); + GR_VK_CALL(gpu->vkInterface(), DestroySampler(gpu->device(), fSampler, nullptr)); + if (fYcbcrConversion) { + fYcbcrConversion->unref(gpu); + } +} + +void GrVkSampler::abandonGPUData() const { + if (fYcbcrConversion) { + fYcbcrConversion->unrefAndAbandon(); + } +} + +GrVkSampler::Key GrVkSampler::GenerateKey(const GrSamplerState& samplerState, + const GrVkYcbcrConversionInfo& ycbcrInfo) { + return { GrSamplerState::GenerateKey(samplerState), + GrVkSamplerYcbcrConversion::GenerateKey(ycbcrInfo) }; +} + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSampler.h b/gfx/skia/skia/src/gpu/vk/GrVkSampler.h new file mode 100644 index 0000000000..c0069c6710 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSampler.h @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkSampler_DEFINED +#define GrVkSampler_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/core/SkOpts.h" +#include "src/gpu/vk/GrVkResource.h" +#include "src/gpu/vk/GrVkSamplerYcbcrConversion.h" +#include + +class GrSamplerState; +class GrVkGpu; + +class GrVkSampler : public GrVkResource { +public: + static GrVkSampler* Create(GrVkGpu* gpu, const GrSamplerState&, const GrVkYcbcrConversionInfo&); + + VkSampler sampler() const { return fSampler; } + const VkSampler* samplerPtr() const { return &fSampler; } + + struct Key { + Key(uint8_t samplerKey, const GrVkSamplerYcbcrConversion::Key& ycbcrKey) { + // We must memset here since the GrVkSamplerYcbcrConversion has a 64 bit value which may + // force alignment padding to occur in the middle of the Key struct. + memset(this, 0, sizeof(Key)); + fSamplerKey = samplerKey; + fYcbcrKey = ycbcrKey; + } + uint8_t fSamplerKey; + GrVkSamplerYcbcrConversion::Key fYcbcrKey; + + bool operator==(const Key& that) const { + return this->fSamplerKey == that.fSamplerKey && + this->fYcbcrKey == that.fYcbcrKey; + } + }; + + // Helpers for hashing GrVkSampler + static Key GenerateKey(const GrSamplerState&, const GrVkYcbcrConversionInfo&); + + static const Key& GetKey(const GrVkSampler& sampler) { return sampler.fKey; } + static uint32_t Hash(const Key& key) { + return SkOpts::hash(reinterpret_cast(&key), sizeof(Key)); + } + + uint32_t uniqueID() const { return fUniqueID; } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkSampler: %d (%d refs)\n", fSampler, this->getRefCnt()); + } +#endif + +private: + GrVkSampler(VkSampler sampler, GrVkSamplerYcbcrConversion* ycbcrConversion, Key key) + : INHERITED() + , fSampler(sampler) + , fYcbcrConversion(ycbcrConversion) + , fKey(key) + , fUniqueID(GenID()) {} + + void freeGPUData(GrVkGpu* gpu) const override; + void abandonGPUData() const override; + + static uint32_t GenID() { + static std::atomic nextID{1}; + uint32_t id; + do { + id = nextID++; + } while (id == SK_InvalidUniqueID); + return id; + } + + VkSampler fSampler; + GrVkSamplerYcbcrConversion* fYcbcrConversion; + Key fKey; + uint32_t fUniqueID; + + typedef GrVkResource INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp b/gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp new file mode 100644 index 0000000000..f33f5216dc --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkSamplerYcbcrConversion.h" + +#include "src/gpu/vk/GrVkGpu.h" + +GrVkSamplerYcbcrConversion* GrVkSamplerYcbcrConversion::Create( + const GrVkGpu* gpu, const GrVkYcbcrConversionInfo& info) { + if (!gpu->vkCaps().supportsYcbcrConversion()) { + return nullptr; + } + +#ifdef SK_DEBUG + const VkFormatFeatureFlags& featureFlags = info.fFormatFeatures; + if (info.fXChromaOffset == VK_CHROMA_LOCATION_MIDPOINT || + info.fYChromaOffset == VK_CHROMA_LOCATION_MIDPOINT) { + SkASSERT(featureFlags & VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT); + } + if (info.fXChromaOffset == VK_CHROMA_LOCATION_COSITED_EVEN || + info.fYChromaOffset == VK_CHROMA_LOCATION_COSITED_EVEN) { + SkASSERT(featureFlags & VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT); + } + if (info.fChromaFilter == VK_FILTER_LINEAR) { + SkASSERT(featureFlags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT); + } + if (info.fForceExplicitReconstruction) { + SkASSERT(featureFlags & + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT); + } +#endif + + + VkSamplerYcbcrConversionCreateInfo ycbcrCreateInfo; + ycbcrCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO; + ycbcrCreateInfo.pNext = nullptr; + ycbcrCreateInfo.format = info.fFormat; + ycbcrCreateInfo.ycbcrModel = info.fYcbcrModel; + ycbcrCreateInfo.ycbcrRange = info.fYcbcrRange; + + // Components is ignored for external format conversions. For all other formats identity swizzle + // is used. It can be added to GrVkYcbcrConversionInfo if necessary. + ycbcrCreateInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; + ycbcrCreateInfo.xChromaOffset = info.fXChromaOffset; + ycbcrCreateInfo.yChromaOffset = info.fYChromaOffset; + ycbcrCreateInfo.chromaFilter = info.fChromaFilter; + ycbcrCreateInfo.forceExplicitReconstruction = info.fForceExplicitReconstruction; + +#ifdef SK_BUILD_FOR_ANDROID + VkExternalFormatANDROID externalFormat; + if (info.fExternalFormat) { + // Format must not be specified for external images. + SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED); + externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID; + externalFormat.pNext = nullptr; + externalFormat.externalFormat = info.fExternalFormat; + ycbcrCreateInfo.pNext = &externalFormat; + } +#else + // External images are supported only on Android; + SkASSERT(!info.fExternalFormat); +#endif + + if (!info.fExternalFormat) { + SkASSERT(info.fFormat != VK_FORMAT_UNDEFINED); + } + + VkSamplerYcbcrConversion conversion; + GR_VK_CALL(gpu->vkInterface(), CreateSamplerYcbcrConversion(gpu->device(), &ycbcrCreateInfo, + nullptr, &conversion)); + if (conversion == VK_NULL_HANDLE) { + return nullptr; + } + + return new GrVkSamplerYcbcrConversion(conversion, GenerateKey(info)); +} + +void GrVkSamplerYcbcrConversion::freeGPUData(GrVkGpu* gpu) const { + SkASSERT(fYcbcrConversion); + GR_VK_CALL(gpu->vkInterface(), DestroySamplerYcbcrConversion(gpu->device(), fYcbcrConversion, + nullptr)); +} + +GrVkSamplerYcbcrConversion::Key GrVkSamplerYcbcrConversion::GenerateKey( + const GrVkYcbcrConversionInfo& ycbcrInfo) { + SkASSERT(static_cast(ycbcrInfo.fYcbcrModel <= 7)); + static const int kRangeShift = 3; + SkASSERT(static_cast(ycbcrInfo.fYcbcrRange) <= 1); + static const int kXChromaOffsetShift = kRangeShift + 1; + SkASSERT(static_cast(ycbcrInfo.fXChromaOffset) <= 1); + static const int kYChromaOffsetShift = kXChromaOffsetShift + 1; + SkASSERT(static_cast(ycbcrInfo.fXChromaOffset) <= 1); + static const int kChromaFilterShift = kYChromaOffsetShift + 1; + SkASSERT(static_cast(ycbcrInfo.fChromaFilter) <= 1); + static const int kReconShift = kChromaFilterShift + 1; + SkASSERT(static_cast(ycbcrInfo.fForceExplicitReconstruction) <= 1); + GR_STATIC_ASSERT(kReconShift <= 7); + + uint8_t ycbcrKey = static_cast(ycbcrInfo.fYcbcrModel); + ycbcrKey |= (static_cast(ycbcrInfo.fYcbcrRange) << kRangeShift); + ycbcrKey |= (static_cast(ycbcrInfo.fXChromaOffset) << kXChromaOffsetShift); + ycbcrKey |= (static_cast(ycbcrInfo.fYChromaOffset) << kYChromaOffsetShift); + ycbcrKey |= (static_cast(ycbcrInfo.fChromaFilter) << kChromaFilterShift); + ycbcrKey |= (static_cast(ycbcrInfo.fForceExplicitReconstruction) << kReconShift); + + return Key{ycbcrInfo.fFormat, ycbcrInfo.fExternalFormat, ycbcrKey}; +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h b/gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h new file mode 100644 index 0000000000..cf7a2c5995 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h @@ -0,0 +1,75 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkSamplerYcbcrConverison_DEFINED +#define GrVkSamplerYcbcrConverison_DEFINED + +#include "src/gpu/vk/GrVkResource.h" + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/core/SkOpts.h" + +class GrVkGpu; + +class GrVkSamplerYcbcrConversion : public GrVkResource { +public: + static GrVkSamplerYcbcrConversion* Create(const GrVkGpu* gpu, const GrVkYcbcrConversionInfo&); + + VkSamplerYcbcrConversion ycbcrConversion() const { return fYcbcrConversion; } + + struct Key { + Key() : fVkFormat(VK_FORMAT_UNDEFINED), fExternalFormat(0), fConversionKey(0) {} + Key(VkFormat vkFormat, uint64_t externalFormat, uint8_t conversionKey) { + memset(this, 0, sizeof(Key)); + fVkFormat = vkFormat; + fExternalFormat = externalFormat; + fConversionKey = conversionKey; + } + + VkFormat fVkFormat; + uint64_t fExternalFormat; + uint8_t fConversionKey; + + bool operator==(const Key& that) const { + return this->fVkFormat == that.fVkFormat && + this->fExternalFormat == that.fExternalFormat && + this->fConversionKey == that.fConversionKey; + } + }; + + // Helpers for hashing GrVkSamplerYcbcrConversion + static Key GenerateKey(const GrVkYcbcrConversionInfo& ycbcrInfo); + + static const Key& GetKey(const GrVkSamplerYcbcrConversion& ycbcrConversion) { + return ycbcrConversion.fKey; + } + static uint32_t Hash(const Key& key) { + return SkOpts::hash(reinterpret_cast(&key), sizeof(Key)); + } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkSamplerYcbcrConversion: %d (%d refs)\n", fYcbcrConversion, this->getRefCnt()); + } +#endif + +private: + GrVkSamplerYcbcrConversion(VkSamplerYcbcrConversion ycbcrConversion, Key key) + : INHERITED() + , fYcbcrConversion(ycbcrConversion) + , fKey(key) {} + + void freeGPUData(GrVkGpu* gpu) const override; + + VkSamplerYcbcrConversion fYcbcrConversion; + Key fKey; + + typedef GrVkResource INHERITED; +}; + +#endif + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp b/gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp new file mode 100644 index 0000000000..8f71f3ec5a --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkSecondaryCBDrawContext.h" + +#include "include/core/SkImageInfo.h" +#include "include/core/SkSurfaceCharacterization.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/SkDeferredDisplayList.h" +#include "src/core/SkSurfacePriv.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/SkGpuDevice.h" + +sk_sp GrVkSecondaryCBDrawContext::Make(GrContext* ctx, + const SkImageInfo& imageInfo, + const GrVkDrawableInfo& vkInfo, + const SkSurfaceProps* props) { + if (!ctx) { + return nullptr; + } + + if (ctx->backend() != GrBackendApi::kVulkan) { + return nullptr; + } + + auto rtc = ctx->priv().makeVulkanSecondaryCBRenderTargetContext(imageInfo, vkInfo, props); + SkASSERT(rtc->asSurfaceProxy()->isInstantiated()); + + sk_sp device( + SkGpuDevice::Make(ctx, std::move(rtc), SkGpuDevice::kUninit_InitContents)); + if (!device) { + return nullptr; + } + + return sk_sp(new GrVkSecondaryCBDrawContext(std::move(device), + props)); +} + +GrVkSecondaryCBDrawContext::GrVkSecondaryCBDrawContext(sk_sp device, + const SkSurfaceProps* props) + : fDevice(device) + , fProps(SkSurfacePropsCopyOrDefault(props)) {} + +GrVkSecondaryCBDrawContext::~GrVkSecondaryCBDrawContext() { + SkASSERT(!fDevice); + SkASSERT(!fCachedCanvas.get()); +} + +SkCanvas* GrVkSecondaryCBDrawContext::getCanvas() { + if (!fCachedCanvas) { + fCachedCanvas = std::unique_ptr(new SkCanvas(fDevice)); + } + return fCachedCanvas.get(); +} + +void GrVkSecondaryCBDrawContext::flush() { + fDevice->flush(); +} + +bool GrVkSecondaryCBDrawContext::wait(int numSemaphores, + const GrBackendSemaphore waitSemaphores[]) { + return fDevice->wait(numSemaphores, waitSemaphores); +} + +void GrVkSecondaryCBDrawContext::releaseResources() { + fCachedCanvas.reset(); + fDevice.reset(); +} + +bool GrVkSecondaryCBDrawContext::characterize(SkSurfaceCharacterization* characterization) const { + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + GrContext* ctx = fDevice->context(); + + size_t maxResourceBytes = ctx->getResourceCacheLimit(); + + // We current don't support textured GrVkSecondaryCBDrawContexts. + SkASSERT(!rtc->asTextureProxy()); + + SkColorType ct = GrColorTypeToSkColorType(rtc->colorInfo().colorType()); + if (ct == kUnknown_SkColorType) { + return false; + } + + SkImageInfo ii = SkImageInfo::Make(rtc->width(), rtc->height(), ct, kPremul_SkAlphaType, + rtc->colorInfo().refColorSpace()); + + GrBackendFormat format = rtc->asRenderTargetProxy()->backendFormat(); + + characterization->set(ctx->threadSafeProxy(), maxResourceBytes, ii, format, + rtc->origin(), rtc->numSamples(), + SkSurfaceCharacterization::Textureable(false), + SkSurfaceCharacterization::MipMapped(false), + SkSurfaceCharacterization::UsesGLFBO0(false), + SkSurfaceCharacterization::VulkanSecondaryCBCompatible(true), + GrProtected(rtc->asRenderTargetProxy()->isProtected()), + this->props()); + + return true; +} + +bool GrVkSecondaryCBDrawContext::isCompatible( + const SkSurfaceCharacterization& characterization) const { + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + GrContext* ctx = fDevice->context(); + + if (!characterization.isValid()) { + return false; + } + + if (!characterization.vulkanSecondaryCBCompatible()) { + return false; + } + + // As long as the current state in the context allows for greater or equal resources, + // we allow the DDL to be replayed. + // DDL TODO: should we just remove the resource check and ignore the cache limits on playback? + size_t maxResourceBytes = ctx->getResourceCacheLimit(); + + if (characterization.isTextureable()) { + // We don't support textureable DDL when rendering to a GrVkSecondaryCBDrawContext. + return false; + } + + if (characterization.usesGLFBO0()) { + return false; + } + + SkColorType rtColorType = GrColorTypeToSkColorType(rtc->colorInfo().colorType()); + if (rtColorType == kUnknown_SkColorType) { + return false; + } + + GrBackendFormat rtcFormat = rtc->asRenderTargetProxy()->backendFormat(); + GrProtected isProtected = GrProtected(rtc->asRenderTargetProxy()->isProtected()); + + return characterization.contextInfo() && characterization.contextInfo()->priv().matches(ctx) && + characterization.cacheMaxResourceBytes() <= maxResourceBytes && + characterization.origin() == rtc->origin() && + characterization.backendFormat() == rtcFormat && + characterization.width() == rtc->width() && + characterization.height() == rtc->height() && + characterization.colorType() == rtColorType && + characterization.sampleCount() == rtc->numSamples() && + SkColorSpace::Equals(characterization.colorSpace(), rtc->colorInfo().colorSpace()) && + characterization.isProtected() == isProtected && + characterization.surfaceProps() == rtc->surfaceProps(); +} + +bool GrVkSecondaryCBDrawContext::draw(SkDeferredDisplayList* ddl) { + if (!ddl || !this->isCompatible(ddl->characterization())) { + return false; + } + + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + GrContext* ctx = fDevice->context(); + + ctx->priv().copyRenderTasksFromDDL(ddl, rtc->asRenderTargetProxy()); + return true; +} + + diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h b/gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h new file mode 100644 index 0000000000..b37f6a6093 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkSecondaryCBDrawContext_DEFINED +#define GrVkSecondaryCBDrawContext_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkSurfaceProps.h" +#include "include/core/SkTypes.h" + +class GrBackendSemaphore; +class GrContext; +struct GrVkDrawableInfo; +class SkCanvas; +class SkDeferredDisplayList; +class SkGpuDevice; +struct SkImageInfo; +class SkSurfaceCharacterization; +class SkSurfaceProps; + +/** + * This class is a private header that is intended to only be used inside of Chromium. This requires + * Chromium to burrow in and include this specifically since it is not part of skia's public include + * directory. + */ + +/** + * This class is used to draw into an external Vulkan secondary command buffer that is imported + * by the client. The secondary command buffer that gets imported must already have had begin called + * on it with VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT. Thus any draws to the imported + * command buffer cannot require changing the render pass. This requirement means that certain types + * of draws will not be supported when using a GrVkSecondaryCBDrawContext. This includes: + * Draws that require a dst copy for blending will be dropped + * Text draws will be dropped (these may require intermediate uploads of text data) + * Read and Write pixels will not work + * Any other draw that requires a copy will fail (this includes using backdrop filter with save + * layer). + * Stenciling is also disabled, but that should not restrict any actual draws from working. + * + * While using a GrVkSecondaryCBDrawContext, the client can also draw into normal SkSurfaces and + * then draw those SkSufaces (as SkImages) into the GrVkSecondaryCBDrawContext. If any of the + * previously mentioned unsupported draws are needed by the client, they can draw them into an + * offscreen surface, and then draw that into the GrVkSecondaryCBDrawContext. + * + * After all drawing to the GrVkSecondaryCBDrawContext has been done, the client must call flush() + * on the GrVkSecondaryCBDrawContext to actually fill in the secondary VkCommandBuffer with the + * draws. + * + * Additionally, the client must keep the GrVkSecondaryCBDrawContext alive until the secondary + * VkCommandBuffer has been submitted and all work finished on the GPU. Before deleting the + * GrVkSecondaryCBDrawContext, the client must call releaseResources() so that Skia can cleanup + * any internal objects that were created for the draws into the secondary command buffer. + */ +class SK_API GrVkSecondaryCBDrawContext : public SkRefCnt { +public: + static sk_sp Make(GrContext*, const SkImageInfo&, + const GrVkDrawableInfo&, + const SkSurfaceProps* props); + + ~GrVkSecondaryCBDrawContext() override; + + SkCanvas* getCanvas(); + + // Records all the draws to the imported secondary command buffer and sets any dependent + // offscreen draws to the GPU. + void flush(); + + /** Inserts a list of GPU semaphores that Skia will have the driver wait on before executing + commands for this secondary CB. The wait semaphores will get added to the VkCommandBuffer + owned by this GrContext when flush() is called, and not the command buffer which the + Secondary CB is from. This will guarantee that the driver waits on the semaphores before + the secondary command buffer gets executed. Skia will take ownership of the underlying + semaphores and delete them once they have been signaled and waited on. If this call returns + false, then the GPU back-end will not wait on any passed in semaphores, and the client will + still own the semaphores. + + @param numSemaphores size of waitSemaphores array + @param waitSemaphores array of semaphore containers + @return true if GPU is waiting on semaphores + */ + bool wait(int numSemaphores, const GrBackendSemaphore waitSemaphores[]); + + // This call will release all resources held by the draw context. The client must call + // releaseResources() before deleting the drawing context. However, the resources also include + // any Vulkan resources that were created and used for draws. Therefore the client must only + // call releaseResources() after submitting the secondary command buffer, and waiting for it to + // finish on the GPU. If it is called earlier then some vulkan objects may be deleted while they + // are still in use by the GPU. + void releaseResources(); + + const SkSurfaceProps& props() const { return fProps; } + + // TODO: Fill out these calls to support DDL + bool characterize(SkSurfaceCharacterization* characterization) const; + bool draw(SkDeferredDisplayList* deferredDisplayList); + +private: + explicit GrVkSecondaryCBDrawContext(sk_sp, const SkSurfaceProps*); + + bool isCompatible(const SkSurfaceCharacterization& characterization) const; + + sk_sp fDevice; + std::unique_ptr fCachedCanvas; + const SkSurfaceProps fProps; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSemaphore.cpp b/gfx/skia/skia/src/gpu/vk/GrVkSemaphore.cpp new file mode 100644 index 0000000000..dd7796ee88 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSemaphore.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkSemaphore.h" + +#include "include/gpu/GrBackendSemaphore.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkUtil.h" + +#ifdef VK_USE_PLATFORM_WIN32_KHR +// windows wants to define this as CreateSemaphoreA or CreateSemaphoreW +#undef CreateSemaphore +#endif + +sk_sp GrVkSemaphore::Make(GrVkGpu* gpu, bool isOwned) { + VkSemaphoreCreateInfo createInfo; + memset(&createInfo, 0, sizeof(VkSemaphoreCreateInfo)); + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = nullptr; + createInfo.flags = 0; + VkSemaphore semaphore = VK_NULL_HANDLE; + GR_VK_CALL_ERRCHECK(gpu->vkInterface(), + CreateSemaphore(gpu->device(), &createInfo, nullptr, &semaphore)); + + return sk_sp(new GrVkSemaphore(gpu, semaphore, false, false, isOwned)); +} + +sk_sp GrVkSemaphore::MakeWrapped(GrVkGpu* gpu, + VkSemaphore semaphore, + WrapType wrapType, + GrWrapOwnership ownership) { + if (VK_NULL_HANDLE == semaphore) { + return nullptr; + } + bool prohibitSignal = WrapType::kWillWait == wrapType; + bool prohibitWait = WrapType::kWillSignal == wrapType; + return sk_sp(new GrVkSemaphore(gpu, semaphore, prohibitSignal, prohibitWait, + kBorrow_GrWrapOwnership != ownership)); +} + +GrVkSemaphore::GrVkSemaphore(GrVkGpu* gpu, VkSemaphore semaphore, bool prohibitSignal, + bool prohibitWait, bool isOwned) + : INHERITED(gpu) { + fResource = new Resource(semaphore, prohibitSignal, prohibitWait, isOwned); + isOwned ? this->registerWithCache(SkBudgeted::kNo) + : this->registerWithCacheWrapped(GrWrapCacheable::kNo); +} + +void GrVkSemaphore::onRelease() { + if (fResource) { + fResource->unref(static_cast(this->getGpu())); + fResource = nullptr; + } + INHERITED::onRelease(); +} + +void GrVkSemaphore::onAbandon() { + if (fResource) { + fResource->unrefAndAbandon(); + fResource = nullptr; + } + INHERITED::onAbandon(); +} + +void GrVkSemaphore::Resource::freeGPUData(GrVkGpu* gpu) const { + if (fIsOwned) { + GR_VK_CALL(gpu->vkInterface(), + DestroySemaphore(gpu->device(), fSemaphore, nullptr)); + } +} + +GrBackendSemaphore GrVkSemaphore::backendSemaphore() const { + GrBackendSemaphore backendSemaphore; + backendSemaphore.initVulkan(fResource->semaphore()); + return backendSemaphore; +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkSemaphore.h b/gfx/skia/skia/src/gpu/vk/GrVkSemaphore.h new file mode 100644 index 0000000000..0c73aa1191 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkSemaphore.h @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkSemaphore_DEFINED +#define GrVkSemaphore_DEFINED + +#include "src/gpu/GrSemaphore.h" + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/vk/GrVkResource.h" + +class GrBackendSemaphore; +class GrVkGpu; + +class GrVkSemaphore : public GrSemaphore { +public: + static sk_sp Make(GrVkGpu* gpu, bool isOwned); + + using WrapType = GrResourceProvider::SemaphoreWrapType; + + static sk_sp MakeWrapped(GrVkGpu* gpu, + VkSemaphore semaphore, + WrapType wrapType, + GrWrapOwnership); + + GrBackendSemaphore backendSemaphore() const override; + + class Resource : public GrVkResource { + public: + Resource(VkSemaphore semaphore, bool prohibitSignal, bool prohibitWait, bool isOwned) + : INHERITED() + , fSemaphore(semaphore) + , fHasBeenSubmittedToQueueForSignal(prohibitSignal) + , fHasBeenSubmittedToQueueForWait(prohibitWait) + , fIsOwned(isOwned) {} + + ~Resource() override {} + + VkSemaphore semaphore() const { return fSemaphore; } + + bool shouldSignal() const { + return !fHasBeenSubmittedToQueueForSignal; + } + bool shouldWait() const { + return !fHasBeenSubmittedToQueueForWait; + } + + void markAsSignaled() { + fHasBeenSubmittedToQueueForSignal = true; + } + void markAsWaited() { + fHasBeenSubmittedToQueueForWait = true; + } + +#ifdef SK_TRACE_VK_RESOURCES + void dumpInfo() const override { + SkDebugf("GrVkSemaphore: %d (%d refs)\n", fSemaphore, this->getRefCnt()); + } +#endif + private: + void freeGPUData(GrVkGpu* gpu) const override; + + VkSemaphore fSemaphore; + bool fHasBeenSubmittedToQueueForSignal; + bool fHasBeenSubmittedToQueueForWait; + bool fIsOwned; + + typedef GrVkResource INHERITED; + }; + + Resource* getResource() { return fResource; } + +private: + GrVkSemaphore(GrVkGpu* gpu, VkSemaphore semaphore, bool prohibitSignal, bool prohibitWait, + bool isOwned); + + void onRelease() override; + void onAbandon() override; + + Resource* fResource; + + typedef GrSemaphore INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.cpp b/gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.cpp new file mode 100644 index 0000000000..58c92d8c4b --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImage.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkStencilAttachment.h" +#include "src/gpu/vk/GrVkUtil.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +GrVkStencilAttachment::GrVkStencilAttachment(GrVkGpu* gpu, + const Format& format, + const GrVkImage::ImageDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* stencilView) + : GrStencilAttachment(gpu, desc.fWidth, desc.fHeight, format.fStencilBits, desc.fSamples) + , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kOwned) + , fFormat(format) + , fStencilView(stencilView) { + this->registerWithCache(SkBudgeted::kYes); + stencilView->ref(); +} + +GrVkStencilAttachment* GrVkStencilAttachment::Create(GrVkGpu* gpu, + int width, + int height, + int sampleCnt, + const Format& format) { + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = format.fInternalFormat; + imageDesc.fWidth = width; + imageDesc.fHeight = height; + imageDesc.fLevels = 1; + imageDesc.fSamples = sampleCnt; + imageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; + imageDesc.fMemProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + GrVkImageInfo info; + if (!GrVkImage::InitImageInfo(gpu, imageDesc, &info)) { + return nullptr; + } + + const GrVkImageView* imageView = GrVkImageView::Create(gpu, info.fImage, + format.fInternalFormat, + GrVkImageView::kStencil_Type, 1, + GrVkYcbcrConversionInfo()); + if (!imageView) { + GrVkImage::DestroyImageInfo(gpu, &info); + return nullptr; + } + + sk_sp layout(new GrVkImageLayout(info.fImageLayout)); + GrVkStencilAttachment* stencil = new GrVkStencilAttachment(gpu, format, imageDesc, + info, std::move(layout), imageView); + imageView->unref(gpu); + + return stencil; +} + +GrVkStencilAttachment::~GrVkStencilAttachment() { + // should have been released or abandoned first + SkASSERT(!fStencilView); +} + +size_t GrVkStencilAttachment::onGpuMemorySize() const { + uint64_t size = this->width(); + size *= this->height(); + size *= fFormat.fTotalBits; + size *= this->numSamples(); + return static_cast(size / 8); +} + +void GrVkStencilAttachment::onRelease() { + GrVkGpu* gpu = this->getVkGpu(); + + this->releaseImage(gpu); + + fStencilView->unref(gpu); + fStencilView = nullptr; + GrStencilAttachment::onRelease(); +} + +void GrVkStencilAttachment::onAbandon() { + this->abandonImage(); + fStencilView->unrefAndAbandon(); + fStencilView = nullptr; + GrStencilAttachment::onAbandon(); +} + +GrVkGpu* GrVkStencilAttachment::getVkGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.h b/gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.h new file mode 100644 index 0000000000..568e0e5c93 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkStencilAttachment.h @@ -0,0 +1,58 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkStencil_DEFINED +#define GrVkStencil_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrStencilAttachment.h" +#include "src/gpu/vk/GrVkImage.h" + +class GrVkImageView; +class GrVkGpu; + +class GrVkStencilAttachment : public GrStencilAttachment, public GrVkImage { +public: + struct Format { + VkFormat fInternalFormat; + int fStencilBits; + int fTotalBits; + bool fPacked; + }; + + static GrVkStencilAttachment* Create(GrVkGpu* gpu, int width, int height, + int sampleCnt, const Format& format); + + ~GrVkStencilAttachment() override; + + const GrVkResource* imageResource() const { return this->resource(); } + const GrVkImageView* stencilView() const { return fStencilView; } + + VkFormat vkFormat() const { return fFormat.fInternalFormat; } + +protected: + void onRelease() override; + void onAbandon() override; + +private: + size_t onGpuMemorySize() const override; + + GrVkStencilAttachment(GrVkGpu* gpu, + const Format& format, + const GrVkImage::ImageDesc&, + const GrVkImageInfo&, + sk_sp layout, + const GrVkImageView* stencilView); + + GrVkGpu* getVkGpu() const; + + Format fFormat; + + const GrVkImageView* fStencilView; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTexture.cpp b/gfx/skia/skia/src/gpu/vk/GrVkTexture.cpp new file mode 100644 index 0000000000..e162b34b99 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTexture.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkTexture.h" + +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkTextureRenderTarget.h" +#include "src/gpu/vk/GrVkUtil.h" + +#include "include/gpu/vk/GrVkTypes.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +// Because this class is virtually derived from GrSurface we must explicitly call its constructor. +GrVkTexture::GrVkTexture(GrVkGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* view, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kOwned) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected, + GrTextureType::k2D, mipMapsStatus) + , fTextureView(view) { + SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == info.fLevelCount)); + // We don't support creating external GrVkTextures + SkASSERT(!info.fYcbcrConversionInfo.isValid() || !info.fYcbcrConversionInfo.fExternalFormat); + this->registerWithCache(budgeted); + if (GrVkFormatIsCompressed(info.fFormat)) { + this->setReadOnly(); + } +} + +GrVkTexture::GrVkTexture(GrVkGpu* gpu, const GrSurfaceDesc& desc, const GrVkImageInfo& info, + sk_sp layout, const GrVkImageView* view, + GrMipMapsStatus mipMapsStatus, GrBackendObjectOwnership ownership, + GrWrapCacheable cacheable, GrIOType ioType, bool isExternal) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, std::move(layout), ownership) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected, + isExternal ? GrTextureType::kExternal : GrTextureType::k2D, mipMapsStatus) + , fTextureView(view) { + SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == info.fLevelCount)); + if (ioType == kRead_GrIOType) { + this->setReadOnly(); + } + this->registerWithCacheWrapped(cacheable); +} + +// Because this class is virtually derived from GrSurface we must explicitly call its constructor. +GrVkTexture::GrVkTexture(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* view, + GrMipMapsStatus mipMapsStatus, + GrBackendObjectOwnership ownership) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, layout, ownership) + , INHERITED(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected, + GrTextureType::k2D, mipMapsStatus) + , fTextureView(view) { + SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == info.fLevelCount)); + // Since this ctor is only called from GrVkTextureRenderTarget, we can't have a ycbcr conversion + // since we don't support that on render targets. + SkASSERT(!info.fYcbcrConversionInfo.isValid()); +} + +sk_sp GrVkTexture::MakeNewTexture(GrVkGpu* gpu, SkBudgeted budgeted, + const GrSurfaceDesc& desc, + const GrVkImage::ImageDesc& imageDesc, + GrMipMapsStatus mipMapsStatus) { + SkASSERT(imageDesc.fUsageFlags & VK_IMAGE_USAGE_SAMPLED_BIT); + + GrVkImageInfo info; + if (!GrVkImage::InitImageInfo(gpu, imageDesc, &info)) { + return nullptr; + } + + const GrVkImageView* imageView = GrVkImageView::Create( + gpu, info.fImage, info.fFormat, GrVkImageView::kColor_Type, info.fLevelCount, + info.fYcbcrConversionInfo); + if (!imageView) { + GrVkImage::DestroyImageInfo(gpu, &info); + return nullptr; + } + sk_sp layout(new GrVkImageLayout(info.fImageLayout)); + + return sk_sp(new GrVkTexture(gpu, budgeted, desc, info, std::move(layout), + imageView, mipMapsStatus)); +} + +sk_sp GrVkTexture::MakeWrappedTexture(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + GrWrapOwnership wrapOwnership, + GrWrapCacheable cacheable, + GrIOType ioType, + const GrVkImageInfo& info, + sk_sp layout) { + // Adopted textures require both image and allocation because we're responsible for freeing + SkASSERT(VK_NULL_HANDLE != info.fImage && + (kBorrow_GrWrapOwnership == wrapOwnership || VK_NULL_HANDLE != info.fAlloc.fMemory)); + + const GrVkImageView* imageView = GrVkImageView::Create( + gpu, info.fImage, info.fFormat, GrVkImageView::kColor_Type, info.fLevelCount, + info.fYcbcrConversionInfo); + if (!imageView) { + return nullptr; + } + + GrMipMapsStatus mipMapsStatus = info.fLevelCount > 1 ? GrMipMapsStatus::kValid + : GrMipMapsStatus::kNotAllocated; + + GrBackendObjectOwnership ownership = kBorrow_GrWrapOwnership == wrapOwnership + ? GrBackendObjectOwnership::kBorrowed : GrBackendObjectOwnership::kOwned; + bool isExternal = info.fYcbcrConversionInfo.isValid() && + (info.fYcbcrConversionInfo.fExternalFormat != 0); + return sk_sp(new GrVkTexture(gpu, desc, info, std::move(layout), imageView, + mipMapsStatus, ownership, cacheable, ioType, + isExternal)); +} + +GrVkTexture::~GrVkTexture() { + // either release or abandon should have been called by the owner of this object. + SkASSERT(!fTextureView); +} + +void GrVkTexture::onRelease() { + // We're about to be severed from our GrVkResource. If there are "finish" idle procs we have to + // decide who will handle them. If the resource is still tied to a command buffer we let it + // handle them. Otherwise, we handle them. + if (this->hasResource() && this->resource()->isOwnedByCommandBuffer()) { + this->removeFinishIdleProcs(); + } + + // we create this and don't hand it off, so we should always destroy it + if (fTextureView) { + fTextureView->unref(this->getVkGpu()); + fTextureView = nullptr; + } + + this->releaseImage(this->getVkGpu()); + + INHERITED::onRelease(); +} + +void GrVkTexture::onAbandon() { + // We're about to be severed from our GrVkResource. If there are "finish" idle procs we have to + // decide who will handle them. If the resource is still tied to a command buffer we let it + // handle them. Otherwise, we handle them. + if (this->hasResource() && this->resource()->isOwnedByCommandBuffer()) { + this->removeFinishIdleProcs(); + } + + // we create this and don't hand it off, so we should always destroy it + if (fTextureView) { + fTextureView->unrefAndAbandon(); + fTextureView = nullptr; + } + + this->abandonImage(); + INHERITED::onAbandon(); +} + +GrBackendTexture GrVkTexture::getBackendTexture() const { + return GrBackendTexture(this->width(), this->height(), fInfo, this->grVkImageLayout()); +} + +GrVkGpu* GrVkTexture::getVkGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} + +const GrVkImageView* GrVkTexture::textureView() { + return fTextureView; +} + +void GrVkTexture::addIdleProc(sk_sp idleProc, IdleState type) { + INHERITED::addIdleProc(idleProc, type); + if (type == IdleState::kFinished) { + if (auto* resource = this->resource()) { + resource->addIdleProc(this, std::move(idleProc)); + } + } +} + +void GrVkTexture::callIdleProcsOnBehalfOfResource() { + // If we got here then the resource is being removed from its last command buffer and the + // texture is idle in the cache. Any kFlush idle procs should already have been called. So + // the texture and resource should have the same set of procs. + SkASSERT(this->resource()); + SkASSERT(this->resource()->idleProcCnt() == fIdleProcs.count()); +#ifdef SK_DEBUG + for (int i = 0; i < fIdleProcs.count(); ++i) { + SkASSERT(fIdleProcs[i] == this->resource()->idleProc(i)); + } +#endif + fIdleProcs.reset(); + this->resource()->resetIdleProcs(); +} + +void GrVkTexture::willRemoveLastRef() { + if (!fIdleProcs.count()) { + return; + } + // This is called when the GrTexture is purgeable. However, we need to check whether the + // Resource is still owned by any command buffers. If it is then it will call the proc. + auto* resource = this->hasResource() ? this->resource() : nullptr; + bool callFinishProcs = !resource || !resource->isOwnedByCommandBuffer(); + if (callFinishProcs) { + // Everything must go! + fIdleProcs.reset(); + resource->resetIdleProcs(); + } else { + // The procs that should be called on flush but not finish are those that are owned + // by the GrVkTexture and not the Resource. We do this by copying the resource's array + // and thereby dropping refs to procs we own but the resource does not. + SkASSERT(resource); + fIdleProcs.reset(resource->idleProcCnt()); + for (int i = 0; i < fIdleProcs.count(); ++i) { + fIdleProcs[i] = resource->idleProc(i); + } + } +} + +void GrVkTexture::removeFinishIdleProcs() { + // This should only be called by onRelease/onAbandon when we have already checked for a + // resource. + const auto* resource = this->resource(); + SkASSERT(resource); + SkSTArray<4, sk_sp> procsToKeep; + int resourceIdx = 0; + // The idle procs that are common between the GrVkTexture and its Resource should be found in + // the same order. + for (int i = 0; i < fIdleProcs.count(); ++i) { + if (fIdleProcs[i] == resource->idleProc(resourceIdx)) { + ++resourceIdx; + } else { + procsToKeep.push_back(fIdleProcs[i]); + } + } + SkASSERT(resourceIdx == resource->idleProcCnt()); + fIdleProcs = procsToKeep; +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTexture.h b/gfx/skia/skia/src/gpu/vk/GrVkTexture.h new file mode 100644 index 0000000000..6d90826227 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTexture.h @@ -0,0 +1,81 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkTexture_DEFINED +#define GrVkTexture_DEFINED + +#include "include/gpu/GrTexture.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkImage.h" + +class GrVkGpu; +class GrVkImageView; +struct GrVkImageInfo; + +class GrVkTexture : public GrTexture, public virtual GrVkImage { +public: + static sk_sp MakeNewTexture(GrVkGpu*, + SkBudgeted budgeted, + const GrSurfaceDesc&, + const GrVkImage::ImageDesc&, + GrMipMapsStatus); + + static sk_sp MakeWrappedTexture(GrVkGpu*, const GrSurfaceDesc&, GrWrapOwnership, + GrWrapCacheable, GrIOType, const GrVkImageInfo&, + sk_sp); + + ~GrVkTexture() override; + + GrBackendTexture getBackendTexture() const override; + + GrBackendFormat backendFormat() const override { return this->getBackendFormat(); } + + void textureParamsModified() override {} + + const GrVkImageView* textureView(); + + void addIdleProc(sk_sp, IdleState) override; + void callIdleProcsOnBehalfOfResource(); + +protected: + GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp, + const GrVkImageView*, GrMipMapsStatus, GrBackendObjectOwnership); + + GrVkGpu* getVkGpu() const; + + void onAbandon() override; + void onRelease() override; + + bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override { + return false; + } + + void willRemoveLastRef() override; + +private: + GrVkTexture(GrVkGpu*, SkBudgeted, const GrSurfaceDesc&, const GrVkImageInfo&, + sk_sp layout, const GrVkImageView* imageView, + GrMipMapsStatus); + GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp, + const GrVkImageView*, GrMipMapsStatus, GrBackendObjectOwnership, GrWrapCacheable, + GrIOType, bool isExternal); + + // In Vulkan we call the release proc after we are finished with the underlying + // GrVkImage::Resource object (which occurs after the GPU has finished all work on it). + void onSetRelease(sk_sp releaseHelper) override { + // Forward the release proc on to GrVkImage + this->setResourceRelease(std::move(releaseHelper)); + } + + void removeFinishIdleProcs(); + + const GrVkImageView* fTextureView; + + typedef GrTexture INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.cpp b/gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.cpp new file mode 100644 index 0000000000..d89822da3b --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.cpp @@ -0,0 +1,251 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkTextureRenderTarget.h" + +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkImageView.h" +#include "src/gpu/vk/GrVkUtil.h" + +#include "src/core/SkMipMap.h" + +#include "include/gpu/vk/GrVkTypes.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +GrVkTextureRenderTarget::GrVkTextureRenderTarget(GrVkGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, layout, GrBackendObjectOwnership::kOwned) + , GrVkTexture(gpu, desc, info, layout, texView, mipMapsStatus, + GrBackendObjectOwnership::kOwned) + , GrVkRenderTarget(gpu, desc, sampleCnt, info, layout, msaaInfo, std::move(msaaLayout), + colorAttachmentView, resolveAttachmentView, + GrBackendObjectOwnership::kOwned) { + SkASSERT(info.fProtected == msaaInfo.fProtected); + this->registerWithCache(budgeted); +} + +GrVkTextureRenderTarget::GrVkTextureRenderTarget(GrVkGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageView* colorAttachmentView, + GrMipMapsStatus mipMapsStatus) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, layout, GrBackendObjectOwnership::kOwned) + , GrVkTexture(gpu, desc, info, layout, texView, mipMapsStatus, + GrBackendObjectOwnership::kOwned) + , GrVkRenderTarget(gpu, desc, info, layout, colorAttachmentView, + GrBackendObjectOwnership::kOwned) { + this->registerWithCache(budgeted); +} + +GrVkTextureRenderTarget::GrVkTextureRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView, + GrMipMapsStatus mipMapsStatus, + GrBackendObjectOwnership ownership, + GrWrapCacheable cacheable) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, layout, ownership) + , GrVkTexture(gpu, desc, info, layout, texView, mipMapsStatus, ownership) + , GrVkRenderTarget(gpu, desc, sampleCnt, info, layout, msaaInfo, std::move(msaaLayout), + colorAttachmentView, resolveAttachmentView, ownership) { + SkASSERT(info.fProtected == msaaInfo.fProtected); + this->registerWithCacheWrapped(cacheable); +} + +GrVkTextureRenderTarget::GrVkTextureRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageView* colorAttachmentView, + GrMipMapsStatus mipMapsStatus, + GrBackendObjectOwnership ownership, + GrWrapCacheable cacheable) + : GrSurface(gpu, {desc.fWidth, desc.fHeight}, desc.fConfig, info.fProtected) + , GrVkImage(info, layout, ownership) + , GrVkTexture(gpu, desc, info, layout, texView, mipMapsStatus, ownership) + , GrVkRenderTarget(gpu, desc, info, layout, colorAttachmentView, ownership) { + this->registerWithCacheWrapped(cacheable); +} + +namespace { +struct Views { + const GrVkImageView* imageView = nullptr; + const GrVkImageView* colorAttachmentView = nullptr; + const GrVkImageView* resolveAttachmentView = nullptr; + GrVkImageInfo msInfo; + sk_sp msLayout; +}; +} // anonymous namespace + +static Views create_views(GrVkGpu* gpu, const GrSurfaceDesc& desc, int sampleCnt, + const GrVkImageInfo& info) { + VkImage image = info.fImage; + // Create the texture ImageView + Views views; + views.imageView = GrVkImageView::Create(gpu, image, info.fFormat, GrVkImageView::kColor_Type, + info.fLevelCount, info.fYcbcrConversionInfo); + if (!views.imageView) { + return {}; + } + + VkFormat pixelFormat = info.fFormat; + + VkImage colorImage; + + // create msaa surface if necessary + if (sampleCnt > 1) { + GrVkImage::ImageDesc msImageDesc; + msImageDesc.fImageType = VK_IMAGE_TYPE_2D; + msImageDesc.fFormat = pixelFormat; + msImageDesc.fWidth = desc.fWidth; + msImageDesc.fHeight = desc.fHeight; + msImageDesc.fLevels = 1; + msImageDesc.fSamples = sampleCnt; + msImageDesc.fImageTiling = VK_IMAGE_TILING_OPTIMAL; + msImageDesc.fUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + msImageDesc.fMemProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + if (!GrVkImage::InitImageInfo(gpu, msImageDesc, &views.msInfo)) { + views.imageView->unref(gpu); + return {}; + } + + // Set color attachment image + colorImage = views.msInfo.fImage; + + // Create resolve attachment view. + views.resolveAttachmentView = + GrVkImageView::Create(gpu, image, pixelFormat, GrVkImageView::kColor_Type, + info.fLevelCount, GrVkYcbcrConversionInfo()); + if (!views.resolveAttachmentView) { + GrVkImage::DestroyImageInfo(gpu, &views.msInfo); + views.imageView->unref(gpu); + return {}; + } + views.msLayout.reset(new GrVkImageLayout(views.msInfo.fImageLayout)); + } else { + // Set color attachment image + colorImage = info.fImage; + } + + views.colorAttachmentView = GrVkImageView::Create( + gpu, colorImage, pixelFormat, GrVkImageView::kColor_Type, 1, GrVkYcbcrConversionInfo()); + if (!views.colorAttachmentView) { + if (sampleCnt > 1) { + views.resolveAttachmentView->unref(gpu); + GrVkImage::DestroyImageInfo(gpu, &views.msInfo); + } + views.imageView->unref(gpu); + return {}; + } + return views; +} + +sk_sp GrVkTextureRenderTarget::MakeNewTextureRenderTarget( + GrVkGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImage::ImageDesc& imageDesc, + GrMipMapsStatus mipMapsStatus) { + SkASSERT(imageDesc.fUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + SkASSERT(imageDesc.fUsageFlags & VK_IMAGE_USAGE_SAMPLED_BIT); + + GrVkImageInfo info; + if (!GrVkImage::InitImageInfo(gpu, imageDesc, &info)) { + return nullptr; + } + sk_sp layout(new GrVkImageLayout(info.fImageLayout)); + + Views views = create_views(gpu, desc, sampleCnt, info); + if (!views.colorAttachmentView) { + GrVkImage::DestroyImageInfo(gpu, &info); + return nullptr; + } + if (sampleCnt > 1) { + return sk_sp(new GrVkTextureRenderTarget( + gpu, budgeted, desc, sampleCnt, info, std::move(layout), views.imageView, + views.msInfo, std::move(views.msLayout), views.colorAttachmentView, + views.resolveAttachmentView, mipMapsStatus)); + } else { + return sk_sp(new GrVkTextureRenderTarget( + gpu, budgeted, desc, info, std::move(layout), views.imageView, + views.colorAttachmentView, mipMapsStatus)); + } +} + +sk_sp GrVkTextureRenderTarget::MakeWrappedTextureRenderTarget( + GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + GrWrapOwnership wrapOwnership, + GrWrapCacheable cacheable, + const GrVkImageInfo& info, + sk_sp layout) { + // Adopted textures require both image and allocation because we're responsible for freeing + SkASSERT(VK_NULL_HANDLE != info.fImage && + (kBorrow_GrWrapOwnership == wrapOwnership || VK_NULL_HANDLE != info.fAlloc.fMemory)); + + GrMipMapsStatus mipMapsStatus = info.fLevelCount > 1 ? GrMipMapsStatus::kDirty + : GrMipMapsStatus::kNotAllocated; + + GrBackendObjectOwnership ownership = kBorrow_GrWrapOwnership == wrapOwnership + ? GrBackendObjectOwnership::kBorrowed : GrBackendObjectOwnership::kOwned; + Views views = create_views(gpu, desc, sampleCnt, info); + if (!views.colorAttachmentView) { + return nullptr; + } + if (sampleCnt > 1) { + return sk_sp(new GrVkTextureRenderTarget( + gpu, desc, sampleCnt, info, std::move(layout), views.imageView, views.msInfo, + std::move(views.msLayout), views.colorAttachmentView, views.resolveAttachmentView, + mipMapsStatus, ownership, cacheable)); + } else { + return sk_sp(new GrVkTextureRenderTarget( + gpu, desc, info, std::move(layout), views.imageView, views.colorAttachmentView, + mipMapsStatus, ownership, cacheable)); + } +} + +size_t GrVkTextureRenderTarget::onGpuMemorySize() const { + int numColorSamples = this->numSamples(); + if (numColorSamples > 1) { + // Add one to account for the resolve VkImage. + ++numColorSamples; + } + const GrCaps& caps = *this->getGpu()->caps(); + return GrSurface::ComputeSize(caps, this->backendFormat(), this->width(), this->height(), + numColorSamples, // TODO: this still correct? + this->texturePriv().mipMapped()); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.h b/gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.h new file mode 100644 index 0000000000..077143195e --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTextureRenderTarget.h @@ -0,0 +1,120 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrVkTextureRenderTarget_DEFINED +#define GrVkTextureRenderTarget_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkRenderTarget.h" +#include "src/gpu/vk/GrVkTexture.h" + +class GrVkGpu; + +#ifdef SK_BUILD_FOR_WIN +// Windows gives bogus warnings about inheriting asTexture/asRenderTarget via dominance. +#pragma warning(push) +#pragma warning(disable: 4250) +#endif + +class GrVkImageView; +struct GrVkImageInfo; + +class GrVkTextureRenderTarget: public GrVkTexture, public GrVkRenderTarget { +public: + static sk_sp MakeNewTextureRenderTarget(GrVkGpu*, SkBudgeted, + const GrSurfaceDesc&, + int sampleCnt, + const GrVkImage::ImageDesc&, + GrMipMapsStatus); + + static sk_sp MakeWrappedTextureRenderTarget(GrVkGpu*, + const GrSurfaceDesc&, + int sampleCnt, + GrWrapOwnership, + GrWrapCacheable, + const GrVkImageInfo&, + sk_sp); + + GrBackendFormat backendFormat() const override { return this->getBackendFormat(); } + +protected: + void onAbandon() override { + // In order to correctly handle calling texture idle procs, GrVkTexture must go first. + GrVkTexture::onAbandon(); + GrVkRenderTarget::onAbandon(); + } + + void onRelease() override { + // In order to correctly handle calling texture idle procs, GrVkTexture must go first. + GrVkTexture::onRelease(); + GrVkRenderTarget::onRelease(); + } + +private: + // MSAA, not-wrapped + GrVkTextureRenderTarget(GrVkGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView, + GrMipMapsStatus); + + // non-MSAA, not-wrapped + GrVkTextureRenderTarget(GrVkGpu* gpu, + SkBudgeted budgeted, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageView* colorAttachmentView, + GrMipMapsStatus); + + // MSAA, wrapped + GrVkTextureRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + int sampleCnt, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageInfo& msaaInfo, + sk_sp msaaLayout, + const GrVkImageView* colorAttachmentView, + const GrVkImageView* resolveAttachmentView, + GrMipMapsStatus, + GrBackendObjectOwnership, + GrWrapCacheable); + + // non-MSAA, wrapped + GrVkTextureRenderTarget(GrVkGpu* gpu, + const GrSurfaceDesc& desc, + const GrVkImageInfo& info, + sk_sp layout, + const GrVkImageView* texView, + const GrVkImageView* colorAttachmentView, + GrMipMapsStatus, + GrBackendObjectOwnership, + GrWrapCacheable); + + // GrGLRenderTarget accounts for the texture's memory and any MSAA renderbuffer's memory. + size_t onGpuMemorySize() const override; + + // In Vulkan we call the release proc after we are finished with the underlying + // GrVkImage::Resource object (which occurs after the GPU has finished all work on it). + void onSetRelease(sk_sp releaseHelper) override { + // Forward the release proc on to GrVkImage + this->setResourceRelease(std::move(releaseHelper)); + } +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.cpp new file mode 100644 index 0000000000..38b00e11a7 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.cpp @@ -0,0 +1,61 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/core/SkTraceMemoryDump.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkTransferBuffer.h" + +sk_sp GrVkTransferBuffer::Make(GrVkGpu* gpu, size_t size, + GrVkBuffer::Type type) { + GrVkBuffer::Desc desc; + desc.fDynamic = true; + SkASSERT(GrVkBuffer::kCopyRead_Type == type || GrVkBuffer::kCopyWrite_Type == type); + desc.fType = type; + desc.fSizeInBytes = size; + + const GrVkBuffer::Resource* bufferResource = GrVkBuffer::Create(gpu, desc); + if (!bufferResource) { + return nullptr; + } + + GrVkTransferBuffer* buffer = new GrVkTransferBuffer(gpu, desc, bufferResource); + if (!buffer) { + bufferResource->unref(gpu); + } + return sk_sp(buffer); +} + +GrVkTransferBuffer::GrVkTransferBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkBuffer::Resource* bufferResource) + : INHERITED(gpu, desc.fSizeInBytes, + kCopyRead_Type == desc.fType ? GrGpuBufferType::kXferCpuToGpu + : GrGpuBufferType::kXferGpuToCpu, + kStream_GrAccessPattern) + , GrVkBuffer(desc, bufferResource) { + this->registerWithCache(SkBudgeted::kYes); +} + +void GrVkTransferBuffer::onRelease() { + if (!this->wasDestroyed()) { + this->vkRelease(this->getVkGpu()); + } + + INHERITED::onRelease(); +} + +void GrVkTransferBuffer::onAbandon() { + this->vkAbandon(); + INHERITED::onAbandon(); +} + +void GrVkTransferBuffer::setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, + const SkString& dumpName) const { + SkString buffer_id; + buffer_id.appendU64((uint64_t)this->buffer()); + traceMemoryDump->setMemoryBacking(dumpName.c_str(), "vk_buffer", + buffer_id.c_str()); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.h new file mode 100644 index 0000000000..210cf228d9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTransferBuffer.h @@ -0,0 +1,47 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkTransferBuffer_DEFINED +#define GrVkTransferBuffer_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/vk/GrVkBuffer.h" + +class GrVkGpu; + +class GrVkTransferBuffer : public GrGpuBuffer, public GrVkBuffer { +public: + static sk_sp Make(GrVkGpu* gpu, size_t size, GrVkBuffer::Type type); + +protected: + void onAbandon() override; + void onRelease() override; + +private: + GrVkTransferBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkBuffer::Resource* resource); + void setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, + const SkString& dumpName) const override; + + void onMap() override { this->GrGpuBuffer::fMapPtr = this->vkMap(this->getVkGpu()); } + + void onUnmap() override { this->vkUnmap(this->getVkGpu()); } + + bool onUpdateData(const void* src, size_t srcSizeInBytes) override { + SK_ABORT("Not implemented for transfer buffers."); + } + + GrVkGpu* getVkGpu() const { + SkASSERT(!this->wasDestroyed()); + return reinterpret_cast(this->getGpu()); + } + + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkTypesPriv.cpp b/gfx/skia/skia/src/gpu/vk/GrVkTypesPriv.cpp new file mode 100644 index 0000000000..6f5d0b27f2 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkTypesPriv.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/GrVkTypesPriv.h" + +#include "src/gpu/vk/GrVkImageLayout.h" + +void GrVkBackendSurfaceInfo::cleanup() { + SkSafeUnref(fLayout); + fLayout = nullptr; +}; + +void GrVkBackendSurfaceInfo::assign(const GrVkBackendSurfaceInfo& that, bool isThisValid) { + fImageInfo = that.fImageInfo; + GrVkImageLayout* oldLayout = fLayout; + fLayout = SkSafeRef(that.fLayout); + if (isThisValid) { + SkSafeUnref(oldLayout); + } +} + +void GrVkBackendSurfaceInfo::setImageLayout(VkImageLayout layout) { + SkASSERT(fLayout); + fLayout->setImageLayout(layout); +} + +sk_sp GrVkBackendSurfaceInfo::getGrVkImageLayout() const { + SkASSERT(fLayout); + return sk_ref_sp(fLayout); +} + +GrVkImageInfo GrVkBackendSurfaceInfo::snapImageInfo() const { + return GrVkImageInfo(fImageInfo, fLayout->getImageLayout()); +} + +#if GR_TEST_UTILS +bool GrVkBackendSurfaceInfo::operator==(const GrVkBackendSurfaceInfo& that) const { + GrVkImageInfo cpyInfoThis = fImageInfo; + GrVkImageInfo cpyInfoThat = that.fImageInfo; + // We don't care about the fImageLayout here since we require they use the same + // GrVkImageLayout. + cpyInfoThis.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + cpyInfoThat.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + return cpyInfoThis == cpyInfoThat && fLayout == that.fLayout; +} +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.cpp new file mode 100644 index 0000000000..d5a4f9d86b --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.cpp @@ -0,0 +1,103 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkUniformBuffer.h" + +#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X) + +GrVkUniformBuffer* GrVkUniformBuffer::Create(GrVkGpu* gpu, size_t size) { + if (0 == size) { + return nullptr; + } + const GrVkResource* resource = nullptr; + if (size <= GrVkUniformBuffer::kStandardSize) { + resource = gpu->resourceProvider().findOrCreateStandardUniformBufferResource(); + } else { + resource = CreateResource(gpu, size); + } + if (!resource) { + return nullptr; + } + + GrVkBuffer::Desc desc; + desc.fDynamic = true; + desc.fType = GrVkBuffer::kUniform_Type; + desc.fSizeInBytes = size; + GrVkUniformBuffer* buffer = new GrVkUniformBuffer(gpu, desc, + (const GrVkUniformBuffer::Resource*) resource); + if (!buffer) { + // this will destroy anything we got from the resource provider, + // but this avoids a conditional + resource->unref(gpu); + } + return buffer; +} + +// We implement our own creation function for special buffer resource type +const GrVkResource* GrVkUniformBuffer::CreateResource(GrVkGpu* gpu, size_t size) { + if (0 == size) { + return nullptr; + } + + VkBuffer buffer; + GrVkAlloc alloc; + + // create the buffer object + VkBufferCreateInfo bufInfo; + memset(&bufInfo, 0, sizeof(VkBufferCreateInfo)); + bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufInfo.flags = 0; + bufInfo.size = size; + bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufInfo.queueFamilyIndexCount = 0; + bufInfo.pQueueFamilyIndices = nullptr; + + VkResult err; + err = VK_CALL(gpu, CreateBuffer(gpu->device(), &bufInfo, nullptr, &buffer)); + if (err) { + return nullptr; + } + + if (!GrVkMemory::AllocAndBindBufferMemory(gpu, + buffer, + kUniform_Type, + true, // dynamic + &alloc)) { + return nullptr; + } + + const GrVkResource* resource = new GrVkUniformBuffer::Resource(buffer, alloc); + if (!resource) { + VK_CALL(gpu, DestroyBuffer(gpu->device(), buffer, nullptr)); + GrVkMemory::FreeBufferMemory(gpu, kUniform_Type, alloc); + return nullptr; + } + + return resource; +} + +const GrVkBuffer::Resource* GrVkUniformBuffer::createResource(GrVkGpu* gpu, + const GrVkBuffer::Desc& descriptor) { + const GrVkResource* vkResource; + if (descriptor.fSizeInBytes <= GrVkUniformBuffer::kStandardSize) { + GrVkResourceProvider& provider = gpu->resourceProvider(); + vkResource = provider.findOrCreateStandardUniformBufferResource(); + } else { + vkResource = CreateResource(gpu, descriptor.fSizeInBytes); + } + return (const GrVkBuffer::Resource*) vkResource; +} + +void GrVkUniformBuffer::Resource::onRecycle(GrVkGpu* gpu) const { + if (fAlloc.fSize <= GrVkUniformBuffer::kStandardSize) { + gpu->resourceProvider().recycleStandardUniformBufferResource(this); + } else { + this->unref(gpu); + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.h new file mode 100644 index 0000000000..9a3ad3cdae --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkUniformBuffer.h @@ -0,0 +1,59 @@ +/* +* Copyright 2015 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkUniformBuffer_DEFINED +#define GrVkUniformBuffer_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/vk/GrVkBuffer.h" + +class GrVkGpu; + +class GrVkUniformBuffer : public GrVkBuffer { + +public: + static GrVkUniformBuffer* Create(GrVkGpu* gpu, size_t size); + static const GrVkResource* CreateResource(GrVkGpu* gpu, size_t size); + static const size_t kStandardSize = 256; + + void* map(GrVkGpu* gpu) { + return this->vkMap(gpu); + } + void unmap(GrVkGpu* gpu) { + this->vkUnmap(gpu); + } + // The output variable createdNewBuffer must be set to true if a new VkBuffer is created in + // order to upload the data + bool updateData(GrVkGpu* gpu, const void* src, size_t srcSizeInBytes, + bool* createdNewBuffer) { + return this->vkUpdateData(gpu, src, srcSizeInBytes, createdNewBuffer); + } + void release(const GrVkGpu* gpu) { this->vkRelease(gpu); } + void abandon() { this->vkAbandon(); } + +private: + class Resource : public GrVkBuffer::Resource { + public: + Resource(VkBuffer buf, const GrVkAlloc& alloc) + : INHERITED(buf, alloc, kUniform_Type) {} + + void onRecycle(GrVkGpu* gpu) const override; + + typedef GrVkBuffer::Resource INHERITED; + }; + + const GrVkBuffer::Resource* createResource(GrVkGpu* gpu, + const GrVkBuffer::Desc& descriptor) override; + + GrVkUniformBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkUniformBuffer::Resource* resource) + : INHERITED(desc, resource) {} + + typedef GrVkBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.cpp b/gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.cpp new file mode 100644 index 0000000000..6df62ce6a9 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.cpp @@ -0,0 +1,341 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "src/gpu/vk/GrVkUniformHandler.h" + +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/glsl/GrGLSLProgramBuilder.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkPipelineStateBuilder.h" +#include "src/gpu/vk/GrVkTexture.h" + +// To determine whether a current offset is aligned, we can just 'and' the lowest bits with the +// alignment mask. A value of 0 means aligned, any other value is how many bytes past alignment we +// are. This works since all alignments are powers of 2. The mask is always (alignment - 1). +// This alignment mask will give correct alignments for using the std430 block layout. If you want +// the std140 alignment, you can use this, but then make sure if you have an array type it is +// aligned to 16 bytes (i.e. has mask of 0xF). +// These are designated in the Vulkan spec, section 14.5.4 "Offset and Stride Assignment". +// https://www.khronos.org/registry/vulkan/specs/1.0-wsi_extensions/html/vkspec.html#interfaces-resources-layout +static uint32_t grsltype_to_alignment_mask(GrSLType type) { + switch(type) { + case kByte_GrSLType: // fall through + case kUByte_GrSLType: + return 0x0; + case kByte2_GrSLType: // fall through + case kUByte2_GrSLType: + return 0x1; + case kByte3_GrSLType: // fall through + case kByte4_GrSLType: + case kUByte3_GrSLType: + case kUByte4_GrSLType: + return 0x3; + case kShort_GrSLType: // fall through + case kUShort_GrSLType: + return 0x1; + case kShort2_GrSLType: // fall through + case kUShort2_GrSLType: + return 0x3; + case kShort3_GrSLType: // fall through + case kShort4_GrSLType: + case kUShort3_GrSLType: + case kUShort4_GrSLType: + return 0x7; + case kInt_GrSLType: + case kUint_GrSLType: + return 0x3; + case kHalf_GrSLType: // fall through + case kFloat_GrSLType: + return 0x3; + case kHalf2_GrSLType: // fall through + case kFloat2_GrSLType: + return 0x7; + case kHalf3_GrSLType: // fall through + case kFloat3_GrSLType: + return 0xF; + case kHalf4_GrSLType: // fall through + case kFloat4_GrSLType: + return 0xF; + case kUint2_GrSLType: + return 0x7; + case kInt2_GrSLType: + return 0x7; + case kInt3_GrSLType: + return 0xF; + case kInt4_GrSLType: + return 0xF; + case kHalf2x2_GrSLType: // fall through + case kFloat2x2_GrSLType: + return 0x7; + case kHalf3x3_GrSLType: // fall through + case kFloat3x3_GrSLType: + return 0xF; + case kHalf4x4_GrSLType: // fall through + case kFloat4x4_GrSLType: + return 0xF; + + // This query is only valid for certain types. + case kVoid_GrSLType: + case kBool_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kSampler_GrSLType: + case kTexture2D_GrSLType: + break; + } + SK_ABORT("Unexpected type"); +} + +/** Returns the size in bytes taken up in vulkanbuffers for GrSLTypes. */ +static inline uint32_t grsltype_to_vk_size(GrSLType type) { + switch(type) { + case kByte_GrSLType: + return sizeof(int8_t); + case kByte2_GrSLType: + return 2 * sizeof(int8_t); + case kByte3_GrSLType: + return 3 * sizeof(int8_t); + case kByte4_GrSLType: + return 4 * sizeof(int8_t); + case kUByte_GrSLType: + return sizeof(uint8_t); + case kUByte2_GrSLType: + return 2 * sizeof(uint8_t); + case kUByte3_GrSLType: + return 3 * sizeof(uint8_t); + case kUByte4_GrSLType: + return 4 * sizeof(uint8_t); + case kShort_GrSLType: + return sizeof(int16_t); + case kShort2_GrSLType: + return 2 * sizeof(int16_t); + case kShort3_GrSLType: + return 3 * sizeof(int16_t); + case kShort4_GrSLType: + return 4 * sizeof(int16_t); + case kUShort_GrSLType: + return sizeof(uint16_t); + case kUShort2_GrSLType: + return 2 * sizeof(uint16_t); + case kUShort3_GrSLType: + return 3 * sizeof(uint16_t); + case kUShort4_GrSLType: + return 4 * sizeof(uint16_t); + case kInt_GrSLType: + return sizeof(int32_t); + case kUint_GrSLType: + return sizeof(int32_t); + case kHalf_GrSLType: // fall through + case kFloat_GrSLType: + return sizeof(float); + case kHalf2_GrSLType: // fall through + case kFloat2_GrSLType: + return 2 * sizeof(float); + case kHalf3_GrSLType: // fall through + case kFloat3_GrSLType: + return 3 * sizeof(float); + case kHalf4_GrSLType: // fall through + case kFloat4_GrSLType: + return 4 * sizeof(float); + case kUint2_GrSLType: + return 2 * sizeof(uint32_t); + case kInt2_GrSLType: + return 2 * sizeof(int32_t); + case kInt3_GrSLType: + return 3 * sizeof(int32_t); + case kInt4_GrSLType: + return 4 * sizeof(int32_t); + case kHalf2x2_GrSLType: // fall through + case kFloat2x2_GrSLType: + //TODO: this will be 4 * szof(float) on std430. + return 8 * sizeof(float); + case kHalf3x3_GrSLType: // fall through + case kFloat3x3_GrSLType: + return 12 * sizeof(float); + case kHalf4x4_GrSLType: // fall through + case kFloat4x4_GrSLType: + return 16 * sizeof(float); + + // This query is only valid for certain types. + case kVoid_GrSLType: + case kBool_GrSLType: + case kTexture2DSampler_GrSLType: + case kTextureExternalSampler_GrSLType: + case kTexture2DRectSampler_GrSLType: + case kSampler_GrSLType: + case kTexture2D_GrSLType: + break; + } + SK_ABORT("Unexpected type"); +} + + +// Given the current offset into the ubo, calculate the offset for the uniform we're trying to add +// taking into consideration all alignment requirements. The uniformOffset is set to the offset for +// the new uniform, and currentOffset is updated to be the offset to the end of the new uniform. +static void get_ubo_aligned_offset(uint32_t* uniformOffset, + uint32_t* currentOffset, + GrSLType type, + int arrayCount) { + uint32_t alignmentMask = grsltype_to_alignment_mask(type); + // We want to use the std140 layout here, so we must make arrays align to 16 bytes. + if (arrayCount || type == kFloat2x2_GrSLType) { + alignmentMask = 0xF; + } + uint32_t offsetDiff = *currentOffset & alignmentMask; + if (offsetDiff != 0) { + offsetDiff = alignmentMask - offsetDiff + 1; + } + *uniformOffset = *currentOffset + offsetDiff; + SkASSERT(sizeof(float) == 4); + if (arrayCount) { + uint32_t elementSize = SkTMax(16, grsltype_to_vk_size(type)); + SkASSERT(0 == (elementSize & 0xF)); + *currentOffset = *uniformOffset + elementSize * arrayCount; + } else { + *currentOffset = *uniformOffset + grsltype_to_vk_size(type); + } +} + +GrVkUniformHandler::~GrVkUniformHandler() { + GrVkGpu* gpu = static_cast(fProgramBuilder)->gpu(); + for (decltype(fSamplers)::Iter iter(&fSamplers); iter.next();) { + if (iter->fImmutableSampler) { + iter->fImmutableSampler->unref(gpu); + iter->fImmutableSampler = nullptr; + } + } +} + +GrGLSLUniformHandler::UniformHandle GrVkUniformHandler::internalAddUniformArray( + uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) { + SkASSERT(name && strlen(name)); + SkASSERT(GrSLTypeIsFloatType(type)); + + UniformInfo& uni = fUniforms.push_back(); + uni.fVariable.setType(type); + // TODO this is a bit hacky, lets think of a better way. Basically we need to be able to use + // the uniform view matrix name in the GP, and the GP is immutable so it has to tell the PB + // exactly what name it wants to use for the uniform view matrix. If we prefix anythings, then + // the names will mismatch. I think the correct solution is to have all GPs which need the + // uniform view matrix, they should upload the view matrix in their setData along with regular + // uniforms. + char prefix = 'u'; + if ('u' == name[0] || !strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX))) { + prefix = '\0'; + } + fProgramBuilder->nameVariable(uni.fVariable.accessName(), prefix, name, mangleName); + uni.fVariable.setArrayCount(arrayCount); + uni.fVisibility = visibility; + // When outputing the GLSL, only the outer uniform block will get the Uniform modifier. Thus + // we set the modifier to none for all uniforms declared inside the block. + uni.fVariable.setTypeModifier(GrShaderVar::kNone_TypeModifier); + + get_ubo_aligned_offset(&uni.fUBOffset, &fCurrentUBOOffset, type, arrayCount); + + SkString layoutQualifier; + layoutQualifier.appendf("offset=%d", uni.fUBOffset); + uni.fVariable.addLayoutQualifier(layoutQualifier.c_str()); + + if (outName) { + *outName = uni.fVariable.c_str(); + } + + return GrGLSLUniformHandler::UniformHandle(fUniforms.count() - 1); +} + +GrGLSLUniformHandler::SamplerHandle GrVkUniformHandler::addSampler(const GrTextureProxy* texture, + const GrSamplerState& state, + const GrSwizzle& swizzle, + const char* name, + const GrShaderCaps* shaderCaps) { + SkASSERT(name && strlen(name)); + SkString mangleName; + char prefix = 'u'; + fProgramBuilder->nameVariable(&mangleName, prefix, name, true); + + GrTextureType type = texture->textureType(); + + UniformInfo& info = fSamplers.push_back(); + info.fVariable.setType(GrSLCombinedSamplerTypeForTextureType(type)); + info.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier); + info.fVariable.setName(mangleName); + SkString layoutQualifier; + layoutQualifier.appendf("set=%d, binding=%d", kSamplerDescSet, fSamplers.count() - 1); + info.fVariable.addLayoutQualifier(layoutQualifier.c_str()); + info.fVisibility = kFragment_GrShaderFlag; + info.fUBOffset = 0; + + // Check if we are dealing with an external texture and store the needed information if so. + auto ycbcrInfo = texture->backendFormat().getVkYcbcrConversionInfo(); + if (ycbcrInfo && ycbcrInfo->isValid()) { + GrVkGpu* gpu = static_cast(fProgramBuilder)->gpu(); + info.fImmutableSampler = gpu->resourceProvider().findOrCreateCompatibleSampler( + state, *ycbcrInfo); + SkASSERT(info.fImmutableSampler); + } + + SkASSERT(shaderCaps->textureSwizzleAppliedInShader()); + fSamplerSwizzles.push_back(swizzle); + SkASSERT(fSamplerSwizzles.count() == fSamplers.count()); + return GrGLSLUniformHandler::SamplerHandle(fSamplers.count() - 1); +} + +void GrVkUniformHandler::appendUniformDecls(GrShaderFlags visibility, SkString* out) const { + for (int i = 0; i < fSamplers.count(); ++i) { + const UniformInfo& sampler = fSamplers[i]; + SkASSERT(sampler.fVariable.getType() == kTexture2DSampler_GrSLType || + sampler.fVariable.getType() == kTextureExternalSampler_GrSLType); + if (visibility == sampler.fVisibility) { + sampler.fVariable.appendDecl(fProgramBuilder->shaderCaps(), out); + out->append(";\n"); + } + } + +#ifdef SK_DEBUG + bool firstOffsetCheck = false; + for (int i = 0; i < fUniforms.count(); ++i) { + const UniformInfo& localUniform = fUniforms[i]; + if (!firstOffsetCheck) { + // Check to make sure we are starting our offset at 0 so the offset qualifier we + // set on each variable in the uniform block is valid. + SkASSERT(0 == localUniform.fUBOffset); + firstOffsetCheck = true; + } + } +#endif + + SkString uniformsString; + for (int i = 0; i < fUniforms.count(); ++i) { + const UniformInfo& localUniform = fUniforms[i]; + if (visibility & localUniform.fVisibility) { + if (GrSLTypeIsFloatType(localUniform.fVariable.getType())) { + localUniform.fVariable.appendDecl(fProgramBuilder->shaderCaps(), &uniformsString); + uniformsString.append(";\n"); + } + } + } + + if (!uniformsString.isEmpty()) { + out->appendf("layout (set=%d, binding=%d) uniform uniformBuffer\n{\n", + kUniformBufferDescSet, kUniformBinding); + out->appendf("%s\n};\n", uniformsString.c_str()); + } +} + +uint32_t GrVkUniformHandler::getRTHeightOffset() const { + uint32_t result; + uint32_t currentOffset = fCurrentUBOOffset; + get_ubo_aligned_offset(&result, ¤tOffset, kFloat_GrSLType, 0); + return result; +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.h b/gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.h new file mode 100644 index 0000000000..76f82f03db --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkUniformHandler.h @@ -0,0 +1,120 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkUniformHandler_DEFINED +#define GrVkUniformHandler_DEFINED + +#include "include/gpu/vk/GrVkTypes.h" +#include "src/gpu/GrAllocator.h" +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/GrShaderVar.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#include "src/gpu/vk/GrVkSampler.h" + +class GrVkUniformHandler : public GrGLSLUniformHandler { +public: + static const int kUniformsPerBlock = 8; + + enum { + /** + * Binding a descriptor set invalidates all higher index descriptor sets. We must bind + * in the order of this enumeration. Samplers are after Uniforms because GrOps can specify + * GP textures as dynamic state, meaning they get rebound for each GrMesh in a draw while + * uniforms are bound once before all the draws. + */ + kUniformBufferDescSet = 0, + kSamplerDescSet = 1, + }; + enum { + kUniformBinding = 0 + }; + + struct UniformInfo { + GrShaderVar fVariable; + uint32_t fVisibility; + // fUBOffset is only valid if the GrSLType of the fVariable is not a sampler + uint32_t fUBOffset; + // fImmutableSampler is used for sampling an image with a ycbcr conversion. + const GrVkSampler* fImmutableSampler = nullptr; + }; + typedef GrTAllocator UniformInfoArray; + + ~GrVkUniformHandler() override; + + const GrShaderVar& getUniformVariable(UniformHandle u) const override { + return fUniforms[u.toIndex()].fVariable; + } + + const char* getUniformCStr(UniformHandle u) const override { + return this->getUniformVariable(u).c_str(); + } + + /** + * Returns the offset that the RTHeight synthetic uniform should use if it needs to be created. + */ + uint32_t getRTHeightOffset() const; + +private: + explicit GrVkUniformHandler(GrGLSLProgramBuilder* program) + : INHERITED(program) + , fUniforms(kUniformsPerBlock) + , fSamplers(kUniformsPerBlock) + , fCurrentUBOOffset(0) { + } + + UniformHandle internalAddUniformArray(uint32_t visibility, + GrSLType type, + const char* name, + bool mangleName, + int arrayCount, + const char** outName) override; + + void updateUniformVisibility(UniformHandle u, uint32_t visibility) override { + fUniforms[u.toIndex()].fVisibility |= visibility; + } + + SamplerHandle addSampler(const GrTextureProxy*, + const GrSamplerState&, + const GrSwizzle&, + const char* name, + const GrShaderCaps*) override; + + int numSamplers() const { return fSamplers.count(); } + const char* samplerVariable(SamplerHandle handle) const override { + return fSamplers[handle.toIndex()].fVariable.c_str(); + } + GrSwizzle samplerSwizzle(SamplerHandle handle) const override { + return fSamplerSwizzles[handle.toIndex()]; + } + uint32_t samplerVisibility(SamplerHandle handle) const { + return fSamplers[handle.toIndex()].fVisibility; + } + + const GrVkSampler* immutableSampler(UniformHandle u) const { + return fSamplers[u.toIndex()].fImmutableSampler; + } + + void appendUniformDecls(GrShaderFlags, SkString*) const override; + + const UniformInfo& getUniformInfo(UniformHandle u) const { + return fUniforms[u.toIndex()]; + } + + + UniformInfoArray fUniforms; + UniformInfoArray fSamplers; + SkTArray fSamplerSwizzles; + + uint32_t fCurrentUBOOffset; + + friend class GrVkPipelineStateBuilder; + friend class GrVkDescriptorSetManager; + + typedef GrGLSLUniformHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkUtil.cpp b/gfx/skia/skia/src/gpu/vk/GrVkUtil.cpp new file mode 100644 index 0000000000..a49dcea8ae --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkUtil.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkUtil.h" + +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/vk/GrVkGpu.h" +#include "src/sksl/SkSLCompiler.h" + +#ifdef SK_DEBUG +bool GrVkFormatColorTypePairIsValid(VkFormat format, GrColorType colorType) { + switch (format) { + case VK_FORMAT_R8G8B8A8_UNORM: return GrColorType::kRGBA_8888 == colorType || + GrColorType::kRGB_888x == colorType; + case VK_FORMAT_B8G8R8A8_UNORM: return GrColorType::kBGRA_8888 == colorType; + case VK_FORMAT_R8G8B8A8_SRGB: return GrColorType::kRGBA_8888_SRGB == colorType; + case VK_FORMAT_R8G8B8_UNORM: return GrColorType::kRGB_888x == colorType; + case VK_FORMAT_R8G8_UNORM: return GrColorType::kRG_88 == colorType; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return GrColorType::kRGBA_1010102 == colorType; + case VK_FORMAT_R5G6B5_UNORM_PACK16: return GrColorType::kBGR_565 == colorType; + // R4G4B4A4 is not required to be supported so we actually + // store RGBA_4444 data as B4G4R4A4. + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return GrColorType::kABGR_4444 == colorType; + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return GrColorType::kABGR_4444 == colorType; + case VK_FORMAT_R8_UNORM: return GrColorType::kAlpha_8 == colorType || + GrColorType::kGray_8 == colorType; + case VK_FORMAT_R16G16B16A16_SFLOAT: return GrColorType::kRGBA_F16 == colorType || + GrColorType::kRGBA_F16_Clamped == colorType; + case VK_FORMAT_R16_SFLOAT: return GrColorType::kAlpha_F16 == colorType; + case VK_FORMAT_R16_UNORM: return GrColorType::kAlpha_16 == colorType; + case VK_FORMAT_R16G16_UNORM: return GrColorType::kRG_1616 == colorType; + case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: return GrColorType::kRGB_888x == colorType; + case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: return GrColorType::kRGB_888x == colorType; + case VK_FORMAT_R16G16B16A16_UNORM: return GrColorType::kRGBA_16161616 == colorType; + case VK_FORMAT_R16G16_SFLOAT: return GrColorType::kRG_F16 == colorType; + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return GrColorType::kRGB_888x == colorType; + default: return false; + } + + SkUNREACHABLE; +} +#endif + +bool GrVkFormatIsSupported(VkFormat format) { + switch (format) { + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SRGB: + case VK_FORMAT_R8G8B8_UNORM: + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + case VK_FORMAT_R16G16B16A16_SFLOAT: + case VK_FORMAT_R16_SFLOAT: + case VK_FORMAT_R16_UNORM: + case VK_FORMAT_R16G16_UNORM: + case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: + case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: + case VK_FORMAT_R16G16B16A16_UNORM: + case VK_FORMAT_R16G16_SFLOAT: + return true; + default: + return false; + } +} + +bool GrVkFormatNeedsYcbcrSampler(VkFormat format) { + return format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM || + format == VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM; +} + +bool GrSampleCountToVkSampleCount(uint32_t samples, VkSampleCountFlagBits* vkSamples) { + SkASSERT(samples >= 1); + switch (samples) { + case 1: + *vkSamples = VK_SAMPLE_COUNT_1_BIT; + return true; + case 2: + *vkSamples = VK_SAMPLE_COUNT_2_BIT; + return true; + case 4: + *vkSamples = VK_SAMPLE_COUNT_4_BIT; + return true; + case 8: + *vkSamples = VK_SAMPLE_COUNT_8_BIT; + return true; + case 16: + *vkSamples = VK_SAMPLE_COUNT_16_BIT; + return true; + case 32: + *vkSamples = VK_SAMPLE_COUNT_32_BIT; + return true; + case 64: + *vkSamples = VK_SAMPLE_COUNT_64_BIT; + return true; + default: + return false; + } +} + +SkSL::Program::Kind vk_shader_stage_to_skiasl_kind(VkShaderStageFlagBits stage) { + if (VK_SHADER_STAGE_VERTEX_BIT == stage) { + return SkSL::Program::kVertex_Kind; + } + if (VK_SHADER_STAGE_GEOMETRY_BIT == stage) { + return SkSL::Program::kGeometry_Kind; + } + SkASSERT(VK_SHADER_STAGE_FRAGMENT_BIT == stage); + return SkSL::Program::kFragment_Kind; +} + +bool GrCompileVkShaderModule(const GrVkGpu* gpu, + const SkSL::String& shaderString, + VkShaderStageFlagBits stage, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo, + const SkSL::Program::Settings& settings, + SkSL::String* outSPIRV, + SkSL::Program::Inputs* outInputs) { + auto errorHandler = gpu->getContext()->priv().getShaderErrorHandler(); + std::unique_ptr program = gpu->shaderCompiler()->convertProgram( + vk_shader_stage_to_skiasl_kind(stage), shaderString, settings); + if (!program) { + errorHandler->compileError(shaderString.c_str(), + gpu->shaderCompiler()->errorText().c_str()); + return false; + } + *outInputs = program->fInputs; + if (!gpu->shaderCompiler()->toSPIRV(*program, outSPIRV)) { + errorHandler->compileError(shaderString.c_str(), + gpu->shaderCompiler()->errorText().c_str()); + return false; + } + + return GrInstallVkShaderModule(gpu, *outSPIRV, stage, shaderModule, stageInfo); +} + +bool GrInstallVkShaderModule(const GrVkGpu* gpu, + const SkSL::String& spirv, + VkShaderStageFlagBits stage, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo) { + VkShaderModuleCreateInfo moduleCreateInfo; + memset(&moduleCreateInfo, 0, sizeof(VkShaderModuleCreateInfo)); + moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + moduleCreateInfo.pNext = nullptr; + moduleCreateInfo.flags = 0; + moduleCreateInfo.codeSize = spirv.size(); + moduleCreateInfo.pCode = (const uint32_t*)spirv.c_str(); + + VkResult err = GR_VK_CALL(gpu->vkInterface(), CreateShaderModule(gpu->device(), + &moduleCreateInfo, + nullptr, + shaderModule)); + if (err) { + return false; + } + + memset(stageInfo, 0, sizeof(VkPipelineShaderStageCreateInfo)); + stageInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageInfo->pNext = nullptr; + stageInfo->flags = 0; + stageInfo->stage = stage; + stageInfo->module = *shaderModule; + stageInfo->pName = "main"; + stageInfo->pSpecializationInfo = nullptr; + + return true; +} + +bool GrVkFormatIsCompressed(VkFormat vkFormat) { + switch (vkFormat) { + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + return true; + default: + return false; + } +} + +bool GrVkFormatToCompressionType(VkFormat vkFormat, SkImage::CompressionType* compressionType) { + switch (vkFormat) { + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + *compressionType = SkImage::kETC1_CompressionType; + return true; + default: + return false; + } +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkUtil.h b/gfx/skia/skia/src/gpu/vk/GrVkUtil.h new file mode 100644 index 0000000000..b34dfbca0c --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkUtil.h @@ -0,0 +1,97 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkUtil_DEFINED +#define GrVkUtil_DEFINED + +#include "include/gpu/GrTypes.h" +#include "include/gpu/vk/GrVkTypes.h" +#include "include/private/SkMacros.h" +#include "src/gpu/GrColor.h" +#include "src/gpu/GrDataUtils.h" +#include "src/gpu/vk/GrVkInterface.h" +#include "src/sksl/ir/SkSLProgram.h" + +class GrVkGpu; + +// makes a Vk call on the interface +#define GR_VK_CALL(IFACE, X) (IFACE)->fFunctions.f##X +// same as GR_VK_CALL but checks for success +#ifdef SK_DEBUG +#define GR_VK_CALL_ERRCHECK(IFACE, X) \ + VkResult SK_MACRO_APPEND_LINE(ret) = GR_VK_CALL(IFACE, X); \ + SkASSERT(VK_SUCCESS == SK_MACRO_APPEND_LINE(ret)) +#else +#define GR_VK_CALL_ERRCHECK(IFACE, X) (void) GR_VK_CALL(IFACE, X) +#endif + +bool GrVkFormatIsSupported(VkFormat); + +bool GrVkFormatNeedsYcbcrSampler(VkFormat format); + +#ifdef SK_DEBUG +/** + * Returns true if the passed in VkFormat and GrColorType are compatible with each other. + */ +bool GrVkFormatColorTypePairIsValid(VkFormat, GrColorType); +#endif + +bool GrSampleCountToVkSampleCount(uint32_t samples, VkSampleCountFlagBits* vkSamples); + +bool GrCompileVkShaderModule(const GrVkGpu* gpu, + const SkSL::String& shaderString, + VkShaderStageFlagBits stage, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo, + const SkSL::Program::Settings& settings, + SkSL::String* outSPIRV, + SkSL::Program::Inputs* outInputs); + +bool GrInstallVkShaderModule(const GrVkGpu* gpu, + const SkSL::String& spirv, + VkShaderStageFlagBits stage, + VkShaderModule* shaderModule, + VkPipelineShaderStageCreateInfo* stageInfo); + +/** + * Returns true if the format is compressed. + */ +bool GrVkFormatIsCompressed(VkFormat); + +/** + * Maps a vk format into the CompressionType enum if applicable. + */ +bool GrVkFormatToCompressionType(VkFormat vkFormat, SkImage::CompressionType* compressionType); + +#if GR_TEST_UTILS +static constexpr const char* GrVkFormatToStr(VkFormat vkFormat) { + switch (vkFormat) { + case VK_FORMAT_R8G8B8A8_UNORM: return "R8G8B8A8_UNORM"; + case VK_FORMAT_R8_UNORM: return "R8_UNORM"; + case VK_FORMAT_B8G8R8A8_UNORM: return "B8G8R8A8_UNORM"; + case VK_FORMAT_R5G6B5_UNORM_PACK16: return "R5G6B5_UNORM_PACK16"; + case VK_FORMAT_R16G16B16A16_SFLOAT: return "R16G16B16A16_SFLOAT"; + case VK_FORMAT_R16_SFLOAT: return "R16_SFLOAT"; + case VK_FORMAT_R8G8B8_UNORM: return "R8G8B8_UNORM"; + case VK_FORMAT_R8G8_UNORM: return "R8G8_UNORM"; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return "A2B10G10R10_UNORM_PACK32"; + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return "B4G4R4A4_UNORM_PACK16"; + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return "R4G4B4A4_UNORM_PACK16"; + case VK_FORMAT_R32G32B32A32_SFLOAT: return "R32G32B32A32_SFLOAT"; + case VK_FORMAT_R8G8B8A8_SRGB: return "R8G8B8A8_SRGB"; + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return "ETC2_R8G8B8_UNORM_BLOCK"; + case VK_FORMAT_R16_UNORM: return "R16_UNORM"; + case VK_FORMAT_R16G16_UNORM: return "R16G16_UNORM"; + case VK_FORMAT_R16G16B16A16_UNORM: return "R16G16B16A16_UNORM"; + case VK_FORMAT_R16G16_SFLOAT: return "R16G16_SFLOAT"; + + default: return "Unknown"; + } +} + +#endif +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.cpp b/gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.cpp new file mode 100644 index 0000000000..5fe2f45373 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkVaryingHandler.h" + +/** Returns the number of locations take up by a given GrSLType. We assume that all + scalar values are 32 bits. */ +static inline int grsltype_to_location_size(GrSLType type) { + switch(type) { + case kVoid_GrSLType: + return 0; + case kFloat_GrSLType: // fall through + case kHalf_GrSLType: + return 1; + case kFloat2_GrSLType: // fall through + case kHalf2_GrSLType: + return 1; + case kFloat3_GrSLType: + case kHalf3_GrSLType: + return 1; + case kFloat4_GrSLType: + case kHalf4_GrSLType: + return 1; + case kUint2_GrSLType: + return 1; + case kInt2_GrSLType: + case kShort2_GrSLType: + case kUShort2_GrSLType: + case kByte2_GrSLType: + case kUByte2_GrSLType: + return 1; + case kInt3_GrSLType: + case kShort3_GrSLType: + case kUShort3_GrSLType: + case kByte3_GrSLType: + case kUByte3_GrSLType: + return 1; + case kInt4_GrSLType: + case kShort4_GrSLType: + case kUShort4_GrSLType: + case kByte4_GrSLType: + case kUByte4_GrSLType: + return 1; + case kFloat2x2_GrSLType: + case kHalf2x2_GrSLType: + return 2; + case kFloat3x3_GrSLType: + case kHalf3x3_GrSLType: + return 3; + case kFloat4x4_GrSLType: + case kHalf4x4_GrSLType: + return 4; + case kTexture2DSampler_GrSLType: + case kSampler_GrSLType: + case kTexture2D_GrSLType: + return 0; + case kTextureExternalSampler_GrSLType: + return 0; + case kTexture2DRectSampler_GrSLType: + return 0; + case kBool_GrSLType: + return 1; + case kInt_GrSLType: // fall through + case kShort_GrSLType: + case kByte_GrSLType: + return 1; + case kUint_GrSLType: // fall through + case kUShort_GrSLType: + case kUByte_GrSLType: + return 1; + } + SK_ABORT("Unexpected type"); +} + +static void finalize_helper(GrVkVaryingHandler::VarArray& vars) { + int locationIndex = 0; + for (int i = 0; i < vars.count(); ++i) { + GrShaderVar& var = vars[i]; + SkString location; + location.appendf("location = %d", locationIndex); + var.addLayoutQualifier(location.c_str()); + + int elementSize = grsltype_to_location_size(var.getType()); + SkASSERT(elementSize > 0); + int numElements = 1; + if (var.isArray() && !var.isUnsizedArray()) { + numElements = var.getArrayCount(); + } + SkASSERT(numElements > 0); + locationIndex += elementSize * numElements; + } + // Vulkan requires at least 64 locations to be supported for both vertex output and fragment + // input. If we ever hit this assert, then we'll need to add a cap to actually check the + // supported input and output values and adjust our supported shaders based on those values. + SkASSERT(locationIndex <= 64); +} + +void GrVkVaryingHandler::onFinalize() { + finalize_helper(fVertexInputs); + finalize_helper(fVertexOutputs); + finalize_helper(fGeomInputs); + finalize_helper(fGeomOutputs); + finalize_helper(fFragInputs); + finalize_helper(fFragOutputs); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.h b/gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.h new file mode 100644 index 0000000000..523ab6a021 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkVaryingHandler.h @@ -0,0 +1,27 @@ +/* +* Copyright 2016 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef GrVkVaryingHandler_DEFINED +#define GrVkVaryingHandler_DEFINED + +#include "src/gpu/glsl/GrGLSLVarying.h" + +class GrVkVaryingHandler : public GrGLSLVaryingHandler { +public: + GrVkVaryingHandler(GrGLSLProgramBuilder* program) : INHERITED(program) {} + + typedef GrGLSLVaryingHandler::VarArray VarArray; + +private: + void onFinalize() override; + + friend class GrVkPipelineStateBuilder; + + typedef GrGLSLVaryingHandler INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.cpp b/gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.cpp new file mode 100644 index 0000000000..50cfc347f4 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/gpu/vk/GrVkGpu.h" +#include "src/gpu/vk/GrVkVertexBuffer.h" + +GrVkVertexBuffer::GrVkVertexBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkBuffer::Resource* bufferResource) + : INHERITED(gpu, desc.fSizeInBytes, GrGpuBufferType::kVertex, + desc.fDynamic ? kDynamic_GrAccessPattern : kStatic_GrAccessPattern) + , GrVkBuffer(desc, bufferResource) { + this->registerWithCache(SkBudgeted::kYes); +} + +sk_sp GrVkVertexBuffer::Make(GrVkGpu* gpu, size_t size, bool dynamic) { + GrVkBuffer::Desc desc; + desc.fDynamic = gpu->protectedContext() ? true : dynamic; + desc.fType = GrVkBuffer::kVertex_Type; + desc.fSizeInBytes = size; + + const GrVkBuffer::Resource* bufferResource = GrVkBuffer::Create(gpu, desc); + if (!bufferResource) { + return nullptr; + } + + GrVkVertexBuffer* buffer = new GrVkVertexBuffer(gpu, desc, bufferResource); + if (!buffer) { + bufferResource->unref(gpu); + } + return sk_sp(buffer); +} + +void GrVkVertexBuffer::onRelease() { + if (!this->wasDestroyed()) { + this->vkRelease(this->getVkGpu()); + } + + INHERITED::onRelease(); +} + +void GrVkVertexBuffer::onAbandon() { + this->vkAbandon(); + INHERITED::onAbandon(); +} + +void GrVkVertexBuffer::onMap() { + if (!this->wasDestroyed()) { + this->GrGpuBuffer::fMapPtr = this->vkMap(this->getVkGpu()); + } +} + +void GrVkVertexBuffer::onUnmap() { + if (!this->wasDestroyed()) { + this->vkUnmap(this->getVkGpu()); + } +} + +bool GrVkVertexBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) { + if (!this->wasDestroyed()) { + return this->vkUpdateData(this->getVkGpu(), src, srcSizeInBytes); + } else { + return false; + } +} + +GrVkGpu* GrVkVertexBuffer::getVkGpu() const { + SkASSERT(!this->wasDestroyed()); + return static_cast(this->getGpu()); +} diff --git a/gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.h b/gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.h new file mode 100644 index 0000000000..8ddc460568 --- /dev/null +++ b/gfx/skia/skia/src/gpu/vk/GrVkVertexBuffer.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrVkVertexBuffer_DEFINED +#define GrVkVertexBuffer_DEFINED + +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/vk/GrVkBuffer.h" + +class GrVkGpu; + +class GrVkVertexBuffer : public GrGpuBuffer, public GrVkBuffer { +public: + static sk_sp Make(GrVkGpu* gpu, size_t size, bool dynamic); + +protected: + void onAbandon() override; + void onRelease() override; + +private: + GrVkVertexBuffer(GrVkGpu* gpu, const GrVkBuffer::Desc& desc, + const GrVkBuffer::Resource* resource); + + void onMap() override; + void onUnmap() override; + bool onUpdateData(const void* src, size_t srcSizeInBytes) override; + + GrVkGpu* getVkGpu() const; + + typedef GrGpuBuffer INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/image/SkImage.cpp b/gfx/skia/skia/src/image/SkImage.cpp new file mode 100644 index 0000000000..30179c7e7f --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage.cpp @@ -0,0 +1,535 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/core/SkImageEncoder.h" +#include "include/core/SkImageFilter.h" +#include "include/core/SkImageGenerator.h" +#include "include/core/SkPicture.h" +#include "include/core/SkString.h" +#include "include/core/SkSurface.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkCachedData.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkNextID.h" +#include "src/core/SkSpecialImage.h" +#include "src/image/SkImage_Base.h" +#include "src/image/SkReadPixelsRec.h" +#include "src/shaders/SkImageShader.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "src/image/SkImage_Gpu.h" +#endif +#include "include/gpu/GrBackendSurface.h" + +SkImage::SkImage(const SkImageInfo& info, uint32_t uniqueID) + : fInfo(info) + , fUniqueID(kNeedNewImageUniqueID == uniqueID ? SkNextID::ImageID() : uniqueID) { + SkASSERT(info.width() > 0); + SkASSERT(info.height() > 0); +} + +bool SkImage::peekPixels(SkPixmap* pm) const { + SkPixmap tmp; + if (!pm) { + pm = &tmp; + } + return as_IB(this)->onPeekPixels(pm); +} + +bool SkImage::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, + int srcY, CachingHint chint) const { + return as_IB(this)->onReadPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY, chint); +} + +bool SkImage::scalePixels(const SkPixmap& dst, SkFilterQuality quality, CachingHint chint) const { + if (this->width() == dst.width() && this->height() == dst.height()) { + return this->readPixels(dst, 0, 0, chint); + } + + // Idea: If/when SkImageGenerator supports a native-scaling API (where the generator itself + // can scale more efficiently) we should take advantage of it here. + // + SkBitmap bm; + if (as_IB(this)->getROPixels(&bm, chint)) { + SkPixmap pmap; + // Note: By calling the pixmap scaler, we never cache the final result, so the chint + // is (currently) only being applied to the getROPixels. If we get a request to + // also attempt to cache the final (scaled) result, we would add that logic here. + // + return bm.peekPixels(&pmap) && pmap.scalePixels(dst, quality); + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkColorType SkImage::colorType() const { return fInfo.colorType(); } + +SkAlphaType SkImage::alphaType() const { return fInfo.alphaType(); } + +SkColorSpace* SkImage::colorSpace() const { return fInfo.colorSpace(); } + +sk_sp SkImage::refColorSpace() const { return fInfo.refColorSpace(); } + +sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix) const { + return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, localMatrix); +} + +sk_sp SkImage::encodeToData(SkEncodedImageFormat type, int quality) const { + SkBitmap bm; + if (as_IB(this)->getROPixels(&bm)) { + return SkEncodeBitmap(bm, type, quality); + } + return nullptr; +} + +sk_sp SkImage::encodeToData() const { + if (auto encoded = this->refEncodedData()) { + return encoded; + } + + return this->encodeToData(SkEncodedImageFormat::kPNG, 100); +} + +sk_sp SkImage::refEncodedData() const { + return sk_sp(as_IB(this)->onRefEncoded()); +} + +sk_sp SkImage::MakeFromEncoded(sk_sp encoded, const SkIRect* subset) { + if (nullptr == encoded || 0 == encoded->size()) { + return nullptr; + } + return SkImage::MakeFromGenerator(SkImageGenerator::MakeFromEncoded(std::move(encoded)), + subset); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage::makeSubset(const SkIRect& subset) const { + if (subset.isEmpty()) { + return nullptr; + } + + const SkIRect bounds = SkIRect::MakeWH(this->width(), this->height()); + if (!bounds.contains(subset)) { + return nullptr; + } + + // optimization : return self if the subset == our bounds + if (bounds == subset) { + return sk_ref_sp(const_cast(this)); + } + + // CONTEXT TODO: propagate the context parameter to the top-level API +#if SK_SUPPORT_GPU + return as_IB(this)->onMakeSubset(as_IB(this)->context(), subset); +#else + return as_IB(this)->onMakeSubset(nullptr, subset); +#endif +} + +#if SK_SUPPORT_GPU + +GrTexture* SkImage::getTexture() const { + return as_IB(this)->onGetTexture(); +} + +bool SkImage::isTextureBacked() const { return as_IB(this)->onIsTextureBacked(); } + +GrBackendTexture SkImage::getBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin) const { + return as_IB(this)->onGetBackendTexture(flushPendingGrContextIO, origin); +} + +bool SkImage::isValid(GrContext* context) const { + if (context && context->abandoned()) { + return false; + } + return as_IB(this)->onIsValid(context); +} + +GrSemaphoresSubmitted SkImage::flush(GrContext* context, const GrFlushInfo& flushInfo) { + return as_IB(this)->onFlush(context, flushInfo); +} + +void SkImage::flush(GrContext* context) { as_IB(this)->onFlush(context, {}); } + +#else + +GrTexture* SkImage::getTexture() const { return nullptr; } + +bool SkImage::isTextureBacked() const { return false; } + +GrBackendTexture SkImage::getBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin) const { + return GrBackendTexture(); // invalid +} + +bool SkImage::isValid(GrContext* context) const { + if (context) { + return false; + } + return as_IB(this)->onIsValid(context); +} + +GrSemaphoresSubmitted SkImage::flush(GrContext*, const GrFlushInfo&) { + return GrSemaphoresSubmitted::kNo; +} + +void SkImage::flush(GrContext*) {} + +#endif + +/////////////////////////////////////////////////////////////////////////////// + +SkImage_Base::SkImage_Base(const SkImageInfo& info, uint32_t uniqueID) + : INHERITED(info, uniqueID), fAddedToRasterCache(false) {} + +SkImage_Base::~SkImage_Base() { + if (fAddedToRasterCache.load()) { + SkNotifyBitmapGenIDIsStale(this->uniqueID()); + } +} + +GrBackendTexture SkImage_Base::onGetBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin) const { + return GrBackendTexture(); // invalid +} + +bool SkImage::readPixels(const SkPixmap& pmap, int srcX, int srcY, CachingHint chint) const { + return this->readPixels(pmap.info(), pmap.writable_addr(), pmap.rowBytes(), srcX, srcY, chint); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage::MakeFromBitmap(const SkBitmap& bm) { + if (!bm.pixelRef()) { + return nullptr; + } + + return SkMakeImageFromRasterBitmap(bm, kIfMutable_SkCopyPixelsMode); +} + +bool SkImage::asLegacyBitmap(SkBitmap* bitmap, LegacyBitmapMode ) const { + return as_IB(this)->onAsLegacyBitmap(bitmap); +} + +sk_sp SkImage_Base::getPlanes(SkYUVASizeInfo*, SkYUVAIndex[4], + SkYUVColorSpace*, const void*[4]) { + return nullptr; +} + +bool SkImage_Base::onAsLegacyBitmap(SkBitmap* bitmap) const { + // As the base-class, all we can do is make a copy (regardless of mode). + // Subclasses that want to be more optimal should override. + SkImageInfo info = fInfo.makeColorType(kN32_SkColorType).makeColorSpace(nullptr); + if (!bitmap->tryAllocPixels(info)) { + return false; + } + if (!this->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { + bitmap->reset(); + return false; + } + + bitmap->setImmutable(); + return true; +} + +sk_sp SkImage::MakeFromPicture(sk_sp picture, const SkISize& dimensions, + const SkMatrix* matrix, const SkPaint* paint, + BitDepth bitDepth, sk_sp colorSpace) { + return MakeFromGenerator(SkImageGenerator::MakeFromPicture(dimensions, std::move(picture), + matrix, paint, bitDepth, + std::move(colorSpace))); +} + +sk_sp SkImage::makeWithFilter(const SkImageFilter* filter, const SkIRect& subset, + const SkIRect& clipBounds, SkIRect* outSubset, + SkIPoint* offset) const { + GrContext* context = as_IB(this)->context(); + + return this->makeWithFilter(context, filter, subset, clipBounds, outSubset, offset); +} + +sk_sp SkImage::makeWithFilter(GrContext* grContext, + const SkImageFilter* filter, const SkIRect& subset, + const SkIRect& clipBounds, SkIRect* outSubset, + SkIPoint* offset) const { + if (!filter || !outSubset || !offset || !this->bounds().contains(subset)) { + return nullptr; + } + sk_sp srcSpecialImage = +#if SK_SUPPORT_GPU + SkSpecialImage::MakeFromImage(grContext, subset, sk_ref_sp(const_cast(this))); +#else + SkSpecialImage::MakeFromImage(nullptr, subset, sk_ref_sp(const_cast(this))); +#endif + if (!srcSpecialImage) { + return nullptr; + } + + sk_sp cache( + SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize)); + + // The filters operate in the local space of the src image, where (0,0) corresponds to the + // subset's top left corner. But the clip bounds and any crop rects on the filters are in the + // original coordinate system, so configure the CTM to correct crop rects and explicitly adjust + // the clip bounds (since it is assumed to already be in image space). + SkImageFilter_Base::Context context(SkMatrix::MakeTrans(-subset.x(), -subset.y()), + clipBounds.makeOffset(-subset.topLeft()), + cache.get(), fInfo.colorType(), fInfo.colorSpace(), + srcSpecialImage.get()); + + sk_sp result = as_IFB(filter)->filterImage(context).imageAndOffset(offset); + if (!result) { + return nullptr; + } + + // The output image and offset are relative to the subset rectangle, so the offset needs to + // be shifted to put it in the correct spot with respect to the original coordinate system + offset->fX += subset.x(); + offset->fY += subset.y(); + + // Final clip against the exact clipBounds (the clip provided in the context gets adjusted + // to account for pixel-moving filters so doesn't always exactly match when finished). The + // clipBounds are translated into the clippedDstRect coordinate space, including the + // result->subset() ensures that the result's image pixel origin does not affect results. + SkIRect dstRect = result->subset(); + SkIRect clippedDstRect = dstRect; + if (!clippedDstRect.intersect(clipBounds.makeOffset(result->subset().topLeft() - *offset))) { + return nullptr; + } + + // Adjust the geometric offset if the top-left corner moved as well + offset->fX += (clippedDstRect.x() - dstRect.x()); + offset->fY += (clippedDstRect.y() - dstRect.y()); + *outSubset = clippedDstRect; + return result->asImage(); +} + +bool SkImage::isLazyGenerated() const { + return as_IB(this)->onIsLazyGenerated(); +} + +bool SkImage::isAlphaOnly() const { return SkColorTypeIsAlphaOnly(fInfo.colorType()); } + +sk_sp SkImage::makeColorSpace(sk_sp target) const { + if (!target) { + return nullptr; + } + + // No need to create a new image if: + // (1) The color spaces are equal. + // (2) The color type is kAlpha8. + SkColorSpace* colorSpace = this->colorSpace(); + if (!colorSpace) { + colorSpace = sk_srgb_singleton(); + } + if (SkColorSpace::Equals(colorSpace, target.get()) || this->isAlphaOnly()) { + return sk_ref_sp(const_cast(this)); + } + + // CONTEXT TODO: propagate the context parameter to the top-level API +#if SK_SUPPORT_GPU + return as_IB(this)->onMakeColorTypeAndColorSpace(as_IB(this)->context(), +#else + return as_IB(this)->onMakeColorTypeAndColorSpace(nullptr, +#endif + this->colorType(), std::move(target)); +} + +sk_sp SkImage::makeColorTypeAndColorSpace(SkColorType targetColorType, + sk_sp targetColorSpace) const { + if (kUnknown_SkColorType == targetColorType || !targetColorSpace) { + return nullptr; + } + + SkColorType colorType = this->colorType(); + SkColorSpace* colorSpace = this->colorSpace(); + if (!colorSpace) { + colorSpace = sk_srgb_singleton(); + } + if (colorType == targetColorType && + (SkColorSpace::Equals(colorSpace, targetColorSpace.get()) || this->isAlphaOnly())) { + return sk_ref_sp(const_cast(this)); + } + + // CONTEXT TODO: propagate the context parameter to the top-level API +#if SK_SUPPORT_GPU + return as_IB(this)->onMakeColorTypeAndColorSpace(as_IB(this)->context(), +#else + return as_IB(this)->onMakeColorTypeAndColorSpace(nullptr, +#endif + targetColorType, std::move(targetColorSpace)); +} + +sk_sp SkImage::reinterpretColorSpace(sk_sp target) const { + if (!target) { + return nullptr; + } + + // No need to create a new image if: + // (1) The color spaces are equal. + // (2) The color type is kAlpha8. + SkColorSpace* colorSpace = this->colorSpace(); + if (!colorSpace) { + colorSpace = sk_srgb_singleton(); + } + if (SkColorSpace::Equals(colorSpace, target.get()) || this->isAlphaOnly()) { + return sk_ref_sp(const_cast(this)); + } + + return as_IB(this)->onReinterpretColorSpace(std::move(target)); +} + +sk_sp SkImage::makeNonTextureImage() const { + if (!this->isTextureBacked()) { + return sk_ref_sp(const_cast(this)); + } + return this->makeRasterImage(); +} + +sk_sp SkImage::makeRasterImage() const { + SkPixmap pm; + if (this->peekPixels(&pm)) { + return sk_ref_sp(const_cast(this)); + } + + const size_t rowBytes = fInfo.minRowBytes(); + size_t size = fInfo.computeByteSize(rowBytes); + if (SkImageInfo::ByteSizeOverflowed(size)) { + return nullptr; + } + + sk_sp data = SkData::MakeUninitialized(size); + pm = {fInfo.makeColorSpace(nullptr), data->writable_data(), fInfo.minRowBytes()}; + if (!this->readPixels(pm, 0, 0)) { + return nullptr; + } + + return SkImage::MakeRasterData(fInfo, std::move(data), rowBytes); +} + +////////////////////////////////////////////////////////////////////////////////////// + +#if !SK_SUPPORT_GPU + +sk_sp SkImage::DecodeToTexture(GrContext*, const void*, size_t, const SkIRect*) { + return nullptr; +} + +sk_sp SkImage::MakeFromTexture(GrContext* ctx, + const GrBackendTexture& tex, GrSurfaceOrigin origin, + SkColorType ct, SkAlphaType at, sk_sp cs, + TextureReleaseProc releaseP, ReleaseContext releaseC) { + return nullptr; +} + +bool SkImage::MakeBackendTextureFromSkImage(GrContext*, + sk_sp, + GrBackendTexture*, + BackendTextureReleaseProc*) { + return false; +} + +sk_sp SkImage::MakeFromAdoptedTexture(GrContext* ctx, + const GrBackendTexture& tex, GrSurfaceOrigin origin, + SkColorType ct, SkAlphaType at, + sk_sp cs) { + return nullptr; +} + +sk_sp SkImage::MakeFromYUVATexturesCopy(GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace) { + return nullptr; +} + +sk_sp SkImage::MakeFromYUVATexturesCopyWithExternalBackend( + GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext) { + return nullptr; +} + +sk_sp SkImage::MakeFromYUVTexturesCopy(GrContext* ctx, SkYUVColorSpace space, + const GrBackendTexture[3], + GrSurfaceOrigin origin, + sk_sp imageColorSpace) { + return nullptr; +} + +sk_sp SkImage::MakeFromYUVTexturesCopyWithExternalBackend( + GrContext* context, SkYUVColorSpace yuvColorSpace, const GrBackendTexture yuvTextures[3], + GrSurfaceOrigin surfaceOrigin, const GrBackendTexture& backendTexture, + sk_sp colorSpace) { + return nullptr; +} + +sk_sp SkImage::MakeFromNV12TexturesCopy(GrContext* ctx, SkYUVColorSpace space, + const GrBackendTexture[2], + GrSurfaceOrigin origin, + sk_sp imageColorSpace) { + return nullptr; +} + +sk_sp SkImage::makeTextureImage(GrContext*, GrMipMapped mipMapped) const { + return nullptr; +} + +sk_sp SkImage::MakeFromNV12TexturesCopyWithExternalBackend( + GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture nv12Textures[2], + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext) { + return nullptr; +} + +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkImage_pinAsTexture(const SkImage* image, GrContext* ctx) { + SkASSERT(image); + SkASSERT(ctx); + return as_IB(image)->onPinAsTexture(ctx); +} + +void SkImage_unpinAsTexture(const SkImage* image, GrContext* ctx) { + SkASSERT(image); + SkASSERT(ctx); + as_IB(image)->onUnpinAsTexture(ctx); +} + +SkIRect SkImage_getSubset(const SkImage* image) { + SkASSERT(image); + return as_IB(image)->onGetSubset(); +} diff --git a/gfx/skia/skia/src/image/SkImage_Base.h b/gfx/skia/skia/src/image/SkImage_Base.h new file mode 100644 index 0000000000..1c01aa1a70 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_Base.h @@ -0,0 +1,128 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImage_Base_DEFINED +#define SkImage_Base_DEFINED + +#include "include/core/SkImage.h" +#include "include/core/SkSurface.h" +#include + +#if SK_SUPPORT_GPU +#include "include/private/SkTDArray.h" +#include "src/gpu/GrTextureProxy.h" + +class GrRecordingContext; +class GrTexture; +#endif + +#include + +class GrSamplerState; +class SkCachedData; +struct SkYUVASizeInfo; + +enum { + kNeedNewImageUniqueID = 0 +}; + +class SkImage_Base : public SkImage { +public: + virtual ~SkImage_Base(); + + virtual SkIRect onGetSubset() const { + return { 0, 0, this->width(), this->height() }; + } + + virtual bool onPeekPixels(SkPixmap*) const { return false; } + + virtual const SkBitmap* onPeekBitmap() const { return nullptr; } + + virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY, CachingHint) const = 0; + + virtual GrContext* context() const { return nullptr; } + +#if SK_SUPPORT_GPU + virtual GrSemaphoresSubmitted onFlush(GrContext* context, const GrFlushInfo&) { + return GrSemaphoresSubmitted::kNo; + } + + // Return the proxy if this image is backed by a single proxy. For YUVA images, this + // will return nullptr unless the YUVA planes have been converted to RGBA in which case + // that single backing proxy will be returned. + virtual GrTextureProxy* peekProxy() const { return nullptr; } + virtual sk_sp asTextureProxyRef(GrRecordingContext*) const { return nullptr; } + virtual sk_sp asTextureProxyRef(GrRecordingContext*, const GrSamplerState&, + SkScalar scaleAdjust[2]) const = 0; + virtual sk_sp refPinnedTextureProxy(GrRecordingContext*, + uint32_t* uniqueID) const { + return nullptr; + } + virtual bool isYUVA() const { return false; } + virtual GrTexture* onGetTexture() const { return nullptr; } +#endif + virtual GrBackendTexture onGetBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin) const; + + // return a read-only copy of the pixels. We promise to not modify them, + // but only inspect them (or encode them). + virtual bool getROPixels(SkBitmap*, CachingHint = kAllow_CachingHint) const = 0; + + virtual sk_sp onMakeSubset(GrRecordingContext*, const SkIRect&) const = 0; + + virtual sk_sp getPlanes(SkYUVASizeInfo*, SkYUVAIndex[4], + SkYUVColorSpace*, const void* planes[4]); + virtual sk_sp onRefEncoded() const { return nullptr; } + + virtual bool onAsLegacyBitmap(SkBitmap*) const; + + // True for picture-backed and codec-backed + virtual bool onIsLazyGenerated() const { return false; } + + // True for images instantiated in GPU memory + virtual bool onIsTextureBacked() const { return false; } + + // Call when this image is part of the key to a resourcecache entry. This allows the cache + // to know automatically those entries can be purged when this SkImage deleted. + virtual void notifyAddedToRasterCache() const { + fAddedToRasterCache.store(true); + } + + virtual bool onIsValid(GrContext*) const = 0; + + virtual bool onPinAsTexture(GrContext*) const { return false; } + virtual void onUnpinAsTexture(GrContext*) const {} + + virtual sk_sp onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType, sk_sp) const = 0; + + virtual sk_sp onReinterpretColorSpace(sk_sp) const = 0; + +protected: + SkImage_Base(const SkImageInfo& info, uint32_t uniqueID); + +private: + // Set true by caches when they cache content that's derived from the current pixels. + mutable std::atomic fAddedToRasterCache; + + typedef SkImage INHERITED; +}; + +static inline SkImage_Base* as_IB(SkImage* image) { + return static_cast(image); +} + +static inline SkImage_Base* as_IB(const sk_sp& image) { + return static_cast(image.get()); +} + +static inline const SkImage_Base* as_IB(const SkImage* image) { + return static_cast(image); +} + +#endif diff --git a/gfx/skia/skia/src/image/SkImage_Gpu.cpp b/gfx/skia/skia/src/image/SkImage_Gpu.cpp new file mode 100644 index 0000000000..30254e5bb5 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_Gpu.cpp @@ -0,0 +1,681 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include + +#include "include/core/SkCanvas.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkScopeExit.h" +#include "src/core/SkTraceEvent.h" +#include "src/gpu/GrAHardwareBufferImageGenerator.h" +#include "src/gpu/GrAHardwareBufferUtils.h" +#include "src/gpu/GrBackendTextureImageGenerator.h" +#include "src/gpu/GrBitmapTextureMaker.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDrawingManager.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrImageTextureMaker.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrSemaphore.h" +#include "src/gpu/GrSurfacePriv.h" +#include "src/gpu/GrTextureAdjuster.h" +#include "src/gpu/GrTextureContext.h" +#include "src/gpu/GrTexturePriv.h" +#include "src/gpu/GrTextureProxy.h" +#include "src/gpu/GrTextureProxyPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/gl/GrGLTexture.h" +#include "src/image/SkImage_Gpu.h" + +static SkColorType proxy_color_type(GrTextureProxy* proxy) { + SkColorType colorType; + if (!GrPixelConfigToColorType(proxy->config(), &colorType)) { + colorType = kUnknown_SkColorType; + } + return colorType; +} + +SkImage_Gpu::SkImage_Gpu(sk_sp context, uint32_t uniqueID, SkAlphaType at, + sk_sp proxy, sk_sp colorSpace) + : INHERITED(std::move(context), proxy->worstCaseWidth(), proxy->worstCaseHeight(), uniqueID, + proxy_color_type(proxy.get()), at, colorSpace) + , fProxy(std::move(proxy)) {} + +SkImage_Gpu::~SkImage_Gpu() {} + +GrSemaphoresSubmitted SkImage_Gpu::onFlush(GrContext* context, const GrFlushInfo& info) { + if (!context || !fContext->priv().matches(context) || fContext->abandoned()) { + return GrSemaphoresSubmitted::kNo; + } + + GrSurfaceProxy* p[1] = {fProxy.get()}; + return context->priv().flushSurfaces(p, 1, info); +} + +sk_sp SkImage_Gpu::onMakeColorTypeAndColorSpace(GrRecordingContext* context, + SkColorType targetCT, + sk_sp targetCS) const { + if (!context || !fContext->priv().matches(context)) { + return nullptr; + } + + auto xform = GrColorSpaceXformEffect::Make(this->colorSpace(), this->alphaType(), + targetCS.get(), this->alphaType()); + SkASSERT(xform || targetCT != this->colorType()); + + sk_sp proxy = this->asTextureProxyRef(context); + + auto renderTargetContext = context->priv().makeDeferredRenderTargetContextWithFallback( + SkBackingFit::kExact, this->width(), this->height(), SkColorTypeToGrColorType(targetCT), + nullptr); + if (!renderTargetContext) { + return nullptr; + } + + GrPaint paint; + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + paint.addColorTextureProcessor(std::move(proxy), SkColorTypeToGrColorType(this->colorType()), + SkMatrix::I()); + if (xform) { + paint.addColorFragmentProcessor(std::move(xform)); + } + + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), + SkRect::MakeIWH(this->width(), this->height())); + if (!renderTargetContext->asTextureProxy()) { + return nullptr; + } + + // MDB: this call is okay bc we know 'renderTargetContext' was exact + return sk_make_sp(fContext, kNeedNewImageUniqueID, this->alphaType(), + renderTargetContext->asTextureProxyRef(), std::move(targetCS)); +} + +sk_sp SkImage_Gpu::onReinterpretColorSpace(sk_sp newCS) const { + return sk_make_sp(fContext, kNeedNewImageUniqueID, this->alphaType(), fProxy, + std::move(newCS)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static sk_sp new_wrapped_texture_common(GrContext* ctx, + const GrBackendTexture& backendTex, + GrColorType colorType, GrSurfaceOrigin origin, + SkAlphaType at, sk_sp colorSpace, + GrWrapOwnership ownership, + SkImage::TextureReleaseProc releaseProc, + SkImage::ReleaseContext releaseCtx) { + if (!backendTex.isValid() || backendTex.width() <= 0 || backendTex.height() <= 0) { + return nullptr; + } + + GrProxyProvider* proxyProvider = ctx->priv().proxyProvider(); + sk_sp proxy = + proxyProvider->wrapBackendTexture(backendTex, colorType, origin, ownership, + GrWrapCacheable::kNo, kRead_GrIOType, + releaseProc, releaseCtx); + if (!proxy) { + return nullptr; + } + return sk_make_sp(sk_ref_sp(ctx), kNeedNewImageUniqueID, at, std::move(proxy), + std::move(colorSpace)); +} + +sk_sp SkImage::MakeFromTexture(GrContext* ctx, + const GrBackendTexture& tex, GrSurfaceOrigin origin, + SkColorType ct, SkAlphaType at, sk_sp cs, + TextureReleaseProc releaseP, ReleaseContext releaseC) { + if (!ctx) { + return nullptr; + } + + const GrCaps* caps = ctx->priv().caps(); + + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(caps, ct, tex.getBackendFormat()); + if (GrColorType::kUnknown == grColorType) { + return nullptr; + } + + if (!SkImage_GpuBase::ValidateBackendTexture(caps, tex, grColorType, ct, at, cs)) { + return nullptr; + } + + return new_wrapped_texture_common(ctx, tex, grColorType, origin, at, std::move(cs), + kBorrow_GrWrapOwnership, releaseP, releaseC); +} + +sk_sp SkImage::MakeFromAdoptedTexture(GrContext* ctx, + const GrBackendTexture& tex, GrSurfaceOrigin origin, + SkColorType ct, SkAlphaType at, + sk_sp cs) { + if (!ctx || !ctx->priv().resourceProvider()) { + // We have a DDL context and we don't support adopted textures for them. + return nullptr; + } + + const GrCaps* caps = ctx->priv().caps(); + + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(caps, ct, tex.getBackendFormat()); + if (GrColorType::kUnknown == grColorType) { + return nullptr; + } + + if (!SkImage_GpuBase::ValidateBackendTexture(caps, tex, grColorType, ct, at, cs)) { + return nullptr; + } + + return new_wrapped_texture_common(ctx, tex, grColorType, origin, at, std::move(cs), + kAdopt_GrWrapOwnership, nullptr, nullptr); +} + +sk_sp SkImage::MakeFromCompressed(GrContext* context, sk_sp data, + int width, int height, CompressionType type) { + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + sk_sp proxy = proxyProvider->createCompressedTextureProxy( + width, height, SkBudgeted::kYes, type, std::move(data)); + + if (!proxy) { + return nullptr; + } + + return sk_make_sp(sk_ref_sp(context), kNeedNewImageUniqueID, kOpaque_SkAlphaType, + std::move(proxy), nullptr); +} + +sk_sp SkImage_Gpu::ConvertYUVATexturesToRGB(GrContext* ctx, SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], SkISize size, + GrSurfaceOrigin origin, + GrRenderTargetContext* renderTargetContext) { + SkASSERT(renderTargetContext); + + int numTextures; + if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures)) { + return nullptr; + } + + sk_sp tempTextureProxies[4]; + if (!SkImage_GpuBase::MakeTempTextureProxies(ctx, yuvaTextures, numTextures, yuvaIndices, + origin, tempTextureProxies)) { + return nullptr; + } + + const SkRect rect = SkRect::MakeIWH(size.width(), size.height()); + if (!RenderYUVAToRGBA(ctx, renderTargetContext, rect, yuvColorSpace, nullptr, + tempTextureProxies, yuvaIndices)) { + return nullptr; + } + + SkAlphaType at = GetAlphaTypeFromYUVAIndices(yuvaIndices); + // MDB: this call is okay bc we know 'renderTargetContext' was exact + return sk_make_sp(sk_ref_sp(ctx), kNeedNewImageUniqueID, at, + renderTargetContext->asTextureProxyRef(), + renderTargetContext->colorInfo().refColorSpace()); +} + +sk_sp SkImage::MakeFromYUVATexturesCopy(GrContext* ctx, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace) { + const int width = imageSize.width(); + const int height = imageSize.height(); + + // Needs to create a render target in order to draw to it for the yuv->rgb conversion. + auto renderTargetContext = ctx->priv().makeDeferredRenderTargetContext( + SkBackingFit::kExact, width, height, GrColorType::kRGBA_8888, + std::move(imageColorSpace), 1, GrMipMapped::kNo, imageOrigin); + if (!renderTargetContext) { + return nullptr; + } + + return SkImage_Gpu::ConvertYUVATexturesToRGB(ctx, yuvColorSpace, yuvaTextures, yuvaIndices, + imageSize, imageOrigin, renderTargetContext.get()); +} + +sk_sp SkImage::MakeFromYUVATexturesCopyWithExternalBackend( + GrContext* ctx, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext) { + const GrCaps* caps = ctx->priv().caps(); + + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(caps, kRGBA_8888_SkColorType, + backendTexture.getBackendFormat()); + if (GrColorType::kUnknown == grColorType) { + return nullptr; + } + + SkAlphaType at = SkImage_GpuBase::GetAlphaTypeFromYUVAIndices(yuvaIndices); + if (!SkImage_Gpu::ValidateBackendTexture(caps, backendTexture, grColorType, + kRGBA_8888_SkColorType, at, nullptr)) { + return nullptr; + } + + // Needs to create a render target with external texture + // in order to draw to it for the yuv->rgb conversion. + auto renderTargetContext = ctx->priv().makeBackendTextureRenderTargetContext( + backendTexture, imageOrigin, 1, grColorType, std::move(imageColorSpace), nullptr, + textureReleaseProc, releaseContext); + if (!renderTargetContext) { + return nullptr; + } + + return SkImage_Gpu::ConvertYUVATexturesToRGB(ctx, yuvColorSpace, yuvaTextures, yuvaIndices, + imageSize, imageOrigin, renderTargetContext.get()); +} + +sk_sp SkImage::MakeFromYUVTexturesCopy(GrContext* ctx, SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvTextures[3], + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace) { + // TODO: SkImageSourceChannel input is being ingored right now. Setup correctly in the future. + SkYUVAIndex yuvaIndices[4] = { + SkYUVAIndex{0, SkColorChannel::kR}, + SkYUVAIndex{1, SkColorChannel::kR}, + SkYUVAIndex{2, SkColorChannel::kR}, + SkYUVAIndex{-1, SkColorChannel::kA}}; + SkISize size{yuvTextures[0].width(), yuvTextures[0].height()}; + return SkImage_Gpu::MakeFromYUVATexturesCopy(ctx, yuvColorSpace, yuvTextures, yuvaIndices, + size, imageOrigin, std::move(imageColorSpace)); +} + +sk_sp SkImage::MakeFromYUVTexturesCopyWithExternalBackend( + GrContext* ctx, SkYUVColorSpace yuvColorSpace, const GrBackendTexture yuvTextures[3], + GrSurfaceOrigin imageOrigin, const GrBackendTexture& backendTexture, + sk_sp imageColorSpace) { + SkYUVAIndex yuvaIndices[4] = { + SkYUVAIndex{0, SkColorChannel::kR}, + SkYUVAIndex{1, SkColorChannel::kR}, + SkYUVAIndex{2, SkColorChannel::kR}, + SkYUVAIndex{-1, SkColorChannel::kA}}; + SkISize size{yuvTextures[0].width(), yuvTextures[0].height()}; + return SkImage_Gpu::MakeFromYUVATexturesCopyWithExternalBackend( + ctx, yuvColorSpace, yuvTextures, yuvaIndices, size, imageOrigin, backendTexture, + std::move(imageColorSpace), nullptr, nullptr); +} + +sk_sp SkImage::MakeFromNV12TexturesCopy(GrContext* ctx, SkYUVColorSpace yuvColorSpace, + const GrBackendTexture nv12Textures[2], + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace) { + // TODO: SkImageSourceChannel input is being ingored right now. Setup correctly in the future. + SkYUVAIndex yuvaIndices[4] = { + SkYUVAIndex{0, SkColorChannel::kR}, + SkYUVAIndex{1, SkColorChannel::kR}, + SkYUVAIndex{1, SkColorChannel::kG}, + SkYUVAIndex{-1, SkColorChannel::kA}}; + SkISize size{nv12Textures[0].width(), nv12Textures[0].height()}; + return SkImage_Gpu::MakeFromYUVATexturesCopy(ctx, yuvColorSpace, nv12Textures, yuvaIndices, + size, imageOrigin, std::move(imageColorSpace)); +} + +sk_sp SkImage::MakeFromNV12TexturesCopyWithExternalBackend( + GrContext* ctx, + SkYUVColorSpace yuvColorSpace, + const GrBackendTexture nv12Textures[2], + GrSurfaceOrigin imageOrigin, + const GrBackendTexture& backendTexture, + sk_sp imageColorSpace, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext) { + SkYUVAIndex yuvaIndices[4] = { + SkYUVAIndex{0, SkColorChannel::kR}, + SkYUVAIndex{1, SkColorChannel::kR}, + SkYUVAIndex{1, SkColorChannel::kG}, + SkYUVAIndex{-1, SkColorChannel::kA}}; + SkISize size{nv12Textures[0].width(), nv12Textures[0].height()}; + return SkImage_Gpu::MakeFromYUVATexturesCopyWithExternalBackend( + ctx, yuvColorSpace, nv12Textures, yuvaIndices, size, imageOrigin, backendTexture, + std::move(imageColorSpace), textureReleaseProc, releaseContext); +} + +static sk_sp create_image_from_producer(GrContext* context, GrTextureProducer* producer, + SkAlphaType at, uint32_t id, + GrMipMapped mipMapped) { + sk_sp proxy(producer->refTextureProxy(mipMapped)); + if (!proxy) { + return nullptr; + } + return sk_make_sp(sk_ref_sp(context), id, at, std::move(proxy), + sk_ref_sp(producer->colorSpace())); +} + +sk_sp SkImage::makeTextureImage(GrContext* context, GrMipMapped mipMapped) const { + if (!context) { + return nullptr; + } + + if (this->isTextureBacked()) { + if (!as_IB(this)->context()->priv().matches(context)) { + return nullptr; + } + + sk_sp proxy = as_IB(this)->asTextureProxyRef(context); + SkASSERT(proxy); + if (GrMipMapped::kNo == mipMapped || proxy->mipMapped() == mipMapped) { + return sk_ref_sp(const_cast(this)); + } + GrTextureAdjuster adjuster(context, std::move(proxy), + SkColorTypeToGrColorType(this->colorType()), this->alphaType(), + this->uniqueID(), this->colorSpace()); + return create_image_from_producer(context, &adjuster, this->alphaType(), + this->uniqueID(), mipMapped); + } + + if (this->isLazyGenerated()) { + GrImageTextureMaker maker(context, this, kDisallow_CachingHint); + return create_image_from_producer(context, &maker, this->alphaType(), + this->uniqueID(), mipMapped); + } + + if (const SkBitmap* bmp = as_IB(this)->onPeekBitmap()) { + GrBitmapTextureMaker maker(context, *bmp); + return create_image_from_producer(context, &maker, this->alphaType(), + this->uniqueID(), mipMapped); + } + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage_Gpu::MakePromiseTexture(GrContext* context, + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContext, + PromiseImageApiVersion version) { + // The contract here is that if 'promiseDoneProc' is passed in it should always be called, + // even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes + // responsibility for calling the done proc. + if (!textureDoneProc) { + return nullptr; + } + SkScopeExit callDone([textureDoneProc, textureContext]() { textureDoneProc(textureContext); }); + + SkImageInfo info = SkImageInfo::Make(width, height, colorType, alphaType, colorSpace); + if (!SkImageInfoIsValid(info)) { + return nullptr; + } + + if (!context) { + return nullptr; + } + + if (width <= 0 || height <= 0) { + return nullptr; + } + + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(context->priv().caps(), + colorType, + backendFormat); + if (GrColorType::kUnknown == grColorType) { + return nullptr; + } + + callDone.clear(); + auto proxy = MakePromiseImageLazyProxy(context, width, height, origin, + grColorType, backendFormat, + mipMapped, textureFulfillProc, textureReleaseProc, + textureDoneProc, textureContext, version); + if (!proxy) { + return nullptr; + } + return sk_make_sp(sk_ref_sp(context), kNeedNewImageUniqueID, alphaType, + std::move(proxy), std::move(colorSpace)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage::MakeCrossContextFromPixmap(GrContext* context, + const SkPixmap& originalPixmap, bool buildMips, + bool limitToMaxTextureSize) { + // Some backends or drivers don't support (safely) moving resources between contexts + if (!context || !context->priv().caps()->crossContextTextureSupport()) { + return SkImage::MakeRasterCopy(originalPixmap); + } + + // If we don't have access to the resource provider and gpu (i.e. in a DDL context) we will not + // be able to make everything needed for a GPU CrossContext image. Thus return a raster copy + // instead. + if (!context->priv().resourceProvider()) { + return SkImage::MakeRasterCopy(originalPixmap); + } + + // If non-power-of-two mipmapping isn't supported, ignore the client's request + if (!context->priv().caps()->mipMapSupport()) { + buildMips = false; + } + + const SkPixmap* pixmap = &originalPixmap; + SkAutoPixmapStorage resized; + int maxTextureSize = context->priv().caps()->maxTextureSize(); + int maxDim = SkTMax(originalPixmap.width(), originalPixmap.height()); + if (limitToMaxTextureSize && maxDim > maxTextureSize) { + float scale = static_cast(maxTextureSize) / maxDim; + int newWidth = SkTMin(static_cast(originalPixmap.width() * scale), maxTextureSize); + int newHeight = SkTMin(static_cast(originalPixmap.height() * scale), maxTextureSize); + SkImageInfo info = originalPixmap.info().makeWH(newWidth, newHeight); + if (!resized.tryAlloc(info) || !originalPixmap.scalePixels(resized, kLow_SkFilterQuality)) { + return nullptr; + } + pixmap = &resized; + } + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + // Turn the pixmap into a GrTextureProxy + SkBitmap bmp; + bmp.installPixels(*pixmap); + GrMipMapped mipMapped = buildMips ? GrMipMapped::kYes : GrMipMapped::kNo; + sk_sp proxy = proxyProvider->createProxyFromBitmap(bmp, mipMapped); + if (!proxy) { + return SkImage::MakeRasterCopy(*pixmap); + } + + sk_sp texture = sk_ref_sp(proxy->peekTexture()); + + // Flush any writes or uploads + context->priv().flushSurface(proxy.get()); + GrGpu* gpu = context->priv().getGpu(); + + sk_sp sema = gpu->prepareTextureForCrossContextUsage(texture.get()); + + auto gen = GrBackendTextureImageGenerator::Make(std::move(texture), proxy->origin(), + std::move(sema), pixmap->colorType(), + pixmap->alphaType(), + pixmap->info().refColorSpace()); + return SkImage::MakeFromGenerator(std::move(gen)); +} + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 +sk_sp SkImage::MakeFromAHardwareBuffer(AHardwareBuffer* graphicBuffer, SkAlphaType at, + sk_sp cs, + GrSurfaceOrigin surfaceOrigin) { + auto gen = GrAHardwareBufferImageGenerator::Make(graphicBuffer, at, cs, surfaceOrigin); + return SkImage::MakeFromGenerator(std::move(gen)); +} + +sk_sp SkImage::MakeFromAHardwareBufferWithData(GrContext* context, + const SkPixmap& pixmap, + AHardwareBuffer* hardwareBuffer, + GrSurfaceOrigin surfaceOrigin) { + AHardwareBuffer_Desc bufferDesc; + AHardwareBuffer_describe(hardwareBuffer, &bufferDesc); + + if (!SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE)) { + return nullptr; + } + + GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(context, + hardwareBuffer, + bufferDesc.format, + true); + + if (!backendFormat.isValid()) { + return nullptr; + } + + GrAHardwareBufferUtils::DeleteImageProc deleteImageProc = nullptr; + GrAHardwareBufferUtils::UpdateImageProc updateImageProc = nullptr; + GrAHardwareBufferUtils::TexImageCtx deleteImageCtx = nullptr; + + GrBackendTexture backendTexture = + GrAHardwareBufferUtils::MakeBackendTexture(context, hardwareBuffer, + bufferDesc.width, bufferDesc.height, + &deleteImageProc, &updateImageProc, + &deleteImageCtx, false, backendFormat, true); + if (!backendTexture.isValid()) { + return nullptr; + } + SkASSERT(deleteImageProc); + + SkColorType colorType = + GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(bufferDesc.format); + + GrColorType grColorType = SkColorTypeToGrColorType(colorType); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + if (!proxyProvider) { + deleteImageProc(deleteImageCtx); + return nullptr; + } + + sk_sp proxy = + proxyProvider->wrapBackendTexture(backendTexture, grColorType, surfaceOrigin, + kBorrow_GrWrapOwnership, GrWrapCacheable::kNo, + kRW_GrIOType, deleteImageProc, deleteImageCtx); + if (!proxy) { + deleteImageProc(deleteImageCtx); + return nullptr; + } + + sk_sp cs = pixmap.refColorSpace(); + SkAlphaType at = pixmap.alphaType(); + + sk_sp image = sk_make_sp(sk_ref_sp(context), kNeedNewImageUniqueID, at, + proxy, cs); + if (!image) { + return nullptr; + } + + GrDrawingManager* drawingManager = context->priv().drawingManager(); + if (!drawingManager) { + return nullptr; + } + + auto texContext = + drawingManager->makeTextureContext(proxy, SkColorTypeToGrColorType(pixmap.colorType()), + pixmap.alphaType(), cs); + if (!texContext) { + return nullptr; + } + + SkImageInfo srcInfo = SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType, at, + std::move(cs)); + texContext->writePixels(srcInfo, pixmap.addr(0, 0), pixmap.rowBytes(), {0, 0}); + + GrFlushInfo info; + info.fFlags = kSyncCpu_GrFlushFlag; + GrSurfaceProxy* p[1] = {proxy.get()}; + drawingManager->flush(p, 1, SkSurface::BackendSurfaceAccess::kNoAccess, info, + GrPrepareForExternalIORequests()); + + return image; +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkImage::MakeBackendTextureFromSkImage(GrContext* ctx, + sk_sp image, + GrBackendTexture* backendTexture, + BackendTextureReleaseProc* releaseProc) { + if (!image || !ctx || !backendTexture || !releaseProc) { + return false; + } + + // Ensure we have a texture backed image. + if (!image->isTextureBacked()) { + image = image->makeTextureImage(ctx); + if (!image) { + return false; + } + } + GrTexture* texture = image->getTexture(); + if (!texture) { + // In context-loss cases, we may not have a texture. + return false; + } + + // If the image's context doesn't match the provided context, fail. + if (texture->getContext() != ctx) { + return false; + } + + // Flush any pending IO on the texture. + ctx->priv().flushSurface(as_IB(image)->peekProxy()); + + // We must make a copy of the image if the image is not unique, if the GrTexture owned by the + // image is not unique, or if the texture wraps an external object. + if (!image->unique() || !texture->unique() || + texture->resourcePriv().refsWrappedObjects()) { + // onMakeSubset will always copy the image. + image = as_IB(image)->onMakeSubset(ctx, image->bounds()); + if (!image) { + return false; + } + + texture = image->getTexture(); + if (!texture) { + return false; + } + + // Flush to ensure that the copy is completed before we return the texture. + ctx->priv().flushSurface(as_IB(image)->peekProxy()); + } + + SkASSERT(!texture->resourcePriv().refsWrappedObjects()); + SkASSERT(texture->unique()); + SkASSERT(image->unique()); + + // Take a reference to the GrTexture and release the image. + sk_sp textureRef(SkSafeRef(texture)); + image = nullptr; + + // Steal the backend texture from the GrTexture, releasing the GrTexture in the process. + return GrTexture::StealBackendTexture(std::move(textureRef), backendTexture, releaseProc); +} diff --git a/gfx/skia/skia/src/image/SkImage_Gpu.h b/gfx/skia/skia/src/image/SkImage_Gpu.h new file mode 100644 index 0000000000..71f3b14f4a --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_Gpu.h @@ -0,0 +1,75 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImage_Gpu_DEFINED +#define SkImage_Gpu_DEFINED + +#include "include/gpu/GrContext.h" +#include "src/core/SkImagePriv.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrSurfaceProxyPriv.h" +#include "src/gpu/SkGr.h" +#include "src/image/SkImage_GpuBase.h" + +class GrTexture; + +class SkBitmap; +struct SkYUVAIndex; + +class SkImage_Gpu : public SkImage_GpuBase { +public: + SkImage_Gpu(sk_sp, uint32_t uniqueID, SkAlphaType, sk_sp, + sk_sp); + ~SkImage_Gpu() override; + + GrSemaphoresSubmitted onFlush(GrContext*, const GrFlushInfo&) override; + + GrTextureProxy* peekProxy() const override { + return fProxy.get(); + } + sk_sp asTextureProxyRef(GrRecordingContext*) const override { + return fProxy; + } + + bool onIsTextureBacked() const override { return SkToBool(fProxy.get()); } + + sk_sp onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType, sk_sp) const final; + + sk_sp onReinterpretColorSpace(sk_sp) const final; + + /** + * This is the implementation of SkDeferredDisplayListRecorder::makePromiseImage. + */ + static sk_sp MakePromiseTexture(GrContext* context, + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContext, + PromiseImageApiVersion); + + static sk_sp ConvertYUVATexturesToRGB(GrContext*, SkYUVColorSpace yuvColorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, GrSurfaceOrigin imageOrigin, + GrRenderTargetContext*); + +private: + sk_sp fProxy; + + typedef SkImage_GpuBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/image/SkImage_GpuBase.cpp b/gfx/skia/skia/src/image/SkImage_GpuBase.cpp new file mode 100644 index 0000000000..58d0eb8617 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_GpuBase.cpp @@ -0,0 +1,518 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPromiseImageTexture.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkTLList.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrImageInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureAdjuster.h" +#include "src/gpu/effects/GrYUVtoRGBEffect.h" +#include "src/image/SkImage_Gpu.h" +#include "src/image/SkImage_GpuBase.h" +#include "src/image/SkReadPixelsRec.h" + +SkImage_GpuBase::SkImage_GpuBase(sk_sp context, int width, int height, uint32_t uniqueID, + SkColorType ct, SkAlphaType at, sk_sp cs) + : INHERITED(SkImageInfo::Make(width, height, ct, at, std::move(cs)), uniqueID) + , fContext(std::move(context)) {} + +SkImage_GpuBase::~SkImage_GpuBase() {} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +#if GR_TEST_UTILS +void SkImage_GpuBase::resetContext(sk_sp newContext) { + SkASSERT(fContext->priv().matches(newContext.get())); + fContext = newContext; +} +#endif + +bool SkImage_GpuBase::ValidateBackendTexture(const GrCaps* caps, const GrBackendTexture& tex, + GrColorType grCT, SkColorType ct, SkAlphaType at, + sk_sp cs) { + if (!tex.isValid()) { + return false; + } + // TODO: Create a SkImageColorInfo struct for color, alpha, and color space so we don't need to + // create a fake image info here. + SkImageInfo info = SkImageInfo::Make(1, 1, ct, at, cs); + if (!SkImageInfoIsValid(info)) { + return false; + } + GrBackendFormat backendFormat = tex.getBackendFormat(); + if (!backendFormat.isValid()) { + return false; + } + + return caps->areColorTypeAndFormatCompatible(grCT, backendFormat); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkImage_GpuBase::getROPixels(SkBitmap* dst, CachingHint chint) const { + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + // DDL TODO: buffer up the readback so it occurs when the DDL is drawn? + return false; + } + + const auto desc = SkBitmapCacheDesc::Make(this); + if (SkBitmapCache::Find(desc, dst)) { + SkASSERT(dst->isImmutable()); + SkASSERT(dst->getPixels()); + return true; + } + + SkBitmapCache::RecPtr rec = nullptr; + SkPixmap pmap; + if (kAllow_CachingHint == chint) { + rec = SkBitmapCache::Alloc(desc, this->imageInfo(), &pmap); + if (!rec) { + return false; + } + } else { + if (!dst->tryAllocPixels(this->imageInfo()) || !dst->peekPixels(&pmap)) { + return false; + } + } + + sk_sp texProxy = this->asTextureProxyRef(direct); + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(fContext->priv().caps(), + this->colorType(), + texProxy->backendFormat()); + + auto sContext = direct->priv().makeWrappedSurfaceContext( + std::move(texProxy), grColorType, this->alphaType(), this->refColorSpace()); + if (!sContext) { + return false; + } + + if (!sContext->readPixels(pmap.info(), pmap.writable_addr(), pmap.rowBytes(), {0, 0})) { + return false; + } + + if (rec) { + SkBitmapCache::Add(std::move(rec), dst); + this->notifyAddedToRasterCache(); + } + return true; +} + +sk_sp SkImage_GpuBase::onMakeSubset(GrRecordingContext* context, + const SkIRect& subset) const { + if (!context || !fContext->priv().matches(context)) { + return nullptr; + } + + sk_sp proxy = this->asTextureProxyRef(context); + GrColorType srcColorType = SkColorTypeToGrColorType(this->colorType()); + + sk_sp copyProxy = GrSurfaceProxy::Copy( + context, proxy.get(), srcColorType, GrMipMapped::kNo, subset, SkBackingFit::kExact, + proxy->isBudgeted()); + + if (!copyProxy) { + return nullptr; + } + + // MDB: this call is okay bc we know 'sContext' was kExact + return sk_make_sp(fContext, kNeedNewImageUniqueID, this->alphaType(), + std::move(copyProxy), this->refColorSpace()); +} + +bool SkImage_GpuBase::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + int srcX, int srcY, CachingHint) const { + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + // DDL TODO: buffer up the readback so it occurs when the DDL is drawn? + return false; + } + + if (!SkImageInfoValidConversion(dstInfo, this->imageInfo())) { + return false; + } + + sk_sp texProxy = this->asTextureProxyRef(direct); + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(fContext->priv().caps(), + this->colorType(), + texProxy->backendFormat()); + + auto sContext = direct->priv().makeWrappedSurfaceContext( + std::move(texProxy), grColorType, this->alphaType(), this->refColorSpace()); + if (!sContext) { + return false; + } + + return sContext->readPixels(dstInfo, dstPixels, dstRB, {srcX, srcY}); +} + +sk_sp SkImage_GpuBase::asTextureProxyRef(GrRecordingContext* context, + const GrSamplerState& params, + SkScalar scaleAdjust[2]) const { + if (!context || !fContext->priv().matches(context)) { + SkASSERT(0); + return nullptr; + } + + GrTextureAdjuster adjuster(fContext.get(), this->asTextureProxyRef(context), + SkColorTypeToGrColorType(this->colorType()), this->alphaType(), + this->uniqueID(), this->colorSpace()); + return adjuster.refTextureProxyForParams(params, scaleAdjust); +} + +GrBackendTexture SkImage_GpuBase::onGetBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin) const { + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + // This image was created with a DDL context and cannot be instantiated. + return GrBackendTexture(); // invalid + } + + sk_sp proxy = this->asTextureProxyRef(direct); + SkASSERT(proxy); + + if (!proxy->isInstantiated()) { + auto resourceProvider = direct->priv().resourceProvider(); + + if (!proxy->instantiate(resourceProvider)) { + return GrBackendTexture(); // invalid + } + } + + GrTexture* texture = proxy->peekTexture(); + if (texture) { + if (flushPendingGrContextIO) { + direct->priv().flushSurface(proxy.get()); + } + if (origin) { + *origin = proxy->origin(); + } + return texture->getBackendTexture(); + } + return GrBackendTexture(); // invalid +} + +GrTexture* SkImage_GpuBase::onGetTexture() const { + GrTextureProxy* proxy = this->peekProxy(); + if (proxy && proxy->isInstantiated()) { + return proxy->peekTexture(); + } + + auto direct = fContext->priv().asDirectContext(); + if (!direct) { + // This image was created with a DDL context and cannot be instantiated. + return nullptr; + } + + sk_sp proxyRef = this->asTextureProxyRef(direct); + SkASSERT(proxyRef && !proxyRef->isInstantiated()); + + if (!proxyRef->instantiate(direct->priv().resourceProvider())) { + return nullptr; + } + + return proxyRef->peekTexture(); +} + +bool SkImage_GpuBase::onIsValid(GrContext* context) const { + // The base class has already checked that context isn't abandoned (if it's not nullptr) + if (fContext->priv().abandoned()) { + return false; + } + + if (context && !fContext->priv().matches(context)) { + return false; + } + + return true; +} + +bool SkImage_GpuBase::MakeTempTextureProxies(GrContext* ctx, const GrBackendTexture yuvaTextures[], + int numTextures, const SkYUVAIndex yuvaIndices[4], + GrSurfaceOrigin imageOrigin, + sk_sp tempTextureProxies[4]) { + GrProxyProvider* proxyProvider = ctx->priv().proxyProvider(); + const GrCaps* caps = ctx->priv().caps(); + + for (int textureIndex = 0; textureIndex < numTextures; ++textureIndex) { + GrBackendFormat backendFormat = yuvaTextures[textureIndex].getBackendFormat(); + if (!backendFormat.isValid()) { + return false; + } + + GrColorType grColorType = caps->getYUVAColorTypeFromBackendFormat( + backendFormat, + yuvaIndices[3].fIndex == textureIndex); + if (GrColorType::kUnknown == grColorType) { + return false; + } + + SkASSERT(yuvaTextures[textureIndex].isValid()); + + tempTextureProxies[textureIndex] = proxyProvider->wrapBackendTexture( + yuvaTextures[textureIndex], grColorType, imageOrigin, kBorrow_GrWrapOwnership, + GrWrapCacheable::kNo, kRead_GrIOType); + if (!tempTextureProxies[textureIndex]) { + return false; + } + + // Check that each texture contains the channel data for the corresponding YUVA index + auto componentFlags = GrColorTypeComponentFlags(grColorType); + for (int yuvaIndex = 0; yuvaIndex < SkYUVAIndex::kIndexCount; ++yuvaIndex) { + if (yuvaIndices[yuvaIndex].fIndex == textureIndex) { + switch (yuvaIndices[yuvaIndex].fChannel) { + case SkColorChannel::kR: + // TODO: Chrome needs to be patched before this can be + // enforced. +// if (!(kRed_SkColorTypeComponentFlag & componentFlags)) { +// return false; +// } + break; + case SkColorChannel::kG: + if (!(kGreen_SkColorTypeComponentFlag & componentFlags)) { + return false; + } + break; + case SkColorChannel::kB: + if (!(kBlue_SkColorTypeComponentFlag & componentFlags)) { + return false; + } + break; + case SkColorChannel::kA: + if (!(kAlpha_SkColorTypeComponentFlag & componentFlags)) { + return false; + } + break; + } + } + } + } + + return true; +} + +bool SkImage_GpuBase::RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* renderTargetContext, + const SkRect& rect, SkYUVColorSpace yuvColorSpace, + sk_sp colorSpaceXform, + const sk_sp proxies[4], + const SkYUVAIndex yuvaIndices[4]) { + SkASSERT(renderTargetContext); + if (!renderTargetContext->asSurfaceProxy()) { + return false; + } + + GrPaint paint; + paint.setPorterDuffXPFactory(SkBlendMode::kSrc); + + auto fp = GrYUVtoRGBEffect::Make(proxies, yuvaIndices, yuvColorSpace, + GrSamplerState::Filter::kNearest); + if (colorSpaceXform) { + fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(colorSpaceXform)); + } + paint.addColorFragmentProcessor(std::move(fp)); + + renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect); + return true; +} + +sk_sp SkImage_GpuBase::MakePromiseImageLazyProxy( + GrContext* context, int width, int height, GrSurfaceOrigin origin, GrColorType colorType, + GrBackendFormat backendFormat, GrMipMapped mipMapped, + PromiseImageTextureFulfillProc fulfillProc, + PromiseImageTextureReleaseProc releaseProc, + PromiseImageTextureDoneProc doneProc, + PromiseImageTextureContext textureContext, + PromiseImageApiVersion version) { + SkASSERT(context); + SkASSERT(width > 0 && height > 0); + SkASSERT(doneProc); + SkASSERT(colorType != GrColorType::kUnknown); + + if (!fulfillProc || !releaseProc) { + doneProc(textureContext); + return nullptr; + } + + if (mipMapped == GrMipMapped::kYes && + GrTextureTypeHasRestrictedSampling(backendFormat.textureType())) { + // It is invalid to have a GL_TEXTURE_EXTERNAL or GL_TEXTURE_RECTANGLE and have mips as + // well. + doneProc(textureContext); + return nullptr; + } + + /** + * This class is the lazy instantiation callback for promise images. It manages calling the + * client's Fulfill, Release, and Done procs. It attempts to reuse a GrTexture instance in + * cases where the client provides the same SkPromiseImageTexture as Fulfill results for + * multiple SkImages. The created GrTexture is given a key based on a unique ID associated with + * the SkPromiseImageTexture. + * + * The GrTexutre idle proc mechanism is used to call the Release and Done procs. We use this + * instead of the GrSurface release proc because the GrTexture is cached and therefore may + * outlive the proxy into which this callback is installed. + * + * A key invalidation message is installed on the SkPromiseImageTexture so that the GrTexture + * is deleted once it can no longer be used to instantiate a proxy. + */ + class PromiseLazyInstantiateCallback { + public: + PromiseLazyInstantiateCallback(PromiseImageTextureFulfillProc fulfillProc, + PromiseImageTextureReleaseProc releaseProc, + PromiseImageTextureDoneProc doneProc, + PromiseImageTextureContext context, + GrColorType colorType, + PromiseImageApiVersion version) + : fFulfillProc(fulfillProc) + , fReleaseProc(releaseProc) + , fColorType(colorType) + , fVersion(version) { + fDoneCallback = sk_make_sp(doneProc, context); + } + PromiseLazyInstantiateCallback(PromiseLazyInstantiateCallback&&) = default; + PromiseLazyInstantiateCallback(const PromiseLazyInstantiateCallback&) { + // Because we get wrapped in std::function we must be copyable. But we should never + // be copied. + SkASSERT(false); + } + PromiseLazyInstantiateCallback& operator=(PromiseLazyInstantiateCallback&&) = default; + PromiseLazyInstantiateCallback& operator=(const PromiseLazyInstantiateCallback&) { + SkASSERT(false); + return *this; + } + + ~PromiseLazyInstantiateCallback() { + // Our destructor can run on any thread. We trigger the unref of fTexture by message. + // This unreffed texture pointer is a real problem! When the context has been + // abandoned, the GrTexture pointed to by this pointer is deleted! Due to virtual + // inheritance any manipulation of this pointer at that point will cause a crash. + // For now we "work around" the problem by just passing it, untouched, into the + // message bus but this very fragile. + // In the future the GrSurface class hierarchy refactoring should eliminate this + // difficulty by removing the virtual inheritance. + if (fTexture) { + SkMessageBus::Post({fTexture, fTextureContextID}); + } + } + + GrSurfaceProxy::LazyCallbackResult operator()(GrResourceProvider* resourceProvider) { + // We use the unique key in a way that is unrelated to the SkImage-based key that the + // proxy may receive, hence kUnsynced. + static constexpr auto kKeySyncMode = + GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced; + + // In order to make the SkImage "thread safe" we rely on holding an extra ref to the + // texture in the callback and signalling the unref via a message to the resource cache. + // We need to extend the callback's lifetime to that of the proxy. + static constexpr auto kReleaseCallbackOnInstantiation = false; + + // Our proxy is getting instantiated for the second+ time. We are only allowed to call + // Fulfill once. So return our cached result. + if (fTexture) { + return {sk_ref_sp(fTexture), kReleaseCallbackOnInstantiation, kKeySyncMode}; + } else if (fColorType == GrColorType::kUnknown) { + // We've already called fulfill and it failed. Our contract says that we should only + // call each callback once. + return {}; + } + SkASSERT(fDoneCallback); + PromiseImageTextureContext textureContext = fDoneCallback->context(); + sk_sp promiseTexture = fFulfillProc(textureContext); + // From here on out our contract is that the release proc must be called, even if + // the return from fulfill was invalid or we fail for some other reason. + auto releaseCallback = sk_make_sp(fReleaseProc, textureContext); + if (!promiseTexture) { + // This records that we have failed. + fColorType = GrColorType::kUnknown; + return {}; + } + + const GrBackendTexture& backendTexture = promiseTexture->backendTexture(); + if (!backendTexture.isValid()) { + return {}; + } + + sk_sp tex; + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey key; + GrUniqueKey::Builder builder(&key, kDomain, 2, "promise"); + builder[0] = promiseTexture->uniqueID(); + builder[1] = (uint32_t) fColorType; + builder.finish(); + // A texture with this key may already exist from a different instance of this lazy + // callback. This could happen if the client fulfills a promise image with a texture + // that was previously used to fulfill a different promise image. + if (auto surf = resourceProvider->findByUniqueKey(key)) { + tex = sk_ref_sp(surf->asTexture()); + SkASSERT(tex); + } else { + if ((tex = resourceProvider->wrapBackendTexture( + backendTexture, fColorType, kBorrow_GrWrapOwnership, + GrWrapCacheable::kYes, kRead_GrIOType))) { + tex->resourcePriv().setUniqueKey(key); + } else { + return {}; + } + } + auto releaseIdleState = fVersion == PromiseImageApiVersion::kLegacy + ? GrTexture::IdleState::kFinished + : GrTexture::IdleState::kFlushed; + tex->addIdleProc(std::move(releaseCallback), releaseIdleState); + tex->addIdleProc(std::move(fDoneCallback), GrTexture::IdleState::kFinished); + promiseTexture->addKeyToInvalidate(tex->getContext()->priv().contextID(), key); + fTexture = tex.get(); + // We need to hold on to the GrTexture in case our proxy gets reinstantiated. However, + // we can't unref in our destructor because we may be on another thread then. So we + // let the cache know it is waiting on an unref message. We will send that message from + // our destructor. + GrContext* context = fTexture->getContext(); + context->priv().getResourceCache()->insertDelayedTextureUnref(fTexture); + fTextureContextID = context->priv().contextID(); + return {std::move(tex), kReleaseCallbackOnInstantiation, kKeySyncMode}; + } + + private: + PromiseImageTextureFulfillProc fFulfillProc; + PromiseImageTextureReleaseProc fReleaseProc; + sk_sp fDoneCallback; + GrTexture* fTexture = nullptr; + uint32_t fTextureContextID = SK_InvalidUniqueID; + GrColorType fColorType; + PromiseImageApiVersion fVersion; + } callback(fulfillProc, releaseProc, doneProc, textureContext, colorType, version); + + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + + GrPixelConfig config = context->priv().caps()->getConfigFromBackendFormat( + backendFormat, + colorType); + + GrSurfaceDesc desc; + desc.fWidth = width; + desc.fHeight = height; + desc.fConfig = config; + + // Ganesh assumes that, when wrapping a mipmapped backend texture from a client, that its + // mipmaps are fully fleshed out. + GrMipMapsStatus mipMapsStatus = (GrMipMapped::kYes == mipMapped) + ? GrMipMapsStatus::kValid : GrMipMapsStatus::kNotAllocated; + + // We pass kReadOnly here since we should treat content of the client's texture as immutable. + // The promise API provides no way for the client to indicated that the texture is protected. + return proxyProvider->createLazyProxy( + std::move(callback), backendFormat, desc, GrRenderable::kNo, 1, origin, mipMapped, + mipMapsStatus, GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, SkBudgeted::kNo, + GrProtected::kNo, GrSurfaceProxy::UseAllocator::kYes); +} diff --git a/gfx/skia/skia/src/image/SkImage_GpuBase.h b/gfx/skia/skia/src/image/SkImage_GpuBase.h new file mode 100644 index 0000000000..c78f7993f4 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_GpuBase.h @@ -0,0 +1,103 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImage_GpuBase_DEFINED +#define SkImage_GpuBase_DEFINED + +#include "include/core/SkDeferredDisplayListRecorder.h" +#include "include/core/SkYUVAIndex.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "include/private/GrTypesPriv.h" +#include "src/image/SkImage_Base.h" + +class GrColorSpaceXform; +class SkColorSpace; + +class SkImage_GpuBase : public SkImage_Base { +public: + SkImage_GpuBase(sk_sp, int width, int height, uint32_t uniqueID, SkColorType, + SkAlphaType, sk_sp); + ~SkImage_GpuBase() override; + + GrContext* context() const final { return fContext.get(); } + + bool getROPixels(SkBitmap*, CachingHint) const final; + sk_sp onMakeSubset(GrRecordingContext*, const SkIRect& subset) const final; + + bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + int srcX, int srcY, CachingHint) const override; + + sk_sp asTextureProxyRef(GrRecordingContext* context) const override { + // we shouldn't end up calling this + SkASSERT(false); + return this->INHERITED::asTextureProxyRef(context); + } + sk_sp asTextureProxyRef(GrRecordingContext*, const GrSamplerState&, + SkScalar scaleAdjust[2]) const final; + + sk_sp refPinnedTextureProxy(GrRecordingContext* context, + uint32_t* uniqueID) const final { + *uniqueID = this->uniqueID(); + return this->asTextureProxyRef(context); + } + + GrBackendTexture onGetBackendTexture(bool flushPendingGrContextIO, + GrSurfaceOrigin* origin) const final; + + GrTexture* onGetTexture() const final; + + bool onIsValid(GrContext*) const final; + +#if GR_TEST_UTILS + void resetContext(sk_sp newContext); +#endif + + static bool ValidateBackendTexture(const GrCaps*, const GrBackendTexture& tex, + GrColorType grCT, SkColorType ct, SkAlphaType at, + sk_sp cs); + static bool MakeTempTextureProxies(GrContext* ctx, const GrBackendTexture yuvaTextures[], + int numTextures, const SkYUVAIndex [4], + GrSurfaceOrigin imageOrigin, + sk_sp tempTextureProxies[4]); + + static SkAlphaType GetAlphaTypeFromYUVAIndices(const SkYUVAIndex yuvaIndices[4]) { + return -1 != yuvaIndices[SkYUVAIndex::kA_Index].fIndex ? kPremul_SkAlphaType + : kOpaque_SkAlphaType; + } + + using PromiseImageTextureContext = SkDeferredDisplayListRecorder::PromiseImageTextureContext; + using PromiseImageTextureFulfillProc = + SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc; + using PromiseImageTextureReleaseProc = + SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc; + using PromiseImageTextureDoneProc = SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc; + +protected: + using PromiseImageApiVersion = SkDeferredDisplayListRecorder::PromiseImageApiVersion; + // Helper for making a lazy proxy for a promise image. The PromiseDoneProc we be called, + // if not null, immediately if this function fails. Othwerwise, it is installed in the + // proxy along with the TextureFulfillProc and TextureReleaseProc. PromiseDoneProc must not + // be null. + static sk_sp MakePromiseImageLazyProxy( + GrContext*, int width, int height, GrSurfaceOrigin, GrColorType, GrBackendFormat, + GrMipMapped, PromiseImageTextureFulfillProc, PromiseImageTextureReleaseProc, + PromiseImageTextureDoneProc, PromiseImageTextureContext, PromiseImageApiVersion); + + static bool RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* renderTargetContext, + const SkRect& rect, SkYUVColorSpace yuvColorSpace, + sk_sp colorSpaceXform, + const sk_sp proxies[4], + const SkYUVAIndex yuvaIndices[4]); + + sk_sp fContext; + +private: + typedef SkImage_Base INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/image/SkImage_GpuYUVA.cpp b/gfx/skia/skia/src/image/SkImage_GpuYUVA.cpp new file mode 100644 index 0000000000..03a7cacd70 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_GpuYUVA.cpp @@ -0,0 +1,400 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include + +#include "include/core/SkYUVASizeInfo.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkMipMap.h" +#include "src/core/SkScopeExit.h" +#include "src/gpu/GrClip.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrGpu.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTargetContext.h" +#include "src/gpu/GrTextureProducer.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrYUVtoRGBEffect.h" +#include "src/image/SkImage_Gpu.h" +#include "src/image/SkImage_GpuYUVA.h" + +static constexpr auto kAssumedColorType = kRGBA_8888_SkColorType; + +SkImage_GpuYUVA::SkImage_GpuYUVA(sk_sp context, int width, int height, uint32_t uniqueID, + SkYUVColorSpace colorSpace, sk_sp proxies[], + GrColorType proxyColorTypes[], int numProxies, + const SkYUVAIndex yuvaIndices[4], GrSurfaceOrigin origin, + sk_sp imageColorSpace) + : INHERITED(std::move(context), width, height, uniqueID, kAssumedColorType, + // If an alpha channel is present we always switch to kPremul. This is because, + // although the planar data is always un-premul, the final interleaved RGB image + // is/would-be premul. + GetAlphaTypeFromYUVAIndices(yuvaIndices), std::move(imageColorSpace)) + , fNumProxies(numProxies) + , fYUVColorSpace(colorSpace) + , fOrigin(origin) { + // The caller should have done this work, just verifying + SkDEBUGCODE(int textureCount;) + SkASSERT(SkYUVAIndex::AreValidIndices(yuvaIndices, &textureCount)); + SkASSERT(textureCount == fNumProxies); + + for (int i = 0; i < numProxies; ++i) { + fProxies[i] = std::move(proxies[i]); + fProxyColorTypes[i] = proxyColorTypes[i]; + } + memcpy(fYUVAIndices, yuvaIndices, 4*sizeof(SkYUVAIndex)); +} + +// For onMakeColorSpace() +SkImage_GpuYUVA::SkImage_GpuYUVA(const SkImage_GpuYUVA* image, sk_sp targetCS) + : INHERITED(image->fContext, image->width(), image->height(), kNeedNewImageUniqueID, + kAssumedColorType, + // If an alpha channel is present we always switch to kPremul. This is because, + // although the planar data is always un-premul, the final interleaved RGB image + // is/would-be premul. + GetAlphaTypeFromYUVAIndices(image->fYUVAIndices), std::move(targetCS)) + , fNumProxies(image->fNumProxies) + , fYUVColorSpace(image->fYUVColorSpace) + , fOrigin(image->fOrigin) + // Since null fFromColorSpace means no GrColorSpaceXform, we turn a null + // image->refColorSpace() into an explicit SRGB. + , fFromColorSpace(image->colorSpace() ? image->refColorSpace() : SkColorSpace::MakeSRGB()) { + // The caller should have done this work, just verifying + SkDEBUGCODE(int textureCount;) + SkASSERT(SkYUVAIndex::AreValidIndices(image->fYUVAIndices, &textureCount)); + SkASSERT(textureCount == fNumProxies); + + if (image->fRGBProxy) { + fRGBProxy = image->fRGBProxy; // we ref in this case, not move + } else { + for (int i = 0; i < fNumProxies; ++i) { + fProxies[i] = image->fProxies[i]; // we ref in this case, not move + fProxyColorTypes[i] = image->fProxyColorTypes[i]; + } + } + memcpy(fYUVAIndices, image->fYUVAIndices, 4 * sizeof(SkYUVAIndex)); +} + +SkImage_GpuYUVA::~SkImage_GpuYUVA() {} + +bool SkImage_GpuYUVA::setupMipmapsForPlanes(GrRecordingContext* context) const { + // We shouldn't get here if the planes were already flattened to RGBA. + SkASSERT(fProxies[0] && !fRGBProxy); + if (!context || !fContext->priv().matches(context)) { + return false; + } + + for (int i = 0; i < fNumProxies; ++i) { + GrTextureProducer::CopyParams copyParams; + int mipCount = SkMipMap::ComputeLevelCount(fProxies[i]->width(), fProxies[i]->height()); + if (mipCount && GrGpu::IsACopyNeededForMips(fContext->priv().caps(), + fProxies[i].get(), + GrSamplerState::Filter::kMipMap, + ©Params)) { + auto mippedProxy = GrCopyBaseMipMapToTextureProxy(context, fProxies[i].get(), + fProxyColorTypes[i]); + if (!mippedProxy) { + return false; + } + fProxies[i] = mippedProxy; + } + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +GrSemaphoresSubmitted SkImage_GpuYUVA::onFlush(GrContext* context, const GrFlushInfo& info) { + if (!context || !fContext->priv().matches(context) || fContext->abandoned()) { + return GrSemaphoresSubmitted::kNo; + } + + GrSurfaceProxy* proxies[4] = {fProxies[0].get(), fProxies[1].get(), + fProxies[2].get(), fProxies[3].get()}; + int numProxies = fNumProxies; + if (fRGBProxy) { + // Either we've already flushed the flattening draw or the flattening is unflushed. In the + // latter case it should still be ok to just pass fRGBProxy because it in turn depends on + // the planar proxies and will cause all of their work to flush as well. + proxies[0] = fRGBProxy.get(); + numProxies = 1; + } + return context->priv().flushSurfaces(proxies, numProxies, info); +} + +GrTextureProxy* SkImage_GpuYUVA::peekProxy() const { + return fRGBProxy.get(); +} + +sk_sp SkImage_GpuYUVA::asTextureProxyRef(GrRecordingContext* context) const { + if (fRGBProxy) { + return fRGBProxy; + } + + if (!context || !fContext->priv().matches(context)) { + return nullptr; + } + + // Needs to create a render target in order to draw to it for the yuv->rgb conversion. + auto renderTargetContext = context->priv().makeDeferredRenderTargetContext( + SkBackingFit::kExact, this->width(), this->height(), GrColorType::kRGBA_8888, + this->refColorSpace(), 1, GrMipMapped::kNo, fOrigin); + if (!renderTargetContext) { + return nullptr; + } + + sk_sp colorSpaceXform; + if (fFromColorSpace) { + colorSpaceXform = GrColorSpaceXform::Make(fFromColorSpace.get(), this->alphaType(), + this->colorSpace(), this->alphaType()); + } + const SkRect rect = SkRect::MakeIWH(this->width(), this->height()); + if (!RenderYUVAToRGBA(fContext.get(), renderTargetContext.get(), rect, fYUVColorSpace, + std::move(colorSpaceXform), fProxies, fYUVAIndices)) { + return nullptr; + } + + fRGBProxy = renderTargetContext->asTextureProxyRef(); + for (auto& p : fProxies) { + p.reset(); + } + return fRGBProxy; +} + +sk_sp SkImage_GpuYUVA::asMippedTextureProxyRef(GrRecordingContext* context) const { + if (!context || !fContext->priv().matches(context)) { + return nullptr; + } + + // if invalid or already has miplevels + auto proxy = this->asTextureProxyRef(context); + if (!proxy || GrMipMapped::kYes == fRGBProxy->mipMapped()) { + return proxy; + } + + // need to generate mips for the proxy + GrColorType srcColorType = SkColorTypeToGrColorType(this->colorType()); + if (auto mippedProxy = GrCopyBaseMipMapToTextureProxy(context, proxy.get(), srcColorType)) { + fRGBProxy = mippedProxy; + return mippedProxy; + } + + // failed to generate mips + return nullptr; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage_GpuYUVA::onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType, + sk_sp targetCS) const { + // We explicitly ignore color type changes, for now. + + // we may need a mutex here but for now we expect usage to be in a single thread + if (fOnMakeColorSpaceTarget && + SkColorSpace::Equals(targetCS.get(), fOnMakeColorSpaceTarget.get())) { + return fOnMakeColorSpaceResult; + } + sk_sp result = sk_sp(new SkImage_GpuYUVA(this, targetCS)); + if (result) { + fOnMakeColorSpaceTarget = targetCS; + fOnMakeColorSpaceResult = result; + } + return result; +} + +sk_sp SkImage_GpuYUVA::onReinterpretColorSpace(sk_sp newCS) const { + return sk_make_sp(fContext, this->width(), this->height(), + kNeedNewImageUniqueID, fYUVColorSpace, fProxies, + fProxyColorTypes, fNumProxies, fYUVAIndices, fOrigin, + std::move(newCS)); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage::MakeFromYUVATextures(GrContext* ctx, + SkYUVColorSpace colorSpace, + const GrBackendTexture yuvaTextures[], + const SkYUVAIndex yuvaIndices[4], + SkISize imageSize, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace) { + int numTextures; + if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures)) { + return nullptr; + } + + sk_sp tempTextureProxies[4]; + if (!SkImage_GpuBase::MakeTempTextureProxies(ctx, yuvaTextures, numTextures, yuvaIndices, + imageOrigin, tempTextureProxies)) { + return nullptr; + } + GrColorType proxyColorTypes[4]; + for (int i = 0; i < numTextures; ++i) { + proxyColorTypes[i] = ctx->priv().caps()->getYUVAColorTypeFromBackendFormat( + yuvaTextures[i].getBackendFormat(), yuvaIndices[3].fIndex == i); + } + + return sk_make_sp(sk_ref_sp(ctx), imageSize.width(), imageSize.height(), + kNeedNewImageUniqueID, colorSpace, tempTextureProxies, + proxyColorTypes, numTextures, yuvaIndices, imageOrigin, + imageColorSpace); +} + +sk_sp SkImage::MakeFromYUVAPixmaps( + GrContext* context, SkYUVColorSpace yuvColorSpace, const SkPixmap yuvaPixmaps[], + const SkYUVAIndex yuvaIndices[4], SkISize imageSize, GrSurfaceOrigin imageOrigin, + bool buildMips, bool limitToMaxTextureSize, sk_sp imageColorSpace) { + if (!context) { + return nullptr; // until we impl this for raster backend + } + + int numPixmaps; + if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numPixmaps)) { + return nullptr; + } + + if (!context->priv().caps()->mipMapSupport()) { + buildMips = false; + } + + // Make proxies + GrProxyProvider* proxyProvider = context->priv().proxyProvider(); + sk_sp tempTextureProxies[4]; + GrColorType proxyColorTypes[4]; + for (int i = 0; i < numPixmaps; ++i) { + const SkPixmap* pixmap = &yuvaPixmaps[i]; + SkAutoPixmapStorage resized; + int maxTextureSize = context->priv().caps()->maxTextureSize(); + int maxDim = SkTMax(yuvaPixmaps[i].width(), yuvaPixmaps[i].height()); + if (limitToMaxTextureSize && maxDim > maxTextureSize) { + float scale = static_cast(maxTextureSize) / maxDim; + int newWidth = SkTMin(static_cast(yuvaPixmaps[i].width() * scale), + maxTextureSize); + int newHeight = SkTMin(static_cast(yuvaPixmaps[i].height() * scale), + maxTextureSize); + SkImageInfo info = yuvaPixmaps[i].info().makeWH(newWidth, newHeight); + if (!resized.tryAlloc(info) || + !yuvaPixmaps[i].scalePixels(resized, kLow_SkFilterQuality)) { + return nullptr; + } + pixmap = &resized; + } + // Turn the pixmap into a GrTextureProxy + SkBitmap bmp; + bmp.installPixels(*pixmap); + GrMipMapped mipMapped = buildMips ? GrMipMapped::kYes : GrMipMapped::kNo; + tempTextureProxies[i] = proxyProvider->createProxyFromBitmap(bmp, mipMapped); + if (!tempTextureProxies[i]) { + return nullptr; + } + proxyColorTypes[i] = SkColorTypeToGrColorType(bmp.colorType()); + } + + return sk_make_sp(sk_ref_sp(context), imageSize.width(), imageSize.height(), + kNeedNewImageUniqueID, yuvColorSpace, tempTextureProxies, + proxyColorTypes, numPixmaps, yuvaIndices, imageOrigin, + imageColorSpace); +} + + +///////////////////////////////////////////////////////////////////////////////////////////////// +sk_sp SkImage_GpuYUVA::MakePromiseYUVATexture( + GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendFormat yuvaFormats[], + const SkISize yuvaSizes[], + const SkYUVAIndex yuvaIndices[4], + int imageWidth, + int imageHeight, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc promiseDoneProc, + PromiseImageTextureContext textureContexts[], + PromiseImageApiVersion version) { + int numTextures; + bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures); + + // The contract here is that if 'promiseDoneProc' is passed in it should always be called, + // even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes + // responsibility for calling the done proc. + if (!promiseDoneProc) { + return nullptr; + } + int proxiesCreated = 0; + SkScopeExit callDone([promiseDoneProc, textureContexts, numTextures, &proxiesCreated]() { + for (int i = proxiesCreated; i < numTextures; ++i) { + promiseDoneProc(textureContexts[i]); + } + }); + + if (!valid) { + return nullptr; + } + + if (!context) { + return nullptr; + } + + if (imageWidth <= 0 || imageHeight <= 0) { + return nullptr; + } + + SkAlphaType at = (-1 != yuvaIndices[SkYUVAIndex::kA_Index].fIndex) ? kPremul_SkAlphaType + : kOpaque_SkAlphaType; + SkImageInfo info = SkImageInfo::Make(imageWidth, imageHeight, kAssumedColorType, + at, imageColorSpace); + if (!SkImageInfoIsValid(info)) { + return nullptr; + } + + // verify sizes with expected texture count + for (int i = 0; i < numTextures; ++i) { + if (yuvaSizes[i].isEmpty()) { + return nullptr; + } + } + for (int i = numTextures; i < SkYUVASizeInfo::kMaxCount; ++i) { + if (!yuvaSizes[i].isEmpty()) { + return nullptr; + } + } + + // Get lazy proxies + sk_sp proxies[4]; + GrColorType proxyColorTypes[4]; + for (int texIdx = 0; texIdx < numTextures; ++texIdx) { + GrColorType colorType = context->priv().caps()->getYUVAColorTypeFromBackendFormat( + yuvaFormats[texIdx], + yuvaIndices[3].fIndex == texIdx); + if (GrColorType::kUnknown == colorType) { + return nullptr; + } + + proxies[texIdx] = MakePromiseImageLazyProxy( + context, yuvaSizes[texIdx].width(), yuvaSizes[texIdx].height(), imageOrigin, + colorType, yuvaFormats[texIdx], GrMipMapped::kNo, textureFulfillProc, + textureReleaseProc, promiseDoneProc, textureContexts[texIdx], version); + ++proxiesCreated; + if (!proxies[texIdx]) { + return nullptr; + } + proxyColorTypes[texIdx] = colorType; + } + + return sk_make_sp(sk_ref_sp(context), imageWidth, imageHeight, + kNeedNewImageUniqueID, yuvColorSpace, proxies, + proxyColorTypes, numTextures, yuvaIndices, imageOrigin, + std::move(imageColorSpace)); +} diff --git a/gfx/skia/skia/src/image/SkImage_GpuYUVA.h b/gfx/skia/skia/src/image/SkImage_GpuYUVA.h new file mode 100644 index 0000000000..eac04e4f25 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_GpuYUVA.h @@ -0,0 +1,107 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImage_GpuYUVA_DEFINED +#define SkImage_GpuYUVA_DEFINED + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "src/core/SkCachedData.h" +#include "src/image/SkImage_GpuBase.h" + +class GrTexture; +struct SkYUVASizeInfo; + +// Wraps the 3 or 4 planes of a YUVA image for consumption by the GPU. +// Initially any direct rendering will be done by passing the individual planes to a shader. +// Once any method requests a flattened image (e.g., onReadPixels), the flattened RGB +// proxy will be stored and used for any future rendering. +class SkImage_GpuYUVA : public SkImage_GpuBase { +public: + friend class GrYUVAImageTextureMaker; + + SkImage_GpuYUVA(sk_sp, int width, int height, uint32_t uniqueID, SkYUVColorSpace, + sk_sp proxies[], GrColorType proxyColorTypes[], int numProxies, + const SkYUVAIndex[4], GrSurfaceOrigin, sk_sp); + ~SkImage_GpuYUVA() override; + + GrSemaphoresSubmitted onFlush(GrContext*, const GrFlushInfo&) override; + + // This returns the single backing proxy if the YUV channels have already been flattened but + // nullptr if they have not. + GrTextureProxy* peekProxy() const override; + sk_sp asTextureProxyRef(GrRecordingContext*) const override; + + virtual bool onIsTextureBacked() const override { return fProxies[0] || fRGBProxy; } + + sk_sp onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType, sk_sp) const final; + + sk_sp onReinterpretColorSpace(sk_sp) const final; + + virtual bool isYUVA() const override { return true; } + + bool setupMipmapsForPlanes(GrRecordingContext*) const; + + // Returns a ref-ed texture proxy with miplevels + sk_sp asMippedTextureProxyRef(GrRecordingContext*) const; + +#if GR_TEST_UTILS + bool testingOnly_IsFlattened() const { + // We should only have the flattened proxy or the planar proxies at one point in time. + SkASSERT(SkToBool(fRGBProxy) != SkToBool(fProxies[0])); + return SkToBool(fRGBProxy); + } +#endif + + /** + * This is the implementation of SkDeferredDisplayListRecorder::makeYUVAPromiseTexture. + */ + static sk_sp MakePromiseYUVATexture(GrContext* context, + SkYUVColorSpace yuvColorSpace, + const GrBackendFormat yuvaFormats[], + const SkISize yuvaSizes[], + const SkYUVAIndex yuvaIndices[4], + int width, + int height, + GrSurfaceOrigin imageOrigin, + sk_sp imageColorSpace, + PromiseImageTextureFulfillProc textureFulfillProc, + PromiseImageTextureReleaseProc textureReleaseProc, + PromiseImageTextureDoneProc textureDoneProc, + PromiseImageTextureContext textureContexts[], + PromiseImageApiVersion); + +private: + SkImage_GpuYUVA(const SkImage_GpuYUVA* image, sk_sp); + + // This array will usually only be sparsely populated. + // The actual non-null fields are dictated by the 'fYUVAIndices' indices + mutable sk_sp fProxies[4]; + mutable GrColorType fProxyColorTypes[4]; + int fNumProxies; + SkYUVAIndex fYUVAIndices[4]; + const SkYUVColorSpace fYUVColorSpace; + GrSurfaceOrigin fOrigin; + // If this is non-null then the planar data should be converted from fFromColorSpace to + // this->colorSpace(). Otherwise we assume the planar data (post YUV->RGB conversion) is already + // in this->colorSpace(). + const sk_sp fFromColorSpace; + + // Repeated calls to onMakeColorSpace will result in a proliferation of unique IDs and + // SkImage_GpuYUVA instances. Cache the result of the last successful onMakeColorSpace call. + mutable sk_sp fOnMakeColorSpaceTarget; + mutable sk_sp fOnMakeColorSpaceResult; + + // This is only allocated when the image needs to be flattened rather than + // using the separate YUVA planes. From thence forth we will only use the + // the RGBProxy. + mutable sk_sp fRGBProxy; + typedef SkImage_GpuBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/image/SkImage_Lazy.cpp b/gfx/skia/skia/src/image/SkImage_Lazy.cpp new file mode 100644 index 0000000000..022ba40f45 --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_Lazy.cpp @@ -0,0 +1,568 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/image/SkImage_Lazy.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkImageGenerator.h" +#include "src/core/SkBitmapCache.h" +#include "src/core/SkCachedData.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkNextID.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "include/private/GrResourceKey.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrGpuResourcePriv.h" +#include "src/gpu/GrImageTextureMaker.h" +#include "src/gpu/GrProxyProvider.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrSamplerState.h" +#include "src/gpu/GrYUVProvider.h" +#include "src/gpu/SkGr.h" +#endif + +// Ref-counted tuple(SkImageGenerator, SkMutex) which allows sharing one generator among N images +class SharedGenerator final : public SkNVRefCnt { +public: + static sk_sp Make(std::unique_ptr gen) { + return gen ? sk_sp(new SharedGenerator(std::move(gen))) : nullptr; + } + + // This is thread safe. It is a const field set in the constructor. + const SkImageInfo& getInfo() { return fGenerator->getInfo(); } + +private: + explicit SharedGenerator(std::unique_ptr gen) + : fGenerator(std::move(gen)) { + SkASSERT(fGenerator); + } + + friend class ScopedGenerator; + friend class SkImage_Lazy; + + std::unique_ptr fGenerator; + SkMutex fMutex; +}; + +/////////////////////////////////////////////////////////////////////////////// + +SkImage_Lazy::Validator::Validator(sk_sp gen, const SkIRect* subset, + const SkColorType* colorType, sk_sp colorSpace) + : fSharedGenerator(std::move(gen)) { + if (!fSharedGenerator) { + return; + } + + // The following generator accessors are safe without acquiring the mutex (const getters). + // TODO: refactor to use a ScopedGenerator instead, for clarity. + const SkImageInfo& info = fSharedGenerator->fGenerator->getInfo(); + if (info.isEmpty()) { + fSharedGenerator.reset(); + return; + } + + fUniqueID = fSharedGenerator->fGenerator->uniqueID(); + const SkIRect bounds = SkIRect::MakeWH(info.width(), info.height()); + if (subset) { + if (!bounds.contains(*subset)) { + fSharedGenerator.reset(); + return; + } + if (*subset != bounds) { + // we need a different uniqueID since we really are a subset of the raw generator + fUniqueID = SkNextID::ImageID(); + } + } else { + subset = &bounds; + } + + fInfo = info.makeDimensions(subset->size()); + fOrigin = SkIPoint::Make(subset->x(), subset->y()); + if (colorType || colorSpace) { + if (colorType) { + fInfo = fInfo.makeColorType(*colorType); + } + if (colorSpace) { + fInfo = fInfo.makeColorSpace(colorSpace); + } + fUniqueID = SkNextID::ImageID(); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// Helper for exclusive access to a shared generator. +class SkImage_Lazy::ScopedGenerator { +public: + ScopedGenerator(const sk_sp& gen) + : fSharedGenerator(gen) + , fAutoAquire(gen->fMutex) {} + + SkImageGenerator* operator->() const { + fSharedGenerator->fMutex.assertHeld(); + return fSharedGenerator->fGenerator.get(); + } + + operator SkImageGenerator*() const { + fSharedGenerator->fMutex.assertHeld(); + return fSharedGenerator->fGenerator.get(); + } + +private: + const sk_sp& fSharedGenerator; + SkAutoMutexExclusive fAutoAquire; +}; + +/////////////////////////////////////////////////////////////////////////////// + +SkImage_Lazy::SkImage_Lazy(Validator* validator) + : INHERITED(validator->fInfo, validator->fUniqueID) + , fSharedGenerator(std::move(validator->fSharedGenerator)) + , fOrigin(validator->fOrigin) { + SkASSERT(fSharedGenerator); + fUniqueID = validator->fUniqueID; +} + +SkImage_Lazy::~SkImage_Lazy() { +#if SK_SUPPORT_GPU + for (int i = 0; i < fUniqueKeyInvalidatedMessages.count(); ++i) { + SkMessageBus::Post(*fUniqueKeyInvalidatedMessages[i]); + } + fUniqueKeyInvalidatedMessages.deleteAll(); +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool generate_pixels(SkImageGenerator* gen, const SkPixmap& pmap, int originX, int originY) { + const int genW = gen->getInfo().width(); + const int genH = gen->getInfo().height(); + const SkIRect srcR = SkIRect::MakeWH(genW, genH); + const SkIRect dstR = SkIRect::MakeXYWH(originX, originY, pmap.width(), pmap.height()); + if (!srcR.contains(dstR)) { + return false; + } + + // If they are requesting a subset, we have to have a temp allocation for full image, and + // then copy the subset into their allocation + SkBitmap full; + SkPixmap fullPM; + const SkPixmap* dstPM = &pmap; + if (srcR != dstR) { + if (!full.tryAllocPixels(pmap.info().makeWH(genW, genH))) { + return false; + } + if (!full.peekPixels(&fullPM)) { + return false; + } + dstPM = &fullPM; + } + + if (!gen->getPixels(dstPM->info(), dstPM->writable_addr(), dstPM->rowBytes())) { + return false; + } + + if (srcR != dstR) { + if (!full.readPixels(pmap, originX, originY)) { + return false; + } + } + return true; +} + +bool SkImage_Lazy::getROPixels(SkBitmap* bitmap, SkImage::CachingHint chint) const { + auto check_output_bitmap = [bitmap]() { + SkASSERT(bitmap->isImmutable()); + SkASSERT(bitmap->getPixels()); + (void)bitmap; + }; + + auto desc = SkBitmapCacheDesc::Make(this); + if (SkBitmapCache::Find(desc, bitmap)) { + check_output_bitmap(); + return true; + } + + if (SkImage::kAllow_CachingHint == chint) { + SkPixmap pmap; + SkBitmapCache::RecPtr cacheRec = SkBitmapCache::Alloc(desc, this->imageInfo(), &pmap); + if (!cacheRec || + !generate_pixels(ScopedGenerator(fSharedGenerator), pmap, + fOrigin.x(), fOrigin.y())) { + return false; + } + SkBitmapCache::Add(std::move(cacheRec), bitmap); + this->notifyAddedToRasterCache(); + } else { + if (!bitmap->tryAllocPixels(this->imageInfo()) || + !generate_pixels(ScopedGenerator(fSharedGenerator), bitmap->pixmap(), fOrigin.x(), + fOrigin.y())) { + return false; + } + bitmap->setImmutable(); + } + + check_output_bitmap(); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkImage_Lazy::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, + int srcX, int srcY, CachingHint chint) const { + SkBitmap bm; + if (this->getROPixels(&bm, chint)) { + return bm.readPixels(dstInfo, dstPixels, dstRB, srcX, srcY); + } + return false; +} + +sk_sp SkImage_Lazy::onRefEncoded() const { + ScopedGenerator generator(fSharedGenerator); + return generator->refEncodedData(); +} + +bool SkImage_Lazy::onIsValid(GrContext* context) const { + ScopedGenerator generator(fSharedGenerator); + return generator->isValid(context); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU +sk_sp SkImage_Lazy::asTextureProxyRef(GrRecordingContext* context, + const GrSamplerState& params, + SkScalar scaleAdjust[2]) const { + if (!context) { + return nullptr; + } + + GrImageTextureMaker textureMaker(context, this, kAllow_CachingHint); + return textureMaker.refTextureProxyForParams(params, scaleAdjust); +} +#endif + +sk_sp SkImage_Lazy::onMakeSubset(GrRecordingContext* context, + const SkIRect& subset) const { + SkASSERT(this->bounds().contains(subset)); + SkASSERT(this->bounds() != subset); + + const SkIRect generatorSubset = subset.makeOffset(fOrigin); + const SkColorType colorType = this->colorType(); + Validator validator(fSharedGenerator, &generatorSubset, &colorType, this->refColorSpace()); + return validator ? sk_sp(new SkImage_Lazy(&validator)) : nullptr; +} + +sk_sp SkImage_Lazy::onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType targetCT, + sk_sp targetCS) const { + SkAutoMutexExclusive autoAquire(fOnMakeColorTypeAndSpaceMutex); + if (fOnMakeColorTypeAndSpaceResult && + targetCT == fOnMakeColorTypeAndSpaceResult->colorType() && + SkColorSpace::Equals(targetCS.get(), fOnMakeColorTypeAndSpaceResult->colorSpace())) { + return fOnMakeColorTypeAndSpaceResult; + } + const SkIRect generatorSubset = + SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), this->width(), this->height()); + Validator validator(fSharedGenerator, &generatorSubset, &targetCT, targetCS); + sk_sp result = validator ? sk_sp(new SkImage_Lazy(&validator)) : nullptr; + if (result) { + fOnMakeColorTypeAndSpaceResult = result; + } + return result; +} + +sk_sp SkImage_Lazy::onReinterpretColorSpace(sk_sp newCS) const { + // TODO: The correct thing is to clone the generator, and modify its color space. That's hard, + // because we don't have a clone method, and generator is public (and derived-from by clients). + // So do the simple/inefficient thing here, and fallback to raster when this is called. + + // We allocate the bitmap with the new color space, then generate the image using the original. + SkBitmap bitmap; + if (bitmap.tryAllocPixels(this->imageInfo().makeColorSpace(std::move(newCS)))) { + SkPixmap pixmap = bitmap.pixmap(); + pixmap.setColorSpace(this->refColorSpace()); + if (generate_pixels(ScopedGenerator(fSharedGenerator), pixmap, fOrigin.x(), fOrigin.y())) { + bitmap.setImmutable(); + return SkImage::MakeFromBitmap(bitmap); + } + } + return nullptr; +} + +sk_sp SkImage::MakeFromGenerator(std::unique_ptr generator, + const SkIRect* subset) { + SkImage_Lazy::Validator + validator(SharedGenerator::Make(std::move(generator)), subset, nullptr, nullptr); + + return validator ? sk_make_sp(&validator) : nullptr; +} + +sk_sp SkImage::DecodeToRaster(const void* encoded, size_t length, const SkIRect* subset) { + // The generator will not outlive this function, so we can wrap the encoded data without copy + auto gen = SkImageGenerator::MakeFromEncoded(SkData::MakeWithoutCopy(encoded, length)); + if (!gen) { + return nullptr; + } + SkImageInfo info = gen->getInfo(); + if (info.isEmpty()) { + return nullptr; + } + + SkIPoint origin = {0, 0}; + if (subset) { + if (!SkIRect::MakeWH(info.width(), info.height()).contains(*subset)) { + return nullptr; + } + info = info.makeDimensions(subset->size()); + origin = {subset->x(), subset->y()}; + } + + size_t rb = info.minRowBytes(); + if (rb == 0) { + return nullptr; // rb was too big + } + size_t size = info.computeByteSize(rb); + if (size == SIZE_MAX) { + return nullptr; + } + auto data = SkData::MakeUninitialized(size); + + SkPixmap pmap(info, data->writable_data(), rb); + if (!generate_pixels(gen.get(), pmap, origin.x(), origin.y())) { + return nullptr; + } + + return SkImage::MakeRasterData(info, data, rb); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +void SkImage_Lazy::makeCacheKeyFromOrigKey(const GrUniqueKey& origKey, + GrUniqueKey* cacheKey) const { + SkASSERT(!cacheKey->isValid()); + if (origKey.isValid()) { + static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); + GrUniqueKey::Builder builder(cacheKey, origKey, kDomain, 0, "Image"); + } +} + +class Generator_GrYUVProvider : public GrYUVProvider { +public: + Generator_GrYUVProvider(SkImageGenerator* gen) : fGen(gen) {} + +private: + uint32_t onGetID() const override { return fGen->uniqueID(); } + bool onQueryYUVA8(SkYUVASizeInfo* sizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace) const override { + return fGen->queryYUVA8(sizeInfo, yuvaIndices, colorSpace); + } + bool onGetYUVA8Planes(const SkYUVASizeInfo& sizeInfo, + const SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + void* planes[]) override { + return fGen->getYUVA8Planes(sizeInfo, yuvaIndices, planes); + } + + SkImageGenerator* fGen; + + typedef GrYUVProvider INHERITED; +}; + +static void set_key_on_proxy(GrProxyProvider* proxyProvider, + GrTextureProxy* proxy, GrTextureProxy* originalProxy, + const GrUniqueKey& key) { + if (key.isValid()) { + if (originalProxy && originalProxy->getUniqueKey().isValid()) { + SkASSERT(originalProxy->getUniqueKey() == key); + SkASSERT(GrMipMapped::kYes == proxy->mipMapped() && + GrMipMapped::kNo == originalProxy->mipMapped()); + // If we had an originalProxy with a valid key, that means there already is a proxy in + // the cache which matches the key, but it does not have mip levels and we require them. + // Thus we must remove the unique key from that proxy. + SkASSERT(originalProxy->getUniqueKey() == key); + proxyProvider->removeUniqueKeyFromProxy(originalProxy); + } + proxyProvider->assignUniqueKeyToProxy(key, proxy); + } +} + +sk_sp SkImage_Lazy::getPlanes(SkYUVASizeInfo* yuvaSizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* yuvColorSpace, + const void* planes[SkYUVASizeInfo::kMaxCount]) { + ScopedGenerator generator(fSharedGenerator); + Generator_GrYUVProvider provider(generator); + + sk_sp data = provider.getPlanes(yuvaSizeInfo, yuvaIndices, yuvColorSpace, planes); + if (!data) { + return nullptr; + } + + return data; +} + + +/* + * We have 4 ways to try to return a texture (in sorted order) + * + * 1. Check the cache for a pre-existing one + * 2. Ask the generator to natively create one + * 3. Ask the generator to return YUV planes, which the GPU can convert + * 4. Ask the generator to return RGB(A) data, which the GPU can convert + */ +sk_sp SkImage_Lazy::lockTextureProxy( + GrRecordingContext* ctx, + const GrUniqueKey& origKey, + SkImage::CachingHint chint, + bool willBeMipped, + GrTextureMaker::AllowedTexGenType genType) const { + // Values representing the various texture lock paths we can take. Used for logging the path + // taken to a histogram. + enum LockTexturePath { + kFailure_LockTexturePath, + kPreExisting_LockTexturePath, + kNative_LockTexturePath, + kCompressed_LockTexturePath, // Deprecated + kYUV_LockTexturePath, + kRGBA_LockTexturePath, + }; + + enum { kLockTexturePathCount = kRGBA_LockTexturePath + 1 }; + + // Build our texture key. + // Even though some proxies created here may have a specific origin and use that origin, we do + // not include that in the key. Since SkImages are meant to be immutable, a given SkImage will + // always have an associated proxy that is always one origin or the other. It never can change + // origins. Thus we don't need to include that info in the key iteself. + GrUniqueKey key; + this->makeCacheKeyFromOrigKey(origKey, &key); + + GrProxyProvider* proxyProvider = ctx->priv().proxyProvider(); + sk_sp proxy; + + // 1. Check the cache for a pre-existing one + if (key.isValid()) { + auto ct = SkColorTypeToGrColorType(this->colorType()); + proxy = proxyProvider->findOrCreateProxyByUniqueKey(key, ct, kTopLeft_GrSurfaceOrigin); + if (proxy) { + SK_HISTOGRAM_ENUMERATION("LockTexturePath", kPreExisting_LockTexturePath, + kLockTexturePathCount); + if (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped()) { + return proxy; + } + } + } + + // 2. Ask the generator to natively create one + if (!proxy) { + ScopedGenerator generator(fSharedGenerator); + if (GrTextureMaker::AllowedTexGenType::kCheap == genType && + SkImageGenerator::TexGenType::kCheap != generator->onCanGenerateTexture()) { + return nullptr; + } + if ((proxy = generator->generateTexture(ctx, this->imageInfo(), fOrigin, willBeMipped))) { + SK_HISTOGRAM_ENUMERATION("LockTexturePath", kNative_LockTexturePath, + kLockTexturePathCount); + set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key); + if (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped()) { + *fUniqueKeyInvalidatedMessages.append() = + new GrUniqueKeyInvalidatedMessage(key, ctx->priv().contextID()); + return proxy; + } + } + } + + // 3. Ask the generator to return YUV planes, which the GPU can convert. If we will be mipping + // the texture we fall through here and have the CPU generate the mip maps for us. + if (!proxy && !willBeMipped && !ctx->priv().options().fDisableGpuYUVConversion) { + const GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(this->imageInfo()); + + SkColorType colorType = this->colorType(); + + ScopedGenerator generator(fSharedGenerator); + Generator_GrYUVProvider provider(generator); + + // The pixels in the texture will be in the generator's color space. + // If onMakeColorTypeAndColorSpace has been called then this will not match this image's + // color space. To correct this, apply a color space conversion from the generator's color + // space to this image's color space. + SkColorSpace* generatorColorSpace = fSharedGenerator->fGenerator->getInfo().colorSpace(); + SkColorSpace* thisColorSpace = this->colorSpace(); + + // TODO: Update to create the mipped surface in the YUV generator and draw the base + // layer directly into the mipped surface. + proxy = provider.refAsTextureProxy(ctx, desc, SkColorTypeToGrColorType(colorType), + generatorColorSpace, thisColorSpace); + if (proxy) { + SK_HISTOGRAM_ENUMERATION("LockTexturePath", kYUV_LockTexturePath, + kLockTexturePathCount); + set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key); + *fUniqueKeyInvalidatedMessages.append() = + new GrUniqueKeyInvalidatedMessage(key, ctx->priv().contextID()); + return proxy; + } + } + + // 4. Ask the generator to return RGB(A) data, which the GPU can convert + SkBitmap bitmap; + if (!proxy && this->getROPixels(&bitmap, chint)) { + proxy = proxyProvider->createProxyFromBitmap(bitmap, willBeMipped ? GrMipMapped::kYes + : GrMipMapped::kNo); + if (proxy && (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped())) { + SK_HISTOGRAM_ENUMERATION("LockTexturePath", kRGBA_LockTexturePath, + kLockTexturePathCount); + set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key); + *fUniqueKeyInvalidatedMessages.append() = + new GrUniqueKeyInvalidatedMessage(key, ctx->priv().contextID()); + return proxy; + } + } + + if (proxy) { + // We need a mipped proxy, but we either found a proxy earlier that wasn't mipped, generated + // a native non mipped proxy, or generated a non-mipped yuv proxy. Thus we generate a new + // mipped surface and copy the original proxy into the base layer. We will then let the gpu + // generate the rest of the mips. + SkASSERT(willBeMipped); + SkASSERT(GrMipMapped::kNo == proxy->mipMapped()); + *fUniqueKeyInvalidatedMessages.append() = + new GrUniqueKeyInvalidatedMessage(key, ctx->priv().contextID()); + GrColorType srcColorType = SkColorTypeToGrColorType(this->colorType()); + if (auto mippedProxy = GrCopyBaseMipMapToTextureProxy(ctx, proxy.get(), srcColorType)) { + set_key_on_proxy(proxyProvider, mippedProxy.get(), proxy.get(), key); + return mippedProxy; + } + // We failed to make a mipped proxy with the base copied into it. This could have + // been from failure to make the proxy or failure to do the copy. Thus we will fall + // back to just using the non mipped proxy; See skbug.com/7094. + return proxy; + } + + SK_HISTOGRAM_ENUMERATION("LockTexturePath", kFailure_LockTexturePath, + kLockTexturePathCount); + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage::DecodeToTexture(GrContext* ctx, const void* encoded, size_t length, + const SkIRect* subset) { + // img will not survive this function, so we don't need to copy/own the encoded data, + auto img = MakeFromEncoded(SkData::MakeWithoutCopy(encoded, length), subset); + if (!img) { + return nullptr; + } + return img->makeTextureImage(ctx); +} + +#endif diff --git a/gfx/skia/skia/src/image/SkImage_Lazy.h b/gfx/skia/skia/src/image/SkImage_Lazy.h new file mode 100644 index 0000000000..ca094b3d1f --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_Lazy.h @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImage_Lazy_DEFINED +#define SkImage_Lazy_DEFINED + +#include "include/private/SkMutex.h" +#include "src/image/SkImage_Base.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrTextureMaker.h" +#endif + +class SharedGenerator; + +class SkImage_Lazy : public SkImage_Base { +public: + struct Validator { + Validator(sk_sp, const SkIRect* subset, const SkColorType* colorType, + sk_sp colorSpace); + + MOZ_IMPLICIT operator bool() const { return fSharedGenerator.get(); } + + sk_sp fSharedGenerator; + SkImageInfo fInfo; + SkIPoint fOrigin; + sk_sp fColorSpace; + uint32_t fUniqueID; + }; + + SkImage_Lazy(Validator* validator); + ~SkImage_Lazy() override; + + SkIRect onGetSubset() const override { + return SkIRect::MakeXYWH(fOrigin.fX, fOrigin.fY, this->width(), this->height()); + } + + bool onReadPixels(const SkImageInfo&, void*, size_t, int srcX, int srcY, + CachingHint) const override; +#if SK_SUPPORT_GPU + sk_sp asTextureProxyRef(GrRecordingContext*, + const GrSamplerState&, + SkScalar scaleAdjust[2]) const override; + sk_sp getPlanes(SkYUVASizeInfo*, SkYUVAIndex[4], + SkYUVColorSpace*, const void* planes[4]) override; +#endif + sk_sp onRefEncoded() const override; + sk_sp onMakeSubset(GrRecordingContext*, const SkIRect&) const override; + bool getROPixels(SkBitmap*, CachingHint) const override; + bool onIsLazyGenerated() const override { return true; } + sk_sp onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType, sk_sp) const override; + sk_sp onReinterpretColorSpace(sk_sp) const final; + + bool onIsValid(GrContext*) const override; + +#if SK_SUPPORT_GPU + // Returns the texture proxy. If we're going to generate and cache the texture, we should use + // the passed in key (if the key is valid). If genType is AllowedTexGenType::kCheap and the + // texture is not trivial to construct, returns nullptr. + sk_sp lockTextureProxy(GrRecordingContext*, + const GrUniqueKey& key, + SkImage::CachingHint, + bool willBeMipped, + GrTextureMaker::AllowedTexGenType genType) const; + + void makeCacheKeyFromOrigKey(const GrUniqueKey& origKey, GrUniqueKey* cacheKey) const; +#endif + +private: + class ScopedGenerator; + + // Note that this->imageInfo() is not necessarily the info from the generator. It may be + // cropped by onMakeSubset and its color type/space may be changed by + // onMakeColorTypeAndColorSpace. + sk_sp fSharedGenerator; + const SkIPoint fOrigin; + + uint32_t fUniqueID; + + // Repeated calls to onMakeColorTypeAndColorSpace will result in a proliferation of unique IDs + // and SkImage_Lazy instances. Cache the result of the last successful call. + mutable SkMutex fOnMakeColorTypeAndSpaceMutex; + mutable sk_sp fOnMakeColorTypeAndSpaceResult; + +#if SK_SUPPORT_GPU + // When the SkImage_Lazy goes away, we will iterate over all the unique keys we've used and + // send messages to the GrContexts to say the unique keys are no longer valid. The GrContexts + // can then release the resources, conntected with the those unique keys, from their caches. + mutable SkTDArray fUniqueKeyInvalidatedMessages; +#endif + + typedef SkImage_Base INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/image/SkImage_Raster.cpp b/gfx/skia/skia/src/image/SkImage_Raster.cpp new file mode 100644 index 0000000000..5992af798d --- /dev/null +++ b/gfx/skia/skia/src/image/SkImage_Raster.cpp @@ -0,0 +1,360 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/core/SkPixelRef.h" +#include "include/core/SkSurface.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/codec/SkColorTable.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkTLazy.h" +#include "src/image/SkImage_Base.h" +#include "src/shaders/SkBitmapProcShader.h" + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/GrTextureAdjuster.h" +#include "src/gpu/SkGr.h" +#endif + +// fixes https://bug.skia.org/5096 +static bool is_not_subset(const SkBitmap& bm) { + SkASSERT(bm.pixelRef()); + SkISize dim = SkISize::Make(bm.pixelRef()->width(), bm.pixelRef()->height()); + SkASSERT(dim != bm.dimensions() || bm.pixelRefOrigin().isZero()); + return dim == bm.dimensions(); +} + +class SkImage_Raster : public SkImage_Base { +public: + static bool ValidArgs(const SkImageInfo& info, size_t rowBytes, size_t* minSize) { + const int maxDimension = SK_MaxS32 >> 2; + + if (info.width() <= 0 || info.height() <= 0) { + return false; + } + if (info.width() > maxDimension || info.height() > maxDimension) { + return false; + } + if ((unsigned)info.colorType() > (unsigned)kLastEnum_SkColorType) { + return false; + } + if ((unsigned)info.alphaType() > (unsigned)kLastEnum_SkAlphaType) { + return false; + } + + if (kUnknown_SkColorType == info.colorType()) { + return false; + } + if (!info.validRowBytes(rowBytes)) { + return false; + } + + size_t size = info.computeByteSize(rowBytes); + if (SkImageInfo::ByteSizeOverflowed(size)) { + return false; + } + + if (minSize) { + *minSize = size; + } + return true; + } + + SkImage_Raster(const SkImageInfo&, sk_sp, size_t rb, + uint32_t id = kNeedNewImageUniqueID); + ~SkImage_Raster() override; + + bool onReadPixels(const SkImageInfo&, void*, size_t, int srcX, int srcY, CachingHint) const override; + bool onPeekPixels(SkPixmap*) const override; + const SkBitmap* onPeekBitmap() const override { return &fBitmap; } + +#if SK_SUPPORT_GPU + sk_sp asTextureProxyRef(GrRecordingContext*, const GrSamplerState&, + SkScalar scaleAdjust[2]) const override; +#endif + + bool getROPixels(SkBitmap*, CachingHint) const override; + sk_sp onMakeSubset(GrRecordingContext*, const SkIRect&) const override; + + SkPixelRef* getPixelRef() const { return fBitmap.pixelRef(); } + + bool onAsLegacyBitmap(SkBitmap*) const override; + + SkImage_Raster(const SkBitmap& bm, bool bitmapMayBeMutable = false) + : INHERITED(bm.info(), + is_not_subset(bm) ? bm.getGenerationID() : (uint32_t)kNeedNewImageUniqueID) + , fBitmap(bm) { + SkASSERT(bitmapMayBeMutable || fBitmap.isImmutable()); + } + + sk_sp onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType, sk_sp) const override; + + sk_sp onReinterpretColorSpace(sk_sp) const override; + + bool onIsValid(GrContext* context) const override { return true; } + void notifyAddedToRasterCache() const override { + // We explicitly DON'T want to call INHERITED::notifyAddedToRasterCache. That ties the + // lifetime of derived/cached resources to the image. In this case, we only want cached + // data (eg mips) tied to the lifetime of the underlying pixelRef. + SkASSERT(fBitmap.pixelRef()); + fBitmap.pixelRef()->notifyAddedToCache(); + } + +#if SK_SUPPORT_GPU + sk_sp refPinnedTextureProxy(GrRecordingContext*, + uint32_t* uniqueID) const override; + bool onPinAsTexture(GrContext*) const override; + void onUnpinAsTexture(GrContext*) const override; +#endif + +private: + SkBitmap fBitmap; + +#if SK_SUPPORT_GPU + mutable sk_sp fPinnedProxy; + mutable int32_t fPinnedCount = 0; + mutable uint32_t fPinnedUniqueID = 0; +#endif + + typedef SkImage_Base INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +static void release_data(void* addr, void* context) { + SkData* data = static_cast(context); + data->unref(); +} + +SkImage_Raster::SkImage_Raster(const SkImageInfo& info, sk_sp data, size_t rowBytes, + uint32_t id) + : INHERITED(info, id) { + void* addr = const_cast(data->data()); + + fBitmap.installPixels(info, addr, rowBytes, release_data, data.release()); + fBitmap.setImmutable(); +} + +SkImage_Raster::~SkImage_Raster() { +#if SK_SUPPORT_GPU + SkASSERT(nullptr == fPinnedProxy.get()); // want the caller to have manually unpinned +#endif +} + +bool SkImage_Raster::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY, CachingHint) const { + SkBitmap shallowCopy(fBitmap); + return shallowCopy.readPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY); +} + +bool SkImage_Raster::onPeekPixels(SkPixmap* pm) const { + return fBitmap.peekPixels(pm); +} + +bool SkImage_Raster::getROPixels(SkBitmap* dst, CachingHint) const { + *dst = fBitmap; + return true; +} + +#if SK_SUPPORT_GPU +sk_sp SkImage_Raster::asTextureProxyRef(GrRecordingContext* context, + const GrSamplerState& params, + SkScalar scaleAdjust[2]) const { + if (!context) { + return nullptr; + } + + uint32_t uniqueID; + sk_sp tex = this->refPinnedTextureProxy(context, &uniqueID); + if (tex) { + GrTextureAdjuster adjuster(context, fPinnedProxy, + SkColorTypeToGrColorType(fBitmap.colorType()), + fBitmap.alphaType(), fPinnedUniqueID, fBitmap.colorSpace()); + return adjuster.refTextureProxyForParams(params, scaleAdjust); + } + + return GrRefCachedBitmapTextureProxy(context, fBitmap, params, scaleAdjust); +} +#endif + +#if SK_SUPPORT_GPU + +sk_sp SkImage_Raster::refPinnedTextureProxy(GrRecordingContext*, + uint32_t* uniqueID) const { + if (fPinnedProxy) { + SkASSERT(fPinnedCount > 0); + SkASSERT(fPinnedUniqueID != 0); + *uniqueID = fPinnedUniqueID; + return fPinnedProxy; + } + return nullptr; +} + +bool SkImage_Raster::onPinAsTexture(GrContext* ctx) const { + if (fPinnedProxy) { + SkASSERT(fPinnedCount > 0); + SkASSERT(fPinnedUniqueID != 0); + } else { + SkASSERT(fPinnedCount == 0); + SkASSERT(fPinnedUniqueID == 0); + fPinnedProxy = GrRefCachedBitmapTextureProxy(ctx, fBitmap, GrSamplerState::ClampNearest(), + nullptr); + if (!fPinnedProxy) { + return false; + } + fPinnedUniqueID = fBitmap.getGenerationID(); + } + // Note: we only increment if the texture was successfully pinned + ++fPinnedCount; + return true; +} + +void SkImage_Raster::onUnpinAsTexture(GrContext* ctx) const { + // Note: we always decrement, even if fPinnedTexture is null + SkASSERT(fPinnedCount > 0); + SkASSERT(fPinnedUniqueID != 0); + + if (0 == --fPinnedCount) { + fPinnedProxy.reset(nullptr); + fPinnedUniqueID = 0; + } +} +#endif + +sk_sp SkImage_Raster::onMakeSubset(GrRecordingContext*, const SkIRect& subset) const { + SkImageInfo info = fBitmap.info().makeDimensions(subset.size()); + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(info)) { + return nullptr; + } + + void* dst = bitmap.getPixels(); + void* src = fBitmap.getAddr(subset.x(), subset.y()); + if (!dst || !src) { + SkDEBUGFAIL("SkImage_Raster::onMakeSubset with nullptr src or dst"); + return nullptr; + } + + SkRectMemcpy(dst, bitmap.rowBytes(), src, fBitmap.rowBytes(), bitmap.rowBytes(), + subset.height()); + + bitmap.setImmutable(); + return MakeFromBitmap(bitmap); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp MakeRasterCopyPriv(const SkPixmap& pmap, uint32_t id) { + size_t size; + if (!SkImage_Raster::ValidArgs(pmap.info(), pmap.rowBytes(), &size) || !pmap.addr()) { + return nullptr; + } + + // Here we actually make a copy of the caller's pixel data + sk_sp data(SkData::MakeWithCopy(pmap.addr(), size)); + return sk_make_sp(pmap.info(), std::move(data), pmap.rowBytes(), id); +} + +sk_sp SkImage::MakeRasterCopy(const SkPixmap& pmap) { + return MakeRasterCopyPriv(pmap, kNeedNewImageUniqueID); +} + +sk_sp SkImage::MakeRasterData(const SkImageInfo& info, sk_sp data, + size_t rowBytes) { + size_t size; + if (!SkImage_Raster::ValidArgs(info, rowBytes, &size) || !data) { + return nullptr; + } + + // did they give us enough data? + if (data->size() < size) { + return nullptr; + } + + return sk_make_sp(info, std::move(data), rowBytes); +} + +sk_sp SkImage::MakeFromRaster(const SkPixmap& pmap, RasterReleaseProc proc, + ReleaseContext ctx) { + size_t size; + if (!SkImage_Raster::ValidArgs(pmap.info(), pmap.rowBytes(), &size) || !pmap.addr()) { + return nullptr; + } + + sk_sp data(SkData::MakeWithProc(pmap.addr(), size, proc, ctx)); + return sk_make_sp(pmap.info(), std::move(data), pmap.rowBytes()); +} + +sk_sp SkMakeImageFromRasterBitmapPriv(const SkBitmap& bm, SkCopyPixelsMode cpm, + uint32_t idForCopy) { + if (kAlways_SkCopyPixelsMode == cpm || (!bm.isImmutable() && kNever_SkCopyPixelsMode != cpm)) { + SkPixmap pmap; + if (bm.peekPixels(&pmap)) { + return MakeRasterCopyPriv(pmap, idForCopy); + } else { + return sk_sp(); + } + } + + return sk_make_sp(bm, kNever_SkCopyPixelsMode == cpm); +} + +sk_sp SkMakeImageFromRasterBitmap(const SkBitmap& bm, SkCopyPixelsMode cpm) { + if (!SkImageInfoIsValid(bm.info()) || bm.rowBytes() < bm.info().minRowBytes()) { + return nullptr; + } + + return SkMakeImageFromRasterBitmapPriv(bm, cpm, kNeedNewImageUniqueID); +} + +const SkPixelRef* SkBitmapImageGetPixelRef(const SkImage* image) { + return ((const SkImage_Raster*)image)->getPixelRef(); +} + +bool SkImage_Raster::onAsLegacyBitmap(SkBitmap* bitmap) const { + // When we're a snapshot from a surface, our bitmap may not be marked immutable + // even though logically always we are, but in that case we can't physically share our + // pixelref since the caller might call setImmutable() themselves + // (thus changing our state). + if (fBitmap.isImmutable()) { + SkIPoint origin = fBitmap.pixelRefOrigin(); + bitmap->setInfo(fBitmap.info(), fBitmap.rowBytes()); + bitmap->setPixelRef(sk_ref_sp(fBitmap.pixelRef()), origin.x(), origin.y()); + return true; + } + return this->INHERITED::onAsLegacyBitmap(bitmap); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkImage_Raster::onMakeColorTypeAndColorSpace(GrRecordingContext*, + SkColorType targetCT, + sk_sp targetCS) const { + SkPixmap src; + SkAssertResult(fBitmap.peekPixels(&src)); + + SkBitmap dst; + dst.allocPixels(fBitmap.info().makeColorType(targetCT).makeColorSpace(targetCS)); + + SkAssertResult(dst.writePixels(src)); + dst.setImmutable(); + return SkImage::MakeFromBitmap(dst); +} + +sk_sp SkImage_Raster::onReinterpretColorSpace(sk_sp newCS) const { + // TODO: If our bitmap is immutable, then we could theoretically create another image sharing + // our pixelRef. That doesn't work (without more invasive logic), because the image gets its + // gen ID from the bitmap, which gets it from the pixelRef. + SkPixmap pixmap = fBitmap.pixmap(); + pixmap.setColorSpace(std::move(newCS)); + return SkImage::MakeRasterCopy(pixmap); +} diff --git a/gfx/skia/skia/src/image/SkReadPixelsRec.h b/gfx/skia/skia/src/image/SkReadPixelsRec.h new file mode 100644 index 0000000000..dd88d6c1c1 --- /dev/null +++ b/gfx/skia/skia/src/image/SkReadPixelsRec.h @@ -0,0 +1,50 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkReadPixelsRec_DEFINED +#define SkReadPixelsRec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" + +/** + * Helper class to package and trim the parameters passed to readPixels() + */ +struct SkReadPixelsRec { + SkReadPixelsRec(const SkImageInfo& info, void* pixels, size_t rowBytes, int x, int y) + : fPixels(pixels) + , fRowBytes(rowBytes) + , fInfo(info) + , fX(x) + , fY(y) + {} + + SkReadPixelsRec(const SkPixmap& pm, int x, int y) + : fPixels(pm.writable_addr()) + , fRowBytes(pm.rowBytes()) + , fInfo(pm.info()) + , fX(x) + , fY(y) + {} + + void* fPixels; + size_t fRowBytes; + SkImageInfo fInfo; + int fX; + int fY; + + /* + * On true, may have modified its fields (except fRowBytes) to make it a legal subset + * of the specified src width/height. + * + * On false, leaves self unchanged, but indicates that it does not overlap src, or + * is not valid (e.g. bad fInfo) for readPixels(). + */ + bool trim(int srcWidth, int srcHeight); +}; + +#endif diff --git a/gfx/skia/skia/src/image/SkSurface.cpp b/gfx/skia/skia/src/image/SkSurface.cpp new file mode 100644 index 0000000000..a04982dc51 --- /dev/null +++ b/gfx/skia/skia/src/image/SkSurface.cpp @@ -0,0 +1,643 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include "include/core/SkCanvas.h" +#include "include/core/SkFontLCDConfig.h" +#include "include/gpu/GrBackendSurface.h" +#include "src/core/SkAutoPixmapStorage.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/image/SkSurface_Base.h" + +static SkPixelGeometry compute_default_geometry() { + SkFontLCDConfig::LCDOrder order = SkFontLCDConfig::GetSubpixelOrder(); + if (SkFontLCDConfig::kNONE_LCDOrder == order) { + return kUnknown_SkPixelGeometry; + } else { + // Bit0 is RGB(0), BGR(1) + // Bit1 is H(0), V(1) + const SkPixelGeometry gGeo[] = { + kRGB_H_SkPixelGeometry, + kBGR_H_SkPixelGeometry, + kRGB_V_SkPixelGeometry, + kBGR_V_SkPixelGeometry, + }; + int index = 0; + if (SkFontLCDConfig::kBGR_LCDOrder == order) { + index |= 1; + } + if (SkFontLCDConfig::kVertical_LCDOrientation == SkFontLCDConfig::GetSubpixelOrientation()){ + index |= 2; + } + return gGeo[index]; + } +} + +SkSurfaceProps::SkSurfaceProps() : fFlags(0), fPixelGeometry(kUnknown_SkPixelGeometry) {} + +SkSurfaceProps::SkSurfaceProps(InitType) : fFlags(0), fPixelGeometry(compute_default_geometry()) {} + +SkSurfaceProps::SkSurfaceProps(uint32_t flags, InitType) + : fFlags(flags) + , fPixelGeometry(compute_default_geometry()) +{} + +SkSurfaceProps::SkSurfaceProps(uint32_t flags, SkPixelGeometry pg) + : fFlags(flags), fPixelGeometry(pg) +{} + +SkSurfaceProps::SkSurfaceProps(const SkSurfaceProps& other) + : fFlags(other.fFlags) + , fPixelGeometry(other.fPixelGeometry) +{} + +/////////////////////////////////////////////////////////////////////////////// + +SkSurface_Base::SkSurface_Base(int width, int height, const SkSurfaceProps* props) + : INHERITED(width, height, props) { +} + +SkSurface_Base::SkSurface_Base(const SkImageInfo& info, const SkSurfaceProps* props) + : INHERITED(info, props) { +} + +SkSurface_Base::~SkSurface_Base() { + // in case the canvas outsurvives us, we null the callback + if (fCachedCanvas) { + fCachedCanvas->setSurfaceBase(nullptr); + } +} + +GrBackendTexture SkSurface_Base::onGetBackendTexture(BackendHandleAccess) { + return GrBackendTexture(); // invalid +} + +GrBackendRenderTarget SkSurface_Base::onGetBackendRenderTarget(BackendHandleAccess) { + return GrBackendRenderTarget(); // invalid +} + +bool SkSurface_Base::onReplaceBackendTexture(const GrBackendTexture&, + GrSurfaceOrigin, + TextureReleaseProc, + ReleaseContext) { + return false; +} + +void SkSurface_Base::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) { + auto image = this->makeImageSnapshot(); + if (image) { + canvas->drawImage(image, x, y, paint); + } +} + +void SkSurface_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect, + SkSurface::RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + SkSurface::ReadPixelsCallback callback, + SkSurface::ReadPixelsContext context) { + int srcW = srcRect.width(); + int srcH = srcRect.height(); + float sx = (float)info.width() / srcW; + float sy = (float)info.height() / srcH; + // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling. + int stepsX; + int stepsY; + if (rescaleQuality > kNone_SkFilterQuality) { + stepsX = static_cast((sx > 1.f) ? std::ceil(std::log2f(sx)) + : std::floor(std::log2f(sx))); + stepsY = static_cast((sy > 1.f) ? std::ceil(std::log2f(sy)) + : std::floor(std::log2f(sy))); + } else { + stepsX = sx != 1.f; + stepsY = sy != 1.f; + } + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + if (stepsX < 0 || stepsY < 0) { + // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for + // downscaling draws. + rescaleQuality = std::min(rescaleQuality, kLow_SkFilterQuality); + } + paint.setFilterQuality(rescaleQuality); + sk_sp src(SkRef(this)); + int srcX = srcRect.fLeft; + int srcY = srcRect.fTop; + SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint; + // Assume we should ignore the rescale linear request if the surface has no color space since + // it's unclear how we'd linearize from an unknown color space. + if (rescaleGamma == SkSurface::RescaleGamma::kLinear && + this->getCanvas()->imageInfo().colorSpace() && + !this->getCanvas()->imageInfo().colorSpace()->gammaIsLinear()) { + auto cs = this->getCanvas()->imageInfo().colorSpace()->makeLinearGamma(); + // Promote to F16 color type to preserve precision. + auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType, + this->getCanvas()->imageInfo().alphaType(), std::move(cs)); + auto linearSurf = this->makeSurface(ii); + if (!linearSurf) { + // Maybe F16 isn't supported? Try again with original color type. + ii = ii.makeColorType(this->getCanvas()->imageInfo().colorType()); + linearSurf = this->makeSurface(ii); + if (!linearSurf) { + callback(context, nullptr); + return; + } + } + this->draw(linearSurf->getCanvas(), -srcX, -srcY, &paint); + src = std::move(linearSurf); + srcX = 0; + srcY = 0; + constraint = SkCanvas::kFast_SrcRectConstraint; + } + while (stepsX || stepsY) { + int nextW = info.width(); + int nextH = info.height(); + if (stepsX < 0) { + nextW = info.width() << (-stepsX - 1); + stepsX++; + } else if (stepsX != 0) { + if (stepsX > 1) { + nextW = srcW * 2; + } + --stepsX; + } + if (stepsY < 0) { + nextH = info.height() << (-stepsY - 1); + stepsY++; + } else if (stepsY != 0) { + if (stepsY > 1) { + nextH = srcH * 2; + } + --stepsY; + } + auto ii = src->getCanvas()->imageInfo().makeWH(nextW, nextH); + if (!stepsX && !stepsY) { + // Might as well fold conversion to final info in the last step. + ii = info; + } + auto next = this->makeSurface(ii); + if (!next) { + callback(context, nullptr); + return; + } + next->getCanvas()->drawImageRect( + src->makeImageSnapshot(), SkIRect::MakeXYWH(srcX, srcY, srcW, srcH), + SkRect::MakeWH((float)nextW, (float)nextH), &paint, constraint); + src = std::move(next); + srcX = srcY = 0; + srcW = nextW; + srcH = nextH; + constraint = SkCanvas::kFast_SrcRectConstraint; + } + + size_t rowBytes = info.minRowBytes(); + std::unique_ptr data(new char[info.height() * rowBytes]); + SkPixmap pm(info, data.get(), rowBytes); + if (src->readPixels(pm, srcX, srcY)) { + class Result : public AsyncReadResult { + public: + Result(std::unique_ptr data, size_t rowBytes) + : fData(std::move(data)), fRowBytes(rowBytes) {} + int count() const override { return 1; } + const void* data(int i) const override { return fData.get(); } + size_t rowBytes(int i) const override { return fRowBytes; } + + private: + std::unique_ptr fData; + size_t fRowBytes; + }; + callback(context, skstd::make_unique(std::move(data), rowBytes)); + } else { + callback(context, nullptr); + } +} + +void SkSurface_Base::onAsyncRescaleAndReadPixelsYUV420( + SkYUVColorSpace yuvColorSpace, sk_sp dstColorSpace, const SkIRect& srcRect, + const SkISize& dstSize, RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, ReadPixelsContext context) { + // TODO: Call non-YUV asyncRescaleAndReadPixels and then make our callback convert to YUV and + // call client's callback. + callback(context, nullptr); +} + +bool SkSurface_Base::outstandingImageSnapshot() const { + return fCachedImage && !fCachedImage->unique(); +} + +void SkSurface_Base::aboutToDraw(ContentChangeMode mode) { + this->dirtyGenerationID(); + + SkASSERT(!fCachedCanvas || fCachedCanvas->getSurfaceBase() == this); + + if (fCachedImage) { + // the surface may need to fork its backend, if its sharing it with + // the cached image. Note: we only call if there is an outstanding owner + // on the image (besides us). + bool unique = fCachedImage->unique(); + if (!unique) { + this->onCopyOnWrite(mode); + } + + // regardless of copy-on-write, we must drop our cached image now, so + // that the next request will get our new contents. + fCachedImage.reset(); + + if (unique) { + // Our content isn't held by any image now, so we can consider that content mutable. + // Raster surfaces need to be told it's safe to consider its pixels mutable again. + // We make this call after the ->unref() so the subclass can assert there are no images. + this->onRestoreBackingMutability(); + } + } else if (kDiscard_ContentChangeMode == mode) { + this->onDiscard(); + } +} + +uint32_t SkSurface_Base::newGenerationID() { + SkASSERT(!fCachedCanvas || fCachedCanvas->getSurfaceBase() == this); + static std::atomic nextID{1}; + return nextID++; +} + +static SkSurface_Base* asSB(SkSurface* surface) { + return static_cast(surface); +} + +static const SkSurface_Base* asConstSB(const SkSurface* surface) { + return static_cast(surface); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkSurface::SkSurface(int width, int height, const SkSurfaceProps* props) + : fProps(SkSurfacePropsCopyOrDefault(props)), fWidth(width), fHeight(height) +{ + SkASSERT(fWidth > 0); + SkASSERT(fHeight > 0); + fGenerationID = 0; +} + +SkSurface::SkSurface(const SkImageInfo& info, const SkSurfaceProps* props) + : fProps(SkSurfacePropsCopyOrDefault(props)), fWidth(info.width()), fHeight(info.height()) +{ + SkASSERT(fWidth > 0); + SkASSERT(fHeight > 0); + fGenerationID = 0; +} + +SkImageInfo SkSurface::imageInfo() { + // TODO: do we need to go through canvas for this? + return this->getCanvas()->imageInfo(); +} + +uint32_t SkSurface::generationID() { + if (0 == fGenerationID) { + fGenerationID = asSB(this)->newGenerationID(); + } + return fGenerationID; +} + +void SkSurface::notifyContentWillChange(ContentChangeMode mode) { + asSB(this)->aboutToDraw(mode); +} + +SkCanvas* SkSurface::getCanvas() { + return asSB(this)->getCachedCanvas(); +} + +sk_sp SkSurface::makeImageSnapshot() { + return asSB(this)->refCachedImage(); +} + +sk_sp SkSurface::makeImageSnapshot(const SkIRect& srcBounds) { + const SkIRect surfBounds = { 0, 0, fWidth, fHeight }; + SkIRect bounds = srcBounds; + if (!bounds.intersect(surfBounds)) { + return nullptr; + } + SkASSERT(!bounds.isEmpty()); + if (bounds == surfBounds) { + return this->makeImageSnapshot(); + } else { + return asSB(this)->onNewImageSnapshot(&bounds); + } +} + +sk_sp SkSurface::makeSurface(const SkImageInfo& info) { + return asSB(this)->onNewSurface(info); +} + +sk_sp SkSurface::makeSurface(int width, int height) { + return this->makeSurface(this->imageInfo().makeWH(width, height)); +} + +void SkSurface::draw(SkCanvas* canvas, SkScalar x, SkScalar y, + const SkPaint* paint) { + return asSB(this)->onDraw(canvas, x, y, paint); +} + +bool SkSurface::peekPixels(SkPixmap* pmap) { + return this->getCanvas()->peekPixels(pmap); +} + +bool SkSurface::readPixels(const SkPixmap& pm, int srcX, int srcY) { + return this->getCanvas()->readPixels(pm, srcX, srcY); +} + +bool SkSurface::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, + int srcX, int srcY) { + return this->readPixels({dstInfo, dstPixels, dstRowBytes}, srcX, srcY); +} + +bool SkSurface::readPixels(const SkBitmap& bitmap, int srcX, int srcY) { + SkPixmap pm; + return bitmap.peekPixels(&pm) && this->readPixels(pm, srcX, srcY); +} + +// Stuff to keep the legacy async readback APIs working on top of the new implementation. +namespace { +struct BridgeContext { + SkSurface::ReadPixelsContext fClientContext; + SkSurface::LegacyReadPixelsCallback* fClientCallback; +}; +struct BridgeContextYUV420 { + SkSurface::ReadPixelsContext fClientContext; + SkSurface::LegacyReadPixelsCallbackYUV420* fClientCallback; +}; +} // anonymous namespace + +static void bridge_callback(SkSurface::ReadPixelsContext context, + std::unique_ptr result) { + auto bridgeContext = static_cast(context); + if (!result || result->count() != 1) { + bridgeContext->fClientCallback(bridgeContext->fClientContext, nullptr, 0); + } else { + bridgeContext->fClientCallback(bridgeContext->fClientContext, result->data(0), + result->rowBytes(0)); + } + delete bridgeContext; +} + +static void bridge_callback_yuv420(SkSurface::ReadPixelsContext context, + std::unique_ptr result) { + auto bridgeContext = static_cast(context); + if (!result || result->count() != 3) { + bridgeContext->fClientCallback(bridgeContext->fClientContext, nullptr, 0); + } else { + const void* data[] = {result->data(0), result->data(1), result->data(2)}; + size_t rowBytes[] = {result->rowBytes(0), result->rowBytes(1), result->rowBytes(2)}; + bridgeContext->fClientCallback(bridgeContext->fClientContext, data, rowBytes); + } + delete bridgeContext; +} + +void SkSurface::asyncRescaleAndReadPixels(const SkImageInfo& info, + const SkIRect& srcRect, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + LegacyReadPixelsCallback callback, + ReadPixelsContext context) { + if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || + !SkImageInfoIsValid(info)) { + callback(context, nullptr, 0); + return; + } + + auto bridgeContext = new BridgeContext{context, callback}; + asSB(this)->onAsyncRescaleAndReadPixels(info, srcRect, rescaleGamma, rescaleQuality, + bridge_callback, bridgeContext); +} + +void SkSurface::asyncRescaleAndReadPixels(const SkImageInfo& info, + const SkIRect& srcRect, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) { + if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || + !SkImageInfoIsValid(info)) { + callback(context, nullptr); + return; + } + asSB(this)->onAsyncRescaleAndReadPixels( + info, srcRect, rescaleGamma, rescaleQuality, callback, context); +} + +void SkSurface::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + int dstW, int dstH, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + LegacyReadPixelsCallbackYUV420 callback, + ReadPixelsContext context) { + if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || (dstW & 0b1) || + (dstH & 0b1)) { + callback(context, nullptr, nullptr); + return; + } + auto bridgeContext = new BridgeContextYUV420{context, callback}; + asSB(this)->onAsyncRescaleAndReadPixelsYUV420(yuvColorSpace, + std::move(dstColorSpace), srcRect, + {dstW, dstH}, + rescaleGamma, + rescaleQuality, + bridge_callback_yuv420, + bridgeContext); +} + +void SkSurface::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) { + if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || dstSize.isZero() || + (dstSize.width() & 0b1) || (dstSize.height() & 0b1)) { + callback(context, nullptr); + return; + } + asSB(this)->onAsyncRescaleAndReadPixelsYUV420(yuvColorSpace, + std::move(dstColorSpace), + srcRect, + dstSize, + rescaleGamma, + rescaleQuality, + callback, + context); +} + +void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) { + if (pmap.addr() == nullptr || pmap.width() <= 0 || pmap.height() <= 0) { + return; + } + + const SkIRect srcR = SkIRect::MakeXYWH(x, y, pmap.width(), pmap.height()); + const SkIRect dstR = SkIRect::MakeWH(this->width(), this->height()); + if (SkIRect::Intersects(srcR, dstR)) { + ContentChangeMode mode = kRetain_ContentChangeMode; + if (srcR.contains(dstR)) { + mode = kDiscard_ContentChangeMode; + } + asSB(this)->aboutToDraw(mode); + asSB(this)->onWritePixels(pmap, x, y); + } +} + +void SkSurface::writePixels(const SkBitmap& src, int x, int y) { + SkPixmap pm; + if (src.peekPixels(&pm)) { + this->writePixels(pm, x, y); + } +} + +GrBackendTexture SkSurface::getBackendTexture(BackendHandleAccess access) { + return asSB(this)->onGetBackendTexture(access); +} + +GrBackendRenderTarget SkSurface::getBackendRenderTarget(BackendHandleAccess access) { + return asSB(this)->onGetBackendRenderTarget(access); +} + +bool SkSurface::replaceBackendTexture(const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext) { + return asSB(this)->onReplaceBackendTexture(backendTexture, origin, textureReleaseProc, + releaseContext); +} + +void SkSurface::flush() { + this->flush(BackendSurfaceAccess::kNoAccess, GrFlushInfo()); +} + +GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, const GrFlushInfo& flushInfo) { + return asSB(this)->onFlush(access, flushInfo); +} + +GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, GrFlushFlags flags, + int numSemaphores, GrBackendSemaphore signalSemaphores[], + GrGpuFinishedProc finishedProc, + GrGpuFinishedContext finishedContext) { + GrFlushInfo info; + info.fFlags = flags; + info.fNumSemaphores = numSemaphores; + info.fSignalSemaphores = signalSemaphores; + info.fFinishedProc = finishedProc; + info.fFinishedContext = finishedContext; + return this->flush(access, info); +} + +GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, FlushFlags flags, + int numSemaphores, GrBackendSemaphore signalSemaphores[]) { + GrFlushFlags grFlags = flags == kSyncCpu_FlushFlag ? kSyncCpu_GrFlushFlag : kNone_GrFlushFlags; + GrFlushInfo info; + info.fFlags = grFlags; + info.fNumSemaphores = numSemaphores; + info.fSignalSemaphores = signalSemaphores; + return this->flush(access, info); +} + +GrSemaphoresSubmitted SkSurface::flushAndSignalSemaphores(int numSemaphores, + GrBackendSemaphore signalSemaphores[]) { + GrFlushInfo info; + info.fNumSemaphores = numSemaphores; + info.fSignalSemaphores = signalSemaphores; + return this->flush(BackendSurfaceAccess::kNoAccess, info); +} + +bool SkSurface::wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) { + return asSB(this)->onWait(numSemaphores, waitSemaphores); +} + +bool SkSurface::characterize(SkSurfaceCharacterization* characterization) const { + return asConstSB(this)->onCharacterize(characterization); +} + +bool SkSurface::isCompatible(const SkSurfaceCharacterization& characterization) const { + return asConstSB(this)->onIsCompatible(characterization); +} + +bool SkSurface::draw(SkDeferredDisplayList* ddl) { + return asSB(this)->onDraw(ddl); +} + +////////////////////////////////////////////////////////////////////////////////////// +#include "include/utils/SkNoDrawCanvas.h" + +class SkNullSurface : public SkSurface_Base { +public: + SkNullSurface(int width, int height) : SkSurface_Base(width, height, nullptr) {} + +protected: + SkCanvas* onNewCanvas() override { + return new SkNoDrawCanvas(this->width(), this->height()); + } + sk_sp onNewSurface(const SkImageInfo& info) override { + return MakeNull(info.width(), info.height()); + } + sk_sp onNewImageSnapshot(const SkIRect* subsetOrNull) override { return nullptr; } + void onWritePixels(const SkPixmap&, int x, int y) override {} + void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {} + void onCopyOnWrite(ContentChangeMode) override {} +}; + +sk_sp SkSurface::MakeNull(int width, int height) { + if (width < 1 || height < 1) { + return nullptr; + } + return sk_sp(new SkNullSurface(width, height)); +} + +////////////////////////////////////////////////////////////////////////////////////// + +#if !SK_SUPPORT_GPU + +sk_sp SkSurface::MakeRenderTarget(GrContext*, SkBudgeted, const SkImageInfo&, int, + GrSurfaceOrigin, const SkSurfaceProps*, bool) { + return nullptr; +} + +sk_sp SkSurface::MakeRenderTarget(GrRecordingContext*, const SkSurfaceCharacterization&, + SkBudgeted) { + return nullptr; +} + +sk_sp SkSurface::MakeFromBackendTexture(GrContext*, const GrBackendTexture&, + GrSurfaceOrigin origin, int sampleCnt, + SkColorType, sk_sp, + const SkSurfaceProps*, + TextureReleaseProc, ReleaseContext) { + return nullptr; +} + +sk_sp SkSurface::MakeFromBackendRenderTarget(GrContext*, + const GrBackendRenderTarget&, + GrSurfaceOrigin origin, + SkColorType, + sk_sp, + const SkSurfaceProps*, + RenderTargetReleaseProc, ReleaseContext) { + return nullptr; +} + +sk_sp SkSurface::MakeFromBackendTextureAsRenderTarget(GrContext*, + const GrBackendTexture&, + GrSurfaceOrigin origin, + int sampleCnt, + SkColorType, + sk_sp, + const SkSurfaceProps*) { + return nullptr; +} + +#endif diff --git a/gfx/skia/skia/src/image/SkSurface_Base.h b/gfx/skia/skia/src/image/SkSurface_Base.h new file mode 100644 index 0000000000..7bff9a87e9 --- /dev/null +++ b/gfx/skia/skia/src/image/SkSurface_Base.h @@ -0,0 +1,169 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSurface_Base_DEFINED +#define SkSurface_Base_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkSurface.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkSurfacePriv.h" + +class SkSurface_Base : public SkSurface { +public: + SkSurface_Base(int width, int height, const SkSurfaceProps*); + SkSurface_Base(const SkImageInfo&, const SkSurfaceProps*); + virtual ~SkSurface_Base(); + + virtual GrBackendTexture onGetBackendTexture(BackendHandleAccess); + virtual GrBackendRenderTarget onGetBackendRenderTarget(BackendHandleAccess); + virtual bool onReplaceBackendTexture(const GrBackendTexture&, + GrSurfaceOrigin, + TextureReleaseProc, + ReleaseContext); + /** + * Allocate a canvas that will draw into this surface. We will cache this + * canvas, to return the same object to the caller multiple times. We + * take ownership, and will call unref() on the canvas when we go out of + * scope. + */ + virtual SkCanvas* onNewCanvas() = 0; + + virtual sk_sp onNewSurface(const SkImageInfo&) = 0; + + /** + * Allocate an SkImage that represents the current contents of the surface. + * This needs to be able to outlive the surface itself (if need be), and + * must faithfully represent the current contents, even if the surface + * is changed after this called (e.g. it is drawn to via its canvas). + * + * If a subset is specified, the the impl must make a copy, rather than try to wait + * on copy-on-write. + */ + virtual sk_sp onNewImageSnapshot(const SkIRect* subset = nullptr) { return nullptr; } + + virtual void onWritePixels(const SkPixmap&, int x, int y) = 0; + + /** + * Default implementation does a rescale/read and then calls the callback. + */ + virtual void onAsyncRescaleAndReadPixels(const SkImageInfo&, + const SkIRect& srcRect, + RescaleGamma, + SkFilterQuality, + ReadPixelsCallback, + ReadPixelsContext); + /** + * Default implementation does a rescale/read/yuv conversion and then calls the callback. + */ + virtual void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma, + SkFilterQuality, + ReadPixelsCallback, + ReadPixelsContext); + + /** + * Default implementation: + * + * image = this->newImageSnapshot(); + * if (image) { + * image->draw(canvas, ...); + * image->unref(); + * } + */ + virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*); + + /** + * Called as a performance hint when the Surface is allowed to make it's contents + * undefined. + */ + virtual void onDiscard() {} + + /** + * If the surface is about to change, we call this so that our subclass + * can optionally fork their backend (copy-on-write) in case it was + * being shared with the cachedImage. + */ + virtual void onCopyOnWrite(ContentChangeMode) = 0; + + /** + * Signal the surface to remind its backing store that it's mutable again. + * Called only when we _didn't_ copy-on-write; we assume the copies start mutable. + */ + virtual void onRestoreBackingMutability() {} + + /** + * Issue any pending surface IO to the current backend 3D API and resolve any surface MSAA. + * Inserts the requested number of semaphores for the gpu to signal when work is complete on the + * gpu and inits the array of GrBackendSemaphores with the signaled semaphores. + */ + virtual GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, const GrFlushInfo&) { + return GrSemaphoresSubmitted::kNo; + } + + /** + * Caused the current backend 3D API to wait on the passed in semaphores before executing new + * commands on the gpu. Any previously submitting commands will not be blocked by these + * semaphores. + */ + virtual bool onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) { + return false; + } + + virtual bool onCharacterize(SkSurfaceCharacterization*) const { return false; } + virtual bool onIsCompatible(const SkSurfaceCharacterization&) const { return false; } + virtual bool onDraw(const SkDeferredDisplayList*) { return false; } + + inline SkCanvas* getCachedCanvas(); + inline sk_sp refCachedImage(); + + bool hasCachedImage() const { return fCachedImage != nullptr; } + + // called by SkSurface to compute a new genID + uint32_t newGenerationID(); + +private: + std::unique_ptr fCachedCanvas; + sk_sp fCachedImage; + + void aboutToDraw(ContentChangeMode mode); + + // Returns true if there is an outstanding image-snapshot, indicating that a call to aboutToDraw + // would trigger a copy-on-write. + bool outstandingImageSnapshot() const; + + friend class SkCanvas; + friend class SkSurface; + + typedef SkSurface INHERITED; +}; + +SkCanvas* SkSurface_Base::getCachedCanvas() { + if (nullptr == fCachedCanvas) { + fCachedCanvas = std::unique_ptr(this->onNewCanvas()); + if (fCachedCanvas) { + fCachedCanvas->setSurfaceBase(this); + } + } + return fCachedCanvas.get(); +} + +sk_sp SkSurface_Base::refCachedImage() { + if (fCachedImage) { + return fCachedImage; + } + + fCachedImage = this->onNewImageSnapshot(); + + SkASSERT(!fCachedCanvas || fCachedCanvas->getSurfaceBase() == this); + return fCachedImage; +} + +#endif diff --git a/gfx/skia/skia/src/image/SkSurface_Gpu.cpp b/gfx/skia/skia/src/image/SkSurface_Gpu.cpp new file mode 100644 index 0000000000..623c54eab0 --- /dev/null +++ b/gfx/skia/skia/src/image/SkSurface_Gpu.cpp @@ -0,0 +1,741 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkSurfaceCharacterization.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrTexture.h" +#include "include/private/GrRecordingContext.h" +#include "include/private/SkDeferredDisplayList.h" +#include "src/core/SkImagePriv.h" +#include "src/gpu/GrAHardwareBufferUtils.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrContextThreadSafeProxyPriv.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/GrRenderTarget.h" +#include "src/gpu/GrRenderTargetContextPriv.h" +#include "src/gpu/GrRenderTargetProxyPriv.h" +#include "src/gpu/SkGpuDevice.h" +#include "src/image/SkImage_Base.h" +#include "src/image/SkImage_Gpu.h" +#include "src/image/SkSurface_Base.h" +#include "src/image/SkSurface_Gpu.h" + +#if SK_SUPPORT_GPU + +SkSurface_Gpu::SkSurface_Gpu(sk_sp device) + : INHERITED(device->width(), device->height(), &device->surfaceProps()) + , fDevice(std::move(device)) { + SkASSERT(fDevice->accessRenderTargetContext()->asSurfaceProxy()->priv().isExact()); +} + +SkSurface_Gpu::~SkSurface_Gpu() { +} + +static GrRenderTarget* prepare_rt_for_external_access(SkSurface_Gpu* surface, + SkSurface::BackendHandleAccess access) { + switch (access) { + case SkSurface::kFlushRead_BackendHandleAccess: + break; + case SkSurface::kFlushWrite_BackendHandleAccess: + case SkSurface::kDiscardWrite_BackendHandleAccess: + // for now we don't special-case on Discard, but we may in the future. + surface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode); + break; + } + + // Grab the render target *after* firing notifications, as it may get switched if CoW kicks in. + surface->getDevice()->flush(SkSurface::BackendSurfaceAccess::kNoAccess, GrFlushInfo()); + GrRenderTargetContext* rtc = surface->getDevice()->accessRenderTargetContext(); + return rtc->accessRenderTarget(); +} + +GrBackendTexture SkSurface_Gpu::onGetBackendTexture(BackendHandleAccess access) { + GrRenderTarget* rt = prepare_rt_for_external_access(this, access); + if (!rt) { + return GrBackendTexture(); // invalid + } + GrTexture* texture = rt->asTexture(); + if (texture) { + return texture->getBackendTexture(); + } + return GrBackendTexture(); // invalid +} + +GrBackendRenderTarget SkSurface_Gpu::onGetBackendRenderTarget(BackendHandleAccess access) { + GrRenderTarget* rt = prepare_rt_for_external_access(this, access); + if (!rt) { + return GrBackendRenderTarget(); // invalid + } + + return rt->getBackendRenderTarget(); +} + +SkCanvas* SkSurface_Gpu::onNewCanvas() { return new SkCanvas(fDevice); } + +sk_sp SkSurface_Gpu::onNewSurface(const SkImageInfo& info) { + int sampleCount = fDevice->accessRenderTargetContext()->numSamples(); + GrSurfaceOrigin origin = fDevice->accessRenderTargetContext()->origin(); + // TODO: Make caller specify this (change virtual signature of onNewSurface). + static const SkBudgeted kBudgeted = SkBudgeted::kNo; + return SkSurface::MakeRenderTarget(fDevice->context(), kBudgeted, info, sampleCount, + origin, &this->props()); +} + +sk_sp SkSurface_Gpu::onNewImageSnapshot(const SkIRect* subset) { + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + if (!rtc) { + return nullptr; + } + + GrContext* ctx = fDevice->context(); + + if (!rtc->asSurfaceProxy()) { + return nullptr; + } + + SkBudgeted budgeted = rtc->asSurfaceProxy()->isBudgeted(); + + sk_sp srcProxy = rtc->asTextureProxyRef(); + + if (subset) { + srcProxy = GrSurfaceProxy::Copy(ctx, rtc->asSurfaceProxy(), rtc->colorInfo().colorType(), + rtc->mipMapped(), *subset, SkBackingFit::kExact, budgeted); + } else if (!srcProxy || rtc->priv().refsWrappedObjects()) { + // If the original render target is a buffer originally created by the client, then we don't + // want to ever retarget the SkSurface at another buffer we create. Force a copy now to avoid + // copy-on-write. + SkASSERT(rtc->origin() == rtc->asSurfaceProxy()->origin()); + + srcProxy = GrSurfaceProxy::Copy(ctx, rtc->asSurfaceProxy(), rtc->colorInfo().colorType(), + rtc->mipMapped(), SkBackingFit::kExact, budgeted); + } + + const SkImageInfo info = fDevice->imageInfo(); + sk_sp image; + if (srcProxy) { + // The renderTargetContext coming out of SkGpuDevice should always be exact and the + // above copy creates a kExact surfaceContext. + SkASSERT(srcProxy->priv().isExact()); + image = sk_make_sp(sk_ref_sp(ctx), kNeedNewImageUniqueID, info.alphaType(), + std::move(srcProxy), info.refColorSpace()); + } + return image; +} + +void SkSurface_Gpu::onWritePixels(const SkPixmap& src, int x, int y) { + fDevice->writePixels(src, x, y); +} + +void SkSurface_Gpu::onAsyncRescaleAndReadPixels(const SkImageInfo& info, + const SkIRect& srcRect, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) { + auto* rtc = this->fDevice->accessRenderTargetContext(); + rtc->asyncRescaleAndReadPixels(info, srcRect, rescaleGamma, rescaleQuality, callback, context); +} + +void SkSurface_Gpu::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) { + auto* rtc = this->fDevice->accessRenderTargetContext(); + rtc->asyncRescaleAndReadPixelsYUV420(yuvColorSpace, + std::move(dstColorSpace), + srcRect, + dstSize, + rescaleGamma, + rescaleQuality, + callback, + context); +} + +// Create a new render target and, if necessary, copy the contents of the old +// render target into it. Note that this flushes the SkGpuDevice but +// doesn't force an OpenGL flush. +void SkSurface_Gpu::onCopyOnWrite(ContentChangeMode mode) { + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + + // are we sharing our backing proxy with the image? Note this call should never create a new + // image because onCopyOnWrite is only called when there is a cached image. + sk_sp image(this->refCachedImage()); + SkASSERT(image); + + GrSurfaceProxy* imageProxy = ((SkImage_Base*) image.get())->peekProxy(); + SkASSERT(imageProxy); + + if (rtc->asSurfaceProxy()->underlyingUniqueID() == imageProxy->underlyingUniqueID()) { + fDevice->replaceRenderTargetContext(SkSurface::kRetain_ContentChangeMode == mode); + } else if (kDiscard_ContentChangeMode == mode) { + this->SkSurface_Gpu::onDiscard(); + } +} + +void SkSurface_Gpu::onDiscard() { + fDevice->accessRenderTargetContext()->discard(); +} + +GrSemaphoresSubmitted SkSurface_Gpu::onFlush(BackendSurfaceAccess access, + const GrFlushInfo& info) { + return fDevice->flush(access, info); +} + +bool SkSurface_Gpu::onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) { + return fDevice->wait(numSemaphores, waitSemaphores); +} + +bool SkSurface_Gpu::onCharacterize(SkSurfaceCharacterization* characterization) const { + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + GrContext* ctx = fDevice->context(); + + size_t maxResourceBytes = ctx->getResourceCacheLimit(); + + bool mipmapped = rtc->asTextureProxy() ? GrMipMapped::kYes == rtc->asTextureProxy()->mipMapped() + : false; + + SkColorType ct = GrColorTypeToSkColorType(rtc->colorInfo().colorType()); + if (ct == kUnknown_SkColorType) { + return false; + } + + bool usesGLFBO0 = rtc->asRenderTargetProxy()->rtPriv().glRTFBOIDIs0(); + // We should never get in the situation where we have a texture render target that is also + // backend by FBO 0. + SkASSERT(!usesGLFBO0 || !SkToBool(rtc->asTextureProxy())); + + SkImageInfo ii = SkImageInfo::Make(rtc->width(), rtc->height(), ct, kPremul_SkAlphaType, + rtc->colorInfo().refColorSpace()); + + GrBackendFormat format = rtc->asSurfaceProxy()->backendFormat(); + + characterization->set(ctx->threadSafeProxy(), maxResourceBytes, ii, format, + rtc->origin(), rtc->numSamples(), + SkSurfaceCharacterization::Textureable(SkToBool(rtc->asTextureProxy())), + SkSurfaceCharacterization::MipMapped(mipmapped), + SkSurfaceCharacterization::UsesGLFBO0(usesGLFBO0), + SkSurfaceCharacterization::VulkanSecondaryCBCompatible(false), + GrProtected(rtc->asRenderTargetProxy()->isProtected()), + this->props()); + return true; +} + +void SkSurface_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) { + // If the dst is also GPU we try to not force a new image snapshot (by calling the base class + // onDraw) since that may not always perform the copy-on-write optimization. + auto tryDraw = [&] { + SkASSERT(fDevice->context()->priv().asDirectContext()); + GrContext* context = fDevice->context(); + GrContext* canvasContext = canvas->getGrContext(); + if (!canvasContext) { + return false; + } + if (!canvasContext->priv().asDirectContext() || + canvasContext->priv().contextID() != context->priv().contextID()) { + return false; + } + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + if (!rtc) { + return false; + } + sk_sp srcProxy = rtc->asTextureProxyRef(); + if (!srcProxy) { + return false; + } + // Possibly we could skip making an image here if SkGpuDevice exposed a lower level way + // of drawing a texture proxy. + const SkImageInfo info = fDevice->imageInfo(); + sk_sp image; + image = sk_make_sp(sk_ref_sp(context), kNeedNewImageUniqueID, info.alphaType(), + std::move(srcProxy), info.refColorSpace()); + canvas->drawImage(image, x, y, paint); + return true; + }; + if (!tryDraw()) { + INHERITED::onDraw(canvas, x, y, paint); + } +} + +bool SkSurface_Gpu::onIsCompatible(const SkSurfaceCharacterization& characterization) const { + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + GrContext* ctx = fDevice->context(); + + if (!characterization.isValid()) { + return false; + } + + if (characterization.vulkanSecondaryCBCompatible()) { + return false; + } + + // As long as the current state if the context allows for greater or equal resources, + // we allow the DDL to be replayed. + // DDL TODO: should we just remove the resource check and ignore the cache limits on playback? + size_t maxResourceBytes = ctx->getResourceCacheLimit(); + + if (characterization.isTextureable()) { + if (!rtc->asTextureProxy()) { + // If the characterization was textureable we require the replay dest to also be + // textureable. If the characterized surface wasn't textureable we allow the replay + // dest to be textureable. + return false; + } + + if (characterization.isMipMapped() && + GrMipMapped::kNo == rtc->asTextureProxy()->mipMapped()) { + // Fail if the DDL's surface was mipmapped but the replay surface is not. + // Allow drawing to proceed if the DDL was not mipmapped but the replay surface is. + return false; + } + } + + if (characterization.usesGLFBO0() != rtc->asRenderTargetProxy()->rtPriv().glRTFBOIDIs0()) { + return false; + } + + SkColorType rtcColorType = GrColorTypeToSkColorType(rtc->colorInfo().colorType()); + if (rtcColorType == kUnknown_SkColorType) { + return false; + } + + GrProtected isProtected = GrProtected(rtc->asSurfaceProxy()->isProtected()); + + return characterization.contextInfo() && characterization.contextInfo()->priv().matches(ctx) && + characterization.cacheMaxResourceBytes() <= maxResourceBytes && + characterization.origin() == rtc->origin() && + characterization.backendFormat() == rtc->asSurfaceProxy()->backendFormat() && + characterization.width() == rtc->width() && characterization.height() == rtc->height() && + characterization.colorType() == rtcColorType && + characterization.sampleCount() == rtc->numSamples() && + SkColorSpace::Equals(characterization.colorSpace(), rtc->colorInfo().colorSpace()) && + characterization.isProtected() == isProtected && + characterization.surfaceProps() == rtc->surfaceProps(); +} + +bool SkSurface_Gpu::onDraw(const SkDeferredDisplayList* ddl) { + if (!ddl || !this->isCompatible(ddl->characterization())) { + return false; + } + + GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext(); + GrContext* ctx = fDevice->context(); + + ctx->priv().copyRenderTasksFromDDL(ddl, rtc->asRenderTargetProxy()); + return true; +} + + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkSurface::MakeRenderTarget(GrRecordingContext* context, + const SkSurfaceCharacterization& c, + SkBudgeted budgeted) { + if (!context || !c.isValid()) { + return nullptr; + } + + if (c.usesGLFBO0()) { + // If we are making the surface we will never use FBO0. + return nullptr; + } + + if (c.vulkanSecondaryCBCompatible()) { + return nullptr; + } + + GrColorType grColorType = SkColorTypeToGrColorType(c.colorType()); + + auto rtc = context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, + c.width(), + c.height(), + grColorType, + c.refColorSpace(), + c.sampleCount(), + GrMipMapped(c.isMipMapped()), + c.origin(), + &c.surfaceProps(), + budgeted, + c.isProtected()); + if (!rtc) { + return nullptr; + } + + // CONTEXT TODO: remove this use of 'backdoor' to create an SkGpuDevice + sk_sp device(SkGpuDevice::Make(context->priv().backdoor(), std::move(rtc), + SkGpuDevice::kClear_InitContents)); + if (!device) { + return nullptr; + } + + sk_sp result = sk_make_sp(std::move(device)); +#ifdef SK_DEBUG + if (result) { + SkASSERT(result->isCompatible(c)); + } +#endif + + return result; +} + +static bool validate_backend_texture(const GrCaps* caps, const GrBackendTexture& tex, + int sampleCnt, GrColorType grCT, + bool texturable) { + if (!tex.isValid()) { + return false; + } + + GrBackendFormat backendFormat = tex.getBackendFormat(); + if (!backendFormat.isValid()) { + return false; + } + + if (!caps->areColorTypeAndFormatCompatible(grCT, backendFormat)) { + return false; + } + + if (!caps->isFormatAsColorTypeRenderable(grCT, backendFormat, sampleCnt)) { + return false; + } + + if (texturable && !caps->isFormatTexturable(backendFormat)) { + return false; + } + + return true; +} + +sk_sp SkSurface::MakeFromBackendTexture(GrContext* context, + const SkSurfaceCharacterization& c, + const GrBackendTexture& backendTexture, + TextureReleaseProc textureReleaseProc, + ReleaseContext releaseContext) { + if (!context || !c.isValid()) { + return nullptr; + } + + if (c.usesGLFBO0()) { + // If we are making the surface we will never use FBO0. + return nullptr; + } + + if (!c.isCompatible(backendTexture)) { + return nullptr; + } + + GrColorType grCT = SkColorTypeAndFormatToGrColorType(context->priv().caps(), c.colorType(), + backendTexture.getBackendFormat()); + if (grCT == GrColorType::kUnknown) { + return nullptr; + } + + if (!validate_backend_texture(context->priv().caps(), backendTexture, + c.sampleCount(), grCT, true)) { + return nullptr; + } + + auto rtc = context->priv().makeBackendTextureRenderTargetContext( + backendTexture, c.origin(), c.sampleCount(), grCT, c.refColorSpace(), &c.surfaceProps(), + textureReleaseProc, releaseContext); + if (!rtc) { + return nullptr; + } + + auto device = SkGpuDevice::Make(context, std::move(rtc), SkGpuDevice::kUninit_InitContents); + if (!device) { + return nullptr; + } + + sk_sp result = sk_make_sp(std::move(device)); +#ifdef SK_DEBUG + if (result) { + SkASSERT(result->isCompatible(c)); + } +#endif + + return result; +} + +sk_sp SkSurface::MakeRenderTarget(GrContext* ctx, SkBudgeted budgeted, + const SkImageInfo& info, int sampleCount, + GrSurfaceOrigin origin, const SkSurfaceProps* props, + bool shouldCreateWithMips) { + if (!ctx) { + return nullptr; + } + sampleCount = SkTMax(1, sampleCount); + GrMipMapped mipMapped = shouldCreateWithMips ? GrMipMapped::kYes : GrMipMapped::kNo; + + if (!ctx->priv().caps()->mipMapSupport()) { + mipMapped = GrMipMapped::kNo; + } + + sk_sp device(SkGpuDevice::Make( + ctx, budgeted, info, sampleCount, origin, props, mipMapped, + SkGpuDevice::kClear_InitContents)); + if (!device) { + return nullptr; + } + return sk_make_sp(std::move(device)); +} + +sk_sp SkSurface_Gpu::MakeWrappedRenderTarget( + GrContext* context, std::unique_ptr rtc) { + if (!context) { + return nullptr; + } + + auto device = SkGpuDevice::Make(context, std::move(rtc), SkGpuDevice::kUninit_InitContents); + if (!device) { + return nullptr; + } + + return sk_make_sp(std::move(device)); +} + +sk_sp SkSurface::MakeFromBackendTexture(GrContext* context, const GrBackendTexture& tex, + GrSurfaceOrigin origin, int sampleCnt, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props, + SkSurface::TextureReleaseProc textureReleaseProc, + SkSurface::ReleaseContext releaseContext) { + if (!context) { + return nullptr; + } + sampleCnt = SkTMax(1, sampleCnt); + + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(context->priv().caps(), colorType, + tex.getBackendFormat()); + if (grColorType == GrColorType::kUnknown) { + return nullptr; + } + + if (!validate_backend_texture(context->priv().caps(), tex, sampleCnt, grColorType, true)) { + return nullptr; + } + + auto rtc = context->priv().makeBackendTextureRenderTargetContext( + tex, origin, sampleCnt, grColorType, std::move(colorSpace), props, textureReleaseProc, + releaseContext); + if (!rtc) { + return nullptr; + } + + auto device = SkGpuDevice::Make(context, std::move(rtc), SkGpuDevice::kUninit_InitContents); + if (!device) { + return nullptr; + } + return sk_make_sp(std::move(device)); +} + +bool SkSurface_Gpu::onReplaceBackendTexture(const GrBackendTexture& backendTexture, + GrSurfaceOrigin origin, TextureReleaseProc releaseProc, + ReleaseContext releaseContext) { + auto context = this->fDevice->context(); + if (context->abandoned()) { + return false; + } + if (!backendTexture.isValid()) { + return false; + } + if (backendTexture.width() != this->width() || backendTexture.height() != this->height()) { + return false; + } + auto* oldRTC = fDevice->accessRenderTargetContext(); + auto oldProxy = sk_ref_sp(oldRTC->asTextureProxy()); + if (!oldProxy) { + return false; + } + auto* oldTexture = oldProxy->peekTexture(); + if (!oldTexture) { + return false; + } + if (!oldTexture->resourcePriv().refsWrappedObjects()) { + return false; + } + if (oldTexture->backendFormat() != backendTexture.getBackendFormat()) { + return false; + } + if (oldTexture->getBackendTexture().isSameTexture(backendTexture)) { + return false; + } + SkASSERT(oldTexture->asRenderTarget()); + int sampleCnt = oldTexture->asRenderTarget()->numSamples(); + GrColorType grColorType = SkColorTypeToGrColorType(this->getCanvas()->imageInfo().colorType()); + auto colorSpace = sk_ref_sp(oldRTC->colorInfo().colorSpace()); + if (!validate_backend_texture(context->priv().caps(), backendTexture, + sampleCnt, grColorType, true)) { + return false; + } + auto rtc = + context->priv().makeBackendTextureRenderTargetContext(backendTexture, + origin, + sampleCnt, + oldRTC->colorInfo().colorType(), + std::move(colorSpace), + &this->props(), + releaseProc, + releaseContext); + if (!rtc) { + return false; + } + fDevice->replaceRenderTargetContext(std::move(rtc), true); + return true; +} + +bool validate_backend_render_target(const GrCaps* caps, const GrBackendRenderTarget& rt, + GrColorType grCT) { + if (!caps->areColorTypeAndFormatCompatible(grCT, rt.getBackendFormat())) { + return false; + } + + if (!caps->isFormatAsColorTypeRenderable(grCT, rt.getBackendFormat(), rt.sampleCnt())) { + return false; + } + return true; +} + +sk_sp SkSurface::MakeFromBackendRenderTarget(GrContext* context, + const GrBackendRenderTarget& rt, + GrSurfaceOrigin origin, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props, + SkSurface::RenderTargetReleaseProc relProc, + SkSurface::ReleaseContext releaseContext) { + if (!context) { + return nullptr; + } + + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(context->priv().caps(), colorType, + rt.getBackendFormat()); + if (grColorType == GrColorType::kUnknown) { + return nullptr; + } + + if (!validate_backend_render_target(context->priv().caps(), rt, grColorType)) { + return nullptr; + } + + auto rtc = context->priv().makeBackendRenderTargetRenderTargetContext( + rt, origin, grColorType, std::move(colorSpace), props, relProc, releaseContext); + if (!rtc) { + return nullptr; + } + + auto device = SkGpuDevice::Make(context, std::move(rtc), SkGpuDevice::kUninit_InitContents); + if (!device) { + return nullptr; + } + + return sk_make_sp(std::move(device)); +} + +sk_sp SkSurface::MakeFromBackendTextureAsRenderTarget(GrContext* context, + const GrBackendTexture& tex, + GrSurfaceOrigin origin, + int sampleCnt, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* props) { + if (!context) { + return nullptr; + } + + sampleCnt = SkTMax(1, sampleCnt); + GrColorType grColorType = SkColorTypeAndFormatToGrColorType(context->priv().caps(), colorType, + tex.getBackendFormat()); + if (grColorType == GrColorType::kUnknown) { + return nullptr; + } + if (!validate_backend_texture(context->priv().caps(), tex, sampleCnt, grColorType, false)) { + return nullptr; + } + + auto rtc = context->priv().makeBackendTextureAsRenderTargetRenderTargetContext( + tex, origin, sampleCnt, grColorType, std::move(colorSpace), props); + if (!rtc) { + return nullptr; + } + + auto device = SkGpuDevice::Make(context, std::move(rtc), SkGpuDevice::kUninit_InitContents); + if (!device) { + return nullptr; + } + return sk_make_sp(std::move(device)); +} + +#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 +sk_sp SkSurface::MakeFromAHardwareBuffer(GrContext* context, + AHardwareBuffer* hardwareBuffer, + GrSurfaceOrigin origin, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps) { + AHardwareBuffer_Desc bufferDesc; + AHardwareBuffer_describe(hardwareBuffer, &bufferDesc); + + if (!SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT)) { + return nullptr; + } + + bool isTextureable = SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE); + bool isProtectedContent = SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); + + // We currently don't support protected content + if (isProtectedContent) { + SkDebugf("We currently don't support protected content on android\n"); + return nullptr; + } + + GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(context, + hardwareBuffer, + bufferDesc.format, + true); + if (!backendFormat.isValid()) { + return nullptr; + } + + if (isTextureable) { + GrAHardwareBufferUtils::DeleteImageProc deleteImageProc = nullptr; + GrAHardwareBufferUtils::UpdateImageProc updateImageProc = nullptr; + GrAHardwareBufferUtils::TexImageCtx deleteImageCtx = nullptr; + + GrBackendTexture backendTexture = + GrAHardwareBufferUtils::MakeBackendTexture(context, hardwareBuffer, + bufferDesc.width, bufferDesc.height, + &deleteImageProc, &updateImageProc, + &deleteImageCtx, isProtectedContent, + backendFormat, true); + if (!backendTexture.isValid()) { + return nullptr; + } + + SkColorType colorType = + GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(bufferDesc.format); + + sk_sp surface = SkSurface::MakeFromBackendTexture(context, backendTexture, + origin, 0, colorType, std::move(colorSpace), surfaceProps, deleteImageProc, + deleteImageCtx); + + if (!surface) { + SkASSERT(deleteImageProc); + deleteImageProc(deleteImageCtx); + } + return surface; + } else { + return nullptr; + } +} +#endif + +#endif diff --git a/gfx/skia/skia/src/image/SkSurface_Gpu.h b/gfx/skia/skia/src/image/SkSurface_Gpu.h new file mode 100644 index 0000000000..e27e7e5df1 --- /dev/null +++ b/gfx/skia/skia/src/image/SkSurface_Gpu.h @@ -0,0 +1,69 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSurface_Gpu_DEFINED +#define SkSurface_Gpu_DEFINED + +#include "include/private/GrTypesPriv.h" +#include "src/image/SkSurface_Base.h" + +#if SK_SUPPORT_GPU + +class GrBackendFormat; +class SkGpuDevice; + +class SkSurface_Gpu : public SkSurface_Base { +public: + SkSurface_Gpu(sk_sp); + ~SkSurface_Gpu() override; + + // This is an internal-only factory + static sk_sp MakeWrappedRenderTarget(GrContext*, + std::unique_ptr); + + GrBackendTexture onGetBackendTexture(BackendHandleAccess) override; + GrBackendRenderTarget onGetBackendRenderTarget(BackendHandleAccess) override; + bool onReplaceBackendTexture(const GrBackendTexture&, GrSurfaceOrigin, TextureReleaseProc, + ReleaseContext) override; + + SkCanvas* onNewCanvas() override; + sk_sp onNewSurface(const SkImageInfo&) override; + sk_sp onNewImageSnapshot(const SkIRect* subset) override; + void onWritePixels(const SkPixmap&, int x, int y) override; + void onAsyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect, + RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) override; + void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, + sk_sp dstColorSpace, + const SkIRect& srcRect, + const SkISize& dstSize, + RescaleGamma rescaleGamma, + SkFilterQuality rescaleQuality, + ReadPixelsCallback callback, + ReadPixelsContext context) override; + + void onCopyOnWrite(ContentChangeMode) override; + void onDiscard() override; + GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, const GrFlushInfo& info) override; + bool onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) override; + bool onCharacterize(SkSurfaceCharacterization*) const override; + bool onIsCompatible(const SkSurfaceCharacterization&) const override; + void onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) override; + bool onDraw(const SkDeferredDisplayList*) override; + + SkGpuDevice* getDevice() { return fDevice.get(); } + +private: + sk_sp fDevice; + + typedef SkSurface_Base INHERITED; +}; + +#endif // SK_SUPPORT_GPU + +#endif // SkSurface_Gpu_DEFINED diff --git a/gfx/skia/skia/src/image/SkSurface_GpuMtl.mm b/gfx/skia/skia/src/image/SkSurface_GpuMtl.mm new file mode 100644 index 0000000000..dc410886f1 --- /dev/null +++ b/gfx/skia/skia/src/image/SkSurface_GpuMtl.mm @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkSurface.h" +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrContext.h" +#include "include/gpu/mtl/GrMtlTypes.h" + +#if SK_SUPPORT_GPU + +#ifdef SK_METAL +#import +#import + +sk_sp SkSurface::MakeFromCAMetalLayer(GrContext* context, + GrMTLHandle layer, + GrSurfaceOrigin origin, + int sampleCnt, + SkColorType colorType, + sk_sp colorSpace, + const SkSurfaceProps* surfaceProps, + GrMTLHandle* drawable) { + // TODO: Apple recommends grabbing the drawable (which we're implicitly doing here) + // for as little time as possible. I'm not sure it matters for our test apps, but + // you can get better throughput by doing any offscreen renders, texture uploads, or + // other non-dependant tasks first before grabbing the drawable. + CAMetalLayer* metalLayer = (__bridge CAMetalLayer*)layer; + id currentDrawable = [metalLayer nextDrawable]; + + GrMtlTextureInfo fbInfo; + fbInfo.fTexture.retain((__bridge const void*)(currentDrawable.texture)); + + CGSize size = [metalLayer drawableSize]; + sk_sp surface; + if (sampleCnt <= 1) { + GrBackendRenderTarget backendRT(size.width, + size.height, + sampleCnt, + fbInfo); + + surface = SkSurface::MakeFromBackendRenderTarget(context, backendRT, origin, colorType, + colorSpace, surfaceProps); + } else { + GrBackendTexture backendTexture(size.width, + size.height, + GrMipMapped::kNo, + fbInfo); + + surface = SkSurface::MakeFromBackendTexture(context, backendTexture, origin, sampleCnt, + colorType, colorSpace, surfaceProps); + } + *drawable = (__bridge_retained GrMTLHandle) currentDrawable; + + return surface; +} +#endif + +#endif diff --git a/gfx/skia/skia/src/image/SkSurface_Raster.cpp b/gfx/skia/skia/src/image/SkSurface_Raster.cpp new file mode 100644 index 0000000000..a086165ea4 --- /dev/null +++ b/gfx/skia/skia/src/image/SkSurface_Raster.cpp @@ -0,0 +1,214 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkMallocPixelRef.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/core/SkDevice.h" +#include "src/core/SkImagePriv.h" +#include "src/image/SkSurface_Base.h" + +class SkSurface_Raster : public SkSurface_Base { +public: + SkSurface_Raster(const SkImageInfo&, void*, size_t rb, + void (*releaseProc)(void* pixels, void* context), void* context, + const SkSurfaceProps*); + SkSurface_Raster(const SkImageInfo& info, sk_sp, const SkSurfaceProps*); + + SkCanvas* onNewCanvas() override; + sk_sp onNewSurface(const SkImageInfo&) override; + sk_sp onNewImageSnapshot(const SkIRect* subset) override; + void onWritePixels(const SkPixmap&, int x, int y) override; + void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override; + void onCopyOnWrite(ContentChangeMode) override; + void onRestoreBackingMutability() override; + +private: + SkBitmap fBitmap; + size_t fRowBytes; + bool fWeOwnThePixels; + + typedef SkSurface_Base INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +bool SkSurfaceValidateRasterInfo(const SkImageInfo& info, size_t rowBytes) { + if (!SkImageInfoIsValid(info)) { + return false; + } + + if (info.colorType() == kR8G8_unorm_SkColorType || + info.colorType() == kR16G16_unorm_SkColorType || + info.colorType() == kR16G16_float_SkColorType || + info.colorType() == kA16_unorm_SkColorType || + info.colorType() == kA16_float_SkColorType || + info.colorType() == kR16G16B16A16_unorm_SkColorType) { + return false; + } + + if (kIgnoreRowBytesValue == rowBytes) { + return true; + } + + int shift = info.shiftPerPixel(); + + uint64_t minRB = (uint64_t)info.width() << shift; + if (minRB > rowBytes) { + return false; + } + + size_t alignedRowBytes = rowBytes >> shift << shift; + if (alignedRowBytes != rowBytes) { + return false; + } + + uint64_t size = sk_64_mul(info.height(), rowBytes); + static const size_t kMaxTotalSize = SK_MaxS32; + if (size > kMaxTotalSize) { + return false; + } + + return true; +} + +SkSurface_Raster::SkSurface_Raster(const SkImageInfo& info, void* pixels, size_t rb, + void (*releaseProc)(void* pixels, void* context), void* context, + const SkSurfaceProps* props) + : INHERITED(info, props) +{ + fBitmap.installPixels(info, pixels, rb, releaseProc, context); + fRowBytes = 0; // don't need to track the rowbytes + fWeOwnThePixels = false; // We are "Direct" +} + +SkSurface_Raster::SkSurface_Raster(const SkImageInfo& info, sk_sp pr, + const SkSurfaceProps* props) + : INHERITED(pr->width(), pr->height(), props) +{ + fBitmap.setInfo(info, pr->rowBytes()); + fRowBytes = pr->rowBytes(); // we track this, so that subsequent re-allocs will match + fBitmap.setPixelRef(std::move(pr), 0, 0); + fWeOwnThePixels = true; +} + +SkCanvas* SkSurface_Raster::onNewCanvas() { return new SkCanvas(fBitmap, this->props()); } + +sk_sp SkSurface_Raster::onNewSurface(const SkImageInfo& info) { + return SkSurface::MakeRaster(info, &this->props()); +} + +void SkSurface_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, + const SkPaint* paint) { + canvas->drawBitmap(fBitmap, x, y, paint); +} + +sk_sp SkSurface_Raster::onNewImageSnapshot(const SkIRect* subset) { + if (subset) { + SkASSERT(SkIRect::MakeWH(fBitmap.width(), fBitmap.height()).contains(*subset)); + SkBitmap dst; + dst.allocPixels(fBitmap.info().makeDimensions(subset->size())); + SkAssertResult(fBitmap.readPixels(dst.pixmap(), subset->left(), subset->top())); + dst.setImmutable(); // key, so MakeFromBitmap doesn't make a copy of the buffer + return SkImage::MakeFromBitmap(dst); + } + + SkCopyPixelsMode cpm = kIfMutable_SkCopyPixelsMode; + if (fWeOwnThePixels) { + // SkImage_raster requires these pixels are immutable for its full lifetime. + // We'll undo this via onRestoreBackingMutability() if we can avoid the COW. + if (SkPixelRef* pr = fBitmap.pixelRef()) { + pr->setTemporarilyImmutable(); + } + } else { + cpm = kAlways_SkCopyPixelsMode; + } + + // Our pixels are in memory, so read access on the snapshot SkImage could be cheap. + // Lock the shared pixel ref to ensure peekPixels() is usable. + return SkMakeImageFromRasterBitmap(fBitmap, cpm); +} + +void SkSurface_Raster::onWritePixels(const SkPixmap& src, int x, int y) { + fBitmap.writePixels(src, x, y); +} + +void SkSurface_Raster::onRestoreBackingMutability() { + SkASSERT(!this->hasCachedImage()); // Shouldn't be any snapshots out there. + if (SkPixelRef* pr = fBitmap.pixelRef()) { + pr->restoreMutability(); + } +} + +void SkSurface_Raster::onCopyOnWrite(ContentChangeMode mode) { + // are we sharing pixelrefs with the image? + sk_sp cached(this->refCachedImage()); + SkASSERT(cached); + if (SkBitmapImageGetPixelRef(cached.get()) == fBitmap.pixelRef()) { + SkASSERT(fWeOwnThePixels); + if (kDiscard_ContentChangeMode == mode) { + fBitmap.allocPixels(); + } else { + SkBitmap prev(fBitmap); + fBitmap.allocPixels(); + SkASSERT(prev.info() == fBitmap.info()); + SkASSERT(prev.rowBytes() == fBitmap.rowBytes()); + memcpy(fBitmap.getPixels(), prev.getPixels(), fBitmap.computeByteSize()); + } + SkASSERT(fBitmap.rowBytes() == fRowBytes); // be sure we always use the same value + + // Now fBitmap is a deep copy of itself (and therefore different from + // what is being used by the image. Next we update the canvas to use + // this as its backend, so we can't modify the image's pixels anymore. + SkASSERT(this->getCachedCanvas()); + this->getCachedCanvas()->getDevice()->replaceBitmapBackendForRasterSurface(fBitmap); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkSurface::MakeRasterDirectReleaseProc(const SkImageInfo& info, void* pixels, + size_t rb, void (*releaseProc)(void* pixels, void* context), void* context, + const SkSurfaceProps* props) { + if (nullptr == releaseProc) { + context = nullptr; + } + if (!SkSurfaceValidateRasterInfo(info, rb)) { + return nullptr; + } + if (nullptr == pixels) { + return nullptr; + } + + return sk_make_sp(info, pixels, rb, releaseProc, context, props); +} + +sk_sp SkSurface::MakeRasterDirect(const SkImageInfo& info, void* pixels, size_t rowBytes, + const SkSurfaceProps* props) { + return MakeRasterDirectReleaseProc(info, pixels, rowBytes, nullptr, nullptr, props); +} + +sk_sp SkSurface::MakeRaster(const SkImageInfo& info, size_t rowBytes, + const SkSurfaceProps* props) { + if (!SkSurfaceValidateRasterInfo(info)) { + return nullptr; + } + + sk_sp pr = SkMallocPixelRef::MakeAllocate(info, rowBytes); + if (!pr) { + return nullptr; + } + if (rowBytes) { + SkASSERT(pr->rowBytes() == rowBytes); + } + return sk_make_sp(info, std::move(pr), props); +} + +sk_sp SkSurface::MakeRasterN32Premul(int width, int height, + const SkSurfaceProps* surfaceProps) { + return MakeRaster(SkImageInfo::MakeN32Premul(width, height), surfaceProps); +} diff --git a/gfx/skia/skia/src/images/SkImageEncoder.cpp b/gfx/skia/skia/src/images/SkImageEncoder.cpp new file mode 100644 index 0000000000..101b34f505 --- /dev/null +++ b/gfx/skia/skia/src/images/SkImageEncoder.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/encode/SkJpegEncoder.h" +#include "include/encode/SkPngEncoder.h" +#include "include/encode/SkWebpEncoder.h" +#include "src/images/SkImageEncoderPriv.h" + +#ifndef SK_HAS_JPEG_LIBRARY +bool SkJpegEncoder::Encode(SkWStream*, const SkPixmap&, const Options&) { return false; } +std::unique_ptr SkJpegEncoder::Make(SkWStream*, const SkPixmap&, const Options&) { + return nullptr; +} +#endif + +#ifndef SK_HAS_PNG_LIBRARY +bool SkPngEncoder::Encode(SkWStream*, const SkPixmap&, const Options&) { return false; } +std::unique_ptr SkPngEncoder::Make(SkWStream*, const SkPixmap&, const Options&) { + return nullptr; +} +#endif + +#ifndef SK_HAS_WEBP_LIBRARY +bool SkWebpEncoder::Encode(SkWStream*, const SkPixmap&, const Options&) { return false; } +#endif + +bool SkEncodeImage(SkWStream* dst, const SkPixmap& src, + SkEncodedImageFormat format, int quality) { + #ifdef SK_USE_CG_ENCODER + (void)quality; + return SkEncodeImageWithCG(dst, src, format); + #elif SK_USE_WIC_ENCODER + return SkEncodeImageWithWIC(dst, src, format, quality); + #else + switch(format) { + case SkEncodedImageFormat::kJPEG: { + SkJpegEncoder::Options opts; + opts.fQuality = quality; + return SkJpegEncoder::Encode(dst, src, opts); + } + case SkEncodedImageFormat::kPNG: { + SkPngEncoder::Options opts; + return SkPngEncoder::Encode(dst, src, opts); + } + case SkEncodedImageFormat::kWEBP: { + SkWebpEncoder::Options opts; + if (quality == 100) { + opts.fCompression = SkWebpEncoder::Compression::kLossless; + // Note: SkEncodeImage treats 0 quality as the lowest quality + // (greatest compression) and 100 as the highest quality (least + // compression). For kLossy, this matches libwebp's + // interpretation, so it is passed directly to libwebp. But + // with kLossless, libwebp always creates the highest quality + // image. In this case, fQuality is reinterpreted as how much + // effort (time) to put into making a smaller file. This API + // does not provide a way to specify this value (though it can + // be specified by using SkWebpEncoder::Encode) so we have to + // pick one arbitrarily. This value matches that chosen by + // blink::ImageEncoder::ComputeWebpOptions as well + // WebPConfigInit. + opts.fQuality = 75; + } else { + opts.fCompression = SkWebpEncoder::Compression::kLossy; + opts.fQuality = quality; + } + return SkWebpEncoder::Encode(dst, src, opts); + } + default: + return false; + } + #endif +} + +bool SkEncoder::encodeRows(int numRows) { + SkASSERT(numRows > 0 && fCurrRow < fSrc.height()); + if (numRows <= 0 || fCurrRow >= fSrc.height()) { + return false; + } + + if (fCurrRow + numRows > fSrc.height()) { + numRows = fSrc.height() - fCurrRow; + } + + if (!this->onEncodeRows(numRows)) { + // If we fail, short circuit any future calls. + fCurrRow = fSrc.height(); + return false; + } + + return true; +} + +sk_sp SkEncodePixmap(const SkPixmap& src, SkEncodedImageFormat format, int quality) { + SkDynamicMemoryWStream stream; + return SkEncodeImage(&stream, src, format, quality) ? stream.detachAsData() : nullptr; +} + +sk_sp SkEncodeBitmap(const SkBitmap& src, SkEncodedImageFormat format, int quality) { + SkPixmap pixmap; + return src.peekPixels(&pixmap) ? SkEncodePixmap(pixmap, format, quality) : nullptr; +} diff --git a/gfx/skia/skia/src/images/SkImageEncoderFns.h b/gfx/skia/skia/src/images/SkImageEncoderFns.h new file mode 100644 index 0000000000..42b9dd7c79 --- /dev/null +++ b/gfx/skia/skia/src/images/SkImageEncoderFns.h @@ -0,0 +1,178 @@ +/* + * Copyright 2012 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageEncoderFns_DEFINED +#define SkImageEncoderFns_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkICC.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/third_party/skcms/skcms.h" + +typedef void (*transform_scanline_proc)(char* dst, const char* src, int width, int bpp); + +static inline void transform_scanline_memcpy(char* dst, const char* src, int width, int bpp) { + memcpy(dst, src, width * bpp); +} + +static inline void transform_scanline_A8_to_GrayAlpha(char* dst, const char* src, int width, int) { + for (int i = 0; i < width; i++) { + *dst++ = 0; + *dst++ = *src++; + } +} + + +static void skcms(char* dst, const char* src, int n, + skcms_PixelFormat srcFmt, skcms_AlphaFormat srcAlpha, + skcms_PixelFormat dstFmt, skcms_AlphaFormat dstAlpha) { + SkAssertResult(skcms_Transform(src, srcFmt, srcAlpha, nullptr, + dst, dstFmt, dstAlpha, nullptr, n)); +} + +static inline void transform_scanline_gray(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_G_8, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_565(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_BGR_565, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_RGBX(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGB_888 , skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_BGRX(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGB_888 , skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_444(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_ABGR_4444, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGB_888 , skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_rgbA(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_bgrA(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_to_premul_legacy(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_PremulAsEncoded); +} + +static inline void transform_scanline_BGRA(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_4444(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_ABGR_4444, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_101010x(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_1010102, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGB_161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_1010102(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_1010102, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_1010102_premul(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_1010102, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_F16(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_F16_premul(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_F16_to_8888(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_F16_premul_to_8888(char* dst, + const char* src, + int width, + int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_F16_to_premul_8888(char* dst, + const char* src, + int width, + int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_PremulAsEncoded); +} + +static inline void transform_scanline_F32(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul, + skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline void transform_scanline_F32_premul(char* dst, const char* src, int width, int) { + skcms(dst, src, width, + skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_PremulAsEncoded, + skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul); +} + +static inline sk_sp icc_from_color_space(const SkImageInfo& info) { + SkColorSpace* cs = info.colorSpace(); + if (!cs) { + return nullptr; + } + + skcms_TransferFunction fn; + skcms_Matrix3x3 toXYZD50; + if (cs->isNumericalTransferFn(&fn) && cs->toXYZD50(&toXYZD50)) { + return SkWriteICCProfile(fn, toXYZD50); + } + return nullptr; +} + +#endif // SkImageEncoderFns_DEFINED diff --git a/gfx/skia/skia/src/images/SkImageEncoderPriv.h b/gfx/skia/skia/src/images/SkImageEncoderPriv.h new file mode 100644 index 0000000000..67ede545b9 --- /dev/null +++ b/gfx/skia/skia/src/images/SkImageEncoderPriv.h @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageEncoderPriv_DEFINED +#define SkImageEncoderPriv_DEFINED + +#include "include/core/SkImageEncoder.h" +#include "include/private/SkImageInfoPriv.h" + +static inline bool SkPixmapIsValid(const SkPixmap& src) { + if (!SkImageInfoIsValid(src.info())) { + return false; + } + + if (!src.addr() || src.rowBytes() < src.info().minRowBytes()) { + return false; + } + + return true; +} + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + bool SkEncodeImageWithCG(SkWStream*, const SkPixmap&, SkEncodedImageFormat); +#else + #define SkEncodeImageWithCG(...) false +#endif + +#ifdef SK_BUILD_FOR_WIN + bool SkEncodeImageWithWIC(SkWStream*, const SkPixmap&, SkEncodedImageFormat, int quality); +#else + #define SkEncodeImageWithWIC(...) false +#endif + +#endif // SkImageEncoderPriv_DEFINED diff --git a/gfx/skia/skia/src/images/SkJPEGWriteUtility.cpp b/gfx/skia/skia/src/images/SkJPEGWriteUtility.cpp new file mode 100644 index 0000000000..4536bb4ff6 --- /dev/null +++ b/gfx/skia/skia/src/images/SkJPEGWriteUtility.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/images/SkJPEGWriteUtility.h" + +/////////////////////////////////////////////////////////////////////////////// + +static void sk_init_destination(j_compress_ptr cinfo) { + skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest; + + dest->next_output_byte = dest->fBuffer; + dest->free_in_buffer = skjpeg_destination_mgr::kBufferSize; +} + +static boolean sk_empty_output_buffer(j_compress_ptr cinfo) { + skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest; + +// if (!dest->fStream->write(dest->fBuffer, skjpeg_destination_mgr::kBufferSize - dest->free_in_buffer)) + if (!dest->fStream->write(dest->fBuffer, + skjpeg_destination_mgr::kBufferSize)) { + ERREXIT(cinfo, JERR_FILE_WRITE); + return FALSE; + } + + dest->next_output_byte = dest->fBuffer; + dest->free_in_buffer = skjpeg_destination_mgr::kBufferSize; + return TRUE; +} + +static void sk_term_destination (j_compress_ptr cinfo) { + skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest; + + size_t size = skjpeg_destination_mgr::kBufferSize - dest->free_in_buffer; + if (size > 0) { + if (!dest->fStream->write(dest->fBuffer, size)) { + ERREXIT(cinfo, JERR_FILE_WRITE); + return; + } + } + dest->fStream->flush(); +} + +skjpeg_destination_mgr::skjpeg_destination_mgr(SkWStream* stream) + : fStream(stream) { + this->init_destination = sk_init_destination; + this->empty_output_buffer = sk_empty_output_buffer; + this->term_destination = sk_term_destination; +} + +void skjpeg_error_exit(j_common_ptr cinfo) { + skjpeg_error_mgr* error = (skjpeg_error_mgr*)cinfo->err; + + (*error->output_message) (cinfo); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + if (error->fJmpBufStack.empty()) { + SK_ABORT("JPEG error with no jmp_buf set."); + } + longjmp(*error->fJmpBufStack.back(), -1); +} diff --git a/gfx/skia/skia/src/images/SkJPEGWriteUtility.h b/gfx/skia/skia/src/images/SkJPEGWriteUtility.h new file mode 100644 index 0000000000..356b08674b --- /dev/null +++ b/gfx/skia/skia/src/images/SkJPEGWriteUtility.h @@ -0,0 +1,39 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkJpegUtility_DEFINED +#define SkJpegUtility_DEFINED + +#include "include/core/SkStream.h" +#include "src/codec/SkJpegPriv.h" + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +#include + +void SK_API skjpeg_error_exit(j_common_ptr cinfo); + +///////////////////////////////////////////////////////////////////////////// +/* Our destination struct for directing decompressed pixels to our stream + * object. + */ +struct SK_API skjpeg_destination_mgr : jpeg_destination_mgr { + skjpeg_destination_mgr(SkWStream* stream); + + SkWStream* fStream; + + enum { + kBufferSize = 1024 + }; + uint8_t fBuffer[kBufferSize]; +}; + +#endif diff --git a/gfx/skia/skia/src/images/SkJpegEncoder.cpp b/gfx/skia/skia/src/images/SkJpegEncoder.cpp new file mode 100644 index 0000000000..144876d8fc --- /dev/null +++ b/gfx/skia/skia/src/images/SkJpegEncoder.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/images/SkImageEncoderPriv.h" + +#ifdef SK_HAS_JPEG_LIBRARY + +#include "include/core/SkStream.h" +#include "include/encode/SkJpegEncoder.h" +#include "include/private/SkColorData.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkMSAN.h" +#include "src/images/SkImageEncoderFns.h" +#include "src/images/SkJPEGWriteUtility.h" + +#include + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +class SkJpegEncoderMgr final : SkNoncopyable { +public: + + /* + * Create the decode manager + * Does not take ownership of stream + */ + static std::unique_ptr Make(SkWStream* stream) { + return std::unique_ptr(new SkJpegEncoderMgr(stream)); + } + + bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options); + + jpeg_compress_struct* cinfo() { return &fCInfo; } + + skjpeg_error_mgr* errorMgr() { return &fErrMgr; } + + transform_scanline_proc proc() const { return fProc; } + + ~SkJpegEncoderMgr() { + jpeg_destroy_compress(&fCInfo); + } + +private: + + SkJpegEncoderMgr(SkWStream* stream) + : fDstMgr(stream) + , fProc(nullptr) + { + fCInfo.err = jpeg_std_error(&fErrMgr); + fErrMgr.error_exit = skjpeg_error_exit; + jpeg_create_compress(&fCInfo); + fCInfo.dest = &fDstMgr; + } + + jpeg_compress_struct fCInfo; + skjpeg_error_mgr fErrMgr; + skjpeg_destination_mgr fDstMgr; + transform_scanline_proc fProc; +}; + +bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options) +{ + auto chooseProc8888 = [&]() { + if (kUnpremul_SkAlphaType == srcInfo.alphaType() && + options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) { + return transform_scanline_to_premul_legacy; + } + return (transform_scanline_proc) nullptr; + }; + + J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA; + int numComponents = 0; + switch (srcInfo.colorType()) { + case kRGBA_8888_SkColorType: + fProc = chooseProc8888(); + jpegColorType = JCS_EXT_RGBA; + numComponents = 4; + break; + case kBGRA_8888_SkColorType: + fProc = chooseProc8888(); + jpegColorType = JCS_EXT_BGRA; + numComponents = 4; + break; + case kRGB_565_SkColorType: + fProc = transform_scanline_565; + jpegColorType = JCS_RGB; + numComponents = 3; + break; + case kARGB_4444_SkColorType: + if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) { + return false; + } + + fProc = transform_scanline_444; + jpegColorType = JCS_RGB; + numComponents = 3; + break; + case kGray_8_SkColorType: + SkASSERT(srcInfo.isOpaque()); + jpegColorType = JCS_GRAYSCALE; + numComponents = 1; + break; + case kRGBA_F16_SkColorType: + if (kUnpremul_SkAlphaType == srcInfo.alphaType() && + options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) { + fProc = transform_scanline_F16_to_premul_8888; + } else { + fProc = transform_scanline_F16_to_8888; + } + jpegColorType = JCS_EXT_RGBA; + numComponents = 4; + break; + default: + return false; + } + + fCInfo.image_width = srcInfo.width(); + fCInfo.image_height = srcInfo.height(); + fCInfo.in_color_space = jpegColorType; + fCInfo.input_components = numComponents; + jpeg_set_defaults(&fCInfo); + + if (kGray_8_SkColorType != srcInfo.colorType()) { + switch (options.fDownsample) { + case SkJpegEncoder::Downsample::k420: + SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor); + SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor); + SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor); + break; + case SkJpegEncoder::Downsample::k422: + fCInfo.comp_info[0].h_samp_factor = 2; + fCInfo.comp_info[0].v_samp_factor = 1; + fCInfo.comp_info[1].h_samp_factor = 1; + fCInfo.comp_info[1].v_samp_factor = 1; + fCInfo.comp_info[2].h_samp_factor = 1; + fCInfo.comp_info[2].v_samp_factor = 1; + break; + case SkJpegEncoder::Downsample::k444: + fCInfo.comp_info[0].h_samp_factor = 1; + fCInfo.comp_info[0].v_samp_factor = 1; + fCInfo.comp_info[1].h_samp_factor = 1; + fCInfo.comp_info[1].v_samp_factor = 1; + fCInfo.comp_info[2].h_samp_factor = 1; + fCInfo.comp_info[2].v_samp_factor = 1; + break; + } + } + + // Tells libjpeg-turbo to compute optimal Huffman coding tables + // for the image. This improves compression at the cost of + // slower encode performance. + fCInfo.optimize_coding = TRUE; + return true; +} + +std::unique_ptr SkJpegEncoder::Make(SkWStream* dst, const SkPixmap& src, + const Options& options) { + if (!SkPixmapIsValid(src)) { + return nullptr; + } + + std::unique_ptr encoderMgr = SkJpegEncoderMgr::Make(dst); + + skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr()); + if (setjmp(jmp)) { + return nullptr; + } + + if (!encoderMgr->setParams(src.info(), options)) { + return nullptr; + } + + jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE); + jpeg_start_compress(encoderMgr->cinfo(), TRUE); + + sk_sp icc = icc_from_color_space(src.info()); + if (icc) { + // Create a contiguous block of memory with the icc signature followed by the profile. + sk_sp markerData = + SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size()); + uint8_t* ptr = (uint8_t*) markerData->writable_data(); + memcpy(ptr, kICCSig, sizeof(kICCSig)); + ptr += sizeof(kICCSig); + *ptr++ = 1; // This is the first marker. + *ptr++ = 1; // Out of one total markers. + memcpy(ptr, icc->data(), icc->size()); + + jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(), markerData->size()); + } + + return std::unique_ptr(new SkJpegEncoder(std::move(encoderMgr), src)); +} + +SkJpegEncoder::SkJpegEncoder(std::unique_ptr encoderMgr, const SkPixmap& src) + : INHERITED(src, encoderMgr->proc() ? encoderMgr->cinfo()->input_components*src.width() : 0) + , fEncoderMgr(std::move(encoderMgr)) +{} + +SkJpegEncoder::~SkJpegEncoder() {} + +bool SkJpegEncoder::onEncodeRows(int numRows) { + skjpeg_error_mgr::AutoPushJmpBuf jmp(fEncoderMgr->errorMgr()); + if (setjmp(jmp)) { + return false; + } + + const size_t srcBytes = SkColorTypeBytesPerPixel(fSrc.colorType()) * fSrc.width(); + const size_t jpegSrcBytes = fEncoderMgr->cinfo()->input_components * fSrc.width(); + + const void* srcRow = fSrc.addr(0, fCurrRow); + for (int i = 0; i < numRows; i++) { + JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow; + if (fEncoderMgr->proc()) { + sk_msan_assert_initialized(srcRow, SkTAddOffset(srcRow, srcBytes)); + fEncoderMgr->proc()((char*)fStorage.get(), + (const char*)srcRow, + fSrc.width(), + fEncoderMgr->cinfo()->input_components); + jpegSrcRow = fStorage.get(); + sk_msan_assert_initialized(jpegSrcRow, + SkTAddOffset(jpegSrcRow, jpegSrcBytes)); + } else { + // Same as above, but this repetition allows determining whether a + // proc was used when msan asserts. + sk_msan_assert_initialized(jpegSrcRow, + SkTAddOffset(jpegSrcRow, jpegSrcBytes)); + } + + jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1); + srcRow = SkTAddOffset(srcRow, fSrc.rowBytes()); + } + + fCurrRow += numRows; + if (fCurrRow == fSrc.height()) { + jpeg_finish_compress(fEncoderMgr->cinfo()); + } + + return true; +} + +bool SkJpegEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) { + auto encoder = SkJpegEncoder::Make(dst, src, options); + return encoder.get() && encoder->encodeRows(src.height()); +} + +#endif diff --git a/gfx/skia/skia/src/images/SkPngEncoder.cpp b/gfx/skia/skia/src/images/SkPngEncoder.cpp new file mode 100644 index 0000000000..5a3c9e6957 --- /dev/null +++ b/gfx/skia/skia/src/images/SkPngEncoder.cpp @@ -0,0 +1,450 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/images/SkImageEncoderPriv.h" + +#ifdef SK_HAS_PNG_LIBRARY + +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/encode/SkPngEncoder.h" +#include "include/private/SkImageInfoPriv.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkPngPriv.h" +#include "src/core/SkMSAN.h" +#include "src/images/SkImageEncoderFns.h" +#include + +#include "png.h" + +static_assert(PNG_FILTER_NONE == (int)SkPngEncoder::FilterFlag::kNone, "Skia libpng filter err."); +static_assert(PNG_FILTER_SUB == (int)SkPngEncoder::FilterFlag::kSub, "Skia libpng filter err."); +static_assert(PNG_FILTER_UP == (int)SkPngEncoder::FilterFlag::kUp, "Skia libpng filter err."); +static_assert(PNG_FILTER_AVG == (int)SkPngEncoder::FilterFlag::kAvg, "Skia libpng filter err."); +static_assert(PNG_FILTER_PAETH == (int)SkPngEncoder::FilterFlag::kPaeth, "Skia libpng filter err."); +static_assert(PNG_ALL_FILTERS == (int)SkPngEncoder::FilterFlag::kAll, "Skia libpng filter err."); + +static constexpr bool kSuppressPngEncodeWarnings = true; + +static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { + if (!kSuppressPngEncodeWarnings) { + SkDebugf("libpng encode error: %s\n", msg); + } + + longjmp(png_jmpbuf(png_ptr), 1); +} + +static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) { + SkWStream* stream = (SkWStream*)png_get_io_ptr(png_ptr); + if (!stream->write(data, len)) { + png_error(png_ptr, "sk_write_fn cannot write to stream"); + } +} + +class SkPngEncoderMgr final : SkNoncopyable { +public: + + /* + * Create the decode manager + * Does not take ownership of stream + */ + static std::unique_ptr Make(SkWStream* stream); + + bool setHeader(const SkImageInfo& srcInfo, const SkPngEncoder::Options& options); + bool setColorSpace(const SkImageInfo& info); + bool writeInfo(const SkImageInfo& srcInfo); + void chooseProc(const SkImageInfo& srcInfo); + + png_structp pngPtr() { return fPngPtr; } + png_infop infoPtr() { return fInfoPtr; } + int pngBytesPerPixel() const { return fPngBytesPerPixel; } + transform_scanline_proc proc() const { return fProc; } + + ~SkPngEncoderMgr() { + png_destroy_write_struct(&fPngPtr, &fInfoPtr); + } + +private: + + SkPngEncoderMgr(png_structp pngPtr, png_infop infoPtr) + : fPngPtr(pngPtr) + , fInfoPtr(infoPtr) + {} + + png_structp fPngPtr; + png_infop fInfoPtr; + int fPngBytesPerPixel; + transform_scanline_proc fProc; +}; + +std::unique_ptr SkPngEncoderMgr::Make(SkWStream* stream) { + png_structp pngPtr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr); + if (!pngPtr) { + return nullptr; + } + + png_infop infoPtr = png_create_info_struct(pngPtr); + if (!infoPtr) { + png_destroy_write_struct(&pngPtr, nullptr); + return nullptr; + } + + png_set_write_fn(pngPtr, (void*)stream, sk_write_fn, nullptr); + return std::unique_ptr(new SkPngEncoderMgr(pngPtr, infoPtr)); +} + +bool SkPngEncoderMgr::setHeader(const SkImageInfo& srcInfo, const SkPngEncoder::Options& options) { + if (setjmp(png_jmpbuf(fPngPtr))) { + return false; + } + + int pngColorType; + png_color_8 sigBit; + int bitDepth = 8; + switch (srcInfo.colorType()) { + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: + case kRGBA_F32_SkColorType: + sigBit.red = 16; + sigBit.green = 16; + sigBit.blue = 16; + sigBit.alpha = 16; + bitDepth = 16; + pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + fPngBytesPerPixel = 8; + break; + case kGray_8_SkColorType: + sigBit.gray = 8; + pngColorType = PNG_COLOR_TYPE_GRAY; + fPngBytesPerPixel = 1; + SkASSERT(srcInfo.isOpaque()); + break; + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + sigBit.red = 8; + sigBit.green = 8; + sigBit.blue = 8; + sigBit.alpha = 8; + pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + fPngBytesPerPixel = srcInfo.isOpaque() ? 3 : 4; + break; + case kRGB_888x_SkColorType: + sigBit.red = 8; + sigBit.green = 8; + sigBit.blue = 8; + pngColorType = PNG_COLOR_TYPE_RGB; + fPngBytesPerPixel = 3; + SkASSERT(srcInfo.isOpaque()); + break; + case kARGB_4444_SkColorType: + if (kUnpremul_SkAlphaType == srcInfo.alphaType()) { + return false; + } + + sigBit.red = 4; + sigBit.green = 4; + sigBit.blue = 4; + sigBit.alpha = 4; + pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + fPngBytesPerPixel = srcInfo.isOpaque() ? 3 : 4; + break; + case kRGB_565_SkColorType: + sigBit.red = 5; + sigBit.green = 6; + sigBit.blue = 5; + pngColorType = PNG_COLOR_TYPE_RGB; + fPngBytesPerPixel = 3; + SkASSERT(srcInfo.isOpaque()); + break; + case kAlpha_8_SkColorType: // store as gray+alpha, but ignore gray + sigBit.gray = kGraySigBit_GrayAlphaIsJustAlpha; + sigBit.alpha = 8; + pngColorType = PNG_COLOR_TYPE_GRAY_ALPHA; + fPngBytesPerPixel = 2; + break; + case kRGBA_1010102_SkColorType: + bitDepth = 16; + sigBit.red = 10; + sigBit.green = 10; + sigBit.blue = 10; + sigBit.alpha = 2; + pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + fPngBytesPerPixel = 8; + break; + case kRGB_101010x_SkColorType: + bitDepth = 16; + sigBit.red = 10; + sigBit.green = 10; + sigBit.blue = 10; + pngColorType = PNG_COLOR_TYPE_RGB; + fPngBytesPerPixel = 6; + break; + default: + return false; + } + + png_set_IHDR(fPngPtr, fInfoPtr, srcInfo.width(), srcInfo.height(), + bitDepth, pngColorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + png_set_sBIT(fPngPtr, fInfoPtr, &sigBit); + + int filters = (int)options.fFilterFlags & (int)SkPngEncoder::FilterFlag::kAll; + SkASSERT(filters == (int)options.fFilterFlags); + png_set_filter(fPngPtr, PNG_FILTER_TYPE_BASE, filters); + + int zlibLevel = SkTMin(SkTMax(0, options.fZLibLevel), 9); + SkASSERT(zlibLevel == options.fZLibLevel); + png_set_compression_level(fPngPtr, zlibLevel); + + // Set comments in tEXt chunk + const sk_sp& comments = options.fComments; + if (comments != nullptr) { + std::vector png_texts(comments->count()); + std::vector clippedKeys; + for (int i = 0; i < comments->count() / 2; ++i) { + const char* keyword; + const char* originalKeyword = comments->atStr(2 * i); + const char* text = comments->atStr(2 * i + 1); + if (strlen(originalKeyword) <= PNG_KEYWORD_MAX_LENGTH) { + keyword = originalKeyword; + } else { + SkDEBUGFAILF("PNG tEXt keyword should be no longer than %d.", + PNG_KEYWORD_MAX_LENGTH); + clippedKeys.emplace_back(originalKeyword, PNG_KEYWORD_MAX_LENGTH); + keyword = clippedKeys.back().c_str(); + } + // It seems safe to convert png_const_charp to png_charp for key/text, + // and we don't have to provide text_length and other fields as we're providing + // 0-terminated c_str with PNG_TEXT_COMPRESSION_NONE (no compression, no itxt). + png_texts[i].compression = PNG_TEXT_COMPRESSION_NONE; + png_texts[i].key = (png_charp)keyword; + png_texts[i].text = (png_charp)text; + } + png_set_text(fPngPtr, fInfoPtr, png_texts.data(), png_texts.size()); + } + + return true; +} + +static transform_scanline_proc choose_proc(const SkImageInfo& info) { + switch (info.colorType()) { + case kUnknown_SkColorType: + break; + + case kRGBA_8888_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + return transform_scanline_RGBX; + case kUnpremul_SkAlphaType: + return transform_scanline_memcpy; + case kPremul_SkAlphaType: + return transform_scanline_rgbA; + default: + SkASSERT(false); + return nullptr; + } + case kBGRA_8888_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + return transform_scanline_BGRX; + case kUnpremul_SkAlphaType: + return transform_scanline_BGRA; + case kPremul_SkAlphaType: + return transform_scanline_bgrA; + default: + SkASSERT(false); + return nullptr; + } + case kRGB_565_SkColorType: + return transform_scanline_565; + case kRGB_888x_SkColorType: + return transform_scanline_RGBX; + case kARGB_4444_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + return transform_scanline_444; + case kPremul_SkAlphaType: + return transform_scanline_4444; + default: + SkASSERT(false); + return nullptr; + } + case kGray_8_SkColorType: + return transform_scanline_memcpy; + + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + case kUnpremul_SkAlphaType: + return transform_scanline_F16; + case kPremul_SkAlphaType: + return transform_scanline_F16_premul; + default: + SkASSERT(false); + return nullptr; + } + case kRGBA_F32_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + case kUnpremul_SkAlphaType: + return transform_scanline_F32; + case kPremul_SkAlphaType: + return transform_scanline_F32_premul; + default: + SkASSERT(false); + return nullptr; + } + case kRGBA_1010102_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + case kUnpremul_SkAlphaType: + return transform_scanline_1010102; + case kPremul_SkAlphaType: + return transform_scanline_1010102_premul; + default: + SkASSERT(false); + return nullptr; + } + case kRGB_101010x_SkColorType: + return transform_scanline_101010x; + case kAlpha_8_SkColorType: + return transform_scanline_A8_to_GrayAlpha; + case kR8G8_unorm_SkColorType: + case kR16G16_unorm_SkColorType: + case kR16G16_float_SkColorType: + case kA16_unorm_SkColorType: + case kA16_float_SkColorType: + case kR16G16B16A16_unorm_SkColorType: + return nullptr; + } + SkASSERT(false); + return nullptr; +} + +static void set_icc(png_structp png_ptr, png_infop info_ptr, const SkImageInfo& info) { + sk_sp icc = icc_from_color_space(info); + if (!icc) { + return; + } + +#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5) + const char* name = "Skia"; + png_const_bytep iccPtr = icc->bytes(); +#else + SkString str("Skia"); + char* name = str.writable_str(); + png_charp iccPtr = (png_charp) icc->writable_data(); +#endif + png_set_iCCP(png_ptr, info_ptr, name, 0, iccPtr, icc->size()); +} + +bool SkPngEncoderMgr::setColorSpace(const SkImageInfo& info) { + if (setjmp(png_jmpbuf(fPngPtr))) { + return false; + } + + if (info.colorSpace() && info.colorSpace()->isSRGB()) { + png_set_sRGB(fPngPtr, fInfoPtr, PNG_sRGB_INTENT_PERCEPTUAL); + } else { + set_icc(fPngPtr, fInfoPtr, info); + } + + return true; +} + +bool SkPngEncoderMgr::writeInfo(const SkImageInfo& srcInfo) { + if (setjmp(png_jmpbuf(fPngPtr))) { + return false; + } + + png_write_info(fPngPtr, fInfoPtr); + if (kRGBA_F16_SkColorType == srcInfo.colorType() && + kOpaque_SkAlphaType == srcInfo.alphaType()) + { + // For kOpaque, kRGBA_F16, we will keep the row as RGBA and tell libpng + // to skip the alpha channel. + png_set_filler(fPngPtr, 0, PNG_FILLER_AFTER); + } + + return true; +} + +void SkPngEncoderMgr::chooseProc(const SkImageInfo& srcInfo) { + fProc = choose_proc(srcInfo); +} + +std::unique_ptr SkPngEncoder::Make(SkWStream* dst, const SkPixmap& src, + const Options& options) { + if (!SkPixmapIsValid(src)) { + return nullptr; + } + + std::unique_ptr encoderMgr = SkPngEncoderMgr::Make(dst); + if (!encoderMgr) { + return nullptr; + } + + if (!encoderMgr->setHeader(src.info(), options)) { + return nullptr; + } + + if (!encoderMgr->setColorSpace(src.info())) { + return nullptr; + } + + if (!encoderMgr->writeInfo(src.info())) { + return nullptr; + } + + encoderMgr->chooseProc(src.info()); + + return std::unique_ptr(new SkPngEncoder(std::move(encoderMgr), src)); +} + +SkPngEncoder::SkPngEncoder(std::unique_ptr encoderMgr, const SkPixmap& src) + : INHERITED(src, encoderMgr->pngBytesPerPixel() * src.width()) + , fEncoderMgr(std::move(encoderMgr)) +{} + +SkPngEncoder::~SkPngEncoder() {} + +bool SkPngEncoder::onEncodeRows(int numRows) { + if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) { + return false; + } + + const void* srcRow = fSrc.addr(0, fCurrRow); + for (int y = 0; y < numRows; y++) { + sk_msan_assert_initialized(srcRow, + (const uint8_t*)srcRow + (fSrc.width() << fSrc.shiftPerPixel())); + fEncoderMgr->proc()((char*)fStorage.get(), + (const char*)srcRow, + fSrc.width(), + SkColorTypeBytesPerPixel(fSrc.colorType())); + + png_bytep rowPtr = (png_bytep) fStorage.get(); + png_write_rows(fEncoderMgr->pngPtr(), &rowPtr, 1); + srcRow = SkTAddOffset(srcRow, fSrc.rowBytes()); + } + + fCurrRow += numRows; + if (fCurrRow == fSrc.height()) { + png_write_end(fEncoderMgr->pngPtr(), fEncoderMgr->infoPtr()); + } + + return true; +} + +bool SkPngEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) { + auto encoder = SkPngEncoder::Make(dst, src, options); + return encoder.get() && encoder->encodeRows(src.height()); +} + +#endif diff --git a/gfx/skia/skia/src/images/SkWebpEncoder.cpp b/gfx/skia/skia/src/images/SkWebpEncoder.cpp new file mode 100644 index 0000000000..32801cb341 --- /dev/null +++ b/gfx/skia/skia/src/images/SkWebpEncoder.cpp @@ -0,0 +1,219 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/images/SkImageEncoderPriv.h" + +#ifdef SK_HAS_WEBP_LIBRARY + +#include "include/core/SkBitmap.h" +#include "include/core/SkStream.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/encode/SkWebpEncoder.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "src/images/SkImageEncoderFns.h" +#include "src/utils/SkUTF.h" + +// A WebP encoder only, on top of (subset of) libwebp +// For more information on WebP image format, and libwebp library, see: +// http://code.google.com/speed/webp/ +// http://www.webmproject.org/code/#libwebp_webp_image_decoder_library +// http://review.webmproject.org/gitweb?p=libwebp.git + +#include +extern "C" { +// If moving libwebp out of skia source tree, path for webp headers must be +// updated accordingly. Here, we enforce using local copy in webp sub-directory. +#include "webp/encode.h" +#include "webp/mux.h" +} + +static transform_scanline_proc choose_proc(const SkImageInfo& info) { + switch (info.colorType()) { + case kRGBA_8888_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + return transform_scanline_RGBX; + case kUnpremul_SkAlphaType: + return transform_scanline_memcpy; + case kPremul_SkAlphaType: + return transform_scanline_rgbA; + default: + return nullptr; + } + case kBGRA_8888_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + return transform_scanline_BGRX; + case kUnpremul_SkAlphaType: + return transform_scanline_BGRA; + case kPremul_SkAlphaType: + return transform_scanline_bgrA; + default: + return nullptr; + } + case kRGB_565_SkColorType: + if (!info.isOpaque()) { + return nullptr; + } + + return transform_scanline_565; + case kARGB_4444_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + return transform_scanline_444; + case kPremul_SkAlphaType: + return transform_scanline_4444; + default: + return nullptr; + } + case kGray_8_SkColorType: + return transform_scanline_gray; + case kRGBA_F16_SkColorType: + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + case kUnpremul_SkAlphaType: + return transform_scanline_F16_to_8888; + case kPremul_SkAlphaType: + return transform_scanline_F16_premul_to_8888; + default: + return nullptr; + } + default: + return nullptr; + } +} + +static int stream_writer(const uint8_t* data, size_t data_size, + const WebPPicture* const picture) { + SkWStream* const stream = (SkWStream*)picture->custom_ptr; + return stream->write(data, data_size) ? 1 : 0; +} + +bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) { + if (!SkPixmapIsValid(pixmap)) { + return false; + } + + const transform_scanline_proc proc = choose_proc(pixmap.info()); + if (!proc) { + return false; + } + + int bpp; + if (kRGBA_F16_SkColorType == pixmap.colorType()) { + bpp = 4; + } else { + bpp = pixmap.isOpaque() ? 3 : 4; + } + + if (nullptr == pixmap.addr()) { + return false; + } + + WebPConfig webp_config; + if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) { + return false; + } + + WebPPicture pic; + WebPPictureInit(&pic); + SkAutoTCallVProc autoPic(&pic); + pic.width = pixmap.width(); + pic.height = pixmap.height(); + pic.writer = stream_writer; + + // Set compression, method, and pixel format. + // libwebp recommends using BGRA for lossless and YUV for lossy. + // The choices of |webp_config.method| currently just match Chrome's defaults. We + // could potentially expose this decision to the client. + if (Compression::kLossy == opts.fCompression) { + webp_config.lossless = 0; +#ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD + webp_config.method = 3; +#endif + pic.use_argb = 0; + } else { + webp_config.lossless = 1; + webp_config.method = 0; + pic.use_argb = 1; + } + + // If there is no need to embed an ICC profile, we write directly to the input stream. + // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp + // forces us to have an encoded image before we can add a profile. + sk_sp icc = icc_from_color_space(pixmap.info()); + SkDynamicMemoryWStream tmp; + pic.custom_ptr = icc ? (void*)&tmp : (void*)stream; + + const uint8_t* src = (uint8_t*)pixmap.addr(); + const int rgbStride = pic.width * bpp; + const size_t rowBytes = pixmap.rowBytes(); + + // Import (for each scanline) the bit-map image (in appropriate color-space) + // to RGB color space. + std::unique_ptr rgb(new uint8_t[rgbStride * pic.height]); + for (int y = 0; y < pic.height; ++y) { + proc((char*) &rgb[y * rgbStride], + (const char*) &src[y * rowBytes], + pic.width, + bpp); + } + + auto importProc = WebPPictureImportRGB; + if (3 != bpp) { + if (pixmap.isOpaque()) { + importProc = WebPPictureImportRGBX; + } else { + importProc = WebPPictureImportRGBA; + } + } + + if (!importProc(&pic, &rgb[0], rgbStride)) { + return false; + } + + if (!WebPEncode(&webp_config, &pic)) { + return false; + } + + if (icc) { + sk_sp encodedData = tmp.detachAsData(); + WebPData encoded = { encodedData->bytes(), encodedData->size() }; + WebPData iccChunk = { icc->bytes(), icc->size() }; + + SkAutoTCallVProc mux(WebPMuxNew()); + if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) { + return false; + } + + if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) { + return false; + } + + WebPData assembled; + if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) { + return false; + } + + stream->write(assembled.bytes, assembled.size); + WebPDataClear(&assembled); + } + + return true; +} + +#endif diff --git a/gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.cpp b/gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.cpp new file mode 100644 index 0000000000..bf6df9fb41 --- /dev/null +++ b/gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkDiscardableMemory.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTInternalLList.h" +#include "src/lazy/SkDiscardableMemoryPool.h" + +// Note: +// A PoolDiscardableMemory is memory that is counted in a pool. +// A DiscardableMemoryPool is a pool of PoolDiscardableMemorys. + +namespace { + +class PoolDiscardableMemory; + +/** + * This non-global pool can be used for unit tests to verify that the + * pool works. + */ +class DiscardableMemoryPool : public SkDiscardableMemoryPool { +public: + DiscardableMemoryPool(size_t budget); + ~DiscardableMemoryPool() override; + + std::unique_ptr make(size_t bytes); + SkDiscardableMemory* create(size_t bytes) override { + return this->make(bytes).release(); // TODO: change API + } + + size_t getRAMUsed() override; + void setRAMBudget(size_t budget) override; + size_t getRAMBudget() override { return fBudget; } + + /** purges all unlocked DMs */ + void dumpPool() override; + + #if SK_LAZY_CACHE_STATS // Defined in SkDiscardableMemoryPool.h + int getCacheHits() override { return fCacheHits; } + int getCacheMisses() override { return fCacheMisses; } + void resetCacheHitsAndMisses() override { + fCacheHits = fCacheMisses = 0; + } + int fCacheHits; + int fCacheMisses; + #endif // SK_LAZY_CACHE_STATS + +private: + SkMutex fMutex; + size_t fBudget; + size_t fUsed; + SkTInternalLList fList; + + /** Function called to free memory if needed */ + void dumpDownTo(size_t budget); + /** called by DiscardableMemoryPool upon destruction */ + void removeFromPool(PoolDiscardableMemory* dm); + /** called by DiscardableMemoryPool::lock() */ + bool lock(PoolDiscardableMemory* dm); + /** called by DiscardableMemoryPool::unlock() */ + void unlock(PoolDiscardableMemory* dm); + + friend class PoolDiscardableMemory; + + typedef SkDiscardableMemory::Factory INHERITED; +}; + +/** + * A PoolDiscardableMemory is a SkDiscardableMemory that relies on + * a DiscardableMemoryPool object to manage the memory. + */ +class PoolDiscardableMemory : public SkDiscardableMemory { +public: + PoolDiscardableMemory(sk_sp pool, SkAutoFree pointer, size_t bytes); + ~PoolDiscardableMemory() override; + bool lock() override; + void* data() override; + void unlock() override; + friend class DiscardableMemoryPool; +private: + SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory); + sk_sp fPool; + bool fLocked; + SkAutoFree fPointer; + const size_t fBytes; +}; + +PoolDiscardableMemory::PoolDiscardableMemory(sk_sp pool, + SkAutoFree pointer, + size_t bytes) + : fPool(std::move(pool)), fLocked(true), fPointer(std::move(pointer)), fBytes(bytes) { + SkASSERT(fPool != nullptr); + SkASSERT(fPointer != nullptr); + SkASSERT(fBytes > 0); +} + +PoolDiscardableMemory::~PoolDiscardableMemory() { + SkASSERT(!fLocked); // contract for SkDiscardableMemory + fPool->removeFromPool(this); +} + +bool PoolDiscardableMemory::lock() { + SkASSERT(!fLocked); // contract for SkDiscardableMemory + return fPool->lock(this); +} + +void* PoolDiscardableMemory::data() { + SkASSERT(fLocked); // contract for SkDiscardableMemory + return fPointer.get(); +} + +void PoolDiscardableMemory::unlock() { + SkASSERT(fLocked); // contract for SkDiscardableMemory + fPool->unlock(this); +} + +//////////////////////////////////////////////////////////////////////////////// + +DiscardableMemoryPool::DiscardableMemoryPool(size_t budget) + : fBudget(budget) + , fUsed(0) { + #if SK_LAZY_CACHE_STATS + fCacheHits = 0; + fCacheMisses = 0; + #endif // SK_LAZY_CACHE_STATS +} +DiscardableMemoryPool::~DiscardableMemoryPool() { + // PoolDiscardableMemory objects that belong to this pool are + // always deleted before deleting this pool since each one has a + // ref to the pool. + SkASSERT(fList.isEmpty()); +} + +void DiscardableMemoryPool::dumpDownTo(size_t budget) { + fMutex.assertHeld(); + if (fUsed <= budget) { + return; + } + using Iter = SkTInternalLList::Iter; + Iter iter; + PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart); + while ((fUsed > budget) && (cur)) { + if (!cur->fLocked) { + PoolDiscardableMemory* dm = cur; + SkASSERT(dm->fPointer != nullptr); + dm->fPointer = nullptr; + SkASSERT(fUsed >= dm->fBytes); + fUsed -= dm->fBytes; + cur = iter.prev(); + // Purged DMs are taken out of the list. This saves times + // looking them up. Purged DMs are NOT deleted. + fList.remove(dm); + } else { + cur = iter.prev(); + } + } +} + +std::unique_ptr DiscardableMemoryPool::make(size_t bytes) { + SkAutoFree addr(sk_malloc_canfail(bytes)); + if (nullptr == addr) { + return nullptr; + } + auto dm = skstd::make_unique(sk_ref_sp(this), std::move(addr), bytes); + SkAutoMutexExclusive autoMutexAcquire(fMutex); + fList.addToHead(dm.get()); + fUsed += bytes; + this->dumpDownTo(fBudget); + return dm; +} + +void DiscardableMemoryPool::removeFromPool(PoolDiscardableMemory* dm) { + SkAutoMutexExclusive autoMutexAcquire(fMutex); + // This is called by dm's destructor. + if (dm->fPointer != nullptr) { + SkASSERT(fUsed >= dm->fBytes); + fUsed -= dm->fBytes; + fList.remove(dm); + } else { + SkASSERT(!fList.isInList(dm)); + } +} + +bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) { + SkASSERT(dm != nullptr); + SkAutoMutexExclusive autoMutexAcquire(fMutex); + if (nullptr == dm->fPointer) { + // May have been purged while waiting for lock. + #if SK_LAZY_CACHE_STATS + ++fCacheMisses; + #endif // SK_LAZY_CACHE_STATS + return false; + } + dm->fLocked = true; + fList.remove(dm); + fList.addToHead(dm); + #if SK_LAZY_CACHE_STATS + ++fCacheHits; + #endif // SK_LAZY_CACHE_STATS + return true; +} + +void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) { + SkASSERT(dm != nullptr); + SkAutoMutexExclusive autoMutexAcquire(fMutex); + dm->fLocked = false; + this->dumpDownTo(fBudget); +} + +size_t DiscardableMemoryPool::getRAMUsed() { + return fUsed; +} +void DiscardableMemoryPool::setRAMBudget(size_t budget) { + SkAutoMutexExclusive autoMutexAcquire(fMutex); + fBudget = budget; + this->dumpDownTo(fBudget); +} +void DiscardableMemoryPool::dumpPool() { + SkAutoMutexExclusive autoMutexAcquire(fMutex); + this->dumpDownTo(0); +} + +} // namespace + +sk_sp SkDiscardableMemoryPool::Make(size_t size) { + return sk_make_sp(size); +} + +SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() { + // Intentionally leak this global pool. + static SkDiscardableMemoryPool* global = + new DiscardableMemoryPool(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE); + return global; +} diff --git a/gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.h b/gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.h new file mode 100644 index 0000000000..46519e3a18 --- /dev/null +++ b/gfx/skia/skia/src/lazy/SkDiscardableMemoryPool.h @@ -0,0 +1,67 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDiscardableMemoryPool_DEFINED +#define SkDiscardableMemoryPool_DEFINED + +#include "include/private/SkMutex.h" +#include "src/core/SkDiscardableMemory.h" + +#ifndef SK_LAZY_CACHE_STATS + #ifdef SK_DEBUG + #define SK_LAZY_CACHE_STATS 1 + #else + #define SK_LAZY_CACHE_STATS 0 + #endif +#endif + +/** + * An implementation of Discardable Memory that manages a fixed-size + * budget of memory. When the allocated memory exceeds this size, + * unlocked blocks of memory are purged. If all memory is locked, it + * can exceed the memory-use budget. + */ +class SkDiscardableMemoryPool : public SkDiscardableMemory::Factory { +public: + virtual ~SkDiscardableMemoryPool() { } + + virtual size_t getRAMUsed() = 0; + virtual void setRAMBudget(size_t budget) = 0; + virtual size_t getRAMBudget() = 0; + + /** purges all unlocked DMs */ + virtual void dumpPool() = 0; + + #if SK_LAZY_CACHE_STATS + /** + * These two values are a count of the number of successful and + * failed calls to SkDiscardableMemory::lock() for all DMs managed + * by this pool. + */ + virtual int getCacheHits() = 0; + virtual int getCacheMisses() = 0; + virtual void resetCacheHitsAndMisses() = 0; + #endif + + /** + * This non-global pool can be used for unit tests to verify that + * the pool works. + */ + static sk_sp Make(size_t size); +}; + +/** + * Returns (and creates if needed) a threadsafe global + * SkDiscardableMemoryPool. + */ +SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool(); + +#if !defined(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE) +#define SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE (128 * 1024 * 1024) +#endif + +#endif // SkDiscardableMemoryPool_DEFINED diff --git a/gfx/skia/skia/src/opts/Sk4px_NEON.h b/gfx/skia/skia/src/opts/Sk4px_NEON.h new file mode 100644 index 0000000000..81e3f60d23 --- /dev/null +++ b/gfx/skia/skia/src/opts/Sk4px_NEON.h @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +namespace { // NOLINT(google-build-namespaces) + +inline Sk4px::Wide Sk4px::widen() const { + return Sk16h(vmovl_u8(vget_low_u8 (this->fVec)), + vmovl_u8(vget_high_u8(this->fVec))); +} + +inline Sk4px::Wide Sk4px::mulWiden(const Sk16b& other) const { + return Sk16h(vmull_u8(vget_low_u8 (this->fVec), vget_low_u8 (other.fVec)), + vmull_u8(vget_high_u8(this->fVec), vget_high_u8(other.fVec))); +} + +inline Sk4px Sk4px::Wide::addNarrowHi(const Sk16h& other) const { + const Sk4px::Wide o(other); // Should be no code, but allows us to access fLo, fHi. + return Sk16b(vcombine_u8(vaddhn_u16(this->fLo.fVec, o.fLo.fVec), + vaddhn_u16(this->fHi.fVec, o.fHi.fVec))); +} + +inline Sk4px Sk4px::Wide::div255() const { + // Calculated as (x + (x+128)>>8 +128) >> 8. The 'r' in each instruction provides each +128. + return Sk16b(vcombine_u8(vraddhn_u16(this->fLo.fVec, vrshrq_n_u16(this->fLo.fVec, 8)), + vraddhn_u16(this->fHi.fVec, vrshrq_n_u16(this->fHi.fVec, 8)))); +} + +inline Sk4px Sk4px::alphas() const { + auto as = vshrq_n_u32((uint32x4_t)fVec, SK_A32_SHIFT); // ___3 ___2 ___1 ___0 + return Sk16b((uint8x16_t)vmulq_n_u32(as, 0x01010101)); // 3333 2222 1111 0000 +} + +inline Sk4px Sk4px::Load4Alphas(const SkAlpha a[4]) { + uint8x16_t a8 = vdupq_n_u8(0); // ____ ____ ____ ____ + a8 = vld1q_lane_u8(a+0, a8, 0); // ____ ____ ____ ___0 + a8 = vld1q_lane_u8(a+1, a8, 4); // ____ ____ ___1 ___0 + a8 = vld1q_lane_u8(a+2, a8, 8); // ____ ___2 ___1 ___0 + a8 = vld1q_lane_u8(a+3, a8, 12); // ___3 ___2 ___1 ___0 + auto a32 = (uint32x4_t)a8; // + return Sk16b((uint8x16_t)vmulq_n_u32(a32, 0x01010101)); // 3333 2222 1111 0000 +} + +inline Sk4px Sk4px::Load2Alphas(const SkAlpha a[2]) { + uint8x16_t a8 = vdupq_n_u8(0); // ____ ____ ____ ____ + a8 = vld1q_lane_u8(a+0, a8, 0); // ____ ____ ____ ___0 + a8 = vld1q_lane_u8(a+1, a8, 4); // ____ ____ ___1 ___0 + auto a32 = (uint32x4_t)a8; // + return Sk16b((uint8x16_t)vmulq_n_u32(a32, 0x01010101)); // ____ ____ 1111 0000 +} + +} // namespace + diff --git a/gfx/skia/skia/src/opts/Sk4px_SSE2.h b/gfx/skia/skia/src/opts/Sk4px_SSE2.h new file mode 100644 index 0000000000..6ce6dd5698 --- /dev/null +++ b/gfx/skia/skia/src/opts/Sk4px_SSE2.h @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +namespace { // NOLINT(google-build-namespaces) + +inline Sk4px::Wide Sk4px::widen() const { + return Sk16h(_mm_unpacklo_epi8(this->fVec, _mm_setzero_si128()), + _mm_unpackhi_epi8(this->fVec, _mm_setzero_si128())); +} + +inline Sk4px::Wide Sk4px::mulWiden(const Sk16b& other) const { + return this->widen() * Sk4px(other).widen(); +} + +inline Sk4px Sk4px::Wide::addNarrowHi(const Sk16h& other) const { + Sk4px::Wide r = (*this + other) >> 8; + return Sk4px(_mm_packus_epi16(r.fLo.fVec, r.fHi.fVec)); +} + +inline Sk4px Sk4px::Wide::div255() const { + // (x + 127) / 255 == ((x+128) * 257)>>16, + // and _mm_mulhi_epu16 makes the (_ * 257)>>16 part very convenient. + const __m128i _128 = _mm_set1_epi16(128), + _257 = _mm_set1_epi16(257); + return Sk4px(_mm_packus_epi16(_mm_mulhi_epu16(_mm_add_epi16(fLo.fVec, _128), _257), + _mm_mulhi_epu16(_mm_add_epi16(fHi.fVec, _128), _257))); +} + +// Load4Alphas and Load2Alphas use possibly-unaligned loads (SkAlpha[] -> uint16_t or uint32_t). +// These are safe on x86, often with no speed penalty. + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + inline Sk4px Sk4px::alphas() const { + static_assert(SK_A32_SHIFT == 24, "Intel's always little-endian."); + __m128i splat = _mm_set_epi8(15,15,15,15, 11,11,11,11, 7,7,7,7, 3,3,3,3); + return Sk16b(_mm_shuffle_epi8(this->fVec, splat)); + } + + inline Sk4px Sk4px::Load4Alphas(const SkAlpha a[4]) { + uint32_t as; + memcpy(&as, a, 4); + __m128i splat = _mm_set_epi8(3,3,3,3, 2,2,2,2, 1,1,1,1, 0,0,0,0); + return Sk16b(_mm_shuffle_epi8(_mm_cvtsi32_si128(as), splat)); + } +#else + inline Sk4px Sk4px::alphas() const { + static_assert(SK_A32_SHIFT == 24, "Intel's always little-endian."); + // We exploit that A >= rgb for any premul pixel. + __m128i as = fVec; // 3xxx 2xxx 1xxx 0xxx + as = _mm_max_epu8(as, _mm_srli_epi32(as, 8)); // 33xx 22xx 11xx 00xx + as = _mm_max_epu8(as, _mm_srli_epi32(as, 16)); // 3333 2222 1111 0000 + return Sk16b(as); + } + + inline Sk4px Sk4px::Load4Alphas(const SkAlpha a[4]) { + __m128i as; + memcpy(&as, a, 4); // ____ ____ ____ 3210 + as = _mm_unpacklo_epi8 (as, as); // ____ ____ 3322 1100 + as = _mm_unpacklo_epi16(as, as); // 3333 2222 1111 0000 + return Sk16b(as); + } +#endif + +inline Sk4px Sk4px::Load2Alphas(const SkAlpha a[2]) { + uint16_t alphas; + memcpy(&alphas, a, 2); + uint32_t alphas_and_two_zeros = alphas; // Aa -> Aa00 + + return Load4Alphas((const SkAlpha*)&alphas_and_two_zeros); +} + +} // namespace diff --git a/gfx/skia/skia/src/opts/Sk4px_none.h b/gfx/skia/skia/src/opts/Sk4px_none.h new file mode 100644 index 0000000000..41e8168bbb --- /dev/null +++ b/gfx/skia/skia/src/opts/Sk4px_none.h @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkUtils.h" + +namespace { // NOLINT(google-build-namespaces) + +inline Sk4px::Wide Sk4px::widen() const { + return Sk16h((*this)[ 0], (*this)[ 1], (*this)[ 2], (*this)[ 3], + (*this)[ 4], (*this)[ 5], (*this)[ 6], (*this)[ 7], + (*this)[ 8], (*this)[ 9], (*this)[10], (*this)[11], + (*this)[12], (*this)[13], (*this)[14], (*this)[15]); +} + +inline Sk4px::Wide Sk4px::mulWiden(const Sk16b& other) const { + return this->widen() * Sk4px(other).widen(); +} + +inline Sk4px Sk4px::Wide::addNarrowHi(const Sk16h& other) const { + Sk4px::Wide r = (*this + other) >> 8; + return Sk16b(r[ 0], r[ 1], r[ 2], r[ 3], + r[ 4], r[ 5], r[ 6], r[ 7], + r[ 8], r[ 9], r[10], r[11], + r[12], r[13], r[14], r[15]); +} + +inline Sk4px Sk4px::Wide::div255() const { + // Calculated as ((x+128) + ((x+128)>>8)) >> 8. + auto v = *this + Sk16h(128); + return v.addNarrowHi(v>>8); +} + +inline Sk4px Sk4px::alphas() const { + static_assert(SK_A32_SHIFT == 24, "This method assumes little-endian."); + return Sk16b((*this)[ 3], (*this)[ 3], (*this)[ 3], (*this)[ 3], + (*this)[ 7], (*this)[ 7], (*this)[ 7], (*this)[ 7], + (*this)[11], (*this)[11], (*this)[11], (*this)[11], + (*this)[15], (*this)[15], (*this)[15], (*this)[15]); +} + +inline Sk4px Sk4px::Load4Alphas(const SkAlpha a[4]) { + return Sk16b(a[0], a[0], a[0], a[0], + a[1], a[1], a[1], a[1], + a[2], a[2], a[2], a[2], + a[3], a[3], a[3], a[3]); +} + +inline Sk4px Sk4px::Load2Alphas(const SkAlpha a[2]) { + return Sk16b(a[0], a[0], a[0], a[0], + a[1], a[1], a[1], a[1], + 0,0,0,0, + 0,0,0,0); +} + +} // namespace diff --git a/gfx/skia/skia/src/opts/SkBitmapFilter_opts.h b/gfx/skia/skia/src/opts/SkBitmapFilter_opts.h new file mode 100644 index 0000000000..240b21caf9 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkBitmapFilter_opts.h @@ -0,0 +1,927 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapFilter_opts_DEFINED +#define SkBitmapFilter_opts_DEFINED + +#include "src/core/SkConvolver.h" + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include +#elif defined(SK_ARM_HAS_NEON) + #include +#endif + +namespace SK_OPTS_NS { + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + + static SK_ALWAYS_INLINE void AccumRemainder(const unsigned char* pixelsLeft, + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, __m128i& accum, int r) { + int remainder[4] = {0}; + for (int i = 0; i < r; i++) { + SkConvolutionFilter1D::ConvolutionFixed coeff = filterValues[i]; + remainder[0] += coeff * pixelsLeft[i * 4 + 0]; + remainder[1] += coeff * pixelsLeft[i * 4 + 1]; + remainder[2] += coeff * pixelsLeft[i * 4 + 2]; + remainder[3] += coeff * pixelsLeft[i * 4 + 3]; + } + __m128i t = _mm_setr_epi32(remainder[0], remainder[1], remainder[2], remainder[3]); + accum = _mm_add_epi32(accum, t); + } + + // Convolves horizontally along a single row. The row data is given in + // |srcData| and continues for the numValues() of the filter. + void convolve_horizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, + bool /*hasAlpha*/) { + // Output one pixel each iteration, calculating all channels (RGBA) together. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + // Get the filter that determines the current output pixel. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filterLength| pixels (4 bytes each) after this. + const unsigned char* rowToFilter = &srcData[filterOffset * 4]; + + __m128i zero = _mm_setzero_si128(); + __m128i accum = _mm_setzero_si128(); + + // We will load and accumulate with four coefficients per iteration. + for (int filterX = 0; filterX < filterLength >> 2; filterX++) { + // Load 4 coefficients => duplicate 1st and 2nd of them for all channels. + __m128i coeff, coeff16; + // [16] xx xx xx xx c3 c2 c1 c0 + coeff = _mm_loadl_epi64(reinterpret_cast(filterValues)); + // [16] xx xx xx xx c1 c1 c0 c0 + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + + // Load four pixels => unpack the first two pixels to 16 bits => + // multiply with coefficients => accumulate the convolution result. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src8 = _mm_loadu_si128(reinterpret_cast(rowToFilter)); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0*c0 b0*c0 g0*c0 r0*c0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + // [32] a1*c1 b1*c1 g1*c1 r1*c1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + // Duplicate 3rd and 4th coefficients for all channels => + // unpack the 3rd and 4th pixels to 16 bits => multiply with coefficients + // => accumulate the convolution results. + // [16] xx xx xx xx c3 c3 c2 c2 + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + // [16] a3 g3 b3 r3 a2 g2 b2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2*c2 b2*c2 g2*c2 r2*c2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + // [32] a3*c3 b3*c3 g3*c3 r3*c3 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + // Advance the pixel and coefficients pointers. + rowToFilter += 16; + filterValues += 4; + } + + // When |filterLength| is not divisible by 4, we accumulate the last 1 - 3 + // coefficients one at a time. + int r = filterLength & 3; + if (r) { + int remainderOffset = (filterOffset + filterLength - r) * 4; + AccumRemainder(srcData + remainderOffset, filterValues, accum, r); + } + + // Shift right for fixed point implementation. + accum = _mm_srai_epi32(accum, SkConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + accum = _mm_packs_epi32(accum, zero); + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + accum = _mm_packus_epi16(accum, zero); + + // Store the pixel value of 32 bits. + *(reinterpret_cast(outRow)) = _mm_cvtsi128_si32(accum); + outRow += 4; + } + } + + // Convolves horizontally along four rows. The row data is given in + // |srcData| and continues for the numValues() of the filter. + // The algorithm is almost same as |convolve_horizontally|. Please + // refer to that function for detailed comments. + void convolve_4_rows_horizontally(const unsigned char* srcData[4], + const SkConvolutionFilter1D& filter, + unsigned char* outRow[4], + size_t outRowBytes) { + SkDEBUGCODE(const unsigned char* out_row_0_start = outRow[0];) + + // Output one pixel each iteration, calculating all channels (RGBA) together. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + __m128i zero = _mm_setzero_si128(); + + // four pixels in a column per iteration. + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + __m128i accum3 = _mm_setzero_si128(); + + int start = filterOffset * 4; + // We will load and accumulate with four coefficients per iteration. + for (int filterX = 0; filterX < (filterLength >> 2); filterX++) { + __m128i coeff, coeff16lo, coeff16hi; + // [16] xx xx xx xx c3 c2 c1 c0 + coeff = _mm_loadl_epi64(reinterpret_cast(filterValues)); + // [16] xx xx xx xx c1 c1 c0 c0 + coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo); + // [16] xx xx xx xx c3 c3 c2 c2 + coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi); + + __m128i src8, src16, mul_hi, mul_lo, t; + +#define ITERATION(src, accum) \ + src8 = _mm_loadu_si128(reinterpret_cast(src)); \ + src16 = _mm_unpacklo_epi8(src8, zero); \ + mul_hi = _mm_mulhi_epi16(src16, coeff16lo); \ + mul_lo = _mm_mullo_epi16(src16, coeff16lo); \ + t = _mm_unpacklo_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + t = _mm_unpackhi_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + src16 = _mm_unpackhi_epi8(src8, zero); \ + mul_hi = _mm_mulhi_epi16(src16, coeff16hi); \ + mul_lo = _mm_mullo_epi16(src16, coeff16hi); \ + t = _mm_unpacklo_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + t = _mm_unpackhi_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t) + + ITERATION(srcData[0] + start, accum0); + ITERATION(srcData[1] + start, accum1); + ITERATION(srcData[2] + start, accum2); + ITERATION(srcData[3] + start, accum3); + + start += 16; + filterValues += 4; + } + + int r = filterLength & 3; + if (r) { + int remainderOffset = (filterOffset + filterLength - r) * 4; + AccumRemainder(srcData[0] + remainderOffset, filterValues, accum0, r); + AccumRemainder(srcData[1] + remainderOffset, filterValues, accum1, r); + AccumRemainder(srcData[2] + remainderOffset, filterValues, accum2, r); + AccumRemainder(srcData[3] + remainderOffset, filterValues, accum3, r); + } + + accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits); + accum0 = _mm_packs_epi32(accum0, zero); + accum0 = _mm_packus_epi16(accum0, zero); + accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits); + accum1 = _mm_packs_epi32(accum1, zero); + accum1 = _mm_packus_epi16(accum1, zero); + accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits); + accum2 = _mm_packs_epi32(accum2, zero); + accum2 = _mm_packus_epi16(accum2, zero); + accum3 = _mm_srai_epi32(accum3, SkConvolutionFilter1D::kShiftBits); + accum3 = _mm_packs_epi32(accum3, zero); + accum3 = _mm_packus_epi16(accum3, zero); + + // We seem to be running off the edge here (chromium:491660). + SkASSERT(((size_t)outRow[0] - (size_t)out_row_0_start) < outRowBytes); + + *(reinterpret_cast(outRow[0])) = _mm_cvtsi128_si32(accum0); + *(reinterpret_cast(outRow[1])) = _mm_cvtsi128_si32(accum1); + *(reinterpret_cast(outRow[2])) = _mm_cvtsi128_si32(accum2); + *(reinterpret_cast(outRow[3])) = _mm_cvtsi128_si32(accum3); + + outRow[0] += 4; + outRow[1] += 4; + outRow[2] += 4; + outRow[3] += 4; + } + } + + // Does vertical convolution to produce one output row. The filter values and + // length are given in the first two parameters. These are applied to each + // of the rows pointed to in the |sourceDataRows| array, with each row + // being |pixelWidth| wide. + // + // The output must have room for |pixelWidth * 4| bytes. + template + void ConvolveVertically(const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, + unsigned char* const* sourceDataRows, + int pixelWidth, + unsigned char* outRow) { + // Output four pixels per iteration (16 bytes). + int width = pixelWidth & ~3; + __m128i zero = _mm_setzero_si128(); + for (int outX = 0; outX < width; outX += 4) { + // Accumulated result for each pixel. 32 bits per RGBA channel. + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + __m128i accum3 = _mm_setzero_si128(); + + // Convolve with one filter coefficient per iteration. + for (int filterY = 0; filterY < filterLength; filterY++) { + + // Duplicate the filter coefficient 8 times. + // [16] cj cj cj cj cj cj cj cj + __m128i coeff16 = _mm_set1_epi16(filterValues[filterY]); + + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + const __m128i* src = reinterpret_cast( + &sourceDataRows[filterY][outX << 2]); + __m128i src8 = _mm_loadu_si128(src); + + // Unpack 1st and 2nd pixels from 8 bits to 16 bits for each channels => + // multiply with current coefficient => accumulate the result. + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0 b0 g0 r0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum0 = _mm_add_epi32(accum0, t); + // [32] a1 b1 g1 r1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum1 = _mm_add_epi32(accum1, t); + + // Unpack 3rd and 4th pixels from 8 bits to 16 bits for each channels => + // multiply with current coefficient => accumulate the result. + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2 b2 g2 r2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum2 = _mm_add_epi32(accum2, t); + // [32] a3 b3 g3 r3 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum3 = _mm_add_epi32(accum3, t); + } + + // Shift right for fixed point implementation. + accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits); + accum3 = _mm_srai_epi32(accum3, SkConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packs_epi32(accum0, accum1); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + accum2 = _mm_packs_epi32(accum2, accum3); + + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packus_epi16(accum0, accum2); + + if (hasAlpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + __m128i a = _mm_srli_epi32(accum0, 8); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + __m128i b = _mm_max_epu8(a, accum0); // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = _mm_srli_epi32(accum0, 16); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = _mm_max_epu8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = _mm_slli_epi32(b, 24); + + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum0 = _mm_max_epu8(b, accum0); + } else { + // Set value of alpha channels to 0xFF. + __m128i mask = _mm_set1_epi32(0xff000000); + accum0 = _mm_or_si128(accum0, mask); + } + + // Store the convolution result (16 bytes) and advance the pixel pointers. + _mm_storeu_si128(reinterpret_cast<__m128i*>(outRow), accum0); + outRow += 16; + } + + // When the width of the output is not divisible by 4, We need to save one + // pixel (4 bytes) each time. And also the fourth pixel is always absent. + int r = pixelWidth & 3; + if (r) { + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + for (int filterY = 0; filterY < filterLength; ++filterY) { + __m128i coeff16 = _mm_set1_epi16(filterValues[filterY]); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + const __m128i* src = reinterpret_cast( + &sourceDataRows[filterY][width << 2]); + __m128i src8 = _mm_loadu_si128(src); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0 b0 g0 r0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum0 = _mm_add_epi32(accum0, t); + // [32] a1 b1 g1 r1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum1 = _mm_add_epi32(accum1, t); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2 b2 g2 r2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum2 = _mm_add_epi32(accum2, t); + } + + accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packs_epi32(accum0, accum1); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + accum2 = _mm_packs_epi32(accum2, zero); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packus_epi16(accum0, accum2); + if (hasAlpha) { + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + __m128i a = _mm_srli_epi32(accum0, 8); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + __m128i b = _mm_max_epu8(a, accum0); // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = _mm_srli_epi32(accum0, 16); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = _mm_max_epu8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = _mm_slli_epi32(b, 24); + accum0 = _mm_max_epu8(b, accum0); + } else { + __m128i mask = _mm_set1_epi32(0xff000000); + accum0 = _mm_or_si128(accum0, mask); + } + + for (int i = 0; i < r; i++) { + *(reinterpret_cast(outRow)) = _mm_cvtsi128_si32(accum0); + accum0 = _mm_srli_si128(accum0, 4); + outRow += 4; + } + } + } + +#elif defined(SK_ARM_HAS_NEON) + + static SK_ALWAYS_INLINE void AccumRemainder(const unsigned char* pixelsLeft, + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, int32x4_t& accum, int r) { + int remainder[4] = {0}; + for (int i = 0; i < r; i++) { + SkConvolutionFilter1D::ConvolutionFixed coeff = filterValues[i]; + remainder[0] += coeff * pixelsLeft[i * 4 + 0]; + remainder[1] += coeff * pixelsLeft[i * 4 + 1]; + remainder[2] += coeff * pixelsLeft[i * 4 + 2]; + remainder[3] += coeff * pixelsLeft[i * 4 + 3]; + } + int32x4_t t = {remainder[0], remainder[1], remainder[2], remainder[3]}; + accum += t; + } + + // Convolves horizontally along a single row. The row data is given in + // |srcData| and continues for the numValues() of the filter. + void convolve_horizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, + bool /*hasAlpha*/) { + // Loop over each pixel on this row in the output image. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + uint8x8_t coeff_mask0 = vcreate_u8(0x0100010001000100); + uint8x8_t coeff_mask1 = vcreate_u8(0x0302030203020302); + uint8x8_t coeff_mask2 = vcreate_u8(0x0504050405040504); + uint8x8_t coeff_mask3 = vcreate_u8(0x0706070607060706); + // Get the filter that determines the current output pixel. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filterLength| pixels (4 bytes each) after this. + const unsigned char* rowToFilter = &srcData[filterOffset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int32x4_t accum = vdupq_n_s32(0); + for (int filterX = 0; filterX < filterLength >> 2; filterX++) { + // Load 4 coefficients + int16x4_t coeffs, coeff0, coeff1, coeff2, coeff3; + coeffs = vld1_s16(filterValues); + coeff0 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask0)); + coeff1 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask1)); + coeff2 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask2)); + coeff3 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask3)); + + // Load pixels and calc + uint8x16_t pixels = vld1q_u8(rowToFilter); + int16x8_t p01_16 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(pixels))); + int16x8_t p23_16 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(pixels))); + + int16x4_t p0_src = vget_low_s16(p01_16); + int16x4_t p1_src = vget_high_s16(p01_16); + int16x4_t p2_src = vget_low_s16(p23_16); + int16x4_t p3_src = vget_high_s16(p23_16); + + int32x4_t p0 = vmull_s16(p0_src, coeff0); + int32x4_t p1 = vmull_s16(p1_src, coeff1); + int32x4_t p2 = vmull_s16(p2_src, coeff2); + int32x4_t p3 = vmull_s16(p3_src, coeff3); + + accum += p0; + accum += p1; + accum += p2; + accum += p3; + + // Advance the pointers + rowToFilter += 16; + filterValues += 4; + } + + int r = filterLength & 3; + if (r) { + int remainder_offset = (filterOffset + filterLength - r) * 4; + AccumRemainder(srcData + remainder_offset, filterValues, accum, r); + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum = vshrq_n_s32(accum, SkConvolutionFilter1D::kShiftBits); + + // Pack and store the new pixel. + int16x4_t accum16 = vqmovn_s32(accum); + uint8x8_t accum8 = vqmovun_s16(vcombine_s16(accum16, accum16)); + vst1_lane_u32(reinterpret_cast(outRow), vreinterpret_u32_u8(accum8), 0); + outRow += 4; + } + } + + // Convolves horizontally along four rows. The row data is given in + // |srcData| and continues for the numValues() of the filter. + // The algorithm is almost same as |convolve_horizontally|. Please + // refer to that function for detailed comments. + void convolve_4_rows_horizontally(const unsigned char* srcData[4], + const SkConvolutionFilter1D& filter, + unsigned char* outRow[4], + size_t outRowBytes) { + // Output one pixel each iteration, calculating all channels (RGBA) together. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // four pixels in a column per iteration. + int32x4_t accum0 = vdupq_n_s32(0); + int32x4_t accum1 = vdupq_n_s32(0); + int32x4_t accum2 = vdupq_n_s32(0); + int32x4_t accum3 = vdupq_n_s32(0); + + uint8x8_t coeff_mask0 = vcreate_u8(0x0100010001000100); + uint8x8_t coeff_mask1 = vcreate_u8(0x0302030203020302); + uint8x8_t coeff_mask2 = vcreate_u8(0x0504050405040504); + uint8x8_t coeff_mask3 = vcreate_u8(0x0706070607060706); + + int start = filterOffset * 4; + + // We will load and accumulate with four coefficients per iteration. + for (int filterX = 0; filterX < (filterLength >> 2); filterX++) { + int16x4_t coeffs, coeff0, coeff1, coeff2, coeff3; + + coeffs = vld1_s16(filterValues); + coeff0 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask0)); + coeff1 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask1)); + coeff2 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask2)); + coeff3 = vreinterpret_s16_u8(vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask3)); + + uint8x16_t pixels; + int16x8_t p01_16, p23_16; + int32x4_t p0, p1, p2, p3; + +#define ITERATION(src, accum) \ + pixels = vld1q_u8(src); \ + p01_16 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(pixels))); \ + p23_16 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(pixels))); \ + p0 = vmull_s16(vget_low_s16(p01_16), coeff0); \ + p1 = vmull_s16(vget_high_s16(p01_16), coeff1); \ + p2 = vmull_s16(vget_low_s16(p23_16), coeff2); \ + p3 = vmull_s16(vget_high_s16(p23_16), coeff3); \ + accum += p0; \ + accum += p1; \ + accum += p2; \ + accum += p3 + + ITERATION(srcData[0] + start, accum0); + ITERATION(srcData[1] + start, accum1); + ITERATION(srcData[2] + start, accum2); + ITERATION(srcData[3] + start, accum3); + + start += 16; + filterValues += 4; + } + + int r = filterLength & 3; + if (r) { + int remainder_offset = (filterOffset + filterLength - r) * 4; + AccumRemainder(srcData[0] + remainder_offset, filterValues, accum0, r); + AccumRemainder(srcData[1] + remainder_offset, filterValues, accum1, r); + AccumRemainder(srcData[2] + remainder_offset, filterValues, accum2, r); + AccumRemainder(srcData[3] + remainder_offset, filterValues, accum3, r); + } + + int16x4_t accum16; + uint8x8_t res0, res1, res2, res3; + +#define PACK_RESULT(accum, res) \ + accum = vshrq_n_s32(accum, SkConvolutionFilter1D::kShiftBits); \ + accum16 = vqmovn_s32(accum); \ + res = vqmovun_s16(vcombine_s16(accum16, accum16)); + + PACK_RESULT(accum0, res0); + PACK_RESULT(accum1, res1); + PACK_RESULT(accum2, res2); + PACK_RESULT(accum3, res3); + + vst1_lane_u32(reinterpret_cast(outRow[0]), vreinterpret_u32_u8(res0), 0); + vst1_lane_u32(reinterpret_cast(outRow[1]), vreinterpret_u32_u8(res1), 0); + vst1_lane_u32(reinterpret_cast(outRow[2]), vreinterpret_u32_u8(res2), 0); + vst1_lane_u32(reinterpret_cast(outRow[3]), vreinterpret_u32_u8(res3), 0); + outRow[0] += 4; + outRow[1] += 4; + outRow[2] += 4; + outRow[3] += 4; + } + } + + + // Does vertical convolution to produce one output row. The filter values and + // length are given in the first two parameters. These are applied to each + // of the rows pointed to in the |sourceDataRows| array, with each row + // being |pixelWidth| wide. + // + // The output must have room for |pixelWidth * 4| bytes. + template + void ConvolveVertically(const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, + unsigned char* const* sourceDataRows, + int pixelWidth, + unsigned char* outRow) { + int width = pixelWidth & ~3; + + // Output four pixels per iteration (16 bytes). + for (int outX = 0; outX < width; outX += 4) { + + // Accumulated result for each pixel. 32 bits per RGBA channel. + int32x4_t accum0 = vdupq_n_s32(0); + int32x4_t accum1 = vdupq_n_s32(0); + int32x4_t accum2 = vdupq_n_s32(0); + int32x4_t accum3 = vdupq_n_s32(0); + + // Convolve with one filter coefficient per iteration. + for (int filterY = 0; filterY < filterLength; filterY++) { + + // Duplicate the filter coefficient 4 times. + // [16] cj cj cj cj + int16x4_t coeff16 = vdup_n_s16(filterValues[filterY]); + + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + uint8x16_t src8 = vld1q_u8(&sourceDataRows[filterY][outX << 2]); + + int16x8_t src16_01 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(src8))); + int16x8_t src16_23 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(src8))); + int16x4_t src16_0 = vget_low_s16(src16_01); + int16x4_t src16_1 = vget_high_s16(src16_01); + int16x4_t src16_2 = vget_low_s16(src16_23); + int16x4_t src16_3 = vget_high_s16(src16_23); + + accum0 += vmull_s16(src16_0, coeff16); + accum1 += vmull_s16(src16_1, coeff16); + accum2 += vmull_s16(src16_2, coeff16); + accum3 += vmull_s16(src16_3, coeff16); + } + + // Shift right for fixed point implementation. + accum0 = vshrq_n_s32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = vshrq_n_s32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = vshrq_n_s32(accum2, SkConvolutionFilter1D::kShiftBits); + accum3 = vshrq_n_s32(accum3, SkConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + int16x8_t accum16_0 = vcombine_s16(vqmovn_s32(accum0), vqmovn_s32(accum1)); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + int16x8_t accum16_1 = vcombine_s16(vqmovn_s32(accum2), vqmovn_s32(accum3)); + + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + uint8x16_t accum8 = vcombine_u8(vqmovun_s16(accum16_0), vqmovun_s16(accum16_1)); + + if (hasAlpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + uint8x16_t a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 8)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + uint8x16_t b = vmaxq_u8(a, accum8); // Max of r and g + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 16)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = vmaxq_u8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(b), 24)); + + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum8 = vmaxq_u8(b, accum8); + } else { + // Set value of alpha channels to 0xFF. + accum8 = vreinterpretq_u8_u32(vreinterpretq_u32_u8(accum8) | vdupq_n_u32(0xFF000000)); + } + + // Store the convolution result (16 bytes) and advance the pixel pointers. + vst1q_u8(outRow, accum8); + outRow += 16; + } + + // Process the leftovers when the width of the output is not divisible + // by 4, that is at most 3 pixels. + int r = pixelWidth & 3; + if (r) { + + int32x4_t accum0 = vdupq_n_s32(0); + int32x4_t accum1 = vdupq_n_s32(0); + int32x4_t accum2 = vdupq_n_s32(0); + + for (int filterY = 0; filterY < filterLength; ++filterY) { + int16x4_t coeff16 = vdup_n_s16(filterValues[filterY]); + + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + uint8x16_t src8 = vld1q_u8(&sourceDataRows[filterY][width << 2]); + + int16x8_t src16_01 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(src8))); + int16x8_t src16_23 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(src8))); + int16x4_t src16_0 = vget_low_s16(src16_01); + int16x4_t src16_1 = vget_high_s16(src16_01); + int16x4_t src16_2 = vget_low_s16(src16_23); + + accum0 += vmull_s16(src16_0, coeff16); + accum1 += vmull_s16(src16_1, coeff16); + accum2 += vmull_s16(src16_2, coeff16); + } + + accum0 = vshrq_n_s32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = vshrq_n_s32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = vshrq_n_s32(accum2, SkConvolutionFilter1D::kShiftBits); + + int16x8_t accum16_0 = vcombine_s16(vqmovn_s32(accum0), vqmovn_s32(accum1)); + int16x8_t accum16_1 = vcombine_s16(vqmovn_s32(accum2), vqmovn_s32(accum2)); + + uint8x16_t accum8 = vcombine_u8(vqmovun_s16(accum16_0), vqmovun_s16(accum16_1)); + + if (hasAlpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + uint8x16_t a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 8)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + uint8x16_t b = vmaxq_u8(a, accum8); // Max of r and g + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 16)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = vmaxq_u8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(b), 24)); + + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum8 = vmaxq_u8(b, accum8); + } else { + // Set value of alpha channels to 0xFF. + accum8 = vreinterpretq_u8_u32(vreinterpretq_u32_u8(accum8) | vdupq_n_u32(0xFF000000)); + } + + switch(r) { + case 1: + vst1q_lane_u32(reinterpret_cast(outRow), vreinterpretq_u32_u8(accum8), 0); + break; + case 2: + vst1_u32(reinterpret_cast(outRow), + vreinterpret_u32_u8(vget_low_u8(accum8))); + break; + case 3: + vst1_u32(reinterpret_cast(outRow), + vreinterpret_u32_u8(vget_low_u8(accum8))); + vst1q_lane_u32(reinterpret_cast(outRow+8), vreinterpretq_u32_u8(accum8), 2); + break; + } + } + } + +#else + + // Converts the argument to an 8-bit unsigned value by clamping to the range + // 0-255. + inline unsigned char ClampTo8(int a) { + if (static_cast(a) < 256) { + return a; // Avoid the extra check in the common case. + } + if (a < 0) { + return 0; + } + return 255; + } + + // Convolves horizontally along a single row. The row data is given in + // |srcData| and continues for the numValues() of the filter. + template + void ConvolveHorizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow) { + // Loop over each pixel on this row in the output image. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + // Get the filter that determines the current output pixel. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filterLength| pixels (4 bytes each) after this. + const unsigned char* rowToFilter = &srcData[filterOffset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int accum[4] = {0}; + for (int filterX = 0; filterX < filterLength; filterX++) { + SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX]; + accum[0] += curFilter * rowToFilter[filterX * 4 + 0]; + accum[1] += curFilter * rowToFilter[filterX * 4 + 1]; + accum[2] += curFilter * rowToFilter[filterX * 4 + 2]; + if (hasAlpha) { + accum[3] += curFilter * rowToFilter[filterX * 4 + 3]; + } + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum[0] >>= SkConvolutionFilter1D::kShiftBits; + accum[1] >>= SkConvolutionFilter1D::kShiftBits; + accum[2] >>= SkConvolutionFilter1D::kShiftBits; + if (hasAlpha) { + accum[3] >>= SkConvolutionFilter1D::kShiftBits; + } + + // Store the new pixel. + outRow[outX * 4 + 0] = ClampTo8(accum[0]); + outRow[outX * 4 + 1] = ClampTo8(accum[1]); + outRow[outX * 4 + 2] = ClampTo8(accum[2]); + if (hasAlpha) { + outRow[outX * 4 + 3] = ClampTo8(accum[3]); + } + } + } + + // Does vertical convolution to produce one output row. The filter values and + // length are given in the first two parameters. These are applied to each + // of the rows pointed to in the |sourceDataRows| array, with each row + // being |pixelWidth| wide. + // + // The output must have room for |pixelWidth * 4| bytes. + template + void ConvolveVertically(const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, + unsigned char* const* sourceDataRows, + int pixelWidth, + unsigned char* outRow) { + // We go through each column in the output and do a vertical convolution, + // generating one output pixel each time. + for (int outX = 0; outX < pixelWidth; outX++) { + // Compute the number of bytes over in each row that the current column + // we're convolving starts at. The pixel will cover the next 4 bytes. + int byteOffset = outX * 4; + + // Apply the filter to one column of pixels. + int accum[4] = {0}; + for (int filterY = 0; filterY < filterLength; filterY++) { + SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY]; + accum[0] += curFilter * sourceDataRows[filterY][byteOffset + 0]; + accum[1] += curFilter * sourceDataRows[filterY][byteOffset + 1]; + accum[2] += curFilter * sourceDataRows[filterY][byteOffset + 2]; + if (hasAlpha) { + accum[3] += curFilter * sourceDataRows[filterY][byteOffset + 3]; + } + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of precision. + accum[0] >>= SkConvolutionFilter1D::kShiftBits; + accum[1] >>= SkConvolutionFilter1D::kShiftBits; + accum[2] >>= SkConvolutionFilter1D::kShiftBits; + if (hasAlpha) { + accum[3] >>= SkConvolutionFilter1D::kShiftBits; + } + + // Store the new pixel. + outRow[byteOffset + 0] = ClampTo8(accum[0]); + outRow[byteOffset + 1] = ClampTo8(accum[1]); + outRow[byteOffset + 2] = ClampTo8(accum[2]); + if (hasAlpha) { + unsigned char alpha = ClampTo8(accum[3]); + + // Make sure the alpha channel doesn't come out smaller than any of the + // color channels. We use premultipled alpha channels, so this should + // never happen, but rounding errors will cause this from time to time. + // These "impossible" colors will cause overflows (and hence random pixel + // values) when the resulting bitmap is drawn to the screen. + // + // We only need to do this when generating the final output row (here). + int maxColorChannel = SkTMax(outRow[byteOffset + 0], + SkTMax(outRow[byteOffset + 1], + outRow[byteOffset + 2])); + if (alpha < maxColorChannel) { + outRow[byteOffset + 3] = maxColorChannel; + } else { + outRow[byteOffset + 3] = alpha; + } + } else { + // No alpha channel, the image is opaque. + outRow[byteOffset + 3] = 0xff; + } + } + } + + void convolve_horizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, + bool hasAlpha) { + if (hasAlpha) { + ConvolveHorizontally(srcData, filter, outRow); + } else { + ConvolveHorizontally(srcData, filter, outRow); + } + } + + void (*convolve_4_rows_horizontally)(const unsigned char* srcData[4], + const SkConvolutionFilter1D& filter, + unsigned char* outRow[4], + size_t outRowBytes) + = nullptr; + + +#endif + + void convolve_vertically(const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, + unsigned char* const* sourceDataRows, + int pixelWidth, + unsigned char* outRow, + bool hasAlpha) { + if (hasAlpha) { + ConvolveVertically(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } else { + ConvolveVertically(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } + } + +} // namespace SK_OPTS_NS + +#endif//SkBitmapFilter_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkBitmapProcState_opts.h b/gfx/skia/skia/src/opts/SkBitmapProcState_opts.h new file mode 100644 index 0000000000..85313bf99d --- /dev/null +++ b/gfx/skia/skia/src/opts/SkBitmapProcState_opts.h @@ -0,0 +1,502 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitmapProcState_opts_DEFINED +#define SkBitmapProcState_opts_DEFINED + +#include "include/private/SkVx.h" +#include "src/core/SkBitmapProcState.h" +#include "src/core/SkMSAN.h" + +// SkBitmapProcState optimized Shader, Sample, or Matrix procs. +// +// Only S32_alpha_D32_filter_DX exploits instructions beyond +// our common baseline SSE2/NEON instruction sets, so that's +// all that lives here. +// +// The rest are scattershot at the moment but I want to get them +// all migrated to be normal code inside SkBitmapProcState.cpp. + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include +#elif defined(SK_ARM_HAS_NEON) + #include +#endif + +namespace SK_OPTS_NS { + +// This same basic packing scheme is used throughout the file. +template +static void decode_packed_coordinates_and_weight(U32 packed, Out* v0, Out* v1, Out* w) { + *v0 = (packed >> 18); // Integer coordinate x0 or y0. + *v1 = (packed & 0x3fff); // Integer coordinate x1 or y1. + *w = (packed >> 14) & 0xf; // Lerp weight for v1; weight for v0 is 16-w. +} + +#if 1 && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + /*not static*/ inline + void S32_alpha_D32_filter_DX(const SkBitmapProcState& s, + const uint32_t* xy, int count, uint32_t* colors) { + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(s.fFilterQuality != kNone_SkFilterQuality); + SkASSERT(kN32_SkColorType == s.fPixmap.colorType()); + SkASSERT(s.fAlphaScale <= 256); + + // In a _DX variant only X varies; all samples share y0/y1 coordinates and wy weight. + int y0, y1, wy; + decode_packed_coordinates_and_weight(*xy++, &y0, &y1, &wy); + + const uint32_t* row0 = s.fPixmap.addr32(0,y0); + const uint32_t* row1 = s.fPixmap.addr32(0,y1); + + auto bilerp = [&](skvx::Vec<8,uint32_t> packed_x_coordinates) -> skvx::Vec<8,uint32_t> { + // Decode up to 8 output pixels' x-coordinates and weights. + skvx::Vec<8,uint32_t> x0,x1,wx; + decode_packed_coordinates_and_weight(packed_x_coordinates, &x0, &x1, &wx); + + // Splat wx to each color channel. + wx = (wx << 0) + | (wx << 8) + | (wx << 16) + | (wx << 24); + + auto gather = [](const uint32_t* ptr, skvx::Vec<8,uint32_t> ix) { + #if 1 + // Drop into AVX2 intrinsics for vpgatherdd. + return skvx::bit_pun>( + _mm256_i32gather_epi32((const int*)ptr, skvx::bit_pun<__m256i>(ix), 4)); + #else + // Portable version... sometimes I don't trust vpgatherdd. + return skvx::Vec<8,uint32_t>{ + ptr[ix[0]], ptr[ix[1]], ptr[ix[2]], ptr[ix[3]], + ptr[ix[4]], ptr[ix[5]], ptr[ix[6]], ptr[ix[7]], + }; + #endif + }; + + // Gather the 32 32-bit pixels that we'll bilerp into our 8 output pixels. + skvx::Vec<8,uint32_t> tl = gather(row0, x0), tr = gather(row0, x1), + bl = gather(row1, x0), br = gather(row1, x1); + + #if 1 + // We'll use _mm256_maddubs_epi16() to lerp much like in the SSSE3 code. + auto lerp_x = [&](skvx::Vec<8,uint32_t> L, skvx::Vec<8,uint32_t> R) { + __m256i l = skvx::bit_pun<__m256i>(L), + r = skvx::bit_pun<__m256i>(R), + wr = skvx::bit_pun<__m256i>(wx), + wl = _mm256_sub_epi8(_mm256_set1_epi8(16), wr); + + // Interlace l,r bytewise and line them up with their weights, then lerp. + __m256i lo = _mm256_maddubs_epi16(_mm256_unpacklo_epi8( l, r), + _mm256_unpacklo_epi8(wl,wr)); + __m256i hi = _mm256_maddubs_epi16(_mm256_unpackhi_epi8( l, r), + _mm256_unpackhi_epi8(wl,wr)); + + // Those _mm256_unpack??_epi8() calls left us in a bit of an odd order: + // + // if l = a b c d | e f g h + // and r = A B C D | E F G H + // + // then lo = a A b B | e E f F (low half of each input) + // and hi = c C d D | g G h H (high half of each input) + // + // To get everything back in original order we need to transpose that. + __m256i abcd = _mm256_permute2x128_si256(lo, hi, 0x20), + efgh = _mm256_permute2x128_si256(lo, hi, 0x31); + + return skvx::join(skvx::bit_pun>(abcd), + skvx::bit_pun>(efgh)); + }; + + skvx::Vec<32, uint16_t> top = lerp_x(tl, tr), + bot = lerp_x(bl, br), + sum = 16*top + (bot-top)*wy; + #else + // Treat 32-bit pixels as 4 8-bit values, and expand to 16-bit for room to multiply. + auto to_16x4 = [](auto v) -> skvx::Vec<32, uint16_t> { + return skvx::cast(skvx::bit_pun>(v)); + }; + + // Sum up weighted sample pixels. The naive, redundant math would be, + // + // sum = tl * (16-wy) * (16-wx) + // + bl * ( wy) * (16-wx) + // + tr * (16-wy) * ( wx) + // + br * ( wy) * ( wx) + // + // But we refactor to eliminate a bunch of those common factors. + auto lerp = [](auto lo, auto hi, auto w) { + return 16*lo + (hi-lo)*w; + }; + skvx::Vec<32, uint16_t> sum = lerp(lerp(to_16x4(tl), to_16x4(bl), wy), + lerp(to_16x4(tr), to_16x4(br), wy), to_16x4(wx)); + #endif + + // Get back to [0,255] by dividing by maximum weight 16x16 = 256. + sum >>= 8; + + // Scale by [0,256] alpha. + sum *= s.fAlphaScale; + sum >>= 8; + + // Pack back to 8-bit channels, undoing to_16x4(). + return skvx::bit_pun>(skvx::cast(sum)); + }; + + while (count >= 8) { + bilerp(skvx::Vec<8,uint32_t>::Load(xy)).store(colors); + xy += 8; + colors += 8; + count -= 8; + } + if (count > 0) { + __m256i active = skvx::bit_pun<__m256i>( count > skvx::Vec<8,int>{0,1,2,3, 4,5,6,7} ), + coords = _mm256_maskload_epi32((const int*)xy, active), + pixels; + + bilerp(skvx::bit_pun>(coords)).store(&pixels); + _mm256_maskstore_epi32((int*)colors, active, pixels); + + sk_msan_mark_initialized(colors, colors+count, + "MSAN still doesn't understand AVX2 mask loads and stores."); + } + } + +#elif 1 && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + + /*not static*/ inline + void S32_alpha_D32_filter_DX(const SkBitmapProcState& s, + const uint32_t* xy, int count, uint32_t* colors) { + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(s.fFilterQuality != kNone_SkFilterQuality); + SkASSERT(kN32_SkColorType == s.fPixmap.colorType()); + SkASSERT(s.fAlphaScale <= 256); + + // interpolate_in_x() is the crux of the SSSE3 implementation, + // interpolating in X for up to two output pixels (A and B) using _mm_maddubs_epi16(). + auto interpolate_in_x = [](uint32_t A0, uint32_t A1, + uint32_t B0, uint32_t B1, + __m128i interlaced_x_weights) { + // _mm_maddubs_epi16() is a little idiosyncratic, but great as the core of a lerp. + // + // It takes two arguments interlaced byte-wise: + // - first arg: [ l,r, ... 7 more pairs of unsigned 8-bit values ...] + // - second arg: [ w,W, ... 7 more pairs of signed 8-bit values ...] + // and returns 8 signed 16-bit values: [ l*w + r*W, ... 7 more ... ]. + // + // That's why we go to all this trouble to make interlaced_x_weights, + // and here we're about to interlace A0 with A1 and B0 with B1 to match. + // + // Our interlaced_x_weights are all in [0,16], and so we need not worry about + // the signedness of that input nor about the signedness of the output. + + __m128i interlaced_A = _mm_unpacklo_epi8(_mm_cvtsi32_si128(A0), _mm_cvtsi32_si128(A1)), + interlaced_B = _mm_unpacklo_epi8(_mm_cvtsi32_si128(B0), _mm_cvtsi32_si128(B1)); + + return _mm_maddubs_epi16(_mm_unpacklo_epi64(interlaced_A, interlaced_B), + interlaced_x_weights); + }; + + // Interpolate {A0..A3} --> output pixel A, and {B0..B3} --> output pixel B. + // Returns two pixels, with each color channel in a 16-bit lane of the __m128i. + auto interpolate_in_x_and_y = [&](uint32_t A0, uint32_t A1, + uint32_t A2, uint32_t A3, + uint32_t B0, uint32_t B1, + uint32_t B2, uint32_t B3, + __m128i interlaced_x_weights, + int wy) { + // Interpolate each row in X, leaving 16-bit lanes scaled by interlaced_x_weights. + __m128i top = interpolate_in_x(A0,A1, B0,B1, interlaced_x_weights), + bot = interpolate_in_x(A2,A3, B2,B3, interlaced_x_weights); + + // Interpolate in Y. As in the SSE2 code, we calculate top*(16-wy) + bot*wy + // as 16*top + (bot-top)*wy to save a multiply. + __m128i px = _mm_add_epi16(_mm_slli_epi16(top, 4), + _mm_mullo_epi16(_mm_sub_epi16(bot, top), + _mm_set1_epi16(wy))); + + // Scale down by total max weight 16x16 = 256. + px = _mm_srli_epi16(px, 8); + + // Scale by alpha if needed. + if (s.fAlphaScale < 256) { + px = _mm_srli_epi16(_mm_mullo_epi16(px, _mm_set1_epi16(s.fAlphaScale)), 8); + } + return px; + }; + + // We're in _DX mode here, so we're only varying in X. + // That means the first entry of xy is our constant pair of Y coordinates and weight in Y. + // All the other entries in xy will be pairs of X coordinates and the X weight. + int y0, y1, wy; + decode_packed_coordinates_and_weight(*xy++, &y0, &y1, &wy); + + auto row0 = (const uint32_t*)((const uint8_t*)s.fPixmap.addr() + y0 * s.fPixmap.rowBytes()), + row1 = (const uint32_t*)((const uint8_t*)s.fPixmap.addr() + y1 * s.fPixmap.rowBytes()); + + while (count >= 4) { + // We can really get going, loading 4 X-pairs at a time to produce 4 output pixels. + int x0[4], + x1[4]; + __m128i wx; + + // decode_packed_coordinates_and_weight(), 4x. + __m128i packed = _mm_loadu_si128((const __m128i*)xy); + _mm_storeu_si128((__m128i*)x0, _mm_srli_epi32(packed, 18)); + _mm_storeu_si128((__m128i*)x1, _mm_and_si128 (packed, _mm_set1_epi32(0x3fff))); + wx = _mm_and_si128(_mm_srli_epi32(packed, 14), _mm_set1_epi32(0xf)); // [0,15] + + // Splat each x weight 4x (for each color channel) as wr for pixels on the right at x1, + // and sixteen minus that as wl for pixels on the left at x0. + __m128i wr = _mm_shuffle_epi8(wx, _mm_setr_epi8(0,0,0,0,4,4,4,4,8,8,8,8,12,12,12,12)), + wl = _mm_sub_epi8(_mm_set1_epi8(16), wr); + + // We need to interlace wl and wr for _mm_maddubs_epi16(). + __m128i interlaced_x_weights_AB = _mm_unpacklo_epi8(wl,wr), + interlaced_x_weights_CD = _mm_unpackhi_epi8(wl,wr); + + enum { A,B,C,D }; + + // interpolate_in_x_and_y() can produce two output pixels (A and B) at a time + // from eight input pixels {A0..A3} and {B0..B3}, arranged in a 2x2 grid for each. + __m128i AB = interpolate_in_x_and_y(row0[x0[A]], row0[x1[A]], + row1[x0[A]], row1[x1[A]], + row0[x0[B]], row0[x1[B]], + row1[x0[B]], row1[x1[B]], + interlaced_x_weights_AB, wy); + + // Once more with the other half of the x-weights for two more pixels C,D. + __m128i CD = interpolate_in_x_and_y(row0[x0[C]], row0[x1[C]], + row1[x0[C]], row1[x1[C]], + row0[x0[D]], row0[x1[D]], + row1[x0[D]], row1[x1[D]], + interlaced_x_weights_CD, wy); + + // Scale by alpha, pack back together to 8-bit lanes, and write out four pixels! + _mm_storeu_si128((__m128i*)colors, _mm_packus_epi16(AB, CD)); + xy += 4; + colors += 4; + count -= 4; + } + + while (count --> 0) { + // This is exactly the same flow as the count >= 4 loop above, but writing one pixel. + int x0, x1, wx; + decode_packed_coordinates_and_weight(*xy++, &x0, &x1, &wx); + + // As above, splat out wx four times as wr, and sixteen minus that as wl. + __m128i wr = _mm_set1_epi8(wx), // This splats it out 16 times, but that's fine. + wl = _mm_sub_epi8(_mm_set1_epi8(16), wr); + + __m128i interlaced_x_weights = _mm_unpacklo_epi8(wl, wr); + + __m128i A = interpolate_in_x_and_y(row0[x0], row0[x1], + row1[x0], row1[x1], + 0, 0, + 0, 0, + interlaced_x_weights, wy); + + *colors++ = _mm_cvtsi128_si32(_mm_packus_epi16(A, _mm_setzero_si128())); + } + } + + +#elif 1 && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + + /*not static*/ inline + void S32_alpha_D32_filter_DX(const SkBitmapProcState& s, + const uint32_t* xy, int count, uint32_t* colors) { + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(s.fFilterQuality != kNone_SkFilterQuality); + SkASSERT(kN32_SkColorType == s.fPixmap.colorType()); + SkASSERT(s.fAlphaScale <= 256); + + int y0, y1, wy; + decode_packed_coordinates_and_weight(*xy++, &y0, &y1, &wy); + + auto row0 = (const uint32_t*)( (const char*)s.fPixmap.addr() + y0 * s.fPixmap.rowBytes() ), + row1 = (const uint32_t*)( (const char*)s.fPixmap.addr() + y1 * s.fPixmap.rowBytes() ); + + // We'll put one pixel in the low 4 16-bit lanes to line up with wy, + // and another in the upper 4 16-bit lanes to line up with 16 - wy. + const __m128i allY = _mm_unpacklo_epi64(_mm_set1_epi16( wy), // Bottom pixel goes here. + _mm_set1_epi16(16-wy)); // Top pixel goes here. + + while (count --> 0) { + int x0, x1, wx; + decode_packed_coordinates_and_weight(*xy++, &x0, &x1, &wx); + + // Load the 4 pixels we're interpolating, in this grid: + // | tl tr | + // | bl br | + const __m128i tl = _mm_cvtsi32_si128(row0[x0]), tr = _mm_cvtsi32_si128(row0[x1]), + bl = _mm_cvtsi32_si128(row1[x0]), br = _mm_cvtsi32_si128(row1[x1]); + + // We want to calculate a sum of 4 pixels weighted in two directions: + // + // sum = tl * (16-wy) * (16-wx) + // + bl * ( wy) * (16-wx) + // + tr * (16-wy) * ( wx) + // + br * ( wy) * ( wx) + // + // (Notice top --> 16-wy, bottom --> wy, left --> 16-wx, right --> wx.) + // + // We've already prepared allY as a vector containing [wy, 16-wy] as a way + // to apply those y-direction weights. So we'll start on the x-direction + // first, grouping into left and right halves, lined up with allY: + // + // L = [bl, tl] + // R = [br, tr] + // + // sum = horizontalSum( allY * (L*(16-wx) + R*wx) ) + // + // Rewriting that one more step, we can replace a multiply with a shift: + // + // sum = horizontalSum( allY * (16*L + (R-L)*wx) ) + // + // That's how we'll actually do this math. + + __m128i L = _mm_unpacklo_epi8(_mm_unpacklo_epi32(bl, tl), _mm_setzero_si128()), + R = _mm_unpacklo_epi8(_mm_unpacklo_epi32(br, tr), _mm_setzero_si128()); + + __m128i inner = _mm_add_epi16(_mm_slli_epi16(L, 4), + _mm_mullo_epi16(_mm_sub_epi16(R,L), _mm_set1_epi16(wx))); + + __m128i sum_in_x = _mm_mullo_epi16(inner, allY); + + // sum = horizontalSum( ... ) + __m128i sum = _mm_add_epi16(sum_in_x, _mm_srli_si128(sum_in_x, 8)); + + // Get back to [0,255] by dividing by maximum weight 16x16 = 256. + sum = _mm_srli_epi16(sum, 8); + + if (s.fAlphaScale < 256) { + // Scale by alpha, which is in [0,256]. + sum = _mm_mullo_epi16(sum, _mm_set1_epi16(s.fAlphaScale)); + sum = _mm_srli_epi16(sum, 8); + } + + // Pack back into 8-bit values and store. + *colors++ = _mm_cvtsi128_si32(_mm_packus_epi16(sum, _mm_setzero_si128())); + } + } + +#else + + // The NEON code only actually differs from the portable code in the + // filtering step after we've loaded all four pixels we want to bilerp. + + #if defined(SK_ARM_HAS_NEON) + static void filter_and_scale_by_alpha(unsigned x, unsigned y, + SkPMColor a00, SkPMColor a01, + SkPMColor a10, SkPMColor a11, + SkPMColor *dst, + uint16_t scale) { + uint8x8_t vy, vconst16_8, v16_y, vres; + uint16x4_t vx, vconst16_16, v16_x, tmp, vscale; + uint32x2_t va0, va1; + uint16x8_t tmp1, tmp2; + + vy = vdup_n_u8(y); // duplicate y into vy + vconst16_8 = vmov_n_u8(16); // set up constant in vconst16_8 + v16_y = vsub_u8(vconst16_8, vy); // v16_y = 16-y + + va0 = vdup_n_u32(a00); // duplicate a00 + va1 = vdup_n_u32(a10); // duplicate a10 + va0 = vset_lane_u32(a01, va0, 1); // set top to a01 + va1 = vset_lane_u32(a11, va1, 1); // set top to a11 + + tmp1 = vmull_u8(vreinterpret_u8_u32(va0), v16_y); // tmp1 = [a01|a00] * (16-y) + tmp2 = vmull_u8(vreinterpret_u8_u32(va1), vy); // tmp2 = [a11|a10] * y + + vx = vdup_n_u16(x); // duplicate x into vx + vconst16_16 = vmov_n_u16(16); // set up constant in vconst16_16 + v16_x = vsub_u16(vconst16_16, vx); // v16_x = 16-x + + tmp = vmul_u16(vget_high_u16(tmp1), vx); // tmp = a01 * x + tmp = vmla_u16(tmp, vget_high_u16(tmp2), vx); // tmp += a11 * x + tmp = vmla_u16(tmp, vget_low_u16(tmp1), v16_x); // tmp += a00 * (16-x) + tmp = vmla_u16(tmp, vget_low_u16(tmp2), v16_x); // tmp += a10 * (16-x) + + if (scale < 256) { + vscale = vdup_n_u16(scale); // duplicate scale + tmp = vshr_n_u16(tmp, 8); // shift down result by 8 + tmp = vmul_u16(tmp, vscale); // multiply result by scale + } + + vres = vshrn_n_u16(vcombine_u16(tmp, vcreate_u16(0)), 8); // shift down result by 8 + vst1_lane_u32(dst, vreinterpret_u32_u8(vres), 0); // store result + } + #else + static void filter_and_scale_by_alpha(unsigned x, unsigned y, + SkPMColor a00, SkPMColor a01, + SkPMColor a10, SkPMColor a11, + SkPMColor* dstColor, + unsigned alphaScale) { + SkASSERT((unsigned)x <= 0xF); + SkASSERT((unsigned)y <= 0xF); + SkASSERT(alphaScale <= 256); + + int xy = x * y; + const uint32_t mask = 0xFF00FF; + + int scale = 256 - 16*y - 16*x + xy; + uint32_t lo = (a00 & mask) * scale; + uint32_t hi = ((a00 >> 8) & mask) * scale; + + scale = 16*x - xy; + lo += (a01 & mask) * scale; + hi += ((a01 >> 8) & mask) * scale; + + scale = 16*y - xy; + lo += (a10 & mask) * scale; + hi += ((a10 >> 8) & mask) * scale; + + lo += (a11 & mask) * xy; + hi += ((a11 >> 8) & mask) * xy; + + if (alphaScale < 256) { + lo = ((lo >> 8) & mask) * alphaScale; + hi = ((hi >> 8) & mask) * alphaScale; + } + + *dstColor = ((lo >> 8) & mask) | (hi & ~mask); + } + #endif + + + /*not static*/ inline + void S32_alpha_D32_filter_DX(const SkBitmapProcState& s, + const uint32_t* xy, int count, SkPMColor* colors) { + SkASSERT(count > 0 && colors != nullptr); + SkASSERT(s.fFilterQuality != kNone_SkFilterQuality); + SkASSERT(4 == s.fPixmap.info().bytesPerPixel()); + SkASSERT(s.fAlphaScale <= 256); + + int y0, y1, wy; + decode_packed_coordinates_and_weight(*xy++, &y0, &y1, &wy); + + auto row0 = (const uint32_t*)( (const char*)s.fPixmap.addr() + y0 * s.fPixmap.rowBytes() ), + row1 = (const uint32_t*)( (const char*)s.fPixmap.addr() + y1 * s.fPixmap.rowBytes() ); + + while (count --> 0) { + int x0, x1, wx; + decode_packed_coordinates_and_weight(*xy++, &x0, &x1, &wx); + + filter_and_scale_by_alpha(wx, wy, + row0[x0], row0[x1], + row1[x0], row1[x1], + colors++, + s.fAlphaScale); + } + } + +#endif + +} // namespace SK_OPTS_NS + +#endif diff --git a/gfx/skia/skia/src/opts/SkBlitMask_opts.h b/gfx/skia/skia/src/opts/SkBlitMask_opts.h new file mode 100644 index 0000000000..2dfe5ecf96 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkBlitMask_opts.h @@ -0,0 +1,231 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlitMask_opts_DEFINED +#define SkBlitMask_opts_DEFINED + +#include "src/core/Sk4px.h" + +namespace SK_OPTS_NS { + +#if defined(SK_ARM_HAS_NEON) + // The Sk4px versions below will work fine with NEON, but we have had many indications + // that it doesn't perform as well as this NEON-specific code. TODO(mtklein): why? + + #define NEON_A (SK_A32_SHIFT / 8) + #define NEON_R (SK_R32_SHIFT / 8) + #define NEON_G (SK_G32_SHIFT / 8) + #define NEON_B (SK_B32_SHIFT / 8) + + static inline uint16x8_t SkAlpha255To256_neon8(uint8x8_t alpha) { + return vaddw_u8(vdupq_n_u16(1), alpha); + } + + static inline uint8x8_t SkAlphaMul_neon8(uint8x8_t color, uint16x8_t scale) { + return vshrn_n_u16(vmovl_u8(color) * scale, 8); + } + + static inline uint8x8x4_t SkAlphaMulQ_neon8(uint8x8x4_t color, uint16x8_t scale) { + uint8x8x4_t ret; + + ret.val[0] = SkAlphaMul_neon8(color.val[0], scale); + ret.val[1] = SkAlphaMul_neon8(color.val[1], scale); + ret.val[2] = SkAlphaMul_neon8(color.val[2], scale); + ret.val[3] = SkAlphaMul_neon8(color.val[3], scale); + + return ret; + } + + + template + static void D32_A8_Opaque_Color_neon(void* SK_RESTRICT dst, size_t dstRB, + const void* SK_RESTRICT maskPtr, size_t maskRB, + SkColor color, int width, int height) { + SkPMColor pmc = SkPreMultiplyColor(color); + SkPMColor* SK_RESTRICT device = (SkPMColor*)dst; + const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr; + uint8x8x4_t vpmc; + + maskRB -= width; + dstRB -= (width << 2); + + if (width >= 8) { + vpmc.val[NEON_A] = vdup_n_u8(SkGetPackedA32(pmc)); + vpmc.val[NEON_R] = vdup_n_u8(SkGetPackedR32(pmc)); + vpmc.val[NEON_G] = vdup_n_u8(SkGetPackedG32(pmc)); + vpmc.val[NEON_B] = vdup_n_u8(SkGetPackedB32(pmc)); + } + do { + int w = width; + while (w >= 8) { + uint8x8_t vmask = vld1_u8(mask); + uint16x8_t vscale, vmask256 = SkAlpha255To256_neon8(vmask); + if (isColor) { + vscale = vsubw_u8(vdupq_n_u16(256), + SkAlphaMul_neon8(vpmc.val[NEON_A], vmask256)); + } else { + vscale = vsubw_u8(vdupq_n_u16(256), vmask); + } + uint8x8x4_t vdev = vld4_u8((uint8_t*)device); + + vdev.val[NEON_A] = SkAlphaMul_neon8(vpmc.val[NEON_A], vmask256) + + SkAlphaMul_neon8(vdev.val[NEON_A], vscale); + vdev.val[NEON_R] = SkAlphaMul_neon8(vpmc.val[NEON_R], vmask256) + + SkAlphaMul_neon8(vdev.val[NEON_R], vscale); + vdev.val[NEON_G] = SkAlphaMul_neon8(vpmc.val[NEON_G], vmask256) + + SkAlphaMul_neon8(vdev.val[NEON_G], vscale); + vdev.val[NEON_B] = SkAlphaMul_neon8(vpmc.val[NEON_B], vmask256) + + SkAlphaMul_neon8(vdev.val[NEON_B], vscale); + + vst4_u8((uint8_t*)device, vdev); + + mask += 8; + device += 8; + w -= 8; + } + + while (w--) { + unsigned aa = *mask++; + if (isColor) { + *device = SkBlendARGB32(pmc, *device, aa); + } else { + *device = SkAlphaMulQ(pmc, SkAlpha255To256(aa)) + + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa)); + } + device += 1; + } + + device = (uint32_t*)((char*)device + dstRB); + mask += maskRB; + + } while (--height != 0); + } + + static void blit_mask_d32_a8_general(SkPMColor* dst, size_t dstRB, + const SkAlpha* mask, size_t maskRB, + SkColor color, int w, int h) { + D32_A8_Opaque_Color_neon(dst, dstRB, mask, maskRB, color, w, h); + } + + // As above, but made slightly simpler by requiring that color is opaque. + static void blit_mask_d32_a8_opaque(SkPMColor* dst, size_t dstRB, + const SkAlpha* mask, size_t maskRB, + SkColor color, int w, int h) { + D32_A8_Opaque_Color_neon(dst, dstRB, mask, maskRB, color, w, h); + } + + // Same as _opaque, but assumes color == SK_ColorBLACK, a very common and even simpler case. + static void blit_mask_d32_a8_black(SkPMColor* dst, size_t dstRB, + const SkAlpha* maskPtr, size_t maskRB, + int width, int height) { + SkPMColor* SK_RESTRICT device = (SkPMColor*)dst; + const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr; + + maskRB -= width; + dstRB -= (width << 2); + do { + int w = width; + while (w >= 8) { + uint8x8_t vmask = vld1_u8(mask); + uint16x8_t vscale = vsubw_u8(vdupq_n_u16(256), vmask); + uint8x8x4_t vdevice = vld4_u8((uint8_t*)device); + + vdevice = SkAlphaMulQ_neon8(vdevice, vscale); + vdevice.val[NEON_A] += vmask; + + vst4_u8((uint8_t*)device, vdevice); + + mask += 8; + device += 8; + w -= 8; + } + while (w-- > 0) { + unsigned aa = *mask++; + *device = (aa << SK_A32_SHIFT) + + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa)); + device += 1; + } + device = (uint32_t*)((char*)device + dstRB); + mask += maskRB; + } while (--height != 0); + } + +#else + static void blit_mask_d32_a8_general(SkPMColor* dst, size_t dstRB, + const SkAlpha* mask, size_t maskRB, + SkColor color, int w, int h) { + auto s = Sk4px::DupPMColor(SkPreMultiplyColor(color)); + auto fn = [&](const Sk4px& d, const Sk4px& aa) { + // = (s + d(1-sa))aa + d(1-aa) + // = s*aa + d(1-sa*aa) + auto left = s.approxMulDiv255(aa), + right = d.approxMulDiv255(left.alphas().inv()); + return left + right; // This does not overflow (exhaustively checked). + }; + while (h --> 0) { + Sk4px::MapDstAlpha(w, dst, mask, fn); + dst += dstRB / sizeof(*dst); + mask += maskRB / sizeof(*mask); + } + } + + // As above, but made slightly simpler by requiring that color is opaque. + static void blit_mask_d32_a8_opaque(SkPMColor* dst, size_t dstRB, + const SkAlpha* mask, size_t maskRB, + SkColor color, int w, int h) { + SkASSERT(SkColorGetA(color) == 0xFF); + auto s = Sk4px::DupPMColor(SkPreMultiplyColor(color)); + auto fn = [&](const Sk4px& d, const Sk4px& aa) { + // = (s + d(1-sa))aa + d(1-aa) + // = s*aa + d(1-sa*aa) + // ~~~> + // = s*aa + d(1-aa) + return s.approxMulDiv255(aa) + d.approxMulDiv255(aa.inv()); + }; + while (h --> 0) { + Sk4px::MapDstAlpha(w, dst, mask, fn); + dst += dstRB / sizeof(*dst); + mask += maskRB / sizeof(*mask); + } + } + + // Same as _opaque, but assumes color == SK_ColorBLACK, a very common and even simpler case. + static void blit_mask_d32_a8_black(SkPMColor* dst, size_t dstRB, + const SkAlpha* mask, size_t maskRB, + int w, int h) { + auto fn = [](const Sk4px& d, const Sk4px& aa) { + // = (s + d(1-sa))aa + d(1-aa) + // = s*aa + d(1-sa*aa) + // ~~~> + // a = 1*aa + d(1-1*aa) = aa + d(1-aa) + // c = 0*aa + d(1-1*aa) = d(1-aa) + return Sk4px(Sk16b(aa) & Sk16b(0,0,0,255, 0,0,0,255, 0,0,0,255, 0,0,0,255)) + + d.approxMulDiv255(aa.inv()); + }; + while (h --> 0) { + Sk4px::MapDstAlpha(w, dst, mask, fn); + dst += dstRB / sizeof(*dst); + mask += maskRB / sizeof(*mask); + } + } +#endif + +/*not static*/ inline void blit_mask_d32_a8(SkPMColor* dst, size_t dstRB, + const SkAlpha* mask, size_t maskRB, + SkColor color, int w, int h) { + if (color == SK_ColorBLACK) { + blit_mask_d32_a8_black(dst, dstRB, mask, maskRB, w, h); + } else if (SkColorGetA(color) == 0xFF) { + blit_mask_d32_a8_opaque(dst, dstRB, mask, maskRB, color, w, h); + } else { + blit_mask_d32_a8_general(dst, dstRB, mask, maskRB, color, w, h); + } +} + +} // SK_OPTS_NS + +#endif//SkBlitMask_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkBlitRow_opts.h b/gfx/skia/skia/src/opts/SkBlitRow_opts.h new file mode 100644 index 0000000000..2e9e182582 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkBlitRow_opts.h @@ -0,0 +1,202 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBlitRow_opts_DEFINED +#define SkBlitRow_opts_DEFINED + +#include "include/private/SkColorData.h" +#include "include/private/SkVx.h" +#include "src/core/SkMSAN.h" +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + #include + + static inline __m256i SkPMSrcOver_AVX2(const __m256i& src, const __m256i& dst) { + // Abstractly srcover is + // b = s + d*(1-srcA) + // + // In terms of unorm8 bytes, that works out to + // b = s + (d*(255-srcA) + 127) / 255 + // + // But we approximate that to within a bit with + // b = s + (d*(255-srcA) + d) / 256 + // a.k.a + // b = s + (d*(256-srcA)) >> 8 + + // The bottleneck of this math is the multiply, and we want to do it as + // narrowly as possible, here getting inputs into 16-bit lanes and + // using 16-bit multiplies. We can do twice as many multiplies at once + // as using naive 32-bit multiplies, and on top of that, the 16-bit multiplies + // are themselves a couple cycles quicker. Win-win. + + // We'll get everything in 16-bit lanes for two multiplies, one + // handling dst red and blue, the other green and alpha. (They're + // conveniently 16-bits apart, you see.) We don't need the individual + // src channels beyond alpha until the very end when we do the "s + " + // add, and we don't even need to unpack them; the adds cannot overflow. + + // Shuffle each pixel's srcA to the low byte of each 16-bit half of the pixel. + const int _ = -1; // fills a literal 0 byte. + __m256i srcA_x2 = _mm256_shuffle_epi8(src, + _mm256_setr_epi8(3,_,3,_, 7,_,7,_, 11,_,11,_, 15,_,15,_, + 3,_,3,_, 7,_,7,_, 11,_,11,_, 15,_,15,_)); + __m256i scale_x2 = _mm256_sub_epi16(_mm256_set1_epi16(256), + srcA_x2); + + // Scale red and blue, leaving results in the low byte of each 16-bit lane. + __m256i rb = _mm256_and_si256(_mm256_set1_epi32(0x00ff00ff), dst); + rb = _mm256_mullo_epi16(rb, scale_x2); + rb = _mm256_srli_epi16 (rb, 8); + + // Scale green and alpha, leaving results in the high byte, masking off the low bits. + __m256i ga = _mm256_srli_epi16(dst, 8); + ga = _mm256_mullo_epi16(ga, scale_x2); + ga = _mm256_andnot_si256(_mm256_set1_epi32(0x00ff00ff), ga); + + return _mm256_add_epi32(src, _mm256_or_si256(rb, ga)); + } +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #include + + static inline __m128i SkPMSrcOver_SSE2(const __m128i& src, const __m128i& dst) { + __m128i scale = _mm_sub_epi32(_mm_set1_epi32(256), + _mm_srli_epi32(src, 24)); + __m128i scale_x2 = _mm_or_si128(_mm_slli_epi32(scale, 16), scale); + + __m128i rb = _mm_and_si128(_mm_set1_epi32(0x00ff00ff), dst); + rb = _mm_mullo_epi16(rb, scale_x2); + rb = _mm_srli_epi16(rb, 8); + + __m128i ga = _mm_srli_epi16(dst, 8); + ga = _mm_mullo_epi16(ga, scale_x2); + ga = _mm_andnot_si128(_mm_set1_epi32(0x00ff00ff), ga); + + return _mm_add_epi32(src, _mm_or_si128(rb, ga)); + } +#endif + +#if defined(SK_ARM_HAS_NEON) + #include + // SkMulDiv255Round() applied to each lane. + static inline uint8x8_t SkMulDiv255Round_neon8(uint8x8_t x, uint8x8_t y) { + uint16x8_t prod = vmull_u8(x, y); + return vraddhn_u16(prod, vrshrq_n_u16(prod, 8)); + } + static inline uint8x8x4_t SkPMSrcOver_neon8(uint8x8x4_t dst, uint8x8x4_t src) { + uint8x8_t nalphas = vmvn_u8(src.val[3]); // 256 - alpha + return { + vadd_u8(src.val[0], SkMulDiv255Round_neon8(nalphas, dst.val[0])), + vadd_u8(src.val[1], SkMulDiv255Round_neon8(nalphas, dst.val[1])), + vadd_u8(src.val[2], SkMulDiv255Round_neon8(nalphas, dst.val[2])), + vadd_u8(src.val[3], SkMulDiv255Round_neon8(nalphas, dst.val[3])), + }; + } + // Variant assuming dst and src contain the color components of two consecutive pixels. + static inline uint8x8_t SkPMSrcOver_neon2(uint8x8_t dst, uint8x8_t src) { + const uint8x8_t alpha_indices = vcreate_u8(0x0707070703030303); + uint8x8_t nalphas = vmvn_u8(vtbl1_u8(src, alpha_indices)); + return vadd_u8(src, SkMulDiv255Round_neon8(nalphas, dst)); + } +#endif + +namespace SK_OPTS_NS { + +/*not static*/ +inline void blit_row_s32a_opaque(SkPMColor* dst, const SkPMColor* src, int len, U8CPU alpha) { + SkASSERT(alpha == 0xFF); + sk_msan_assert_initialized(src, src+len); + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + while (len >= 8) { + _mm256_storeu_si256((__m256i*)dst, + SkPMSrcOver_AVX2(_mm256_loadu_si256((const __m256i*)src), + _mm256_loadu_si256((const __m256i*)dst))); + src += 8; + dst += 8; + len -= 8; + } +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + while (len >= 4) { + _mm_storeu_si128((__m128i*)dst, SkPMSrcOver_SSE2(_mm_loadu_si128((const __m128i*)src), + _mm_loadu_si128((const __m128i*)dst))); + src += 4; + dst += 4; + len -= 4; + } +#endif + +#if defined(SK_ARM_HAS_NEON) + while (len >= 8) { + vst4_u8((uint8_t*)dst, SkPMSrcOver_neon8(vld4_u8((const uint8_t*)dst), + vld4_u8((const uint8_t*)src))); + src += 8; + dst += 8; + len -= 8; + } + + while (len >= 2) { + vst1_u8((uint8_t*)dst, SkPMSrcOver_neon2(vld1_u8((const uint8_t*)dst), + vld1_u8((const uint8_t*)src))); + src += 2; + dst += 2; + len -= 2; + } + + if (len != 0) { + uint8x8_t result = SkPMSrcOver_neon2(vcreate_u8((uint64_t)*dst), + vcreate_u8((uint64_t)*src)); + vst1_lane_u32(dst, vreinterpret_u32_u8(result), 0); + } + return; +#endif + + while (len --> 0) { + *dst = SkPMSrcOver(*src, *dst); + src++; + dst++; + } +} + +// Blend constant color over count src pixels, writing into dst. +/*not static*/ +inline void blit_row_color32(SkPMColor* dst, const SkPMColor* src, int count, SkPMColor color) { + constexpr int N = 4; // 8, 16 also reasonable choices + using U32 = skvx::Vec< N, uint32_t>; + using U16 = skvx::Vec<4*N, uint16_t>; + using U8 = skvx::Vec<4*N, uint8_t>; + + auto kernel = [color](U32 src) { + unsigned invA = 255 - SkGetPackedA32(color); + invA += invA >> 7; + SkASSERT(0 < invA && invA < 256); // We handle alpha == 0 or alpha == 255 specially. + + // (src * invA + (color << 8) + 128) >> 8 + // Should all fit in 16 bits. + U8 s = skvx::bit_pun(src), + a = U8(invA); + U16 c = skvx::cast(skvx::bit_pun(U32(color))), + d = (mull(s,a) + (c << 8) + 128)>>8; + return skvx::bit_pun(skvx::cast(d)); + }; + + while (count >= N) { + kernel(U32::Load(src)).store(dst); + src += N; + dst += N; + count -= N; + } + while (count --> 0) { + *dst++ = kernel(U32{*src++})[0]; + } +} + +} // SK_OPTS_NS + +#endif//SkBlitRow_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkChecksum_opts.h b/gfx/skia/skia/src/opts/SkChecksum_opts.h new file mode 100644 index 0000000000..4e91443ee7 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkChecksum_opts.h @@ -0,0 +1,212 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkChecksum_opts_DEFINED +#define SkChecksum_opts_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkChecksum.h" +#include "src/core/SkUtils.h" // sk_unaligned_load + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE42 + #include +#elif defined(SK_ARM_HAS_CRC32) + #include +#endif + +namespace SK_OPTS_NS { + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE42 && (defined(__x86_64__) || defined(_M_X64)) + // This is not a CRC32. It's Just A Hash that uses those instructions because they're fast. + /*not static*/ inline uint32_t hash_fn(const void* vdata, size_t bytes, uint32_t seed) { + auto data = (const uint8_t*)vdata; + + // _mm_crc32_u64() operates on 64-bit registers, so we use uint64_t for a while. + uint64_t hash = seed; + if (bytes >= 24) { + // We'll create 3 independent hashes, each using _mm_crc32_u64() + // to hash 8 bytes per step. Both 3 and independent are important: + // we can execute 3 of these instructions in parallel on a single core. + uint64_t a = hash, + b = hash, + c = hash; + size_t steps = bytes/24; + while (steps --> 0) { + a = _mm_crc32_u64(a, sk_unaligned_load(data+ 0)); + b = _mm_crc32_u64(b, sk_unaligned_load(data+ 8)); + c = _mm_crc32_u64(c, sk_unaligned_load(data+16)); + data += 24; + } + bytes %= 24; + hash = _mm_crc32_u32(a, _mm_crc32_u32(b, c)); + } + + SkASSERT(bytes < 24); + if (bytes >= 16) { + hash = _mm_crc32_u64(hash, sk_unaligned_load(data)); + bytes -= 8; + data += 8; + } + + SkASSERT(bytes < 16); + if (bytes & 8) { + hash = _mm_crc32_u64(hash, sk_unaligned_load(data)); + data += 8; + } + + // The remainder of these _mm_crc32_u*() operate on a 32-bit register. + // We don't lose anything here: only the bottom 32-bits were populated. + auto hash32 = (uint32_t)hash; + + if (bytes & 4) { + hash32 = _mm_crc32_u32(hash32, sk_unaligned_load(data)); + data += 4; + } + if (bytes & 2) { + hash32 = _mm_crc32_u16(hash32, sk_unaligned_load(data)); + data += 2; + } + if (bytes & 1) { + hash32 = _mm_crc32_u8(hash32, sk_unaligned_load(data)); + } + return hash32; + } + +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE42 + // 32-bit version of above, using _mm_crc32_u32() but not _mm_crc32_u64(). + /*not static*/ inline uint32_t hash_fn(const void* vdata, size_t bytes, uint32_t hash) { + auto data = (const uint8_t*)vdata; + + if (bytes >= 12) { + // We'll create 3 independent hashes, each using _mm_crc32_u32() + // to hash 4 bytes per step. Both 3 and independent are important: + // we can execute 3 of these instructions in parallel on a single core. + uint32_t a = hash, + b = hash, + c = hash; + size_t steps = bytes/12; + while (steps --> 0) { + a = _mm_crc32_u32(a, sk_unaligned_load(data+0)); + b = _mm_crc32_u32(b, sk_unaligned_load(data+4)); + c = _mm_crc32_u32(c, sk_unaligned_load(data+8)); + data += 12; + } + bytes %= 12; + hash = _mm_crc32_u32(a, _mm_crc32_u32(b, c)); + } + + SkASSERT(bytes < 12); + if (bytes >= 8) { + hash = _mm_crc32_u32(hash, sk_unaligned_load(data)); + bytes -= 4; + data += 4; + } + + SkASSERT(bytes < 8); + if (bytes & 4) { + hash = _mm_crc32_u32(hash, sk_unaligned_load(data)); + data += 4; + } + if (bytes & 2) { + hash = _mm_crc32_u16(hash, sk_unaligned_load(data)); + data += 2; + } + if (bytes & 1) { + hash = _mm_crc32_u8(hash, sk_unaligned_load(data)); + } + return hash; + } + +#elif defined(SK_ARM_HAS_CRC32) + /*not static*/ inline uint32_t hash_fn(const void* vdata, size_t bytes, uint32_t hash) { + auto data = (const uint8_t*)vdata; + if (bytes >= 24) { + uint32_t a = hash, + b = hash, + c = hash; + size_t steps = bytes/24; + while (steps --> 0) { + a = __crc32d(a, sk_unaligned_load(data+ 0)); + b = __crc32d(b, sk_unaligned_load(data+ 8)); + c = __crc32d(c, sk_unaligned_load(data+16)); + data += 24; + } + bytes %= 24; + hash = __crc32w(a, __crc32w(b, c)); + } + + SkASSERT(bytes < 24); + if (bytes >= 16) { + hash = __crc32d(hash, sk_unaligned_load(data)); + bytes -= 8; + data += 8; + } + + SkASSERT(bytes < 16); + if (bytes & 8) { + hash = __crc32d(hash, sk_unaligned_load(data)); + data += 8; + } + if (bytes & 4) { + hash = __crc32w(hash, sk_unaligned_load(data)); + data += 4; + } + if (bytes & 2) { + hash = __crc32h(hash, sk_unaligned_load(data)); + data += 2; + } + if (bytes & 1) { + hash = __crc32b(hash, sk_unaligned_load(data)); + } + return hash; + } + +#else + // This is Murmur3. + /*not static*/ inline uint32_t hash_fn(const void* vdata, size_t bytes, uint32_t hash) { + auto data = (const uint8_t*)vdata; + + size_t original_bytes = bytes; + + // Handle 4 bytes at a time while possible. + while (bytes >= 4) { + uint32_t k = sk_unaligned_load(data); + k *= 0xcc9e2d51; + k = (k << 15) | (k >> 17); + k *= 0x1b873593; + + hash ^= k; + hash = (hash << 13) | (hash >> 19); + hash *= 5; + hash += 0xe6546b64; + + bytes -= 4; + data += 4; + } + + // Handle last 0-3 bytes. + uint32_t k = 0; + switch (bytes & 3) { + case 3: k ^= data[2] << 16; + case 2: k ^= data[1] << 8; + case 1: k ^= data[0] << 0; + k *= 0xcc9e2d51; + k = (k << 15) | (k >> 17); + k *= 0x1b873593; + hash ^= k; + } + + hash ^= original_bytes; + return SkChecksum::Mix(hash); + } +#endif + +#undef unaligned_load + +} // namespace SK_OPTS_NS + +#endif//SkChecksum_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkOpts_avx.cpp b/gfx/skia/skia/src/opts/SkOpts_avx.cpp new file mode 100644 index 0000000000..229892f379 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkOpts_avx.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkOpts.h" + +#ifdef __clang__ +#define SK_OPTS_NS avx +#include "src/opts/SkRasterPipeline_opts.h" +#endif + +#include "src/opts/SkUtils_opts.h" + +namespace SkOpts { + void Init_avx() { + memset16 = SK_OPTS_NS::memset16; + memset32 = SK_OPTS_NS::memset32; + memset64 = SK_OPTS_NS::memset64; + + rect_memset16 = SK_OPTS_NS::rect_memset16; + rect_memset32 = SK_OPTS_NS::rect_memset32; + rect_memset64 = SK_OPTS_NS::rect_memset64; + +#ifdef __clang__ + #define M(st) stages_highp[SkRasterPipeline::st] = (StageFn)SK_OPTS_NS::st; + SK_RASTER_PIPELINE_STAGES(M) + just_return_highp = (StageFn)SK_OPTS_NS::just_return; + start_pipeline_highp = SK_OPTS_NS::start_pipeline; + #undef M + + #define M(st) stages_lowp[SkRasterPipeline::st] = (StageFn)SK_OPTS_NS::lowp::st; + SK_RASTER_PIPELINE_STAGES(M) + just_return_lowp = (StageFn)SK_OPTS_NS::lowp::just_return; + start_pipeline_lowp = SK_OPTS_NS::lowp::start_pipeline; + #undef M +#endif + } +} diff --git a/gfx/skia/skia/src/opts/SkOpts_crc32.cpp b/gfx/skia/skia/src/opts/SkOpts_crc32.cpp new file mode 100644 index 0000000000..205f0ebf37 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkOpts_crc32.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkOpts.h" + +#define SK_OPTS_NS crc32 +#include "src/opts/SkChecksum_opts.h" + +namespace SkOpts { + void Init_crc32() { + hash_fn = crc32::hash_fn; + } +} diff --git a/gfx/skia/skia/src/opts/SkOpts_hsw.cpp b/gfx/skia/skia/src/opts/SkOpts_hsw.cpp new file mode 100644 index 0000000000..f28835d432 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkOpts_hsw.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#if defined(__AVX2__) + +#include +#include + +namespace hsw { + + void convolve_vertically(const int16_t* filter, int filterLen, + uint8_t* const* srcRows, int width, + uint8_t* out, bool hasAlpha) { + // It's simpler to work with the output array in terms of 4-byte pixels. + auto dst = (int*)out; + + // Output up to eight pixels per iteration. + for (int x = 0; x < width; x += 8) { + // Accumulated result for 4 (non-adjacent) pairs of pixels, + // with each channel in signed 17.14 fixed point. + auto accum04 = _mm256_setzero_si256(), + accum15 = _mm256_setzero_si256(), + accum26 = _mm256_setzero_si256(), + accum37 = _mm256_setzero_si256(); + + // Convolve with the filter. (This inner loop is where we spend ~all our time.) + // While we can, we consume 2 filter coefficients and 2 rows of 8 pixels each at a time. + auto convolve_16_pixels = [&](__m256i interlaced_coeffs, + __m256i pixels_01234567, __m256i pixels_89ABCDEF) { + // Interlaced R0R8 G0G8 B0B8 A0A8 R1R9 G1G9... 32 8-bit values each. + auto _08194C5D = _mm256_unpacklo_epi8(pixels_01234567, pixels_89ABCDEF), + _2A3B6E7F = _mm256_unpackhi_epi8(pixels_01234567, pixels_89ABCDEF); + + // Still interlaced R0R8 G0G8... as above, each channel expanded to 16-bit lanes. + auto _084C = _mm256_unpacklo_epi8(_08194C5D, _mm256_setzero_si256()), + _195D = _mm256_unpackhi_epi8(_08194C5D, _mm256_setzero_si256()), + _2A6E = _mm256_unpacklo_epi8(_2A3B6E7F, _mm256_setzero_si256()), + _3B7F = _mm256_unpackhi_epi8(_2A3B6E7F, _mm256_setzero_si256()); + + // accum0_R += R0*coeff0 + R8*coeff1, etc. + accum04 = _mm256_add_epi32(accum04, _mm256_madd_epi16(_084C, interlaced_coeffs)); + accum15 = _mm256_add_epi32(accum15, _mm256_madd_epi16(_195D, interlaced_coeffs)); + accum26 = _mm256_add_epi32(accum26, _mm256_madd_epi16(_2A6E, interlaced_coeffs)); + accum37 = _mm256_add_epi32(accum37, _mm256_madd_epi16(_3B7F, interlaced_coeffs)); + }; + + int i = 0; + for (; i < filterLen/2*2; i += 2) { + convolve_16_pixels(_mm256_set1_epi32(*(const int32_t*)(filter+i)), + _mm256_loadu_si256((const __m256i*)(srcRows[i+0] + x*4)), + _mm256_loadu_si256((const __m256i*)(srcRows[i+1] + x*4))); + } + if (i < filterLen) { + convolve_16_pixels(_mm256_set1_epi32(*(const int16_t*)(filter+i)), + _mm256_loadu_si256((const __m256i*)(srcRows[i] + x*4)), + _mm256_setzero_si256()); + } + + // Trim the fractional parts off the accumulators. + accum04 = _mm256_srai_epi32(accum04, 14); + accum15 = _mm256_srai_epi32(accum15, 14); + accum26 = _mm256_srai_epi32(accum26, 14); + accum37 = _mm256_srai_epi32(accum37, 14); + + // Pack back down to 8-bit channels. + auto pixels = _mm256_packus_epi16(_mm256_packs_epi32(accum04, accum15), + _mm256_packs_epi32(accum26, accum37)); + + if (hasAlpha) { + // Clamp alpha to the max of r,g,b to make sure we stay premultiplied. + __m256i max_rg = _mm256_max_epu8(pixels, _mm256_srli_epi32(pixels, 8)), + max_rgb = _mm256_max_epu8(max_rg, _mm256_srli_epi32(pixels, 16)); + pixels = _mm256_max_epu8(pixels, _mm256_slli_epi32(max_rgb, 24)); + } else { + // Force opaque. + pixels = _mm256_or_si256(pixels, _mm256_set1_epi32(0xff000000)); + } + + // Normal path to store 8 pixels. + if (x + 8 <= width) { + _mm256_storeu_si256((__m256i*)dst, pixels); + dst += 8; + continue; + } + + // Store one pixel at a time on the last iteration. + for (int i = x; i < width; i++) { + *dst++ = _mm_cvtsi128_si32(_mm256_castsi256_si128(pixels)); + pixels = _mm256_permutevar8x32_epi32(pixels, _mm256_setr_epi32(1,2,3,4,5,6,7,0)); + } + } + } + +} + +#include "src/core/SkOpts.h" + +#define SK_OPTS_NS hsw +#include "src/core/SkCubicSolver.h" +#include "src/opts/SkBitmapProcState_opts.h" +#include "src/opts/SkBlitRow_opts.h" +#include "src/opts/SkRasterPipeline_opts.h" +#include "src/opts/SkUtils_opts.h" + +namespace SkOpts { + // See SkOpts.h, writing SkConvolutionFilter1D::ConvolutionFixed as the underlying type. + extern void (*convolve_vertically)(const int16_t* filter, int filterLen, + uint8_t* const* srcRows, int width, + uint8_t* out, bool hasAlpha); + void Init_hsw() { + convolve_vertically = hsw::convolve_vertically; + + blit_row_color32 = hsw::blit_row_color32; + blit_row_s32a_opaque = hsw::blit_row_s32a_opaque; + + S32_alpha_D32_filter_DX = hsw::S32_alpha_D32_filter_DX; + + cubic_solver = SK_OPTS_NS::cubic_solver; + + #define M(st) stages_highp[SkRasterPipeline::st] = (StageFn)SK_OPTS_NS::st; + SK_RASTER_PIPELINE_STAGES(M) + just_return_highp = (StageFn)SK_OPTS_NS::just_return; + start_pipeline_highp = SK_OPTS_NS::start_pipeline; + #undef M + + #define M(st) stages_lowp[SkRasterPipeline::st] = (StageFn)SK_OPTS_NS::lowp::st; + SK_RASTER_PIPELINE_STAGES(M) + just_return_lowp = (StageFn)SK_OPTS_NS::lowp::just_return; + start_pipeline_lowp = SK_OPTS_NS::lowp::start_pipeline; + #undef M + } +} + +#else // defined(__AVX2__) is not true... + +namespace SkOpts { void Init_hsw() {} } + +#endif diff --git a/gfx/skia/skia/src/opts/SkOpts_sse41.cpp b/gfx/skia/skia/src/opts/SkOpts_sse41.cpp new file mode 100644 index 0000000000..8e525968da --- /dev/null +++ b/gfx/skia/skia/src/opts/SkOpts_sse41.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkOpts.h" + +#define SK_OPTS_NS sse41 +#include "src/opts/SkBlitRow_opts.h" +#include "src/opts/SkRasterPipeline_opts.h" + +namespace SkOpts { + void Init_sse41() { + blit_row_color32 = sse41::blit_row_color32; + blit_row_s32a_opaque = sse41::blit_row_s32a_opaque; + + #define M(st) stages_highp[SkRasterPipeline::st] = (StageFn)SK_OPTS_NS::st; + SK_RASTER_PIPELINE_STAGES(M) + just_return_highp = (StageFn)SK_OPTS_NS::just_return; + start_pipeline_highp = SK_OPTS_NS::start_pipeline; + #undef M + + #define M(st) stages_lowp[SkRasterPipeline::st] = (StageFn)SK_OPTS_NS::lowp::st; + SK_RASTER_PIPELINE_STAGES(M) + just_return_lowp = (StageFn)SK_OPTS_NS::lowp::just_return; + start_pipeline_lowp = SK_OPTS_NS::lowp::start_pipeline; + #undef M + } +} diff --git a/gfx/skia/skia/src/opts/SkOpts_sse42.cpp b/gfx/skia/skia/src/opts/SkOpts_sse42.cpp new file mode 100644 index 0000000000..8e80fffa4a --- /dev/null +++ b/gfx/skia/skia/src/opts/SkOpts_sse42.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkOpts.h" + +#define SK_OPTS_NS sse42 +#include "src/opts/SkChecksum_opts.h" + +namespace SkOpts { + void Init_sse42() { + hash_fn = sse42::hash_fn; + } +} + diff --git a/gfx/skia/skia/src/opts/SkOpts_ssse3.cpp b/gfx/skia/skia/src/opts/SkOpts_ssse3.cpp new file mode 100644 index 0000000000..daa69872e0 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkOpts_ssse3.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkOpts.h" +#define SK_OPTS_NS ssse3 +#include "src/opts/SkBitmapProcState_opts.h" +#include "src/opts/SkBlitMask_opts.h" +#include "src/opts/SkSwizzler_opts.h" +#include "src/opts/SkXfermode_opts.h" + +namespace SkOpts { + void Init_ssse3() { + create_xfermode = ssse3::create_xfermode; + blit_mask_d32_a8 = ssse3::blit_mask_d32_a8; + + RGBA_to_BGRA = ssse3::RGBA_to_BGRA; + RGBA_to_rgbA = ssse3::RGBA_to_rgbA; + RGBA_to_bgrA = ssse3::RGBA_to_bgrA; + RGB_to_RGB1 = ssse3::RGB_to_RGB1; + RGB_to_BGR1 = ssse3::RGB_to_BGR1; + gray_to_RGB1 = ssse3::gray_to_RGB1; + grayA_to_RGBA = ssse3::grayA_to_RGBA; + grayA_to_rgbA = ssse3::grayA_to_rgbA; + inverted_CMYK_to_RGB1 = ssse3::inverted_CMYK_to_RGB1; + inverted_CMYK_to_BGR1 = ssse3::inverted_CMYK_to_BGR1; + + S32_alpha_D32_filter_DX = ssse3::S32_alpha_D32_filter_DX; + } +} diff --git a/gfx/skia/skia/src/opts/SkRasterPipeline_opts.h b/gfx/skia/skia/src/opts/SkRasterPipeline_opts.h new file mode 100644 index 0000000000..aea70141a6 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkRasterPipeline_opts.h @@ -0,0 +1,4557 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRasterPipeline_opts_DEFINED +#define SkRasterPipeline_opts_DEFINED + +#include "include/core/SkTypes.h" +#include "src/core/SkUtils.h" // unaligned_{load,store} +#include "src/sksl/SkSLByteCode.h" + +// Every function in this file should be marked static and inline using SI. +#if defined(__clang__) || defined(__GNUC__) + #define SI __attribute__((always_inline)) static inline +#else + #define SI static inline +#endif + +template +SI Dst bit_cast(const Src& src) { + static_assert(sizeof(Dst) == sizeof(Src), ""); + return sk_unaligned_load(&src); +} + +template +SI Dst widen_cast(const Src& src) { + static_assert(sizeof(Dst) > sizeof(Src), ""); + Dst dst; + memcpy(&dst, &src, sizeof(Src)); + return dst; +} + +// Our program is an array of void*, either +// - 1 void* per stage with no context pointer, the next stage; +// - 2 void* per stage with a context pointer, first the context pointer, then the next stage. + +// load_and_inc() steps the program forward by 1 void*, returning that pointer. +SI void* load_and_inc(void**& program) { +#if defined(__GNUC__) && defined(__x86_64__) + // If program is in %rsi (we try to make this likely) then this is a single instruction. + void* rax; + asm("lodsq" : "=a"(rax), "+S"(program)); // Write-only %rax, read-write %rsi. + return rax; +#else + // On ARM *program++ compiles into pretty ideal code without any handholding. + return *program++; +#endif +} + +// Lazily resolved on first cast. Does nothing if cast to Ctx::None. +struct Ctx { + struct None {}; + + void* ptr; + void**& program; + + explicit Ctx(void**& p) : ptr(nullptr), program(p) {} + + template + operator T*() { + if (!ptr) { ptr = load_and_inc(program); } + return (T*)ptr; + } + operator None() { return None{}; } +}; + + +#if !defined(__clang__) + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + #define JUMPER_IS_SSE41 + #elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #define JUMPER_IS_SSE2 + #else + #define JUMPER_IS_SCALAR + #endif +#elif defined(SK_ARM_HAS_NEON) + #define JUMPER_IS_NEON +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX512 + #define JUMPER_IS_AVX512 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + #define JUMPER_IS_HSW +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX + #define JUMPER_IS_AVX +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 + #define JUMPER_IS_SSE41 +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + #define JUMPER_IS_SSE2 +#else + #define JUMPER_IS_SCALAR +#endif + +// Older Clangs seem to crash when generating non-optimized NEON code for ARMv7. +#if defined(__clang__) && !defined(__OPTIMIZE__) && defined(SK_CPU_ARM32) + // Apple Clang 9 and vanilla Clang 5 are fine, and may even be conservative. + #if defined(__apple_build_version__) && __clang_major__ < 9 + #define JUMPER_IS_SCALAR + #elif __clang_major__ < 5 + #define JUMPER_IS_SCALAR + #endif + + #if defined(JUMPER_IS_NEON) && defined(JUMPER_IS_SCALAR) + #undef JUMPER_IS_NEON + #endif +#endif + +#if defined(JUMPER_IS_SCALAR) + #include +#elif defined(JUMPER_IS_NEON) + #include +#else + #include +#endif + +#if !defined(__clang__) && !defined(JUMPER_IS_SCALAR) +#include "include/private/SkNx.h" +#endif + +#ifdef __clang__ +#define SK_ASSUME(cond) __builtin_assume(cond) +#elif defined(__GNUC__) +#define SK_ASSUME(cond) ((cond) ? (void)0 : __builtin_unreachable()) +#elif defined(_MSC_VER) +#define SK_ASSUME(cond) __assume(cond) +#else +#define SK_ASSUME(cond) ((void)0) +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define SK_EXPECT(exp, p) __builtin_expect(exp, p) +#else +#define SK_EXPECT(exp, p) (exp) +#endif + +#if defined(JUMPER_IS_SCALAR) +#define SK_CONVERTVECTOR(vec, type) ((type)(vec)) +#elif defined(__clang__) +#define SK_CONVERTVECTOR(vec, type) __builtin_convertvector(vec, type) +#else +template struct SkNx_element {}; +template struct SkNx_element> { typedef T type; }; +#define SK_CONVERTVECTOR(vec, vtype) SkNx_cast::type>(vec) +#endif + +#ifdef __clang__ +#define SK_VECTORTYPE(type, size) type __attribute__((ext_vector_type(size))) +#else +#define SK_VECTORTYPE(type, size) SkNx +#endif + +namespace SK_OPTS_NS { + +#if defined(JUMPER_IS_SCALAR) + // This path should lead to portable scalar code. + using F = float ; + using I32 = int32_t; + using U64 = uint64_t; + using U32 = uint32_t; + using U16 = uint16_t; + using U8 = uint8_t ; + + SI F mad(F f, F m, F a) { return f*m+a; } + SI F min(F a, F b) { return fminf(a,b); } + SI F max(F a, F b) { return fmaxf(a,b); } + SI F abs_ (F v) { return fabsf(v); } + SI F floor_(F v) { return floorf(v); } + SI F rcp (F v) { return 1.0f / v; } + SI F rsqrt (F v) { return 1.0f / sqrtf(v); } + SI F sqrt_(F v) { return sqrtf(v); } + SI U32 round (F v, F scale) { return (uint32_t)(v*scale + 0.5f); } + SI U16 pack(U32 v) { return (U16)v; } + SI U8 pack(U16 v) { return (U8)v; } + + SI F if_then_else(I32 c, F t, F e) { return c ? t : e; } + + template + SI T gather(const T* p, U32 ix) { return p[ix]; } + + SI void load2(const uint16_t* ptr, size_t tail, U16* r, U16* g) { + *r = ptr[0]; + *g = ptr[1]; + } + SI void store2(uint16_t* ptr, size_t tail, U16 r, U16 g) { + ptr[0] = r; + ptr[1] = g; + } + SI void load3(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b) { + *r = ptr[0]; + *g = ptr[1]; + *b = ptr[2]; + } + SI void load4(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b, U16* a) { + *r = ptr[0]; + *g = ptr[1]; + *b = ptr[2]; + *a = ptr[3]; + } + SI void store4(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) { + ptr[0] = r; + ptr[1] = g; + ptr[2] = b; + ptr[3] = a; + } + + SI void load2(const float* ptr, size_t tail, F* r, F* g) { + *r = ptr[0]; + *g = ptr[1]; + } + SI void store2(float* ptr, size_t tail, F r, F g) { + ptr[0] = r; + ptr[1] = g; + } + SI void load4(const float* ptr, size_t tail, F* r, F* g, F* b, F* a) { + *r = ptr[0]; + *g = ptr[1]; + *b = ptr[2]; + *a = ptr[3]; + } + SI void store4(float* ptr, size_t tail, F r, F g, F b, F a) { + ptr[0] = r; + ptr[1] = g; + ptr[2] = b; + ptr[3] = a; + } + +#elif defined(JUMPER_IS_NEON) + // Since we know we're using Clang, we can use its vector extensions. + template using V = SK_VECTORTYPE(T, 4); + using F = V; + using I32 = V< int32_t>; + using U64 = V; + using U32 = V; + using U16 = V; + using U8 = V; + + // We polyfill a few routines that Clang doesn't build into ext_vector_types. + SI F min(F a, F b) { return vminq_f32(a,b); } + SI F max(F a, F b) { return vmaxq_f32(a,b); } + SI F abs_ (F v) { return vabsq_f32(v); } + SI F rcp (F v) { auto e = vrecpeq_f32 (v); return vrecpsq_f32 (v,e ) * e; } + SI F rsqrt (F v) { auto e = vrsqrteq_f32(v); return vrsqrtsq_f32(v,e*e) * e; } + SI U16 pack(U32 v) { return SK_CONVERTVECTOR(v, U16); } + SI U8 pack(U16 v) { return SK_CONVERTVECTOR(v, U8); } + + SI F if_then_else(I32 c, F t, F e) { return vbslq_f32((U32)c,t,e); } + + #if defined(SK_CPU_ARM64) + SI F mad(F f, F m, F a) { return vfmaq_f32(a,f,m); } + SI F floor_(F v) { return vrndmq_f32(v); } + SI F sqrt_(F v) { return vsqrtq_f32(v); } + SI U32 round(F v, F scale) { return vcvtnq_u32_f32(v*scale); } + #else + SI F mad(F f, F m, F a) { return vmlaq_f32(a,f,m); } + SI F floor_(F v) { + F roundtrip = vcvtq_f32_s32(vcvtq_s32_f32(v)); + return roundtrip - if_then_else(roundtrip > v, 1, 0); + } + + SI F sqrt_(F v) { + auto e = vrsqrteq_f32(v); // Estimate and two refinement steps for e = rsqrt(v). + e *= vrsqrtsq_f32(v,e*e); + e *= vrsqrtsq_f32(v,e*e); + return v*e; // sqrt(v) == v*rsqrt(v). + } + + SI U32 round(F v, F scale) { + return vcvtq_u32_f32(mad(v,scale,0.5f)); + } + #endif + + + template + SI V gather(const T* p, U32 ix) { + return {p[ix[0]], p[ix[1]], p[ix[2]], p[ix[3]]}; + } + SI void load2(const uint16_t* ptr, size_t tail, U16* r, U16* g) { + uint16x4x2_t rg; + if (SK_EXPECT(tail,0)) { + if ( true ) { rg = vld2_lane_u16(ptr + 0, rg, 0); } + if (tail > 1) { rg = vld2_lane_u16(ptr + 2, rg, 1); } + if (tail > 2) { rg = vld2_lane_u16(ptr + 4, rg, 2); } + } else { + rg = vld2_u16(ptr); + } + *r = rg.val[0]; + *g = rg.val[1]; + } + SI void store2(uint16_t* ptr, size_t tail, U16 r, U16 g) { + if (SK_EXPECT(tail,0)) { + if ( true ) { vst2_lane_u16(ptr + 0, (uint16x4x2_t{{r,g}}), 0); } + if (tail > 1) { vst2_lane_u16(ptr + 2, (uint16x4x2_t{{r,g}}), 1); } + if (tail > 2) { vst2_lane_u16(ptr + 4, (uint16x4x2_t{{r,g}}), 2); } + } else { + vst2_u16(ptr, (uint16x4x2_t{{r,g}})); + } + } + SI void load3(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b) { + uint16x4x3_t rgb; + if (SK_EXPECT(tail,0)) { + if ( true ) { rgb = vld3_lane_u16(ptr + 0, rgb, 0); } + if (tail > 1) { rgb = vld3_lane_u16(ptr + 3, rgb, 1); } + if (tail > 2) { rgb = vld3_lane_u16(ptr + 6, rgb, 2); } + } else { + rgb = vld3_u16(ptr); + } + *r = rgb.val[0]; + *g = rgb.val[1]; + *b = rgb.val[2]; + } + SI void load4(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b, U16* a) { + uint16x4x4_t rgba; + if (SK_EXPECT(tail,0)) { + if ( true ) { rgba = vld4_lane_u16(ptr + 0, rgba, 0); } + if (tail > 1) { rgba = vld4_lane_u16(ptr + 4, rgba, 1); } + if (tail > 2) { rgba = vld4_lane_u16(ptr + 8, rgba, 2); } + } else { + rgba = vld4_u16(ptr); + } + *r = rgba.val[0]; + *g = rgba.val[1]; + *b = rgba.val[2]; + *a = rgba.val[3]; + } + + SI void store4(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) { + if (SK_EXPECT(tail,0)) { + if ( true ) { vst4_lane_u16(ptr + 0, (uint16x4x4_t{{r,g,b,a}}), 0); } + if (tail > 1) { vst4_lane_u16(ptr + 4, (uint16x4x4_t{{r,g,b,a}}), 1); } + if (tail > 2) { vst4_lane_u16(ptr + 8, (uint16x4x4_t{{r,g,b,a}}), 2); } + } else { + vst4_u16(ptr, (uint16x4x4_t{{r,g,b,a}})); + } + } + SI void load2(const float* ptr, size_t tail, F* r, F* g) { + float32x4x2_t rg; + if (SK_EXPECT(tail,0)) { + if ( true ) { rg = vld2q_lane_f32(ptr + 0, rg, 0); } + if (tail > 1) { rg = vld2q_lane_f32(ptr + 2, rg, 1); } + if (tail > 2) { rg = vld2q_lane_f32(ptr + 4, rg, 2); } + } else { + rg = vld2q_f32(ptr); + } + *r = rg.val[0]; + *g = rg.val[1]; + } + SI void store2(float* ptr, size_t tail, F r, F g) { + if (SK_EXPECT(tail,0)) { + if ( true ) { vst2q_lane_f32(ptr + 0, (float32x4x2_t{{r,g}}), 0); } + if (tail > 1) { vst2q_lane_f32(ptr + 2, (float32x4x2_t{{r,g}}), 1); } + if (tail > 2) { vst2q_lane_f32(ptr + 4, (float32x4x2_t{{r,g}}), 2); } + } else { + vst2q_f32(ptr, (float32x4x2_t{{r,g}})); + } + } + SI void load4(const float* ptr, size_t tail, F* r, F* g, F* b, F* a) { + float32x4x4_t rgba; + if (SK_EXPECT(tail,0)) { + if ( true ) { rgba = vld4q_lane_f32(ptr + 0, rgba, 0); } + if (tail > 1) { rgba = vld4q_lane_f32(ptr + 4, rgba, 1); } + if (tail > 2) { rgba = vld4q_lane_f32(ptr + 8, rgba, 2); } + } else { + rgba = vld4q_f32(ptr); + } + *r = rgba.val[0]; + *g = rgba.val[1]; + *b = rgba.val[2]; + *a = rgba.val[3]; + } + SI void store4(float* ptr, size_t tail, F r, F g, F b, F a) { + if (SK_EXPECT(tail,0)) { + if ( true ) { vst4q_lane_f32(ptr + 0, (float32x4x4_t{{r,g,b,a}}), 0); } + if (tail > 1) { vst4q_lane_f32(ptr + 4, (float32x4x4_t{{r,g,b,a}}), 1); } + if (tail > 2) { vst4q_lane_f32(ptr + 8, (float32x4x4_t{{r,g,b,a}}), 2); } + } else { + vst4q_f32(ptr, (float32x4x4_t{{r,g,b,a}})); + } + } + +#elif defined(JUMPER_IS_AVX) || defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + // These are __m256 and __m256i, but friendlier and strongly-typed. + template using V = SK_VECTORTYPE(T, 8); + using F = V; + using I32 = V< int32_t>; + using U64 = V; + using U32 = V; + using U16 = V; + using U8 = V; + + SI F mad(F f, F m, F a) { + #if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + return _mm256_fmadd_ps(f,m,a); + #else + return f*m+a; + #endif + } + + SI F min(F a, F b) { return _mm256_min_ps(a,b); } + SI F max(F a, F b) { return _mm256_max_ps(a,b); } + SI F abs_ (F v) { return _mm256_and_ps(v, 0-v); } + SI F floor_(F v) { return _mm256_floor_ps(v); } + SI F rcp (F v) { return _mm256_rcp_ps (v); } + SI F rsqrt (F v) { return _mm256_rsqrt_ps(v); } + SI F sqrt_(F v) { return _mm256_sqrt_ps (v); } + SI U32 round (F v, F scale) { return _mm256_cvtps_epi32(v*scale); } + + SI U16 pack(U32 v) { + return _mm_packus_epi32(_mm256_extractf128_si256(v, 0), + _mm256_extractf128_si256(v, 1)); + } + SI U8 pack(U16 v) { + auto r = _mm_packus_epi16(v,v); + return sk_unaligned_load(&r); + } + + SI F if_then_else(I32 c, F t, F e) { return _mm256_blendv_ps(e,t,c); } + + template + SI V gather(const T* p, U32 ix) { + return { p[ix[0]], p[ix[1]], p[ix[2]], p[ix[3]], + p[ix[4]], p[ix[5]], p[ix[6]], p[ix[7]], }; + } + #if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + SI F gather(const float* p, U32 ix) { return _mm256_i32gather_ps (p, ix, 4); } + SI U32 gather(const uint32_t* p, U32 ix) { return _mm256_i32gather_epi32(p, ix, 4); } + SI U64 gather(const uint64_t* p, U32 ix) { + __m256i parts[] = { + _mm256_i32gather_epi64(p, _mm256_extracti128_si256(ix,0), 8), + _mm256_i32gather_epi64(p, _mm256_extracti128_si256(ix,1), 8), + }; + return bit_cast(parts); + } + #endif + + SI void load2(const uint16_t* ptr, size_t tail, U16* r, U16* g) { + U16 _0123, _4567; + if (SK_EXPECT(tail,0)) { + _0123 = _4567 = _mm_setzero_si128(); + auto* d = &_0123; + if (tail > 3) { + *d = _mm_loadu_si128(((__m128i*)ptr) + 0); + tail -= 4; + ptr += 8; + d = &_4567; + } + bool high = false; + if (tail > 1) { + *d = _mm_loadu_si64(ptr); + tail -= 2; + ptr += 4; + high = true; + } + if (tail > 0) { + (*d)[high ? 4 : 0] = *(ptr + 0); + (*d)[high ? 5 : 1] = *(ptr + 1); + } + } else { + _0123 = _mm_loadu_si128(((__m128i*)ptr) + 0); + _4567 = _mm_loadu_si128(((__m128i*)ptr) + 1); + } + *r = _mm_packs_epi32(_mm_srai_epi32(_mm_slli_epi32(_0123, 16), 16), + _mm_srai_epi32(_mm_slli_epi32(_4567, 16), 16)); + *g = _mm_packs_epi32(_mm_srai_epi32(_0123, 16), + _mm_srai_epi32(_4567, 16)); + } + SI void store2(uint16_t* ptr, size_t tail, U16 r, U16 g) { + auto _0123 = _mm_unpacklo_epi16(r, g), + _4567 = _mm_unpackhi_epi16(r, g); + if (SK_EXPECT(tail,0)) { + const auto* s = &_0123; + if (tail > 3) { + _mm_storeu_si128((__m128i*)ptr, *s); + s = &_4567; + tail -= 4; + ptr += 8; + } + bool high = false; + if (tail > 1) { + _mm_storel_epi64((__m128i*)ptr, *s); + ptr += 4; + tail -= 2; + high = true; + } + if (tail > 0) { + if (high) { + *(int32_t*)ptr = _mm_extract_epi32(*s, 2); + } else { + *(int32_t*)ptr = _mm_cvtsi128_si32(*s); + } + } + } else { + _mm_storeu_si128((__m128i*)ptr + 0, _0123); + _mm_storeu_si128((__m128i*)ptr + 1, _4567); + } + } + + SI void load3(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b) { + __m128i _0,_1,_2,_3,_4,_5,_6,_7; + if (SK_EXPECT(tail,0)) { + auto load_rgb = [](const uint16_t* src) { + auto v = _mm_cvtsi32_si128(*(const uint32_t*)src); + return _mm_insert_epi16(v, src[2], 2); + }; + _1 = _2 = _3 = _4 = _5 = _6 = _7 = _mm_setzero_si128(); + if ( true ) { _0 = load_rgb(ptr + 0); } + if (tail > 1) { _1 = load_rgb(ptr + 3); } + if (tail > 2) { _2 = load_rgb(ptr + 6); } + if (tail > 3) { _3 = load_rgb(ptr + 9); } + if (tail > 4) { _4 = load_rgb(ptr + 12); } + if (tail > 5) { _5 = load_rgb(ptr + 15); } + if (tail > 6) { _6 = load_rgb(ptr + 18); } + } else { + // Load 0+1, 2+3, 4+5 normally, and 6+7 backed up 4 bytes so we don't run over. + auto _01 = _mm_loadu_si128((const __m128i*)(ptr + 0)) ; + auto _23 = _mm_loadu_si128((const __m128i*)(ptr + 6)) ; + auto _45 = _mm_loadu_si128((const __m128i*)(ptr + 12)) ; + auto _67 = _mm_srli_si128(_mm_loadu_si128((const __m128i*)(ptr + 16)), 4); + _0 = _01; _1 = _mm_srli_si128(_01, 6); + _2 = _23; _3 = _mm_srli_si128(_23, 6); + _4 = _45; _5 = _mm_srli_si128(_45, 6); + _6 = _67; _7 = _mm_srli_si128(_67, 6); + } + + auto _02 = _mm_unpacklo_epi16(_0, _2), // r0 r2 g0 g2 b0 b2 xx xx + _13 = _mm_unpacklo_epi16(_1, _3), + _46 = _mm_unpacklo_epi16(_4, _6), + _57 = _mm_unpacklo_epi16(_5, _7); + + auto rg0123 = _mm_unpacklo_epi16(_02, _13), // r0 r1 r2 r3 g0 g1 g2 g3 + bx0123 = _mm_unpackhi_epi16(_02, _13), // b0 b1 b2 b3 xx xx xx xx + rg4567 = _mm_unpacklo_epi16(_46, _57), + bx4567 = _mm_unpackhi_epi16(_46, _57); + + *r = _mm_unpacklo_epi64(rg0123, rg4567); + *g = _mm_unpackhi_epi64(rg0123, rg4567); + *b = _mm_unpacklo_epi64(bx0123, bx4567); + } + SI void load4(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b, U16* a) { + __m128i _01, _23, _45, _67; + if (SK_EXPECT(tail,0)) { + auto src = (const double*)ptr; + _01 = _23 = _45 = _67 = _mm_setzero_si128(); + if (tail > 0) { _01 = _mm_loadl_pd(_01, src+0); } + if (tail > 1) { _01 = _mm_loadh_pd(_01, src+1); } + if (tail > 2) { _23 = _mm_loadl_pd(_23, src+2); } + if (tail > 3) { _23 = _mm_loadh_pd(_23, src+3); } + if (tail > 4) { _45 = _mm_loadl_pd(_45, src+4); } + if (tail > 5) { _45 = _mm_loadh_pd(_45, src+5); } + if (tail > 6) { _67 = _mm_loadl_pd(_67, src+6); } + } else { + _01 = _mm_loadu_si128(((__m128i*)ptr) + 0); + _23 = _mm_loadu_si128(((__m128i*)ptr) + 1); + _45 = _mm_loadu_si128(((__m128i*)ptr) + 2); + _67 = _mm_loadu_si128(((__m128i*)ptr) + 3); + } + + auto _02 = _mm_unpacklo_epi16(_01, _23), // r0 r2 g0 g2 b0 b2 a0 a2 + _13 = _mm_unpackhi_epi16(_01, _23), // r1 r3 g1 g3 b1 b3 a1 a3 + _46 = _mm_unpacklo_epi16(_45, _67), + _57 = _mm_unpackhi_epi16(_45, _67); + + auto rg0123 = _mm_unpacklo_epi16(_02, _13), // r0 r1 r2 r3 g0 g1 g2 g3 + ba0123 = _mm_unpackhi_epi16(_02, _13), // b0 b1 b2 b3 a0 a1 a2 a3 + rg4567 = _mm_unpacklo_epi16(_46, _57), + ba4567 = _mm_unpackhi_epi16(_46, _57); + + *r = _mm_unpacklo_epi64(rg0123, rg4567); + *g = _mm_unpackhi_epi64(rg0123, rg4567); + *b = _mm_unpacklo_epi64(ba0123, ba4567); + *a = _mm_unpackhi_epi64(ba0123, ba4567); + } + SI void store4(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) { + auto rg0123 = _mm_unpacklo_epi16(r, g), // r0 g0 r1 g1 r2 g2 r3 g3 + rg4567 = _mm_unpackhi_epi16(r, g), // r4 g4 r5 g5 r6 g6 r7 g7 + ba0123 = _mm_unpacklo_epi16(b, a), + ba4567 = _mm_unpackhi_epi16(b, a); + + auto _01 = _mm_unpacklo_epi32(rg0123, ba0123), + _23 = _mm_unpackhi_epi32(rg0123, ba0123), + _45 = _mm_unpacklo_epi32(rg4567, ba4567), + _67 = _mm_unpackhi_epi32(rg4567, ba4567); + + if (SK_EXPECT(tail,0)) { + auto dst = (double*)ptr; + if (tail > 0) { _mm_storel_pd(dst+0, _01); } + if (tail > 1) { _mm_storeh_pd(dst+1, _01); } + if (tail > 2) { _mm_storel_pd(dst+2, _23); } + if (tail > 3) { _mm_storeh_pd(dst+3, _23); } + if (tail > 4) { _mm_storel_pd(dst+4, _45); } + if (tail > 5) { _mm_storeh_pd(dst+5, _45); } + if (tail > 6) { _mm_storel_pd(dst+6, _67); } + } else { + _mm_storeu_si128((__m128i*)ptr + 0, _01); + _mm_storeu_si128((__m128i*)ptr + 1, _23); + _mm_storeu_si128((__m128i*)ptr + 2, _45); + _mm_storeu_si128((__m128i*)ptr + 3, _67); + } + } + + SI void load2(const float* ptr, size_t tail, F* r, F* g) { + F _0123, _4567; + if (SK_EXPECT(tail, 0)) { + _0123 = _4567 = _mm256_setzero_ps(); + F* d = &_0123; + if (tail > 3) { + *d = _mm256_loadu_ps(ptr); + ptr += 8; + tail -= 4; + d = &_4567; + } + bool high = false; + if (tail > 1) { + *d = _mm256_castps128_ps256(_mm_loadu_ps(ptr)); + ptr += 4; + tail -= 2; + high = true; + } + if (tail > 0) { + *d = high ? _mm256_insertf128_ps(*d, _mm_loadu_si64(ptr), 1) + : _mm256_insertf128_ps(*d, _mm_loadu_si64(ptr), 0); + } + } else { + _0123 = _mm256_loadu_ps(ptr + 0); + _4567 = _mm256_loadu_ps(ptr + 8); + } + + F _0145 = _mm256_permute2f128_pd(_0123, _4567, 0x20), + _2367 = _mm256_permute2f128_pd(_0123, _4567, 0x31); + + *r = _mm256_shuffle_ps(_0145, _2367, 0x88); + *g = _mm256_shuffle_ps(_0145, _2367, 0xDD); + } + SI void store2(float* ptr, size_t tail, F r, F g) { + F _0145 = _mm256_unpacklo_ps(r, g), + _2367 = _mm256_unpackhi_ps(r, g); + F _0123 = _mm256_permute2f128_pd(_0145, _2367, 0x20), + _4567 = _mm256_permute2f128_pd(_0145, _2367, 0x31); + + if (SK_EXPECT(tail, 0)) { + const __m256* s = &_0123; + if (tail > 3) { + _mm256_storeu_ps(ptr, *s); + s = &_4567; + tail -= 4; + ptr += 8; + } + bool high = false; + if (tail > 1) { + _mm_storeu_ps(ptr, _mm256_extractf128_ps(*s, 0)); + ptr += 4; + tail -= 2; + high = true; + } + if (tail > 0) { + *(ptr + 0) = (*s)[ high ? 4 : 0]; + *(ptr + 1) = (*s)[ high ? 5 : 1]; + } + } else { + _mm256_storeu_ps(ptr + 0, _0123); + _mm256_storeu_ps(ptr + 8, _4567); + } + } + + SI void load4(const float* ptr, size_t tail, F* r, F* g, F* b, F* a) { + F _04, _15, _26, _37; + _04 = _15 = _26 = _37 = 0; + switch (tail) { + case 0: _37 = _mm256_insertf128_ps(_37, _mm_loadu_ps(ptr+28), 1); + case 7: _26 = _mm256_insertf128_ps(_26, _mm_loadu_ps(ptr+24), 1); + case 6: _15 = _mm256_insertf128_ps(_15, _mm_loadu_ps(ptr+20), 1); + case 5: _04 = _mm256_insertf128_ps(_04, _mm_loadu_ps(ptr+16), 1); + case 4: _37 = _mm256_insertf128_ps(_37, _mm_loadu_ps(ptr+12), 0); + case 3: _26 = _mm256_insertf128_ps(_26, _mm_loadu_ps(ptr+ 8), 0); + case 2: _15 = _mm256_insertf128_ps(_15, _mm_loadu_ps(ptr+ 4), 0); + case 1: _04 = _mm256_insertf128_ps(_04, _mm_loadu_ps(ptr+ 0), 0); + } + + F rg0145 = _mm256_unpacklo_ps(_04,_15), // r0 r1 g0 g1 | r4 r5 g4 g5 + ba0145 = _mm256_unpackhi_ps(_04,_15), + rg2367 = _mm256_unpacklo_ps(_26,_37), + ba2367 = _mm256_unpackhi_ps(_26,_37); + + *r = _mm256_unpacklo_pd(rg0145, rg2367); + *g = _mm256_unpackhi_pd(rg0145, rg2367); + *b = _mm256_unpacklo_pd(ba0145, ba2367); + *a = _mm256_unpackhi_pd(ba0145, ba2367); + } + SI void store4(float* ptr, size_t tail, F r, F g, F b, F a) { + F rg0145 = _mm256_unpacklo_ps(r, g), // r0 g0 r1 g1 | r4 g4 r5 g5 + rg2367 = _mm256_unpackhi_ps(r, g), // r2 ... | r6 ... + ba0145 = _mm256_unpacklo_ps(b, a), // b0 a0 b1 a1 | b4 a4 b5 a5 + ba2367 = _mm256_unpackhi_ps(b, a); // b2 ... | b6 ... + + F _04 = _mm256_unpacklo_pd(rg0145, ba0145), // r0 g0 b0 a0 | r4 g4 b4 a4 + _15 = _mm256_unpackhi_pd(rg0145, ba0145), // r1 ... | r5 ... + _26 = _mm256_unpacklo_pd(rg2367, ba2367), // r2 ... | r6 ... + _37 = _mm256_unpackhi_pd(rg2367, ba2367); // r3 ... | r7 ... + + if (SK_EXPECT(tail, 0)) { + if (tail > 0) { _mm_storeu_ps(ptr+ 0, _mm256_extractf128_ps(_04, 0)); } + if (tail > 1) { _mm_storeu_ps(ptr+ 4, _mm256_extractf128_ps(_15, 0)); } + if (tail > 2) { _mm_storeu_ps(ptr+ 8, _mm256_extractf128_ps(_26, 0)); } + if (tail > 3) { _mm_storeu_ps(ptr+12, _mm256_extractf128_ps(_37, 0)); } + if (tail > 4) { _mm_storeu_ps(ptr+16, _mm256_extractf128_ps(_04, 1)); } + if (tail > 5) { _mm_storeu_ps(ptr+20, _mm256_extractf128_ps(_15, 1)); } + if (tail > 6) { _mm_storeu_ps(ptr+24, _mm256_extractf128_ps(_26, 1)); } + } else { + F _01 = _mm256_permute2f128_ps(_04, _15, 32), // 32 == 0010 0000 == lo, lo + _23 = _mm256_permute2f128_ps(_26, _37, 32), + _45 = _mm256_permute2f128_ps(_04, _15, 49), // 49 == 0011 0001 == hi, hi + _67 = _mm256_permute2f128_ps(_26, _37, 49); + _mm256_storeu_ps(ptr+ 0, _01); + _mm256_storeu_ps(ptr+ 8, _23); + _mm256_storeu_ps(ptr+16, _45); + _mm256_storeu_ps(ptr+24, _67); + } + } + +#elif defined(JUMPER_IS_SSE2) || defined(JUMPER_IS_SSE41) + template using V = SK_VECTORTYPE(T, 4); + using F = V; + using I32 = V< int32_t>; + using U64 = V; + using U32 = V; + using U16 = V; + using U8 = V; + + #ifndef __clang__ + template SI T unaligned_load_SkNx(const P* p) { return T::Load(p); } + template SI void unaligned_store_SkNx(P* p, T v) { v.store(p); } + #define sk_unaligned_load unaligned_load_SkNx + #define sk_unaligned_store unaligned_store_SkNx + template SI __m128i widen_cast(const U16& src) { static_assert(sizeof(Dst) == sizeof(__m128i), ""); return src.fVec; } + #endif + + SI F mad(F f, F m, F a) { + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + return _mm_fmadd_ps(f,m,a); + #else + return f*m+a; + #endif + } + + SI F min(F a, F b) { return _mm_min_ps(a,b); } + SI F max(F a, F b) { return _mm_max_ps(a,b); } + SI F abs_(F v) { return _mm_and_ps(v, -v); } + SI F rcp (F v) { return _mm_rcp_ps (v); } + SI F rsqrt (F v) { return _mm_rsqrt_ps(v); } + SI F sqrt_(F v) { return _mm_sqrt_ps (v); } + SI U32 round(F v, F scale) { return _mm_cvtps_epi32(v*scale); } + + SI U16 pack(U32 v) { + #if defined(JUMPER_IS_SSE41) + auto p = _mm_packus_epi32(v,v); + #else + // Sign extend so that _mm_packs_epi32() does the pack we want. + auto p = _mm_srai_epi32(_mm_slli_epi32(v, 16), 16); + p = _mm_packs_epi32(p,p); + #endif + return sk_unaligned_load(&p); // We have two copies. Return (the lower) one. + } + SI U8 pack(U16 v) { + auto r = widen_cast<__m128i>(v); + r = _mm_packus_epi16(r,r); + return sk_unaligned_load(&r); + } + + SI F if_then_else(I32 c, F t, F e) { + #if defined(JUMPER_IS_SSE41) + return _mm_blendv_ps(e, t, _mm_castsi128_ps(c)); + #else + return _mm_or_ps(_mm_and_ps(_mm_castsi128_ps(c), t), _mm_andnot_ps(_mm_castsi128_ps(c), e)); + #endif + } + + SI F if_then_else(F c, F t, F e) { + #if defined(JUMPER_IS_SSE41) + return _mm_blendv_ps(e, t, c); + #else + return _mm_or_ps(_mm_and_ps(c, t), _mm_andnot_ps(c, e)); + #endif + } + + SI F floor_(F v) { + #if defined(JUMPER_IS_SSE41) + return _mm_floor_ps(v); + #else + F roundtrip = _mm_cvtepi32_ps(_mm_cvttps_epi32(v)); + return roundtrip - if_then_else(roundtrip > v, 1, 0); + #endif + } + + template + SI V gather(const T* p, U32 ix) { + return {p[ix[0]], p[ix[1]], p[ix[2]], p[ix[3]]}; + } + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + SI F gather(const float* p, U32 ix) { return _mm_i32gather_ps (p, ix, 4); } + SI U32 gather(const uint32_t* p, U32 ix) { return _mm_i32gather_epi32((const int*)p, ix, 4); } + SI U64 gather(const uint64_t* p, U32 ix) { + __m128i parts[] = { + _mm_i32gather_epi64((const long long int*)p, ix, 8), + _mm_i32gather_epi64((const long long int*)p, _mm_unpackhi_epi64(ix, _mm_setzero_si128()), 8), + }; + return bit_cast(parts); + } + #endif + + // TODO: these loads and stores are incredibly difficult to follow. + + SI void load2(const uint16_t* ptr, size_t tail, U16* r, U16* g) { + __m128i _01; + if (SK_EXPECT(tail,0)) { + _01 = _mm_setzero_si128(); + if (tail > 1) { + _01 = _mm_castpd_si128(_mm_loadl_pd(_mm_castsi128_pd(_01), (const double*)ptr)); // r0 g0 r1 g1 00 00 00 00 + if (tail > 2) { + _01 = _mm_insert_epi16(_01, *(ptr+4), 4); // r0 g0 r1 g1 r2 00 00 00 + _01 = _mm_insert_epi16(_01, *(ptr+5), 5); // r0 g0 r1 g1 r2 g2 00 00 + } + } else { + _01 = _mm_cvtsi32_si128(*(const uint32_t*)ptr); // r0 g0 00 00 00 00 00 00 + } + } else { + _01 = _mm_loadu_si128(((__m128i*)ptr) + 0); // r0 g0 r1 g1 r2 g2 r3 g3 + } + auto rg01_23 = _mm_shufflelo_epi16(_01, 0xD8); // r0 r1 g0 g1 r2 g2 r3 g3 + auto rg = _mm_shufflehi_epi16(rg01_23, 0xD8); // r0 r1 g0 g1 r2 r3 g2 g3 + + auto R = _mm_shuffle_epi32(rg, 0x88); // r0 r1 r2 r3 r0 r1 r2 r3 + auto G = _mm_shuffle_epi32(rg, 0xDD); // g0 g1 g2 g3 g0 g1 g2 g3 + *r = sk_unaligned_load(&R); + *g = sk_unaligned_load(&G); + } + SI void store2(uint16_t* ptr, size_t tail, U16 r, U16 g) { + U32 rg = _mm_unpacklo_epi16(widen_cast<__m128i>(r), widen_cast<__m128i>(g)); + if (SK_EXPECT(tail, 0)) { + if (tail > 1) { + _mm_storel_epi64((__m128i*)ptr, rg); + if (tail > 2) { + int32_t rgpair = rg[2]; + memcpy(ptr + 4, &rgpair, sizeof(rgpair)); + } + } else { + int32_t rgpair = rg[0]; + memcpy(ptr, &rgpair, sizeof(rgpair)); + } + } else { + _mm_storeu_si128((__m128i*)ptr + 0, rg); + } + } + + SI void load3(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b) { + __m128i _0, _1, _2, _3; + if (SK_EXPECT(tail,0)) { + _1 = _2 = _3 = _mm_setzero_si128(); + auto load_rgb = [](const uint16_t* src) { + auto v = _mm_cvtsi32_si128(*(const uint32_t*)src); + return _mm_insert_epi16(v, src[2], 2); + }; + if ( true ) { _0 = load_rgb(ptr + 0); } + if (tail > 1) { _1 = load_rgb(ptr + 3); } + if (tail > 2) { _2 = load_rgb(ptr + 6); } + } else { + // Load slightly weirdly to make sure we don't load past the end of 4x48 bits. + auto _01 = _mm_loadu_si128((const __m128i*)(ptr + 0)) , + _23 = _mm_srli_si128(_mm_loadu_si128((const __m128i*)(ptr + 4)), 4); + + // Each _N holds R,G,B for pixel N in its lower 3 lanes (upper 5 are ignored). + _0 = _01; + _1 = _mm_srli_si128(_01, 6); + _2 = _23; + _3 = _mm_srli_si128(_23, 6); + } + + // De-interlace to R,G,B. + auto _02 = _mm_unpacklo_epi16(_0, _2), // r0 r2 g0 g2 b0 b2 xx xx + _13 = _mm_unpacklo_epi16(_1, _3); // r1 r3 g1 g3 b1 b3 xx xx + + auto R = _mm_unpacklo_epi16(_02, _13), // r0 r1 r2 r3 g0 g1 g2 g3 + G = _mm_srli_si128(R, 8), + B = _mm_unpackhi_epi16(_02, _13); // b0 b1 b2 b3 xx xx xx xx + + *r = sk_unaligned_load(&R); + *g = sk_unaligned_load(&G); + *b = sk_unaligned_load(&B); + } + + SI void load4(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b, U16* a) { + __m128i _01, _23; + if (SK_EXPECT(tail,0)) { + _01 = _23 = _mm_setzero_si128(); + auto src = (const double*)ptr; + if ( true ) { _01 = _mm_castpd_si128(_mm_loadl_pd(_mm_castsi128_pd(_01), src + 0)); } // r0 g0 b0 a0 00 00 00 00 + if (tail > 1) { _01 = _mm_castpd_si128(_mm_loadh_pd(_mm_castsi128_pd(_01), src + 1)); } // r0 g0 b0 a0 r1 g1 b1 a1 + if (tail > 2) { _23 = _mm_castpd_si128(_mm_loadl_pd(_mm_castsi128_pd(_23), src + 2)); } // r2 g2 b2 a2 00 00 00 00 + } else { + _01 = _mm_loadu_si128(((__m128i*)ptr) + 0); // r0 g0 b0 a0 r1 g1 b1 a1 + _23 = _mm_loadu_si128(((__m128i*)ptr) + 1); // r2 g2 b2 a2 r3 g3 b3 a3 + } + + auto _02 = _mm_unpacklo_epi16(_01, _23), // r0 r2 g0 g2 b0 b2 a0 a2 + _13 = _mm_unpackhi_epi16(_01, _23); // r1 r3 g1 g3 b1 b3 a1 a3 + + auto rg = _mm_unpacklo_epi16(_02, _13), // r0 r1 r2 r3 g0 g1 g2 g3 + ba = _mm_unpackhi_epi16(_02, _13); // b0 b1 b2 b3 a0 a1 a2 a3 + + *r = sk_unaligned_load((uint16_t*)&rg + 0); + *g = sk_unaligned_load((uint16_t*)&rg + 4); + *b = sk_unaligned_load((uint16_t*)&ba + 0); + *a = sk_unaligned_load((uint16_t*)&ba + 4); + } + + SI void store4(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) { + auto rg = _mm_unpacklo_epi16(widen_cast<__m128i>(r), widen_cast<__m128i>(g)), + ba = _mm_unpacklo_epi16(widen_cast<__m128i>(b), widen_cast<__m128i>(a)); + + if (SK_EXPECT(tail, 0)) { + auto dst = (double*)ptr; + if ( true ) { _mm_storel_pd(dst + 0, _mm_castsi128_pd(_mm_unpacklo_epi32(rg, ba))); } + if (tail > 1) { _mm_storeh_pd(dst + 1, _mm_castsi128_pd(_mm_unpacklo_epi32(rg, ba))); } + if (tail > 2) { _mm_storel_pd(dst + 2, _mm_castsi128_pd(_mm_unpackhi_epi32(rg, ba))); } + } else { + _mm_storeu_si128((__m128i*)ptr + 0, _mm_unpacklo_epi32(rg, ba)); + _mm_storeu_si128((__m128i*)ptr + 1, _mm_unpackhi_epi32(rg, ba)); + } + } + + SI void load2(const float* ptr, size_t tail, F* r, F* g) { + F _01, _23; + if (SK_EXPECT(tail, 0)) { + _01 = _23 = _mm_setzero_ps(); + if ( true ) { _01 = _mm_loadl_pi(_01, (__m64 const*)(ptr + 0)); } + if (tail > 1) { _01 = _mm_loadh_pi(_01, (__m64 const*)(ptr + 2)); } + if (tail > 2) { _23 = _mm_loadl_pi(_23, (__m64 const*)(ptr + 4)); } + } else { + _01 = _mm_loadu_ps(ptr + 0); + _23 = _mm_loadu_ps(ptr + 4); + } + *r = _mm_shuffle_ps(_01, _23, 0x88); + *g = _mm_shuffle_ps(_01, _23, 0xDD); + } + SI void store2(float* ptr, size_t tail, F r, F g) { + F _01 = _mm_unpacklo_ps(r, g), + _23 = _mm_unpackhi_ps(r, g); + if (SK_EXPECT(tail, 0)) { + if ( true ) { _mm_storel_pi((__m64*)(ptr + 0), _01); } + if (tail > 1) { _mm_storeh_pi((__m64*)(ptr + 2), _01); } + if (tail > 2) { _mm_storel_pi((__m64*)(ptr + 4), _23); } + } else { + _mm_storeu_ps(ptr + 0, _01); + _mm_storeu_ps(ptr + 4, _23); + } + } + + SI void load4(const float* ptr, size_t tail, F* r, F* g, F* b, F* a) { + F _0, _1, _2, _3; + if (SK_EXPECT(tail, 0)) { + _1 = _2 = _3 = _mm_setzero_ps(); + if ( true ) { _0 = _mm_loadu_ps(ptr + 0); } + if (tail > 1) { _1 = _mm_loadu_ps(ptr + 4); } + if (tail > 2) { _2 = _mm_loadu_ps(ptr + 8); } + } else { + _0 = _mm_loadu_ps(ptr + 0); + _1 = _mm_loadu_ps(ptr + 4); + _2 = _mm_loadu_ps(ptr + 8); + _3 = _mm_loadu_ps(ptr +12); + } + _MM_TRANSPOSE4_PS(_0,_1,_2,_3); + *r = _0; + *g = _1; + *b = _2; + *a = _3; + } + + SI void store4(float* ptr, size_t tail, F r, F g, F b, F a) { + _MM_TRANSPOSE4_PS(r,g,b,a); + if (SK_EXPECT(tail, 0)) { + if ( true ) { _mm_storeu_ps(ptr + 0, r); } + if (tail > 1) { _mm_storeu_ps(ptr + 4, g); } + if (tail > 2) { _mm_storeu_ps(ptr + 8, b); } + } else { + _mm_storeu_ps(ptr + 0, r); + _mm_storeu_ps(ptr + 4, g); + _mm_storeu_ps(ptr + 8, b); + _mm_storeu_ps(ptr +12, a); + } + } +#endif + +// We need to be a careful with casts. +// (F)x means cast x to float in the portable path, but bit_cast x to float in the others. +// These named casts and bit_cast() are always what they seem to be. +SI F cast (U32 v) { return SK_CONVERTVECTOR((I32)v, F); } +SI F cast64(U64 v) { return SK_CONVERTVECTOR( v, F); } +SI U32 trunc_(F v) { return (U32)SK_CONVERTVECTOR( v, I32); } +SI U32 expand(U16 v) { return SK_CONVERTVECTOR( v, U32); } +SI U32 expand(U8 v) { return SK_CONVERTVECTOR( v, U32); } + +#if defined(__clang__) || defined(JUMPER_IS_SCALAR) +template +SI V if_then_else(I32 c, V t, V e) { + return bit_cast(if_then_else(c, bit_cast(t), bit_cast(e))); +} +#else +template +SI V if_then_else(T c, V t, V e) { + return bit_cast(if_then_else(c, bit_cast(t), bit_cast(e))); +} +#endif + +SI U16 bswap(U16 x) { +#if defined(JUMPER_IS_SSE2) || defined(JUMPER_IS_SSE41) + // Somewhat inexplicably Clang decides to do (x<<8) | (x>>8) in 32-bit lanes + // when generating code for SSE2 and SSE4.1. We'll do it manually... + auto v = widen_cast<__m128i>(x); + v = _mm_or_si128(_mm_slli_epi16(v,8), _mm_srli_epi16(v,8)); + return sk_unaligned_load(&v); +#else + return (x<<8) | (x>>8); +#endif +} + +SI F fract(F v) { return v - floor_(v); } + +// See http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html. +SI F approx_log2(F x) { + // e - 127 is a fair approximation of log2(x) in its own right... + F e = cast(bit_cast(x)) * (1.0f / (1<<23)); + + // ... but using the mantissa to refine its error is _much_ better. + F m = bit_cast((bit_cast(x) & 0x007fffff) | 0x3f000000); + return e + - 124.225514990f + - 1.498030302f * m + - 1.725879990f / (0.3520887068f + m); +} + +SI F approx_log(F x) { + const float ln2 = 0.69314718f; + return ln2 * approx_log2(x); +} + +SI F approx_pow2(F x) { + F f = fract(x); + return bit_cast(round(1.0f * (1<<23), + x + 121.274057500f + - 1.490129070f * f + + 27.728023300f / (4.84252568f - f))); +} + +SI F approx_exp(F x) { + const float log2_e = 1.4426950408889634074f; + return approx_pow2(log2_e * x); +} + +SI F approx_powf(F x, F y) { +#if defined(SK_LEGACY_APPROX_POWF_SPECIALCASE) + return if_then_else((x == 0) , 0 +#else + return if_then_else((x == 0)|(x == 1), x +#endif + , approx_pow2(approx_log2(x) * y)); +} + +SI F from_half(U16 h) { +#if defined(JUMPER_IS_NEON) && defined(SK_CPU_ARM64) \ + && !defined(SK_BUILD_FOR_GOOGLE3) // Temporary workaround for some Google3 builds. + return vcvt_f32_f16(h); + +#elif defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + return _mm256_cvtph_ps(h); +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + return _mm_cvtph_ps(h); +#else + // Remember, a half is 1-5-10 (sign-exponent-mantissa) with 15 exponent bias. + U32 sem = expand(h), + s = sem & 0x8000, + em = sem ^ s; + + // Convert to 1-8-23 float with 127 bias, flushing denorm halfs (including zero) to zero. + auto denorm = (I32)em < 0x0400; // I32 comparison is often quicker, and always safe here. + return if_then_else(denorm, F(0) + , bit_cast( (s<<16) + (em<<13) + ((127-15)<<23) )); +#endif +} + +SI U16 to_half(F f) { +#if defined(JUMPER_IS_NEON) && defined(SK_CPU_ARM64) \ + && !defined(SK_BUILD_FOR_GOOGLE3) // Temporary workaround for some Google3 builds. + return vcvt_f16_f32(f); + +#elif defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + return _mm256_cvtps_ph(f, _MM_FROUND_CUR_DIRECTION); +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + return _mm_cvtps_ph(f, _MM_FROUND_CUR_DIRECTION); +#else + // Remember, a float is 1-8-23 (sign-exponent-mantissa) with 127 exponent bias. + U32 sem = bit_cast(f), + s = sem & 0x80000000, + em = sem ^ s; + + // Convert to 1-5-10 half with 15 bias, flushing denorm halfs (including zero) to zero. + auto denorm = (I32)em < 0x38800000; // I32 comparison is often quicker, and always safe here. + return pack(if_then_else(denorm, U32(0) + , (s>>16) + (em>>13) - ((127-15)<<10))); +#endif +} + +// Our fundamental vector depth is our pixel stride. +static const size_t N = sizeof(F) / sizeof(float); + +// We're finally going to get to what a Stage function looks like! +// tail == 0 ~~> work on a full N pixels +// tail != 0 ~~> work on only the first tail pixels +// tail is always < N. + +// Any custom ABI to use for all (non-externally-facing) stage functions? +// Also decide here whether to use narrow (compromise) or wide (ideal) stages. +#if defined(SK_CPU_ARM32) && defined(JUMPER_IS_NEON) + // This lets us pass vectors more efficiently on 32-bit ARM. + // We can still only pass 16 floats, so best as 4x {r,g,b,a}. + #define ABI __attribute__((pcs("aapcs-vfp"))) + #define JUMPER_NARROW_STAGES 1 +#elif 0 && defined(_MSC_VER) && defined(__clang__) && defined(__x86_64__) + // SysV ABI makes it very sensible to use wide stages with clang-cl. + // TODO: crashes during compilation :( + #define ABI __attribute__((sysv_abi)) + #define JUMPER_NARROW_STAGES 0 +#elif defined(_MSC_VER) + // Even if not vectorized, this lets us pass {r,g,b,a} as registers, + // instead of {b,a} on the stack. Narrow stages work best for __vectorcall. + #define ABI __vectorcall + #define JUMPER_NARROW_STAGES 1 +#elif defined(__x86_64__) || defined(SK_CPU_ARM64) + // These platforms are ideal for wider stages, and their default ABI is ideal. + #define ABI + #define JUMPER_NARROW_STAGES 0 +#else + // 32-bit or unknown... shunt them down the narrow path. + // Odds are these have few registers and are better off there. + #define ABI + #define JUMPER_NARROW_STAGES 1 +#endif + +#if JUMPER_NARROW_STAGES + struct Params { + size_t dx, dy, tail; + F dr,dg,db,da; + }; + using Stage = void(ABI*)(Params*, void** program, F r, F g, F b, F a); +#else + // We keep program the second argument, so that it's passed in rsi for load_and_inc(). + using Stage = void(ABI*)(size_t tail, void** program, size_t dx, size_t dy, F,F,F,F, F,F,F,F); +#endif + + +static void start_pipeline(size_t dx, size_t dy, size_t xlimit, size_t ylimit, void** program) { + auto start = (Stage)load_and_inc(program); + const size_t x0 = dx; + for (; dy < ylimit; dy++) { + #if JUMPER_NARROW_STAGES + Params params = { x0,dy,0, 0,0,0,0 }; + while (params.dx + N <= xlimit) { + start(¶ms,program, 0,0,0,0); + params.dx += N; + } + if (size_t tail = xlimit - params.dx) { + params.tail = tail; + start(¶ms,program, 0,0,0,0); + } + #else + dx = x0; + while (dx + N <= xlimit) { + start(0,program,dx,dy, 0,0,0,0, 0,0,0,0); + dx += N; + } + if (size_t tail = xlimit - dx) { + start(tail,program,dx,dy, 0,0,0,0, 0,0,0,0); + } + #endif + } +} + +#if JUMPER_NARROW_STAGES + #define STAGE(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + F& r, F& g, F& b, F& a, F& dr, F& dg, F& db, F& da); \ + static void ABI name(Params* params, void** program, \ + F r, F g, F b, F a) { \ + name##_k(Ctx{program},params->dx,params->dy,params->tail, r,g,b,a, \ + params->dr, params->dg, params->db, params->da); \ + auto next = (Stage)load_and_inc(program); \ + next(params,program, r,g,b,a); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + F& r, F& g, F& b, F& a, F& dr, F& dg, F& db, F& da) +#else + #define STAGE(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + F& r, F& g, F& b, F& a, F& dr, F& dg, F& db, F& da); \ + static void ABI name(size_t tail, void** program, size_t dx, size_t dy, \ + F r, F g, F b, F a, F dr, F dg, F db, F da) { \ + name##_k(Ctx{program},dx,dy,tail, r,g,b,a, dr,dg,db,da); \ + auto next = (Stage)load_and_inc(program); \ + next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + F& r, F& g, F& b, F& a, F& dr, F& dg, F& db, F& da) +#endif + + +// just_return() is a simple no-op stage that only exists to end the chain, +// returning back up to start_pipeline(), and from there to the caller. +#if JUMPER_NARROW_STAGES + static void ABI just_return(Params*, void**, F,F,F,F) {} +#else + static void ABI just_return(size_t, void**, size_t,size_t, F,F,F,F, F,F,F,F) {} +#endif + + +// We could start defining normal Stages now. But first, some helper functions. + +// These load() and store() methods are tail-aware, +// but focus mainly on keeping the at-stride tail==0 case fast. + +template +SI V load(const T* src, size_t tail) { +#if !defined(JUMPER_IS_SCALAR) + SK_ASSUME(tail < N); + if (SK_EXPECT(tail, 0)) { + #ifdef __clang__ + V v{}; // Any inactive lanes are zeroed. + switch (tail) { + case 7: v[6] = src[6]; + case 6: v[5] = src[5]; + case 5: v[4] = src[4]; + case 4: memcpy(&v, src, 4*sizeof(T)); break; + case 3: v[2] = src[2]; + case 2: memcpy(&v, src, 2*sizeof(T)); break; + case 1: memcpy(&v, src, 1*sizeof(T)); break; + } + #else + V v(0); + memcpy(&v, src, tail * sizeof(T)); + #endif + return v; + } +#endif + return sk_unaligned_load(src); +} + +template +SI void store(T* dst, V v, size_t tail) { +#if !defined(JUMPER_IS_SCALAR) + SK_ASSUME(tail < N); + if (SK_EXPECT(tail, 0)) { + #ifdef __clang__ + switch (tail) { + case 7: dst[6] = v[6]; + case 6: dst[5] = v[5]; + case 5: dst[4] = v[4]; + case 4: memcpy(dst, &v, 4*sizeof(T)); break; + case 3: dst[2] = v[2]; + case 2: memcpy(dst, &v, 2*sizeof(T)); break; + case 1: memcpy(dst, &v, 1*sizeof(T)); break; + } + #else + memcpy(dst, &v, tail * sizeof(T)); + #endif + return; + } +#endif + sk_unaligned_store(dst, v); +} + +SI F from_byte(U8 b) { + return cast(expand(b)) * (1/255.0f); +} +SI F from_short(U16 s) { + return cast(expand(s)) * (1/65535.0f); +} +SI void from_565(U16 _565, F* r, F* g, F* b) { + U32 wide = expand(_565); + *r = cast(wide & (31<<11)) * (1.0f / (31<<11)); + *g = cast(wide & (63<< 5)) * (1.0f / (63<< 5)); + *b = cast(wide & (31<< 0)) * (1.0f / (31<< 0)); +} +SI void from_4444(U16 _4444, F* r, F* g, F* b, F* a) { + U32 wide = expand(_4444); + *r = cast(wide & (15<<12)) * (1.0f / (15<<12)); + *g = cast(wide & (15<< 8)) * (1.0f / (15<< 8)); + *b = cast(wide & (15<< 4)) * (1.0f / (15<< 4)); + *a = cast(wide & (15<< 0)) * (1.0f / (15<< 0)); +} +SI void from_8888(U32 _8888, F* r, F* g, F* b, F* a) { + *r = cast((_8888 ) & 0xff) * (1/255.0f); + *g = cast((_8888 >> 8) & 0xff) * (1/255.0f); + *b = cast((_8888 >> 16) & 0xff) * (1/255.0f); + *a = cast((_8888 >> 24) ) * (1/255.0f); +} +SI void from_88(U16 _88, F* r, F* g) { + U32 wide = expand(_88); + *r = cast((wide ) & 0xff) * (1/255.0f); + *g = cast((wide >> 8) & 0xff) * (1/255.0f); +} +SI void from_1010102(U32 rgba, F* r, F* g, F* b, F* a) { + *r = cast((rgba ) & 0x3ff) * (1/1023.0f); + *g = cast((rgba >> 10) & 0x3ff) * (1/1023.0f); + *b = cast((rgba >> 20) & 0x3ff) * (1/1023.0f); + *a = cast((rgba >> 30) ) * (1/ 3.0f); +} +SI void from_1616(U32 _1616, F* r, F* g) { + *r = cast((_1616 ) & 0xffff) * (1/65535.0f); + *g = cast((_1616 >> 16) & 0xffff) * (1/65535.0f); +} +SI void from_16161616(U64 _16161616, F* r, F* g, F* b, F* a) { + *r = cast64((_16161616 ) & 0xffff) * (1/65535.0f); + *g = cast64((_16161616 >> 16) & 0xffff) * (1/65535.0f); + *b = cast64((_16161616 >> 32) & 0xffff) * (1/65535.0f); + *a = cast64((_16161616 >> 48) & 0xffff) * (1/65535.0f); +} + +// Used by load_ and store_ stages to get to the right (dx,dy) starting point of contiguous memory. +template +SI T* ptr_at_xy(const SkRasterPipeline_MemoryCtx* ctx, size_t dx, size_t dy) { + return (T*)ctx->pixels + dy*ctx->stride + dx; +} + +// clamp v to [0,limit). +SI F clamp(F v, F limit) { + F inclusive = bit_cast( bit_cast(limit) - 1 ); // Exclusive -> inclusive. + return min(max(0, v), inclusive); +} + +// Used by gather_ stages to calculate the base pointer and a vector of indices to load. +template +SI U32 ix_and_ptr(T** ptr, const SkRasterPipeline_GatherCtx* ctx, F x, F y) { + x = clamp(x, ctx->width); + y = clamp(y, ctx->height); + + *ptr = (const T*)ctx->pixels; + return trunc_(y)*ctx->stride + trunc_(x); +} + +// We often have a nominally [0,1] float value we need to scale and convert to an integer, +// whether for a table lookup or to pack back down into bytes for storage. +// +// In practice, especially when dealing with interesting color spaces, that notionally +// [0,1] float may be out of [0,1] range. Unorms cannot represent that, so we must clamp. +// +// You can adjust the expected input to [0,bias] by tweaking that parameter. +SI U32 to_unorm(F v, F scale, F bias = 1.0f) { + // TODO: platform-specific implementations to to_unorm(), removing round() entirely? + // Any time we use round() we probably want to use to_unorm(). + return round(min(max(0, v), bias), scale); +} + +#if !defined(__clang__) && !defined(JUMPER_IS_SCALAR) +SI I32 cond_to_mask(F cond) { return _mm_castps_si128(cond); } +#else +SI I32 cond_to_mask(I32 cond) { return if_then_else(cond, I32(~0), I32(0)); } +#endif + +// Now finally, normal Stages! + +STAGE(seed_shader, Ctx::None) { + static const float iota[] = { + 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, + 8.5f, 9.5f,10.5f,11.5f,12.5f,13.5f,14.5f,15.5f, + }; + // It's important for speed to explicitly cast(dx) and cast(dy), + // which has the effect of splatting them to vectors before converting to floats. + // On Intel this breaks a data dependency on previous loop iterations' registers. + r = cast(dx) + sk_unaligned_load(iota); + g = cast(dy) + 0.5f; + b = 1.0f; + a = 0; + dr = dg = db = da = 0; +} + +STAGE(dither, const float* rate) { + // Get [(dx,dy), (dx+1,dy), (dx+2,dy), ...] loaded up in integer vectors. + uint32_t iota[] = {0,1,2,3,4,5,6,7}; + U32 X = sk_unaligned_load(iota) + dx, + Y = dy; + + // We're doing 8x8 ordered dithering, see https://en.wikipedia.org/wiki/Ordered_dithering. + // In this case n=8 and we're using the matrix that looks like 1/64 x [ 0 48 12 60 ... ]. + + // We only need X and X^Y from here on, so it's easier to just think of that as "Y". + Y ^= X; + + // We'll mix the bottom 3 bits of each of X and Y to make 6 bits, + // for 2^6 == 64 == 8x8 matrix values. If X=abc and Y=def, we make fcebda. + U32 M = (Y & 1) << 5 | (X & 1) << 4 + | (Y & 2) << 2 | (X & 2) << 1 + | (Y & 4) >> 1 | (X & 4) >> 2; + + // Scale that dither to [0,1), then (-0.5,+0.5), here using 63/128 = 0.4921875 as 0.5-epsilon. + // We want to make sure our dither is less than 0.5 in either direction to keep exact values + // like 0 and 1 unchanged after rounding. + F dither = cast(M) * (2/128.0f) - (63/128.0f); + + r += *rate*dither; + g += *rate*dither; + b += *rate*dither; + + r = max(0, min(r, a)); + g = max(0, min(g, a)); + b = max(0, min(b, a)); +} + +// load 4 floats from memory, and splat them into r,g,b,a +STAGE(uniform_color, const SkRasterPipeline_UniformColorCtx* c) { + r = c->r; + g = c->g; + b = c->b; + a = c->a; +} +STAGE(unbounded_uniform_color, const SkRasterPipeline_UniformColorCtx* c) { + r = c->r; + g = c->g; + b = c->b; + a = c->a; +} +// load 4 floats from memory, and splat them into dr,dg,db,da +STAGE(uniform_color_dst, const SkRasterPipeline_UniformColorCtx* c) { + dr = c->r; + dg = c->g; + db = c->b; + da = c->a; +} + +// splats opaque-black into r,g,b,a +STAGE(black_color, Ctx::None) { + r = g = b = 0.0f; + a = 1.0f; +} + +STAGE(white_color, Ctx::None) { + r = g = b = a = 1.0f; +} + +// load registers r,g,b,a from context (mirrors store_rgba) +STAGE(load_src, const float* ptr) { + r = sk_unaligned_load(ptr + 0*N); + g = sk_unaligned_load(ptr + 1*N); + b = sk_unaligned_load(ptr + 2*N); + a = sk_unaligned_load(ptr + 3*N); +} + +// store registers r,g,b,a into context (mirrors load_rgba) +STAGE(store_src, float* ptr) { + sk_unaligned_store(ptr + 0*N, r); + sk_unaligned_store(ptr + 1*N, g); + sk_unaligned_store(ptr + 2*N, b); + sk_unaligned_store(ptr + 3*N, a); +} + +// load registers dr,dg,db,da from context (mirrors store_dst) +STAGE(load_dst, const float* ptr) { + dr = sk_unaligned_load(ptr + 0*N); + dg = sk_unaligned_load(ptr + 1*N); + db = sk_unaligned_load(ptr + 2*N); + da = sk_unaligned_load(ptr + 3*N); +} + +// store registers dr,dg,db,da into context (mirrors load_dst) +STAGE(store_dst, float* ptr) { + sk_unaligned_store(ptr + 0*N, dr); + sk_unaligned_store(ptr + 1*N, dg); + sk_unaligned_store(ptr + 2*N, db); + sk_unaligned_store(ptr + 3*N, da); +} + +// Most blend modes apply the same logic to each channel. +#define BLEND_MODE(name) \ + SI F name##_channel(F s, F d, F sa, F da); \ + STAGE(name, Ctx::None) { \ + r = name##_channel(r,dr,a,da); \ + g = name##_channel(g,dg,a,da); \ + b = name##_channel(b,db,a,da); \ + a = name##_channel(a,da,a,da); \ + } \ + SI F name##_channel(F s, F d, F sa, F da) + +SI F inv(F x) { return 1.0f - x; } +SI F two(F x) { return x + x; } + + +BLEND_MODE(clear) { return 0; } +BLEND_MODE(srcatop) { return s*da + d*inv(sa); } +BLEND_MODE(dstatop) { return d*sa + s*inv(da); } +BLEND_MODE(srcin) { return s * da; } +BLEND_MODE(dstin) { return d * sa; } +BLEND_MODE(srcout) { return s * inv(da); } +BLEND_MODE(dstout) { return d * inv(sa); } +BLEND_MODE(srcover) { return mad(d, inv(sa), s); } +BLEND_MODE(dstover) { return mad(s, inv(da), d); } + +BLEND_MODE(modulate) { return s*d; } +BLEND_MODE(multiply) { return s*inv(da) + d*inv(sa) + s*d; } +BLEND_MODE(plus_) { return min(s + d, 1.0f); } // We can clamp to either 1 or sa. +BLEND_MODE(screen) { return s + d - s*d; } +BLEND_MODE(xor_) { return s*inv(da) + d*inv(sa); } +#undef BLEND_MODE + +// Most other blend modes apply the same logic to colors, and srcover to alpha. +#define BLEND_MODE(name) \ + SI F name##_channel(F s, F d, F sa, F da); \ + STAGE(name, Ctx::None) { \ + r = name##_channel(r,dr,a,da); \ + g = name##_channel(g,dg,a,da); \ + b = name##_channel(b,db,a,da); \ + a = mad(da, inv(a), a); \ + } \ + SI F name##_channel(F s, F d, F sa, F da) + +BLEND_MODE(darken) { return s + d - max(s*da, d*sa) ; } +BLEND_MODE(lighten) { return s + d - min(s*da, d*sa) ; } +BLEND_MODE(difference) { return s + d - two(min(s*da, d*sa)); } +BLEND_MODE(exclusion) { return s + d - two(s*d); } + +BLEND_MODE(colorburn) { + return if_then_else(d == da, d + s*inv(da), + if_then_else(s == 0, /* s + */ d*inv(sa), + sa*(da - min(da, (da-d)*sa*rcp(s))) + s*inv(da) + d*inv(sa))); +} +BLEND_MODE(colordodge) { + return if_then_else(d == 0, /* d + */ s*inv(da), + if_then_else(s == sa, s + d*inv(sa), + sa*min(da, (d*sa)*rcp(sa - s)) + s*inv(da) + d*inv(sa))); +} +BLEND_MODE(hardlight) { + return s*inv(da) + d*inv(sa) + + if_then_else(two(s) <= sa, two(s*d), sa*da - two((da-d)*(sa-s))); +} +BLEND_MODE(overlay) { + return s*inv(da) + d*inv(sa) + + if_then_else(two(d) <= da, two(s*d), sa*da - two((da-d)*(sa-s))); +} + +BLEND_MODE(softlight) { + F m = if_then_else(da > 0, d / da, 0), + s2 = two(s), + m4 = two(two(m)); + + // The logic forks three ways: + // 1. dark src? + // 2. light src, dark dst? + // 3. light src, light dst? + F darkSrc = d*(sa + (s2 - sa)*(1.0f - m)), // Used in case 1. + darkDst = (m4*m4 + m4)*(m - 1.0f) + 7.0f*m, // Used in case 2. + liteDst = rcp(rsqrt(m)) - m, // Used in case 3. + liteSrc = d*sa + da*(s2 - sa) * if_then_else(two(two(d)) <= da, darkDst, liteDst); // 2 or 3? + return s*inv(da) + d*inv(sa) + if_then_else(s2 <= sa, darkSrc, liteSrc); // 1 or (2 or 3)? +} +#undef BLEND_MODE + +// We're basing our implemenation of non-separable blend modes on +// https://www.w3.org/TR/compositing-1/#blendingnonseparable. +// and +// https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf +// They're equivalent, but ES' math has been better simplified. +// +// Anything extra we add beyond that is to make the math work with premul inputs. + +SI F sat(F r, F g, F b) { return max(r, max(g,b)) - min(r, min(g,b)); } +SI F lum(F r, F g, F b) { return r*0.30f + g*0.59f + b*0.11f; } + +SI void set_sat(F* r, F* g, F* b, F s) { + F mn = min(*r, min(*g,*b)), + mx = max(*r, max(*g,*b)), + sat = mx - mn; + + // Map min channel to 0, max channel to s, and scale the middle proportionally. + auto scale = [=](F c) { + return if_then_else(sat == 0, 0, (c - mn) * s / sat); + }; + *r = scale(*r); + *g = scale(*g); + *b = scale(*b); +} +SI void set_lum(F* r, F* g, F* b, F l) { + F diff = l - lum(*r, *g, *b); + *r += diff; + *g += diff; + *b += diff; +} +SI void clip_color(F* r, F* g, F* b, F a) { + F mn = min(*r, min(*g, *b)), + mx = max(*r, max(*g, *b)), + l = lum(*r, *g, *b); + + auto clip = [=](F c) { + c = if_then_else(mn < 0 && l != mn, l + (c - l) * ( l) / (l - mn), c); + c = if_then_else(mx > a && l != mx, l + (c - l) * (a - l) / (mx - l), c); + c = max(c, 0); // Sometimes without this we may dip just a little negative. + return c; + }; + *r = clip(*r); + *g = clip(*g); + *b = clip(*b); +} + +STAGE(hue, Ctx::None) { + F R = r*a, + G = g*a, + B = b*a; + + set_sat(&R, &G, &B, sat(dr,dg,db)*a); + set_lum(&R, &G, &B, lum(dr,dg,db)*a); + clip_color(&R,&G,&B, a*da); + + r = r*inv(da) + dr*inv(a) + R; + g = g*inv(da) + dg*inv(a) + G; + b = b*inv(da) + db*inv(a) + B; + a = a + da - a*da; +} +STAGE(saturation, Ctx::None) { + F R = dr*a, + G = dg*a, + B = db*a; + + set_sat(&R, &G, &B, sat( r, g, b)*da); + set_lum(&R, &G, &B, lum(dr,dg,db)* a); // (This is not redundant.) + clip_color(&R,&G,&B, a*da); + + r = r*inv(da) + dr*inv(a) + R; + g = g*inv(da) + dg*inv(a) + G; + b = b*inv(da) + db*inv(a) + B; + a = a + da - a*da; +} +STAGE(color, Ctx::None) { + F R = r*da, + G = g*da, + B = b*da; + + set_lum(&R, &G, &B, lum(dr,dg,db)*a); + clip_color(&R,&G,&B, a*da); + + r = r*inv(da) + dr*inv(a) + R; + g = g*inv(da) + dg*inv(a) + G; + b = b*inv(da) + db*inv(a) + B; + a = a + da - a*da; +} +STAGE(luminosity, Ctx::None) { + F R = dr*a, + G = dg*a, + B = db*a; + + set_lum(&R, &G, &B, lum(r,g,b)*da); + clip_color(&R,&G,&B, a*da); + + r = r*inv(da) + dr*inv(a) + R; + g = g*inv(da) + dg*inv(a) + G; + b = b*inv(da) + db*inv(a) + B; + a = a + da - a*da; +} + +STAGE(srcover_rgba_8888, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U32 dst = load(ptr, tail); + dr = cast((dst ) & 0xff); + dg = cast((dst >> 8) & 0xff); + db = cast((dst >> 16) & 0xff); + da = cast((dst >> 24) ); + // {dr,dg,db,da} are in [0,255] + // { r, g, b, a} are in [0, 1] (but may be out of gamut) + + r = mad(dr, inv(a), r*255.0f); + g = mad(dg, inv(a), g*255.0f); + b = mad(db, inv(a), b*255.0f); + a = mad(da, inv(a), a*255.0f); + // { r, g, b, a} are now in [0,255] (but may be out of gamut) + + // to_unorm() clamps back to gamut. Scaling by 1 since we're already 255-biased. + dst = to_unorm(r, 1, 255) + | to_unorm(g, 1, 255) << 8 + | to_unorm(b, 1, 255) << 16 + | to_unorm(a, 1, 255) << 24; + store(ptr, dst, tail); +} + +STAGE(clamp_0, Ctx::None) { + r = max(r, 0); + g = max(g, 0); + b = max(b, 0); + a = max(a, 0); +} + +STAGE(clamp_1, Ctx::None) { + r = min(r, 1.0f); + g = min(g, 1.0f); + b = min(b, 1.0f); + a = min(a, 1.0f); +} + +STAGE(clamp_a, Ctx::None) { + a = min(a, 1.0f); + r = min(r, a); + g = min(g, a); + b = min(b, a); +} + +STAGE(clamp_gamut, Ctx::None) { + // If you're using this stage, a should already be in [0,1]. + r = min(max(r, 0), a); + g = min(max(g, 0), a); + b = min(max(b, 0), a); +} + +STAGE(set_rgb, const float* rgb) { + r = rgb[0]; + g = rgb[1]; + b = rgb[2]; +} +STAGE(unbounded_set_rgb, const float* rgb) { + r = rgb[0]; + g = rgb[1]; + b = rgb[2]; +} + +STAGE(swap_rb, Ctx::None) { + auto tmp = r; + r = b; + b = tmp; +} +STAGE(swap_rb_dst, Ctx::None) { + auto tmp = dr; + dr = db; + db = tmp; +} + +STAGE(move_src_dst, Ctx::None) { + dr = r; + dg = g; + db = b; + da = a; +} +STAGE(move_dst_src, Ctx::None) { + r = dr; + g = dg; + b = db; + a = da; +} + +STAGE(premul, Ctx::None) { + r = r * a; + g = g * a; + b = b * a; +} +STAGE(premul_dst, Ctx::None) { + dr = dr * da; + dg = dg * da; + db = db * da; +} +STAGE(unpremul, Ctx::None) { + float inf = bit_cast(0x7f800000); + auto scale = if_then_else(1.0f/a < inf, 1.0f/a, 0); + r *= scale; + g *= scale; + b *= scale; +} + +STAGE(force_opaque , Ctx::None) { a = 1; } +STAGE(force_opaque_dst, Ctx::None) { da = 1; } + +STAGE(rgb_to_hsl, Ctx::None) { + F mx = max(r, max(g,b)), + mn = min(r, min(g,b)), + d = mx - mn, + d_rcp = 1.0f / d; + + F h = (1/6.0f) * + if_then_else(mx == mn, 0, + if_then_else(mx == r, (g-b)*d_rcp + if_then_else(g < b, 6.0f, 0), + if_then_else(mx == g, (b-r)*d_rcp + 2.0f, + (r-g)*d_rcp + 4.0f))); + + F l = (mx + mn) * 0.5f; + F s = if_then_else(mx == mn, 0, + d / if_then_else(l > 0.5f, 2.0f-mx-mn, mx+mn)); + + r = h; + g = s; + b = l; +} +STAGE(hsl_to_rgb, Ctx::None) { + F h = r, + s = g, + l = b; + + F q = l + if_then_else(l >= 0.5f, s - l*s, l*s), + p = 2.0f*l - q; + + auto hue_to_rgb = [&](F t) { + t = fract(t); + + F r = p; + r = if_then_else(t >= 4/6.0f, r, p + (q-p)*(4.0f - 6.0f*t)); + r = if_then_else(t >= 3/6.0f, r, q); + r = if_then_else(t >= 1/6.0f, r, p + (q-p)*( 6.0f*t)); + return r; + }; + + r = if_then_else(s == 0, l, hue_to_rgb(h + (1/3.0f))); + g = if_then_else(s == 0, l, hue_to_rgb(h )); + b = if_then_else(s == 0, l, hue_to_rgb(h - (1/3.0f))); +} + +// Derive alpha's coverage from rgb coverage and the values of src and dst alpha. +SI F alpha_coverage_from_rgb_coverage(F a, F da, F cr, F cg, F cb) { + return if_then_else(a < da, min(cr, min(cg,cb)) + , max(cr, max(cg,cb))); +} + +STAGE(scale_1_float, const float* c) { + r = r * *c; + g = g * *c; + b = b * *c; + a = a * *c; +} +STAGE(scale_u8, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + auto scales = load(ptr, tail); + auto c = from_byte(scales); + + r = r * c; + g = g * c; + b = b * c; + a = a * c; +} +STAGE(scale_565, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + F cr,cg,cb; + from_565(load(ptr, tail), &cr, &cg, &cb); + + F ca = alpha_coverage_from_rgb_coverage(a,da, cr,cg,cb); + + r = r * cr; + g = g * cg; + b = b * cb; + a = a * ca; +} + +SI F lerp(F from, F to, F t) { + return mad(to-from, t, from); +} + +STAGE(lerp_1_float, const float* c) { + r = lerp(dr, r, *c); + g = lerp(dg, g, *c); + b = lerp(db, b, *c); + a = lerp(da, a, *c); +} +STAGE(lerp_native, const float scales[]) { + auto c = sk_unaligned_load(scales); + r = lerp(dr, r, c); + g = lerp(dg, g, c); + b = lerp(db, b, c); + a = lerp(da, a, c); +} +STAGE(lerp_u8, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + auto scales = load(ptr, tail); + auto c = from_byte(scales); + + r = lerp(dr, r, c); + g = lerp(dg, g, c); + b = lerp(db, b, c); + a = lerp(da, a, c); +} +STAGE(lerp_565, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + F cr,cg,cb; + from_565(load(ptr, tail), &cr, &cg, &cb); + + F ca = alpha_coverage_from_rgb_coverage(a,da, cr,cg,cb); + + r = lerp(dr, r, cr); + g = lerp(dg, g, cg); + b = lerp(db, b, cb); + a = lerp(da, a, ca); +} + +STAGE(emboss, const SkRasterPipeline_EmbossCtx* ctx) { + auto mptr = ptr_at_xy(&ctx->mul, dx,dy), + aptr = ptr_at_xy(&ctx->add, dx,dy); + + F mul = from_byte(load(mptr, tail)), + add = from_byte(load(aptr, tail)); + + r = mad(r, mul, add); + g = mad(g, mul, add); + b = mad(b, mul, add); +} + +STAGE(byte_tables, const void* ctx) { // TODO: rename Tables SkRasterPipeline_ByteTablesCtx + struct Tables { const uint8_t *r, *g, *b, *a; }; + auto tables = (const Tables*)ctx; + + r = from_byte(gather(tables->r, to_unorm(r, 255))); + g = from_byte(gather(tables->g, to_unorm(g, 255))); + b = from_byte(gather(tables->b, to_unorm(b, 255))); + a = from_byte(gather(tables->a, to_unorm(a, 255))); +} + +SI F strip_sign(F x, U32* sign) { + U32 bits = bit_cast(x); + *sign = bits & 0x80000000; + return bit_cast(bits ^ *sign); +} + +SI F apply_sign(F x, U32 sign) { + return bit_cast(sign | bit_cast(x)); +} + +STAGE(parametric, const skcms_TransferFunction* ctx) { + auto fn = [&](F v) { + U32 sign; + v = strip_sign(v, &sign); + + F r = if_then_else(v <= ctx->d, mad(ctx->c, v, ctx->f) + , approx_powf(mad(ctx->a, v, ctx->b), ctx->g) + ctx->e); + return apply_sign(r, sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} + +STAGE(gamma_, const float* G) { + auto fn = [&](F v) { + U32 sign; + v = strip_sign(v, &sign); + return apply_sign(approx_powf(v, *G), sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} + +STAGE(PQish, const skcms_TransferFunction* ctx) { + auto fn = [&](F v) { + U32 sign; + v = strip_sign(v, &sign); + + F r = approx_powf(max(mad(ctx->b, approx_powf(v, ctx->c), ctx->a), 0) + / (mad(ctx->e, approx_powf(v, ctx->c), ctx->d)), + ctx->f); + + return apply_sign(r, sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} + +STAGE(HLGish, const skcms_TransferFunction* ctx) { + auto fn = [&](F v) { + U32 sign; + v = strip_sign(v, &sign); + + const float R = ctx->a, G = ctx->b, + a = ctx->c, b = ctx->d, c = ctx->e; + + F r = if_then_else(v*R <= 1, approx_powf(v*R, G) + , approx_exp((v-c)*a) + b); + + return apply_sign(r, sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} + +STAGE(HLGinvish, const skcms_TransferFunction* ctx) { + auto fn = [&](F v) { + U32 sign; + v = strip_sign(v, &sign); + + const float R = ctx->a, G = ctx->b, + a = ctx->c, b = ctx->d, c = ctx->e; + + F r = if_then_else(v <= 1, R * approx_powf(v, G) + , a * approx_log(v - b) + c); + + return apply_sign(r, sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} + +STAGE(from_srgb, Ctx::None) { + auto fn = [](F s) { + U32 sign; + s = strip_sign(s, &sign); + auto lo = s * (1/12.92f); + auto hi = mad(s*s, mad(s, 0.3000f, 0.6975f), 0.0025f); + return apply_sign(if_then_else(s < 0.055f, lo, hi), sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} +STAGE(to_srgb, Ctx::None) { + auto fn = [](F l) { + U32 sign; + l = strip_sign(l, &sign); + // We tweak c and d for each instruction set to make sure fn(1) is exactly 1. + #if defined(JUMPER_IS_AVX512) + const float c = 1.130026340485f, + d = 0.141387879848f; + #elif defined(JUMPER_IS_SSE2) || defined(JUMPER_IS_SSE41) || \ + defined(JUMPER_IS_AVX ) || defined(JUMPER_IS_HSW ) + const float c = 1.130048394203f, + d = 0.141357362270f; + #elif defined(JUMPER_IS_NEON) + const float c = 1.129999995232f, + d = 0.141381442547f; + #else + const float c = 1.129999995232f, + d = 0.141377761960f; + #endif + F t = rsqrt(l); + auto lo = l * 12.92f; + auto hi = mad(t, mad(t, -0.0024542345f, 0.013832027f), c) + * rcp(d + t); + return apply_sign(if_then_else(l < 0.00465985f, lo, hi), sign); + }; + r = fn(r); + g = fn(g); + b = fn(b); +} + +STAGE(load_a8, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + r = g = b = 0.0f; + a = from_byte(load(ptr, tail)); +} +STAGE(load_a8_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + dr = dg = db = 0.0f; + da = from_byte(load(ptr, tail)); +} +STAGE(gather_a8, const SkRasterPipeline_GatherCtx* ctx) { + const uint8_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + r = g = b = 0.0f; + a = from_byte(gather(ptr, ix)); +} +STAGE(store_a8, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U8 packed = pack(pack(to_unorm(a, 255))); + store(ptr, packed, tail); +} + +STAGE(load_565, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + from_565(load(ptr, tail), &r,&g,&b); + a = 1.0f; +} +STAGE(load_565_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + from_565(load(ptr, tail), &dr,&dg,&db); + da = 1.0f; +} +STAGE(gather_565, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + from_565(gather(ptr, ix), &r,&g,&b); + a = 1.0f; +} +STAGE(store_565, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U16 px = pack( to_unorm(r, 31) << 11 + | to_unorm(g, 63) << 5 + | to_unorm(b, 31) ); + store(ptr, px, tail); +} + +STAGE(load_4444, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + from_4444(load(ptr, tail), &r,&g,&b,&a); +} +STAGE(load_4444_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + from_4444(load(ptr, tail), &dr,&dg,&db,&da); +} +STAGE(gather_4444, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + from_4444(gather(ptr, ix), &r,&g,&b,&a); +} +STAGE(store_4444, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + U16 px = pack( to_unorm(r, 15) << 12 + | to_unorm(g, 15) << 8 + | to_unorm(b, 15) << 4 + | to_unorm(a, 15) ); + store(ptr, px, tail); +} + +STAGE(load_8888, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + from_8888(load(ptr, tail), &r,&g,&b,&a); +} +STAGE(load_8888_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + from_8888(load(ptr, tail), &dr,&dg,&db,&da); +} +STAGE(gather_8888, const SkRasterPipeline_GatherCtx* ctx) { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + from_8888(gather(ptr, ix), &r,&g,&b,&a); +} +STAGE(store_8888, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U32 px = to_unorm(r, 255) + | to_unorm(g, 255) << 8 + | to_unorm(b, 255) << 16 + | to_unorm(a, 255) << 24; + store(ptr, px, tail); +} + +STAGE(load_rg88, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + from_88(load(ptr, tail), &r, &g); + b = 0; + a = 1; +} +STAGE(load_rg88_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + from_88(load(ptr, tail), &dr, &dg); + db = 0; + da = 1; +} +STAGE(gather_rg88, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r, g); + from_88(gather(ptr, ix), &r, &g); + b = 0; + a = 1; +} +STAGE(store_rg88, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + U16 px = pack( to_unorm(r, 255) | to_unorm(g, 255) << 8 ); + store(ptr, px, tail); +} + +STAGE(load_a16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + r = g = b = 0; + a = from_short(load(ptr, tail)); +} +STAGE(load_a16_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + dr = dg = db = 0.0f; + da = from_short(load(ptr, tail)); +} +STAGE(gather_a16, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r, g); + r = g = b = 0.0f; + a = from_short(gather(ptr, ix)); +} +STAGE(store_a16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U16 px = pack(to_unorm(a, 65535)); + store(ptr, px, tail); +} + +STAGE(load_rg1616, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + b = 0; a = 1; + from_1616(load(ptr, tail), &r,&g); +} +STAGE(load_rg1616_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + from_1616(load(ptr, tail), &dr, &dg); + db = 0; + da = 1; +} +STAGE(gather_rg1616, const SkRasterPipeline_GatherCtx* ctx) { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r, g); + from_1616(gather(ptr, ix), &r, &g); + b = 0; + a = 1; +} +STAGE(store_rg1616, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U32 px = to_unorm(r, 65535) + | to_unorm(g, 65535) << 16; + store(ptr, px, tail); +} + +STAGE(load_16161616, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + from_16161616(load(ptr, tail), &r,&g, &b, &a); +} +STAGE(load_16161616_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + from_16161616(load(ptr, tail), &dr, &dg, &db, &da); +} +STAGE(gather_16161616, const SkRasterPipeline_GatherCtx* ctx) { + const uint64_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r, g); + from_16161616(gather(ptr, ix), &r, &g, &b, &a); +} +STAGE(store_16161616, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 4*dx,4*dy); + + U16 R = pack(to_unorm(r, 65535)), + G = pack(to_unorm(g, 65535)), + B = pack(to_unorm(b, 65535)), + A = pack(to_unorm(a, 65535)); + + store4(ptr,tail, R,G,B,A); +} + + +STAGE(load_1010102, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + from_1010102(load(ptr, tail), &r,&g,&b,&a); +} +STAGE(load_1010102_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + from_1010102(load(ptr, tail), &dr,&dg,&db,&da); +} +STAGE(gather_1010102, const SkRasterPipeline_GatherCtx* ctx) { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + from_1010102(gather(ptr, ix), &r,&g,&b,&a); +} +STAGE(store_1010102, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U32 px = to_unorm(r, 1023) + | to_unorm(g, 1023) << 10 + | to_unorm(b, 1023) << 20 + | to_unorm(a, 3) << 30; + store(ptr, px, tail); +} + +STAGE(load_f16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U16 R,G,B,A; + load4((const uint16_t*)ptr,tail, &R,&G,&B,&A); + r = from_half(R); + g = from_half(G); + b = from_half(B); + a = from_half(A); +} +STAGE(load_f16_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U16 R,G,B,A; + load4((const uint16_t*)ptr,tail, &R,&G,&B,&A); + dr = from_half(R); + dg = from_half(G); + db = from_half(B); + da = from_half(A); +} +STAGE(gather_f16, const SkRasterPipeline_GatherCtx* ctx) { + const uint64_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + auto px = gather(ptr, ix); + + U16 R,G,B,A; + load4((const uint16_t*)&px,0, &R,&G,&B,&A); + r = from_half(R); + g = from_half(G); + b = from_half(B); + a = from_half(A); +} +STAGE(store_f16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + store4((uint16_t*)ptr,tail, to_half(r) + , to_half(g) + , to_half(b) + , to_half(a)); +} + +STAGE(store_u16_be, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 4*dx,dy); + + U16 R = bswap(pack(to_unorm(r, 65535))), + G = bswap(pack(to_unorm(g, 65535))), + B = bswap(pack(to_unorm(b, 65535))), + A = bswap(pack(to_unorm(a, 65535))); + + store4(ptr,tail, R,G,B,A); +} + +STAGE(load_af16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + U16 A = load((const uint16_t*)ptr, tail); + r = 0; + g = 0; + b = 0; + a = from_half(A); +} +STAGE(load_af16_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + + U16 A = load((const uint16_t*)ptr, tail); + dr = dg = db = 0.0f; + da = from_half(A); +} +STAGE(gather_af16, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r, g); + r = g = b = 0.0f; + a = from_half(gather(ptr, ix)); +} +STAGE(store_af16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + store(ptr, to_half(a), tail); +} + +STAGE(load_rgf16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + + U16 R,G; + load2((const uint16_t*)ptr, tail, &R, &G); + r = from_half(R); + g = from_half(G); + b = 0; + a = 1; +} +STAGE(load_rgf16_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + + U16 R,G; + load2((const uint16_t*)ptr, tail, &R, &G); + dr = from_half(R); + dg = from_half(G); + db = 0; + da = 1; +} +STAGE(gather_rgf16, const SkRasterPipeline_GatherCtx* ctx) { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r, g); + auto px = gather(ptr, ix); + + U16 R,G; + load2((const uint16_t*)&px, 0, &R, &G); + r = from_half(R); + g = from_half(G); + b = 0; + a = 1; +} +STAGE(store_rgf16, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx, dy); + store2((uint16_t*)ptr, tail, to_half(r) + , to_half(g)); +} + +STAGE(load_f32, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 4*dx,4*dy); + load4(ptr,tail, &r,&g,&b,&a); +} +STAGE(load_f32_dst, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 4*dx,4*dy); + load4(ptr,tail, &dr,&dg,&db,&da); +} +STAGE(gather_f32, const SkRasterPipeline_GatherCtx* ctx) { + const float* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, r,g); + r = gather(ptr, (ix<<2) + 0); + g = gather(ptr, (ix<<2) + 1); + b = gather(ptr, (ix<<2) + 2); + a = gather(ptr, (ix<<2) + 3); +} +STAGE(store_f32, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 4*dx,4*dy); + store4(ptr,tail, r,g,b,a); +} + +STAGE(load_rgf32, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 2*dx,2*dy); + load2(ptr, tail, &r, &g); + b = 0; + a = 1; +} +STAGE(store_rgf32, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, 2*dx,2*dy); + store2(ptr, tail, r, g); +} + +SI F exclusive_repeat(F v, const SkRasterPipeline_TileCtx* ctx) { + return v - floor_(v*ctx->invScale)*ctx->scale; +} +SI F exclusive_mirror(F v, const SkRasterPipeline_TileCtx* ctx) { + auto limit = ctx->scale; + auto invLimit = ctx->invScale; + return abs_( (v-limit) - (limit+limit)*floor_((v-limit)*(invLimit*0.5f)) - limit ); +} +// Tile x or y to [0,limit) == [0,limit - 1 ulp] (think, sampling from images). +// The gather stages will hard clamp the output of these stages to [0,limit)... +// we just need to do the basic repeat or mirroring. +STAGE(repeat_x, const SkRasterPipeline_TileCtx* ctx) { r = exclusive_repeat(r, ctx); } +STAGE(repeat_y, const SkRasterPipeline_TileCtx* ctx) { g = exclusive_repeat(g, ctx); } +STAGE(mirror_x, const SkRasterPipeline_TileCtx* ctx) { r = exclusive_mirror(r, ctx); } +STAGE(mirror_y, const SkRasterPipeline_TileCtx* ctx) { g = exclusive_mirror(g, ctx); } + +// Clamp x to [0,1], both sides inclusive (think, gradients). +// Even repeat and mirror funnel through a clamp to handle bad inputs like +Inf, NaN. +SI F clamp_01(F v) { return min(max(0, v), 1); } + +STAGE( clamp_x_1, Ctx::None) { r = clamp_01(r); } +STAGE(repeat_x_1, Ctx::None) { r = clamp_01(r - floor_(r)); } +STAGE(mirror_x_1, Ctx::None) { r = clamp_01(abs_( (r-1.0f) - two(floor_((r-1.0f)*0.5f)) - 1.0f )); } + +// Decal stores a 32bit mask after checking the coordinate (x and/or y) against its domain: +// mask == 0x00000000 if the coordinate(s) are out of bounds +// mask == 0xFFFFFFFF if the coordinate(s) are in bounds +// After the gather stage, the r,g,b,a values are AND'd with this mask, setting them to 0 +// if either of the coordinates were out of bounds. + +STAGE(decal_x, SkRasterPipeline_DecalTileCtx* ctx) { + auto w = ctx->limit_x; + sk_unaligned_store(ctx->mask, cond_to_mask((0 <= r) & (r < w))); +} +STAGE(decal_y, SkRasterPipeline_DecalTileCtx* ctx) { + auto h = ctx->limit_y; + sk_unaligned_store(ctx->mask, cond_to_mask((0 <= g) & (g < h))); +} +STAGE(decal_x_and_y, SkRasterPipeline_DecalTileCtx* ctx) { + auto w = ctx->limit_x; + auto h = ctx->limit_y; + sk_unaligned_store(ctx->mask, + cond_to_mask((0 <= r) & (r < w) & (0 <= g) & (g < h))); +} +STAGE(check_decal_mask, SkRasterPipeline_DecalTileCtx* ctx) { + auto mask = sk_unaligned_load(ctx->mask); + r = bit_cast( bit_cast(r) & mask ); + g = bit_cast( bit_cast(g) & mask ); + b = bit_cast( bit_cast(b) & mask ); + a = bit_cast( bit_cast(a) & mask ); +} + +STAGE(alpha_to_gray, Ctx::None) { + r = g = b = a; + a = 1; +} +STAGE(alpha_to_gray_dst, Ctx::None) { + dr = dg = db = da; + da = 1; +} +STAGE(bt709_luminance_or_luma_to_alpha, Ctx::None) { + a = r*0.2126f + g*0.7152f + b*0.0722f; + r = g = b = 0; +} + +STAGE(matrix_translate, const float* m) { + r += m[0]; + g += m[1]; +} +STAGE(matrix_scale_translate, const float* m) { + r = mad(r,m[0], m[2]); + g = mad(g,m[1], m[3]); +} +STAGE(matrix_2x3, const float* m) { + auto R = mad(r,m[0], mad(g,m[2], m[4])), + G = mad(r,m[1], mad(g,m[3], m[5])); + r = R; + g = G; +} +STAGE(matrix_3x3, const float* m) { + auto R = mad(r,m[0], mad(g,m[3], b*m[6])), + G = mad(r,m[1], mad(g,m[4], b*m[7])), + B = mad(r,m[2], mad(g,m[5], b*m[8])); + r = R; + g = G; + b = B; +} +STAGE(matrix_3x4, const float* m) { + auto R = mad(r,m[0], mad(g,m[3], mad(b,m[6], m[ 9]))), + G = mad(r,m[1], mad(g,m[4], mad(b,m[7], m[10]))), + B = mad(r,m[2], mad(g,m[5], mad(b,m[8], m[11]))); + r = R; + g = G; + b = B; +} +STAGE(matrix_4x5, const float* m) { + auto R = mad(r,m[ 0], mad(g,m[ 1], mad(b,m[ 2], mad(a,m[ 3], m[ 4])))), + G = mad(r,m[ 5], mad(g,m[ 6], mad(b,m[ 7], mad(a,m[ 8], m[ 9])))), + B = mad(r,m[10], mad(g,m[11], mad(b,m[12], mad(a,m[13], m[14])))), + A = mad(r,m[15], mad(g,m[16], mad(b,m[17], mad(a,m[18], m[19])))); + r = R; + g = G; + b = B; + a = A; +} +STAGE(matrix_4x3, const float* m) { + auto X = r, + Y = g; + + r = mad(X, m[0], mad(Y, m[4], m[ 8])); + g = mad(X, m[1], mad(Y, m[5], m[ 9])); + b = mad(X, m[2], mad(Y, m[6], m[10])); + a = mad(X, m[3], mad(Y, m[7], m[11])); +} +STAGE(matrix_perspective, const float* m) { + // N.B. Unlike the other matrix_ stages, this matrix is row-major. + auto R = mad(r,m[0], mad(g,m[1], m[2])), + G = mad(r,m[3], mad(g,m[4], m[5])), + Z = mad(r,m[6], mad(g,m[7], m[8])); + r = R * rcp(Z); + g = G * rcp(Z); +} + +SI void gradient_lookup(const SkRasterPipeline_GradientCtx* c, U32 idx, F t, + F* r, F* g, F* b, F* a) { + F fr, br, fg, bg, fb, bb, fa, ba; +#if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + if (c->stopCount <=8) { + fr = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[0]), idx); + br = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[0]), idx); + fg = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[1]), idx); + bg = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[1]), idx); + fb = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[2]), idx); + bb = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[2]), idx); + fa = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[3]), idx); + ba = _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[3]), idx); + } else +#elif defined(JUMPER_IS_AVX) + if (c->stopCount <= 4) { + auto permute = [](const float* ptr, U32 idx) { + __m128 v = _mm_loadu_ps(ptr); + __m256 vv = _mm256_insertf128_ps(_mm256_castps128_ps256(v), v, 1); + return _mm256_permutevar_ps(vv, idx); + }; + fr = permute(c->fs[0], idx); + br = permute(c->bs[0], idx); + fg = permute(c->fs[1], idx); + bg = permute(c->bs[1], idx); + fb = permute(c->fs[2], idx); + bb = permute(c->bs[2], idx); + fa = permute(c->fs[3], idx); + ba = permute(c->bs[3], idx); + } else +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX + if (c->stopCount <= 4) { + fr = _mm_permutevar_ps(_mm_loadu_ps(c->fs[0]), idx); + br = _mm_permutevar_ps(_mm_loadu_ps(c->bs[0]), idx); + fg = _mm_permutevar_ps(_mm_loadu_ps(c->fs[1]), idx); + bg = _mm_permutevar_ps(_mm_loadu_ps(c->bs[1]), idx); + fb = _mm_permutevar_ps(_mm_loadu_ps(c->fs[2]), idx); + bb = _mm_permutevar_ps(_mm_loadu_ps(c->bs[2]), idx); + fa = _mm_permutevar_ps(_mm_loadu_ps(c->fs[3]), idx); + ba = _mm_permutevar_ps(_mm_loadu_ps(c->bs[3]), idx); + } else +#endif + { + fr = gather(c->fs[0], idx); + br = gather(c->bs[0], idx); + fg = gather(c->fs[1], idx); + bg = gather(c->bs[1], idx); + fb = gather(c->fs[2], idx); + bb = gather(c->bs[2], idx); + fa = gather(c->fs[3], idx); + ba = gather(c->bs[3], idx); + } + + *r = mad(t, fr, br); + *g = mad(t, fg, bg); + *b = mad(t, fb, bb); + *a = mad(t, fa, ba); +} + +STAGE(evenly_spaced_gradient, const SkRasterPipeline_GradientCtx* c) { + auto t = r; + auto idx = trunc_(t * (c->stopCount-1)); + gradient_lookup(c, idx, t, &r, &g, &b, &a); +} + +STAGE(gradient, const SkRasterPipeline_GradientCtx* c) { + auto t = r; + U32 idx = 0; + + // N.B. The loop starts at 1 because idx 0 is the color to use before the first stop. + for (size_t i = 1; i < c->stopCount; i++) { +#ifdef JUMPER_IS_SCALAR + if (t >= c->ts[i]) { + idx++; + } else { + break; + } +#else + idx += bit_cast(t >= c->ts[i]); +#endif + } + +#ifndef JUMPER_IS_SCALAR + idx = U32(0) - idx; +#endif + + gradient_lookup(c, idx, t, &r, &g, &b, &a); +} + +STAGE(evenly_spaced_2_stop_gradient, const void* ctx) { + // TODO: Rename Ctx SkRasterPipeline_EvenlySpaced2StopGradientCtx. + struct Ctx { float f[4], b[4]; }; + auto c = (const Ctx*)ctx; + + auto t = r; + r = mad(t, c->f[0], c->b[0]); + g = mad(t, c->f[1], c->b[1]); + b = mad(t, c->f[2], c->b[2]); + a = mad(t, c->f[3], c->b[3]); +} + +#ifdef JUMPER_IS_SCALAR +SI bool isnan_(float v) { return sk_float_isnan(v); } +#else +template SI auto isnan_(T v) -> decltype(v != v) { return v != v; } +#endif + +STAGE(xy_to_unit_angle, Ctx::None) { + F X = r, + Y = g; + F xabs = abs_(X), + yabs = abs_(Y); + + F slope = min(xabs, yabs)/max(xabs, yabs); + F s = slope * slope; + + // Use a 7th degree polynomial to approximate atan. + // This was generated using sollya.gforge.inria.fr. + // A float optimized polynomial was generated using the following command. + // P1 = fpminimax((1/(2*Pi))*atan(x),[|1,3,5,7|],[|24...|],[2^(-40),1],relative); + F phi = slope + * (0.15912117063999176025390625f + s + * (-5.185396969318389892578125e-2f + s + * (2.476101927459239959716796875e-2f + s + * (-7.0547382347285747528076171875e-3f)))); + + phi = if_then_else(xabs < yabs, 1.0f/4.0f - phi, phi); + phi = if_then_else(X < 0.0f , 1.0f/2.0f - phi, phi); + phi = if_then_else(Y < 0.0f , 1.0f - phi , phi); + phi = if_then_else(isnan_(phi), 0 , phi); // Check for NaN. + r = phi; +} + +STAGE(xy_to_radius, Ctx::None) { + F X2 = r * r, + Y2 = g * g; + r = sqrt_(X2 + Y2); +} + +// Please see https://skia.org/dev/design/conical for how our 2pt conical shader works. + +STAGE(negate_x, Ctx::None) { r = -r; } + +STAGE(xy_to_2pt_conical_strip, const SkRasterPipeline_2PtConicalCtx* ctx) { + F x = r, y = g, &t = r; + t = x + sqrt_(ctx->fP0 - y*y); // ctx->fP0 = r0 * r0 +} + +STAGE(xy_to_2pt_conical_focal_on_circle, Ctx::None) { + F x = r, y = g, &t = r; + t = x + y*y / x; // (x^2 + y^2) / x +} + +STAGE(xy_to_2pt_conical_well_behaved, const SkRasterPipeline_2PtConicalCtx* ctx) { + F x = r, y = g, &t = r; + t = sqrt_(x*x + y*y) - x * ctx->fP0; // ctx->fP0 = 1/r1 +} + +STAGE(xy_to_2pt_conical_greater, const SkRasterPipeline_2PtConicalCtx* ctx) { + F x = r, y = g, &t = r; + t = sqrt_(x*x - y*y) - x * ctx->fP0; // ctx->fP0 = 1/r1 +} + +STAGE(xy_to_2pt_conical_smaller, const SkRasterPipeline_2PtConicalCtx* ctx) { + F x = r, y = g, &t = r; + t = -sqrt_(x*x - y*y) - x * ctx->fP0; // ctx->fP0 = 1/r1 +} + +STAGE(alter_2pt_conical_compensate_focal, const SkRasterPipeline_2PtConicalCtx* ctx) { + F& t = r; + t = t + ctx->fP1; // ctx->fP1 = f +} + +STAGE(alter_2pt_conical_unswap, Ctx::None) { + F& t = r; + t = 1 - t; +} + +STAGE(mask_2pt_conical_nan, SkRasterPipeline_2PtConicalCtx* c) { + F& t = r; + auto is_degenerate = isnan_(t); // NaN + t = if_then_else(is_degenerate, F(0), t); + sk_unaligned_store(&c->fMask, cond_to_mask(!is_degenerate)); +} + +STAGE(mask_2pt_conical_degenerates, SkRasterPipeline_2PtConicalCtx* c) { + F& t = r; + auto is_degenerate = (t <= 0) | isnan_(t); + t = if_then_else(is_degenerate, F(0), t); + sk_unaligned_store(&c->fMask, cond_to_mask(!is_degenerate)); +} + +STAGE(apply_vector_mask, const uint32_t* ctx) { + const U32 mask = sk_unaligned_load(ctx); + r = bit_cast(bit_cast(r) & mask); + g = bit_cast(bit_cast(g) & mask); + b = bit_cast(bit_cast(b) & mask); + a = bit_cast(bit_cast(a) & mask); +} + +STAGE(save_xy, SkRasterPipeline_SamplerCtx* c) { + // Whether bilinear or bicubic, all sample points are at the same fractional offset (fx,fy). + // They're either the 4 corners of a logical 1x1 pixel or the 16 corners of a 3x3 grid + // surrounding (x,y) at (0.5,0.5) off-center. + F fx = fract(r + 0.5f), + fy = fract(g + 0.5f); + + // Samplers will need to load x and fx, or y and fy. + sk_unaligned_store(c->x, r); + sk_unaligned_store(c->y, g); + sk_unaligned_store(c->fx, fx); + sk_unaligned_store(c->fy, fy); +} + +STAGE(accumulate, const SkRasterPipeline_SamplerCtx* c) { + // Bilinear and bicubic filters are both separable, so we produce independent contributions + // from x and y, multiplying them together here to get each pixel's total scale factor. + auto scale = sk_unaligned_load(c->scalex) + * sk_unaligned_load(c->scaley); + dr = mad(scale, r, dr); + dg = mad(scale, g, dg); + db = mad(scale, b, db); + da = mad(scale, a, da); +} + +// In bilinear interpolation, the 4 pixels at +/- 0.5 offsets from the sample pixel center +// are combined in direct proportion to their area overlapping that logical query pixel. +// At positive offsets, the x-axis contribution to that rectangle is fx, or (1-fx) at negative x. +// The y-axis is symmetric. + +template +SI void bilinear_x(SkRasterPipeline_SamplerCtx* ctx, F* x) { + *x = sk_unaligned_load(ctx->x) + (kScale * 0.5f); + F fx = sk_unaligned_load(ctx->fx); + + F scalex; + if (kScale == -1) { scalex = 1.0f - fx; } + if (kScale == +1) { scalex = fx; } + sk_unaligned_store(ctx->scalex, scalex); +} +template +SI void bilinear_y(SkRasterPipeline_SamplerCtx* ctx, F* y) { + *y = sk_unaligned_load(ctx->y) + (kScale * 0.5f); + F fy = sk_unaligned_load(ctx->fy); + + F scaley; + if (kScale == -1) { scaley = 1.0f - fy; } + if (kScale == +1) { scaley = fy; } + sk_unaligned_store(ctx->scaley, scaley); +} + +STAGE(bilinear_nx, SkRasterPipeline_SamplerCtx* ctx) { bilinear_x<-1>(ctx, &r); } +STAGE(bilinear_px, SkRasterPipeline_SamplerCtx* ctx) { bilinear_x<+1>(ctx, &r); } +STAGE(bilinear_ny, SkRasterPipeline_SamplerCtx* ctx) { bilinear_y<-1>(ctx, &g); } +STAGE(bilinear_py, SkRasterPipeline_SamplerCtx* ctx) { bilinear_y<+1>(ctx, &g); } + + +// In bicubic interpolation, the 16 pixels and +/- 0.5 and +/- 1.5 offsets from the sample +// pixel center are combined with a non-uniform cubic filter, with higher values near the center. +// +// We break this function into two parts, one for near 0.5 offsets and one for far 1.5 offsets. +// See GrCubicEffect for details of this particular filter. + +SI F bicubic_near(F t) { + // 1/18 + 9/18t + 27/18t^2 - 21/18t^3 == t ( t ( -21/18t + 27/18) + 9/18) + 1/18 + return mad(t, mad(t, mad((-21/18.0f), t, (27/18.0f)), (9/18.0f)), (1/18.0f)); +} +SI F bicubic_far(F t) { + // 0/18 + 0/18*t - 6/18t^2 + 7/18t^3 == t^2 (7/18t - 6/18) + return (t*t)*mad((7/18.0f), t, (-6/18.0f)); +} + +template +SI void bicubic_x(SkRasterPipeline_SamplerCtx* ctx, F* x) { + *x = sk_unaligned_load(ctx->x) + (kScale * 0.5f); + F fx = sk_unaligned_load(ctx->fx); + + F scalex; + if (kScale == -3) { scalex = bicubic_far (1.0f - fx); } + if (kScale == -1) { scalex = bicubic_near(1.0f - fx); } + if (kScale == +1) { scalex = bicubic_near( fx); } + if (kScale == +3) { scalex = bicubic_far ( fx); } + sk_unaligned_store(ctx->scalex, scalex); +} +template +SI void bicubic_y(SkRasterPipeline_SamplerCtx* ctx, F* y) { + *y = sk_unaligned_load(ctx->y) + (kScale * 0.5f); + F fy = sk_unaligned_load(ctx->fy); + + F scaley; + if (kScale == -3) { scaley = bicubic_far (1.0f - fy); } + if (kScale == -1) { scaley = bicubic_near(1.0f - fy); } + if (kScale == +1) { scaley = bicubic_near( fy); } + if (kScale == +3) { scaley = bicubic_far ( fy); } + sk_unaligned_store(ctx->scaley, scaley); +} + +STAGE(bicubic_n3x, SkRasterPipeline_SamplerCtx* ctx) { bicubic_x<-3>(ctx, &r); } +STAGE(bicubic_n1x, SkRasterPipeline_SamplerCtx* ctx) { bicubic_x<-1>(ctx, &r); } +STAGE(bicubic_p1x, SkRasterPipeline_SamplerCtx* ctx) { bicubic_x<+1>(ctx, &r); } +STAGE(bicubic_p3x, SkRasterPipeline_SamplerCtx* ctx) { bicubic_x<+3>(ctx, &r); } + +STAGE(bicubic_n3y, SkRasterPipeline_SamplerCtx* ctx) { bicubic_y<-3>(ctx, &g); } +STAGE(bicubic_n1y, SkRasterPipeline_SamplerCtx* ctx) { bicubic_y<-1>(ctx, &g); } +STAGE(bicubic_p1y, SkRasterPipeline_SamplerCtx* ctx) { bicubic_y<+1>(ctx, &g); } +STAGE(bicubic_p3y, SkRasterPipeline_SamplerCtx* ctx) { bicubic_y<+3>(ctx, &g); } + +STAGE(callback, SkRasterPipeline_CallbackCtx* c) { + store4(c->rgba,0, r,g,b,a); + c->fn(c, tail ? tail : N); + load4(c->read_from,0, &r,&g,&b,&a); +} + +// shader: void main(float x, float y, inout half4 color) +// colorfilter: void main(inout half4 color) +STAGE(interpreter, SkRasterPipeline_InterpreterCtx* c) { + // If N is less than the interpreter's VecWidth, then we are doing more work than necessary in + // the interpreter. This is a known issue, and will be addressed at some point. + float xx[N], yy[N], + rr[N], gg[N], bb[N], aa[N]; + + float* args[] = { xx, yy, rr, gg, bb, aa }; + float** in_args = args; + int in_count = 6; + + if (c->shaderConvention) { + // our caller must have called seed_shader to set these + sk_unaligned_store(xx, r); + sk_unaligned_store(yy, g); + sk_unaligned_store(rr, F(c->paintColor.fR)); + sk_unaligned_store(gg, F(c->paintColor.fG)); + sk_unaligned_store(bb, F(c->paintColor.fB)); + sk_unaligned_store(aa, F(c->paintColor.fA)); + } else { + in_args += 2; // skip x,y + in_count = 4; + sk_unaligned_store(rr, r); + sk_unaligned_store(gg, g); + sk_unaligned_store(bb, b); + sk_unaligned_store(aa, a); + } + + SkAssertResult(c->byteCode->runStriped(c->fn, tail ? tail : N, in_args, in_count, + nullptr, 0, (const float*)c->inputs, c->ninputs)); + + r = sk_unaligned_load(rr); + g = sk_unaligned_load(gg); + b = sk_unaligned_load(bb); + a = sk_unaligned_load(aa); +} + +STAGE(gauss_a_to_rgba, Ctx::None) { + // x = 1 - x; + // exp(-x * x * 4) - 0.018f; + // ... now approximate with quartic + // + const float c4 = -2.26661229133605957031f; + const float c3 = 2.89795351028442382812f; + const float c2 = 0.21345567703247070312f; + const float c1 = 0.15489584207534790039f; + const float c0 = 0.00030726194381713867f; + a = mad(a, mad(a, mad(a, mad(a, c4, c3), c2), c1), c0); + r = a; + g = a; + b = a; +} + +SI F tile(F v, SkTileMode mode, float limit, float invLimit) { + // The ix_and_ptr() calls in sample() will clamp tile()'s output, so no need to clamp here. + switch (mode) { + case SkTileMode::kDecal: // TODO, for now fallthrough to clamp + case SkTileMode::kClamp: return v; + case SkTileMode::kRepeat: return v - floor_(v*invLimit)*limit; + case SkTileMode::kMirror: + return abs_( (v-limit) - (limit+limit)*floor_((v-limit)*(invLimit*0.5f)) - limit ); + } + SkUNREACHABLE; +} + +SI void sample(const SkRasterPipeline_SamplerCtx2* ctx, F x, F y, + F* r, F* g, F* b, F* a) { + x = tile(x, ctx->tileX, ctx->width , ctx->invWidth ); + y = tile(y, ctx->tileY, ctx->height, ctx->invHeight); + + switch (ctx->ct) { + default: *r = *g = *b = *a = 0; // TODO + break; + + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + from_8888(gather(ptr, ix), r,g,b,a); + if (ctx->ct == kBGRA_8888_SkColorType) { + std::swap(*r,*b); + } + } break; + } +} + +template +SI void sampler(const SkRasterPipeline_SamplerCtx2* ctx, + F cx, F cy, const F (&wx)[D], const F (&wy)[D], + F* r, F* g, F* b, F* a) { + + float start = -0.5f*(D-1); + + *r = *g = *b = *a = 0; + F y = cy + start; + for (int j = 0; j < D; j++, y += 1.0f) { + F x = cx + start; + for (int i = 0; i < D; i++, x += 1.0f) { + F R,G,B,A; + sample(ctx, x,y, &R,&G,&B,&A); + + F w = wx[i] * wy[j]; + *r = mad(w,R,*r); + *g = mad(w,G,*g); + *b = mad(w,B,*b); + *a = mad(w,A,*a); + } + } +} + +STAGE(bilinear, const SkRasterPipeline_SamplerCtx2* ctx) { + F x = r, fx = fract(x + 0.5f), + y = g, fy = fract(y + 0.5f); + const F wx[] = {1.0f - fx, fx}; + const F wy[] = {1.0f - fy, fy}; + + sampler(ctx, x,y, wx,wy, &r,&g,&b,&a); +} +STAGE(bicubic, SkRasterPipeline_SamplerCtx2* ctx) { + F x = r, fx = fract(x + 0.5f), + y = g, fy = fract(y + 0.5f); + const F wx[] = { bicubic_far(1-fx), bicubic_near(1-fx), bicubic_near(fx), bicubic_far(fx) }; + const F wy[] = { bicubic_far(1-fy), bicubic_near(1-fy), bicubic_near(fy), bicubic_far(fy) }; + + sampler(ctx, x,y, wx,wy, &r,&g,&b,&a); +} + +// A specialized fused image shader for clamp-x, clamp-y, non-sRGB sampling. +STAGE(bilerp_clamp_8888, const SkRasterPipeline_GatherCtx* ctx) { + // (cx,cy) are the center of our sample. + F cx = r, + cy = g; + + // All sample points are at the same fractional offset (fx,fy). + // They're the 4 corners of a logical 1x1 pixel surrounding (x,y) at (0.5,0.5) offsets. + F fx = fract(cx + 0.5f), + fy = fract(cy + 0.5f); + + // We'll accumulate the color of all four samples into {r,g,b,a} directly. + r = g = b = a = 0; + + for (float dy = -0.5f; dy <= +0.5f; dy += 1.0f) + for (float dx = -0.5f; dx <= +0.5f; dx += 1.0f) { + // (x,y) are the coordinates of this sample point. + F x = cx + dx, + y = cy + dy; + + // ix_and_ptr() will clamp to the image's bounds for us. + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + + F sr,sg,sb,sa; + from_8888(gather(ptr, ix), &sr,&sg,&sb,&sa); + + // In bilinear interpolation, the 4 pixels at +/- 0.5 offsets from the sample pixel center + // are combined in direct proportion to their area overlapping that logical query pixel. + // At positive offsets, the x-axis contribution to that rectangle is fx, + // or (1-fx) at negative x. Same deal for y. + F sx = (dx > 0) ? fx : 1.0f - fx, + sy = (dy > 0) ? fy : 1.0f - fy, + area = sx * sy; + + r += sr * area; + g += sg * area; + b += sb * area; + a += sa * area; + } +} + +// A specialized fused image shader for clamp-x, clamp-y, non-sRGB sampling. +STAGE(bicubic_clamp_8888, const SkRasterPipeline_GatherCtx* ctx) { + // (cx,cy) are the center of our sample. + F cx = r, + cy = g; + + // All sample points are at the same fractional offset (fx,fy). + // They're the 4 corners of a logical 1x1 pixel surrounding (x,y) at (0.5,0.5) offsets. + F fx = fract(cx + 0.5f), + fy = fract(cy + 0.5f); + + // We'll accumulate the color of all four samples into {r,g,b,a} directly. + r = g = b = a = 0; + + const F scaley[4] = { + bicubic_far (1.0f - fy), bicubic_near(1.0f - fy), + bicubic_near( fy), bicubic_far ( fy), + }; + const F scalex[4] = { + bicubic_far (1.0f - fx), bicubic_near(1.0f - fx), + bicubic_near( fx), bicubic_far ( fx), + }; + + F sample_y = cy - 1.5f; + for (int yy = 0; yy <= 3; ++yy) { + F sample_x = cx - 1.5f; + for (int xx = 0; xx <= 3; ++xx) { + F scale = scalex[xx] * scaley[yy]; + + // ix_and_ptr() will clamp to the image's bounds for us. + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, sample_x, sample_y); + + F sr,sg,sb,sa; + from_8888(gather(ptr, ix), &sr,&sg,&sb,&sa); + + r = mad(scale, sr, r); + g = mad(scale, sg, g); + b = mad(scale, sb, b); + a = mad(scale, sa, a); + + sample_x += 1.0f; + } + sample_y += 1.0f; + } +} + +// ~~~~~~ GrSwizzle stage ~~~~~~ // + +STAGE(swizzle, void* ctx) { + auto ir = r, ig = g, ib = b, ia = a; + F* o[] = {&r, &g, &b, &a}; + char swiz[4]; + memcpy(swiz, &ctx, sizeof(swiz)); + + for (int i = 0; i < 4; ++i) { + switch (swiz[i]) { + case 'r': *o[i] = ir; break; + case 'g': *o[i] = ig; break; + case 'b': *o[i] = ib; break; + case 'a': *o[i] = ia; break; + case '0': *o[i] = F(0); break; + case '1': *o[i] = F(1); break; + default: break; + } + } +} + +namespace lowp { +#if defined(JUMPER_IS_SCALAR) || defined(SK_DISABLE_LOWP_RASTER_PIPELINE) + // If we're not compiled by Clang, or otherwise switched into scalar mode (old Clang, manually), + // we don't generate lowp stages. All these nullptrs will tell SkJumper.cpp to always use the + // highp float pipeline. + #define M(st) static void (*st)(void) = nullptr; + SK_RASTER_PIPELINE_STAGES(M) + #undef M + static void (*just_return)(void) = nullptr; + + static void start_pipeline(size_t,size_t,size_t,size_t, void**) {} + +#else // We are compiling vector code with Clang... let's make some lowp stages! + +#if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + template using V = SK_VECTORTYPE(T, 16); +#else + template using V = SK_VECTORTYPE(T, 8); +#endif + using U8 = V; + using U16 = V; + using I16 = V< int16_t>; + using I32 = V< int32_t>; + using U32 = V; + using F = V; + +static const size_t N = sizeof(U16) / sizeof(uint16_t); + +// Once again, some platforms benefit from a restricted Stage calling convention, +// but others can pass tons and tons of registers and we're happy to exploit that. +// It's exactly the same decision and implementation strategy as the F stages above. +#if JUMPER_NARROW_STAGES + struct Params { + size_t dx, dy, tail; + U16 dr,dg,db,da; + }; + using Stage = void(ABI*)(Params*, void** program, U16 r, U16 g, U16 b, U16 a); +#else + // We pass program as the second argument so that load_and_inc() will find it in %rsi on x86-64. + using Stage = void (ABI*)(size_t tail, void** program, size_t dx, size_t dy, + U16 r, U16 g, U16 b, U16 a, + U16 dr, U16 dg, U16 db, U16 da); +#endif + +static void start_pipeline(const size_t x0, const size_t y0, + const size_t xlimit, const size_t ylimit, void** program) { + auto start = (Stage)load_and_inc(program); + for (size_t dy = y0; dy < ylimit; dy++) { + #if JUMPER_NARROW_STAGES + Params params = { x0,dy,0, 0,0,0,0 }; + for (; params.dx + N <= xlimit; params.dx += N) { + start(¶ms,program, 0,0,0,0); + } + if (size_t tail = xlimit - params.dx) { + params.tail = tail; + start(¶ms,program, 0,0,0,0); + } + #else + size_t dx = x0; + for (; dx + N <= xlimit; dx += N) { + start( 0,program,dx,dy, 0,0,0,0, 0,0,0,0); + } + if (size_t tail = xlimit - dx) { + start(tail,program,dx,dy, 0,0,0,0, 0,0,0,0); + } + #endif + } +} + +#if JUMPER_NARROW_STAGES + static void ABI just_return(Params*, void**, U16,U16,U16,U16) {} +#else + static void ABI just_return(size_t,void**,size_t,size_t, U16,U16,U16,U16, U16,U16,U16,U16) {} +#endif + +// All stages use the same function call ABI to chain into each other, but there are three types: +// GG: geometry in, geometry out -- think, a matrix +// GP: geometry in, pixels out. -- think, a memory gather +// PP: pixels in, pixels out. -- think, a blend mode +// +// (Some stages ignore their inputs or produce no logical output. That's perfectly fine.) +// +// These three STAGE_ macros let you define each type of stage, +// and will have (x,y) geometry and/or (r,g,b,a, dr,dg,db,da) pixel arguments as appropriate. + +#if JUMPER_NARROW_STAGES + #define STAGE_GG(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y); \ + static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) { \ + auto x = join(r,g), \ + y = join(b,a); \ + name##_k(Ctx{program}, params->dx,params->dy,params->tail, x,y); \ + split(x, &r,&g); \ + split(y, &b,&a); \ + auto next = (Stage)load_and_inc(program); \ + next(params,program, r,g,b,a); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y) + + #define STAGE_GP(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da); \ + static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) { \ + auto x = join(r,g), \ + y = join(b,a); \ + name##_k(Ctx{program}, params->dx,params->dy,params->tail, x,y, r,g,b,a, \ + params->dr,params->dg,params->db,params->da); \ + auto next = (Stage)load_and_inc(program); \ + next(params,program, r,g,b,a); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da) + + #define STAGE_PP(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da); \ + static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) { \ + name##_k(Ctx{program}, params->dx,params->dy,params->tail, r,g,b,a, \ + params->dr,params->dg,params->db,params->da); \ + auto next = (Stage)load_and_inc(program); \ + next(params,program, r,g,b,a); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da) +#else + #define STAGE_GG(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y); \ + static void ABI name(size_t tail, void** program, size_t dx, size_t dy, \ + U16 r, U16 g, U16 b, U16 a, \ + U16 dr, U16 dg, U16 db, U16 da) { \ + auto x = join(r,g), \ + y = join(b,a); \ + name##_k(Ctx{program}, dx,dy,tail, x,y); \ + split(x, &r,&g); \ + split(y, &b,&a); \ + auto next = (Stage)load_and_inc(program); \ + next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y) + + #define STAGE_GP(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da); \ + static void ABI name(size_t tail, void** program, size_t dx, size_t dy, \ + U16 r, U16 g, U16 b, U16 a, \ + U16 dr, U16 dg, U16 db, U16 da) { \ + auto x = join(r,g), \ + y = join(b,a); \ + name##_k(Ctx{program}, dx,dy,tail, x,y, r,g,b,a, dr,dg,db,da); \ + auto next = (Stage)load_and_inc(program); \ + next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da) + + #define STAGE_PP(name, ...) \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da); \ + static void ABI name(size_t tail, void** program, size_t dx, size_t dy, \ + U16 r, U16 g, U16 b, U16 a, \ + U16 dr, U16 dg, U16 db, U16 da) { \ + name##_k(Ctx{program}, dx,dy,tail, r,g,b,a, dr,dg,db,da); \ + auto next = (Stage)load_and_inc(program); \ + next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da); \ + } \ + SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, \ + U16& r, U16& g, U16& b, U16& a, \ + U16& dr, U16& dg, U16& db, U16& da) +#endif + +// ~~~~~~ Commonly used helper functions ~~~~~~ // + +SI U16 div255(U16 v) { +#if 0 + return (v+127)/255; // The ideal rounding divide by 255. +#elif 1 && defined(JUMPER_IS_NEON) + // With NEON we can compute (v+127)/255 as (v + ((v+128)>>8) + 128)>>8 + // just as fast as we can do the approximation below, so might as well be correct! + // First we compute v + ((v+128)>>8), then one more round of (...+128)>>8 to finish up. + return vrshrq_n_u16(vrsraq_n_u16(v, v, 8), 8); +#else + return (v+255) >> 8; // A good approximation of (v+127)/255. +#endif +} + +SI U16 inv(U16 v) { return 255-v; } + +#if defined(__clang__) +SI U16 if_then_else(I16 c, U16 t, U16 e) { return (t & c) | (e & ~c); } +SI U32 if_then_else(I32 c, U32 t, U32 e) { return (t & c) | (e & ~c); } +SI U16 max(U16 x, U16 y) { return if_then_else(x < y, y, x); } +SI U16 min(U16 x, U16 y) { return if_then_else(x < y, x, y); } +#else +template SI R if_then_else(T c, V t, R e) { return bit_cast(c).thenElse(t, e); } +template SI T max(V x, T y) { return T::Max(x, y); } +template SI T min(T x, V y) { return T::Min(x, y); } +#endif + +SI U16 from_float(float f) { return f * 255.0f + 0.5f; } + +SI U16 lerp(U16 from, U16 to, U16 t) { return div255( from*inv(t) + to*t ); } + +template +SI D cast(S src) { + return SK_CONVERTVECTOR(src, D); +} + +template +SI void split(S v, D* lo, D* hi) { + static_assert(2*sizeof(D) == sizeof(S), ""); + memcpy(lo, (const char*)&v + 0*sizeof(D), sizeof(D)); + memcpy(hi, (const char*)&v + 1*sizeof(D), sizeof(D)); +} +template +SI D join(S lo, S hi) { + static_assert(sizeof(D) == 2*sizeof(S), ""); + D v; + memcpy((char*)&v + 0*sizeof(S), &lo, sizeof(S)); + memcpy((char*)&v + 1*sizeof(S), &hi, sizeof(S)); + return v; +} + +#if defined(__clang__) +SI F if_then_else(I32 c, F t, F e) { + return bit_cast( (bit_cast(t) & c) | (bit_cast(e) & ~c) ); +} +SI F max(F x, F y) { return if_then_else(x < y, y, x); } +SI F min(F x, F y) { return if_then_else(x < y, x, y); } + +SI U32 trunc_(F x) { return (U32)cast(x); } +#else +SI U32 trunc_(F x) { return SkNx_cast(SkNx_cast(x)); } +#endif + +SI F mad(F f, F m, F a) { return f*m+a; } + +SI F rcp(F x) { +#if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + __m256 lo,hi; + split(x, &lo,&hi); + return join(_mm256_rcp_ps(lo), _mm256_rcp_ps(hi)); +#elif defined(JUMPER_IS_SSE2) || defined(JUMPER_IS_SSE41) || defined(JUMPER_IS_AVX) + __m128 lo,hi; + split(x, &lo,&hi); + return join(_mm_rcp_ps(lo), _mm_rcp_ps(hi)); +#elif defined(JUMPER_IS_NEON) + auto rcp = [](float32x4_t v) { + auto est = vrecpeq_f32(v); + return vrecpsq_f32(v,est)*est; + }; + float32x4_t lo,hi; + split(x, &lo,&hi); + return join(rcp(lo), rcp(hi)); +#else + return 1.0f / x; +#endif +} +SI F sqrt_(F x) { +#if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + __m256 lo,hi; + split(x, &lo,&hi); + return join(_mm256_sqrt_ps(lo), _mm256_sqrt_ps(hi)); +#elif defined(JUMPER_IS_SSE2) || defined(JUMPER_IS_SSE41) || defined(JUMPER_IS_AVX) + __m128 lo,hi; + split(x, &lo,&hi); + return join(_mm_sqrt_ps(lo), _mm_sqrt_ps(hi)); +#elif defined(SK_CPU_ARM64) + float32x4_t lo,hi; + split(x, &lo,&hi); + return join(vsqrtq_f32(lo), vsqrtq_f32(hi)); +#elif defined(JUMPER_IS_NEON) + auto sqrt = [](float32x4_t v) { + auto est = vrsqrteq_f32(v); // Estimate and two refinement steps for est = rsqrt(v). + est *= vrsqrtsq_f32(v,est*est); + est *= vrsqrtsq_f32(v,est*est); + return v*est; // sqrt(v) == v*rsqrt(v). + }; + float32x4_t lo,hi; + split(x, &lo,&hi); + return join(sqrt(lo), sqrt(hi)); +#else + return F{ + sqrtf(x[0]), sqrtf(x[1]), sqrtf(x[2]), sqrtf(x[3]), + sqrtf(x[4]), sqrtf(x[5]), sqrtf(x[6]), sqrtf(x[7]), + }; +#endif +} + +SI F floor_(F x) { +#if defined(SK_CPU_ARM64) + float32x4_t lo,hi; + split(x, &lo,&hi); + return join(vrndmq_f32(lo), vrndmq_f32(hi)); +#elif defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + __m256 lo,hi; + split(x, &lo,&hi); + return join(_mm256_floor_ps(lo), _mm256_floor_ps(hi)); +#elif defined(JUMPER_IS_SSE41) || defined(JUMPER_IS_AVX) + __m128 lo,hi; + split(x, &lo,&hi); + return join(_mm_floor_ps(lo), _mm_floor_ps(hi)); +#else + F roundtrip = cast(cast(x)); + return roundtrip - if_then_else(roundtrip > x, F(1), F(0)); +#endif +} +SI F fract(F x) { return x - floor_(x); } +SI F abs_(F x) { return bit_cast( bit_cast(x) & 0x7fffffff ); } + +// ~~~~~~ Basic / misc. stages ~~~~~~ // + +STAGE_GG(seed_shader, Ctx::None) { + static const float iota[] = { + 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, + 8.5f, 9.5f,10.5f,11.5f,12.5f,13.5f,14.5f,15.5f, + }; + x = cast(I32(dx)) + sk_unaligned_load(iota); + y = cast(I32(dy)) + 0.5f; +} + +STAGE_GG(matrix_translate, const float* m) { + x += m[0]; + y += m[1]; +} +STAGE_GG(matrix_scale_translate, const float* m) { + x = mad(x,m[0], m[2]); + y = mad(y,m[1], m[3]); +} +STAGE_GG(matrix_2x3, const float* m) { + auto X = mad(x,m[0], mad(y,m[2], m[4])), + Y = mad(x,m[1], mad(y,m[3], m[5])); + x = X; + y = Y; +} +STAGE_GG(matrix_perspective, const float* m) { + // N.B. Unlike the other matrix_ stages, this matrix is row-major. + auto X = mad(x,m[0], mad(y,m[1], m[2])), + Y = mad(x,m[3], mad(y,m[4], m[5])), + Z = mad(x,m[6], mad(y,m[7], m[8])); + x = X * rcp(Z); + y = Y * rcp(Z); +} + +STAGE_PP(uniform_color, const SkRasterPipeline_UniformColorCtx* c) { + r = c->rgba[0]; + g = c->rgba[1]; + b = c->rgba[2]; + a = c->rgba[3]; +} +STAGE_PP(uniform_color_dst, const SkRasterPipeline_UniformColorCtx* c) { + dr = c->rgba[0]; + dg = c->rgba[1]; + db = c->rgba[2]; + da = c->rgba[3]; +} +STAGE_PP(black_color, Ctx::None) { r = g = b = 0; a = 255; } +STAGE_PP(white_color, Ctx::None) { r = g = b = 255; a = 255; } + +STAGE_PP(set_rgb, const float rgb[3]) { + r = from_float(rgb[0]); + g = from_float(rgb[1]); + b = from_float(rgb[2]); +} + +STAGE_PP(clamp_0, Ctx::None) { /*definitely a noop*/ } +STAGE_PP(clamp_1, Ctx::None) { /*_should_ be a noop*/ } + +STAGE_PP(clamp_a, Ctx::None) { + r = min(r, a); + g = min(g, a); + b = min(b, a); +} + +STAGE_PP(clamp_gamut, Ctx::None) { + // It shouldn't be possible to get out-of-gamut + // colors when working in lowp. +} + +STAGE_PP(premul, Ctx::None) { + r = div255(r * a); + g = div255(g * a); + b = div255(b * a); +} +STAGE_PP(premul_dst, Ctx::None) { + dr = div255(dr * da); + dg = div255(dg * da); + db = div255(db * da); +} + +STAGE_PP(force_opaque , Ctx::None) { a = 255; } +STAGE_PP(force_opaque_dst, Ctx::None) { da = 255; } + +STAGE_PP(swap_rb, Ctx::None) { + auto tmp = r; + r = b; + b = tmp; +} +STAGE_PP(swap_rb_dst, Ctx::None) { + auto tmp = dr; + dr = db; + db = tmp; +} + +STAGE_PP(move_src_dst, Ctx::None) { + dr = r; + dg = g; + db = b; + da = a; +} + +STAGE_PP(move_dst_src, Ctx::None) { + r = dr; + g = dg; + b = db; + a = da; +} + +// ~~~~~~ Blend modes ~~~~~~ // + +// The same logic applied to all 4 channels. +#define BLEND_MODE(name) \ + SI U16 name##_channel(U16 s, U16 d, U16 sa, U16 da); \ + STAGE_PP(name, Ctx::None) { \ + r = name##_channel(r,dr,a,da); \ + g = name##_channel(g,dg,a,da); \ + b = name##_channel(b,db,a,da); \ + a = name##_channel(a,da,a,da); \ + } \ + SI U16 name##_channel(U16 s, U16 d, U16 sa, U16 da) + + BLEND_MODE(clear) { return 0; } + BLEND_MODE(srcatop) { return div255( s*da + d*inv(sa) ); } + BLEND_MODE(dstatop) { return div255( d*sa + s*inv(da) ); } + BLEND_MODE(srcin) { return div255( s*da ); } + BLEND_MODE(dstin) { return div255( d*sa ); } + BLEND_MODE(srcout) { return div255( s*inv(da) ); } + BLEND_MODE(dstout) { return div255( d*inv(sa) ); } + BLEND_MODE(srcover) { return s + div255( d*inv(sa) ); } + BLEND_MODE(dstover) { return d + div255( s*inv(da) ); } + BLEND_MODE(modulate) { return div255( s*d ); } + BLEND_MODE(multiply) { return div255( s*inv(da) + d*inv(sa) + s*d ); } + BLEND_MODE(plus_) { return min(s+d, 255); } + BLEND_MODE(screen) { return s + d - div255( s*d ); } + BLEND_MODE(xor_) { return div255( s*inv(da) + d*inv(sa) ); } +#undef BLEND_MODE + +// The same logic applied to color, and srcover for alpha. +#define BLEND_MODE(name) \ + SI U16 name##_channel(U16 s, U16 d, U16 sa, U16 da); \ + STAGE_PP(name, Ctx::None) { \ + r = name##_channel(r,dr,a,da); \ + g = name##_channel(g,dg,a,da); \ + b = name##_channel(b,db,a,da); \ + a = a + div255( da*inv(a) ); \ + } \ + SI U16 name##_channel(U16 s, U16 d, U16 sa, U16 da) + + BLEND_MODE(darken) { return s + d - div255( max(s*da, d*sa) ); } + BLEND_MODE(lighten) { return s + d - div255( min(s*da, d*sa) ); } + BLEND_MODE(difference) { return s + d - 2*div255( min(s*da, d*sa) ); } + BLEND_MODE(exclusion) { return s + d - 2*div255( s*d ); } + + BLEND_MODE(hardlight) { + return div255( s*inv(da) + d*inv(sa) + + if_then_else(sa < 2*s, sa*da - 2*(sa-s)*(da-d), 2*s*d) ); + } + BLEND_MODE(overlay) { + return div255( s*inv(da) + d*inv(sa) + + if_then_else(da < 2*d, sa*da - 2*(sa-s)*(da-d), 2*s*d) ); + } +#undef BLEND_MODE + +// ~~~~~~ Helpers for interacting with memory ~~~~~~ // + +template +SI T* ptr_at_xy(const SkRasterPipeline_MemoryCtx* ctx, size_t dx, size_t dy) { + return (T*)ctx->pixels + dy*ctx->stride + dx; +} + +template +SI U32 ix_and_ptr(T** ptr, const SkRasterPipeline_GatherCtx* ctx, F x, F y) { + auto clamp = [](F v, F limit) { + limit = bit_cast( bit_cast(limit) - 1 ); // Exclusive -> inclusive. + return min(max(0, v), limit); + }; + x = clamp(x, ctx->width); + y = clamp(y, ctx->height); + + *ptr = (const T*)ctx->pixels; + return trunc_(y)*ctx->stride + trunc_(x); +} + +template +SI V load(const T* ptr, size_t tail) { + V v = 0; + switch (tail & (N-1)) { +#if defined(__clang__) + case 0: memcpy(&v, ptr, sizeof(v)); break; + #if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + case 15: v[14] = ptr[14]; + case 14: v[13] = ptr[13]; + case 13: v[12] = ptr[12]; + case 12: memcpy(&v, ptr, 12*sizeof(T)); break; + case 11: v[10] = ptr[10]; + case 10: v[ 9] = ptr[ 9]; + case 9: v[ 8] = ptr[ 8]; + case 8: memcpy(&v, ptr, 8*sizeof(T)); break; + #endif + case 7: v[ 6] = ptr[ 6]; + case 6: v[ 5] = ptr[ 5]; + case 5: v[ 4] = ptr[ 4]; + case 4: memcpy(&v, ptr, 4*sizeof(T)); break; + case 3: v[ 2] = ptr[ 2]; + case 2: memcpy(&v, ptr, 2*sizeof(T)); break; + case 1: v[ 0] = ptr[ 0]; +#else + case 0: v = V::Load(ptr); break; + default: memcpy(&v, ptr, (tail & (N-1)) * sizeof(T)); break; +#endif + } + return v; +} +template +SI void store(T* ptr, size_t tail, V v) { + switch (tail & (N-1)) { +#if defined(__clang__) + case 0: memcpy(ptr, &v, sizeof(v)); break; + #if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + case 15: ptr[14] = v[14]; + case 14: ptr[13] = v[13]; + case 13: ptr[12] = v[12]; + case 12: memcpy(ptr, &v, 12*sizeof(T)); break; + case 11: ptr[10] = v[10]; + case 10: ptr[ 9] = v[ 9]; + case 9: ptr[ 8] = v[ 8]; + case 8: memcpy(ptr, &v, 8*sizeof(T)); break; + #endif + case 7: ptr[ 6] = v[ 6]; + case 6: ptr[ 5] = v[ 5]; + case 5: ptr[ 4] = v[ 4]; + case 4: memcpy(ptr, &v, 4*sizeof(T)); break; + case 3: ptr[ 2] = v[ 2]; + case 2: memcpy(ptr, &v, 2*sizeof(T)); break; + case 1: ptr[ 0] = v[ 0]; +#else + case 0: v.store(ptr); break; + default: memcpy(ptr, &v, (tail & (N-1)) * sizeof(T)); break; +#endif + } +} + +#if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + template + SI V gather(const T* ptr, U32 ix) { + return V{ ptr[ix[ 0]], ptr[ix[ 1]], ptr[ix[ 2]], ptr[ix[ 3]], + ptr[ix[ 4]], ptr[ix[ 5]], ptr[ix[ 6]], ptr[ix[ 7]], + ptr[ix[ 8]], ptr[ix[ 9]], ptr[ix[10]], ptr[ix[11]], + ptr[ix[12]], ptr[ix[13]], ptr[ix[14]], ptr[ix[15]], }; + } + + template<> + F gather(const float* ptr, U32 ix) { + __m256i lo, hi; + split(ix, &lo, &hi); + + return join(_mm256_i32gather_ps(ptr, lo, 4), + _mm256_i32gather_ps(ptr, hi, 4)); + } + + template<> + U32 gather(const uint32_t* ptr, U32 ix) { + __m256i lo, hi; + split(ix, &lo, &hi); + + return join(_mm256_i32gather_epi32(ptr, lo, 4), + _mm256_i32gather_epi32(ptr, hi, 4)); + } +#else + template + SI V gather(const T* ptr, U32 ix) { + return V{ ptr[ix[ 0]], ptr[ix[ 1]], ptr[ix[ 2]], ptr[ix[ 3]], + ptr[ix[ 4]], ptr[ix[ 5]], ptr[ix[ 6]], ptr[ix[ 7]], }; + } + + #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + template<> + F gather(const float* ptr, U32 ix) { + __m128i lo, hi; + split(ix, &lo, &hi); + + return join(_mm_i32gather_ps(ptr, lo, 4), + _mm_i32gather_ps(ptr, hi, 4)); + } + + template<> + U32 gather(const uint32_t* ptr, U32 ix) { + __m128i lo, hi; + split(ix, &lo, &hi); + + return join(_mm_i32gather_epi32((const int*)ptr, lo, 4), + _mm_i32gather_epi32((const int*)ptr, hi, 4)); + } + #endif +#endif + + +// ~~~~~~ 32-bit memory loads and stores ~~~~~~ // + +SI void from_8888(U32 rgba, U16* r, U16* g, U16* b, U16* a) { +#if 1 && defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + // Swap the middle 128-bit lanes to make _mm256_packus_epi32() in cast_U16() work out nicely. + __m256i _01,_23; + split(rgba, &_01, &_23); + __m256i _02 = _mm256_permute2x128_si256(_01,_23, 0x20), + _13 = _mm256_permute2x128_si256(_01,_23, 0x31); + rgba = join(_02, _13); + + auto cast_U16 = [](U32 v) -> U16 { + __m256i _02,_13; + split(v, &_02,&_13); + return _mm256_packus_epi32(_02,_13); + }; +#else + auto cast_U16 = [](U32 v) -> U16 { + return cast(v); + }; +#endif + *r = cast_U16(rgba & 65535) & 255; + *g = cast_U16(rgba & 65535) >> 8; + *b = cast_U16(rgba >> 16) & 255; + *a = cast_U16(rgba >> 16) >> 8; +} + +SI void load_8888_(const uint32_t* ptr, size_t tail, U16* r, U16* g, U16* b, U16* a) { +#if 1 && defined(JUMPER_IS_NEON) + uint8x8x4_t rgba; + switch (tail & (N-1)) { + case 0: rgba = vld4_u8 ((const uint8_t*)(ptr+0) ); break; + case 7: rgba = vld4_lane_u8((const uint8_t*)(ptr+6), rgba, 6); + case 6: rgba = vld4_lane_u8((const uint8_t*)(ptr+5), rgba, 5); + case 5: rgba = vld4_lane_u8((const uint8_t*)(ptr+4), rgba, 4); + case 4: rgba = vld4_lane_u8((const uint8_t*)(ptr+3), rgba, 3); + case 3: rgba = vld4_lane_u8((const uint8_t*)(ptr+2), rgba, 2); + case 2: rgba = vld4_lane_u8((const uint8_t*)(ptr+1), rgba, 1); + case 1: rgba = vld4_lane_u8((const uint8_t*)(ptr+0), rgba, 0); + } + *r = cast(rgba.val[0]); + *g = cast(rgba.val[1]); + *b = cast(rgba.val[2]); + *a = cast(rgba.val[3]); +#else + from_8888(load(ptr, tail), r,g,b,a); +#endif +} +SI void store_8888_(uint32_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) { +#if 1 && defined(JUMPER_IS_NEON) + uint8x8x4_t rgba = {{ + cast(r), + cast(g), + cast(b), + cast(a), + }}; + switch (tail & (N-1)) { + case 0: vst4_u8 ((uint8_t*)(ptr+0), rgba ); break; + case 7: vst4_lane_u8((uint8_t*)(ptr+6), rgba, 6); + case 6: vst4_lane_u8((uint8_t*)(ptr+5), rgba, 5); + case 5: vst4_lane_u8((uint8_t*)(ptr+4), rgba, 4); + case 4: vst4_lane_u8((uint8_t*)(ptr+3), rgba, 3); + case 3: vst4_lane_u8((uint8_t*)(ptr+2), rgba, 2); + case 2: vst4_lane_u8((uint8_t*)(ptr+1), rgba, 1); + case 1: vst4_lane_u8((uint8_t*)(ptr+0), rgba, 0); + } +#else + store(ptr, tail, cast(r | (g<<8)) << 0 + | cast(b | (a<<8)) << 16); +#endif +} + +STAGE_PP(load_8888, const SkRasterPipeline_MemoryCtx* ctx) { + load_8888_(ptr_at_xy(ctx, dx,dy), tail, &r,&g,&b,&a); +} +STAGE_PP(load_8888_dst, const SkRasterPipeline_MemoryCtx* ctx) { + load_8888_(ptr_at_xy(ctx, dx,dy), tail, &dr,&dg,&db,&da); +} +STAGE_PP(store_8888, const SkRasterPipeline_MemoryCtx* ctx) { + store_8888_(ptr_at_xy(ctx, dx,dy), tail, r,g,b,a); +} +STAGE_GP(gather_8888, const SkRasterPipeline_GatherCtx* ctx) { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + from_8888(gather(ptr, ix), &r, &g, &b, &a); +} + +// ~~~~~~ 16-bit memory loads and stores ~~~~~~ // + +SI void from_565(U16 rgb, U16* r, U16* g, U16* b) { + // Format for 565 buffers: 15|rrrrr gggggg bbbbb|0 + U16 R = (rgb >> 11) & 31, + G = (rgb >> 5) & 63, + B = (rgb >> 0) & 31; + + // These bit replications are the same as multiplying by 255/31 or 255/63 to scale to 8-bit. + *r = (R << 3) | (R >> 2); + *g = (G << 2) | (G >> 4); + *b = (B << 3) | (B >> 2); +} +SI void load_565_(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b) { + from_565(load(ptr, tail), r,g,b); +} +SI void store_565_(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b) { +#ifdef __clang__ + // Round from [0,255] to [0,31] or [0,63], as if x * (31/255.0f) + 0.5f. + // (Don't feel like you need to find some fundamental truth in these... + // they were brute-force searched.) + U16 R = (r * 9 + 36) / 74, // 9/74 ≈ 31/255, plus 36/74, about half. + G = (g * 21 + 42) / 85, // 21/85 = 63/255 exactly. + B = (b * 9 + 36) / 74; +#else + // Select the top 5,6,5 bits. + U16 R = r >> 3, + G = g >> 2, + B = b >> 3; +#endif + // Pack them back into 15|rrrrr gggggg bbbbb|0. + store(ptr, tail, R << 11 + | G << 5 + | B << 0); +} + +STAGE_PP(load_565, const SkRasterPipeline_MemoryCtx* ctx) { + load_565_(ptr_at_xy(ctx, dx,dy), tail, &r,&g,&b); + a = 255; +} +STAGE_PP(load_565_dst, const SkRasterPipeline_MemoryCtx* ctx) { + load_565_(ptr_at_xy(ctx, dx,dy), tail, &dr,&dg,&db); + da = 255; +} +STAGE_PP(store_565, const SkRasterPipeline_MemoryCtx* ctx) { + store_565_(ptr_at_xy(ctx, dx,dy), tail, r,g,b); +} +STAGE_GP(gather_565, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + from_565(gather(ptr, ix), &r, &g, &b); + a = 255; +} + +SI void from_4444(U16 rgba, U16* r, U16* g, U16* b, U16* a) { + // Format for 4444 buffers: 15|rrrr gggg bbbb aaaa|0. + U16 R = (rgba >> 12) & 15, + G = (rgba >> 8) & 15, + B = (rgba >> 4) & 15, + A = (rgba >> 0) & 15; + + // Scale [0,15] to [0,255]. + *r = (R << 4) | R; + *g = (G << 4) | G; + *b = (B << 4) | B; + *a = (A << 4) | A; +} +SI void load_4444_(const uint16_t* ptr, size_t tail, U16* r, U16* g, U16* b, U16* a) { + from_4444(load(ptr, tail), r,g,b,a); +} +SI void store_4444_(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) { +#ifdef __clang__ + // Round from [0,255] to [0,15], producing the same value as (x*(15/255.0f) + 0.5f). + U16 R = (r + 8) / 17, + G = (g + 8) / 17, + B = (b + 8) / 17, + A = (a + 8) / 17; +#else + // Select the top 4 bits of each. + U16 R = r >> 4, + G = g >> 4, + B = b >> 4, + A = a >> 4; +#endif + // Pack them back into 15|rrrr gggg bbbb aaaa|0. + store(ptr, tail, R << 12 + | G << 8 + | B << 4 + | A << 0); +} + +STAGE_PP(load_4444, const SkRasterPipeline_MemoryCtx* ctx) { + load_4444_(ptr_at_xy(ctx, dx,dy), tail, &r,&g,&b,&a); +} +STAGE_PP(load_4444_dst, const SkRasterPipeline_MemoryCtx* ctx) { + load_4444_(ptr_at_xy(ctx, dx,dy), tail, &dr,&dg,&db,&da); +} +STAGE_PP(store_4444, const SkRasterPipeline_MemoryCtx* ctx) { + store_4444_(ptr_at_xy(ctx, dx,dy), tail, r,g,b,a); +} +STAGE_GP(gather_4444, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + from_4444(gather(ptr, ix), &r,&g,&b,&a); +} + +SI void from_88(U16 rg, U16* r, U16* g) { + *r = (rg & 0xFF); + *g = (rg >> 8); +} + +SI void load_88_(const uint16_t* ptr, size_t tail, U16* r, U16* g) { +#if 1 && defined(JUMPER_IS_NEON) + uint8x8x2_t rg; + switch (tail & (N-1)) { + case 0: rg = vld2_u8 ((const uint8_t*)(ptr+0) ); break; + case 7: rg = vld2_lane_u8((const uint8_t*)(ptr+6), rg, 6); + case 6: rg = vld2_lane_u8((const uint8_t*)(ptr+5), rg, 5); + case 5: rg = vld2_lane_u8((const uint8_t*)(ptr+4), rg, 4); + case 4: rg = vld2_lane_u8((const uint8_t*)(ptr+3), rg, 3); + case 3: rg = vld2_lane_u8((const uint8_t*)(ptr+2), rg, 2); + case 2: rg = vld2_lane_u8((const uint8_t*)(ptr+1), rg, 1); + case 1: rg = vld2_lane_u8((const uint8_t*)(ptr+0), rg, 0); + } + *r = cast(rg.val[0]); + *g = cast(rg.val[1]); +#else + from_88(load(ptr, tail), r,g); +#endif +} + +SI void store_88_(uint16_t* ptr, size_t tail, U16 r, U16 g) { +#if 1 && defined(JUMPER_IS_NEON) + uint8x8x2_t rg = {{ + cast(r), + cast(g), + }}; + switch (tail & (N-1)) { + case 0: vst2_u8 ((uint8_t*)(ptr+0), rg ); break; + case 7: vst2_lane_u8((uint8_t*)(ptr+6), rg, 6); + case 6: vst2_lane_u8((uint8_t*)(ptr+5), rg, 5); + case 5: vst2_lane_u8((uint8_t*)(ptr+4), rg, 4); + case 4: vst2_lane_u8((uint8_t*)(ptr+3), rg, 3); + case 3: vst2_lane_u8((uint8_t*)(ptr+2), rg, 2); + case 2: vst2_lane_u8((uint8_t*)(ptr+1), rg, 1); + case 1: vst2_lane_u8((uint8_t*)(ptr+0), rg, 0); + } +#else + store(ptr, tail, (r | (g<<8))); +#endif +} + +STAGE_PP(load_rg88, const SkRasterPipeline_MemoryCtx* ctx) { + load_88_(ptr_at_xy(ctx, dx, dy), tail, &r, &g); + b = 0; + a = 255; +} +STAGE_PP(load_rg88_dst, const SkRasterPipeline_MemoryCtx* ctx) { + load_88_(ptr_at_xy(ctx, dx, dy), tail, &dr, &dg); + db = 0; + da = 255; +} +STAGE_PP(store_rg88, const SkRasterPipeline_MemoryCtx* ctx) { + store_88_(ptr_at_xy(ctx, dx, dy), tail, r, g); +} +STAGE_GP(gather_rg88, const SkRasterPipeline_GatherCtx* ctx) { + const uint16_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x, y); + from_88(gather(ptr, ix), &r, &g); + b = 0; + a = 255; +} + +// ~~~~~~ 8-bit memory loads and stores ~~~~~~ // + +SI U16 load_8(const uint8_t* ptr, size_t tail) { + return cast(load(ptr, tail)); +} +SI void store_8(uint8_t* ptr, size_t tail, U16 v) { + store(ptr, tail, cast(v)); +} + +STAGE_PP(load_a8, const SkRasterPipeline_MemoryCtx* ctx) { + r = g = b = 0; + a = load_8(ptr_at_xy(ctx, dx,dy), tail); +} +STAGE_PP(load_a8_dst, const SkRasterPipeline_MemoryCtx* ctx) { + dr = dg = db = 0; + da = load_8(ptr_at_xy(ctx, dx,dy), tail); +} +STAGE_PP(store_a8, const SkRasterPipeline_MemoryCtx* ctx) { + store_8(ptr_at_xy(ctx, dx,dy), tail, a); +} +STAGE_GP(gather_a8, const SkRasterPipeline_GatherCtx* ctx) { + const uint8_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + r = g = b = 0; + a = cast(gather(ptr, ix)); +} + +STAGE_PP(alpha_to_gray, Ctx::None) { + r = g = b = a; + a = 255; +} +STAGE_PP(alpha_to_gray_dst, Ctx::None) { + dr = dg = db = da; + da = 255; +} +STAGE_PP(bt709_luminance_or_luma_to_alpha, Ctx::None) { + a = (r*54 + g*183 + b*19)>>8; // 0.2126, 0.7152, 0.0722 with 256 denominator. + r = g = b = 0; +} + +// ~~~~~~ Coverage scales / lerps ~~~~~~ // + +STAGE_PP(load_src, const uint16_t* ptr) { + r = sk_unaligned_load(ptr + 0*N); + g = sk_unaligned_load(ptr + 1*N); + b = sk_unaligned_load(ptr + 2*N); + a = sk_unaligned_load(ptr + 3*N); +} +STAGE_PP(store_src, uint16_t* ptr) { + sk_unaligned_store(ptr + 0*N, r); + sk_unaligned_store(ptr + 1*N, g); + sk_unaligned_store(ptr + 2*N, b); + sk_unaligned_store(ptr + 3*N, a); +} +STAGE_PP(load_dst, const uint16_t* ptr) { + dr = sk_unaligned_load(ptr + 0*N); + dg = sk_unaligned_load(ptr + 1*N); + db = sk_unaligned_load(ptr + 2*N); + da = sk_unaligned_load(ptr + 3*N); +} +STAGE_PP(store_dst, uint16_t* ptr) { + sk_unaligned_store(ptr + 0*N, dr); + sk_unaligned_store(ptr + 1*N, dg); + sk_unaligned_store(ptr + 2*N, db); + sk_unaligned_store(ptr + 3*N, da); +} + +// ~~~~~~ Coverage scales / lerps ~~~~~~ // + +STAGE_PP(scale_1_float, const float* f) { + U16 c = from_float(*f); + r = div255( r * c ); + g = div255( g * c ); + b = div255( b * c ); + a = div255( a * c ); +} +STAGE_PP(lerp_1_float, const float* f) { + U16 c = from_float(*f); + r = lerp(dr, r, c); + g = lerp(dg, g, c); + b = lerp(db, b, c); + a = lerp(da, a, c); +} +STAGE_PP(lerp_native, const uint16_t scales[]) { + auto c = sk_unaligned_load(scales); + r = lerp(dr, r, c); + g = lerp(dg, g, c); + b = lerp(db, b, c); + a = lerp(da, a, c); +} + +STAGE_PP(scale_u8, const SkRasterPipeline_MemoryCtx* ctx) { + U16 c = load_8(ptr_at_xy(ctx, dx,dy), tail); + r = div255( r * c ); + g = div255( g * c ); + b = div255( b * c ); + a = div255( a * c ); +} +STAGE_PP(lerp_u8, const SkRasterPipeline_MemoryCtx* ctx) { + U16 c = load_8(ptr_at_xy(ctx, dx,dy), tail); + r = lerp(dr, r, c); + g = lerp(dg, g, c); + b = lerp(db, b, c); + a = lerp(da, a, c); +} + +// Derive alpha's coverage from rgb coverage and the values of src and dst alpha. +SI U16 alpha_coverage_from_rgb_coverage(U16 a, U16 da, U16 cr, U16 cg, U16 cb) { + return if_then_else(a < da, min(cr, min(cg,cb)) + , max(cr, max(cg,cb))); +} +STAGE_PP(scale_565, const SkRasterPipeline_MemoryCtx* ctx) { + U16 cr,cg,cb; + load_565_(ptr_at_xy(ctx, dx,dy), tail, &cr,&cg,&cb); + U16 ca = alpha_coverage_from_rgb_coverage(a,da, cr,cg,cb); + + r = div255( r * cr ); + g = div255( g * cg ); + b = div255( b * cb ); + a = div255( a * ca ); +} +STAGE_PP(lerp_565, const SkRasterPipeline_MemoryCtx* ctx) { + U16 cr,cg,cb; + load_565_(ptr_at_xy(ctx, dx,dy), tail, &cr,&cg,&cb); + U16 ca = alpha_coverage_from_rgb_coverage(a,da, cr,cg,cb); + + r = lerp(dr, r, cr); + g = lerp(dg, g, cg); + b = lerp(db, b, cb); + a = lerp(da, a, ca); +} + +STAGE_PP(emboss, const SkRasterPipeline_EmbossCtx* ctx) { + U16 mul = load_8(ptr_at_xy(&ctx->mul, dx,dy), tail), + add = load_8(ptr_at_xy(&ctx->add, dx,dy), tail); + + r = min(div255(r*mul) + add, a); + g = min(div255(g*mul) + add, a); + b = min(div255(b*mul) + add, a); +} + + +// ~~~~~~ Gradient stages ~~~~~~ // + +// Clamp x to [0,1], both sides inclusive (think, gradients). +// Even repeat and mirror funnel through a clamp to handle bad inputs like +Inf, NaN. +SI F clamp_01(F v) { return min(max(0, v), 1); } + +STAGE_GG(clamp_x_1 , Ctx::None) { x = clamp_01(x); } +STAGE_GG(repeat_x_1, Ctx::None) { x = clamp_01(x - floor_(x)); } +STAGE_GG(mirror_x_1, Ctx::None) { + auto two = [](F x){ return x+x; }; + x = clamp_01(abs_( (x-1.0f) - two(floor_((x-1.0f)*0.5f)) - 1.0f )); +} + +#if defined(__clang__) +SI U16 cond_to_mask_16(I32 cond) { return cast(cond); } +#else +SI U16 cond_to_mask_16(F cond) { + return _mm_packs_epi32(_mm_castps_si128(cond.fLo.fVec), _mm_castps_si128(cond.fHi.fVec)); +} +#endif + +STAGE_GG(decal_x, SkRasterPipeline_DecalTileCtx* ctx) { + auto w = ctx->limit_x; + sk_unaligned_store(ctx->mask, cond_to_mask_16((0 <= x) & (x < w))); +} +STAGE_GG(decal_y, SkRasterPipeline_DecalTileCtx* ctx) { + auto h = ctx->limit_y; + sk_unaligned_store(ctx->mask, cond_to_mask_16((0 <= y) & (y < h))); +} +STAGE_GG(decal_x_and_y, SkRasterPipeline_DecalTileCtx* ctx) { + auto w = ctx->limit_x; + auto h = ctx->limit_y; + sk_unaligned_store(ctx->mask, cond_to_mask_16((0 <= x) & (x < w) & (0 <= y) & (y < h))); +} +STAGE_PP(check_decal_mask, SkRasterPipeline_DecalTileCtx* ctx) { + auto mask = sk_unaligned_load(ctx->mask); + r = r & mask; + g = g & mask; + b = b & mask; + a = a & mask; +} + +SI void round_F_to_U16(F R, F G, F B, F A, bool interpolatedInPremul, + U16* r, U16* g, U16* b, U16* a) { + auto round = [](F x) { return cast(x * 255.0f + 0.5f); }; + + F limit = interpolatedInPremul ? A + : 1; + *r = round(min(max(0,R), limit)); + *g = round(min(max(0,G), limit)); + *b = round(min(max(0,B), limit)); + *a = round(A); // we assume alpha is already in [0,1]. +} + +SI void gradient_lookup(const SkRasterPipeline_GradientCtx* c, U32 idx, F t, + U16* r, U16* g, U16* b, U16* a) { + + F fr, fg, fb, fa, br, bg, bb, ba; +#if defined(JUMPER_IS_HSW) || defined(JUMPER_IS_AVX512) + if (c->stopCount <=8) { + __m256i lo, hi; + split(idx, &lo, &hi); + + fr = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[0]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[0]), hi)); + br = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[0]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[0]), hi)); + fg = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[1]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[1]), hi)); + bg = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[1]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[1]), hi)); + fb = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[2]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[2]), hi)); + bb = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[2]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[2]), hi)); + fa = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[3]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->fs[3]), hi)); + ba = join(_mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[3]), lo), + _mm256_permutevar8x32_ps(_mm256_loadu_ps(c->bs[3]), hi)); + } else +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX + if (c->stopCount <= 4) { + auto permute = [](const float* ptr, U32 idx) { + __m128 v = _mm_loadu_ps(ptr); + __m256 vv = _mm256_insertf128_ps(_mm256_castps128_ps256(v), v, 1); + return bit_cast(_mm256_permutevar_ps(vv, bit_cast<__m256i>(idx))); + }; + fr = permute(c->fs[0], idx); + br = permute(c->bs[0], idx); + fg = permute(c->fs[1], idx); + bg = permute(c->bs[1], idx); + fb = permute(c->fs[2], idx); + bb = permute(c->bs[2], idx); + fa = permute(c->fs[3], idx); + ba = permute(c->bs[3], idx); + } else +#endif + { + fr = gather(c->fs[0], idx); + fg = gather(c->fs[1], idx); + fb = gather(c->fs[2], idx); + fa = gather(c->fs[3], idx); + br = gather(c->bs[0], idx); + bg = gather(c->bs[1], idx); + bb = gather(c->bs[2], idx); + ba = gather(c->bs[3], idx); + } + round_F_to_U16(mad(t, fr, br), + mad(t, fg, bg), + mad(t, fb, bb), + mad(t, fa, ba), + c->interpolatedInPremul, + r,g,b,a); +} + +STAGE_GP(gradient, const SkRasterPipeline_GradientCtx* c) { + auto t = x; + U32 idx = 0; + + // N.B. The loop starts at 1 because idx 0 is the color to use before the first stop. + for (size_t i = 1; i < c->stopCount; i++) { + idx += bit_cast(t >= c->ts[i]); + } + + idx = U32(0) - idx; + + gradient_lookup(c, idx, t, &r, &g, &b, &a); +} + +STAGE_GP(evenly_spaced_gradient, const SkRasterPipeline_GradientCtx* c) { + auto t = x; + auto idx = trunc_(t * (c->stopCount-1)); + gradient_lookup(c, idx, t, &r, &g, &b, &a); +} + +STAGE_GP(evenly_spaced_2_stop_gradient, const SkRasterPipeline_EvenlySpaced2StopGradientCtx* c) { + auto t = x; + round_F_to_U16(mad(t, c->f[0], c->b[0]), + mad(t, c->f[1], c->b[1]), + mad(t, c->f[2], c->b[2]), + mad(t, c->f[3], c->b[3]), + c->interpolatedInPremul, + &r,&g,&b,&a); +} + +STAGE_GG(xy_to_unit_angle, Ctx::None) { + F xabs = abs_(x), + yabs = abs_(y); + + F slope = min(xabs, yabs)/max(xabs, yabs); + F s = slope * slope; + + // Use a 7th degree polynomial to approximate atan. + // This was generated using sollya.gforge.inria.fr. + // A float optimized polynomial was generated using the following command. + // P1 = fpminimax((1/(2*Pi))*atan(x),[|1,3,5,7|],[|24...|],[2^(-40),1],relative); + F phi = slope + * (0.15912117063999176025390625f + s + * (-5.185396969318389892578125e-2f + s + * (2.476101927459239959716796875e-2f + s + * (-7.0547382347285747528076171875e-3f)))); + + phi = if_then_else(xabs < yabs, 1.0f/4.0f - phi, phi); + phi = if_then_else(x < 0.0f , 1.0f/2.0f - phi, phi); + phi = if_then_else(y < 0.0f , 1.0f - phi , phi); + phi = if_then_else(isnan_(phi), 0 , phi); // Check for NaN. + x = phi; +} +STAGE_GG(xy_to_radius, Ctx::None) { + x = sqrt_(x*x + y*y); +} + +// ~~~~~~ Compound stages ~~~~~~ // + +STAGE_PP(srcover_rgba_8888, const SkRasterPipeline_MemoryCtx* ctx) { + auto ptr = ptr_at_xy(ctx, dx,dy); + + load_8888_(ptr, tail, &dr,&dg,&db,&da); + r = r + div255( dr*inv(a) ); + g = g + div255( dg*inv(a) ); + b = b + div255( db*inv(a) ); + a = a + div255( da*inv(a) ); + store_8888_(ptr, tail, r,g,b,a); +} + +#if defined(SK_DISABLE_LOWP_BILERP_CLAMP_CLAMP_STAGE) + static void(*bilerp_clamp_8888)(void) = nullptr; + static void(*bilinear)(void) = nullptr; +#else +STAGE_GP(bilerp_clamp_8888, const SkRasterPipeline_GatherCtx* ctx) { + // (cx,cy) are the center of our sample. + F cx = x, + cy = y; + + // All sample points are at the same fractional offset (fx,fy). + // They're the 4 corners of a logical 1x1 pixel surrounding (x,y) at (0.5,0.5) offsets. + F fx = fract(cx + 0.5f), + fy = fract(cy + 0.5f); + + // We'll accumulate the color of all four samples into {r,g,b,a} directly. + r = g = b = a = 0; + + // The first three sample points will calculate their area using math + // just like in the float code above, but the fourth will take up all the rest. + // + // Logically this is the same as doing the math for the fourth pixel too, + // but rounding error makes this a better strategy, keeping opaque opaque, etc. + // + // We can keep up to 8 bits of fractional precision without overflowing 16-bit, + // so our "1.0" area is 256. + const uint16_t bias = 256; + const uint16_t biasShift = 8; + U16 remaining = bias; + + for (float dy = -0.5f; dy <= +0.5f; dy += 1.0f) + for (float dx = -0.5f; dx <= +0.5f; dx += 1.0f) { + // (x,y) are the coordinates of this sample point. + F x = cx + dx, + y = cy + dy; + + // ix_and_ptr() will clamp to the image's bounds for us. + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + + U16 sr,sg,sb,sa; + from_8888(gather(ptr, ix), &sr,&sg,&sb,&sa); + + // In bilinear interpolation, the 4 pixels at +/- 0.5 offsets from the sample pixel center + // are combined in direct proportion to their area overlapping that logical query pixel. + // At positive offsets, the x-axis contribution to that rectangle is fx, + // or (1-fx) at negative x. Same deal for y. + F sx = (dx > 0) ? fx : 1.0f - fx, + sy = (dy > 0) ? fy : 1.0f - fy; + + U16 area = (dy == 0.5f && dx == 0.5f) ? remaining + : cast(sx * sy * bias); + for (size_t i = 0; i < N; i++) { + SkASSERT(remaining[i] >= area[i]); + } + remaining -= area; + + r += sr * area; + g += sg * area; + b += sb * area; + a += sa * area; + } + + r = (r + bias/2) >> biasShift; + g = (g + bias/2) >> biasShift; + b = (b + bias/2) >> biasShift; + a = (a + bias/2) >> biasShift; +} + +// TODO: lowp::tile() is identical to the highp tile()... share? +SI F tile(F v, SkTileMode mode, float limit, float invLimit) { + // After ix_and_ptr() will clamp the output of tile(), so we need not clamp here. + switch (mode) { + case SkTileMode::kDecal: // TODO, for now fallthrough to clamp + case SkTileMode::kClamp: return v; + case SkTileMode::kRepeat: return v - floor_(v*invLimit)*limit; + case SkTileMode::kMirror: + return abs_( (v-limit) - (limit+limit)*floor_((v-limit)*(invLimit*0.5f)) - limit ); + } + SkUNREACHABLE; +} + +SI void sample(const SkRasterPipeline_SamplerCtx2* ctx, F x, F y, + U16* r, U16* g, U16* b, U16* a) { + x = tile(x, ctx->tileX, ctx->width , ctx->invWidth ); + y = tile(y, ctx->tileY, ctx->height, ctx->invHeight); + + switch (ctx->ct) { + default: *r = *g = *b = *a = 0; // TODO + break; + + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + const uint32_t* ptr; + U32 ix = ix_and_ptr(&ptr, ctx, x,y); + from_8888(gather(ptr, ix), r,g,b,a); + if (ctx->ct == kBGRA_8888_SkColorType) { + std::swap(*r,*b); + } + } break; + } +} + +template +SI void sampler(const SkRasterPipeline_SamplerCtx2* ctx, + F cx, F cy, const F (&wx)[D], const F (&wy)[D], + U16* r, U16* g, U16* b, U16* a) { + + float start = -0.5f*(D-1); + + const uint16_t bias = 256; + const uint16_t biasShift = 8; + U16 remaining = bias; + + *r = *g = *b = *a = 0; + F y = cy + start; + for (int j = 0; j < D; j++, y += 1.0f) { + F x = cx + start; + for (int i = 0; i < D; i++, x += 1.0f) { + U16 R,G,B,A; + sample(ctx, x,y, &R,&G,&B,&A); + + U16 w = (i == D-1 && j == D-1) ? remaining + : cast(wx[i]*wy[j]*bias); + remaining -= w; + *r += w*R; + *g += w*G; + *b += w*B; + *a += w*A; + } + } + *r = (*r + bias/2) >> biasShift; + *g = (*g + bias/2) >> biasShift; + *b = (*b + bias/2) >> biasShift; + *a = (*a + bias/2) >> biasShift; +} + +STAGE_GP(bilinear, const SkRasterPipeline_SamplerCtx2* ctx) { + F fx = fract(x + 0.5f), + fy = fract(y + 0.5f); + const F wx[] = {1.0f - fx, fx}; + const F wy[] = {1.0f - fy, fy}; + + sampler(ctx, x,y, wx,wy, &r,&g,&b,&a); +} +#endif + +// ~~~~~~ GrSwizzle stage ~~~~~~ // + +STAGE_PP(swizzle, void* ctx) { + auto ir = r, ig = g, ib = b, ia = a; + U16* o[] = {&r, &g, &b, &a}; + char swiz[4]; + memcpy(swiz, &ctx, sizeof(swiz)); + + for (int i = 0; i < 4; ++i) { + switch (swiz[i]) { + case 'r': *o[i] = ir; break; + case 'g': *o[i] = ig; break; + case 'b': *o[i] = ib; break; + case 'a': *o[i] = ia; break; + case '0': *o[i] = U16(0); break; + case '1': *o[i] = U16(255); break; + default: break; + } + } +} + +// Now we'll add null stand-ins for stages we haven't implemented in lowp. +// If a pipeline uses these stages, it'll boot it out of lowp into highp. +#define NOT_IMPLEMENTED(st) static void (*st)(void) = nullptr; + NOT_IMPLEMENTED(callback) + NOT_IMPLEMENTED(interpreter) + NOT_IMPLEMENTED(unbounded_set_rgb) + NOT_IMPLEMENTED(unbounded_uniform_color) + NOT_IMPLEMENTED(unpremul) + NOT_IMPLEMENTED(dither) // TODO + NOT_IMPLEMENTED(from_srgb) + NOT_IMPLEMENTED(to_srgb) + NOT_IMPLEMENTED(load_16161616) + NOT_IMPLEMENTED(load_16161616_dst) + NOT_IMPLEMENTED(store_16161616) + NOT_IMPLEMENTED(gather_16161616) + NOT_IMPLEMENTED(load_a16) + NOT_IMPLEMENTED(load_a16_dst) + NOT_IMPLEMENTED(store_a16) + NOT_IMPLEMENTED(gather_a16) + NOT_IMPLEMENTED(load_rg1616) + NOT_IMPLEMENTED(load_rg1616_dst) + NOT_IMPLEMENTED(store_rg1616) + NOT_IMPLEMENTED(gather_rg1616) + NOT_IMPLEMENTED(load_f16) + NOT_IMPLEMENTED(load_f16_dst) + NOT_IMPLEMENTED(store_f16) + NOT_IMPLEMENTED(gather_f16) + NOT_IMPLEMENTED(load_af16) + NOT_IMPLEMENTED(load_af16_dst) + NOT_IMPLEMENTED(store_af16) + NOT_IMPLEMENTED(gather_af16) + NOT_IMPLEMENTED(load_rgf16) + NOT_IMPLEMENTED(load_rgf16_dst) + NOT_IMPLEMENTED(store_rgf16) + NOT_IMPLEMENTED(gather_rgf16) + NOT_IMPLEMENTED(load_f32) + NOT_IMPLEMENTED(load_f32_dst) + NOT_IMPLEMENTED(store_f32) + NOT_IMPLEMENTED(gather_f32) + NOT_IMPLEMENTED(load_rgf32) + NOT_IMPLEMENTED(store_rgf32) + NOT_IMPLEMENTED(load_1010102) + NOT_IMPLEMENTED(load_1010102_dst) + NOT_IMPLEMENTED(store_1010102) + NOT_IMPLEMENTED(gather_1010102) + NOT_IMPLEMENTED(store_u16_be) + NOT_IMPLEMENTED(byte_tables) // TODO + NOT_IMPLEMENTED(colorburn) + NOT_IMPLEMENTED(colordodge) + NOT_IMPLEMENTED(softlight) + NOT_IMPLEMENTED(hue) + NOT_IMPLEMENTED(saturation) + NOT_IMPLEMENTED(color) + NOT_IMPLEMENTED(luminosity) + NOT_IMPLEMENTED(matrix_3x3) + NOT_IMPLEMENTED(matrix_3x4) + NOT_IMPLEMENTED(matrix_4x5) // TODO + NOT_IMPLEMENTED(matrix_4x3) // TODO + NOT_IMPLEMENTED(parametric) + NOT_IMPLEMENTED(gamma_) + NOT_IMPLEMENTED(PQish) + NOT_IMPLEMENTED(HLGish) + NOT_IMPLEMENTED(HLGinvish) + NOT_IMPLEMENTED(rgb_to_hsl) + NOT_IMPLEMENTED(hsl_to_rgb) + NOT_IMPLEMENTED(gauss_a_to_rgba) // TODO + NOT_IMPLEMENTED(mirror_x) // TODO + NOT_IMPLEMENTED(repeat_x) // TODO + NOT_IMPLEMENTED(mirror_y) // TODO + NOT_IMPLEMENTED(repeat_y) // TODO + NOT_IMPLEMENTED(negate_x) + NOT_IMPLEMENTED(bicubic) // TODO if I can figure out negative weights + NOT_IMPLEMENTED(bicubic_clamp_8888) + NOT_IMPLEMENTED(bilinear_nx) // TODO + NOT_IMPLEMENTED(bilinear_ny) // TODO + NOT_IMPLEMENTED(bilinear_px) // TODO + NOT_IMPLEMENTED(bilinear_py) // TODO + NOT_IMPLEMENTED(bicubic_n3x) // TODO + NOT_IMPLEMENTED(bicubic_n1x) // TODO + NOT_IMPLEMENTED(bicubic_p1x) // TODO + NOT_IMPLEMENTED(bicubic_p3x) // TODO + NOT_IMPLEMENTED(bicubic_n3y) // TODO + NOT_IMPLEMENTED(bicubic_n1y) // TODO + NOT_IMPLEMENTED(bicubic_p1y) // TODO + NOT_IMPLEMENTED(bicubic_p3y) // TODO + NOT_IMPLEMENTED(save_xy) // TODO + NOT_IMPLEMENTED(accumulate) // TODO + NOT_IMPLEMENTED(xy_to_2pt_conical_well_behaved) + NOT_IMPLEMENTED(xy_to_2pt_conical_strip) + NOT_IMPLEMENTED(xy_to_2pt_conical_focal_on_circle) + NOT_IMPLEMENTED(xy_to_2pt_conical_smaller) + NOT_IMPLEMENTED(xy_to_2pt_conical_greater) + NOT_IMPLEMENTED(alter_2pt_conical_compensate_focal) + NOT_IMPLEMENTED(alter_2pt_conical_unswap) + NOT_IMPLEMENTED(mask_2pt_conical_nan) + NOT_IMPLEMENTED(mask_2pt_conical_degenerates) + NOT_IMPLEMENTED(apply_vector_mask) +#undef NOT_IMPLEMENTED + +#endif//defined(JUMPER_IS_SCALAR) controlling whether we build lowp stages +} // namespace lowp + +} // namespace SK_OPTS_NS + +#endif//SkRasterPipeline_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkSwizzler_opts.h b/gfx/skia/skia/src/opts/SkSwizzler_opts.h new file mode 100644 index 0000000000..f7f7c25457 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkSwizzler_opts.h @@ -0,0 +1,823 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSwizzler_opts_DEFINED +#define SkSwizzler_opts_DEFINED + +#include "include/private/SkColorData.h" + +#include + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + #include +#elif defined(SK_ARM_HAS_NEON) + #include +#endif + +namespace SK_OPTS_NS { + +static void RGBA_to_rgbA_portable(uint32_t* dst, const uint32_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t a = (src[i] >> 24) & 0xFF, + b = (src[i] >> 16) & 0xFF, + g = (src[i] >> 8) & 0xFF, + r = (src[i] >> 0) & 0xFF; + b = (b*a+127)/255; + g = (g*a+127)/255; + r = (r*a+127)/255; + dst[i] = (uint32_t)a << 24 + | (uint32_t)b << 16 + | (uint32_t)g << 8 + | (uint32_t)r << 0; + } +} + +static void RGBA_to_bgrA_portable(uint32_t* dst, const uint32_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t a = (src[i] >> 24) & 0xFF, + b = (src[i] >> 16) & 0xFF, + g = (src[i] >> 8) & 0xFF, + r = (src[i] >> 0) & 0xFF; + b = (b*a+127)/255; + g = (g*a+127)/255; + r = (r*a+127)/255; + dst[i] = (uint32_t)a << 24 + | (uint32_t)r << 16 + | (uint32_t)g << 8 + | (uint32_t)b << 0; + } +} + +static void RGBA_to_BGRA_portable(uint32_t* dst, const uint32_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t a = (src[i] >> 24) & 0xFF, + b = (src[i] >> 16) & 0xFF, + g = (src[i] >> 8) & 0xFF, + r = (src[i] >> 0) & 0xFF; + dst[i] = (uint32_t)a << 24 + | (uint32_t)r << 16 + | (uint32_t)g << 8 + | (uint32_t)b << 0; + } +} + +static void RGB_to_RGB1_portable(uint32_t dst[], const uint8_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t r = src[0], + g = src[1], + b = src[2]; + src += 3; + dst[i] = (uint32_t)0xFF << 24 + | (uint32_t)b << 16 + | (uint32_t)g << 8 + | (uint32_t)r << 0; + } +} + +static void RGB_to_BGR1_portable(uint32_t dst[], const uint8_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t r = src[0], + g = src[1], + b = src[2]; + src += 3; + dst[i] = (uint32_t)0xFF << 24 + | (uint32_t)r << 16 + | (uint32_t)g << 8 + | (uint32_t)b << 0; + } +} + +static void gray_to_RGB1_portable(uint32_t dst[], const uint8_t* src, int count) { + for (int i = 0; i < count; i++) { + dst[i] = (uint32_t)0xFF << 24 + | (uint32_t)src[i] << 16 + | (uint32_t)src[i] << 8 + | (uint32_t)src[i] << 0; + } +} + +static void grayA_to_RGBA_portable(uint32_t dst[], const uint8_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t g = src[0], + a = src[1]; + src += 2; + dst[i] = (uint32_t)a << 24 + | (uint32_t)g << 16 + | (uint32_t)g << 8 + | (uint32_t)g << 0; + } +} + +static void grayA_to_rgbA_portable(uint32_t dst[], const uint8_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t g = src[0], + a = src[1]; + src += 2; + g = (g*a+127)/255; + dst[i] = (uint32_t)a << 24 + | (uint32_t)g << 16 + | (uint32_t)g << 8 + | (uint32_t)g << 0; + } +} + +static void inverted_CMYK_to_RGB1_portable(uint32_t* dst, const uint32_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t k = (src[i] >> 24) & 0xFF, + y = (src[i] >> 16) & 0xFF, + m = (src[i] >> 8) & 0xFF, + c = (src[i] >> 0) & 0xFF; + // See comments in SkSwizzler.cpp for details on the conversion formula. + uint8_t b = (y*k+127)/255, + g = (m*k+127)/255, + r = (c*k+127)/255; + dst[i] = (uint32_t)0xFF << 24 + | (uint32_t) b << 16 + | (uint32_t) g << 8 + | (uint32_t) r << 0; + } +} + +static void inverted_CMYK_to_BGR1_portable(uint32_t* dst, const uint32_t* src, int count) { + for (int i = 0; i < count; i++) { + uint8_t k = (src[i] >> 24) & 0xFF, + y = (src[i] >> 16) & 0xFF, + m = (src[i] >> 8) & 0xFF, + c = (src[i] >> 0) & 0xFF; + uint8_t b = (y*k+127)/255, + g = (m*k+127)/255, + r = (c*k+127)/255; + dst[i] = (uint32_t)0xFF << 24 + | (uint32_t) r << 16 + | (uint32_t) g << 8 + | (uint32_t) b << 0; + } +} + +#if defined(SK_ARM_HAS_NEON) + +// Rounded divide by 255, (x + 127) / 255 +static uint8x8_t div255_round(uint16x8_t x) { + // result = (x + 127) / 255 + // result = (x + 127) / 256 + error1 + // + // error1 = (x + 127) / (255 * 256) + // error1 = (x + 127) / (256 * 256) + error2 + // + // error2 = (x + 127) / (255 * 256 * 256) + // + // The maximum value of error2 is too small to matter. Thus: + // result = (x + 127) / 256 + (x + 127) / (256 * 256) + // result = ((x + 127) / 256 + x + 127) / 256 + // result = ((x + 127) >> 8 + x + 127) >> 8 + // + // Use >>> to represent "rounded right shift" which, conveniently, + // NEON supports in one instruction. + // result = ((x >>> 8) + x) >>> 8 + // + // Note that the second right shift is actually performed as an + // "add, round, and narrow back to 8-bits" instruction. + return vraddhn_u16(x, vrshrq_n_u16(x, 8)); +} + +// Scale a byte by another, (x * y + 127) / 255 +static uint8x8_t scale(uint8x8_t x, uint8x8_t y) { + return div255_round(vmull_u8(x, y)); +} + +template +static void premul_should_swapRB(uint32_t* dst, const uint32_t* src, int count) { + while (count >= 8) { + // Load 8 pixels. + uint8x8x4_t rgba = vld4_u8((const uint8_t*) src); + + uint8x8_t a = rgba.val[3], + b = rgba.val[2], + g = rgba.val[1], + r = rgba.val[0]; + + // Premultiply. + b = scale(b, a); + g = scale(g, a); + r = scale(r, a); + + // Store 8 premultiplied pixels. + if (kSwapRB) { + rgba.val[2] = r; + rgba.val[1] = g; + rgba.val[0] = b; + } else { + rgba.val[2] = b; + rgba.val[1] = g; + rgba.val[0] = r; + } + vst4_u8((uint8_t*) dst, rgba); + src += 8; + dst += 8; + count -= 8; + } + + // Call portable code to finish up the tail of [0,8) pixels. + auto proc = kSwapRB ? RGBA_to_bgrA_portable : RGBA_to_rgbA_portable; + proc(dst, src, count); +} + +/*not static*/ inline void RGBA_to_rgbA(uint32_t* dst, const uint32_t* src, int count) { + premul_should_swapRB(dst, src, count); +} + +/*not static*/ inline void RGBA_to_bgrA(uint32_t* dst, const uint32_t* src, int count) { + premul_should_swapRB(dst, src, count); +} + +/*not static*/ inline void RGBA_to_BGRA(uint32_t* dst, const uint32_t* src, int count) { + using std::swap; + while (count >= 16) { + // Load 16 pixels. + uint8x16x4_t rgba = vld4q_u8((const uint8_t*) src); + + // Swap r and b. + swap(rgba.val[0], rgba.val[2]); + + // Store 16 pixels. + vst4q_u8((uint8_t*) dst, rgba); + src += 16; + dst += 16; + count -= 16; + } + + if (count >= 8) { + // Load 8 pixels. + uint8x8x4_t rgba = vld4_u8((const uint8_t*) src); + + // Swap r and b. + swap(rgba.val[0], rgba.val[2]); + + // Store 8 pixels. + vst4_u8((uint8_t*) dst, rgba); + src += 8; + dst += 8; + count -= 8; + } + + RGBA_to_BGRA_portable(dst, src, count); +} + +template +static void insert_alpha_should_swaprb(uint32_t dst[], const uint8_t* src, int count) { + while (count >= 16) { + // Load 16 pixels. + uint8x16x3_t rgb = vld3q_u8(src); + + // Insert an opaque alpha channel and swap if needed. + uint8x16x4_t rgba; + if (kSwapRB) { + rgba.val[0] = rgb.val[2]; + rgba.val[2] = rgb.val[0]; + } else { + rgba.val[0] = rgb.val[0]; + rgba.val[2] = rgb.val[2]; + } + rgba.val[1] = rgb.val[1]; + rgba.val[3] = vdupq_n_u8(0xFF); + + // Store 16 pixels. + vst4q_u8((uint8_t*) dst, rgba); + src += 16*3; + dst += 16; + count -= 16; + } + + if (count >= 8) { + // Load 8 pixels. + uint8x8x3_t rgb = vld3_u8(src); + + // Insert an opaque alpha channel and swap if needed. + uint8x8x4_t rgba; + if (kSwapRB) { + rgba.val[0] = rgb.val[2]; + rgba.val[2] = rgb.val[0]; + } else { + rgba.val[0] = rgb.val[0]; + rgba.val[2] = rgb.val[2]; + } + rgba.val[1] = rgb.val[1]; + rgba.val[3] = vdup_n_u8(0xFF); + + // Store 8 pixels. + vst4_u8((uint8_t*) dst, rgba); + src += 8*3; + dst += 8; + count -= 8; + } + + // Call portable code to finish up the tail of [0,8) pixels. + auto proc = kSwapRB ? RGB_to_BGR1_portable : RGB_to_RGB1_portable; + proc(dst, src, count); +} + +/*not static*/ inline void RGB_to_RGB1(uint32_t dst[], const uint8_t* src, int count) { + insert_alpha_should_swaprb(dst, src, count); +} + +/*not static*/ inline void RGB_to_BGR1(uint32_t dst[], const uint8_t* src, int count) { + insert_alpha_should_swaprb(dst, src, count); +} + +/*not static*/ inline void gray_to_RGB1(uint32_t dst[], const uint8_t* src, int count) { + while (count >= 16) { + // Load 16 pixels. + uint8x16_t gray = vld1q_u8(src); + + // Set each of the color channels. + uint8x16x4_t rgba; + rgba.val[0] = gray; + rgba.val[1] = gray; + rgba.val[2] = gray; + rgba.val[3] = vdupq_n_u8(0xFF); + + // Store 16 pixels. + vst4q_u8((uint8_t*) dst, rgba); + src += 16; + dst += 16; + count -= 16; + } + + if (count >= 8) { + // Load 8 pixels. + uint8x8_t gray = vld1_u8(src); + + // Set each of the color channels. + uint8x8x4_t rgba; + rgba.val[0] = gray; + rgba.val[1] = gray; + rgba.val[2] = gray; + rgba.val[3] = vdup_n_u8(0xFF); + + // Store 8 pixels. + vst4_u8((uint8_t*) dst, rgba); + src += 8; + dst += 8; + count -= 8; + } + + gray_to_RGB1_portable(dst, src, count); +} + +template +static void expand_grayA(uint32_t dst[], const uint8_t* src, int count) { + while (count >= 16) { + // Load 16 pixels. + uint8x16x2_t ga = vld2q_u8(src); + + // Premultiply if requested. + if (kPremul) { + ga.val[0] = vcombine_u8( + scale(vget_low_u8(ga.val[0]), vget_low_u8(ga.val[1])), + scale(vget_high_u8(ga.val[0]), vget_high_u8(ga.val[1]))); + } + + // Set each of the color channels. + uint8x16x4_t rgba; + rgba.val[0] = ga.val[0]; + rgba.val[1] = ga.val[0]; + rgba.val[2] = ga.val[0]; + rgba.val[3] = ga.val[1]; + + // Store 16 pixels. + vst4q_u8((uint8_t*) dst, rgba); + src += 16*2; + dst += 16; + count -= 16; + } + + if (count >= 8) { + // Load 8 pixels. + uint8x8x2_t ga = vld2_u8(src); + + // Premultiply if requested. + if (kPremul) { + ga.val[0] = scale(ga.val[0], ga.val[1]); + } + + // Set each of the color channels. + uint8x8x4_t rgba; + rgba.val[0] = ga.val[0]; + rgba.val[1] = ga.val[0]; + rgba.val[2] = ga.val[0]; + rgba.val[3] = ga.val[1]; + + // Store 8 pixels. + vst4_u8((uint8_t*) dst, rgba); + src += 8*2; + dst += 8; + count -= 8; + } + + auto proc = kPremul ? grayA_to_rgbA_portable : grayA_to_RGBA_portable; + proc(dst, src, count); +} + +/*not static*/ inline void grayA_to_RGBA(uint32_t dst[], const uint8_t* src, int count) { + expand_grayA(dst, src, count); +} + +/*not static*/ inline void grayA_to_rgbA(uint32_t dst[], const uint8_t* src, int count) { + expand_grayA(dst, src, count); +} + +enum Format { kRGB1, kBGR1 }; +template +static void inverted_cmyk_to(uint32_t* dst, const uint32_t* src, int count) { + while (count >= 8) { + // Load 8 cmyk pixels. + uint8x8x4_t pixels = vld4_u8((const uint8_t*) src); + + uint8x8_t k = pixels.val[3], + y = pixels.val[2], + m = pixels.val[1], + c = pixels.val[0]; + + // Scale to r, g, b. + uint8x8_t b = scale(y, k); + uint8x8_t g = scale(m, k); + uint8x8_t r = scale(c, k); + + // Store 8 rgba pixels. + if (kBGR1 == format) { + pixels.val[3] = vdup_n_u8(0xFF); + pixels.val[2] = r; + pixels.val[1] = g; + pixels.val[0] = b; + } else { + pixels.val[3] = vdup_n_u8(0xFF); + pixels.val[2] = b; + pixels.val[1] = g; + pixels.val[0] = r; + } + vst4_u8((uint8_t*) dst, pixels); + src += 8; + dst += 8; + count -= 8; + } + + auto proc = (kBGR1 == format) ? inverted_CMYK_to_BGR1_portable : inverted_CMYK_to_RGB1_portable; + proc(dst, src, count); +} + +/*not static*/ inline void inverted_CMYK_to_RGB1(uint32_t dst[], const uint32_t* src, int count) { + inverted_cmyk_to(dst, src, count); +} + +/*not static*/ inline void inverted_CMYK_to_BGR1(uint32_t dst[], const uint32_t* src, int count) { + inverted_cmyk_to(dst, src, count); +} + +#elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3 + +// Scale a byte by another. +// Inputs are stored in 16-bit lanes, but are not larger than 8-bits. +static __m128i scale(__m128i x, __m128i y) { + const __m128i _128 = _mm_set1_epi16(128); + const __m128i _257 = _mm_set1_epi16(257); + + // (x+127)/255 == ((x+128)*257)>>16 for 0 <= x <= 255*255. + return _mm_mulhi_epu16(_mm_add_epi16(_mm_mullo_epi16(x, y), _128), _257); +} + +template +static void premul_should_swapRB(uint32_t* dst, const uint32_t* src, int count) { + + auto premul8 = [](__m128i* lo, __m128i* hi) { + const __m128i zeros = _mm_setzero_si128(); + __m128i planar; + if (kSwapRB) { + planar = _mm_setr_epi8(2,6,10,14, 1,5,9,13, 0,4,8,12, 3,7,11,15); + } else { + planar = _mm_setr_epi8(0,4,8,12, 1,5,9,13, 2,6,10,14, 3,7,11,15); + } + + // Swizzle the pixels to 8-bit planar. + *lo = _mm_shuffle_epi8(*lo, planar); // rrrrgggg bbbbaaaa + *hi = _mm_shuffle_epi8(*hi, planar); // RRRRGGGG BBBBAAAA + __m128i rg = _mm_unpacklo_epi32(*lo, *hi), // rrrrRRRR ggggGGGG + ba = _mm_unpackhi_epi32(*lo, *hi); // bbbbBBBB aaaaAAAA + + // Unpack to 16-bit planar. + __m128i r = _mm_unpacklo_epi8(rg, zeros), // r_r_r_r_ R_R_R_R_ + g = _mm_unpackhi_epi8(rg, zeros), // g_g_g_g_ G_G_G_G_ + b = _mm_unpacklo_epi8(ba, zeros), // b_b_b_b_ B_B_B_B_ + a = _mm_unpackhi_epi8(ba, zeros); // a_a_a_a_ A_A_A_A_ + + // Premultiply! + r = scale(r, a); + g = scale(g, a); + b = scale(b, a); + + // Repack into interlaced pixels. + rg = _mm_or_si128(r, _mm_slli_epi16(g, 8)); // rgrgrgrg RGRGRGRG + ba = _mm_or_si128(b, _mm_slli_epi16(a, 8)); // babababa BABABABA + *lo = _mm_unpacklo_epi16(rg, ba); // rgbargba rgbargba + *hi = _mm_unpackhi_epi16(rg, ba); // RGBARGBA RGBARGBA + }; + + while (count >= 8) { + __m128i lo = _mm_loadu_si128((const __m128i*) (src + 0)), + hi = _mm_loadu_si128((const __m128i*) (src + 4)); + + premul8(&lo, &hi); + + _mm_storeu_si128((__m128i*) (dst + 0), lo); + _mm_storeu_si128((__m128i*) (dst + 4), hi); + + src += 8; + dst += 8; + count -= 8; + } + + if (count >= 4) { + __m128i lo = _mm_loadu_si128((const __m128i*) src), + hi = _mm_setzero_si128(); + + premul8(&lo, &hi); + + _mm_storeu_si128((__m128i*) dst, lo); + + src += 4; + dst += 4; + count -= 4; + } + + // Call portable code to finish up the tail of [0,4) pixels. + auto proc = kSwapRB ? RGBA_to_bgrA_portable : RGBA_to_rgbA_portable; + proc(dst, src, count); +} + +/*not static*/ inline void RGBA_to_rgbA(uint32_t* dst, const uint32_t* src, int count) { + premul_should_swapRB(dst, src, count); +} + +/*not static*/ inline void RGBA_to_bgrA(uint32_t* dst, const uint32_t* src, int count) { + premul_should_swapRB(dst, src, count); +} + +/*not static*/ inline void RGBA_to_BGRA(uint32_t* dst, const uint32_t* src, int count) { + const __m128i swapRB = _mm_setr_epi8(2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15); + + while (count >= 4) { + __m128i rgba = _mm_loadu_si128((const __m128i*) src); + __m128i bgra = _mm_shuffle_epi8(rgba, swapRB); + _mm_storeu_si128((__m128i*) dst, bgra); + + src += 4; + dst += 4; + count -= 4; + } + + RGBA_to_BGRA_portable(dst, src, count); +} + +template +static void insert_alpha_should_swaprb(uint32_t dst[], const uint8_t* src, int count) { + const __m128i alphaMask = _mm_set1_epi32(0xFF000000); + __m128i expand; + const uint8_t X = 0xFF; // Used a placeholder. The value of X is irrelevant. + if (kSwapRB) { + expand = _mm_setr_epi8(2,1,0,X, 5,4,3,X, 8,7,6,X, 11,10,9,X); + } else { + expand = _mm_setr_epi8(0,1,2,X, 3,4,5,X, 6,7,8,X, 9,10,11,X); + } + + while (count >= 6) { + // Load a vector. While this actually contains 5 pixels plus an + // extra component, we will discard all but the first four pixels on + // this iteration. + __m128i rgb = _mm_loadu_si128((const __m128i*) src); + + // Expand the first four pixels to RGBX and then mask to RGB(FF). + __m128i rgba = _mm_or_si128(_mm_shuffle_epi8(rgb, expand), alphaMask); + + // Store 4 pixels. + _mm_storeu_si128((__m128i*) dst, rgba); + + src += 4*3; + dst += 4; + count -= 4; + } + + // Call portable code to finish up the tail of [0,4) pixels. + auto proc = kSwapRB ? RGB_to_BGR1_portable : RGB_to_RGB1_portable; + proc(dst, src, count); +} + +/*not static*/ inline void RGB_to_RGB1(uint32_t dst[], const uint8_t* src, int count) { + insert_alpha_should_swaprb(dst, src, count); +} + +/*not static*/ inline void RGB_to_BGR1(uint32_t dst[], const uint8_t* src, int count) { + insert_alpha_should_swaprb(dst, src, count); +} + +/*not static*/ inline void gray_to_RGB1(uint32_t dst[], const uint8_t* src, int count) { + const __m128i alphas = _mm_set1_epi8((uint8_t) 0xFF); + while (count >= 16) { + __m128i grays = _mm_loadu_si128((const __m128i*) src); + + __m128i gg_lo = _mm_unpacklo_epi8(grays, grays); + __m128i gg_hi = _mm_unpackhi_epi8(grays, grays); + __m128i ga_lo = _mm_unpacklo_epi8(grays, alphas); + __m128i ga_hi = _mm_unpackhi_epi8(grays, alphas); + + __m128i ggga0 = _mm_unpacklo_epi16(gg_lo, ga_lo); + __m128i ggga1 = _mm_unpackhi_epi16(gg_lo, ga_lo); + __m128i ggga2 = _mm_unpacklo_epi16(gg_hi, ga_hi); + __m128i ggga3 = _mm_unpackhi_epi16(gg_hi, ga_hi); + + _mm_storeu_si128((__m128i*) (dst + 0), ggga0); + _mm_storeu_si128((__m128i*) (dst + 4), ggga1); + _mm_storeu_si128((__m128i*) (dst + 8), ggga2); + _mm_storeu_si128((__m128i*) (dst + 12), ggga3); + + src += 16; + dst += 16; + count -= 16; + } + + gray_to_RGB1_portable(dst, src, count); +} + +/*not static*/ inline void grayA_to_RGBA(uint32_t dst[], const uint8_t* src, int count) { + while (count >= 8) { + __m128i ga = _mm_loadu_si128((const __m128i*) src); + + __m128i gg = _mm_or_si128(_mm_and_si128(ga, _mm_set1_epi16(0x00FF)), + _mm_slli_epi16(ga, 8)); + + __m128i ggga_lo = _mm_unpacklo_epi16(gg, ga); + __m128i ggga_hi = _mm_unpackhi_epi16(gg, ga); + + _mm_storeu_si128((__m128i*) (dst + 0), ggga_lo); + _mm_storeu_si128((__m128i*) (dst + 4), ggga_hi); + + src += 8*2; + dst += 8; + count -= 8; + } + + grayA_to_RGBA_portable(dst, src, count); +} + +/*not static*/ inline void grayA_to_rgbA(uint32_t dst[], const uint8_t* src, int count) { + while (count >= 8) { + __m128i grayA = _mm_loadu_si128((const __m128i*) src); + + __m128i g0 = _mm_and_si128(grayA, _mm_set1_epi16(0x00FF)); + __m128i a0 = _mm_srli_epi16(grayA, 8); + + // Premultiply + g0 = scale(g0, a0); + + __m128i gg = _mm_or_si128(g0, _mm_slli_epi16(g0, 8)); + __m128i ga = _mm_or_si128(g0, _mm_slli_epi16(a0, 8)); + + + __m128i ggga_lo = _mm_unpacklo_epi16(gg, ga); + __m128i ggga_hi = _mm_unpackhi_epi16(gg, ga); + + _mm_storeu_si128((__m128i*) (dst + 0), ggga_lo); + _mm_storeu_si128((__m128i*) (dst + 4), ggga_hi); + + src += 8*2; + dst += 8; + count -= 8; + } + + grayA_to_rgbA_portable(dst, src, count); +} + +enum Format { kRGB1, kBGR1 }; +template +static void inverted_cmyk_to(uint32_t* dst, const uint32_t* src, int count) { + auto convert8 = [](__m128i* lo, __m128i* hi) { + const __m128i zeros = _mm_setzero_si128(); + __m128i planar; + if (kBGR1 == format) { + planar = _mm_setr_epi8(2,6,10,14, 1,5,9,13, 0,4,8,12, 3,7,11,15); + } else { + planar = _mm_setr_epi8(0,4,8,12, 1,5,9,13, 2,6,10,14, 3,7,11,15); + } + + // Swizzle the pixels to 8-bit planar. + *lo = _mm_shuffle_epi8(*lo, planar); // ccccmmmm yyyykkkk + *hi = _mm_shuffle_epi8(*hi, planar); // CCCCMMMM YYYYKKKK + __m128i cm = _mm_unpacklo_epi32(*lo, *hi), // ccccCCCC mmmmMMMM + yk = _mm_unpackhi_epi32(*lo, *hi); // yyyyYYYY kkkkKKKK + + // Unpack to 16-bit planar. + __m128i c = _mm_unpacklo_epi8(cm, zeros), // c_c_c_c_ C_C_C_C_ + m = _mm_unpackhi_epi8(cm, zeros), // m_m_m_m_ M_M_M_M_ + y = _mm_unpacklo_epi8(yk, zeros), // y_y_y_y_ Y_Y_Y_Y_ + k = _mm_unpackhi_epi8(yk, zeros); // k_k_k_k_ K_K_K_K_ + + // Scale to r, g, b. + __m128i r = scale(c, k), + g = scale(m, k), + b = scale(y, k); + + // Repack into interlaced pixels. + __m128i rg = _mm_or_si128(r, _mm_slli_epi16(g, 8)), // rgrgrgrg RGRGRGRG + ba = _mm_or_si128(b, _mm_set1_epi16((uint16_t) 0xFF00)); // b1b1b1b1 B1B1B1B1 + *lo = _mm_unpacklo_epi16(rg, ba); // rgbargba rgbargba + *hi = _mm_unpackhi_epi16(rg, ba); // RGB1RGB1 RGB1RGB1 + }; + + while (count >= 8) { + __m128i lo = _mm_loadu_si128((const __m128i*) (src + 0)), + hi = _mm_loadu_si128((const __m128i*) (src + 4)); + + convert8(&lo, &hi); + + _mm_storeu_si128((__m128i*) (dst + 0), lo); + _mm_storeu_si128((__m128i*) (dst + 4), hi); + + src += 8; + dst += 8; + count -= 8; + } + + if (count >= 4) { + __m128i lo = _mm_loadu_si128((const __m128i*) src), + hi = _mm_setzero_si128(); + + convert8(&lo, &hi); + + _mm_storeu_si128((__m128i*) dst, lo); + + src += 4; + dst += 4; + count -= 4; + } + + auto proc = (kBGR1 == format) ? inverted_CMYK_to_BGR1_portable : inverted_CMYK_to_RGB1_portable; + proc(dst, src, count); +} + +/*not static*/ inline void inverted_CMYK_to_RGB1(uint32_t dst[], const uint32_t* src, int count) { + inverted_cmyk_to(dst, src, count); +} + +/*not static*/ inline void inverted_CMYK_to_BGR1(uint32_t dst[], const uint32_t* src, int count) { + inverted_cmyk_to(dst, src, count); +} + +#else + +/*not static*/ inline void RGBA_to_rgbA(uint32_t* dst, const uint32_t* src, int count) { + RGBA_to_rgbA_portable(dst, src, count); +} + +/*not static*/ inline void RGBA_to_bgrA(uint32_t* dst, const uint32_t* src, int count) { + RGBA_to_bgrA_portable(dst, src, count); +} + +/*not static*/ inline void RGBA_to_BGRA(uint32_t* dst, const uint32_t* src, int count) { + RGBA_to_BGRA_portable(dst, src, count); +} + +/*not static*/ inline void RGB_to_RGB1(uint32_t dst[], const uint8_t* src, int count) { + RGB_to_RGB1_portable(dst, src, count); +} + +/*not static*/ inline void RGB_to_BGR1(uint32_t dst[], const uint8_t* src, int count) { + RGB_to_BGR1_portable(dst, src, count); +} + +/*not static*/ inline void gray_to_RGB1(uint32_t dst[], const uint8_t* src, int count) { + gray_to_RGB1_portable(dst, src, count); +} + +/*not static*/ inline void grayA_to_RGBA(uint32_t dst[], const uint8_t* src, int count) { + grayA_to_RGBA_portable(dst, src, count); +} + +/*not static*/ inline void grayA_to_rgbA(uint32_t dst[], const uint8_t* src, int count) { + grayA_to_rgbA_portable(dst, src, count); +} + +/*not static*/ inline void inverted_CMYK_to_RGB1(uint32_t dst[], const uint32_t* src, int count) { + inverted_CMYK_to_RGB1_portable(dst, src, count); +} + +/*not static*/ inline void inverted_CMYK_to_BGR1(uint32_t dst[], const uint32_t* src, int count) { + inverted_CMYK_to_BGR1_portable(dst, src, count); +} + +#endif + +} + +#endif // SkSwizzler_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkUtils_opts.h b/gfx/skia/skia/src/opts/SkUtils_opts.h new file mode 100644 index 0000000000..6605457b17 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkUtils_opts.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkUtils_opts_DEFINED +#define SkUtils_opts_DEFINED + +#include +#include "include/private/SkNx.h" + +namespace SK_OPTS_NS { + + template + static void memsetT(T buffer[], T value, int count) { + #if defined(SK_CPU_SSE_LEVEL) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX + static const int N = 32 / sizeof(T); + #else + static const int N = 16 / sizeof(T); + #endif + while (count >= N) { + SkNx(value).store(buffer); + buffer += N; + count -= N; + } + while (count --> 0) { + *buffer++ = value; + } + } + + /*not static*/ inline void memset16(uint16_t buffer[], uint16_t value, int count) { + memsetT(buffer, value, count); + } + /*not static*/ inline void memset32(uint32_t buffer[], uint32_t value, int count) { + memsetT(buffer, value, count); + } + /*not static*/ inline void memset64(uint64_t buffer[], uint64_t value, int count) { + memsetT(buffer, value, count); + } + + template + static void rect_memsetT(T buffer[], T value, int count, size_t rowBytes, int height) { + while (height --> 0) { + memsetT(buffer, value, count); + buffer = (T*)((char*)buffer + rowBytes); + } + } + + /*not static*/ inline void rect_memset16(uint16_t buffer[], uint16_t value, int count, + size_t rowBytes, int height) { + rect_memsetT(buffer, value, count, rowBytes, height); + } + /*not static*/ inline void rect_memset32(uint32_t buffer[], uint32_t value, int count, + size_t rowBytes, int height) { + rect_memsetT(buffer, value, count, rowBytes, height); + } + /*not static*/ inline void rect_memset64(uint64_t buffer[], uint64_t value, int count, + size_t rowBytes, int height) { + rect_memsetT(buffer, value, count, rowBytes, height); + } + +} + +#endif//SkUtils_opts_DEFINED diff --git a/gfx/skia/skia/src/opts/SkXfermode_opts.h b/gfx/skia/skia/src/opts/SkXfermode_opts.h new file mode 100644 index 0000000000..577d6e2538 --- /dev/null +++ b/gfx/skia/skia/src/opts/SkXfermode_opts.h @@ -0,0 +1,138 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4pxXfermode_DEFINED +#define Sk4pxXfermode_DEFINED + +#include "include/private/SkNx.h" +#include "src/core/Sk4px.h" +#include "src/core/SkMSAN.h" +#include "src/core/SkXfermodePriv.h" + +#ifdef SK_FORCE_RASTER_PIPELINE_BLITTER + +namespace SK_OPTS_NS { + /*not static*/ inline SkXfermode* create_xfermode(SkBlendMode) { return nullptr; } +} + +#else + +namespace { // NOLINT(google-build-namespaces) + +// Most xfermodes can be done most efficiently 4 pixels at a time in 8 or 16-bit fixed point. +#define XFERMODE(Xfermode) \ + struct Xfermode { Sk4px operator()(const Sk4px&, const Sk4px&) const; }; \ + inline Sk4px Xfermode::operator()(const Sk4px& d, const Sk4px& s) const + +XFERMODE(Clear) { return Sk4px::DupPMColor(0); } +XFERMODE(Src) { return s; } +XFERMODE(Dst) { return d; } +XFERMODE(SrcIn) { return s.approxMulDiv255(d.alphas() ); } +XFERMODE(SrcOut) { return s.approxMulDiv255(d.alphas().inv()); } +XFERMODE(SrcOver) { return s + d.approxMulDiv255(s.alphas().inv()); } +XFERMODE(DstIn) { return SrcIn ()(s,d); } +XFERMODE(DstOut) { return SrcOut ()(s,d); } +XFERMODE(DstOver) { return SrcOver()(s,d); } + +// [ S * Da + (1 - Sa) * D] +XFERMODE(SrcATop) { return (s * d.alphas() + d * s.alphas().inv()).div255(); } +XFERMODE(DstATop) { return SrcATop()(s,d); } +//[ S * (1 - Da) + (1 - Sa) * D ] +XFERMODE(Xor) { return (s * d.alphas().inv() + d * s.alphas().inv()).div255(); } +// [S + D ] +XFERMODE(Plus) { return s.saturatedAdd(d); } +// [S * D ] +XFERMODE(Modulate) { return s.approxMulDiv255(d); } +// [S + D - S * D] +XFERMODE(Screen) { + // Doing the math as S + (1-S)*D or S + (D - S*D) means the add and subtract can be done + // in 8-bit space without overflow. S + (1-S)*D is a touch faster because inv() is cheap. + return s + d.approxMulDiv255(s.inv()); +} + +#undef XFERMODE + +// A reasonable fallback mode for doing AA is to simply apply the transfermode first, +// then linearly interpolate the AA. +template +static Sk4px xfer_aa(const Sk4px& d, const Sk4px& s, const Sk4px& aa) { + Sk4px bw = Xfermode()(d, s); + return (bw * aa + d * aa.inv()).div255(); +} + +// For some transfermodes we specialize AA, either for correctness or performance. +#define XFERMODE_AA(Xfermode) \ + template <> Sk4px xfer_aa(const Sk4px& d, const Sk4px& s, const Sk4px& aa) + +// Plus' clamp needs to happen after AA. skia:3852 +XFERMODE_AA(Plus) { // [ clamp( (1-AA)D + (AA)(S+D) ) == clamp(D + AA*S) ] + return d.saturatedAdd(s.approxMulDiv255(aa)); +} + +#undef XFERMODE_AA + +// Src and Clear modes are safe to use with unitialized dst buffers, +// even if the implementation branches based on bytes from dst (e.g. asserts in Debug mode). +// For those modes, just lie to MSAN that dst is always intialized. +template static void mark_dst_initialized_if_safe(void*, void*) {} +template <> void mark_dst_initialized_if_safe(void* dst, void* end) { + sk_msan_mark_initialized(dst, end, "Src doesn't read dst."); +} +template <> void mark_dst_initialized_if_safe(void* dst, void* end) { + sk_msan_mark_initialized(dst, end, "Clear doesn't read dst."); +} + +template +class Sk4pxXfermode : public SkXfermode { +public: + Sk4pxXfermode() {} + + void xfer32(SkPMColor dst[], const SkPMColor src[], int n, const SkAlpha aa[]) const override { + mark_dst_initialized_if_safe(dst, dst+n); + if (nullptr == aa) { + Sk4px::MapDstSrc(n, dst, src, Xfermode()); + } else { + Sk4px::MapDstSrcAlpha(n, dst, src, aa, xfer_aa); + } + } +}; + +} // namespace + +namespace SK_OPTS_NS { + +/*not static*/ inline SkXfermode* create_xfermode(SkBlendMode mode) { + switch (mode) { +#define CASE(Xfermode) \ + case SkBlendMode::k##Xfermode: return new Sk4pxXfermode() + CASE(Clear); + CASE(Src); + CASE(Dst); + CASE(SrcOver); + CASE(DstOver); + CASE(SrcIn); + CASE(DstIn); + CASE(SrcOut); + CASE(DstOut); + CASE(SrcATop); + CASE(DstATop); + CASE(Xor); + CASE(Plus); + CASE(Modulate); + CASE(Screen); + #undef CASE + + default: break; + } + return nullptr; +} + +} // namespace SK_OPTS_NS + +#endif // #ifdef SK_FORCE_RASTER_PIPELINE_BLITTER + +#endif//Sk4pxXfermode_DEFINED diff --git a/gfx/skia/skia/src/pathops/SkAddIntersections.cpp b/gfx/skia/skia/src/pathops/SkAddIntersections.cpp new file mode 100644 index 0000000000..75de64e8c8 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkAddIntersections.cpp @@ -0,0 +1,579 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkAddIntersections.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkPathOpsBounds.h" + +#include + +#if DEBUG_ADD_INTERSECTING_TS + +static void debugShowLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " LINE_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, LINE_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " LINE_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], LINE_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + if (pts == 2) { + SkDebugf(" " T_DEBUG_STR(wtTs, 1) " " PT_DEBUG_STR, i[0][1], PT_DEBUG_DATA(i, 1)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + if (pts == 2) { + SkDebugf(" " T_DEBUG_STR(wnTs, 1), i[1][1]); + } + SkDebugf("\n"); +} + +static void debugShowQuadLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, + const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " QUAD_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " QUAD_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowConicLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CONIC_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowConicQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CONIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowConicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CONIC_DEBUG_STR " " CONIC_DEBUG_STR "\n", + __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), + CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CONIC_DEBUG_STR, i[1][0], CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicConicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CONIC_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CONIC_DEBUG_STR, i[1][0], CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CUBIC_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CUBIC_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CUBIC_DEBUG_STR, i[1][0], CUBIC_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +#else +static void debugShowLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowQuadLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowConicLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowConicQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowConicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicConicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} +#endif + +bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence) { + if (test != next) { + if (AlmostLessUlps(test->bounds().fBottom, next->bounds().fTop)) { + return false; + } + // OPTIMIZATION: outset contour bounds a smidgen instead? + if (!SkPathOpsBounds::Intersects(test->bounds(), next->bounds())) { + return true; + } + } + SkIntersectionHelper wt; + wt.init(test); + do { + SkIntersectionHelper wn; + wn.init(next); + test->debugValidate(); + next->debugValidate(); + if (test == next && !wn.startAfter(wt)) { + continue; + } + do { + if (!SkPathOpsBounds::Intersects(wt.bounds(), wn.bounds())) { + continue; + } + int pts = 0; + SkIntersections ts { SkDEBUGCODE(test->globalState()) }; + bool swap = false; + SkDQuad quad1, quad2; + SkDConic conic1, conic2; + SkDCubic cubic1, cubic2; + switch (wt.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + case SkIntersectionHelper::kVerticalLine_Segment: + case SkIntersectionHelper::kLine_Segment: + pts = ts.lineHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kQuad_Segment: + pts = ts.quadHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kConic_Segment: + pts = ts.conicHorizontal(wn.pts(), wn.weight(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowConicLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kCubic_Segment: + pts = ts.cubicHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kVerticalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + case SkIntersectionHelper::kVerticalLine_Segment: + case SkIntersectionHelper::kLine_Segment: { + pts = ts.lineVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.quadVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + pts = ts.conicVertical(wn.pts(), wn.weight(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowConicLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.cubicVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kLine_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.lineHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.lineVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.lineLine(wt.pts(), wn.pts()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: + swap = true; + pts = ts.quadLine(wn.pts(), wt.pts()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kConic_Segment: + swap = true; + pts = ts.conicLine(wn.pts(), wn.weight(), wt.pts()); + debugShowConicLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kCubic_Segment: + swap = true; + pts = ts.cubicLine(wn.pts(), wt.pts()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kQuad_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.quadHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.quadVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.quadLine(wt.pts(), wn.pts()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.intersect(quad1.set(wt.pts()), quad2.set(wn.pts())); + debugShowQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + swap = true; + pts = ts.intersect(conic2.set(wn.pts(), wn.weight()), + quad1.set(wt.pts())); + debugShowConicQuadIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + swap = true; + pts = ts.intersect(cubic2.set(wn.pts()), quad1.set(wt.pts())); + debugShowCubicQuadIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kConic_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.conicHorizontal(wt.pts(), wt.weight(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowConicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.conicVertical(wt.pts(), wt.weight(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowConicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.conicLine(wt.pts(), wt.weight(), wn.pts()); + debugShowConicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.intersect(conic1.set(wt.pts(), wt.weight()), + quad2.set(wn.pts())); + debugShowConicQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + pts = ts.intersect(conic1.set(wt.pts(), wt.weight()), + conic2.set(wn.pts(), wn.weight())); + debugShowConicIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + swap = true; + pts = ts.intersect(cubic2.set(wn.pts() + SkDEBUGPARAMS(ts.globalState())), + conic1.set(wt.pts(), wt.weight() + SkDEBUGPARAMS(ts.globalState()))); + debugShowCubicConicIntersection(pts, wn, wt, ts); + break; + } + } + break; + case SkIntersectionHelper::kCubic_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.cubicHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.cubicVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.cubicLine(wt.pts(), wn.pts()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.intersect(cubic1.set(wt.pts()), quad2.set(wn.pts())); + debugShowCubicQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + pts = ts.intersect(cubic1.set(wt.pts() + SkDEBUGPARAMS(ts.globalState())), + conic2.set(wn.pts(), wn.weight() + SkDEBUGPARAMS(ts.globalState()))); + debugShowCubicConicIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.intersect(cubic1.set(wt.pts()), cubic2.set(wn.pts())); + debugShowCubicIntersection(pts, wt, wn, ts); + break; + } + default: + SkASSERT(0); + } + break; + default: + SkASSERT(0); + } +#if DEBUG_T_SECT_LOOP_COUNT + test->globalState()->debugAddLoopCount(&ts, wt, wn); +#endif + int coinIndex = -1; + SkOpPtT* coinPtT[2]; + for (int pt = 0; pt < pts; ++pt) { + SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1); + SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1); + wt.segment()->debugValidate(); + // if t value is used to compute pt in addT, error may creep in and + // rect intersections may result in non-rects. if pt value from intersection + // is passed in, current tests break. As a workaround, pass in pt + // value from intersection only if pt.x and pt.y is integral + SkPoint iPt = ts.pt(pt).asSkPoint(); + bool iPtIsIntegral = iPt.fX == floor(iPt.fX) && iPt.fY == floor(iPt.fY); + SkOpPtT* testTAt = iPtIsIntegral ? wt.segment()->addT(ts[swap][pt], iPt) + : wt.segment()->addT(ts[swap][pt]); + wn.segment()->debugValidate(); + SkOpPtT* nextTAt = iPtIsIntegral ? wn.segment()->addT(ts[!swap][pt], iPt) + : wn.segment()->addT(ts[!swap][pt]); + if (!testTAt->contains(nextTAt)) { + SkOpPtT* oppPrev = testTAt->oppPrev(nextTAt); // Returns nullptr if pair + if (oppPrev) { // already share a pt-t loop. + testTAt->span()->mergeMatches(nextTAt->span()); + testTAt->addOpp(nextTAt, oppPrev); + } + if (testTAt->fPt != nextTAt->fPt) { + testTAt->span()->unaligned(); + nextTAt->span()->unaligned(); + } + wt.segment()->debugValidate(); + wn.segment()->debugValidate(); + } + if (!ts.isCoincident(pt)) { + continue; + } + if (coinIndex < 0) { + coinPtT[0] = testTAt; + coinPtT[1] = nextTAt; + coinIndex = pt; + continue; + } + if (coinPtT[0]->span() == testTAt->span()) { + coinIndex = -1; + continue; + } + if (coinPtT[1]->span() == nextTAt->span()) { + coinIndex = -1; // coincidence span collapsed + continue; + } + if (swap) { + using std::swap; + swap(coinPtT[0], coinPtT[1]); + swap(testTAt, nextTAt); + } + SkASSERT(coincidence->globalState()->debugSkipAssert() + || coinPtT[0]->span()->t() < testTAt->span()->t()); + if (coinPtT[0]->span()->deleted()) { + coinIndex = -1; + continue; + } + if (testTAt->span()->deleted()) { + coinIndex = -1; + continue; + } + coincidence->add(coinPtT[0], testTAt, coinPtT[1], nextTAt); + wt.segment()->debugValidate(); + wn.segment()->debugValidate(); + coinIndex = -1; + } + SkOPOBJASSERT(coincidence, coinIndex < 0); // expect coincidence to be paired + } while (wn.advance()); + } while (wt.advance()); + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkAddIntersections.h b/gfx/skia/skia/src/pathops/SkAddIntersections.h new file mode 100644 index 0000000000..b48493bf54 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkAddIntersections.h @@ -0,0 +1,17 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkAddIntersections_DEFINED +#define SkAddIntersections_DEFINED + +#include "src/pathops/SkIntersectionHelper.h" +#include "src/pathops/SkIntersections.h" + +class SkOpCoincidence; + +bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence); + +#endif diff --git a/gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp new file mode 100644 index 0000000000..6a9eb4b76c --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp @@ -0,0 +1,384 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkPathOpsConic.h" +#include "src/pathops/SkPathOpsCurve.h" +#include "src/pathops/SkPathOpsLine.h" + +class LineConicIntersections { +public: + enum PinTPoint { + kPointUninitialized, + kPointInitialized + }; + + LineConicIntersections(const SkDConic& c, const SkDLine& l, SkIntersections* i) + : fConic(c) + , fLine(&l) + , fIntersections(i) + , fAllowNear(true) { + i->setMax(4); // allow short partial coincidence plus discrete intersection + } + + LineConicIntersections(const SkDConic& c) + : fConic(c) + SkDEBUGPARAMS(fLine(nullptr)) + SkDEBUGPARAMS(fIntersections(nullptr)) + SkDEBUGPARAMS(fAllowNear(false)) { + } + + void allowNear(bool allow) { + fAllowNear = allow; + } + + void checkCoincident() { + int last = fIntersections->used() - 1; + for (int index = 0; index < last; ) { + double conicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; + SkDPoint conicMidPt = fConic.ptAtT(conicMidT); + double t = fLine->nearPoint(conicMidPt, nullptr); + if (t < 0) { + ++index; + continue; + } + if (fIntersections->isCoincident(index)) { + fIntersections->removeOne(index); + --last; + } else if (fIntersections->isCoincident(index + 1)) { + fIntersections->removeOne(index + 1); + --last; + } else { + fIntersections->setCoincident(index++); + } + fIntersections->setCoincident(index); + } + } + +#ifdef SK_DEBUG + static bool close_to(double a, double b, const double c[3]) { + double max = SkTMax(-SkTMin(SkTMin(c[0], c[1]), c[2]), SkTMax(SkTMax(c[0], c[1]), c[2])); + return approximately_zero_when_compared_to(a - b, max); + } +#endif + int horizontalIntersect(double axisIntercept, double roots[2]) { + double conicVals[] = { fConic[0].fY, fConic[1].fY, fConic[2].fY }; + return this->validT(conicVals, axisIntercept, roots); + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + this->addExactHorizontalEndPoints(left, right, axisIntercept); + if (fAllowNear) { + this->addNearHorizontalEndPoints(left, right, axisIntercept); + } + double roots[2]; + int count = this->horizontalIntersect(axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double conicT = roots[index]; + SkDPoint pt = fConic.ptAtT(conicT); + SkDEBUGCODE(double conicVals[] = { fConic[0].fY, fConic[1].fY, fConic[2].fY }); + SkOPOBJASSERT(fIntersections, close_to(pt.fY, axisIntercept, conicVals)); + double lineT = (pt.fX - left) / (right - left); + if (this->pinTs(&conicT, &lineT, &pt, kPointInitialized) + && this->uniqueAnswer(conicT, pt)) { + fIntersections->insert(conicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + this->checkCoincident(); + return fIntersections->used(); + } + + int intersect() { + this->addExactEndPoints(); + if (fAllowNear) { + this->addNearEndPoints(); + } + double rootVals[2]; + int roots = this->intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double conicT = rootVals[index]; + double lineT = this->findLineT(conicT); +#ifdef SK_DEBUG + if (!fIntersections->globalState() + || !fIntersections->globalState()->debugSkipAssert()) { + SkDEBUGCODE(SkDPoint conicPt = fConic.ptAtT(conicT)); + SkDEBUGCODE(SkDPoint linePt = fLine->ptAtT(lineT)); + SkASSERT(conicPt.approximatelyDEqual(linePt)); + } +#endif + SkDPoint pt; + if (this->pinTs(&conicT, &lineT, &pt, kPointUninitialized) + && this->uniqueAnswer(conicT, pt)) { + fIntersections->insert(conicT, lineT, pt); + } + } + this->checkCoincident(); + return fIntersections->used(); + } + + int intersectRay(double roots[2]) { + double adj = (*fLine)[1].fX - (*fLine)[0].fX; + double opp = (*fLine)[1].fY - (*fLine)[0].fY; + double r[3]; + for (int n = 0; n < 3; ++n) { + r[n] = (fConic[n].fY - (*fLine)[0].fY) * adj - (fConic[n].fX - (*fLine)[0].fX) * opp; + } + return this->validT(r, 0, roots); + } + + int validT(double r[3], double axisIntercept, double roots[2]) { + double A = r[2]; + double B = r[1] * fConic.fWeight - axisIntercept * fConic.fWeight + axisIntercept; + double C = r[0]; + A += C - 2 * B; // A = a + c - 2*(b*w - xCept*w + xCept) + B -= C; // B = b*w - w * xCept + xCept - a + C -= axisIntercept; + return SkDQuad::RootsValidT(A, 2 * B, C, roots); + } + + int verticalIntersect(double axisIntercept, double roots[2]) { + double conicVals[] = { fConic[0].fX, fConic[1].fX, fConic[2].fX }; + return this->validT(conicVals, axisIntercept, roots); + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + this->addExactVerticalEndPoints(top, bottom, axisIntercept); + if (fAllowNear) { + this->addNearVerticalEndPoints(top, bottom, axisIntercept); + } + double roots[2]; + int count = this->verticalIntersect(axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double conicT = roots[index]; + SkDPoint pt = fConic.ptAtT(conicT); + SkDEBUGCODE(double conicVals[] = { fConic[0].fX, fConic[1].fX, fConic[2].fX }); + SkOPOBJASSERT(fIntersections, close_to(pt.fX, axisIntercept, conicVals)); + double lineT = (pt.fY - top) / (bottom - top); + if (this->pinTs(&conicT, &lineT, &pt, kPointInitialized) + && this->uniqueAnswer(conicT, pt)) { + fIntersections->insert(conicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + this->checkCoincident(); + return fIntersections->used(); + } + +protected: +// OPTIMIZE: Functions of the form add .. points are indentical to the conic routines. + // add endpoints first to get zero and one t values exactly + void addExactEndPoints() { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double lineT = fLine->exactPoint(fConic[cIndex]); + if (lineT < 0) { + continue; + } + double conicT = (double) (cIndex >> 1); + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + } + + void addNearEndPoints() { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double conicT = (double) (cIndex >> 1); + if (fIntersections->hasT(conicT)) { + continue; + } + double lineT = fLine->nearPoint(fConic[cIndex], nullptr); + if (lineT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double conicT = ((SkDCurve*) &fConic)->nearPoint(SkPath::kConic_Verb, + (*fLine)[lIndex], (*fLine)[!lIndex]); + if (conicT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, (*fLine)[lIndex]); + } + } + + void addExactHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double lineT = SkDLine::ExactPointH(fConic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + double conicT = (double) (cIndex >> 1); + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + } + + void addNearHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double conicT = (double) (cIndex >> 1); + if (fIntersections->hasT(conicT)) { + continue; + } + double lineT = SkDLine::NearPointH(fConic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addExactVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double lineT = SkDLine::ExactPointV(fConic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + double conicT = (double) (cIndex >> 1); + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + } + + void addNearVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double conicT = (double) (cIndex >> 1); + if (fIntersections->hasT(conicT)) { + continue; + } + double lineT = SkDLine::NearPointV(fConic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + this->addLineNearEndPoints(); + } + + double findLineT(double t) { + SkDPoint xy = fConic.ptAtT(t); + double dx = (*fLine)[1].fX - (*fLine)[0].fX; + double dy = (*fLine)[1].fY - (*fLine)[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - (*fLine)[0].fX) / dx; + } + return (xy.fY - (*fLine)[0].fY) / dy; + } + + bool pinTs(double* conicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { + if (!approximately_one_or_less_double(*lineT)) { + return false; + } + if (!approximately_zero_or_more_double(*lineT)) { + return false; + } + double qT = *conicT = SkPinT(*conicT); + double lT = *lineT = SkPinT(*lineT); + if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) { + *pt = (*fLine).ptAtT(lT); + } else if (ptSet == kPointUninitialized) { + *pt = fConic.ptAtT(qT); + } + SkPoint gridPt = pt->asSkPoint(); + if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[0].asSkPoint())) { + *pt = (*fLine)[0]; + *lineT = 0; + } else if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[1].asSkPoint())) { + *pt = (*fLine)[1]; + *lineT = 1; + } + if (fIntersections->used() > 0 && approximately_equal((*fIntersections)[1][0], *lineT)) { + return false; + } + if (gridPt == fConic[0].asSkPoint()) { + *pt = fConic[0]; + *conicT = 0; + } else if (gridPt == fConic[2].asSkPoint()) { + *pt = fConic[2]; + *conicT = 1; + } + return true; + } + + bool uniqueAnswer(double conicT, const SkDPoint& pt) { + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingConicT = (*fIntersections)[0][inner]; + if (conicT == existingConicT) { + return false; + } + // check if midway on conic is also same point. If so, discard this + double conicMidT = (existingConicT + conicT) / 2; + SkDPoint conicMidPt = fConic.ptAtT(conicMidT); + if (conicMidPt.approximatelyEqual(pt)) { + return false; + } + } +#if ONE_OFF_DEBUG + SkDPoint qPt = fConic.ptAtT(conicT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + qPt.fX, qPt.fY); +#endif + return true; + } + +private: + const SkDConic& fConic; + const SkDLine* fLine; + SkIntersections* fIntersections; + bool fAllowNear; +}; + +int SkIntersections::horizontal(const SkDConic& conic, double left, double right, double y, + bool flipped) { + SkDLine line = {{{ left, y }, { right, y }}}; + LineConicIntersections c(conic, line, this); + return c.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDConic& conic, double top, double bottom, double x, + bool flipped) { + SkDLine line = {{{ x, top }, { x, bottom }}}; + LineConicIntersections c(conic, line, this); + return c.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDConic& conic, const SkDLine& line) { + LineConicIntersections c(conic, line, this); + c.allowNear(fAllowNear); + return c.intersect(); +} + +int SkIntersections::intersectRay(const SkDConic& conic, const SkDLine& line) { + LineConicIntersections c(conic, line, this); + fUsed = c.intersectRay(fT[0]); + for (int index = 0; index < fUsed; ++index) { + fPt[index] = conic.ptAtT(fT[0][index]); + } + return fUsed; +} + +int SkIntersections::HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots) { + LineConicIntersections c(conic); + return c.horizontalIntersect(y, roots); +} + +int SkIntersections::VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots) { + LineConicIntersections c(conic); + return c.verticalIntersect(x, roots); +} diff --git a/gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp new file mode 100644 index 0000000000..98cc03d5c0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp @@ -0,0 +1,455 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsCurve.h" +#include "src/pathops/SkPathOpsLine.h" + +/* +Find the interection of a line and cubic by solving for valid t values. + +Analogous to line-quadratic intersection, solve line-cubic intersection by +representing the cubic as: + x = a(1-t)^3 + 2b(1-t)^2t + c(1-t)t^2 + dt^3 + y = e(1-t)^3 + 2f(1-t)^2t + g(1-t)t^2 + ht^3 +and the line as: + y = i*x + j (if the line is more horizontal) +or: + x = i*y + j (if the line is more vertical) + +Then using Mathematica, solve for the values of t where the cubic intersects the +line: + + (in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - x, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - i*x - j, x] + (out) -e + j + + 3 e t - 3 f t - + 3 e t^2 + 6 f t^2 - 3 g t^2 + + e t^3 - 3 f t^3 + 3 g t^3 - h t^3 + + i ( a - + 3 a t + 3 b t + + 3 a t^2 - 6 b t^2 + 3 c t^2 - + a t^3 + 3 b t^3 - 3 c t^3 + d t^3 ) + +if i goes to infinity, we can rewrite the line in terms of x. Mathematica: + + (in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - i*y - j, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] + (out) a - j - + 3 a t + 3 b t + + 3 a t^2 - 6 b t^2 + 3 c t^2 - + a t^3 + 3 b t^3 - 3 c t^3 + d t^3 - + i ( e - + 3 e t + 3 f t + + 3 e t^2 - 6 f t^2 + 3 g t^2 - + e t^3 + 3 f t^3 - 3 g t^3 + h t^3 ) + +Solving this with Mathematica produces an expression with hundreds of terms; +instead, use Numeric Solutions recipe to solve the cubic. + +The near-horizontal case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 + A = (-(-e + 3*f - 3*g + h) + i*(-a + 3*b - 3*c + d) ) + B = 3*(-( e - 2*f + g ) + i*( a - 2*b + c ) ) + C = 3*(-(-e + f ) + i*(-a + b ) ) + D = (-( e ) + i*( a ) + j ) + +The near-vertical case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 + A = ( (-a + 3*b - 3*c + d) - i*(-e + 3*f - 3*g + h) ) + B = 3*( ( a - 2*b + c ) - i*( e - 2*f + g ) ) + C = 3*( (-a + b ) - i*(-e + f ) ) + D = ( ( a ) - i*( e ) - j ) + +For horizontal lines: +(in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - j, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] +(out) e - j - + 3 e t + 3 f t + + 3 e t^2 - 6 f t^2 + 3 g t^2 - + e t^3 + 3 f t^3 - 3 g t^3 + h t^3 + */ + +class LineCubicIntersections { +public: + enum PinTPoint { + kPointUninitialized, + kPointInitialized + }; + + LineCubicIntersections(const SkDCubic& c, const SkDLine& l, SkIntersections* i) + : fCubic(c) + , fLine(l) + , fIntersections(i) + , fAllowNear(true) { + i->setMax(4); + } + + void allowNear(bool allow) { + fAllowNear = allow; + } + + void checkCoincident() { + int last = fIntersections->used() - 1; + for (int index = 0; index < last; ) { + double cubicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; + SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); + double t = fLine.nearPoint(cubicMidPt, nullptr); + if (t < 0) { + ++index; + continue; + } + if (fIntersections->isCoincident(index)) { + fIntersections->removeOne(index); + --last; + } else if (fIntersections->isCoincident(index + 1)) { + fIntersections->removeOne(index + 1); + --last; + } else { + fIntersections->setCoincident(index++); + } + fIntersections->setCoincident(index); + } + } + + // see parallel routine in line quadratic intersections + int intersectRay(double roots[3]) { + double adj = fLine[1].fX - fLine[0].fX; + double opp = fLine[1].fY - fLine[0].fY; + SkDCubic c; + SkDEBUGCODE(c.fDebugGlobalState = fIntersections->globalState()); + for (int n = 0; n < 4; ++n) { + c[n].fX = (fCubic[n].fY - fLine[0].fY) * adj - (fCubic[n].fX - fLine[0].fX) * opp; + } + double A, B, C, D; + SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); + int count = SkDCubic::RootsValidT(A, B, C, D, roots); + for (int index = 0; index < count; ++index) { + SkDPoint calcPt = c.ptAtT(roots[index]); + if (!approximately_zero(calcPt.fX)) { + for (int n = 0; n < 4; ++n) { + c[n].fY = (fCubic[n].fY - fLine[0].fY) * opp + + (fCubic[n].fX - fLine[0].fX) * adj; + } + double extremeTs[6]; + int extrema = SkDCubic::FindExtrema(&c[0].fX, extremeTs); + count = c.searchRoots(extremeTs, extrema, 0, SkDCubic::kXAxis, roots); + break; + } + } + return count; + } + + int intersect() { + addExactEndPoints(); + if (fAllowNear) { + addNearEndPoints(); + } + double rootVals[3]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double cubicT = rootVals[index]; + double lineT = findLineT(cubicT); + SkDPoint pt; + if (pinTs(&cubicT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(cubicT, pt)) { + fIntersections->insert(cubicT, lineT, pt); + } + } + checkCoincident(); + return fIntersections->used(); + } + + static int HorizontalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { + double A, B, C, D; + SkDCubic::Coefficients(&c[0].fY, &A, &B, &C, &D); + D -= axisIntercept; + int count = SkDCubic::RootsValidT(A, B, C, D, roots); + for (int index = 0; index < count; ++index) { + SkDPoint calcPt = c.ptAtT(roots[index]); + if (!approximately_equal(calcPt.fY, axisIntercept)) { + double extremeTs[6]; + int extrema = SkDCubic::FindExtrema(&c[0].fY, extremeTs); + count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kYAxis, roots); + break; + } + } + return count; + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + addExactHorizontalEndPoints(left, right, axisIntercept); + if (fAllowNear) { + addNearHorizontalEndPoints(left, right, axisIntercept); + } + double roots[3]; + int count = HorizontalIntersect(fCubic, axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double cubicT = roots[index]; + SkDPoint pt = { fCubic.ptAtT(cubicT).fX, axisIntercept }; + double lineT = (pt.fX - left) / (right - left); + if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { + fIntersections->insert(cubicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + + bool uniqueAnswer(double cubicT, const SkDPoint& pt) { + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingCubicT = (*fIntersections)[0][inner]; + if (cubicT == existingCubicT) { + return false; + } + // check if midway on cubic is also same point. If so, discard this + double cubicMidT = (existingCubicT + cubicT) / 2; + SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); + if (cubicMidPt.approximatelyEqual(pt)) { + return false; + } + } +#if ONE_OFF_DEBUG + SkDPoint cPt = fCubic.ptAtT(cubicT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + cPt.fX, cPt.fY); +#endif + return true; + } + + static int VerticalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { + double A, B, C, D; + SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); + D -= axisIntercept; + int count = SkDCubic::RootsValidT(A, B, C, D, roots); + for (int index = 0; index < count; ++index) { + SkDPoint calcPt = c.ptAtT(roots[index]); + if (!approximately_equal(calcPt.fX, axisIntercept)) { + double extremeTs[6]; + int extrema = SkDCubic::FindExtrema(&c[0].fX, extremeTs); + count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kXAxis, roots); + break; + } + } + return count; + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + addExactVerticalEndPoints(top, bottom, axisIntercept); + if (fAllowNear) { + addNearVerticalEndPoints(top, bottom, axisIntercept); + } + double roots[3]; + int count = VerticalIntersect(fCubic, axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double cubicT = roots[index]; + SkDPoint pt = { axisIntercept, fCubic.ptAtT(cubicT).fY }; + double lineT = (pt.fY - top) / (bottom - top); + if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { + fIntersections->insert(cubicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + + protected: + + void addExactEndPoints() { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double lineT = fLine.exactPoint(fCubic[cIndex]); + if (lineT < 0) { + continue; + } + double cubicT = (double) (cIndex >> 1); + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + } + + /* Note that this does not look for endpoints of the line that are near the cubic. + These points are found later when check ends looks for missing points */ + void addNearEndPoints() { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double cubicT = (double) (cIndex >> 1); + if (fIntersections->hasT(cubicT)) { + continue; + } + double lineT = fLine.nearPoint(fCubic[cIndex], nullptr); + if (lineT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double cubicT = ((SkDCurve*) &fCubic)->nearPoint(SkPath::kCubic_Verb, + fLine[lIndex], fLine[!lIndex]); + if (cubicT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fLine[lIndex]); + } + } + + void addExactHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double lineT = SkDLine::ExactPointH(fCubic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + double cubicT = (double) (cIndex >> 1); + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + } + + void addNearHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double cubicT = (double) (cIndex >> 1); + if (fIntersections->hasT(cubicT)) { + continue; + } + double lineT = SkDLine::NearPointH(fCubic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addExactVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double lineT = SkDLine::ExactPointV(fCubic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + double cubicT = (double) (cIndex >> 1); + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + } + + void addNearVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double cubicT = (double) (cIndex >> 1); + if (fIntersections->hasT(cubicT)) { + continue; + } + double lineT = SkDLine::NearPointV(fCubic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + this->addLineNearEndPoints(); + } + + double findLineT(double t) { + SkDPoint xy = fCubic.ptAtT(t); + double dx = fLine[1].fX - fLine[0].fX; + double dy = fLine[1].fY - fLine[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - fLine[0].fX) / dx; + } + return (xy.fY - fLine[0].fY) / dy; + } + + bool pinTs(double* cubicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { + if (!approximately_one_or_less(*lineT)) { + return false; + } + if (!approximately_zero_or_more(*lineT)) { + return false; + } + double cT = *cubicT = SkPinT(*cubicT); + double lT = *lineT = SkPinT(*lineT); + SkDPoint lPt = fLine.ptAtT(lT); + SkDPoint cPt = fCubic.ptAtT(cT); + if (!lPt.roughlyEqual(cPt)) { + return false; + } + // FIXME: if points are roughly equal but not approximately equal, need to do + // a binary search like quad/quad intersection to find more precise t values + if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && cT != 0 && cT != 1)) { + *pt = lPt; + } else if (ptSet == kPointUninitialized) { + *pt = cPt; + } + SkPoint gridPt = pt->asSkPoint(); + if (gridPt == fLine[0].asSkPoint()) { + *lineT = 0; + } else if (gridPt == fLine[1].asSkPoint()) { + *lineT = 1; + } + if (gridPt == fCubic[0].asSkPoint() && approximately_equal(*cubicT, 0)) { + *cubicT = 0; + } else if (gridPt == fCubic[3].asSkPoint() && approximately_equal(*cubicT, 1)) { + *cubicT = 1; + } + return true; + } + +private: + const SkDCubic& fCubic; + const SkDLine& fLine; + SkIntersections* fIntersections; + bool fAllowNear; +}; + +int SkIntersections::horizontal(const SkDCubic& cubic, double left, double right, double y, + bool flipped) { + SkDLine line = {{{ left, y }, { right, y }}}; + LineCubicIntersections c(cubic, line, this); + return c.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDCubic& cubic, double top, double bottom, double x, + bool flipped) { + SkDLine line = {{{ x, top }, { x, bottom }}}; + LineCubicIntersections c(cubic, line, this); + return c.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDCubic& cubic, const SkDLine& line) { + LineCubicIntersections c(cubic, line, this); + c.allowNear(fAllowNear); + return c.intersect(); +} + +int SkIntersections::intersectRay(const SkDCubic& cubic, const SkDLine& line) { + LineCubicIntersections c(cubic, line, this); + fUsed = c.intersectRay(fT[0]); + for (int index = 0; index < fUsed; ++index) { + fPt[index] = cubic.ptAtT(fT[0][index]); + } + return fUsed; +} + +// SkDCubic accessors to Intersection utilities + +int SkDCubic::horizontalIntersect(double yIntercept, double roots[3]) const { + return LineCubicIntersections::HorizontalIntersect(*this, yIntercept, roots); +} + +int SkDCubic::verticalIntersect(double xIntercept, double roots[3]) const { + return LineCubicIntersections::VerticalIntersect(*this, xIntercept, roots); +} diff --git a/gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp b/gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp new file mode 100644 index 0000000000..8f5c6fbde5 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* +http://stackoverflow.com/questions/2009160/how-do-i-convert-the-2-control-points-of-a-cubic-curve-to-the-single-control-poi +*/ + +/* +Let's call the control points of the cubic Q0..Q3 and the control points of the quadratic P0..P2. +Then for degree elevation, the equations are: + +Q0 = P0 +Q1 = 1/3 P0 + 2/3 P1 +Q2 = 2/3 P1 + 1/3 P2 +Q3 = P2 +In your case you have Q0..Q3 and you're solving for P0..P2. There are two ways to compute P1 from + the equations above: + +P1 = 3/2 Q1 - 1/2 Q0 +P1 = 3/2 Q2 - 1/2 Q3 +If this is a degree-elevated cubic, then both equations will give the same answer for P1. Since + it's likely not, your best bet is to average them. So, + +P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3 +*/ + +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsQuad.h" + +// used for testing only +SkDQuad SkDCubic::toQuad() const { + SkDQuad quad; + quad[0] = fPts[0]; + const SkDPoint fromC1 = {(3 * fPts[1].fX - fPts[0].fX) / 2, (3 * fPts[1].fY - fPts[0].fY) / 2}; + const SkDPoint fromC2 = {(3 * fPts[2].fX - fPts[3].fX) / 2, (3 * fPts[2].fY - fPts[3].fY) / 2}; + quad[1].fX = (fromC1.fX + fromC2.fX) / 2; + quad[1].fY = (fromC1.fY + fromC2.fY) / 2; + quad[2] = fPts[3]; + return quad; +} diff --git a/gfx/skia/skia/src/pathops/SkDLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDLineIntersection.cpp new file mode 100644 index 0000000000..83fe84cd90 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDLineIntersection.cpp @@ -0,0 +1,339 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkPathOpsLine.h" + +#include + +void SkIntersections::cleanUpParallelLines(bool parallel) { + while (fUsed > 2) { + removeOne(1); + } + if (fUsed == 2 && !parallel) { + bool startMatch = fT[0][0] == 0 || zero_or_one(fT[1][0]); + bool endMatch = fT[0][1] == 1 || zero_or_one(fT[1][1]); + if ((!startMatch && !endMatch) || approximately_equal(fT[0][0], fT[0][1])) { + SkASSERT(startMatch || endMatch); + if (startMatch && endMatch && (fT[0][0] != 0 || !zero_or_one(fT[1][0])) + && fT[0][1] == 1 && zero_or_one(fT[1][1])) { + removeOne(0); + } else { + removeOne(endMatch); + } + } + } + if (fUsed == 2) { + fIsCoincident[0] = fIsCoincident[1] = 0x03; + } +} + +void SkIntersections::computePoints(const SkDLine& line, int used) { + fPt[0] = line.ptAtT(fT[0][0]); + if ((fUsed = used) == 2) { + fPt[1] = line.ptAtT(fT[0][1]); + } +} + +int SkIntersections::intersectRay(const SkDLine& a, const SkDLine& b) { + fMax = 2; + SkDVector aLen = a[1] - a[0]; + SkDVector bLen = b[1] - b[0]; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen == 0 ( == denom ) + */ + double denom = bLen.fY * aLen.fX - aLen.fY * bLen.fX; + int used; + if (!approximately_zero(denom)) { + SkDVector ab0 = a[0] - b[0]; + double numerA = ab0.fY * bLen.fX - bLen.fY * ab0.fX; + double numerB = ab0.fY * aLen.fX - aLen.fY * ab0.fX; + numerA /= denom; + numerB /= denom; + fT[0][0] = numerA; + fT[1][0] = numerB; + used = 1; + } else { + /* See if the axis intercepts match: + ay - ax * ayLen / axLen == by - bx * ayLen / axLen + axLen * (ay - ax * ayLen / axLen) == axLen * (by - bx * ayLen / axLen) + axLen * ay - ax * ayLen == axLen * by - bx * ayLen + */ + if (!AlmostEqualUlps(aLen.fX * a[0].fY - aLen.fY * a[0].fX, + aLen.fX * b[0].fY - aLen.fY * b[0].fX)) { + return fUsed = 0; + } + // there's no great answer for intersection points for coincident rays, but return something + fT[0][0] = fT[1][0] = 0; + fT[1][0] = fT[1][1] = 1; + used = 2; + } + computePoints(a, used); + return fUsed; +} + +// note that this only works if both lines are neither horizontal nor vertical +int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) { + fMax = 3; // note that we clean up so that there is no more than two in the end + // see if end points intersect the opposite line + double t; + for (int iA = 0; iA < 2; ++iA) { + if ((t = b.exactPoint(a[iA])) >= 0) { + insert(iA, t, a[iA]); + } + } + for (int iB = 0; iB < 2; ++iB) { + if ((t = a.exactPoint(b[iB])) >= 0) { + insert(t, iB, b[iB]); + } + } + /* Determine the intersection point of two line segments + Return FALSE if the lines don't intersect + from: http://paulbourke.net/geometry/lineline2d/ */ + double axLen = a[1].fX - a[0].fX; + double ayLen = a[1].fY - a[0].fY; + double bxLen = b[1].fX - b[0].fX; + double byLen = b[1].fY - b[0].fY; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen == 0 ( == denom ) + */ + double axByLen = axLen * byLen; + double ayBxLen = ayLen * bxLen; + // detect parallel lines the same way here and in SkOpAngle operator < + // so that non-parallel means they are also sortable + bool unparallel = fAllowNear ? NotAlmostEqualUlps_Pin(axByLen, ayBxLen) + : NotAlmostDequalUlps(axByLen, ayBxLen); + if (unparallel && fUsed == 0) { + double ab0y = a[0].fY - b[0].fY; + double ab0x = a[0].fX - b[0].fX; + double numerA = ab0y * bxLen - byLen * ab0x; + double numerB = ab0y * axLen - ayLen * ab0x; + double denom = axByLen - ayBxLen; + if (between(0, numerA, denom) && between(0, numerB, denom)) { + fT[0][0] = numerA / denom; + fT[1][0] = numerB / denom; + computePoints(a, 1); + } + } +/* Allow tracking that both sets of end points are near each other -- the lines are entirely + coincident -- even when the end points are not exactly the same. + Mark this as a 'wild card' for the end points, so that either point is considered totally + coincident. Then, avoid folding the lines over each other, but allow either end to mate + to the next set of lines. + */ + if (fAllowNear || !unparallel) { + double aNearB[2]; + double bNearA[2]; + bool aNotB[2] = {false, false}; + bool bNotA[2] = {false, false}; + int nearCount = 0; + for (int index = 0; index < 2; ++index) { + aNearB[index] = t = b.nearPoint(a[index], &aNotB[index]); + nearCount += t >= 0; + bNearA[index] = t = a.nearPoint(b[index], &bNotA[index]); + nearCount += t >= 0; + } + if (nearCount > 0) { + // Skip if each segment contributes to one end point. + if (nearCount != 2 || aNotB[0] == aNotB[1]) { + for (int iA = 0; iA < 2; ++iA) { + if (!aNotB[iA]) { + continue; + } + int nearer = aNearB[iA] > 0.5; + if (!bNotA[nearer]) { + continue; + } + SkASSERT(a[iA] != b[nearer]); + SkOPASSERT(iA == (bNearA[nearer] > 0.5)); + insertNear(iA, nearer, a[iA], b[nearer]); + aNearB[iA] = -1; + bNearA[nearer] = -1; + nearCount -= 2; + } + } + if (nearCount > 0) { + for (int iA = 0; iA < 2; ++iA) { + if (aNearB[iA] >= 0) { + insert(iA, aNearB[iA], a[iA]); + } + } + for (int iB = 0; iB < 2; ++iB) { + if (bNearA[iB] >= 0) { + insert(bNearA[iB], iB, b[iB]); + } + } + } + } + } + cleanUpParallelLines(!unparallel); + SkASSERT(fUsed <= 2); + return fUsed; +} + +static int horizontal_coincident(const SkDLine& line, double y) { + double min = line[0].fY; + double max = line[1].fY; + if (min > max) { + using std::swap; + swap(min, max); + } + if (min > y || max < y) { + return 0; + } + if (AlmostEqualUlps(min, max) && max - min < fabs(line[0].fX - line[1].fX)) { + return 2; + } + return 1; +} + +double SkIntersections::HorizontalIntercept(const SkDLine& line, double y) { + SkASSERT(line[1].fY != line[0].fY); + return SkPinT((y - line[0].fY) / (line[1].fY - line[0].fY)); +} + +int SkIntersections::horizontal(const SkDLine& line, double left, double right, + double y, bool flipped) { + fMax = 3; // clean up parallel at the end will limit the result to 2 at the most + // see if end points intersect the opposite line + double t; + const SkDPoint leftPt = { left, y }; + if ((t = line.exactPoint(leftPt)) >= 0) { + insert(t, (double) flipped, leftPt); + } + if (left != right) { + const SkDPoint rightPt = { right, y }; + if ((t = line.exactPoint(rightPt)) >= 0) { + insert(t, (double) !flipped, rightPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::ExactPointH(line[index], left, right, y)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + int result = horizontal_coincident(line, y); + if (result == 1 && fUsed == 0) { + fT[0][0] = HorizontalIntercept(line, y); + double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX); + if (between(left, xIntercept, right)) { + fT[1][0] = (xIntercept - left) / (right - left); + if (flipped) { + // OPTIMIZATION: ? instead of swapping, pass original line, use [1].fX - [0].fX + for (int index = 0; index < result; ++index) { + fT[1][index] = 1 - fT[1][index]; + } + } + fPt[0].fX = xIntercept; + fPt[0].fY = y; + fUsed = 1; + } + } + if (fAllowNear || result == 2) { + if ((t = line.nearPoint(leftPt, nullptr)) >= 0) { + insert(t, (double) flipped, leftPt); + } + if (left != right) { + const SkDPoint rightPt = { right, y }; + if ((t = line.nearPoint(rightPt, nullptr)) >= 0) { + insert(t, (double) !flipped, rightPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::NearPointH(line[index], left, right, y)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + } + cleanUpParallelLines(result == 2); + return fUsed; +} + +static int vertical_coincident(const SkDLine& line, double x) { + double min = line[0].fX; + double max = line[1].fX; + if (min > max) { + using std::swap; + swap(min, max); + } + if (!precisely_between(min, x, max)) { + return 0; + } + if (AlmostEqualUlps(min, max)) { + return 2; + } + return 1; +} + +double SkIntersections::VerticalIntercept(const SkDLine& line, double x) { + SkASSERT(line[1].fX != line[0].fX); + return SkPinT((x - line[0].fX) / (line[1].fX - line[0].fX)); +} + +int SkIntersections::vertical(const SkDLine& line, double top, double bottom, + double x, bool flipped) { + fMax = 3; // cleanup parallel lines will bring this back line + // see if end points intersect the opposite line + double t; + SkDPoint topPt = { x, top }; + if ((t = line.exactPoint(topPt)) >= 0) { + insert(t, (double) flipped, topPt); + } + if (top != bottom) { + SkDPoint bottomPt = { x, bottom }; + if ((t = line.exactPoint(bottomPt)) >= 0) { + insert(t, (double) !flipped, bottomPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::ExactPointV(line[index], top, bottom, x)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + int result = vertical_coincident(line, x); + if (result == 1 && fUsed == 0) { + fT[0][0] = VerticalIntercept(line, x); + double yIntercept = line[0].fY + fT[0][0] * (line[1].fY - line[0].fY); + if (between(top, yIntercept, bottom)) { + fT[1][0] = (yIntercept - top) / (bottom - top); + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].fY - [0].fY + for (int index = 0; index < result; ++index) { + fT[1][index] = 1 - fT[1][index]; + } + } + fPt[0].fX = x; + fPt[0].fY = yIntercept; + fUsed = 1; + } + } + if (fAllowNear || result == 2) { + if ((t = line.nearPoint(topPt, nullptr)) >= 0) { + insert(t, (double) flipped, topPt); + } + if (top != bottom) { + SkDPoint bottomPt = { x, bottom }; + if ((t = line.nearPoint(bottomPt, nullptr)) >= 0) { + insert(t, (double) !flipped, bottomPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::NearPointV(line[index], top, bottom, x)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + } + cleanUpParallelLines(result == 2); + SkASSERT(fUsed <= 2); + return fUsed; +} + diff --git a/gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp new file mode 100644 index 0000000000..95c56132a0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp @@ -0,0 +1,470 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkPathOpsCurve.h" +#include "src/pathops/SkPathOpsLine.h" +#include "src/pathops/SkPathOpsQuad.h" + +/* +Find the interection of a line and quadratic by solving for valid t values. + +From http://stackoverflow.com/questions/1853637/how-to-find-the-mathematical-function-defining-a-bezier-curve + +"A Bezier curve is a parametric function. A quadratic Bezier curve (i.e. three +control points) can be expressed as: F(t) = A(1 - t)^2 + B(1 - t)t + Ct^2 where +A, B and C are points and t goes from zero to one. + +This will give you two equations: + + x = a(1 - t)^2 + b(1 - t)t + ct^2 + y = d(1 - t)^2 + e(1 - t)t + ft^2 + +If you add for instance the line equation (y = kx + m) to that, you'll end up +with three equations and three unknowns (x, y and t)." + +Similar to above, the quadratic is represented as + x = a(1-t)^2 + 2b(1-t)t + ct^2 + y = d(1-t)^2 + 2e(1-t)t + ft^2 +and the line as + y = g*x + h + +Using Mathematica, solve for the values of t where the quadratic intersects the +line: + + (in) t1 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - x, + d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - g*x - h, x] + (out) -d + h + 2 d t - 2 e t - d t^2 + 2 e t^2 - f t^2 + + g (a - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2) + (in) Solve[t1 == 0, t] + (out) { + {t -> (-2 d + 2 e + 2 a g - 2 b g - + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - + 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / + (2 (-d + 2 e - f + a g - 2 b g + c g)) + }, + {t -> (-2 d + 2 e + 2 a g - 2 b g + + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - + 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / + (2 (-d + 2 e - f + a g - 2 b g + c g)) + } + } + +Using the results above (when the line tends towards horizontal) + A = (-(d - 2*e + f) + g*(a - 2*b + c) ) + B = 2*( (d - e ) - g*(a - b ) ) + C = (-(d ) + g*(a ) + h ) + +If g goes to infinity, we can rewrite the line in terms of x. + x = g'*y + h' + +And solve accordingly in Mathematica: + + (in) t2 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - g'*y - h', + d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - y, y] + (out) a - h' - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2 - + g' (d - 2 d t + 2 e t + d t^2 - 2 e t^2 + f t^2) + (in) Solve[t2 == 0, t] + (out) { + {t -> (2 a - 2 b - 2 d g' + 2 e g' - + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - + 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')]) / + (2 (a - 2 b + c - d g' + 2 e g' - f g')) + }, + {t -> (2 a - 2 b - 2 d g' + 2 e g' + + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - + 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')])/ + (2 (a - 2 b + c - d g' + 2 e g' - f g')) + } + } + +Thus, if the slope of the line tends towards vertical, we use: + A = ( (a - 2*b + c) - g'*(d - 2*e + f) ) + B = 2*(-(a - b ) + g'*(d - e ) ) + C = ( (a ) - g'*(d ) - h' ) + */ + +class LineQuadraticIntersections { +public: + enum PinTPoint { + kPointUninitialized, + kPointInitialized + }; + + LineQuadraticIntersections(const SkDQuad& q, const SkDLine& l, SkIntersections* i) + : fQuad(q) + , fLine(&l) + , fIntersections(i) + , fAllowNear(true) { + i->setMax(5); // allow short partial coincidence plus discrete intersections + } + + LineQuadraticIntersections(const SkDQuad& q) + : fQuad(q) + SkDEBUGPARAMS(fLine(nullptr)) + SkDEBUGPARAMS(fIntersections(nullptr)) + SkDEBUGPARAMS(fAllowNear(false)) { + } + + void allowNear(bool allow) { + fAllowNear = allow; + } + + void checkCoincident() { + int last = fIntersections->used() - 1; + for (int index = 0; index < last; ) { + double quadMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; + SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); + double t = fLine->nearPoint(quadMidPt, nullptr); + if (t < 0) { + ++index; + continue; + } + if (fIntersections->isCoincident(index)) { + fIntersections->removeOne(index); + --last; + } else if (fIntersections->isCoincident(index + 1)) { + fIntersections->removeOne(index + 1); + --last; + } else { + fIntersections->setCoincident(index++); + } + fIntersections->setCoincident(index); + } + } + + int intersectRay(double roots[2]) { + /* + solve by rotating line+quad so line is horizontal, then finding the roots + set up matrix to rotate quad to x-axis + |cos(a) -sin(a)| + |sin(a) cos(a)| + note that cos(a) = A(djacent) / Hypoteneuse + sin(a) = O(pposite) / Hypoteneuse + since we are computing Ts, we can ignore hypoteneuse, the scale factor: + | A -O | + | O A | + A = line[1].fX - line[0].fX (adjacent side of the right triangle) + O = line[1].fY - line[0].fY (opposite side of the right triangle) + for each of the three points (e.g. n = 0 to 2) + quad[n].fY' = (quad[n].fY - line[0].fY) * A - (quad[n].fX - line[0].fX) * O + */ + double adj = (*fLine)[1].fX - (*fLine)[0].fX; + double opp = (*fLine)[1].fY - (*fLine)[0].fY; + double r[3]; + for (int n = 0; n < 3; ++n) { + r[n] = (fQuad[n].fY - (*fLine)[0].fY) * adj - (fQuad[n].fX - (*fLine)[0].fX) * opp; + } + double A = r[2]; + double B = r[1]; + double C = r[0]; + A += C - 2 * B; // A = a - 2*b + c + B -= C; // B = -(b - c) + return SkDQuad::RootsValidT(A, 2 * B, C, roots); + } + + int intersect() { + addExactEndPoints(); + if (fAllowNear) { + addNearEndPoints(); + } + double rootVals[2]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + double lineT = findLineT(quadT); + SkDPoint pt; + if (pinTs(&quadT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(quadT, pt)) { + fIntersections->insert(quadT, lineT, pt); + } + } + checkCoincident(); + return fIntersections->used(); + } + + int horizontalIntersect(double axisIntercept, double roots[2]) { + double D = fQuad[2].fY; // f + double E = fQuad[1].fY; // e + double F = fQuad[0].fY; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return SkDQuad::RootsValidT(D, 2 * E, F, roots); + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + addExactHorizontalEndPoints(left, right, axisIntercept); + if (fAllowNear) { + addNearHorizontalEndPoints(left, right, axisIntercept); + } + double rootVals[2]; + int roots = horizontalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + SkDPoint pt = fQuad.ptAtT(quadT); + double lineT = (pt.fX - left) / (right - left); + if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { + fIntersections->insert(quadT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + + bool uniqueAnswer(double quadT, const SkDPoint& pt) { + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingQuadT = (*fIntersections)[0][inner]; + if (quadT == existingQuadT) { + return false; + } + // check if midway on quad is also same point. If so, discard this + double quadMidT = (existingQuadT + quadT) / 2; + SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); + if (quadMidPt.approximatelyEqual(pt)) { + return false; + } + } +#if ONE_OFF_DEBUG + SkDPoint qPt = fQuad.ptAtT(quadT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + qPt.fX, qPt.fY); +#endif + return true; + } + + int verticalIntersect(double axisIntercept, double roots[2]) { + double D = fQuad[2].fX; // f + double E = fQuad[1].fX; // e + double F = fQuad[0].fX; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return SkDQuad::RootsValidT(D, 2 * E, F, roots); + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + addExactVerticalEndPoints(top, bottom, axisIntercept); + if (fAllowNear) { + addNearVerticalEndPoints(top, bottom, axisIntercept); + } + double rootVals[2]; + int roots = verticalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + SkDPoint pt = fQuad.ptAtT(quadT); + double lineT = (pt.fY - top) / (bottom - top); + if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { + fIntersections->insert(quadT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + +protected: + // add endpoints first to get zero and one t values exactly + void addExactEndPoints() { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double lineT = fLine->exactPoint(fQuad[qIndex]); + if (lineT < 0) { + continue; + } + double quadT = (double) (qIndex >> 1); + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + } + + void addNearEndPoints() { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double quadT = (double) (qIndex >> 1); + if (fIntersections->hasT(quadT)) { + continue; + } + double lineT = fLine->nearPoint(fQuad[qIndex], nullptr); + if (lineT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double quadT = ((SkDCurve*) &fQuad)->nearPoint(SkPath::kQuad_Verb, + (*fLine)[lIndex], (*fLine)[!lIndex]); + if (quadT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, (*fLine)[lIndex]); + } + } + + void addExactHorizontalEndPoints(double left, double right, double y) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double lineT = SkDLine::ExactPointH(fQuad[qIndex], left, right, y); + if (lineT < 0) { + continue; + } + double quadT = (double) (qIndex >> 1); + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + } + + void addNearHorizontalEndPoints(double left, double right, double y) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double quadT = (double) (qIndex >> 1); + if (fIntersections->hasT(quadT)) { + continue; + } + double lineT = SkDLine::NearPointH(fQuad[qIndex], left, right, y); + if (lineT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + this->addLineNearEndPoints(); + } + + void addExactVerticalEndPoints(double top, double bottom, double x) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double lineT = SkDLine::ExactPointV(fQuad[qIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + double quadT = (double) (qIndex >> 1); + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + } + + void addNearVerticalEndPoints(double top, double bottom, double x) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double quadT = (double) (qIndex >> 1); + if (fIntersections->hasT(quadT)) { + continue; + } + double lineT = SkDLine::NearPointV(fQuad[qIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + this->addLineNearEndPoints(); + } + + double findLineT(double t) { + SkDPoint xy = fQuad.ptAtT(t); + double dx = (*fLine)[1].fX - (*fLine)[0].fX; + double dy = (*fLine)[1].fY - (*fLine)[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - (*fLine)[0].fX) / dx; + } + return (xy.fY - (*fLine)[0].fY) / dy; + } + + bool pinTs(double* quadT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { + if (!approximately_one_or_less_double(*lineT)) { + return false; + } + if (!approximately_zero_or_more_double(*lineT)) { + return false; + } + double qT = *quadT = SkPinT(*quadT); + double lT = *lineT = SkPinT(*lineT); + if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) { + *pt = (*fLine).ptAtT(lT); + } else if (ptSet == kPointUninitialized) { + *pt = fQuad.ptAtT(qT); + } + SkPoint gridPt = pt->asSkPoint(); + if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[0].asSkPoint())) { + *pt = (*fLine)[0]; + *lineT = 0; + } else if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[1].asSkPoint())) { + *pt = (*fLine)[1]; + *lineT = 1; + } + if (fIntersections->used() > 0 && approximately_equal((*fIntersections)[1][0], *lineT)) { + return false; + } + if (gridPt == fQuad[0].asSkPoint()) { + *pt = fQuad[0]; + *quadT = 0; + } else if (gridPt == fQuad[2].asSkPoint()) { + *pt = fQuad[2]; + *quadT = 1; + } + return true; + } + +private: + const SkDQuad& fQuad; + const SkDLine* fLine; + SkIntersections* fIntersections; + bool fAllowNear; +}; + +int SkIntersections::horizontal(const SkDQuad& quad, double left, double right, double y, + bool flipped) { + SkDLine line = {{{ left, y }, { right, y }}}; + LineQuadraticIntersections q(quad, line, this); + return q.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDQuad& quad, double top, double bottom, double x, + bool flipped) { + SkDLine line = {{{ x, top }, { x, bottom }}}; + LineQuadraticIntersections q(quad, line, this); + return q.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDQuad& quad, const SkDLine& line) { + LineQuadraticIntersections q(quad, line, this); + q.allowNear(fAllowNear); + return q.intersect(); +} + +int SkIntersections::intersectRay(const SkDQuad& quad, const SkDLine& line) { + LineQuadraticIntersections q(quad, line, this); + fUsed = q.intersectRay(fT[0]); + for (int index = 0; index < fUsed; ++index) { + fPt[index] = quad.ptAtT(fT[0][index]); + } + return fUsed; +} + +int SkIntersections::HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots) { + LineQuadraticIntersections q(quad); + return q.horizontalIntersect(y, roots); +} + +int SkIntersections::VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots) { + LineQuadraticIntersections q(quad); + return q.verticalIntersect(x, roots); +} + +// SkDQuad accessors to Intersection utilities + +int SkDQuad::horizontalIntersect(double yIntercept, double roots[2]) const { + return SkIntersections::HorizontalIntercept(*this, yIntercept, roots); +} + +int SkDQuad::verticalIntersect(double xIntercept, double roots[2]) const { + return SkIntersections::VerticalIntercept(*this, xIntercept, roots); +} diff --git a/gfx/skia/skia/src/pathops/SkIntersectionHelper.h b/gfx/skia/skia/src/pathops/SkIntersectionHelper.h new file mode 100644 index 0000000000..9eb7cbf807 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkIntersectionHelper.h @@ -0,0 +1,113 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIntersectionHelper_DEFINED +#define SkIntersectionHelper_DEFINED + +#include "include/core/SkPath.h" +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkOpSegment.h" + +#ifdef SK_DEBUG +#include "src/pathops/SkPathOpsPoint.h" +#endif + +class SkIntersectionHelper { +public: + enum SegmentType { + kHorizontalLine_Segment = -1, + kVerticalLine_Segment = 0, + kLine_Segment = SkPath::kLine_Verb, + kQuad_Segment = SkPath::kQuad_Verb, + kConic_Segment = SkPath::kConic_Verb, + kCubic_Segment = SkPath::kCubic_Verb, + }; + + bool advance() { + fSegment = fSegment->next(); + return fSegment != nullptr; + } + + SkScalar bottom() const { + return bounds().fBottom; + } + + const SkPathOpsBounds& bounds() const { + return fSegment->bounds(); + } + + SkOpContour* contour() const { + return fSegment->contour(); + } + + void init(SkOpContour* contour) { + fSegment = contour->first(); + } + + SkScalar left() const { + return bounds().fLeft; + } + + const SkPoint* pts() const { + return fSegment->pts(); + } + + SkScalar right() const { + return bounds().fRight; + } + + SkOpSegment* segment() const { + return fSegment; + } + + SegmentType segmentType() const { + SegmentType type = (SegmentType) fSegment->verb(); + if (type != kLine_Segment) { + return type; + } + if (fSegment->isHorizontal()) { + return kHorizontalLine_Segment; + } + if (fSegment->isVertical()) { + return kVerticalLine_Segment; + } + return kLine_Segment; + } + + bool startAfter(const SkIntersectionHelper& after) { + fSegment = after.fSegment->next(); + return fSegment != nullptr; + } + + SkScalar top() const { + return bounds().fTop; + } + + SkScalar weight() const { + return fSegment->weight(); + } + + SkScalar x() const { + return bounds().fLeft; + } + + bool xFlipped() const { + return x() != pts()[0].fX; + } + + SkScalar y() const { + return bounds().fTop; + } + + bool yFlipped() const { + return y() != pts()[0].fY; + } + +private: + SkOpSegment* fSegment; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkIntersections.cpp b/gfx/skia/skia/src/pathops/SkIntersections.cpp new file mode 100644 index 0000000000..2b1db9a45f --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkIntersections.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pathops/SkIntersections.h" + +int SkIntersections::closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, + double* closestDist) const { + int closest = -1; + *closestDist = SK_ScalarMax; + for (int index = 0; index < fUsed; ++index) { + if (!between(rangeStart, fT[0][index], rangeEnd)) { + continue; + } + const SkDPoint& iPt = fPt[index]; + double dist = testPt.distanceSquared(iPt); + if (*closestDist > dist) { + *closestDist = dist; + closest = index; + } + } + return closest; +} + +void SkIntersections::flip() { + for (int index = 0; index < fUsed; ++index) { + fT[1][index] = 1 - fT[1][index]; + } +} + +int SkIntersections::insert(double one, double two, const SkDPoint& pt) { + if (fIsCoincident[0] == 3 && between(fT[0][0], one, fT[0][1])) { + // For now, don't allow a mix of coincident and non-coincident intersections + return -1; + } + SkASSERT(fUsed <= 1 || fT[0][0] <= fT[0][1]); + int index; + for (index = 0; index < fUsed; ++index) { + double oldOne = fT[0][index]; + double oldTwo = fT[1][index]; + if (one == oldOne && two == oldTwo) { + return -1; + } + if (more_roughly_equal(oldOne, one) && more_roughly_equal(oldTwo, two)) { + if ((!precisely_zero(one) || precisely_zero(oldOne)) + && (!precisely_equal(one, 1) || precisely_equal(oldOne, 1)) + && (!precisely_zero(two) || precisely_zero(oldTwo)) + && (!precisely_equal(two, 1) || precisely_equal(oldTwo, 1))) { + return -1; + } + SkASSERT(one >= 0 && one <= 1); + SkASSERT(two >= 0 && two <= 1); + // remove this and reinsert below in case replacing would make list unsorted + int remaining = fUsed - index - 1; + memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); + int clearMask = ~((1 << index) - 1); + fIsCoincident[0] -= (fIsCoincident[0] >> 1) & clearMask; + fIsCoincident[1] -= (fIsCoincident[1] >> 1) & clearMask; + --fUsed; + break; + } + #if ONE_OFF_DEBUG + if (pt.roughlyEqual(fPt[index])) { + SkDebugf("%s t=%1.9g pts roughly equal\n", __FUNCTION__, one); + } + #endif + } + for (index = 0; index < fUsed; ++index) { + if (fT[0][index] > one) { + break; + } + } + if (fUsed >= fMax) { + SkOPASSERT(0); // FIXME : this error, if it is to be handled at runtime in release, must + // be propagated all the way back down to the caller, and return failure. + fUsed = 0; + return 0; + } + int remaining = fUsed - index; + if (remaining > 0) { + memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); + int clearMask = ~((1 << index) - 1); + fIsCoincident[0] += fIsCoincident[0] & clearMask; + fIsCoincident[1] += fIsCoincident[1] & clearMask; + } + fPt[index] = pt; + if (one < 0 || one > 1) { + return -1; + } + if (two < 0 || two > 1) { + return -1; + } + fT[0][index] = one; + fT[1][index] = two; + ++fUsed; + SkASSERT(fUsed <= SK_ARRAY_COUNT(fPt)); + return index; +} + +void SkIntersections::insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2) { + SkASSERT(one == 0 || one == 1); + SkASSERT(two == 0 || two == 1); + SkASSERT(pt1 != pt2); + fNearlySame[one ? 1 : 0] = true; + (void) insert(one, two, pt1); + fPt2[one ? 1 : 0] = pt2; +} + +int SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { + int index = insertSwap(one, two, pt); + if (index >= 0) { + setCoincident(index); + } + return index; +} + +void SkIntersections::setCoincident(int index) { + SkASSERT(index >= 0); + int bit = 1 << index; + fIsCoincident[0] |= bit; + fIsCoincident[1] |= bit; +} + +void SkIntersections::merge(const SkIntersections& a, int aIndex, const SkIntersections& b, + int bIndex) { + this->reset(); + fT[0][0] = a.fT[0][aIndex]; + fT[1][0] = b.fT[0][bIndex]; + fPt[0] = a.fPt[aIndex]; + fPt2[0] = b.fPt[bIndex]; + fUsed = 1; +} + +int SkIntersections::mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const { + int result = -1; + for (int index = 0; index < fUsed; ++index) { + if (!between(rangeStart, fT[0][index], rangeEnd)) { + continue; + } + if (result < 0) { + result = index; + continue; + } + SkDVector best = fPt[result] - origin; + SkDVector test = fPt[index] - origin; + if (test.crossCheck(best) < 0) { + result = index; + } + } + return result; +} + +void SkIntersections::removeOne(int index) { + int remaining = --fUsed - index; + if (remaining <= 0) { + return; + } + memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); +// SkASSERT(fIsCoincident[0] == 0); + int coBit = fIsCoincident[0] & (1 << index); + fIsCoincident[0] -= ((fIsCoincident[0] >> 1) & ~((1 << index) - 1)) + coBit; + SkASSERT(!(coBit ^ (fIsCoincident[1] & (1 << index)))); + fIsCoincident[1] -= ((fIsCoincident[1] >> 1) & ~((1 << index) - 1)) + coBit; +} diff --git a/gfx/skia/skia/src/pathops/SkIntersections.h b/gfx/skia/skia/src/pathops/SkIntersections.h new file mode 100644 index 0000000000..71d1c80fcc --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkIntersections.h @@ -0,0 +1,333 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIntersections_DEFINE +#define SkIntersections_DEFINE + +#include "src/pathops/SkPathOpsConic.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsLine.h" +#include "src/pathops/SkPathOpsPoint.h" +#include "src/pathops/SkPathOpsQuad.h" + +class SkIntersections { +public: + SkIntersections(SkDEBUGCODE(SkOpGlobalState* globalState = nullptr)) + : fSwap(0) +#ifdef SK_DEBUG + SkDEBUGPARAMS(fDebugGlobalState(globalState)) + , fDepth(0) +#endif + { + sk_bzero(fPt, sizeof(fPt)); + sk_bzero(fPt2, sizeof(fPt2)); + sk_bzero(fT, sizeof(fT)); + sk_bzero(fNearlySame, sizeof(fNearlySame)); +#if DEBUG_T_SECT_LOOP_COUNT + sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); +#endif + reset(); + fMax = 0; // require that the caller set the max + } + + class TArray { + public: + explicit TArray(const double ts[10]) : fTArray(ts) {} + double operator[](int n) const { + return fTArray[n]; + } + const double* fTArray; + }; + TArray operator[](int n) const { return TArray(fT[n]); } + + void allowNear(bool nearAllowed) { + fAllowNear = nearAllowed; + } + + void clearCoincidence(int index) { + SkASSERT(index >= 0); + int bit = 1 << index; + fIsCoincident[0] &= ~bit; + fIsCoincident[1] &= ~bit; + } + + int conicHorizontal(const SkPoint a[3], SkScalar weight, SkScalar left, SkScalar right, + SkScalar y, bool flipped) { + SkDConic conic; + conic.set(a, weight); + fMax = 2; + return horizontal(conic, left, right, y, flipped); + } + + int conicVertical(const SkPoint a[3], SkScalar weight, SkScalar top, SkScalar bottom, + SkScalar x, bool flipped) { + SkDConic conic; + conic.set(a, weight); + fMax = 2; + return vertical(conic, top, bottom, x, flipped); + } + + int conicLine(const SkPoint a[3], SkScalar weight, const SkPoint b[2]) { + SkDConic conic; + conic.set(a, weight); + SkDLine line; + line.set(b); + fMax = 3; // 2; permit small coincident segment + non-coincident intersection + return intersect(conic, line); + } + + int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDCubic cubic; + cubic.set(a); + fMax = 3; + return horizontal(cubic, left, right, y, flipped); + } + + int cubicVertical(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDCubic cubic; + cubic.set(a); + fMax = 3; + return vertical(cubic, top, bottom, x, flipped); + } + + int cubicLine(const SkPoint a[4], const SkPoint b[2]) { + SkDCubic cubic; + cubic.set(a); + SkDLine line; + line.set(b); + fMax = 3; + return intersect(cubic, line); + } + +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const { return fDebugGlobalState; } +#endif + + bool hasT(double t) const { + SkASSERT(t == 0 || t == 1); + return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1); + } + + bool hasOppT(double t) const { + SkASSERT(t == 0 || t == 1); + return fUsed > 0 && (fT[1][0] == t || fT[1][fUsed - 1] == t); + } + + int insertSwap(double one, double two, const SkDPoint& pt) { + if (fSwap) { + return insert(two, one, pt); + } else { + return insert(one, two, pt); + } + } + + bool isCoincident(int index) { + return (fIsCoincident[0] & 1 << index) != 0; + } + + int lineHorizontal(const SkPoint a[2], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDLine line; + line.set(a); + fMax = 2; + return horizontal(line, left, right, y, flipped); + } + + int lineVertical(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDLine line; + line.set(a); + fMax = 2; + return vertical(line, top, bottom, x, flipped); + } + + int lineLine(const SkPoint a[2], const SkPoint b[2]) { + SkDLine aLine, bLine; + aLine.set(a); + bLine.set(b); + fMax = 2; + return intersect(aLine, bLine); + } + + bool nearlySame(int index) const { + SkASSERT(index == 0 || index == 1); + return fNearlySame[index]; + } + + const SkDPoint& pt(int index) const { + return fPt[index]; + } + + const SkDPoint& pt2(int index) const { + return fPt2[index]; + } + + int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDQuad quad; + quad.set(a); + fMax = 2; + return horizontal(quad, left, right, y, flipped); + } + + int quadVertical(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDQuad quad; + quad.set(a); + fMax = 2; + return vertical(quad, top, bottom, x, flipped); + } + + int quadLine(const SkPoint a[3], const SkPoint b[2]) { + SkDQuad quad; + quad.set(a); + SkDLine line; + line.set(b); + return intersect(quad, line); + } + + // leaves swap, max alone + void reset() { + fAllowNear = true; + fUsed = 0; + sk_bzero(fIsCoincident, sizeof(fIsCoincident)); + } + + void set(bool swap, int tIndex, double t) { + fT[(int) swap][tIndex] = t; + } + + void setMax(int max) { + SkASSERT(max <= (int) SK_ARRAY_COUNT(fPt)); + fMax = max; + } + + void swap() { + fSwap ^= true; + } + + bool swapped() const { + return fSwap; + } + + int used() const { + return fUsed; + } + + void downDepth() { + SkASSERT(--fDepth >= 0); + } + + bool unBumpT(int index) { + SkASSERT(fUsed == 1); + fT[0][index] = fT[0][index] * (1 + BUMP_EPSILON * 2) - BUMP_EPSILON; + if (!between(0, fT[0][index], 1)) { + fUsed = 0; + return false; + } + return true; + } + + void upDepth() { + SkASSERT(++fDepth < 16); + } + + void alignQuadPts(const SkPoint a[3], const SkPoint b[3]); + int cleanUpCoincidence(); + int closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* dist) const; + void cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& c1, + const SkDCubic& c2); + void flip(); + int horizontal(const SkDLine&, double left, double right, double y, bool flipped); + int horizontal(const SkDQuad&, double left, double right, double y, bool flipped); + int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]); + int horizontal(const SkDCubic&, double y, double tRange[3]); + int horizontal(const SkDConic&, double left, double right, double y, bool flipped); + int horizontal(const SkDCubic&, double left, double right, double y, bool flipped); + int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]); + static double HorizontalIntercept(const SkDLine& line, double y); + static int HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots); + static int HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots); + // FIXME : does not respect swap + int insert(double one, double two, const SkDPoint& pt); + void insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2); + // start if index == 0 : end if index == 1 + int insertCoincident(double one, double two, const SkDPoint& pt); + int intersect(const SkDLine&, const SkDLine&); + int intersect(const SkDQuad&, const SkDLine&); + int intersect(const SkDQuad&, const SkDQuad&); + int intersect(const SkDConic&, const SkDLine&); + int intersect(const SkDConic&, const SkDQuad&); + int intersect(const SkDConic&, const SkDConic&); + int intersect(const SkDCubic&, const SkDLine&); + int intersect(const SkDCubic&, const SkDQuad&); + int intersect(const SkDCubic&, const SkDConic&); + int intersect(const SkDCubic&, const SkDCubic&); + int intersectRay(const SkDLine&, const SkDLine&); + int intersectRay(const SkDQuad&, const SkDLine&); + int intersectRay(const SkDConic&, const SkDLine&); + int intersectRay(const SkDCubic&, const SkDLine&); + int intersectRay(const SkTCurve& tCurve, const SkDLine& line) { + return tCurve.intersectRay(this, line); + } + + void merge(const SkIntersections& , int , const SkIntersections& , int ); + int mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const; + void removeOne(int index); + void setCoincident(int index); + int vertical(const SkDLine&, double top, double bottom, double x, bool flipped); + int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped); + int vertical(const SkDConic&, double top, double bottom, double x, bool flipped); + int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped); + static double VerticalIntercept(const SkDLine& line, double x); + static int VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots); + static int VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots); + + int depth() const { +#ifdef SK_DEBUG + return fDepth; +#else + return 0; +#endif + } + + enum DebugLoop { + kIterations_DebugLoop, + kCoinCheck_DebugLoop, + kComputePerp_DebugLoop, + }; + + void debugBumpLoopCount(DebugLoop ); + int debugCoincidentUsed() const; + int debugLoopCount(DebugLoop ) const; + void debugResetLoopCount(); + void dump() const; // implemented for testing only + +private: + bool cubicCheckCoincidence(const SkDCubic& c1, const SkDCubic& c2); + bool cubicExactEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2); + void cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& ); + void cleanUpParallelLines(bool parallel); + void computePoints(const SkDLine& line, int used); + + SkDPoint fPt[13]; // FIXME: since scans store points as SkPoint, this should also + SkDPoint fPt2[2]; // used by nearly same to store alternate intersection point + double fT[2][13]; + uint16_t fIsCoincident[2]; // bit set for each curve's coincident T + bool fNearlySame[2]; // true if end points nearly match + unsigned char fUsed; + unsigned char fMax; + bool fAllowNear; + bool fSwap; +#ifdef SK_DEBUG + SkOpGlobalState* fDebugGlobalState; + int fDepth; +#endif +#if DEBUG_T_SECT_LOOP_COUNT + int fDebugLoopCount[3]; +#endif +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkLineParameters.h b/gfx/skia/skia/src/pathops/SkLineParameters.h new file mode 100644 index 0000000000..45d1ed4ed6 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkLineParameters.h @@ -0,0 +1,181 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLineParameters_DEFINED +#define SkLineParameters_DEFINED + +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsLine.h" +#include "src/pathops/SkPathOpsQuad.h" + +// Sources +// computer-aided design - volume 22 number 9 november 1990 pp 538 - 549 +// online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf + +// This turns a line segment into a parameterized line, of the form +// ax + by + c = 0 +// When a^2 + b^2 == 1, the line is normalized. +// The distance to the line for (x, y) is d(x,y) = ax + by + c +// +// Note that the distances below are not necessarily normalized. To get the true +// distance, it's necessary to either call normalize() after xxxEndPoints(), or +// divide the result of xxxDistance() by sqrt(normalSquared()) + +class SkLineParameters { +public: + + bool cubicEndPoints(const SkDCubic& pts) { + int endIndex = 1; + cubicEndPoints(pts, 0, endIndex); + if (dy() != 0) { + return true; + } + if (dx() == 0) { + cubicEndPoints(pts, 0, ++endIndex); + SkASSERT(endIndex == 2); + if (dy() != 0) { + return true; + } + if (dx() == 0) { + cubicEndPoints(pts, 0, ++endIndex); // line + SkASSERT(endIndex == 3); + return false; + } + } + // FIXME: after switching to round sort, remove bumping fA + if (dx() < 0) { // only worry about y bias when breaking cw/ccw tie + return true; + } + // if cubic tangent is on x axis, look at next control point to break tie + // control point may be approximate, so it must move significantly to account for error + if (NotAlmostEqualUlps(pts[0].fY, pts[++endIndex].fY)) { + if (pts[0].fY > pts[endIndex].fY) { + fA = DBL_EPSILON; // push it from 0 to slightly negative (y() returns -a) + } + return true; + } + if (endIndex == 3) { + return true; + } + SkASSERT(endIndex == 2); + if (pts[0].fY > pts[3].fY) { + fA = DBL_EPSILON; // push it from 0 to slightly negative (y() returns -a) + } + return true; + } + + void cubicEndPoints(const SkDCubic& pts, int s, int e) { + fA = pts[s].fY - pts[e].fY; + fB = pts[e].fX - pts[s].fX; + fC = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; + } + + double cubicPart(const SkDCubic& part) { + cubicEndPoints(part); + if (part[0] == part[1] || ((const SkDLine& ) part[0]).nearRay(part[2])) { + return pointDistance(part[3]); + } + return pointDistance(part[2]); + } + + void lineEndPoints(const SkDLine& pts) { + fA = pts[0].fY - pts[1].fY; + fB = pts[1].fX - pts[0].fX; + fC = pts[0].fX * pts[1].fY - pts[1].fX * pts[0].fY; + } + + bool quadEndPoints(const SkDQuad& pts) { + quadEndPoints(pts, 0, 1); + if (dy() != 0) { + return true; + } + if (dx() == 0) { + quadEndPoints(pts, 0, 2); + return false; + } + if (dx() < 0) { // only worry about y bias when breaking cw/ccw tie + return true; + } + // FIXME: after switching to round sort, remove this + if (pts[0].fY > pts[2].fY) { + fA = DBL_EPSILON; + } + return true; + } + + void quadEndPoints(const SkDQuad& pts, int s, int e) { + fA = pts[s].fY - pts[e].fY; + fB = pts[e].fX - pts[s].fX; + fC = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; + } + + double quadPart(const SkDQuad& part) { + quadEndPoints(part); + return pointDistance(part[2]); + } + + double normalSquared() const { + return fA * fA + fB * fB; + } + + bool normalize() { + double normal = sqrt(normalSquared()); + if (approximately_zero(normal)) { + fA = fB = fC = 0; + return false; + } + double reciprocal = 1 / normal; + fA *= reciprocal; + fB *= reciprocal; + fC *= reciprocal; + return true; + } + + void cubicDistanceY(const SkDCubic& pts, SkDCubic& distance) const { + double oneThird = 1 / 3.0; + for (int index = 0; index < 4; ++index) { + distance[index].fX = index * oneThird; + distance[index].fY = fA * pts[index].fX + fB * pts[index].fY + fC; + } + } + + void quadDistanceY(const SkDQuad& pts, SkDQuad& distance) const { + double oneHalf = 1 / 2.0; + for (int index = 0; index < 3; ++index) { + distance[index].fX = index * oneHalf; + distance[index].fY = fA * pts[index].fX + fB * pts[index].fY + fC; + } + } + + double controlPtDistance(const SkDCubic& pts, int index) const { + SkASSERT(index == 1 || index == 2); + return fA * pts[index].fX + fB * pts[index].fY + fC; + } + + double controlPtDistance(const SkDQuad& pts) const { + return fA * pts[1].fX + fB * pts[1].fY + fC; + } + + double pointDistance(const SkDPoint& pt) const { + return fA * pt.fX + fB * pt.fY + fC; + } + + double dx() const { + return fB; + } + + double dy() const { + return -fA; + } + +private: + double fA; + double fB; + double fC; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpAngle.cpp b/gfx/skia/skia/src/pathops/SkOpAngle.cpp new file mode 100644 index 0000000000..a701d0f2fe --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpAngle.cpp @@ -0,0 +1,1141 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkTSort.h" +#include "src/pathops/SkOpAngle.h" +#include "src/pathops/SkOpSegment.h" +#include "src/pathops/SkPathOpsCurve.h" + +/* Angles are sorted counterclockwise. The smallest angle has a positive x and the smallest + positive y. The largest angle has a positive x and a zero y. */ + +#if DEBUG_ANGLE + static bool CompareResult(const char* func, SkString* bugOut, SkString* bugPart, int append, + bool compare) { + SkDebugf("%s %c %d\n", bugOut->c_str(), compare ? 'T' : 'F', append); + SkDebugf("%sPart %s\n", func, bugPart[0].c_str()); + SkDebugf("%sPart %s\n", func, bugPart[1].c_str()); + SkDebugf("%sPart %s\n", func, bugPart[2].c_str()); + return compare; + } + + #define COMPARE_RESULT(append, compare) CompareResult(__FUNCTION__, &bugOut, bugPart, append, \ + compare) +#else + #define COMPARE_RESULT(append, compare) compare +#endif + +/* quarter angle values for sector + +31 x > 0, y == 0 horizontal line (to the right) +0 x > 0, y == epsilon quad/cubic horizontal tangent eventually going +y +1 x > 0, y > 0, x > y nearer horizontal angle +2 x + e == y quad/cubic 45 going horiz +3 x > 0, y > 0, x == y 45 angle +4 x == y + e quad/cubic 45 going vert +5 x > 0, y > 0, x < y nearer vertical angle +6 x == epsilon, y > 0 quad/cubic vertical tangent eventually going +x +7 x == 0, y > 0 vertical line (to the top) + + 8 7 6 + 9 | 5 + 10 | 4 + 11 | 3 + 12 \ | / 2 + 13 | 1 + 14 | 0 + 15 --------------+------------- 31 + 16 | 30 + 17 | 29 + 18 / | \ 28 + 19 | 27 + 20 | 26 + 21 | 25 + 22 23 24 +*/ + +// return true if lh < this < rh +bool SkOpAngle::after(SkOpAngle* test) { + SkOpAngle* lh = test; + SkOpAngle* rh = lh->fNext; + SkASSERT(lh != rh); + fPart.fCurve = fOriginalCurvePart; + lh->fPart.fCurve = lh->fOriginalCurvePart; + lh->fPart.fCurve.offset(lh->segment()->verb(), fPart.fCurve[0] - lh->fPart.fCurve[0]); + rh->fPart.fCurve = rh->fOriginalCurvePart; + rh->fPart.fCurve.offset(rh->segment()->verb(), fPart.fCurve[0] - rh->fPart.fCurve[0]); + +#if DEBUG_ANGLE + SkString bugOut; + bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, + lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, + lh->fStart->t(), lh->fEnd->t(), + segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), + rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, + rh->fStart->t(), rh->fEnd->t()); + SkString bugPart[3] = { lh->debugPart(), this->debugPart(), rh->debugPart() }; +#endif + if (lh->fComputeSector && !lh->computeSector()) { + return COMPARE_RESULT(1, true); + } + if (fComputeSector && !this->computeSector()) { + return COMPARE_RESULT(2, true); + } + if (rh->fComputeSector && !rh->computeSector()) { + return COMPARE_RESULT(3, true); + } +#if DEBUG_ANGLE // reset bugOut with computed sectors + bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, + lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, + lh->fStart->t(), lh->fEnd->t(), + segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), + rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, + rh->fStart->t(), rh->fEnd->t()); +#endif + bool ltrOverlap = (lh->fSectorMask | rh->fSectorMask) & fSectorMask; + bool lrOverlap = lh->fSectorMask & rh->fSectorMask; + int lrOrder; // set to -1 if either order works + if (!lrOverlap) { // no lh/rh sector overlap + if (!ltrOverlap) { // no lh/this/rh sector overlap + return COMPARE_RESULT(4, (lh->fSectorEnd > rh->fSectorStart) + ^ (fSectorStart > lh->fSectorEnd) ^ (fSectorStart > rh->fSectorStart)); + } + int lrGap = (rh->fSectorStart - lh->fSectorStart + 32) & 0x1f; + /* A tiny change can move the start +/- 4. The order can only be determined if + lr gap is not 12 to 20 or -12 to -20. + -31 ..-21 1 + -20 ..-12 -1 + -11 .. -1 0 + 0 shouldn't get here + 11 .. 1 1 + 12 .. 20 -1 + 21 .. 31 0 + */ + lrOrder = lrGap > 20 ? 0 : lrGap > 11 ? -1 : 1; + } else { + lrOrder = lh->orderable(rh); + if (!ltrOverlap && lrOrder >= 0) { + return COMPARE_RESULT(5, !lrOrder); + } + } + int ltOrder; + SkASSERT((lh->fSectorMask & fSectorMask) || (rh->fSectorMask & fSectorMask) || -1 == lrOrder); + if (lh->fSectorMask & fSectorMask) { + ltOrder = lh->orderable(this); + } else { + int ltGap = (fSectorStart - lh->fSectorStart + 32) & 0x1f; + ltOrder = ltGap > 20 ? 0 : ltGap > 11 ? -1 : 1; + } + int trOrder; + if (rh->fSectorMask & fSectorMask) { + trOrder = this->orderable(rh); + } else { + int trGap = (rh->fSectorStart - fSectorStart + 32) & 0x1f; + trOrder = trGap > 20 ? 0 : trGap > 11 ? -1 : 1; + } + this->alignmentSameSide(lh, <Order); + this->alignmentSameSide(rh, &trOrder); + if (lrOrder >= 0 && ltOrder >= 0 && trOrder >= 0) { + return COMPARE_RESULT(7, lrOrder ? (ltOrder & trOrder) : (ltOrder | trOrder)); + } +// SkASSERT(lrOrder >= 0 || ltOrder >= 0 || trOrder >= 0); +// There's not enough information to sort. Get the pairs of angles in opposite planes. +// If an order is < 0, the pair is already in an opposite plane. Check the remaining pairs. + // FIXME : once all variants are understood, rewrite this more simply + if (ltOrder == 0 && lrOrder == 0) { + SkASSERT(trOrder < 0); + // FIXME : once this is verified to work, remove one opposite angle call + SkDEBUGCODE(bool lrOpposite = lh->oppositePlanes(rh)); + bool ltOpposite = lh->oppositePlanes(this); + SkOPASSERT(lrOpposite != ltOpposite); + return COMPARE_RESULT(8, ltOpposite); + } else if (ltOrder == 1 && trOrder == 0) { + SkASSERT(lrOrder < 0); + bool trOpposite = oppositePlanes(rh); + return COMPARE_RESULT(9, trOpposite); + } else if (lrOrder == 1 && trOrder == 1) { + SkASSERT(ltOrder < 0); +// SkDEBUGCODE(bool trOpposite = oppositePlanes(rh)); + bool lrOpposite = lh->oppositePlanes(rh); +// SkASSERT(lrOpposite != trOpposite); + return COMPARE_RESULT(10, lrOpposite); + } + // If a pair couldn't be ordered, there's not enough information to determine the sort. + // Refer to: https://docs.google.com/drawings/d/1KV-8SJTedku9fj4K6fd1SB-8divuV_uivHVsSgwXICQ + if (fUnorderable || lh->fUnorderable || rh->fUnorderable) { + // limit to lines; should work with curves, but wait for a failing test to verify + if (!fPart.isCurve() && !lh->fPart.isCurve() && !rh->fPart.isCurve()) { + // see if original raw data is orderable + // if two share a point, check if third has both points in same half plane + int ltShare = lh->fOriginalCurvePart[0] == fOriginalCurvePart[0]; + int lrShare = lh->fOriginalCurvePart[0] == rh->fOriginalCurvePart[0]; + int trShare = fOriginalCurvePart[0] == rh->fOriginalCurvePart[0]; + // if only one pair are the same, the third point touches neither of the pair + if (ltShare + lrShare + trShare == 1) { + if (lrShare) { + int ltOOrder = lh->linesOnOriginalSide(this); + int rtOOrder = rh->linesOnOriginalSide(this); + if ((rtOOrder ^ ltOOrder) == 1) { + return ltOOrder; + } + } else if (trShare) { + int tlOOrder = this->linesOnOriginalSide(lh); + int rlOOrder = rh->linesOnOriginalSide(lh); + if ((tlOOrder ^ rlOOrder) == 1) { + return rlOOrder; + } + } else { + SkASSERT(ltShare); + int trOOrder = rh->linesOnOriginalSide(this); + int lrOOrder = lh->linesOnOriginalSide(rh); + // result must be 0 and 1 or 1 and 0 to be valid + if ((lrOOrder ^ trOOrder) == 1) { + return trOOrder; + } + } + } + } + } + if (lrOrder < 0) { + if (ltOrder < 0) { + return COMPARE_RESULT(11, trOrder); + } + return COMPARE_RESULT(12, ltOrder); + } + return COMPARE_RESULT(13, !lrOrder); +} + +int SkOpAngle::lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test, + bool useOriginal) const { + double crosses[3]; + SkPath::Verb testVerb = test->segment()->verb(); + int iMax = SkPathOpsVerbToPoints(testVerb); +// SkASSERT(origin == test.fCurveHalf[0]); + const SkDCurve& testCurve = useOriginal ? test->fOriginalCurvePart : test->fPart.fCurve; + for (int index = 1; index <= iMax; ++index) { + double xy1 = line.fX * (testCurve[index].fY - origin.fY); + double xy2 = line.fY * (testCurve[index].fX - origin.fX); + crosses[index - 1] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2; + } + if (crosses[0] * crosses[1] < 0) { + return -1; + } + if (SkPath::kCubic_Verb == testVerb) { + if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) { + return -1; + } + } + if (crosses[0]) { + return crosses[0] < 0; + } + if (crosses[1]) { + return crosses[1] < 0; + } + if (SkPath::kCubic_Verb == testVerb && crosses[2]) { + return crosses[2] < 0; + } + return -2; +} + +// given a line, see if the opposite curve's convex hull is all on one side +// returns -1=not on one side 0=this CW of test 1=this CCW of test +int SkOpAngle::lineOnOneSide(const SkOpAngle* test, bool useOriginal) { + SkASSERT(!fPart.isCurve()); + SkASSERT(test->fPart.isCurve()); + SkDPoint origin = fPart.fCurve[0]; + SkDVector line = fPart.fCurve[1] - origin; + int result = this->lineOnOneSide(origin, line, test, useOriginal); + if (-2 == result) { + fUnorderable = true; + result = -1; + } + return result; +} + +// experiment works only with lines for now +int SkOpAngle::linesOnOriginalSide(const SkOpAngle* test) { + SkASSERT(!fPart.isCurve()); + SkASSERT(!test->fPart.isCurve()); + SkDPoint origin = fOriginalCurvePart[0]; + SkDVector line = fOriginalCurvePart[1] - origin; + double dots[2]; + double crosses[2]; + const SkDCurve& testCurve = test->fOriginalCurvePart; + for (int index = 0; index < 2; ++index) { + SkDVector testLine = testCurve[index] - origin; + double xy1 = line.fX * testLine.fY; + double xy2 = line.fY * testLine.fX; + dots[index] = line.fX * testLine.fX + line.fY * testLine.fY; + crosses[index] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2; + } + if (crosses[0] * crosses[1] < 0) { + return -1; + } + if (crosses[0]) { + return crosses[0] < 0; + } + if (crosses[1]) { + return crosses[1] < 0; + } + if ((!dots[0] && dots[1] < 0) || (dots[0] < 0 && !dots[1])) { + return 2; // 180 degrees apart + } + fUnorderable = true; + return -1; +} + +// To sort the angles, all curves are translated to have the same starting point. +// If the curve's control point in its original position is on one side of a compared line, +// and translated is on the opposite side, reverse the previously computed order. +void SkOpAngle::alignmentSameSide(const SkOpAngle* test, int* order) const { + if (*order < 0) { + return; + } + if (fPart.isCurve()) { + // This should support all curve types, but only bug that requires this has lines + // Turning on for curves causes existing tests to fail + return; + } + if (test->fPart.isCurve()) { + return; + } + const SkDPoint& xOrigin = test->fPart.fCurve.fLine[0]; + const SkDPoint& oOrigin = test->fOriginalCurvePart.fLine[0]; + if (xOrigin == oOrigin) { + return; + } + int iMax = SkPathOpsVerbToPoints(this->segment()->verb()); + SkDVector xLine = test->fPart.fCurve.fLine[1] - xOrigin; + SkDVector oLine = test->fOriginalCurvePart.fLine[1] - oOrigin; + for (int index = 1; index <= iMax; ++index) { + const SkDPoint& testPt = fPart.fCurve[index]; + double xCross = oLine.crossCheck(testPt - xOrigin); + double oCross = xLine.crossCheck(testPt - oOrigin); + if (oCross * xCross < 0) { + *order ^= 1; + break; + } + } +} + +bool SkOpAngle::checkCrossesZero() const { + int start = SkTMin(fSectorStart, fSectorEnd); + int end = SkTMax(fSectorStart, fSectorEnd); + bool crossesZero = end - start > 16; + return crossesZero; +} + +bool SkOpAngle::checkParallel(SkOpAngle* rh) { + SkDVector scratch[2]; + const SkDVector* sweep, * tweep; + if (this->fPart.isOrdered()) { + sweep = this->fPart.fSweep; + } else { + scratch[0] = this->fPart.fCurve[1] - this->fPart.fCurve[0]; + sweep = &scratch[0]; + } + if (rh->fPart.isOrdered()) { + tweep = rh->fPart.fSweep; + } else { + scratch[1] = rh->fPart.fCurve[1] - rh->fPart.fCurve[0]; + tweep = &scratch[1]; + } + double s0xt0 = sweep->crossCheck(*tweep); + if (tangentsDiverge(rh, s0xt0)) { + return s0xt0 < 0; + } + // compute the perpendicular to the endpoints and see where it intersects the opposite curve + // if the intersections within the t range, do a cross check on those + bool inside; + if (!fEnd->contains(rh->fEnd)) { + if (this->endToSide(rh, &inside)) { + return inside; + } + if (rh->endToSide(this, &inside)) { + return !inside; + } + } + if (this->midToSide(rh, &inside)) { + return inside; + } + if (rh->midToSide(this, &inside)) { + return !inside; + } + // compute the cross check from the mid T values (last resort) + SkDVector m0 = segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0]; + SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0]; + double m0xm1 = m0.crossCheck(m1); + if (m0xm1 == 0) { + this->fUnorderable = true; + rh->fUnorderable = true; + return true; + } + return m0xm1 < 0; +} + +// the original angle is too short to get meaningful sector information +// lengthen it until it is long enough to be meaningful or leave it unset if lengthening it +// would cause it to intersect one of the adjacent angles +bool SkOpAngle::computeSector() { + if (fComputedSector) { + return !fUnorderable; + } + fComputedSector = true; + bool stepUp = fStart->t() < fEnd->t(); + SkOpSpanBase* checkEnd = fEnd; + if (checkEnd->final() && stepUp) { + fUnorderable = true; + return false; + } + do { +// advance end + const SkOpSegment* other = checkEnd->segment(); + const SkOpSpanBase* oSpan = other->head(); + do { + if (oSpan->segment() != segment()) { + continue; + } + if (oSpan == checkEnd) { + continue; + } + if (!approximately_equal(oSpan->t(), checkEnd->t())) { + continue; + } + goto recomputeSector; + } while (!oSpan->final() && (oSpan = oSpan->upCast()->next())); + checkEnd = stepUp ? !checkEnd->final() + ? checkEnd->upCast()->next() : nullptr + : checkEnd->prev(); + } while (checkEnd); +recomputeSector: + SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head() + : checkEnd ? checkEnd->upCast()->next() : fEnd->segment()->tail(); + if (checkEnd == fEnd || computedEnd == fEnd || computedEnd == fStart) { + fUnorderable = true; + return false; + } + if (stepUp != (fStart->t() < computedEnd->t())) { + fUnorderable = true; + return false; + } + SkOpSpanBase* saveEnd = fEnd; + fComputedEnd = fEnd = computedEnd; + setSpans(); + setSector(); + fEnd = saveEnd; + return !fUnorderable; +} + +int SkOpAngle::convexHullOverlaps(const SkOpAngle* rh) { + const SkDVector* sweep = this->fPart.fSweep; + const SkDVector* tweep = rh->fPart.fSweep; + double s0xs1 = sweep[0].crossCheck(sweep[1]); + double s0xt0 = sweep[0].crossCheck(tweep[0]); + double s1xt0 = sweep[1].crossCheck(tweep[0]); + bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0; + double s0xt1 = sweep[0].crossCheck(tweep[1]); + double s1xt1 = sweep[1].crossCheck(tweep[1]); + tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0; + double t0xt1 = tweep[0].crossCheck(tweep[1]); + if (tBetweenS) { + return -1; + } + if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1 + return -1; + } + bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0; + sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0; + if (sBetweenT) { + return -1; + } + // if all of the sweeps are in the same half plane, then the order of any pair is enough + if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) { + return 0; + } + if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) { + return 1; + } + // if the outside sweeps are greater than 180 degress: + // first assume the inital tangents are the ordering + // if the midpoint direction matches the inital order, that is enough + SkDVector m0 = this->segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0]; + SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0]; + double m0xm1 = m0.crossCheck(m1); + if (s0xt0 > 0 && m0xm1 > 0) { + return 0; + } + if (s0xt0 < 0 && m0xm1 < 0) { + return 1; + } + if (tangentsDiverge(rh, s0xt0)) { + return s0xt0 < 0; + } + return m0xm1 < 0; +} + +// OPTIMIZATION: longest can all be either lazily computed here or precomputed in setup +double SkOpAngle::distEndRatio(double dist) const { + double longest = 0; + const SkOpSegment& segment = *this->segment(); + int ptCount = SkPathOpsVerbToPoints(segment.verb()); + const SkPoint* pts = segment.pts(); + for (int idx1 = 0; idx1 <= ptCount - 1; ++idx1) { + for (int idx2 = idx1 + 1; idx2 <= ptCount; ++idx2) { + if (idx1 == idx2) { + continue; + } + SkDVector v; + v.set(pts[idx2] - pts[idx1]); + double lenSq = v.lengthSquared(); + longest = SkTMax(longest, lenSq); + } + } + return sqrt(longest) / dist; +} + +bool SkOpAngle::endsIntersect(SkOpAngle* rh) { + SkPath::Verb lVerb = this->segment()->verb(); + SkPath::Verb rVerb = rh->segment()->verb(); + int lPts = SkPathOpsVerbToPoints(lVerb); + int rPts = SkPathOpsVerbToPoints(rVerb); + SkDLine rays[] = {{{this->fPart.fCurve[0], rh->fPart.fCurve[rPts]}}, + {{this->fPart.fCurve[0], this->fPart.fCurve[lPts]}}}; + if (this->fEnd->contains(rh->fEnd)) { + return checkParallel(rh); + } + double smallTs[2] = {-1, -1}; + bool limited[2] = {false, false}; + for (int index = 0; index < 2; ++index) { + SkPath::Verb cVerb = index ? rVerb : lVerb; + // if the curve is a line, then the line and the ray intersect only at their crossing + if (cVerb == SkPath::kLine_Verb) { + continue; + } + const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); + SkIntersections i; + (*CurveIntersectRay[cVerb])(segment.pts(), segment.weight(), rays[index], &i); + double tStart = index ? rh->fStart->t() : this->fStart->t(); + double tEnd = index ? rh->fComputedEnd->t() : this->fComputedEnd->t(); + bool testAscends = tStart < (index ? rh->fComputedEnd->t() : this->fComputedEnd->t()); + double t = testAscends ? 0 : 1; + for (int idx2 = 0; idx2 < i.used(); ++idx2) { + double testT = i[0][idx2]; + if (!approximately_between_orderable(tStart, testT, tEnd)) { + continue; + } + if (approximately_equal_orderable(tStart, testT)) { + continue; + } + smallTs[index] = t = testAscends ? SkTMax(t, testT) : SkTMin(t, testT); + limited[index] = approximately_equal_orderable(t, tEnd); + } + } + bool sRayLonger = false; + SkDVector sCept = {0, 0}; + double sCeptT = -1; + int sIndex = -1; + bool useIntersect = false; + for (int index = 0; index < 2; ++index) { + if (smallTs[index] < 0) { + continue; + } + const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); + const SkDPoint& dPt = segment.dPtAtT(smallTs[index]); + SkDVector cept = dPt - rays[index][0]; + // If this point is on the curve, it should have been detected earlier by ordinary + // curve intersection. This may be hard to determine in general, but for lines, + // the point could be close to or equal to its end, but shouldn't be near the start. + if ((index ? lPts : rPts) == 1) { + SkDVector total = rays[index][1] - rays[index][0]; + if (cept.lengthSquared() * 2 < total.lengthSquared()) { + continue; + } + } + SkDVector end = rays[index][1] - rays[index][0]; + if (cept.fX * end.fX < 0 || cept.fY * end.fY < 0) { + continue; + } + double rayDist = cept.length(); + double endDist = end.length(); + bool rayLonger = rayDist > endDist; + if (limited[0] && limited[1] && rayLonger) { + useIntersect = true; + sRayLonger = rayLonger; + sCept = cept; + sCeptT = smallTs[index]; + sIndex = index; + break; + } + double delta = fabs(rayDist - endDist); + double minX, minY, maxX, maxY; + minX = minY = SK_ScalarInfinity; + maxX = maxY = -SK_ScalarInfinity; + const SkDCurve& curve = index ? rh->fPart.fCurve : this->fPart.fCurve; + int ptCount = index ? rPts : lPts; + for (int idx2 = 0; idx2 <= ptCount; ++idx2) { + minX = SkTMin(minX, curve[idx2].fX); + minY = SkTMin(minY, curve[idx2].fY); + maxX = SkTMax(maxX, curve[idx2].fX); + maxY = SkTMax(maxY, curve[idx2].fY); + } + double maxWidth = SkTMax(maxX - minX, maxY - minY); + delta = sk_ieee_double_divide(delta, maxWidth); + // FIXME: move these magic numbers + // This fixes skbug.com/8380 + // Larger changes (like changing the constant in the next block) cause other + // tests to fail as documented in the bug. + // This could probably become a more general test: e.g., if translating the + // curve causes the cross product of any control point or end point to change + // sign with regard to the opposite curve's hull, treat the curves as parallel. + + // Moreso, this points to the general fragility of this approach of assigning + // winding by sorting the angles of curves sharing a common point, as mentioned + // in the bug. + if (delta < 4e-3 && delta > 1e-3 && !useIntersect && fPart.isCurve() + && rh->fPart.isCurve() && fOriginalCurvePart[0] != fPart.fCurve.fLine[0]) { + // see if original curve is on one side of hull; translated is on the other + const SkDPoint& origin = rh->fOriginalCurvePart[0]; + int count = SkPathOpsVerbToPoints(rh->segment()->verb()); + const SkDVector line = rh->fOriginalCurvePart[count] - origin; + int originalSide = rh->lineOnOneSide(origin, line, this, true); + if (originalSide >= 0) { + int translatedSide = rh->lineOnOneSide(origin, line, this, false); + if (originalSide != translatedSide) { + continue; + } + } + } + if (delta > 1e-3 && (useIntersect ^= true)) { + sRayLonger = rayLonger; + sCept = cept; + sCeptT = smallTs[index]; + sIndex = index; + } + } + if (useIntersect) { + const SkDCurve& curve = sIndex ? rh->fPart.fCurve : this->fPart.fCurve; + const SkOpSegment& segment = sIndex ? *rh->segment() : *this->segment(); + double tStart = sIndex ? rh->fStart->t() : fStart->t(); + SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0]; + double septDir = mid.crossCheck(sCept); + if (!septDir) { + return checkParallel(rh); + } + return sRayLonger ^ (sIndex == 0) ^ (septDir < 0); + } else { + return checkParallel(rh); + } +} + +bool SkOpAngle::endToSide(const SkOpAngle* rh, bool* inside) const { + const SkOpSegment* segment = this->segment(); + SkPath::Verb verb = segment->verb(); + SkDLine rayEnd; + rayEnd[0].set(this->fEnd->pt()); + rayEnd[1] = rayEnd[0]; + SkDVector slopeAtEnd = (*CurveDSlopeAtT[verb])(segment->pts(), segment->weight(), + this->fEnd->t()); + rayEnd[1].fX += slopeAtEnd.fY; + rayEnd[1].fY -= slopeAtEnd.fX; + SkIntersections iEnd; + const SkOpSegment* oppSegment = rh->segment(); + SkPath::Verb oppVerb = oppSegment->verb(); + (*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayEnd, &iEnd); + double endDist; + int closestEnd = iEnd.closestTo(rh->fStart->t(), rh->fEnd->t(), rayEnd[0], &endDist); + if (closestEnd < 0) { + return false; + } + if (!endDist) { + return false; + } + SkDPoint start; + start.set(this->fStart->pt()); + // OPTIMIZATION: multiple times in the code we find the max scalar + double minX, minY, maxX, maxY; + minX = minY = SK_ScalarInfinity; + maxX = maxY = -SK_ScalarInfinity; + const SkDCurve& curve = rh->fPart.fCurve; + int oppPts = SkPathOpsVerbToPoints(oppVerb); + for (int idx2 = 0; idx2 <= oppPts; ++idx2) { + minX = SkTMin(minX, curve[idx2].fX); + minY = SkTMin(minY, curve[idx2].fY); + maxX = SkTMax(maxX, curve[idx2].fX); + maxY = SkTMax(maxY, curve[idx2].fY); + } + double maxWidth = SkTMax(maxX - minX, maxY - minY); + endDist = sk_ieee_double_divide(endDist, maxWidth); + if (!(endDist >= 5e-12)) { // empirically found + return false; // ! above catches NaN + } + const SkDPoint* endPt = &rayEnd[0]; + SkDPoint oppPt = iEnd.pt(closestEnd); + SkDVector vLeft = *endPt - start; + SkDVector vRight = oppPt - start; + double dir = vLeft.crossNoNormalCheck(vRight); + if (!dir) { + return false; + } + *inside = dir < 0; + return true; +} + +/* y<0 y==0 y>0 x<0 x==0 x>0 xy<0 xy==0 xy>0 + 0 x x x + 1 x x x + 2 x x x + 3 x x x + 4 x x x + 5 x x x + 6 x x x + 7 x x x + 8 x x x + 9 x x x + 10 x x x + 11 x x x + 12 x x x + 13 x x x + 14 x x x + 15 x x x +*/ +int SkOpAngle::findSector(SkPath::Verb verb, double x, double y) const { + double absX = fabs(x); + double absY = fabs(y); + double xy = SkPath::kLine_Verb == verb || !AlmostEqualUlps(absX, absY) ? absX - absY : 0; + // If there are four quadrants and eight octants, and since the Latin for sixteen is sedecim, + // one could coin the term sedecimant for a space divided into 16 sections. + // http://english.stackexchange.com/questions/133688/word-for-something-partitioned-into-16-parts + static const int sedecimant[3][3][3] = { + // y<0 y==0 y>0 + // x<0 x==0 x>0 x<0 x==0 x>0 x<0 x==0 x>0 + {{ 4, 3, 2}, { 7, -1, 15}, {10, 11, 12}}, // abs(x) < abs(y) + {{ 5, -1, 1}, {-1, -1, -1}, { 9, -1, 13}}, // abs(x) == abs(y) + {{ 6, 3, 0}, { 7, -1, 15}, { 8, 11, 14}}, // abs(x) > abs(y) + }; + int sector = sedecimant[(xy >= 0) + (xy > 0)][(y >= 0) + (y > 0)][(x >= 0) + (x > 0)] * 2 + 1; +// SkASSERT(SkPath::kLine_Verb == verb || sector >= 0); + return sector; +} + +SkOpGlobalState* SkOpAngle::globalState() const { + return this->segment()->globalState(); +} + + +// OPTIMIZE: if this loops to only one other angle, after first compare fails, insert on other side +// OPTIMIZE: return where insertion succeeded. Then, start next insertion on opposite side +bool SkOpAngle::insert(SkOpAngle* angle) { + if (angle->fNext) { + if (loopCount() >= angle->loopCount()) { + if (!merge(angle)) { + return true; + } + } else if (fNext) { + if (!angle->merge(this)) { + return true; + } + } else { + angle->insert(this); + } + return true; + } + bool singleton = nullptr == fNext; + if (singleton) { + fNext = this; + } + SkOpAngle* next = fNext; + if (next->fNext == this) { + if (singleton || angle->after(this)) { + this->fNext = angle; + angle->fNext = next; + } else { + next->fNext = angle; + angle->fNext = this; + } + debugValidateNext(); + return true; + } + SkOpAngle* last = this; + bool flipAmbiguity = false; + do { + SkASSERT(last->fNext == next); + if (angle->after(last) ^ (angle->tangentsAmbiguous() & flipAmbiguity)) { + last->fNext = angle; + angle->fNext = next; + debugValidateNext(); + return true; + } + last = next; + if (last == this) { + FAIL_IF(flipAmbiguity); + // We're in a loop. If a sort was ambiguous, flip it to end the loop. + flipAmbiguity = true; + } + next = next->fNext; + } while (true); + return true; +} + +SkOpSpanBase* SkOpAngle::lastMarked() const { + if (fLastMarked) { + if (fLastMarked->chased()) { + return nullptr; + } + fLastMarked->setChased(true); + } + return fLastMarked; +} + +bool SkOpAngle::loopContains(const SkOpAngle* angle) const { + if (!fNext) { + return false; + } + const SkOpAngle* first = this; + const SkOpAngle* loop = this; + const SkOpSegment* tSegment = angle->fStart->segment(); + double tStart = angle->fStart->t(); + double tEnd = angle->fEnd->t(); + do { + const SkOpSegment* lSegment = loop->fStart->segment(); + if (lSegment != tSegment) { + continue; + } + double lStart = loop->fStart->t(); + if (lStart != tEnd) { + continue; + } + double lEnd = loop->fEnd->t(); + if (lEnd == tStart) { + return true; + } + } while ((loop = loop->fNext) != first); + return false; +} + +int SkOpAngle::loopCount() const { + int count = 0; + const SkOpAngle* first = this; + const SkOpAngle* next = this; + do { + next = next->fNext; + ++count; + } while (next && next != first); + return count; +} + +bool SkOpAngle::merge(SkOpAngle* angle) { + SkASSERT(fNext); + SkASSERT(angle->fNext); + SkOpAngle* working = angle; + do { + if (this == working) { + return false; + } + working = working->fNext; + } while (working != angle); + do { + SkOpAngle* next = working->fNext; + working->fNext = nullptr; + insert(working); + working = next; + } while (working != angle); + // it's likely that a pair of the angles are unorderable + debugValidateNext(); + return true; +} + +double SkOpAngle::midT() const { + return (fStart->t() + fEnd->t()) / 2; +} + +bool SkOpAngle::midToSide(const SkOpAngle* rh, bool* inside) const { + const SkOpSegment* segment = this->segment(); + SkPath::Verb verb = segment->verb(); + const SkPoint& startPt = this->fStart->pt(); + const SkPoint& endPt = this->fEnd->pt(); + SkDPoint dStartPt; + dStartPt.set(startPt); + SkDLine rayMid; + rayMid[0].fX = (startPt.fX + endPt.fX) / 2; + rayMid[0].fY = (startPt.fY + endPt.fY) / 2; + rayMid[1].fX = rayMid[0].fX + (endPt.fY - startPt.fY); + rayMid[1].fY = rayMid[0].fY - (endPt.fX - startPt.fX); + SkIntersections iMid; + (*CurveIntersectRay[verb])(segment->pts(), segment->weight(), rayMid, &iMid); + int iOutside = iMid.mostOutside(this->fStart->t(), this->fEnd->t(), dStartPt); + if (iOutside < 0) { + return false; + } + const SkOpSegment* oppSegment = rh->segment(); + SkPath::Verb oppVerb = oppSegment->verb(); + SkIntersections oppMid; + (*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayMid, &oppMid); + int oppOutside = oppMid.mostOutside(rh->fStart->t(), rh->fEnd->t(), dStartPt); + if (oppOutside < 0) { + return false; + } + SkDVector iSide = iMid.pt(iOutside) - dStartPt; + SkDVector oppSide = oppMid.pt(oppOutside) - dStartPt; + double dir = iSide.crossCheck(oppSide); + if (!dir) { + return false; + } + *inside = dir < 0; + return true; +} + +bool SkOpAngle::oppositePlanes(const SkOpAngle* rh) const { + int startSpan = SkTAbs(rh->fSectorStart - fSectorStart); + return startSpan >= 8; +} + +int SkOpAngle::orderable(SkOpAngle* rh) { + int result; + if (!fPart.isCurve()) { + if (!rh->fPart.isCurve()) { + double leftX = fTangentHalf.dx(); + double leftY = fTangentHalf.dy(); + double rightX = rh->fTangentHalf.dx(); + double rightY = rh->fTangentHalf.dy(); + double x_ry = leftX * rightY; + double rx_y = rightX * leftY; + if (x_ry == rx_y) { + if (leftX * rightX < 0 || leftY * rightY < 0) { + return 1; // exactly 180 degrees apart + } + goto unorderable; + } + SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier + return x_ry < rx_y ? 1 : 0; + } + if ((result = this->lineOnOneSide(rh, false)) >= 0) { + return result; + } + if (fUnorderable || approximately_zero(rh->fSide)) { + goto unorderable; + } + } else if (!rh->fPart.isCurve()) { + if ((result = rh->lineOnOneSide(this, false)) >= 0) { + return result ? 0 : 1; + } + if (rh->fUnorderable || approximately_zero(fSide)) { + goto unorderable; + } + } else if ((result = this->convexHullOverlaps(rh)) >= 0) { + return result; + } + return this->endsIntersect(rh) ? 1 : 0; +unorderable: + fUnorderable = true; + rh->fUnorderable = true; + return -1; +} + +// OPTIMIZE: if this shows up in a profile, add a previous pointer +// as is, this should be rarely called +SkOpAngle* SkOpAngle::previous() const { + SkOpAngle* last = fNext; + do { + SkOpAngle* next = last->fNext; + if (next == this) { + return last; + } + last = next; + } while (true); +} + +SkOpSegment* SkOpAngle::segment() const { + return fStart->segment(); +} + +void SkOpAngle::set(SkOpSpanBase* start, SkOpSpanBase* end) { + fStart = start; + fComputedEnd = fEnd = end; + SkASSERT(start != end); + fNext = nullptr; + fComputeSector = fComputedSector = fCheckCoincidence = fTangentsAmbiguous = false; + setSpans(); + setSector(); + SkDEBUGCODE(fID = start ? start->globalState()->nextAngleID() : -1); +} + +void SkOpAngle::setSpans() { + fUnorderable = false; + fLastMarked = nullptr; + if (!fStart) { + fUnorderable = true; + return; + } + const SkOpSegment* segment = fStart->segment(); + const SkPoint* pts = segment->pts(); + SkDEBUGCODE(fPart.fCurve.fVerb = SkPath::kCubic_Verb); // required for SkDCurve debug check + SkDEBUGCODE(fPart.fCurve[2].fX = fPart.fCurve[2].fY = fPart.fCurve[3].fX = fPart.fCurve[3].fY + = SK_ScalarNaN); // make the non-line part uninitialized + SkDEBUGCODE(fPart.fCurve.fVerb = segment->verb()); // set the curve type for real + segment->subDivide(fStart, fEnd, &fPart.fCurve); // set at least the line part if not more + fOriginalCurvePart = fPart.fCurve; + const SkPath::Verb verb = segment->verb(); + fPart.setCurveHullSweep(verb); + if (SkPath::kLine_Verb != verb && !fPart.isCurve()) { + SkDLine lineHalf; + fPart.fCurve[1] = fPart.fCurve[SkPathOpsVerbToPoints(verb)]; + fOriginalCurvePart[1] = fPart.fCurve[1]; + lineHalf[0].set(fPart.fCurve[0].asSkPoint()); + lineHalf[1].set(fPart.fCurve[1].asSkPoint()); + fTangentHalf.lineEndPoints(lineHalf); + fSide = 0; + } + switch (verb) { + case SkPath::kLine_Verb: { + SkASSERT(fStart != fEnd); + const SkPoint& cP1 = pts[fStart->t() < fEnd->t()]; + SkDLine lineHalf; + lineHalf[0].set(fStart->pt()); + lineHalf[1].set(cP1); + fTangentHalf.lineEndPoints(lineHalf); + fSide = 0; + } return; + case SkPath::kQuad_Verb: + case SkPath::kConic_Verb: { + SkLineParameters tangentPart; + (void) tangentPart.quadEndPoints(fPart.fCurve.fQuad); + fSide = -tangentPart.pointDistance(fPart.fCurve[2]); // not normalized -- compare sign only + } break; + case SkPath::kCubic_Verb: { + SkLineParameters tangentPart; + (void) tangentPart.cubicPart(fPart.fCurve.fCubic); + fSide = -tangentPart.pointDistance(fPart.fCurve[3]); + double testTs[4]; + // OPTIMIZATION: keep inflections precomputed with cubic segment? + int testCount = SkDCubic::FindInflections(pts, testTs); + double startT = fStart->t(); + double endT = fEnd->t(); + double limitT = endT; + int index; + for (index = 0; index < testCount; ++index) { + if (!::between(startT, testTs[index], limitT)) { + testTs[index] = -1; + } + } + testTs[testCount++] = startT; + testTs[testCount++] = endT; + SkTQSort(testTs, &testTs[testCount - 1]); + double bestSide = 0; + int testCases = (testCount << 1) - 1; + index = 0; + while (testTs[index] < 0) { + ++index; + } + index <<= 1; + for (; index < testCases; ++index) { + int testIndex = index >> 1; + double testT = testTs[testIndex]; + if (index & 1) { + testT = (testT + testTs[testIndex + 1]) / 2; + } + // OPTIMIZE: could avoid call for t == startT, endT + SkDPoint pt = dcubic_xy_at_t(pts, segment->weight(), testT); + SkLineParameters tangentPart; + tangentPart.cubicEndPoints(fPart.fCurve.fCubic); + double testSide = tangentPart.pointDistance(pt); + if (fabs(bestSide) < fabs(testSide)) { + bestSide = testSide; + } + } + fSide = -bestSide; // compare sign only + } break; + default: + SkASSERT(0); + } +} + +void SkOpAngle::setSector() { + if (!fStart) { + fUnorderable = true; + return; + } + const SkOpSegment* segment = fStart->segment(); + SkPath::Verb verb = segment->verb(); + fSectorStart = this->findSector(verb, fPart.fSweep[0].fX, fPart.fSweep[0].fY); + if (fSectorStart < 0) { + goto deferTilLater; + } + if (!fPart.isCurve()) { // if it's a line or line-like, note that both sectors are the same + SkASSERT(fSectorStart >= 0); + fSectorEnd = fSectorStart; + fSectorMask = 1 << fSectorStart; + return; + } + SkASSERT(SkPath::kLine_Verb != verb); + fSectorEnd = this->findSector(verb, fPart.fSweep[1].fX, fPart.fSweep[1].fY); + if (fSectorEnd < 0) { +deferTilLater: + fSectorStart = fSectorEnd = -1; + fSectorMask = 0; + fComputeSector = true; // can't determine sector until segment length can be found + return; + } + if (fSectorEnd == fSectorStart + && (fSectorStart & 3) != 3) { // if the sector has no span, it can't be an exact angle + fSectorMask = 1 << fSectorStart; + return; + } + bool crossesZero = this->checkCrossesZero(); + int start = SkTMin(fSectorStart, fSectorEnd); + bool curveBendsCCW = (fSectorStart == start) ^ crossesZero; + // bump the start and end of the sector span if they are on exact compass points + if ((fSectorStart & 3) == 3) { + fSectorStart = (fSectorStart + (curveBendsCCW ? 1 : 31)) & 0x1f; + } + if ((fSectorEnd & 3) == 3) { + fSectorEnd = (fSectorEnd + (curveBendsCCW ? 31 : 1)) & 0x1f; + } + crossesZero = this->checkCrossesZero(); + start = SkTMin(fSectorStart, fSectorEnd); + int end = SkTMax(fSectorStart, fSectorEnd); + if (!crossesZero) { + fSectorMask = (unsigned) -1 >> (31 - end + start) << start; + } else { + fSectorMask = (unsigned) -1 >> (31 - start) | ((unsigned) -1 << end); + } +} + +SkOpSpan* SkOpAngle::starter() { + return fStart->starter(fEnd); +} + +bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) { + if (s0xt0 == 0) { + return false; + } + // if the ctrl tangents are not nearly parallel, use them + // solve for opposite direction displacement scale factor == m + // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x + // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1] + // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x) + // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x) + // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x + // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y) + // m = v1.cross(v2) / v1.dot(v2) + const SkDVector* sweep = fPart.fSweep; + const SkDVector* tweep = rh->fPart.fSweep; + double s0dt0 = sweep[0].dot(tweep[0]); + if (!s0dt0) { + return true; + } + SkASSERT(s0dt0 != 0); + double m = s0xt0 / s0dt0; + double sDist = sweep[0].length() * m; + double tDist = tweep[0].length() * m; + bool useS = fabs(sDist) < fabs(tDist); + double mFactor = fabs(useS ? this->distEndRatio(sDist) : rh->distEndRatio(tDist)); + fTangentsAmbiguous = mFactor >= 50 && mFactor < 200; + return mFactor < 50; // empirically found limit +} diff --git a/gfx/skia/skia/src/pathops/SkOpAngle.h b/gfx/skia/skia/src/pathops/SkOpAngle.h new file mode 100644 index 0000000000..73d0e11bdb --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpAngle.h @@ -0,0 +1,146 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpAngle_DEFINED +#define SkOpAngle_DEFINED + +#include "src/pathops/SkLineParameters.h" +#include "src/pathops/SkPathOpsCurve.h" +#if DEBUG_ANGLE +#include "include/core/SkString.h" +#endif + +class SkOpContour; +class SkOpPtT; +class SkOpSegment; +class SkOpSpanBase; +class SkOpSpan; + +class SkOpAngle { +public: + enum IncludeType { + kUnaryWinding, + kUnaryXor, + kBinarySingle, + kBinaryOpp, + }; + + const SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; + SkOpContour* debugContour(int id) const; + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + +#if DEBUG_SORT + void debugLoop() const; +#endif + +#if DEBUG_ANGLE + bool debugCheckCoincidence() const { return fCheckCoincidence; } + void debugCheckNearCoincidence() const; + SkString debugPart() const; +#endif + const SkOpPtT* debugPtT(int id) const; + const SkOpSegment* debugSegment(int id) const; + int debugSign() const; + const SkOpSpanBase* debugSpan(int id) const; + void debugValidate() const; + void debugValidateNext() const; // in debug builds, verify that angle loop is uncorrupted + double distEndRatio(double dist) const; + // available to testing only + void dump() const; + void dumpCurves() const; + void dumpLoop() const; + void dumpOne(bool functionHeader) const; + void dumpTo(const SkOpSegment* fromSeg, const SkOpAngle* ) const; + void dumpTest() const; + + SkOpSpanBase* end() const { + return fEnd; + } + + bool insert(SkOpAngle* ); + SkOpSpanBase* lastMarked() const; + bool loopContains(const SkOpAngle* ) const; + int loopCount() const; + + SkOpAngle* next() const { + return fNext; + } + + SkOpAngle* previous() const; + SkOpSegment* segment() const; + void set(SkOpSpanBase* start, SkOpSpanBase* end); + + void setLastMarked(SkOpSpanBase* marked) { + fLastMarked = marked; + } + + SkOpSpanBase* start() const { + return fStart; + } + + SkOpSpan* starter(); + + bool tangentsAmbiguous() const { + return fTangentsAmbiguous; + } + + bool unorderable() const { + return fUnorderable; + } + +private: + bool after(SkOpAngle* test); + void alignmentSameSide(const SkOpAngle* test, int* order) const; + bool checkCrossesZero() const; + bool checkParallel(SkOpAngle* ); + bool computeSector(); + int convexHullOverlaps(const SkOpAngle* ); + bool endToSide(const SkOpAngle* rh, bool* inside) const; + bool endsIntersect(SkOpAngle* ); + int findSector(SkPath::Verb verb, double x, double y) const; + SkOpGlobalState* globalState() const; + int lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test, + bool useOriginal) const; + int lineOnOneSide(const SkOpAngle* test, bool useOriginal); + int linesOnOriginalSide(const SkOpAngle* test); + bool merge(SkOpAngle* ); + double midT() const; + bool midToSide(const SkOpAngle* rh, bool* inside) const; + bool oppositePlanes(const SkOpAngle* rh) const; + int orderable(SkOpAngle* rh); // false == this < rh ; true == this > rh; -1 == unorderable + void setSector(); + void setSpans(); + bool tangentsDiverge(const SkOpAngle* rh, double s0xt0); + + SkDCurve fOriginalCurvePart; // the curve from start to end + SkDCurveSweep fPart; // the curve from start to end offset as needed + double fSide; + SkLineParameters fTangentHalf; // used only to sort a pair of lines or line-like sections + SkOpAngle* fNext; + SkOpSpanBase* fLastMarked; + SkOpSpanBase* fStart; + SkOpSpanBase* fEnd; + SkOpSpanBase* fComputedEnd; + int fSectorMask; + int8_t fSectorStart; // in 32nds of a circle + int8_t fSectorEnd; + bool fUnorderable; + bool fComputeSector; + bool fComputedSector; + bool fCheckCoincidence; + bool fTangentsAmbiguous; + SkDEBUGCODE(int fID); + + friend class PathOpsAngleTester; +}; + + + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpBuilder.cpp b/gfx/skia/skia/src/pathops/SkOpBuilder.cpp new file mode 100644 index 0000000000..9dac160495 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpBuilder.cpp @@ -0,0 +1,195 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMatrix.h" +#include "include/pathops/SkPathOps.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkPathPriv.h" +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkPathOpsCommon.h" + +static bool one_contour(const SkPath& path) { + SkSTArenaAlloc<256> allocator; + int verbCount = path.countVerbs(); + uint8_t* verbs = (uint8_t*) allocator.makeArrayDefault(verbCount); + (void) path.getVerbs(verbs, verbCount); + for (int index = 1; index < verbCount; ++index) { + if (verbs[index] == SkPath::kMove_Verb) { + return false; + } + } + return true; +} + +void SkOpBuilder::ReversePath(SkPath* path) { + SkPath temp; + SkPoint lastPt; + SkAssertResult(path->getLastPt(&lastPt)); + temp.moveTo(lastPt); + temp.reversePathTo(*path); + temp.close(); + *path = temp; +} + +bool SkOpBuilder::FixWinding(SkPath* path) { + SkPath::FillType fillType = path->getFillType(); + if (fillType == SkPath::kInverseEvenOdd_FillType) { + fillType = SkPath::kInverseWinding_FillType; + } else if (fillType == SkPath::kEvenOdd_FillType) { + fillType = SkPath::kWinding_FillType; + } + SkPathPriv::FirstDirection dir; + if (one_contour(*path) && SkPathPriv::CheapComputeFirstDirection(*path, &dir)) { + if (dir != SkPathPriv::kCCW_FirstDirection) { + ReversePath(path); + } + path->setFillType(fillType); + return true; + } + SkSTArenaAlloc<4096> allocator; + SkOpContourHead contourHead; + SkOpGlobalState globalState(&contourHead, &allocator SkDEBUGPARAMS(false) + SkDEBUGPARAMS(nullptr)); + SkOpEdgeBuilder builder(*path, &contourHead, &globalState); + if (builder.unparseable() || !builder.finish()) { + return false; + } + if (!contourHead.count()) { + return true; + } + if (!contourHead.next()) { + return false; + } + contourHead.joinAllSegments(); + contourHead.resetReverse(); + bool writePath = false; + SkOpSpan* topSpan; + globalState.setPhase(SkOpPhase::kFixWinding); + while ((topSpan = FindSortableTop(&contourHead))) { + SkOpSegment* topSegment = topSpan->segment(); + SkOpContour* topContour = topSegment->contour(); + SkASSERT(topContour->isCcw() >= 0); +#if DEBUG_WINDING + SkDebugf("%s id=%d nested=%d ccw=%d\n", __FUNCTION__, + topSegment->debugID(), globalState.nested(), topContour->isCcw()); +#endif + if ((globalState.nested() & 1) != SkToBool(topContour->isCcw())) { + topContour->setReverse(); + writePath = true; + } + topContour->markAllDone(); + globalState.clearNested(); + } + if (!writePath) { + path->setFillType(fillType); + return true; + } + SkPath empty; + SkPathWriter woundPath(empty); + SkOpContour* test = &contourHead; + do { + if (!test->count()) { + continue; + } + if (test->reversed()) { + test->toReversePath(&woundPath); + } else { + test->toPath(&woundPath); + } + } while ((test = test->next())); + *path = *woundPath.nativePath(); + path->setFillType(fillType); + return true; +} + +void SkOpBuilder::add(const SkPath& path, SkPathOp op) { + if (0 == fOps.count() && op != kUnion_SkPathOp) { + fPathRefs.push_back() = SkPath(); + *fOps.append() = kUnion_SkPathOp; + } + fPathRefs.push_back() = path; + *fOps.append() = op; +} + +void SkOpBuilder::reset() { + fPathRefs.reset(); + fOps.reset(); +} + +/* OPTIMIZATION: Union doesn't need to be all-or-nothing. A run of three or more convex + paths with union ops could be locally resolved and still improve over doing the + ops one at a time. */ +bool SkOpBuilder::resolve(SkPath* result) { + SkPath original = *result; + int count = fOps.count(); + bool allUnion = true; + SkPathPriv::FirstDirection firstDir = SkPathPriv::kUnknown_FirstDirection; + for (int index = 0; index < count; ++index) { + SkPath* test = &fPathRefs[index]; + if (kUnion_SkPathOp != fOps[index] || test->isInverseFillType()) { + allUnion = false; + break; + } + // If all paths are convex, track direction, reversing as needed. + if (test->isConvex()) { + SkPathPriv::FirstDirection dir; + if (!SkPathPriv::CheapComputeFirstDirection(*test, &dir)) { + allUnion = false; + break; + } + if (firstDir == SkPathPriv::kUnknown_FirstDirection) { + firstDir = dir; + } else if (firstDir != dir) { + ReversePath(test); + } + continue; + } + // If the path is not convex but its bounds do not intersect the others, simplify is enough. + const SkRect& testBounds = test->getBounds(); + for (int inner = 0; inner < index; ++inner) { + // OPTIMIZE: check to see if the contour bounds do not intersect other contour bounds? + if (SkRect::Intersects(fPathRefs[inner].getBounds(), testBounds)) { + allUnion = false; + break; + } + } + } + if (!allUnion) { + *result = fPathRefs[0]; + for (int index = 1; index < count; ++index) { + if (!Op(*result, fPathRefs[index], fOps[index], result)) { + reset(); + *result = original; + return false; + } + } + reset(); + return true; + } + SkPath sum; + for (int index = 0; index < count; ++index) { + if (!Simplify(fPathRefs[index], &fPathRefs[index])) { + reset(); + *result = original; + return false; + } + if (!fPathRefs[index].isEmpty()) { + // convert the even odd result back to winding form before accumulating it + if (!FixWinding(&fPathRefs[index])) { + *result = original; + return false; + } + sum.addPath(fPathRefs[index]); + } + } + reset(); + bool success = Simplify(sum, result); + if (!success) { + *result = original; + } + return success; +} diff --git a/gfx/skia/skia/src/pathops/SkOpCoincidence.cpp b/gfx/skia/skia/src/pathops/SkOpCoincidence.cpp new file mode 100644 index 0000000000..31de4f1a3c --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpCoincidence.cpp @@ -0,0 +1,1448 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpSegment.h" +#include "src/pathops/SkPathOpsTSect.h" + +#include + +// returns true if coincident span's start and end are the same +bool SkCoincidentSpans::collapsed(const SkOpPtT* test) const { + return (fCoinPtTStart == test && fCoinPtTEnd->contains(test)) + || (fCoinPtTEnd == test && fCoinPtTStart->contains(test)) + || (fOppPtTStart == test && fOppPtTEnd->contains(test)) + || (fOppPtTEnd == test && fOppPtTStart->contains(test)); +} + +// out of line since this function is referenced by address +const SkOpPtT* SkCoincidentSpans::coinPtTEnd() const { + return fCoinPtTEnd; +} + +// out of line since this function is referenced by address +const SkOpPtT* SkCoincidentSpans::coinPtTStart() const { + return fCoinPtTStart; +} + +// sets the span's end to the ptT referenced by the previous-next +void SkCoincidentSpans::correctOneEnd( + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) ) { + const SkOpPtT* origPtT = (this->*getEnd)(); + const SkOpSpanBase* origSpan = origPtT->span(); + const SkOpSpan* prev = origSpan->prev(); + const SkOpPtT* testPtT = prev ? prev->next()->ptT() + : origSpan->upCast()->next()->prev()->ptT(); + if (origPtT != testPtT) { + (this->*setEnd)(testPtT); + } +} + +/* Please keep this in sync with debugCorrectEnds */ +// FIXME: member pointers have fallen out of favor and can be replaced with +// an alternative approach. +// makes all span ends agree with the segment's spans that define them +void SkCoincidentSpans::correctEnds() { + this->correctOneEnd(&SkCoincidentSpans::coinPtTStart, &SkCoincidentSpans::setCoinPtTStart); + this->correctOneEnd(&SkCoincidentSpans::coinPtTEnd, &SkCoincidentSpans::setCoinPtTEnd); + this->correctOneEnd(&SkCoincidentSpans::oppPtTStart, &SkCoincidentSpans::setOppPtTStart); + this->correctOneEnd(&SkCoincidentSpans::oppPtTEnd, &SkCoincidentSpans::setOppPtTEnd); +} + +/* Please keep this in sync with debugExpand */ +// expand the range by checking adjacent spans for coincidence +bool SkCoincidentSpans::expand() { + bool expanded = false; + const SkOpSegment* segment = coinPtTStart()->segment(); + const SkOpSegment* oppSegment = oppPtTStart()->segment(); + do { + const SkOpSpan* start = coinPtTStart()->span()->upCast(); + const SkOpSpan* prev = start->prev(); + const SkOpPtT* oppPtT; + if (!prev || !(oppPtT = prev->contains(oppSegment))) { + break; + } + double midT = (prev->t() + start->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + setStarts(prev->ptT(), oppPtT); + expanded = true; + } while (true); + do { + const SkOpSpanBase* end = coinPtTEnd()->span(); + SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); + if (next && next->deleted()) { + break; + } + const SkOpPtT* oppPtT; + if (!next || !(oppPtT = next->contains(oppSegment))) { + break; + } + double midT = (end->t() + next->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + setEnds(next->ptT(), oppPtT); + expanded = true; + } while (true); + return expanded; +} + +// increase the range of this span +bool SkCoincidentSpans::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + bool result = false; + if (fCoinPtTStart->fT > coinPtTStart->fT || (this->flipped() + ? fOppPtTStart->fT < oppPtTStart->fT : fOppPtTStart->fT > oppPtTStart->fT)) { + this->setStarts(coinPtTStart, oppPtTStart); + result = true; + } + if (fCoinPtTEnd->fT < coinPtTEnd->fT || (this->flipped() + ? fOppPtTEnd->fT > oppPtTEnd->fT : fOppPtTEnd->fT < oppPtTEnd->fT)) { + this->setEnds(coinPtTEnd, oppPtTEnd); + result = true; + } + return result; +} + +// set the range of this span +void SkCoincidentSpans::set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, + const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + SkASSERT(SkOpCoincidence::Ordered(coinPtTStart, oppPtTStart)); + fNext = next; + this->setStarts(coinPtTStart, oppPtTStart); + this->setEnds(coinPtTEnd, oppPtTEnd); +} + +// returns true if both points are inside this +bool SkCoincidentSpans::contains(const SkOpPtT* s, const SkOpPtT* e) const { + if (s->fT > e->fT) { + using std::swap; + swap(s, e); + } + if (s->segment() == fCoinPtTStart->segment()) { + return fCoinPtTStart->fT <= s->fT && e->fT <= fCoinPtTEnd->fT; + } else { + SkASSERT(s->segment() == fOppPtTStart->segment()); + double oppTs = fOppPtTStart->fT; + double oppTe = fOppPtTEnd->fT; + if (oppTs > oppTe) { + using std::swap; + swap(oppTs, oppTe); + } + return oppTs <= s->fT && e->fT <= oppTe; + } +} + +// out of line since this function is referenced by address +const SkOpPtT* SkCoincidentSpans::oppPtTStart() const { + return fOppPtTStart; +} + +// out of line since this function is referenced by address +const SkOpPtT* SkCoincidentSpans::oppPtTEnd() const { + return fOppPtTEnd; +} + +// A coincident span is unordered if the pairs of points in the main and opposite curves' +// t values do not ascend or descend. For instance, if a tightly arced quadratic is +// coincident with another curve, it may intersect it out of order. +bool SkCoincidentSpans::ordered(bool* result) const { + const SkOpSpanBase* start = this->coinPtTStart()->span(); + const SkOpSpanBase* end = this->coinPtTEnd()->span(); + const SkOpSpanBase* next = start->upCast()->next(); + if (next == end) { + *result = true; + return true; + } + bool flipped = this->flipped(); + const SkOpSegment* oppSeg = this->oppPtTStart()->segment(); + double oppLastT = fOppPtTStart->fT; + do { + const SkOpPtT* opp = next->contains(oppSeg); + if (!opp) { +// SkOPOBJASSERT(start, 0); // may assert if coincident span isn't fully processed + return false; + } + if ((oppLastT > opp->fT) != flipped) { + *result = false; + return true; + } + oppLastT = opp->fT; + if (next == end) { + break; + } + if (!next->upCastable()) { + *result = false; + return true; + } + next = next->upCast()->next(); + } while (true); + *result = true; + return true; +} + +// if there is an existing pair that overlaps the addition, extend it +bool SkOpCoincidence::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + SkCoincidentSpans* test = fHead; + if (!test) { + return false; + } + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (!Ordered(coinPtTStart, oppPtTStart)) { + using std::swap; + swap(coinSeg, oppSeg); + swap(coinPtTStart, oppPtTStart); + swap(coinPtTEnd, oppPtTEnd); + if (coinPtTStart->fT > coinPtTEnd->fT) { + swap(coinPtTStart, coinPtTEnd); + swap(oppPtTStart, oppPtTEnd); + } + } + double oppMinT = SkTMin(oppPtTStart->fT, oppPtTEnd->fT); + SkDEBUGCODE(double oppMaxT = SkTMax(oppPtTStart->fT, oppPtTEnd->fT)); + do { + if (coinSeg != test->coinPtTStart()->segment()) { + continue; + } + if (oppSeg != test->oppPtTStart()->segment()) { + continue; + } + double oTestMinT = SkTMin(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); + double oTestMaxT = SkTMax(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); + // if debug check triggers, caller failed to check if extended already exists + SkASSERT(test->coinPtTStart()->fT > coinPtTStart->fT + || coinPtTEnd->fT > test->coinPtTEnd()->fT + || oTestMinT > oppMinT || oppMaxT > oTestMaxT); + if ((test->coinPtTStart()->fT <= coinPtTEnd->fT + && coinPtTStart->fT <= test->coinPtTEnd()->fT) + || (oTestMinT <= oTestMaxT && oppMinT <= oTestMaxT)) { + test->extend(coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + return true; + } + } while ((test = test->next())); + return false; +} + +// verifies that the coincidence hasn't already been added +static void DebugCheckAdd(const SkCoincidentSpans* check, const SkOpPtT* coinPtTStart, + const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { +#if DEBUG_COINCIDENCE + while (check) { + SkASSERT(check->coinPtTStart() != coinPtTStart || check->coinPtTEnd() != coinPtTEnd + || check->oppPtTStart() != oppPtTStart || check->oppPtTEnd() != oppPtTEnd); + SkASSERT(check->coinPtTStart() != oppPtTStart || check->coinPtTEnd() != oppPtTEnd + || check->oppPtTStart() != coinPtTStart || check->oppPtTEnd() != coinPtTEnd); + check = check->next(); + } +#endif +} + +// adds a new coincident pair +void SkOpCoincidence::add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, + SkOpPtT* oppPtTEnd) { + // OPTIMIZE: caller should have already sorted + if (!Ordered(coinPtTStart, oppPtTStart)) { + if (oppPtTStart->fT < oppPtTEnd->fT) { + this->add(oppPtTStart, oppPtTEnd, coinPtTStart, coinPtTEnd); + } else { + this->add(oppPtTEnd, oppPtTStart, coinPtTEnd, coinPtTStart); + } + return; + } + SkASSERT(Ordered(coinPtTStart, oppPtTStart)); + // choose the ptT at the front of the list to track + coinPtTStart = coinPtTStart->span()->ptT(); + coinPtTEnd = coinPtTEnd->span()->ptT(); + oppPtTStart = oppPtTStart->span()->ptT(); + oppPtTEnd = oppPtTEnd->span()->ptT(); + SkOPASSERT(coinPtTStart->fT < coinPtTEnd->fT); + SkOPASSERT(oppPtTStart->fT != oppPtTEnd->fT); + SkOPASSERT(!coinPtTStart->deleted()); + SkOPASSERT(!coinPtTEnd->deleted()); + SkOPASSERT(!oppPtTStart->deleted()); + SkOPASSERT(!oppPtTEnd->deleted()); + DebugCheckAdd(fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + DebugCheckAdd(fTop, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + SkCoincidentSpans* coinRec = this->globalState()->allocator()->make(); + coinRec->init(SkDEBUGCODE(fGlobalState)); + coinRec->set(this->fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + fHead = coinRec; +} + +// description below +bool SkOpCoincidence::addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan) { + const SkOpPtT* testPtT = testSpan->ptT(); + const SkOpPtT* stopPtT = testPtT; + const SkOpSegment* baseSeg = base->segment(); + int escapeHatch = 100000; // this is 100 times larger than the debugLoopLimit test + while ((testPtT = testPtT->next()) != stopPtT) { + if (--escapeHatch <= 0) { + return false; // if triggered (likely by a fuzz-generated test) too complex to succeed + } + const SkOpSegment* testSeg = testPtT->segment(); + if (testPtT->deleted()) { + continue; + } + if (testSeg == baseSeg) { + continue; + } + if (testPtT->span()->ptT() != testPtT) { + continue; + } + if (this->contains(baseSeg, testSeg, testPtT->fT)) { + continue; + } + // intersect perp with base->ptT() with testPtT->segment() + SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); + const SkPoint& pt = base->pt(); + SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; + SkIntersections i SkDEBUGCODE((this->globalState())); + (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); + for (int index = 0; index < i.used(); ++index) { + double t = i[0][index]; + if (!between(0, t, 1)) { + continue; + } + SkDPoint oppPt = i.pt(index); + if (!oppPt.approximatelyEqual(pt)) { + continue; + } + SkOpSegment* writableSeg = const_cast(testSeg); + SkOpPtT* oppStart = writableSeg->addT(t); + if (oppStart == testPtT) { + continue; + } + SkOpSpan* writableBase = const_cast(base); + oppStart->span()->addOpp(writableBase); + if (oppStart->deleted()) { + continue; + } + SkOpSegment* coinSeg = base->segment(); + SkOpSegment* oppSeg = oppStart->segment(); + double coinTs, coinTe, oppTs, oppTe; + if (Ordered(coinSeg, oppSeg)) { + coinTs = base->t(); + coinTe = testSpan->t(); + oppTs = oppStart->fT; + oppTe = testPtT->fT; + } else { + using std::swap; + swap(coinSeg, oppSeg); + coinTs = oppStart->fT; + coinTe = testPtT->fT; + oppTs = base->t(); + oppTe = testSpan->t(); + } + if (coinTs > coinTe) { + using std::swap; + swap(coinTs, coinTe); + swap(oppTs, oppTe); + } + bool added; + FAIL_IF(!this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &added)); + } + } + return true; +} + +// description below +bool SkOpCoincidence::addEndMovedSpans(const SkOpPtT* ptT) { + FAIL_IF(!ptT->span()->upCastable()); + const SkOpSpan* base = ptT->span()->upCast(); + const SkOpSpan* prev = base->prev(); + FAIL_IF(!prev); + if (!prev->isCanceled()) { + if (!this->addEndMovedSpans(base, base->prev())) { + return false; + } + } + if (!base->isCanceled()) { + if (!this->addEndMovedSpans(base, base->next())) { + return false; + } + } + return true; +} + +/* If A is coincident with B and B includes an endpoint, and A's matching point + is not the endpoint (i.e., there's an implied line connecting B-end and A) + then assume that the same implied line may intersect another curve close to B. + Since we only care about coincidence that was undetected, look at the + ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but + next door) and see if the A matching point is close enough to form another + coincident pair. If so, check for a new coincident span between B-end/A ptT loop + and the adjacent ptT loop. +*/ +bool SkOpCoincidence::addEndMovedSpans(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* span = fHead; + if (!span) { + return true; + } + fTop = span; + fHead = nullptr; + do { + if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { + FAIL_IF(1 == span->coinPtTStart()->fT); + bool onEnd = span->coinPtTStart()->fT == 0; + bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); + if (onEnd) { + if (!oOnEnd) { // if both are on end, any nearby intersect was already found + if (!this->addEndMovedSpans(span->oppPtTStart())) { + return false; + } + } + } else if (oOnEnd) { + if (!this->addEndMovedSpans(span->coinPtTStart())) { + return false; + } + } + } + if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { + bool onEnd = span->coinPtTEnd()->fT == 1; + bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); + if (onEnd) { + if (!oOnEnd) { + if (!this->addEndMovedSpans(span->oppPtTEnd())) { + return false; + } + } + } else if (oOnEnd) { + if (!this->addEndMovedSpans(span->coinPtTEnd())) { + return false; + } + } + } + } while ((span = span->next())); + this->restoreHead(); + return true; +} + +/* Please keep this in sync with debugAddExpanded */ +// for each coincident pair, match the spans +// if the spans don't match, add the missing pt to the segment and loop it in the opposite span +bool SkOpCoincidence::addExpanded(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = this->fHead; + if (!coin) { + return true; + } + do { + const SkOpPtT* startPtT = coin->coinPtTStart(); + const SkOpPtT* oStartPtT = coin->oppPtTStart(); + double priorT = startPtT->fT; + double oPriorT = oStartPtT->fT; + FAIL_IF(!startPtT->contains(oStartPtT)); + SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); + const SkOpSpanBase* start = startPtT->span(); + const SkOpSpanBase* oStart = oStartPtT->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); + FAIL_IF(oEnd->deleted()); + FAIL_IF(!start->upCastable()); + const SkOpSpanBase* test = start->upCast()->next(); + FAIL_IF(!coin->flipped() && !oStart->upCastable()); + const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); + FAIL_IF(!oTest); + SkOpSegment* seg = start->segment(); + SkOpSegment* oSeg = oStart->segment(); + while (test != end || oTest != oEnd) { + const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); + const SkOpPtT* containedThis = oTest->ptT()->contains(seg); + if (!containedOpp || !containedThis) { + // choose the ends, or the first common pt-t list shared by both + double nextT, oNextT; + if (containedOpp) { + nextT = test->t(); + oNextT = containedOpp->fT; + } else if (containedThis) { + nextT = containedThis->fT; + oNextT = oTest->t(); + } else { + // iterate through until a pt-t list found that contains the other + const SkOpSpanBase* walk = test; + const SkOpPtT* walkOpp; + do { + FAIL_IF(!walk->upCastable()); + walk = walk->upCast()->next(); + } while (!(walkOpp = walk->ptT()->contains(oSeg)) + && walk != coin->coinPtTEnd()->span()); + FAIL_IF(!walkOpp); + nextT = walk->t(); + oNextT = walkOpp->fT; + } + // use t ranges to guess which one is missing + double startRange = nextT - priorT; + FAIL_IF(!startRange); + double startPart = (test->t() - priorT) / startRange; + double oStartRange = oNextT - oPriorT; + FAIL_IF(!oStartRange); + double oStartPart = (oTest->t() - oPriorT) / oStartRange; + FAIL_IF(startPart == oStartPart); + bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart + : !!containedThis; + bool startOver = false; + bool success = addToOpp ? oSeg->addExpanded( + oPriorT + oStartRange * startPart, test, &startOver) + : seg->addExpanded( + priorT + startRange * oStartPart, oTest, &startOver); + FAIL_IF(!success); + if (startOver) { + test = start; + oTest = oStart; + } + end = coin->coinPtTEnd()->span(); + oEnd = coin->oppPtTEnd()->span(); + } + if (test != end) { + FAIL_IF(!test->upCastable()); + priorT = test->t(); + test = test->upCast()->next(); + } + if (oTest != oEnd) { + oPriorT = oTest->t(); + if (coin->flipped()) { + oTest = oTest->prev(); + } else { + FAIL_IF(!oTest->upCastable()); + oTest = oTest->upCast()->next(); + } + FAIL_IF(!oTest); + } + + } + } while ((coin = coin->next())); + return true; +} + +// given a t span, map the same range on the coincident span +/* +the curves may not scale linearly, so interpolation may only happen within known points +remap over1s, over1e, cointPtTStart, coinPtTEnd to smallest range that captures over1s +then repeat to capture over1e +*/ +double SkOpCoincidence::TRange(const SkOpPtT* overS, double t, + const SkOpSegment* coinSeg SkDEBUGPARAMS(const SkOpPtT* overE)) { + const SkOpSpanBase* work = overS->span(); + const SkOpPtT* foundStart = nullptr; + const SkOpPtT* foundEnd = nullptr; + const SkOpPtT* coinStart = nullptr; + const SkOpPtT* coinEnd = nullptr; + do { + const SkOpPtT* contained = work->contains(coinSeg); + if (!contained) { + if (work->final()) { + break; + } + continue; + } + if (work->t() <= t) { + coinStart = contained; + foundStart = work->ptT(); + } + if (work->t() >= t) { + coinEnd = contained; + foundEnd = work->ptT(); + break; + } + SkASSERT(work->ptT() != overE); + } while ((work = work->upCast()->next())); + if (!coinStart || !coinEnd) { + return 1; + } + // while overS->fT <=t and overS contains coinSeg + double denom = foundEnd->fT - foundStart->fT; + double sRatio = denom ? (t - foundStart->fT) / denom : 1; + return coinStart->fT + (coinEnd->fT - coinStart->fT) * sRatio; +} + +// return true if span overlaps existing and needs to adjust the coincident list +bool SkOpCoincidence::checkOverlap(SkCoincidentSpans* check, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + SkTDArray* overlaps) const { + if (!Ordered(coinSeg, oppSeg)) { + if (oppTs < oppTe) { + return this->checkOverlap(check, oppSeg, coinSeg, oppTs, oppTe, coinTs, coinTe, + overlaps); + } + return this->checkOverlap(check, oppSeg, coinSeg, oppTe, oppTs, coinTe, coinTs, overlaps); + } + bool swapOpp = oppTs > oppTe; + if (swapOpp) { + using std::swap; + swap(oppTs, oppTe); + } + do { + if (check->coinPtTStart()->segment() != coinSeg) { + continue; + } + if (check->oppPtTStart()->segment() != oppSeg) { + continue; + } + double checkTs = check->coinPtTStart()->fT; + double checkTe = check->coinPtTEnd()->fT; + bool coinOutside = coinTe < checkTs || coinTs > checkTe; + double oCheckTs = check->oppPtTStart()->fT; + double oCheckTe = check->oppPtTEnd()->fT; + if (swapOpp) { + if (oCheckTs <= oCheckTe) { + return false; + } + using std::swap; + swap(oCheckTs, oCheckTe); + } + bool oppOutside = oppTe < oCheckTs || oppTs > oCheckTe; + if (coinOutside && oppOutside) { + continue; + } + bool coinInside = coinTe <= checkTe && coinTs >= checkTs; + bool oppInside = oppTe <= oCheckTe && oppTs >= oCheckTs; + if (coinInside && oppInside) { // already included, do nothing + return false; + } + *overlaps->append() = check; // partial overlap, extend existing entry + } while ((check = check->next())); + return true; +} + +/* Please keep this in sync with debugAddIfMissing() */ +// note that over1s, over1e, over2s, over2e are ordered +bool SkOpCoincidence::addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, SkOpSegment* coinSeg, SkOpSegment* oppSeg, bool* added + SkDEBUGPARAMS(const SkOpPtT* over1e) SkDEBUGPARAMS(const SkOpPtT* over2e)) { + SkASSERT(tStart < tEnd); + SkASSERT(over1s->fT < over1e->fT); + SkASSERT(between(over1s->fT, tStart, over1e->fT)); + SkASSERT(between(over1s->fT, tEnd, over1e->fT)); + SkASSERT(over2s->fT < over2e->fT); + SkASSERT(between(over2s->fT, tStart, over2e->fT)); + SkASSERT(between(over2s->fT, tEnd, over2e->fT)); + SkASSERT(over1s->segment() == over1e->segment()); + SkASSERT(over2s->segment() == over2e->segment()); + SkASSERT(over1s->segment() == over2s->segment()); + SkASSERT(over1s->segment() != coinSeg); + SkASSERT(over1s->segment() != oppSeg); + SkASSERT(coinSeg != oppSeg); + double coinTs, coinTe, oppTs, oppTe; + coinTs = TRange(over1s, tStart, coinSeg SkDEBUGPARAMS(over1e)); + coinTe = TRange(over1s, tEnd, coinSeg SkDEBUGPARAMS(over1e)); + SkOpSpanBase::Collapsed result = coinSeg->collapsed(coinTs, coinTe); + if (SkOpSpanBase::Collapsed::kNo != result) { + return SkOpSpanBase::Collapsed::kYes == result; + } + oppTs = TRange(over2s, tStart, oppSeg SkDEBUGPARAMS(over2e)); + oppTe = TRange(over2s, tEnd, oppSeg SkDEBUGPARAMS(over2e)); + result = oppSeg->collapsed(oppTs, oppTe); + if (SkOpSpanBase::Collapsed::kNo != result) { + return SkOpSpanBase::Collapsed::kYes == result; + } + if (coinTs > coinTe) { + using std::swap; + swap(coinTs, coinTe); + swap(oppTs, oppTe); + } + (void) this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, added); + return true; +} + +/* Please keep this in sync with debugAddOrOverlap() */ +// If this is called by addEndMovedSpans(), a returned false propogates out to an abort. +// If this is called by AddIfMissing(), a returned false indicates there was nothing to add +bool SkOpCoincidence::addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, bool* added) { + SkTDArray overlaps; + FAIL_IF(!fTop); + if (!this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { + return true; + } + if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, + coinTe, oppTs, oppTe, &overlaps)) { + return true; + } + SkCoincidentSpans* overlap = overlaps.count() ? overlaps[0] : nullptr; + for (int index = 1; index < overlaps.count(); ++index) { // combine overlaps before continuing + SkCoincidentSpans* test = overlaps[index]; + if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { + overlap->setCoinPtTStart(test->coinPtTStart()); + } + if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { + overlap->setCoinPtTEnd(test->coinPtTEnd()); + } + if (overlap->flipped() + ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT + : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { + overlap->setOppPtTStart(test->oppPtTStart()); + } + if (overlap->flipped() + ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT + : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { + overlap->setOppPtTEnd(test->oppPtTEnd()); + } + if (!fHead || !this->release(fHead, test)) { + SkAssertResult(this->release(fTop, test)); + } + } + const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); + const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); + if (overlap && cs && ce && overlap->contains(cs, ce)) { + return true; + } + FAIL_IF(cs == ce && cs); + const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); + const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); + if (overlap && os && oe && overlap->contains(os, oe)) { + return true; + } + FAIL_IF(cs && cs->deleted()); + FAIL_IF(os && os->deleted()); + FAIL_IF(ce && ce->deleted()); + FAIL_IF(oe && oe->deleted()); + const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; + const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; + FAIL_IF(csExisting && csExisting == ceExisting); +// FAIL_IF(csExisting && (csExisting == ce || +// csExisting->contains(ceExisting ? ceExisting : ce))); + FAIL_IF(ceExisting && (ceExisting == cs || + ceExisting->contains(csExisting ? csExisting : cs))); + const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; + const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; + FAIL_IF(osExisting && osExisting == oeExisting); + FAIL_IF(osExisting && (osExisting == oe || + osExisting->contains(oeExisting ? oeExisting : oe))); + FAIL_IF(oeExisting && (oeExisting == os || + oeExisting->contains(osExisting ? osExisting : os))); + // extra line in debug code + this->debugValidate(); + if (!cs || !os) { + SkOpPtT* csWritable = cs ? const_cast(cs) + : coinSeg->addT(coinTs); + if (csWritable == ce) { + return true; + } + SkOpPtT* osWritable = os ? const_cast(os) + : oppSeg->addT(oppTs); + FAIL_IF(!csWritable || !osWritable); + csWritable->span()->addOpp(osWritable->span()); + cs = csWritable; + os = osWritable->active(); + FAIL_IF(!os); + FAIL_IF((ce && ce->deleted()) || (oe && oe->deleted())); + } + if (!ce || !oe) { + SkOpPtT* ceWritable = ce ? const_cast(ce) + : coinSeg->addT(coinTe); + SkOpPtT* oeWritable = oe ? const_cast(oe) + : oppSeg->addT(oppTe); + FAIL_IF(!ceWritable->span()->addOpp(oeWritable->span())); + ce = ceWritable; + oe = oeWritable; + } + this->debugValidate(); + FAIL_IF(cs->deleted()); + FAIL_IF(os->deleted()); + FAIL_IF(ce->deleted()); + FAIL_IF(oe->deleted()); + FAIL_IF(cs->contains(ce) || os->contains(oe)); + bool result = true; + if (overlap) { + if (overlap->coinPtTStart()->segment() == coinSeg) { + result = overlap->extend(cs, ce, os, oe); + } else { + if (os->fT > oe->fT) { + using std::swap; + swap(cs, ce); + swap(os, oe); + } + result = overlap->extend(os, oe, cs, ce); + } +#if DEBUG_COINCIDENCE_VERBOSE + if (result) { + overlaps[0]->debugShow(); + } +#endif + } else { + this->add(cs, ce, os, oe); +#if DEBUG_COINCIDENCE_VERBOSE + fHead->debugShow(); +#endif + } + this->debugValidate(); + if (result) { + *added = true; + } + return true; +} + +// Please keep this in sync with debugAddMissing() +/* detects overlaps of different coincident runs on same segment */ +/* does not detect overlaps for pairs without any segments in common */ +// returns true if caller should loop again +bool SkOpCoincidence::addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()) { + SkCoincidentSpans* outer = fHead; + *added = false; + if (!outer) { + return true; + } + fTop = outer; + fHead = nullptr; + do { + // addifmissing can modify the list that this is walking + // save head so that walker can iterate over old data unperturbed + // addifmissing adds to head freely then add saved head in the end + const SkOpPtT* ocs = outer->coinPtTStart(); + FAIL_IF(ocs->deleted()); + const SkOpSegment* outerCoin = ocs->segment(); + FAIL_IF(outerCoin->done()); + const SkOpPtT* oos = outer->oppPtTStart(); + if (oos->deleted()) { + return true; + } + const SkOpSegment* outerOpp = oos->segment(); + SkOPASSERT(!outerOpp->done()); + SkOpSegment* outerCoinWritable = const_cast(outerCoin); + SkOpSegment* outerOppWritable = const_cast(outerOpp); + SkCoincidentSpans* inner = outer; +#ifdef IS_FUZZING_WITH_LIBFUZZER + int safetyNet = 1000; +#endif + while ((inner = inner->next())) { +#ifdef IS_FUZZING_WITH_LIBFUZZER + if (!--safetyNet) { + return false; + } +#endif + this->debugValidate(); + double overS, overE; + const SkOpPtT* ics = inner->coinPtTStart(); + FAIL_IF(ics->deleted()); + const SkOpSegment* innerCoin = ics->segment(); + FAIL_IF(innerCoin->done()); + const SkOpPtT* ios = inner->oppPtTStart(); + FAIL_IF(ios->deleted()); + const SkOpSegment* innerOpp = ios->segment(); + SkOPASSERT(!innerOpp->done()); + SkOpSegment* innerCoinWritable = const_cast(innerCoin); + SkOpSegment* innerOppWritable = const_cast(innerOpp); + if (outerCoin == innerCoin) { + const SkOpPtT* oce = outer->coinPtTEnd(); + if (oce->deleted()) { + return true; + } + const SkOpPtT* ice = inner->coinPtTEnd(); + FAIL_IF(ice->deleted()); + if (outerOpp != innerOpp && this->overlap(ocs, oce, ics, ice, &overS, &overE)) { + FAIL_IF(!this->addIfMissing(ocs->starter(oce), ics->starter(ice), + overS, overE, outerOppWritable, innerOppWritable, added + SkDEBUGPARAMS(ocs->debugEnder(oce)) + SkDEBUGPARAMS(ics->debugEnder(ice)))); + } + } else if (outerCoin == innerOpp) { + const SkOpPtT* oce = outer->coinPtTEnd(); + FAIL_IF(oce->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + FAIL_IF(ioe->deleted()); + if (outerOpp != innerCoin && this->overlap(ocs, oce, ios, ioe, &overS, &overE)) { + FAIL_IF(!this->addIfMissing(ocs->starter(oce), ios->starter(ioe), + overS, overE, outerOppWritable, innerCoinWritable, added + SkDEBUGPARAMS(ocs->debugEnder(oce)) + SkDEBUGPARAMS(ios->debugEnder(ioe)))); + } + } else if (outerOpp == innerCoin) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + FAIL_IF(ooe->deleted()); + const SkOpPtT* ice = inner->coinPtTEnd(); + FAIL_IF(ice->deleted()); + SkASSERT(outerCoin != innerOpp); + if (this->overlap(oos, ooe, ics, ice, &overS, &overE)) { + FAIL_IF(!this->addIfMissing(oos->starter(ooe), ics->starter(ice), + overS, overE, outerCoinWritable, innerOppWritable, added + SkDEBUGPARAMS(oos->debugEnder(ooe)) + SkDEBUGPARAMS(ics->debugEnder(ice)))); + } + } else if (outerOpp == innerOpp) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + FAIL_IF(ooe->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + if (ioe->deleted()) { + return true; + } + SkASSERT(outerCoin != innerCoin); + if (this->overlap(oos, ooe, ios, ioe, &overS, &overE)) { + FAIL_IF(!this->addIfMissing(oos->starter(ooe), ios->starter(ioe), + overS, overE, outerCoinWritable, innerCoinWritable, added + SkDEBUGPARAMS(oos->debugEnder(ooe)) + SkDEBUGPARAMS(ios->debugEnder(ioe)))); + } + } + this->debugValidate(); + } + } while ((outer = outer->next())); + this->restoreHead(); + return true; +} + +bool SkOpCoincidence::addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, + const SkOpSegment* seg2, const SkOpSegment* seg2o, + const SkOpPtT* overS, const SkOpPtT* overE) { + const SkOpPtT* s1 = overS->find(seg1); + const SkOpPtT* e1 = overE->find(seg1); + FAIL_IF(!s1); + FAIL_IF(!e1); + if (!s1->starter(e1)->span()->upCast()->windValue()) { + s1 = overS->find(seg1o); + e1 = overE->find(seg1o); + FAIL_IF(!s1); + FAIL_IF(!e1); + if (!s1->starter(e1)->span()->upCast()->windValue()) { + return true; + } + } + const SkOpPtT* s2 = overS->find(seg2); + const SkOpPtT* e2 = overE->find(seg2); + FAIL_IF(!s2); + FAIL_IF(!e2); + if (!s2->starter(e2)->span()->upCast()->windValue()) { + s2 = overS->find(seg2o); + e2 = overE->find(seg2o); + FAIL_IF(!s2); + FAIL_IF(!e2); + if (!s2->starter(e2)->span()->upCast()->windValue()) { + return true; + } + } + if (s1->segment() == s2->segment()) { + return true; + } + if (s1->fT > e1->fT) { + using std::swap; + swap(s1, e1); + swap(s2, e2); + } + this->add(s1, e1, s2, e2); + return true; +} + +bool SkOpCoincidence::contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const { + if (this->contains(fHead, seg, opp, oppT)) { + return true; + } + if (this->contains(fTop, seg, opp, oppT)) { + return true; + } + return false; +} + +bool SkOpCoincidence::contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, + const SkOpSegment* opp, double oppT) const { + if (!coin) { + return false; + } + do { + if (coin->coinPtTStart()->segment() == seg && coin->oppPtTStart()->segment() == opp + && between(coin->oppPtTStart()->fT, oppT, coin->oppPtTEnd()->fT)) { + return true; + } + if (coin->oppPtTStart()->segment() == seg && coin->coinPtTStart()->segment() == opp + && between(coin->coinPtTStart()->fT, oppT, coin->coinPtTEnd()->fT)) { + return true; + } + } while ((coin = coin->next())); + return false; +} + +bool SkOpCoincidence::contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const { + const SkCoincidentSpans* test = fHead; + if (!test) { + return false; + } + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (!Ordered(coinPtTStart, oppPtTStart)) { + using std::swap; + swap(coinSeg, oppSeg); + swap(coinPtTStart, oppPtTStart); + swap(coinPtTEnd, oppPtTEnd); + if (coinPtTStart->fT > coinPtTEnd->fT) { + swap(coinPtTStart, coinPtTEnd); + swap(oppPtTStart, oppPtTEnd); + } + } + double oppMinT = SkTMin(oppPtTStart->fT, oppPtTEnd->fT); + double oppMaxT = SkTMax(oppPtTStart->fT, oppPtTEnd->fT); + do { + if (coinSeg != test->coinPtTStart()->segment()) { + continue; + } + if (coinPtTStart->fT < test->coinPtTStart()->fT) { + continue; + } + if (coinPtTEnd->fT > test->coinPtTEnd()->fT) { + continue; + } + if (oppSeg != test->oppPtTStart()->segment()) { + continue; + } + if (oppMinT < SkTMin(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { + continue; + } + if (oppMaxT > SkTMax(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { + continue; + } + return true; + } while ((test = test->next())); + return false; +} + +void SkOpCoincidence::correctEnds(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + coin->correctEnds(); + } while ((coin = coin->next())); +} + +// walk span sets in parallel, moving winding from one to the other +bool SkOpCoincidence::apply(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return true; + } + do { + SkOpSpanBase* startSpan = coin->coinPtTStartWritable()->span(); + FAIL_IF(!startSpan->upCastable()); + SkOpSpan* start = startSpan->upCast(); + if (start->deleted()) { + continue; + } + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + FAIL_IF(start != start->starter(end)); + bool flipped = coin->flipped(); + SkOpSpanBase* oStartBase = (flipped ? coin->oppPtTEndWritable() + : coin->oppPtTStartWritable())->span(); + FAIL_IF(!oStartBase->upCastable()); + SkOpSpan* oStart = oStartBase->upCast(); + if (oStart->deleted()) { + continue; + } + const SkOpSpanBase* oEnd = (flipped ? coin->oppPtTStart() : coin->oppPtTEnd())->span(); + SkASSERT(oStart == oStart->starter(oEnd)); + SkOpSegment* segment = start->segment(); + SkOpSegment* oSegment = oStart->segment(); + bool operandSwap = segment->operand() != oSegment->operand(); + if (flipped) { + if (oEnd->deleted()) { + continue; + } + do { + SkOpSpanBase* oNext = oStart->next(); + if (oNext == oEnd) { + break; + } + FAIL_IF(!oNext->upCastable()); + oStart = oNext->upCast(); + } while (true); + } + do { + int windValue = start->windValue(); + int oppValue = start->oppValue(); + int oWindValue = oStart->windValue(); + int oOppValue = oStart->oppValue(); + // winding values are added or subtracted depending on direction and wind type + // same or opposite values are summed depending on the operand value + int windDiff = operandSwap ? oOppValue : oWindValue; + int oWindDiff = operandSwap ? oppValue : windValue; + if (!flipped) { + windDiff = -windDiff; + oWindDiff = -oWindDiff; + } + bool addToStart = windValue && (windValue > windDiff || (windValue == windDiff + && oWindValue <= oWindDiff)); + if (addToStart ? start->done() : oStart->done()) { + addToStart ^= true; + } + if (addToStart) { + if (operandSwap) { + using std::swap; + swap(oWindValue, oOppValue); + } + if (flipped) { + windValue -= oWindValue; + oppValue -= oOppValue; + } else { + windValue += oWindValue; + oppValue += oOppValue; + } + if (segment->isXor()) { + windValue &= 1; + } + if (segment->oppXor()) { + oppValue &= 1; + } + oWindValue = oOppValue = 0; + } else { + if (operandSwap) { + using std::swap; + swap(windValue, oppValue); + } + if (flipped) { + oWindValue -= windValue; + oOppValue -= oppValue; + } else { + oWindValue += windValue; + oOppValue += oppValue; + } + if (oSegment->isXor()) { + oWindValue &= 1; + } + if (oSegment->oppXor()) { + oOppValue &= 1; + } + windValue = oppValue = 0; + } +#if 0 && DEBUG_COINCIDENCE + SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", segment->debugID(), + start->debugID(), windValue, oppValue); + SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", oSegment->debugID(), + oStart->debugID(), oWindValue, oOppValue); +#endif + FAIL_IF(windValue <= -1); + start->setWindValue(windValue); + start->setOppValue(oppValue); + FAIL_IF(oWindValue <= -1); + oStart->setWindValue(oWindValue); + oStart->setOppValue(oOppValue); + if (!windValue && !oppValue) { + segment->markDone(start); + } + if (!oWindValue && !oOppValue) { + oSegment->markDone(oStart); + } + SkOpSpanBase* next = start->next(); + SkOpSpanBase* oNext = flipped ? oStart->prev() : oStart->next(); + if (next == end) { + break; + } + FAIL_IF(!next->upCastable()); + start = next->upCast(); + // if the opposite ran out too soon, just reuse the last span + if (!oNext || !oNext->upCastable()) { + oNext = oStart; + } + oStart = oNext->upCast(); + } while (true); + } while ((coin = coin->next())); + return true; +} + +// Please keep this in sync with debugRelease() +bool SkOpCoincidence::release(SkCoincidentSpans* coin, SkCoincidentSpans* remove) { + SkCoincidentSpans* head = coin; + SkCoincidentSpans* prev = nullptr; + SkCoincidentSpans* next; + do { + next = coin->next(); + if (coin == remove) { + if (prev) { + prev->setNext(next); + } else if (head == fHead) { + fHead = next; + } else { + fTop = next; + } + break; + } + prev = coin; + } while ((coin = next)); + return coin != nullptr; +} + +void SkOpCoincidence::releaseDeleted(SkCoincidentSpans* coin) { + if (!coin) { + return; + } + SkCoincidentSpans* head = coin; + SkCoincidentSpans* prev = nullptr; + SkCoincidentSpans* next; + do { + next = coin->next(); + if (coin->coinPtTStart()->deleted()) { + SkOPASSERT(coin->flipped() ? coin->oppPtTEnd()->deleted() : + coin->oppPtTStart()->deleted()); + if (prev) { + prev->setNext(next); + } else if (head == fHead) { + fHead = next; + } else { + fTop = next; + } + } else { + SkOPASSERT(coin->flipped() ? !coin->oppPtTEnd()->deleted() : + !coin->oppPtTStart()->deleted()); + prev = coin; + } + } while ((coin = next)); +} + +void SkOpCoincidence::releaseDeleted() { + this->releaseDeleted(fHead); + this->releaseDeleted(fTop); +} + +void SkOpCoincidence::restoreHead() { + SkCoincidentSpans** headPtr = &fHead; + while (*headPtr) { + headPtr = (*headPtr)->nextPtr(); + } + *headPtr = fTop; + fTop = nullptr; + // segments may have collapsed in the meantime; remove empty referenced segments + headPtr = &fHead; + while (*headPtr) { + SkCoincidentSpans* test = *headPtr; + if (test->coinPtTStart()->segment()->done() || test->oppPtTStart()->segment()->done()) { + *headPtr = test->next(); + continue; + } + headPtr = (*headPtr)->nextPtr(); + } +} + +// Please keep this in sync with debugExpand() +// expand the range by checking adjacent spans for coincidence +bool SkOpCoincidence::expand(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return false; + } + bool expanded = false; + do { + if (coin->expand()) { + // check to see if multiple spans expanded so they are now identical + SkCoincidentSpans* test = fHead; + do { + if (coin == test) { + continue; + } + if (coin->coinPtTStart() == test->coinPtTStart() + && coin->oppPtTStart() == test->oppPtTStart()) { + this->release(fHead, test); + break; + } + } while ((test = test->next())); + expanded = true; + } + } while ((coin = coin->next())); + return expanded; +} + +bool SkOpCoincidence::findOverlaps(SkOpCoincidence* overlaps DEBUG_COIN_DECLARE_PARAMS()) const { + DEBUG_SET_PHASE(); + overlaps->fHead = overlaps->fTop = nullptr; + SkCoincidentSpans* outer = fHead; + while (outer) { + const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); + const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); + SkCoincidentSpans* inner = outer; + while ((inner = inner->next())) { + const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); + if (outerCoin == innerCoin) { + continue; // both winners are the same segment, so there's no additional overlap + } + const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); + const SkOpPtT* overlapS; + const SkOpPtT* overlapE; + if ((outerOpp == innerCoin && SkOpPtT::Overlaps(outer->oppPtTStart(), + outer->oppPtTEnd(),inner->coinPtTStart(), inner->coinPtTEnd(), &overlapS, + &overlapE)) + || (outerCoin == innerOpp && SkOpPtT::Overlaps(outer->coinPtTStart(), + outer->coinPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), + &overlapS, &overlapE)) + || (outerOpp == innerOpp && SkOpPtT::Overlaps(outer->oppPtTStart(), + outer->oppPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), + &overlapS, &overlapE))) { + if (!overlaps->addOverlap(outerCoin, outerOpp, innerCoin, innerOpp, + overlapS, overlapE)) { + return false; + } + } + } + outer = outer->next(); + } + return true; +} + +void SkOpCoincidence::fixUp(SkOpPtT* deleted, const SkOpPtT* kept) { + SkOPASSERT(deleted != kept); + if (fHead) { + this->fixUp(fHead, deleted, kept); + } + if (fTop) { + this->fixUp(fTop, deleted, kept); + } +} + +void SkOpCoincidence::fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept) { + SkCoincidentSpans* head = coin; + do { + if (coin->coinPtTStart() == deleted) { + if (coin->coinPtTEnd()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setCoinPtTStart(kept); + } + if (coin->coinPtTEnd() == deleted) { + if (coin->coinPtTStart()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setCoinPtTEnd(kept); + } + if (coin->oppPtTStart() == deleted) { + if (coin->oppPtTEnd()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setOppPtTStart(kept); + } + if (coin->oppPtTEnd() == deleted) { + if (coin->oppPtTStart()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setOppPtTEnd(kept); + } + } while ((coin = coin->next())); +} + +// Please keep this in sync with debugMark() +/* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ +bool SkOpCoincidence::mark(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return true; + } + do { + SkOpSpanBase* startBase = coin->coinPtTStartWritable()->span(); + FAIL_IF(!startBase->upCastable()); + SkOpSpan* start = startBase->upCast(); + FAIL_IF(start->deleted()); + SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); + SkOPASSERT(!end->deleted()); + SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); + SkOPASSERT(!oStart->deleted()); + SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); + FAIL_IF(oEnd->deleted()); + bool flipped = coin->flipped(); + if (flipped) { + using std::swap; + swap(oStart, oEnd); + } + /* coin and opp spans may not match up. Mark the ends, and then let the interior + get marked as many times as the spans allow */ + FAIL_IF(!oStart->upCastable()); + start->insertCoincidence(oStart->upCast()); + end->insertCoinEnd(oEnd); + const SkOpSegment* segment = start->segment(); + const SkOpSegment* oSegment = oStart->segment(); + SkOpSpanBase* next = start; + SkOpSpanBase* oNext = oStart; + bool ordered; + FAIL_IF(!coin->ordered(&ordered)); + while ((next = next->upCast()->next()) != end) { + FAIL_IF(!next->upCastable()); + FAIL_IF(!next->upCast()->insertCoincidence(oSegment, flipped, ordered)); + } + while ((oNext = oNext->upCast()->next()) != oEnd) { + FAIL_IF(!oNext->upCastable()); + FAIL_IF(!oNext->upCast()->insertCoincidence(segment, flipped, ordered)); + } + } while ((coin = coin->next())); + return true; +} + +// Please keep in sync with debugMarkCollapsed() +void SkOpCoincidence::markCollapsed(SkCoincidentSpans* coin, SkOpPtT* test) { + SkCoincidentSpans* head = coin; + while (coin) { + if (coin->collapsed(test)) { + if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { + coin->coinPtTStartWritable()->segment()->markAllDone(); + } + if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { + coin->oppPtTStartWritable()->segment()->markAllDone(); + } + this->release(head, coin); + } + coin = coin->next(); + } +} + +// Please keep in sync with debugMarkCollapsed() +void SkOpCoincidence::markCollapsed(SkOpPtT* test) { + markCollapsed(fHead, test); + markCollapsed(fTop, test); +} + +bool SkOpCoincidence::Ordered(const SkOpSegment* coinSeg, const SkOpSegment* oppSeg) { + if (coinSeg->verb() < oppSeg->verb()) { + return true; + } + if (coinSeg->verb() > oppSeg->verb()) { + return false; + } + int count = (SkPathOpsVerbToPoints(coinSeg->verb()) + 1) * 2; + const SkScalar* cPt = &coinSeg->pts()[0].fX; + const SkScalar* oPt = &oppSeg->pts()[0].fX; + for (int index = 0; index < count; ++index) { + if (*cPt < *oPt) { + return true; + } + if (*cPt > *oPt) { + return false; + } + ++cPt; + ++oPt; + } + return true; +} + +bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, + const SkOpPtT* coin2s, const SkOpPtT* coin2e, double* overS, double* overE) const { + SkASSERT(coin1s->segment() == coin2s->segment()); + *overS = SkTMax(SkTMin(coin1s->fT, coin1e->fT), SkTMin(coin2s->fT, coin2e->fT)); + *overE = SkTMin(SkTMax(coin1s->fT, coin1e->fT), SkTMax(coin2s->fT, coin2e->fT)); + return *overS < *overE; +} + +// Commented-out lines keep this in sync with debugRelease() +void SkOpCoincidence::release(const SkOpSegment* deleted) { + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + if (coin->coinPtTStart()->segment() == deleted + || coin->coinPtTEnd()->segment() == deleted + || coin->oppPtTStart()->segment() == deleted + || coin->oppPtTEnd()->segment() == deleted) { + this->release(fHead, coin); + } + } while ((coin = coin->next())); +} diff --git a/gfx/skia/skia/src/pathops/SkOpCoincidence.h b/gfx/skia/skia/src/pathops/SkOpCoincidence.h new file mode 100644 index 0000000000..283c28e105 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpCoincidence.h @@ -0,0 +1,303 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpCoincidence_DEFINED +#define SkOpCoincidence_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/core/SkArenaAlloc.h" +#include "src/pathops/SkOpSpan.h" +#include "src/pathops/SkPathOpsTypes.h" + +class SkOpPtT; +class SkOpSpanBase; + +class SkCoincidentSpans { +public: + const SkOpPtT* coinPtTEnd() const; + const SkOpPtT* coinPtTStart() const; + + // These return non-const pointers so that, as copies, they can be added + // to a new span pair + SkOpPtT* coinPtTEndWritable() const { return const_cast(fCoinPtTEnd); } + SkOpPtT* coinPtTStartWritable() const { return const_cast(fCoinPtTStart); } + + bool collapsed(const SkOpPtT* ) const; + bool contains(const SkOpPtT* s, const SkOpPtT* e) const; + void correctEnds(); + void correctOneEnd(const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) ); + +#if DEBUG_COIN + void debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const; + void debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) const) const; + bool debugExpand(SkPathOpsDebug::GlitchLog* log) const; +#endif + + const char* debugID() const { +#if DEBUG_COIN + return fGlobalState->debugCoinDictEntry().fFunctionName; +#else + return nullptr; +#endif + } + + void debugShow() const; +#ifdef SK_DEBUG + void debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, + const SkOpGlobalState* debugState) const; +#endif + void dump() const; + bool expand(); + bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); + bool flipped() const { return fOppPtTStart->fT > fOppPtTEnd->fT; } + SkDEBUGCODE(SkOpGlobalState* globalState() { return fGlobalState; }) + + void init(SkDEBUGCODE(SkOpGlobalState* globalState)) { + sk_bzero(this, sizeof(*this)); + SkDEBUGCODE(fGlobalState = globalState); + } + + SkCoincidentSpans* next() { return fNext; } + const SkCoincidentSpans* next() const { return fNext; } + SkCoincidentSpans** nextPtr() { return &fNext; } + const SkOpPtT* oppPtTStart() const; + const SkOpPtT* oppPtTEnd() const; + // These return non-const pointers so that, as copies, they can be added + // to a new span pair + SkOpPtT* oppPtTStartWritable() const { return const_cast(fOppPtTStart); } + SkOpPtT* oppPtTEndWritable() const { return const_cast(fOppPtTEnd); } + bool ordered(bool* result) const; + + void set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); + + void setCoinPtTEnd(const SkOpPtT* ptT) { + SkOPASSERT(ptT == ptT->span()->ptT()); + SkOPASSERT(!fCoinPtTStart || ptT->fT != fCoinPtTStart->fT); + SkASSERT(!fCoinPtTStart || fCoinPtTStart->segment() == ptT->segment()); + fCoinPtTEnd = ptT; + ptT->setCoincident(); + } + + void setCoinPtTStart(const SkOpPtT* ptT) { + SkOPASSERT(ptT == ptT->span()->ptT()); + SkOPASSERT(!fCoinPtTEnd || ptT->fT != fCoinPtTEnd->fT); + SkASSERT(!fCoinPtTEnd || fCoinPtTEnd->segment() == ptT->segment()); + fCoinPtTStart = ptT; + ptT->setCoincident(); + } + + void setEnds(const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTEnd) { + this->setCoinPtTEnd(coinPtTEnd); + this->setOppPtTEnd(oppPtTEnd); + } + + void setOppPtTEnd(const SkOpPtT* ptT) { + SkOPASSERT(ptT == ptT->span()->ptT()); + SkOPASSERT(!fOppPtTStart || ptT->fT != fOppPtTStart->fT); + SkASSERT(!fOppPtTStart || fOppPtTStart->segment() == ptT->segment()); + fOppPtTEnd = ptT; + ptT->setCoincident(); + } + + void setOppPtTStart(const SkOpPtT* ptT) { + SkOPASSERT(ptT == ptT->span()->ptT()); + SkOPASSERT(!fOppPtTEnd || ptT->fT != fOppPtTEnd->fT); + SkASSERT(!fOppPtTEnd || fOppPtTEnd->segment() == ptT->segment()); + fOppPtTStart = ptT; + ptT->setCoincident(); + } + + void setStarts(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { + this->setCoinPtTStart(coinPtTStart); + this->setOppPtTStart(oppPtTStart); + } + + void setNext(SkCoincidentSpans* next) { fNext = next; } + +private: + SkCoincidentSpans* fNext; + const SkOpPtT* fCoinPtTStart; + const SkOpPtT* fCoinPtTEnd; + const SkOpPtT* fOppPtTStart; + const SkOpPtT* fOppPtTEnd; + SkDEBUGCODE(SkOpGlobalState* fGlobalState); +}; + +class SkOpCoincidence { +public: + SkOpCoincidence(SkOpGlobalState* globalState) + : fHead(nullptr) + , fTop(nullptr) + , fGlobalState(globalState) + , fContinue(false) + , fSpanDeleted(false) + , fPtAllocated(false) + , fCoinExtended(false) + , fSpanMerged(false) { + globalState->setCoincidence(this); + } + + void add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, + SkOpPtT* oppPtTEnd); + bool addEndMovedSpans(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool addExpanded(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()); + bool apply(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const; + void correctEnds(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + +#if DEBUG_COIN + void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const; + void debugAddExpanded(SkPathOpsDebug::GlitchLog* ) const; + void debugAddMissing(SkPathOpsDebug::GlitchLog* , bool* added) const; + void debugAddOrOverlap(SkPathOpsDebug::GlitchLog* log, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + bool* added) const; +#endif + + const SkOpAngle* debugAngle(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugAngle(id), nullptr); + } + + void debugCheckBetween() const; + +#if DEBUG_COIN + void debugCheckValid(SkPathOpsDebug::GlitchLog* log) const; +#endif + + SkOpContour* debugContour(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugContour(id), nullptr); + } + +#if DEBUG_COIN + void debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const; + bool debugExpand(SkPathOpsDebug::GlitchLog* ) const; + void debugMark(SkPathOpsDebug::GlitchLog* ) const; + void debugMarkCollapsed(SkPathOpsDebug::GlitchLog* , + const SkCoincidentSpans* coin, const SkOpPtT* test) const; + void debugMarkCollapsed(SkPathOpsDebug::GlitchLog* , const SkOpPtT* test) const; +#endif + + const SkOpPtT* debugPtT(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugPtT(id), nullptr); + } + + const SkOpSegment* debugSegment(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugSegment(id), nullptr); + } + +#if DEBUG_COIN + void debugRelease(SkPathOpsDebug::GlitchLog* , const SkCoincidentSpans* , + const SkCoincidentSpans* ) const; + void debugRelease(SkPathOpsDebug::GlitchLog* , const SkOpSegment* ) const; +#endif + void debugShowCoincidence() const; + + const SkOpSpanBase* debugSpan(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugSpan(id), nullptr); + } + + void debugValidate() const; + void dump() const; + bool expand(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, + const SkOpPtT* oppPtTEnd); + bool findOverlaps(SkOpCoincidence* DEBUG_COIN_DECLARE_PARAMS()) const; + void fixUp(SkOpPtT* deleted, const SkOpPtT* kept); + + SkOpGlobalState* globalState() { + return fGlobalState; + } + + const SkOpGlobalState* globalState() const { + return fGlobalState; + } + + bool isEmpty() const { + return !fHead && !fTop; + } + + bool mark(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + void markCollapsed(SkOpPtT* ); + + static bool Ordered(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { + return Ordered(coinPtTStart->segment(), oppPtTStart->segment()); + } + + static bool Ordered(const SkOpSegment* coin, const SkOpSegment* opp); + void release(const SkOpSegment* ); + void releaseDeleted(); + +private: + void add(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, + const SkOpPtT* oppPtTEnd) { + this->add(const_cast(coinPtTStart), const_cast(coinPtTEnd), + const_cast(oppPtTStart), const_cast(oppPtTEnd)); + } + + bool addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan); + bool addEndMovedSpans(const SkOpPtT* ptT); + + bool addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, SkOpSegment* coinSeg, SkOpSegment* oppSeg, + bool* added + SkDEBUGPARAMS(const SkOpPtT* over1e) SkDEBUGPARAMS(const SkOpPtT* over2e)); + bool addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, bool* added); + bool addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, + const SkOpSegment* seg2, const SkOpSegment* seg2o, + const SkOpPtT* overS, const SkOpPtT* overE); + bool checkOverlap(SkCoincidentSpans* check, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + SkTDArray* overlaps) const; + bool contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const; + bool contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, + const SkOpSegment* opp, double oppT) const; +#if DEBUG_COIN + void debugAddIfMissing(SkPathOpsDebug::GlitchLog* , + const SkCoincidentSpans* outer, const SkOpPtT* over1s, + const SkOpPtT* over1e) const; + void debugAddIfMissing(SkPathOpsDebug::GlitchLog* , + const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, bool* added, + const SkOpPtT* over1e, const SkOpPtT* over2e) const; + void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* , + const SkOpSpan* base, const SkOpSpanBase* testSpan) const; + void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* , + const SkOpPtT* ptT) const; +#endif + void fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept); + void markCollapsed(SkCoincidentSpans* head, SkOpPtT* test); + bool overlap(const SkOpPtT* coinStart1, const SkOpPtT* coinEnd1, + const SkOpPtT* coinStart2, const SkOpPtT* coinEnd2, + double* overS, double* overE) const; + bool release(SkCoincidentSpans* coin, SkCoincidentSpans* ); + void releaseDeleted(SkCoincidentSpans* ); + void restoreHead(); + // return coinPtT->segment()->t mapped from overS->fT <= t <= overE->fT + static double TRange(const SkOpPtT* overS, double t, const SkOpSegment* coinPtT + SkDEBUGPARAMS(const SkOpPtT* overE)); + + SkCoincidentSpans* fHead; + SkCoincidentSpans* fTop; + SkOpGlobalState* fGlobalState; + bool fContinue; + bool fSpanDeleted; + bool fPtAllocated; + bool fCoinExtended; + bool fSpanMerged; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpContour.cpp b/gfx/skia/skia/src/pathops/SkOpContour.cpp new file mode 100644 index 0000000000..508c324725 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpContour.cpp @@ -0,0 +1,109 @@ +/* +* Copyright 2013 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ +#include "src/core/SkTSort.h" +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkPathWriter.h" +#include "src/pathops/SkReduceOrder.h" + +void SkOpContour::toPath(SkPathWriter* path) const { + if (!this->count()) { + return; + } + const SkOpSegment* segment = &fHead; + do { + SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path)); + } while ((segment = segment->next())); + path->finishContour(); + path->assemble(); +} + +void SkOpContour::toReversePath(SkPathWriter* path) const { + const SkOpSegment* segment = fTail; + do { + SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path)); + } while ((segment = segment->prev())); + path->finishContour(); + path->assemble(); +} + +SkOpSpan* SkOpContour::undoneSpan() { + SkOpSegment* testSegment = &fHead; + do { + if (testSegment->done()) { + continue; + } + return testSegment->undoneSpan(); + } while ((testSegment = testSegment->next())); + fDone = true; + return nullptr; +} + +void SkOpContourBuilder::addConic(SkPoint pts[3], SkScalar weight) { + this->flush(); + fContour->addConic(pts, weight); +} + +void SkOpContourBuilder::addCubic(SkPoint pts[4]) { + this->flush(); + fContour->addCubic(pts); +} + +void SkOpContourBuilder::addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight) { + if (SkPath::kLine_Verb == verb) { + this->addLine(pts); + return; + } + SkArenaAlloc* allocator = fContour->globalState()->allocator(); + switch (verb) { + case SkPath::kQuad_Verb: { + SkPoint* ptStorage = allocator->makeArrayDefault(3); + memcpy(ptStorage, pts, sizeof(SkPoint) * 3); + this->addQuad(ptStorage); + } break; + case SkPath::kConic_Verb: { + SkPoint* ptStorage = allocator->makeArrayDefault(3); + memcpy(ptStorage, pts, sizeof(SkPoint) * 3); + this->addConic(ptStorage, weight); + } break; + case SkPath::kCubic_Verb: { + SkPoint* ptStorage = allocator->makeArrayDefault(4); + memcpy(ptStorage, pts, sizeof(SkPoint) * 4); + this->addCubic(ptStorage); + } break; + default: + SkASSERT(0); + } +} + +void SkOpContourBuilder::addLine(const SkPoint pts[2]) { + // if the previous line added is the exact opposite, eliminate both + if (fLastIsLine) { + if (fLastLine[0] == pts[1] && fLastLine[1] == pts[0]) { + fLastIsLine = false; + return; + } else { + flush(); + } + } + memcpy(fLastLine, pts, sizeof(fLastLine)); + fLastIsLine = true; +} + +void SkOpContourBuilder::addQuad(SkPoint pts[3]) { + this->flush(); + fContour->addQuad(pts); +} + +void SkOpContourBuilder::flush() { + if (!fLastIsLine) + return; + SkArenaAlloc* allocator = fContour->globalState()->allocator(); + SkPoint* ptStorage = allocator->makeArrayDefault(2); + memcpy(ptStorage, fLastLine, sizeof(fLastLine)); + (void) fContour->addLine(ptStorage); + fLastIsLine = false; +} diff --git a/gfx/skia/skia/src/pathops/SkOpContour.h b/gfx/skia/skia/src/pathops/SkOpContour.h new file mode 100644 index 0000000000..e5a55fe964 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpContour.h @@ -0,0 +1,454 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpContour_DEFINED +#define SkOpContour_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/core/SkTSort.h" +#include "src/pathops/SkOpSegment.h" + +enum class SkOpRayDir; +struct SkOpRayHit; +class SkPathWriter; + +class SkOpContour { +public: + SkOpContour() { + reset(); + } + + bool operator<(const SkOpContour& rh) const { + return fBounds.fTop == rh.fBounds.fTop + ? fBounds.fLeft < rh.fBounds.fLeft + : fBounds.fTop < rh.fBounds.fTop; + } + + void addConic(SkPoint pts[3], SkScalar weight) { + appendSegment().addConic(pts, weight, this); + } + + void addCubic(SkPoint pts[4]) { + appendSegment().addCubic(pts, this); + } + + SkOpSegment* addLine(SkPoint pts[2]) { + SkASSERT(pts[0] != pts[1]); + return appendSegment().addLine(pts, this); + } + + void addQuad(SkPoint pts[3]) { + appendSegment().addQuad(pts, this); + } + + SkOpSegment& appendSegment() { + SkOpSegment* result = fCount++ ? this->globalState()->allocator()->make() + : &fHead; + result->setPrev(fTail); + if (fTail) { + fTail->setNext(result); + } + fTail = result; + return *result; + } + + const SkPathOpsBounds& bounds() const { + return fBounds; + } + + void calcAngles() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + segment->calcAngles(); + } while ((segment = segment->next())); + } + + void complete() { + setBounds(); + } + + int count() const { + return fCount; + } + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + + int debugIndent() const { + return SkDEBUGRELEASE(fDebugIndent, 0); + } + + + const SkOpAngle* debugAngle(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugAngle(id), nullptr); + } + + const SkOpCoincidence* debugCoincidence() const { + return this->globalState()->coincidence(); + } + +#if DEBUG_COIN + void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const; +#endif + + SkOpContour* debugContour(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugContour(id), nullptr); + } + +#if DEBUG_COIN + void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const; + void debugMoveMultiples(SkPathOpsDebug::GlitchLog* ) const; + void debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const; +#endif + + const SkOpPtT* debugPtT(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugPtT(id), nullptr); + } + + const SkOpSegment* debugSegment(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugSegment(id), nullptr); + } + +#if DEBUG_ACTIVE_SPANS + void debugShowActiveSpans(SkString* str) { + SkOpSegment* segment = &fHead; + do { + segment->debugShowActiveSpans(str); + } while ((segment = segment->next())); + } +#endif + + const SkOpSpanBase* debugSpan(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugSpan(id), nullptr); + } + + SkOpGlobalState* globalState() const { + return fState; + } + + void debugValidate() const { +#if DEBUG_VALIDATE + const SkOpSegment* segment = &fHead; + const SkOpSegment* prior = nullptr; + do { + segment->debugValidate(); + SkASSERT(segment->prev() == prior); + prior = segment; + } while ((segment = segment->next())); + SkASSERT(prior == fTail); +#endif + } + + bool done() const { + return fDone; + } + + void dump() const; + void dumpAll() const; + void dumpAngles() const; + void dumpContours() const; + void dumpContoursAll() const; + void dumpContoursAngles() const; + void dumpContoursPts() const; + void dumpContoursPt(int segmentID) const; + void dumpContoursSegment(int segmentID) const; + void dumpContoursSpan(int segmentID) const; + void dumpContoursSpans() const; + void dumpPt(int ) const; + void dumpPts(const char* prefix = "seg") const; + void dumpPtsX(const char* prefix) const; + void dumpSegment(int ) const; + void dumpSegments(const char* prefix = "seg", SkPathOp op = (SkPathOp) -1) const; + void dumpSpan(int ) const; + void dumpSpans() const; + + const SkPoint& end() const { + return fTail->pts()[SkPathOpsVerbToPoints(fTail->verb())]; + } + + SkOpSpan* findSortableTop(SkOpContour* ); + + SkOpSegment* first() { + SkASSERT(fCount > 0); + return &fHead; + } + + const SkOpSegment* first() const { + SkASSERT(fCount > 0); + return &fHead; + } + + void indentDump() const { + SkDEBUGCODE(fDebugIndent += 2); + } + + void init(SkOpGlobalState* globalState, bool operand, bool isXor) { + fState = globalState; + fOperand = operand; + fXor = isXor; + SkDEBUGCODE(fID = globalState->nextContourID()); + } + + int isCcw() const { + return fCcw; + } + + bool isXor() const { + return fXor; + } + + void joinSegments() { + SkOpSegment* segment = &fHead; + SkOpSegment* next; + do { + next = segment->next(); + segment->joinEnds(next ? next : &fHead); + } while ((segment = next)); + } + + void markAllDone() { + SkOpSegment* segment = &fHead; + do { + segment->markAllDone(); + } while ((segment = segment->next())); + } + + // Please keep this aligned with debugMissingCoincidence() + bool missingCoincidence() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + bool result = false; + do { + if (segment->missingCoincidence()) { + result = true; + } + segment = segment->next(); + } while (segment); + return result; + } + + bool moveMultiples() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + if (!segment->moveMultiples()) { + return false; + } + } while ((segment = segment->next())); + return true; + } + + bool moveNearby() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + if (!segment->moveNearby()) { + return false; + } + } while ((segment = segment->next())); + return true; + } + + SkOpContour* next() { + return fNext; + } + + const SkOpContour* next() const { + return fNext; + } + + bool operand() const { + return fOperand; + } + + bool oppXor() const { + return fOppXor; + } + + void outdentDump() const { + SkDEBUGCODE(fDebugIndent -= 2); + } + + void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkArenaAlloc*); + + void reset() { + fTail = nullptr; + fNext = nullptr; + fCount = 0; + fDone = false; + SkDEBUGCODE(fBounds.setLTRB(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin)); + SkDEBUGCODE(fFirstSorted = -1); + SkDEBUGCODE(fDebugIndent = 0); + } + + void resetReverse() { + SkOpContour* next = this; + do { + if (!next->count()) { + continue; + } + next->fCcw = -1; + next->fReverse = false; + } while ((next = next->next())); + } + + bool reversed() const { + return fReverse; + } + + void setBounds() { + SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; + fBounds = segment->bounds(); + while ((segment = segment->next())) { + fBounds.add(segment->bounds()); + } + } + + void setCcw(int ccw) { + fCcw = ccw; + } + + void setGlobalState(SkOpGlobalState* state) { + fState = state; + } + + void setNext(SkOpContour* contour) { +// SkASSERT(!fNext == !!contour); + fNext = contour; + } + + void setOperand(bool isOp) { + fOperand = isOp; + } + + void setOppXor(bool isOppXor) { + fOppXor = isOppXor; + } + + void setReverse() { + fReverse = true; + } + + void setXor(bool isXor) { + fXor = isXor; + } + + bool sortAngles() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + FAIL_IF(!segment->sortAngles()); + } while ((segment = segment->next())); + return true; + } + + const SkPoint& start() const { + return fHead.pts()[0]; + } + + void toPartialBackward(SkPathWriter* path) const { + const SkOpSegment* segment = fTail; + do { + SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path)); + } while ((segment = segment->prev())); + } + + void toPartialForward(SkPathWriter* path) const { + const SkOpSegment* segment = &fHead; + do { + SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path)); + } while ((segment = segment->next())); + } + + void toReversePath(SkPathWriter* path) const; + void toPath(SkPathWriter* path) const; + SkOpSpan* undoneSpan(); + +protected: + SkOpGlobalState* fState; + SkOpSegment fHead; + SkOpSegment* fTail; + SkOpContour* fNext; + SkPathOpsBounds fBounds; + int fCcw; + int fCount; + int fFirstSorted; + bool fDone; // set by find top segment + bool fOperand; // true for the second argument to a binary operator + bool fReverse; // true if contour should be reverse written to path (used only by fix winding) + bool fXor; // set if original path had even-odd fill + bool fOppXor; // set if opposite path had even-odd fill + SkDEBUGCODE(int fID); + SkDEBUGCODE(mutable int fDebugIndent); +}; + +class SkOpContourHead : public SkOpContour { +public: + SkOpContour* appendContour() { + SkOpContour* contour = this->globalState()->allocator()->make(); + contour->setNext(nullptr); + SkOpContour* prev = this; + SkOpContour* next; + while ((next = prev->next())) { + prev = next; + } + prev->setNext(contour); + return contour; + } + + void joinAllSegments() { + SkOpContour* next = this; + do { + if (!next->count()) { + continue; + } + next->joinSegments(); + } while ((next = next->next())); + } + + void remove(SkOpContour* contour) { + if (contour == this) { + SkASSERT(this->count() == 0); + return; + } + SkASSERT(contour->next() == nullptr); + SkOpContour* prev = this; + SkOpContour* next; + while ((next = prev->next()) != contour) { + SkASSERT(next); + prev = next; + } + SkASSERT(prev); + prev->setNext(nullptr); + } + +}; + +class SkOpContourBuilder { +public: + SkOpContourBuilder(SkOpContour* contour) + : fContour(contour) + , fLastIsLine(false) { + } + + void addConic(SkPoint pts[3], SkScalar weight); + void addCubic(SkPoint pts[4]); + void addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight = 1); + void addLine(const SkPoint pts[2]); + void addQuad(SkPoint pts[3]); + void flush(); + SkOpContour* contour() { return fContour; } + void setContour(SkOpContour* contour) { flush(); fContour = contour; } +protected: + SkOpContour* fContour; + SkPoint fLastLine[2]; + bool fLastIsLine; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpCubicHull.cpp b/gfx/skia/skia/src/pathops/SkOpCubicHull.cpp new file mode 100644 index 0000000000..61e49635f4 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpCubicHull.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkPathOpsCubic.h" + +static bool rotate(const SkDCubic& cubic, int zero, int index, SkDCubic& rotPath) { + double dy = cubic[index].fY - cubic[zero].fY; + double dx = cubic[index].fX - cubic[zero].fX; + if (approximately_zero(dy)) { + if (approximately_zero(dx)) { + return false; + } + rotPath = cubic; + if (dy) { + rotPath[index].fY = cubic[zero].fY; + int mask = other_two(index, zero); + int side1 = index ^ mask; + int side2 = zero ^ mask; + if (approximately_equal(cubic[side1].fY, cubic[zero].fY)) { + rotPath[side1].fY = cubic[zero].fY; + } + if (approximately_equal(cubic[side2].fY, cubic[zero].fY)) { + rotPath[side2].fY = cubic[zero].fY; + } + } + return true; + } + for (int index = 0; index < 4; ++index) { + rotPath[index].fX = cubic[index].fX * dx + cubic[index].fY * dy; + rotPath[index].fY = cubic[index].fY * dx - cubic[index].fX * dy; + } + return true; +} + + +// Returns 0 if negative, 1 if zero, 2 if positive +static int side(double x) { + return (x > 0) + (x >= 0); +} + +/* Given a cubic, find the convex hull described by the end and control points. + The hull may have 3 or 4 points. Cubics that degenerate into a point or line + are not considered. + + The hull is computed by assuming that three points, if unique and non-linear, + form a triangle. The fourth point may replace one of the first three, may be + discarded if in the triangle or on an edge, or may be inserted between any of + the three to form a convex quadralateral. + + The indices returned in order describe the convex hull. +*/ +int SkDCubic::convexHull(char order[4]) const { + size_t index; + // find top point + size_t yMin = 0; + for (index = 1; index < 4; ++index) { + if (fPts[yMin].fY > fPts[index].fY || (fPts[yMin].fY == fPts[index].fY + && fPts[yMin].fX > fPts[index].fX)) { + yMin = index; + } + } + order[0] = yMin; + int midX = -1; + int backupYMin = -1; + for (int pass = 0; pass < 2; ++pass) { + for (index = 0; index < 4; ++index) { + if (index == yMin) { + continue; + } + // rotate line from (yMin, index) to axis + // see if remaining two points are both above or below + // use this to find mid + int mask = other_two(yMin, index); + int side1 = yMin ^ mask; + int side2 = index ^ mask; + SkDCubic rotPath; + if (!rotate(*this, yMin, index, rotPath)) { // ! if cbc[yMin]==cbc[idx] + order[1] = side1; + order[2] = side2; + return 3; + } + int sides = side(rotPath[side1].fY - rotPath[yMin].fY); + sides ^= side(rotPath[side2].fY - rotPath[yMin].fY); + if (sides == 2) { // '2' means one remaining point <0, one >0 + if (midX >= 0) { + // one of the control points is equal to an end point + order[0] = 0; + order[1] = 3; + if (fPts[1] == fPts[0] || fPts[1] == fPts[3]) { + order[2] = 2; + return 3; + } + if (fPts[2] == fPts[0] || fPts[2] == fPts[3]) { + order[2] = 1; + return 3; + } + // one of the control points may be very nearly but not exactly equal -- + double dist1_0 = fPts[1].distanceSquared(fPts[0]); + double dist1_3 = fPts[1].distanceSquared(fPts[3]); + double dist2_0 = fPts[2].distanceSquared(fPts[0]); + double dist2_3 = fPts[2].distanceSquared(fPts[3]); + double smallest1distSq = SkTMin(dist1_0, dist1_3); + double smallest2distSq = SkTMin(dist2_0, dist2_3); + if (approximately_zero(SkTMin(smallest1distSq, smallest2distSq))) { + order[2] = smallest1distSq < smallest2distSq ? 2 : 1; + return 3; + } + } + midX = index; + } else if (sides == 0) { // '0' means both to one side or the other + backupYMin = index; + } + } + if (midX >= 0) { + break; + } + if (backupYMin < 0) { + break; + } + yMin = backupYMin; + backupYMin = -1; + } + if (midX < 0) { + midX = yMin ^ 3; // choose any other point + } + int mask = other_two(yMin, midX); + int least = yMin ^ mask; + int most = midX ^ mask; + order[0] = yMin; + order[1] = least; + + // see if mid value is on same side of line (least, most) as yMin + SkDCubic midPath; + if (!rotate(*this, least, most, midPath)) { // ! if cbc[least]==cbc[most] + order[2] = midX; + return 3; + } + int midSides = side(midPath[yMin].fY - midPath[least].fY); + midSides ^= side(midPath[midX].fY - midPath[least].fY); + if (midSides != 2) { // if mid point is not between + order[2] = most; + return 3; // result is a triangle + } + order[2] = midX; + order[3] = most; + return 4; // result is a quadralateral +} diff --git a/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp new file mode 100644 index 0000000000..3efb0e0326 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp @@ -0,0 +1,357 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkGeometry.h" +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkReduceOrder.h" + +void SkOpEdgeBuilder::init() { + fOperand = false; + fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask + : kWinding_PathOpsMask; + fUnparseable = false; + fSecondHalf = preFetch(); +} + +// very tiny points cause numerical instability : don't allow them +static void force_small_to_zero(SkPoint* pt) { + if (SkScalarAbs(pt->fX) < FLT_EPSILON_ORDERABLE_ERR) { + pt->fX = 0; + } + if (SkScalarAbs(pt->fY) < FLT_EPSILON_ORDERABLE_ERR) { + pt->fY = 0; + } +} + +static bool can_add_curve(SkPath::Verb verb, SkPoint* curve) { + if (SkPath::kMove_Verb == verb) { + return false; + } + for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) { + force_small_to_zero(&curve[index]); + } + return SkPath::kLine_Verb != verb || !SkDPoint::ApproximatelyEqual(curve[0], curve[1]); +} + +void SkOpEdgeBuilder::addOperand(const SkPath& path) { + SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb); + fPathVerbs.pop(); + fPath = &path; + fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask + : kWinding_PathOpsMask; + preFetch(); +} + +bool SkOpEdgeBuilder::finish() { + fOperand = false; + if (fUnparseable || !walk()) { + return false; + } + complete(); + SkOpContour* contour = fContourBuilder.contour(); + if (contour && !contour->count()) { + fContoursHead->remove(contour); + } + return true; +} + +void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) { + if (!SkDPoint::ApproximatelyEqual(curveEnd, curveStart)) { + *fPathVerbs.append() = SkPath::kLine_Verb; + *fPathPts.append() = curveStart; + } else { + int verbCount = fPathVerbs.count(); + int ptsCount = fPathPts.count(); + if (SkPath::kLine_Verb == fPathVerbs[verbCount - 1] + && fPathPts[ptsCount - 2] == curveStart) { + fPathVerbs.pop(); + fPathPts.pop(); + } else { + fPathPts[ptsCount - 1] = curveStart; + } + } + *fPathVerbs.append() = SkPath::kClose_Verb; +} + +int SkOpEdgeBuilder::preFetch() { + if (!fPath->isFinite()) { + fUnparseable = true; + return 0; + } + SkPath::RawIter iter(*fPath); + SkPoint curveStart; + SkPoint curve[4]; + SkPoint pts[4]; + SkPath::Verb verb; + bool lastCurve = false; + do { + verb = iter.next(pts); + switch (verb) { + case SkPath::kMove_Verb: + if (!fAllowOpenContours && lastCurve) { + closeContour(curve[0], curveStart); + } + *fPathVerbs.append() = verb; + force_small_to_zero(&pts[0]); + *fPathPts.append() = pts[0]; + curveStart = curve[0] = pts[0]; + lastCurve = false; + continue; + case SkPath::kLine_Verb: + force_small_to_zero(&pts[1]); + if (SkDPoint::ApproximatelyEqual(curve[0], pts[1])) { + uint8_t lastVerb = fPathVerbs.top(); + if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) { + fPathPts.top() = curve[0] = pts[1]; + } + continue; // skip degenerate points + } + break; + case SkPath::kQuad_Verb: + force_small_to_zero(&pts[1]); + force_small_to_zero(&pts[2]); + curve[1] = pts[1]; + curve[2] = pts[2]; + verb = SkReduceOrder::Quad(curve, pts); + if (verb == SkPath::kMove_Verb) { + continue; // skip degenerate points + } + break; + case SkPath::kConic_Verb: + force_small_to_zero(&pts[1]); + force_small_to_zero(&pts[2]); + curve[1] = pts[1]; + curve[2] = pts[2]; + verb = SkReduceOrder::Quad(curve, pts); + if (SkPath::kQuad_Verb == verb && 1 != iter.conicWeight()) { + verb = SkPath::kConic_Verb; + } else if (verb == SkPath::kMove_Verb) { + continue; // skip degenerate points + } + break; + case SkPath::kCubic_Verb: + force_small_to_zero(&pts[1]); + force_small_to_zero(&pts[2]); + force_small_to_zero(&pts[3]); + curve[1] = pts[1]; + curve[2] = pts[2]; + curve[3] = pts[3]; + verb = SkReduceOrder::Cubic(curve, pts); + if (verb == SkPath::kMove_Verb) { + continue; // skip degenerate points + } + break; + case SkPath::kClose_Verb: + closeContour(curve[0], curveStart); + lastCurve = false; + continue; + case SkPath::kDone_Verb: + continue; + } + *fPathVerbs.append() = verb; + int ptCount = SkPathOpsVerbToPoints(verb); + fPathPts.append(ptCount, &pts[1]); + if (verb == SkPath::kConic_Verb) { + *fWeights.append() = iter.conicWeight(); + } + curve[0] = pts[ptCount]; + lastCurve = true; + } while (verb != SkPath::kDone_Verb); + if (!fAllowOpenContours && lastCurve) { + closeContour(curve[0], curveStart); + } + *fPathVerbs.append() = SkPath::kDone_Verb; + return fPathVerbs.count() - 1; +} + +bool SkOpEdgeBuilder::close() { + complete(); + return true; +} + +bool SkOpEdgeBuilder::walk() { + uint8_t* verbPtr = fPathVerbs.begin(); + uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; + SkPoint* pointsPtr = fPathPts.begin(); + SkScalar* weightPtr = fWeights.begin(); + SkPath::Verb verb; + SkOpContour* contour = fContourBuilder.contour(); + int moveToPtrBump = 0; + while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) { + if (verbPtr == endOfFirstHalf) { + fOperand = true; + } + verbPtr++; + switch (verb) { + case SkPath::kMove_Verb: + if (contour && contour->count()) { + if (fAllowOpenContours) { + complete(); + } else if (!close()) { + return false; + } + } + if (!contour) { + fContourBuilder.setContour(contour = fContoursHead->appendContour()); + } + contour->init(fGlobalState, fOperand, + fXorMask[fOperand] == kEvenOdd_PathOpsMask); + pointsPtr += moveToPtrBump; + moveToPtrBump = 1; + continue; + case SkPath::kLine_Verb: + fContourBuilder.addLine(pointsPtr); + break; + case SkPath::kQuad_Verb: + { + SkVector v1 = pointsPtr[1] - pointsPtr[0]; + SkVector v2 = pointsPtr[2] - pointsPtr[1]; + if (v1.dot(v2) < 0) { + SkPoint pair[5]; + if (SkChopQuadAtMaxCurvature(pointsPtr, pair) == 1) { + goto addOneQuad; + } + if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) { + return false; + } + for (unsigned index = 0; index < SK_ARRAY_COUNT(pair); ++index) { + force_small_to_zero(&pair[index]); + } + SkPoint cStorage[2][2]; + SkPath::Verb v1 = SkReduceOrder::Quad(&pair[0], cStorage[0]); + SkPath::Verb v2 = SkReduceOrder::Quad(&pair[2], cStorage[1]); + SkPoint* curve1 = v1 != SkPath::kLine_Verb ? &pair[0] : cStorage[0]; + SkPoint* curve2 = v2 != SkPath::kLine_Verb ? &pair[2] : cStorage[1]; + if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { + fContourBuilder.addCurve(v1, curve1); + fContourBuilder.addCurve(v2, curve2); + break; + } + } + } + addOneQuad: + fContourBuilder.addQuad(pointsPtr); + break; + case SkPath::kConic_Verb: { + SkVector v1 = pointsPtr[1] - pointsPtr[0]; + SkVector v2 = pointsPtr[2] - pointsPtr[1]; + SkScalar weight = *weightPtr++; + if (v1.dot(v2) < 0) { + // FIXME: max curvature for conics hasn't been implemented; use placeholder + SkScalar maxCurvature = SkFindQuadMaxCurvature(pointsPtr); + if (0 < maxCurvature && maxCurvature < 1) { + SkConic conic(pointsPtr, weight); + SkConic pair[2]; + if (!conic.chopAt(maxCurvature, pair)) { + // if result can't be computed, use original + fContourBuilder.addConic(pointsPtr, weight); + break; + } + SkPoint cStorage[2][3]; + SkPath::Verb v1 = SkReduceOrder::Conic(pair[0], cStorage[0]); + SkPath::Verb v2 = SkReduceOrder::Conic(pair[1], cStorage[1]); + SkPoint* curve1 = v1 != SkPath::kLine_Verb ? pair[0].fPts : cStorage[0]; + SkPoint* curve2 = v2 != SkPath::kLine_Verb ? pair[1].fPts : cStorage[1]; + if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { + fContourBuilder.addCurve(v1, curve1, pair[0].fW); + fContourBuilder.addCurve(v2, curve2, pair[1].fW); + break; + } + } + } + fContourBuilder.addConic(pointsPtr, weight); + } break; + case SkPath::kCubic_Verb: + { + // Split complex cubics (such as self-intersecting curves or + // ones with difficult curvature) in two before proceeding. + // This can be required for intersection to succeed. + SkScalar splitT[3]; + int breaks = SkDCubic::ComplexBreak(pointsPtr, splitT); + if (!breaks) { + fContourBuilder.addCubic(pointsPtr); + break; + } + SkASSERT(breaks <= (int) SK_ARRAY_COUNT(splitT)); + struct Splitsville { + double fT[2]; + SkPoint fPts[4]; + SkPoint fReduced[4]; + SkPath::Verb fVerb; + bool fCanAdd; + } splits[4]; + SkASSERT(SK_ARRAY_COUNT(splits) == SK_ARRAY_COUNT(splitT) + 1); + SkTQSort(splitT, &splitT[breaks - 1]); + for (int index = 0; index <= breaks; ++index) { + Splitsville* split = &splits[index]; + split->fT[0] = index ? splitT[index - 1] : 0; + split->fT[1] = index < breaks ? splitT[index] : 1; + SkDCubic part = SkDCubic::SubDivide(pointsPtr, split->fT[0], split->fT[1]); + if (!part.toFloatPoints(split->fPts)) { + return false; + } + split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced); + SkPoint* curve = SkPath::kCubic_Verb == verb + ? split->fPts : split->fReduced; + split->fCanAdd = can_add_curve(split->fVerb, curve); + } + for (int index = 0; index <= breaks; ++index) { + Splitsville* split = &splits[index]; + if (!split->fCanAdd) { + continue; + } + int prior = index; + while (prior > 0 && !splits[prior - 1].fCanAdd) { + --prior; + } + if (prior < index) { + split->fT[0] = splits[prior].fT[0]; + split->fPts[0] = splits[prior].fPts[0]; + } + int next = index; + int breakLimit = SkTMin(breaks, (int) SK_ARRAY_COUNT(splits) - 1); + while (next < breakLimit && !splits[next + 1].fCanAdd) { + ++next; + } + if (next > index) { + split->fT[1] = splits[next].fT[1]; + split->fPts[3] = splits[next].fPts[3]; + } + if (prior < index || next > index) { + split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced); + } + SkPoint* curve = SkPath::kCubic_Verb == split->fVerb + ? split->fPts : split->fReduced; + if (!can_add_curve(split->fVerb, curve)) { + return false; + } + fContourBuilder.addCurve(split->fVerb, curve); + } + } + break; + case SkPath::kClose_Verb: + SkASSERT(contour); + if (!close()) { + return false; + } + contour = nullptr; + continue; + default: + SkDEBUGFAIL("bad verb"); + return false; + } + SkASSERT(contour); + if (contour->count()) { + contour->debugValidate(); + } + pointsPtr += SkPathOpsVerbToPoints(verb); + } + fContourBuilder.flush(); + if (contour && contour->count() &&!fAllowOpenContours && !close()) { + return false; + } + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h new file mode 100644 index 0000000000..67dff910dc --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h @@ -0,0 +1,75 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpEdgeBuilder_DEFINED +#define SkOpEdgeBuilder_DEFINED + +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkPathWriter.h" + +class SkOpEdgeBuilder { +public: + SkOpEdgeBuilder(const SkPathWriter& path, SkOpContourHead* contours2, + SkOpGlobalState* globalState) + : fGlobalState(globalState) + , fPath(path.nativePath()) + , fContourBuilder(contours2) + , fContoursHead(contours2) + , fAllowOpenContours(true) { + init(); + } + + SkOpEdgeBuilder(const SkPath& path, SkOpContourHead* contours2, SkOpGlobalState* globalState) + : fGlobalState(globalState) + , fPath(&path) + , fContourBuilder(contours2) + , fContoursHead(contours2) + , fAllowOpenContours(false) { + init(); + } + + void addOperand(const SkPath& path); + + void complete() { + fContourBuilder.flush(); + SkOpContour* contour = fContourBuilder.contour(); + if (contour && contour->count()) { + contour->complete(); + fContourBuilder.setContour(nullptr); + } + } + + bool finish(); + + const SkOpContour* head() const { + return fContoursHead; + } + + void init(); + bool unparseable() const { return fUnparseable; } + SkPathOpsMask xorMask() const { return fXorMask[fOperand]; } + +private: + void closeContour(const SkPoint& curveEnd, const SkPoint& curveStart); + bool close(); + int preFetch(); + bool walk(); + + SkOpGlobalState* fGlobalState; + const SkPath* fPath; + SkTDArray fPathPts; + SkTDArray fWeights; + SkTDArray fPathVerbs; + SkOpContourBuilder fContourBuilder; + SkOpContourHead* fContoursHead; + SkPathOpsMask fXorMask[2]; + int fSecondHalf; + bool fOperand; + bool fAllowOpenContours; + bool fUnparseable; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpSegment.cpp b/gfx/skia/skia/src/pathops/SkOpSegment.cpp new file mode 100644 index 0000000000..66fa6ee5ff --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSegment.cpp @@ -0,0 +1,1781 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkPointPriv.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkOpSegment.h" +#include "src/pathops/SkPathWriter.h" + +#include + +/* +After computing raw intersections, post process all segments to: +- find small collections of points that can be collapsed to a single point +- find missing intersections to resolve differences caused by different algorithms + +Consider segments containing tiny or small intervals. Consider coincident segments +because coincidence finds intersections through distance measurement that non-coincident +intersection tests cannot. + */ + +#define F (false) // discard the edge +#define T (true) // keep the edge + +static const bool gUnaryActiveEdge[2][2] = { +// from=0 from=1 +// to=0,1 to=0,1 + {F, T}, {T, F}, +}; + +static const bool gActiveEdge[kXOR_SkPathOp + 1][2][2][2][2] = { +// miFrom=0 miFrom=1 +// miTo=0 miTo=1 miTo=0 miTo=1 +// suFrom=0 1 suFrom=0 1 suFrom=0 1 suFrom=0 1 +// suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 + {{{{F, F}, {F, F}}, {{T, F}, {T, F}}}, {{{T, T}, {F, F}}, {{F, T}, {T, F}}}}, // mi - su + {{{{F, F}, {F, F}}, {{F, T}, {F, T}}}, {{{F, F}, {T, T}}, {{F, T}, {T, F}}}}, // mi & su + {{{{F, T}, {T, F}}, {{T, T}, {F, F}}}, {{{T, F}, {T, F}}, {{F, F}, {F, F}}}}, // mi | su + {{{{F, T}, {T, F}}, {{T, F}, {F, T}}}, {{{T, F}, {F, T}}, {{F, T}, {T, F}}}}, // mi ^ su +}; + +#undef F +#undef T + +SkOpAngle* SkOpSegment::activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done) { + if (SkOpAngle* result = activeAngleInner(start, startPtr, endPtr, done)) { + return result; + } + if (SkOpAngle* result = activeAngleOther(start, startPtr, endPtr, done)) { + return result; + } + return nullptr; +} + +SkOpAngle* SkOpSegment::activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done) { + SkOpSpan* upSpan = start->upCastable(); + if (upSpan) { + if (upSpan->windValue() || upSpan->oppValue()) { + SkOpSpanBase* next = upSpan->next(); + if (!*endPtr) { + *startPtr = start; + *endPtr = next; + } + if (!upSpan->done()) { + if (upSpan->windSum() != SK_MinS32) { + return spanToAngle(start, next); + } + *done = false; + } + } else { + SkASSERT(upSpan->done()); + } + } + SkOpSpan* downSpan = start->prev(); + // edge leading into junction + if (downSpan) { + if (downSpan->windValue() || downSpan->oppValue()) { + if (!*endPtr) { + *startPtr = start; + *endPtr = downSpan; + } + if (!downSpan->done()) { + if (downSpan->windSum() != SK_MinS32) { + return spanToAngle(start, downSpan); + } + *done = false; + } + } else { + SkASSERT(downSpan->done()); + } + } + return nullptr; +} + +SkOpAngle* SkOpSegment::activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done) { + SkOpPtT* oPtT = start->ptT()->next(); + SkOpSegment* other = oPtT->segment(); + SkOpSpanBase* oSpan = oPtT->span(); + return other->activeAngleInner(oSpan, startPtr, endPtr, done); +} + +bool SkOpSegment::activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, + SkPathOp op) { + int sumMiWinding = this->updateWinding(end, start); + int sumSuWinding = this->updateOppWinding(end, start); +#if DEBUG_LIMIT_WIND_SUM + SkASSERT(abs(sumMiWinding) <= DEBUG_LIMIT_WIND_SUM); + SkASSERT(abs(sumSuWinding) <= DEBUG_LIMIT_WIND_SUM); +#endif + if (this->operand()) { + using std::swap; + swap(sumMiWinding, sumSuWinding); + } + return this->activeOp(xorMiMask, xorSuMask, start, end, op, &sumMiWinding, &sumSuWinding); +} + +bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, + SkPathOp op, int* sumMiWinding, int* sumSuWinding) { + int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; + this->setUpWindings(start, end, sumMiWinding, sumSuWinding, + &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + bool miFrom; + bool miTo; + bool suFrom; + bool suTo; + if (operand()) { + miFrom = (oppMaxWinding & xorMiMask) != 0; + miTo = (oppSumWinding & xorMiMask) != 0; + suFrom = (maxWinding & xorSuMask) != 0; + suTo = (sumWinding & xorSuMask) != 0; + } else { + miFrom = (maxWinding & xorMiMask) != 0; + miTo = (sumWinding & xorMiMask) != 0; + suFrom = (oppMaxWinding & xorSuMask) != 0; + suTo = (oppSumWinding & xorSuMask) != 0; + } + bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo]; +#if DEBUG_ACTIVE_OP + SkDebugf("%s id=%d t=%1.9g tEnd=%1.9g op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", + __FUNCTION__, debugID(), start->t(), end->t(), + SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result); +#endif + return result; +} + +bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end) { + int sumWinding = updateWinding(end, start); + return activeWinding(start, end, &sumWinding); +} + +bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding) { + int maxWinding; + setUpWinding(start, end, &maxWinding, sumWinding); + bool from = maxWinding != 0; + bool to = *sumWinding != 0; + bool result = gUnaryActiveEdge[from][to]; + return result; +} + +bool SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, + SkPathWriter* path) const { + const SkOpSpan* spanStart = start->starter(end); + FAIL_IF(spanStart->alreadyAdded()); + const_cast(spanStart)->markAdded(); + SkDCurveSweep curvePart; + start->segment()->subDivide(start, end, &curvePart.fCurve); + curvePart.setCurveHullSweep(fVerb); + SkPath::Verb verb = curvePart.isCurve() ? fVerb : SkPath::kLine_Verb; + path->deferredMove(start->ptT()); + switch (verb) { + case SkPath::kLine_Verb: + FAIL_IF(!path->deferredLine(end->ptT())); + break; + case SkPath::kQuad_Verb: + path->quadTo(curvePart.fCurve.fQuad[1].asSkPoint(), end->ptT()); + break; + case SkPath::kConic_Verb: + path->conicTo(curvePart.fCurve.fConic[1].asSkPoint(), end->ptT(), + curvePart.fCurve.fConic.fWeight); + break; + case SkPath::kCubic_Verb: + path->cubicTo(curvePart.fCurve.fCubic[1].asSkPoint(), + curvePart.fCurve.fCubic[2].asSkPoint(), end->ptT()); + break; + default: + SkASSERT(0); + } + return true; +} + +const SkOpPtT* SkOpSegment::existing(double t, const SkOpSegment* opp) const { + const SkOpSpanBase* test = &fHead; + const SkOpPtT* testPtT; + SkPoint pt = this->ptAtT(t); + do { + testPtT = test->ptT(); + if (testPtT->fT == t) { + break; + } + if (!this->match(testPtT, this, t, pt)) { + if (t < testPtT->fT) { + return nullptr; + } + continue; + } + if (!opp) { + return testPtT; + } + const SkOpPtT* loop = testPtT->next(); + while (loop != testPtT) { + if (loop->segment() == this && loop->fT == t && loop->fPt == pt) { + goto foundMatch; + } + loop = loop->next(); + } + return nullptr; + } while ((test = test->upCast()->next())); +foundMatch: + return opp && !test->contains(opp) ? nullptr : testPtT; +} + +// break the span so that the coincident part does not change the angle of the remainder +bool SkOpSegment::addExpanded(double newT, const SkOpSpanBase* test, bool* startOver) { + if (this->contains(newT)) { + return true; + } + this->globalState()->resetAllocatedOpSpan(); + FAIL_IF(!between(0, newT, 1)); + SkOpPtT* newPtT = this->addT(newT); + *startOver |= this->globalState()->allocatedOpSpan(); + if (!newPtT) { + return false; + } + newPtT->fPt = this->ptAtT(newT); + SkOpPtT* oppPrev = test->ptT()->oppPrev(newPtT); + if (oppPrev) { + // const cast away to change linked list; pt/t values stays unchanged + SkOpSpanBase* writableTest = const_cast(test); + writableTest->mergeMatches(newPtT->span()); + writableTest->ptT()->addOpp(newPtT, oppPrev); + writableTest->checkForCollapsedCoincidence(); + } + return true; +} + +// Please keep this in sync with debugAddT() +SkOpPtT* SkOpSegment::addT(double t, const SkPoint& pt) { + debugValidate(); + SkOpSpanBase* spanBase = &fHead; + do { + SkOpPtT* result = spanBase->ptT(); + if (t == result->fT || (!zero_or_one(t) && this->match(result, this, t, pt))) { + spanBase->bumpSpanAdds(); + return result; + } + if (t < result->fT) { + SkOpSpan* prev = result->span()->prev(); + FAIL_WITH_NULL_IF(!prev); + // marks in global state that new op span has been allocated + SkOpSpan* span = this->insert(prev); + span->init(this, prev, t, pt); + this->debugValidate(); +#if DEBUG_ADD_T + SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, + span->segment()->debugID(), span->debugID()); +#endif + span->bumpSpanAdds(); + return span->ptT(); + } + FAIL_WITH_NULL_IF(spanBase == &fTail); + } while ((spanBase = spanBase->upCast()->next())); + SkASSERT(0); + return nullptr; // we never get here, but need this to satisfy compiler +} + +SkOpPtT* SkOpSegment::addT(double t) { + return addT(t, this->ptAtT(t)); +} + +void SkOpSegment::calcAngles() { + bool activePrior = !fHead.isCanceled(); + if (activePrior && !fHead.simple()) { + addStartSpan(); + } + SkOpSpan* prior = &fHead; + SkOpSpanBase* spanBase = fHead.next(); + while (spanBase != &fTail) { + if (activePrior) { + SkOpAngle* priorAngle = this->globalState()->allocator()->make(); + priorAngle->set(spanBase, prior); + spanBase->setFromAngle(priorAngle); + } + SkOpSpan* span = spanBase->upCast(); + bool active = !span->isCanceled(); + SkOpSpanBase* next = span->next(); + if (active) { + SkOpAngle* angle = this->globalState()->allocator()->make(); + angle->set(span, next); + span->setToAngle(angle); + } + activePrior = active; + prior = span; + spanBase = next; + } + if (activePrior && !fTail.simple()) { + addEndSpan(); + } +} + +// Please keep this in sync with debugClearAll() +void SkOpSegment::clearAll() { + SkOpSpan* span = &fHead; + do { + this->clearOne(span); + } while ((span = span->next()->upCastable())); + this->globalState()->coincidence()->release(this); +} + +// Please keep this in sync with debugClearOne() +void SkOpSegment::clearOne(SkOpSpan* span) { + span->setWindValue(0); + span->setOppValue(0); + this->markDone(span); +} + +SkOpSpanBase::Collapsed SkOpSegment::collapsed(double s, double e) const { + const SkOpSpanBase* span = &fHead; + do { + SkOpSpanBase::Collapsed result = span->collapsed(s, e); + if (SkOpSpanBase::Collapsed::kNo != result) { + return result; + } + } while (span->upCastable() && (span = span->upCast()->next())); + return SkOpSpanBase::Collapsed::kNo; +} + +bool SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType includeType) { + SkOpSegment* baseSegment = baseAngle->segment(); + int sumMiWinding = baseSegment->updateWindingReverse(baseAngle); + int sumSuWinding; + bool binary = includeType >= SkOpAngle::kBinarySingle; + if (binary) { + sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle); + if (baseSegment->operand()) { + using std::swap; + swap(sumMiWinding, sumSuWinding); + } + } + SkOpSegment* nextSegment = nextAngle->segment(); + int maxWinding, sumWinding; + SkOpSpanBase* last = nullptr; + if (binary) { + int oppMaxWinding, oppSumWinding; + nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, + &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + if (!nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, + nextAngle, &last)) { + return false; + } + } else { + nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, + &maxWinding, &sumWinding); + if (!nextSegment->markAngle(maxWinding, sumWinding, nextAngle, &last)) { + return false; + } + } + nextAngle->setLastMarked(last); + return true; +} + +bool SkOpSegment::ComputeOneSumReverse(SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType includeType) { + SkOpSegment* baseSegment = baseAngle->segment(); + int sumMiWinding = baseSegment->updateWinding(baseAngle); + int sumSuWinding; + bool binary = includeType >= SkOpAngle::kBinarySingle; + if (binary) { + sumSuWinding = baseSegment->updateOppWinding(baseAngle); + if (baseSegment->operand()) { + using std::swap; + swap(sumMiWinding, sumSuWinding); + } + } + SkOpSegment* nextSegment = nextAngle->segment(); + int maxWinding, sumWinding; + SkOpSpanBase* last = nullptr; + if (binary) { + int oppMaxWinding, oppSumWinding; + nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, + &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + if (!nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, + nextAngle, &last)) { + return false; + } + } else { + nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, + &maxWinding, &sumWinding); + if (!nextSegment->markAngle(maxWinding, sumWinding, nextAngle, &last)) { + return false; + } + } + nextAngle->setLastMarked(last); + return true; +} + +// at this point, the span is already ordered, or unorderable +int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, + SkOpAngle::IncludeType includeType) { + SkASSERT(includeType != SkOpAngle::kUnaryXor); + SkOpAngle* firstAngle = this->spanToAngle(end, start); + if (nullptr == firstAngle || nullptr == firstAngle->next()) { + return SK_NaN32; + } + // if all angles have a computed winding, + // or if no adjacent angles are orderable, + // or if adjacent orderable angles have no computed winding, + // there's nothing to do + // if two orderable angles are adjacent, and both are next to orderable angles, + // and one has winding computed, transfer to the other + SkOpAngle* baseAngle = nullptr; + bool tryReverse = false; + // look for counterclockwise transfers + SkOpAngle* angle = firstAngle->previous(); + SkOpAngle* next = angle->next(); + firstAngle = next; + do { + SkOpAngle* prior = angle; + angle = next; + next = angle->next(); + SkASSERT(prior->next() == angle); + SkASSERT(angle->next() == next); + if (prior->unorderable() || angle->unorderable() || next->unorderable()) { + baseAngle = nullptr; + continue; + } + int testWinding = angle->starter()->windSum(); + if (SK_MinS32 != testWinding) { + baseAngle = angle; + tryReverse = true; + continue; + } + if (baseAngle) { + ComputeOneSum(baseAngle, angle, includeType); + baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : nullptr; + } + } while (next != firstAngle); + if (baseAngle && SK_MinS32 == firstAngle->starter()->windSum()) { + firstAngle = baseAngle; + tryReverse = true; + } + if (tryReverse) { + baseAngle = nullptr; + SkOpAngle* prior = firstAngle; + do { + angle = prior; + prior = angle->previous(); + SkASSERT(prior->next() == angle); + next = angle->next(); + if (prior->unorderable() || angle->unorderable() || next->unorderable()) { + baseAngle = nullptr; + continue; + } + int testWinding = angle->starter()->windSum(); + if (SK_MinS32 != testWinding) { + baseAngle = angle; + continue; + } + if (baseAngle) { + ComputeOneSumReverse(baseAngle, angle, includeType); + baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : nullptr; + } + } while (prior != firstAngle); + } + return start->starter(end)->windSum(); +} + +bool SkOpSegment::contains(double newT) const { + const SkOpSpanBase* spanBase = &fHead; + do { + if (spanBase->ptT()->contains(this, newT)) { + return true; + } + if (spanBase == &fTail) { + break; + } + spanBase = spanBase->upCast()->next(); + } while (true); + return false; +} + +void SkOpSegment::release(const SkOpSpan* span) { + if (span->done()) { + --fDoneCount; + } + --fCount; + SkOPASSERT(fCount >= fDoneCount); +} + +#if DEBUG_ANGLE +// called only by debugCheckNearCoincidence +double SkOpSegment::distSq(double t, const SkOpAngle* oppAngle) const { + SkDPoint testPt = this->dPtAtT(t); + SkDLine testPerp = {{ testPt, testPt }}; + SkDVector slope = this->dSlopeAtT(t); + testPerp[1].fX += slope.fY; + testPerp[1].fY -= slope.fX; + SkIntersections i; + const SkOpSegment* oppSegment = oppAngle->segment(); + (*CurveIntersectRay[oppSegment->verb()])(oppSegment->pts(), oppSegment->weight(), testPerp, &i); + double closestDistSq = SK_ScalarInfinity; + for (int index = 0; index < i.used(); ++index) { + if (!between(oppAngle->start()->t(), i[0][index], oppAngle->end()->t())) { + continue; + } + double testDistSq = testPt.distanceSquared(i.pt(index)); + if (closestDistSq > testDistSq) { + closestDistSq = testDistSq; + } + } + return closestDistSq; +} +#endif + +/* + The M and S variable name parts stand for the operators. + Mi stands for Minuend (see wiki subtraction, analogous to difference) + Su stands for Subtrahend + The Opp variable name part designates that the value is for the Opposite operator. + Opposite values result from combining coincident spans. + */ +SkOpSegment* SkOpSegment::findNextOp(SkTDArray* chase, SkOpSpanBase** nextStart, + SkOpSpanBase** nextEnd, bool* unsortable, bool* simple, + SkPathOp op, int xorMiMask, int xorSuMask) { + SkOpSpanBase* start = *nextStart; + SkOpSpanBase* end = *nextEnd; + SkASSERT(start != end); + int step = start->step(end); + SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart + if ((*simple = other)) { + // mark the smaller of startIndex, endIndex done, and all adjacent + // spans with the same T value (but not 'other' spans) +#if DEBUG_WINDING + SkDebugf("%s simple\n", __FUNCTION__); +#endif + SkOpSpan* startSpan = start->starter(end); + if (startSpan->done()) { + return nullptr; + } + markDone(startSpan); + *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); + return other; + } + SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); + SkASSERT(endNear == end); // is this ever not end? + SkASSERT(endNear); + SkASSERT(start != endNear); + SkASSERT((start->t() < endNear->t()) ^ (step < 0)); + // more than one viable candidate -- measure angles to find best + int calcWinding = computeSum(start, endNear, SkOpAngle::kBinaryOpp); + bool sortable = calcWinding != SK_NaN32; + if (!sortable) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } + SkOpAngle* angle = this->spanToAngle(end, start); + if (angle->unorderable()) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } +#if DEBUG_SORT + SkDebugf("%s\n", __FUNCTION__); + angle->debugLoop(); +#endif + int sumMiWinding = updateWinding(end, start); + if (sumMiWinding == SK_MinS32) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } + int sumSuWinding = updateOppWinding(end, start); + if (operand()) { + using std::swap; + swap(sumMiWinding, sumSuWinding); + } + SkOpAngle* nextAngle = angle->next(); + const SkOpAngle* foundAngle = nullptr; + bool foundDone = false; + // iterate through the angle, and compute everyone's winding + SkOpSegment* nextSegment; + int activeCount = 0; + do { + nextSegment = nextAngle->segment(); + bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(), + nextAngle->end(), op, &sumMiWinding, &sumSuWinding); + if (activeAngle) { + ++activeCount; + if (!foundAngle || (foundDone && activeCount & 1)) { + foundAngle = nextAngle; + foundDone = nextSegment->done(nextAngle); + } + } + if (nextSegment->done()) { + continue; + } + if (!activeAngle) { + (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end(), nullptr); + } + SkOpSpanBase* last = nextAngle->lastMarked(); + if (last) { + SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); + *chase->append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, + last->segment()->debugID(), last->debugID()); + if (!last->final()) { + SkDebugf(" windSum=%d", last->upCast()->windSum()); + } + SkDebugf("\n"); +#endif + } + } while ((nextAngle = nextAngle->next()) != angle); + start->segment()->markDone(start->starter(end)); + if (!foundAngle) { + return nullptr; + } + *nextStart = foundAngle->start(); + *nextEnd = foundAngle->end(); + nextSegment = foundAngle->segment(); +#if DEBUG_WINDING + SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n", + __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); + #endif + return nextSegment; +} + +SkOpSegment* SkOpSegment::findNextWinding(SkTDArray* chase, + SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable) { + SkOpSpanBase* start = *nextStart; + SkOpSpanBase* end = *nextEnd; + SkASSERT(start != end); + int step = start->step(end); + SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart + if (other) { + // mark the smaller of startIndex, endIndex done, and all adjacent + // spans with the same T value (but not 'other' spans) +#if DEBUG_WINDING + SkDebugf("%s simple\n", __FUNCTION__); +#endif + SkOpSpan* startSpan = start->starter(end); + if (startSpan->done()) { + return nullptr; + } + markDone(startSpan); + *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); + return other; + } + SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); + SkASSERT(endNear == end); // is this ever not end? + SkASSERT(endNear); + SkASSERT(start != endNear); + SkASSERT((start->t() < endNear->t()) ^ (step < 0)); + // more than one viable candidate -- measure angles to find best + int calcWinding = computeSum(start, endNear, SkOpAngle::kUnaryWinding); + bool sortable = calcWinding != SK_NaN32; + if (!sortable) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } + SkOpAngle* angle = this->spanToAngle(end, start); + if (angle->unorderable()) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } +#if DEBUG_SORT + SkDebugf("%s\n", __FUNCTION__); + angle->debugLoop(); +#endif + int sumWinding = updateWinding(end, start); + SkOpAngle* nextAngle = angle->next(); + const SkOpAngle* foundAngle = nullptr; + bool foundDone = false; + // iterate through the angle, and compute everyone's winding + SkOpSegment* nextSegment; + int activeCount = 0; + do { + nextSegment = nextAngle->segment(); + bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(), + &sumWinding); + if (activeAngle) { + ++activeCount; + if (!foundAngle || (foundDone && activeCount & 1)) { + foundAngle = nextAngle; + foundDone = nextSegment->done(nextAngle); + } + } + if (nextSegment->done()) { + continue; + } + if (!activeAngle) { + (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end(), nullptr); + } + SkOpSpanBase* last = nextAngle->lastMarked(); + if (last) { + SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); + *chase->append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, + last->segment()->debugID(), last->debugID()); + if (!last->final()) { + SkDebugf(" windSum=%d", last->upCast()->windSum()); + } + SkDebugf("\n"); +#endif + } + } while ((nextAngle = nextAngle->next()) != angle); + start->segment()->markDone(start->starter(end)); + if (!foundAngle) { + return nullptr; + } + *nextStart = foundAngle->start(); + *nextEnd = foundAngle->end(); + nextSegment = foundAngle->segment(); +#if DEBUG_WINDING + SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n", + __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); + #endif + return nextSegment; +} + +SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, + bool* unsortable) { + SkOpSpanBase* start = *nextStart; + SkOpSpanBase* end = *nextEnd; + SkASSERT(start != end); + int step = start->step(end); + SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart + if (other) { + // mark the smaller of startIndex, endIndex done, and all adjacent + // spans with the same T value (but not 'other' spans) +#if DEBUG_WINDING + SkDebugf("%s simple\n", __FUNCTION__); +#endif + SkOpSpan* startSpan = start->starter(end); + if (startSpan->done()) { + return nullptr; + } + markDone(startSpan); + *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); + return other; + } + SkDEBUGCODE(SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() \ + : (*nextStart)->prev()); + SkASSERT(endNear == end); // is this ever not end? + SkASSERT(endNear); + SkASSERT(start != endNear); + SkASSERT((start->t() < endNear->t()) ^ (step < 0)); + SkOpAngle* angle = this->spanToAngle(end, start); + if (!angle || angle->unorderable()) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } +#if DEBUG_SORT + SkDebugf("%s\n", __FUNCTION__); + angle->debugLoop(); +#endif + SkOpAngle* nextAngle = angle->next(); + const SkOpAngle* foundAngle = nullptr; + bool foundDone = false; + // iterate through the angle, and compute everyone's winding + SkOpSegment* nextSegment; + int activeCount = 0; + do { + if (!nextAngle) { + return nullptr; + } + nextSegment = nextAngle->segment(); + ++activeCount; + if (!foundAngle || (foundDone && activeCount & 1)) { + foundAngle = nextAngle; + if (!(foundDone = nextSegment->done(nextAngle))) { + break; + } + } + nextAngle = nextAngle->next(); + } while (nextAngle != angle); + start->segment()->markDone(start->starter(end)); + if (!foundAngle) { + return nullptr; + } + *nextStart = foundAngle->start(); + *nextEnd = foundAngle->end(); + nextSegment = foundAngle->segment(); +#if DEBUG_WINDING + SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n", + __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); + #endif + return nextSegment; +} + +SkOpGlobalState* SkOpSegment::globalState() const { + return contour()->globalState(); +} + +void SkOpSegment::init(SkPoint pts[], SkScalar weight, SkOpContour* contour, SkPath::Verb verb) { + fContour = contour; + fNext = nullptr; + fPts = pts; + fWeight = weight; + fVerb = verb; + fCount = 0; + fDoneCount = 0; + fVisited = false; + SkOpSpan* zeroSpan = &fHead; + zeroSpan->init(this, nullptr, 0, fPts[0]); + SkOpSpanBase* oneSpan = &fTail; + zeroSpan->setNext(oneSpan); + oneSpan->initBase(this, zeroSpan, 1, fPts[SkPathOpsVerbToPoints(fVerb)]); + SkDEBUGCODE(fID = globalState()->nextSegmentID()); +} + +bool SkOpSegment::isClose(double t, const SkOpSegment* opp) const { + SkDPoint cPt = this->dPtAtT(t); + SkDVector dxdy = (*CurveDSlopeAtT[this->verb()])(this->pts(), this->weight(), t); + SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; + SkIntersections i; + (*CurveIntersectRay[opp->verb()])(opp->pts(), opp->weight(), perp, &i); + int used = i.used(); + for (int index = 0; index < used; ++index) { + if (cPt.roughlyEqual(i.pt(index))) { + return true; + } + } + return false; +} + +bool SkOpSegment::isXor() const { + return fContour->isXor(); +} + +void SkOpSegment::markAllDone() { + SkOpSpan* span = this->head(); + do { + this->markDone(span); + } while ((span = span->next()->upCastable())); +} + + bool SkOpSegment::markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end, SkOpSpanBase** found) { + int step = start->step(end); + SkOpSpan* minSpan = start->starter(end); + markDone(minSpan); + SkOpSpanBase* last = nullptr; + SkOpSegment* other = this; + SkOpSpan* priorDone = nullptr; + SkOpSpan* lastDone = nullptr; + int safetyNet = 100000; + while ((other = other->nextChase(&start, &step, &minSpan, &last))) { + if (!--safetyNet) { + return false; + } + if (other->done()) { + SkASSERT(!last); + break; + } + if (lastDone == minSpan || priorDone == minSpan) { + if (found) { + *found = nullptr; + } + return true; + } + other->markDone(minSpan); + priorDone = lastDone; + lastDone = minSpan; + } + if (found) { + *found = last; + } + return true; +} + +bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, + SkOpSpanBase** lastPtr) { + SkOpSpan* spanStart = start->starter(end); + int step = start->step(end); + bool success = markWinding(spanStart, winding); + SkOpSpanBase* last = nullptr; + SkOpSegment* other = this; + int safetyNet = 100000; + while ((other = other->nextChase(&start, &step, &spanStart, &last))) { + if (!--safetyNet) { + return false; + } + if (spanStart->windSum() != SK_MinS32) { +// SkASSERT(spanStart->windSum() == winding); // FIXME: is this assert too aggressive? + SkASSERT(!last); + break; + } + (void) other->markWinding(spanStart, winding); + } + if (lastPtr) { + *lastPtr = last; + } + return success; +} + +bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, + int winding, int oppWinding, SkOpSpanBase** lastPtr) { + SkOpSpan* spanStart = start->starter(end); + int step = start->step(end); + bool success = markWinding(spanStart, winding, oppWinding); + SkOpSpanBase* last = nullptr; + SkOpSegment* other = this; + int safetyNet = 100000; + while ((other = other->nextChase(&start, &step, &spanStart, &last))) { + if (!--safetyNet) { + return false; + } + if (spanStart->windSum() != SK_MinS32) { + if (this->operand() == other->operand()) { + if (spanStart->windSum() != winding || spanStart->oppSum() != oppWinding) { + this->globalState()->setWindingFailed(); + return true; // ... but let it succeed anyway + } + } else { + FAIL_IF(spanStart->windSum() != oppWinding); + FAIL_IF(spanStart->oppSum() != winding); + } + SkASSERT(!last); + break; + } + if (this->operand() == other->operand()) { + (void) other->markWinding(spanStart, winding, oppWinding); + } else { + (void) other->markWinding(spanStart, oppWinding, winding); + } + } + if (lastPtr) { + *lastPtr = last; + } + return success; +} + +bool SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle, + SkOpSpanBase** result) { + SkASSERT(angle->segment() == this); + if (UseInnerWinding(maxWinding, sumWinding)) { + maxWinding = sumWinding; + } + if (!markAndChaseWinding(angle->start(), angle->end(), maxWinding, result)) { + return false; + } +#if DEBUG_WINDING + SkOpSpanBase* last = *result; + if (last) { + SkDebugf("%s last seg=%d span=%d", __FUNCTION__, + last->segment()->debugID(), last->debugID()); + if (!last->final()) { + SkDebugf(" windSum="); + SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); + } + SkDebugf("\n"); + } +#endif + return true; +} + +bool SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding, + int oppSumWinding, const SkOpAngle* angle, SkOpSpanBase** result) { + SkASSERT(angle->segment() == this); + if (UseInnerWinding(maxWinding, sumWinding)) { + maxWinding = sumWinding; + } + if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) { + oppMaxWinding = oppSumWinding; + } + // caller doesn't require that this marks anything + if (!markAndChaseWinding(angle->start(), angle->end(), maxWinding, oppMaxWinding, result)) { + return false; + } +#if DEBUG_WINDING + if (result) { + SkOpSpanBase* last = *result; + if (last) { + SkDebugf("%s last segment=%d span=%d", __FUNCTION__, + last->segment()->debugID(), last->debugID()); + if (!last->final()) { + SkDebugf(" windSum="); + SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); + } + SkDebugf(" \n"); + } + } +#endif + return true; +} + +void SkOpSegment::markDone(SkOpSpan* span) { + SkASSERT(this == span->segment()); + if (span->done()) { + return; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(__FUNCTION__, span, span->windSum(), span->oppSum()); +#endif + span->setDone(true); + ++fDoneCount; + debugValidate(); +} + +bool SkOpSegment::markWinding(SkOpSpan* span, int winding) { + SkASSERT(this == span->segment()); + SkASSERT(winding); + if (span->done()) { + return false; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(__FUNCTION__, span, winding); +#endif + span->setWindSum(winding); + debugValidate(); + return true; +} + +bool SkOpSegment::markWinding(SkOpSpan* span, int winding, int oppWinding) { + SkASSERT(this == span->segment()); + SkASSERT(winding || oppWinding); + if (span->done()) { + return false; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(__FUNCTION__, span, winding, oppWinding); +#endif + span->setWindSum(winding); + span->setOppSum(oppWinding); + debugValidate(); + return true; +} + +bool SkOpSegment::match(const SkOpPtT* base, const SkOpSegment* testParent, double testT, + const SkPoint& testPt) const { + SkASSERT(this == base->segment()); + if (this == testParent) { + if (precisely_equal(base->fT, testT)) { + return true; + } + } + if (!SkDPoint::ApproximatelyEqual(testPt, base->fPt)) { + return false; + } + return this != testParent || !this->ptsDisjoint(base->fT, base->fPt, testT, testPt); +} + +static SkOpSegment* set_last(SkOpSpanBase** last, SkOpSpanBase* endSpan) { + if (last) { + *last = endSpan; + } + return nullptr; +} + +SkOpSegment* SkOpSegment::nextChase(SkOpSpanBase** startPtr, int* stepPtr, SkOpSpan** minPtr, + SkOpSpanBase** last) const { + SkOpSpanBase* origStart = *startPtr; + int step = *stepPtr; + SkOpSpanBase* endSpan = step > 0 ? origStart->upCast()->next() : origStart->prev(); + SkASSERT(endSpan); + SkOpAngle* angle = step > 0 ? endSpan->fromAngle() : endSpan->upCast()->toAngle(); + SkOpSpanBase* foundSpan; + SkOpSpanBase* otherEnd; + SkOpSegment* other; + if (angle == nullptr) { + if (endSpan->t() != 0 && endSpan->t() != 1) { + return nullptr; + } + SkOpPtT* otherPtT = endSpan->ptT()->next(); + other = otherPtT->segment(); + foundSpan = otherPtT->span(); + otherEnd = step > 0 + ? foundSpan->upCastable() ? foundSpan->upCast()->next() : nullptr + : foundSpan->prev(); + } else { + int loopCount = angle->loopCount(); + if (loopCount > 2) { + return set_last(last, endSpan); + } + const SkOpAngle* next = angle->next(); + if (nullptr == next) { + return nullptr; + } +#if DEBUG_WINDING + if (angle->debugSign() != next->debugSign() && !angle->segment()->contour()->isXor() + && !next->segment()->contour()->isXor()) { + SkDebugf("%s mismatched signs\n", __FUNCTION__); + } +#endif + other = next->segment(); + foundSpan = endSpan = next->start(); + otherEnd = next->end(); + } + if (!otherEnd) { + return nullptr; + } + int foundStep = foundSpan->step(otherEnd); + if (*stepPtr != foundStep) { + return set_last(last, endSpan); + } + SkASSERT(*startPtr); +// SkASSERT(otherEnd >= 0); + SkOpSpan* origMin = step < 0 ? origStart->prev() : origStart->upCast(); + SkOpSpan* foundMin = foundSpan->starter(otherEnd); + if (foundMin->windValue() != origMin->windValue() + || foundMin->oppValue() != origMin->oppValue()) { + return set_last(last, endSpan); + } + *startPtr = foundSpan; + *stepPtr = foundStep; + if (minPtr) { + *minPtr = foundMin; + } + return other; +} + +// Please keep this in sync with DebugClearVisited() +void SkOpSegment::ClearVisited(SkOpSpanBase* span) { + // reset visited flag back to false + do { + SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + SkOpSegment* opp = ptT->segment(); + opp->resetVisited(); + } + } while (!span->final() && (span = span->upCast()->next())); +} + +// Please keep this in sync with debugMissingCoincidence() +// look for pairs of undetected coincident curves +// assumes that segments going in have visited flag clear +// Even though pairs of curves correct detect coincident runs, a run may be missed +// if the coincidence is a product of multiple intersections. For instance, given +// curves A, B, and C: +// A-B intersect at a point 1; A-C and B-C intersect at point 2, so near +// the end of C that the intersection is replaced with the end of C. +// Even though A-B correctly do not detect an intersection at point 2, +// the resulting run from point 1 to point 2 is coincident on A and B. +bool SkOpSegment::missingCoincidence() { + if (this->done()) { + return false; + } + SkOpSpan* prior = nullptr; + SkOpSpanBase* spanBase = &fHead; + bool result = false; + int safetyNet = 100000; + do { + SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; + SkOPASSERT(ptT->span() == spanBase); + while ((ptT = ptT->next()) != spanStopPtT) { + if (!--safetyNet) { + return false; + } + if (ptT->deleted()) { + continue; + } + SkOpSegment* opp = ptT->span()->segment(); + if (opp->done()) { + continue; + } + // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence + if (!opp->visited()) { + continue; + } + if (spanBase == &fHead) { + continue; + } + if (ptT->segment() == this) { + continue; + } + SkOpSpan* span = spanBase->upCastable(); + // FIXME?: this assumes that if the opposite segment is coincident then no more + // coincidence needs to be detected. This may not be true. + if (span && span->containsCoincidence(opp)) { + continue; + } + if (spanBase->containsCoinEnd(opp)) { + continue; + } + SkOpPtT* priorPtT = nullptr, * priorStopPtT; + // find prior span containing opp segment + SkOpSegment* priorOpp = nullptr; + SkOpSpan* priorTest = spanBase->prev(); + while (!priorOpp && priorTest) { + priorStopPtT = priorPtT = priorTest->ptT(); + while ((priorPtT = priorPtT->next()) != priorStopPtT) { + if (priorPtT->deleted()) { + continue; + } + SkOpSegment* segment = priorPtT->span()->segment(); + if (segment == opp) { + prior = priorTest; + priorOpp = opp; + break; + } + } + priorTest = priorTest->prev(); + } + if (!priorOpp) { + continue; + } + if (priorPtT == ptT) { + continue; + } + SkOpPtT* oppStart = prior->ptT(); + SkOpPtT* oppEnd = spanBase->ptT(); + bool swapped = priorPtT->fT > ptT->fT; + if (swapped) { + using std::swap; + swap(priorPtT, ptT); + swap(oppStart, oppEnd); + } + SkOpCoincidence* coincidences = this->globalState()->coincidence(); + SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); + SkOpPtT* rootPtT = ptT->span()->ptT(); + SkOpPtT* rootOppStart = oppStart->span()->ptT(); + SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); + if (coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + goto swapBack; + } + if (this->testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { + // mark coincidence +#if DEBUG_COINCIDENCE_VERBOSE + SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, + rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), + rootOppEnd->debugID()); +#endif + if (!coincidences->extend(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); + } +#if DEBUG_COINCIDENCE + SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)); +#endif + result = true; + } + swapBack: + if (swapped) { + using std::swap; + swap(priorPtT, ptT); + } + } + } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); + ClearVisited(&fHead); + return result; +} + +// please keep this in sync with debugMoveMultiples() +// if a span has more than one intersection, merge the other segments' span as needed +bool SkOpSegment::moveMultiples() { + debugValidate(); + SkOpSpanBase* test = &fHead; + do { + int addCount = test->spanAddsCount(); +// FAIL_IF(addCount < 1); + if (addCount <= 1) { + continue; + } + SkOpPtT* startPtT = test->ptT(); + SkOpPtT* testPtT = startPtT; + int safetyHatch = 1000000; + do { // iterate through all spans associated with start + if (!--safetyHatch) { + return false; + } + SkOpSpanBase* oppSpan = testPtT->span(); + if (oppSpan->spanAddsCount() == addCount) { + continue; + } + if (oppSpan->deleted()) { + continue; + } + SkOpSegment* oppSegment = oppSpan->segment(); + if (oppSegment == this) { + continue; + } + // find range of spans to consider merging + SkOpSpanBase* oppPrev = oppSpan; + SkOpSpanBase* oppFirst = oppSpan; + while ((oppPrev = oppPrev->prev())) { + if (!roughly_equal(oppPrev->t(), oppSpan->t())) { + break; + } + if (oppPrev->spanAddsCount() == addCount) { + continue; + } + if (oppPrev->deleted()) { + continue; + } + oppFirst = oppPrev; + } + SkOpSpanBase* oppNext = oppSpan; + SkOpSpanBase* oppLast = oppSpan; + while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { + if (!roughly_equal(oppNext->t(), oppSpan->t())) { + break; + } + if (oppNext->spanAddsCount() == addCount) { + continue; + } + if (oppNext->deleted()) { + continue; + } + oppLast = oppNext; + } + if (oppFirst == oppLast) { + continue; + } + SkOpSpanBase* oppTest = oppFirst; + do { + if (oppTest == oppSpan) { + continue; + } + // check to see if the candidate meets specific criteria: + // it contains spans of segments in test's loop but not including 'this' + SkOpPtT* oppStartPtT = oppTest->ptT(); + SkOpPtT* oppPtT = oppStartPtT; + while ((oppPtT = oppPtT->next()) != oppStartPtT) { + SkOpSegment* oppPtTSegment = oppPtT->segment(); + if (oppPtTSegment == this) { + goto tryNextSpan; + } + SkOpPtT* matchPtT = startPtT; + do { + if (matchPtT->segment() == oppPtTSegment) { + goto foundMatch; + } + } while ((matchPtT = matchPtT->next()) != startPtT); + goto tryNextSpan; + foundMatch: // merge oppTest and oppSpan + oppSegment->debugValidate(); + oppTest->mergeMatches(oppSpan); + oppTest->addOpp(oppSpan); + oppSegment->debugValidate(); + goto checkNextSpan; + } + tryNextSpan: + ; + } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); + } while ((testPtT = testPtT->next()) != startPtT); +checkNextSpan: + ; + } while ((test = test->final() ? nullptr : test->upCast()->next())); + debugValidate(); + return true; +} + +// adjacent spans may have points close by +bool SkOpSegment::spansNearby(const SkOpSpanBase* refSpan, const SkOpSpanBase* checkSpan, + bool* found) const { + const SkOpPtT* refHead = refSpan->ptT(); + const SkOpPtT* checkHead = checkSpan->ptT(); +// if the first pt pair from adjacent spans are far apart, assume that all are far enough apart + if (!SkDPoint::WayRoughlyEqual(refHead->fPt, checkHead->fPt)) { +#if DEBUG_COINCIDENCE + // verify that no combination of points are close + const SkOpPtT* dBugRef = refHead; + do { + const SkOpPtT* dBugCheck = checkHead; + do { + SkOPASSERT(!SkDPoint::ApproximatelyEqual(dBugRef->fPt, dBugCheck->fPt)); + dBugCheck = dBugCheck->next(); + } while (dBugCheck != checkHead); + dBugRef = dBugRef->next(); + } while (dBugRef != refHead); +#endif + *found = false; + return true; + } + // check only unique points + SkScalar distSqBest = SK_ScalarMax; + const SkOpPtT* refBest = nullptr; + const SkOpPtT* checkBest = nullptr; + const SkOpPtT* ref = refHead; + do { + if (ref->deleted()) { + continue; + } + while (ref->ptAlreadySeen(refHead)) { + ref = ref->next(); + if (ref == refHead) { + goto doneCheckingDistance; + } + } + const SkOpPtT* check = checkHead; + const SkOpSegment* refSeg = ref->segment(); + int escapeHatch = 100000; // defend against infinite loops + do { + if (check->deleted()) { + continue; + } + while (check->ptAlreadySeen(checkHead)) { + check = check->next(); + if (check == checkHead) { + goto nextRef; + } + } + SkScalar distSq = SkPointPriv::DistanceToSqd(ref->fPt, check->fPt); + if (distSqBest > distSq && (refSeg != check->segment() + || !refSeg->ptsDisjoint(*ref, *check))) { + distSqBest = distSq; + refBest = ref; + checkBest = check; + } + if (--escapeHatch <= 0) { + return false; + } + } while ((check = check->next()) != checkHead); + nextRef: + ; + } while ((ref = ref->next()) != refHead); +doneCheckingDistance: + *found = checkBest && refBest->segment()->match(refBest, checkBest->segment(), checkBest->fT, + checkBest->fPt); + return true; +} + +// Please keep this function in sync with debugMoveNearby() +// Move nearby t values and pts so they all hang off the same span. Alignment happens later. +bool SkOpSegment::moveNearby() { + debugValidate(); + // release undeleted spans pointing to this seg that are linked to the primary span + SkOpSpanBase* spanBase = &fHead; + int escapeHatch = 9999; // the largest count for a regular test is 50; for a fuzzer, 500 + do { + SkOpPtT* ptT = spanBase->ptT(); + const SkOpPtT* headPtT = ptT; + while ((ptT = ptT->next()) != headPtT) { + if (!--escapeHatch) { + return false; + } + SkOpSpanBase* test = ptT->span(); + if (ptT->segment() == this && !ptT->deleted() && test != spanBase + && test->ptT() == ptT) { + if (test->final()) { + if (spanBase == &fHead) { + this->clearAll(); + return true; + } + spanBase->upCast()->release(ptT); + } else if (test->prev()) { + test->upCast()->release(headPtT); + } + break; + } + } + spanBase = spanBase->upCast()->next(); + } while (!spanBase->final()); + // This loop looks for adjacent spans which are near by + spanBase = &fHead; + do { // iterate through all spans associated with start + SkOpSpanBase* test = spanBase->upCast()->next(); + bool found; + if (!this->spansNearby(spanBase, test, &found)) { + return false; + } + if (found) { + if (test->final()) { + if (spanBase->prev()) { + test->merge(spanBase->upCast()); + } else { + this->clearAll(); + return true; + } + } else { + spanBase->merge(test->upCast()); + } + } + spanBase = test; + } while (!spanBase->final()); + debugValidate(); + return true; +} + +bool SkOpSegment::operand() const { + return fContour->operand(); +} + +bool SkOpSegment::oppXor() const { + return fContour->oppXor(); +} + +bool SkOpSegment::ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const { + if (fVerb == SkPath::kLine_Verb) { + return false; + } + // quads (and cubics) can loop back to nearly a line so that an opposite curve + // hits in two places with very different t values. + // OPTIMIZATION: curves could be preflighted so that, for example, something like + // 'controls contained by ends' could avoid this check for common curves + // 'ends are extremes in x or y' is cheaper to compute and real-world common + // on the other hand, the below check is relatively inexpensive + double midT = (t1 + t2) / 2; + SkPoint midPt = this->ptAtT(midT); + double seDistSq = SkTMax(SkPointPriv::DistanceToSqd(pt1, pt2) * 2, FLT_EPSILON * 2); + return SkPointPriv::DistanceToSqd(midPt, pt1) > seDistSq || + SkPointPriv::DistanceToSqd(midPt, pt2) > seDistSq; +} + +void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, + int* maxWinding, int* sumWinding) { + int deltaSum = SpanSign(start, end); + *maxWinding = *sumMiWinding; + *sumWinding = *sumMiWinding -= deltaSum; + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); +} + +void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, + int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, + int* oppSumWinding) { + int deltaSum = SpanSign(start, end); + int oppDeltaSum = OppSign(start, end); + if (operand()) { + *maxWinding = *sumSuWinding; + *sumWinding = *sumSuWinding -= deltaSum; + *oppMaxWinding = *sumMiWinding; + *oppSumWinding = *sumMiWinding -= oppDeltaSum; + } else { + *maxWinding = *sumMiWinding; + *sumWinding = *sumMiWinding -= deltaSum; + *oppMaxWinding = *sumSuWinding; + *oppSumWinding = *sumSuWinding -= oppDeltaSum; + } + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM); +} + +bool SkOpSegment::sortAngles() { + SkOpSpanBase* span = &this->fHead; + do { + SkOpAngle* fromAngle = span->fromAngle(); + SkOpAngle* toAngle = span->final() ? nullptr : span->upCast()->toAngle(); + if (!fromAngle && !toAngle) { + continue; + } +#if DEBUG_ANGLE + bool wroteAfterHeader = false; +#endif + SkOpAngle* baseAngle = fromAngle; + if (fromAngle && toAngle) { +#if DEBUG_ANGLE + SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), span->t(), + span->debugID()); + wroteAfterHeader = true; +#endif + FAIL_IF(!fromAngle->insert(toAngle)); + } else if (!fromAngle) { + baseAngle = toAngle; + } + SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; + int safetyNet = 1000000; + do { + if (!--safetyNet) { + return false; + } + SkOpSpanBase* oSpan = ptT->span(); + if (oSpan == span) { + continue; + } + SkOpAngle* oAngle = oSpan->fromAngle(); + if (oAngle) { +#if DEBUG_ANGLE + if (!wroteAfterHeader) { + SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), + span->t(), span->debugID()); + wroteAfterHeader = true; + } +#endif + if (!oAngle->loopContains(baseAngle)) { + baseAngle->insert(oAngle); + } + } + if (!oSpan->final()) { + oAngle = oSpan->upCast()->toAngle(); + if (oAngle) { +#if DEBUG_ANGLE + if (!wroteAfterHeader) { + SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), + span->t(), span->debugID()); + wroteAfterHeader = true; + } +#endif + if (!oAngle->loopContains(baseAngle)) { + baseAngle->insert(oAngle); + } + } + } + } while ((ptT = ptT->next()) != stopPtT); + if (baseAngle->loopCount() == 1) { + span->setFromAngle(nullptr); + if (toAngle) { + span->upCast()->setToAngle(nullptr); + } + baseAngle = nullptr; + } +#if DEBUG_SORT + SkASSERT(!baseAngle || baseAngle->loopCount() > 1); +#endif + } while (!span->final() && (span = span->upCast()->next())); + return true; +} + +bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, + SkDCurve* edge) const { + SkASSERT(start != end); + const SkOpPtT& startPtT = *start->ptT(); + const SkOpPtT& endPtT = *end->ptT(); + SkDEBUGCODE(edge->fVerb = fVerb); + edge->fCubic[0].set(startPtT.fPt); + int points = SkPathOpsVerbToPoints(fVerb); + edge->fCubic[points].set(endPtT.fPt); + if (fVerb == SkPath::kLine_Verb) { + return false; + } + double startT = startPtT.fT; + double endT = endPtT.fT; + if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) { + // don't compute midpoints if we already have them + if (fVerb == SkPath::kQuad_Verb) { + edge->fLine[1].set(fPts[1]); + return false; + } + if (fVerb == SkPath::kConic_Verb) { + edge->fConic[1].set(fPts[1]); + edge->fConic.fWeight = fWeight; + return false; + } + SkASSERT(fVerb == SkPath::kCubic_Verb); + if (startT == 0) { + edge->fCubic[1].set(fPts[1]); + edge->fCubic[2].set(fPts[2]); + return false; + } + edge->fCubic[1].set(fPts[2]); + edge->fCubic[2].set(fPts[1]); + return false; + } + if (fVerb == SkPath::kQuad_Verb) { + edge->fQuad[1] = SkDQuad::SubDivide(fPts, edge->fQuad[0], edge->fQuad[2], startT, endT); + } else if (fVerb == SkPath::kConic_Verb) { + edge->fConic[1] = SkDConic::SubDivide(fPts, fWeight, edge->fQuad[0], edge->fQuad[2], + startT, endT, &edge->fConic.fWeight); + } else { + SkASSERT(fVerb == SkPath::kCubic_Verb); + SkDCubic::SubDivide(fPts, edge->fCubic[0], edge->fCubic[3], startT, endT, &edge->fCubic[1]); + } + return true; +} + +bool SkOpSegment::testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, + const SkOpSpanBase* prior, const SkOpSpanBase* spanBase, const SkOpSegment* opp) const { + // average t, find mid pt + double midT = (prior->t() + spanBase->t()) / 2; + SkPoint midPt = this->ptAtT(midT); + bool coincident = true; + // if the mid pt is not near either end pt, project perpendicular through opp seg + if (!SkDPoint::ApproximatelyEqual(priorPtT->fPt, midPt) + && !SkDPoint::ApproximatelyEqual(ptT->fPt, midPt)) { + if (priorPtT->span() == ptT->span()) { + return false; + } + coincident = false; + SkIntersections i; + SkDCurve curvePart; + this->subDivide(prior, spanBase, &curvePart); + SkDVector dxdy = (*CurveDDSlopeAtT[fVerb])(curvePart, 0.5f); + SkDPoint partMidPt = (*CurveDDPointAtT[fVerb])(curvePart, 0.5f); + SkDLine ray = {{{midPt.fX, midPt.fY}, {partMidPt.fX + dxdy.fY, partMidPt.fY - dxdy.fX}}}; + SkDCurve oppPart; + opp->subDivide(priorPtT->span(), ptT->span(), &oppPart); + (*CurveDIntersectRay[opp->verb()])(oppPart, ray, &i); + // measure distance and see if it's small enough to denote coincidence + for (int index = 0; index < i.used(); ++index) { + if (!between(0, i[0][index], 1)) { + continue; + } + SkDPoint oppPt = i.pt(index); + if (oppPt.approximatelyDEqual(midPt)) { + // the coincidence can occur at almost any angle + coincident = true; + } + } + } + return coincident; +} + +SkOpSpan* SkOpSegment::undoneSpan() { + SkOpSpan* span = &fHead; + SkOpSpanBase* next; + do { + next = span->next(); + if (!span->done()) { + return span; + } + } while (!next->final() && (span = next->upCast())); + return nullptr; +} + +int SkOpSegment::updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const { + const SkOpSpan* lesser = start->starter(end); + int oppWinding = lesser->oppSum(); + int oppSpanWinding = SkOpSegment::OppSign(start, end); + if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding) + && oppWinding != SK_MaxS32) { + oppWinding -= oppSpanWinding; + } + return oppWinding; +} + +int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const { + const SkOpSpanBase* startSpan = angle->start(); + const SkOpSpanBase* endSpan = angle->end(); + return updateOppWinding(endSpan, startSpan); +} + +int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const { + const SkOpSpanBase* startSpan = angle->start(); + const SkOpSpanBase* endSpan = angle->end(); + return updateOppWinding(startSpan, endSpan); +} + +int SkOpSegment::updateWinding(SkOpSpanBase* start, SkOpSpanBase* end) { + SkOpSpan* lesser = start->starter(end); + int winding = lesser->windSum(); + if (winding == SK_MinS32) { + winding = lesser->computeWindSum(); + } + if (winding == SK_MinS32) { + return winding; + } + int spanWinding = SkOpSegment::SpanSign(start, end); + if (winding && UseInnerWinding(winding - spanWinding, winding) + && winding != SK_MaxS32) { + winding -= spanWinding; + } + return winding; +} + +int SkOpSegment::updateWinding(SkOpAngle* angle) { + SkOpSpanBase* startSpan = angle->start(); + SkOpSpanBase* endSpan = angle->end(); + return updateWinding(endSpan, startSpan); +} + +int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) { + SkOpSpanBase* startSpan = angle->start(); + SkOpSpanBase* endSpan = angle->end(); + return updateWinding(startSpan, endSpan); +} + +// OPTIMIZATION: does the following also work, and is it any faster? +// return outerWinding * innerWinding > 0 +// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0))) +bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) { + SkASSERT(outerWinding != SK_MaxS32); + SkASSERT(innerWinding != SK_MaxS32); + int absOut = SkTAbs(outerWinding); + int absIn = SkTAbs(innerWinding); + bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn; + return result; +} + +int SkOpSegment::windSum(const SkOpAngle* angle) const { + const SkOpSpan* minSpan = angle->start()->starter(angle->end()); + return minSpan->windSum(); +} diff --git a/gfx/skia/skia/src/pathops/SkOpSegment.h b/gfx/skia/skia/src/pathops/SkOpSegment.h new file mode 100644 index 0000000000..032f822d19 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSegment.h @@ -0,0 +1,455 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSegment_DEFINE +#define SkOpSegment_DEFINE + +#include "src/core/SkArenaAlloc.h" +#include "src/pathops/SkOpAngle.h" +#include "src/pathops/SkOpSpan.h" +#include "src/pathops/SkPathOpsBounds.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsCurve.h" + +struct SkDCurve; +class SkOpCoincidence; +class SkOpContour; +enum class SkOpRayDir; +struct SkOpRayHit; +class SkPathWriter; + +class SkOpSegment { +public: + bool operator<(const SkOpSegment& rh) const { + return fBounds.fTop < rh.fBounds.fTop; + } + + SkOpAngle* activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, + bool* done); + SkOpAngle* activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done); + SkOpAngle* activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done); + bool activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, + SkPathOp op); + bool activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, SkPathOp op, + int* sumMiWinding, int* sumSuWinding); + + bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end); + bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding); + + SkOpSegment* addConic(SkPoint pts[3], SkScalar weight, SkOpContour* parent) { + init(pts, weight, parent, SkPath::kConic_Verb); + SkDCurve curve; + curve.fConic.set(pts, weight); + curve.setConicBounds(pts, weight, 0, 1, &fBounds); + return this; + } + + SkOpSegment* addCubic(SkPoint pts[4], SkOpContour* parent) { + init(pts, 1, parent, SkPath::kCubic_Verb); + SkDCurve curve; + curve.fCubic.set(pts); + curve.setCubicBounds(pts, 1, 0, 1, &fBounds); + return this; + } + + bool addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path) const; + + SkOpAngle* addEndSpan() { + SkOpAngle* angle = this->globalState()->allocator()->make(); + angle->set(&fTail, fTail.prev()); + fTail.setFromAngle(angle); + return angle; + } + + bool addExpanded(double newT, const SkOpSpanBase* test, bool* startOver); + + SkOpSegment* addLine(SkPoint pts[2], SkOpContour* parent) { + SkASSERT(pts[0] != pts[1]); + init(pts, 1, parent, SkPath::kLine_Verb); + fBounds.setBounds(pts, 2); + return this; + } + + SkOpPtT* addMissing(double t, SkOpSegment* opp, bool* allExist); + + SkOpAngle* addStartSpan() { + SkOpAngle* angle = this->globalState()->allocator()->make(); + angle->set(&fHead, fHead.next()); + fHead.setToAngle(angle); + return angle; + } + + SkOpSegment* addQuad(SkPoint pts[3], SkOpContour* parent) { + init(pts, 1, parent, SkPath::kQuad_Verb); + SkDCurve curve; + curve.fQuad.set(pts); + curve.setQuadBounds(pts, 1, 0, 1, &fBounds); + return this; + } + + SkOpPtT* addT(double t); + SkOpPtT* addT(double t, const SkPoint& pt); + + const SkPathOpsBounds& bounds() const { + return fBounds; + } + + void bumpCount() { + ++fCount; + } + + void calcAngles(); + SkOpSpanBase::Collapsed collapsed(double startT, double endT) const; + static bool ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType ); + static bool ComputeOneSumReverse(SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType ); + int computeSum(SkOpSpanBase* start, SkOpSpanBase* end, SkOpAngle::IncludeType includeType); + + void clearAll(); + void clearOne(SkOpSpan* span); + static void ClearVisited(SkOpSpanBase* span); + bool contains(double t) const; + + SkOpContour* contour() const { + return fContour; + } + + int count() const { + return fCount; + } + + void debugAddAngle(double startT, double endT); +#if DEBUG_COIN + const SkOpPtT* debugAddT(double t, SkPathOpsDebug::GlitchLog* ) const; +#endif + const SkOpAngle* debugAngle(int id) const; +#if DEBUG_ANGLE + void debugCheckAngleCoin() const; +#endif +#if DEBUG_COIN + void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const; + void debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const; + void debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const; +#endif + const SkOpCoincidence* debugCoincidence() const; + SkOpContour* debugContour(int id) const; + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + + SkOpAngle* debugLastAngle(); +#if DEBUG_COIN + void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* glitches) const; + void debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const; + void debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const; +#endif + const SkOpPtT* debugPtT(int id) const; + void debugReset(); + const SkOpSegment* debugSegment(int id) const; + +#if DEBUG_ACTIVE_SPANS + void debugShowActiveSpans(SkString* str) const; +#endif +#if DEBUG_MARK_DONE + void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding); + void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, int oppWinding); +#endif + + const SkOpSpanBase* debugSpan(int id) const; + void debugValidate() const; + +#if DEBUG_COINCIDENCE_ORDER + void debugResetCoinT() const; + void debugSetCoinT(int, SkScalar ) const; +#endif + +#if DEBUG_COIN + static void DebugClearVisited(const SkOpSpanBase* span); + + bool debugVisited() const { + if (!fDebugVisited) { + fDebugVisited = true; + return false; + } + return true; + } +#endif + +#if DEBUG_ANGLE + double distSq(double t, const SkOpAngle* opp) const; +#endif + + bool done() const { + SkOPASSERT(fDoneCount <= fCount); + return fDoneCount == fCount; + } + + bool done(const SkOpAngle* angle) const { + return angle->start()->starter(angle->end())->done(); + } + + SkDPoint dPtAtT(double mid) const { + return (*CurveDPointAtT[fVerb])(fPts, fWeight, mid); + } + + SkDVector dSlopeAtT(double mid) const { + return (*CurveDSlopeAtT[fVerb])(fPts, fWeight, mid); + } + + void dump() const; + void dumpAll() const; + void dumpAngles() const; + void dumpCoin() const; + void dumpPts(const char* prefix = "seg") const; + void dumpPtsInner(const char* prefix = "seg") const; + + const SkOpPtT* existing(double t, const SkOpSegment* opp) const; + SkOpSegment* findNextOp(SkTDArray* chase, SkOpSpanBase** nextStart, + SkOpSpanBase** nextEnd, bool* unsortable, bool* simple, + SkPathOp op, int xorMiMask, int xorSuMask); + SkOpSegment* findNextWinding(SkTDArray* chase, SkOpSpanBase** nextStart, + SkOpSpanBase** nextEnd, bool* unsortable); + SkOpSegment* findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable); + SkOpSpan* findSortableTop(SkOpContour* ); + SkOpGlobalState* globalState() const; + + const SkOpSpan* head() const { + return &fHead; + } + + SkOpSpan* head() { + return &fHead; + } + + void init(SkPoint pts[], SkScalar weight, SkOpContour* parent, SkPath::Verb verb); + + SkOpSpan* insert(SkOpSpan* prev) { + SkOpGlobalState* globalState = this->globalState(); + globalState->setAllocatedOpSpan(); + SkOpSpan* result = globalState->allocator()->make(); + SkOpSpanBase* next = prev->next(); + result->setPrev(prev); + prev->setNext(result); + SkDEBUGCODE(result->ptT()->fT = 0); + result->setNext(next); + if (next) { + next->setPrev(result); + } + return result; + } + + bool isClose(double t, const SkOpSegment* opp) const; + + bool isHorizontal() const { + return fBounds.fTop == fBounds.fBottom; + } + + SkOpSegment* isSimple(SkOpSpanBase** end, int* step) const { + return nextChase(end, step, nullptr, nullptr); + } + + bool isVertical() const { + return fBounds.fLeft == fBounds.fRight; + } + + bool isVertical(SkOpSpanBase* start, SkOpSpanBase* end) const { + return (*CurveIsVertical[fVerb])(fPts, fWeight, start->t(), end->t()); + } + + bool isXor() const; + + void joinEnds(SkOpSegment* start) { + fTail.ptT()->addOpp(start->fHead.ptT(), start->fHead.ptT()); + } + + const SkPoint& lastPt() const { + return fPts[SkPathOpsVerbToPoints(fVerb)]; + } + + void markAllDone(); + bool markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end, SkOpSpanBase** found); + bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, + SkOpSpanBase** lastPtr); + bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, + int oppWinding, SkOpSpanBase** lastPtr); + bool markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle, SkOpSpanBase** result); + bool markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, + const SkOpAngle* angle, SkOpSpanBase** result); + void markDone(SkOpSpan* ); + bool markWinding(SkOpSpan* , int winding); + bool markWinding(SkOpSpan* , int winding, int oppWinding); + bool match(const SkOpPtT* span, const SkOpSegment* parent, double t, const SkPoint& pt) const; + bool missingCoincidence(); + bool moveMultiples(); + bool moveNearby(); + + SkOpSegment* next() const { + return fNext; + } + + SkOpSegment* nextChase(SkOpSpanBase** , int* step, SkOpSpan** , SkOpSpanBase** last) const; + bool operand() const; + + static int OppSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { + int result = start->t() < end->t() ? -start->upCast()->oppValue() + : end->upCast()->oppValue(); + return result; + } + + bool oppXor() const; + + const SkOpSegment* prev() const { + return fPrev; + } + + SkPoint ptAtT(double mid) const { + return (*CurvePointAtT[fVerb])(fPts, fWeight, mid); + } + + const SkPoint* pts() const { + return fPts; + } + + bool ptsDisjoint(const SkOpPtT& span, const SkOpPtT& test) const { + SkASSERT(this == span.segment()); + SkASSERT(this == test.segment()); + return ptsDisjoint(span.fT, span.fPt, test.fT, test.fPt); + } + + bool ptsDisjoint(const SkOpPtT& span, double t, const SkPoint& pt) const { + SkASSERT(this == span.segment()); + return ptsDisjoint(span.fT, span.fPt, t, pt); + } + + bool ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const; + + void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkArenaAlloc*); + void release(const SkOpSpan* ); + +#if DEBUG_COIN + void resetDebugVisited() const { + fDebugVisited = false; + } +#endif + + void resetVisited() { + fVisited = false; + } + + void setContour(SkOpContour* contour) { + fContour = contour; + } + + void setNext(SkOpSegment* next) { + fNext = next; + } + + void setPrev(SkOpSegment* prev) { + fPrev = prev; + } + + void setUpWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* maxWinding, int* sumWinding) { + int deltaSum = SpanSign(start, end); + *maxWinding = *sumWinding; + if (*sumWinding == SK_MinS32) { + return; + } + *sumWinding -= deltaSum; + } + + void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, + int* maxWinding, int* sumWinding); + void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); + bool sortAngles(); + bool spansNearby(const SkOpSpanBase* ref, const SkOpSpanBase* check, bool* found) const; + + static int SpanSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { + int result = start->t() < end->t() ? -start->upCast()->windValue() + : end->upCast()->windValue(); + return result; + } + + SkOpAngle* spanToAngle(SkOpSpanBase* start, SkOpSpanBase* end) { + SkASSERT(start != end); + return start->t() < end->t() ? start->upCast()->toAngle() : start->fromAngle(); + } + + bool subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, SkDCurve* result) const; + + const SkOpSpanBase* tail() const { + return &fTail; + } + + SkOpSpanBase* tail() { + return &fTail; + } + + bool testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, const SkOpSpanBase* prior, + const SkOpSpanBase* spanBase, const SkOpSegment* opp) const; + + SkOpSpan* undoneSpan(); + int updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const; + int updateOppWinding(const SkOpAngle* angle) const; + int updateOppWindingReverse(const SkOpAngle* angle) const; + int updateWinding(SkOpSpanBase* start, SkOpSpanBase* end); + int updateWinding(SkOpAngle* angle); + int updateWindingReverse(const SkOpAngle* angle); + + static bool UseInnerWinding(int outerWinding, int innerWinding); + + SkPath::Verb verb() const { + return fVerb; + } + + // look for two different spans that point to the same opposite segment + bool visited() { + if (!fVisited) { + fVisited = true; + return false; + } + return true; + } + + SkScalar weight() const { + return fWeight; + } + + SkOpSpan* windingSpanAtT(double tHit); + int windSum(const SkOpAngle* angle) const; + +private: + SkOpSpan fHead; // the head span always has its t set to zero + SkOpSpanBase fTail; // the tail span always has its t set to one + SkOpContour* fContour; + SkOpSegment* fNext; // forward-only linked list used by contour to walk the segments + const SkOpSegment* fPrev; + SkPoint* fPts; // pointer into array of points owned by edge builder that may be tweaked + SkPathOpsBounds fBounds; // tight bounds + SkScalar fWeight; + int fCount; // number of spans (one for a non-intersecting segment) + int fDoneCount; // number of processed spans (zero initially) + SkPath::Verb fVerb; + bool fVisited; // used by missing coincidence check +#if DEBUG_COIN + mutable bool fDebugVisited; // used by debug missing coincidence check +#endif +#if DEBUG_COINCIDENCE_ORDER + mutable int fDebugBaseIndex; + mutable SkScalar fDebugBaseMin; // if > 0, the 1st t value in this seg vis-a-vis the ref seg + mutable SkScalar fDebugBaseMax; + mutable int fDebugLastIndex; + mutable SkScalar fDebugLastMin; // if > 0, the last t -- next t val - base has same sign + mutable SkScalar fDebugLastMax; +#endif + SkDEBUGCODE(int fID); +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpSpan.cpp b/gfx/skia/skia/src/pathops/SkOpSpan.cpp new file mode 100644 index 0000000000..ea57756b60 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSpan.cpp @@ -0,0 +1,484 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkOpSegment.h" +#include "src/pathops/SkPathWriter.h" + +bool SkOpPtT::alias() const { + return this->span()->ptT() != this; +} + +const SkOpPtT* SkOpPtT::active() const { + if (!fDeleted) { + return this; + } + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fSpan == fSpan && !ptT->fDeleted) { + return ptT; + } + } + return nullptr; // should never return deleted; caller must abort +} + +bool SkOpPtT::contains(const SkOpPtT* check) const { + SkOPASSERT(this != check); + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT == check) { + return true; + } + } + return false; +} + +bool SkOpPtT::contains(const SkOpSegment* segment, const SkPoint& pt) const { + SkASSERT(this->segment() != segment); + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fPt == pt && ptT->segment() == segment) { + return true; + } + } + return false; +} + +bool SkOpPtT::contains(const SkOpSegment* segment, double t) const { + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fT == t && ptT->segment() == segment) { + return true; + } + } + return false; +} + +const SkOpPtT* SkOpPtT::contains(const SkOpSegment* check) const { + SkASSERT(this->segment() != check); + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->segment() == check && !ptT->deleted()) { + return ptT; + } + } + return nullptr; +} + +SkOpContour* SkOpPtT::contour() const { + return segment()->contour(); +} + +const SkOpPtT* SkOpPtT::find(const SkOpSegment* segment) const { + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + do { + if (ptT->segment() == segment && !ptT->deleted()) { + return ptT; + } + ptT = ptT->fNext; + } while (stopPtT != ptT); +// SkASSERT(0); + return nullptr; +} + +SkOpGlobalState* SkOpPtT::globalState() const { + return contour()->globalState(); +} + +void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) { + fT = t; + fPt = pt; + fSpan = span; + fNext = this; + fDuplicatePt = duplicate; + fDeleted = false; + fCoincident = false; + SkDEBUGCODE(fID = span->globalState()->nextPtTID()); +} + +bool SkOpPtT::onEnd() const { + const SkOpSpanBase* span = this->span(); + if (span->ptT() != this) { + return false; + } + const SkOpSegment* segment = this->segment(); + return span == segment->head() || span == segment->tail(); +} + +bool SkOpPtT::ptAlreadySeen(const SkOpPtT* check) const { + while (this != check) { + if (this->fPt == check->fPt) { + return true; + } + check = check->fNext; + } + return false; +} + +SkOpPtT* SkOpPtT::prev() { + SkOpPtT* result = this; + SkOpPtT* next = this; + while ((next = next->fNext) != this) { + result = next; + } + SkASSERT(result->fNext == this); + return result; +} + +const SkOpSegment* SkOpPtT::segment() const { + return span()->segment(); +} + +SkOpSegment* SkOpPtT::segment() { + return span()->segment(); +} + +void SkOpPtT::setDeleted() { + SkASSERT(this->span()->debugDeleted() || this->span()->ptT() != this); + SkOPASSERT(!fDeleted); + fDeleted = true; +} + +bool SkOpSpanBase::addOpp(SkOpSpanBase* opp) { + SkOpPtT* oppPrev = this->ptT()->oppPrev(opp->ptT()); + if (!oppPrev) { + return true; + } + FAIL_IF(!this->mergeMatches(opp)); + this->ptT()->addOpp(opp->ptT(), oppPrev); + this->checkForCollapsedCoincidence(); + return true; +} + +SkOpSpanBase::Collapsed SkOpSpanBase::collapsed(double s, double e) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* startNext = nullptr; + const SkOpPtT* walk = start; + double min = walk->fT; + double max = min; + const SkOpSegment* segment = this->segment(); + int safetyNet = 100000; + while ((walk = walk->next()) != start) { + if (!--safetyNet) { + return Collapsed::kError; + } + if (walk == startNext) { + return Collapsed::kError; + } + if (walk->segment() != segment) { + continue; + } + min = SkTMin(min, walk->fT); + max = SkTMax(max, walk->fT); + if (between(min, s, max) && between(min, e, max)) { + return Collapsed::kYes; + } + startNext = start->next(); + } + return Collapsed::kNo; +} + +bool SkOpSpanBase::contains(const SkOpSpanBase* span) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* check = &span->fPtT; + SkOPASSERT(start != check); + const SkOpPtT* walk = start; + while ((walk = walk->next()) != start) { + if (walk == check) { + return true; + } + } + return false; +} + +const SkOpPtT* SkOpSpanBase::contains(const SkOpSegment* segment) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* walk = start; + while ((walk = walk->next()) != start) { + if (walk->deleted()) { + continue; + } + if (walk->segment() == segment && walk->span()->ptT() == walk) { + return walk; + } + } + return nullptr; +} + +bool SkOpSpanBase::containsCoinEnd(const SkOpSegment* segment) const { + SkASSERT(this->segment() != segment); + const SkOpSpanBase* next = this; + while ((next = next->fCoinEnd) != this) { + if (next->segment() == segment) { + return true; + } + } + return false; +} + +SkOpContour* SkOpSpanBase::contour() const { + return segment()->contour(); +} + +SkOpGlobalState* SkOpSpanBase::globalState() const { + return contour()->globalState(); +} + +void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { + fSegment = segment; + fPtT.init(this, t, pt, false); + fCoinEnd = this; + fFromAngle = nullptr; + fPrev = prev; + fSpanAdds = 0; + fAligned = true; + fChased = false; + SkDEBUGCODE(fCount = 1); + SkDEBUGCODE(fID = globalState()->nextSpanID()); + SkDEBUGCODE(fDebugDeleted = false); +} + +// this pair of spans share a common t value or point; merge them and eliminate duplicates +// this does not compute the best t or pt value; this merely moves all data into a single list +void SkOpSpanBase::merge(SkOpSpan* span) { + SkOpPtT* spanPtT = span->ptT(); + SkASSERT(this->t() != spanPtT->fT); + SkASSERT(!zero_or_one(spanPtT->fT)); + span->release(this->ptT()); + if (this->contains(span)) { + SkOPASSERT(0); // check to see if this ever happens -- should have been found earlier + return; // merge is already in the ptT loop + } + SkOpPtT* remainder = spanPtT->next(); + this->ptT()->insert(spanPtT); + while (remainder != spanPtT) { + SkOpPtT* next = remainder->next(); + SkOpPtT* compare = spanPtT->next(); + while (compare != spanPtT) { + SkOpPtT* nextC = compare->next(); + if (nextC->span() == remainder->span() && nextC->fT == remainder->fT) { + goto tryNextRemainder; + } + compare = nextC; + } + spanPtT->insert(remainder); +tryNextRemainder: + remainder = next; + } + fSpanAdds += span->fSpanAdds; +} + +// please keep in sync with debugCheckForCollapsedCoincidence() +void SkOpSpanBase::checkForCollapsedCoincidence() { + SkOpCoincidence* coins = this->globalState()->coincidence(); + if (coins->isEmpty()) { + return; + } +// the insert above may have put both ends of a coincident run in the same span +// for each coincident ptT in loop; see if its opposite in is also in the loop +// this implementation is the motivation for marking that a ptT is referenced by a coincident span + SkOpPtT* head = this->ptT(); + SkOpPtT* test = head; + do { + if (!test->coincident()) { + continue; + } + coins->markCollapsed(test); + } while ((test = test->next()) != head); + coins->releaseDeleted(); +} + +// please keep in sync with debugMergeMatches() +// Look to see if pt-t linked list contains same segment more than once +// if so, and if each pt-t is directly pointed to by spans in that segment, +// merge them +// keep the points, but remove spans so that the segment doesn't have 2 or more +// spans pointing to the same pt-t loop at different loop elements +bool SkOpSpanBase::mergeMatches(SkOpSpanBase* opp) { + SkOpPtT* test = &fPtT; + SkOpPtT* testNext; + const SkOpPtT* stop = test; + int safetyHatch = 1000000; + do { + if (!--safetyHatch) { + return false; + } + testNext = test->next(); + if (test->deleted()) { + continue; + } + SkOpSpanBase* testBase = test->span(); + SkASSERT(testBase->ptT() == test); + SkOpSegment* segment = test->segment(); + if (segment->done()) { + continue; + } + SkOpPtT* inner = opp->ptT(); + const SkOpPtT* innerStop = inner; + do { + if (inner->segment() != segment) { + continue; + } + if (inner->deleted()) { + continue; + } + SkOpSpanBase* innerBase = inner->span(); + SkASSERT(innerBase->ptT() == inner); + // when the intersection is first detected, the span base is marked if there are + // more than one point in the intersection. + if (!zero_or_one(inner->fT)) { + innerBase->upCast()->release(test); + } else { + SkOPASSERT(inner->fT != test->fT); + if (!zero_or_one(test->fT)) { + testBase->upCast()->release(inner); + } else { + segment->markAllDone(); // mark segment as collapsed + SkDEBUGCODE(testBase->debugSetDeleted()); + test->setDeleted(); + SkDEBUGCODE(innerBase->debugSetDeleted()); + inner->setDeleted(); + } + } +#ifdef SK_DEBUG // assert if another undeleted entry points to segment + const SkOpPtT* debugInner = inner; + while ((debugInner = debugInner->next()) != innerStop) { + if (debugInner->segment() != segment) { + continue; + } + if (debugInner->deleted()) { + continue; + } + SkOPASSERT(0); + } +#endif + break; + } while ((inner = inner->next()) != innerStop); + } while ((test = testNext) != stop); + this->checkForCollapsedCoincidence(); + return true; +} + +int SkOpSpan::computeWindSum() { + SkOpGlobalState* globals = this->globalState(); + SkOpContour* contourHead = globals->contourHead(); + int windTry = 0; + while (!this->sortableTop(contourHead) && ++windTry < SkOpGlobalState::kMaxWindingTries) { + } + return this->windSum(); +} + +bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const { + SkASSERT(this->segment() != segment); + const SkOpSpan* next = fCoincident; + do { + if (next->segment() == segment) { + return true; + } + } while ((next = next->fCoincident) != this); + return false; +} + +void SkOpSpan::init(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { + SkASSERT(t != 1); + initBase(segment, prev, t, pt); + fCoincident = this; + fToAngle = nullptr; + fWindSum = fOppSum = SK_MinS32; + fWindValue = 1; + fOppValue = 0; + fTopTTry = 0; + fChased = fDone = false; + segment->bumpCount(); + fAlreadyAdded = false; +} + +// Please keep this in sync with debugInsertCoincidence() +bool SkOpSpan::insertCoincidence(const SkOpSegment* segment, bool flipped, bool ordered) { + if (this->containsCoincidence(segment)) { + return true; + } + SkOpPtT* next = &fPtT; + while ((next = next->next()) != &fPtT) { + if (next->segment() == segment) { + SkOpSpan* span; + SkOpSpanBase* base = next->span(); + if (!ordered) { + const SkOpPtT* spanEndPtT = fNext->contains(segment); + FAIL_IF(!spanEndPtT); + const SkOpSpanBase* spanEnd = spanEndPtT->span(); + const SkOpPtT* start = base->ptT()->starter(spanEnd->ptT()); + FAIL_IF(!start->span()->upCastable()); + span = const_cast(start->span()->upCast()); + } else if (flipped) { + span = base->prev(); + FAIL_IF(!span); + } else { + FAIL_IF(!base->upCastable()); + span = base->upCast(); + } + this->insertCoincidence(span); + return true; + } + } +#if DEBUG_COINCIDENCE + SkASSERT(0); // FIXME? if we get here, the span is missing its opposite segment... +#endif + return true; +} + +void SkOpSpan::release(const SkOpPtT* kept) { + SkDEBUGCODE(fDebugDeleted = true); + SkOPASSERT(kept->span() != this); + SkASSERT(!final()); + SkOpSpan* prev = this->prev(); + SkASSERT(prev); + SkOpSpanBase* next = this->next(); + SkASSERT(next); + prev->setNext(next); + next->setPrev(prev); + this->segment()->release(this); + SkOpCoincidence* coincidence = this->globalState()->coincidence(); + if (coincidence) { + coincidence->fixUp(this->ptT(), kept); + } + this->ptT()->setDeleted(); + SkOpPtT* stopPtT = this->ptT(); + SkOpPtT* testPtT = stopPtT; + const SkOpSpanBase* keptSpan = kept->span(); + do { + if (this == testPtT->span()) { + testPtT->setSpan(keptSpan); + } + } while ((testPtT = testPtT->next()) != stopPtT); +} + +void SkOpSpan::setOppSum(int oppSum) { + SkASSERT(!final()); + if (fOppSum != SK_MinS32 && fOppSum != oppSum) { + this->globalState()->setWindingFailed(); + return; + } + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(oppSum) <= DEBUG_LIMIT_WIND_SUM); + fOppSum = oppSum; +} + +void SkOpSpan::setWindSum(int windSum) { + SkASSERT(!final()); + if (fWindSum != SK_MinS32 && fWindSum != windSum) { + this->globalState()->setWindingFailed(); + return; + } + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(windSum) <= DEBUG_LIMIT_WIND_SUM); + fWindSum = windSum; +} diff --git a/gfx/skia/skia/src/pathops/SkOpSpan.h b/gfx/skia/skia/src/pathops/SkOpSpan.h new file mode 100644 index 0000000000..d04ae50ae8 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSpan.h @@ -0,0 +1,578 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSpan_DEFINED +#define SkOpSpan_DEFINED + +#include "include/core/SkPoint.h" +#include "src/pathops/SkPathOpsDebug.h" +#include "src/pathops/SkPathOpsTypes.h" + +class SkArenaAlloc; +class SkOpAngle; +class SkOpContour; +class SkOpGlobalState; +class SkOpSegment; +class SkOpSpanBase; +class SkOpSpan; +struct SkPathOpsBounds; + +// subset of op span used by terminal span (when t is equal to one) +class SkOpPtT { +public: + enum { + kIsAlias = 1, + kIsDuplicate = 1 + }; + + const SkOpPtT* active() const; + + // please keep in sync with debugAddOpp() + void addOpp(SkOpPtT* opp, SkOpPtT* oppPrev) { + SkOpPtT* oldNext = this->fNext; + SkASSERT(this != opp); + this->fNext = opp; + SkASSERT(oppPrev != oldNext); + oppPrev->fNext = oldNext; + } + + bool alias() const; + bool coincident() const { return fCoincident; } + bool contains(const SkOpPtT* ) const; + bool contains(const SkOpSegment*, const SkPoint& ) const; + bool contains(const SkOpSegment*, double t) const; + const SkOpPtT* contains(const SkOpSegment* ) const; + SkOpContour* contour() const; + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + + void debugAddOpp(const SkOpPtT* opp, const SkOpPtT* oppPrev) const; + const SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; + bool debugContains(const SkOpPtT* ) const; + const SkOpPtT* debugContains(const SkOpSegment* check) const; + SkOpContour* debugContour(int id) const; + const SkOpPtT* debugEnder(const SkOpPtT* end) const; + int debugLoopLimit(bool report) const; + bool debugMatchID(int id) const; + const SkOpPtT* debugOppPrev(const SkOpPtT* opp) const; + const SkOpPtT* debugPtT(int id) const; + void debugResetCoinT() const; + const SkOpSegment* debugSegment(int id) const; + void debugSetCoinT(int ) const; + const SkOpSpanBase* debugSpan(int id) const; + void debugValidate() const; + + bool deleted() const { + return fDeleted; + } + + bool duplicate() const { + return fDuplicatePt; + } + + void dump() const; // available to testing only + void dumpAll() const; + void dumpBase() const; + + const SkOpPtT* find(const SkOpSegment* ) const; + SkOpGlobalState* globalState() const; + void init(SkOpSpanBase* , double t, const SkPoint& , bool dup); + + void insert(SkOpPtT* span) { + SkASSERT(span != this); + span->fNext = fNext; + fNext = span; + } + + const SkOpPtT* next() const { + return fNext; + } + + SkOpPtT* next() { + return fNext; + } + + bool onEnd() const; + + // returns nullptr if this is already in the opp ptT loop + SkOpPtT* oppPrev(const SkOpPtT* opp) const { + // find the fOpp ptr to opp + SkOpPtT* oppPrev = opp->fNext; + if (oppPrev == this) { + return nullptr; + } + while (oppPrev->fNext != opp) { + oppPrev = oppPrev->fNext; + if (oppPrev == this) { + return nullptr; + } + } + return oppPrev; + } + + static bool Overlaps(const SkOpPtT* s1, const SkOpPtT* e1, const SkOpPtT* s2, + const SkOpPtT* e2, const SkOpPtT** sOut, const SkOpPtT** eOut) { + const SkOpPtT* start1 = s1->fT < e1->fT ? s1 : e1; + const SkOpPtT* start2 = s2->fT < e2->fT ? s2 : e2; + *sOut = between(s1->fT, start2->fT, e1->fT) ? start2 + : between(s2->fT, start1->fT, e2->fT) ? start1 : nullptr; + const SkOpPtT* end1 = s1->fT < e1->fT ? e1 : s1; + const SkOpPtT* end2 = s2->fT < e2->fT ? e2 : s2; + *eOut = between(s1->fT, end2->fT, e1->fT) ? end2 + : between(s2->fT, end1->fT, e2->fT) ? end1 : nullptr; + if (*sOut == *eOut) { + SkOPOBJASSERT(s1, start1->fT >= end2->fT || start2->fT >= end1->fT); + return false; + } + SkASSERT(!*sOut || *sOut != *eOut); + return *sOut && *eOut; + } + + bool ptAlreadySeen(const SkOpPtT* head) const; + SkOpPtT* prev(); + + const SkOpSegment* segment() const; + SkOpSegment* segment(); + + void setCoincident() const { + SkOPASSERT(!fDeleted); + fCoincident = true; + } + + void setDeleted(); + + void setSpan(const SkOpSpanBase* span) { + fSpan = const_cast(span); + } + + const SkOpSpanBase* span() const { + return fSpan; + } + + SkOpSpanBase* span() { + return fSpan; + } + + const SkOpPtT* starter(const SkOpPtT* end) const { + return fT < end->fT ? this : end; + } + + double fT; + SkPoint fPt; // cache of point value at this t +protected: + SkOpSpanBase* fSpan; // contains winding data + SkOpPtT* fNext; // intersection on opposite curve or alias on this curve + bool fDeleted; // set if removed from span list + bool fDuplicatePt; // set if identical pt is somewhere in the next loop + // below mutable since referrer is otherwise always const + mutable bool fCoincident; // set if at some point a coincident span pointed here + SkDEBUGCODE(int fID); +}; + +class SkOpSpanBase { +public: + enum class Collapsed { + kNo, + kYes, + kError, + }; + + bool addOpp(SkOpSpanBase* opp); + + void bumpSpanAdds() { + ++fSpanAdds; + } + + bool chased() const { + return fChased; + } + + void checkForCollapsedCoincidence(); + + const SkOpSpanBase* coinEnd() const { + return fCoinEnd; + } + + Collapsed collapsed(double s, double e) const; + bool contains(const SkOpSpanBase* ) const; + const SkOpPtT* contains(const SkOpSegment* ) const; + + bool containsCoinEnd(const SkOpSpanBase* coin) const { + SkASSERT(this != coin); + const SkOpSpanBase* next = this; + while ((next = next->fCoinEnd) != this) { + if (next == coin) { + return true; + } + } + return false; + } + + bool containsCoinEnd(const SkOpSegment* ) const; + SkOpContour* contour() const; + + int debugBumpCount() { + return SkDEBUGRELEASE(++fCount, -1); + } + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + +#if DEBUG_COIN + void debugAddOpp(SkPathOpsDebug::GlitchLog* , const SkOpSpanBase* opp) const; +#endif + bool debugAlignedEnd(double t, const SkPoint& pt) const; + bool debugAlignedInner() const; + const SkOpAngle* debugAngle(int id) const; +#if DEBUG_COIN + void debugCheckForCollapsedCoincidence(SkPathOpsDebug::GlitchLog* ) const; +#endif + const SkOpCoincidence* debugCoincidence() const; + bool debugCoinEndLoopCheck() const; + SkOpContour* debugContour(int id) const; +#ifdef SK_DEBUG + bool debugDeleted() const { return fDebugDeleted; } +#endif +#if DEBUG_COIN + void debugInsertCoinEnd(SkPathOpsDebug::GlitchLog* , + const SkOpSpanBase* ) const; + void debugMergeMatches(SkPathOpsDebug::GlitchLog* log, + const SkOpSpanBase* opp) const; +#endif + const SkOpPtT* debugPtT(int id) const; + void debugResetCoinT() const; + const SkOpSegment* debugSegment(int id) const; + void debugSetCoinT(int ) const; +#ifdef SK_DEBUG + void debugSetDeleted() { fDebugDeleted = true; } +#endif + const SkOpSpanBase* debugSpan(int id) const; + const SkOpSpan* debugStarter(SkOpSpanBase const** endPtr) const; + SkOpGlobalState* globalState() const; + void debugValidate() const; + + bool deleted() const { + return fPtT.deleted(); + } + + void dump() const; // available to testing only + void dumpCoin() const; + void dumpAll() const; + void dumpBase() const; + void dumpHead() const; + + bool final() const { + return fPtT.fT == 1; + } + + SkOpAngle* fromAngle() const { + return fFromAngle; + } + + void initBase(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); + + // Please keep this in sync with debugInsertCoinEnd() + void insertCoinEnd(SkOpSpanBase* coin) { + if (containsCoinEnd(coin)) { + SkASSERT(coin->containsCoinEnd(this)); + return; + } + debugValidate(); + SkASSERT(this != coin); + SkOpSpanBase* coinNext = coin->fCoinEnd; + coin->fCoinEnd = this->fCoinEnd; + this->fCoinEnd = coinNext; + debugValidate(); + } + + void merge(SkOpSpan* span); + bool mergeMatches(SkOpSpanBase* opp); + + const SkOpSpan* prev() const { + return fPrev; + } + + SkOpSpan* prev() { + return fPrev; + } + + const SkPoint& pt() const { + return fPtT.fPt; + } + + const SkOpPtT* ptT() const { + return &fPtT; + } + + SkOpPtT* ptT() { + return &fPtT; + } + + SkOpSegment* segment() const { + return fSegment; + } + + void setAligned() { + fAligned = true; + } + + void setChased(bool chased) { + fChased = chased; + } + + void setFromAngle(SkOpAngle* angle) { + fFromAngle = angle; + } + + void setPrev(SkOpSpan* prev) { + fPrev = prev; + } + + bool simple() const { + fPtT.debugValidate(); + return fPtT.next()->next() == &fPtT; + } + + int spanAddsCount() const { + return fSpanAdds; + } + + const SkOpSpan* starter(const SkOpSpanBase* end) const { + const SkOpSpanBase* result = t() < end->t() ? this : end; + return result->upCast(); + } + + SkOpSpan* starter(SkOpSpanBase* end) { + SkASSERT(this->segment() == end->segment()); + SkOpSpanBase* result = t() < end->t() ? this : end; + return result->upCast(); + } + + SkOpSpan* starter(SkOpSpanBase** endPtr) { + SkOpSpanBase* end = *endPtr; + SkASSERT(this->segment() == end->segment()); + SkOpSpanBase* result; + if (t() < end->t()) { + result = this; + } else { + result = end; + *endPtr = this; + } + return result->upCast(); + } + + int step(const SkOpSpanBase* end) const { + return t() < end->t() ? 1 : -1; + } + + double t() const { + return fPtT.fT; + } + + void unaligned() { + fAligned = false; + } + + SkOpSpan* upCast() { + SkASSERT(!final()); + return (SkOpSpan*) this; + } + + const SkOpSpan* upCast() const { + SkOPASSERT(!final()); + return (const SkOpSpan*) this; + } + + SkOpSpan* upCastable() { + return final() ? nullptr : upCast(); + } + + const SkOpSpan* upCastable() const { + return final() ? nullptr : upCast(); + } + +private: + void alignInner(); + +protected: // no direct access to internals to avoid treating a span base as a span + SkOpPtT fPtT; // list of points and t values associated with the start of this span + SkOpSegment* fSegment; // segment that contains this span + SkOpSpanBase* fCoinEnd; // linked list of coincident spans that end here (may point to itself) + SkOpAngle* fFromAngle; // points to next angle from span start to end + SkOpSpan* fPrev; // previous intersection point + int fSpanAdds; // number of times intersections have been added to span + bool fAligned; + bool fChased; // set after span has been added to chase array + SkDEBUGCODE(int fCount); // number of pt/t pairs added + SkDEBUGCODE(int fID); + SkDEBUGCODE(bool fDebugDeleted); // set when span was merged with another span +}; + +class SkOpSpan : public SkOpSpanBase { +public: + bool alreadyAdded() const { + if (fAlreadyAdded) { + return true; + } + return false; + } + + bool clearCoincident() { + SkASSERT(!final()); + if (fCoincident == this) { + return false; + } + fCoincident = this; + return true; + } + + int computeWindSum(); + bool containsCoincidence(const SkOpSegment* ) const; + + bool containsCoincidence(const SkOpSpan* coin) const { + SkASSERT(this != coin); + const SkOpSpan* next = this; + while ((next = next->fCoincident) != this) { + if (next == coin) { + return true; + } + } + return false; + } + + bool debugCoinLoopCheck() const; +#if DEBUG_COIN + void debugInsertCoincidence(SkPathOpsDebug::GlitchLog* , const SkOpSpan* ) const; + void debugInsertCoincidence(SkPathOpsDebug::GlitchLog* , + const SkOpSegment* , bool flipped, bool ordered) const; +#endif + void dumpCoin() const; + bool dumpSpan() const; + + bool done() const { + SkASSERT(!final()); + return fDone; + } + + void init(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); + bool insertCoincidence(const SkOpSegment* , bool flipped, bool ordered); + + // Please keep this in sync with debugInsertCoincidence() + void insertCoincidence(SkOpSpan* coin) { + if (containsCoincidence(coin)) { + SkASSERT(coin->containsCoincidence(this)); + return; + } + debugValidate(); + SkASSERT(this != coin); + SkOpSpan* coinNext = coin->fCoincident; + coin->fCoincident = this->fCoincident; + this->fCoincident = coinNext; + debugValidate(); + } + + bool isCanceled() const { + SkASSERT(!final()); + return fWindValue == 0 && fOppValue == 0; + } + + bool isCoincident() const { + SkASSERT(!final()); + return fCoincident != this; + } + + void markAdded() { + fAlreadyAdded = true; + } + + SkOpSpanBase* next() const { + SkASSERT(!final()); + return fNext; + } + + int oppSum() const { + SkASSERT(!final()); + return fOppSum; + } + + int oppValue() const { + SkASSERT(!final()); + return fOppValue; + } + + void release(const SkOpPtT* ); + + SkOpPtT* setCoinStart(SkOpSpan* oldCoinStart, SkOpSegment* oppSegment); + + void setDone(bool done) { + SkASSERT(!final()); + fDone = done; + } + + void setNext(SkOpSpanBase* nextT) { + SkASSERT(!final()); + fNext = nextT; + } + + void setOppSum(int oppSum); + + void setOppValue(int oppValue) { + SkASSERT(!final()); + SkASSERT(fOppSum == SK_MinS32); + SkOPASSERT(!oppValue || !fDone); + fOppValue = oppValue; + } + + void setToAngle(SkOpAngle* angle) { + SkASSERT(!final()); + fToAngle = angle; + } + + void setWindSum(int windSum); + + void setWindValue(int windValue) { + SkASSERT(!final()); + SkASSERT(windValue >= 0); + SkASSERT(fWindSum == SK_MinS32); + SkOPASSERT(!windValue || !fDone); + fWindValue = windValue; + } + + bool sortableTop(SkOpContour* ); + + SkOpAngle* toAngle() const { + SkASSERT(!final()); + return fToAngle; + } + + int windSum() const { + SkASSERT(!final()); + return fWindSum; + } + + int windValue() const { + SkOPASSERT(!final()); + return fWindValue; + } + +private: // no direct access to internals to avoid treating a span base as a span + SkOpSpan* fCoincident; // linked list of spans coincident with this one (may point to itself) + SkOpAngle* fToAngle; // points to next angle from span start to end + SkOpSpanBase* fNext; // next intersection point + int fWindSum; // accumulated from contours surrounding this one. + int fOppSum; // for binary operators: the opposite winding sum + int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident + int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here + int fTopTTry; // specifies direction and t value to try next + bool fDone; // if set, this span to next higher T has been processed + bool fAlreadyAdded; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsAsWinding.cpp b/gfx/skia/skia/src/pathops/SkPathOpsAsWinding.cpp new file mode 100644 index 0000000000..80e52bee2a --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsAsWinding.cpp @@ -0,0 +1,427 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkRect.h" +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkPathOpsCommon.h" +#include +#include + +using std::vector; + +struct Contour { + enum class Direction { // SkPath::Direction doesn't have 'none' state + kCCW = -1, + kNone, + kCW, + }; + + Contour(const SkRect& bounds, int lastStart, int verbStart) + : fBounds(bounds) + , fVerbStart(lastStart) + , fVerbEnd(verbStart) { + } + + vector fChildren; + const SkRect fBounds; + SkPoint fMinXY{SK_ScalarMax, SK_ScalarMax}; + const int fVerbStart; + const int fVerbEnd; + Direction fDirection{Direction::kNone}; + bool fContained{false}; + bool fReverse{false}; +}; + +static const int kPtCount[] = { 1, 1, 2, 2, 3, 0 }; +static const int kPtIndex[] = { 0, 1, 1, 1, 1, 0 }; + +static Contour::Direction to_direction(SkScalar dy) { + return dy > 0 ? Contour::Direction::kCCW : dy < 0 ? Contour::Direction::kCW : + Contour::Direction::kNone; +} + +static int contains_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, const SkPoint& edge) { + SkRect bounds; + bounds.setBounds(pts, kPtCount[verb] + 1); + if (bounds.fTop > edge.fY) { + return 0; + } + if (bounds.fBottom <= edge.fY) { // check to see if y is at line end to avoid double counting + return 0; + } + if (bounds.fLeft >= edge.fX) { + return 0; + } + int winding = 0; + double tVals[3]; + Contour::Direction directions[3]; + // must intersect horz ray with curve in case it intersects more than once + int count = (*CurveIntercept[verb * 2])(pts, weight, edge.fY, tVals); + SkASSERT(between(0, count, 3)); + // remove results to the right of edge + for (int index = 0; index < count; ) { + SkScalar intersectX = (*CurvePointAtT[verb])(pts, weight, tVals[index]).fX; + if (intersectX < edge.fX) { + ++index; + continue; + } + if (intersectX > edge.fX) { + tVals[index] = tVals[--count]; + continue; + } + // if intersect x equals edge x, we need to determine if pts is to the left or right of edge + if (pts[0].fX < edge.fX && pts[kPtCount[verb]].fX < edge.fX) { + ++index; + continue; + } + // TODO : other cases need discriminating. need op angle code to figure it out + // example: edge ends 45 degree diagonal going up. If pts is to the left of edge, keep. + // if pts is to the right of edge, discard. With code as is, can't distiguish the two cases. + tVals[index] = tVals[--count]; + } + // use first derivative to determine if intersection is contributing +1 or -1 to winding + for (int index = 0; index < count; ++index) { + directions[index] = to_direction((*CurveSlopeAtT[verb])(pts, weight, tVals[index]).fY); + } + for (int index = 0; index < count; ++index) { + // skip intersections that end at edge and go up + if (zero_or_one(tVals[index]) && Contour::Direction::kCCW != directions[index]) { + continue; + } + winding += (int) directions[index]; + } + return winding; // note winding indicates containership, not contour direction +} + +static SkScalar conic_weight(const SkPath::Iter& iter, SkPath::Verb verb) { + return SkPath::kConic_Verb == verb ? iter.conicWeight() : 1; +} + +static SkPoint left_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, + Contour::Direction* direction) { + SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb); + SkPoint result; + double dy; + double t SK_INIT_TO_AVOID_WARNING; + int roots = 0; + if (SkPath::kLine_Verb == verb) { + result = pts[0].fX < pts[1].fX ? pts[0] : pts[1]; + dy = pts[1].fY - pts[0].fY; + } else if (SkPath::kQuad_Verb == verb) { + SkDQuad quad; + quad.set(pts); + if (!quad.monotonicInX()) { + roots = SkDQuad::FindExtrema(&quad[0].fX, &t); + } + if (roots) { + result = quad.ptAtT(t).asSkPoint(); + } else { + result = pts[0].fX < pts[2].fX ? pts[0] : pts[2]; + t = pts[0].fX < pts[2].fX ? 0 : 1; + } + dy = quad.dxdyAtT(t).fY; + } else if (SkPath::kConic_Verb == verb) { + SkDConic conic; + conic.set(pts, weight); + if (!conic.monotonicInX()) { + roots = SkDConic::FindExtrema(&conic[0].fX, weight, &t); + } + if (roots) { + result = conic.ptAtT(t).asSkPoint(); + } else { + result = pts[0].fX < pts[2].fX ? pts[0] : pts[2]; + t = pts[0].fX < pts[2].fX ? 0 : 1; + } + dy = conic.dxdyAtT(t).fY; + } else { + SkASSERT(SkPath::kCubic_Verb == verb); + SkDCubic cubic; + cubic.set(pts); + if (!cubic.monotonicInX()) { + double tValues[2]; + roots = SkDCubic::FindExtrema(&cubic[0].fX, tValues); + SkASSERT(roots <= 2); + for (int index = 0; index < roots; ++index) { + SkPoint temp = cubic.ptAtT(tValues[index]).asSkPoint(); + if (0 == index || result.fX > temp.fX) { + result = temp; + t = tValues[index]; + } + } + } + if (roots) { + result = cubic.ptAtT(t).asSkPoint(); + } else { + result = pts[0].fX < pts[3].fX ? pts[0] : pts[3]; + t = pts[0].fX < pts[3].fX ? 0 : 1; + } + dy = cubic.dxdyAtT(t).fY; + } + *direction = to_direction(dy); + return result; +} + +class OpAsWinding { +public: + enum class Edge { + kInitial, + kCompare, + }; + + OpAsWinding(const SkPath& path) + : fPath(path) { + } + + void contourBounds(vector* containers) { + SkRect bounds; + bounds.setEmpty(); + SkPath::RawIter iter(fPath); + SkPoint pts[4]; + SkPath::Verb verb; + int lastStart = 0; + int verbStart = 0; + do { + verb = iter.next(pts); + if (SkPath::kMove_Verb == verb) { + if (!bounds.isEmpty()) { + containers->emplace_back(bounds, lastStart, verbStart); + lastStart = verbStart; + } + bounds.setBounds(&pts[kPtIndex[verb]], kPtCount[verb]); + } + if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) { + SkRect verbBounds; + verbBounds.setBounds(&pts[kPtIndex[verb]], kPtCount[verb]); + bounds.joinPossiblyEmptyRect(verbBounds); + } + ++verbStart; + } while (SkPath::kDone_Verb != verb); + if (!bounds.isEmpty()) { + containers->emplace_back(bounds, lastStart, verbStart); + } + } + + int nextEdge(Contour& contour, Edge edge) { + SkPath::Iter iter(fPath, true); + SkPoint pts[4]; + SkPath::Verb verb; + int verbCount = -1; + int winding = 0; + do { + verb = iter.next(pts); + if (++verbCount < contour.fVerbStart) { + continue; + } + if (verbCount >= contour.fVerbEnd) { + continue; + } + if (SkPath::kLine_Verb > verb || verb > SkPath::kCubic_Verb) { + continue; + } + bool horizontal = true; + for (int index = 1; index <= kPtCount[verb]; ++index) { + if (pts[0].fY != pts[index].fY) { + horizontal = false; + break; + } + } + if (horizontal) { + continue; + } + if (edge == Edge::kCompare) { + winding += contains_edge(pts, verb, conic_weight(iter, verb), contour.fMinXY); + continue; + } + SkASSERT(edge == Edge::kInitial); + Contour::Direction direction; + SkPoint minXY = left_edge(pts, verb, conic_weight(iter, verb), &direction); + if (minXY.fX > contour.fMinXY.fX) { + continue; + } + if (minXY.fX == contour.fMinXY.fX) { + if (minXY.fY != contour.fMinXY.fY) { + continue; + } + if (direction == contour.fDirection) { + continue; + } + // incomplete: must sort edges to find the one most to left + // File a bug if this code path is triggered and AsWinding was + // expected to succeed. + SkDEBUGF("incomplete\n"); + // TODO: add edges as opangle and sort + } + contour.fMinXY = minXY; + contour.fDirection = direction; + } while (SkPath::kDone_Verb != verb); + return winding; + } + + bool containerContains(Contour& contour, Contour& test) { + // find outside point on lesser contour + // arbitrarily, choose non-horizontal edge where point <= bounds left + // note that if leftmost point is control point, may need tight bounds + // to find edge with minimum-x + if (SK_ScalarMax == test.fMinXY.fX) { + this->nextEdge(test, Edge::kInitial); + } + // find all edges on greater equal or to the left of one on lesser + contour.fMinXY = test.fMinXY; + int winding = this->nextEdge(contour, Edge::kCompare); + // if edge is up, mark contour cw, otherwise, ccw + // sum of greater edges direction should be cw, 0, ccw + test.fContained = winding != 0; + return -1 <= winding && winding <= 1; + } + + void inParent(Contour& contour, Contour& parent) { + // move contour into sibling list contained by parent + for (auto test : parent.fChildren) { + if (test->fBounds.contains(contour.fBounds)) { + inParent(contour, *test); + return; + } + } + // move parent's children into contour's children if contained by contour + for (auto iter = parent.fChildren.begin(); iter != parent.fChildren.end(); ) { + if (contour.fBounds.contains((*iter)->fBounds)) { + contour.fChildren.push_back(*iter); + iter = parent.fChildren.erase(iter); + continue; + } + ++iter; + } + parent.fChildren.push_back(&contour); + } + + bool checkContainerChildren(Contour* parent, Contour* child) { + for (auto grandChild : child->fChildren) { + if (!checkContainerChildren(child, grandChild)) { + return false; + } + } + if (parent) { + if (!containerContains(*parent, *child)) { + return false; + } + } + return true; + } + + bool markReverse(Contour* parent, Contour* child) { + bool reversed = false; + for (auto grandChild : child->fChildren) { + reversed |= markReverse(grandChild->fContained ? child : parent, grandChild); + } + if (parent && parent->fDirection == child->fDirection) { + child->fReverse = true; + child->fDirection = (Contour::Direction) -(int) child->fDirection; + return true; + } + return reversed; + } + + void reverseMarkedContours(vector& contours, SkPath* result) { + SkPath::RawIter iter(fPath); + int verbCount = 0; + for (auto contour : contours) { + SkPath reverse; + SkPath* temp = contour.fReverse ? &reverse : result; + do { + SkPoint pts[4]; + switch (iter.next(pts)) { + case SkPath::kMove_Verb: + temp->moveTo(pts[0]); + break; + case SkPath::kLine_Verb: + temp->lineTo(pts[1]); + break; + case SkPath::kQuad_Verb: + temp->quadTo(pts[1], pts[2]); + break; + case SkPath::kConic_Verb: + temp->conicTo(pts[1], pts[2], iter.conicWeight()); + break; + case SkPath::kCubic_Verb: + temp->cubicTo(pts[1], pts[2], pts[3]); + break; + case SkPath::kClose_Verb: + temp->close(); + break; + case SkPath::kDone_Verb: + break; + default: + SkASSERT(0); + } + } while (++verbCount < contour.fVerbEnd); + if (contour.fReverse) { + result->reverseAddPath(reverse); + } + } + } + +private: + const SkPath& fPath; +}; + +static bool set_result_path(SkPath* result, const SkPath& path, SkPath::FillType fillType) { + *result = path; + result->setFillType(fillType); + return true; +} + +bool SK_API AsWinding(const SkPath& path, SkPath* result) { + if (!path.isFinite()) { + return false; + } + SkPath::FillType fillType = path.getFillType(); + if (fillType == SkPath::kWinding_FillType + || fillType == SkPath::kInverseWinding_FillType ) { + return set_result_path(result, path, fillType); + } + fillType = path.isInverseFillType() ? SkPath::kInverseWinding_FillType : + SkPath::kWinding_FillType; + if (path.isEmpty() || path.isConvex()) { + return set_result_path(result, path, fillType); + } + // count contours + vector contours; // one per contour + OpAsWinding winder(path); + winder.contourBounds(&contours); + if (contours.size() <= 1) { + return set_result_path(result, path, fillType); + } + // create contour bounding box tree + Contour sorted(SkRect(), 0, 0); + for (auto& contour : contours) { + winder.inParent(contour, sorted); + } + // if sorted has no grandchildren, no child has to fix its children's winding + if (std::all_of(sorted.fChildren.begin(), sorted.fChildren.end(), + [](const Contour* contour) -> bool { return !contour->fChildren.size(); } )) { + return set_result_path(result, path, fillType); + } + // starting with outermost and moving inward, see if one path contains another + for (auto contour : sorted.fChildren) { + winder.nextEdge(*contour, OpAsWinding::Edge::kInitial); + if (!winder.checkContainerChildren(nullptr, contour)) { + return false; + } + } + // starting with outermost and moving inward, mark paths to reverse + bool reversed = false; + for (auto contour : sorted.fChildren) { + reversed |= winder.markReverse(nullptr, contour); + } + if (!reversed) { + return set_result_path(result, path, fillType); + } + SkPath temp; + temp.setFillType(fillType); + winder.reverseMarkedContours(contours, &temp); + result->swap(temp); + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsBounds.h b/gfx/skia/skia/src/pathops/SkPathOpsBounds.h new file mode 100644 index 0000000000..1ebd46f6c0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsBounds.h @@ -0,0 +1,65 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpBounds_DEFINED +#define SkPathOpBounds_DEFINED + +#include "include/core/SkRect.h" +#include "src/pathops/SkPathOpsRect.h" + +// SkPathOpsBounds, unlike SkRect, does not consider a line to be empty. +struct SkPathOpsBounds : public SkRect { + static bool Intersects(const SkPathOpsBounds& a, const SkPathOpsBounds& b) { + return AlmostLessOrEqualUlps(a.fLeft, b.fRight) + && AlmostLessOrEqualUlps(b.fLeft, a.fRight) + && AlmostLessOrEqualUlps(a.fTop, b.fBottom) + && AlmostLessOrEqualUlps(b.fTop, a.fBottom); + } + + // Note that add(), unlike SkRect::join() or SkRect::growToInclude() + // does not treat the bounds of horizontal and vertical lines as + // empty rectangles. + void add(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { + if (left < fLeft) fLeft = left; + if (top < fTop) fTop = top; + if (right > fRight) fRight = right; + if (bottom > fBottom) fBottom = bottom; + } + + void add(const SkPathOpsBounds& toAdd) { + add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom); + } + + void add(const SkPoint& pt) { + if (pt.fX < fLeft) fLeft = pt.fX; + if (pt.fY < fTop) fTop = pt.fY; + if (pt.fX > fRight) fRight = pt.fX; + if (pt.fY > fBottom) fBottom = pt.fY; + } + + void add(const SkDPoint& pt) { + if (pt.fX < fLeft) fLeft = SkDoubleToScalar(pt.fX); + if (pt.fY < fTop) fTop = SkDoubleToScalar(pt.fY); + if (pt.fX > fRight) fRight = SkDoubleToScalar(pt.fX); + if (pt.fY > fBottom) fBottom = SkDoubleToScalar(pt.fY); + } + + bool almostContains(const SkPoint& pt) const { + return AlmostLessOrEqualUlps(fLeft, pt.fX) + && AlmostLessOrEqualUlps(pt.fX, fRight) + && AlmostLessOrEqualUlps(fTop, pt.fY) + && AlmostLessOrEqualUlps(pt.fY, fBottom); + } + + bool contains(const SkPoint& pt) const { + return fLeft <= pt.fX && fTop <= pt.fY && + fRight >= pt.fX && fBottom >= pt.fY; + } + + typedef SkRect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp b/gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp new file mode 100644 index 0000000000..a4d0fcdc0e --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp @@ -0,0 +1,333 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMacros.h" +#include "src/core/SkTSort.h" +#include "src/pathops/SkAddIntersections.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkPathOpsCommon.h" +#include "src/pathops/SkPathWriter.h" + +const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, + bool* sortablePtr) { + // find first angle, initialize winding to computed fWindSum + SkOpSegment* segment = start->segment(); + const SkOpAngle* angle = segment->spanToAngle(start, end); + if (!angle) { + *windingPtr = SK_MinS32; + return nullptr; + } + bool computeWinding = false; + const SkOpAngle* firstAngle = angle; + bool loop = false; + bool unorderable = false; + int winding = SK_MinS32; + do { + angle = angle->next(); + if (!angle) { + return nullptr; + } + unorderable |= angle->unorderable(); + if ((computeWinding = unorderable || (angle == firstAngle && loop))) { + break; // if we get here, there's no winding, loop is unorderable + } + loop |= angle == firstAngle; + segment = angle->segment(); + winding = segment->windSum(angle); + } while (winding == SK_MinS32); + // if the angle loop contains an unorderable span, the angle order may be useless + // directly compute the winding in this case for each span + if (computeWinding) { + firstAngle = angle; + winding = SK_MinS32; + do { + SkOpSpanBase* startSpan = angle->start(); + SkOpSpanBase* endSpan = angle->end(); + SkOpSpan* lesser = startSpan->starter(endSpan); + int testWinding = lesser->windSum(); + if (testWinding == SK_MinS32) { + testWinding = lesser->computeWindSum(); + } + if (testWinding != SK_MinS32) { + segment = angle->segment(); + winding = testWinding; + } + angle = angle->next(); + } while (angle != firstAngle); + } + *sortablePtr = !unorderable; + *windingPtr = winding; + return angle; +} + +SkOpSpan* FindUndone(SkOpContourHead* contourHead) { + SkOpContour* contour = contourHead; + do { + if (contour->done()) { + continue; + } + SkOpSpan* result = contour->undoneSpan(); + if (result) { + return result; + } + } while ((contour = contour->next())); + return nullptr; +} + +SkOpSegment* FindChase(SkTDArray* chase, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr) { + while (chase->count()) { + SkOpSpanBase* span; + chase->pop(&span); + SkOpSegment* segment = span->segment(); + *startPtr = span->ptT()->next()->span(); + bool done = true; + *endPtr = nullptr; + if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) { + *startPtr = last->start(); + *endPtr = last->end(); + #if TRY_ROTATE + *chase->insert(0) = span; + #else + *chase->append() = span; + #endif + return last->segment(); + } + if (done) { + continue; + } + // find first angle, initialize winding to computed wind sum + int winding; + bool sortable; + const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable); + if (!angle) { + return nullptr; + } + if (winding == SK_MinS32) { + continue; + } + int sumWinding SK_INIT_TO_AVOID_WARNING; + if (sortable) { + segment = angle->segment(); + sumWinding = segment->updateWindingReverse(angle); + } + SkOpSegment* first = nullptr; + const SkOpAngle* firstAngle = angle; + while ((angle = angle->next()) != firstAngle) { + segment = angle->segment(); + SkOpSpanBase* start = angle->start(); + SkOpSpanBase* end = angle->end(); + int maxWinding SK_INIT_TO_AVOID_WARNING; + if (sortable) { + segment->setUpWinding(start, end, &maxWinding, &sumWinding); + } + if (!segment->done(angle)) { + if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) { + first = segment; + *startPtr = start; + *endPtr = end; + } + // OPTIMIZATION: should this also add to the chase? + if (sortable) { + // TODO: add error handling + SkAssertResult(segment->markAngle(maxWinding, sumWinding, angle, nullptr)); + } + } + } + if (first) { + #if TRY_ROTATE + *chase->insert(0) = span; + #else + *chase->append() = span; + #endif + return first; + } + } + return nullptr; +} + +bool SortContourList(SkOpContourHead** contourList, bool evenOdd, bool oppEvenOdd) { + SkTDArray list; + SkOpContour* contour = *contourList; + do { + if (contour->count()) { + contour->setOppXor(contour->operand() ? evenOdd : oppEvenOdd); + *list.append() = contour; + } + } while ((contour = contour->next())); + int count = list.count(); + if (!count) { + return false; + } + if (count > 1) { + SkTQSort(list.begin(), list.end() - 1); + } + contour = list[0]; + SkOpContourHead* contourHead = static_cast(contour); + contour->globalState()->setContourHead(contourHead); + *contourList = contourHead; + for (int index = 1; index < count; ++index) { + SkOpContour* next = list[index]; + contour->setNext(next); + contour = next; + } + contour->setNext(nullptr); + return true; +} + +static void calc_angles(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + do { + contour->calcAngles(); + } while ((contour = contour->next())); +} + +static bool missing_coincidence(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + bool result = false; + do { + result |= contour->missingCoincidence(); + } while ((contour = contour->next())); + return result; +} + +static bool move_multiples(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + do { + if (!contour->moveMultiples()) { + return false; + } + } while ((contour = contour->next())); + return true; +} + +static bool move_nearby(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + do { + if (!contour->moveNearby()) { + return false; + } + } while ((contour = contour->next())); + return true; +} + +static bool sort_angles(SkOpContourHead* contourList) { + SkOpContour* contour = contourList; + do { + if (!contour->sortAngles()) { + return false; + } + } while ((contour = contour->next())); + return true; +} + +bool HandleCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidence) { + SkOpGlobalState* globalState = contourList->globalState(); + // match up points within the coincident runs + if (!coincidence->addExpanded(DEBUG_PHASE_ONLY_PARAMS(kIntersecting))) { + return false; + } + // combine t values when multiple intersections occur on some segments but not others + if (!move_multiples(contourList DEBUG_PHASE_PARAMS(kWalking))) { + return false; + } + // move t values and points together to eliminate small/tiny gaps + if (!move_nearby(contourList DEBUG_COIN_PARAMS())) { + return false; + } + // add coincidence formed by pairing on curve points and endpoints + coincidence->correctEnds(DEBUG_PHASE_ONLY_PARAMS(kIntersecting)); + if (!coincidence->addEndMovedSpans(DEBUG_COIN_ONLY_PARAMS())) { + return false; + } + const int SAFETY_COUNT = 3; + int safetyHatch = SAFETY_COUNT; + // look for coincidence present in A-B and A-C but missing in B-C + do { + bool added; + if (!coincidence->addMissing(&added DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch))) { + return false; + } + if (!added) { + break; + } + if (!--safetyHatch) { + SkASSERT(globalState->debugSkipAssert()); + return false; + } + move_nearby(contourList DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch - 1)); + } while (true); + // check to see if, loosely, coincident ranges may be expanded + if (coincidence->expand(DEBUG_COIN_ONLY_PARAMS())) { + bool added; + if (!coincidence->addMissing(&added DEBUG_COIN_PARAMS())) { + return false; + } + if (!coincidence->addExpanded(DEBUG_COIN_ONLY_PARAMS())) { + return false; + } + if (!move_multiples(contourList DEBUG_COIN_PARAMS())) { + return false; + } + move_nearby(contourList DEBUG_COIN_PARAMS()); + } + // the expanded ranges may not align -- add the missing spans + if (!coincidence->addExpanded(DEBUG_PHASE_ONLY_PARAMS(kWalking))) { + return false; + } + // mark spans of coincident segments as coincident + coincidence->mark(DEBUG_COIN_ONLY_PARAMS()); + // look for coincidence lines and curves undetected by intersection + if (missing_coincidence(contourList DEBUG_COIN_PARAMS())) { + (void) coincidence->expand(DEBUG_PHASE_ONLY_PARAMS(kIntersecting)); + if (!coincidence->addExpanded(DEBUG_COIN_ONLY_PARAMS())) { + return false; + } + if (!coincidence->mark(DEBUG_PHASE_ONLY_PARAMS(kWalking))) { + return false; + } + } else { + (void) coincidence->expand(DEBUG_COIN_ONLY_PARAMS()); + } + (void) coincidence->expand(DEBUG_COIN_ONLY_PARAMS()); + + SkOpCoincidence overlaps(globalState); + safetyHatch = SAFETY_COUNT; + do { + SkOpCoincidence* pairs = overlaps.isEmpty() ? coincidence : &overlaps; + // adjust the winding value to account for coincident edges + if (!pairs->apply(DEBUG_ITER_ONLY_PARAMS(SAFETY_COUNT - safetyHatch))) { + return false; + } + // For each coincident pair that overlaps another, when the receivers (the 1st of the pair) + // are different, construct a new pair to resolve their mutual span + if (!pairs->findOverlaps(&overlaps DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch))) { + return false; + } + if (!--safetyHatch) { + SkASSERT(globalState->debugSkipAssert()); + return false; + } + } while (!overlaps.isEmpty()); + calc_angles(contourList DEBUG_COIN_PARAMS()); + if (!sort_angles(contourList)) { + return false; + } +#if DEBUG_COINCIDENCE_VERBOSE + coincidence->debugShowCoincidence(); +#endif +#if DEBUG_COINCIDENCE + coincidence->debugValidate(); +#endif + SkPathOpsDebug::ShowActiveSpans(contourList); + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCommon.h b/gfx/skia/skia/src/pathops/SkPathOpsCommon.h new file mode 100644 index 0000000000..af27f2eaf4 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCommon.h @@ -0,0 +1,30 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsCommon_DEFINED +#define SkPathOpsCommon_DEFINED + +#include "include/private/SkTDArray.h" +#include "src/pathops/SkOpAngle.h" + +class SkOpCoincidence; +class SkOpContour; +class SkPathWriter; + +const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, + bool* sortable); +SkOpSegment* FindChase(SkTDArray* chase, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr); +SkOpSpan* FindSortableTop(SkOpContourHead* ); +SkOpSpan* FindUndone(SkOpContourHead* ); +bool FixWinding(SkPath* path); +bool SortContourList(SkOpContourHead** , bool evenOdd, bool oppEvenOdd); +bool HandleCoincidence(SkOpContourHead* , SkOpCoincidence* ); +bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result + SkDEBUGPARAMS(bool skipAssert) + SkDEBUGPARAMS(const char* testName)); + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsConic.cpp b/gfx/skia/skia/src/pathops/SkPathOpsConic.cpp new file mode 100644 index 0000000000..c0bbda8ce9 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsConic.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkLineParameters.h" +#include "src/pathops/SkPathOpsConic.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsQuad.h" +#include "src/pathops/SkPathOpsRect.h" + +// cribbed from the float version in SkGeometry.cpp +static void conic_deriv_coeff(const double src[], + SkScalar w, + double coeff[3]) { + const double P20 = src[4] - src[0]; + const double P10 = src[2] - src[0]; + const double wP10 = w * P10; + coeff[0] = w * P20 - P20; + coeff[1] = P20 - 2 * wP10; + coeff[2] = wP10; +} + +static double conic_eval_tan(const double coord[], SkScalar w, double t) { + double coeff[3]; + conic_deriv_coeff(coord, w, coeff); + return t * (t * coeff[0] + coeff[1]) + coeff[2]; +} + +int SkDConic::FindExtrema(const double src[], SkScalar w, double t[1]) { + double coeff[3]; + conic_deriv_coeff(src, w, coeff); + + double tValues[2]; + int roots = SkDQuad::RootsValidT(coeff[0], coeff[1], coeff[2], tValues); + // In extreme cases, the number of roots returned can be 2. Pathops + // will fail later on, so there's no advantage to plumbing in an error + // return here. + // SkASSERT(0 == roots || 1 == roots); + + if (1 == roots) { + t[0] = tValues[0]; + return 1; + } + return 0; +} + +SkDVector SkDConic::dxdyAtT(double t) const { + SkDVector result = { + conic_eval_tan(&fPts[0].fX, fWeight, t), + conic_eval_tan(&fPts[0].fY, fWeight, t) + }; + if (result.fX == 0 && result.fY == 0) { + if (zero_or_one(t)) { + result = fPts[2] - fPts[0]; + } else { + // incomplete + SkDebugf("!k"); + } + } + return result; +} + +static double conic_eval_numerator(const double src[], SkScalar w, double t) { + SkASSERT(src); + SkASSERT(t >= 0 && t <= 1); + double src2w = src[2] * w; + double C = src[0]; + double A = src[4] - 2 * src2w + C; + double B = 2 * (src2w - C); + return (A * t + B) * t + C; +} + + +static double conic_eval_denominator(SkScalar w, double t) { + double B = 2 * (w - 1); + double C = 1; + double A = -B; + return (A * t + B) * t + C; +} + +bool SkDConic::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { + return cubic.hullIntersects(*this, isLinear); +} + +SkDPoint SkDConic::ptAtT(double t) const { + if (t == 0) { + return fPts[0]; + } + if (t == 1) { + return fPts[2]; + } + double denominator = conic_eval_denominator(fWeight, t); + SkDPoint result = { + sk_ieee_double_divide(conic_eval_numerator(&fPts[0].fX, fWeight, t), denominator), + sk_ieee_double_divide(conic_eval_numerator(&fPts[0].fY, fWeight, t), denominator) + }; + return result; +} + +/* see quad subdivide for point rationale */ +/* w rationale : the mid point between t1 and t2 could be determined from the computed a/b/c + values if the computed w was known. Since we know the mid point at (t1+t2)/2, we'll assume + that it is the same as the point on the new curve t==(0+1)/2. + + d / dz == conic_poly(dst, unknownW, .5) / conic_weight(unknownW, .5); + + conic_poly(dst, unknownW, .5) + = a / 4 + (b * unknownW) / 2 + c / 4 + = (a + c) / 4 + (bx * unknownW) / 2 + + conic_weight(unknownW, .5) + = unknownW / 2 + 1 / 2 + + d / dz == ((a + c) / 2 + b * unknownW) / (unknownW + 1) + d / dz * (unknownW + 1) == (a + c) / 2 + b * unknownW + unknownW = ((a + c) / 2 - d / dz) / (d / dz - b) + + Thus, w is the ratio of the distance from the mid of end points to the on-curve point, and the + distance of the on-curve point to the control point. + */ +SkDConic SkDConic::subDivide(double t1, double t2) const { + double ax, ay, az; + if (t1 == 0) { + ax = fPts[0].fX; + ay = fPts[0].fY; + az = 1; + } else if (t1 != 1) { + ax = conic_eval_numerator(&fPts[0].fX, fWeight, t1); + ay = conic_eval_numerator(&fPts[0].fY, fWeight, t1); + az = conic_eval_denominator(fWeight, t1); + } else { + ax = fPts[2].fX; + ay = fPts[2].fY; + az = 1; + } + double midT = (t1 + t2) / 2; + double dx = conic_eval_numerator(&fPts[0].fX, fWeight, midT); + double dy = conic_eval_numerator(&fPts[0].fY, fWeight, midT); + double dz = conic_eval_denominator(fWeight, midT); + double cx, cy, cz; + if (t2 == 1) { + cx = fPts[2].fX; + cy = fPts[2].fY; + cz = 1; + } else if (t2 != 0) { + cx = conic_eval_numerator(&fPts[0].fX, fWeight, t2); + cy = conic_eval_numerator(&fPts[0].fY, fWeight, t2); + cz = conic_eval_denominator(fWeight, t2); + } else { + cx = fPts[0].fX; + cy = fPts[0].fY; + cz = 1; + } + double bx = 2 * dx - (ax + cx) / 2; + double by = 2 * dy - (ay + cy) / 2; + double bz = 2 * dz - (az + cz) / 2; + if (!bz) { + bz = 1; // if bz is 0, weight is 0, control point has no effect: any value will do + } + SkDConic dst = {{{{ax / az, ay / az}, {bx / bz, by / bz}, {cx / cz, cy / cz}} + SkDEBUGPARAMS(fPts.fDebugGlobalState) }, + SkDoubleToScalar(bz / sqrt(az * cz)) }; + return dst; +} + +SkDPoint SkDConic::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2, + SkScalar* weight) const { + SkDConic chopped = this->subDivide(t1, t2); + *weight = chopped.fWeight; + return chopped[1]; +} + +int SkTConic::intersectRay(SkIntersections* i, const SkDLine& line) const { + return i->intersectRay(fConic, line); +} + +bool SkTConic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { + return quad.hullIntersects(fConic, isLinear); +} + +bool SkTConic::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { + return cubic.hullIntersects(fConic, isLinear); +} + +void SkTConic::setBounds(SkDRect* rect) const { + rect->setBounds(fConic); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsConic.h b/gfx/skia/skia/src/pathops/SkPathOpsConic.h new file mode 100644 index 0000000000..27893ee46d --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsConic.h @@ -0,0 +1,193 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsConic_DEFINED +#define SkPathOpsConic_DEFINED + +#include "src/pathops/SkPathOpsQuad.h" + +struct SkDConic { + static const int kPointCount = 3; + static const int kPointLast = kPointCount - 1; + static const int kMaxIntersections = 4; + + SkDQuad fPts; + SkScalar fWeight; + + bool collapsed() const { + return fPts.collapsed(); + } + + bool controlsInside() const { + return fPts.controlsInside(); + } + + void debugInit() { + fPts.debugInit(); + fWeight = 0; + } + + void debugSet(const SkDPoint* pts, SkScalar weight); + + SkDConic flip() const { + SkDConic result = {{{fPts[2], fPts[1], fPts[0]} + SkDEBUGPARAMS(fPts.fDebugGlobalState) }, fWeight}; + return result; + } + +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const { return fPts.globalState(); } +#endif + + static bool IsConic() { return true; } + + const SkDConic& set(const SkPoint pts[kPointCount], SkScalar weight + SkDEBUGPARAMS(SkOpGlobalState* state = nullptr)) { + fPts.set(pts SkDEBUGPARAMS(state)); + fWeight = weight; + return *this; + } + + const SkDPoint& operator[](int n) const { return fPts[n]; } + SkDPoint& operator[](int n) { return fPts[n]; } + + static int AddValidTs(double s[], int realRoots, double* t) { + return SkDQuad::AddValidTs(s, realRoots, t); + } + + void align(int endIndex, SkDPoint* dstPt) const { + fPts.align(endIndex, dstPt); + } + + SkDVector dxdyAtT(double t) const; + static int FindExtrema(const double src[], SkScalar weight, double tValue[1]); + + bool hullIntersects(const SkDQuad& quad, bool* isLinear) const { + return fPts.hullIntersects(quad, isLinear); + } + + bool hullIntersects(const SkDConic& conic, bool* isLinear) const { + return fPts.hullIntersects(conic.fPts, isLinear); + } + + bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const; + + bool isLinear(int startIndex, int endIndex) const { + return fPts.isLinear(startIndex, endIndex); + } + + static int maxIntersections() { return kMaxIntersections; } + + bool monotonicInX() const { + return fPts.monotonicInX(); + } + + bool monotonicInY() const { + return fPts.monotonicInY(); + } + + void otherPts(int oddMan, const SkDPoint* endPt[2]) const { + fPts.otherPts(oddMan, endPt); + } + + static int pointCount() { return kPointCount; } + static int pointLast() { return kPointLast; } + SkDPoint ptAtT(double t) const; + + static int RootsReal(double A, double B, double C, double t[2]) { + return SkDQuad::RootsReal(A, B, C, t); + } + + static int RootsValidT(const double A, const double B, const double C, double s[2]) { + return SkDQuad::RootsValidT(A, B, C, s); + } + + SkDConic subDivide(double t1, double t2) const; + void subDivide(double t1, double t2, SkDConic* c) const { *c = this->subDivide(t1, t2); } + + static SkDConic SubDivide(const SkPoint a[kPointCount], SkScalar weight, double t1, double t2) { + SkDConic conic; + conic.set(a, weight); + return conic.subDivide(t1, t2); + } + + SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2, + SkScalar* weight) const; + + static SkDPoint SubDivide(const SkPoint pts[kPointCount], SkScalar weight, + const SkDPoint& a, const SkDPoint& c, + double t1, double t2, SkScalar* newWeight) { + SkDConic conic; + conic.set(pts, weight); + return conic.subDivide(a, c, t1, t2, newWeight); + } + + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + void dumpID(int id) const; + void dumpInner() const; + +}; + +class SkTConic : public SkTCurve { +public: + SkDConic fConic; + + SkTConic() {} + + SkTConic(const SkDConic& c) + : fConic(c) { + } + + ~SkTConic() override {} + + const SkDPoint& operator[](int n) const override { return fConic[n]; } + SkDPoint& operator[](int n) override { return fConic[n]; } + + bool collapsed() const override { return fConic.collapsed(); } + bool controlsInside() const override { return fConic.controlsInside(); } + void debugInit() override { return fConic.debugInit(); } +#if DEBUG_T_SECT + void dumpID(int id) const override { return fConic.dumpID(id); } +#endif + SkDVector dxdyAtT(double t) const override { return fConic.dxdyAtT(t); } +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const override { return fConic.globalState(); } +#endif + bool hullIntersects(const SkDQuad& quad, bool* isLinear) const override; + + bool hullIntersects(const SkDConic& conic, bool* isLinear) const override { + return conic.hullIntersects(fConic, isLinear); + } + + bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const override; + + bool hullIntersects(const SkTCurve& curve, bool* isLinear) const override { + return curve.hullIntersects(fConic, isLinear); + } + + int intersectRay(SkIntersections* i, const SkDLine& line) const override; + bool IsConic() const override { return true; } + SkTCurve* make(SkArenaAlloc& heap) const override { return heap.make(); } + + int maxIntersections() const override { return SkDConic::kMaxIntersections; } + + void otherPts(int oddMan, const SkDPoint* endPt[2]) const override { + fConic.otherPts(oddMan, endPt); + } + + int pointCount() const override { return SkDConic::kPointCount; } + int pointLast() const override { return SkDConic::kPointLast; } + SkDPoint ptAtT(double t) const override { return fConic.ptAtT(t); } + void setBounds(SkDRect* ) const override; + + void subDivide(double t1, double t2, SkTCurve* curve) const override { + ((SkTConic*) curve)->fConic = fConic.subDivide(t1, t2); + } +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp b/gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp new file mode 100644 index 0000000000..bb5261f9e1 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp @@ -0,0 +1,750 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkGeometry.h" +#include "src/core/SkTSort.h" +#include "src/pathops/SkLineParameters.h" +#include "src/pathops/SkPathOpsConic.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsCurve.h" +#include "src/pathops/SkPathOpsLine.h" +#include "src/pathops/SkPathOpsQuad.h" +#include "src/pathops/SkPathOpsRect.h" + +const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework + +void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const { + if (fPts[endIndex].fX == fPts[ctrlIndex].fX) { + dstPt->fX = fPts[endIndex].fX; + } + if (fPts[endIndex].fY == fPts[ctrlIndex].fY) { + dstPt->fY = fPts[endIndex].fY; + } +} + +// give up when changing t no longer moves point +// also, copy point rather than recompute it when it does change +double SkDCubic::binarySearch(double min, double max, double axisIntercept, + SearchAxis xAxis) const { + double t = (min + max) / 2; + double step = (t - min) / 2; + SkDPoint cubicAtT = ptAtT(t); + double calcPos = (&cubicAtT.fX)[xAxis]; + double calcDist = calcPos - axisIntercept; + do { + double priorT = std::max(min, t - step); + SkDPoint lessPt = ptAtT(priorT); + if (approximately_equal_half(lessPt.fX, cubicAtT.fX) + && approximately_equal_half(lessPt.fY, cubicAtT.fY)) { + return -1; // binary search found no point at this axis intercept + } + double lessDist = (&lessPt.fX)[xAxis] - axisIntercept; +#if DEBUG_CUBIC_BINARY_SEARCH + SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist, + step, lessDist); +#endif + double lastStep = step; + step /= 2; + if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) { + t = priorT; + } else { + double nextT = t + lastStep; + if (nextT > max) { + return -1; + } + SkDPoint morePt = ptAtT(nextT); + if (approximately_equal_half(morePt.fX, cubicAtT.fX) + && approximately_equal_half(morePt.fY, cubicAtT.fY)) { + return -1; // binary search found no point at this axis intercept + } + double moreDist = (&morePt.fX)[xAxis] - axisIntercept; + if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) { + continue; + } + t = nextT; + } + SkDPoint testAtT = ptAtT(t); + cubicAtT = testAtT; + calcPos = (&cubicAtT.fX)[xAxis]; + calcDist = calcPos - axisIntercept; + } while (!approximately_equal(calcPos, axisIntercept)); + return t; +} + +// get the rough scale of the cubic; used to determine if curvature is extreme +double SkDCubic::calcPrecision() const { + return ((fPts[1] - fPts[0]).length() + + (fPts[2] - fPts[1]).length() + + (fPts[3] - fPts[2]).length()) / gPrecisionUnit; +} + +/* classic one t subdivision */ +static void interp_cubic_coords(const double* src, double* dst, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + double abcd = SkDInterp(abc, bcd, t); + + dst[0] = src[0]; + dst[2] = ab; + dst[4] = abc; + dst[6] = abcd; + dst[8] = bcd; + dst[10] = cd; + dst[12] = src[6]; +} + +SkDCubicPair SkDCubic::chopAt(double t) const { + SkDCubicPair dst; + if (t == 0.5) { + dst.pts[0] = fPts[0]; + dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2; + dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2; + dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4; + dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4; + dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8; + dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8; + dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4; + dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4; + dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2; + dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2; + dst.pts[6] = fPts[3]; + return dst; + } + interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t); + interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t); + return dst; +} + +void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) { + *A = src[6]; // d + *B = src[4] * 3; // 3*c + *C = src[2] * 3; // 3*b + *D = src[0]; // a + *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d + *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c + *C -= 3 * *D; // C = -3*a + 3*b +} + +bool SkDCubic::endsAreExtremaInXOrY() const { + return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX) + && between(fPts[0].fX, fPts[2].fX, fPts[3].fX)) + || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY) + && between(fPts[0].fY, fPts[2].fY, fPts[3].fY)); +} + +// Do a quick reject by rotating all points relative to a line formed by +// a pair of one cubic's points. If the 2nd cubic's points +// are on the line or on the opposite side from the 1st cubic's 'odd man', the +// curves at most intersect at the endpoints. +/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear + if returning false, check contains true if the the cubic pair have only the end point in common +*/ +bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const { + bool linear = true; + char hullOrder[4]; + int hullCount = convexHull(hullOrder); + int end1 = hullOrder[0]; + int hullIndex = 0; + const SkDPoint* endPt[2]; + endPt[0] = &fPts[end1]; + do { + hullIndex = (hullIndex + 1) % hullCount; + int end2 = hullOrder[hullIndex]; + endPt[1] = &fPts[end2]; + double origX = endPt[0]->fX; + double origY = endPt[0]->fY; + double adj = endPt[1]->fX - origX; + double opp = endPt[1]->fY - origY; + int oddManMask = other_two(end1, end2); + int oddMan = end1 ^ oddManMask; + double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; + int oddMan2 = end2 ^ oddManMask; + double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp; + if (sign * sign2 < 0) { + continue; + } + if (approximately_zero(sign)) { + sign = sign2; + if (approximately_zero(sign)) { + continue; + } + } + linear = false; + bool foundOutlier = false; + for (int n = 0; n < ptCount; ++n) { + double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp; + if (test * sign > 0 && !precisely_zero(test)) { + foundOutlier = true; + break; + } + } + if (!foundOutlier) { + return false; + } + endPt[0] = endPt[1]; + end1 = end2; + } while (hullIndex); + *isLinear = linear; + return true; +} + +bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const { + return hullIntersects(c2.fPts, c2.kPointCount, isLinear); +} + +bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { + return hullIntersects(quad.fPts, quad.kPointCount, isLinear); +} + +bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const { + + return hullIntersects(conic.fPts, isLinear); +} + +bool SkDCubic::isLinear(int startIndex, int endIndex) const { + if (fPts[0].approximatelyDEqual(fPts[3])) { + return ((const SkDQuad *) this)->isLinear(0, 2); + } + SkLineParameters lineParameters; + lineParameters.cubicEndPoints(*this, startIndex, endIndex); + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); + double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); + largest = SkTMax(largest, -tiniest); + double distance = lineParameters.controlPtDistance(*this, 1); + if (!approximately_zero_when_compared_to(distance, largest)) { + return false; + } + distance = lineParameters.controlPtDistance(*this, 2); + return approximately_zero_when_compared_to(distance, largest); +} + +// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf +// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3 +// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2 +// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2 +static double derivative_at_t(const double* src, double t) { + double one_t = 1 - t; + double a = src[0]; + double b = src[2]; + double c = src[4]; + double d = src[6]; + return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); +} + +int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { + SkDCubic cubic; + cubic.set(pointsPtr); + if (cubic.monotonicInX() && cubic.monotonicInY()) { + return 0; + } + double tt[2], ss[2]; + SkCubicType cubicType = SkClassifyCubic(pointsPtr, tt, ss); + switch (cubicType) { + case SkCubicType::kLoop: { + const double &td = tt[0], &te = tt[1], &sd = ss[0], &se = ss[1]; + if (roughly_between(0, td, sd) && roughly_between(0, te, se)) { + t[0] = static_cast((td * se + te * sd) / (2 * sd * se)); + return (int) (t[0] > 0 && t[0] < 1); + } + } + // fall through if no t value found + case SkCubicType::kSerpentine: + case SkCubicType::kLocalCusp: + case SkCubicType::kCuspAtInfinity: { + double inflectionTs[2]; + int infTCount = cubic.findInflections(inflectionTs); + double maxCurvature[3]; + int roots = cubic.findMaxCurvature(maxCurvature); + #if DEBUG_CUBIC_SPLIT + SkDebugf("%s\n", __FUNCTION__); + cubic.dump(); + for (int index = 0; index < infTCount; ++index) { + SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]); + SkDPoint pt = cubic.ptAtT(inflectionTs[index]); + SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]); + SkDLine perp = {{pt - dPt, pt + dPt}}; + perp.dump(); + } + for (int index = 0; index < roots; ++index) { + SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]); + SkDPoint pt = cubic.ptAtT(maxCurvature[index]); + SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]); + SkDLine perp = {{pt - dPt, pt + dPt}}; + perp.dump(); + } + #endif + if (infTCount == 2) { + for (int index = 0; index < roots; ++index) { + if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { + t[0] = maxCurvature[index]; + return (int) (t[0] > 0 && t[0] < 1); + } + } + } else { + int resultCount = 0; + // FIXME: constant found through experimentation -- maybe there's a better way.... + double precision = cubic.calcPrecision() * 2; + for (int index = 0; index < roots; ++index) { + double testT = maxCurvature[index]; + if (0 >= testT || testT >= 1) { + continue; + } + // don't call dxdyAtT since we want (0,0) results + SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT), + derivative_at_t(&cubic.fPts[0].fY, testT) }; + double dPtLen = dPt.length(); + if (dPtLen < precision) { + t[resultCount++] = testT; + } + } + if (!resultCount && infTCount == 1) { + t[0] = inflectionTs[0]; + resultCount = (int) (t[0] > 0 && t[0] < 1); + } + return resultCount; + } + } + default: + ; + } + return 0; +} + +bool SkDCubic::monotonicInX() const { + return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX) + && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX); +} + +bool SkDCubic::monotonicInY() const { + return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY) + && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY); +} + +void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const { + int offset = (int) !SkToBool(index); + o1Pts[0] = &fPts[offset]; + o1Pts[1] = &fPts[++offset]; + o1Pts[2] = &fPts[++offset]; +} + +int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept, + SearchAxis xAxis, double* validRoots) const { + extrema += findInflections(&extremeTs[extrema]); + extremeTs[extrema++] = 0; + extremeTs[extrema] = 1; + SkASSERT(extrema < 6); + SkTQSort(extremeTs, extremeTs + extrema); + int validCount = 0; + for (int index = 0; index < extrema; ) { + double min = extremeTs[index]; + double max = extremeTs[++index]; + if (min == max) { + continue; + } + double newT = binarySearch(min, max, axisIntercept, xAxis); + if (newT >= 0) { + if (validCount >= 3) { + return 0; + } + validRoots[validCount++] = newT; + } + } + return validCount; +} + +// cubic roots + +static const double PI = 3.141592653589793; + +// from SkGeometry.cpp (and Numeric Solutions, 5.6) +int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) { + double s[3]; + int realRoots = RootsReal(A, B, C, D, s); + int foundRoots = SkDQuad::AddValidTs(s, realRoots, t); + for (int index = 0; index < realRoots; ++index) { + double tValue = s[index]; + if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) { + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], 1)) { + goto nextRoot; + } + } + SkASSERT(foundRoots < 3); + t[foundRoots++] = 1; + } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) { + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], 0)) { + goto nextRoot; + } + } + SkASSERT(foundRoots < 3); + t[foundRoots++] = 0; + } +nextRoot: + ; + } + return foundRoots; +} + +int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) { +#ifdef SK_DEBUG + // create a string mathematica understands + // GDB set print repe 15 # if repeated digits is a bother + // set print elements 400 # if line doesn't fit + char str[1024]; + sk_bzero(str, sizeof(str)); + SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]", + A, B, C, D); + SkPathOpsDebug::MathematicaIze(str, sizeof(str)); +#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA + SkDebugf("%s\n", str); +#endif +#endif + if (approximately_zero(A) + && approximately_zero_when_compared_to(A, B) + && approximately_zero_when_compared_to(A, C) + && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic + return SkDQuad::RootsReal(B, C, D, s); + } + if (approximately_zero_when_compared_to(D, A) + && approximately_zero_when_compared_to(D, B) + && approximately_zero_when_compared_to(D, C)) { // 0 is one root + int num = SkDQuad::RootsReal(A, B, C, s); + for (int i = 0; i < num; ++i) { + if (approximately_zero(s[i])) { + return num; + } + } + s[num++] = 0; + return num; + } + if (approximately_zero(A + B + C + D)) { // 1 is one root + int num = SkDQuad::RootsReal(A, A + B, -D, s); + for (int i = 0; i < num; ++i) { + if (AlmostDequalUlps(s[i], 1)) { + return num; + } + } + s[num++] = 1; + return num; + } + double a, b, c; + { + double invA = 1 / A; + a = B * invA; + b = C * invA; + c = D * invA; + } + double a2 = a * a; + double Q = (a2 - b * 3) / 9; + double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; + double R2 = R * R; + double Q3 = Q * Q * Q; + double R2MinusQ3 = R2 - Q3; + double adiv3 = a / 3; + double r; + double* roots = s; + if (R2MinusQ3 < 0) { // we have 3 real roots + // the divide/root can, due to finite precisions, be slightly outside of -1...1 + double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.)); + double neg2RootQ = -2 * sqrt(Q); + + r = neg2RootQ * cos(theta / 3) - adiv3; + *roots++ = r; + + r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3; + if (!AlmostDequalUlps(s[0], r)) { + *roots++ = r; + } + r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3; + if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) { + *roots++ = r; + } + } else { // we have 1 real root + double sqrtR2MinusQ3 = sqrt(R2MinusQ3); + double A = fabs(R) + sqrtR2MinusQ3; + A = SkDCubeRoot(A); + if (R > 0) { + A = -A; + } + if (A != 0) { + A += Q / A; + } + r = A - adiv3; + *roots++ = r; + if (AlmostDequalUlps((double) R2, (double) Q3)) { + r = -A / 2 - adiv3; + if (!AlmostDequalUlps(s[0], r)) { + *roots++ = r; + } + } + } + return static_cast(roots - s); +} + +// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t? +SkDVector SkDCubic::dxdyAtT(double t) const { + SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) }; + if (result.fX == 0 && result.fY == 0) { + if (t == 0) { + result = fPts[2] - fPts[0]; + } else if (t == 1) { + result = fPts[3] - fPts[1]; + } else { + // incomplete + SkDebugf("!c"); + } + if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) { + result = fPts[3] - fPts[0]; + } + } + return result; +} + +// OPTIMIZE? share code with formulate_F1DotF2 +int SkDCubic::findInflections(double tValues[]) const { + double Ax = fPts[1].fX - fPts[0].fX; + double Ay = fPts[1].fY - fPts[0].fY; + double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX; + double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY; + double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX; + double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY; + return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues); +} + +static void formulate_F1DotF2(const double src[], double coeff[4]) { + double a = src[2] - src[0]; + double b = src[4] - 2 * src[2] + src[0]; + double c = src[6] + 3 * (src[2] - src[4]) - src[0]; + coeff[0] = c * c; + coeff[1] = 3 * b * c; + coeff[2] = 2 * b * b + c * a; + coeff[3] = a * b; +} + +/** SkDCubic'(t) = At^2 + Bt + C, where + A = 3(-a + 3(b - c) + d) + B = 6(a - 2b + c) + C = 3(b - a) + Solve for t, keeping only those that fit between 0 < t < 1 +*/ +int SkDCubic::FindExtrema(const double src[], double tValues[2]) { + // we divide A,B,C by 3 to simplify + double a = src[0]; + double b = src[2]; + double c = src[4]; + double d = src[6]; + double A = d - a + 3 * (b - c); + double B = 2 * (a - b - b + c); + double C = b - a; + + return SkDQuad::RootsValidT(A, B, C, tValues); +} + +/* from SkGeometry.cpp + Looking for F' dot F'' == 0 + + A = b - a + B = c - 2b + a + C = d - 3c + 3b - a + + F' = 3Ct^2 + 6Bt + 3A + F'' = 6Ct + 6B + + F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB +*/ +int SkDCubic::findMaxCurvature(double tValues[]) const { + double coeffX[4], coeffY[4]; + int i; + formulate_F1DotF2(&fPts[0].fX, coeffX); + formulate_F1DotF2(&fPts[0].fY, coeffY); + for (i = 0; i < 4; i++) { + coeffX[i] = coeffX[i] + coeffY[i]; + } + return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues); +} + +SkDPoint SkDCubic::ptAtT(double t) const { + if (0 == t) { + return fPts[0]; + } + if (1 == t) { + return fPts[3]; + } + double one_t = 1 - t; + double one_t2 = one_t * one_t; + double a = one_t2 * one_t; + double b = 3 * one_t2 * t; + double t2 = t * t; + double c = 3 * one_t * t2; + double d = t2 * t; + SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY}; + return result; +} + +/* + Given a cubic c, t1, and t2, find a small cubic segment. + + The new cubic is defined as points A, B, C, and D, where + s1 = 1 - t1 + s2 = 1 - t2 + A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1 + D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2 + + We don't have B or C. So We define two equations to isolate them. + First, compute two reference T values 1/3 and 2/3 from t1 to t2: + + c(at (2*t1 + t2)/3) == E + c(at (t1 + 2*t2)/3) == F + + Next, compute where those values must be if we know the values of B and C: + + _12 = A*2/3 + B*1/3 + 12_ = A*1/3 + B*2/3 + _23 = B*2/3 + C*1/3 + 23_ = B*1/3 + C*2/3 + _34 = C*2/3 + D*1/3 + 34_ = C*1/3 + D*2/3 + _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9 + 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9 + _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9 + 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9 + _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3 + = A*8/27 + B*12/27 + C*6/27 + D*1/27 + = E + 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3 + = A*1/27 + B*6/27 + C*12/27 + D*8/27 + = F + E*27 = A*8 + B*12 + C*6 + D + F*27 = A + B*6 + C*12 + D*8 + +Group the known values on one side: + + M = E*27 - A*8 - D = B*12 + C* 6 + N = F*27 - A - D*8 = B* 6 + C*12 + M*2 - N = B*18 + N*2 - M = C*18 + B = (M*2 - N)/18 + C = (N*2 - M)/18 + */ + +static double interp_cubic_coords(const double* src, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + double abcd = SkDInterp(abc, bcd, t); + return abcd; +} + +SkDCubic SkDCubic::subDivide(double t1, double t2) const { + if (t1 == 0 || t2 == 1) { + if (t1 == 0 && t2 == 1) { + return *this; + } + SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1); + SkDCubic dst = t1 == 0 ? pair.first() : pair.second(); + return dst; + } + SkDCubic dst; + double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1); + double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1); + double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3); + double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3); + double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3); + double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3); + double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2); + double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2); + double mx = ex * 27 - ax * 8 - dx; + double my = ey * 27 - ay * 8 - dy; + double nx = fx * 27 - ax - dx * 8; + double ny = fy * 27 - ay - dy * 8; + /* bx = */ dst[1].fX = (mx * 2 - nx) / 18; + /* by = */ dst[1].fY = (my * 2 - ny) / 18; + /* cx = */ dst[2].fX = (nx * 2 - mx) / 18; + /* cy = */ dst[2].fY = (ny * 2 - my) / 18; + // FIXME: call align() ? + return dst; +} + +void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d, + double t1, double t2, SkDPoint dst[2]) const { + SkASSERT(t1 != t2); + // this approach assumes that the control points computed directly are accurate enough + SkDCubic sub = subDivide(t1, t2); + dst[0] = sub[1] + (a - sub[0]); + dst[1] = sub[2] + (d - sub[3]); + if (t1 == 0 || t2 == 0) { + align(0, 1, t1 == 0 ? &dst[0] : &dst[1]); + } + if (t1 == 1 || t2 == 1) { + align(3, 2, t1 == 1 ? &dst[0] : &dst[1]); + } + if (AlmostBequalUlps(dst[0].fX, a.fX)) { + dst[0].fX = a.fX; + } + if (AlmostBequalUlps(dst[0].fY, a.fY)) { + dst[0].fY = a.fY; + } + if (AlmostBequalUlps(dst[1].fX, d.fX)) { + dst[1].fX = d.fX; + } + if (AlmostBequalUlps(dst[1].fY, d.fY)) { + dst[1].fY = d.fY; + } +} + +bool SkDCubic::toFloatPoints(SkPoint* pts) const { + const double* dCubic = &fPts[0].fX; + SkScalar* cubic = &pts[0].fX; + for (int index = 0; index < kPointCount * 2; ++index) { + cubic[index] = SkDoubleToScalar(dCubic[index]); + if (SkScalarAbs(cubic[index]) < FLT_EPSILON_ORDERABLE_ERR) { + cubic[index] = 0; + } + } + return SkScalarsAreFinite(&pts->fX, kPointCount * 2); +} + +double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const { + double extremeTs[2]; + double topT = -1; + int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs); + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * extremeTs[index]; + SkDPoint mid = dCurve.ptAtT(t); + if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) { + topT = t; + *topPt = mid; + } + } + return topT; +} + +int SkTCubic::intersectRay(SkIntersections* i, const SkDLine& line) const { + return i->intersectRay(fCubic, line); +} + +bool SkTCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { + return quad.hullIntersects(fCubic, isLinear); +} + +bool SkTCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const { + return conic.hullIntersects(fCubic, isLinear); +} + +void SkTCubic::setBounds(SkDRect* rect) const { + rect->setBounds(fCubic); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCubic.h b/gfx/skia/skia/src/pathops/SkPathOpsCubic.h new file mode 100644 index 0000000000..cc923e458a --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCubic.h @@ -0,0 +1,240 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsCubic_DEFINED +#define SkPathOpsCubic_DEFINED + +#include "include/core/SkPath.h" +#include "src/core/SkArenaAlloc.h" +#include "src/pathops/SkPathOpsTCurve.h" + +struct SkDCubicPair; + +struct SkDCubic { + static const int kPointCount = 4; + static const int kPointLast = kPointCount - 1; + static const int kMaxIntersections = 9; + + enum SearchAxis { + kXAxis, + kYAxis + }; + + bool collapsed() const { + return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]) + && fPts[0].approximatelyEqual(fPts[3]); + } + + bool controlsInside() const { + SkDVector v01 = fPts[0] - fPts[1]; + SkDVector v02 = fPts[0] - fPts[2]; + SkDVector v03 = fPts[0] - fPts[3]; + SkDVector v13 = fPts[1] - fPts[3]; + SkDVector v23 = fPts[2] - fPts[3]; + return v03.dot(v01) > 0 && v03.dot(v02) > 0 && v03.dot(v13) > 0 && v03.dot(v23) > 0; + } + + static bool IsConic() { return false; } + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + + void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const; + double binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const; + double calcPrecision() const; + SkDCubicPair chopAt(double t) const; + static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D); + static int ComplexBreak(const SkPoint pts[4], SkScalar* t); + int convexHull(char order[kPointCount]) const; + + void debugInit() { + sk_bzero(fPts, sizeof(fPts)); + } + + void debugSet(const SkDPoint* pts); + + void dump() const; // callable from the debugger when the implementation code is linked in + void dumpID(int id) const; + void dumpInner() const; + SkDVector dxdyAtT(double t) const; + bool endsAreExtremaInXOrY() const; + static int FindExtrema(const double src[], double tValue[2]); + int findInflections(double tValues[2]) const; + + static int FindInflections(const SkPoint a[kPointCount], double tValues[2]) { + SkDCubic cubic; + return cubic.set(a).findInflections(tValues); + } + + int findMaxCurvature(double tValues[]) const; + +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const { return fDebugGlobalState; } +#endif + + bool hullIntersects(const SkDCubic& c2, bool* isLinear) const; + bool hullIntersects(const SkDConic& c, bool* isLinear) const; + bool hullIntersects(const SkDQuad& c2, bool* isLinear) const; + bool hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const; + bool isLinear(int startIndex, int endIndex) const; + static int maxIntersections() { return kMaxIntersections; } + bool monotonicInX() const; + bool monotonicInY() const; + void otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const; + static int pointCount() { return kPointCount; } + static int pointLast() { return kPointLast; } + SkDPoint ptAtT(double t) const; + static int RootsReal(double A, double B, double C, double D, double t[3]); + static int RootsValidT(const double A, const double B, const double C, double D, double s[3]); + + int searchRoots(double extremes[6], int extrema, double axisIntercept, + SearchAxis xAxis, double* validRoots) const; + + bool toFloatPoints(SkPoint* ) const; + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified horizontal line. + */ + int horizontalIntersect(double yIntercept, double roots[3]) const; + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified vertical line. + */ + int verticalIntersect(double xIntercept, double roots[3]) const; + +// add debug only global pointer so asserts can be skipped by fuzzers + const SkDCubic& set(const SkPoint pts[kPointCount] + SkDEBUGPARAMS(SkOpGlobalState* state = nullptr)) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + fPts[2] = pts[2]; + fPts[3] = pts[3]; + SkDEBUGCODE(fDebugGlobalState = state); + return *this; + } + + SkDCubic subDivide(double t1, double t2) const; + void subDivide(double t1, double t2, SkDCubic* c) const { *c = this->subDivide(t1, t2); } + + static SkDCubic SubDivide(const SkPoint a[kPointCount], double t1, double t2) { + SkDCubic cubic; + return cubic.set(a).subDivide(t1, t2); + } + + void subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) const; + + static void SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& d, double t1, + double t2, SkDPoint p[2]) { + SkDCubic cubic; + cubic.set(pts).subDivide(a, d, t1, t2, p); + } + + double top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const; + SkDQuad toQuad() const; + + static const int gPrecisionUnit; + SkDPoint fPts[kPointCount]; + SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); +}; + +/* Given the set [0, 1, 2, 3], and two of the four members, compute an XOR mask + that computes the other two. Note that: + + one ^ two == 3 for (0, 3), (1, 2) + one ^ two < 3 for (0, 1), (0, 2), (1, 3), (2, 3) + 3 - (one ^ two) is either 0, 1, or 2 + 1 >> (3 - (one ^ two)) is either 0 or 1 +thus: + returned == 2 for (0, 3), (1, 2) + returned == 3 for (0, 1), (0, 2), (1, 3), (2, 3) +given that: + (0, 3) ^ 2 -> (2, 1) (1, 2) ^ 2 -> (3, 0) + (0, 1) ^ 3 -> (3, 2) (0, 2) ^ 3 -> (3, 1) (1, 3) ^ 3 -> (2, 0) (2, 3) ^ 3 -> (1, 0) +*/ +inline int other_two(int one, int two) { + return 1 >> (3 - (one ^ two)) ^ 3; +} + +struct SkDCubicPair { + const SkDCubic first() const { +#ifdef SK_DEBUG + SkDCubic result; + result.debugSet(&pts[0]); + return result; +#else + return (const SkDCubic&) pts[0]; +#endif + } + const SkDCubic second() const { +#ifdef SK_DEBUG + SkDCubic result; + result.debugSet(&pts[3]); + return result; +#else + return (const SkDCubic&) pts[3]; +#endif + } + SkDPoint pts[7]; +}; + +class SkTCubic : public SkTCurve { +public: + SkDCubic fCubic; + + SkTCubic() {} + + SkTCubic(const SkDCubic& c) + : fCubic(c) { + } + + ~SkTCubic() override {} + + const SkDPoint& operator[](int n) const override { return fCubic[n]; } + SkDPoint& operator[](int n) override { return fCubic[n]; } + + bool collapsed() const override { return fCubic.collapsed(); } + bool controlsInside() const override { return fCubic.controlsInside(); } + void debugInit() override { return fCubic.debugInit(); } +#if DEBUG_T_SECT + void dumpID(int id) const override { return fCubic.dumpID(id); } +#endif + SkDVector dxdyAtT(double t) const override { return fCubic.dxdyAtT(t); } +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const override { return fCubic.globalState(); } +#endif + bool hullIntersects(const SkDQuad& quad, bool* isLinear) const override; + bool hullIntersects(const SkDConic& conic, bool* isLinear) const override; + + bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const override { + return cubic.hullIntersects(fCubic, isLinear); + } + + bool hullIntersects(const SkTCurve& curve, bool* isLinear) const override { + return curve.hullIntersects(fCubic, isLinear); + } + + int intersectRay(SkIntersections* i, const SkDLine& line) const override; + bool IsConic() const override { return false; } + SkTCurve* make(SkArenaAlloc& heap) const override { return heap.make(); } + + int maxIntersections() const override { return SkDCubic::kMaxIntersections; } + + void otherPts(int oddMan, const SkDPoint* endPt[2]) const override { + fCubic.otherPts(oddMan, endPt); + } + + int pointCount() const override { return SkDCubic::kPointCount; } + int pointLast() const override { return SkDCubic::kPointLast; } + SkDPoint ptAtT(double t) const override { return fCubic.ptAtT(t); } + void setBounds(SkDRect* ) const override; + + void subDivide(double t1, double t2, SkTCurve* curve) const override { + ((SkTCubic*) curve)->fCubic = fCubic.subDivide(t1, t2); + } +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp b/gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp new file mode 100644 index 0000000000..734d599958 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkPathOpsBounds.h" +#include "src/pathops/SkPathOpsCurve.h" +#include "src/pathops/SkPathOpsRect.h" + + // this cheats and assumes that the perpendicular to the point is the closest ray to the curve + // this case (where the line and the curve are nearly coincident) may be the only case that counts +double SkDCurve::nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const { + int count = SkPathOpsVerbToPoints(verb); + double minX = fCubic.fPts[0].fX; + double maxX = minX; + for (int index = 1; index <= count; ++index) { + minX = SkTMin(minX, fCubic.fPts[index].fX); + maxX = SkTMax(maxX, fCubic.fPts[index].fX); + } + if (!AlmostBetweenUlps(minX, xy.fX, maxX)) { + return -1; + } + double minY = fCubic.fPts[0].fY; + double maxY = minY; + for (int index = 1; index <= count; ++index) { + minY = SkTMin(minY, fCubic.fPts[index].fY); + maxY = SkTMax(maxY, fCubic.fPts[index].fY); + } + if (!AlmostBetweenUlps(minY, xy.fY, maxY)) { + return -1; + } + SkIntersections i; + SkDLine perp = {{ xy, { xy.fX + opp.fY - xy.fY, xy.fY + xy.fX - opp.fX }}}; + (*CurveDIntersectRay[verb])(*this, perp, &i); + int minIndex = -1; + double minDist = FLT_MAX; + for (int index = 0; index < i.used(); ++index) { + double dist = xy.distance(i.pt(index)); + if (minDist > dist) { + minDist = dist; + minIndex = index; + } + } + if (minIndex < 0) { + return -1; + } + double largest = SkTMax(SkTMax(maxX, maxY), -SkTMin(minX, minY)); + if (!AlmostEqualUlps_Pin(largest, largest + minDist)) { // is distance within ULPS tolerance? + return -1; + } + return SkPinT(i[0][minIndex]); +} + +void SkDCurve::offset(SkPath::Verb verb, const SkDVector& off) { + int count = SkPathOpsVerbToPoints(verb); + for (int index = 0; index <= count; ++index) { + fCubic.fPts[index] += off; + } +} + +void SkDCurve::setConicBounds(const SkPoint curve[3], SkScalar curveWeight, + double tStart, double tEnd, SkPathOpsBounds* bounds) { + SkDConic dCurve; + dCurve.set(curve, curveWeight); + SkDRect dRect; + dRect.setBounds(dCurve, fConic, tStart, tEnd); + bounds->setLTRB(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkDCurve::setCubicBounds(const SkPoint curve[4], SkScalar , + double tStart, double tEnd, SkPathOpsBounds* bounds) { + SkDCubic dCurve; + dCurve.set(curve); + SkDRect dRect; + dRect.setBounds(dCurve, fCubic, tStart, tEnd); + bounds->setLTRB(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkDCurve::setQuadBounds(const SkPoint curve[3], SkScalar , + double tStart, double tEnd, SkPathOpsBounds* bounds) { + SkDQuad dCurve; + dCurve.set(curve); + SkDRect dRect; + dRect.setBounds(dCurve, fQuad, tStart, tEnd); + bounds->setLTRB(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkDCurveSweep::setCurveHullSweep(SkPath::Verb verb) { + fOrdered = true; + fSweep[0] = fCurve[1] - fCurve[0]; + if (SkPath::kLine_Verb == verb) { + fSweep[1] = fSweep[0]; + fIsCurve = false; + return; + } + fSweep[1] = fCurve[2] - fCurve[0]; + // OPTIMIZE: I do the following float check a lot -- probably need a + // central place for this val-is-small-compared-to-curve check + double maxVal = 0; + for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) { + maxVal = SkTMax(maxVal, SkTMax(SkTAbs(fCurve[index].fX), + SkTAbs(fCurve[index].fY))); + } + { + if (SkPath::kCubic_Verb != verb) { + if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) + && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { + fSweep[0] = fSweep[1]; + } + goto setIsCurve; + } + SkDVector thirdSweep = fCurve[3] - fCurve[0]; + if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { + fSweep[0] = fSweep[1]; + fSweep[1] = thirdSweep; + if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) + && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { + fSweep[0] = fSweep[1]; + fCurve[1] = fCurve[3]; + } + goto setIsCurve; + } + double s1x3 = fSweep[0].crossCheck(thirdSweep); + double s3x2 = thirdSweep.crossCheck(fSweep[1]); + if (s1x3 * s3x2 >= 0) { // if third vector is on or between first two vectors + goto setIsCurve; + } + double s2x1 = fSweep[1].crossCheck(fSweep[0]); + // FIXME: If the sweep of the cubic is greater than 180 degrees, we're in trouble + // probably such wide sweeps should be artificially subdivided earlier so that never happens + SkASSERT(s1x3 * s2x1 < 0 || s1x3 * s3x2 < 0); + if (s3x2 * s2x1 < 0) { + SkASSERT(s2x1 * s1x3 > 0); + fSweep[0] = fSweep[1]; + fOrdered = false; + } + fSweep[1] = thirdSweep; + } +setIsCurve: + fIsCurve = fSweep[0].crossCheck(fSweep[1]) != 0; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCurve.h b/gfx/skia/skia/src/pathops/SkPathOpsCurve.h new file mode 100644 index 0000000000..50511c876b --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCurve.h @@ -0,0 +1,421 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsCurve_DEFINE +#define SkPathOpsCurve_DEFINE + +#include "src/pathops/SkIntersections.h" + +#ifndef SK_RELEASE +#include "include/core/SkPath.h" +#endif + +struct SkPathOpsBounds; + +struct SkOpCurve { + SkPoint fPts[4]; + SkScalar fWeight; + SkDEBUGCODE(SkPath::Verb fVerb); + + const SkPoint& operator[](int n) const { + SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); + return fPts[n]; + } + + void dump() const; + + void set(const SkDQuad& quad) { + for (int index = 0; index < SkDQuad::kPointCount; ++index) { + fPts[index] = quad[index].asSkPoint(); + } + SkDEBUGCODE(fWeight = 1); + SkDEBUGCODE(fVerb = SkPath::kQuad_Verb); + } + + void set(const SkDCubic& cubic) { + for (int index = 0; index < SkDCubic::kPointCount; ++index) { + fPts[index] = cubic[index].asSkPoint(); + } + SkDEBUGCODE(fWeight = 1); + SkDEBUGCODE(fVerb = SkPath::kCubic_Verb); + } + +}; + +struct SkDCurve { + union { + SkDLine fLine; + SkDQuad fQuad; + SkDConic fConic; + SkDCubic fCubic; + }; + SkDEBUGCODE(SkPath::Verb fVerb); + + const SkDPoint& operator[](int n) const { + SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); + return fCubic[n]; + } + + SkDPoint& operator[](int n) { + SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); + return fCubic[n]; + } + + SkDPoint conicTop(const SkPoint curve[3], SkScalar curveWeight, + double s, double e, double* topT); + SkDPoint cubicTop(const SkPoint curve[4], SkScalar , double s, double e, double* topT); + void dump() const; + void dumpID(int ) const; + SkDPoint lineTop(const SkPoint[2], SkScalar , double , double , double* topT); + double nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const; + void offset(SkPath::Verb verb, const SkDVector& ); + SkDPoint quadTop(const SkPoint curve[3], SkScalar , double s, double e, double* topT); + + void setConicBounds(const SkPoint curve[3], SkScalar curveWeight, + double s, double e, SkPathOpsBounds* ); + void setCubicBounds(const SkPoint curve[4], SkScalar , + double s, double e, SkPathOpsBounds* ); + void setQuadBounds(const SkPoint curve[3], SkScalar , + double s, double e, SkPathOpsBounds*); +}; + +class SkDCurveSweep { +public: + bool isCurve() const { return fIsCurve; } + bool isOrdered() const { return fOrdered; } + void setCurveHullSweep(SkPath::Verb verb); + + SkDCurve fCurve; + SkDVector fSweep[2]; +private: + bool fIsCurve; + bool fOrdered; // cleared when a cubic's control point isn't between the sweep vectors + +}; + +extern SkDPoint (SkDCurve::* const Top[])(const SkPoint curve[], SkScalar cWeight, + double tStart, double tEnd, double* topT); + +static SkDPoint dline_xy_at_t(const SkPoint a[2], SkScalar , double t) { + SkDLine line; + line.set(a); + return line.ptAtT(t); +} + +static SkDPoint dquad_xy_at_t(const SkPoint a[3], SkScalar , double t) { + SkDQuad quad; + quad.set(a); + return quad.ptAtT(t); +} + +static SkDPoint dconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { + SkDConic conic; + conic.set(a, weight); + return conic.ptAtT(t); +} + +static SkDPoint dcubic_xy_at_t(const SkPoint a[4], SkScalar , double t) { + SkDCubic cubic; + cubic.set(a); + return cubic.ptAtT(t); +} + +static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + dline_xy_at_t, + dquad_xy_at_t, + dconic_xy_at_t, + dcubic_xy_at_t +}; + +static SkDPoint ddline_xy_at_t(const SkDCurve& c, double t) { + return c.fLine.ptAtT(t); +} + +static SkDPoint ddquad_xy_at_t(const SkDCurve& c, double t) { + return c.fQuad.ptAtT(t); +} + +static SkDPoint ddconic_xy_at_t(const SkDCurve& c, double t) { + return c.fConic.ptAtT(t); +} + +static SkDPoint ddcubic_xy_at_t(const SkDCurve& c, double t) { + return c.fCubic.ptAtT(t); +} + +static SkDPoint (* const CurveDDPointAtT[])(const SkDCurve& , double ) = { + nullptr, + ddline_xy_at_t, + ddquad_xy_at_t, + ddconic_xy_at_t, + ddcubic_xy_at_t +}; + +static SkPoint fline_xy_at_t(const SkPoint a[2], SkScalar weight, double t) { + return dline_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint fquad_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dquad_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint fconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dconic_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint fcubic_xy_at_t(const SkPoint a[4], SkScalar weight, double t) { + return dcubic_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint (* const CurvePointAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + fline_xy_at_t, + fquad_xy_at_t, + fconic_xy_at_t, + fcubic_xy_at_t +}; + +static SkDVector dline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { + SkDLine line; + line.set(a); + return line[1] - line[0]; +} + +static SkDVector dquad_dxdy_at_t(const SkPoint a[3], SkScalar , double t) { + SkDQuad quad; + quad.set(a); + return quad.dxdyAtT(t); +} + +static SkDVector dconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { + SkDConic conic; + conic.set(a, weight); + return conic.dxdyAtT(t); +} + +static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], SkScalar , double t) { + SkDCubic cubic; + cubic.set(a); + return cubic.dxdyAtT(t); +} + +static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + dline_dxdy_at_t, + dquad_dxdy_at_t, + dconic_dxdy_at_t, + dcubic_dxdy_at_t +}; + +static SkDVector ddline_dxdy_at_t(const SkDCurve& c, double ) { + return c.fLine.fPts[1] - c.fLine.fPts[0]; +} + +static SkDVector ddquad_dxdy_at_t(const SkDCurve& c, double t) { + return c.fQuad.dxdyAtT(t); +} + +static SkDVector ddconic_dxdy_at_t(const SkDCurve& c, double t) { + return c.fConic.dxdyAtT(t); +} + +static SkDVector ddcubic_dxdy_at_t(const SkDCurve& c, double t) { + return c.fCubic.dxdyAtT(t); +} + +static SkDVector (* const CurveDDSlopeAtT[])(const SkDCurve& , double ) = { + nullptr, + ddline_dxdy_at_t, + ddquad_dxdy_at_t, + ddconic_dxdy_at_t, + ddcubic_dxdy_at_t +}; + +static SkVector fline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { + return a[1] - a[0]; +} + +static SkVector fquad_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dquad_dxdy_at_t(a, weight, t).asSkVector(); +} + +static SkVector fconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dconic_dxdy_at_t(a, weight, t).asSkVector(); +} + +static SkVector fcubic_dxdy_at_t(const SkPoint a[4], SkScalar weight, double t) { + return dcubic_dxdy_at_t(a, weight, t).asSkVector(); +} + +static SkVector (* const CurveSlopeAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + fline_dxdy_at_t, + fquad_dxdy_at_t, + fconic_dxdy_at_t, + fcubic_dxdy_at_t +}; + +static bool line_is_vertical(const SkPoint a[2], SkScalar , double startT, double endT) { + SkDLine line; + line.set(a); + SkDPoint dst[2] = { line.ptAtT(startT), line.ptAtT(endT) }; + return AlmostEqualUlps(dst[0].fX, dst[1].fX); +} + +static bool quad_is_vertical(const SkPoint a[3], SkScalar , double startT, double endT) { + SkDQuad quad; + quad.set(a); + SkDQuad dst = quad.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); +} + +static bool conic_is_vertical(const SkPoint a[3], SkScalar weight, double startT, double endT) { + SkDConic conic; + conic.set(a, weight); + SkDConic dst = conic.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); +} + +static bool cubic_is_vertical(const SkPoint a[4], SkScalar , double startT, double endT) { + SkDCubic cubic; + cubic.set(a); + SkDCubic dst = cubic.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX) + && AlmostEqualUlps(dst[2].fX, dst[3].fX); +} + +static bool (* const CurveIsVertical[])(const SkPoint[], SkScalar , double , double) = { + nullptr, + line_is_vertical, + quad_is_vertical, + conic_is_vertical, + cubic_is_vertical +}; + +static void line_intersect_ray(const SkPoint a[2], SkScalar , const SkDLine& ray, + SkIntersections* i) { + SkDLine line; + line.set(a); + i->intersectRay(line, ray); +} + +static void quad_intersect_ray(const SkPoint a[3], SkScalar , const SkDLine& ray, + SkIntersections* i) { + SkDQuad quad; + quad.set(a); + i->intersectRay(quad, ray); +} + +static void conic_intersect_ray(const SkPoint a[3], SkScalar weight, const SkDLine& ray, + SkIntersections* i) { + SkDConic conic; + conic.set(a, weight); + i->intersectRay(conic, ray); +} + +static void cubic_intersect_ray(const SkPoint a[4], SkScalar , const SkDLine& ray, + SkIntersections* i) { + SkDCubic cubic; + cubic.set(a); + i->intersectRay(cubic, ray); +} + +static void (* const CurveIntersectRay[])(const SkPoint[] , SkScalar , const SkDLine& , + SkIntersections* ) = { + nullptr, + line_intersect_ray, + quad_intersect_ray, + conic_intersect_ray, + cubic_intersect_ray +}; + +static void dline_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fLine, ray); +} + +static void dquad_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fQuad, ray); +} + +static void dconic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fConic, ray); +} + +static void dcubic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fCubic, ray); +} + +static void (* const CurveDIntersectRay[])(const SkDCurve& , const SkDLine& , SkIntersections* ) = { + nullptr, + dline_intersect_ray, + dquad_intersect_ray, + dconic_intersect_ray, + dcubic_intersect_ray +}; + +static int line_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { + if (a[0].fY == a[1].fY) { + return false; + } + SkDLine line; + roots[0] = SkIntersections::HorizontalIntercept(line.set(a), y); + return between(0, roots[0], 1); +} + +static int line_intercept_v(const SkPoint a[2], SkScalar , SkScalar x, double* roots) { + if (a[0].fX == a[1].fX) { + return false; + } + SkDLine line; + roots[0] = SkIntersections::VerticalIntercept(line.set(a), x); + return between(0, roots[0], 1); +} + +static int quad_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { + SkDQuad quad; + return SkIntersections::HorizontalIntercept(quad.set(a), y, roots); +} + +static int quad_intercept_v(const SkPoint a[2], SkScalar , SkScalar x, double* roots) { + SkDQuad quad; + return SkIntersections::VerticalIntercept(quad.set(a), x, roots); +} + +static int conic_intercept_h(const SkPoint a[2], SkScalar w, SkScalar y, double* roots) { + SkDConic conic; + return SkIntersections::HorizontalIntercept(conic.set(a, w), y, roots); +} + +static int conic_intercept_v(const SkPoint a[2], SkScalar w, SkScalar x, double* roots) { + SkDConic conic; + return SkIntersections::VerticalIntercept(conic.set(a, w), x, roots); +} + +static int cubic_intercept_h(const SkPoint a[3], SkScalar , SkScalar y, double* roots) { + SkDCubic cubic; + return cubic.set(a).horizontalIntersect(y, roots); +} + +static int cubic_intercept_v(const SkPoint a[3], SkScalar , SkScalar x, double* roots) { + SkDCubic cubic; + return cubic.set(a).verticalIntersect(x, roots); +} + +static int (* const CurveIntercept[])(const SkPoint[] , SkScalar , SkScalar , double* ) = { + nullptr, + nullptr, + line_intercept_h, + line_intercept_v, + quad_intercept_h, + quad_intercept_v, + conic_intercept_h, + conic_intercept_v, + cubic_intercept_h, + cubic_intercept_v, +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp b/gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp new file mode 100644 index 0000000000..7ee44deb9d --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp @@ -0,0 +1,3147 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPath.h" +#include "include/core/SkString.h" +#include "include/private/SkMutex.h" +#include "src/core/SkOSFile.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkPathOpsDebug.h" + +#include + +#if DEBUG_DUMP_VERIFY +bool SkPathOpsDebug::gDumpOp; // set to true to write op to file before a crash +bool SkPathOpsDebug::gVerifyOp; // set to true to compare result against regions +#endif + +bool SkPathOpsDebug::gRunFail; // set to true to check for success on tests known to fail +bool SkPathOpsDebug::gVeryVerbose; // set to true to run extensive checking tests + +#undef FAIL_IF +#define FAIL_IF(cond, coin) \ + do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, coin); } while (false) + +#undef FAIL_WITH_NULL_IF +#define FAIL_WITH_NULL_IF(cond, span) \ + do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, span); } while (false) + +#undef RETURN_FALSE_IF +#define RETURN_FALSE_IF(cond, span) \ + do { if (cond) log->record(SkPathOpsDebug::kReturnFalse_Glitch, span); \ + } while (false) + +class SkCoincidentSpans; + +#if DEBUG_SORT +int SkPathOpsDebug::gSortCountDefault = SK_MaxS32; +int SkPathOpsDebug::gSortCount; +#endif + +#if DEBUG_ACTIVE_OP +const char* SkPathOpsDebug::kPathOpStr[] = {"diff", "sect", "union", "xor", "rdiff"}; +#endif + +#if defined SK_DEBUG || !FORCE_RELEASE + +int SkPathOpsDebug::gContourID = 0; +int SkPathOpsDebug::gSegmentID = 0; + +bool SkPathOpsDebug::ChaseContains(const SkTDArray& chaseArray, + const SkOpSpanBase* span) { + for (int index = 0; index < chaseArray.count(); ++index) { + const SkOpSpanBase* entry = chaseArray[index]; + if (entry == span) { + return true; + } + } + return false; +} +#endif + +#if DEBUG_ACTIVE_SPANS +SkString SkPathOpsDebug::gActiveSpans; +#endif + +#if DEBUG_COIN + +SkPathOpsDebug::CoinDict SkPathOpsDebug::gCoinSumChangedDict; +SkPathOpsDebug::CoinDict SkPathOpsDebug::gCoinSumVisitedDict; + +static const int kGlitchType_Count = SkPathOpsDebug::kUnalignedTail_Glitch + 1; + +struct SpanGlitch { + const SkOpSpanBase* fBase; + const SkOpSpanBase* fSuspect; + const SkOpSegment* fSegment; + const SkOpSegment* fOppSegment; + const SkOpPtT* fCoinSpan; + const SkOpPtT* fEndSpan; + const SkOpPtT* fOppSpan; + const SkOpPtT* fOppEndSpan; + double fStartT; + double fEndT; + double fOppStartT; + double fOppEndT; + SkPoint fPt; + SkPathOpsDebug::GlitchType fType; + + void dumpType() const; +}; + +struct SkPathOpsDebug::GlitchLog { + void init(const SkOpGlobalState* state) { + fGlobalState = state; + } + + SpanGlitch* recordCommon(GlitchType type) { + SpanGlitch* glitch = fGlitches.push(); + glitch->fBase = nullptr; + glitch->fSuspect = nullptr; + glitch->fSegment = nullptr; + glitch->fOppSegment = nullptr; + glitch->fCoinSpan = nullptr; + glitch->fEndSpan = nullptr; + glitch->fOppSpan = nullptr; + glitch->fOppEndSpan = nullptr; + glitch->fStartT = SK_ScalarNaN; + glitch->fEndT = SK_ScalarNaN; + glitch->fOppStartT = SK_ScalarNaN; + glitch->fOppEndT = SK_ScalarNaN; + glitch->fPt = { SK_ScalarNaN, SK_ScalarNaN }; + glitch->fType = type; + return glitch; + } + + void record(GlitchType type, const SkOpSpanBase* base, + const SkOpSpanBase* suspect = NULL) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fSuspect = suspect; + } + + void record(GlitchType type, const SkOpSpanBase* base, + const SkOpPtT* ptT) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fCoinSpan = ptT; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkCoincidentSpans* opp = NULL) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + if (opp) { + glitch->fOppSpan = opp->coinPtTStart(); + glitch->fOppEndSpan = opp->coinPtTEnd(); + } + } + + void record(GlitchType type, const SkOpSpanBase* base, + const SkOpSegment* seg, double t, SkPoint pt) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fSegment = seg; + glitch->fStartT = t; + glitch->fPt = pt; + } + + void record(GlitchType type, const SkOpSpanBase* base, double t, + SkPoint pt) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fStartT = t; + glitch->fPt = pt; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkOpPtT* coinSpan, const SkOpPtT* endSpan) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + glitch->fEndSpan = endSpan; + glitch->fOppSpan = coinSpan; + glitch->fOppEndSpan = endSpan; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkOpSpanBase* base) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + } + + void record(GlitchType type, const SkOpPtT* ptTS, const SkOpPtT* ptTE, + const SkOpPtT* oPtTS, const SkOpPtT* oPtTE) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = ptTS; + glitch->fEndSpan = ptTE; + glitch->fOppSpan = oPtTS; + glitch->fOppEndSpan = oPtTE; + } + + void record(GlitchType type, const SkOpSegment* seg, double startT, + double endT, const SkOpSegment* oppSeg, double oppStartT, double oppEndT) { + SpanGlitch* glitch = recordCommon(type); + glitch->fSegment = seg; + glitch->fStartT = startT; + glitch->fEndT = endT; + glitch->fOppSegment = oppSeg; + glitch->fOppStartT = oppStartT; + glitch->fOppEndT = oppEndT; + } + + void record(GlitchType type, const SkOpSegment* seg, + const SkOpSpan* span) { + SpanGlitch* glitch = recordCommon(type); + glitch->fSegment = seg; + glitch->fBase = span; + } + + void record(GlitchType type, double t, const SkOpSpanBase* span) { + SpanGlitch* glitch = recordCommon(type); + glitch->fStartT = t; + glitch->fBase = span; + } + + void record(GlitchType type, const SkOpSegment* seg) { + SpanGlitch* glitch = recordCommon(type); + glitch->fSegment = seg; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkOpPtT* ptT) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = ptT; + } + + SkTDArray fGlitches; + const SkOpGlobalState* fGlobalState; +}; + + +void SkPathOpsDebug::CoinDict::add(const SkPathOpsDebug::CoinDict& dict) { + int count = dict.fDict.count(); + for (int index = 0; index < count; ++index) { + this->add(dict.fDict[index]); + } +} + +void SkPathOpsDebug::CoinDict::add(const CoinDictEntry& key) { + int count = fDict.count(); + for (int index = 0; index < count; ++index) { + CoinDictEntry* entry = &fDict[index]; + if (entry->fIteration == key.fIteration && entry->fLineNumber == key.fLineNumber) { + SkASSERT(!strcmp(entry->fFunctionName, key.fFunctionName)); + if (entry->fGlitchType == kUninitialized_Glitch) { + entry->fGlitchType = key.fGlitchType; + } + return; + } + } + *fDict.append() = key; +} + +#endif + +#if DEBUG_COIN +static void missing_coincidence(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { + const SkOpContour* contour = contourList; + // bool result = false; + do { + /* result |= */ contour->debugMissingCoincidence(glitches); + } while ((contour = contour->next())); + return; +} + +static void move_multiples(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { + const SkOpContour* contour = contourList; + do { + if (contour->debugMoveMultiples(glitches), false) { + return; + } + } while ((contour = contour->next())); + return; +} + +static void move_nearby(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { + const SkOpContour* contour = contourList; + do { + contour->debugMoveNearby(glitches); + } while ((contour = contour->next())); +} + + +#endif + +#if DEBUG_COIN +void SkOpGlobalState::debugAddToCoinChangedDict() { + +#if DEBUG_COINCIDENCE + SkPathOpsDebug::CheckHealth(fContourHead); +#endif + // see if next coincident operation makes a change; if so, record it + SkPathOpsDebug::GlitchLog glitches; + const char* funcName = fCoinDictEntry.fFunctionName; + if (!strcmp("calc_angles", funcName)) { + ; + } else if (!strcmp("missing_coincidence", funcName)) { + missing_coincidence(&glitches, fContourHead); + } else if (!strcmp("move_multiples", funcName)) { + move_multiples(&glitches, fContourHead); + } else if (!strcmp("move_nearby", funcName)) { + move_nearby(&glitches, fContourHead); + } else if (!strcmp("addExpanded", funcName)) { + fCoincidence->debugAddExpanded(&glitches); + } else if (!strcmp("addMissing", funcName)) { + bool added; + fCoincidence->debugAddMissing(&glitches, &added); + } else if (!strcmp("addEndMovedSpans", funcName)) { + fCoincidence->debugAddEndMovedSpans(&glitches); + } else if (!strcmp("correctEnds", funcName)) { + fCoincidence->debugCorrectEnds(&glitches); + } else if (!strcmp("expand", funcName)) { + fCoincidence->debugExpand(&glitches); + } else if (!strcmp("findOverlaps", funcName)) { + ; + } else if (!strcmp("mark", funcName)) { + fCoincidence->debugMark(&glitches); + } else if (!strcmp("apply", funcName)) { + ; + } else { + SkASSERT(0); // add missing case + } + if (glitches.fGlitches.count()) { + fCoinDictEntry.fGlitchType = glitches.fGlitches[0].fType; + } + fCoinChangedDict.add(fCoinDictEntry); +} +#endif + +void SkPathOpsDebug::ShowActiveSpans(SkOpContourHead* contourList) { +#if DEBUG_ACTIVE_SPANS + SkString str; + SkOpContour* contour = contourList; + do { + contour->debugShowActiveSpans(&str); + } while ((contour = contour->next())); + if (!gActiveSpans.equals(str)) { + const char* s = str.c_str(); + const char* end; + while ((end = strchr(s, '\n'))) { + SkDebugf("%.*s", end - s + 1, s); + s = end + 1; + } + gActiveSpans.set(str); + } +#endif +} + +#if DEBUG_COINCIDENCE || DEBUG_COIN +void SkPathOpsDebug::CheckHealth(SkOpContourHead* contourList) { +#if DEBUG_COINCIDENCE + contourList->globalState()->debugSetCheckHealth(true); +#endif +#if DEBUG_COIN + GlitchLog glitches; + const SkOpContour* contour = contourList; + const SkOpCoincidence* coincidence = contour->globalState()->coincidence(); + coincidence->debugCheckValid(&glitches); // don't call validate; spans may be inconsistent + do { + contour->debugCheckHealth(&glitches); + contour->debugMissingCoincidence(&glitches); + } while ((contour = contour->next())); + bool added; + coincidence->debugAddMissing(&glitches, &added); + coincidence->debugExpand(&glitches); + coincidence->debugAddExpanded(&glitches); + coincidence->debugMark(&glitches); + unsigned mask = 0; + for (int index = 0; index < glitches.fGlitches.count(); ++index) { + const SpanGlitch& glitch = glitches.fGlitches[index]; + mask |= 1 << glitch.fType; + } + for (int index = 0; index < kGlitchType_Count; ++index) { + SkDebugf(mask & (1 << index) ? "x" : "-"); + } + SkDebugf(" %s\n", contourList->globalState()->debugCoinDictEntry().fFunctionName); + for (int index = 0; index < glitches.fGlitches.count(); ++index) { + const SpanGlitch& glitch = glitches.fGlitches[index]; + SkDebugf("%02d: ", index); + if (glitch.fBase) { + SkDebugf(" seg/base=%d/%d", glitch.fBase->segment()->debugID(), + glitch.fBase->debugID()); + } + if (glitch.fSuspect) { + SkDebugf(" seg/base=%d/%d", glitch.fSuspect->segment()->debugID(), + glitch.fSuspect->debugID()); + } + if (glitch.fSegment) { + SkDebugf(" segment=%d", glitch.fSegment->debugID()); + } + if (glitch.fCoinSpan) { + SkDebugf(" coinSeg/Span/PtT=%d/%d/%d", glitch.fCoinSpan->segment()->debugID(), + glitch.fCoinSpan->span()->debugID(), glitch.fCoinSpan->debugID()); + } + if (glitch.fEndSpan) { + SkDebugf(" endSpan=%d", glitch.fEndSpan->debugID()); + } + if (glitch.fOppSpan) { + SkDebugf(" oppSeg/Span/PtT=%d/%d/%d", glitch.fOppSpan->segment()->debugID(), + glitch.fOppSpan->span()->debugID(), glitch.fOppSpan->debugID()); + } + if (glitch.fOppEndSpan) { + SkDebugf(" oppEndSpan=%d", glitch.fOppEndSpan->debugID()); + } + if (!SkScalarIsNaN(glitch.fStartT)) { + SkDebugf(" startT=%g", glitch.fStartT); + } + if (!SkScalarIsNaN(glitch.fEndT)) { + SkDebugf(" endT=%g", glitch.fEndT); + } + if (glitch.fOppSegment) { + SkDebugf(" segment=%d", glitch.fOppSegment->debugID()); + } + if (!SkScalarIsNaN(glitch.fOppStartT)) { + SkDebugf(" oppStartT=%g", glitch.fOppStartT); + } + if (!SkScalarIsNaN(glitch.fOppEndT)) { + SkDebugf(" oppEndT=%g", glitch.fOppEndT); + } + if (!SkScalarIsNaN(glitch.fPt.fX) || !SkScalarIsNaN(glitch.fPt.fY)) { + SkDebugf(" pt=%g,%g", glitch.fPt.fX, glitch.fPt.fY); + } + DumpGlitchType(glitch.fType); + SkDebugf("\n"); + } +#if DEBUG_COINCIDENCE + contourList->globalState()->debugSetCheckHealth(false); +#endif +#if 01 && DEBUG_ACTIVE_SPANS +// SkDebugf("active after %s:\n", id); + ShowActiveSpans(contourList); +#endif +#endif +} +#endif + +#if DEBUG_COIN +void SkPathOpsDebug::DumpGlitchType(GlitchType glitchType) { + switch (glitchType) { + case kAddCorruptCoin_Glitch: SkDebugf(" AddCorruptCoin"); break; + case kAddExpandedCoin_Glitch: SkDebugf(" AddExpandedCoin"); break; + case kAddExpandedFail_Glitch: SkDebugf(" AddExpandedFail"); break; + case kAddIfCollapsed_Glitch: SkDebugf(" AddIfCollapsed"); break; + case kAddIfMissingCoin_Glitch: SkDebugf(" AddIfMissingCoin"); break; + case kAddMissingCoin_Glitch: SkDebugf(" AddMissingCoin"); break; + case kAddMissingExtend_Glitch: SkDebugf(" AddMissingExtend"); break; + case kAddOrOverlap_Glitch: SkDebugf(" AAddOrOverlap"); break; + case kCollapsedCoin_Glitch: SkDebugf(" CollapsedCoin"); break; + case kCollapsedDone_Glitch: SkDebugf(" CollapsedDone"); break; + case kCollapsedOppValue_Glitch: SkDebugf(" CollapsedOppValue"); break; + case kCollapsedSpan_Glitch: SkDebugf(" CollapsedSpan"); break; + case kCollapsedWindValue_Glitch: SkDebugf(" CollapsedWindValue"); break; + case kCorrectEnd_Glitch: SkDebugf(" CorrectEnd"); break; + case kDeletedCoin_Glitch: SkDebugf(" DeletedCoin"); break; + case kExpandCoin_Glitch: SkDebugf(" ExpandCoin"); break; + case kFail_Glitch: SkDebugf(" Fail"); break; + case kMarkCoinEnd_Glitch: SkDebugf(" MarkCoinEnd"); break; + case kMarkCoinInsert_Glitch: SkDebugf(" MarkCoinInsert"); break; + case kMarkCoinMissing_Glitch: SkDebugf(" MarkCoinMissing"); break; + case kMarkCoinStart_Glitch: SkDebugf(" MarkCoinStart"); break; + case kMergeMatches_Glitch: SkDebugf(" MergeMatches"); break; + case kMissingCoin_Glitch: SkDebugf(" MissingCoin"); break; + case kMissingDone_Glitch: SkDebugf(" MissingDone"); break; + case kMissingIntersection_Glitch: SkDebugf(" MissingIntersection"); break; + case kMoveMultiple_Glitch: SkDebugf(" MoveMultiple"); break; + case kMoveNearbyClearAll_Glitch: SkDebugf(" MoveNearbyClearAll"); break; + case kMoveNearbyClearAll2_Glitch: SkDebugf(" MoveNearbyClearAll2"); break; + case kMoveNearbyMerge_Glitch: SkDebugf(" MoveNearbyMerge"); break; + case kMoveNearbyMergeFinal_Glitch: SkDebugf(" MoveNearbyMergeFinal"); break; + case kMoveNearbyRelease_Glitch: SkDebugf(" MoveNearbyRelease"); break; + case kMoveNearbyReleaseFinal_Glitch: SkDebugf(" MoveNearbyReleaseFinal"); break; + case kReleasedSpan_Glitch: SkDebugf(" ReleasedSpan"); break; + case kReturnFalse_Glitch: SkDebugf(" ReturnFalse"); break; + case kUnaligned_Glitch: SkDebugf(" Unaligned"); break; + case kUnalignedHead_Glitch: SkDebugf(" UnalignedHead"); break; + case kUnalignedTail_Glitch: SkDebugf(" UnalignedTail"); break; + case kUninitialized_Glitch: break; + default: SkASSERT(0); + } +} +#endif + +#if defined SK_DEBUG || !FORCE_RELEASE +void SkPathOpsDebug::MathematicaIze(char* str, size_t bufferLen) { + size_t len = strlen(str); + bool num = false; + for (size_t idx = 0; idx < len; ++idx) { + if (num && str[idx] == 'e') { + if (len + 2 >= bufferLen) { + return; + } + memmove(&str[idx + 2], &str[idx + 1], len - idx); + str[idx] = '*'; + str[idx + 1] = '^'; + ++len; + } + num = str[idx] >= '0' && str[idx] <= '9'; + } +} + +bool SkPathOpsDebug::ValidWind(int wind) { + return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF; +} + +void SkPathOpsDebug::WindingPrintf(int wind) { + if (wind == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", wind); + } +} +#endif // defined SK_DEBUG || !FORCE_RELEASE + + +#if DEBUG_SHOW_TEST_NAME +void* SkPathOpsDebug::CreateNameStr() { return new char[DEBUG_FILENAME_STRING_LENGTH]; } + +void SkPathOpsDebug::DeleteNameStr(void* v) { delete[] reinterpret_cast(v); } + +void SkPathOpsDebug::BumpTestName(char* test) { + char* num = test + strlen(test); + while (num[-1] >= '0' && num[-1] <= '9') { + --num; + } + if (num[0] == '\0') { + return; + } + int dec = atoi(num); + if (dec == 0) { + return; + } + ++dec; + SK_SNPRINTF(num, DEBUG_FILENAME_STRING_LENGTH - (num - test), "%d", dec); +} +#endif + +static void show_function_header(const char* functionName) { + SkDebugf("\nstatic void %s(skiatest::Reporter* reporter, const char* filename) {\n", functionName); + if (strcmp("skphealth_com76", functionName) == 0) { + SkDebugf("found it\n"); + } +} + +static const char* gOpStrs[] = { + "kDifference_SkPathOp", + "kIntersect_SkPathOp", + "kUnion_SkPathOp", + "kXOR_PathOp", + "kReverseDifference_SkPathOp", +}; + +const char* SkPathOpsDebug::OpStr(SkPathOp op) { + return gOpStrs[op]; +} + +static void show_op(SkPathOp op, const char* pathOne, const char* pathTwo) { + SkDebugf(" testPathOp(reporter, %s, %s, %s, filename);\n", pathOne, pathTwo, gOpStrs[op]); + SkDebugf("}\n"); +} + +void SkPathOpsDebug::ShowPath(const SkPath& a, const SkPath& b, SkPathOp shapeOp, + const char* testName) { + static SkMutex& mutex = *(new SkMutex); + + SkAutoMutexExclusive ac(mutex); + show_function_header(testName); + ShowOnePath(a, "path", true); + ShowOnePath(b, "pathB", true); + show_op(shapeOp, "path", "pathB"); +} + +#include "src/pathops/SkIntersectionHelper.h" +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkPathOpsTypes.h" + +#if DEBUG_COIN + +void SkOpGlobalState::debugAddToGlobalCoinDicts() { + static SkMutex& mutex = *(new SkMutex); + SkAutoMutexExclusive ac(mutex); + SkPathOpsDebug::gCoinSumChangedDict.add(fCoinChangedDict); + SkPathOpsDebug::gCoinSumVisitedDict.add(fCoinVisitedDict); +} + +#endif + +#if DEBUG_T_SECT_LOOP_COUNT +void SkOpGlobalState::debugAddLoopCount(SkIntersections* i, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn) { + for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { + SkIntersections::DebugLoop looper = (SkIntersections::DebugLoop) index; + if (fDebugLoopCount[index] >= i->debugLoopCount(looper)) { + continue; + } + fDebugLoopCount[index] = i->debugLoopCount(looper); + fDebugWorstVerb[index * 2] = wt.segment()->verb(); + fDebugWorstVerb[index * 2 + 1] = wn.segment()->verb(); + sk_bzero(&fDebugWorstPts[index * 8], sizeof(SkPoint) * 8); + memcpy(&fDebugWorstPts[index * 2 * 4], wt.pts(), + (SkPathOpsVerbToPoints(wt.segment()->verb()) + 1) * sizeof(SkPoint)); + memcpy(&fDebugWorstPts[(index * 2 + 1) * 4], wn.pts(), + (SkPathOpsVerbToPoints(wn.segment()->verb()) + 1) * sizeof(SkPoint)); + fDebugWorstWeight[index * 2] = wt.weight(); + fDebugWorstWeight[index * 2 + 1] = wn.weight(); + } + i->debugResetLoopCount(); +} + +void SkOpGlobalState::debugDoYourWorst(SkOpGlobalState* local) { + for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { + if (fDebugLoopCount[index] >= local->fDebugLoopCount[index]) { + continue; + } + fDebugLoopCount[index] = local->fDebugLoopCount[index]; + fDebugWorstVerb[index * 2] = local->fDebugWorstVerb[index * 2]; + fDebugWorstVerb[index * 2 + 1] = local->fDebugWorstVerb[index * 2 + 1]; + memcpy(&fDebugWorstPts[index * 2 * 4], &local->fDebugWorstPts[index * 2 * 4], + sizeof(SkPoint) * 8); + fDebugWorstWeight[index * 2] = local->fDebugWorstWeight[index * 2]; + fDebugWorstWeight[index * 2 + 1] = local->fDebugWorstWeight[index * 2 + 1]; + } + local->debugResetLoopCounts(); +} + +static void dump_curve(SkPath::Verb verb, const SkPoint& pts, float weight) { + if (!verb) { + return; + } + const char* verbs[] = { "", "line", "quad", "conic", "cubic" }; + SkDebugf("%s: {{", verbs[verb]); + int ptCount = SkPathOpsVerbToPoints(verb); + for (int index = 0; index <= ptCount; ++index) { + SkDPoint::Dump((&pts)[index]); + if (index < ptCount - 1) { + SkDebugf(", "); + } + } + SkDebugf("}"); + if (weight != 1) { + SkDebugf(", "); + if (weight == floorf(weight)) { + SkDebugf("%.0f", weight); + } else { + SkDebugf("%1.9gf", weight); + } + } + SkDebugf("}\n"); +} + +void SkOpGlobalState::debugLoopReport() { + const char* loops[] = { "iterations", "coinChecks", "perpCalcs" }; + SkDebugf("\n"); + for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { + SkDebugf("%s: %d\n", loops[index], fDebugLoopCount[index]); + dump_curve(fDebugWorstVerb[index * 2], fDebugWorstPts[index * 2 * 4], + fDebugWorstWeight[index * 2]); + dump_curve(fDebugWorstVerb[index * 2 + 1], fDebugWorstPts[(index * 2 + 1) * 4], + fDebugWorstWeight[index * 2 + 1]); + } +} + +void SkOpGlobalState::debugResetLoopCounts() { + sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); + sk_bzero(fDebugWorstVerb, sizeof(fDebugWorstVerb)); + sk_bzero(fDebugWorstPts, sizeof(fDebugWorstPts)); + sk_bzero(fDebugWorstWeight, sizeof(fDebugWorstWeight)); +} +#endif + +bool SkOpGlobalState::DebugRunFail() { + return SkPathOpsDebug::gRunFail; +} + +// this is const so it can be called by const methods that overwise don't alter state +#if DEBUG_VALIDATE || DEBUG_COIN +void SkOpGlobalState::debugSetPhase(const char* funcName DEBUG_COIN_DECLARE_PARAMS()) const { + auto writable = const_cast(this); +#if DEBUG_VALIDATE + writable->setPhase(phase); +#endif +#if DEBUG_COIN + SkPathOpsDebug::CoinDictEntry* entry = &writable->fCoinDictEntry; + writable->fPreviousFuncName = entry->fFunctionName; + entry->fIteration = iteration; + entry->fLineNumber = lineNo; + entry->fGlitchType = SkPathOpsDebug::kUninitialized_Glitch; + entry->fFunctionName = funcName; + writable->fCoinVisitedDict.add(*entry); + writable->debugAddToCoinChangedDict(); +#endif +} +#endif + +#if DEBUG_T_SECT_LOOP_COUNT +void SkIntersections::debugBumpLoopCount(DebugLoop index) { + fDebugLoopCount[index]++; +} + +int SkIntersections::debugLoopCount(DebugLoop index) const { + return fDebugLoopCount[index]; +} + +void SkIntersections::debugResetLoopCount() { + sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); +} +#endif + +#include "src/pathops/SkPathOpsConic.h" +#include "src/pathops/SkPathOpsCubic.h" + +SkDCubic SkDQuad::debugToCubic() const { + SkDCubic cubic; + cubic[0] = fPts[0]; + cubic[2] = fPts[1]; + cubic[3] = fPts[2]; + cubic[1].fX = (cubic[0].fX + cubic[2].fX * 2) / 3; + cubic[1].fY = (cubic[0].fY + cubic[2].fY * 2) / 3; + cubic[2].fX = (cubic[3].fX + cubic[2].fX * 2) / 3; + cubic[2].fY = (cubic[3].fY + cubic[2].fY * 2) / 3; + return cubic; +} + +void SkDQuad::debugSet(const SkDPoint* pts) { + memcpy(fPts, pts, sizeof(fPts)); + SkDEBUGCODE(fDebugGlobalState = nullptr); +} + +void SkDCubic::debugSet(const SkDPoint* pts) { + memcpy(fPts, pts, sizeof(fPts)); + SkDEBUGCODE(fDebugGlobalState = nullptr); +} + +void SkDConic::debugSet(const SkDPoint* pts, SkScalar weight) { + fPts.debugSet(pts); + fWeight = weight; +} + +void SkDRect::debugInit() { + fLeft = fTop = fRight = fBottom = SK_ScalarNaN; +} + +#include "src/pathops/SkOpAngle.h" +#include "src/pathops/SkOpSegment.h" + +#if DEBUG_COIN +// commented-out lines keep this in sync with addT() + const SkOpPtT* SkOpSegment::debugAddT(double t, SkPathOpsDebug::GlitchLog* log) const { + debugValidate(); + SkPoint pt = this->ptAtT(t); + const SkOpSpanBase* span = &fHead; + do { + const SkOpPtT* result = span->ptT(); + if (t == result->fT || this->match(result, this, t, pt)) { +// span->bumpSpanAdds(); + return result; + } + if (t < result->fT) { + const SkOpSpan* prev = result->span()->prev(); + FAIL_WITH_NULL_IF(!prev, span); + // marks in global state that new op span has been allocated + this->globalState()->setAllocatedOpSpan(); +// span->init(this, prev, t, pt); + this->debugValidate(); +// #if DEBUG_ADD_T +// SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, +// span->segment()->debugID(), span->debugID()); +// #endif +// span->bumpSpanAdds(); + return nullptr; + } + FAIL_WITH_NULL_IF(span != &fTail, span); + } while ((span = span->upCast()->next())); + SkASSERT(0); + return nullptr; // we never get here, but need this to satisfy compiler +} +#endif + +#if DEBUG_ANGLE +void SkOpSegment::debugCheckAngleCoin() const { + const SkOpSpanBase* base = &fHead; + const SkOpSpan* span; + do { + const SkOpAngle* angle = base->fromAngle(); + if (angle && angle->debugCheckCoincidence()) { + angle->debugCheckNearCoincidence(); + } + if (base->final()) { + break; + } + span = base->upCast(); + angle = span->toAngle(); + if (angle && angle->debugCheckCoincidence()) { + angle->debugCheckNearCoincidence(); + } + } while ((base = span->next())); +} +#endif + +#if DEBUG_COIN +// this mimics the order of the checks in handle coincidence +void SkOpSegment::debugCheckHealth(SkPathOpsDebug::GlitchLog* glitches) const { + debugMoveMultiples(glitches); + debugMoveNearby(glitches); + debugMissingCoincidence(glitches); +} + +// commented-out lines keep this in sync with clearAll() +void SkOpSegment::debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const { + const SkOpSpan* span = &fHead; + do { + this->debugClearOne(span, glitches); + } while ((span = span->next()->upCastable())); + this->globalState()->coincidence()->debugRelease(glitches, this); +} + +// commented-out lines keep this in sync with clearOne() +void SkOpSegment::debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const { + if (span->windValue()) glitches->record(SkPathOpsDebug::kCollapsedWindValue_Glitch, span); + if (span->oppValue()) glitches->record(SkPathOpsDebug::kCollapsedOppValue_Glitch, span); + if (!span->done()) glitches->record(SkPathOpsDebug::kCollapsedDone_Glitch, span); +} +#endif + +SkOpAngle* SkOpSegment::debugLastAngle() { + SkOpAngle* result = nullptr; + SkOpSpan* span = this->head(); + do { + if (span->toAngle()) { + SkASSERT(!result); + result = span->toAngle(); + } + } while ((span = span->next()->upCastable())); + SkASSERT(result); + return result; +} + +#if DEBUG_COIN +// commented-out lines keep this in sync with ClearVisited +void SkOpSegment::DebugClearVisited(const SkOpSpanBase* span) { + // reset visited flag back to false + do { + const SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + const SkOpSegment* opp = ptT->segment(); + opp->resetDebugVisited(); + } + } while (!span->final() && (span = span->upCast()->next())); +} +#endif + +#if DEBUG_COIN +// commented-out lines keep this in sync with missingCoincidence() +// look for pairs of undetected coincident curves +// assumes that segments going in have visited flag clear +// Even though pairs of curves correct detect coincident runs, a run may be missed +// if the coincidence is a product of multiple intersections. For instance, given +// curves A, B, and C: +// A-B intersect at a point 1; A-C and B-C intersect at point 2, so near +// the end of C that the intersection is replaced with the end of C. +// Even though A-B correctly do not detect an intersection at point 2, +// the resulting run from point 1 to point 2 is coincident on A and B. +void SkOpSegment::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { + if (this->done()) { + return; + } + const SkOpSpan* prior = nullptr; + const SkOpSpanBase* spanBase = &fHead; +// bool result = false; + do { + const SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; + SkASSERT(ptT->span() == spanBase); + while ((ptT = ptT->next()) != spanStopPtT) { + if (ptT->deleted()) { + continue; + } + const SkOpSegment* opp = ptT->span()->segment(); + if (opp->done()) { + continue; + } + // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence + if (!opp->debugVisited()) { + continue; + } + if (spanBase == &fHead) { + continue; + } + if (ptT->segment() == this) { + continue; + } + const SkOpSpan* span = spanBase->upCastable(); + // FIXME?: this assumes that if the opposite segment is coincident then no more + // coincidence needs to be detected. This may not be true. + if (span && span->segment() != opp && span->containsCoincidence(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted + continue; + } + if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted + continue; + } + const SkOpPtT* priorPtT = nullptr, * priorStopPtT; + // find prior span containing opp segment + const SkOpSegment* priorOpp = nullptr; + const SkOpSpan* priorTest = spanBase->prev(); + while (!priorOpp && priorTest) { + priorStopPtT = priorPtT = priorTest->ptT(); + while ((priorPtT = priorPtT->next()) != priorStopPtT) { + if (priorPtT->deleted()) { + continue; + } + const SkOpSegment* segment = priorPtT->span()->segment(); + if (segment == opp) { + prior = priorTest; + priorOpp = opp; + break; + } + } + priorTest = priorTest->prev(); + } + if (!priorOpp) { + continue; + } + if (priorPtT == ptT) { + continue; + } + const SkOpPtT* oppStart = prior->ptT(); + const SkOpPtT* oppEnd = spanBase->ptT(); + bool swapped = priorPtT->fT > ptT->fT; + if (swapped) { + using std::swap; + swap(priorPtT, ptT); + swap(oppStart, oppEnd); + } + const SkOpCoincidence* coincidence = this->globalState()->coincidence(); + const SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); + const SkOpPtT* rootPtT = ptT->span()->ptT(); + const SkOpPtT* rootOppStart = oppStart->span()->ptT(); + const SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); + if (coincidence->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + goto swapBack; + } + if (testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { + // mark coincidence +#if DEBUG_COINCIDENCE_VERBOSE +// SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, +// rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), +// rootOppEnd->debugID()); +#endif + log->record(SkPathOpsDebug::kMissingCoin_Glitch, priorPtT, ptT, oppStart, oppEnd); + // coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); + // } +#if DEBUG_COINCIDENCE +// SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); +#endif + // result = true; + } + swapBack: + if (swapped) { + using std::swap; + swap(priorPtT, ptT); + } + } + } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); + DebugClearVisited(&fHead); + return; +} + +// commented-out lines keep this in sync with moveMultiples() +// if a span has more than one intersection, merge the other segments' span as needed +void SkOpSegment::debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const { + debugValidate(); + const SkOpSpanBase* test = &fHead; + do { + int addCount = test->spanAddsCount(); +// SkASSERT(addCount >= 1); + if (addCount <= 1) { + continue; + } + const SkOpPtT* startPtT = test->ptT(); + const SkOpPtT* testPtT = startPtT; + do { // iterate through all spans associated with start + const SkOpSpanBase* oppSpan = testPtT->span(); + if (oppSpan->spanAddsCount() == addCount) { + continue; + } + if (oppSpan->deleted()) { + continue; + } + const SkOpSegment* oppSegment = oppSpan->segment(); + if (oppSegment == this) { + continue; + } + // find range of spans to consider merging + const SkOpSpanBase* oppPrev = oppSpan; + const SkOpSpanBase* oppFirst = oppSpan; + while ((oppPrev = oppPrev->prev())) { + if (!roughly_equal(oppPrev->t(), oppSpan->t())) { + break; + } + if (oppPrev->spanAddsCount() == addCount) { + continue; + } + if (oppPrev->deleted()) { + continue; + } + oppFirst = oppPrev; + } + const SkOpSpanBase* oppNext = oppSpan; + const SkOpSpanBase* oppLast = oppSpan; + while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { + if (!roughly_equal(oppNext->t(), oppSpan->t())) { + break; + } + if (oppNext->spanAddsCount() == addCount) { + continue; + } + if (oppNext->deleted()) { + continue; + } + oppLast = oppNext; + } + if (oppFirst == oppLast) { + continue; + } + const SkOpSpanBase* oppTest = oppFirst; + do { + if (oppTest == oppSpan) { + continue; + } + // check to see if the candidate meets specific criteria: + // it contains spans of segments in test's loop but not including 'this' + const SkOpPtT* oppStartPtT = oppTest->ptT(); + const SkOpPtT* oppPtT = oppStartPtT; + while ((oppPtT = oppPtT->next()) != oppStartPtT) { + const SkOpSegment* oppPtTSegment = oppPtT->segment(); + if (oppPtTSegment == this) { + goto tryNextSpan; + } + const SkOpPtT* matchPtT = startPtT; + do { + if (matchPtT->segment() == oppPtTSegment) { + goto foundMatch; + } + } while ((matchPtT = matchPtT->next()) != startPtT); + goto tryNextSpan; + foundMatch: // merge oppTest and oppSpan + oppSegment->debugValidate(); + oppTest->debugMergeMatches(glitches, oppSpan); + oppTest->debugAddOpp(glitches, oppSpan); + oppSegment->debugValidate(); + goto checkNextSpan; + } + tryNextSpan: + ; + } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); + } while ((testPtT = testPtT->next()) != startPtT); +checkNextSpan: + ; + } while ((test = test->final() ? nullptr : test->upCast()->next())); + debugValidate(); + return; +} + +// commented-out lines keep this in sync with moveNearby() +// Move nearby t values and pts so they all hang off the same span. Alignment happens later. +void SkOpSegment::debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const { + debugValidate(); + // release undeleted spans pointing to this seg that are linked to the primary span + const SkOpSpanBase* spanBase = &fHead; + do { + const SkOpPtT* ptT = spanBase->ptT(); + const SkOpPtT* headPtT = ptT; + while ((ptT = ptT->next()) != headPtT) { + const SkOpSpanBase* test = ptT->span(); + if (ptT->segment() == this && !ptT->deleted() && test != spanBase + && test->ptT() == ptT) { + if (test->final()) { + if (spanBase == &fHead) { + glitches->record(SkPathOpsDebug::kMoveNearbyClearAll_Glitch, this); +// return; + } + glitches->record(SkPathOpsDebug::kMoveNearbyReleaseFinal_Glitch, spanBase, ptT); + } else if (test->prev()) { + glitches->record(SkPathOpsDebug::kMoveNearbyRelease_Glitch, test, headPtT); + } +// break; + } + } + spanBase = spanBase->upCast()->next(); + } while (!spanBase->final()); + + // This loop looks for adjacent spans which are near by + spanBase = &fHead; + do { // iterate through all spans associated with start + const SkOpSpanBase* test = spanBase->upCast()->next(); + bool found; + if (!this->spansNearby(spanBase, test, &found)) { + glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test); + } + if (found) { + if (test->final()) { + if (spanBase->prev()) { + glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test); + } else { + glitches->record(SkPathOpsDebug::kMoveNearbyClearAll2_Glitch, this); + // return + } + } else { + glitches->record(SkPathOpsDebug::kMoveNearbyMerge_Glitch, spanBase); + } + } + spanBase = test; + } while (!spanBase->final()); + debugValidate(); +} +#endif + +void SkOpSegment::debugReset() { + this->init(this->fPts, this->fWeight, this->contour(), this->verb()); +} + +#if DEBUG_COINCIDENCE_ORDER +void SkOpSegment::debugSetCoinT(int index, SkScalar t) const { + if (fDebugBaseMax < 0 || fDebugBaseIndex == index) { + fDebugBaseIndex = index; + fDebugBaseMin = SkTMin(t, fDebugBaseMin); + fDebugBaseMax = SkTMax(t, fDebugBaseMax); + return; + } + SkASSERT(fDebugBaseMin >= t || t >= fDebugBaseMax); + if (fDebugLastMax < 0 || fDebugLastIndex == index) { + fDebugLastIndex = index; + fDebugLastMin = SkTMin(t, fDebugLastMin); + fDebugLastMax = SkTMax(t, fDebugLastMax); + return; + } + SkASSERT(fDebugLastMin >= t || t >= fDebugLastMax); + SkASSERT((t - fDebugBaseMin > 0) == (fDebugLastMin - fDebugBaseMin > 0)); +} +#endif + +#if DEBUG_ACTIVE_SPANS +void SkOpSegment::debugShowActiveSpans(SkString* str) const { + debugValidate(); + if (done()) { + return; + } + int lastId = -1; + double lastT = -1; + const SkOpSpan* span = &fHead; + do { + if (span->done()) { + continue; + } + if (lastId == this->debugID() && lastT == span->t()) { + continue; + } + lastId = this->debugID(); + lastT = span->t(); + str->appendf("%s id=%d", __FUNCTION__, this->debugID()); + // since endpoints may have be adjusted, show actual computed curves + SkDCurve curvePart; + this->subDivide(span, span->next(), &curvePart); + const SkDPoint* pts = curvePart.fCubic.fPts; + str->appendf(" (%1.9g,%1.9g", pts[0].fX, pts[0].fY); + for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { + str->appendf(" %1.9g,%1.9g", pts[vIndex].fX, pts[vIndex].fY); + } + if (SkPath::kConic_Verb == fVerb) { + str->appendf(" %1.9gf", curvePart.fConic.fWeight); + } + str->appendf(") t=%1.9g tEnd=%1.9g", span->t(), span->next()->t()); + if (span->windSum() == SK_MinS32) { + str->appendf(" windSum=?"); + } else { + str->appendf(" windSum=%d", span->windSum()); + } + if (span->oppValue() && span->oppSum() == SK_MinS32) { + str->appendf(" oppSum=?"); + } else if (span->oppValue() || span->oppSum() != SK_MinS32) { + str->appendf(" oppSum=%d", span->oppSum()); + } + str->appendf(" windValue=%d", span->windValue()); + if (span->oppValue() || span->oppSum() != SK_MinS32) { + str->appendf(" oppValue=%d", span->oppValue()); + } + str->appendf("\n"); + } while ((span = span->next()->upCastable())); +} +#endif + +#if DEBUG_MARK_DONE +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding) { + const SkPoint& pt = span->ptT()->fPt; + SkDebugf("%s id=%d", fun, this->debugID()); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", + span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t()); + if (winding == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", winding); + } + SkDebugf(" windSum="); + if (span->windSum() == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span->windSum()); + } + SkDebugf(" windValue=%d\n", span->windValue()); +} + +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, + int oppWinding) { + const SkPoint& pt = span->ptT()->fPt; + SkDebugf("%s id=%d", fun, this->debugID()); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", + span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t(), winding, oppWinding); + if (winding == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", winding); + } + SkDebugf(" newOppSum="); + if (oppWinding == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", oppWinding); + } + SkDebugf(" oppSum="); + if (span->oppSum() == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span->oppSum()); + } + SkDebugf(" windSum="); + if (span->windSum() == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span->windSum()); + } + SkDebugf(" windValue=%d oppValue=%d\n", span->windValue(), span->oppValue()); +} + +#endif + +// loop looking for a pair of angle parts that are too close to be sorted +/* This is called after other more simple intersection and angle sorting tests have been exhausted. + This should be rarely called -- the test below is thorough and time consuming. + This checks the distance between start points; the distance between +*/ +#if DEBUG_ANGLE +void SkOpAngle::debugCheckNearCoincidence() const { + const SkOpAngle* test = this; + do { + const SkOpSegment* testSegment = test->segment(); + double testStartT = test->start()->t(); + SkDPoint testStartPt = testSegment->dPtAtT(testStartT); + double testEndT = test->end()->t(); + SkDPoint testEndPt = testSegment->dPtAtT(testEndT); + double testLenSq = testStartPt.distanceSquared(testEndPt); + SkDebugf("%s testLenSq=%1.9g id=%d\n", __FUNCTION__, testLenSq, testSegment->debugID()); + double testMidT = (testStartT + testEndT) / 2; + const SkOpAngle* next = test; + while ((next = next->fNext) != this) { + SkOpSegment* nextSegment = next->segment(); + double testMidDistSq = testSegment->distSq(testMidT, next); + double testEndDistSq = testSegment->distSq(testEndT, next); + double nextStartT = next->start()->t(); + SkDPoint nextStartPt = nextSegment->dPtAtT(nextStartT); + double distSq = testStartPt.distanceSquared(nextStartPt); + double nextEndT = next->end()->t(); + double nextMidT = (nextStartT + nextEndT) / 2; + double nextMidDistSq = nextSegment->distSq(nextMidT, test); + double nextEndDistSq = nextSegment->distSq(nextEndT, test); + SkDebugf("%s distSq=%1.9g testId=%d nextId=%d\n", __FUNCTION__, distSq, + testSegment->debugID(), nextSegment->debugID()); + SkDebugf("%s testMidDistSq=%1.9g\n", __FUNCTION__, testMidDistSq); + SkDebugf("%s testEndDistSq=%1.9g\n", __FUNCTION__, testEndDistSq); + SkDebugf("%s nextMidDistSq=%1.9g\n", __FUNCTION__, nextMidDistSq); + SkDebugf("%s nextEndDistSq=%1.9g\n", __FUNCTION__, nextEndDistSq); + SkDPoint nextEndPt = nextSegment->dPtAtT(nextEndT); + double nextLenSq = nextStartPt.distanceSquared(nextEndPt); + SkDebugf("%s nextLenSq=%1.9g\n", __FUNCTION__, nextLenSq); + SkDebugf("\n"); + } + test = test->fNext; + } while (test->fNext != this); +} +#endif + +#if DEBUG_ANGLE +SkString SkOpAngle::debugPart() const { + SkString result; + switch (this->segment()->verb()) { + case SkPath::kLine_Verb: + result.printf(LINE_DEBUG_STR " id=%d", LINE_DEBUG_DATA(fPart.fCurve), + this->segment()->debugID()); + break; + case SkPath::kQuad_Verb: + result.printf(QUAD_DEBUG_STR " id=%d", QUAD_DEBUG_DATA(fPart.fCurve), + this->segment()->debugID()); + break; + case SkPath::kConic_Verb: + result.printf(CONIC_DEBUG_STR " id=%d", + CONIC_DEBUG_DATA(fPart.fCurve, fPart.fCurve.fConic.fWeight), + this->segment()->debugID()); + break; + case SkPath::kCubic_Verb: + result.printf(CUBIC_DEBUG_STR " id=%d", CUBIC_DEBUG_DATA(fPart.fCurve), + this->segment()->debugID()); + break; + default: + SkASSERT(0); + } + return result; +} +#endif + +#if DEBUG_SORT +void SkOpAngle::debugLoop() const { + const SkOpAngle* first = this; + const SkOpAngle* next = this; + do { + next->dumpOne(true); + SkDebugf("\n"); + next = next->fNext; + } while (next && next != first); + next = first; + do { + next->debugValidate(); + next = next->fNext; + } while (next && next != first); +} +#endif + +void SkOpAngle::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + const SkOpAngle* first = this; + const SkOpAngle* next = this; + int wind = 0; + int opp = 0; + int lastXor = -1; + int lastOppXor = -1; + do { + if (next->unorderable()) { + return; + } + const SkOpSpan* minSpan = next->start()->starter(next->end()); + if (minSpan->windValue() == SK_MinS32) { + return; + } + bool op = next->segment()->operand(); + bool isXor = next->segment()->isXor(); + bool oppXor = next->segment()->oppXor(); + SkASSERT(!DEBUG_LIMIT_WIND_SUM || between(0, minSpan->windValue(), DEBUG_LIMIT_WIND_SUM)); + SkASSERT(!DEBUG_LIMIT_WIND_SUM + || between(-DEBUG_LIMIT_WIND_SUM, minSpan->oppValue(), DEBUG_LIMIT_WIND_SUM)); + bool useXor = op ? oppXor : isXor; + SkASSERT(lastXor == -1 || lastXor == (int) useXor); + lastXor = (int) useXor; + wind += next->debugSign() * (op ? minSpan->oppValue() : minSpan->windValue()); + if (useXor) { + wind &= 1; + } + useXor = op ? isXor : oppXor; + SkASSERT(lastOppXor == -1 || lastOppXor == (int) useXor); + lastOppXor = (int) useXor; + opp += next->debugSign() * (op ? minSpan->windValue() : minSpan->oppValue()); + if (useXor) { + opp &= 1; + } + next = next->fNext; + } while (next && next != first); + SkASSERT(wind == 0 || !SkPathOpsDebug::gRunFail); + SkASSERT(opp == 0 || !SkPathOpsDebug::gRunFail); +#endif +} + +void SkOpAngle::debugValidateNext() const { +#if !FORCE_RELEASE + const SkOpAngle* first = this; + const SkOpAngle* next = first; + SkTDArray(angles); + do { +// SkASSERT_RELEASE(next->fSegment->debugContains(next)); + angles.push_back(next); + next = next->next(); + if (next == first) { + break; + } + SkASSERT_RELEASE(!angles.contains(next)); + if (!next) { + return; + } + } while (true); +#endif +} + +#ifdef SK_DEBUG +void SkCoincidentSpans::debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, + const SkOpGlobalState* debugState) const { + SkASSERT(coinPtTEnd()->span() == over || !SkOpGlobalState::DebugRunFail()); + SkASSERT(oppPtTEnd()->span() == outer || !SkOpGlobalState::DebugRunFail()); +} +#endif + +#if DEBUG_COIN +// sets the span's end to the ptT referenced by the previous-next +void SkCoincidentSpans::debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) const ) const { + const SkOpPtT* origPtT = (this->*getEnd)(); + const SkOpSpanBase* origSpan = origPtT->span(); + const SkOpSpan* prev = origSpan->prev(); + const SkOpPtT* testPtT = prev ? prev->next()->ptT() + : origSpan->upCast()->next()->prev()->ptT(); + if (origPtT != testPtT) { + log->record(SkPathOpsDebug::kCorrectEnd_Glitch, this, origPtT, testPtT); + } +} + + +/* Commented-out lines keep this in sync with correctEnds */ +// FIXME: member pointers have fallen out of favor and can be replaced with +// an alternative approach. +// makes all span ends agree with the segment's spans that define them +void SkCoincidentSpans::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const { + this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTStart, nullptr); + this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTEnd, nullptr); + this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTStart, nullptr); + this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTEnd, nullptr); +} + +/* Commented-out lines keep this in sync with expand */ +// expand the range by checking adjacent spans for coincidence +bool SkCoincidentSpans::debugExpand(SkPathOpsDebug::GlitchLog* log) const { + bool expanded = false; + const SkOpSegment* segment = coinPtTStart()->segment(); + const SkOpSegment* oppSegment = oppPtTStart()->segment(); + do { + const SkOpSpan* start = coinPtTStart()->span()->upCast(); + const SkOpSpan* prev = start->prev(); + const SkOpPtT* oppPtT; + if (!prev || !(oppPtT = prev->contains(oppSegment))) { + break; + } + double midT = (prev->t() + start->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, prev->ptT(), oppPtT); + expanded = true; + } while (false); // actual continues while expansion is possible + do { + const SkOpSpanBase* end = coinPtTEnd()->span(); + SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); + if (next && next->deleted()) { + break; + } + const SkOpPtT* oppPtT; + if (!next || !(oppPtT = next->contains(oppSegment))) { + break; + } + double midT = (end->t() + next->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, next->ptT(), oppPtT); + expanded = true; + } while (false); // actual continues while expansion is possible + return expanded; +} + +// description below +void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log, const SkOpSpan* base, const SkOpSpanBase* testSpan) const { + const SkOpPtT* testPtT = testSpan->ptT(); + const SkOpPtT* stopPtT = testPtT; + const SkOpSegment* baseSeg = base->segment(); + while ((testPtT = testPtT->next()) != stopPtT) { + const SkOpSegment* testSeg = testPtT->segment(); + if (testPtT->deleted()) { + continue; + } + if (testSeg == baseSeg) { + continue; + } + if (testPtT->span()->ptT() != testPtT) { + continue; + } + if (this->contains(baseSeg, testSeg, testPtT->fT)) { + continue; + } + // intersect perp with base->ptT() with testPtT->segment() + SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); + const SkPoint& pt = base->pt(); + SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; + SkIntersections i; + (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); + for (int index = 0; index < i.used(); ++index) { + double t = i[0][index]; + if (!between(0, t, 1)) { + continue; + } + SkDPoint oppPt = i.pt(index); + if (!oppPt.approximatelyEqual(pt)) { + continue; + } + SkOpSegment* writableSeg = const_cast(testSeg); + SkOpPtT* oppStart = writableSeg->addT(t); + if (oppStart == testPtT) { + continue; + } + SkOpSpan* writableBase = const_cast(base); + oppStart->span()->addOpp(writableBase); + if (oppStart->deleted()) { + continue; + } + SkOpSegment* coinSeg = base->segment(); + SkOpSegment* oppSeg = oppStart->segment(); + double coinTs, coinTe, oppTs, oppTe; + if (Ordered(coinSeg, oppSeg)) { + coinTs = base->t(); + coinTe = testSpan->t(); + oppTs = oppStart->fT; + oppTe = testPtT->fT; + } else { + using std::swap; + swap(coinSeg, oppSeg); + coinTs = oppStart->fT; + coinTe = testPtT->fT; + oppTs = base->t(); + oppTe = testSpan->t(); + } + if (coinTs > coinTe) { + using std::swap; + swap(coinTs, coinTe); + swap(oppTs, oppTe); + } + bool added; + if (this->debugAddOrOverlap(log, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &added), false) { + return; + } + } + } + return; +} + +// description below +void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* ptT) const { + FAIL_IF(!ptT->span()->upCastable(), ptT->span()); + const SkOpSpan* base = ptT->span()->upCast(); + const SkOpSpan* prev = base->prev(); + FAIL_IF(!prev, ptT->span()); + if (!prev->isCanceled()) { + if (this->debugAddEndMovedSpans(log, base, base->prev()), false) { + return; + } + } + if (!base->isCanceled()) { + if (this->debugAddEndMovedSpans(log, base, base->next()), false) { + return; + } + } + return; +} + +/* If A is coincident with B and B includes an endpoint, and A's matching point + is not the endpoint (i.e., there's an implied line connecting B-end and A) + then assume that the same implied line may intersect another curve close to B. + Since we only care about coincidence that was undetected, look at the + ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but + next door) and see if the A matching point is close enough to form another + coincident pair. If so, check for a new coincident span between B-end/A ptT loop + and the adjacent ptT loop. +*/ +void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* span = fHead; + if (!span) { + return; + } +// fTop = span; +// fHead = nullptr; + do { + if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { + FAIL_IF(1 == span->coinPtTStart()->fT, span); + bool onEnd = span->coinPtTStart()->fT == 0; + bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); + if (onEnd) { + if (!oOnEnd) { // if both are on end, any nearby intersect was already found + if (this->debugAddEndMovedSpans(log, span->oppPtTStart()), false) { + return; + } + } + } else if (oOnEnd) { + if (this->debugAddEndMovedSpans(log, span->coinPtTStart()), false) { + return; + } + } + } + if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { + bool onEnd = span->coinPtTEnd()->fT == 1; + bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); + if (onEnd) { + if (!oOnEnd) { + if (this->debugAddEndMovedSpans(log, span->oppPtTEnd()), false) { + return; + } + } + } else if (oOnEnd) { + if (this->debugAddEndMovedSpans(log, span->coinPtTEnd()), false) { + return; + } + } + } + } while ((span = span->next())); +// this->restoreHead(); + return; +} + +/* Commented-out lines keep this in sync with addExpanded */ +// for each coincident pair, match the spans +// if the spans don't match, add the mssing pt to the segment and loop it in the opposite span +void SkOpCoincidence::debugAddExpanded(SkPathOpsDebug::GlitchLog* log) const { +// DEBUG_SET_PHASE(); + const SkCoincidentSpans* coin = this->fHead; + if (!coin) { + return; + } + do { + const SkOpPtT* startPtT = coin->coinPtTStart(); + const SkOpPtT* oStartPtT = coin->oppPtTStart(); + double priorT = startPtT->fT; + double oPriorT = oStartPtT->fT; + FAIL_IF(!startPtT->contains(oStartPtT), coin); + SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); + const SkOpSpanBase* start = startPtT->span(); + const SkOpSpanBase* oStart = oStartPtT->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); + FAIL_IF(oEnd->deleted(), coin); + FAIL_IF(!start->upCastable(), coin); + const SkOpSpanBase* test = start->upCast()->next(); + FAIL_IF(!coin->flipped() && !oStart->upCastable(), coin); + const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); + FAIL_IF(!oTest, coin); + const SkOpSegment* seg = start->segment(); + const SkOpSegment* oSeg = oStart->segment(); + while (test != end || oTest != oEnd) { + const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); + const SkOpPtT* containedThis = oTest->ptT()->contains(seg); + if (!containedOpp || !containedThis) { + // choose the ends, or the first common pt-t list shared by both + double nextT, oNextT; + if (containedOpp) { + nextT = test->t(); + oNextT = containedOpp->fT; + } else if (containedThis) { + nextT = containedThis->fT; + oNextT = oTest->t(); + } else { + // iterate through until a pt-t list found that contains the other + const SkOpSpanBase* walk = test; + const SkOpPtT* walkOpp; + do { + FAIL_IF(!walk->upCastable(), coin); + walk = walk->upCast()->next(); + } while (!(walkOpp = walk->ptT()->contains(oSeg)) + && walk != coin->coinPtTEnd()->span()); + FAIL_IF(!walkOpp, coin); + nextT = walk->t(); + oNextT = walkOpp->fT; + } + // use t ranges to guess which one is missing + double startRange = nextT - priorT; + FAIL_IF(!startRange, coin); + double startPart = (test->t() - priorT) / startRange; + double oStartRange = oNextT - oPriorT; + FAIL_IF(!oStartRange, coin); + double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange; + FAIL_IF(startPart == oStartPart, coin); + bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart + : !!containedThis; + bool startOver = false; + addToOpp ? log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch, + oPriorT + oStartRange * startPart, test) + : log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch, + priorT + startRange * oStartPart, oTest); + // FAIL_IF(!success, coin); + if (startOver) { + test = start; + oTest = oStart; + } + end = coin->coinPtTEnd()->span(); + oEnd = coin->oppPtTEnd()->span(); + } + if (test != end) { + FAIL_IF(!test->upCastable(), coin); + priorT = test->t(); + test = test->upCast()->next(); + } + if (oTest != oEnd) { + oPriorT = oTest->t(); + oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next(); + FAIL_IF(!oTest, coin); + } + } + } while ((coin = coin->next())); + return; +} + +/* Commented-out lines keep this in sync addIfMissing() */ +// note that over1s, over1e, over2s, over2e are ordered +void SkOpCoincidence::debugAddIfMissing(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, bool* added, + const SkOpPtT* over1e, const SkOpPtT* over2e) const { + SkASSERT(tStart < tEnd); + SkASSERT(over1s->fT < over1e->fT); + SkASSERT(between(over1s->fT, tStart, over1e->fT)); + SkASSERT(between(over1s->fT, tEnd, over1e->fT)); + SkASSERT(over2s->fT < over2e->fT); + SkASSERT(between(over2s->fT, tStart, over2e->fT)); + SkASSERT(between(over2s->fT, tEnd, over2e->fT)); + SkASSERT(over1s->segment() == over1e->segment()); + SkASSERT(over2s->segment() == over2e->segment()); + SkASSERT(over1s->segment() == over2s->segment()); + SkASSERT(over1s->segment() != coinSeg); + SkASSERT(over1s->segment() != oppSeg); + SkASSERT(coinSeg != oppSeg); + double coinTs, coinTe, oppTs, oppTe; + coinTs = TRange(over1s, tStart, coinSeg SkDEBUGPARAMS(over1e)); + coinTe = TRange(over1s, tEnd, coinSeg SkDEBUGPARAMS(over1e)); + SkOpSpanBase::Collapsed result = coinSeg->collapsed(coinTs, coinTe); + if (SkOpSpanBase::Collapsed::kNo != result) { + return log->record(SkPathOpsDebug::kAddIfCollapsed_Glitch, coinSeg); + } + oppTs = TRange(over2s, tStart, oppSeg SkDEBUGPARAMS(over2e)); + oppTe = TRange(over2s, tEnd, oppSeg SkDEBUGPARAMS(over2e)); + result = oppSeg->collapsed(oppTs, oppTe); + if (SkOpSpanBase::Collapsed::kNo != result) { + return log->record(SkPathOpsDebug::kAddIfCollapsed_Glitch, oppSeg); + } + if (coinTs > coinTe) { + using std::swap; + swap(coinTs, coinTe); + swap(oppTs, oppTe); + } + this->debugAddOrOverlap(log, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, added); + return; +} + +/* Commented-out lines keep this in sync addOrOverlap() */ +// If this is called by addEndMovedSpans(), a returned false propogates out to an abort. +// If this is called by AddIfMissing(), a returned false indicates there was nothing to add +void SkOpCoincidence::debugAddOrOverlap(SkPathOpsDebug::GlitchLog* log, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, bool* added) const { + SkTDArray overlaps; + SkOPASSERT(!fTop); // this is (correctly) reversed in addifMissing() + if (fTop && !this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, + &overlaps)) { + return; + } + if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, + coinTe, oppTs, oppTe, &overlaps)) { + return; + } + const SkCoincidentSpans* overlap = overlaps.count() ? overlaps[0] : nullptr; + for (int index = 1; index < overlaps.count(); ++index) { // combine overlaps before continuing + const SkCoincidentSpans* test = overlaps[index]; + if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->coinPtTStart()); + } + if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->coinPtTEnd()); + } + if (overlap->flipped() + ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT + : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->oppPtTStart()); + } + if (overlap->flipped() + ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT + : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->oppPtTEnd()); + } + if (!fHead) { this->debugRelease(log, fHead, test); + this->debugRelease(log, fTop, test); + } + } + const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); + const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); + RETURN_FALSE_IF(overlap && cs && ce && overlap->contains(cs, ce), coinSeg); + RETURN_FALSE_IF(cs != ce || !cs, coinSeg); + const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); + const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); + RETURN_FALSE_IF(overlap && os && oe && overlap->contains(os, oe), oppSeg); + SkASSERT(true || !cs || !cs->deleted()); + SkASSERT(true || !os || !os->deleted()); + SkASSERT(true || !ce || !ce->deleted()); + SkASSERT(true || !oe || !oe->deleted()); + const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; + const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; + RETURN_FALSE_IF(csExisting && csExisting == ceExisting, coinSeg); + RETURN_FALSE_IF(csExisting && (csExisting == ce || + csExisting->contains(ceExisting ? ceExisting : ce)), coinSeg); + RETURN_FALSE_IF(ceExisting && (ceExisting == cs || + ceExisting->contains(csExisting ? csExisting : cs)), coinSeg); + const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; + const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; + RETURN_FALSE_IF(osExisting && osExisting == oeExisting, oppSeg); + RETURN_FALSE_IF(osExisting && (osExisting == oe || + osExisting->contains(oeExisting ? oeExisting : oe)), oppSeg); + RETURN_FALSE_IF(oeExisting && (oeExisting == os || + oeExisting->contains(osExisting ? osExisting : os)), oppSeg); + bool csDeleted = false, osDeleted = false, ceDeleted = false, oeDeleted = false; + this->debugValidate(); + if (!cs || !os) { + if (!cs) + cs = coinSeg->debugAddT(coinTs, log); + if (!os) + os = oppSeg->debugAddT(oppTs, log); +// RETURN_FALSE_IF(callerAborts, !csWritable || !osWritable); + if (cs && os) cs->span()->debugAddOpp(log, os->span()); +// cs = csWritable; +// os = osWritable->active(); + RETURN_FALSE_IF((ce && ce->deleted()) || (oe && oe->deleted()), coinSeg); + } + if (!ce || !oe) { + if (!ce) + ce = coinSeg->debugAddT(coinTe, log); + if (!oe) + oe = oppSeg->debugAddT(oppTe, log); + if (ce && oe) ce->span()->debugAddOpp(log, oe->span()); +// ce = ceWritable; +// oe = oeWritable; + } + this->debugValidate(); + RETURN_FALSE_IF(csDeleted, coinSeg); + RETURN_FALSE_IF(osDeleted, oppSeg); + RETURN_FALSE_IF(ceDeleted, coinSeg); + RETURN_FALSE_IF(oeDeleted, oppSeg); + RETURN_FALSE_IF(!cs || !ce || cs == ce || cs->contains(ce) || !os || !oe || os == oe || os->contains(oe), coinSeg); + bool result = true; + if (overlap) { + if (overlap->coinPtTStart()->segment() == coinSeg) { + log->record(SkPathOpsDebug::kAddMissingExtend_Glitch, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); + } else { + if (oppTs > oppTe) { + using std::swap; + swap(coinTs, coinTe); + swap(oppTs, oppTe); + } + log->record(SkPathOpsDebug::kAddMissingExtend_Glitch, oppSeg, oppTs, oppTe, coinSeg, coinTs, coinTe); + } +#if 0 && DEBUG_COINCIDENCE_VERBOSE + if (result) { + overlap->debugShow(); + } +#endif + } else { + log->record(SkPathOpsDebug::kAddMissingCoin_Glitch, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); +#if 0 && DEBUG_COINCIDENCE_VERBOSE + fHead->debugShow(); +#endif + } + this->debugValidate(); + return (void) result; +} + +// Extra commented-out lines keep this in sync with addMissing() +/* detects overlaps of different coincident runs on same segment */ +/* does not detect overlaps for pairs without any segments in common */ +// returns true if caller should loop again +void SkOpCoincidence::debugAddMissing(SkPathOpsDebug::GlitchLog* log, bool* added) const { + const SkCoincidentSpans* outer = fHead; + *added = false; + if (!outer) { + return; + } + // fTop = outer; + // fHead = nullptr; + do { + // addifmissing can modify the list that this is walking + // save head so that walker can iterate over old data unperturbed + // addifmissing adds to head freely then add saved head in the end + const SkOpPtT* ocs = outer->coinPtTStart(); + SkASSERT(!ocs->deleted()); + const SkOpSegment* outerCoin = ocs->segment(); + SkASSERT(!outerCoin->done()); // if it's done, should have already been removed from list + const SkOpPtT* oos = outer->oppPtTStart(); + if (oos->deleted()) { + return; + } + const SkOpSegment* outerOpp = oos->segment(); + SkASSERT(!outerOpp->done()); +// SkOpSegment* outerCoinWritable = const_cast(outerCoin); +// SkOpSegment* outerOppWritable = const_cast(outerOpp); + const SkCoincidentSpans* inner = outer; + while ((inner = inner->next())) { + this->debugValidate(); + double overS, overE; + const SkOpPtT* ics = inner->coinPtTStart(); + SkASSERT(!ics->deleted()); + const SkOpSegment* innerCoin = ics->segment(); + SkASSERT(!innerCoin->done()); + const SkOpPtT* ios = inner->oppPtTStart(); + SkASSERT(!ios->deleted()); + const SkOpSegment* innerOpp = ios->segment(); + SkASSERT(!innerOpp->done()); +// SkOpSegment* innerCoinWritable = const_cast(innerCoin); +// SkOpSegment* innerOppWritable = const_cast(innerOpp); + if (outerCoin == innerCoin) { + const SkOpPtT* oce = outer->coinPtTEnd(); + if (oce->deleted()) { + return; + } + const SkOpPtT* ice = inner->coinPtTEnd(); + SkASSERT(!ice->deleted()); + if (outerOpp != innerOpp && this->overlap(ocs, oce, ics, ice, &overS, &overE)) { + this->debugAddIfMissing(log, ocs->starter(oce), ics->starter(ice), + overS, overE, outerOpp, innerOpp, added, + ocs->debugEnder(oce), + ics->debugEnder(ice)); + } + } else if (outerCoin == innerOpp) { + const SkOpPtT* oce = outer->coinPtTEnd(); + SkASSERT(!oce->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + SkASSERT(!ioe->deleted()); + if (outerOpp != innerCoin && this->overlap(ocs, oce, ios, ioe, &overS, &overE)) { + this->debugAddIfMissing(log, ocs->starter(oce), ios->starter(ioe), + overS, overE, outerOpp, innerCoin, added, + ocs->debugEnder(oce), + ios->debugEnder(ioe)); + } + } else if (outerOpp == innerCoin) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + SkASSERT(!ooe->deleted()); + const SkOpPtT* ice = inner->coinPtTEnd(); + SkASSERT(!ice->deleted()); + SkASSERT(outerCoin != innerOpp); + if (this->overlap(oos, ooe, ics, ice, &overS, &overE)) { + this->debugAddIfMissing(log, oos->starter(ooe), ics->starter(ice), + overS, overE, outerCoin, innerOpp, added, + oos->debugEnder(ooe), + ics->debugEnder(ice)); + } + } else if (outerOpp == innerOpp) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + SkASSERT(!ooe->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + if (ioe->deleted()) { + return; + } + SkASSERT(outerCoin != innerCoin); + if (this->overlap(oos, ooe, ios, ioe, &overS, &overE)) { + this->debugAddIfMissing(log, oos->starter(ooe), ios->starter(ioe), + overS, overE, outerCoin, innerCoin, added, + oos->debugEnder(ooe), + ios->debugEnder(ioe)); + } + } + this->debugValidate(); + } + } while ((outer = outer->next())); + // this->restoreHead(); + return; +} + +// Commented-out lines keep this in sync with release() +void SkOpCoincidence::debugRelease(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkCoincidentSpans* remove) const { + const SkCoincidentSpans* head = coin; + const SkCoincidentSpans* prev = nullptr; + const SkCoincidentSpans* next; + do { + next = coin->next(); + if (coin == remove) { + if (prev) { +// prev->setNext(next); + } else if (head == fHead) { +// fHead = next; + } else { +// fTop = next; + } + log->record(SkPathOpsDebug::kReleasedSpan_Glitch, coin); + } + prev = coin; + } while ((coin = next)); + return; +} + +void SkOpCoincidence::debugRelease(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* deleted) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + if (coin->coinPtTStart()->segment() == deleted + || coin->coinPtTEnd()->segment() == deleted + || coin->oppPtTStart()->segment() == deleted + || coin->oppPtTEnd()->segment() == deleted) { + log->record(SkPathOpsDebug::kReleasedSpan_Glitch, coin); + } + } while ((coin = coin->next())); +} + +// Commented-out lines keep this in sync with expand() +// expand the range by checking adjacent spans for coincidence +bool SkOpCoincidence::debugExpand(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return false; + } + bool expanded = false; + do { + if (coin->debugExpand(log)) { + // check to see if multiple spans expanded so they are now identical + const SkCoincidentSpans* test = fHead; + do { + if (coin == test) { + continue; + } + if (coin->coinPtTStart() == test->coinPtTStart() + && coin->oppPtTStart() == test->oppPtTStart()) { + if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, fHead, test->coinPtTStart()); + break; + } + } while ((test = test->next())); + expanded = true; + } + } while ((coin = coin->next())); + return expanded; +} + +// Commented-out lines keep this in sync with mark() +/* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ +void SkOpCoincidence::debugMark(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + FAIL_IF(!coin->coinPtTStartWritable()->span()->upCastable(), coin); + const SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); +// SkASSERT(start->deleted()); + const SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); +// SkASSERT(end->deleted()); + const SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); +// SkASSERT(oStart->deleted()); + const SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); +// SkASSERT(oEnd->deleted()); + bool flipped = coin->flipped(); + if (flipped) { + using std::swap; + swap(oStart, oEnd); + } + /* coin and opp spans may not match up. Mark the ends, and then let the interior + get marked as many times as the spans allow */ + start->debugInsertCoincidence(log, oStart->upCast()); + end->debugInsertCoinEnd(log, oEnd); + const SkOpSegment* segment = start->segment(); + const SkOpSegment* oSegment = oStart->segment(); + const SkOpSpanBase* next = start; + const SkOpSpanBase* oNext = oStart; + bool ordered; + FAIL_IF(!coin->ordered(&ordered), coin); + while ((next = next->upCast()->next()) != end) { + FAIL_IF(!next->upCastable(), coin); + if (next->upCast()->debugInsertCoincidence(log, oSegment, flipped, ordered), false) { + return; + } + } + while ((oNext = oNext->upCast()->next()) != oEnd) { + FAIL_IF(!oNext->upCastable(), coin); + if (oNext->upCast()->debugInsertCoincidence(log, segment, flipped, ordered), false) { + return; + } + } + } while ((coin = coin->next())); + return; +} +#endif + +#if DEBUG_COIN +// Commented-out lines keep this in sync with markCollapsed() +void SkOpCoincidence::debugMarkCollapsed(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkOpPtT* test) const { + const SkCoincidentSpans* head = coin; + while (coin) { + if (coin->collapsed(test)) { + if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { + log->record(SkPathOpsDebug::kCollapsedCoin_Glitch, coin); + } + if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { + log->record(SkPathOpsDebug::kCollapsedCoin_Glitch, coin); + } + this->debugRelease(log, head, coin); + } + coin = coin->next(); + } +} + +// Commented-out lines keep this in sync with markCollapsed() +void SkOpCoincidence::debugMarkCollapsed(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* test) const { + this->debugMarkCollapsed(log, fHead, test); + this->debugMarkCollapsed(log, fTop, test); +} +#endif + +void SkCoincidentSpans::debugShow() const { + SkDebugf("coinSpan - id=%d t=%1.9g tEnd=%1.9g\n", coinPtTStart()->segment()->debugID(), + coinPtTStart()->fT, coinPtTEnd()->fT); + SkDebugf("coinSpan + id=%d t=%1.9g tEnd=%1.9g\n", oppPtTStart()->segment()->debugID(), + oppPtTStart()->fT, oppPtTEnd()->fT); +} + +void SkOpCoincidence::debugShowCoincidence() const { +#if DEBUG_COINCIDENCE + const SkCoincidentSpans* span = fHead; + while (span) { + span->debugShow(); + span = span->next(); + } +#endif +} + +#if DEBUG_COIN +static void DebugCheckBetween(const SkOpSpanBase* next, const SkOpSpanBase* end, + double oStart, double oEnd, const SkOpSegment* oSegment, + SkPathOpsDebug::GlitchLog* log) { + SkASSERT(next != end); + SkASSERT(!next->contains(end) || log); + if (next->t() > end->t()) { + using std::swap; + swap(next, end); + } + do { + const SkOpPtT* ptT = next->ptT(); + int index = 0; + bool somethingBetween = false; + do { + ++index; + ptT = ptT->next(); + const SkOpPtT* checkPtT = next->ptT(); + if (ptT == checkPtT) { + break; + } + bool looped = false; + for (int check = 0; check < index; ++check) { + if ((looped = checkPtT == ptT)) { + break; + } + checkPtT = checkPtT->next(); + } + if (looped) { + SkASSERT(0); + break; + } + if (ptT->deleted()) { + continue; + } + if (ptT->segment() != oSegment) { + continue; + } + somethingBetween |= between(oStart, ptT->fT, oEnd); + } while (true); + SkASSERT(somethingBetween); + } while (next != end && (next = next->upCast()->next())); +} + +static void DebugCheckOverlap(const SkCoincidentSpans* test, const SkCoincidentSpans* list, + SkPathOpsDebug::GlitchLog* log) { + if (!list) { + return; + } + const SkOpSegment* coinSeg = test->coinPtTStart()->segment(); + SkASSERT(coinSeg == test->coinPtTEnd()->segment()); + const SkOpSegment* oppSeg = test->oppPtTStart()->segment(); + SkASSERT(oppSeg == test->oppPtTEnd()->segment()); + SkASSERT(coinSeg != test->oppPtTStart()->segment()); + SkDEBUGCODE(double tcs = test->coinPtTStart()->fT); + SkASSERT(between(0, tcs, 1)); + SkDEBUGCODE(double tce = test->coinPtTEnd()->fT); + SkASSERT(between(0, tce, 1)); + SkASSERT(tcs < tce); + double tos = test->oppPtTStart()->fT; + SkASSERT(between(0, tos, 1)); + double toe = test->oppPtTEnd()->fT; + SkASSERT(between(0, toe, 1)); + SkASSERT(tos != toe); + if (tos > toe) { + using std::swap; + swap(tos, toe); + } + do { + double lcs, lce, los, loe; + if (coinSeg == list->coinPtTStart()->segment()) { + if (oppSeg != list->oppPtTStart()->segment()) { + continue; + } + lcs = list->coinPtTStart()->fT; + lce = list->coinPtTEnd()->fT; + los = list->oppPtTStart()->fT; + loe = list->oppPtTEnd()->fT; + if (los > loe) { + using std::swap; + swap(los, loe); + } + } else if (coinSeg == list->oppPtTStart()->segment()) { + if (oppSeg != list->coinPtTStart()->segment()) { + continue; + } + lcs = list->oppPtTStart()->fT; + lce = list->oppPtTEnd()->fT; + if (lcs > lce) { + using std::swap; + swap(lcs, lce); + } + los = list->coinPtTStart()->fT; + loe = list->coinPtTEnd()->fT; + } else { + continue; + } + SkASSERT(tce < lcs || lce < tcs); + SkASSERT(toe < los || loe < tos); + } while ((list = list->next())); +} + + +static void DebugCheckOverlapTop(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + SkPathOpsDebug::GlitchLog* log) { + // check for overlapping coincident spans + const SkCoincidentSpans* test = head; + while (test) { + const SkCoincidentSpans* next = test->next(); + DebugCheckOverlap(test, next, log); + DebugCheckOverlap(test, opt, log); + test = next; + } +} + +static void DebugValidate(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + SkPathOpsDebug::GlitchLog* log) { + // look for pts inside coincident spans that are not inside the opposite spans + const SkCoincidentSpans* coin = head; + while (coin) { + SkASSERT(SkOpCoincidence::Ordered(coin->coinPtTStart()->segment(), + coin->oppPtTStart()->segment())); + SkASSERT(coin->coinPtTStart()->span()->ptT() == coin->coinPtTStart()); + SkASSERT(coin->coinPtTEnd()->span()->ptT() == coin->coinPtTEnd()); + SkASSERT(coin->oppPtTStart()->span()->ptT() == coin->oppPtTStart()); + SkASSERT(coin->oppPtTEnd()->span()->ptT() == coin->oppPtTEnd()); + coin = coin->next(); + } + DebugCheckOverlapTop(head, opt, log); +} +#endif + +void SkOpCoincidence::debugValidate() const { +#if DEBUG_COINCIDENCE + DebugValidate(fHead, fTop, nullptr); + DebugValidate(fTop, nullptr, nullptr); +#endif +} + +#if DEBUG_COIN +static void DebugCheckBetween(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + SkPathOpsDebug::GlitchLog* log) { + // look for pts inside coincident spans that are not inside the opposite spans + const SkCoincidentSpans* coin = head; + while (coin) { + DebugCheckBetween(coin->coinPtTStart()->span(), coin->coinPtTEnd()->span(), + coin->oppPtTStart()->fT, coin->oppPtTEnd()->fT, coin->oppPtTStart()->segment(), + log); + DebugCheckBetween(coin->oppPtTStart()->span(), coin->oppPtTEnd()->span(), + coin->coinPtTStart()->fT, coin->coinPtTEnd()->fT, coin->coinPtTStart()->segment(), + log); + coin = coin->next(); + } + DebugCheckOverlapTop(head, opt, log); +} +#endif + +void SkOpCoincidence::debugCheckBetween() const { +#if DEBUG_COINCIDENCE + if (fGlobalState->debugCheckHealth()) { + return; + } + DebugCheckBetween(fHead, fTop, nullptr); + DebugCheckBetween(fTop, nullptr, nullptr); +#endif +} + +#if DEBUG_COIN +void SkOpContour::debugCheckHealth(SkPathOpsDebug::GlitchLog* log) const { + const SkOpSegment* segment = &fHead; + do { + segment->debugCheckHealth(log); + } while ((segment = segment->next())); +} + +void SkOpCoincidence::debugCheckValid(SkPathOpsDebug::GlitchLog* log) const { +#if DEBUG_VALIDATE + DebugValidate(fHead, fTop, log); + DebugValidate(fTop, nullptr, log); +#endif +} + +void SkOpCoincidence::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + coin->debugCorrectEnds(log); + } while ((coin = coin->next())); +} + +// commmented-out lines keep this aligned with missingCoincidence() +void SkOpContour::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { +// SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; +// bool result = false; + do { + if (segment->debugMissingCoincidence(log), false) { +// result = true; + } + segment = segment->next(); + } while (segment); + return; +} + +void SkOpContour::debugMoveMultiples(SkPathOpsDebug::GlitchLog* log) const { + SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; + do { + if (segment->debugMoveMultiples(log), false) { + return; + } + } while ((segment = segment->next())); + return; +} + +void SkOpContour::debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const { + SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; + do { + segment->debugMoveNearby(log); + } while ((segment = segment->next())); +} +#endif + +#if DEBUG_COINCIDENCE_ORDER +void SkOpSegment::debugResetCoinT() const { + fDebugBaseIndex = -1; + fDebugBaseMin = 1; + fDebugBaseMax = -1; + fDebugLastIndex = -1; + fDebugLastMin = 1; + fDebugLastMax = -1; +} +#endif + +void SkOpSegment::debugValidate() const { +#if DEBUG_COINCIDENCE_ORDER + { + const SkOpSpanBase* span = &fHead; + do { + span->debugResetCoinT(); + } while (!span->final() && (span = span->upCast()->next())); + span = &fHead; + int index = 0; + do { + span->debugSetCoinT(index++); + } while (!span->final() && (span = span->upCast()->next())); + } +#endif +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + const SkOpSpanBase* span = &fHead; + double lastT = -1; + const SkOpSpanBase* prev = nullptr; + int count = 0; + int done = 0; + do { + if (!span->final()) { + ++count; + done += span->upCast()->done() ? 1 : 0; + } + SkASSERT(span->segment() == this); + SkASSERT(!prev || prev->upCast()->next() == span); + SkASSERT(!prev || prev == span->prev()); + prev = span; + double t = span->ptT()->fT; + SkASSERT(lastT < t); + lastT = t; + span->debugValidate(); + } while (!span->final() && (span = span->upCast()->next())); + SkASSERT(count == fCount); + SkASSERT(done == fDoneCount); + SkASSERT(count >= fDoneCount); + SkASSERT(span->final()); + span->debugValidate(); +#endif +} + +#if DEBUG_COIN + +// Commented-out lines keep this in sync with addOpp() +void SkOpSpanBase::debugAddOpp(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const { + const SkOpPtT* oppPrev = this->ptT()->oppPrev(opp->ptT()); + if (!oppPrev) { + return; + } + this->debugMergeMatches(log, opp); + this->ptT()->debugAddOpp(opp->ptT(), oppPrev); + this->debugCheckForCollapsedCoincidence(log); +} + +// Commented-out lines keep this in sync with checkForCollapsedCoincidence() +void SkOpSpanBase::debugCheckForCollapsedCoincidence(SkPathOpsDebug::GlitchLog* log) const { + const SkOpCoincidence* coins = this->globalState()->coincidence(); + if (coins->isEmpty()) { + return; + } +// the insert above may have put both ends of a coincident run in the same span +// for each coincident ptT in loop; see if its opposite in is also in the loop +// this implementation is the motivation for marking that a ptT is referenced by a coincident span + const SkOpPtT* head = this->ptT(); + const SkOpPtT* test = head; + do { + if (!test->coincident()) { + continue; + } + coins->debugMarkCollapsed(log, test); + } while ((test = test->next()) != head); +} +#endif + +bool SkOpSpanBase::debugCoinEndLoopCheck() const { + int loop = 0; + const SkOpSpanBase* next = this; + SkOpSpanBase* nextCoin; + do { + nextCoin = next->fCoinEnd; + SkASSERT(nextCoin == this || nextCoin->fCoinEnd != nextCoin); + for (int check = 1; check < loop - 1; ++check) { + const SkOpSpanBase* checkCoin = this->fCoinEnd; + const SkOpSpanBase* innerCoin = checkCoin; + for (int inner = check + 1; inner < loop; ++inner) { + innerCoin = innerCoin->fCoinEnd; + if (checkCoin == innerCoin) { + SkDebugf("*** bad coincident end loop ***\n"); + return false; + } + } + } + ++loop; + } while ((next = nextCoin) && next != this); + return true; +} + +#if DEBUG_COIN +// Commented-out lines keep this in sync with insertCoinEnd() +void SkOpSpanBase::debugInsertCoinEnd(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* coin) const { + if (containsCoinEnd(coin)) { +// SkASSERT(coin->containsCoinEnd(this)); + return; + } + debugValidate(); +// SkASSERT(this != coin); + log->record(SkPathOpsDebug::kMarkCoinEnd_Glitch, this, coin); +// coin->fCoinEnd = this->fCoinEnd; +// this->fCoinEnd = coinNext; + debugValidate(); +} + +// Commented-out lines keep this in sync with mergeMatches() +// Look to see if pt-t linked list contains same segment more than once +// if so, and if each pt-t is directly pointed to by spans in that segment, +// merge them +// keep the points, but remove spans so that the segment doesn't have 2 or more +// spans pointing to the same pt-t loop at different loop elements +void SkOpSpanBase::debugMergeMatches(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const { + const SkOpPtT* test = &fPtT; + const SkOpPtT* testNext; + const SkOpPtT* stop = test; + do { + testNext = test->next(); + if (test->deleted()) { + continue; + } + const SkOpSpanBase* testBase = test->span(); + SkASSERT(testBase->ptT() == test); + const SkOpSegment* segment = test->segment(); + if (segment->done()) { + continue; + } + const SkOpPtT* inner = opp->ptT(); + const SkOpPtT* innerStop = inner; + do { + if (inner->segment() != segment) { + continue; + } + if (inner->deleted()) { + continue; + } + const SkOpSpanBase* innerBase = inner->span(); + SkASSERT(innerBase->ptT() == inner); + // when the intersection is first detected, the span base is marked if there are + // more than one point in the intersection. +// if (!innerBase->hasMultipleHint() && !testBase->hasMultipleHint()) { + if (!zero_or_one(inner->fT)) { + log->record(SkPathOpsDebug::kMergeMatches_Glitch, innerBase, test); + } else { + SkASSERT(inner->fT != test->fT); + if (!zero_or_one(test->fT)) { + log->record(SkPathOpsDebug::kMergeMatches_Glitch, testBase, inner); + } else { + log->record(SkPathOpsDebug::kMergeMatches_Glitch, segment); +// SkDEBUGCODE(testBase->debugSetDeleted()); +// test->setDeleted(); +// SkDEBUGCODE(innerBase->debugSetDeleted()); +// inner->setDeleted(); + } + } +#ifdef SK_DEBUG // assert if another undeleted entry points to segment + const SkOpPtT* debugInner = inner; + while ((debugInner = debugInner->next()) != innerStop) { + if (debugInner->segment() != segment) { + continue; + } + if (debugInner->deleted()) { + continue; + } + SkOPASSERT(0); + } +#endif + break; +// } + break; + } while ((inner = inner->next()) != innerStop); + } while ((test = testNext) != stop); + this->debugCheckForCollapsedCoincidence(log); +} + +#endif + +void SkOpSpanBase::debugResetCoinT() const { +#if DEBUG_COINCIDENCE_ORDER + const SkOpPtT* ptT = &fPtT; + do { + ptT->debugResetCoinT(); + ptT = ptT->next(); + } while (ptT != &fPtT); +#endif +} + +void SkOpSpanBase::debugSetCoinT(int index) const { +#if DEBUG_COINCIDENCE_ORDER + const SkOpPtT* ptT = &fPtT; + do { + if (!ptT->deleted()) { + ptT->debugSetCoinT(index); + } + ptT = ptT->next(); + } while (ptT != &fPtT); +#endif +} + +const SkOpSpan* SkOpSpanBase::debugStarter(SkOpSpanBase const** endPtr) const { + const SkOpSpanBase* end = *endPtr; + SkASSERT(this->segment() == end->segment()); + const SkOpSpanBase* result; + if (t() < end->t()) { + result = this; + } else { + result = end; + *endPtr = this; + } + return result->upCast(); +} + +void SkOpSpanBase::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + const SkOpPtT* ptT = &fPtT; + SkASSERT(ptT->span() == this); + do { +// SkASSERT(SkDPoint::RoughlyEqual(fPtT.fPt, ptT->fPt)); + ptT->debugValidate(); + ptT = ptT->next(); + } while (ptT != &fPtT); + SkASSERT(this->debugCoinEndLoopCheck()); + if (!this->final()) { + SkASSERT(this->upCast()->debugCoinLoopCheck()); + } + if (fFromAngle) { + fFromAngle->debugValidate(); + } + if (!this->final() && this->upCast()->toAngle()) { + this->upCast()->toAngle()->debugValidate(); + } +#endif +} + +bool SkOpSpan::debugCoinLoopCheck() const { + int loop = 0; + const SkOpSpan* next = this; + SkOpSpan* nextCoin; + do { + nextCoin = next->fCoincident; + SkASSERT(nextCoin == this || nextCoin->fCoincident != nextCoin); + for (int check = 1; check < loop - 1; ++check) { + const SkOpSpan* checkCoin = this->fCoincident; + const SkOpSpan* innerCoin = checkCoin; + for (int inner = check + 1; inner < loop; ++inner) { + innerCoin = innerCoin->fCoincident; + if (checkCoin == innerCoin) { + SkDebugf("*** bad coincident loop ***\n"); + return false; + } + } + } + ++loop; + } while ((next = nextCoin) && next != this); + return true; +} + +#if DEBUG_COIN +// Commented-out lines keep this in sync with insertCoincidence() in header +void SkOpSpan::debugInsertCoincidence(SkPathOpsDebug::GlitchLog* log, const SkOpSpan* coin) const { + if (containsCoincidence(coin)) { +// SkASSERT(coin->containsCoincidence(this)); + return; + } + debugValidate(); +// SkASSERT(this != coin); + log->record(SkPathOpsDebug::kMarkCoinStart_Glitch, this, coin); +// coin->fCoincident = this->fCoincident; +// this->fCoincident = coinNext; + debugValidate(); +} + +// Commented-out lines keep this in sync with insertCoincidence() +void SkOpSpan::debugInsertCoincidence(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* segment, bool flipped, bool ordered) const { + if (this->containsCoincidence(segment)) { + return; + } + const SkOpPtT* next = &fPtT; + while ((next = next->next()) != &fPtT) { + if (next->segment() == segment) { + const SkOpSpan* span; + const SkOpSpanBase* base = next->span(); + if (!ordered) { + const SkOpSpanBase* spanEnd = fNext->contains(segment)->span(); + const SkOpPtT* start = base->ptT()->starter(spanEnd->ptT()); + FAIL_IF(!start->span()->upCastable(), this); + span = const_cast(start->span()->upCast()); + } + else if (flipped) { + span = base->prev(); + FAIL_IF(!span, this); + } + else { + FAIL_IF(!base->upCastable(), this); + span = base->upCast(); + } + log->record(SkPathOpsDebug::kMarkCoinInsert_Glitch, span); + return; + } + } +#if DEBUG_COIN + log->record(SkPathOpsDebug::kMarkCoinMissing_Glitch, segment, this); +#endif + return; +} +#endif + +// called only by test code +int SkIntersections::debugCoincidentUsed() const { + if (!fIsCoincident[0]) { + SkASSERT(!fIsCoincident[1]); + return 0; + } + int count = 0; + SkDEBUGCODE(int count2 = 0;) + for (int index = 0; index < fUsed; ++index) { + if (fIsCoincident[0] & (1 << index)) { + ++count; + } +#ifdef SK_DEBUG + if (fIsCoincident[1] & (1 << index)) { + ++count2; + } +#endif + } + SkASSERT(count == count2); + return count; +} + +#include "src/pathops/SkOpContour.h" + +// Commented-out lines keep this in sync with addOpp() +void SkOpPtT::debugAddOpp(const SkOpPtT* opp, const SkOpPtT* oppPrev) const { + SkDEBUGCODE(const SkOpPtT* oldNext = this->fNext); + SkASSERT(this != opp); +// this->fNext = opp; + SkASSERT(oppPrev != oldNext); +// oppPrev->fNext = oldNext; +} + +bool SkOpPtT::debugContains(const SkOpPtT* check) const { + SkASSERT(this != check); + const SkOpPtT* ptT = this; + int links = 0; + do { + ptT = ptT->next(); + if (ptT == check) { + return true; + } + ++links; + const SkOpPtT* test = this; + for (int index = 0; index < links; ++index) { + if (ptT == test) { + return false; + } + test = test->next(); + } + } while (true); +} + +const SkOpPtT* SkOpPtT::debugContains(const SkOpSegment* check) const { + SkASSERT(this->segment() != check); + const SkOpPtT* ptT = this; + int links = 0; + do { + ptT = ptT->next(); + if (ptT->segment() == check) { + return ptT; + } + ++links; + const SkOpPtT* test = this; + for (int index = 0; index < links; ++index) { + if (ptT == test) { + return nullptr; + } + test = test->next(); + } + } while (true); +} + +const SkOpPtT* SkOpPtT::debugEnder(const SkOpPtT* end) const { + return fT < end->fT ? end : this; +} + +int SkOpPtT::debugLoopLimit(bool report) const { + int loop = 0; + const SkOpPtT* next = this; + do { + for (int check = 1; check < loop - 1; ++check) { + const SkOpPtT* checkPtT = this->fNext; + const SkOpPtT* innerPtT = checkPtT; + for (int inner = check + 1; inner < loop; ++inner) { + innerPtT = innerPtT->fNext; + if (checkPtT == innerPtT) { + if (report) { + SkDebugf("*** bad ptT loop ***\n"); + } + return loop; + } + } + } + // there's nothing wrong with extremely large loop counts -- but this may appear to hang + // by taking a very long time to figure out that no loop entry is a duplicate + // -- and it's likely that a large loop count is indicative of a bug somewhere + if (++loop > 1000) { + SkDebugf("*** loop count exceeds 1000 ***\n"); + return 1000; + } + } while ((next = next->fNext) && next != this); + return 0; +} + +const SkOpPtT* SkOpPtT::debugOppPrev(const SkOpPtT* opp) const { + return this->oppPrev(const_cast(opp)); +} + +void SkOpPtT::debugResetCoinT() const { +#if DEBUG_COINCIDENCE_ORDER + this->segment()->debugResetCoinT(); +#endif +} + +void SkOpPtT::debugSetCoinT(int index) const { +#if DEBUG_COINCIDENCE_ORDER + this->segment()->debugSetCoinT(index, fT); +#endif +} + +void SkOpPtT::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + SkOpPhase phase = contour()->globalState()->phase(); + if (phase == SkOpPhase::kIntersecting || phase == SkOpPhase::kFixWinding) { + return; + } + SkASSERT(fNext); + SkASSERT(fNext != this); + SkASSERT(fNext->fNext); + SkASSERT(debugLoopLimit(false) == 0); +#endif +} + +static void output_scalar(SkScalar num) { + if (num == (int) num) { + SkDebugf("%d", (int) num); + } else { + SkString str; + str.printf("%1.9g", num); + int width = (int) str.size(); + const char* cStr = str.c_str(); + while (cStr[width - 1] == '0') { + --width; + } + str.resize(width); + SkDebugf("%sf", str.c_str()); + } +} + +static void output_points(const SkPoint* pts, int count) { + for (int index = 0; index < count; ++index) { + output_scalar(pts[index].fX); + SkDebugf(", "); + output_scalar(pts[index].fY); + if (index + 1 < count) { + SkDebugf(", "); + } + } +} + +static void showPathContours(SkPath::RawIter& iter, const char* pathName) { + uint8_t verb; + SkPoint pts[4]; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + SkDebugf(" %s.moveTo(", pathName); + output_points(&pts[0], 1); + SkDebugf(");\n"); + continue; + case SkPath::kLine_Verb: + SkDebugf(" %s.lineTo(", pathName); + output_points(&pts[1], 1); + SkDebugf(");\n"); + break; + case SkPath::kQuad_Verb: + SkDebugf(" %s.quadTo(", pathName); + output_points(&pts[1], 2); + SkDebugf(");\n"); + break; + case SkPath::kConic_Verb: + SkDebugf(" %s.conicTo(", pathName); + output_points(&pts[1], 2); + SkDebugf(", %1.9gf);\n", iter.conicWeight()); + break; + case SkPath::kCubic_Verb: + SkDebugf(" %s.cubicTo(", pathName); + output_points(&pts[1], 3); + SkDebugf(");\n"); + break; + case SkPath::kClose_Verb: + SkDebugf(" %s.close();\n", pathName); + break; + default: + SkDEBUGFAIL("bad verb"); + return; + } + } +} + +static const char* gFillTypeStr[] = { + "kWinding_FillType", + "kEvenOdd_FillType", + "kInverseWinding_FillType", + "kInverseEvenOdd_FillType" +}; + +void SkPathOpsDebug::ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration) { + SkPath::RawIter iter(path); +#define SUPPORT_RECT_CONTOUR_DETECTION 0 +#if SUPPORT_RECT_CONTOUR_DETECTION + int rectCount = path.isRectContours() ? path.rectContours(nullptr, nullptr) : 0; + if (rectCount > 0) { + SkTDArray rects; + SkTDArray directions; + rects.setCount(rectCount); + directions.setCount(rectCount); + path.rectContours(rects.begin(), directions.begin()); + for (int contour = 0; contour < rectCount; ++contour) { + const SkRect& rect = rects[contour]; + SkDebugf("path.addRect(%1.9g, %1.9g, %1.9g, %1.9g, %s);\n", rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom, directions[contour] == SkPath::kCCW_Direction + ? "SkPath::kCCW_Direction" : "SkPath::kCW_Direction"); + } + return; + } +#endif + SkPath::FillType fillType = path.getFillType(); + SkASSERT(fillType >= SkPath::kWinding_FillType && fillType <= SkPath::kInverseEvenOdd_FillType); + if (includeDeclaration) { + SkDebugf(" SkPath %s;\n", name); + } + SkDebugf(" %s.setFillType(SkPath::%s);\n", name, gFillTypeStr[fillType]); + iter.setPath(path); + showPathContours(iter, name); +} + +#if DEBUG_DUMP_VERIFY +#include "include/core/SkData.h" +#include "include/core/SkStream.h" + +static void dump_path(FILE* file, const SkPath& path, bool force, bool dumpAsHex) { + SkDynamicMemoryWStream wStream; + path.dump(&wStream, force, dumpAsHex); + sk_sp data(wStream.detachAsData()); + fprintf(file, "%.*s\n", (int) data->size(), (char*) data->data()); +} + +static int dumpID = 0; + +void SkPathOpsDebug::DumpOp(const SkPath& one, const SkPath& two, SkPathOp op, + const char* testName) { + FILE* file = sk_fopen("op_dump.txt", kWrite_SkFILE_Flag); + DumpOp(file, one, two, op, testName); +} + +void SkPathOpsDebug::DumpOp(FILE* file, const SkPath& one, const SkPath& two, SkPathOp op, + const char* testName) { + const char* name = testName ? testName : "op"; + fprintf(file, + "\nstatic void %s_%d(skiatest::Reporter* reporter, const char* filename) {\n", + name, ++dumpID); + fprintf(file, " SkPath path;\n"); + fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", one.getFillType()); + dump_path(file, one, false, true); + fprintf(file, " SkPath path1(path);\n"); + fprintf(file, " path.reset();\n"); + fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", two.getFillType()); + dump_path(file, two, false, true); + fprintf(file, " SkPath path2(path);\n"); + fprintf(file, " testPathOp(reporter, path1, path2, (SkPathOp) %d, filename);\n", op); + fprintf(file, "}\n\n"); + fclose(file); +} + +void SkPathOpsDebug::DumpSimplify(const SkPath& path, const char* testName) { + FILE* file = sk_fopen("simplify_dump.txt", kWrite_SkFILE_Flag); + DumpSimplify(file, path, testName); +} + +void SkPathOpsDebug::DumpSimplify(FILE* file, const SkPath& path, const char* testName) { + const char* name = testName ? testName : "simplify"; + fprintf(file, + "\nstatic void %s_%d(skiatest::Reporter* reporter, const char* filename) {\n", + name, ++dumpID); + fprintf(file, " SkPath path;\n"); + fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", path.getFillType()); + dump_path(file, path, false, true); + fprintf(file, " testSimplify(reporter, path, filename);\n"); + fprintf(file, "}\n\n"); + fclose(file); +} + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkPaint.h" + +const int bitWidth = 64; +const int bitHeight = 64; + +static void debug_scale_matrix(const SkPath& one, const SkPath* two, SkMatrix& scale) { + SkRect larger = one.getBounds(); + if (two) { + larger.join(two->getBounds()); + } + SkScalar largerWidth = larger.width(); + if (largerWidth < 4) { + largerWidth = 4; + } + SkScalar largerHeight = larger.height(); + if (largerHeight < 4) { + largerHeight = 4; + } + SkScalar hScale = (bitWidth - 2) / largerWidth; + SkScalar vScale = (bitHeight - 2) / largerHeight; + scale.reset(); + scale.preScale(hScale, vScale); + larger.fLeft *= hScale; + larger.fRight *= hScale; + larger.fTop *= vScale; + larger.fBottom *= vScale; + SkScalar dx = -16000 > larger.fLeft ? -16000 - larger.fLeft + : 16000 < larger.fRight ? 16000 - larger.fRight : 0; + SkScalar dy = -16000 > larger.fTop ? -16000 - larger.fTop + : 16000 < larger.fBottom ? 16000 - larger.fBottom : 0; + scale.preTranslate(dx, dy); +} + +static int debug_paths_draw_the_same(const SkPath& one, const SkPath& two, SkBitmap& bits) { + if (bits.width() == 0) { + bits.allocN32Pixels(bitWidth * 2, bitHeight); + } + SkCanvas canvas(bits); + canvas.drawColor(SK_ColorWHITE); + SkPaint paint; + canvas.save(); + const SkRect& bounds1 = one.getBounds(); + canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1); + canvas.drawPath(one, paint); + canvas.restore(); + canvas.save(); + canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1); + canvas.drawPath(two, paint); + canvas.restore(); + int errors = 0; + for (int y = 0; y < bitHeight - 1; ++y) { + uint32_t* addr1 = bits.getAddr32(0, y); + uint32_t* addr2 = bits.getAddr32(0, y + 1); + uint32_t* addr3 = bits.getAddr32(bitWidth, y); + uint32_t* addr4 = bits.getAddr32(bitWidth, y + 1); + for (int x = 0; x < bitWidth - 1; ++x) { + // count 2x2 blocks + bool err = addr1[x] != addr3[x]; + if (err) { + errors += addr1[x + 1] != addr3[x + 1] + && addr2[x] != addr4[x] && addr2[x + 1] != addr4[x + 1]; + } + } + } + return errors; +} + +void SkPathOpsDebug::ReportOpFail(const SkPath& one, const SkPath& two, SkPathOp op) { + SkDebugf("// Op did not expect failure\n"); + DumpOp(stderr, one, two, op, "opTest"); + fflush(stderr); +} + +void SkPathOpsDebug::VerifyOp(const SkPath& one, const SkPath& two, SkPathOp op, + const SkPath& result) { + SkPath pathOut, scaledPathOut; + SkRegion rgnA, rgnB, openClip, rgnOut; + openClip.setRect(-16000, -16000, 16000, 16000); + rgnA.setPath(one, openClip); + rgnB.setPath(two, openClip); + rgnOut.op(rgnA, rgnB, (SkRegion::Op) op); + rgnOut.getBoundaryPath(&pathOut); + SkMatrix scale; + debug_scale_matrix(one, &two, scale); + SkRegion scaledRgnA, scaledRgnB, scaledRgnOut; + SkPath scaledA, scaledB; + scaledA.addPath(one, scale); + scaledA.setFillType(one.getFillType()); + scaledB.addPath(two, scale); + scaledB.setFillType(two.getFillType()); + scaledRgnA.setPath(scaledA, openClip); + scaledRgnB.setPath(scaledB, openClip); + scaledRgnOut.op(scaledRgnA, scaledRgnB, (SkRegion::Op) op); + scaledRgnOut.getBoundaryPath(&scaledPathOut); + SkBitmap bitmap; + SkPath scaledOut; + scaledOut.addPath(result, scale); + scaledOut.setFillType(result.getFillType()); + int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); + const int MAX_ERRORS = 9; + if (errors > MAX_ERRORS) { + fprintf(stderr, "// Op did not expect errors=%d\n", errors); + DumpOp(stderr, one, two, op, "opTest"); + fflush(stderr); + } +} + +void SkPathOpsDebug::ReportSimplifyFail(const SkPath& path) { + SkDebugf("// Simplify did not expect failure\n"); + DumpSimplify(stderr, path, "simplifyTest"); + fflush(stderr); +} + +void SkPathOpsDebug::VerifySimplify(const SkPath& path, const SkPath& result) { + SkPath pathOut, scaledPathOut; + SkRegion rgnA, openClip, rgnOut; + openClip.setRect(-16000, -16000, 16000, 16000); + rgnA.setPath(path, openClip); + rgnOut.getBoundaryPath(&pathOut); + SkMatrix scale; + debug_scale_matrix(path, nullptr, scale); + SkRegion scaledRgnA; + SkPath scaledA; + scaledA.addPath(path, scale); + scaledA.setFillType(path.getFillType()); + scaledRgnA.setPath(scaledA, openClip); + scaledRgnA.getBoundaryPath(&scaledPathOut); + SkBitmap bitmap; + SkPath scaledOut; + scaledOut.addPath(result, scale); + scaledOut.setFillType(result.getFillType()); + int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); + const int MAX_ERRORS = 9; + if (errors > MAX_ERRORS) { + fprintf(stderr, "// Simplify did not expect errors=%d\n", errors); + DumpSimplify(stderr, path, "simplifyTest"); + fflush(stderr); + } +} + +#endif + +// global path dumps for msvs Visual Studio 17 to use from Immediate Window +void Dump(const SkPath& path) { + path.dump(); +} + +void DumpHex(const SkPath& path) { + path.dumpHex(); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsDebug.h b/gfx/skia/skia/src/pathops/SkPathOpsDebug.h new file mode 100644 index 0000000000..dca0102443 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsDebug.h @@ -0,0 +1,495 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsDebug_DEFINED +#define SkPathOpsDebug_DEFINED + +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/pathops/SkPathOps.h" + +#include +#include + +enum class SkOpPhase : char; +struct SkDQuad; +class SkOpAngle; +class SkOpCoincidence; +class SkOpContour; +class SkOpContourHead; +class SkOpPtT; +class SkOpSegment; +class SkOpSpan; +class SkOpSpanBase; +struct SkDPoint; +struct SkDLine; +struct SkDQuad; +struct SkDConic; +struct SkDCubic; +class SkTSect; + +// define this when running fuzz +// #define IS_FUZZING_WITH_LIBFUZZER + +// dummy classes to fool msvs Visual Studio 2018 Immediate Window +#define DummyClasses(a, b) \ +class SkDebugTCoincident##a##b; \ +class SkDebugTSect##a##b; \ +class SkDebugTSpan##a##b + +DummyClasses(Quad, Quad); +DummyClasses(Conic, Quad); +DummyClasses(Conic, Conic); +DummyClasses(Cubic, Quad); +DummyClasses(Cubic, Conic); +DummyClasses(Cubic, Cubic); + +#undef DummyClasses + +#ifdef SK_RELEASE +#define FORCE_RELEASE 1 +#else +#define FORCE_RELEASE 1 // set force release to 1 for multiple thread -- no debugging +#endif + +#define DEBUG_UNDER_DEVELOPMENT 0 + +#define ONE_OFF_DEBUG 0 +#define ONE_OFF_DEBUG_MATHEMATICA 0 + +#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_ANDROID) + #define SK_RAND(seed) rand() +#else + #define SK_RAND(seed) rand_r(&seed) +#endif +#ifdef SK_BUILD_FOR_WIN + #define SK_SNPRINTF _snprintf +#else + #define SK_SNPRINTF snprintf +#endif + +#define WIND_AS_STRING(x) char x##Str[12]; \ + if (!SkPathOpsDebug::ValidWind(x)) strcpy(x##Str, "?"); \ + else SK_SNPRINTF(x##Str, sizeof(x##Str), "%d", x) + +#if FORCE_RELEASE + +#define DEBUG_ACTIVE_OP 0 +#define DEBUG_ACTIVE_SPANS 0 +#define DEBUG_ADD_INTERSECTING_TS 0 +#define DEBUG_ADD_T 0 +#define DEBUG_ALIGNMENT 0 +#define DEBUG_ANGLE 0 +#define DEBUG_ASSEMBLE 0 +#define DEBUG_COINCIDENCE 0 // sanity checking +#define DEBUG_COINCIDENCE_DUMP 0 // accumulate and dump which algorithms fired +#define DEBUG_COINCIDENCE_ORDER 0 // for well behaved curves, check if pairs match up in t-order +#define DEBUG_COINCIDENCE_VERBOSE 0 // usually whether the next function generates coincidence +#define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_CUBIC_SPLIT 0 +#define DEBUG_DUMP_SEGMENTS 0 +#define DEBUG_DUMP_VERIFY 0 +#define DEBUG_FLOW 0 +#define DEBUG_LIMIT_WIND_SUM 0 +#define DEBUG_MARK_DONE 0 +#define DEBUG_PATH_CONSTRUCTION 0 +#define DEBUG_PERP 0 +#define DEBUG_SHOW_TEST_NAME 0 +#define DEBUG_SORT 0 +#define DEBUG_T_SECT 0 +#define DEBUG_T_SECT_DUMP 0 +#define DEBUG_T_SECT_LOOP_COUNT 0 +#define DEBUG_VALIDATE 0 +#define DEBUG_WINDING 0 +#define DEBUG_WINDING_AT_T 0 + +#else + +#define DEBUG_ACTIVE_OP 1 +#define DEBUG_ACTIVE_SPANS 1 +#define DEBUG_ADD_INTERSECTING_TS 1 +#define DEBUG_ADD_T 1 +#define DEBUG_ALIGNMENT 0 +#define DEBUG_ANGLE 1 +#define DEBUG_ASSEMBLE 1 +#define DEBUG_COINCIDENCE 1 +#define DEBUG_COINCIDENCE_DUMP 0 +#define DEBUG_COINCIDENCE_ORDER 0 // tight arc quads may generate out-of-order coincidence spans +#define DEBUG_COINCIDENCE_VERBOSE 1 +#define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_CUBIC_SPLIT 1 +#define DEBUG_DUMP_VERIFY 0 +#define DEBUG_DUMP_SEGMENTS 1 +#define DEBUG_FLOW 1 +#define DEBUG_LIMIT_WIND_SUM 15 +#define DEBUG_MARK_DONE 1 +#define DEBUG_PATH_CONSTRUCTION 1 +#define DEBUG_PERP 1 +#define DEBUG_SHOW_TEST_NAME 1 +#define DEBUG_SORT 1 +#define DEBUG_T_SECT 0 // enabling may trigger validate asserts even though op does not fail +#define DEBUG_T_SECT_DUMP 0 // Use 1 normally. Use 2 to number segments, 3 for script output +#define DEBUG_T_SECT_LOOP_COUNT 0 +#define DEBUG_VALIDATE 1 +#define DEBUG_WINDING 1 +#define DEBUG_WINDING_AT_T 1 + +#endif + +#ifdef SK_RELEASE + #define SkDEBUGRELEASE(a, b) b + #define SkDEBUGPARAMS(...) +#else + #define SkDEBUGRELEASE(a, b) a + #define SkDEBUGPARAMS(...) , __VA_ARGS__ +#endif + +#if DEBUG_VALIDATE == 0 + #define PATH_OPS_DEBUG_VALIDATE_PARAMS(...) +#else + #define PATH_OPS_DEBUG_VALIDATE_PARAMS(...) , __VA_ARGS__ +#endif + +#if DEBUG_T_SECT == 0 + #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) b + #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) + #define PATH_OPS_DEBUG_T_SECT_CODE(...) +#else + #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) a + #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) , __VA_ARGS__ + #define PATH_OPS_DEBUG_T_SECT_CODE(...) __VA_ARGS__ +#endif + +#if DEBUG_T_SECT_DUMP > 1 + extern int gDumpTSectNum; +#endif + +#if DEBUG_COINCIDENCE || DEBUG_COINCIDENCE_DUMP + #define DEBUG_COIN 1 +#else + #define DEBUG_COIN 0 +#endif + +#if DEBUG_COIN + #define DEBUG_COIN_DECLARE_ONLY_PARAMS() \ + int lineNo, SkOpPhase phase, int iteration + #define DEBUG_COIN_DECLARE_PARAMS() \ + , DEBUG_COIN_DECLARE_ONLY_PARAMS() + #define DEBUG_COIN_ONLY_PARAMS() \ + __LINE__, SkOpPhase::kNoChange, 0 + #define DEBUG_COIN_PARAMS() \ + , DEBUG_COIN_ONLY_PARAMS() + #define DEBUG_ITER_ONLY_PARAMS(iteration) \ + __LINE__, SkOpPhase::kNoChange, iteration + #define DEBUG_ITER_PARAMS(iteration) \ + , DEBUG_ITER_ONLY_PARAMS(iteration) + #define DEBUG_PHASE_ONLY_PARAMS(phase) \ + __LINE__, SkOpPhase::phase, 0 + #define DEBUG_PHASE_PARAMS(phase) \ + , DEBUG_PHASE_ONLY_PARAMS(phase) + #define DEBUG_SET_PHASE() \ + this->globalState()->debugSetPhase(__func__, lineNo, phase, iteration) + #define DEBUG_STATIC_SET_PHASE(obj) \ + obj->globalState()->debugSetPhase(__func__, lineNo, phase, iteration) +#elif DEBUG_VALIDATE + #define DEBUG_COIN_DECLARE_ONLY_PARAMS() \ + SkOpPhase phase + #define DEBUG_COIN_DECLARE_PARAMS() \ + , DEBUG_COIN_DECLARE_ONLY_PARAMS() + #define DEBUG_COIN_ONLY_PARAMS() \ + SkOpPhase::kNoChange + #define DEBUG_COIN_PARAMS() \ + , DEBUG_COIN_ONLY_PARAMS() + #define DEBUG_ITER_ONLY_PARAMS(iteration) \ + SkOpPhase::kNoChange + #define DEBUG_ITER_PARAMS(iteration) \ + , DEBUG_ITER_ONLY_PARAMS(iteration) + #define DEBUG_PHASE_ONLY_PARAMS(phase) \ + SkOpPhase::phase + #define DEBUG_PHASE_PARAMS(phase) \ + , DEBUG_PHASE_ONLY_PARAMS(phase) + #define DEBUG_SET_PHASE() \ + this->globalState()->debugSetPhase(phase) + #define DEBUG_STATIC_SET_PHASE(obj) \ + obj->globalState()->debugSetPhase(phase) +#else + #define DEBUG_COIN_DECLARE_ONLY_PARAMS() + #define DEBUG_COIN_DECLARE_PARAMS() + #define DEBUG_COIN_ONLY_PARAMS() + #define DEBUG_COIN_PARAMS() + #define DEBUG_ITER_ONLY_PARAMS(iteration) + #define DEBUG_ITER_PARAMS(iteration) + #define DEBUG_PHASE_ONLY_PARAMS(phase) + #define DEBUG_PHASE_PARAMS(phase) + #define DEBUG_SET_PHASE() + #define DEBUG_STATIC_SET_PHASE(obj) +#endif + +#define CUBIC_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" +#define CONIC_DEBUG_STR "{{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}, %1.9g}" +#define QUAD_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" +#define LINE_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}" +#define PT_DEBUG_STR "{{%1.9g,%1.9g}}" + +#define T_DEBUG_STR(t, n) #t "[" #n "]=%1.9g" +#define TX_DEBUG_STR(t) #t "[%d]=%1.9g" +#define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY +#define CONIC_DEBUG_DATA(c, w) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, w +#define QUAD_DEBUG_DATA(q) q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY +#define LINE_DEBUG_DATA(l) l[0].fX, l[0].fY, l[1].fX, l[1].fY +#define PT_DEBUG_DATA(i, n) i.pt(n).asSkPoint().fX, i.pt(n).asSkPoint().fY + +#ifndef DEBUG_TEST +#define DEBUG_TEST 0 +#endif + +#if DEBUG_SHOW_TEST_NAME +#include "src/core/SkTLS.h" +#endif + +// Tests with extreme numbers may fail, but all other tests should never fail. +#define FAIL_IF(cond) \ + do { bool fail = (cond); SkOPASSERT(!fail); if (fail) return false; } while (false) + +#define FAIL_WITH_NULL_IF(cond) \ + do { bool fail = (cond); SkOPASSERT(!fail); if (fail) return nullptr; } while (false) + +// Some functions serve two masters: one allows the function to fail, the other expects success +// always. If abort is true, tests with normal numbers may not fail and assert if they do so. +// If abort is false, both normal and extreme numbers may return false without asserting. +#define RETURN_FALSE_IF(abort, cond) \ + do { bool fail = (cond); SkOPASSERT(!(abort) || !fail); if (fail) return false; \ + } while (false) + +class SkPathOpsDebug { +public: +#if DEBUG_COIN + struct GlitchLog; + + enum GlitchType { + kUninitialized_Glitch, + kAddCorruptCoin_Glitch, + kAddExpandedCoin_Glitch, + kAddExpandedFail_Glitch, + kAddIfCollapsed_Glitch, + kAddIfMissingCoin_Glitch, + kAddMissingCoin_Glitch, + kAddMissingExtend_Glitch, + kAddOrOverlap_Glitch, + kCollapsedCoin_Glitch, + kCollapsedDone_Glitch, + kCollapsedOppValue_Glitch, + kCollapsedSpan_Glitch, + kCollapsedWindValue_Glitch, + kCorrectEnd_Glitch, + kDeletedCoin_Glitch, + kExpandCoin_Glitch, + kFail_Glitch, + kMarkCoinEnd_Glitch, + kMarkCoinInsert_Glitch, + kMarkCoinMissing_Glitch, + kMarkCoinStart_Glitch, + kMergeMatches_Glitch, + kMissingCoin_Glitch, + kMissingDone_Glitch, + kMissingIntersection_Glitch, + kMoveMultiple_Glitch, + kMoveNearbyClearAll_Glitch, + kMoveNearbyClearAll2_Glitch, + kMoveNearbyMerge_Glitch, + kMoveNearbyMergeFinal_Glitch, + kMoveNearbyRelease_Glitch, + kMoveNearbyReleaseFinal_Glitch, + kReleasedSpan_Glitch, + kReturnFalse_Glitch, + kUnaligned_Glitch, + kUnalignedHead_Glitch, + kUnalignedTail_Glitch, + }; + + struct CoinDictEntry { + int fIteration; + int fLineNumber; + GlitchType fGlitchType; + const char* fFunctionName; + }; + + struct CoinDict { + void add(const CoinDictEntry& key); + void add(const CoinDict& dict); + void dump(const char* str, bool visitCheck) const; + SkTDArray fDict; + }; + + static CoinDict gCoinSumChangedDict; + static CoinDict gCoinSumVisitedDict; + static CoinDict gCoinVistedDict; +#endif + +#if defined(SK_DEBUG) || !FORCE_RELEASE + static int gContourID; + static int gSegmentID; +#endif + +#if DEBUG_SORT + static int gSortCountDefault; + static int gSortCount; +#endif + +#if DEBUG_ACTIVE_OP + static const char* kPathOpStr[]; +#endif + static bool gRunFail; + static bool gVeryVerbose; + +#if DEBUG_ACTIVE_SPANS + static SkString gActiveSpans; +#endif +#if DEBUG_DUMP_VERIFY + static bool gDumpOp; + static bool gVerifyOp; +#endif + + static const char* OpStr(SkPathOp ); + static void MathematicaIze(char* str, size_t bufferSize); + static bool ValidWind(int winding); + static void WindingPrintf(int winding); + +#if DEBUG_SHOW_TEST_NAME + static void* CreateNameStr(); + static void DeleteNameStr(void* v); +#define DEBUG_FILENAME_STRING_LENGTH 64 +#define DEBUG_FILENAME_STRING (reinterpret_cast(SkTLS::Get(SkPathOpsDebug::CreateNameStr, \ + SkPathOpsDebug::DeleteNameStr))) + static void BumpTestName(char* ); +#endif + static void ShowActiveSpans(SkOpContourHead* contourList); + static void ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration); + static void ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name); + + static bool ChaseContains(const SkTDArray& , const SkOpSpanBase* ); + + static void CheckHealth(class SkOpContourHead* contourList); + +#if DEBUG_COIN + static void DumpCoinDict(); + static void DumpGlitchType(GlitchType ); +#endif + +}; + +// Visual Studio 2017 does not permit calling member functions from the Immediate Window. +// Global functions work fine, however. Use globals rather than static members inside a class. +const SkOpAngle* AngleAngle(const SkOpAngle*, int id); +SkOpContour* AngleContour(SkOpAngle*, int id); +const SkOpPtT* AnglePtT(const SkOpAngle*, int id); +const SkOpSegment* AngleSegment(const SkOpAngle*, int id); +const SkOpSpanBase* AngleSpan(const SkOpAngle*, int id); + +const SkOpAngle* ContourAngle(SkOpContour*, int id); +SkOpContour* ContourContour(SkOpContour*, int id); +const SkOpPtT* ContourPtT(SkOpContour*, int id); +const SkOpSegment* ContourSegment(SkOpContour*, int id); +const SkOpSpanBase* ContourSpan(SkOpContour*, int id); + +const SkOpAngle* CoincidenceAngle(SkOpCoincidence*, int id); +SkOpContour* CoincidenceContour(SkOpCoincidence*, int id); +const SkOpPtT* CoincidencePtT(SkOpCoincidence*, int id); +const SkOpSegment* CoincidenceSegment(SkOpCoincidence*, int id); +const SkOpSpanBase* CoincidenceSpan(SkOpCoincidence*, int id); + +const SkOpAngle* PtTAngle(const SkOpPtT*, int id); +SkOpContour* PtTContour(SkOpPtT*, int id); +const SkOpPtT* PtTPtT(const SkOpPtT*, int id); +const SkOpSegment* PtTSegment(const SkOpPtT*, int id); +const SkOpSpanBase* PtTSpan(const SkOpPtT*, int id); + +const SkOpAngle* SegmentAngle(const SkOpSegment*, int id); +SkOpContour* SegmentContour(SkOpSegment*, int id); +const SkOpPtT* SegmentPtT(const SkOpSegment*, int id); +const SkOpSegment* SegmentSegment(const SkOpSegment*, int id); +const SkOpSpanBase* SegmentSpan(const SkOpSegment*, int id); + +const SkOpAngle* SpanAngle(const SkOpSpanBase*, int id); +SkOpContour* SpanContour(SkOpSpanBase*, int id); +const SkOpPtT* SpanPtT(const SkOpSpanBase*, int id); +const SkOpSegment* SpanSegment(const SkOpSpanBase*, int id); +const SkOpSpanBase* SpanSpan(const SkOpSpanBase*, int id); + +#if DEBUG_DUMP_VERIFY +void DumpOp(const SkPath& one, const SkPath& two, SkPathOp op, + const char* testName); +void DumpOp(FILE* file, const SkPath& one, const SkPath& two, SkPathOp op, + const char* testName); +void DumpSimplify(const SkPath& path, const char* testName); +void DumpSimplify(FILE* file, const SkPath& path, const char* testName); +void ReportOpFail(const SkPath& one, const SkPath& two, SkPathOp op); +void ReportSimplifyFail(const SkPath& path); +void VerifyOp(const SkPath& one, const SkPath& two, SkPathOp op, + const SkPath& result); +void VerifySimplify(const SkPath& path, const SkPath& result); +#endif + +// global path dumps for msvs Visual Studio 17 to use from Immediate Window +void Dump(const SkOpContour& ); +void DumpAll(const SkOpContour& ); +void DumpAngles(const SkOpContour& ); +void DumpContours(const SkOpContour& ); +void DumpContoursAll(const SkOpContour& ); +void DumpContoursAngles(const SkOpContour& ); +void DumpContoursPts(const SkOpContour& ); +void DumpContoursPt(const SkOpContour& , int segmentID); +void DumpContoursSegment(const SkOpContour& , int segmentID); +void DumpContoursSpan(const SkOpContour& , int segmentID); +void DumpContoursSpans(const SkOpContour& ); +void DumpPt(const SkOpContour& , int ); +void DumpPts(const SkOpContour& , const char* prefix = "seg"); +void DumpSegment(const SkOpContour& , int ); +void DumpSegments(const SkOpContour& , const char* prefix = "seg", SkPathOp op = (SkPathOp) -1); +void DumpSpan(const SkOpContour& , int ); +void DumpSpans(const SkOpContour& ); + +void Dump(const SkOpSegment& ); +void DumpAll(const SkOpSegment& ); +void DumpAngles(const SkOpSegment& ); +void DumpCoin(const SkOpSegment& ); +void DumpPts(const SkOpSegment& , const char* prefix = "seg"); + +void Dump(const SkOpPtT& ); +void DumpAll(const SkOpPtT& ); + +void Dump(const SkOpSpanBase& ); +void DumpCoin(const SkOpSpanBase& ); +void DumpAll(const SkOpSpanBase& ); + +void DumpCoin(const SkOpSpan& ); +bool DumpSpan(const SkOpSpan& ); + +void Dump(const SkDConic& ); +void DumpID(const SkDConic& , int id); + +void Dump(const SkDCubic& ); +void DumpID(const SkDCubic& , int id); + +void Dump(const SkDLine& ); +void DumpID(const SkDLine& , int id); + +void Dump(const SkDQuad& ); +void DumpID(const SkDQuad& , int id); + +void Dump(const SkDPoint& ); + +void Dump(const SkOpAngle& ); + +// generates tools/path_sorter.htm and path_visualizer.htm compatible data +void DumpQ(const SkDQuad& quad1, const SkDQuad& quad2, int testNo); +void DumpT(const SkDQuad& quad, double t); + +// global path dumps for msvs Visual Studio 17 to use from Immediate Window +void Dump(const SkPath& path); +void DumpHex(const SkPath& path); + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsLine.cpp b/gfx/skia/skia/src/pathops/SkPathOpsLine.cpp new file mode 100644 index 0000000000..9003547036 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsLine.cpp @@ -0,0 +1,149 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkPathOpsLine.h" + +SkDPoint SkDLine::ptAtT(double t) const { + if (0 == t) { + return fPts[0]; + } + if (1 == t) { + return fPts[1]; + } + double one_t = 1 - t; + SkDPoint result = { one_t * fPts[0].fX + t * fPts[1].fX, one_t * fPts[0].fY + t * fPts[1].fY }; + return result; +} + +double SkDLine::exactPoint(const SkDPoint& xy) const { + if (xy == fPts[0]) { // do cheapest test first + return 0; + } + if (xy == fPts[1]) { + return 1; + } + return -1; +} + +double SkDLine::nearPoint(const SkDPoint& xy, bool* unequal) const { + if (!AlmostBetweenUlps(fPts[0].fX, xy.fX, fPts[1].fX) + || !AlmostBetweenUlps(fPts[0].fY, xy.fY, fPts[1].fY)) { + return -1; + } + // project a perpendicular ray from the point to the line; find the T on the line + SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line + double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay + SkDVector ab0 = xy - fPts[0]; + double numer = len.fX * ab0.fX + ab0.fY * len.fY; + if (!between(0, numer, denom)) { + return -1; + } + if (!denom) { + return 0; + } + double t = numer / denom; + SkDPoint realPt = ptAtT(t); + double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? + // find the ordinal in the original line with the largest unsigned exponent + double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + largest = SkTMax(largest, -tiniest); + if (!AlmostEqualUlps_Pin(largest, largest + dist)) { // is the dist within ULPS tolerance? + return -1; + } + if (unequal) { + *unequal = (float) largest != (float) (largest + dist); + } + t = SkPinT(t); // a looser pin breaks skpwww_lptemp_com_3 + SkASSERT(between(0, t, 1)); + return t; +} + +bool SkDLine::nearRay(const SkDPoint& xy) const { + // project a perpendicular ray from the point to the line; find the T on the line + SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line + double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay + SkDVector ab0 = xy - fPts[0]; + double numer = len.fX * ab0.fX + ab0.fY * len.fY; + double t = numer / denom; + SkDPoint realPt = ptAtT(t); + double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? + // find the ordinal in the original line with the largest unsigned exponent + double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + largest = SkTMax(largest, -tiniest); + return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? +} + +double SkDLine::ExactPointH(const SkDPoint& xy, double left, double right, double y) { + if (xy.fY == y) { + if (xy.fX == left) { + return 0; + } + if (xy.fX == right) { + return 1; + } + } + return -1; +} + +double SkDLine::NearPointH(const SkDPoint& xy, double left, double right, double y) { + if (!AlmostBequalUlps(xy.fY, y)) { + return -1; + } + if (!AlmostBetweenUlps(left, xy.fX, right)) { + return -1; + } + double t = (xy.fX - left) / (right - left); + t = SkPinT(t); + SkASSERT(between(0, t, 1)); + double realPtX = (1 - t) * left + t * right; + SkDVector distU = {xy.fY - y, xy.fX - realPtX}; + double distSq = distU.fX * distU.fX + distU.fY * distU.fY; + double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(y, left), right); + double largest = SkTMax(SkTMax(y, left), right); + largest = SkTMax(largest, -tiniest); + if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? + return -1; + } + return t; +} + +double SkDLine::ExactPointV(const SkDPoint& xy, double top, double bottom, double x) { + if (xy.fX == x) { + if (xy.fY == top) { + return 0; + } + if (xy.fY == bottom) { + return 1; + } + } + return -1; +} + +double SkDLine::NearPointV(const SkDPoint& xy, double top, double bottom, double x) { + if (!AlmostBequalUlps(xy.fX, x)) { + return -1; + } + if (!AlmostBetweenUlps(top, xy.fY, bottom)) { + return -1; + } + double t = (xy.fY - top) / (bottom - top); + t = SkPinT(t); + SkASSERT(between(0, t, 1)); + double realPtY = (1 - t) * top + t * bottom; + SkDVector distU = {xy.fX - x, xy.fY - realPtY}; + double distSq = distU.fX * distU.fX + distU.fY * distU.fY; + double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(x, top), bottom); + double largest = SkTMax(SkTMax(x, top), bottom); + largest = SkTMax(largest, -tiniest); + if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? + return -1; + } + return t; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsLine.h b/gfx/skia/skia/src/pathops/SkPathOpsLine.h new file mode 100644 index 0000000000..0c6c7ea6e4 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsLine.h @@ -0,0 +1,39 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsLine_DEFINED +#define SkPathOpsLine_DEFINED + +#include "src/pathops/SkPathOpsPoint.h" + +struct SkDLine { + SkDPoint fPts[2]; + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 2); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 2); return fPts[n]; } + + const SkDLine& set(const SkPoint pts[2]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + return *this; + } + + double exactPoint(const SkDPoint& xy) const; + static double ExactPointH(const SkDPoint& xy, double left, double right, double y); + static double ExactPointV(const SkDPoint& xy, double top, double bottom, double x); + + double nearPoint(const SkDPoint& xy, bool* unequal) const; + bool nearRay(const SkDPoint& xy) const; + static double NearPointH(const SkDPoint& xy, double left, double right, double y); + static double NearPointV(const SkDPoint& xy, double top, double bottom, double x); + SkDPoint ptAtT(double t) const; + + void dump() const; + void dumpID(int ) const; + void dumpInner() const; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsOp.cpp b/gfx/skia/skia/src/pathops/SkPathOpsOp.cpp new file mode 100644 index 0000000000..825ae7b197 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsOp.cpp @@ -0,0 +1,382 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkAddIntersections.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkPathOpsCommon.h" +#include "src/pathops/SkPathWriter.h" + +#include + +static bool findChaseOp(SkTDArray& chase, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, SkOpSegment** result) { + while (chase.count()) { + SkOpSpanBase* span; + chase.pop(&span); + // OPTIMIZE: prev makes this compatible with old code -- but is it necessary? + *startPtr = span->ptT()->prev()->span(); + SkOpSegment* segment = (*startPtr)->segment(); + bool done = true; + *endPtr = nullptr; + if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) { + *startPtr = last->start(); + *endPtr = last->end(); + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + *result = last->segment(); + return true; + } + if (done) { + continue; + } + int winding; + bool sortable; + const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable); + if (!angle) { + *result = nullptr; + return true; + } + if (winding == SK_MinS32) { + continue; + } + int sumMiWinding, sumSuWinding; + if (sortable) { + segment = angle->segment(); + sumMiWinding = segment->updateWindingReverse(angle); + if (sumMiWinding == SK_MinS32) { + SkASSERT(segment->globalState()->debugSkipAssert()); + *result = nullptr; + return true; + } + sumSuWinding = segment->updateOppWindingReverse(angle); + if (sumSuWinding == SK_MinS32) { + SkASSERT(segment->globalState()->debugSkipAssert()); + *result = nullptr; + return true; + } + if (segment->operand()) { + using std::swap; + swap(sumMiWinding, sumSuWinding); + } + } + SkOpSegment* first = nullptr; + const SkOpAngle* firstAngle = angle; + while ((angle = angle->next()) != firstAngle) { + segment = angle->segment(); + SkOpSpanBase* start = angle->start(); + SkOpSpanBase* end = angle->end(); + int maxWinding = 0, sumWinding = 0, oppMaxWinding = 0, oppSumWinding = 0; + if (sortable) { + segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding, + &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + } + if (!segment->done(angle)) { + if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) { + first = segment; + *startPtr = start; + *endPtr = end; + } + // OPTIMIZATION: should this also add to the chase? + if (sortable) { + if (!segment->markAngle(maxWinding, sumWinding, oppMaxWinding, + oppSumWinding, angle, nullptr)) { + return false; + } + } + } + } + if (first) { + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + *result = first; + return true; + } + } + *result = nullptr; + return true; +} + +static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op, + const int xorMask, const int xorOpMask, SkPathWriter* writer) { + bool unsortable = false; + bool lastSimple = false; + bool simple = false; + do { + SkOpSpan* span = FindSortableTop(contourList); + if (!span) { + break; + } + SkOpSegment* current = span->segment(); + SkOpSpanBase* start = span->next(); + SkOpSpanBase* end = span; + SkTDArray chase; + do { + if (current->activeOp(start, end, xorMask, xorOpMask, op)) { + do { + if (!unsortable && current->done()) { + break; + } + SkASSERT(unsortable || !current->done()); + SkOpSpanBase* nextStart = start; + SkOpSpanBase* nextEnd = end; + lastSimple = simple; + SkOpSegment* next = current->findNextOp(&chase, &nextStart, &nextEnd, + &unsortable, &simple, op, xorMask, xorOpMask); + if (!next) { + if (!unsortable && writer->hasMove() + && current->verb() != SkPath::kLine_Verb + && !writer->isClosed()) { + if (!current->addCurveTo(start, end, writer)) { + return false; + } + if (!writer->isClosed()) { + SkPathOpsDebug::ShowActiveSpans(contourList); + } + } else if (lastSimple) { + if (!current->addCurveTo(start, end, writer)) { + return false; + } + } + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), start->pt().fX, start->pt().fY, + end->pt().fX, end->pt().fY); + #endif + if (!current->addCurveTo(start, end, writer)) { + return false; + } + current = next; + start = nextStart; + end = nextEnd; + } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); + if (current->activeWinding(start, end) && !writer->isClosed()) { + SkOpSpan* spanStart = start->starter(end); + if (!spanStart->done()) { + if (!current->addCurveTo(start, end, writer)) { + return false; + } + current->markDone(spanStart); + } + } + writer->finishContour(); + } else { + SkOpSpanBase* last; + if (!current->markAndChaseDone(start, end, &last)) { + return false; + } + if (last && !last->chased()) { + last->setChased(true); + SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); + *chase.append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); + if (!last->final()) { + SkDebugf(" windSum=%d", last->upCast()->windSum()); + } + SkDebugf("\n"); +#endif + } + } + if (!findChaseOp(chase, &start, &end, ¤t)) { + return false; + } + SkPathOpsDebug::ShowActiveSpans(contourList); + if (!current) { + break; + } + } while (true); + } while (true); + return true; +} + +// diagram of why this simplifcation is possible is here: +// https://skia.org/dev/present/pathops link at bottom of the page +// https://drive.google.com/file/d/0BwoLUwz9PYkHLWpsaXd0UDdaN00/view?usp=sharing +static const SkPathOp gOpInverse[kReverseDifference_SkPathOp + 1][2][2] = { +// inside minuend outside minuend +// inside subtrahend outside subtrahend inside subtrahend outside subtrahend +{{ kDifference_SkPathOp, kIntersect_SkPathOp }, { kUnion_SkPathOp, kReverseDifference_SkPathOp }}, +{{ kIntersect_SkPathOp, kDifference_SkPathOp }, { kReverseDifference_SkPathOp, kUnion_SkPathOp }}, +{{ kUnion_SkPathOp, kReverseDifference_SkPathOp }, { kDifference_SkPathOp, kIntersect_SkPathOp }}, +{{ kXOR_SkPathOp, kXOR_SkPathOp }, { kXOR_SkPathOp, kXOR_SkPathOp }}, +{{ kReverseDifference_SkPathOp, kUnion_SkPathOp }, { kIntersect_SkPathOp, kDifference_SkPathOp }}, +}; + +static const bool gOutInverse[kReverseDifference_SkPathOp + 1][2][2] = { + {{ false, false }, { true, false }}, // diff + {{ false, false }, { false, true }}, // sect + {{ false, true }, { true, true }}, // union + {{ false, true }, { true, false }}, // xor + {{ false, true }, { false, false }}, // rev diff +}; + +#if DEBUG_T_SECT_LOOP_COUNT + +#include "include/private/SkMutex.h" + +SkOpGlobalState debugWorstState(nullptr, nullptr SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); + +void ReportPathOpsDebugging() { + debugWorstState.debugLoopReport(); +} + +extern void (*gVerboseFinalize)(); + +#endif + +bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result + SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { +#if DEBUG_DUMP_VERIFY +#ifndef SK_DEBUG + const char* testName = "release"; +#endif + if (SkPathOpsDebug::gDumpOp) { + SkPathOpsDebug::DumpOp(one, two, op, testName); + } +#endif + op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()]; + bool inverseFill = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]; + SkPath::FillType fillType = inverseFill ? SkPath::kInverseEvenOdd_FillType : + SkPath::kEvenOdd_FillType; + SkRect rect1, rect2; + if (kIntersect_SkPathOp == op && one.isRect(&rect1) && two.isRect(&rect2)) { + result->reset(); + result->setFillType(fillType); + if (rect1.intersect(rect2)) { + result->addRect(rect1); + } + return true; + } + if (one.isEmpty() || two.isEmpty()) { + SkPath work; + switch (op) { + case kIntersect_SkPathOp: + break; + case kUnion_SkPathOp: + case kXOR_SkPathOp: + work = one.isEmpty() ? two : one; + break; + case kDifference_SkPathOp: + if (!one.isEmpty()) { + work = one; + } + break; + case kReverseDifference_SkPathOp: + if (!two.isEmpty()) { + work = two; + } + break; + default: + SkASSERT(0); // unhandled case + } + if (inverseFill != work.isInverseFillType()) { + work.toggleInverseFillType(); + } + return Simplify(work, result); + } + SkSTArenaAlloc<4096> allocator; // FIXME: add a constant expression here, tune + SkOpContour contour; + SkOpContourHead* contourList = static_cast(&contour); + SkOpGlobalState globalState(contourList, &allocator + SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpCoincidence coincidence(&globalState); + const SkPath* minuend = &one; + const SkPath* subtrahend = &two; + if (op == kReverseDifference_SkPathOp) { + using std::swap; + swap(minuend, subtrahend); + op = kDifference_SkPathOp; + } +#if DEBUG_SORT + SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; +#endif + // turn path into list of segments + SkOpEdgeBuilder builder(*minuend, contourList, &globalState); + if (builder.unparseable()) { + return false; + } + const int xorMask = builder.xorMask(); + builder.addOperand(*subtrahend); + if (!builder.finish()) { + return false; + } +#if DEBUG_DUMP_SEGMENTS + contourList->dumpSegments("seg", op); +#endif + + const int xorOpMask = builder.xorMask(); + if (!SortContourList(&contourList, xorMask == kEvenOdd_PathOpsMask, + xorOpMask == kEvenOdd_PathOpsMask)) { + result->reset(); + result->setFillType(fillType); + return true; + } + // find all intersections between segments + SkOpContour* current = contourList; + do { + SkOpContour* next = current; + while (AddIntersectTs(current, next, &coincidence) + && (next = next->next())) + ; + } while ((current = current->next())); +#if DEBUG_VALIDATE + globalState.setPhase(SkOpPhase::kWalking); +#endif + bool success = HandleCoincidence(contourList, &coincidence); +#if DEBUG_COIN + globalState.debugAddToGlobalCoinDicts(); +#endif + if (!success) { + return false; + } +#if DEBUG_ALIGNMENT + contourList->dumpSegments("aligned"); +#endif + // construct closed contours + SkPath original = *result; + result->reset(); + result->setFillType(fillType); + SkPathWriter wrapper(*result); + if (!bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper)) { + *result = original; + return false; + } + wrapper.assemble(); // if some edges could not be resolved, assemble remaining +#if DEBUG_T_SECT_LOOP_COUNT + static SkMutex& debugWorstLoop = *(new SkMutex); + { + SkAutoMutexExclusive autoM(debugWorstLoop); + if (!gVerboseFinalize) { + gVerboseFinalize = &ReportPathOpsDebugging; + } + debugWorstState.debugDoYourWorst(&globalState); + } +#endif + return true; +} + +bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { +#if DEBUG_DUMP_VERIFY + if (SkPathOpsDebug::gVerifyOp) { + if (!OpDebug(one, two, op, result SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr))) { + SkPathOpsDebug::ReportOpFail(one, two, op); + return false; + } + SkPathOpsDebug::VerifyOp(one, two, op, *result); + return true; + } +#endif + return OpDebug(one, two, op, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsPoint.h b/gfx/skia/skia/src/pathops/SkPathOpsPoint.h new file mode 100644 index 0000000000..bca0530fed --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsPoint.h @@ -0,0 +1,279 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsPoint_DEFINED +#define SkPathOpsPoint_DEFINED + +#include "include/core/SkPoint.h" +#include "src/pathops/SkPathOpsTypes.h" + +inline bool AlmostEqualUlps(const SkPoint& pt1, const SkPoint& pt2) { + return AlmostEqualUlps(pt1.fX, pt2.fX) && AlmostEqualUlps(pt1.fY, pt2.fY); +} + +struct SkDVector { + double fX; + double fY; + + SkDVector& set(const SkVector& pt) { + fX = pt.fX; + fY = pt.fY; + return *this; + } + + // only used by testing + void operator+=(const SkDVector& v) { + fX += v.fX; + fY += v.fY; + } + + // only called by nearestT, which is currently only used by testing + void operator-=(const SkDVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + // only used by testing + void operator/=(const double s) { + fX /= s; + fY /= s; + } + + // only used by testing + void operator*=(const double s) { + fX *= s; + fY *= s; + } + + SkVector asSkVector() const { + SkVector v = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; + return v; + } + + // only used by testing + double cross(const SkDVector& a) const { + return fX * a.fY - fY * a.fX; + } + + // similar to cross, this bastardization considers nearly coincident to be zero + // uses ulps epsilon == 16 + double crossCheck(const SkDVector& a) const { + double xy = fX * a.fY; + double yx = fY * a.fX; + return AlmostEqualUlps(xy, yx) ? 0 : xy - yx; + } + + // allow tinier numbers + double crossNoNormalCheck(const SkDVector& a) const { + double xy = fX * a.fY; + double yx = fY * a.fX; + return AlmostEqualUlpsNoNormalCheck(xy, yx) ? 0 : xy - yx; + } + + double dot(const SkDVector& a) const { + return fX * a.fX + fY * a.fY; + } + + double length() const { + return sqrt(lengthSquared()); + } + + double lengthSquared() const { + return fX * fX + fY * fY; + } + + SkDVector& normalize() { + double inverseLength = sk_ieee_double_divide(1, this->length()); + fX *= inverseLength; + fY *= inverseLength; + return *this; + } + + bool isFinite() const { + return std::isfinite(fX) && std::isfinite(fY); + } +}; + +struct SkDPoint { + double fX; + double fY; + + void set(const SkPoint& pt) { + fX = pt.fX; + fY = pt.fY; + } + + friend SkDVector operator-(const SkDPoint& a, const SkDPoint& b) { + return { a.fX - b.fX, a.fY - b.fY }; + } + + friend bool operator==(const SkDPoint& a, const SkDPoint& b) { + return a.fX == b.fX && a.fY == b.fY; + } + + friend bool operator!=(const SkDPoint& a, const SkDPoint& b) { + return a.fX != b.fX || a.fY != b.fY; + } + + void operator=(const SkPoint& pt) { + fX = pt.fX; + fY = pt.fY; + } + + // only used by testing + void operator+=(const SkDVector& v) { + fX += v.fX; + fY += v.fY; + } + + // only used by testing + void operator-=(const SkDVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + // only used by testing + SkDPoint operator+(const SkDVector& v) { + SkDPoint result = *this; + result += v; + return result; + } + + // only used by testing + SkDPoint operator-(const SkDVector& v) { + SkDPoint result = *this; + result -= v; + return result; + } + + // note: this can not be implemented with + // return approximately_equal(a.fY, fY) && approximately_equal(a.fX, fX); + // because that will not take the magnitude of the values into account + bool approximatelyDEqual(const SkDPoint& a) const { + if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { + return true; + } + if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { + return false; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return AlmostDequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + bool approximatelyDEqual(const SkPoint& a) const { + SkDPoint dA; + dA.set(a); + return approximatelyDEqual(dA); + } + + bool approximatelyEqual(const SkDPoint& a) const { + if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { + return true; + } + if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { + return false; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return AlmostPequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + bool approximatelyEqual(const SkPoint& a) const { + SkDPoint dA; + dA.set(a); + return approximatelyEqual(dA); + } + + static bool ApproximatelyEqual(const SkPoint& a, const SkPoint& b) { + if (approximately_equal(a.fX, b.fX) && approximately_equal(a.fY, b.fY)) { + return true; + } + if (!RoughlyEqualUlps(a.fX, b.fX) || !RoughlyEqualUlps(a.fY, b.fY)) { + return false; + } + SkDPoint dA, dB; + dA.set(a); + dB.set(b); + double dist = dA.distance(dB); // OPTIMIZATION: can we compare against distSq instead ? + float tiniest = SkTMin(SkTMin(SkTMin(a.fX, b.fX), a.fY), b.fY); + float largest = SkTMax(SkTMax(SkTMax(a.fX, b.fX), a.fY), b.fY); + largest = SkTMax(largest, -tiniest); + return AlmostDequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? + } + + // only used by testing + bool approximatelyZero() const { + return approximately_zero(fX) && approximately_zero(fY); + } + + SkPoint asSkPoint() const { + SkPoint pt = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; + return pt; + } + + double distance(const SkDPoint& a) const { + SkDVector temp = *this - a; + return temp.length(); + } + + double distanceSquared(const SkDPoint& a) const { + SkDVector temp = *this - a; + return temp.lengthSquared(); + } + + static SkDPoint Mid(const SkDPoint& a, const SkDPoint& b) { + SkDPoint result; + result.fX = (a.fX + b.fX) / 2; + result.fY = (a.fY + b.fY) / 2; + return result; + } + + bool roughlyEqual(const SkDPoint& a) const { + if (roughly_equal(fX, a.fX) && roughly_equal(fY, a.fY)) { + return true; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + static bool RoughlyEqual(const SkPoint& a, const SkPoint& b) { + if (!RoughlyEqualUlps(a.fX, b.fX) && !RoughlyEqualUlps(a.fY, b.fY)) { + return false; + } + SkDPoint dA, dB; + dA.set(a); + dB.set(b); + double dist = dA.distance(dB); // OPTIMIZATION: can we compare against distSq instead ? + float tiniest = SkTMin(SkTMin(SkTMin(a.fX, b.fX), a.fY), b.fY); + float largest = SkTMax(SkTMax(SkTMax(a.fX, b.fX), a.fY), b.fY); + largest = SkTMax(largest, -tiniest); + return RoughlyEqualUlps((double) largest, largest + dist); // is dist within ULPS tolerance? + } + + // very light weight check, should only be used for inequality check + static bool WayRoughlyEqual(const SkPoint& a, const SkPoint& b) { + float largestNumber = SkTMax(SkTAbs(a.fX), SkTMax(SkTAbs(a.fY), + SkTMax(SkTAbs(b.fX), SkTAbs(b.fY)))); + SkVector diffs = a - b; + float largestDiff = SkTMax(diffs.fX, diffs.fY); + return roughly_zero_when_compared_to(largestDiff, largestNumber); + } + + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + static void Dump(const SkPoint& pt); + static void DumpHex(const SkPoint& pt); +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp b/gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp new file mode 100644 index 0000000000..45cd78b356 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp @@ -0,0 +1,416 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkLineParameters.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsCurve.h" +#include "src/pathops/SkPathOpsQuad.h" +#include "src/pathops/SkPathOpsRect.h" + +// from blackpawn.com/texts/pointinpoly +static bool pointInTriangle(const SkDPoint fPts[3], const SkDPoint& test) { + SkDVector v0 = fPts[2] - fPts[0]; + SkDVector v1 = fPts[1] - fPts[0]; + SkDVector v2 = test - fPts[0]; + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + // Compute barycentric coordinates + double denom = dot00 * dot11 - dot01 * dot01; + double u = dot11 * dot02 - dot01 * dot12; + double v = dot00 * dot12 - dot01 * dot02; + // Check if point is in triangle + if (denom >= 0) { + return u >= 0 && v >= 0 && u + v < denom; + } + return u <= 0 && v <= 0 && u + v > denom; +} + +static bool matchesEnd(const SkDPoint fPts[3], const SkDPoint& test) { + return fPts[0] == test || fPts[2] == test; +} + +/* started with at_most_end_pts_in_common from SkDQuadIntersection.cpp */ +// Do a quick reject by rotating all points relative to a line formed by +// a pair of one quad's points. If the 2nd quad's points +// are on the line or on the opposite side from the 1st quad's 'odd man', the +// curves at most intersect at the endpoints. +/* if returning true, check contains true if quad's hull collapsed, making the cubic linear + if returning false, check contains true if the the quad pair have only the end point in common +*/ +bool SkDQuad::hullIntersects(const SkDQuad& q2, bool* isLinear) const { + bool linear = true; + for (int oddMan = 0; oddMan < kPointCount; ++oddMan) { + const SkDPoint* endPt[2]; + this->otherPts(oddMan, endPt); + double origX = endPt[0]->fX; + double origY = endPt[0]->fY; + double adj = endPt[1]->fX - origX; + double opp = endPt[1]->fY - origY; + double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; + if (approximately_zero(sign)) { + continue; + } + linear = false; + bool foundOutlier = false; + for (int n = 0; n < kPointCount; ++n) { + double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; + if (test * sign > 0 && !precisely_zero(test)) { + foundOutlier = true; + break; + } + } + if (!foundOutlier) { + return false; + } + } + if (linear && !matchesEnd(fPts, q2.fPts[0]) && !matchesEnd(fPts, q2.fPts[2])) { + // if the end point of the opposite quad is inside the hull that is nearly a line, + // then representing the quad as a line may cause the intersection to be missed. + // Check to see if the endpoint is in the triangle. + if (pointInTriangle(fPts, q2.fPts[0]) || pointInTriangle(fPts, q2.fPts[2])) { + linear = false; + } + } + *isLinear = linear; + return true; +} + +bool SkDQuad::hullIntersects(const SkDConic& conic, bool* isLinear) const { + return conic.hullIntersects(*this, isLinear); +} + +bool SkDQuad::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { + return cubic.hullIntersects(*this, isLinear); +} + +/* bit twiddling for finding the off curve index (x&~m is the pair in [0,1,2] excluding oddMan) +oddMan opp x=oddMan^opp x=x-oddMan m=x>>2 x&~m + 0 1 1 1 0 1 + 2 2 2 0 2 + 1 1 0 -1 -1 0 + 2 3 2 0 2 + 2 1 3 1 0 1 + 2 0 -2 -1 0 +*/ +void SkDQuad::otherPts(int oddMan, const SkDPoint* endPt[2]) const { + for (int opp = 1; opp < kPointCount; ++opp) { + int end = (oddMan ^ opp) - oddMan; // choose a value not equal to oddMan + end &= ~(end >> 2); // if the value went negative, set it to zero + endPt[opp - 1] = &fPts[end]; + } +} + +int SkDQuad::AddValidTs(double s[], int realRoots, double* t) { + int foundRoots = 0; + for (int index = 0; index < realRoots; ++index) { + double tValue = s[index]; + if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { + if (approximately_less_than_zero(tValue)) { + tValue = 0; + } else if (approximately_greater_than_one(tValue)) { + tValue = 1; + } + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], tValue)) { + goto nextRoot; + } + } + t[foundRoots++] = tValue; + } +nextRoot: + {} + } + return foundRoots; +} + +// note: caller expects multiple results to be sorted smaller first +// note: http://en.wikipedia.org/wiki/Loss_of_significance has an interesting +// analysis of the quadratic equation, suggesting why the following looks at +// the sign of B -- and further suggesting that the greatest loss of precision +// is in b squared less two a c +int SkDQuad::RootsValidT(double A, double B, double C, double t[2]) { + double s[2]; + int realRoots = RootsReal(A, B, C, s); + int foundRoots = AddValidTs(s, realRoots, t); + return foundRoots; +} + +static int handle_zero(const double B, const double C, double s[2]) { + if (approximately_zero(B)) { + s[0] = 0; + return C == 0; + } + s[0] = -C / B; + return 1; +} + +/* +Numeric Solutions (5.6) suggests to solve the quadratic by computing + Q = -1/2(B + sgn(B)Sqrt(B^2 - 4 A C)) +and using the roots + t1 = Q / A + t2 = C / Q +*/ +// this does not discard real roots <= 0 or >= 1 +int SkDQuad::RootsReal(const double A, const double B, const double C, double s[2]) { + if (!A) { + return handle_zero(B, C, s); + } + const double p = B / (2 * A); + const double q = C / A; + if (approximately_zero(A) && (approximately_zero_inverse(p) || approximately_zero_inverse(q))) { + return handle_zero(B, C, s); + } + /* normal form: x^2 + px + q = 0 */ + const double p2 = p * p; + if (!AlmostDequalUlps(p2, q) && p2 < q) { + return 0; + } + double sqrt_D = 0; + if (p2 > q) { + sqrt_D = sqrt(p2 - q); + } + s[0] = sqrt_D - p; + s[1] = -sqrt_D - p; + return 1 + !AlmostDequalUlps(s[0], s[1]); +} + +bool SkDQuad::isLinear(int startIndex, int endIndex) const { + SkLineParameters lineParameters; + lineParameters.quadEndPoints(*this, startIndex, endIndex); + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double distance = lineParameters.controlPtDistance(*this); + double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); + double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); + largest = SkTMax(largest, -tiniest); + return approximately_zero_when_compared_to(distance, largest); +} + +SkDVector SkDQuad::dxdyAtT(double t) const { + double a = t - 1; + double b = 1 - 2 * t; + double c = t; + SkDVector result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; + if (result.fX == 0 && result.fY == 0) { + if (zero_or_one(t)) { + result = fPts[2] - fPts[0]; + } else { + // incomplete + SkDebugf("!q"); + } + } + return result; +} + +// OPTIMIZE: assert if caller passes in t == 0 / t == 1 ? +SkDPoint SkDQuad::ptAtT(double t) const { + if (0 == t) { + return fPts[0]; + } + if (1 == t) { + return fPts[2]; + } + double one_t = 1 - t; + double a = one_t * one_t; + double b = 2 * one_t * t; + double c = t * t; + SkDPoint result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; + return result; +} + +static double interp_quad_coords(const double* src, double t) { + if (0 == t) { + return src[0]; + } + if (1 == t) { + return src[4]; + } + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double abc = SkDInterp(ab, bc, t); + return abc; +} + +bool SkDQuad::monotonicInX() const { + return between(fPts[0].fX, fPts[1].fX, fPts[2].fX); +} + +bool SkDQuad::monotonicInY() const { + return between(fPts[0].fY, fPts[1].fY, fPts[2].fY); +} + +/* +Given a quadratic q, t1, and t2, find a small quadratic segment. + +The new quadratic is defined by A, B, and C, where + A = c[0]*(1 - t1)*(1 - t1) + 2*c[1]*t1*(1 - t1) + c[2]*t1*t1 + C = c[3]*(1 - t1)*(1 - t1) + 2*c[2]*t1*(1 - t1) + c[1]*t1*t1 + +To find B, compute the point halfway between t1 and t2: + +q(at (t1 + t2)/2) == D + +Next, compute where D must be if we know the value of B: + +_12 = A/2 + B/2 +12_ = B/2 + C/2 +123 = A/4 + B/2 + C/4 + = D + +Group the known values on one side: + +B = D*2 - A/2 - C/2 +*/ + +// OPTIMIZE? : special case t1 = 1 && t2 = 0 +SkDQuad SkDQuad::subDivide(double t1, double t2) const { + if (0 == t1 && 1 == t2) { + return *this; + } + SkDQuad dst; + double ax = dst[0].fX = interp_quad_coords(&fPts[0].fX, t1); + double ay = dst[0].fY = interp_quad_coords(&fPts[0].fY, t1); + double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2); + double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2); + double cx = dst[2].fX = interp_quad_coords(&fPts[0].fX, t2); + double cy = dst[2].fY = interp_quad_coords(&fPts[0].fY, t2); + /* bx = */ dst[1].fX = 2 * dx - (ax + cx) / 2; + /* by = */ dst[1].fY = 2 * dy - (ay + cy) / 2; + return dst; +} + +void SkDQuad::align(int endIndex, SkDPoint* dstPt) const { + if (fPts[endIndex].fX == fPts[1].fX) { + dstPt->fX = fPts[endIndex].fX; + } + if (fPts[endIndex].fY == fPts[1].fY) { + dstPt->fY = fPts[endIndex].fY; + } +} + +SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const { + SkASSERT(t1 != t2); + SkDPoint b; + SkDQuad sub = subDivide(t1, t2); + SkDLine b0 = {{a, sub[1] + (a - sub[0])}}; + SkDLine b1 = {{c, sub[1] + (c - sub[2])}}; + SkIntersections i; + i.intersectRay(b0, b1); + if (i.used() == 1 && i[0][0] >= 0 && i[1][0] >= 0) { + b = i.pt(0); + } else { + SkASSERT(i.used() <= 2); + return SkDPoint::Mid(b0[1], b1[1]); + } + if (t1 == 0 || t2 == 0) { + align(0, &b); + } + if (t1 == 1 || t2 == 1) { + align(2, &b); + } + if (AlmostBequalUlps(b.fX, a.fX)) { + b.fX = a.fX; + } else if (AlmostBequalUlps(b.fX, c.fX)) { + b.fX = c.fX; + } + if (AlmostBequalUlps(b.fY, a.fY)) { + b.fY = a.fY; + } else if (AlmostBequalUlps(b.fY, c.fY)) { + b.fY = c.fY; + } + return b; +} + +/* classic one t subdivision */ +static void interp_quad_coords(const double* src, double* dst, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + dst[0] = src[0]; + dst[2] = ab; + dst[4] = SkDInterp(ab, bc, t); + dst[6] = bc; + dst[8] = src[4]; +} + +SkDQuadPair SkDQuad::chopAt(double t) const +{ + SkDQuadPair dst; + interp_quad_coords(&fPts[0].fX, &dst.pts[0].fX, t); + interp_quad_coords(&fPts[0].fY, &dst.pts[0].fY, t); + return dst; +} + +static int valid_unit_divide(double numer, double denom, double* ratio) +{ + if (numer < 0) { + numer = -numer; + denom = -denom; + } + if (denom == 0 || numer == 0 || numer >= denom) { + return 0; + } + double r = numer / denom; + if (r == 0) { // catch underflow if numer <<<< denom + return 0; + } + *ratio = r; + return 1; +} + +/** Quad'(t) = At + B, where + A = 2(a - 2b + c) + B = 2(b - a) + Solve for t, only if it fits between 0 < t < 1 +*/ +int SkDQuad::FindExtrema(const double src[], double tValue[1]) { + /* At + B == 0 + t = -B / A + */ + double a = src[0]; + double b = src[2]; + double c = src[4]; + return valid_unit_divide(a - b, a - b - b + c, tValue); +} + +/* Parameterization form, given A*t*t + 2*B*t*(1-t) + C*(1-t)*(1-t) + * + * a = A - 2*B + C + * b = 2*B - 2*C + * c = C + */ +void SkDQuad::SetABC(const double* quad, double* a, double* b, double* c) { + *a = quad[0]; // a = A + *b = 2 * quad[2]; // b = 2*B + *c = quad[4]; // c = C + *b -= *c; // b = 2*B - C + *a -= *b; // a = A - 2*B + C + *b -= *c; // b = 2*B - 2*C +} + +int SkTQuad::intersectRay(SkIntersections* i, const SkDLine& line) const { + return i->intersectRay(fQuad, line); +} + +bool SkTQuad::hullIntersects(const SkDConic& conic, bool* isLinear) const { + return conic.hullIntersects(fQuad, isLinear); +} + +bool SkTQuad::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { + return cubic.hullIntersects(fQuad, isLinear); +} + +void SkTQuad::setBounds(SkDRect* rect) const { + rect->setBounds(fQuad); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsQuad.h b/gfx/skia/skia/src/pathops/SkPathOpsQuad.h new file mode 100644 index 0000000000..5489485eba --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsQuad.h @@ -0,0 +1,184 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsQuad_DEFINED +#define SkPathOpsQuad_DEFINED + +#include "src/core/SkArenaAlloc.h" +#include "src/pathops/SkPathOpsTCurve.h" + +struct SkOpCurve; + +struct SkDQuadPair { + const SkDQuad& first() const { return (const SkDQuad&) pts[0]; } + const SkDQuad& second() const { return (const SkDQuad&) pts[2]; } + SkDPoint pts[5]; +}; + +struct SkDQuad { + static const int kPointCount = 3; + static const int kPointLast = kPointCount - 1; + static const int kMaxIntersections = 4; + + SkDPoint fPts[kPointCount]; + + bool collapsed() const { + return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]); + } + + bool controlsInside() const { + SkDVector v01 = fPts[0] - fPts[1]; + SkDVector v02 = fPts[0] - fPts[2]; + SkDVector v12 = fPts[1] - fPts[2]; + return v02.dot(v01) > 0 && v02.dot(v12) > 0; + } + + void debugInit() { + sk_bzero(fPts, sizeof(fPts)); + } + + void debugSet(const SkDPoint* pts); + + SkDQuad flip() const { + SkDQuad result = {{fPts[2], fPts[1], fPts[0]} SkDEBUGPARAMS(fDebugGlobalState) }; + return result; + } + + static bool IsConic() { return false; } + + const SkDQuad& set(const SkPoint pts[kPointCount] + SkDEBUGPARAMS(SkOpGlobalState* state = nullptr)) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + fPts[2] = pts[2]; + SkDEBUGCODE(fDebugGlobalState = state); + return *this; + } + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + + static int AddValidTs(double s[], int realRoots, double* t); + void align(int endIndex, SkDPoint* dstPt) const; + SkDQuadPair chopAt(double t) const; + SkDVector dxdyAtT(double t) const; + static int FindExtrema(const double src[], double tValue[1]); + +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const { return fDebugGlobalState; } +#endif + + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified horizontal line. + */ + int horizontalIntersect(double yIntercept, double roots[2]) const; + + bool hullIntersects(const SkDQuad& , bool* isLinear) const; + bool hullIntersects(const SkDConic& , bool* isLinear) const; + bool hullIntersects(const SkDCubic& , bool* isLinear) const; + bool isLinear(int startIndex, int endIndex) const; + static int maxIntersections() { return kMaxIntersections; } + bool monotonicInX() const; + bool monotonicInY() const; + void otherPts(int oddMan, const SkDPoint* endPt[2]) const; + static int pointCount() { return kPointCount; } + static int pointLast() { return kPointLast; } + SkDPoint ptAtT(double t) const; + static int RootsReal(double A, double B, double C, double t[2]); + static int RootsValidT(const double A, const double B, const double C, double s[2]); + static void SetABC(const double* quad, double* a, double* b, double* c); + SkDQuad subDivide(double t1, double t2) const; + void subDivide(double t1, double t2, SkDQuad* quad) const { *quad = this->subDivide(t1, t2); } + + static SkDQuad SubDivide(const SkPoint a[kPointCount], double t1, double t2) { + SkDQuad quad; + quad.set(a); + return quad.subDivide(t1, t2); + } + SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const; + static SkDPoint SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& c, + double t1, double t2) { + SkDQuad quad; + quad.set(pts); + return quad.subDivide(a, c, t1, t2); + } + + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified vertical line. + */ + int verticalIntersect(double xIntercept, double roots[2]) const; + + SkDCubic debugToCubic() const; + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + void dumpID(int id) const; + void dumpInner() const; + + SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); +}; + + +class SkTQuad : public SkTCurve { +public: + SkDQuad fQuad; + + SkTQuad() {} + + SkTQuad(const SkDQuad& q) + : fQuad(q) { + } + + ~SkTQuad() override {} + + const SkDPoint& operator[](int n) const override { return fQuad[n]; } + SkDPoint& operator[](int n) override { return fQuad[n]; } + + bool collapsed() const override { return fQuad.collapsed(); } + bool controlsInside() const override { return fQuad.controlsInside(); } + void debugInit() override { return fQuad.debugInit(); } +#if DEBUG_T_SECT + void dumpID(int id) const override { return fQuad.dumpID(id); } +#endif + SkDVector dxdyAtT(double t) const override { return fQuad.dxdyAtT(t); } +#ifdef SK_DEBUG + SkOpGlobalState* globalState() const override { return fQuad.globalState(); } +#endif + + bool hullIntersects(const SkDQuad& quad, bool* isLinear) const override { + return quad.hullIntersects(fQuad, isLinear); + } + + bool hullIntersects(const SkDConic& conic, bool* isLinear) const override; + bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const override; + + bool hullIntersects(const SkTCurve& curve, bool* isLinear) const override { + return curve.hullIntersects(fQuad, isLinear); + } + + int intersectRay(SkIntersections* i, const SkDLine& line) const override; + bool IsConic() const override { return false; } + SkTCurve* make(SkArenaAlloc& heap) const override { return heap.make(); } + + int maxIntersections() const override { return SkDQuad::kMaxIntersections; } + + void otherPts(int oddMan, const SkDPoint* endPt[2]) const override { + fQuad.otherPts(oddMan, endPt); + } + + int pointCount() const override { return SkDQuad::kPointCount; } + int pointLast() const override { return SkDQuad::kPointLast; } + SkDPoint ptAtT(double t) const override { return fQuad.ptAtT(t); } + void setBounds(SkDRect* ) const override; + + void subDivide(double t1, double t2, SkTCurve* curve) const override { + ((SkTQuad*) curve)->fQuad = fQuad.subDivide(t1, t2); + } +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsRect.cpp b/gfx/skia/skia/src/pathops/SkPathOpsRect.cpp new file mode 100644 index 0000000000..c2a0014ea7 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsRect.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkPathOpsConic.h" +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsLine.h" +#include "src/pathops/SkPathOpsQuad.h" +#include "src/pathops/SkPathOpsRect.h" + +void SkDRect::setBounds(const SkDQuad& curve, const SkDQuad& sub, double startT, double endT) { + set(sub[0]); + add(sub[2]); + double tValues[2]; + int roots = 0; + if (!sub.monotonicInX()) { + roots = SkDQuad::FindExtrema(&sub[0].fX, tValues); + } + if (!sub.monotonicInY()) { + roots += SkDQuad::FindExtrema(&sub[0].fY, &tValues[roots]); + } + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * tValues[index]; + add(curve.ptAtT(t)); + } +} + +void SkDRect::setBounds(const SkDConic& curve, const SkDConic& sub, double startT, double endT) { + set(sub[0]); + add(sub[2]); + double tValues[2]; + int roots = 0; + if (!sub.monotonicInX()) { + roots = SkDConic::FindExtrema(&sub[0].fX, sub.fWeight, tValues); + } + if (!sub.monotonicInY()) { + roots += SkDConic::FindExtrema(&sub[0].fY, sub.fWeight, &tValues[roots]); + } + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * tValues[index]; + add(curve.ptAtT(t)); + } +} + +void SkDRect::setBounds(const SkDCubic& curve, const SkDCubic& sub, double startT, double endT) { + set(sub[0]); + add(sub[3]); + double tValues[4]; + int roots = 0; + if (!sub.monotonicInX()) { + roots = SkDCubic::FindExtrema(&sub[0].fX, tValues); + } + if (!sub.monotonicInY()) { + roots += SkDCubic::FindExtrema(&sub[0].fY, &tValues[roots]); + } + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * tValues[index]; + add(curve.ptAtT(t)); + } +} + +void SkDRect::setBounds(const SkTCurve& curve) { + curve.setBounds(this); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsRect.h b/gfx/skia/skia/src/pathops/SkPathOpsRect.h new file mode 100644 index 0000000000..2f2a9f0c65 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsRect.h @@ -0,0 +1,77 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsRect_DEFINED +#define SkPathOpsRect_DEFINED + +#include "src/pathops/SkPathOpsPoint.h" + +class SkTCurve; + +struct SkDRect { + double fLeft, fTop, fRight, fBottom; + + void add(const SkDPoint& pt) { + fLeft = SkTMin(fLeft, pt.fX); + fTop = SkTMin(fTop, pt.fY); + fRight = SkTMax(fRight, pt.fX); + fBottom = SkTMax(fBottom, pt.fY); + } + + bool contains(const SkDPoint& pt) const { + return approximately_between(fLeft, pt.fX, fRight) + && approximately_between(fTop, pt.fY, fBottom); + } + + void debugInit(); + + bool intersects(const SkDRect& r) const { + SkASSERT(fLeft <= fRight); + SkASSERT(fTop <= fBottom); + SkASSERT(r.fLeft <= r.fRight); + SkASSERT(r.fTop <= r.fBottom); + return r.fLeft <= fRight && fLeft <= r.fRight && r.fTop <= fBottom && fTop <= r.fBottom; + } + + void set(const SkDPoint& pt) { + fLeft = fRight = pt.fX; + fTop = fBottom = pt.fY; + } + + double width() const { + return fRight - fLeft; + } + + double height() const { + return fBottom - fTop; + } + + void setBounds(const SkDConic& curve) { + setBounds(curve, curve, 0, 1); + } + + void setBounds(const SkDConic& curve, const SkDConic& sub, double tStart, double tEnd); + + void setBounds(const SkDCubic& curve) { + setBounds(curve, curve, 0, 1); + } + + void setBounds(const SkDCubic& curve, const SkDCubic& sub, double tStart, double tEnd); + + void setBounds(const SkDQuad& curve) { + setBounds(curve, curve, 0, 1); + } + + void setBounds(const SkDQuad& curve, const SkDQuad& sub, double tStart, double tEnd); + + void setBounds(const SkTCurve& curve); + + bool valid() const { + return fLeft <= fRight && fTop <= fBottom; + } +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp b/gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp new file mode 100644 index 0000000000..f079b50a8d --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp @@ -0,0 +1,226 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkAddIntersections.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkPathOpsCommon.h" +#include "src/pathops/SkPathWriter.h" + +static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* writer) { + bool unsortable = false; + do { + SkOpSpan* span = FindSortableTop(contourList); + if (!span) { + break; + } + SkOpSegment* current = span->segment(); + SkOpSpanBase* start = span->next(); + SkOpSpanBase* end = span; + SkTDArray chase; + do { + if (current->activeWinding(start, end)) { + do { + if (!unsortable && current->done()) { + break; + } + SkASSERT(unsortable || !current->done()); + SkOpSpanBase* nextStart = start; + SkOpSpanBase* nextEnd = end; + SkOpSegment* next = current->findNextWinding(&chase, &nextStart, &nextEnd, + &unsortable); + if (!next) { + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), start->pt().fX, start->pt().fY, + end->pt().fX, end->pt().fY); + #endif + if (!current->addCurveTo(start, end, writer)) { + return false; + } + current = next; + start = nextStart; + end = nextEnd; + } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); + if (current->activeWinding(start, end) && !writer->isClosed()) { + SkOpSpan* spanStart = start->starter(end); + if (!spanStart->done()) { + if (!current->addCurveTo(start, end, writer)) { + return false; + } + current->markDone(spanStart); + } + } + writer->finishContour(); + } else { + SkOpSpanBase* last; + if (!current->markAndChaseDone(start, end, &last)) { + return false; + } + if (last && !last->chased()) { + last->setChased(true); + SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); + *chase.append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); + if (!last->final()) { + SkDebugf(" windSum=%d", last->upCast()->windSum()); + } + SkDebugf("\n"); +#endif + } + } + current = FindChase(&chase, &start, &end); + SkPathOpsDebug::ShowActiveSpans(contourList); + if (!current) { + break; + } + } while (true); + } while (true); + return true; +} + +// returns true if all edges were processed +static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* writer) { + bool unsortable = false; + int safetyNet = 1000000; + do { + SkOpSpan* span = FindUndone(contourList); + if (!span) { + break; + } + SkOpSegment* current = span->segment(); + SkOpSpanBase* start = span->next(); + SkOpSpanBase* end = span; + do { + if (--safetyNet < 0) { + return false; + } + if (!unsortable && current->done()) { + break; + } + SkASSERT(unsortable || !current->done()); + SkOpSpanBase* nextStart = start; + SkOpSpanBase* nextEnd = end; + SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, + &unsortable); + if (!next) { + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), start->pt().fX, start->pt().fY, + end->pt().fX, end->pt().fY); + #endif + if (!current->addCurveTo(start, end, writer)) { + return false; + } + current = next; + start = nextStart; + end = nextEnd; + } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); + if (!writer->isClosed()) { + SkOpSpan* spanStart = start->starter(end); + if (!spanStart->done()) { + return false; + } + } + writer->finishContour(); + SkPathOpsDebug::ShowActiveSpans(contourList); + } while (true); + return true; +} + +// FIXME : add this as a member of SkPath +bool SimplifyDebug(const SkPath& path, SkPath* result + SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { + // returns 1 for evenodd, -1 for winding, regardless of inverse-ness + SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType + : SkPath::kEvenOdd_FillType; + if (path.isConvex()) { + if (result != &path) { + *result = path; + } + result->setFillType(fillType); + return true; + } + // turn path into list of segments + SkSTArenaAlloc<4096> allocator; // FIXME: constant-ize, tune + SkOpContour contour; + SkOpContourHead* contourList = static_cast(&contour); + SkOpGlobalState globalState(contourList, &allocator + SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpCoincidence coincidence(&globalState); +#if DEBUG_DUMP_VERIFY +#ifndef SK_DEBUG + const char* testName = "release"; +#endif + if (SkPathOpsDebug::gDumpOp) { + SkPathOpsDebug::DumpSimplify(path, testName); + } +#endif +#if DEBUG_SORT + SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; +#endif + SkOpEdgeBuilder builder(path, contourList, &globalState); + if (!builder.finish()) { + return false; + } +#if DEBUG_DUMP_SEGMENTS + contour.dumpSegments(); +#endif + if (!SortContourList(&contourList, false, false)) { + result->reset(); + result->setFillType(fillType); + return true; + } + // find all intersections between segments + SkOpContour* current = contourList; + do { + SkOpContour* next = current; + while (AddIntersectTs(current, next, &coincidence) + && (next = next->next())); + } while ((current = current->next())); +#if DEBUG_VALIDATE + globalState.setPhase(SkOpPhase::kWalking); +#endif + bool success = HandleCoincidence(contourList, &coincidence); +#if DEBUG_COIN + globalState.debugAddToGlobalCoinDicts(); +#endif + if (!success) { + return false; + } +#if DEBUG_DUMP_ALIGNMENT + contour.dumpSegments("aligned"); +#endif + // construct closed contours + result->reset(); + result->setFillType(fillType); + SkPathWriter wrapper(*result); + if (builder.xorMask() == kWinding_PathOpsMask ? !bridgeWinding(contourList, &wrapper) + : !bridgeXor(contourList, &wrapper)) { + return false; + } + wrapper.assemble(); // if some edges could not be resolved, assemble remaining + return true; +} + +bool Simplify(const SkPath& path, SkPath* result) { +#if DEBUG_DUMP_VERIFY + if (SkPathOpsDebug::gVerifyOp) { + if (!SimplifyDebug(path, result SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr))) { + SkPathOpsDebug::ReportSimplifyFail(path); + return false; + } + SkPathOpsDebug::VerifySimplify(path, *result); + return true; + } +#endif + return SimplifyDebug(path, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTCurve.h b/gfx/skia/skia/src/pathops/SkPathOpsTCurve.h new file mode 100644 index 0000000000..f9053488b7 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTCurve.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsTCurve_DEFINED +#define SkPathOpsTCurve_DEFINED + +#include "src/pathops/SkPathOpsPoint.h" + +class SkArenaAlloc; +class SkIntersections; + +class SkTCurve { +public: + virtual ~SkTCurve() {} + virtual const SkDPoint& operator[](int n) const = 0; + virtual SkDPoint& operator[](int n) = 0; + + virtual bool collapsed() const = 0; + virtual bool controlsInside() const = 0; + virtual void debugInit() = 0; +#if DEBUG_T_SECT + virtual void dumpID(int id) const = 0; +#endif + virtual SkDVector dxdyAtT(double t) const = 0; + virtual bool hullIntersects(const SkDQuad& , bool* isLinear) const = 0; + virtual bool hullIntersects(const SkDConic& , bool* isLinear) const = 0; + virtual bool hullIntersects(const SkDCubic& , bool* isLinear) const = 0; + virtual bool hullIntersects(const SkTCurve& , bool* isLinear) const = 0; + virtual int intersectRay(SkIntersections* i, const SkDLine& line) const = 0; + virtual bool IsConic() const = 0; + virtual SkTCurve* make(SkArenaAlloc& ) const = 0; + virtual int maxIntersections() const = 0; + virtual void otherPts(int oddMan, const SkDPoint* endPt[2]) const = 0; + virtual int pointCount() const = 0; + virtual int pointLast() const = 0; + virtual SkDPoint ptAtT(double t) const = 0; + virtual void setBounds(SkDRect* ) const = 0; + virtual void subDivide(double t1, double t2, SkTCurve* curve) const = 0; +#ifdef SK_DEBUG + virtual SkOpGlobalState* globalState() const = 0; +#endif +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp b/gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp new file mode 100644 index 0000000000..f24d4b51de --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp @@ -0,0 +1,2138 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pathops/SkPathOpsTSect.h" + +#define COINCIDENT_SPAN_COUNT 9 + +void SkTCoincident::setPerp(const SkTCurve& c1, double t, + const SkDPoint& cPt, const SkTCurve& c2) { + SkDVector dxdy = c1.dxdyAtT(t); + SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; + SkIntersections i SkDEBUGCODE((c1.globalState())); + int used = i.intersectRay(c2, perp); + // only keep closest + if (used == 0 || used == 3) { + this->init(); + return; + } + fPerpT = i[0][0]; + fPerpPt = i.pt(0); + SkASSERT(used <= 2); + if (used == 2) { + double distSq = (fPerpPt - cPt).lengthSquared(); + double dist2Sq = (i.pt(1) - cPt).lengthSquared(); + if (dist2Sq < distSq) { + fPerpT = i[0][1]; + fPerpPt = i.pt(1); + } + } +#if DEBUG_T_SECT + SkDebugf("setPerp t=%1.9g cPt=(%1.9g,%1.9g) %s oppT=%1.9g fPerpPt=(%1.9g,%1.9g)\n", + t, cPt.fX, cPt.fY, + cPt.approximatelyEqual(fPerpPt) ? "==" : "!=", fPerpT, fPerpPt.fX, fPerpPt.fY); +#endif + fMatch = cPt.approximatelyEqual(fPerpPt); +#if DEBUG_T_SECT + if (fMatch) { + SkDebugf(""); // allow setting breakpoint + } +#endif +} + +void SkTSpan::addBounded(SkTSpan* span, SkArenaAlloc* heap) { + SkTSpanBounded* bounded = heap->make(); + bounded->fBounded = span; + bounded->fNext = fBounded; + fBounded = bounded; +} + +SkTSpan* SkTSect::addFollowing( + SkTSpan* prior) { + SkTSpan* result = this->addOne(); + SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); + result->fStartT = prior ? prior->fEndT : 0; + SkTSpan* next = prior ? prior->fNext : fHead; + result->fEndT = next ? next->fStartT : 1; + result->fPrev = prior; + result->fNext = next; + if (prior) { + prior->fNext = result; + } else { + fHead = result; + } + if (next) { + next->fPrev = result; + } + result->resetBounds(fCurve); + // world may not be consistent to call validate here + result->validate(); + return result; +} + +void SkTSect::addForPerp(SkTSpan* span, double t) { + if (!span->hasOppT(t)) { + SkTSpan* priorSpan; + SkTSpan* opp = this->spanAtT(t, &priorSpan); + if (!opp) { + opp = this->addFollowing(priorSpan); +#if DEBUG_PERP + SkDebugf("%s priorSpan=%d t=%1.9g opp=%d\n", __FUNCTION__, priorSpan ? + priorSpan->debugID() : -1, t, opp->debugID()); +#endif + } +#if DEBUG_PERP + opp->dump(); SkDebugf("\n"); + SkDebugf("%s addBounded span=%d opp=%d\n", __FUNCTION__, priorSpan ? + priorSpan->debugID() : -1, opp->debugID()); +#endif + opp->addBounded(span, &fHeap); + span->addBounded(opp, &fHeap); + } + this->validate(); +#if DEBUG_T_SECT + span->validatePerpT(t); +#endif +} + +double SkTSpan::closestBoundedT(const SkDPoint& pt) const { + double result = -1; + double closest = DBL_MAX; + const SkTSpanBounded* testBounded = fBounded; + while (testBounded) { + const SkTSpan* test = testBounded->fBounded; + double startDist = test->pointFirst().distanceSquared(pt); + if (closest > startDist) { + closest = startDist; + result = test->fStartT; + } + double endDist = test->pointLast().distanceSquared(pt); + if (closest > endDist) { + closest = endDist; + result = test->fEndT; + } + testBounded = testBounded->fNext; + } + SkASSERT(between(0, result, 1)); + return result; +} + +#ifdef SK_DEBUG + +bool SkTSpan::debugIsBefore(const SkTSpan* span) const { + const SkTSpan* work = this; + do { + if (span == work) { + return true; + } + } while ((work = work->fNext)); + return false; +} +#endif + +bool SkTSpan::contains(double t) const { + const SkTSpan* work = this; + do { + if (between(work->fStartT, t, work->fEndT)) { + return true; + } + } while ((work = work->fNext)); + return false; +} + +const SkTSect* SkTSpan::debugOpp() const { + return SkDEBUGRELEASE(fDebugSect->debugOpp(), nullptr); +} + +SkTSpan* SkTSpan::findOppSpan( + const SkTSpan* opp) const { + SkTSpanBounded* bounded = fBounded; + while (bounded) { + SkTSpan* test = bounded->fBounded; + if (opp == test) { + return test; + } + bounded = bounded->fNext; + } + return nullptr; +} + +// returns 0 if no hull intersection +// 1 if hulls intersect +// 2 if hulls only share a common endpoint +// -1 if linear and further checking is required + +int SkTSpan::hullCheck(const SkTSpan* opp, + bool* start, bool* oppStart) { + if (fIsLinear) { + return -1; + } + bool ptsInCommon; + if (onlyEndPointsInCommon(opp, start, oppStart, &ptsInCommon)) { + SkASSERT(ptsInCommon); + return 2; + } + bool linear; + if (fPart->hullIntersects(*opp->fPart, &linear)) { + if (!linear) { // check set true if linear + return 1; + } + fIsLinear = true; + fIsLine = fPart->controlsInside(); + return ptsInCommon ? 1 : -1; + } else { // hull is not linear; check set true if intersected at the end points + return ((int) ptsInCommon) << 1; // 0 or 2 + } + return 0; +} + +// OPTIMIZE ? If at_most_end_pts_in_common detects that one quad is near linear, +// use line intersection to guess a better split than 0.5 +// OPTIMIZE Once at_most_end_pts_in_common detects linear, mark span so all future splits are linear + +int SkTSpan::hullsIntersect(SkTSpan* opp, + bool* start, bool* oppStart) { + if (!fBounds.intersects(opp->fBounds)) { + return 0; + } + int hullSect = this->hullCheck(opp, start, oppStart); + if (hullSect >= 0) { + return hullSect; + } + hullSect = opp->hullCheck(this, oppStart, start); + if (hullSect >= 0) { + return hullSect; + } + return -1; +} + +void SkTSpan::init(const SkTCurve& c) { + fPrev = fNext = nullptr; + fStartT = 0; + fEndT = 1; + fBounded = nullptr; + resetBounds(c); +} + +bool SkTSpan::initBounds(const SkTCurve& c) { + if (SkDoubleIsNaN(fStartT) || SkDoubleIsNaN(fEndT)) { + return false; + } + c.subDivide(fStartT, fEndT, fPart); + fBounds.setBounds(*fPart); + fCoinStart.init(); + fCoinEnd.init(); + fBoundsMax = SkTMax(fBounds.width(), fBounds.height()); + fCollapsed = fPart->collapsed(); + fHasPerp = false; + fDeleted = false; +#if DEBUG_T_SECT + if (fCollapsed) { + SkDebugf(""); // for convenient breakpoints + } +#endif + return fBounds.valid(); +} + +bool SkTSpan::linearsIntersect(SkTSpan* span) { + int result = this->linearIntersects(*span->fPart); + if (result <= 1) { + return SkToBool(result); + } + SkASSERT(span->fIsLinear); + result = span->linearIntersects(*fPart); +// SkASSERT(result <= 1); + return SkToBool(result); +} + +double SkTSpan::linearT(const SkDPoint& pt) const { + SkDVector len = this->pointLast() - this->pointFirst(); + return fabs(len.fX) > fabs(len.fY) + ? (pt.fX - this->pointFirst().fX) / len.fX + : (pt.fY - this->pointFirst().fY) / len.fY; +} + +int SkTSpan::linearIntersects(const SkTCurve& q2) const { + // looks like q1 is near-linear + int start = 0, end = fPart->pointLast(); // the outside points are usually the extremes + if (!fPart->controlsInside()) { + double dist = 0; // if there's any question, compute distance to find best outsiders + for (int outer = 0; outer < this->pointCount() - 1; ++outer) { + for (int inner = outer + 1; inner < this->pointCount(); ++inner) { + double test = ((*fPart)[outer] - (*fPart)[inner]).lengthSquared(); + if (dist > test) { + continue; + } + dist = test; + start = outer; + end = inner; + } + } + } + // see if q2 is on one side of the line formed by the extreme points + double origX = (*fPart)[start].fX; + double origY = (*fPart)[start].fY; + double adj = (*fPart)[end].fX - origX; + double opp = (*fPart)[end].fY - origY; + double maxPart = SkTMax(fabs(adj), fabs(opp)); + double sign = 0; // initialization to shut up warning in release build + for (int n = 0; n < q2.pointCount(); ++n) { + double dx = q2[n].fY - origY; + double dy = q2[n].fX - origX; + double maxVal = SkTMax(maxPart, SkTMax(fabs(dx), fabs(dy))); + double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; + if (precisely_zero_when_compared_to(test, maxVal)) { + return 1; + } + if (approximately_zero_when_compared_to(test, maxVal)) { + return 3; + } + if (n == 0) { + sign = test; + continue; + } + if (test * sign < 0) { + return 1; + } + } + return 0; +} + +bool SkTSpan::onlyEndPointsInCommon(const SkTSpan* opp, + bool* start, bool* oppStart, bool* ptsInCommon) { + if (opp->pointFirst() == this->pointFirst()) { + *start = *oppStart = true; + } else if (opp->pointFirst() == this->pointLast()) { + *start = false; + *oppStart = true; + } else if (opp->pointLast() == this->pointFirst()) { + *start = true; + *oppStart = false; + } else if (opp->pointLast() == this->pointLast()) { + *start = *oppStart = false; + } else { + *ptsInCommon = false; + return false; + } + *ptsInCommon = true; + const SkDPoint* otherPts[4], * oppOtherPts[4]; +// const SkDPoint* otherPts[this->pointCount() - 1], * oppOtherPts[opp->pointCount() - 1]; + int baseIndex = *start ? 0 : fPart->pointLast(); + fPart->otherPts(baseIndex, otherPts); + opp->fPart->otherPts(*oppStart ? 0 : opp->fPart->pointLast(), oppOtherPts); + const SkDPoint& base = (*fPart)[baseIndex]; + for (int o1 = 0; o1 < this->pointCount() - 1; ++o1) { + SkDVector v1 = *otherPts[o1] - base; + for (int o2 = 0; o2 < opp->pointCount() - 1; ++o2) { + SkDVector v2 = *oppOtherPts[o2] - base; + if (v2.dot(v1) >= 0) { + return false; + } + } + } + return true; +} + +SkTSpan* SkTSpan::oppT(double t) const { + SkTSpanBounded* bounded = fBounded; + while (bounded) { + SkTSpan* test = bounded->fBounded; + if (between(test->fStartT, t, test->fEndT)) { + return test; + } + bounded = bounded->fNext; + } + return nullptr; +} + +bool SkTSpan::removeAllBounded() { + bool deleteSpan = false; + SkTSpanBounded* bounded = fBounded; + while (bounded) { + SkTSpan* opp = bounded->fBounded; + deleteSpan |= opp->removeBounded(this); + bounded = bounded->fNext; + } + return deleteSpan; +} + +bool SkTSpan::removeBounded(const SkTSpan* opp) { + if (fHasPerp) { + bool foundStart = false; + bool foundEnd = false; + SkTSpanBounded* bounded = fBounded; + while (bounded) { + SkTSpan* test = bounded->fBounded; + if (opp != test) { + foundStart |= between(test->fStartT, fCoinStart.perpT(), test->fEndT); + foundEnd |= between(test->fStartT, fCoinEnd.perpT(), test->fEndT); + } + bounded = bounded->fNext; + } + if (!foundStart || !foundEnd) { + fHasPerp = false; + fCoinStart.init(); + fCoinEnd.init(); + } + } + SkTSpanBounded* bounded = fBounded; + SkTSpanBounded* prev = nullptr; + while (bounded) { + SkTSpanBounded* boundedNext = bounded->fNext; + if (opp == bounded->fBounded) { + if (prev) { + prev->fNext = boundedNext; + return false; + } else { + fBounded = boundedNext; + return fBounded == nullptr; + } + } + prev = bounded; + bounded = boundedNext; + } + SkOPASSERT(0); + return false; +} + +bool SkTSpan::splitAt(SkTSpan* work, double t, SkArenaAlloc* heap) { + fStartT = t; + fEndT = work->fEndT; + if (fStartT == fEndT) { + fCollapsed = true; + return false; + } + work->fEndT = t; + if (work->fStartT == work->fEndT) { + work->fCollapsed = true; + return false; + } + fPrev = work; + fNext = work->fNext; + fIsLinear = work->fIsLinear; + fIsLine = work->fIsLine; + + work->fNext = this; + if (fNext) { + fNext->fPrev = this; + } + this->validate(); + SkTSpanBounded* bounded = work->fBounded; + fBounded = nullptr; + while (bounded) { + this->addBounded(bounded->fBounded, heap); + bounded = bounded->fNext; + } + bounded = fBounded; + while (bounded) { + bounded->fBounded->addBounded(this, heap); + bounded = bounded->fNext; + } + return true; +} + +void SkTSpan::validate() const { +#if DEBUG_VALIDATE + SkASSERT(this != fPrev); + SkASSERT(this != fNext); + SkASSERT(fNext == nullptr || fNext != fPrev); + SkASSERT(fNext == nullptr || this == fNext->fPrev); + SkASSERT(fPrev == nullptr || this == fPrev->fNext); + this->validateBounded(); +#endif +#if DEBUG_T_SECT + SkASSERT(fBounds.width() || fBounds.height() || fCollapsed); + SkASSERT(fBoundsMax == SkTMax(fBounds.width(), fBounds.height()) || fCollapsed == 0xFF); + SkASSERT(0 <= fStartT); + SkASSERT(fEndT <= 1); + SkASSERT(fStartT <= fEndT); + SkASSERT(fBounded || fCollapsed == 0xFF); + if (fHasPerp) { + if (fCoinStart.isMatch()) { + validatePerpT(fCoinStart.perpT()); + validatePerpPt(fCoinStart.perpT(), fCoinStart.perpPt()); + } + if (fCoinEnd.isMatch()) { + validatePerpT(fCoinEnd.perpT()); + validatePerpPt(fCoinEnd.perpT(), fCoinEnd.perpPt()); + } + } +#endif +} + +void SkTSpan::validateBounded() const { +#if DEBUG_VALIDATE + const SkTSpanBounded* testBounded = fBounded; + while (testBounded) { + SkDEBUGCODE(const SkTSpan* overlap = testBounded->fBounded); + SkASSERT(!overlap->fDeleted); +#if DEBUG_T_SECT + SkASSERT(((this->debugID() ^ overlap->debugID()) & 1) == 1); + SkASSERT(overlap->findOppSpan(this)); +#endif + testBounded = testBounded->fNext; + } +#endif +} + +void SkTSpan::validatePerpT(double oppT) const { + const SkTSpanBounded* testBounded = fBounded; + while (testBounded) { + const SkTSpan* overlap = testBounded->fBounded; + if (precisely_between(overlap->fStartT, oppT, overlap->fEndT)) { + return; + } + testBounded = testBounded->fNext; + } + SkASSERT(0); +} + +void SkTSpan::validatePerpPt(double t, const SkDPoint& pt) const { + SkASSERT(fDebugSect->fOppSect->fCurve.ptAtT(t) == pt); +} + +SkTSect::SkTSect(const SkTCurve& c + SkDEBUGPARAMS(SkOpGlobalState* debugGlobalState) + PATH_OPS_DEBUG_T_SECT_PARAMS(int id)) + : fCurve(c) + , fHeap(sizeof(SkTSpan) * 4) + , fCoincident(nullptr) + , fDeleted(nullptr) + , fActiveCount(0) + , fHung(false) + SkDEBUGPARAMS(fDebugGlobalState(debugGlobalState)) + PATH_OPS_DEBUG_T_SECT_PARAMS(fID(id)) + PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugCount(0)) + PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugAllocatedCount(0)) +{ + this->resetRemovedEnds(); + fHead = this->addOne(); + SkDEBUGCODE(fHead->debugSetGlobalState(debugGlobalState)); + fHead->init(c); +} + +SkTSpan* SkTSect::addOne() { + SkTSpan* result; + if (fDeleted) { + result = fDeleted; + fDeleted = result->fNext; + } else { + result = fHeap.make(fCurve, fHeap); +#if DEBUG_T_SECT + ++fDebugAllocatedCount; +#endif + } + result->reset(); + result->fHasPerp = false; + result->fDeleted = false; + ++fActiveCount; + PATH_OPS_DEBUG_T_SECT_CODE(result->fID = fDebugCount++ * 2 + fID); + SkDEBUGCODE(result->fDebugSect = this); +#ifdef SK_DEBUG + result->debugInit(fCurve, fHeap); + result->fCoinStart.debugInit(); + result->fCoinEnd.debugInit(); + result->fPrev = result->fNext = nullptr; + result->fBounds.debugInit(); + result->fStartT = result->fEndT = result->fBoundsMax = SK_ScalarNaN; + result->fCollapsed = result->fIsLinear = result->fIsLine = 0xFF; +#endif + return result; +} + +bool SkTSect::binarySearchCoin(SkTSect* sect2, double tStart, + double tStep, double* resultT, double* oppT, SkTSpan** oppFirst) { + SkTSpan work(fCurve, fHeap); + double result = work.fStartT = work.fEndT = tStart; + SkDEBUGCODE(work.fDebugSect = this); + SkDPoint last = fCurve.ptAtT(tStart); + SkDPoint oppPt; + bool flip = false; + bool contained = false; + bool down = tStep < 0; + const SkTCurve& opp = sect2->fCurve; + do { + tStep *= 0.5; + work.fStartT += tStep; + if (flip) { + tStep = -tStep; + flip = false; + } + work.initBounds(fCurve); + if (work.fCollapsed) { + return false; + } + if (last.approximatelyEqual(work.pointFirst())) { + break; + } + last = work.pointFirst(); + work.fCoinStart.setPerp(fCurve, work.fStartT, last, opp); + if (work.fCoinStart.isMatch()) { +#if DEBUG_T_SECT + work.validatePerpPt(work.fCoinStart.perpT(), work.fCoinStart.perpPt()); +#endif + double oppTTest = work.fCoinStart.perpT(); + if (sect2->fHead->contains(oppTTest)) { + *oppT = oppTTest; + oppPt = work.fCoinStart.perpPt(); + contained = true; + if (down ? result <= work.fStartT : result >= work.fStartT) { + *oppFirst = nullptr; // signal caller to fail + return false; + } + result = work.fStartT; + continue; + } + } + tStep = -tStep; + flip = true; + } while (true); + if (!contained) { + return false; + } + if (last.approximatelyEqual(fCurve[0])) { + result = 0; + } else if (last.approximatelyEqual(this->pointLast())) { + result = 1; + } + if (oppPt.approximatelyEqual(opp[0])) { + *oppT = 0; + } else if (oppPt.approximatelyEqual(sect2->pointLast())) { + *oppT = 1; + } + *resultT = result; + return true; +} + +// OPTIMIZE ? keep a sorted list of sizes in the form of a doubly-linked list in quad span +// so that each quad sect has a pointer to the largest, and can update it as spans +// are split + +SkTSpan* SkTSect::boundsMax() { + SkTSpan* test = fHead; + SkTSpan* largest = fHead; + bool lCollapsed = largest->fCollapsed; + int safetyNet = 10000; + while ((test = test->fNext)) { + if (!--safetyNet) { + fHung = true; + return nullptr; + } + bool tCollapsed = test->fCollapsed; + if ((lCollapsed && !tCollapsed) || (lCollapsed == tCollapsed && + largest->fBoundsMax < test->fBoundsMax)) { + largest = test; + lCollapsed = test->fCollapsed; + } + } + return largest; +} + +bool SkTSect::coincidentCheck(SkTSect* sect2) { + SkTSpan* first = fHead; + if (!first) { + return false; + } + SkTSpan* last, * next; + do { + int consecutive = this->countConsecutiveSpans(first, &last); + next = last->fNext; + if (consecutive < COINCIDENT_SPAN_COUNT) { + continue; + } + this->validate(); + sect2->validate(); + this->computePerpendiculars(sect2, first, last); + this->validate(); + sect2->validate(); + // check to see if a range of points are on the curve + SkTSpan* coinStart = first; + do { + bool success = this->extractCoincident(sect2, coinStart, last, &coinStart); + if (!success) { + return false; + } + } while (coinStart && !last->fDeleted); + if (!fHead || !sect2->fHead) { + break; + } + if (!next || next->fDeleted) { + break; + } + } while ((first = next)); + return true; +} + +void SkTSect::coincidentForce(SkTSect* sect2, + double start1s, double start1e) { + SkTSpan* first = fHead; + SkTSpan* last = this->tail(); + SkTSpan* oppFirst = sect2->fHead; + SkTSpan* oppLast = sect2->tail(); + if (!last || !oppLast) { + return; + } + bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); + deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); + this->removeSpanRange(first, last); + sect2->removeSpanRange(oppFirst, oppLast); + first->fStartT = start1s; + first->fEndT = start1e; + first->resetBounds(fCurve); + first->fCoinStart.setPerp(fCurve, start1s, fCurve[0], sect2->fCurve); + first->fCoinEnd.setPerp(fCurve, start1e, this->pointLast(), sect2->fCurve); + bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); + double oppStartT = first->fCoinStart.perpT() == -1 ? 0 : SkTMax(0., first->fCoinStart.perpT()); + double oppEndT = first->fCoinEnd.perpT() == -1 ? 1 : SkTMin(1., first->fCoinEnd.perpT()); + if (!oppMatched) { + using std::swap; + swap(oppStartT, oppEndT); + } + oppFirst->fStartT = oppStartT; + oppFirst->fEndT = oppEndT; + oppFirst->resetBounds(sect2->fCurve); + this->removeCoincident(first, false); + sect2->removeCoincident(oppFirst, true); + if (deleteEmptySpans) { + this->deleteEmptySpans(); + sect2->deleteEmptySpans(); + } +} + +bool SkTSect::coincidentHasT(double t) { + SkTSpan* test = fCoincident; + while (test) { + if (between(test->fStartT, t, test->fEndT)) { + return true; + } + test = test->fNext; + } + return false; +} + +int SkTSect::collapsed() const { + int result = 0; + const SkTSpan* test = fHead; + while (test) { + if (test->fCollapsed) { + ++result; + } + test = test->next(); + } + return result; +} + +void SkTSect::computePerpendiculars(SkTSect* sect2, + SkTSpan* first, SkTSpan* last) { + if (!last) { + return; + } + const SkTCurve& opp = sect2->fCurve; + SkTSpan* work = first; + SkTSpan* prior = nullptr; + do { + if (!work->fHasPerp && !work->fCollapsed) { + if (prior) { + work->fCoinStart = prior->fCoinEnd; + } else { + work->fCoinStart.setPerp(fCurve, work->fStartT, work->pointFirst(), opp); + } + if (work->fCoinStart.isMatch()) { + double perpT = work->fCoinStart.perpT(); + if (sect2->coincidentHasT(perpT)) { + work->fCoinStart.init(); + } else { + sect2->addForPerp(work, perpT); + } + } + work->fCoinEnd.setPerp(fCurve, work->fEndT, work->pointLast(), opp); + if (work->fCoinEnd.isMatch()) { + double perpT = work->fCoinEnd.perpT(); + if (sect2->coincidentHasT(perpT)) { + work->fCoinEnd.init(); + } else { + sect2->addForPerp(work, perpT); + } + } + work->fHasPerp = true; + } + if (work == last) { + break; + } + prior = work; + work = work->fNext; + SkASSERT(work); + } while (true); +} + +int SkTSect::countConsecutiveSpans(SkTSpan* first, + SkTSpan** lastPtr) const { + int consecutive = 1; + SkTSpan* last = first; + do { + SkTSpan* next = last->fNext; + if (!next) { + break; + } + if (next->fStartT > last->fEndT) { + break; + } + ++consecutive; + last = next; + } while (true); + *lastPtr = last; + return consecutive; +} + +bool SkTSect::hasBounded(const SkTSpan* span) const { + const SkTSpan* test = fHead; + if (!test) { + return false; + } + do { + if (test->findOppSpan(span)) { + return true; + } + } while ((test = test->next())); + return false; +} + +bool SkTSect::deleteEmptySpans() { + SkTSpan* test; + SkTSpan* next = fHead; + int safetyHatch = 1000; + while ((test = next)) { + next = test->fNext; + if (!test->fBounded) { + if (!this->removeSpan(test)) { + return false; + } + } + if (--safetyHatch < 0) { + return false; + } + } + return true; +} + +bool SkTSect::extractCoincident( + SkTSect* sect2, + SkTSpan* first, SkTSpan* last, + SkTSpan** result) { + first = findCoincidentRun(first, &last); + if (!first || !last) { + *result = nullptr; + return true; + } + // march outwards to find limit of coincidence from here to previous and next spans + double startT = first->fStartT; + double oppStartT SK_INIT_TO_AVOID_WARNING; + double oppEndT SK_INIT_TO_AVOID_WARNING; + SkTSpan* prev = first->fPrev; + SkASSERT(first->fCoinStart.isMatch()); + SkTSpan* oppFirst = first->findOppT(first->fCoinStart.perpT()); + SkOPASSERT(last->fCoinEnd.isMatch()); + bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); + double coinStart; + SkDEBUGCODE(double coinEnd); + SkTSpan* cutFirst; + if (prev && prev->fEndT == startT + && this->binarySearchCoin(sect2, startT, prev->fStartT - startT, &coinStart, + &oppStartT, &oppFirst) + && prev->fStartT < coinStart && coinStart < startT + && (cutFirst = prev->oppT(oppStartT))) { + oppFirst = cutFirst; + first = this->addSplitAt(prev, coinStart); + first->markCoincident(); + prev->fCoinEnd.markCoincident(); + if (oppFirst->fStartT < oppStartT && oppStartT < oppFirst->fEndT) { + SkTSpan* oppHalf = sect2->addSplitAt(oppFirst, oppStartT); + if (oppMatched) { + oppFirst->fCoinEnd.markCoincident(); + oppHalf->markCoincident(); + oppFirst = oppHalf; + } else { + oppFirst->markCoincident(); + oppHalf->fCoinStart.markCoincident(); + } + } + } else { + if (!oppFirst) { + return false; + } + SkDEBUGCODE(coinStart = first->fStartT); + SkDEBUGCODE(oppStartT = oppMatched ? oppFirst->fStartT : oppFirst->fEndT); + } + // FIXME: incomplete : if we're not at the end, find end of coin + SkTSpan* oppLast; + SkOPASSERT(last->fCoinEnd.isMatch()); + oppLast = last->findOppT(last->fCoinEnd.perpT()); + SkDEBUGCODE(coinEnd = last->fEndT); +#ifdef SK_DEBUG + if (!this->globalState() || !this->globalState()->debugSkipAssert()) { + oppEndT = oppMatched ? oppLast->fEndT : oppLast->fStartT; + } +#endif + if (!oppMatched) { + using std::swap; + swap(oppFirst, oppLast); + swap(oppStartT, oppEndT); + } + SkOPASSERT(oppStartT < oppEndT); + SkASSERT(coinStart == first->fStartT); + SkASSERT(coinEnd == last->fEndT); + if (!oppFirst) { + *result = nullptr; + return true; + } + SkOPASSERT(oppStartT == oppFirst->fStartT); + if (!oppLast) { + *result = nullptr; + return true; + } + SkOPASSERT(oppEndT == oppLast->fEndT); + // reduce coincident runs to single entries + this->validate(); + sect2->validate(); + bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); + deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); + this->removeSpanRange(first, last); + sect2->removeSpanRange(oppFirst, oppLast); + first->fEndT = last->fEndT; + first->resetBounds(this->fCurve); + first->fCoinStart.setPerp(fCurve, first->fStartT, first->pointFirst(), sect2->fCurve); + first->fCoinEnd.setPerp(fCurve, first->fEndT, first->pointLast(), sect2->fCurve); + oppStartT = first->fCoinStart.perpT(); + oppEndT = first->fCoinEnd.perpT(); + if (between(0, oppStartT, 1) && between(0, oppEndT, 1)) { + if (!oppMatched) { + using std::swap; + swap(oppStartT, oppEndT); + } + oppFirst->fStartT = oppStartT; + oppFirst->fEndT = oppEndT; + oppFirst->resetBounds(sect2->fCurve); + } + this->validateBounded(); + sect2->validateBounded(); + last = first->fNext; + if (!this->removeCoincident(first, false)) { + return false; + } + if (!sect2->removeCoincident(oppFirst, true)) { + return false; + } + if (deleteEmptySpans) { + if (!this->deleteEmptySpans() || !sect2->deleteEmptySpans()) { + *result = nullptr; + return false; + } + } + this->validate(); + sect2->validate(); + *result = last && !last->fDeleted && fHead && sect2->fHead ? last : nullptr; + return true; +} + +SkTSpan* SkTSect::findCoincidentRun( + SkTSpan* first, SkTSpan** lastPtr) { + SkTSpan* work = first; + SkTSpan* lastCandidate = nullptr; + first = nullptr; + // find the first fully coincident span + do { + if (work->fCoinStart.isMatch()) { +#if DEBUG_T_SECT + work->validatePerpT(work->fCoinStart.perpT()); + work->validatePerpPt(work->fCoinStart.perpT(), work->fCoinStart.perpPt()); +#endif + SkOPASSERT(work->hasOppT(work->fCoinStart.perpT())); + if (!work->fCoinEnd.isMatch()) { + break; + } + lastCandidate = work; + if (!first) { + first = work; + } + } else if (first && work->fCollapsed) { + *lastPtr = lastCandidate; + return first; + } else { + lastCandidate = nullptr; + SkOPASSERT(!first); + } + if (work == *lastPtr) { + return first; + } + work = work->fNext; + if (!work) { + return nullptr; + } + } while (true); + if (lastCandidate) { + *lastPtr = lastCandidate; + } + return first; +} + +int SkTSect::intersects(SkTSpan* span, + SkTSect* opp, + SkTSpan* oppSpan, int* oppResult) { + bool spanStart, oppStart; + int hullResult = span->hullsIntersect(oppSpan, &spanStart, &oppStart); + if (hullResult >= 0) { + if (hullResult == 2) { // hulls have one point in common + if (!span->fBounded || !span->fBounded->fNext) { + SkASSERT(!span->fBounded || span->fBounded->fBounded == oppSpan); + if (spanStart) { + span->fEndT = span->fStartT; + } else { + span->fStartT = span->fEndT; + } + } else { + hullResult = 1; + } + if (!oppSpan->fBounded || !oppSpan->fBounded->fNext) { + if (oppSpan->fBounded && oppSpan->fBounded->fBounded != span) { + return 0; + } + if (oppStart) { + oppSpan->fEndT = oppSpan->fStartT; + } else { + oppSpan->fStartT = oppSpan->fEndT; + } + *oppResult = 2; + } else { + *oppResult = 1; + } + } else { + *oppResult = 1; + } + return hullResult; + } + if (span->fIsLine && oppSpan->fIsLine) { + SkIntersections i; + int sects = this->linesIntersect(span, opp, oppSpan, &i); + if (sects == 2) { + return *oppResult = 1; + } + if (!sects) { + return -1; + } + this->removedEndCheck(span); + span->fStartT = span->fEndT = i[0][0]; + opp->removedEndCheck(oppSpan); + oppSpan->fStartT = oppSpan->fEndT = i[1][0]; + return *oppResult = 2; + } + if (span->fIsLinear || oppSpan->fIsLinear) { + return *oppResult = (int) span->linearsIntersect(oppSpan); + } + return *oppResult = 1; +} + +template +static bool is_parallel(const SkDLine& thisLine, const SkTCurve& opp) { + if (!opp.IsConic()) { + return false; // FIXME : breaks a lot of stuff now + } + int finds = 0; + SkDLine thisPerp; + thisPerp.fPts[0].fX = thisLine.fPts[1].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); + thisPerp.fPts[0].fY = thisLine.fPts[1].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); + thisPerp.fPts[1] = thisLine.fPts[1]; + SkIntersections perpRayI; + perpRayI.intersectRay(opp, thisPerp); + for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { + finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[1]); + } + thisPerp.fPts[1].fX = thisLine.fPts[0].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); + thisPerp.fPts[1].fY = thisLine.fPts[0].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); + thisPerp.fPts[0] = thisLine.fPts[0]; + perpRayI.intersectRay(opp, thisPerp); + for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { + finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[0]); + } + return finds >= 2; +} + +// while the intersection points are sufficiently far apart: +// construct the tangent lines from the intersections +// find the point where the tangent line intersects the opposite curve + +int SkTSect::linesIntersect(SkTSpan* span, + SkTSect* opp, + SkTSpan* oppSpan, SkIntersections* i) { + SkIntersections thisRayI SkDEBUGCODE((span->fDebugGlobalState)); + SkIntersections oppRayI SkDEBUGCODE((span->fDebugGlobalState)); + SkDLine thisLine = {{ span->pointFirst(), span->pointLast() }}; + SkDLine oppLine = {{ oppSpan->pointFirst(), oppSpan->pointLast() }}; + int loopCount = 0; + double bestDistSq = DBL_MAX; + if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { + return 0; + } + if (!oppRayI.intersectRay(this->fCurve, oppLine)) { + return 0; + } + // if the ends of each line intersect the opposite curve, the lines are coincident + if (thisRayI.used() > 1) { + int ptMatches = 0; + for (int tIndex = 0; tIndex < thisRayI.used(); ++tIndex) { + for (int lIndex = 0; lIndex < (int) SK_ARRAY_COUNT(thisLine.fPts); ++lIndex) { + ptMatches += thisRayI.pt(tIndex).approximatelyEqual(thisLine.fPts[lIndex]); + } + } + if (ptMatches == 2 || is_parallel(thisLine, opp->fCurve)) { + return 2; + } + } + if (oppRayI.used() > 1) { + int ptMatches = 0; + for (int oIndex = 0; oIndex < oppRayI.used(); ++oIndex) { + for (int lIndex = 0; lIndex < (int) SK_ARRAY_COUNT(oppLine.fPts); ++lIndex) { + ptMatches += oppRayI.pt(oIndex).approximatelyEqual(oppLine.fPts[lIndex]); + } + } + if (ptMatches == 2|| is_parallel(oppLine, this->fCurve)) { + return 2; + } + } + do { + // pick the closest pair of points + double closest = DBL_MAX; + int closeIndex SK_INIT_TO_AVOID_WARNING; + int oppCloseIndex SK_INIT_TO_AVOID_WARNING; + for (int index = 0; index < oppRayI.used(); ++index) { + if (!roughly_between(span->fStartT, oppRayI[0][index], span->fEndT)) { + continue; + } + for (int oIndex = 0; oIndex < thisRayI.used(); ++oIndex) { + if (!roughly_between(oppSpan->fStartT, thisRayI[0][oIndex], oppSpan->fEndT)) { + continue; + } + double distSq = thisRayI.pt(index).distanceSquared(oppRayI.pt(oIndex)); + if (closest > distSq) { + closest = distSq; + closeIndex = index; + oppCloseIndex = oIndex; + } + } + } + if (closest == DBL_MAX) { + break; + } + const SkDPoint& oppIPt = thisRayI.pt(oppCloseIndex); + const SkDPoint& iPt = oppRayI.pt(closeIndex); + if (between(span->fStartT, oppRayI[0][closeIndex], span->fEndT) + && between(oppSpan->fStartT, thisRayI[0][oppCloseIndex], oppSpan->fEndT) + && oppIPt.approximatelyEqual(iPt)) { + i->merge(oppRayI, closeIndex, thisRayI, oppCloseIndex); + return i->used(); + } + double distSq = oppIPt.distanceSquared(iPt); + if (bestDistSq < distSq || ++loopCount > 5) { + return 0; + } + bestDistSq = distSq; + double oppStart = oppRayI[0][closeIndex]; + thisLine[0] = fCurve.ptAtT(oppStart); + thisLine[1] = thisLine[0] + fCurve.dxdyAtT(oppStart); + if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { + break; + } + double start = thisRayI[0][oppCloseIndex]; + oppLine[0] = opp->fCurve.ptAtT(start); + oppLine[1] = oppLine[0] + opp->fCurve.dxdyAtT(start); + if (!oppRayI.intersectRay(this->fCurve, oppLine)) { + break; + } + } while (true); + // convergence may fail if the curves are nearly coincident + SkTCoincident oCoinS, oCoinE; + oCoinS.setPerp(opp->fCurve, oppSpan->fStartT, oppSpan->pointFirst(), fCurve); + oCoinE.setPerp(opp->fCurve, oppSpan->fEndT, oppSpan->pointLast(), fCurve); + double tStart = oCoinS.perpT(); + double tEnd = oCoinE.perpT(); + bool swap = tStart > tEnd; + if (swap) { + using std::swap; + swap(tStart, tEnd); + } + tStart = SkTMax(tStart, span->fStartT); + tEnd = SkTMin(tEnd, span->fEndT); + if (tStart > tEnd) { + return 0; + } + SkDVector perpS, perpE; + if (tStart == span->fStartT) { + SkTCoincident coinS; + coinS.setPerp(fCurve, span->fStartT, span->pointFirst(), opp->fCurve); + perpS = span->pointFirst() - coinS.perpPt(); + } else if (swap) { + perpS = oCoinE.perpPt() - oppSpan->pointLast(); + } else { + perpS = oCoinS.perpPt() - oppSpan->pointFirst(); + } + if (tEnd == span->fEndT) { + SkTCoincident coinE; + coinE.setPerp(fCurve, span->fEndT, span->pointLast(), opp->fCurve); + perpE = span->pointLast() - coinE.perpPt(); + } else if (swap) { + perpE = oCoinS.perpPt() - oppSpan->pointFirst(); + } else { + perpE = oCoinE.perpPt() - oppSpan->pointLast(); + } + if (perpS.dot(perpE) >= 0) { + return 0; + } + SkTCoincident coinW; + double workT = tStart; + double tStep = tEnd - tStart; + SkDPoint workPt; + do { + tStep *= 0.5; + if (precisely_zero(tStep)) { + return 0; + } + workT += tStep; + workPt = fCurve.ptAtT(workT); + coinW.setPerp(fCurve, workT, workPt, opp->fCurve); + double perpT = coinW.perpT(); + if (coinW.isMatch() ? !between(oppSpan->fStartT, perpT, oppSpan->fEndT) : perpT < 0) { + continue; + } + SkDVector perpW = workPt - coinW.perpPt(); + if ((perpS.dot(perpW) >= 0) == (tStep < 0)) { + tStep = -tStep; + } + if (workPt.approximatelyEqual(coinW.perpPt())) { + break; + } + } while (true); + double oppTTest = coinW.perpT(); + if (!opp->fHead->contains(oppTTest)) { + return 0; + } + i->setMax(1); + i->insert(workT, oppTTest, workPt); + return 1; +} + +bool SkTSect::markSpanGone(SkTSpan* span) { + if (--fActiveCount < 0) { + return false; + } + span->fNext = fDeleted; + fDeleted = span; + SkOPASSERT(!span->fDeleted); + span->fDeleted = true; + return true; +} + +bool SkTSect::matchedDirection(double t, const SkTSect* sect2, + double t2) const { + SkDVector dxdy = this->fCurve.dxdyAtT(t); + SkDVector dxdy2 = sect2->fCurve.dxdyAtT(t2); + return dxdy.dot(dxdy2) >= 0; +} + +void SkTSect::matchedDirCheck(double t, const SkTSect* sect2, + double t2, bool* calcMatched, bool* oppMatched) const { + if (*calcMatched) { + SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); + } else { + *oppMatched = this->matchedDirection(t, sect2, t2); + *calcMatched = true; + } +} + +void SkTSect::mergeCoincidence(SkTSect* sect2) { + double smallLimit = 0; + do { + // find the smallest unprocessed span + SkTSpan* smaller = nullptr; + SkTSpan* test = fCoincident; + do { + if (!test) { + return; + } + if (test->fStartT < smallLimit) { + continue; + } + if (smaller && smaller->fEndT < test->fStartT) { + continue; + } + smaller = test; + } while ((test = test->fNext)); + if (!smaller) { + return; + } + smallLimit = smaller->fEndT; + // find next larger span + SkTSpan* prior = nullptr; + SkTSpan* larger = nullptr; + SkTSpan* largerPrior = nullptr; + test = fCoincident; + do { + if (test->fStartT < smaller->fEndT) { + continue; + } + SkOPASSERT(test->fStartT != smaller->fEndT); + if (larger && larger->fStartT < test->fStartT) { + continue; + } + largerPrior = prior; + larger = test; + } while ((void) (prior = test), (test = test->fNext)); + if (!larger) { + continue; + } + // check middle t value to see if it is coincident as well + double midT = (smaller->fEndT + larger->fStartT) / 2; + SkDPoint midPt = fCurve.ptAtT(midT); + SkTCoincident coin; + coin.setPerp(fCurve, midT, midPt, sect2->fCurve); + if (coin.isMatch()) { + smaller->fEndT = larger->fEndT; + smaller->fCoinEnd = larger->fCoinEnd; + if (largerPrior) { + largerPrior->fNext = larger->fNext; + largerPrior->validate(); + } else { + fCoincident = larger->fNext; + } + } + } while (true); +} + +SkTSpan* SkTSect::prev( + SkTSpan* span) const { + SkTSpan* result = nullptr; + SkTSpan* test = fHead; + while (span != test) { + result = test; + test = test->fNext; + SkASSERT(test); + } + return result; +} + +void SkTSect::recoverCollapsed() { + SkTSpan* deleted = fDeleted; + while (deleted) { + SkTSpan* delNext = deleted->fNext; + if (deleted->fCollapsed) { + SkTSpan** spanPtr = &fHead; + while (*spanPtr && (*spanPtr)->fEndT <= deleted->fStartT) { + spanPtr = &(*spanPtr)->fNext; + } + deleted->fNext = *spanPtr; + *spanPtr = deleted; + } + deleted = delNext; + } +} + +void SkTSect::removeAllBut(const SkTSpan* keep, + SkTSpan* span, SkTSect* opp) { + const SkTSpanBounded* testBounded = span->fBounded; + while (testBounded) { + SkTSpan* bounded = testBounded->fBounded; + const SkTSpanBounded* next = testBounded->fNext; + // may have been deleted when opp did 'remove all but' + if (bounded != keep && !bounded->fDeleted) { + SkAssertResult(SkDEBUGCODE(!) span->removeBounded(bounded)); + if (bounded->removeBounded(span)) { + opp->removeSpan(bounded); + } + } + testBounded = next; + } + SkASSERT(!span->fDeleted); + SkASSERT(span->findOppSpan(keep)); + SkASSERT(keep->findOppSpan(span)); +} + +bool SkTSect::removeByPerpendicular(SkTSect* opp) { + SkTSpan* test = fHead; + SkTSpan* next; + do { + next = test->fNext; + if (test->fCoinStart.perpT() < 0 || test->fCoinEnd.perpT() < 0) { + continue; + } + SkDVector startV = test->fCoinStart.perpPt() - test->pointFirst(); + SkDVector endV = test->fCoinEnd.perpPt() - test->pointLast(); +#if DEBUG_T_SECT + SkDebugf("%s startV=(%1.9g,%1.9g) endV=(%1.9g,%1.9g) dot=%1.9g\n", __FUNCTION__, + startV.fX, startV.fY, endV.fX, endV.fY, startV.dot(endV)); +#endif + if (startV.dot(endV) <= 0) { + continue; + } + if (!this->removeSpans(test, opp)) { + return false; + } + } while ((test = next)); + return true; +} + +bool SkTSect::removeCoincident(SkTSpan* span, bool isBetween) { + if (!this->unlinkSpan(span)) { + return false; + } + if (isBetween || between(0, span->fCoinStart.perpT(), 1)) { + --fActiveCount; + span->fNext = fCoincident; + fCoincident = span; + } else { + this->markSpanGone(span); + } + return true; +} + +void SkTSect::removedEndCheck(SkTSpan* span) { + if (!span->fStartT) { + fRemovedStartT = true; + } + if (1 == span->fEndT) { + fRemovedEndT = true; + } +} + +bool SkTSect::removeSpan(SkTSpan* span) {\ + this->removedEndCheck(span); + if (!this->unlinkSpan(span)) { + return false; + } + return this->markSpanGone(span); +} + +void SkTSect::removeSpanRange(SkTSpan* first, + SkTSpan* last) { + if (first == last) { + return; + } + SkTSpan* span = first; + SkASSERT(span); + SkTSpan* final = last->fNext; + SkTSpan* next = span->fNext; + while ((span = next) && span != final) { + next = span->fNext; + this->markSpanGone(span); + } + if (final) { + final->fPrev = first; + } + first->fNext = final; + // world may not be ready for validation here + first->validate(); +} + +bool SkTSect::removeSpans(SkTSpan* span, + SkTSect* opp) { + SkTSpanBounded* bounded = span->fBounded; + while (bounded) { + SkTSpan* spanBounded = bounded->fBounded; + SkTSpanBounded* next = bounded->fNext; + if (span->removeBounded(spanBounded)) { // shuffles last into position 0 + this->removeSpan(span); + } + if (spanBounded->removeBounded(span)) { + opp->removeSpan(spanBounded); + } + if (span->fDeleted && opp->hasBounded(span)) { + return false; + } + bounded = next; + } + return true; +} + +SkTSpan* SkTSect::spanAtT(double t, + SkTSpan** priorSpan) { + SkTSpan* test = fHead; + SkTSpan* prev = nullptr; + while (test && test->fEndT < t) { + prev = test; + test = test->fNext; + } + *priorSpan = prev; + return test && test->fStartT <= t ? test : nullptr; +} + +SkTSpan* SkTSect::tail() { + SkTSpan* result = fHead; + SkTSpan* next = fHead; + int safetyNet = 100000; + while ((next = next->fNext)) { + if (!--safetyNet) { + return nullptr; + } + if (next->fEndT > result->fEndT) { + result = next; + } + } + return result; +} + +/* Each span has a range of opposite spans it intersects. After the span is split in two, + adjust the range to its new size */ + +bool SkTSect::trim(SkTSpan* span, + SkTSect* opp) { + FAIL_IF(!span->initBounds(fCurve)); + const SkTSpanBounded* testBounded = span->fBounded; + while (testBounded) { + SkTSpan* test = testBounded->fBounded; + const SkTSpanBounded* next = testBounded->fNext; + int oppSects, sects = this->intersects(span, opp, test, &oppSects); + if (sects >= 1) { + if (oppSects == 2) { + test->initBounds(opp->fCurve); + opp->removeAllBut(span, test, this); + } + if (sects == 2) { + span->initBounds(fCurve); + this->removeAllBut(test, span, opp); + return true; + } + } else { + if (span->removeBounded(test)) { + this->removeSpan(span); + } + if (test->removeBounded(span)) { + opp->removeSpan(test); + } + } + testBounded = next; + } + return true; +} + +bool SkTSect::unlinkSpan(SkTSpan* span) { + SkTSpan* prev = span->fPrev; + SkTSpan* next = span->fNext; + if (prev) { + prev->fNext = next; + if (next) { + next->fPrev = prev; + if (next->fStartT > next->fEndT) { + return false; + } + // world may not be ready for validate here + next->validate(); + } + } else { + fHead = next; + if (next) { + next->fPrev = nullptr; + } + } + return true; +} + +bool SkTSect::updateBounded(SkTSpan* first, + SkTSpan* last, SkTSpan* oppFirst) { + SkTSpan* test = first; + const SkTSpan* final = last->next(); + bool deleteSpan = false; + do { + deleteSpan |= test->removeAllBounded(); + } while ((test = test->fNext) != final && test); + first->fBounded = nullptr; + first->addBounded(oppFirst, &fHeap); + // cannot call validate until remove span range is called + return deleteSpan; +} + +void SkTSect::validate() const { +#if DEBUG_VALIDATE + int count = 0; + double last = 0; + if (fHead) { + const SkTSpan* span = fHead; + SkASSERT(!span->fPrev); + const SkTSpan* next; + do { + span->validate(); + SkASSERT(span->fStartT >= last); + last = span->fEndT; + ++count; + next = span->fNext; + SkASSERT(next != span); + } while ((span = next) != nullptr); + } + SkASSERT(count == fActiveCount); +#endif +#if DEBUG_T_SECT + SkASSERT(fActiveCount <= fDebugAllocatedCount); + int deletedCount = 0; + const SkTSpan* deleted = fDeleted; + while (deleted) { + ++deletedCount; + deleted = deleted->fNext; + } + const SkTSpan* coincident = fCoincident; + while (coincident) { + ++deletedCount; + coincident = coincident->fNext; + } + SkASSERT(fActiveCount + deletedCount == fDebugAllocatedCount); +#endif +} + +void SkTSect::validateBounded() const { +#if DEBUG_VALIDATE + if (!fHead) { + return; + } + const SkTSpan* span = fHead; + do { + span->validateBounded(); + } while ((span = span->fNext) != nullptr); +#endif +} + +int SkTSect::EndsEqual(const SkTSect* sect1, + const SkTSect* sect2, SkIntersections* intersections) { + int zeroOneSet = 0; + if (sect1->fCurve[0] == sect2->fCurve[0]) { + zeroOneSet |= kZeroS1Set | kZeroS2Set; + intersections->insert(0, 0, sect1->fCurve[0]); + } + if (sect1->fCurve[0] == sect2->pointLast()) { + zeroOneSet |= kZeroS1Set | kOneS2Set; + intersections->insert(0, 1, sect1->fCurve[0]); + } + if (sect1->pointLast() == sect2->fCurve[0]) { + zeroOneSet |= kOneS1Set | kZeroS2Set; + intersections->insert(1, 0, sect1->pointLast()); + } + if (sect1->pointLast() == sect2->pointLast()) { + zeroOneSet |= kOneS1Set | kOneS2Set; + intersections->insert(1, 1, sect1->pointLast()); + } + // check for zero + if (!(zeroOneSet & (kZeroS1Set | kZeroS2Set)) + && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[0])) { + zeroOneSet |= kZeroS1Set | kZeroS2Set; + intersections->insertNear(0, 0, sect1->fCurve[0], sect2->fCurve[0]); + } + if (!(zeroOneSet & (kZeroS1Set | kOneS2Set)) + && sect1->fCurve[0].approximatelyEqual(sect2->pointLast())) { + zeroOneSet |= kZeroS1Set | kOneS2Set; + intersections->insertNear(0, 1, sect1->fCurve[0], sect2->pointLast()); + } + // check for one + if (!(zeroOneSet & (kOneS1Set | kZeroS2Set)) + && sect1->pointLast().approximatelyEqual(sect2->fCurve[0])) { + zeroOneSet |= kOneS1Set | kZeroS2Set; + intersections->insertNear(1, 0, sect1->pointLast(), sect2->fCurve[0]); + } + if (!(zeroOneSet & (kOneS1Set | kOneS2Set)) + && sect1->pointLast().approximatelyEqual(sect2->pointLast())) { + zeroOneSet |= kOneS1Set | kOneS2Set; + intersections->insertNear(1, 1, sect1->pointLast(), sect2->pointLast()); + } + return zeroOneSet; +} + +struct SkClosestRecord { + bool operator<(const SkClosestRecord& rh) const { + return fClosest < rh.fClosest; + } + + void addIntersection(SkIntersections* intersections) const { + double r1t = fC1Index ? fC1Span->endT() : fC1Span->startT(); + double r2t = fC2Index ? fC2Span->endT() : fC2Span->startT(); + intersections->insert(r1t, r2t, fC1Span->part()[fC1Index]); + } + + void findEnd(const SkTSpan* span1, const SkTSpan* span2, + int c1Index, int c2Index) { + const SkTCurve& c1 = span1->part(); + const SkTCurve& c2 = span2->part(); + if (!c1[c1Index].approximatelyEqual(c2[c2Index])) { + return; + } + double dist = c1[c1Index].distanceSquared(c2[c2Index]); + if (fClosest < dist) { + return; + } + fC1Span = span1; + fC2Span = span2; + fC1StartT = span1->startT(); + fC1EndT = span1->endT(); + fC2StartT = span2->startT(); + fC2EndT = span2->endT(); + fC1Index = c1Index; + fC2Index = c2Index; + fClosest = dist; + } + + bool matesWith(const SkClosestRecord& mate SkDEBUGPARAMS(SkIntersections* i)) const { + SkOPOBJASSERT(i, fC1Span == mate.fC1Span || fC1Span->endT() <= mate.fC1Span->startT() + || mate.fC1Span->endT() <= fC1Span->startT()); + SkOPOBJASSERT(i, fC2Span == mate.fC2Span || fC2Span->endT() <= mate.fC2Span->startT() + || mate.fC2Span->endT() <= fC2Span->startT()); + return fC1Span == mate.fC1Span || fC1Span->endT() == mate.fC1Span->startT() + || fC1Span->startT() == mate.fC1Span->endT() + || fC2Span == mate.fC2Span + || fC2Span->endT() == mate.fC2Span->startT() + || fC2Span->startT() == mate.fC2Span->endT(); + } + + void merge(const SkClosestRecord& mate) { + fC1Span = mate.fC1Span; + fC2Span = mate.fC2Span; + fClosest = mate.fClosest; + fC1Index = mate.fC1Index; + fC2Index = mate.fC2Index; + } + + void reset() { + fClosest = FLT_MAX; + SkDEBUGCODE(fC1Span = nullptr); + SkDEBUGCODE(fC2Span = nullptr); + SkDEBUGCODE(fC1Index = fC2Index = -1); + } + + void update(const SkClosestRecord& mate) { + fC1StartT = SkTMin(fC1StartT, mate.fC1StartT); + fC1EndT = SkTMax(fC1EndT, mate.fC1EndT); + fC2StartT = SkTMin(fC2StartT, mate.fC2StartT); + fC2EndT = SkTMax(fC2EndT, mate.fC2EndT); + } + + const SkTSpan* fC1Span; + const SkTSpan* fC2Span; + double fC1StartT; + double fC1EndT; + double fC2StartT; + double fC2EndT; + double fClosest; + int fC1Index; + int fC2Index; +}; + +struct SkClosestSect { + SkClosestSect() + : fUsed(0) { + fClosest.push_back().reset(); + } + + bool find(const SkTSpan* span1, const SkTSpan* span2 + SkDEBUGPARAMS(SkIntersections* i)) { + SkClosestRecord* record = &fClosest[fUsed]; + record->findEnd(span1, span2, 0, 0); + record->findEnd(span1, span2, 0, span2->part().pointLast()); + record->findEnd(span1, span2, span1->part().pointLast(), 0); + record->findEnd(span1, span2, span1->part().pointLast(), span2->part().pointLast()); + if (record->fClosest == FLT_MAX) { + return false; + } + for (int index = 0; index < fUsed; ++index) { + SkClosestRecord* test = &fClosest[index]; + if (test->matesWith(*record SkDEBUGPARAMS(i))) { + if (test->fClosest > record->fClosest) { + test->merge(*record); + } + test->update(*record); + record->reset(); + return false; + } + } + ++fUsed; + fClosest.push_back().reset(); + return true; + } + + void finish(SkIntersections* intersections) const { + SkSTArray closestPtrs; + for (int index = 0; index < fUsed; ++index) { + closestPtrs.push_back(&fClosest[index]); + } + SkTQSort(closestPtrs.begin(), closestPtrs.end() + - 1); + for (int index = 0; index < fUsed; ++index) { + const SkClosestRecord* test = closestPtrs[index]; + test->addIntersection(intersections); + } + } + + // this is oversized so that an extra records can merge into final one + SkSTArray fClosest; + int fUsed; +}; + +// returns true if the rect is too small to consider + +void SkTSect::BinarySearch(SkTSect* sect1, + SkTSect* sect2, SkIntersections* intersections) { +#if DEBUG_T_SECT_DUMP > 1 + gDumpTSectNum = 0; +#endif + SkDEBUGCODE(sect1->fOppSect = sect2); + SkDEBUGCODE(sect2->fOppSect = sect1); + intersections->reset(); + intersections->setMax(sect1->fCurve.maxIntersections() + 4); // give extra for slop + SkTSpan* span1 = sect1->fHead; + SkTSpan* span2 = sect2->fHead; + int oppSect, sect = sect1->intersects(span1, sect2, span2, &oppSect); +// SkASSERT(between(0, sect, 2)); + if (!sect) { + return; + } + if (sect == 2 && oppSect == 2) { + (void) EndsEqual(sect1, sect2, intersections); + return; + } + span1->addBounded(span2, §1->fHeap); + span2->addBounded(span1, §2->fHeap); + const int kMaxCoinLoopCount = 8; + int coinLoopCount = kMaxCoinLoopCount; + double start1s SK_INIT_TO_AVOID_WARNING; + double start1e SK_INIT_TO_AVOID_WARNING; + do { + // find the largest bounds + SkTSpan* largest1 = sect1->boundsMax(); + if (!largest1) { + if (sect1->fHung) { + return; + } + break; + } + SkTSpan* largest2 = sect2->boundsMax(); + // split it + if (!largest2 || (largest1 && (largest1->fBoundsMax > largest2->fBoundsMax + || (!largest1->fCollapsed && largest2->fCollapsed)))) { + if (sect2->fHung) { + return; + } + if (largest1->fCollapsed) { + break; + } + sect1->resetRemovedEnds(); + sect2->resetRemovedEnds(); + // trim parts that don't intersect the opposite + SkTSpan* half1 = sect1->addOne(); + SkDEBUGCODE(half1->debugSetGlobalState(sect1->globalState())); + if (!half1->split(largest1, §1->fHeap)) { + break; + } + if (!sect1->trim(largest1, sect2)) { + SkOPOBJASSERT(intersections, 0); + return; + } + if (!sect1->trim(half1, sect2)) { + SkOPOBJASSERT(intersections, 0); + return; + } + } else { + if (largest2->fCollapsed) { + break; + } + sect1->resetRemovedEnds(); + sect2->resetRemovedEnds(); + // trim parts that don't intersect the opposite + SkTSpan* half2 = sect2->addOne(); + SkDEBUGCODE(half2->debugSetGlobalState(sect2->globalState())); + if (!half2->split(largest2, §2->fHeap)) { + break; + } + if (!sect2->trim(largest2, sect1)) { + SkOPOBJASSERT(intersections, 0); + return; + } + if (!sect2->trim(half2, sect1)) { + SkOPOBJASSERT(intersections, 0); + return; + } + } + sect1->validate(); + sect2->validate(); +#if DEBUG_T_SECT_LOOP_COUNT + intersections->debugBumpLoopCount(SkIntersections::kIterations_DebugLoop); +#endif + // if there are 9 or more continuous spans on both sects, suspect coincidence + if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT + && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { + if (coinLoopCount == kMaxCoinLoopCount) { + start1s = sect1->fHead->fStartT; + start1e = sect1->tail()->fEndT; + } + if (!sect1->coincidentCheck(sect2)) { + return; + } + sect1->validate(); + sect2->validate(); +#if DEBUG_T_SECT_LOOP_COUNT + intersections->debugBumpLoopCount(SkIntersections::kCoinCheck_DebugLoop); +#endif + if (!--coinLoopCount && sect1->fHead && sect2->fHead) { + /* All known working cases resolve in two tries. Sadly, cubicConicTests[0] + gets stuck in a loop. It adds an extension to allow a coincident end + perpendicular to track its intersection in the opposite curve. However, + the bounding box of the extension does not intersect the original curve, + so the extension is discarded, only to be added again the next time around. */ + sect1->coincidentForce(sect2, start1s, start1e); + sect1->validate(); + sect2->validate(); + } + } + if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT + && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { + if (!sect1->fHead) { + return; + } + sect1->computePerpendiculars(sect2, sect1->fHead, sect1->tail()); + if (!sect2->fHead) { + return; + } + sect2->computePerpendiculars(sect1, sect2->fHead, sect2->tail()); + if (!sect1->removeByPerpendicular(sect2)) { + return; + } + sect1->validate(); + sect2->validate(); +#if DEBUG_T_SECT_LOOP_COUNT + intersections->debugBumpLoopCount(SkIntersections::kComputePerp_DebugLoop); +#endif + if (sect1->collapsed() > sect1->fCurve.maxIntersections()) { + break; + } + } +#if DEBUG_T_SECT_DUMP + sect1->dumpBoth(sect2); +#endif + if (!sect1->fHead || !sect2->fHead) { + break; + } + } while (true); + SkTSpan* coincident = sect1->fCoincident; + if (coincident) { + // if there is more than one coincident span, check loosely to see if they should be joined + if (coincident->fNext) { + sect1->mergeCoincidence(sect2); + coincident = sect1->fCoincident; + } + SkASSERT(sect2->fCoincident); // courtesy check : coincidence only looks at sect 1 + do { + if (!coincident) { + return; + } + if (!coincident->fCoinStart.isMatch()) { + continue; + } + if (!coincident->fCoinEnd.isMatch()) { + continue; + } + double perpT = coincident->fCoinStart.perpT(); + if (perpT < 0) { + return; + } + int index = intersections->insertCoincident(coincident->fStartT, + perpT, coincident->pointFirst()); + if ((intersections->insertCoincident(coincident->fEndT, + coincident->fCoinEnd.perpT(), + coincident->pointLast()) < 0) && index >= 0) { + intersections->clearCoincidence(index); + } + } while ((coincident = coincident->fNext)); + } + int zeroOneSet = EndsEqual(sect1, sect2, intersections); +// if (!sect1->fHead || !sect2->fHead) { + // if the final iteration contains an end (0 or 1), + if (sect1->fRemovedStartT && !(zeroOneSet & kZeroS1Set)) { + SkTCoincident perp; // intersect perpendicular with opposite curve + perp.setPerp(sect1->fCurve, 0, sect1->fCurve[0], sect2->fCurve); + if (perp.isMatch()) { + intersections->insert(0, perp.perpT(), perp.perpPt()); + } + } + if (sect1->fRemovedEndT && !(zeroOneSet & kOneS1Set)) { + SkTCoincident perp; + perp.setPerp(sect1->fCurve, 1, sect1->pointLast(), sect2->fCurve); + if (perp.isMatch()) { + intersections->insert(1, perp.perpT(), perp.perpPt()); + } + } + if (sect2->fRemovedStartT && !(zeroOneSet & kZeroS2Set)) { + SkTCoincident perp; + perp.setPerp(sect2->fCurve, 0, sect2->fCurve[0], sect1->fCurve); + if (perp.isMatch()) { + intersections->insert(perp.perpT(), 0, perp.perpPt()); + } + } + if (sect2->fRemovedEndT && !(zeroOneSet & kOneS2Set)) { + SkTCoincident perp; + perp.setPerp(sect2->fCurve, 1, sect2->pointLast(), sect1->fCurve); + if (perp.isMatch()) { + intersections->insert(perp.perpT(), 1, perp.perpPt()); + } + } +// } + if (!sect1->fHead || !sect2->fHead) { + return; + } + sect1->recoverCollapsed(); + sect2->recoverCollapsed(); + SkTSpan* result1 = sect1->fHead; + // check heads and tails for zero and ones and insert them if we haven't already done so + const SkTSpan* head1 = result1; + if (!(zeroOneSet & kZeroS1Set) && approximately_less_than_zero(head1->fStartT)) { + const SkDPoint& start1 = sect1->fCurve[0]; + if (head1->isBounded()) { + double t = head1->closestBoundedT(start1); + if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) { + intersections->insert(0, t, start1); + } + } + } + const SkTSpan* head2 = sect2->fHead; + if (!(zeroOneSet & kZeroS2Set) && approximately_less_than_zero(head2->fStartT)) { + const SkDPoint& start2 = sect2->fCurve[0]; + if (head2->isBounded()) { + double t = head2->closestBoundedT(start2); + if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) { + intersections->insert(t, 0, start2); + } + } + } + if (!(zeroOneSet & kOneS1Set)) { + const SkTSpan* tail1 = sect1->tail(); + if (!tail1) { + return; + } + if (approximately_greater_than_one(tail1->fEndT)) { + const SkDPoint& end1 = sect1->pointLast(); + if (tail1->isBounded()) { + double t = tail1->closestBoundedT(end1); + if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) { + intersections->insert(1, t, end1); + } + } + } + } + if (!(zeroOneSet & kOneS2Set)) { + const SkTSpan* tail2 = sect2->tail(); + if (!tail2) { + return; + } + if (approximately_greater_than_one(tail2->fEndT)) { + const SkDPoint& end2 = sect2->pointLast(); + if (tail2->isBounded()) { + double t = tail2->closestBoundedT(end2); + if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) { + intersections->insert(t, 1, end2); + } + } + } + } + SkClosestSect closest; + do { + while (result1 && result1->fCoinStart.isMatch() && result1->fCoinEnd.isMatch()) { + result1 = result1->fNext; + } + if (!result1) { + break; + } + SkTSpan* result2 = sect2->fHead; + bool found = false; + while (result2) { + found |= closest.find(result1, result2 SkDEBUGPARAMS(intersections)); + result2 = result2->fNext; + } + } while ((result1 = result1->fNext)); + closest.finish(intersections); + // if there is more than one intersection and it isn't already coincident, check + int last = intersections->used() - 1; + for (int index = 0; index < last; ) { + if (intersections->isCoincident(index) && intersections->isCoincident(index + 1)) { + ++index; + continue; + } + double midT = ((*intersections)[0][index] + (*intersections)[0][index + 1]) / 2; + SkDPoint midPt = sect1->fCurve.ptAtT(midT); + // intersect perpendicular with opposite curve + SkTCoincident perp; + perp.setPerp(sect1->fCurve, midT, midPt, sect2->fCurve); + if (!perp.isMatch()) { + ++index; + continue; + } + if (intersections->isCoincident(index)) { + intersections->removeOne(index); + --last; + } else if (intersections->isCoincident(index + 1)) { + intersections->removeOne(index + 1); + --last; + } else { + intersections->setCoincident(index++); + } + intersections->setCoincident(index); + } + SkOPOBJASSERT(intersections, intersections->used() <= sect1->fCurve.maxIntersections()); +} + +int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) { + SkTQuad quad1(q1); + SkTQuad quad2(q2); + SkTSect sect1(quad1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect sect2(quad2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDConic& c, const SkDQuad& q) { + SkTConic conic(c); + SkTQuad quad(q); + SkTSect sect1(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDConic& c1, const SkDConic& c2) { + SkTConic conic1(c1); + SkTConic conic2(c2); + SkTSect sect1(conic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect sect2(conic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDCubic& c, const SkDQuad& q) { + SkTCubic cubic(c); + SkTQuad quad(q); + SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDCubic& cu, const SkDConic& co) { + SkTCubic cubic(cu); + SkTConic conic(co); + SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect sect2(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect::BinarySearch(§1, §2, this); + return used(); + +} + +int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) { + SkTCubic cubic1(c1); + SkTCubic cubic2(c2); + SkTSect sect1(cubic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect sect2(cubic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect::BinarySearch(§1, §2, this); + return used(); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTSect.h b/gfx/skia/skia/src/pathops/SkPathOpsTSect.h new file mode 100644 index 0000000000..ddd5acdf06 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTSect.h @@ -0,0 +1,372 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsTSect_DEFINED +#define SkPathOpsTSect_DEFINED + +#include "include/private/SkMacros.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkTSort.h" +#include "src/pathops/SkIntersections.h" +#include "src/pathops/SkPathOpsBounds.h" +#include "src/pathops/SkPathOpsRect.h" +#include "src/pathops/SkPathOpsTCurve.h" + +#include + +#ifdef SK_DEBUG +typedef uint8_t SkOpDebugBool; +#else +typedef bool SkOpDebugBool; +#endif + +#define SkDoubleIsNaN sk_double_isnan + +class SkTCoincident { +public: + SkTCoincident() { + this->init(); + } + + void debugInit() { +#ifdef SK_DEBUG + this->fPerpPt.fX = this->fPerpPt.fY = SK_ScalarNaN; + this->fPerpT = SK_ScalarNaN; + this->fMatch = 0xFF; +#endif + } + + char dumpIsCoincidentStr() const; + void dump() const; + + bool isMatch() const { + SkASSERT(!!fMatch == fMatch); + return SkToBool(fMatch); + } + + void init() { + fPerpT = -1; + fMatch = false; + fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN; + } + + void markCoincident() { + if (!fMatch) { + fPerpT = -1; + } + fMatch = true; + } + + const SkDPoint& perpPt() const { + return fPerpPt; + } + + double perpT() const { + return fPerpT; + } + + void setPerp(const SkTCurve& c1, double t, const SkDPoint& cPt, const SkTCurve& ); + +private: + SkDPoint fPerpPt; + double fPerpT; // perpendicular intersection on opposite curve + SkOpDebugBool fMatch; +}; + +class SkTSect; +class SkTSpan; + +struct SkTSpanBounded { + SkTSpan* fBounded; + SkTSpanBounded* fNext; +}; + +class SkTSpan { +public: + SkTSpan(const SkTCurve& curve, SkArenaAlloc& heap) { + fPart = curve.make(heap); + } + + void addBounded(SkTSpan* , SkArenaAlloc* ); + double closestBoundedT(const SkDPoint& pt) const; + bool contains(double t) const; + + void debugInit(const SkTCurve& curve, SkArenaAlloc& heap) { +#ifdef SK_DEBUG + SkTCurve* dummy = curve.make(heap); + dummy->debugInit(); + init(*dummy); + initBounds(*dummy); + fCoinStart.init(); + fCoinEnd.init(); +#endif + } + + const SkTSect* debugOpp() const; + +#ifdef SK_DEBUG + void debugSetGlobalState(SkOpGlobalState* state) { + fDebugGlobalState = state; + } + + const SkTSpan* debugSpan(int ) const; + const SkTSpan* debugT(double t) const; + bool debugIsBefore(const SkTSpan* span) const; +#endif + void dump() const; + void dumpAll() const; + void dumpBounded(int id) const; + void dumpBounds() const; + void dumpCoin() const; + + double endT() const { + return fEndT; + } + + SkTSpan* findOppSpan(const SkTSpan* opp) const; + + SkTSpan* findOppT(double t) const { + SkTSpan* result = oppT(t); + SkOPASSERT(result); + return result; + } + + SkDEBUGCODE(SkOpGlobalState* globalState() const { return fDebugGlobalState; }) + + bool hasOppT(double t) const { + return SkToBool(oppT(t)); + } + + int hullsIntersect(SkTSpan* span, bool* start, bool* oppStart); + void init(const SkTCurve& ); + bool initBounds(const SkTCurve& ); + + bool isBounded() const { + return fBounded != nullptr; + } + + bool linearsIntersect(SkTSpan* span); + double linearT(const SkDPoint& ) const; + + void markCoincident() { + fCoinStart.markCoincident(); + fCoinEnd.markCoincident(); + } + + const SkTSpan* next() const { + return fNext; + } + + bool onlyEndPointsInCommon(const SkTSpan* opp, bool* start, + bool* oppStart, bool* ptsInCommon); + + const SkTCurve& part() const { + return *fPart; + } + + int pointCount() const { + return fPart->pointCount(); + } + + const SkDPoint& pointFirst() const { + return (*fPart)[0]; + } + + const SkDPoint& pointLast() const { + return (*fPart)[fPart->pointLast()]; + } + + bool removeAllBounded(); + bool removeBounded(const SkTSpan* opp); + + void reset() { + fBounded = nullptr; + } + + void resetBounds(const SkTCurve& curve) { + fIsLinear = fIsLine = false; + initBounds(curve); + } + + bool split(SkTSpan* work, SkArenaAlloc* heap) { + return splitAt(work, (work->fStartT + work->fEndT) * 0.5, heap); + } + + bool splitAt(SkTSpan* work, double t, SkArenaAlloc* heap); + + double startT() const { + return fStartT; + } + +private: + + // implementation is for testing only + int debugID() const { + return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); + } + + void dumpID() const; + + int hullCheck(const SkTSpan* opp, bool* start, bool* oppStart); + int linearIntersects(const SkTCurve& ) const; + SkTSpan* oppT(double t) const; + + void validate() const; + void validateBounded() const; + void validatePerpT(double oppT) const; + void validatePerpPt(double t, const SkDPoint& ) const; + + SkTCurve* fPart; + SkTCoincident fCoinStart; + SkTCoincident fCoinEnd; + SkTSpanBounded* fBounded; + SkTSpan* fPrev; + SkTSpan* fNext; + SkDRect fBounds; + double fStartT; + double fEndT; + double fBoundsMax; + SkOpDebugBool fCollapsed; + SkOpDebugBool fHasPerp; + SkOpDebugBool fIsLinear; + SkOpDebugBool fIsLine; + SkOpDebugBool fDeleted; + SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); + SkDEBUGCODE(SkTSect* fDebugSect); + PATH_OPS_DEBUG_T_SECT_CODE(int fID); + friend class SkTSect; +}; + +class SkTSect { +public: + SkTSect(const SkTCurve& c + SkDEBUGPARAMS(SkOpGlobalState* ) PATH_OPS_DEBUG_T_SECT_PARAMS(int id)); + static void BinarySearch(SkTSect* sect1, SkTSect* sect2, + SkIntersections* intersections); + + SkDEBUGCODE(SkOpGlobalState* globalState() { return fDebugGlobalState; }) + bool hasBounded(const SkTSpan* ) const; + + const SkTSect* debugOpp() const { + return SkDEBUGRELEASE(fOppSect, nullptr); + } + +#ifdef SK_DEBUG + const SkTSpan* debugSpan(int id) const; + const SkTSpan* debugT(double t) const; +#endif + void dump() const; + void dumpBoth(SkTSect* ) const; + void dumpBounded(int id) const; + void dumpBounds() const; + void dumpCoin() const; + void dumpCoinCurves() const; + void dumpCurves() const; + +private: + enum { + kZeroS1Set = 1, + kOneS1Set = 2, + kZeroS2Set = 4, + kOneS2Set = 8 + }; + + SkTSpan* addFollowing(SkTSpan* prior); + void addForPerp(SkTSpan* span, double t); + SkTSpan* addOne(); + + SkTSpan* addSplitAt(SkTSpan* span, double t) { + SkTSpan* result = this->addOne(); + SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); + result->splitAt(span, t, &fHeap); + result->initBounds(fCurve); + span->initBounds(fCurve); + return result; + } + + bool binarySearchCoin(SkTSect* , double tStart, double tStep, double* t, + double* oppT, SkTSpan** oppFirst); + SkTSpan* boundsMax(); + bool coincidentCheck(SkTSect* sect2); + void coincidentForce(SkTSect* sect2, double start1s, double start1e); + bool coincidentHasT(double t); + int collapsed() const; + void computePerpendiculars(SkTSect* sect2, SkTSpan* first, + SkTSpan* last); + int countConsecutiveSpans(SkTSpan* first, + SkTSpan** last) const; + + int debugID() const { + return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); + } + + bool deleteEmptySpans(); + void dumpCommon(const SkTSpan* ) const; + void dumpCommonCurves(const SkTSpan* ) const; + static int EndsEqual(const SkTSect* sect1, const SkTSect* sect2, + SkIntersections* ); + bool extractCoincident(SkTSect* sect2, SkTSpan* first, + SkTSpan* last, SkTSpan** result); + SkTSpan* findCoincidentRun(SkTSpan* first, SkTSpan** lastPtr); + int intersects(SkTSpan* span, SkTSect* opp, + SkTSpan* oppSpan, int* oppResult); + bool isParallel(const SkDLine& thisLine, const SkTSect* opp) const; + int linesIntersect(SkTSpan* span, SkTSect* opp, + SkTSpan* oppSpan, SkIntersections* ); + bool markSpanGone(SkTSpan* span); + bool matchedDirection(double t, const SkTSect* sect2, double t2) const; + void matchedDirCheck(double t, const SkTSect* sect2, double t2, + bool* calcMatched, bool* oppMatched) const; + void mergeCoincidence(SkTSect* sect2); + + const SkDPoint& pointLast() const { + return fCurve[fCurve.pointLast()]; + } + + SkTSpan* prev(SkTSpan* ) const; + bool removeByPerpendicular(SkTSect* opp); + void recoverCollapsed(); + bool removeCoincident(SkTSpan* span, bool isBetween); + void removeAllBut(const SkTSpan* keep, SkTSpan* span, + SkTSect* opp); + bool removeSpan(SkTSpan* span); + void removeSpanRange(SkTSpan* first, SkTSpan* last); + bool removeSpans(SkTSpan* span, SkTSect* opp); + void removedEndCheck(SkTSpan* span); + + void resetRemovedEnds() { + fRemovedStartT = fRemovedEndT = false; + } + + SkTSpan* spanAtT(double t, SkTSpan** priorSpan); + SkTSpan* tail(); + bool trim(SkTSpan* span, SkTSect* opp); + bool unlinkSpan(SkTSpan* span); + bool updateBounded(SkTSpan* first, SkTSpan* last, + SkTSpan* oppFirst); + void validate() const; + void validateBounded() const; + + const SkTCurve& fCurve; + SkSTArenaAlloc<1024> fHeap; + SkTSpan* fHead; + SkTSpan* fCoincident; + SkTSpan* fDeleted; + int fActiveCount; + bool fRemovedStartT; + bool fRemovedEndT; + bool fHung; + SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); + SkDEBUGCODE(SkTSect* fOppSect); + PATH_OPS_DEBUG_T_SECT_CODE(int fID); + PATH_OPS_DEBUG_T_SECT_CODE(int fDebugCount); +#if DEBUG_T_SECT + int fDebugAllocatedCount; +#endif + friend class SkTSpan; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp b/gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp new file mode 100644 index 0000000000..ff1d177dc5 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/pathops/SkOpEdgeBuilder.h" +#include "src/pathops/SkPathOpsCommon.h" + +bool TightBounds(const SkPath& path, SkRect* result) { + SkPath::RawIter iter(path); + SkRect moveBounds = { SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin }; + bool wellBehaved = true; + SkPath::Verb verb; + do { + SkPoint pts[4]; + verb = iter.next(pts); + switch (verb) { + case SkPath::kMove_Verb: + moveBounds.fLeft = SkTMin(moveBounds.fLeft, pts[0].fX); + moveBounds.fTop = SkTMin(moveBounds.fTop, pts[0].fY); + moveBounds.fRight = SkTMax(moveBounds.fRight, pts[0].fX); + moveBounds.fBottom = SkTMax(moveBounds.fBottom, pts[0].fY); + break; + case SkPath::kQuad_Verb: + case SkPath::kConic_Verb: + if (!wellBehaved) { + break; + } + wellBehaved &= between(pts[0].fX, pts[1].fX, pts[2].fX); + wellBehaved &= between(pts[0].fY, pts[1].fY, pts[2].fY); + break; + case SkPath::kCubic_Verb: + if (!wellBehaved) { + break; + } + wellBehaved &= between(pts[0].fX, pts[1].fX, pts[3].fX); + wellBehaved &= between(pts[0].fY, pts[1].fY, pts[3].fY); + wellBehaved &= between(pts[0].fX, pts[2].fX, pts[3].fX); + wellBehaved &= between(pts[0].fY, pts[2].fY, pts[3].fY); + break; + default: + break; + } + } while (verb != SkPath::kDone_Verb); + if (wellBehaved) { + *result = path.getBounds(); + return true; + } + SkSTArenaAlloc<4096> allocator; // FIXME: constant-ize, tune + SkOpContour contour; + SkOpContourHead* contourList = static_cast(&contour); + SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(false) + SkDEBUGPARAMS(nullptr)); + // turn path into list of segments + SkOpEdgeBuilder builder(path, contourList, &globalState); + if (!builder.finish()) { + return false; + } + if (!SortContourList(&contourList, false, false)) { + *result = moveBounds; + return true; + } + SkOpContour* current = contourList; + SkPathOpsBounds bounds = current->bounds(); + while ((current = current->next())) { + bounds.add(current->bounds()); + } + *result = bounds; + if (!moveBounds.isEmpty()) { + result->join(moveBounds); + } + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp b/gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp new file mode 100644 index 0000000000..92c3a7ae87 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp @@ -0,0 +1,255 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/private/SkFloatBits.h" +#include "src/core/SkArenaAlloc.h" +#include "src/pathops/SkOpCoincidence.h" +#include "src/pathops/SkPathOpsTypes.h" + +static bool arguments_denormalized(float a, float b, int epsilon) { + float denormalizedCheck = FLT_EPSILON * epsilon / 2; + return fabsf(a) <= denormalizedCheck && fabsf(b) <= denormalizedCheck; +} + +// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ +// FIXME: move to SkFloatBits.h +static bool equal_ulps(float a, float b, int epsilon, int depsilon) { + if (arguments_denormalized(a, b, depsilon)) { + return true; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool equal_ulps_no_normal_check(float a, float b, int epsilon, int depsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool equal_ulps_pin(float a, float b, int epsilon, int depsilon) { + if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { + return false; + } + if (arguments_denormalized(a, b, depsilon)) { + return true; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool d_equal_ulps(float a, float b, int epsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool not_equal_ulps(float a, float b, int epsilon) { + if (arguments_denormalized(a, b, epsilon)) { + return false; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits >= bBits + epsilon || bBits >= aBits + epsilon; +} + +static bool not_equal_ulps_pin(float a, float b, int epsilon) { + if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { + return false; + } + if (arguments_denormalized(a, b, epsilon)) { + return false; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits >= bBits + epsilon || bBits >= aBits + epsilon; +} + +static bool d_not_equal_ulps(float a, float b, int epsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits >= bBits + epsilon || bBits >= aBits + epsilon; +} + +static bool less_ulps(float a, float b, int epsilon) { + if (arguments_denormalized(a, b, epsilon)) { + return a <= b - FLT_EPSILON * epsilon; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits <= bBits - epsilon; +} + +static bool less_or_equal_ulps(float a, float b, int epsilon) { + if (arguments_denormalized(a, b, epsilon)) { + return a < b + FLT_EPSILON * epsilon; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon; +} + +// equality using the same error term as between +bool AlmostBequalUlps(float a, float b) { + const int UlpsEpsilon = 2; + return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostPequalUlps(float a, float b) { + const int UlpsEpsilon = 8; + return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostDequalUlps(float a, float b) { + const int UlpsEpsilon = 16; + return d_equal_ulps(a, b, UlpsEpsilon); +} + +bool AlmostDequalUlps(double a, double b) { + if (fabs(a) < SK_ScalarMax && fabs(b) < SK_ScalarMax) { + return AlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); + } + return fabs(a - b) / SkTMax(fabs(a), fabs(b)) < FLT_EPSILON * 16; +} + +bool AlmostEqualUlps(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostEqualUlpsNoNormalCheck(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps_no_normal_check(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostEqualUlps_Pin(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps_pin(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool NotAlmostEqualUlps(float a, float b) { + const int UlpsEpsilon = 16; + return not_equal_ulps(a, b, UlpsEpsilon); +} + +bool NotAlmostEqualUlps_Pin(float a, float b) { + const int UlpsEpsilon = 16; + return not_equal_ulps_pin(a, b, UlpsEpsilon); +} + +bool NotAlmostDequalUlps(float a, float b) { + const int UlpsEpsilon = 16; + return d_not_equal_ulps(a, b, UlpsEpsilon); +} + +bool RoughlyEqualUlps(float a, float b) { + const int UlpsEpsilon = 256; + const int DUlpsEpsilon = 1024; + return equal_ulps(a, b, UlpsEpsilon, DUlpsEpsilon); +} + +bool AlmostBetweenUlps(float a, float b, float c) { + const int UlpsEpsilon = 2; + return a <= c ? less_or_equal_ulps(a, b, UlpsEpsilon) && less_or_equal_ulps(b, c, UlpsEpsilon) + : less_or_equal_ulps(b, a, UlpsEpsilon) && less_or_equal_ulps(c, b, UlpsEpsilon); +} + +bool AlmostLessUlps(float a, float b) { + const int UlpsEpsilon = 16; + return less_ulps(a, b, UlpsEpsilon); +} + +bool AlmostLessOrEqualUlps(float a, float b) { + const int UlpsEpsilon = 16; + return less_or_equal_ulps(a, b, UlpsEpsilon); +} + +int UlpsDistance(float a, float b) { + SkFloatIntUnion floatIntA, floatIntB; + floatIntA.fFloat = a; + floatIntB.fFloat = b; + // Different signs means they do not match. + if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) { + // Check for equality to make sure +0 == -0 + return a == b ? 0 : SK_MaxS32; + } + // Find the difference in ULPs. + return SkTAbs(floatIntA.fSignBitInt - floatIntB.fSignBitInt); +} + +// cube root approximation using bit hack for 64-bit float +// adapted from Kahan's cbrt +static double cbrt_5d(double d) { + const unsigned int B1 = 715094163; + double t = 0.0; + unsigned int* pt = (unsigned int*) &t; + unsigned int* px = (unsigned int*) &d; + pt[1] = px[1] / 3 + B1; + return t; +} + +// iterative cube root approximation using Halley's method (double) +static double cbrta_halleyd(const double a, const double R) { + const double a3 = a * a * a; + const double b = a * (a3 + R + R) / (a3 + a3 + R); + return b; +} + +// cube root approximation using 3 iterations of Halley's method (double) +static double halley_cbrt3d(double d) { + double a = cbrt_5d(d); + a = cbrta_halleyd(a, d); + a = cbrta_halleyd(a, d); + return cbrta_halleyd(a, d); +} + +double SkDCubeRoot(double x) { + if (approximately_zero_cubed(x)) { + return 0; + } + double result = halley_cbrt3d(fabs(x)); + if (x < 0) { + result = -result; + } + return result; +} + +SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head, + SkArenaAlloc* allocator + SkDEBUGPARAMS(bool debugSkipAssert) + SkDEBUGPARAMS(const char* testName)) + : fAllocator(allocator) + , fCoincidence(nullptr) + , fContourHead(head) + , fNested(0) + , fWindingFailed(false) + , fPhase(SkOpPhase::kIntersecting) + SkDEBUGPARAMS(fDebugTestName(testName)) + SkDEBUGPARAMS(fAngleID(0)) + SkDEBUGPARAMS(fCoinID(0)) + SkDEBUGPARAMS(fContourID(0)) + SkDEBUGPARAMS(fPtTID(0)) + SkDEBUGPARAMS(fSegmentID(0)) + SkDEBUGPARAMS(fSpanID(0)) + SkDEBUGPARAMS(fDebugSkipAssert(debugSkipAssert)) { +#if DEBUG_T_SECT_LOOP_COUNT + debugResetLoopCounts(); +#endif +#if DEBUG_COIN + fPreviousFuncName = nullptr; +#endif +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTypes.h b/gfx/skia/skia/src/pathops/SkPathOpsTypes.h new file mode 100644 index 0000000000..0edd1ba9e7 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTypes.h @@ -0,0 +1,624 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsTypes_DEFINED +#define SkPathOpsTypes_DEFINED + +#include // for FLT_EPSILON + +#include "include/core/SkPath.h" +#include "include/core/SkScalar.h" +#include "include/pathops/SkPathOps.h" +#include "include/private/SkFloatingPoint.h" +#include "include/private/SkSafe_math.h" +#include "src/pathops/SkPathOpsDebug.h" + +enum SkPathOpsMask { + kWinding_PathOpsMask = -1, + kNo_PathOpsMask = 0, + kEvenOdd_PathOpsMask = 1 +}; + +class SkArenaAlloc; +class SkOpCoincidence; +class SkOpContour; +class SkOpContourHead; +class SkIntersections; +class SkIntersectionHelper; + +enum class SkOpPhase : char { + kNoChange, + kIntersecting, + kWalking, + kFixWinding, +}; + +class SkOpGlobalState { +public: + SkOpGlobalState(SkOpContourHead* head, + SkArenaAlloc* allocator SkDEBUGPARAMS(bool debugSkipAssert) + SkDEBUGPARAMS(const char* testName)); + + enum { + kMaxWindingTries = 10 + }; + + bool allocatedOpSpan() const { + return fAllocatedOpSpan; + } + + SkArenaAlloc* allocator() { + return fAllocator; + } + + void bumpNested() { + ++fNested; + } + + void clearNested() { + fNested = 0; + } + + SkOpCoincidence* coincidence() { + return fCoincidence; + } + + SkOpContourHead* contourHead() { + return fContourHead; + } + +#ifdef SK_DEBUG + const class SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; + SkOpContour* debugContour(int id) const; + const class SkOpPtT* debugPtT(int id) const; +#endif + + static bool DebugRunFail(); + +#ifdef SK_DEBUG + const class SkOpSegment* debugSegment(int id) const; + bool debugSkipAssert() const { return fDebugSkipAssert; } + const class SkOpSpanBase* debugSpan(int id) const; + const char* debugTestName() const { return fDebugTestName; } +#endif + +#if DEBUG_T_SECT_LOOP_COUNT + void debugAddLoopCount(SkIntersections* , const SkIntersectionHelper& , + const SkIntersectionHelper& ); + void debugDoYourWorst(SkOpGlobalState* ); + void debugLoopReport(); + void debugResetLoopCounts(); +#endif + +#if DEBUG_COINCIDENCE + void debugSetCheckHealth(bool check) { fDebugCheckHealth = check; } + bool debugCheckHealth() const { return fDebugCheckHealth; } +#endif + +#if DEBUG_VALIDATE || DEBUG_COIN + void debugSetPhase(const char* funcName DEBUG_COIN_DECLARE_PARAMS()) const; +#endif + +#if DEBUG_COIN + void debugAddToCoinChangedDict(); + void debugAddToGlobalCoinDicts(); + SkPathOpsDebug::CoinDict* debugCoinChangedDict() { return &fCoinChangedDict; } + const SkPathOpsDebug::CoinDictEntry& debugCoinDictEntry() const { return fCoinDictEntry; } + + static void DumpCoinDict(); +#endif + + + int nested() const { + return fNested; + } + +#ifdef SK_DEBUG + int nextAngleID() { + return ++fAngleID; + } + + int nextCoinID() { + return ++fCoinID; + } + + int nextContourID() { + return ++fContourID; + } + + int nextPtTID() { + return ++fPtTID; + } + + int nextSegmentID() { + return ++fSegmentID; + } + + int nextSpanID() { + return ++fSpanID; + } +#endif + + SkOpPhase phase() const { + return fPhase; + } + + void resetAllocatedOpSpan() { + fAllocatedOpSpan = false; + } + + void setAllocatedOpSpan() { + fAllocatedOpSpan = true; + } + + void setCoincidence(SkOpCoincidence* coincidence) { + fCoincidence = coincidence; + } + + void setContourHead(SkOpContourHead* contourHead) { + fContourHead = contourHead; + } + + void setPhase(SkOpPhase phase) { + if (SkOpPhase::kNoChange == phase) { + return; + } + SkASSERT(fPhase != phase); + fPhase = phase; + } + + // called in very rare cases where angles are sorted incorrectly -- signfies op will fail + void setWindingFailed() { + fWindingFailed = true; + } + + bool windingFailed() const { + return fWindingFailed; + } + +private: + SkArenaAlloc* fAllocator; + SkOpCoincidence* fCoincidence; + SkOpContourHead* fContourHead; + int fNested; + bool fAllocatedOpSpan; + bool fWindingFailed; + SkOpPhase fPhase; +#ifdef SK_DEBUG + const char* fDebugTestName; + void* fDebugReporter; + int fAngleID; + int fCoinID; + int fContourID; + int fPtTID; + int fSegmentID; + int fSpanID; + bool fDebugSkipAssert; +#endif +#if DEBUG_T_SECT_LOOP_COUNT + int fDebugLoopCount[3]; + SkPath::Verb fDebugWorstVerb[6]; + SkPoint fDebugWorstPts[24]; + float fDebugWorstWeight[6]; +#endif +#if DEBUG_COIN + SkPathOpsDebug::CoinDict fCoinChangedDict; + SkPathOpsDebug::CoinDict fCoinVisitedDict; + SkPathOpsDebug::CoinDictEntry fCoinDictEntry; + const char* fPreviousFuncName; +#endif +#if DEBUG_COINCIDENCE + bool fDebugCheckHealth; +#endif +}; + +#ifdef SK_DEBUG +#if DEBUG_COINCIDENCE +#define SkOPASSERT(cond) SkASSERT((this->globalState() && \ + (this->globalState()->debugCheckHealth() || \ + this->globalState()->debugSkipAssert())) || (cond)) +#else +#define SkOPASSERT(cond) SkASSERT((this->globalState() && \ + this->globalState()->debugSkipAssert()) || (cond)) +#endif +#define SkOPOBJASSERT(obj, cond) SkASSERT((obj->globalState() && \ + obj->globalState()->debugSkipAssert()) || (cond)) +#else +#define SkOPASSERT(cond) +#define SkOPOBJASSERT(obj, cond) +#endif + +// Use Almost Equal when comparing coordinates. Use epsilon to compare T values. +bool AlmostEqualUlps(float a, float b); +inline bool AlmostEqualUlps(double a, double b) { + return AlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostEqualUlpsNoNormalCheck(float a, float b); +inline bool AlmostEqualUlpsNoNormalCheck(double a, double b) { + return AlmostEqualUlpsNoNormalCheck(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostEqualUlps_Pin(float a, float b); +inline bool AlmostEqualUlps_Pin(double a, double b) { + return AlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +// Use Almost Dequal when comparing should not special case denormalized values. +bool AlmostDequalUlps(float a, float b); +bool AlmostDequalUlps(double a, double b); + +bool NotAlmostEqualUlps(float a, float b); +inline bool NotAlmostEqualUlps(double a, double b) { + return NotAlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool NotAlmostEqualUlps_Pin(float a, float b); +inline bool NotAlmostEqualUlps_Pin(double a, double b) { + return NotAlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool NotAlmostDequalUlps(float a, float b); +inline bool NotAlmostDequalUlps(double a, double b) { + return NotAlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +// Use Almost Bequal when comparing coordinates in conjunction with between. +bool AlmostBequalUlps(float a, float b); +inline bool AlmostBequalUlps(double a, double b) { + return AlmostBequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostPequalUlps(float a, float b); +inline bool AlmostPequalUlps(double a, double b) { + return AlmostPequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool RoughlyEqualUlps(float a, float b); +inline bool RoughlyEqualUlps(double a, double b) { + return RoughlyEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostLessUlps(float a, float b); +inline bool AlmostLessUlps(double a, double b) { + return AlmostLessUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostLessOrEqualUlps(float a, float b); +inline bool AlmostLessOrEqualUlps(double a, double b) { + return AlmostLessOrEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostBetweenUlps(float a, float b, float c); +inline bool AlmostBetweenUlps(double a, double b, double c) { + return AlmostBetweenUlps(SkDoubleToScalar(a), SkDoubleToScalar(b), SkDoubleToScalar(c)); +} + +int UlpsDistance(float a, float b); +inline int UlpsDistance(double a, double b) { + return UlpsDistance(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +// FLT_EPSILON == 1.19209290E-07 == 1 / (2 ^ 23) +// DBL_EPSILON == 2.22045e-16 +const double FLT_EPSILON_CUBED = FLT_EPSILON * FLT_EPSILON * FLT_EPSILON; +const double FLT_EPSILON_HALF = FLT_EPSILON / 2; +const double FLT_EPSILON_DOUBLE = FLT_EPSILON * 2; +const double FLT_EPSILON_ORDERABLE_ERR = FLT_EPSILON * 16; +const double FLT_EPSILON_SQUARED = FLT_EPSILON * FLT_EPSILON; +// Use a compile-time constant for FLT_EPSILON_SQRT to avoid initializers. +// A 17 digit constant guarantees exact results. +const double FLT_EPSILON_SQRT = 0.00034526697709225118; // sqrt(FLT_EPSILON); +const double FLT_EPSILON_INVERSE = 1 / FLT_EPSILON; +const double DBL_EPSILON_ERR = DBL_EPSILON * 4; // FIXME: tune -- allow a few bits of error +const double DBL_EPSILON_SUBDIVIDE_ERR = DBL_EPSILON * 16; +const double ROUGH_EPSILON = FLT_EPSILON * 64; +const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; +const double WAY_ROUGH_EPSILON = FLT_EPSILON * 2048; +const double BUMP_EPSILON = FLT_EPSILON * 4096; + +const SkScalar INVERSE_NUMBER_RANGE = FLT_EPSILON_ORDERABLE_ERR; + +inline bool zero_or_one(double x) { + return x == 0 || x == 1; +} + +inline bool approximately_zero(double x) { + return fabs(x) < FLT_EPSILON; +} + +inline bool precisely_zero(double x) { + return fabs(x) < DBL_EPSILON_ERR; +} + +inline bool precisely_subdivide_zero(double x) { + return fabs(x) < DBL_EPSILON_SUBDIVIDE_ERR; +} + +inline bool approximately_zero(float x) { + return fabs(x) < FLT_EPSILON; +} + +inline bool approximately_zero_cubed(double x) { + return fabs(x) < FLT_EPSILON_CUBED; +} + +inline bool approximately_zero_half(double x) { + return fabs(x) < FLT_EPSILON_HALF; +} + +inline bool approximately_zero_double(double x) { + return fabs(x) < FLT_EPSILON_DOUBLE; +} + +inline bool approximately_zero_orderable(double x) { + return fabs(x) < FLT_EPSILON_ORDERABLE_ERR; +} + +inline bool approximately_zero_squared(double x) { + return fabs(x) < FLT_EPSILON_SQUARED; +} + +inline bool approximately_zero_sqrt(double x) { + return fabs(x) < FLT_EPSILON_SQRT; +} + +inline bool roughly_zero(double x) { + return fabs(x) < ROUGH_EPSILON; +} + +inline bool approximately_zero_inverse(double x) { + return fabs(x) > FLT_EPSILON_INVERSE; +} + +inline bool approximately_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * FLT_EPSILON); +} + +inline bool precisely_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * DBL_EPSILON); +} + +inline bool roughly_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * ROUGH_EPSILON); +} + +// Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use +// AlmostEqualUlps instead. +inline bool approximately_equal(double x, double y) { + return approximately_zero(x - y); +} + +inline bool precisely_equal(double x, double y) { + return precisely_zero(x - y); +} + +inline bool precisely_subdivide_equal(double x, double y) { + return precisely_subdivide_zero(x - y); +} + +inline bool approximately_equal_half(double x, double y) { + return approximately_zero_half(x - y); +} + +inline bool approximately_equal_double(double x, double y) { + return approximately_zero_double(x - y); +} + +inline bool approximately_equal_orderable(double x, double y) { + return approximately_zero_orderable(x - y); +} + +inline bool approximately_equal_squared(double x, double y) { + return approximately_equal(x, y); +} + +inline bool approximately_greater(double x, double y) { + return x - FLT_EPSILON >= y; +} + +inline bool approximately_greater_double(double x, double y) { + return x - FLT_EPSILON_DOUBLE >= y; +} + +inline bool approximately_greater_orderable(double x, double y) { + return x - FLT_EPSILON_ORDERABLE_ERR >= y; +} + +inline bool approximately_greater_or_equal(double x, double y) { + return x + FLT_EPSILON > y; +} + +inline bool approximately_greater_or_equal_double(double x, double y) { + return x + FLT_EPSILON_DOUBLE > y; +} + +inline bool approximately_greater_or_equal_orderable(double x, double y) { + return x + FLT_EPSILON_ORDERABLE_ERR > y; +} + +inline bool approximately_lesser(double x, double y) { + return x + FLT_EPSILON <= y; +} + +inline bool approximately_lesser_double(double x, double y) { + return x + FLT_EPSILON_DOUBLE <= y; +} + +inline bool approximately_lesser_orderable(double x, double y) { + return x + FLT_EPSILON_ORDERABLE_ERR <= y; +} + +inline bool approximately_lesser_or_equal(double x, double y) { + return x - FLT_EPSILON < y; +} + +inline bool approximately_lesser_or_equal_double(double x, double y) { + return x - FLT_EPSILON_DOUBLE < y; +} + +inline bool approximately_lesser_or_equal_orderable(double x, double y) { + return x - FLT_EPSILON_ORDERABLE_ERR < y; +} + +inline bool approximately_greater_than_one(double x) { + return x > 1 - FLT_EPSILON; +} + +inline bool precisely_greater_than_one(double x) { + return x > 1 - DBL_EPSILON_ERR; +} + +inline bool approximately_less_than_zero(double x) { + return x < FLT_EPSILON; +} + +inline bool precisely_less_than_zero(double x) { + return x < DBL_EPSILON_ERR; +} + +inline bool approximately_negative(double x) { + return x < FLT_EPSILON; +} + +inline bool approximately_negative_orderable(double x) { + return x < FLT_EPSILON_ORDERABLE_ERR; +} + +inline bool precisely_negative(double x) { + return x < DBL_EPSILON_ERR; +} + +inline bool approximately_one_or_less(double x) { + return x < 1 + FLT_EPSILON; +} + +inline bool approximately_one_or_less_double(double x) { + return x < 1 + FLT_EPSILON_DOUBLE; +} + +inline bool approximately_positive(double x) { + return x > -FLT_EPSILON; +} + +inline bool approximately_positive_squared(double x) { + return x > -(FLT_EPSILON_SQUARED); +} + +inline bool approximately_zero_or_more(double x) { + return x > -FLT_EPSILON; +} + +inline bool approximately_zero_or_more_double(double x) { + return x > -FLT_EPSILON_DOUBLE; +} + +inline bool approximately_between_orderable(double a, double b, double c) { + return a <= c + ? approximately_negative_orderable(a - b) && approximately_negative_orderable(b - c) + : approximately_negative_orderable(b - a) && approximately_negative_orderable(c - b); +} + +inline bool approximately_between(double a, double b, double c) { + return a <= c ? approximately_negative(a - b) && approximately_negative(b - c) + : approximately_negative(b - a) && approximately_negative(c - b); +} + +inline bool precisely_between(double a, double b, double c) { + return a <= c ? precisely_negative(a - b) && precisely_negative(b - c) + : precisely_negative(b - a) && precisely_negative(c - b); +} + +// returns true if (a <= b <= c) || (a >= b >= c) +inline bool between(double a, double b, double c) { + SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0) + || (precisely_zero(a) && precisely_zero(b) && precisely_zero(c))); + return (a - b) * (c - b) <= 0; +} + +inline bool roughly_equal(double x, double y) { + return fabs(x - y) < ROUGH_EPSILON; +} + +inline bool roughly_negative(double x) { + return x < ROUGH_EPSILON; +} + +inline bool roughly_between(double a, double b, double c) { + return a <= c ? roughly_negative(a - b) && roughly_negative(b - c) + : roughly_negative(b - a) && roughly_negative(c - b); +} + +inline bool more_roughly_equal(double x, double y) { + return fabs(x - y) < MORE_ROUGH_EPSILON; +} + +struct SkDPoint; +struct SkDVector; +struct SkDLine; +struct SkDQuad; +struct SkDConic; +struct SkDCubic; +struct SkDRect; + +inline SkPath::Verb SkPathOpsPointsToVerb(int points) { + int verb = (1 << points) >> 1; +#ifdef SK_DEBUG + switch (points) { + case 0: SkASSERT(SkPath::kMove_Verb == verb); break; + case 1: SkASSERT(SkPath::kLine_Verb == verb); break; + case 2: SkASSERT(SkPath::kQuad_Verb == verb); break; + case 3: SkASSERT(SkPath::kCubic_Verb == verb); break; + default: SkDEBUGFAIL("should not be here"); + } +#endif + return (SkPath::Verb)verb; +} + +inline int SkPathOpsVerbToPoints(SkPath::Verb verb) { + int points = (int) verb - (((int) verb + 1) >> 2); +#ifdef SK_DEBUG + switch (verb) { + case SkPath::kLine_Verb: SkASSERT(1 == points); break; + case SkPath::kQuad_Verb: SkASSERT(2 == points); break; + case SkPath::kConic_Verb: SkASSERT(2 == points); break; + case SkPath::kCubic_Verb: SkASSERT(3 == points); break; + default: SkDEBUGFAIL("should not get here"); + } +#endif + return points; +} + +inline double SkDInterp(double A, double B, double t) { + return A + (B - A) * t; +} + +double SkDCubeRoot(double x); + +/* Returns -1 if negative, 0 if zero, 1 if positive +*/ +inline int SkDSign(double x) { + return (x > 0) - (x < 0); +} + +/* Returns 0 if negative, 1 if zero, 2 if positive +*/ +inline int SKDSide(double x) { + return (x > 0) + (x >= 0); +} + +/* Returns 1 if negative, 2 if zero, 4 if positive +*/ +inline int SkDSideBit(double x) { + return 1 << SKDSide(x); +} + +inline double SkPinT(double t) { + return precisely_less_than_zero(t) ? 0 : precisely_greater_than_one(t) ? 1 : t; +} + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp b/gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp new file mode 100644 index 0000000000..4eb7298f3a --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp @@ -0,0 +1,425 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// given a prospective edge, compute its initial winding by projecting a ray +// if the ray hits another edge + // if the edge doesn't have a winding yet, hop up to that edge and start over + // concern : check for hops forming a loop + // if the edge is unsortable, or + // the intersection is nearly at the ends, or + // the tangent at the intersection is nearly coincident to the ray, + // choose a different ray and try again + // concern : if it is unable to succeed after N tries, try another edge? direction? +// if no edge is hit, compute the winding directly + +// given the top span, project the most perpendicular ray and look for intersections + // let's try up and then down. What the hey + +// bestXY is initialized by caller with basePt + +#include "src/pathops/SkOpContour.h" +#include "src/pathops/SkOpSegment.h" +#include "src/pathops/SkPathOpsCurve.h" + +#include + +enum class SkOpRayDir { + kLeft, + kTop, + kRight, + kBottom, +}; + +#if DEBUG_WINDING +const char* gDebugRayDirName[] = { + "kLeft", + "kTop", + "kRight", + "kBottom" +}; +#endif + +static int xy_index(SkOpRayDir dir) { + return static_cast(dir) & 1; +} + +static SkScalar pt_xy(const SkPoint& pt, SkOpRayDir dir) { + return (&pt.fX)[xy_index(dir)]; +} + +static SkScalar pt_yx(const SkPoint& pt, SkOpRayDir dir) { + return (&pt.fX)[!xy_index(dir)]; +} + +static double pt_dxdy(const SkDVector& v, SkOpRayDir dir) { + return (&v.fX)[xy_index(dir)]; +} + +static double pt_dydx(const SkDVector& v, SkOpRayDir dir) { + return (&v.fX)[!xy_index(dir)]; +} + +static SkScalar rect_side(const SkRect& r, SkOpRayDir dir) { + return (&r.fLeft)[static_cast(dir)]; +} + +static bool sideways_overlap(const SkRect& rect, const SkPoint& pt, SkOpRayDir dir) { + int i = !xy_index(dir); + return approximately_between((&rect.fLeft)[i], (&pt.fX)[i], (&rect.fRight)[i]); +} + +static bool less_than(SkOpRayDir dir) { + return static_cast((static_cast(dir) & 2) == 0); +} + +static bool ccw_dxdy(const SkDVector& v, SkOpRayDir dir) { + bool vPartPos = pt_dydx(v, dir) > 0; + bool leftBottom = ((static_cast(dir) + 1) & 2) != 0; + return vPartPos == leftBottom; +} + +struct SkOpRayHit { + SkOpRayDir makeTestBase(SkOpSpan* span, double t) { + fNext = nullptr; + fSpan = span; + fT = span->t() * (1 - t) + span->next()->t() * t; + SkOpSegment* segment = span->segment(); + fSlope = segment->dSlopeAtT(fT); + fPt = segment->ptAtT(fT); + fValid = true; + return fabs(fSlope.fX) < fabs(fSlope.fY) ? SkOpRayDir::kLeft : SkOpRayDir::kTop; + } + + SkOpRayHit* fNext; + SkOpSpan* fSpan; + SkPoint fPt; + double fT; + SkDVector fSlope; + bool fValid; +}; + +void SkOpContour::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, + SkArenaAlloc* allocator) { + // if the bounds extreme is outside the best, we're done + SkScalar baseXY = pt_xy(base.fPt, dir); + SkScalar boundsXY = rect_side(fBounds, dir); + bool checkLessThan = less_than(dir); + if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { + return; + } + SkOpSegment* testSegment = &fHead; + do { + testSegment->rayCheck(base, dir, hits, allocator); + } while ((testSegment = testSegment->next())); +} + +void SkOpSegment::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, + SkArenaAlloc* allocator) { + if (!sideways_overlap(fBounds, base.fPt, dir)) { + return; + } + SkScalar baseXY = pt_xy(base.fPt, dir); + SkScalar boundsXY = rect_side(fBounds, dir); + bool checkLessThan = less_than(dir); + if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { + return; + } + double tVals[3]; + SkScalar baseYX = pt_yx(base.fPt, dir); + int roots = (*CurveIntercept[fVerb * 2 + xy_index(dir)])(fPts, fWeight, baseYX, tVals); + for (int index = 0; index < roots; ++index) { + double t = tVals[index]; + if (base.fSpan->segment() == this && approximately_equal(base.fT, t)) { + continue; + } + SkDVector slope; + SkPoint pt; + SkDEBUGCODE(sk_bzero(&slope, sizeof(slope))); + bool valid = false; + if (approximately_zero(t)) { + pt = fPts[0]; + } else if (approximately_equal(t, 1)) { + pt = fPts[SkPathOpsVerbToPoints(fVerb)]; + } else { + SkASSERT(between(0, t, 1)); + pt = this->ptAtT(t); + if (SkDPoint::ApproximatelyEqual(pt, base.fPt)) { + if (base.fSpan->segment() == this) { + continue; + } + } else { + SkScalar ptXY = pt_xy(pt, dir); + if (!approximately_equal(baseXY, ptXY) && (baseXY < ptXY) == checkLessThan) { + continue; + } + slope = this->dSlopeAtT(t); + if (fVerb == SkPath::kCubic_Verb && base.fSpan->segment() == this + && roughly_equal(base.fT, t) + && SkDPoint::RoughlyEqual(pt, base.fPt)) { + #if DEBUG_WINDING + SkDebugf("%s (rarely expect this)\n", __FUNCTION__); + #endif + continue; + } + if (fabs(pt_dydx(slope, dir) * 10000) > fabs(pt_dxdy(slope, dir))) { + valid = true; + } + } + } + SkOpSpan* span = this->windingSpanAtT(t); + if (!span) { + valid = false; + } else if (!span->windValue() && !span->oppValue()) { + continue; + } + SkOpRayHit* newHit = allocator->make(); + newHit->fNext = *hits; + newHit->fPt = pt; + newHit->fSlope = slope; + newHit->fSpan = span; + newHit->fT = t; + newHit->fValid = valid; + *hits = newHit; + } +} + +SkOpSpan* SkOpSegment::windingSpanAtT(double tHit) { + SkOpSpan* span = &fHead; + SkOpSpanBase* next; + do { + next = span->next(); + if (approximately_equal(tHit, next->t())) { + return nullptr; + } + if (tHit < next->t()) { + return span; + } + } while (!next->final() && (span = next->upCast())); + return nullptr; +} + +static bool hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { + return a->fPt.fX < b->fPt.fX; +} + +static bool reverse_hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { + return b->fPt.fX < a->fPt.fX; +} + +static bool hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { + return a->fPt.fY < b->fPt.fY; +} + +static bool reverse_hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { + return b->fPt.fY < a->fPt.fY; +} + +static double get_t_guess(int tTry, int* dirOffset) { + double t = 0.5; + *dirOffset = tTry & 1; + int tBase = tTry >> 1; + int tBits = 0; + while (tTry >>= 1) { + t /= 2; + ++tBits; + } + if (tBits) { + int tIndex = (tBase - 1) & ((1 << tBits) - 1); + t += t * 2 * tIndex; + } + return t; +} + +bool SkOpSpan::sortableTop(SkOpContour* contourHead) { + SkSTArenaAlloc<1024> allocator; + int dirOffset; + double t = get_t_guess(fTopTTry++, &dirOffset); + SkOpRayHit hitBase; + SkOpRayDir dir = hitBase.makeTestBase(this, t); + if (hitBase.fSlope.fX == 0 && hitBase.fSlope.fY == 0) { + return false; + } + SkOpRayHit* hitHead = &hitBase; + dir = static_cast(static_cast(dir) + dirOffset); + if (hitBase.fSpan && hitBase.fSpan->segment()->verb() > SkPath::kLine_Verb + && !pt_dydx(hitBase.fSlope, dir)) { + return false; + } + SkOpContour* contour = contourHead; + do { + if (!contour->count()) { + continue; + } + contour->rayCheck(hitBase, dir, &hitHead, &allocator); + } while ((contour = contour->next())); + // sort hits + SkSTArray<1, SkOpRayHit*> sorted; + SkOpRayHit* hit = hitHead; + while (hit) { + sorted.push_back(hit); + hit = hit->fNext; + } + int count = sorted.count(); + SkTQSort(sorted.begin(), sorted.end() - 1, xy_index(dir) + ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y + : less_than(dir) ? hit_compare_x : reverse_hit_compare_x); + // verify windings +#if DEBUG_WINDING + SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n", __FUNCTION__, + gDebugRayDirName[static_cast(dir)], hitBase.fSpan->segment()->debugID(), + hitBase.fT, hitBase.fPt.fX, hitBase.fPt.fY); + for (int index = 0; index < count; ++index) { + hit = sorted[index]; + SkOpSpan* span = hit->fSpan; + SkOpSegment* hitSegment = span ? span->segment() : nullptr; + bool operand = span ? hitSegment->operand() : false; + bool ccw = ccw_dxdy(hit->fSlope, dir); + SkDebugf("%s [%d] valid=%d operand=%d span=%d ccw=%d ", __FUNCTION__, index, + hit->fValid, operand, span ? span->debugID() : -1, ccw); + if (span) { + hitSegment->dumpPtsInner(); + } + SkDebugf(" t=%1.9g pt=(%1.9g,%1.9g) slope=(%1.9g,%1.9g)\n", hit->fT, + hit->fPt.fX, hit->fPt.fY, hit->fSlope.fX, hit->fSlope.fY); + } +#endif + const SkPoint* last = nullptr; + int wind = 0; + int oppWind = 0; + for (int index = 0; index < count; ++index) { + hit = sorted[index]; + if (!hit->fValid) { + return false; + } + bool ccw = ccw_dxdy(hit->fSlope, dir); +// SkASSERT(!approximately_zero(hit->fT) || !hit->fValid); + SkOpSpan* span = hit->fSpan; + if (!span) { + return false; + } + SkOpSegment* hitSegment = span->segment(); + if (span->windValue() == 0 && span->oppValue() == 0) { + continue; + } + if (last && SkDPoint::ApproximatelyEqual(*last, hit->fPt)) { + return false; + } + if (index < count - 1) { + const SkPoint& next = sorted[index + 1]->fPt; + if (SkDPoint::ApproximatelyEqual(next, hit->fPt)) { + return false; + } + } + bool operand = hitSegment->operand(); + if (operand) { + using std::swap; + swap(wind, oppWind); + } + int lastWind = wind; + int lastOpp = oppWind; + int windValue = ccw ? -span->windValue() : span->windValue(); + int oppValue = ccw ? -span->oppValue() : span->oppValue(); + wind += windValue; + oppWind += oppValue; + bool sumSet = false; + int spanSum = span->windSum(); + int windSum = SkOpSegment::UseInnerWinding(lastWind, wind) ? wind : lastWind; + if (spanSum == SK_MinS32) { + span->setWindSum(windSum); + sumSet = true; + } else { + // the need for this condition suggests that UseInnerWinding is flawed + // happened when last = 1 wind = -1 +#if 0 + SkASSERT((hitSegment->isXor() ? (windSum & 1) == (spanSum & 1) : windSum == spanSum) + || (abs(wind) == abs(lastWind) + && (windSum ^ wind ^ lastWind) == spanSum)); +#endif + } + int oSpanSum = span->oppSum(); + int oppSum = SkOpSegment::UseInnerWinding(lastOpp, oppWind) ? oppWind : lastOpp; + if (oSpanSum == SK_MinS32) { + span->setOppSum(oppSum); + } else { +#if 0 + SkASSERT(hitSegment->oppXor() ? (oppSum & 1) == (oSpanSum & 1) : oppSum == oSpanSum + || (abs(oppWind) == abs(lastOpp) + && (oppSum ^ oppWind ^ lastOpp) == oSpanSum)); +#endif + } + if (sumSet) { + if (this->globalState()->phase() == SkOpPhase::kFixWinding) { + hitSegment->contour()->setCcw(ccw); + } else { + (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, nullptr); + (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, nullptr); + } + } + if (operand) { + using std::swap; + swap(wind, oppWind); + } + last = &hit->fPt; + this->globalState()->bumpNested(); + } + return true; +} + +SkOpSpan* SkOpSegment::findSortableTop(SkOpContour* contourHead) { + SkOpSpan* span = &fHead; + SkOpSpanBase* next; + do { + next = span->next(); + if (span->done()) { + continue; + } + if (span->windSum() != SK_MinS32) { + return span; + } + if (span->sortableTop(contourHead)) { + return span; + } + } while (!next->final() && (span = next->upCast())); + return nullptr; +} + +SkOpSpan* SkOpContour::findSortableTop(SkOpContour* contourHead) { + bool allDone = true; + if (fCount) { + SkOpSegment* testSegment = &fHead; + do { + if (testSegment->done()) { + continue; + } + allDone = false; + SkOpSpan* result = testSegment->findSortableTop(contourHead); + if (result) { + return result; + } + } while ((testSegment = testSegment->next())); + } + if (allDone) { + fDone = true; + } + return nullptr; +} + +SkOpSpan* FindSortableTop(SkOpContourHead* contourHead) { + for (int index = 0; index < SkOpGlobalState::kMaxWindingTries; ++index) { + SkOpContour* contour = contourHead; + do { + if (contour->done()) { + continue; + } + SkOpSpan* result = contour->findSortableTop(contourHead); + if (result) { + return result; + } + } while ((contour = contour->next())); + } + return nullptr; +} diff --git a/gfx/skia/skia/src/pathops/SkPathWriter.cpp b/gfx/skia/skia/src/pathops/SkPathWriter.cpp new file mode 100644 index 0000000000..6c2faab068 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathWriter.cpp @@ -0,0 +1,434 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkTSort.h" +#include "src/pathops/SkOpSegment.h" +#include "src/pathops/SkOpSpan.h" +#include "src/pathops/SkPathOpsPoint.h" +#include "src/pathops/SkPathWriter.h" + +// wrap path to keep track of whether the contour is initialized and non-empty +SkPathWriter::SkPathWriter(SkPath& path) + : fPathPtr(&path) +{ + init(); +} + +void SkPathWriter::close() { + if (fCurrent.isEmpty()) { + return; + } + SkASSERT(this->isClosed()); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.close();\n"); +#endif + fCurrent.close(); + fPathPtr->addPath(fCurrent); + fCurrent.reset(); + init(); +} + +void SkPathWriter::conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight) { + SkPoint pt2pt = this->update(pt2); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.conicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g);\n", + pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY, weight); +#endif + fCurrent.conicTo(pt1, pt2pt, weight); +} + +void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3) { + SkPoint pt3pt = this->update(pt3); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n", + pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3pt.fX, pt3pt.fY); +#endif + fCurrent.cubicTo(pt1, pt2, pt3pt); +} + +bool SkPathWriter::deferredLine(const SkOpPtT* pt) { + SkASSERT(fFirstPtT); + SkASSERT(fDefer[0]); + if (fDefer[0] == pt) { + // FIXME: why we're adding a degenerate line? Caller should have preflighted this. + return true; + } + if (pt->contains(fDefer[0])) { + // FIXME: why we're adding a degenerate line? + return true; + } + if (this->matchedLast(pt)) { + return false; + } + if (fDefer[1] && this->changedSlopes(pt)) { + this->lineTo(); + fDefer[0] = fDefer[1]; + } + fDefer[1] = pt; + return true; +} + +void SkPathWriter::deferredMove(const SkOpPtT* pt) { + if (!fDefer[1]) { + fFirstPtT = fDefer[0] = pt; + return; + } + SkASSERT(fDefer[0]); + if (!this->matchedLast(pt)) { + this->finishContour(); + fFirstPtT = fDefer[0] = pt; + } +} + +void SkPathWriter::finishContour() { + if (!this->matchedLast(fDefer[0])) { + if (!fDefer[1]) { + return; + } + this->lineTo(); + } + if (fCurrent.isEmpty()) { + return; + } + if (this->isClosed()) { + this->close(); + } else { + SkASSERT(fDefer[1]); + fEndPtTs.push_back(fFirstPtT); + fEndPtTs.push_back(fDefer[1]); + fPartials.push_back(fCurrent); + this->init(); + } +} + +void SkPathWriter::init() { + fCurrent.reset(); + fFirstPtT = fDefer[0] = fDefer[1] = nullptr; +} + +bool SkPathWriter::isClosed() const { + return this->matchedLast(fFirstPtT); +} + +void SkPathWriter::lineTo() { + if (fCurrent.isEmpty()) { + this->moveTo(); + } +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.lineTo(%1.9g,%1.9g);\n", fDefer[1]->fPt.fX, fDefer[1]->fPt.fY); +#endif + fCurrent.lineTo(fDefer[1]->fPt); +} + +bool SkPathWriter::matchedLast(const SkOpPtT* test) const { + if (test == fDefer[1]) { + return true; + } + if (!test) { + return false; + } + if (!fDefer[1]) { + return false; + } + return test->contains(fDefer[1]); +} + +void SkPathWriter::moveTo() { +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.moveTo(%1.9g,%1.9g);\n", fFirstPtT->fPt.fX, fFirstPtT->fPt.fY); +#endif + fCurrent.moveTo(fFirstPtT->fPt); +} + +void SkPathWriter::quadTo(const SkPoint& pt1, const SkOpPtT* pt2) { + SkPoint pt2pt = this->update(pt2); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n", + pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY); +#endif + fCurrent.quadTo(pt1, pt2pt); +} + +// if last point to be written matches the current path's first point, alter the +// last to avoid writing a degenerate lineTo when the path is closed +SkPoint SkPathWriter::update(const SkOpPtT* pt) { + if (!fDefer[1]) { + this->moveTo(); + } else if (!this->matchedLast(fDefer[0])) { + this->lineTo(); + } + SkPoint result = pt->fPt; + if (fFirstPtT && result != fFirstPtT->fPt && fFirstPtT->contains(pt)) { + result = fFirstPtT->fPt; + } + fDefer[0] = fDefer[1] = pt; // set both to know that there is not a pending deferred line + return result; +} + +bool SkPathWriter::someAssemblyRequired() { + this->finishContour(); + return fEndPtTs.count() > 0; +} + +bool SkPathWriter::changedSlopes(const SkOpPtT* ptT) const { + if (matchedLast(fDefer[0])) { + return false; + } + SkVector deferDxdy = fDefer[1]->fPt - fDefer[0]->fPt; + SkVector lineDxdy = ptT->fPt - fDefer[1]->fPt; + return deferDxdy.fX * lineDxdy.fY != deferDxdy.fY * lineDxdy.fX; +} + +class DistanceLessThan { +public: + DistanceLessThan(double* distances) : fDistances(distances) { } + double* fDistances; + bool operator()(const int one, const int two) { + return fDistances[one] < fDistances[two]; + } +}; + + /* + check start and end of each contour + if not the same, record them + match them up + connect closest + reassemble contour pieces into new path + */ +void SkPathWriter::assemble() { +#if DEBUG_SHOW_TEST_NAME + SkDebugf("\n"); +#endif + if (!this->someAssemblyRequired()) { + return; + } +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("%s\n", __FUNCTION__); +#endif + SkOpPtT const* const* runs = fEndPtTs.begin(); // starts, ends of partial contours + int endCount = fEndPtTs.count(); // all starts and ends + SkASSERT(endCount > 0); + SkASSERT(endCount == fPartials.count() * 2); +#if DEBUG_ASSEMBLE + for (int index = 0; index < endCount; index += 2) { + const SkOpPtT* eStart = runs[index]; + const SkOpPtT* eEnd = runs[index + 1]; + SkASSERT(eStart != eEnd); + SkASSERT(!eStart->contains(eEnd)); + SkDebugf("%s contour start=(%1.9g,%1.9g) end=(%1.9g,%1.9g)\n", __FUNCTION__, + eStart->fPt.fX, eStart->fPt.fY, eEnd->fPt.fX, eEnd->fPt.fY); + } +#endif + // lengthen any partial contour adjacent to a simple segment + for (int pIndex = 0; pIndex < endCount; pIndex++) { + SkOpPtT* opPtT = const_cast(runs[pIndex]); + SkPath dummy; + SkPathWriter partWriter(dummy); + do { + if (!zero_or_one(opPtT->fT)) { + break; + } + SkOpSpanBase* opSpanBase = opPtT->span(); + SkOpSpanBase* start = opPtT->fT ? opSpanBase->prev() : opSpanBase->upCast()->next(); + int step = opPtT->fT ? 1 : -1; + const SkOpSegment* opSegment = opSpanBase->segment(); + const SkOpSegment* nextSegment = opSegment->isSimple(&start, &step); + if (!nextSegment) { + break; + } + SkOpSpanBase* opSpanEnd = start->t() ? start->prev() : start->upCast()->next(); + if (start->starter(opSpanEnd)->alreadyAdded()) { + break; + } + nextSegment->addCurveTo(start, opSpanEnd, &partWriter); + opPtT = opSpanEnd->ptT(); + SkOpPtT** runsPtr = const_cast(&runs[pIndex]); + *runsPtr = opPtT; + } while (true); + partWriter.finishContour(); + const SkTArray& partPartials = partWriter.partials(); + if (!partPartials.count()) { + continue; + } + // if pIndex is even, reverse and prepend to fPartials; otherwise, append + SkPath& partial = const_cast(fPartials[pIndex >> 1]); + const SkPath& part = partPartials[0]; + if (pIndex & 1) { + partial.addPath(part, SkPath::kExtend_AddPathMode); + } else { + SkPath reverse; + reverse.reverseAddPath(part); + reverse.addPath(partial, SkPath::kExtend_AddPathMode); + partial = reverse; + } + } + SkTDArray sLink, eLink; + int linkCount = endCount / 2; // number of partial contours + sLink.append(linkCount); + eLink.append(linkCount); + int rIndex, iIndex; + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + sLink[rIndex] = eLink[rIndex] = SK_MaxS32; + } + const int entries = endCount * (endCount - 1) / 2; // folded triangle + SkSTArray<8, double, true> distances(entries); + SkSTArray<8, int, true> sortedDist(entries); + SkSTArray<8, int, true> distLookup(entries); + int rRow = 0; + int dIndex = 0; + for (rIndex = 0; rIndex < endCount - 1; ++rIndex) { + const SkOpPtT* oPtT = runs[rIndex]; + for (iIndex = rIndex + 1; iIndex < endCount; ++iIndex) { + const SkOpPtT* iPtT = runs[iIndex]; + double dx = iPtT->fPt.fX - oPtT->fPt.fX; + double dy = iPtT->fPt.fY - oPtT->fPt.fY; + double dist = dx * dx + dy * dy; + distLookup.push_back(rRow + iIndex); + distances.push_back(dist); // oStart distance from iStart + sortedDist.push_back(dIndex++); + } + rRow += endCount; + } + SkASSERT(dIndex == entries); + SkTQSort(sortedDist.begin(), sortedDist.end() - 1, DistanceLessThan(distances.begin())); + int remaining = linkCount; // number of start/end pairs + for (rIndex = 0; rIndex < entries; ++rIndex) { + int pair = sortedDist[rIndex]; + pair = distLookup[pair]; + int row = pair / endCount; + int col = pair - row * endCount; + int ndxOne = row >> 1; + bool endOne = row & 1; + int* linkOne = endOne ? eLink.begin() : sLink.begin(); + if (linkOne[ndxOne] != SK_MaxS32) { + continue; + } + int ndxTwo = col >> 1; + bool endTwo = col & 1; + int* linkTwo = endTwo ? eLink.begin() : sLink.begin(); + if (linkTwo[ndxTwo] != SK_MaxS32) { + continue; + } + SkASSERT(&linkOne[ndxOne] != &linkTwo[ndxTwo]); + bool flip = endOne == endTwo; + linkOne[ndxOne] = flip ? ~ndxTwo : ndxTwo; + linkTwo[ndxTwo] = flip ? ~ndxOne : ndxOne; + if (!--remaining) { + break; + } + } + SkASSERT(!remaining); +#if DEBUG_ASSEMBLE + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + int s = sLink[rIndex]; + int e = eLink[rIndex]; + SkDebugf("%s %c%d <- s%d - e%d -> %c%d\n", __FUNCTION__, s < 0 ? 's' : 'e', + s < 0 ? ~s : s, rIndex, rIndex, e < 0 ? 'e' : 's', e < 0 ? ~e : e); + } +#endif + rIndex = 0; + do { + bool forward = true; + bool first = true; + int sIndex = sLink[rIndex]; + SkASSERT(sIndex != SK_MaxS32); + sLink[rIndex] = SK_MaxS32; + int eIndex; + if (sIndex < 0) { + eIndex = sLink[~sIndex]; + sLink[~sIndex] = SK_MaxS32; + } else { + eIndex = eLink[sIndex]; + eLink[sIndex] = SK_MaxS32; + } + SkASSERT(eIndex != SK_MaxS32); +#if DEBUG_ASSEMBLE + SkDebugf("%s sIndex=%c%d eIndex=%c%d\n", __FUNCTION__, sIndex < 0 ? 's' : 'e', + sIndex < 0 ? ~sIndex : sIndex, eIndex < 0 ? 's' : 'e', + eIndex < 0 ? ~eIndex : eIndex); +#endif + do { + const SkPath& contour = fPartials[rIndex]; + if (!first) { + SkPoint prior, next; + if (!fPathPtr->getLastPt(&prior)) { + return; + } + if (forward) { + next = contour.getPoint(0); + } else { + SkAssertResult(contour.getLastPt(&next)); + } + if (prior != next) { + /* TODO: if there is a gap between open path written so far and path to come, + connect by following segments from one to the other, rather than introducing + a diagonal to connect the two. + */ + SkDebugf(""); + } + } + if (forward) { + fPathPtr->addPath(contour, + first ? SkPath::kAppend_AddPathMode : SkPath::kExtend_AddPathMode); + } else { + SkASSERT(!first); + fPathPtr->reversePathTo(contour); + } + if (first) { + first = false; + } +#if DEBUG_ASSEMBLE + SkDebugf("%s rIndex=%d eIndex=%s%d close=%d\n", __FUNCTION__, rIndex, + eIndex < 0 ? "~" : "", eIndex < 0 ? ~eIndex : eIndex, + sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)); +#endif + if (sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)) { + fPathPtr->close(); + break; + } + if (forward) { + eIndex = eLink[rIndex]; + SkASSERT(eIndex != SK_MaxS32); + eLink[rIndex] = SK_MaxS32; + if (eIndex >= 0) { + SkASSERT(sLink[eIndex] == rIndex); + sLink[eIndex] = SK_MaxS32; + } else { + SkASSERT(eLink[~eIndex] == ~rIndex); + eLink[~eIndex] = SK_MaxS32; + } + } else { + eIndex = sLink[rIndex]; + SkASSERT(eIndex != SK_MaxS32); + sLink[rIndex] = SK_MaxS32; + if (eIndex >= 0) { + SkASSERT(eLink[eIndex] == rIndex); + eLink[eIndex] = SK_MaxS32; + } else { + SkASSERT(sLink[~eIndex] == ~rIndex); + sLink[~eIndex] = SK_MaxS32; + } + } + rIndex = eIndex; + if (rIndex < 0) { + forward ^= 1; + rIndex = ~rIndex; + } + } while (true); + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + if (sLink[rIndex] != SK_MaxS32) { + break; + } + } + } while (rIndex < linkCount); +#if DEBUG_ASSEMBLE + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + SkASSERT(sLink[rIndex] == SK_MaxS32); + SkASSERT(eLink[rIndex] == SK_MaxS32); + } +#endif + return; +} diff --git a/gfx/skia/skia/src/pathops/SkPathWriter.h b/gfx/skia/skia/src/pathops/SkPathWriter.h new file mode 100644 index 0000000000..3963710ff0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathWriter.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathWriter_DEFINED +#define SkPathWriter_DEFINED + +#include "include/core/SkPath.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" + +class SkOpPtT; + +// Construct the path one contour at a time. +// If the contour is closed, copy it to the final output. +// Otherwise, keep the partial contour for later assembly. + +class SkPathWriter { +public: + SkPathWriter(SkPath& path); + void assemble(); + void conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight); + void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3); + bool deferredLine(const SkOpPtT* pt); + void deferredMove(const SkOpPtT* pt); + void finishContour(); + bool hasMove() const { return !fFirstPtT; } + void init(); + bool isClosed() const; + const SkPath* nativePath() const { return fPathPtr; } + void quadTo(const SkPoint& pt1, const SkOpPtT* pt2); + +private: + bool changedSlopes(const SkOpPtT* pt) const; + void close(); + const SkTDArray& endPtTs() const { return fEndPtTs; } + void lineTo(); + bool matchedLast(const SkOpPtT*) const; + void moveTo(); + const SkTArray& partials() const { return fPartials; } + bool someAssemblyRequired(); + SkPoint update(const SkOpPtT* pt); + + SkPath fCurrent; // contour under construction + SkTArray fPartials; // contours with mismatched starts and ends + SkTDArray fEndPtTs; // possible pt values for partial starts and ends + SkPath* fPathPtr; // closed contours are written here + const SkOpPtT* fDefer[2]; // [0] deferred move, [1] deferred line + const SkOpPtT* fFirstPtT; // first in current contour +}; + +#endif /* defined(__PathOps__SkPathWriter__) */ diff --git a/gfx/skia/skia/src/pathops/SkReduceOrder.cpp b/gfx/skia/skia/src/pathops/SkReduceOrder.cpp new file mode 100644 index 0000000000..72a6168717 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkReduceOrder.cpp @@ -0,0 +1,283 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/core/SkGeometry.h" +#include "src/pathops/SkReduceOrder.h" + +int SkReduceOrder::reduce(const SkDLine& line) { + fLine[0] = line[0]; + int different = line[0] != line[1]; + fLine[1] = line[different]; + return 1 + different; +} + +static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = reduction[1] = quad[0]; + return 1; +} + +static int reductionLineCount(const SkDQuad& reduction) { + return 1 + !reduction[0].approximatelyEqual(reduction[1]); +} + +static int vertical_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = quad[0]; + reduction[1] = quad[2]; + return reductionLineCount(reduction); +} + +static int horizontal_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = quad[0]; + reduction[1] = quad[2]; + return reductionLineCount(reduction); +} + +static int check_linear(const SkDQuad& quad, + int minX, int maxX, int minY, int maxY, SkDQuad& reduction) { + if (!quad.isLinear(0, 2)) { + return 0; + } + // four are colinear: return line formed by outside + reduction[0] = quad[0]; + reduction[1] = quad[2]; + return reductionLineCount(reduction); +} + +// reduce to a quadratic or smaller +// look for identical points +// look for all four points in a line + // note that three points in a line doesn't simplify a cubic +// look for approximation with single quadratic + // save approximation with multiple quadratics for later +int SkReduceOrder::reduce(const SkDQuad& quad) { + int index, minX, maxX, minY, maxY; + int minXSet, minYSet; + minX = maxX = minY = maxY = 0; + minXSet = minYSet = 0; + for (index = 1; index < 3; ++index) { + if (quad[minX].fX > quad[index].fX) { + minX = index; + } + if (quad[minY].fY > quad[index].fY) { + minY = index; + } + if (quad[maxX].fX < quad[index].fX) { + maxX = index; + } + if (quad[maxY].fY < quad[index].fY) { + maxY = index; + } + } + for (index = 0; index < 3; ++index) { + if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) { + minXSet |= 1 << index; + } + if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) { + minYSet |= 1 << index; + } + } + if ((minXSet & 0x05) == 0x5 && (minYSet & 0x05) == 0x5) { // test for degenerate + // this quad starts and ends at the same place, so never contributes + // to the fill + return coincident_line(quad, fQuad); + } + if (minXSet == 0x7) { // test for vertical line + return vertical_line(quad, fQuad); + } + if (minYSet == 0x7) { // test for horizontal line + return horizontal_line(quad, fQuad); + } + int result = check_linear(quad, minX, maxX, minY, maxY, fQuad); + if (result) { + return result; + } + fQuad = quad; + return 3; +} + +//////////////////////////////////////////////////////////////////////////////////// + +static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = reduction[1] = cubic[0]; + return 1; +} + +static int reductionLineCount(const SkDCubic& reduction) { + return 1 + !reduction[0].approximatelyEqual(reduction[1]); +} + +static int vertical_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + return reductionLineCount(reduction); +} + +static int horizontal_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + return reductionLineCount(reduction); +} + +// check to see if it is a quadratic or a line +static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) { + double dx10 = cubic[1].fX - cubic[0].fX; + double dx23 = cubic[2].fX - cubic[3].fX; + double midX = cubic[0].fX + dx10 * 3 / 2; + double sideAx = midX - cubic[3].fX; + double sideBx = dx23 * 3 / 2; + if (approximately_zero(sideAx) ? !approximately_equal(sideAx, sideBx) + : !AlmostEqualUlps_Pin(sideAx, sideBx)) { + return 0; + } + double dy10 = cubic[1].fY - cubic[0].fY; + double dy23 = cubic[2].fY - cubic[3].fY; + double midY = cubic[0].fY + dy10 * 3 / 2; + double sideAy = midY - cubic[3].fY; + double sideBy = dy23 * 3 / 2; + if (approximately_zero(sideAy) ? !approximately_equal(sideAy, sideBy) + : !AlmostEqualUlps_Pin(sideAy, sideBy)) { + return 0; + } + reduction[0] = cubic[0]; + reduction[1].fX = midX; + reduction[1].fY = midY; + reduction[2] = cubic[3]; + return 3; +} + +static int check_linear(const SkDCubic& cubic, + int minX, int maxX, int minY, int maxY, SkDCubic& reduction) { + if (!cubic.isLinear(0, 3)) { + return 0; + } + // four are colinear: return line formed by outside + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + return reductionLineCount(reduction); +} + +/* food for thought: +http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html + +Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the +corresponding quadratic Bezier are (given in convex combinations of +points): + +q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4 +q2 = -c1 + (3/2)c2 + (3/2)c3 - c4 +q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4 + +Of course, this curve does not interpolate the end-points, but it would +be interesting to see the behaviour of such a curve in an applet. + +-- +Kalle Rutanen +http://kaba.hilvi.org + +*/ + +// reduce to a quadratic or smaller +// look for identical points +// look for all four points in a line + // note that three points in a line doesn't simplify a cubic +// look for approximation with single quadratic + // save approximation with multiple quadratics for later +int SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics) { + int index, minX, maxX, minY, maxY; + int minXSet, minYSet; + minX = maxX = minY = maxY = 0; + minXSet = minYSet = 0; + for (index = 1; index < 4; ++index) { + if (cubic[minX].fX > cubic[index].fX) { + minX = index; + } + if (cubic[minY].fY > cubic[index].fY) { + minY = index; + } + if (cubic[maxX].fX < cubic[index].fX) { + maxX = index; + } + if (cubic[maxY].fY < cubic[index].fY) { + maxY = index; + } + } + for (index = 0; index < 4; ++index) { + double cx = cubic[index].fX; + double cy = cubic[index].fY; + double denom = SkTMax(fabs(cx), SkTMax(fabs(cy), + SkTMax(fabs(cubic[minX].fX), fabs(cubic[minY].fY)))); + if (denom == 0) { + minXSet |= 1 << index; + minYSet |= 1 << index; + continue; + } + double inv = 1 / denom; + if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) { + minXSet |= 1 << index; + } + if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) { + minYSet |= 1 << index; + } + } + if (minXSet == 0xF) { // test for vertical line + if (minYSet == 0xF) { // return 1 if all four are coincident + return coincident_line(cubic, fCubic); + } + return vertical_line(cubic, fCubic); + } + if (minYSet == 0xF) { // test for horizontal line + return horizontal_line(cubic, fCubic); + } + int result = check_linear(cubic, minX, maxX, minY, maxY, fCubic); + if (result) { + return result; + } + if (allowQuadratics == SkReduceOrder::kAllow_Quadratics + && (result = check_quadratic(cubic, fCubic))) { + return result; + } + fCubic = cubic; + return 4; +} + +SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) { + SkDQuad quad; + quad.set(a); + SkReduceOrder reducer; + int order = reducer.reduce(quad); + if (order == 2) { // quad became line + for (int index = 0; index < order; ++index) { + *reducePts++ = reducer.fLine[index].asSkPoint(); + } + } + return SkPathOpsPointsToVerb(order - 1); +} + +SkPath::Verb SkReduceOrder::Conic(const SkConic& c, SkPoint* reducePts) { + SkPath::Verb verb = SkReduceOrder::Quad(c.fPts, reducePts); + if (verb > SkPath::kLine_Verb && c.fW == 1) { + return SkPath::kQuad_Verb; + } + return verb == SkPath::kQuad_Verb ? SkPath::kConic_Verb : verb; +} + +SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) { + if (SkDPoint::ApproximatelyEqual(a[0], a[1]) && SkDPoint::ApproximatelyEqual(a[0], a[2]) + && SkDPoint::ApproximatelyEqual(a[0], a[3])) { + reducePts[0] = a[0]; + return SkPath::kMove_Verb; + } + SkDCubic cubic; + cubic.set(a); + SkReduceOrder reducer; + int order = reducer.reduce(cubic, kAllow_Quadratics); + if (order == 2 || order == 3) { // cubic became line or quad + for (int index = 0; index < order; ++index) { + *reducePts++ = reducer.fQuad[index].asSkPoint(); + } + } + return SkPathOpsPointsToVerb(order - 1); +} diff --git a/gfx/skia/skia/src/pathops/SkReduceOrder.h b/gfx/skia/skia/src/pathops/SkReduceOrder.h new file mode 100644 index 0000000000..69ea0adb6f --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkReduceOrder.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkReduceOrder_DEFINED +#define SkReduceOrder_DEFINED + +#include "src/pathops/SkPathOpsCubic.h" +#include "src/pathops/SkPathOpsLine.h" +#include "src/pathops/SkPathOpsQuad.h" + +struct SkConic; + +union SkReduceOrder { + enum Quadratics { + kNo_Quadratics, + kAllow_Quadratics + }; + + int reduce(const SkDCubic& cubic, Quadratics); + int reduce(const SkDLine& line); + int reduce(const SkDQuad& quad); + + static SkPath::Verb Conic(const SkConic& conic, SkPoint* reducePts); + static SkPath::Verb Cubic(const SkPoint pts[4], SkPoint* reducePts); + static SkPath::Verb Quad(const SkPoint pts[3], SkPoint* reducePts); + + SkDLine fLine; + SkDQuad fQuad; + SkDCubic fCubic; +}; + +#endif diff --git a/gfx/skia/skia/src/pdf/SkBitmapKey.h b/gfx/skia/skia/src/pdf/SkBitmapKey.h new file mode 100644 index 0000000000..72df0c7abe --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkBitmapKey.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBitmapKey_DEFINED +#define SkBitmapKey_DEFINED + +#include "include/core/SkRect.h" + +struct SkBitmapKey { + SkIRect fSubset; + uint32_t fID; + bool operator==(const SkBitmapKey& rhs) const { + return fID == rhs.fID && fSubset == rhs.fSubset; + } + bool operator!=(const SkBitmapKey& rhs) const { return !(*this == rhs); } +}; + + +#endif // SkBitmapKey_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkClusterator.cpp b/gfx/skia/skia/src/pdf/SkClusterator.cpp new file mode 100644 index 0000000000..ab2fceba71 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkClusterator.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkClusterator.h" + +#include "include/private/SkTo.h" +#include "src/core/SkGlyphRun.h" +#include "src/utils/SkUTF.h" + +static bool is_reversed(const uint32_t* clusters, uint32_t count) { + // "ReversedChars" is how PDF deals with RTL text. + // return true if more than one cluster and monotonicly decreasing to zero. + if (count < 2 || clusters[0] == 0 || clusters[count - 1] != 0) { + return false; + } + for (uint32_t i = 0; i + 1 < count; ++i) { + if (clusters[i + 1] > clusters[i]) { + return false; + } + } + return true; +} + +SkClusterator::SkClusterator(const SkGlyphRun& run) + : fClusters(run.clusters().data()) + , fUtf8Text(run.text().data()) + , fGlyphCount(SkToU32(run.glyphsIDs().size())) + , fTextByteLength(SkToU32(run.text().size())) + , fReversedChars(fClusters ? is_reversed(fClusters, fGlyphCount) : false) +{ + if (fClusters) { + SkASSERT(fUtf8Text && fTextByteLength > 0 && fGlyphCount > 0); + } else { + SkASSERT(!fUtf8Text && fTextByteLength == 0); + } +} + +SkClusterator::Cluster SkClusterator::next() { + if (fCurrentGlyphIndex >= fGlyphCount) { + return Cluster{nullptr, 0, 0, 0}; + } + if (!fClusters || !fUtf8Text) { + return Cluster{nullptr, 0, fCurrentGlyphIndex++, 1}; + } + uint32_t clusterGlyphIndex = fCurrentGlyphIndex; + uint32_t cluster = fClusters[clusterGlyphIndex]; + do { + ++fCurrentGlyphIndex; + } while (fCurrentGlyphIndex < fGlyphCount && cluster == fClusters[fCurrentGlyphIndex]); + uint32_t clusterGlyphCount = fCurrentGlyphIndex - clusterGlyphIndex; + uint32_t clusterEnd = fTextByteLength; + for (unsigned i = 0; i < fGlyphCount; ++i) { + uint32_t c = fClusters[i]; + if (c > cluster && c < clusterEnd) { + clusterEnd = c; + } + } + uint32_t clusterLen = clusterEnd - cluster; + return Cluster{fUtf8Text + cluster, clusterLen, clusterGlyphIndex, clusterGlyphCount}; +} diff --git a/gfx/skia/skia/src/pdf/SkClusterator.h b/gfx/skia/skia/src/pdf/SkClusterator.h new file mode 100644 index 0000000000..ec87d4dd66 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkClusterator.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkClusterator_DEFINED +#define SkClusterator_DEFINED + +#include +#include + +class SkGlyphRun; + +/** Given the m-to-n glyph-to-character mapping data (as returned by + harfbuzz), iterate over the clusters. */ +class SkClusterator { +public: + SkClusterator(const SkGlyphRun& run); + uint32_t glyphCount() const { return fGlyphCount; } + bool reversedChars() const { return fReversedChars; } + struct Cluster { + const char* fUtf8Text; + uint32_t fTextByteLength; + uint32_t fGlyphIndex; + uint32_t fGlyphCount; + explicit operator bool() const { return fGlyphCount != 0; } + bool operator==(const SkClusterator::Cluster& o) { + return fUtf8Text == o.fUtf8Text + && fTextByteLength == o.fTextByteLength + && fGlyphIndex == o.fGlyphIndex + && fGlyphCount == o.fGlyphCount; + } + }; + Cluster next(); + +private: + uint32_t const * const fClusters; + char const * const fUtf8Text; + uint32_t const fGlyphCount; + uint32_t const fTextByteLength; + bool const fReversedChars; + uint32_t fCurrentGlyphIndex = 0; +}; + + +#endif // SkClusterator_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkDeflate.cpp b/gfx/skia/skia/src/pdf/SkDeflate.cpp new file mode 100644 index 0000000000..f803c17b46 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkDeflate.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkDeflate.h" + +#include "include/core/SkData.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTraceEvent.h" + +#include "zlib.h" + +namespace { + +// Different zlib implementations use different T. +// We've seen size_t and unsigned. +template void* skia_alloc_func(void*, T items, T size) { + return sk_calloc_throw(SkToSizeT(items) * SkToSizeT(size)); +} + +void skia_free_func(void*, void* address) { sk_free(address); } + +} // namespace + +#define SKDEFLATEWSTREAM_INPUT_BUFFER_SIZE 4096 +#define SKDEFLATEWSTREAM_OUTPUT_BUFFER_SIZE 4224 // 4096 + 128, usually big + // enough to always do a + // single loop. + +// called by both write() and finalize() +static void do_deflate(int flush, + z_stream* zStream, + SkWStream* out, + unsigned char* inBuffer, + size_t inBufferSize) { + zStream->next_in = inBuffer; + zStream->avail_in = SkToInt(inBufferSize); + unsigned char outBuffer[SKDEFLATEWSTREAM_OUTPUT_BUFFER_SIZE]; + SkDEBUGCODE(int returnValue;) + do { + zStream->next_out = outBuffer; + zStream->avail_out = sizeof(outBuffer); + SkDEBUGCODE(returnValue =) deflate(zStream, flush); + SkASSERT(!zStream->msg); + + out->write(outBuffer, sizeof(outBuffer) - zStream->avail_out); + } while (zStream->avail_in || !zStream->avail_out); + SkASSERT(flush == Z_FINISH + ? returnValue == Z_STREAM_END + : returnValue == Z_OK); +} + +// Hide all zlib impl details. +struct SkDeflateWStream::Impl { + SkWStream* fOut; + unsigned char fInBuffer[SKDEFLATEWSTREAM_INPUT_BUFFER_SIZE]; + size_t fInBufferIndex; + z_stream fZStream; +}; + +SkDeflateWStream::SkDeflateWStream(SkWStream* out, + int compressionLevel, + bool gzip) + : fImpl(skstd::make_unique()) { + fImpl->fOut = out; + fImpl->fInBufferIndex = 0; + if (!fImpl->fOut) { + return; + } + fImpl->fZStream.next_in = nullptr; + fImpl->fZStream.zalloc = &skia_alloc_func; + fImpl->fZStream.zfree = &skia_free_func; + fImpl->fZStream.opaque = nullptr; + SkASSERT(compressionLevel <= 9 && compressionLevel >= -1); + SkDEBUGCODE(int r =) deflateInit2(&fImpl->fZStream, compressionLevel, + Z_DEFLATED, gzip ? 0x1F : 0x0F, + 8, Z_DEFAULT_STRATEGY); + SkASSERT(Z_OK == r); +} + +SkDeflateWStream::~SkDeflateWStream() { this->finalize(); } + +void SkDeflateWStream::finalize() { + TRACE_EVENT0("skia", TRACE_FUNC); + if (!fImpl->fOut) { + return; + } + do_deflate(Z_FINISH, &fImpl->fZStream, fImpl->fOut, fImpl->fInBuffer, + fImpl->fInBufferIndex); + (void)deflateEnd(&fImpl->fZStream); + fImpl->fOut = nullptr; +} + +bool SkDeflateWStream::write(const void* void_buffer, size_t len) { + TRACE_EVENT0("skia", TRACE_FUNC); + if (!fImpl->fOut) { + return false; + } + const char* buffer = (const char*)void_buffer; + while (len > 0) { + size_t tocopy = + SkTMin(len, sizeof(fImpl->fInBuffer) - fImpl->fInBufferIndex); + memcpy(fImpl->fInBuffer + fImpl->fInBufferIndex, buffer, tocopy); + len -= tocopy; + buffer += tocopy; + fImpl->fInBufferIndex += tocopy; + SkASSERT(fImpl->fInBufferIndex <= sizeof(fImpl->fInBuffer)); + + // if the buffer isn't filled, don't call into zlib yet. + if (sizeof(fImpl->fInBuffer) == fImpl->fInBufferIndex) { + do_deflate(Z_NO_FLUSH, &fImpl->fZStream, fImpl->fOut, + fImpl->fInBuffer, fImpl->fInBufferIndex); + fImpl->fInBufferIndex = 0; + } + } + return true; +} + +size_t SkDeflateWStream::bytesWritten() const { + return fImpl->fZStream.total_in + fImpl->fInBufferIndex; +} diff --git a/gfx/skia/skia/src/pdf/SkDeflate.h b/gfx/skia/skia/src/pdf/SkDeflate.h new file mode 100644 index 0000000000..46e24a531f --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkDeflate.h @@ -0,0 +1,53 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkFlate_DEFINED +#define SkFlate_DEFINED + +#include "include/core/SkStream.h" + +/** + * Wrap a stream in this class to compress the information written to + * this stream using the Deflate algorithm. + * + * See http://en.wikipedia.org/wiki/DEFLATE + */ +class SkDeflateWStream final : public SkWStream { +public: + /** Does not take ownership of the stream. + + @param compressionLevel - 0 is no compression; 1 is best + speed; 9 is best compression. The default, -1, is to use + zlib's Z_DEFAULT_COMPRESSION level. + + @param gzip iff true, output a gzip file. "The gzip format is + a wrapper, documented in RFC 1952, around a deflate stream." + gzip adds a header with a magic number to the beginning of the + stream, allowing a client to identify a gzip file. + */ + SkDeflateWStream(SkWStream*, + int compressionLevel = -1, + bool gzip = false); + + /** The destructor calls finalize(). */ + ~SkDeflateWStream() override; + + /** Write the end of the compressed stream. All subsequent calls to + write() will fail. Subsequent calls to finalize() do nothing. */ + void finalize(); + + // The SkWStream interface: + bool write(const void*, size_t) override; + size_t bytesWritten() const override; + +private: + struct Impl; + std::unique_ptr fImpl; +}; + +#endif // SkFlate_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkDocument_PDF_None.cpp b/gfx/skia/skia/src/pdf/SkDocument_PDF_None.cpp new file mode 100644 index 0000000000..2dc580c34b --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkDocument_PDF_None.cpp @@ -0,0 +1,15 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/docs/SkPDFDocument.h" + +sk_sp SkPDF::MakeDocument(SkWStream*, const SkPDF::Metadata&) { return nullptr; } + +void SkPDF::SetNodeId(SkCanvas* c, int n) { + c->drawAnnotation({0, 0, 0, 0}, "PDF_Node_Key", SkData::MakeWithCopy(&n, sizeof(n)).get()); +} diff --git a/gfx/skia/skia/src/pdf/SkJpegInfo.cpp b/gfx/skia/skia/src/pdf/SkJpegInfo.cpp new file mode 100644 index 0000000000..f2c3a5d0cb --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkJpegInfo.cpp @@ -0,0 +1,128 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkJpegInfo.h" + +#include "include/private/SkTo.h" + +#ifndef SK_HAS_JPEG_LIBRARY + +namespace { +class JpegSegment { +public: + JpegSegment(const void* data, size_t size) + : fData(static_cast(data)) + , fSize(size) + , fOffset(0) + , fLength(0) {} + bool read() { + if (!this->readBigendianUint16(&fMarker)) { + return false; + } + if (JpegSegment::StandAloneMarker(fMarker)) { + fLength = 0; + fBuffer = nullptr; + return true; + } + if (!this->readBigendianUint16(&fLength) || fLength < 2) { + return false; + } + fLength -= 2; // Length includes itself for some reason. + if (fOffset + fLength > fSize) { + return false; // Segment too long. + } + fBuffer = &fData[fOffset]; + fOffset += fLength; + return true; + } + + bool isSOF() { + return (fMarker & 0xFFF0) == 0xFFC0 && fMarker != 0xFFC4 && + fMarker != 0xFFC8 && fMarker != 0xFFCC; + } + uint16_t marker() { return fMarker; } + uint16_t length() { return fLength; } + const char* data() { return fBuffer; } + + static uint16_t GetBigendianUint16(const char* ptr) { + // "the most significant byte shall come first" + return (static_cast(ptr[0]) << 8) | + static_cast(ptr[1]); + } + +private: + const char* const fData; + const size_t fSize; + size_t fOffset; + const char* fBuffer; + uint16_t fMarker; + uint16_t fLength; + + bool readBigendianUint16(uint16_t* value) { + if (fOffset + 2 > fSize) { + return false; + } + *value = JpegSegment::GetBigendianUint16(&fData[fOffset]); + fOffset += 2; + return true; + } + static bool StandAloneMarker(uint16_t marker) { + // RST[m] markers or SOI, EOI, TEM + return (marker & 0xFFF8) == 0xFFD0 || marker == 0xFFD8 || + marker == 0xFFD9 || marker == 0xFF01; + } +}; +} // namespace + +bool SkGetJpegInfo(const void* data, size_t len, + SkISize* size, + SkEncodedInfo::Color* colorType, + SkEncodedOrigin* orientation) { + static const uint16_t kSOI = 0xFFD8; + static const uint16_t kAPP0 = 0xFFE0; + JpegSegment segment(data, len); + if (!segment.read() || segment.marker() != kSOI) { + return false; // not a JPEG + } + if (!segment.read() || segment.marker() != kAPP0) { + return false; // not an APP0 segment + } + static const char kJfif[] = {'J', 'F', 'I', 'F', '\0'}; + SkASSERT(segment.data()); + if (SkToSizeT(segment.length()) < sizeof(kJfif) || + 0 != memcmp(segment.data(), kJfif, sizeof(kJfif))) { + return false; // Not JFIF JPEG + } + do { + if (!segment.read()) { + return false; // malformed JPEG + } + } while (!segment.isSOF()); + if (segment.length() < 6) { + return false; // SOF segment is short + } + if (8 != segment.data()[0]) { + return false; // Only support 8-bit precision + } + int numberOfComponents = segment.data()[5]; + if (numberOfComponents != 1 && numberOfComponents != 3) { + return false; // Invalid JFIF + } + if (size) { + *size = {JpegSegment::GetBigendianUint16(&segment.data()[3]), + JpegSegment::GetBigendianUint16(&segment.data()[1])}; + } + if (colorType) { + *colorType = numberOfComponents == 3 ? SkEncodedInfo::kYUV_Color + : SkEncodedInfo::kGray_Color; + } + if (orientation) { + *orientation = kTopLeft_SkEncodedOrigin; + } + return true; +} +#endif // SK_HAS_JPEG_LIBRARY diff --git a/gfx/skia/skia/src/pdf/SkJpegInfo.h b/gfx/skia/skia/src/pdf/SkJpegInfo.h new file mode 100644 index 0000000000..82a8a736fd --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkJpegInfo.h @@ -0,0 +1,25 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkJpegInfo_DEFINED +#define SkJpegInfo_DEFINED + +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkSize.h" +#include "include/private/SkEncodedInfo.h" + +/** Returns true if the data seems to be a valid JPEG image with a known colorType. + + @param [out] size Image size in pixels + @param [out] colorType Encoded color type (kGray_Color, kYUV_Color, several others). + @param [out] orientation EXIF Orientation of the image. +*/ +bool SkGetJpegInfo(const void* data, size_t len, + SkISize* size, + SkEncodedInfo::Color* colorType, + SkEncodedOrigin* orientation); + +#endif // SkJpegInfo_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkKeyedImage.cpp b/gfx/skia/skia/src/pdf/SkKeyedImage.cpp new file mode 100644 index 0000000000..ce59da7bf3 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkKeyedImage.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkKeyedImage.h" + +#include "src/image/SkImage_Base.h" + +SkBitmapKey SkBitmapKeyFromImage(const SkImage* image) { + if (!image) { + return {{0, 0, 0, 0}, 0}; + } + if (const SkBitmap* bm = as_IB(image)->onPeekBitmap()) { + SkIPoint o = bm->pixelRefOrigin(); + return {image->bounds().makeOffset(o), bm->getGenerationID()}; + } + return {image->bounds(), image->uniqueID()}; +} + +SkKeyedImage::SkKeyedImage(sk_sp i) : fImage(std::move(i)) { + fKey = SkBitmapKeyFromImage(fImage.get()); +} + +SkKeyedImage::SkKeyedImage(const SkBitmap& bm) : fImage(SkImage::MakeFromBitmap(bm)) { + if (fImage) { + fKey = {bm.getSubset(), bm.getGenerationID()}; + } +} + +SkKeyedImage SkKeyedImage::subset(SkIRect subset) const { + SkKeyedImage img; + if (fImage && subset.intersect(fImage->bounds())) { + img.fImage = fImage->makeSubset(subset); + if (img.fImage) { + img.fKey = {subset.makeOffset(fKey.fSubset.topLeft()), fKey.fID}; + } + } + return img; +} + +sk_sp SkKeyedImage::release() { + sk_sp image = std::move(fImage); + SkASSERT(nullptr == fImage); + fKey = {{0, 0, 0, 0}, 0}; + return image; +} diff --git a/gfx/skia/skia/src/pdf/SkKeyedImage.h b/gfx/skia/skia/src/pdf/SkKeyedImage.h new file mode 100644 index 0000000000..db7b09d7b6 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkKeyedImage.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkKeyedImage_DEFINED +#define SkKeyedImage_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkImage.h" +#include "src/pdf/SkBitmapKey.h" + +/** + This class has all the advantages of SkBitmaps and SkImages. + + The SkImage holds on to encoded data. The SkBitmapKey properly de-dups subsets. + */ +class SkKeyedImage { +public: + SkKeyedImage() {} + SkKeyedImage(sk_sp); + SkKeyedImage(const SkBitmap&); + SkKeyedImage(SkKeyedImage&&) = default; + SkKeyedImage(const SkKeyedImage&) = default; + + SkKeyedImage& operator=(SkKeyedImage&&) = default; + SkKeyedImage& operator=(const SkKeyedImage&) = default; + + explicit operator bool() const { return fImage != nullptr; } + const SkBitmapKey& key() const { return fKey; } + const sk_sp& image() const { return fImage; } + sk_sp release(); + SkKeyedImage subset(SkIRect subset) const; + +private: + sk_sp fImage; + SkBitmapKey fKey = {{0, 0, 0, 0}, 0}; +}; + +/** + * Given an Image, return the Bitmap Key that corresponds to it. If the Image + * wraps a Bitmap, use that Bitmap's key. + */ +SkBitmapKey SkBitmapKeyFromImage(const SkImage*); +#endif // SkKeyedImage_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFBitmap.cpp b/gfx/skia/skia/src/pdf/SkPDFBitmap.cpp new file mode 100644 index 0000000000..beaf08846d --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFBitmap.cpp @@ -0,0 +1,290 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFBitmap.h" + +#include "include/core/SkData.h" +#include "include/core/SkExecutor.h" +#include "include/core/SkImage.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkImageInfoPriv.h" +#include "include/private/SkTo.h" +#include "src/pdf/SkDeflate.h" +#include "src/pdf/SkJpegInfo.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFTypes.h" +#include "src/pdf/SkPDFUtils.h" + +//////////////////////////////////////////////////////////////////////////////// + +// write a single byte to a stream n times. +static void fill_stream(SkWStream* out, char value, size_t n) { + char buffer[4096]; + memset(buffer, value, sizeof(buffer)); + for (size_t i = 0; i < n / sizeof(buffer); ++i) { + out->write(buffer, sizeof(buffer)); + } + out->write(buffer, n % sizeof(buffer)); +} + +/* It is necessary to average the color component of transparent + pixels with their surrounding neighbors since the PDF renderer may + separately re-sample the alpha and color channels when the image is + not displayed at its native resolution. Since an alpha of zero + gives no information about the color component, the pathological + case is a white image with sharp transparency bounds - the color + channel goes to black, and the should-be-transparent pixels are + rendered as grey because of the separate soft mask and color + resizing. e.g.: gm/bitmappremul.cpp */ +static SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) { + SkASSERT(kBGRA_8888_SkColorType == bm.colorType()); + unsigned r = 0, g = 0, b = 0, n = 0; + // Clamp the range to the edge of the bitmap. + int ymin = SkTMax(0, yOrig - 1); + int ymax = SkTMin(yOrig + 1, bm.height() - 1); + int xmin = SkTMax(0, xOrig - 1); + int xmax = SkTMin(xOrig + 1, bm.width() - 1); + for (int y = ymin; y <= ymax; ++y) { + const SkColor* scanline = bm.addr32(0, y); + for (int x = xmin; x <= xmax; ++x) { + SkColor color = scanline[x]; + if (color != SK_ColorTRANSPARENT) { + r += SkColorGetR(color); + g += SkColorGetG(color); + b += SkColorGetB(color); + n++; + } + } + } + return n > 0 ? SkColorSetRGB(SkToU8(r / n), SkToU8(g / n), SkToU8(b / n)) + : SK_ColorTRANSPARENT; +} + +template +static void emit_image_stream(SkPDFDocument* doc, + SkPDFIndirectReference ref, + T writeStream, + SkISize size, + const char* colorSpace, + SkPDFIndirectReference sMask, + int length, + bool isJpeg) { + SkPDFDict pdfDict("XObject"); + pdfDict.insertName("Subtype", "Image"); + pdfDict.insertInt("Width", size.width()); + pdfDict.insertInt("Height", size.height()); + pdfDict.insertName("ColorSpace", colorSpace); + if (sMask) { + pdfDict.insertRef("SMask", sMask); + } + pdfDict.insertInt("BitsPerComponent", 8); + #ifdef SK_PDF_BASE85_BINARY + auto filters = SkPDFMakeArray(); + filters->appendName("ASCII85Decode"); + filters->appendName(isJpeg ? "DCTDecode" : "FlateDecode"); + pdfDict.insertObject("Filter", std::move(filters)); + #else + pdfDict.insertName("Filter", isJpeg ? "DCTDecode" : "FlateDecode"); + #endif + if (isJpeg) { + pdfDict.insertInt("ColorTransform", 0); + } + pdfDict.insertInt("Length", length); + doc->emitStream(pdfDict, std::move(writeStream), ref); +} + +static void do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc, SkPDFIndirectReference ref) { + SkDynamicMemoryWStream buffer; + SkDeflateWStream deflateWStream(&buffer); + if (kAlpha_8_SkColorType == pm.colorType()) { + SkASSERT(pm.rowBytes() == (size_t)pm.width()); + buffer.write(pm.addr8(), pm.width() * pm.height()); + } else { + SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType); + SkASSERT(pm.colorType() == kBGRA_8888_SkColorType); + SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4); + const uint32_t* ptr = pm.addr32(); + const uint32_t* stop = ptr + pm.height() * pm.width(); + + uint8_t byteBuffer[4092]; + uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer); + uint8_t* dst = byteBuffer; + while (ptr != stop) { + *dst++ = 0xFF & ((*ptr++) >> SK_BGRA_A32_SHIFT); + if (dst == bufferStop) { + deflateWStream.write(byteBuffer, sizeof(byteBuffer)); + dst = byteBuffer; + } + } + deflateWStream.write(byteBuffer, dst - byteBuffer); + } + deflateWStream.finalize(); + + #ifdef SK_PDF_BASE85_BINARY + SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer); + #endif + int length = SkToInt(buffer.bytesWritten()); + emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); }, + pm.info().dimensions(), "DeviceGray", SkPDFIndirectReference(), + length, false); +} + +static void do_deflated_image(const SkPixmap& pm, + SkPDFDocument* doc, + bool isOpaque, + SkPDFIndirectReference ref) { + SkPDFIndirectReference sMask; + if (!isOpaque) { + sMask = doc->reserveRef(); + } + SkDynamicMemoryWStream buffer; + SkDeflateWStream deflateWStream(&buffer); + const char* colorSpace = "DeviceGray"; + switch (pm.colorType()) { + case kAlpha_8_SkColorType: + fill_stream(&deflateWStream, '\x00', pm.width() * pm.height()); + break; + case kGray_8_SkColorType: + SkASSERT(sMask.fValue = -1); + SkASSERT(pm.rowBytes() == (size_t)pm.width()); + deflateWStream.write(pm.addr8(), pm.width() * pm.height()); + break; + default: + colorSpace = "DeviceRGB"; + SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType); + SkASSERT(pm.colorType() == kBGRA_8888_SkColorType); + SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4); + uint8_t byteBuffer[3072]; + static_assert(SK_ARRAY_COUNT(byteBuffer) % 3 == 0, ""); + uint8_t* bufferStop = byteBuffer + SK_ARRAY_COUNT(byteBuffer); + uint8_t* dst = byteBuffer; + for (int y = 0; y < pm.height(); ++y) { + const SkColor* src = pm.addr32(0, y); + for (int x = 0; x < pm.width(); ++x) { + SkColor color = *src++; + if (SkColorGetA(color) == SK_AlphaTRANSPARENT) { + color = get_neighbor_avg_color(pm, x, y); + } + *dst++ = SkColorGetR(color); + *dst++ = SkColorGetG(color); + *dst++ = SkColorGetB(color); + if (dst == bufferStop) { + deflateWStream.write(byteBuffer, sizeof(byteBuffer)); + dst = byteBuffer; + } + } + } + deflateWStream.write(byteBuffer, dst - byteBuffer); + } + deflateWStream.finalize(); + #ifdef SK_PDF_BASE85_BINARY + SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer); + #endif + int length = SkToInt(buffer.bytesWritten()); + emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); }, + pm.info().dimensions(), colorSpace, sMask, length, false); + if (!isOpaque) { + do_deflated_alpha(pm, doc, sMask); + } +} + +static bool do_jpeg(sk_sp data, SkPDFDocument* doc, SkISize size, + SkPDFIndirectReference ref) { + SkISize jpegSize; + SkEncodedInfo::Color jpegColorType; + SkEncodedOrigin exifOrientation; + if (!SkGetJpegInfo(data->data(), data->size(), &jpegSize, + &jpegColorType, &exifOrientation)) { + return false; + } + bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color; + bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color; + if (jpegSize != size // Sanity check. + || !goodColorType + || kTopLeft_SkEncodedOrigin != exifOrientation) { + return false; + } + #ifdef SK_PDF_BASE85_BINARY + SkDynamicMemoryWStream buffer; + SkPDFUtils::Base85Encode(SkMemoryStream::MakeDirect(data->data(), data->size()), &buffer); + data = buffer.detachAsData(); + #endif + + emit_image_stream(doc, ref, + [&data](SkWStream* dst) { dst->write(data->data(), data->size()); }, + jpegSize, yuv ? "DeviceRGB" : "DeviceGray", + SkPDFIndirectReference(), SkToInt(data->size()), true); + return true; +} + +static SkBitmap to_pixels(const SkImage* image) { + SkBitmap bm; + int w = image->width(), + h = image->height(); + switch (image->colorType()) { + case kAlpha_8_SkColorType: + bm.allocPixels(SkImageInfo::MakeA8(w, h)); + break; + case kGray_8_SkColorType: + bm.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType)); + break; + default: { + // TODO: makeColorSpace(sRGB) or actually tag the images + SkAlphaType at = bm.isOpaque() ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType; + bm.allocPixels(SkImageInfo::Make(w, h, kBGRA_8888_SkColorType, at)); + } + } + if (!image->readPixels(bm.pixmap(), 0, 0)) { + bm.eraseColor(SkColorSetARGB(0xFF, 0, 0, 0)); + } + return bm; +} + +void serialize_image(const SkImage* img, + int encodingQuality, + SkPDFDocument* doc, + SkPDFIndirectReference ref) { + SkASSERT(img); + SkASSERT(doc); + SkASSERT(encodingQuality >= 0); + SkISize dimensions = img->dimensions(); + sk_sp data = img->refEncodedData(); + if (data && do_jpeg(std::move(data), doc, dimensions, ref)) { + return; + } + SkBitmap bm = to_pixels(img); + SkPixmap pm = bm.pixmap(); + bool isOpaque = pm.isOpaque() || pm.computeIsOpaque(); + if (encodingQuality <= 100 && isOpaque) { + sk_sp data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality); + if (data && do_jpeg(std::move(data), doc, dimensions, ref)) { + return; + } + } + do_deflated_image(pm, doc, isOpaque, ref); +} + +SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img, + SkPDFDocument* doc, + int encodingQuality) { + SkASSERT(img); + SkASSERT(doc); + SkPDFIndirectReference ref = doc->reserveRef(); + if (SkExecutor* executor = doc->executor()) { + SkRef(img); + doc->incrementJobCount(); + executor->add([img, encodingQuality, doc, ref]() { + serialize_image(img, encodingQuality, doc, ref); + SkSafeUnref(img); + doc->signalJobComplete(); + }); + return ref; + } + serialize_image(img, encodingQuality, doc, ref); + return ref; +} diff --git a/gfx/skia/skia/src/pdf/SkPDFBitmap.h b/gfx/skia/skia/src/pdf/SkPDFBitmap.h new file mode 100644 index 0000000000..bc2c57bd3b --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFBitmap.h @@ -0,0 +1,22 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFBitmap_DEFINED +#define SkPDFBitmap_DEFINED + +class SkImage; +class SkPDFDocument; +struct SkPDFIndirectReference; + +/** + * Serialize a SkImage as an Image Xobject. + * quality > 100 means lossless + */ +SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img, + SkPDFDocument* doc, + int encodingQuality = 101); + +#endif // SkPDFBitmap_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFDevice.cpp b/gfx/skia/skia/src/pdf/SkPDFDevice.cpp new file mode 100644 index 0000000000..2ac998f757 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFDevice.cpp @@ -0,0 +1,1765 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFDevice.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkColorFilter.h" +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" +#include "include/core/SkRRect.h" +#include "include/core/SkString.h" +#include "include/core/SkSurface.h" +#include "include/core/SkTextBlob.h" +#include "include/docs/SkPDFDocument.h" +#include "include/encode/SkJpegEncoder.h" +#include "include/pathops/SkPathOps.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkAnnotationKeys.h" +#include "src/core/SkBitmapDevice.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkDraw.h" +#include "src/core/SkGlyphRun.h" +#include "src/core/SkImageFilterCache.h" +#include "src/core/SkImageFilter_Base.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMaskFilterBase.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkScopeExit.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeSpec.h" +#include "src/core/SkTextFormatParams.h" +#include "src/core/SkXfermodeInterpretation.h" +#include "src/pdf/SkBitmapKey.h" +#include "src/pdf/SkClusterator.h" +#include "src/pdf/SkPDFBitmap.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFFont.h" +#include "src/pdf/SkPDFFormXObject.h" +#include "src/pdf/SkPDFGraphicState.h" +#include "src/pdf/SkPDFResourceDict.h" +#include "src/pdf/SkPDFShader.h" +#include "src/pdf/SkPDFTypes.h" +#include "src/pdf/SkPDFUtils.h" +#include "src/utils/SkUTF.h" + +#include + +#ifndef SK_PDF_MASK_QUALITY + // If MASK_QUALITY is in [0,100], will be used for JpegEncoder. + // Otherwise, just encode masks losslessly. + #define SK_PDF_MASK_QUALITY 50 + // Since these masks are used for blurry shadows, we shouldn't need + // high quality. Raise this value if your shadows have visible JPEG + // artifacts. + // If SkJpegEncoder::Encode fails, we will fall back to the lossless + // encoding. +#endif + +// Utility functions + +static SkPath to_path(const SkRect& r) { + SkPath p; + p.addRect(r); + return p; +} + +// This function destroys the mask and either frees or takes the pixels. +sk_sp mask_to_greyscale_image(SkMask* mask) { + sk_sp img; + SkPixmap pm(SkImageInfo::Make(mask->fBounds.width(), mask->fBounds.height(), + kGray_8_SkColorType, kOpaque_SkAlphaType), + mask->fImage, mask->fRowBytes); + const int imgQuality = SK_PDF_MASK_QUALITY; + if (imgQuality <= 100 && imgQuality >= 0) { + SkDynamicMemoryWStream buffer; + SkJpegEncoder::Options jpegOptions; + jpegOptions.fQuality = imgQuality; + if (SkJpegEncoder::Encode(&buffer, pm, jpegOptions)) { + img = SkImage::MakeFromEncoded(buffer.detachAsData()); + SkASSERT(img); + if (img) { + SkMask::FreeImage(mask->fImage); + } + } + } + if (!img) { + img = SkImage::MakeFromRaster(pm, [](const void* p, void*) { SkMask::FreeImage((void*)p); }, + nullptr); + } + *mask = SkMask(); // destructive; + return img; +} + +sk_sp alpha_image_to_greyscale_image(const SkImage* mask) { + int w = mask->width(), h = mask->height(); + SkBitmap greyBitmap; + greyBitmap.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType)); + if (!mask->readPixels(SkImageInfo::MakeA8(w, h), + greyBitmap.getPixels(), greyBitmap.rowBytes(), 0, 0)) { + return nullptr; + } + return SkImage::MakeFromBitmap(greyBitmap); +} + +static int add_resource(SkTHashSet& resources, SkPDFIndirectReference ref) { + resources.add(ref); + return ref.fValue; +} + +static void draw_points(SkCanvas::PointMode mode, + size_t count, + const SkPoint* points, + const SkPaint& paint, + const SkIRect& bounds, + const SkMatrix& ctm, + SkBaseDevice* device) { + SkRasterClip rc(bounds); + SkDraw draw; + draw.fDst = SkPixmap(SkImageInfo::MakeUnknown(bounds.right(), bounds.bottom()), nullptr, 0); + draw.fMatrix = &ctm; + draw.fRC = &rc; + draw.drawPoints(mode, count, points, paint, device); +} + +// A shader's matrix is: CTMM x LocalMatrix x WrappingLocalMatrix. We want to +// switch to device space, where CTM = I, while keeping the original behavior. +// +// I * LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix +// LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix +// InvLocalMatrix * LocalMatrix * NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix +// NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix +// +static void transform_shader(SkPaint* paint, const SkMatrix& ctm) { + SkASSERT(!ctm.isIdentity()); + SkMatrix lm = SkPDFUtils::GetShaderLocalMatrix(paint->getShader()); + SkMatrix lmInv; + if (lm.invert(&lmInv)) { + SkMatrix m = SkMatrix::Concat(SkMatrix::Concat(lmInv, ctm), lm); + paint->setShader(paint->getShader()->makeWithLocalMatrix(m)); + } +} + +static SkTCopyOnFirstWrite clean_paint(const SkPaint& srcPaint) { + SkTCopyOnFirstWrite paint(srcPaint); + // If the paint will definitely draw opaquely, replace kSrc with + // kSrcOver. http://crbug.com/473572 + if (SkBlendMode::kSrcOver != paint->getBlendMode() && + kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false)) + { + paint.writable()->setBlendMode(SkBlendMode::kSrcOver); + } + // If the paint has a color filter, apply the color filter to the shader or the + // paint color. Remove the color filter. + if (SkColorFilter* cf = paint->getColorFilter()) { + SkPaint* p = paint.writable(); + if (SkShader* shader = paint->getShader()) { + p->setShader(shader->makeWithColorFilter(paint->refColorFilter())); + } else { + SkColorSpace* dstCS = sk_srgb_singleton(); // don't know PDF's space, so use srgb + SkColor4f newColor = cf->filterColor4f(p->getColor4f(), sk_srgb_singleton(), dstCS); + p->setColor4f(newColor, dstCS); + } + p->setColorFilter(nullptr); + } + return paint; +} + +static void set_style(SkTCopyOnFirstWrite* paint, SkPaint::Style style) { + if (paint->get()->getStyle() != style) { + paint->writable()->setStyle(style); + } +} + +/* Calculate an inverted path's equivalent non-inverted path, given the + * canvas bounds. + * outPath may alias with invPath (since this is supported by PathOps). + */ +static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath, + SkPath* outPath) { + SkASSERT(invPath.isInverseFillType()); + return Op(to_path(bounds), invPath, kIntersect_SkPathOp, outPath); +} + +SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) { + // PDF does not support image filters, so render them on CPU. + // Note that this rendering is done at "screen" resolution (100dpi), not + // printer resolution. + + // TODO: It may be possible to express some filters natively using PDF + // to improve quality and file size (https://bug.skia.org/3043) + if (layerPaint && (layerPaint->getImageFilter() || layerPaint->getColorFilter())) { + // need to return a raster device, which we will detect in drawDevice() + return SkBitmapDevice::Create(cinfo.fInfo, SkSurfaceProps(0, kUnknown_SkPixelGeometry)); + } + return new SkPDFDevice(cinfo.fInfo.dimensions(), fDocument); +} + +// A helper class to automatically finish a ContentEntry at the end of a +// drawing method and maintain the state needed between set up and finish. +class ScopedContentEntry { +public: + ScopedContentEntry(SkPDFDevice* device, + const SkClipStack* clipStack, + const SkMatrix& matrix, + const SkPaint& paint, + SkScalar textScale = 0) + : fDevice(device) + , fBlendMode(SkBlendMode::kSrcOver) + , fClipStack(clipStack) + { + if (matrix.hasPerspective()) { + NOT_IMPLEMENTED(!matrix.hasPerspective(), false); + return; + } + fBlendMode = paint.getBlendMode(); + fContentStream = + fDevice->setUpContentEntry(clipStack, matrix, paint, textScale, &fDstFormXObject); + } + ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, SkScalar textScale = 0) + : ScopedContentEntry(dev, &dev->cs(), dev->ctm(), paint, textScale) {} + + ~ScopedContentEntry() { + if (fContentStream) { + SkPath* shape = &fShape; + if (shape->isEmpty()) { + shape = nullptr; + } + fDevice->finishContentEntry(fClipStack, fBlendMode, fDstFormXObject, shape); + } + } + + explicit operator bool() const { return fContentStream != nullptr; } + SkDynamicMemoryWStream* stream() { return fContentStream; } + + /* Returns true when we explicitly need the shape of the drawing. */ + bool needShape() { + switch (fBlendMode) { + case SkBlendMode::kClear: + case SkBlendMode::kSrc: + case SkBlendMode::kSrcIn: + case SkBlendMode::kSrcOut: + case SkBlendMode::kDstIn: + case SkBlendMode::kDstOut: + case SkBlendMode::kSrcATop: + case SkBlendMode::kDstATop: + case SkBlendMode::kModulate: + return true; + default: + return false; + } + } + + /* Returns true unless we only need the shape of the drawing. */ + bool needSource() { + if (fBlendMode == SkBlendMode::kClear) { + return false; + } + return true; + } + + /* If the shape is different than the alpha component of the content, then + * setShape should be called with the shape. In particular, images and + * devices have rectangular shape. + */ + void setShape(const SkPath& shape) { + fShape = shape; + } + +private: + SkPDFDevice* fDevice = nullptr; + SkDynamicMemoryWStream* fContentStream = nullptr; + SkBlendMode fBlendMode; + SkPDFIndirectReference fDstFormXObject; + SkPath fShape; + const SkClipStack* fClipStack; +}; + +//////////////////////////////////////////////////////////////////////////////// + +SkPDFDevice::SkPDFDevice(SkISize pageSize, SkPDFDocument* doc, const SkMatrix& transform) + : INHERITED(SkImageInfo::MakeUnknown(pageSize.width(), pageSize.height()), + SkSurfaceProps(0, kUnknown_SkPixelGeometry)) + , fInitialTransform(transform) + , fNodeId(0) + , fDocument(doc) +{ + SkASSERT(!pageSize.isEmpty()); +} + +SkPDFDevice::~SkPDFDevice() = default; + +void SkPDFDevice::reset() { + fGraphicStateResources.reset(); + fXObjectResources.reset(); + fShaderResources.reset(); + fFontResources.reset(); + fContent.reset(); + fActiveStackState = SkPDFGraphicStackState(); +} + +void SkPDFDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) { + if (!value) { + return; + } + const SkMatrix& pageXform = fDocument->currentPageTransform(); + SkPoint deviceOffset = {(float)this->getOrigin().x(), (float)this->getOrigin().y()}; + if (rect.isEmpty()) { + if (!strcmp(key, SkPDFGetNodeIdKey())) { + int nodeID; + if (value->size() != sizeof(nodeID)) { return; } + memcpy(&nodeID, value->data(), sizeof(nodeID)); + fNodeId = nodeID; + return; + } + if (!strcmp(SkAnnotationKeys::Define_Named_Dest_Key(), key)) { + SkPoint p = deviceOffset + this->ctm().mapXY(rect.x(), rect.y()); + pageXform.mapPoints(&p, 1); + auto pg = fDocument->currentPage(); + fDocument->fNamedDestinations.push_back(SkPDFNamedDestination{sk_ref_sp(value), p, pg}); + } + return; + } + // Convert to path to handle non-90-degree rotations. + SkPath path = to_path(rect); + path.transform(this->ctm(), &path); + SkPath clip; + (void)this->cs().asPath(&clip); + Op(clip, path, kIntersect_SkPathOp, &path); + // PDF wants a rectangle only. + SkRect transformedRect = + pageXform.mapRect(path.getBounds().makeOffset(deviceOffset.x(), deviceOffset.y())); + if (transformedRect.isEmpty()) { + return; + } + if (!strcmp(SkAnnotationKeys::URL_Key(), key)) { + fDocument->fCurrentPageLinkToURLs.push_back( + std::make_pair(sk_ref_sp(value), transformedRect)); + } else if (!strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) { + fDocument->fCurrentPageLinkToDestinations.emplace_back( + std::make_pair(sk_ref_sp(value), transformedRect)); + } +} + +void SkPDFDevice::drawPaint(const SkPaint& srcPaint) { + SkMatrix inverse; + if (!this->ctm().invert(&inverse)) { + return; + } + SkRect bbox = this->cs().bounds(this->bounds()); + inverse.mapRect(&bbox); + bbox.roundOut(&bbox); + if (this->hasEmptyClip()) { + return; + } + SkPaint newPaint = srcPaint; + newPaint.setStyle(SkPaint::kFill_Style); + this->drawRect(bbox, newPaint); +} + +void SkPDFDevice::drawPoints(SkCanvas::PointMode mode, + size_t count, + const SkPoint* points, + const SkPaint& srcPaint) { + if (this->hasEmptyClip()) { + return; + } + if (count == 0) { + return; + } + SkTCopyOnFirstWrite paint(clean_paint(srcPaint)); + + + + if (SkCanvas::kPoints_PointMode != mode) { + set_style(&paint, SkPaint::kStroke_Style); + } + + // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath. + // We only use this when there's a path effect because of the overhead + // of multiple calls to setUpContentEntry it causes. + if (paint->getPathEffect()) { + draw_points(mode, count, points, *paint, + this->devClipBounds(), this->ctm(), this); + return; + } + + + if (mode == SkCanvas::kPoints_PointMode && paint->getStrokeCap() != SkPaint::kRound_Cap) { + if (paint->getStrokeWidth()) { + // PDF won't draw a single point with square/butt caps because the + // orientation is ambiguous. Draw a rectangle instead. + set_style(&paint, SkPaint::kFill_Style); + SkScalar strokeWidth = paint->getStrokeWidth(); + SkScalar halfStroke = SkScalarHalf(strokeWidth); + for (size_t i = 0; i < count; i++) { + SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0); + r.inset(-halfStroke, -halfStroke); + this->drawRect(r, *paint); + } + return; + } else { + if (paint->getStrokeCap() != SkPaint::kRound_Cap) { + paint.writable()->setStrokeCap(SkPaint::kRound_Cap); + } + } + } + + ScopedContentEntry content(this, *paint); + if (!content) { + return; + } + SkDynamicMemoryWStream* contentStream = content.stream(); + switch (mode) { + case SkCanvas::kPolygon_PointMode: + SkPDFUtils::MoveTo(points[0].fX, points[0].fY, contentStream); + for (size_t i = 1; i < count; i++) { + SkPDFUtils::AppendLine(points[i].fX, points[i].fY, contentStream); + } + SkPDFUtils::StrokePath(contentStream); + break; + case SkCanvas::kLines_PointMode: + for (size_t i = 0; i < count/2; i++) { + SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY, contentStream); + SkPDFUtils::AppendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY, contentStream); + SkPDFUtils::StrokePath(contentStream); + } + break; + case SkCanvas::kPoints_PointMode: + SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap); + for (size_t i = 0; i < count; i++) { + SkPDFUtils::MoveTo(points[i].fX, points[i].fY, contentStream); + SkPDFUtils::ClosePath(contentStream); + SkPDFUtils::StrokePath(contentStream); + } + break; + default: + SkASSERT(false); + } +} + +void SkPDFDevice::drawRect(const SkRect& rect, const SkPaint& paint) { + SkRect r = rect; + r.sort(); + this->internalDrawPath(this->cs(), this->ctm(), to_path(r), paint, true); +} + +void SkPDFDevice::drawRRect(const SkRRect& rrect, const SkPaint& paint) { + SkPath path; + path.addRRect(rrect); + this->internalDrawPath(this->cs(), this->ctm(), path, paint, true); +} + +void SkPDFDevice::drawOval(const SkRect& oval, const SkPaint& paint) { + SkPath path; + path.addOval(oval); + this->internalDrawPath(this->cs(), this->ctm(), path, paint, true); +} + +void SkPDFDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) { + this->internalDrawPath(this->cs(), this->ctm(), path, paint, pathIsMutable); +} + +void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack, + const SkMatrix& ctm, + const SkPath& origPath, + const SkPaint& origPaint) { + SkASSERT(origPaint.getMaskFilter()); + SkPath path(origPath); + SkTCopyOnFirstWrite paint(origPaint); + + SkStrokeRec::InitStyle initStyle = paint->getFillPath(path, &path) + ? SkStrokeRec::kFill_InitStyle + : SkStrokeRec::kHairline_InitStyle; + path.transform(ctm, &path); + + SkIRect bounds = clipStack.bounds(this->bounds()).roundOut(); + SkMask sourceMask; + if (!SkDraw::DrawToMask(path, &bounds, paint->getMaskFilter(), &SkMatrix::I(), + &sourceMask, SkMask::kComputeBoundsAndRenderImage_CreateMode, + initStyle)) { + return; + } + SkAutoMaskFreeImage srcAutoMaskFreeImage(sourceMask.fImage); + SkMask dstMask; + SkIPoint margin; + if (!as_MFB(paint->getMaskFilter())->filterMask(&dstMask, sourceMask, ctm, &margin)) { + return; + } + SkIRect dstMaskBounds = dstMask.fBounds; + sk_sp mask = mask_to_greyscale_image(&dstMask); + // PDF doesn't seem to allow masking vector graphics with an Image XObject. + // Must mask with a Form XObject. + sk_sp maskDevice = this->makeCongruentDevice(); + { + SkCanvas canvas(maskDevice); + canvas.drawImage(mask, dstMaskBounds.x(), dstMaskBounds.y()); + } + if (!ctm.isIdentity() && paint->getShader()) { + transform_shader(paint.writable(), ctm); // Since we are using identity matrix. + } + ScopedContentEntry content(this, &clipStack, SkMatrix::I(), *paint); + if (!content) { + return; + } + this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState( + maskDevice->makeFormXObjectFromDevice(dstMaskBounds, true), false, + SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), content.stream()); + SkPDFUtils::AppendRectangle(SkRect::Make(dstMaskBounds), content.stream()); + SkPDFUtils::PaintPath(SkPaint::kFill_Style, path.getFillType(), content.stream()); + this->clearMaskOnGraphicState(content.stream()); +} + +void SkPDFDevice::setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream* content) { + SkPDFUtils::ApplyGraphicState(add_resource(fGraphicStateResources, gs), content); +} + +void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) { + // The no-softmask graphic state is used to "turn off" the mask for later draw calls. + SkPDFIndirectReference& noSMaskGS = fDocument->fNoSmaskGraphicState; + if (!noSMaskGS) { + SkPDFDict tmp("ExtGState"); + tmp.insertName("SMask", "None"); + noSMaskGS = fDocument->emit(tmp); + } + this->setGraphicState(noSMaskGS, contentStream); +} + +void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack, + const SkMatrix& ctm, + const SkPath& origPath, + const SkPaint& srcPaint, + bool pathIsMutable) { + if (clipStack.isEmpty(this->bounds())) { + return; + } + SkTCopyOnFirstWrite paint(clean_paint(srcPaint)); + SkPath modifiedPath; + SkPath* pathPtr = const_cast(&origPath); + + if (paint->getMaskFilter()) { + this->internalDrawPathWithFilter(clipStack, ctm, origPath, *paint); + return; + } + + SkMatrix matrix = ctm; + + if (paint->getPathEffect()) { + if (clipStack.isEmpty(this->bounds())) { + return; + } + if (!pathIsMutable) { + modifiedPath = origPath; + pathPtr = &modifiedPath; + pathIsMutable = true; + } + if (paint->getFillPath(*pathPtr, pathPtr)) { + set_style(&paint, SkPaint::kFill_Style); + } else { + set_style(&paint, SkPaint::kStroke_Style); + if (paint->getStrokeWidth() != 0) { + paint.writable()->setStrokeWidth(0); + } + } + paint.writable()->setPathEffect(nullptr); + } + + if (this->handleInversePath(*pathPtr, *paint, pathIsMutable)) { + return; + } + if (matrix.getType() & SkMatrix::kPerspective_Mask) { + if (!pathIsMutable) { + modifiedPath = origPath; + pathPtr = &modifiedPath; + pathIsMutable = true; + } + pathPtr->transform(matrix); + if (paint->getShader()) { + transform_shader(paint.writable(), matrix); + } + matrix = SkMatrix::I(); + } + + ScopedContentEntry content(this, &clipStack, matrix, *paint); + if (!content) { + return; + } + constexpr SkScalar kToleranceScale = 0.0625f; // smaller = better conics (circles). + SkScalar matrixScale = matrix.mapRadius(1.0f); + SkScalar tolerance = matrixScale > 0.0f ? kToleranceScale / matrixScale : kToleranceScale; + bool consumeDegeratePathSegments = + paint->getStyle() == SkPaint::kFill_Style || + (paint->getStrokeCap() != SkPaint::kRound_Cap && + paint->getStrokeCap() != SkPaint::kSquare_Cap); + SkPDFUtils::EmitPath(*pathPtr, paint->getStyle(), consumeDegeratePathSegments, content.stream(), + tolerance); + SkPDFUtils::PaintPath(paint->getStyle(), pathPtr->getFillType(), content.stream()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void SkPDFDevice::drawImageRect(const SkImage* image, + const SkRect* src, + const SkRect& dst, + const SkPaint& paint, + SkCanvas::SrcRectConstraint) { + SkASSERT(image); + this->internalDrawImageRect(SkKeyedImage(sk_ref_sp(const_cast(image))), + src, dst, paint, this->ctm()); +} + +void SkPDFDevice::drawBitmapRect(const SkBitmap& bm, + const SkRect* src, + const SkRect& dst, + const SkPaint& paint, + SkCanvas::SrcRectConstraint) { + SkASSERT(!bm.drawsNothing()); + this->internalDrawImageRect(SkKeyedImage(bm), src, dst, paint, this->ctm()); +} + +void SkPDFDevice::drawSprite(const SkBitmap& bm, int x, int y, const SkPaint& paint) { + SkASSERT(!bm.drawsNothing()); + auto r = SkRect::MakeXYWH(x, y, bm.width(), bm.height()); + this->internalDrawImageRect(SkKeyedImage(bm), nullptr, r, paint, SkMatrix::I()); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { +class GlyphPositioner { +public: + GlyphPositioner(SkDynamicMemoryWStream* content, + SkScalar textSkewX, + SkPoint origin) + : fContent(content) + , fCurrentMatrixOrigin(origin) + , fTextSkewX(textSkewX) { + } + ~GlyphPositioner() { this->flush(); } + void flush() { + if (fInText) { + fContent->writeText("> Tj\n"); + fInText = false; + } + } + void setWideChars(bool wide) { + this->flush(); + fWideChars = wide; + } + void writeGlyph(SkPoint xy, + SkScalar advanceWidth, + uint16_t glyph) { + if (!fInitialized) { + // Flip the text about the x-axis to account for origin swap and include + // the passed parameters. + fContent->writeText("1 0 "); + SkPDFUtils::AppendScalar(-fTextSkewX, fContent); + fContent->writeText(" -1 "); + SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.x(), fContent); + fContent->writeText(" "); + SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.y(), fContent); + fContent->writeText(" Tm\n"); + fCurrentMatrixOrigin.set(0.0f, 0.0f); + fInitialized = true; + } + SkPoint position = xy - fCurrentMatrixOrigin; + if (position != SkPoint{fXAdvance, 0}) { + this->flush(); + SkPDFUtils::AppendScalar(position.x() - position.y() * fTextSkewX, fContent); + fContent->writeText(" "); + SkPDFUtils::AppendScalar(-position.y(), fContent); + fContent->writeText(" Td "); + fCurrentMatrixOrigin = xy; + fXAdvance = 0; + } + fXAdvance += advanceWidth; + if (!fInText) { + fContent->writeText("<"); + fInText = true; + } + if (fWideChars) { + SkPDFUtils::WriteUInt16BE(fContent, glyph); + } else { + SkASSERT(0 == glyph >> 8); + SkPDFUtils::WriteUInt8(fContent, static_cast(glyph)); + } + } + +private: + SkDynamicMemoryWStream* fContent; + SkPoint fCurrentMatrixOrigin; + SkScalar fXAdvance = 0.0f; + SkScalar fTextSkewX; + bool fWideChars = true; + bool fInText = false; + bool fInitialized = false; +}; +} // namespace + +static SkUnichar map_glyph(const std::vector& glyphToUnicode, SkGlyphID glyph) { + return glyph < glyphToUnicode.size() ? glyphToUnicode[SkToInt(glyph)] : -1; +} + +namespace { +struct PositionedGlyph { + SkPoint fPos; + SkGlyphID fGlyph; +}; +} + +static SkRect get_glyph_bounds_device_space(const SkGlyph* glyph, + SkScalar xScale, SkScalar yScale, + SkPoint xy, const SkMatrix& ctm) { + SkRect glyphBounds = SkMatrix::MakeScale(xScale, yScale).mapRect(glyph->rect()); + glyphBounds.offset(xy); + ctm.mapRect(&glyphBounds); // now in dev space. + return glyphBounds; +} + +static bool contains(const SkRect& r, SkPoint p) { + return r.left() <= p.x() && p.x() <= r.right() && + r.top() <= p.y() && p.y() <= r.bottom(); +} + +void SkPDFDevice::drawGlyphRunAsPath( + const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint) { + const SkFont& font = glyphRun.font(); + SkPath path; + + struct Rec { + SkPath* fPath; + SkPoint fOffset; + const SkPoint* fPos; + } rec = {&path, offset, glyphRun.positions().data()}; + + font.getPaths(glyphRun.glyphsIDs().data(), glyphRun.glyphsIDs().size(), + [](const SkPath* path, const SkMatrix& mx, void* ctx) { + Rec* rec = reinterpret_cast(ctx); + if (path) { + SkMatrix total = mx; + total.postTranslate(rec->fPos->fX + rec->fOffset.fX, + rec->fPos->fY + rec->fOffset.fY); + rec->fPath->addPath(*path, total); + } + rec->fPos += 1; // move to the next glyph's position + }, &rec); + this->internalDrawPath(this->cs(), this->ctm(), path, runPaint, true); + + SkFont transparentFont = glyphRun.font(); + transparentFont.setEmbolden(false); // Stop Recursion + SkGlyphRun tmpGlyphRun(glyphRun, transparentFont); + + SkPaint transparent; + transparent.setColor(SK_ColorTRANSPARENT); + + if (this->ctm().hasPerspective()) { + SkMatrix prevCTM = this->ctm(); + this->setCTM(SkMatrix::I()); + this->internalDrawGlyphRun(tmpGlyphRun, offset, transparent); + this->setCTM(prevCTM); + } else { + this->internalDrawGlyphRun(tmpGlyphRun, offset, transparent); + } +} + +static bool needs_new_font(SkPDFFont* font, const SkGlyph* glyph, + SkAdvancedTypefaceMetrics::FontType fontType) { + if (!font || !font->hasGlyph(glyph->getGlyphID())) { + return true; + } + if (fontType == SkAdvancedTypefaceMetrics::kOther_Font) { + return false; + } + if (glyph->isEmpty()) { + return false; + } + + bool bitmapOnly = nullptr == glyph->path(); + bool convertedToType3 = (font->getType() == SkAdvancedTypefaceMetrics::kOther_Font); + return convertedToType3 != bitmapOnly; +} + +void SkPDFDevice::internalDrawGlyphRun( + const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint) { + + const SkGlyphID* glyphIDs = glyphRun.glyphsIDs().data(); + uint32_t glyphCount = SkToU32(glyphRun.glyphsIDs().size()); + const SkFont& glyphRunFont = glyphRun.font(); + + if (!glyphCount || !glyphIDs || glyphRunFont.getSize() <= 0 || this->hasEmptyClip()) { + return; + } + if (runPaint.getPathEffect() + || runPaint.getMaskFilter() + || glyphRunFont.isEmbolden() + || this->ctm().hasPerspective() + || SkPaint::kFill_Style != runPaint.getStyle()) { + // Stroked Text doesn't work well with Type3 fonts. + this->drawGlyphRunAsPath(glyphRun, offset, runPaint); + return; + } + SkTypeface* typeface = glyphRunFont.getTypefaceOrDefault(); + if (!typeface) { + SkDebugf("SkPDF: SkTypeface::MakeDefault() returned nullptr.\n"); + return; + } + + const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, fDocument); + if (!metrics) { + return; + } + SkAdvancedTypefaceMetrics::FontType fontType = SkPDFFont::FontType(*metrics); + + const std::vector& glyphToUnicode = SkPDFFont::GetUnicodeMap(typeface, fDocument); + + SkClusterator clusterator(glyphRun); + + int emSize; + SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(*typeface, &emSize); + + SkScalar textSize = glyphRunFont.getSize(); + SkScalar advanceScale = textSize * glyphRunFont.getScaleX() / emSize; + + // textScaleX and textScaleY are used to get a conservative bounding box for glyphs. + SkScalar textScaleY = textSize / emSize; + SkScalar textScaleX = advanceScale + glyphRunFont.getSkewX() * textScaleY; + + SkRect clipStackBounds = this->cs().bounds(this->bounds()); + + SkTCopyOnFirstWrite paint(clean_paint(runPaint)); + ScopedContentEntry content(this, *paint, glyphRunFont.getScaleX()); + if (!content) { + return; + } + SkDynamicMemoryWStream* out = content.stream(); + + out->writeText("BT\n"); + + int markId = -1; + if (fNodeId) { + markId = fDocument->getMarkIdForNodeId(fNodeId); + } + + if (markId != -1) { + out->writeText("/P <writeDecAsText(markId); + out->writeText(" >>BDC\n"); + } + SK_AT_SCOPE_EXIT(if (markId != -1) out->writeText("EMC\n")); + + SK_AT_SCOPE_EXIT(out->writeText("ET\n")); + + const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1); + + if (clusterator.reversedChars()) { + out->writeText("/ReversedChars BMC\n"); + } + SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } ); + GlyphPositioner glyphPositioner(out, glyphRunFont.getSkewX(), offset); + SkPDFFont* font = nullptr; + + SkBulkGlyphMetricsAndPaths paths{strikeSpec}; + auto glyphs = paths.glyphs(glyphRun.glyphsIDs()); + + while (SkClusterator::Cluster c = clusterator.next()) { + int index = c.fGlyphIndex; + int glyphLimit = index + c.fGlyphCount; + + bool actualText = false; + SK_AT_SCOPE_EXIT(if (actualText) { + glyphPositioner.flush(); + out->writeText("EMC\n"); + }); + if (c.fUtf8Text) { // real cluster + // Check if `/ActualText` needed. + const char* textPtr = c.fUtf8Text; + const char* textEnd = c.fUtf8Text + c.fTextByteLength; + SkUnichar unichar = SkUTF::NextUTF8(&textPtr, textEnd); + if (unichar < 0) { + return; + } + if (textPtr < textEnd || // more characters left + glyphLimit > index + 1 || // toUnicode wouldn't work + unichar != map_glyph(glyphToUnicode, glyphIDs[index])) // test single Unichar map + { + glyphPositioner.flush(); + out->writeText("/Span<writeText("> >> BDC\n"); // begin marked-content sequence + // with an associated property list. + actualText = true; + } + } + for (; index < glyphLimit; ++index) { + SkGlyphID gid = glyphIDs[index]; + if (gid > maxGlyphID) { + continue; + } + SkPoint xy = glyphRun.positions()[index]; + // Do a glyph-by-glyph bounds-reject if positions are absolute. + SkRect glyphBounds = get_glyph_bounds_device_space( + glyphs[index], textScaleX, textScaleY, + xy + offset, this->ctm()); + if (glyphBounds.isEmpty()) { + if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) { + continue; + } + } else { + if (!clipStackBounds.intersects(glyphBounds)) { + continue; // reject glyphs as out of bounds + } + } + if (needs_new_font(font, glyphs[index], fontType)) { + // Not yet specified font or need to switch font. + font = SkPDFFont::GetFontResource(fDocument, glyphs[index], typeface); + SkASSERT(font); // All preconditions for SkPDFFont::GetFontResource are met. + glyphPositioner.flush(); + glyphPositioner.setWideChars(font->multiByteGlyphs()); + SkPDFWriteResourceName(out, SkPDFResourceType::kFont, + add_resource(fFontResources, font->indirectReference())); + out->writeText(" "); + SkPDFUtils::AppendScalar(textSize, out); + out->writeText(" Tf\n"); + + } + font->noteGlyphUsage(gid); + SkGlyphID encodedGlyph = font->multiByteGlyphs() + ? gid : font->glyphToPDFFontEncoding(gid); + SkScalar advance = advanceScale * glyphs[index]->advanceX(); + glyphPositioner.writeGlyph(xy, advance, encodedGlyph); + } + } +} + +void SkPDFDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) { + for (const SkGlyphRun& glyphRun : glyphRunList) { + this->internalDrawGlyphRun(glyphRun, glyphRunList.origin(), glyphRunList.paint()); + } +} + +void SkPDFDevice::drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode, + const SkPaint&) { + if (this->hasEmptyClip()) { + return; + } + // TODO: implement drawVertices +} + +void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content) { + SkASSERT(xObject); + SkPDFWriteResourceName(content, SkPDFResourceType::kXObject, + add_resource(fXObjectResources, xObject)); + content->writeText(" Do\n"); +} + +void SkPDFDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint& paint) { + SkASSERT(!paint.getImageFilter()); + + // Check if the source device is really a bitmapdevice (because that's what we returned + // from createDevice (likely due to an imagefilter) + SkPixmap pmap; + if (device->peekPixels(&pmap)) { + SkBitmap bitmap; + bitmap.installPixels(pmap); + this->drawSprite(bitmap, x, y, paint); + return; + } + + // our onCreateCompatibleDevice() always creates SkPDFDevice subclasses. + SkPDFDevice* pdfDevice = static_cast(device); + + if (pdfDevice->isContentEmpty()) { + return; + } + + SkMatrix matrix = SkMatrix::MakeTrans(SkIntToScalar(x), SkIntToScalar(y)); + ScopedContentEntry content(this, &this->cs(), matrix, paint); + if (!content) { + return; + } + if (content.needShape()) { + SkISize dim = device->imageInfo().dimensions(); + content.setShape(to_path(SkRect::Make(SkIRect::MakeXYWH(x, y, dim.width(), dim.height())))); + } + if (!content.needSource()) { + return; + } + this->drawFormXObject(pdfDevice->makeFormXObjectFromDevice(), content.stream()); +} + +sk_sp SkPDFDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) { + return SkSurface::MakeRaster(info, &props); +} + +static std::vector sort(const SkTHashSet& src) { + std::vector dst; + dst.reserve(src.count()); + src.foreach([&dst](SkPDFIndirectReference ref) { dst.push_back(ref); } ); + std::sort(dst.begin(), dst.end(), + [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; }); + return dst; +} + +std::unique_ptr SkPDFDevice::makeResourceDict() { + return SkPDFMakeResourceDict(sort(fGraphicStateResources), + sort(fShaderResources), + sort(fXObjectResources), + sort(fFontResources)); +} + +std::unique_ptr SkPDFDevice::content() { + if (fActiveStackState.fContentStream) { + fActiveStackState.drainStack(); + fActiveStackState = SkPDFGraphicStackState(); + } + if (fContent.bytesWritten() == 0) { + return skstd::make_unique(); + } + SkDynamicMemoryWStream buffer; + if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) { + SkPDFUtils::AppendTransform(fInitialTransform, &buffer); + } + if (fNeedsExtraSave) { + buffer.writeText("q\n"); + } + fContent.writeToAndReset(&buffer); + if (fNeedsExtraSave) { + buffer.writeText("Q\n"); + } + fNeedsExtraSave = false; + return std::unique_ptr(buffer.detachAsStream()); +} + +/* Draws an inverse filled path by using Path Ops to compute the positive + * inverse using the current clip as the inverse bounds. + * Return true if this was an inverse path and was properly handled, + * otherwise returns false and the normal drawing routine should continue, + * either as a (incorrect) fallback or because the path was not inverse + * in the first place. + */ +bool SkPDFDevice::handleInversePath(const SkPath& origPath, + const SkPaint& paint, + bool pathIsMutable) { + if (!origPath.isInverseFillType()) { + return false; + } + + if (this->hasEmptyClip()) { + return false; + } + + SkPath modifiedPath; + SkPath* pathPtr = const_cast(&origPath); + SkPaint noInversePaint(paint); + + // Merge stroking operations into final path. + if (SkPaint::kStroke_Style == paint.getStyle() || + SkPaint::kStrokeAndFill_Style == paint.getStyle()) { + bool doFillPath = paint.getFillPath(origPath, &modifiedPath); + if (doFillPath) { + noInversePaint.setStyle(SkPaint::kFill_Style); + noInversePaint.setStrokeWidth(0); + pathPtr = &modifiedPath; + } else { + // To be consistent with the raster output, hairline strokes + // are rendered as non-inverted. + modifiedPath.toggleInverseFillType(); + this->internalDrawPath(this->cs(), this->ctm(), modifiedPath, paint, true); + return true; + } + } + + // Get bounds of clip in current transform space + // (clip bounds are given in device space). + SkMatrix transformInverse; + SkMatrix totalMatrix = this->ctm(); + + if (!totalMatrix.invert(&transformInverse)) { + return false; + } + SkRect bounds = this->cs().bounds(this->bounds()); + transformInverse.mapRect(&bounds); + + // Extend the bounds by the line width (plus some padding) + // so the edge doesn't cause a visible stroke. + bounds.outset(paint.getStrokeWidth() + SK_Scalar1, + paint.getStrokeWidth() + SK_Scalar1); + + if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) { + return false; + } + + this->internalDrawPath(this->cs(), this->ctm(), modifiedPath, noInversePaint, true); + return true; +} + +SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(SkIRect bounds, bool alpha) { + SkMatrix inverseTransform = SkMatrix::I(); + if (!fInitialTransform.isIdentity()) { + if (!fInitialTransform.invert(&inverseTransform)) { + SkDEBUGFAIL("Layer initial transform should be invertible."); + inverseTransform.reset(); + } + } + const char* colorSpace = alpha ? "DeviceGray" : nullptr; + + SkPDFIndirectReference xobject = + SkPDFMakeFormXObject(fDocument, this->content(), + SkPDFMakeArray(bounds.left(), bounds.top(), + bounds.right(), bounds.bottom()), + this->makeResourceDict(), inverseTransform, colorSpace); + // We always draw the form xobjects that we create back into the device, so + // we simply preserve the font usage instead of pulling it out and merging + // it back in later. + this->reset(); + return xobject; +} + +SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) { + return this->makeFormXObjectFromDevice(SkIRect{0, 0, this->width(), this->height()}, alpha); +} + +void SkPDFDevice::drawFormXObjectWithMask(SkPDFIndirectReference xObject, + SkPDFIndirectReference sMask, + SkBlendMode mode, + bool invertClip) { + SkASSERT(sMask); + SkPaint paint; + paint.setBlendMode(mode); + ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint); + if (!content) { + return; + } + this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState( + sMask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode, + fDocument), content.stream()); + this->drawFormXObject(xObject, content.stream()); + this->clearMaskOnGraphicState(content.stream()); +} + + +static bool treat_as_regular_pdf_blend_mode(SkBlendMode blendMode) { + return nullptr != SkPDFUtils::BlendModeName(blendMode); +} + +static void populate_graphic_state_entry_from_paint( + SkPDFDocument* doc, + const SkMatrix& matrix, + const SkClipStack* clipStack, + SkIRect deviceBounds, + const SkPaint& paint, + const SkMatrix& initialTransform, + SkScalar textScale, + SkPDFGraphicStackState::Entry* entry, + SkTHashSet* shaderResources, + SkTHashSet* graphicStateResources) { + NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false); + NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false); + NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false); + + entry->fMatrix = matrix; + entry->fClipStackGenID = clipStack ? clipStack->getTopmostGenID() + : SkClipStack::kWideOpenGenID; + SkColor4f color = paint.getColor4f(); + entry->fColor = {color.fR, color.fG, color.fB, 1}; + entry->fShaderIndex = -1; + + // PDF treats a shader as a color, so we only set one or the other. + SkShader* shader = paint.getShader(); + if (shader) { + if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) { + // We don't have to set a shader just for a color. + SkShader::GradientInfo gradientInfo; + SkColor gradientColor = SK_ColorBLACK; + gradientInfo.fColors = &gradientColor; + gradientInfo.fColorOffsets = nullptr; + gradientInfo.fColorCount = 1; + SkAssertResult(shader->asAGradient(&gradientInfo) == SkShader::kColor_GradientType); + color = SkColor4f::FromColor(gradientColor); + entry->fColor ={color.fR, color.fG, color.fB, 1}; + + } else { + // PDF positions patterns relative to the initial transform, so + // we need to apply the current transform to the shader parameters. + SkMatrix transform = matrix; + transform.postConcat(initialTransform); + + // PDF doesn't support kClamp_TileMode, so we simulate it by making + // a pattern the size of the current clip. + SkRect clipStackBounds = clipStack ? clipStack->bounds(deviceBounds) + : SkRect::Make(deviceBounds); + + // We need to apply the initial transform to bounds in order to get + // bounds in a consistent coordinate system. + initialTransform.mapRect(&clipStackBounds); + SkIRect bounds; + clipStackBounds.roundOut(&bounds); + + SkPDFIndirectReference pdfShader + = SkPDFMakeShader(doc, shader, transform, bounds, paint.getColor4f()); + + if (pdfShader) { + // pdfShader has been canonicalized so we can directly compare pointers. + entry->fShaderIndex = add_resource(*shaderResources, pdfShader); + } + } + } + + SkPDFIndirectReference newGraphicState; + if (color == paint.getColor4f()) { + newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(doc, paint); + } else { + SkPaint newPaint = paint; + newPaint.setColor4f(color, nullptr); + newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(doc, newPaint); + } + entry->fGraphicStateIndex = add_resource(*graphicStateResources, newGraphicState); + entry->fTextScaleX = textScale; +} + +SkDynamicMemoryWStream* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack, + const SkMatrix& matrix, + const SkPaint& paint, + SkScalar textScale, + SkPDFIndirectReference* dst) { + SkASSERT(!*dst); + SkBlendMode blendMode = paint.getBlendMode(); + + // Dst xfer mode doesn't draw source at all. + if (blendMode == SkBlendMode::kDst) { + return nullptr; + } + + // For the following modes, we want to handle source and destination + // separately, so make an object of what's already there. + if (!treat_as_regular_pdf_blend_mode(blendMode) && blendMode != SkBlendMode::kDstOver) { + if (!isContentEmpty()) { + *dst = this->makeFormXObjectFromDevice(); + SkASSERT(isContentEmpty()); + } else if (blendMode != SkBlendMode::kSrc && + blendMode != SkBlendMode::kSrcOut) { + // Except for Src and SrcOut, if there isn't anything already there, + // then we're done. + return nullptr; + } + } + // TODO(vandebo): Figure out how/if we can handle the following modes: + // Xor, Plus. For now, we treat them as SrcOver/Normal. + + if (treat_as_regular_pdf_blend_mode(blendMode)) { + if (!fActiveStackState.fContentStream) { + if (fContent.bytesWritten() != 0) { + fContent.writeText("Q\nq\n"); + fNeedsExtraSave = true; + } + fActiveStackState = SkPDFGraphicStackState(&fContent); + } else { + SkASSERT(fActiveStackState.fContentStream = &fContent); + } + } else { + fActiveStackState.drainStack(); + fActiveStackState = SkPDFGraphicStackState(&fContentBuffer); + } + SkASSERT(fActiveStackState.fContentStream); + SkPDFGraphicStackState::Entry entry; + populate_graphic_state_entry_from_paint( + fDocument, + matrix, + clipStack, + this->bounds(), + paint, + fInitialTransform, + textScale, + &entry, + &fShaderResources, + &fGraphicStateResources); + fActiveStackState.updateClip(clipStack, this->bounds()); + fActiveStackState.updateMatrix(entry.fMatrix); + fActiveStackState.updateDrawingState(entry); + + return fActiveStackState.fContentStream; +} + +void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack, + SkBlendMode blendMode, + SkPDFIndirectReference dst, + SkPath* shape) { + SkASSERT(blendMode != SkBlendMode::kDst); + if (treat_as_regular_pdf_blend_mode(blendMode)) { + SkASSERT(!dst); + return; + } + + SkASSERT(fActiveStackState.fContentStream); + + fActiveStackState.drainStack(); + fActiveStackState = SkPDFGraphicStackState(); + + if (blendMode == SkBlendMode::kDstOver) { + SkASSERT(!dst); + if (fContentBuffer.bytesWritten() != 0) { + if (fContent.bytesWritten() != 0) { + fContentBuffer.writeText("Q\nq\n"); + fNeedsExtraSave = true; + } + fContentBuffer.prependToAndReset(&fContent); + SkASSERT(fContentBuffer.bytesWritten() == 0); + } + return; + } + if (fContentBuffer.bytesWritten() != 0) { + if (fContent.bytesWritten() != 0) { + fContent.writeText("Q\nq\n"); + fNeedsExtraSave = true; + } + fContentBuffer.writeToAndReset(&fContent); + SkASSERT(fContentBuffer.bytesWritten() == 0); + } + + if (!dst) { + SkASSERT(blendMode == SkBlendMode::kSrc || + blendMode == SkBlendMode::kSrcOut); + return; + } + + SkASSERT(dst); + // Changing the current content into a form-xobject will destroy the clip + // objects which is fine since the xobject will already be clipped. However + // if source has shape, we need to clip it too, so a copy of the clip is + // saved. + + SkPaint stockPaint; + + SkPDFIndirectReference srcFormXObject; + if (this->isContentEmpty()) { + // If nothing was drawn and there's no shape, then the draw was a + // no-op, but dst needs to be restored for that to be true. + // If there is shape, then an empty source with Src, SrcIn, SrcOut, + // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop + // reduces to Dst. + if (shape == nullptr || blendMode == SkBlendMode::kDstOut || + blendMode == SkBlendMode::kSrcATop) { + ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint); + this->drawFormXObject(dst, content.stream()); + return; + } else { + blendMode = SkBlendMode::kClear; + } + } else { + srcFormXObject = this->makeFormXObjectFromDevice(); + } + + // TODO(vandebo) srcFormXObject may contain alpha, but here we want it + // without alpha. + if (blendMode == SkBlendMode::kSrcATop) { + // TODO(vandebo): In order to properly support SrcATop we have to track + // the shape of what's been drawn at all times. It's the intersection of + // the non-transparent parts of the device and the outlines (shape) of + // all images and devices drawn. + this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, true); + } else { + if (shape != nullptr) { + // Draw shape into a form-xobject. + SkPaint filledPaint; + filledPaint.setColor(SK_ColorBLACK); + filledPaint.setStyle(SkPaint::kFill_Style); + SkClipStack empty; + SkPDFDevice shapeDev(this->size(), fDocument, fInitialTransform); + shapeDev.internalDrawPath(clipStack ? *clipStack : empty, + SkMatrix::I(), *shape, filledPaint, true); + this->drawFormXObjectWithMask(dst, shapeDev.makeFormXObjectFromDevice(), + SkBlendMode::kSrcOver, true); + } else { + this->drawFormXObjectWithMask(dst, srcFormXObject, SkBlendMode::kSrcOver, true); + } + } + + if (blendMode == SkBlendMode::kClear) { + return; + } else if (blendMode == SkBlendMode::kSrc || + blendMode == SkBlendMode::kDstATop) { + ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint); + if (content) { + this->drawFormXObject(srcFormXObject, content.stream()); + } + if (blendMode == SkBlendMode::kSrc) { + return; + } + } else if (blendMode == SkBlendMode::kSrcATop) { + ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint); + if (content) { + this->drawFormXObject(dst, content.stream()); + } + } + + SkASSERT(blendMode == SkBlendMode::kSrcIn || + blendMode == SkBlendMode::kDstIn || + blendMode == SkBlendMode::kSrcOut || + blendMode == SkBlendMode::kDstOut || + blendMode == SkBlendMode::kSrcATop || + blendMode == SkBlendMode::kDstATop || + blendMode == SkBlendMode::kModulate); + + if (blendMode == SkBlendMode::kSrcIn || + blendMode == SkBlendMode::kSrcOut || + blendMode == SkBlendMode::kSrcATop) { + this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, + blendMode == SkBlendMode::kSrcOut); + return; + } else { + SkBlendMode mode = SkBlendMode::kSrcOver; + if (blendMode == SkBlendMode::kModulate) { + this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, false); + mode = SkBlendMode::kMultiply; + } + this->drawFormXObjectWithMask(dst, srcFormXObject, mode, blendMode == SkBlendMode::kDstOut); + return; + } +} + +bool SkPDFDevice::isContentEmpty() { + return fContent.bytesWritten() == 0 && fContentBuffer.bytesWritten() == 0; +} + +static SkSize rect_to_size(const SkRect& r) { return {r.width(), r.height()}; } + +static sk_sp color_filter(const SkImage* image, + SkColorFilter* colorFilter) { + auto surface = + SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(image->dimensions())); + SkASSERT(surface); + SkCanvas* canvas = surface->getCanvas(); + canvas->clear(SK_ColorTRANSPARENT); + SkPaint paint; + paint.setColorFilter(sk_ref_sp(colorFilter)); + canvas->drawImage(image, 0, 0, &paint); + return surface->makeImageSnapshot(); +} + +//////////////////////////////////////////////////////////////////////////////// + +static bool is_integer(SkScalar x) { + return x == SkScalarTruncToScalar(x); +} + +static bool is_integral(const SkRect& r) { + return is_integer(r.left()) && + is_integer(r.top()) && + is_integer(r.right()) && + is_integer(r.bottom()); +} + +void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset, + const SkRect* src, + const SkRect& dst, + const SkPaint& srcPaint, + const SkMatrix& ctm) { + if (this->hasEmptyClip()) { + return; + } + if (!imageSubset) { + return; + } + + // First, figure out the src->dst transform and subset the image if needed. + SkIRect bounds = imageSubset.image()->bounds(); + SkRect srcRect = src ? *src : SkRect::Make(bounds); + SkMatrix transform; + transform.setRectToRect(srcRect, dst, SkMatrix::kFill_ScaleToFit); + if (src && *src != SkRect::Make(bounds)) { + if (!srcRect.intersect(SkRect::Make(bounds))) { + return; + } + srcRect.roundOut(&bounds); + transform.preTranslate(SkIntToScalar(bounds.x()), + SkIntToScalar(bounds.y())); + if (bounds != imageSubset.image()->bounds()) { + imageSubset = imageSubset.subset(bounds); + } + if (!imageSubset) { + return; + } + } + + // If the image is opaque and the paint's alpha is too, replace + // kSrc blendmode with kSrcOver. http://crbug.com/473572 + SkTCopyOnFirstWrite paint(srcPaint); + if (SkBlendMode::kSrcOver != paint->getBlendMode() && + imageSubset.image()->isOpaque() && + kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false)) + { + paint.writable()->setBlendMode(SkBlendMode::kSrcOver); + } + + // Alpha-only images need to get their color from the shader, before + // applying the colorfilter. + if (imageSubset.image()->isAlphaOnly() && paint->getColorFilter()) { + // must blend alpha image and shader before applying colorfilter. + auto surface = + SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(imageSubset.image()->dimensions())); + SkCanvas* canvas = surface->getCanvas(); + SkPaint tmpPaint; + // In the case of alpha images with shaders, the shader's coordinate + // system is the image's coordiantes. + tmpPaint.setShader(sk_ref_sp(paint->getShader())); + tmpPaint.setColor4f(paint->getColor4f(), nullptr); + canvas->clear(0x00000000); + canvas->drawImage(imageSubset.image().get(), 0, 0, &tmpPaint); + if (paint->getShader() != nullptr) { + paint.writable()->setShader(nullptr); + } + imageSubset = SkKeyedImage(surface->makeImageSnapshot()); + SkASSERT(!imageSubset.image()->isAlphaOnly()); + } + + if (imageSubset.image()->isAlphaOnly()) { + // The ColorFilter applies to the paint color/shader, not the alpha layer. + SkASSERT(nullptr == paint->getColorFilter()); + + sk_sp mask = alpha_image_to_greyscale_image(imageSubset.image().get()); + if (!mask) { + return; + } + // PDF doesn't seem to allow masking vector graphics with an Image XObject. + // Must mask with a Form XObject. + sk_sp maskDevice = this->makeCongruentDevice(); + { + SkCanvas canvas(maskDevice); + // This clip prevents the mask image shader from covering + // entire device if unnecessary. + canvas.clipRect(this->cs().bounds(this->bounds())); + canvas.concat(ctm); + if (paint->getMaskFilter()) { + SkPaint tmpPaint; + tmpPaint.setShader(mask->makeShader(&transform)); + tmpPaint.setMaskFilter(sk_ref_sp(paint->getMaskFilter())); + canvas.drawRect(dst, tmpPaint); + } else { + if (src && !is_integral(*src)) { + canvas.clipRect(dst); + } + canvas.concat(transform); + canvas.drawImage(mask, 0, 0); + } + } + SkIRect maskDeviceBounds = maskDevice->cs().bounds(maskDevice->bounds()).roundOut(); + if (!ctm.isIdentity() && paint->getShader()) { + transform_shader(paint.writable(), ctm); // Since we are using identity matrix. + } + ScopedContentEntry content(this, &this->cs(), SkMatrix::I(), *paint); + if (!content) { + return; + } + this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState( + maskDevice->makeFormXObjectFromDevice(maskDeviceBounds, true), false, + SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), content.stream()); + SkPDFUtils::AppendRectangle(SkRect::Make(this->size()), content.stream()); + SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kWinding_FillType, content.stream()); + this->clearMaskOnGraphicState(content.stream()); + return; + } + if (paint->getMaskFilter()) { + paint.writable()->setShader(imageSubset.image()->makeShader(&transform)); + SkPath path = to_path(dst); // handles non-integral clipping. + this->internalDrawPath(this->cs(), this->ctm(), path, *paint, true); + return; + } + transform.postConcat(ctm); + + bool needToRestore = false; + if (src && !is_integral(*src)) { + // Need sub-pixel clipping to fix https://bug.skia.org/4374 + this->cs().save(); + this->cs().clipRect(dst, ctm, SkClipOp::kIntersect, true); + needToRestore = true; + } + SK_AT_SCOPE_EXIT(if (needToRestore) { this->cs().restore(); }); + + SkMatrix matrix = transform; + + // Rasterize the bitmap using perspective in a new bitmap. + if (transform.hasPerspective()) { + // Transform the bitmap in the new space, without taking into + // account the initial transform. + SkRect imageBounds = SkRect::Make(imageSubset.image()->bounds()); + SkPath perspectiveOutline = to_path(imageBounds); + perspectiveOutline.transform(transform); + + // TODO(edisonn): perf - use current clip too. + // Retrieve the bounds of the new shape. + SkRect bounds = perspectiveOutline.getBounds(); + + // Transform the bitmap in the new space, taking into + // account the initial transform. + SkMatrix total = transform; + total.postConcat(fInitialTransform); + + SkPath physicalPerspectiveOutline = to_path(imageBounds); + physicalPerspectiveOutline.transform(total); + + SkRect physicalPerspectiveBounds = + physicalPerspectiveOutline.getBounds(); + SkScalar scaleX = physicalPerspectiveBounds.width() / bounds.width(); + SkScalar scaleY = physicalPerspectiveBounds.height() / bounds.height(); + + // TODO(edisonn): A better approach would be to use a bitmap shader + // (in clamp mode) and draw a rect over the entire bounding box. Then + // intersect perspectiveOutline to the clip. That will avoid introducing + // alpha to the image while still giving good behavior at the edge of + // the image. Avoiding alpha will reduce the pdf size and generation + // CPU time some. + + SkISize wh = rect_to_size(physicalPerspectiveBounds).toCeil(); + + auto surface = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(wh)); + if (!surface) { + return; + } + SkCanvas* canvas = surface->getCanvas(); + canvas->clear(SK_ColorTRANSPARENT); + + SkScalar deltaX = bounds.left(); + SkScalar deltaY = bounds.top(); + + SkMatrix offsetMatrix = transform; + offsetMatrix.postTranslate(-deltaX, -deltaY); + offsetMatrix.postScale(scaleX, scaleY); + + // Translate the draw in the new canvas, so we perfectly fit the + // shape in the bitmap. + canvas->setMatrix(offsetMatrix); + canvas->drawImage(imageSubset.image(), 0, 0); + // Make sure the final bits are in the bitmap. + surface->flush(); + + // In the new space, we use the identity matrix translated + // and scaled to reflect DPI. + matrix.setScale(1 / scaleX, 1 / scaleY); + matrix.postTranslate(deltaX, deltaY); + + imageSubset = SkKeyedImage(surface->makeImageSnapshot()); + if (!imageSubset) { + return; + } + } + + SkMatrix scaled; + // Adjust for origin flip. + scaled.setScale(SK_Scalar1, -SK_Scalar1); + scaled.postTranslate(0, SK_Scalar1); + // Scale the image up from 1x1 to WxH. + SkIRect subset = imageSubset.image()->bounds(); + scaled.postScale(SkIntToScalar(subset.width()), + SkIntToScalar(subset.height())); + scaled.postConcat(matrix); + ScopedContentEntry content(this, &this->cs(), scaled, *paint); + if (!content) { + return; + } + if (content.needShape()) { + SkPath shape = to_path(SkRect::Make(subset)); + shape.transform(matrix); + content.setShape(shape); + } + if (!content.needSource()) { + return; + } + + if (SkColorFilter* colorFilter = paint->getColorFilter()) { + sk_sp img = color_filter(imageSubset.image().get(), colorFilter); + imageSubset = SkKeyedImage(std::move(img)); + if (!imageSubset) { + return; + } + // TODO(halcanary): de-dupe this by caching filtered images. + // (maybe in the resource cache?) + } + + SkBitmapKey key = imageSubset.key(); + SkPDFIndirectReference* pdfimagePtr = fDocument->fPDFBitmapMap.find(key); + SkPDFIndirectReference pdfimage = pdfimagePtr ? *pdfimagePtr : SkPDFIndirectReference(); + if (!pdfimagePtr) { + SkASSERT(imageSubset); + pdfimage = SkPDFSerializeImage(imageSubset.image().get(), fDocument, + fDocument->metadata().fEncodingQuality); + SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0})); + fDocument->fPDFBitmapMap.set(key, pdfimage); + } + SkASSERT(pdfimage != SkPDFIndirectReference()); + this->drawFormXObject(pdfimage, content.stream()); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkImageFilter.h" +#include "src/core/SkSpecialImage.h" + +void SkPDFDevice::drawSpecial(SkSpecialImage* srcImg, int x, int y, const SkPaint& paint, + SkImage* clipImage, const SkMatrix& clipMatrix) { + if (this->hasEmptyClip()) { + return; + } + SkASSERT(!srcImg->isTextureBacked()); + + //TODO: clipImage support + + SkBitmap resultBM; + + SkImageFilter* filter = paint.getImageFilter(); + if (filter) { + SkIPoint offset = SkIPoint::Make(0, 0); + SkMatrix matrix = this->ctm(); + matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y)); + const SkIRect clipBounds = + this->cs().bounds(this->bounds()).roundOut().makeOffset(-x, -y); + sk_sp cache(this->getImageFilterCache()); + // TODO: Should PDF be operating in a specified color type/space? For now, run the filter + // in the same color space as the source (this is different from all other backends). + SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), kN32_SkColorType, + srcImg->getColorSpace(), srcImg); + + sk_sp resultImg(as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset)); + if (resultImg) { + SkPaint tmpUnfiltered(paint); + tmpUnfiltered.setImageFilter(nullptr); + if (resultImg->getROPixels(&resultBM)) { + this->drawSprite(resultBM, x + offset.x(), y + offset.y(), tmpUnfiltered); + } + } + } else { + if (srcImg->getROPixels(&resultBM)) { + this->drawSprite(resultBM, x, y, paint); + } + } +} + +sk_sp SkPDFDevice::makeSpecial(const SkBitmap& bitmap) { + return SkSpecialImage::MakeFromRaster(bitmap.bounds(), bitmap); +} + +sk_sp SkPDFDevice::makeSpecial(const SkImage* image) { + return SkSpecialImage::MakeFromImage(nullptr, image->bounds(), image->makeNonTextureImage()); +} + +SkImageFilterCache* SkPDFDevice::getImageFilterCache() { + // We always return a transient cache, so it is freed after each + // filter traversal. + return SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFDevice.h b/gfx/skia/skia/src/pdf/SkPDFDevice.h new file mode 100644 index 0000000000..ad8c12a19f --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFDevice.h @@ -0,0 +1,200 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPDFDevice_DEFINED +#define SkPDFDevice_DEFINED + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/core/SkPaint.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/private/SkTHash.h" +#include "src/core/SkClipStack.h" +#include "src/core/SkClipStackDevice.h" +#include "src/core/SkTextBlobPriv.h" +#include "src/pdf/SkKeyedImage.h" +#include "src/pdf/SkPDFGraphicStackState.h" +#include "src/pdf/SkPDFTypes.h" + +#include + +class SkGlyphRunList; +class SkKeyedImage; +class SkPDFArray; +class SkPDFDevice; +class SkPDFDict; +class SkPDFDocument; +class SkPDFFont; +class SkPDFObject; +class SkPath; +class SkRRect; +struct SkPDFIndirectReference; + +/** + * \class SkPDFDevice + * + * An SkPDFDevice is the drawing context for a page or layer of PDF + * content. + */ +class SkPDFDevice final : public SkClipStackDevice { +public: + /** + * @param pageSize Page size in point units. + * 1 point == 127/360 mm == 1/72 inch + * @param document A non-null pointer back to the + * PDFDocument object. The document is responsible for + * de-duplicating across pages (via the SkPDFDocument) and + * for early serializing of large immutable objects, such + * as images (via SkPDFDocument::serialize()). + * @param initialTransform Transform to be applied to the entire page. + */ + SkPDFDevice(SkISize pageSize, SkPDFDocument* document, + const SkMatrix& initialTransform = SkMatrix::I()); + + sk_sp makeCongruentDevice() { + return sk_make_sp(this->size(), fDocument); + } + + ~SkPDFDevice() override; + + /** + * These are called inside the per-device-layer loop for each draw call. + * When these are called, we have already applied any saveLayer + * operations, and are handling any looping from the paint. + */ + void drawPaint(const SkPaint& paint) override; + void drawPoints(SkCanvas::PointMode mode, + size_t count, const SkPoint[], + const SkPaint& paint) override; + void drawRect(const SkRect& r, const SkPaint& paint) override; + void drawOval(const SkRect& oval, const SkPaint& paint) override; + void drawRRect(const SkRRect& rr, const SkPaint& paint) override; + void drawPath(const SkPath& origpath, const SkPaint& paint, bool pathIsMutable) override; + void drawBitmapRect(const SkBitmap& bitmap, const SkRect* src, + const SkRect& dst, const SkPaint&, SkCanvas::SrcRectConstraint) override; + void drawSprite(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint) override; + + void drawImageRect(const SkImage*, + const SkRect* src, + const SkRect& dst, + const SkPaint&, + SkCanvas::SrcRectConstraint) override; + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override; + void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, SkBlendMode, + const SkPaint&) override; + void drawDevice(SkBaseDevice*, int x, int y, + const SkPaint&) override; + + // PDF specific methods. + + /** Create the resource dictionary for this device. Destructive. */ + std::unique_ptr makeResourceDict(); + + /** Returns a SkStream with the page contents. + */ + std::unique_ptr content(); + + SkISize size() const { return this->imageInfo().dimensions(); } + SkIRect bounds() const { return this->imageInfo().bounds(); } + + void DrawGlyphRunAsPath(SkPDFDevice* dev, const SkGlyphRun& glyphRun, SkPoint offset); + + const SkMatrix& initialTransform() const { return fInitialTransform; } + +protected: + sk_sp makeSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + void drawAnnotation(const SkRect&, const char key[], SkData* value) override; + + void drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&, + SkImage*, const SkMatrix&) override; + sk_sp makeSpecial(const SkBitmap&) override; + sk_sp makeSpecial(const SkImage*) override; + SkImageFilterCache* getImageFilterCache() override; + +private: + // TODO(vandebo): push most of SkPDFDevice's state into a core object in + // order to get the right access levels without using friend. + friend class ScopedContentEntry; + + SkMatrix fInitialTransform; + + SkTHashSet fGraphicStateResources; + SkTHashSet fXObjectResources; + SkTHashSet fShaderResources; + SkTHashSet fFontResources; + int fNodeId; + + SkDynamicMemoryWStream fContent; + SkDynamicMemoryWStream fContentBuffer; + bool fNeedsExtraSave = false; + SkPDFGraphicStackState fActiveStackState; + SkPDFDocument* fDocument; + + //////////////////////////////////////////////////////////////////////////// + + SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override; + + // Set alpha to true if making a transparency group form x-objects. + SkPDFIndirectReference makeFormXObjectFromDevice(bool alpha = false); + SkPDFIndirectReference makeFormXObjectFromDevice(SkIRect bbox, bool alpha = false); + + void drawFormXObjectWithMask(SkPDFIndirectReference xObject, + SkPDFIndirectReference sMask, + SkBlendMode, + bool invertClip); + + // If the paint or clip is such that we shouldn't draw anything, this + // returns nullptr and does not create a content entry. + // setUpContentEntry and finishContentEntry can be used directly, but + // the preferred method is to use the ScopedContentEntry helper class. + SkDynamicMemoryWStream* setUpContentEntry(const SkClipStack* clipStack, + const SkMatrix& matrix, + const SkPaint& paint, + SkScalar, + SkPDFIndirectReference* dst); + void finishContentEntry(const SkClipStack*, SkBlendMode, SkPDFIndirectReference, SkPath*); + bool isContentEmpty(); + + void internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint); + void drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint); + + void internalDrawImageRect(SkKeyedImage, + const SkRect* src, + const SkRect& dst, + const SkPaint&, + const SkMatrix& canvasTransformationMatrix); + + void internalDrawPath(const SkClipStack&, + const SkMatrix&, + const SkPath&, + const SkPaint&, + bool pathIsMutable); + + void internalDrawPathWithFilter(const SkClipStack& clipStack, + const SkMatrix& ctm, + const SkPath& origPath, + const SkPaint& paint); + + bool handleInversePath(const SkPath& origPath, const SkPaint& paint, bool pathIsMutable); + + void clearMaskOnGraphicState(SkDynamicMemoryWStream*); + void setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream*); + void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*); + + bool hasEmptyClip() const { return this->cs().isEmpty(this->bounds()); } + + void reset(); + + typedef SkClipStackDevice INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFDocument.cpp b/gfx/skia/skia/src/pdf/SkPDFDocument.cpp new file mode 100644 index 0000000000..abf301596f --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFDocument.cpp @@ -0,0 +1,594 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/docs/SkPDFDocument.h" +#include "src/pdf/SkPDFDocumentPriv.h" + +#include "include/core/SkStream.h" +#include "include/docs/SkPDFDocument.h" +#include "include/private/SkTo.h" +#include "src/core/SkMakeUnique.h" +#include "src/pdf/SkPDFDevice.h" +#include "src/pdf/SkPDFFont.h" +#include "src/pdf/SkPDFGradientShader.h" +#include "src/pdf/SkPDFGraphicState.h" +#include "src/pdf/SkPDFShader.h" +#include "src/pdf/SkPDFTag.h" +#include "src/pdf/SkPDFUtils.h" + +#include + +// For use in SkCanvas::drawAnnotation +const char* SkPDFGetNodeIdKey() { + static constexpr char key[] = "PDF_Node_Key"; + return key; +} + +//////////////////////////////////////////////////////////////////////////////// + +void SkPDFOffsetMap::markStartOfDocument(const SkWStream* s) { fBaseOffset = s->bytesWritten(); } + +static size_t difference(size_t minuend, size_t subtrahend) { + return SkASSERT(minuend >= subtrahend), minuend - subtrahend; +} + +void SkPDFOffsetMap::markStartOfObject(int referenceNumber, const SkWStream* s) { + SkASSERT(referenceNumber > 0); + size_t index = SkToSizeT(referenceNumber - 1); + if (index >= fOffsets.size()) { + fOffsets.resize(index + 1); + } + fOffsets[index] = SkToInt(difference(s->bytesWritten(), fBaseOffset)); +} + +int SkPDFOffsetMap::objectCount() const { + return SkToInt(fOffsets.size() + 1); // Include the special zeroth object in the count. +} + +int SkPDFOffsetMap::emitCrossReferenceTable(SkWStream* s) const { + int xRefFileOffset = SkToInt(difference(s->bytesWritten(), fBaseOffset)); + s->writeText("xref\n0 "); + s->writeDecAsText(this->objectCount()); + s->writeText("\n0000000000 65535 f \n"); + for (int offset : fOffsets) { + SkASSERT(offset > 0); // Offset was set. + s->writeBigDecAsText(offset, 10); + s->writeText(" 00000 n \n"); + } + return xRefFileOffset; +} +// +//////////////////////////////////////////////////////////////////////////////// + +#define SKPDF_MAGIC "\xD3\xEB\xE9\xE1" +#ifndef SK_BUILD_FOR_WIN +static_assert((SKPDF_MAGIC[0] & 0x7F) == "Skia"[0], ""); +static_assert((SKPDF_MAGIC[1] & 0x7F) == "Skia"[1], ""); +static_assert((SKPDF_MAGIC[2] & 0x7F) == "Skia"[2], ""); +static_assert((SKPDF_MAGIC[3] & 0x7F) == "Skia"[3], ""); +#endif +static void serializeHeader(SkPDFOffsetMap* offsetMap, SkWStream* wStream) { + offsetMap->markStartOfDocument(wStream); + wStream->writeText("%PDF-1.4\n%" SKPDF_MAGIC "\n"); + // The PDF spec recommends including a comment with four + // bytes, all with their high bits set. "\xD3\xEB\xE9\xE1" is + // "Skia" with the high bits set. +} +#undef SKPDF_MAGIC + +static void begin_indirect_object(SkPDFOffsetMap* offsetMap, + SkPDFIndirectReference ref, + SkWStream* s) { + offsetMap->markStartOfObject(ref.fValue, s); + s->writeDecAsText(ref.fValue); + s->writeText(" 0 obj\n"); // Generation number is always 0. +} + +static void end_indirect_object(SkWStream* s) { s->writeText("\nendobj\n"); } + +// Xref table and footer +static void serialize_footer(const SkPDFOffsetMap& offsetMap, + SkWStream* wStream, + SkPDFIndirectReference infoDict, + SkPDFIndirectReference docCatalog, + SkUUID uuid) { + int xRefFileOffset = offsetMap.emitCrossReferenceTable(wStream); + SkPDFDict trailerDict; + trailerDict.insertInt("Size", offsetMap.objectCount()); + SkASSERT(docCatalog != SkPDFIndirectReference()); + trailerDict.insertRef("Root", docCatalog); + SkASSERT(infoDict != SkPDFIndirectReference()); + trailerDict.insertRef("Info", infoDict); + if (SkUUID() != uuid) { + trailerDict.insertObject("ID", SkPDFMetadata::MakePdfId(uuid, uuid)); + } + wStream->writeText("trailer\n"); + trailerDict.emitObject(wStream); + wStream->writeText("\nstartxref\n"); + wStream->writeBigDecAsText(xRefFileOffset); + wStream->writeText("\n%%EOF"); +} + +static SkPDFIndirectReference generate_page_tree( + SkPDFDocument* doc, + std::vector> pages, + const std::vector& pageRefs) { + // PDF wants a tree describing all the pages in the document. We arbitrary + // choose 8 (kNodeSize) as the number of allowed children. The internal + // nodes have type "Pages" with an array of children, a parent pointer, and + // the number of leaves below the node as "Count." The leaves are passed + // into the method, have type "Page" and need a parent pointer. This method + // builds the tree bottom up, skipping internal nodes that would have only + // one child. + SkASSERT(pages.size() > 0); + struct PageTreeNode { + std::unique_ptr fNode; + SkPDFIndirectReference fReservedRef; + int fPageObjectDescendantCount; + + static std::vector Layer(std::vector vec, SkPDFDocument* doc) { + std::vector result; + static constexpr size_t kMaxNodeSize = 8; + const size_t n = vec.size(); + SkASSERT(n >= 1); + const size_t result_len = (n - 1) / kMaxNodeSize + 1; + SkASSERT(result_len >= 1); + SkASSERT(n == 1 || result_len < n); + result.reserve(result_len); + size_t index = 0; + for (size_t i = 0; i < result_len; ++i) { + if (n != 1 && index + 1 == n) { // No need to create a new node. + result.push_back(std::move(vec[index++])); + continue; + } + SkPDFIndirectReference parent = doc->reserveRef(); + auto kids_list = SkPDFMakeArray(); + int descendantCount = 0; + for (size_t j = 0; j < kMaxNodeSize && index < n; ++j) { + PageTreeNode& node = vec[index++]; + node.fNode->insertRef("Parent", parent); + kids_list->appendRef(doc->emit(*node.fNode, node.fReservedRef)); + descendantCount += node.fPageObjectDescendantCount; + } + auto next = SkPDFMakeDict("Pages"); + next->insertInt("Count", descendantCount); + next->insertObject("Kids", std::move(kids_list)); + result.push_back(PageTreeNode{std::move(next), parent, descendantCount}); + } + return result; + } + }; + std::vector currentLayer; + currentLayer.reserve(pages.size()); + SkASSERT(pages.size() == pageRefs.size()); + for (size_t i = 0; i < pages.size(); ++i) { + currentLayer.push_back(PageTreeNode{std::move(pages[i]), pageRefs[i], 1}); + } + currentLayer = PageTreeNode::Layer(std::move(currentLayer), doc); + while (currentLayer.size() > 1) { + currentLayer = PageTreeNode::Layer(std::move(currentLayer), doc); + } + SkASSERT(currentLayer.size() == 1); + const PageTreeNode& root = currentLayer[0]; + return doc->emit(*root.fNode, root.fReservedRef); +} + +template +static void reset_object(T* dst, Args&&... args) { + dst->~T(); + new (dst) T(std::forward(args)...); +} + +//////////////////////////////////////////////////////////////////////////////// + +SkPDFDocument::SkPDFDocument(SkWStream* stream, + SkPDF::Metadata metadata) + : SkDocument(stream) + , fMetadata(std::move(metadata)) { + constexpr float kDpiForRasterScaleOne = 72.0f; + if (fMetadata.fRasterDPI != kDpiForRasterScaleOne) { + fInverseRasterScale = kDpiForRasterScaleOne / fMetadata.fRasterDPI; + fRasterScale = fMetadata.fRasterDPI / kDpiForRasterScaleOne; + } + if (fMetadata.fStructureElementTreeRoot) { + fTagTree.init(fMetadata.fStructureElementTreeRoot); + } + fExecutor = metadata.fExecutor; +} + +SkPDFDocument::~SkPDFDocument() { + // subclasses of SkDocument must call close() in their destructors. + this->close(); +} + +SkPDFIndirectReference SkPDFDocument::emit(const SkPDFObject& object, SkPDFIndirectReference ref){ + SkAutoMutexExclusive lock(fMutex); + object.emitObject(this->beginObject(ref)); + this->endObject(); + return ref; +} + +SkWStream* SkPDFDocument::beginObject(SkPDFIndirectReference ref) SK_REQUIRES(fMutex) { + begin_indirect_object(&fOffsetMap, ref, this->getStream()); + return this->getStream(); +}; + +void SkPDFDocument::endObject() SK_REQUIRES(fMutex) { + end_indirect_object(this->getStream()); +}; + +static SkSize operator*(SkISize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; } +static SkSize operator*(SkSize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; } + +SkCanvas* SkPDFDocument::onBeginPage(SkScalar width, SkScalar height) { + SkASSERT(fCanvas.imageInfo().dimensions().isZero()); + if (fPages.empty()) { + // if this is the first page if the document. + { + SkAutoMutexExclusive autoMutexAcquire(fMutex); + serializeHeader(&fOffsetMap, this->getStream()); + + } + + fInfoDict = this->emit(*SkPDFMetadata::MakeDocumentInformationDict(fMetadata)); + if (fMetadata.fPDFA) { + fUUID = SkPDFMetadata::CreateUUID(fMetadata); + // We use the same UUID for Document ID and Instance ID since this + // is the first revision of this document (and Skia does not + // support revising existing PDF documents). + // If we are not in PDF/A mode, don't use a UUID since testing + // works best with reproducible outputs. + fXMP = SkPDFMetadata::MakeXMPObject(fMetadata, fUUID, fUUID, this); + } + } + // By scaling the page at the device level, we will create bitmap layer + // devices at the rasterized scale, not the 72dpi scale. Bitmap layer + // devices are created when saveLayer is called with an ImageFilter; see + // SkPDFDevice::onCreateDevice(). + SkISize pageSize = (SkSize{width, height} * fRasterScale).toRound(); + SkMatrix initialTransform; + // Skia uses the top left as the origin but PDF natively has the origin at the + // bottom left. This matrix corrects for that, as well as the raster scale. + initialTransform.setScaleTranslate(fInverseRasterScale, -fInverseRasterScale, + 0, fInverseRasterScale * pageSize.height()); + fPageDevice = sk_make_sp(pageSize, this, initialTransform); + reset_object(&fCanvas, fPageDevice); + fCanvas.scale(fRasterScale, fRasterScale); + fPageRefs.push_back(this->reserveRef()); + return &fCanvas; +} + +static void populate_link_annotation(SkPDFDict* annotation, const SkRect& r) { + annotation->insertName("Subtype", "Link"); + annotation->insertInt("F", 4); // required by ISO 19005 + // Border: 0 = Horizontal corner radius. + // 0 = Vertical corner radius. + // 0 = Width, 0 = no border. + annotation->insertObject("Border", SkPDFMakeArray(0, 0, 0)); + annotation->insertObject("Rect", SkPDFMakeArray(r.fLeft, r.fTop, r.fRight, r.fBottom)); +} + +static SkString to_string(const SkData& d) { + return SkString(static_cast(d.data()), d.size() - 1); +} + +static std::unique_ptr get_annotations( + SkPDFDocument* doc, + const std::vector, SkRect>>& linkToURLs, + const std::vector, SkRect>>& linkToDestinations) +{ + std::unique_ptr array; + size_t count = linkToURLs.size() + linkToDestinations.size(); + if (0 == count) { + return array; // is nullptr + } + array = SkPDFMakeArray(); + array->reserve(count); + for (const auto& rectWithURL : linkToURLs) { + SkPDFDict annotation("Annot"); + populate_link_annotation(&annotation, rectWithURL.second); + std::unique_ptr action = SkPDFMakeDict("Action"); + action->insertName("S", "URI"); + action->insertString("URI", to_string(*rectWithURL.first)); + annotation.insertObject("A", std::move(action)); + array->appendRef(doc->emit(annotation)); + } + for (const auto& linkToDestination : linkToDestinations) { + SkPDFDict annotation("Annot"); + populate_link_annotation(&annotation, linkToDestination.second); + annotation.insertName("Dest", to_string(*linkToDestination.first)); + array->appendRef(doc->emit(annotation)); + } + return array; +} + +static SkPDFIndirectReference append_destinations( + SkPDFDocument* doc, + const std::vector& namedDestinations) +{ + SkPDFDict destinations; + for (const SkPDFNamedDestination& dest : namedDestinations) { + auto pdfDest = SkPDFMakeArray(); + pdfDest->reserve(5); + pdfDest->appendRef(dest.fPage); + pdfDest->appendName("XYZ"); + pdfDest->appendScalar(dest.fPoint.x()); + pdfDest->appendScalar(dest.fPoint.y()); + pdfDest->appendInt(0); // Leave zoom unchanged + destinations.insertObject(SkString((const char*)dest.fName->data()), std::move(pdfDest)); + } + return doc->emit(destinations); +} + +void SkPDFDocument::onEndPage() { + SkASSERT(!fCanvas.imageInfo().dimensions().isZero()); + reset_object(&fCanvas); + SkASSERT(fPageDevice); + + auto page = SkPDFMakeDict("Page"); + + SkSize mediaSize = fPageDevice->imageInfo().dimensions() * fInverseRasterScale; + std::unique_ptr pageContent = fPageDevice->content(); + auto resourceDict = fPageDevice->makeResourceDict(); + SkASSERT(fPageRefs.size() > 0); + fPageDevice = nullptr; + + page->insertObject("Resources", std::move(resourceDict)); + page->insertObject("MediaBox", SkPDFUtils::RectToArray(SkRect::MakeSize(mediaSize))); + + if (std::unique_ptr annotations = + get_annotations(this, fCurrentPageLinkToURLs, fCurrentPageLinkToDestinations)) { + page->insertObject("Annots", std::move(annotations)); + fCurrentPageLinkToURLs.clear(); + fCurrentPageLinkToDestinations.clear(); + } + + page->insertRef("Contents", SkPDFStreamOut(nullptr, std::move(pageContent), this)); + // The StructParents unique identifier for each page is just its + // 0-based page index. + page->insertInt("StructParents", SkToInt(this->currentPageIndex())); + fPages.emplace_back(std::move(page)); +} + +void SkPDFDocument::onAbort() { + this->waitForJobs(); +} + +static sk_sp SkSrgbIcm() { + // Source: http://www.argyllcms.com/icclibsrc.html + static const char kProfile[] = + "\0\0\14\214argl\2 \0\0mntrRGB XYZ \7\336\0\1\0\6\0\26\0\17\0:acspM" + "SFT\0\0\0\0IEC sRGB\0\0\0\0\0\0\0\0\0\0\0\0\0\0\366\326\0\1\0\0\0\0" + "\323-argl\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\21desc\0\0\1P\0\0\0\231cprt\0" + "\0\1\354\0\0\0gdmnd\0\0\2T\0\0\0pdmdd\0\0\2\304\0\0\0\210tech\0\0\3" + "L\0\0\0\14vued\0\0\3X\0\0\0gview\0\0\3\300\0\0\0$lumi\0\0\3\344\0\0" + "\0\24meas\0\0\3\370\0\0\0$wtpt\0\0\4\34\0\0\0\24bkpt\0\0\0040\0\0\0" + "\24rXYZ\0\0\4D\0\0\0\24gXYZ\0\0\4X\0\0\0\24bXYZ\0\0\4l\0\0\0\24rTR" + "C\0\0\4\200\0\0\10\14gTRC\0\0\4\200\0\0\10\14bTRC\0\0\4\200\0\0\10" + "\14desc\0\0\0\0\0\0\0?sRGB IEC61966-2.1 (Equivalent to www.srgb.co" + "m 1998 HP profile)\0\0\0\0\0\0\0\0\0\0\0?sRGB IEC61966-2.1 (Equiva" + "lent to www.srgb.com 1998 HP profile)\0\0\0\0\0\0\0\0text\0\0\0\0C" + "reated by Graeme W. Gill. Released into the public domain. No Warr" + "anty, Use at your own risk.\0\0desc\0\0\0\0\0\0\0\26IEC http://www" + ".iec.ch\0\0\0\0\0\0\0\0\0\0\0\26IEC http://www.iec.ch\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0desc\0\0\0\0\0\0\0.IEC 61966-2.1 Default RGB colour sp" + "ace - sRGB\0\0\0\0\0\0\0\0\0\0\0.IEC 61966-2.1 Default RGB colour " + "space - sRGB\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0sig \0\0\0" + "\0CRT desc\0\0\0\0\0\0\0\rIEC61966-2.1\0\0\0\0\0\0\0\0\0\0\0\rIEC6" + "1966-2.1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0view\0\0\0\0" + "\0\23\244|\0\24_0\0\20\316\2\0\3\355\262\0\4\23\n\0\3\\g\0\0\0\1XY" + "Z \0\0\0\0\0L\n=\0P\0\0\0W\36\270meas\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\2\217\0\0\0\2XYZ \0\0\0\0\0\0\363Q\0\1\0\0\0" + "\1\26\314XYZ \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0XYZ \0\0\0\0\0\0o\240" + "\0\0008\365\0\0\3\220XYZ \0\0\0\0\0\0b\227\0\0\267\207\0\0\30\331X" + "YZ \0\0\0\0\0\0$\237\0\0\17\204\0\0\266\304curv\0\0\0\0\0\0\4\0\0\0" + "\0\5\0\n\0\17\0\24\0\31\0\36\0#\0(\0-\0002\0007\0;\0@\0E\0J\0O\0T\0" + "Y\0^\0c\0h\0m\0r\0w\0|\0\201\0\206\0\213\0\220\0\225\0\232\0\237\0" + "\244\0\251\0\256\0\262\0\267\0\274\0\301\0\306\0\313\0\320\0\325\0" + "\333\0\340\0\345\0\353\0\360\0\366\0\373\1\1\1\7\1\r\1\23\1\31\1\37" + "\1%\1+\0012\0018\1>\1E\1L\1R\1Y\1`\1g\1n\1u\1|\1\203\1\213\1\222\1" + "\232\1\241\1\251\1\261\1\271\1\301\1\311\1\321\1\331\1\341\1\351\1" + "\362\1\372\2\3\2\14\2\24\2\35\2&\2/\0028\2A\2K\2T\2]\2g\2q\2z\2\204" + "\2\216\2\230\2\242\2\254\2\266\2\301\2\313\2\325\2\340\2\353\2\365" + "\3\0\3\13\3\26\3!\3-\0038\3C\3O\3Z\3f\3r\3~\3\212\3\226\3\242\3\256" + "\3\272\3\307\3\323\3\340\3\354\3\371\4\6\4\23\4 \4-\4;\4H\4U\4c\4q" + "\4~\4\214\4\232\4\250\4\266\4\304\4\323\4\341\4\360\4\376\5\r\5\34" + "\5+\5:\5I\5X\5g\5w\5\206\5\226\5\246\5\265\5\305\5\325\5\345\5\366" + "\6\6\6\26\6'\0067\6H\6Y\6j\6{\6\214\6\235\6\257\6\300\6\321\6\343\6" + "\365\7\7\7\31\7+\7=\7O\7a\7t\7\206\7\231\7\254\7\277\7\322\7\345\7" + "\370\10\13\10\37\0102\10F\10Z\10n\10\202\10\226\10\252\10\276\10\322" + "\10\347\10\373\t\20\t%\t:\tO\td\ty\t\217\t\244\t\272\t\317\t\345\t" + "\373\n\21\n'\n=\nT\nj\n\201\n\230\n\256\n\305\n\334\n\363\13\13\13" + "\"\0139\13Q\13i\13\200\13\230\13\260\13\310\13\341\13\371\14\22\14" + "*\14C\14\\\14u\14\216\14\247\14\300\14\331\14\363\r\r\r&\r@\rZ\rt\r" + "\216\r\251\r\303\r\336\r\370\16\23\16.\16I\16d\16\177\16\233\16\266" + "\16\322\16\356\17\t\17%\17A\17^\17z\17\226\17\263\17\317\17\354\20" + "\t\20&\20C\20a\20~\20\233\20\271\20\327\20\365\21\23\0211\21O\21m\21" + "\214\21\252\21\311\21\350\22\7\22&\22E\22d\22\204\22\243\22\303\22" + "\343\23\3\23#\23C\23c\23\203\23\244\23\305\23\345\24\6\24'\24I\24j" + "\24\213\24\255\24\316\24\360\25\22\0254\25V\25x\25\233\25\275\25\340" + "\26\3\26&\26I\26l\26\217\26\262\26\326\26\372\27\35\27A\27e\27\211" + "\27\256\27\322\27\367\30\33\30@\30e\30\212\30\257\30\325\30\372\31" + " \31E\31k\31\221\31\267\31\335\32\4\32*\32Q\32w\32\236\32\305\32\354" + "\33\24\33;\33c\33\212\33\262\33\332\34\2\34*\34R\34{\34\243\34\314" + "\34\365\35\36\35G\35p\35\231\35\303\35\354\36\26\36@\36j\36\224\36" + "\276\36\351\37\23\37>\37i\37\224\37\277\37\352 \25 A l \230 \304 \360" + "!\34!H!u!\241!\316!\373\"'\"U\"\202\"\257\"\335#\n#8#f#\224#\302#\360" + "$\37$M$|$\253$\332%\t%8%h%\227%\307%\367&'&W&\207&\267&\350'\30'I'" + "z'\253'\334(\r(?(q(\242(\324)\6)8)k)\235)\320*\2*5*h*\233*\317+\2+" + "6+i+\235+\321,\5,9,n,\242,\327-\14-A-v-\253-\341.\26.L.\202.\267.\356" + "/$/Z/\221/\307/\376050l0\2440\3331\0221J1\2021\2721\3622*2c2\2332\324" + "3\r3F3\1773\2703\3614+4e4\2364\3305\0235M5\2075\3025\375676r6\2566" + "\3517$7`7\2347\3278\0248P8\2148\3109\0059B9\1779\2749\371:6:t:\262" + ":\357;-;k;\252;\350<' >`>\240>\340?!?a" + "?\242?\342@#@d@\246@\347A)AjA\254A\356B0BrB\265B\367C:C}C\300D\3DG" + "D\212D\316E\22EUE\232E\336F\"FgF\253F\360G5G{G\300H\5HKH\221H\327I" + "\35IcI\251I\360J7J}J\304K\14KSK\232K\342L*LrL\272M\2MJM\223M\334N%" + "NnN\267O\0OIO\223O\335P'PqP\273Q\6QPQ\233Q\346R1R|R\307S\23S_S\252" + "S\366TBT\217T\333U(UuU\302V\17V\\V\251V\367WDW\222W\340X/X}X\313Y\32" + "YiY\270Z\7ZVZ\246Z\365[E[\225[\345\\5\\\206\\\326]']x]\311^\32^l^\275" + "_\17_a_\263`\5`W`\252`\374aOa\242a\365bIb\234b\360cCc\227c\353d@d\224" + "d\351e=e\222e\347f=f\222f\350g=g\223g\351h?h\226h\354iCi\232i\361j" + "Hj\237j\367kOk\247k\377lWl\257m\10m`m\271n\22nkn\304o\36oxo\321p+p" + "\206p\340q:q\225q\360rKr\246s\1s]s\270t\24tpt\314u(u\205u\341v>v\233" + "v\370wVw\263x\21xnx\314y*y\211y\347zFz\245{\4{c{\302|!|\201|\341}A" + "}\241~\1~b~\302\177#\177\204\177\345\200G\200\250\201\n\201k\201\315" + "\2020\202\222\202\364\203W\203\272\204\35\204\200\204\343\205G\205" + "\253\206\16\206r\206\327\207;\207\237\210\4\210i\210\316\2113\211\231" + "\211\376\212d\212\312\2130\213\226\213\374\214c\214\312\2151\215\230" + "\215\377\216f\216\316\2176\217\236\220\6\220n\220\326\221?\221\250" + "\222\21\222z\222\343\223M\223\266\224 \224\212\224\364\225_\225\311" + "\2264\226\237\227\n\227u\227\340\230L\230\270\231$\231\220\231\374" + "\232h\232\325\233B\233\257\234\34\234\211\234\367\235d\235\322\236" + "@\236\256\237\35\237\213\237\372\240i\240\330\241G\241\266\242&\242" + "\226\243\6\243v\243\346\244V\244\307\2458\245\251\246\32\246\213\246" + "\375\247n\247\340\250R\250\304\2517\251\251\252\34\252\217\253\2\253" + "u\253\351\254\\\254\320\255D\255\270\256-\256\241\257\26\257\213\260" + "\0\260u\260\352\261`\261\326\262K\262\302\2638\263\256\264%\264\234" + "\265\23\265\212\266\1\266y\266\360\267h\267\340\270Y\270\321\271J\271" + "\302\272;\272\265\273.\273\247\274!\274\233\275\25\275\217\276\n\276" + "\204\276\377\277z\277\365\300p\300\354\301g\301\343\302_\302\333\303" + "X\303\324\304Q\304\316\305K\305\310\306F\306\303\307A\307\277\310=" + "\310\274\311:\311\271\3128\312\267\3136\313\266\3145\314\265\3155\315" + "\265\3166\316\266\3177\317\270\3209\320\272\321<\321\276\322?\322\301" + "\323D\323\306\324I\324\313\325N\325\321\326U\326\330\327\\\327\340" + "\330d\330\350\331l\331\361\332v\332\373\333\200\334\5\334\212\335\20" + "\335\226\336\34\336\242\337)\337\257\3406\340\275\341D\341\314\342" + "S\342\333\343c\343\353\344s\344\374\345\204\346\r\346\226\347\37\347" + "\251\3502\350\274\351F\351\320\352[\352\345\353p\353\373\354\206\355" + "\21\355\234\356(\356\264\357@\357\314\360X\360\345\361r\361\377\362" + "\214\363\31\363\247\3644\364\302\365P\365\336\366m\366\373\367\212" + "\370\31\370\250\3718\371\307\372W\372\347\373w\374\7\374\230\375)\375" + "\272\376K\376\334\377m\377\377"; + const size_t kProfileLength = 3212; + static_assert(kProfileLength == sizeof(kProfile) - 1, ""); + return SkData::MakeWithoutCopy(kProfile, kProfileLength); +} + +static SkPDFIndirectReference make_srgb_color_profile(SkPDFDocument* doc) { + std::unique_ptr dict = SkPDFMakeDict(); + dict->insertInt("N", 3); + dict->insertObject("Range", SkPDFMakeArray(0, 1, 0, 1, 0, 1)); + return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(SkSrgbIcm()), doc, true); +} + +static std::unique_ptr make_srgb_output_intents(SkPDFDocument* doc) { + // sRGB is specified by HTML, CSS, and SVG. + auto outputIntent = SkPDFMakeDict("OutputIntent"); + outputIntent->insertName("S", "GTS_PDFA1"); + outputIntent->insertString("RegistryName", "http://www.color.org"); + outputIntent->insertString("OutputConditionIdentifier", + "Custom"); + outputIntent->insertString("Info","sRGB IEC61966-2.1"); + outputIntent->insertRef("DestOutputProfile", make_srgb_color_profile(doc)); + auto intentArray = SkPDFMakeArray(); + intentArray->appendObject(std::move(outputIntent)); + return intentArray; +} + +SkPDFIndirectReference SkPDFDocument::getPage(size_t pageIndex) const { + SkASSERT(pageIndex < fPageRefs.size()); + return fPageRefs[pageIndex]; +} + +const SkMatrix& SkPDFDocument::currentPageTransform() const { + return fPageDevice->initialTransform(); +} + +int SkPDFDocument::getMarkIdForNodeId(int nodeId) { + return fTagTree.getMarkIdForNodeId(nodeId, SkToUInt(this->currentPageIndex())); +} + +static std::vector get_fonts(const SkPDFDocument& canon) { + std::vector fonts; + fonts.reserve(canon.fFontMap.count()); + // Sort so the output PDF is reproducible. + canon.fFontMap.foreach([&fonts](uint64_t, const SkPDFFont& font) { fonts.push_back(&font); }); + std::sort(fonts.begin(), fonts.end(), [](const SkPDFFont* u, const SkPDFFont* v) { + return u->indirectReference().fValue < v->indirectReference().fValue; + }); + return fonts; +} + +void SkPDFDocument::onClose(SkWStream* stream) { + SkASSERT(fCanvas.imageInfo().dimensions().isZero()); + if (fPages.empty()) { + this->waitForJobs(); + return; + } + auto docCatalog = SkPDFMakeDict("Catalog"); + if (fMetadata.fPDFA) { + SkASSERT(fXMP != SkPDFIndirectReference()); + docCatalog->insertRef("Metadata", fXMP); + // Don't specify OutputIntents if we are not in PDF/A mode since + // no one has ever asked for this feature. + docCatalog->insertObject("OutputIntents", make_srgb_output_intents(this)); + } + + docCatalog->insertRef("Pages", generate_page_tree(this, std::move(fPages), fPageRefs)); + + if (!fNamedDestinations.empty()) { + docCatalog->insertRef("Dests", append_destinations(this, fNamedDestinations)); + fNamedDestinations.clear(); + } + + // Handle tagged PDFs. + if (SkPDFIndirectReference root = fTagTree.makeStructTreeRoot(this)) { + // In the document catalog, indicate that this PDF is tagged. + auto markInfo = SkPDFMakeDict("MarkInfo"); + markInfo->insertBool("Marked", true); + docCatalog->insertObject("MarkInfo", std::move(markInfo)); + docCatalog->insertRef("StructTreeRoot", root); + } + + auto docCatalogRef = this->emit(*docCatalog); + + for (const SkPDFFont* f : get_fonts(*this)) { + f->emitSubset(this); + } + + this->waitForJobs(); + { + SkAutoMutexExclusive autoMutexAcquire(fMutex); + serialize_footer(fOffsetMap, this->getStream(), fInfoDict, docCatalogRef, fUUID); + } +} + +void SkPDFDocument::incrementJobCount() { fJobCount++; } + +void SkPDFDocument::signalJobComplete() { fSemaphore.signal(); } + +void SkPDFDocument::waitForJobs() { + // fJobCount can increase while we wait. + while (fJobCount > 0) { + fSemaphore.wait(); + --fJobCount; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkPDF::SetNodeId(SkCanvas* canvas, int nodeID) { + sk_sp payload = SkData::MakeWithCopy(&nodeID, sizeof(nodeID)); + const char* key = SkPDFGetNodeIdKey(); + canvas->drawAnnotation({0, 0, 0, 0}, key, payload.get()); +} + +sk_sp SkPDF::MakeDocument(SkWStream* stream, const SkPDF::Metadata& metadata) { + SkPDF::Metadata meta = metadata; + if (meta.fRasterDPI <= 0) { + meta.fRasterDPI = 72.0f; + } + if (meta.fEncodingQuality < 0) { + meta.fEncodingQuality = 0; + } + return stream ? sk_make_sp(stream, std::move(meta)) : nullptr; +} + diff --git a/gfx/skia/skia/src/pdf/SkPDFDocumentPriv.h b/gfx/skia/skia/src/pdf/SkPDFDocumentPriv.h new file mode 100644 index 0000000000..ab2a62edb9 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFDocumentPriv.h @@ -0,0 +1,159 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFDocumentPriv_DEFINED +#define SkPDFDocumentPriv_DEFINED + +#include "include/core/SkCanvas.h" +#include "include/core/SkStream.h" +#include "include/docs/SkPDFDocument.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTHash.h" +#include "src/pdf/SkPDFMetadata.h" +#include "src/pdf/SkPDFTag.h" + +#include +#include +#include + +class SkExecutor; +class SkPDFDevice; +class SkPDFFont; +struct SkAdvancedTypefaceMetrics; +struct SkBitmapKey; +struct SkPDFFillGraphicState; +struct SkPDFImageShaderKey; +struct SkPDFStrokeGraphicState; + +namespace SkPDFGradientShader { +struct Key; +struct KeyHash; +} + +const char* SkPDFGetNodeIdKey(); + +// Logically part of SkPDFDocument, but separate to keep similar functionality together. +class SkPDFOffsetMap { +public: + void markStartOfDocument(const SkWStream*); + void markStartOfObject(int referenceNumber, const SkWStream*); + int objectCount() const; + int emitCrossReferenceTable(SkWStream* s) const; +private: + std::vector fOffsets; + size_t fBaseOffset = SIZE_MAX; +}; + + +struct SkPDFNamedDestination { + sk_sp fName; + SkPoint fPoint; + SkPDFIndirectReference fPage; +}; + +/** Concrete implementation of SkDocument that creates PDF files. This + class does not produced linearized or optimized PDFs; instead it + it attempts to use a minimum amount of RAM. */ +class SkPDFDocument : public SkDocument { +public: + SkPDFDocument(SkWStream*, SkPDF::Metadata); + ~SkPDFDocument() override; + SkCanvas* onBeginPage(SkScalar, SkScalar) override; + void onEndPage() override; + void onClose(SkWStream*) override; + void onAbort() override; + + /** + Serialize the object, as well as any other objects it + indirectly refers to. If any any other objects have been added + to the SkPDFObjNumMap without serializing them, they will be + serialized as well. + + It might go without saying that objects should not be changed + after calling serialize, since those changes will be too late. + */ + SkPDFIndirectReference emit(const SkPDFObject&, SkPDFIndirectReference); + SkPDFIndirectReference emit(const SkPDFObject& o) { return this->emit(o, this->reserveRef()); } + + template + void emitStream(const SkPDFDict& dict, T writeStream, SkPDFIndirectReference ref) { + SkAutoMutexExclusive lock(fMutex); + SkWStream* stream = this->beginObject(ref); + dict.emitObject(stream); + stream->writeText(" stream\n"); + writeStream(stream); + stream->writeText("\nendstream"); + this->endObject(); + } + + const SkPDF::Metadata& metadata() const { return fMetadata; } + + SkPDFIndirectReference getPage(size_t pageIndex) const; + SkPDFIndirectReference currentPage() const { + return SkASSERT(!fPageRefs.empty()), fPageRefs.back(); + } + // Returns -1 if no mark ID. + int getMarkIdForNodeId(int nodeId); + + SkPDFIndirectReference reserveRef() { return SkPDFIndirectReference{fNextObjectNumber++}; } + + SkExecutor* executor() const { return fExecutor; } + void incrementJobCount(); + void signalJobComplete(); + size_t currentPageIndex() { return fPages.size(); } + size_t pageCount() { return fPageRefs.size(); } + + const SkMatrix& currentPageTransform() const; + + // Canonicalized objects + SkTHashMap fImageShaderMap; + SkTHashMap + fGradientPatternMap; + SkTHashMap fPDFBitmapMap; + SkTHashMap> fTypefaceMetrics; + SkTHashMap> fType1GlyphNames; + SkTHashMap> fToUnicodeMap; + SkTHashMap fFontDescriptors; + SkTHashMap fType3FontDescriptors; + SkTHashMap fFontMap; + SkTHashMap fStrokeGSMap; + SkTHashMap fFillGSMap; + SkPDFIndirectReference fInvertFunction; + SkPDFIndirectReference fNoSmaskGraphicState; + + std::vector, SkRect>> fCurrentPageLinkToURLs; + std::vector, SkRect>> fCurrentPageLinkToDestinations; + std::vector fNamedDestinations; + +private: + SkPDFOffsetMap fOffsetMap; + SkCanvas fCanvas; + std::vector> fPages; + std::vector fPageRefs; + + sk_sp fPageDevice; + std::atomic fNextObjectNumber = {1}; + std::atomic fJobCount = {0}; + SkUUID fUUID; + SkPDFIndirectReference fInfoDict; + SkPDFIndirectReference fXMP; + SkPDF::Metadata fMetadata; + SkScalar fRasterScale = 1; + SkScalar fInverseRasterScale = 1; + SkExecutor* fExecutor = nullptr; + + // For tagged PDFs. + SkPDFTagTree fTagTree; + + SkMutex fMutex; + SkSemaphore fSemaphore; + + void waitForJobs(); + SkWStream* beginObject(SkPDFIndirectReference); + void endObject(); +}; + +#endif // SkPDFDocumentPriv_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFFont.cpp b/gfx/skia/skia/src/pdf/SkPDFFont.cpp new file mode 100644 index 0000000000..c0275236a6 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFFont.cpp @@ -0,0 +1,690 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkFont.h" +#include "include/core/SkFontMetrics.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkSurfaceProps.h" +#include "include/core/SkTypes.h" +#include "include/docs/SkPDFDocument.h" +#include "include/private/SkBitmaskEnum.h" +#include "include/private/SkTHash.h" +#include "include/private/SkTo.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkImagePriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMask.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeSpec.h" +#include "src/pdf/SkPDFBitmap.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFFont.h" +#include "src/pdf/SkPDFMakeCIDGlyphWidthsArray.h" +#include "src/pdf/SkPDFMakeToUnicodeCmap.h" +#include "src/pdf/SkPDFSubsetFont.h" +#include "src/pdf/SkPDFType1Font.h" +#include "src/pdf/SkPDFUtils.h" +#include "src/utils/SkUTF.h" + +#include +#include +#include +#include + +void SkPDFFont::GetType1GlyphNames(const SkTypeface& face, SkString* dst) { + face.getPostScriptGlyphNames(dst); +} + +namespace { +// PDF's notion of symbolic vs non-symbolic is related to the character set, not +// symbols vs. characters. Rarely is a font the right character set to call it +// non-symbolic, so always call it symbolic. (PDF 1.4 spec, section 5.7.1) +static const int32_t kPdfSymbolic = 4; + + +// scale from em-units to base-1000, returning as a SkScalar +inline SkScalar from_font_units(SkScalar scaled, uint16_t emSize) { + return emSize == 1000 ? scaled : scaled * 1000 / emSize; +} + +inline SkScalar scaleFromFontUnits(int16_t val, uint16_t emSize) { + return from_font_units(SkIntToScalar(val), emSize); +} + +void setGlyphWidthAndBoundingBox(SkScalar width, SkIRect box, + SkDynamicMemoryWStream* content) { + // Specify width and bounding box for the glyph. + SkPDFUtils::AppendScalar(width, content); + content->writeText(" 0 "); + content->writeDecAsText(box.fLeft); + content->writeText(" "); + content->writeDecAsText(box.fTop); + content->writeText(" "); + content->writeDecAsText(box.fRight); + content->writeText(" "); + content->writeDecAsText(box.fBottom); + content->writeText(" d1\n"); +} +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// class SkPDFFont +/////////////////////////////////////////////////////////////////////////////// + +/* Resources are canonicalized and uniqueified by pointer so there has to be + * some additional state indicating which subset of the font is used. It + * must be maintained at the document granularity. + */ + +SkPDFFont::~SkPDFFont() = default; + +SkPDFFont::SkPDFFont(SkPDFFont&&) = default; + +SkPDFFont& SkPDFFont::operator=(SkPDFFont&&) = default; + +static bool can_embed(const SkAdvancedTypefaceMetrics& metrics) { + return !SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag); +} + +const SkAdvancedTypefaceMetrics* SkPDFFont::GetMetrics(const SkTypeface* typeface, + SkPDFDocument* canon) { + SkASSERT(typeface); + SkFontID id = typeface->uniqueID(); + if (std::unique_ptr* ptr = canon->fTypefaceMetrics.find(id)) { + return ptr->get(); // canon retains ownership. + } + int count = typeface->countGlyphs(); + if (count <= 0 || count > 1 + SkTo(UINT16_MAX)) { + // Cache nullptr to skip this check. Use SkSafeUnref(). + canon->fTypefaceMetrics.set(id, nullptr); + return nullptr; + } + std::unique_ptr metrics = typeface->getAdvancedMetrics(); + if (!metrics) { + metrics = skstd::make_unique(); + } + + if (0 == metrics->fStemV || 0 == metrics->fCapHeight) { + SkFont font; + font.setHinting(SkFontHinting::kNone); + font.setTypeface(sk_ref_sp(typeface)); + font.setSize(1000); // glyph coordinate system + if (0 == metrics->fStemV) { + // Figure out a good guess for StemV - Min width of i, I, !, 1. + // This probably isn't very good with an italic font. + int16_t stemV = SHRT_MAX; + for (char c : {'i', 'I', '!', '1'}) { + uint16_t g = font.unicharToGlyph(c); + SkRect bounds; + font.getBounds(&g, 1, &bounds, nullptr); + stemV = SkTMin(stemV, SkToS16(SkScalarRoundToInt(bounds.width()))); + } + metrics->fStemV = stemV; + } + if (0 == metrics->fCapHeight) { + // Figure out a good guess for CapHeight: average the height of M and X. + SkScalar capHeight = 0; + for (char c : {'M', 'X'}) { + uint16_t g = font.unicharToGlyph(c); + SkRect bounds; + font.getBounds(&g, 1, &bounds, nullptr); + capHeight += bounds.height(); + } + metrics->fCapHeight = SkToS16(SkScalarRoundToInt(capHeight / 2)); + } + } + return canon->fTypefaceMetrics.set(id, std::move(metrics))->get(); +} + +const std::vector& SkPDFFont::GetUnicodeMap(const SkTypeface* typeface, + SkPDFDocument* canon) { + SkASSERT(typeface); + SkASSERT(canon); + SkFontID id = typeface->uniqueID(); + if (std::vector* ptr = canon->fToUnicodeMap.find(id)) { + return *ptr; + } + std::vector buffer(typeface->countGlyphs()); + typeface->getGlyphToUnicodeMap(buffer.data()); + return *canon->fToUnicodeMap.set(id, std::move(buffer)); +} + +SkAdvancedTypefaceMetrics::FontType SkPDFFont::FontType(const SkAdvancedTypefaceMetrics& metrics) { + if (SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kMultiMaster_FontFlag) || + SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag)) { + // force Type3 fallback. + return SkAdvancedTypefaceMetrics::kOther_Font; + } + return metrics.fType; +} + +static SkGlyphID first_nonzero_glyph_for_single_byte_encoding(SkGlyphID gid) { + return gid != 0 ? gid - (gid - 1) % 255 : 1; +} + +SkPDFFont* SkPDFFont::GetFontResource(SkPDFDocument* doc, + const SkGlyph* glyph, + SkTypeface* face) { + SkASSERT(doc); + SkASSERT(face); // All SkPDFDevice::internalDrawText ensures this. + const SkAdvancedTypefaceMetrics* fontMetrics = SkPDFFont::GetMetrics(face, doc); + SkASSERT(fontMetrics); // SkPDFDevice::internalDrawText ensures the typeface is good. + // GetMetrics only returns null to signify a bad typeface. + const SkAdvancedTypefaceMetrics& metrics = *fontMetrics; + SkAdvancedTypefaceMetrics::FontType type = SkPDFFont::FontType(metrics); + if (!(glyph->isEmpty() || glyph->path())) { + type = SkAdvancedTypefaceMetrics::kOther_Font; + } + bool multibyte = SkPDFFont::IsMultiByte(type); + SkGlyphID subsetCode = + multibyte ? 0 : first_nonzero_glyph_for_single_byte_encoding(glyph->getGlyphID()); + uint64_t fontID = (static_cast(SkTypeface::UniqueID(face)) << 16) | subsetCode; + + if (SkPDFFont* found = doc->fFontMap.find(fontID)) { + SkASSERT(multibyte == found->multiByteGlyphs()); + return found; + } + + sk_sp typeface(sk_ref_sp(face)); + SkASSERT(typeface); + + SkGlyphID lastGlyph = SkToU16(typeface->countGlyphs() - 1); + + // should be caught by SkPDFDevice::internalDrawText + SkASSERT(glyph->getGlyphID() <= lastGlyph); + + SkGlyphID firstNonZeroGlyph; + if (multibyte) { + firstNonZeroGlyph = 1; + } else { + firstNonZeroGlyph = subsetCode; + lastGlyph = SkToU16(SkTMin((int)lastGlyph, 254 + (int)subsetCode)); + } + auto ref = doc->reserveRef(); + return doc->fFontMap.set( + fontID, SkPDFFont(std::move(typeface), firstNonZeroGlyph, lastGlyph, type, ref)); +} + +SkPDFFont::SkPDFFont(sk_sp typeface, + SkGlyphID firstGlyphID, + SkGlyphID lastGlyphID, + SkAdvancedTypefaceMetrics::FontType fontType, + SkPDFIndirectReference indirectReference) + : fTypeface(std::move(typeface)) + , fGlyphUsage(firstGlyphID, lastGlyphID) + , fIndirectReference(indirectReference) + , fFontType(fontType) +{ + // Always include glyph 0 + this->noteGlyphUsage(0); +} + +void SkPDFFont::PopulateCommonFontDescriptor(SkPDFDict* descriptor, + const SkAdvancedTypefaceMetrics& metrics, + uint16_t emSize, + int16_t defaultWidth) { + descriptor->insertName("FontName", metrics.fPostScriptName); + descriptor->insertInt("Flags", (size_t)(metrics.fStyle | kPdfSymbolic)); + descriptor->insertScalar("Ascent", + scaleFromFontUnits(metrics.fAscent, emSize)); + descriptor->insertScalar("Descent", + scaleFromFontUnits(metrics.fDescent, emSize)); + descriptor->insertScalar("StemV", + scaleFromFontUnits(metrics.fStemV, emSize)); + descriptor->insertScalar("CapHeight", + scaleFromFontUnits(metrics.fCapHeight, emSize)); + descriptor->insertInt("ItalicAngle", metrics.fItalicAngle); + descriptor->insertObject("FontBBox", + SkPDFMakeArray(scaleFromFontUnits(metrics.fBBox.left(), emSize), + scaleFromFontUnits(metrics.fBBox.bottom(), emSize), + scaleFromFontUnits(metrics.fBBox.right(), emSize), + scaleFromFontUnits(metrics.fBBox.top(), emSize))); + if (defaultWidth > 0) { + descriptor->insertScalar("MissingWidth", + scaleFromFontUnits(defaultWidth, emSize)); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Type0Font +/////////////////////////////////////////////////////////////////////////////// + +// if possible, make no copy. +static sk_sp stream_to_data(std::unique_ptr stream) { + SkASSERT(stream); + (void)stream->rewind(); + SkASSERT(stream->hasLength()); + size_t size = stream->getLength(); + if (const void* base = stream->getMemoryBase()) { + SkData::ReleaseProc proc = + [](const void*, void* ctx) { delete (SkStreamAsset*)ctx; }; + return SkData::MakeWithProc(base, size, proc, stream.release()); + } + return SkData::MakeFromStream(stream.get(), size); +} + +static void emit_subset_type0(const SkPDFFont& font, SkPDFDocument* doc) { + const SkAdvancedTypefaceMetrics* metricsPtr = + SkPDFFont::GetMetrics(font.typeface(), doc); + SkASSERT(metricsPtr); + if (!metricsPtr) { return; } + const SkAdvancedTypefaceMetrics& metrics = *metricsPtr; + SkASSERT(can_embed(metrics)); + SkAdvancedTypefaceMetrics::FontType type = font.getType(); + SkTypeface* face = font.typeface(); + SkASSERT(face); + + auto descriptor = SkPDFMakeDict("FontDescriptor"); + uint16_t emSize = SkToU16(font.typeface()->getUnitsPerEm()); + SkPDFFont::PopulateCommonFontDescriptor(descriptor.get(), metrics, emSize, 0); + + int ttcIndex; + std::unique_ptr fontAsset = face->openStream(&ttcIndex); + size_t fontSize = fontAsset ? fontAsset->getLength() : 0; + if (0 == fontSize) { + SkDebugf("Error: (SkTypeface)(%p)::openStream() returned " + "empty stream (%p) when identified as kType1CID_Font " + "or kTrueType_Font.\n", face, fontAsset.get()); + } else { + switch (type) { + case SkAdvancedTypefaceMetrics::kTrueType_Font: { + if (!SkToBool(metrics.fFlags & + SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag)) { + SkASSERT(font.firstGlyphID() == 1); + sk_sp subsetFontData = SkPDFSubsetFont( + stream_to_data(std::move(fontAsset)), font.glyphUsage(), + doc->metadata().fSubsetter, + metrics.fFontName.c_str(), ttcIndex); + if (subsetFontData) { + std::unique_ptr tmp = SkPDFMakeDict(); + tmp->insertInt("Length1", SkToInt(subsetFontData->size())); + descriptor->insertRef( + "FontFile2", + SkPDFStreamOut(std::move(tmp), + SkMemoryStream::Make(std::move(subsetFontData)), + doc, true)); + break; + } + // If subsetting fails, fall back to original font data. + fontAsset = face->openStream(&ttcIndex); + SkASSERT(fontAsset); + SkASSERT(fontAsset->getLength() == fontSize); + if (!fontAsset || fontAsset->getLength() == 0) { break; } + } + std::unique_ptr tmp = SkPDFMakeDict(); + tmp->insertInt("Length1", fontSize); + descriptor->insertRef("FontFile2", + SkPDFStreamOut(std::move(tmp), std::move(fontAsset), + doc, true)); + break; + } + case SkAdvancedTypefaceMetrics::kType1CID_Font: { + std::unique_ptr tmp = SkPDFMakeDict(); + tmp->insertName("Subtype", "CIDFontType0C"); + descriptor->insertRef("FontFile3", + SkPDFStreamOut(std::move(tmp), std::move(fontAsset), + doc, true)); + break; + } + default: + SkASSERT(false); + } + } + + auto newCIDFont = SkPDFMakeDict("Font"); + newCIDFont->insertRef("FontDescriptor", doc->emit(*descriptor)); + newCIDFont->insertName("BaseFont", metrics.fPostScriptName); + + switch (type) { + case SkAdvancedTypefaceMetrics::kType1CID_Font: + newCIDFont->insertName("Subtype", "CIDFontType0"); + break; + case SkAdvancedTypefaceMetrics::kTrueType_Font: + newCIDFont->insertName("Subtype", "CIDFontType2"); + newCIDFont->insertName("CIDToGIDMap", "Identity"); + break; + default: + SkASSERT(false); + } + auto sysInfo = SkPDFMakeDict(); + sysInfo->insertString("Registry", "Adobe"); + sysInfo->insertString("Ordering", "Identity"); + sysInfo->insertInt("Supplement", 0); + newCIDFont->insertObject("CIDSystemInfo", std::move(sysInfo)); + + SkScalar defaultWidth = 0; + { + std::unique_ptr widths = SkPDFMakeCIDGlyphWidthsArray( + *face, font.glyphUsage(), &defaultWidth); + if (widths && widths->size() > 0) { + newCIDFont->insertObject("W", std::move(widths)); + } + newCIDFont->insertScalar("DW", defaultWidth); + } + + //////////////////////////////////////////////////////////////////////////// + + SkPDFDict fontDict("Font"); + fontDict.insertName("Subtype", "Type0"); + fontDict.insertName("BaseFont", metrics.fPostScriptName); + fontDict.insertName("Encoding", "Identity-H"); + auto descendantFonts = SkPDFMakeArray(); + descendantFonts->appendRef(doc->emit(*newCIDFont)); + fontDict.insertObject("DescendantFonts", std::move(descendantFonts)); + + const std::vector& glyphToUnicode = + SkPDFFont::GetUnicodeMap(font.typeface(), doc); + SkASSERT(SkToSizeT(font.typeface()->countGlyphs()) == glyphToUnicode.size()); + std::unique_ptr toUnicode = + SkPDFMakeToUnicodeCmap(glyphToUnicode.data(), + &font.glyphUsage(), + font.multiByteGlyphs(), + font.firstGlyphID(), + font.lastGlyphID()); + fontDict.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicode), doc)); + + doc->emit(fontDict, font.indirectReference()); +} + +/////////////////////////////////////////////////////////////////////////////// +// PDFType3Font +/////////////////////////////////////////////////////////////////////////////// + +namespace { +// returns [0, first, first+1, ... last-1, last] +struct SingleByteGlyphIdIterator { + SingleByteGlyphIdIterator(SkGlyphID first, SkGlyphID last) + : fFirst(first), fLast(last) { + SkASSERT(fFirst > 0); + SkASSERT(fLast >= first); + } + struct Iter { + void operator++() { + fCurrent = (0 == fCurrent) ? fFirst : fCurrent + 1; + } + // This is an input_iterator + SkGlyphID operator*() const { return (SkGlyphID)fCurrent; } + bool operator!=(const Iter& rhs) const { + return fCurrent != rhs.fCurrent; + } + Iter(SkGlyphID f, int c) : fFirst(f), fCurrent(c) {} + private: + const SkGlyphID fFirst; + int fCurrent; // must be int to make fLast+1 to fit + }; + Iter begin() const { return Iter(fFirst, 0); } + Iter end() const { return Iter(fFirst, (int)fLast + 1); } +private: + const SkGlyphID fFirst; + const SkGlyphID fLast; +}; +} + +struct ImageAndOffset { + sk_sp fImage; + SkIPoint fOffset; +}; +static ImageAndOffset to_image(SkGlyphID gid, SkStrike* cache) { + (void)cache->prepareImage(cache->glyph(gid)); + SkMask mask = cache->glyph(gid)->mask(); + if (!mask.fImage) { + return {nullptr, {0, 0}}; + } + SkIRect bounds = mask.fBounds; + SkBitmap bm; + switch (mask.fFormat) { + case SkMask::kBW_Format: + bm.allocPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height())); + for (int y = 0; y < bm.height(); ++y) { + for (int x8 = 0; x8 < bm.width(); x8 += 8) { + uint8_t v = *mask.getAddr1(x8 + bounds.x(), y + bounds.y()); + int e = SkTMin(x8 + 8, bm.width()); + for (int x = x8; x < e; ++x) { + *bm.getAddr8(x, y) = (v >> (x & 0x7)) & 0x1 ? 0xFF : 0x00; + } + } + } + bm.setImmutable(); + return {SkImage::MakeFromBitmap(bm), {bounds.x(), bounds.y()}}; + case SkMask::kA8_Format: + bm.installPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()), + mask.fImage, mask.fRowBytes); + return {SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode), + {bounds.x(), bounds.y()}}; + case SkMask::kARGB32_Format: + bm.installPixels(SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()), + mask.fImage, mask.fRowBytes); + return {SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode), + {bounds.x(), bounds.y()}}; + case SkMask::k3D_Format: + case SkMask::kLCD16_Format: + default: + SkASSERT(false); + return {nullptr, {0, 0}}; + } +} + +static SkPDFIndirectReference type3_descriptor(SkPDFDocument* doc, + const SkTypeface* typeface, + SkStrike* cache) { + if (SkPDFIndirectReference* ptr = doc->fType3FontDescriptors.find(typeface->uniqueID())) { + return *ptr; + } + + SkPDFDict descriptor("FontDescriptor"); + int32_t fontDescriptorFlags = kPdfSymbolic; + if (const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, doc)) { + // Type3 FontDescriptor does not require all the same fields. + descriptor.insertName("FontName", metrics->fPostScriptName); + descriptor.insertInt("ItalicAngle", metrics->fItalicAngle); + fontDescriptorFlags |= (int32_t)metrics->fStyle; + // Adobe requests CapHeight, XHeight, and StemV be added + // to "greatly help our workflow downstream". + if (metrics->fCapHeight != 0) { descriptor.insertInt("CapHeight", metrics->fCapHeight); } + if (metrics->fStemV != 0) { descriptor.insertInt("StemV", metrics->fStemV); } + SkScalar xHeight = cache->getFontMetrics().fXHeight; + if (xHeight != 0) { + descriptor.insertScalar("XHeight", xHeight); + } + } + descriptor.insertInt("Flags", fontDescriptorFlags); + SkPDFIndirectReference ref = doc->emit(descriptor); + doc->fType3FontDescriptors.set(typeface->uniqueID(), ref); + return ref; +} + +#ifdef SK_PDF_BITMAP_GLYPH_RASTER_SIZE +static constexpr float kBitmapFontSize = SK_PDF_BITMAP_GLYPH_RASTER_SIZE; +#else +static constexpr float kBitmapFontSize = 64; +#endif + +SkStrikeSpec make_small_strike(const SkTypeface& typeface) { + SkFont font(sk_ref_sp(&typeface), kBitmapFontSize); + font.setHinting(SkFontHinting::kNone); + font.setEdging(SkFont::Edging::kAlias); + return SkStrikeSpec::MakeMask(font, + SkPaint(), + SkSurfaceProps(0, kUnknown_SkPixelGeometry), + kFakeGammaAndBoostContrast, + SkMatrix::I()); +} + +static void emit_subset_type3(const SkPDFFont& pdfFont, SkPDFDocument* doc) { + SkTypeface* typeface = pdfFont.typeface(); + SkGlyphID firstGlyphID = pdfFont.firstGlyphID(); + SkGlyphID lastGlyphID = pdfFont.lastGlyphID(); + const SkPDFGlyphUse& subset = pdfFont.glyphUsage(); + SkASSERT(lastGlyphID >= firstGlyphID); + // Remove unused glyphs at the end of the range. + // Keep the lastGlyphID >= firstGlyphID invariant true. + while (lastGlyphID > firstGlyphID && !subset.has(lastGlyphID)) { + --lastGlyphID; + } + int unitsPerEm; + SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(*typeface, &unitsPerEm); + auto cache = strikeSpec.findOrCreateExclusiveStrike(); + SkASSERT(cache); + SkScalar emSize = (SkScalar)unitsPerEm; + + SkStrikeSpec strikeSpecSmall = kBitmapFontSize > 0 ? make_small_strike(*typeface) + : strikeSpec; + auto smallCache = strikeSpecSmall.findOrCreateExclusiveStrike(); + SkASSERT(smallCache); + float bitmapScale = kBitmapFontSize > 0 ? emSize / kBitmapFontSize : 1.0f; + + SkPDFDict font("Font"); + font.insertName("Subtype", "Type3"); + // Flip about the x-axis and scale by 1/emSize. + SkMatrix fontMatrix; + fontMatrix.setScale(SkScalarInvert(emSize), -SkScalarInvert(emSize)); + font.insertObject("FontMatrix", SkPDFUtils::MatrixToArray(fontMatrix)); + + auto charProcs = SkPDFMakeDict(); + auto encoding = SkPDFMakeDict("Encoding"); + + auto encDiffs = SkPDFMakeArray(); + // length(firstGlyphID .. lastGlyphID) == lastGlyphID - firstGlyphID + 1 + // plus 1 for glyph 0; + SkASSERT(firstGlyphID > 0); + SkASSERT(lastGlyphID >= firstGlyphID); + int glyphCount = lastGlyphID - firstGlyphID + 2; + // one other entry for the index of first glyph. + encDiffs->reserve(glyphCount + 1); + encDiffs->appendInt(0); // index of first glyph + + auto widthArray = SkPDFMakeArray(); + widthArray->reserve(glyphCount); + + SkIRect bbox = SkIRect::MakeEmpty(); + + std::vector> imageGlyphs; + for (SkGlyphID gID : SingleByteGlyphIdIterator(firstGlyphID, lastGlyphID)) { + bool skipGlyph = gID != 0 && !subset.has(gID); + SkString characterName; + SkScalar advance = 0.0f; + SkIRect glyphBBox; + if (skipGlyph) { + characterName.set("g0"); + } else { + characterName.printf("g%X", gID); + SkGlyph* glyph = cache->glyph(gID); + advance = glyph->advanceX(); + glyphBBox = glyph->iRect(); + bbox.join(glyphBBox); + const SkPath* path = cache->preparePath(glyph); + SkDynamicMemoryWStream content; + if (path && !path->isEmpty()) { + setGlyphWidthAndBoundingBox(glyph->advanceX(), glyphBBox, &content); + SkPDFUtils::EmitPath(*path, SkPaint::kFill_Style, &content); + SkPDFUtils::PaintPath(SkPaint::kFill_Style, path->getFillType(), &content); + } else { + auto pimg = to_image(gID, smallCache.get()); + if (!pimg.fImage) { + setGlyphWidthAndBoundingBox(glyph->advanceX(), glyphBBox, &content); + } else { + using SkPDFUtils::AppendScalar; + imageGlyphs.emplace_back(gID, SkPDFSerializeImage(pimg.fImage.get(), doc)); + AppendScalar(glyph->advanceX(), &content); + content.writeText(" 0 d0\n"); + AppendScalar(pimg.fImage->width() * bitmapScale, &content); + content.writeText(" 0 0 "); + AppendScalar(-pimg.fImage->height() * bitmapScale, &content); + content.writeText(" "); + AppendScalar(pimg.fOffset.x() * bitmapScale, &content); + content.writeText(" "); + AppendScalar((pimg.fImage->height() + pimg.fOffset.y()) * bitmapScale, + &content); + content.writeText(" cm\n/X"); + content.write(characterName.c_str(), characterName.size()); + content.writeText(" Do\n"); + } + } + charProcs->insertRef(characterName, SkPDFStreamOut(nullptr, + content.detachAsStream(), doc)); + } + encDiffs->appendName(std::move(characterName)); + widthArray->appendScalar(advance); + } + + if (!imageGlyphs.empty()) { + auto d0 = SkPDFMakeDict(); + for (const auto& pair : imageGlyphs) { + d0->insertRef(SkStringPrintf("Xg%X", pair.first), pair.second); + } + auto d1 = SkPDFMakeDict(); + d1->insertObject("XObject", std::move(d0)); + font.insertObject("Resources", std::move(d1)); + } + + encoding->insertObject("Differences", std::move(encDiffs)); + font.insertInt("FirstChar", 0); + font.insertInt("LastChar", lastGlyphID - firstGlyphID + 1); + /* FontBBox: "A rectangle expressed in the glyph coordinate + system, specifying the font bounding box. This is the smallest + rectangle enclosing the shape that would result if all of the + glyphs of the font were placed with their origins coincident and + then filled." */ + font.insertObject("FontBBox", SkPDFMakeArray(bbox.left(), + bbox.bottom(), + bbox.right(), + bbox.top())); + + font.insertName("CIDToGIDMap", "Identity"); + + const std::vector& glyphToUnicode = SkPDFFont::GetUnicodeMap(typeface, doc); + SkASSERT(glyphToUnicode.size() == SkToSizeT(typeface->countGlyphs())); + auto toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(), + &subset, + false, + firstGlyphID, + lastGlyphID); + font.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicodeCmap), doc)); + font.insertRef("FontDescriptor", type3_descriptor(doc, typeface, cache.get())); + font.insertObject("Widths", std::move(widthArray)); + font.insertObject("Encoding", std::move(encoding)); + font.insertObject("CharProcs", std::move(charProcs)); + + doc->emit(font, pdfFont.indirectReference()); +} + +void SkPDFFont::emitSubset(SkPDFDocument* doc) const { + SkASSERT(fFontType != SkPDFFont().fFontType); // not default value + switch (fFontType) { + case SkAdvancedTypefaceMetrics::kType1CID_Font: + case SkAdvancedTypefaceMetrics::kTrueType_Font: + return emit_subset_type0(*this, doc); +#ifndef SK_PDF_DO_NOT_SUPPORT_TYPE_1_FONTS + case SkAdvancedTypefaceMetrics::kType1_Font: + return SkPDFEmitType1Font(*this, doc); +#endif + default: + return emit_subset_type3(*this, doc); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SkPDFFont::CanEmbedTypeface(SkTypeface* typeface, SkPDFDocument* doc) { + const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, doc); + return metrics && can_embed(*metrics); +} + diff --git a/gfx/skia/skia/src/pdf/SkPDFFont.h b/gfx/skia/skia/src/pdf/SkPDFFont.h new file mode 100644 index 0000000000..f047aa17d2 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFFont.h @@ -0,0 +1,141 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFFont_DEFINED +#define SkPDFFont_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkStrikeCache.h" +#include "src/pdf/SkPDFGlyphUse.h" +#include "src/pdf/SkPDFTypes.h" + +#include + +class SkPDFDocument; +class SkStrike; +class SkString; + +/** \class SkPDFFont + A PDF Object class representing a font. The font may have resources + attached to it in order to embed the font. SkPDFFonts are canonicalized + so that resource deduplication will only include one copy of a font. + This class uses the same pattern as SkPDFGraphicState, a static weak + reference to each instantiated class. +*/ +class SkPDFFont { +public: + SkPDFFont() {} + ~SkPDFFont(); + SkPDFFont(SkPDFFont&&); + SkPDFFont& operator=(SkPDFFont&&); + + /** Returns the typeface represented by this class. Returns nullptr for the + * default typeface. + */ + SkTypeface* typeface() const { return fTypeface.get(); } + + /** Returns the font type represented in this font. For Type0 fonts, + * returns the type of the descendant font. + */ + SkAdvancedTypefaceMetrics::FontType getType() const { return fFontType; } + + static SkAdvancedTypefaceMetrics::FontType FontType(const SkAdvancedTypefaceMetrics&); + static void GetType1GlyphNames(const SkTypeface&, SkString*); + + static bool IsMultiByte(SkAdvancedTypefaceMetrics::FontType type) { + return type == SkAdvancedTypefaceMetrics::kType1CID_Font || + type == SkAdvancedTypefaceMetrics::kTrueType_Font; + } + + /** Returns true if this font encoding supports glyph IDs above 255. + */ + bool multiByteGlyphs() const { return SkPDFFont::IsMultiByte(this->getType()); } + + /** Return true if this font has an encoding for the passed glyph id. + */ + bool hasGlyph(SkGlyphID gid) { + return (gid >= this->firstGlyphID() && gid <= this->lastGlyphID()) || gid == 0; + } + + /** Convert the input glyph ID into the font encoding. */ + SkGlyphID glyphToPDFFontEncoding(SkGlyphID gid) const { + if (this->multiByteGlyphs() || gid == 0) { + return gid; + } + SkASSERT(gid >= this->firstGlyphID() && gid <= this->lastGlyphID()); + SkASSERT(this->firstGlyphID() > 0); + return gid - this->firstGlyphID() + 1; + } + + void noteGlyphUsage(SkGlyphID glyph) { + SkASSERT(this->hasGlyph(glyph)); + fGlyphUsage.set(glyph); + } + + SkPDFIndirectReference indirectReference() const { return fIndirectReference; } + + /** Get the font resource for the passed typeface and glyphID. The + * reference count of the object is incremented and it is the caller's + * responsibility to unreference it when done. This is needed to + * accommodate the weak reference pattern used when the returned object + * is new and has no other references. + * @param typeface The typeface to find, not nullptr. + * @param glyphID Specify which section of a large font is of interest. + */ + static SkPDFFont* GetFontResource(SkPDFDocument* doc, + const SkGlyph* glyphs, + SkTypeface* typeface); + + /** Gets SkAdvancedTypefaceMetrics, and caches the result. + * @param typeface can not be nullptr. + * @return nullptr only when typeface is bad. + */ + static const SkAdvancedTypefaceMetrics* GetMetrics(const SkTypeface* typeface, + SkPDFDocument* canon); + + static const std::vector& GetUnicodeMap(const SkTypeface* typeface, + SkPDFDocument* canon); + + static void PopulateCommonFontDescriptor(SkPDFDict* descriptor, + const SkAdvancedTypefaceMetrics&, + uint16_t emSize, + int16_t defaultWidth); + + void emitSubset(SkPDFDocument*) const; + + /** + * Return false iff the typeface has its NotEmbeddable flag set. + * typeface is not nullptr + */ + static bool CanEmbedTypeface(SkTypeface*, SkPDFDocument*); + + SkGlyphID firstGlyphID() const { return fGlyphUsage.firstNonZero(); } + SkGlyphID lastGlyphID() const { return fGlyphUsage.lastGlyph(); } + const SkPDFGlyphUse& glyphUsage() const { return fGlyphUsage; } + sk_sp refTypeface() const { return fTypeface; } + +private: + sk_sp fTypeface; + SkPDFGlyphUse fGlyphUsage; + SkPDFIndirectReference fIndirectReference; + SkAdvancedTypefaceMetrics::FontType fFontType = (SkAdvancedTypefaceMetrics::FontType)(-1); + + SkPDFFont(sk_sp, + SkGlyphID firstGlyphID, + SkGlyphID lastGlyphID, + SkAdvancedTypefaceMetrics::FontType fontType, + SkPDFIndirectReference indirectReference); + // The glyph IDs accessible with this font. For Type1 (non CID) fonts, + // this will be a subset if the font has more than 255 glyphs. + + SkPDFFont(const SkPDFFont&) = delete; + SkPDFFont& operator=(const SkPDFFont&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFFormXObject.cpp b/gfx/skia/skia/src/pdf/SkPDFFormXObject.cpp new file mode 100644 index 0000000000..cc07e2a0fd --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFFormXObject.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "src/pdf/SkPDFFormXObject.h" +#include "src/pdf/SkPDFUtils.h" + +SkPDFIndirectReference SkPDFMakeFormXObject(SkPDFDocument* doc, + std::unique_ptr content, + std::unique_ptr mediaBox, + std::unique_ptr resourceDict, + const SkMatrix& inverseTransform, + const char* colorSpace) { + std::unique_ptr dict = SkPDFMakeDict(); + dict->insertName("Type", "XObject"); + dict->insertName("Subtype", "Form"); + if (!inverseTransform.isIdentity()) { + dict->insertObject("Matrix", SkPDFUtils::MatrixToArray(inverseTransform)); + } + dict->insertObject("Resources", std::move(resourceDict)); + dict->insertObject("BBox", std::move(mediaBox)); + + // Right now FormXObject is only used for saveLayer, which implies + // isolated blending. Do this conditionally if that changes. + // TODO(halcanary): Is this comment obsolete, since we use it for + // alpha masks? + auto group = SkPDFMakeDict("Group"); + group->insertName("S", "Transparency"); + if (colorSpace != nullptr) { + group->insertName("CS", colorSpace); + } + group->insertBool("I", true); // Isolated. + dict->insertObject("Group", std::move(group)); + return SkPDFStreamOut(std::move(dict), std::move(content), doc); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFFormXObject.h b/gfx/skia/skia/src/pdf/SkPDFFormXObject.h new file mode 100644 index 0000000000..b12c8b2ea7 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFFormXObject.h @@ -0,0 +1,28 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkPDFFormXObject_DEFINED +#define SkPDFFormXObject_DEFINED + +#include "src/pdf/SkPDFDevice.h" +#include "src/pdf/SkPDFTypes.h" + +class SkPDFDocument; + +/** A form XObject is a self contained description of a graphics + object. A form XObject is a page object with slightly different + syntax, that can be drawn into a page content stream, just like a + bitmap XObject can be drawn into a page content stream. +*/ +SkPDFIndirectReference SkPDFMakeFormXObject(SkPDFDocument* doc, + std::unique_ptr content, + std::unique_ptr mediaBox, + std::unique_ptr resourceDict, + const SkMatrix& inverseTransform, + const char* colorSpace); +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFGlyphUse.h b/gfx/skia/skia/src/pdf/SkPDFGlyphUse.h new file mode 100644 index 0000000000..e4afa72dba --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGlyphUse.h @@ -0,0 +1,49 @@ +// Copyright 2018 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkPDFGlyphUse_DEFINED +#define SkPDFGlyphUse_DEFINED + +#include "include/core/SkTypes.h" +#include "src/utils/SkBitSet.h" + +class SkPDFGlyphUse { +public: + SkPDFGlyphUse() : fBitSet(0) {} + SkPDFGlyphUse(SkGlyphID firstNonZero, SkGlyphID lastGlyph) + : fBitSet((int)lastGlyph - firstNonZero + 2) + , fFirstNonZero(firstNonZero) + , fLastGlyph(lastGlyph) { SkASSERT(firstNonZero >= 1); } + ~SkPDFGlyphUse() = default; + SkPDFGlyphUse(SkPDFGlyphUse&&) = default; + SkPDFGlyphUse& operator=(SkPDFGlyphUse&&) = default; + + SkGlyphID firstNonZero() const { return fFirstNonZero; } + SkGlyphID lastGlyph() const { return fLastGlyph; } + void set(SkGlyphID gid) { fBitSet.set(this->toCode(gid)); } + bool has(SkGlyphID gid) const { return fBitSet.has(this->toCode(gid)); } + + template + void getSetValues(FN f) const { + if (fFirstNonZero == 1) { + return fBitSet.getSetValues(std::move(f)); + } + uint16_t offset = fFirstNonZero - 1; + fBitSet.getSetValues([&f, offset](unsigned v) { f(v == 0 ? v : v + offset); }); + } + +private: + SkBitSet fBitSet; + SkGlyphID fFirstNonZero = 0; + SkGlyphID fLastGlyph = 0; + + uint16_t toCode(SkGlyphID gid) const { + if (gid == 0 || fFirstNonZero == 1) { + return gid; + } + SkASSERT(gid >= fFirstNonZero && gid <= fLastGlyph); + return gid - fFirstNonZero + 1; + } + SkPDFGlyphUse(const SkPDFGlyphUse&) = delete; + SkPDFGlyphUse& operator=(const SkPDFGlyphUse&) = delete; +}; +#endif // SkPDFGlyphUse_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFGradientShader.cpp b/gfx/skia/skia/src/pdf/SkPDFGradientShader.cpp new file mode 100644 index 0000000000..919c07a543 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGradientShader.cpp @@ -0,0 +1,921 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFGradientShader.h" + +#include "include/docs/SkPDFDocument.h" +#include "src/core/SkOpts.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFFormXObject.h" +#include "src/pdf/SkPDFGraphicState.h" +#include "src/pdf/SkPDFResourceDict.h" +#include "src/pdf/SkPDFTypes.h" +#include "src/pdf/SkPDFUtils.h" + +static uint32_t hash(const SkShader::GradientInfo& v) { + uint32_t buffer[] = { + (uint32_t)v.fColorCount, + SkOpts::hash(v.fColors, v.fColorCount * sizeof(SkColor)), + SkOpts::hash(v.fColorOffsets, v.fColorCount * sizeof(SkScalar)), + SkOpts::hash(v.fPoint, 2 * sizeof(SkPoint)), + SkOpts::hash(v.fRadius, 2 * sizeof(SkScalar)), + (uint32_t)v.fTileMode, + v.fGradientFlags, + }; + return SkOpts::hash(buffer, sizeof(buffer)); +} + +static uint32_t hash(const SkPDFGradientShader::Key& k) { + uint32_t buffer[] = { + (uint32_t)k.fType, + hash(k.fInfo), + SkOpts::hash(&k.fCanvasTransform, sizeof(SkMatrix)), + SkOpts::hash(&k.fShaderTransform, sizeof(SkMatrix)), + SkOpts::hash(&k.fBBox, sizeof(SkIRect)) + }; + return SkOpts::hash(buffer, sizeof(buffer)); +} + +static void unit_to_points_matrix(const SkPoint pts[2], SkMatrix* matrix) { + SkVector vec = pts[1] - pts[0]; + SkScalar mag = vec.length(); + SkScalar inv = mag ? SkScalarInvert(mag) : 0; + + vec.scale(inv); + matrix->setSinCos(vec.fY, vec.fX); + matrix->preScale(mag, mag); + matrix->postTranslate(pts[0].fX, pts[0].fY); +} + +static const int kColorComponents = 3; +typedef uint8_t ColorTuple[kColorComponents]; + +/* Assumes t + startOffset is on the stack and does a linear interpolation on t + between startOffset and endOffset from prevColor to curColor (for each color + component), leaving the result in component order on the stack. It assumes + there are always 3 components per color. + @param range endOffset - startOffset + @param curColor[components] The current color components. + @param prevColor[components] The previous color components. + @param result The result ps function. + */ +static void interpolate_color_code(SkScalar range, const ColorTuple& curColor, + const ColorTuple& prevColor, + SkDynamicMemoryWStream* result) { + SkASSERT(range != SkIntToScalar(0)); + + // Figure out how to scale each color component. + SkScalar multiplier[kColorComponents]; + for (int i = 0; i < kColorComponents; i++) { + static const SkScalar kColorScale = SkScalarInvert(255); + multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range; + } + + // Calculate when we no longer need to keep a copy of the input parameter t. + // If the last component to use t is i, then dupInput[0..i - 1] = true + // and dupInput[i .. components] = false. + bool dupInput[kColorComponents]; + dupInput[kColorComponents - 1] = false; + for (int i = kColorComponents - 2; i >= 0; i--) { + dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0; + } + + if (!dupInput[0] && multiplier[0] == 0) { + result->writeText("pop "); + } + + for (int i = 0; i < kColorComponents; i++) { + // If the next components needs t and this component will consume a + // copy, make another copy. + if (dupInput[i] && multiplier[i] != 0) { + result->writeText("dup "); + } + + if (multiplier[i] == 0) { + SkPDFUtils::AppendColorComponent(prevColor[i], result); + result->writeText(" "); + } else { + if (multiplier[i] != 1) { + SkPDFUtils::AppendScalar(multiplier[i], result); + result->writeText(" mul "); + } + if (prevColor[i] != 0) { + SkPDFUtils::AppendColorComponent(prevColor[i], result); + result->writeText(" add "); + } + } + + if (dupInput[i]) { + result->writeText("exch\n"); + } + } +} + +/* Generate Type 4 function code to map t=[0,1) to the passed gradient, + clamping at the edges of the range. The generated code will be of the form: + if (t < 0) { + return colorData[0][r,g,b]; + } else { + if (t < info.fColorOffsets[1]) { + return linearinterpolation(colorData[0][r,g,b], + colorData[1][r,g,b]); + } else { + if (t < info.fColorOffsets[2]) { + return linearinterpolation(colorData[1][r,g,b], + colorData[2][r,g,b]); + } else { + + ... } else { + return colorData[info.fColorCount - 1][r,g,b]; + } + ... + } + } + */ +static void gradient_function_code(const SkShader::GradientInfo& info, + SkDynamicMemoryWStream* result) { + /* We want to linearly interpolate from the previous color to the next. + Scale the colors from 0..255 to 0..1 and determine the multipliers + for interpolation. + C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. + */ + + SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount); + ColorTuple *colorData = colorDataAlloc.get(); + for (int i = 0; i < info.fColorCount; i++) { + colorData[i][0] = SkColorGetR(info.fColors[i]); + colorData[i][1] = SkColorGetG(info.fColors[i]); + colorData[i][2] = SkColorGetB(info.fColors[i]); + } + + // Clamp the initial color. + result->writeText("dup 0 le {pop "); + SkPDFUtils::AppendColorComponent(colorData[0][0], result); + result->writeText(" "); + SkPDFUtils::AppendColorComponent(colorData[0][1], result); + result->writeText(" "); + SkPDFUtils::AppendColorComponent(colorData[0][2], result); + result->writeText(" }\n"); + + // The gradient colors. + int gradients = 0; + for (int i = 1 ; i < info.fColorCount; i++) { + if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) { + continue; + } + gradients++; + + result->writeText("{dup "); + SkPDFUtils::AppendScalar(info.fColorOffsets[i], result); + result->writeText(" le {"); + if (info.fColorOffsets[i - 1] != 0) { + SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result); + result->writeText(" sub\n"); + } + + interpolate_color_code(info.fColorOffsets[i] - info.fColorOffsets[i - 1], + colorData[i], colorData[i - 1], result); + result->writeText("}\n"); + } + + // Clamp the final color. + result->writeText("{pop "); + SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result); + result->writeText(" "); + SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result); + result->writeText(" "); + SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result); + + for (int i = 0 ; i < gradients + 1; i++) { + result->writeText("} ifelse\n"); + } +} + +static std::unique_ptr createInterpolationFunction(const ColorTuple& color1, + const ColorTuple& color2) { + auto retval = SkPDFMakeDict(); + + auto c0 = SkPDFMakeArray(); + c0->appendColorComponent(color1[0]); + c0->appendColorComponent(color1[1]); + c0->appendColorComponent(color1[2]); + retval->insertObject("C0", std::move(c0)); + + auto c1 = SkPDFMakeArray(); + c1->appendColorComponent(color2[0]); + c1->appendColorComponent(color2[1]); + c1->appendColorComponent(color2[2]); + retval->insertObject("C1", std::move(c1)); + + retval->insertObject("Domain", SkPDFMakeArray(0, 1)); + + retval->insertInt("FunctionType", 2); + retval->insertScalar("N", 1.0f); + + return retval; +} + +static std::unique_ptr gradientStitchCode(const SkShader::GradientInfo& info) { + auto retval = SkPDFMakeDict(); + + // normalize color stops + int colorCount = info.fColorCount; + std::vector colors(info.fColors, info.fColors + colorCount); + std::vector colorOffsets(info.fColorOffsets, info.fColorOffsets + colorCount); + + int i = 1; + while (i < colorCount - 1) { + // ensure stops are in order + if (colorOffsets[i - 1] > colorOffsets[i]) { + colorOffsets[i] = colorOffsets[i - 1]; + } + + // remove points that are between 2 coincident points + if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) { + colorCount -= 1; + colors.erase(colors.begin() + i); + colorOffsets.erase(colorOffsets.begin() + i); + } else { + i++; + } + } + // find coincident points and slightly move them over + for (i = 1; i < colorCount - 1; i++) { + if (colorOffsets[i - 1] == colorOffsets[i]) { + colorOffsets[i] += 0.00001f; + } + } + // check if last 2 stops coincide + if (colorOffsets[i - 1] == colorOffsets[i]) { + colorOffsets[i - 1] -= 0.00001f; + } + + SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount); + ColorTuple *colorData = colorDataAlloc.get(); + for (int i = 0; i < colorCount; i++) { + colorData[i][0] = SkColorGetR(colors[i]); + colorData[i][1] = SkColorGetG(colors[i]); + colorData[i][2] = SkColorGetB(colors[i]); + } + + // no need for a stitch function if there are only 2 stops. + if (colorCount == 2) + return createInterpolationFunction(colorData[0], colorData[1]); + + auto encode = SkPDFMakeArray(); + auto bounds = SkPDFMakeArray(); + auto functions = SkPDFMakeArray(); + + retval->insertObject("Domain", SkPDFMakeArray(0, 1)); + retval->insertInt("FunctionType", 3); + + for (int i = 1; i < colorCount; i++) { + if (i > 1) { + bounds->appendScalar(colorOffsets[i-1]); + } + + encode->appendScalar(0); + encode->appendScalar(1.0f); + + functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i])); + } + + retval->insertObject("Encode", std::move(encode)); + retval->insertObject("Bounds", std::move(bounds)); + retval->insertObject("Functions", std::move(functions)); + + return retval; +} + +/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ +static void tileModeCode(SkTileMode mode, SkDynamicMemoryWStream* result) { + if (mode == SkTileMode::kRepeat) { + result->writeText("dup truncate sub\n"); // Get the fractional part. + result->writeText("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1) + return; + } + + if (mode == SkTileMode::kMirror) { + // Map t mod 2 into [0, 1, 1, 0]. + // Code Stack + result->writeText("abs " // Map negative to positive. + "dup " // t.s t.s + "truncate " // t.s t + "dup " // t.s t t + "cvi " // t.s t T + "2 mod " // t.s t (i mod 2) + "1 eq " // t.s t true|false + "3 1 roll " // true|false t.s t + "sub " // true|false 0.s + "exch " // 0.s true|false + "{1 exch sub} if\n"); // 1 - 0.s|0.s + } +} + +/** + * Returns PS function code that applies inverse perspective + * to a x, y point. + * The function assumes that the stack has at least two elements, + * and that the top 2 elements are numeric values. + * After executing this code on a PS stack, the last 2 elements are updated + * while the rest of the stack is preserved intact. + * inversePerspectiveMatrix is the inverse perspective matrix. + */ +static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix, + SkDynamicMemoryWStream* code) { + if (!inversePerspectiveMatrix.hasPerspective()) { + return; + } + + // Perspective matrix should be: + // 1 0 0 + // 0 1 0 + // p0 p1 p2 + + const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; + const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; + const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; + + // y = y / (p2 + p0 x + p1 y) + // x = x / (p2 + p0 x + p1 y) + + // Input on stack: x y + code->writeText(" dup "); // x y y + SkPDFUtils::AppendScalar(p1, code); // x y y p1 + code->writeText(" mul " // x y y*p1 + " 2 index "); // x y y*p1 x + SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0 + code->writeText(" mul "); // x y y*p1 x*p0 + SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2 + code->writeText(" add " // x y y*p1 x*p0+p2 + "add " // x y y*p1+x*p0+p2 + "3 1 roll " // y*p1+x*p0+p2 x y + "2 index " // z x y y*p1+x*p0+p2 + "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) + "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x + "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 + "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) + "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) +} + +static void linearCode(const SkShader::GradientInfo& info, + const SkMatrix& perspectiveRemover, + SkDynamicMemoryWStream* function) { + function->writeText("{"); + + apply_perspective_to_coordinates(perspectiveRemover, function); + + function->writeText("pop\n"); // Just ditch the y value. + tileModeCode((SkTileMode)info.fTileMode, function); + gradient_function_code(info, function); + function->writeText("}"); +} + +static void radialCode(const SkShader::GradientInfo& info, + const SkMatrix& perspectiveRemover, + SkDynamicMemoryWStream* function) { + function->writeText("{"); + + apply_perspective_to_coordinates(perspectiveRemover, function); + + // Find the distance from the origin. + function->writeText("dup " // x y y + "mul " // x y^2 + "exch " // y^2 x + "dup " // y^2 x x + "mul " // y^2 x^2 + "add " // y^2+x^2 + "sqrt\n"); // sqrt(y^2+x^2) + + tileModeCode((SkTileMode)info.fTileMode, function); + gradient_function_code(info, function); + function->writeText("}"); +} + +/* Conical gradient shader, based on the Canvas spec for radial gradients + See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient + */ +static void twoPointConicalCode(const SkShader::GradientInfo& info, + const SkMatrix& perspectiveRemover, + SkDynamicMemoryWStream* function) { + SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX; + SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY; + SkScalar r0 = info.fRadius[0]; + SkScalar dr = info.fRadius[1] - info.fRadius[0]; + SkScalar a = dx * dx + dy * dy - dr * dr; + + // First compute t, if the pixel falls outside the cone, then we'll end + // with 'false' on the stack, otherwise we'll push 'true' with t below it + + // We start with a stack of (x y), copy it and then consume one copy in + // order to calculate b and the other to calculate c. + function->writeText("{"); + + apply_perspective_to_coordinates(perspectiveRemover, function); + + function->writeText("2 copy "); + + // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr). + SkPDFUtils::AppendScalar(dy, function); + function->writeText(" mul exch "); + SkPDFUtils::AppendScalar(dx, function); + function->writeText(" mul add "); + SkPDFUtils::AppendScalar(r0 * dr, function); + function->writeText(" add -2 mul dup dup mul\n"); + + // c = x^2 + y^2 + radius0^2 + function->writeText("4 2 roll dup mul exch dup mul add "); + SkPDFUtils::AppendScalar(r0 * r0, function); + function->writeText(" sub dup 4 1 roll\n"); + + // Contents of the stack at this point: c, b, b^2, c + + // if a = 0, then we collapse to a simpler linear case + if (a == 0) { + + // t = -c/b + function->writeText("pop pop div neg dup "); + + // compute radius(t) + SkPDFUtils::AppendScalar(dr, function); + function->writeText(" mul "); + SkPDFUtils::AppendScalar(r0, function); + function->writeText(" add\n"); + + // if r(t) < 0, then it's outside the cone + function->writeText("0 lt {pop false} {true} ifelse\n"); + + } else { + + // quadratic case: the Canvas spec wants the largest + // root t for which radius(t) > 0 + + // compute the discriminant (b^2 - 4ac) + SkPDFUtils::AppendScalar(a * 4, function); + function->writeText(" mul sub dup\n"); + + // if d >= 0, proceed + function->writeText("0 ge {\n"); + + // an intermediate value we'll use to compute the roots: + // q = -0.5 * (b +/- sqrt(d)) + function->writeText("sqrt exch dup 0 lt {exch -1 mul} if"); + function->writeText(" add -0.5 mul dup\n"); + + // first root = q / a + SkPDFUtils::AppendScalar(a, function); + function->writeText(" div\n"); + + // second root = c / q + function->writeText("3 1 roll div\n"); + + // put the larger root on top of the stack + function->writeText("2 copy gt {exch} if\n"); + + // compute radius(t) for larger root + function->writeText("dup "); + SkPDFUtils::AppendScalar(dr, function); + function->writeText(" mul "); + SkPDFUtils::AppendScalar(r0, function); + function->writeText(" add\n"); + + // if r(t) > 0, we have our t, pop off the smaller root and we're done + function->writeText(" 0 gt {exch pop true}\n"); + + // otherwise, throw out the larger one and try the smaller root + function->writeText("{pop dup\n"); + SkPDFUtils::AppendScalar(dr, function); + function->writeText(" mul "); + SkPDFUtils::AppendScalar(r0, function); + function->writeText(" add\n"); + + // if r(t) < 0, push false, otherwise the smaller root is our t + function->writeText("0 le {pop false} {true} ifelse\n"); + function->writeText("} ifelse\n"); + + // d < 0, clear the stack and push false + function->writeText("} {pop pop pop false} ifelse\n"); + } + + // if the pixel is in the cone, proceed to compute a color + function->writeText("{"); + tileModeCode((SkTileMode)info.fTileMode, function); + gradient_function_code(info, function); + + // otherwise, just write black + function->writeText("} {0 0 0} ifelse }"); +} + +static void sweepCode(const SkShader::GradientInfo& info, + const SkMatrix& perspectiveRemover, + SkDynamicMemoryWStream* function) { + function->writeText("{exch atan 360 div\n"); + tileModeCode((SkTileMode)info.fTileMode, function); + gradient_function_code(info, function); + function->writeText("}"); +} + + +// catch cases where the inner just touches the outer circle +// and make the inner circle just inside the outer one to match raster +static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) { + // detect touching circles + SkScalar distance = SkPoint::Distance(p1, p2); + SkScalar subtractRadii = fabs(r1 - r2); + if (fabs(distance - subtractRadii) < 0.002f) { + if (r1 > r2) { + r1 += 0.002f; + } else { + r2 += 0.002f; + } + } +} + +// Finds affine and persp such that in = affine * persp. +// but it returns the inverse of perspective matrix. +static bool split_perspective(const SkMatrix in, SkMatrix* affine, + SkMatrix* perspectiveInverse) { + const SkScalar p2 = in[SkMatrix::kMPersp2]; + + if (SkScalarNearlyZero(p2)) { + return false; + } + + const SkScalar zero = SkIntToScalar(0); + const SkScalar one = SkIntToScalar(1); + + const SkScalar sx = in[SkMatrix::kMScaleX]; + const SkScalar kx = in[SkMatrix::kMSkewX]; + const SkScalar tx = in[SkMatrix::kMTransX]; + const SkScalar ky = in[SkMatrix::kMSkewY]; + const SkScalar sy = in[SkMatrix::kMScaleY]; + const SkScalar ty = in[SkMatrix::kMTransY]; + const SkScalar p0 = in[SkMatrix::kMPersp0]; + const SkScalar p1 = in[SkMatrix::kMPersp1]; + + // Perspective matrix would be: + // 1 0 0 + // 0 1 0 + // p0 p1 p2 + // But we need the inverse of persp. + perspectiveInverse->setAll(one, zero, zero, + zero, one, zero, + -p0/p2, -p1/p2, 1/p2); + + affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2, + ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2, + zero, zero, one); + + return true; +} + +static SkPDFIndirectReference make_ps_function(std::unique_ptr psCode, + std::unique_ptr domain, + std::unique_ptr range, + SkPDFDocument* doc) { + std::unique_ptr dict = SkPDFMakeDict(); + dict->insertInt("FunctionType", 4); + dict->insertObject("Domain", std::move(domain)); + dict->insertObject("Range", std::move(range)); + return SkPDFStreamOut(std::move(dict), std::move(psCode), doc); +} + +static SkPDFIndirectReference make_function_shader(SkPDFDocument* doc, + const SkPDFGradientShader::Key& state) { + SkPoint transformPoints[2]; + const SkShader::GradientInfo& info = state.fInfo; + SkMatrix finalMatrix = state.fCanvasTransform; + finalMatrix.preConcat(state.fShaderTransform); + + bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType || + state.fType == SkShader::kRadial_GradientType || + state.fType == SkShader::kConical_GradientType) && + (SkTileMode)info.fTileMode == SkTileMode::kClamp && + !finalMatrix.hasPerspective(); + + int32_t shadingType = 1; + auto pdfShader = SkPDFMakeDict(); + // The two point radial gradient further references + // state.fInfo + // in translating from x, y coordinates to the t parameter. So, we have + // to transform the points and radii according to the calculated matrix. + if (doStitchFunctions) { + pdfShader->insertObject("Function", gradientStitchCode(info)); + shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3; + + auto extend = SkPDFMakeArray(); + extend->reserve(2); + extend->appendBool(true); + extend->appendBool(true); + pdfShader->insertObject("Extend", std::move(extend)); + + std::unique_ptr coords; + if (state.fType == SkShader::kConical_GradientType) { + SkScalar r1 = info.fRadius[0]; + SkScalar r2 = info.fRadius[1]; + SkPoint pt1 = info.fPoint[0]; + SkPoint pt2 = info.fPoint[1]; + FixUpRadius(pt1, r1, pt2, r2); + + coords = SkPDFMakeArray(pt1.x(), + pt1.y(), + r1, + pt2.x(), + pt2.y(), + r2); + } else if (state.fType == SkShader::kRadial_GradientType) { + const SkPoint& pt1 = info.fPoint[0]; + coords = SkPDFMakeArray(pt1.x(), + pt1.y(), + 0, + pt1.x(), + pt1.y(), + info.fRadius[0]); + } else { + const SkPoint& pt1 = info.fPoint[0]; + const SkPoint& pt2 = info.fPoint[1]; + coords = SkPDFMakeArray(pt1.x(), + pt1.y(), + pt2.x(), + pt2.y()); + } + + pdfShader->insertObject("Coords", std::move(coords)); + } else { + // Depending on the type of the gradient, we want to transform the + // coordinate space in different ways. + transformPoints[0] = info.fPoint[0]; + transformPoints[1] = info.fPoint[1]; + switch (state.fType) { + case SkShader::kLinear_GradientType: + break; + case SkShader::kRadial_GradientType: + transformPoints[1] = transformPoints[0]; + transformPoints[1].fX += info.fRadius[0]; + break; + case SkShader::kConical_GradientType: { + transformPoints[1] = transformPoints[0]; + transformPoints[1].fX += SK_Scalar1; + break; + } + case SkShader::kSweep_GradientType: + transformPoints[1] = transformPoints[0]; + transformPoints[1].fX += SK_Scalar1; + break; + case SkShader::kColor_GradientType: + case SkShader::kNone_GradientType: + default: + return SkPDFIndirectReference(); + } + + // Move any scaling (assuming a unit gradient) or translation + // (and rotation for linear gradient), of the final gradient from + // info.fPoints to the matrix (updating bbox appropriately). Now + // the gradient can be drawn on on the unit segment. + SkMatrix mapperMatrix; + unit_to_points_matrix(transformPoints, &mapperMatrix); + + finalMatrix.preConcat(mapperMatrix); + + // Preserves as much as possible in the final matrix, and only removes + // the perspective. The inverse of the perspective is stored in + // perspectiveInverseOnly matrix and has 3 useful numbers + // (p0, p1, p2), while everything else is either 0 or 1. + // In this way the shader will handle it eficiently, with minimal code. + SkMatrix perspectiveInverseOnly = SkMatrix::I(); + if (finalMatrix.hasPerspective()) { + if (!split_perspective(finalMatrix, + &finalMatrix, &perspectiveInverseOnly)) { + return SkPDFIndirectReference(); + } + } + + SkRect bbox; + bbox.set(state.fBBox); + if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) { + return SkPDFIndirectReference(); + } + SkDynamicMemoryWStream functionCode; + + SkShader::GradientInfo infoCopy = info; + + if (state.fType == SkShader::kConical_GradientType) { + SkMatrix inverseMapperMatrix; + if (!mapperMatrix.invert(&inverseMapperMatrix)) { + return SkPDFIndirectReference(); + } + inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2); + infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]); + infoCopy.fRadius[1] = inverseMapperMatrix.mapRadius(info.fRadius[1]); + } + switch (state.fType) { + case SkShader::kLinear_GradientType: + linearCode(infoCopy, perspectiveInverseOnly, &functionCode); + break; + case SkShader::kRadial_GradientType: + radialCode(infoCopy, perspectiveInverseOnly, &functionCode); + break; + case SkShader::kConical_GradientType: + twoPointConicalCode(infoCopy, perspectiveInverseOnly, &functionCode); + break; + case SkShader::kSweep_GradientType: + sweepCode(infoCopy, perspectiveInverseOnly, &functionCode); + break; + default: + SkASSERT(false); + } + pdfShader->insertObject( + "Domain", SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom())); + + auto domain = SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom()); + std::unique_ptr rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1); + pdfShader->insertRef("Function", + make_ps_function(functionCode.detachAsStream(), std::move(domain), + std::move(rangeObject), doc)); + } + + pdfShader->insertInt("ShadingType", shadingType); + pdfShader->insertName("ColorSpace", "DeviceRGB"); + + SkPDFDict pdfFunctionShader("Pattern"); + pdfFunctionShader.insertInt("PatternType", 2); + pdfFunctionShader.insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix)); + pdfFunctionShader.insertObject("Shading", std::move(pdfShader)); + return doc->emit(pdfFunctionShader); +} + +static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc, + SkPDFGradientShader::Key key, + bool keyHasAlpha); + +static std::unique_ptr get_gradient_resource_dict(SkPDFIndirectReference functionShader, + SkPDFIndirectReference gState) { + std::vector patternShaders; + if (functionShader != SkPDFIndirectReference()) { + patternShaders.push_back(functionShader); + } + std::vector graphicStates; + if (gState != SkPDFIndirectReference()) { + graphicStates.push_back(gState); + } + return SkPDFMakeResourceDict(std::move(graphicStates), + std::move(patternShaders), + std::vector(), + std::vector()); +} + +// Creates a content stream which fills the pattern P0 across bounds. +// @param gsIndex A graphics state resource index to apply, or <0 if no +// graphics state to apply. +static std::unique_ptr create_pattern_fill_content(int gsIndex, + int patternIndex, + SkRect& bounds) { + SkDynamicMemoryWStream content; + if (gsIndex >= 0) { + SkPDFUtils::ApplyGraphicState(gsIndex, &content); + } + SkPDFUtils::ApplyPattern(patternIndex, &content); + SkPDFUtils::AppendRectangle(bounds, &content); + SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, &content); + return content.detachAsStream(); +} + +static bool gradient_has_alpha(const SkPDFGradientShader::Key& key) { + SkASSERT(key.fType != SkShader::kNone_GradientType); + for (int i = 0; i < key.fInfo.fColorCount; i++) { + if ((SkAlpha)SkColorGetA(key.fInfo.fColors[i]) != SK_AlphaOPAQUE) { + return true; + } + } + return false; +} + +// warning: does not set fHash on new key. (Both callers need to change fields.) +static SkPDFGradientShader::Key clone_key(const SkPDFGradientShader::Key& k) { + SkPDFGradientShader::Key clone = { + k.fType, + k.fInfo, // change pointers later. + std::unique_ptr(new SkColor[k.fInfo.fColorCount]), + std::unique_ptr(new SkScalar[k.fInfo.fColorCount]), + k.fCanvasTransform, + k.fShaderTransform, + k.fBBox, 0}; + clone.fInfo.fColors = clone.fColors.get(); + clone.fInfo.fColorOffsets = clone.fStops.get(); + for (int i = 0; i < clone.fInfo.fColorCount; i++) { + clone.fInfo.fColorOffsets[i] = k.fInfo.fColorOffsets[i]; + clone.fInfo.fColors[i] = k.fInfo.fColors[i]; + } + return clone; +} + +static SkPDFIndirectReference create_smask_graphic_state(SkPDFDocument* doc, + const SkPDFGradientShader::Key& state) { + SkASSERT(state.fType != SkShader::kNone_GradientType); + SkPDFGradientShader::Key luminosityState = clone_key(state); + for (int i = 0; i < luminosityState.fInfo.fColorCount; i++) { + SkAlpha alpha = SkColorGetA(luminosityState.fInfo.fColors[i]); + luminosityState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); + } + luminosityState.fHash = hash(luminosityState); + + SkASSERT(!gradient_has_alpha(luminosityState)); + SkPDFIndirectReference luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false); + std::unique_ptr resources = get_gradient_resource_dict(luminosityShader, + SkPDFIndirectReference()); + SkRect bbox = SkRect::Make(state.fBBox); + SkPDFIndirectReference alphaMask = + SkPDFMakeFormXObject(doc, + create_pattern_fill_content(-1, luminosityShader.fValue, bbox), + SkPDFUtils::RectToArray(bbox), + std::move(resources), + SkMatrix::I(), + "DeviceRGB"); + return SkPDFGraphicState::GetSMaskGraphicState( + alphaMask, false, SkPDFGraphicState::kLuminosity_SMaskMode, doc); +} + +static SkPDFIndirectReference make_alpha_function_shader(SkPDFDocument* doc, + const SkPDFGradientShader::Key& state) { + SkASSERT(state.fType != SkShader::kNone_GradientType); + SkPDFGradientShader::Key opaqueState = clone_key(state); + for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) { + opaqueState.fInfo.fColors[i] = SkColorSetA(opaqueState.fInfo.fColors[i], SK_AlphaOPAQUE); + } + opaqueState.fHash = hash(opaqueState); + + SkASSERT(!gradient_has_alpha(opaqueState)); + SkRect bbox = SkRect::Make(state.fBBox); + SkPDFIndirectReference colorShader = find_pdf_shader(doc, std::move(opaqueState), false); + if (!colorShader) { + return SkPDFIndirectReference(); + } + // Create resource dict with alpha graphics state as G0 and + // pattern shader as P0, then write content stream. + SkPDFIndirectReference alphaGsRef = create_smask_graphic_state(doc, state); + + std::unique_ptr resourceDict = get_gradient_resource_dict(colorShader, alphaGsRef); + + std::unique_ptr colorStream = + create_pattern_fill_content(alphaGsRef.fValue, colorShader.fValue, bbox); + std::unique_ptr alphaFunctionShader = SkPDFMakeDict(); + SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader.get(), bbox, + std::move(resourceDict), SkMatrix::I()); + return SkPDFStreamOut(std::move(alphaFunctionShader), std::move(colorStream), doc); +} + +static SkPDFGradientShader::Key make_key(const SkShader* shader, + const SkMatrix& canvasTransform, + const SkIRect& bbox) { + SkPDFGradientShader::Key key = { + SkShader::kNone_GradientType, + {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkTileMode::kClamp, 0}, + nullptr, + nullptr, + canvasTransform, + SkPDFUtils::GetShaderLocalMatrix(shader), + bbox, 0}; + key.fType = shader->asAGradient(&key.fInfo); + SkASSERT(SkShader::kNone_GradientType != key.fType); + SkASSERT(key.fInfo.fColorCount > 0); + key.fColors.reset(new SkColor[key.fInfo.fColorCount]); + key.fStops.reset(new SkScalar[key.fInfo.fColorCount]); + key.fInfo.fColors = key.fColors.get(); + key.fInfo.fColorOffsets = key.fStops.get(); + (void)shader->asAGradient(&key.fInfo); + key.fHash = hash(key); + return key; +} + +static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc, + SkPDFGradientShader::Key key, + bool keyHasAlpha) { + SkASSERT(gradient_has_alpha(key) == keyHasAlpha); + auto& gradientPatternMap = doc->fGradientPatternMap; + if (SkPDFIndirectReference* ptr = gradientPatternMap.find(key)) { + return *ptr; + } + SkPDFIndirectReference pdfShader; + if (keyHasAlpha) { + pdfShader = make_alpha_function_shader(doc, key); + } else { + pdfShader = make_function_shader(doc, key); + } + gradientPatternMap.set(std::move(key), pdfShader); + return pdfShader; +} + +SkPDFIndirectReference SkPDFGradientShader::Make(SkPDFDocument* doc, + SkShader* shader, + const SkMatrix& canvasTransform, + const SkIRect& bbox) { + SkASSERT(shader); + SkASSERT(SkShader::kNone_GradientType != shader->asAGradient(nullptr)); + SkPDFGradientShader::Key key = make_key(shader, canvasTransform, bbox); + bool alpha = gradient_has_alpha(key); + return find_pdf_shader(doc, std::move(key), alpha); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFGradientShader.h b/gfx/skia/skia/src/pdf/SkPDFGradientShader.h new file mode 100644 index 0000000000..63876a7584 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGradientShader.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFGradientShader_DEFINED +#define SkPDFGradientShader_DEFINED + +#include "include/core/SkShader.h" +#include "src/pdf/SkPDFTypes.h" +#include "src/pdf/SkPDFUtils.h" + +class SkMatrix; +class SkPDFDocument; +struct SkIRect; + +namespace SkPDFGradientShader { + +SkPDFIndirectReference Make(SkPDFDocument* doc, + SkShader* shader, + const SkMatrix& matrix, + const SkIRect& surfaceBBox); + +struct Key { + SkShader::GradientType fType; + SkShader::GradientInfo fInfo; + std::unique_ptr fColors; + std::unique_ptr fStops; + SkMatrix fCanvasTransform; + SkMatrix fShaderTransform; + SkIRect fBBox; + uint32_t fHash; +}; + +struct KeyHash { + uint32_t operator()(const Key& k) const { return k.fHash; } +}; + +inline bool operator==(const SkShader::GradientInfo& u, const SkShader::GradientInfo& v) { + return u.fColorCount == v.fColorCount + && u.fPoint[0] == v.fPoint[0] + && u.fPoint[1] == v.fPoint[1] + && u.fRadius[0] == v.fRadius[0] + && u.fRadius[1] == v.fRadius[1] + && u.fTileMode == v.fTileMode + && u.fGradientFlags == v.fGradientFlags + && SkPackedArrayEqual(u.fColors, v.fColors, u.fColorCount) + && SkPackedArrayEqual(u.fColorOffsets, v.fColorOffsets, u.fColorCount); +} + +inline bool operator==(const Key& u, const Key& v) { + SkASSERT(u.fInfo.fColors == u.fColors.get()); + SkASSERT(u.fInfo.fColorOffsets == u.fStops.get()); + SkASSERT(v.fInfo.fColors == v.fColors.get()); + SkASSERT(v.fInfo.fColorOffsets == v.fStops.get()); + return u.fType == v.fType + && u.fInfo == v.fInfo + && u.fCanvasTransform == v.fCanvasTransform + && u.fShaderTransform == v.fShaderTransform + && u.fBBox == v.fBBox; +} +inline bool operator!=(const Key& u, const Key& v) { return !(u == v); } + +} // namespace SkPDFGradientShader +#endif // SkPDFGradientShader_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFGraphicStackState.cpp b/gfx/skia/skia/src/pdf/SkPDFGraphicStackState.cpp new file mode 100644 index 0000000000..a27170b707 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGraphicStackState.cpp @@ -0,0 +1,250 @@ +// Copyright 2019 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +#include "src/pdf/SkPDFGraphicStackState.h" + +#include "include/core/SkStream.h" +#include "include/pathops/SkPathOps.h" +#include "src/pdf/SkPDFUtils.h" + +static SkPath to_path(const SkRect& r) { + SkPath p; + p.addRect(r); + return p; +} + +static void emit_pdf_color(SkColor4f color, SkWStream* result) { + SkASSERT(color.fA == 1); // We handle alpha elsewhere. + SkPDFUtils::AppendColorComponentF(color.fR, result); + result->writeText(" "); + SkPDFUtils::AppendColorComponentF(color.fG, result); + result->writeText(" "); + SkPDFUtils::AppendColorComponentF(color.fB, result); + result->writeText(" "); +} + +static SkRect rect_intersect(SkRect u, SkRect v) { + if (u.isEmpty() || v.isEmpty()) { return {0, 0, 0, 0}; } + return u.intersect(v) ? u : SkRect{0, 0, 0, 0}; +} + +// Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code +// and speed thing up. +static bool is_rect(const SkClipStack& clipStack, const SkRect& bounds, SkRect* dst) { + SkRect currentClip = bounds; + SkClipStack::Iter iter(clipStack, SkClipStack::Iter::kBottom_IterStart); + while (const SkClipStack::Element* element = iter.next()) { + SkRect elementRect{0, 0, 0, 0}; + switch (element->getDeviceSpaceType()) { + case SkClipStack::Element::DeviceSpaceType::kEmpty: + break; + case SkClipStack::Element::DeviceSpaceType::kRect: + elementRect = element->getDeviceSpaceRect(); + break; + default: + return false; + } + switch (element->getOp()) { + case kReplace_SkClipOp: + currentClip = rect_intersect(bounds, elementRect); + break; + case SkClipOp::kIntersect: + currentClip = rect_intersect(currentClip, elementRect); + break; + default: + return false; + } + } + *dst = currentClip; + return true; +} + +static bool is_complex_clip(const SkClipStack& stack) { + SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); + while (const SkClipStack::Element* element = iter.next()) { + switch (element->getOp()) { + case SkClipOp::kDifference: + case SkClipOp::kIntersect: + break; + default: + return true; + } + } + return false; +} + +template +static void apply_clip(const SkClipStack& stack, const SkRect& outerBounds, F fn) { + // assumes clipstack is not complex. + constexpr SkRect kHuge{-30000, -30000, 30000, 30000}; + SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); + SkRect bounds = outerBounds; + while (const SkClipStack::Element* element = iter.next()) { + SkPath operand; + element->asDeviceSpacePath(&operand); + SkPathOp op; + switch (element->getOp()) { + case SkClipOp::kDifference: op = kDifference_SkPathOp; break; + case SkClipOp::kIntersect: op = kIntersect_SkPathOp; break; + default: SkASSERT(false); return; + } + if (op == kDifference_SkPathOp || + operand.isInverseFillType() || + !kHuge.contains(operand.getBounds())) + { + Op(to_path(bounds), operand, op, &operand); + } + SkASSERT(!operand.isInverseFillType()); + fn(operand); + if (!bounds.intersect(operand.getBounds())) { + return; // return early; + } + } +} + +static void append_clip_path(const SkPath& clipPath, SkWStream* wStream) { + SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, wStream); + SkPath::FillType clipFill = clipPath.getFillType(); + NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false); + NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false); + if (clipFill == SkPath::kEvenOdd_FillType) { + wStream->writeText("W* n\n"); + } else { + wStream->writeText("W n\n"); + } +} + +static void append_clip(const SkClipStack& clipStack, + const SkIRect& bounds, + SkWStream* wStream) { + // The bounds are slightly outset to ensure this is correct in the + // face of floating-point accuracy and possible SkRegion bitmap + // approximations. + SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1)); + + SkRect clipStackRect; + if (is_rect(clipStack, outsetBounds, &clipStackRect)) { + SkPDFUtils::AppendRectangle(clipStackRect, wStream); + wStream->writeText("W* n\n"); + return; + } + + if (is_complex_clip(clipStack)) { + SkPath clipPath; + (void)clipStack.asPath(&clipPath); + if (Op(clipPath, to_path(outsetBounds), kIntersect_SkPathOp, &clipPath)) { + append_clip_path(clipPath, wStream); + } + // If Op() fails (pathological case; e.g. input values are + // extremely large or NaN), emit no clip at all. + } else { + apply_clip(clipStack, outsetBounds, [wStream](const SkPath& path) { + append_clip_path(path, wStream); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void SkPDFGraphicStackState::updateClip(const SkClipStack* clipStack, const SkIRect& bounds) { + uint32_t clipStackGenID = clipStack ? clipStack->getTopmostGenID() + : SkClipStack::kWideOpenGenID; + if (clipStackGenID == currentEntry()->fClipStackGenID) { + return; + } + while (fStackDepth > 0) { + this->pop(); + if (clipStackGenID == currentEntry()->fClipStackGenID) { + return; + } + } + SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID); + if (clipStackGenID != SkClipStack::kWideOpenGenID) { + SkASSERT(clipStack); + this->push(); + + currentEntry()->fClipStackGenID = clipStackGenID; + append_clip(*clipStack, bounds, fContentStream); + } +} + + +void SkPDFGraphicStackState::updateMatrix(const SkMatrix& matrix) { + if (matrix == currentEntry()->fMatrix) { + return; + } + + if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) { + SkASSERT(fStackDepth > 0); + SkASSERT(fEntries[fStackDepth].fClipStackGenID == + fEntries[fStackDepth -1].fClipStackGenID); + this->pop(); + + SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask); + } + if (matrix.getType() == SkMatrix::kIdentity_Mask) { + return; + } + + this->push(); + SkPDFUtils::AppendTransform(matrix, fContentStream); + currentEntry()->fMatrix = matrix; +} + +void SkPDFGraphicStackState::updateDrawingState(const SkPDFGraphicStackState::Entry& state) { + // PDF treats a shader as a color, so we only set one or the other. + if (state.fShaderIndex >= 0) { + if (state.fShaderIndex != currentEntry()->fShaderIndex) { + SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream); + currentEntry()->fShaderIndex = state.fShaderIndex; + } + } else { + if (state.fColor != currentEntry()->fColor || + currentEntry()->fShaderIndex >= 0) { + emit_pdf_color(state.fColor, fContentStream); + fContentStream->writeText("RG "); + emit_pdf_color(state.fColor, fContentStream); + fContentStream->writeText("rg\n"); + currentEntry()->fColor = state.fColor; + currentEntry()->fShaderIndex = -1; + } + } + + if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) { + SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream); + currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex; + } + + if (state.fTextScaleX) { + if (state.fTextScaleX != currentEntry()->fTextScaleX) { + SkScalar pdfScale = state.fTextScaleX * 100; + SkPDFUtils::AppendScalar(pdfScale, fContentStream); + fContentStream->writeText(" Tz\n"); + currentEntry()->fTextScaleX = state.fTextScaleX; + } + } +} + +void SkPDFGraphicStackState::push() { + SkASSERT(fStackDepth < kMaxStackDepth); + fContentStream->writeText("q\n"); + ++fStackDepth; + fEntries[fStackDepth] = fEntries[fStackDepth - 1]; +} + +void SkPDFGraphicStackState::pop() { + SkASSERT(fStackDepth > 0); + fContentStream->writeText("Q\n"); + fEntries[fStackDepth] = SkPDFGraphicStackState::Entry(); + --fStackDepth; +} + +void SkPDFGraphicStackState::drainStack() { + if (fContentStream) { + while (fStackDepth) { + this->pop(); + } + } + SkASSERT(fStackDepth == 0); +} + diff --git a/gfx/skia/skia/src/pdf/SkPDFGraphicStackState.h b/gfx/skia/skia/src/pdf/SkPDFGraphicStackState.h new file mode 100644 index 0000000000..593e9b197b --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGraphicStackState.h @@ -0,0 +1,40 @@ +// Copyright 2019 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkPDFGraphicStackState_DEFINED +#define SkPDFGraphicStackState_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkScalar.h" +#include "src/core/SkClipStack.h" + +class SkDynamicMemoryWStream; + +// It is important to not confuse SkPDFGraphicStackState with SkPDFGraphicState, the +// later being our representation of an object in the PDF file. +struct SkPDFGraphicStackState { + struct Entry { + SkMatrix fMatrix = SkMatrix::I(); + uint32_t fClipStackGenID = SkClipStack::kWideOpenGenID; + SkColor4f fColor = {0, 0, 0, 1}; + SkScalar fTextScaleX = 1; // Zero means we don't care what the value is. + int fShaderIndex = -1; + int fGraphicStateIndex = -1; + }; + // Must use stack for matrix, and for clip, plus one for no matrix or clip. + static constexpr int kMaxStackDepth = 2; + Entry fEntries[kMaxStackDepth + 1]; + int fStackDepth = 0; + SkDynamicMemoryWStream* fContentStream; + + SkPDFGraphicStackState(SkDynamicMemoryWStream* s = nullptr) : fContentStream(s) {} + void updateClip(const SkClipStack* clipStack, const SkIRect& bounds); + void updateMatrix(const SkMatrix& matrix); + void updateDrawingState(const Entry& state); + void push(); + void pop(); + void drainStack(); + Entry* currentEntry() { return &fEntries[fStackDepth]; } +}; + +#endif // SkPDFGraphicStackState_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFGraphicState.cpp b/gfx/skia/skia/src/pdf/SkPDFGraphicState.cpp new file mode 100644 index 0000000000..5817c11f12 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGraphicState.cpp @@ -0,0 +1,140 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFGraphicState.h" + +#include "include/core/SkData.h" +#include "include/core/SkPaint.h" +#include "include/docs/SkPDFDocument.h" +#include "include/private/SkTo.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFFormXObject.h" +#include "src/pdf/SkPDFUtils.h" + +static const char* as_pdf_blend_mode_name(SkBlendMode mode) { + const char* name = SkPDFUtils::BlendModeName(mode); + SkASSERT(name); + return name; +} + +static int to_stroke_cap(uint8_t cap) { + // PDF32000.book section 8.4.3.3 "Line Cap Style" + switch ((SkPaint::Cap)cap) { + case SkPaint::kButt_Cap: return 0; + case SkPaint::kRound_Cap: return 1; + case SkPaint::kSquare_Cap: return 2; + default: SkASSERT(false); return 0; + } +} + +static int to_stroke_join(uint8_t join) { + // PDF32000.book section 8.4.3.4 "Line Join Style" + switch ((SkPaint::Join)join) { + case SkPaint::kMiter_Join: return 0; + case SkPaint::kRound_Join: return 1; + case SkPaint::kBevel_Join: return 2; + default: SkASSERT(false); return 0; + } +} + +// If a SkXfermode is unsupported in PDF, this function returns +// SrcOver, otherwise, it returns that Xfermode as a Mode. +static uint8_t pdf_blend_mode(SkBlendMode mode) { + if (!SkPDFUtils::BlendModeName(mode) + || SkBlendMode::kXor == mode + || SkBlendMode::kPlus == mode) + { + mode = SkBlendMode::kSrcOver; + } + return SkToU8((unsigned)mode); +} + +SkPDFIndirectReference SkPDFGraphicState::GetGraphicStateForPaint(SkPDFDocument* doc, + const SkPaint& p) { + SkASSERT(doc); + if (SkPaint::kFill_Style == p.getStyle()) { + SkPDFFillGraphicState fillKey = {p.getColor4f().fA, pdf_blend_mode(p.getBlendMode())}; + auto& fillMap = doc->fFillGSMap; + if (SkPDFIndirectReference* statePtr = fillMap.find(fillKey)) { + return *statePtr; + } + SkPDFDict state; + state.reserve(2); + state.insertColorComponentF("ca", fillKey.fAlpha); + state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode)); + SkPDFIndirectReference ref = doc->emit(state); + fillMap.set(fillKey, ref); + return ref; + } else { + SkPDFStrokeGraphicState strokeKey = { + p.getStrokeWidth(), + p.getStrokeMiter(), + p.getColor4f().fA, + SkToU8(p.getStrokeCap()), + SkToU8(p.getStrokeJoin()), + pdf_blend_mode(p.getBlendMode()) + }; + auto& sMap = doc->fStrokeGSMap; + if (SkPDFIndirectReference* statePtr = sMap.find(strokeKey)) { + return *statePtr; + } + SkPDFDict state; + state.reserve(8); + state.insertColorComponentF("CA", strokeKey.fAlpha); + state.insertColorComponentF("ca", strokeKey.fAlpha); + state.insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap)); + state.insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin)); + state.insertScalar("LW", strokeKey.fStrokeWidth); + state.insertScalar("ML", strokeKey.fStrokeMiter); + state.insertBool("SA", true); // SA = Auto stroke adjustment. + state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode)); + SkPDFIndirectReference ref = doc->emit(state); + sMap.set(strokeKey, ref); + return ref; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +static SkPDFIndirectReference make_invert_function(SkPDFDocument* doc) { + // Acrobat crashes if we use a type 0 function, kpdf crashes if we use + // a type 2 function, so we use a type 4 function. + static const char psInvert[] = "{1 exch sub}"; + // Do not copy the trailing '\0' into the SkData. + auto invertFunction = SkData::MakeWithoutCopy(psInvert, strlen(psInvert)); + + std::unique_ptr dict = SkPDFMakeDict(); + dict->insertInt("FunctionType", 4); + dict->insertObject("Domain", SkPDFMakeArray(0, 1)); + dict->insertObject("Range", SkPDFMakeArray(0, 1)); + return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(std::move(invertFunction)), doc); +} + +SkPDFIndirectReference SkPDFGraphicState::GetSMaskGraphicState(SkPDFIndirectReference sMask, + bool invert, + SkPDFSMaskMode sMaskMode, + SkPDFDocument* doc) { + // The practical chances of using the same mask more than once are unlikely + // enough that it's not worth canonicalizing. + auto sMaskDict = SkPDFMakeDict("Mask"); + if (sMaskMode == kAlpha_SMaskMode) { + sMaskDict->insertName("S", "Alpha"); + } else if (sMaskMode == kLuminosity_SMaskMode) { + sMaskDict->insertName("S", "Luminosity"); + } + sMaskDict->insertRef("G", sMask); + if (invert) { + // let the doc deduplicate this object. + if (doc->fInvertFunction == SkPDFIndirectReference()) { + doc->fInvertFunction = make_invert_function(doc); + } + sMaskDict->insertRef("TR", doc->fInvertFunction); + } + SkPDFDict result("ExtGState"); + result.insertObject("SMask", std::move(sMaskDict)); + return doc->emit(result); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFGraphicState.h b/gfx/skia/skia/src/pdf/SkPDFGraphicState.h new file mode 100644 index 0000000000..e5dd192990 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFGraphicState.h @@ -0,0 +1,71 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkPDFGraphicState_DEFINED +#define SkPDFGraphicState_DEFINED + +#include "include/private/SkMacros.h" +#include "src/core/SkOpts.h" +#include "src/pdf/SkPDFTypes.h" + +class SkPaint; + + +/** \class SkPDFGraphicState + SkPaint objects roughly correspond to graphic state dictionaries that can + be installed. So that a given dictionary is only output to the pdf file + once, we want to canonicalize them. +*/ +namespace SkPDFGraphicState { + enum SkPDFSMaskMode { + kAlpha_SMaskMode, + kLuminosity_SMaskMode + }; + + /** Get the graphic state for the passed SkPaint. + */ + SkPDFIndirectReference GetGraphicStateForPaint(SkPDFDocument*, const SkPaint&); + + /** Make a graphic state that only sets the passed soft mask. + * @param sMask The form xobject to use as a soft mask. + * @param invert Indicates if the alpha of the sMask should be inverted. + * @param sMaskMode Whether to use alpha or luminosity for the sMask. + * + * These are not de-duped. + */ + SkPDFIndirectReference GetSMaskGraphicState(SkPDFIndirectReference sMask, + bool invert, + SkPDFSMaskMode sMaskMode, + SkPDFDocument* doc); +} + +SK_BEGIN_REQUIRE_DENSE +struct SkPDFStrokeGraphicState { + SkScalar fStrokeWidth; + SkScalar fStrokeMiter; + SkScalar fAlpha; + uint8_t fStrokeCap; // SkPaint::Cap + uint8_t fStrokeJoin; // SkPaint::Join + uint8_t fBlendMode; // SkBlendMode + uint8_t fPADDING = 0; + bool operator==(const SkPDFStrokeGraphicState& o) const { return !memcmp(this, &o, sizeof(o)); } + bool operator!=(const SkPDFStrokeGraphicState& o) const { return !(*this == o); } +}; +SK_END_REQUIRE_DENSE + +SK_BEGIN_REQUIRE_DENSE +struct SkPDFFillGraphicState { + SkScalar fAlpha; + uint8_t fBlendMode; + uint8_t fPADDING[3] = {0, 0, 0}; + bool operator==(const SkPDFFillGraphicState& o) const { return !memcmp(this, &o, sizeof(o)); } + bool operator!=(const SkPDFFillGraphicState& o) const { return !(*this == o); } +}; +SK_END_REQUIRE_DENSE + +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp b/gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp new file mode 100644 index 0000000000..210202db4d --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFMakeCIDGlyphWidthsArray.h" + +#include "include/core/SkPaint.h" +#include "include/private/SkTo.h" +#include "src/core/SkStrike.h" +#include "src/core/SkStrikeSpec.h" +#include "src/pdf/SkPDFGlyphUse.h" + +#include +#include + +// TODO(halcanary): Write unit tests for SkPDFMakeCIDGlyphWidthsArray(). + +// TODO(halcanary): The logic in this file originated in several +// disparate places. I feel sure that someone could simplify this +// down to a single easy-to-read function. + +namespace { + +// scale from em-units to base-1000, returning as a SkScalar +SkScalar from_font_units(SkScalar scaled, uint16_t emSize) { + if (emSize == 1000) { + return scaled; + } else { + return scaled * 1000 / emSize; + } +} + +SkScalar scale_from_font_units(int16_t val, uint16_t emSize) { + return from_font_units(SkIntToScalar(val), emSize); +} + +// Unfortunately poppler does not appear to respect the default width setting. +#if defined(SK_PDF_CAN_USE_DW) +int16_t findMode(SkSpan advances) { + if (advances.empty()) { + return 0; + } + + int16_t previousAdvance = advances[0]; + int16_t currentModeAdvance = advances[0]; + size_t currentCount = 1; + size_t currentModeCount = 1; + + for (size_t i = 1; i < advances.size(); ++i) { + if (advances[i] == previousAdvance) { + ++currentCount; + } else { + if (currentCount > currentModeCount) { + currentModeAdvance = previousAdvance; + currentModeCount = currentCount; + } + previousAdvance = advances[i]; + currentCount = 1; + } + } + + return currentCount > currentModeCount ? previousAdvance : currentModeAdvance; +} +#endif +} // namespace + +/** Retrieve advance data for glyphs. Used by the PDF backend. */ +// TODO(halcanary): this function is complex enough to need its logic +// tested with unit tests. +std::unique_ptr SkPDFMakeCIDGlyphWidthsArray(const SkTypeface& typeface, + const SkPDFGlyphUse& subset, + SkScalar* defaultAdvance) { + // There are two ways of expressing advances + // + // range: " gfid [adv.ances adv.ances ... adv.ances]" + // run: " gfid gfid adv.ances" + // + // Assuming that on average + // the ASCII representation of an advance plus a space is 10 characters + // the ASCII representation of a glyph id plus a space is 4 characters + // the ASCII representation of unused gid plus a space in a range is 2 characters + // + // When not in a range or run + // a. Skipping don't cares or defaults is a win (trivial) + // b. Run wins for 2+ repeats " gid gid adv.ances" + // " gid [adv.ances adv.ances]" + // rule: 2+ repeats create run as long as possible, else start range + // + // When in a range + // Cost of stopping and starting a range is 8 characters "] gid [" + // c. Skipping defaults is always a win " adv.ances" + // rule: end range if default seen + // d. Skipping 4+ don't cares is a win " 0 0 0 0" + // rule: end range if 4+ don't cares + // Cost of stop and start range plus run is 28 characters "] gid gid adv.ances gid [" + // e. Switching for 2+ repeats and 4+ don't cares wins " 0 0 adv.ances 0 0 adv.ances" + // rule: end range for 2+ repeats with 4+ don't cares + // f. Switching for 3+ repeats wins " adv.ances adv.ances adv.ances" + // rule: end range for 3+ repeats + + int emSize; + SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(typeface, &emSize); + SkBulkGlyphMetricsAndPaths paths{strikeSpec}; + + auto result = SkPDFMakeArray(); + + std::vector glyphIDs; + subset.getSetValues([&](unsigned index) { + glyphIDs.push_back(SkToU16(index)); + }); + auto glyphs = paths.glyphs(SkMakeSpan(glyphIDs)); + +#if defined(SK_PDF_CAN_USE_DW) + std::vector advances; + advances.reserve(glyphs.size()); + for (const SkGlyph* glyph : glyphs) { + advances.push_back((int16_t)glyph->advanceX()); + } + std::sort(advances.begin(), advances.end()); + int16_t modeAdvance = findMode(SkMakeSpan(advances)); + *defaultAdvance = scale_from_font_units(modeAdvance, emSize); +#else + *defaultAdvance = 0; +#endif + + for (size_t i = 0; i < glyphs.size(); ++i) { + int16_t advance = (int16_t)glyphs[i]->advanceX(); + +#if defined(SK_PDF_CAN_USE_DW) + // a. Skipping don't cares or defaults is a win (trivial) + if (advance == modeAdvance) { + continue; + } +#endif + + // b. 2+ repeats create run as long as possible, else start range + { + size_t j = i + 1; // j is always one past the last known repeat + for (; j < glyphs.size(); ++j) { + int16_t next_advance = (int16_t)glyphs[j]->advanceX(); + if (advance != next_advance) { + break; + } + } + if (j - i >= 2) { + result->appendInt(glyphs[i]->getGlyphID()); + result->appendInt(glyphs[j - 1]->getGlyphID()); + result->appendScalar(scale_from_font_units(advance, emSize)); + i = j - 1; + continue; + } + } + + { + result->appendInt(glyphs[i]->getGlyphID()); + auto advanceArray = SkPDFMakeArray(); + advanceArray->appendScalar(scale_from_font_units(advance, emSize)); + size_t j = i + 1; // j is always one past the last output + for (; j < glyphs.size(); ++j) { + advance = (int16_t)glyphs[j]->advanceX(); +#if defined(SK_PDF_CAN_USE_DW) + // c. end range if default seen + if (advance == modeAdvance) { + break; + } +#endif + + int dontCares = glyphs[j]->getGlyphID() - glyphs[j - 1]->getGlyphID() - 1; + // d. end range if 4+ don't cares + if (dontCares >= 4) { + break; + } + + int16_t next_advance = 0; + // e. end range for 2+ repeats with 4+ don't cares + if (j + 1 < glyphs.size()) { + next_advance = (int16_t)glyphs[j+1]->advanceX(); + int next_dontCares = glyphs[j+1]->getGlyphID() - glyphs[j]->getGlyphID() - 1; + if (advance == next_advance && dontCares + next_dontCares >= 4) { + break; + } + } + + // f. end range for 3+ repeats + if (j + 2 < glyphs.size() && advance == next_advance) { + next_advance = (int16_t)glyphs[j+2]->advanceX(); + if (advance == next_advance) { + break; + } + } + + while (dontCares --> 0) { + advanceArray->appendScalar(0); + } + advanceArray->appendScalar(scale_from_font_units(advance, emSize)); + } + result->appendObject(std::move(advanceArray)); + i = j - 1; + } + } + + return result; +} diff --git a/gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h b/gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h new file mode 100644 index 0000000000..041fe28d8c --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFMakeCIDGlyphWidthsArray_DEFINED +#define SkPDFMakeCIDGlyphWidthsArray_DEFINED + +#include "src/pdf/SkPDFTypes.h" + +class SkStrike; +class SkPDFGlyphUse; +class SkTypeface; + +/* PDF 32000-1:2008, page 270: "The array's elements have a variable + format that can specify individual widths for consecutive CIDs or + one width for a range of CIDs". */ +std::unique_ptr SkPDFMakeCIDGlyphWidthsArray(const SkTypeface& typeface, + const SkPDFGlyphUse& subset, + SkScalar* defaultAdvance); + +#endif // SkPDFMakeCIDGlyphWidthsArray_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.cpp b/gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.cpp new file mode 100644 index 0000000000..edcaa65eb3 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.cpp @@ -0,0 +1,220 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFMakeToUnicodeCmap.h" + +#include "include/private/SkTo.h" +#include "src/pdf/SkPDFUtils.h" +#include "src/utils/SkUTF.h" + +static void append_tounicode_header(SkDynamicMemoryWStream* cmap, + bool multibyte) { + // 12 dict begin: 12 is an Adobe-suggested value. Shall not change. + // It's there to prevent old version Adobe Readers from malfunctioning. + const char* kHeader = + "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n"; + cmap->writeText(kHeader); + + // The /CIDSystemInfo must be consistent to the one in + // SkPDFFont::populateCIDFont(). + // We can not pass over the system info object here because the format is + // different. This is not a reference object. + const char* kSysInfo = + "/CIDSystemInfo\n" + "<< /Registry (Adobe)\n" + "/Ordering (UCS)\n" + "/Supplement 0\n" + ">> def\n"; + cmap->writeText(kSysInfo); + + // The CMapName must be consistent to /CIDSystemInfo above. + // /CMapType 2 means ToUnicode. + // Codespace range just tells the PDF processor the valid range. + const char* kTypeInfoHeader = + "/CMapName /Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n"; + cmap->writeText(kTypeInfoHeader); + if (multibyte) { + cmap->writeText("<0000> \n"); + } else { + cmap->writeText("<00> \n"); + } + cmap->writeText("endcodespacerange\n"); +} + +static void append_cmap_footer(SkDynamicMemoryWStream* cmap) { + const char kFooter[] = + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end"; + cmap->writeText(kFooter); +} + +namespace { +struct BFChar { + SkGlyphID fGlyphId; + SkUnichar fUnicode; +}; + +struct BFRange { + SkGlyphID fStart; + SkGlyphID fEnd; + SkUnichar fUnicode; +}; +} // namespace + +static void write_glyph(SkDynamicMemoryWStream* cmap, + bool multiByte, + SkGlyphID gid) { + if (multiByte) { + SkPDFUtils::WriteUInt16BE(cmap, gid); + } else { + SkPDFUtils::WriteUInt8(cmap, SkToU8(gid)); + } +} + +static void append_bfchar_section(const std::vector& bfchar, + bool multiByte, + SkDynamicMemoryWStream* cmap) { + // PDF spec defines that every bf* list can have at most 100 entries. + for (size_t i = 0; i < bfchar.size(); i += 100) { + int count = SkToInt(bfchar.size() - i); + count = SkMin32(count, 100); + cmap->writeDecAsText(count); + cmap->writeText(" beginbfchar\n"); + for (int j = 0; j < count; ++j) { + cmap->writeText("<"); + write_glyph(cmap, multiByte, bfchar[i + j].fGlyphId); + cmap->writeText("> <"); + SkPDFUtils::WriteUTF16beHex(cmap, bfchar[i + j].fUnicode); + cmap->writeText(">\n"); + } + cmap->writeText("endbfchar\n"); + } +} + +static void append_bfrange_section(const std::vector& bfrange, + bool multiByte, + SkDynamicMemoryWStream* cmap) { + // PDF spec defines that every bf* list can have at most 100 entries. + for (size_t i = 0; i < bfrange.size(); i += 100) { + int count = SkToInt(bfrange.size() - i); + count = SkMin32(count, 100); + cmap->writeDecAsText(count); + cmap->writeText(" beginbfrange\n"); + for (int j = 0; j < count; ++j) { + cmap->writeText("<"); + write_glyph(cmap, multiByte, bfrange[i + j].fStart); + cmap->writeText("> <"); + write_glyph(cmap, multiByte, bfrange[i + j].fEnd); + cmap->writeText("> <"); + SkPDFUtils::WriteUTF16beHex(cmap, bfrange[i + j].fUnicode); + cmap->writeText(">\n"); + } + cmap->writeText("endbfrange\n"); + } +} + +// Generate and table according to PDF spec 1.4 and Adobe +// Technote 5014. +// The function is not static so we can test it in unit tests. +// +// Current implementation guarantees bfchar and bfrange entries do not overlap. +// +// Current implementation does not attempt aggressive optimizations against +// following case because the specification is not clear. +// +// 4 beginbfchar 1 beginbfchar +// <0003> <0013> <0020> <0014> +// <0005> <0015> to endbfchar +// <0007> <0017> 1 beginbfrange +// <0020> <0014> <0003> <0007> <0013> +// endbfchar endbfrange +// +// Adobe Technote 5014 said: "Code mappings (unlike codespace ranges) may +// overlap, but succeeding maps supersede preceding maps." +// +// In case of searching text in PDF, bfrange will have higher precedence so +// typing char id 0x0014 in search box will get glyph id 0x0004 first. However, +// the spec does not mention how will this kind of conflict being resolved. +// +// For the worst case (having 65536 continuous unicode and we use every other +// one of them), the possible savings by aggressive optimization is 416KB +// pre-compressed and does not provide enough motivation for implementation. +void SkPDFAppendCmapSections(const SkUnichar* glyphToUnicode, + const SkPDFGlyphUse* subset, + SkDynamicMemoryWStream* cmap, + bool multiByteGlyphs, + SkGlyphID firstGlyphID, + SkGlyphID lastGlyphID) { + int glyphOffset = 0; + if (!multiByteGlyphs) { + glyphOffset = firstGlyphID - 1; + } + + std::vector bfcharEntries; + std::vector bfrangeEntries; + + BFRange currentRangeEntry = {0, 0, 0}; + bool rangeEmpty = true; + const int limit = (int)lastGlyphID + 1 - glyphOffset; + + for (int i = firstGlyphID - glyphOffset; i < limit + 1; ++i) { + SkGlyphID gid = i + glyphOffset; + bool inSubset = i < limit && (subset == nullptr || subset->has(gid)); + if (!rangeEmpty) { + // PDF spec requires bfrange not changing the higher byte, + // e.g. <1035> <10FF> <2222> is ok, but + // <1035> <1100> <2222> is no good + bool inRange = + i == currentRangeEntry.fEnd + 1 && + i >> 8 == currentRangeEntry.fStart >> 8 && + i < limit && + glyphToUnicode[gid] == + currentRangeEntry.fUnicode + i - currentRangeEntry.fStart; + if (!inSubset || !inRange) { + if (currentRangeEntry.fEnd > currentRangeEntry.fStart) { + bfrangeEntries.push_back(currentRangeEntry); + } else { + bfcharEntries.push_back({currentRangeEntry.fStart, currentRangeEntry.fUnicode}); + } + rangeEmpty = true; + } + } + if (inSubset) { + currentRangeEntry.fEnd = i; + if (rangeEmpty) { + currentRangeEntry.fStart = i; + currentRangeEntry.fUnicode = glyphToUnicode[gid]; + rangeEmpty = false; + } + } + } + + // The spec requires all bfchar entries for a font must come before bfrange + // entries. + append_bfchar_section(bfcharEntries, multiByteGlyphs, cmap); + append_bfrange_section(bfrangeEntries, multiByteGlyphs, cmap); +} + +std::unique_ptr SkPDFMakeToUnicodeCmap( + const SkUnichar* glyphToUnicode, + const SkPDFGlyphUse* subset, + bool multiByteGlyphs, + SkGlyphID firstGlyphID, + SkGlyphID lastGlyphID) { + SkDynamicMemoryWStream cmap; + append_tounicode_header(&cmap, multiByteGlyphs); + SkPDFAppendCmapSections(glyphToUnicode, subset, &cmap, multiByteGlyphs, + firstGlyphID, lastGlyphID); + append_cmap_footer(&cmap); + return cmap.detachAsStream(); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.h b/gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.h new file mode 100644 index 0000000000..b77f23de16 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFMakeToUnicodeCmap.h @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFMakeToUnicodeCmap_DEFINED +#define SkPDFMakeToUnicodeCmap_DEFINED + +#include "include/core/SkStream.h" +#include "src/pdf/SkPDFFont.h" + +std::unique_ptr SkPDFMakeToUnicodeCmap( + const SkUnichar* glyphToUnicode, + const SkPDFGlyphUse* subset, + bool multiByteGlyphs, + SkGlyphID firstGlyphID, + SkGlyphID lastGlyphID); + +// Exposed for unit testing. +void SkPDFAppendCmapSections(const SkUnichar* glyphToUnicode, + const SkPDFGlyphUse* subset, + SkDynamicMemoryWStream* cmap, + bool multiByteGlyphs, + SkGlyphID firstGlyphID, + SkGlyphID lastGlyphID); + +#endif // SkPDFMakeToUnicodeCmap_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFMetadata.cpp b/gfx/skia/skia/src/pdf/SkPDFMetadata.cpp new file mode 100644 index 0000000000..582fc1e5b7 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFMetadata.cpp @@ -0,0 +1,412 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFMetadata.h" + +#include "include/core/SkMilestone.h" +#include "include/private/SkTo.h" +#include "src/core/SkMD5.h" +#include "src/core/SkUtils.h" +#include "src/pdf/SkPDFTypes.h" + +#include + +#define SKPDF_STRING(X) SKPDF_STRING_IMPL(X) +#define SKPDF_STRING_IMPL(X) #X +#define SKPDF_PRODUCER "Skia/PDF m" SKPDF_STRING(SK_MILESTONE) +#define SKPDF_CUSTOM_PRODUCER_KEY "ProductionLibrary" + +static constexpr SkTime::DateTime kZeroTime = {0, 0, 0, 0, 0, 0, 0, 0}; + +static bool operator!=(const SkTime::DateTime& u, const SkTime::DateTime& v) { + return u.fTimeZoneMinutes != v.fTimeZoneMinutes || + u.fYear != v.fYear || + u.fMonth != v.fMonth || + u.fDayOfWeek != v.fDayOfWeek || + u.fDay != v.fDay || + u.fHour != v.fHour || + u.fMinute != v.fMinute || + u.fSecond != v.fSecond; +} + +static SkString pdf_date(const SkTime::DateTime& dt) { + int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); + char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; + int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; + timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; + return SkStringPrintf( + "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", + static_cast(dt.fYear), static_cast(dt.fMonth), + static_cast(dt.fDay), static_cast(dt.fHour), + static_cast(dt.fMinute), + static_cast(dt.fSecond), timezoneSign, timeZoneHours, + timeZoneMinutes); +} + +static bool utf8_is_pdfdocencoding(const char* src, size_t len) { + const uint8_t* end = (const uint8_t*)src + len; + for (const uint8_t* ptr = (const uint8_t*)src; ptr < end; ++ptr) { + uint8_t v = *ptr; + // See Table D.2 (PDFDocEncoding Character Set) in the PDF3200_2008 spec. + if ((v > 23 && v < 32) || v > 126) { + return false; + } + } + return true; +} + +void write_utf16be(char** ptr, uint16_t value) { + *(*ptr)++ = (value >> 8); + *(*ptr)++ = (value & 0xFF); +} + +// Please Note: This "abuses" the SkString, which "should" only hold UTF8. +// But the SkString is written as if it is really just a ref-counted array of +// chars, so this works, as long as we handle endiness and conversions ourselves. +// +// Input: UTF-8 +// Output UTF-16-BE +static SkString to_utf16be(const char* src, size_t len) { + SkString ret; + const char* const end = src + len; + size_t n = 1; // BOM + for (const char* ptr = src; ptr < end;) { + SkUnichar u = SkUTF::NextUTF8(&ptr, end); + if (u < 0) { + break; + } + n += SkUTF::ToUTF16(u); + } + ret.resize(2 * n); + char* out = ret.writable_str(); + write_utf16be(&out, 0xFEFF); // BOM + for (const char* ptr = src; ptr < end;) { + SkUnichar u = SkUTF::NextUTF8(&ptr, end); + if (u < 0) { + break; + } + uint16_t utf16[2]; + size_t l = SkUTF::ToUTF16(u, utf16); + write_utf16be(&out, utf16[0]); + if (l == 2) { + write_utf16be(&out, utf16[1]); + } + } + SkASSERT(out == ret.writable_str() + 2 * n); + return ret; +} + +// Input: UTF-8 +// Output UTF-16-BE OR PDFDocEncoding (if that encoding is identical to ASCII encoding). +// +// See sections 14.3.3 (Document Information Dictionary) and 7.9.2.2 (Text String Type) +// of the PDF32000_2008 spec. +static SkString convert(const SkString& s) { + return utf8_is_pdfdocencoding(s.c_str(), s.size()) ? s : to_utf16be(s.c_str(), s.size()); +} +static SkString convert(const char* src) { + size_t len = strlen(src); + return utf8_is_pdfdocencoding(src, len) ? SkString(src, len) : to_utf16be(src, len); +} + +namespace { +static const struct { + const char* const key; + SkString SkPDF::Metadata::*const valuePtr; +} gMetadataKeys[] = { + {"Title", &SkPDF::Metadata::fTitle}, + {"Author", &SkPDF::Metadata::fAuthor}, + {"Subject", &SkPDF::Metadata::fSubject}, + {"Keywords", &SkPDF::Metadata::fKeywords}, + {"Creator", &SkPDF::Metadata::fCreator}, +}; +} // namespace + +std::unique_ptr SkPDFMetadata::MakeDocumentInformationDict( + const SkPDF::Metadata& metadata) { + auto dict = SkPDFMakeDict(); + for (const auto keyValuePtr : gMetadataKeys) { + const SkString& value = metadata.*(keyValuePtr.valuePtr); + if (value.size() > 0) { + dict->insertString(keyValuePtr.key, convert(value)); + } + } + if (metadata.fProducer.isEmpty()) { + dict->insertString("Producer", convert(SKPDF_PRODUCER)); + } else { + dict->insertString("Producer", convert(metadata.fProducer)); + dict->insertString(SKPDF_CUSTOM_PRODUCER_KEY, convert(SKPDF_PRODUCER)); + } + if (metadata.fCreation != kZeroTime) { + dict->insertString("CreationDate", pdf_date(metadata.fCreation)); + } + if (metadata.fModified != kZeroTime) { + dict->insertString("ModDate", pdf_date(metadata.fModified)); + } + return dict; +} + +SkUUID SkPDFMetadata::CreateUUID(const SkPDF::Metadata& metadata) { + // The main requirement is for the UUID to be unique; the exact + // format of the data that will be hashed is not important. + SkMD5 md5; + const char uuidNamespace[] = "org.skia.pdf\n"; + md5.writeText(uuidNamespace); + double msec = SkTime::GetMSecs(); + md5.write(&msec, sizeof(msec)); + SkTime::DateTime dateTime; + SkTime::GetDateTime(&dateTime); + md5.write(&dateTime, sizeof(dateTime)); + md5.write(&metadata.fCreation, sizeof(metadata.fCreation)); + md5.write(&metadata.fModified, sizeof(metadata.fModified)); + + for (const auto keyValuePtr : gMetadataKeys) { + md5.writeText(keyValuePtr.key); + md5.write("\037", 1); + const SkString& value = metadata.*(keyValuePtr.valuePtr); + md5.write(value.c_str(), value.size()); + md5.write("\036", 1); + } + SkMD5::Digest digest = md5.finish(); + // See RFC 4122, page 6-7. + digest.data[6] = (digest.data[6] & 0x0F) | 0x30; + digest.data[8] = (digest.data[6] & 0x3F) | 0x80; + static_assert(sizeof(digest) == sizeof(SkUUID), "uuid_size"); + SkUUID uuid; + memcpy(&uuid, &digest, sizeof(digest)); + return uuid; +} + +std::unique_ptr SkPDFMetadata::MakePdfId(const SkUUID& doc, + const SkUUID& instance) { + // /ID [ <81b14aafa313db63dbd6f981e49f94f4> + // <81b14aafa313db63dbd6f981e49f94f4> ] + auto array = SkPDFMakeArray(); + static_assert(sizeof(SkUUID) == 16, "uuid_size"); + array->appendString( + SkString(reinterpret_cast(&doc), sizeof(SkUUID))); + array->appendString( + SkString(reinterpret_cast(&instance), sizeof(SkUUID))); + return array; +} + +// Convert a block of memory to hexadecimal. Input and output pointers will be +// moved to end of the range. +static void hexify(const uint8_t** inputPtr, char** outputPtr, int count) { + SkASSERT(inputPtr && *inputPtr); + SkASSERT(outputPtr && *outputPtr); + while (count-- > 0) { + uint8_t value = *(*inputPtr)++; + *(*outputPtr)++ = SkHexadecimalDigits::gLower[value >> 4]; + *(*outputPtr)++ = SkHexadecimalDigits::gLower[value & 0xF]; + } +} + +static SkString uuid_to_string(const SkUUID& uuid) { + // 8-4-4-4-12 + char buffer[36]; // [32 + 4] + char* ptr = buffer; + const uint8_t* data = uuid.fData; + hexify(&data, &ptr, 4); + *ptr++ = '-'; + hexify(&data, &ptr, 2); + *ptr++ = '-'; + hexify(&data, &ptr, 2); + *ptr++ = '-'; + hexify(&data, &ptr, 2); + *ptr++ = '-'; + hexify(&data, &ptr, 6); + SkASSERT(ptr == buffer + 36); + SkASSERT(data == uuid.fData + 16); + return SkString(buffer, 36); +} + +namespace { +class PDFXMLObject final : public SkPDFObject { +public: + PDFXMLObject(SkString xml) : fXML(std::move(xml)) {} + void emitObject(SkWStream* stream) const override { + SkPDFDict dict("Metadata"); + dict.insertName("Subtype", "XML"); + dict.insertInt("Length", fXML.size()); + dict.emitObject(stream); + static const char streamBegin[] = " stream\n"; + stream->writeText(streamBegin); + // Do not compress this. The standard requires that a + // program that does not understand PDF can grep for + // "write(fXML.c_str(), fXML.size()); + static const char streamEnd[] = "\nendstream"; + stream->writeText(streamEnd); + } + +private: + const SkString fXML; +}; +} // namespace + +static int count_xml_escape_size(const SkString& input) { + int extra = 0; + for (size_t i = 0; i < input.size(); ++i) { + if (input[i] == '&') { + extra += 4; // strlen("&") - strlen("&") + } else if (input[i] == '<') { + extra += 3; // strlen("<") - strlen("<") + } + } + return extra; +} + +const SkString escape_xml(const SkString& input, + const char* before = nullptr, + const char* after = nullptr) { + if (input.size() == 0) { + return input; + } + // "&" --> "&" and "<" --> "<" + // text is assumed to be in UTF-8 + // all strings are xml content, not attribute values. + size_t beforeLen = before ? strlen(before) : 0; + size_t afterLen = after ? strlen(after) : 0; + int extra = count_xml_escape_size(input); + SkString output(input.size() + extra + beforeLen + afterLen); + char* out = output.writable_str(); + if (before) { + strncpy(out, before, beforeLen); + out += beforeLen; + } + static const char kAmp[] = "&"; + static const char kLt[] = "<"; + for (size_t i = 0; i < input.size(); ++i) { + if (input[i] == '&') { + strncpy(out, kAmp, strlen(kAmp)); + out += strlen(kAmp); + } else if (input[i] == '<') { + strncpy(out, kLt, strlen(kLt)); + out += strlen(kLt); + } else { + *out++ = input[i]; + } + } + if (after) { + strncpy(out, after, afterLen); + out += afterLen; + } + // Validate that we haven't written outside of our string. + SkASSERT(out == &output.writable_str()[output.size()]); + *out = '\0'; + return output; +} + +SkPDFIndirectReference SkPDFMetadata::MakeXMPObject( + const SkPDF::Metadata& metadata, + const SkUUID& doc, + const SkUUID& instance, + SkPDFDocument* docPtr) { + static const char templateString[] = + "\n" + "\n" + "\n" + "\n" + "2\n" + "B\n" + "%s" // ModifyDate + "%s" // CreateDate + "%s" // xmp:CreatorTool + "application/pdf\n" + "%s" // dc:title + "%s" // dc:description + "%s" // author + "%s" // keywords + "uuid:%s\n" + "uuid:%s\n" + "%s" // pdf:Producer + "%s" // pdf:Keywords + "\n" + "\n" + "\n" // Note: the standard suggests 4k of padding. + "\n"; + + SkString creationDate; + SkString modificationDate; + if (metadata.fCreation != kZeroTime) { + SkString tmp; + metadata.fCreation.toISO8601(&tmp); + SkASSERT(0 == count_xml_escape_size(tmp)); + // YYYY-mm-ddTHH:MM:SS[+|-]ZZ:ZZ; no need to escape + creationDate = SkStringPrintf("%s\n", + tmp.c_str()); + } + if (metadata.fModified != kZeroTime) { + SkString tmp; + metadata.fModified.toISO8601(&tmp); + SkASSERT(0 == count_xml_escape_size(tmp)); + modificationDate = SkStringPrintf( + "%s\n", tmp.c_str()); + } + SkString title = + escape_xml(metadata.fTitle, + "", + "\n"); + SkString author = + escape_xml(metadata.fAuthor, "", + "\n"); + // TODO: in theory, XMP can support multiple authors. Split on a delimiter? + SkString subject = escape_xml( + metadata.fSubject, + "", + "\n"); + SkString keywords1 = + escape_xml(metadata.fKeywords, "", + "\n"); + SkString keywords2 = escape_xml(metadata.fKeywords, "", + "\n"); + // TODO: in theory, keywords can be a list too. + + SkString producer("" SKPDF_PRODUCER "\n"); + if (!metadata.fProducer.isEmpty()) { + // TODO: register a developer prefix to make + // a real XML tag. + producer = escape_xml( + metadata.fProducer, "", + "\n\n"); + } + + SkString creator = escape_xml(metadata.fCreator, "", + "\n"); + SkString documentID = uuid_to_string(doc); // no need to escape + SkASSERT(0 == count_xml_escape_size(documentID)); + SkString instanceID = uuid_to_string(instance); + SkASSERT(0 == count_xml_escape_size(instanceID)); + + + auto value = SkStringPrintf( + templateString, modificationDate.c_str(), creationDate.c_str(), + creator.c_str(), title.c_str(), subject.c_str(), author.c_str(), + keywords1.c_str(), documentID.c_str(), instanceID.c_str(), + producer.c_str(), keywords2.c_str()); + + std::unique_ptr dict = SkPDFMakeDict("Metadata"); + dict->insertName("Subtype", "XML"); + return SkPDFStreamOut(std::move(dict), + SkMemoryStream::MakeCopy(value.c_str(), value.size()), + docPtr, false); +} + +#undef SKPDF_CUSTOM_PRODUCER_KEY +#undef SKPDF_PRODUCER +#undef SKPDF_STRING +#undef SKPDF_STRING_IMPL diff --git a/gfx/skia/skia/src/pdf/SkPDFMetadata.h b/gfx/skia/skia/src/pdf/SkPDFMetadata.h new file mode 100644 index 0000000000..590caff735 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFMetadata.h @@ -0,0 +1,29 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPDFMetadata_DEFINED +#define SkPDFMetadata_DEFINED + +#include "include/docs/SkPDFDocument.h" +#include "src/pdf/SkPDFTypes.h" +#include "src/pdf/SkUUID.h" + +class SkPDFObject; + +namespace SkPDFMetadata { +std::unique_ptr MakeDocumentInformationDict(const SkPDF::Metadata&); + +SkUUID CreateUUID(const SkPDF::Metadata&); + +std::unique_ptr MakePdfId(const SkUUID& doc, const SkUUID& instance); + +SkPDFIndirectReference MakeXMPObject(const SkPDF::Metadata& metadata, + const SkUUID& doc, + const SkUUID& instance, + SkPDFDocument*); +} +#endif // SkPDFMetadata_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFResourceDict.cpp b/gfx/skia/skia/src/pdf/SkPDFResourceDict.cpp new file mode 100644 index 0000000000..c143f8d816 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFResourceDict.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "src/pdf/SkPDFResourceDict.h" +#include "src/pdf/SkPDFTypes.h" + +// Sanity check that the values of enum ResourceType correspond to the +// expected values as defined in the arrays below. +// If these are failing, you may need to update the kResourceTypePrefixes +// and kResourceTypeNames arrays below. +static_assert(0 == (int)SkPDFResourceType::kExtGState, "resource_type_mismatch"); +static_assert(1 == (int)SkPDFResourceType::kPattern, "resource_type_mismatch"); +static_assert(2 == (int)SkPDFResourceType::kXObject, "resource_type_mismatch"); +static_assert(3 == (int)SkPDFResourceType::kFont, "resource_type_mismatch"); + +// One extra character for the Prefix. +constexpr size_t kMaxResourceNameLength = 1 + SkStrAppendS32_MaxSize; + +// returns pointer just past end of what's written into `dst`. +static char* get_resource_name(char dst[kMaxResourceNameLength], SkPDFResourceType type, int key) { + static const char kResourceTypePrefixes[] = { + 'G', // kExtGState + 'P', // kPattern + 'X', // kXObject + 'F' // kFont + }; + SkASSERT((unsigned)type < SK_ARRAY_COUNT(kResourceTypePrefixes)); + dst[0] = kResourceTypePrefixes[(unsigned)type]; + return SkStrAppendS32(dst + 1, key); +} + +void SkPDFWriteResourceName(SkWStream* dst, SkPDFResourceType type, int key) { + // One extra character for the leading '/'. + char buffer[1 + kMaxResourceNameLength]; + buffer[0] = '/'; + char* end = get_resource_name(buffer + 1, type, key); + dst->write(buffer, (size_t)(end - buffer)); +} + +static const char* resource_name(SkPDFResourceType type) { + static const char* kResourceTypeNames[] = { + "ExtGState", + "Pattern", + "XObject", + "Font" + }; + SkASSERT((unsigned)type < SK_ARRAY_COUNT(kResourceTypeNames)); + return kResourceTypeNames[(unsigned)type]; +} + +static SkString resource(SkPDFResourceType type, int index) { + char buffer[kMaxResourceNameLength]; + char* end = get_resource_name(buffer, type, index); + return SkString(buffer, (size_t)(end - buffer)); +} + +static void add_subdict(const std::vector& resourceList, + SkPDFResourceType type, + SkPDFDict* dst) { + if (!resourceList.empty()) { + auto resources = SkPDFMakeDict(); + for (SkPDFIndirectReference ref : resourceList) { + resources->insertRef(resource(type, ref.fValue), ref); + } + dst->insertObject(resource_name(type), std::move(resources)); + } +} + +static std::unique_ptr make_proc_set() { + auto procSets = SkPDFMakeArray(); + static const char kProcs[][7] = { "PDF", "Text", "ImageB", "ImageC", "ImageI"}; + procSets->reserve(SK_ARRAY_COUNT(kProcs)); + for (const char* proc : kProcs) { + procSets->appendName(proc); + } + return procSets; +} + +std::unique_ptr SkPDFMakeResourceDict( + const std::vector& graphicStateResources, + const std::vector& shaderResources, + const std::vector& xObjectResources, + const std::vector& fontResources) { + auto dict = SkPDFMakeDict(); + dict->insertObject("ProcSet", make_proc_set()); + add_subdict(graphicStateResources, SkPDFResourceType::kExtGState, dict.get()); + add_subdict(shaderResources, SkPDFResourceType::kPattern, dict.get()); + add_subdict(xObjectResources, SkPDFResourceType::kXObject, dict.get()); + add_subdict(fontResources, SkPDFResourceType::kFont, dict.get()); + return dict; +} diff --git a/gfx/skia/skia/src/pdf/SkPDFResourceDict.h b/gfx/skia/skia/src/pdf/SkPDFResourceDict.h new file mode 100644 index 0000000000..4cd9dfa1c3 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFResourceDict.h @@ -0,0 +1,50 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPDFResourceDict_DEFINED +#define SkPDFResourceDict_DEFINED + +#include "src/pdf/SkPDFFont.h" + +#include + +class SkPDFDict; +class SkPDFObject; +class SkWStream; + +enum class SkPDFResourceType { + kExtGState = 0, + kPattern = 1, + kXObject = 2, + kFont = 3, + // These additional types are defined by the spec, but not + // currently used by Skia: ColorSpace, Shading, Properties +}; + + +/** Create a PDF resource dictionary. + * The full set of ProcSet entries is automatically created for backwards + * compatibility, as recommended by the PDF spec. + * + * Any arguments can be nullptr. + */ +std::unique_ptr SkPDFMakeResourceDict( + const std::vector& graphicStateResources, + const std::vector& shaderResources, + const std::vector& xObjectResources, + const std::vector& fontResources); + +/** + * Writes the name for the resource that will be generated by the resource + * dict. + * + * @param type The type of resource being entered + * @param key The resource key, should be unique within its type. + */ +void SkPDFWriteResourceName(SkWStream*, SkPDFResourceType type, int key); + +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFShader.cpp b/gfx/skia/skia/src/pdf/SkPDFShader.cpp new file mode 100644 index 0000000000..c08a76f734 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFShader.cpp @@ -0,0 +1,375 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFShader.h" + +#include "include/core/SkData.h" +#include "include/core/SkMath.h" +#include "include/core/SkScalar.h" +#include "include/core/SkStream.h" +#include "include/core/SkSurface.h" +#include "include/docs/SkPDFDocument.h" +#include "include/private/SkTemplates.h" +#include "src/pdf/SkPDFDevice.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFFormXObject.h" +#include "src/pdf/SkPDFGradientShader.h" +#include "src/pdf/SkPDFGraphicState.h" +#include "src/pdf/SkPDFResourceDict.h" +#include "src/pdf/SkPDFUtils.h" + +static void draw(SkCanvas* canvas, const SkImage* image, SkColor4f paintColor) { + SkPaint paint(paintColor); + canvas->drawImage(image, 0, 0, &paint); +} + +static SkBitmap to_bitmap(const SkImage* image) { + SkBitmap bitmap; + if (!SkPDFUtils::ToBitmap(image, &bitmap)) { + bitmap.allocN32Pixels(image->width(), image->height()); + bitmap.eraseColor(0x00000000); + } + return bitmap; +} + +static void draw_matrix(SkCanvas* canvas, const SkImage* image, + const SkMatrix& matrix, SkColor4f paintColor) { + SkAutoCanvasRestore acr(canvas, true); + canvas->concat(matrix); + draw(canvas, image, paintColor); +} + +static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm, + const SkMatrix& matrix, SkColor4f paintColor) { + SkAutoCanvasRestore acr(canvas, true); + canvas->concat(matrix); + SkPaint paint(paintColor); + canvas->drawBitmap(bm, 0, 0, &paint); +} + +static void fill_color_from_bitmap(SkCanvas* canvas, + float left, float top, float right, float bottom, + const SkBitmap& bitmap, int x, int y, float alpha) { + SkRect rect{left, top, right, bottom}; + if (!rect.isEmpty()) { + SkColor4f color = SkColor4f::FromColor(bitmap.getColor(x, y)); + SkPaint paint(SkColor4f{color.fR, color.fG, color.fB, alpha * color.fA}); + canvas->drawRect(rect, paint); + } +} + +static SkMatrix scale_translate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty) { + SkMatrix m; + m.setScaleTranslate(sx, sy, tx, ty); + return m; +} + +static bool is_tiled(SkTileMode m) { return SkTileMode::kMirror == m || SkTileMode::kRepeat == m; } + +static SkPDFIndirectReference make_image_shader(SkPDFDocument* doc, + SkMatrix finalMatrix, + SkTileMode tileModesX, + SkTileMode tileModesY, + SkRect bBox, + const SkImage* image, + SkColor4f paintColor) { + // The image shader pattern cell will be drawn into a separate device + // in pattern cell space (no scaling on the bitmap, though there may be + // translations so that all content is in the device, coordinates > 0). + + // Map clip bounds to shader space to ensure the device is large enough + // to handle fake clamping. + + SkRect deviceBounds = bBox; + if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) { + return SkPDFIndirectReference(); + } + + SkRect bitmapBounds = SkRect::MakeSize(SkSize::Make(image->dimensions())); + + // For tiling modes, the bounds should be extended to include the bitmap, + // otherwise the bitmap gets clipped out and the shader is empty and awful. + // For clamp modes, we're only interested in the clip region, whether + // or not the main bitmap is in it. + if (is_tiled(tileModesX) || is_tiled(tileModesY)) { + deviceBounds.join(bitmapBounds); + } + + SkISize patternDeviceSize = {SkScalarCeilToInt(deviceBounds.width()), + SkScalarCeilToInt(deviceBounds.height())}; + auto patternDevice = sk_make_sp(patternDeviceSize, doc); + SkCanvas canvas(patternDevice); + + SkRect patternBBox = SkRect::MakeSize(SkSize::Make(image->dimensions())); + SkScalar width = patternBBox.width(); + SkScalar height = patternBBox.height(); + + // Translate the canvas so that the bitmap origin is at (0, 0). + canvas.translate(-deviceBounds.left(), -deviceBounds.top()); + patternBBox.offset(-deviceBounds.left(), -deviceBounds.top()); + // Undo the translation in the final matrix + finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top()); + + // If the bitmap is out of bounds (i.e. clamp mode where we only see the + // stretched sides), canvas will clip this out and the extraneous data + // won't be saved to the PDF. + draw(&canvas, image, paintColor); + + // Tiling is implied. First we handle mirroring. + if (tileModesX == SkTileMode::kMirror) { + draw_matrix(&canvas, image, scale_translate(-1, 1, 2 * width, 0), paintColor); + patternBBox.fRight += width; + } + if (tileModesY == SkTileMode::kMirror) { + draw_matrix(&canvas, image, scale_translate(1, -1, 0, 2 * height), paintColor); + patternBBox.fBottom += height; + } + if (tileModesX == SkTileMode::kMirror && tileModesY == SkTileMode::kMirror) { + draw_matrix(&canvas, image, scale_translate(-1, -1, 2 * width, 2 * height), paintColor); + } + + // Then handle Clamping, which requires expanding the pattern canvas to + // cover the entire surfaceBBox. + + SkBitmap bitmap; + if (tileModesX == SkTileMode::kClamp || tileModesY == SkTileMode::kClamp) { + // For now, the easiest way to access the colors in the corners and sides is + // to just make a bitmap from the image. + bitmap = to_bitmap(image); + } + + // If both x and y are in clamp mode, we start by filling in the corners. + // (Which are just a rectangles of the corner colors.) + if (tileModesX == SkTileMode::kClamp && tileModesY == SkTileMode::kClamp) { + SkASSERT(!bitmap.drawsNothing()); + + fill_color_from_bitmap(&canvas, deviceBounds.left(), deviceBounds.top(), 0, 0, + bitmap, 0, 0, paintColor.fA); + + fill_color_from_bitmap(&canvas, width, deviceBounds.top(), deviceBounds.right(), 0, + bitmap, bitmap.width() - 1, 0, paintColor.fA); + + fill_color_from_bitmap(&canvas, width, height, deviceBounds.right(), deviceBounds.bottom(), + bitmap, bitmap.width() - 1, bitmap.height() - 1, paintColor.fA); + + fill_color_from_bitmap(&canvas, deviceBounds.left(), height, 0, deviceBounds.bottom(), + bitmap, 0, bitmap.height() - 1, paintColor.fA); + } + + // Then expand the left, right, top, then bottom. + if (tileModesX == SkTileMode::kClamp) { + SkASSERT(!bitmap.drawsNothing()); + SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height()); + if (deviceBounds.left() < 0) { + SkBitmap left; + SkAssertResult(bitmap.extractSubset(&left, subset)); + + SkMatrix leftMatrix = scale_translate(-deviceBounds.left(), 1, deviceBounds.left(), 0); + draw_bitmap_matrix(&canvas, left, leftMatrix, paintColor); + + if (tileModesY == SkTileMode::kMirror) { + leftMatrix.postScale(SK_Scalar1, -SK_Scalar1); + leftMatrix.postTranslate(0, 2 * height); + draw_bitmap_matrix(&canvas, left, leftMatrix, paintColor); + } + patternBBox.fLeft = 0; + } + + if (deviceBounds.right() > width) { + SkBitmap right; + subset.offset(bitmap.width() - 1, 0); + SkAssertResult(bitmap.extractSubset(&right, subset)); + + SkMatrix rightMatrix = scale_translate(deviceBounds.right() - width, 1, width, 0); + draw_bitmap_matrix(&canvas, right, rightMatrix, paintColor); + + if (tileModesY == SkTileMode::kMirror) { + rightMatrix.postScale(SK_Scalar1, -SK_Scalar1); + rightMatrix.postTranslate(0, 2 * height); + draw_bitmap_matrix(&canvas, right, rightMatrix, paintColor); + } + patternBBox.fRight = deviceBounds.width(); + } + } + if (tileModesX == SkTileMode::kDecal) { + if (deviceBounds.left() < 0) { + patternBBox.fLeft = 0; + } + if (deviceBounds.right() > width) { + patternBBox.fRight = deviceBounds.width(); + } + } + + if (tileModesY == SkTileMode::kClamp) { + SkASSERT(!bitmap.drawsNothing()); + SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1); + if (deviceBounds.top() < 0) { + SkBitmap top; + SkAssertResult(bitmap.extractSubset(&top, subset)); + + SkMatrix topMatrix = scale_translate(1, -deviceBounds.top(), 0, deviceBounds.top()); + draw_bitmap_matrix(&canvas, top, topMatrix, paintColor); + + if (tileModesX == SkTileMode::kMirror) { + topMatrix.postScale(-1, 1); + topMatrix.postTranslate(2 * width, 0); + draw_bitmap_matrix(&canvas, top, topMatrix, paintColor); + } + patternBBox.fTop = 0; + } + + if (deviceBounds.bottom() > height) { + SkBitmap bottom; + subset.offset(0, bitmap.height() - 1); + SkAssertResult(bitmap.extractSubset(&bottom, subset)); + + SkMatrix bottomMatrix = scale_translate(1, deviceBounds.bottom() - height, 0, height); + draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paintColor); + + if (tileModesX == SkTileMode::kMirror) { + bottomMatrix.postScale(-1, 1); + bottomMatrix.postTranslate(2 * width, 0); + draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paintColor); + } + patternBBox.fBottom = deviceBounds.height(); + } + } + if (tileModesY == SkTileMode::kDecal) { + if (deviceBounds.top() < 0) { + patternBBox.fTop = 0; + } + if (deviceBounds.bottom() > height) { + patternBBox.fBottom = deviceBounds.height(); + } + } + + auto imageShader = patternDevice->content(); + std::unique_ptr resourceDict = patternDevice->makeResourceDict(); + std::unique_ptr dict = SkPDFMakeDict(); + SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox, + std::move(resourceDict), finalMatrix); + return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc); +} + +// Generic fallback for unsupported shaders: +// * allocate a surfaceBBox-sized bitmap +// * shade the whole area +// * use the result as a bitmap shader +static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc, + SkShader* shader, + const SkMatrix& canvasTransform, + const SkIRect& surfaceBBox, + SkColor4f paintColor) { + // TODO(vandebo) This drops SKComposeShader on the floor. We could + // handle compose shader by pulling things up to a layer, drawing with + // the first shader, applying the xfer mode and drawing again with the + // second shader, then applying the layer to the original drawing. + + SkMatrix shaderTransform = as_SB(shader)->getLocalMatrix(); + + // surfaceBBox is in device space. While that's exactly what we + // want for sizing our bitmap, we need to map it into + // shader space for adjustments (to match + // MakeImageShader's behavior). + SkRect shaderRect = SkRect::Make(surfaceBBox); + if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) { + return SkPDFIndirectReference(); + } + // Clamp the bitmap size to about 1M pixels + static const int kMaxBitmapArea = 1024 * 1024; + SkScalar bitmapArea = (float)surfaceBBox.width() * (float)surfaceBBox.height(); + SkScalar rasterScale = 1.0f; + if (bitmapArea > (float)kMaxBitmapArea) { + rasterScale *= SkScalarSqrt((float)kMaxBitmapArea / bitmapArea); + } + + SkISize size = { + SkTClamp(SkScalarCeilToInt(rasterScale * surfaceBBox.width()), 1, kMaxBitmapArea), + SkTClamp(SkScalarCeilToInt(rasterScale * surfaceBBox.height()), 1, kMaxBitmapArea)}; + SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(), + SkIntToScalar(size.height()) / shaderRect.height()}; + + auto surface = SkSurface::MakeRasterN32Premul(size.width(), size.height()); + SkASSERT(surface); + SkCanvas* canvas = surface->getCanvas(); + canvas->clear(SK_ColorTRANSPARENT); + + SkPaint p(paintColor); + p.setShader(sk_ref_sp(shader)); + + canvas->scale(scale.width(), scale.height()); + canvas->translate(-shaderRect.x(), -shaderRect.y()); + canvas->drawPaint(p); + + shaderTransform.setTranslate(shaderRect.x(), shaderRect.y()); + shaderTransform.preScale(1 / scale.width(), 1 / scale.height()); + + sk_sp image = surface->makeImageSnapshot(); + SkASSERT(image); + return make_image_shader(doc, + SkMatrix::Concat(canvasTransform, shaderTransform), + SkTileMode::kClamp, SkTileMode::kClamp, + SkRect::Make(surfaceBBox), + image.get(), + paintColor); +} + +static SkColor4f adjust_color(SkShader* shader, SkColor4f paintColor) { + if (SkImage* img = shader->isAImage(nullptr, (SkTileMode*)nullptr)) { + if (img->isAlphaOnly()) { + return paintColor; + } + } + return SkColor4f{0, 0, 0, paintColor.fA}; // only preserve the alpha. +} + +SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc, + SkShader* shader, + const SkMatrix& canvasTransform, + const SkIRect& surfaceBBox, + SkColor4f paintColor) { + SkASSERT(shader); + SkASSERT(doc); + if (SkShader::kNone_GradientType != shader->asAGradient(nullptr)) { + return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox); + } + if (surfaceBBox.isEmpty()) { + return SkPDFIndirectReference(); + } + SkBitmap image; + + SkASSERT(shader->asAGradient(nullptr) == SkShader::kNone_GradientType) ; + + paintColor = adjust_color(shader, paintColor); + SkMatrix shaderTransform; + SkTileMode imageTileModes[2]; + if (SkImage* skimg = shader->isAImage(&shaderTransform, imageTileModes)) { + SkMatrix finalMatrix = SkMatrix::Concat(canvasTransform, shaderTransform); + SkPDFImageShaderKey key = { + finalMatrix, + surfaceBBox, + SkBitmapKeyFromImage(skimg), + {imageTileModes[0], imageTileModes[1]}, + paintColor}; + SkPDFIndirectReference* shaderPtr = doc->fImageShaderMap.find(key); + if (shaderPtr) { + return *shaderPtr; + } + SkPDFIndirectReference pdfShader = + make_image_shader(doc, + finalMatrix, + imageTileModes[0], + imageTileModes[1], + SkRect::Make(surfaceBBox), + skimg, + paintColor); + doc->fImageShaderMap.set(std::move(key), pdfShader); + return pdfShader; + } + // Don't bother to de-dup fallback shader. + return make_fallback_shader(doc, shader, canvasTransform, surfaceBBox, paintColor); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFShader.h b/gfx/skia/skia/src/pdf/SkPDFShader.h new file mode 100644 index 0000000000..ee270ea3af --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFShader.h @@ -0,0 +1,67 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkPDFShader_DEFINED +#define SkPDFShader_DEFINED + +#include "include/core/SkShader.h" +#include "include/private/SkMacros.h" +#include "src/pdf/SkBitmapKey.h" +#include "src/pdf/SkPDFTypes.h" + + +class SkPDFDocument; +class SkMatrix; +struct SkIRect; + +/** Make a PDF shader for the passed SkShader. If the SkShader is invalid in + * some way, returns nullptr. + * + * In PDF parlance, this is a pattern, used in place of a color when the + * pattern color space is selected. + * + * May cache the shader in the document for later re-use. If this function is + * called again with an equivalent shader, a new reference to the cached pdf + * shader may be returned. + * + * @param doc The parent document, must be non-null. + * @param shader The SkShader to emulate. + * @param ctm The current transform matrix. (PDF shaders are absolutely + * positioned, relative to where the page is drawn.) + * @param surfaceBBox The bounding box of the drawing surface (with matrix + * already applied). + * @param paintColor Color+Alpha of the paint. Color is usually ignored, + * unless it is a alpha shader. + */ +SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc, + SkShader* shader, + const SkMatrix& ctm, + const SkIRect& surfaceBBox, + SkColor4f paintColor); + +SK_BEGIN_REQUIRE_DENSE +struct SkPDFImageShaderKey { + SkMatrix fTransform; + SkIRect fBBox; + SkBitmapKey fBitmapKey; + SkTileMode fImageTileModes[2]; + SkColor4f fPaintColor; +}; +SK_END_REQUIRE_DENSE + +inline bool operator==(const SkPDFImageShaderKey& a, const SkPDFImageShaderKey& b) { + SkASSERT(a.fBitmapKey.fID != 0); + SkASSERT(b.fBitmapKey.fID != 0); + return a.fTransform == b.fTransform + && a.fBBox == b.fBBox + && a.fBitmapKey == b.fBitmapKey + && a.fImageTileModes[0] == b.fImageTileModes[0] + && a.fImageTileModes[1] == b.fImageTileModes[1] + && a.fPaintColor == b.fPaintColor; +} +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFSubsetFont.cpp b/gfx/skia/skia/src/pdf/SkPDFSubsetFont.cpp new file mode 100644 index 0000000000..df2999e67c --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFSubsetFont.cpp @@ -0,0 +1,181 @@ +// Copyright 2018 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +#include "src/pdf/SkPDFSubsetFont.h" + +#if defined(SK_USING_THIRD_PARTY_ICU) +#include "SkLoadICU.h" +#endif + +#if defined(SK_PDF_USE_HARFBUZZ_SUBSET) + +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/utils/SkCallableTraits.h" + +#include "hb.h" +#include "hb-subset.h" + +template using resource = + std::unique_ptr, P>>; +using HBBlob = resource; +using HBFace = resource; +using HBSubsetInput = resource; +using HBSet = resource; + +static HBBlob to_blob(sk_sp data) { + using blob_size_t = SkCallableTraits::argument<1>::type; + if (!SkTFitsIn(data->size())) { + return nullptr; + } + const char* blobData = static_cast(data->data()); + blob_size_t blobSize = SkTo(data->size()); + return HBBlob(hb_blob_create(blobData, blobSize, + HB_MEMORY_MODE_READONLY, + data.release(), [](void* p){ ((SkData*)p)->unref(); })); +} + +static sk_sp to_data(HBBlob blob) { + if (!blob) { + return nullptr; + } + unsigned int length; + const char* data = hb_blob_get_data(blob.get(), &length); + if (!data || !length) { + return nullptr; + } + return SkData::MakeWithProc(data, SkToSizeT(length), + [](const void*, void* ctx) { hb_blob_destroy((hb_blob_t*)ctx); }, + blob.release()); +} + +static sk_sp subset_harfbuzz(sk_sp fontData, + const SkPDFGlyphUse& glyphUsage, + int ttcIndex) { +#if defined(SK_USING_THIRD_PARTY_ICU) + if (!SkLoadICU()) { + return nullptr; + } +#endif + if (!fontData) { + return nullptr; + } + HBFace face(hb_face_create(to_blob(std::move(fontData)).get(), ttcIndex)); + SkASSERT(face); + + HBSubsetInput input(hb_subset_input_create_or_fail()); + SkASSERT(input); + if (!face || !input) { + return nullptr; + } + hb_set_t* glyphs = hb_subset_input_glyph_set(input.get()); + glyphUsage.getSetValues([&glyphs](unsigned gid) { hb_set_add(glyphs, gid);}); + + hb_subset_input_set_retain_gids(input.get(), true); + // TODO: When possible, check if a font is 'tricky' with FT_IS_TRICKY. + // If it isn't known if a font is 'tricky', retain the hints. + hb_subset_input_set_drop_hints(input.get(), false); + HBFace subset(hb_subset(face.get(), input.get())); + HBBlob result(hb_face_reference_blob(subset.get())); + return to_data(std::move(result)); +} + +#endif // defined(SK_PDF_USE_HARFBUZZ_SUBSET) + +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SK_PDF_USE_SFNTLY) + +#include "sample/chromium/font_subsetter.h" +#include + +static sk_sp subset_sfntly(sk_sp fontData, + const SkPDFGlyphUse& glyphUsage, + const char* fontName, + int ttcIndex) { +#if defined(SK_USING_THIRD_PARTY_ICU) + if (!SkLoadICU()) { + return nullptr; + } +#endif + // Generate glyph id array in format needed by sfntly. + // TODO(halcanary): sfntly should take a more compact format. + std::vector subset; + glyphUsage.getSetValues([&subset](unsigned v) { subset.push_back(v); }); + + unsigned char* subsetFont{nullptr}; +#if defined(SK_BUILD_FOR_GOOGLE3) + // TODO(halcanary): update SK_BUILD_FOR_GOOGLE3 to newest version of Sfntly. + (void)ttcIndex; + int subsetFontSize = SfntlyWrapper::SubsetFont(fontName, + fontData->bytes(), + fontData->size(), + subset.data(), + subset.size(), + &subsetFont); +#else // defined(SK_BUILD_FOR_GOOGLE3) + (void)fontName; + int subsetFontSize = SfntlyWrapper::SubsetFont(ttcIndex, + fontData->bytes(), + fontData->size(), + subset.data(), + subset.size(), + &subsetFont); +#endif // defined(SK_BUILD_FOR_GOOGLE3) + SkASSERT(subsetFontSize > 0 || subsetFont == nullptr); + if (subsetFontSize < 1 || subsetFont == nullptr) { + return nullptr; + } + return SkData::MakeWithProc(subsetFont, subsetFontSize, + [](const void* p, void*) { delete[] (unsigned char*)p; }, + nullptr); +} + +#endif // defined(SK_PDF_USE_SFNTLY) + +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SK_PDF_USE_SFNTLY) && defined(SK_PDF_USE_HARFBUZZ_SUBSET) + +sk_sp SkPDFSubsetFont(sk_sp fontData, + const SkPDFGlyphUse& glyphUsage, + SkPDF::Metadata::Subsetter subsetter, + const char* fontName, + int ttcIndex) { + switch (subsetter) { + case SkPDF::Metadata::kHarfbuzz_Subsetter: + return subset_harfbuzz(std::move(fontData), glyphUsage, ttcIndex); + case SkPDF::Metadata::kSfntly_Subsetter: + return subset_sfntly(std::move(fontData), glyphUsage, fontName, ttcIndex); + } + return nullptr; +} + +#elif defined(SK_PDF_USE_SFNTLY) + +sk_sp SkPDFSubsetFont(sk_sp fontData, + const SkPDFGlyphUse& glyphUsage, + SkPDF::Metadata::Subsetter, + const char* fontName, + int ttcIndex) { + return subset_sfntly(std::move(fontData), glyphUsage, fontName, ttcIndex); +} + +#elif defined(SK_PDF_USE_HARFBUZZ_SUBSET) + +sk_sp SkPDFSubsetFont(sk_sp fontData, + const SkPDFGlyphUse& glyphUsage, + SkPDF::Metadata::Subsetter, + const char*, + int ttcIndex) { + return subset_harfbuzz(std::move(fontData), glyphUsage, ttcIndex); +} + +#else + +sk_sp SkPDFSubsetFont(sk_sp, const SkPDFGlyphUse&, SkPDF::Metadata::Subsetter, + const char*, int) { + return nullptr; +} +#endif // defined(SK_PDF_USE_SFNTLY) + diff --git a/gfx/skia/skia/src/pdf/SkPDFSubsetFont.h b/gfx/skia/skia/src/pdf/SkPDFSubsetFont.h new file mode 100644 index 0000000000..b812c52ff5 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFSubsetFont.h @@ -0,0 +1,16 @@ +// Copyright 2018 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkPDFSubsetFont_DEFINED +#define SkPDFSubsetFont_DEFINED + +#include "include/core/SkData.h" +#include "include/docs/SkPDFDocument.h" +#include "src/pdf/SkPDFGlyphUse.h" + +sk_sp SkPDFSubsetFont(sk_sp fontData, + const SkPDFGlyphUse& glyphUsage, + SkPDF::Metadata::Subsetter subsetter, + const char* fontName, + int ttcIndex); + +#endif // SkPDFSubsetFont_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFTag.cpp b/gfx/skia/skia/src/pdf/SkPDFTag.cpp new file mode 100644 index 0000000000..a2248070e8 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFTag.cpp @@ -0,0 +1,226 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFTag.h" + +// Table 333 in PDF 32000-1:2008 +static const char* tag_name_from_type(SkPDF::DocumentStructureType type) { + switch (type) { + #define M(X) case SkPDF::DocumentStructureType::k ## X: return #X + M(Document); + M(Part); + M(Art); + M(Sect); + M(Div); + M(BlockQuote); + M(Caption); + M(TOC); + M(TOCI); + M(Index); + M(NonStruct); + M(Private); + M(H); + M(H1); + M(H2); + M(H3); + M(H4); + M(H5); + M(H6); + M(P); + M(L); + M(LI); + M(Lbl); + M(LBody); + M(Table); + M(TR); + M(TH); + M(TD); + M(THead); + M(TBody); + M(TFoot); + M(Span); + M(Quote); + M(Note); + M(Reference); + M(BibEntry); + M(Code); + M(Link); + M(Annot); + M(Ruby); + M(RB); + M(RT); + M(RP); + M(Warichu); + M(WT); + M(WP); + M(Figure); + M(Formula); + M(Form); + #undef M + } + SK_ABORT("bad tag"); +} + +struct SkPDFTagNode { + SkPDFTagNode* fChildren = nullptr; + size_t fChildCount = 0; + struct MarkedContentInfo { + unsigned fPageIndex; + int fMarkId; + }; + SkTArray fMarkedContent; + int fNodeId; + SkPDF::DocumentStructureType fType; + SkPDFIndirectReference fRef; + enum State { + kUnknown, + kYes, + kNo, + } fCanDiscard = kUnknown; +}; + +SkPDFTagTree::SkPDFTagTree() : fArena(4 * sizeof(SkPDFTagNode)) {} + +SkPDFTagTree::~SkPDFTagTree() = default; + +static void copy(const SkPDF::StructureElementNode& node, + SkPDFTagNode* dst, + SkArenaAlloc* arena, + SkTHashMap* nodeMap) { + nodeMap->set(node.fNodeId, dst); + size_t childCount = node.fChildCount; + SkPDFTagNode* children = arena->makeArray(childCount); + dst->fChildCount = childCount; + dst->fNodeId = node.fNodeId; + dst->fType = node.fType; + dst->fChildren = children; + for (size_t i = 0; i < childCount; ++i) { + copy(node.fChildren[i], &children[i], arena, nodeMap); + } +} + +void SkPDFTagTree::init(const SkPDF::StructureElementNode* node) { + if (node) { + fRoot = fArena.make(); + copy(*node, fRoot, &fArena, &fNodeMap); + } +} + +void SkPDFTagTree::reset() { + fArena.reset(); + fNodeMap.reset(); + fMarksPerPage.reset(); + fRoot = nullptr; +} + +int SkPDFTagTree::getMarkIdForNodeId(int nodeId, unsigned pageIndex) { + if (!fRoot) { + return -1; + } + SkPDFTagNode** tagPtr = fNodeMap.find(nodeId); + if (!tagPtr) { + return -1; + } + SkPDFTagNode* tag = *tagPtr; + SkASSERT(tag); + while (fMarksPerPage.size() < pageIndex + 1) { + fMarksPerPage.push_back(); + } + SkTArray& pageMarks = fMarksPerPage[pageIndex]; + int markId = pageMarks.count(); + tag->fMarkedContent.push_back({pageIndex, markId}); + pageMarks.push_back(tag); + return markId; +} + +static bool can_discard(SkPDFTagNode* node) { + if (node->fCanDiscard == SkPDFTagNode::kYes) { + return true; + } + if (node->fCanDiscard == SkPDFTagNode::kNo) { + return false; + } + if (!node->fMarkedContent.empty()) { + node->fCanDiscard = SkPDFTagNode::kNo; + return false; + } + for (size_t i = 0; i < node->fChildCount; ++i) { + if (!can_discard(&node->fChildren[i])) { + node->fCanDiscard = SkPDFTagNode::kNo; + return false; + } + } + node->fCanDiscard = SkPDFTagNode::kYes; + return true; +} + + +SkPDFIndirectReference prepare_tag_tree_to_emit(SkPDFIndirectReference parent, + SkPDFTagNode* node, + SkPDFDocument* doc) { + SkPDFIndirectReference ref = doc->reserveRef(); + std::unique_ptr kids = SkPDFMakeArray(); + SkPDFTagNode* children = node->fChildren; + size_t childCount = node->fChildCount; + for (size_t i = 0; i < childCount; ++i) { + SkPDFTagNode* child = &children[i]; + if (!(can_discard(child))) { + kids->appendRef(prepare_tag_tree_to_emit(ref, child, doc)); + } + } + for (const SkPDFTagNode::MarkedContentInfo& info : node->fMarkedContent) { + std::unique_ptr mcr = SkPDFMakeDict("MCR"); + mcr->insertRef("Pg", doc->getPage(info.fPageIndex)); + mcr->insertInt("MCID", info.fMarkId); + kids->appendObject(std::move(mcr)); + } + node->fRef = ref; + SkPDFDict dict("StructElem"); + dict.insertName("S", tag_name_from_type(node->fType)); + dict.insertRef("P", parent); + dict.insertObject("K", std::move(kids)); + return doc->emit(dict, ref); +} + +SkPDFIndirectReference SkPDFTagTree::makeStructTreeRoot(SkPDFDocument* doc) { + if (!fRoot) { + return SkPDFIndirectReference(); + } + if (can_discard(fRoot)) { + SkDEBUGFAIL("PDF has tag tree but no marked content."); + } + SkPDFIndirectReference ref = doc->reserveRef(); + + unsigned pageCount = SkToUInt(doc->pageCount()); + + // Build the StructTreeRoot. + SkPDFDict structTreeRoot("StructTreeRoot"); + structTreeRoot.insertRef("K", prepare_tag_tree_to_emit(ref, fRoot, doc)); + structTreeRoot.insertInt("ParentTreeNextKey", SkToInt(pageCount)); + + // Build the parent tree, which is a mapping from the marked + // content IDs on each page to their corressponding tags. + SkPDFDict parentTree("ParentTree"); + auto parentTreeNums = SkPDFMakeArray(); + + SkASSERT(fMarksPerPage.size() <= pageCount); + for (size_t j = 0; j < fMarksPerPage.size(); ++j) { + const SkTArray& pageMarks = fMarksPerPage[j]; + SkPDFArray markToTagArray; + for (SkPDFTagNode* mark : pageMarks) { + SkASSERT(mark->fRef); + markToTagArray.appendRef(mark->fRef); + } + parentTreeNums->appendInt(j); + parentTreeNums->appendRef(doc->emit(markToTagArray)); + } + parentTree.insertObject("Nums", std::move(parentTreeNums)); + structTreeRoot.insertRef("ParentTree", doc->emit(parentTree)); + return doc->emit(structTreeRoot, ref); +} + diff --git a/gfx/skia/skia/src/pdf/SkPDFTag.h b/gfx/skia/skia/src/pdf/SkPDFTag.h new file mode 100644 index 0000000000..d0718d381e --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFTag.h @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPDFTag_DEFINED +#define SkPDFTag_DEFINED + +#include "include/docs/SkPDFDocument.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTHash.h" +#include "src/core/SkArenaAlloc.h" + +class SkPDFDocument; +struct SkPDFIndirectReference; +struct SkPDFTagNode; + +class SkPDFTagTree { +public: + SkPDFTagTree(); + ~SkPDFTagTree(); + void init(const SkPDF::StructureElementNode*); + void reset(); + int getMarkIdForNodeId(int nodeId, unsigned pageIndex); + SkPDFIndirectReference makeStructTreeRoot(SkPDFDocument* doc); + +private: + SkArenaAlloc fArena; + SkTHashMap fNodeMap; + SkPDFTagNode* fRoot = nullptr; + SkTArray> fMarksPerPage; + + SkPDFTagTree(const SkPDFTagTree&) = delete; + SkPDFTagTree& operator=(const SkPDFTagTree&) = delete; +}; + +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFType1Font.cpp b/gfx/skia/skia/src/pdf/SkPDFType1Font.cpp new file mode 100644 index 0000000000..5bcb3f8e5a --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFType1Font.cpp @@ -0,0 +1,335 @@ +// Copyright 2019 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +#include "src/pdf/SkPDFType1Font.h" + +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkStrikeSpec.h" + +#include + +/* + "A standard Type 1 font program, as described in the Adobe Type 1 + Font Format specification, consists of three parts: a clear-text + portion (written using PostScript syntax), an encrypted portion, and + a fixed-content portion. The fixed-content portion contains 512 + ASCII zeros followed by a cleartomark operator, and perhaps followed + by additional data. Although the encrypted portion of a standard + Type 1 font may be in binary or ASCII hexadecimal format, PDF + supports only the binary format." +*/ +static bool parsePFBSection(const uint8_t** src, size_t* len, int sectionType, + size_t* size) { + // PFB sections have a two or six bytes header. 0x80 and a one byte + // section type followed by a four byte section length. Type one is + // an ASCII section (includes a length), type two is a binary section + // (includes a length) and type three is an EOF marker with no length. + const uint8_t* buf = *src; + if (*len < 2 || buf[0] != 0x80 || buf[1] != sectionType) { + return false; + } else if (buf[1] == 3) { + return true; + } else if (*len < 6) { + return false; + } + + *size = (size_t)buf[2] | ((size_t)buf[3] << 8) | ((size_t)buf[4] << 16) | + ((size_t)buf[5] << 24); + size_t consumed = *size + 6; + if (consumed > *len) { + return false; + } + *src = *src + consumed; + *len = *len - consumed; + return true; +} + +static bool parsePFB(const uint8_t* src, size_t size, size_t* headerLen, + size_t* dataLen, size_t* trailerLen) { + const uint8_t* srcPtr = src; + size_t remaining = size; + + return parsePFBSection(&srcPtr, &remaining, 1, headerLen) && + parsePFBSection(&srcPtr, &remaining, 2, dataLen) && + parsePFBSection(&srcPtr, &remaining, 1, trailerLen) && + parsePFBSection(&srcPtr, &remaining, 3, nullptr); +} + +/* The sections of a PFA file are implicitly defined. The body starts + * after the line containing "eexec," and the trailer starts with 512 + * literal 0's followed by "cleartomark" (plus arbitrary white space). + * + * This function assumes that src is NUL terminated, but the NUL + * termination is not included in size. + * + */ +static bool parsePFA(const char* src, size_t size, size_t* headerLen, + size_t* hexDataLen, size_t* dataLen, size_t* trailerLen) { + const char* end = src + size; + + const char* dataPos = strstr(src, "eexec"); + if (!dataPos) { + return false; + } + dataPos += strlen("eexec"); + while ((*dataPos == '\n' || *dataPos == '\r' || *dataPos == ' ') && + dataPos < end) { + dataPos++; + } + *headerLen = dataPos - src; + + const char* trailerPos = strstr(dataPos, "cleartomark"); + if (!trailerPos) { + return false; + } + int zeroCount = 0; + for (trailerPos--; trailerPos > dataPos && zeroCount < 512; trailerPos--) { + if (*trailerPos == '\n' || *trailerPos == '\r' || *trailerPos == ' ') { + continue; + } else if (*trailerPos == '0') { + zeroCount++; + } else { + return false; + } + } + if (zeroCount != 512) { + return false; + } + + *hexDataLen = trailerPos - src - *headerLen; + *trailerLen = size - *headerLen - *hexDataLen; + + // Verify that the data section is hex encoded and count the bytes. + int nibbles = 0; + for (; dataPos < trailerPos; dataPos++) { + if (isspace(*dataPos)) { + continue; + } + // isxdigit() is locale-sensitive https://bugs.skia.org/8285 + if (nullptr == strchr("0123456789abcdefABCDEF", *dataPos)) { + return false; + } + nibbles++; + } + *dataLen = (nibbles + 1) / 2; + + return true; +} + +static int8_t hexToBin(uint8_t c) { + if (!isxdigit(c)) { + return -1; + } else if (c <= '9') { + return c - '0'; + } else if (c <= 'F') { + return c - 'A' + 10; + } else if (c <= 'f') { + return c - 'a' + 10; + } + return -1; +} + +static sk_sp convert_type1_font_stream(std::unique_ptr srcStream, + size_t* headerLen, + size_t* dataLen, + size_t* trailerLen) { + size_t srcLen = srcStream ? srcStream->getLength() : 0; + SkASSERT(srcLen); + if (!srcLen) { + return nullptr; + } + // Flatten and Nul-terminate the source stream so that we can use + // strstr() to search it. + SkAutoTMalloc sourceBuffer(SkToInt(srcLen + 1)); + (void)srcStream->read(sourceBuffer.get(), srcLen); + sourceBuffer[SkToInt(srcLen)] = 0; + const uint8_t* src = sourceBuffer.get(); + + if (parsePFB(src, srcLen, headerLen, dataLen, trailerLen)) { + static const int kPFBSectionHeaderLength = 6; + const size_t length = *headerLen + *dataLen + *trailerLen; + SkASSERT(length > 0); + SkASSERT(length + (2 * kPFBSectionHeaderLength) <= srcLen); + + sk_sp data(SkData::MakeUninitialized(length)); + + const uint8_t* const srcHeader = src + kPFBSectionHeaderLength; + // There is a six-byte section header before header and data + // (but not trailer) that we're not going to copy. + const uint8_t* const srcData = srcHeader + *headerLen + kPFBSectionHeaderLength; + const uint8_t* const srcTrailer = srcData + *headerLen; + + uint8_t* const resultHeader = (uint8_t*)data->writable_data(); + uint8_t* const resultData = resultHeader + *headerLen; + uint8_t* const resultTrailer = resultData + *dataLen; + + SkASSERT(resultTrailer + *trailerLen == resultHeader + length); + + memcpy(resultHeader, srcHeader, *headerLen); + memcpy(resultData, srcData, *dataLen); + memcpy(resultTrailer, srcTrailer, *trailerLen); + + return data; + } + + // A PFA has to be converted for PDF. + size_t hexDataLen; + if (!parsePFA((const char*)src, srcLen, headerLen, &hexDataLen, dataLen, + trailerLen)) { + return nullptr; + } + const size_t length = *headerLen + *dataLen + *trailerLen; + SkASSERT(length > 0); + auto data = SkData::MakeUninitialized(length); + uint8_t* buffer = (uint8_t*)data->writable_data(); + + memcpy(buffer, src, *headerLen); + uint8_t* const resultData = &(buffer[*headerLen]); + + const uint8_t* hexData = src + *headerLen; + const uint8_t* trailer = hexData + hexDataLen; + size_t outputOffset = 0; + uint8_t dataByte = 0; // To hush compiler. + bool highNibble = true; + for (; hexData < trailer; hexData++) { + int8_t curNibble = hexToBin(*hexData); + if (curNibble < 0) { + continue; + } + if (highNibble) { + dataByte = curNibble << 4; + highNibble = false; + } else { + dataByte |= curNibble; + highNibble = true; + resultData[outputOffset++] = dataByte; + } + } + if (!highNibble) { + resultData[outputOffset++] = dataByte; + } + SkASSERT(outputOffset == *dataLen); + + uint8_t* const resultTrailer = &(buffer[SkToInt(*headerLen + outputOffset)]); + memcpy(resultTrailer, src + *headerLen + hexDataLen, *trailerLen); + return data; +} + +inline static bool can_embed(const SkAdvancedTypefaceMetrics& metrics) { + return !SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag); +} + +inline static SkScalar from_font_units(SkScalar scaled, uint16_t emSize) { + return emSize == 1000 ? scaled : scaled * 1000 / emSize; +} + +static SkPDFIndirectReference make_type1_font_descriptor(SkPDFDocument* doc, + const SkTypeface* typeface, + const SkAdvancedTypefaceMetrics* info) { + SkPDFDict descriptor("FontDescriptor"); + uint16_t emSize = SkToU16(typeface->getUnitsPerEm()); + if (info) { + SkPDFFont::PopulateCommonFontDescriptor(&descriptor, *info, emSize, 0); + if (can_embed(*info)) { + int ttcIndex; + size_t header SK_INIT_TO_AVOID_WARNING; + size_t data SK_INIT_TO_AVOID_WARNING; + size_t trailer SK_INIT_TO_AVOID_WARNING; + std::unique_ptr rawFontData = typeface->openStream(&ttcIndex); + sk_sp fontData = convert_type1_font_stream(std::move(rawFontData), + &header, &data, &trailer); + if (fontData) { + std::unique_ptr dict = SkPDFMakeDict(); + dict->insertInt("Length1", header); + dict->insertInt("Length2", data); + dict->insertInt("Length3", trailer); + auto fontStream = SkMemoryStream::Make(std::move(fontData)); + descriptor.insertRef("FontFile", SkPDFStreamOut(std::move(dict), + std::move(fontStream), doc, true)); + } + } + } + return doc->emit(descriptor); +} + + +static const std::vector& type_1_glyphnames(SkPDFDocument* canon, + const SkTypeface* typeface) { + SkFontID fontID = typeface->uniqueID(); + const std::vector* glyphNames = canon->fType1GlyphNames.find(fontID); + if (!glyphNames) { + std::vector names(typeface->countGlyphs()); + SkPDFFont::GetType1GlyphNames(*typeface, names.data()); + glyphNames = canon->fType1GlyphNames.set(fontID, std::move(names)); + } + SkASSERT(glyphNames); + return *glyphNames; +} + +static SkPDFIndirectReference type1_font_descriptor(SkPDFDocument* doc, + const SkTypeface* typeface) { + SkFontID fontID = typeface->uniqueID(); + if (SkPDFIndirectReference* ptr = doc->fFontDescriptors.find(fontID)) { + return *ptr; + } + const SkAdvancedTypefaceMetrics* info = SkPDFFont::GetMetrics(typeface, doc); + auto fontDescriptor = make_type1_font_descriptor(doc, typeface, info); + doc->fFontDescriptors.set(fontID, fontDescriptor); + return fontDescriptor; +} + + +void SkPDFEmitType1Font(const SkPDFFont& pdfFont, SkPDFDocument* doc) { + SkTypeface* typeface = pdfFont.typeface(); + const std::vector glyphNames = type_1_glyphnames(doc, typeface); + SkGlyphID firstGlyphID = pdfFont.firstGlyphID(); + SkGlyphID lastGlyphID = pdfFont.lastGlyphID(); + + SkPDFDict font("Font"); + font.insertRef("FontDescriptor", type1_font_descriptor(doc, typeface)); + font.insertName("Subtype", "Type1"); + if (const SkAdvancedTypefaceMetrics* info = SkPDFFont::GetMetrics(typeface, doc)) { + font.insertName("BaseFont", info->fPostScriptName); + } + + // glyphCount not including glyph 0 + unsigned glyphCount = 1 + lastGlyphID - firstGlyphID; + SkASSERT(glyphCount > 0 && glyphCount <= 255); + font.insertInt("FirstChar", (size_t)0); + font.insertInt("LastChar", (size_t)glyphCount); + { + int emSize; + auto widths = SkPDFMakeArray(); + + int glyphRangeSize = lastGlyphID - firstGlyphID + 2; + SkAutoTArray glyphIDs{glyphRangeSize}; + glyphIDs[0] = 0; + for (unsigned gId = firstGlyphID; gId <= lastGlyphID; gId++) { + glyphIDs[gId - firstGlyphID + 1] = gId; + } + SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(*typeface, &emSize); + SkBulkGlyphMetrics metrics{strikeSpec}; + auto glyphs = metrics.glyphs(SkMakeSpan(glyphIDs.get(), glyphRangeSize)); + for (int i = 0; i < glyphRangeSize; ++i) { + widths->appendScalar(from_font_units(glyphs[i]->advanceX(), SkToU16(emSize))); + } + font.insertObject("Widths", std::move(widths)); + } + auto encDiffs = SkPDFMakeArray(); + encDiffs->reserve(lastGlyphID - firstGlyphID + 3); + encDiffs->appendInt(0); + + SkASSERT(glyphNames.size() > lastGlyphID); + const SkString unknown("UNKNOWN"); + encDiffs->appendName(glyphNames[0].isEmpty() ? unknown : glyphNames[0]); + for (int gID = firstGlyphID; gID <= lastGlyphID; gID++) { + encDiffs->appendName(glyphNames[gID].isEmpty() ? unknown : glyphNames[gID]); + } + + auto encoding = SkPDFMakeDict("Encoding"); + encoding->insertObject("Differences", std::move(encDiffs)); + font.insertObject("Encoding", std::move(encoding)); + + doc->emit(font, pdfFont.indirectReference()); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFType1Font.h b/gfx/skia/skia/src/pdf/SkPDFType1Font.h new file mode 100644 index 0000000000..7f9d972fe5 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFType1Font.h @@ -0,0 +1,11 @@ +// Copyright 2019 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkPDFType1Font_DEFINED +#define SkPDFType1Font_DEFINED + +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFFont.h" + +void SkPDFEmitType1Font(const SkPDFFont&, SkPDFDocument*); + +#endif // SkPDFType1Font_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFTypes.cpp b/gfx/skia/skia/src/pdf/SkPDFTypes.cpp new file mode 100644 index 0000000000..9a74fd2020 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFTypes.cpp @@ -0,0 +1,486 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFTypes.h" + +#include "include/core/SkData.h" +#include "include/core/SkExecutor.h" +#include "include/core/SkStream.h" +#include "include/private/SkTo.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkStreamPriv.h" +#include "src/pdf/SkDeflate.h" +#include "src/pdf/SkPDFDocumentPriv.h" +#include "src/pdf/SkPDFUnion.h" +#include "src/pdf/SkPDFUtils.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// + +SkPDFUnion::SkPDFUnion(Type t) : fType(t) {} +SkPDFUnion::SkPDFUnion(Type t, int32_t v) : fIntValue (v), fType(t) {} +SkPDFUnion::SkPDFUnion(Type t, bool v) : fBoolValue (v), fType(t) {} +SkPDFUnion::SkPDFUnion(Type t, SkScalar v) : fScalarValue (v), fType(t) {} +SkPDFUnion::SkPDFUnion(Type t, SkString v) : fType(t) { fSkString.init(std::move(v)); } + +SkPDFUnion::~SkPDFUnion() { + switch (fType) { + case Type::kNameSkS: + case Type::kStringSkS: + fSkString.destroy(); + return; + case Type::kObject: + SkASSERT(fObject); + delete fObject; + return; + default: + return; + } +} + +SkPDFUnion& SkPDFUnion::operator=(SkPDFUnion&& other) { + if (this != &other) { + this->~SkPDFUnion(); + new (this) SkPDFUnion(std::move(other)); + } + return *this; +} + +SkPDFUnion::SkPDFUnion(SkPDFUnion&& other) { + SkASSERT(this != &other); + memcpy(this, &other, sizeof(*this)); + other.fType = Type::kDestroyed; +} + +#if 0 +SkPDFUnion SkPDFUnion::copy() const { + SkPDFUnion u(fType); + memcpy(&u, this, sizeof(u)); + switch (fType) { + case Type::kNameSkS: + case Type::kStringSkS: + u.fSkString.init(fSkString.get()); + return u; + case Type::kObject: + SkRef(u.fObject); + return u; + default: + return u; + } +} +SkPDFUnion& SkPDFUnion::operator=(const SkPDFUnion& other) { + return *this = other.copy(); +} +SkPDFUnion::SkPDFUnion(const SkPDFUnion& other) { + *this = other.copy(); +} +#endif + +bool SkPDFUnion::isName() const { + return Type::kName == fType || Type::kNameSkS == fType; +} + +#ifdef SK_DEBUG +// Most names need no escaping. Such names are handled as static +// const strings. +bool is_valid_name(const char* n) { + static const char kControlChars[] = "/%()<>[]{}"; + while (*n) { + if (*n < '!' || *n > '~' || strchr(kControlChars, *n)) { + return false; + } + ++n; + } + return true; +} +#endif // SK_DEBUG + +// Given an arbitrary string, write it as a valid name (not including +// leading slash). +static void write_name_escaped(SkWStream* o, const char* name) { + static const char kToEscape[] = "#/%()<>[]{}"; + for (const uint8_t* n = reinterpret_cast(name); *n; ++n) { + uint8_t v = *n; + if (v < '!' || v > '~' || strchr(kToEscape, v)) { + char buffer[3] = {'#', + SkHexadecimalDigits::gUpper[v >> 4], + SkHexadecimalDigits::gUpper[v & 0xF]}; + o->write(buffer, sizeof(buffer)); + } else { + o->write(n, 1); + } + } +} + +static void write_string(SkWStream* wStream, const char* cin, size_t len) { + SkDEBUGCODE(static const size_t kMaxLen = 65535;) + SkASSERT(len <= kMaxLen); + + size_t extraCharacterCount = 0; + for (size_t i = 0; i < len; i++) { + if (cin[i] > '~' || cin[i] < ' ') { + extraCharacterCount += 3; + } else if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') { + ++extraCharacterCount; + } + } + if (extraCharacterCount <= len) { + wStream->writeText("("); + for (size_t i = 0; i < len; i++) { + if (cin[i] > '~' || cin[i] < ' ') { + uint8_t c = static_cast(cin[i]); + uint8_t octal[4] = { '\\', + (uint8_t)('0' | ( c >> 6 )), + (uint8_t)('0' | ((c >> 3) & 0x07)), + (uint8_t)('0' | ( c & 0x07)) }; + wStream->write(octal, 4); + } else { + if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') { + wStream->writeText("\\"); + } + wStream->write(&cin[i], 1); + } + } + wStream->writeText(")"); + } else { + wStream->writeText("<"); + for (size_t i = 0; i < len; i++) { + uint8_t c = static_cast(cin[i]); + char hexValue[2] = { SkHexadecimalDigits::gUpper[c >> 4], + SkHexadecimalDigits::gUpper[c & 0xF] }; + wStream->write(hexValue, 2); + } + wStream->writeText(">"); + } +} + +void SkPDFWriteString(SkWStream* wStream, const char* cin, size_t len) { + write_string(wStream, cin, len); +} + +void SkPDFUnion::emitObject(SkWStream* stream) const { + switch (fType) { + case Type::kInt: + stream->writeDecAsText(fIntValue); + return; + case Type::kColorComponent: + SkPDFUtils::AppendColorComponent(SkToU8(fIntValue), stream); + return; + case Type::kColorComponentF: + SkPDFUtils::AppendColorComponentF(fScalarValue, stream); + return; + case Type::kBool: + stream->writeText(fBoolValue ? "true" : "false"); + return; + case Type::kScalar: + SkPDFUtils::AppendScalar(fScalarValue, stream); + return; + case Type::kName: + stream->writeText("/"); + SkASSERT(is_valid_name(fStaticString)); + stream->writeText(fStaticString); + return; + case Type::kString: + SkASSERT(fStaticString); + write_string(stream, fStaticString, strlen(fStaticString)); + return; + case Type::kNameSkS: + stream->writeText("/"); + write_name_escaped(stream, fSkString.get().c_str()); + return; + case Type::kStringSkS: + write_string(stream, fSkString.get().c_str(), fSkString.get().size()); + return; + case Type::kObject: + fObject->emitObject(stream); + return; + case Type::kRef: + SkASSERT(fIntValue >= 0); + stream->writeDecAsText(fIntValue); + stream->writeText(" 0 R"); // Generation number is always 0. + return; + default: + SkDEBUGFAIL("SkPDFUnion::emitObject with bad type"); + } +} + +SkPDFUnion SkPDFUnion::Int(int32_t value) { return SkPDFUnion(Type::kInt, value); } + +SkPDFUnion SkPDFUnion::ColorComponent(uint8_t value) { + return SkPDFUnion(Type::kColorComponent, (int32_t)value); +} + +SkPDFUnion SkPDFUnion::ColorComponentF(float value) { + return SkPDFUnion(Type::kColorComponentF, (SkScalar)value); +} + +SkPDFUnion SkPDFUnion::Bool(bool value) { + return SkPDFUnion(Type::kBool, value); +} + +SkPDFUnion SkPDFUnion::Scalar(SkScalar value) { + return SkPDFUnion(Type::kScalar, value); +} + +SkPDFUnion SkPDFUnion::Name(const char* value) { + SkPDFUnion u(Type::kName); + SkASSERT(value); + SkASSERT(is_valid_name(value)); + u.fStaticString = value; + return u; +} + +SkPDFUnion SkPDFUnion::String(const char* value) { + SkPDFUnion u(Type::kString); + SkASSERT(value); + u.fStaticString = value; + return u; +} + +SkPDFUnion SkPDFUnion::Name(SkString s) { return SkPDFUnion(Type::kNameSkS, std::move(s)); } + +SkPDFUnion SkPDFUnion::String(SkString s) { return SkPDFUnion(Type::kStringSkS, std::move(s)); } + +SkPDFUnion SkPDFUnion::Object(std::unique_ptr objSp) { + SkPDFUnion u(Type::kObject); + SkASSERT(objSp.get()); + u.fObject = objSp.release(); // take ownership into union{} + return u; +} + +SkPDFUnion SkPDFUnion::Ref(SkPDFIndirectReference ref) { + return SkASSERT(ref.fValue > 0), SkPDFUnion(Type::kRef, (int32_t)ref.fValue); +} + +//////////////////////////////////////////////////////////////////////////////// + +#if 0 // Enable if needed. +void SkPDFAtom::emitObject(SkWStream* stream) const { + fValue.emitObject(stream); +} +#endif // 0 + +//////////////////////////////////////////////////////////////////////////////// + +SkPDFArray::SkPDFArray() {} + +SkPDFArray::~SkPDFArray() {} + +size_t SkPDFArray::size() const { return fValues.size(); } + +void SkPDFArray::reserve(int length) { + fValues.reserve(length); +} + +void SkPDFArray::emitObject(SkWStream* stream) const { + stream->writeText("["); + for (size_t i = 0; i < fValues.size(); i++) { + fValues[i].emitObject(stream); + if (i + 1 < fValues.size()) { + stream->writeText(" "); + } + } + stream->writeText("]"); +} + +void SkPDFArray::append(SkPDFUnion&& value) { + fValues.emplace_back(std::move(value)); +} + +void SkPDFArray::appendInt(int32_t value) { + this->append(SkPDFUnion::Int(value)); +} + +void SkPDFArray::appendColorComponent(uint8_t value) { + this->append(SkPDFUnion::ColorComponent(value)); +} + +void SkPDFArray::appendBool(bool value) { + this->append(SkPDFUnion::Bool(value)); +} + +void SkPDFArray::appendScalar(SkScalar value) { + this->append(SkPDFUnion::Scalar(value)); +} + +void SkPDFArray::appendName(const char name[]) { + this->append(SkPDFUnion::Name(SkString(name))); +} + +void SkPDFArray::appendName(SkString name) { + this->append(SkPDFUnion::Name(std::move(name))); +} + +void SkPDFArray::appendString(SkString value) { + this->append(SkPDFUnion::String(std::move(value))); +} + +void SkPDFArray::appendString(const char value[]) { + this->append(SkPDFUnion::String(value)); +} + +void SkPDFArray::appendObject(std::unique_ptr&& objSp) { + this->append(SkPDFUnion::Object(std::move(objSp))); +} + +void SkPDFArray::appendRef(SkPDFIndirectReference ref) { + this->append(SkPDFUnion::Ref(ref)); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPDFDict::~SkPDFDict() {} + +SkPDFDict::SkPDFDict(const char type[]) { + if (type) { + this->insertName("Type", type); + } +} + +void SkPDFDict::emitObject(SkWStream* stream) const { + stream->writeText("<<"); + for (size_t i = 0; i < fRecords.size(); ++i) { + const std::pair& record = fRecords[i]; + record.first.emitObject(stream); + stream->writeText(" "); + record.second.emitObject(stream); + if (i + 1 < fRecords.size()) { + stream->writeText("\n"); + } + } + stream->writeText(">>"); +} + +size_t SkPDFDict::size() const { return fRecords.size(); } + +void SkPDFDict::reserve(int n) { + fRecords.reserve(n); +} + +void SkPDFDict::insertRef(const char key[], SkPDFIndirectReference ref) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Ref(ref)); +} + +void SkPDFDict::insertRef(SkString key, SkPDFIndirectReference ref) { + fRecords.emplace_back(SkPDFUnion::Name(std::move(key)), SkPDFUnion::Ref(ref)); +} + +void SkPDFDict::insertObject(const char key[], std::unique_ptr&& objSp) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Object(std::move(objSp))); +} +void SkPDFDict::insertObject(SkString key, std::unique_ptr&& objSp) { + fRecords.emplace_back(SkPDFUnion::Name(std::move(key)), + SkPDFUnion::Object(std::move(objSp))); +} + +void SkPDFDict::insertBool(const char key[], bool value) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Bool(value)); +} + +void SkPDFDict::insertInt(const char key[], int32_t value) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Int(value)); +} + +void SkPDFDict::insertInt(const char key[], size_t value) { + this->insertInt(key, SkToS32(value)); +} + +void SkPDFDict::insertColorComponentF(const char key[], SkScalar value) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::ColorComponentF(value)); +} + +void SkPDFDict::insertScalar(const char key[], SkScalar value) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Scalar(value)); +} + +void SkPDFDict::insertName(const char key[], const char name[]) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Name(name)); +} + +void SkPDFDict::insertName(const char key[], SkString name) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Name(std::move(name))); +} + +void SkPDFDict::insertString(const char key[], const char value[]) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::String(value)); +} + +void SkPDFDict::insertString(const char key[], SkString value) { + fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::String(std::move(value))); +} + +//////////////////////////////////////////////////////////////////////////////// + + + +static void serialize_stream(SkPDFDict* origDict, + SkStreamAsset* stream, + bool deflate, + SkPDFDocument* doc, + SkPDFIndirectReference ref) { + // Code assumes that the stream starts at the beginning. + SkASSERT(stream && stream->hasLength()); + + std::unique_ptr tmp; + SkPDFDict tmpDict; + SkPDFDict& dict = origDict ? *origDict : tmpDict; + static const size_t kMinimumSavings = strlen("/Filter_/FlateDecode_"); + if (deflate && stream->getLength() > kMinimumSavings) { + SkDynamicMemoryWStream compressedData; + SkDeflateWStream deflateWStream(&compressedData); + SkStreamCopy(&deflateWStream, stream); + deflateWStream.finalize(); + #ifdef SK_PDF_BASE85_BINARY + { + SkPDFUtils::Base85Encode(compressedData.detachAsStream(), &compressedData); + tmp = compressedData.detachAsStream(); + stream = tmp.get(); + auto filters = SkPDFMakeArray(); + filters->appendName("ASCII85Decode"); + filters->appendName("FlateDecode"); + dict.insertObject("Filter", std::move(filters)); + } + #else + if (stream->getLength() > compressedData.bytesWritten() + kMinimumSavings) { + tmp = compressedData.detachAsStream(); + stream = tmp.get(); + dict.insertName("Filter", "FlateDecode"); + } else { + SkAssertResult(stream->rewind()); + } + #endif + + } + dict.insertInt("Length", stream->getLength()); + doc->emitStream(dict, + [stream](SkWStream* dst) { dst->writeStream(stream, stream->getLength()); }, + ref); +} + +SkPDFIndirectReference SkPDFStreamOut(std::unique_ptr dict, + std::unique_ptr content, + SkPDFDocument* doc, + bool deflate) { + SkPDFIndirectReference ref = doc->reserveRef(); + if (SkExecutor* executor = doc->executor()) { + SkPDFDict* dictPtr = dict.release(); + SkStreamAsset* contentPtr = content.release(); + // Pass ownership of both pointers into a std::function, which should + // only be executed once. + doc->incrementJobCount(); + executor->add([dictPtr, contentPtr, deflate, doc, ref]() { + serialize_stream(dictPtr, contentPtr, deflate, doc, ref); + delete dictPtr; + delete contentPtr; + doc->signalJobComplete(); + }); + return ref; + } + serialize_stream(dict.get(), content.get(), deflate, doc, ref); + return ref; +} diff --git a/gfx/skia/skia/src/pdf/SkPDFTypes.h b/gfx/skia/skia/src/pdf/SkPDFTypes.h new file mode 100644 index 0000000000..06c90e2ca0 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFTypes.h @@ -0,0 +1,204 @@ +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPDFTypes_DEFINED +#define SkPDFTypes_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTHash.h" +#include "include/private/SkTo.h" +#include "src/core/SkMakeUnique.h" + +#include +#include +#include +#include +#include + +class SkData; +class SkPDFArray; + +class SkPDFDict; +class SkPDFDocument; +class SkPDFObject; +class SkPDFUnion; +class SkStreamAsset; +class SkString; +class SkWStream; +struct SkPDFObjectSerializer; + +struct SkPDFIndirectReference { + int fValue = -1; + explicit operator bool() { return fValue != -1; } +}; + +inline static bool operator==(SkPDFIndirectReference u, SkPDFIndirectReference v) { + return u.fValue == v.fValue; +} + +inline static bool operator!=(SkPDFIndirectReference u, SkPDFIndirectReference v) { + return u.fValue != v.fValue; +} + +/** \class SkPDFObject + + A PDF Object is the base class for primitive elements in a PDF file. A + common subtype is used to ease the use of indirect object references, + which are common in the PDF format. + +*/ +class SkPDFObject { +public: + SkPDFObject() = default; + + /** Subclasses must implement this method to print the object to the + * PDF file. + * @param catalog The object catalog to use. + * @param stream The writable output stream to send the output to. + */ + virtual void emitObject(SkWStream* stream) const = 0; + + virtual ~SkPDFObject() = default; + +private: + SkPDFObject(SkPDFObject&&) = delete; + SkPDFObject(const SkPDFObject&) = delete; + SkPDFObject& operator=(SkPDFObject&&) = delete; + SkPDFObject& operator=(const SkPDFObject&) = delete; +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** \class SkPDFArray + + An array object in a PDF. +*/ +class SkPDFArray final : public SkPDFObject { +public: + /** Create a PDF array. Maximum length is 8191. + */ + SkPDFArray(); + ~SkPDFArray() override; + + // The SkPDFObject interface. + void emitObject(SkWStream* stream) const override; + + /** The size of the array. + */ + size_t size() const; + + /** Preallocate space for the given number of entries. + * @param length The number of array slots to preallocate. + */ + void reserve(int length); + + /** Appends a value to the end of the array. + * @param value The value to add to the array. + */ + void appendInt(int32_t); + void appendColorComponent(uint8_t); + void appendBool(bool); + void appendScalar(SkScalar); + void appendName(const char[]); + void appendName(SkString); + void appendString(const char[]); + void appendString(SkString); + void appendObject(std::unique_ptr&&); + void appendRef(SkPDFIndirectReference); + +private: + std::vector fValues; + void append(SkPDFUnion&& value); +}; + +static inline void SkPDFArray_Append(SkPDFArray* a, int v) { a->appendInt(v); } + +static inline void SkPDFArray_Append(SkPDFArray* a, SkScalar v) { a->appendScalar(v); } + +template +static inline void SkPDFArray_Append(SkPDFArray* a, T v, Args... args) { + SkPDFArray_Append(a, v); + SkPDFArray_Append(a, args...); +} + +static inline void SkPDFArray_Append(SkPDFArray* a) {} + +template +static inline std::unique_ptr SkPDFMakeArray(Args... args) { + std::unique_ptr ret(new SkPDFArray()); + ret->reserve(sizeof...(Args)); + SkPDFArray_Append(ret.get(), args...); + return ret; +} + +/** \class SkPDFDict + + A dictionary object in a PDF. +*/ +class SkPDFDict final : public SkPDFObject { +public: + /** Create a PDF dictionary. + * @param type The value of the Type entry, nullptr for no type. + */ + explicit SkPDFDict(const char type[] = nullptr); + + ~SkPDFDict() override; + + // The SkPDFObject interface. + void emitObject(SkWStream* stream) const override; + + /** The size of the dictionary. + */ + size_t size() const; + + /** Preallocate space for n key-value pairs */ + void reserve(int n); + + /** Add the value to the dictionary with the given key. + * @param key The text of the key for this dictionary entry. + * @param value The value for this dictionary entry. + */ + void insertObject(const char key[], std::unique_ptr&&); + void insertObject(SkString, std::unique_ptr&&); + void insertRef(const char key[], SkPDFIndirectReference); + void insertRef(SkString, SkPDFIndirectReference); + + /** Add the value to the dictionary with the given key. + * @param key The text of the key for this dictionary entry. + * @param value The value for this dictionary entry. + */ + void insertBool(const char key[], bool value); + void insertInt(const char key[], int32_t value); + void insertInt(const char key[], size_t value); + void insertScalar(const char key[], SkScalar value); + void insertColorComponentF(const char key[], SkScalar value); + void insertName(const char key[], const char nameValue[]); + void insertName(const char key[], SkString nameValue); + void insertString(const char key[], const char value[]); + void insertString(const char key[], SkString value); + +private: + std::vector> fRecords; +}; + +static inline std::unique_ptr SkPDFMakeDict(const char* type = nullptr) { + return std::unique_ptr(new SkPDFDict(type)); +} + +#ifdef SK_PDF_LESS_COMPRESSION + static constexpr bool kSkPDFDefaultDoDeflate = false; +#else + static constexpr bool kSkPDFDefaultDoDeflate = true; +#endif + +SkPDFIndirectReference SkPDFStreamOut(std::unique_ptr dict, + std::unique_ptr stream, + SkPDFDocument* doc, + bool deflate = kSkPDFDefaultDoDeflate); +#endif diff --git a/gfx/skia/skia/src/pdf/SkPDFUnion.h b/gfx/skia/skia/src/pdf/SkPDFUnion.h new file mode 100644 index 0000000000..9eeec20480 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFUnion.h @@ -0,0 +1,128 @@ +// Copyright 2018 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkPDFUnion_DEFINED +#define SkPDFUnion_DEFINED + +#include "src/pdf/SkPDFTypes.h" + +template +class SkStorageFor { +public: + const T& get() const { return *reinterpret_cast(&fStore); } + T& get() { return *reinterpret_cast(&fStore); } + // Up to caller to keep track of status. + template void init(Args&&... args) { + new (&this->get()) T(std::forward(args)...); + } + void destroy() { this->get().~T(); } +private: + typename std::aligned_storage::type fStore; +}; + +// Exposed for unit testing. +void SkPDFWriteString(SkWStream* wStream, const char* cin, size_t len); + +//////////////////////////////////////////////////////////////////////////////// + +/** + A SkPDFUnion is a non-virtualized implementation of the + non-compound, non-specialized PDF Object types: Name, String, + Number, Boolean. + */ +class SkPDFUnion { +public: + // Move contstructor and assignment operator destroy the argument + // and steal their references (if needed). + SkPDFUnion(SkPDFUnion&& other); + SkPDFUnion& operator=(SkPDFUnion&& other); + + ~SkPDFUnion(); + + /** The following nine functions are the standard way of creating + SkPDFUnion objects. */ + + static SkPDFUnion Int(int32_t); + + static SkPDFUnion Int(size_t v) { return SkPDFUnion::Int(SkToS32(v)); } + + static SkPDFUnion Bool(bool); + + static SkPDFUnion Scalar(SkScalar); + + static SkPDFUnion ColorComponent(uint8_t); + + static SkPDFUnion ColorComponentF(float); + + /** These two functions do NOT take ownership of char*, and do NOT + copy the string. Suitable for passing in static const + strings. For example: + SkPDFUnion n = SkPDFUnion::Name("Length"); + SkPDFUnion u = SkPDFUnion::String("Identity"); */ + + /** SkPDFUnion::Name(const char*) assumes that the passed string + is already a valid name (that is: it has no control or + whitespace characters). This will not copy the name. */ + static SkPDFUnion Name(const char*); + + /** SkPDFUnion::String will encode the passed string. This will + not copy the name. */ + static SkPDFUnion String(const char*); + + /** SkPDFUnion::Name(SkString) does not assume that the + passed string is already a valid name and it will escape the + string. */ + static SkPDFUnion Name(SkString); + + /** SkPDFUnion::String will encode the passed string. */ + static SkPDFUnion String(SkString); + + static SkPDFUnion Object(std::unique_ptr); + + static SkPDFUnion Ref(SkPDFIndirectReference); + + /** These two non-virtual methods mirror SkPDFObject's + corresponding virtuals. */ + void emitObject(SkWStream*) const; + + bool isName() const; + +private: + union { + int32_t fIntValue; + bool fBoolValue; + SkScalar fScalarValue; + const char* fStaticString; + SkStorageFor fSkString; + SkPDFObject* fObject; + }; + enum class Type : char { + /** It is an error to call emitObject() or addResources() on an + kDestroyed object. */ + kDestroyed = 0, + kInt, + kColorComponent, + kColorComponentF, + kBool, + kScalar, + kName, + kString, + kNameSkS, + kStringSkS, + kObject, + kRef, + }; + Type fType; + + SkPDFUnion(Type); + SkPDFUnion(Type, int32_t); + SkPDFUnion(Type, bool); + SkPDFUnion(Type, SkScalar); + SkPDFUnion(Type, SkString); + // We do not now need copy constructor and copy assignment, so we + // will disable this functionality. + SkPDFUnion& operator=(const SkPDFUnion&) = delete; + SkPDFUnion(const SkPDFUnion&) = delete; +}; +static_assert(sizeof(SkString) == sizeof(void*), "SkString_size"); + +#endif // SkPDFUnion_DEFINED diff --git a/gfx/skia/skia/src/pdf/SkPDFUtils.cpp b/gfx/skia/skia/src/pdf/SkPDFUtils.cpp new file mode 100644 index 0000000000..9beedfb5ab --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFUtils.cpp @@ -0,0 +1,395 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/pdf/SkPDFUtils.h" + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/private/SkFixed.h" +#include "src/core/SkGeometry.h" +#include "src/core/SkPathPriv.h" +#include "src/image/SkImage_Base.h" +#include "src/pdf/SkPDFResourceDict.h" +#include "src/pdf/SkPDFTypes.h" + +#include + +const char* SkPDFUtils::BlendModeName(SkBlendMode mode) { + // PDF32000.book section 11.3.5 "Blend Mode" + switch (mode) { + case SkBlendMode::kSrcOver: return "Normal"; + case SkBlendMode::kXor: return "Normal"; // (unsupported mode) + case SkBlendMode::kPlus: return "Normal"; // (unsupported mode) + case SkBlendMode::kScreen: return "Screen"; + case SkBlendMode::kOverlay: return "Overlay"; + case SkBlendMode::kDarken: return "Darken"; + case SkBlendMode::kLighten: return "Lighten"; + case SkBlendMode::kColorDodge: return "ColorDodge"; + case SkBlendMode::kColorBurn: return "ColorBurn"; + case SkBlendMode::kHardLight: return "HardLight"; + case SkBlendMode::kSoftLight: return "SoftLight"; + case SkBlendMode::kDifference: return "Difference"; + case SkBlendMode::kExclusion: return "Exclusion"; + case SkBlendMode::kMultiply: return "Multiply"; + case SkBlendMode::kHue: return "Hue"; + case SkBlendMode::kSaturation: return "Saturation"; + case SkBlendMode::kColor: return "Color"; + case SkBlendMode::kLuminosity: return "Luminosity"; + // Other blendmodes are handled in SkPDFDevice::setUpContentEntry. + default: return nullptr; + } +} + +std::unique_ptr SkPDFUtils::RectToArray(const SkRect& r) { + return SkPDFMakeArray(r.left(), r.top(), r.right(), r.bottom()); +} + +std::unique_ptr SkPDFUtils::MatrixToArray(const SkMatrix& matrix) { + SkScalar a[6]; + if (!matrix.asAffine(a)) { + SkMatrix::SetAffineIdentity(a); + } + return SkPDFMakeArray(a[0], a[1], a[2], a[3], a[4], a[5]); +} + +void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) { + SkPDFUtils::AppendScalar(x, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(y, content); + content->writeText(" m\n"); +} + +void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) { + SkPDFUtils::AppendScalar(x, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(y, content); + content->writeText(" l\n"); +} + +static void append_cubic(SkScalar ctl1X, SkScalar ctl1Y, + SkScalar ctl2X, SkScalar ctl2Y, + SkScalar dstX, SkScalar dstY, SkWStream* content) { + SkString cmd("y\n"); + SkPDFUtils::AppendScalar(ctl1X, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(ctl1Y, content); + content->writeText(" "); + if (ctl2X != dstX || ctl2Y != dstY) { + cmd.set("c\n"); + SkPDFUtils::AppendScalar(ctl2X, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(ctl2Y, content); + content->writeText(" "); + } + SkPDFUtils::AppendScalar(dstX, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(dstY, content); + content->writeText(" "); + content->writeText(cmd.c_str()); +} + +static void append_quad(const SkPoint quad[], SkWStream* content) { + SkPoint cubic[4]; + SkConvertQuadToCubic(quad, cubic); + append_cubic(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, + cubic[3].fX, cubic[3].fY, content); +} + +void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) { + // Skia has 0,0 at top left, pdf at bottom left. Do the right thing. + SkScalar bottom = SkMinScalar(rect.fBottom, rect.fTop); + + SkPDFUtils::AppendScalar(rect.fLeft, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(bottom, content); + content->writeText(" "); + SkPDFUtils::AppendScalar(rect.width(), content); + content->writeText(" "); + SkPDFUtils::AppendScalar(rect.height(), content); + content->writeText(" re\n"); +} + +void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle, + bool doConsumeDegerates, SkWStream* content, + SkScalar tolerance) { + if (path.isEmpty() && SkPaint::kFill_Style == paintStyle) { + SkPDFUtils::AppendRectangle({0, 0, 0, 0}, content); + return; + } + // Filling a path with no area results in a drawing in PDF renderers but + // Chrome expects to be able to draw some such entities with no visible + // result, so we detect those cases and discard the drawing for them. + // Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y). + + SkRect rect; + bool isClosed; // Both closure and direction need to be checked. + SkPath::Direction direction; + if (path.isRect(&rect, &isClosed, &direction) && + isClosed && + (SkPath::kCW_Direction == direction || + SkPath::kEvenOdd_FillType == path.getFillType())) + { + SkPDFUtils::AppendRectangle(rect, content); + return; + } + + enum SkipFillState { + kEmpty_SkipFillState, + kSingleLine_SkipFillState, + kNonSingleLine_SkipFillState, + }; + SkipFillState fillState = kEmpty_SkipFillState; + //if (paintStyle != SkPaint::kFill_Style) { + // fillState = kNonSingleLine_SkipFillState; + //} + SkPoint lastMovePt = SkPoint::Make(0,0); + SkDynamicMemoryWStream currentSegment; + SkPoint args[4]; + SkPath::Iter iter(path, false); + for (SkPath::Verb verb = iter.next(args); + verb != SkPath::kDone_Verb; + verb = iter.next(args)) { + // args gets all the points, even the implicit first point. + switch (verb) { + case SkPath::kMove_Verb: + MoveTo(args[0].fX, args[0].fY, ¤tSegment); + lastMovePt = args[0]; + fillState = kEmpty_SkipFillState; + break; + case SkPath::kLine_Verb: + if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 2)) { + AppendLine(args[1].fX, args[1].fY, ¤tSegment); + if ((fillState == kEmpty_SkipFillState) && (args[0] != lastMovePt)) { + fillState = kSingleLine_SkipFillState; + break; + } + fillState = kNonSingleLine_SkipFillState; + } + break; + case SkPath::kQuad_Verb: + if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 3)) { + append_quad(args, ¤tSegment); + fillState = kNonSingleLine_SkipFillState; + } + break; + case SkPath::kConic_Verb: + if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 3)) { + SkAutoConicToQuads converter; + const SkPoint* quads = converter.computeQuads(args, iter.conicWeight(), tolerance); + for (int i = 0; i < converter.countQuads(); ++i) { + append_quad(&quads[i * 2], ¤tSegment); + } + fillState = kNonSingleLine_SkipFillState; + } + break; + case SkPath::kCubic_Verb: + if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 4)) { + append_cubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY, + args[3].fX, args[3].fY, ¤tSegment); + fillState = kNonSingleLine_SkipFillState; + } + break; + case SkPath::kClose_Verb: + ClosePath(¤tSegment); + currentSegment.writeToStream(content); + currentSegment.reset(); + break; + default: + SkASSERT(false); + break; + } + } + if (currentSegment.bytesWritten() > 0) { + currentSegment.writeToStream(content); + } +} + +void SkPDFUtils::ClosePath(SkWStream* content) { + content->writeText("h\n"); +} + +void SkPDFUtils::PaintPath(SkPaint::Style style, SkPath::FillType fill, + SkWStream* content) { + if (style == SkPaint::kFill_Style) { + content->writeText("f"); + } else if (style == SkPaint::kStrokeAndFill_Style) { + content->writeText("B"); + } else if (style == SkPaint::kStroke_Style) { + content->writeText("S"); + } + + if (style != SkPaint::kStroke_Style) { + NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false); + NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false); + if (fill == SkPath::kEvenOdd_FillType) { + content->writeText("*"); + } + } + content->writeText("\n"); +} + +void SkPDFUtils::StrokePath(SkWStream* content) { + SkPDFUtils::PaintPath( + SkPaint::kStroke_Style, SkPath::kWinding_FillType, content); +} + +void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) { + SkPDFWriteResourceName(content, SkPDFResourceType::kExtGState, objectIndex); + content->writeText(" gs\n"); +} + +void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) { + // Select Pattern color space (CS, cs) and set pattern object as current + // color (SCN, scn) + content->writeText("/Pattern CS/Pattern cs"); + SkPDFWriteResourceName(content, SkPDFResourceType::kPattern, objectIndex); + content->writeText(" SCN"); + SkPDFWriteResourceName(content, SkPDFResourceType::kPattern, objectIndex); + content->writeText(" scn\n"); +} + +// return "x/pow(10, places)", given 0 0; --i) { + result[i] = '0' + x % 10; + x /= 10; + } + int j; + for (j = places; j > 1; --j) { + if (result[j] != '0') { + break; + } + } + result[j + 1] = '\0'; + return j + 1; +} + + +static constexpr int int_pow(int base, unsigned exp, int acc = 1) { + return exp < 1 ? acc + : int_pow(base * base, + exp / 2, + (exp % 2) ? acc * base : acc); +} + + +size_t SkPDFUtils::ColorToDecimalF(float value, char result[kFloatColorDecimalCount + 2]) { + static constexpr int kFactor = int_pow(10, kFloatColorDecimalCount); + int x = sk_float_round2int(value * kFactor); + if (x >= kFactor || x <= 0) { // clamp to 0-1 + result[0] = x > 0 ? '1' : '0'; + result[1] = '\0'; + return 1; + } + return print_permil_as_decimal(x, result, kFloatColorDecimalCount); +} + +size_t SkPDFUtils::ColorToDecimal(uint8_t value, char result[5]) { + if (value == 255 || value == 0) { + result[0] = value ? '1' : '0'; + result[1] = '\0'; + return 1; + } + // int x = 0.5 + (1000.0 / 255.0) * value; + int x = SkFixedRoundToInt((SK_Fixed1 * 1000 / 255) * value); + return print_permil_as_decimal(x, result, 3); +} + +bool SkPDFUtils::InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox) { + SkMatrix inverse; + if (!matrix.invert(&inverse)) { + return false; + } + inverse.mapRect(bbox); + return true; +} + +void SkPDFUtils::PopulateTilingPatternDict(SkPDFDict* pattern, + SkRect& bbox, + std::unique_ptr resources, + const SkMatrix& matrix) { + const int kTiling_PatternType = 1; + const int kColoredTilingPattern_PaintType = 1; + const int kConstantSpacing_TilingType = 1; + + pattern->insertName("Type", "Pattern"); + pattern->insertInt("PatternType", kTiling_PatternType); + pattern->insertInt("PaintType", kColoredTilingPattern_PaintType); + pattern->insertInt("TilingType", kConstantSpacing_TilingType); + pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox)); + pattern->insertScalar("XStep", bbox.width()); + pattern->insertScalar("YStep", bbox.height()); + pattern->insertObject("Resources", std::move(resources)); + if (!matrix.isIdentity()) { + pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix)); + } +} + +bool SkPDFUtils::ToBitmap(const SkImage* img, SkBitmap* dst) { + SkASSERT(img); + SkASSERT(dst); + SkBitmap bitmap; + if(as_IB(img)->getROPixels(&bitmap)) { + SkASSERT(bitmap.dimensions() == img->dimensions()); + SkASSERT(!bitmap.drawsNothing()); + *dst = std::move(bitmap); + return true; + } + return false; +} + +#ifdef SK_PDF_BASE85_BINARY +void SkPDFUtils::Base85Encode(std::unique_ptr stream, SkDynamicMemoryWStream* dst) { + SkASSERT(dst); + SkASSERT(stream); + dst->writeText("\n"); + int column = 0; + while (true) { + uint8_t src[4] = {0, 0, 0, 0}; + size_t count = stream->read(src, 4); + SkASSERT(count < 5); + if (0 == count) { + dst->writeText("~>\n"); + return; + } + uint32_t v = ((uint32_t)src[0] << 24) | ((uint32_t)src[1] << 16) | + ((uint32_t)src[2] << 8) | src[3]; + if (v == 0 && count == 4) { + dst->writeText("z"); + column += 1; + } else { + char buffer[5]; + for (int n = 4; n > 0; --n) { + buffer[n] = (v % 85) + '!'; + v /= 85; + } + buffer[0] = v + '!'; + dst->write(buffer, count + 1); + column += count + 1; + } + if (column > 74) { + dst->writeText("\n"); + column = 0; + } + } +} +#endif // SK_PDF_BASE85_BINARY + +void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) { + SkScalar values[6]; + if (!matrix.asAffine(values)) { + SkMatrix::SetAffineIdentity(values); + } + for (SkScalar v : values) { + SkPDFUtils::AppendScalar(v, content); + content->writeText(" "); + } + content->writeText("cm\n"); +} diff --git a/gfx/skia/skia/src/pdf/SkPDFUtils.h b/gfx/skia/skia/src/pdf/SkPDFUtils.h new file mode 100644 index 0000000000..90b1c572eb --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkPDFUtils.h @@ -0,0 +1,137 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPDFUtils_DEFINED +#define SkPDFUtils_DEFINED + +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkShader.h" +#include "include/core/SkStream.h" +#include "src/core/SkUtils.h" +#include "src/pdf/SkPDFTypes.h" +#include "src/shaders/SkShaderBase.h" +#include "src/utils/SkFloatToDecimal.h" + +class SkMatrix; +class SkPDFArray; +struct SkRect; + +template +bool SkPackedArrayEqual(T* u, T* v, size_t n) { + SkASSERT(u); + SkASSERT(v); + return 0 == memcmp(u, v, n * sizeof(T)); +} + +#if 0 +#define PRINT_NOT_IMPL(str) fprintf(stderr, str) +#else +#define PRINT_NOT_IMPL(str) +#endif + +#define NOT_IMPLEMENTED(condition, assert) \ + do { \ + if ((bool)(condition)) { \ + PRINT_NOT_IMPL("NOT_IMPLEMENTED: " #condition "\n"); \ + SkDEBUGCODE(SkASSERT(!assert);) \ + } \ + } while (0) + +namespace SkPDFUtils { + +const char* BlendModeName(SkBlendMode); + +std::unique_ptr RectToArray(const SkRect& rect); +std::unique_ptr MatrixToArray(const SkMatrix& matrix); + +void MoveTo(SkScalar x, SkScalar y, SkWStream* content); +void AppendLine(SkScalar x, SkScalar y, SkWStream* content); +void AppendRectangle(const SkRect& rect, SkWStream* content); +void EmitPath(const SkPath& path, SkPaint::Style paintStyle, + bool doConsumeDegerates, SkWStream* content, SkScalar tolerance = 0.25f); +inline void EmitPath(const SkPath& path, SkPaint::Style paintStyle, + SkWStream* content, SkScalar tolerance = 0.25f) { + SkPDFUtils::EmitPath(path, paintStyle, true, content, tolerance); +} +void ClosePath(SkWStream* content); +void PaintPath(SkPaint::Style style, SkPath::FillType fill, + SkWStream* content); +void StrokePath(SkWStream* content); +void ApplyGraphicState(int objectIndex, SkWStream* content); +void ApplyPattern(int objectIndex, SkWStream* content); + +// Converts (value / 255.0) with three significant digits of accuracy. +// Writes value as string into result. Returns strlen() of result. +size_t ColorToDecimal(uint8_t value, char result[5]); + +static constexpr unsigned kFloatColorDecimalCount = 4; +size_t ColorToDecimalF(float value, char result[kFloatColorDecimalCount + 2]); +inline void AppendColorComponent(uint8_t value, SkWStream* wStream) { + char buffer[5]; + size_t len = SkPDFUtils::ColorToDecimal(value, buffer); + wStream->write(buffer, len); +} +inline void AppendColorComponentF(float value, SkWStream* wStream) { + char buffer[kFloatColorDecimalCount + 2]; + size_t len = SkPDFUtils::ColorToDecimalF(value, buffer); + wStream->write(buffer, len); +} + +inline void AppendScalar(SkScalar value, SkWStream* stream) { + char result[kMaximumSkFloatToDecimalLength]; + size_t len = SkFloatToDecimal(SkScalarToFloat(value), result); + SkASSERT(len < kMaximumSkFloatToDecimalLength); + stream->write(result, len); +} + +inline void WriteUInt16BE(SkDynamicMemoryWStream* wStream, uint16_t value) { + char result[4] = { SkHexadecimalDigits::gUpper[ value >> 12 ], + SkHexadecimalDigits::gUpper[0xF & (value >> 8 )], + SkHexadecimalDigits::gUpper[0xF & (value >> 4 )], + SkHexadecimalDigits::gUpper[0xF & (value )] }; + wStream->write(result, 4); +} + +inline void WriteUInt8(SkDynamicMemoryWStream* wStream, uint8_t value) { + char result[2] = { SkHexadecimalDigits::gUpper[value >> 4], + SkHexadecimalDigits::gUpper[value & 0xF] }; + wStream->write(result, 2); +} + +inline void WriteUTF16beHex(SkDynamicMemoryWStream* wStream, SkUnichar utf32) { + uint16_t utf16[2] = {0, 0}; + size_t len = SkUTF::ToUTF16(utf32, utf16); + SkASSERT(len == 1 || len == 2); + SkPDFUtils::WriteUInt16BE(wStream, utf16[0]); + if (len == 2) { + SkPDFUtils::WriteUInt16BE(wStream, utf16[1]); + } +} + +inline SkMatrix GetShaderLocalMatrix(const SkShader* shader) { + SkMatrix localMatrix; + if (sk_sp s = as_SB(shader)->makeAsALocalMatrixShader(&localMatrix)) { + return SkMatrix::Concat(as_SB(s)->getLocalMatrix(), localMatrix); + } + return as_SB(shader)->getLocalMatrix(); +} +bool InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox); +void PopulateTilingPatternDict(SkPDFDict* pattern, + SkRect& bbox, + std::unique_ptr resources, + const SkMatrix& matrix); + +bool ToBitmap(const SkImage* img, SkBitmap* dst); + +#ifdef SK_PDF_BASE85_BINARY +void Base85Encode(std::unique_ptr src, SkDynamicMemoryWStream* dst); +#endif // SK_PDF_BASE85_BINARY + +void AppendTransform(const SkMatrix&, SkWStream*); +} // namespace SkPDFUtils + +#endif diff --git a/gfx/skia/skia/src/pdf/SkUUID.h b/gfx/skia/skia/src/pdf/SkUUID.h new file mode 100644 index 0000000000..3d81865dc0 --- /dev/null +++ b/gfx/skia/skia/src/pdf/SkUUID.h @@ -0,0 +1,18 @@ +// Copyright 2018 Google LLC. +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +#ifndef SkUUID_DEFINED +#define SkUUID_DEFINED + +#include +#include + +struct SkUUID { + uint8_t fData[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +static inline bool operator==(const SkUUID& u, const SkUUID& v) { + return 0 == memcmp(u.fData, v.fData, sizeof(u.fData)); +} +static inline bool operator!=(const SkUUID& u, const SkUUID& v) { return !(u == v); } + +#endif // SkUUID_DEFINED diff --git a/gfx/skia/skia/src/ports/SkDebug_android.cpp b/gfx/skia/skia/src/ports/SkDebug_android.cpp new file mode 100644 index 0000000000..5e32174797 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkDebug_android.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_ANDROID) + +#include + +#define LOG_TAG "skia" +#include + +// Print debug output to stdout as well. This is useful for command line +// applications (e.g. skia_launcher). +bool gSkDebugToStdOut = false; + +void SkDebugf(const char format[], ...) { + va_list args1, args2; + va_start(args1, format); + + if (gSkDebugToStdOut) { + va_copy(args2, args1); + vprintf(format, args2); + va_end(args2); + } + + __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, format, args1); + + va_end(args1); +} + +#endif//defined(SK_BUILD_FOR_ANDROID) diff --git a/gfx/skia/skia/src/ports/SkDebug_stdio.cpp b/gfx/skia/skia/src/ports/SkDebug_stdio.cpp new file mode 100644 index 0000000000..1bba63cc0a --- /dev/null +++ b/gfx/skia/skia/src/ports/SkDebug_stdio.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_ANDROID) + +#include +#include + +void SkDebugf(const char format[], ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} +#endif//!defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_ANDROID) diff --git a/gfx/skia/skia/src/ports/SkDebug_win.cpp b/gfx/skia/skia/src/ports/SkDebug_win.cpp new file mode 100644 index 0000000000..ce2c980555 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkDebug_win.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_WIN) + +#include "src/core/SkLeanWindows.h" + +#include +#include + +static const size_t kBufferSize = 2048; + +void SkDebugf(const char format[], ...) { + char buffer[kBufferSize + 1]; + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fflush(stderr); // stderr seems to be buffered on Windows. + + va_start(args, format); + vsnprintf(buffer, kBufferSize, format, args); + va_end(args); + + OutputDebugStringA(buffer); +} +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkDiscardableMemory_none.cpp b/gfx/skia/skia/src/ports/SkDiscardableMemory_none.cpp new file mode 100644 index 0000000000..794775a02a --- /dev/null +++ b/gfx/skia/skia/src/ports/SkDiscardableMemory_none.cpp @@ -0,0 +1,14 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/core/SkDiscardableMemory.h" +#include "src/lazy/SkDiscardableMemoryPool.h" + +SkDiscardableMemory* SkDiscardableMemory::Create(size_t bytes) { + return SkGetGlobalDiscardableMemoryPool()->create(bytes); +} diff --git a/gfx/skia/skia/src/ports/SkFontConfigInterface.cpp b/gfx/skia/skia/src/ports/SkFontConfigInterface.cpp new file mode 100644 index 0000000000..0ec9c9a547 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontConfigInterface.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/core/SkRefCnt.h" +#include "include/ports/SkFontConfigInterface.h" +#include "include/private/SkMutex.h" + +static SkMutex& font_config_interface_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} +static SkFontConfigInterface* gFontConfigInterface; + +sk_sp SkFontConfigInterface::RefGlobal() { + SkAutoMutexExclusive ac(font_config_interface_mutex()); + + if (gFontConfigInterface) { + return sk_ref_sp(gFontConfigInterface); + } + return sk_ref_sp(SkFontConfigInterface::GetSingletonDirectInterface()); +} + +void SkFontConfigInterface::SetGlobal(sk_sp fc) { + SkAutoMutexExclusive ac(font_config_interface_mutex()); + + SkSafeUnref(gFontConfigInterface); + gFontConfigInterface = fc.release(); +} diff --git a/gfx/skia/skia/src/ports/SkFontConfigInterface_direct.cpp b/gfx/skia/skia/src/ports/SkFontConfigInterface_direct.cpp new file mode 100644 index 0000000000..84fd11b1f3 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontConfigInterface_direct.cpp @@ -0,0 +1,710 @@ +/* + * Copyright 2009-2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* migrated from chrome/src/skia/ext/SkFontHost_fontconfig_direct.cpp */ + +#include "include/core/SkFontStyle.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkFixed.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkBuffer.h" +#include "src/ports/SkFontConfigInterface_direct.h" + +#include +#include + +#ifdef SK_DEBUG +# include "src/core/SkTLS.h" +#endif + +namespace { + +// Fontconfig is not threadsafe before 2.10.91. Before that, we lock with a global mutex. +// See https://bug.skia.org/1497 for background. +static SkMutex& f_c_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} + +#ifdef SK_DEBUG +void* CreateThreadFcLocked() { return new bool(false); } +void DeleteThreadFcLocked(void* v) { delete static_cast(v); } +# define THREAD_FC_LOCKED \ + static_cast(SkTLS::Get(CreateThreadFcLocked, DeleteThreadFcLocked)) +#endif + +struct FCLocker { + // Assume FcGetVersion() has always been thread safe. + + FCLocker() { + if (FcGetVersion() < 21091) { + f_c_mutex().acquire(); + } else { + SkDEBUGCODE(bool* threadLocked = THREAD_FC_LOCKED); + SkASSERT(false == *threadLocked); + SkDEBUGCODE(*threadLocked = true); + } + } + + ~FCLocker() { + AssertHeld(); + if (FcGetVersion() < 21091) { + f_c_mutex().release(); + } else { + SkDEBUGCODE(*THREAD_FC_LOCKED = false); + } + } + + static void AssertHeld() { SkDEBUGCODE( + if (FcGetVersion() < 21091) { + f_c_mutex().assertHeld(); + } else { + SkASSERT(true == *THREAD_FC_LOCKED); + } + ) } +}; + +} // namespace + +size_t SkFontConfigInterface::FontIdentity::writeToMemory(void* addr) const { + size_t size = sizeof(fID) + sizeof(fTTCIndex); + size += sizeof(int32_t) + sizeof(int32_t) + sizeof(uint8_t); // weight, width, italic + size += sizeof(int32_t) + fString.size(); // store length+data + if (addr) { + SkWBuffer buffer(addr, size); + + buffer.write32(fID); + buffer.write32(fTTCIndex); + buffer.write32(fString.size()); + buffer.write32(fStyle.weight()); + buffer.write32(fStyle.width()); + buffer.write8(fStyle.slant()); + buffer.write(fString.c_str(), fString.size()); + buffer.padToAlign4(); + + SkASSERT(buffer.pos() == size); + } + return size; +} + +size_t SkFontConfigInterface::FontIdentity::readFromMemory(const void* addr, + size_t size) { + SkRBuffer buffer(addr, size); + + (void)buffer.readU32(&fID); + (void)buffer.readS32(&fTTCIndex); + uint32_t strLen, weight, width; + (void)buffer.readU32(&strLen); + (void)buffer.readU32(&weight); + (void)buffer.readU32(&width); + uint8_t u8; + (void)buffer.readU8(&u8); + SkFontStyle::Slant slant = (SkFontStyle::Slant)u8; + fStyle = SkFontStyle(weight, width, slant); + fString.resize(strLen); + (void)buffer.read(fString.writable_str(), strLen); + buffer.skipToAlign4(); + + return buffer.pos(); // the actual number of bytes read +} + +#ifdef SK_DEBUG +static void make_iden(SkFontConfigInterface::FontIdentity* iden) { + iden->fID = 10; + iden->fTTCIndex = 2; + iden->fString.set("Hello world"); + iden->fStyle = SkFontStyle(300, 6, SkFontStyle::kItalic_Slant); +} + +static void test_writeToMemory(const SkFontConfigInterface::FontIdentity& iden0, + int initValue) { + SkFontConfigInterface::FontIdentity iden1; + + size_t size0 = iden0.writeToMemory(nullptr); + + SkAutoMalloc storage(size0); + memset(storage.get(), initValue, size0); + + size_t size1 = iden0.writeToMemory(storage.get()); + SkASSERT(size0 == size1); + + SkASSERT(iden0 != iden1); + size_t size2 = iden1.readFromMemory(storage.get(), size1); + SkASSERT(size2 == size1); + SkASSERT(iden0 == iden1); +} + +static void fontconfiginterface_unittest() { + SkFontConfigInterface::FontIdentity iden0, iden1; + + SkASSERT(iden0 == iden1); + + make_iden(&iden0); + SkASSERT(iden0 != iden1); + + make_iden(&iden1); + SkASSERT(iden0 == iden1); + + test_writeToMemory(iden0, 0); + test_writeToMemory(iden0, 0); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// Returns the string from the pattern, or nullptr +static const char* get_string(FcPattern* pattern, const char field[], int index = 0) { + const char* name; + if (FcPatternGetString(pattern, field, index, (FcChar8**)&name) != FcResultMatch) { + name = nullptr; + } + return name; +} + +/////////////////////////////////////////////////////////////////////////////// + +namespace { + +// Equivalence classes, used to match the Liberation and other fonts +// with their metric-compatible replacements. See the discussion in +// GetFontEquivClass(). +enum FontEquivClass +{ + OTHER, + SANS, + SERIF, + MONO, + SYMBOL, + PGOTHIC, + GOTHIC, + PMINCHO, + MINCHO, + SIMSUN, + NSIMSUN, + SIMHEI, + PMINGLIU, + MINGLIU, + PMINGLIUHK, + MINGLIUHK, + CAMBRIA, + CALIBRI, +}; + +// Match the font name against a whilelist of fonts, returning the equivalence +// class. +FontEquivClass GetFontEquivClass(const char* fontname) +{ + // It would be nice for fontconfig to tell us whether a given suggested + // replacement is a "strong" match (that is, an equivalent font) or + // a "weak" match (that is, fontconfig's next-best attempt at finding a + // substitute). However, I played around with the fontconfig API for + // a good few hours and could not make it reveal this information. + // + // So instead, we hardcode. Initially this function emulated + // /etc/fonts/conf.d/30-metric-aliases.conf + // from my Ubuntu system, but we're better off being very conservative. + + // Arimo, Tinos and Cousine are a set of fonts metric-compatible with + // Arial, Times New Roman and Courier New with a character repertoire + // much larger than Liberation. Note that Cousine is metrically + // compatible with Courier New, but the former is sans-serif while + // the latter is serif. + + + struct FontEquivMap { + FontEquivClass clazz; + const char name[40]; + }; + + static const FontEquivMap kFontEquivMap[] = { + { SANS, "Arial" }, + { SANS, "Arimo" }, + { SANS, "Liberation Sans" }, + + { SERIF, "Times New Roman" }, + { SERIF, "Tinos" }, + { SERIF, "Liberation Serif" }, + + { MONO, "Courier New" }, + { MONO, "Cousine" }, + { MONO, "Liberation Mono" }, + + { SYMBOL, "Symbol" }, + { SYMBOL, "Symbol Neu" }, + + // MS Pゴシック + { PGOTHIC, "MS PGothic" }, + { PGOTHIC, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0" + "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" }, + { PGOTHIC, "Noto Sans CJK JP" }, + { PGOTHIC, "IPAPGothic" }, + { PGOTHIC, "MotoyaG04Gothic" }, + + // MS ゴシック + { GOTHIC, "MS Gothic" }, + { GOTHIC, "\xef\xbc\xad\xef\xbc\xb3 " + "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" }, + { GOTHIC, "Noto Sans Mono CJK JP" }, + { GOTHIC, "IPAGothic" }, + { GOTHIC, "MotoyaG04GothicMono" }, + + // MS P明朝 + { PMINCHO, "MS PMincho" }, + { PMINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0" + "\xe6\x98\x8e\xe6\x9c\x9d"}, + { PMINCHO, "Noto Serif CJK JP" }, + { PMINCHO, "IPAPMincho" }, + { PMINCHO, "MotoyaG04Mincho" }, + + // MS 明朝 + { MINCHO, "MS Mincho" }, + { MINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xe6\x98\x8e\xe6\x9c\x9d" }, + { MINCHO, "Noto Serif CJK JP" }, + { MINCHO, "IPAMincho" }, + { MINCHO, "MotoyaG04MinchoMono" }, + + // 宋体 + { SIMSUN, "Simsun" }, + { SIMSUN, "\xe5\xae\x8b\xe4\xbd\x93" }, + { SIMSUN, "Noto Serif CJK SC" }, + { SIMSUN, "MSung GB18030" }, + { SIMSUN, "Song ASC" }, + + // 新宋体 + { NSIMSUN, "NSimsun" }, + { NSIMSUN, "\xe6\x96\xb0\xe5\xae\x8b\xe4\xbd\x93" }, + { NSIMSUN, "Noto Serif CJK SC" }, + { NSIMSUN, "MSung GB18030" }, + { NSIMSUN, "N Song ASC" }, + + // 黑体 + { SIMHEI, "Simhei" }, + { SIMHEI, "\xe9\xbb\x91\xe4\xbd\x93" }, + { SIMHEI, "Noto Sans CJK SC" }, + { SIMHEI, "MYingHeiGB18030" }, + { SIMHEI, "MYingHeiB5HK" }, + + // 新細明體 + { PMINGLIU, "PMingLiU"}, + { PMINGLIU, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" }, + { PMINGLIU, "Noto Serif CJK TC"}, + { PMINGLIU, "MSung B5HK"}, + + // 細明體 + { MINGLIU, "MingLiU"}, + { MINGLIU, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" }, + { MINGLIU, "Noto Serif CJK TC"}, + { MINGLIU, "MSung B5HK"}, + + // 新細明體 + { PMINGLIUHK, "PMingLiU_HKSCS"}, + { PMINGLIUHK, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" }, + { PMINGLIUHK, "Noto Serif CJK TC"}, + { PMINGLIUHK, "MSung B5HK"}, + + // 細明體 + { MINGLIUHK, "MingLiU_HKSCS"}, + { MINGLIUHK, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" }, + { MINGLIUHK, "Noto Serif CJK TC"}, + { MINGLIUHK, "MSung B5HK"}, + + // Cambria + { CAMBRIA, "Cambria" }, + { CAMBRIA, "Caladea" }, + + // Calibri + { CALIBRI, "Calibri" }, + { CALIBRI, "Carlito" }, + }; + + static const size_t kFontCount = + sizeof(kFontEquivMap)/sizeof(kFontEquivMap[0]); + + // TODO(jungshik): If this loop turns out to be hot, turn + // the array to a static (hash)map to speed it up. + for (size_t i = 0; i < kFontCount; ++i) { + if (strcasecmp(kFontEquivMap[i].name, fontname) == 0) + return kFontEquivMap[i].clazz; + } + return OTHER; +} + + +// Return true if |font_a| and |font_b| are visually and at the metrics +// level interchangeable. +bool IsMetricCompatibleReplacement(const char* font_a, const char* font_b) +{ + FontEquivClass class_a = GetFontEquivClass(font_a); + FontEquivClass class_b = GetFontEquivClass(font_b); + + return class_a != OTHER && class_a == class_b; +} + +// Normally we only return exactly the font asked for. In last-resort +// cases, the request either doesn't specify a font or is one of the +// basic font names like "Sans", "Serif" or "Monospace". This function +// tells you whether a given request is for such a fallback. +bool IsFallbackFontAllowed(const SkString& family) { + const char* family_cstr = family.c_str(); + return family.isEmpty() || + strcasecmp(family_cstr, "sans") == 0 || + strcasecmp(family_cstr, "serif") == 0 || + strcasecmp(family_cstr, "monospace") == 0; +} + +// Retrieves |is_bold|, |is_italic| and |font_family| properties from |font|. +static int get_int(FcPattern* pattern, const char object[], int missing) { + int value; + if (FcPatternGetInteger(pattern, object, 0, &value) != FcResultMatch) { + return missing; + } + return value; +} + +static int map_range(SkScalar value, + SkScalar old_min, SkScalar old_max, + SkScalar new_min, SkScalar new_max) +{ + SkASSERT(old_min < old_max); + SkASSERT(new_min <= new_max); + return new_min + ((value - old_min) * (new_max - new_min) / (old_max - old_min)); +} + +struct MapRanges { + SkScalar old_val; + SkScalar new_val; +}; + +static SkScalar map_ranges(SkScalar val, MapRanges const ranges[], int rangesCount) { + // -Inf to [0] + if (val < ranges[0].old_val) { + return ranges[0].new_val; + } + + // Linear from [i] to [i+1] + for (int i = 0; i < rangesCount - 1; ++i) { + if (val < ranges[i+1].old_val) { + return map_range(val, ranges[i].old_val, ranges[i+1].old_val, + ranges[i].new_val, ranges[i+1].new_val); + } + } + + // From [n] to +Inf + // if (fcweight < Inf) + return ranges[rangesCount-1].new_val; +} + +#ifndef FC_WEIGHT_DEMILIGHT +#define FC_WEIGHT_DEMILIGHT 65 +#endif + +static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) { + typedef SkFontStyle SkFS; + + static constexpr MapRanges weightRanges[] = { + { FC_WEIGHT_THIN, SkFS::kThin_Weight }, + { FC_WEIGHT_EXTRALIGHT, SkFS::kExtraLight_Weight }, + { FC_WEIGHT_LIGHT, SkFS::kLight_Weight }, + { FC_WEIGHT_DEMILIGHT, 350 }, + { FC_WEIGHT_BOOK, 380 }, + { FC_WEIGHT_REGULAR, SkFS::kNormal_Weight }, + { FC_WEIGHT_MEDIUM, SkFS::kMedium_Weight }, + { FC_WEIGHT_DEMIBOLD, SkFS::kSemiBold_Weight }, + { FC_WEIGHT_BOLD, SkFS::kBold_Weight }, + { FC_WEIGHT_EXTRABOLD, SkFS::kExtraBold_Weight }, + { FC_WEIGHT_BLACK, SkFS::kBlack_Weight }, + { FC_WEIGHT_EXTRABLACK, SkFS::kExtraBlack_Weight }, + }; + SkScalar weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR), + weightRanges, SK_ARRAY_COUNT(weightRanges)); + + static constexpr MapRanges widthRanges[] = { + { FC_WIDTH_ULTRACONDENSED, SkFS::kUltraCondensed_Width }, + { FC_WIDTH_EXTRACONDENSED, SkFS::kExtraCondensed_Width }, + { FC_WIDTH_CONDENSED, SkFS::kCondensed_Width }, + { FC_WIDTH_SEMICONDENSED, SkFS::kSemiCondensed_Width }, + { FC_WIDTH_NORMAL, SkFS::kNormal_Width }, + { FC_WIDTH_SEMIEXPANDED, SkFS::kSemiExpanded_Width }, + { FC_WIDTH_EXPANDED, SkFS::kExpanded_Width }, + { FC_WIDTH_EXTRAEXPANDED, SkFS::kExtraExpanded_Width }, + { FC_WIDTH_ULTRAEXPANDED, SkFS::kUltraExpanded_Width }, + }; + SkScalar width = map_ranges(get_int(pattern, FC_WIDTH, FC_WIDTH_NORMAL), + widthRanges, SK_ARRAY_COUNT(widthRanges)); + + SkFS::Slant slant = SkFS::kUpright_Slant; + switch (get_int(pattern, FC_SLANT, FC_SLANT_ROMAN)) { + case FC_SLANT_ROMAN: slant = SkFS::kUpright_Slant; break; + case FC_SLANT_ITALIC : slant = SkFS::kItalic_Slant ; break; + case FC_SLANT_OBLIQUE: slant = SkFS::kOblique_Slant; break; + default: SkASSERT(false); break; + } + + return SkFontStyle(SkScalarRoundToInt(weight), SkScalarRoundToInt(width), slant); +} + +static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) { + typedef SkFontStyle SkFS; + + static constexpr MapRanges weightRanges[] = { + { SkFS::kThin_Weight, FC_WEIGHT_THIN }, + { SkFS::kExtraLight_Weight, FC_WEIGHT_EXTRALIGHT }, + { SkFS::kLight_Weight, FC_WEIGHT_LIGHT }, + { 350, FC_WEIGHT_DEMILIGHT }, + { 380, FC_WEIGHT_BOOK }, + { SkFS::kNormal_Weight, FC_WEIGHT_REGULAR }, + { SkFS::kMedium_Weight, FC_WEIGHT_MEDIUM }, + { SkFS::kSemiBold_Weight, FC_WEIGHT_DEMIBOLD }, + { SkFS::kBold_Weight, FC_WEIGHT_BOLD }, + { SkFS::kExtraBold_Weight, FC_WEIGHT_EXTRABOLD }, + { SkFS::kBlack_Weight, FC_WEIGHT_BLACK }, + { SkFS::kExtraBlack_Weight, FC_WEIGHT_EXTRABLACK }, + }; + int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges)); + + static constexpr MapRanges widthRanges[] = { + { SkFS::kUltraCondensed_Width, FC_WIDTH_ULTRACONDENSED }, + { SkFS::kExtraCondensed_Width, FC_WIDTH_EXTRACONDENSED }, + { SkFS::kCondensed_Width, FC_WIDTH_CONDENSED }, + { SkFS::kSemiCondensed_Width, FC_WIDTH_SEMICONDENSED }, + { SkFS::kNormal_Width, FC_WIDTH_NORMAL }, + { SkFS::kSemiExpanded_Width, FC_WIDTH_SEMIEXPANDED }, + { SkFS::kExpanded_Width, FC_WIDTH_EXPANDED }, + { SkFS::kExtraExpanded_Width, FC_WIDTH_EXTRAEXPANDED }, + { SkFS::kUltraExpanded_Width, FC_WIDTH_ULTRAEXPANDED }, + }; + int width = map_ranges(style.width(), widthRanges, SK_ARRAY_COUNT(widthRanges)); + + int slant = FC_SLANT_ROMAN; + switch (style.slant()) { + case SkFS::kUpright_Slant: slant = FC_SLANT_ROMAN ; break; + case SkFS::kItalic_Slant : slant = FC_SLANT_ITALIC ; break; + case SkFS::kOblique_Slant: slant = FC_SLANT_OBLIQUE; break; + default: SkASSERT(false); break; + } + + FcPatternAddInteger(pattern, FC_WEIGHT, weight); + FcPatternAddInteger(pattern, FC_WIDTH , width); + FcPatternAddInteger(pattern, FC_SLANT , slant); +} + +} // anonymous namespace + +/////////////////////////////////////////////////////////////////////////////// + +#define kMaxFontFamilyLength 2048 +#ifdef SK_FONT_CONFIG_INTERFACE_ONLY_ALLOW_SFNT_FONTS +const char* kFontFormatTrueType = "TrueType"; +const char* kFontFormatCFF = "CFF"; +#endif + +SkFontConfigInterfaceDirect::SkFontConfigInterfaceDirect() { + FCLocker lock; + + FcInit(); + + SkDEBUGCODE(fontconfiginterface_unittest();) +} + +SkFontConfigInterfaceDirect::~SkFontConfigInterfaceDirect() { +} + +bool SkFontConfigInterfaceDirect::isAccessible(const char* filename) { + if (access(filename, R_OK) != 0) { + return false; + } + return true; +} + +bool SkFontConfigInterfaceDirect::isValidPattern(FcPattern* pattern) { +#ifdef SK_FONT_CONFIG_INTERFACE_ONLY_ALLOW_SFNT_FONTS + const char* font_format = get_string(pattern, FC_FONTFORMAT); + if (font_format + && strcmp(font_format, kFontFormatTrueType) != 0 + && strcmp(font_format, kFontFormatCFF) != 0) + { + return false; + } +#endif + + // fontconfig can also return fonts which are unreadable + const char* c_filename = get_string(pattern, FC_FILE); + if (!c_filename) { + return false; + } + const char* sysroot = (const char*)FcConfigGetSysRoot(nullptr); + SkString resolvedFilename; + if (sysroot) { + resolvedFilename = sysroot; + resolvedFilename += c_filename; + c_filename = resolvedFilename.c_str(); + } + return this->isAccessible(c_filename); +} + +// Find matching font from |font_set| for the given font family. +FcPattern* SkFontConfigInterfaceDirect::MatchFont(FcFontSet* font_set, + const char* post_config_family, + const SkString& family) { + // Older versions of fontconfig have a bug where they cannot select + // only scalable fonts so we have to manually filter the results. + FcPattern* match = nullptr; + for (int i = 0; i < font_set->nfont; ++i) { + FcPattern* current = font_set->fonts[i]; + if (this->isValidPattern(current)) { + match = current; + break; + } + } + + if (match && !IsFallbackFontAllowed(family)) { + bool acceptable_substitute = false; + for (int id = 0; id < 255; ++id) { + const char* post_match_family = get_string(match, FC_FAMILY, id); + if (!post_match_family) + break; + acceptable_substitute = + (strcasecmp(post_config_family, post_match_family) == 0 || + // Workaround for Issue 12530: + // requested family: "Bitstream Vera Sans" + // post_config_family: "Arial" + // post_match_family: "Bitstream Vera Sans" + // -> We should treat this case as a good match. + strcasecmp(family.c_str(), post_match_family) == 0) || + IsMetricCompatibleReplacement(family.c_str(), post_match_family); + if (acceptable_substitute) + break; + } + if (!acceptable_substitute) + return nullptr; + } + + return match; +} + +bool SkFontConfigInterfaceDirect::matchFamilyName(const char familyName[], + SkFontStyle style, + FontIdentity* outIdentity, + SkString* outFamilyName, + SkFontStyle* outStyle) { + SkString familyStr(familyName ? familyName : ""); + if (familyStr.size() > kMaxFontFamilyLength) { + return false; + } + + FCLocker lock; + + FcPattern* pattern = FcPatternCreate(); + + if (familyName) { + FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName); + } + fcpattern_from_skfontstyle(style, pattern); + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(nullptr, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + // Font matching: + // CSS often specifies a fallback list of families: + // font-family: a, b, c, serif; + // However, fontconfig will always do its best to find *a* font when asked + // for something so we need a way to tell if the match which it has found is + // "good enough" for us. Otherwise, we can return nullptr which gets piped up + // and lets WebKit know to try the next CSS family name. However, fontconfig + // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we + // wish to support that. + // + // Thus, if a specific family is requested we set @family_requested. Then we + // record two strings: the family name after config processing and the + // family name after resolving. If the two are equal, it's a good match. + // + // So consider the case where a user has mapped Arial to Helvetica in their + // config. + // requested family: "Arial" + // post_config_family: "Helvetica" + // post_match_family: "Helvetica" + // -> good match + // + // and for a missing font: + // requested family: "Monaco" + // post_config_family: "Monaco" + // post_match_family: "Times New Roman" + // -> BAD match + // + // However, we special-case fallback fonts; see IsFallbackFontAllowed(). + + const char* post_config_family = get_string(pattern, FC_FAMILY); + if (!post_config_family) { + // we can just continue with an empty name, e.g. default font + post_config_family = ""; + } + + FcResult result; + FcFontSet* font_set = FcFontSort(nullptr, pattern, 0, nullptr, &result); + if (!font_set) { + FcPatternDestroy(pattern); + return false; + } + + FcPattern* match = this->MatchFont(font_set, post_config_family, familyStr); + if (!match) { + FcPatternDestroy(pattern); + FcFontSetDestroy(font_set); + return false; + } + + FcPatternDestroy(pattern); + + // From here out we just extract our results from 'match' + + post_config_family = get_string(match, FC_FAMILY); + if (!post_config_family) { + FcFontSetDestroy(font_set); + return false; + } + + const char* c_filename = get_string(match, FC_FILE); + if (!c_filename) { + FcFontSetDestroy(font_set); + return false; + } + const char* sysroot = (const char*)FcConfigGetSysRoot(nullptr); + SkString resolvedFilename; + if (sysroot) { + resolvedFilename = sysroot; + resolvedFilename += c_filename; + c_filename = resolvedFilename.c_str(); + } + + int face_index = get_int(match, FC_INDEX, 0); + + FcFontSetDestroy(font_set); + + if (outIdentity) { + outIdentity->fTTCIndex = face_index; + outIdentity->fString.set(c_filename); + } + if (outFamilyName) { + outFamilyName->set(post_config_family); + } + if (outStyle) { + *outStyle = skfontstyle_from_fcpattern(match); + } + return true; +} + +SkStreamAsset* SkFontConfigInterfaceDirect::openStream(const FontIdentity& identity) { + return SkStream::MakeFromFile(identity.fString.c_str()).release(); +} diff --git a/gfx/skia/skia/src/ports/SkFontConfigInterface_direct.h b/gfx/skia/skia/src/ports/SkFontConfigInterface_direct.h new file mode 100644 index 0000000000..f31047f170 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontConfigInterface_direct.h @@ -0,0 +1,39 @@ +/* + * Copyright 2009-2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* migrated from chrome/src/skia/ext/SkFontHost_fontconfig_direct.cpp */ +#ifndef SKFONTCONFIGINTERFACE_DIRECT_H_ +#define SKFONTCONFIGINTERFACE_DIRECT_H_ + +#include "include/ports/SkFontConfigInterface.h" + +#include + +class SkFontConfigInterfaceDirect : public SkFontConfigInterface { +public: + SkFontConfigInterfaceDirect(); + ~SkFontConfigInterfaceDirect() override; + + bool matchFamilyName(const char familyName[], + SkFontStyle requested, + FontIdentity* outFontIdentifier, + SkString* outFamilyName, + SkFontStyle* outStyle) override; + + SkStreamAsset* openStream(const FontIdentity&) override; + +protected: + virtual bool isAccessible(const char* filename); + +private: + bool isValidPattern(FcPattern* pattern); + FcPattern* MatchFont(FcFontSet* font_set, const char* post_config_family, + const SkString& family); + typedef SkFontConfigInterface INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/ports/SkFontConfigInterface_direct_factory.cpp b/gfx/skia/skia/src/ports/SkFontConfigInterface_direct_factory.cpp new file mode 100644 index 0000000000..7b70b64136 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontConfigInterface_direct_factory.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2009-2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkOnce.h" +#include "src/ports/SkFontConfigInterface_direct.h" + +SkFontConfigInterface* SkFontConfigInterface::GetSingletonDirectInterface() { + static SkFontConfigInterface* singleton; + static SkOnce once; + once([]{ singleton = new SkFontConfigInterfaceDirect(); }); + return singleton; +} diff --git a/gfx/skia/skia/src/ports/SkFontConfigTypeface.h b/gfx/skia/skia/src/ports/SkFontConfigTypeface.h new file mode 100644 index 0000000000..269486de74 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontConfigTypeface.h @@ -0,0 +1,85 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontConfigTypeface_DEFINED +#define SkFontConfigTypeface_DEFINED + +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/ports/SkFontConfigInterface.h" +#include "src/core/SkFontDescriptor.h" +#include "src/ports/SkFontHost_FreeType_common.h" + +class SkFontDescriptor; + +class SkTypeface_FCI : public SkTypeface_FreeType { + sk_sp fFCI; + SkFontConfigInterface::FontIdentity fIdentity; + SkString fFamilyName; + std::unique_ptr fFontData; + +public: + static SkTypeface_FCI* Create(sk_sp fci, + const SkFontConfigInterface::FontIdentity& fi, + SkString familyName, + const SkFontStyle& style) + { + return new SkTypeface_FCI(std::move(fci), fi, std::move(familyName), style); + } + + static SkTypeface_FCI* Create(std::unique_ptr data, + SkString familyName, SkFontStyle style, bool isFixedPitch) + { + return new SkTypeface_FCI(std::move(data), std::move(familyName), style, isFixedPitch); + } + + const SkFontConfigInterface::FontIdentity& getIdentity() const { + return fIdentity; + } + + sk_sp onMakeClone(const SkFontArguments& args) const override { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + return sk_sp(new SkTypeface_FCI(std::move(data), + fFamilyName, + this->fontStyle(), + this->isFixedPitch())); + } + +protected: + SkTypeface_FCI(sk_sp fci, + const SkFontConfigInterface::FontIdentity& fi, + SkString familyName, + const SkFontStyle& style) + : INHERITED(style, false) + , fFCI(std::move(fci)) + , fIdentity(fi) + , fFamilyName(std::move(familyName)) + , fFontData(nullptr) {} + + SkTypeface_FCI(std::unique_ptr data, + SkString familyName, SkFontStyle style, bool isFixedPitch) + : INHERITED(style, isFixedPitch) + , fFamilyName(std::move(familyName)) + , fFontData(std::move(data)) + { + SkASSERT(fFontData); + fIdentity.fTTCIndex = fFontData->getIndex(); + } + + void onGetFamilyName(SkString* familyName) const override { *familyName = fFamilyName; } + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override; + std::unique_ptr onOpenStream(int* ttcIndex) const override; + std::unique_ptr onMakeFontData() const override; + +private: + typedef SkTypeface_FreeType INHERITED; +}; + +#endif // SkFontConfigTypeface_DEFINED diff --git a/gfx/skia/skia/src/ports/SkFontHost_FreeType.cpp b/gfx/skia/skia/src/ports/SkFontHost_FreeType.cpp new file mode 100644 index 0000000000..34f9f47549 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontHost_FreeType.cpp @@ -0,0 +1,2034 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/core/SkFontMetrics.h" +#include "include/core/SkPath.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkFDot6.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMask.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkScalerContext.h" +#include "src/ports/SkFontHost_FreeType_common.h" +#include "src/sfnt/SkOTUtils.h" +#include "src/utils/SkCallableTraits.h" +#include "src/utils/SkMatrix22.h" + +#include + +#include +#include FT_ADVANCES_H +#include FT_BITMAP_H +#ifdef FT_COLOR_H +# include FT_COLOR_H +#endif +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H +#include FT_MODULE_H +#include FT_MULTIPLE_MASTERS_H +#include FT_OUTLINE_H +#include FT_SIZES_H +#include FT_SYSTEM_H +#include FT_TRUETYPE_TABLES_H +#include FT_TYPE1_TABLES_H +#include FT_XFREE86_H + +// SK_FREETYPE_MINIMUM_RUNTIME_VERSION 0x +// Flag SK_FREETYPE_DLOPEN: also try dlopen to get newer features. +#define SK_FREETYPE_DLOPEN (0x1) +#ifndef SK_FREETYPE_MINIMUM_RUNTIME_VERSION +# if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) || defined (SK_BUILD_FOR_GOOGLE3) +# define SK_FREETYPE_MINIMUM_RUNTIME_VERSION (((FREETYPE_MAJOR) << 24) | ((FREETYPE_MINOR) << 16) | ((FREETYPE_PATCH) << 8)) +# else +# define SK_FREETYPE_MINIMUM_RUNTIME_VERSION ((2 << 24) | (3 << 16) | (11 << 8) | (SK_FREETYPE_DLOPEN)) +# endif +#endif +#if SK_FREETYPE_MINIMUM_RUNTIME_VERSION & SK_FREETYPE_DLOPEN +# include +#endif + +// FT_LOAD_COLOR and the corresponding FT_Pixel_Mode::FT_PIXEL_MODE_BGRA +// were introduced in FreeType 2.5.0. +// The following may be removed once FreeType 2.5.0 is required to build. +#ifndef FT_LOAD_COLOR +# define FT_LOAD_COLOR ( 1L << 20 ) +# define FT_PIXEL_MODE_BGRA 7 +#endif + +// FT_LOAD_BITMAP_METRICS_ONLY was introduced in FreeType 2.7.1 +// The following may be removed once FreeType 2.7.1 is required to build. +#ifndef FT_LOAD_BITMAP_METRICS_ONLY +# define FT_LOAD_BITMAP_METRICS_ONLY ( 1L << 22 ) +#endif + +// FT_VAR_AXIS_FLAG_HIDDEN was introduced in FreeType 2.8.1 +// The variation axis should not be exposed to user interfaces. +#ifndef FT_VAR_AXIS_FLAG_HIDDEN +# define FT_VAR_AXIS_FLAG_HIDDEN 1 +#endif + +//#define ENABLE_GLYPH_SPEW // for tracing calls +//#define DUMP_STRIKE_CREATION +//#define SK_FONTHOST_FREETYPE_RUNTIME_VERSION +//#define SK_GAMMA_APPLY_TO_A8 + +#if 1 + #define LOG_INFO(...) +#else + #define LOG_INFO SkDEBUGF +#endif + +static bool isLCD(const SkScalerContextRec& rec) { + return SkMask::kLCD16_Format == rec.fMaskFormat; +} + +static SkScalar SkFT_FixedToScalar(FT_Fixed x) { + return SkFixedToScalar(x); +} + +////////////////////////////////////////////////////////////////////////// + +using FT_Alloc_size_t = SkCallableTraits::argument<1>::type; +static_assert(std::is_same::value || + std::is_same::value,""); + +extern "C" { + static void* sk_ft_alloc(FT_Memory, FT_Alloc_size_t size) { + return sk_malloc_throw(size); + } + static void sk_ft_free(FT_Memory, void* block) { + sk_free(block); + } + static void* sk_ft_realloc(FT_Memory, FT_Alloc_size_t cur_size, + FT_Alloc_size_t new_size, void* block) { + return sk_realloc_throw(block, new_size); + } +}; +FT_MemoryRec_ gFTMemory = { nullptr, sk_ft_alloc, sk_ft_free, sk_ft_realloc }; + +class FreeTypeLibrary : SkNoncopyable { +public: + FreeTypeLibrary() + : fGetVarDesignCoordinates(nullptr) + , fGetVarAxisFlags(nullptr) + , fLibrary(nullptr) + , fIsLCDSupported(false) + , fLightHintingIsYOnly(false) + , fLCDExtra(0) + { + if (FT_New_Library(&gFTMemory, &fLibrary)) { + return; + } + FT_Add_Default_Modules(fLibrary); + + // When using dlsym + // *(void**)(&procPtr) = dlsym(self, "proc"); + // is non-standard, but safe for POSIX. Cannot write + // *reinterpret_cast(&procPtr) = dlsym(self, "proc"); + // because clang has not implemented DR573. See http://clang.llvm.org/cxx_dr_status.html . + + FT_Int major, minor, patch; + FT_Library_Version(fLibrary, &major, &minor, &patch); + +#if SK_FREETYPE_MINIMUM_RUNTIME_VERSION >= 0x02070100 + fGetVarDesignCoordinates = FT_Get_Var_Design_Coordinates; +#elif SK_FREETYPE_MINIMUM_RUNTIME_VERSION & SK_FREETYPE_DLOPEN + if (major > 2 || ((major == 2 && minor > 7) || (major == 2 && minor == 7 && patch >= 0))) { + //The FreeType library is already loaded, so symbols are available in process. + void* self = dlopen(nullptr, RTLD_LAZY); + if (self) { + *(void**)(&fGetVarDesignCoordinates) = dlsym(self, "FT_Get_Var_Design_Coordinates"); + dlclose(self); + } + } +#endif + +#if SK_FREETYPE_MINIMUM_RUNTIME_VERSION >= 0x02070200 + FT_Set_Default_Properties(fLibrary); +#elif SK_FREETYPE_MINIMUM_RUNTIME_VERSION & SK_FREETYPE_DLOPEN + if (major > 2 || ((major == 2 && minor > 7) || (major == 2 && minor == 7 && patch >= 1))) { + //The FreeType library is already loaded, so symbols are available in process. + void* self = dlopen(nullptr, RTLD_LAZY); + if (self) { + FT_Set_Default_PropertiesProc setDefaultProperties; + *(void**)(&setDefaultProperties) = dlsym(self, "FT_Set_Default_Properties"); + dlclose(self); + + if (setDefaultProperties) { + setDefaultProperties(fLibrary); + } + } + } +#endif + +// The 'light' hinting is vertical only starting in 2.8.0. +#if SK_FREETYPE_MINIMUM_RUNTIME_VERSION >= 0x02080000 + fLightHintingIsYOnly = true; +#else + if (major > 2 || ((major == 2 && minor > 8) || (major == 2 && minor == 8 && patch >= 0))) { + fLightHintingIsYOnly = true; + } +#endif + + +#if SK_FREETYPE_MINIMUM_RUNTIME_VERSION >= 0x02080100 + fGetVarAxisFlags = FT_Get_Var_Axis_Flags; +#elif SK_FREETYPE_MINIMUM_RUNTIME_VERSION & SK_FREETYPE_DLOPEN + if (major > 2 || ((major == 2 && minor > 7) || (major == 2 && minor == 7 && patch >= 0))) { + //The FreeType library is already loaded, so symbols are available in process. + void* self = dlopen(nullptr, RTLD_LAZY); + if (self) { + *(void**)(&fGetVarAxisFlags) = dlsym(self, "FT_Get_Var_Axis_Flags"); + dlclose(self); + } + } +#endif + + // Setup LCD filtering. This reduces color fringes for LCD smoothed glyphs. + // The default has changed over time, so this doesn't mean the same thing to all users. + if (FT_Library_SetLcdFilter(fLibrary, FT_LCD_FILTER_DEFAULT) == 0) { + fIsLCDSupported = true; + fLCDExtra = 2; //Using a filter adds one full pixel to each side. + } + } + ~FreeTypeLibrary() { + if (fLibrary) { + FT_Done_Library(fLibrary); + } + } + + FT_Library library() { return fLibrary; } + bool isLCDSupported() { return fIsLCDSupported; } + int lcdExtra() { return fLCDExtra; } + bool lightHintingIsYOnly() { return fLightHintingIsYOnly; } + + // FT_Get_{MM,Var}_{Blend,Design}_Coordinates were added in FreeType 2.7.1. + // Prior to this there was no way to get the coordinates out of the FT_Face. + // This wasn't too bad because you needed to specify them anyway, and the clamp was provided. + // However, this doesn't work when face_index specifies named variations as introduced in 2.6.1. + using FT_Get_Var_Blend_CoordinatesProc = FT_Error (*)(FT_Face, FT_UInt, FT_Fixed*); + FT_Get_Var_Blend_CoordinatesProc fGetVarDesignCoordinates; + + // FT_Get_Var_Axis_Flags was introduced in FreeType 2.8.1 + // Get the ‘flags’ field of an OpenType Variation Axis Record. + // Not meaningful for Adobe MM fonts (‘*flags’ is always zero). + using FT_Get_Var_Axis_FlagsProc = FT_Error (*)(FT_MM_Var*, FT_UInt, FT_UInt*); + FT_Get_Var_Axis_FlagsProc fGetVarAxisFlags; + +private: + FT_Library fLibrary; + bool fIsLCDSupported; + bool fLightHintingIsYOnly; + int fLCDExtra; + + // FT_Library_SetLcdFilterWeights was introduced in FreeType 2.4.0. + // The following platforms provide FreeType of at least 2.4.0. + // Ubuntu >= 11.04 (previous deprecated April 2013) + // Debian >= 6.0 (good) + // OpenSuse >= 11.4 (previous deprecated January 2012 / Nov 2013 for Evergreen 11.2) + // Fedora >= 14 (good) + // Android >= Gingerbread (good) + // RHEL >= 7 (6 has 2.3.11, EOL Nov 2020, Phase 3 May 2017) + using FT_Library_SetLcdFilterWeightsProc = FT_Error (*)(FT_Library, unsigned char*); + + // FreeType added the ability to read global properties in 2.7.0. After 2.7.1 a means for users + // of FT_New_Library to request these global properties to be read was added. + using FT_Set_Default_PropertiesProc = void (*)(FT_Library); +}; + +struct SkFaceRec; + +static SkMutex& f_t_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} + +static FreeTypeLibrary* gFTLibrary; +static SkFaceRec* gFaceRecHead; + +// Private to ref_ft_library and unref_ft_library +static int gFTCount; + +// Caller must lock f_t_mutex() before calling this function. +static bool ref_ft_library() { + f_t_mutex().assertHeld(); + SkASSERT(gFTCount >= 0); + + if (0 == gFTCount) { + SkASSERT(nullptr == gFTLibrary); + gFTLibrary = new FreeTypeLibrary; + } + ++gFTCount; + return gFTLibrary->library(); +} + +// Caller must lock f_t_mutex() before calling this function. +static void unref_ft_library() { + f_t_mutex().assertHeld(); + SkASSERT(gFTCount > 0); + + --gFTCount; + if (0 == gFTCount) { + SkASSERT(nullptr == gFaceRecHead); + SkASSERT(nullptr != gFTLibrary); + delete gFTLibrary; + SkDEBUGCODE(gFTLibrary = nullptr;) + } +} + +/////////////////////////////////////////////////////////////////////////// + +struct SkFaceRec { + SkFaceRec* fNext; + std::unique_ptr> fFace; + FT_StreamRec fFTStream; + std::unique_ptr fSkStream; + uint32_t fRefCnt; + uint32_t fFontID; + + // FreeType prior to 2.7.1 does not implement retreiving variation design metrics. + // Cache the variation design metrics used to create the font if the user specifies them. + SkAutoSTMalloc<4, SkFixed> fAxes; + int fAxesCount; + + // FreeType from 2.6.1 (14d6b5d7) until 2.7.0 (ee3f36f6b38) uses font_index for both font index + // and named variation index on input, but masks the named variation index part on output. + // Manually keep track of when a named variation is requested for 2.6.1 until 2.7.1. + bool fNamedVariationSpecified; + + SkFaceRec(std::unique_ptr stream, uint32_t fontID); +}; + +extern "C" { + static unsigned long sk_ft_stream_io(FT_Stream ftStream, + unsigned long offset, + unsigned char* buffer, + unsigned long count) + { + SkStreamAsset* stream = static_cast(ftStream->descriptor.pointer); + + if (count) { + if (!stream->seek(offset)) { + return 0; + } + count = stream->read(buffer, count); + } + return count; + } + + static void sk_ft_stream_close(FT_Stream) {} +} + +SkFaceRec::SkFaceRec(std::unique_ptr stream, uint32_t fontID) + : fNext(nullptr), fSkStream(std::move(stream)), fRefCnt(1), fFontID(fontID) + , fAxesCount(0), fNamedVariationSpecified(false) +{ + sk_bzero(&fFTStream, sizeof(fFTStream)); + fFTStream.size = fSkStream->getLength(); + fFTStream.descriptor.pointer = fSkStream.get(); + fFTStream.read = sk_ft_stream_io; + fFTStream.close = sk_ft_stream_close; +} + +static void ft_face_setup_axes(SkFaceRec* rec, const SkFontData& data) { + if (!(rec->fFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + return; + } + + // If a named variation is requested, don't overwrite the named variation's position. + if (data.getIndex() > 0xFFFF) { + rec->fNamedVariationSpecified = true; + return; + } + + SkDEBUGCODE( + FT_MM_Var* variations = nullptr; + if (FT_Get_MM_Var(rec->fFace.get(), &variations)) { + LOG_INFO("INFO: font %s claims variations, but none found.\n", + rec->fFace->family_name); + return; + } + SkAutoFree autoFreeVariations(variations); + + if (static_cast(data.getAxisCount()) != variations->num_axis) { + LOG_INFO("INFO: font %s has %d variations, but %d were specified.\n", + rec->fFace->family_name, variations->num_axis, data.getAxisCount()); + return; + } + ) + + SkAutoSTMalloc<4, FT_Fixed> coords(data.getAxisCount()); + for (int i = 0; i < data.getAxisCount(); ++i) { + coords[i] = data.getAxis()[i]; + } + if (FT_Set_Var_Design_Coordinates(rec->fFace.get(), data.getAxisCount(), coords.get())) { + LOG_INFO("INFO: font %s has variations, but specified variations could not be set.\n", + rec->fFace->family_name); + return; + } + + rec->fAxesCount = data.getAxisCount(); + rec->fAxes.reset(rec->fAxesCount); + for (int i = 0; i < rec->fAxesCount; ++i) { + rec->fAxes[i] = data.getAxis()[i]; + } +} + +// Will return nullptr on failure +// Caller must lock f_t_mutex() before calling this function. +static SkFaceRec* ref_ft_face(const SkTypeface* typeface) { + f_t_mutex().assertHeld(); + + const SkFontID fontID = typeface->uniqueID(); + SkFaceRec* cachedRec = gFaceRecHead; + while (cachedRec) { + if (cachedRec->fFontID == fontID) { + SkASSERT(cachedRec->fFace); + cachedRec->fRefCnt += 1; + return cachedRec; + } + cachedRec = cachedRec->fNext; + } + + std::unique_ptr data = typeface->makeFontData(); + if (nullptr == data || !data->hasStream()) { + return nullptr; + } + + std::unique_ptr rec(new SkFaceRec(data->detachStream(), fontID)); + + FT_Open_Args args; + memset(&args, 0, sizeof(args)); + const void* memoryBase = rec->fSkStream->getMemoryBase(); + if (memoryBase) { + args.flags = FT_OPEN_MEMORY; + args.memory_base = (const FT_Byte*)memoryBase; + args.memory_size = rec->fSkStream->getLength(); + } else { + args.flags = FT_OPEN_STREAM; + args.stream = &rec->fFTStream; + } + + { + FT_Face rawFace; + FT_Error err = FT_Open_Face(gFTLibrary->library(), &args, data->getIndex(), &rawFace); + if (err) { + SK_TRACEFTR(err, "unable to open font '%x'", fontID); + return nullptr; + } + rec->fFace.reset(rawFace); + } + SkASSERT(rec->fFace); + + ft_face_setup_axes(rec.get(), *data); + + // FreeType will set the charmap to the "most unicode" cmap if it exists. + // If there are no unicode cmaps, the charmap is set to nullptr. + // However, "symbol" cmaps should also be considered "fallback unicode" cmaps + // because they are effectively private use area only (even if they aren't). + // This is the last on the fallback list at + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html + if (!rec->fFace->charmap) { + FT_Select_Charmap(rec->fFace.get(), FT_ENCODING_MS_SYMBOL); + } + + rec->fNext = gFaceRecHead; + gFaceRecHead = rec.get(); + return rec.release(); +} + +// Caller must lock f_t_mutex() before calling this function. +// Marked extern because vc++ does not support internal linkage template parameters. +extern /*static*/ void unref_ft_face(SkFaceRec* faceRec) { + f_t_mutex().assertHeld(); + + SkFaceRec* rec = gFaceRecHead; + SkFaceRec* prev = nullptr; + while (rec) { + SkFaceRec* next = rec->fNext; + if (rec->fFace == faceRec->fFace) { + if (--rec->fRefCnt == 0) { + if (prev) { + prev->fNext = next; + } else { + gFaceRecHead = next; + } + delete rec; + } + return; + } + prev = rec; + rec = next; + } + SkDEBUGFAIL("shouldn't get here, face not in list"); +} + +class AutoFTAccess { +public: + AutoFTAccess(const SkTypeface* tf) : fFaceRec(nullptr) { + f_t_mutex().acquire(); + SkASSERT_RELEASE(ref_ft_library()); + fFaceRec = ref_ft_face(tf); + } + + ~AutoFTAccess() { + if (fFaceRec) { + unref_ft_face(fFaceRec); + } + unref_ft_library(); + f_t_mutex().release(); + } + + FT_Face face() { return fFaceRec ? fFaceRec->fFace.get() : nullptr; } + int getAxesCount() { return fFaceRec ? fFaceRec->fAxesCount : 0; } + SkFixed* getAxes() { return fFaceRec ? fFaceRec->fAxes.get() : nullptr; } + bool isNamedVariationSpecified() { + return fFaceRec ? fFaceRec->fNamedVariationSpecified : false; + } + +private: + SkFaceRec* fFaceRec; +}; + +/////////////////////////////////////////////////////////////////////////// + +class SkScalerContext_FreeType : public SkScalerContext_FreeType_Base { +public: + SkScalerContext_FreeType(sk_sp, + const SkScalerContextEffects&, + const SkDescriptor* desc); + ~SkScalerContext_FreeType() override; + + bool success() const { + return fFTSize != nullptr && fFace != nullptr; + } + +protected: + unsigned generateGlyphCount() override; + bool generateAdvance(SkGlyph* glyph) override; + void generateMetrics(SkGlyph* glyph) override; + void generateImage(const SkGlyph& glyph) override; + bool generatePath(SkGlyphID glyphID, SkPath* path) override; + void generateFontMetrics(SkFontMetrics*) override; + +private: + using UnrefFTFace = SkFunctionWrapper; + std::unique_ptr fFaceRec; + + FT_Face fFace; // Borrowed face from gFaceRecHead. + FT_Size fFTSize; // The size on the fFace for this scaler. + FT_Int fStrikeIndex; + + /** The rest of the matrix after FreeType handles the size. + * With outline font rasterization this is handled by FreeType with FT_Set_Transform. + * With bitmap only fonts this matrix must be applied to scale the bitmap. + */ + SkMatrix fMatrix22Scalar; + /** Same as fMatrix22Scalar, but in FreeType units and space. */ + FT_Matrix fMatrix22; + /** The actual size requested. */ + SkVector fScale; + + uint32_t fLoadGlyphFlags; + bool fDoLinearMetrics; + bool fLCDIsVert; + + FT_Error setupSize(); + void getBBoxForCurrentGlyph(const SkGlyph* glyph, FT_BBox* bbox, + bool snapToPixelBoundary = false); + bool getCBoxForLetter(char letter, FT_BBox* bbox); + // Caller must lock f_t_mutex() before calling this function. + void updateGlyphIfLCD(SkGlyph* glyph); + // Caller must lock f_t_mutex() before calling this function. + // update FreeType2 glyph slot with glyph emboldened + void emboldenIfNeeded(FT_Face face, FT_GlyphSlot glyph, SkGlyphID gid); + bool shouldSubpixelBitmap(const SkGlyph&, const SkMatrix&); +}; + +/////////////////////////////////////////////////////////////////////////// + +static bool canEmbed(FT_Face face) { + FT_UShort fsType = FT_Get_FSType_Flags(face); + return (fsType & (FT_FSTYPE_RESTRICTED_LICENSE_EMBEDDING | + FT_FSTYPE_BITMAP_EMBEDDING_ONLY)) == 0; +} + +static bool canSubset(FT_Face face) { + FT_UShort fsType = FT_Get_FSType_Flags(face); + return (fsType & FT_FSTYPE_NO_SUBSETTING) == 0; +} + +static SkAdvancedTypefaceMetrics::FontType get_font_type(FT_Face face) { + const char* fontType = FT_Get_X11_Font_Format(face); + static struct { const char* s; SkAdvancedTypefaceMetrics::FontType t; } values[] = { + { "Type 1", SkAdvancedTypefaceMetrics::kType1_Font }, + { "CID Type 1", SkAdvancedTypefaceMetrics::kType1CID_Font }, + { "CFF", SkAdvancedTypefaceMetrics::kCFF_Font }, + { "TrueType", SkAdvancedTypefaceMetrics::kTrueType_Font }, + }; + for(const auto& v : values) { if (strcmp(fontType, v.s) == 0) { return v.t; } } + return SkAdvancedTypefaceMetrics::kOther_Font; +} + +std::unique_ptr SkTypeface_FreeType::onGetAdvancedMetrics() const { + AutoFTAccess fta(this); + FT_Face face = fta.face(); + if (!face) { + return nullptr; + } + + std::unique_ptr info(new SkAdvancedTypefaceMetrics); + info->fPostScriptName.set(FT_Get_Postscript_Name(face)); + info->fFontName = info->fPostScriptName; + + if (FT_HAS_MULTIPLE_MASTERS(face)) { + info->fFlags |= SkAdvancedTypefaceMetrics::kMultiMaster_FontFlag; + } + if (!canEmbed(face)) { + info->fFlags |= SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag; + } + if (!canSubset(face)) { + info->fFlags |= SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag; + } + + info->fType = get_font_type(face); + info->fStyle = (SkAdvancedTypefaceMetrics::StyleFlags)0; + if (FT_IS_FIXED_WIDTH(face)) { + info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style; + } + if (face->style_flags & FT_STYLE_FLAG_ITALIC) { + info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style; + } + + PS_FontInfoRec psFontInfo; + TT_Postscript* postTable; + if (FT_Get_PS_Font_Info(face, &psFontInfo) == 0) { + info->fItalicAngle = psFontInfo.italic_angle; + } else if ((postTable = (TT_Postscript*)FT_Get_Sfnt_Table(face, ft_sfnt_post)) != nullptr) { + info->fItalicAngle = SkFixedFloorToInt(postTable->italicAngle); + } else { + info->fItalicAngle = 0; + } + + info->fAscent = face->ascender; + info->fDescent = face->descender; + + TT_PCLT* pcltTable; + TT_OS2* os2Table; + if ((pcltTable = (TT_PCLT*)FT_Get_Sfnt_Table(face, ft_sfnt_pclt)) != nullptr) { + info->fCapHeight = pcltTable->CapHeight; + uint8_t serif_style = pcltTable->SerifStyle & 0x3F; + if (2 <= serif_style && serif_style <= 6) { + info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style; + } else if (9 <= serif_style && serif_style <= 12) { + info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style; + } + } else if (((os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2)) != nullptr) && + // sCapHeight is available only when version 2 or later. + os2Table->version != 0xFFFF && + os2Table->version >= 2) + { + info->fCapHeight = os2Table->sCapHeight; + } + info->fBBox = SkIRect::MakeLTRB(face->bbox.xMin, face->bbox.yMax, + face->bbox.xMax, face->bbox.yMin); + return info; +} + +void SkTypeface_FreeType::getGlyphToUnicodeMap(SkUnichar* dstArray) const { + SkASSERT(dstArray); + AutoFTAccess fta(this); + FT_Face face = fta.face(); + FT_Long numGlyphs = face->num_glyphs; + sk_bzero(dstArray, sizeof(SkUnichar) * numGlyphs); + + FT_UInt glyphIndex; + SkUnichar charCode = FT_Get_First_Char(face, &glyphIndex); + while (glyphIndex) { + SkASSERT(glyphIndex < SkToUInt(numGlyphs)); + // Use the first character that maps to this glyphID. https://crbug.com/359065 + if (0 == dstArray[glyphIndex]) { + dstArray[glyphIndex] = charCode; + } + charCode = FT_Get_Next_Char(face, charCode, &glyphIndex); + } +} + +void SkTypeface_FreeType::getPostScriptGlyphNames(SkString* dstArray) const { + SkASSERT(dstArray); + AutoFTAccess fta(this); + FT_Face face = fta.face(); + if (face && FT_HAS_GLYPH_NAMES(face)) { + for (int gID = 0; gID < face->num_glyphs; gID++) { + char glyphName[128]; // PS limit for names is 127 bytes. + FT_Get_Glyph_Name(face, gID, glyphName, 128); + dstArray[gID] = glyphName; + } + } +} + +/////////////////////////////////////////////////////////////////////////// + +static bool bothZero(SkScalar a, SkScalar b) { + return 0 == a && 0 == b; +} + +// returns false if there is any non-90-rotation or skew +static bool isAxisAligned(const SkScalerContextRec& rec) { + return 0 == rec.fPreSkewX && + (bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) || + bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1])); +} + +SkScalerContext* SkTypeface_FreeType::onCreateScalerContext(const SkScalerContextEffects& effects, + const SkDescriptor* desc) const { + auto c = skstd::make_unique( + sk_ref_sp(const_cast(this)), effects, desc); + if (!c->success()) { + return nullptr; + } + return c.release(); +} + +std::unique_ptr SkTypeface_FreeType::cloneFontData( + const SkFontArguments& args) const { + SkString name; + AutoFTAccess fta(this); + FT_Face face = fta.face(); + Scanner::AxisDefinitions axisDefinitions; + + if (!Scanner::GetAxes(face, &axisDefinitions)) { + return nullptr; + } + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + Scanner::computeAxisValues(axisDefinitions, args.getVariationDesignPosition(), + axisValues, name); + int ttcIndex; + std::unique_ptr stream = this->openStream(&ttcIndex); + return skstd::make_unique(std::move(stream), ttcIndex, axisValues.get(), + axisDefinitions.count()); +} + +void SkTypeface_FreeType::onFilterRec(SkScalerContextRec* rec) const { + //BOGUS: http://code.google.com/p/chromium/issues/detail?id=121119 + //Cap the requested size as larger sizes give bogus values. + //Remove when http://code.google.com/p/skia/issues/detail?id=554 is fixed. + //Note that this also currently only protects against large text size requests, + //the total matrix is not taken into account here. + if (rec->fTextSize > SkIntToScalar(1 << 14)) { + rec->fTextSize = SkIntToScalar(1 << 14); + } + + if (isLCD(*rec)) { + // TODO: re-work so that FreeType is set-up and selected by the SkFontMgr. + SkAutoMutexExclusive ama(f_t_mutex()); + ref_ft_library(); + if (!gFTLibrary->isLCDSupported()) { + // If the runtime Freetype library doesn't support LCD, disable it here. + rec->fMaskFormat = SkMask::kA8_Format; + } + unref_ft_library(); + } + + SkFontHinting h = rec->getHinting(); + if (SkFontHinting::kFull == h && !isLCD(*rec)) { + // collapse full->normal hinting if we're not doing LCD + h = SkFontHinting::kNormal; + } + + // rotated text looks bad with hinting, so we disable it as needed + if (!isAxisAligned(*rec)) { + h = SkFontHinting::kNone; + } + rec->setHinting(h); + +#ifndef SK_GAMMA_APPLY_TO_A8 + if (!isLCD(*rec)) { + // SRGBTODO: Is this correct? Do we want contrast boost? + rec->ignorePreBlend(); + } +#endif +} + +int SkTypeface_FreeType::GetUnitsPerEm(FT_Face face) { + if (!face) { + return 0; + } + + SkScalar upem = SkIntToScalar(face->units_per_EM); + // At least some versions of FreeType set face->units_per_EM to 0 for bitmap only fonts. + if (upem == 0) { + TT_Header* ttHeader = (TT_Header*)FT_Get_Sfnt_Table(face, ft_sfnt_head); + if (ttHeader) { + upem = SkIntToScalar(ttHeader->Units_Per_EM); + } + } + return upem; +} + +int SkTypeface_FreeType::onGetUPEM() const { + AutoFTAccess fta(this); + FT_Face face = fta.face(); + return GetUnitsPerEm(face); +} + +bool SkTypeface_FreeType::onGetKerningPairAdjustments(const uint16_t glyphs[], + int count, int32_t adjustments[]) const { + AutoFTAccess fta(this); + FT_Face face = fta.face(); + if (!face || !FT_HAS_KERNING(face)) { + return false; + } + + for (int i = 0; i < count - 1; ++i) { + FT_Vector delta; + FT_Error err = FT_Get_Kerning(face, glyphs[i], glyphs[i+1], + FT_KERNING_UNSCALED, &delta); + if (err) { + return false; + } + adjustments[i] = delta.x; + } + return true; +} + +/** Returns the bitmap strike equal to or just larger than the requested size. */ +static FT_Int chooseBitmapStrike(FT_Face face, FT_F26Dot6 scaleY) { + if (face == nullptr) { + LOG_INFO("chooseBitmapStrike aborted due to nullptr face.\n"); + return -1; + } + + FT_Pos requestedPPEM = scaleY; // FT_Bitmap_Size::y_ppem is in 26.6 format. + FT_Int chosenStrikeIndex = -1; + FT_Pos chosenPPEM = 0; + for (FT_Int strikeIndex = 0; strikeIndex < face->num_fixed_sizes; ++strikeIndex) { + FT_Pos strikePPEM = face->available_sizes[strikeIndex].y_ppem; + if (strikePPEM == requestedPPEM) { + // exact match - our search stops here + return strikeIndex; + } else if (chosenPPEM < requestedPPEM) { + // attempt to increase chosenPPEM + if (chosenPPEM < strikePPEM) { + chosenPPEM = strikePPEM; + chosenStrikeIndex = strikeIndex; + } + } else { + // attempt to decrease chosenPPEM, but not below requestedPPEM + if (requestedPPEM < strikePPEM && strikePPEM < chosenPPEM) { + chosenPPEM = strikePPEM; + chosenStrikeIndex = strikeIndex; + } + } + } + return chosenStrikeIndex; +} + +SkScalerContext_FreeType::SkScalerContext_FreeType(sk_sp typeface, + const SkScalerContextEffects& effects, + const SkDescriptor* desc) + : SkScalerContext_FreeType_Base(std::move(typeface), effects, desc) + , fFace(nullptr) + , fFTSize(nullptr) + , fStrikeIndex(-1) +{ + SkAutoMutexExclusive ac(f_t_mutex()); + SkASSERT_RELEASE(ref_ft_library()); + + fFaceRec.reset(ref_ft_face(this->getTypeface())); + + // load the font file + if (nullptr == fFaceRec) { + LOG_INFO("Could not create FT_Face.\n"); + return; + } + + fLCDIsVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); + + // compute the flags we send to Load_Glyph + bool linearMetrics = this->isLinearMetrics(); + { + FT_Int32 loadFlags = FT_LOAD_DEFAULT; + + if (SkMask::kBW_Format == fRec.fMaskFormat) { + // See http://code.google.com/p/chromium/issues/detail?id=43252#c24 + loadFlags = FT_LOAD_TARGET_MONO; + if (fRec.getHinting() == SkFontHinting::kNone) { + loadFlags = FT_LOAD_NO_HINTING; + linearMetrics = true; + } + } else { + switch (fRec.getHinting()) { + case SkFontHinting::kNone: + loadFlags = FT_LOAD_NO_HINTING; + linearMetrics = true; + break; + case SkFontHinting::kSlight: + loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT + if (gFTLibrary->lightHintingIsYOnly()) { + linearMetrics = true; + } + break; + case SkFontHinting::kNormal: + loadFlags = FT_LOAD_TARGET_NORMAL; + break; + case SkFontHinting::kFull: + loadFlags = FT_LOAD_TARGET_NORMAL; + if (isLCD(fRec)) { + if (fLCDIsVert) { + loadFlags = FT_LOAD_TARGET_LCD_V; + } else { + loadFlags = FT_LOAD_TARGET_LCD; + } + } + break; + default: + LOG_INFO("---------- UNKNOWN hinting %d\n", fRec.getHinting()); + break; + } + } + + if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { + loadFlags |= FT_LOAD_FORCE_AUTOHINT; +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + } else { + loadFlags |= FT_LOAD_NO_AUTOHINT; +#endif + } + + if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) { + loadFlags |= FT_LOAD_NO_BITMAP; + } + + // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct + // advances, as fontconfig and cairo do. + // See http://code.google.com/p/skia/issues/detail?id=222. + loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; + + // Use vertical layout if requested. + if (this->isVertical()) { + loadFlags |= FT_LOAD_VERTICAL_LAYOUT; + } + + loadFlags |= FT_LOAD_COLOR; + + fLoadGlyphFlags = loadFlags; + } + + using DoneFTSize = SkFunctionWrapper; + std::unique_ptr, DoneFTSize> ftSize([this]() -> FT_Size { + FT_Size size; + FT_Error err = FT_New_Size(fFaceRec->fFace.get(), &size); + if (err != 0) { + SK_TRACEFTR(err, "FT_New_Size(%s) failed.", fFaceRec->fFace->family_name); + return nullptr; + } + return size; + }()); + if (nullptr == ftSize) { + LOG_INFO("Could not create FT_Size.\n"); + return; + } + + FT_Error err = FT_Activate_Size(ftSize.get()); + if (err != 0) { + SK_TRACEFTR(err, "FT_Activate_Size(%s) failed.", fFaceRec->fFace->family_name); + return; + } + + fRec.computeMatrices(SkScalerContextRec::kFull_PreMatrixScale, &fScale, &fMatrix22Scalar); + FT_F26Dot6 scaleX = SkScalarToFDot6(fScale.fX); + FT_F26Dot6 scaleY = SkScalarToFDot6(fScale.fY); + + if (FT_IS_SCALABLE(fFaceRec->fFace)) { + err = FT_Set_Char_Size(fFaceRec->fFace.get(), scaleX, scaleY, 72, 72); + if (err != 0) { + SK_TRACEFTR(err, "FT_Set_CharSize(%s, %f, %f) failed.", + fFaceRec->fFace->family_name, fScale.fX, fScale.fY); + return; + } + + // Adjust the matrix to reflect the actually chosen scale. + // FreeType currently does not allow requesting sizes less than 1, this allow for scaling. + // Don't do this at all sizes as that will interfere with hinting. + if (fScale.fX < 1 || fScale.fY < 1) { + SkScalar upem = fFaceRec->fFace->units_per_EM; + FT_Size_Metrics& ftmetrics = fFaceRec->fFace->size->metrics; + SkScalar x_ppem = upem * SkFT_FixedToScalar(ftmetrics.x_scale) / 64.0f; + SkScalar y_ppem = upem * SkFT_FixedToScalar(ftmetrics.y_scale) / 64.0f; + fMatrix22Scalar.preScale(fScale.x() / x_ppem, fScale.y() / y_ppem); + } + + } else if (FT_HAS_FIXED_SIZES(fFaceRec->fFace)) { + fStrikeIndex = chooseBitmapStrike(fFaceRec->fFace.get(), scaleY); + if (fStrikeIndex == -1) { + LOG_INFO("No glyphs for font \"%s\" size %f.\n", + fFaceRec->fFace->family_name, fScale.fY); + return; + } + + err = FT_Select_Size(fFaceRec->fFace.get(), fStrikeIndex); + if (err != 0) { + SK_TRACEFTR(err, "FT_Select_Size(%s, %d) failed.", + fFaceRec->fFace->family_name, fStrikeIndex); + fStrikeIndex = -1; + return; + } + + // Adjust the matrix to reflect the actually chosen scale. + // It is likely that the ppem chosen was not the one requested, this allows for scaling. + fMatrix22Scalar.preScale(fScale.x() / fFaceRec->fFace->size->metrics.x_ppem, + fScale.y() / fFaceRec->fFace->size->metrics.y_ppem); + + // FreeType does not provide linear metrics for bitmap fonts. + linearMetrics = false; + + // FreeType documentation says: + // FT_LOAD_NO_BITMAP -- Ignore bitmap strikes when loading. + // Bitmap-only fonts ignore this flag. + // + // However, in FreeType 2.5.1 color bitmap only fonts do not ignore this flag. + // Force this flag off for bitmap only fonts. + fLoadGlyphFlags &= ~FT_LOAD_NO_BITMAP; + } else { + LOG_INFO("Unknown kind of font \"%s\" size %f.\n", fFaceRec->fFace->family_name, fScale.fY); + return; + } + + fMatrix22.xx = SkScalarToFixed(fMatrix22Scalar.getScaleX()); + fMatrix22.xy = SkScalarToFixed(-fMatrix22Scalar.getSkewX()); + fMatrix22.yx = SkScalarToFixed(-fMatrix22Scalar.getSkewY()); + fMatrix22.yy = SkScalarToFixed(fMatrix22Scalar.getScaleY()); + +#ifdef FT_COLOR_H + FT_Palette_Select(fFaceRec->fFace.get(), 0, nullptr); +#endif + + fFTSize = ftSize.release(); + fFace = fFaceRec->fFace.get(); + fDoLinearMetrics = linearMetrics; +} + +SkScalerContext_FreeType::~SkScalerContext_FreeType() { + SkAutoMutexExclusive ac(f_t_mutex()); + + if (fFTSize != nullptr) { + FT_Done_Size(fFTSize); + } + + fFaceRec = nullptr; + + unref_ft_library(); +} + +/* We call this before each use of the fFace, since we may be sharing + this face with other context (at different sizes). +*/ +FT_Error SkScalerContext_FreeType::setupSize() { + f_t_mutex().assertHeld(); + FT_Error err = FT_Activate_Size(fFTSize); + if (err != 0) { + return err; + } + FT_Set_Transform(fFace, &fMatrix22, nullptr); + return 0; +} + +unsigned SkScalerContext_FreeType::generateGlyphCount() { + return fFace->num_glyphs; +} + +bool SkScalerContext_FreeType::generateAdvance(SkGlyph* glyph) { + /* unhinted and light hinted text have linearly scaled advances + * which are very cheap to compute with some font formats... + */ + if (!fDoLinearMetrics) { + return false; + } + + SkAutoMutexExclusive ac(f_t_mutex()); + + if (this->setupSize()) { + glyph->zeroMetrics(); + return true; + } + + FT_Error error; + FT_Fixed advance; + + error = FT_Get_Advance( fFace, glyph->getGlyphID(), + fLoadGlyphFlags | FT_ADVANCE_FLAG_FAST_ONLY, + &advance ); + + if (error != 0) { + return false; + } + + const SkScalar advanceScalar = SkFT_FixedToScalar(advance); + glyph->fAdvanceX = SkScalarToFloat(fMatrix22Scalar.getScaleX() * advanceScalar); + glyph->fAdvanceY = SkScalarToFloat(fMatrix22Scalar.getSkewY() * advanceScalar); + return true; +} + +void SkScalerContext_FreeType::getBBoxForCurrentGlyph(const SkGlyph* glyph, + FT_BBox* bbox, + bool snapToPixelBoundary) { + + FT_Outline_Get_CBox(&fFace->glyph->outline, bbox); + + if (this->isSubpixel()) { + int dx = SkFixedToFDot6(glyph->getSubXFixed()); + int dy = SkFixedToFDot6(glyph->getSubYFixed()); + // negate dy since freetype-y-goes-up and skia-y-goes-down + bbox->xMin += dx; + bbox->yMin -= dy; + bbox->xMax += dx; + bbox->yMax -= dy; + } + + // outset the box to integral boundaries + if (snapToPixelBoundary) { + bbox->xMin &= ~63; + bbox->yMin &= ~63; + bbox->xMax = (bbox->xMax + 63) & ~63; + bbox->yMax = (bbox->yMax + 63) & ~63; + } + + // Must come after snapToPixelBoundary so that the width and height are + // consistent. Otherwise asserts will fire later on when generating the + // glyph image. + if (this->isVertical()) { + FT_Vector vector; + vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX; + vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY; + FT_Vector_Transform(&vector, &fMatrix22); + bbox->xMin += vector.x; + bbox->xMax += vector.x; + bbox->yMin += vector.y; + bbox->yMax += vector.y; + } +} + +bool SkScalerContext_FreeType::getCBoxForLetter(char letter, FT_BBox* bbox) { + const FT_UInt glyph_id = FT_Get_Char_Index(fFace, letter); + if (!glyph_id) { + return false; + } + if (FT_Load_Glyph(fFace, glyph_id, fLoadGlyphFlags) != 0) { + return false; + } + emboldenIfNeeded(fFace, fFace->glyph, SkTo(glyph_id)); + FT_Outline_Get_CBox(&fFace->glyph->outline, bbox); + return true; +} + +void SkScalerContext_FreeType::updateGlyphIfLCD(SkGlyph* glyph) { + if (glyph->fMaskFormat == SkMask::kLCD16_Format) { + if (fLCDIsVert) { + glyph->fHeight += gFTLibrary->lcdExtra(); + glyph->fTop -= gFTLibrary->lcdExtra() >> 1; + } else { + glyph->fWidth += gFTLibrary->lcdExtra(); + glyph->fLeft -= gFTLibrary->lcdExtra() >> 1; + } + } +} + +bool SkScalerContext_FreeType::shouldSubpixelBitmap(const SkGlyph& glyph, const SkMatrix& matrix) { + // If subpixel rendering of a bitmap *can* be done. + bool mechanism = fFace->glyph->format == FT_GLYPH_FORMAT_BITMAP && + this->isSubpixel() && + (glyph.getSubXFixed() || glyph.getSubYFixed()); + + // If subpixel rendering of a bitmap *should* be done. + // 1. If the face is not scalable then always allow subpixel rendering. + // Otherwise, if the font has an 8ppem strike 7 will subpixel render but 8 won't. + // 2. If the matrix is already not identity the bitmap will already be resampled, + // so resampling slightly differently shouldn't make much difference. + bool policy = !FT_IS_SCALABLE(fFace) || !matrix.isIdentity(); + + return mechanism && policy; +} + +void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) { + SkAutoMutexExclusive ac(f_t_mutex()); + + glyph->fMaskFormat = fRec.fMaskFormat; + + if (this->setupSize()) { + glyph->zeroMetrics(); + return; + } + + FT_Error err; + err = FT_Load_Glyph( fFace, glyph->getGlyphID(), + fLoadGlyphFlags | FT_LOAD_BITMAP_METRICS_ONLY ); + if (err != 0) { + glyph->zeroMetrics(); + return; + } + emboldenIfNeeded(fFace, fFace->glyph, glyph->getGlyphID()); + + if (fFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + using FT_PosLimits = std::numeric_limits; + FT_BBox bounds = { FT_PosLimits::max(), FT_PosLimits::max(), + FT_PosLimits::min(), FT_PosLimits::min() }; +#ifdef FT_COLOR_H + FT_Bool haveLayers = false; + FT_LayerIterator layerIterator = { 0, 0, nullptr }; + FT_UInt layerGlyphIndex; + FT_UInt layerColorIndex; + while (FT_Get_Color_Glyph_Layer(fFace, glyph->getGlyphID(), + &layerGlyphIndex, &layerColorIndex, &layerIterator)) + { + haveLayers = true; + err = FT_Load_Glyph(fFace, layerGlyphIndex, + fLoadGlyphFlags | FT_LOAD_BITMAP_METRICS_ONLY); + if (err != 0) { + glyph->zeroMetrics(); + return; + } + emboldenIfNeeded(fFace, fFace->glyph, layerGlyphIndex); + + if (0 < fFace->glyph->outline.n_contours) { + FT_BBox bbox; + getBBoxForCurrentGlyph(glyph, &bbox, true); + + // Union + bounds.xMin = std::min(bbox.xMin, bounds.xMin); + bounds.yMin = std::min(bbox.yMin, bounds.yMin); + bounds.xMax = std::max(bbox.xMax, bounds.xMax); + bounds.yMax = std::max(bbox.yMax, bounds.yMax); + } + } + + if (haveLayers) { + glyph->fMaskFormat = SkMask::kARGB32_Format; + if (!(bounds.xMin < bounds.xMax && bounds.yMin < bounds.yMax)) { + bounds = { 0, 0, 0, 0 }; + } + } else { +#endif + if (0 < fFace->glyph->outline.n_contours) { + getBBoxForCurrentGlyph(glyph, &bounds, true); + } else { + bounds = { 0, 0, 0, 0 }; + } +#ifdef FT_COLOR_H + } +#endif + // Round out, no longer dot6. + bounds.xMin = SkFDot6Floor(bounds.xMin); + bounds.yMin = SkFDot6Floor(bounds.yMin); + bounds.xMax = SkFDot6Ceil (bounds.xMax); + bounds.yMax = SkFDot6Ceil (bounds.yMax); + + FT_Pos width = bounds.xMax - bounds.xMin; + FT_Pos height = bounds.yMax - bounds.yMin; + FT_Pos top = -bounds.yMax; // Freetype y-up, Skia y-down. + FT_Pos left = bounds.xMin; + if (!SkTFitsInfWidth )>(width ) || + !SkTFitsInfHeight)>(height) || + !SkTFitsInfTop )>(top ) || + !SkTFitsInfLeft )>(left ) ) + { + width = height = top = left = 0; + } + + glyph->fWidth = SkToU16(width ); + glyph->fHeight = SkToU16(height); + glyph->fTop = SkToS16(top ); + glyph->fLeft = SkToS16(left ); + updateGlyphIfLCD(glyph); + + } else if (fFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) { + if (this->isVertical()) { + FT_Vector vector; + vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX; + vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY; + FT_Vector_Transform(&vector, &fMatrix22); + fFace->glyph->bitmap_left += SkFDot6Floor(vector.x); + fFace->glyph->bitmap_top += SkFDot6Floor(vector.y); + } + + if (fFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + glyph->fMaskFormat = SkMask::kARGB32_Format; + } + + { + SkRect rect = SkRect::MakeXYWH(SkIntToScalar(fFace->glyph->bitmap_left), + -SkIntToScalar(fFace->glyph->bitmap_top), + SkIntToScalar(fFace->glyph->bitmap.width), + SkIntToScalar(fFace->glyph->bitmap.rows)); + fMatrix22Scalar.mapRect(&rect); + if (this->shouldSubpixelBitmap(*glyph, fMatrix22Scalar)) { + rect.offset(SkFixedToScalar(glyph->getSubXFixed()), + SkFixedToScalar(glyph->getSubYFixed())); + } + SkIRect irect = rect.roundOut(); + glyph->fWidth = SkToU16(irect.width()); + glyph->fHeight = SkToU16(irect.height()); + glyph->fTop = SkToS16(irect.top()); + glyph->fLeft = SkToS16(irect.left()); + } + } else { + SkDEBUGFAIL("unknown glyph format"); + glyph->zeroMetrics(); + return; + } + + if (this->isVertical()) { + if (fDoLinearMetrics) { + const SkScalar advanceScalar = SkFT_FixedToScalar(fFace->glyph->linearVertAdvance); + glyph->fAdvanceX = SkScalarToFloat(fMatrix22Scalar.getSkewX() * advanceScalar); + glyph->fAdvanceY = SkScalarToFloat(fMatrix22Scalar.getScaleY() * advanceScalar); + } else { + glyph->fAdvanceX = -SkFDot6ToFloat(fFace->glyph->advance.x); + glyph->fAdvanceY = SkFDot6ToFloat(fFace->glyph->advance.y); + } + } else { + if (fDoLinearMetrics) { + const SkScalar advanceScalar = SkFT_FixedToScalar(fFace->glyph->linearHoriAdvance); + glyph->fAdvanceX = SkScalarToFloat(fMatrix22Scalar.getScaleX() * advanceScalar); + glyph->fAdvanceY = SkScalarToFloat(fMatrix22Scalar.getSkewY() * advanceScalar); + } else { + glyph->fAdvanceX = SkFDot6ToFloat(fFace->glyph->advance.x); + glyph->fAdvanceY = -SkFDot6ToFloat(fFace->glyph->advance.y); + } + } + +#ifdef ENABLE_GLYPH_SPEW + LOG_INFO("Metrics(glyph:%d flags:0x%x) w:%d\n", glyph->getGlyphID(), fLoadGlyphFlags, glyph->fWidth); +#endif +} + +void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) { + SkAutoMutexExclusive ac(f_t_mutex()); + + if (this->setupSize()) { + sk_bzero(glyph.fImage, glyph.imageSize()); + return; + } + + FT_Error err = FT_Load_Glyph(fFace, glyph.getGlyphID(), fLoadGlyphFlags); + if (err != 0) { + SK_TRACEFTR(err, "SkScalerContext_FreeType::generateImage: FT_Load_Glyph(glyph:%d " + "width:%d height:%d rb:%d flags:%d) failed.", + glyph.getGlyphID(), glyph.width(), glyph.height(), glyph.rowBytes(), + fLoadGlyphFlags); + sk_bzero(glyph.fImage, glyph.imageSize()); + return; + } + + emboldenIfNeeded(fFace, fFace->glyph, glyph.getGlyphID()); + SkMatrix* bitmapMatrix = &fMatrix22Scalar; + SkMatrix subpixelBitmapMatrix; + if (this->shouldSubpixelBitmap(glyph, *bitmapMatrix)) { + subpixelBitmapMatrix = fMatrix22Scalar; + subpixelBitmapMatrix.postTranslate(SkFixedToScalar(glyph.getSubXFixed()), + SkFixedToScalar(glyph.getSubYFixed())); + bitmapMatrix = &subpixelBitmapMatrix; + } + generateGlyphImage(fFace, glyph, *bitmapMatrix); +} + + +bool SkScalerContext_FreeType::generatePath(SkGlyphID glyphID, SkPath* path) { + SkASSERT(path); + + SkAutoMutexExclusive ac(f_t_mutex()); + + // FT_IS_SCALABLE is documented to mean the face contains outline glyphs. + if (!FT_IS_SCALABLE(fFace) || this->setupSize()) { + path->reset(); + return false; + } + + uint32_t flags = fLoadGlyphFlags; + flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline + flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline) + + FT_Error err = FT_Load_Glyph(fFace, glyphID, flags); + if (err != 0 || fFace->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { + path->reset(); + return false; + } + emboldenIfNeeded(fFace, fFace->glyph, glyphID); + + if (!generateGlyphPath(fFace, path)) { + path->reset(); + return false; + } + + // The path's origin from FreeType is always the horizontal layout origin. + // Offset the path so that it is relative to the vertical origin if needed. + if (this->isVertical()) { + FT_Vector vector; + vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX; + vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY; + FT_Vector_Transform(&vector, &fMatrix22); + path->offset(SkFDot6ToScalar(vector.x), -SkFDot6ToScalar(vector.y)); + } + return true; +} + +void SkScalerContext_FreeType::generateFontMetrics(SkFontMetrics* metrics) { + if (nullptr == metrics) { + return; + } + + SkAutoMutexExclusive ac(f_t_mutex()); + + if (this->setupSize()) { + sk_bzero(metrics, sizeof(*metrics)); + return; + } + + FT_Face face = fFace; + metrics->fFlags = 0; + + SkScalar upem = SkIntToScalar(SkTypeface_FreeType::GetUnitsPerEm(face)); + + // use the os/2 table as a source of reasonable defaults. + SkScalar x_height = 0.0f; + SkScalar avgCharWidth = 0.0f; + SkScalar cap_height = 0.0f; + SkScalar strikeoutThickness = 0.0f, strikeoutPosition = 0.0f; + TT_OS2* os2 = (TT_OS2*) FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2) { + x_height = SkIntToScalar(os2->sxHeight) / upem * fScale.y(); + avgCharWidth = SkIntToScalar(os2->xAvgCharWidth) / upem; + strikeoutThickness = SkIntToScalar(os2->yStrikeoutSize) / upem; + strikeoutPosition = -SkIntToScalar(os2->yStrikeoutPosition) / upem; + metrics->fFlags |= SkFontMetrics::kStrikeoutThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kStrikeoutPositionIsValid_Flag; + if (os2->version != 0xFFFF && os2->version >= 2) { + cap_height = SkIntToScalar(os2->sCapHeight) / upem * fScale.y(); + } + } + + // pull from format-specific metrics as needed + SkScalar ascent, descent, leading, xmin, xmax, ymin, ymax; + SkScalar underlineThickness, underlinePosition; + if (face->face_flags & FT_FACE_FLAG_SCALABLE) { // scalable outline font + // FreeType will always use HHEA metrics if they're not zero. + // It completely ignores the OS/2 fsSelection::UseTypoMetrics bit. + // It also ignores the VDMX tables, which are also of interest here + // (and override everything else when they apply). + static const int kUseTypoMetricsMask = (1 << 7); + if (os2 && os2->version != 0xFFFF && (os2->fsSelection & kUseTypoMetricsMask)) { + ascent = -SkIntToScalar(os2->sTypoAscender) / upem; + descent = -SkIntToScalar(os2->sTypoDescender) / upem; + leading = SkIntToScalar(os2->sTypoLineGap) / upem; + } else { + ascent = -SkIntToScalar(face->ascender) / upem; + descent = -SkIntToScalar(face->descender) / upem; + leading = SkIntToScalar(face->height + (face->descender - face->ascender)) / upem; + } + xmin = SkIntToScalar(face->bbox.xMin) / upem; + xmax = SkIntToScalar(face->bbox.xMax) / upem; + ymin = -SkIntToScalar(face->bbox.yMin) / upem; + ymax = -SkIntToScalar(face->bbox.yMax) / upem; + underlineThickness = SkIntToScalar(face->underline_thickness) / upem; + underlinePosition = -SkIntToScalar(face->underline_position + + face->underline_thickness / 2) / upem; + + metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag; + + // we may be able to synthesize x_height and cap_height from outline + if (!x_height) { + FT_BBox bbox; + if (getCBoxForLetter('x', &bbox)) { + x_height = SkIntToScalar(bbox.yMax) / 64.0f; + } + } + if (!cap_height) { + FT_BBox bbox; + if (getCBoxForLetter('H', &bbox)) { + cap_height = SkIntToScalar(bbox.yMax) / 64.0f; + } + } + } else if (fStrikeIndex != -1) { // bitmap strike metrics + SkScalar xppem = SkIntToScalar(face->size->metrics.x_ppem); + SkScalar yppem = SkIntToScalar(face->size->metrics.y_ppem); + ascent = -SkIntToScalar(face->size->metrics.ascender) / (yppem * 64.0f); + descent = -SkIntToScalar(face->size->metrics.descender) / (yppem * 64.0f); + leading = (SkIntToScalar(face->size->metrics.height) / (yppem * 64.0f)) + ascent - descent; + xmin = 0.0f; + xmax = SkIntToScalar(face->available_sizes[fStrikeIndex].width) / xppem; + ymin = descent; + ymax = ascent; + underlineThickness = 0; + underlinePosition = 0; + metrics->fFlags &= ~SkFontMetrics::kUnderlineThicknessIsValid_Flag; + metrics->fFlags &= ~SkFontMetrics::kUnderlinePositionIsValid_Flag; + + TT_Postscript* post = (TT_Postscript*) FT_Get_Sfnt_Table(face, ft_sfnt_post); + if (post) { + underlineThickness = SkIntToScalar(post->underlineThickness) / upem; + underlinePosition = -SkIntToScalar(post->underlinePosition) / upem; + metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag; + } + } else { + sk_bzero(metrics, sizeof(*metrics)); + return; + } + + // synthesize elements that were not provided by the os/2 table or format-specific metrics + if (!x_height) { + x_height = -ascent * fScale.y(); + } + if (!avgCharWidth) { + avgCharWidth = xmax - xmin; + } + if (!cap_height) { + cap_height = -ascent * fScale.y(); + } + + // disallow negative linespacing + if (leading < 0.0f) { + leading = 0.0f; + } + + metrics->fTop = ymax * fScale.y(); + metrics->fAscent = ascent * fScale.y(); + metrics->fDescent = descent * fScale.y(); + metrics->fBottom = ymin * fScale.y(); + metrics->fLeading = leading * fScale.y(); + metrics->fAvgCharWidth = avgCharWidth * fScale.y(); + metrics->fXMin = xmin * fScale.y(); + metrics->fXMax = xmax * fScale.y(); + metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin; + metrics->fXHeight = x_height; + metrics->fCapHeight = cap_height; + metrics->fUnderlineThickness = underlineThickness * fScale.y(); + metrics->fUnderlinePosition = underlinePosition * fScale.y(); + metrics->fStrikeoutThickness = strikeoutThickness * fScale.y(); + metrics->fStrikeoutPosition = strikeoutPosition * fScale.y(); +} + +/////////////////////////////////////////////////////////////////////////////// + +// hand-tuned value to reduce outline embolden strength +#ifndef SK_OUTLINE_EMBOLDEN_DIVISOR + #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #define SK_OUTLINE_EMBOLDEN_DIVISOR 34 + #else + #define SK_OUTLINE_EMBOLDEN_DIVISOR 24 + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// + +void SkScalerContext_FreeType::emboldenIfNeeded(FT_Face face, FT_GlyphSlot glyph, SkGlyphID gid) { + // check to see if the embolden bit is set + if (0 == (fRec.fFlags & SkScalerContext::kEmbolden_Flag)) { + return; + } + + switch (glyph->format) { + case FT_GLYPH_FORMAT_OUTLINE: + FT_Pos strength; + strength = FT_MulFix(face->units_per_EM, face->size->metrics.y_scale) + / SK_OUTLINE_EMBOLDEN_DIVISOR; + FT_Outline_Embolden(&glyph->outline, strength); + break; + case FT_GLYPH_FORMAT_BITMAP: + if (!fFace->glyph->bitmap.buffer) { + FT_Load_Glyph(fFace, gid, fLoadGlyphFlags); + } + FT_GlyphSlot_Own_Bitmap(glyph); + FT_Bitmap_Embolden(glyph->library, &glyph->bitmap, kBitmapEmboldenStrength, 0); + break; + default: + SkDEBUGFAIL("unknown glyph format"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "src/core/SkUtils.h" + +// Just made up, so we don't end up storing 1000s of entries +constexpr int kMaxC2GCacheCount = 512; + +void SkTypeface_FreeType::onCharsToGlyphs(const SkUnichar uni[], int count, + SkGlyphID glyphs[]) const { + // Try the cache first, *before* accessing freetype lib/face, as that + // can be very slow. If we do need to compute a new glyphID, then + // access those freetype objects and continue the loop. + + SkAutoMutexExclusive ama(fC2GCacheMutex); + + int i; + for (i = 0; i < count; ++i) { + int index = fC2GCache.findGlyphIndex(uni[i]); + if (index < 0) { + break; + } + glyphs[i] = SkToU16(index); + } + if (i == count) { + // we're done, no need to access the freetype objects + return; + } + + AutoFTAccess fta(this); + FT_Face face = fta.face(); + if (!face) { + sk_bzero(glyphs, count * sizeof(glyphs[0])); + return; + } + + for (; i < count; ++i) { + SkUnichar c = uni[i]; + int index = fC2GCache.findGlyphIndex(c); + if (index >= 0) { + glyphs[i] = SkToU16(index); + } else { + glyphs[i] = SkToU16(FT_Get_Char_Index(face, c)); + fC2GCache.insertCharAndGlyph(~index, c, glyphs[i]); + } + } + + if (fC2GCache.count() > kMaxC2GCacheCount) { + fC2GCache.reset(); + } +} + +int SkTypeface_FreeType::onCountGlyphs() const { + AutoFTAccess fta(this); + FT_Face face = fta.face(); + return face ? face->num_glyphs : 0; +} + +SkTypeface::LocalizedStrings* SkTypeface_FreeType::onCreateFamilyNameIterator() const { + sk_sp nameIter = + SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*this); + if (!nameIter) { + SkString familyName; + this->getFamilyName(&familyName); + SkString language("und"); //undetermined + nameIter = sk_make_sp(familyName, language); + } + return nameIter.release(); +} + +int SkTypeface_FreeType::onGetVariationDesignPosition( + SkFontArguments::VariationPosition::Coordinate coordinates[], int coordinateCount) const +{ + AutoFTAccess fta(this); + FT_Face face = fta.face(); + if (!face) { + return -1; + } + + if (!(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + return 0; + } + + FT_MM_Var* variations = nullptr; + if (FT_Get_MM_Var(face, &variations)) { + return -1; + } + SkAutoFree autoFreeVariations(variations); + + if (!coordinates || coordinateCount < SkToInt(variations->num_axis)) { + return variations->num_axis; + } + + SkAutoSTMalloc<4, FT_Fixed> coords(variations->num_axis); + // FT_Get_{MM,Var}_{Blend,Design}_Coordinates were added in FreeType 2.7.1. + if (gFTLibrary->fGetVarDesignCoordinates && + !gFTLibrary->fGetVarDesignCoordinates(face, variations->num_axis, coords.get())) + { + for (FT_UInt i = 0; i < variations->num_axis; ++i) { + coordinates[i].axis = variations->axis[i].tag; + coordinates[i].value = SkFixedToScalar(coords[i]); + } + } else if (static_cast(fta.getAxesCount()) == variations->num_axis) { + for (FT_UInt i = 0; i < variations->num_axis; ++i) { + coordinates[i].axis = variations->axis[i].tag; + coordinates[i].value = SkFixedToScalar(fta.getAxes()[i]); + } + } else if (fta.isNamedVariationSpecified()) { + // The font has axes, they cannot be retrieved, and some named axis was specified. + return -1; + } else { + // The font has axes, they cannot be retrieved, but no named instance was specified. + return 0; + } + + return variations->num_axis; +} + +int SkTypeface_FreeType::onGetVariationDesignParameters( + SkFontParameters::Variation::Axis parameters[], int parameterCount) const +{ + AutoFTAccess fta(this); + FT_Face face = fta.face(); + if (!face) { + return -1; + } + + if (!(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + return 0; + } + + FT_MM_Var* variations = nullptr; + if (FT_Get_MM_Var(face, &variations)) { + return -1; + } + SkAutoFree autoFreeVariations(variations); + + if (!parameters || parameterCount < SkToInt(variations->num_axis)) { + return variations->num_axis; + } + + for (FT_UInt i = 0; i < variations->num_axis; ++i) { + parameters[i].tag = variations->axis[i].tag; + parameters[i].min = SkFixedToScalar(variations->axis[i].minimum); + parameters[i].def = SkFixedToScalar(variations->axis[i].def); + parameters[i].max = SkFixedToScalar(variations->axis[i].maximum); + FT_UInt flags = 0; + bool hidden = gFTLibrary->fGetVarAxisFlags && + !gFTLibrary->fGetVarAxisFlags(variations, i, &flags) && + (flags & FT_VAR_AXIS_FLAG_HIDDEN); + parameters[i].setHidden(hidden); + } + + return variations->num_axis; +} + +int SkTypeface_FreeType::onGetTableTags(SkFontTableTag tags[]) const { + AutoFTAccess fta(this); + FT_Face face = fta.face(); + + FT_ULong tableCount = 0; + FT_Error error; + + // When 'tag' is nullptr, returns number of tables in 'length'. + error = FT_Sfnt_Table_Info(face, 0, nullptr, &tableCount); + if (error) { + return 0; + } + + if (tags) { + for (FT_ULong tableIndex = 0; tableIndex < tableCount; ++tableIndex) { + FT_ULong tableTag; + FT_ULong tablelength; + error = FT_Sfnt_Table_Info(face, tableIndex, &tableTag, &tablelength); + if (error) { + return 0; + } + tags[tableIndex] = static_cast(tableTag); + } + } + return tableCount; +} + +size_t SkTypeface_FreeType::onGetTableData(SkFontTableTag tag, size_t offset, + size_t length, void* data) const +{ + AutoFTAccess fta(this); + FT_Face face = fta.face(); + + FT_ULong tableLength = 0; + FT_Error error; + + // When 'length' is 0 it is overwritten with the full table length; 'offset' is ignored. + error = FT_Load_Sfnt_Table(face, tag, 0, nullptr, &tableLength); + if (error) { + return 0; + } + + if (offset > tableLength) { + return 0; + } + FT_ULong size = SkTMin((FT_ULong)length, tableLength - (FT_ULong)offset); + if (data) { + error = FT_Load_Sfnt_Table(face, tag, offset, reinterpret_cast(data), &size); + if (error) { + return 0; + } + } + + return size; +} + +sk_sp SkTypeface_FreeType::onCopyTableData(SkFontTableTag tag) const { + AutoFTAccess fta(this); + FT_Face face = fta.face(); + + FT_ULong tableLength = 0; + FT_Error error; + + // When 'length' is 0 it is overwritten with the full table length; 'offset' is ignored. + error = FT_Load_Sfnt_Table(face, tag, 0, nullptr, &tableLength); + if (error) { + return nullptr; + } + + sk_sp data = SkData::MakeUninitialized(tableLength); + if (data) { + error = FT_Load_Sfnt_Table(face, tag, 0, + reinterpret_cast(data->writable_data()), &tableLength); + if (error) { + data.reset(); + } + } + return data; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SkTypeface_FreeType::Scanner::Scanner() : fLibrary(nullptr) { + if (FT_New_Library(&gFTMemory, &fLibrary)) { + return; + } + FT_Add_Default_Modules(fLibrary); +} +SkTypeface_FreeType::Scanner::~Scanner() { + if (fLibrary) { + FT_Done_Library(fLibrary); + } +} + +FT_Face SkTypeface_FreeType::Scanner::openFace(SkStreamAsset* stream, int ttcIndex, + FT_Stream ftStream) const +{ + if (fLibrary == nullptr) { + return nullptr; + } + + FT_Open_Args args; + memset(&args, 0, sizeof(args)); + + const void* memoryBase = stream->getMemoryBase(); + + if (memoryBase) { + args.flags = FT_OPEN_MEMORY; + args.memory_base = (const FT_Byte*)memoryBase; + args.memory_size = stream->getLength(); + } else { + memset(ftStream, 0, sizeof(*ftStream)); + ftStream->size = stream->getLength(); + ftStream->descriptor.pointer = stream; + ftStream->read = sk_ft_stream_io; + ftStream->close = sk_ft_stream_close; + + args.flags = FT_OPEN_STREAM; + args.stream = ftStream; + } + + FT_Face face; + if (FT_Open_Face(fLibrary, &args, ttcIndex, &face)) { + return nullptr; + } + return face; +} + +bool SkTypeface_FreeType::Scanner::recognizedFont(SkStreamAsset* stream, int* numFaces) const { + SkAutoMutexExclusive libraryLock(fLibraryMutex); + + FT_StreamRec streamRec; + FT_Face face = this->openFace(stream, -1, &streamRec); + if (nullptr == face) { + return false; + } + + *numFaces = face->num_faces; + + FT_Done_Face(face); + return true; +} + +#include "src/core/SkTSearch.h" +bool SkTypeface_FreeType::Scanner::scanFont( + SkStreamAsset* stream, int ttcIndex, + SkString* name, SkFontStyle* style, bool* isFixedPitch, AxisDefinitions* axes) const +{ + SkAutoMutexExclusive libraryLock(fLibraryMutex); + + FT_StreamRec streamRec; + FT_Face face = this->openFace(stream, ttcIndex, &streamRec); + if (nullptr == face) { + return false; + } + + int weight = SkFontStyle::kNormal_Weight; + int width = SkFontStyle::kNormal_Width; + SkFontStyle::Slant slant = SkFontStyle::kUpright_Slant; + if (face->style_flags & FT_STYLE_FLAG_BOLD) { + weight = SkFontStyle::kBold_Weight; + } + if (face->style_flags & FT_STYLE_FLAG_ITALIC) { + slant = SkFontStyle::kItalic_Slant; + } + + PS_FontInfoRec psFontInfo; + TT_OS2* os2 = static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); + if (os2 && os2->version != 0xffff) { + weight = os2->usWeightClass; + width = os2->usWidthClass; + + // OS/2::fsSelection bit 9 indicates oblique. + if (SkToBool(os2->fsSelection & (1u << 9))) { + slant = SkFontStyle::kOblique_Slant; + } + } else if (0 == FT_Get_PS_Font_Info(face, &psFontInfo) && psFontInfo.weight) { + static const struct { + char const * const name; + int const weight; + } commonWeights [] = { + // There are probably more common names, but these are known to exist. + { "all", SkFontStyle::kNormal_Weight }, // Multiple Masters usually default to normal. + { "black", SkFontStyle::kBlack_Weight }, + { "bold", SkFontStyle::kBold_Weight }, + { "book", (SkFontStyle::kNormal_Weight + SkFontStyle::kLight_Weight)/2 }, + { "demi", SkFontStyle::kSemiBold_Weight }, + { "demibold", SkFontStyle::kSemiBold_Weight }, + { "extra", SkFontStyle::kExtraBold_Weight }, + { "extrabold", SkFontStyle::kExtraBold_Weight }, + { "extralight", SkFontStyle::kExtraLight_Weight }, + { "hairline", SkFontStyle::kThin_Weight }, + { "heavy", SkFontStyle::kBlack_Weight }, + { "light", SkFontStyle::kLight_Weight }, + { "medium", SkFontStyle::kMedium_Weight }, + { "normal", SkFontStyle::kNormal_Weight }, + { "plain", SkFontStyle::kNormal_Weight }, + { "regular", SkFontStyle::kNormal_Weight }, + { "roman", SkFontStyle::kNormal_Weight }, + { "semibold", SkFontStyle::kSemiBold_Weight }, + { "standard", SkFontStyle::kNormal_Weight }, + { "thin", SkFontStyle::kThin_Weight }, + { "ultra", SkFontStyle::kExtraBold_Weight }, + { "ultrablack", SkFontStyle::kExtraBlack_Weight }, + { "ultrabold", SkFontStyle::kExtraBold_Weight }, + { "ultraheavy", SkFontStyle::kExtraBlack_Weight }, + { "ultralight", SkFontStyle::kExtraLight_Weight }, + }; + int const index = SkStrLCSearch(&commonWeights[0].name, SK_ARRAY_COUNT(commonWeights), + psFontInfo.weight, sizeof(commonWeights[0])); + if (index >= 0) { + weight = commonWeights[index].weight; + } else { + LOG_INFO("Do not know weight for: %s (%s) \n", face->family_name, psFontInfo.weight); + } + } + + if (name) { + name->set(face->family_name); + } + if (style) { + *style = SkFontStyle(weight, width, slant); + } + if (isFixedPitch) { + *isFixedPitch = FT_IS_FIXED_WIDTH(face); + } + + bool success = GetAxes(face, axes); + FT_Done_Face(face); + return success; +} + +bool SkTypeface_FreeType::Scanner::GetAxes(FT_Face face, AxisDefinitions* axes) { + if (axes && face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + FT_MM_Var* variations = nullptr; + FT_Error err = FT_Get_MM_Var(face, &variations); + if (err) { + LOG_INFO("INFO: font %s claims to have variations, but none found.\n", + face->family_name); + return false; + } + SkAutoFree autoFreeVariations(variations); + + axes->reset(variations->num_axis); + for (FT_UInt i = 0; i < variations->num_axis; ++i) { + const FT_Var_Axis& ftAxis = variations->axis[i]; + (*axes)[i].fTag = ftAxis.tag; + (*axes)[i].fMinimum = ftAxis.minimum; + (*axes)[i].fDefault = ftAxis.def; + (*axes)[i].fMaximum = ftAxis.maximum; + } + } + return true; +} + +/*static*/ void SkTypeface_FreeType::Scanner::computeAxisValues( + AxisDefinitions axisDefinitions, + const SkFontArguments::VariationPosition position, + SkFixed* axisValues, + const SkString& name) +{ + for (int i = 0; i < axisDefinitions.count(); ++i) { + const Scanner::AxisDefinition& axisDefinition = axisDefinitions[i]; + const SkScalar axisMin = SkFixedToScalar(axisDefinition.fMinimum); + const SkScalar axisMax = SkFixedToScalar(axisDefinition.fMaximum); + axisValues[i] = axisDefinition.fDefault; + // The position may be over specified. If there are multiple values for a given axis, + // use the last one since that's what css-fonts-4 requires. + for (int j = position.coordinateCount; j --> 0;) { + const auto& coordinate = position.coordinates[j]; + if (axisDefinition.fTag == coordinate.axis) { + const SkScalar axisValue = SkTPin(coordinate.value, axisMin, axisMax); + if (coordinate.value != axisValue) { + LOG_INFO("Requested font axis value out of range: " + "%s '%c%c%c%c' %f; pinned to %f.\n", + name.c_str(), + (axisDefinition.fTag >> 24) & 0xFF, + (axisDefinition.fTag >> 16) & 0xFF, + (axisDefinition.fTag >> 8) & 0xFF, + (axisDefinition.fTag ) & 0xFF, + SkScalarToDouble(coordinate.value), + SkScalarToDouble(axisValue)); + } + axisValues[i] = SkScalarToFixed(axisValue); + break; + } + } + // TODO: warn on defaulted axis? + } + + SkDEBUGCODE( + // Check for axis specified, but not matched in font. + for (int i = 0; i < position.coordinateCount; ++i) { + SkFourByteTag skTag = position.coordinates[i].axis; + bool found = false; + for (int j = 0; j < axisDefinitions.count(); ++j) { + if (skTag == axisDefinitions[j].fTag) { + found = true; + break; + } + } + if (!found) { + LOG_INFO("Requested font axis not found: %s '%c%c%c%c'\n", + name.c_str(), + (skTag >> 24) & 0xFF, + (skTag >> 16) & 0xFF, + (skTag >> 8) & 0xFF, + (skTag) & 0xFF); + } + } + ) +} diff --git a/gfx/skia/skia/src/ports/SkFontHost_FreeType_common.cpp b/gfx/skia/skia/src/ports/SkFontHost_FreeType_common.cpp new file mode 100644 index 0000000000..531ca88357 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontHost_FreeType_common.cpp @@ -0,0 +1,760 @@ +/* + * Copyright 2006-2012 The Android Open Source Project + * Copyright 2012 Mozilla Foundation + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkPath.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/core/SkFDot6.h" +#include "src/ports/SkFontHost_FreeType_common.h" + +#include + +#include +#include FT_FREETYPE_H +#include FT_BITMAP_H +#ifdef FT_COLOR_H +# include FT_COLOR_H +#endif +#include FT_IMAGE_H +#include FT_OUTLINE_H +// In the past, FT_GlyphSlot_Own_Bitmap was defined in this header file. +#include FT_SYNTHESIS_H + +// FT_LOAD_COLOR and the corresponding FT_Pixel_Mode::FT_PIXEL_MODE_BGRA +// were introduced in FreeType 2.5.0. +// The following may be removed once FreeType 2.5.0 is required to build. +#ifndef FT_LOAD_COLOR +# define FT_LOAD_COLOR ( 1L << 20 ) +# define FT_PIXEL_MODE_BGRA 7 +#endif + +#ifdef SK_DEBUG +const char* SkTraceFtrGetError(int e) { + switch ((FT_Error)e) { + #undef FTERRORS_H_ + #define FT_ERRORDEF( e, v, s ) case v: return s; + #define FT_ERROR_START_LIST + #define FT_ERROR_END_LIST + #include FT_ERRORS_H + #undef FT_ERRORDEF + #undef FT_ERROR_START_LIST + #undef FT_ERROR_END_LIST + default: return ""; + } +} +#endif // SK_DEBUG + +namespace { + +FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) { + switch (format) { + case SkMask::kBW_Format: + return FT_PIXEL_MODE_MONO; + case SkMask::kA8_Format: + default: + return FT_PIXEL_MODE_GRAY; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +uint16_t packTriple(U8CPU r, U8CPU g, U8CPU b) { +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + r = SkTMax(r, (U8CPU)0x40); + g = SkTMax(g, (U8CPU)0x40); + b = SkTMax(b, (U8CPU)0x40); +#endif + return SkPack888ToRGB16(r, g, b); +} + +uint16_t grayToRGB16(U8CPU gray) { +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + gray = SkTMax(gray, (U8CPU)0x40); +#endif + return SkPack888ToRGB16(gray, gray, gray); +} + +int bittst(const uint8_t data[], int bitOffset) { + SkASSERT(bitOffset >= 0); + int lowBit = data[bitOffset >> 3] >> (~bitOffset & 7); + return lowBit & 1; +} + +/** + * Copies a FT_Bitmap into an SkMask with the same dimensions. + * + * FT_PIXEL_MODE_MONO + * FT_PIXEL_MODE_GRAY + * FT_PIXEL_MODE_LCD + * FT_PIXEL_MODE_LCD_V + */ +template +void copyFT2LCD16(const FT_Bitmap& bitmap, const SkMask& mask, int lcdIsBGR, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) +{ + SkASSERT(SkMask::kLCD16_Format == mask.fFormat); + if (FT_PIXEL_MODE_LCD != bitmap.pixel_mode) { + SkASSERT(mask.fBounds.width() == static_cast(bitmap.width)); + } + if (FT_PIXEL_MODE_LCD_V != bitmap.pixel_mode) { + SkASSERT(mask.fBounds.height() == static_cast(bitmap.rows)); + } + + const uint8_t* src = bitmap.buffer; + uint16_t* dst = reinterpret_cast(mask.fImage); + const size_t dstRB = mask.fRowBytes; + + const int width = mask.fBounds.width(); + const int height = mask.fBounds.height(); + + switch (bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: + for (int y = height; y --> 0;) { + for (int x = 0; x < width; ++x) { + dst[x] = -bittst(src, x); + } + dst = (uint16_t*)((char*)dst + dstRB); + src += bitmap.pitch; + } + break; + case FT_PIXEL_MODE_GRAY: + for (int y = height; y --> 0;) { + for (int x = 0; x < width; ++x) { + dst[x] = grayToRGB16(src[x]); + } + dst = (uint16_t*)((char*)dst + dstRB); + src += bitmap.pitch; + } + break; + case FT_PIXEL_MODE_LCD: + SkASSERT(3 * mask.fBounds.width() == static_cast(bitmap.width)); + for (int y = height; y --> 0;) { + const uint8_t* triple = src; + if (lcdIsBGR) { + for (int x = 0; x < width; x++) { + dst[x] = packTriple(sk_apply_lut_if(triple[2], tableR), + sk_apply_lut_if(triple[1], tableG), + sk_apply_lut_if(triple[0], tableB)); + triple += 3; + } + } else { + for (int x = 0; x < width; x++) { + dst[x] = packTriple(sk_apply_lut_if(triple[0], tableR), + sk_apply_lut_if(triple[1], tableG), + sk_apply_lut_if(triple[2], tableB)); + triple += 3; + } + } + src += bitmap.pitch; + dst = (uint16_t*)((char*)dst + dstRB); + } + break; + case FT_PIXEL_MODE_LCD_V: + SkASSERT(3 * mask.fBounds.height() == static_cast(bitmap.rows)); + for (int y = height; y --> 0;) { + const uint8_t* srcR = src; + const uint8_t* srcG = srcR + bitmap.pitch; + const uint8_t* srcB = srcG + bitmap.pitch; + if (lcdIsBGR) { + using std::swap; + swap(srcR, srcB); + } + for (int x = 0; x < width; x++) { + dst[x] = packTriple(sk_apply_lut_if(*srcR++, tableR), + sk_apply_lut_if(*srcG++, tableG), + sk_apply_lut_if(*srcB++, tableB)); + } + src += 3 * bitmap.pitch; + dst = (uint16_t*)((char*)dst + dstRB); + } + break; + default: + SkDEBUGF("FT_Pixel_Mode %d", bitmap.pixel_mode); + SkDEBUGFAIL("unsupported FT_Pixel_Mode for LCD16"); + break; + } +} + +/** + * Copies a FT_Bitmap into an SkMask with the same dimensions. + * + * Yes, No, Never Requested, Never Produced + * + * kBW kA8 k3D kARGB32 kLCD16 + * FT_PIXEL_MODE_MONO Y Y NR N Y + * FT_PIXEL_MODE_GRAY N Y NR N Y + * FT_PIXEL_MODE_GRAY2 NP NP NR NP NP + * FT_PIXEL_MODE_GRAY4 NP NP NR NP NP + * FT_PIXEL_MODE_LCD NP NP NR NP NP + * FT_PIXEL_MODE_LCD_V NP NP NR NP NP + * FT_PIXEL_MODE_BGRA N N NR Y N + * + * TODO: All of these N need to be Y or otherwise ruled out. + */ +void copyFTBitmap(const FT_Bitmap& srcFTBitmap, SkMask& dstMask) { + SkASSERTF(dstMask.fBounds.width() == static_cast(srcFTBitmap.width), + "dstMask.fBounds.width() = %d\n" + "static_cast(srcFTBitmap.width) = %d", + dstMask.fBounds.width(), + static_cast(srcFTBitmap.width) + ); + SkASSERTF(dstMask.fBounds.height() == static_cast(srcFTBitmap.rows), + "dstMask.fBounds.height() = %d\n" + "static_cast(srcFTBitmap.rows) = %d", + dstMask.fBounds.height(), + static_cast(srcFTBitmap.rows) + ); + + const uint8_t* src = reinterpret_cast(srcFTBitmap.buffer); + const FT_Pixel_Mode srcFormat = static_cast(srcFTBitmap.pixel_mode); + // FT_Bitmap::pitch is an int and allowed to be negative. + const int srcPitch = srcFTBitmap.pitch; + const size_t srcRowBytes = SkTAbs(srcPitch); + + uint8_t* dst = dstMask.fImage; + const SkMask::Format dstFormat = static_cast(dstMask.fFormat); + const size_t dstRowBytes = dstMask.fRowBytes; + + const size_t width = srcFTBitmap.width; + const size_t height = srcFTBitmap.rows; + + if (SkMask::kLCD16_Format == dstFormat) { + copyFT2LCD16(srcFTBitmap, dstMask, false, nullptr, nullptr, nullptr); + return; + } + + if ((FT_PIXEL_MODE_MONO == srcFormat && SkMask::kBW_Format == dstFormat) || + (FT_PIXEL_MODE_GRAY == srcFormat && SkMask::kA8_Format == dstFormat)) + { + size_t commonRowBytes = SkTMin(srcRowBytes, dstRowBytes); + for (size_t y = height; y --> 0;) { + memcpy(dst, src, commonRowBytes); + src += srcPitch; + dst += dstRowBytes; + } + } else if (FT_PIXEL_MODE_MONO == srcFormat && SkMask::kA8_Format == dstFormat) { + for (size_t y = height; y --> 0;) { + uint8_t byte = 0; + int bits = 0; + const uint8_t* src_row = src; + uint8_t* dst_row = dst; + for (size_t x = width; x --> 0;) { + if (0 == bits) { + byte = *src_row++; + bits = 8; + } + *dst_row++ = byte & 0x80 ? 0xff : 0x00; + bits--; + byte <<= 1; + } + src += srcPitch; + dst += dstRowBytes; + } + } else if (FT_PIXEL_MODE_BGRA == srcFormat && SkMask::kARGB32_Format == dstFormat) { + // FT_PIXEL_MODE_BGRA is pre-multiplied. + for (size_t y = height; y --> 0;) { + const uint8_t* src_row = src; + SkPMColor* dst_row = reinterpret_cast(dst); + for (size_t x = 0; x < width; ++x) { + uint8_t b = *src_row++; + uint8_t g = *src_row++; + uint8_t r = *src_row++; + uint8_t a = *src_row++; + *dst_row++ = SkPackARGB32(a, r, g, b); +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + *(dst_row-1) = SkFourByteInterp256(*(dst_row-1), SK_ColorWHITE, 0x40); +#endif + } + src += srcPitch; + dst += dstRowBytes; + } + } else { + SkDEBUGF("FT_Pixel_Mode %d, SkMask::Format %d\n", srcFormat, dstFormat); + SkDEBUGFAIL("unsupported combination of FT_Pixel_Mode and SkMask::Format"); + } +} + +inline int convert_8_to_1(unsigned byte) { + SkASSERT(byte <= 0xFF); + // Arbitrary decision that making the cutoff at 1/4 instead of 1/2 in general looks better. + return (byte >> 6) != 0; +} + +uint8_t pack_8_to_1(const uint8_t alpha[8]) { + unsigned bits = 0; + for (int i = 0; i < 8; ++i) { + bits <<= 1; + bits |= convert_8_to_1(alpha[i]); + } + return SkToU8(bits); +} + +void packA8ToA1(const SkMask& mask, const uint8_t* src, size_t srcRB) { + const int height = mask.fBounds.height(); + const int width = mask.fBounds.width(); + const int octs = width >> 3; + const int leftOverBits = width & 7; + + uint8_t* dst = mask.fImage; + const int dstPad = mask.fRowBytes - SkAlign8(width)/8; + SkASSERT(dstPad >= 0); + + const int srcPad = srcRB - width; + SkASSERT(srcPad >= 0); + + for (int y = 0; y < height; ++y) { + for (int i = 0; i < octs; ++i) { + *dst++ = pack_8_to_1(src); + src += 8; + } + if (leftOverBits > 0) { + unsigned bits = 0; + int shift = 7; + for (int i = 0; i < leftOverBits; ++i, --shift) { + bits |= convert_8_to_1(*src++) << shift; + } + *dst++ = bits; + } + src += srcPad; + dst += dstPad; + } +} + +inline SkMask::Format SkMaskFormat_for_SkColorType(SkColorType colorType) { + switch (colorType) { + case kAlpha_8_SkColorType: + return SkMask::kA8_Format; + case kN32_SkColorType: + return SkMask::kARGB32_Format; + default: + SkDEBUGFAIL("unsupported SkBitmap::Config"); + return SkMask::kA8_Format; + } +} + +inline SkColorType SkColorType_for_FTPixelMode(FT_Pixel_Mode pixel_mode) { + switch (pixel_mode) { + case FT_PIXEL_MODE_MONO: + case FT_PIXEL_MODE_GRAY: + return kAlpha_8_SkColorType; + case FT_PIXEL_MODE_BGRA: + return kN32_SkColorType; + default: + SkDEBUGFAIL("unsupported FT_PIXEL_MODE"); + return kAlpha_8_SkColorType; + } +} + +inline SkColorType SkColorType_for_SkMaskFormat(SkMask::Format format) { + switch (format) { + case SkMask::kBW_Format: + case SkMask::kA8_Format: + case SkMask::kLCD16_Format: + return kAlpha_8_SkColorType; + case SkMask::kARGB32_Format: + return kN32_SkColorType; + default: + SkDEBUGFAIL("unsupported destination SkBitmap::Config"); + return kAlpha_8_SkColorType; + } +} + +} // namespace + +void SkScalerContext_FreeType_Base::generateGlyphImage( + FT_Face face, + const SkGlyph& glyph, + const SkMatrix& bitmapTransform) +{ + const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); + const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); + + switch ( face->glyph->format ) { + case FT_GLYPH_FORMAT_OUTLINE: { + FT_Outline* outline = &face->glyph->outline; + + int dx = 0, dy = 0; + if (this->isSubpixel()) { + dx = SkFixedToFDot6(glyph.getSubXFixed()); + dy = SkFixedToFDot6(glyph.getSubYFixed()); + // negate dy since freetype-y-goes-up and skia-y-goes-down + dy = -dy; + } + + memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); + +#ifdef FT_COLOR_H + if (SkMask::kARGB32_Format == glyph.fMaskFormat) { + SkBitmap dstBitmap; + // TODO: mark this as sRGB when the blits will be sRGB. + dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight, + kN32_SkColorType, + kPremul_SkAlphaType), + glyph.rowBytes()); + dstBitmap.setPixels(glyph.fImage); + + // Scale unscaledBitmap into dstBitmap. + SkCanvas canvas(dstBitmap); +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + canvas.clear(0x33FF0000); +#else + canvas.clear(SK_ColorTRANSPARENT); +#endif + canvas.translate(-glyph.fLeft, -glyph.fTop); + + if (this->isSubpixel()) { + canvas.translate(SkFixedToScalar(glyph.getSubXFixed()), + SkFixedToScalar(glyph.getSubYFixed())); + } + + SkPaint paint; + paint.setAntiAlias(true); + + FT_Color *palette; + FT_Error err = FT_Palette_Select(face, 0, &palette); + if (err) { + SK_TRACEFTR(err, "Could not get palette from %s fontFace.", face->family_name); + return; + } + FT_LayerIterator layerIterator; + layerIterator.p = NULL; + FT_Bool haveLayers = false; + FT_UInt layerGlyphIndex; + FT_UInt layerColorIndex; + + while (FT_Get_Color_Glyph_Layer(face, glyph.getGlyphID(), &layerGlyphIndex, + &layerColorIndex, + &layerIterator)) { + haveLayers = true; + if (layerColorIndex == 0xFFFF) { + paint.setColor(SK_ColorBLACK); + } else { + SkColor color = SkColorSetARGB(palette[layerColorIndex].alpha, + palette[layerColorIndex].red, + palette[layerColorIndex].green, + palette[layerColorIndex].blue); + paint.setColor(color); + } + SkPath path; + if (this->generateFacePath(face, layerGlyphIndex, &path)) { + canvas.drawPath(path, paint); + } + } + + if (!haveLayers) { + SK_TRACEFTR(err, "Could not get layers from %s fontFace.", face->family_name); + return; + } + } else +#endif + if (SkMask::kLCD16_Format == glyph.fMaskFormat) { + FT_Outline_Translate(outline, dx, dy); + FT_Error err = FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : + FT_RENDER_MODE_LCD); + if (err) { + SK_TRACEFTR(err, "Could not render glyph %x.", face->glyph); + return; + } + + SkMask mask = glyph.mask(); +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + memset(mask.fImage, 0x80, mask.fBounds.height() * mask.fRowBytes); +#endif + FT_GlyphSlotRec& ftGlyph = *face->glyph; + + if (!SkIRect::Intersects(mask.fBounds, + SkIRect::MakeXYWH( ftGlyph.bitmap_left, + -ftGlyph.bitmap_top, + ftGlyph.bitmap.width, + ftGlyph.bitmap.rows))) + { + return; + } + + // If the FT_Bitmap extent is larger, discard bits of the bitmap outside the mask. + // If the SkMask extent is larger, shrink mask to fit bitmap (clearing discarded). + unsigned char* origBuffer = ftGlyph.bitmap.buffer; + // First align the top left (origin). + if (-ftGlyph.bitmap_top < mask.fBounds.fTop) { + int32_t topDiff = mask.fBounds.fTop - (-ftGlyph.bitmap_top); + ftGlyph.bitmap.buffer += ftGlyph.bitmap.pitch * topDiff; + ftGlyph.bitmap.rows -= topDiff; + ftGlyph.bitmap_top = -mask.fBounds.fTop; + } + if (ftGlyph.bitmap_left < mask.fBounds.fLeft) { + int32_t leftDiff = mask.fBounds.fLeft - ftGlyph.bitmap_left; + ftGlyph.bitmap.buffer += leftDiff; + ftGlyph.bitmap.width -= leftDiff; + ftGlyph.bitmap_left = mask.fBounds.fLeft; + } + if (mask.fBounds.fTop < -ftGlyph.bitmap_top) { + mask.fImage += mask.fRowBytes * (-ftGlyph.bitmap_top - mask.fBounds.fTop); + mask.fBounds.fTop = -ftGlyph.bitmap_top; + } + if (mask.fBounds.fLeft < ftGlyph.bitmap_left) { + mask.fImage += sizeof(uint16_t) * (ftGlyph.bitmap_left - mask.fBounds.fLeft); + mask.fBounds.fLeft = ftGlyph.bitmap_left; + } + // Origins aligned, clean up the width and height. + int ftVertScale = (doVert ? 3 : 1); + int ftHoriScale = (doVert ? 1 : 3); + if (mask.fBounds.height() * ftVertScale < SkToInt(ftGlyph.bitmap.rows)) { + ftGlyph.bitmap.rows = mask.fBounds.height() * ftVertScale; + } + if (mask.fBounds.width() * ftHoriScale < SkToInt(ftGlyph.bitmap.width)) { + ftGlyph.bitmap.width = mask.fBounds.width() * ftHoriScale; + } + if (SkToInt(ftGlyph.bitmap.rows) < mask.fBounds.height() * ftVertScale) { + mask.fBounds.fBottom = mask.fBounds.fTop + ftGlyph.bitmap.rows / ftVertScale; + } + if (SkToInt(ftGlyph.bitmap.width) < mask.fBounds.width() * ftHoriScale) { + mask.fBounds.fRight = mask.fBounds.fLeft + ftGlyph.bitmap.width / ftHoriScale; + } + if (fPreBlend.isApplicable()) { + copyFT2LCD16(ftGlyph.bitmap, mask, doBGR, + fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); + } else { + copyFT2LCD16(ftGlyph.bitmap, mask, doBGR, + fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); + } + // Restore the buffer pointer so FreeType can properly free it. + ftGlyph.bitmap.buffer = origBuffer; + } else { + FT_BBox bbox; + FT_Bitmap target; + FT_Outline_Get_CBox(outline, &bbox); + /* + what we really want to do for subpixel is + offset(dx, dy) + compute_bounds + offset(bbox & !63) + but that is two calls to offset, so we do the following, which + achieves the same thing with only one offset call. + */ + FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), + dy - ((bbox.yMin + dy) & ~63)); + + target.width = glyph.fWidth; + target.rows = glyph.fHeight; + target.pitch = glyph.rowBytes(); + target.buffer = reinterpret_cast(glyph.fImage); + target.pixel_mode = compute_pixel_mode( (SkMask::Format)glyph.fMaskFormat); + target.num_grays = 256; + + FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + for (int y = 0; y < glyph.fHeight; ++y) { + for (int x = 0; x < glyph.fWidth; ++x) { + uint8_t& a = ((uint8_t*)glyph.fImage)[(glyph.rowBytes() * y) + x]; + a = SkTMax(a, 0x20); + } + } +#endif + } + } break; + + case FT_GLYPH_FORMAT_BITMAP: { + FT_Pixel_Mode pixel_mode = static_cast(face->glyph->bitmap.pixel_mode); + SkMask::Format maskFormat = static_cast(glyph.fMaskFormat); + + // Assume that the other formats do not exist. + SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode || + FT_PIXEL_MODE_GRAY == pixel_mode || + FT_PIXEL_MODE_BGRA == pixel_mode); + + // These are the only formats this ScalerContext should request. + SkASSERT(SkMask::kBW_Format == maskFormat || + SkMask::kA8_Format == maskFormat || + SkMask::kARGB32_Format == maskFormat || + SkMask::kLCD16_Format == maskFormat); + + // If no scaling needed, directly copy glyph bitmap. + if (bitmapTransform.isIdentity()) { + SkMask dstMask = glyph.mask(); + copyFTBitmap(face->glyph->bitmap, dstMask); + break; + } + + // Otherwise, scale the bitmap. + + // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB) + SkBitmap unscaledBitmap; + // TODO: mark this as sRGB when the blits will be sRGB. + unscaledBitmap.allocPixels(SkImageInfo::Make(face->glyph->bitmap.width, + face->glyph->bitmap.rows, + SkColorType_for_FTPixelMode(pixel_mode), + kPremul_SkAlphaType)); + + SkMask unscaledBitmapAlias; + unscaledBitmapAlias.fImage = reinterpret_cast(unscaledBitmap.getPixels()); + unscaledBitmapAlias.fBounds.setWH(unscaledBitmap.width(), unscaledBitmap.height()); + unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes(); + unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType()); + copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias); + + // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD. + // BW requires an A8 target for resizing, which can then be down sampled. + // LCD should use a 4x A8 target, which will then be down sampled. + // For simplicity, LCD uses A8 and is replicated. + int bitmapRowBytes = 0; + if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) { + bitmapRowBytes = glyph.rowBytes(); + } + SkBitmap dstBitmap; + // TODO: mark this as sRGB when the blits will be sRGB. + dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight, + SkColorType_for_SkMaskFormat(maskFormat), + kPremul_SkAlphaType), + bitmapRowBytes); + if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { + dstBitmap.allocPixels(); + } else { + dstBitmap.setPixels(glyph.fImage); + } + + // Scale unscaledBitmap into dstBitmap. + SkCanvas canvas(dstBitmap); +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + canvas.clear(0x33FF0000); +#else + canvas.clear(SK_ColorTRANSPARENT); +#endif + canvas.translate(-glyph.fLeft, -glyph.fTop); + canvas.concat(bitmapTransform); + canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top); + + SkPaint paint; + // Using kMedium FilterQuality will cause mipmaps to be generated. Use + // kLow when the results will be roughly the same in order to avoid + // the mipmap generation cost. + // See skbug.com/6967 + if (bitmapTransform.getMinScale() < 0.5) { + paint.setFilterQuality(kMedium_SkFilterQuality); + } else { + paint.setFilterQuality(kLow_SkFilterQuality); + } + canvas.drawBitmap(unscaledBitmap, 0, 0, &paint); + + // If the destination is BW or LCD, convert from A8. + if (SkMask::kBW_Format == maskFormat) { + // Copy the A8 dstBitmap into the A1 glyph.fImage. + SkMask dstMask = glyph.mask(); + packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes()); + } else if (SkMask::kLCD16_Format == maskFormat) { + // Copy the A8 dstBitmap into the LCD16 glyph.fImage. + uint8_t* src = dstBitmap.getAddr8(0, 0); + uint16_t* dst = reinterpret_cast(glyph.fImage); + for (int y = dstBitmap.height(); y --> 0;) { + for (int x = 0; x < dstBitmap.width(); ++x) { + dst[x] = grayToRGB16(src[x]); + } + dst = (uint16_t*)((char*)dst + glyph.rowBytes()); + src += dstBitmap.rowBytes(); + } + } + + } break; + + default: + SkDEBUGFAIL("unknown glyph format"); + memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); + return; + } + +// We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, +// it is optional +#if defined(SK_GAMMA_APPLY_TO_A8) + if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { + uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; + unsigned rowBytes = glyph.rowBytes(); + + for (int y = glyph.fHeight - 1; y >= 0; --y) { + for (int x = glyph.fWidth - 1; x >= 0; --x) { + dst[x] = fPreBlend.fG[dst[x]]; + } + dst += rowBytes; + } + } +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +namespace { + +int move_proc(const FT_Vector* pt, void* ctx) { + SkPath* path = (SkPath*)ctx; + path->close(); // to close the previous contour (if any) + path->moveTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y)); + return 0; +} + +int line_proc(const FT_Vector* pt, void* ctx) { + SkPath* path = (SkPath*)ctx; + path->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y)); + return 0; +} + +int quad_proc(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) { + SkPath* path = (SkPath*)ctx; + path->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y), + SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y)); + return 0; +} + +int cubic_proc(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) { + SkPath* path = (SkPath*)ctx; + path->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y), + SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y), + SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y)); + return 0; +} + +} // namespace + +bool SkScalerContext_FreeType_Base::generateGlyphPath(FT_Face face, SkPath* path) { + FT_Outline_Funcs funcs; + + funcs.move_to = move_proc; + funcs.line_to = line_proc; + funcs.conic_to = quad_proc; + funcs.cubic_to = cubic_proc; + funcs.shift = 0; + funcs.delta = 0; + + FT_Error err = FT_Outline_Decompose(&face->glyph->outline, &funcs, path); + + if (err != 0) { + path->reset(); + return false; + } + + path->close(); + return true; +} + +bool SkScalerContext_FreeType_Base::generateFacePath(FT_Face face, SkGlyphID glyphID, SkPath* path) { + uint32_t flags = 0; //fLoadGlyphFlags; + flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline + flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline) + + FT_Error err = FT_Load_Glyph(face, glyphID, flags); + if (err != 0) { + path->reset(); + return false; + } + + if (!generateGlyphPath(face, path)) { + path->reset(); + return false; + } + return true; +} diff --git a/gfx/skia/skia/src/ports/SkFontHost_FreeType_common.h b/gfx/skia/skia/src/ports/SkFontHost_FreeType_common.h new file mode 100644 index 0000000000..31b46d3cad --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontHost_FreeType_common.h @@ -0,0 +1,127 @@ +/* + * Copyright 2006-2012 The Android Open Source Project + * Copyright 2012 Mozilla Foundation + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKFONTHOST_FREETYPE_COMMON_H_ +#define SKFONTHOST_FREETYPE_COMMON_H_ + +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/private/SkMutex.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkScalerContext.h" +#include "src/utils/SkCharToGlyphCache.h" + +#include "include/core/SkFontMgr.h" + +// These are forward declared to avoid pimpl but also hide the FreeType implementation. +typedef struct FT_LibraryRec_* FT_Library; +typedef struct FT_FaceRec_* FT_Face; +typedef struct FT_StreamRec_* FT_Stream; +typedef signed long FT_Pos; + + +#ifdef SK_DEBUG +const char* SkTraceFtrGetError(int); +#define SK_TRACEFTR(ERR, MSG, ...) \ + SkDebugf("%s:%lu:1: error: 0x%x '%s' " MSG "\n", __FILE__, __LINE__, ERR, \ + SkTraceFtrGetError((int)(ERR)), __VA_ARGS__) +#else +#define SK_TRACEFTR(ERR, ...) do { sk_ignore_unused_variable(ERR); } while (false) +#endif + + +class SkScalerContext_FreeType_Base : public SkScalerContext { +protected: + // See http://freetype.sourceforge.net/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden + // This value was chosen by eyeballing the result in Firefox and trying to match it. + static const FT_Pos kBitmapEmboldenStrength = 1 << 6; + + SkScalerContext_FreeType_Base(sk_sp typeface, const SkScalerContextEffects& effects, + const SkDescriptor *desc) + : INHERITED(std::move(typeface), effects, desc) + {} + + void generateGlyphImage(FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform); + bool generateGlyphPath(FT_Face face, SkPath* path); + bool generateFacePath(FT_Face face, SkGlyphID glyphID, SkPath* path); +private: + typedef SkScalerContext INHERITED; +}; + +class SkTypeface_FreeType : public SkTypeface { +public: + /** For SkFontMgrs to make use of our ability to extract + * name and style from a stream, using FreeType's API. + */ + class Scanner : ::SkNoncopyable { + public: + Scanner(); + ~Scanner(); + struct AxisDefinition { + SkFourByteTag fTag; + SkFixed fMinimum; + SkFixed fDefault; + SkFixed fMaximum; + }; + using AxisDefinitions = SkSTArray<4, AxisDefinition, true>; + bool recognizedFont(SkStreamAsset* stream, int* numFonts) const; + bool scanFont(SkStreamAsset* stream, int ttcIndex, + SkString* name, SkFontStyle* style, bool* isFixedPitch, + AxisDefinitions* axes) const; + static void computeAxisValues( + AxisDefinitions axisDefinitions, + const SkFontArguments::VariationPosition position, + SkFixed* axisValues, + const SkString& name); + static bool GetAxes(FT_Face face, AxisDefinitions* axes); + + private: + FT_Face openFace(SkStreamAsset* stream, int ttcIndex, FT_Stream ftStream) const; + FT_Library fLibrary; + mutable SkMutex fLibraryMutex; + }; + + /** Fetch units/EM from "head" table if needed (ie for bitmap fonts) */ + static int GetUnitsPerEm(FT_Face face); +protected: + SkTypeface_FreeType(const SkFontStyle& style, bool isFixedPitch) + : INHERITED(style, isFixedPitch) + {} + + std::unique_ptr cloneFontData(const SkFontArguments&) const; + virtual SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&, + const SkDescriptor*) const override; + void onFilterRec(SkScalerContextRec*) const override; + void getGlyphToUnicodeMap(SkUnichar*) const override; + std::unique_ptr onGetAdvancedMetrics() const override; + void getPostScriptGlyphNames(SkString* dstArray) const override; + int onGetUPEM() const override; + bool onGetKerningPairAdjustments(const uint16_t glyphs[], int count, + int32_t adjustments[]) const override; + void onCharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const override; + int onCountGlyphs() const override; + + LocalizedStrings* onCreateFamilyNameIterator() const override; + + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override; + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override; + int onGetTableTags(SkFontTableTag tags[]) const override; + size_t onGetTableData(SkFontTableTag, size_t offset, + size_t length, void* data) const override; + sk_sp onCopyTableData(SkFontTableTag) const override; + +private: + mutable SkMutex fC2GCacheMutex; + mutable SkCharToGlyphCache fC2GCache; + + typedef SkTypeface INHERITED; +}; + +#endif // SKFONTHOST_FREETYPE_COMMON_H_ diff --git a/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp b/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp new file mode 100644 index 0000000000..c10f56120c --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp @@ -0,0 +1,688 @@ + +/* + * Copyright 2012 Mozilla Foundation + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/ports/SkFontHost_FreeType_common.h" + +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkFDot6.h" +#include "include/core/SkFontMetrics.h" +#include "include/core/SkPath.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkTypefaceCache.h" + +#include + +#include +#include FT_FREETYPE_H +#include FT_OUTLINE_H + +// for FT_GlyphSlot_Embolden +#ifdef FT_SYNTHESIS_H +#include FT_SYNTHESIS_H +#endif + +// for FT_Library_SetLcdFilter +#ifdef FT_LCD_FILTER_H +#include FT_LCD_FILTER_H +#else +typedef enum FT_LcdFilter_ +{ + FT_LCD_FILTER_NONE = 0, + FT_LCD_FILTER_DEFAULT = 1, + FT_LCD_FILTER_LIGHT = 2, + FT_LCD_FILTER_LEGACY = 16, +} FT_LcdFilter; +#endif + +// If compiling with FreeType before 2.5.0 +#ifndef FT_LOAD_COLOR +# define FT_LOAD_COLOR ( 1L << 20 ) +# define FT_PIXEL_MODE_BGRA 7 +#endif + +// If compiling with FreeType before 2.12.0 +#ifndef FT_FACE_FLAG_SVG +// We need the format tag so that we can switch on it and handle a possibly- +// newer version of the library at runtime. +static constexpr FT_UInt32 FT_IMAGE_TAG(FT_GLYPH_FORMAT_SVG, 'S', 'V', 'G', ' '); +#endif + +#ifndef SK_CAN_USE_DLOPEN +#define SK_CAN_USE_DLOPEN 1 +#endif +#if SK_CAN_USE_DLOPEN +#include +#endif + +#ifndef SK_FONTHOST_CAIRO_STANDALONE +#define SK_FONTHOST_CAIRO_STANDALONE 1 +#endif + +static bool gFontHintingEnabled = true; +static FT_Error (*gSetLcdFilter)(FT_Library, FT_LcdFilter) = nullptr; + +extern "C" +{ + void mozilla_LockFTLibrary(FT_Library aLibrary); + void mozilla_UnlockFTLibrary(FT_Library aLibrary); + void mozilla_AddRefSharedFTFace(void* aContext); + void mozilla_ReleaseSharedFTFace(void* aContext, void* aOwner); + void mozilla_ForgetSharedFTFaceLockOwner(void* aContext, void* aOwner); + int mozilla_LockSharedFTFace(void* aContext, void* aOwner); + void mozilla_UnlockSharedFTFace(void* aContext); + FT_Error mozilla_LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex, int32_t aFlags); + // Implemented in webrender: + void mozilla_glyphslot_embolden_less(FT_GlyphSlot slot); +} + +void SkInitCairoFT(bool fontHintingEnabled) +{ + gFontHintingEnabled = fontHintingEnabled; +#if SK_CAN_USE_DLOPEN + gSetLcdFilter = (FT_Error (*)(FT_Library, FT_LcdFilter))dlsym(RTLD_DEFAULT, "FT_Library_SetLcdFilter"); +#else + gSetLcdFilter = &FT_Library_SetLcdFilter; +#endif + // FT_Library_SetLcdFilter may be provided but have no effect if FreeType + // is built without FT_CONFIG_OPTION_SUBPIXEL_RENDERING. + if (gSetLcdFilter && + gSetLcdFilter(nullptr, FT_LCD_FILTER_NONE) == FT_Err_Unimplemented_Feature) { + gSetLcdFilter = nullptr; + } +} + +class SkScalerContext_CairoFT : public SkScalerContext_FreeType_Base { +public: + SkScalerContext_CairoFT(sk_sp typeface, + const SkScalerContextEffects& effects, + const SkDescriptor* desc, FT_Face face, + void* faceContext, SkPixelGeometry pixelGeometry, + FT_LcdFilter lcdFilter); + + virtual ~SkScalerContext_CairoFT() { + mozilla_ForgetSharedFTFaceLockOwner(fFTFaceContext, this); + } + + bool isValid() const { return fFTFaceContext != nullptr; } + + void Lock() { + if (!mozilla_LockSharedFTFace(fFTFaceContext, this)) { + FT_Set_Transform(fFTFace, fHaveShape ? &fShapeMatrixFT : nullptr, nullptr); + FT_Set_Char_Size(fFTFace, FT_F26Dot6(fScaleX * 64.0f + 0.5f), + FT_F26Dot6(fScaleY * 64.0f + 0.5f), 0, 0); + } + } + + void Unlock() { mozilla_UnlockSharedFTFace(fFTFaceContext); } + +protected: + unsigned generateGlyphCount() override; + bool generateAdvance(SkGlyph* glyph) override; + void generateMetrics(SkGlyph* glyph) override; + void generateImage(const SkGlyph& glyph) override; + bool generatePath(SkGlyphID glyphID, SkPath* path) override; + void generateFontMetrics(SkFontMetrics* metrics) override; + +private: + bool computeShapeMatrix(const SkMatrix& m); + void prepareGlyph(FT_GlyphSlot glyph); + + FT_Face fFTFace; + void* fFTFaceContext; + FT_Int32 fLoadGlyphFlags; + FT_LcdFilter fLcdFilter; + SkScalar fScaleX; + SkScalar fScaleY; + SkMatrix fShapeMatrix; + FT_Matrix fShapeMatrixFT; + bool fHaveShape; +}; + +class AutoLockFTFace { +public: + AutoLockFTFace(SkScalerContext_CairoFT* scalerContext) + : fScalerContext(scalerContext) { + fScalerContext->Lock(); + } + + ~AutoLockFTFace() { fScalerContext->Unlock(); } + +private: + SkScalerContext_CairoFT* fScalerContext; +}; + +static bool isLCD(const SkScalerContextRec& rec) { + return SkMask::kLCD16_Format == rec.fMaskFormat; +} + +static bool bothZero(SkScalar a, SkScalar b) { + return 0 == a && 0 == b; +} + +// returns false if there is any non-90-rotation or skew +static bool isAxisAligned(const SkScalerContextRec& rec) { + return 0 == rec.fPreSkewX && + (bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) || + bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1])); +} + +class SkCairoFTTypeface : public SkTypeface { +public: + std::unique_ptr onOpenStream(int*) const override { return nullptr; } + + std::unique_ptr onGetAdvancedMetrics() const override + { + SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetAdvancedMetrics unimplemented\n")); + return nullptr; + } + + SkScalerContext* onCreateScalerContext(const SkScalerContextEffects& effects, const SkDescriptor* desc) const override + { + SkScalerContext_CairoFT* ctx = new SkScalerContext_CairoFT( + sk_ref_sp(const_cast(this)), effects, desc, + fFTFace, fFTFaceContext, fPixelGeometry, fLcdFilter); + if (!ctx->isValid()) { + delete ctx; + return nullptr; + } + return ctx; + } + + void onFilterRec(SkScalerContextRec* rec) const override + { + // rotated text looks bad with hinting, so we disable it as needed + if (!gFontHintingEnabled || !isAxisAligned(*rec)) { + rec->setHinting(SkFontHinting::kNone); + } + + // Don't apply any gamma so that we match cairo-ft's results. + rec->ignorePreBlend(); + } + + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override + { + SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetFontDescriptor unimplemented\n")); + } + + void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override + { + mozilla_LockSharedFTFace(fFTFaceContext, nullptr); + for (int i = 0; i < count; ++i) { + glyphs[i] = SkToU16(FT_Get_Char_Index(fFTFace, chars[i])); + } + mozilla_UnlockSharedFTFace(fFTFaceContext); + } + + int onCountGlyphs() const override + { + return fFTFace->num_glyphs; + } + + int onGetUPEM() const override + { + return 0; + } + + SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override + { + return nullptr; + } + + void onGetFamilyName(SkString* familyName) const override + { + familyName->reset(); + } + + int onGetTableTags(SkFontTableTag*) const override + { + return 0; + } + + size_t onGetTableData(SkFontTableTag, size_t, size_t, void*) const override + { + return 0; + } + + void getPostScriptGlyphNames(SkString*) const override {} + + void getGlyphToUnicodeMap(SkUnichar*) const override {} + + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override + { + return 0; + } + + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override + { + return 0; + } + + sk_sp onMakeClone(const SkFontArguments& args) const override { + return sk_ref_sp(this); + } + + SkCairoFTTypeface(FT_Face face, void* faceContext, + SkPixelGeometry pixelGeometry, FT_LcdFilter lcdFilter) + : SkTypeface(SkFontStyle::Normal()) + , fFTFace(face) + , fFTFaceContext(faceContext) + , fPixelGeometry(pixelGeometry) + , fLcdFilter(lcdFilter) + { + mozilla_AddRefSharedFTFace(fFTFaceContext); + } + + void* GetFTFaceContext() const { return fFTFaceContext; } + + bool hasColorGlyphs() const override + { + // Check if the font has scalable outlines. If not, then avoid trying + // to render it as a path. + if (fFTFace) { + return !FT_IS_SCALABLE(fFTFace); + } + return false; + } + +private: + ~SkCairoFTTypeface() + { + mozilla_ReleaseSharedFTFace(fFTFaceContext, nullptr); + } + + FT_Face fFTFace; + void* fFTFaceContext; + SkPixelGeometry fPixelGeometry; + FT_LcdFilter fLcdFilter; +}; + +static bool FindByFTFaceContext(SkTypeface* typeface, void* context) { + return static_cast(typeface)->GetFTFaceContext() == context; +} + +SkTypeface* SkCreateTypefaceFromCairoFTFont(FT_Face face, void* faceContext, + SkPixelGeometry pixelGeometry, + uint8_t lcdFilter) +{ + sk_sp typeface = + SkTypefaceCache::FindByProcAndRef(FindByFTFaceContext, faceContext); + if (!typeface) { + typeface = sk_make_sp(face, faceContext, pixelGeometry, + (FT_LcdFilter)lcdFilter); + SkTypefaceCache::Add(typeface); + } + + return typeface.release(); +} + +SkScalerContext_CairoFT::SkScalerContext_CairoFT( + sk_sp typeface, const SkScalerContextEffects& effects, + const SkDescriptor* desc, FT_Face face, void* faceContext, + SkPixelGeometry pixelGeometry, FT_LcdFilter lcdFilter) + : SkScalerContext_FreeType_Base(std::move(typeface), effects, desc) + , fFTFace(face) + , fFTFaceContext(faceContext) + , fLcdFilter(lcdFilter) +{ + SkMatrix matrix; + fRec.getSingleMatrix(&matrix); + + computeShapeMatrix(matrix); + + FT_Int32 loadFlags = FT_LOAD_DEFAULT; + + if (SkMask::kBW_Format == fRec.fMaskFormat) { + if (fRec.getHinting() == SkFontHinting::kNone) { + loadFlags |= FT_LOAD_NO_HINTING; + } else { + loadFlags = FT_LOAD_TARGET_MONO; + } + loadFlags |= FT_LOAD_MONOCHROME; + } else { + if (isLCD(fRec)) { + switch (pixelGeometry) { + case kRGB_H_SkPixelGeometry: + default: + break; + case kRGB_V_SkPixelGeometry: + fRec.fFlags |= SkScalerContext::kLCD_Vertical_Flag; + break; + case kBGR_H_SkPixelGeometry: + fRec.fFlags |= SkScalerContext::kLCD_BGROrder_Flag; + break; + case kBGR_V_SkPixelGeometry: + fRec.fFlags |= SkScalerContext::kLCD_Vertical_Flag | + SkScalerContext::kLCD_BGROrder_Flag; + break; + } + } + + switch (fRec.getHinting()) { + case SkFontHinting::kNone: + loadFlags |= FT_LOAD_NO_HINTING; + break; + case SkFontHinting::kSlight: + loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT + break; + case SkFontHinting::kNormal: + if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { + loadFlags |= FT_LOAD_FORCE_AUTOHINT; + } + break; + case SkFontHinting::kFull: + if (isLCD(fRec)) { + if (fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag) { + loadFlags = FT_LOAD_TARGET_LCD_V; + } else { + loadFlags = FT_LOAD_TARGET_LCD; + } + } + if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { + loadFlags |= FT_LOAD_FORCE_AUTOHINT; + } + break; + default: + SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting()); + break; + } + } + + // Disable autohinting when asked to disable hinting, except for "tricky" fonts. + if (!gFontHintingEnabled) { + if (fFTFace && !(fFTFace->face_flags & FT_FACE_FLAG_TRICKY)) { + loadFlags |= FT_LOAD_NO_AUTOHINT; + } + } + + if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) { + loadFlags |= FT_LOAD_NO_BITMAP; + } + + // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct + // advances, as fontconfig and cairo do. + // See http://code.google.com/p/skia/issues/detail?id=222. + loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; + + loadFlags |= FT_LOAD_COLOR; + + fLoadGlyphFlags = loadFlags; +} + +bool SkScalerContext_CairoFT::computeShapeMatrix(const SkMatrix& m) +{ + // Compute a shape matrix compatible with Cairo's _compute_transform. + // Finds major/minor scales and uses them to normalize the transform. + double scaleX = m.getScaleX(); + double skewX = m.getSkewX(); + double skewY = m.getSkewY(); + double scaleY = m.getScaleY(); + double det = scaleX * scaleY - skewY * skewX; + if (!std::isfinite(det)) { + fScaleX = fRec.fTextSize * fRec.fPreScaleX; + fScaleY = fRec.fTextSize; + fHaveShape = false; + return false; + } + double major = det != 0.0 ? hypot(scaleX, skewY) : 0.0; + double minor = major != 0.0 ? fabs(det) / major : 0.0; + // Limit scales to be above 1pt. + major = SkTMax(major, 1.0); + minor = SkTMax(minor, 1.0); + + // If the font is not scalable, then choose the best available size. + if (fFTFace && !FT_IS_SCALABLE(fFTFace)) { + double bestDist = DBL_MAX; + FT_Int bestSize = -1; + for (FT_Int i = 0; i < fFTFace->num_fixed_sizes; i++) { + // Distance is positive if strike is larger than desired size, + // or negative if smaller. If previously a found smaller strike, + // then prefer a larger strike. Otherwise, minimize distance. + double dist = fFTFace->available_sizes[i].y_ppem / 64.0 - minor; + if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { + bestDist = dist; + bestSize = i; + } + } + if (bestSize < 0) { + fScaleX = fRec.fTextSize * fRec.fPreScaleX; + fScaleY = fRec.fTextSize; + fHaveShape = false; + return false; + } + major = fFTFace->available_sizes[bestSize].x_ppem / 64.0; + minor = fFTFace->available_sizes[bestSize].y_ppem / 64.0; + fHaveShape = true; + } else { + fHaveShape = !m.isScaleTranslate() || scaleX < 0.0 || scaleY < 0.0; + } + + fScaleX = SkDoubleToScalar(major); + fScaleY = SkDoubleToScalar(minor); + + if (fHaveShape) { + // Normalize the transform and convert to fixed-point. + fShapeMatrix = m; + fShapeMatrix.preScale(SkDoubleToScalar(1.0 / major), SkDoubleToScalar(1.0 / minor)); + + fShapeMatrixFT.xx = SkScalarToFixed(fShapeMatrix.getScaleX()); + fShapeMatrixFT.yx = SkScalarToFixed(-fShapeMatrix.getSkewY()); + fShapeMatrixFT.xy = SkScalarToFixed(-fShapeMatrix.getSkewX()); + fShapeMatrixFT.yy = SkScalarToFixed(fShapeMatrix.getScaleY()); + } + return true; +} + +unsigned SkScalerContext_CairoFT::generateGlyphCount() +{ + return fFTFace->num_glyphs; +} + +bool SkScalerContext_CairoFT::generateAdvance(SkGlyph* glyph) +{ + generateMetrics(glyph); + return !glyph->isEmpty(); +} + +void SkScalerContext_CairoFT::prepareGlyph(FT_GlyphSlot glyph) +{ + if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { + // Not FT_GlyphSlot_Embolden because we want a less extreme effect. + mozilla_glyphslot_embolden_less(glyph); + } +} + +void SkScalerContext_CairoFT::generateMetrics(SkGlyph* glyph) +{ + glyph->fMaskFormat = fRec.fMaskFormat; + + glyph->zeroMetrics(); + + AutoLockFTFace faceLock(this); + + FT_Error err = mozilla_LoadFTGlyph(fFTFace, glyph->getGlyphID(), fLoadGlyphFlags); + if (err != 0) { + return; + } + + prepareGlyph(fFTFace->glyph); + + glyph->fAdvanceX = SkFDot6ToFloat(fFTFace->glyph->advance.x); + glyph->fAdvanceY = -SkFDot6ToFloat(fFTFace->glyph->advance.y); + + SkIRect bounds; + switch (fFTFace->glyph->format) { + case FT_GLYPH_FORMAT_OUTLINE: + if (!fFTFace->glyph->outline.n_contours) { + return; + } + + FT_BBox bbox; + FT_Outline_Get_CBox(&fFTFace->glyph->outline, &bbox); + if (this->isSubpixel()) { + int dx = SkFixedToFDot6(glyph->getSubXFixed()); + int dy = SkFixedToFDot6(glyph->getSubYFixed()); + bbox.xMin += dx; + bbox.yMin -= dy; + bbox.xMax += dx; + bbox.yMax -= dy; + } + bbox.xMin &= ~63; + bbox.yMin &= ~63; + bbox.xMax = (bbox.xMax + 63) & ~63; + bbox.yMax = (bbox.yMax + 63) & ~63; + bounds = SkIRect::MakeLTRB(SkFDot6Floor(bbox.xMin), + -SkFDot6Floor(bbox.yMax), + SkFDot6Floor(bbox.xMax), + -SkFDot6Floor(bbox.yMin)); + + if (isLCD(fRec)) { + // In FreeType < 2.8.1, LCD filtering, if explicitly used, may + // add padding to the glyph. When not used, there is no padding. + // As of 2.8.1, LCD filtering is now always supported and may + // add padding even if an LCD filter is not explicitly set. + // Regardless, if no LCD filtering is used, or if LCD filtering + // doesn't add padding, it is safe to modify the glyph's bounds + // here. generateGlyphImage will detect if the mask is smaller + // than the bounds and clip things appropriately. + if (fRec.fFlags & kLCD_Vertical_Flag) { + bounds.outset(0, 1); + } else { + bounds.outset(1, 0); + } + } + break; + case FT_GLYPH_FORMAT_BITMAP: + if (fFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + glyph->fMaskFormat = SkMask::kARGB32_Format; + } + + if (isLCD(fRec)) { + fRec.fMaskFormat = SkMask::kA8_Format; + } + + if (fHaveShape) { + // Ensure filtering is preserved when the bitmap is transformed. + // Otherwise, the result will look horrifically aliased. + if (fRec.fMaskFormat == SkMask::kBW_Format) { + fRec.fMaskFormat = SkMask::kA8_Format; + } + + // Apply the shape matrix to the glyph's bounding box. + SkRect srcRect = SkRect::MakeXYWH( + SkIntToScalar(fFTFace->glyph->bitmap_left), + -SkIntToScalar(fFTFace->glyph->bitmap_top), + SkIntToScalar(fFTFace->glyph->bitmap.width), + SkIntToScalar(fFTFace->glyph->bitmap.rows)); + SkRect destRect; + fShapeMatrix.mapRect(&destRect, srcRect); + SkIRect glyphRect = destRect.roundOut(); + bounds = SkIRect::MakeXYWH(SkScalarRoundToInt(destRect.fLeft), + SkScalarRoundToInt(destRect.fTop), + glyphRect.width(), + glyphRect.height()); + } else { + bounds = SkIRect::MakeXYWH(fFTFace->glyph->bitmap_left, + -fFTFace->glyph->bitmap_top, + fFTFace->glyph->bitmap.width, + fFTFace->glyph->bitmap.rows); + } + break; + case FT_GLYPH_FORMAT_SVG: + // We don't support getting glyph bounds for SVG, but at least the advance + // should be correctly returned, and we don't want to fire an assertion. + break; + default: + SkDEBUGFAIL("unknown glyph format"); + return; + } + + if (SkIRect::MakeXYWH(SHRT_MIN, SHRT_MIN, USHRT_MAX, USHRT_MAX).contains(bounds)) { + glyph->fWidth = SkToU16(bounds.width()); + glyph->fHeight = SkToU16(bounds.height()); + glyph->fLeft = SkToS16(bounds.left()); + glyph->fTop = SkToS16(bounds.top()); + } +} + +void SkScalerContext_CairoFT::generateImage(const SkGlyph& glyph) +{ + AutoLockFTFace faceLock(this); + + FT_Error err = mozilla_LoadFTGlyph(fFTFace, glyph.getGlyphID(), fLoadGlyphFlags); + + if (err != 0) { + memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); + return; + } + + prepareGlyph(fFTFace->glyph); + + bool useLcdFilter = + fFTFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE && + glyph.maskFormat() == SkMask::kLCD16_Format && + gSetLcdFilter; + if (useLcdFilter) { + mozilla_LockFTLibrary(fFTFace->glyph->library); + gSetLcdFilter(fFTFace->glyph->library, fLcdFilter); + } + + SkMatrix matrix; + if (fFTFace->glyph->format == FT_GLYPH_FORMAT_BITMAP && + fHaveShape) { + matrix = fShapeMatrix; + } else { + matrix.setIdentity(); + } + generateGlyphImage(fFTFace, glyph, matrix); + + if (useLcdFilter) { + gSetLcdFilter(fFTFace->glyph->library, FT_LCD_FILTER_NONE); + mozilla_UnlockFTLibrary(fFTFace->glyph->library); + } +} + +bool SkScalerContext_CairoFT::generatePath(SkGlyphID glyphID, SkPath* path) +{ + AutoLockFTFace faceLock(this); + + SkASSERT(path); + + uint32_t flags = fLoadGlyphFlags; + flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline + flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline) + + FT_Error err = mozilla_LoadFTGlyph(fFTFace, glyphID, flags); + + if (err != 0) { + path->reset(); + return false; + } + + prepareGlyph(fFTFace->glyph); + + return generateGlyphPath(fFTFace, path); +} + +void SkScalerContext_CairoFT::generateFontMetrics(SkFontMetrics* metrics) +{ + if (metrics) { + memset(metrics, 0, sizeof(SkFontMetrics)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkFontMgr.h" + +sk_sp SkFontMgr::Factory() { + // todo + return nullptr; +} + diff --git a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp new file mode 100644 index 0000000000..8e7b9363ba --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp @@ -0,0 +1,3025 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#ifdef SK_BUILD_FOR_MAC +#import +#endif + +#ifdef SK_BUILD_FOR_IOS +#include +#include +#include +#include +#endif + +#include "include/core/SkFontMetrics.h" +#include "include/core/SkFontMgr.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/ports/SkTypeface_mac.h" +#include "include/private/SkColorData.h" +#include "include/private/SkFloatingPoint.h" +#include "include/private/SkMutex.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "include/utils/mac/SkCGUtils.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkAutoMalloc.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkEndian.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkTypefaceCache.h" +#include "src/core/SkUtils.h" +#include "src/sfnt/SkOTTable_OS_2.h" +#include "src/sfnt/SkOTUtils.h" +#include "src/sfnt/SkSFNTHeader.h" +#include "src/utils/SkUTF.h" +#include "src/utils/mac/SkUniqueCFRef.h" + +#include + +#include + +// Set to make glyph bounding boxes visible. +#define SK_SHOW_TEXT_BLIT_COVERAGE 0 + +CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face) { + return face ? (CTFontRef)face->internal_private_getCTFontRef() : nullptr; +} + +class SkScalerContext_Mac; + +static SkUniqueCFRef make_CFString(const char s[]) { + return SkUniqueCFRef(CFStringCreateWithCString(nullptr, s, kCFStringEncodingUTF8)); +} + +// inline versions of these rect helpers + +static bool CGRectIsEmpty_inline(const CGRect& rect) { + return rect.size.width <= 0 || rect.size.height <= 0; +} + +static CGFloat CGRectGetMinX_inline(const CGRect& rect) { + return rect.origin.x; +} + +static CGFloat CGRectGetMaxX_inline(const CGRect& rect) { + return rect.origin.x + rect.size.width; +} + +static CGFloat CGRectGetMinY_inline(const CGRect& rect) { + return rect.origin.y; +} + +static CGFloat CGRectGetMaxY_inline(const CGRect& rect) { + return rect.origin.y + rect.size.height; +} + +static CGFloat CGRectGetWidth_inline(const CGRect& rect) { + return rect.size.width; +} + +/////////////////////////////////////////////////////////////////////////////// + +static void sk_memset_rect32(uint32_t* ptr, uint32_t value, + int width, int height, size_t rowBytes) { + SkASSERT(width); + SkASSERT(width * sizeof(uint32_t) <= rowBytes); + + if (width >= 32) { + while (height) { + sk_memset32(ptr, value, width); + ptr = (uint32_t*)((char*)ptr + rowBytes); + height -= 1; + } + return; + } + + rowBytes -= width * sizeof(uint32_t); + + if (width >= 8) { + while (height) { + int w = width; + do { + *ptr++ = value; *ptr++ = value; + *ptr++ = value; *ptr++ = value; + *ptr++ = value; *ptr++ = value; + *ptr++ = value; *ptr++ = value; + w -= 8; + } while (w >= 8); + while (--w >= 0) { + *ptr++ = value; + } + ptr = (uint32_t*)((char*)ptr + rowBytes); + height -= 1; + } + } else { + while (height) { + int w = width; + do { + *ptr++ = value; + } while (--w > 0); + ptr = (uint32_t*)((char*)ptr + rowBytes); + height -= 1; + } + } +} + +typedef uint32_t CGRGBPixel; + +static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) { + return pixel & 0xFF; +} + +static CGFloat ScalarToCG(SkScalar scalar) { + if (sizeof(CGFloat) == sizeof(float)) { + return SkScalarToFloat(scalar); + } else { + SkASSERT(sizeof(CGFloat) == sizeof(double)); + return (CGFloat) SkScalarToDouble(scalar); + } +} + +static SkScalar CGToScalar(CGFloat cgFloat) { + if (sizeof(CGFloat) == sizeof(float)) { + return SkFloatToScalar(cgFloat); + } else { + SkASSERT(sizeof(CGFloat) == sizeof(double)); + return SkDoubleToScalar(cgFloat); + } +} + +static float CGToFloat(CGFloat cgFloat) { + if (sizeof(CGFloat) == sizeof(float)) { + return cgFloat; + } else { + SkASSERT(sizeof(CGFloat) == sizeof(double)); + return static_cast(cgFloat); + } +} + +static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix) { + return CGAffineTransformMake( ScalarToCG(matrix[SkMatrix::kMScaleX]), + -ScalarToCG(matrix[SkMatrix::kMSkewY] ), + -ScalarToCG(matrix[SkMatrix::kMSkewX] ), + ScalarToCG(matrix[SkMatrix::kMScaleY]), + ScalarToCG(matrix[SkMatrix::kMTransX]), + ScalarToCG(matrix[SkMatrix::kMTransY])); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) + +/** Drawn in FontForge, reduced with fonttools ttx, converted by xxd -i, + * this TrueType font contains a glyph of the spider. + * + * To re-forge the original bytes of the TrueType font file, + * remove all ',|( +0x)' from this definition, + * copy the data to the clipboard, + * run 'pbpaste | xxd -p -r - spider.ttf'. + */ +static constexpr const uint8_t kSpiderSymbol_ttf[] = { + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x80, 0x00, 0x03, 0x00, 0x40, + 0x47, 0x44, 0x45, 0x46, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x07, 0xa8, + 0x00, 0x00, 0x00, 0x18, 0x4f, 0x53, 0x2f, 0x32, 0x8a, 0xf4, 0xfb, 0xdb, + 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70, + 0xe0, 0x7f, 0x10, 0x7e, 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x54, + 0x67, 0x61, 0x73, 0x70, 0xff, 0xff, 0x00, 0x03, 0x00, 0x00, 0x07, 0xa0, + 0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66, 0x97, 0x0b, 0x6a, 0xf6, + 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x03, 0x40, 0x68, 0x65, 0x61, 0x64, + 0x0f, 0xa2, 0x24, 0x1a, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x36, + 0x68, 0x68, 0x65, 0x61, 0x0e, 0xd3, 0x07, 0x3f, 0x00, 0x00, 0x01, 0x04, + 0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78, 0x10, 0x03, 0x00, 0x44, + 0x00, 0x00, 0x01, 0xa8, 0x00, 0x00, 0x00, 0x0e, 0x6c, 0x6f, 0x63, 0x61, + 0x01, 0xb4, 0x00, 0x28, 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x0a, + 0x6d, 0x61, 0x78, 0x70, 0x00, 0x4a, 0x01, 0x4d, 0x00, 0x00, 0x01, 0x28, + 0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0xc3, 0xe5, 0x39, 0xd4, + 0x00, 0x00, 0x05, 0x58, 0x00, 0x00, 0x02, 0x28, 0x70, 0x6f, 0x73, 0x74, + 0xff, 0x03, 0x00, 0x67, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x0f, 0x08, 0x1d, + 0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd1, 0x97, 0xa8, 0x5a, 0x00, 0x00, 0x00, 0x00, 0xd6, 0xe8, 0x32, 0x33, + 0x00, 0x03, 0xff, 0x3b, 0x08, 0x00, 0x05, 0x55, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x05, 0x55, 0xff, 0x3b, 0x01, 0x79, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x1c, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x2e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x01, 0x90, 0x00, 0x05, + 0x00, 0x00, 0x05, 0x33, 0x05, 0x99, 0x00, 0x00, 0x01, 0x1e, 0x05, 0x33, + 0x05, 0x99, 0x00, 0x00, 0x03, 0xd7, 0x00, 0x66, 0x02, 0x12, 0x00, 0x00, + 0x05, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x73, 0x6b, 0x69, 0x61, 0x00, 0xc0, 0x00, 0x00, 0xf0, 0x21, + 0x06, 0x66, 0xfe, 0x66, 0x01, 0x79, 0x05, 0x55, 0x00, 0xc5, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x01, 0x08, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x48, + 0x00, 0x00, 0x00, 0x0e, 0x00, 0x08, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x09, 0x00, 0x0d, 0x00, 0x1d, 0x00, 0x21, 0xf0, 0x21, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d, 0x00, 0x1d, 0x00, 0x21, + 0xf0, 0x21, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf9, 0xff, 0xf5, 0xff, 0xe4, + 0xff, 0xe2, 0x0f, 0xe2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x01, 0xa0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x44, + 0x00, 0x00, 0x02, 0x64, 0x05, 0x55, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x33, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0x44, 0x02, 0x20, 0xfe, + 0x24, 0x01, 0x98, 0xfe, 0x68, 0x05, 0x55, 0xfa, 0xab, 0x44, 0x04, 0xcd, + 0x00, 0x04, 0x00, 0x03, 0xff, 0x3b, 0x08, 0x00, 0x05, 0x4c, 0x00, 0x15, + 0x00, 0x1d, 0x00, 0x25, 0x01, 0x1b, 0x00, 0x00, 0x01, 0x36, 0x37, 0x36, + 0x27, 0x26, 0x07, 0x06, 0x06, 0x23, 0x22, 0x27, 0x26, 0x27, 0x26, 0x07, + 0x06, 0x17, 0x16, 0x17, 0x16, 0x32, 0x37, 0x32, 0x35, 0x34, 0x23, 0x22, + 0x15, 0x14, 0x27, 0x32, 0x35, 0x34, 0x23, 0x22, 0x15, 0x14, 0x03, 0x32, + 0x17, 0x30, 0x17, 0x31, 0x36, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x33, + 0x32, 0x33, 0x16, 0x33, 0x32, 0x17, 0x16, 0x07, 0x06, 0x23, 0x22, 0x27, + 0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x1f, 0x02, 0x37, 0x36, 0x37, 0x36, 0x33, 0x32, 0x17, 0x17, 0x16, 0x33, + 0x16, 0x17, 0x16, 0x07, 0x06, 0x23, 0x22, 0x27, 0x27, 0x26, 0x23, 0x22, + 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x33, 0x32, 0x33, 0x32, + 0x37, 0x36, 0x37, 0x36, 0x17, 0x16, 0x1f, 0x02, 0x16, 0x17, 0x16, 0x15, + 0x14, 0x23, 0x22, 0x27, 0x27, 0x26, 0x27, 0x27, 0x26, 0x27, 0x26, 0x07, + 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x15, 0x14, 0x07, 0x06, 0x07, + 0x06, 0x23, 0x22, 0x27, 0x26, 0x07, 0x06, 0x07, 0x06, 0x15, 0x14, 0x17, + 0x16, 0x17, 0x16, 0x15, 0x14, 0x07, 0x06, 0x23, 0x22, 0x27, 0x26, 0x27, + 0x26, 0x35, 0x34, 0x37, 0x36, 0x37, 0x36, 0x37, 0x34, 0x27, 0x26, 0x07, + 0x06, 0x07, 0x06, 0x0f, 0x02, 0x06, 0x23, 0x22, 0x27, 0x26, 0x35, 0x34, + 0x37, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, + 0x26, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x07, 0x06, 0x23, 0x22, + 0x27, 0x26, 0x35, 0x34, 0x37, 0x36, 0x37, 0x37, 0x36, 0x37, 0x37, 0x36, + 0x37, 0x36, 0x37, 0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, + 0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x27, 0x26, 0x27, 0x26, + 0x27, 0x26, 0x35, 0x34, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x33, 0x32, + 0x17, 0x16, 0x33, 0x32, 0x37, 0x36, 0x35, 0x34, 0x37, 0x36, 0x37, 0x36, + 0x33, 0x04, 0xf5, 0x23, 0x13, 0x11, 0x14, 0x16, 0x1d, 0x1b, 0x4c, 0x1f, + 0x0e, 0x2d, 0x23, 0x14, 0x2c, 0x13, 0x18, 0x25, 0x2c, 0x10, 0x3c, 0x71, + 0x1d, 0x5c, 0x5c, 0x3f, 0xae, 0x5c, 0x5c, 0x3f, 0x6a, 0x27, 0x31, 0x5b, + 0x09, 0x27, 0x36, 0x03, 0x0a, 0x26, 0x35, 0x2e, 0x09, 0x08, 0xc6, 0x13, + 0x81, 0x17, 0x20, 0x18, 0x21, 0x1e, 0x04, 0x04, 0x15, 0x5c, 0x22, 0x26, + 0x48, 0x56, 0x3b, 0x10, 0x21, 0x01, 0x0c, 0x06, 0x06, 0x0f, 0x31, 0x44, + 0x3c, 0x52, 0x4a, 0x1d, 0x11, 0x3f, 0xb4, 0x71, 0x01, 0x26, 0x06, 0x0d, + 0x15, 0x1a, 0x2a, 0x13, 0x53, 0xaa, 0x42, 0x1d, 0x0a, 0x33, 0x20, 0x21, + 0x2b, 0x01, 0x02, 0x3e, 0x21, 0x09, 0x02, 0x02, 0x0f, 0x2d, 0x4b, 0x0a, + 0x22, 0x15, 0x20, 0x1f, 0x72, 0x8b, 0x2d, 0x2f, 0x1d, 0x1f, 0x0e, 0x25, + 0x3f, 0x4d, 0x1b, 0x63, 0x2a, 0x2c, 0x14, 0x22, 0x18, 0x1c, 0x0f, 0x08, + 0x2a, 0x08, 0x08, 0x0d, 0x3b, 0x4c, 0x52, 0x74, 0x27, 0x71, 0x2e, 0x01, + 0x0c, 0x10, 0x15, 0x0d, 0x06, 0x0d, 0x05, 0x01, 0x06, 0x2c, 0x28, 0x14, + 0x1b, 0x05, 0x04, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x16, 0x27, 0x03, 0x0d, + 0x30, 0x4c, 0x4c, 0x4b, 0x1f, 0x0b, 0x22, 0x26, 0x0d, 0x15, 0x0d, 0x2d, + 0x68, 0x34, 0x14, 0x3c, 0x25, 0x12, 0x04, 0x10, 0x18, 0x0b, 0x09, 0x30, + 0x2b, 0x44, 0x66, 0x14, 0x47, 0x47, 0x59, 0x73, 0x25, 0x05, 0x03, 0x1f, + 0x01, 0x08, 0x3f, 0x48, 0x4b, 0x4b, 0x76, 0x2f, 0x49, 0x2d, 0x22, 0x24, + 0x0c, 0x15, 0x08, 0x0e, 0x33, 0x03, 0x44, 0x4c, 0x10, 0x46, 0x13, 0x1f, + 0x27, 0x1b, 0x1d, 0x13, 0x02, 0x24, 0x08, 0x02, 0x42, 0x0e, 0x4d, 0x3c, + 0x19, 0x1b, 0x40, 0x2b, 0x2b, 0x1e, 0x16, 0x11, 0x04, 0x1f, 0x11, 0x04, + 0x18, 0x11, 0x35, 0x01, 0xa3, 0x13, 0x24, 0x1f, 0x0b, 0x0c, 0x19, 0x19, + 0x18, 0x13, 0x0f, 0x0c, 0x1a, 0x18, 0x1f, 0x19, 0x1e, 0x07, 0x1a, 0xc3, + 0x54, 0x51, 0x54, 0x51, 0x04, 0x53, 0x51, 0x54, 0x50, 0x02, 0x48, 0x1a, + 0x31, 0x18, 0x55, 0x74, 0x04, 0x0e, 0x09, 0x0d, 0x06, 0x10, 0x16, 0x1b, + 0x24, 0x01, 0x04, 0x0b, 0x04, 0x10, 0x3f, 0x0a, 0x41, 0x02, 0x41, 0x20, + 0x06, 0x12, 0x16, 0x21, 0x17, 0x2a, 0x1e, 0x15, 0x40, 0x27, 0x11, 0x0e, + 0x1e, 0x11, 0x15, 0x1f, 0x43, 0x13, 0x1a, 0x10, 0x15, 0x1b, 0x04, 0x09, + 0x4d, 0x2a, 0x0f, 0x19, 0x0a, 0x0a, 0x03, 0x05, 0x15, 0x3c, 0x64, 0x21, + 0x4b, 0x2e, 0x21, 0x28, 0x13, 0x47, 0x44, 0x19, 0x3f, 0x11, 0x18, 0x0b, + 0x0a, 0x07, 0x18, 0x0d, 0x07, 0x24, 0x2c, 0x2b, 0x21, 0x32, 0x10, 0x48, + 0x2a, 0x2d, 0x1e, 0x1a, 0x01, 0x0c, 0x43, 0x59, 0x28, 0x4e, 0x1c, 0x0d, + 0x5d, 0x24, 0x14, 0x0a, 0x05, 0x1f, 0x24, 0x32, 0x46, 0x3e, 0x5f, 0x3e, + 0x44, 0x1a, 0x30, 0x15, 0x0d, 0x07, 0x18, 0x2b, 0x03, 0x0d, 0x1a, 0x28, + 0x28, 0x57, 0xb2, 0x29, 0x27, 0x40, 0x2c, 0x23, 0x16, 0x63, 0x58, 0x1a, + 0x0a, 0x18, 0x11, 0x23, 0x08, 0x1b, 0x29, 0x05, 0x04, 0x0b, 0x15, 0x0d, + 0x14, 0x0b, 0x2a, 0x29, 0x5a, 0x62, 0x01, 0x19, 0x1e, 0x05, 0x05, 0x26, + 0x42, 0x42, 0x2a, 0x2a, 0x3f, 0x0d, 0x0f, 0x09, 0x05, 0x07, 0x01, 0x0b, + 0x25, 0x3e, 0x0d, 0x17, 0x11, 0x01, 0x03, 0x0d, 0x13, 0x20, 0x19, 0x11, + 0x03, 0x02, 0x01, 0x04, 0x11, 0x04, 0x05, 0x1b, 0x3d, 0x10, 0x29, 0x20, + 0x04, 0x04, 0x0a, 0x07, 0x04, 0x1f, 0x15, 0x20, 0x3e, 0x0f, 0x2a, 0x1e, + 0x00, 0x00, 0x00, 0x1b, 0x01, 0x4a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x07, 0x00, 0x27, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0e, 0x00, 0x1a, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x00, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x01, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x02, 0x00, 0x0e, 0x00, 0x98, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x03, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x04, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x05, 0x00, 0x04, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x06, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x0d, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x0e, 0x00, 0x34, 0x00, 0xaa, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x00, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x01, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x02, 0x00, 0x0e, 0x00, 0x98, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x03, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x04, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x05, 0x00, 0x04, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x06, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x0d, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x0e, 0x00, 0x34, 0x00, 0xaa, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, + 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x31, 0x35, + 0x2c, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x53, 0x70, 0x69, + 0x64, 0x65, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x52, 0x65, 0x67, + 0x75, 0x6c, 0x61, 0x72, 0x56, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, + 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x2e, 0x73, 0x69, 0x6c, + 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x4f, 0x46, 0x4c, 0x00, 0x43, 0x00, 0x6f, + 0x00, 0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, + 0x00, 0x74, 0x00, 0x20, 0x00, 0x28, 0x00, 0x63, 0x00, 0x29, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x35, 0x00, 0x2c, 0x00, 0x20, + 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, + 0x00, 0x2e, 0x00, 0x53, 0x00, 0x70, 0x00, 0x69, 0x00, 0x64, 0x00, 0x65, + 0x00, 0x72, 0x00, 0x53, 0x00, 0x79, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x6f, + 0x00, 0x6c, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c, + 0x00, 0x61, 0x00, 0x72, 0x00, 0x56, 0x00, 0x31, 0x00, 0x68, 0x00, 0x74, + 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73, + 0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x73, + 0x00, 0x2e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x2e, 0x00, 0x6f, + 0x00, 0x72, 0x00, 0x67, 0x00, 0x2f, 0x00, 0x4f, 0x00, 0x46, 0x00, 0x4c, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x66, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xff, 0xff, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x14, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00 +}; + +enum class SmoothBehavior { + none, // SmoothFonts produces no effect. + some, // SmoothFonts produces some effect, but not subpixel coverage. + subpixel, // SmoothFonts produces some effect and provides subpixel coverage. +}; + +/** + * There does not appear to be a publicly accessable API for determining if lcd + * font smoothing will be applied if we request it. The main issue is that if + * smoothing is applied a gamma of 2.0 will be used, if not a gamma of 1.0. + */ +static SmoothBehavior smooth_behavior() { + static SmoothBehavior gSmoothBehavior = []{ + uint32_t noSmoothBitmap[16][16] = {}; + uint32_t smoothBitmap[16][16] = {}; + + SkUniqueCFRef colorspace(CGColorSpaceCreateDeviceRGB()); + SkUniqueCFRef noSmoothContext( + CGBitmapContextCreate(&noSmoothBitmap, 16, 16, 8, 16*4, + colorspace.get(), BITMAP_INFO_RGB)); + SkUniqueCFRef smoothContext( + CGBitmapContextCreate(&smoothBitmap, 16, 16, 8, 16*4, + colorspace.get(), BITMAP_INFO_RGB)); + + SkUniqueCFRef data( + CGDataProviderCreateWithData(nullptr, kSpiderSymbol_ttf, + SK_ARRAY_COUNT(kSpiderSymbol_ttf), nullptr)); + SkUniqueCFRef cgFont(CGFontCreateWithDataProvider(data.get())); + SkASSERT(cgFont); + SkUniqueCFRef ctFont( + CTFontCreateWithGraphicsFont(cgFont.get(), 16, nullptr, nullptr)); + SkASSERT(ctFont); + + CGContextSetShouldSmoothFonts(noSmoothContext.get(), false); + CGContextSetShouldAntialias(noSmoothContext.get(), true); + CGContextSetTextDrawingMode(noSmoothContext.get(), kCGTextFill); + CGContextSetGrayFillColor(noSmoothContext.get(), 1, 1); + + CGContextSetShouldSmoothFonts(smoothContext.get(), true); + CGContextSetShouldAntialias(smoothContext.get(), true); + CGContextSetTextDrawingMode(smoothContext.get(), kCGTextFill); + CGContextSetGrayFillColor(smoothContext.get(), 1, 1); + + CGPoint point = CGPointMake(0, 3); + CGGlyph spiderGlyph = 3; + CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, noSmoothContext.get()); + CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, smoothContext.get()); + + // For debugging. + //SkUniqueCFRef image(CGBitmapContextCreateImage(noSmoothContext())); + //SkUniqueCFRef image(CGBitmapContextCreateImage(smoothContext())); + + SmoothBehavior smoothBehavior = SmoothBehavior::none; + for (int x = 0; x < 16; ++x) { + for (int y = 0; y < 16; ++y) { + uint32_t smoothPixel = smoothBitmap[x][y]; + uint32_t r = (smoothPixel >> 16) & 0xFF; + uint32_t g = (smoothPixel >> 8) & 0xFF; + uint32_t b = (smoothPixel >> 0) & 0xFF; + if (r != g || r != b) { + return SmoothBehavior::subpixel; + } + if (noSmoothBitmap[x][y] != smoothPixel) { + smoothBehavior = SmoothBehavior::some; + } + } + } + return smoothBehavior; + }(); + return gSmoothBehavior; +} + +class Offscreen { +public: + Offscreen() + : fRGBSpace(nullptr) + , fCG(nullptr) + , fDoAA(false) + , fDoLCD(false) + { + fSize.set(0, 0); + } + + CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, + CGGlyph glyphID, size_t* rowBytesPtr, bool generateA8FromLCD, + bool lightOnDark); + +private: + enum { + kSize = 32 * 32 * sizeof(CGRGBPixel) + }; + SkAutoSMalloc fImageStorage; + SkUniqueCFRef fRGBSpace; + + // cached state + SkUniqueCFRef fCG; + SkISize fSize; + bool fDoAA; + bool fDoLCD; + + static int RoundSize(int dimension) { + return SkNextPow2(dimension); + } +}; + +/////////////////////////////////////////////////////////////////////////////// + +static bool find_dict_CGFloat(CFDictionaryRef dict, CFStringRef name, CGFloat* value) { + CFNumberRef num; + return CFDictionaryGetValueIfPresent(dict, name, (const void**)&num) + && CFNumberIsFloatType(num) + && CFNumberGetValue(num, kCFNumberCGFloatType, value); +} + +template struct LinearInterpolater { + struct Mapping { + S src_val; + D dst_val; + }; + constexpr LinearInterpolater(Mapping const mapping[], int mappingCount) + : fMapping(mapping), fMappingCount(mappingCount) {} + + static D map(S value, S src_min, S src_max, D dst_min, D dst_max) { + SkASSERT(src_min < src_max); + SkASSERT(dst_min <= dst_max); + return C()(dst_min + (((value - src_min) * (dst_max - dst_min)) / (src_max - src_min))); + } + + D map(S val) const { + // -Inf to [0] + if (val < fMapping[0].src_val) { + return fMapping[0].dst_val; + } + + // Linear from [i] to [i+1] + for (int i = 0; i < fMappingCount - 1; ++i) { + if (val < fMapping[i+1].src_val) { + return map(val, fMapping[i].src_val, fMapping[i+1].src_val, + fMapping[i].dst_val, fMapping[i+1].dst_val); + } + } + + // From [n] to +Inf + // if (fcweight < Inf) + return fMapping[fMappingCount - 1].dst_val; + } + + Mapping const * fMapping; + int fMappingCount; +}; + +struct RoundCGFloatToInt { + int operator()(CGFloat s) { return s + 0.5; } +}; +struct CGFloatIdentity { + CGFloat operator()(CGFloat s) { return s; } +}; + +/** Returns the [-1, 1] CTFontDescriptor weights for the + * <0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000> CSS weights. + * + * It is assumed that the values will be interpolated linearly between these points. + * NSFontWeightXXX were added in 10.11, appear in 10.10, but do not appear in 10.9. + * The actual values appear to be stable, but they may change in the future without notice. + */ +static CGFloat(&get_NSFontWeight_mapping())[11] { + + // Declarations in on macOS, on iOS +#ifdef SK_BUILD_FOR_MAC +# define SK_KIT_FONT_WEIGHT_PREFIX "NS" +#endif +#ifdef SK_BUILD_FOR_IOS +# define SK_KIT_FONT_WEIGHT_PREFIX "UI" +#endif + static constexpr struct { + CGFloat defaultValue; + const char* name; + } nsFontWeightLoaderInfos[] = { + { -0.80f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightUltraLight" }, + { -0.60f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightThin" }, + { -0.40f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightLight" }, + { 0.00f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightRegular" }, + { 0.23f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightMedium" }, + { 0.30f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightSemibold" }, + { 0.40f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightBold" }, + { 0.56f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightHeavy" }, + { 0.62f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightBlack" }, + }; + + static_assert(SK_ARRAY_COUNT(nsFontWeightLoaderInfos) == 9, ""); + static CGFloat nsFontWeights[11]; + static SkOnce once; + once([&] { + size_t i = 0; + nsFontWeights[i++] = -1.00; + for (const auto& nsFontWeightLoaderInfo : nsFontWeightLoaderInfos) { + void* nsFontWeightValuePtr = dlsym(RTLD_DEFAULT, nsFontWeightLoaderInfo.name); + if (nsFontWeightValuePtr) { + nsFontWeights[i++] = *(static_cast(nsFontWeightValuePtr)); + } else { + nsFontWeights[i++] = nsFontWeightLoaderInfo.defaultValue; + } + } + nsFontWeights[i++] = 1.00; + }); + return nsFontWeights; +} + +/** Convert the [0, 1000] CSS weight to [-1, 1] CTFontDescriptor weight (for system fonts). + * + * The -1 to 1 weights reported by CTFontDescriptors have different mappings depending on if the + * CTFont is native or created from a CGDataProvider. + */ +static CGFloat fontstyle_to_ct_weight(int fontstyleWeight) { + using Interpolator = LinearInterpolater; + + // Note that Mac supports the old OS2 version A so 0 through 10 are as if multiplied by 100. + // However, on this end we can't tell, so this is ignored. + + static Interpolator::Mapping nativeWeightMappings[11]; + static SkOnce once; + once([&] { + CGFloat(&nsFontWeights)[11] = get_NSFontWeight_mapping(); + for (int i = 0; i < 11; ++i) { + nativeWeightMappings[i].src_val = i * 100; + nativeWeightMappings[i].dst_val = nsFontWeights[i]; + } + }); + static constexpr Interpolator nativeInterpolator( + nativeWeightMappings, SK_ARRAY_COUNT(nativeWeightMappings)); + + return nativeInterpolator.map(fontstyleWeight); +} + + +/** Convert the [-1, 1] CTFontDescriptor weight to [0, 1000] CSS weight. + * + * The -1 to 1 weights reported by CTFontDescriptors have different mappings depending on if the + * CTFont is native or created from a CGDataProvider. + */ +static int ct_weight_to_fontstyle(CGFloat cgWeight, bool fromDataProvider) { + using Interpolator = LinearInterpolater; + + // Note that Mac supports the old OS2 version A so 0 through 10 are as if multiplied by 100. + // However, on this end we can't tell, so this is ignored. + + /** This mapping for CGDataProvider created fonts is determined by creating font data with every + * weight, creating a CTFont, and asking the CTFont for its weight. See the TypefaceStyle test + * in tests/TypefaceTest.cpp for the code used to determine these values. + */ + static constexpr Interpolator::Mapping dataProviderWeightMappings[] = { + { -1.00, 0 }, + { -0.70, 100 }, + { -0.50, 200 }, + { -0.23, 300 }, + { 0.00, 400 }, + { 0.20, 500 }, + { 0.30, 600 }, + { 0.40, 700 }, + { 0.60, 800 }, + { 0.80, 900 }, + { 1.00, 1000 }, + }; + static constexpr Interpolator dataProviderInterpolator( + dataProviderWeightMappings, SK_ARRAY_COUNT(dataProviderWeightMappings)); + + static Interpolator::Mapping nativeWeightMappings[11]; + static SkOnce once; + once([&] { + CGFloat(&nsFontWeights)[11] = get_NSFontWeight_mapping(); + for (int i = 0; i < 11; ++i) { + nativeWeightMappings[i].src_val = nsFontWeights[i]; + nativeWeightMappings[i].dst_val = i * 100; + } + }); + static constexpr Interpolator nativeInterpolator( + nativeWeightMappings, SK_ARRAY_COUNT(nativeWeightMappings)); + + return fromDataProvider ? dataProviderInterpolator.map(cgWeight) + : nativeInterpolator.map(cgWeight); +} + +/** Convert the [0, 10] CSS weight to [-1, 1] CTFontDescriptor width. */ +static int fontstyle_to_ct_width(int fontstyleWidth) { + using Interpolator = LinearInterpolater; + + // Values determined by creating font data with every width, creating a CTFont, + // and asking the CTFont for its width. See TypefaceStyle test for basics. + static constexpr Interpolator::Mapping widthMappings[] = { + { 0, -0.5 }, + { 10, 0.5 }, + }; + static constexpr Interpolator interpolator(widthMappings, SK_ARRAY_COUNT(widthMappings)); + return interpolator.map(fontstyleWidth); +} + +/** Convert the [-1, 1] CTFontDescriptor width to [0, 10] CSS weight. */ +static int ct_width_to_fontstyle(CGFloat cgWidth) { + using Interpolator = LinearInterpolater; + + // Values determined by creating font data with every width, creating a CTFont, + // and asking the CTFont for its width. See TypefaceStyle test for basics. + static constexpr Interpolator::Mapping widthMappings[] = { + { -0.5, 0 }, + { 0.5, 10 }, + }; + static constexpr Interpolator interpolator(widthMappings, SK_ARRAY_COUNT(widthMappings)); + return interpolator.map(cgWidth); +} + +static SkFontStyle fontstyle_from_descriptor(CTFontDescriptorRef desc, bool fromDataProvider) { + SkUniqueCFRef traits(CTFontDescriptorCopyAttribute(desc, kCTFontTraitsAttribute)); + if (!traits || CFDictionaryGetTypeID() != CFGetTypeID(traits.get())) { + return SkFontStyle(); + } + SkUniqueCFRef fontTraitsDict(static_cast(traits.release())); + + CGFloat weight, width, slant; + if (!find_dict_CGFloat(fontTraitsDict.get(), kCTFontWeightTrait, &weight)) { + weight = 0; + } + if (!find_dict_CGFloat(fontTraitsDict.get(), kCTFontWidthTrait, &width)) { + width = 0; + } + if (!find_dict_CGFloat(fontTraitsDict.get(), kCTFontSlantTrait, &slant)) { + slant = 0; + } + + return SkFontStyle(ct_weight_to_fontstyle(weight, fromDataProvider), + ct_width_to_fontstyle(width), + slant ? SkFontStyle::kItalic_Slant + : SkFontStyle::kUpright_Slant); +} + +class SkTypeface_Mac : public SkTypeface { +public: + SkTypeface_Mac(SkUniqueCFRef fontRef, SkUniqueCFRef resourceRef, + const SkFontStyle& fs, bool isFixedPitch, + std::unique_ptr providedData) + : SkTypeface(fs, isFixedPitch) + , fFontRef(std::move(fontRef)) + , fOriginatingCFTypeRef(std::move(resourceRef)) + , fHasColorGlyphs( + SkToBool(CTFontGetSymbolicTraits(fFontRef.get()) & kCTFontColorGlyphsTrait)) + , fStream(std::move(providedData)) + , fIsFromStream(fStream) + { + SkASSERT(fFontRef); + } + + SkUniqueCFRef fFontRef; + SkUniqueCFRef fOriginatingCFTypeRef; + const bool fHasColorGlyphs; + bool hasColorGlyphs() const override { return fHasColorGlyphs; } + +protected: + int onGetUPEM() const override; + std::unique_ptr onOpenStream(int* ttcIndex) const override; + std::unique_ptr onMakeFontData() const override; + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override; + void onGetFamilyName(SkString* familyName) const override; + SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override; + int onGetTableTags(SkFontTableTag tags[]) const override; + size_t onGetTableData(SkFontTableTag, size_t offset, size_t length, void* data) const override; + sk_sp onCopyTableData(SkFontTableTag) const override; + SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&, + const SkDescriptor*) const override; + void onFilterRec(SkScalerContextRec*) const override; + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override; + void getGlyphToUnicodeMap(SkUnichar*) const override; + std::unique_ptr onGetAdvancedMetrics() const override; + void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override; + int onCountGlyphs() const override; + void getPostScriptGlyphNames(SkString*) const override {} + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override + { + return -1; + } + sk_sp onMakeClone(const SkFontArguments&) const override { + return nullptr; + } + + void* onGetCTFontRef() const override { return (void*)fFontRef.get(); } + +private: + mutable std::unique_ptr fStream; + bool fIsFromStream; + mutable SkOnce fInitStream; + + typedef SkTypeface INHERITED; +}; + +static bool find_by_CTFontRef(SkTypeface* cached, void* context) { + CTFontRef self = (CTFontRef)context; + CTFontRef other = (CTFontRef)cached->internal_private_getCTFontRef(); + + return CFEqual(self, other); +} + +/** Creates a typeface, searching the cache if isLocalStream is false. */ +static sk_sp create_from_CTFontRef(SkUniqueCFRef font, + SkUniqueCFRef resource, + std::unique_ptr providedData) { + SkASSERT(font); + const bool isFromStream(providedData); + + if (!isFromStream) { + sk_sp face = SkTypefaceCache::FindByProcAndRef(find_by_CTFontRef, + (void*)font.get()); + if (face) { + return face; + } + } + + SkUniqueCFRef desc(CTFontCopyFontDescriptor(font.get())); + SkFontStyle style = fontstyle_from_descriptor(desc.get(), isFromStream); + CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font.get()); + bool isFixedPitch = SkToBool(traits & kCTFontMonoSpaceTrait); + + sk_sp face(new SkTypeface_Mac(std::move(font), std::move(resource), + style, isFixedPitch, std::move(providedData))); + if (!isFromStream) { + SkTypefaceCache::Add(face); + } + return face; +} + +/** Creates a typeface from a descriptor, searching the cache. */ +static sk_sp create_from_desc(CTFontDescriptorRef desc) { + SkUniqueCFRef ctFont(CTFontCreateWithFontDescriptor(desc, 0, nullptr)); + if (!ctFont) { + return nullptr; + } + + return create_from_CTFontRef(std::move(ctFont), nullptr, nullptr); +} + +static SkUniqueCFRef create_descriptor(const char familyName[], + const SkFontStyle& style) { + SkUniqueCFRef cfAttributes( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + SkUniqueCFRef cfTraits( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + if (!cfAttributes || !cfTraits) { + return nullptr; + } + + // CTFontTraits (symbolic) + // macOS 14 and iOS 12 seem to behave badly when kCTFontSymbolicTrait is set. + + // CTFontTraits (weight) + CGFloat ctWeight = fontstyle_to_ct_weight(style.weight()); + SkUniqueCFRef cfFontWeight( + CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &ctWeight)); + if (cfFontWeight) { + CFDictionaryAddValue(cfTraits.get(), kCTFontWeightTrait, cfFontWeight.get()); + } + // CTFontTraits (width) + CGFloat ctWidth = fontstyle_to_ct_width(style.width()); + SkUniqueCFRef cfFontWidth( + CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &ctWidth)); + if (cfFontWidth) { + CFDictionaryAddValue(cfTraits.get(), kCTFontWidthTrait, cfFontWidth.get()); + } + // CTFontTraits (slant) + CGFloat ctSlant = style.slant() == SkFontStyle::kUpright_Slant ? 0 : 1; + SkUniqueCFRef cfFontSlant( + CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &ctSlant)); + if (cfFontSlant) { + CFDictionaryAddValue(cfTraits.get(), kCTFontSlantTrait, cfFontSlant.get()); + } + // CTFontTraits + CFDictionaryAddValue(cfAttributes.get(), kCTFontTraitsAttribute, cfTraits.get()); + + // CTFontFamilyName + if (familyName) { + SkUniqueCFRef cfFontName = make_CFString(familyName); + if (cfFontName) { + CFDictionaryAddValue(cfAttributes.get(), kCTFontFamilyNameAttribute, cfFontName.get()); + } + } + + return SkUniqueCFRef( + CTFontDescriptorCreateWithAttributes(cfAttributes.get())); +} + +// Same as the above function except style is included so we can +// compare whether the created font conforms to the style. If not, we need +// to recreate the font with symbolic traits. This is needed due to MacOS 10.11 +// font creation problem https://bugs.chromium.org/p/skia/issues/detail?id=8447. +static sk_sp create_from_desc_and_style(CTFontDescriptorRef desc, + const SkFontStyle& style) { + SkUniqueCFRef ctFont(CTFontCreateWithFontDescriptor(desc, 0, nullptr)); + if (!ctFont) { + return nullptr; + } + + const CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctFont.get()); + CTFontSymbolicTraits expected_traits = traits; + if (style.slant() != SkFontStyle::kUpright_Slant) { + expected_traits |= kCTFontItalicTrait; + } + if (style.weight() >= SkFontStyle::kBold_Weight) { + expected_traits |= kCTFontBoldTrait; + } + + if (expected_traits != traits) { + SkUniqueCFRef ctNewFont(CTFontCreateCopyWithSymbolicTraits(ctFont.get(), 0, nullptr, expected_traits, expected_traits)); + if (ctNewFont) { + ctFont = std::move(ctNewFont); + } + } + + return create_from_CTFontRef(std::move(ctFont), nullptr, nullptr); +} + +/** Creates a typeface from a name, searching the cache. */ +static sk_sp create_from_name(const char familyName[], const SkFontStyle& style) { + SkUniqueCFRef desc = create_descriptor(familyName, style); + if (!desc) { + return nullptr; + } + return create_from_desc_and_style(desc.get(), style); +} + +/////////////////////////////////////////////////////////////////////////////// + +/* This function is visible on the outside. It first searches the cache, and if + * not found, returns a new entry (after adding it to the cache). + */ +SkTypeface* SkCreateTypefaceFromCTFont(CTFontRef font, CFTypeRef resource) { + CFRetain(font); + if (resource) { + CFRetain(resource); + } + return create_from_CTFontRef(SkUniqueCFRef(font), + SkUniqueCFRef(resource), + nullptr).release(); +} + +static const char* map_css_names(const char* name) { + static const struct { + const char* fFrom; // name the caller specified + const char* fTo; // "canonical" name we map to + } gPairs[] = { + { "sans-serif", "Helvetica" }, + { "serif", "Times" }, + { "monospace", "Courier" } + }; + + for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) { + if (strcmp(name, gPairs[i].fFrom) == 0) { + return gPairs[i].fTo; + } + } + return name; // no change +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkScalerContext_Mac : public SkScalerContext { +public: + SkScalerContext_Mac(sk_sp, const SkScalerContextEffects&, const SkDescriptor*); + +protected: + unsigned generateGlyphCount(void) override; + bool generateAdvance(SkGlyph* glyph) override; + void generateMetrics(SkGlyph* glyph) override; + void generateImage(const SkGlyph& glyph) override; + bool generatePath(SkGlyphID glyph, SkPath* path) override; + void generateFontMetrics(SkFontMetrics*) override; + +private: + static void CTPathElement(void *info, const CGPathElement *element); + template + static void RGBToA8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, + const SkGlyph& glyph, const uint8_t* table8); + template + static uint16_t RGBToLcd16(CGRGBPixel rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB); + template + static void RGBToLcd16(const CGRGBPixel* SK_RESTRICT cgPixels, + size_t cgRowBytes, + const SkGlyph& glyph, + const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB); + + Offscreen fOffscreen; + + /** Unrotated variant of fCTFont. + * + * In 10.10.1 CTFontGetAdvancesForGlyphs applies the font transform to the width of the + * advances, but always sets the height to 0. This font is used to get the advances of the + * unrotated glyph, and then the rotation is applied separately. + * + * CT vertical metrics are pre-rotated (in em space, before transform) 90deg clock-wise. + * This makes kCTFontOrientationDefault dangerous, because the metrics from + * kCTFontOrientationHorizontal are in a different space from kCTFontOrientationVertical. + * With kCTFontOrientationVertical the advances must be unrotated. + * + * Sometimes, creating a copy of a CTFont with the same size but different trasform will select + * different underlying font data. As a result, avoid ever creating more than one CTFont per + * SkScalerContext to ensure that only one CTFont is used. + * + * As a result of the above (and other constraints) this font contains the size, but not the + * transform. The transform must always be applied separately. + */ + SkUniqueCFRef fCTFont; + + /** The transform without the font size. */ + CGAffineTransform fTransform; + CGAffineTransform fInvTransform; + + SkUniqueCFRef fCGFont; + uint16_t fGlyphCount; + const bool fDoSubPosition; + + friend class Offscreen; + + typedef SkScalerContext INHERITED; +}; + +// CTFontCreateCopyWithAttributes or CTFontCreateCopyWithSymbolicTraits cannot be used on 10.10 +// and later, as they will return different underlying fonts depending on the size requested. +static SkUniqueCFRef ctfont_create_exact_copy(CTFontRef baseFont, CGFloat textSize, + const CGAffineTransform* transform) +{ + // To figure out if a font is installed locally or used from a @font-face + // resource, we check whether its descriptor can provide a URL. This will + // be present for installed fonts, but not for those activated from an + // in-memory resource. + auto IsInstalledFont = [](CTFontRef aFont) { + CTFontDescriptorRef desc = CTFontCopyFontDescriptor(aFont); + CFTypeRef attr = CTFontDescriptorCopyAttribute(desc, kCTFontURLAttribute); + CFRelease(desc); + bool result = false; + if (attr) { + result = true; + CFRelease(attr); + } + return result; + }; + + // If we have a system font we need to use the CGFont APIs to avoid having the + // underlying font change for us when using CTFontCreateCopyWithAttributes. + if (IsInstalledFont(baseFont)) { + SkUniqueCFRef baseCGFont(CTFontCopyGraphicsFont(baseFont, nullptr)); + + // The last parameter (CTFontDescriptorRef attributes) *must* be nullptr. + // If non-nullptr then with fonts with variation axes, the copy will fail in + // CGFontVariationFromDictCallback when it assumes kCGFontVariationAxisName is CFNumberRef + // which it quite obviously is not. + + // Because we cannot setup the CTFont descriptor to match, the same restriction applies here + // as other uses of CTFontCreateWithGraphicsFont which is that such CTFonts should not escape + // the scaler context, since they aren't 'normal'. + + // Avoid calling potentially buggy variation APIs on pre-Sierra macOS + // versions (see bug 1331683). + // + // And on HighSierra, CTFontCreateWithGraphicsFont properly carries over + // variation settings from the CGFont to CTFont, so we don't need to do + // the extra work here -- and this seems to avoid Core Text crashiness + // seen in bug 1454094. + // + // However, for installed fonts it seems we DO need to copy the variations + // explicitly even on 10.13, otherwise fonts fail to render (as in bug + // 1455494) when non-default values are used. Fortunately, the crash + // mentioned above occurs with data fonts, not (AFAICT) with system- + // installed fonts. + // + // So we only need to do this "the hard way" on Sierra, and for installed + // fonts on HighSierra+; otherwise, just let the standard CTFont function + // do its thing. + // + // NOTE in case this ever needs further adjustment: there is similar logic + // in four places in the tree (sadly): + // CreateCTFontFromCGFontWithVariations in gfxMacFont.cpp + // CreateCTFontFromCGFontWithVariations in ScaledFontMac.cpp + // CreateCTFontFromCGFontWithVariations in cairo-quartz-font.c + // ctfont_create_exact_copy in SkFontHost_mac.cpp + + // Not UniqueCFRef<> because CGFontCopyVariations can return null! + CFDictionaryRef variations = CGFontCopyVariations(baseCGFont.get()); + if (variations) { + SkUniqueCFRef + varAttr(CFDictionaryCreate(nullptr, + (const void**)&kCTFontVariationAttribute, + (const void**)&variations, + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + CFRelease(variations); + + SkUniqueCFRef + varDesc(CTFontDescriptorCreateWithAttributes(varAttr.get())); + + return SkUniqueCFRef( + CTFontCreateWithGraphicsFont(baseCGFont.get(), textSize, transform, varDesc.get())); + } + return SkUniqueCFRef( + CTFontCreateWithGraphicsFont(baseCGFont.get(), textSize, transform, nullptr)); + } else { + return SkUniqueCFRef(CTFontCreateCopyWithAttributes(baseFont, textSize, transform, nullptr)); + } +} + +SkScalerContext_Mac::SkScalerContext_Mac(sk_sp typeface, + const SkScalerContextEffects& effects, + const SkDescriptor* desc) + : INHERITED(std::move(typeface), effects, desc) + , fDoSubPosition(SkToBool(fRec.fFlags & kSubpixelPositioning_Flag)) + +{ + CTFontRef ctFont = (CTFontRef)this->getTypeface()->internal_private_getCTFontRef(); + CFIndex numGlyphs = CTFontGetGlyphCount(ctFont); + SkASSERT(numGlyphs >= 1 && numGlyphs <= 0xFFFF); + fGlyphCount = SkToU16(numGlyphs); + + // CT on (at least) 10.9 will size color glyphs down from the requested size, but not up. + // As a result, it is necessary to know the actual device size and request that. + SkVector scale; + SkMatrix skTransform; + bool invertible = fRec.computeMatrices(SkScalerContextRec::kVertical_PreMatrixScale, + &scale, &skTransform, nullptr, nullptr, nullptr); + fTransform = MatrixToCGAffineTransform(skTransform); + // CGAffineTransformInvert documents that if the transform is non-invertible it will return the + // passed transform unchanged. It does so, but then also prints a message to stdout. Avoid this. + if (invertible) { + fInvTransform = CGAffineTransformInvert(fTransform); + } else { + fInvTransform = fTransform; + } + + // The transform contains everything except the requested text size. + // Some properties, like 'trak', are based on the text size (before applying the matrix). + CGFloat textSize = ScalarToCG(scale.y()); + fCTFont = ctfont_create_exact_copy(ctFont, textSize, nullptr); + fCGFont.reset(CTFontCopyGraphicsFont(fCTFont.get(), nullptr)); +} + +CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, + CGGlyph glyphID, size_t* rowBytesPtr, + bool generateA8FromLCD, bool lightOnDark) { + if (!fRGBSpace) { + //It doesn't appear to matter what color space is specified. + //Regular blends and antialiased text are always (s*a + d*(1-a)) + //and subpixel antialiased text is always g=2.0. + fRGBSpace.reset(CGColorSpaceCreateDeviceRGB()); + } + + // default to kBW_Format + bool doAA = false; + bool doLCD = false; + + if (SkMask::kBW_Format != glyph.maskFormat()) { + doLCD = true; + doAA = true; + } + + // FIXME: lcd smoothed un-hinted rasterization unsupported. + if (!generateA8FromLCD && SkMask::kA8_Format == glyph.maskFormat()) { + doLCD = false; + doAA = true; + } + + // If this font might have color glyphs, disable LCD as there's no way to support it. + // CoreText doesn't tell us which format it ended up using, so we can't detect it. + // A8 will end up black on transparent, but TODO: we can detect gray and set to A8. + if (SkMask::kARGB32_Format == glyph.maskFormat()) { + doLCD = false; + } + + size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel); + if (!fCG || fSize.fWidth < glyph.width() || fSize.fHeight < glyph.height()) { + if (fSize.fWidth < glyph.width()) { + fSize.fWidth = RoundSize(glyph.width()); + } + if (fSize.fHeight < glyph.height()) { + fSize.fHeight = RoundSize(glyph.height()); + } + + rowBytes = fSize.fWidth * sizeof(CGRGBPixel); + void* image = fImageStorage.reset(rowBytes * fSize.fHeight); + const CGImageAlphaInfo alpha = (glyph.isColor()) + ? kCGImageAlphaPremultipliedFirst + : kCGImageAlphaNoneSkipFirst; + const CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | alpha; + fCG.reset(CGBitmapContextCreate(image, fSize.fWidth, fSize.fHeight, 8, + rowBytes, fRGBSpace.get(), bitmapInfo)); + + // Skia handles quantization and subpixel positioning, + // so disable quantization and enabe subpixel positioning in CG. + CGContextSetAllowsFontSubpixelQuantization(fCG.get(), false); + CGContextSetShouldSubpixelQuantizeFonts(fCG.get(), false); + + // Because CG always draws from the horizontal baseline, + // if there is a non-integral translation from the horizontal origin to the vertical origin, + // then CG cannot draw the glyph in the correct location without subpixel positioning. + CGContextSetAllowsFontSubpixelPositioning(fCG.get(), true); + CGContextSetShouldSubpixelPositionFonts(fCG.get(), true); + + CGContextSetTextDrawingMode(fCG.get(), kCGTextFill); + + // force our checks below to happen + fDoAA = !doAA; + fDoLCD = !doLCD; + + CGContextSetTextMatrix(fCG.get(), context.fTransform); + } + + if (glyph.isColor()) { + // Set the current color for layers with palette index 0xffff, or non-colored + // glyphs that may be present in a color font. + // + // Per comment in the Windows code at https://searchfox.org/mozilla-central/source/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp#1069-1074: + // > "getLuminanceColor() is kinda sorta what is wanted here, but not really, + // > it will often be the wrong value because it wan't designed for this. + // + // Empirically, in simple testcases it looks like a decent approximation of what + // we need. + SkColor4f color = SkColor4f::FromColor(context.getRec().getLuminanceColor()); + CGContextSetRGBFillColor(fCG.get(), color.fR, color.fG, color.fB, 1.0f); + } else { + // Draw black on white to create mask. (Special path exists to speed this up in CG.) + // If light-on-dark is requested, draw white on black. + CGContextSetGrayFillColor(fCG.get(), lightOnDark ? 1.0f : 0.0f, 1.0f); + } + + if (fDoAA != doAA) { + CGContextSetShouldAntialias(fCG.get(), doAA); + fDoAA = doAA; + } + if (fDoLCD != doLCD) { + CGContextSetShouldSmoothFonts(fCG.get(), doLCD); + fDoLCD = doLCD; + } + + CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get(); + // skip rows based on the glyph's height + image += (fSize.fHeight - glyph.height()) * fSize.fWidth; + + // Erase to white (or transparent black if it's a color glyph, to not composite against white). + // For light-on-dark, instead erase to black. + uint32_t bgColor = (!glyph.isColor()) ? (lightOnDark ? 0xFF000000 : 0xFFFFFFFF) : 0x00000000; + sk_memset_rect32(image, bgColor, glyph.width(), glyph.height(), rowBytes); + + float subX = 0; + float subY = 0; + if (context.fDoSubPosition) { + subX = SkFixedToFloat(glyph.getSubXFixed()); + subY = SkFixedToFloat(glyph.getSubYFixed()); + } + + CGPoint point = CGPointMake(-glyph.left() + subX, glyph.top() + glyph.height() - subY); + // Prior to 10.10, CTFontDrawGlyphs acted like CGContextShowGlyphsAtPositions and took + // 'positions' which are in text space. The glyph location (in device space) must be + // mapped into text space, so that CG can convert it back into device space. + // In 10.10.1, this is handled directly in CTFontDrawGlyphs. + // + // However, in 10.10.2 color glyphs no longer rotate based on the font transform. + // So always make the font transform identity and place the transform on the context. + point = CGPointApplyAffineTransform(point, context.fInvTransform); + + CTFontDrawGlyphs(context.fCTFont.get(), &glyphID, &point, 1, fCG.get()); + + SkASSERT(rowBytesPtr); + *rowBytesPtr = rowBytes; + return image; +} + +unsigned SkScalerContext_Mac::generateGlyphCount(void) { + return fGlyphCount; +} + +bool SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) { + return false; +} + +void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) { + glyph->fMaskFormat = fRec.fMaskFormat; + + const CGGlyph cgGlyph = (CGGlyph) glyph->getGlyphID(); + glyph->zeroMetrics(); + + // The following block produces cgAdvance in CG units (pixels, y up). + CGSize cgAdvance; + CTFontGetAdvancesForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal, + &cgGlyph, &cgAdvance, 1); + cgAdvance = CGSizeApplyAffineTransform(cgAdvance, fTransform); + glyph->fAdvanceX = CGToFloat(cgAdvance.width); + glyph->fAdvanceY = -CGToFloat(cgAdvance.height); + + // The following produces skBounds in SkGlyph units (pixels, y down), + // or returns early if skBounds would be empty. + SkRect skBounds; + + // Glyphs are always drawn from the horizontal origin. The caller must manually use the result + // of CTFontGetVerticalTranslationsForGlyphs to calculate where to draw the glyph for vertical + // glyphs. As a result, always get the horizontal bounds of a glyph and translate it if the + // glyph is vertical. This avoids any diagreement between the various means of retrieving + // vertical metrics. + { + // CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up). + CGRect cgBounds; + CTFontGetBoundingRectsForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal, + &cgGlyph, &cgBounds, 1); + cgBounds = CGRectApplyAffineTransform(cgBounds, fTransform); + + // BUG? + // 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when + // it should be empty. So, if we see a zero-advance, we check if it has an + // empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance + // is rare, so we won't incur a big performance cost for this extra check. + // Avoid trying to create a path from a color font due to crashing on 10.9. + if (0 == cgAdvance.width && 0 == cgAdvance.height && + SkMask::kARGB32_Format != glyph->fMaskFormat) { + SkUniqueCFRef path(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph,nullptr)); + if (!path || CGPathIsEmpty(path.get())) { + return; + } + } + + if (CGRectIsEmpty_inline(cgBounds)) { + return; + } + + // Convert cgBounds to SkGlyph units (pixels, y down). + skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height, + cgBounds.size.width, cgBounds.size.height); + } + + // Currently the bounds are based on being rendered at (0,0). + // The top left must not move, since that is the base from which subpixel positioning is offset. + if (fDoSubPosition) { + skBounds.fRight += SkFixedToFloat(glyph->getSubXFixed()); + skBounds.fBottom += SkFixedToFloat(glyph->getSubYFixed()); + } + + // We're trying to pack left and top into int16_t, + // and width and height into uint16_t, after outsetting by 1. + if (!SkRect::MakeXYWH(-32767, -32767, 65535, 65535).contains(skBounds)) { + return; + } + + SkIRect skIBounds; + skBounds.roundOut(&skIBounds); + // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. + // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset + // is not currently known, as CG dilates the outlines by some percentage. + // Note that if this context is A8 and not back-forming from LCD, there is no need to outset. + skIBounds.outset(1, 1); + glyph->fLeft = SkToS16(skIBounds.fLeft); + glyph->fTop = SkToS16(skIBounds.fTop); + glyph->fWidth = SkToU16(skIBounds.width()); + glyph->fHeight = SkToU16(skIBounds.height()); +} + +#include "include/private/SkColorData.h" + +static constexpr uint8_t sk_pow2_table(size_t i) { + return SkToU8(((i * i + 128) / 255)); +} + +/** + * This will invert the gamma applied by CoreGraphics, so we can get linear + * values. + * + * CoreGraphics obscurely defaults to 2.0 as the subpixel coverage gamma value. + * The color space used does not appear to affect this choice. + */ +static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table); + +static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) { + while (count > 0) { + uint8_t mask = 0; + for (int i = 7; i >= 0; --i) { + mask |= ((CGRGBPixel_getAlpha(*src++) >> 7) ^ 0x1) << i; + if (0 == --count) { + break; + } + } + *dst++ = mask; + } +} + +template +static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) { + U8CPU r = 0xFF - ((rgb >> 16) & 0xFF); + U8CPU g = 0xFF - ((rgb >> 8) & 0xFF); + U8CPU b = 0xFF - ((rgb >> 0) & 0xFF); + U8CPU lum = sk_apply_lut_if(SkComputeLuminance(r, g, b), table8); +#if SK_SHOW_TEXT_BLIT_COVERAGE + lum = SkTMax(lum, (U8CPU)0x30); +#endif + return lum; +} + +template +void SkScalerContext_Mac::RGBToA8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, + const SkGlyph& glyph, const uint8_t* table8) { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; + + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; ++i) { + dst[i] = rgb_to_a8(cgPixels[i], table8); + } + cgPixels = SkTAddOffset(cgPixels, cgRowBytes); + dst = SkTAddOffset(dst, dstRB); + } +} + +template +uint16_t SkScalerContext_Mac::RGBToLcd16(CGRGBPixel rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + U8CPU r = sk_apply_lut_if(0xFF - ((rgb >> 16) & 0xFF), tableR); + U8CPU g = sk_apply_lut_if(0xFF - ((rgb >> 8) & 0xFF), tableG); + U8CPU b = sk_apply_lut_if(0xFF - ((rgb >> 0) & 0xFF), tableB); +#if SK_SHOW_TEXT_BLIT_COVERAGE + r = SkTMax(r, (U8CPU)0x30); + g = SkTMax(g, (U8CPU)0x30); + b = SkTMax(b, (U8CPU)0x30); +#endif + return SkPack888ToRGB16(r, g, b); +} + +template +void SkScalerContext_Mac::RGBToLcd16(const CGRGBPixel* SK_RESTRICT cgPixels, + size_t cgRowBytes, + const SkGlyph& glyph, + const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint16_t* SK_RESTRICT dst = (uint16_t*)glyph.fImage; + + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; i++) { + dst[i] = RGBToLcd16(cgPixels[i], tableR, tableG, tableB); + } + cgPixels = SkTAddOffset(cgPixels, cgRowBytes); + dst = SkTAddOffset(dst, dstRB); + } +} + +static SkPMColor cgpixels_to_pmcolor(CGRGBPixel rgb) { + U8CPU a = (rgb >> 24) & 0xFF; + U8CPU r = (rgb >> 16) & 0xFF; + U8CPU g = (rgb >> 8) & 0xFF; + U8CPU b = (rgb >> 0) & 0xFF; +#if SK_SHOW_TEXT_BLIT_COVERAGE + a = SkTMax(a, (U8CPU)0x30); +#endif + return SkPackARGB32(a, r, g, b); +} + +void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) { + CGGlyph cgGlyph = SkTo(glyph.getGlyphID()); + + // FIXME: lcd smoothed un-hinted rasterization unsupported. + bool requestSmooth = fRec.getHinting() != SkFontHinting::kNone; + bool lightOnDark = (fRec.fFlags & SkScalerContext::kLightOnDark_Flag) != 0; + + // Draw the glyph + size_t cgRowBytes; + CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth, lightOnDark); + if (cgPixels == nullptr) { + return; + } + + // Fix the glyph + if ((glyph.fMaskFormat == SkMask::kLCD16_Format) || + (glyph.fMaskFormat == SkMask::kA8_Format + && requestSmooth + && smooth_behavior() != SmoothBehavior::none)) + { + const uint8_t* linear = gLinearCoverageFromCGLCDValue.data(); + + //Note that the following cannot really be integrated into the + //pre-blend, since we may not be applying the pre-blend; when we aren't + //applying the pre-blend it means that a filter wants linear anyway. + //Other code may also be applying the pre-blend, so we'd need another + //one with this and one without. + CGRGBPixel* addr = cgPixels; + for (int y = 0; y < glyph.fHeight; ++y) { + for (int x = 0; x < glyph.fWidth; ++x) { + int r = linear[(addr[x] >> 16) & 0xFF]; + int g = linear[(addr[x] >> 8) & 0xFF]; + int b = linear[(addr[x] >> 0) & 0xFF]; + // If light-on-dark was requested, the mask is drawn inverted. + if (lightOnDark) { + r = 255 - r; + g = 255 - g; + b = 255 - b; + } + addr[x] = (r << 16) | (g << 8) | b; + } + addr = SkTAddOffset(addr, cgRowBytes); + } + } + + // Convert glyph to mask + switch (glyph.fMaskFormat) { + case SkMask::kLCD16_Format: { + if (fPreBlend.isApplicable()) { + RGBToLcd16(cgPixels, cgRowBytes, glyph, + fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); + } else { + RGBToLcd16(cgPixels, cgRowBytes, glyph, + fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); + } + } break; + case SkMask::kA8_Format: { + if (fPreBlend.isApplicable()) { + RGBToA8(cgPixels, cgRowBytes, glyph, fPreBlend.fG); + } else { + RGBToA8(cgPixels, cgRowBytes, glyph, fPreBlend.fG); + } + } break; + case SkMask::kBW_Format: { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint8_t* dst = (uint8_t*)glyph.fImage; + for (int y = 0; y < glyph.fHeight; y++) { + cgpixels_to_bits(dst, cgPixels, width); + cgPixels = SkTAddOffset(cgPixels, cgRowBytes); + dst = SkTAddOffset(dst, dstRB); + } + } break; + case SkMask::kARGB32_Format: { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + SkPMColor* dst = (SkPMColor*)glyph.fImage; + for (int y = 0; y < glyph.fHeight; y++) { + for (int x = 0; x < width; ++x) { + dst[x] = cgpixels_to_pmcolor(cgPixels[x]); + } + cgPixels = SkTAddOffset(cgPixels, cgRowBytes); + dst = SkTAddOffset(dst, dstRB); + } + } break; + default: + SkDEBUGFAIL("unexpected mask format"); + break; + } +} + +/* + * Our subpixel resolution is only 2 bits in each direction, so a scale of 4 + * seems sufficient, and possibly even correct, to allow the hinted outline + * to be subpixel positioned. + */ +#define kScaleForSubPixelPositionHinting (4.0f) + +bool SkScalerContext_Mac::generatePath(SkGlyphID glyph, SkPath* path) { + SkScalar scaleX = SK_Scalar1; + SkScalar scaleY = SK_Scalar1; + + CGAffineTransform xform = fTransform; + /* + * For subpixel positioning, we want to return an unhinted outline, so it + * can be positioned nicely at fractional offsets. However, we special-case + * if the baseline of the (horizontal) text is axis-aligned. In those cases + * we want to retain hinting in the direction orthogonal to the baseline. + * e.g. for horizontal baseline, we want to retain hinting in Y. + * The way we remove hinting is to scale the font by some value (4) in that + * direction, ask for the path, and then scale the path back down. + */ + if (fDoSubPosition) { + // start out by assuming that we want no hining in X and Y + scaleX = scaleY = kScaleForSubPixelPositionHinting; + // now see if we need to restore hinting for axis-aligned baselines + switch (this->computeAxisAlignmentForHText()) { + case kX_SkAxisAlignment: + scaleY = SK_Scalar1; // want hinting in the Y direction + break; + case kY_SkAxisAlignment: + scaleX = SK_Scalar1; // want hinting in the X direction + break; + default: + break; + } + + CGAffineTransform scale(CGAffineTransformMakeScale(ScalarToCG(scaleX), ScalarToCG(scaleY))); + xform = CGAffineTransformConcat(fTransform, scale); + } + + CGGlyph cgGlyph = SkTo(glyph); + SkUniqueCFRef cgPath(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph, &xform)); + + path->reset(); + if (!cgPath) { + return false; + } + + CGPathApply(cgPath.get(), path, SkScalerContext_Mac::CTPathElement); + if (fDoSubPosition) { + SkMatrix m; + m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY)); + path->transform(m); + } + return true; +} + +void SkScalerContext_Mac::generateFontMetrics(SkFontMetrics* metrics) { + if (nullptr == metrics) { + return; + } + + CGRect theBounds = CTFontGetBoundingBox(fCTFont.get()); + + metrics->fTop = CGToScalar(-CGRectGetMaxY_inline(theBounds)); + metrics->fAscent = CGToScalar(-CTFontGetAscent(fCTFont.get())); + metrics->fDescent = CGToScalar( CTFontGetDescent(fCTFont.get())); + metrics->fBottom = CGToScalar(-CGRectGetMinY_inline(theBounds)); + metrics->fLeading = CGToScalar( CTFontGetLeading(fCTFont.get())); + metrics->fAvgCharWidth = CGToScalar( CGRectGetWidth_inline(theBounds)); + metrics->fXMin = CGToScalar( CGRectGetMinX_inline(theBounds)); + metrics->fXMax = CGToScalar( CGRectGetMaxX_inline(theBounds)); + metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin; + metrics->fXHeight = CGToScalar( CTFontGetXHeight(fCTFont.get())); + metrics->fCapHeight = CGToScalar( CTFontGetCapHeight(fCTFont.get())); + metrics->fUnderlineThickness = CGToScalar( CTFontGetUnderlineThickness(fCTFont.get())); + metrics->fUnderlinePosition = -CGToScalar( CTFontGetUnderlinePosition(fCTFont.get())); + + metrics->fFlags = 0; + metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag; + + // See https://bugs.chromium.org/p/skia/issues/detail?id=6203 + // At least on 10.12.3 with memory based fonts the x-height is always 0.6666 of the ascent and + // the cap-height is always 0.8888 of the ascent. It appears that the values from the 'OS/2' + // table are read, but then overwritten if the font is not a system font. As a result, if there + // is a valid 'OS/2' table available use the values from the table if they aren't too strange. + struct OS2HeightMetrics { + SK_OT_SHORT sxHeight; + SK_OT_SHORT sCapHeight; + } heights; + size_t bytesRead = this->getTypeface()->getTableData( + SkTEndian_SwapBE32(SkOTTableOS2::TAG), offsetof(SkOTTableOS2, version.v2.sxHeight), + sizeof(heights), &heights); + if (bytesRead == sizeof(heights)) { + // 'fontSize' is correct because the entire resolved size is set by the constructor. + CGFloat fontSize = CTFontGetSize(this->fCTFont.get()); + unsigned upem = CTFontGetUnitsPerEm(this->fCTFont.get()); + unsigned maxSaneHeight = upem * 2; + uint16_t xHeight = SkEndian_SwapBE16(heights.sxHeight); + if (xHeight && xHeight < maxSaneHeight) { + metrics->fXHeight = CGToScalar(xHeight * fontSize / upem); + } + uint16_t capHeight = SkEndian_SwapBE16(heights.sCapHeight); + if (capHeight && capHeight < maxSaneHeight) { + metrics->fCapHeight = CGToScalar(capHeight * fontSize / upem); + } + } +} + +void SkScalerContext_Mac::CTPathElement(void *info, const CGPathElement *element) { + SkPath* skPath = (SkPath*)info; + + // Process the path element + switch (element->type) { + case kCGPathElementMoveToPoint: + skPath->moveTo(element->points[0].x, -element->points[0].y); + break; + + case kCGPathElementAddLineToPoint: + skPath->lineTo(element->points[0].x, -element->points[0].y); + break; + + case kCGPathElementAddQuadCurveToPoint: + skPath->quadTo(element->points[0].x, -element->points[0].y, + element->points[1].x, -element->points[1].y); + break; + + case kCGPathElementAddCurveToPoint: + skPath->cubicTo(element->points[0].x, -element->points[0].y, + element->points[1].x, -element->points[1].y, + element->points[2].x, -element->points[2].y); + break; + + case kCGPathElementCloseSubpath: + skPath->close(); + break; + + default: + SkDEBUGFAIL("Unknown path element!"); + break; + } +} + + +/////////////////////////////////////////////////////////////////////////////// + +// Returns nullptr on failure +// Call must still manage its ownership of provider +static sk_sp create_from_dataProvider(SkUniqueCFRef provider, + std::unique_ptr providedData, + int ttcIndex) { + if (ttcIndex != 0) { + return nullptr; + } + SkUniqueCFRef cg(CGFontCreateWithDataProvider(provider.get())); + if (!cg) { + return nullptr; + } + SkUniqueCFRef ct(CTFontCreateWithGraphicsFont(cg.get(), 0, nullptr, nullptr)); + if (!ct) { + return nullptr; + } + return create_from_CTFontRef(std::move(ct), nullptr, std::move(providedData)); +} + +// Web fonts added to the CTFont registry do not return their character set. +// Iterate through the font in this case. The existing caller caches the result, +// so the performance impact isn't too bad. +static void populate_glyph_to_unicode_slow(CTFontRef ctFont, CFIndex glyphCount, + SkUnichar* out) { + sk_bzero(out, glyphCount * sizeof(SkUnichar)); + UniChar unichar = 0; + while (glyphCount > 0) { + CGGlyph glyph; + if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) { + if (out[glyph] == 0) { + out[glyph] = unichar; + --glyphCount; + } + } + if (++unichar == 0) { + break; + } + } +} + +static constexpr uint16_t kPlaneSize = 1 << 13; + +static void get_plane_glyph_map(const uint8_t* bits, + CTFontRef ctFont, + CFIndex glyphCount, + SkUnichar* glyphToUnicode, + uint8_t planeIndex) { + SkUnichar planeOrigin = (SkUnichar)planeIndex << 16; // top half of codepoint. + for (uint16_t i = 0; i < kPlaneSize; i++) { + uint8_t mask = bits[i]; + if (!mask) { + continue; + } + for (uint8_t j = 0; j < 8; j++) { + if (0 == (mask & ((uint8_t)1 << j))) { + continue; + } + uint16_t planeOffset = (i << 3) | j; + SkUnichar codepoint = planeOrigin | (SkUnichar)planeOffset; + uint16_t utf16[2] = {planeOffset, 0}; + size_t count = 1; + if (planeOrigin != 0) { + count = SkUTF::ToUTF16(codepoint, utf16); + } + CGGlyph glyphs[2] = {0, 0}; + if (CTFontGetGlyphsForCharacters(ctFont, utf16, glyphs, count)) { + SkASSERT(glyphs[1] == 0); + SkASSERT(glyphs[0] < glyphCount); + // CTFontCopyCharacterSet and CTFontGetGlyphsForCharacters seem to add 'support' + // for characters 0x9, 0xA, and 0xD mapping them to the glyph for character 0x20? + // Prefer mappings to codepoints at or above 0x20. + if (glyphToUnicode[glyphs[0]] < 0x20) { + glyphToUnicode[glyphs[0]] = codepoint; + } + } + } + } +} +// Construct Glyph to Unicode table. +static void populate_glyph_to_unicode(CTFontRef ctFont, CFIndex glyphCount, + SkUnichar* glyphToUnicode) { + sk_bzero(glyphToUnicode, sizeof(SkUnichar) * glyphCount); + SkUniqueCFRef charSet(CTFontCopyCharacterSet(ctFont)); + if (!charSet) { + populate_glyph_to_unicode_slow(ctFont, glyphCount, glyphToUnicode); + return; + } + + SkUniqueCFRef bitmap( + CFCharacterSetCreateBitmapRepresentation(nullptr, charSet.get())); + if (!bitmap) { + return; + } + CFIndex dataLength = CFDataGetLength(bitmap.get()); + if (!dataLength) { + return; + } + SkASSERT(dataLength >= kPlaneSize); + const UInt8* bits = CFDataGetBytePtr(bitmap.get()); + + get_plane_glyph_map(bits, ctFont, glyphCount, glyphToUnicode, 0); + /* + A CFData object that specifies the bitmap representation of the Unicode + character points the for the new character set. The bitmap representation could + contain all the Unicode character range starting from BMP to Plane 16. The + first 8KiB (8192 bytes) of the data represent the BMP range. The BMP range 8KiB + can be followed by zero to sixteen 8KiB bitmaps, each prepended with the plane + index byte. For example, the bitmap representing the BMP and Plane 2 has the + size of 16385 bytes (8KiB for BMP, 1 byte index, and a 8KiB bitmap for Plane + 2). The plane index byte, in this case, contains the integer value two. + */ + + if (dataLength <= kPlaneSize) { + return; + } + int extraPlaneCount = (dataLength - kPlaneSize) / (1 + kPlaneSize); + SkASSERT(dataLength == kPlaneSize + extraPlaneCount * (1 + kPlaneSize)); + while (extraPlaneCount-- > 0) { + bits += kPlaneSize; + uint8_t planeIndex = *bits++; + SkASSERT(planeIndex >= 1); + SkASSERT(planeIndex <= 16); + get_plane_glyph_map(bits, ctFont, glyphCount, glyphToUnicode, planeIndex); + } +} + +/** Assumes src and dst are not nullptr. */ +static void CFStringToSkString(CFStringRef src, SkString* dst) { + // Reserve enough room for the worst-case string, + // plus 1 byte for the trailing null. + CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(src), + kCFStringEncodingUTF8) + 1; + dst->resize(length); + CFStringGetCString(src, dst->writable_str(), length, kCFStringEncodingUTF8); + // Resize to the actual UTF-8 length used, stripping the null character. + dst->resize(strlen(dst->c_str())); +} + +void SkTypeface_Mac::getGlyphToUnicodeMap(SkUnichar* dstArray) const { + SkUniqueCFRef ctFont = + ctfont_create_exact_copy(fFontRef.get(), CTFontGetUnitsPerEm(fFontRef.get()), nullptr); + CFIndex glyphCount = CTFontGetGlyphCount(ctFont.get()); + populate_glyph_to_unicode(ctFont.get(), glyphCount, dstArray); +} + +std::unique_ptr SkTypeface_Mac::onGetAdvancedMetrics() const { + + SkUniqueCFRef ctFont = + ctfont_create_exact_copy(fFontRef.get(), CTFontGetUnitsPerEm(fFontRef.get()), nullptr); + + std::unique_ptr info(new SkAdvancedTypefaceMetrics); + + { + SkUniqueCFRef fontName(CTFontCopyPostScriptName(ctFont.get())); + if (fontName.get()) { + CFStringToSkString(fontName.get(), &info->fPostScriptName); + info->fFontName = info->fPostScriptName; + } + } + + // In 10.10 and earlier, CTFontCopyVariationAxes and CTFontCopyVariation do not work when + // applied to fonts which started life with CGFontCreateWithDataProvider (they simply always + // return nullptr). As a result, we are limited to CGFontCopyVariationAxes and + // CGFontCopyVariations here until support for 10.10 and earlier is removed. + SkUniqueCFRef cgFont(CTFontCopyGraphicsFont(ctFont.get(), nullptr)); + if (cgFont) { + SkUniqueCFRef cgAxes(CGFontCopyVariationAxes(cgFont.get())); + if (cgAxes && CFArrayGetCount(cgAxes.get()) > 0) { + info->fFlags |= SkAdvancedTypefaceMetrics::kMultiMaster_FontFlag; + } + } + + SkOTTableOS2_V4::Type fsType; + if (sizeof(fsType) == this->getTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG), + offsetof(SkOTTableOS2_V4, fsType), + sizeof(fsType), + &fsType)) { + SkOTUtils::SetAdvancedTypefaceFlags(fsType, info.get()); + } + + // If it's not a truetype font, mark it as 'other'. Assume that TrueType + // fonts always have both glyf and loca tables. At the least, this is what + // sfntly needs to subset the font. CTFontCopyAttribute() does not always + // succeed in determining this directly. + if (!this->getTableSize('glyf') || !this->getTableSize('loca')) { + return info; + } + + info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font; + CTFontSymbolicTraits symbolicTraits = CTFontGetSymbolicTraits(ctFont.get()); + if (symbolicTraits & kCTFontMonoSpaceTrait) { + info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style; + } + if (symbolicTraits & kCTFontItalicTrait) { + info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style; + } + CTFontStylisticClass stylisticClass = symbolicTraits & kCTFontClassMaskTrait; + if (stylisticClass >= kCTFontOldStyleSerifsClass && stylisticClass <= kCTFontSlabSerifsClass) { + info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style; + } else if (stylisticClass & kCTFontScriptsClass) { + info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style; + } + info->fItalicAngle = (int16_t) CTFontGetSlantAngle(ctFont.get()); + info->fAscent = (int16_t) CTFontGetAscent(ctFont.get()); + info->fDescent = (int16_t) CTFontGetDescent(ctFont.get()); + info->fCapHeight = (int16_t) CTFontGetCapHeight(ctFont.get()); + CGRect bbox = CTFontGetBoundingBox(ctFont.get()); + + SkRect r; + r.setLTRB(CGToScalar(CGRectGetMinX_inline(bbox)), // Left + CGToScalar(CGRectGetMaxY_inline(bbox)), // Top + CGToScalar(CGRectGetMaxX_inline(bbox)), // Right + CGToScalar(CGRectGetMinY_inline(bbox))); // Bottom + + r.roundOut(&(info->fBBox)); + + // Figure out a good guess for StemV - Min width of i, I, !, 1. + // This probably isn't very good with an italic font. + int16_t min_width = SHRT_MAX; + info->fStemV = 0; + static const UniChar stem_chars[] = {'i', 'I', '!', '1'}; + const size_t count = sizeof(stem_chars) / sizeof(stem_chars[0]); + CGGlyph glyphs[count]; + CGRect boundingRects[count]; + if (CTFontGetGlyphsForCharacters(ctFont.get(), stem_chars, glyphs, count)) { + CTFontGetBoundingRectsForGlyphs(ctFont.get(), kCTFontOrientationHorizontal, + glyphs, boundingRects, count); + for (size_t i = 0; i < count; i++) { + int16_t width = (int16_t) boundingRects[i].size.width; + if (width > 0 && width < min_width) { + min_width = width; + info->fStemV = min_width; + } + } + } + return info; +} + +/////////////////////////////////////////////////////////////////////////////// + +static SK_SFNT_ULONG get_font_type_tag(CTFontRef ctFont) { + SkUniqueCFRef fontFormatRef( + static_cast(CTFontCopyAttribute(ctFont, kCTFontFormatAttribute))); + if (!fontFormatRef) { + return 0; + } + + SInt32 fontFormatValue; + if (!CFNumberGetValue(fontFormatRef.get(), kCFNumberSInt32Type, &fontFormatValue)) { + return 0; + } + + switch (fontFormatValue) { + case kCTFontFormatOpenTypePostScript: + return SkSFNTHeader::fontType_OpenTypeCFF::TAG; + case kCTFontFormatOpenTypeTrueType: + return SkSFNTHeader::fontType_WindowsTrueType::TAG; + case kCTFontFormatTrueType: + return SkSFNTHeader::fontType_MacTrueType::TAG; + case kCTFontFormatPostScript: + return SkSFNTHeader::fontType_PostScript::TAG; + case kCTFontFormatBitmap: + return SkSFNTHeader::fontType_MacTrueType::TAG; + case kCTFontFormatUnrecognized: + default: + return 0; + } +} + +std::unique_ptr SkTypeface_Mac::onOpenStream(int* ttcIndex) const { + *ttcIndex = 0; + + fInitStream([this]{ + if (fStream) { + return; + } + + SK_SFNT_ULONG fontType = get_font_type_tag(fFontRef.get()); + + // get table tags + int numTables = this->countTables(); + SkTDArray tableTags; + tableTags.setCount(numTables); + this->getTableTags(tableTags.begin()); + + // CT seems to be unreliable in being able to obtain the type, + // even if all we want is the first four bytes of the font resource. + // Just the presence of the FontForge 'FFTM' table seems to throw it off. + if (fontType == 0) { + fontType = SkSFNTHeader::fontType_WindowsTrueType::TAG; + + // see https://skbug.com/7630#c7 + bool couldBeCFF = false; + constexpr SkFontTableTag CFFTag = SkSetFourByteTag('C', 'F', 'F', ' '); + constexpr SkFontTableTag CFF2Tag = SkSetFourByteTag('C', 'F', 'F', '2'); + for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { + if (CFFTag == tableTags[tableIndex] || CFF2Tag == tableTags[tableIndex]) { + couldBeCFF = true; + } + } + if (couldBeCFF) { + fontType = SkSFNTHeader::fontType_OpenTypeCFF::TAG; + } + } + + // Sometimes CoreGraphics incorrectly thinks a font is kCTFontFormatPostScript. + // It is exceedingly unlikely that this is the case, so double check + // (see https://crbug.com/809763 ). + if (fontType == SkSFNTHeader::fontType_PostScript::TAG) { + // see if there are any required 'typ1' tables (see Adobe Technical Note #5180) + bool couldBeTyp1 = false; + constexpr SkFontTableTag TYPE1Tag = SkSetFourByteTag('T', 'Y', 'P', '1'); + constexpr SkFontTableTag CIDTag = SkSetFourByteTag('C', 'I', 'D', ' '); + for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { + if (TYPE1Tag == tableTags[tableIndex] || CIDTag == tableTags[tableIndex]) { + couldBeTyp1 = true; + } + } + if (!couldBeTyp1) { + fontType = SkSFNTHeader::fontType_OpenTypeCFF::TAG; + } + } + + // get the table sizes and accumulate the total size of the font + SkTDArray tableSizes; + size_t totalSize = sizeof(SkSFNTHeader) + sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables; + for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { + size_t tableSize = this->getTableSize(tableTags[tableIndex]); + totalSize += (tableSize + 3) & ~3; + *tableSizes.append() = tableSize; + } + + // reserve memory for stream, and zero it (tables must be zero padded) + fStream.reset(new SkMemoryStream(totalSize)); + char* dataStart = (char*)fStream->getMemoryBase(); + sk_bzero(dataStart, totalSize); + char* dataPtr = dataStart; + + // compute font header entries + uint16_t entrySelector = 0; + uint16_t searchRange = 1; + while (searchRange < numTables >> 1) { + entrySelector++; + searchRange <<= 1; + } + searchRange <<= 4; + uint16_t rangeShift = (numTables << 4) - searchRange; + + // write font header + SkSFNTHeader* header = (SkSFNTHeader*)dataPtr; + header->fontType = fontType; + header->numTables = SkEndian_SwapBE16(numTables); + header->searchRange = SkEndian_SwapBE16(searchRange); + header->entrySelector = SkEndian_SwapBE16(entrySelector); + header->rangeShift = SkEndian_SwapBE16(rangeShift); + dataPtr += sizeof(SkSFNTHeader); + + // write tables + SkSFNTHeader::TableDirectoryEntry* entry = (SkSFNTHeader::TableDirectoryEntry*)dataPtr; + dataPtr += sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables; + for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { + size_t tableSize = tableSizes[tableIndex]; + this->getTableData(tableTags[tableIndex], 0, tableSize, dataPtr); + entry->tag = SkEndian_SwapBE32(tableTags[tableIndex]); + entry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum((SK_OT_ULONG*)dataPtr, + tableSize)); + entry->offset = SkEndian_SwapBE32(SkToU32(dataPtr - dataStart)); + entry->logicalLength = SkEndian_SwapBE32(SkToU32(tableSize)); + + dataPtr += (tableSize + 3) & ~3; + ++entry; + } + }); + return fStream->duplicate(); +} + +struct NonDefaultAxesContext { + SkFixed* axisValue; + CFArrayRef cgAxes; +}; +static void set_non_default_axes(CFTypeRef key, CFTypeRef value, void* context) { + NonDefaultAxesContext* self = static_cast(context); + + if (CFGetTypeID(key) != CFStringGetTypeID() || CFGetTypeID(value) != CFNumberGetTypeID()) { + return; + } + + // The key is a CFString which is a string from the 'name' table. + // Search the cgAxes for an axis with this name, and use its index to store the value. + CFIndex keyIndex = -1; + CFStringRef keyString = static_cast(key); + for (CFIndex i = 0; i < CFArrayGetCount(self->cgAxes); ++i) { + CFTypeRef cgAxis = CFArrayGetValueAtIndex(self->cgAxes, i); + if (CFGetTypeID(cgAxis) != CFDictionaryGetTypeID()) { + continue; + } + + CFDictionaryRef cgAxisDict = static_cast(cgAxis); + CFTypeRef cgAxisName = CFDictionaryGetValue(cgAxisDict, kCGFontVariationAxisName); + if (!cgAxisName || CFGetTypeID(cgAxisName) != CFStringGetTypeID()) { + continue; + } + CFStringRef cgAxisNameString = static_cast(cgAxisName); + if (CFStringCompare(keyString, cgAxisNameString, 0) == kCFCompareEqualTo) { + keyIndex = i; + break; + } + } + if (keyIndex == -1) { + return; + } + + CFNumberRef valueNumber = static_cast(value); + double valueDouble; + if (!CFNumberGetValue(valueNumber, kCFNumberDoubleType, &valueDouble) || + valueDouble < SkFixedToDouble(SK_FixedMin) || SkFixedToDouble(SK_FixedMax) < valueDouble) + { + return; + } + self->axisValue[keyIndex] = SkDoubleToFixed(valueDouble); +} +static bool get_variations(CTFontRef ctFont, CFIndex* cgAxisCount, + SkAutoSTMalloc<4, SkFixed>* axisValues) +{ + // In 10.10 and earlier, CTFontCopyVariationAxes and CTFontCopyVariation do not work when + // applied to fonts which started life with CGFontCreateWithDataProvider (they simply always + // return nullptr). As a result, we are limited to CGFontCopyVariationAxes and + // CGFontCopyVariations here until support for 10.10 and earlier is removed. + SkUniqueCFRef cgFont(CTFontCopyGraphicsFont(ctFont, nullptr)); + if (!cgFont) { + return false; + } + + SkUniqueCFRef cgVariations(CGFontCopyVariations(cgFont.get())); + // If a font has no variations CGFontCopyVariations returns nullptr (instead of an empty dict). + if (!cgVariations) { + return false; + } + + SkUniqueCFRef cgAxes(CGFontCopyVariationAxes(cgFont.get())); + if (!cgAxes) { + return false; + } + *cgAxisCount = CFArrayGetCount(cgAxes.get()); + axisValues->reset(*cgAxisCount); + + // Set all of the axes to their default values. + // Fail if any default value cannot be determined. + for (CFIndex i = 0; i < *cgAxisCount; ++i) { + CFTypeRef cgAxis = CFArrayGetValueAtIndex(cgAxes.get(), i); + if (CFGetTypeID(cgAxis) != CFDictionaryGetTypeID()) { + return false; + } + + CFDictionaryRef cgAxisDict = static_cast(cgAxis); + CFTypeRef axisDefaultValue = CFDictionaryGetValue(cgAxisDict, + kCGFontVariationAxisDefaultValue); + if (!axisDefaultValue || CFGetTypeID(axisDefaultValue) != CFNumberGetTypeID()) { + return false; + } + CFNumberRef axisDefaultValueNumber = static_cast(axisDefaultValue); + double axisDefaultValueDouble; + if (!CFNumberGetValue(axisDefaultValueNumber, kCFNumberDoubleType, &axisDefaultValueDouble)) + { + return false; + } + if (axisDefaultValueDouble < SkFixedToDouble(SK_FixedMin) || + SkFixedToDouble(SK_FixedMax) < axisDefaultValueDouble) + { + return false; + } + (*axisValues)[(int)i] = SkDoubleToFixed(axisDefaultValueDouble); + } + + // Override the default values with the given font's stated axis values. + NonDefaultAxesContext c = { axisValues->get(), cgAxes.get() }; + CFDictionaryApplyFunction(cgVariations.get(), set_non_default_axes, &c); + + return true; +} +std::unique_ptr SkTypeface_Mac::onMakeFontData() const { + int index; + std::unique_ptr stream(this->onOpenStream(&index)); + + CFIndex cgAxisCount; + SkAutoSTMalloc<4, SkFixed> axisValues; + if (get_variations(fFontRef.get(), &cgAxisCount, &axisValues)) { + return skstd::make_unique(std::move(stream), index, + axisValues.get(), cgAxisCount); + } + return skstd::make_unique(std::move(stream), index, nullptr, 0); +} + +/** Creates a CT variation dictionary {tag, value} from a CG variation dictionary {name, value}. */ +static SkUniqueCFRef ct_variation_from_cg_variation(CFDictionaryRef cgVariations, + CFArrayRef ctAxes) { + + SkUniqueCFRef ctVariations( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CFIndex axisCount = CFArrayGetCount(ctAxes); + for (CFIndex i = 0; i < axisCount; ++i) { + CFTypeRef axisInfo = CFArrayGetValueAtIndex(ctAxes, i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return nullptr; + } + CFDictionaryRef axisInfoDict = static_cast(axisInfo); + + // The assumption is that values produced by kCTFontVariationAxisNameKey and + // kCGFontVariationAxisName will always be equal. + CFTypeRef axisName = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisNameKey); + if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) { + return nullptr; + } + + CFTypeRef axisValue = CFDictionaryGetValue(cgVariations, axisName); + if (!axisValue || CFGetTypeID(axisValue) != CFNumberGetTypeID()) { + return nullptr; + } + + CFTypeRef axisTag = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisIdentifierKey); + if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) { + return nullptr; + } + + CFDictionaryAddValue(ctVariations.get(), axisTag, axisValue); + } + return ctVariations; +} + +int SkTypeface_Mac::onGetVariationDesignPosition( + SkFontArguments::VariationPosition::Coordinate coordinates[], int coordinateCount) const +{ + // The CGFont variation data does not contain the tag. + + // CTFontCopyVariationAxes returns nullptr for CGFontCreateWithDataProvider fonts with + // macOS 10.10 and iOS 9 or earlier. When this happens, there is no API to provide the tag. + SkUniqueCFRef ctAxes(CTFontCopyVariationAxes(fFontRef.get())); + if (!ctAxes) { + return -1; + } + CFIndex axisCount = CFArrayGetCount(ctAxes.get()); + if (!coordinates || coordinateCount < axisCount) { + return axisCount; + } + + // This call always returns nullptr on 10.11 and under for CGFontCreateWithDataProvider fonts. + // When this happens, try converting the CG variation to a CT variation. + // On 10.12 and later, this only returns non-default variations. + SkUniqueCFRef ctVariations(CTFontCopyVariation(fFontRef.get())); + if (!ctVariations) { + // When 10.11 and earlier are no longer supported, the following code can be replaced with + // return -1 and ct_variation_from_cg_variation can be removed. + SkUniqueCFRef cgFont(CTFontCopyGraphicsFont(fFontRef.get(), nullptr)); + if (!cgFont) { + return -1; + } + SkUniqueCFRef cgVariations(CGFontCopyVariations(cgFont.get())); + if (!cgVariations) { + return -1; + } + ctVariations = ct_variation_from_cg_variation(cgVariations.get(), ctAxes.get()); + if (!ctVariations) { + return -1; + } + } + + for (int i = 0; i < axisCount; ++i) { + CFTypeRef axisInfo = CFArrayGetValueAtIndex(ctAxes.get(), i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return -1; + } + CFDictionaryRef axisInfoDict = static_cast(axisInfo); + + CFTypeRef tag = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisIdentifierKey); + if (!tag || CFGetTypeID(tag) != CFNumberGetTypeID()) { + return -1; + } + CFNumberRef tagNumber = static_cast(tag); + int64_t tagLong; + if (!CFNumberGetValue(tagNumber, kCFNumberSInt64Type, &tagLong)) { + return -1; + } + coordinates[i].axis = tagLong; + + CGFloat variationCGFloat; + CFTypeRef variationValue = CFDictionaryGetValue(ctVariations.get(), tagNumber); + if (variationValue) { + if (CFGetTypeID(variationValue) != CFNumberGetTypeID()) { + return -1; + } + CFNumberRef variationNumber = static_cast(variationValue); + if (!CFNumberGetValue(variationNumber, kCFNumberCGFloatType, &variationCGFloat)) { + return -1; + } + } else { + CFTypeRef def = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisDefaultValueKey); + if (!def || CFGetTypeID(def) != CFNumberGetTypeID()) { + return -1; + } + CFNumberRef defNumber = static_cast(def); + if (!CFNumberGetValue(defNumber, kCFNumberCGFloatType, &variationCGFloat)) { + return -1; + } + } + coordinates[i].value = CGToScalar(variationCGFloat); + + } + return axisCount; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +int SkTypeface_Mac::onGetUPEM() const { + SkUniqueCFRef cgFont(CTFontCopyGraphicsFont(fFontRef.get(), nullptr)); + return CGFontGetUnitsPerEm(cgFont.get()); +} + +SkTypeface::LocalizedStrings* SkTypeface_Mac::onCreateFamilyNameIterator() const { + sk_sp nameIter = + SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*this); + if (!nameIter) { + CFStringRef cfLanguageRaw; + SkUniqueCFRef cfFamilyName( + CTFontCopyLocalizedName(fFontRef.get(), kCTFontFamilyNameKey, &cfLanguageRaw)); + SkUniqueCFRef cfLanguage(cfLanguageRaw); + + SkString skLanguage; + SkString skFamilyName; + if (cfLanguage) { + CFStringToSkString(cfLanguage.get(), &skLanguage); + } else { + skLanguage = "und"; //undetermined + } + if (cfFamilyName) { + CFStringToSkString(cfFamilyName.get(), &skFamilyName); + } + + nameIter = sk_make_sp(skFamilyName, skLanguage); + } + return nameIter.release(); +} + +int SkTypeface_Mac::onGetTableTags(SkFontTableTag tags[]) const { + SkUniqueCFRef cfArray( + CTFontCopyAvailableTables(fFontRef.get(), kCTFontTableOptionNoOptions)); + if (!cfArray) { + return 0; + } + int count = SkToInt(CFArrayGetCount(cfArray.get())); + if (tags) { + for (int i = 0; i < count; ++i) { + uintptr_t fontTag = reinterpret_cast( + CFArrayGetValueAtIndex(cfArray.get(), i)); + tags[i] = static_cast(fontTag); + } + } + return count; +} + +// If, as is the case with web fonts, the CTFont data isn't available, +// the CGFont data may work. While the CGFont may always provide the +// right result, leave the CTFont code path to minimize disruption. +static SkUniqueCFRef copy_table_from_font(CTFontRef ctFont, SkFontTableTag tag) { + SkUniqueCFRef data(CTFontCopyTable(ctFont, (CTFontTableTag) tag, + kCTFontTableOptionNoOptions)); + if (!data) { + SkUniqueCFRef cgFont(CTFontCopyGraphicsFont(ctFont, nullptr)); + data.reset(CGFontCopyTableForTag(cgFont.get(), tag)); + } + return data; +} + +size_t SkTypeface_Mac::onGetTableData(SkFontTableTag tag, size_t offset, + size_t length, void* dstData) const { + SkUniqueCFRef srcData = copy_table_from_font(fFontRef.get(), tag); + if (!srcData) { + return 0; + } + + size_t srcSize = CFDataGetLength(srcData.get()); + if (offset >= srcSize) { + return 0; + } + if (length > srcSize - offset) { + length = srcSize - offset; + } + if (dstData) { + memcpy(dstData, CFDataGetBytePtr(srcData.get()) + offset, length); + } + return length; +} + +sk_sp SkTypeface_Mac::onCopyTableData(SkFontTableTag tag) const { + SkUniqueCFRef srcData = copy_table_from_font(fFontRef.get(), tag); + if (!srcData) { + return nullptr; + } + const UInt8* data = CFDataGetBytePtr(srcData.get()); + CFIndex length = CFDataGetLength(srcData.get()); + return SkData::MakeWithProc(data, length, + [](const void*, void* ctx) { + CFRelease((CFDataRef)ctx); + }, (void*)srcData.release()); +} + +SkScalerContext* SkTypeface_Mac::onCreateScalerContext(const SkScalerContextEffects& effects, + const SkDescriptor* desc) const { + return new SkScalerContext_Mac(sk_ref_sp(const_cast(this)), effects, desc); +} + +void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const { + if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag || + rec->fFlags & SkScalerContext::kLCD_Vertical_Flag) + { + rec->fMaskFormat = SkMask::kA8_Format; + // Render the glyphs as close as possible to what was requested. + // The above turns off subpixel rendering, but the user requested it. + // Normal hinting will cause the A8 masks to be generated from CoreGraphics subpixel masks. + // See comments below for more details. + rec->setHinting(SkFontHinting::kNormal); + } + + unsigned flagsWeDontSupport = SkScalerContext::kForceAutohinting_Flag | + SkScalerContext::kLCD_BGROrder_Flag | + SkScalerContext::kLCD_Vertical_Flag; + + rec->fFlags &= ~flagsWeDontSupport; + + const SmoothBehavior smoothBehavior = smooth_behavior(); + + // Only two levels of hinting are supported. + // kNo_Hinting means avoid CoreGraphics outline dilation (smoothing). + // kNormal_Hinting means CoreGraphics outline dilation (smoothing) is allowed. + if (rec->getHinting() != SkFontHinting::kNone) { + rec->setHinting(SkFontHinting::kNormal); + } + // If smoothing has no effect, don't request it. + if (smoothBehavior == SmoothBehavior::none) { + rec->setHinting(SkFontHinting::kNone); + } + + // FIXME: lcd smoothed un-hinted rasterization unsupported. + // Tracked by http://code.google.com/p/skia/issues/detail?id=915 . + // There is no current means to honor a request for unhinted lcd, + // so arbitrarilly ignore the hinting request and honor lcd. + + // Hinting and smoothing should be orthogonal, but currently they are not. + // CoreGraphics has no API to influence hinting. However, its lcd smoothed + // output is drawn from auto-dilated outlines (the amount of which is + // determined by AppleFontSmoothing). Its regular anti-aliased output is + // drawn from un-dilated outlines. + + // The behavior of Skia is as follows: + // [AA][no-hint]: generate AA using CoreGraphic's AA output. + // [AA][yes-hint]: use CoreGraphic's LCD output and reduce it to a single + // channel. This matches [LCD][yes-hint] in weight. + // [LCD][no-hint]: curently unable to honor, and must pick which to respect. + // Currenly side with LCD, effectively ignoring the hinting setting. + // [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output. + if (rec->fMaskFormat == SkMask::kLCD16_Format) { + if (smoothBehavior == SmoothBehavior::subpixel) { + //CoreGraphics creates 555 masks for smoothed text anyway. + rec->fMaskFormat = SkMask::kLCD16_Format; + rec->setHinting(SkFontHinting::kNormal); + } else { + rec->fMaskFormat = SkMask::kA8_Format; + if (smoothBehavior != SmoothBehavior::none) { + rec->setHinting(SkFontHinting::kNormal); + } + } + } + + // CoreText provides no information as to whether a glyph will be color or not. + // Fonts may mix outlines and bitmaps, so information is needed on a glyph by glyph basis. + // If a font contains an 'sbix' table, consider it to be a color font, and disable lcd. + if (fHasColorGlyphs) { + rec->fMaskFormat = SkMask::kARGB32_Format; + } + + // Smoothing will be used if the format is either LCD or if there is hinting. + // In those cases, we need to choose the proper dilation mask based on the color. + if (rec->fMaskFormat == SkMask::kLCD16_Format || + (rec->fMaskFormat == SkMask::kA8_Format && rec->getHinting() != SkFontHinting::kNone)) { + SkColor color = rec->getLuminanceColor(); + int r = SkColorGetR(color); + int g = SkColorGetG(color); + int b = SkColorGetB(color); + // Choose whether to draw using a light-on-dark mask based on observed + // color/luminance thresholds that CoreText uses. + if (r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255) { + rec->fFlags |= SkScalerContext::kLightOnDark_Flag; + } + } + + // Unhinted A8 masks (those not derived from LCD masks) must respect SK_GAMMA_APPLY_TO_A8. + // All other masks can use regular gamma. + if (SkMask::kA8_Format == rec->fMaskFormat && SkFontHinting::kNone == rec->getHinting()) { +#ifndef SK_GAMMA_APPLY_TO_A8 + // SRGBTODO: Is this correct? Do we want contrast boost? + rec->ignorePreBlend(); +#endif + } else { +#ifndef SK_IGNORE_MAC_BLENDING_MATCH_FIX + SkColor color = rec->getLuminanceColor(); + if (smoothBehavior == SmoothBehavior::some) { + // CoreGraphics smoothed text without subpixel coverage blitting goes from a gamma of + // 2.0 for black foreground to a gamma of 1.0 for white foreground. Emulate this + // through the mask gamma by reducing the color values to 1/2. + color = SkColorSetRGB(SkColorGetR(color) * 1/2, + SkColorGetG(color) * 1/2, + SkColorGetB(color) * 1/2); + } else if (smoothBehavior == SmoothBehavior::subpixel) { + // CoreGraphics smoothed text with subpixel coverage blitting goes from a gamma of + // 2.0 for black foreground to a gamma of ~1.4? for white foreground. Emulate this + // through the mask gamma by reducing the color values to 3/4. + color = SkColorSetRGB(SkColorGetR(color) * 3/4, + SkColorGetG(color) * 3/4, + SkColorGetB(color) * 3/4); + } + rec->setLuminanceColor(color); +#endif + // CoreGraphics dialates smoothed text to provide contrast. + rec->setContrast(0); + } +} + +/** Takes ownership of the CFStringRef. */ +static const char* get_str(CFStringRef ref, SkString* str) { + if (nullptr == ref) { + return nullptr; + } + CFStringToSkString(ref, str); + CFRelease(ref); + return str->c_str(); +} + +void SkTypeface_Mac::onGetFamilyName(SkString* familyName) const { + get_str(CTFontCopyFamilyName(fFontRef.get()), familyName); +} + +void SkTypeface_Mac::onGetFontDescriptor(SkFontDescriptor* desc, + bool* isLocalStream) const { + SkString tmpStr; + + desc->setFamilyName(get_str(CTFontCopyFamilyName(fFontRef.get()), &tmpStr)); + desc->setFullName(get_str(CTFontCopyFullName(fFontRef.get()), &tmpStr)); + desc->setPostscriptName(get_str(CTFontCopyPostScriptName(fFontRef.get()), &tmpStr)); + desc->setStyle(this->fontStyle()); + *isLocalStream = fIsFromStream; +} + +void SkTypeface_Mac::onCharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const { + // Undocumented behavior of CTFontGetGlyphsForCharacters with non-bmp code points: + // When a surrogate pair is detected, the glyph index used is the index of the high surrogate. + // It is documented that if a mapping is unavailable, the glyph will be set to 0. + + SkAutoSTMalloc<1024, UniChar> charStorage; + const UniChar* src; // UniChar is a UTF-16 16-bit code unit. + int srcCount; + const SkUnichar* utf32 = reinterpret_cast(uni); + UniChar* utf16 = charStorage.reset(2 * count); + src = utf16; + for (int i = 0; i < count; ++i) { + utf16 += SkUTF::ToUTF16(utf32[i], utf16); + } + srcCount = SkToInt(utf16 - src); + + // If there are any non-bmp code points, the provided 'glyphs' storage will be inadequate. + SkAutoSTMalloc<1024, uint16_t> glyphStorage; + uint16_t* macGlyphs = glyphs; + if (srcCount > count) { + macGlyphs = glyphStorage.reset(srcCount); + } + + CTFontGetGlyphsForCharacters(fFontRef.get(), src, macGlyphs, srcCount); + + // If there were any non-bmp, then copy and compact. + // If all are bmp, 'glyphs' already contains the compact glyphs. + // If some are non-bmp, copy and compact into 'glyphs'. + if (srcCount > count) { + SkASSERT(glyphs != macGlyphs); + int extra = 0; + for (int i = 0; i < count; ++i) { + glyphs[i] = macGlyphs[i + extra]; + if (SkUTF16_IsLeadingSurrogate(src[i + extra])) { + ++extra; + } + } + } else { + SkASSERT(glyphs == macGlyphs); + } +} + +int SkTypeface_Mac::onCountGlyphs() const { + return SkToInt(CTFontGetGlyphCount(fFontRef.get())); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static bool find_desc_str(CTFontDescriptorRef desc, CFStringRef name, SkString* value) { + SkUniqueCFRef ref((CFStringRef)CTFontDescriptorCopyAttribute(desc, name)); + if (!ref) { + return false; + } + CFStringToSkString(ref.get(), value); + return true; +} + +#include "include/core/SkFontMgr.h" + +static inline int sqr(int value) { + SkASSERT(SkAbs32(value) < 0x7FFF); // check for overflow + return value * value; +} + +// We normalize each axis (weight, width, italic) to be base-900 +static int compute_metric(const SkFontStyle& a, const SkFontStyle& b) { + return sqr(a.weight() - b.weight()) + + sqr((a.width() - b.width()) * 100) + + sqr((a.slant() != b.slant()) * 900); +} + +class SkFontStyleSet_Mac : public SkFontStyleSet { +public: + SkFontStyleSet_Mac(CTFontDescriptorRef desc) + : fArray(CTFontDescriptorCreateMatchingFontDescriptors(desc, nullptr)) + , fCount(0) + { + if (!fArray) { + fArray.reset(CFArrayCreate(nullptr, nullptr, 0, nullptr)); + } + fCount = SkToInt(CFArrayGetCount(fArray.get())); + } + + int count() override { + return fCount; + } + + void getStyle(int index, SkFontStyle* style, SkString* name) override { + SkASSERT((unsigned)index < (unsigned)fCount); + CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray.get(), index); + if (style) { + *style = fontstyle_from_descriptor(desc, false); + } + if (name) { + if (!find_desc_str(desc, kCTFontStyleNameAttribute, name)) { + name->reset(); + } + } + } + + SkTypeface* createTypeface(int index) override { + SkASSERT((unsigned)index < (unsigned)CFArrayGetCount(fArray.get())); + CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray.get(), index); + + return create_from_desc(desc).release(); + } + + SkTypeface* matchStyle(const SkFontStyle& pattern) override { + if (0 == fCount) { + return nullptr; + } + return create_from_desc(findMatchingDesc(pattern)).release(); + } + +private: + SkUniqueCFRef fArray; + int fCount; + + CTFontDescriptorRef findMatchingDesc(const SkFontStyle& pattern) const { + int bestMetric = SK_MaxS32; + CTFontDescriptorRef bestDesc = nullptr; + + for (int i = 0; i < fCount; ++i) { + CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray.get(), i); + int metric = compute_metric(pattern, fontstyle_from_descriptor(desc, false)); + if (0 == metric) { + return desc; + } + if (metric < bestMetric) { + bestMetric = metric; + bestDesc = desc; + } + } + SkASSERT(bestDesc); + return bestDesc; + } +}; + +class SkFontMgr_Mac : public SkFontMgr { + SkUniqueCFRef fNames; + int fCount; + + CFStringRef getFamilyNameAt(int index) const { + SkASSERT((unsigned)index < (unsigned)fCount); + return (CFStringRef)CFArrayGetValueAtIndex(fNames.get(), index); + } + + static SkFontStyleSet* CreateSet(CFStringRef cfFamilyName) { + SkUniqueCFRef cfAttr( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CFDictionaryAddValue(cfAttr.get(), kCTFontFamilyNameAttribute, cfFamilyName); + + SkUniqueCFRef desc( + CTFontDescriptorCreateWithAttributes(cfAttr.get())); + return new SkFontStyleSet_Mac(desc.get()); + } + + /** CTFontManagerCopyAvailableFontFamilyNames() is not always available, so we + * provide a wrapper here that will return an empty array if need be. + */ + static SkUniqueCFRef CopyAvailableFontFamilyNames() { +#ifdef SK_BUILD_FOR_IOS + return SkUniqueCFRef(CFArrayCreate(nullptr, nullptr, 0, nullptr)); +#else + return SkUniqueCFRef(CTFontManagerCopyAvailableFontFamilyNames()); +#endif + } + +public: + SkFontMgr_Mac() + : fNames(CopyAvailableFontFamilyNames()) + , fCount(fNames ? SkToInt(CFArrayGetCount(fNames.get())) : 0) {} + +protected: + int onCountFamilies() const override { + return fCount; + } + + void onGetFamilyName(int index, SkString* familyName) const override { + if ((unsigned)index < (unsigned)fCount) { + CFStringToSkString(this->getFamilyNameAt(index), familyName); + } else { + familyName->reset(); + } + } + + SkFontStyleSet* onCreateStyleSet(int index) const override { + if ((unsigned)index >= (unsigned)fCount) { + return nullptr; + } + return CreateSet(this->getFamilyNameAt(index)); + } + + SkFontStyleSet* onMatchFamily(const char familyName[]) const override { + if (!familyName) { + return nullptr; + } + SkUniqueCFRef cfName = make_CFString(familyName); + return CreateSet(cfName.get()); + } + + SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& style) const override { + SkUniqueCFRef desc = create_descriptor(familyName, style); + return create_from_desc(desc.get()).release(); + } + + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override { + SkUniqueCFRef desc = create_descriptor(familyName, style); + SkUniqueCFRef familyFont(CTFontCreateWithFontDescriptor(desc.get(), 0, nullptr)); + + // kCFStringEncodingUTF32 is BE unless there is a BOM. + // Since there is no machine endian option, explicitly state machine endian. +#ifdef SK_CPU_LENDIAN + constexpr CFStringEncoding encoding = kCFStringEncodingUTF32LE; +#else + constexpr CFStringEncoding encoding = kCFStringEncodingUTF32BE; +#endif + SkUniqueCFRef string(CFStringCreateWithBytes( + kCFAllocatorDefault, reinterpret_cast(&character), sizeof(character), + encoding, false)); + CFRange range = CFRangeMake(0, CFStringGetLength(string.get())); // in UniChar units. + SkUniqueCFRef fallbackFont( + CTFontCreateForString(familyFont.get(), string.get(), range)); + return create_from_CTFontRef(std::move(fallbackFont), nullptr, nullptr).release(); + } + + SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle&) const override { + return nullptr; + } + + sk_sp onMakeFromData(sk_sp data, int ttcIndex) const override { + SkUniqueCFRef pr(SkCreateDataProviderFromData(data)); + if (!pr) { + return nullptr; + } + return create_from_dataProvider(std::move(pr), SkMemoryStream::Make(std::move(data)), + ttcIndex); + } + + sk_sp onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const override { + SkUniqueCFRef pr(SkCreateDataProviderFromStream(stream->duplicate())); + if (!pr) { + return nullptr; + } + return create_from_dataProvider(std::move(pr), std::move(stream), ttcIndex); + } + + /** Creates a dictionary suitable for setting the axes on a CGFont. */ + static SkUniqueCFRef copy_axes(CGFontRef cg, const SkFontArguments& args) { + // The CGFont variation data is keyed by name, but lacks the tag. + // The CTFont variation data is keyed by tag, and also has the name. + // We would like to work with CTFont variations, but creating a CTFont font with + // CTFont variation dictionary runs into bugs. So use the CTFont variation data + // to match names to tags to create the appropriate CGFont. + SkUniqueCFRef ct(CTFontCreateWithGraphicsFont(cg, 0, nullptr, nullptr)); + // CTFontCopyVariationAxes returns nullptr for CGFontCreateWithDataProvider fonts with + // macOS 10.10 and iOS 9 or earlier. When this happens, there is no API to provide the tag. + SkUniqueCFRef ctAxes(CTFontCopyVariationAxes(ct.get())); + if (!ctAxes) { + return nullptr; + } + CFIndex axisCount = CFArrayGetCount(ctAxes.get()); + + const SkFontArguments::VariationPosition position = args.getVariationDesignPosition(); + + SkUniqueCFRef dict( + CFDictionaryCreateMutable(kCFAllocatorDefault, axisCount, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + for (int i = 0; i < axisCount; ++i) { + CFTypeRef axisInfo = CFArrayGetValueAtIndex(ctAxes.get(), i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return nullptr; + } + CFDictionaryRef axisInfoDict = static_cast(axisInfo); + + // The assumption is that values produced by kCTFontVariationAxisNameKey and + // kCGFontVariationAxisName will always be equal. + // If they are ever not, seach the project history for "get_tag_for_name". + CFTypeRef axisName = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisNameKey); + if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) { + return nullptr; + } + + CFTypeRef tag = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisIdentifierKey); + if (!tag || CFGetTypeID(tag) != CFNumberGetTypeID()) { + return nullptr; + } + CFNumberRef tagNumber = static_cast(tag); + int64_t tagLong; + if (!CFNumberGetValue(tagNumber, kCFNumberSInt64Type, &tagLong)) { + return nullptr; + } + + // The variation axes can be set to any value, but cg will effectively pin them. + // Pin them here to normalize. + CFTypeRef min = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMinimumValueKey); + CFTypeRef max = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMaximumValueKey); + CFTypeRef def = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisDefaultValueKey); + if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || + !max || CFGetTypeID(max) != CFNumberGetTypeID() || + !def || CFGetTypeID(def) != CFNumberGetTypeID()) + { + return nullptr; + } + CFNumberRef minNumber = static_cast(min); + CFNumberRef maxNumber = static_cast(max); + CFNumberRef defNumber = static_cast(def); + double minDouble; + double maxDouble; + double defDouble; + if (!CFNumberGetValue(minNumber, kCFNumberDoubleType, &minDouble) || + !CFNumberGetValue(maxNumber, kCFNumberDoubleType, &maxDouble) || + !CFNumberGetValue(defNumber, kCFNumberDoubleType, &defDouble)) + { + return nullptr; + } + + double value = defDouble; + // The position may be over specified. If there are multiple values for a given axis, + // use the last one since that's what css-fonts-4 requires. + for (int j = position.coordinateCount; j --> 0;) { + if (position.coordinates[j].axis == tagLong) { + value = SkTPin(SkScalarToDouble(position.coordinates[j].value), + minDouble, maxDouble); + break; + } + } + SkUniqueCFRef valueNumber( + CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); + CFDictionaryAddValue(dict.get(), axisName, valueNumber.get()); + } + return dict; + } + sk_sp onMakeFromStreamArgs(std::unique_ptr s, + const SkFontArguments& args) const override { + if (args.getCollectionIndex() != 0) { + return nullptr; + } + SkUniqueCFRef provider(SkCreateDataProviderFromStream(s->duplicate())); + if (!provider) { + return nullptr; + } + SkUniqueCFRef cg(CGFontCreateWithDataProvider(provider.get())); + if (!cg) { + return nullptr; + } + + SkUniqueCFRef cgVariations = copy_axes(cg.get(), args); + // The CGFontRef returned by CGFontCreateCopyWithVariations when the passed CGFontRef was + // created from a data provider does not appear to have any ownership of the underlying + // data. The original CGFontRef must be kept alive until the copy will no longer be used. + SkUniqueCFRef cgVariant; + if (cgVariations) { + cgVariant.reset(CGFontCreateCopyWithVariations(cg.get(), cgVariations.get())); + } else { + cgVariant.reset(cg.release()); + } + + SkUniqueCFRef ct( + CTFontCreateWithGraphicsFont(cgVariant.get(), 0, nullptr, nullptr)); + if (!ct) { + return nullptr; + } + return create_from_CTFontRef(std::move(ct), std::move(cg), std::move(s)); + } + + /** Creates a dictionary suitable for setting the axes on a CGFont. */ + static SkUniqueCFRef copy_axes(CGFontRef cg, SkFontData* fontData) { + SkUniqueCFRef cgAxes(CGFontCopyVariationAxes(cg)); + if (!cgAxes) { + return nullptr; + } + + CFIndex axisCount = CFArrayGetCount(cgAxes.get()); + if (0 == axisCount || axisCount != fontData->getAxisCount()) { + return nullptr; + } + + SkUniqueCFRef dict( + CFDictionaryCreateMutable(kCFAllocatorDefault, axisCount, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + for (int i = 0; i < fontData->getAxisCount(); ++i) { + CFTypeRef axisInfo = CFArrayGetValueAtIndex(cgAxes.get(), i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return nullptr; + } + CFDictionaryRef axisInfoDict = static_cast(axisInfo); + + CFTypeRef axisName = CFDictionaryGetValue(axisInfoDict, kCGFontVariationAxisName); + if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) { + return nullptr; + } + + // The variation axes can be set to any value, but cg will effectively pin them. + // Pin them here to normalize. + CFTypeRef min = CFDictionaryGetValue(axisInfoDict, kCGFontVariationAxisMinValue); + CFTypeRef max = CFDictionaryGetValue(axisInfoDict, kCGFontVariationAxisMaxValue); + if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || + !max || CFGetTypeID(max) != CFNumberGetTypeID()) + { + return nullptr; + } + CFNumberRef minNumber = static_cast(min); + CFNumberRef maxNumber = static_cast(max); + double minDouble; + double maxDouble; + if (!CFNumberGetValue(minNumber, kCFNumberDoubleType, &minDouble) || + !CFNumberGetValue(maxNumber, kCFNumberDoubleType, &maxDouble)) + { + return nullptr; + } + double value = SkTPin(SkFixedToDouble(fontData->getAxis()[i]), minDouble, maxDouble); + SkUniqueCFRef valueNumber( + CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); + CFDictionaryAddValue(dict.get(), axisName, valueNumber.get()); + } + return dict; + } + sk_sp onMakeFromFontData(std::unique_ptr fontData) const override { + if (fontData->getIndex() != 0) { + return nullptr; + } + SkUniqueCFRef provider( + SkCreateDataProviderFromStream(fontData->getStream()->duplicate())); + if (!provider) { + return nullptr; + } + SkUniqueCFRef cg(CGFontCreateWithDataProvider(provider.get())); + if (!cg) { + return nullptr; + } + + SkUniqueCFRef cgVariations = copy_axes(cg.get(), fontData.get()); + // The CGFontRef returned by CGFontCreateCopyWithVariations when the passed CGFontRef was + // created from a data provider does not appear to have any ownership of the underlying + // data. The original CGFontRef must be kept alive until the copy will no longer be used. + SkUniqueCFRef cgVariant; + if (cgVariations) { + cgVariant.reset(CGFontCreateCopyWithVariations(cg.get(), cgVariations.get())); + } else { + cgVariant.reset(cg.release()); + } + + SkUniqueCFRef ct( + CTFontCreateWithGraphicsFont(cgVariant.get(), 0, nullptr, nullptr)); + if (!ct) { + return nullptr; + } + return create_from_CTFontRef(std::move(ct), std::move(cg), fontData->detachStream()); + } + + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override { + SkUniqueCFRef pr(CGDataProviderCreateWithFilename(path)); + if (!pr) { + return nullptr; + } + return create_from_dataProvider(std::move(pr), SkFILEStream::Make(path), ttcIndex); + } + + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override { + if (familyName) { + familyName = map_css_names(familyName); + } + + sk_sp face = create_from_name(familyName, style); + if (face) { + return face; + } + + static SkTypeface* gDefaultFace; + static SkOnce lookupDefault; + static const char FONT_DEFAULT_NAME[] = "Lucida Sans"; + lookupDefault([]{ + gDefaultFace = create_from_name(FONT_DEFAULT_NAME, SkFontStyle()).release(); + }); + return sk_ref_sp(gDefaultFace); + } +}; + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkFontMgr::Factory() { return sk_make_sp(); } + +#endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) diff --git a/gfx/skia/skia/src/ports/SkFontHost_win.cpp b/gfx/skia/skia/src/ports/SkFontHost_win.cpp new file mode 100644 index 0000000000..2090727ae2 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontHost_win.cpp @@ -0,0 +1,2287 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "include/core/SkData.h" +#include "include/core/SkFontMetrics.h" +#include "include/core/SkPath.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/ports/SkTypeface_win.h" +#include "src/ports/SkTypeface_win_dw.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "include/private/SkOnce.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "include/utils/SkBase64.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkDescriptor.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkLeanWindows.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkTypefaceCache.h" +#include "src/core/SkUtils.h" +#include "src/sfnt/SkOTTable_OS_2.h" +#include "src/sfnt/SkOTTable_maxp.h" +#include "src/sfnt/SkOTTable_name.h" +#include "src/sfnt/SkOTUtils.h" +#include "src/sfnt/SkSFNTHeader.h" +#include "src/utils/SkMatrix22.h" +#include "src/utils/win/SkHRESULT.h" + +#include +#include +#include + +static void (*gEnsureLOGFONTAccessibleProc)(const LOGFONT&); + +void SkTypeface_SetEnsureLOGFONTAccessibleProc(void (*proc)(const LOGFONT&)) { + gEnsureLOGFONTAccessibleProc = proc; +} + +static void call_ensure_accessible(const LOGFONT& lf) { + if (gEnsureLOGFONTAccessibleProc) { + gEnsureLOGFONTAccessibleProc(lf); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// always packed xxRRGGBB +typedef uint32_t SkGdiRGB; + +// define this in your Makefile or .gyp to enforce AA requests +// which GDI ignores at small sizes. This flag guarantees AA +// for rotated text, regardless of GDI's notions. +//#define SK_ENFORCE_ROTATED_TEXT_AA_ON_WINDOWS + +static bool isLCD(const SkScalerContextRec& rec) { + return SkMask::kLCD16_Format == rec.fMaskFormat; +} + +static bool bothZero(SkScalar a, SkScalar b) { + return 0 == a && 0 == b; +} + +// returns false if there is any non-90-rotation or skew +static bool isAxisAligned(const SkScalerContextRec& rec) { + return 0 == rec.fPreSkewX && + (bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) || + bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1])); +} + +static bool needToRenderWithSkia(const SkScalerContextRec& rec) { +#ifdef SK_ENFORCE_ROTATED_TEXT_AA_ON_WINDOWS + // What we really want to catch is when GDI will ignore the AA request and give + // us BW instead. Smallish rotated text is one heuristic, so this code is just + // an approximation. We shouldn't need to do this for larger sizes, but at those + // sizes, the quality difference gets less and less between our general + // scanconverter and GDI's. + if (SkMask::kA8_Format == rec.fMaskFormat && !isAxisAligned(rec)) { + return true; + } +#endif + return rec.getHinting() == SkFontHinting::kNone || rec.getHinting() == SkFontHinting::kSlight; +} + +static void tchar_to_skstring(const TCHAR t[], SkString* s) { +#ifdef UNICODE + size_t sSize = WideCharToMultiByte(CP_UTF8, 0, t, -1, nullptr, 0, nullptr, nullptr); + s->resize(sSize); + WideCharToMultiByte(CP_UTF8, 0, t, -1, s->writable_str(), sSize, nullptr, nullptr); +#else + s->set(t); +#endif +} + +static void dcfontname_to_skstring(HDC deviceContext, const LOGFONT& lf, SkString* familyName) { + int fontNameLen; //length of fontName in TCHARS. + if (0 == (fontNameLen = GetTextFace(deviceContext, 0, nullptr))) { + call_ensure_accessible(lf); + if (0 == (fontNameLen = GetTextFace(deviceContext, 0, nullptr))) { + fontNameLen = 0; + } + } + + SkAutoSTArray fontName(fontNameLen+1); + if (0 == GetTextFace(deviceContext, fontNameLen, fontName.get())) { + call_ensure_accessible(lf); + if (0 == GetTextFace(deviceContext, fontNameLen, fontName.get())) { + fontName[0] = 0; + } + } + + tchar_to_skstring(fontName.get(), familyName); +} + +static void make_canonical(LOGFONT* lf) { + lf->lfHeight = -64; + lf->lfWidth = 0; // lfWidth is related to lfHeight, not to the OS/2::usWidthClass. + lf->lfQuality = CLEARTYPE_QUALITY;//PROOF_QUALITY; + lf->lfCharSet = DEFAULT_CHARSET; +// lf->lfClipPrecision = 64; +} + +static SkFontStyle get_style(const LOGFONT& lf) { + return SkFontStyle(lf.lfWeight, + SkFontStyle::kNormal_Width, + lf.lfItalic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); +} + +static inline FIXED SkFixedToFIXED(SkFixed x) { + return *(FIXED*)(&x); +} +static inline SkFixed SkFIXEDToFixed(FIXED x) { + return *(SkFixed*)(&x); +} + +static inline FIXED SkScalarToFIXED(SkScalar x) { + return SkFixedToFIXED(SkScalarToFixed(x)); +} + +static inline SkScalar SkFIXEDToScalar(FIXED x) { + return SkFixedToScalar(SkFIXEDToFixed(x)); +} + +static unsigned calculateGlyphCount(HDC hdc, const LOGFONT& lf) { + TEXTMETRIC textMetric; + if (0 == GetTextMetrics(hdc, &textMetric)) { + textMetric.tmPitchAndFamily = TMPF_VECTOR; + call_ensure_accessible(lf); + GetTextMetrics(hdc, &textMetric); + } + + if (!(textMetric.tmPitchAndFamily & TMPF_VECTOR)) { + return textMetric.tmLastChar; + } + + // The 'maxp' table stores the number of glyphs at offset 4, in 2 bytes. + uint16_t glyphs; + if (GDI_ERROR != GetFontData(hdc, SkOTTableMaximumProfile::TAG, 4, &glyphs, sizeof(glyphs))) { + return SkEndian_SwapBE16(glyphs); + } + + // Binary search for glyph count. + static const MAT2 mat2 = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; + int32_t max = UINT16_MAX + 1; + int32_t min = 0; + GLYPHMETRICS gm; + while (min < max) { + int32_t mid = min + ((max - min) / 2); + if (GetGlyphOutlineW(hdc, mid, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, + nullptr, &mat2) == GDI_ERROR) { + max = mid; + } else { + min = mid + 1; + } + } + SkASSERT(min == max); + return min; +} + +static unsigned calculateUPEM(HDC hdc, const LOGFONT& lf) { + TEXTMETRIC textMetric; + if (0 == GetTextMetrics(hdc, &textMetric)) { + textMetric.tmPitchAndFamily = TMPF_VECTOR; + call_ensure_accessible(lf); + GetTextMetrics(hdc, &textMetric); + } + + if (!(textMetric.tmPitchAndFamily & TMPF_VECTOR)) { + return textMetric.tmMaxCharWidth; + } + + OUTLINETEXTMETRIC otm; + unsigned int otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm); + if (0 == otmRet) { + call_ensure_accessible(lf); + otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm); + } + + return (0 == otmRet) ? 0 : otm.otmEMSquare; +} + +class SkAutoHDC { +public: + explicit SkAutoHDC(const LOGFONT& lf) + : fHdc(::CreateCompatibleDC(nullptr)) + , fFont(::CreateFontIndirect(&lf)) + , fSavefont((HFONT)::SelectObject(fHdc, fFont)) + { } + ~SkAutoHDC() { + if (fHdc) { + ::SelectObject(fHdc, fSavefont); + ::DeleteDC(fHdc); + } + if (fFont) { + ::DeleteObject(fFont); + } + } + operator HDC() { return fHdc; } +private: + HDC fHdc; + HFONT fFont; + HFONT fSavefont; +}; +#define SkAutoHDC(...) SK_REQUIRE_LOCAL_VAR(SkAutoHDC) + +class LogFontTypeface : public SkTypeface { +public: + LogFontTypeface(const SkFontStyle& style, const LOGFONT& lf, bool serializeAsStream) + : SkTypeface(style, false) + , fLogFont(lf) + , fSerializeAsStream(serializeAsStream) + { + SkAutoHDC hdc(fLogFont); + TEXTMETRIC textMetric; + if (0 == GetTextMetrics(hdc, &textMetric)) { + call_ensure_accessible(lf); + if (0 == GetTextMetrics(hdc, &textMetric)) { + textMetric.tmPitchAndFamily = TMPF_TRUETYPE; + } + } + + // The fixed pitch bit is set if the font is *not* fixed pitch. + this->setIsFixedPitch((textMetric.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0); + this->setFontStyle(SkFontStyle(textMetric.tmWeight, style.width(), style.slant())); + + // Used a logfont on a memory context, should never get a device font. + // Therefore all TMPF_DEVICE will be PostScript (cubic) fonts. + // If the font has cubic outlines, it will not be rendered with ClearType. + fCanBeLCD = !((textMetric.tmPitchAndFamily & TMPF_VECTOR) && + (textMetric.tmPitchAndFamily & TMPF_DEVICE)); + } + + LOGFONT fLogFont; + bool fSerializeAsStream; + bool fCanBeLCD; + + static sk_sp Make(const LOGFONT& lf) { + return sk_sp(new LogFontTypeface(get_style(lf), lf, false)); + } + + static void EnsureAccessible(const SkTypeface* face) { + call_ensure_accessible(static_cast(face)->fLogFont); + } + +protected: + std::unique_ptr onOpenStream(int* ttcIndex) const override; + sk_sp onMakeClone(const SkFontArguments& args) const override; + SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&, + const SkDescriptor*) const override; + void onFilterRec(SkScalerContextRec*) const override; + void getGlyphToUnicodeMap(SkUnichar*) const override; + std::unique_ptr onGetAdvancedMetrics() const override; + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override; + void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override; + int onCountGlyphs() const override; + void getPostScriptGlyphNames(SkString*) const override; + int onGetUPEM() const override; + void onGetFamilyName(SkString* familyName) const override; + SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override; + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override + { + return -1; + } + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override + { + return -1; + } + int onGetTableTags(SkFontTableTag tags[]) const override; + size_t onGetTableData(SkFontTableTag, size_t offset, size_t length, void* data) const override; + sk_sp onCopyTableData(SkFontTableTag) const override; +}; + +class FontMemResourceTypeface : public LogFontTypeface { +public: + /** + * The created FontMemResourceTypeface takes ownership of fontMemResource. + */ + static sk_sp Make(const LOGFONT& lf, HANDLE fontMemResource) { + return sk_sp( + new FontMemResourceTypeface(get_style(lf), lf, fontMemResource)); + } + +protected: + void weak_dispose() const override { + RemoveFontMemResourceEx(fFontMemResource); + INHERITED::weak_dispose(); + } + +private: + /** + * Takes ownership of fontMemResource. + */ + FontMemResourceTypeface(const SkFontStyle& style, const LOGFONT& lf, HANDLE fontMemResource) + : LogFontTypeface(style, lf, true), fFontMemResource(fontMemResource) + { } + + HANDLE fFontMemResource; + + typedef LogFontTypeface INHERITED; +}; + +static const LOGFONT& get_default_font() { + static LOGFONT gDefaultFont; + return gDefaultFont; +} + +static bool FindByLogFont(SkTypeface* face, void* ctx) { + LogFontTypeface* lface = static_cast(face); + const LOGFONT* lf = reinterpret_cast(ctx); + + return !memcmp(&lface->fLogFont, lf, sizeof(LOGFONT)); +} + +/** + * This guy is public. It first searches the cache, and if a match is not found, + * it creates a new face. + */ +SkTypeface* SkCreateTypefaceFromLOGFONT(const LOGFONT& origLF) { + LOGFONT lf = origLF; + make_canonical(&lf); + sk_sp face = SkTypefaceCache::FindByProcAndRef(FindByLogFont, &lf); + if (!face) { + face = LogFontTypeface::Make(lf); + SkTypefaceCache::Add(face); + } + return face.release(); +} + +/*** + * This guy is public. + */ +SkTypeface* SkCreateTypefaceFromDWriteFont(IDWriteFactory* aFactory, + IDWriteFontFace* aFontFace, + SkFontStyle aStyle, + int aRenderingMode, + float aGamma, + float aContrast, + float aClearTypeLevel) +{ + return DWriteFontTypeface::Create(aFactory, aFontFace, aStyle, + (DWRITE_RENDERING_MODE)aRenderingMode, + aGamma, aContrast, aClearTypeLevel); +} + +/** + * The created SkTypeface takes ownership of fontMemResource. + */ +sk_sp SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) { + LOGFONT lf = origLF; + make_canonical(&lf); + // We'll never get a cache hit, so no point in putting this in SkTypefaceCache. + return FontMemResourceTypeface::Make(lf, fontMemResource); +} + +/** + * This guy is public + */ +void SkLOGFONTFromTypeface(const SkTypeface* face, LOGFONT* lf) { + if (nullptr == face) { + *lf = get_default_font(); + } else { + *lf = static_cast(face)->fLogFont; + } +} + +// Construct Glyph to Unicode table. +// Unicode code points that require conjugate pairs in utf16 are not +// supported. +// TODO(arthurhsu): Add support for conjugate pairs. It looks like that may +// require parsing the TTF cmap table (platform 4, encoding 12) directly instead +// of calling GetFontUnicodeRange(). +static void populate_glyph_to_unicode(HDC fontHdc, const unsigned glyphCount, + SkUnichar* glyphToUnicode) { + sk_bzero(glyphToUnicode, sizeof(SkUnichar) * glyphCount); + DWORD glyphSetBufferSize = GetFontUnicodeRanges(fontHdc, nullptr); + if (!glyphSetBufferSize) { + return; + } + + std::unique_ptr glyphSetBuffer(new BYTE[glyphSetBufferSize]); + GLYPHSET* glyphSet = + reinterpret_cast(glyphSetBuffer.get()); + if (GetFontUnicodeRanges(fontHdc, glyphSet) != glyphSetBufferSize) { + return; + } + + for (DWORD i = 0; i < glyphSet->cRanges; ++i) { + // There is no guarantee that within a Unicode range, the corresponding + // glyph id in a font file are continuous. So, even if we have ranges, + // we can't just use the first and last entry of the range to compute + // result. We need to enumerate them one by one. + int count = glyphSet->ranges[i].cGlyphs; + SkAutoTArray chars(count + 1); + chars[count] = 0; // termintate string + SkAutoTArray glyph(count); + for (USHORT j = 0; j < count; ++j) { + chars[j] = glyphSet->ranges[i].wcLow + j; + } + GetGlyphIndicesW(fontHdc, chars.get(), count, glyph.get(), + GGI_MARK_NONEXISTING_GLYPHS); + // If the glyph ID is valid, and the glyph is not mapped, then we will + // fill in the char id into the vector. If the glyph is mapped already, + // skip it. + // TODO(arthurhsu): better improve this. e.g. Get all used char ids from + // font cache, then generate this mapping table from there. It's + // unlikely to have collisions since glyph reuse happens mostly for + // different Unicode pages. + for (USHORT j = 0; j < count; ++j) { + if (glyph[j] != 0xFFFF && glyph[j] < glyphCount && glyphToUnicode[glyph[j]] == 0) { + glyphToUnicode[glyph[j]] = chars[j]; + } + } + } +} + +////////////////////////////////////////////////////////////////////////////////////// + +static int alignTo32(int n) { + return (n + 31) & ~31; +} + +struct MyBitmapInfo : public BITMAPINFO { + RGBQUAD fMoreSpaceForColors[1]; +}; + +class HDCOffscreen { +public: + HDCOffscreen() = default; + + ~HDCOffscreen() { + if (fDC) { + ::SelectObject(fDC, fSavefont); + ::DeleteDC(fDC); + } + if (fBM) { + DeleteObject(fBM); + } + } + + void init(HFONT font, const XFORM& xform) { + fFont = font; + fXform = xform; + } + + const void* draw(const SkGlyph&, bool isBW, size_t* srcRBPtr); + +private: + HDC fDC{0}; + HFONT fSavefont{0}; + HBITMAP fBM{0}; + HFONT fFont{0}; + XFORM fXform{1, 0, 0, 1, 0, 0}; + void* fBits{nullptr}; // points into fBM + int fWidth{0}; + int fHeight{0}; + bool fIsBW{false}; +}; + +const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW, + size_t* srcRBPtr) { + // Can we share the scalercontext's fDDC, so we don't need to create + // a separate fDC here? + if (0 == fDC) { + fDC = CreateCompatibleDC(0); + if (0 == fDC) { + return nullptr; + } + SetGraphicsMode(fDC, GM_ADVANCED); + SetBkMode(fDC, TRANSPARENT); + SetTextAlign(fDC, TA_LEFT | TA_BASELINE); + fSavefont = (HFONT)SelectObject(fDC, fFont); + + COLORREF color = 0x00FFFFFF; + SkDEBUGCODE(COLORREF prev =) SetTextColor(fDC, color); + SkASSERT(prev != CLR_INVALID); + } + + if (fBM && (fIsBW != isBW || fWidth < glyph.width() || fHeight < glyph.height())) { + DeleteObject(fBM); + fBM = 0; + } + fIsBW = isBW; + + fWidth = SkMax32(fWidth, glyph.width()); + fHeight = SkMax32(fHeight, glyph.height()); + + int biWidth = isBW ? alignTo32(fWidth) : fWidth; + + if (0 == fBM) { + MyBitmapInfo info; + sk_bzero(&info, sizeof(info)); + if (isBW) { + RGBQUAD blackQuad = { 0, 0, 0, 0 }; + RGBQUAD whiteQuad = { 0xFF, 0xFF, 0xFF, 0 }; + info.bmiColors[0] = blackQuad; + info.bmiColors[1] = whiteQuad; + } + info.bmiHeader.biSize = sizeof(info.bmiHeader); + info.bmiHeader.biWidth = biWidth; + info.bmiHeader.biHeight = fHeight; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = isBW ? 1 : 32; + info.bmiHeader.biCompression = BI_RGB; + if (isBW) { + info.bmiHeader.biClrUsed = 2; + } + fBM = CreateDIBSection(fDC, &info, DIB_RGB_COLORS, &fBits, 0, 0); + if (0 == fBM) { + return nullptr; + } + SelectObject(fDC, fBM); + } + + // erase + size_t srcRB = isBW ? (biWidth >> 3) : (fWidth << 2); + size_t size = fHeight * srcRB; + memset(fBits, 0, size); + + XFORM xform = fXform; + xform.eDx = (float)-glyph.left(); + xform.eDy = (float)-glyph.top(); + SetWorldTransform(fDC, &xform); + + uint16_t glyphID = glyph.getGlyphID(); + BOOL ret = ExtTextOutW(fDC, 0, 0, ETO_GLYPH_INDEX, nullptr, reinterpret_cast(&glyphID), 1, nullptr); + GdiFlush(); + if (0 == ret) { + return nullptr; + } + *srcRBPtr = srcRB; + // offset to the start of the image + return (const char*)fBits + (fHeight - glyph.height()) * srcRB; +} + +////////////////////////////////////////////////////////////////////////////// +#define BUFFERSIZE (1 << 13) + +class SkScalerContext_GDI : public SkScalerContext { +public: + SkScalerContext_GDI(sk_sp, + const SkScalerContextEffects&, + const SkDescriptor* desc); + ~SkScalerContext_GDI() override; + + // Returns true if the constructor was able to complete all of its + // initializations (which may include calling GDI). + bool isValid() const; + +protected: + unsigned generateGlyphCount() override; + bool generateAdvance(SkGlyph* glyph) override; + void generateMetrics(SkGlyph* glyph) override; + void generateImage(const SkGlyph& glyph) override; + bool generatePath(SkGlyphID glyph, SkPath* path) override; + void generateFontMetrics(SkFontMetrics*) override; + +private: + DWORD getGDIGlyphPath(SkGlyphID glyph, UINT flags, + SkAutoSTMalloc* glyphbuf); + template + static void RGBToA8(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, + const SkGlyph& glyph, const uint8_t* table8); + + template + static void RGBToLcd16(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB); + + HDCOffscreen fOffscreen; + /** fGsA is the non-rotational part of total matrix without the text height scale. + * Used to find the magnitude of advances. + */ + MAT2 fGsA; + /** The total matrix without the textSize. */ + MAT2 fMat22; + /** Scales font to EM size. */ + MAT2 fHighResMat22; + HDC fDDC; + HFONT fSavefont; + HFONT fFont; + SCRIPT_CACHE fSC; + int fGlyphCount; + + /** The total matrix which also removes EM scale. */ + SkMatrix fHiResMatrix; + /** fG_inv is the inverse of the rotational part of the total matrix. + * Used to set the direction of advances. + */ + SkMatrix fG_inv; + enum Type { + kTrueType_Type, kBitmap_Type, kLine_Type + } fType; + TEXTMETRIC fTM; +}; + +static FIXED float2FIXED(float x) { + return SkFixedToFIXED(SkFloatToFixed(x)); +} + +static inline float FIXED2float(FIXED x) { + return SkFixedToFloat(SkFIXEDToFixed(x)); +} + +static BYTE compute_quality(const SkScalerContextRec& rec) { + switch (rec.fMaskFormat) { + case SkMask::kBW_Format: + return NONANTIALIASED_QUALITY; + case SkMask::kLCD16_Format: + return CLEARTYPE_QUALITY; + default: + if (rec.fFlags & SkScalerContext::kGenA8FromLCD_Flag) { + return CLEARTYPE_QUALITY; + } else { + return ANTIALIASED_QUALITY; + } + } +} + +SkScalerContext_GDI::SkScalerContext_GDI(sk_sp rawTypeface, + const SkScalerContextEffects& effects, + const SkDescriptor* desc) + : SkScalerContext(std::move(rawTypeface), effects, desc) + , fDDC(0) + , fSavefont(0) + , fFont(0) + , fSC(0) + , fGlyphCount(-1) +{ + LogFontTypeface* typeface = static_cast(this->getTypeface()); + + fDDC = ::CreateCompatibleDC(nullptr); + if (!fDDC) { + return; + } + SetGraphicsMode(fDDC, GM_ADVANCED); + SetBkMode(fDDC, TRANSPARENT); + + // When GDI hinting, remove the entire Y scale from sA and GsA. (Prevents 'linear' metrics.) + // When not hinting, remove only the integer Y scale from sA and GsA. (Applied by GDI.) + SkScalerContextRec::PreMatrixScale scaleConstraints = + (fRec.getHinting() == SkFontHinting::kNone || fRec.getHinting() == SkFontHinting::kSlight) + ? SkScalerContextRec::kVerticalInteger_PreMatrixScale + : SkScalerContextRec::kVertical_PreMatrixScale; + SkVector scale; + SkMatrix sA; + SkMatrix GsA; + SkMatrix A; + fRec.computeMatrices(scaleConstraints, &scale, &sA, &GsA, &fG_inv, &A); + + fGsA.eM11 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleX)); + fGsA.eM12 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewY)); // This should be ~0. + fGsA.eM21 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewX)); + fGsA.eM22 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleY)); + + // When not hinting, scale was computed with kVerticalInteger, so is already an integer. + // The sA and GsA transforms will be used to create 'linear' metrics. + + // When hinting, scale was computed with kVertical, stating that our port can handle + // non-integer scales. This is done so that sA and GsA are computed without any 'residual' + // scale in them, preventing 'linear' metrics. However, GDI cannot actually handle non-integer + // scales so we need to round in this case. This is fine, since all of the scale has been + // removed from sA and GsA, so GDI will be handling the scale completely. + SkScalar gdiTextSize = SkScalarRoundToScalar(scale.fY); + + // GDI will not accept a size of zero, so round the range [0, 1] to 1. + // If the size was non-zero, the scale factors will also be non-zero and 1px tall text is drawn. + // If the size actually was zero, the scale factors will also be zero, so GDI will draw nothing. + if (gdiTextSize == 0) { + gdiTextSize = SK_Scalar1; + } + + LOGFONT lf = typeface->fLogFont; + lf.lfHeight = -SkScalarTruncToInt(gdiTextSize); + lf.lfQuality = compute_quality(fRec); + fFont = CreateFontIndirect(&lf); + if (!fFont) { + return; + } + + fSavefont = (HFONT)SelectObject(fDDC, fFont); + + if (0 == GetTextMetrics(fDDC, &fTM)) { + call_ensure_accessible(lf); + if (0 == GetTextMetrics(fDDC, &fTM)) { + fTM.tmPitchAndFamily = TMPF_TRUETYPE; + } + } + + XFORM xform; + if (fTM.tmPitchAndFamily & TMPF_VECTOR) { + // Used a logfont on a memory context, should never get a device font. + // Therefore all TMPF_DEVICE will be PostScript fonts. + + // If TMPF_VECTOR is set, one of TMPF_TRUETYPE or TMPF_DEVICE means that + // we have an outline font. Otherwise we have a vector FON, which is + // scalable, but not an outline font. + // This was determined by testing with Type1 PFM/PFB and + // OpenTypeCFF OTF, as well as looking at Wine bugs and sources. + if (fTM.tmPitchAndFamily & (TMPF_TRUETYPE | TMPF_DEVICE)) { + // Truetype or PostScript. + fType = SkScalerContext_GDI::kTrueType_Type; + } else { + // Stroked FON. + fType = SkScalerContext_GDI::kLine_Type; + } + + // fPost2x2 is column-major, left handed (y down). + // XFORM 2x2 is row-major, left handed (y down). + xform.eM11 = SkScalarToFloat(sA.get(SkMatrix::kMScaleX)); + xform.eM12 = SkScalarToFloat(sA.get(SkMatrix::kMSkewY)); + xform.eM21 = SkScalarToFloat(sA.get(SkMatrix::kMSkewX)); + xform.eM22 = SkScalarToFloat(sA.get(SkMatrix::kMScaleY)); + xform.eDx = 0; + xform.eDy = 0; + + // MAT2 is row major, right handed (y up). + fMat22.eM11 = float2FIXED(xform.eM11); + fMat22.eM12 = float2FIXED(-xform.eM12); + fMat22.eM21 = float2FIXED(-xform.eM21); + fMat22.eM22 = float2FIXED(xform.eM22); + + if (needToRenderWithSkia(fRec)) { + this->forceGenerateImageFromPath(); + } + + // Create a hires matrix if we need linear metrics. + if (this->isLinearMetrics()) { + OUTLINETEXTMETRIC otm; + UINT success = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm); + if (0 == success) { + call_ensure_accessible(lf); + success = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm); + } + if (0 != success) { + SkScalar upem = SkIntToScalar(otm.otmEMSquare); + + SkScalar gdiTextSizeToEMScale = upem / gdiTextSize; + fHighResMat22.eM11 = float2FIXED(gdiTextSizeToEMScale); + fHighResMat22.eM12 = float2FIXED(0); + fHighResMat22.eM21 = float2FIXED(0); + fHighResMat22.eM22 = float2FIXED(gdiTextSizeToEMScale); + + SkScalar removeEMScale = SkScalarInvert(upem); + fHiResMatrix = A; + fHiResMatrix.preScale(removeEMScale, removeEMScale); + } + } + + } else { + // Assume bitmap + fType = SkScalerContext_GDI::kBitmap_Type; + + xform.eM11 = 1.0f; + xform.eM12 = 0.0f; + xform.eM21 = 0.0f; + xform.eM22 = 1.0f; + xform.eDx = 0.0f; + xform.eDy = 0.0f; + + // fPost2x2 is column-major, left handed (y down). + // MAT2 is row major, right handed (y up). + fMat22.eM11 = SkScalarToFIXED(fRec.fPost2x2[0][0]); + fMat22.eM12 = SkScalarToFIXED(-fRec.fPost2x2[1][0]); + fMat22.eM21 = SkScalarToFIXED(-fRec.fPost2x2[0][1]); + fMat22.eM22 = SkScalarToFIXED(fRec.fPost2x2[1][1]); + } + + fOffscreen.init(fFont, xform); +} + +SkScalerContext_GDI::~SkScalerContext_GDI() { + if (fDDC) { + ::SelectObject(fDDC, fSavefont); + ::DeleteDC(fDDC); + } + if (fFont) { + ::DeleteObject(fFont); + } + if (fSC) { + ::ScriptFreeCache(&fSC); + } +} + +bool SkScalerContext_GDI::isValid() const { + return fDDC && fFont; +} + +unsigned SkScalerContext_GDI::generateGlyphCount() { + if (fGlyphCount < 0) { + fGlyphCount = calculateGlyphCount( + fDDC, static_cast(this->getTypeface())->fLogFont); + } + return fGlyphCount; +} + +bool SkScalerContext_GDI::generateAdvance(SkGlyph* glyph) { + return false; +} + +void SkScalerContext_GDI::generateMetrics(SkGlyph* glyph) { + SkASSERT(fDDC); + + glyph->fMaskFormat = fRec.fMaskFormat; + + if (fType == SkScalerContext_GDI::kBitmap_Type || fType == SkScalerContext_GDI::kLine_Type) { + SIZE size; + WORD glyphs = glyph->getGlyphID(); + if (0 == GetTextExtentPointI(fDDC, &glyphs, 1, &size)) { + glyph->fWidth = SkToS16(fTM.tmMaxCharWidth); + glyph->fHeight = SkToS16(fTM.tmHeight); + } else { + glyph->fWidth = SkToS16(size.cx); + glyph->fHeight = SkToS16(size.cy); + } + + glyph->fTop = SkToS16(-fTM.tmAscent); + // Bitmap FON cannot underhang, but vector FON may. + // There appears no means of determining underhang of vector FON. + glyph->fLeft = SkToS16(0); + glyph->fAdvanceX = glyph->width(); + glyph->fAdvanceY = 0; + + // Vector FON will transform nicely, but bitmap FON do not. + if (fType == SkScalerContext_GDI::kLine_Type) { + SkRect bounds = SkRect::MakeXYWH(glyph->fLeft, glyph->fTop, + glyph->width(), glyph->height()); + SkMatrix m; + m.setAll(SkFIXEDToScalar(fMat22.eM11), -SkFIXEDToScalar(fMat22.eM21), 0, + -SkFIXEDToScalar(fMat22.eM12), SkFIXEDToScalar(fMat22.eM22), 0, + 0, 0, 1); + m.mapRect(&bounds); + bounds.roundOut(&bounds); + glyph->fLeft = SkScalarTruncToInt(bounds.fLeft); + glyph->fTop = SkScalarTruncToInt(bounds.fTop); + glyph->fWidth = SkScalarTruncToInt(bounds.width()); + glyph->fHeight = SkScalarTruncToInt(bounds.height()); + } + + // Apply matrix to advance. + glyph->fAdvanceY = -FIXED2float(fMat22.eM12) * glyph->fAdvanceX; + glyph->fAdvanceX *= FIXED2float(fMat22.eM11); + + return; + } + + UINT glyphId = glyph->getGlyphID(); + + GLYPHMETRICS gm; + sk_bzero(&gm, sizeof(gm)); + + DWORD status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fMat22); + if (GDI_ERROR == status) { + LogFontTypeface::EnsureAccessible(this->getTypeface()); + status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fMat22); + if (GDI_ERROR == status) { + glyph->zeroMetrics(); + return; + } + } + + bool empty = false; + // The black box is either the embedded bitmap size or the outline extent. + // It is 1x1 if nothing is to be drawn, but will also be 1x1 if something very small + // is to be drawn, like a '.'. We need to outset '.' but do not wish to outset ' '. + if (1 == gm.gmBlackBoxX && 1 == gm.gmBlackBoxY) { + // If GetGlyphOutline with GGO_NATIVE returns 0, we know there was no outline. + DWORD bufferSize = GetGlyphOutlineW(fDDC, glyphId, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fMat22); + empty = (0 == bufferSize); + } + + glyph->fTop = SkToS16(-gm.gmptGlyphOrigin.y); + glyph->fLeft = SkToS16(gm.gmptGlyphOrigin.x); + if (empty) { + glyph->fWidth = 0; + glyph->fHeight = 0; + } else { + // Outset, since the image may bleed out of the black box. + // For embedded bitmaps the black box should be exact. + // For outlines we need to outset by 1 in all directions for bleed. + // For ClearType we need to outset by 2 for bleed. + glyph->fWidth = gm.gmBlackBoxX + 4; + glyph->fHeight = gm.gmBlackBoxY + 4; + glyph->fTop -= 2; + glyph->fLeft -= 2; + } + // TODO(benjaminwagner): What is the type of gm.gmCellInc[XY]? + glyph->fAdvanceX = (float)((int)gm.gmCellIncX); + glyph->fAdvanceY = (float)((int)gm.gmCellIncY); + + if ((fTM.tmPitchAndFamily & TMPF_VECTOR) && this->isLinearMetrics()) { + sk_bzero(&gm, sizeof(gm)); + status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fHighResMat22); + if (GDI_ERROR != status) { + SkPoint advance; + fHiResMatrix.mapXY(SkIntToScalar(gm.gmCellIncX), SkIntToScalar(gm.gmCellIncY), &advance); + glyph->fAdvanceX = SkScalarToFloat(advance.fX); + glyph->fAdvanceY = SkScalarToFloat(advance.fY); + } + } else if (!isAxisAligned(this->fRec)) { + status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fGsA); + if (GDI_ERROR != status) { + SkPoint advance; + fG_inv.mapXY(SkIntToScalar(gm.gmCellIncX), SkIntToScalar(gm.gmCellIncY), &advance); + glyph->fAdvanceX = SkScalarToFloat(advance.fX); + glyph->fAdvanceY = SkScalarToFloat(advance.fY); + } + } +} + +static const MAT2 gMat2Identity = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; +void SkScalerContext_GDI::generateFontMetrics(SkFontMetrics* metrics) { + if (nullptr == metrics) { + return; + } + sk_bzero(metrics, sizeof(*metrics)); + + SkASSERT(fDDC); + +#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS + if (fType == SkScalerContext_GDI::kBitmap_Type || fType == SkScalerContext_GDI::kLine_Type) { +#endif + metrics->fTop = SkIntToScalar(-fTM.tmAscent); + metrics->fAscent = SkIntToScalar(-fTM.tmAscent); + metrics->fDescent = SkIntToScalar(fTM.tmDescent); + metrics->fBottom = SkIntToScalar(fTM.tmDescent); + metrics->fLeading = SkIntToScalar(fTM.tmExternalLeading); + metrics->fAvgCharWidth = SkIntToScalar(fTM.tmAveCharWidth); + metrics->fMaxCharWidth = SkIntToScalar(fTM.tmMaxCharWidth); + metrics->fXMin = 0; + metrics->fXMax = metrics->fMaxCharWidth; + //metrics->fXHeight = 0; +#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS + return; + } +#endif + + OUTLINETEXTMETRIC otm; + + uint32_t ret = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm); + if (0 == ret) { + LogFontTypeface::EnsureAccessible(this->getTypeface()); + ret = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm); + } + if (0 == ret) { + return; + } + +#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS + metrics->fTop = SkIntToScalar(-otm.otmrcFontBox.top); + metrics->fAscent = SkIntToScalar(-otm.otmAscent); + metrics->fDescent = SkIntToScalar(-otm.otmDescent); + metrics->fBottom = SkIntToScalar(-otm.otmrcFontBox.bottom); + metrics->fLeading = SkIntToScalar(otm.otmLineGap); + metrics->fAvgCharWidth = SkIntToScalar(otm.otmTextMetrics.tmAveCharWidth); + metrics->fMaxCharWidth = SkIntToScalar(otm.otmTextMetrics.tmMaxCharWidth); + metrics->fXMin = SkIntToScalar(otm.otmrcFontBox.left); + metrics->fXMax = SkIntToScalar(otm.otmrcFontBox.right); +#endif + metrics->fUnderlineThickness = SkIntToScalar(otm.otmsUnderscoreSize); + metrics->fUnderlinePosition = -SkIntToScalar(otm.otmsUnderscorePosition); + + metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag; + + metrics->fXHeight = SkIntToScalar(otm.otmsXHeight); + GLYPHMETRICS gm; + sk_bzero(&gm, sizeof(gm)); + DWORD len = GetGlyphOutlineW(fDDC, 'x', GGO_METRICS, &gm, 0, 0, &gMat2Identity); + if (len != GDI_ERROR && gm.gmBlackBoxY > 0) { + metrics->fXHeight = SkIntToScalar(gm.gmBlackBoxY); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// + +#define SK_SHOW_TEXT_BLIT_COVERAGE 0 + +static void build_power_table(uint8_t table[], float ee) { + for (int i = 0; i < 256; i++) { + float x = i / 255.f; + x = sk_float_pow(x, ee); + int xx = SkScalarRoundToInt(x * 255); + table[i] = SkToU8(xx); + } +} + +/** + * This will invert the gamma applied by GDI (gray-scale antialiased), so we + * can get linear values. + * + * GDI grayscale appears to use a hard-coded gamma of 2.3. + * + * GDI grayscale appears to draw using the black and white rasterizer at four + * times the size and then downsamples to compute the coverage mask. As a + * result there are only seventeen total grays. This lack of fidelity means + * that shifting into other color spaces is imprecise. + */ +static const uint8_t* getInverseGammaTableGDI() { + static SkOnce once; + static uint8_t gTableGdi[256]; + once([]{ + build_power_table(gTableGdi, 2.3f); + }); + return gTableGdi; +} + +/** + * This will invert the gamma applied by GDI ClearType, so we can get linear + * values. + * + * GDI ClearType uses SPI_GETFONTSMOOTHINGCONTRAST / 1000 as the gamma value. + * If this value is not specified, the default is a gamma of 1.4. + */ +static const uint8_t* getInverseGammaTableClearType() { + static SkOnce once; + static uint8_t gTableClearType[256]; + once([]{ + UINT level = 0; + if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &level, 0) || !level) { + // can't get the data, so use a default + level = 1400; + } + build_power_table(gTableClearType, level / 1000.0f); + }); + return gTableClearType; +} + +#include "include/private/SkColorData.h" + +//Cannot assume that the input rgb is gray due to possible setting of kGenA8FromLCD_Flag. +template +static inline uint8_t rgb_to_a8(SkGdiRGB rgb, const uint8_t* table8) { + U8CPU r = (rgb >> 16) & 0xFF; + U8CPU g = (rgb >> 8) & 0xFF; + U8CPU b = (rgb >> 0) & 0xFF; + return sk_apply_lut_if(SkComputeLuminance(r, g, b), table8); +} + +template +static inline uint16_t rgb_to_lcd16(SkGdiRGB rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); +#if SK_SHOW_TEXT_BLIT_COVERAGE + r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10); +#endif + return SkPack888ToRGB16(r, g, b); +} + +template +void SkScalerContext_GDI::RGBToA8(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, + const SkGlyph& glyph, const uint8_t* table8) { + const size_t dstRB = glyph.rowBytes(); + const int width = glyph.width(); + uint8_t* SK_RESTRICT dst = (uint8_t*)((char*)glyph.fImage + (glyph.height() - 1) * dstRB); + + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; i++) { + dst[i] = rgb_to_a8(src[i], table8); +#if SK_SHOW_TEXT_BLIT_COVERAGE + dst[i] = SkMax32(dst[i], 10); +#endif + } + src = SkTAddOffset(src, srcRB); + dst -= dstRB; + } +} + +template +void SkScalerContext_GDI::RGBToLcd16( + const SkGdiRGB* SK_RESTRICT src, size_t srcRB, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { + const size_t dstRB = glyph.rowBytes(); + const int width = glyph.width(); + uint16_t* SK_RESTRICT dst = (uint16_t*)((char*)glyph.fImage + (glyph.height() - 1) * dstRB); + + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; i++) { + dst[i] = rgb_to_lcd16(src[i], tableR, tableG, tableB); + } + src = SkTAddOffset(src, srcRB); + dst = (uint16_t*)((char*)dst - dstRB); + } +} + +void SkScalerContext_GDI::generateImage(const SkGlyph& glyph) { + SkASSERT(fDDC); + + const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat; + const bool isAA = !isLCD(fRec); + + size_t srcRB; + const void* bits = fOffscreen.draw(glyph, isBW, &srcRB); + if (nullptr == bits) { + LogFontTypeface::EnsureAccessible(this->getTypeface()); + bits = fOffscreen.draw(glyph, isBW, &srcRB); + if (nullptr == bits) { + sk_bzero(glyph.fImage, glyph.imageSize()); + return; + } + } + + if (!isBW) { + const uint8_t* table; + //The offscreen contains a GDI blit if isAA and kGenA8FromLCD_Flag is not set. + //Otherwise the offscreen contains a ClearType blit. + if (isAA && !(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag)) { + table = getInverseGammaTableGDI(); + } else { + table = getInverseGammaTableClearType(); + } + //Note that the following cannot really be integrated into the + //pre-blend, since we may not be applying the pre-blend; when we aren't + //applying the pre-blend it means that a filter wants linear anyway. + //Other code may also be applying the pre-blend, so we'd need another + //one with this and one without. + SkGdiRGB* addr = (SkGdiRGB*)bits; + for (int y = 0; y < glyph.fHeight; ++y) { + for (int x = 0; x < glyph.width(); ++x) { + int r = (addr[x] >> 16) & 0xFF; + int g = (addr[x] >> 8) & 0xFF; + int b = (addr[x] >> 0) & 0xFF; + addr[x] = (table[r] << 16) | (table[g] << 8) | table[b]; + } + addr = SkTAddOffset(addr, srcRB); + } + } + + size_t dstRB = glyph.rowBytes(); + if (isBW) { + const uint8_t* src = (const uint8_t*)bits; + uint8_t* dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB); + for (int y = 0; y < glyph.fHeight; y++) { + memcpy(dst, src, dstRB); + src += srcRB; + dst -= dstRB; + } +#if SK_SHOW_TEXT_BLIT_COVERAGE + if (glyph.width() > 0 && glyph.fHeight > 0) { + int bitCount = glyph.width() & 7; + uint8_t* first = (uint8_t*)glyph.fImage; + uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.height() * dstRB - 1); + *first |= 1 << 7; + *last |= bitCount == 0 ? 1 : 1 << (8 - bitCount); + } +#endif + } else if (isAA) { + // since the caller may require A8 for maskfilters, we can't check for BW + // ... until we have the caller tell us that explicitly + const SkGdiRGB* src = (const SkGdiRGB*)bits; + if (fPreBlend.isApplicable()) { + RGBToA8(src, srcRB, glyph, fPreBlend.fG); + } else { + RGBToA8(src, srcRB, glyph, fPreBlend.fG); + } + } else { // LCD16 + const SkGdiRGB* src = (const SkGdiRGB*)bits; + SkASSERT(SkMask::kLCD16_Format == glyph.fMaskFormat); + if (fPreBlend.isApplicable()) { + RGBToLcd16(src, srcRB, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); + } else { + RGBToLcd16(src, srcRB, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); + } + } +} + +class GDIGlyphbufferPointIter { +public: + GDIGlyphbufferPointIter(const uint8_t* glyphbuf, DWORD total_size) + : fHeaderIter(glyphbuf, total_size), fCurveIter(), fPointIter() + { } + + POINTFX const * next() { +nextHeader: + if (!fCurveIter.isSet()) { + const TTPOLYGONHEADER* header = fHeaderIter.next(); + if (nullptr == header) { + return nullptr; + } + fCurveIter.set(header); + const TTPOLYCURVE* curve = fCurveIter.next(); + if (nullptr == curve) { + return nullptr; + } + fPointIter.set(curve); + return &header->pfxStart; + } + + const POINTFX* nextPoint = fPointIter.next(); + if (nullptr == nextPoint) { + const TTPOLYCURVE* curve = fCurveIter.next(); + if (nullptr == curve) { + fCurveIter.set(); + goto nextHeader; + } else { + fPointIter.set(curve); + } + nextPoint = fPointIter.next(); + } + return nextPoint; + } + + WORD currentCurveType() { + return fPointIter.fCurveType; + } + +private: + /** Iterates over all of the polygon headers in a glyphbuf. */ + class GDIPolygonHeaderIter { + public: + GDIPolygonHeaderIter(const uint8_t* glyphbuf, DWORD total_size) + : fCurPolygon(reinterpret_cast(glyphbuf)) + , fEndPolygon(SkTAddOffset(glyphbuf, total_size)) + { } + + const TTPOLYGONHEADER* next() { + if (fCurPolygon >= fEndPolygon) { + return nullptr; + } + const TTPOLYGONHEADER* thisPolygon = fCurPolygon; + fCurPolygon = SkTAddOffset(fCurPolygon, fCurPolygon->cb); + return thisPolygon; + } + private: + const TTPOLYGONHEADER* fCurPolygon; + const TTPOLYGONHEADER* fEndPolygon; + }; + + /** Iterates over all of the polygon curves in a polygon header. */ + class GDIPolygonCurveIter { + public: + GDIPolygonCurveIter() : fCurCurve(nullptr), fEndCurve(nullptr) { } + + GDIPolygonCurveIter(const TTPOLYGONHEADER* curPolygon) + : fCurCurve(SkTAddOffset(curPolygon, sizeof(TTPOLYGONHEADER))) + , fEndCurve(SkTAddOffset(curPolygon, curPolygon->cb)) + { } + + bool isSet() { return fCurCurve != nullptr; } + + void set(const TTPOLYGONHEADER* curPolygon) { + fCurCurve = SkTAddOffset(curPolygon, sizeof(TTPOLYGONHEADER)); + fEndCurve = SkTAddOffset(curPolygon, curPolygon->cb); + } + void set() { + fCurCurve = nullptr; + fEndCurve = nullptr; + } + + const TTPOLYCURVE* next() { + if (fCurCurve >= fEndCurve) { + return nullptr; + } + const TTPOLYCURVE* thisCurve = fCurCurve; + fCurCurve = SkTAddOffset(fCurCurve, size_of_TTPOLYCURVE(*fCurCurve)); + return thisCurve; + } + private: + size_t size_of_TTPOLYCURVE(const TTPOLYCURVE& curve) { + return 2*sizeof(WORD) + curve.cpfx*sizeof(POINTFX); + } + const TTPOLYCURVE* fCurCurve; + const TTPOLYCURVE* fEndCurve; + }; + + /** Iterates over all of the polygon points in a polygon curve. */ + class GDIPolygonCurvePointIter { + public: + GDIPolygonCurvePointIter() : fCurveType(0), fCurPoint(nullptr), fEndPoint(nullptr) { } + + GDIPolygonCurvePointIter(const TTPOLYCURVE* curPolygon) + : fCurveType(curPolygon->wType) + , fCurPoint(&curPolygon->apfx[0]) + , fEndPoint(&curPolygon->apfx[curPolygon->cpfx]) + { } + + bool isSet() { return fCurPoint != nullptr; } + + void set(const TTPOLYCURVE* curPolygon) { + fCurveType = curPolygon->wType; + fCurPoint = &curPolygon->apfx[0]; + fEndPoint = &curPolygon->apfx[curPolygon->cpfx]; + } + void set() { + fCurPoint = nullptr; + fEndPoint = nullptr; + } + + const POINTFX* next() { + if (fCurPoint >= fEndPoint) { + return nullptr; + } + const POINTFX* thisPoint = fCurPoint; + ++fCurPoint; + return thisPoint; + } + + WORD fCurveType; + private: + const POINTFX* fCurPoint; + const POINTFX* fEndPoint; + }; + + GDIPolygonHeaderIter fHeaderIter; + GDIPolygonCurveIter fCurveIter; + GDIPolygonCurvePointIter fPointIter; +}; + +static void sk_path_from_gdi_path(SkPath* path, const uint8_t* glyphbuf, DWORD total_size) { + const uint8_t* cur_glyph = glyphbuf; + const uint8_t* end_glyph = glyphbuf + total_size; + + while (cur_glyph < end_glyph) { + const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)cur_glyph; + + const uint8_t* end_poly = cur_glyph + th->cb; + const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER); + + path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)), + SkFixedToScalar(-SkFIXEDToFixed(th->pfxStart.y))); + + while (cur_poly < end_poly) { + const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly; + + if (pc->wType == TT_PRIM_LINE) { + for (uint16_t i = 0; i < pc->cpfx; i++) { + path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)), + SkFixedToScalar(-SkFIXEDToFixed(pc->apfx[i].y))); + } + } + + if (pc->wType == TT_PRIM_QSPLINE) { + for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline + POINTFX pnt_b = pc->apfx[u]; // B is always the current point + POINTFX pnt_c = pc->apfx[u+1]; + + if (u < pc->cpfx - 2) { // If not on last spline, compute C + pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x), + SkFIXEDToFixed(pnt_c.x))); + pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y), + SkFIXEDToFixed(pnt_c.y))); + } + + path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)), + SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)), + SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)), + SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y))); + } + } + // Advance past this TTPOLYCURVE. + cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx; + } + cur_glyph += th->cb; + path->close(); + } +} + +#define move_next_expected_hinted_point(iter, pElem) do {\ + pElem = iter.next(); \ + if (nullptr == pElem) return false; \ +} while(0) + +// It is possible for the hinted and unhinted versions of the same path to have +// a different number of points due to GDI's handling of flipped points. +// If this is detected, this will return false. +static bool sk_path_from_gdi_paths(SkPath* path, const uint8_t* glyphbuf, DWORD total_size, + GDIGlyphbufferPointIter hintedYs) { + const uint8_t* cur_glyph = glyphbuf; + const uint8_t* end_glyph = glyphbuf + total_size; + + POINTFX const * hintedPoint; + + while (cur_glyph < end_glyph) { + const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)cur_glyph; + + const uint8_t* end_poly = cur_glyph + th->cb; + const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER); + + move_next_expected_hinted_point(hintedYs, hintedPoint); + path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)), + SkFixedToScalar(-SkFIXEDToFixed(hintedPoint->y))); + + while (cur_poly < end_poly) { + const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly; + + if (pc->wType == TT_PRIM_LINE) { + for (uint16_t i = 0; i < pc->cpfx; i++) { + move_next_expected_hinted_point(hintedYs, hintedPoint); + path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)), + SkFixedToScalar(-SkFIXEDToFixed(hintedPoint->y))); + } + } + + if (pc->wType == TT_PRIM_QSPLINE) { + POINTFX currentPoint = pc->apfx[0]; + move_next_expected_hinted_point(hintedYs, hintedPoint); + // only take the hinted y if it wasn't flipped + if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) { + currentPoint.y = hintedPoint->y; + } + for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline + POINTFX pnt_b = currentPoint;//pc->apfx[u]; // B is always the current point + POINTFX pnt_c = pc->apfx[u+1]; + move_next_expected_hinted_point(hintedYs, hintedPoint); + // only take the hinted y if it wasn't flipped + if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) { + pnt_c.y = hintedPoint->y; + } + currentPoint.x = pnt_c.x; + currentPoint.y = pnt_c.y; + + if (u < pc->cpfx - 2) { // If not on last spline, compute C + pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x), + SkFIXEDToFixed(pnt_c.x))); + pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y), + SkFIXEDToFixed(pnt_c.y))); + } + + path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)), + SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)), + SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)), + SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y))); + } + } + // Advance past this TTPOLYCURVE. + cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx; + } + cur_glyph += th->cb; + path->close(); + } + return true; +} + +DWORD SkScalerContext_GDI::getGDIGlyphPath(SkGlyphID glyph, UINT flags, + SkAutoSTMalloc* glyphbuf) +{ + GLYPHMETRICS gm; + + DWORD total_size = GetGlyphOutlineW(fDDC, glyph, flags, &gm, BUFFERSIZE, glyphbuf->get(), &fMat22); + // Sometimes GetGlyphOutlineW returns a number larger than BUFFERSIZE even if BUFFERSIZE > 0. + // It has been verified that this does not involve a buffer overrun. + if (GDI_ERROR == total_size || total_size > BUFFERSIZE) { + // GDI_ERROR because the BUFFERSIZE was too small, or because the data was not accessible. + // When the data is not accessable GetGlyphOutlineW fails rather quickly, + // so just try to get the size. If that fails then ensure the data is accessible. + total_size = GetGlyphOutlineW(fDDC, glyph, flags, &gm, 0, nullptr, &fMat22); + if (GDI_ERROR == total_size) { + LogFontTypeface::EnsureAccessible(this->getTypeface()); + total_size = GetGlyphOutlineW(fDDC, glyph, flags, &gm, 0, nullptr, &fMat22); + if (GDI_ERROR == total_size) { + // GetGlyphOutlineW is known to fail for some characters, such as spaces. + // In these cases, just return that the glyph does not have a shape. + return 0; + } + } + + glyphbuf->reset(total_size); + + DWORD ret = GetGlyphOutlineW(fDDC, glyph, flags, &gm, total_size, glyphbuf->get(), &fMat22); + if (GDI_ERROR == ret) { + LogFontTypeface::EnsureAccessible(this->getTypeface()); + ret = GetGlyphOutlineW(fDDC, glyph, flags, &gm, total_size, glyphbuf->get(), &fMat22); + if (GDI_ERROR == ret) { + SkASSERT(false); + return 0; + } + } + } + return total_size; +} + +bool SkScalerContext_GDI::generatePath(SkGlyphID glyph, SkPath* path) { + SkASSERT(path); + SkASSERT(fDDC); + + path->reset(); + + // Out of all the fonts on a typical Windows box, + // 25% of glyphs require more than 2KB. + // 1% of glyphs require more than 4KB. + // 0.01% of glyphs require more than 8KB. + // 8KB is less than 1% of the normal 1MB stack on Windows. + // Note that some web fonts glyphs require more than 20KB. + //static const DWORD BUFFERSIZE = (1 << 13); + + //GDI only uses hinted outlines when axis aligned. + UINT format = GGO_NATIVE | GGO_GLYPH_INDEX; + if (fRec.getHinting() == SkFontHinting::kNone || fRec.getHinting() == SkFontHinting::kSlight){ + format |= GGO_UNHINTED; + } + SkAutoSTMalloc glyphbuf(BUFFERSIZE); + DWORD total_size = getGDIGlyphPath(glyph, format, &glyphbuf); + if (0 == total_size) { + return false; + } + + if (fRec.getHinting() != SkFontHinting::kSlight) { + sk_path_from_gdi_path(path, glyphbuf, total_size); + } else { + //GDI only uses hinted outlines when axis aligned. + UINT format = GGO_NATIVE | GGO_GLYPH_INDEX; + + SkAutoSTMalloc hintedGlyphbuf(BUFFERSIZE); + DWORD hinted_total_size = getGDIGlyphPath(glyph, format, &hintedGlyphbuf); + if (0 == hinted_total_size) { + return false; + } + + if (!sk_path_from_gdi_paths(path, glyphbuf, total_size, + GDIGlyphbufferPointIter(hintedGlyphbuf, hinted_total_size))) + { + path->reset(); + sk_path_from_gdi_path(path, glyphbuf, total_size); + } + } + return true; +} + +static void logfont_for_name(const char* familyName, LOGFONT* lf) { + sk_bzero(lf, sizeof(LOGFONT)); +#ifdef UNICODE + // Get the buffer size needed first. + size_t str_len = ::MultiByteToWideChar(CP_UTF8, 0, familyName, + -1, nullptr, 0); + // Allocate a buffer (str_len already has terminating null + // accounted for). + wchar_t *wideFamilyName = new wchar_t[str_len]; + // Now actually convert the string. + ::MultiByteToWideChar(CP_UTF8, 0, familyName, -1, + wideFamilyName, str_len); + ::wcsncpy(lf->lfFaceName, wideFamilyName, LF_FACESIZE - 1); + delete [] wideFamilyName; + lf->lfFaceName[LF_FACESIZE-1] = L'\0'; +#else + ::strncpy(lf->lfFaceName, familyName, LF_FACESIZE - 1); + lf->lfFaceName[LF_FACESIZE - 1] = '\0'; +#endif +} + +void LogFontTypeface::onGetFamilyName(SkString* familyName) const { + // Get the actual name of the typeface. The logfont may not know this. + SkAutoHDC hdc(fLogFont); + dcfontname_to_skstring(hdc, fLogFont, familyName); +} + +void LogFontTypeface::onGetFontDescriptor(SkFontDescriptor* desc, + bool* isLocalStream) const { + SkString familyName; + this->onGetFamilyName(&familyName); + desc->setFamilyName(familyName.c_str()); + desc->setStyle(this->fontStyle()); + *isLocalStream = this->fSerializeAsStream; +} + +void LogFontTypeface::getGlyphToUnicodeMap(SkUnichar* dstArray) const { + SkAutoHDC hdc(fLogFont); + unsigned int glyphCount = calculateGlyphCount(hdc, fLogFont); + populate_glyph_to_unicode(hdc, glyphCount, dstArray); +} + +std::unique_ptr LogFontTypeface::onGetAdvancedMetrics() const { + LOGFONT lf = fLogFont; + std::unique_ptr info(nullptr); + + // The design HFONT must be destroyed after the HDC + using HFONT_T = typename std::remove_pointer::type; + std::unique_ptr> designFont; + SkAutoHDC hdc(lf); + + const char stem_chars[] = {'i', 'I', '!', '1'}; + int16_t min_width; + unsigned glyphCount; + + // To request design units, create a logical font whose height is specified + // as unitsPerEm. + OUTLINETEXTMETRIC otm; + unsigned int otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm); + if (0 == otmRet) { + call_ensure_accessible(lf); + otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm); + } + if (!otmRet || !GetTextFace(hdc, LF_FACESIZE, lf.lfFaceName)) { + return info; + } + lf.lfHeight = -SkToS32(otm.otmEMSquare); + designFont.reset(CreateFontIndirect(&lf)); + SelectObject(hdc, designFont.get()); + if (!GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) { + return info; + } + glyphCount = calculateGlyphCount(hdc, fLogFont); + + info.reset(new SkAdvancedTypefaceMetrics); + tchar_to_skstring(lf.lfFaceName, &info->fFontName); + + SkOTTableOS2_V4::Type fsType; + if (sizeof(fsType) == this->getTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG), + offsetof(SkOTTableOS2_V4, fsType), + sizeof(fsType), + &fsType)) { + SkOTUtils::SetAdvancedTypefaceFlags(fsType, info.get()); + } else { + // If bit 1 is set, the font may not be embedded in a document. + // If bit 1 is clear, the font can be embedded. + // If bit 2 is set, the embedding is read-only. + if (otm.otmfsType & 0x1) { + info->fFlags |= SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag; + } + } + + if (glyphCount == 0 || (otm.otmTextMetrics.tmPitchAndFamily & TMPF_TRUETYPE) == 0) { + return info; + } + info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font; + + // If this bit is clear the font is a fixed pitch font. + if (!(otm.otmTextMetrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) { + info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style; + } + if (otm.otmTextMetrics.tmItalic) { + info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style; + } + if (otm.otmTextMetrics.tmPitchAndFamily & FF_ROMAN) { + info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style; + } else if (otm.otmTextMetrics.tmPitchAndFamily & FF_SCRIPT) { + info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style; + } + + // The main italic angle of the font, in tenths of a degree counterclockwise + // from vertical. + info->fItalicAngle = otm.otmItalicAngle / 10; + info->fAscent = SkToS16(otm.otmTextMetrics.tmAscent); + info->fDescent = SkToS16(-otm.otmTextMetrics.tmDescent); + // TODO(ctguil): Use alternate cap height calculation. + // MSDN says otmsCapEmHeight is not support but it is returning a value on + // my Win7 box. + info->fCapHeight = otm.otmsCapEmHeight; + info->fBBox = + SkIRect::MakeLTRB(otm.otmrcFontBox.left, otm.otmrcFontBox.top, + otm.otmrcFontBox.right, otm.otmrcFontBox.bottom); + + // Figure out a good guess for StemV - Min width of i, I, !, 1. + // This probably isn't very good with an italic font. + min_width = SHRT_MAX; + info->fStemV = 0; + for (size_t i = 0; i < SK_ARRAY_COUNT(stem_chars); i++) { + ABC abcWidths; + if (GetCharABCWidths(hdc, stem_chars[i], stem_chars[i], &abcWidths)) { + int16_t width = abcWidths.abcB; + if (width > 0 && width < min_width) { + min_width = width; + info->fStemV = min_width; + } + } + } + + return info; +} + +//Dummy representation of a Base64 encoded GUID from create_unique_font_name. +#define BASE64_GUID_ID "XXXXXXXXXXXXXXXXXXXXXXXX" +//Length of GUID representation from create_id, including nullptr terminator. +#define BASE64_GUID_ID_LEN SK_ARRAY_COUNT(BASE64_GUID_ID) + +static_assert(BASE64_GUID_ID_LEN < LF_FACESIZE, "GUID_longer_than_facesize"); + +/** + NameID 6 Postscript names cannot have the character '/'. + It would be easier to hex encode the GUID, but that is 32 bytes, + and many systems have issues with names longer than 28 bytes. + The following need not be any standard base64 encoding. + The encoded value is never decoded. +*/ +static const char postscript_safe_base64_encode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_="; + +/** + Formats a GUID into Base64 and places it into buffer. + buffer should have space for at least BASE64_GUID_ID_LEN characters. + The string will always be null terminated. + XXXXXXXXXXXXXXXXXXXXXXXX0 + */ +static void format_guid_b64(const GUID& guid, char* buffer, size_t bufferSize) { + SkASSERT(bufferSize >= BASE64_GUID_ID_LEN); + size_t written = SkBase64::Encode(&guid, sizeof(guid), buffer, postscript_safe_base64_encode); + SkASSERT(written < LF_FACESIZE); + buffer[written] = '\0'; +} + +/** + Creates a Base64 encoded GUID and places it into buffer. + buffer should have space for at least BASE64_GUID_ID_LEN characters. + The string will always be null terminated. + XXXXXXXXXXXXXXXXXXXXXXXX0 + */ +static HRESULT create_unique_font_name(char* buffer, size_t bufferSize) { + GUID guid = {}; + if (FAILED(CoCreateGuid(&guid))) { + return E_UNEXPECTED; + } + format_guid_b64(guid, buffer, bufferSize); + + return S_OK; +} + +/** + Introduces a font to GDI. On failure will return nullptr. The returned handle + should eventually be passed to RemoveFontMemResourceEx. +*/ +static HANDLE activate_font(SkData* fontData) { + DWORD numFonts = 0; + //AddFontMemResourceEx just copies the data, but does not specify const. + HANDLE fontHandle = AddFontMemResourceEx(const_cast(fontData->data()), + static_cast(fontData->size()), + 0, + &numFonts); + + if (fontHandle != nullptr && numFonts < 1) { + RemoveFontMemResourceEx(fontHandle); + return nullptr; + } + + return fontHandle; +} + +// Does not affect ownership of stream. +static sk_sp create_from_stream(std::unique_ptr stream) { + // Create a unique and unpredictable font name. + // Avoids collisions and access from CSS. + char familyName[BASE64_GUID_ID_LEN]; + const int familyNameSize = SK_ARRAY_COUNT(familyName); + if (FAILED(create_unique_font_name(familyName, familyNameSize))) { + return nullptr; + } + + // Change the name of the font. + sk_sp rewrittenFontData(SkOTUtils::RenameFont(stream.get(), familyName, familyNameSize-1)); + if (nullptr == rewrittenFontData.get()) { + return nullptr; + } + + // Register the font with GDI. + HANDLE fontReference = activate_font(rewrittenFontData.get()); + if (nullptr == fontReference) { + return nullptr; + } + + // Create the typeface. + LOGFONT lf; + logfont_for_name(familyName, &lf); + + return sk_sp(SkCreateFontMemResourceTypefaceFromLOGFONT(lf, fontReference)); +} + +std::unique_ptr LogFontTypeface::onOpenStream(int* ttcIndex) const { + *ttcIndex = 0; + + const DWORD kTTCTag = SkEndian_SwapBE32(SkSetFourByteTag('t', 't', 'c', 'f')); + LOGFONT lf = fLogFont; + + SkAutoHDC hdc(lf); + + std::unique_ptr stream; + DWORD tables[2] = {kTTCTag, 0}; + for (size_t i = 0; i < SK_ARRAY_COUNT(tables); i++) { + DWORD bufferSize = GetFontData(hdc, tables[i], 0, nullptr, 0); + if (bufferSize == GDI_ERROR) { + call_ensure_accessible(lf); + bufferSize = GetFontData(hdc, tables[i], 0, nullptr, 0); + } + if (bufferSize != GDI_ERROR) { + stream.reset(new SkMemoryStream(bufferSize)); + if (GetFontData(hdc, tables[i], 0, (void*)stream->getMemoryBase(), bufferSize)) { + break; + } else { + stream.reset(); + } + } + } + return stream; +} + +sk_sp LogFontTypeface::onMakeClone(const SkFontArguments& args) const { + return sk_ref_sp(this); +} + +static void bmpCharsToGlyphs(HDC hdc, const WCHAR* bmpChars, int count, uint16_t* glyphs, + bool Ox1FHack) +{ + // Type1 fonts fail with uniscribe API. Use GetGlyphIndices for plane 0. + + /** Real documentation for GetGlyphIndicesW: + * + * When GGI_MARK_NONEXISTING_GLYPHS is not specified and a character does not map to a + * glyph, then the 'default character's glyph is returned instead. The 'default character' + * is available in fTM.tmDefaultChar. FON fonts have a default character, and there exists + * a usDefaultChar in the 'OS/2' table, version 2 and later. If there is no + * 'default character' specified by the font, then often the first character found is used. + * + * When GGI_MARK_NONEXISTING_GLYPHS is specified and a character does not map to a glyph, + * then the glyph 0xFFFF is used. In Windows XP and earlier, Bitmap/Vector FON usually use + * glyph 0x1F instead ('Terminal' appears to be special, returning 0xFFFF). + * Type1 PFM/PFB, TT, OT TT, OT CFF all appear to use 0xFFFF, even on XP. + */ + DWORD result = GetGlyphIndicesW(hdc, bmpChars, count, glyphs, GGI_MARK_NONEXISTING_GLYPHS); + if (GDI_ERROR == result) { + for (int i = 0; i < count; ++i) { + glyphs[i] = 0; + } + return; + } + + if (Ox1FHack) { + for (int i = 0; i < count; ++i) { + if (0xFFFF == glyphs[i] || 0x1F == glyphs[i]) { + glyphs[i] = 0; + } + } + } else { + for (int i = 0; i < count; ++i) { + if (0xFFFF == glyphs[i]){ + glyphs[i] = 0; + } + } + } +} + +static uint16_t nonBmpCharToGlyph(HDC hdc, SCRIPT_CACHE* scriptCache, const WCHAR utf16[2]) { + uint16_t index = 0; + // Use uniscribe to detemine glyph index for non-BMP characters. + static const int numWCHAR = 2; + static const int maxItems = 2; + // MSDN states that this can be nullptr, but some things don't work then. + SCRIPT_CONTROL scriptControl; + memset(&scriptControl, 0, sizeof(scriptControl)); + // Add extra item to SCRIPT_ITEM to work around a bug (now documented). + // https://bugzilla.mozilla.org/show_bug.cgi?id=366643 + SCRIPT_ITEM si[maxItems + 1]; + int numItems; + HRZM(ScriptItemize(utf16, numWCHAR, maxItems, &scriptControl, nullptr, si, &numItems), + "Could not itemize character."); + + // Sometimes ScriptShape cannot find a glyph for a non-BMP and returns 2 space glyphs. + static const int maxGlyphs = 2; + SCRIPT_VISATTR vsa[maxGlyphs]; + WORD outGlyphs[maxGlyphs]; + WORD logClust[numWCHAR]; + int numGlyphs; + SCRIPT_ANALYSIS& script = si[0].a; + script.eScript = SCRIPT_UNDEFINED; + script.fRTL = FALSE; + script.fLayoutRTL = FALSE; + script.fLinkBefore = FALSE; + script.fLinkAfter = FALSE; + script.fLogicalOrder = FALSE; + script.fNoGlyphIndex = FALSE; + script.s.uBidiLevel = 0; + script.s.fOverrideDirection = 0; + script.s.fInhibitSymSwap = TRUE; + script.s.fCharShape = FALSE; + script.s.fDigitSubstitute = FALSE; + script.s.fInhibitLigate = FALSE; + script.s.fDisplayZWG = TRUE; + script.s.fArabicNumContext = FALSE; + script.s.fGcpClusters = FALSE; + script.s.fReserved = 0; + script.s.fEngineReserved = 0; + // For the future, 0x80040200 from here is USP_E_SCRIPT_NOT_IN_FONT + HRZM(ScriptShape(hdc, scriptCache, utf16, numWCHAR, maxGlyphs, &script, + outGlyphs, logClust, vsa, &numGlyphs), + "Could not shape character."); + if (1 == numGlyphs) { + index = outGlyphs[0]; + } + return index; +} + +void LogFontTypeface::onCharsToGlyphs(const SkUnichar* uni, int glyphCount, + SkGlyphID glyphs[]) const +{ + SkAutoHDC hdc(fLogFont); + + TEXTMETRIC tm; + if (0 == GetTextMetrics(hdc, &tm)) { + call_ensure_accessible(fLogFont); + if (0 == GetTextMetrics(hdc, &tm)) { + tm.tmPitchAndFamily = TMPF_TRUETYPE; + } + } + bool Ox1FHack = !(tm.tmPitchAndFamily & TMPF_VECTOR) /*&& winVer < Vista */; + + SCRIPT_CACHE sc = 0; + static const int scratchCount = 256; + WCHAR scratch[scratchCount]; + int glyphIndex = 0; + const uint32_t* utf32 = reinterpret_cast(uni); + while (glyphIndex < glyphCount) { + // Try a run of bmp. + int glyphsLeft = SkTMin(glyphCount - glyphIndex, scratchCount); + int runLength = 0; + while (runLength < glyphsLeft && utf32[glyphIndex + runLength] <= 0xFFFF) { + scratch[runLength] = static_cast(utf32[glyphIndex + runLength]); + ++runLength; + } + if (runLength) { + bmpCharsToGlyphs(hdc, scratch, runLength, &glyphs[glyphIndex], Ox1FHack); + glyphIndex += runLength; + } + + // Try a run of non-bmp. + while (glyphIndex < glyphCount && utf32[glyphIndex] > 0xFFFF) { + SkUTF::ToUTF16(utf32[glyphIndex], reinterpret_cast(scratch)); + glyphs[glyphIndex] = nonBmpCharToGlyph(hdc, &sc, scratch); + ++glyphIndex; + } + } + + if (sc) { + ::ScriptFreeCache(&sc); + } +} + +int LogFontTypeface::onCountGlyphs() const { + SkAutoHDC hdc(fLogFont); + return calculateGlyphCount(hdc, fLogFont); +} + +void LogFontTypeface::getPostScriptGlyphNames(SkString*) const {} + +int LogFontTypeface::onGetUPEM() const { + SkAutoHDC hdc(fLogFont); + return calculateUPEM(hdc, fLogFont); +} + +SkTypeface::LocalizedStrings* LogFontTypeface::onCreateFamilyNameIterator() const { + sk_sp nameIter = + SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*this); + if (!nameIter) { + SkString familyName; + this->getFamilyName(&familyName); + SkString language("und"); //undetermined + nameIter = sk_make_sp(familyName, language); + } + return nameIter.release(); +} + +int LogFontTypeface::onGetTableTags(SkFontTableTag tags[]) const { + SkSFNTHeader header; + if (sizeof(header) != this->onGetTableData(0, 0, sizeof(header), &header)) { + return 0; + } + + int numTables = SkEndian_SwapBE16(header.numTables); + + if (tags) { + size_t size = numTables * sizeof(SkSFNTHeader::TableDirectoryEntry); + SkAutoSTMalloc<0x20, SkSFNTHeader::TableDirectoryEntry> dir(numTables); + if (size != this->onGetTableData(0, sizeof(header), size, dir.get())) { + return 0; + } + + for (int i = 0; i < numTables; ++i) { + tags[i] = SkEndian_SwapBE32(dir[i].tag); + } + } + return numTables; +} + +size_t LogFontTypeface::onGetTableData(SkFontTableTag tag, size_t offset, + size_t length, void* data) const +{ + LOGFONT lf = fLogFont; + SkAutoHDC hdc(lf); + + tag = SkEndian_SwapBE32(tag); + if (nullptr == data) { + length = 0; + } + DWORD bufferSize = GetFontData(hdc, tag, (DWORD) offset, data, (DWORD) length); + if (bufferSize == GDI_ERROR) { + call_ensure_accessible(lf); + bufferSize = GetFontData(hdc, tag, (DWORD) offset, data, (DWORD) length); + } + return bufferSize == GDI_ERROR ? 0 : bufferSize; +} + +sk_sp LogFontTypeface::onCopyTableData(SkFontTableTag tag) const { + LOGFONT lf = fLogFont; + SkAutoHDC hdc(lf); + + tag = SkEndian_SwapBE32(tag); + DWORD size = GetFontData(hdc, tag, 0, nullptr, 0); + if (size == GDI_ERROR) { + call_ensure_accessible(lf); + size = GetFontData(hdc, tag, 0, nullptr, 0); + } + + sk_sp data; + if (size != GDI_ERROR) { + data = SkData::MakeUninitialized(size); + if (GetFontData(hdc, tag, 0, data->writable_data(), size) == GDI_ERROR) { + data.reset(); + } + } + return data; +} + +SkScalerContext* LogFontTypeface::onCreateScalerContext(const SkScalerContextEffects& effects, + const SkDescriptor* desc) const { + auto ctx = skstd::make_unique( + sk_ref_sp(const_cast(this)), effects, desc); + if (!ctx->isValid()) { + return nullptr; + } + return ctx.release(); +} + +void LogFontTypeface::onFilterRec(SkScalerContextRec* rec) const { + if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag || + rec->fFlags & SkScalerContext::kLCD_Vertical_Flag) + { + rec->fMaskFormat = SkMask::kA8_Format; + rec->fFlags |= SkScalerContext::kGenA8FromLCD_Flag; + } + + unsigned flagsWeDontSupport = SkScalerContext::kForceAutohinting_Flag | + SkScalerContext::kEmbeddedBitmapText_Flag | + SkScalerContext::kEmbolden_Flag | + SkScalerContext::kLCD_BGROrder_Flag | + SkScalerContext::kLCD_Vertical_Flag; + rec->fFlags &= ~flagsWeDontSupport; + + SkFontHinting h = rec->getHinting(); + switch (h) { + case SkFontHinting::kNone: + break; + case SkFontHinting::kSlight: + // Only do slight hinting when axis aligned. + // TODO: re-enable slight hinting when FontHostTest can pass. + //if (!isAxisAligned(*rec)) { + h = SkFontHinting::kNone; + //} + break; + case SkFontHinting::kNormal: + case SkFontHinting::kFull: + // TODO: need to be able to distinguish subpixel positioned glyphs + // and linear metrics. + //rec->fFlags &= ~SkScalerContext::kSubpixelPositioning_Flag; + h = SkFontHinting::kNormal; + break; + default: + SkDEBUGFAIL("unknown hinting"); + } + //TODO: if this is a bitmap font, squash hinting and subpixel. + rec->setHinting(h); + +// turn this off since GDI might turn A8 into BW! Need a bigger fix. +#if 0 + // Disable LCD when rotated, since GDI's output is ugly + if (isLCD(*rec) && !isAxisAligned(*rec)) { + rec->fMaskFormat = SkMask::kA8_Format; + } +#endif + + if (!fCanBeLCD && isLCD(*rec)) { + rec->fMaskFormat = SkMask::kA8_Format; + rec->fFlags &= ~SkScalerContext::kGenA8FromLCD_Flag; + } else if (rec->fMaskFormat == SkMask::kA8_Format) { + // Bug 1277404 + // If we have non LCD GDI text, render the fonts as cleartype and convert them + // to grayscale. This seems to be what Chrome and IE are doing on Windows 7. + // This also applies if cleartype is disabled system wide. + rec->fFlags |= SkScalerContext::kGenA8FromLCD_Flag; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/core/SkDataTable.h" +#include "include/core/SkFontMgr.h" + +static bool valid_logfont_for_enum(const LOGFONT& lf) { + // TODO: Vector FON is unsupported and should not be listed. + return + // Ignore implicit vertical variants. + lf.lfFaceName[0] && lf.lfFaceName[0] != '@' + + // DEFAULT_CHARSET is used to get all fonts, but also implies all + // character sets. Filter assuming all fonts support ANSI_CHARSET. + && ANSI_CHARSET == lf.lfCharSet + ; +} + +/** An EnumFontFamExProc implementation which interprets builderParam as + * an SkTDArray* and appends logfonts which + * pass the valid_logfont_for_enum predicate. + */ +static int CALLBACK enum_family_proc(const LOGFONT* lf, const TEXTMETRIC*, + DWORD fontType, LPARAM builderParam) { + if (valid_logfont_for_enum(*lf)) { + SkTDArray* array = (SkTDArray*)builderParam; + *array->append() = *(ENUMLOGFONTEX*)lf; + } + return 1; // non-zero means continue +} + +class SkFontStyleSetGDI : public SkFontStyleSet { +public: + SkFontStyleSetGDI(const TCHAR familyName[]) { + LOGFONT lf; + sk_bzero(&lf, sizeof(lf)); + lf.lfCharSet = DEFAULT_CHARSET; + _tcscpy_s(lf.lfFaceName, familyName); + + HDC hdc = ::CreateCompatibleDC(nullptr); + ::EnumFontFamiliesEx(hdc, &lf, enum_family_proc, (LPARAM)&fArray, 0); + ::DeleteDC(hdc); + } + + int count() override { + return fArray.count(); + } + + void getStyle(int index, SkFontStyle* fs, SkString* styleName) override { + if (fs) { + *fs = get_style(fArray[index].elfLogFont); + } + if (styleName) { + const ENUMLOGFONTEX& ref = fArray[index]; + // For some reason, ENUMLOGFONTEX and LOGFONT disagree on their type in the + // non-unicode version. + // ENUMLOGFONTEX uses BYTE + // LOGFONT uses CHAR + // Here we assert they that the style name is logically the same (size) as + // a TCHAR, so we can use the same converter function. + SkASSERT(sizeof(TCHAR) == sizeof(ref.elfStyle[0])); + tchar_to_skstring((const TCHAR*)ref.elfStyle, styleName); + } + } + + SkTypeface* createTypeface(int index) override { + return SkCreateTypefaceFromLOGFONT(fArray[index].elfLogFont); + } + + SkTypeface* matchStyle(const SkFontStyle& pattern) override { + return this->matchStyleCSS3(pattern); + } + +private: + SkTDArray fArray; +}; + +class SkFontMgrGDI : public SkFontMgr { +public: + SkFontMgrGDI() { + LOGFONT lf; + sk_bzero(&lf, sizeof(lf)); + lf.lfCharSet = DEFAULT_CHARSET; + + HDC hdc = ::CreateCompatibleDC(nullptr); + ::EnumFontFamiliesEx(hdc, &lf, enum_family_proc, (LPARAM)&fLogFontArray, 0); + ::DeleteDC(hdc); + } + +protected: + int onCountFamilies() const override { + return fLogFontArray.count(); + } + + void onGetFamilyName(int index, SkString* familyName) const override { + SkASSERT((unsigned)index < (unsigned)fLogFontArray.count()); + tchar_to_skstring(fLogFontArray[index].elfLogFont.lfFaceName, familyName); + } + + SkFontStyleSet* onCreateStyleSet(int index) const override { + SkASSERT((unsigned)index < (unsigned)fLogFontArray.count()); + return new SkFontStyleSetGDI(fLogFontArray[index].elfLogFont.lfFaceName); + } + + SkFontStyleSet* onMatchFamily(const char familyName[]) const override { + if (nullptr == familyName) { + familyName = ""; // do we need this check??? + } + LOGFONT lf; + logfont_for_name(familyName, &lf); + return new SkFontStyleSetGDI(lf.lfFaceName); + } + + virtual SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontstyle) const override { + // could be in base impl + sk_sp sset(this->matchFamily(familyName)); + return sset->matchStyle(fontstyle); + } + + virtual SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override { + return nullptr; + } + + virtual SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontstyle) const override { + // could be in base impl + SkString familyName; + ((LogFontTypeface*)familyMember)->getFamilyName(&familyName); + return this->matchFamilyStyle(familyName.c_str(), fontstyle); + } + + sk_sp onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const override { + if (ttcIndex != 0) { + return nullptr; + } + return create_from_stream(std::move(stream)); + } + + sk_sp onMakeFromData(sk_sp data, int ttcIndex) const override { + // could be in base impl + return this->makeFromStream(std::unique_ptr(new SkMemoryStream(std::move(data))), + ttcIndex); + } + + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override { + // could be in base impl + auto stream = SkStream::MakeFromFile(path); + return stream ? this->makeFromStream(std::move(stream), ttcIndex) : nullptr; + } + + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override { + LOGFONT lf; + if (nullptr == familyName) { + lf = get_default_font(); + } else { + logfont_for_name(familyName, &lf); + } + + lf.lfWeight = style.weight(); + lf.lfItalic = style.slant() == SkFontStyle::kUpright_Slant ? FALSE : TRUE; + return sk_sp(SkCreateTypefaceFromLOGFONT(lf)); + } + +private: + SkTDArray fLogFontArray; +}; + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkFontMgr_New_GDI() { return sk_make_sp(); } + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface.cpp b/gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface.cpp new file mode 100644 index 0000000000..23986cbaaa --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface.cpp @@ -0,0 +1,328 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkString.h" +#include "include/core/SkTypeface.h" +#include "include/ports/SkFontConfigInterface.h" +#include "include/ports/SkFontMgr_FontConfigInterface.h" +#include "include/private/SkMutex.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkResourceCache.h" +#include "src/core/SkTypefaceCache.h" +#include "src/ports/SkFontConfigTypeface.h" +#include + +std::unique_ptr SkTypeface_FCI::onOpenStream(int* ttcIndex) const { + *ttcIndex = this->getIdentity().fTTCIndex; + + if (fFontData) { + SkStreamAsset* stream = fFontData->getStream(); + if (!stream) { + return nullptr; + } + return stream->duplicate(); + } + + return std::unique_ptr(fFCI->openStream(this->getIdentity())); +} + +std::unique_ptr SkTypeface_FCI::onMakeFontData() const { + if (fFontData) { + return skstd::make_unique(*fFontData); + } + + const SkFontConfigInterface::FontIdentity& id = this->getIdentity(); + return skstd::make_unique(std::unique_ptr(fFCI->openStream(id)), + id.fTTCIndex, nullptr, 0); +} + +void SkTypeface_FCI::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocalStream) const { + SkString name; + this->getFamilyName(&name); + desc->setFamilyName(name.c_str()); + desc->setStyle(this->fontStyle()); + *isLocalStream = SkToBool(fFontData); +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkFontStyleSet_FCI : public SkFontStyleSet { +public: + SkFontStyleSet_FCI() {} + + int count() override { return 0; } + void getStyle(int index, SkFontStyle*, SkString* style) override { SkASSERT(false); } + SkTypeface* createTypeface(int index) override { SkASSERT(false); return nullptr; } + SkTypeface* matchStyle(const SkFontStyle& pattern) override { return nullptr; } +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkFontRequestCache { +public: + struct Request : public SkResourceCache::Key { + private: + Request(const char* name, size_t nameLen, const SkFontStyle& style) : fStyle(style) { + /** Pointer to just after the last field of this class. */ + char* content = const_cast(SkTAfter(&this->fStyle)); + + // No holes. + SkASSERT(SkTAddOffset(this, sizeof(SkResourceCache::Key) + keySize) == content); + + // Has a size divisible by size of uint32_t. + SkASSERT((content - reinterpret_cast(this)) % sizeof(uint32_t) == 0); + + size_t contentLen = SkAlign4(nameLen); + sk_careful_memcpy(content, name, nameLen); + sk_bzero(content + nameLen, contentLen - nameLen); + this->init(nullptr, 0, keySize + contentLen); + } + const SkFontStyle fStyle; + /** The sum of the sizes of the fields of this class. */ + static const size_t keySize = sizeof(fStyle); + + public: + static Request* Create(const char* name, const SkFontStyle& style) { + size_t nameLen = name ? strlen(name) : 0; + size_t contentLen = SkAlign4(nameLen); + char* storage = new char[sizeof(Request) + contentLen]; + return new (storage) Request(name, nameLen, style); + } + void operator delete(void* storage) { + delete[] reinterpret_cast(storage); + } + }; + + +private: + struct Result : public SkResourceCache::Rec { + Result(Request* request, sk_sp typeface) + : fRequest(request), fFace(std::move(typeface)) {} + Result(Result&&) = default; + Result& operator=(Result&&) = default; + + const Key& getKey() const override { return *fRequest; } + size_t bytesUsed() const override { return fRequest->size() + sizeof(fFace); } + const char* getCategory() const override { return "request_cache"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; } + + std::unique_ptr fRequest; + sk_sp fFace; + }; + + SkResourceCache fCachedResults; + +public: + SkFontRequestCache(size_t maxSize) : fCachedResults(maxSize) {} + + /** Takes ownership of request. It will be deleted when no longer needed. */ + void add(sk_sp face, Request* request) { + fCachedResults.add(new Result(request, std::move(face))); + } + /** Does not take ownership of request. */ + sk_sp findAndRef(Request* request) { + sk_sp face; + fCachedResults.find(*request, [](const SkResourceCache::Rec& rec, void* context) -> bool { + const Result& result = static_cast(rec); + sk_sp* face = static_cast*>(context); + + *face = result.fFace; + return true; + }, &face); + return face; + } +}; + +/////////////////////////////////////////////////////////////////////////////// + +static bool find_by_FontIdentity(SkTypeface* cachedTypeface, void* ctx) { + typedef SkFontConfigInterface::FontIdentity FontIdentity; + SkTypeface_FCI* cachedFCTypeface = static_cast(cachedTypeface); + FontIdentity* identity = static_cast(ctx); + + return cachedFCTypeface->getIdentity() == *identity; +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkFontMgr_FCI : public SkFontMgr { + sk_sp fFCI; + SkTypeface_FreeType::Scanner fScanner; + + mutable SkMutex fMutex; + mutable SkTypefaceCache fTFCache; + + // The value of maxSize here is a compromise between cache hits and cache size. + // See https://crbug.com/424082#63 for reason for current size. + static const size_t kMaxSize = 1 << 15; + mutable SkFontRequestCache fCache; + +public: + SkFontMgr_FCI(sk_sp fci) + : fFCI(std::move(fci)) + , fCache(kMaxSize) + {} + +protected: + int onCountFamilies() const override { + SK_ABORT("Not implemented."); + } + + void onGetFamilyName(int index, SkString* familyName) const override { + SK_ABORT("Not implemented."); + } + + SkFontStyleSet* onCreateStyleSet(int index) const override { + SK_ABORT("Not implemented."); + } + + SkFontStyleSet* onMatchFamily(const char familyName[]) const override { + SK_ABORT("Not implemented."); + } + + SkTypeface* onMatchFamilyStyle(const char requestedFamilyName[], + const SkFontStyle& requestedStyle) const override + { + SkAutoMutexExclusive ama(fMutex); + + SkFontConfigInterface::FontIdentity identity; + SkString outFamilyName; + SkFontStyle outStyle; + if (!fFCI->matchFamilyName(requestedFamilyName, requestedStyle, + &identity, &outFamilyName, &outStyle)) + { + return nullptr; + } + + // Check if a typeface with this FontIdentity is already in the FontIdentity cache. + sk_sp face = fTFCache.findByProcAndRef(find_by_FontIdentity, &identity); + if (!face) { + face.reset(SkTypeface_FCI::Create(fFCI, identity, std::move(outFamilyName), outStyle)); + // Add this FontIdentity to the FontIdentity cache. + fTFCache.add(face); + } + return face.release(); + } + + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override { + SK_ABORT("Not implemented."); + } + + SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override { + SK_ABORT("Not implemented."); + } + + sk_sp onMakeFromData(sk_sp data, int ttcIndex) const override { + return this->onMakeFromStreamIndex(SkMemoryStream::Make(std::move(data)), ttcIndex); + } + + sk_sp onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const override { + const size_t length = stream->getLength(); + if (!length) { + return nullptr; + } + if (length >= 1024 * 1024 * 1024) { + return nullptr; // don't accept too large fonts (>= 1GB) for safety. + } + + // TODO should the caller give us the style or should we get it from freetype? + SkString name; + SkFontStyle style; + bool isFixedPitch = false; + if (!fScanner.scanFont(stream.get(), 0, &name, &style, &isFixedPitch, nullptr)) { + return nullptr; + } + + auto fontData = skstd::make_unique(std::move(stream), ttcIndex, nullptr, 0); + return sk_sp(SkTypeface_FCI::Create(std::move(fontData), std::move(name), + style, isFixedPitch)); + } + + sk_sp onMakeFromStreamArgs(std::unique_ptr stream, + const SkFontArguments& args) const override { + using Scanner = SkTypeface_FreeType::Scanner; + const size_t length = stream->getLength(); + if (!length) { + return nullptr; + } + if (length >= 1024 * 1024 * 1024) { + return nullptr; // don't accept too large fonts (>= 1GB) for safety. + } + + bool isFixedPitch; + SkFontStyle style; + SkString name; + Scanner::AxisDefinitions axisDefinitions; + if (!fScanner.scanFont(stream.get(), args.getCollectionIndex(), + &name, &style, &isFixedPitch, &axisDefinitions)) + { + return nullptr; + } + + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + Scanner::computeAxisValues(axisDefinitions, args.getVariationDesignPosition(), + axisValues, name); + + auto fontData = skstd::make_unique(std::move(stream), + args.getCollectionIndex(), + axisValues.get(), + axisDefinitions.count()); + return sk_sp(SkTypeface_FCI::Create(std::move(fontData), std::move(name), + style, isFixedPitch)); + } + + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override { + std::unique_ptr stream = SkStream::MakeFromFile(path); + return stream ? this->makeFromStream(std::move(stream), ttcIndex) : nullptr; + } + + sk_sp onLegacyMakeTypeface(const char requestedFamilyName[], + SkFontStyle requestedStyle) const override + { + SkAutoMutexExclusive ama(fMutex); + + // Check if this request is already in the request cache. + using Request = SkFontRequestCache::Request; + std::unique_ptr request(Request::Create(requestedFamilyName, requestedStyle)); + sk_sp face = fCache.findAndRef(request.get()); + if (face) { + return sk_sp(face); + } + + SkFontConfigInterface::FontIdentity identity; + SkString outFamilyName; + SkFontStyle outStyle; + if (!fFCI->matchFamilyName(requestedFamilyName, requestedStyle, + &identity, &outFamilyName, &outStyle)) + { + return nullptr; + } + + // Check if a typeface with this FontIdentity is already in the FontIdentity cache. + face = fTFCache.findByProcAndRef(find_by_FontIdentity, &identity); + if (!face) { + face.reset(SkTypeface_FCI::Create(fFCI, identity, std::move(outFamilyName), outStyle)); + // Add this FontIdentity to the FontIdentity cache. + fTFCache.add(face); + } + // Add this request to the request cache. + fCache.add(face, request.release()); + + return face; + } +}; + +SK_API sk_sp SkFontMgr_New_FCI(sk_sp fci) { + SkASSERT(fci); + return sk_make_sp(std::move(fci)); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface_factory.cpp new file mode 100644 index 0000000000..cb64ec1ed4 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_FontConfigInterface_factory.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2008 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/ports/SkFontConfigInterface.h" +#include "include/ports/SkFontMgr_FontConfigInterface.h" + +sk_sp SkFontMgr::Factory() { + sk_sp fci(SkFontConfigInterface::RefGlobal()); + if (!fci) { + return nullptr; + } + return SkFontMgr_New_FCI(std::move(fci)); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_android.cpp b/gfx/skia/skia/src/ports/SkFontMgr_android.cpp new file mode 100644 index 0000000000..5642402750 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_android.cpp @@ -0,0 +1,598 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#include "include/core/SkData.h" +#include "include/core/SkFontMgr.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkPaint.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/ports/SkFontMgr_android.h" +#include "include/private/SkFixed.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkOSFile.h" +#include "src/core/SkTSearch.h" +#include "src/core/SkTypefaceCache.h" +#include "src/ports/SkFontHost_FreeType_common.h" +#include "src/ports/SkFontMgr_android_parser.h" + +#include +#include + +class SkData; + +class SkTypeface_Android : public SkTypeface_FreeType { +public: + SkTypeface_Android(const SkFontStyle& style, + bool isFixedPitch, + const SkString& familyName) + : INHERITED(style, isFixedPitch) + , fFamilyName(familyName) + { } + +protected: + void onGetFamilyName(SkString* familyName) const override { + *familyName = fFamilyName; + } + + SkString fFamilyName; + +private: + typedef SkTypeface_FreeType INHERITED; +}; + +class SkTypeface_AndroidSystem : public SkTypeface_Android { +public: + SkTypeface_AndroidSystem(const SkString& pathName, + const bool cacheFontFiles, + int index, + const SkFixed* axes, int axesCount, + const SkFontStyle& style, + bool isFixedPitch, + const SkString& familyName, + const SkTArray& lang, + FontVariant variantStyle) + : INHERITED(style, isFixedPitch, familyName) + , fPathName(pathName) + , fIndex(index) + , fAxes(axes, axesCount) + , fLang(lang) + , fVariantStyle(variantStyle) + , fFile(cacheFontFiles ? sk_fopen(fPathName.c_str(), kRead_SkFILE_Flag) : nullptr) { + if (cacheFontFiles) { + SkASSERT(fFile); + } + } + + std::unique_ptr makeStream() const { + if (fFile) { + sk_sp data(SkData::MakeFromFILE(fFile)); + return data ? skstd::make_unique(std::move(data)) : nullptr; + } + return SkStream::MakeFromFile(fPathName.c_str()); + } + + virtual void onGetFontDescriptor(SkFontDescriptor* desc, bool* serialize) const override { + SkASSERT(desc); + SkASSERT(serialize); + desc->setFamilyName(fFamilyName.c_str()); + desc->setStyle(this->fontStyle()); + *serialize = false; + } + std::unique_ptr onOpenStream(int* ttcIndex) const override { + *ttcIndex = fIndex; + return this->makeStream(); + } + std::unique_ptr onMakeFontData() const override { + return skstd::make_unique(this->makeStream(), fIndex, + fAxes.begin(), fAxes.count()); + } + sk_sp onMakeClone(const SkFontArguments& args) const override { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + return sk_make_sp(fPathName, + fFile, + fIndex, + data->getAxis(), + data->getAxisCount(), + this->fontStyle(), + this->isFixedPitch(), + fFamilyName, + fLang, + fVariantStyle); + } + + const SkString fPathName; + int fIndex; + const SkSTArray<4, SkFixed, true> fAxes; + const SkSTArray<4, SkLanguage, true> fLang; + const FontVariant fVariantStyle; + SkAutoTCallVProc fFile; + + typedef SkTypeface_Android INHERITED; +}; + +class SkTypeface_AndroidStream : public SkTypeface_Android { +public: + SkTypeface_AndroidStream(std::unique_ptr data, + const SkFontStyle& style, + bool isFixedPitch, + const SkString& familyName) + : INHERITED(style, isFixedPitch, familyName) + , fData(std::move(data)) + { } + + virtual void onGetFontDescriptor(SkFontDescriptor* desc, + bool* serialize) const override { + SkASSERT(desc); + SkASSERT(serialize); + desc->setFamilyName(fFamilyName.c_str()); + *serialize = true; + } + + std::unique_ptr onOpenStream(int* ttcIndex) const override { + *ttcIndex = fData->getIndex(); + return fData->getStream()->duplicate(); + } + + std::unique_ptr onMakeFontData() const override { + return skstd::make_unique(*fData); + } + + sk_sp onMakeClone(const SkFontArguments& args) const override { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + return sk_make_sp(std::move(data), + this->fontStyle(), + this->isFixedPitch(), + fFamilyName); + } + +private: + const std::unique_ptr fData; + typedef SkTypeface_Android INHERITED; +}; + +class SkFontStyleSet_Android : public SkFontStyleSet { + typedef SkTypeface_FreeType::Scanner Scanner; + +public: + explicit SkFontStyleSet_Android(const FontFamily& family, const Scanner& scanner, + const bool cacheFontFiles) { + const SkString* cannonicalFamilyName = nullptr; + if (family.fNames.count() > 0) { + cannonicalFamilyName = &family.fNames[0]; + } + fFallbackFor = family.fFallbackFor; + + // TODO? make this lazy + for (int i = 0; i < family.fFonts.count(); ++i) { + const FontFileInfo& fontFile = family.fFonts[i]; + + SkString pathName(family.fBasePath); + pathName.append(fontFile.fFileName); + + std::unique_ptr stream = SkStream::MakeFromFile(pathName.c_str()); + if (!stream) { + SkDEBUGF("Requested font file %s does not exist or cannot be opened.\n", + pathName.c_str()); + continue; + } + + const int ttcIndex = fontFile.fIndex; + SkString familyName; + SkFontStyle style; + bool isFixedWidth; + Scanner::AxisDefinitions axisDefinitions; + if (!scanner.scanFont(stream.get(), ttcIndex, + &familyName, &style, &isFixedWidth, &axisDefinitions)) + { + SkDEBUGF("Requested font file %s exists, but is not a valid font.\n", + pathName.c_str()); + continue; + } + + int weight = fontFile.fWeight != 0 ? fontFile.fWeight : style.weight(); + SkFontStyle::Slant slant = style.slant(); + switch (fontFile.fStyle) { + case FontFileInfo::Style::kAuto: slant = style.slant(); break; + case FontFileInfo::Style::kNormal: slant = SkFontStyle::kUpright_Slant; break; + case FontFileInfo::Style::kItalic: slant = SkFontStyle::kItalic_Slant; break; + default: SkASSERT(false); break; + } + style = SkFontStyle(weight, style.width(), slant); + + uint32_t variant = family.fVariant; + if (kDefault_FontVariant == variant) { + variant = kCompact_FontVariant | kElegant_FontVariant; + } + + // The first specified family name overrides the family name found in the font. + // TODO: SkTypeface_AndroidSystem::onCreateFamilyNameIterator should return + // all of the specified family names in addition to the names found in the font. + if (cannonicalFamilyName != nullptr) { + familyName = *cannonicalFamilyName; + } + + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + SkFontArguments::VariationPosition position = { + fontFile.fVariationDesignPosition.begin(), + fontFile.fVariationDesignPosition.count() + }; + Scanner::computeAxisValues(axisDefinitions, position, + axisValues, familyName); + + fStyles.push_back().reset(new SkTypeface_AndroidSystem( + pathName, cacheFontFiles, ttcIndex, axisValues.get(), axisDefinitions.count(), + style, isFixedWidth, familyName, family.fLanguages, variant)); + } + } + + int count() override { + return fStyles.count(); + } + void getStyle(int index, SkFontStyle* style, SkString* name) override { + if (index < 0 || fStyles.count() <= index) { + return; + } + if (style) { + *style = fStyles[index]->fontStyle(); + } + if (name) { + name->reset(); + } + } + SkTypeface_AndroidSystem* createTypeface(int index) override { + if (index < 0 || fStyles.count() <= index) { + return nullptr; + } + return SkRef(fStyles[index].get()); + } + + SkTypeface_AndroidSystem* matchStyle(const SkFontStyle& pattern) override { + return static_cast(this->matchStyleCSS3(pattern)); + } + +private: + SkTArray> fStyles; + SkString fFallbackFor; + + friend struct NameToFamily; + friend class SkFontMgr_Android; + + typedef SkFontStyleSet INHERITED; +}; + +/** On Android a single family can have many names, but our API assumes unique names. + * Map names to the back end so that all names for a given family refer to the same + * (non-replicated) set of typefaces. + * SkTDict<> doesn't let us do index-based lookup, so we write our own mapping. + */ +struct NameToFamily { + SkString name; + SkFontStyleSet_Android* styleSet; +}; + +class SkFontMgr_Android : public SkFontMgr { +public: + SkFontMgr_Android(const SkFontMgr_Android_CustomFonts* custom) { + SkTDArray families; + if (custom && SkFontMgr_Android_CustomFonts::kPreferSystem != custom->fSystemFontUse) { + SkString base(custom->fBasePath); + SkFontMgr_Android_Parser::GetCustomFontFamilies( + families, base, custom->fFontsXml, custom->fFallbackFontsXml); + } + if (!custom || + (custom && SkFontMgr_Android_CustomFonts::kOnlyCustom != custom->fSystemFontUse)) + { + SkFontMgr_Android_Parser::GetSystemFontFamilies(families); + } + if (custom && SkFontMgr_Android_CustomFonts::kPreferSystem == custom->fSystemFontUse) { + SkString base(custom->fBasePath); + SkFontMgr_Android_Parser::GetCustomFontFamilies( + families, base, custom->fFontsXml, custom->fFallbackFontsXml); + } + this->buildNameToFamilyMap(families, custom ? custom->fIsolated : false); + this->findDefaultStyleSet(); + families.deleteAll(); + } + +protected: + /** Returns not how many families we have, but how many unique names + * exist among the families. + */ + int onCountFamilies() const override { + return fNameToFamilyMap.count(); + } + + void onGetFamilyName(int index, SkString* familyName) const override { + if (index < 0 || fNameToFamilyMap.count() <= index) { + familyName->reset(); + return; + } + familyName->set(fNameToFamilyMap[index].name); + } + + SkFontStyleSet* onCreateStyleSet(int index) const override { + if (index < 0 || fNameToFamilyMap.count() <= index) { + return nullptr; + } + return SkRef(fNameToFamilyMap[index].styleSet); + } + + SkFontStyleSet* onMatchFamily(const char familyName[]) const override { + if (!familyName) { + return nullptr; + } + SkAutoAsciiToLC tolc(familyName); + for (int i = 0; i < fNameToFamilyMap.count(); ++i) { + if (fNameToFamilyMap[i].name.equals(tolc.lc())) { + return SkRef(fNameToFamilyMap[i].styleSet); + } + } + // TODO: eventually we should not need to name fallback families. + for (int i = 0; i < fFallbackNameToFamilyMap.count(); ++i) { + if (fFallbackNameToFamilyMap[i].name.equals(tolc.lc())) { + return SkRef(fFallbackNameToFamilyMap[i].styleSet); + } + } + return nullptr; + } + + virtual SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& style) const override { + sk_sp sset(this->matchFamily(familyName)); + return sset->matchStyle(style); + } + + virtual SkTypeface* onMatchFaceStyle(const SkTypeface* typeface, + const SkFontStyle& style) const override { + for (int i = 0; i < fStyleSets.count(); ++i) { + for (int j = 0; j < fStyleSets[i]->fStyles.count(); ++j) { + if (fStyleSets[i]->fStyles[j].get() == typeface) { + return fStyleSets[i]->matchStyle(style); + } + } + } + return nullptr; + } + + static sk_sp find_family_style_character( + const SkString& familyName, + const SkTArray& fallbackNameToFamilyMap, + const SkFontStyle& style, bool elegant, + const SkString& langTag, SkUnichar character) + { + for (int i = 0; i < fallbackNameToFamilyMap.count(); ++i) { + SkFontStyleSet_Android* family = fallbackNameToFamilyMap[i].styleSet; + if (familyName != family->fFallbackFor) { + continue; + } + sk_sp face(family->matchStyle(style)); + + if (!langTag.isEmpty() && + std::none_of(face->fLang.begin(), face->fLang.end(), [&](SkLanguage lang){ + return lang.getTag().startsWith(langTag.c_str()); + })) + { + continue; + } + + if (SkToBool(face->fVariantStyle & kElegant_FontVariant) != elegant) { + continue; + } + + if (face->unicharToGlyph(character) != 0) { + return face; + } + } + return nullptr; + } + + virtual SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const override + { + // The variant 'elegant' is 'not squashed', 'compact' is 'stays in ascent/descent'. + // The variant 'default' means 'compact and elegant'. + // As a result, it is not possible to know the variant context from the font alone. + // TODO: add 'is_elegant' and 'is_compact' bits to 'style' request. + + SkString familyNameString(familyName); + for (const SkString& currentFamilyName : { familyNameString, SkString() }) { + // The first time match anything elegant, second time anything not elegant. + for (int elegant = 2; elegant --> 0;) { + for (int bcp47Index = bcp47Count; bcp47Index --> 0;) { + SkLanguage lang(bcp47[bcp47Index]); + while (!lang.getTag().isEmpty()) { + sk_sp matchingTypeface = + find_family_style_character(currentFamilyName, fFallbackNameToFamilyMap, + style, SkToBool(elegant), + lang.getTag(), character); + if (matchingTypeface) { + return matchingTypeface.release(); + } + + lang = lang.getParent(); + } + } + sk_sp matchingTypeface = + find_family_style_character(currentFamilyName, fFallbackNameToFamilyMap, + style, SkToBool(elegant), + SkString(), character); + if (matchingTypeface) { + return matchingTypeface.release(); + } + } + } + return nullptr; + } + + sk_sp onMakeFromData(sk_sp data, int ttcIndex) const override { + return this->makeFromStream(std::unique_ptr(new SkMemoryStream(std::move(data))), + ttcIndex); + } + + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override { + std::unique_ptr stream = SkStream::MakeFromFile(path); + return stream.get() ? this->makeFromStream(std::move(stream), ttcIndex) : nullptr; + } + + sk_sp onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const override { + bool isFixedPitch; + SkFontStyle style; + SkString name; + if (!fScanner.scanFont(stream.get(), ttcIndex, &name, &style, &isFixedPitch, nullptr)) { + return nullptr; + } + auto data = skstd::make_unique(std::move(stream), ttcIndex, nullptr, 0); + return sk_sp(new SkTypeface_AndroidStream(std::move(data), + style, isFixedPitch, name)); + } + + sk_sp onMakeFromStreamArgs(std::unique_ptr stream, + const SkFontArguments& args) const override { + using Scanner = SkTypeface_FreeType::Scanner; + bool isFixedPitch; + SkFontStyle style; + SkString name; + Scanner::AxisDefinitions axisDefinitions; + if (!fScanner.scanFont(stream.get(), args.getCollectionIndex(), + &name, &style, &isFixedPitch, &axisDefinitions)) + { + return nullptr; + } + + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + Scanner::computeAxisValues(axisDefinitions, args.getVariationDesignPosition(), + axisValues, name); + + auto data = skstd::make_unique(std::move(stream), args.getCollectionIndex(), + axisValues.get(), axisDefinitions.count()); + return sk_sp(new SkTypeface_AndroidStream(std::move(data), + style, isFixedPitch, name)); + } + + sk_sp onMakeFromFontData(std::unique_ptr data) const override { + SkStreamAsset* stream(data->getStream()); + bool isFixedPitch; + SkFontStyle style; + SkString name; + if (!fScanner.scanFont(stream, data->getIndex(), &name, &style, &isFixedPitch, nullptr)) { + return nullptr; + } + return sk_sp(new SkTypeface_AndroidStream(std::move(data), + style, isFixedPitch, name)); + } + + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override { + if (familyName) { + // On Android, we must return nullptr when we can't find the requested + // named typeface so that the system/app can provide their own recovery + // mechanism. On other platforms we'd provide a typeface from the + // default family instead. + return sk_sp(this->onMatchFamilyStyle(familyName, style)); + } + return sk_sp(fDefaultStyleSet->matchStyle(style)); + } + + +private: + + SkTypeface_FreeType::Scanner fScanner; + + SkTArray> fStyleSets; + sk_sp fDefaultStyleSet; + + SkTArray fNameToFamilyMap; + SkTArray fFallbackNameToFamilyMap; + + void addFamily(FontFamily& family, const bool isolated, int familyIndex) { + SkTArray* nameToFamily = &fNameToFamilyMap; + if (family.fIsFallbackFont) { + nameToFamily = &fFallbackNameToFamilyMap; + + if (0 == family.fNames.count()) { + SkString& fallbackName = family.fNames.push_back(); + fallbackName.printf("%.2x##fallback", familyIndex); + } + } + + sk_sp newSet = + sk_make_sp(family, fScanner, isolated); + if (0 == newSet->count()) { + return; + } + + for (const SkString& name : family.fNames) { + nameToFamily->emplace_back(NameToFamily{name, newSet.get()}); + } + fStyleSets.emplace_back(std::move(newSet)); + } + void buildNameToFamilyMap(SkTDArray families, const bool isolated) { + int familyIndex = 0; + for (FontFamily* family : families) { + addFamily(*family, isolated, familyIndex++); + family->fallbackFamilies.foreach([this, isolated, &familyIndex] + (SkString, std::unique_ptr* fallbackFamily) { + addFamily(*(*fallbackFamily).get(), isolated, familyIndex++); + } + ); + } + } + + void findDefaultStyleSet() { + SkASSERT(!fStyleSets.empty()); + + static const char* defaultNames[] = { "sans-serif" }; + for (const char* defaultName : defaultNames) { + fDefaultStyleSet.reset(this->onMatchFamily(defaultName)); + if (fDefaultStyleSet) { + break; + } + } + if (nullptr == fDefaultStyleSet) { + fDefaultStyleSet = fStyleSets[0]; + } + SkASSERT(fDefaultStyleSet); + } + + typedef SkFontMgr INHERITED; +}; + +#ifdef SK_DEBUG +static char const * const gSystemFontUseStrings[] = { + "OnlyCustom", "PreferCustom", "PreferSystem" +}; +#endif + +sk_sp SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts* custom) { + if (custom) { + SkASSERT(0 <= custom->fSystemFontUse); + SkASSERT(custom->fSystemFontUse < SK_ARRAY_COUNT(gSystemFontUseStrings)); + SkDEBUGF("SystemFontUse: %s BasePath: %s Fonts: %s FallbackFonts: %s\n", + gSystemFontUseStrings[custom->fSystemFontUse], + custom->fBasePath, + custom->fFontsXml, + custom->fFallbackFontsXml); + } + return sk_make_sp(custom); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_android_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_android_factory.cpp new file mode 100644 index 0000000000..9ec10a9eb9 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_android_factory.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_ANDROID) + +#include "include/core/SkFontMgr.h" +#include "include/ports/SkFontMgr_android.h" + +sk_sp SkFontMgr::Factory() { + return SkFontMgr_New_Android(nullptr); +} + +#endif//defined(SK_BUILD_FOR_ANDROID) diff --git a/gfx/skia/skia/src/ports/SkFontMgr_android_parser.cpp b/gfx/skia/skia/src/ports/SkFontMgr_android_parser.cpp new file mode 100644 index 0000000000..6f4ec7632b --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_android_parser.cpp @@ -0,0 +1,836 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Despite the name and location, this is portable code. + +#include "include/core/SkFontMgr.h" +#include "include/core/SkStream.h" +#include "include/private/SkFixed.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTLogic.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkOSFile.h" +#include "src/core/SkTSearch.h" +#include "src/ports/SkFontMgr_android_parser.h" + +#include + +#include +#include + +#define LMP_SYSTEM_FONTS_FILE "/system/etc/fonts.xml" +#define OLD_SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml" +#define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml" +#define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml" + +#define LOCALE_FALLBACK_FONTS_SYSTEM_DIR "/system/etc" +#define LOCALE_FALLBACK_FONTS_VENDOR_DIR "/vendor/etc" +#define LOCALE_FALLBACK_FONTS_PREFIX "fallback_fonts-" +#define LOCALE_FALLBACK_FONTS_SUFFIX ".xml" + +#ifndef SK_FONT_FILE_PREFIX +# define SK_FONT_FILE_PREFIX "/fonts/" +#endif + +/** + * This file contains TWO 'familyset' handlers: + * One for JB and earlier which works with + * /system/etc/system_fonts.xml + * /system/etc/fallback_fonts.xml + * /vendor/etc/fallback_fonts.xml + * /system/etc/fallback_fonts-XX.xml + * /vendor/etc/fallback_fonts-XX.xml + * and the other for LMP and later which works with + * /system/etc/fonts.xml + * + * If the 'familyset' 'version' attribute is 21 or higher the LMP parser is used, otherwise the JB. + */ + +struct FamilyData; + +struct TagHandler { + /** Called at the start tag. + * Called immediately after the parent tag retuns this handler from a call to 'tag'. + * Allows setting up for handling the tag content and processing attributes. + * If nullptr, will not be called. + */ + void (*start)(FamilyData* data, const char* tag, const char** attributes); + + /** Called at the end tag. + * Allows post-processing of any accumulated information. + * This will be the last call made in relation to the current tag. + * If nullptr, will not be called. + */ + void (*end)(FamilyData* data, const char* tag); + + /** Called when a nested tag is encountered. + * This is responsible for determining how to handle the tag. + * If the tag is not recognized, return nullptr to skip the tag. + * If nullptr, all nested tags will be skipped. + */ + const TagHandler* (*tag)(FamilyData* data, const char* tag, const char** attributes); + + /** The character handler for this tag. + * This is only active for character data contained directly in this tag (not sub-tags). + * The first parameter will be castable to a FamilyData*. + * If nullptr, any character data in this tag will be ignored. + */ + XML_CharacterDataHandler chars; +}; + +/** Represents the current parsing state. */ +struct FamilyData { + FamilyData(XML_Parser parser, SkTDArray& families, + const SkString& basePath, bool isFallback, const char* filename, + const TagHandler* topLevelHandler) + : fParser(parser) + , fFamilies(families) + , fCurrentFamily(nullptr) + , fCurrentFontInfo(nullptr) + , fVersion(0) + , fBasePath(basePath) + , fIsFallback(isFallback) + , fFilename(filename) + , fDepth(1) + , fSkip(0) + , fHandler(&topLevelHandler, 1) + { } + + XML_Parser fParser; // The expat parser doing the work, owned by caller + SkTDArray& fFamilies; // The array to append families, owned by caller + std::unique_ptr fCurrentFamily; // The family being created, owned by this + FontFileInfo* fCurrentFontInfo; // The info being created, owned by fCurrentFamily + int fVersion; // The version of the file parsed. + const SkString& fBasePath; // The current base path. + const bool fIsFallback; // The file being parsed is a fallback file + const char* fFilename; // The name of the file currently being parsed. + + int fDepth; // The current element depth of the parse. + int fSkip; // The depth to stop skipping, 0 if not skipping. + SkTDArray fHandler; // The stack of current tag handlers. +}; + +static bool memeq(const char* s1, const char* s2, size_t n1, size_t n2) { + return n1 == n2 && 0 == memcmp(s1, s2, n1); +} +#define MEMEQ(c, s, n) memeq(c, s, sizeof(c) - 1, n) + +#define ATTS_NON_NULL(a, i) (a[i] != nullptr && a[i+1] != nullptr) + +#define SK_FONTMGR_ANDROID_PARSER_PREFIX "[SkFontMgr Android Parser] " + +#define SK_FONTCONFIGPARSER_WARNING(message, ...) \ + SkDebugf(SK_FONTMGR_ANDROID_PARSER_PREFIX "%s:%d:%d: warning: " message "\n", self->fFilename, \ + XML_GetCurrentLineNumber(self->fParser), XML_GetCurrentColumnNumber(self->fParser), \ + ##__VA_ARGS__) + +static bool is_whitespace(char c) { + return c == ' ' || c == '\n'|| c == '\r' || c == '\t'; +} + +static void trim_string(SkString* s) { + char* str = s->writable_str(); + const char* start = str; // start is inclusive + const char* end = start + s->size(); // end is exclusive + while (is_whitespace(*start)) { ++start; } + if (start != end) { + --end; // make end inclusive + while (is_whitespace(*end)) { --end; } + ++end; // make end exclusive + } + size_t len = end - start; + memmove(str, start, len); + s->resize(len); +} + +namespace lmpParser { + +static const TagHandler axisHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + FontFileInfo& file = *self->fCurrentFontInfo; + SkFourByteTag axisTag = SkSetFourByteTag('\0','\0','\0','\0'); + SkFixed axisStyleValue = 0; + bool axisTagIsValid = false; + bool axisStyleValueIsValid = false; + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* name = attributes[i]; + const char* value = attributes[i+1]; + size_t nameLen = strlen(name); + if (MEMEQ("tag", name, nameLen)) { + size_t valueLen = strlen(value); + if (valueLen == 4) { + axisTag = SkSetFourByteTag(value[0], value[1], value[2], value[3]); + axisTagIsValid = true; + for (int j = 0; j < file.fVariationDesignPosition.count() - 1; ++j) { + if (file.fVariationDesignPosition[j].axis == axisTag) { + axisTagIsValid = false; + SK_FONTCONFIGPARSER_WARNING("'%c%c%c%c' axis specified more than once", + (axisTag >> 24) & 0xFF, + (axisTag >> 16) & 0xFF, + (axisTag >> 8) & 0xFF, + (axisTag ) & 0xFF); + } + } + } else { + SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid axis tag", value); + } + } else if (MEMEQ("stylevalue", name, nameLen)) { + if (parse_fixed<16>(value, &axisStyleValue)) { + axisStyleValueIsValid = true; + } else { + SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid axis stylevalue", value); + } + } + } + if (axisTagIsValid && axisStyleValueIsValid) { + auto& coordinate = file.fVariationDesignPosition.push_back(); + coordinate.axis = axisTag; + coordinate.value = SkFixedToScalar(axisStyleValue); + } + }, + /*end*/nullptr, + /*tag*/nullptr, + /*chars*/nullptr, +}; + +static const TagHandler fontHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + // 'weight' (non-negative integer) [default 0] + // 'style' ("normal", "italic") [default "auto"] + // 'index' (non-negative integer) [default 0] + // The character data should be a filename. + FontFileInfo& file = self->fCurrentFamily->fFonts.push_back(); + self->fCurrentFontInfo = &file; + SkString fallbackFor; + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* name = attributes[i]; + const char* value = attributes[i+1]; + size_t nameLen = strlen(name); + if (MEMEQ("weight", name, nameLen)) { + if (!parse_non_negative_integer(value, &file.fWeight)) { + SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); + } + } else if (MEMEQ("style", name, nameLen)) { + size_t valueLen = strlen(value); + if (MEMEQ("normal", value, valueLen)) { + file.fStyle = FontFileInfo::Style::kNormal; + } else if (MEMEQ("italic", value, valueLen)) { + file.fStyle = FontFileInfo::Style::kItalic; + } + } else if (MEMEQ("index", name, nameLen)) { + if (!parse_non_negative_integer(value, &file.fIndex)) { + SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); + } + } else if (MEMEQ("fallbackFor", name, nameLen)) { + /** fallbackFor specifies a family fallback and should have been on family. */ + fallbackFor = value; + } + } + if (!fallbackFor.isEmpty()) { + std::unique_ptr* fallbackFamily = + self->fCurrentFamily->fallbackFamilies.find(fallbackFor); + if (!fallbackFamily) { + std::unique_ptr newFallbackFamily( + new FontFamily(self->fCurrentFamily->fBasePath, true)); + fallbackFamily = self->fCurrentFamily->fallbackFamilies.set( + fallbackFor, std::move(newFallbackFamily)); + (*fallbackFamily)->fLanguages = self->fCurrentFamily->fLanguages; + (*fallbackFamily)->fVariant = self->fCurrentFamily->fVariant; + (*fallbackFamily)->fOrder = self->fCurrentFamily->fOrder; + (*fallbackFamily)->fFallbackFor = fallbackFor; + } + self->fCurrentFontInfo = &(*fallbackFamily)->fFonts.emplace_back(file); + self->fCurrentFamily->fFonts.pop_back(); + } + }, + /*end*/[](FamilyData* self, const char* tag) { + trim_string(&self->fCurrentFontInfo->fFileName); + }, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("axis", tag, len)) { + return &axisHandler; + } + return nullptr; + }, + /*chars*/[](void* data, const char* s, int len) { + FamilyData* self = static_cast(data); + self->fCurrentFontInfo->fFileName.append(s, len); + } +}; + +static const TagHandler familyHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + // 'name' (string) [optional] + // 'lang' (space separated string) [default ""] + // 'variant' ("elegant", "compact") [default "default"] + // If there is no name, this is a fallback only font. + FontFamily* family = new FontFamily(self->fBasePath, true); + self->fCurrentFamily.reset(family); + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* name = attributes[i]; + const char* value = attributes[i+1]; + size_t nameLen = strlen(name); + size_t valueLen = strlen(value); + if (MEMEQ("name", name, nameLen)) { + SkAutoAsciiToLC tolc(value); + family->fNames.push_back().set(tolc.lc()); + family->fIsFallbackFont = false; + } else if (MEMEQ("lang", name, nameLen)) { + size_t i = 0; + while (true) { + for (; i < valueLen && is_whitespace(value[i]); ++i) { } + if (i == valueLen) { break; } + size_t j; + for (j = i + 1; j < valueLen && !is_whitespace(value[j]); ++j) { } + family->fLanguages.emplace_back(value + i, j - i); + i = j; + if (i == valueLen) { break; } + } + } else if (MEMEQ("variant", name, nameLen)) { + if (MEMEQ("elegant", value, valueLen)) { + family->fVariant = kElegant_FontVariant; + } else if (MEMEQ("compact", value, valueLen)) { + family->fVariant = kCompact_FontVariant; + } + } + } + }, + /*end*/[](FamilyData* self, const char* tag) { + *self->fFamilies.append() = self->fCurrentFamily.release(); + }, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("font", tag, len)) { + return &fontHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +static FontFamily* find_family(FamilyData* self, const SkString& familyName) { + for (int i = 0; i < self->fFamilies.count(); i++) { + FontFamily* candidate = self->fFamilies[i]; + for (int j = 0; j < candidate->fNames.count(); j++) { + if (candidate->fNames[j] == familyName) { + return candidate; + } + } + } + return nullptr; +} + +static const TagHandler aliasHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + // 'name' (string) introduces a new family name. + // 'to' (string) specifies which (previous) family to alias + // 'weight' (non-negative integer) [optional] + // If it *does not* have a weight, 'name' is an alias for the entire 'to' family. + // If it *does* have a weight, 'name' is a new family consisting of + // the font(s) with 'weight' from the 'to' family. + + SkString aliasName; + SkString to; + int weight = 0; + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* name = attributes[i]; + const char* value = attributes[i+1]; + size_t nameLen = strlen(name); + if (MEMEQ("name", name, nameLen)) { + SkAutoAsciiToLC tolc(value); + aliasName.set(tolc.lc()); + } else if (MEMEQ("to", name, nameLen)) { + to.set(value); + } else if (MEMEQ("weight", name, nameLen)) { + if (!parse_non_negative_integer(value, &weight)) { + SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); + } + } + } + + // Assumes that the named family is already declared + FontFamily* targetFamily = find_family(self, to); + if (!targetFamily) { + SK_FONTCONFIGPARSER_WARNING("'%s' alias target not found", to.c_str()); + return; + } + + if (weight) { + FontFamily* family = new FontFamily(targetFamily->fBasePath, self->fIsFallback); + family->fNames.push_back().set(aliasName); + + for (int i = 0; i < targetFamily->fFonts.count(); i++) { + if (targetFamily->fFonts[i].fWeight == weight) { + family->fFonts.push_back(targetFamily->fFonts[i]); + } + } + *self->fFamilies.append() = family; + } else { + targetFamily->fNames.push_back().set(aliasName); + } + }, + /*end*/nullptr, + /*tag*/nullptr, + /*chars*/nullptr, +}; + +static const TagHandler familySetHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { }, + /*end*/nullptr, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("family", tag, len)) { + return &familyHandler; + } else if (MEMEQ("alias", tag, len)) { + return &aliasHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +} // lmpParser + +namespace jbParser { + +static const TagHandler fileHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + // 'variant' ("elegant", "compact") [default "default"] + // 'lang' (string) [default ""] + // 'index' (non-negative integer) [default 0] + // The character data should be a filename. + FontFamily& currentFamily = *self->fCurrentFamily.get(); + FontFileInfo& newFileInfo = currentFamily.fFonts.push_back(); + if (attributes) { + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* name = attributes[i]; + const char* value = attributes[i+1]; + size_t nameLen = strlen(name); + size_t valueLen = strlen(value); + if (MEMEQ("variant", name, nameLen)) { + const FontVariant prevVariant = currentFamily.fVariant; + if (MEMEQ("elegant", value, valueLen)) { + currentFamily.fVariant = kElegant_FontVariant; + } else if (MEMEQ("compact", value, valueLen)) { + currentFamily.fVariant = kCompact_FontVariant; + } + if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) { + SK_FONTCONFIGPARSER_WARNING("'%s' unexpected variant found\n" + "Note: Every font file within a family must have identical variants.", + value); + } + + } else if (MEMEQ("lang", name, nameLen)) { + SkLanguage currentLanguage = SkLanguage(value, valueLen); + bool showWarning = false; + if (currentFamily.fLanguages.empty()) { + showWarning = (currentFamily.fFonts.count() > 1); + currentFamily.fLanguages.push_back(std::move(currentLanguage)); + } else if (currentFamily.fLanguages[0] != currentLanguage) { + showWarning = true; + currentFamily.fLanguages[0] = std::move(currentLanguage); + } + if (showWarning) { + SK_FONTCONFIGPARSER_WARNING("'%s' unexpected language found\n" + "Note: Every font file within a family must have identical languages.", + value); + } + + } else if (MEMEQ("index", name, nameLen)) { + if (!parse_non_negative_integer(value, &newFileInfo.fIndex)) { + SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); + } + } + } + } + self->fCurrentFontInfo = &newFileInfo; + }, + /*end*/nullptr, + /*tag*/nullptr, + /*chars*/[](void* data, const char* s, int len) { + FamilyData* self = static_cast(data); + self->fCurrentFontInfo->fFileName.append(s, len); + } +}; + +static const TagHandler fileSetHandler = { + /*start*/nullptr, + /*end*/nullptr, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("file", tag, len)) { + return &fileHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +static const TagHandler nameHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + // The character data should be a name for the font. + self->fCurrentFamily->fNames.push_back(); + }, + /*end*/nullptr, + /*tag*/nullptr, + /*chars*/[](void* data, const char* s, int len) { + FamilyData* self = static_cast(data); + SkAutoAsciiToLC tolc(s, len); + self->fCurrentFamily->fNames.back().append(tolc.lc(), len); + } +}; + +static const TagHandler nameSetHandler = { + /*start*/nullptr, + /*end*/nullptr, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("name", tag, len)) { + return &nameHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +static const TagHandler familyHandler = { + /*start*/[](FamilyData* self, const char* tag, const char** attributes) { + self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback)); + // 'order' (non-negative integer) [default -1] + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* value = attributes[i+1]; + parse_non_negative_integer(value, &self->fCurrentFamily->fOrder); + } + }, + /*end*/[](FamilyData* self, const char* tag) { + *self->fFamilies.append() = self->fCurrentFamily.release(); + }, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("nameset", tag, len)) { + return &nameSetHandler; + } else if (MEMEQ("fileset", tag, len)) { + return &fileSetHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +static const TagHandler familySetHandler = { + /*start*/nullptr, + /*end*/nullptr, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("family", tag, len)) { + return &familyHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +} // namespace jbParser + +static const TagHandler topLevelHandler = { + /*start*/nullptr, + /*end*/nullptr, + /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { + size_t len = strlen(tag); + if (MEMEQ("familyset", tag, len)) { + // 'version' (non-negative integer) [default 0] + for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { + const char* name = attributes[i]; + size_t nameLen = strlen(name); + if (MEMEQ("version", name, nameLen)) { + const char* value = attributes[i+1]; + if (parse_non_negative_integer(value, &self->fVersion)) { + if (self->fVersion >= 21) { + return &lmpParser::familySetHandler; + } + } + } + } + return &jbParser::familySetHandler; + } + return nullptr; + }, + /*chars*/nullptr, +}; + +static void XMLCALL start_element_handler(void *data, const char *tag, const char **attributes) { + FamilyData* self = static_cast(data); + + if (!self->fSkip) { + const TagHandler* parent = self->fHandler.top(); + const TagHandler* child = parent->tag ? parent->tag(self, tag, attributes) : nullptr; + if (child) { + if (child->start) { + child->start(self, tag, attributes); + } + self->fHandler.push_back(child); + XML_SetCharacterDataHandler(self->fParser, child->chars); + } else { + SK_FONTCONFIGPARSER_WARNING("'%s' tag not recognized, skipping", tag); + XML_SetCharacterDataHandler(self->fParser, nullptr); + self->fSkip = self->fDepth; + } + } + + ++self->fDepth; +} + +static void XMLCALL end_element_handler(void* data, const char* tag) { + FamilyData* self = static_cast(data); + --self->fDepth; + + if (!self->fSkip) { + const TagHandler* child = self->fHandler.top(); + if (child->end) { + child->end(self, tag); + } + self->fHandler.pop(); + const TagHandler* parent = self->fHandler.top(); + XML_SetCharacterDataHandler(self->fParser, parent->chars); + } + + if (self->fSkip == self->fDepth) { + self->fSkip = 0; + const TagHandler* parent = self->fHandler.top(); + XML_SetCharacterDataHandler(self->fParser, parent->chars); + } +} + +static void XMLCALL xml_entity_decl_handler(void *data, + const XML_Char *entityName, + int is_parameter_entity, + const XML_Char *value, + int value_length, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId, + const XML_Char *notationName) +{ + FamilyData* self = static_cast(data); + SK_FONTCONFIGPARSER_WARNING("'%s' entity declaration found, stopping processing", entityName); + XML_StopParser(self->fParser, XML_FALSE); +} + +static const XML_Memory_Handling_Suite sk_XML_alloc = { + sk_malloc_throw, + sk_realloc_throw, + sk_free +}; + +/** + * This function parses the given filename and stores the results in the given + * families array. Returns the version of the file, negative if the file does not exist. + */ +static int parse_config_file(const char* filename, SkTDArray& families, + const SkString& basePath, bool isFallback) +{ + SkFILEStream file(filename); + + // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml) + // are optional - failure here is okay because one of these optional files may not exist. + if (!file.isValid()) { + SkDebugf(SK_FONTMGR_ANDROID_PARSER_PREFIX "'%s' could not be opened\n", filename); + return -1; + } + + SkAutoTCallVProc, XML_ParserFree> parser( + XML_ParserCreate_MM(nullptr, &sk_XML_alloc, nullptr)); + if (!parser) { + SkDebugf(SK_FONTMGR_ANDROID_PARSER_PREFIX "could not create XML parser\n"); + return -1; + } + + FamilyData self(parser, families, basePath, isFallback, filename, &topLevelHandler); + XML_SetUserData(parser, &self); + + // Disable entity processing, to inhibit internal entity expansion. See expat CVE-2013-0340 + XML_SetEntityDeclHandler(parser, xml_entity_decl_handler); + + // Start parsing oldschool; switch these in flight if we detect a newer version of the file. + XML_SetElementHandler(parser, start_element_handler, end_element_handler); + + // One would assume it would be faster to have a buffer on the stack and call XML_Parse. + // But XML_Parse will call XML_GetBuffer anyway and memmove the passed buffer into it. + // (Unless XML_CONTEXT_BYTES is undefined, but all users define it.) + // In debug, buffer a small odd number of bytes to detect slicing in XML_CharacterDataHandler. + static const int bufferSize = 512 SkDEBUGCODE( - 507); + bool done = false; + while (!done) { + void* buffer = XML_GetBuffer(parser, bufferSize); + if (!buffer) { + SkDebugf(SK_FONTMGR_ANDROID_PARSER_PREFIX "could not buffer enough to continue\n"); + return -1; + } + size_t len = file.read(buffer, bufferSize); + done = file.isAtEnd(); + XML_Status status = XML_ParseBuffer(parser, len, done); + if (XML_STATUS_ERROR == status) { + XML_Error error = XML_GetErrorCode(parser); + int line = XML_GetCurrentLineNumber(parser); + int column = XML_GetCurrentColumnNumber(parser); + const XML_LChar* errorString = XML_ErrorString(error); + SkDebugf(SK_FONTMGR_ANDROID_PARSER_PREFIX "%s:%d:%d error %d: %s.\n", + filename, line, column, error, errorString); + return -1; + } + } + return self.fVersion; +} + +/** Returns the version of the system font file actually found, negative if none. */ +static int append_system_font_families(SkTDArray& fontFamilies, + const SkString& basePath) +{ + int initialCount = fontFamilies.count(); + int version = parse_config_file(LMP_SYSTEM_FONTS_FILE, fontFamilies, basePath, false); + if (version < 0 || fontFamilies.count() == initialCount) { + version = parse_config_file(OLD_SYSTEM_FONTS_FILE, fontFamilies, basePath, false); + } + return version; +} + +/** + * In some versions of Android prior to Android 4.2 (JellyBean MR1 at API + * Level 17) the fallback fonts for certain locales were encoded in their own + * XML files with a suffix that identified the locale. We search the provided + * directory for those files,add all of their entries to the fallback chain, and + * include the locale as part of each entry. + */ +static void append_fallback_font_families_for_locale(SkTDArray& fallbackFonts, + const char* dir, + const SkString& basePath) +{ + SkOSFile::Iter iter(dir, nullptr); + SkString fileName; + while (iter.next(&fileName, false)) { + // The size of the prefix and suffix. + static const size_t fixedLen = sizeof(LOCALE_FALLBACK_FONTS_PREFIX) - 1 + + sizeof(LOCALE_FALLBACK_FONTS_SUFFIX) - 1; + + // The size of the prefix, suffix, and a minimum valid language code + static const size_t minSize = fixedLen + 2; + + if (fileName.size() < minSize || + !fileName.startsWith(LOCALE_FALLBACK_FONTS_PREFIX) || + !fileName.endsWith(LOCALE_FALLBACK_FONTS_SUFFIX)) + { + continue; + } + + SkString locale(fileName.c_str() + sizeof(LOCALE_FALLBACK_FONTS_PREFIX) - 1, + fileName.size() - fixedLen); + + SkString absoluteFilename; + absoluteFilename.printf("%s/%s", dir, fileName.c_str()); + + SkTDArray langSpecificFonts; + parse_config_file(absoluteFilename.c_str(), langSpecificFonts, basePath, true); + + for (int i = 0; i < langSpecificFonts.count(); ++i) { + FontFamily* family = langSpecificFonts[i]; + family->fLanguages.emplace_back(locale); + *fallbackFonts.append() = family; + } + } +} + +static void append_system_fallback_font_families(SkTDArray& fallbackFonts, + const SkString& basePath) +{ + parse_config_file(FALLBACK_FONTS_FILE, fallbackFonts, basePath, true); + append_fallback_font_families_for_locale(fallbackFonts, + LOCALE_FALLBACK_FONTS_SYSTEM_DIR, + basePath); +} + +static void mixin_vendor_fallback_font_families(SkTDArray& fallbackFonts, + const SkString& basePath) +{ + SkTDArray vendorFonts; + parse_config_file(VENDOR_FONTS_FILE, vendorFonts, basePath, true); + append_fallback_font_families_for_locale(vendorFonts, + LOCALE_FALLBACK_FONTS_VENDOR_DIR, + basePath); + + // This loop inserts the vendor fallback fonts in the correct order in the + // overall fallbacks list. + int currentOrder = -1; + for (int i = 0; i < vendorFonts.count(); ++i) { + FontFamily* family = vendorFonts[i]; + int order = family->fOrder; + if (order < 0) { + if (currentOrder < 0) { + // Default case - just add it to the end of the fallback list + *fallbackFonts.append() = family; + } else { + // no order specified on this font, but we're incrementing the order + // based on an earlier order insertion request + *fallbackFonts.insert(currentOrder++) = family; + } + } else { + // Add the font into the fallback list in the specified order. Set + // currentOrder for correct placement of other fonts in the vendor list. + *fallbackFonts.insert(order) = family; + currentOrder = order + 1; + } + } +} + +void SkFontMgr_Android_Parser::GetSystemFontFamilies(SkTDArray& fontFamilies) { + // Version 21 of the system font configuration does not need any fallback configuration files. + SkString basePath(getenv("ANDROID_ROOT")); + basePath.append(SK_FONT_FILE_PREFIX, sizeof(SK_FONT_FILE_PREFIX) - 1); + + if (append_system_font_families(fontFamilies, basePath) >= 21) { + return; + } + + // Append all the fallback fonts to system fonts + SkTDArray fallbackFonts; + append_system_fallback_font_families(fallbackFonts, basePath); + mixin_vendor_fallback_font_families(fallbackFonts, basePath); + fontFamilies.append(fallbackFonts.count(), fallbackFonts.begin()); +} + +void SkFontMgr_Android_Parser::GetCustomFontFamilies(SkTDArray& fontFamilies, + const SkString& basePath, + const char* fontsXml, + const char* fallbackFontsXml, + const char* langFallbackFontsDir) +{ + if (fontsXml) { + parse_config_file(fontsXml, fontFamilies, basePath, false); + } + if (fallbackFontsXml) { + parse_config_file(fallbackFontsXml, fontFamilies, basePath, true); + } + if (langFallbackFontsDir) { + append_fallback_font_families_for_locale(fontFamilies, + langFallbackFontsDir, + basePath); + } +} + +SkLanguage SkLanguage::getParent() const { + SkASSERT(!fTag.isEmpty()); + const char* tag = fTag.c_str(); + + // strip off the rightmost "-.*" + const char* parentTagEnd = strrchr(tag, '-'); + if (parentTagEnd == nullptr) { + return SkLanguage(); + } + size_t parentTagLen = parentTagEnd - tag; + return SkLanguage(tag, parentTagLen); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_android_parser.h b/gfx/skia/skia/src/ports/SkFontMgr_android_parser.h new file mode 100644 index 0000000000..7a33f30b89 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_android_parser.h @@ -0,0 +1,216 @@ +/* + * Copyright 2011 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_android_parser_DEFINED +#define SkFontMgr_android_parser_DEFINED + +#include "include/core/SkFontMgr.h" +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTHash.h" + +#include +#include + +/** \class SkLanguage + + The SkLanguage class represents a human written language, and is used by + text draw operations to determine which glyph to draw when drawing + characters with variants (ie Han-derived characters). +*/ +class SkLanguage { +public: + SkLanguage() { } + SkLanguage(const SkString& tag) : fTag(tag) { } + SkLanguage(const char* tag) : fTag(tag) { } + SkLanguage(const char* tag, size_t len) : fTag(tag, len) { } + SkLanguage(const SkLanguage& b) : fTag(b.fTag) { } + + /** Gets a BCP 47 language identifier for this SkLanguage. + @return a BCP 47 language identifier representing this language + */ + const SkString& getTag() const { return fTag; } + + /** Performs BCP 47 fallback to return an SkLanguage one step more general. + @return an SkLanguage one step more general + */ + SkLanguage getParent() const; + + bool operator==(const SkLanguage& b) const { + return fTag == b.fTag; + } + bool operator!=(const SkLanguage& b) const { + return fTag != b.fTag; + } + SkLanguage& operator=(const SkLanguage& b) { + fTag = b.fTag; + return *this; + } + +private: + //! BCP 47 language identifier + SkString fTag; +}; + +enum FontVariants { + kDefault_FontVariant = 0x01, + kCompact_FontVariant = 0x02, + kElegant_FontVariant = 0x04, + kLast_FontVariant = kElegant_FontVariant, +}; +typedef uint32_t FontVariant; + +// Must remain trivially movable (can be memmoved). +struct FontFileInfo { + FontFileInfo() : fIndex(0), fWeight(0), fStyle(Style::kAuto) { } + + SkString fFileName; + int fIndex; + int fWeight; + enum class Style { kAuto, kNormal, kItalic } fStyle; + SkTArray fVariationDesignPosition; +}; + +/** + * A font family provides one or more names for a collection of fonts, each of + * which has a different style (normal, italic) or weight (thin, light, bold, + * etc). + * Some fonts may occur in compact variants for use in the user interface. + * Android distinguishes "fallback" fonts to support non-ASCII character sets. + */ +struct FontFamily { + FontFamily(const SkString& basePath, bool isFallbackFont) + : fVariant(kDefault_FontVariant) + , fOrder(-1) + , fIsFallbackFont(isFallbackFont) + , fBasePath(basePath) + { } + + SkTArray fNames; + SkTArray fFonts; + SkTArray fLanguages; + SkTHashMap> fallbackFamilies; + FontVariant fVariant; + int fOrder; // internal to the parser, not useful to users. + bool fIsFallbackFont; + SkString fFallbackFor; + const SkString fBasePath; +}; + +namespace SkFontMgr_Android_Parser { + +/** Parses system font configuration files and appends result to fontFamilies. */ +void GetSystemFontFamilies(SkTDArray& fontFamilies); + +/** Parses font configuration files and appends result to fontFamilies. */ +void GetCustomFontFamilies(SkTDArray& fontFamilies, + const SkString& basePath, + const char* fontsXml, + const char* fallbackFontsXml, + const char* langFallbackFontsDir = nullptr); + +} // SkFontMgr_Android_Parser namespace + + +/** Parses a null terminated string into an integer type, checking for overflow. + * http://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def + * + * If the string cannot be parsed into 'value', returns false and does not change 'value'. + */ +template static bool parse_non_negative_integer(const char* s, T* value) { + static_assert(std::numeric_limits::is_integer, "T_must_be_integer"); + + if (*s == '\0') { + return false; + } + + const T nMax = std::numeric_limits::max() / 10; + const T dMax = std::numeric_limits::max() - (nMax * 10); + T n = 0; + for (; *s; ++s) { + // Check if digit + if (*s < '0' || '9' < *s) { + return false; + } + T d = *s - '0'; + // Check for overflow + if (n > nMax || (n == nMax && d > dMax)) { + return false; + } + n = (n * 10) + d; + } + *value = n; + return true; +} + +/** Parses a null terminated string into a signed fixed point value with bias N. + * + * Like http://www.w3.org/TR/html-markup/datatypes.html#common.data.float-def , + * but may start with '.' and does not support 'e'. '-?((:digit:+(.:digit:+)?)|(.:digit:+))' + * + * Checks for overflow. + * Low bit rounding is not defined (is currently truncate). + * Bias (N) required to allow for the sign bit and 4 bits of integer. + * + * If the string cannot be parsed into 'value', returns false and does not change 'value'. + */ +template static bool parse_fixed(const char* s, T* value) { + static_assert(std::numeric_limits::is_integer, "T_must_be_integer"); + static_assert(std::numeric_limits::is_signed, "T_must_be_signed"); + static_assert(sizeof(T) * CHAR_BIT - N >= 5, "N_must_leave_four_bits_plus_sign"); + + bool negate = false; + if (*s == '-') { + ++s; + negate = true; + } + if (*s == '\0') { + return false; + } + + const T nMax = (std::numeric_limits::max() >> N) / 10; + const T dMax = (std::numeric_limits::max() >> N) - (nMax * 10); + T n = 0; + T frac = 0; + for (; *s; ++s) { + // Check if digit + if (*s < '0' || '9' < *s) { + // If it wasn't a digit, check if it is a '.' followed by something. + if (*s != '.' || s[1] == '\0') { + return false; + } + // Find the end, verify digits. + for (++s; *s; ++s) { + if (*s < '0' || '9' < *s) { + return false; + } + } + // Read back toward the '.'. + for (--s; *s != '.'; --s) { + T d = *s - '0'; + frac = (frac + (d << N)) / 10; // This requires four bits overhead. + } + break; + } + T d = *s - '0'; + // Check for overflow + if (n > nMax || (n == nMax && d > dMax)) { + return false; + } + n = (n * 10) + d; + } + if (negate) { + n = -n; + frac = -frac; + } + *value = SkLeftShift(n, N) + frac; + return true; +} + +#endif /* SkFontMgr_android_parser_DEFINED */ diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom.cpp new file mode 100644 index 0000000000..6ee4e5454f --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom.cpp @@ -0,0 +1,289 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontArguments.h" +#include "include/core/SkFontMgr.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" +#include "src/ports/SkFontHost_FreeType_common.h" +#include "src/ports/SkFontMgr_custom.h" + +#include +#include + +class SkData; + +SkTypeface_Custom::SkTypeface_Custom(const SkFontStyle& style, bool isFixedPitch, + bool sysFont, const SkString familyName, int index) + : INHERITED(style, isFixedPitch) + , fIsSysFont(sysFont), fFamilyName(familyName), fIndex(index) +{ } + +bool SkTypeface_Custom::isSysFont() const { return fIsSysFont; } + +void SkTypeface_Custom::onGetFamilyName(SkString* familyName) const { + *familyName = fFamilyName; +} + +void SkTypeface_Custom::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const { + desc->setFamilyName(fFamilyName.c_str()); + desc->setStyle(this->fontStyle()); + *isLocal = !this->isSysFont(); +} + +int SkTypeface_Custom::getIndex() const { return fIndex; } + + +SkTypeface_Empty::SkTypeface_Empty() : INHERITED(SkFontStyle(), false, true, SkString(), 0) {} + +std::unique_ptr SkTypeface_Empty::onOpenStream(int*) const { return nullptr; } + +sk_sp SkTypeface_Empty::onMakeClone(const SkFontArguments& args) const { + return sk_ref_sp(this); +} + +SkTypeface_Stream::SkTypeface_Stream(std::unique_ptr fontData, + const SkFontStyle& style, bool isFixedPitch, bool sysFont, + const SkString familyName) + : INHERITED(style, isFixedPitch, sysFont, familyName, fontData->getIndex()) + , fData(std::move(fontData)) +{ } + +std::unique_ptr SkTypeface_Stream::onOpenStream(int* ttcIndex) const { + *ttcIndex = fData->getIndex(); + return fData->getStream()->duplicate(); +} + +std::unique_ptr SkTypeface_Stream::onMakeFontData() const { + return skstd::make_unique(*fData); +} + +sk_sp SkTypeface_Stream::onMakeClone(const SkFontArguments& args) const { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + + SkString familyName; + this->getFamilyName(&familyName); + + return sk_make_sp(std::move(data), + this->fontStyle(), + this->isFixedPitch(), + this->isSysFont(), + familyName); +} + +SkTypeface_File::SkTypeface_File(const SkFontStyle& style, bool isFixedPitch, bool sysFont, + const SkString familyName, const char path[], int index) + : INHERITED(style, isFixedPitch, sysFont, familyName, index) + , fPath(path) +{ } + +std::unique_ptr SkTypeface_File::onOpenStream(int* ttcIndex) const { + *ttcIndex = this->getIndex(); + return SkStream::MakeFromFile(fPath.c_str()); +} + +sk_sp SkTypeface_File::onMakeClone(const SkFontArguments& args) const { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + + SkString familyName; + this->getFamilyName(&familyName); + + return sk_make_sp(std::move(data), + this->fontStyle(), + this->isFixedPitch(), + this->isSysFont(), + familyName); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkFontStyleSet_Custom::SkFontStyleSet_Custom(const SkString familyName) : fFamilyName(familyName) {} + +void SkFontStyleSet_Custom::appendTypeface(sk_sp typeface) { + fStyles.emplace_back(std::move(typeface)); +} + +int SkFontStyleSet_Custom::count() { + return fStyles.count(); +} + +void SkFontStyleSet_Custom::getStyle(int index, SkFontStyle* style, SkString* name) { + SkASSERT(index < fStyles.count()); + if (style) { + *style = fStyles[index]->fontStyle(); + } + if (name) { + name->reset(); + } +} + +SkTypeface* SkFontStyleSet_Custom::createTypeface(int index) { + SkASSERT(index < fStyles.count()); + return SkRef(fStyles[index].get()); +} + +SkTypeface* SkFontStyleSet_Custom::matchStyle(const SkFontStyle& pattern) { + return this->matchStyleCSS3(pattern); +} + +SkString SkFontStyleSet_Custom::getFamilyName() { return fFamilyName; } + + +SkFontMgr_Custom::SkFontMgr_Custom(const SystemFontLoader& loader) : fDefaultFamily(nullptr) { + loader.loadSystemFonts(fScanner, &fFamilies); + + // Try to pick a default font. + static const char* defaultNames[] = { + "Arial", "Verdana", "Times New Roman", "Droid Sans", nullptr + }; + for (size_t i = 0; i < SK_ARRAY_COUNT(defaultNames); ++i) { + sk_sp set(this->onMatchFamily(defaultNames[i])); + if (nullptr == set) { + continue; + } + + sk_sp tf(set->matchStyle(SkFontStyle(SkFontStyle::kNormal_Weight, + SkFontStyle::kNormal_Width, + SkFontStyle::kUpright_Slant))); + if (nullptr == tf) { + continue; + } + + fDefaultFamily = set.get(); + break; + } + if (nullptr == fDefaultFamily) { + fDefaultFamily = fFamilies[0].get(); + } +} + +int SkFontMgr_Custom::onCountFamilies() const { + return fFamilies.count(); +} + +void SkFontMgr_Custom::onGetFamilyName(int index, SkString* familyName) const { + SkASSERT(index < fFamilies.count()); + familyName->set(fFamilies[index]->getFamilyName()); +} + +SkFontStyleSet_Custom* SkFontMgr_Custom::onCreateStyleSet(int index) const { + SkASSERT(index < fFamilies.count()); + return SkRef(fFamilies[index].get()); +} + +SkFontStyleSet_Custom* SkFontMgr_Custom::onMatchFamily(const char familyName[]) const { + for (int i = 0; i < fFamilies.count(); ++i) { + if (fFamilies[i]->getFamilyName().equals(familyName)) { + return SkRef(fFamilies[i].get()); + } + } + return nullptr; +} + +SkTypeface* SkFontMgr_Custom::onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontStyle) const +{ + sk_sp sset(this->matchFamily(familyName)); + return sset->matchStyle(fontStyle); +} + +SkTypeface* SkFontMgr_Custom::onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const +{ + return nullptr; +} + +SkTypeface* SkFontMgr_Custom::onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontStyle) const +{ + for (int i = 0; i < fFamilies.count(); ++i) { + for (int j = 0; j < fFamilies[i]->fStyles.count(); ++j) { + if (fFamilies[i]->fStyles[j].get() == familyMember) { + return fFamilies[i]->matchStyle(fontStyle); + } + } + } + return nullptr; +} + +sk_sp SkFontMgr_Custom::onMakeFromData(sk_sp data, int ttcIndex) const { + return this->makeFromStream(skstd::make_unique(std::move(data)), ttcIndex); +} + +sk_sp SkFontMgr_Custom::onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const { + return this->makeFromStream(std::move(stream), SkFontArguments().setCollectionIndex(ttcIndex)); +} + +sk_sp SkFontMgr_Custom::onMakeFromStreamArgs(std::unique_ptr stream, + const SkFontArguments& args) const { + using Scanner = SkTypeface_FreeType::Scanner; + bool isFixedPitch; + SkFontStyle style; + SkString name; + Scanner::AxisDefinitions axisDefinitions; + if (!fScanner.scanFont(stream.get(), args.getCollectionIndex(), + &name, &style, &isFixedPitch, &axisDefinitions)) + { + return nullptr; + } + + const SkFontArguments::VariationPosition position = args.getVariationDesignPosition(); + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + Scanner::computeAxisValues(axisDefinitions, position, axisValues, name); + + auto data = skstd::make_unique(std::move(stream), args.getCollectionIndex(), + axisValues.get(), axisDefinitions.count()); + return sk_sp(new SkTypeface_Stream(std::move(data), style, isFixedPitch, false, name)); +} + +sk_sp SkFontMgr_Custom::onMakeFromFontData(std::unique_ptr data) const { + bool isFixedPitch; + SkFontStyle style; + SkString name; + if (!fScanner.scanFont(data->getStream(), data->getIndex(), + &name, &style, &isFixedPitch, nullptr)) { + return nullptr; + } + return sk_sp(new SkTypeface_Stream(std::move(data), style, isFixedPitch, false, name)); +} + +sk_sp SkFontMgr_Custom::onMakeFromFile(const char path[], int ttcIndex) const { + std::unique_ptr stream = SkStream::MakeFromFile(path); + return stream ? this->makeFromStream(std::move(stream), ttcIndex) : nullptr; +} + +sk_sp SkFontMgr_Custom::onLegacyMakeTypeface(const char familyName[], + SkFontStyle style) const { + sk_sp tf; + + if (familyName) { + tf.reset(this->onMatchFamilyStyle(familyName, style)); + } + + if (nullptr == tf) { + tf.reset(fDefaultFamily->matchStyle(style)); + } + + return tf; +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom.h b/gfx/skia/skia/src/ports/SkFontMgr_custom.h new file mode 100644 index 0000000000..4d76c2986b --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom.h @@ -0,0 +1,161 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFontMgr_custom_DEFINED +#define SkFontMgr_custom_DEFINED + +#include "include/core/SkFontMgr.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" +#include "src/ports/SkFontHost_FreeType_common.h" + +class SkData; +class SkFontDescriptor; +class SkStreamAsset; +class SkTypeface; + +/** The base SkTypeface implementation for the custom font manager. */ +class SkTypeface_Custom : public SkTypeface_FreeType { +public: + SkTypeface_Custom(const SkFontStyle& style, bool isFixedPitch, + bool sysFont, const SkString familyName, int index); + bool isSysFont() const; + +protected: + void onGetFamilyName(SkString* familyName) const override; + void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override; + int getIndex() const; + +private: + const bool fIsSysFont; + const SkString fFamilyName; + const int fIndex; + + typedef SkTypeface_FreeType INHERITED; +}; + +/** The empty SkTypeface implementation for the custom font manager. + * Used as the last resort fallback typeface. + */ +class SkTypeface_Empty : public SkTypeface_Custom { +public: + SkTypeface_Empty() ; + +protected: + std::unique_ptr onOpenStream(int*) const override; + sk_sp onMakeClone(const SkFontArguments& args) const override; + +private: + typedef SkTypeface_Custom INHERITED; +}; + +/** The stream SkTypeface implementation for the custom font manager. */ +class SkTypeface_Stream : public SkTypeface_Custom { +public: + SkTypeface_Stream(std::unique_ptr fontData, + const SkFontStyle& style, bool isFixedPitch, bool sysFont, + const SkString familyName); + +protected: + std::unique_ptr onOpenStream(int* ttcIndex) const override; + std::unique_ptr onMakeFontData() const override; + sk_sp onMakeClone(const SkFontArguments& args) const override; + +private: + const std::unique_ptr fData; + + typedef SkTypeface_Custom INHERITED; +}; + +/** The file SkTypeface implementation for the custom font manager. */ +class SkTypeface_File : public SkTypeface_Custom { +public: + SkTypeface_File(const SkFontStyle& style, bool isFixedPitch, bool sysFont, + const SkString familyName, const char path[], int index); + +protected: + std::unique_ptr onOpenStream(int* ttcIndex) const override; + sk_sp onMakeClone(const SkFontArguments& args) const override; + +private: + SkString fPath; + + typedef SkTypeface_Custom INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * SkFontStyleSet_Custom + * + * This class is used by SkFontMgr_Custom to hold SkTypeface_Custom families. + */ +class SkFontStyleSet_Custom : public SkFontStyleSet { +public: + explicit SkFontStyleSet_Custom(const SkString familyName); + + /** Should only be called during the initial build phase. */ + void appendTypeface(sk_sp typeface); + int count() override; + void getStyle(int index, SkFontStyle* style, SkString* name) override; + SkTypeface* createTypeface(int index) override; + SkTypeface* matchStyle(const SkFontStyle& pattern) override; + SkString getFamilyName(); + +private: + SkTArray> fStyles; + SkString fFamilyName; + + friend class SkFontMgr_Custom; +}; + +/** + * SkFontMgr_Custom + * + * This class is essentially a collection of SkFontStyleSet_Custom, + * one SkFontStyleSet_Custom for each family. This class may be modified + * to load fonts from any source by changing the initialization. + */ +class SkFontMgr_Custom : public SkFontMgr { +public: + typedef SkTArray> Families; + class SystemFontLoader { + public: + virtual ~SystemFontLoader() { } + virtual void loadSystemFonts(const SkTypeface_FreeType::Scanner&, Families*) const = 0; + }; + explicit SkFontMgr_Custom(const SystemFontLoader& loader); + +protected: + int onCountFamilies() const override; + void onGetFamilyName(int index, SkString* familyName) const override; + SkFontStyleSet_Custom* onCreateStyleSet(int index) const override; + SkFontStyleSet_Custom* onMatchFamily(const char familyName[]) const override; + SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontStyle) const override; + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override; + SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontStyle) const override; + sk_sp onMakeFromData(sk_sp data, int ttcIndex) const override; + sk_sp onMakeFromStreamIndex(std::unique_ptr, int ttcIndex) const override; + sk_sp onMakeFromStreamArgs(std::unique_ptr, const SkFontArguments&) const override; + sk_sp onMakeFromFontData(std::unique_ptr data) const override; + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override; + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override; + +private: + Families fFamilies; + SkFontStyleSet_Custom* fDefaultFamily; + SkTypeface_FreeType::Scanner fScanner; +}; + +#endif diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom_directory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom_directory.cpp new file mode 100644 index 0000000000..c4c1c3599b --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom_directory.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/ports/SkFontMgr_directory.h" +#include "src/core/SkOSFile.h" +#include "src/ports/SkFontMgr_custom.h" +#include "src/utils/SkOSPath.h" + +class DirectorySystemFontLoader : public SkFontMgr_Custom::SystemFontLoader { +public: + DirectorySystemFontLoader(const char* dir) : fBaseDirectory(dir) { } + + void loadSystemFonts(const SkTypeface_FreeType::Scanner& scanner, + SkFontMgr_Custom::Families* families) const override + { + load_directory_fonts(scanner, fBaseDirectory, ".ttf", families); + load_directory_fonts(scanner, fBaseDirectory, ".ttc", families); + load_directory_fonts(scanner, fBaseDirectory, ".otf", families); + load_directory_fonts(scanner, fBaseDirectory, ".pfb", families); + + if (families->empty()) { + SkFontStyleSet_Custom* family = new SkFontStyleSet_Custom(SkString()); + families->push_back().reset(family); + family->appendTypeface(sk_make_sp()); + } + } + +private: + static SkFontStyleSet_Custom* find_family(SkFontMgr_Custom::Families& families, + const char familyName[]) + { + for (int i = 0; i < families.count(); ++i) { + if (families[i]->getFamilyName().equals(familyName)) { + return families[i].get(); + } + } + return nullptr; + } + + static void load_directory_fonts(const SkTypeface_FreeType::Scanner& scanner, + const SkString& directory, const char* suffix, + SkFontMgr_Custom::Families* families) + { + SkOSFile::Iter iter(directory.c_str(), suffix); + SkString name; + + while (iter.next(&name, false)) { + SkString filename(SkOSPath::Join(directory.c_str(), name.c_str())); + std::unique_ptr stream = SkStream::MakeFromFile(filename.c_str()); + if (!stream) { + // SkDebugf("---- failed to open <%s>\n", filename.c_str()); + continue; + } + + int numFaces; + if (!scanner.recognizedFont(stream.get(), &numFaces)) { + // SkDebugf("---- failed to open <%s> as a font\n", filename.c_str()); + continue; + } + + for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) { + bool isFixedPitch; + SkString realname; + SkFontStyle style = SkFontStyle(); // avoid uninitialized warning + if (!scanner.scanFont(stream.get(), faceIndex, + &realname, &style, &isFixedPitch, nullptr)) + { + // SkDebugf("---- failed to open <%s> <%d> as a font\n", + // filename.c_str(), faceIndex); + continue; + } + + SkFontStyleSet_Custom* addTo = find_family(*families, realname.c_str()); + if (nullptr == addTo) { + addTo = new SkFontStyleSet_Custom(realname); + families->push_back().reset(addTo); + } + addTo->appendTypeface(sk_make_sp(style, isFixedPitch, true, + realname, filename.c_str(), + faceIndex)); + } + } + + SkOSFile::Iter dirIter(directory.c_str()); + while (dirIter.next(&name, true)) { + if (name.startsWith(".")) { + continue; + } + SkString dirname(SkOSPath::Join(directory.c_str(), name.c_str())); + load_directory_fonts(scanner, dirname, suffix, families); + } + } + + SkString fBaseDirectory; +}; + +SK_API sk_sp SkFontMgr_New_Custom_Directory(const char* dir) { + return sk_make_sp(DirectorySystemFontLoader(dir)); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom_directory_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom_directory_factory.cpp new file mode 100644 index 0000000000..3bb52eb339 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom_directory_factory.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/ports/SkFontMgr_directory.h" + +#ifndef SK_FONT_FILE_PREFIX +# define SK_FONT_FILE_PREFIX "/usr/share/fonts/" +#endif + +sk_sp SkFontMgr::Factory() { + return SkFontMgr_New_Custom_Directory(SK_FONT_FILE_PREFIX); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom_embedded.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom_embedded.cpp new file mode 100644 index 0000000000..45a3151134 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom_embedded.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" +#include "src/ports/SkFontMgr_custom.h" + +struct SkEmbeddedResource { const uint8_t* data; size_t size; }; +struct SkEmbeddedResourceHeader { const SkEmbeddedResource* entries; int count; }; + +static void load_font_from_data(const SkTypeface_FreeType::Scanner& scanner, + const uint8_t* data, size_t size, int index, + SkFontMgr_Custom::Families* families); + +class EmbeddedSystemFontLoader : public SkFontMgr_Custom::SystemFontLoader { +public: + EmbeddedSystemFontLoader(const SkEmbeddedResourceHeader* header) : fHeader(header) { } + + void loadSystemFonts(const SkTypeface_FreeType::Scanner& scanner, + SkFontMgr_Custom::Families* families) const override + { + for (int i = 0; i < fHeader->count; ++i) { + const SkEmbeddedResource& fontEntry = fHeader->entries[i]; + load_font_from_data(scanner, fontEntry.data, fontEntry.size, i, families); + } + + if (families->empty()) { + SkFontStyleSet_Custom* family = new SkFontStyleSet_Custom(SkString()); + families->push_back().reset(family); + family->appendTypeface(sk_make_sp()); + } + } + + const SkEmbeddedResourceHeader* fHeader; +}; + +class DataFontLoader : public SkFontMgr_Custom::SystemFontLoader { +public: + DataFontLoader(const uint8_t** datas, const size_t* sizes, int n) : fDatas(datas), fSizes(sizes), fNum(n) { } + + void loadSystemFonts(const SkTypeface_FreeType::Scanner& scanner, + SkFontMgr_Custom::Families* families) const override + { + for (int i = 0; i < fNum; ++i) { + load_font_from_data(scanner, fDatas[i], fSizes[i], i, families); + } + + if (families->empty()) { + SkFontStyleSet_Custom* family = new SkFontStyleSet_Custom(SkString()); + families->push_back().reset(family); + family->appendTypeface(sk_make_sp()); + } + } + + const uint8_t** fDatas; + const size_t* fSizes; + const int fNum; +}; + +static SkFontStyleSet_Custom* find_family(SkFontMgr_Custom::Families& families, + const char familyName[]) +{ + for (int i = 0; i < families.count(); ++i) { + if (families[i]->getFamilyName().equals(familyName)) { + return families[i].get(); + } + } + return nullptr; +} + +static void load_font_from_data(const SkTypeface_FreeType::Scanner& scanner, + const uint8_t* data, size_t size, int index, + SkFontMgr_Custom::Families* families) +{ + auto stream = skstd::make_unique(data, size, false); + + int numFaces; + if (!scanner.recognizedFont(stream.get(), &numFaces)) { + SkDebugf("---- failed to open <%d> as a font\n", index); + return; + } + + for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) { + bool isFixedPitch; + SkString realname; + SkFontStyle style = SkFontStyle(); // avoid uninitialized warning + if (!scanner.scanFont(stream.get(), faceIndex, + &realname, &style, &isFixedPitch, nullptr)) + { + SkDebugf("---- failed to open <%d> <%d> as a font\n", index, faceIndex); + return; + } + + SkFontStyleSet_Custom* addTo = find_family(*families, realname.c_str()); + if (nullptr == addTo) { + addTo = new SkFontStyleSet_Custom(realname); + families->push_back().reset(addTo); + } + auto data = skstd::make_unique(std::move(stream), faceIndex, nullptr, 0); + addTo->appendTypeface(sk_make_sp(std::move(data), + style, isFixedPitch, + true, realname)); + } +} + +sk_sp SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header) { + return sk_make_sp(EmbeddedSystemFontLoader(header)); +} + +// SkFontMgr_New_Custom_Data expects to be called with the data for n font files. datas and sizes +// are parallel arrays of bytes and byte lengths. +sk_sp SkFontMgr_New_Custom_Data(const uint8_t** datas, const size_t* sizes, int n) { + SkASSERT(datas != nullptr); + SkASSERT(sizes != nullptr); + SkASSERT(n > 0); + return sk_make_sp(DataFontLoader(datas, sizes, n)); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom_embedded_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom_embedded_factory.cpp new file mode 100644 index 0000000000..82e1b842ad --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom_embedded_factory.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" + +struct SkEmbeddedResource { const uint8_t* data; size_t size; }; +struct SkEmbeddedResourceHeader { const SkEmbeddedResource* entries; int count; }; +sk_sp SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header); + +extern "C" const SkEmbeddedResourceHeader SK_EMBEDDED_FONTS; +sk_sp SkFontMgr::Factory() { + return SkFontMgr_New_Custom_Embedded(&SK_EMBEDDED_FONTS); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom_empty.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom_empty.cpp new file mode 100644 index 0000000000..0e3e18aefd --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom_empty.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/ports/SkFontMgr_empty.h" +#include "src/ports/SkFontMgr_custom.h" + +class EmptyFontLoader : public SkFontMgr_Custom::SystemFontLoader { +public: + EmptyFontLoader() { } + + void loadSystemFonts(const SkTypeface_FreeType::Scanner& scanner, + SkFontMgr_Custom::Families* families) const override + { + SkFontStyleSet_Custom* family = new SkFontStyleSet_Custom(SkString()); + families->push_back().reset(family); + family->appendTypeface(sk_make_sp()); + } + +}; + +SK_API sk_sp SkFontMgr_New_Custom_Empty() { + return sk_make_sp(EmptyFontLoader()); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_custom_empty_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_custom_empty_factory.cpp new file mode 100644 index 0000000000..b97c199490 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_custom_empty_factory.cpp @@ -0,0 +1,13 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/ports/SkFontMgr_empty.h" + +sk_sp SkFontMgr::Factory() { + return SkFontMgr_New_Custom_Empty(); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_empty_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_empty_factory.cpp new file mode 100644 index 0000000000..69410c5ef9 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_empty_factory.cpp @@ -0,0 +1,13 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" + +sk_sp SkFontMgr::Factory() { + // Always return nullptr, an empty SkFontMgr will be used. + return nullptr; +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_fontconfig.cpp b/gfx/skia/skia/src/ports/SkFontMgr_fontconfig.cpp new file mode 100644 index 0000000000..5131562faf --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_fontconfig.cpp @@ -0,0 +1,1042 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkDataTable.h" +#include "include/core/SkFontMgr.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkMath.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/private/SkFixed.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTDArray.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkOSFile.h" +#include "src/core/SkTypefaceCache.h" +#include "src/ports/SkFontHost_FreeType_common.h" + +#include +#include + +class SkData; + +// FC_POSTSCRIPT_NAME was added with b561ff20 which ended up in 2.10.92 +// Ubuntu 14.04 is on 2.11.0 +// Debian 8 and 9 are on 2.11 +// OpenSUSE Leap 42.1 is on 2.11.0 (42.3 is on 2.11.1) +// Fedora 24 is on 2.11.94 +#ifndef FC_POSTSCRIPT_NAME +# define FC_POSTSCRIPT_NAME "postscriptname" +#endif + +#ifdef SK_DEBUG +# include "src/core/SkTLS.h" +#endif + +/** Since FontConfig is poorly documented, this gives a high level overview: + * + * FcConfig is a handle to a FontConfig configuration instance. Each 'configuration' is independent + * from any others which may exist. There exists a default global configuration which is created + * and destroyed by FcInit and FcFini, but this default should not normally be used. + * Instead, one should use FcConfigCreate and FcInit* to have a named local state. + * + * FcPatterns are {objectName -> [element]} (maps from object names to a list of elements). + * Each element is some internal data plus an FcValue which is a variant (a union with a type tag). + * Lists of elements are not typed, except by convention. Any collection of FcValues must be + * assumed to be heterogeneous by the code, but the code need not do anything particularly + * interesting if the values go against convention. + * + * Somewhat like DirectWrite, FontConfig supports synthetics through FC_EMBOLDEN and FC_MATRIX. + * Like all synthetic information, such information must be passed with the font data. + */ + +namespace { + +// Fontconfig is not threadsafe before 2.10.91. Before that, we lock with a global mutex. +// See https://bug.skia.org/1497 for background. +static SkMutex& f_c_mutex() { + static SkMutex& mutex = *(new SkMutex); + return mutex; +} + +#ifdef SK_DEBUG +void* CreateThreadFcLocked() { return new bool(false); } +void DeleteThreadFcLocked(void* v) { delete static_cast(v); } +# define THREAD_FC_LOCKED \ + static_cast(SkTLS::Get(CreateThreadFcLocked, DeleteThreadFcLocked)) +#endif + +class FCLocker { + // Assume FcGetVersion() has always been thread safe. + static void lock() SK_NO_THREAD_SAFETY_ANALYSIS { + if (FcGetVersion() < 21091) { + f_c_mutex().acquire(); + } else { + SkDEBUGCODE(bool* threadLocked = THREAD_FC_LOCKED); + SkASSERT(false == *threadLocked); + SkDEBUGCODE(*threadLocked = true); + } + } + static void unlock() SK_NO_THREAD_SAFETY_ANALYSIS { + AssertHeld(); + if (FcGetVersion() < 21091) { + f_c_mutex().release(); + } else { + SkDEBUGCODE(*THREAD_FC_LOCKED = false); + } + } + +public: + FCLocker() { lock(); } + ~FCLocker() { unlock(); } + + /** If acquire and release were free, FCLocker would be used around each call into FontConfig. + * Instead a much more granular approach is taken, but this means there are times when the + * mutex is held when it should not be. A Suspend will drop the lock until it is destroyed. + * While a Suspend exists, FontConfig should not be used without re-taking the lock. + */ + struct Suspend { + Suspend() { unlock(); } + ~Suspend() { lock(); } + }; + + static void AssertHeld() { SkDEBUGCODE( + if (FcGetVersion() < 21091) { + f_c_mutex().assertHeld(); + } else { + SkASSERT(true == *THREAD_FC_LOCKED); + } + ) } +}; + +} // namespace + +template void FcTDestroy(T* t) { + FCLocker::AssertHeld(); + D(t); +} +template class SkAutoFc + : public SkAutoTCallVProc > { +public: + SkAutoFc() : SkAutoTCallVProc >(C()) { + T* obj = this->operator T*(); + SkASSERT_RELEASE(nullptr != obj); + } + explicit SkAutoFc(T* obj) : SkAutoTCallVProc >(obj) {} +}; + +typedef SkAutoFc SkAutoFcCharSet; +typedef SkAutoFc SkAutoFcConfig; +typedef SkAutoFc SkAutoFcFontSet; +typedef SkAutoFc SkAutoFcLangSet; +typedef SkAutoFc SkAutoFcObjectSet; +typedef SkAutoFc SkAutoFcPattern; + +static bool get_bool(FcPattern* pattern, const char object[], bool missing = false) { + FcBool value; + if (FcPatternGetBool(pattern, object, 0, &value) != FcResultMatch) { + return missing; + } + return value; +} + +static int get_int(FcPattern* pattern, const char object[], int missing) { + int value; + if (FcPatternGetInteger(pattern, object, 0, &value) != FcResultMatch) { + return missing; + } + return value; +} + +static const char* get_string(FcPattern* pattern, const char object[], const char* missing = "") { + FcChar8* value; + if (FcPatternGetString(pattern, object, 0, &value) != FcResultMatch) { + return missing; + } + return (const char*)value; +} + +static const FcMatrix* get_matrix(FcPattern* pattern, const char object[]) { + FcMatrix* matrix; + if (FcPatternGetMatrix(pattern, object, 0, &matrix) != FcResultMatch) { + return nullptr; + } + return matrix; +} + +enum SkWeakReturn { + kIsWeak_WeakReturn, + kIsStrong_WeakReturn, + kNoId_WeakReturn +}; +/** Ideally there would exist a call like + * FcResult FcPatternIsWeak(pattern, object, id, FcBool* isWeak); + * Sometime after 2.12.4 FcPatternGetWithBinding was added which can retrieve the binding. + * + * However, there is no such call and as of Fc 2.11.0 even FcPatternEquals ignores the weak bit. + * Currently, the only reliable way of finding the weak bit is by its effect on matching. + * The weak bit only affects the matching of FC_FAMILY and FC_POSTSCRIPT_NAME object values. + * A element with the weak bit is scored after FC_LANG, without the weak bit is scored before. + * Note that the weak bit is stored on the element, not on the value it holds. + */ +static SkWeakReturn is_weak(FcPattern* pattern, const char object[], int id) { + FCLocker::AssertHeld(); + + FcResult result; + + // Create a copy of the pattern with only the value 'pattern'['object'['id']] in it. + // Internally, FontConfig pattern objects are linked lists, so faster to remove from head. + SkAutoFcObjectSet requestedObjectOnly(FcObjectSetBuild(object, nullptr)); + SkAutoFcPattern minimal(FcPatternFilter(pattern, requestedObjectOnly)); + FcBool hasId = true; + for (int i = 0; hasId && i < id; ++i) { + hasId = FcPatternRemove(minimal, object, 0); + } + if (!hasId) { + return kNoId_WeakReturn; + } + FcValue value; + result = FcPatternGet(minimal, object, 0, &value); + if (result != FcResultMatch) { + return kNoId_WeakReturn; + } + while (hasId) { + hasId = FcPatternRemove(minimal, object, 1); + } + + // Create a font set with two patterns. + // 1. the same 'object' as minimal and a lang object with only 'nomatchlang'. + // 2. a different 'object' from minimal and a lang object with only 'matchlang'. + SkAutoFcFontSet fontSet; + + SkAutoFcLangSet strongLangSet; + FcLangSetAdd(strongLangSet, (const FcChar8*)"nomatchlang"); + SkAutoFcPattern strong(FcPatternDuplicate(minimal)); + FcPatternAddLangSet(strong, FC_LANG, strongLangSet); + + SkAutoFcLangSet weakLangSet; + FcLangSetAdd(weakLangSet, (const FcChar8*)"matchlang"); + SkAutoFcPattern weak; + FcPatternAddString(weak, object, (const FcChar8*)"nomatchstring"); + FcPatternAddLangSet(weak, FC_LANG, weakLangSet); + + FcFontSetAdd(fontSet, strong.release()); + FcFontSetAdd(fontSet, weak.release()); + + // Add 'matchlang' to the copy of the pattern. + FcPatternAddLangSet(minimal, FC_LANG, weakLangSet); + + // Run a match against the copy of the pattern. + // If the 'id' was weak, then we should match the pattern with 'matchlang'. + // If the 'id' was strong, then we should match the pattern with 'nomatchlang'. + + // Note that this config is only used for FcFontRenderPrepare, which we don't even want. + // However, there appears to be no way to match/sort without it. + SkAutoFcConfig config; + FcFontSet* fontSets[1] = { fontSet }; + SkAutoFcPattern match(FcFontSetMatch(config, fontSets, SK_ARRAY_COUNT(fontSets), + minimal, &result)); + + FcLangSet* matchLangSet; + FcPatternGetLangSet(match, FC_LANG, 0, &matchLangSet); + return FcLangEqual == FcLangSetHasLang(matchLangSet, (const FcChar8*)"matchlang") + ? kIsWeak_WeakReturn : kIsStrong_WeakReturn; +} + +/** Removes weak elements from either FC_FAMILY or FC_POSTSCRIPT_NAME objects in the property. + * This can be quite expensive, and should not be used more than once per font lookup. + * This removes all of the weak elements after the last strong element. + */ +static void remove_weak(FcPattern* pattern, const char object[]) { + FCLocker::AssertHeld(); + + SkAutoFcObjectSet requestedObjectOnly(FcObjectSetBuild(object, nullptr)); + SkAutoFcPattern minimal(FcPatternFilter(pattern, requestedObjectOnly)); + + int lastStrongId = -1; + int numIds; + SkWeakReturn result; + for (int id = 0; ; ++id) { + result = is_weak(minimal, object, 0); + if (kNoId_WeakReturn == result) { + numIds = id; + break; + } + if (kIsStrong_WeakReturn == result) { + lastStrongId = id; + } + SkAssertResult(FcPatternRemove(minimal, object, 0)); + } + + // If they were all weak, then leave the pattern alone. + if (lastStrongId < 0) { + return; + } + + // Remove everything after the last strong. + for (int id = lastStrongId + 1; id < numIds; ++id) { + SkAssertResult(FcPatternRemove(pattern, object, lastStrongId + 1)); + } +} + +static int map_range(SkScalar value, + SkScalar old_min, SkScalar old_max, + SkScalar new_min, SkScalar new_max) +{ + SkASSERT(old_min < old_max); + SkASSERT(new_min <= new_max); + return new_min + ((value - old_min) * (new_max - new_min) / (old_max - old_min)); +} + +struct MapRanges { + SkScalar old_val; + SkScalar new_val; +}; + +static SkScalar map_ranges(SkScalar val, MapRanges const ranges[], int rangesCount) { + // -Inf to [0] + if (val < ranges[0].old_val) { + return ranges[0].new_val; + } + + // Linear from [i] to [i+1] + for (int i = 0; i < rangesCount - 1; ++i) { + if (val < ranges[i+1].old_val) { + return map_range(val, ranges[i].old_val, ranges[i+1].old_val, + ranges[i].new_val, ranges[i+1].new_val); + } + } + + // From [n] to +Inf + // if (fcweight < Inf) + return ranges[rangesCount-1].new_val; +} + +#ifndef FC_WEIGHT_DEMILIGHT +#define FC_WEIGHT_DEMILIGHT 65 +#endif + +static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) { + typedef SkFontStyle SkFS; + + // FcWeightToOpenType was buggy until 2.12.4 + static constexpr MapRanges weightRanges[] = { + { FC_WEIGHT_THIN, SkFS::kThin_Weight }, + { FC_WEIGHT_EXTRALIGHT, SkFS::kExtraLight_Weight }, + { FC_WEIGHT_LIGHT, SkFS::kLight_Weight }, + { FC_WEIGHT_DEMILIGHT, 350 }, + { FC_WEIGHT_BOOK, 380 }, + { FC_WEIGHT_REGULAR, SkFS::kNormal_Weight }, + { FC_WEIGHT_MEDIUM, SkFS::kMedium_Weight }, + { FC_WEIGHT_DEMIBOLD, SkFS::kSemiBold_Weight }, + { FC_WEIGHT_BOLD, SkFS::kBold_Weight }, + { FC_WEIGHT_EXTRABOLD, SkFS::kExtraBold_Weight }, + { FC_WEIGHT_BLACK, SkFS::kBlack_Weight }, + { FC_WEIGHT_EXTRABLACK, SkFS::kExtraBlack_Weight }, + }; + SkScalar weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR), + weightRanges, SK_ARRAY_COUNT(weightRanges)); + + static constexpr MapRanges widthRanges[] = { + { FC_WIDTH_ULTRACONDENSED, SkFS::kUltraCondensed_Width }, + { FC_WIDTH_EXTRACONDENSED, SkFS::kExtraCondensed_Width }, + { FC_WIDTH_CONDENSED, SkFS::kCondensed_Width }, + { FC_WIDTH_SEMICONDENSED, SkFS::kSemiCondensed_Width }, + { FC_WIDTH_NORMAL, SkFS::kNormal_Width }, + { FC_WIDTH_SEMIEXPANDED, SkFS::kSemiExpanded_Width }, + { FC_WIDTH_EXPANDED, SkFS::kExpanded_Width }, + { FC_WIDTH_EXTRAEXPANDED, SkFS::kExtraExpanded_Width }, + { FC_WIDTH_ULTRAEXPANDED, SkFS::kUltraExpanded_Width }, + }; + SkScalar width = map_ranges(get_int(pattern, FC_WIDTH, FC_WIDTH_NORMAL), + widthRanges, SK_ARRAY_COUNT(widthRanges)); + + SkFS::Slant slant = SkFS::kUpright_Slant; + switch (get_int(pattern, FC_SLANT, FC_SLANT_ROMAN)) { + case FC_SLANT_ROMAN: slant = SkFS::kUpright_Slant; break; + case FC_SLANT_ITALIC : slant = SkFS::kItalic_Slant ; break; + case FC_SLANT_OBLIQUE: slant = SkFS::kOblique_Slant; break; + default: SkASSERT(false); break; + } + + return SkFontStyle(SkScalarRoundToInt(weight), SkScalarRoundToInt(width), slant); +} + +static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) { + FCLocker::AssertHeld(); + + typedef SkFontStyle SkFS; + + // FcWeightFromOpenType was buggy until 2.12.4 + static constexpr MapRanges weightRanges[] = { + { SkFS::kThin_Weight, FC_WEIGHT_THIN }, + { SkFS::kExtraLight_Weight, FC_WEIGHT_EXTRALIGHT }, + { SkFS::kLight_Weight, FC_WEIGHT_LIGHT }, + { 350, FC_WEIGHT_DEMILIGHT }, + { 380, FC_WEIGHT_BOOK }, + { SkFS::kNormal_Weight, FC_WEIGHT_REGULAR }, + { SkFS::kMedium_Weight, FC_WEIGHT_MEDIUM }, + { SkFS::kSemiBold_Weight, FC_WEIGHT_DEMIBOLD }, + { SkFS::kBold_Weight, FC_WEIGHT_BOLD }, + { SkFS::kExtraBold_Weight, FC_WEIGHT_EXTRABOLD }, + { SkFS::kBlack_Weight, FC_WEIGHT_BLACK }, + { SkFS::kExtraBlack_Weight, FC_WEIGHT_EXTRABLACK }, + }; + int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges)); + + static constexpr MapRanges widthRanges[] = { + { SkFS::kUltraCondensed_Width, FC_WIDTH_ULTRACONDENSED }, + { SkFS::kExtraCondensed_Width, FC_WIDTH_EXTRACONDENSED }, + { SkFS::kCondensed_Width, FC_WIDTH_CONDENSED }, + { SkFS::kSemiCondensed_Width, FC_WIDTH_SEMICONDENSED }, + { SkFS::kNormal_Width, FC_WIDTH_NORMAL }, + { SkFS::kSemiExpanded_Width, FC_WIDTH_SEMIEXPANDED }, + { SkFS::kExpanded_Width, FC_WIDTH_EXPANDED }, + { SkFS::kExtraExpanded_Width, FC_WIDTH_EXTRAEXPANDED }, + { SkFS::kUltraExpanded_Width, FC_WIDTH_ULTRAEXPANDED }, + }; + int width = map_ranges(style.width(), widthRanges, SK_ARRAY_COUNT(widthRanges)); + + int slant = FC_SLANT_ROMAN; + switch (style.slant()) { + case SkFS::kUpright_Slant: slant = FC_SLANT_ROMAN ; break; + case SkFS::kItalic_Slant : slant = FC_SLANT_ITALIC ; break; + case SkFS::kOblique_Slant: slant = FC_SLANT_OBLIQUE; break; + default: SkASSERT(false); break; + } + + FcPatternAddInteger(pattern, FC_WEIGHT, weight); + FcPatternAddInteger(pattern, FC_WIDTH , width); + FcPatternAddInteger(pattern, FC_SLANT , slant); +} + +class SkTypeface_stream : public SkTypeface_FreeType { +public: + SkTypeface_stream(std::unique_ptr data, + SkString familyName, const SkFontStyle& style, bool fixedWidth) + : INHERITED(style, fixedWidth) + , fFamilyName(std::move(familyName)) + , fData(std::move(data)) + { } + + void onGetFamilyName(SkString* familyName) const override { + *familyName = fFamilyName; + } + + void onGetFontDescriptor(SkFontDescriptor* desc, bool* serialize) const override { + *serialize = true; + } + + std::unique_ptr onOpenStream(int* ttcIndex) const override { + *ttcIndex = fData->getIndex(); + return fData->getStream()->duplicate(); + } + + std::unique_ptr onMakeFontData() const override { + return skstd::make_unique(*fData); + } + + sk_sp onMakeClone(const SkFontArguments& args) const override { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + return sk_make_sp(std::move(data), + fFamilyName, + this->fontStyle(), + this->isFixedPitch()); + } + +private: + SkString fFamilyName; + const std::unique_ptr fData; + + typedef SkTypeface_FreeType INHERITED; +}; + +class SkTypeface_fontconfig : public SkTypeface_FreeType { +public: + static sk_sp Make(SkAutoFcPattern pattern, SkString sysroot) { + return sk_sp(new SkTypeface_fontconfig(std::move(pattern), + std::move(sysroot))); + } + mutable SkAutoFcPattern fPattern; // Mutable for passing to FontConfig API. + const SkString fSysroot; + + void onGetFamilyName(SkString* familyName) const override { + *familyName = get_string(fPattern, FC_FAMILY); + } + + void onGetFontDescriptor(SkFontDescriptor* desc, bool* serialize) const override { + FCLocker lock; + desc->setFamilyName(get_string(fPattern, FC_FAMILY)); + desc->setFullName(get_string(fPattern, FC_FULLNAME)); + desc->setPostscriptName(get_string(fPattern, FC_POSTSCRIPT_NAME)); + desc->setStyle(this->fontStyle()); + *serialize = false; + } + + std::unique_ptr onOpenStream(int* ttcIndex) const override { + FCLocker lock; + *ttcIndex = get_int(fPattern, FC_INDEX, 0); + const char* filename = get_string(fPattern, FC_FILE); + // See FontAccessible for note on searching sysroot then non-sysroot path. + SkString resolvedFilename; + if (!fSysroot.isEmpty()) { + resolvedFilename = fSysroot; + resolvedFilename += filename; + if (sk_exists(resolvedFilename.c_str(), kRead_SkFILE_Flag)) { + filename = resolvedFilename.c_str(); + } + } + return SkStream::MakeFromFile(filename); + } + + void onFilterRec(SkScalerContextRec* rec) const override { + // FontConfig provides 10-scale-bitmap-fonts.conf which applies an inverse "pixelsize" + // matrix. It is not known if this .conf is active or not, so it is not clear if + // "pixelsize" should be applied before this matrix. Since using a matrix with a bitmap + // font isn't a great idea, only apply the matrix to outline fonts. + const FcMatrix* fcMatrix = get_matrix(fPattern, FC_MATRIX); + bool fcOutline = get_bool(fPattern, FC_OUTLINE, true); + if (fcOutline && fcMatrix) { + // fPost2x2 is column-major, left handed (y down). + // FcMatrix is column-major, right handed (y up). + SkMatrix fm; + fm.setAll(fcMatrix->xx,-fcMatrix->xy, 0, + -fcMatrix->yx, fcMatrix->yy, 0, + 0 , 0 , 1); + + SkMatrix sm; + rec->getMatrixFrom2x2(&sm); + + sm.preConcat(fm); + rec->fPost2x2[0][0] = sm.getScaleX(); + rec->fPost2x2[0][1] = sm.getSkewX(); + rec->fPost2x2[1][0] = sm.getSkewY(); + rec->fPost2x2[1][1] = sm.getScaleY(); + } + if (get_bool(fPattern, FC_EMBOLDEN)) { + rec->fFlags |= SkScalerContext::kEmbolden_Flag; + } + this->INHERITED::onFilterRec(rec); + } + + std::unique_ptr onGetAdvancedMetrics() const override { + std::unique_ptr info = + this->INHERITED::onGetAdvancedMetrics(); + + // Simulated fonts shouldn't be considered to be of the type of their data. + if (get_matrix(fPattern, FC_MATRIX) || get_bool(fPattern, FC_EMBOLDEN)) { + info->fType = SkAdvancedTypefaceMetrics::kOther_Font; + } + return info; + } + + sk_sp onMakeClone(const SkFontArguments& args) const override { + std::unique_ptr data = this->cloneFontData(args); + if (!data) { + return nullptr; + } + + SkString familyName; + this->getFamilyName(&familyName); + + return sk_make_sp(std::move(data), + familyName, + this->fontStyle(), + this->isFixedPitch()); + } + + ~SkTypeface_fontconfig() override { + // Hold the lock while unrefing the pattern. + FCLocker lock; + fPattern.reset(); + } + +private: + SkTypeface_fontconfig(SkAutoFcPattern pattern, SkString sysroot) + : INHERITED(skfontstyle_from_fcpattern(pattern), + FC_PROPORTIONAL != get_int(pattern, FC_SPACING, FC_PROPORTIONAL)) + , fPattern(std::move(pattern)) + , fSysroot(std::move(sysroot)) + { } + + typedef SkTypeface_FreeType INHERITED; +}; + +class SkFontMgr_fontconfig : public SkFontMgr { + mutable SkAutoFcConfig fFC; // Only mutable to avoid const cast when passed to FontConfig API. + const SkString fSysroot; + const sk_sp fFamilyNames; + const SkTypeface_FreeType::Scanner fScanner; + + class StyleSet : public SkFontStyleSet { + public: + StyleSet(sk_sp parent, SkAutoFcFontSet fontSet) + : fFontMgr(std::move(parent)), fFontSet(std::move(fontSet)) + { } + + ~StyleSet() override { + // Hold the lock while unrefing the font set. + FCLocker lock; + fFontSet.reset(); + } + + int count() override { return fFontSet->nfont; } + + void getStyle(int index, SkFontStyle* style, SkString* styleName) override { + if (index < 0 || fFontSet->nfont <= index) { + return; + } + + FCLocker lock; + if (style) { + *style = skfontstyle_from_fcpattern(fFontSet->fonts[index]); + } + if (styleName) { + *styleName = get_string(fFontSet->fonts[index], FC_STYLE); + } + } + + SkTypeface* createTypeface(int index) override { + FCLocker lock; + + FcPattern* match = fFontSet->fonts[index]; + return fFontMgr->createTypefaceFromFcPattern(match).release(); + } + + SkTypeface* matchStyle(const SkFontStyle& style) override { + FCLocker lock; + + SkAutoFcPattern pattern; + fcpattern_from_skfontstyle(style, pattern); + FcConfigSubstitute(fFontMgr->fFC, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcFontSet* fontSets[1] = { fFontSet }; + SkAutoFcPattern match(FcFontSetMatch(fFontMgr->fFC, + fontSets, SK_ARRAY_COUNT(fontSets), + pattern, &result)); + if (nullptr == match) { + return nullptr; + } + + return fFontMgr->createTypefaceFromFcPattern(match).release(); + } + + private: + sk_sp fFontMgr; + SkAutoFcFontSet fFontSet; + }; + + static bool FindName(const SkTDArray& list, const char* str) { + int count = list.count(); + for (int i = 0; i < count; ++i) { + if (!strcmp(list[i], str)) { + return true; + } + } + return false; + } + + static sk_sp GetFamilyNames(FcConfig* fcconfig) { + FCLocker lock; + + SkTDArray names; + SkTDArray sizes; + + static const FcSetName fcNameSet[] = { FcSetSystem, FcSetApplication }; + for (int setIndex = 0; setIndex < (int)SK_ARRAY_COUNT(fcNameSet); ++setIndex) { + // Return value of FcConfigGetFonts must not be destroyed. + FcFontSet* allFonts(FcConfigGetFonts(fcconfig, fcNameSet[setIndex])); + if (nullptr == allFonts) { + continue; + } + + for (int fontIndex = 0; fontIndex < allFonts->nfont; ++fontIndex) { + FcPattern* current = allFonts->fonts[fontIndex]; + for (int id = 0; ; ++id) { + FcChar8* fcFamilyName; + FcResult result = FcPatternGetString(current, FC_FAMILY, id, &fcFamilyName); + if (FcResultNoId == result) { + break; + } + if (FcResultMatch != result) { + continue; + } + const char* familyName = reinterpret_cast(fcFamilyName); + if (familyName && !FindName(names, familyName)) { + *names.append() = familyName; + *sizes.append() = strlen(familyName) + 1; + } + } + } + } + + return SkDataTable::MakeCopyArrays((void const *const *)names.begin(), + sizes.begin(), names.count()); + } + + static bool FindByFcPattern(SkTypeface* cached, void* ctx) { + SkTypeface_fontconfig* cshFace = static_cast(cached); + FcPattern* ctxPattern = static_cast(ctx); + return FcTrue == FcPatternEqual(cshFace->fPattern, ctxPattern); + } + + mutable SkMutex fTFCacheMutex; + mutable SkTypefaceCache fTFCache; + /** Creates a typeface using a typeface cache. + * @param pattern a complete pattern from FcFontRenderPrepare. + */ + sk_sp createTypefaceFromFcPattern(FcPattern* pattern) const { + FCLocker::AssertHeld(); + SkAutoMutexExclusive ama(fTFCacheMutex); + sk_sp face = fTFCache.findByProcAndRef(FindByFcPattern, pattern); + if (!face) { + FcPatternReference(pattern); + face = SkTypeface_fontconfig::Make(SkAutoFcPattern(pattern), fSysroot); + if (face) { + // Cannot hold the lock when calling add; an evicted typeface may need to lock. + FCLocker::Suspend suspend; + fTFCache.add(face); + } + } + return face; + } + +public: + /** Takes control of the reference to 'config'. */ + explicit SkFontMgr_fontconfig(FcConfig* config) + : fFC(config ? config : FcInitLoadConfigAndFonts()) + , fSysroot(reinterpret_cast(FcConfigGetSysRoot(fFC))) + , fFamilyNames(GetFamilyNames(fFC)) { } + + ~SkFontMgr_fontconfig() override { + // Hold the lock while unrefing the config. + FCLocker lock; + fFC.reset(); + } + +protected: + int onCountFamilies() const override { + return fFamilyNames->count(); + } + + void onGetFamilyName(int index, SkString* familyName) const override { + familyName->set(fFamilyNames->atStr(index)); + } + + SkFontStyleSet* onCreateStyleSet(int index) const override { + return this->onMatchFamily(fFamilyNames->atStr(index)); + } + + /** True if any string object value in the font is the same + * as a string object value in the pattern. + */ + static bool AnyMatching(FcPattern* font, FcPattern* pattern, const char* object) { + FcChar8* fontString; + FcChar8* patternString; + FcResult result; + // Set an arbitrary limit on the number of pattern object values to consider. + // TODO: re-write this to avoid N*M + static const int maxId = 16; + for (int patternId = 0; patternId < maxId; ++patternId) { + result = FcPatternGetString(pattern, object, patternId, &patternString); + if (FcResultNoId == result) { + break; + } + if (FcResultMatch != result) { + continue; + } + for (int fontId = 0; fontId < maxId; ++fontId) { + result = FcPatternGetString(font, object, fontId, &fontString); + if (FcResultNoId == result) { + break; + } + if (FcResultMatch != result) { + continue; + } + if (0 == FcStrCmpIgnoreCase(patternString, fontString)) { + return true; + } + } + } + return false; + } + + bool FontAccessible(FcPattern* font) const { + // FontConfig can return fonts which are unreadable. + const char* filename = get_string(font, FC_FILE, nullptr); + if (nullptr == filename) { + return false; + } + + // When sysroot was implemented in e96d7760886a3781a46b3271c76af99e15cb0146 (before 2.11.0) + // it was broken; mostly fixed in d17f556153fbaf8fe57fdb4fc1f0efa4313f0ecf (after 2.11.1). + // This leaves Debian 8 and 9 with broken support for this feature. + // As a result, this feature should not be used until at least 2.11.91. + // The broken support is mostly around not making all paths relative to the sysroot. + // However, even at 2.13.1 it is possible to get a mix of sysroot and non-sysroot paths, + // as any added file path not lexically starting with the sysroot will be unchanged. + // To allow users to add local app files outside the sysroot, + // prefer the sysroot but also look without the sysroot. + if (!fSysroot.isEmpty()) { + SkString resolvedFilename; + resolvedFilename = fSysroot; + resolvedFilename += filename; + if (sk_exists(resolvedFilename.c_str(), kRead_SkFILE_Flag)) { + return true; + } + } + return sk_exists(filename, kRead_SkFILE_Flag); + } + + static bool FontFamilyNameMatches(FcPattern* font, FcPattern* pattern) { + return AnyMatching(font, pattern, FC_FAMILY); + } + + static bool FontContainsCharacter(FcPattern* font, uint32_t character) { + FcResult result; + FcCharSet* matchCharSet; + for (int charSetId = 0; ; ++charSetId) { + result = FcPatternGetCharSet(font, FC_CHARSET, charSetId, &matchCharSet); + if (FcResultNoId == result) { + break; + } + if (FcResultMatch != result) { + continue; + } + if (FcCharSetHasChar(matchCharSet, character)) { + return true; + } + } + return false; + } + + SkFontStyleSet* onMatchFamily(const char familyName[]) const override { + if (!familyName) { + return nullptr; + } + FCLocker lock; + + SkAutoFcPattern pattern; + FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName); + FcConfigSubstitute(fFC, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcPattern* matchPattern; + SkAutoFcPattern strongPattern(nullptr); + if (familyName) { + strongPattern.reset(FcPatternDuplicate(pattern)); + remove_weak(strongPattern, FC_FAMILY); + matchPattern = strongPattern; + } else { + matchPattern = pattern; + } + + SkAutoFcFontSet matches; + // TODO: Some families have 'duplicates' due to symbolic links. + // The patterns are exactly the same except for the FC_FILE. + // It should be possible to collapse these patterns by normalizing. + static const FcSetName fcNameSet[] = { FcSetSystem, FcSetApplication }; + for (int setIndex = 0; setIndex < (int)SK_ARRAY_COUNT(fcNameSet); ++setIndex) { + // Return value of FcConfigGetFonts must not be destroyed. + FcFontSet* allFonts(FcConfigGetFonts(fFC, fcNameSet[setIndex])); + if (nullptr == allFonts) { + continue; + } + + for (int fontIndex = 0; fontIndex < allFonts->nfont; ++fontIndex) { + FcPattern* font = allFonts->fonts[fontIndex]; + if (FontAccessible(font) && FontFamilyNameMatches(font, matchPattern)) { + FcFontSetAdd(matches, FcFontRenderPrepare(fFC, pattern, font)); + } + } + } + + return new StyleSet(sk_ref_sp(this), std::move(matches)); + } + + SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& style) const override + { + FCLocker lock; + + SkAutoFcPattern pattern; + FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName); + fcpattern_from_skfontstyle(style, pattern); + FcConfigSubstitute(fFC, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + // We really want to match strong (prefered) and same (acceptable) only here. + // If a family name was specified, assume that any weak matches after the last strong match + // are weak (default) and ignore them. + // The reason for is that after substitution the pattern for 'sans-serif' looks like + // "wwwwwwwwwwwwwwswww" where there are many weak but preferred names, followed by defaults. + // So it is possible to have weakly matching but preferred names. + // In aliases, bindings are weak by default, so this is easy and common. + // If no family name was specified, we'll probably only get weak matches, but that's ok. + FcPattern* matchPattern; + SkAutoFcPattern strongPattern(nullptr); + if (familyName) { + strongPattern.reset(FcPatternDuplicate(pattern)); + remove_weak(strongPattern, FC_FAMILY); + matchPattern = strongPattern; + } else { + matchPattern = pattern; + } + + FcResult result; + SkAutoFcPattern font(FcFontMatch(fFC, pattern, &result)); + if (nullptr == font || !FontAccessible(font) || !FontFamilyNameMatches(font, matchPattern)) { + return nullptr; + } + + return createTypefaceFromFcPattern(font).release(); + } + + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const override + { + FCLocker lock; + + SkAutoFcPattern pattern; + if (familyName) { + FcValue familyNameValue; + familyNameValue.type = FcTypeString; + familyNameValue.u.s = reinterpret_cast(familyName); + FcPatternAddWeak(pattern, FC_FAMILY, familyNameValue, FcFalse); + } + fcpattern_from_skfontstyle(style, pattern); + + SkAutoFcCharSet charSet; + FcCharSetAddChar(charSet, character); + FcPatternAddCharSet(pattern, FC_CHARSET, charSet); + + if (bcp47Count > 0) { + SkASSERT(bcp47); + SkAutoFcLangSet langSet; + for (int i = bcp47Count; i --> 0;) { + FcLangSetAdd(langSet, (const FcChar8*)bcp47[i]); + } + FcPatternAddLangSet(pattern, FC_LANG, langSet); + } + + FcConfigSubstitute(fFC, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + SkAutoFcPattern font(FcFontMatch(fFC, pattern, &result)); + if (nullptr == font || !FontAccessible(font) || !FontContainsCharacter(font, character)) { + return nullptr; + } + + return createTypefaceFromFcPattern(font).release(); + } + + SkTypeface* onMatchFaceStyle(const SkTypeface* typeface, + const SkFontStyle& style) const override + { + //TODO: should the SkTypeface_fontconfig know its family? + const SkTypeface_fontconfig* fcTypeface = + static_cast(typeface); + return this->matchFamilyStyle(get_string(fcTypeface->fPattern, FC_FAMILY), style); + } + + sk_sp onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const override { + const size_t length = stream->getLength(); + if (length <= 0 || (1u << 30) < length) { + return nullptr; + } + + SkString name; + SkFontStyle style; + bool isFixedWidth = false; + if (!fScanner.scanFont(stream.get(), ttcIndex, &name, &style, &isFixedWidth, nullptr)) { + return nullptr; + } + + auto data = skstd::make_unique(std::move(stream), ttcIndex, nullptr, 0); + return sk_sp(new SkTypeface_stream(std::move(data), std::move(name), + style, isFixedWidth)); + } + + sk_sp onMakeFromStreamArgs(std::unique_ptr stream, + const SkFontArguments& args) const override { + using Scanner = SkTypeface_FreeType::Scanner; + bool isFixedPitch; + SkFontStyle style; + SkString name; + Scanner::AxisDefinitions axisDefinitions; + if (!fScanner.scanFont(stream.get(), args.getCollectionIndex(), + &name, &style, &isFixedPitch, &axisDefinitions)) + { + return nullptr; + } + + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + Scanner::computeAxisValues(axisDefinitions, args.getVariationDesignPosition(), + axisValues, name); + + auto data = skstd::make_unique(std::move(stream), args.getCollectionIndex(), + axisValues.get(), axisDefinitions.count()); + return sk_sp(new SkTypeface_stream(std::move(data), std::move(name), + style, isFixedPitch)); + } + + sk_sp onMakeFromData(sk_sp data, int ttcIndex) const override { + return this->makeFromStream(skstd::make_unique(std::move(data)), ttcIndex); + } + + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override { + return this->makeFromStream(SkStream::MakeFromFile(path), ttcIndex); + } + + sk_sp onMakeFromFontData(std::unique_ptr fontData) const override { + SkStreamAsset* stream(fontData->getStream()); + const size_t length = stream->getLength(); + if (length <= 0 || (1u << 30) < length) { + return nullptr; + } + + const int ttcIndex = fontData->getIndex(); + SkString name; + SkFontStyle style; + bool isFixedWidth = false; + if (!fScanner.scanFont(stream, ttcIndex, &name, &style, &isFixedWidth, nullptr)) { + return nullptr; + } + + return sk_sp(new SkTypeface_stream(std::move(fontData), std::move(name), + style, isFixedWidth)); + } + + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override { + sk_sp typeface(this->matchFamilyStyle(familyName, style)); + if (typeface) { + return typeface; + } + + return sk_sp(this->matchFamilyStyle(nullptr, style)); + } +}; + +SK_API sk_sp SkFontMgr_New_FontConfig(FcConfig* fc) { + return sk_make_sp(fc); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_fontconfig_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_fontconfig_factory.cpp new file mode 100644 index 0000000000..a011ec5c1e --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_fontconfig_factory.cpp @@ -0,0 +1,14 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFontMgr.h" +#include "include/core/SkTypes.h" +#include "include/ports/SkFontMgr_fontconfig.h" + +sk_sp SkFontMgr::Factory() { + return SkFontMgr_New_FontConfig(nullptr); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_fuchsia.cpp b/gfx/skia/skia/src/ports/SkFontMgr_fuchsia.cpp new file mode 100644 index 0000000000..ceb7c54ac9 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_fuchsia.cpp @@ -0,0 +1,505 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/ports/SkFontMgr_fuchsia.h" + +#include +#include +#include +#include +#include + +#include "src/core/SkFontDescriptor.h" +#include "src/ports/SkFontMgr_custom.h" + +#include "include/core/SkFontMgr.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" +#include "src/core/SkTypefaceCache.h" + +void UnmapMemory(const void* buffer, uint64_t size) { + static_assert(sizeof(void*) == sizeof(uint64_t), "pointers aren't 64-bit"); + zx::vmar::root_self()->unmap(reinterpret_cast(buffer), size); +} + +struct ReleaseSkDataContext { + uint64_t fBufferSize; + std::function releaseProc; + + ReleaseSkDataContext(uint64_t bufferSize, const std::function& releaseProc) + : fBufferSize(bufferSize), releaseProc(releaseProc) {} +}; + +void ReleaseSkData(const void* buffer, void* context) { + auto releaseSkDataContext = reinterpret_cast(context); + SkASSERT(releaseSkDataContext); + UnmapMemory(buffer, releaseSkDataContext->fBufferSize); + releaseSkDataContext->releaseProc(); + delete releaseSkDataContext; +} + +sk_sp MakeSkDataFromBuffer(const fuchsia::mem::Buffer& data, + std::function release_proc) { + uint64_t size = data.size; + uintptr_t buffer = 0; + zx_status_t status = zx::vmar::root_self()->map(0, data.vmo, 0, size, ZX_VM_PERM_READ, &buffer); + if (status != ZX_OK) return nullptr; + auto context = new ReleaseSkDataContext(size, release_proc); + return SkData::MakeWithProc(reinterpret_cast(buffer), size, ReleaseSkData, context); +} + +fuchsia::fonts::Slant SkToFuchsiaSlant(SkFontStyle::Slant slant) { + switch (slant) { + case SkFontStyle::kOblique_Slant: + return fuchsia::fonts::Slant::OBLIQUE; + case SkFontStyle::kItalic_Slant: + return fuchsia::fonts::Slant::ITALIC; + case SkFontStyle::kUpright_Slant: + default: + return fuchsia::fonts::Slant::UPRIGHT; + } +} + +SkFontStyle::Slant FuchsiaToSkSlant(fuchsia::fonts::Slant slant) { + switch (slant) { + case fuchsia::fonts::Slant::OBLIQUE: + return SkFontStyle::kOblique_Slant; + case fuchsia::fonts::Slant::ITALIC: + return SkFontStyle::kItalic_Slant; + case fuchsia::fonts::Slant::UPRIGHT: + default: + return SkFontStyle::kUpright_Slant; + } +} + +fuchsia::fonts::Width SkToFuchsiaWidth(SkFontStyle::Width width) { + switch (width) { + case SkFontStyle::Width::kUltraCondensed_Width: + return fuchsia::fonts::Width::ULTRA_CONDENSED; + case SkFontStyle::Width::kExtraCondensed_Width: + return fuchsia::fonts::Width::EXTRA_CONDENSED; + case SkFontStyle::Width::kCondensed_Width: + return fuchsia::fonts::Width::CONDENSED; + case SkFontStyle::Width::kSemiCondensed_Width: + return fuchsia::fonts::Width::SEMI_CONDENSED; + case SkFontStyle::Width::kNormal_Width: + return fuchsia::fonts::Width::NORMAL; + case SkFontStyle::Width::kSemiExpanded_Width: + return fuchsia::fonts::Width::SEMI_EXPANDED; + case SkFontStyle::Width::kExpanded_Width: + return fuchsia::fonts::Width::EXPANDED; + case SkFontStyle::Width::kExtraExpanded_Width: + return fuchsia::fonts::Width::EXTRA_EXPANDED; + case SkFontStyle::Width::kUltraExpanded_Width: + return fuchsia::fonts::Width::ULTRA_EXPANDED; + } +} + +// Tries to convert the given integer Skia style width value to the Fuchsia equivalent. +// +// On success, returns true. On failure, returns false, and `outFuchsiaWidth` is left untouched. +bool SkToFuchsiaWidth(int skWidth, fuchsia::fonts::Width* outFuchsiaWidth) { + if (skWidth < SkFontStyle::Width::kUltraCondensed_Width || + skWidth > SkFontStyle::Width::kUltraExpanded_Width) { + return false; + } + auto typedSkWidth = static_cast(skWidth); + *outFuchsiaWidth = SkToFuchsiaWidth(typedSkWidth); + return true; +} + +SkFontStyle::Width FuchsiaToSkWidth(fuchsia::fonts::Width width) { + switch (width) { + case fuchsia::fonts::Width::ULTRA_CONDENSED: + return SkFontStyle::Width::kUltraCondensed_Width; + case fuchsia::fonts::Width::EXTRA_CONDENSED: + return SkFontStyle::Width::kExtraCondensed_Width; + case fuchsia::fonts::Width::CONDENSED: + return SkFontStyle::Width::kCondensed_Width; + case fuchsia::fonts::Width::SEMI_CONDENSED: + return SkFontStyle::Width::kSemiCondensed_Width; + case fuchsia::fonts::Width::NORMAL: + return SkFontStyle::Width::kNormal_Width; + case fuchsia::fonts::Width::SEMI_EXPANDED: + return SkFontStyle::Width::kSemiExpanded_Width; + case fuchsia::fonts::Width::EXPANDED: + return SkFontStyle::Width::kExpanded_Width; + case fuchsia::fonts::Width::EXTRA_EXPANDED: + return SkFontStyle::Width::kExtraExpanded_Width; + case fuchsia::fonts::Width::ULTRA_EXPANDED: + return SkFontStyle::Width::kUltraExpanded_Width; + } +} + +fuchsia::fonts::Style2 SkToFuchsiaStyle(const SkFontStyle& style) { + fuchsia::fonts::Style2 fuchsiaStyle; + fuchsiaStyle.set_slant(SkToFuchsiaSlant(style.slant())).set_weight(style.weight()); + + fuchsia::fonts::Width fuchsiaWidth = fuchsia::fonts::Width::NORMAL; + if (SkToFuchsiaWidth(style.width(), &fuchsiaWidth)) { + fuchsiaStyle.set_width(fuchsiaWidth); + } + + return fuchsiaStyle; +} + +constexpr struct { + const char* fName; + fuchsia::fonts::GenericFontFamily fGenericFontFamily; +} kGenericFontFamiliesByName[] = {{"serif", fuchsia::fonts::GenericFontFamily::SERIF}, + {"sans", fuchsia::fonts::GenericFontFamily::SANS_SERIF}, + {"sans-serif", fuchsia::fonts::GenericFontFamily::SANS_SERIF}, + {"mono", fuchsia::fonts::GenericFontFamily::MONOSPACE}, + {"monospace", fuchsia::fonts::GenericFontFamily::MONOSPACE}, + {"cursive", fuchsia::fonts::GenericFontFamily::CURSIVE}, + {"fantasy", fuchsia::fonts::GenericFontFamily::FANTASY}, + {"system-ui", fuchsia::fonts::GenericFontFamily::SYSTEM_UI}, + {"emoji", fuchsia::fonts::GenericFontFamily::EMOJI}, + {"math", fuchsia::fonts::GenericFontFamily::MATH}, + {"fangsong", fuchsia::fonts::GenericFontFamily::FANGSONG}}; + +// Tries to find a generic font family with the given name. If none is found, returns false. +bool GetGenericFontFamilyByName(const char* name, + fuchsia::fonts::GenericFontFamily* outGenericFamily) { + if (!name) return false; + for (auto& genericFamily : kGenericFontFamiliesByName) { + if (strcasecmp(genericFamily.fName, name) == 0) { + *outGenericFamily = genericFamily.fGenericFontFamily; + return true; + } + } + return false; +} + +struct TypefaceId { + uint32_t bufferId; + uint32_t ttcIndex; + + bool operator==(TypefaceId& other) { + return std::tie(bufferId, ttcIndex) == std::tie(other.bufferId, other.ttcIndex); + } +} + +constexpr kNullTypefaceId = {0xFFFFFFFF, 0xFFFFFFFF}; + +class SkTypeface_Fuchsia : public SkTypeface_Stream { +public: + SkTypeface_Fuchsia(std::unique_ptr fontData, const SkFontStyle& style, + bool isFixedPitch, const SkString familyName, TypefaceId id) + : SkTypeface_Stream(std::move(fontData), style, isFixedPitch, + /*sys_font=*/true, familyName) + , fId(id) {} + + TypefaceId id() { return fId; } + +private: + TypefaceId fId; +}; + +sk_sp CreateTypefaceFromSkStream(std::unique_ptr stream, + const SkFontArguments& args, TypefaceId id) { + using Scanner = SkTypeface_FreeType::Scanner; + Scanner scanner; + bool isFixedPitch; + SkFontStyle style; + SkString name; + Scanner::AxisDefinitions axisDefinitions; + if (!scanner.scanFont(stream.get(), args.getCollectionIndex(), &name, &style, &isFixedPitch, + &axisDefinitions)) { + return nullptr; + } + + const SkFontArguments::VariationPosition position = args.getVariationDesignPosition(); + SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count()); + Scanner::computeAxisValues(axisDefinitions, position, axisValues, name); + + auto fontData = std::make_unique(std::move(stream), args.getCollectionIndex(), + axisValues.get(), axisDefinitions.count()); + return sk_make_sp(std::move(fontData), style, isFixedPitch, name, id); +} + +sk_sp CreateTypefaceFromSkData(sk_sp data, TypefaceId id) { + return CreateTypefaceFromSkStream(std::make_unique(std::move(data)), + SkFontArguments().setCollectionIndex(id.ttcIndex), id); +} + +class SkFontMgr_Fuchsia final : public SkFontMgr { +public: + SkFontMgr_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider); + ~SkFontMgr_Fuchsia() override; + +protected: + // SkFontMgr overrides. + int onCountFamilies() const override; + void onGetFamilyName(int index, SkString* familyName) const override; + SkFontStyleSet* onMatchFamily(const char familyName[]) const override; + SkFontStyleSet* onCreateStyleSet(int index) const override; + SkTypeface* onMatchFamilyStyle(const char familyName[], const SkFontStyle&) const override; + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override; + SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override; + sk_sp onMakeFromData(sk_sp, int ttcIndex) const override; + sk_sp onMakeFromStreamIndex(std::unique_ptr, + int ttcIndex) const override; + sk_sp onMakeFromStreamArgs(std::unique_ptr, + const SkFontArguments&) const override; + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override; + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override; + +private: + friend class SkFontStyleSet_Fuchsia; + + sk_sp FetchTypeface(const char familyName[], const SkFontStyle& style, + const char* bcp47[], int bcp47Count, SkUnichar character, + bool allow_fallback, bool exact_style_match) const; + + sk_sp GetOrCreateSkData(int bufferId, const fuchsia::mem::Buffer& buffer) const; + void OnSkDataDeleted(int bufferId) const; + + sk_sp GetOrCreateTypeface(TypefaceId id, const fuchsia::mem::Buffer& buffer) const; + + mutable fuchsia::fonts::ProviderSyncPtr fFontProvider; + + mutable SkMutex fCacheMutex; + + // Must be accessed only with fCacheMutex acquired. + mutable std::unordered_map fBufferCache; + mutable SkTypefaceCache fTypefaceCache; +}; + +class SkFontStyleSet_Fuchsia : public SkFontStyleSet { +public: + SkFontStyleSet_Fuchsia(sk_sp font_manager, std::string familyName, + std::vector styles) + : fFontManager(font_manager), fFamilyName(familyName), fStyles(styles) {} + + ~SkFontStyleSet_Fuchsia() override = default; + + int count() override { return fStyles.size(); } + + void getStyle(int index, SkFontStyle* style, SkString* styleName) override { + SkASSERT(index >= 0 && index < static_cast(fStyles.size())); + if (style) *style = fStyles[index]; + + // We don't have style names. Return an empty name. + if (styleName) styleName->reset(); + } + + SkTypeface* createTypeface(int index) override { + SkASSERT(index >= 0 && index < static_cast(fStyles.size())); + + if (fTypefaces.empty()) fTypefaces.resize(fStyles.size()); + + if (!fTypefaces[index]) { + fTypefaces[index] = fFontManager->FetchTypeface( + fFamilyName.c_str(), fStyles[index], /*bcp47=*/nullptr, + /*bcp47Count=*/0, /*character=*/0, + /*allow_fallback=*/false, /*exact_style_match=*/true); + } + + return SkSafeRef(fTypefaces[index].get()); + } + + SkTypeface* matchStyle(const SkFontStyle& pattern) override { return matchStyleCSS3(pattern); } + +private: + sk_sp fFontManager; + std::string fFamilyName; + std::vector fStyles; + std::vector> fTypefaces; +}; + +SkFontMgr_Fuchsia::SkFontMgr_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider) + : fFontProvider(std::move(provider)) {} + +SkFontMgr_Fuchsia::~SkFontMgr_Fuchsia() = default; + +int SkFontMgr_Fuchsia::onCountFamilies() const { + // Family enumeration is not supported. + return 0; +} + +void SkFontMgr_Fuchsia::onGetFamilyName(int index, SkString* familyName) const { + // Family enumeration is not supported. + familyName->reset(); +} + +SkFontStyleSet* SkFontMgr_Fuchsia::onCreateStyleSet(int index) const { + // Family enumeration is not supported. + return nullptr; +} + +SkFontStyleSet* SkFontMgr_Fuchsia::onMatchFamily(const char familyName[]) const { + fuchsia::fonts::FamilyName typedFamilyName; + typedFamilyName.name = familyName; + + fuchsia::fonts::FontFamilyInfo familyInfo; + int result = fFontProvider->GetFontFamilyInfo(typedFamilyName, &familyInfo); + if (result != ZX_OK || !familyInfo.has_styles() || familyInfo.styles().empty()) return nullptr; + + std::vector styles; + for (auto& style : familyInfo.styles()) { + styles.push_back(SkFontStyle(style.weight(), FuchsiaToSkWidth(style.width()), + FuchsiaToSkSlant(style.slant()))); + } + + return new SkFontStyleSet_Fuchsia(sk_ref_sp(this), familyInfo.name().name, std::move(styles)); +} + +SkTypeface* SkFontMgr_Fuchsia::onMatchFamilyStyle(const char familyName[], + const SkFontStyle& style) const { + sk_sp typeface = + FetchTypeface(familyName, style, /*bcp47=*/nullptr, + /*bcp47Count=*/0, /*character=*/0, + /*allow_fallback=*/false, /*exact_style_match=*/false); + return typeface.release(); +} + +SkTypeface* SkFontMgr_Fuchsia::onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], int bcp47Count, + SkUnichar character) const { + sk_sp typeface = + FetchTypeface(familyName, style, bcp47, bcp47Count, character, /*allow_fallback=*/true, + /*exact_style_match=*/false); + return typeface.release(); +} + +SkTypeface* SkFontMgr_Fuchsia::onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const { + return nullptr; +} + +sk_sp SkFontMgr_Fuchsia::onMakeFromData(sk_sp, int ttcIndex) const { + SkASSERT(false); + return nullptr; +} + +sk_sp SkFontMgr_Fuchsia::onMakeFromStreamIndex(std::unique_ptr asset, + int ttcIndex) const { + return makeFromStream(std::move(asset), SkFontArguments().setCollectionIndex(ttcIndex)); +} + +sk_sp SkFontMgr_Fuchsia::onMakeFromStreamArgs(std::unique_ptr asset, + const SkFontArguments& args) const { + return CreateTypefaceFromSkStream(std::move(asset), args, kNullTypefaceId); +} + +sk_sp SkFontMgr_Fuchsia::onMakeFromFile(const char path[], int ttcIndex) const { + return makeFromStream(std::make_unique(path), ttcIndex); +} + +sk_sp SkFontMgr_Fuchsia::onLegacyMakeTypeface(const char familyName[], + SkFontStyle style) const { + return sk_sp(matchFamilyStyle(familyName, style)); +} + +sk_sp SkFontMgr_Fuchsia::FetchTypeface(const char familyName[], + const SkFontStyle& style, const char* bcp47[], + int bcp47Count, SkUnichar character, + bool allow_fallback, + bool exact_style_match) const { + fuchsia::fonts::TypefaceQuery query; + query.set_style(SkToFuchsiaStyle(style)); + + if (bcp47Count > 0) { + std::vector languages{}; + for (int i = 0; i < bcp47Count; i++) { + fuchsia::intl::LocaleId localeId; + localeId.id = bcp47[i]; + languages.push_back(localeId); + } + query.set_languages(std::move(languages)); + } + + if (character) { + query.set_code_points({static_cast(character)}); + } + + // If family name is not specified or is a generic family name (e.g. "serif"), then enable + // fallback; otherwise, pass the family name as is. + fuchsia::fonts::GenericFontFamily genericFontFamily = + fuchsia::fonts::GenericFontFamily::SANS_SERIF; + bool isGenericFontFamily = GetGenericFontFamilyByName(familyName, &genericFontFamily); + if (!familyName || *familyName == '\0' || isGenericFontFamily) { + if (isGenericFontFamily) { + query.set_fallback_family(genericFontFamily); + } + allow_fallback = true; + } else { + fuchsia::fonts::FamilyName typedFamilyName{}; + typedFamilyName.name = familyName; + query.set_family(typedFamilyName); + } + + fuchsia::fonts::TypefaceRequestFlags flags{}; + if (!allow_fallback) flags |= fuchsia::fonts::TypefaceRequestFlags::EXACT_FAMILY; + if (exact_style_match) flags |= fuchsia::fonts::TypefaceRequestFlags::EXACT_STYLE; + + fuchsia::fonts::TypefaceRequest request; + request.set_query(std::move(query)); + request.set_flags(flags); + + fuchsia::fonts::TypefaceResponse response; + int result = fFontProvider->GetTypeface(std::move(request), &response); + if (result != ZX_OK) return nullptr; + + // The service may return an empty response if there is no font matching the request. + if (response.IsEmpty()) return nullptr; + + return GetOrCreateTypeface(TypefaceId{response.buffer_id(), response.font_index()}, + response.buffer()); +} + +sk_sp SkFontMgr_Fuchsia::GetOrCreateSkData(int bufferId, + const fuchsia::mem::Buffer& buffer) const { + fCacheMutex.assertHeld(); + + auto iter = fBufferCache.find(bufferId); + if (iter != fBufferCache.end()) { + return sk_ref_sp(iter->second); + } + auto font_mgr = sk_ref_sp(this); + auto data = MakeSkDataFromBuffer( + buffer, [font_mgr, bufferId]() { font_mgr->OnSkDataDeleted(bufferId); }); + if (!data) { + return nullptr; + } + fBufferCache[bufferId] = data.get(); + return data; +} + +void SkFontMgr_Fuchsia::OnSkDataDeleted(int bufferId) const { + SK_UNUSED bool wasFound = fBufferCache.erase(bufferId) != 0; + SkASSERT(wasFound); +} + +static bool FindByTypefaceId(SkTypeface* cachedTypeface, void* ctx) { + SkTypeface_Fuchsia* cachedFuchsiaTypeface = static_cast(cachedTypeface); + TypefaceId* id = static_cast(ctx); + + return cachedFuchsiaTypeface->id() == *id; +} + +sk_sp SkFontMgr_Fuchsia::GetOrCreateTypeface(TypefaceId id, + const fuchsia::mem::Buffer& buffer) const { + SkAutoMutexExclusive mutexLock(fCacheMutex); + + sk_sp cached = fTypefaceCache.findByProcAndRef(FindByTypefaceId, &id); + if (cached) return cached; + + sk_sp data = GetOrCreateSkData(id.bufferId, buffer); + if (!data) return nullptr; + + auto result = CreateTypefaceFromSkData(std::move(data), id); + fTypefaceCache.add(result); + return result; +} + +SK_API sk_sp SkFontMgr_New_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider) { + return sk_make_sp(std::move(provider)); +} diff --git a/gfx/skia/skia/src/ports/SkFontMgr_win_dw.cpp b/gfx/skia/skia/src/ports/SkFontMgr_win_dw.cpp new file mode 100644 index 0000000000..acf59190fe --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_win_dw.cpp @@ -0,0 +1,1227 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/utils/win/SkDWriteNTDDI_VERSION.h" + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "include/core/SkFontMgr.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/private/SkMutex.h" +#include "src/core/SkEndian.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkTypefaceCache.h" +#include "src/ports/SkTypeface_win_dw.h" +#include "src/utils/SkUTF.h" +#include "src/utils/win/SkDWrite.h" +#include "src/utils/win/SkDWriteFontFileStream.h" +#include "src/utils/win/SkHRESULT.h" +#include "src/utils/win/SkObjBase.h" +#include "src/utils/win/SkTScopedComPtr.h" + +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +class StreamFontFileLoader : public IDWriteFontFileLoader { +public: + // IUnknown methods + SK_STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override; + SK_STDMETHODIMP_(ULONG) AddRef() override; + SK_STDMETHODIMP_(ULONG) Release() override; + + // IDWriteFontFileLoader methods + SK_STDMETHODIMP CreateStreamFromKey( + void const* fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream** fontFileStream) override; + + // Takes ownership of stream. + static HRESULT Create(std::unique_ptr stream, + StreamFontFileLoader** streamFontFileLoader) { + *streamFontFileLoader = new StreamFontFileLoader(std::move(stream)); + if (nullptr == *streamFontFileLoader) { + return E_OUTOFMEMORY; + } + return S_OK; + } + +private: + StreamFontFileLoader(std::unique_ptr stream) + : fStream(std::move(stream)), fRefCount(1) + {} + virtual ~StreamFontFileLoader() { } + + std::unique_ptr fStream; + ULONG fRefCount; +}; + +SK_STDMETHODIMP StreamFontFileLoader::QueryInterface(REFIID iid, void** ppvObject) { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) { + *ppvObject = this; + AddRef(); + return S_OK; + } else { + *ppvObject = nullptr; + return E_NOINTERFACE; + } +} + +SK_STDMETHODIMP_(ULONG) StreamFontFileLoader::AddRef() { + return InterlockedIncrement(&fRefCount); +} + +SK_STDMETHODIMP_(ULONG) StreamFontFileLoader::Release() { + ULONG newCount = InterlockedDecrement(&fRefCount); + if (0 == newCount) { + delete this; + } + return newCount; +} + +SK_STDMETHODIMP StreamFontFileLoader::CreateStreamFromKey( + void const* fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream** fontFileStream) +{ + SkTScopedComPtr stream; + HR(SkDWriteFontFileStreamWrapper::Create(fStream->duplicate().release(), &stream)); + *fontFileStream = stream.release(); + return S_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class StreamFontFileEnumerator : public IDWriteFontFileEnumerator { +public: + // IUnknown methods + SK_STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override; + SK_STDMETHODIMP_(ULONG) AddRef() override; + SK_STDMETHODIMP_(ULONG) Release() override; + + // IDWriteFontFileEnumerator methods + SK_STDMETHODIMP MoveNext(BOOL* hasCurrentFile) override; + SK_STDMETHODIMP GetCurrentFontFile(IDWriteFontFile** fontFile) override; + + static HRESULT Create(IDWriteFactory* factory, IDWriteFontFileLoader* fontFileLoader, + StreamFontFileEnumerator** streamFontFileEnumerator) { + *streamFontFileEnumerator = new StreamFontFileEnumerator(factory, fontFileLoader); + if (nullptr == *streamFontFileEnumerator) { + return E_OUTOFMEMORY; + } + return S_OK; + } +private: + StreamFontFileEnumerator(IDWriteFactory* factory, IDWriteFontFileLoader* fontFileLoader); + virtual ~StreamFontFileEnumerator() { } + + ULONG fRefCount; + + SkTScopedComPtr fFactory; + SkTScopedComPtr fCurrentFile; + SkTScopedComPtr fFontFileLoader; + bool fHasNext; +}; + +StreamFontFileEnumerator::StreamFontFileEnumerator(IDWriteFactory* factory, + IDWriteFontFileLoader* fontFileLoader) + : fRefCount(1) + , fFactory(SkRefComPtr(factory)) + , fCurrentFile() + , fFontFileLoader(SkRefComPtr(fontFileLoader)) + , fHasNext(true) +{ } + +SK_STDMETHODIMP StreamFontFileEnumerator::QueryInterface(REFIID iid, void** ppvObject) { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileEnumerator)) { + *ppvObject = this; + AddRef(); + return S_OK; + } else { + *ppvObject = nullptr; + return E_NOINTERFACE; + } +} + +SK_STDMETHODIMP_(ULONG) StreamFontFileEnumerator::AddRef() { + return InterlockedIncrement(&fRefCount); +} + +SK_STDMETHODIMP_(ULONG) StreamFontFileEnumerator::Release() { + ULONG newCount = InterlockedDecrement(&fRefCount); + if (0 == newCount) { + delete this; + } + return newCount; +} + +SK_STDMETHODIMP StreamFontFileEnumerator::MoveNext(BOOL* hasCurrentFile) { + *hasCurrentFile = FALSE; + + if (!fHasNext) { + return S_OK; + } + fHasNext = false; + + UINT32 dummy = 0; + HR(fFactory->CreateCustomFontFileReference( + &dummy, //cannot be nullptr + sizeof(dummy), //even if this is 0 + fFontFileLoader.get(), + &fCurrentFile)); + + *hasCurrentFile = TRUE; + return S_OK; +} + +SK_STDMETHODIMP StreamFontFileEnumerator::GetCurrentFontFile(IDWriteFontFile** fontFile) { + if (fCurrentFile.get() == nullptr) { + *fontFile = nullptr; + return E_FAIL; + } + + *fontFile = SkRefComPtr(fCurrentFile.get()); + return S_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class StreamFontCollectionLoader : public IDWriteFontCollectionLoader { +public: + // IUnknown methods + SK_STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override; + SK_STDMETHODIMP_(ULONG) AddRef() override; + SK_STDMETHODIMP_(ULONG) Release() override; + + // IDWriteFontCollectionLoader methods + SK_STDMETHODIMP CreateEnumeratorFromKey( + IDWriteFactory* factory, + void const* collectionKey, + UINT32 collectionKeySize, + IDWriteFontFileEnumerator** fontFileEnumerator) override; + + static HRESULT Create(IDWriteFontFileLoader* fontFileLoader, + StreamFontCollectionLoader** streamFontCollectionLoader) { + *streamFontCollectionLoader = new StreamFontCollectionLoader(fontFileLoader); + if (nullptr == *streamFontCollectionLoader) { + return E_OUTOFMEMORY; + } + return S_OK; + } +private: + StreamFontCollectionLoader(IDWriteFontFileLoader* fontFileLoader) + : fRefCount(1) + , fFontFileLoader(SkRefComPtr(fontFileLoader)) + { } + virtual ~StreamFontCollectionLoader() { } + + ULONG fRefCount; + SkTScopedComPtr fFontFileLoader; +}; + +SK_STDMETHODIMP StreamFontCollectionLoader::QueryInterface(REFIID iid, void** ppvObject) { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontCollectionLoader)) { + *ppvObject = this; + AddRef(); + return S_OK; + } else { + *ppvObject = nullptr; + return E_NOINTERFACE; + } +} + +SK_STDMETHODIMP_(ULONG) StreamFontCollectionLoader::AddRef() { + return InterlockedIncrement(&fRefCount); +} + +SK_STDMETHODIMP_(ULONG) StreamFontCollectionLoader::Release() { + ULONG newCount = InterlockedDecrement(&fRefCount); + if (0 == newCount) { + delete this; + } + return newCount; +} + +SK_STDMETHODIMP StreamFontCollectionLoader::CreateEnumeratorFromKey( + IDWriteFactory* factory, + void const* collectionKey, + UINT32 collectionKeySize, + IDWriteFontFileEnumerator** fontFileEnumerator) +{ + SkTScopedComPtr enumerator; + HR(StreamFontFileEnumerator::Create(factory, fFontFileLoader.get(), &enumerator)); + *fontFileEnumerator = enumerator.release(); + return S_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class SkFontMgr_DirectWrite : public SkFontMgr { +public: + /** localeNameLength and defaultFamilyNameLength must include the null terminator. */ + SkFontMgr_DirectWrite(IDWriteFactory* factory, IDWriteFontCollection* fontCollection, + IDWriteFontFallback* fallback, + const WCHAR* localeName, int localeNameLength, + const WCHAR* defaultFamilyName, int defaultFamilyNameLength) + : fFactory(SkRefComPtr(factory)) + , fFontFallback(SkSafeRefComPtr(fallback)) + , fFontCollection(SkRefComPtr(fontCollection)) + , fLocaleName(localeNameLength) + , fDefaultFamilyName(defaultFamilyNameLength) + { + memcpy(fLocaleName.get(), localeName, localeNameLength * sizeof(WCHAR)); + memcpy(fDefaultFamilyName.get(), defaultFamilyName, defaultFamilyNameLength*sizeof(WCHAR)); + } + +protected: + int onCountFamilies() const override; + void onGetFamilyName(int index, SkString* familyName) const override; + SkFontStyleSet* onCreateStyleSet(int index) const override; + SkFontStyleSet* onMatchFamily(const char familyName[]) const override; + SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontstyle) const override; + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override; + SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontstyle) const override; + sk_sp onMakeFromStreamIndex(std::unique_ptr, int ttcIndex) const override; + sk_sp onMakeFromStreamArgs(std::unique_ptr, const SkFontArguments&) const override; + sk_sp onMakeFromData(sk_sp, int ttcIndex) const override; + sk_sp onMakeFromFile(const char path[], int ttcIndex) const override; + sk_sp onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override; + +private: + HRESULT getByFamilyName(const WCHAR familyName[], IDWriteFontFamily** fontFamily) const; + sk_sp fallback(const WCHAR* dwFamilyName, DWriteStyle, + const WCHAR* dwBcp47, UINT32 character) const; + sk_sp layoutFallback(const WCHAR* dwFamilyName, DWriteStyle, + const WCHAR* dwBcp47, UINT32 character) const; + + /** Creates a typeface using a typeface cache. */ + sk_sp makeTypefaceFromDWriteFont(IDWriteFontFace* fontFace, + IDWriteFont* font, + IDWriteFontFamily* fontFamily) const; + + SkTScopedComPtr fFactory; + SkTScopedComPtr fFontFallback; + SkTScopedComPtr fFontCollection; + SkSMallocWCHAR fLocaleName; + SkSMallocWCHAR fDefaultFamilyName; + mutable SkMutex fTFCacheMutex; + mutable SkTypefaceCache fTFCache; + + friend class SkFontStyleSet_DirectWrite; + friend class FontFallbackRenderer; +}; + +class SkFontStyleSet_DirectWrite : public SkFontStyleSet { +public: + SkFontStyleSet_DirectWrite(const SkFontMgr_DirectWrite* fontMgr, + IDWriteFontFamily* fontFamily) + : fFontMgr(SkRef(fontMgr)) + , fFontFamily(SkRefComPtr(fontFamily)) + { } + + int count() override; + void getStyle(int index, SkFontStyle* fs, SkString* styleName) override; + SkTypeface* createTypeface(int index) override; + SkTypeface* matchStyle(const SkFontStyle& pattern) override; + +private: + sk_sp fFontMgr; + SkTScopedComPtr fFontFamily; +}; + +static HRESULT are_same(IUnknown* a, IUnknown* b, bool& same) { + SkTScopedComPtr iunkA; + HRM(a->QueryInterface(&iunkA), "Failed to QI for a."); + + SkTScopedComPtr iunkB; + HRM(b->QueryInterface(&iunkB), "Failed to QI for b."); + + same = (iunkA.get() == iunkB.get()); + return S_OK; +} + +struct ProtoDWriteTypeface { + IDWriteFontFace* fDWriteFontFace; + IDWriteFont* fDWriteFont; + IDWriteFontFamily* fDWriteFontFamily; +}; + +static bool FindByDWriteFont(SkTypeface* cached, void* ctx) { + DWriteFontTypeface* cshFace = reinterpret_cast(cached); + ProtoDWriteTypeface* ctxFace = reinterpret_cast(ctx); + bool same; + + //Check to see if the two fonts are identical. + HRB(are_same(cshFace->fDWriteFont.get(), ctxFace->fDWriteFont, same)); + if (same) { + return true; + } + + HRB(are_same(cshFace->fDWriteFontFace.get(), ctxFace->fDWriteFontFace, same)); + if (same) { + return true; + } + + //Check if the two fonts share the same loader and have the same key. + UINT32 cshNumFiles; + UINT32 ctxNumFiles; + HRB(cshFace->fDWriteFontFace->GetFiles(&cshNumFiles, nullptr)); + HRB(ctxFace->fDWriteFontFace->GetFiles(&ctxNumFiles, nullptr)); + if (cshNumFiles != ctxNumFiles) { + return false; + } + + SkTScopedComPtr cshFontFile; + SkTScopedComPtr ctxFontFile; + HRB(cshFace->fDWriteFontFace->GetFiles(&cshNumFiles, &cshFontFile)); + HRB(ctxFace->fDWriteFontFace->GetFiles(&ctxNumFiles, &ctxFontFile)); + + //for (each file) { //we currently only admit fonts from one file. + SkTScopedComPtr cshFontFileLoader; + SkTScopedComPtr ctxFontFileLoader; + HRB(cshFontFile->GetLoader(&cshFontFileLoader)); + HRB(ctxFontFile->GetLoader(&ctxFontFileLoader)); + HRB(are_same(cshFontFileLoader.get(), ctxFontFileLoader.get(), same)); + if (!same) { + return false; + } + //} + + const void* cshRefKey; + UINT32 cshRefKeySize; + const void* ctxRefKey; + UINT32 ctxRefKeySize; + HRB(cshFontFile->GetReferenceKey(&cshRefKey, &cshRefKeySize)); + HRB(ctxFontFile->GetReferenceKey(&ctxRefKey, &ctxRefKeySize)); + if (cshRefKeySize != ctxRefKeySize) { + return false; + } + if (0 != memcmp(cshRefKey, ctxRefKey, ctxRefKeySize)) { + return false; + } + + //TODO: better means than comparing name strings? + //NOTE: .ttc and fake bold/italic will end up here. + SkTScopedComPtr cshFamilyNames; + SkTScopedComPtr cshFaceNames; + HRB(cshFace->fDWriteFontFamily->GetFamilyNames(&cshFamilyNames)); + HRB(cshFace->fDWriteFont->GetFaceNames(&cshFaceNames)); + UINT32 cshFamilyNameLength; + UINT32 cshFaceNameLength; + HRB(cshFamilyNames->GetStringLength(0, &cshFamilyNameLength)); + HRB(cshFaceNames->GetStringLength(0, &cshFaceNameLength)); + + SkTScopedComPtr ctxFamilyNames; + SkTScopedComPtr ctxFaceNames; + HRB(ctxFace->fDWriteFontFamily->GetFamilyNames(&ctxFamilyNames)); + HRB(ctxFace->fDWriteFont->GetFaceNames(&ctxFaceNames)); + UINT32 ctxFamilyNameLength; + UINT32 ctxFaceNameLength; + HRB(ctxFamilyNames->GetStringLength(0, &ctxFamilyNameLength)); + HRB(ctxFaceNames->GetStringLength(0, &ctxFaceNameLength)); + + if (cshFamilyNameLength != ctxFamilyNameLength || + cshFaceNameLength != ctxFaceNameLength) + { + return false; + } + + SkSMallocWCHAR cshFamilyName(cshFamilyNameLength+1); + SkSMallocWCHAR cshFaceName(cshFaceNameLength+1); + HRB(cshFamilyNames->GetString(0, cshFamilyName.get(), cshFamilyNameLength+1)); + HRB(cshFaceNames->GetString(0, cshFaceName.get(), cshFaceNameLength+1)); + + SkSMallocWCHAR ctxFamilyName(ctxFamilyNameLength+1); + SkSMallocWCHAR ctxFaceName(ctxFaceNameLength+1); + HRB(ctxFamilyNames->GetString(0, ctxFamilyName.get(), ctxFamilyNameLength+1)); + HRB(ctxFaceNames->GetString(0, ctxFaceName.get(), ctxFaceNameLength+1)); + + return wcscmp(cshFamilyName.get(), ctxFamilyName.get()) == 0 && + wcscmp(cshFaceName.get(), ctxFaceName.get()) == 0; +} + +sk_sp SkFontMgr_DirectWrite::makeTypefaceFromDWriteFont( + IDWriteFontFace* fontFace, + IDWriteFont* font, + IDWriteFontFamily* fontFamily) const { + SkAutoMutexExclusive ama(fTFCacheMutex); + ProtoDWriteTypeface spec = { fontFace, font, fontFamily }; + sk_sp face = fTFCache.findByProcAndRef(FindByDWriteFont, &spec); + if (nullptr == face) { + face = DWriteFontTypeface::Make(fFactory.get(), fontFace, font, fontFamily); + if (face) { + fTFCache.add(face); + } + } + return face; +} + +int SkFontMgr_DirectWrite::onCountFamilies() const { + return fFontCollection->GetFontFamilyCount(); +} + +void SkFontMgr_DirectWrite::onGetFamilyName(int index, SkString* familyName) const { + SkTScopedComPtr fontFamily; + HRVM(fFontCollection->GetFontFamily(index, &fontFamily), "Could not get requested family."); + + SkTScopedComPtr familyNames; + HRVM(fontFamily->GetFamilyNames(&familyNames), "Could not get family names."); + + sk_get_locale_string(familyNames.get(), fLocaleName.get(), familyName); +} + +SkFontStyleSet* SkFontMgr_DirectWrite::onCreateStyleSet(int index) const { + SkTScopedComPtr fontFamily; + HRNM(fFontCollection->GetFontFamily(index, &fontFamily), "Could not get requested family."); + + return new SkFontStyleSet_DirectWrite(this, fontFamily.get()); +} + +SkFontStyleSet* SkFontMgr_DirectWrite::onMatchFamily(const char familyName[]) const { + if (!familyName) { + return nullptr; + } + + SkSMallocWCHAR dwFamilyName; + HRN(sk_cstring_to_wchar(familyName, &dwFamilyName)); + + UINT32 index; + BOOL exists; + HRNM(fFontCollection->FindFamilyName(dwFamilyName.get(), &index, &exists), + "Failed while finding family by name."); + if (!exists) { + return nullptr; + } + + return this->onCreateStyleSet(index); +} + +SkTypeface* SkFontMgr_DirectWrite::onMatchFamilyStyle(const char familyName[], + const SkFontStyle& fontstyle) const { + sk_sp sset(this->matchFamily(familyName)); + return sset->matchStyle(fontstyle); +} + +class FontFallbackRenderer : public IDWriteTextRenderer { +public: + FontFallbackRenderer(const SkFontMgr_DirectWrite* outer, UINT32 character) + : fRefCount(1), fOuter(SkSafeRef(outer)), fCharacter(character), fResolvedTypeface(nullptr) { + } + + // IUnknown methods + SK_STDMETHODIMP QueryInterface(IID const& riid, void** ppvObject) override { + if (__uuidof(IUnknown) == riid || + __uuidof(IDWritePixelSnapping) == riid || + __uuidof(IDWriteTextRenderer) == riid) + { + *ppvObject = this; + this->AddRef(); + return S_OK; + } + *ppvObject = nullptr; + return E_FAIL; + } + + SK_STDMETHODIMP_(ULONG) AddRef() override { + return InterlockedIncrement(&fRefCount); + } + + SK_STDMETHODIMP_(ULONG) Release() override { + ULONG newCount = InterlockedDecrement(&fRefCount); + if (0 == newCount) { + delete this; + } + return newCount; + } + + // IDWriteTextRenderer methods + SK_STDMETHODIMP DrawGlyphRun( + void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, + DWRITE_GLYPH_RUN const* glyphRun, + DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, + IUnknown* clientDrawingEffect) override + { + if (!glyphRun->fontFace) { + HRM(E_INVALIDARG, "Glyph run without font face."); + } + + SkTScopedComPtr font; + HRM(fOuter->fFontCollection->GetFontFromFontFace(glyphRun->fontFace, &font), + "Could not get font from font face."); + + // It is possible that the font passed does not actually have the requested character, + // due to no font being found and getting the fallback font. + // Check that the font actually contains the requested character. + BOOL exists; + HRM(font->HasCharacter(fCharacter, &exists), "Could not find character."); + + if (exists) { + SkTScopedComPtr fontFamily; + HRM(font->GetFontFamily(&fontFamily), "Could not get family."); + fResolvedTypeface = fOuter->makeTypefaceFromDWriteFont(glyphRun->fontFace, + font.get(), + fontFamily.get()); + } + + return S_OK; + } + + SK_STDMETHODIMP DrawUnderline( + void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_UNDERLINE const* underline, + IUnknown* clientDrawingEffect) override + { return E_NOTIMPL; } + + SK_STDMETHODIMP DrawStrikethrough( + void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_STRIKETHROUGH const* strikethrough, + IUnknown* clientDrawingEffect) override + { return E_NOTIMPL; } + + SK_STDMETHODIMP DrawInlineObject( + void* clientDrawingContext, + FLOAT originX, + FLOAT originY, + IDWriteInlineObject* inlineObject, + BOOL isSideways, + BOOL isRightToLeft, + IUnknown* clientDrawingEffect) override + { return E_NOTIMPL; } + + // IDWritePixelSnapping methods + SK_STDMETHODIMP IsPixelSnappingDisabled( + void* clientDrawingContext, + BOOL* isDisabled) override + { + *isDisabled = FALSE; + return S_OK; + } + + SK_STDMETHODIMP GetCurrentTransform( + void* clientDrawingContext, + DWRITE_MATRIX* transform) override + { + const DWRITE_MATRIX ident = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }; + *transform = ident; + return S_OK; + } + + SK_STDMETHODIMP GetPixelsPerDip( + void* clientDrawingContext, + FLOAT* pixelsPerDip) override + { + *pixelsPerDip = 1.0f; + return S_OK; + } + + sk_sp ConsumeFallbackTypeface() { return std::move(fResolvedTypeface); } + +private: + virtual ~FontFallbackRenderer() { } + + ULONG fRefCount; + sk_sp fOuter; + UINT32 fCharacter; + sk_sp fResolvedTypeface; +}; + +class FontFallbackSource : public IDWriteTextAnalysisSource { +public: + FontFallbackSource(const WCHAR* string, UINT32 length, const WCHAR* locale, + IDWriteNumberSubstitution* numberSubstitution) + : fRefCount(1) + , fString(string) + , fLength(length) + , fLocale(locale) + , fNumberSubstitution(numberSubstitution) + { } + + // IUnknown methods + SK_STDMETHODIMP QueryInterface(IID const& riid, void** ppvObject) override { + if (__uuidof(IUnknown) == riid || + __uuidof(IDWriteTextAnalysisSource) == riid) + { + *ppvObject = this; + this->AddRef(); + return S_OK; + } + *ppvObject = nullptr; + return E_FAIL; + } + + SK_STDMETHODIMP_(ULONG) AddRef() override { + return InterlockedIncrement(&fRefCount); + } + + SK_STDMETHODIMP_(ULONG) Release() override { + ULONG newCount = InterlockedDecrement(&fRefCount); + if (0 == newCount) { + delete this; + } + return newCount; + } + + // IDWriteTextAnalysisSource methods + SK_STDMETHODIMP GetTextAtPosition( + UINT32 textPosition, + WCHAR const** textString, + UINT32* textLength) override + { + if (fLength <= textPosition) { + *textString = nullptr; + *textLength = 0; + return S_OK; + } + *textString = fString + textPosition; + *textLength = fLength - textPosition; + return S_OK; + } + + SK_STDMETHODIMP GetTextBeforePosition( + UINT32 textPosition, + WCHAR const** textString, + UINT32* textLength) override + { + if (textPosition < 1 || fLength <= textPosition) { + *textString = nullptr; + *textLength = 0; + return S_OK; + } + *textString = fString; + *textLength = textPosition; + return S_OK; + } + + SK_STDMETHODIMP_(DWRITE_READING_DIRECTION) GetParagraphReadingDirection() override { + // TODO: this is also interesting. + return DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; + } + + SK_STDMETHODIMP GetLocaleName( + UINT32 textPosition, + UINT32* textLength, + WCHAR const** localeName) override + { + *localeName = fLocale; + return S_OK; + } + + SK_STDMETHODIMP GetNumberSubstitution( + UINT32 textPosition, + UINT32* textLength, + IDWriteNumberSubstitution** numberSubstitution) override + { + *numberSubstitution = fNumberSubstitution; + return S_OK; + } + +private: + virtual ~FontFallbackSource() { } + + ULONG fRefCount; + const WCHAR* fString; + UINT32 fLength; + const WCHAR* fLocale; + IDWriteNumberSubstitution* fNumberSubstitution; +}; + +SkTypeface* SkFontMgr_DirectWrite::onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle& style, + const char* bcp47[], int bcp47Count, + SkUnichar character) const +{ + const DWriteStyle dwStyle(style); + + const WCHAR* dwFamilyName = nullptr; + SkSMallocWCHAR dwFamilyNameLocal; + if (familyName) { + HRN(sk_cstring_to_wchar(familyName, &dwFamilyNameLocal)); + dwFamilyName = dwFamilyNameLocal; + } + + const SkSMallocWCHAR* dwBcp47; + SkSMallocWCHAR dwBcp47Local; + if (bcp47Count < 1) { + dwBcp47 = &fLocaleName; + } else { + // TODO: support fallback stack. + // TODO: DirectWrite supports 'zh-CN' or 'zh-Hans', but 'zh' misses completely + // and may produce a Japanese font. + HRN(sk_cstring_to_wchar(bcp47[bcp47Count - 1], &dwBcp47Local)); + dwBcp47 = &dwBcp47Local; + } + + if (fFontFallback) { + return this->fallback(dwFamilyName, dwStyle, dwBcp47->get(), character).release(); + } + + // LayoutFallback may use the system font collection for fallback. + return this->layoutFallback(dwFamilyName, dwStyle, dwBcp47->get(), character).release(); +} + +sk_sp SkFontMgr_DirectWrite::fallback(const WCHAR* dwFamilyName, + DWriteStyle dwStyle, + const WCHAR* dwBcp47, + UINT32 character) const +{ + WCHAR str[16]; + UINT32 strLen = SkTo(SkUTF::ToUTF16(character, reinterpret_cast(str))); + + if (!fFontFallback) { + return nullptr; + } + + SkTScopedComPtr numberSubstitution; + HRNM(fFactory->CreateNumberSubstitution(DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, dwBcp47, + TRUE, &numberSubstitution), + "Could not create number substitution."); + SkTScopedComPtr fontFallbackSource( + new FontFallbackSource(str, strLen, dwBcp47, numberSubstitution.get())); + + UINT32 mappedLength; + SkTScopedComPtr font; + FLOAT scale; + HRNM(fFontFallback->MapCharacters(fontFallbackSource.get(), + 0, // textPosition, + strLen, + fFontCollection.get(), + dwFamilyName, + dwStyle.fWeight, + dwStyle.fSlant, + dwStyle.fWidth, + &mappedLength, + &font, + &scale), + "Could not map characters"); + if (!font.get()) { + return nullptr; + } + + SkTScopedComPtr fontFace; + HRNM(font->CreateFontFace(&fontFace), "Could not get font face from font."); + + SkTScopedComPtr fontFamily; + HRNM(font->GetFontFamily(&fontFamily), "Could not get family from font."); + return this->makeTypefaceFromDWriteFont(fontFace.get(), font.get(), fontFamily.get()); +} + +sk_sp SkFontMgr_DirectWrite::layoutFallback(const WCHAR* dwFamilyName, + DWriteStyle dwStyle, + const WCHAR* dwBcp47, + UINT32 character) const +{ + WCHAR str[16]; + UINT32 strLen = SkTo(SkUTF::ToUTF16(character, reinterpret_cast(str))); + + SkTScopedComPtr fallbackFormat; + HRNM(fFactory->CreateTextFormat(dwFamilyName ? dwFamilyName : L"", + fFontCollection.get(), + dwStyle.fWeight, + dwStyle.fSlant, + dwStyle.fWidth, + 72.0f, + dwBcp47, + &fallbackFormat), + "Could not create text format."); + + // No matter how the font collection is set on this IDWriteTextLayout, it is not possible to + // disable use of the system font collection in fallback. + SkTScopedComPtr fallbackLayout; + HRNM(fFactory->CreateTextLayout(str, strLen, fallbackFormat.get(), + 200.0f, 200.0f, + &fallbackLayout), + "Could not create text layout."); + + SkTScopedComPtr fontFallbackRenderer( + new FontFallbackRenderer(this, character)); + + HRNM(fallbackLayout->SetFontCollection(fFontCollection.get(), { 0, strLen }), + "Could not set layout font collection."); + HRNM(fallbackLayout->Draw(nullptr, fontFallbackRenderer.get(), 50.0f, 50.0f), + "Could not draw layout with renderer."); + + return fontFallbackRenderer->ConsumeFallbackTypeface(); +} + +SkTypeface* SkFontMgr_DirectWrite::onMatchFaceStyle(const SkTypeface* familyMember, + const SkFontStyle& fontstyle) const { + SkString familyName; + SkFontStyleSet_DirectWrite sset( + this, ((DWriteFontTypeface*)familyMember)->fDWriteFontFamily.get() + ); + return sset.matchStyle(fontstyle); +} + +template class SkAutoIDWriteUnregister { +public: + SkAutoIDWriteUnregister(IDWriteFactory* factory, T* unregister) + : fFactory(factory), fUnregister(unregister) + { } + + ~SkAutoIDWriteUnregister() { + if (fUnregister) { + unregister(fFactory, fUnregister); + } + } + + T* detatch() { + T* old = fUnregister; + fUnregister = nullptr; + return old; + } + +private: + HRESULT unregister(IDWriteFactory* factory, IDWriteFontFileLoader* unregister) { + return factory->UnregisterFontFileLoader(unregister); + } + + HRESULT unregister(IDWriteFactory* factory, IDWriteFontCollectionLoader* unregister) { + return factory->UnregisterFontCollectionLoader(unregister); + } + + IDWriteFactory* fFactory; + T* fUnregister; +}; + +sk_sp SkFontMgr_DirectWrite::onMakeFromStreamIndex(std::unique_ptr stream, + int ttcIndex) const { + SkTScopedComPtr fontFileLoader; + // This transfers ownership of stream to the new object. + HRN(StreamFontFileLoader::Create(std::move(stream), &fontFileLoader)); + HRN(fFactory->RegisterFontFileLoader(fontFileLoader.get())); + SkAutoIDWriteUnregister autoUnregisterFontFileLoader( + fFactory.get(), fontFileLoader.get()); + + SkTScopedComPtr fontCollectionLoader; + HRN(StreamFontCollectionLoader::Create(fontFileLoader.get(), &fontCollectionLoader)); + HRN(fFactory->RegisterFontCollectionLoader(fontCollectionLoader.get())); + SkAutoIDWriteUnregister autoUnregisterFontCollectionLoader( + fFactory.get(), fontCollectionLoader.get()); + + SkTScopedComPtr fontCollection; + HRN(fFactory->CreateCustomFontCollection(fontCollectionLoader.get(), nullptr, 0, &fontCollection)); + + // Find the first non-simulated font which has the given ttc index. + UINT32 familyCount = fontCollection->GetFontFamilyCount(); + for (UINT32 familyIndex = 0; familyIndex < familyCount; ++familyIndex) { + SkTScopedComPtr fontFamily; + HRN(fontCollection->GetFontFamily(familyIndex, &fontFamily)); + + UINT32 fontCount = fontFamily->GetFontCount(); + for (UINT32 fontIndex = 0; fontIndex < fontCount; ++fontIndex) { + SkTScopedComPtr font; + HRN(fontFamily->GetFont(fontIndex, &font)); + if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + continue; + } + + SkTScopedComPtr fontFace; + HRN(font->CreateFontFace(&fontFace)); + + int faceIndex = fontFace->GetIndex(); + if (faceIndex == ttcIndex) { + return DWriteFontTypeface::Make(fFactory.get(), + fontFace.get(), font.get(), fontFamily.get(), + autoUnregisterFontFileLoader.detatch(), + autoUnregisterFontCollectionLoader.detatch()); + } + } + } + + return nullptr; +} + +sk_sp SkFontMgr_DirectWrite::onMakeFromStreamArgs(std::unique_ptr stream, + const SkFontArguments& args) const { + SkTScopedComPtr fontFileLoader; + // This transfers ownership of stream to the new object. + HRN(StreamFontFileLoader::Create(std::move(stream), &fontFileLoader)); + HRN(fFactory->RegisterFontFileLoader(fontFileLoader.get())); + SkAutoIDWriteUnregister autoUnregisterFontFileLoader( + fFactory.get(), fontFileLoader.get()); + + SkTScopedComPtr fontCollectionLoader; + HRN(StreamFontCollectionLoader::Create(fontFileLoader.get(), &fontCollectionLoader)); + HRN(fFactory->RegisterFontCollectionLoader(fontCollectionLoader.get())); + SkAutoIDWriteUnregister autoUnregisterFontCollectionLoader( + fFactory.get(), fontCollectionLoader.get()); + + SkTScopedComPtr fontCollection; + HRN(fFactory->CreateCustomFontCollection(fontCollectionLoader.get(), nullptr, 0, + &fontCollection)); + + // Find the first non-simulated font which has the given ttc index. + UINT32 familyCount = fontCollection->GetFontFamilyCount(); + for (UINT32 familyIndex = 0; familyIndex < familyCount; ++familyIndex) { + SkTScopedComPtr fontFamily; + HRN(fontCollection->GetFontFamily(familyIndex, &fontFamily)); + + UINT32 fontCount = fontFamily->GetFontCount(); + for (UINT32 fontIndex = 0; fontIndex < fontCount; ++fontIndex) { + SkTScopedComPtr font; + HRN(fontFamily->GetFont(fontIndex, &font)); + + // Skip if the current font is simulated + if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) { + continue; + } + SkTScopedComPtr fontFace; + HRN(font->CreateFontFace(&fontFace)); + int faceIndex = fontFace->GetIndex(); + int ttcIndex = args.getCollectionIndex(); + + // Skip if the current face index does not match the ttcIndex + if (faceIndex != ttcIndex) { + continue; + } + +#if defined(NTDDI_WIN10_RS3) && NTDDI_VERSION >= NTDDI_WIN10_RS3 + + SkTScopedComPtr fontFace5; + if (SUCCEEDED(fontFace->QueryInterface(&fontFace5)) && fontFace5->HasVariations()) { + UINT32 fontAxisCount = fontFace5->GetFontAxisValueCount(); + UINT32 argsCoordCount = args.getVariationDesignPosition().coordinateCount; + SkAutoSTMalloc<8, DWRITE_FONT_AXIS_VALUE> fontAxisValues(fontAxisCount); + SkTScopedComPtr fontResource; + HRN(fontFace5->GetFontResource(&fontResource)); + // Set all axes by default values + HRN(fontResource->GetDefaultFontAxisValues(fontAxisValues, fontAxisCount)); + + for (UINT32 fontIndex = 0; fontIndex < fontAxisCount; ++fontIndex) { + for (UINT32 argsIndex = 0; argsIndex < argsCoordCount; ++argsIndex) { + if (SkEndian_SwapBE32(fontAxisValues[fontIndex].axisTag) == + args.getVariationDesignPosition().coordinates[argsIndex].axis) { + fontAxisValues[fontIndex].value = + args.getVariationDesignPosition().coordinates[argsIndex].value; + } + } + } + + SkTScopedComPtr fontFace5_Out; + HRN(fontResource->CreateFontFace(DWRITE_FONT_SIMULATIONS_NONE, + fontAxisValues.get(), + fontAxisCount, + &fontFace5_Out)); + fontFace.reset(); + HRN(fontFace5_Out->QueryInterface(&fontFace)); + } + +#endif + + return DWriteFontTypeface::Make( + fFactory.get(), fontFace.get(), font.get(), fontFamily.get(), + autoUnregisterFontFileLoader.detatch(), + autoUnregisterFontCollectionLoader.detatch()); + } + } + + return nullptr; +} + +sk_sp SkFontMgr_DirectWrite::onMakeFromData(sk_sp data, int ttcIndex) const { + return this->makeFromStream(skstd::make_unique(std::move(data)), ttcIndex); +} + +sk_sp SkFontMgr_DirectWrite::onMakeFromFile(const char path[], int ttcIndex) const { + return this->makeFromStream(SkStream::MakeFromFile(path), ttcIndex); +} + +HRESULT SkFontMgr_DirectWrite::getByFamilyName(const WCHAR wideFamilyName[], + IDWriteFontFamily** fontFamily) const { + UINT32 index; + BOOL exists; + HR(fFontCollection->FindFamilyName(wideFamilyName, &index, &exists)); + + if (exists) { + HR(fFontCollection->GetFontFamily(index, fontFamily)); + } + return S_OK; +} + +sk_sp SkFontMgr_DirectWrite::onLegacyMakeTypeface(const char familyName[], + SkFontStyle style) const { + SkTScopedComPtr fontFamily; + const DWriteStyle dwStyle(style); + if (familyName) { + SkSMallocWCHAR dwFamilyName; + if (SUCCEEDED(sk_cstring_to_wchar(familyName, &dwFamilyName))) { + this->getByFamilyName(dwFamilyName, &fontFamily); + if (!fontFamily && fFontFallback) { + return this->fallback(dwFamilyName, dwStyle, fLocaleName.get(), 32); + } + } + } + + if (!fontFamily) { + if (fFontFallback) { + return this->fallback(nullptr, dwStyle, fLocaleName.get(), 32); + } + // SPI_GETNONCLIENTMETRICS lfMessageFont can fail in Win8. (DisallowWin32kSystemCalls) + // layoutFallback causes DCHECK in Chromium. (Uses system font collection.) + HRNM(this->getByFamilyName(fDefaultFamilyName, &fontFamily), + "Could not create DWrite font family from LOGFONT."); + } + + if (!fontFamily) { + // Could not obtain the default font. + HRNM(fFontCollection->GetFontFamily(0, &fontFamily), + "Could not get default-default font family."); + } + + SkTScopedComPtr font; + HRNM(fontFamily->GetFirstMatchingFont(dwStyle.fWeight, dwStyle.fWidth, dwStyle.fSlant, &font), + "Could not get matching font."); + + SkTScopedComPtr fontFace; + HRNM(font->CreateFontFace(&fontFace), "Could not create font face."); + + return this->makeTypefaceFromDWriteFont(fontFace.get(), font.get(), fontFamily.get()); +} + +/////////////////////////////////////////////////////////////////////////////// + +int SkFontStyleSet_DirectWrite::count() { + return fFontFamily->GetFontCount(); +} + +SkTypeface* SkFontStyleSet_DirectWrite::createTypeface(int index) { + SkTScopedComPtr font; + HRNM(fFontFamily->GetFont(index, &font), "Could not get font."); + + SkTScopedComPtr fontFace; + HRNM(font->CreateFontFace(&fontFace), "Could not create font face."); + + return fFontMgr->makeTypefaceFromDWriteFont(fontFace.get(), font.get(), fFontFamily.get()).release(); +} + +void SkFontStyleSet_DirectWrite::getStyle(int index, SkFontStyle* fs, SkString* styleName) { + SkTScopedComPtr font; + HRVM(fFontFamily->GetFont(index, &font), "Could not get font."); + + if (fs) { + *fs = get_style(font.get()); + } + + if (styleName) { + SkTScopedComPtr faceNames; + if (SUCCEEDED(font->GetFaceNames(&faceNames))) { + sk_get_locale_string(faceNames.get(), fFontMgr->fLocaleName.get(), styleName); + } + } +} + +SkTypeface* SkFontStyleSet_DirectWrite::matchStyle(const SkFontStyle& pattern) { + SkTScopedComPtr font; + DWriteStyle dwStyle(pattern); + // TODO: perhaps use GetMatchingFonts and get the least simulated? + HRNM(fFontFamily->GetFirstMatchingFont(dwStyle.fWeight, dwStyle.fWidth, dwStyle.fSlant, &font), + "Could not match font in family."); + + SkTScopedComPtr fontFace; + HRNM(font->CreateFontFace(&fontFace), "Could not create font face."); + + return fFontMgr->makeTypefaceFromDWriteFont(fontFace.get(), font.get(), + fFontFamily.get()).release(); +} + +//////////////////////////////////////////////////////////////////////////////// +#include "include/ports/SkTypeface_win.h" + +SK_API sk_sp SkFontMgr_New_DirectWrite(IDWriteFactory* factory, + IDWriteFontCollection* collection) { + return SkFontMgr_New_DirectWrite(factory, collection, nullptr); +} + +SK_API sk_sp SkFontMgr_New_DirectWrite(IDWriteFactory* factory, + IDWriteFontCollection* collection, + IDWriteFontFallback* fallback) { + if (nullptr == factory) { + factory = sk_get_dwrite_factory(); + if (nullptr == factory) { + return nullptr; + } + } + + SkTScopedComPtr systemFontCollection; + if (nullptr == collection) { + HRNM(factory->GetSystemFontCollection(&systemFontCollection, FALSE), + "Could not get system font collection."); + collection = systemFontCollection.get(); + } + + // It is possible to have been provided a font fallback when factory2 is not available. + SkTScopedComPtr systemFontFallback; + if (nullptr == fallback) { + SkTScopedComPtr factory2; + if (!SUCCEEDED(factory->QueryInterface(&factory2))) { + // IUnknown::QueryInterface states that if it fails, punk will be set to nullptr. + // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/26/96777.aspx + SkASSERT_RELEASE(nullptr == factory2.get()); + } else { + HRNM(factory2->GetSystemFontFallback(&systemFontFallback), + "Could not get system fallback."); + fallback = systemFontFallback.get(); + } + } + + const WCHAR* defaultFamilyName = L""; + int defaultFamilyNameLen = 1; + NONCLIENTMETRICSW metrics; + metrics.cbSize = sizeof(metrics); + if (nullptr == fallback) { + if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0)) { + defaultFamilyName = metrics.lfMessageFont.lfFaceName; + defaultFamilyNameLen = LF_FACESIZE; + } + } + + WCHAR localeNameStorage[LOCALE_NAME_MAX_LENGTH]; + const WCHAR* localeName = L""; + int localeNameLen = 1; + + // Dynamically load GetUserDefaultLocaleName function, as it is not available on XP. + SkGetUserDefaultLocaleNameProc getUserDefaultLocaleNameProc = nullptr; + HRESULT hr = SkGetGetUserDefaultLocaleNameProc(&getUserDefaultLocaleNameProc); + if (nullptr == getUserDefaultLocaleNameProc) { + SK_TRACEHR(hr, "Could not get GetUserDefaultLocaleName."); + } else { + int size = getUserDefaultLocaleNameProc(localeNameStorage, LOCALE_NAME_MAX_LENGTH); + if (size) { + localeName = localeNameStorage; + localeNameLen = size; + } + } + + return sk_make_sp(factory, collection, fallback, + localeName, localeNameLen, + defaultFamilyName, defaultFamilyNameLen); +} + +#include "include/ports/SkFontMgr_indirect.h" +SK_API sk_sp SkFontMgr_New_DirectWriteRenderer(sk_sp proxy) { + sk_sp impl(SkFontMgr_New_DirectWrite()); + if (!impl) { + return nullptr; + } + return sk_make_sp(std::move(impl), std::move(proxy)); +} +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkFontMgr_win_dw_factory.cpp b/gfx/skia/skia/src/ports/SkFontMgr_win_dw_factory.cpp new file mode 100644 index 0000000000..08195c569f --- /dev/null +++ b/gfx/skia/skia/src/ports/SkFontMgr_win_dw_factory.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) // And !SKIA_GDI? + +#include "include/core/SkFontMgr.h" +#include "include/ports/SkTypeface_win.h" + +sk_sp SkFontMgr::Factory() { + return SkFontMgr_New_DirectWrite(); +} + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkGlobalInitialization_default.cpp b/gfx/skia/skia/src/ports/SkGlobalInitialization_default.cpp new file mode 100644 index 0000000000..5b4ca74985 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkGlobalInitialization_default.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkFlattenable.h" + +#if defined(SK_DISABLE_EFFECT_DESERIALIZATION) + + void SkFlattenable::PrivateInitializer::InitEffects() {} + void SkFlattenable::PrivateInitializer::InitImageFilters() {} + +#else + + #include "include/core/SkColorFilter.h" + #include "src/effects/SkDashImpl.h" + #include "include/effects/SkGradientShader.h" + #include "include/core/SkMaskFilter.h" + + #include "include/effects/SkBlurImageFilter.h" + #include "include/effects/SkComposeImageFilter.h" + + /* + * Register most effects for deserialization. + * + * None of these are strictly required for Skia to operate, so if you're + * not using deserialization yourself, you can define + * SK_DISABLE_EFFECT_SERIALIZATION, or modify/replace this file as needed. + */ + void SkFlattenable::PrivateInitializer::InitEffects() { + // Shaders. + SkGradientShader::RegisterFlattenables(); + + // Color filters. + SkColorFilter::RegisterFlattenables(); + + // Mask filters. + SkMaskFilter::RegisterFlattenables(); + + // Path effects. + SK_REGISTER_FLATTENABLE(SkDashImpl); + } + + /* + * Register SkImageFilters for deserialization. + * + * None of these are strictly required for Skia to operate, so if you're + * not using deserialization yourself, you can define + * SK_DISABLE_EFFECT_SERIALIZATION, or modify/replace this file as needed. + */ + void SkFlattenable::PrivateInitializer::InitImageFilters() { + SkBlurImageFilter::RegisterFlattenables(); + SkComposeImageFilter::RegisterFlattenables(); + } + +#endif diff --git a/gfx/skia/skia/src/ports/SkImageEncoder_CG.cpp b/gfx/skia/skia/src/ports/SkImageEncoder_CG.cpp new file mode 100644 index 0000000000..af43829deb --- /dev/null +++ b/gfx/skia/skia/src/ports/SkImageEncoder_CG.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/images/SkImageEncoderPriv.h" + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#include "include/core/SkBitmap.h" +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "include/utils/mac/SkCGUtils.h" +#include "src/core/SkStreamPriv.h" +#include "src/utils/mac/SkUniqueCFRef.h" + +#ifdef SK_BUILD_FOR_MAC +#include +#endif + +#ifdef SK_BUILD_FOR_IOS +#include +#include +#include +#endif + +static size_t consumer_put(void* info, const void* buffer, size_t count) { + SkWStream* stream = reinterpret_cast(info); + return stream->write(buffer, count) ? count : 0; +} + +static void consumer_release(void* info) { + // we do nothing, since by design we don't "own" the stream (i.e. info) +} + +static SkUniqueCFRef SkStreamToCGDataConsumer(SkWStream* stream) { + CGDataConsumerCallbacks procs; + procs.putBytes = consumer_put; + procs.releaseConsumer = consumer_release; + // we don't own/reference the stream, so it our consumer must not live + // longer that our caller's ownership of the stream + return SkUniqueCFRef(CGDataConsumerCreate(stream, &procs)); +} + +static SkUniqueCFRef SkStreamToImageDestination(SkWStream* stream, + CFStringRef type) { + SkUniqueCFRef consumer = SkStreamToCGDataConsumer(stream); + if (nullptr == consumer) { + return nullptr; + } + + return SkUniqueCFRef( + CGImageDestinationCreateWithDataConsumer(consumer.get(), type, 1, nullptr)); +} + +/* Encode bitmaps via CGImageDestination. We setup a DataConsumer which writes + to our SkWStream. Since we don't reference/own the SkWStream, our consumer + must only live for the duration of the onEncode() method. + */ +bool SkEncodeImageWithCG(SkWStream* stream, const SkPixmap& pixmap, SkEncodedImageFormat format) { + SkBitmap bm; + if (!bm.installPixels(pixmap)) { + return false; + } + bm.setImmutable(); + + CFStringRef type; + switch (format) { + case SkEncodedImageFormat::kICO: + type = kUTTypeICO; + break; + case SkEncodedImageFormat::kBMP: + type = kUTTypeBMP; + break; + case SkEncodedImageFormat::kGIF: + type = kUTTypeGIF; + break; + case SkEncodedImageFormat::kJPEG: + type = kUTTypeJPEG; + break; + case SkEncodedImageFormat::kPNG: + // PNG encoding an ARGB_4444 bitmap gives the following errors in GM: + // : CGImageDestinationAddImage image could not be converted to destination + // format. + // : CGImageDestinationFinalize image destination does not have enough images + // So instead we copy to 8888. + if (bm.colorType() == kARGB_4444_SkColorType) { + SkBitmap bitmapN32; + bitmapN32.allocPixels(bm.info().makeColorType(kN32_SkColorType)); + bm.readPixels(bitmapN32.info(), bitmapN32.getPixels(), bitmapN32.rowBytes(), 0, 0); + bm.swap(bitmapN32); + } + type = kUTTypePNG; + break; + default: + return false; + } + + SkUniqueCFRef dst = SkStreamToImageDestination(stream, type); + if (nullptr == dst) { + return false; + } + + SkUniqueCFRef image(SkCreateCGImageRef(bm)); + if (nullptr == image) { + return false; + } + + CGImageDestinationAddImage(dst.get(), image.get(), nullptr); + return CGImageDestinationFinalize(dst.get()); +} + +#endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) diff --git a/gfx/skia/skia/src/ports/SkImageEncoder_WIC.cpp b/gfx/skia/skia/src/ports/SkImageEncoder_WIC.cpp new file mode 100644 index 0000000000..af1ec698ac --- /dev/null +++ b/gfx/skia/skia/src/ports/SkImageEncoder_WIC.cpp @@ -0,0 +1,198 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#if defined(SK_BUILD_FOR_WIN) + +#include "include/core/SkBitmap.h" +#include "include/core/SkImageEncoder.h" +#include "include/core/SkStream.h" +#include "include/core/SkUnPreMultiply.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkAutoMalloc.h" +#include "src/images/SkImageEncoderPriv.h" +#include "src/utils/win/SkAutoCoInitialize.h" +#include "src/utils/win/SkIStream.h" +#include "src/utils/win/SkTScopedComPtr.h" +#include + +//All Windows SDKs back to XPSP2 export the CLSID_WICImagingFactory symbol. +//In the Windows8 SDK the CLSID_WICImagingFactory symbol is still exported +//but CLSID_WICImagingFactory is then #defined to CLSID_WICImagingFactory2. +//Undo this #define if it has been done so that we link against the symbols +//we intended to link against on all SDKs. +#if defined(CLSID_WICImagingFactory) +#undef CLSID_WICImagingFactory +#endif + +bool SkEncodeImageWithWIC(SkWStream* stream, const SkPixmap& pixmap, + SkEncodedImageFormat format, int quality) { + GUID type; + switch (format) { + case SkEncodedImageFormat::kJPEG: + type = GUID_ContainerFormatJpeg; + break; + case SkEncodedImageFormat::kPNG: + type = GUID_ContainerFormatPng; + break; + default: + return false; + } + SkBitmap bitmapOrig; + if (!bitmapOrig.installPixels(pixmap)) { + return false; + } + bitmapOrig.setImmutable(); + + // First convert to BGRA if necessary. + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(bitmapOrig.info().makeColorType(kBGRA_8888_SkColorType)) || + !bitmapOrig.readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), 0, 0)) + { + return false; + } + + // WIC expects unpremultiplied pixels. Unpremultiply if necessary. + if (kPremul_SkAlphaType == bitmap.alphaType()) { + uint8_t* pixels = reinterpret_cast(bitmap.getPixels()); + for (int y = 0; y < bitmap.height(); ++y) { + for (int x = 0; x < bitmap.width(); ++x) { + uint8_t* bytes = pixels + y * bitmap.rowBytes() + x * bitmap.bytesPerPixel(); + SkPMColor* src = reinterpret_cast(bytes); + SkColor* dst = reinterpret_cast(bytes); + *dst = SkUnPreMultiply::PMColorToColor(*src); + } + } + } + + // Finally, if we are performing a jpeg encode, we must convert to BGR. + void* pixels = bitmap.getPixels(); + size_t rowBytes = bitmap.rowBytes(); + SkAutoMalloc pixelStorage; + WICPixelFormatGUID formatDesired = GUID_WICPixelFormat32bppBGRA; + if (SkEncodedImageFormat::kJPEG == format) { + formatDesired = GUID_WICPixelFormat24bppBGR; + rowBytes = SkAlign4(bitmap.width() * 3); + pixelStorage.reset(rowBytes * bitmap.height()); + for (int y = 0; y < bitmap.height(); y++) { + uint8_t* dstRow = SkTAddOffset(pixelStorage.get(), y * rowBytes); + for (int x = 0; x < bitmap.width(); x++) { + uint32_t bgra = *bitmap.getAddr32(x, y); + dstRow[0] = (uint8_t) ((bgra >> 0) & 0xFF); + dstRow[1] = (uint8_t) ((bgra >> 8) & 0xFF); + dstRow[2] = (uint8_t) ((bgra >> 16) & 0xFF); + dstRow += 3; + } + } + + pixels = pixelStorage.get(); + } + + + //Initialize COM. + SkAutoCoInitialize scopedCo; + if (!scopedCo.succeeded()) { + return false; + } + + HRESULT hr = S_OK; + + //Create Windows Imaging Component ImagingFactory. + SkTScopedComPtr piImagingFactory; + if (SUCCEEDED(hr)) { + hr = CoCreateInstance( + CLSID_WICImagingFactory + , nullptr + , CLSCTX_INPROC_SERVER + , IID_PPV_ARGS(&piImagingFactory) + ); + } + + //Convert the SkWStream to an IStream. + SkTScopedComPtr piStream; + if (SUCCEEDED(hr)) { + hr = SkWIStream::CreateFromSkWStream(stream, &piStream); + } + + //Create an encode of the appropriate type. + SkTScopedComPtr piEncoder; + if (SUCCEEDED(hr)) { + hr = piImagingFactory->CreateEncoder(type, nullptr, &piEncoder); + } + + if (SUCCEEDED(hr)) { + hr = piEncoder->Initialize(piStream.get(), WICBitmapEncoderNoCache); + } + + //Create a the frame. + SkTScopedComPtr piBitmapFrameEncode; + SkTScopedComPtr piPropertybag; + if (SUCCEEDED(hr)) { + hr = piEncoder->CreateNewFrame(&piBitmapFrameEncode, &piPropertybag); + } + + if (SUCCEEDED(hr)) { + PROPBAG2 name; + memset(&name, 0, sizeof(name)); + name.dwType = PROPBAG2_TYPE_DATA; + name.vt = VT_R4; + name.pstrName = const_cast(L"ImageQuality"); + + VARIANT value; + VariantInit(&value); + value.vt = VT_R4; + value.fltVal = (FLOAT)(quality / 100.0); + + //Ignore result code. + // This returns E_FAIL if the named property is not in the bag. + //TODO(bungeman) enumerate the properties, + // write and set hr iff property exists. + piPropertybag->Write(1, &name, &value); + } + if (SUCCEEDED(hr)) { + hr = piBitmapFrameEncode->Initialize(piPropertybag.get()); + } + + //Set the size of the frame. + const UINT width = bitmap.width(); + const UINT height = bitmap.height(); + if (SUCCEEDED(hr)) { + hr = piBitmapFrameEncode->SetSize(width, height); + } + + //Set the pixel format of the frame. If native encoded format cannot match BGRA, + //it will choose the closest pixel format that it supports. + WICPixelFormatGUID formatGUID = formatDesired; + if (SUCCEEDED(hr)) { + hr = piBitmapFrameEncode->SetPixelFormat(&formatGUID); + } + if (SUCCEEDED(hr)) { + //Be sure the image format is the one requested. + hr = IsEqualGUID(formatGUID, formatDesired) ? S_OK : E_FAIL; + } + + //Write the pixels into the frame. + if (SUCCEEDED(hr)) { + hr = piBitmapFrameEncode->WritePixels(height, + (UINT) rowBytes, + (UINT) rowBytes * height, + reinterpret_cast(pixels)); + } + + if (SUCCEEDED(hr)) { + hr = piBitmapFrameEncode->Commit(); + } + + if (SUCCEEDED(hr)) { + hr = piEncoder->Commit(); + } + + return SUCCEEDED(hr); +} + +#endif // defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkImageGeneratorCG.cpp b/gfx/skia/skia/src/ports/SkImageGeneratorCG.cpp new file mode 100644 index 0000000000..bda9a04beb --- /dev/null +++ b/gfx/skia/skia/src/ports/SkImageGeneratorCG.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkEncodedOrigin.h" +#include "include/ports/SkImageGeneratorCG.h" +#include "include/private/SkTemplates.h" +#include "include/utils/mac/SkCGUtils.h" +#include "src/core/SkPixmapPriv.h" +#include "src/utils/mac/SkUniqueCFRef.h" + +#ifdef SK_BUILD_FOR_MAC +#include +#endif + +#ifdef SK_BUILD_FOR_IOS +#include +#include +#include +#endif + +namespace { +class ImageGeneratorCG : public SkImageGenerator { +public: + ImageGeneratorCG(const SkImageInfo&, SkUniqueCFRef imageSrc, + sk_sp data, SkEncodedOrigin); + +protected: + sk_sp onRefEncodedData() override; + + bool onGetPixels(const SkImageInfo&, void* pixels, size_t rowBytes, const Options&) override; + +private: + const SkUniqueCFRef fImageSrc; + const sk_sp fData; + const SkEncodedOrigin fOrigin; + + typedef SkImageGenerator INHERITED; +}; + +static SkUniqueCFRef data_to_CGImageSrc(SkData* data) { + SkUniqueCFRef cgData( + CGDataProviderCreateWithData(data, data->data(), data->size(), nullptr)); + if (!cgData) { + return nullptr; + } + return SkUniqueCFRef( + CGImageSourceCreateWithDataProvider(cgData.get(), nullptr)); +} + +} // namespace + +std::unique_ptr SkImageGeneratorCG::MakeFromEncodedCG(sk_sp data) { + SkUniqueCFRef imageSrc = data_to_CGImageSrc(data.get()); + if (!imageSrc) { + return nullptr; + } + + SkUniqueCFRef properties( + CGImageSourceCopyPropertiesAtIndex(imageSrc.get(), 0, nullptr)); + if (!properties) { + return nullptr; + } + + CFNumberRef widthRef = static_cast( + CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth)); + CFNumberRef heightRef = static_cast( + CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight)); + if (nullptr == widthRef || nullptr == heightRef) { + return nullptr; + } + + int width, height; + if (!CFNumberGetValue(widthRef , kCFNumberIntType, &width ) || + !CFNumberGetValue(heightRef, kCFNumberIntType, &height)) + { + return nullptr; + } + + bool hasAlpha = bool(CFDictionaryGetValue(properties.get(), kCGImagePropertyHasAlpha)); + SkAlphaType alphaType = hasAlpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType; + SkImageInfo info = SkImageInfo::MakeS32(width, height, alphaType); + + SkEncodedOrigin origin = kDefault_SkEncodedOrigin; + CFNumberRef orientationRef = static_cast( + CFDictionaryGetValue(properties.get(), kCGImagePropertyOrientation)); + int originInt; + if (orientationRef && CFNumberGetValue(orientationRef, kCFNumberIntType, &originInt)) { + origin = (SkEncodedOrigin) originInt; + } + + if (SkPixmapPriv::ShouldSwapWidthHeight(origin)) { + info = SkPixmapPriv::SwapWidthHeight(info); + } + + // FIXME: We have the opportunity to extract color space information here, + // though I think it makes sense to wait until we understand how + // we want to communicate it to the generator. + + return std::unique_ptr(new ImageGeneratorCG(info, std::move(imageSrc), + std::move(data), origin)); +} + +ImageGeneratorCG::ImageGeneratorCG(const SkImageInfo& info, SkUniqueCFRef src, + sk_sp data, SkEncodedOrigin origin) + : INHERITED(info) + , fImageSrc(std::move(src)) + , fData(std::move(data)) + , fOrigin(origin) +{} + +sk_sp ImageGeneratorCG::onRefEncodedData() { + return fData; +} + +bool ImageGeneratorCG::onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const Options&) +{ + if (kN32_SkColorType != info.colorType()) { + // FIXME: Support other colorTypes. + return false; + } + + switch (info.alphaType()) { + case kOpaque_SkAlphaType: + if (kOpaque_SkAlphaType != this->getInfo().alphaType()) { + return false; + } + break; + case kPremul_SkAlphaType: + break; + default: + return false; + } + + SkUniqueCFRef image(CGImageSourceCreateImageAtIndex(fImageSrc.get(), 0, nullptr)); + if (!image) { + return false; + } + + SkPixmap dst(info, pixels, rowBytes); + auto decode = [&image](const SkPixmap& pm) { + // FIXME: Using SkCopyPixelsFromCGImage (as opposed to swizzling + // ourselves) greatly restricts the color and alpha types that we + // support. If we swizzle ourselves, we can add support for: + // kUnpremul_SkAlphaType + // 16-bit per component RGBA + // kGray_8_SkColorType + // Additionally, it would be interesting to compare the performance + // of SkSwizzler with CG's built in swizzler. + return SkCopyPixelsFromCGImage(pm, image.get()); + }; + return SkPixmapPriv::Orient(dst, fOrigin, decode); +} diff --git a/gfx/skia/skia/src/ports/SkImageGeneratorWIC.cpp b/gfx/skia/skia/src/ports/SkImageGeneratorWIC.cpp new file mode 100644 index 0000000000..cea31ad641 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkImageGeneratorWIC.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/ports/SkImageGeneratorWIC.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkMakeUnique.h" +#include "src/utils/win/SkIStream.h" +#include "src/utils/win/SkTScopedComPtr.h" + +#include + +// All Windows SDKs back to XPSP2 export the CLSID_WICImagingFactory symbol. +// In the Windows8 SDK the CLSID_WICImagingFactory symbol is still exported +// but CLSID_WICImagingFactory is then #defined to CLSID_WICImagingFactory2. +// Undo this #define if it has been done so that we link against the symbols +// we intended to link against on all SDKs. +#if defined(CLSID_WICImagingFactory) + #undef CLSID_WICImagingFactory +#endif + +namespace { +class ImageGeneratorWIC : public SkImageGenerator { +public: + /* + * Takes ownership of the imagingFactory + * Takes ownership of the imageSource + */ + ImageGeneratorWIC(const SkImageInfo& info, IWICImagingFactory* imagingFactory, + IWICBitmapSource* imageSource, sk_sp); +protected: + sk_sp onRefEncodedData() override; + + bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options&) + override; + +private: + SkTScopedComPtr fImagingFactory; + SkTScopedComPtr fImageSource; + sk_sp fData; + + typedef SkImageGenerator INHERITED; +}; +} // namespace + +std::unique_ptr SkImageGeneratorWIC::MakeFromEncodedWIC(sk_sp data) { + // Create Windows Imaging Component ImagingFactory. + SkTScopedComPtr imagingFactory; + HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&imagingFactory)); + if (FAILED(hr)) { + return nullptr; + } + + // Create an IStream. + SkTScopedComPtr iStream; + // Note that iStream will take ownership of the new memory stream because + // we set |deleteOnRelease| to true. + hr = SkIStream::CreateFromSkStream(skstd::make_unique(data), &iStream); + if (FAILED(hr)) { + return nullptr; + } + + // Create the decoder from the stream. + SkTScopedComPtr decoder; + hr = imagingFactory->CreateDecoderFromStream(iStream.get(), nullptr, + WICDecodeMetadataCacheOnDemand, &decoder); + if (FAILED(hr)) { + return nullptr; + } + + // Select the first frame from the decoder. + SkTScopedComPtr imageFrame; + hr = decoder->GetFrame(0, &imageFrame); + if (FAILED(hr)) { + return nullptr; + } + + // Treat the frame as an image source. + SkTScopedComPtr imageSource; + hr = imageFrame->QueryInterface(IID_PPV_ARGS(&imageSource)); + if (FAILED(hr)) { + return nullptr; + } + + // Get the size of the image. + UINT width; + UINT height; + hr = imageSource->GetSize(&width, &height); + if (FAILED(hr)) { + return nullptr; + } + + // Get the encoded pixel format. + WICPixelFormatGUID format; + hr = imageSource->GetPixelFormat(&format); + if (FAILED(hr)) { + return nullptr; + } + + // Recommend kOpaque if the image is opaque and kPremul otherwise. + // FIXME: We are stuck recommending kPremul for all indexed formats + // (Ex: GUID_WICPixelFormat8bppIndexed) because we don't have + // a way to check if the image has alpha. + SkAlphaType alphaType = kPremul_SkAlphaType; + + if (GUID_WICPixelFormat16bppBGR555 == format || + GUID_WICPixelFormat16bppBGR565 == format || + GUID_WICPixelFormat32bppBGR101010 == format || + GUID_WICPixelFormatBlackWhite == format || + GUID_WICPixelFormat2bppGray == format || + GUID_WICPixelFormat4bppGray == format || + GUID_WICPixelFormat8bppGray == format || + GUID_WICPixelFormat16bppGray == format || + GUID_WICPixelFormat16bppGrayFixedPoint == format || + GUID_WICPixelFormat16bppGrayHalf == format || + GUID_WICPixelFormat32bppGrayFloat == format || + GUID_WICPixelFormat32bppGrayFixedPoint == format || + GUID_WICPixelFormat32bppRGBE == format || + GUID_WICPixelFormat24bppRGB == format || + GUID_WICPixelFormat24bppBGR == format || + GUID_WICPixelFormat32bppBGR == format || + GUID_WICPixelFormat48bppRGB == format || + GUID_WICPixelFormat48bppBGR == format || + GUID_WICPixelFormat48bppRGBFixedPoint == format || + GUID_WICPixelFormat48bppBGRFixedPoint == format || + GUID_WICPixelFormat48bppRGBHalf == format || + GUID_WICPixelFormat64bppRGBFixedPoint == format || + GUID_WICPixelFormat64bppRGBHalf == format || + GUID_WICPixelFormat96bppRGBFixedPoint == format || + GUID_WICPixelFormat128bppRGBFloat == format || + GUID_WICPixelFormat128bppRGBFixedPoint == format || + GUID_WICPixelFormat32bppRGB == format || + GUID_WICPixelFormat64bppRGB == format || + GUID_WICPixelFormat96bppRGBFloat == format || + GUID_WICPixelFormat32bppCMYK == format || + GUID_WICPixelFormat64bppCMYK == format || + GUID_WICPixelFormat8bppY == format || + GUID_WICPixelFormat8bppCb == format || + GUID_WICPixelFormat8bppCr == format || + GUID_WICPixelFormat16bppCbCr == format) + { + alphaType = kOpaque_SkAlphaType; + } + + // FIXME: If we change the implementation to handle swizzling ourselves, + // we can support more output formats. + SkImageInfo info = SkImageInfo::MakeS32(width, height, alphaType); + return std::unique_ptr( + new ImageGeneratorWIC(info, imagingFactory.release(), imageSource.release(), + std::move(data))); +} + +ImageGeneratorWIC::ImageGeneratorWIC(const SkImageInfo& info, + IWICImagingFactory* imagingFactory, IWICBitmapSource* imageSource, sk_sp data) + : INHERITED(info) + , fImagingFactory(imagingFactory) + , fImageSource(imageSource) + , fData(std::move(data)) +{} + +sk_sp ImageGeneratorWIC::onRefEncodedData() { + return fData; +} + +bool ImageGeneratorWIC::onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const Options&) { + if (kN32_SkColorType != info.colorType()) { + return false; + } + + // Create a format converter. + SkTScopedComPtr formatConverter; + HRESULT hr = fImagingFactory->CreateFormatConverter(&formatConverter); + if (FAILED(hr)) { + return false; + } + + GUID format = GUID_WICPixelFormat32bppPBGRA; + if (kUnpremul_SkAlphaType == info.alphaType()) { + format = GUID_WICPixelFormat32bppBGRA; + } + + hr = formatConverter->Initialize(fImageSource.get(), format, WICBitmapDitherTypeNone, nullptr, + 0.0, WICBitmapPaletteTypeCustom); + if (FAILED(hr)) { + return false; + } + + // Treat the format converter as an image source. + SkTScopedComPtr formatConverterSrc; + hr = formatConverter->QueryInterface(IID_PPV_ARGS(&formatConverterSrc)); + if (FAILED(hr)) { + return false; + } + + // Set the destination pixels. + hr = formatConverterSrc->CopyPixels(nullptr, (UINT) rowBytes, (UINT) rowBytes * info.height(), + (BYTE*) pixels); + + return SUCCEEDED(hr); +} diff --git a/gfx/skia/skia/src/ports/SkImageGenerator_none.cpp b/gfx/skia/skia/src/ports/SkImageGenerator_none.cpp new file mode 100644 index 0000000000..197876505d --- /dev/null +++ b/gfx/skia/skia/src/ports/SkImageGenerator_none.cpp @@ -0,0 +1,12 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkImageGenerator.h" + +std::unique_ptr SkImageGenerator::MakeFromEncodedImpl(sk_sp) { + return nullptr; +} diff --git a/gfx/skia/skia/src/ports/SkImageGenerator_skia.cpp b/gfx/skia/skia/src/ports/SkImageGenerator_skia.cpp new file mode 100644 index 0000000000..c62a8435f9 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkImageGenerator_skia.cpp @@ -0,0 +1,13 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "src/codec/SkCodecImageGenerator.h" + +std::unique_ptr SkImageGenerator::MakeFromEncodedImpl(sk_sp data) { + return SkCodecImageGenerator::MakeFromEncodedCodec(std::move(data)); +} diff --git a/gfx/skia/skia/src/ports/SkMemory_malloc.cpp b/gfx/skia/skia/src/ports/SkMemory_malloc.cpp new file mode 100644 index 0000000000..7fc6eb19f5 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkMemory_malloc.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" + +#include + +#define SK_DEBUGFAILF(fmt, ...) \ + SkASSERT((SkDebugf(fmt"\n", __VA_ARGS__), false)) + +static inline void sk_out_of_memory(size_t size) { + SK_DEBUGFAILF("sk_out_of_memory (asked for " SK_SIZE_T_SPECIFIER " bytes)", + size); +#if defined(IS_FUZZING_WITH_AFL) + exit(1); +#else + abort(); +#endif +} + +static inline void* throw_on_failure(size_t size, void* p) { + if (size > 0 && p == nullptr) { + // If we've got a nullptr here, the only reason we should have failed is running out of RAM. + sk_out_of_memory(size); + } + return p; +} + +bool sk_abort_is_enabled() { return true; } + +void sk_abort_no_print() { +#if defined(SK_BUILD_FOR_WIN) && defined(SK_IS_BOT) + // do not display a system dialog before aborting the process + _set_abort_behavior(0, _WRITE_ABORT_MSG); +#endif +#if defined(SK_DEBUG) && defined(SK_BUILD_FOR_WIN) + __debugbreak(); +#elif defined(__clang__) + __builtin_debugtrap(); +#else + abort(); +#endif +} + +void sk_out_of_memory(void) { + SkDEBUGFAIL("sk_out_of_memory"); +#if defined(IS_FUZZING_WITH_AFL) + exit(1); +#else + abort(); +#endif +} + +void* sk_realloc_throw(void* addr, size_t size) { + return throw_on_failure(size, realloc(addr, size)); +} + +void sk_free(void* p) { + if (p) { + free(p); + } +} + +void* sk_malloc_flags(size_t size, unsigned flags) { + void* p; + if (flags & SK_MALLOC_ZERO_INITIALIZE) { + p = calloc(size, 1); + } else { + p = malloc(size); + } + if (flags & SK_MALLOC_THROW) { + return throw_on_failure(size, p); + } else { + return p; + } +} diff --git a/gfx/skia/skia/src/ports/SkMemory_mozalloc.cpp b/gfx/skia/skia/src/ports/SkMemory_mozalloc.cpp new file mode 100644 index 0000000000..626404fb32 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkMemory_mozalloc.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Google Inc. + * Copyright 2012 Mozilla Foundation + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" + +#include "include/core/SkTypes.h" +#include "mozilla/mozalloc.h" +#include "mozilla/mozalloc_abort.h" +#include "mozilla/mozalloc_oom.h" +#include "prenv.h" + +bool sk_abort_is_enabled() { +#ifdef SK_DEBUG + const char* env = PR_GetEnv("MOZ_SKIA_DISABLE_ASSERTS"); + if (env && *env != '0') { + return false; + } +#endif + return true; +} + +void sk_abort_no_print() { + mozalloc_abort("Abort from sk_abort"); +} + +void sk_out_of_memory(void) { + SkDEBUGFAIL("sk_out_of_memory"); + mozalloc_handle_oom(0); +} + +void sk_free(void* p) { + free(p); +} + +void* sk_realloc_throw(void* addr, size_t size) { + return moz_xrealloc(addr, size); +} + +void* sk_malloc_flags(size_t size, unsigned flags) { + if (flags & SK_MALLOC_ZERO_INITIALIZE) { + return (flags & SK_MALLOC_THROW) ? moz_xcalloc(size, 1) : calloc(size, 1); + } + return (flags & SK_MALLOC_THROW) ? moz_xmalloc(size) : malloc(size); +} diff --git a/gfx/skia/skia/src/ports/SkOSFile_ios.h b/gfx/skia/skia/src/ports/SkOSFile_ios.h new file mode 100644 index 0000000000..e541b3ce72 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSFile_ios.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOSFile_ios_DEFINED +#define SkOSFile_ios_DEFINED + +#include "include/core/SkString.h" + +#ifdef SK_BUILD_FOR_IOS +#import + +static bool ios_get_path_in_bundle(const char path[], SkString* result) { + // Get a reference to the main bundle + CFBundleRef mainBundle = CFBundleGetMainBundle(); + + // Get a reference to the file's URL + CFStringRef pathRef = CFStringCreateWithCString(nullptr, path, kCFStringEncodingUTF8); + // We use "data" as our subdirectory to match {{bundle_resources_dir}}/data in GN + // Unfortunately "resources" is not a valid top-level name in iOS, so we push it one level down + CFURLRef imageURL = CFBundleCopyResourceURL(mainBundle, pathRef, nullptr, CFSTR("data")); + CFRelease(pathRef); + if (!imageURL) { + return false; + } + if (!result) { + return true; + } + + // Convert the URL reference into a string reference + CFStringRef imagePath = CFURLCopyFileSystemPath(imageURL, kCFURLPOSIXPathStyle); + CFRelease(imageURL); + + // Get the system encoding method + CFStringEncoding encodingMethod = CFStringGetSystemEncoding(); + + // Convert the string reference into an SkString + result->set(CFStringGetCStringPtr(imagePath, encodingMethod)); + CFRelease(imagePath); + return true; +} +#endif + +#endif // SkOSFile_ios_DEFINED diff --git a/gfx/skia/skia/src/ports/SkOSFile_posix.cpp b/gfx/skia/skia/src/ports/SkOSFile_posix.cpp new file mode 100644 index 0000000000..91a9f9a2ff --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSFile_posix.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTFitsIn.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkOSFile.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SK_BUILD_FOR_IOS +#include "src/ports/SkOSFile_ios.h" +#endif + +bool sk_exists(const char *path, SkFILE_Flags flags) { + int mode = F_OK; + if (flags & kRead_SkFILE_Flag) { + mode |= R_OK; + } + if (flags & kWrite_SkFILE_Flag) { + mode |= W_OK; + } +#ifdef SK_BUILD_FOR_IOS + // if the default path fails, check the bundle (but only if read-only) + if (0 == access(path, mode)) { + return true; + } else { + return (kRead_SkFILE_Flag == flags && ios_get_path_in_bundle(path, nullptr)); + } +#else + return (0 == access(path, mode)); +#endif +} + +typedef struct { + dev_t dev; + ino_t ino; +} SkFILEID; + +static bool sk_ino(FILE* a, SkFILEID* id) { + int fd = fileno(a); + if (fd < 0) { + return 0; + } + struct stat status; + if (0 != fstat(fd, &status)) { + return 0; + } + id->dev = status.st_dev; + id->ino = status.st_ino; + return true; +} + +bool sk_fidentical(FILE* a, FILE* b) { + SkFILEID aID, bID; + return sk_ino(a, &aID) && sk_ino(b, &bID) + && aID.ino == bID.ino + && aID.dev == bID.dev; +} + +void sk_fmunmap(const void* addr, size_t length) { + munmap(const_cast(addr), length); +} + +void* sk_fdmmap(int fd, size_t* size) { + struct stat status; + if (0 != fstat(fd, &status)) { + return nullptr; + } + if (!S_ISREG(status.st_mode)) { + return nullptr; + } + if (!SkTFitsIn(status.st_size)) { + return nullptr; + } + size_t fileSize = static_cast(status.st_size); + + void* addr = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0); + if (MAP_FAILED == addr) { + return nullptr; + } + + *size = fileSize; + return addr; +} + +int sk_fileno(FILE* f) { + return fileno(f); +} + +void* sk_fmmap(FILE* f, size_t* size) { + int fd = sk_fileno(f); + if (fd < 0) { + return nullptr; + } + + return sk_fdmmap(fd, size); +} + +size_t sk_qread(FILE* file, void* buffer, size_t count, size_t offset) { + int fd = sk_fileno(file); + if (fd < 0) { + return SIZE_MAX; + } + ssize_t bytesRead = pread(fd, buffer, count, offset); + if (bytesRead < 0) { + return SIZE_MAX; + } + return bytesRead; +} + +//////////////////////////////////////////////////////////////////////////// + +struct SkOSFileIterData { + SkOSFileIterData() : fDIR(nullptr) { } + DIR* fDIR; + SkString fPath, fSuffix; +}; +static_assert(sizeof(SkOSFileIterData) <= SkOSFile::Iter::kStorageSize, "not_enough_space"); + +SkOSFile::Iter::Iter() { new (fSelf.get()) SkOSFileIterData; } + +SkOSFile::Iter::Iter(const char path[], const char suffix[]) { + new (fSelf.get()) SkOSFileIterData; + this->reset(path, suffix); +} + +SkOSFile::Iter::~Iter() { + SkOSFileIterData& self = *static_cast(fSelf.get()); + if (self.fDIR) { + ::closedir(self.fDIR); + } + self.~SkOSFileIterData(); +} + +void SkOSFile::Iter::reset(const char path[], const char suffix[]) { + SkOSFileIterData& self = *static_cast(fSelf.get()); + if (self.fDIR) { + ::closedir(self.fDIR); + self.fDIR = nullptr; + } + self.fPath.set(path); + + if (path) { + self.fDIR = ::opendir(path); +#ifdef SK_BUILD_FOR_IOS + // check bundle for directory + if (!self.fDIR && ios_get_path_in_bundle(path, &self.fPath)) { + self.fDIR = ::opendir(self.fPath.c_str()); + } +#endif + self.fSuffix.set(suffix); + } else { + self.fSuffix.reset(); + } +} + +// returns true if suffix is empty, or if str ends with suffix +static bool issuffixfor(const SkString& suffix, const char str[]) { + size_t suffixLen = suffix.size(); + size_t strLen = strlen(str); + + return strLen >= suffixLen && + memcmp(suffix.c_str(), str + strLen - suffixLen, suffixLen) == 0; +} + +bool SkOSFile::Iter::next(SkString* name, bool getDir) { + SkOSFileIterData& self = *static_cast(fSelf.get()); + if (self.fDIR) { + dirent* entry; + + while ((entry = ::readdir(self.fDIR)) != nullptr) { + struct stat s; + SkString str(self.fPath); + + if (!str.endsWith("/") && !str.endsWith("\\")) { + str.append("/"); + } + str.append(entry->d_name); + + if (0 == stat(str.c_str(), &s)) { + if (getDir) { + if (s.st_mode & S_IFDIR) { + break; + } + } else { + if (!(s.st_mode & S_IFDIR) && issuffixfor(self.fSuffix, entry->d_name)) { + break; + } + } + } + } + if (entry) { // we broke out with a file + if (name) { + name->set(entry->d_name); + } + return true; + } + } + return false; +} diff --git a/gfx/skia/skia/src/ports/SkOSFile_stdio.cpp b/gfx/skia/skia/src/ports/SkOSFile_stdio.cpp new file mode 100644 index 0000000000..f870c5bfd2 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSFile_stdio.cpp @@ -0,0 +1,188 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/core/SkOSFile.h" + +#include +#include +#include + +#ifdef SK_BUILD_FOR_UNIX +#include +#endif + +#ifdef _WIN32 +#include +#include +#include +#include "src/utils/SkUTF.h" +#endif + +#ifdef SK_BUILD_FOR_IOS +#include "src/ports/SkOSFile_ios.h" +#endif + +#ifdef _WIN32 +static bool is_ascii(const char* s) { + while (char v = *s++) { + if ((v & 0x80) != 0) { + return false; + } + } + return true; +} + +static FILE* fopen_win(const char* utf8path, const char* perm) { + if (is_ascii(utf8path)) { + return fopen(utf8path, perm); + } + + const char* ptr = utf8path; + const char* end = utf8path + strlen(utf8path); + size_t n = 0; + while (ptr < end) { + SkUnichar u = SkUTF::NextUTF8(&ptr, end); + if (u < 0) { + return nullptr; // malformed UTF-8 + } + n += SkUTF::ToUTF16(u); + } + std::vector wchars(n + 1); + uint16_t* out = wchars.data(); + for (const char* ptr = utf8path; ptr < end;) { + out += SkUTF::ToUTF16(SkUTF::NextUTF8(&ptr, end), out); + } + SkASSERT(out == &wchars[n]); + *out = 0; // final null + wchar_t wperms[4] = {(wchar_t)perm[0], (wchar_t)perm[1], (wchar_t)perm[2], (wchar_t)perm[3]}; + return _wfopen((wchar_t*)wchars.data(), wperms); +} +#endif + +FILE* sk_fopen(const char path[], SkFILE_Flags flags) { + char perm[4] = {0, 0, 0, 0}; + char* p = perm; + + if (flags & kRead_SkFILE_Flag) { + *p++ = 'r'; + } + if (flags & kWrite_SkFILE_Flag) { + *p++ = 'w'; + } + *p = 'b'; + + FILE* file = nullptr; +#ifdef _WIN32 + file = fopen_win(path, perm); +#else + file = fopen(path, perm); +#endif +#ifdef SK_BUILD_FOR_IOS + // if not found in default path and read-only, try to open from bundle + if (!file && kRead_SkFILE_Flag == flags) { + SkString bundlePath; + if (ios_get_path_in_bundle(path, &bundlePath)) { + file = fopen(bundlePath.c_str(), perm); + } + } +#endif + + if (nullptr == file && (flags & kWrite_SkFILE_Flag)) { + SkDEBUGF("sk_fopen: fopen(\"%s\", \"%s\") returned nullptr (errno:%d): %s\n", + path, perm, errno, strerror(errno)); + } + return file; +} + +size_t sk_fgetsize(FILE* f) { + SkASSERT(f); + + long curr = ftell(f); // remember where we are + if (curr < 0) { + return 0; + } + + fseek(f, 0, SEEK_END); // go to the end + long size = ftell(f); // record the size + if (size < 0) { + size = 0; + } + + fseek(f, curr, SEEK_SET); // go back to our prev location + return size; +} + +size_t sk_fwrite(const void* buffer, size_t byteCount, FILE* f) { + SkASSERT(f); + return fwrite(buffer, 1, byteCount, f); +} + +void sk_fflush(FILE* f) { + SkASSERT(f); + fflush(f); +} + +void sk_fsync(FILE* f) { +#if !defined(_WIN32) && !defined(SK_BUILD_FOR_ANDROID) && !defined(__UCLIBC__) \ + && !defined(_NEWLIB_VERSION) + int fd = fileno(f); + fsync(fd); +#endif +} + +size_t sk_ftell(FILE* f) { + long curr = ftell(f); + if (curr < 0) { + return 0; + } + return curr; +} + +void sk_fclose(FILE* f) { + if (f) { + fclose(f); + } +} + +bool sk_isdir(const char *path) { + struct stat status; + if (0 != stat(path, &status)) { +#ifdef SK_BUILD_FOR_IOS + // check the bundle directory if not in default path + SkString bundlePath; + if (ios_get_path_in_bundle(path, &bundlePath)) { + if (0 != stat(bundlePath.c_str(), &status)) { + return false; + } + } +#else + return false; +#endif + } + return SkToBool(status.st_mode & S_IFDIR); +} + +bool sk_mkdir(const char* path) { + if (sk_isdir(path)) { + return true; + } + if (sk_exists(path)) { + fprintf(stderr, + "sk_mkdir: path '%s' already exists but is not a directory\n", + path); + return false; + } + + int retval; +#ifdef _WIN32 + retval = _mkdir(path); +#else + retval = mkdir(path, 0777); +#endif + return 0 == retval; +} diff --git a/gfx/skia/skia/src/ports/SkOSFile_win.cpp b/gfx/skia/skia/src/ports/SkOSFile_win.cpp new file mode 100644 index 0000000000..3dcdc1fb09 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSFile_win.cpp @@ -0,0 +1,282 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "include/private/SkMalloc.h" +#include "include/private/SkNoncopyable.h" +#include "include/private/SkTFitsIn.h" +#include "src/core/SkLeanWindows.h" +#include "src/core/SkOSFile.h" +#include "src/core/SkStringUtils.h" + +#include +#include +#include +#include + +bool sk_exists(const char *path, SkFILE_Flags flags) { + int mode = 0; // existence + if (flags & kRead_SkFILE_Flag) { + mode |= 4; // read + } + if (flags & kWrite_SkFILE_Flag) { + mode |= 2; // write + } + return (0 == _access(path, mode)); +} + +typedef struct { + ULONGLONG fVolume; + ULONGLONG fLsbSize; + ULONGLONG fMsbSize; +} SkFILEID; + +static bool sk_ino(FILE* f, SkFILEID* id) { + int fileno = _fileno((FILE*)f); + if (fileno < 0) { + return false; + } + + HANDLE file = (HANDLE)_get_osfhandle(fileno); + if (INVALID_HANDLE_VALUE == file) { + return false; + } + + //TODO: call GetFileInformationByHandleEx on Vista and later with FileIdInfo. + BY_HANDLE_FILE_INFORMATION info; + if (0 == GetFileInformationByHandle(file, &info)) { + return false; + } + id->fVolume = info.dwVolumeSerialNumber; + id->fLsbSize = info.nFileIndexLow + (((ULONGLONG)info.nFileIndexHigh) << 32); + id->fMsbSize = 0; + + return true; +} + +bool sk_fidentical(FILE* a, FILE* b) { + SkFILEID aID, bID; + return sk_ino(a, &aID) && sk_ino(b, &bID) + && aID.fLsbSize == bID.fLsbSize + && aID.fMsbSize == bID.fMsbSize + && aID.fVolume == bID.fVolume; +} + +class SkAutoNullKernelHandle : SkNoncopyable { +public: + SkAutoNullKernelHandle(const HANDLE handle) : fHandle(handle) { } + ~SkAutoNullKernelHandle() { CloseHandle(fHandle); } + operator HANDLE() const { return fHandle; } + bool isValid() const { return SkToBool(fHandle); } +private: + HANDLE fHandle; +}; +typedef SkAutoNullKernelHandle SkAutoWinMMap; + +void sk_fmunmap(const void* addr, size_t) { + UnmapViewOfFile(addr); +} + +void* sk_fdmmap(int fileno, size_t* length) { + HANDLE file = (HANDLE)_get_osfhandle(fileno); + if (INVALID_HANDLE_VALUE == file) { + return nullptr; + } + + LARGE_INTEGER fileSize; + if (0 == GetFileSizeEx(file, &fileSize)) { + //TODO: use SK_TRACEHR(GetLastError(), "Could not get file size.") to report. + return nullptr; + } + if (!SkTFitsIn(fileSize.QuadPart)) { + return nullptr; + } + + SkAutoWinMMap mmap(CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr)); + if (!mmap.isValid()) { + //TODO: use SK_TRACEHR(GetLastError(), "Could not create file mapping.") to report. + return nullptr; + } + + // Eventually call UnmapViewOfFile + void* addr = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); + if (nullptr == addr) { + //TODO: use SK_TRACEHR(GetLastError(), "Could not map view of file.") to report. + return nullptr; + } + + *length = static_cast(fileSize.QuadPart); + return addr; +} + +int sk_fileno(FILE* f) { + return _fileno((FILE*)f); +} + +void* sk_fmmap(FILE* f, size_t* length) { + int fileno = sk_fileno(f); + if (fileno < 0) { + return nullptr; + } + + return sk_fdmmap(fileno, length); +} + +size_t sk_qread(FILE* file, void* buffer, size_t count, size_t offset) { + int fileno = sk_fileno(file); + HANDLE fileHandle = (HANDLE)_get_osfhandle(fileno); + if (INVALID_HANDLE_VALUE == file) { + return SIZE_MAX; + } + + OVERLAPPED overlapped; + memset(&overlapped, 0, sizeof(overlapped)); + ULARGE_INTEGER winOffset; + winOffset.QuadPart = offset; + overlapped.Offset = winOffset.LowPart; + overlapped.OffsetHigh = winOffset.HighPart; + + if (!SkTFitsIn(count)) { + count = std::numeric_limits::max(); + } + + DWORD bytesRead; + if (ReadFile(fileHandle, buffer, static_cast(count), &bytesRead, &overlapped)) { + return bytesRead; + } + if (GetLastError() == ERROR_HANDLE_EOF) { + return 0; + } + return SIZE_MAX; +} + +//////////////////////////////////////////////////////////////////////////// + +struct SkOSFileIterData { + SkOSFileIterData() : fHandle(0), fPath16(nullptr) { } + HANDLE fHandle; + uint16_t* fPath16; +}; +static_assert(sizeof(SkOSFileIterData) <= SkOSFile::Iter::kStorageSize, "not_enough_space"); + +static uint16_t* concat_to_16(const char src[], const char suffix[]) { + size_t i, len = strlen(src); + size_t len2 = 3 + (suffix ? strlen(suffix) : 0); + uint16_t* dst = (uint16_t*)sk_malloc_throw((len + len2) * sizeof(uint16_t)); + + for (i = 0; i < len; i++) { + dst[i] = src[i]; + } + + if (i > 0 && dst[i-1] != '/') { + dst[i++] = '/'; + } + dst[i++] = '*'; + + if (suffix) { + while (*suffix) { + dst[i++] = *suffix++; + } + } + dst[i] = 0; + SkASSERT(i + 1 <= len + len2); + + return dst; +} + +SkOSFile::Iter::Iter() { new (fSelf.get()) SkOSFileIterData; } + +SkOSFile::Iter::Iter(const char path[], const char suffix[]) { + new (fSelf.get()) SkOSFileIterData; + this->reset(path, suffix); +} + +SkOSFile::Iter::~Iter() { + SkOSFileIterData& self = *static_cast(fSelf.get()); + sk_free(self.fPath16); + if (self.fHandle) { + ::FindClose(self.fHandle); + } + self.~SkOSFileIterData(); +} + +void SkOSFile::Iter::reset(const char path[], const char suffix[]) { + SkOSFileIterData& self = *static_cast(fSelf.get()); + if (self.fHandle) { + ::FindClose(self.fHandle); + self.fHandle = 0; + } + if (nullptr == path) { + path = ""; + } + + sk_free(self.fPath16); + self.fPath16 = concat_to_16(path, suffix); +} + +static bool is_magic_dir(const uint16_t dir[]) { + // return true for "." and ".." + return dir[0] == '.' && (dir[1] == 0 || (dir[1] == '.' && dir[2] == 0)); +} + +static bool get_the_file(HANDLE handle, SkString* name, WIN32_FIND_DATAW* dataPtr, bool getDir) { + WIN32_FIND_DATAW data; + + if (nullptr == dataPtr) { + if (::FindNextFileW(handle, &data)) + dataPtr = &data; + else + return false; + } + + for (;;) { + if (getDir) { + if ((dataPtr->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !is_magic_dir((uint16_t*)dataPtr->cFileName)) + { + break; + } + } else { + if (!(dataPtr->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + break; + } + } + if (!::FindNextFileW(handle, dataPtr)) { + return false; + } + } + // if we get here, we've found a file/dir + if (name) { + const uint16_t* utf16name = (const uint16_t*)dataPtr->cFileName; + const uint16_t* ptr = utf16name; + while (*ptr != 0) { ++ptr; } + *name = SkStringFromUTF16(utf16name, ptr - utf16name); + } + return true; +} + +bool SkOSFile::Iter::next(SkString* name, bool getDir) { + SkOSFileIterData& self = *static_cast(fSelf.get()); + WIN32_FIND_DATAW data; + WIN32_FIND_DATAW* dataPtr = nullptr; + + if (self.fHandle == 0) { // our first time + if (self.fPath16 == nullptr || *self.fPath16 == 0) { // check for no path + return false; + } + + self.fHandle = ::FindFirstFileW((LPCWSTR)self.fPath16, &data); + if (self.fHandle != 0 && self.fHandle != (HANDLE)~0) { + dataPtr = &data; + } + } + return self.fHandle != (HANDLE)~0 && get_the_file(self.fHandle, name, dataPtr, getDir); +} + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkOSLibrary.h b/gfx/skia/skia/src/ports/SkOSLibrary.h new file mode 100644 index 0000000000..ea1378f8b0 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSLibrary.h @@ -0,0 +1,14 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOSLibrary_DEFINED +#define SkOSLibrary_DEFINED + +void* DynamicLoadLibrary(const char* libraryName); +void* GetProcedureAddress(void* library, const char* functionName); + +#endif diff --git a/gfx/skia/skia/src/ports/SkOSLibrary_posix.cpp b/gfx/skia/skia/src/ports/SkOSLibrary_posix.cpp new file mode 100644 index 0000000000..b4b957e062 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSLibrary_posix.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkTypes.h" +#if !defined(SK_BUILD_FOR_WIN) + +#include "src/ports/SkOSLibrary.h" + +#include + +void* DynamicLoadLibrary(const char* libraryName) { + return dlopen(libraryName, RTLD_LAZY); +} + +void* GetProcedureAddress(void* library, const char* functionName) { + return dlsym(library, functionName); +} +#endif//!defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkOSLibrary_win.cpp b/gfx/skia/skia/src/ports/SkOSLibrary_win.cpp new file mode 100644 index 0000000000..6e19065f4b --- /dev/null +++ b/gfx/skia/skia/src/ports/SkOSLibrary_win.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "src/core/SkLeanWindows.h" +#include "src/ports/SkOSLibrary.h" + +void* DynamicLoadLibrary(const char* libraryName) { + return LoadLibraryA(libraryName); +} + +void* GetProcedureAddress(void* library, const char* functionName) { + return reinterpret_cast(::GetProcAddress((HMODULE)library, functionName)); +} + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkRemotableFontMgr_win_dw.cpp b/gfx/skia/skia/src/ports/SkRemotableFontMgr_win_dw.cpp new file mode 100644 index 0000000000..fc9c6d8cac --- /dev/null +++ b/gfx/skia/skia/src/ports/SkRemotableFontMgr_win_dw.cpp @@ -0,0 +1,472 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/utils/win/SkDWriteNTDDI_VERSION.h" + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "include/core/SkStream.h" +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "include/ports/SkRemotableFontMgr.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTArray.h" +#include "src/ports/SkTypeface_win_dw.h" +#include "src/utils/SkUTF.h" +#include "src/utils/win/SkDWrite.h" +#include "src/utils/win/SkDWriteFontFileStream.h" +#include "src/utils/win/SkHRESULT.h" +#include "src/utils/win/SkObjBase.h" +#include "src/utils/win/SkTScopedComPtr.h" + +#include + +class SK_API SkRemotableFontMgr_DirectWrite : public SkRemotableFontMgr { +private: + struct DataId { + IUnknown* fLoader; // In COM only IUnknown pointers may be safely used for identity. + void* fKey; + UINT32 fKeySize; + + DataId() { } + + DataId(DataId&& that) : fLoader(that.fLoader), fKey(that.fKey), fKeySize(that.fKeySize) { + that.fLoader = nullptr; + that.fKey = nullptr; + SkDEBUGCODE(that.fKeySize = 0xFFFFFFFF;) + } + + ~DataId() { + if (fLoader) { + fLoader->Release(); + } + sk_free(fKey); + } + }; + + mutable SkTArray fDataIdCache; + mutable SkMutex fDataIdCacheMutex; + + int FindOrAdd(IDWriteFontFileLoader* fontFileLoader, + const void* refKey, UINT32 refKeySize) const + { + SkTScopedComPtr fontFileLoaderId; + HR_GENERAL(fontFileLoader->QueryInterface(&fontFileLoaderId), + "Failed to re-convert to IDWriteFontFileLoader.", + SkFontIdentity::kInvalidDataId); + + SkAutoMutexExclusive ama(fDataIdCacheMutex); + int count = fDataIdCache.count(); + int i; + for (i = 0; i < count; ++i) { + const DataId& current = fDataIdCache[i]; + if (fontFileLoaderId.get() == current.fLoader && + refKeySize == current.fKeySize && + 0 == memcmp(refKey, current.fKey, refKeySize)) + { + return i; + } + } + DataId& added = fDataIdCache.push_back(); + added.fLoader = fontFileLoaderId.release(); // Ref is passed. + added.fKey = sk_malloc_throw(refKeySize); + memcpy(added.fKey, refKey, refKeySize); + added.fKeySize = refKeySize; + + return i; + } + +public: + + + /** localeNameLength must include the null terminator. */ + SkRemotableFontMgr_DirectWrite(IDWriteFontCollection* fontCollection, + WCHAR* localeName, int localeNameLength) + : fFontCollection(SkRefComPtr(fontCollection)) + , fLocaleName(localeNameLength) + { + memcpy(fLocaleName.get(), localeName, localeNameLength * sizeof(WCHAR)); + } + + HRESULT FontToIdentity(IDWriteFont* font, SkFontIdentity* fontId) const { + SkTScopedComPtr fontFace; + HRM(font->CreateFontFace(&fontFace), "Could not create font face."); + + UINT32 numFiles; + HR(fontFace->GetFiles(&numFiles, nullptr)); + if (numFiles > 1) { + return E_FAIL; + } + + // data id + SkTScopedComPtr fontFile; + HR(fontFace->GetFiles(&numFiles, &fontFile)); + + SkTScopedComPtr fontFileLoader; + HR(fontFile->GetLoader(&fontFileLoader)); + + const void* refKey; + UINT32 refKeySize; + HR(fontFile->GetReferenceKey(&refKey, &refKeySize)); + + fontId->fDataId = FindOrAdd(fontFileLoader.get(), refKey, refKeySize); + + // index + fontId->fTtcIndex = fontFace->GetIndex(); + + // style + fontId->fFontStyle = get_style(font); + return S_OK; + } + + SkRemotableFontIdentitySet* getIndex(int familyIndex) const override { + SkTScopedComPtr fontFamily; + HRNM(fFontCollection->GetFontFamily(familyIndex, &fontFamily), + "Could not get requested family."); + + int count = fontFamily->GetFontCount(); + SkFontIdentity* fontIds; + sk_sp fontIdSet( + new SkRemotableFontIdentitySet(count, &fontIds)); + for (int fontIndex = 0; fontIndex < count; ++fontIndex) { + SkTScopedComPtr font; + HRNM(fontFamily->GetFont(fontIndex, &font), "Could not get font."); + + HRN(FontToIdentity(font.get(), &fontIds[fontIndex])); + } + return fontIdSet.release(); + } + + virtual SkFontIdentity matchIndexStyle(int familyIndex, + const SkFontStyle& pattern) const override + { + SkFontIdentity identity = { SkFontIdentity::kInvalidDataId }; + + SkTScopedComPtr fontFamily; + HR_GENERAL(fFontCollection->GetFontFamily(familyIndex, &fontFamily), + "Could not get requested family.", + identity); + + const DWriteStyle dwStyle(pattern); + SkTScopedComPtr font; + HR_GENERAL(fontFamily->GetFirstMatchingFont(dwStyle.fWeight, dwStyle.fWidth, + dwStyle.fSlant, &font), + "Could not match font in family.", + identity); + + HR_GENERAL(FontToIdentity(font.get(), &identity), nullptr, identity); + + return identity; + } + + static HRESULT getDefaultFontFamilyName(SkSMallocWCHAR* name) { + NONCLIENTMETRICSW metrics; + metrics.cbSize = sizeof(metrics); + if (0 == SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, + sizeof(metrics), + &metrics, + 0)) { + return E_UNEXPECTED; + } + + size_t len = wcsnlen_s(metrics.lfMessageFont.lfFaceName, LF_FACESIZE) + 1; + if (0 != wcsncpy_s(name->reset(len), len, metrics.lfMessageFont.lfFaceName, _TRUNCATE)) { + return E_UNEXPECTED; + } + + return S_OK; + } + + SkRemotableFontIdentitySet* matchName(const char familyName[]) const override { + SkSMallocWCHAR dwFamilyName; + if (nullptr == familyName) { + HR_GENERAL(getDefaultFontFamilyName(&dwFamilyName), + nullptr, SkRemotableFontIdentitySet::NewEmpty()); + } else { + HR_GENERAL(sk_cstring_to_wchar(familyName, &dwFamilyName), + nullptr, SkRemotableFontIdentitySet::NewEmpty()); + } + + UINT32 index; + BOOL exists; + HR_GENERAL(fFontCollection->FindFamilyName(dwFamilyName.get(), &index, &exists), + "Failed while finding family by name.", + SkRemotableFontIdentitySet::NewEmpty()); + if (!exists) { + return SkRemotableFontIdentitySet::NewEmpty(); + } + + return this->getIndex(index); + } + + virtual SkFontIdentity matchNameStyle(const char familyName[], + const SkFontStyle& style) const override + { + SkFontIdentity identity = { SkFontIdentity::kInvalidDataId }; + + SkSMallocWCHAR dwFamilyName; + if (nullptr == familyName) { + HR_GENERAL(getDefaultFontFamilyName(&dwFamilyName), nullptr, identity); + } else { + HR_GENERAL(sk_cstring_to_wchar(familyName, &dwFamilyName), nullptr, identity); + } + + UINT32 index; + BOOL exists; + HR_GENERAL(fFontCollection->FindFamilyName(dwFamilyName.get(), &index, &exists), + "Failed while finding family by name.", + identity); + if (!exists) { + return identity; + } + + return this->matchIndexStyle(index, style); + } + + class FontFallbackRenderer : public IDWriteTextRenderer { + public: + FontFallbackRenderer(const SkRemotableFontMgr_DirectWrite* outer, UINT32 character) + : fRefCount(1), fOuter(SkSafeRef(outer)), fCharacter(character) { + fIdentity.fDataId = SkFontIdentity::kInvalidDataId; + } + + virtual ~FontFallbackRenderer() { } + + // IDWriteTextRenderer methods + SK_STDMETHODIMP DrawGlyphRun( + void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, + DWRITE_GLYPH_RUN const* glyphRun, + DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, + IUnknown* clientDrawingEffect) override + { + SkTScopedComPtr font; + HRM(fOuter->fFontCollection->GetFontFromFontFace(glyphRun->fontFace, &font), + "Could not get font from font face."); + + // It is possible that the font passed does not actually have the requested character, + // due to no font being found and getting the fallback font. + // Check that the font actually contains the requested character. + BOOL exists; + HRM(font->HasCharacter(fCharacter, &exists), "Could not find character."); + + if (exists) { + HR(fOuter->FontToIdentity(font.get(), &fIdentity)); + } + + return S_OK; + } + + SK_STDMETHODIMP DrawUnderline( + void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_UNDERLINE const* underline, + IUnknown* clientDrawingEffect) override + { return E_NOTIMPL; } + + SK_STDMETHODIMP DrawStrikethrough( + void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_STRIKETHROUGH const* strikethrough, + IUnknown* clientDrawingEffect) override + { return E_NOTIMPL; } + + SK_STDMETHODIMP DrawInlineObject( + void* clientDrawingContext, + FLOAT originX, + FLOAT originY, + IDWriteInlineObject* inlineObject, + BOOL isSideways, + BOOL isRightToLeft, + IUnknown* clientDrawingEffect) override + { return E_NOTIMPL; } + + // IDWritePixelSnapping methods + SK_STDMETHODIMP IsPixelSnappingDisabled( + void* clientDrawingContext, + BOOL* isDisabled) override + { + *isDisabled = FALSE; + return S_OK; + } + + SK_STDMETHODIMP GetCurrentTransform( + void* clientDrawingContext, + DWRITE_MATRIX* transform) override + { + const DWRITE_MATRIX ident = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; + *transform = ident; + return S_OK; + } + + SK_STDMETHODIMP GetPixelsPerDip( + void* clientDrawingContext, + FLOAT* pixelsPerDip) override + { + *pixelsPerDip = 1.0f; + return S_OK; + } + + // IUnknown methods + SK_STDMETHODIMP_(ULONG) AddRef() override { + return InterlockedIncrement(&fRefCount); + } + + SK_STDMETHODIMP_(ULONG) Release() override { + ULONG newCount = InterlockedDecrement(&fRefCount); + if (0 == newCount) { + delete this; + } + return newCount; + } + + SK_STDMETHODIMP QueryInterface( + IID const& riid, void** ppvObject) override + { + if (__uuidof(IUnknown) == riid || + __uuidof(IDWritePixelSnapping) == riid || + __uuidof(IDWriteTextRenderer) == riid) + { + *ppvObject = this; + this->AddRef(); + return S_OK; + } + *ppvObject = nullptr; + return E_FAIL; + } + + const SkFontIdentity FallbackIdentity() { return fIdentity; } + + protected: + ULONG fRefCount; + sk_sp fOuter; + UINT32 fCharacter; + SkFontIdentity fIdentity; + }; + + virtual SkFontIdentity matchNameStyleCharacter(const char familyName[], + const SkFontStyle& pattern, + const char* bcp47[], int bcp47Count, + SkUnichar character) const override + { + SkFontIdentity identity = { SkFontIdentity::kInvalidDataId }; + + IDWriteFactory* dwFactory = sk_get_dwrite_factory(); + if (nullptr == dwFactory) { + return identity; + } + + // TODO: use IDWriteFactory2::GetSystemFontFallback when available. + + const DWriteStyle dwStyle(pattern); + + SkSMallocWCHAR dwFamilyName; + if (nullptr == familyName) { + HR_GENERAL(getDefaultFontFamilyName(&dwFamilyName), nullptr, identity); + } else { + HR_GENERAL(sk_cstring_to_wchar(familyName, &dwFamilyName), nullptr, identity); + } + + const SkSMallocWCHAR* dwBcp47; + SkSMallocWCHAR dwBcp47Local; + if (bcp47Count < 1) { + dwBcp47 = &fLocaleName; + } else { + //TODO: support fallback stack. + HR_GENERAL(sk_cstring_to_wchar(bcp47[bcp47Count-1], &dwBcp47Local), nullptr, identity); + dwBcp47 = &dwBcp47Local; + } + + SkTScopedComPtr fallbackFormat; + HR_GENERAL(dwFactory->CreateTextFormat(dwFamilyName, + fFontCollection.get(), + dwStyle.fWeight, + dwStyle.fSlant, + dwStyle.fWidth, + 72.0f, + *dwBcp47, + &fallbackFormat), + "Could not create text format.", + identity); + + WCHAR str[16]; + UINT32 strLen = static_cast( + SkUTF::ToUTF16(character, reinterpret_cast(str))); + SkTScopedComPtr fallbackLayout; + HR_GENERAL(dwFactory->CreateTextLayout(str, strLen, fallbackFormat.get(), + 200.0f, 200.0f, + &fallbackLayout), + "Could not create text layout.", + identity); + + SkTScopedComPtr fontFallbackRenderer( + new FontFallbackRenderer(this, character)); + + HR_GENERAL(fallbackLayout->Draw(nullptr, fontFallbackRenderer.get(), 50.0f, 50.0f), + "Could not draw layout with renderer.", + identity); + + return fontFallbackRenderer->FallbackIdentity(); + } + + SkStreamAsset* getData(int dataId) const override { + SkAutoMutexExclusive ama(fDataIdCacheMutex); + if (dataId >= fDataIdCache.count()) { + return nullptr; + } + const DataId& id = fDataIdCache[dataId]; + + SkTScopedComPtr loader; + HRNM(id.fLoader->QueryInterface(&loader), "QuerryInterface IDWriteFontFileLoader failed"); + + SkTScopedComPtr fontFileStream; + HRNM(loader->CreateStreamFromKey(id.fKey, id.fKeySize, &fontFileStream), + "Could not create font file stream."); + + return new SkDWriteFontFileStream(fontFileStream.get()); + } + +private: + SkTScopedComPtr fFontCollection; + SkSMallocWCHAR fLocaleName; + + typedef SkRemotableFontMgr INHERITED; +}; + +SkRemotableFontMgr* SkRemotableFontMgr_New_DirectWrite() { + IDWriteFactory* factory = sk_get_dwrite_factory(); + if (nullptr == factory) { + return nullptr; + } + + SkTScopedComPtr sysFontCollection; + HRNM(factory->GetSystemFontCollection(&sysFontCollection, FALSE), + "Could not get system font collection."); + + WCHAR localeNameStorage[LOCALE_NAME_MAX_LENGTH]; + WCHAR* localeName = nullptr; + int localeNameLen = 0; + + // Dynamically load GetUserDefaultLocaleName function, as it is not available on XP. + SkGetUserDefaultLocaleNameProc getUserDefaultLocaleNameProc = nullptr; + HRESULT hr = SkGetGetUserDefaultLocaleNameProc(&getUserDefaultLocaleNameProc); + if (nullptr == getUserDefaultLocaleNameProc) { + SK_TRACEHR(hr, "Could not get GetUserDefaultLocaleName."); + } else { + localeNameLen = getUserDefaultLocaleNameProc(localeNameStorage, LOCALE_NAME_MAX_LENGTH); + if (localeNameLen) { + localeName = localeNameStorage; + }; + } + + return new SkRemotableFontMgr_DirectWrite(sysFontCollection.get(), localeName, localeNameLen); +} +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp new file mode 100644 index 0000000000..2e2cf08288 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp @@ -0,0 +1,1241 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/utils/win/SkDWriteNTDDI_VERSION.h" + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#undef GetGlyphIndices + +#include "include/codec/SkCodec.h" +#include "include/core/SkFontMetrics.h" +#include "include/core/SkPath.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTo.h" +#include "src/core/SkDraw.h" +#include "src/core/SkEndian.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkMaskGamma.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkSharedMutex.h" +#include "src/ports/SkScalerContext_win_dw.h" +#include "src/ports/SkTypeface_win_dw.h" +#include "src/sfnt/SkOTTable_EBLC.h" +#include "src/sfnt/SkOTTable_EBSC.h" +#include "src/sfnt/SkOTTable_gasp.h" +#include "src/sfnt/SkOTTable_maxp.h" +#include "src/utils/SkMatrix22.h" +#include "src/utils/win/SkDWrite.h" +#include "src/utils/win/SkDWriteGeometrySink.h" +#include "src/utils/win/SkHRESULT.h" +#include "src/utils/win/SkTScopedComPtr.h" + +#include +#include +#include + +/* Note: + * In versions 8 and 8.1 of Windows, some calls in DWrite are not thread safe. + * The DWriteFactoryMutex protects the calls that are problematic. + * + * On DWrite 3 or above, which is only available on Windows 10, we don't enable + * the locking to avoid thread contention. + */ +static SkSharedMutex DWriteFactoryMutex; + +struct MaybeExclusive { + MaybeExclusive(SkScalerContext_DW* ctx) : fEnabled(!ctx->isDWrite3()) { + if (fEnabled) { + DWriteFactoryMutex.acquire(); + } + } + ~MaybeExclusive() { + if (fEnabled) { + DWriteFactoryMutex.release(); + } + } + bool fEnabled; +}; + +struct MaybeShared { + MaybeShared(SkScalerContext_DW* ctx) : fEnabled(!ctx->isDWrite3()) { + if (fEnabled) { + DWriteFactoryMutex.acquireShared(); + } + } + ~MaybeShared() { + if (fEnabled) { + DWriteFactoryMutex.releaseShared(); + } + } + bool fEnabled; +}; + +static bool isLCD(const SkScalerContextRec& rec) { + return SkMask::kLCD16_Format == rec.fMaskFormat; +} + +static bool is_hinted(SkScalerContext_DW* ctx, DWriteFontTypeface* typeface) { + MaybeExclusive l(ctx); + AutoTDWriteTable maxp(typeface->fDWriteFontFace.get()); + if (!maxp.fExists) { + return false; + } + if (maxp.fSize < sizeof(SkOTTableMaximumProfile::Version::TT)) { + return false; + } + if (maxp->version.version != SkOTTableMaximumProfile::Version::TT::VERSION) { + return false; + } + return (0 != maxp->version.tt.maxSizeOfInstructions); +} + +/** A GaspRange is inclusive, [min, max]. */ +struct GaspRange { + using Behavior = SkOTTableGridAndScanProcedure::GaspRange::behavior; + GaspRange(int min, int max, int version, Behavior flags) + : fMin(min), fMax(max), fVersion(version), fFlags(flags) { } + int fMin; + int fMax; + int fVersion; + Behavior fFlags; +}; + +bool get_gasp_range(DWriteFontTypeface* typeface, int size, GaspRange* range) { + AutoTDWriteTable gasp(typeface->fDWriteFontFace.get()); + if (!gasp.fExists) { + return false; + } + if (gasp.fSize < sizeof(SkOTTableGridAndScanProcedure)) { + return false; + } + if (gasp->version != SkOTTableGridAndScanProcedure::version0 && + gasp->version != SkOTTableGridAndScanProcedure::version1) + { + return false; + } + + uint16_t numRanges = SkEndianSwap16(gasp->numRanges); + if (numRanges > 1024 || + gasp.fSize < sizeof(SkOTTableGridAndScanProcedure) + + sizeof(SkOTTableGridAndScanProcedure::GaspRange) * numRanges) + { + return false; + } + + const SkOTTableGridAndScanProcedure::GaspRange* rangeTable = + SkTAfter(gasp.get()); + int minPPEM = -1; + for (uint16_t i = 0; i < numRanges; ++i, ++rangeTable) { + int maxPPEM = SkEndianSwap16(rangeTable->maxPPEM); + if (minPPEM < size && size <= maxPPEM) { + range->fMin = minPPEM + 1; + range->fMax = maxPPEM; + range->fVersion = SkEndian_SwapBE16(gasp->version); + range->fFlags = rangeTable->flags; + return true; + } + minPPEM = maxPPEM; + } + return false; +} +/** If the rendering mode for the specified 'size' is gridfit, then place + * the gridfit range into 'range'. Otherwise, leave 'range' alone. + */ +static bool is_gridfit_only(GaspRange::Behavior flags) { + return flags.raw.value == GaspRange::Behavior::Raw::GridfitMask; +} + +static bool has_bitmap_strike(SkScalerContext_DW* ctx, DWriteFontTypeface* typeface, GaspRange range) { + MaybeExclusive l(ctx); + { + AutoTDWriteTable eblc(typeface->fDWriteFontFace.get()); + if (!eblc.fExists) { + return false; + } + if (eblc.fSize < sizeof(SkOTTableEmbeddedBitmapLocation)) { + return false; + } + if (eblc->version != SkOTTableEmbeddedBitmapLocation::version_initial) { + return false; + } + + uint32_t numSizes = SkEndianSwap32(eblc->numSizes); + if (numSizes > 1024 || + eblc.fSize < sizeof(SkOTTableEmbeddedBitmapLocation) + + sizeof(SkOTTableEmbeddedBitmapLocation::BitmapSizeTable) * numSizes) + { + return false; + } + + const SkOTTableEmbeddedBitmapLocation::BitmapSizeTable* sizeTable = + SkTAfter(eblc.get()); + for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) { + if (sizeTable->ppemX == sizeTable->ppemY && + range.fMin <= sizeTable->ppemX && sizeTable->ppemX <= range.fMax) + { + // TODO: determine if we should dig through IndexSubTableArray/IndexSubTable + // to determine the actual number of glyphs with bitmaps. + + // TODO: Ensure that the bitmaps actually cover a significant portion of the strike. + + // TODO: Ensure that the bitmaps are bi-level? + if (sizeTable->endGlyphIndex >= sizeTable->startGlyphIndex + 3) { + return true; + } + } + } + } + + { + AutoTDWriteTable ebsc(typeface->fDWriteFontFace.get()); + if (!ebsc.fExists) { + return false; + } + if (ebsc.fSize < sizeof(SkOTTableEmbeddedBitmapScaling)) { + return false; + } + if (ebsc->version != SkOTTableEmbeddedBitmapScaling::version_initial) { + return false; + } + + uint32_t numSizes = SkEndianSwap32(ebsc->numSizes); + if (numSizes > 1024 || + ebsc.fSize < sizeof(SkOTTableEmbeddedBitmapScaling) + + sizeof(SkOTTableEmbeddedBitmapScaling::BitmapScaleTable) * numSizes) + { + return false; + } + + const SkOTTableEmbeddedBitmapScaling::BitmapScaleTable* scaleTable = + SkTAfter(ebsc.get()); + for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) { + if (scaleTable->ppemX == scaleTable->ppemY && + range.fMin <= scaleTable->ppemX && scaleTable->ppemX <= range.fMax) { + // EBSC tables are normally only found in bitmap only fonts. + return true; + } + } + } + + return false; +} + +static bool both_zero(SkScalar a, SkScalar b) { + return 0 == a && 0 == b; +} + +// returns false if there is any non-90-rotation or skew +static bool is_axis_aligned(const SkScalerContextRec& rec) { + return 0 == rec.fPreSkewX && + (both_zero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) || + both_zero(rec.fPost2x2[0][0], rec.fPost2x2[1][1])); +} + +SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, + const SkScalerContextEffects& effects, + const SkDescriptor* desc) + : SkScalerContext(std::move(typefaceRef), effects, desc) + , fGlyphCount(-1) { + + DWriteFontTypeface* typeface = this->getDWriteTypeface(); + fIsColorFont = typeface->fFactory2 && + typeface->fDWriteFontFace2 && + typeface->fDWriteFontFace2->IsColorFont(); + fClearTypeLevel = int(typeface->GetClearTypeLevel() * 256); + + // In general, all glyphs should use DWriteFontFace::GetRecommendedRenderingMode + // except when bi-level rendering is requested or there are embedded + // bi-level bitmaps (and the embedded bitmap flag is set and no rotation). + // + // DirectWrite's IDWriteFontFace::GetRecommendedRenderingMode does not do + // this. As a result, determine the actual size of the text and then see if + // there are any embedded bi-level bitmaps of that size. If there are, then + // force bitmaps by requesting bi-level rendering. + // + // FreeType allows for separate ppemX and ppemY, but DirectWrite assumes + // square pixels and only uses ppemY. Therefore the transform must track any + // non-uniform x-scale. + // + // Also, rotated glyphs should have the same absolute advance widths as + // horizontal glyphs and the subpixel flag should not affect glyph shapes. + + SkVector scale; + fRec.computeMatrices(SkScalerContextRec::kVertical_PreMatrixScale, &scale, &fSkXform); + + fXform.m11 = SkScalarToFloat(fSkXform.getScaleX()); + fXform.m12 = SkScalarToFloat(fSkXform.getSkewY()); + fXform.m21 = SkScalarToFloat(fSkXform.getSkewX()); + fXform.m22 = SkScalarToFloat(fSkXform.getScaleY()); + fXform.dx = 0; + fXform.dy = 0; + + // realTextSize is the actual device size we want (as opposed to the size the user requested). + // gdiTextSize is the size we request when GDI compatible. + // If the scale is negative, this means the matrix will do the flip anyway. + const SkScalar realTextSize = scale.fY; + // Due to floating point math, the lower bits are suspect. Round carefully. + SkScalar gdiTextSize = SkScalarRoundToScalar(realTextSize * 64.0f) / 64.0f; + if (gdiTextSize == 0) { + gdiTextSize = SK_Scalar1; + } + + bool bitmapRequested = SkToBool(fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag); + bool treatLikeBitmap = false; + bool axisAlignedBitmap = false; + if (bitmapRequested) { + // When embedded bitmaps are requested, treat the entire range like + // a bitmap strike if the range is gridfit only and contains a bitmap. + int bitmapPPEM = SkScalarTruncToInt(gdiTextSize); + GaspRange range(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior()); + if (get_gasp_range(typeface, bitmapPPEM, &range)) { + if (!is_gridfit_only(range.fFlags)) { + range = GaspRange(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior()); + } + } + treatLikeBitmap = has_bitmap_strike(this, typeface, range); + + axisAlignedBitmap = is_axis_aligned(fRec); + } + + GaspRange range(0, 0xFFFF, 0, GaspRange::Behavior()); + + // If the user requested aliased, do so with aliased compatible metrics. + if (SkMask::kBW_Format == fRec.fMaskFormat) { + fTextSizeRender = gdiTextSize; + fRenderingMode = DWRITE_RENDERING_MODE_ALIASED; + fTextureType = DWRITE_TEXTURE_ALIASED_1x1; + fTextSizeMeasure = gdiTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; + + // If we can use a bitmap, use gdi classic rendering and measurement. + // This will not always provide a bitmap, but matches expected behavior. + } else if ((treatLikeBitmap && axisAlignedBitmap) || typeface->ForceGDI()) { + fTextSizeRender = gdiTextSize; + fRenderingMode = DWRITE_RENDERING_MODE_GDI_CLASSIC; + fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; + fTextSizeMeasure = gdiTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; + + // If rotated but the horizontal text could have used a bitmap, + // render high quality rotated glyphs but measure using bitmap metrics. + } else if (treatLikeBitmap) { + fTextSizeRender = gdiTextSize; + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; + fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; + fTextSizeMeasure = gdiTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; + + // Force symmetric if the font is above the threshold or there is an explicit mode. + // Here we check if the size exceeds 20 before checking the GASP table to match the + // results of calling GetRecommendedRenderingMode/Direct2D, which skip looking at + // the GASP table if the text is too large. + } else if (realTextSize > SkIntToScalar(20) || + typeface->GetRenderingMode() == DWRITE_RENDERING_MODE_NATURAL || + typeface->GetRenderingMode() == DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC) { + fTextSizeRender = realTextSize; + fRenderingMode = typeface->GetRenderingMode() == DWRITE_RENDERING_MODE_NATURAL ? + DWRITE_RENDERING_MODE_NATURAL : DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; + fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; + fTextSizeMeasure = realTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; + // If the font has a gasp table version 1, use it to determine symmetric rendering. + } else if (get_gasp_range(typeface, SkScalarRoundToInt(gdiTextSize), &range) && + range.fVersion >= 1) { + fTextSizeRender = realTextSize; + fRenderingMode = !range.fFlags.field.SymmetricSmoothing ? + DWRITE_RENDERING_MODE_NATURAL : DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; + fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; + fTextSizeMeasure = realTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; + // Fonts with hints, no gasp or gasp version 0, and below 20px get non-symmetric rendering. + // Often such fonts have hints which were only tested with GDI ClearType classic. + // Some of these fonts rely on drop out control in the y direction in order to be legible. + // Tenor Sans + // https://fonts.google.com/specimen/Tenor+Sans + // Gill Sans W04 + // https://cdn.leagueoflegends.com/lolkit/1.1.9/resources/fonts/gill-sans-w04-book.woff + // https://na.leagueoflegends.com/en/news/game-updates/patch/patch-410-notes + // See https://crbug.com/385897 + } else { + if (is_hinted(this, typeface)) { + fTextSizeRender = gdiTextSize; + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL; + } else { + // Unhinted but with no gasp and below 20px defaults to symmetric for + // GetRecommendedRenderingMode. + fTextSizeRender = realTextSize; + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; + } + fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; + fTextSizeMeasure = realTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; + } + + // DirectWrite2 allows for grayscale hinting. + fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE; + if (typeface->fFactory2 && typeface->fDWriteFontFace2 && + SkMask::kA8_Format == fRec.fMaskFormat && + !(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag)) + { + // DWRITE_TEXTURE_ALIASED_1x1 is now misnamed, it must also be used with grayscale. + fTextureType = DWRITE_TEXTURE_ALIASED_1x1; + fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE; + } + + // DirectWrite2 allows hinting to be disabled. + fGridFitMode = DWRITE_GRID_FIT_MODE_ENABLED; + if (fRec.getHinting() == SkFontHinting::kNone) { + fGridFitMode = DWRITE_GRID_FIT_MODE_DISABLED; + if (fRenderingMode != DWRITE_RENDERING_MODE_ALIASED) { + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; + } + } + + if (this->isLinearMetrics()) { + fTextSizeMeasure = realTextSize; + fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; + } +} + +SkScalerContext_DW::~SkScalerContext_DW() { +} + +unsigned SkScalerContext_DW::generateGlyphCount() { + if (fGlyphCount < 0) { + fGlyphCount = this->getDWriteTypeface()->fDWriteFontFace->GetGlyphCount(); + } + return fGlyphCount; +} + +bool SkScalerContext_DW::generateAdvance(SkGlyph* glyph) { + glyph->fAdvanceX = 0; + glyph->fAdvanceY = 0; + + uint16_t glyphId = glyph->getGlyphID(); + DWRITE_GLYPH_METRICS gm; + + if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode || + DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode) + { + MaybeExclusive l(this); + HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGdiCompatibleGlyphMetrics( + fTextSizeMeasure, + 1.0f, // pixelsPerDip + // This parameter does not act like the lpmat2 parameter to GetGlyphOutlineW. + // If it did then GsA here and G_inv below to mapVectors. + nullptr, + DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode, + &glyphId, 1, + &gm), + "Could not get gdi compatible glyph metrics."); + } else { + MaybeExclusive l(this); + HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm), + "Could not get design metrics."); + } + + DWRITE_FONT_METRICS dwfm; + { + MaybeShared l(this); + this->getDWriteTypeface()->fDWriteFontFace->GetMetrics(&dwfm); + } + SkScalar advanceX = fTextSizeMeasure * gm.advanceWidth / dwfm.designUnitsPerEm; + + SkVector advance = { advanceX, 0 }; + if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode || + DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode) + { + // DirectWrite produced 'compatible' metrics, but while close, + // the end result is not always an integer as it would be with GDI. + advance.fX = SkScalarRoundToScalar(advance.fX); + } + fSkXform.mapVectors(&advance, 1); + + glyph->fAdvanceX = SkScalarToFloat(advance.fX); + glyph->fAdvanceY = SkScalarToFloat(advance.fY); + return true; +} + +HRESULT SkScalerContext_DW::getBoundingBox(SkGlyph* glyph, + DWRITE_RENDERING_MODE renderingMode, + DWRITE_TEXTURE_TYPE textureType, + RECT* bbox) +{ + //Measure raster size. + fXform.dx = SkFixedToFloat(glyph->getSubXFixed()); + fXform.dy = SkFixedToFloat(glyph->getSubYFixed()); + + FLOAT advance = 0; + + UINT16 glyphId = glyph->getGlyphID(); + + DWRITE_GLYPH_OFFSET offset; + offset.advanceOffset = 0.0f; + offset.ascenderOffset = 0.0f; + + DWRITE_GLYPH_RUN run; + run.glyphCount = 1; + run.glyphAdvances = &advance; + run.fontFace = this->getDWriteTypeface()->fDWriteFontFace.get(); + run.fontEmSize = SkScalarToFloat(fTextSizeRender); + run.bidiLevel = 0; + run.glyphIndices = &glyphId; + run.isSideways = FALSE; + run.glyphOffsets = &offset; + + SkTScopedComPtr glyphRunAnalysis; + { + MaybeExclusive l(this); + // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs. + if (this->getDWriteTypeface()->fFactory2 && + (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED || + fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE)) + { + HRM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis( + &run, + &fXform, + renderingMode, + fMeasuringMode, + fGridFitMode, + fAntiAliasMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create DW2 glyph run analysis."); + } else { + HRM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis(&run, + 1.0f, // pixelsPerDip, + &fXform, + renderingMode, + fMeasuringMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create glyph run analysis."); + } + } + { + MaybeShared l(this); + HRM(glyphRunAnalysis->GetAlphaTextureBounds(textureType, bbox), + "Could not get texture bounds."); + } + return S_OK; +} + +bool SkScalerContext_DW::isColorGlyph(const SkGlyph& glyph) { + SkTScopedComPtr colorLayer; + return getColorGlyphRun(glyph, &colorLayer); +} + +bool SkScalerContext_DW::isPngGlyph(const SkGlyph& glyph) { + if (!this->getDWriteTypeface()->fDWriteFontFace4) { + return false; + } + + DWRITE_GLYPH_IMAGE_FORMATS f; + IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get(); + HRBM(fontFace4->GetGlyphImageFormats(glyph.getGlyphID(), 0, UINT32_MAX, &f), + "Cannot get glyph image formats."); + return f & DWRITE_GLYPH_IMAGE_FORMATS_PNG; +} + +bool SkScalerContext_DW::getColorGlyphRun(const SkGlyph& glyph, + IDWriteColorGlyphRunEnumerator** colorGlyph) +{ + FLOAT advance = 0; + UINT16 glyphId = glyph.getGlyphID(); + + DWRITE_GLYPH_OFFSET offset; + offset.advanceOffset = 0.0f; + offset.ascenderOffset = 0.0f; + + DWRITE_GLYPH_RUN run; + run.glyphCount = 1; + run.glyphAdvances = &advance; + run.fontFace = this->getDWriteTypeface()->fDWriteFontFace.get(); + run.fontEmSize = SkScalarToFloat(fTextSizeRender); + run.bidiLevel = 0; + run.glyphIndices = &glyphId; + run.isSideways = FALSE; + run.glyphOffsets = &offset; + + HRESULT hr = this->getDWriteTypeface()->fFactory2->TranslateColorGlyphRun( + 0, 0, &run, nullptr, fMeasuringMode, &fXform, 0, colorGlyph); + if (hr == DWRITE_E_NOCOLOR) { + return false; + } + HRBM(hr, "Failed to translate color glyph run"); + return true; +} + +void SkScalerContext_DW::generateColorMetrics(SkGlyph* glyph) { + SkTScopedComPtr colorLayers; + HRVM(getColorGlyphRun(*glyph, &colorLayers), "Could not get color glyph run"); + SkASSERT(colorLayers.get()); + + SkRect bounds = SkRect::MakeEmpty(); + BOOL hasNextRun = FALSE; + while (SUCCEEDED(colorLayers->MoveNext(&hasNextRun)) && hasNextRun) { + const DWRITE_COLOR_GLYPH_RUN* colorGlyph; + HRVM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run"); + + SkPath path; + SkTScopedComPtr geometryToPath; + HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath), + "Could not create geometry to path converter."); + { + MaybeExclusive l(this); + HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline( + colorGlyph->glyphRun.fontEmSize, + colorGlyph->glyphRun.glyphIndices, + colorGlyph->glyphRun.glyphAdvances, + colorGlyph->glyphRun.glyphOffsets, + colorGlyph->glyphRun.glyphCount, + colorGlyph->glyphRun.isSideways, + colorGlyph->glyphRun.bidiLevel % 2, //rtl + geometryToPath.get()), + "Could not create glyph outline."); + } + bounds.join(path.getBounds()); + } + SkMatrix matrix = fSkXform; + if (this->isSubpixel()) { + matrix.postTranslate(SkFixedToScalar(glyph->getSubXFixed()), + SkFixedToScalar(glyph->getSubYFixed())); + } + matrix.mapRect(&bounds); + // Round float bound values into integer. + SkIRect ibounds = bounds.roundOut(); + + glyph->fWidth = ibounds.fRight - ibounds.fLeft; + glyph->fHeight = ibounds.fBottom - ibounds.fTop; + glyph->fLeft = ibounds.fLeft; + glyph->fTop = ibounds.fTop; +} + +#ifdef USE_PNG +namespace { +struct Context { + SkTScopedComPtr fontFace4; + void* glyphDataContext; + Context(IDWriteFontFace4* face4, void* context) + : fontFace4(SkRefComPtr(face4)) + , glyphDataContext(context) + {} +}; + +static void ReleaseProc(const void* ptr, void* context) { + Context* ctx = (Context*)context; + ctx->fontFace4->ReleaseGlyphImageData(ctx->glyphDataContext); + delete ctx; +} +} + +void SkScalerContext_DW::generatePngMetrics(SkGlyph* glyph) { + SkASSERT(isPngGlyph(*glyph)); + SkASSERT(glyph->fMaskFormat == SkMask::Format::kARGB32_Format); + SkASSERT(this->getDWriteTypeface()->fDWriteFontFace4); + + IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get(); + DWRITE_GLYPH_IMAGE_DATA glyphData; + void* glyphDataContext; + HRVM(fontFace4->GetGlyphImageData(glyph->getGlyphID(), + fTextSizeRender, + DWRITE_GLYPH_IMAGE_FORMATS_PNG, + &glyphData, + &glyphDataContext), + "Glyph image data could not be acquired."); + + Context* context = new Context(fontFace4, glyphDataContext); + sk_sp data = SkData::MakeWithProc(glyphData.imageData, + glyphData.imageDataSize, + &ReleaseProc, + context); + + std::unique_ptr codec = SkCodec::MakeFromData(std::move(data)); + if (!codec) { + return; + } + + SkImageInfo info = codec->getInfo(); + SkRect bounds = SkRect::MakeLTRB(SkIntToScalar(info.bounds().fLeft), + SkIntToScalar(info.bounds().fTop), + SkIntToScalar(info.bounds().fRight), + SkIntToScalar(info.bounds().fBottom)); + + SkMatrix matrix = fSkXform; + SkScalar scale = fTextSizeRender / glyphData.pixelsPerEm; + matrix.preScale(scale, scale); + matrix.preTranslate(-glyphData.horizontalLeftOrigin.x, -glyphData.horizontalLeftOrigin.y); + if (this->isSubpixel()) { + matrix.postTranslate(SkFixedToScalar(glyph->getSubXFixed()), + SkFixedToScalar(glyph->getSubYFixed())); + } + matrix.mapRect(&bounds); + bounds.roundOut(); + + glyph->fWidth = bounds.width(); + glyph->fHeight = bounds.height(); + glyph->fLeft = bounds.left(); + glyph->fTop = bounds.top(); + return; +} +#endif + +void SkScalerContext_DW::generateMetrics(SkGlyph* glyph) { + + + // GetAlphaTextureBounds succeeds but sometimes returns empty bounds like + // { 0x80000000, 0x80000000, 0x80000000, 0x80000000 } + // for small, but not quite zero, sized glyphs. + // Only set as non-empty if the returned bounds are non-empty. + auto glyphCheckAndSetBounds = [](SkGlyph* glyph, const RECT& bbox) { + if (bbox.left >= bbox.right || bbox.top >= bbox.bottom) { + return false; + } + + // We're trying to pack left and top into int16_t, + // and width and height into uint16_t, after outsetting by 1. + if (!SkIRect::MakeXYWH(-32767, -32767, 65535, 65535).contains( + SkIRect::MakeLTRB(bbox.left, bbox.top, bbox.right, bbox.bottom))) { + return false; + } + + glyph->fWidth = SkToU16(bbox.right - bbox.left); + glyph->fHeight = SkToU16(bbox.bottom - bbox.top); + glyph->fLeft = SkToS16(bbox.left); + glyph->fTop = SkToS16(bbox.top); + return true; + }; + + glyph->fWidth = 0; + glyph->fHeight = 0; + glyph->fLeft = 0; + glyph->fTop = 0; + glyph->fMaskFormat = fRec.fMaskFormat; + + if (!this->generateAdvance(glyph)) { + return; + } + + if (fIsColorFont && isColorGlyph(*glyph)) { + glyph->fMaskFormat = SkMask::kARGB32_Format; + generateColorMetrics(glyph); + return; + } + + if (fIsColorFont && isPngGlyph(*glyph)) { +#ifdef USE_PNG + glyph->fMaskFormat = SkMask::kARGB32_Format; + generatePngMetrics(glyph); +#endif + return; + } + + RECT bbox; + HRVM(this->getBoundingBox(glyph, fRenderingMode, fTextureType, &bbox), + "Requested bounding box could not be determined."); + + if (glyphCheckAndSetBounds(glyph, bbox)) { + return; + } + + // GetAlphaTextureBounds succeeds but returns an empty RECT if there are no + // glyphs of the specified texture type or it is too big for smoothing. + // When this happens, try with the alternate texture type. + if (DWRITE_TEXTURE_ALIASED_1x1 != fTextureType || + DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE == fAntiAliasMode) + { + HRVM(this->getBoundingBox(glyph, + DWRITE_RENDERING_MODE_ALIASED, + DWRITE_TEXTURE_ALIASED_1x1, + &bbox), + "Fallback bounding box could not be determined."); + if (glyphCheckAndSetBounds(glyph, bbox)) { + glyph->fForceBW = 1; + glyph->fMaskFormat = SkMask::kBW_Format; + } + } + // TODO: handle the case where a request for DWRITE_TEXTURE_ALIASED_1x1 + // fails, and try DWRITE_TEXTURE_CLEARTYPE_3x1. +} + +void SkScalerContext_DW::generateFontMetrics(SkFontMetrics* metrics) { + if (nullptr == metrics) { + return; + } + + sk_bzero(metrics, sizeof(*metrics)); + + DWRITE_FONT_METRICS dwfm; + if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode || + DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode) + { + this->getDWriteTypeface()->fDWriteFontFace->GetGdiCompatibleMetrics( + fTextSizeRender, + 1.0f, // pixelsPerDip + &fXform, + &dwfm); + } else { + this->getDWriteTypeface()->fDWriteFontFace->GetMetrics(&dwfm); + } + + SkScalar upem = SkIntToScalar(dwfm.designUnitsPerEm); + + metrics->fAscent = -fTextSizeRender * SkIntToScalar(dwfm.ascent) / upem; + metrics->fDescent = fTextSizeRender * SkIntToScalar(dwfm.descent) / upem; + metrics->fLeading = fTextSizeRender * SkIntToScalar(dwfm.lineGap) / upem; + metrics->fXHeight = fTextSizeRender * SkIntToScalar(dwfm.xHeight) / upem; + metrics->fCapHeight = fTextSizeRender * SkIntToScalar(dwfm.capHeight) / upem; + metrics->fUnderlineThickness = fTextSizeRender * SkIntToScalar(dwfm.underlineThickness) / upem; + metrics->fUnderlinePosition = -(fTextSizeRender * SkIntToScalar(dwfm.underlinePosition) / upem); + metrics->fStrikeoutThickness = fTextSizeRender * SkIntToScalar(dwfm.strikethroughThickness) / upem; + metrics->fStrikeoutPosition = -(fTextSizeRender * SkIntToScalar(dwfm.strikethroughPosition) / upem); + + metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kStrikeoutThicknessIsValid_Flag; + metrics->fFlags |= SkFontMetrics::kStrikeoutPositionIsValid_Flag; + + if (this->getDWriteTypeface()->fDWriteFontFace1.get()) { + DWRITE_FONT_METRICS1 dwfm1; + this->getDWriteTypeface()->fDWriteFontFace1->GetMetrics(&dwfm1); + metrics->fTop = -fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxTop) / upem; + metrics->fBottom = -fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxBottom) / upem; + metrics->fXMin = fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxLeft) / upem; + metrics->fXMax = fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxRight) / upem; + + metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin; + return; + } + + AutoTDWriteTable head(this->getDWriteTypeface()->fDWriteFontFace.get()); + if (head.fExists && + head.fSize >= sizeof(SkOTTableHead) && + head->version == SkOTTableHead::version1) + { + metrics->fTop = -fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->yMax) / upem; + metrics->fBottom = -fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->yMin) / upem; + metrics->fXMin = fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->xMin) / upem; + metrics->fXMax = fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->xMax) / upem; + + metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin; + return; + } + + metrics->fTop = metrics->fAscent; + metrics->fBottom = metrics->fDescent; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "include/private/SkColorData.h" + +void SkScalerContext_DW::BilevelToBW(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph) { + const int width = glyph.width(); + const size_t dstRB = (width + 7) >> 3; + uint8_t* SK_RESTRICT dst = static_cast(glyph.fImage); + + int byteCount = width >> 3; + int bitCount = width & 7; + + for (int y = 0; y < glyph.height(); ++y) { + if (byteCount > 0) { + for (int i = 0; i < byteCount; ++i) { + unsigned byte = 0; + byte |= src[0] & (1 << 7); + byte |= src[1] & (1 << 6); + byte |= src[2] & (1 << 5); + byte |= src[3] & (1 << 4); + byte |= src[4] & (1 << 3); + byte |= src[5] & (1 << 2); + byte |= src[6] & (1 << 1); + byte |= src[7] & (1 << 0); + dst[i] = byte; + src += 8; + } + } + if (bitCount > 0) { + unsigned byte = 0; + unsigned mask = 0x80; + for (int i = 0; i < bitCount; i++) { + byte |= (src[i]) & mask; + mask >>= 1; + } + dst[byteCount] = byte; + } + src += bitCount; + dst += dstRB; + } +} + +template +void SkScalerContext_DW::GrayscaleToA8(const uint8_t* SK_RESTRICT src, + const SkGlyph& glyph, + const uint8_t* table8) { + const size_t dstRB = glyph.rowBytes(); + const int width = glyph.width(); + uint8_t* SK_RESTRICT dst = static_cast(glyph.fImage); + + for (int y = 0; y < glyph.height(); y++) { + for (int i = 0; i < width; i++) { + U8CPU a = *(src++); + dst[i] = sk_apply_lut_if(a, table8); + } + dst = SkTAddOffset(dst, dstRB); + } +} + +template +void SkScalerContext_DW::RGBToA8(const uint8_t* SK_RESTRICT src, + const SkGlyph& glyph, + const uint8_t* table8) { + const size_t dstRB = glyph.rowBytes(); + const int width = glyph.width(); + uint8_t* SK_RESTRICT dst = static_cast(glyph.fImage); + + for (int y = 0; y < glyph.height(); y++) { + for (int i = 0; i < width; i++) { + U8CPU g = src[1]; + src += 3; + + // Ignore the R, B channels. It looks the closest to what + // D2D with grayscale AA has. But there's no way + // to just get a grayscale AA alpha texture from a glyph run. + dst[i] = sk_apply_lut_if(g, table8); + } + dst = SkTAddOffset(dst, dstRB); + } +} + +template +void SkScalerContext_DW::RGBToLcd16(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, + const uint8_t* tableB, int clearTypeLevel) { + const size_t dstRB = glyph.rowBytes(); + const int width = glyph.width(); + uint16_t* SK_RESTRICT dst = static_cast(glyph.fImage); + + for (int y = 0; y < glyph.height(); y++) { + for (int i = 0; i < width; i++) { + int r, g, b; + if (RGB) { + r = sk_apply_lut_if(*(src++), tableR); + g = sk_apply_lut_if(*(src++), tableG); + b = sk_apply_lut_if(*(src++), tableB); + } else { + b = sk_apply_lut_if(*(src++), tableB); + g = sk_apply_lut_if(*(src++), tableG); + r = sk_apply_lut_if(*(src++), tableR); + } + r = g + (((r - g) * clearTypeLevel) >> 8); + b = g + (((b - g) * clearTypeLevel) >> 8); + dst[i] = SkPack888ToRGB16(r, g, b); + } + dst = SkTAddOffset(dst, dstRB); + } +} + +const void* SkScalerContext_DW::drawDWMask(const SkGlyph& glyph, + DWRITE_RENDERING_MODE renderingMode, + DWRITE_TEXTURE_TYPE textureType) +{ + int sizeNeeded = glyph.width() * glyph.height(); + if (DWRITE_TEXTURE_CLEARTYPE_3x1 == textureType) { + sizeNeeded *= 3; + } + if (sizeNeeded > fBits.count()) { + fBits.setCount(sizeNeeded); + } + + // erase + memset(fBits.begin(), 0, sizeNeeded); + + fXform.dx = SkFixedToFloat(glyph.getSubXFixed()); + fXform.dy = SkFixedToFloat(glyph.getSubYFixed()); + + FLOAT advance = 0.0f; + + UINT16 index = glyph.getGlyphID(); + + DWRITE_GLYPH_OFFSET offset; + offset.advanceOffset = 0.0f; + offset.ascenderOffset = 0.0f; + + DWRITE_GLYPH_RUN run; + run.glyphCount = 1; + run.glyphAdvances = &advance; + run.fontFace = this->getDWriteTypeface()->fDWriteFontFace.get(); + run.fontEmSize = SkScalarToFloat(fTextSizeRender); + run.bidiLevel = 0; + run.glyphIndices = &index; + run.isSideways = FALSE; + run.glyphOffsets = &offset; + { + SkTScopedComPtr glyphRunAnalysis; + { + MaybeExclusive l(this); + // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs. + if (this->getDWriteTypeface()->fFactory2 && + (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED || + fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE)) + { + HRNM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(&run, + &fXform, + renderingMode, + fMeasuringMode, + fGridFitMode, + fAntiAliasMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create DW2 glyph run analysis."); + } else { + HRNM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis(&run, + 1.0f, // pixelsPerDip, + &fXform, + renderingMode, + fMeasuringMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create glyph run analysis."); + } + } + //NOTE: this assumes that the glyph has already been measured + //with an exact same glyph run analysis. + RECT bbox; + bbox.left = glyph.left(); + bbox.top = glyph.top(); + bbox.right = glyph.left() + glyph.width(); + bbox.bottom = glyph.top() + glyph.height(); + { + MaybeShared l(this); + HRNM(glyphRunAnalysis->CreateAlphaTexture(textureType, + &bbox, + fBits.begin(), + sizeNeeded), + "Could not draw mask."); + } + } + return fBits.begin(); +} + +void SkScalerContext_DW::generateColorGlyphImage(const SkGlyph& glyph) { + SkASSERT(isColorGlyph(glyph)); + SkASSERT(glyph.fMaskFormat == SkMask::Format::kARGB32_Format); + + memset(glyph.fImage, 0, glyph.imageSize()); + + SkTScopedComPtr colorLayers; + getColorGlyphRun(glyph, &colorLayers); + SkASSERT(colorLayers.get()); + + SkMatrix matrix = fSkXform; + matrix.postTranslate(-SkIntToScalar(glyph.left()), -SkIntToScalar(glyph.top())); + if (this->isSubpixel()) { + matrix.postTranslate(SkFixedToScalar(glyph.getSubXFixed()), + SkFixedToScalar(glyph.getSubYFixed())); + } + SkRasterClip rc(SkIRect::MakeWH(glyph.width(), glyph.height())); + SkDraw draw; + draw.fDst = SkPixmap(SkImageInfo::MakeN32(glyph.width(), glyph.height(), kPremul_SkAlphaType), + glyph.fImage, + glyph.rowBytesUsingFormat(SkMask::Format::kARGB32_Format)); + draw.fMatrix = &matrix; + draw.fRC = &rc; + + SkPaint paint; + paint.setAntiAlias(fRenderingMode != DWRITE_RENDERING_MODE_ALIASED); + + BOOL hasNextRun = FALSE; + while (SUCCEEDED(colorLayers->MoveNext(&hasNextRun)) && hasNextRun) { + const DWRITE_COLOR_GLYPH_RUN* colorGlyph; + HRVM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run"); + + SkColor color; + if (colorGlyph->paletteIndex != 0xffff) { + color = SkColorSetARGB(sk_float_round2int(colorGlyph->runColor.a * 255), + sk_float_round2int(colorGlyph->runColor.r * 255), + sk_float_round2int(colorGlyph->runColor.g * 255), + sk_float_round2int(colorGlyph->runColor.b * 255)); + } else { + // If all components of runColor are 0 or (equivalently) paletteIndex is 0xFFFF then + // the 'current brush' is used. fRec.getLuminanceColor() is kinda sorta what is wanted + // here, but not really, it will often be the wrong value because it wan't designed for + // this. + // TODO: implement this fully, bug.skia.org/5788 + color = fRec.getLuminanceColor(); + } + paint.setColor(color); + + SkPath path; + SkTScopedComPtr geometryToPath; + HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath), + "Could not create geometry to path converter."); + { + MaybeExclusive l(this); + HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline( + colorGlyph->glyphRun.fontEmSize, + colorGlyph->glyphRun.glyphIndices, + colorGlyph->glyphRun.glyphAdvances, + colorGlyph->glyphRun.glyphOffsets, + colorGlyph->glyphRun.glyphCount, + colorGlyph->glyphRun.isSideways, + colorGlyph->glyphRun.bidiLevel % 2, //rtl + geometryToPath.get()), + "Could not create glyph outline."); + } + draw.drawPath(path, paint, nullptr, true /* pathIsMutable */); + } +} + +#ifdef USE_PNG +void SkScalerContext_DW::generatePngGlyphImage(const SkGlyph& glyph) { + SkASSERT(isPngGlyph(glyph)); + SkASSERT(glyph.fMaskFormat == SkMask::Format::kARGB32_Format); + SkASSERT(this->getDWriteTypeface()->fDWriteFontFace4); + + IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get(); + DWRITE_GLYPH_IMAGE_DATA glyphData; + void* glyphDataContext; + HRVM(fontFace4->GetGlyphImageData(glyph.getGlyphID(), + fTextSizeRender, + DWRITE_GLYPH_IMAGE_FORMATS_PNG, + &glyphData, + &glyphDataContext), + "Glyph image data could not be acquired."); + Context* context = new Context(fontFace4, glyphDataContext); + sk_sp data = SkData::MakeWithProc(glyphData.imageData, + glyphData.imageDataSize, + &ReleaseProc, + context); + sk_sp image = SkImage::MakeFromEncoded(std::move(data)); + + SkBitmap dstBitmap; + dstBitmap.setInfo(SkImageInfo::Make(glyph.width(), glyph.height(), + kN32_SkColorType, + kPremul_SkAlphaType), + glyph.rowBytes()); + dstBitmap.setPixels(glyph.fImage); + + SkCanvas canvas(dstBitmap); + canvas.clear(SK_ColorTRANSPARENT); + canvas.translate(-glyph.left(), -glyph.top()); + if (this->isSubpixel()) { + canvas.translate(SkFixedToScalar(glyph.getSubXFixed()), + SkFixedToScalar(glyph.getSubYFixed())); + } + canvas.concat(fSkXform); + SkScalar ratio = fTextSizeRender / glyphData.pixelsPerEm; + canvas.scale(ratio, ratio); + canvas.translate(-glyphData.horizontalLeftOrigin.x, -glyphData.horizontalLeftOrigin.y); + canvas.drawImage(image, 0, 0, nullptr); +} +#endif + +void SkScalerContext_DW::generateImage(const SkGlyph& glyph) { + //Create the mask. + DWRITE_RENDERING_MODE renderingMode = fRenderingMode; + DWRITE_TEXTURE_TYPE textureType = fTextureType; + if (glyph.fForceBW) { + renderingMode = DWRITE_RENDERING_MODE_ALIASED; + textureType = DWRITE_TEXTURE_ALIASED_1x1; + } + + if (SkMask::kARGB32_Format == glyph.fMaskFormat) { + if (fIsColorFont) { + if (isColorGlyph(glyph)) { + generateColorGlyphImage(glyph); + return; +#ifdef USE_PNG + } else if (isPngGlyph(glyph)) { + generatePngGlyphImage(glyph); + return; +#endif + } + } + SkDEBUGFAIL("Could not generate image from the given color font format."); + return; + } + + const void* bits = this->drawDWMask(glyph, renderingMode, textureType); + if (!bits) { + sk_bzero(glyph.fImage, glyph.imageSize()); + return; + } + + //Copy the mask into the glyph. + const uint8_t* src = (const uint8_t*)bits; + if (DWRITE_RENDERING_MODE_ALIASED == renderingMode) { + SkASSERT(SkMask::kBW_Format == glyph.fMaskFormat); + SkASSERT(DWRITE_TEXTURE_ALIASED_1x1 == textureType); + BilevelToBW(src, glyph); + } else if (!isLCD(fRec)) { + if (textureType == DWRITE_TEXTURE_ALIASED_1x1) { + if (fPreBlend.isApplicable()) { + GrayscaleToA8(src, glyph, fPreBlend.fG); + } else { + GrayscaleToA8(src, glyph, fPreBlend.fG); + } + } else { + if (fPreBlend.isApplicable()) { + RGBToA8(src, glyph, fPreBlend.fG); + } else { + RGBToA8(src, glyph, fPreBlend.fG); + } + } + } else { + SkASSERT(SkMask::kLCD16_Format == glyph.fMaskFormat); + if (fPreBlend.isApplicable()) { + if (fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag) { + RGBToLcd16(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB, fClearTypeLevel); + } else { + RGBToLcd16(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB, fClearTypeLevel); + } + } else { + if (fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag) { + RGBToLcd16(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB, fClearTypeLevel); + } else { + RGBToLcd16(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB, fClearTypeLevel); + } + } + } +} + +bool SkScalerContext_DW::generatePath(SkGlyphID glyph, SkPath* path) { + SkASSERT(path); + + path->reset(); + + SkTScopedComPtr geometryToPath; + HRBM(SkDWriteGeometrySink::Create(path, &geometryToPath), + "Could not create geometry to path converter."); + UINT16 glyphId = SkTo(glyph); + { + MaybeExclusive l(this); + //TODO: convert to<->from DIUs? This would make a difference if hinting. + //It may not be needed, it appears that DirectWrite only hints at em size. + HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGlyphRunOutline( + SkScalarToFloat(fTextSizeRender), + &glyphId, + nullptr, //advances + nullptr, //offsets + 1, //num glyphs + FALSE, //sideways + FALSE, //rtl + geometryToPath.get()), + "Could not create glyph outline."); + } + + path->transform(fSkXform); + return true; +} + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h new file mode 100644 index 0000000000..ac7dae6e37 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h @@ -0,0 +1,106 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkScalarContext_win_dw_DEFINED +#define SkScalarContext_win_dw_DEFINED + +#include "include/core/SkScalar.h" +#include "include/core/SkTypes.h" +#include "src/core/SkScalerContext.h" +#include "src/ports/SkTypeface_win_dw.h" + +#include +#include + +class SkGlyph; +class SkDescriptor; + +class SkScalerContext_DW : public SkScalerContext { +public: + SkScalerContext_DW(sk_sp, + const SkScalerContextEffects&, + const SkDescriptor*); + ~SkScalerContext_DW() override; + + // The IDWriteFontFace4 interface is only available in DWrite 3, + // so checking if it was found is sufficient to detect DWrite 3. + bool isDWrite3() { return bool(getDWriteTypeface()->fDWriteFontFace4); } + +protected: + unsigned generateGlyphCount() override; + bool generateAdvance(SkGlyph* glyph) override; + void generateMetrics(SkGlyph* glyph) override; + void generateImage(const SkGlyph& glyph) override; + bool generatePath(SkGlyphID glyph, SkPath* path) override; + void generateFontMetrics(SkFontMetrics*) override; + +private: + static void BilevelToBW(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph); + + template + static void GrayscaleToA8(const uint8_t* SK_RESTRICT src, + const SkGlyph& glyph, + const uint8_t* table8); + + template + static void RGBToA8(const uint8_t* SK_RESTRICT src, + const SkGlyph& glyph, + const uint8_t* table8); + + template + static void RGBToLcd16(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB, + int clearTypeLevel); + + const void* drawDWMask(const SkGlyph& glyph, + DWRITE_RENDERING_MODE renderingMode, + DWRITE_TEXTURE_TYPE textureType); + + HRESULT getBoundingBox(SkGlyph* glyph, + DWRITE_RENDERING_MODE renderingMode, + DWRITE_TEXTURE_TYPE textureType, + RECT* bbox); + + bool isColorGlyph(const SkGlyph& glyph); + + bool isPngGlyph(const SkGlyph& glyph); + + DWriteFontTypeface* getDWriteTypeface() { + return static_cast(this->getTypeface()); + } + + bool getColorGlyphRun(const SkGlyph& glyph, IDWriteColorGlyphRunEnumerator** colorGlyph); + + void generateColorMetrics(SkGlyph* glyph); + + void generateColorGlyphImage(const SkGlyph& glyph); + + void generatePngMetrics(SkGlyph* glyph); + + void generatePngGlyphImage(const SkGlyph& glyph); + + + SkTDArray fBits; + /** The total matrix without the text height scale. */ + SkMatrix fSkXform; + /** The total matrix without the text height scale. */ + DWRITE_MATRIX fXform; + /** The text size to render with. */ + SkScalar fTextSizeRender; + /** The text size to measure with. */ + SkScalar fTextSizeMeasure; + int fGlyphCount; + DWRITE_RENDERING_MODE fRenderingMode; + DWRITE_TEXTURE_TYPE fTextureType; + DWRITE_MEASURING_MODE fMeasuringMode; + DWRITE_TEXT_ANTIALIAS_MODE fAntiAliasMode; + DWRITE_GRID_FIT_MODE fGridFitMode; + bool fIsColorFont; + int fClearTypeLevel; +}; + +#endif diff --git a/gfx/skia/skia/src/ports/SkTLS_none.cpp b/gfx/skia/skia/src/ports/SkTLS_none.cpp new file mode 100644 index 0000000000..7d9b31f1ea --- /dev/null +++ b/gfx/skia/skia/src/ports/SkTLS_none.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkTLS.h" + +static void* gSpecific = nullptr; + +void* SkTLS::PlatformGetSpecific(bool) { + return gSpecific; +} + +void SkTLS::PlatformSetSpecific(void* ptr) { + gSpecific = ptr; +} diff --git a/gfx/skia/skia/src/ports/SkTLS_pthread.cpp b/gfx/skia/skia/src/ports/SkTLS_pthread.cpp new file mode 100644 index 0000000000..c96271b2a2 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkTLS_pthread.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkOnce.h" +#include "src/core/SkTLS.h" + +#include + +static pthread_key_t gSkTLSKey; + +void* SkTLS::PlatformGetSpecific(bool forceCreateTheSlot) { + // should we use forceCreateTheSlot to potentially just return nullptr if + // we've never been called with forceCreateTheSlot==true ? + static SkOnce once; + once(pthread_key_create, &gSkTLSKey, SkTLS::Destructor); + return pthread_getspecific(gSkTLSKey); +} + +void SkTLS::PlatformSetSpecific(void* ptr) { + (void)pthread_setspecific(gSkTLSKey, ptr); +} diff --git a/gfx/skia/skia/src/ports/SkTLS_win.cpp b/gfx/skia/skia/src/ports/SkTLS_win.cpp new file mode 100644 index 0000000000..e598c2fb7d --- /dev/null +++ b/gfx/skia/skia/src/ports/SkTLS_win.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +#include "include/private/SkMutex.h" +#include "src/core/SkLeanWindows.h" +#include "src/core/SkTLS.h" + +static bool gOnce = false; +static DWORD gTlsIndex; + +void* SkTLS::PlatformGetSpecific(bool forceCreateTheSlot) { + static SkMutex& mutex = *(new SkMutex); + if (!forceCreateTheSlot && !gOnce) { + return nullptr; + } + + if (!gOnce) { + SkAutoMutexExclusive tmp(mutex); + if (!gOnce) { + gTlsIndex = TlsAlloc(); + gOnce = true; + } + } + return TlsGetValue(gTlsIndex); +} + +void SkTLS::PlatformSetSpecific(void* ptr) { + SkASSERT(gOnce); + (void)TlsSetValue(gTlsIndex, ptr); +} + +// Call TLS destructors on thread exit. Code based on Chromium's +// base/threading/thread_local_storage_win.cc +#ifdef _WIN64 + +#pragma comment(linker, "/INCLUDE:_tls_used") +#pragma comment(linker, "/INCLUDE:skia_tls_callback") + +#else + +#pragma comment(linker, "/INCLUDE:__tls_used") +#pragma comment(linker, "/INCLUDE:_skia_tls_callback") + +#endif + +void NTAPI onTLSCallback(PVOID unused, DWORD reason, PVOID unused2) { + if ((DLL_THREAD_DETACH == reason || DLL_PROCESS_DETACH == reason) && gOnce) { + void* ptr = TlsGetValue(gTlsIndex); + if (ptr != nullptr) { + SkTLS::Destructor(ptr); + TlsSetValue(gTlsIndex, nullptr); + } + } +} + +extern "C" { + +#ifdef _WIN64 + +#pragma const_seg(".CRT$XLB") +extern const PIMAGE_TLS_CALLBACK skia_tls_callback; +const PIMAGE_TLS_CALLBACK skia_tls_callback = onTLSCallback; +#pragma const_seg() + +#else + +#pragma data_seg(".CRT$XLB") +PIMAGE_TLS_CALLBACK skia_tls_callback = onTLSCallback; +#pragma data_seg() + +#endif +} + +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkTypeface_win_dw.cpp b/gfx/skia/skia/src/ports/SkTypeface_win_dw.cpp new file mode 100644 index 0000000000..cdc243b59d --- /dev/null +++ b/gfx/skia/skia/src/ports/SkTypeface_win_dw.cpp @@ -0,0 +1,533 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/utils/win/SkDWriteNTDDI_VERSION.h" + +#include "include/core/SkTypes.h" +#if defined(SK_BUILD_FOR_WIN) + +// SkTypes will include Windows.h, which will pull in all of the GDI defines. +// GDI #defines GetGlyphIndices to GetGlyphIndicesA or GetGlyphIndicesW, but +// IDWriteFontFace has a method called GetGlyphIndices. Since this file does +// not use GDI, undefing GetGlyphIndices makes things less confusing. +#undef GetGlyphIndices + +#include "include/core/SkData.h" +#include "include/private/SkTo.h" +#include "src/core/SkFontDescriptor.h" +#include "src/core/SkFontStream.h" +#include "src/core/SkScalerContext.h" +#include "src/core/SkUtils.h" +#include "src/ports/SkScalerContext_win_dw.h" +#include "src/ports/SkTypeface_win_dw.h" +#include "src/sfnt/SkOTTable_OS_2.h" +#include "src/sfnt/SkOTTable_fvar.h" +#include "src/sfnt/SkOTTable_head.h" +#include "src/sfnt/SkOTTable_hhea.h" +#include "src/sfnt/SkOTTable_post.h" +#include "src/sfnt/SkOTUtils.h" +#include "src/utils/win/SkDWrite.h" +#include "src/utils/win/SkDWriteFontFileStream.h" + +void DWriteFontTypeface::onGetFamilyName(SkString* familyName) const { + SkTScopedComPtr familyNames; + HRV(fDWriteFontFamily->GetFamilyNames(&familyNames)); + + sk_get_locale_string(familyNames.get(), nullptr/*fMgr->fLocaleName.get()*/, familyName); +} + +void DWriteFontTypeface::onGetFontDescriptor(SkFontDescriptor* desc, + bool* isLocalStream) const { + // Get the family name. + SkTScopedComPtr familyNames; + HRV(fDWriteFontFamily->GetFamilyNames(&familyNames)); + + SkString utf8FamilyName; + sk_get_locale_string(familyNames.get(), nullptr/*fMgr->fLocaleName.get()*/, &utf8FamilyName); + + desc->setFamilyName(utf8FamilyName.c_str()); + desc->setStyle(this->fontStyle()); + *isLocalStream = SkToBool(fDWriteFontFileLoader.get()); +} + +void DWriteFontTypeface::onCharsToGlyphs(const SkUnichar uni[], int count, + SkGlyphID glyphs[]) const { + fDWriteFontFace->GetGlyphIndices((const UINT32*)uni, count, glyphs); +} + +int DWriteFontTypeface::onCountGlyphs() const { + return fDWriteFontFace->GetGlyphCount(); +} + +void DWriteFontTypeface::getPostScriptGlyphNames(SkString*) const {} + +int DWriteFontTypeface::onGetUPEM() const { + DWRITE_FONT_METRICS metrics; + fDWriteFontFace->GetMetrics(&metrics); + return metrics.designUnitsPerEm; +} + +class LocalizedStrings_IDWriteLocalizedStrings : public SkTypeface::LocalizedStrings { +public: + /** Takes ownership of the IDWriteLocalizedStrings. */ + explicit LocalizedStrings_IDWriteLocalizedStrings(IDWriteLocalizedStrings* strings) + : fIndex(0), fStrings(strings) + { } + + bool next(SkTypeface::LocalizedString* localizedString) override { + if (fIndex >= fStrings->GetCount()) { + return false; + } + + // String + UINT32 stringLen; + HRBM(fStrings->GetStringLength(fIndex, &stringLen), "Could not get string length."); + + SkSMallocWCHAR wString(stringLen+1); + HRBM(fStrings->GetString(fIndex, wString.get(), stringLen+1), "Could not get string."); + + HRB(sk_wchar_to_skstring(wString.get(), stringLen, &localizedString->fString)); + + // Locale + UINT32 localeLen; + HRBM(fStrings->GetLocaleNameLength(fIndex, &localeLen), "Could not get locale length."); + + SkSMallocWCHAR wLocale(localeLen+1); + HRBM(fStrings->GetLocaleName(fIndex, wLocale.get(), localeLen+1), "Could not get locale."); + + HRB(sk_wchar_to_skstring(wLocale.get(), localeLen, &localizedString->fLanguage)); + + ++fIndex; + return true; + } + +private: + UINT32 fIndex; + SkTScopedComPtr fStrings; +}; + +SkTypeface::LocalizedStrings* DWriteFontTypeface::onCreateFamilyNameIterator() const { + sk_sp nameIter = + SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*this); + if (!nameIter) { + SkTScopedComPtr familyNames; + HRNM(fDWriteFontFamily->GetFamilyNames(&familyNames), "Could not obtain family names."); + nameIter = sk_make_sp(familyNames.release()); + } + return nameIter.release(); +} + +int DWriteFontTypeface::onGetVariationDesignPosition( + SkFontArguments::VariationPosition::Coordinate coordinates[], int coordinateCount) const +{ + +#if defined(NTDDI_WIN10_RS3) && NTDDI_VERSION >= NTDDI_WIN10_RS3 + + SkTScopedComPtr fontFace5; + if (FAILED(fDWriteFontFace->QueryInterface(&fontFace5))) { + return -1; + } + + // Return 0 if the font is not variable font. + if (!fontFace5->HasVariations()) { + return 0; + } + + UINT32 fontAxisCount = fontFace5->GetFontAxisValueCount(); + SkTScopedComPtr fontResource; + HR_GENERAL(fontFace5->GetFontResource(&fontResource), nullptr, -1); + UINT32 variableAxisCount = 0; + for (UINT32 i = 0; i < fontAxisCount; ++i) { + if (fontResource->GetFontAxisAttributes(i) & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { + ++variableAxisCount; + } + } + + if (!coordinates || coordinateCount < 0 || (unsigned)coordinateCount < variableAxisCount) { + return SkTo(variableAxisCount); + } + + SkAutoSTMalloc<8, DWRITE_FONT_AXIS_VALUE> fontAxisValue(fontAxisCount); + HR_GENERAL(fontFace5->GetFontAxisValues(fontAxisValue.get(), fontAxisCount), nullptr, -1); + UINT32 coordIndex = 0; + for (UINT32 axisIndex = 0; axisIndex < fontAxisCount; ++axisIndex) { + if (fontResource->GetFontAxisAttributes(axisIndex) & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { + coordinates[coordIndex].axis = SkEndian_SwapBE32(fontAxisValue[axisIndex].axisTag); + coordinates[coordIndex].value = fontAxisValue[axisIndex].value; + ++coordIndex; + } + } + + SkASSERT(coordIndex == variableAxisCount); + return SkTo(variableAxisCount); + +#endif + + return -1; +} + +int DWriteFontTypeface::onGetVariationDesignParameters( + SkFontParameters::Variation::Axis parameters[], int parameterCount) const +{ + +#if defined(NTDDI_WIN10_RS3) && NTDDI_VERSION >= NTDDI_WIN10_RS3 + + SkTScopedComPtr fontFace5; + if (FAILED(fDWriteFontFace->QueryInterface(&fontFace5))) { + return -1; + } + + // Return 0 if the font is not variable font. + if (!fontFace5->HasVariations()) { + return 0; + } + + UINT32 fontAxisCount = fontFace5->GetFontAxisValueCount(); + SkTScopedComPtr fontResource; + HR_GENERAL(fontFace5->GetFontResource(&fontResource), nullptr, -1); + int variableAxisCount = 0; + for (UINT32 i = 0; i < fontAxisCount; ++i) { + if (fontResource->GetFontAxisAttributes(i) & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { + variableAxisCount++; + } + } + + if (!parameters || parameterCount < variableAxisCount) { + return variableAxisCount; + } + + SkAutoSTMalloc<8, DWRITE_FONT_AXIS_RANGE> fontAxisRange(fontAxisCount); + HR_GENERAL(fontResource->GetFontAxisRanges(fontAxisRange.get(), fontAxisCount), nullptr, -1); + SkAutoSTMalloc<8, DWRITE_FONT_AXIS_VALUE> fontAxisDefaultValue(fontAxisCount); + HR_GENERAL(fontResource->GetDefaultFontAxisValues(fontAxisDefaultValue.get(), fontAxisCount), + nullptr, -1); + UINT32 coordIndex = 0; + + for (UINT32 axisIndex = 0; axisIndex < fontAxisCount; ++axisIndex) { + if (fontResource->GetFontAxisAttributes(axisIndex) & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { + parameters[coordIndex].tag = SkEndian_SwapBE32(fontAxisDefaultValue[axisIndex].axisTag); + parameters[coordIndex].min = fontAxisRange[axisIndex].minValue; + parameters[coordIndex].def = fontAxisDefaultValue[axisIndex].value; + parameters[coordIndex].max = fontAxisRange[axisIndex].maxValue; + parameters[coordIndex].setHidden(fontResource->GetFontAxisAttributes(axisIndex) & + DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN); + } + } + + return variableAxisCount; + +#endif + + return -1; +} + +int DWriteFontTypeface::onGetTableTags(SkFontTableTag tags[]) const { + DWRITE_FONT_FACE_TYPE type = fDWriteFontFace->GetType(); + if (type != DWRITE_FONT_FACE_TYPE_CFF && + type != DWRITE_FONT_FACE_TYPE_TRUETYPE && + type != DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION) + { + return 0; + } + + int ttcIndex; + std::unique_ptr stream = this->openStream(&ttcIndex); + return stream.get() ? SkFontStream::GetTableTags(stream.get(), ttcIndex, tags) : 0; +} + +size_t DWriteFontTypeface::onGetTableData(SkFontTableTag tag, size_t offset, + size_t length, void* data) const +{ + AutoDWriteTable table(fDWriteFontFace.get(), SkEndian_SwapBE32(tag)); + if (!table.fExists) { + return 0; + } + + if (offset > table.fSize) { + return 0; + } + size_t size = SkTMin(length, table.fSize - offset); + if (data) { + memcpy(data, table.fData + offset, size); + } + + return size; +} + +sk_sp DWriteFontTypeface::onCopyTableData(SkFontTableTag tag) const { + const uint8_t* data; + UINT32 size; + void* lock; + BOOL exists; + fDWriteFontFace->TryGetFontTable(SkEndian_SwapBE32(tag), + reinterpret_cast(&data), &size, &lock, &exists); + if (!exists) { + return nullptr; + } + struct Context { + Context(void* lock, IDWriteFontFace* face) : fLock(lock), fFontFace(SkRefComPtr(face)) {} + ~Context() { fFontFace->ReleaseFontTable(fLock); } + void* fLock; + SkTScopedComPtr fFontFace; + }; + return SkData::MakeWithProc(data, size, + [](const void*, void* ctx) { delete (Context*)ctx; }, + new Context(lock, fDWriteFontFace.get())); +} + +sk_sp DWriteFontTypeface::onMakeClone(const SkFontArguments& args) const { + // Skip if the current face index does not match the ttcIndex + if (fDWriteFontFace->GetIndex() != SkTo(args.getCollectionIndex())) { + return sk_ref_sp(this); + } + +#if defined(NTDDI_WIN10_RS3) && NTDDI_VERSION >= NTDDI_WIN10_RS3 + + SkTScopedComPtr fontFace5; + + if (SUCCEEDED(fDWriteFontFace->QueryInterface(&fontFace5)) && fontFace5->HasVariations()) { + UINT32 fontAxisCount = fontFace5->GetFontAxisValueCount(); + UINT32 argsCoordCount = args.getVariationDesignPosition().coordinateCount; + SkAutoSTMalloc<8, DWRITE_FONT_AXIS_VALUE> fontAxisValue(fontAxisCount); + HRN(fontFace5->GetFontAxisValues(fontAxisValue.get(), fontAxisCount)); + + for (UINT32 fontIndex = 0; fontIndex < fontAxisCount; ++fontIndex) { + for (UINT32 argsIndex = 0; argsIndex < argsCoordCount; ++argsIndex) { + if (SkEndian_SwapBE32(fontAxisValue[fontIndex].axisTag) == + args.getVariationDesignPosition().coordinates[argsIndex].axis) { + fontAxisValue[fontIndex].value = + args.getVariationDesignPosition().coordinates[argsIndex].value; + } + } + } + SkTScopedComPtr fontResource; + HRN(fontFace5->GetFontResource(&fontResource)); + SkTScopedComPtr newFontFace5; + HRN(fontResource->CreateFontFace(fDWriteFont->GetSimulations(), + fontAxisValue.get(), + fontAxisCount, + &newFontFace5)); + + SkTScopedComPtr newFontFace; + HRN(newFontFace5->QueryInterface(&newFontFace)); + return DWriteFontTypeface::Make(fFactory.get(), + newFontFace.get(), + fDWriteFont.get(), + fDWriteFontFamily.get(), + fDWriteFontFileLoader.get(), + fDWriteFontCollectionLoader.get()); + } + +#endif + + return sk_ref_sp(this); +} + +std::unique_ptr DWriteFontTypeface::onOpenStream(int* ttcIndex) const { + *ttcIndex = fDWriteFontFace->GetIndex(); + + UINT32 numFiles; + HRNM(fDWriteFontFace->GetFiles(&numFiles, nullptr), + "Could not get number of font files."); + if (numFiles != 1) { + return nullptr; + } + + SkTScopedComPtr fontFile; + HRNM(fDWriteFontFace->GetFiles(&numFiles, &fontFile), "Could not get font files."); + + const void* fontFileKey; + UINT32 fontFileKeySize; + HRNM(fontFile->GetReferenceKey(&fontFileKey, &fontFileKeySize), + "Could not get font file reference key."); + + SkTScopedComPtr fontFileLoader; + HRNM(fontFile->GetLoader(&fontFileLoader), "Could not get font file loader."); + + SkTScopedComPtr fontFileStream; + HRNM(fontFileLoader->CreateStreamFromKey(fontFileKey, fontFileKeySize, + &fontFileStream), + "Could not create font file stream."); + + return std::unique_ptr(new SkDWriteFontFileStream(fontFileStream.get())); +} + +SkScalerContext* DWriteFontTypeface::onCreateScalerContext(const SkScalerContextEffects& effects, + const SkDescriptor* desc) const { + return new SkScalerContext_DW(sk_ref_sp(const_cast(this)), effects, desc); +} + +void DWriteFontTypeface::onFilterRec(SkScalerContextRec* rec) const { + if (rec->fFlags & SkScalerContext::kLCD_Vertical_Flag) { + rec->fMaskFormat = SkMask::kA8_Format; + rec->fFlags |= SkScalerContext::kGenA8FromLCD_Flag; + } + + unsigned flagsWeDontSupport = SkScalerContext::kForceAutohinting_Flag | + SkScalerContext::kEmbolden_Flag | + SkScalerContext::kLCD_Vertical_Flag; + rec->fFlags &= ~flagsWeDontSupport; + + SkFontHinting h = rec->getHinting(); + // DirectWrite2 allows for hinting to be turned off. Force everything else to normal. + if (h != SkFontHinting::kNone || !fFactory2 || !fDWriteFontFace2) { + h = SkFontHinting::kNormal; + } + rec->setHinting(h); + +#if defined(SK_FONT_HOST_USE_SYSTEM_SETTINGS) + IDWriteFactory* factory = sk_get_dwrite_factory(); + if (factory != nullptr) { + SkTScopedComPtr defaultRenderingParams; + if (SUCCEEDED(factory->CreateRenderingParams(&defaultRenderingParams))) { + float gamma = defaultRenderingParams->GetGamma(); + rec->setDeviceGamma(gamma); + rec->setPaintGamma(gamma); + + rec->setContrast(defaultRenderingParams->GetEnhancedContrast()); + } + } +#elif defined(MOZ_SKIA) + rec->setContrast(fContrast); + + rec->setDeviceGamma(fGamma); + rec->setPaintGamma(fGamma); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +//PDF Support + +void DWriteFontTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const { + unsigned glyphCount = fDWriteFontFace->GetGlyphCount(); + sk_bzero(glyphToUnicode, sizeof(SkUnichar) * glyphCount); + IDWriteFontFace* fontFace = fDWriteFontFace.get(); + int maxGlyph = -1; + unsigned remainingGlyphCount = glyphCount; + for (UINT32 c = 0; c < 0x10FFFF && remainingGlyphCount != 0; ++c) { + UINT16 glyph = 0; + HRVM(fontFace->GetGlyphIndices(&c, 1, &glyph), "Failed to get glyph index."); + // Intermittent DW bug on Windows 10. See crbug.com/470146. + if (glyph >= glyphCount) { + return; + } + if (0 < glyph && glyphToUnicode[glyph] == 0) { + maxGlyph = SkTMax(static_cast(glyph), maxGlyph); + glyphToUnicode[glyph] = c; // Always use lowest-index unichar. + --remainingGlyphCount; + } + } +} + +std::unique_ptr DWriteFontTypeface::onGetAdvancedMetrics() const { + + std::unique_ptr info(nullptr); + + DWRITE_FONT_METRICS dwfm; + fDWriteFontFace->GetMetrics(&dwfm); + + info.reset(new SkAdvancedTypefaceMetrics); + + info->fAscent = SkToS16(dwfm.ascent); + info->fDescent = SkToS16(dwfm.descent); + info->fCapHeight = SkToS16(dwfm.capHeight); + + { + SkTScopedComPtr postScriptNames; + BOOL exists = FALSE; + if (FAILED(fDWriteFont->GetInformationalStrings( + DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME, + &postScriptNames, + &exists)) || + !exists || + FAILED(sk_get_locale_string(postScriptNames.get(), nullptr, &info->fPostScriptName))) + { + SkDEBUGF("Unable to get postscript name for typeface %p\n", this); + } + } + + // SkAdvancedTypefaceMetrics::fFontName must actually be a family name. + SkTScopedComPtr familyNames; + if (FAILED(fDWriteFontFamily->GetFamilyNames(&familyNames)) || + FAILED(sk_get_locale_string(familyNames.get(), nullptr, &info->fFontName))) + { + SkDEBUGF("Unable to get family name for typeface 0x%p\n", this); + } + if (info->fPostScriptName.isEmpty()) { + info->fPostScriptName = info->fFontName; + } + + DWRITE_FONT_FACE_TYPE fontType = fDWriteFontFace->GetType(); + if (fontType != DWRITE_FONT_FACE_TYPE_TRUETYPE && + fontType != DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION) + { + return info; + } + + // Simulated fonts aren't really TrueType fonts. + if (fDWriteFontFace->GetSimulations() == DWRITE_FONT_SIMULATIONS_NONE) { + info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font; + } + + AutoTDWriteTable headTable(fDWriteFontFace.get()); + AutoTDWriteTable postTable(fDWriteFontFace.get()); + AutoTDWriteTable hheaTable(fDWriteFontFace.get()); + AutoTDWriteTable os2Table(fDWriteFontFace.get()); + if (!headTable.fExists || !postTable.fExists || !hheaTable.fExists || !os2Table.fExists) { + return info; + } + + SkOTUtils::SetAdvancedTypefaceFlags(os2Table->version.v4.fsType, info.get()); + + // There are versions of DirectWrite which support named instances for system variation fonts, + // but no means to indicate that such a typeface is a variation. + AutoTDWriteTable fvarTable(fDWriteFontFace.get()); + if (fvarTable.fExists) { + info->fFlags |= SkAdvancedTypefaceMetrics::kMultiMaster_FontFlag; + } + + //There exist CJK fonts which set the IsFixedPitch and Monospace bits, + //but have full width, latin half-width, and half-width kana. + bool fixedWidth = (postTable->isFixedPitch && + (1 == SkEndian_SwapBE16(hheaTable->numberOfHMetrics))); + //Monospace + if (fixedWidth) { + info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style; + } + //Italic + if (os2Table->version.v0.fsSelection.field.Italic) { + info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style; + } + //Serif + using SerifStyle = SkPanose::Data::TextAndDisplay::SerifStyle; + SerifStyle serifStyle = os2Table->version.v0.panose.data.textAndDisplay.bSerifStyle; + if (SkPanose::FamilyType::TextAndDisplay == os2Table->version.v0.panose.bFamilyType) { + if (SerifStyle::Cove == serifStyle || + SerifStyle::ObtuseCove == serifStyle || + SerifStyle::SquareCove == serifStyle || + SerifStyle::ObtuseSquareCove == serifStyle || + SerifStyle::Square == serifStyle || + SerifStyle::Thin == serifStyle || + SerifStyle::Bone == serifStyle || + SerifStyle::Exaggerated == serifStyle || + SerifStyle::Triangle == serifStyle) + { + info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style; + } + //Script + } else if (SkPanose::FamilyType::Script == os2Table->version.v0.panose.bFamilyType) { + info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style; + } + + info->fItalicAngle = SkEndian_SwapBE32(postTable->italicAngle) >> 16; + + info->fBBox = SkIRect::MakeLTRB((int32_t)SkEndian_SwapBE16((uint16_t)headTable->xMin), + (int32_t)SkEndian_SwapBE16((uint16_t)headTable->yMax), + (int32_t)SkEndian_SwapBE16((uint16_t)headTable->xMax), + (int32_t)SkEndian_SwapBE16((uint16_t)headTable->yMin)); + return info; +} +#endif//defined(SK_BUILD_FOR_WIN) diff --git a/gfx/skia/skia/src/ports/SkTypeface_win_dw.h b/gfx/skia/skia/src/ports/SkTypeface_win_dw.h new file mode 100644 index 0000000000..392dec4330 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkTypeface_win_dw.h @@ -0,0 +1,171 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTypeface_win_dw_DEFINED +#define SkTypeface_win_dw_DEFINED + +#include "include/core/SkTypeface.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkLeanWindows.h" +#include "src/core/SkTypefaceCache.h" +#include "src/utils/win/SkDWrite.h" +#include "src/utils/win/SkHRESULT.h" +#include "src/utils/win/SkTScopedComPtr.h" + +#include +#include +#include +#include + +#if !defined(__MINGW32__) && WINVER < 0x0A00 +#include "mozilla/gfx/dw-extra.h" +#endif + +class SkFontDescriptor; +struct SkScalerContextRec; + +static SkFontStyle get_style(IDWriteFont* font) { + int weight = font->GetWeight(); + int width = font->GetStretch(); + SkFontStyle::Slant slant = SkFontStyle::kUpright_Slant; + switch (font->GetStyle()) { + case DWRITE_FONT_STYLE_NORMAL: slant = SkFontStyle::kUpright_Slant; break; + case DWRITE_FONT_STYLE_OBLIQUE: slant = SkFontStyle::kOblique_Slant; break; + case DWRITE_FONT_STYLE_ITALIC: slant = SkFontStyle::kItalic_Slant; break; + default: SkASSERT(false); break; + } + return SkFontStyle(weight, width, slant); +} + +class DWriteFontTypeface : public SkTypeface { +private: + DWriteFontTypeface(const SkFontStyle& style, + IDWriteFactory* factory, + IDWriteFontFace* fontFace, + IDWriteFont* font = nullptr, + IDWriteFontFamily* fontFamily = nullptr, + IDWriteFontFileLoader* fontFileLoader = nullptr, + IDWriteFontCollectionLoader* fontCollectionLoader = nullptr) + : SkTypeface(style, false) + , fFactory(SkRefComPtr(factory)) + , fDWriteFontCollectionLoader(SkSafeRefComPtr(fontCollectionLoader)) + , fDWriteFontFileLoader(SkSafeRefComPtr(fontFileLoader)) + , fDWriteFontFamily(SkSafeRefComPtr(fontFamily)) + , fDWriteFont(SkSafeRefComPtr(font)) + , fDWriteFontFace(SkRefComPtr(fontFace)) + , fRenderingMode(DWRITE_RENDERING_MODE_DEFAULT) + , fGamma(2.2f) + , fContrast(1.0f) + , fClearTypeLevel(1.0f) + { + if (!SUCCEEDED(fDWriteFontFace->QueryInterface(&fDWriteFontFace1))) { + // IUnknown::QueryInterface states that if it fails, punk will be set to nullptr. + // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/26/96777.aspx + SkASSERT_RELEASE(nullptr == fDWriteFontFace1.get()); + } + if (!SUCCEEDED(fDWriteFontFace->QueryInterface(&fDWriteFontFace2))) { + SkASSERT_RELEASE(nullptr == fDWriteFontFace2.get()); + } + if (!SUCCEEDED(fDWriteFontFace->QueryInterface(&fDWriteFontFace4))) { + SkASSERT_RELEASE(nullptr == fDWriteFontFace4.get()); + } + if (!SUCCEEDED(fFactory->QueryInterface(&fFactory2))) { + SkASSERT_RELEASE(nullptr == fFactory2.get()); + } + } + +public: + SkTScopedComPtr fFactory; + SkTScopedComPtr fFactory2; + SkTScopedComPtr fDWriteFontCollectionLoader; + SkTScopedComPtr fDWriteFontFileLoader; + SkTScopedComPtr fDWriteFontFamily; + SkTScopedComPtr fDWriteFont; + SkTScopedComPtr fDWriteFontFace; + SkTScopedComPtr fDWriteFontFace1; + SkTScopedComPtr fDWriteFontFace2; + SkTScopedComPtr fDWriteFontFace4; + + static sk_sp Make( + IDWriteFactory* factory, + IDWriteFontFace* fontFace, + IDWriteFont* font, + IDWriteFontFamily* fontFamily, + IDWriteFontFileLoader* fontFileLoader = nullptr, + IDWriteFontCollectionLoader* fontCollectionLoader = nullptr) + { + return sk_sp( + new DWriteFontTypeface(get_style(font), factory, fontFace, font, fontFamily, + fontFileLoader, fontCollectionLoader)); + } + + static DWriteFontTypeface* Create(IDWriteFactory* factory, + IDWriteFontFace* fontFace, + SkFontStyle aStyle, + DWRITE_RENDERING_MODE aRenderingMode, + float aGamma, + float aContrast, + float aClearTypeLevel) { + DWriteFontTypeface* typeface = + new DWriteFontTypeface(aStyle, factory, fontFace, + nullptr, nullptr, + nullptr, nullptr); + typeface->fRenderingMode = aRenderingMode; + typeface->fGamma = aGamma; + typeface->fContrast = aContrast; + typeface->fClearTypeLevel = aClearTypeLevel; + return typeface; + } + + bool ForceGDI() const { return fRenderingMode == DWRITE_RENDERING_MODE_GDI_CLASSIC; } + DWRITE_RENDERING_MODE GetRenderingMode() const { return fRenderingMode; } + float GetClearTypeLevel() const { return fClearTypeLevel; } + +protected: + void weak_dispose() const override { + if (fDWriteFontCollectionLoader.get()) { + HRV(fFactory->UnregisterFontCollectionLoader(fDWriteFontCollectionLoader.get())); + } + if (fDWriteFontFileLoader.get()) { + HRV(fFactory->UnregisterFontFileLoader(fDWriteFontFileLoader.get())); + } + + //SkTypefaceCache::Remove(this); + INHERITED::weak_dispose(); + } + + sk_sp onMakeClone(const SkFontArguments&) const override; + std::unique_ptr onOpenStream(int* ttcIndex) const override; + SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&, + const SkDescriptor*) const override; + void onFilterRec(SkScalerContextRec*) const override; + void getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const override; + std::unique_ptr onGetAdvancedMetrics() const override; + void onGetFontDescriptor(SkFontDescriptor*, bool*) const override; + void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override; + int onCountGlyphs() const override; + void getPostScriptGlyphNames(SkString*) const override; + int onGetUPEM() const override; + void onGetFamilyName(SkString* familyName) const override; + SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override; + int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[], + int coordinateCount) const override; + int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], + int parameterCount) const override; + int onGetTableTags(SkFontTableTag tags[]) const override; + size_t onGetTableData(SkFontTableTag, size_t offset, size_t length, void* data) const override; + sk_sp onCopyTableData(SkFontTableTag) const override; + +private: + typedef SkTypeface INHERITED; + DWRITE_RENDERING_MODE fRenderingMode; + float fGamma; + float fContrast; + float fClearTypeLevel; +}; + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkIBMFamilyClass.h b/gfx/skia/skia/src/sfnt/SkIBMFamilyClass.h new file mode 100644 index 0000000000..8ad1dbeaf4 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkIBMFamilyClass.h @@ -0,0 +1,142 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkIBMFamilyClass_DEFINED +#define SkIBMFamilyClass_DEFINED + +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkIBMFamilyClass { + enum class Class : SK_OT_BYTE { + NoClassification = 0, + OldstyleSerifs = 1, + TransitionalSerifs = 2, + ModernSerifs = 3, + ClarendonSerifs = 4, + SlabSerifs = 5, + //6 reserved for future use + FreeformSerifs = 7, + SansSerif = 8, + Ornamentals = 9, + Scripts = 10, + //11 reserved for future use + Symbolic = 12, + //13-15 reserved for future use + } familyClass; + union SubClass { + enum class OldstyleSerifs : SK_OT_BYTE { + NoClassification = 0, + IBMRoundedLegibility = 1, + Garalde = 2, + Venetian = 3, + ModifiedVenetian = 4, + DutchModern = 5, + DutchTraditional = 6, + Contemporary = 7, + Calligraphic = 8, + //9-14 reserved for future use + Miscellaneous = 15, + } oldstyleSerifs; + enum class TransitionalSerifs : SK_OT_BYTE { + NoClassification = 0, + DirectLine = 1, + Script = 2, + //3-14 reserved for future use + Miscellaneous = 15, + } transitionalSerifs; + enum class ModernSerifs : SK_OT_BYTE { + NoClassification = 0, + Italian = 1, + Script = 2, + //3-14 reserved for future use + Miscellaneous = 15, + } modernSerifs; + enum class ClarendonSerifs : SK_OT_BYTE { + NoClassification = 0, + Clarendon = 1, + Modern = 2, + Traditional = 3, + Newspaper = 4, + StubSerif = 5, + Monotone = 6, + Typewriter = 7, + //8-14 reserved for future use + Miscellaneous = 15, + } clarendonSerifs; + enum class SlabSerifs : SK_OT_BYTE { + NoClassification = 0, + Monotone = 1, + Humanist = 2, + Geometric = 3, + Swiss = 4, + Typewriter = 5, + //6-14 reserved for future use + Miscellaneous = 15, + } slabSerifs; + enum class FreeformSerifs : SK_OT_BYTE { + NoClassification = 0, + Modern = 1, + //2-14 reserved for future use + Miscellaneous = 15, + } freeformSerifs; + enum class SansSerif : SK_OT_BYTE { + NoClassification = 0, + IBMNeoGrotesqueGothic = 1, + Humanist = 2, + LowXRoundGeometric = 3, + HighXRoundGeometric = 4, + NeoGrotesqueGothic = 5, + ModifiedNeoGrotesqueGothic = 6, + //7-8 reserved for future use + TypewriterGothic = 9, + Matrix = 10, + //11-14 reserved for future use + Miscellaneous = 15, + } sansSerif; + enum class Ornamentals : SK_OT_BYTE { + NoClassification = 0, + Engraver = 1, + BlackLetter = 2, + Decorative = 3, + ThreeDimensional = 4, + //5-14 reserved for future use + Miscellaneous = 15, + } ornamentals; + enum class Scripts : SK_OT_BYTE { + NoClassification = 0, + Uncial = 1, + Brush_Joined = 2, + Formal_Joined = 3, + Monotone_Joined = 4, + Calligraphic = 5, + Brush_Unjoined = 6, + Formal_Unjoined = 7, + Monotone_Unjoined = 8, + //9-14 reserved for future use + Miscellaneous = 15, + } scripts; + enum class Symbolic : SK_OT_BYTE { + NoClassification = 0, + //1-2 reserved for future use + MixedSerif = 3, + //4-5 reserved for future use + OldstyleSerif = 6, + NeoGrotesqueSansSerif = 7, + //8-14 reserved for future use + Miscellaneous = 15, + } symbolic; + } familySubClass; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkIBMFamilyClass) == 2, "sizeof_SkIBMFamilyClass_not_2"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTableTypes.h b/gfx/skia/skia/src/sfnt/SkOTTableTypes.h new file mode 100644 index 0000000000..c5f352e24b --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTableTypes.h @@ -0,0 +1,62 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTableTypes_DEFINED +#define SkOTTableTypes_DEFINED + +#include "include/core/SkTypes.h" +#include "src/core/SkEndian.h" + +//All SK_OT_ prefixed types should be considered as big endian. +typedef uint8_t SK_OT_BYTE; +#if CHAR_BIT == 8 +typedef signed char SK_OT_CHAR; //easier to debug +#else +typedef int8_t SK_OT_CHAR; +#endif +typedef uint16_t SK_OT_SHORT; +typedef uint16_t SK_OT_USHORT; +typedef uint32_t SK_OT_ULONG; +typedef uint32_t SK_OT_LONG; +//16.16 Signed fixed point representation. +typedef int32_t SK_OT_Fixed; +//2.14 Signed fixed point representation. +typedef uint16_t SK_OT_F2DOT14; +//F units are the units of measurement in em space. +typedef uint16_t SK_OT_FWORD; +typedef uint16_t SK_OT_UFWORD; +//Number of seconds since 12:00 midnight, January 1, 1904. +typedef uint64_t SK_OT_LONGDATETIME; + +#define SK_OT_BYTE_BITFIELD SK_UINT8_BITFIELD + +template class SkOTTableTAG { +public: + /** + * SkOTTableTAG::value is the big endian value of an OpenType table tag. + * It may be directly compared with raw big endian table data. + */ + static const SK_OT_ULONG value = SkTEndian_SwapBE32( + SkSetFourByteTag(T::TAG0, T::TAG1, T::TAG2, T::TAG3) + ); +}; + +/** SkOTSetUSHORTBit::value is an SK_OT_USHORT with the Nth BE bit set. */ +template struct SkOTSetUSHORTBit { + static_assert(N < 16, "NTooBig"); + static const uint16_t bit = 1u << N; + static const SK_OT_USHORT value = SkTEndian_SwapBE16(bit); +}; + +/** SkOTSetULONGBit::value is an SK_OT_ULONG with the Nth BE bit set. */ +template struct SkOTSetULONGBit { + static_assert(N < 32, "NTooBig"); + static const uint32_t bit = 1u << N; + static const SK_OT_ULONG value = SkTEndian_SwapBE32(bit); +}; + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_EBDT.h b/gfx/skia/skia/src/sfnt/SkOTTable_EBDT.h new file mode 100644 index 0000000000..c8d887a31b --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_EBDT.h @@ -0,0 +1,108 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_EBDT_DEFINED +#define SkOTTable_EBDT_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_head.h" +#include "src/sfnt/SkOTTable_loca.h" + +#pragma pack(push, 1) + +struct SkOTTableEmbeddedBitmapData { + static const SK_OT_CHAR TAG0 = 'E'; + static const SK_OT_CHAR TAG1 = 'B'; + static const SK_OT_CHAR TAG2 = 'D'; + static const SK_OT_CHAR TAG3 = 'T'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_Fixed version; + static const SK_OT_Fixed version_initial = SkTEndian_SwapBE32(0x00020000); + + struct BigGlyphMetrics { + SK_OT_BYTE height; + SK_OT_BYTE width; + SK_OT_CHAR horiBearingX; + SK_OT_CHAR horiBearingY; + SK_OT_BYTE horiAdvance; + SK_OT_CHAR vertBearingX; + SK_OT_CHAR vertBearingY; + SK_OT_BYTE vertAdvance; + }; + + struct SmallGlyphMetrics { + SK_OT_BYTE height; + SK_OT_BYTE width; + SK_OT_CHAR bearingX; + SK_OT_CHAR bearingY; + SK_OT_BYTE advance; + }; + + // Small metrics, byte-aligned data. + struct Format1 { + SmallGlyphMetrics smallGlyphMetrics; + //SK_OT_BYTE[] byteAlignedBitmap; + }; + + // Small metrics, bit-aligned data. + struct Format2 { + SmallGlyphMetrics smallGlyphMetrics; + //SK_OT_BYTE[] bitAlignedBitmap; + }; + + // Format 3 is not used. + + // EBLC metrics (IndexSubTable::header::indexFormat 2 or 5), compressed data. + // Only used on Mac. + struct Format4 { + SK_OT_ULONG whiteTreeOffset; + SK_OT_ULONG blackTreeOffset; + SK_OT_ULONG glyphDataOffset; + }; + + // EBLC metrics (IndexSubTable::header::indexFormat 2 or 5), bit-aligned data. + struct Format5 { + //SK_OT_BYTE[] bitAlignedBitmap; + }; + + // Big metrics, byte-aligned data. + struct Format6 { + BigGlyphMetrics bigGlyphMetrics; + //SK_OT_BYTE[] byteAlignedBitmap; + }; + + // Big metrics, bit-aligned data. + struct Format7 { + BigGlyphMetrics bigGlyphMetrics; + //SK_OT_BYTE[] bitAlignedBitmap; + }; + + struct EBDTComponent { + SK_OT_USHORT glyphCode; // Component glyph code + SK_OT_CHAR xOffset; // Position of component left + SK_OT_CHAR yOffset; // Position of component top + }; + + struct Format8 { + SmallGlyphMetrics smallMetrics; // Metrics information for the glyph + SK_OT_BYTE pad; // Pad to short boundary + SK_OT_USHORT numComponents; // Number of components + //EBDTComponent componentArray[numComponents]; // Glyph code, offset array + }; + + struct Format9 { + BigGlyphMetrics bigMetrics; // Metrics information for the glyph + SK_OT_USHORT numComponents; // Number of components + //EBDTComponent componentArray[numComponents]; // Glyph code, offset array + }; +}; + +#pragma pack(pop) + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_EBLC.h b/gfx/skia/skia/src/sfnt/SkOTTable_EBLC.h new file mode 100644 index 0000000000..a97e45fa29 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_EBLC.h @@ -0,0 +1,150 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_EBLC_DEFINED +#define SkOTTable_EBLC_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_EBDT.h" + +#pragma pack(push, 1) + +struct SkOTTableEmbeddedBitmapLocation { + static const SK_OT_CHAR TAG0 = 'E'; + static const SK_OT_CHAR TAG1 = 'B'; + static const SK_OT_CHAR TAG2 = 'L'; + static const SK_OT_CHAR TAG3 = 'C'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_Fixed version; + static const SK_OT_Fixed version_initial = SkTEndian_SwapBE32(0x00020000); + + SK_OT_ULONG numSizes; + + struct SbitLineMetrics { + SK_OT_CHAR ascender; + SK_OT_CHAR descender; + SK_OT_BYTE widthMax; + SK_OT_CHAR caretSlopeNumerator; + SK_OT_CHAR caretSlopeDenominator; + SK_OT_CHAR caretOffset; + SK_OT_CHAR minOriginSB; + SK_OT_CHAR minAdvanceSB; + SK_OT_CHAR maxBeforeBL; + SK_OT_CHAR minAfterBL; + SK_OT_CHAR pad1; + SK_OT_CHAR pad2; + }; + + struct BitmapSizeTable { + SK_OT_ULONG indexSubTableArrayOffset; //offset to indexSubtableArray from beginning of EBLC. + SK_OT_ULONG indexTablesSize; //number of bytes in corresponding index subtables and array + SK_OT_ULONG numberOfIndexSubTables; //an index subtable for each range or format change + SK_OT_ULONG colorRef; //not used; set to 0. + SbitLineMetrics hori; //line metrics for text rendered horizontally + SbitLineMetrics vert; //line metrics for text rendered vertically + SK_OT_USHORT startGlyphIndex; //lowest glyph index for this size + SK_OT_USHORT endGlyphIndex; //highest glyph index for this size + SK_OT_BYTE ppemX; //horizontal pixels per Em + SK_OT_BYTE ppemY; //vertical pixels per Em + struct BitDepth { + enum Value : SK_OT_BYTE { + BW = 1, + Gray4 = 2, + Gray16 = 4, + Gray256 = 8, + }; + SK_OT_BYTE value; + } bitDepth; //the Microsoft rasterizer v.1.7 or greater supports + union Flags { + struct Field { + //0-7 + SK_OT_BYTE_BITFIELD( + Horizontal, // Horizontal small glyph metrics + Vertical, // Vertical small glyph metrics + Reserved02, + Reserved03, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_CHAR Horizontal = 1u << 0; + static const SK_OT_CHAR Vertical = 1u << 1; + SK_OT_CHAR value; + } raw; + } flags; + }; //bitmapSizeTable[numSizes]; + + struct IndexSubTableArray { + SK_OT_USHORT firstGlyphIndex; //first glyph code of this range + SK_OT_USHORT lastGlyphIndex; //last glyph code of this range (inclusive) + SK_OT_ULONG additionalOffsetToIndexSubtable; //add to BitmapSizeTable::indexSubTableArrayOffset to get offset from beginning of 'EBLC' + }; //indexSubTableArray[BitmapSizeTable::numberOfIndexSubTables]; + + struct IndexSubHeader { + SK_OT_USHORT indexFormat; //format of this indexSubTable + SK_OT_USHORT imageFormat; //format of 'EBDT' image data + SK_OT_ULONG imageDataOffset; //offset to image data in 'EBDT' table + }; + + // Variable metrics glyphs with 4 byte offsets + struct IndexSubTable1 { + IndexSubHeader header; + //SK_OT_ULONG offsetArray[lastGlyphIndex - firstGlyphIndex + 1 + 1]; //last element points to one past end of last glyph + //glyphData = offsetArray[glyphIndex - firstGlyphIndex] + imageDataOffset + }; + + // All Glyphs have identical metrics + struct IndexSubTable2 { + IndexSubHeader header; + SK_OT_ULONG imageSize; // all glyphs are of the same size + SkOTTableEmbeddedBitmapData::BigGlyphMetrics bigMetrics; // all glyphs have the same metrics; glyph data may be compressed, byte-aligned, or bit-aligned + }; + + // Variable metrics glyphs with 2 byte offsets + struct IndexSubTable3 { + IndexSubHeader header; + //SK_OT_USHORT offsetArray[lastGlyphIndex - firstGlyphIndex + 1 + 1]; //last element points to one past end of last glyph, may have extra element to force even number of elements + //glyphData = offsetArray[glyphIndex - firstGlyphIndex] + imageDataOffset + }; + + // Variable metrics glyphs with sparse glyph codes + struct IndexSubTable4 { + IndexSubHeader header; + SK_OT_ULONG numGlyphs; + struct CodeOffsetPair { + SK_OT_USHORT glyphCode; + SK_OT_USHORT offset; //location in EBDT + }; //glyphArray[numGlyphs+1] + }; + + // Constant metrics glyphs with sparse glyph codes + struct IndexSubTable5 { + IndexSubHeader header; + SK_OT_ULONG imageSize; //all glyphs have the same data size + SkOTTableEmbeddedBitmapData::BigGlyphMetrics bigMetrics; //all glyphs have the same metrics + SK_OT_ULONG numGlyphs; + //SK_OT_USHORT glyphCodeArray[numGlyphs] //must have even number of entries (set pad to 0) + }; + + union IndexSubTable { + IndexSubHeader header; + IndexSubTable1 format1; + IndexSubTable2 format2; + IndexSubTable3 format3; + IndexSubTable4 format4; + IndexSubTable5 format5; + }; + +}; + +#pragma pack(pop) + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_EBSC.h b/gfx/skia/skia/src/sfnt/SkOTTable_EBSC.h new file mode 100644 index 0000000000..1e60ec0ed2 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_EBSC.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_EBSC_DEFINED +#define SkOTTable_EBSC_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_EBLC.h" + +#pragma pack(push, 1) + +struct SkOTTableEmbeddedBitmapScaling { + static const SK_OT_CHAR TAG0 = 'E'; + static const SK_OT_CHAR TAG1 = 'S'; + static const SK_OT_CHAR TAG2 = 'B'; + static const SK_OT_CHAR TAG3 = 'C'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_Fixed version; + static const SK_OT_Fixed version_initial = SkTEndian_SwapBE32(0x00020000); + + SK_OT_ULONG numSizes; + + struct BitmapScaleTable { + SkOTTableEmbeddedBitmapLocation::SbitLineMetrics hori; + SkOTTableEmbeddedBitmapLocation::SbitLineMetrics vert; + SK_OT_BYTE ppemX; //target horizontal pixels per EM + SK_OT_BYTE ppemY; //target vertical pixels per EM + SK_OT_BYTE substitutePpemX; //use bitmaps of this size + SK_OT_BYTE substitutePpemY; //use bitmaps of this size + }; //bitmapScaleTable[numSizes]; +}; + +#pragma pack(pop) + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2.h new file mode 100644 index 0000000000..92619c48dd --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2.h @@ -0,0 +1,52 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_DEFINED +#define SkOTTable_OS_2_DEFINED + +#include "src/sfnt/SkOTTable_OS_2_V0.h" +#include "src/sfnt/SkOTTable_OS_2_V1.h" +#include "src/sfnt/SkOTTable_OS_2_V2.h" +#include "src/sfnt/SkOTTable_OS_2_V3.h" +#include "src/sfnt/SkOTTable_OS_2_V4.h" +#include "src/sfnt/SkOTTable_OS_2_VA.h" + +#pragma pack(push, 1) + +struct SkOTTableOS2 { + static constexpr SK_OT_CHAR TAG0 = 'O'; + static constexpr SK_OT_CHAR TAG1 = 'S'; + static constexpr SK_OT_CHAR TAG2 = '/'; + static constexpr SK_OT_CHAR TAG3 = '2'; + static constexpr SK_OT_ULONG TAG = SkOTTableTAG::value; + + union Version { + SK_OT_USHORT version; + + //original V0 TT + struct VA : SkOTTableOS2_VA { } vA; + struct V0 : SkOTTableOS2_V0 { } v0; + struct V1 : SkOTTableOS2_V1 { } v1; + struct V2 : SkOTTableOS2_V2 { } v2; + //makes fsType 0-3 exclusive + struct V3 : SkOTTableOS2_V3 { } v3; + //defines fsSelection bits 7-9 + struct V4 : SkOTTableOS2_V4 { } v4; + } version; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2::Version::VA) == 68, "sizeof_SkOTTableOS2__VA_not_68"); +static_assert(sizeof(SkOTTableOS2::Version::V0) == 78, "sizeof_SkOTTableOS2__V0_not_78"); +static_assert(sizeof(SkOTTableOS2::Version::V1) == 86, "sizeof_SkOTTableOS2__V1_not_86"); +static_assert(sizeof(SkOTTableOS2::Version::V2) == 96, "sizeof_SkOTTableOS2__V2_not_96"); +static_assert(sizeof(SkOTTableOS2::Version::V3) == 96, "sizeof_SkOTTableOS2__V3_not_96"); +static_assert(sizeof(SkOTTableOS2::Version::V4) == 96, "sizeof_SkOTTableOS2__V4_not_96"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V0.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V0.h new file mode 100644 index 0000000000..286b7e2720 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V0.h @@ -0,0 +1,146 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_V0_DEFINED +#define SkOTTable_OS_2_V0_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkIBMFamilyClass.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkPanose.h" + +#pragma pack(push, 1) + +struct SkOTTableOS2_V0 { + SK_OT_USHORT version; + //SkOTTableOS2_VA::VERSION and SkOTTableOS2_V0::VERSION are both 0. + //The only way to differentiate these two versions is by the size of the table. + static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(0); + + SK_OT_SHORT xAvgCharWidth; + struct WeightClass { + enum Value : SK_OT_USHORT { + Thin = SkTEndian_SwapBE16(100), + ExtraLight = SkTEndian_SwapBE16(200), + Light = SkTEndian_SwapBE16(300), + Normal = SkTEndian_SwapBE16(400), + Medium = SkTEndian_SwapBE16(500), + SemiBold = SkTEndian_SwapBE16(600), + Bold = SkTEndian_SwapBE16(700), + ExtraBold = SkTEndian_SwapBE16(800), + Black = SkTEndian_SwapBE16(900), + }; + SK_OT_USHORT value; + } usWeightClass; + struct WidthClass { + enum Value : SK_OT_USHORT { + UltraCondensed = SkTEndian_SwapBE16(1), + ExtraCondensed = SkTEndian_SwapBE16(2), + Condensed = SkTEndian_SwapBE16(3), + SemiCondensed = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiExpanded = SkTEndian_SwapBE16(6), + Expanded = SkTEndian_SwapBE16(7), + ExtraExpanded = SkTEndian_SwapBE16(8), + UltraExpanded = SkTEndian_SwapBE16(9), + } value; + } usWidthClass; + union Type { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Reserved00, + Restricted, + PreviewPrint, + Editable, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT Installable = 0; + static const SK_OT_USHORT RestrictedMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT PreviewPrintMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT EditableMask = SkOTSetUSHORTBit<3>::value; + SK_OT_USHORT value; + } raw; + } fsType; + SK_OT_SHORT ySubscriptXSize; + SK_OT_SHORT ySubscriptYSize; + SK_OT_SHORT ySubscriptXOffset; + SK_OT_SHORT ySubscriptYOffset; + SK_OT_SHORT ySuperscriptXSize; + SK_OT_SHORT ySuperscriptYSize; + SK_OT_SHORT ySuperscriptXOffset; + SK_OT_SHORT ySuperscriptYOffset; + SK_OT_SHORT yStrikeoutSize; + SK_OT_SHORT yStrikeoutPosition; + SkIBMFamilyClass sFamilyClass; + SkPanose panose; + SK_OT_ULONG ulCharRange[4]; + SK_OT_CHAR achVendID[4]; + union Selection { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Italic, + Underscore, + Negative, + Outlined, + Strikeout, + Bold, + Regular, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT ItalicMask = SkOTSetUSHORTBit<0>::value; + static const SK_OT_USHORT UnderscoreMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT NegativeMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT OutlinedMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT StrikeoutMask = SkOTSetUSHORTBit<4>::value; + static const SK_OT_USHORT BoldMask = SkOTSetUSHORTBit<5>::value; + static const SK_OT_USHORT RegularMask = SkOTSetUSHORTBit<6>::value; + SK_OT_USHORT value; + } raw; + } fsSelection; + SK_OT_USHORT usFirstCharIndex; + SK_OT_USHORT usLastCharIndex; + //version0 + SK_OT_SHORT sTypoAscender; + SK_OT_SHORT sTypoDescender; + SK_OT_SHORT sTypoLineGap; + SK_OT_USHORT usWinAscent; + SK_OT_USHORT usWinDescent; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2_V0) == 78, "sizeof_SkOTTableOS2_V0_not_78"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V1.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V1.h new file mode 100644 index 0000000000..0f58eb8f1e --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V1.h @@ -0,0 +1,515 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_V1_DEFINED +#define SkOTTable_OS_2_V1_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkIBMFamilyClass.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkPanose.h" + +#pragma pack(push, 1) + +struct SkOTTableOS2_V1 { + SK_OT_USHORT version; + static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(1); + + SK_OT_SHORT xAvgCharWidth; + struct WeightClass { + enum Value : SK_OT_USHORT { + Thin = SkTEndian_SwapBE16(100), + ExtraLight = SkTEndian_SwapBE16(200), + Light = SkTEndian_SwapBE16(300), + Normal = SkTEndian_SwapBE16(400), + Medium = SkTEndian_SwapBE16(500), + SemiBold = SkTEndian_SwapBE16(600), + Bold = SkTEndian_SwapBE16(700), + ExtraBold = SkTEndian_SwapBE16(800), + Black = SkTEndian_SwapBE16(900), + }; + SK_OT_USHORT value; + } usWeightClass; + struct WidthClass { + enum Value : SK_OT_USHORT { + UltraCondensed = SkTEndian_SwapBE16(1), + ExtraCondensed = SkTEndian_SwapBE16(2), + Condensed = SkTEndian_SwapBE16(3), + SemiCondensed = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiExpanded = SkTEndian_SwapBE16(6), + Expanded = SkTEndian_SwapBE16(7), + ExtraExpanded = SkTEndian_SwapBE16(8), + UltraExpanded = SkTEndian_SwapBE16(9), + } value; + } usWidthClass; + union Type { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Reserved00, + Restricted, + PreviewPrint, + Editable, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT Installable = 0; + static const SK_OT_USHORT RestrictedMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT PreviewPrintMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT EditableMask = SkOTSetUSHORTBit<3>::value; + SK_OT_USHORT value; + } raw; + } fsType; + SK_OT_SHORT ySubscriptXSize; + SK_OT_SHORT ySubscriptYSize; + SK_OT_SHORT ySubscriptXOffset; + SK_OT_SHORT ySubscriptYOffset; + SK_OT_SHORT ySuperscriptXSize; + SK_OT_SHORT ySuperscriptYSize; + SK_OT_SHORT ySuperscriptXOffset; + SK_OT_SHORT ySuperscriptYOffset; + SK_OT_SHORT yStrikeoutSize; + SK_OT_SHORT yStrikeoutPosition; + SkIBMFamilyClass sFamilyClass; + SkPanose panose; + union UnicodeRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Thai, + Lao, + BasicGeorgian, + GeorgianExtended, + HangulJamo, + LatinExtendedAdditional, + GreekExtended, + GeneralPunctuation) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Bengali, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + GreekSymbolsAndCoptic, + Cyrillic, + Armenian, + BasicHebrew, + HebrewExtendedAB, + BasicArabic, + ArabicExtended, + Devanagari) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + BasicLatin, + Latin1Supplement, + LatinExtendedA, + LatinExtendedB, + IPAExtensions, + SpacingModifierLetters, + CombiningDiacriticalMarks, + BasicGreek) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + Hangul, + Reserved057, + Reserved058, + CJKUnifiedIdeographs, + PrivateUseArea, + CJKCompatibilityIdeographs, + AlphabeticPresentationForms, + ArabicPresentationFormsA) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + CJKSymbolsAndPunctuation, + Hiragana, + Katakana, + Bopomofo, + HangulCompatibilityJamo, + CJKMiscellaneous, + EnclosedCJKLettersAndMonths, + CJKCompatibility) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + ControlPictures, + OpticalCharacterRecognition, + EnclosedAlphanumerics, + BoxDrawing, + BlockElements, + GeometricShapes, + MiscellaneousSymbols, + Dingbats) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + SuperscriptsAndSubscripts, + CurrencySymbols, + CombiningDiacriticalMarksForSymbols, + LetterlikeSymbols, + NumberForms, + Arrows, + MathematicalOperators, + MiscellaneousTechnical) + + //l2 24-31 + SK_OT_BYTE_BITFIELD( + Reserved088, + Reserved089, + Reserved090, + Reserved091, + Reserved092, + Reserved093, + Reserved094, + Reserved095) + //l2 16-23 + SK_OT_BYTE_BITFIELD( + Reserved080, + Reserved081, + Reserved082, + Reserved083, + Reserved084, + Reserved085, + Reserved086, + Reserved087) + //l2 8-15 + SK_OT_BYTE_BITFIELD( + Reserved072, + Reserved073, + Reserved074, + Reserved075, + Reserved076, + Reserved077, + Reserved078, + Reserved079) + //l2 0-7 + SK_OT_BYTE_BITFIELD( + CombiningHalfMarks, + CJKCompatibilityForms, + SmallFormVariants, + ArabicPresentationFormsB, + HalfwidthAndFullwidthForms, + Specials, + Reserved70, + Reserved71) + + //l3 24-31 + SK_OT_BYTE_BITFIELD( + Reserved120, + Reserved121, + Reserved122, + Reserved123, + Reserved124, + Reserved125, + Reserved126, + Reserved127) + //l3 16-23 + SK_OT_BYTE_BITFIELD( + Reserved112, + Reserved113, + Reserved114, + Reserved115, + Reserved116, + Reserved117, + Reserved118, + Reserved119) + //l3 8-15 + SK_OT_BYTE_BITFIELD( + Reserved104, + Reserved105, + Reserved106, + Reserved107, + Reserved108, + Reserved109, + Reserved110, + Reserved111) + //l3 0-7 + SK_OT_BYTE_BITFIELD( + Reserved096, + Reserved097, + Reserved098, + Reserved099, + Reserved100, + Reserved101, + Reserved102, + Reserved103) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG BasicLatinMask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin1SupplementMask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG LatinExtendedAMask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG LatinExtendedBMask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG IPAExtensionsMask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG SpacingModifierLettersMask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG BasicGreekMask = SkOTSetULONGBit<7>::value; + static const SK_OT_ULONG GreekSymbolsAndCCopticMask = SkOTSetULONGBit<8>::value; + static const SK_OT_ULONG CyrillicMask = SkOTSetULONGBit<9>::value; + static const SK_OT_ULONG ArmenianMask = SkOTSetULONGBit<10>::value; + static const SK_OT_ULONG BasicHebrewMask = SkOTSetULONGBit<11>::value; + static const SK_OT_ULONG HebrewExtendedABMask = SkOTSetULONGBit<12>::value; + static const SK_OT_ULONG BasicArabicMask = SkOTSetULONGBit<13>::value; + static const SK_OT_ULONG ArabicExtendedMask = SkOTSetULONGBit<14>::value; + static const SK_OT_ULONG DevanagariMask = SkOTSetULONGBit<15>::value; + static const SK_OT_ULONG BengaliMask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG GurmukhiMask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG GujaratiMask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG OriyaMask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG TamilMask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG TeluguMask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG KannadaMask = SkOTSetULONGBit<22>::value; + static const SK_OT_ULONG MalayalamMask = SkOTSetULONGBit<23>::value; + static const SK_OT_ULONG ThaiMask = SkOTSetULONGBit<24>::value; + static const SK_OT_ULONG LaoMask = SkOTSetULONGBit<25>::value; + static const SK_OT_ULONG BasicGeorgianMask = SkOTSetULONGBit<26>::value; + static const SK_OT_ULONG GeorgianExtendedMask = SkOTSetULONGBit<27>::value; + static const SK_OT_ULONG HangulJamoMask = SkOTSetULONGBit<28>::value; + static const SK_OT_ULONG LatinExtendedAdditionalMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG GreekExtendedMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG GeneralPunctuationMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkOTSetULONGBit<32 - 32>::value; + static const SK_OT_ULONG CurrencySymbolsMask = SkOTSetULONGBit<33 - 32>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkOTSetULONGBit<34 - 32>::value; + static const SK_OT_ULONG LetterlikeSymbolsMask = SkOTSetULONGBit<35 - 32>::value; + static const SK_OT_ULONG NumberFormsMask = SkOTSetULONGBit<36 - 32>::value; + static const SK_OT_ULONG ArrowsMask = SkOTSetULONGBit<37 - 32>::value; + static const SK_OT_ULONG MathematicalOperatorsMask = SkOTSetULONGBit<38 - 32>::value; + static const SK_OT_ULONG MiscellaneousTechnicalMask = SkOTSetULONGBit<39 - 32>::value; + static const SK_OT_ULONG ControlPicturesMask = SkOTSetULONGBit<40 - 32>::value; + static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkOTSetULONGBit<41 - 32>::value; + static const SK_OT_ULONG EnclosedAlphanumericsMask = SkOTSetULONGBit<42 - 32>::value; + static const SK_OT_ULONG BoxDrawingMask = SkOTSetULONGBit<43 - 32>::value; + static const SK_OT_ULONG BlockElementsMask = SkOTSetULONGBit<44 - 32>::value; + static const SK_OT_ULONG GeometricShapesMask = SkOTSetULONGBit<45 - 32>::value; + static const SK_OT_ULONG MiscellaneousSymbolsMask = SkOTSetULONGBit<46 - 32>::value; + static const SK_OT_ULONG DingbatsMask = SkOTSetULONGBit<47 - 32>::value; + static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG HiraganaMask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG KatakanaMask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG BopomofoMask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG HangulCompatibilityJamoMask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG CJKMiscellaneousMask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityMask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG HangulMask = SkOTSetULONGBit<56 - 32>::value; + //Reserved + //Reserved + static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG PrivateUseAreaMask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG ArabicPresentationFormsAMask = SkOTSetULONGBit<63 - 32>::value; + }; + struct l2 { + static const SK_OT_ULONG CombiningHalfMarksMask = SkOTSetULONGBit<64 - 64>::value; + static const SK_OT_ULONG CJKCompatibilityFormsMask = SkOTSetULONGBit<65 - 64>::value; + static const SK_OT_ULONG SmallFormVariantsMask = SkOTSetULONGBit<66 - 64>::value; + static const SK_OT_ULONG ArabicPresentationFormsBMask = SkOTSetULONGBit<67 - 64>::value; + static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkOTSetULONGBit<68 - 64>::value; + static const SK_OT_ULONG SpecialsMask = SkOTSetULONGBit<69 - 64>::value; + }; + SK_OT_ULONG value[4]; + } raw; + } ulUnicodeRange; + SK_OT_CHAR achVendID[4]; + union Selection { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Italic, + Underscore, + Negative, + Outlined, + Strikeout, + Bold, + Regular, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT ItalicMask = SkOTSetUSHORTBit<0>::value; + static const SK_OT_USHORT UnderscoreMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT NegativeMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT OutlinedMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT StrikeoutMask = SkOTSetUSHORTBit<4>::value; + static const SK_OT_USHORT BoldMask = SkOTSetUSHORTBit<5>::value; + static const SK_OT_USHORT RegularMask = SkOTSetUSHORTBit<6>::value; + SK_OT_USHORT value; + } raw; + } fsSelection; + SK_OT_USHORT usFirstCharIndex; + SK_OT_USHORT usLastCharIndex; + //version0 + SK_OT_SHORT sTypoAscender; + SK_OT_SHORT sTypoDescender; + SK_OT_SHORT sTypoLineGap; + SK_OT_USHORT usWinAscent; + SK_OT_USHORT usWinDescent; + //version1 + union CodePageRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Reserved24, + Reserved25, + Reserved26, + Reserved27, + Reserved28, + MacintoshCharacterSet, + OEMCharacterSet, + SymbolCharacterSet) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Thai_874, + JISJapan_932, + ChineseSimplified_936, + KoreanWansung_949, + ChineseTraditional_950, + KoreanJohab_1361, + Reserved22, + Reserved23) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + Latin1_1252, + Latin2EasternEurope_1250, + Cyrillic_1251, + Greek_1253, + Turkish_1254, + Hebrew_1255, + Arabic_1256, + WindowsBaltic_1257) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + IBMTurkish_857, + IBMCyrillic_855, + Latin2_852, + MSDOSBaltic_775, + Greek_737, + Arabic_708, + WELatin1_850, + US_437) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + IBMGreek_869, + MSDOSRussian_866, + MSDOSNordic_865, + Arabic_864, + MSDOSCanadianFrench_863, + Hebrew_862, + MSDOSIcelandic_861, + MSDOSPortuguese_860) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + Reserved40, + Reserved41, + Reserved42, + Reserved43, + Reserved44, + Reserved45, + Reserved46, + Reserved47) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + Reserved32, + Reserved33, + Reserved34, + Reserved35, + Reserved36, + Reserved37, + Reserved38, + Reserved39) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG Latin1_1252Mask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG Cyrillic_1251Mask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG Greek_1253Mask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG Turkish_1254Mask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG Hebrew_1255Mask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG Arabic_1256Mask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG WindowsBaltic_1257Mask = SkOTSetULONGBit<7>::value; + static const SK_OT_ULONG Thai_874Mask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG JISJapan_932Mask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG ChineseSimplified_936Mask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG KoreanWansung_949Mask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG ChineseTraditional_950Mask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG KoreanJohab_1361Mask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG MacintoshCharacterSetMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG OEMCharacterSetMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG SymbolCharacterSetMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG IBMGreek_869Mask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG MSDOSRussian_866Mask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG MSDOSNordic_865Mask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG Arabic_864Mask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG Hebrew_862Mask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG IBMTurkish_857Mask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG IBMCyrillic_855Mask = SkOTSetULONGBit<57 - 32>::value; + static const SK_OT_ULONG Latin2_852Mask = SkOTSetULONGBit<58 - 32>::value; + static const SK_OT_ULONG MSDOSBaltic_775Mask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG Greek_737Mask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG Arabic_708Mask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG WELatin1_850Mask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG US_437Mask = SkOTSetULONGBit<63 - 32>::value; + }; + SK_OT_ULONG value[2]; + } raw; + } ulCodePageRange; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2_V1) == 86, "sizeof_SkOTTableOS2_V1_not_86"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V2.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V2.h new file mode 100644 index 0000000000..1fa053dae6 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V2.h @@ -0,0 +1,538 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_V2_DEFINED +#define SkOTTable_OS_2_V2_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkIBMFamilyClass.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkPanose.h" + +#pragma pack(push, 1) + +struct SkOTTableOS2_V2 { + SK_OT_USHORT version; + static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(2); + + SK_OT_SHORT xAvgCharWidth; + struct WeightClass { + enum Value : SK_OT_USHORT { + Thin = SkTEndian_SwapBE16(100), + ExtraLight = SkTEndian_SwapBE16(200), + Light = SkTEndian_SwapBE16(300), + Normal = SkTEndian_SwapBE16(400), + Medium = SkTEndian_SwapBE16(500), + SemiBold = SkTEndian_SwapBE16(600), + Bold = SkTEndian_SwapBE16(700), + ExtraBold = SkTEndian_SwapBE16(800), + Black = SkTEndian_SwapBE16(900), + }; + SK_OT_USHORT value; + } usWeightClass; + struct WidthClass { + enum Value : SK_OT_USHORT { + UltraCondensed = SkTEndian_SwapBE16(1), + ExtraCondensed = SkTEndian_SwapBE16(2), + Condensed = SkTEndian_SwapBE16(3), + SemiCondensed = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiExpanded = SkTEndian_SwapBE16(6), + Expanded = SkTEndian_SwapBE16(7), + ExtraExpanded = SkTEndian_SwapBE16(8), + UltraExpanded = SkTEndian_SwapBE16(9), + } value; + } usWidthClass; + union Type { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + NoSubsetting, + Bitmap, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Reserved00, + Restricted, + PreviewPrint, + Editable, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT Installable = 0; + static const SK_OT_USHORT RestrictedMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT PreviewPrintMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT EditableMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT NoSubsettingMask = SkOTSetUSHORTBit<8>::value; + static const SK_OT_USHORT BitmapMask = SkOTSetUSHORTBit<9>::value; + SK_OT_USHORT value; + } raw; + } fsType; + SK_OT_SHORT ySubscriptXSize; + SK_OT_SHORT ySubscriptYSize; + SK_OT_SHORT ySubscriptXOffset; + SK_OT_SHORT ySubscriptYOffset; + SK_OT_SHORT ySuperscriptXSize; + SK_OT_SHORT ySuperscriptYSize; + SK_OT_SHORT ySuperscriptXOffset; + SK_OT_SHORT ySuperscriptYOffset; + SK_OT_SHORT yStrikeoutSize; + SK_OT_SHORT yStrikeoutPosition; + SkIBMFamilyClass sFamilyClass; + SkPanose panose; + union UnicodeRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Thai, + Lao, + Georgian, + Reserved027, + HangulJamo, + LatinExtendedAdditional, + GreekExtended, + GeneralPunctuation) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Bengali, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Reserved008, + Cyrillic, + Armenian, + Hebrew, + Reserved012, + Arabic, + Reserved014, + Devanagari) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + BasicLatin, + Latin1Supplement, + LatinExtendedA, + LatinExtendedB, + IPAExtensions, + SpacingModifierLetters, + CombiningDiacriticalMarks, + Greek) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + Hangul, + Surrogates, + Reserved058, + CJKUnifiedIdeographs, + PrivateUseArea, + CJKCompatibilityIdeographs, + AlphabeticPresentationForms, + ArabicPresentationFormsA) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + CJKSymbolsAndPunctuation, + Hiragana, + Katakana, + Bopomofo, + HangulCompatibilityJamo, + CJKMiscellaneous, + EnclosedCJKLettersAndMonths, + CJKCompatibility) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + ControlPictures, + OpticalCharacterRecognition, + EnclosedAlphanumerics, + BoxDrawing, + BlockElements, + GeometricShapes, + MiscellaneousSymbols, + Dingbats) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + SuperscriptsAndSubscripts, + CurrencySymbols, + CombiningDiacriticalMarksForSymbols, + LetterlikeSymbols, + NumberForms, + Arrows, + MathematicalOperators, + MiscellaneousTechnical) + + //l2 24-31 + SK_OT_BYTE_BITFIELD( + Reserved088, + Reserved089, + Reserved090, + Reserved091, + Reserved092, + Reserved093, + Reserved094, + Reserved095) + //l2 16-23 + SK_OT_BYTE_BITFIELD( + Khmer, + Mongolian, + Braille, + Yi, + Reserved084, + Reserved085, + Reserved086, + Reserved087) + //l2 8-15 + SK_OT_BYTE_BITFIELD( + Thaana, + Sinhala, + Myanmar, + Ethiopic, + Cherokee, + UnifiedCanadianSyllabics, + Ogham, + Runic) + //l2 0-7 + SK_OT_BYTE_BITFIELD( + CombiningHalfMarks, + CJKCompatibilityForms, + SmallFormVariants, + ArabicPresentationFormsB, + HalfwidthAndFullwidthForms, + Specials, + Tibetan, + Syriac) + + //l3 24-31 + SK_OT_BYTE_BITFIELD( + Reserved120, + Reserved121, + Reserved122, + Reserved123, + Reserved124, + Reserved125, + Reserved126, + Reserved127) + //l3 16-23 + SK_OT_BYTE_BITFIELD( + Reserved112, + Reserved113, + Reserved114, + Reserved115, + Reserved116, + Reserved117, + Reserved118, + Reserved119) + //l3 8-15 + SK_OT_BYTE_BITFIELD( + Reserved104, + Reserved105, + Reserved106, + Reserved107, + Reserved108, + Reserved109, + Reserved110, + Reserved111) + //l3 0-7 + SK_OT_BYTE_BITFIELD( + Reserved096, + Reserved097, + Reserved098, + Reserved099, + Reserved100, + Reserved101, + Reserved102, + Reserved103) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG BasicLatinMask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin1SupplementMask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG LatinExtendedAMask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG LatinExtendedBMask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG IPAExtensionsMask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG SpacingModifierLettersMask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG GreekMask = SkOTSetULONGBit<7>::value; + //Reserved + static const SK_OT_ULONG CyrillicMask = SkOTSetULONGBit<9>::value; + static const SK_OT_ULONG ArmenianMask = SkOTSetULONGBit<10>::value; + static const SK_OT_ULONG HebrewMask = SkOTSetULONGBit<11>::value; + //Reserved + static const SK_OT_ULONG ArabicMask = SkOTSetULONGBit<13>::value; + //Reserved + static const SK_OT_ULONG DevanagariMask = SkOTSetULONGBit<15>::value; + static const SK_OT_ULONG BengaliMask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG GurmukhiMask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG GujaratiMask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG OriyaMask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG TamilMask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG TeluguMask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG KannadaMask = SkOTSetULONGBit<22>::value; + static const SK_OT_ULONG MalayalamMask = SkOTSetULONGBit<23>::value; + static const SK_OT_ULONG ThaiMask = SkOTSetULONGBit<24>::value; + static const SK_OT_ULONG LaoMask = SkOTSetULONGBit<25>::value; + static const SK_OT_ULONG GeorgianMask = SkOTSetULONGBit<26>::value; + //Reserved + static const SK_OT_ULONG HangulJamoMask = SkOTSetULONGBit<28>::value; + static const SK_OT_ULONG LatinExtendedAdditionalMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG GreekExtendedMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG GeneralPunctuationMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkOTSetULONGBit<32 - 32>::value; + static const SK_OT_ULONG CurrencySymbolsMask = SkOTSetULONGBit<33 - 32>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkOTSetULONGBit<34 - 32>::value; + static const SK_OT_ULONG LetterlikeSymbolsMask = SkOTSetULONGBit<35 - 32>::value; + static const SK_OT_ULONG NumberFormsMask = SkOTSetULONGBit<36 - 32>::value; + static const SK_OT_ULONG ArrowsMask = SkOTSetULONGBit<37 - 32>::value; + static const SK_OT_ULONG MathematicalOperatorsMask = SkOTSetULONGBit<38 - 32>::value; + static const SK_OT_ULONG MiscellaneousTechnicalMask = SkOTSetULONGBit<39 - 32>::value; + static const SK_OT_ULONG ControlPicturesMask = SkOTSetULONGBit<40 - 32>::value; + static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkOTSetULONGBit<41 - 32>::value; + static const SK_OT_ULONG EnclosedAlphanumericsMask = SkOTSetULONGBit<42 - 32>::value; + static const SK_OT_ULONG BoxDrawingMask = SkOTSetULONGBit<43 - 32>::value; + static const SK_OT_ULONG BlockElementsMask = SkOTSetULONGBit<44 - 32>::value; + static const SK_OT_ULONG GeometricShapesMask = SkOTSetULONGBit<45 - 32>::value; + static const SK_OT_ULONG MiscellaneousSymbolsMask = SkOTSetULONGBit<46 - 32>::value; + static const SK_OT_ULONG DingbatsMask = SkOTSetULONGBit<47 - 32>::value; + static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG HiraganaMask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG KatakanaMask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG BopomofoMask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG HangulCompatibilityJamoMask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG CJKMiscellaneousMask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityMask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG HangulMask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG SurrogatesMask = SkOTSetULONGBit<57 - 32>::value; + //Reserved + static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG PrivateUseAreaMask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG ArabicPresentationFormsAMask = SkOTSetULONGBit<63 - 32>::value; + }; + struct l2 { + static const SK_OT_ULONG CombiningHalfMarksMask = SkOTSetULONGBit<64 - 64>::value; + static const SK_OT_ULONG CJKCompatibilityFormsMask = SkOTSetULONGBit<65 - 64>::value; + static const SK_OT_ULONG SmallFormVariantsMask = SkOTSetULONGBit<66 - 64>::value; + static const SK_OT_ULONG ArabicPresentationFormsBMask = SkOTSetULONGBit<67 - 64>::value; + static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkOTSetULONGBit<68 - 64>::value; + static const SK_OT_ULONG SpecialsMask = SkOTSetULONGBit<69 - 64>::value; + static const SK_OT_ULONG TibetanMask = SkOTSetULONGBit<70 - 64>::value; + static const SK_OT_ULONG SyriacMask = SkOTSetULONGBit<71 - 64>::value; + static const SK_OT_ULONG ThaanaMask = SkOTSetULONGBit<72 - 64>::value; + static const SK_OT_ULONG SinhalaMask = SkOTSetULONGBit<73 - 64>::value; + static const SK_OT_ULONG MyanmarMask = SkOTSetULONGBit<74 - 64>::value; + static const SK_OT_ULONG EthiopicMask = SkOTSetULONGBit<75 - 64>::value; + static const SK_OT_ULONG CherokeeMask = SkOTSetULONGBit<76 - 64>::value; + static const SK_OT_ULONG UnifiedCanadianSyllabicsMask = SkOTSetULONGBit<77 - 64>::value; + static const SK_OT_ULONG OghamMask = SkOTSetULONGBit<78 - 64>::value; + static const SK_OT_ULONG RunicMask = SkOTSetULONGBit<79 - 64>::value; + static const SK_OT_ULONG KhmerMask = SkOTSetULONGBit<80 - 64>::value; + static const SK_OT_ULONG MongolianMask = SkOTSetULONGBit<81 - 64>::value; + static const SK_OT_ULONG BrailleMask = SkOTSetULONGBit<82 - 64>::value; + static const SK_OT_ULONG YiMask = SkOTSetULONGBit<83 - 64>::value; + }; + SK_OT_ULONG value[4]; + } raw; + } ulUnicodeRange; + SK_OT_CHAR achVendID[4]; + union Selection { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Italic, + Underscore, + Negative, + Outlined, + Strikeout, + Bold, + Regular, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT ItalicMask = SkOTSetUSHORTBit<0>::value; + static const SK_OT_USHORT UnderscoreMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT NegativeMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT OutlinedMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT StrikeoutMask = SkOTSetUSHORTBit<4>::value; + static const SK_OT_USHORT BoldMask = SkOTSetUSHORTBit<5>::value; + static const SK_OT_USHORT RegularMask = SkOTSetUSHORTBit<6>::value; + SK_OT_USHORT value; + } raw; + } fsSelection; + SK_OT_USHORT usFirstCharIndex; + SK_OT_USHORT usLastCharIndex; + //version0 + SK_OT_SHORT sTypoAscender; + SK_OT_SHORT sTypoDescender; + SK_OT_SHORT sTypoLineGap; + SK_OT_USHORT usWinAscent; + SK_OT_USHORT usWinDescent; + //version1 + union CodePageRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Reserved24, + Reserved25, + Reserved26, + Reserved27, + Reserved28, + MacintoshCharacterSet, + OEMCharacterSet, + SymbolCharacterSet) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Thai_874, + JISJapan_932, + ChineseSimplified_936, + KoreanWansung_949, + ChineseTraditional_950, + KoreanJohab_1361, + Reserved22, + Reserved23) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Vietnamese, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + Latin1_1252, + Latin2EasternEurope_1250, + Cyrillic_1251, + Greek_1253, + Turkish_1254, + Hebrew_1255, + Arabic_1256, + WindowsBaltic_1257) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + IBMTurkish_857, + IBMCyrillic_855, + Latin2_852, + MSDOSBaltic_775, + Greek_737, + Arabic_708, + WELatin1_850, + US_437) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + IBMGreek_869, + MSDOSRussian_866, + MSDOSNordic_865, + Arabic_864, + MSDOSCanadianFrench_863, + Hebrew_862, + MSDOSIcelandic_861, + MSDOSPortuguese_860) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + Reserved40, + Reserved41, + Reserved42, + Reserved43, + Reserved44, + Reserved45, + Reserved46, + Reserved47) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + Reserved32, + Reserved33, + Reserved34, + Reserved35, + Reserved36, + Reserved37, + Reserved38, + Reserved39) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG Latin1_1252Mask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG Cyrillic_1251Mask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG Greek_1253Mask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG Turkish_1254Mask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG Hebrew_1255Mask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG Arabic_1256Mask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG WindowsBaltic_1257Mask = SkOTSetULONGBit<7>::value; + static const SK_OT_ULONG Vietnamese_1258Mask = SkOTSetULONGBit<8>::value; + static const SK_OT_ULONG Thai_874Mask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG JISJapan_932Mask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG ChineseSimplified_936Mask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG KoreanWansung_949Mask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG ChineseTraditional_950Mask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG KoreanJohab_1361Mask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG MacintoshCharacterSetMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG OEMCharacterSetMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG SymbolCharacterSetMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG IBMGreek_869Mask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG MSDOSRussian_866Mask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG MSDOSNordic_865Mask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG Arabic_864Mask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG Hebrew_862Mask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG IBMTurkish_857Mask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG IBMCyrillic_855Mask = SkOTSetULONGBit<57 - 32>::value; + static const SK_OT_ULONG Latin2_852Mask = SkOTSetULONGBit<58 - 32>::value; + static const SK_OT_ULONG MSDOSBaltic_775Mask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG Greek_737Mask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG Arabic_708Mask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG WELatin1_850Mask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG US_437Mask = SkOTSetULONGBit<63 - 32>::value; + }; + SK_OT_ULONG value[2]; + } raw; + } ulCodePageRange; + //version2 + SK_OT_SHORT sxHeight; + SK_OT_SHORT sCapHeight; + SK_OT_USHORT usDefaultChar; + SK_OT_USHORT usBreakChar; + SK_OT_USHORT usMaxContext; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2_V2) == 96, "sizeof_SkOTTableOS2_V2_not_96"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V3.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V3.h new file mode 100644 index 0000000000..f390269930 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V3.h @@ -0,0 +1,547 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_V3_DEFINED +#define SkOTTable_OS_2_V3_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkIBMFamilyClass.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkPanose.h" + +#pragma pack(push, 1) + +struct SkOTTableOS2_V3 { + SK_OT_USHORT version; + static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(3); + + SK_OT_SHORT xAvgCharWidth; + struct WeightClass { + enum Value : SK_OT_USHORT { + Thin = SkTEndian_SwapBE16(100), + ExtraLight = SkTEndian_SwapBE16(200), + Light = SkTEndian_SwapBE16(300), + Normal = SkTEndian_SwapBE16(400), + Medium = SkTEndian_SwapBE16(500), + SemiBold = SkTEndian_SwapBE16(600), + Bold = SkTEndian_SwapBE16(700), + ExtraBold = SkTEndian_SwapBE16(800), + Black = SkTEndian_SwapBE16(900), + }; + SK_OT_USHORT value; + } usWeightClass; + struct WidthClass { + enum Value : SK_OT_USHORT { + UltraCondensed = SkTEndian_SwapBE16(1), + ExtraCondensed = SkTEndian_SwapBE16(2), + Condensed = SkTEndian_SwapBE16(3), + SemiCondensed = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiExpanded = SkTEndian_SwapBE16(6), + Expanded = SkTEndian_SwapBE16(7), + ExtraExpanded = SkTEndian_SwapBE16(8), + UltraExpanded = SkTEndian_SwapBE16(9), + } value; + } usWidthClass; + union Type { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + NoSubsetting, + Bitmap, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Reserved00, + Restricted, + PreviewPrint, + Editable, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT Installable = 0; + static const SK_OT_USHORT RestrictedMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT PreviewPrintMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT EditableMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT NoSubsettingMask = SkOTSetUSHORTBit<8>::value; + static const SK_OT_USHORT BitmapMask = SkOTSetUSHORTBit<9>::value; + SK_OT_USHORT value; + } raw; + } fsType; + SK_OT_SHORT ySubscriptXSize; + SK_OT_SHORT ySubscriptYSize; + SK_OT_SHORT ySubscriptXOffset; + SK_OT_SHORT ySubscriptYOffset; + SK_OT_SHORT ySuperscriptXSize; + SK_OT_SHORT ySuperscriptYSize; + SK_OT_SHORT ySuperscriptXOffset; + SK_OT_SHORT ySuperscriptYOffset; + SK_OT_SHORT yStrikeoutSize; + SK_OT_SHORT yStrikeoutPosition; + SkIBMFamilyClass sFamilyClass; + SkPanose panose; + union UnicodeRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Thai, + Lao, + Georgian, + Reserved027, + HangulJamo, + LatinExtendedAdditional, + GreekExtended, + GeneralPunctuation) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Bengali, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Reserved008, + Cyrillic, + Armenian, + Hebrew, + Reserved012, + Arabic, + Reserved014, + Devanagari) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + BasicLatin, + Latin1Supplement, + LatinExtendedA, + LatinExtendedB, + IPAExtensions, + SpacingModifierLetters, + CombiningDiacriticalMarks, + GreekAndCoptic) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + Hangul, + NonPlane0, + Reserved058, + CJKUnifiedIdeographs, + PrivateUseArea, + CJKCompatibilityIdeographs, + AlphabeticPresentationForms, + ArabicPresentationFormsA) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + CJKSymbolsAndPunctuation, + Hiragana, + Katakana, + Bopomofo, + HangulCompatibilityJamo, + Reserved053, + EnclosedCJKLettersAndMonths, + CJKCompatibility) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + ControlPictures, + OpticalCharacterRecognition, + EnclosedAlphanumerics, + BoxDrawing, + BlockElements, + GeometricShapes, + MiscellaneousSymbols, + Dingbats) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + SuperscriptsAndSubscripts, + CurrencySymbols, + CombiningDiacriticalMarksForSymbols, + LetterlikeSymbols, + NumberForms, + Arrows, + MathematicalOperators, + MiscellaneousTechnical) + + //l2 24-31 + SK_OT_BYTE_BITFIELD( + MusicalSymbols, + MathematicalAlphanumericSymbols, + PrivateUse, + VariationSelectors, + Tags, + Reserved093, + Reserved094, + Reserved095) + //l2 16-23 + SK_OT_BYTE_BITFIELD( + Khmer, + Mongolian, + Braille, + Yi, + Tagalog_Hanunoo_Buhid_Tagbanwa, + OldItalic, + Gothic, + Deseret) + //l2 8-15 + SK_OT_BYTE_BITFIELD( + Thaana, + Sinhala, + Myanmar, + Ethiopic, + Cherokee, + UnifiedCanadianSyllabics, + Ogham, + Runic) + //l2 0-7 + SK_OT_BYTE_BITFIELD( + CombiningHalfMarks, + CJKCompatibilityForms, + SmallFormVariants, + ArabicPresentationFormsB, + HalfwidthAndFullwidthForms, + Specials, + Tibetan, + Syriac) + + //l3 24-31 + SK_OT_BYTE_BITFIELD( + Reserved120, + Reserved121, + Reserved122, + Reserved123, + Reserved124, + Reserved125, + Reserved126, + Reserved127) + //l3 16-23 + SK_OT_BYTE_BITFIELD( + Reserved112, + Reserved113, + Reserved114, + Reserved115, + Reserved116, + Reserved117, + Reserved118, + Reserved119) + //l3 8-15 + SK_OT_BYTE_BITFIELD( + Reserved104, + Reserved105, + Reserved106, + Reserved107, + Reserved108, + Reserved109, + Reserved110, + Reserved111) + //l3 0-7 + SK_OT_BYTE_BITFIELD( + Reserved096, + Reserved097, + Reserved098, + Reserved099, + Reserved100, + Reserved101, + Reserved102, + Reserved103) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG BasicLatinMask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin1SupplementMask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG LatinExtendedAMask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG LatinExtendedBMask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG IPAExtensionsMask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG SpacingModifierLettersMask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG GreekAndCopticMask = SkOTSetULONGBit<7>::value; + //Reserved + static const SK_OT_ULONG CyrillicMask = SkOTSetULONGBit<9>::value; + static const SK_OT_ULONG ArmenianMask = SkOTSetULONGBit<10>::value; + static const SK_OT_ULONG HebrewMask = SkOTSetULONGBit<11>::value; + //Reserved + static const SK_OT_ULONG ArabicMask = SkOTSetULONGBit<13>::value; + //Reserved + static const SK_OT_ULONG DevanagariMask = SkOTSetULONGBit<15>::value; + static const SK_OT_ULONG BengaliMask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG GurmukhiMask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG GujaratiMask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG OriyaMask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG TamilMask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG TeluguMask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG KannadaMask = SkOTSetULONGBit<22>::value; + static const SK_OT_ULONG MalayalamMask = SkOTSetULONGBit<23>::value; + static const SK_OT_ULONG ThaiMask = SkOTSetULONGBit<24>::value; + static const SK_OT_ULONG LaoMask = SkOTSetULONGBit<25>::value; + static const SK_OT_ULONG GeorgianMask = SkOTSetULONGBit<26>::value; + //Reserved + static const SK_OT_ULONG HangulJamoMask = SkOTSetULONGBit<28>::value; + static const SK_OT_ULONG LatinExtendedAdditionalMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG GreekExtendedMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG GeneralPunctuationMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkOTSetULONGBit<32 - 32>::value; + static const SK_OT_ULONG CurrencySymbolsMask = SkOTSetULONGBit<33 - 32>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkOTSetULONGBit<34 - 32>::value; + static const SK_OT_ULONG LetterlikeSymbolsMask = SkOTSetULONGBit<35 - 32>::value; + static const SK_OT_ULONG NumberFormsMask = SkOTSetULONGBit<36 - 32>::value; + static const SK_OT_ULONG ArrowsMask = SkOTSetULONGBit<37 - 32>::value; + static const SK_OT_ULONG MathematicalOperatorsMask = SkOTSetULONGBit<38 - 32>::value; + static const SK_OT_ULONG MiscellaneousTechnicalMask = SkOTSetULONGBit<39 - 32>::value; + static const SK_OT_ULONG ControlPicturesMask = SkOTSetULONGBit<40 - 32>::value; + static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkOTSetULONGBit<41 - 32>::value; + static const SK_OT_ULONG EnclosedAlphanumericsMask = SkOTSetULONGBit<42 - 32>::value; + static const SK_OT_ULONG BoxDrawingMask = SkOTSetULONGBit<43 - 32>::value; + static const SK_OT_ULONG BlockElementsMask = SkOTSetULONGBit<44 - 32>::value; + static const SK_OT_ULONG GeometricShapesMask = SkOTSetULONGBit<45 - 32>::value; + static const SK_OT_ULONG MiscellaneousSymbolsMask = SkOTSetULONGBit<46 - 32>::value; + static const SK_OT_ULONG DingbatsMask = SkOTSetULONGBit<47 - 32>::value; + static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG HiraganaMask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG KatakanaMask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG BopomofoMask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG HangulCompatibilityJamoMask = SkOTSetULONGBit<52 - 32>::value; + //Reserved + static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityMask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG HangulMask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG NonPlane0Mask = SkOTSetULONGBit<57 - 32>::value; + //Reserved + static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG PrivateUseAreaMask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG ArabicPresentationFormsAMask = SkOTSetULONGBit<63 - 32>::value; + }; + struct l2 { + static const SK_OT_ULONG CombiningHalfMarksMask = SkOTSetULONGBit<64 - 64>::value; + static const SK_OT_ULONG CJKCompatibilityFormsMask = SkOTSetULONGBit<65 - 64>::value; + static const SK_OT_ULONG SmallFormVariantsMask = SkOTSetULONGBit<66 - 64>::value; + static const SK_OT_ULONG ArabicPresentationFormsBMask = SkOTSetULONGBit<67 - 64>::value; + static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkOTSetULONGBit<68 - 64>::value; + static const SK_OT_ULONG SpecialsMask = SkOTSetULONGBit<69 - 64>::value; + static const SK_OT_ULONG TibetanMask = SkOTSetULONGBit<70 - 64>::value; + static const SK_OT_ULONG SyriacMask = SkOTSetULONGBit<71 - 64>::value; + static const SK_OT_ULONG ThaanaMask = SkOTSetULONGBit<72 - 64>::value; + static const SK_OT_ULONG SinhalaMask = SkOTSetULONGBit<73 - 64>::value; + static const SK_OT_ULONG MyanmarMask = SkOTSetULONGBit<74 - 64>::value; + static const SK_OT_ULONG EthiopicMask = SkOTSetULONGBit<75 - 64>::value; + static const SK_OT_ULONG CherokeeMask = SkOTSetULONGBit<76 - 64>::value; + static const SK_OT_ULONG UnifiedCanadianSyllabicsMask = SkOTSetULONGBit<77 - 64>::value; + static const SK_OT_ULONG OghamMask = SkOTSetULONGBit<78 - 64>::value; + static const SK_OT_ULONG RunicMask = SkOTSetULONGBit<79 - 64>::value; + static const SK_OT_ULONG KhmerMask = SkOTSetULONGBit<80 - 64>::value; + static const SK_OT_ULONG MongolianMask = SkOTSetULONGBit<81 - 64>::value; + static const SK_OT_ULONG BrailleMask = SkOTSetULONGBit<82 - 64>::value; + static const SK_OT_ULONG YiMask = SkOTSetULONGBit<83 - 64>::value; + static const SK_OT_ULONG Tagalog_Hanunoo_Buhid_TagbanwaMask = SkOTSetULONGBit<84 - 64>::value; + static const SK_OT_ULONG OldItalicMask = SkOTSetULONGBit<85 - 64>::value; + static const SK_OT_ULONG GothicMask = SkOTSetULONGBit<86 - 64>::value; + static const SK_OT_ULONG DeseretMask = SkOTSetULONGBit<87 - 64>::value; + static const SK_OT_ULONG MusicalSymbolsMask = SkOTSetULONGBit<88 - 64>::value; + static const SK_OT_ULONG MathematicalAlphanumericSymbolsMask = SkOTSetULONGBit<89 - 64>::value; + static const SK_OT_ULONG PrivateUseMask = SkOTSetULONGBit<90 - 64>::value; + static const SK_OT_ULONG VariationSelectorsMask = SkOTSetULONGBit<91 - 64>::value; + static const SK_OT_ULONG TagsMask = SkOTSetULONGBit<92 - 64>::value; + }; + SK_OT_ULONG value[4]; + } raw; + } ulUnicodeRange; + SK_OT_CHAR achVendID[4]; + union Selection { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Italic, + Underscore, + Negative, + Outlined, + Strikeout, + Bold, + Regular, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT ItalicMask = SkOTSetUSHORTBit<0>::value; + static const SK_OT_USHORT UnderscoreMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT NegativeMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT OutlinedMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT StrikeoutMask = SkOTSetUSHORTBit<4>::value; + static const SK_OT_USHORT BoldMask = SkOTSetUSHORTBit<5>::value; + static const SK_OT_USHORT RegularMask = SkOTSetUSHORTBit<6>::value; + SK_OT_USHORT value; + } raw; + } fsSelection; + SK_OT_USHORT usFirstCharIndex; + SK_OT_USHORT usLastCharIndex; + //version0 + SK_OT_SHORT sTypoAscender; + SK_OT_SHORT sTypoDescender; + SK_OT_SHORT sTypoLineGap; + SK_OT_USHORT usWinAscent; + SK_OT_USHORT usWinDescent; + //version1 + union CodePageRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Reserved24, + Reserved25, + Reserved26, + Reserved27, + Reserved28, + MacintoshCharacterSet, + OEMCharacterSet, + SymbolCharacterSet) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Thai_874, + JISJapan_932, + ChineseSimplified_936, + KoreanWansung_949, + ChineseTraditional_950, + KoreanJohab_1361, + Reserved22, + Reserved23) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Vietnamese, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + Latin1_1252, + Latin2EasternEurope_1250, + Cyrillic_1251, + Greek_1253, + Turkish_1254, + Hebrew_1255, + Arabic_1256, + WindowsBaltic_1257) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + IBMTurkish_857, + IBMCyrillic_855, + Latin2_852, + MSDOSBaltic_775, + Greek_737, + Arabic_708, + WELatin1_850, + US_437) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + IBMGreek_869, + MSDOSRussian_866, + MSDOSNordic_865, + Arabic_864, + MSDOSCanadianFrench_863, + Hebrew_862, + MSDOSIcelandic_861, + MSDOSPortuguese_860) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + Reserved40, + Reserved41, + Reserved42, + Reserved43, + Reserved44, + Reserved45, + Reserved46, + Reserved47) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + Reserved32, + Reserved33, + Reserved34, + Reserved35, + Reserved36, + Reserved37, + Reserved38, + Reserved39) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG Latin1_1252Mask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG Cyrillic_1251Mask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG Greek_1253Mask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG Turkish_1254Mask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG Hebrew_1255Mask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG Arabic_1256Mask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG WindowsBaltic_1257Mask = SkOTSetULONGBit<7>::value; + static const SK_OT_ULONG Vietnamese_1258Mask = SkOTSetULONGBit<8>::value; + static const SK_OT_ULONG Thai_874Mask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG JISJapan_932Mask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG ChineseSimplified_936Mask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG KoreanWansung_949Mask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG ChineseTraditional_950Mask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG KoreanJohab_1361Mask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG MacintoshCharacterSetMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG OEMCharacterSetMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG SymbolCharacterSetMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG IBMGreek_869Mask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG MSDOSRussian_866Mask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG MSDOSNordic_865Mask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG Arabic_864Mask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG Hebrew_862Mask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG IBMTurkish_857Mask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG IBMCyrillic_855Mask = SkOTSetULONGBit<57 - 32>::value; + static const SK_OT_ULONG Latin2_852Mask = SkOTSetULONGBit<58 - 32>::value; + static const SK_OT_ULONG MSDOSBaltic_775Mask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG Greek_737Mask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG Arabic_708Mask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG WELatin1_850Mask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG US_437Mask = SkOTSetULONGBit<63 - 32>::value; + }; + SK_OT_ULONG value[2]; + } raw; + } ulCodePageRange; + //version2 + SK_OT_SHORT sxHeight; + SK_OT_SHORT sCapHeight; + SK_OT_USHORT usDefaultChar; + SK_OT_USHORT usBreakChar; + SK_OT_USHORT usMaxContext; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2_V3) == 96, "sizeof_SkOTTableOS2_V3_not_96"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V4.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V4.h new file mode 100644 index 0000000000..46712e0aff --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_V4.h @@ -0,0 +1,582 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_V4_DEFINED +#define SkOTTable_OS_2_V4_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkIBMFamilyClass.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkPanose.h" + +#pragma pack(push, 1) + +struct SkOTTableOS2_V4 { + SK_OT_USHORT version; + static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(4); + + SK_OT_SHORT xAvgCharWidth; + struct WeightClass { + enum Value : SK_OT_USHORT { + Thin = SkTEndian_SwapBE16(100), + ExtraLight = SkTEndian_SwapBE16(200), + Light = SkTEndian_SwapBE16(300), + Normal = SkTEndian_SwapBE16(400), + Medium = SkTEndian_SwapBE16(500), + SemiBold = SkTEndian_SwapBE16(600), + Bold = SkTEndian_SwapBE16(700), + ExtraBold = SkTEndian_SwapBE16(800), + Black = SkTEndian_SwapBE16(900), + }; + SK_OT_USHORT value; + } usWeightClass; + struct WidthClass { + enum Value : SK_OT_USHORT { + UltraCondensed = SkTEndian_SwapBE16(1), + ExtraCondensed = SkTEndian_SwapBE16(2), + Condensed = SkTEndian_SwapBE16(3), + SemiCondensed = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiExpanded = SkTEndian_SwapBE16(6), + Expanded = SkTEndian_SwapBE16(7), + ExtraExpanded = SkTEndian_SwapBE16(8), + UltraExpanded = SkTEndian_SwapBE16(9), + } value; + } usWidthClass; + union Type { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + NoSubsetting, + Bitmap, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Reserved00, + Restricted, + PreviewPrint, + Editable, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT Installable = 0; + static const SK_OT_USHORT RestrictedMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT PreviewPrintMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT EditableMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT NoSubsettingMask = SkOTSetUSHORTBit<8>::value; + static const SK_OT_USHORT BitmapMask = SkOTSetUSHORTBit<9>::value; + SK_OT_USHORT value; + } raw; + } fsType; + SK_OT_SHORT ySubscriptXSize; + SK_OT_SHORT ySubscriptYSize; + SK_OT_SHORT ySubscriptXOffset; + SK_OT_SHORT ySubscriptYOffset; + SK_OT_SHORT ySuperscriptXSize; + SK_OT_SHORT ySuperscriptYSize; + SK_OT_SHORT ySuperscriptXOffset; + SK_OT_SHORT ySuperscriptYOffset; + SK_OT_SHORT yStrikeoutSize; + SK_OT_SHORT yStrikeoutPosition; + SkIBMFamilyClass sFamilyClass; + SkPanose panose; + union UnicodeRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Thai, + Lao, + Georgian, + Balinese, + HangulJamo, + LatinExtendedAdditional, + GreekExtended, + GeneralPunctuation) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Bengali, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Coptic, + Cyrillic, + Armenian, + Hebrew, + Vai, + Arabic, + NKo, + Devanagari) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + BasicLatin, + Latin1Supplement, + LatinExtendedA, + LatinExtendedB, + IPAExtensions, + SpacingModifierLetters, + CombiningDiacriticalMarks, + GreekAndCoptic) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + Hangul, + NonPlane0, + Phoenician, + CJKUnifiedIdeographs, + PrivateUseArea, + CJKCompatibilityIdeographs, + AlphabeticPresentationForms, + ArabicPresentationFormsA) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + CJKSymbolsAndPunctuation, + Hiragana, + Katakana, + Bopomofo, + HangulCompatibilityJamo, + PhagsPa, + EnclosedCJKLettersAndMonths, + CJKCompatibility) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + ControlPictures, + OpticalCharacterRecognition, + EnclosedAlphanumerics, + BoxDrawing, + BlockElements, + GeometricShapes, + MiscellaneousSymbols, + Dingbats) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + SuperscriptsAndSubscripts, + CurrencySymbols, + CombiningDiacriticalMarksForSymbols, + LetterlikeSymbols, + NumberForms, + Arrows, + MathematicalOperators, + MiscellaneousTechnical) + + //l2 24-31 + SK_OT_BYTE_BITFIELD( + MusicalSymbols, + MathematicalAlphanumericSymbols, + PrivateUse, + VariationSelectors, + Tags, + Limbu, + TaiLe, + NewTaiLue) + //l2 16-23 + SK_OT_BYTE_BITFIELD( + Khmer, + Mongolian, + Braille, + Yi, + Tagalog_Hanunoo_Buhid_Tagbanwa, + OldItalic, + Gothic, + Deseret) + //l2 8-15 + SK_OT_BYTE_BITFIELD( + Thaana, + Sinhala, + Myanmar, + Ethiopic, + Cherokee, + UnifiedCanadianSyllabics, + Ogham, + Runic) + //l2 0-7 + SK_OT_BYTE_BITFIELD( + CombiningHalfMarks, + CJKCompatibilityForms, + SmallFormVariants, + ArabicPresentationFormsB, + HalfwidthAndFullwidthForms, + Specials, + Tibetan, + Syriac) + + //l3 24-31 + SK_OT_BYTE_BITFIELD( + PhaistosDisc, + Carian_Lycian_Lydian, + DominoTiles_MahjongTiles, + Reserved123, + Reserved124, + Reserved125, + Reserved126, + Reserved127) + //l3 16-23 + SK_OT_BYTE_BITFIELD( + Sundanese, + Lepcha, + OlChiki, + Saurashtra, + KayahLi, + Rejang, + Cham, + AncientSymbols) + //l3 8-15 + SK_OT_BYTE_BITFIELD( + OldPersian, + Shavian, + Osmanya, + CypriotSyllabary, + Kharoshthi, + TaiXuanJingSymbols, + Cuneiform, + CountingRodNumerals) + //l3 0-7 + SK_OT_BYTE_BITFIELD( + Buginese, + Glagolitic, + Tifinagh, + YijingHexagramSymbols, + SylotiNagri, + LinearB_AegeanNumbers, + AncientGreekNumbers, + Ugaritic) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG BasicLatinMask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin1SupplementMask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG LatinExtendedAMask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG LatinExtendedBMask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG IPAExtensionsMask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG SpacingModifierLettersMask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksMask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG GreekAndCopticMask = SkOTSetULONGBit<7>::value; + static const SK_OT_ULONG CopticMask = SkOTSetULONGBit<8>::value; + static const SK_OT_ULONG CyrillicMask = SkOTSetULONGBit<9>::value; + static const SK_OT_ULONG ArmenianMask = SkOTSetULONGBit<10>::value; + static const SK_OT_ULONG HebrewMask = SkOTSetULONGBit<11>::value; + static const SK_OT_ULONG VaiMask = SkOTSetULONGBit<12>::value; + static const SK_OT_ULONG ArabicMask = SkOTSetULONGBit<13>::value; + static const SK_OT_ULONG NKoMask = SkOTSetULONGBit<14>::value; + static const SK_OT_ULONG DevanagariMask = SkOTSetULONGBit<15>::value; + static const SK_OT_ULONG BengaliMask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG GurmukhiMask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG GujaratiMask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG OriyaMask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG TamilMask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG TeluguMask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG KannadaMask = SkOTSetULONGBit<22>::value; + static const SK_OT_ULONG MalayalamMask = SkOTSetULONGBit<23>::value; + static const SK_OT_ULONG ThaiMask = SkOTSetULONGBit<24>::value; + static const SK_OT_ULONG LaoMask = SkOTSetULONGBit<25>::value; + static const SK_OT_ULONG GeorgianMask = SkOTSetULONGBit<26>::value; + static const SK_OT_ULONG BalineseMask = SkOTSetULONGBit<27>::value; + static const SK_OT_ULONG HangulJamoMask = SkOTSetULONGBit<28>::value; + static const SK_OT_ULONG LatinExtendedAdditionalMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG GreekExtendedMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG GeneralPunctuationMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG SuperscriptsAndSubscriptsMask = SkOTSetULONGBit<32 - 32>::value; + static const SK_OT_ULONG CurrencySymbolsMask = SkOTSetULONGBit<33 - 32>::value; + static const SK_OT_ULONG CombiningDiacriticalMarksForSymbolsMask = SkOTSetULONGBit<34 - 32>::value; + static const SK_OT_ULONG LetterlikeSymbolsMask = SkOTSetULONGBit<35 - 32>::value; + static const SK_OT_ULONG NumberFormsMask = SkOTSetULONGBit<36 - 32>::value; + static const SK_OT_ULONG ArrowsMask = SkOTSetULONGBit<37 - 32>::value; + static const SK_OT_ULONG MathematicalOperatorsMask = SkOTSetULONGBit<38 - 32>::value; + static const SK_OT_ULONG MiscellaneousTechnicalMask = SkOTSetULONGBit<39 - 32>::value; + static const SK_OT_ULONG ControlPicturesMask = SkOTSetULONGBit<40 - 32>::value; + static const SK_OT_ULONG OpticalCharacterRecognitionMask = SkOTSetULONGBit<41 - 32>::value; + static const SK_OT_ULONG EnclosedAlphanumericsMask = SkOTSetULONGBit<42 - 32>::value; + static const SK_OT_ULONG BoxDrawingMask = SkOTSetULONGBit<43 - 32>::value; + static const SK_OT_ULONG BlockElementsMask = SkOTSetULONGBit<44 - 32>::value; + static const SK_OT_ULONG GeometricShapesMask = SkOTSetULONGBit<45 - 32>::value; + static const SK_OT_ULONG MiscellaneousSymbolsMask = SkOTSetULONGBit<46 - 32>::value; + static const SK_OT_ULONG DingbatsMask = SkOTSetULONGBit<47 - 32>::value; + static const SK_OT_ULONG CJKSymbolsAndPunctuationMask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG HiraganaMask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG KatakanaMask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG BopomofoMask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG HangulCompatibilityJamoMask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG PhagsPaMask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG EnclosedCJKLettersAndMonthsMask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityMask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG HangulMask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG NonPlane0Mask = SkOTSetULONGBit<57 - 32>::value; + static const SK_OT_ULONG PhoenicianMask = SkOTSetULONGBit<58 - 32>::value; + static const SK_OT_ULONG CJKUnifiedIdeographsMask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG PrivateUseAreaMask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG CJKCompatibilityIdeographsMask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG AlphabeticPresentationFormsMask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG ArabicPresentationFormsAMask = SkOTSetULONGBit<63 - 32>::value; + }; + struct l2 { + static const SK_OT_ULONG CombiningHalfMarksMask = SkOTSetULONGBit<64 - 64>::value; + static const SK_OT_ULONG CJKCompatibilityFormsMask = SkOTSetULONGBit<65 - 64>::value; + static const SK_OT_ULONG SmallFormVariantsMask = SkOTSetULONGBit<66 - 64>::value; + static const SK_OT_ULONG ArabicPresentationFormsBMask = SkOTSetULONGBit<67 - 64>::value; + static const SK_OT_ULONG HalfwidthAndFullwidthFormsMask = SkOTSetULONGBit<68 - 64>::value; + static const SK_OT_ULONG SpecialsMask = SkOTSetULONGBit<69 - 64>::value; + static const SK_OT_ULONG TibetanMask = SkOTSetULONGBit<70 - 64>::value; + static const SK_OT_ULONG SyriacMask = SkOTSetULONGBit<71 - 64>::value; + static const SK_OT_ULONG ThaanaMask = SkOTSetULONGBit<72 - 64>::value; + static const SK_OT_ULONG SinhalaMask = SkOTSetULONGBit<73 - 64>::value; + static const SK_OT_ULONG MyanmarMask = SkOTSetULONGBit<74 - 64>::value; + static const SK_OT_ULONG EthiopicMask = SkOTSetULONGBit<75 - 64>::value; + static const SK_OT_ULONG CherokeeMask = SkOTSetULONGBit<76 - 64>::value; + static const SK_OT_ULONG UnifiedCanadianSyllabicsMask = SkOTSetULONGBit<77 - 64>::value; + static const SK_OT_ULONG OghamMask = SkOTSetULONGBit<78 - 64>::value; + static const SK_OT_ULONG RunicMask = SkOTSetULONGBit<79 - 64>::value; + static const SK_OT_ULONG KhmerMask = SkOTSetULONGBit<80 - 64>::value; + static const SK_OT_ULONG MongolianMask = SkOTSetULONGBit<81 - 64>::value; + static const SK_OT_ULONG BrailleMask = SkOTSetULONGBit<82 - 64>::value; + static const SK_OT_ULONG YiMask = SkOTSetULONGBit<83 - 64>::value; + static const SK_OT_ULONG Tagalog_Hanunoo_Buhid_TagbanwaMask = SkOTSetULONGBit<84 - 64>::value; + static const SK_OT_ULONG OldItalicMask = SkOTSetULONGBit<85 - 64>::value; + static const SK_OT_ULONG GothicMask = SkOTSetULONGBit<86 - 64>::value; + static const SK_OT_ULONG DeseretMask = SkOTSetULONGBit<87 - 64>::value; + static const SK_OT_ULONG MusicalSymbolsMask = SkOTSetULONGBit<88 - 64>::value; + static const SK_OT_ULONG MathematicalAlphanumericSymbolsMask = SkOTSetULONGBit<89 - 64>::value; + static const SK_OT_ULONG PrivateUseMask = SkOTSetULONGBit<90 - 64>::value; + static const SK_OT_ULONG VariationSelectorsMask = SkOTSetULONGBit<91 - 64>::value; + static const SK_OT_ULONG TagsMask = SkOTSetULONGBit<92 - 64>::value; + static const SK_OT_ULONG LimbuMask = SkOTSetULONGBit<93 - 64>::value; + static const SK_OT_ULONG TaiLeMask = SkOTSetULONGBit<94 - 64>::value; + static const SK_OT_ULONG NewTaiLueMask = SkOTSetULONGBit<95 - 64>::value; + }; + struct l3 { + static const SK_OT_ULONG BugineseMask = SkOTSetULONGBit<96 - 96>::value; + static const SK_OT_ULONG GlagoliticMask = SkOTSetULONGBit<97 - 96>::value; + static const SK_OT_ULONG TifinaghMask = SkOTSetULONGBit<98 - 96>::value; + static const SK_OT_ULONG YijingHexagramSymbolsMask = SkOTSetULONGBit<99 - 96>::value; + static const SK_OT_ULONG SylotiNagriMask = SkOTSetULONGBit<100 - 96>::value; + static const SK_OT_ULONG LinearB_AegeanNumbersMask = SkOTSetULONGBit<101 - 96>::value; + static const SK_OT_ULONG AncientGreekNumbersMask = SkOTSetULONGBit<102 - 96>::value; + static const SK_OT_ULONG UgariticMask = SkOTSetULONGBit<103 - 96>::value; + static const SK_OT_ULONG OldPersianMask = SkOTSetULONGBit<104 - 96>::value; + static const SK_OT_ULONG ShavianMask = SkOTSetULONGBit<105 - 96>::value; + static const SK_OT_ULONG OsmanyaMask = SkOTSetULONGBit<106 - 96>::value; + static const SK_OT_ULONG CypriotSyllabaryMask = SkOTSetULONGBit<107 - 96>::value; + static const SK_OT_ULONG KharoshthiMask = SkOTSetULONGBit<108 - 96>::value; + static const SK_OT_ULONG TaiXuanJingSymbolsMask = SkOTSetULONGBit<109 - 96>::value; + static const SK_OT_ULONG CuneiformMask = SkOTSetULONGBit<110 - 96>::value; + static const SK_OT_ULONG CountingRodNumeralsMask = SkOTSetULONGBit<111 - 96>::value; + static const SK_OT_ULONG SundaneseMask = SkOTSetULONGBit<112 - 96>::value; + static const SK_OT_ULONG LepchaMask = SkOTSetULONGBit<113 - 96>::value; + static const SK_OT_ULONG OlChikiMask = SkOTSetULONGBit<114 - 96>::value; + static const SK_OT_ULONG SaurashtraMask = SkOTSetULONGBit<115 - 96>::value; + static const SK_OT_ULONG KayahLiMask = SkOTSetULONGBit<116 - 96>::value; + static const SK_OT_ULONG RejangMask = SkOTSetULONGBit<117 - 96>::value; + static const SK_OT_ULONG ChamMask = SkOTSetULONGBit<118 - 96>::value; + static const SK_OT_ULONG AncientSymbolsMask = SkOTSetULONGBit<119 - 96>::value; + static const SK_OT_ULONG PhaistosDiscMask = SkOTSetULONGBit<120 - 96>::value; + static const SK_OT_ULONG Carian_Lycian_LydianMask = SkOTSetULONGBit<121 - 96>::value; + static const SK_OT_ULONG DominoTiles_MahjongTilesMask = SkOTSetULONGBit<122 - 96>::value; + }; + SK_OT_ULONG value[4]; + } raw; + } ulUnicodeRange; + SK_OT_CHAR achVendID[4]; + union Selection { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + WWS, + Oblique, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Italic, + Underscore, + Negative, + Outlined, + Strikeout, + Bold, + Regular, + UseTypoMetrics) + } field; + struct Raw { + static const SK_OT_USHORT ItalicMask = SkOTSetUSHORTBit<0>::value; + static const SK_OT_USHORT UnderscoreMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT NegativeMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT OutlinedMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT StrikeoutMask = SkOTSetUSHORTBit<4>::value; + static const SK_OT_USHORT BoldMask = SkOTSetUSHORTBit<5>::value; + static const SK_OT_USHORT RegularMask = SkOTSetUSHORTBit<6>::value; + static const SK_OT_USHORT UseTypoMetricsMask = SkOTSetUSHORTBit<7>::value; + static const SK_OT_USHORT WWSMask = SkOTSetUSHORTBit<8>::value; + static const SK_OT_USHORT ObliqueMask = SkOTSetUSHORTBit<9>::value; + SK_OT_USHORT value; + } raw; + } fsSelection; + SK_OT_USHORT usFirstCharIndex; + SK_OT_USHORT usLastCharIndex; + //version0 + SK_OT_SHORT sTypoAscender; + SK_OT_SHORT sTypoDescender; + SK_OT_SHORT sTypoLineGap; + SK_OT_USHORT usWinAscent; + SK_OT_USHORT usWinDescent; + //version1 + union CodePageRange { + struct Field { + //l0 24-31 + SK_OT_BYTE_BITFIELD( + Reserved24, + Reserved25, + Reserved26, + Reserved27, + Reserved28, + MacintoshCharacterSet, + OEMCharacterSet, + SymbolCharacterSet) + //l0 16-23 + SK_OT_BYTE_BITFIELD( + Thai_874, + JISJapan_932, + ChineseSimplified_936, + KoreanWansung_949, + ChineseTraditional_950, + KoreanJohab_1361, + Reserved22, + Reserved23) + //l0 8-15 + SK_OT_BYTE_BITFIELD( + Vietnamese, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //l0 0-7 + SK_OT_BYTE_BITFIELD( + Latin1_1252, + Latin2EasternEurope_1250, + Cyrillic_1251, + Greek_1253, + Turkish_1254, + Hebrew_1255, + Arabic_1256, + WindowsBaltic_1257) + + //l1 24-31 + SK_OT_BYTE_BITFIELD( + IBMTurkish_857, + IBMCyrillic_855, + Latin2_852, + MSDOSBaltic_775, + Greek_737, + Arabic_708, + WELatin1_850, + US_437) + //l1 16-23 + SK_OT_BYTE_BITFIELD( + IBMGreek_869, + MSDOSRussian_866, + MSDOSNordic_865, + Arabic_864, + MSDOSCanadianFrench_863, + Hebrew_862, + MSDOSIcelandic_861, + MSDOSPortuguese_860) + //l1 8-15 + SK_OT_BYTE_BITFIELD( + Reserved40, + Reserved41, + Reserved42, + Reserved43, + Reserved44, + Reserved45, + Reserved46, + Reserved47) + //l1 0-7 + SK_OT_BYTE_BITFIELD( + Reserved32, + Reserved33, + Reserved34, + Reserved35, + Reserved36, + Reserved37, + Reserved38, + Reserved39) + } field; + struct Raw { + struct l0 { + static const SK_OT_ULONG Latin1_1252Mask = SkOTSetULONGBit<0>::value; + static const SK_OT_ULONG Latin2EasternEurope_1250Mask = SkOTSetULONGBit<1>::value; + static const SK_OT_ULONG Cyrillic_1251Mask = SkOTSetULONGBit<2>::value; + static const SK_OT_ULONG Greek_1253Mask = SkOTSetULONGBit<3>::value; + static const SK_OT_ULONG Turkish_1254Mask = SkOTSetULONGBit<4>::value; + static const SK_OT_ULONG Hebrew_1255Mask = SkOTSetULONGBit<5>::value; + static const SK_OT_ULONG Arabic_1256Mask = SkOTSetULONGBit<6>::value; + static const SK_OT_ULONG WindowsBaltic_1257Mask = SkOTSetULONGBit<7>::value; + static const SK_OT_ULONG Vietnamese_1258Mask = SkOTSetULONGBit<8>::value; + static const SK_OT_ULONG Thai_874Mask = SkOTSetULONGBit<16>::value; + static const SK_OT_ULONG JISJapan_932Mask = SkOTSetULONGBit<17>::value; + static const SK_OT_ULONG ChineseSimplified_936Mask = SkOTSetULONGBit<18>::value; + static const SK_OT_ULONG KoreanWansung_949Mask = SkOTSetULONGBit<19>::value; + static const SK_OT_ULONG ChineseTraditional_950Mask = SkOTSetULONGBit<20>::value; + static const SK_OT_ULONG KoreanJohab_1361Mask = SkOTSetULONGBit<21>::value; + static const SK_OT_ULONG MacintoshCharacterSetMask = SkOTSetULONGBit<29>::value; + static const SK_OT_ULONG OEMCharacterSetMask = SkOTSetULONGBit<30>::value; + static const SK_OT_ULONG SymbolCharacterSetMask = SkOTSetULONGBit<31>::value; + }; + struct l1 { + static const SK_OT_ULONG IBMGreek_869Mask = SkOTSetULONGBit<48 - 32>::value; + static const SK_OT_ULONG MSDOSRussian_866Mask = SkOTSetULONGBit<49 - 32>::value; + static const SK_OT_ULONG MSDOSNordic_865Mask = SkOTSetULONGBit<50 - 32>::value; + static const SK_OT_ULONG Arabic_864Mask = SkOTSetULONGBit<51 - 32>::value; + static const SK_OT_ULONG MSDOSCanadianFrench_863Mask = SkOTSetULONGBit<52 - 32>::value; + static const SK_OT_ULONG Hebrew_862Mask = SkOTSetULONGBit<53 - 32>::value; + static const SK_OT_ULONG MSDOSIcelandic_861Mask = SkOTSetULONGBit<54 - 32>::value; + static const SK_OT_ULONG MSDOSPortuguese_860Mask = SkOTSetULONGBit<55 - 32>::value; + static const SK_OT_ULONG IBMTurkish_857Mask = SkOTSetULONGBit<56 - 32>::value; + static const SK_OT_ULONG IBMCyrillic_855Mask = SkOTSetULONGBit<57 - 32>::value; + static const SK_OT_ULONG Latin2_852Mask = SkOTSetULONGBit<58 - 32>::value; + static const SK_OT_ULONG MSDOSBaltic_775Mask = SkOTSetULONGBit<59 - 32>::value; + static const SK_OT_ULONG Greek_737Mask = SkOTSetULONGBit<60 - 32>::value; + static const SK_OT_ULONG Arabic_708Mask = SkOTSetULONGBit<61 - 32>::value; + static const SK_OT_ULONG WELatin1_850Mask = SkOTSetULONGBit<62 - 32>::value; + static const SK_OT_ULONG US_437Mask = SkOTSetULONGBit<63 - 32>::value; + }; + SK_OT_ULONG value[2]; + } raw; + } ulCodePageRange; + //version2 + SK_OT_SHORT sxHeight; + SK_OT_SHORT sCapHeight; + SK_OT_USHORT usDefaultChar; + SK_OT_USHORT usBreakChar; + SK_OT_USHORT usMaxContext; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2_V4) == 96, "sizeof_SkOTTableOS2_V4_not_96"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_VA.h b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_VA.h new file mode 100644 index 0000000000..3476c99377 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_OS_2_VA.h @@ -0,0 +1,141 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_OS_2_VA_DEFINED +#define SkOTTable_OS_2_VA_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkIBMFamilyClass.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkPanose.h" + +#pragma pack(push, 1) + +//Original V0 TT +struct SkOTTableOS2_VA { + SK_OT_USHORT version; + //SkOTTableOS2_VA::VERSION and SkOTTableOS2_V0::VERSION are both 0. + //The only way to differentiate these two versions is by the size of the table. + static const SK_OT_USHORT VERSION = SkTEndian_SwapBE16(0); + + SK_OT_SHORT xAvgCharWidth; + struct WeightClass { + enum Value : SK_OT_USHORT { + UltraLight = SkTEndian_SwapBE16(1), + ExtraLight = SkTEndian_SwapBE16(2), + Light = SkTEndian_SwapBE16(3), + SemiLight = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiBold = SkTEndian_SwapBE16(6), + Bold = SkTEndian_SwapBE16(7), + ExtraBold = SkTEndian_SwapBE16(8), + UltraBold = SkTEndian_SwapBE16(9), + SK_SEQ_END, + } value; + } usWeightClass; + struct WidthClass { + enum Value : SK_OT_USHORT { + UltraCondensed = SkTEndian_SwapBE16(1), + ExtraCondensed = SkTEndian_SwapBE16(2), + Condensed = SkTEndian_SwapBE16(3), + SemiCondensed = SkTEndian_SwapBE16(4), + Medium = SkTEndian_SwapBE16(5), + SemiExpanded = SkTEndian_SwapBE16(6), + Expanded = SkTEndian_SwapBE16(7), + ExtraExpanded = SkTEndian_SwapBE16(8), + UltraExpanded = SkTEndian_SwapBE16(9), + SK_SEQ_END, + } value; + } usWidthClass; + union Type { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Reserved00, + Restricted, + PreviewPrint, + Editable, + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT Installable = 0; + static const SK_OT_USHORT RestrictedMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT PreviewPrintMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT EditableMask = SkOTSetUSHORTBit<3>::value; + SK_OT_USHORT value; + } raw; + } fsType; + SK_OT_SHORT ySubscriptXSize; + SK_OT_SHORT ySubscriptYSize; + SK_OT_SHORT ySubscriptXOffset; + SK_OT_SHORT ySubscriptYOffset; + SK_OT_SHORT ySuperscriptXSize; + SK_OT_SHORT ySuperscriptYSize; + SK_OT_SHORT ySuperscriptXOffset; + SK_OT_SHORT ySuperscriptYOffset; + SK_OT_SHORT yStrikeoutSize; + SK_OT_SHORT yStrikeoutPosition; + SkIBMFamilyClass sFamilyClass; + SkPanose panose; + SK_OT_ULONG ulCharRange[4]; + SK_OT_CHAR achVendID[4]; + union Selection { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Italic, + Underscore, + Negative, + Outlined, + Strikeout, + Bold, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT ItalicMask = SkOTSetUSHORTBit<0>::value; + static const SK_OT_USHORT UnderscoreMask = SkOTSetUSHORTBit<1>::value; + static const SK_OT_USHORT NegativeMask = SkOTSetUSHORTBit<2>::value; + static const SK_OT_USHORT OutlinedMask = SkOTSetUSHORTBit<3>::value; + static const SK_OT_USHORT StrikeoutMask = SkOTSetUSHORTBit<4>::value; + static const SK_OT_USHORT BoldMask = SkOTSetUSHORTBit<5>::value; + SK_OT_USHORT value; + } raw; + } fsSelection; + SK_OT_USHORT usFirstCharIndex; + SK_OT_USHORT usLastCharIndex; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableOS2_VA) == 68, "sizeof_SkOTTableOS2_VA_not_68"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_fvar.h b/gfx/skia/skia/src/sfnt/SkOTTable_fvar.h new file mode 100644 index 0000000000..5720e64d01 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_fvar.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_fvar_DEFINED +#define SkOTTable_fvar_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableFontVariations { + static const SK_OT_CHAR TAG0 = 'f'; + static const SK_OT_CHAR TAG1 = 'v'; + static const SK_OT_CHAR TAG2 = 'a'; + static const SK_OT_CHAR TAG3 = 'r'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_USHORT majorVersion; + SK_OT_USHORT minorVersion; + SK_OT_USHORT offsetToAxesArray; + SK_OT_USHORT reserved; + SK_OT_USHORT axisCount; + SK_OT_USHORT axisSize; // Must be 0x0014 in v1.0 + SK_OT_USHORT instanceCount; + SK_OT_USHORT instanceSize; // Must be axisCount * sizeof(Fixed) + (4 | 6) + + struct VariationAxisRecord { + SK_OT_ULONG axisTag; + SK_OT_Fixed minValue; + SK_OT_Fixed defaultValue; + SK_OT_Fixed maxValue; + SK_OT_USHORT flags; // Must be 0 + SK_OT_USHORT axisNameID; + }; // axes[axisCount]; + + template struct InstanceRecord { + SK_OT_USHORT subfamilyNameID; + SK_OT_USHORT flags; // Must be 0 + SK_OT_Fixed coordinates[AxisCount]; + SK_OT_USHORT postScriptNameID; + }; // instances[instanceCount]; +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTableFontVariations, instanceSize) == 14, "SkOTTableFontVariations_instanceSize_not_at_14"); +static_assert(sizeof(SkOTTableFontVariations) == 16, "sizeof_SkOTTableFontVariations_not_16"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_gasp.h b/gfx/skia/skia/src/sfnt/SkOTTable_gasp.h new file mode 100644 index 0000000000..d99c95130d --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_gasp.h @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_gasp_DEFINED +#define SkOTTable_gasp_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableGridAndScanProcedure { + static const SK_OT_CHAR TAG0 = 'g'; + static const SK_OT_CHAR TAG1 = 'a'; + static const SK_OT_CHAR TAG2 = 's'; + static const SK_OT_CHAR TAG3 = 'p'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_USHORT version; + static const SK_OT_USHORT version0 = SkTEndian_SwapBE16(0); + static const SK_OT_USHORT version1 = SkTEndian_SwapBE16(1); + + SK_OT_USHORT numRanges; + + struct GaspRange { + SK_OT_USHORT maxPPEM; + union behavior { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Gridfit, + DoGray, + SymmetricGridfit, // Version 1 + SymmetricSmoothing, // Version 1 + Reserved04, + Reserved05, + Reserved06, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT GridfitMask = SkTEndian_SwapBE16(1 << 0); + static const SK_OT_USHORT DoGrayMask = SkTEndian_SwapBE16(1 << 1); + static const SK_OT_USHORT SymmetricGridfitMask = SkTEndian_SwapBE16(1 << 2); + static const SK_OT_USHORT SymmetricSmoothingMask = SkTEndian_SwapBE16(1 << 3); + SK_OT_USHORT value; + } raw; + } flags; + }; //gaspRange[numRanges] +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTableGridAndScanProcedure, numRanges) == 2, "SkOTTableGridAndScanProcedure_numRanges_not_at_2"); +static_assert(sizeof(SkOTTableGridAndScanProcedure) == 4, "sizeof_SkOTTableGridAndScanProcedure_not_4"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_glyf.h b/gfx/skia/skia/src/sfnt/SkOTTable_glyf.h new file mode 100644 index 0000000000..98c4b2021d --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_glyf.h @@ -0,0 +1,213 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_glyf_DEFINED +#define SkOTTable_glyf_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_head.h" +#include "src/sfnt/SkOTTable_loca.h" + +#pragma pack(push, 1) + +struct SkOTTableGlyphData; + +extern uint8_t const * const SK_OT_GlyphData_NoOutline; + +struct SkOTTableGlyph { + static const SK_OT_CHAR TAG0 = 'g'; + static const SK_OT_CHAR TAG1 = 'l'; + static const SK_OT_CHAR TAG2 = 'y'; + static const SK_OT_CHAR TAG3 = 'f'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + class Iterator { + public: + Iterator(const SkOTTableGlyph& glyf, + const SkOTTableIndexToLocation& loca, + SkOTTableHead::IndexToLocFormat locaFormat) + : fGlyf(glyf) + , fLocaFormat(SkOTTableHead::IndexToLocFormat::ShortOffsets == locaFormat.value ? 0 : 1) + , fCurrentGlyphOffset(0) + { fLocaPtr.shortOffset = reinterpret_cast(&loca); } + + void advance(uint16_t num) { + fLocaPtr.shortOffset += num << fLocaFormat; + fCurrentGlyphOffset = fLocaFormat ? SkEndian_SwapBE32(*fLocaPtr.longOffset) + : uint32_t(SkEndian_SwapBE16(*fLocaPtr.shortOffset) << 1); + } + const SkOTTableGlyphData* next() { + uint32_t previousGlyphOffset = fCurrentGlyphOffset; + advance(1); + if (previousGlyphOffset == fCurrentGlyphOffset) { + return reinterpret_cast(&SK_OT_GlyphData_NoOutline); + } else { + return reinterpret_cast( + reinterpret_cast(&fGlyf) + previousGlyphOffset + ); + } + } + private: + const SkOTTableGlyph& fGlyf; + uint16_t fLocaFormat; //0 or 1 + uint32_t fCurrentGlyphOffset; + union LocaPtr { + const SK_OT_USHORT* shortOffset; + const SK_OT_ULONG* longOffset; + } fLocaPtr; + }; +}; + +struct SkOTTableGlyphData { + SK_OT_SHORT numberOfContours; //== -1 Composite, > 0 Simple + SK_OT_FWORD xMin; + SK_OT_FWORD yMin; + SK_OT_FWORD xMax; + SK_OT_FWORD yMax; + + struct Simple { + SK_OT_USHORT endPtsOfContours[1/*numberOfContours*/]; + + struct Instructions { + SK_OT_USHORT length; + SK_OT_BYTE data[1/*length*/]; + }; + + union Flags { + struct Field { + SK_OT_BYTE_BITFIELD( + OnCurve, + xShortVector, + yShortVector, + Repeat, + xIsSame_xShortVectorPositive, + yIsSame_yShortVectorPositive, + Reserved6, + Reserved7) + } field; + struct Raw { + static const SK_OT_USHORT OnCurveMask = SkTEndian_SwapBE16(1 << 0); + static const SK_OT_USHORT xShortVectorMask = SkTEndian_SwapBE16(1 << 1); + static const SK_OT_USHORT yShortVectorMask = SkTEndian_SwapBE16(1 << 2); + static const SK_OT_USHORT RepeatMask = SkTEndian_SwapBE16(1 << 3); + static const SK_OT_USHORT xIsSame_xShortVectorPositiveMask = SkTEndian_SwapBE16(1 << 4); + static const SK_OT_USHORT yIsSame_yShortVectorPositiveMask = SkTEndian_SwapBE16(1 << 5); + SK_OT_BYTE value; + } raw; + }; + + //xCoordinates + //yCoordinates + }; + + struct Composite { + struct Component { + union Flags { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + WE_HAVE_INSTRUCTIONS, + USE_MY_METRICS, + OVERLAP_COMPOUND, + SCALED_COMPONENT_OFFSET, + UNSCALED_COMPONENT_OFFSET, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + ARG_1_AND_2_ARE_WORDS, + ARGS_ARE_XY_VALUES, + ROUND_XY_TO_GRID, + WE_HAVE_A_SCALE, + RESERVED, + MORE_COMPONENTS, + WE_HAVE_AN_X_AND_Y_SCALE, + WE_HAVE_A_TWO_BY_TWO) + } field; + struct Raw { + static const SK_OT_USHORT ARG_1_AND_2_ARE_WORDS_Mask = SkTEndian_SwapBE16(1 << 0); + static const SK_OT_USHORT ARGS_ARE_XY_VALUES_Mask = SkTEndian_SwapBE16(1 << 1); + static const SK_OT_USHORT ROUND_XY_TO_GRID_Mask = SkTEndian_SwapBE16(1 << 2); + static const SK_OT_USHORT WE_HAVE_A_SCALE_Mask = SkTEndian_SwapBE16(1 << 3); + static const SK_OT_USHORT RESERVED_Mask = SkTEndian_SwapBE16(1 << 4); + static const SK_OT_USHORT MORE_COMPONENTS_Mask = SkTEndian_SwapBE16(1 << 5); + static const SK_OT_USHORT WE_HAVE_AN_X_AND_Y_SCALE_Mask = SkTEndian_SwapBE16(1 << 6); + static const SK_OT_USHORT WE_HAVE_A_TWO_BY_TWO_Mask = SkTEndian_SwapBE16(1 << 7); + + static const SK_OT_USHORT WE_HAVE_INSTRUCTIONS_Mask = SkTEndian_SwapBE16(1 << 8); + static const SK_OT_USHORT USE_MY_METRICS_Mask = SkTEndian_SwapBE16(1 << 9); + static const SK_OT_USHORT OVERLAP_COMPOUND_Mask = SkTEndian_SwapBE16(1 << 10); + static const SK_OT_USHORT SCALED_COMPONENT_OFFSET_Mask = SkTEndian_SwapBE16(1 << 11); + static const SK_OT_USHORT UNSCALED_COMPONENT_OFFSET_mask = SkTEndian_SwapBE16(1 << 12); + //Reserved + //Reserved + //Reserved + SK_OT_USHORT value; + } raw; + } flags; + SK_OT_USHORT glyphIndex; + union Transform { + union Matrix { + /** !WE_HAVE_A_SCALE & !WE_HAVE_AN_X_AND_Y_SCALE & !WE_HAVE_A_TWO_BY_TWO */ + struct None { } none; + /** WE_HAVE_A_SCALE */ + struct Scale { + SK_OT_F2DOT14 a_d; + } scale; + /** WE_HAVE_AN_X_AND_Y_SCALE */ + struct ScaleXY { + SK_OT_F2DOT14 a; + SK_OT_F2DOT14 d; + } scaleXY; + /** WE_HAVE_A_TWO_BY_TWO */ + struct TwoByTwo { + SK_OT_F2DOT14 a; + SK_OT_F2DOT14 b; + SK_OT_F2DOT14 c; + SK_OT_F2DOT14 d; + } twoByTwo; + }; + /** ARG_1_AND_2_ARE_WORDS & ARGS_ARE_XY_VALUES */ + struct WordValue { + SK_OT_FWORD e; + SK_OT_FWORD f; + SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix; + } wordValue; + /** !ARG_1_AND_2_ARE_WORDS & ARGS_ARE_XY_VALUES */ + struct ByteValue { + SK_OT_CHAR e; + SK_OT_CHAR f; + SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix; + } byteValue; + /** ARG_1_AND_2_ARE_WORDS & !ARGS_ARE_XY_VALUES */ + struct WordIndex { + SK_OT_USHORT compoundPointIndex; + SK_OT_USHORT componentPointIndex; + SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix; + } wordIndex; + /** !ARG_1_AND_2_ARE_WORDS & !ARGS_ARE_XY_VALUES */ + struct ByteIndex { + SK_OT_BYTE compoundPointIndex; + SK_OT_BYTE componentPointIndex; + SkOTTableGlyphData::Composite::Component::Transform::Matrix matrix; + } byteIndex; + } transform; + } component;//[] last element does not set MORE_COMPONENTS + + /** Comes after the last Component if the last component has WE_HAVE_INSTR. */ + struct Instructions { + SK_OT_USHORT length; + SK_OT_BYTE data[1/*length*/]; + }; + }; +}; + +#pragma pack(pop) + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_head.h b/gfx/skia/skia/src/sfnt/SkOTTable_head.h new file mode 100644 index 0000000000..71a443ebfc --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_head.h @@ -0,0 +1,146 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_head_DEFINED +#define SkOTTable_head_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableHead { + static const SK_OT_CHAR TAG0 = 'h'; + static const SK_OT_CHAR TAG1 = 'e'; + static const SK_OT_CHAR TAG2 = 'a'; + static const SK_OT_CHAR TAG3 = 'd'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_Fixed version; + static const SK_OT_Fixed version1 = SkTEndian_SwapBE32(0x00010000); + SK_OT_Fixed fontRevision; + static const uint32_t fontChecksum = 0xB1B0AFBA; //checksum of all TT fonts + SK_OT_ULONG checksumAdjustment; + SK_OT_ULONG magicNumber; + static const SK_OT_ULONG magicNumberConst = SkTEndian_SwapBE32(0x5F0F3CF5); + union Flags { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + GXMetamorphosis_Apple, + HasStrongRTL_Apple, + HasIndicStyleRearrangement, + AgfaMicroTypeExpressProcessed, + FontConverted, + DesignedForClearType, + LastResort, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + BaselineAtY0, + LeftSidebearingAtX0, + InstructionsDependOnPointSize, + IntegerScaling, + InstructionsAlterAdvanceWidth, + VerticalCenteredGlyphs_Apple, + Reserved06, + RequiresLayout_Apple) + } field; + struct Raw { + static const SK_OT_USHORT BaselineAtY0Mask = SkTEndian_SwapBE16(1 << 0); + static const SK_OT_USHORT LeftSidebearingAtX0Mask = SkTEndian_SwapBE16(1 << 1); + static const SK_OT_USHORT InstructionsDependOnPointSizeMask = SkTEndian_SwapBE16(1 << 2); + static const SK_OT_USHORT IntegerScalingMask = SkTEndian_SwapBE16(1 << 3); + static const SK_OT_USHORT InstructionsAlterAdvanceWidthMask = SkTEndian_SwapBE16(1 << 4); + static const SK_OT_USHORT VerticalCenteredGlyphs_AppleMask = SkTEndian_SwapBE16(1 << 5); + //Reserved + static const SK_OT_USHORT RequiresLayout_AppleMask = SkTEndian_SwapBE16(1 << 7); + + static const SK_OT_USHORT GXMetamorphosis_AppleMask = SkTEndian_SwapBE16(1 << 8); + static const SK_OT_USHORT HasStrongRTL_AppleMask = SkTEndian_SwapBE16(1 << 9); + static const SK_OT_USHORT HasIndicStyleRearrangementMask = SkTEndian_SwapBE16(1 << 10); + static const SK_OT_USHORT AgfaMicroTypeExpressProcessedMask = SkTEndian_SwapBE16(1 << 11); + static const SK_OT_USHORT FontConvertedMask = SkTEndian_SwapBE16(1 << 12); + static const SK_OT_USHORT DesignedForClearTypeMask = SkTEndian_SwapBE16(1 << 13); + static const SK_OT_USHORT LastResortMask = SkTEndian_SwapBE16(1 << 14); + //Reserved + SK_OT_USHORT value; + } raw; + } flags; + SK_OT_USHORT unitsPerEm; + SK_OT_LONGDATETIME created; + SK_OT_LONGDATETIME modified; + SK_OT_SHORT xMin; + SK_OT_SHORT yMin; + SK_OT_SHORT xMax; + SK_OT_SHORT yMax; + union MacStyle { + struct Field { + //8-15 + SK_OT_BYTE_BITFIELD( + Reserved08, + Reserved09, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15) + //0-7 + SK_OT_BYTE_BITFIELD( + Bold, + Italic, + Underline, + Outline, + Shadow, + Condensed, + Extended, + Reserved07) + } field; + struct Raw { + static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1); + static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 1); + static const SK_OT_USHORT UnderlineMask = SkTEndian_SwapBE16(1 << 2); + static const SK_OT_USHORT OutlineMask = SkTEndian_SwapBE16(1 << 3); + static const SK_OT_USHORT ShadowMask = SkTEndian_SwapBE16(1 << 4); + static const SK_OT_USHORT CondensedMask = SkTEndian_SwapBE16(1 << 5); + static const SK_OT_USHORT ExtendedMask = SkTEndian_SwapBE16(1 << 6); + + SK_OT_USHORT value; + } raw; + } macStyle; + SK_OT_USHORT lowestRecPPEM; + struct FontDirectionHint { + enum Value : SK_OT_SHORT { + FullyMixedDirectionalGlyphs = SkTEndian_SwapBE16(0), + OnlyStronglyLTR = SkTEndian_SwapBE16(1), + StronglyLTR = SkTEndian_SwapBE16(2), + OnlyStronglyRTL = static_cast(SkTEndian_SwapBE16((uint16_t)-1)), + StronglyRTL = static_cast(SkTEndian_SwapBE16((uint16_t)-2)), + } value; + } fontDirectionHint; + struct IndexToLocFormat { + enum Value : SK_OT_SHORT { + ShortOffsets = SkTEndian_SwapBE16(0), + LongOffsets = SkTEndian_SwapBE16(1), + } value; + } indexToLocFormat; + struct GlyphDataFormat { + enum Value : SK_OT_SHORT { + CurrentFormat = SkTEndian_SwapBE16(0), + } value; + } glyphDataFormat; +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTableHead, glyphDataFormat) == 52, "SkOTTableHead_glyphDataFormat_not_at_52"); +static_assert(sizeof(SkOTTableHead) == 54, "sizeof_SkOTTableHead_not_54"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_hhea.h b/gfx/skia/skia/src/sfnt/SkOTTable_hhea.h new file mode 100644 index 0000000000..1044e79319 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_hhea.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_hhea_DEFINED +#define SkOTTable_hhea_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableHorizontalHeader { + static const SK_OT_CHAR TAG0 = 'h'; + static const SK_OT_CHAR TAG1 = 'h'; + static const SK_OT_CHAR TAG2 = 'e'; + static const SK_OT_CHAR TAG3 = 'a'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_Fixed version; + static const SK_OT_Fixed version1 = SkTEndian_SwapBE32(0x00010000); + SK_OT_FWORD Ascender; + SK_OT_FWORD Descender; + SK_OT_FWORD LineGap; + SK_OT_UFWORD advanceWidthMax; + SK_OT_FWORD minLeftSideBearing; + SK_OT_FWORD minRightSideBearing; + SK_OT_FWORD xMaxExtent; + SK_OT_SHORT caretSlopeRise; + SK_OT_SHORT caretSlopeRun; + SK_OT_SHORT caretOffset; + SK_OT_SHORT Reserved24; + SK_OT_SHORT Reserved26; + SK_OT_SHORT Reserved28; + SK_OT_SHORT Reserved30; + struct MetricDataFormat { + enum Value : SK_OT_SHORT { + CurrentFormat = SkTEndian_SwapBE16(0), + } value; + } metricDataFormat; + SK_OT_USHORT numberOfHMetrics; +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTableHorizontalHeader, numberOfHMetrics) == 34, "SkOTTableHorizontalHeader_numberOfHMetrics_not_at_34"); +static_assert(sizeof(SkOTTableHorizontalHeader) == 36, "sizeof_SkOTTableHorizontalHeader_not_36"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_loca.h b/gfx/skia/skia/src/sfnt/SkOTTable_loca.h new file mode 100644 index 0000000000..98df2833e4 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_loca.h @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_loca_DEFINED +#define SkOTTable_loca_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableIndexToLocation { + static const SK_OT_CHAR TAG0 = 'l'; + static const SK_OT_CHAR TAG1 = 'o'; + static const SK_OT_CHAR TAG2 = 'c'; + static const SK_OT_CHAR TAG3 = 'a'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + union Offsets { + SK_OT_USHORT shortOffset[1]; + SK_OT_ULONG longOffset[1]; + } offsets; +}; + +#pragma pack(pop) + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_maxp.h b/gfx/skia/skia/src/sfnt/SkOTTable_maxp.h new file mode 100644 index 0000000000..aaae28a9e3 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_maxp.h @@ -0,0 +1,34 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_maxp_DEFINED +#define SkOTTable_maxp_DEFINED + +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_maxp_CFF.h" +#include "src/sfnt/SkOTTable_maxp_TT.h" + +#pragma pack(push, 1) + +struct SkOTTableMaximumProfile { + static const SK_OT_CHAR TAG0 = 'm'; + static const SK_OT_CHAR TAG1 = 'a'; + static const SK_OT_CHAR TAG2 = 'x'; + static const SK_OT_CHAR TAG3 = 'p'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + union Version { + SK_OT_Fixed version; + + struct CFF : SkOTTableMaximumProfile_CFF { } cff; + struct TT : SkOTTableMaximumProfile_TT { } tt; + } version; +}; + +#pragma pack(pop) + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_maxp_CFF.h b/gfx/skia/skia/src/sfnt/SkOTTable_maxp_CFF.h new file mode 100644 index 0000000000..bf69beda7c --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_maxp_CFF.h @@ -0,0 +1,30 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_maxp_CFF_DEFINED +#define SkOTTable_maxp_CFF_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableMaximumProfile_CFF { + SK_OT_Fixed version; + static const SK_OT_Fixed VERSION = SkTEndian_SwapBE32(0x00005000); + + SK_OT_USHORT numGlyphs; +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTableMaximumProfile_CFF, numGlyphs) == 4, "SkOTTableMaximumProfile_CFF_numGlyphs_not_at_4"); +static_assert(sizeof(SkOTTableMaximumProfile_CFF) == 6, "sizeof_SkOTTableMaximumProfile_CFF_not_6"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_maxp_TT.h b/gfx/skia/skia/src/sfnt/SkOTTable_maxp_TT.h new file mode 100644 index 0000000000..53b7a70104 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_maxp_TT.h @@ -0,0 +1,48 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_maxp_TT_DEFINED +#define SkOTTable_maxp_TT_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableMaximumProfile_TT { + SK_OT_Fixed version; + static const SK_OT_Fixed VERSION = SkTEndian_SwapBE32(0x00010000); + + SK_OT_USHORT numGlyphs; + SK_OT_USHORT maxPoints; + SK_OT_USHORT maxContours; + SK_OT_USHORT maxCompositePoints; + SK_OT_USHORT maxCompositeContours; + struct MaxZones { + enum Value : SK_OT_USHORT { + DoesNotUseTwilightZone = SkTEndian_SwapBE16(1), + UsesTwilightZone = SkTEndian_SwapBE16(2), + } value; + } maxZones; + SK_OT_USHORT maxTwilightPoints; + SK_OT_USHORT maxStorage; + SK_OT_USHORT maxFunctionDefs; + SK_OT_USHORT maxInstructionDefs; + SK_OT_USHORT maxStackElements; + SK_OT_USHORT maxSizeOfInstructions; + SK_OT_USHORT maxComponentElements; + SK_OT_USHORT maxComponentDepth; +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTableMaximumProfile_TT, maxComponentDepth) == 30, "SkOTTableMaximumProfile_TT_maxComponentDepth_not_at_30"); +static_assert(sizeof(SkOTTableMaximumProfile_TT) == 32, "sizeof_SkOTTableMaximumProfile_TT_not_32"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_name.cpp b/gfx/skia/skia/src/sfnt/SkOTTable_name.cpp new file mode 100644 index 0000000000..185c789f90 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_name.cpp @@ -0,0 +1,586 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sfnt/SkOTTable_name.h" + +#include "include/private/SkTemplates.h" +#include "src/core/SkEndian.h" +#include "src/core/SkStringUtils.h" +#include "src/core/SkTSearch.h" +#include "src/core/SkUtils.h" + +static SkUnichar next_unichar_UTF16BE(const uint8_t** srcPtr, size_t* length) { + SkASSERT(srcPtr && *srcPtr && length); + SkASSERT(*length > 0); + + uint16_t leading; + if (*length < sizeof(leading)) { + *length = 0; + return 0xFFFD; + } + memcpy(&leading, *srcPtr, sizeof(leading)); + *srcPtr += sizeof(leading); + *length -= sizeof(leading); + SkUnichar c = SkEndian_SwapBE16(leading); + + if (SkUTF16_IsTrailingSurrogate(c)) { + return 0xFFFD; + } + if (SkUTF16_IsLeadingSurrogate(c)) { + uint16_t trailing; + if (*length < sizeof(trailing)) { + *length = 0; + return 0xFFFD; + } + memcpy(&trailing, *srcPtr, sizeof(trailing)); + SkUnichar c2 = SkEndian_SwapBE16(trailing); + if (!SkUTF16_IsTrailingSurrogate(c2)) { + return 0xFFFD; + } + *srcPtr += sizeof(trailing); + *length -= sizeof(trailing); + + c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); + } + return c; +} + +static void SkString_from_UTF16BE(const uint8_t* utf16be, size_t length, SkString& utf8) { + // Note that utf16be may not be 2-byte aligned. + SkASSERT(utf16be != nullptr); + + utf8.reset(); + while (length) { + utf8.appendUnichar(next_unichar_UTF16BE(&utf16be, &length)); + } +} + +/** UnicodeFromMacRoman[macRomanPoint - 0x80] -> unicodeCodePoint. + * Derived from http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT . + * In MacRoman the first 128 code points match ASCII code points. + * This maps the second 128 MacRoman code points to unicode code points. + */ +static const uint16_t UnicodeFromMacRoman[0x80] = { + 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, + 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, + 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, + 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, + 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, + 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, + 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, + 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, + 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, + 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, + 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, + 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, + 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, + 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, + 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, +}; + +static void SkStringFromMacRoman(const uint8_t* macRoman, size_t length, SkString& utf8) { + utf8.reset(); + for (size_t i = 0; i < length; ++i) { + utf8.appendUnichar(macRoman[i] < 0x80 ? macRoman[i] + : UnicodeFromMacRoman[macRoman[i] - 0x80]); + } +} + +static const struct BCP47FromLanguageId { + uint16_t languageID; + const char* bcp47; +} +/** The Mac and Windows values do not conflict, so this is currently one single table. */ +BCP47FromLanguageID[] = { + /** A mapping from Mac Language Designators to BCP 47 codes. + * The following list was constructed more or less manually. + * Apple now uses BCP 47 (post OSX10.4), so there will be no new entries. + */ + {0, "en"}, //English + {1, "fr"}, //French + {2, "de"}, //German + {3, "it"}, //Italian + {4, "nl"}, //Dutch + {5, "sv"}, //Swedish + {6, "es"}, //Spanish + {7, "da"}, //Danish + {8, "pt"}, //Portuguese + {9, "nb"}, //Norwegian + {10, "he"}, //Hebrew + {11, "ja"}, //Japanese + {12, "ar"}, //Arabic + {13, "fi"}, //Finnish + {14, "el"}, //Greek + {15, "is"}, //Icelandic + {16, "mt"}, //Maltese + {17, "tr"}, //Turkish + {18, "hr"}, //Croatian + {19, "zh-Hant"}, //Chinese (Traditional) + {20, "ur"}, //Urdu + {21, "hi"}, //Hindi + {22, "th"}, //Thai + {23, "ko"}, //Korean + {24, "lt"}, //Lithuanian + {25, "pl"}, //Polish + {26, "hu"}, //Hungarian + {27, "et"}, //Estonian + {28, "lv"}, //Latvian + {29, "se"}, //Sami + {30, "fo"}, //Faroese + {31, "fa"}, //Farsi (Persian) + {32, "ru"}, //Russian + {33, "zh-Hans"}, //Chinese (Simplified) + {34, "nl"}, //Dutch + {35, "ga"}, //Irish(Gaelic) + {36, "sq"}, //Albanian + {37, "ro"}, //Romanian + {38, "cs"}, //Czech + {39, "sk"}, //Slovak + {40, "sl"}, //Slovenian + {41, "yi"}, //Yiddish + {42, "sr"}, //Serbian + {43, "mk"}, //Macedonian + {44, "bg"}, //Bulgarian + {45, "uk"}, //Ukrainian + {46, "be"}, //Byelorussian + {47, "uz"}, //Uzbek + {48, "kk"}, //Kazakh + {49, "az-Cyrl"}, //Azerbaijani (Cyrillic) + {50, "az-Arab"}, //Azerbaijani (Arabic) + {51, "hy"}, //Armenian + {52, "ka"}, //Georgian + {53, "mo"}, //Moldavian + {54, "ky"}, //Kirghiz + {55, "tg"}, //Tajiki + {56, "tk"}, //Turkmen + {57, "mn-Mong"}, //Mongolian (Traditional) + {58, "mn-Cyrl"}, //Mongolian (Cyrillic) + {59, "ps"}, //Pashto + {60, "ku"}, //Kurdish + {61, "ks"}, //Kashmiri + {62, "sd"}, //Sindhi + {63, "bo"}, //Tibetan + {64, "ne"}, //Nepali + {65, "sa"}, //Sanskrit + {66, "mr"}, //Marathi + {67, "bn"}, //Bengali + {68, "as"}, //Assamese + {69, "gu"}, //Gujarati + {70, "pa"}, //Punjabi + {71, "or"}, //Oriya + {72, "ml"}, //Malayalam + {73, "kn"}, //Kannada + {74, "ta"}, //Tamil + {75, "te"}, //Telugu + {76, "si"}, //Sinhalese + {77, "my"}, //Burmese + {78, "km"}, //Khmer + {79, "lo"}, //Lao + {80, "vi"}, //Vietnamese + {81, "id"}, //Indonesian + {82, "tl"}, //Tagalog + {83, "ms-Latn"}, //Malay (Roman) + {84, "ms-Arab"}, //Malay (Arabic) + {85, "am"}, //Amharic + {86, "ti"}, //Tigrinya + {87, "om"}, //Oromo + {88, "so"}, //Somali + {89, "sw"}, //Swahili + {90, "rw"}, //Kinyarwanda/Ruanda + {91, "rn"}, //Rundi + {92, "ny"}, //Nyanja/Chewa + {93, "mg"}, //Malagasy + {94, "eo"}, //Esperanto + {128, "cy"}, //Welsh + {129, "eu"}, //Basque + {130, "ca"}, //Catalan + {131, "la"}, //Latin + {132, "qu"}, //Quechua + {133, "gn"}, //Guarani + {134, "ay"}, //Aymara + {135, "tt"}, //Tatar + {136, "ug"}, //Uighur + {137, "dz"}, //Dzongkha + {138, "jv-Latn"}, //Javanese (Roman) + {139, "su-Latn"}, //Sundanese (Roman) + {140, "gl"}, //Galician + {141, "af"}, //Afrikaans + {142, "br"}, //Breton + {143, "iu"}, //Inuktitut + {144, "gd"}, //Scottish (Gaelic) + {145, "gv"}, //Manx (Gaelic) + {146, "ga"}, //Irish (Gaelic with Lenition) + {147, "to"}, //Tongan + {148, "el"}, //Greek (Polytonic) Note: ISO 15924 does not have an equivalent script name. + {149, "kl"}, //Greenlandic + {150, "az-Latn"}, //Azerbaijani (Roman) + {151, "nn"}, //Nynorsk + + /** A mapping from Windows LCID to BCP 47 codes. + * This list is the sorted, curated output of tools/win_lcid.cpp. + * Note that these are sorted by value for quick binary lookup, and not logically by lsb. + * The 'bare' language ids (e.g. 0x0001 for Arabic) are ommitted + * as they do not appear as valid language ids in the OpenType specification. + */ + { 0x0401, "ar-SA" }, //Arabic + { 0x0402, "bg-BG" }, //Bulgarian + { 0x0403, "ca-ES" }, //Catalan + { 0x0404, "zh-TW" }, //Chinese (Traditional) + { 0x0405, "cs-CZ" }, //Czech + { 0x0406, "da-DK" }, //Danish + { 0x0407, "de-DE" }, //German + { 0x0408, "el-GR" }, //Greek + { 0x0409, "en-US" }, //English + { 0x040a, "es-ES_tradnl" }, //Spanish + { 0x040b, "fi-FI" }, //Finnish + { 0x040c, "fr-FR" }, //French + { 0x040d, "he-IL" }, //Hebrew + { 0x040d, "he" }, //Hebrew + { 0x040e, "hu-HU" }, //Hungarian + { 0x040e, "hu" }, //Hungarian + { 0x040f, "is-IS" }, //Icelandic + { 0x0410, "it-IT" }, //Italian + { 0x0411, "ja-JP" }, //Japanese + { 0x0412, "ko-KR" }, //Korean + { 0x0413, "nl-NL" }, //Dutch + { 0x0414, "nb-NO" }, //Norwegian (Bokmål) + { 0x0415, "pl-PL" }, //Polish + { 0x0416, "pt-BR" }, //Portuguese + { 0x0417, "rm-CH" }, //Romansh + { 0x0418, "ro-RO" }, //Romanian + { 0x0419, "ru-RU" }, //Russian + { 0x041a, "hr-HR" }, //Croatian + { 0x041b, "sk-SK" }, //Slovak + { 0x041c, "sq-AL" }, //Albanian + { 0x041d, "sv-SE" }, //Swedish + { 0x041e, "th-TH" }, //Thai + { 0x041f, "tr-TR" }, //Turkish + { 0x0420, "ur-PK" }, //Urdu + { 0x0421, "id-ID" }, //Indonesian + { 0x0422, "uk-UA" }, //Ukrainian + { 0x0423, "be-BY" }, //Belarusian + { 0x0424, "sl-SI" }, //Slovenian + { 0x0425, "et-EE" }, //Estonian + { 0x0426, "lv-LV" }, //Latvian + { 0x0427, "lt-LT" }, //Lithuanian + { 0x0428, "tg-Cyrl-TJ" }, //Tajik (Cyrillic) + { 0x0429, "fa-IR" }, //Persian + { 0x042a, "vi-VN" }, //Vietnamese + { 0x042b, "hy-AM" }, //Armenian + { 0x042c, "az-Latn-AZ" }, //Azeri (Latin) + { 0x042d, "eu-ES" }, //Basque + { 0x042e, "hsb-DE" }, //Upper Sorbian + { 0x042f, "mk-MK" }, //Macedonian (FYROM) + { 0x0432, "tn-ZA" }, //Setswana + { 0x0434, "xh-ZA" }, //isiXhosa + { 0x0435, "zu-ZA" }, //isiZulu + { 0x0436, "af-ZA" }, //Afrikaans + { 0x0437, "ka-GE" }, //Georgian + { 0x0438, "fo-FO" }, //Faroese + { 0x0439, "hi-IN" }, //Hindi + { 0x043a, "mt-MT" }, //Maltese + { 0x043b, "se-NO" }, //Sami (Northern) + { 0x043e, "ms-MY" }, //Malay + { 0x043f, "kk-KZ" }, //Kazakh + { 0x0440, "ky-KG" }, //Kyrgyz + { 0x0441, "sw-KE" }, //Kiswahili + { 0x0442, "tk-TM" }, //Turkmen + { 0x0443, "uz-Latn-UZ" }, //Uzbek (Latin) + { 0x0443, "uz" }, //Uzbek + { 0x0444, "tt-RU" }, //Tatar + { 0x0445, "bn-IN" }, //Bengali + { 0x0446, "pa-IN" }, //Punjabi + { 0x0447, "gu-IN" }, //Gujarati + { 0x0448, "or-IN" }, //Oriya + { 0x0449, "ta-IN" }, //Tamil + { 0x044a, "te-IN" }, //Telugu + { 0x044b, "kn-IN" }, //Kannada + { 0x044c, "ml-IN" }, //Malayalam + { 0x044d, "as-IN" }, //Assamese + { 0x044e, "mr-IN" }, //Marathi + { 0x044f, "sa-IN" }, //Sanskrit + { 0x0450, "mn-Cyrl" }, //Mongolian (Cyrillic) + { 0x0451, "bo-CN" }, //Tibetan + { 0x0452, "cy-GB" }, //Welsh + { 0x0453, "km-KH" }, //Khmer + { 0x0454, "lo-LA" }, //Lao + { 0x0456, "gl-ES" }, //Galician + { 0x0457, "kok-IN" }, //Konkani + { 0x045a, "syr-SY" }, //Syriac + { 0x045b, "si-LK" }, //Sinhala + { 0x045d, "iu-Cans-CA" }, //Inuktitut (Syllabics) + { 0x045e, "am-ET" }, //Amharic + { 0x0461, "ne-NP" }, //Nepali + { 0x0462, "fy-NL" }, //Frisian + { 0x0463, "ps-AF" }, //Pashto + { 0x0464, "fil-PH" }, //Filipino + { 0x0465, "dv-MV" }, //Divehi + { 0x0468, "ha-Latn-NG" }, //Hausa (Latin) + { 0x046a, "yo-NG" }, //Yoruba + { 0x046b, "quz-BO" }, //Quechua + { 0x046c, "nso-ZA" }, //Sesotho sa Leboa + { 0x046d, "ba-RU" }, //Bashkir + { 0x046e, "lb-LU" }, //Luxembourgish + { 0x046f, "kl-GL" }, //Greenlandic + { 0x0470, "ig-NG" }, //Igbo + { 0x0478, "ii-CN" }, //Yi + { 0x047a, "arn-CL" }, //Mapudungun + { 0x047c, "moh-CA" }, //Mohawk + { 0x047e, "br-FR" }, //Breton + { 0x0480, "ug-CN" }, //Uyghur + { 0x0481, "mi-NZ" }, //Maori + { 0x0482, "oc-FR" }, //Occitan + { 0x0483, "co-FR" }, //Corsican + { 0x0484, "gsw-FR" }, //Alsatian + { 0x0485, "sah-RU" }, //Yakut + { 0x0486, "qut-GT" }, //K'iche + { 0x0487, "rw-RW" }, //Kinyarwanda + { 0x0488, "wo-SN" }, //Wolof + { 0x048c, "prs-AF" }, //Dari + { 0x0491, "gd-GB" }, //Scottish Gaelic + { 0x0801, "ar-IQ" }, //Arabic + { 0x0804, "zh-Hans" }, //Chinese (Simplified) + { 0x0807, "de-CH" }, //German + { 0x0809, "en-GB" }, //English + { 0x080a, "es-MX" }, //Spanish + { 0x080c, "fr-BE" }, //French + { 0x0810, "it-CH" }, //Italian + { 0x0813, "nl-BE" }, //Dutch + { 0x0814, "nn-NO" }, //Norwegian (Nynorsk) + { 0x0816, "pt-PT" }, //Portuguese + { 0x081a, "sr-Latn-CS" }, //Serbian (Latin) + { 0x081d, "sv-FI" }, //Swedish + { 0x082c, "az-Cyrl-AZ" }, //Azeri (Cyrillic) + { 0x082e, "dsb-DE" }, //Lower Sorbian + { 0x082e, "dsb" }, //Lower Sorbian + { 0x083b, "se-SE" }, //Sami (Northern) + { 0x083c, "ga-IE" }, //Irish + { 0x083e, "ms-BN" }, //Malay + { 0x0843, "uz-Cyrl-UZ" }, //Uzbek (Cyrillic) + { 0x0845, "bn-BD" }, //Bengali + { 0x0850, "mn-Mong-CN" }, //Mongolian (Traditional Mongolian) + { 0x085d, "iu-Latn-CA" }, //Inuktitut (Latin) + { 0x085f, "tzm-Latn-DZ" }, //Tamazight (Latin) + { 0x086b, "quz-EC" }, //Quechua + { 0x0c01, "ar-EG" }, //Arabic + { 0x0c04, "zh-Hant" }, //Chinese (Traditional) + { 0x0c07, "de-AT" }, //German + { 0x0c09, "en-AU" }, //English + { 0x0c0a, "es-ES" }, //Spanish + { 0x0c0c, "fr-CA" }, //French + { 0x0c1a, "sr-Cyrl-CS" }, //Serbian (Cyrillic) + { 0x0c3b, "se-FI" }, //Sami (Northern) + { 0x0c6b, "quz-PE" }, //Quechua + { 0x1001, "ar-LY" }, //Arabic + { 0x1004, "zh-SG" }, //Chinese (Simplified) + { 0x1007, "de-LU" }, //German + { 0x1009, "en-CA" }, //English + { 0x100a, "es-GT" }, //Spanish + { 0x100c, "fr-CH" }, //French + { 0x101a, "hr-BA" }, //Croatian (Latin) + { 0x103b, "smj-NO" }, //Sami (Lule) + { 0x1401, "ar-DZ" }, //Arabic + { 0x1404, "zh-MO" }, //Chinese (Traditional) + { 0x1407, "de-LI" }, //German + { 0x1409, "en-NZ" }, //English + { 0x140a, "es-CR" }, //Spanish + { 0x140c, "fr-LU" }, //French + { 0x141a, "bs-Latn-BA" }, //Bosnian (Latin) + { 0x141a, "bs" }, //Bosnian + { 0x143b, "smj-SE" }, //Sami (Lule) + { 0x143b, "smj" }, //Sami (Lule) + { 0x1801, "ar-MA" }, //Arabic + { 0x1809, "en-IE" }, //English + { 0x180a, "es-PA" }, //Spanish + { 0x180c, "fr-MC" }, //French + { 0x181a, "sr-Latn-BA" }, //Serbian (Latin) + { 0x183b, "sma-NO" }, //Sami (Southern) + { 0x1c01, "ar-TN" }, //Arabic + { 0x1c09, "en-ZA" }, //English + { 0x1c0a, "es-DO" }, //Spanish + { 0x1c1a, "sr-Cyrl-BA" }, //Serbian (Cyrillic) + { 0x1c3b, "sma-SE" }, //Sami (Southern) + { 0x1c3b, "sma" }, //Sami (Southern) + { 0x2001, "ar-OM" }, //Arabic + { 0x2009, "en-JM" }, //English + { 0x200a, "es-VE" }, //Spanish + { 0x201a, "bs-Cyrl-BA" }, //Bosnian (Cyrillic) + { 0x201a, "bs-Cyrl" }, //Bosnian (Cyrillic) + { 0x203b, "sms-FI" }, //Sami (Skolt) + { 0x203b, "sms" }, //Sami (Skolt) + { 0x2401, "ar-YE" }, //Arabic + { 0x2409, "en-029" }, //English + { 0x240a, "es-CO" }, //Spanish + { 0x241a, "sr-Latn-RS" }, //Serbian (Latin) + { 0x243b, "smn-FI" }, //Sami (Inari) + { 0x2801, "ar-SY" }, //Arabic + { 0x2809, "en-BZ" }, //English + { 0x280a, "es-PE" }, //Spanish + { 0x281a, "sr-Cyrl-RS" }, //Serbian (Cyrillic) + { 0x2c01, "ar-JO" }, //Arabic + { 0x2c09, "en-TT" }, //English + { 0x2c0a, "es-AR" }, //Spanish + { 0x2c1a, "sr-Latn-ME" }, //Serbian (Latin) + { 0x3001, "ar-LB" }, //Arabic + { 0x3009, "en-ZW" }, //English + { 0x300a, "es-EC" }, //Spanish + { 0x301a, "sr-Cyrl-ME" }, //Serbian (Cyrillic) + { 0x3401, "ar-KW" }, //Arabic + { 0x3409, "en-PH" }, //English + { 0x340a, "es-CL" }, //Spanish + { 0x3801, "ar-AE" }, //Arabic + { 0x380a, "es-UY" }, //Spanish + { 0x3c01, "ar-BH" }, //Arabic + { 0x3c0a, "es-PY" }, //Spanish + { 0x4001, "ar-QA" }, //Arabic + { 0x4009, "en-IN" }, //English + { 0x400a, "es-BO" }, //Spanish + { 0x4409, "en-MY" }, //English + { 0x440a, "es-SV" }, //Spanish + { 0x4809, "en-SG" }, //English + { 0x480a, "es-HN" }, //Spanish + { 0x4c0a, "es-NI" }, //Spanish + { 0x500a, "es-PR" }, //Spanish + { 0x540a, "es-US" }, //Spanish +}; + +namespace { +bool BCP47FromLanguageIdLess(const BCP47FromLanguageId& a, const BCP47FromLanguageId& b) { + return a.languageID < b.languageID; +} +} + +bool SkOTTableName::Iterator::next(SkOTTableName::Iterator::Record& record) { + SkOTTableName nameTable; + if (fNameTableSize < sizeof(nameTable)) { + return false; + } + memcpy(&nameTable, fNameTable, sizeof(nameTable)); + + const uint8_t* nameRecords = fNameTable + sizeof(nameTable); + const size_t nameRecordsSize = fNameTableSize - sizeof(nameTable); + + const size_t stringTableOffset = SkEndian_SwapBE16(nameTable.stringOffset); + if (fNameTableSize < stringTableOffset) { + return false; + } + const uint8_t* stringTable = fNameTable + stringTableOffset; + const size_t stringTableSize = fNameTableSize - stringTableOffset; + + // Find the next record which matches the requested type. + SkOTTableName::Record nameRecord; + const size_t nameRecordsCount = SkEndian_SwapBE16(nameTable.count); + const size_t nameRecordsMax = SkTMin(nameRecordsCount, nameRecordsSize / sizeof(nameRecord)); + do { + if (fIndex >= nameRecordsMax) { + return false; + } + + memcpy(&nameRecord, nameRecords + sizeof(nameRecord)*fIndex, sizeof(nameRecord)); + ++fIndex; + } while (fType != -1 && nameRecord.nameID.fontSpecific != fType); + + record.type = nameRecord.nameID.fontSpecific; + + // Decode the name into UTF-8. + const size_t nameOffset = SkEndian_SwapBE16(nameRecord.offset); + const size_t nameLength = SkEndian_SwapBE16(nameRecord.length); + if (stringTableSize < nameOffset + nameLength) { + return false; // continue? + } + const uint8_t* nameString = stringTable + nameOffset; + switch (nameRecord.platformID.value) { + case SkOTTableName::Record::PlatformID::Windows: + if (SkOTTableName::Record::EncodingID::Windows::UnicodeBMPUCS2 + != nameRecord.encodingID.windows.value + && SkOTTableName::Record::EncodingID::Windows::UnicodeUCS4 + != nameRecord.encodingID.windows.value + && SkOTTableName::Record::EncodingID::Windows::Symbol + != nameRecord.encodingID.windows.value) + { + record.name.reset(); + break; // continue? + } + case SkOTTableName::Record::PlatformID::Unicode: + case SkOTTableName::Record::PlatformID::ISO: + SkString_from_UTF16BE(nameString, nameLength, record.name); + break; + + case SkOTTableName::Record::PlatformID::Macintosh: + // TODO: need better decoding, especially on Mac. + if (SkOTTableName::Record::EncodingID::Macintosh::Roman + != nameRecord.encodingID.macintosh.value) + { + record.name.reset(); + break; // continue? + } + SkStringFromMacRoman(nameString, nameLength, record.name); + break; + + case SkOTTableName::Record::PlatformID::Custom: + // These should never appear in a 'name' table. + default: + SkASSERT(false); + record.name.reset(); + break; // continue? + } + + // Determine the language. + const uint16_t languageID = SkEndian_SwapBE16(nameRecord.languageID.languageTagID); + + // Handle format 1 languages. + if (SkOTTableName::format_1 == nameTable.format && languageID >= 0x8000) { + const uint16_t languageTagRecordIndex = languageID - 0x8000; + + if (nameRecordsSize < sizeof(nameRecord)*nameRecordsCount) { + return false; //"und" or break? + } + const uint8_t* format1extData = nameRecords + sizeof(nameRecord)*nameRecordsCount; + size_t format1extSize = nameRecordsSize - sizeof(nameRecord)*nameRecordsCount; + SkOTTableName::Format1Ext format1ext; + if (format1extSize < sizeof(format1ext)) { + return false; // "und" or break? + } + memcpy(&format1ext, format1extData, sizeof(format1ext)); + + const uint8_t* languageTagRecords = format1extData + sizeof(format1ext); + size_t languageTagRecordsSize = format1extSize - sizeof(format1ext); + if (languageTagRecordIndex < SkEndian_SwapBE16(format1ext.langTagCount)) { + SkOTTableName::Format1Ext::LangTagRecord languageTagRecord; + if (languageTagRecordsSize < sizeof(languageTagRecord)*(languageTagRecordIndex+1)) { + return false; // "und"? + } + const uint8_t* languageTagData = languageTagRecords + + sizeof(languageTagRecord)*languageTagRecordIndex; + memcpy(&languageTagRecord, languageTagData, sizeof(languageTagRecord)); + + uint16_t languageOffset = SkEndian_SwapBE16(languageTagRecord.offset); + uint16_t languageLength = SkEndian_SwapBE16(languageTagRecord.length); + + if (fNameTableSize < stringTableOffset + languageOffset + languageLength) { + return false; // "und"? + } + const uint8_t* languageString = stringTable + languageOffset; + SkString_from_UTF16BE(languageString, languageLength, record.language); + return true; + } + } + + // Handle format 0 languages, translating them into BCP 47. + const BCP47FromLanguageId target = { languageID, "" }; + int languageIndex = SkTSearch( + BCP47FromLanguageID, SK_ARRAY_COUNT(BCP47FromLanguageID), target, sizeof(target)); + if (languageIndex >= 0) { + record.language = BCP47FromLanguageID[languageIndex].bcp47; + return true; + } + + // Unknown language, return the BCP 47 code 'und' for 'undetermined'. + record.language = "und"; + return true; +} diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_name.h b/gfx/skia/skia/src/sfnt/SkOTTable_name.h new file mode 100644 index 0000000000..07cffd7748 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_name.h @@ -0,0 +1,577 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_name_DEFINED +#define SkOTTable_name_DEFINED + +#include "include/core/SkString.h" +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTableName { + static const SK_OT_CHAR TAG0 = 'n'; + static const SK_OT_CHAR TAG1 = 'a'; + static const SK_OT_CHAR TAG2 = 'm'; + static const SK_OT_CHAR TAG3 = 'e'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_USHORT format; + static const SK_OT_USHORT format_0 = SkTEndian_SwapBE16(0); + /** Format 1 was added in OpenType 1.6 (April 2009). */ + static const SK_OT_USHORT format_1 = SkTEndian_SwapBE16(1); + + /** The number of name records which follow. */ + SK_OT_USHORT count; + + /** Offset in SK_OT_BYTEs to start of string storage area (from start of table). */ + SK_OT_USHORT stringOffset; + + struct Record { + /** The platform ID specifies how to interpret the encoding and language ID. */ + struct PlatformID { + enum Value : SK_OT_USHORT { + Unicode = SkTEndian_SwapBE16(0), + Macintosh = SkTEndian_SwapBE16(1), + ISO = SkTEndian_SwapBE16(2), // Deprecated, use Unicode instead. + Windows = SkTEndian_SwapBE16(3), + Custom = SkTEndian_SwapBE16(4), + } value; + } platformID; + + union EncodingID { + SK_OT_USHORT custom; + + /** Always UTF-16BE. */ + struct Unicode { + enum Value : SK_OT_USHORT { + Unicode10 = SkTEndian_SwapBE16(0), + Unicode11 = SkTEndian_SwapBE16(1), + ISO10646 = SkTEndian_SwapBE16(2), //deprecated, use Unicode11 + Unicode20BMP = SkTEndian_SwapBE16(3), + Unicode20 = SkTEndian_SwapBE16(4), + UnicodeVariationSequences = SkTEndian_SwapBE16(5), + UnicodeFull = SkTEndian_SwapBE16(6), + } value; + } unicode; + + /** These are Mac encodings, see http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ + * for their mappings to unicode. + * Name table strings using PlatformID::Macintosh must use Roman. + */ + struct Macintosh { + enum Value : SK_OT_USHORT { + Roman = SkTEndian_SwapBE16(0), + Japanese = SkTEndian_SwapBE16(1), + ChineseTraditional = SkTEndian_SwapBE16(2), + Korean = SkTEndian_SwapBE16(3), + Arabic = SkTEndian_SwapBE16(4), + Hebrew = SkTEndian_SwapBE16(5), + Greek = SkTEndian_SwapBE16(6), + Russian = SkTEndian_SwapBE16(7), + RSymbol = SkTEndian_SwapBE16(8), + Devanagari = SkTEndian_SwapBE16(9), + Gurmukhi = SkTEndian_SwapBE16(10), + Gujarati = SkTEndian_SwapBE16(11), + Oriya = SkTEndian_SwapBE16(12), + Bengali = SkTEndian_SwapBE16(13), + Tamil = SkTEndian_SwapBE16(14), + Telugu = SkTEndian_SwapBE16(15), + Kannada = SkTEndian_SwapBE16(16), + Malayalam = SkTEndian_SwapBE16(17), + Sinhalese = SkTEndian_SwapBE16(18), + Burmese = SkTEndian_SwapBE16(19), + Khmer = SkTEndian_SwapBE16(20), + Thai = SkTEndian_SwapBE16(21), + Laotian = SkTEndian_SwapBE16(22), + Georgian = SkTEndian_SwapBE16(23), + Armenian = SkTEndian_SwapBE16(24), + ChineseSimplified = SkTEndian_SwapBE16(25), + Tibetan = SkTEndian_SwapBE16(26), + Mongolian = SkTEndian_SwapBE16(27), + Geez = SkTEndian_SwapBE16(28), + Slavic = SkTEndian_SwapBE16(29), + Vietnamese = SkTEndian_SwapBE16(30), + Sindhi = SkTEndian_SwapBE16(31), + Uninterpreted = SkTEndian_SwapBE16(32), + } value; + } macintosh; + + /** Deprecated, use Unicode instead. */ + struct ISO { + enum Value : SK_OT_USHORT { + ASCII7 = SkTEndian_SwapBE16(0), + ISO10646 = SkTEndian_SwapBE16(1), + ISO88591 = SkTEndian_SwapBE16(2), + } value; + } iso; + + /** Name table strings using PlatformID::Windows must use Symbol, UnicodeBMPUCS2, or + * UnicodeUCS4. Symbol and UnicodeBMPUCS2 are both UCS2-BE, UnicodeUCS4 is actually + * UTF-16BE. + */ + struct Windows { + enum Value : SK_OT_USHORT { + Symbol = SkTEndian_SwapBE16(0), // UCS2-BE, but don't use this font to display it's own name. + UnicodeBMPUCS2 = SkTEndian_SwapBE16(1), // UCS2-BE, Windows default + ShiftJIS = SkTEndian_SwapBE16(2), + PRC = SkTEndian_SwapBE16(3), + Big5 = SkTEndian_SwapBE16(4), + Wansung = SkTEndian_SwapBE16(5), + Johab = SkTEndian_SwapBE16(6), + UnicodeUCS4 = SkTEndian_SwapBE16(10), // UTF-16BE. It means UCS4 in charmaps. + } value; + } windows; + } encodingID; + + /** LanguageIDs <= 0x7FFF are predefined. + * LanguageIDs > 0x7FFF are indexes into the langTagRecord array + * (in format 1 name tables, see SkOTTableName::format). + */ + union LanguageID { + /** A value greater than 0x7FFF. + * languageTagID - 0x8000 is an index into the langTagRecord array. + */ + SK_OT_USHORT languageTagID; + + /** These are known as Language Designators. + * Apple now uses BCP 47 (post OSX10.4), so there will be no new entries. + */ + struct Macintosh { + enum Value : SK_OT_USHORT { + English = SkTEndian_SwapBE16(0), + French = SkTEndian_SwapBE16(1), + German = SkTEndian_SwapBE16(2), + Italian = SkTEndian_SwapBE16(3), + Dutch = SkTEndian_SwapBE16(4), + Swedish = SkTEndian_SwapBE16(5), + Spanish = SkTEndian_SwapBE16(6), + Danish = SkTEndian_SwapBE16(7), + Portuguese = SkTEndian_SwapBE16(8), + Norwegian = SkTEndian_SwapBE16(9), + Hebrew = SkTEndian_SwapBE16(10), + Japanese = SkTEndian_SwapBE16(11), + Arabic = SkTEndian_SwapBE16(12), + Finnish = SkTEndian_SwapBE16(13), + Greek = SkTEndian_SwapBE16(14), + Icelandic = SkTEndian_SwapBE16(15), + Maltese = SkTEndian_SwapBE16(16), + Turkish = SkTEndian_SwapBE16(17), + Croatian = SkTEndian_SwapBE16(18), + ChineseTraditional = SkTEndian_SwapBE16(19), + Urdu = SkTEndian_SwapBE16(20), + Hindi = SkTEndian_SwapBE16(21), + Thai = SkTEndian_SwapBE16(22), + Korean = SkTEndian_SwapBE16(23), + Lithuanian = SkTEndian_SwapBE16(24), + Polish = SkTEndian_SwapBE16(25), + Hungarian = SkTEndian_SwapBE16(26), + Estonian = SkTEndian_SwapBE16(27), + Latvian = SkTEndian_SwapBE16(28), + Sami = SkTEndian_SwapBE16(29), + Faroese = SkTEndian_SwapBE16(30), + Farsi_Persian = SkTEndian_SwapBE16(31), + Russian = SkTEndian_SwapBE16(32), + ChineseSimplified = SkTEndian_SwapBE16(33), + Flemish = SkTEndian_SwapBE16(34), + IrishGaelic = SkTEndian_SwapBE16(35), + Albanian = SkTEndian_SwapBE16(36), + Romanian = SkTEndian_SwapBE16(37), + Czech = SkTEndian_SwapBE16(38), + Slovak = SkTEndian_SwapBE16(39), + Slovenian = SkTEndian_SwapBE16(40), + Yiddish = SkTEndian_SwapBE16(41), + Serbian = SkTEndian_SwapBE16(42), + Macedonian = SkTEndian_SwapBE16(43), + Bulgarian = SkTEndian_SwapBE16(44), + Ukrainian = SkTEndian_SwapBE16(45), + Byelorussian = SkTEndian_SwapBE16(46), + Uzbek = SkTEndian_SwapBE16(47), + Kazakh = SkTEndian_SwapBE16(48), + AzerbaijaniCyrillic = SkTEndian_SwapBE16(49), + AzerbaijaniArabic = SkTEndian_SwapBE16(50), + Armenian = SkTEndian_SwapBE16(51), + Georgian = SkTEndian_SwapBE16(52), + Moldavian = SkTEndian_SwapBE16(53), + Kirghiz = SkTEndian_SwapBE16(54), + Tajiki = SkTEndian_SwapBE16(55), + Turkmen = SkTEndian_SwapBE16(56), + MongolianTraditional = SkTEndian_SwapBE16(57), + MongolianCyrillic = SkTEndian_SwapBE16(58), + Pashto = SkTEndian_SwapBE16(59), + Kurdish = SkTEndian_SwapBE16(60), + Kashmiri = SkTEndian_SwapBE16(61), + Sindhi = SkTEndian_SwapBE16(62), + Tibetan = SkTEndian_SwapBE16(63), + Nepali = SkTEndian_SwapBE16(64), + Sanskrit = SkTEndian_SwapBE16(65), + Marathi = SkTEndian_SwapBE16(66), + Bengali = SkTEndian_SwapBE16(67), + Assamese = SkTEndian_SwapBE16(68), + Gujarati = SkTEndian_SwapBE16(69), + Punjabi = SkTEndian_SwapBE16(70), + Oriya = SkTEndian_SwapBE16(71), + Malayalam = SkTEndian_SwapBE16(72), + Kannada = SkTEndian_SwapBE16(73), + Tamil = SkTEndian_SwapBE16(74), + Telugu = SkTEndian_SwapBE16(75), + Sinhalese = SkTEndian_SwapBE16(76), + Burmese = SkTEndian_SwapBE16(77), + Khmer = SkTEndian_SwapBE16(78), + Lao = SkTEndian_SwapBE16(79), + Vietnamese = SkTEndian_SwapBE16(80), + Indonesian = SkTEndian_SwapBE16(81), + Tagalong = SkTEndian_SwapBE16(82), + MalayRoman = SkTEndian_SwapBE16(83), + MalayArabic = SkTEndian_SwapBE16(84), + Amharic = SkTEndian_SwapBE16(85), + Tigrinya = SkTEndian_SwapBE16(86), + Galla = SkTEndian_SwapBE16(87), + Somali = SkTEndian_SwapBE16(88), + Swahili = SkTEndian_SwapBE16(89), + Kinyarwanda_Ruanda = SkTEndian_SwapBE16(90), + Rundi = SkTEndian_SwapBE16(91), + Nyanja_Chewa = SkTEndian_SwapBE16(92), + Malagasy = SkTEndian_SwapBE16(93), + Esperanto = SkTEndian_SwapBE16(94), + Welsh = SkTEndian_SwapBE16(128), + Basque = SkTEndian_SwapBE16(129), + Catalan = SkTEndian_SwapBE16(130), + Latin = SkTEndian_SwapBE16(131), + Quenchua = SkTEndian_SwapBE16(132), + Guarani = SkTEndian_SwapBE16(133), + Aymara = SkTEndian_SwapBE16(134), + Tatar = SkTEndian_SwapBE16(135), + Uighur = SkTEndian_SwapBE16(136), + Dzongkha = SkTEndian_SwapBE16(137), + JavaneseRoman = SkTEndian_SwapBE16(138), + SundaneseRoman = SkTEndian_SwapBE16(139), + Galician = SkTEndian_SwapBE16(140), + Afrikaans = SkTEndian_SwapBE16(141), + Breton = SkTEndian_SwapBE16(142), + Inuktitut = SkTEndian_SwapBE16(143), + ScottishGaelic = SkTEndian_SwapBE16(144), + ManxGaelic = SkTEndian_SwapBE16(145), + IrishGaelicWithLenition = SkTEndian_SwapBE16(146), + Tongan = SkTEndian_SwapBE16(147), + GreekPolytonic = SkTEndian_SwapBE16(148), + Greenlandic = SkTEndian_SwapBE16(149), + AzerbaijaniRoman = SkTEndian_SwapBE16(150), + } value; + } macintosh; + + /** These are known as LCIDs. + * On Windows the current set can be had from EnumSystemLocalesEx and LocaleNameToLCID. + */ + struct Windows { + enum Value : SK_OT_USHORT { + Afrikaans_SouthAfrica = SkTEndian_SwapBE16(0x0436), + Albanian_Albania = SkTEndian_SwapBE16(0x041C), + Alsatian_France = SkTEndian_SwapBE16(0x0484), + Amharic_Ethiopia = SkTEndian_SwapBE16(0x045E), + Arabic_Algeria = SkTEndian_SwapBE16(0x1401), + Arabic_Bahrain = SkTEndian_SwapBE16(0x3C01), + Arabic_Egypt = SkTEndian_SwapBE16(0x0C01), + Arabic_Iraq = SkTEndian_SwapBE16(0x0801), + Arabic_Jordan = SkTEndian_SwapBE16(0x2C01), + Arabic_Kuwait = SkTEndian_SwapBE16(0x3401), + Arabic_Lebanon = SkTEndian_SwapBE16(0x3001), + Arabic_Libya = SkTEndian_SwapBE16(0x1001), + Arabic_Morocco = SkTEndian_SwapBE16(0x1801), + Arabic_Oman = SkTEndian_SwapBE16(0x2001), + Arabic_Qatar = SkTEndian_SwapBE16(0x4001), + Arabic_SaudiArabia = SkTEndian_SwapBE16(0x0401), + Arabic_Syria = SkTEndian_SwapBE16(0x2801), + Arabic_Tunisia = SkTEndian_SwapBE16(0x1C01), + Arabic_UAE = SkTEndian_SwapBE16(0x3801), + Arabic_Yemen = SkTEndian_SwapBE16(0x2401), + Armenian_Armenia = SkTEndian_SwapBE16(0x042B), + Assamese_India = SkTEndian_SwapBE16(0x044D), + AzeriCyrillic_Azerbaijan = SkTEndian_SwapBE16(0x082C), + AzeriLatin_Azerbaijan = SkTEndian_SwapBE16(0x042C), + Bashkir_Russia = SkTEndian_SwapBE16(0x046D), + Basque_Basque = SkTEndian_SwapBE16(0x042D), + Belarusian_Belarus = SkTEndian_SwapBE16(0x0423), + Bengali_Bangladesh = SkTEndian_SwapBE16(0x0845), + Bengali_India = SkTEndian_SwapBE16(0x0445), + BosnianCyrillic_BosniaAndHerzegovina = SkTEndian_SwapBE16(0x201A), + BosnianLatin_BosniaAndHerzegovina = SkTEndian_SwapBE16(0x141A), + Breton_France = SkTEndian_SwapBE16(0x047E), + Bulgarian_Bulgaria = SkTEndian_SwapBE16(0x0402), + Catalan_Catalan = SkTEndian_SwapBE16(0x0403), + Chinese_HongKongSAR = SkTEndian_SwapBE16(0x0C04), + Chinese_MacaoSAR = SkTEndian_SwapBE16(0x1404), + Chinese_PeoplesRepublicOfChina = SkTEndian_SwapBE16(0x0804), + Chinese_Singapore = SkTEndian_SwapBE16(0x1004), + Chinese_Taiwan = SkTEndian_SwapBE16(0x0404), + Corsican_France = SkTEndian_SwapBE16(0x0483), + Croatian_Croatia = SkTEndian_SwapBE16(0x041A), + CroatianLatin_BosniaAndHerzegovina = SkTEndian_SwapBE16(0x101A), + Czech_CzechRepublic = SkTEndian_SwapBE16(0x0405), + Danish_Denmark = SkTEndian_SwapBE16(0x0406), + Dari_Afghanistan = SkTEndian_SwapBE16(0x048C), + Divehi_Maldives = SkTEndian_SwapBE16(0x0465), + Dutch_Belgium = SkTEndian_SwapBE16(0x0813), + Dutch_Netherlands = SkTEndian_SwapBE16(0x0413), + English_Australia = SkTEndian_SwapBE16(0x0C09), + English_Belize = SkTEndian_SwapBE16(0x2809), + English_Canada = SkTEndian_SwapBE16(0x1009), + English_Caribbean = SkTEndian_SwapBE16(0x2409), + English_India = SkTEndian_SwapBE16(0x4009), + English_Ireland = SkTEndian_SwapBE16(0x1809), + English_Jamaica = SkTEndian_SwapBE16(0x2009), + English_Malaysia = SkTEndian_SwapBE16(0x4409), + English_NewZealand = SkTEndian_SwapBE16(0x1409), + English_RepublicOfThePhilippines = SkTEndian_SwapBE16(0x3409), + English_Singapore = SkTEndian_SwapBE16(0x4809), + English_SouthAfrica = SkTEndian_SwapBE16(0x1C09), + English_TrinidadAndTobago = SkTEndian_SwapBE16(0x2C09), + English_UnitedKingdom = SkTEndian_SwapBE16(0x0809), + English_UnitedStates = SkTEndian_SwapBE16(0x0409), + English_Zimbabwe = SkTEndian_SwapBE16(0x3009), + Estonian_Estonia = SkTEndian_SwapBE16(0x0425), + Faroese_FaroeIslands = SkTEndian_SwapBE16(0x0438), + Filipino_Philippines = SkTEndian_SwapBE16(0x0464), + Finnish_Finland = SkTEndian_SwapBE16(0x040B), + French_Belgium = SkTEndian_SwapBE16(0x080C), + French_Canada = SkTEndian_SwapBE16(0x0C0C), + French_France = SkTEndian_SwapBE16(0x040C), + French_Luxembourg = SkTEndian_SwapBE16(0x140c), + French_PrincipalityOfMonoco = SkTEndian_SwapBE16(0x180C), + French_Switzerland = SkTEndian_SwapBE16(0x100C), + Frisian_Netherlands = SkTEndian_SwapBE16(0x0462), + Galician_Galician = SkTEndian_SwapBE16(0x0456), + Georgian_Georgia = SkTEndian_SwapBE16(0x0437), + German_Austria = SkTEndian_SwapBE16(0x0C07), + German_Germany = SkTEndian_SwapBE16(0x0407), + German_Liechtenstein = SkTEndian_SwapBE16(0x1407), + German_Luxembourg = SkTEndian_SwapBE16(0x1007), + German_Switzerland = SkTEndian_SwapBE16(0x0807), + Greek_Greece = SkTEndian_SwapBE16(0x0408), + Greenlandic_Greenland = SkTEndian_SwapBE16(0x046F), + Gujarati_India = SkTEndian_SwapBE16(0x0447), + HausaLatin_Nigeria = SkTEndian_SwapBE16(0x0468), + Hebrew_Israel = SkTEndian_SwapBE16(0x040D), + Hindi_India = SkTEndian_SwapBE16(0x0439), + Hungarian_Hungary = SkTEndian_SwapBE16(0x040E), + Icelandic_Iceland = SkTEndian_SwapBE16(0x040F), + Igbo_Nigeria = SkTEndian_SwapBE16(0x0470), + Indonesian_Indonesia = SkTEndian_SwapBE16(0x0421), + Inuktitut_Canada = SkTEndian_SwapBE16(0x045D), + InuktitutLatin_Canada = SkTEndian_SwapBE16(0x085D), + Irish_Ireland = SkTEndian_SwapBE16(0x083C), + isiXhosa_SouthAfrica = SkTEndian_SwapBE16(0x0434), + isiZulu_SouthAfrica = SkTEndian_SwapBE16(0x0435), + Italian_Italy = SkTEndian_SwapBE16(0x0410), + Italian_Switzerland = SkTEndian_SwapBE16(0x0810), + Japanese_Japan = SkTEndian_SwapBE16(0x0411), + Kannada_India = SkTEndian_SwapBE16(0x044B), + Kazakh_Kazakhstan = SkTEndian_SwapBE16(0x043F), + Khmer_Cambodia = SkTEndian_SwapBE16(0x0453), + Kiche_Guatemala = SkTEndian_SwapBE16(0x0486), + Kinyarwanda_Rwanda = SkTEndian_SwapBE16(0x0487), + Kiswahili_Kenya = SkTEndian_SwapBE16(0x0441), + Konkani_India = SkTEndian_SwapBE16(0x0457), + Korean_Korea = SkTEndian_SwapBE16(0x0412), + Kyrgyz_Kyrgyzstan = SkTEndian_SwapBE16(0x0440), + Lao_LaoPDR = SkTEndian_SwapBE16(0x0454), + Latvian_Latvia = SkTEndian_SwapBE16(0x0426), + Lithuanian_Lithuania = SkTEndian_SwapBE16(0x0427), + LowerSorbian_Germany = SkTEndian_SwapBE16(0x082E), + Luxembourgish_Luxembourg = SkTEndian_SwapBE16(0x046E), + MacedonianFYROM_FormerYugoslavRepublicOfMacedonia = SkTEndian_SwapBE16(0x042F), + Malay_BruneiDarussalam = SkTEndian_SwapBE16(0x083E), + Malay_Malaysia = SkTEndian_SwapBE16(0x043E), + Malayalam_India = SkTEndian_SwapBE16(0x044C), + Maltese_Malta = SkTEndian_SwapBE16(0x043A), + Maori_NewZealand = SkTEndian_SwapBE16(0x0481), + Mapudungun_Chile = SkTEndian_SwapBE16(0x047A), + Marathi_India = SkTEndian_SwapBE16(0x044E), + Mohawk_Mohawk = SkTEndian_SwapBE16(0x047C), + MongolianCyrillic_Mongolia = SkTEndian_SwapBE16(0x0450), + MongolianTraditional_PeoplesRepublicOfChina = SkTEndian_SwapBE16(0x0850), + Nepali_Nepal = SkTEndian_SwapBE16(0x0461), + NorwegianBokmal_Norway = SkTEndian_SwapBE16(0x0414), + NorwegianNynorsk_Norway = SkTEndian_SwapBE16(0x0814), + Occitan_France = SkTEndian_SwapBE16(0x0482), + Odia_India = SkTEndian_SwapBE16(0x0448), + Pashto_Afghanistan = SkTEndian_SwapBE16(0x0463), + Polish_Poland = SkTEndian_SwapBE16(0x0415), + Portuguese_Brazil = SkTEndian_SwapBE16(0x0416), + Portuguese_Portugal = SkTEndian_SwapBE16(0x0816), + Punjabi_India = SkTEndian_SwapBE16(0x0446), + Quechua_Bolivia = SkTEndian_SwapBE16(0x046B), + Quechua_Ecuador = SkTEndian_SwapBE16(0x086B), + Quechua_Peru = SkTEndian_SwapBE16(0x0C6B), + Romanian_Romania = SkTEndian_SwapBE16(0x0418), + Romansh_Switzerland = SkTEndian_SwapBE16(0x0417), + Russian_Russia = SkTEndian_SwapBE16(0x0419), + SamiInari_Finland = SkTEndian_SwapBE16(0x243B), + SamiLule_Norway = SkTEndian_SwapBE16(0x103B), + SamiLule_Sweden = SkTEndian_SwapBE16(0x143B), + SamiNorthern_Finland = SkTEndian_SwapBE16(0x0C3B), + SamiNorthern_Norway = SkTEndian_SwapBE16(0x043B), + SamiNorthern_Sweden = SkTEndian_SwapBE16(0x083B), + SamiSkolt_Finland = SkTEndian_SwapBE16(0x203B), + SamiSouthern_Norway = SkTEndian_SwapBE16(0x183B), + SamiSouthern_Sweden = SkTEndian_SwapBE16(0x1C3B), + Sanskrit_India = SkTEndian_SwapBE16(0x044F), + SerbianCyrillic_BosniaAndHerzegovina = SkTEndian_SwapBE16(0x1C1A), + SerbianCyrillic_Serbia = SkTEndian_SwapBE16(0x0C1A), + SerbianLatin_BosniaAndHerzegovina = SkTEndian_SwapBE16(0x181A), + SerbianLatin_Serbia = SkTEndian_SwapBE16(0x081A), + SesothoSaLeboa_SouthAfrica = SkTEndian_SwapBE16(0x046C), + Setswana_SouthAfrica = SkTEndian_SwapBE16(0x0432), + Sinhala_SriLanka = SkTEndian_SwapBE16(0x045B), + Slovak_Slovakia = SkTEndian_SwapBE16(0x041B), + Slovenian_Slovenia = SkTEndian_SwapBE16(0x0424), + Spanish_Argentina = SkTEndian_SwapBE16(0x2C0A), + Spanish_Bolivia = SkTEndian_SwapBE16(0x400A), + Spanish_Chile = SkTEndian_SwapBE16(0x340A), + Spanish_Colombia = SkTEndian_SwapBE16(0x240A), + Spanish_CostaRica = SkTEndian_SwapBE16(0x140A), + Spanish_DominicanRepublic = SkTEndian_SwapBE16(0x1C0A), + Spanish_Ecuador = SkTEndian_SwapBE16(0x300A), + Spanish_ElSalvador = SkTEndian_SwapBE16(0x440A), + Spanish_Guatemala = SkTEndian_SwapBE16(0x100A), + Spanish_Honduras = SkTEndian_SwapBE16(0x480A), + Spanish_Mexico = SkTEndian_SwapBE16(0x080A), + Spanish_Nicaragua = SkTEndian_SwapBE16(0x4C0A), + Spanish_Panama = SkTEndian_SwapBE16(0x180A), + Spanish_Paraguay = SkTEndian_SwapBE16(0x3C0A), + Spanish_Peru = SkTEndian_SwapBE16(0x280A), + Spanish_PuertoRico = SkTEndian_SwapBE16(0x500A), + SpanishModernSort_Spain = SkTEndian_SwapBE16(0x0C0A), + SpanishTraditionalSort_Spain = SkTEndian_SwapBE16(0x040A), + Spanish_UnitedStates = SkTEndian_SwapBE16(0x540A), + Spanish_Uruguay = SkTEndian_SwapBE16(0x380A), + Spanish_Venezuela = SkTEndian_SwapBE16(0x200A), + Sweden_Finland = SkTEndian_SwapBE16(0x081D), + Swedish_Sweden = SkTEndian_SwapBE16(0x041D), + Syriac_Syria = SkTEndian_SwapBE16(0x045A), + TajikCyrillic_Tajikistan = SkTEndian_SwapBE16(0x0428), + TamazightLatin_Algeria = SkTEndian_SwapBE16(0x085F), + Tamil_India = SkTEndian_SwapBE16(0x0449), + Tatar_Russia = SkTEndian_SwapBE16(0x0444), + Telugu_India = SkTEndian_SwapBE16(0x044A), + Thai_Thailand = SkTEndian_SwapBE16(0x041E), + Tibetan_PRC = SkTEndian_SwapBE16(0x0451), + Turkish_Turkey = SkTEndian_SwapBE16(0x041F), + Turkmen_Turkmenistan = SkTEndian_SwapBE16(0x0442), + Uighur_PRC = SkTEndian_SwapBE16(0x0480), + Ukrainian_Ukraine = SkTEndian_SwapBE16(0x0422), + UpperSorbian_Germany = SkTEndian_SwapBE16(0x042E), + Urdu_IslamicRepublicOfPakistan = SkTEndian_SwapBE16(0x0420), + UzbekCyrillic_Uzbekistan = SkTEndian_SwapBE16(0x0843), + UzbekLatin_Uzbekistan = SkTEndian_SwapBE16(0x0443), + Vietnamese_Vietnam = SkTEndian_SwapBE16(0x042A), + Welsh_UnitedKingdom = SkTEndian_SwapBE16(0x0452), + Wolof_Senegal = SkTEndian_SwapBE16(0x0488), + Yakut_Russia = SkTEndian_SwapBE16(0x0485), + Yi_PRC = SkTEndian_SwapBE16(0x0478), + Yoruba_Nigeria = SkTEndian_SwapBE16(0x046A), + } value; + } windows; + } languageID; + + /** NameIDs <= 0xFF are predefined. Those > 0xFF are font specific. */ + union NameID { + /** A font specific name id which should be greater than 0xFF. */ + SK_OT_USHORT fontSpecific; + struct Predefined { + enum Value : SK_OT_USHORT { + CopyrightNotice = SkTEndian_SwapBE16(0), + FontFamilyName = SkTEndian_SwapBE16(1), + FontSubfamilyName = SkTEndian_SwapBE16(2), + UniqueFontIdentifier = SkTEndian_SwapBE16(3), + FullFontName = SkTEndian_SwapBE16(4), + VersionString = SkTEndian_SwapBE16(5), //Version . + PostscriptName = SkTEndian_SwapBE16(6), //See spec for constraints. + Trademark = SkTEndian_SwapBE16(7), + ManufacturerName = SkTEndian_SwapBE16(8), + Designer = SkTEndian_SwapBE16(9), + Description = SkTEndian_SwapBE16(10), + URLVendor = SkTEndian_SwapBE16(11), + URLDesigner = SkTEndian_SwapBE16(12), + LicenseDescription = SkTEndian_SwapBE16(13), + LicenseInfoURL = SkTEndian_SwapBE16(14), + PreferredFamily = SkTEndian_SwapBE16(16), + PreferredSubfamily = SkTEndian_SwapBE16(17), + CompatibleFullName = SkTEndian_SwapBE16(18), + SampleText = SkTEndian_SwapBE16(19), + PostscriptCIDFindfontName = SkTEndian_SwapBE16(20), + WWSFamilyName = SkTEndian_SwapBE16(21), + WWSSubfamilyName = SkTEndian_SwapBE16(22), + } value; + } predefined; + } nameID; + + /** The length of the string in SK_OT_BYTEs. */ + SK_OT_USHORT length; + + /** Offset in SK_OT_BYTEs from start of string storage area + * (see SkOTTableName::stringOffset). + */ + SK_OT_USHORT offset; + }; //nameRecord[count]; + + struct Format1Ext { + /** The number of languageTagRecords which follow. */ + SK_OT_USHORT langTagCount; + + /** The encoding of a langTagRecord string is always UTF-16BE. + * The content should follow IETF specification BCP 47. + */ + struct LangTagRecord { + /** The length of the string in SK_OT_BYTEs. */ + SK_OT_USHORT length; + + /** Offset in SK_OT_BYTEs from start of string storage area + * (see SkOTTableName::stringOffset). + */ + SK_OT_USHORT offset; + }; //langTagRecord[langTagCount] + }; //format1ext (if format == format_1) + + class Iterator { + public: + Iterator(const uint8_t* nameTable, size_t size) + : fNameTable(nameTable), fNameTableSize(size), fIndex(0), fType(-1) { } + Iterator(const uint8_t* nameTable, size_t size, SK_OT_USHORT type) + : fNameTable(nameTable), fNameTableSize(size), fIndex(0), fType(type) + { } + + void reset(SK_OT_USHORT type) { + fIndex = 0; + fType = type; + } + + struct Record { + SkString name; + SkString language; + SK_OT_USHORT type; + }; + bool next(Record&); + + private: + const uint8_t* fNameTable; + const size_t fNameTableSize; + size_t fIndex; + int fType; + }; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkOTTableName) == 6, "sizeof_SkOTTableName_not_6"); +static_assert(sizeof(SkOTTableName::Format1Ext) == 2, "sizeof_SkOTTableNameF1_not_2"); +static_assert(sizeof(SkOTTableName::Format1Ext::LangTagRecord) == 4, "sizeof_SkOTTableNameLangTagRecord_not_4"); +static_assert(sizeof(SkOTTableName::Record) == 12, "sizeof_SkOTTableNameRecord_not_12"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTTable_post.h b/gfx/skia/skia/src/sfnt/SkOTTable_post.h new file mode 100644 index 0000000000..943bffb9f0 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTTable_post.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTTable_post_DEFINED +#define SkOTTable_post_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkOTTablePostScript { + static const SK_OT_CHAR TAG0 = 'p'; + static const SK_OT_CHAR TAG1 = 'o'; + static const SK_OT_CHAR TAG2 = 's'; + static const SK_OT_CHAR TAG3 = 't'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + struct Format { + enum Value : SK_OT_Fixed { + version1 = SkTEndian_SwapBE32(0x00010000), + version2 = SkTEndian_SwapBE32(0x00020000), + version2_5 = SkTEndian_SwapBE32(0x00025000), + version3 = SkTEndian_SwapBE32(0x00030000), + version4 = SkTEndian_SwapBE32(0x00040000), + }; + SK_OT_Fixed value; + } format; + SK_OT_Fixed italicAngle; + SK_OT_FWORD underlinePosition; + SK_OT_FWORD underlineThickness; + SK_OT_ULONG isFixedPitch; + SK_OT_ULONG minMemType42; + SK_OT_ULONG maxMemType42; + SK_OT_ULONG minMemType1; + SK_OT_ULONG maxMemType1; +}; + +#pragma pack(pop) + + +#include +static_assert(offsetof(SkOTTablePostScript, maxMemType1) == 28, "SkOTTablePostScript_maxMemType1_not_at_28"); +static_assert(sizeof(SkOTTablePostScript) == 32, "sizeof_SkOTTablePostScript_not_32"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkOTUtils.cpp b/gfx/skia/skia/src/sfnt/SkOTUtils.cpp new file mode 100644 index 0000000000..061ceb069e --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTUtils.cpp @@ -0,0 +1,239 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sfnt/SkOTUtils.h" + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/private/SkTo.h" +#include "src/core/SkAdvancedTypefaceMetrics.h" +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_head.h" +#include "src/sfnt/SkOTTable_name.h" +#include "src/sfnt/SkSFNTHeader.h" + +extern const uint8_t SK_OT_GlyphData_NoOutline[] = { + 0x0,0x0, //SkOTTableGlyphData::numberOfContours + 0x0,0x0, //SkOTTableGlyphData::xMin + 0x0,0x0, //SkOTTableGlyphData::yMin + 0x0,0x0, //SkOTTableGlyphData::xMax + 0x0,0x0, //SkOTTableGlyphData::yMax + + 0x0,0x0, //SkOTTableGlyphDataInstructions::length +}; + +uint32_t SkOTUtils::CalcTableChecksum(SK_OT_ULONG *data, size_t length) { + uint32_t sum = 0; + SK_OT_ULONG *dataEnd = data + ((length + 3) & ~3) / sizeof(SK_OT_ULONG); + for (; data < dataEnd; ++data) { + sum += SkEndian_SwapBE32(*data); + } + return sum; +} + +SkData* SkOTUtils::RenameFont(SkStreamAsset* fontData, const char* fontName, int fontNameLen) { + + // Get the sfnt header. + SkSFNTHeader sfntHeader; + if (fontData->read(&sfntHeader, sizeof(sfntHeader)) < sizeof(sfntHeader)) { + return nullptr; + } + + // Find the existing 'name' table. + int tableIndex; + SkSFNTHeader::TableDirectoryEntry tableEntry; + int numTables = SkEndian_SwapBE16(sfntHeader.numTables); + for (tableIndex = 0; tableIndex < numTables; ++tableIndex) { + if (fontData->read(&tableEntry, sizeof(tableEntry)) < sizeof(tableEntry)) { + return nullptr; + } + if (SkOTTableName::TAG == tableEntry.tag) { + break; + } + } + if (tableIndex == numTables) { + return nullptr; + } + + if (!fontData->rewind()) { + return nullptr; + } + + // The required 'name' record types: Family, Style, Unique, Full and PostScript. + static constexpr std::array names{{ + SkOTTableName::Record::NameID::Predefined::FontFamilyName, + SkOTTableName::Record::NameID::Predefined::FontSubfamilyName, + SkOTTableName::Record::NameID::Predefined::UniqueFontIdentifier, + SkOTTableName::Record::NameID::Predefined::FullFontName, + SkOTTableName::Record::NameID::Predefined::PostscriptName, + }}; + + // GDI will not use a Symbol cmap table if there is no Symbol encoded name. + static constexpr std::array encodings{{ + SkOTTableName::Record::EncodingID::Windows::Symbol, + SkOTTableName::Record::EncodingID::Windows::UnicodeBMPUCS2, + }}; + + // Copy the data, leaving out the old name table. + // In theory, we could also remove the DSIG table if it exists. + size_t nameTableLogicalSize = sizeof(SkOTTableName) + + (encodings.size() * names.size() * sizeof(SkOTTableName::Record)) + + (fontNameLen * sizeof(SK_OT_USHORT)); + size_t nameTablePhysicalSize = (nameTableLogicalSize + 3) & ~3; // Rounded up to a multiple of 4. + + size_t oldNameTablePhysicalSize = (SkEndian_SwapBE32(tableEntry.logicalLength) + 3) & ~3; // Rounded up to a multiple of 4. + size_t oldNameTableOffset = SkEndian_SwapBE32(tableEntry.offset); + + //originalDataSize is the size of the original data without the name table. + size_t originalDataSize = fontData->getLength() - oldNameTablePhysicalSize; + size_t newDataSize = originalDataSize + nameTablePhysicalSize; + + auto rewrittenFontData = SkData::MakeUninitialized(newDataSize); + SK_OT_BYTE* data = static_cast(rewrittenFontData->writable_data()); + + if (fontData->read(data, oldNameTableOffset) < oldNameTableOffset) { + return nullptr; + } + if (fontData->skip(oldNameTablePhysicalSize) < oldNameTablePhysicalSize) { + return nullptr; + } + if (fontData->read(data + oldNameTableOffset, originalDataSize - oldNameTableOffset) < originalDataSize - oldNameTableOffset) { + return nullptr; + } + + //Fix up the offsets of the directory entries after the old 'name' table entry. + SkSFNTHeader::TableDirectoryEntry* currentEntry = reinterpret_cast(data + sizeof(SkSFNTHeader)); + SkSFNTHeader::TableDirectoryEntry* endEntry = currentEntry + numTables; + SkSFNTHeader::TableDirectoryEntry* headTableEntry = nullptr; + for (; currentEntry < endEntry; ++currentEntry) { + uint32_t oldOffset = SkEndian_SwapBE32(currentEntry->offset); + if (oldOffset > oldNameTableOffset) { + currentEntry->offset = SkEndian_SwapBE32(SkToU32(oldOffset - oldNameTablePhysicalSize)); + } + + if (SkOTTableHead::TAG == currentEntry->tag) { + headTableEntry = currentEntry; + } + } + + // Make the table directory entry point to the new 'name' table. + SkSFNTHeader::TableDirectoryEntry* nameTableEntry = reinterpret_cast(data + sizeof(SkSFNTHeader)) + tableIndex; + nameTableEntry->logicalLength = SkEndian_SwapBE32(SkToU32(nameTableLogicalSize)); + nameTableEntry->offset = SkEndian_SwapBE32(SkToU32(originalDataSize)); + + // Write the new 'name' table after the original font data. + SkOTTableName* nameTable = reinterpret_cast(data + originalDataSize); + unsigned short stringOffset = sizeof(SkOTTableName) + (encodings.size() * names.size() * sizeof(SkOTTableName::Record)); + nameTable->format = SkOTTableName::format_0; + nameTable->count = SkEndian_SwapBE16(encodings.size() * names.size()); + nameTable->stringOffset = SkEndian_SwapBE16(stringOffset); + + SkOTTableName::Record* nameRecord = reinterpret_cast(data + originalDataSize + sizeof(SkOTTableName)); + for (const auto& encoding : encodings) { + for (const auto& name : names) { + nameRecord->platformID.value = SkOTTableName::Record::PlatformID::Windows; + nameRecord->encodingID.windows.value = encoding; + nameRecord->languageID.windows.value = SkOTTableName::Record::LanguageID::Windows::English_UnitedStates; + nameRecord->nameID.predefined.value = name; + nameRecord->offset = SkEndian_SwapBE16(0); + nameRecord->length = SkEndian_SwapBE16(SkToU16(fontNameLen * sizeof(SK_OT_USHORT))); + ++nameRecord; + } + } + + SK_OT_USHORT* nameString = reinterpret_cast(data + originalDataSize + stringOffset); + for (int i = 0; i < fontNameLen; ++i) { + nameString[i] = SkEndian_SwapBE16(fontName[i]); + } + + unsigned char* logical = data + originalDataSize + nameTableLogicalSize; + unsigned char* physical = data + originalDataSize + nameTablePhysicalSize; + for (; logical < physical; ++logical) { + *logical = 0; + } + + // Update the table checksum in the directory entry. + nameTableEntry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum(reinterpret_cast(nameTable), nameTableLogicalSize)); + + // Update the checksum adjustment in the head table. + if (headTableEntry) { + size_t headTableOffset = SkEndian_SwapBE32(headTableEntry->offset); + if (headTableOffset + sizeof(SkOTTableHead) < originalDataSize) { + SkOTTableHead* headTable = reinterpret_cast(data + headTableOffset); + headTable->checksumAdjustment = SkEndian_SwapBE32(0); + uint32_t unadjustedFontChecksum = SkOTUtils::CalcTableChecksum(reinterpret_cast(data), originalDataSize + nameTablePhysicalSize); + headTable->checksumAdjustment = SkEndian_SwapBE32(SkOTTableHead::fontChecksum - unadjustedFontChecksum); + } + } + + return rewrittenFontData.release(); +} + +sk_sp +SkOTUtils::LocalizedStrings_NameTable::Make(const SkTypeface& typeface, + SK_OT_USHORT types[], + int typesCount) +{ + static const SkFontTableTag nameTag = SkSetFourByteTag('n','a','m','e'); + size_t nameTableSize = typeface.getTableSize(nameTag); + if (0 == nameTableSize) { + return nullptr; + } + std::unique_ptr nameTableData(new uint8_t[nameTableSize]); + size_t copied = typeface.getTableData(nameTag, 0, nameTableSize, nameTableData.get()); + if (copied != nameTableSize) { + return nullptr; + } + + return sk_sp( + new SkOTUtils::LocalizedStrings_NameTable(std::move(nameTableData), nameTableSize, + types, typesCount)); +} + +sk_sp +SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(const SkTypeface& typeface) { + return Make(typeface, + SkOTUtils::LocalizedStrings_NameTable::familyNameTypes, + SK_ARRAY_COUNT(SkOTUtils::LocalizedStrings_NameTable::familyNameTypes)); +} + +bool SkOTUtils::LocalizedStrings_NameTable::next(SkTypeface::LocalizedString* localizedString) { + do { + SkOTTableName::Iterator::Record record; + if (fFamilyNameIter.next(record)) { + localizedString->fString = record.name; + localizedString->fLanguage = record.language; + return true; + } + if (fTypesCount == fTypesIndex + 1) { + return false; + } + ++fTypesIndex; + fFamilyNameIter.reset(fTypes[fTypesIndex]); + } while (true); +} + +SK_OT_USHORT SkOTUtils::LocalizedStrings_NameTable::familyNameTypes[3] = { + SkOTTableName::Record::NameID::Predefined::FontFamilyName, + SkOTTableName::Record::NameID::Predefined::PreferredFamily, + SkOTTableName::Record::NameID::Predefined::WWSFamilyName, +}; + +void SkOTUtils::SetAdvancedTypefaceFlags(SkOTTableOS2_V4::Type fsType, + SkAdvancedTypefaceMetrics* info) { + SkASSERT(info); + // The logic should be identical to SkTypeface_FreeType::onGetAdvancedMetrics(). + if (fsType.raw.value != 0) { + if (SkToBool(fsType.field.Restricted) || SkToBool(fsType.field.Bitmap)) { + info->fFlags |= SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag; + } + if (SkToBool(fsType.field.NoSubsetting)) { + info->fFlags |= SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag; + } + } +} diff --git a/gfx/skia/skia/src/sfnt/SkOTUtils.h b/gfx/skia/skia/src/sfnt/SkOTUtils.h new file mode 100644 index 0000000000..fb2732385c --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkOTUtils.h @@ -0,0 +1,105 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkOTUtils_DEFINED +#define SkOTUtils_DEFINED + +#include "include/core/SkTypeface.h" +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkOTTable_OS_2_V4.h" +#include "src/sfnt/SkOTTable_name.h" + +class SkData; +class SkStream; +struct SkAdvancedTypefaceMetrics; + +struct SkOTUtils { + /** + * Calculates the OpenType checksum for data. + */ + static uint32_t CalcTableChecksum(SK_OT_ULONG *data, size_t length); + + /** + * Renames an sfnt font. On failure (invalid data or not an sfnt font) + * returns nullptr. + * + * Essentially, this removes any existing 'name' table and replaces it + * with a new one in which FontFamilyName, FontSubfamilyName, + * UniqueFontIdentifier, FullFontName, and PostscriptName are fontName. + * + * The new 'name' table records will be written with the Windows, + * UnicodeBMPUCS2, and English_UnitedStates settings. + * + * fontName and fontNameLen must be specified in terms of ASCII chars. + * + * Does not affect fontData's ownership. + */ + static SkData* RenameFont(SkStreamAsset* fontData, const char* fontName, int fontNameLen); + + /** An implementation of LocalizedStrings which obtains it's data from a 'name' table. */ + class LocalizedStrings_NameTable : public SkTypeface::LocalizedStrings { + public: + /** Takes ownership of the nameTableData and will free it with SK_DELETE. */ + LocalizedStrings_NameTable(std::unique_ptr nameTableData, size_t size, + SK_OT_USHORT types[], + int typesCount) + : fTypes(types), fTypesCount(typesCount), fTypesIndex(0) + , fNameTableData(std::move(nameTableData)) + , fFamilyNameIter(fNameTableData.get(), size, fTypes[fTypesIndex]) + { } + + /** Creates an iterator over all data in the 'name' table of a typeface. + * If no valid 'name' table can be found, returns nullptr. + */ + static sk_sp Make( + const SkTypeface& typeface, + SK_OT_USHORT types[], + int typesCount); + + /** Creates an iterator over all the family names in the 'name' table of a typeface. + * If no valid 'name' table can be found, returns nullptr. + */ + static sk_sp MakeForFamilyNames(const SkTypeface& typeface); + + bool next(SkTypeface::LocalizedString* localizedString) override; + private: + static SK_OT_USHORT familyNameTypes[3]; + + SK_OT_USHORT* fTypes; + int fTypesCount; + int fTypesIndex; + std::unique_ptr fNameTableData; + SkOTTableName::Iterator fFamilyNameIter; + }; + + /** An implementation of LocalizedStrings which has one name. */ + class LocalizedStrings_SingleName : public SkTypeface::LocalizedStrings { + public: + LocalizedStrings_SingleName(SkString name, SkString language) + : fName(name), fLanguage(language), fHasNext(true) + { } + + bool next(SkTypeface::LocalizedString* localizedString) override { + localizedString->fString = fName; + localizedString->fLanguage = fLanguage; + + bool hadNext = fHasNext; + fHasNext = false; + return hadNext; + } + + private: + SkString fName; + SkString fLanguage; + bool fHasNext; + }; + + static void SetAdvancedTypefaceFlags(SkOTTableOS2_V4::Type fsType, + SkAdvancedTypefaceMetrics* info); +}; + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkPanose.h b/gfx/skia/skia/src/sfnt/SkPanose.h new file mode 100644 index 0000000000..50ccb7a301 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkPanose.h @@ -0,0 +1,527 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPanose_DEFINED +#define SkPanose_DEFINED + +#include "src/sfnt/SkOTTableTypes.h" + +#pragma pack(push, 1) + +struct SkPanose { + //This value changes the meaning of the following 9 bytes. + enum class FamilyType : SK_OT_BYTE { + Any = 0, + NoFit = 1, + TextAndDisplay = 2, + Script = 3, + Decorative = 4, + Pictoral = 5, + } bFamilyType; + + union Data { + struct TextAndDisplay { + enum class SerifStyle : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Cove = 2, + ObtuseCove = 3, + SquareCove = 4, + ObtuseSquareCove = 5, + Square = 6, + Thin = 7, + Bone = 8, + Exaggerated = 9, + Triangle = 10, + NormalSans = 11, + ObtuseSans = 12, + PerpSans = 13, + Flared = 14, + Rounded = 15, + } bSerifStyle; + + enum class Weight : SK_OT_BYTE { + Any = 0, + NoFit = 1, + VeryLight = 2, + Light = 3, + Thin = 4, + Book = 5, + Medium = 6, + Demi = 7, + Bold = 8, + Heavy = 9, + Black = 10, + ExtraBlack = 11, + } bWeight; + + enum class Proportion : SK_OT_BYTE { + Any = 0, + NoFit = 1, + OldStyle = 2, + Modern = 3, + EvenWidth = 4, + Expanded = 5, + Condensed = 6, + VeryExpanded = 7, + VeryCondensed = 8, + Monospaced = 9, + } bProportion; + + enum class Contrast : SK_OT_BYTE { + Any = 0, + NoFit = 1, + None = 2, + VeryLow = 3, + Low = 4, + MediumLow = 5, + Medium = 6, + MediumHigh = 7, + High = 8, + VeryHigh = 9, + } bContrast; + +#ifdef SK_WIN_PANOSE + //This is what Windows (and FontForge and Apple TT spec) define. + //The Impact font uses 9. + enum class StrokeVariation : SK_OT_BYTE { + Any = 0, + NoFit = 1, + GradualDiagonal = 2, + GradualTransitional = 3, + GradualVertical = 4, + GradualHorizontal = 5, + RapidVertical = 6, + RapidHorizontal = 7, + InstantVertical = 8, + } bStrokeVariation; +#else + //Stroke variation description in OT OS/2 ver0,ver1 is incorrect. + //This is what HP Panose says. + enum class StrokeVariation : SK_OT_BYTE { + Any = 0, + NoFit = 1, + NoVariation = 2, + Gradual_Diagonal = 3, + Gradual_Transitional = 4, + Gradual_Vertical = 5, + Gradual_Horizontal = 6, + Rapid_Vertical = 7, + Rapid_Horizontal = 8, + Instant_Vertical = 9, + Instant_Horizontal = 10, + } bStrokeVariation; +#endif + + enum class ArmStyle : SK_OT_BYTE { + Any = 0, + NoFit = 1, + StraightArms_Horizontal = 2, + StraightArms_Wedge = 3, + StraightArms_Vertical = 4, + StraightArms_SingleSerif = 5, + StraightArms_DoubleSerif = 6, + NonStraightArms_Horizontal = 7, + NonStraightArms_Wedge = 8, + NonStraightArms_Vertical = 9, + NonStraightArms_SingleSerif = 10, + NonStraightArms_DoubleSerif = 11, + } bArmStyle; + + enum class Letterform : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Normal_Contact = 2, + Normal_Weighted = 3, + Normal_Boxed = 4, + Normal_Flattened = 5, + Normal_Rounded = 6, + Normal_OffCenter = 7, + Normal_Square = 8, + Oblique_Contact = 9, + Oblique_Weighted = 10, + Oblique_Boxed = 11, + Oblique_Flattened = 12, + Oblique_Rounded = 13, + Oblique_OffCenter = 14, + Oblique_Square = 15, + } bLetterform; + + enum class Midline : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Standard_Trimmed = 2, + Standard_Pointed = 3, + Standard_Serifed = 4, + High_Trimmed = 5, + High_Pointed = 6, + High_Serifed = 7, + Constant_Trimmed = 8, + Constant_Pointed = 9, + Constant_Serifed = 10, + Low_Trimmed = 11, + Low_Pointed = 12, + Low_Serifed = 13, + } bMidline; + + enum class XHeight : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Constant_Small = 2, + Constant_Standard = 3, + Constant_Large = 4, + Ducking_Small = 5, + Ducking_Standard = 6, + Ducking_Large = 7, + } bXHeight; + } textAndDisplay; + + struct Script { + enum class ToolKind : SK_OT_BYTE { + Any = 0, + NoFit = 1, + FlatNib = 2, + PressurePoint = 3, + Engraved = 4, + Ball = 5, + Brush = 6, + Rough = 7, + FeltPen = 8, + WildBrush = 9, + } bToolKind; + + enum class Weight : SK_OT_BYTE { + Any = 0, + NoFit = 1, + VeryLight = 2, + Light = 3, + Thin = 4, + Book = 5, + Medium = 6, + Demi = 7, + Bold = 8, + Heavy = 9, + Black = 10, + ExtraBlack = 11, + } bWeight; + + enum class Spacing : SK_OT_BYTE { + Any = 0, + NoFit = 1, + ProportionalSpaced = 2, + Monospaced = 3, + } bSpacing; + + enum class AspectRatio : SK_OT_BYTE { + Any = 0, + NoFit = 1, + VeryCondensed = 2, + Condensed = 3, + Normal = 4, + Expanded = 5, + VeryExpanded = 6, + } bAspectRatio; + + enum class Contrast : SK_OT_BYTE { + Any = 0, + NoFit = 1, + None = 2, + VeryLow = 3, + Low = 4, + MediumLow = 5, + Medium = 6, + MediumHigh = 7, + High = 8, + VeryHigh = 9, + } bContrast; + + enum class Topology : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Roman_Disconnected = 2, + Roman_Trailing = 3, + Roman_Connected = 4, + Cursive_Disconnected = 5, + Cursive_Trailing = 6, + Cursive_Connected = 7, + Blackletter_Disconnected = 8, + Blackletter_Trailing = 9, + Blackletter_Connected = 10, + } bTopology; + + enum class Form : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Upright_NoWrapping = 2, + Upright_SomeWrapping = 3, + Upright_MoreWrapping = 4, + Upright_ExtremeWrapping = 5, + Oblique_NoWrapping = 6, + Oblique_SomeWrapping = 7, + Oblique_MoreWrapping = 8, + Oblique_ExtremeWrapping = 9, + Exaggerated_NoWrapping = 10, + Exaggerated_SomeWrapping = 11, + Exaggerated_MoreWrapping = 12, + Exaggerated_ExtremeWrapping = 13, + } bForm; + + enum class Finials : SK_OT_BYTE { + Any = 0, + NoFit = 1, + None_NoLoops = 2, + None_ClosedLoops = 3, + None_OpenLoops = 4, + Sharp_NoLoops = 5, + Sharp_ClosedLoops = 6, + Sharp_OpenLoops = 7, + Tapered_NoLoops = 8, + Tapered_ClosedLoops = 9, + Tapered_OpenLoops = 10, + Round_NoLoops = 11, + Round_ClosedLoops = 12, + Round_OpenLoops = 13, + } bFinials; + + enum class XAscent : SK_OT_BYTE { + Any = 0, + NoFit = 1, + VeryLow = 2, + Low = 3, + Medium = 4, + High = 5, + VeryHigh = 6, + } bXAscent; + } script; + + struct Decorative { + enum class Class : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Derivative = 2, + NonStandard_Topology = 3, + NonStandard_Elements = 4, + NonStandard_Aspect = 5, + Initials = 6, + Cartoon = 7, + PictureStems = 8, + Ornamented = 9, + TextAndBackground = 10, + Collage = 11, + Montage = 12, + } bClass; + + enum class Weight : SK_OT_BYTE { + Any = 0, + NoFit = 1, + VeryLight = 2, + Light = 3, + Thin = 4, + Book = 5, + Medium = 6, + Demi = 7, + Bold = 8, + Heavy = 9, + Black = 10, + ExtraBlack = 11, + } bWeight; + + enum class Aspect : SK_OT_BYTE { + Any = 0, + NoFit = 1, + SuperCondensed = 2, + VeryCondensed = 3, + Condensed = 4, + Normal = 5, + Extended = 6, + VeryExtended = 7, + SuperExtended = 8, + Monospaced = 9, + } bAspect; + + enum class Contrast : SK_OT_BYTE { + Any = 0, + NoFit = 1, + None = 2, + VeryLow = 3, + Low = 4, + MediumLow = 5, + Medium = 6, + MediumHigh = 7, + High = 8, + VeryHigh = 9, + HorizontalLow = 10, + HorizontalMedium = 11, + HorizontalHigh = 12, + Broken = 13, + } bContrast; + + enum class SerifVariant : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Cove = 2, + ObtuseCove = 3, + SquareCove = 4, + ObtuseSquareCove = 5, + Square = 6, + Thin = 7, + Oval = 8, + Exaggerated = 9, + Triangle = 10, + NormalSans = 11, + ObtuseSans = 12, + PerpendicularSans = 13, + Flared = 14, + Rounded = 15, + Script = 16, + } bSerifVariant; + + enum class Treatment : SK_OT_BYTE { + Any = 0, + NoFit = 1, + None_StandardSolidFill = 2, + White_NoFill = 3, + PatternedFill = 4, + ComplexFill = 5, + ShapedFill = 6, + DrawnDistressed = 7, + } bTreatment; + + enum class Lining : SK_OT_BYTE { + Any = 0, + NoFit = 1, + None = 2, + Inline = 3, + Outline = 4, + Engraved = 5, + Shadow = 6, + Relief = 7, + Backdrop = 8, + } bLining; + + enum class Topology : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Standard = 2, + Square = 3, + MultipleSegment = 4, + DecoWacoMidlines = 5, + UnevenWeighting = 6, + DiverseArms = 7, + DiverseForms = 8, + LombardicForms = 9, + UpperCaseInLowerCase = 10, + ImpliedTopology = 11, + HorseshoeEandA = 12, + Cursive = 13, + Blackletter = 14, + SwashVariance = 15, + } bTopology; + + enum class RangeOfCharacters : SK_OT_BYTE { + Any = 0, + NoFit = 1, + ExtendedCollection = 2, + Litterals = 3, + NoLowerCase = 4, + SmallCaps = 5, + } bRangeOfCharacters; + } decorative; + + struct Pictoral { + enum class Kind : SK_OT_BYTE { + Any = 0, + NoFit = 1, + Montages = 2, + Pictures = 3, + Shapes = 4, + Scientific = 5, + Music = 6, + Expert = 7, + Patterns = 8, + Boarders = 9, + Icons = 10, + Logos = 11, + IndustrySpecific = 12, + } bKind; + + enum class Weight : SK_OT_BYTE { + NoFit = 1, + } bWeight; + + enum class Spacing : SK_OT_BYTE { + Any = 0, + NoFit = 1, + ProportionalSpaced = 2, + Monospaced = 3, + } bSpacing; + + enum class AspectRatioAndContrast : SK_OT_BYTE { + NoFit = 1, + } bAspectRatioAndContrast; + + enum class AspectRatio94 : SK_OT_BYTE { + Any = 0, + NoFit = 1, + NoWidth = 2, + ExceptionallyWide = 3, + SuperWide = 4, + VeryWide = 5, + Wide = 6, + Normal = 7, + Narrow = 8, + VeryNarrow = 9, + } bAspectRatio94; + + enum class AspectRatio119 : SK_OT_BYTE { + Any = 0, + NoFit = 1, + NoWidth = 2, + ExceptionallyWide = 3, + SuperWide = 4, + VeryWide = 5, + Wide = 6, + Normal = 7, + Narrow = 8, + VeryNarrow = 9, + } bAspectRatio119; + + enum class AspectRatio157 : SK_OT_BYTE { + Any = 0, + NoFit = 1, + NoWidth = 2, + ExceptionallyWide = 3, + SuperWide = 4, + VeryWide = 5, + Wide = 6, + Normal = 7, + Narrow = 8, + VeryNarrow = 9, + } bAspectRatio157; + + enum class AspectRatio163 : SK_OT_BYTE { + Any = 0, + NoFit = 1, + NoWidth = 2, + ExceptionallyWide = 3, + SuperWide = 4, + VeryWide = 5, + Wide = 6, + Normal = 7, + Narrow = 8, + VeryNarrow = 9, + } bAspectRatio163; + } pictoral; + } data; +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkPanose) == 10, "sizeof_SkPanose_not_10"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkSFNTHeader.h b/gfx/skia/skia/src/sfnt/SkSFNTHeader.h new file mode 100644 index 0000000000..06745c39bf --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkSFNTHeader.h @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSFNTHeader_DEFINED +#define SkSFNTHeader_DEFINED + +#include "src/core/SkEndian.h" +#include "src/sfnt/SkOTTableTypes.h" + +//All SK_SFNT_ prefixed types should be considered as big endian. +typedef uint16_t SK_SFNT_USHORT; +typedef uint32_t SK_SFNT_ULONG; + +#pragma pack(push, 1) + +struct SkSFNTHeader { + SK_SFNT_ULONG fontType; + struct fontType_WindowsTrueType { + static const SK_OT_CHAR TAG0 = 0; + static const SK_OT_CHAR TAG1 = 1; + static const SK_OT_CHAR TAG2 = 0; + static const SK_OT_CHAR TAG3 = 0; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + }; + struct fontType_MacTrueType { + static const SK_OT_CHAR TAG0 = 't'; + static const SK_OT_CHAR TAG1 = 'r'; + static const SK_OT_CHAR TAG2 = 'u'; + static const SK_OT_CHAR TAG3 = 'e'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + }; + struct fontType_PostScript { + static const SK_OT_CHAR TAG0 = 't'; + static const SK_OT_CHAR TAG1 = 'y'; + static const SK_OT_CHAR TAG2 = 'p'; + static const SK_OT_CHAR TAG3 = '1'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + }; + struct fontType_OpenTypeCFF { + static const SK_OT_CHAR TAG0 = 'O'; + static const SK_OT_CHAR TAG1 = 'T'; + static const SK_OT_CHAR TAG2 = 'T'; + static const SK_OT_CHAR TAG3 = 'O'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + }; + + SK_SFNT_USHORT numTables; + SK_SFNT_USHORT searchRange; + SK_SFNT_USHORT entrySelector; + SK_SFNT_USHORT rangeShift; + + struct TableDirectoryEntry { + SK_SFNT_ULONG tag; + SK_SFNT_ULONG checksum; + SK_SFNT_ULONG offset; //From beginning of header. + SK_SFNT_ULONG logicalLength; + }; //tableDirectoryEntries[numTables] +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkSFNTHeader) == 12, "sizeof_SkSFNTHeader_not_12"); +static_assert(sizeof(SkSFNTHeader::TableDirectoryEntry) == 16, "sizeof_SkSFNTHeader_TableDirectoryEntry_not_16"); + +#endif diff --git a/gfx/skia/skia/src/sfnt/SkTTCFHeader.h b/gfx/skia/skia/src/sfnt/SkTTCFHeader.h new file mode 100644 index 0000000000..63eac7c3e2 --- /dev/null +++ b/gfx/skia/skia/src/sfnt/SkTTCFHeader.h @@ -0,0 +1,57 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTTCFHeader_DEFINED +#define SkTTCFHeader_DEFINED + +#include "src/sfnt/SkOTTableTypes.h" +#include "src/sfnt/SkSFNTHeader.h" + +#pragma pack(push, 1) + +struct SkTTCFHeader { + SK_SFNT_ULONG ttcTag; + static const SK_OT_CHAR TAG0 = 't'; + static const SK_OT_CHAR TAG1 = 't'; + static const SK_OT_CHAR TAG2 = 'c'; + static const SK_OT_CHAR TAG3 = 'f'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + + SK_OT_Fixed version; + static const SK_OT_Fixed version_1 = SkTEndian_SwapBE32(1 << 16); + static const SK_OT_Fixed version_2 = SkTEndian_SwapBE32(2 << 16); + + SK_OT_ULONG numOffsets; + //SK_OT_ULONG offset[numOffsets] + + struct Version2Ext { + SK_OT_ULONG dsigType; + struct dsigType_None { + static const SK_OT_CHAR TAG0 = 0; + static const SK_OT_CHAR TAG1 = 0; + static const SK_OT_CHAR TAG2 = 0; + static const SK_OT_CHAR TAG3 = 0; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + }; + struct dsigType_Format1 { + static const SK_OT_CHAR TAG0 = 'D'; + static const SK_OT_CHAR TAG1 = 'S'; + static const SK_OT_CHAR TAG2 = 'I'; + static const SK_OT_CHAR TAG3 = 'G'; + static const SK_OT_ULONG TAG = SkOTTableTAG::value; + }; + SK_OT_ULONG dsigLength; //Length of DSIG table (in bytes). + SK_OT_ULONG dsigOffset; //Offset of DSIG table from the beginning of file (in bytes). + };// version2ext (if version == version_2) +}; + +#pragma pack(pop) + + +static_assert(sizeof(SkTTCFHeader) == 12, "sizeof_SkTTCFHeader_not_12"); + +#endif diff --git a/gfx/skia/skia/src/shaders/SkBitmapProcShader.cpp b/gfx/skia/skia/src/shaders/SkBitmapProcShader.cpp new file mode 100644 index 0000000000..d157a73595 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkBitmapProcShader.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/shaders/SkBitmapProcShader.h" + +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBitmapProcState.h" +#include "src/core/SkXfermodePriv.h" + +static bool only_scale_and_translate(const SkMatrix& matrix) { + unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask; + return (matrix.getType() & ~mask) == 0; +} + +class BitmapProcInfoContext : public SkShaderBase::Context { +public: + // The info has been allocated elsewhere, but we are responsible for calling its destructor. + BitmapProcInfoContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec, + SkBitmapProcInfo* info) + : INHERITED(shader, rec) + , fInfo(info) + { + fFlags = 0; + if (fInfo->fPixmap.isOpaque() && (255 == this->getPaintAlpha())) { + fFlags |= SkShaderBase::kOpaqueAlpha_Flag; + } + + if (1 == fInfo->fPixmap.height() && only_scale_and_translate(this->getTotalInverse())) { + fFlags |= SkShaderBase::kConstInY32_Flag; + } + } + + uint32_t getFlags() const override { return fFlags; } + +private: + SkBitmapProcInfo* fInfo; + uint32_t fFlags; + + typedef SkShaderBase::Context INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class BitmapProcShaderContext : public BitmapProcInfoContext { +public: + BitmapProcShaderContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec, + SkBitmapProcState* state) + : INHERITED(shader, rec, state) + , fState(state) + {} + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override { + const SkBitmapProcState& state = *fState; + if (state.getShaderProc32()) { + state.getShaderProc32()(&state, x, y, dstC, count); + return; + } + + const int BUF_MAX = 128; + uint32_t buffer[BUF_MAX]; + SkBitmapProcState::MatrixProc mproc = state.getMatrixProc(); + SkBitmapProcState::SampleProc32 sproc = state.getSampleProc32(); + const int max = state.maxCountForBufferSize(sizeof(buffer[0]) * BUF_MAX); + + SkASSERT(state.fPixmap.addr()); + + for (;;) { + int n = SkTMin(count, max); + SkASSERT(n > 0 && n < BUF_MAX*2); + mproc(state, buffer, n, x, y); + sproc(state, buffer, n, dstC); + + if ((count -= n) == 0) { + break; + } + SkASSERT(count > 0); + x += n; + dstC += n; + } + } + +private: + SkBitmapProcState* fState; + + typedef BitmapProcInfoContext INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkShaderBase::Context* SkBitmapProcLegacyShader::MakeContext( + const SkShaderBase& shader, SkTileMode tmx, SkTileMode tmy, + const SkImage_Base* image, const ContextRec& rec, SkArenaAlloc* alloc) +{ + SkMatrix totalInverse; + // Do this first, so we know the matrix can be inverted. + if (!shader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &totalInverse)) { + return nullptr; + } + + SkBitmapProcState* state = alloc->make(image, tmx, tmy); + if (!state->setup(totalInverse, *rec.fPaint)) { + return nullptr; + } + return alloc->make(shader, rec, state); +} diff --git a/gfx/skia/skia/src/shaders/SkBitmapProcShader.h b/gfx/skia/skia/src/shaders/SkBitmapProcShader.h new file mode 100644 index 0000000000..0509191ad4 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkBitmapProcShader.h @@ -0,0 +1,25 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBitmapProcShader_DEFINED +#define SkBitmapProcShader_DEFINED + +#include "src/core/SkImagePriv.h" +#include "src/shaders/SkShaderBase.h" + +class SkImage_Base; + +class SkBitmapProcLegacyShader : public SkShaderBase { +private: + friend class SkImageShader; + + static Context* MakeContext(const SkShaderBase&, SkTileMode tmx, SkTileMode tmy, + const SkImage_Base*, const ContextRec&, SkArenaAlloc* alloc); + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkColorFilterShader.cpp b/gfx/skia/skia/src/shaders/SkColorFilterShader.cpp new file mode 100644 index 0000000000..50bb4236c9 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkColorFilterShader.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkShader.h" +#include "include/core/SkString.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkColorFilterShader.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#endif + +SkColorFilterShader::SkColorFilterShader(sk_sp shader, sk_sp filter) + : fShader(std::move(shader)) + , fFilter(std::move(filter)) +{ + SkASSERT(fShader); + SkASSERT(fFilter); +} + +sk_sp SkColorFilterShader::CreateProc(SkReadBuffer& buffer) { + auto shader = buffer.readShader(); + auto filter = buffer.readColorFilter(); + if (!shader || !filter) { + return nullptr; + } + return sk_make_sp(shader, filter); +} + +void SkColorFilterShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fShader.get()); + buffer.writeFlattenable(fFilter.get()); +} + +bool SkColorFilterShader::onAppendStages(const SkStageRec& rec) const { + if (!as_SB(fShader)->appendStages(rec)) { + return false; + } + fFilter->appendStages(rec, fShader->isOpaque()); + return true; +} + +#if SK_SUPPORT_GPU +///////////////////////////////////////////////////////////////////// + +#include "include/gpu/GrContext.h" + +std::unique_ptr SkColorFilterShader::asFragmentProcessor( + const GrFPArgs& args) const { + auto fp1 = as_SB(fShader)->asFragmentProcessor(args); + if (!fp1) { + return nullptr; + } + + auto fp2 = fFilter->asFragmentProcessor(args.fContext, *args.fDstColorInfo); + if (!fp2) { + return fp1; + } + + std::unique_ptr fpSeries[] = { std::move(fp1), std::move(fp2) }; + return GrFragmentProcessor::RunInSeries(fpSeries, 2); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkShader::makeWithColorFilter(sk_sp filter) const { + SkShader* base = const_cast(this); + if (!filter) { + return sk_ref_sp(base); + } + return sk_make_sp(sk_ref_sp(base), filter); +} diff --git a/gfx/skia/skia/src/shaders/SkColorFilterShader.h b/gfx/skia/skia/src/shaders/SkColorFilterShader.h new file mode 100644 index 0000000000..bbe9f16132 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkColorFilterShader.h @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorFilterShader_DEFINED +#define SkColorFilterShader_DEFINED + +#include "include/core/SkColorFilter.h" +#include "src/shaders/SkShaderBase.h" + +class SkArenaAlloc; + +class SkColorFilterShader : public SkShaderBase { +public: + SkColorFilterShader(sk_sp shader, sk_sp filter); + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkColorFilterShader) + + sk_sp fShader; + sk_sp fFilter; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkColorShader.cpp b/gfx/skia/skia/src/shaders/SkColorShader.cpp new file mode 100644 index 0000000000..5d40c7b9df --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkColorShader.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorSpace.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkUtils.h" +#include "src/shaders/SkColorShader.h" + +SkColorShader::SkColorShader(SkColor c) : fColor(c) {} + +bool SkColorShader::isOpaque() const { + return SkColorGetA(fColor) == 255; +} + +sk_sp SkColorShader::CreateProc(SkReadBuffer& buffer) { + return sk_make_sp(buffer.readColor()); +} + +void SkColorShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeColor(fColor); +} + +SkShader::GradientType SkColorShader::asAGradient(GradientInfo* info) const { + if (info) { + if (info->fColors && info->fColorCount >= 1) { + info->fColors[0] = fColor; + } + info->fColorCount = 1; + info->fTileMode = SkTileMode::kRepeat; + } + return kColor_GradientType; +} + +SkColor4Shader::SkColor4Shader(const SkColor4f& color, sk_sp space) + : fColorSpace(std::move(space)) + , fColor(color) +{} + +sk_sp SkColor4Shader::CreateProc(SkReadBuffer& buffer) { + SkColor4f color; + sk_sp colorSpace; + buffer.readColor4f(&color); + if (buffer.readBool()) { + sk_sp data = buffer.readByteArrayAsData(); + colorSpace = data ? SkColorSpace::Deserialize(data->data(), data->size()) : nullptr; + } + return SkShaders::Color(color, std::move(colorSpace)); +} + +void SkColor4Shader::flatten(SkWriteBuffer& buffer) const { + buffer.writeColor4f(fColor); + sk_sp colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr; + if (colorSpaceData) { + buffer.writeBool(true); + buffer.writeDataAsByteArray(colorSpaceData.get()); + } else { + buffer.writeBool(false); + } +} + + +sk_sp SkShaders::Color(const SkColor4f& color, sk_sp space) { + if (!SkScalarsAreFinite(color.vec(), 4)) { + return nullptr; + } + return sk_make_sp(color, std::move(space)); +} + +bool SkColorShader::onAppendStages(const SkStageRec& rec) const { + SkColor4f color = SkColor4f::FromColor(fColor); + SkColorSpaceXformSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType, + rec.fDstCS, kUnpremul_SkAlphaType).apply(color.vec()); + rec.fPipeline->append_constant_color(rec.fAlloc, color.premul().vec()); + return true; +} + +bool SkColor4Shader::onAppendStages(const SkStageRec& rec) const { + SkColor4f color = fColor; + SkColorSpaceXformSteps(fColorSpace.get(), kUnpremul_SkAlphaType, + rec.fDstCS, kUnpremul_SkAlphaType).apply(color.vec()); + rec.fPipeline->append_constant_color(rec.fAlloc, color.premul().vec()); + return true; +} + +#if SK_SUPPORT_GPU + +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrColorSpaceXform.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" + +std::unique_ptr SkColorShader::asFragmentProcessor( + const GrFPArgs& args) const { + SkPMColor4f color = SkColorToPMColor4f(fColor, *args.fDstColorInfo); + return GrConstColorProcessor::Make(color, GrConstColorProcessor::InputMode::kModulateA); +} + +std::unique_ptr SkColor4Shader::asFragmentProcessor( + const GrFPArgs& args) const { + SkColorSpaceXformSteps steps{ fColorSpace.get(), kUnpremul_SkAlphaType, + args.fDstColorInfo->colorSpace(), kUnpremul_SkAlphaType }; + SkColor4f color = fColor; + steps.apply(color.vec()); + return GrConstColorProcessor::Make(color.premul(), + GrConstColorProcessor::InputMode::kModulateA); +} + +#endif diff --git a/gfx/skia/skia/src/shaders/SkColorShader.h b/gfx/skia/skia/src/shaders/SkColorShader.h new file mode 100644 index 0000000000..4e152e3289 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkColorShader.h @@ -0,0 +1,71 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorShader_DEFINED +#define SkColorShader_DEFINED + +#include "src/shaders/SkShaderBase.h" + +/** \class SkColorShader + A Shader that represents a single color. In general, this effect can be + accomplished by just using the color field on the paint, but if an + actual shader object is needed, this provides that feature. +*/ +class SkColorShader : public SkShaderBase { +public: + /** Create a ColorShader that ignores the color in the paint, and uses the + specified color. Note: like all shaders, at draw time the paint's alpha + will be respected, and is applied to the specified color. + */ + explicit SkColorShader(SkColor c); + + bool isOpaque() const override; + bool isConstant() const override { return true; } + + GradientType asAGradient(GradientInfo* info) const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkColorShader) + + void flatten(SkWriteBuffer&) const override; + + bool onAsLuminanceColor(SkColor* lum) const override { + *lum = fColor; + return true; + } + + bool onAppendStages(const SkStageRec&) const override; + + SkColor fColor; +}; + +class SkColor4Shader : public SkShaderBase { +public: + SkColor4Shader(const SkColor4f&, sk_sp); + + bool isOpaque() const override { return fColor.isOpaque(); } + bool isConstant() const override { return true; } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkColor4Shader) + + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; + + sk_sp fColorSpace; + const SkColor4f fColor; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkComposeShader.cpp b/gfx/skia/skia/src/shaders/SkComposeShader.cpp new file mode 100644 index 0000000000..f3a51a46db --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkComposeShader.cpp @@ -0,0 +1,253 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColorFilter.h" +#include "include/core/SkString.h" +#include "include/private/SkColorData.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBlendModePriv.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkColorShader.h" +#include "src/shaders/SkComposeShader.h" + +namespace { + +sk_sp wrap_lm(sk_sp shader, const SkMatrix* lm) { + return (shader && lm) ? shader->makeWithLocalMatrix(*lm) : shader; +} + +struct LocalMatrixStageRec final : public SkStageRec { + LocalMatrixStageRec(const SkStageRec& rec, const SkMatrix& lm) + : INHERITED(rec) { + if (!lm.isIdentity()) { + if (fLocalM) { + fStorage.setConcat(lm, *fLocalM); + fLocalM = fStorage.isIdentity() ? nullptr : &fStorage; + } else { + fLocalM = &lm; + } + } + } + +private: + SkMatrix fStorage; + + using INHERITED = SkStageRec; +}; + +} // namespace + +sk_sp SkShaders::Blend(SkBlendMode mode, sk_sp dst, sk_sp src, + const SkMatrix* lm) { + switch (mode) { + case SkBlendMode::kClear: return Color(0); + case SkBlendMode::kDst: return wrap_lm(std::move(dst), lm); + case SkBlendMode::kSrc: return wrap_lm(std::move(src), lm); + default: break; + } + return sk_sp(new SkShader_Blend(mode, std::move(dst), std::move(src), lm)); +} + +sk_sp SkShaders::Lerp(float weight, sk_sp dst, sk_sp src, + const SkMatrix* lm) { + if (SkScalarIsNaN(weight)) { + return nullptr; + } + if (dst == src || weight <= 0) { + return wrap_lm(std::move(dst), lm); + } + if (weight >= 1) { + return wrap_lm(std::move(src), lm); + } + return sk_sp(new SkShader_Lerp(weight, std::move(dst), std::move(src), lm)); +} + +sk_sp SkShaders::Lerp(sk_sp red, sk_sp dst, sk_sp src, + const SkMatrix* lm) { + if (!red) { + return nullptr; + } + if (dst == src) { + return wrap_lm(std::move(dst), lm); + } + return sk_sp(new SkShader_LerpRed(std::move(red), std::move(dst), std::move(src), + lm)); +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool append_shader_or_paint(const SkStageRec& rec, SkShader* shader) { + if (shader) { + if (!as_SB(shader)->appendStages(rec)) { + return false; + } + } else { + rec.fPipeline->append_constant_color(rec.fAlloc, rec.fPaint.getColor4f().premul().vec()); + } + return true; +} + +// Returns the output of e0, and leaves the output of e1 in r,g,b,a +static float* append_two_shaders(const SkStageRec& rec, SkShader* s0, SkShader* s1) { + struct Storage { + float fRes0[4 * SkRasterPipeline_kMaxStride]; + }; + auto storage = rec.fAlloc->make(); + + if (!append_shader_or_paint(rec, s0)) { + return nullptr; + } + rec.fPipeline->append(SkRasterPipeline::store_src, storage->fRes0); + + if (!append_shader_or_paint(rec, s1)) { + return nullptr; + } + return storage->fRes0; +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkShader_Blend::CreateProc(SkReadBuffer& buffer) { + sk_sp dst(buffer.readShader()); + sk_sp src(buffer.readShader()); + unsigned mode = buffer.read32(); + + // check for valid mode before we cast to the enum type + if (!buffer.validate(mode <= (unsigned)SkBlendMode::kLastMode)) { + return nullptr; + } + return SkShaders::Blend(static_cast(mode), std::move(dst), std::move(src)); +} + +void SkShader_Blend::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fDst.get()); + buffer.writeFlattenable(fSrc.get()); + buffer.write32((int)fMode); +} + +bool SkShader_Blend::onAppendStages(const SkStageRec& orig_rec) const { + const LocalMatrixStageRec rec(orig_rec, this->getLocalMatrix()); + + float* res0 = append_two_shaders(rec, fDst.get(), fSrc.get()); + if (!res0) { + return false; + } + + rec.fPipeline->append(SkRasterPipeline::load_dst, res0); + SkBlendMode_AppendStages(fMode, rec.fPipeline); + return true; +} + +sk_sp SkShader_Lerp::CreateProc(SkReadBuffer& buffer) { + sk_sp dst(buffer.readShader()); + sk_sp src(buffer.readShader()); + float t = buffer.readScalar(); + return buffer.isValid() ? SkShaders::Lerp(t, std::move(dst), std::move(src)) : nullptr; +} + +void SkShader_Lerp::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fDst.get()); + buffer.writeFlattenable(fSrc.get()); + buffer.writeScalar(fWeight); +} + +bool SkShader_Lerp::onAppendStages(const SkStageRec& orig_rec) const { + const LocalMatrixStageRec rec(orig_rec, this->getLocalMatrix()); + + float* res0 = append_two_shaders(rec, fDst.get(), fSrc.get()); + if (!res0) { + return false; + } + + rec.fPipeline->append(SkRasterPipeline::load_dst, res0); + rec.fPipeline->append(SkRasterPipeline::lerp_1_float, &fWeight); + return true; +} + +sk_sp SkShader_LerpRed::CreateProc(SkReadBuffer& buffer) { + sk_sp dst(buffer.readShader()); + sk_sp src(buffer.readShader()); + sk_sp red(buffer.readShader()); + return buffer.isValid() ? + SkShaders::Lerp(std::move(red), std::move(dst), std::move(src)) : nullptr; +} + +void SkShader_LerpRed::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fDst.get()); + buffer.writeFlattenable(fSrc.get()); + buffer.writeFlattenable(fRed.get()); +} + +bool SkShader_LerpRed::onAppendStages(const SkStageRec& orig_rec) const { + const LocalMatrixStageRec rec(orig_rec, this->getLocalMatrix()); + + struct Storage { + float fRed[4 * SkRasterPipeline_kMaxStride]; + }; + auto storage = rec.fAlloc->make(); + if (!as_SB(fRed)->appendStages(rec)) { + return false; + } + // actually, we just need the first (red) channel, but for now we store rgba + rec.fPipeline->append(SkRasterPipeline::store_src, storage->fRed); + + float* res0 = append_two_shaders(rec, fDst.get(), fSrc.get()); + if (!res0) { + return false; + } + + rec.fPipeline->append(SkRasterPipeline::load_dst, res0); + rec.fPipeline->append(SkRasterPipeline::lerp_native, &storage->fRed[0]); + return true; +} + +#if SK_SUPPORT_GPU + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/effects/GrXfermodeFragmentProcessor.h" +#include "src/gpu/effects/generated/GrComposeLerpEffect.h" +#include "src/gpu/effects/generated/GrComposeLerpRedEffect.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" + +static std::unique_ptr as_fp(const GrFPArgs& args, SkShader* shader) { + return shader ? as_SB(shader)->asFragmentProcessor(args) : nullptr; +} + +std::unique_ptr SkShader_Blend::asFragmentProcessor( + const GrFPArgs& orig_args) const { + const GrFPArgs::WithPreLocalMatrix args(orig_args, this->getLocalMatrix()); + auto fpA = as_fp(args, fDst.get()); + auto fpB = as_fp(args, fSrc.get()); + if (!fpA || !fpB) { + return nullptr; + } + return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB), + std::move(fpA), fMode); +} + +std::unique_ptr SkShader_Lerp::asFragmentProcessor( + const GrFPArgs& orig_args) const { + const GrFPArgs::WithPreLocalMatrix args(orig_args, this->getLocalMatrix()); + auto fpA = as_fp(args, fDst.get()); + auto fpB = as_fp(args, fSrc.get()); + return GrComposeLerpEffect::Make(std::move(fpA), std::move(fpB), fWeight); +} + +std::unique_ptr SkShader_LerpRed::asFragmentProcessor( + const GrFPArgs& orig_args) const { + const GrFPArgs::WithPreLocalMatrix args(orig_args, this->getLocalMatrix()); + auto fpA = as_fp(args, fDst.get()); + auto fpB = as_fp(args, fSrc.get()); + auto red = as_SB(fRed)->asFragmentProcessor(args); + if (!red) { + return nullptr; + } + return GrComposeLerpRedEffect::Make(std::move(fpA), std::move(fpB), std::move(red)); +} +#endif diff --git a/gfx/skia/skia/src/shaders/SkComposeShader.h b/gfx/skia/skia/src/shaders/SkComposeShader.h new file mode 100644 index 0000000000..4ea44cff8b --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkComposeShader.h @@ -0,0 +1,101 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkComposeShader_DEFINED +#define SkComposeShader_DEFINED + +#include "include/core/SkBlendMode.h" +#include "src/shaders/SkShaderBase.h" + +class SkShader_Blend final : public SkShaderBase { +public: + SkShader_Blend(SkBlendMode mode, sk_sp dst, sk_sp src, const SkMatrix* lm) + : INHERITED(lm) + , fDst(std::move(dst)) + , fSrc(std::move(src)) + , fMode(mode) + {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + SkShader_Blend(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkShader_Blend) + + sk_sp fDst; + sk_sp fSrc; + const SkBlendMode fMode; + + typedef SkShaderBase INHERITED; +}; + +class SkShader_Lerp final : public SkShaderBase { +public: + SkShader_Lerp(float weight, sk_sp dst, sk_sp src, const SkMatrix* lm) + : INHERITED(lm) + , fDst(std::move(dst)) + , fSrc(std::move(src)) + , fWeight(weight) + { + SkASSERT(weight >= 0 && weight <= 1); + } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + SkShader_Lerp(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkShader_Lerp) + + sk_sp fDst; + sk_sp fSrc; + const float fWeight; + + typedef SkShaderBase INHERITED; +}; + +class SkShader_LerpRed final : public SkShaderBase { +public: + SkShader_LerpRed(sk_sp red, sk_sp dst, sk_sp src, + const SkMatrix* lm) + : INHERITED(lm) + , fDst(std::move(dst)) + , fSrc(std::move(src)) + , fRed(std::move(red)) + {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + SkShader_LerpRed(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkShader_LerpRed) + + sk_sp fDst; + sk_sp fSrc; + sk_sp fRed; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkEmptyShader.h b/gfx/skia/skia/src/shaders/SkEmptyShader.h new file mode 100644 index 0000000000..439082b1f9 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkEmptyShader.h @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEmptyShader_DEFINED +#define SkEmptyShader_DEFINED + +#include "src/shaders/SkShaderBase.h" + +// TODO: move this to private, as there is a public factory on SkShader + +/** + * \class SkEmptyShader + * A Shader that always draws nothing. Its createContext always returns nullptr. + */ +class SkEmptyShader : public SkShaderBase { +public: + SkEmptyShader() {} + +protected: +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override { + return nullptr; + } +#endif + + void flatten(SkWriteBuffer& buffer) const override { + // Do nothing. + // We just don't want to fall through to SkShader::flatten(), + // which will write data we don't care to serialize or decode. + } + + bool onAppendStages(const SkStageRec&) const override { + return false; + } + +private: + SK_FLATTENABLE_HOOKS(SkEmptyShader) + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkImageShader.cpp b/gfx/skia/skia/src/shaders/SkImageShader.cpp new file mode 100644 index 0000000000..5bc13e587c --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkImageShader.cpp @@ -0,0 +1,614 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBitmapController.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/image/SkImage_Base.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkEmptyShader.h" +#include "src/shaders/SkImageShader.h" + +/** + * We are faster in clamp, so always use that tiling when we can. + */ +static SkTileMode optimize(SkTileMode tm, int dimension) { + SkASSERT(dimension > 0); +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + // need to update frameworks/base/libs/hwui/tests/unit/SkiaBehaviorTests.cpp:55 to allow + // for transforming to clamp. + return tm; +#else + return dimension == 1 ? SkTileMode::kClamp : tm; +#endif +} + +SkImageShader::SkImageShader(sk_sp img, + SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, + bool clampAsIfUnpremul) + : INHERITED(localMatrix) + , fImage(std::move(img)) + , fTileModeX(optimize(tmx, fImage->width())) + , fTileModeY(optimize(tmy, fImage->height())) + , fClampAsIfUnpremul(clampAsIfUnpremul) +{} + +// fClampAsIfUnpremul is always false when constructed through public APIs, +// so there's no need to read or write it here. + +sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { + auto tmx = buffer.read32LE(SkTileMode::kLastTileMode); + auto tmy = buffer.read32LE(SkTileMode::kLastTileMode); + SkMatrix localMatrix; + buffer.readMatrix(&localMatrix); + sk_sp img = buffer.readImage(); + if (!img) { + return nullptr; + } + return SkImageShader::Make(std::move(img), tmx, tmy, &localMatrix); +} + +void SkImageShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeUInt((unsigned)fTileModeX); + buffer.writeUInt((unsigned)fTileModeY); + buffer.writeMatrix(this->getLocalMatrix()); + buffer.writeImage(fImage.get()); + SkASSERT(fClampAsIfUnpremul == false); +} + +bool SkImageShader::isOpaque() const { + return fImage->isOpaque() && + fTileModeX != SkTileMode::kDecal && fTileModeY != SkTileMode::kDecal; +} + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +static bool legacy_shader_can_handle(const SkMatrix& inv) { + if (!inv.isScaleTranslate()) { + return false; + } + + // legacy code uses SkFixed 32.32, so ensure the inverse doesn't map device coordinates + // out of range. + const SkScalar max_dev_coord = 32767.0f; + SkRect src; + SkAssertResult(inv.mapRect(&src, SkRect::MakeWH(max_dev_coord, max_dev_coord))); + + // take 1/4 of max signed 32bits so we have room to subtract local values + const SkScalar max_fixed32dot32 = SK_MaxS32 * 0.25f; + if (!SkRect::MakeLTRB(-max_fixed32dot32, -max_fixed32dot32, + max_fixed32dot32, max_fixed32dot32).contains(src)) { + return false; + } + + // legacy shader impl should be able to handle these matrices + return true; +} + +SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + if (fImage->alphaType() == kUnpremul_SkAlphaType) { + return nullptr; + } + if (fImage->colorType() != kN32_SkColorType) { + return nullptr; + } + if (fTileModeX != fTileModeY) { + return nullptr; + } + if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) { + return nullptr; + } + + // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, + // so it can't handle bitmaps larger than 65535. + // + // We back off another bit to 32767 to make small amounts of + // intermediate math safe, e.g. in + // + // SkFixed fx = ...; + // fx = tile(fx + SK_Fixed1); + // + // we want to make sure (fx + SK_Fixed1) never overflows. + if (fImage-> width() > 32767 || + fImage->height() > 32767) { + return nullptr; + } + + SkMatrix inv; + if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &inv) || + !legacy_shader_can_handle(inv)) { + return nullptr; + } + + if (!rec.isLegacyCompatible(fImage->colorSpace())) { + return nullptr; + } + + return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, + as_IB(fImage.get()), rec, alloc); +} +#endif + +SkImage* SkImageShader::onIsAImage(SkMatrix* texM, SkTileMode xy[]) const { + if (texM) { + *texM = this->getLocalMatrix(); + } + if (xy) { + xy[0] = fTileModeX; + xy[1] = fTileModeY; + } + return const_cast(fImage.get()); +} + +sk_sp SkImageShader::Make(sk_sp image, + SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, + bool clampAsIfUnpremul) { + if (!image) { + return sk_make_sp(); + } + return sk_sp{ new SkImageShader(image, tmx, tmy, localMatrix, clampAsIfUnpremul) }; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/GrBicubicEffect.h" +#include "src/gpu/effects/generated/GrSimpleTextureEffect.h" + +static GrSamplerState::WrapMode tile_mode_to_wrap_mode(const SkTileMode tileMode) { + switch (tileMode) { + case SkTileMode::kClamp: + return GrSamplerState::WrapMode::kClamp; + case SkTileMode::kRepeat: + return GrSamplerState::WrapMode::kRepeat; + case SkTileMode::kMirror: + return GrSamplerState::WrapMode::kMirrorRepeat; + case SkTileMode::kDecal: + return GrSamplerState::WrapMode::kClampToBorder; + } + SK_ABORT("Unknown tile mode."); +} + +std::unique_ptr SkImageShader::asFragmentProcessor( + const GrFPArgs& args) const { + const auto lm = this->totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix); + SkMatrix lmInverse; + if (!lm->invert(&lmInverse)) { + return nullptr; + } + + GrSamplerState::WrapMode wrapModes[] = {tile_mode_to_wrap_mode(fTileModeX), + tile_mode_to_wrap_mode(fTileModeY)}; + + // If either domainX or domainY are un-ignored, a texture domain effect has to be used to + // implement the decal mode (while leaving non-decal axes alone). The wrap mode originally + // clamp-to-border is reset to clamp since the hw cannot implement it directly. + GrTextureDomain::Mode domainX = GrTextureDomain::kIgnore_Mode; + GrTextureDomain::Mode domainY = GrTextureDomain::kIgnore_Mode; + if (!args.fContext->priv().caps()->clampToBorderSupport()) { + if (wrapModes[0] == GrSamplerState::WrapMode::kClampToBorder) { + domainX = GrTextureDomain::kDecal_Mode; + wrapModes[0] = GrSamplerState::WrapMode::kClamp; + } + if (wrapModes[1] == GrSamplerState::WrapMode::kClampToBorder) { + domainY = GrTextureDomain::kDecal_Mode; + wrapModes[1] = GrSamplerState::WrapMode::kClamp; + } + } + + // Must set wrap and filter on the sampler before requesting a texture. In two places below + // we check the matrix scale factors to determine how to interpret the filter quality setting. + // This completely ignores the complexity of the drawVertices case where explicit local coords + // are provided by the caller. + bool doBicubic; + GrSamplerState::Filter textureFilterMode = GrSkFilterQualityToGrFilterMode( + fImage->width(), fImage->height(), args.fFilterQuality, *args.fViewMatrix, *lm, + args.fContext->priv().options().fSharpenMipmappedTextures, &doBicubic); + GrSamplerState samplerState(wrapModes, textureFilterMode); + SkScalar scaleAdjust[2] = { 1.0f, 1.0f }; + sk_sp proxy(as_IB(fImage)->asTextureProxyRef(args.fContext, samplerState, + scaleAdjust)); + if (!proxy) { + return nullptr; + } + + GrColorType srcColorType = SkColorTypeToGrColorType(fImage->colorType()); + + lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]); + + std::unique_ptr inner; + if (doBicubic) { + // domainX and domainY will properly apply the decal effect with the texture domain used in + // the bicubic filter if clamp to border was unsupported in hardware + static constexpr auto kDir = GrBicubicEffect::Direction::kXY; + inner = GrBicubicEffect::Make(std::move(proxy), srcColorType, lmInverse, wrapModes, domainX, + domainY, kDir, fImage->alphaType()); + } else { + if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) { + SkRect domain = GrTextureDomain::MakeTexelDomain( + SkIRect::MakeWH(proxy->width(), proxy->height()), + domainX, domainY); + inner = GrTextureDomainEffect::Make(std::move(proxy), srcColorType, lmInverse, domain, + domainX, domainY, samplerState); + } else { + inner = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, lmInverse, + samplerState); + } + } + inner = GrColorSpaceXformEffect::Make(std::move(inner), fImage->colorSpace(), + fImage->alphaType(), args.fDstColorInfo->colorSpace()); + + bool isAlphaOnly = SkColorTypeIsAlphaOnly(fImage->colorType()); + if (isAlphaOnly) { + return inner; + } else if (args.fInputColorIsOpaque) { + return GrFragmentProcessor::OverrideInput(std::move(inner), SK_PMColor4fWHITE, false); + } + return GrFragmentProcessor::MulChildByInputAlpha(std::move(inner)); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "src/core/SkImagePriv.h" + +sk_sp SkMakeBitmapShader(const SkBitmap& src, SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, SkCopyPixelsMode cpm) { + return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm), + tmx, tmy, localMatrix); +} + +sk_sp SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src, + SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, SkCopyPixelsMode mode) { + auto s = SkMakeBitmapShader(src, tmx, tmy, localMatrix, mode); + if (!s) { + return nullptr; + } + if (src.colorType() == kAlpha_8_SkColorType && paint.getShader()) { + // Compose the image shader with the paint's shader. Alpha images+shaders should output the + // texture's alpha multiplied by the shader's color. DstIn (d*sa) will achieve this with + // the source image and dst shader (MakeBlend takes dst first, src second). + s = SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(), std::move(s)); + } + return s; +} + +void SkShaderBase::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkImageShader); } + +class SkImageStageUpdater : public SkStageUpdater { +public: + const SkImageShader* fShader; + + float fMatrixStorage[6]; + +#if 0 // TODO: when we support mipmaps + SkRasterPipeline_GatherCtx* fGather; + SkRasterPipeline_TileCtx* fLimitX; + SkRasterPipeline_TileCtx* fLimitY; + SkRasterPipeline_DecalTileCtx* fDecal; +#endif + + bool update(const SkMatrix& ctm, const SkMatrix* localM) override { + SkMatrix matrix; + return fShader->computeTotalInverse(ctm, localM, &matrix) && + matrix.asAffine(fMatrixStorage); + } +}; + +bool SkImageShader::doStages(const SkStageRec& rec, SkImageStageUpdater* updater) const { + if (updater && + (rec.fPaint.getFilterQuality() == kMedium_SkFilterQuality || + rec.fCTM.hasPerspective())) + { + // TODO: handle these cases + // medium: recall RequestBitmap and update width/height accordingly + // perspt: store 9 floats and use persp stage + return false; + } + + SkRasterPipeline* p = rec.fPipeline; + SkArenaAlloc* alloc = rec.fAlloc; + auto quality = rec.fPaint.getFilterQuality(); + + SkMatrix matrix; + if (!this->computeTotalInverse(rec.fCTM, rec.fLocalM, &matrix)) { + return false; + } + + const auto* state = SkBitmapController::RequestBitmap(as_IB(fImage.get()), + matrix, quality, alloc); + if (!state) { + return false; + } + + const SkPixmap& pm = state->pixmap(); + matrix = state->invMatrix(); + quality = state->quality(); + auto info = pm.info(); + + p->append(SkRasterPipeline::seed_shader); + + if (updater) { + p->append(SkRasterPipeline::matrix_2x3, updater->fMatrixStorage); + } else { + // When the matrix is just an integer translate, bilerp == nearest neighbor. + if (quality == kLow_SkFilterQuality && + matrix.getType() <= SkMatrix::kTranslate_Mask && + matrix.getTranslateX() == (int)matrix.getTranslateX() && + matrix.getTranslateY() == (int)matrix.getTranslateY()) { + quality = kNone_SkFilterQuality; + } + + // See skia:4649 and the GM image_scale_aligned. + if (quality == kNone_SkFilterQuality) { + if (matrix.getScaleX() >= 0) { + matrix.setTranslateX(nextafterf(matrix.getTranslateX(), + floorf(matrix.getTranslateX()))); + } + if (matrix.getScaleY() >= 0) { + matrix.setTranslateY(nextafterf(matrix.getTranslateY(), + floorf(matrix.getTranslateY()))); + } + } + p->append_matrix(alloc, matrix); + } + + auto gather = alloc->make(); + gather->pixels = pm.addr(); + gather->stride = pm.rowBytesAsPixels(); + gather->width = pm.width(); + gather->height = pm.height(); + + auto limit_x = alloc->make(), + limit_y = alloc->make(); + limit_x->scale = pm.width(); + limit_x->invScale = 1.0f / pm.width(); + limit_y->scale = pm.height(); + limit_y->invScale = 1.0f / pm.height(); + + SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr; + bool decal_x_and_y = fTileModeX == SkTileMode::kDecal && fTileModeY == SkTileMode::kDecal; + if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) { + decal_ctx = alloc->make(); + decal_ctx->limit_x = limit_x->scale; + decal_ctx->limit_y = limit_y->scale; + } + +#if 0 // TODO: when we support kMedium + if (updator && (quality == kMedium_SkFilterQuality)) { + // if we change levels in mipmap, we need to update the scales (and invScales) + updator->fGather = gather; + updator->fLimitX = limit_x; + updator->fLimitY = limit_y; + updator->fDecal = decal_ctx; + } +#endif + + auto append_tiling_and_gather = [&] { + if (decal_x_and_y) { + p->append(SkRasterPipeline::decal_x_and_y, decal_ctx); + } else { + switch (fTileModeX) { + case SkTileMode::kClamp: /* The gather_xxx stage will clamp for us. */ break; + case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x, limit_x); break; + case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x, limit_x); break; + case SkTileMode::kDecal: p->append(SkRasterPipeline::decal_x, decal_ctx); break; + } + switch (fTileModeY) { + case SkTileMode::kClamp: /* The gather_xxx stage will clamp for us. */ break; + case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_y, limit_y); break; + case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_y, limit_y); break; + case SkTileMode::kDecal: p->append(SkRasterPipeline::decal_y, decal_ctx); break; + } + } + + void* ctx = gather; + switch (info.colorType()) { + case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, ctx); break; + case kA16_unorm_SkColorType: p->append(SkRasterPipeline::gather_a16, ctx); break; + case kA16_float_SkColorType: p->append(SkRasterPipeline::gather_af16, ctx); break; + case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, ctx); break; + case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, ctx); break; + case kR8G8_unorm_SkColorType: p->append(SkRasterPipeline::gather_rg88, ctx); break; + case kR16G16_unorm_SkColorType: p->append(SkRasterPipeline::gather_rg1616, ctx); break; + case kR16G16_float_SkColorType: p->append(SkRasterPipeline::gather_rgf16, ctx); break; + case kRGBA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); break; + case kRGBA_1010102_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); break; + case kR16G16B16A16_unorm_SkColorType: + p->append(SkRasterPipeline::gather_16161616,ctx); break; + case kRGBA_F16Norm_SkColorType: + case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, ctx); break; + case kRGBA_F32_SkColorType: p->append(SkRasterPipeline::gather_f32, ctx); break; + + case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_a8, ctx); + p->append(SkRasterPipeline::alpha_to_gray ); break; + + case kRGB_888x_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); + p->append(SkRasterPipeline::force_opaque ); break; + + case kRGB_101010x_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); + p->append(SkRasterPipeline::force_opaque ); break; + + case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); + p->append(SkRasterPipeline::swap_rb ); break; + + case kUnknown_SkColorType: SkASSERT(false); + } + if (decal_ctx) { + p->append(SkRasterPipeline::check_decal_mask, decal_ctx); + } + }; + + auto append_misc = [&] { + // TODO: if ref.fDstCS isn't null, we'll premul here then immediately unpremul + // to do the color space transformation. Might be possible to streamline. + if (info.colorType() == kAlpha_8_SkColorType) { + // The color for A8 images comes from the (sRGB) paint color. + p->append_set_rgb(alloc, rec.fPaint.getColor4f()); + p->append(SkRasterPipeline::premul); + } else if (info.alphaType() == kUnpremul_SkAlphaType) { + // Convert unpremul images to premul before we carry on with the rest of the pipeline. + p->append(SkRasterPipeline::premul); + } + + if (quality > kLow_SkFilterQuality) { + // Bicubic filtering naturally produces out of range values on both sides. + p->append(SkRasterPipeline::clamp_0); + p->append(fClampAsIfUnpremul ? SkRasterPipeline::clamp_1 + : SkRasterPipeline::clamp_a); + } + + if (rec.fDstCS) { + // If color managed, convert from premul source all the way to premul dst color space. + auto srcCS = info.colorSpace(); + if (!srcCS || info.colorType() == kAlpha_8_SkColorType) { + // We treat untagged images as sRGB. + // A8 images get their r,g,b from the paint color, so they're also sRGB. + srcCS = sk_srgb_singleton(); + } + alloc->make(srcCS , kPremul_SkAlphaType, + rec.fDstCS, kPremul_SkAlphaType) + ->apply(p, info.colorType()); + } + + return true; + }; + + // Check for fast-path stages. + auto ct = info.colorType(); + if (true + && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) + && quality == kLow_SkFilterQuality + && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) { + + p->append(SkRasterPipeline::bilerp_clamp_8888, gather); + if (ct == kBGRA_8888_SkColorType) { + p->append(SkRasterPipeline::swap_rb); + } + return append_misc(); + } + if (true + && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) // TODO: all formats + && quality == kLow_SkFilterQuality + && fTileModeX != SkTileMode::kDecal // TODO decal too? + && fTileModeY != SkTileMode::kDecal) { + + auto ctx = alloc->make(); + *(SkRasterPipeline_GatherCtx*)(ctx) = *gather; + ctx->ct = ct; + ctx->tileX = fTileModeX; + ctx->tileY = fTileModeY; + ctx->invWidth = 1.0f / ctx->width; + ctx->invHeight = 1.0f / ctx->height; + p->append(SkRasterPipeline::bilinear, ctx); + return append_misc(); + } + if (true + && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) + && quality == kHigh_SkFilterQuality + && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) { + + p->append(SkRasterPipeline::bicubic_clamp_8888, gather); + if (ct == kBGRA_8888_SkColorType) { + p->append(SkRasterPipeline::swap_rb); + } + return append_misc(); + } + if (true + && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) // TODO: all formats + && quality == kHigh_SkFilterQuality + && fTileModeX != SkTileMode::kDecal // TODO decal too? + && fTileModeY != SkTileMode::kDecal) { + + auto ctx = alloc->make(); + *(SkRasterPipeline_GatherCtx*)(ctx) = *gather; + ctx->ct = ct; + ctx->tileX = fTileModeX; + ctx->tileY = fTileModeY; + ctx->invWidth = 1.0f / ctx->width; + ctx->invHeight = 1.0f / ctx->height; + p->append(SkRasterPipeline::bicubic, ctx); + return append_misc(); + } + + SkRasterPipeline_SamplerCtx* sampler = nullptr; + if (quality != kNone_SkFilterQuality) { + sampler = alloc->make(); + } + + auto sample = [&](SkRasterPipeline::StockStage setup_x, + SkRasterPipeline::StockStage setup_y) { + p->append(setup_x, sampler); + p->append(setup_y, sampler); + append_tiling_and_gather(); + p->append(SkRasterPipeline::accumulate, sampler); + }; + + if (quality == kNone_SkFilterQuality) { + append_tiling_and_gather(); + } else if (quality == kLow_SkFilterQuality) { + p->append(SkRasterPipeline::save_xy, sampler); + + sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny); + sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny); + sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py); + sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py); + + p->append(SkRasterPipeline::move_dst_src); + + } else { + p->append(SkRasterPipeline::save_xy, sampler); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y); + + p->append(SkRasterPipeline::move_dst_src); + } + + return append_misc(); +} + +bool SkImageShader::onAppendStages(const SkStageRec& rec) const { + return this->doStages(rec, nullptr); +} + +SkStageUpdater* SkImageShader::onAppendUpdatableStages(const SkStageRec& rec) const { + auto updater = rec.fAlloc->make(); + updater->fShader = this; + return this->doStages(rec, updater) ? updater : nullptr; +} + diff --git a/gfx/skia/skia/src/shaders/SkImageShader.h b/gfx/skia/skia/src/shaders/SkImageShader.h new file mode 100644 index 0000000000..5a01f0ae87 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkImageShader.h @@ -0,0 +1,61 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageShader_DEFINED +#define SkImageShader_DEFINED + +#include "include/core/SkImage.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkShaderBase.h" + +// private subclass of SkStageUpdater +class SkImageStageUpdater; + +class SkImageShader : public SkShaderBase { +public: + static sk_sp Make(sk_sp, + SkTileMode tmx, + SkTileMode tmy, + const SkMatrix* localMatrix, + bool clampAsIfUnpremul = false); + + bool isOpaque() const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkImageShader) + + SkImageShader(sk_sp, + SkTileMode tmx, + SkTileMode tmy, + const SkMatrix* localMatrix, + bool clampAsIfUnpremul); + + void flatten(SkWriteBuffer&) const override; +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override; +#endif + SkImage* onIsAImage(SkMatrix*, SkTileMode*) const override; + + bool onAppendStages(const SkStageRec&) const override; + SkStageUpdater* onAppendUpdatableStages(const SkStageRec&) const override; + + bool doStages(const SkStageRec&, SkImageStageUpdater* = nullptr) const; + + sk_sp fImage; + const SkTileMode fTileModeX; + const SkTileMode fTileModeY; + const bool fClampAsIfUnpremul; + + friend class SkShaderBase; + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkLightingShader.cpp b/gfx/skia/skia/src/shaders/SkLightingShader.cpp new file mode 100644 index 0000000000..4614431fc9 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkLightingShader.cpp @@ -0,0 +1,490 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkColor.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkUnPreMultiply.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkBitmapProcState.h" +#include "src/core/SkMathPriv.h" +#include "src/core/SkNormalSource.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkEmptyShader.h" +#include "src/shaders/SkLightingShader.h" +#include "src/shaders/SkShaderBase.h" + +//////////////////////////////////////////////////////////////////////////// + +/* + SkLightingShader TODOs: + support different light types + support multiple lights + fix non-opaque diffuse textures + + To Test: + A8 diffuse textures + down & upsampled draws +*/ + + + +/** \class SkLightingShaderImpl + This subclass of shader applies lighting. +*/ +class SkLightingShaderImpl : public SkShaderBase { +public: + /** Create a new lighting shader that uses the provided normal map and + lights to light the diffuse bitmap. + @param diffuseShader the shader that provides the diffuse colors + @param normalSource the source of normals for lighting computation + @param lights the lights applied to the geometry + */ + SkLightingShaderImpl(sk_sp diffuseShader, + sk_sp normalSource, + sk_sp lights) + : fDiffuseShader(std::move(diffuseShader)) + , fNormalSource(std::move(normalSource)) + , fLights(std::move(lights)) {} + + bool isOpaque() const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + + class LightingShaderContext : public Context { + public: + // The context takes ownership of the context and provider. It will call their destructors + // and then indirectly free their memory by calling free() on heapAllocated + LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&, + SkShaderBase::Context* diffuseContext, SkNormalSource::Provider*, + void* heapAllocated); + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + + uint32_t getFlags() const override { return fFlags; } + + private: + SkShaderBase::Context* fDiffuseContext; + SkNormalSource::Provider* fNormalProvider; + SkColor fPaintColor; + uint32_t fFlags; + + typedef Context INHERITED; + }; + +protected: + void flatten(SkWriteBuffer&) const override; +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkLightingShaderImpl) + + sk_sp fDiffuseShader; + sk_sp fNormalSource; + sk_sp fLights; + + friend class SkLightingShader; + + typedef SkShaderBase INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" + +// This FP expects a premul'd color input for its diffuse color. Premul'ing of the paint's color is +// handled by the asFragmentProcessor() factory, but shaders providing diffuse color must output it +// premul'd. +class LightingFP : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr normalFP, + sk_sp lights) { + return std::unique_ptr(new LightingFP(std::move(normalFP), + std::move(lights))); + } + + const char* name() const override { return "LightingFP"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new LightingFP(*this)); + } + + const SkTArray& directionalLights() const { return fDirectionalLights; } + const SkColor3f& ambientColor() const { return fAmbientColor; } + +private: + class GLSLLightingFP : public GrGLSLFragmentProcessor { + public: + GLSLLightingFP() { + fAmbientColor.fX = 0.0f; + } + + void emitCode(EmitArgs& args) override { + + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + const LightingFP& lightingFP = args.fFp.cast(); + + const char *lightDirsUniName = nullptr; + const char *lightColorsUniName = nullptr; + if (lightingFP.fDirectionalLights.count() != 0) { + fLightDirsUni = uniformHandler->addUniformArray( + kFragment_GrShaderFlag, + kFloat3_GrSLType, + "LightDir", + lightingFP.fDirectionalLights.count(), + &lightDirsUniName); + fLightColorsUni = uniformHandler->addUniformArray( + kFragment_GrShaderFlag, + kFloat3_GrSLType, + "LightColor", + lightingFP.fDirectionalLights.count(), + &lightColorsUniName); + } + + const char* ambientColorUniName = nullptr; + fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat3_GrSLType, + "AmbientColor", &ambientColorUniName); + + fragBuilder->codeAppendf("half4 diffuseColor = %s;", args.fInputColor); + + SkString dstNormalName("dstNormal"); + this->invokeChild(0, &dstNormalName, args); + + fragBuilder->codeAppendf("float3 normal = %s.xyz;", dstNormalName.c_str()); + + fragBuilder->codeAppend( "half3 result = half3(0.0);"); + + // diffuse light + if (lightingFP.fDirectionalLights.count() != 0) { + fragBuilder->codeAppendf("for (int i = 0; i < %d; i++) {", + lightingFP.fDirectionalLights.count()); + // TODO: modulate the contribution from each light based on the shadow map + fragBuilder->codeAppendf(" half NdotL = saturate(half(dot(normal, %s[i])));", + lightDirsUniName); + fragBuilder->codeAppendf(" result += half3(%s[i])*diffuseColor.rgb*NdotL;", + lightColorsUniName); + fragBuilder->codeAppend("}"); + } + + // ambient light + fragBuilder->codeAppendf("result += half3(%s) * diffuseColor.rgb;", + ambientColorUniName); + + // Clamping to alpha (equivalent to an unpremul'd clamp to 1.0) + fragBuilder->codeAppendf("%s = half4(clamp(result.rgb, 0.0, diffuseColor.a), " + "diffuseColor.a);", args.fOutputColor); + } + + static void GenKey(const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const LightingFP& lightingFP = proc.cast(); + b->add32(lightingFP.fDirectionalLights.count()); + } + + protected: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) override { + const LightingFP& lightingFP = proc.cast(); + + const SkTArray& directionalLights = lightingFP.directionalLights(); + if (directionalLights != fDirectionalLights) { + SkTArray lightDirs(directionalLights.count()); + SkTArray lightColors(directionalLights.count()); + for (const SkLights::Light& light : directionalLights) { + lightDirs.push_back(light.dir()); + lightColors.push_back(light.color()); + } + + pdman.set3fv(fLightDirsUni, directionalLights.count(), &(lightDirs[0].fX)); + pdman.set3fv(fLightColorsUni, directionalLights.count(), &(lightColors[0].fX)); + + fDirectionalLights = directionalLights; + } + + const SkColor3f& ambientColor = lightingFP.ambientColor(); + if (ambientColor != fAmbientColor) { + pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX); + fAmbientColor = ambientColor; + } + } + + private: + SkTArray fDirectionalLights; + GrGLSLProgramDataManager::UniformHandle fLightDirsUni; + GrGLSLProgramDataManager::UniformHandle fLightColorsUni; + + SkColor3f fAmbientColor; + GrGLSLProgramDataManager::UniformHandle fAmbientColorUni; + }; + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLLightingFP::GenKey(*this, caps, b); + } + + LightingFP(std::unique_ptr normalFP, sk_sp lights) + : INHERITED(kLightingFP_ClassID, kPreservesOpaqueInput_OptimizationFlag) { + // fuse all ambient lights into a single one + fAmbientColor = lights->ambientLightColor(); + for (int i = 0; i < lights->numLights(); ++i) { + if (SkLights::Light::kDirectional_LightType == lights->light(i).type()) { + fDirectionalLights.push_back(lights->light(i)); + // TODO get the handle to the shadow map if there is one + } else { + SkDEBUGFAIL("Unimplemented Light Type passed to LightingFP"); + } + } + + this->registerChildProcessor(std::move(normalFP)); + } + + LightingFP(const LightingFP& that) + : INHERITED(kLightingFP_ClassID, kPreservesOpaqueInput_OptimizationFlag) + , fDirectionalLights(that.fDirectionalLights) + , fAmbientColor(that.fAmbientColor) { + this->registerChildProcessor(that.childProcessor(0).clone()); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLLightingFP; } + + bool onIsEqual(const GrFragmentProcessor& proc) const override { + const LightingFP& lightingFP = proc.cast(); + return fDirectionalLights == lightingFP.fDirectionalLights && + fAmbientColor == lightingFP.fAmbientColor; + } + + SkTArray fDirectionalLights; + SkColor3f fAmbientColor; + + typedef GrFragmentProcessor INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// + +std::unique_ptr SkLightingShaderImpl::asFragmentProcessor(const GrFPArgs& args) const { + std::unique_ptr normalFP(fNormalSource->asFragmentProcessor(args)); + if (!normalFP) { + return nullptr; + } + + if (fDiffuseShader) { + std::unique_ptr fpPipeline[] = { + as_SB(fDiffuseShader)->asFragmentProcessor(args), + LightingFP::Make(std::move(normalFP), fLights) + }; + if (!fpPipeline[0] || !fpPipeline[1]) { + return nullptr; + } + + std::unique_ptr innerLightFP = GrFragmentProcessor::RunInSeries(fpPipeline, 2); + // FP is wrapped because paint's alpha needs to be applied to output + return GrFragmentProcessor::MulChildByInputAlpha(std::move(innerLightFP)); + } else { + // FP is wrapped because paint comes in unpremul'd to fragment shader, but LightingFP + // expects premul'd color. + return GrFragmentProcessor::PremulInput(LightingFP::Make(std::move(normalFP), fLights)); + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////// + +bool SkLightingShaderImpl::isOpaque() const { + return (fDiffuseShader ? fDiffuseShader->isOpaque() : false); +} + +SkLightingShaderImpl::LightingShaderContext::LightingShaderContext( + const SkLightingShaderImpl& shader, const ContextRec& rec, + SkShaderBase::Context* diffuseContext, SkNormalSource::Provider* normalProvider, + void* heapAllocated) + : INHERITED(shader, rec) + , fDiffuseContext(diffuseContext) + , fNormalProvider(normalProvider) { + bool isOpaque = shader.isOpaque(); + + // update fFlags + uint32_t flags = 0; + if (isOpaque && (255 == this->getPaintAlpha())) { + flags |= kOpaqueAlpha_Flag; + } + + fPaintColor = rec.fPaint->getColor(); + fFlags = flags; +} + +static inline SkPMColor convert(SkColor3f color, U8CPU a) { + if (color.fX <= 0.0f) { + color.fX = 0.0f; + } else if (color.fX >= 255.0f) { + color.fX = 255.0f; + } + + if (color.fY <= 0.0f) { + color.fY = 0.0f; + } else if (color.fY >= 255.0f) { + color.fY = 255.0f; + } + + if (color.fZ <= 0.0f) { + color.fZ = 0.0f; + } else if (color.fZ >= 255.0f) { + color.fZ = 255.0f; + } + + return SkPreMultiplyARGB(a, (int) color.fX, (int) color.fY, (int) color.fZ); +} + +// larger is better (fewer times we have to loop), but we shouldn't +// take up too much stack-space (each one here costs 16 bytes) +#define BUFFER_MAX 16 +void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y, + SkPMColor result[], int count) { + const SkLightingShaderImpl& lightShader = static_cast(fShader); + + SkPMColor diffuse[BUFFER_MAX]; + SkPoint3 normals[BUFFER_MAX]; + + SkColor diffColor = fPaintColor; + + do { + int n = SkTMin(count, BUFFER_MAX); + + fNormalProvider->fillScanLine(x, y, normals, n); + + if (fDiffuseContext) { + fDiffuseContext->shadeSpan(x, y, diffuse, n); + } + + for (int i = 0; i < n; ++i) { + if (fDiffuseContext) { + diffColor = SkUnPreMultiply::PMColorToColor(diffuse[i]); + } + + SkColor3f accum = SkColor3f::Make(0.0f, 0.0f, 0.0f); + + // Adding ambient light + accum.fX += lightShader.fLights->ambientLightColor().fX * SkColorGetR(diffColor); + accum.fY += lightShader.fLights->ambientLightColor().fY * SkColorGetG(diffColor); + accum.fZ += lightShader.fLights->ambientLightColor().fZ * SkColorGetB(diffColor); + + // This is all done in linear unpremul color space (each component 0..255.0f though) + for (int l = 0; l < lightShader.fLights->numLights(); ++l) { + const SkLights::Light& light = lightShader.fLights->light(l); + + SkScalar illuminanceScalingFactor = 1.0f; + + if (SkLights::Light::kDirectional_LightType == light.type()) { + illuminanceScalingFactor = normals[i].dot(light.dir()); + if (illuminanceScalingFactor < 0.0f) { + illuminanceScalingFactor = 0.0f; + } + } + + accum.fX += light.color().fX * SkColorGetR(diffColor) * illuminanceScalingFactor; + accum.fY += light.color().fY * SkColorGetG(diffColor) * illuminanceScalingFactor; + accum.fZ += light.color().fZ * SkColorGetB(diffColor) * illuminanceScalingFactor; + } + + // convert() premultiplies the accumulate color with alpha + result[i] = convert(accum, SkColorGetA(diffColor)); + } + + result += n; + x += n; + count -= n; + } while (count > 0); +} + +//////////////////////////////////////////////////////////////////////////// + +sk_sp SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) { + + // Discarding SkShader flattenable params + bool hasLocalMatrix = buf.readBool(); + if (hasLocalMatrix) { + return nullptr; + } + + sk_sp lights = SkLights::MakeFromBuffer(buf); + + sk_sp normalSource(buf.readFlattenable()); + + bool hasDiffuse = buf.readBool(); + sk_sp diffuseShader = nullptr; + if (hasDiffuse) { + diffuseShader = buf.readFlattenable(); + } + + return sk_make_sp(std::move(diffuseShader), std::move(normalSource), + std::move(lights)); +} + +void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); + + fLights->flatten(buf); + + buf.writeFlattenable(fNormalSource.get()); + buf.writeBool(static_cast(fDiffuseShader)); + if (fDiffuseShader) { + buf.writeFlattenable(fDiffuseShader.get()); + } +} + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +SkShaderBase::Context* SkLightingShaderImpl::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + SkShaderBase::Context *diffuseContext = nullptr; + if (fDiffuseShader) { + diffuseContext = as_SB(fDiffuseShader)->makeContext(rec, alloc); + if (!diffuseContext) { + return nullptr; + } + } + + SkNormalSource::Provider* normalProvider = fNormalSource->asProvider(rec, alloc); + if (!normalProvider) { + return nullptr; + } + + // The diffuse shader can inspect the rec and make its decision about rec's colorspace. + // What about the lighting shader? Is lighting sensitive to the rec's (device) colorspace? + return alloc->make(*this, rec, diffuseContext, normalProvider, nullptr); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkLightingShader::Make(sk_sp diffuseShader, + sk_sp normalSource, + sk_sp lights) { + SkASSERT(lights); + if (!normalSource) { + normalSource = SkNormalSource::MakeFlat(); + } + + return sk_make_sp(std::move(diffuseShader), std::move(normalSource), + std::move(lights)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkLightingShader::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkLightingShaderImpl); } diff --git a/gfx/skia/skia/src/shaders/SkLightingShader.h b/gfx/skia/skia/src/shaders/SkLightingShader.h new file mode 100644 index 0000000000..cdf27cb40e --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkLightingShader.h @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLightingShader_DEFINED +#define SkLightingShader_DEFINED + +#include "include/core/SkShader.h" +#include "src/shaders/SkLights.h" + +class SkBitmap; +class SkMatrix; +class SkNormalSource; + +class SkLightingShader { +public: + /** Returns a shader that lights the shape, colored by the diffuseShader, using the + normals from normalSource, with the set of lights provided. + + @param diffuseShader the shader that provides the colors. If nullptr, uses the paint's + color. + @param normalSource the source for the shape's normals. If nullptr, assumes straight + up normals (<0,0,1>). + @param lights the lights applied to the normals + + The lighting equation is currently: + result = (LightColor * dot(Normal, LightDir) + AmbientColor) * DiffuseColor + + */ + static sk_sp Make(sk_sp diffuseShader, sk_sp normalSource, + sk_sp lights); + + static void RegisterFlattenables(); +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkLights.cpp b/gfx/skia/skia/src/shaders/SkLights.cpp new file mode 100644 index 0000000000..e80cf2788b --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkLights.cpp @@ -0,0 +1,68 @@ + +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkReadBuffer.h" +#include "src/shaders/SkLights.h" + +sk_sp SkLights::MakeFromBuffer(SkReadBuffer& buf) { + Builder builder; + + SkColor3f ambColor; + if (!buf.readScalarArray(&ambColor.fX, 3)) { + return nullptr; + } + + builder.setAmbientLightColor(ambColor); + + int numLights = buf.readInt(); + + for (int l = 0; l < numLights; ++l) { + bool isPoint = buf.readBool(); + + SkColor3f color; + if (!buf.readScalarArray(&color.fX, 3)) { + return nullptr; + } + + SkVector3 dirOrPos; + if (!buf.readScalarArray(&dirOrPos.fX, 3)) { + return nullptr; + } + + if (isPoint) { + SkScalar intensity; + intensity = buf.readScalar(); + Light light = Light::MakePoint(color, dirOrPos, intensity); + builder.add(light); + } else { + Light light = Light::MakeDirectional(color, dirOrPos); + builder.add(light); + } + } + + return builder.finish(); +} + +void SkLights::flatten(SkWriteBuffer& buf) const { + buf.writeScalarArray(&this->ambientLightColor().fX, 3); + + buf.writeInt(this->numLights()); + for (int l = 0; l < this->numLights(); ++l) { + const Light& light = this->light(l); + + bool isPoint = Light::kPoint_LightType == light.type(); + + buf.writeBool(isPoint); + buf.writeScalarArray(&light.color().fX, 3); + buf.writeScalarArray(&light.dir().fX, 3); + + if (isPoint) { + buf.writeScalar(light.intensity()); + } + } +} diff --git a/gfx/skia/skia/src/shaders/SkLights.h b/gfx/skia/skia/src/shaders/SkLights.h new file mode 100644 index 0000000000..f22d6bbadb --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkLights.h @@ -0,0 +1,192 @@ + +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLights_DEFINED +#define SkLights_DEFINED + +#include "include/core/SkPoint3.h" +#include "include/core/SkRefCnt.h" +#include "include/private/SkTArray.h" + +class SkReadBuffer; +class SkWriteBuffer; + +/** \class SkLights + SkLights encapsulates a set of directional, point and ambient lights for use with the + SkLightingShader. +*/ +class SK_API SkLights : public SkRefCnt { +public: + class Light { + public: + enum LightType { + kDirectional_LightType, + kPoint_LightType + }; + + Light(const Light& other) + : fType(other.fType) + , fColor(other.fColor) + , fDirOrPos(other.fDirOrPos) + , fIntensity(other.fIntensity) {} + + Light(Light&& other) + : fType(other.fType) + , fColor(other.fColor) + , fDirOrPos(other.fDirOrPos) + , fIntensity(other.fIntensity) {} + + static Light MakeDirectional(const SkColor3f& color, const SkVector3& dir) { + Light light(kDirectional_LightType, color, dir, 0.0f); + if (!light.fDirOrPos.normalize()) { + light.fDirOrPos.set(0.0f, 0.0f, 1.0f); + } + return light; + } + + static Light MakePoint(const SkColor3f& color, const SkPoint3& pos, SkScalar intensity) { + return Light(kPoint_LightType, color, pos, intensity); + } + + LightType type() const { return fType; } + const SkColor3f& color() const { return fColor; } + const SkVector3& dir() const { + SkASSERT(kDirectional_LightType == fType); + return fDirOrPos; + } + const SkPoint3& pos() const { + SkASSERT(kPoint_LightType == fType); + return fDirOrPos; + } + SkScalar intensity() const { + SkASSERT(kPoint_LightType == fType); + return fIntensity; + } + + Light& operator=(const Light& other) { + if (this == &other) { + return *this; + } + + fType = other.fType; + fColor = other.fColor; + fDirOrPos = other.fDirOrPos; + fIntensity = other.fIntensity; + return *this; + } + + bool operator==(const Light& other) { + return (fType == other.fType) && + (fColor == other.fColor) && + (fDirOrPos == other.fDirOrPos) && + (fIntensity == other.fIntensity); + } + + bool operator!=(const Light& other) { return !(this->operator==(other)); } + + private: + friend class SkLights; + + Light(LightType type, const SkColor3f& color, const SkVector3& dirOrPos, + SkScalar intensity) + : fType(type) + , fColor(color) + , fDirOrPos(dirOrPos) + , fIntensity(intensity) {} + + LightType fType; + SkColor3f fColor; // linear (unpremul) color. Range is 0..1 in each channel. + + SkVector3 fDirOrPos; // For directional lights, holds the direction towards the + // light (+Z is out of the screen). + // If degenerate, it will be replaced with (0, 0, 1). + // For point lights, holds location of point light + + SkScalar fIntensity; // For point lights, dictates the light intensity. + // Simply a multiplier to the final light output value. + }; + + class Builder { + public: + Builder() : fLights(new SkLights) {} + + void add(const Light& light) { + if (fLights) { + fLights->fLights.push_back(light); + } + } + + void add(Light&& light) { + if (fLights) { + fLights->fLights.push_back(std::move(light)); + } + } + + void setAmbientLightColor(const SkColor3f& color) { + if (fLights) { + fLights->fAmbientLightColor = color; + } + } + + sk_sp finish() { + return std::move(fLights); + } + + private: + sk_sp fLights; + }; + + /** Returns number of lights not including the ambient light. + + @return number of lights not including the ambient light + */ + int numLights() const { return fLights.count(); } + + /** Returns the index-th light. + + @param index the index of the desired light + @return the index-th light + */ + const Light& light(int index) const { return fLights[index]; } + + /** Returns the ambient light. + + @return the ambient light + */ + const SkColor3f& ambientLightColor() const { + return fAmbientLightColor; + } + + /** + * Recreate an SkLights object that was serialized into a buffer. + * + * @param SkReadBuffer Serialized blob data. + * @return A new SkLights representing the serialized data, or NULL if the buffer is + * invalid. + */ + static sk_sp MakeFromBuffer(SkReadBuffer& buf); + + /** + * Serialize to a buffer. + * + * @param buffer the write buffer to write out to + */ + void flatten(SkWriteBuffer& buf) const; + +private: + friend class SkLightingShaderImpl; + + SkLights() : fAmbientLightColor(SkColor3f::Make(0.0f, 0.0f, 0.0f)) {} + + SkTArray fLights; + SkColor3f fAmbientLightColor; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkLocalMatrixShader.cpp b/gfx/skia/skia/src/shaders/SkLocalMatrixShader.cpp new file mode 100644 index 0000000000..be9a6fac5e --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkLocalMatrixShader.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkTLazy.h" +#include "src/shaders/SkLocalMatrixShader.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#endif + +#if SK_SUPPORT_GPU +std::unique_ptr SkLocalMatrixShader::asFragmentProcessor( + const GrFPArgs& args) const { + return as_SB(fProxyShader)->asFragmentProcessor( + GrFPArgs::WithPreLocalMatrix(args, this->getLocalMatrix())); +} +#endif + +sk_sp SkLocalMatrixShader::CreateProc(SkReadBuffer& buffer) { + SkMatrix lm; + buffer.readMatrix(&lm); + auto baseShader(buffer.readShader()); + if (!baseShader) { + return nullptr; + } + return baseShader->makeWithLocalMatrix(lm); +} + +void SkLocalMatrixShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(this->getLocalMatrix()); + buffer.writeFlattenable(fProxyShader.get()); +} + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +SkShaderBase::Context* SkLocalMatrixShader::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + SkTCopyOnFirstWrite lm(this->getLocalMatrix()); + if (rec.fLocalMatrix) { + lm.writable()->preConcat(*rec.fLocalMatrix); + } + + ContextRec newRec(rec); + newRec.fLocalMatrix = lm; + + return as_SB(fProxyShader)->makeContext(newRec, alloc); +} +#endif + +SkImage* SkLocalMatrixShader::onIsAImage(SkMatrix* outMatrix, SkTileMode* mode) const { + SkMatrix imageMatrix; + SkImage* image = fProxyShader->isAImage(&imageMatrix, mode); + if (image && outMatrix) { + // Local matrix must be applied first so it is on the right side of the concat. + *outMatrix = SkMatrix::Concat(imageMatrix, this->getLocalMatrix()); + } + + return image; +} + +SkPicture* SkLocalMatrixShader::isAPicture(SkMatrix* matrix, + SkTileMode tileModes[2], + SkRect* tile) const { + SkMatrix proxyMatrix; + SkPicture* picture = as_SB(fProxyShader)->isAPicture(&proxyMatrix, tileModes, tile); + if (picture && matrix) { + *matrix = SkMatrix::Concat(proxyMatrix, this->getLocalMatrix()); + } + return picture; +} + +bool SkLocalMatrixShader::onAppendStages(const SkStageRec& rec) const { + SkTCopyOnFirstWrite lm(this->getLocalMatrix()); + if (rec.fLocalM) { + lm.writable()->preConcat(*rec.fLocalM); + } + + SkStageRec newRec = rec; + newRec.fLocalM = lm; + return as_SB(fProxyShader)->appendStages(newRec); +} + +sk_sp SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const { + if (localMatrix.isIdentity()) { + return sk_ref_sp(const_cast(this)); + } + + const SkMatrix* lm = &localMatrix; + + sk_sp baseShader; + SkMatrix otherLocalMatrix; + sk_sp proxy(as_SB(this)->makeAsALocalMatrixShader(&otherLocalMatrix)); + if (proxy) { + otherLocalMatrix.preConcat(localMatrix); + lm = &otherLocalMatrix; + baseShader = proxy; + } else { + baseShader = sk_ref_sp(const_cast(this)); + } + + return sk_make_sp(std::move(baseShader), *lm); +} diff --git a/gfx/skia/skia/src/shaders/SkLocalMatrixShader.h b/gfx/skia/skia/src/shaders/SkLocalMatrixShader.h new file mode 100644 index 0000000000..2895dca659 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkLocalMatrixShader.h @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLocalMatrixShader_DEFINED +#define SkLocalMatrixShader_DEFINED + +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkShaderBase.h" + +class GrFragmentProcessor; +class SkArenaAlloc; + +class SkLocalMatrixShader final : public SkShaderBase { +public: + SkLocalMatrixShader(sk_sp proxy, const SkMatrix& localMatrix) + : INHERITED(&localMatrix) + , fProxyShader(std::move(proxy)) + {} + + GradientType asAGradient(GradientInfo* info) const override { + return fProxyShader->asAGradient(info); + } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + + sk_sp makeAsALocalMatrixShader(SkMatrix* localMatrix) const override { + if (localMatrix) { + *localMatrix = this->getLocalMatrix(); + } + return fProxyShader; + } + + SkPicture* isAPicture(SkMatrix*, SkTileMode[2], SkRect* tile) const override; + +protected: + void flatten(SkWriteBuffer&) const override; + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; +#endif + + SkImage* onIsAImage(SkMatrix* matrix, SkTileMode* mode) const override; + + bool onAppendStages(const SkStageRec&) const override; + +private: + SK_FLATTENABLE_HOOKS(SkLocalMatrixShader) + + sk_sp fProxyShader; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkPerlinNoiseShader.cpp b/gfx/skia/skia/src/shaders/SkPerlinNoiseShader.cpp new file mode 100644 index 0000000000..812dc1694f --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkPerlinNoiseShader.cpp @@ -0,0 +1,1546 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/effects/SkPerlinNoiseShader.h" + +#include "include/core/SkColorFilter.h" +#include "include/core/SkShader.h" +#include "include/core/SkString.h" +#include "include/core/SkUnPreMultiply.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCoordTransform.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" +#include "src/gpu/effects/generated/GrConstColorProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentProcessor.h" +#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" +#include "src/gpu/glsl/GrGLSLProgramDataManager.h" +#include "src/gpu/glsl/GrGLSLUniformHandler.h" +#endif + +static const int kBlockSize = 256; +static const int kBlockMask = kBlockSize - 1; +static const int kPerlinNoise = 4096; +static const int kRandMaximum = SK_MaxS32; // 2**31 - 1 + +static uint8_t improved_noise_permutations[] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, + 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, + 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, + 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, + 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, + 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, + 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, + 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, + 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, + 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, + 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, + 141, 128, 195, 78, 66, 215, 61, 156, 180, + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, + 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, + 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, + 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, + 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, + 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, + 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, + 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, + 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, + 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, + 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, + 141, 128, 195, 78, 66, 215, 61, 156, 180 +}; + +class SkPerlinNoiseShaderImpl : public SkShaderBase { +public: + struct StitchData { + StitchData() + : fWidth(0) + , fWrapX(0) + , fHeight(0) + , fWrapY(0) + {} + + StitchData(SkScalar w, SkScalar h) + : fWidth(SkTMin(SkScalarRoundToInt(w), SK_MaxS32 - kPerlinNoise)) + , fWrapX(kPerlinNoise + fWidth) + , fHeight(SkTMin(SkScalarRoundToInt(h), SK_MaxS32 - kPerlinNoise)) + , fWrapY(kPerlinNoise + fHeight) {} + + bool operator==(const StitchData& other) const { + return fWidth == other.fWidth && + fWrapX == other.fWrapX && + fHeight == other.fHeight && + fWrapY == other.fWrapY; + } + + int fWidth; // How much to subtract to wrap for stitching. + int fWrapX; // Minimum value to wrap. + int fHeight; + int fWrapY; + }; + + struct PaintingData { + PaintingData(const SkISize& tileSize, SkScalar seed, + SkScalar baseFrequencyX, SkScalar baseFrequencyY, + const SkMatrix& matrix) + { + SkVector tileVec; + matrix.mapVector(SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight), + &tileVec); + + SkSize scale; + if (!matrix.decomposeScale(&scale, nullptr)) { + scale.set(SK_ScalarNearlyZero, SK_ScalarNearlyZero); + } + fBaseFrequency.set(baseFrequencyX * SkScalarInvert(scale.width()), + baseFrequencyY * SkScalarInvert(scale.height())); + fTileSize.set(SkScalarRoundToInt(tileVec.fX), SkScalarRoundToInt(tileVec.fY)); + this->init(seed); + if (!fTileSize.isEmpty()) { + this->stitch(); + } + + #if SK_SUPPORT_GPU + SkImageInfo info = SkImageInfo::MakeA8(kBlockSize, 1); + SkPixmap permutationsPixmap(info, fLatticeSelector, info.minRowBytes()); + fPermutationsImage = SkImage::MakeFromRaster(permutationsPixmap, nullptr, nullptr); + + info = SkImageInfo::MakeN32Premul(kBlockSize, 4); + SkPixmap noisePixmap(info, fNoise[0][0], info.minRowBytes()); + fNoiseImage = SkImage::MakeFromRaster(noisePixmap, nullptr, nullptr); + + info = SkImageInfo::MakeA8(256, 1); + SkPixmap impPermutationsPixmap(info, improved_noise_permutations, info.minRowBytes()); + fImprovedPermutationsImage = SkImage::MakeFromRaster(impPermutationsPixmap, nullptr, + nullptr); + + static uint8_t gradients[] = { 2, 2, 1, 0, + 0, 2, 1, 0, + 2, 0, 1, 0, + 0, 0, 1, 0, + 2, 1, 2, 0, + 0, 1, 2, 0, + 2, 1, 0, 0, + 0, 1, 0, 0, + 1, 2, 2, 0, + 1, 0, 2, 0, + 1, 2, 0, 0, + 1, 0, 0, 0, + 2, 2, 1, 0, + 1, 0, 2, 0, + 0, 2, 1, 0, + 1, 0, 0, 0 }; + info = SkImageInfo::MakeN32Premul(16, 1); + SkPixmap gradPixmap(info, gradients, info.minRowBytes()); + fGradientImage = SkImage::MakeFromRaster(gradPixmap, nullptr, nullptr); + #endif + } + + #if SK_SUPPORT_GPU + PaintingData(const PaintingData& that) + : fSeed(that.fSeed) + , fTileSize(that.fTileSize) + , fBaseFrequency(that.fBaseFrequency) + , fStitchDataInit(that.fStitchDataInit) + , fPermutationsImage(that.fPermutationsImage) + , fNoiseImage(that.fNoiseImage) + , fImprovedPermutationsImage(that.fImprovedPermutationsImage) + , fGradientImage(that.fGradientImage) { + memcpy(fLatticeSelector, that.fLatticeSelector, sizeof(fLatticeSelector)); + memcpy(fNoise, that.fNoise, sizeof(fNoise)); + memcpy(fGradient, that.fGradient, sizeof(fGradient)); + } + #endif + + int fSeed; + uint8_t fLatticeSelector[kBlockSize]; + uint16_t fNoise[4][kBlockSize][2]; + SkPoint fGradient[4][kBlockSize]; + SkISize fTileSize; + SkVector fBaseFrequency; + StitchData fStitchDataInit; + + private: + + #if SK_SUPPORT_GPU + sk_sp fPermutationsImage; + sk_sp fNoiseImage; + sk_sp fImprovedPermutationsImage; + sk_sp fGradientImage; + #endif + + inline int random() { + static const int gRandAmplitude = 16807; // 7**5; primitive root of m + static const int gRandQ = 127773; // m / a + static const int gRandR = 2836; // m % a + + int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ); + if (result <= 0) + result += kRandMaximum; + fSeed = result; + return result; + } + + // Only called once. Could be part of the constructor. + void init(SkScalar seed) + { + static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize)); + + // According to the SVG spec, we must truncate (not round) the seed value. + fSeed = SkScalarTruncToInt(seed); + // The seed value clamp to the range [1, kRandMaximum - 1]. + if (fSeed <= 0) { + fSeed = -(fSeed % (kRandMaximum - 1)) + 1; + } + if (fSeed > kRandMaximum - 1) { + fSeed = kRandMaximum - 1; + } + for (int channel = 0; channel < 4; ++channel) { + for (int i = 0; i < kBlockSize; ++i) { + fLatticeSelector[i] = i; + fNoise[channel][i][0] = (random() % (2 * kBlockSize)); + fNoise[channel][i][1] = (random() % (2 * kBlockSize)); + } + } + for (int i = kBlockSize - 1; i > 0; --i) { + int k = fLatticeSelector[i]; + int j = random() % kBlockSize; + SkASSERT(j >= 0); + SkASSERT(j < kBlockSize); + fLatticeSelector[i] = fLatticeSelector[j]; + fLatticeSelector[j] = k; + } + + // Perform the permutations now + { + // Copy noise data + uint16_t noise[4][kBlockSize][2]; + for (int i = 0; i < kBlockSize; ++i) { + for (int channel = 0; channel < 4; ++channel) { + for (int j = 0; j < 2; ++j) { + noise[channel][i][j] = fNoise[channel][i][j]; + } + } + } + // Do permutations on noise data + for (int i = 0; i < kBlockSize; ++i) { + for (int channel = 0; channel < 4; ++channel) { + for (int j = 0; j < 2; ++j) { + fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j]; + } + } + } + } + + // Half of the largest possible value for 16 bit unsigned int + static const SkScalar gHalfMax16bits = 32767.5f; + + // Compute gradients from permutated noise data + for (int channel = 0; channel < 4; ++channel) { + for (int i = 0; i < kBlockSize; ++i) { + fGradient[channel][i] = SkPoint::Make( + (fNoise[channel][i][0] - kBlockSize) * gInvBlockSizef, + (fNoise[channel][i][1] - kBlockSize) * gInvBlockSizef); + fGradient[channel][i].normalize(); + // Put the normalized gradient back into the noise data + fNoise[channel][i][0] = SkScalarRoundToInt( + (fGradient[channel][i].fX + 1) * gHalfMax16bits); + fNoise[channel][i][1] = SkScalarRoundToInt( + (fGradient[channel][i].fY + 1) * gHalfMax16bits); + } + } + } + + // Only called once. Could be part of the constructor. + void stitch() { + SkScalar tileWidth = SkIntToScalar(fTileSize.width()); + SkScalar tileHeight = SkIntToScalar(fTileSize.height()); + SkASSERT(tileWidth > 0 && tileHeight > 0); + // When stitching tiled turbulence, the frequencies must be adjusted + // so that the tile borders will be continuous. + if (fBaseFrequency.fX) { + SkScalar lowFrequencx = + SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; + SkScalar highFrequencx = + SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; + // BaseFrequency should be non-negative according to the standard. + // lowFrequencx can be 0 if fBaseFrequency.fX is very small. + if (sk_ieee_float_divide(fBaseFrequency.fX, lowFrequencx) < highFrequencx / fBaseFrequency.fX) { + fBaseFrequency.fX = lowFrequencx; + } else { + fBaseFrequency.fX = highFrequencx; + } + } + if (fBaseFrequency.fY) { + SkScalar lowFrequency = + SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; + SkScalar highFrequency = + SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; + // lowFrequency can be 0 if fBaseFrequency.fY is very small. + if (sk_ieee_float_divide(fBaseFrequency.fY, lowFrequency) < highFrequency / fBaseFrequency.fY) { + fBaseFrequency.fY = lowFrequency; + } else { + fBaseFrequency.fY = highFrequency; + } + } + // Set up TurbulenceInitial stitch values. + fStitchDataInit = StitchData(tileWidth * fBaseFrequency.fX, + tileHeight * fBaseFrequency.fY); + } + + public: + +#if SK_SUPPORT_GPU + const sk_sp getPermutationsImage() const { return fPermutationsImage; } + + const sk_sp getNoiseImage() const { return fNoiseImage; } + + const sk_sp getImprovedPermutationsImage() const { + return fImprovedPermutationsImage; + } + + const sk_sp getGradientImage() const { return fGradientImage; } +#endif + }; + + /** + * About the noise types : the difference between the first 2 is just minor tweaks to the + * algorithm, they're not 2 entirely different noises. The output looks different, but once the + * noise is generated in the [1, -1] range, the output is brought back in the [0, 1] range by + * doing : + * kFractalNoise_Type : noise * 0.5 + 0.5 + * kTurbulence_Type : abs(noise) + * Very little differences between the 2 types, although you can tell the difference visually. + * "Improved" is based on the Improved Perlin Noise algorithm described at + * http://mrl.nyu.edu/~perlin/noise/. It is quite distinct from the other two, and the noise is + * a 2D slice of a 3D noise texture. Minor changes to the Z coordinate will result in minor + * changes to the noise, making it suitable for animated noise. + */ + enum Type { + kFractalNoise_Type, + kTurbulence_Type, + kImprovedNoise_Type, + kLast_Type = kImprovedNoise_Type + }; + + static const int kMaxOctaves = 255; // numOctaves must be <= 0 and <= kMaxOctaves + + SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type, SkScalar baseFrequencyX, + SkScalar baseFrequencyY, int numOctaves, SkScalar seed, + const SkISize* tileSize); + + class PerlinNoiseShaderContext : public Context { + public: + PerlinNoiseShaderContext(const SkPerlinNoiseShaderImpl& shader, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + + private: + SkPMColor shade(const SkPoint& point, StitchData& stitchData) const; + SkScalar calculateTurbulenceValueForPoint( + int channel, + StitchData& stitchData, const SkPoint& point) const; + SkScalar calculateImprovedNoiseValueForPoint(int channel, const SkPoint& point) const; + SkScalar noise2D(int channel, + const StitchData& stitchData, const SkPoint& noiseVector) const; + + SkMatrix fMatrix; + PaintingData fPaintingData; + + typedef Context INHERITED; + }; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + void flatten(SkWriteBuffer&) const override; +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkPerlinNoiseShaderImpl) + + const SkPerlinNoiseShaderImpl::Type fType; + const SkScalar fBaseFrequencyX; + const SkScalar fBaseFrequencyY; + const int fNumOctaves; + const SkScalar fSeed; + const SkISize fTileSize; + const bool fStitchTiles; + + friend class ::SkPerlinNoiseShader; + + typedef SkShaderBase INHERITED; +}; + +namespace { + +// noiseValue is the color component's value (or color) +// limitValue is the maximum perlin noise array index value allowed +// newValue is the current noise dimension (either width or height) +inline int checkNoise(int noiseValue, int limitValue, int newValue) { + // If the noise value would bring us out of bounds of the current noise array while we are + // stiching noise tiles together, wrap the noise around the current dimension of the noise to + // stay within the array bounds in a continuous fashion (so that tiling lines are not visible) + if (noiseValue >= limitValue) { + noiseValue -= newValue; + } + return noiseValue; +} + +inline SkScalar smoothCurve(SkScalar t) { + return t * t * (3 - 2 * t); +} + +} // end namespace + +SkPerlinNoiseShaderImpl::SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type, + SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, + SkScalar seed, + const SkISize* tileSize) + : fType(type) + , fBaseFrequencyX(baseFrequencyX) + , fBaseFrequencyY(baseFrequencyY) + , fNumOctaves(numOctaves > kMaxOctaves ? kMaxOctaves : numOctaves/*[0,255] octaves allowed*/) + , fSeed(seed) + , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize) + , fStitchTiles(!fTileSize.isEmpty()) +{ + SkASSERT(numOctaves >= 0 && numOctaves <= kMaxOctaves); + SkASSERT(fBaseFrequencyX >= 0); + SkASSERT(fBaseFrequencyY >= 0); +} + +sk_sp SkPerlinNoiseShaderImpl::CreateProc(SkReadBuffer& buffer) { + Type type = buffer.read32LE(kLast_Type); + + SkScalar freqX = buffer.readScalar(); + SkScalar freqY = buffer.readScalar(); + int octaves = buffer.read32LE(kMaxOctaves); + + SkScalar seed = buffer.readScalar(); + SkISize tileSize; + tileSize.fWidth = buffer.readInt(); + tileSize.fHeight = buffer.readInt(); + + switch (type) { + case kFractalNoise_Type: + return SkPerlinNoiseShader::MakeFractalNoise(freqX, freqY, octaves, seed, &tileSize); + case kTurbulence_Type: + return SkPerlinNoiseShader::MakeTurbulence(freqX, freqY, octaves, seed, &tileSize); + case kImprovedNoise_Type: + return SkPerlinNoiseShader::MakeImprovedNoise(freqX, freqY, octaves, seed); + default: + // Really shouldn't get here b.c. of earlier check on type + buffer.validate(false); + return nullptr; + } +} + +void SkPerlinNoiseShaderImpl::flatten(SkWriteBuffer& buffer) const { + buffer.writeInt((int) fType); + buffer.writeScalar(fBaseFrequencyX); + buffer.writeScalar(fBaseFrequencyY); + buffer.writeInt(fNumOctaves); + buffer.writeScalar(fSeed); + buffer.writeInt(fTileSize.fWidth); + buffer.writeInt(fTileSize.fHeight); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::noise2D( + int channel, const StitchData& stitchData, const SkPoint& noiseVector) const { + struct Noise { + int noisePositionIntegerValue; + int nextNoisePositionIntegerValue; + SkScalar noisePositionFractionValue; + Noise(SkScalar component) + { + SkScalar position = component + kPerlinNoise; + noisePositionIntegerValue = SkScalarFloorToInt(position); + noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue); + nextNoisePositionIntegerValue = noisePositionIntegerValue + 1; + } + }; + Noise noiseX(noiseVector.x()); + Noise noiseY(noiseVector.y()); + SkScalar u, v; + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast(fShader); + // If stitching, adjust lattice points accordingly. + if (perlinNoiseShader.fStitchTiles) { + noiseX.noisePositionIntegerValue = + checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); + noiseY.noisePositionIntegerValue = + checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); + noiseX.nextNoisePositionIntegerValue = + checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); + noiseY.nextNoisePositionIntegerValue = + checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); + } + noiseX.noisePositionIntegerValue &= kBlockMask; + noiseY.noisePositionIntegerValue &= kBlockMask; + noiseX.nextNoisePositionIntegerValue &= kBlockMask; + noiseY.nextNoisePositionIntegerValue &= kBlockMask; + int i = fPaintingData.fLatticeSelector[noiseX.noisePositionIntegerValue]; + int j = fPaintingData.fLatticeSelector[noiseX.nextNoisePositionIntegerValue]; + int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask; + int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask; + int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask; + int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask; + SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue); + SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue); + + if (sx < 0 || sy < 0 || sx > 1 || sy > 1) { + return 0; // Check for pathological inputs. + } + + // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement + SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue, + noiseY.noisePositionFractionValue); // Offset (0,0) + u = fPaintingData.fGradient[channel][b00].dot(fractionValue); + fractionValue.fX -= SK_Scalar1; // Offset (-1,0) + v = fPaintingData.fGradient[channel][b10].dot(fractionValue); + SkScalar a = SkScalarInterp(u, v, sx); + fractionValue.fY -= SK_Scalar1; // Offset (-1,-1) + v = fPaintingData.fGradient[channel][b11].dot(fractionValue); + fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1) + u = fPaintingData.fGradient[channel][b01].dot(fractionValue); + SkScalar b = SkScalarInterp(u, v, sx); + return SkScalarInterp(a, b, sy); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint( + int channel, StitchData& stitchData, const SkPoint& point) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast(fShader); + if (perlinNoiseShader.fStitchTiles) { + // Set up TurbulenceInitial stitch values. + stitchData = fPaintingData.fStitchDataInit; + } + SkScalar turbulenceFunctionResult = 0; + SkPoint noiseVector(SkPoint::Make(point.x() * fPaintingData.fBaseFrequency.fX, + point.y() * fPaintingData.fBaseFrequency.fY)); + SkScalar ratio = SK_Scalar1; + for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) { + SkScalar noise = noise2D(channel, stitchData, noiseVector); + SkScalar numer = (perlinNoiseShader.fType == kFractalNoise_Type) ? + noise : SkScalarAbs(noise); + turbulenceFunctionResult += numer / ratio; + noiseVector.fX *= 2; + noiseVector.fY *= 2; + ratio *= 2; + if (perlinNoiseShader.fStitchTiles) { + // Update stitch values + stitchData = StitchData(SkIntToScalar(stitchData.fWidth) * 2, + SkIntToScalar(stitchData.fHeight) * 2); + } + } + + // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 + // by fractalNoise and (turbulenceFunctionResult) by turbulence. + if (perlinNoiseShader.fType == kFractalNoise_Type) { + turbulenceFunctionResult = SkScalarHalf(turbulenceFunctionResult + 1); + } + + if (channel == 3) { // Scale alpha by paint value + turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255; + } + + // Clamp result + return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Improved Perlin Noise based on Java implementation found at http://mrl.nyu.edu/~perlin/noise/ +static SkScalar fade(SkScalar t) { + return t * t * t * (t * (t * 6 - 15) + 10); +} + +static SkScalar lerp(SkScalar t, SkScalar a, SkScalar b) { + return a + t * (b - a); +} + +static SkScalar grad(int hash, SkScalar x, SkScalar y, SkScalar z) { + int h = hash & 15; + SkScalar u = h < 8 ? x : y; + SkScalar v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateImprovedNoiseValueForPoint( + int channel, const SkPoint& point) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast(fShader); + SkScalar x = point.fX * perlinNoiseShader.fBaseFrequencyX; + SkScalar y = point.fY * perlinNoiseShader.fBaseFrequencyY; + // z offset between different channels, chosen arbitrarily + static const SkScalar CHANNEL_DELTA = 1000.0f; + SkScalar z = channel * CHANNEL_DELTA + perlinNoiseShader.fSeed; + SkScalar result = 0; + SkScalar ratio = SK_Scalar1; + for (int i = 0; i < perlinNoiseShader.fNumOctaves; i++) { + int X = SkScalarFloorToInt(x) & 255; + int Y = SkScalarFloorToInt(y) & 255; + int Z = SkScalarFloorToInt(z) & 255; + SkScalar px = x - SkScalarFloorToScalar(x); + SkScalar py = y - SkScalarFloorToScalar(y); + SkScalar pz = z - SkScalarFloorToScalar(z); + SkScalar u = fade(px); + SkScalar v = fade(py); + SkScalar w = fade(pz); + uint8_t* permutations = improved_noise_permutations; + int A = permutations[X] + Y; + int AA = permutations[A] + Z; + int AB = permutations[A + 1] + Z; + int B = permutations[X + 1] + Y; + int BA = permutations[B] + Z; + int BB = permutations[B + 1] + Z; + result += lerp(w, lerp(v, lerp(u, grad(permutations[AA ], px , py , pz ), + grad(permutations[BA ], px - 1, py , pz )), + lerp(u, grad(permutations[AB ], px , py - 1, pz ), + grad(permutations[BB ], px - 1, py - 1, pz ))), + lerp(v, lerp(u, grad(permutations[AA + 1], px , py , pz - 1), + grad(permutations[BA + 1], px - 1, py , pz - 1)), + lerp(u, grad(permutations[AB + 1], px , py - 1, pz - 1), + grad(permutations[BB + 1], px - 1, py - 1, pz - 1)))) / + ratio; + x *= 2; + y *= 2; + ratio *= 2; + } + result = SkScalarClampMax((result + 1.0f) / 2.0f, 1.0f); + return result; +} +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SkPMColor SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shade( + const SkPoint& point, StitchData& stitchData) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast(fShader); + SkPoint newPoint; + fMatrix.mapPoints(&newPoint, &point, 1); + newPoint.fX = SkScalarRoundToScalar(newPoint.fX); + newPoint.fY = SkScalarRoundToScalar(newPoint.fY); + + U8CPU rgba[4]; + for (int channel = 3; channel >= 0; --channel) { + SkScalar value; + if (perlinNoiseShader.fType == kImprovedNoise_Type) { + value = calculateImprovedNoiseValueForPoint(channel, newPoint); + } + else { + value = calculateTurbulenceValueForPoint(channel, stitchData, newPoint); + } + rgba[channel] = SkScalarFloorToInt(255 * value); + } + return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]); +} + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +SkShaderBase::Context* SkPerlinNoiseShaderImpl::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + // should we pay attention to rec's device-colorspace? + return alloc->make(*this, rec); +} +#endif + +static inline SkMatrix total_matrix(const SkShaderBase::ContextRec& rec, + const SkShaderBase& shader) { + SkMatrix matrix = SkMatrix::Concat(*rec.fMatrix, shader.getLocalMatrix()); + if (rec.fLocalMatrix) { + matrix.preConcat(*rec.fLocalMatrix); + } + + return matrix; +} + +SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::PerlinNoiseShaderContext( + const SkPerlinNoiseShaderImpl& shader, const ContextRec& rec) + : INHERITED(shader, rec) + , fMatrix(total_matrix(rec, shader)) // used for temp storage, adjusted below + , fPaintingData(shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX, + shader.fBaseFrequencyY, fMatrix) +{ + // This (1,1) translation is due to WebKit's 1 based coordinates for the noise + // (as opposed to 0 based, usually). The same adjustment is in the setData() function. + fMatrix.setTranslate(-fMatrix.getTranslateX() + SK_Scalar1, + -fMatrix.getTranslateY() + SK_Scalar1); +} + +void SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shadeSpan( + int x, int y, SkPMColor result[], int count) { + SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y)); + StitchData stitchData; + for (int i = 0; i < count; ++i) { + result[i] = shade(point, stitchData); + point.fX += SK_Scalar1; + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +class GrGLPerlinNoise : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fStitchDataUni; + GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrPerlinNoise2Effect : public GrFragmentProcessor { +public: + static std::unique_ptr Make( + SkPerlinNoiseShaderImpl::Type type, int numOctaves, bool stitchTiles, + std::unique_ptr paintingData, + sk_sp permutationsProxy, sk_sp noiseProxy, + const SkMatrix& matrix) { + return std::unique_ptr(new GrPerlinNoise2Effect( + type, numOctaves, stitchTiles, std::move(paintingData), + std::move(permutationsProxy), std::move(noiseProxy), matrix)); + } + + const char* name() const override { return "PerlinNoise"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrPerlinNoise2Effect(*this)); + } + + const SkPerlinNoiseShaderImpl::StitchData& stitchData() const { return fPaintingData->fStitchDataInit; } + + SkPerlinNoiseShaderImpl::Type type() const { return fType; } + bool stitchTiles() const { return fStitchTiles; } + const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; } + int numOctaves() const { return fNumOctaves; } + const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + return new GrGLPerlinNoise; + } + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override { + GrGLPerlinNoise::GenKey(*this, caps, b); + } + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const GrPerlinNoise2Effect& s = sBase.cast(); + return fType == s.fType && + fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency && + fNumOctaves == s.fNumOctaves && + fStitchTiles == s.fStitchTiles && + fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit; + } + + GrPerlinNoise2Effect(SkPerlinNoiseShaderImpl::Type type, int numOctaves, bool stitchTiles, + std::unique_ptr paintingData, + sk_sp permutationsProxy, + sk_sp noiseProxy, + const SkMatrix& matrix) + : INHERITED(kGrPerlinNoise2Effect_ClassID, kNone_OptimizationFlags) + , fType(type) + , fNumOctaves(numOctaves) + , fStitchTiles(stitchTiles) + , fPermutationsSampler(std::move(permutationsProxy)) + , fNoiseSampler(std::move(noiseProxy)) + , fPaintingData(std::move(paintingData)) { + this->setTextureSamplerCnt(2); + fCoordTransform = GrCoordTransform(matrix); + this->addCoordTransform(&fCoordTransform); + } + + GrPerlinNoise2Effect(const GrPerlinNoise2Effect& that) + : INHERITED(kGrPerlinNoise2Effect_ClassID, kNone_OptimizationFlags) + , fType(that.fType) + , fCoordTransform(that.fCoordTransform) + , fNumOctaves(that.fNumOctaves) + , fStitchTiles(that.fStitchTiles) + , fPermutationsSampler(that.fPermutationsSampler) + , fNoiseSampler(that.fNoiseSampler) + , fPaintingData(new SkPerlinNoiseShaderImpl::PaintingData(*that.fPaintingData)) { + this->setTextureSamplerCnt(2); + this->addCoordTransform(&fCoordTransform); + } + + const TextureSampler& onTextureSampler(int i) const override { + return IthTextureSampler(i, fPermutationsSampler, fNoiseSampler); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + SkPerlinNoiseShaderImpl::Type fType; + GrCoordTransform fCoordTransform; + int fNumOctaves; + bool fStitchTiles; + TextureSampler fPermutationsSampler; + TextureSampler fNoiseSampler; + std::unique_ptr fPaintingData; + + typedef GrFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect); + +#if GR_TEST_UTILS +std::unique_ptr GrPerlinNoise2Effect::TestCreate(GrProcessorTestData* d) { + int numOctaves = d->fRandom->nextRangeU(2, 10); + bool stitchTiles = d->fRandom->nextBool(); + SkScalar seed = SkIntToScalar(d->fRandom->nextU()); + SkISize tileSize = SkISize::Make(d->fRandom->nextRangeU(4, 4096), + d->fRandom->nextRangeU(4, 4096)); + SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + + sk_sp shader(d->fRandom->nextBool() ? + SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed, + stitchTiles ? &tileSize : nullptr) : + SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed, + stitchTiles ? &tileSize : nullptr)); + + GrTest::TestAsFPArgs asFPArgs(d); + return as_SB(shader)->asFragmentProcessor(asFPArgs.args()); +} +#endif + +void GrGLPerlinNoise::emitCode(EmitArgs& args) { + const GrPerlinNoise2Effect& pne = args.fFp.cast(); + + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + + fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "baseFrequency"); + const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni); + + const char* stitchDataUni = nullptr; + if (pne.stitchTiles()) { + fStitchDataUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "stitchData"); + stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni); + } + + // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8 + const char* chanCoordR = "0.125"; + const char* chanCoordG = "0.375"; + const char* chanCoordB = "0.625"; + const char* chanCoordA = "0.875"; + const char* chanCoord = "chanCoord"; + const char* stitchData = "stitchData"; + const char* ratio = "ratio"; + const char* noiseVec = "noiseVec"; + const char* noiseSmooth = "noiseSmooth"; + const char* floorVal = "floorVal"; + const char* fractVal = "fractVal"; + const char* uv = "uv"; + const char* ab = "ab"; + const char* latticeIdx = "latticeIdx"; + const char* bcoords = "bcoords"; + const char* lattice = "lattice"; + const char* inc8bit = "0.00390625"; // 1.0 / 256.0 + // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a + // [-1,1] vector and perform a dot product between that vector and the provided vector. + const char* dotLattice = "dot(((%s.ga + %s.rb * half2(%s)) * half2(2.0) - half2(1.0)), %s);"; + + // Add noise function + const GrShaderVar gPerlinNoiseArgs[] = { + GrShaderVar(chanCoord, kHalf_GrSLType), + GrShaderVar(noiseVec, kHalf2_GrSLType) + }; + + const GrShaderVar gPerlinNoiseStitchArgs[] = { + GrShaderVar(chanCoord, kHalf_GrSLType), + GrShaderVar(noiseVec, kHalf2_GrSLType), + GrShaderVar(stitchData, kHalf2_GrSLType) + }; + + SkString noiseCode; + + noiseCode.appendf("\thalf4 %s;\n", floorVal); + noiseCode.appendf("\t%s.xy = floor(%s);\n", floorVal, noiseVec); + noiseCode.appendf("\t%s.zw = %s.xy + half2(1.0);\n", floorVal, floorVal); + noiseCode.appendf("\thalf2 %s = fract(%s);\n", fractVal, noiseVec); + + // smooth curve : t * t * (3 - 2 * t) + noiseCode.appendf("\n\thalf2 %s = %s * %s * (half2(3.0) - half2(2.0) * %s);", + noiseSmooth, fractVal, fractVal, fractVal); + + // Adjust frequencies if we're stitching tiles + if (pne.stitchTiles()) { + noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.z >= %s.x) { %s.z -= %s.x; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.w >= %s.y) { %s.w -= %s.y; }", + floorVal, stitchData, floorVal, stitchData); + } + + // Get texture coordinates and normalize + noiseCode.appendf("\n\t%s = fract(floor(mod(%s, 256.0)) / half4(256.0));\n", + floorVal, floorVal); + + // Get permutation for x + { + SkString xCoords(""); + xCoords.appendf("half2(%s.x, 0.5)", floorVal); + + noiseCode.appendf("\n\thalf2 %s;\n\t%s.x = ", latticeIdx, latticeIdx); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[0], xCoords.c_str(), + kHalf2_GrSLType); + noiseCode.append(".r;"); + } + + // Get permutation for x + 1 + { + SkString xCoords(""); + xCoords.appendf("half2(%s.z, 0.5)", floorVal); + + noiseCode.appendf("\n\t%s.y = ", latticeIdx); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[0], xCoords.c_str(), + kHalf2_GrSLType); + noiseCode.append(".r;"); + } + +#if defined(SK_BUILD_FOR_ANDROID) + // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3). + // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit + // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725 + // (or 0.484368 here). The following rounding operation prevents these precision issues from + // affecting the result of the noise by making sure that we only have multiples of 1/255. + // (Note that 1/255 is about 0.003921569, which is the value used here). + noiseCode.appendf("\n\t%s = floor(%s * half2(255.0) + half2(0.5)) * half2(0.003921569);", + latticeIdx, latticeIdx); +#endif + + // Get (x,y) coordinates with the permutated x + noiseCode.appendf("\n\thalf4 %s = fract(%s.xyxy + %s.yyww);", bcoords, latticeIdx, floorVal); + + noiseCode.appendf("\n\n\thalf2 %s;", uv); + // Compute u, at offset (0,0) + { + SkString latticeCoords(""); + latticeCoords.appendf("half2(%s.x, %s)", bcoords, chanCoord); + noiseCode.appendf("\n\thalf4 %s = ", lattice); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kHalf2_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.x = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal); + // Compute v, at offset (-1,0) + { + SkString latticeCoords(""); + latticeCoords.appendf("half2(%s.y, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kHalf2_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.y = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + // Compute 'a' as a linear interpolation of 'u' and 'v' + noiseCode.appendf("\n\thalf2 %s;", ab); + noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); + + noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal); + // Compute v, at offset (-1,-1) + { + SkString latticeCoords(""); + latticeCoords.appendf("half2(%s.w, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kHalf2_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.y = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + noiseCode.appendf("\n\t%s.x += 1.0;", fractVal); + // Compute u, at offset (0,-1) + { + SkString latticeCoords(""); + latticeCoords.appendf("half2(%s.z, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kHalf2_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.x = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + // Compute 'b' as a linear interpolation of 'u' and 'v' + noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); + // Compute the noise as a linear interpolation of 'a' and 'b' + noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth); + + SkString noiseFuncName; + if (pne.stitchTiles()) { + fragBuilder->emitFunction(kHalf_GrSLType, + "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs), + gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName); + } else { + fragBuilder->emitFunction(kHalf_GrSLType, + "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs), + gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName); + } + + // There are rounding errors if the floor operation is not performed here + fragBuilder->codeAppendf("\n\t\thalf2 %s = half2(floor(%s.xy) * %s);", + noiseVec, vCoords.c_str(), baseFrequencyUni); + + // Clear the color accumulator + fragBuilder->codeAppendf("\n\t\t%s = half4(0.0);", args.fOutputColor); + + if (pne.stitchTiles()) { + // Set up TurbulenceInitial stitch values. + fragBuilder->codeAppendf("\n\t\thalf2 %s = %s;", stitchData, stitchDataUni); + } + + fragBuilder->codeAppendf("\n\t\thalf %s = 1.0;", ratio); + + // Loop over all octaves + fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves()); + + fragBuilder->codeAppendf("\n\t\t\t%s += ", args.fOutputColor); + if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + fragBuilder->codeAppend("abs("); + } + if (pne.stitchTiles()) { + fragBuilder->codeAppendf( + "half4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s)," + "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))", + noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData); + } else { + fragBuilder->codeAppendf( + "half4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s)," + "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))", + noiseFuncName.c_str(), chanCoordR, noiseVec, + noiseFuncName.c_str(), chanCoordG, noiseVec, + noiseFuncName.c_str(), chanCoordB, noiseVec, + noiseFuncName.c_str(), chanCoordA, noiseVec); + } + if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + fragBuilder->codeAppendf(")"); // end of "abs(" + } + fragBuilder->codeAppendf(" * %s;", ratio); + + fragBuilder->codeAppendf("\n\t\t\t%s *= half2(2.0);", noiseVec); + fragBuilder->codeAppendf("\n\t\t\t%s *= 0.5;", ratio); + + if (pne.stitchTiles()) { + fragBuilder->codeAppendf("\n\t\t\t%s *= half2(2.0);", stitchData); + } + fragBuilder->codeAppend("\n\t\t}"); // end of the for loop on octaves + + if (pne.type() == SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 + // by fractalNoise and (turbulenceFunctionResult) by turbulence. + fragBuilder->codeAppendf("\n\t\t%s = %s * half4(0.5) + half4(0.5);", + args.fOutputColor,args.fOutputColor); + } + + // Clamp values + fragBuilder->codeAppendf("\n\t\t%s = saturate(%s);", args.fOutputColor, args.fOutputColor); + + // Pre-multiply the result + fragBuilder->codeAppendf("\n\t\t%s = half4(%s.rgb * %s.aaa, %s.a);\n", + args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor); +} + +void GrGLPerlinNoise::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrPerlinNoise2Effect& turbulence = processor.cast(); + + uint32_t key = turbulence.numOctaves(); + + key = key << 3; // Make room for next 3 bits + + switch (turbulence.type()) { + case SkPerlinNoiseShaderImpl::kFractalNoise_Type: + key |= 0x1; + break; + case SkPerlinNoiseShaderImpl::kTurbulence_Type: + key |= 0x2; + break; + default: + // leave key at 0 + break; + } + + if (turbulence.stitchTiles()) { + key |= 0x4; // Flip the 3rd bit if tile stitching is on + } + + b->add32(key); +} + +void GrGLPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + + const GrPerlinNoise2Effect& turbulence = processor.cast(); + + const SkVector& baseFrequency = turbulence.baseFrequency(); + pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); + + if (turbulence.stitchTiles()) { + const SkPerlinNoiseShaderImpl::StitchData& stitchData = turbulence.stitchData(); + pdman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth), + SkIntToScalar(stitchData.fHeight)); + } +} + +///////////////////////////////////////////////////////////////////// + +class GrGLImprovedPerlinNoise : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fZUni; + GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrImprovedPerlinNoiseEffect : public GrFragmentProcessor { +public: + static std::unique_ptr Make( + int octaves, SkScalar z, + std::unique_ptr paintingData, + sk_sp permutationsProxy, sk_sp gradientProxy, + const SkMatrix& matrix) { + return std::unique_ptr(new GrImprovedPerlinNoiseEffect( + octaves, z, std::move(paintingData), std::move(permutationsProxy), + std::move(gradientProxy), matrix)); + } + + const char* name() const override { return "ImprovedPerlinNoise"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new GrImprovedPerlinNoiseEffect(*this)); + } + + const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; } + SkScalar z() const { return fZ; } + int octaves() const { return fOctaves; } + const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + return new GrGLImprovedPerlinNoise; + } + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GrGLImprovedPerlinNoise::GenKey(*this, caps, b); + } + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const GrImprovedPerlinNoiseEffect& s = sBase.cast(); + return fZ == fZ && + fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency; + } + + GrImprovedPerlinNoiseEffect(int octaves, SkScalar z, + std::unique_ptr paintingData, + sk_sp permutationsProxy, + sk_sp gradientProxy, + const SkMatrix& matrix) + : INHERITED(kGrImprovedPerlinNoiseEffect_ClassID, kNone_OptimizationFlags) + , fOctaves(octaves) + , fZ(z) + , fPermutationsSampler(std::move(permutationsProxy)) + , fGradientSampler(std::move(gradientProxy)) + , fPaintingData(std::move(paintingData)) { + this->setTextureSamplerCnt(2); + fCoordTransform = GrCoordTransform(matrix); + this->addCoordTransform(&fCoordTransform); + } + + GrImprovedPerlinNoiseEffect(const GrImprovedPerlinNoiseEffect& that) + : INHERITED(kGrImprovedPerlinNoiseEffect_ClassID, kNone_OptimizationFlags) + , fCoordTransform(that.fCoordTransform) + , fOctaves(that.fOctaves) + , fZ(that.fZ) + , fPermutationsSampler(that.fPermutationsSampler) + , fGradientSampler(that.fGradientSampler) + , fPaintingData(new SkPerlinNoiseShaderImpl::PaintingData(*that.fPaintingData)) { + this->setTextureSamplerCnt(2); + this->addCoordTransform(&fCoordTransform); + } + + const TextureSampler& onTextureSampler(int i) const override { + return IthTextureSampler(i, fPermutationsSampler, fGradientSampler); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + + GrCoordTransform fCoordTransform; + int fOctaves; + SkScalar fZ; + TextureSampler fPermutationsSampler; + TextureSampler fGradientSampler; + std::unique_ptr fPaintingData; + + typedef GrFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrImprovedPerlinNoiseEffect); + +#if GR_TEST_UTILS +std::unique_ptr GrImprovedPerlinNoiseEffect::TestCreate( + GrProcessorTestData* d) { + SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + int numOctaves = d->fRandom->nextRangeU(2, 10); + SkScalar z = SkIntToScalar(d->fRandom->nextU()); + + sk_sp shader(SkPerlinNoiseShader::MakeImprovedNoise(baseFrequencyX, + baseFrequencyY, + numOctaves, + z)); + + GrTest::TestAsFPArgs asFPArgs(d); + return as_SB(shader)->asFragmentProcessor(asFPArgs.args()); +} +#endif + +void GrGLImprovedPerlinNoise::emitCode(EmitArgs& args) { + const GrImprovedPerlinNoiseEffect& pne = args.fFp.cast(); + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint); + + fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, + "baseFrequency"); + const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni); + + fZUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "z"); + const char* zUni = uniformHandler->getUniformCStr(fZUni); + + // fade function + const GrShaderVar fadeArgs[] = { + GrShaderVar("t", kHalf3_GrSLType) + }; + SkString fadeFuncName; + fragBuilder->emitFunction(kHalf3_GrSLType, "fade", SK_ARRAY_COUNT(fadeArgs), + fadeArgs, + "return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);", + &fadeFuncName); + + // perm function + const GrShaderVar permArgs[] = { + GrShaderVar("x", kHalf_GrSLType) + }; + SkString permFuncName; + SkString permCode("return "); + // FIXME even though I'm creating these textures with kRepeat_TileMode, they're clamped. Not + // sure why. Using fract() (here and the next texture lookup) as a workaround. + fragBuilder->appendTextureLookup(&permCode, args.fTexSamplers[0], "float2(fract(x / 256.0), 0.0)", + kHalf2_GrSLType); + permCode.append(".r * 255.0;"); + fragBuilder->emitFunction(kHalf_GrSLType, "perm", SK_ARRAY_COUNT(permArgs), permArgs, + permCode.c_str(), &permFuncName); + + // grad function + const GrShaderVar gradArgs[] = { + GrShaderVar("x", kHalf_GrSLType), + GrShaderVar("p", kHalf3_GrSLType) + }; + SkString gradFuncName; + SkString gradCode("return half(dot("); + fragBuilder->appendTextureLookup(&gradCode, args.fTexSamplers[1], "float2(fract(x / 16.0), 0.0)", + kHalf2_GrSLType); + gradCode.append(".rgb * 255.0 - float3(1.0), p));"); + fragBuilder->emitFunction(kHalf_GrSLType, "grad", SK_ARRAY_COUNT(gradArgs), gradArgs, + gradCode.c_str(), &gradFuncName); + + // lerp function + const GrShaderVar lerpArgs[] = { + GrShaderVar("a", kHalf_GrSLType), + GrShaderVar("b", kHalf_GrSLType), + GrShaderVar("w", kHalf_GrSLType) + }; + SkString lerpFuncName; + fragBuilder->emitFunction(kHalf_GrSLType, "lerp", SK_ARRAY_COUNT(lerpArgs), lerpArgs, + "return a + w * (b - a);", &lerpFuncName); + + // noise function + const GrShaderVar noiseArgs[] = { + GrShaderVar("p", kHalf3_GrSLType), + }; + SkString noiseFuncName; + SkString noiseCode; + noiseCode.append("half3 P = mod(floor(p), 256.0);"); + noiseCode.append("p -= floor(p);"); + noiseCode.appendf("half3 f = %s(p);", fadeFuncName.c_str()); + noiseCode.appendf("half A = %s(P.x) + P.y;", permFuncName.c_str()); + noiseCode.appendf("half AA = %s(A) + P.z;", permFuncName.c_str()); + noiseCode.appendf("half AB = %s(A + 1.0) + P.z;", permFuncName.c_str()); + noiseCode.appendf("half B = %s(P.x + 1.0) + P.y;", permFuncName.c_str()); + noiseCode.appendf("half BA = %s(B) + P.z;", permFuncName.c_str()); + noiseCode.appendf("half BB = %s(B + 1.0) + P.z;", permFuncName.c_str()); + noiseCode.appendf("half result = %s(", lerpFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(%s(AA), p),", lerpFuncName.c_str(), lerpFuncName.c_str(), + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(BA), p + half3(-1.0, 0.0, 0.0)), f.x),", gradFuncName.c_str(), + permFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(AB), p + half3(0.0, -1.0, 0.0)),", lerpFuncName.c_str(), + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(BB), p + half3(-1.0, -1.0, 0.0)), f.x), f.y),", + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(%s(AA + 1.0), p + half3(0.0, 0.0, -1.0)),", + lerpFuncName.c_str(), lerpFuncName.c_str(), gradFuncName.c_str(), + permFuncName.c_str()); + noiseCode.appendf("%s(%s(BA + 1.0), p + half3(-1.0, 0.0, -1.0)), f.x),", + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(AB + 1.0), p + half3(0.0, -1.0, -1.0)),", + lerpFuncName.c_str(), gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(BB + 1.0), p + half3(-1.0, -1.0, -1.0)), f.x), f.y), f.z);", + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.append("return result;"); + fragBuilder->emitFunction(kHalf_GrSLType, "noise", SK_ARRAY_COUNT(noiseArgs), noiseArgs, + noiseCode.c_str(), &noiseFuncName); + + // noiseOctaves function + const GrShaderVar noiseOctavesArgs[] = { + GrShaderVar("p", kHalf3_GrSLType) + }; + SkString noiseOctavesFuncName; + SkString noiseOctavesCode; + noiseOctavesCode.append("half result = 0.0;"); + noiseOctavesCode.append("half ratio = 1.0;"); + noiseOctavesCode.appendf("for (half i = 0.0; i < %d; i++) {", pne.octaves()); + noiseOctavesCode.appendf("result += %s(p) / ratio;", noiseFuncName.c_str()); + noiseOctavesCode.append("p *= 2.0;"); + noiseOctavesCode.append("ratio *= 2.0;"); + noiseOctavesCode.append("}"); + noiseOctavesCode.append("return (result + 1.0) / 2.0;"); + fragBuilder->emitFunction(kHalf_GrSLType, "noiseOctaves", SK_ARRAY_COUNT(noiseOctavesArgs), + noiseOctavesArgs, noiseOctavesCode.c_str(), &noiseOctavesFuncName); + + fragBuilder->codeAppendf("half2 coords = half2(%s * %s);", vCoords.c_str(), baseFrequencyUni); + fragBuilder->codeAppendf("half r = %s(half3(coords, %s));", noiseOctavesFuncName.c_str(), + zUni); + fragBuilder->codeAppendf("half g = %s(half3(coords, %s + 0000.0));", + noiseOctavesFuncName.c_str(), zUni); + fragBuilder->codeAppendf("half b = %s(half3(coords, %s + 0000.0));", + noiseOctavesFuncName.c_str(), zUni); + fragBuilder->codeAppendf("half a = %s(half3(coords, %s + 0000.0));", + noiseOctavesFuncName.c_str(), zUni); + fragBuilder->codeAppendf("%s = half4(r, g, b, a);", args.fOutputColor); + + // Clamp values + fragBuilder->codeAppendf("%s = saturate(%s);", args.fOutputColor, args.fOutputColor); + + // Pre-multiply the result + fragBuilder->codeAppendf("\n\t\t%s = half4(%s.rgb * %s.aaa, %s.a);\n", + args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor); +} + +void GrGLImprovedPerlinNoise::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrImprovedPerlinNoiseEffect& pne = processor.cast(); + b->add32(pne.octaves()); +} + +void GrGLImprovedPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + + const GrImprovedPerlinNoiseEffect& noise = processor.cast(); + + const SkVector& baseFrequency = noise.baseFrequency(); + pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); + + pdman.set1f(fZUni, noise.z()); +} + +///////////////////////////////////////////////////////////////////// +std::unique_ptr SkPerlinNoiseShaderImpl::asFragmentProcessor( + const GrFPArgs& args) const { + SkASSERT(args.fContext); + + const auto localMatrix = this->totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix); + const auto paintMatrix = SkMatrix::Concat(*args.fViewMatrix, *localMatrix); + + // Either we don't stitch tiles, either we have a valid tile size + SkASSERT(!fStitchTiles || !fTileSize.isEmpty()); + + std::unique_ptr paintingData = + skstd::make_unique(fTileSize, + fSeed, + fBaseFrequencyX, + fBaseFrequencyY, + paintMatrix); + + SkMatrix m = *args.fViewMatrix; + m.setTranslateX(-localMatrix->getTranslateX() + SK_Scalar1); + m.setTranslateY(-localMatrix->getTranslateY() + SK_Scalar1); + + auto proxyProvider = args.fContext->priv().proxyProvider(); + if (fType == kImprovedNoise_Type) { + // Need to assert that the textures we'll create are power of 2 so a copy isn't needed. + // We also know that we will not be using mipmaps. If things things weren't true we should + // go through GrBitmapTextureMaker to handle needed copies. + const sk_sp permutationsImage = paintingData->getImprovedPermutationsImage(); + SkASSERT(SkIsPow2(permutationsImage->width()) && SkIsPow2(permutationsImage->height())); + sk_sp permutationsTexture( + GrMakeCachedImageProxy(proxyProvider, std::move(permutationsImage))); + + const sk_sp gradientImage = paintingData->getGradientImage(); + SkASSERT(SkIsPow2(gradientImage->width()) && SkIsPow2(gradientImage->height())); + sk_sp gradientTexture( + GrMakeCachedImageProxy(proxyProvider, std::move(gradientImage))); + return GrImprovedPerlinNoiseEffect::Make(fNumOctaves, fSeed, std::move(paintingData), + std::move(permutationsTexture), + std::move(gradientTexture), m); + } + + if (0 == fNumOctaves) { + if (kFractalNoise_Type == fType) { + // Extract the incoming alpha and emit rgba = (a/4, a/4, a/4, a/2) + // TODO: Either treat the output of this shader as sRGB or allow client to specify a + // color space of the noise. Either way, this case (and the GLSL) need to convert to + // the destination. + auto inner = + GrConstColorProcessor::Make(SkPMColor4f::FromBytes_RGBA(0x80404040), + GrConstColorProcessor::InputMode::kModulateRGBA); + return GrFragmentProcessor::MulChildByInputAlpha(std::move(inner)); + } + // Emit zero. + return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, + GrConstColorProcessor::InputMode::kIgnore); + } + + // Need to assert that the textures we'll create are power of 2 so that now copy is needed. We + // also know that we will not be using mipmaps. If things things weren't true we should go + // through GrBitmapTextureMaker to handle needed copies. + const sk_sp permutationsImage = paintingData->getPermutationsImage(); + SkASSERT(SkIsPow2(permutationsImage->width()) && SkIsPow2(permutationsImage->height())); + sk_sp permutationsProxy = GrMakeCachedImageProxy(proxyProvider, + std::move(permutationsImage)); + + const sk_sp noiseImage = paintingData->getNoiseImage(); + SkASSERT(SkIsPow2(noiseImage->width()) && SkIsPow2(noiseImage->height())); + sk_sp noiseProxy = GrMakeCachedImageProxy(proxyProvider, + std::move(noiseImage)); + + if (permutationsProxy && noiseProxy) { + auto inner = GrPerlinNoise2Effect::Make(fType, + fNumOctaves, + fStitchTiles, + std::move(paintingData), + std::move(permutationsProxy), + std::move(noiseProxy), + m); + return GrFragmentProcessor::MulChildByInputAlpha(std::move(inner)); + } + return nullptr; +} + +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool valid_input(SkScalar baseX, SkScalar baseY, int numOctaves, const SkISize* tileSize, + SkScalar seed) { + if (!(baseX >= 0 && baseY >= 0)) { + return false; + } + if (!(numOctaves >= 0 && numOctaves <= SkPerlinNoiseShaderImpl::kMaxOctaves)) { + return false; + } + if (tileSize && !(tileSize->width() >= 0 && tileSize->height() >= 0)) { + return false; + } + if (!SkScalarIsFinite(seed)) { + return false; + } + return true; +} + +sk_sp SkPerlinNoiseShader::MakeFractalNoise(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize) { + if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, tileSize, seed)) { + return nullptr; + } + return sk_sp(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kFractalNoise_Type, + baseFrequencyX, baseFrequencyY, numOctaves, seed, + tileSize)); +} + +sk_sp SkPerlinNoiseShader::MakeTurbulence(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize) { + if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, tileSize, seed)) { + return nullptr; + } + return sk_sp(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kTurbulence_Type, + baseFrequencyX, baseFrequencyY, numOctaves, seed, + tileSize)); +} + +sk_sp SkPerlinNoiseShader::MakeImprovedNoise(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar z) { + if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, nullptr, z)) { + return nullptr; + } + return sk_sp(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kImprovedNoise_Type, + baseFrequencyX, baseFrequencyY, numOctaves, z, + nullptr)); +} + +void SkPerlinNoiseShader::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkPerlinNoiseShaderImpl); +} diff --git a/gfx/skia/skia/src/shaders/SkPictureShader.cpp b/gfx/skia/skia/src/shaders/SkPictureShader.cpp new file mode 100644 index 0000000000..fc99118e42 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkPictureShader.cpp @@ -0,0 +1,365 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/shaders/SkPictureShader.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkImage.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkMatrixUtils.h" +#include "src/core/SkPicturePriv.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkResourceCache.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkImageShader.h" +#include + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" +#endif + +sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix, + const SkRect* tile) const { + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, localMatrix, tile); +} + +sk_sp SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix) const { + return this->makeShader(tmx, tmy, localMatrix, nullptr); +} + +namespace { +static unsigned gBitmapShaderKeyNamespaceLabel; + +struct BitmapShaderKey : public SkResourceCache::Key { +public: + BitmapShaderKey(SkColorSpace* colorSpace, + SkImage::BitDepth bitDepth, + uint32_t shaderID, + const SkSize& scale) + : fColorSpaceXYZHash(colorSpace->toXYZD50Hash()) + , fColorSpaceTransferFnHash(colorSpace->transferFnHash()) + , fBitDepth(bitDepth) + , fScale(scale) { + + static const size_t keySize = sizeof(fColorSpaceXYZHash) + + sizeof(fColorSpaceTransferFnHash) + + sizeof(fBitDepth) + + sizeof(fScale); + // This better be packed. + SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fColorSpaceXYZHash) == keySize); + this->init(&gBitmapShaderKeyNamespaceLabel, MakeSharedID(shaderID), keySize); + } + + static uint64_t MakeSharedID(uint32_t shaderID) { + uint64_t sharedID = SkSetFourByteTag('p', 's', 'd', 'r'); + return (sharedID << 32) | shaderID; + } + +private: + uint32_t fColorSpaceXYZHash; + uint32_t fColorSpaceTransferFnHash; + SkImage::BitDepth fBitDepth; + SkSize fScale; + + SkDEBUGCODE(uint32_t fEndOfStruct;) +}; + +struct BitmapShaderRec : public SkResourceCache::Rec { + BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader) + : fKey(key) + , fShader(SkRef(tileShader)) {} + + BitmapShaderKey fKey; + sk_sp fShader; + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { + // Just the record overhead -- the actual pixels are accounted by SkImage_Lazy. + return sizeof(fKey) + sizeof(SkImageShader); + } + const char* getCategory() const override { return "bitmap-shader"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; } + + static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) { + const BitmapShaderRec& rec = static_cast(baseRec); + sk_sp* result = reinterpret_cast*>(contextShader); + + *result = rec.fShader; + + // The bitmap shader is backed by an image generator, thus it can always re-generate its + // pixels if discarded. + return true; + } +}; + +uint32_t next_id() { + static std::atomic nextID{1}; + + uint32_t id; + do { + id = nextID++; + } while (id == SK_InvalidGenID); + return id; +} + +} // namespace + +SkPictureShader::SkPictureShader(sk_sp picture, SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, const SkRect* tile) + : INHERITED(localMatrix) + , fPicture(std::move(picture)) + , fTile(tile ? *tile : fPicture->cullRect()) + , fTmx(tmx) + , fTmy(tmy) + , fUniqueID(next_id()) + , fAddedToCache(false) {} + +SkPictureShader::~SkPictureShader() { + if (fAddedToCache.load()) { + SkResourceCache::PostPurgeSharedID(BitmapShaderKey::MakeSharedID(fUniqueID)); + } +} + +sk_sp SkPictureShader::Make(sk_sp picture, SkTileMode tmx, SkTileMode tmy, + const SkMatrix* localMatrix, const SkRect* tile) { + if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) { + return SkShaders::Empty(); + } + return sk_sp(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile)); +} + +SkPicture* SkPictureShader::isAPicture(SkMatrix* matrix, + SkTileMode tileModes[2], + SkRect* tile) const { + if (matrix) { + *matrix = this->getLocalMatrix(); + } + if (tileModes) { + tileModes[0] = fTmx; + tileModes[1] = fTmy; + } + if (tile) { + *tile = fTile; + } + return fPicture.get(); +} + +sk_sp SkPictureShader::CreateProc(SkReadBuffer& buffer) { + SkMatrix lm; + buffer.readMatrix(&lm); + auto tmx = buffer.read32LE(SkTileMode::kLastTileMode); + auto tmy = buffer.read32LE(SkTileMode::kLastTileMode); + SkRect tile; + buffer.readRect(&tile); + + sk_sp picture; + + bool didSerialize = buffer.readBool(); + if (didSerialize) { + picture = SkPicturePriv::MakeFromBuffer(buffer); + } + return SkPictureShader::Make(picture, tmx, tmy, &lm, &tile); +} + +void SkPictureShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(this->getLocalMatrix()); + buffer.write32((unsigned)fTmx); + buffer.write32((unsigned)fTmy); + buffer.writeRect(fTile); + + buffer.writeBool(true); + SkPicturePriv::Flatten(fPicture, buffer); +} + +// Returns a cached image shader, which wraps a single picture tile at the given +// CTM/local matrix. Also adjusts the local matrix for tile scaling. +sk_sp SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix, + SkTCopyOnFirstWrite* localMatrix, + SkColorType dstColorType, + SkColorSpace* dstColorSpace, + const int maxTextureSize) const { + SkASSERT(fPicture && !fPicture->cullRect().isEmpty()); + + const SkMatrix m = SkMatrix::Concat(viewMatrix, **localMatrix); + + // Use a rotation-invariant scale + SkPoint scale; + // + // TODO: replace this with decomposeScale() -- but beware LayoutTest rebaselines! + // + if (!SkDecomposeUpper2x2(m, nullptr, &scale, nullptr)) { + // Decomposition failed, use an approximation. + scale.set(SkScalarSqrt(m.getScaleX() * m.getScaleX() + m.getSkewX() * m.getSkewX()), + SkScalarSqrt(m.getScaleY() * m.getScaleY() + m.getSkewY() * m.getSkewY())); + } + SkSize scaledSize = SkSize::Make(SkScalarAbs(scale.x() * fTile.width()), + SkScalarAbs(scale.y() * fTile.height())); + + // Clamp the tile size to about 4M pixels + static const SkScalar kMaxTileArea = 2048 * 2048; + SkScalar tileArea = scaledSize.width() * scaledSize.height(); + if (tileArea > kMaxTileArea) { + SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea); + scaledSize.set(scaledSize.width() * clampScale, + scaledSize.height() * clampScale); + } +#if SK_SUPPORT_GPU + // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture + if (maxTextureSize) { + if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) { + SkScalar downScale = maxTextureSize / SkMaxScalar(scaledSize.width(), scaledSize.height()); + scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale), + SkScalarFloorToScalar(scaledSize.height() * downScale)); + } + } +#endif + + const SkISize tileSize = scaledSize.toCeil(); + if (tileSize.isEmpty()) { + return SkShaders::Empty(); + } + + // The actual scale, compensating for rounding & clamping. + const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(), + SkIntToScalar(tileSize.height()) / fTile.height()); + + + sk_sp imgCS = dstColorSpace ? sk_ref_sp(dstColorSpace): SkColorSpace::MakeSRGB(); + SkImage::BitDepth bitDepth = + dstColorType >= kRGBA_F16Norm_SkColorType + ? SkImage::BitDepth::kF16 : SkImage::BitDepth::kU8; + + BitmapShaderKey key(imgCS.get(), bitDepth, fUniqueID, tileScale); + + sk_sp tileShader; + if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) { + SkMatrix tileMatrix; + tileMatrix.setRectToRect(fTile, SkRect::MakeIWH(tileSize.width(), tileSize.height()), + SkMatrix::kFill_ScaleToFit); + + sk_sp tileImage = SkImage::MakeFromPicture(fPicture, tileSize, &tileMatrix, + nullptr, bitDepth, std::move(imgCS)); + if (!tileImage) { + return nullptr; + } + + tileShader = tileImage->makeShader(fTmx, fTmy); + + SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get())); + fAddedToCache.store(true); + } + + if (tileScale.width() != 1 || tileScale.height() != 1) { + localMatrix->writable()->preScale(1 / tileScale.width(), 1 / tileScale.height()); + } + + return tileShader; +} + +bool SkPictureShader::onAppendStages(const SkStageRec& rec) const { + auto lm = this->totalLocalMatrix(rec.fLocalM); + + // Keep bitmapShader alive by using alloc instead of stack memory + auto& bitmapShader = *rec.fAlloc->make>(); + bitmapShader = this->refBitmapShader(rec.fCTM, &lm, rec.fDstColorType, rec.fDstCS); + + if (!bitmapShader) { + return false; + } + + SkStageRec localRec = rec; + localRec.fLocalM = lm->isIdentity() ? nullptr : lm.get(); + + return as_SB(bitmapShader)->appendStages(localRec); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) +const { + auto lm = this->totalLocalMatrix(rec.fLocalMatrix); + sk_sp bitmapShader = this->refBitmapShader(*rec.fMatrix, &lm, rec.fDstColorType, + rec.fDstColorSpace); + if (!bitmapShader) { + return nullptr; + } + + ContextRec localRec = rec; + localRec.fLocalMatrix = lm->isIdentity() ? nullptr : lm.get(); + + PictureShaderContext* ctx = + alloc->make(*this, localRec, std::move(bitmapShader), alloc); + if (nullptr == ctx->fBitmapShaderContext) { + ctx = nullptr; + } + return ctx; +} +#endif + +///////////////////////////////////////////////////////////////////////////////////////// + +SkPictureShader::PictureShaderContext::PictureShaderContext( + const SkPictureShader& shader, const ContextRec& rec, sk_sp bitmapShader, + SkArenaAlloc* alloc) + : INHERITED(shader, rec) + , fBitmapShader(std::move(bitmapShader)) +{ + fBitmapShaderContext = as_SB(fBitmapShader)->makeContext(rec, alloc); + //if fBitmapShaderContext is null, we are invalid +} + +uint32_t SkPictureShader::PictureShaderContext::getFlags() const { + SkASSERT(fBitmapShaderContext); + return fBitmapShaderContext->getFlags(); +} + +void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) { + SkASSERT(fBitmapShaderContext); + fBitmapShaderContext->shadeSpan(x, y, dstC, count); +} + +#if SK_SUPPORT_GPU +#include "include/gpu/GrContext.h" +#include "src/gpu/GrContextPriv.h" + +std::unique_ptr SkPictureShader::asFragmentProcessor( + const GrFPArgs& args) const { + int maxTextureSize = 0; + if (args.fContext) { + maxTextureSize = args.fContext->priv().caps()->maxTextureSize(); + } + + auto lm = this->totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix); + SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType()); + if (dstColorType == kUnknown_SkColorType) { + dstColorType = kRGBA_8888_SkColorType; + } + sk_sp bitmapShader(this->refBitmapShader(*args.fViewMatrix, &lm, dstColorType, + args.fDstColorInfo->colorSpace(), + maxTextureSize)); + if (!bitmapShader) { + return nullptr; + } + + // We want to *reset* args.fPreLocalMatrix, not compose it. + GrFPArgs newArgs(args.fContext, args.fViewMatrix, args.fFilterQuality, args.fDstColorInfo); + newArgs.fPreLocalMatrix = lm.get(); + + return as_SB(bitmapShader)->asFragmentProcessor(newArgs); +} +#endif diff --git a/gfx/skia/skia/src/shaders/SkPictureShader.h b/gfx/skia/skia/src/shaders/SkPictureShader.h new file mode 100644 index 0000000000..4ebf39b708 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkPictureShader.h @@ -0,0 +1,81 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureShader_DEFINED +#define SkPictureShader_DEFINED + +#include "include/core/SkTileMode.h" +#include "src/shaders/SkShaderBase.h" +#include + +class SkArenaAlloc; +class SkBitmap; +class SkPicture; + +/* + * An SkPictureShader can be used to draw SkPicture-based patterns. + * + * The SkPicture is first rendered into a tile, which is then used to shade the area according + * to specified tiling rules. + */ +class SkPictureShader : public SkShaderBase { +public: + ~SkPictureShader() override; + + static sk_sp Make(sk_sp, SkTileMode, SkTileMode, const SkMatrix*, + const SkRect*); + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + + SkPicture* isAPicture(SkMatrix*, SkTileMode[2], SkRect* tile) const override; + +protected: + SkPictureShader(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec&) const override; +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; +#endif + +private: + SK_FLATTENABLE_HOOKS(SkPictureShader) + + SkPictureShader(sk_sp, SkTileMode, SkTileMode, const SkMatrix*, const SkRect*); + + sk_sp refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite* localMatrix, + SkColorType dstColorType, SkColorSpace* dstColorSpace, + const int maxTextureSize = 0) const; + + class PictureShaderContext : public Context { + public: + PictureShaderContext( + const SkPictureShader&, const ContextRec&, sk_sp bitmapShader, SkArenaAlloc*); + + uint32_t getFlags() const override; + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + sk_sp fBitmapShader; + SkShaderBase::Context* fBitmapShaderContext; + void* fBitmapShaderContextStorage; + + typedef Context INHERITED; + }; + + sk_sp fPicture; + SkRect fTile; + SkTileMode fTmx, fTmy; + + const uint32_t fUniqueID; + mutable std::atomic fAddedToCache; + + typedef SkShaderBase INHERITED; +}; + +#endif // SkPictureShader_DEFINED diff --git a/gfx/skia/skia/src/shaders/SkRTShader.cpp b/gfx/skia/skia/src/shaders/SkRTShader.cpp new file mode 100644 index 0000000000..c42a8d0e51 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkRTShader.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkRTShader.h" + +#include "src/sksl/SkSLByteCode.h" +#include "src/sksl/SkSLCompiler.h" + +#if SK_SUPPORT_GPU +#include "include/private/GrRecordingContext.h" +#include "src/gpu/GrCaps.h" +#include "src/gpu/GrColorInfo.h" +#include "src/gpu/GrRecordingContextPriv.h" +#include "src/gpu/SkGr.h" + +#include "src/gpu/GrFragmentProcessor.h" +#include "src/gpu/effects/GrSkSLFP.h" +#include "src/gpu/effects/generated/GrMixerEffect.h" + +static inline uint32_t new_sksl_unique_id() { + return GrSkSLFP::NewIndex(); +} +#else +static inline uint32_t new_sksl_unique_id() { + return 0; // not used w/o GPU +} +#endif + +SkRTShader::SkRTShader(int index, SkString sksl, sk_sp inputs, const SkMatrix* localMatrix, + bool isOpaque) + : SkShaderBase(localMatrix) + , fSkSL(std::move(sksl)) + , fInputs(std::move(inputs)) + , fUniqueID(index) + , fIsOpaque(isOpaque) +{} + +bool SkRTShader::onAppendStages(const SkStageRec& rec) const { + SkMatrix inverse; + if (!this->computeTotalInverse(rec.fCTM, rec.fLocalM, &inverse)) { + return false; + } + + auto ctx = rec.fAlloc->make(); + ctx->paintColor = rec.fPaint.getColor4f(); + ctx->inputs = fInputs->data(); + ctx->ninputs = fInputs->size() / 4; + ctx->shaderConvention = true; + + SkAutoMutexExclusive ama(fByteCodeMutex); + if (!fByteCode) { + SkSL::Compiler c; + auto prog = c.convertProgram(SkSL::Program::kPipelineStage_Kind, + SkSL::String(fSkSL.c_str()), + SkSL::Program::Settings()); + if (c.errorCount()) { + SkDebugf("%s\n", c.errorText().c_str()); + return false; + } + fByteCode = c.toByteCode(*prog); + if (c.errorCount()) { + SkDebugf("%s\n", c.errorText().c_str()); + return false; + } + SkASSERT(fByteCode); + if (!fByteCode->getFunction("main")) { + return false; + } + } + ctx->byteCode = fByteCode.get(); + ctx->fn = ctx->byteCode->getFunction("main"); + + rec.fPipeline->append(SkRasterPipeline::seed_shader); + rec.fPipeline->append_matrix(rec.fAlloc, inverse); + rec.fPipeline->append(SkRasterPipeline::interpreter, ctx); + return true; +} + +enum Flags { + kIsOpaque_Flag = 1 << 0, + kHasLocalMatrix_Flag = 1 << 1, +}; + +void SkRTShader::flatten(SkWriteBuffer& buffer) const { + uint32_t flags = 0; + if (fIsOpaque) { + flags |= kIsOpaque_Flag; + } + if (!this->getLocalMatrix().isIdentity()) { + flags |= kHasLocalMatrix_Flag; + } + + buffer.writeString(fSkSL.c_str()); + if (fInputs) { + buffer.writeDataAsByteArray(fInputs.get()); + } else { + buffer.writeByteArray(nullptr, 0); + } + buffer.write32(flags); + if (flags & kHasLocalMatrix_Flag) { + buffer.writeMatrix(this->getLocalMatrix()); + } +} + +sk_sp SkRTShader::CreateProc(SkReadBuffer& buffer) { + // We don't have a way to ensure that indices are consistent and correct when deserializing. + // Perhaps we should have a hash table to map strings to indices? For now, all shaders get a + // new unique ID after serialization. + int index = new_sksl_unique_id(); + + SkString sksl; + buffer.readString(&sksl); + sk_sp inputs = buffer.readByteArrayAsData(); + uint32_t flags = buffer.read32(); + + bool isOpaque = SkToBool(flags & kIsOpaque_Flag); + SkMatrix localM, *localMPtr = nullptr; + if (flags & kHasLocalMatrix_Flag) { + buffer.readMatrix(&localM); + localMPtr = &localM; + } + + return sk_sp(new SkRTShader(index, std::move(sksl), std::move(inputs), + localMPtr, isOpaque)); +} + +#if SK_SUPPORT_GPU +std::unique_ptr SkRTShader::asFragmentProcessor(const GrFPArgs& args) const { + SkMatrix matrix; + if (!this->totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { + return nullptr; + } + return GrSkSLFP::Make(args.fContext, fUniqueID, "runtime-shader", fSkSL, + fInputs->data(), fInputs->size(), SkSL::Program::kPipelineStage_Kind, + &matrix); +} +#endif + +SkRuntimeShaderFactory::SkRuntimeShaderFactory(SkString sksl, bool isOpaque) + : fIndex(new_sksl_unique_id()) + , fSkSL(std::move(sksl)) + , fIsOpaque(isOpaque) {} + +sk_sp SkRuntimeShaderFactory::make(sk_sp inputs, const SkMatrix* localMatrix) { + return sk_sp( + new SkRTShader(fIndex, fSkSL, std::move(inputs), localMatrix, fIsOpaque)); +} diff --git a/gfx/skia/skia/src/shaders/SkRTShader.h b/gfx/skia/skia/src/shaders/SkRTShader.h new file mode 100644 index 0000000000..57d69c324e --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkRTShader.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRTShader_DEFINED +#define SkRTShader_DEFINED + +#include "include/core/SkString.h" +#include "include/private/SkMutex.h" +#include "src/shaders/SkShaderBase.h" +#include "src/sksl/SkSLByteCode.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#endif + +class SkData; +class SkMatrix; + +class SkRTShader : public SkShaderBase { +public: + SkRTShader(int index, SkString sksl, sk_sp inputs, const SkMatrix* localMatrix, + bool isOpaque); + + bool isOpaque() const override { return fIsOpaque; } + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(const SkStageRec& rec) const override; + +private: + SK_FLATTENABLE_HOOKS(SkRTShader) + + SkString fSkSL; + sk_sp fInputs; + const uint32_t fUniqueID; + const bool fIsOpaque; + + mutable SkMutex fByteCodeMutex; + mutable std::unique_ptr fByteCode; + + typedef SkShaderBase INHERITED; +}; + +class SkRuntimeShaderFactory { +public: + SkRuntimeShaderFactory(SkString sksl, bool isOpaque); + + sk_sp make(sk_sp inputs, const SkMatrix* localMatrix); + +private: + int fIndex; + SkString fSkSL; + bool fIsOpaque; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/SkShader.cpp b/gfx/skia/skia/src/shaders/SkShader.cpp new file mode 100644 index 0000000000..0095a9509c --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkShader.cpp @@ -0,0 +1,199 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkMallocPixelRef.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPicture.h" +#include "include/core/SkScalar.h" +#include "src/core/SkArenaAlloc.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkTLazy.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkColorShader.h" +#include "src/shaders/SkEmptyShader.h" +#include "src/shaders/SkPictureShader.h" +#include "src/shaders/SkShaderBase.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFragmentProcessor.h" +#endif + +SkShaderBase::SkShaderBase(const SkMatrix* localMatrix) + : fLocalMatrix(localMatrix ? *localMatrix : SkMatrix::I()) { + // Pre-cache so future calls to fLocalMatrix.getType() are threadsafe. + (void)fLocalMatrix.getType(); +} + +SkShaderBase::~SkShaderBase() {} + +void SkShaderBase::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + bool hasLocalM = !fLocalMatrix.isIdentity(); + buffer.writeBool(hasLocalM); + if (hasLocalM) { + buffer.writeMatrix(fLocalMatrix); + } +} + +SkTCopyOnFirstWrite +SkShaderBase::totalLocalMatrix(const SkMatrix* preLocalMatrix, + const SkMatrix* postLocalMatrix) const { + SkTCopyOnFirstWrite m(fLocalMatrix); + + if (preLocalMatrix) { + m.writable()->preConcat(*preLocalMatrix); + } + + if (postLocalMatrix) { + m.writable()->postConcat(*postLocalMatrix); + } + + return m; +} + +bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm, + const SkMatrix* outerLocalMatrix, + SkMatrix* totalInverse) const { + return SkMatrix::Concat(ctm, *this->totalLocalMatrix(outerLocalMatrix)).invert(totalInverse); +} + +bool SkShaderBase::asLuminanceColor(SkColor* colorPtr) const { + SkColor storage; + if (nullptr == colorPtr) { + colorPtr = &storage; + } + if (this->onAsLuminanceColor(colorPtr)) { + *colorPtr = SkColorSetA(*colorPtr, 0xFF); // we only return opaque + return true; + } + return false; +} + +SkShaderBase::Context* SkShaderBase::makeContext(const ContextRec& rec, SkArenaAlloc* alloc) const { +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + // We always fall back to raster pipeline when perspective is present. + if (rec.fMatrix->hasPerspective() || + fLocalMatrix.hasPerspective() || + (rec.fLocalMatrix && rec.fLocalMatrix->hasPerspective()) || + !this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) { + return nullptr; + } + + return this->onMakeContext(rec, alloc); +#else + return nullptr; +#endif +} + +SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec) + : fShader(shader), fCTM(*rec.fMatrix) +{ + // We should never use a context with perspective. + SkASSERT(!rec.fMatrix->hasPerspective()); + SkASSERT(!rec.fLocalMatrix || !rec.fLocalMatrix->hasPerspective()); + SkASSERT(!shader.getLocalMatrix().hasPerspective()); + + // Because the context parameters must be valid at this point, we know that the matrix is + // invertible. + SkAssertResult(fShader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &fTotalInverse)); + + fPaintAlpha = rec.fPaint->getAlpha(); +} + +SkShaderBase::Context::~Context() {} + +bool SkShaderBase::ContextRec::isLegacyCompatible(SkColorSpace* shaderColorSpace) const { + return !SkColorSpaceXformSteps::Required(shaderColorSpace, fDstColorSpace); +} + +SkImage* SkShader::isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const { + return as_SB(this)->onIsAImage(localMatrix, xy); +} + +SkShader::GradientType SkShader::asAGradient(GradientInfo* info) const { + return kNone_GradientType; +} + +#if SK_SUPPORT_GPU +std::unique_ptr SkShaderBase::asFragmentProcessor(const GrFPArgs&) const { + return nullptr; +} +#endif + +sk_sp SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const { + return nullptr; +} + +sk_sp SkShaders::Empty() { return sk_make_sp(); } +sk_sp SkShaders::Color(SkColor color) { return sk_make_sp(color); } + +sk_sp SkBitmap::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* lm) const { + if (lm && !lm->invert(nullptr)) { + return nullptr; + } + return SkMakeBitmapShader(*this, tmx, tmy, lm, kIfMutable_SkCopyPixelsMode); +} + +sk_sp SkBitmap::makeShader(const SkMatrix* lm) const { + return this->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, lm); +} + +bool SkShaderBase::appendStages(const SkStageRec& rec) const { + return this->onAppendStages(rec); +} + +bool SkShaderBase::onAppendStages(const SkStageRec& rec) const { + // SkShader::Context::shadeSpan() handles the paint opacity internally, + // but SkRasterPipelineBlitter applies it as a separate stage. + // We skip the internal shadeSpan() step by forcing the paint opaque. + SkTCopyOnFirstWrite opaquePaint(rec.fPaint); + if (rec.fPaint.getAlpha() != SK_AlphaOPAQUE) { + opaquePaint.writable()->setAlpha(SK_AlphaOPAQUE); + } + + ContextRec cr(*opaquePaint, rec.fCTM, rec.fLocalM, rec.fDstColorType, sk_srgb_singleton()); + + struct CallbackCtx : SkRasterPipeline_CallbackCtx { + sk_sp shader; + Context* ctx; + }; + auto cb = rec.fAlloc->make(); + cb->shader = sk_ref_sp(this); + cb->ctx = as_SB(this)->makeContext(cr, rec.fAlloc); + cb->fn = [](SkRasterPipeline_CallbackCtx* self, int active_pixels) { + auto c = (CallbackCtx*)self; + int x = (int)c->rgba[0], + y = (int)c->rgba[1]; + SkPMColor tmp[SkRasterPipeline_kMaxStride]; + c->ctx->shadeSpan(x,y, tmp, active_pixels); + + for (int i = 0; i < active_pixels; i++) { + auto rgba_4f = SkPMColor4f::FromPMColor(tmp[i]); + memcpy(c->rgba + 4*i, rgba_4f.vec(), 4*sizeof(float)); + } + }; + + if (cb->ctx) { + rec.fPipeline->append(SkRasterPipeline::seed_shader); + rec.fPipeline->append(SkRasterPipeline::callback, cb); + rec.fAlloc->make(sk_srgb_singleton(), kPremul_SkAlphaType, + rec.fDstCS, kPremul_SkAlphaType) + ->apply(rec.fPipeline, true); + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp SkEmptyShader::CreateProc(SkReadBuffer&) { + return SkShaders::Empty(); +} diff --git a/gfx/skia/skia/src/shaders/SkShaderBase.h b/gfx/skia/skia/src/shaders/SkShaderBase.h new file mode 100644 index 0000000000..e071e22db9 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkShaderBase.h @@ -0,0 +1,252 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkShaderBase_DEFINED +#define SkShaderBase_DEFINED + +#include "include/core/SkFilterQuality.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkShader.h" +#include "include/private/SkNoncopyable.h" +#include "src/core/SkEffectPriv.h" +#include "src/core/SkMask.h" +#include "src/core/SkTLazy.h" + +#if SK_SUPPORT_GPU +#include "src/gpu/GrFPArgs.h" +#endif + +class GrContext; +class GrFragmentProcessor; +class SkArenaAlloc; +class SkColorSpace; +class SkImage; +struct SkImageInfo; +class SkPaint; +class SkRasterPipeline; + +/** + * Shaders can optionally return a subclass of this when appending their stages. + * Doing so tells the caller that the stages can be reused with different CTMs (but nothing + * else can change), by calling the updater's udpate() method before each use. + * + * This can be a perf-win bulk draws like drawAtlas and drawVertices, where most of the setup + * (i.e. uniforms) are constant, and only something small is changing (i.e. matrices). This + * reuse skips the cost of computing the stages (and/or avoids having to allocate a separate + * shader for each small draw. + */ +class SkStageUpdater { +public: + virtual ~SkStageUpdater() {} + + virtual bool update(const SkMatrix& ctm, const SkMatrix* localM) = 0; +}; + +class SkShaderBase : public SkShader { +public: + ~SkShaderBase() override; + + /** + * Returns true if the shader is guaranteed to produce only a single color. + * Subclasses can override this to allow loop-hoisting optimization. + */ + virtual bool isConstant() const { return false; } + + const SkMatrix& getLocalMatrix() const { return fLocalMatrix; } + + enum Flags { + //!< set if all of the colors will be opaque + kOpaqueAlpha_Flag = 1 << 0, + + /** set if the spans only vary in X (const in Y). + e.g. an Nx1 bitmap that is being tiled in Y, or a linear-gradient + that varies from left-to-right. This flag specifies this for + shadeSpan(). + */ + kConstInY32_Flag = 1 << 1, + + /** hint for the blitter that 4f is the preferred shading mode. + */ + kPrefers4f_Flag = 1 << 2, + }; + + /** + * ContextRec acts as a parameter bundle for creating Contexts. + */ + struct ContextRec { + ContextRec(const SkPaint& paint, const SkMatrix& matrix, const SkMatrix* localM, + SkColorType dstColorType, SkColorSpace* dstColorSpace) + : fPaint(&paint) + , fMatrix(&matrix) + , fLocalMatrix(localM) + , fDstColorType(dstColorType) + , fDstColorSpace(dstColorSpace) {} + + const SkPaint* fPaint; // the current paint associated with the draw + const SkMatrix* fMatrix; // the current matrix in the canvas + const SkMatrix* fLocalMatrix; // optional local matrix + SkColorType fDstColorType; // the color type of the dest surface + SkColorSpace* fDstColorSpace; // the color space of the dest surface (if any) + + bool isLegacyCompatible(SkColorSpace* shadersColorSpace) const; + }; + + class Context : public ::SkNoncopyable { + public: + Context(const SkShaderBase& shader, const ContextRec&); + + virtual ~Context(); + + /** + * Called sometimes before drawing with this shader. Return the type of + * alpha your shader will return. The default implementation returns 0. + * Your subclass should override if it can (even sometimes) report a + * non-zero value, since that will enable various blitters to perform + * faster. + */ + virtual uint32_t getFlags() const { return 0; } + + /** + * Called for each span of the object being drawn. Your subclass should + * set the appropriate colors (with premultiplied alpha) that correspond + * to the specified device coordinates. + */ + virtual void shadeSpan(int x, int y, SkPMColor[], int count) = 0; + + protected: + // Reference to shader, so we don't have to dupe information. + const SkShaderBase& fShader; + + uint8_t getPaintAlpha() const { return fPaintAlpha; } + const SkMatrix& getTotalInverse() const { return fTotalInverse; } + const SkMatrix& getCTM() const { return fCTM; } + + private: + SkMatrix fCTM; + SkMatrix fTotalInverse; + uint8_t fPaintAlpha; + + typedef SkNoncopyable INHERITED; + }; + + /** + * Make a context using the memory provided by the arena. + * + * @return pointer to context or nullptr if can't be created + */ + Context* makeContext(const ContextRec&, SkArenaAlloc*) const; + +#if SK_SUPPORT_GPU + /** + * Returns a GrFragmentProcessor that implements the shader for the GPU backend. NULL is + * returned if there is no GPU implementation. + * + * The GPU device does not call SkShader::createContext(), instead we pass the view matrix, + * local matrix, and filter quality directly. + * + * The GrContext may be used by the to create textures that are required by the returned + * processor. + * + * The returned GrFragmentProcessor should expect an unpremultiplied input color and + * produce a premultiplied output. + */ + virtual std::unique_ptr asFragmentProcessor(const GrFPArgs&) const; +#endif + + /** + * If the shader can represent its "average" luminance in a single color, return true and + * if color is not NULL, return that color. If it cannot, return false and ignore the color + * parameter. + * + * Note: if this returns true, the returned color will always be opaque, as only the RGB + * components are used to compute luminance. + */ + bool asLuminanceColor(SkColor*) const; + + // If this returns false, then we draw nothing (do not fall back to shader context) + bool appendStages(const SkStageRec&) const; + + bool SK_WARN_UNUSED_RESULT computeTotalInverse(const SkMatrix& ctm, + const SkMatrix* outerLocalMatrix, + SkMatrix* totalInverse) const; + + // Returns the total local matrix for this shader: + // + // M = postLocalMatrix x shaderLocalMatrix x preLocalMatrix + // + SkTCopyOnFirstWrite totalLocalMatrix(const SkMatrix* preLocalMatrix, + const SkMatrix* postLocalMatrix = nullptr) const; + + virtual SkImage* onIsAImage(SkMatrix*, SkTileMode[2]) const { + return nullptr; + } + virtual SkPicture* isAPicture(SkMatrix*, SkTileMode[2], SkRect* tile) const { return nullptr; } + + static Type GetFlattenableType() { return kSkShaderBase_Type; } + Type getFlattenableType() const override { return GetFlattenableType(); } + + static sk_sp Deserialize(const void* data, size_t size, + const SkDeserialProcs* procs = nullptr) { + return sk_sp(static_cast( + SkFlattenable::Deserialize(GetFlattenableType(), data, size, procs).release())); + } + static void RegisterFlattenables(); + + /** DEPRECATED. skbug.com/8941 + * If this shader can be represented by another shader + a localMatrix, return that shader and + * the localMatrix. If not, return nullptr and ignore the localMatrix parameter. + */ + virtual sk_sp makeAsALocalMatrixShader(SkMatrix* localMatrix) const; + + SkStageUpdater* appendUpdatableStages(const SkStageRec& rec) const { + return this->onAppendUpdatableStages(rec); + } + +protected: + SkShaderBase(const SkMatrix* localMatrix = nullptr); + + void flatten(SkWriteBuffer&) const override; + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + /** + * Specialize creating a SkShader context using the supplied allocator. + * @return pointer to context owned by the arena allocator. + */ + virtual Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const { + return nullptr; + } +#endif + + virtual bool onAsLuminanceColor(SkColor*) const { + return false; + } + + // Default impl creates shadercontext and calls that (not very efficient) + virtual bool onAppendStages(const SkStageRec&) const; + + virtual SkStageUpdater* onAppendUpdatableStages(const SkStageRec&) const { return nullptr; } + +private: + // This is essentially const, but not officially so it can be modified in constructors. + SkMatrix fLocalMatrix; + + typedef SkShader INHERITED; +}; + +inline SkShaderBase* as_SB(SkShader* shader) { + return static_cast(shader); +} + +inline const SkShaderBase* as_SB(const SkShader* shader) { + return static_cast(shader); +} + +inline const SkShaderBase* as_SB(const sk_sp& shader) { + return static_cast(shader.get()); +} + +#endif // SkShaderBase_DEFINED diff --git a/gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.cpp b/gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.cpp new file mode 100644 index 0000000000..1a269e6e28 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.cpp @@ -0,0 +1,313 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "src/shaders/gradients/Sk4fGradientBase.h" +#include + +namespace { + +Sk4f pack_color(const SkColor4f& c4f, bool premul, const Sk4f& component_scale) { + Sk4f pm4f = premul + ? Sk4f::Load(c4f.premul().vec()) + : Sk4f::Load(c4f.vec()); + + if (premul) { + // If the stops are premul, we clamp them to gamut now. + // If the stops are unpremul, the colors will eventually go through Sk4f_toL32(), + // which ends up clamping to gamut then. + pm4f = Sk4f::Max(0, Sk4f::Min(pm4f, pm4f[3])); + } + + return pm4f * component_scale; +} + +class IntervalIterator { +public: + IntervalIterator(const SkGradientShaderBase& shader, bool reverse) + : fShader(shader) + , fFirstPos(reverse ? SK_Scalar1 : 0) + , fBegin(reverse ? shader.fColorCount - 1 : 0) + , fAdvance(reverse ? -1 : 1) { + SkASSERT(shader.fColorCount > 0); + } + + void iterate(const SkColor4f* colors, + std::function func) const { + if (!fShader.fOrigPos) { + this->iterateImplicitPos(colors, func); + return; + } + + const int end = fBegin + fAdvance * (fShader.fColorCount - 1); + int prev = fBegin; + SkScalar prevPos = fFirstPos; + + do { + const int curr = prev + fAdvance; + SkASSERT(curr >= 0 && curr < fShader.fColorCount); + + const SkScalar currPos = fShader.fOrigPos[curr]; + if (currPos != prevPos) { + SkASSERT((currPos - prevPos > 0) == (fAdvance > 0)); + func(colors[prev], colors[curr], prevPos, currPos); + } + + prev = curr; + prevPos = currPos; + } while (prev != end); + } + +private: + void iterateImplicitPos(const SkColor4f* colors, + std::function func) const { + // When clients don't provide explicit color stop positions (fPos == nullptr), + // the color stops are distributed evenly across the unit interval + // (implicit positioning). + const SkScalar dt = fAdvance * SK_Scalar1 / (fShader.fColorCount - 1); + const int end = fBegin + fAdvance * (fShader.fColorCount - 2); + int prev = fBegin; + SkScalar prevPos = fFirstPos; + + while (prev != end) { + const int curr = prev + fAdvance; + SkASSERT(curr >= 0 && curr < fShader.fColorCount); + + const SkScalar currPos = prevPos + dt; + func(colors[prev], colors[curr], prevPos, currPos); + prev = curr; + prevPos = currPos; + } + + // emit the last interval with a pinned end position, to avoid precision issues + func(colors[prev], colors[prev + fAdvance], prevPos, 1 - fFirstPos); + } + + const SkGradientShaderBase& fShader; + const SkScalar fFirstPos; + const int fBegin; + const int fAdvance; +}; + +void addMirrorIntervals(const SkGradientShaderBase& shader, + const SkColor4f* colors, + const Sk4f& componentScale, + bool premulColors, bool reverse, + Sk4fGradientIntervalBuffer::BufferType* buffer) { + const IntervalIterator iter(shader, reverse); + iter.iterate(colors, [&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) { + SkASSERT(buffer->empty() || buffer->back().fT1 == 2 - t0); + + const auto mirror_t0 = 2 - t0; + const auto mirror_t1 = 2 - t1; + // mirror_p1 & mirror_p1 may collapse for very small values - recheck to avoid + // triggering Interval asserts. + if (mirror_t0 != mirror_t1) { + buffer->emplace_back(pack_color(c0, premulColors, componentScale), mirror_t0, + pack_color(c1, premulColors, componentScale), mirror_t1); + } + }); +} + +} // anonymous namespace + +Sk4fGradientInterval::Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, + const Sk4f& c1, SkScalar t1) + : fT0(t0) + , fT1(t1) { + SkASSERT(t0 != t1); + // Either p0 or p1 can be (-)inf for synthetic clamp edge intervals. + SkASSERT(SkScalarIsFinite(t0) || SkScalarIsFinite(t1)); + + const auto dt = t1 - t0; + + // Clamp edge intervals are always zero-ramp. + SkASSERT(SkScalarIsFinite(dt) || (c0 == c1).allTrue()); + SkASSERT(SkScalarIsFinite(t0) || (c0 == c1).allTrue()); + const Sk4f dc = SkScalarIsFinite(dt) ? (c1 - c0) / dt : 0; + const Sk4f bias = c0 - (SkScalarIsFinite(t0) ? t0 * dc : 0); + + bias.store(fCb.vec()); + dc.store(fCg.vec()); +} + +void Sk4fGradientIntervalBuffer::init(const SkGradientShaderBase& shader, SkColorSpace* dstCS, + SkTileMode tileMode, bool premulColors, + SkScalar alpha, bool reverse) { + // The main job here is to build a specialized interval list: a different + // representation of the color stops data, optimized for efficient scan line + // access during shading. + // + // [{P0,C0} , {P1,C1}) [{P1,C2} , {P2,c3}) ... [{Pn,C2n} , {Pn+1,C2n+1}) + // + // The list may be inverted when requested (such that e.g. points are sorted + // in increasing x order when dx < 0). + // + // Note: the current representation duplicates pos data; we could refactor to + // avoid this if interval storage size becomes a concern. + // + // Aside from reordering, we also perform two more pre-processing steps at + // this stage: + // + // 1) scale the color components depending on paint alpha and the requested + // interpolation space (note: the interval color storage is SkPMColor4f, but + // that doesn't necessarily mean the colors are premultiplied; that + // property is tracked in fColorsArePremul) + // + // 2) inject synthetic intervals to support tiling. + // + // * for kRepeat, no extra intervals are needed - the iterator just + // wraps around at the end: + // + // ->[P0,P1)->..[Pn-1,Pn)-> + // + // * for kClamp, we add two "infinite" intervals before/after: + // + // [-/+inf , P0)->[P0 , P1)->..[Pn-1 , Pn)->[Pn , +/-inf) + // + // (the iterator should never run off the end in this mode) + // + // * for kMirror, we extend the range to [0..2] and add a flipped + // interval series - then the iterator operates just as in the + // kRepeat case: + // + // ->[P0,P1)->..[Pn-1,Pn)->[2 - Pn,2 - Pn-1)->..[2 - P1,2 - P0)-> + // + // TODO: investigate collapsing intervals << 1px. + + const auto count = shader.fColorCount; + + SkASSERT(count > 0); + + fIntervals.reset(); + + const Sk4f componentScale = premulColors + ? Sk4f(alpha) + : Sk4f(1.0f, 1.0f, 1.0f, alpha); + const int first_index = reverse ? count - 1 : 0; + const int last_index = count - 1 - first_index; + const SkScalar first_pos = reverse ? SK_Scalar1 : 0; + const SkScalar last_pos = SK_Scalar1 - first_pos; + + // Transform all of the colors to destination color space + SkColor4fXformer xformedColors(shader.fOrigColors4f, count, shader.fColorSpace.get(), dstCS); + + if (tileMode == SkTileMode::kClamp) { + // synthetic edge interval: -/+inf .. P0 + const Sk4f clamp_color = pack_color(xformedColors.fColors[first_index], + premulColors, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity; + fIntervals.emplace_back(clamp_color, clamp_pos, + clamp_color, first_pos); + } else if (tileMode == SkTileMode::kMirror && reverse) { + // synthetic mirror intervals injected before main intervals: (2 .. 1] + addMirrorIntervals(shader, xformedColors.fColors, componentScale, premulColors, false, + &fIntervals); + } + + const IntervalIterator iter(shader, reverse); + iter.iterate(xformedColors.fColors, + [&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) { + SkASSERT(fIntervals.empty() || fIntervals.back().fT1 == t0); + + fIntervals.emplace_back(pack_color(c0, premulColors, componentScale), t0, + pack_color(c1, premulColors, componentScale), t1); + }); + + if (tileMode == SkTileMode::kClamp) { + // synthetic edge interval: Pn .. +/-inf + const Sk4f clamp_color = pack_color(xformedColors.fColors[last_index], + premulColors, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity; + fIntervals.emplace_back(clamp_color, last_pos, + clamp_color, clamp_pos); + } else if (tileMode == SkTileMode::kMirror && !reverse) { + // synthetic mirror intervals injected after main intervals: [1 .. 2) + addMirrorIntervals(shader, xformedColors.fColors, componentScale, premulColors, true, + &fIntervals); + } +} + +const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::find(SkScalar t) const { + // Binary search. + const auto* i0 = fIntervals.begin(); + const auto* i1 = fIntervals.end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(t >= i0->fT0 && t <= i1->fT1); + + const auto* i = i0 + ((i1 - i0) >> 1); + + if (t > i->fT1) { + i0 = i + 1; + } else { + i1 = i; + } + } + + SkASSERT(i0->contains(t)); + return i0; +} + +const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::findNext( + SkScalar t, const Sk4fGradientInterval* prev, bool increasing) const { + + SkASSERT(!prev->contains(t)); + SkASSERT(prev >= fIntervals.begin() && prev < fIntervals.end()); + SkASSERT(t >= fIntervals.front().fT0 && t <= fIntervals.back().fT1); + + const auto* i = prev; + + // Use the |increasing| signal to figure which direction we should search for + // the next interval, then perform a linear search. + if (increasing) { + do { + i += 1; + if (i >= fIntervals.end()) { + i = fIntervals.begin(); + } + } while (!i->contains(t)); + } else { + do { + i -= 1; + if (i < fIntervals.begin()) { + i = fIntervals.end() - 1; + } + } while (!i->contains(t)); + } + + return i; +} + +SkGradientShaderBase:: +GradientShaderBase4fContext::GradientShaderBase4fContext(const SkGradientShaderBase& shader, + const ContextRec& rec) + : INHERITED(shader, rec) + , fFlags(this->INHERITED::getFlags()) + , fDither(rec.fPaint->isDither()) +{ + const SkMatrix& inverse = this->getTotalInverse(); + fDstToPos.setConcat(shader.fPtsToUnit, inverse); + SkASSERT(!fDstToPos.hasPerspective()); + fDstToPosProc = SkMatrixPriv::GetMapXYProc(fDstToPos); + + if (shader.fColorsAreOpaque && this->getPaintAlpha() == SK_AlphaOPAQUE) { + fFlags |= kOpaqueAlpha_Flag; + } + + fColorsArePremul = + (shader.fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) + || shader.fColorsAreOpaque; +} + +bool SkGradientShaderBase:: +GradientShaderBase4fContext::isValid() const { + return fDstToPos.isFinite(); +} diff --git a/gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.h b/gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.h new file mode 100644 index 0000000000..fcc8fae05a --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/Sk4fGradientBase.h @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientBase_DEFINED +#define Sk4fGradientBase_DEFINED + +#include "include/core/SkColor.h" +#include "include/private/SkNx.h" +#include "include/private/SkTArray.h" +#include "src/core/SkMatrixPriv.h" +#include "src/shaders/SkShaderBase.h" +#include "src/shaders/gradients/Sk4fGradientPriv.h" +#include "src/shaders/gradients/SkGradientShaderPriv.h" + +struct Sk4fGradientInterval { + Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, + const Sk4f& c1, SkScalar t1); + + bool contains(SkScalar t) const { + // True if t is in [p0,p1]. Note: this helper assumes a + // natural/increasing interval - so it's not usable in Sk4fLinearGradient. + SkASSERT(fT0 < fT1); + return t >= fT0 && t <= fT1; + } + + // Color bias and color gradient, such that for a t in this interval + // + // C = fCb + t * fCg; + SkPMColor4f fCb, fCg; + SkScalar fT0, fT1; +}; + +class Sk4fGradientIntervalBuffer { +public: + void init(const SkGradientShaderBase&, SkColorSpace* dstCS, SkTileMode tileMode, + bool premulColors, SkScalar alpha, bool reverse); + + const Sk4fGradientInterval* find(SkScalar t) const; + const Sk4fGradientInterval* findNext(SkScalar t, const Sk4fGradientInterval* prev, + bool increasing) const; + + using BufferType = SkSTArray<8, Sk4fGradientInterval, true>; + + const BufferType* operator->() const { return &fIntervals; } + +private: + BufferType fIntervals; +}; + +class SkGradientShaderBase:: +GradientShaderBase4fContext : public Context { +public: + GradientShaderBase4fContext(const SkGradientShaderBase&, + const ContextRec&); + + uint32_t getFlags() const override { return fFlags; } + + bool isValid() const; + +protected: + Sk4fGradientIntervalBuffer fIntervals; + SkMatrix fDstToPos; + SkMatrixPriv::MapXYProc fDstToPosProc; + uint8_t fFlags; + bool fColorsArePremul; + bool fDither; + +private: + using INHERITED = Context; + + void addMirrorIntervals(const SkGradientShaderBase&, + const Sk4f& componentScale, bool reverse); +}; + +#endif // Sk4fGradientBase_DEFINED diff --git a/gfx/skia/skia/src/shaders/gradients/Sk4fGradientPriv.h b/gfx/skia/skia/src/shaders/gradients/Sk4fGradientPriv.h new file mode 100644 index 0000000000..15731ec31f --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/Sk4fGradientPriv.h @@ -0,0 +1,105 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientPriv_DEFINED +#define Sk4fGradientPriv_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkImageInfo.h" +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkNx.h" +#include "src/core/SkUtils.h" + +// Templates shared by various 4f gradient flavors. + +namespace { // NOLINT(google-build-namespaces) + +enum class ApplyPremul { True, False }; + +template +struct PremulTraits; + +template <> +struct PremulTraits { + static Sk4f apply(const Sk4f& c) { return c; } +}; + +template <> +struct PremulTraits { + static Sk4f apply(const Sk4f& c) { + const float alpha = c[3]; + // FIXME: portable swizzle? + return c * Sk4f(alpha, alpha, alpha, 1); + } +}; + +// Struct encapsulating various dest-dependent ops: +// +// - load() Load a SkPMColor4f value into Sk4f. Normally called once per interval +// advance. Also applies a scale and swizzle suitable for DstType. +// +// - store() Store one Sk4f to dest. Optionally handles premul, color space +// conversion, etc. +// +// - store(count) Store the Sk4f value repeatedly to dest, count times. +// +// - store4x() Store 4 Sk4f values to dest (opportunistic optimization). +// + +template +struct DstTraits { + using PM = PremulTraits; + + // For L32, prescaling by 255 saves a per-pixel multiplication when premul is not needed. + static Sk4f load(const SkPMColor4f& c) { + Sk4f c4f = swizzle_rb_if_bgra(Sk4f::Load(c.vec())); + return premul == ApplyPremul::False + ? c4f * Sk4f(255) + : c4f; + } + + static void store(const Sk4f& c, SkPMColor* dst, const Sk4f& bias) { + if (premul == ApplyPremul::False) { + // c is pre-scaled by 255 and pre-biased, just store. + SkNx_cast(c).store(dst); + } else { + *dst = Sk4f_toL32(PM::apply(c) + bias); + } + } + + static void store(const Sk4f& c, SkPMColor* dst, int n) { + SkPMColor pmc; + store(c, &pmc, Sk4f(0)); + sk_memset32(dst, pmc, n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + SkPMColor* dst, + const Sk4f& bias0, + const Sk4f& bias1) { + if (premul == ApplyPremul::False) { + // colors are pre-scaled and pre-biased. + Sk4f_ToBytes((uint8_t*)dst, c0, c1, c2, c3); + } else { + store(c0, dst + 0, bias0); + store(c1, dst + 1, bias1); + store(c2, dst + 2, bias0); + store(c3, dst + 3, bias1); + } + } + + static Sk4f pre_lerp_bias(const Sk4f& bias) { + // We can apply the bias before interpolation when the colors are premultiplied. + return premul == ApplyPremul::False ? bias : 0; + } +}; + +} // anonymous namespace + +#endif // Sk4fGradientPriv_DEFINED diff --git a/gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.cpp b/gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.cpp new file mode 100644 index 0000000000..b4266ced9d --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.cpp @@ -0,0 +1,388 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPaint.h" +#include "src/shaders/gradients/Sk4fLinearGradient.h" + +#include +#include + +namespace { + +template +void ramp(const Sk4f& c, const Sk4f& dc, SkPMColor dst[], int n, + const Sk4f& bias0, const Sk4f& bias1) { + SkASSERT(n > 0); + + const Sk4f dc2 = dc + dc, + dc4 = dc2 + dc2; + + Sk4f c0 = c + DstTraits::pre_lerp_bias(bias0), + c1 = c + dc + DstTraits::pre_lerp_bias(bias1), + c2 = c0 + dc2, + c3 = c1 + dc2; + + while (n >= 4) { + DstTraits::store4x(c0, c1, c2, c3, dst, bias0, bias1); + dst += 4; + + c0 = c0 + dc4; + c1 = c1 + dc4; + c2 = c2 + dc4; + c3 = c3 + dc4; + n -= 4; + } + if (n & 2) { + DstTraits::store(c0, dst++, bias0); + DstTraits::store(c1, dst++, bias1); + c0 = c0 + dc2; + } + if (n & 1) { + DstTraits::store(c0, dst, bias0); + } +} + +template +SkScalar pinFx(SkScalar); + +template<> +SkScalar pinFx(SkScalar fx) { + return fx; +} + +template<> +SkScalar pinFx(SkScalar fx) { + SkScalar f = SkScalarIsFinite(fx) ? SkScalarFraction(fx) : 0; + if (f < 0) { + f = SkTMin(f + 1, nextafterf(1, 0)); + } + SkASSERT(f >= 0); + SkASSERT(f < 1.0f); + return f; +} + +template<> +SkScalar pinFx(SkScalar fx) { + SkScalar f = SkScalarIsFinite(fx) ? SkScalarMod(fx, 2.0f) : 0; + if (f < 0) { + f = SkTMin(f + 2, nextafterf(2, 0)); + } + SkASSERT(f >= 0); + SkASSERT(f < 2.0f); + return f; +} + +// true when x is in [k1,k2], or [k2, k1] when the interval is reversed. +// TODO(fmalita): hoist the reversed interval check out of this helper. +bool in_range(SkScalar x, SkScalar k1, SkScalar k2) { + SkASSERT(k1 != k2); + return (k1 < k2) + ? (x >= k1 && x <= k2) + : (x >= k2 && x <= k1); +} + +} // anonymous namespace + +SkLinearGradient:: +LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader, + const ContextRec& rec) + : INHERITED(shader, rec) { + + // Our fast path expects interval points to be monotonically increasing in x. + const bool reverseIntervals = std::signbit(fDstToPos.getScaleX()); + fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode, + fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals); + + SkASSERT(fIntervals->count() > 0); + fCachedInterval = fIntervals->begin(); +} + +const Sk4fGradientInterval* +SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const { + SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1)); + + if (1) { + // Linear search, using the last scanline interval as a starting point. + SkASSERT(fCachedInterval >= fIntervals->begin()); + SkASSERT(fCachedInterval < fIntervals->end()); + const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1; + while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) { + fCachedInterval += search_dir; + if (fCachedInterval >= fIntervals->end()) { + fCachedInterval = fIntervals->begin(); + } else if (fCachedInterval < fIntervals->begin()) { + fCachedInterval = fIntervals->end() - 1; + } + } + return fCachedInterval; + } else { + // Binary search. Seems less effective than linear + caching. + const auto* i0 = fIntervals->begin(); + const auto* i1 = fIntervals->end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(in_range(fx, i0->fT0, i1->fT1)); + + const auto* i = i0 + ((i1 - i0) >> 1); + + if (in_range(fx, i0->fT0, i->fT1)) { + i1 = i; + } else { + SkASSERT(in_range(fx, i->fT1, i1->fT1)); + i0 = i + 1; + } + } + + SkASSERT(in_range(fx, i0->fT0, i0->fT1)); + return i0; + } +} + + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { + SkASSERT(count > 0); + + float bias0 = 0, + bias1 = 0; + + if (fDither) { + static constexpr float dither_cell[] = { + -3/8.0f, 1/8.0f, + 3/8.0f, -1/8.0f, + }; + + const int rowIndex = (y & 1) << 1; + bias0 = dither_cell[rowIndex + 0]; + bias1 = dither_cell[rowIndex + 1]; + + if (x & 1) { + using std::swap; + swap(bias0, bias1); + } + } + + if (fColorsArePremul) { + // In premul interpolation mode, components are pre-scaled by 255 and the store + // op is truncating. We pre-bias here to achieve rounding. + bias0 += 0.5f; + bias1 += 0.5f; + + this->shadePremulSpan(x, y, dst, count, bias0, bias1); + } else { + // In unpremul interpolation mode, Components are not pre-scaled. + bias0 *= 1/255.0f; + bias1 *= 1/255.0f; + + this->shadePremulSpan(x, y, dst, count, bias0, bias1); + } +} + +template +void SkLinearGradient:: +LinearGradient4fContext::shadePremulSpan(int x, int y, SkPMColor dst[], int count, + float bias0, float bias1) const { + const SkLinearGradient& shader = static_cast(fShader); + switch (shader.fTileMode) { + case SkTileMode::kDecal: + SkASSERT(false); // decal only supported via stages + // fall-through + case SkTileMode::kClamp: + this->shadeSpanInternal(x, y, dst, count, bias0, bias1); + break; + case SkTileMode::kRepeat: + this->shadeSpanInternal(x, y, dst, count, bias0, bias1); + break; + case SkTileMode::kMirror: + this->shadeSpanInternal(x, y, dst, count, bias0, bias1); + break; + } +} + +template +void SkLinearGradient:: +LinearGradient4fContext::shadeSpanInternal(int x, int y, SkPMColor dst[], int count, + float bias0, float bias1) const { + SkPoint pt; + fDstToPosProc(fDstToPos, + x + SK_ScalarHalf, + y + SK_ScalarHalf, + &pt); + const SkScalar fx = pinFx(pt.x()); + const SkScalar dx = fDstToPos.getScaleX(); + LinearIntervalProcessor proc(fIntervals->begin(), + fIntervals->end() - 1, + this->findInterval(fx), + fx, + dx, + SkScalarNearlyZero(dx * count)); + Sk4f bias4f0(bias0), + bias4f1(bias1); + + while (count > 0) { + // What we really want here is SkTPin(advance, 1, count) + // but that's a significant perf hit for >> stops; investigate. + const int n = SkTMin(SkScalarTruncToInt(proc.currentAdvance() + 1), count); + + // The current interval advance can be +inf (e.g. when reaching + // the clamp mode end intervals) - when that happens, we expect to + // a) consume all remaining count in one swoop + // b) return a zero color gradient + SkASSERT(SkScalarIsFinite(proc.currentAdvance()) + || (n == count && proc.currentRampIsZero())); + + if (proc.currentRampIsZero()) { + DstTraits::store(proc.currentColor(), dst, n); + } else { + ramp(proc.currentColor(), proc.currentColorGrad(), dst, n, + bias4f0, bias4f1); + } + + proc.advance(SkIntToScalar(n)); + count -= n; + dst += n; + + if (n & 1) { + using std::swap; + swap(bias4f0, bias4f1); + } + } +} + +template +class SkLinearGradient:: +LinearGradient4fContext::LinearIntervalProcessor { +public: + LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval, + const Sk4fGradientInterval* lastInterval, + const Sk4fGradientInterval* i, + SkScalar fx, + SkScalar dx, + bool is_vertical) + : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx) + , fFirstInterval(firstInterval) + , fLastInterval(lastInterval) + , fInterval(i) + , fDx(dx) + , fIsVertical(is_vertical) + { + SkASSERT(fAdvX >= 0); + SkASSERT(firstInterval <= lastInterval); + + if (tileMode != SkTileMode::kClamp && !is_vertical) { + const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx; + SkASSERT(spanX >= 0); + + // If we're in a repeating tile mode and the whole gradient is compressed into a + // fraction of a pixel, we just use the average color in zero-ramp mode. + // This also avoids cases where we make no progress due to interval advances being + // close to zero. + static constexpr SkScalar kMinSpanX = .25f; + if (spanX < kMinSpanX) { + this->init_average_props(); + return; + } + } + + this->compute_interval_props(fx); + } + + SkScalar currentAdvance() const { + SkASSERT(fAdvX >= 0); + SkASSERT(!std::isfinite(fAdvX) || fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx); + return fAdvX; + } + + bool currentRampIsZero() const { return fZeroRamp; } + const Sk4f& currentColor() const { return fCc; } + const Sk4f& currentColorGrad() const { return fDcDx; } + + void advance(SkScalar advX) { + SkASSERT(advX > 0); + SkASSERT(fAdvX >= 0); + + if (advX >= fAdvX) { + advX = this->advance_interval(advX); + } + SkASSERT(advX < fAdvX); + + fCc = fCc + fDcDx * Sk4f(advX); + fAdvX -= advX; + } + +private: + void compute_interval_props(SkScalar t) { + SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1)); + + const Sk4f dc = DstTraits::load(fInterval->fCg); + fCc = DstTraits::load(fInterval->fCb) + dc * Sk4f(t); + fDcDx = dc * fDx; + fZeroRamp = fIsVertical || (dc == 0).allTrue(); + } + + void init_average_props() { + fAdvX = SK_ScalarInfinity; + fZeroRamp = true; + fDcDx = 0; + fCc = Sk4f(0); + + // TODO: precompute the average at interval setup time? + for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) { + // Each interval contributes its average color to the total/weighted average: + // + // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2 + // + // Avg += C * (t1 - t0) + // + const auto c = DstTraits::load(i->fCb) + + DstTraits::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f; + fCc = fCc + c * (i->fT1 - i->fT0); + } + } + + const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const { + SkASSERT(i >= fFirstInterval); + SkASSERT(i <= fLastInterval); + i++; + + if (tileMode == SkTileMode::kClamp) { + SkASSERT(i <= fLastInterval); + return i; + } + + return (i <= fLastInterval) ? i : fFirstInterval; + } + + SkScalar advance_interval(SkScalar advX) { + SkASSERT(advX >= fAdvX); + + do { + advX -= fAdvX; + fInterval = this->next_interval(fInterval); + fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx; + SkASSERT(fAdvX > 0); + } while (advX >= fAdvX); + + compute_interval_props(fInterval->fT0); + + SkASSERT(advX >= 0); + return advX; + } + + // Current interval properties. + Sk4f fDcDx; // dst color gradient (dc/dx) + Sk4f fCc; // current color, interpolated in dst + SkScalar fAdvX; // remaining interval advance in dst + bool fZeroRamp; // current interval color grad is 0 + + const Sk4fGradientInterval* fFirstInterval; + const Sk4fGradientInterval* fLastInterval; + const Sk4fGradientInterval* fInterval; // current interval + const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx + const bool fIsVertical; +}; diff --git a/gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.h b/gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.h new file mode 100644 index 0000000000..a7885895ee --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/Sk4fLinearGradient.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fLinearGradient_DEFINED +#define Sk4fLinearGradient_DEFINED + +#include "src/shaders/gradients/Sk4fGradientBase.h" +#include "src/shaders/gradients/SkLinearGradient.h" + +class SkLinearGradient:: +LinearGradient4fContext final : public GradientShaderBase4fContext { +public: + LinearGradient4fContext(const SkLinearGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dst[], int count) override; + +private: + using INHERITED = GradientShaderBase4fContext; + + template + class LinearIntervalProcessor; + + template + void shadePremulSpan(int x, int y, SkPMColor dst[], int count, + float bias0, float bias1) const; + + template + void shadeSpanInternal(int x, int y, SkPMColor dst[], int count, + float bias0, float bias1) const; + + const Sk4fGradientInterval* findInterval(SkScalar fx) const; + + mutable const Sk4fGradientInterval* fCachedInterval; +}; + +#endif // Sk4fLinearGradient_DEFINED diff --git a/gfx/skia/skia/src/shaders/gradients/SkGradientShader.cpp b/gfx/skia/skia/src/shaders/gradients/SkGradientShader.cpp new file mode 100644 index 0000000000..641cbce2c3 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkGradientShader.cpp @@ -0,0 +1,886 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include "include/core/SkMallocPixelRef.h" +#include "include/private/SkFloatBits.h" +#include "include/private/SkHalf.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkConvertPixels.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/gradients/Sk4fLinearGradient.h" +#include "src/shaders/gradients/SkGradientShaderPriv.h" +#include "src/shaders/gradients/SkLinearGradient.h" +#include "src/shaders/gradients/SkRadialGradient.h" +#include "src/shaders/gradients/SkSweepGradient.h" +#include "src/shaders/gradients/SkTwoPointConicalGradient.h" + +enum GradientSerializationFlags { + // Bits 29:31 used for various boolean flags + kHasPosition_GSF = 0x80000000, + kHasLocalMatrix_GSF = 0x40000000, + kHasColorSpace_GSF = 0x20000000, + + // Bits 12:28 unused + + // Bits 8:11 for fTileMode + kTileModeShift_GSF = 8, + kTileModeMask_GSF = 0xF, + + // Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80) + kGradFlagsShift_GSF = 0, + kGradFlagsMask_GSF = 0xFF, +}; + +void SkGradientShaderBase::Descriptor::flatten(SkWriteBuffer& buffer) const { + uint32_t flags = 0; + if (fPos) { + flags |= kHasPosition_GSF; + } + if (fLocalMatrix) { + flags |= kHasLocalMatrix_GSF; + } + sk_sp colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr; + if (colorSpaceData) { + flags |= kHasColorSpace_GSF; + } + SkASSERT(static_cast(fTileMode) <= kTileModeMask_GSF); + flags |= ((unsigned)fTileMode << kTileModeShift_GSF); + SkASSERT(fGradFlags <= kGradFlagsMask_GSF); + flags |= (fGradFlags << kGradFlagsShift_GSF); + + buffer.writeUInt(flags); + + buffer.writeColor4fArray(fColors, fCount); + if (colorSpaceData) { + buffer.writeDataAsByteArray(colorSpaceData.get()); + } + if (fPos) { + buffer.writeScalarArray(fPos, fCount); + } + if (fLocalMatrix) { + buffer.writeMatrix(*fLocalMatrix); + } +} + +template +static bool validate_array(SkReadBuffer& buffer, size_t count, SkSTArray* array) { + if (!buffer.validateCanReadN(count)) { + return false; + } + + array->resize_back(count); + return true; +} + +bool SkGradientShaderBase::DescriptorScope::unflatten(SkReadBuffer& buffer) { + // New gradient format. Includes floating point color, color space, densely packed flags + uint32_t flags = buffer.readUInt(); + + fTileMode = (SkTileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF); + fGradFlags = (flags >> kGradFlagsShift_GSF) & kGradFlagsMask_GSF; + + fCount = buffer.getArrayCount(); + + if (!(validate_array(buffer, fCount, &fColorStorage) && + buffer.readColor4fArray(fColorStorage.begin(), fCount))) { + return false; + } + fColors = fColorStorage.begin(); + + if (SkToBool(flags & kHasColorSpace_GSF)) { + sk_sp data = buffer.readByteArrayAsData(); + fColorSpace = data ? SkColorSpace::Deserialize(data->data(), data->size()) : nullptr; + } else { + fColorSpace = nullptr; + } + if (SkToBool(flags & kHasPosition_GSF)) { + if (!(validate_array(buffer, fCount, &fPosStorage) && + buffer.readScalarArray(fPosStorage.begin(), fCount))) { + return false; + } + fPos = fPosStorage.begin(); + } else { + fPos = nullptr; + } + if (SkToBool(flags & kHasLocalMatrix_GSF)) { + fLocalMatrix = &fLocalMatrixStorage; + buffer.readMatrix(&fLocalMatrixStorage); + } else { + fLocalMatrix = nullptr; + } + return buffer.isValid(); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit) + : INHERITED(desc.fLocalMatrix) + , fPtsToUnit(ptsToUnit) + , fColorSpace(desc.fColorSpace ? desc.fColorSpace : SkColorSpace::MakeSRGB()) + , fColorsAreOpaque(true) +{ + fPtsToUnit.getType(); // Precache so reads are threadsafe. + SkASSERT(desc.fCount > 1); + + fGradFlags = static_cast(desc.fGradFlags); + + SkASSERT((unsigned)desc.fTileMode < kSkTileModeCount); + fTileMode = desc.fTileMode; + + /* Note: we let the caller skip the first and/or last position. + i.e. pos[0] = 0.3, pos[1] = 0.7 + In these cases, we insert dummy entries to ensure that the final data + will be bracketed by [0, 1]. + i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1 + + Thus colorCount (the caller's value, and fColorCount (our value) may + differ by up to 2. In the above example: + colorCount = 2 + fColorCount = 4 + */ + fColorCount = desc.fCount; + // check if we need to add in dummy start and/or end position/colors + bool dummyFirst = false; + bool dummyLast = false; + if (desc.fPos) { + dummyFirst = desc.fPos[0] != 0; + dummyLast = desc.fPos[desc.fCount - 1] != SK_Scalar1; + fColorCount += dummyFirst + dummyLast; + } + + size_t storageSize = fColorCount * (sizeof(SkColor4f) + (desc.fPos ? sizeof(SkScalar) : 0)); + fOrigColors4f = reinterpret_cast(fStorage.reset(storageSize)); + fOrigPos = desc.fPos ? reinterpret_cast(fOrigColors4f + fColorCount) + : nullptr; + + // Now copy over the colors, adding the dummies as needed + SkColor4f* origColors = fOrigColors4f; + if (dummyFirst) { + *origColors++ = desc.fColors[0]; + } + for (int i = 0; i < desc.fCount; ++i) { + origColors[i] = desc.fColors[i]; + fColorsAreOpaque = fColorsAreOpaque && (desc.fColors[i].fA == 1); + } + if (dummyLast) { + origColors += desc.fCount; + *origColors = desc.fColors[desc.fCount - 1]; + } + + if (desc.fPos) { + SkScalar prev = 0; + SkScalar* origPosPtr = fOrigPos; + *origPosPtr++ = prev; // force the first pos to 0 + + int startIndex = dummyFirst ? 0 : 1; + int count = desc.fCount + dummyLast; + + bool uniformStops = true; + const SkScalar uniformStep = desc.fPos[startIndex] - prev; + for (int i = startIndex; i < count; i++) { + // Pin the last value to 1.0, and make sure pos is monotonic. + auto curr = (i == desc.fCount) ? 1 : SkScalarPin(desc.fPos[i], prev, 1); + uniformStops &= SkScalarNearlyEqual(uniformStep, curr - prev); + + *origPosPtr++ = prev = curr; + } + + // If the stops are uniform, treat them as implicit. + if (uniformStops) { + fOrigPos = nullptr; + } + } +} + +SkGradientShaderBase::~SkGradientShaderBase() {} + +void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const { + Descriptor desc; + desc.fColors = fOrigColors4f; + desc.fColorSpace = fColorSpace; + desc.fPos = fOrigPos; + desc.fCount = fColorCount; + desc.fTileMode = fTileMode; + desc.fGradFlags = fGradFlags; + + const SkMatrix& m = this->getLocalMatrix(); + desc.fLocalMatrix = m.isIdentity() ? nullptr : &m; + desc.flatten(buffer); +} + +static void add_stop_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f Fs, SkPMColor4f Bs) { + (ctx->fs[0])[stop] = Fs.fR; + (ctx->fs[1])[stop] = Fs.fG; + (ctx->fs[2])[stop] = Fs.fB; + (ctx->fs[3])[stop] = Fs.fA; + (ctx->bs[0])[stop] = Bs.fR; + (ctx->bs[1])[stop] = Bs.fG; + (ctx->bs[2])[stop] = Bs.fB; + (ctx->bs[3])[stop] = Bs.fA; +} + +static void add_const_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f color) { + add_stop_color(ctx, stop, { 0, 0, 0, 0 }, color); +} + +// Calculate a factor F and a bias B so that color = F*t + B when t is in range of +// the stop. Assume that the distance between stops is 1/gapCount. +static void init_stop_evenly( + SkRasterPipeline_GradientCtx* ctx, float gapCount, size_t stop, SkPMColor4f c_l, SkPMColor4f c_r) { + // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar... + SkPMColor4f Fs = { + (c_r.fR - c_l.fR) * gapCount, + (c_r.fG - c_l.fG) * gapCount, + (c_r.fB - c_l.fB) * gapCount, + (c_r.fA - c_l.fA) * gapCount, + }; + SkPMColor4f Bs = { + c_l.fR - Fs.fR*(stop/gapCount), + c_l.fG - Fs.fG*(stop/gapCount), + c_l.fB - Fs.fB*(stop/gapCount), + c_l.fA - Fs.fA*(stop/gapCount), + }; + add_stop_color(ctx, stop, Fs, Bs); +} + +// For each stop we calculate a bias B and a scale factor F, such that +// for any t between stops n and n+1, the color we want is B[n] + F[n]*t. +static void init_stop_pos( + SkRasterPipeline_GradientCtx* ctx, size_t stop, float t_l, float t_r, SkPMColor4f c_l, SkPMColor4f c_r) { + // See note about Clankium's old compiler in init_stop_evenly(). + SkPMColor4f Fs = { + (c_r.fR - c_l.fR) / (t_r - t_l), + (c_r.fG - c_l.fG) / (t_r - t_l), + (c_r.fB - c_l.fB) / (t_r - t_l), + (c_r.fA - c_l.fA) / (t_r - t_l), + }; + SkPMColor4f Bs = { + c_l.fR - Fs.fR*t_l, + c_l.fG - Fs.fG*t_l, + c_l.fB - Fs.fB*t_l, + c_l.fA - Fs.fA*t_l, + }; + ctx->ts[stop] = t_l; + add_stop_color(ctx, stop, Fs, Bs); +} + +bool SkGradientShaderBase::onAppendStages(const SkStageRec& rec) const { + SkRasterPipeline* p = rec.fPipeline; + SkArenaAlloc* alloc = rec.fAlloc; + SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr; + + SkMatrix matrix; + if (!this->computeTotalInverse(rec.fCTM, rec.fLocalM, &matrix)) { + return false; + } + matrix.postConcat(fPtsToUnit); + + SkRasterPipeline_<256> postPipeline; + + p->append(SkRasterPipeline::seed_shader); + p->append_matrix(alloc, matrix); + this->appendGradientStages(alloc, p, &postPipeline); + + switch(fTileMode) { + case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x_1); break; + case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x_1); break; + case SkTileMode::kDecal: + decal_ctx = alloc->make(); + decal_ctx->limit_x = SkBits2Float(SkFloat2Bits(1.0f) + 1); + // reuse mask + limit_x stage, or create a custom decal_1 that just stores the mask + p->append(SkRasterPipeline::decal_x, decal_ctx); + // fall-through to clamp + case SkTileMode::kClamp: + if (!fOrigPos) { + // We clamp only when the stops are evenly spaced. + // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1. + // In that case, we must make sure we're using the general "gradient" stage, + // which is the only stage that will correctly handle unclamped t. + p->append(SkRasterPipeline::clamp_x_1); + } + break; + } + + const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag; + + // Transform all of the colors to destination color space + SkColor4fXformer xformedColors(fOrigColors4f, fColorCount, fColorSpace.get(), rec.fDstCS); + + auto prepareColor = [premulGrad, &xformedColors](int i) { + SkColor4f c = xformedColors.fColors[i]; + return premulGrad ? c.premul() + : SkPMColor4f{ c.fR, c.fG, c.fB, c.fA }; + }; + + // The two-stop case with stops at 0 and 1. + if (fColorCount == 2 && fOrigPos == nullptr) { + const SkPMColor4f c_l = prepareColor(0), + c_r = prepareColor(1); + + // See F and B below. + auto ctx = alloc->make(); + (Sk4f::Load(c_r.vec()) - Sk4f::Load(c_l.vec())).store(ctx->f); + ( Sk4f::Load(c_l.vec())).store(ctx->b); + ctx->interpolatedInPremul = premulGrad; + + p->append(SkRasterPipeline::evenly_spaced_2_stop_gradient, ctx); + } else { + auto* ctx = alloc->make(); + ctx->interpolatedInPremul = premulGrad; + + // Note: In order to handle clamps in search, the search assumes a stop conceptully placed + // at -inf. Therefore, the max number of stops is fColorCount+1. + for (int i = 0; i < 4; i++) { + // Allocate at least at for the AVX2 gather from a YMM register. + ctx->fs[i] = alloc->makeArray(std::max(fColorCount+1, 8)); + ctx->bs[i] = alloc->makeArray(std::max(fColorCount+1, 8)); + } + + if (fOrigPos == nullptr) { + // Handle evenly distributed stops. + + size_t stopCount = fColorCount; + float gapCount = stopCount - 1; + + SkPMColor4f c_l = prepareColor(0); + for (size_t i = 0; i < stopCount - 1; i++) { + SkPMColor4f c_r = prepareColor(i + 1); + init_stop_evenly(ctx, gapCount, i, c_l, c_r); + c_l = c_r; + } + add_const_color(ctx, stopCount - 1, c_l); + + ctx->stopCount = stopCount; + p->append(SkRasterPipeline::evenly_spaced_gradient, ctx); + } else { + // Handle arbitrary stops. + + ctx->ts = alloc->makeArray(fColorCount+1); + + // Remove the dummy stops inserted by SkGradientShaderBase::SkGradientShaderBase + // because they are naturally handled by the search method. + int firstStop; + int lastStop; + if (fColorCount > 2) { + firstStop = fOrigColors4f[0] != fOrigColors4f[1] ? 0 : 1; + lastStop = fOrigColors4f[fColorCount - 2] != fOrigColors4f[fColorCount - 1] + ? fColorCount - 1 : fColorCount - 2; + } else { + firstStop = 0; + lastStop = 1; + } + + size_t stopCount = 0; + float t_l = fOrigPos[firstStop]; + SkPMColor4f c_l = prepareColor(firstStop); + add_const_color(ctx, stopCount++, c_l); + // N.B. lastStop is the index of the last stop, not one after. + for (int i = firstStop; i < lastStop; i++) { + float t_r = fOrigPos[i + 1]; + SkPMColor4f c_r = prepareColor(i + 1); + SkASSERT(t_l <= t_r); + if (t_l < t_r) { + init_stop_pos(ctx, stopCount, t_l, t_r, c_l, c_r); + stopCount += 1; + } + t_l = t_r; + c_l = c_r; + } + + ctx->ts[stopCount] = t_l; + add_const_color(ctx, stopCount++, c_l); + + ctx->stopCount = stopCount; + p->append(SkRasterPipeline::gradient, ctx); + } + } + + if (decal_ctx) { + p->append(SkRasterPipeline::check_decal_mask, decal_ctx); + } + + if (!premulGrad && !this->colorsAreOpaque()) { + p->append(SkRasterPipeline::premul); + } + + p->extend(postPipeline); + + return true; +} + + +bool SkGradientShaderBase::isOpaque() const { + return fColorsAreOpaque && (this->getTileMode() != SkTileMode::kDecal); +} + +static unsigned rounded_divide(unsigned numer, unsigned denom) { + return (numer + (denom >> 1)) / denom; +} + +bool SkGradientShaderBase::onAsLuminanceColor(SkColor* lum) const { + // we just compute an average color. + // possibly we could weight this based on the proportional width for each color + // assuming they are not evenly distributed in the fPos array. + int r = 0; + int g = 0; + int b = 0; + const int n = fColorCount; + // TODO: use linear colors? + for (int i = 0; i < n; ++i) { + SkColor c = this->getLegacyColor(i); + r += SkColorGetR(c); + g += SkColorGetG(c); + b += SkColorGetB(c); + } + *lum = SkColorSetRGB(rounded_divide(r, n), rounded_divide(g, n), rounded_divide(b, n)); + return true; +} + +SkColor4fXformer::SkColor4fXformer(const SkColor4f* colors, int colorCount, + SkColorSpace* src, SkColorSpace* dst) { + fColors = colors; + + if (dst && !SkColorSpace::Equals(src, dst)) { + fStorage.reset(colorCount); + + auto info = SkImageInfo::Make(colorCount,1, kRGBA_F32_SkColorType, kUnpremul_SkAlphaType); + + SkConvertPixels(info.makeColorSpace(sk_ref_sp(dst)), fStorage.begin(), info.minRowBytes(), + info.makeColorSpace(sk_ref_sp(src)), fColors , info.minRowBytes()); + + fColors = fStorage.begin(); + } +} + +void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const { + if (info) { + if (info->fColorCount >= fColorCount) { + if (info->fColors) { + for (int i = 0; i < fColorCount; ++i) { + info->fColors[i] = this->getLegacyColor(i); + } + } + if (info->fColorOffsets) { + for (int i = 0; i < fColorCount; ++i) { + info->fColorOffsets[i] = this->getPos(i); + } + } + } + info->fColorCount = fColorCount; + info->fTileMode = fTileMode; + info->fGradientFlags = fGradFlags; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// Return true if these parameters are valid/legal/safe to construct a gradient +// +static bool valid_grad(const SkColor4f colors[], const SkScalar pos[], int count, + SkTileMode tileMode) { + return nullptr != colors && count >= 1 && (unsigned)tileMode < kSkTileModeCount; +} + +static void desc_init(SkGradientShaderBase::Descriptor* desc, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int colorCount, + SkTileMode mode, uint32_t flags, const SkMatrix* localMatrix) { + SkASSERT(colorCount > 1); + + desc->fColors = colors; + desc->fColorSpace = std::move(colorSpace); + desc->fPos = pos; + desc->fCount = colorCount; + desc->fTileMode = mode; + desc->fGradFlags = flags; + desc->fLocalMatrix = localMatrix; +} + +static SkColor4f average_gradient_color(const SkColor4f colors[], const SkScalar pos[], + int colorCount) { + // The gradient is a piecewise linear interpolation between colors. For a given interval, + // the integral between the two endpoints is 0.5 * (ci + cj) * (pj - pi), which provides that + // intervals average color. The overall average color is thus the sum of each piece. The thing + // to keep in mind is that the provided gradient definition may implicitly use p=0 and p=1. + Sk4f blend(0.0); + // Bake 1/(colorCount - 1) uniform stop difference into this scale factor + SkScalar wScale = pos ? 0.5 : 0.5 / (colorCount - 1); + for (int i = 0; i < colorCount - 1; ++i) { + // Calculate the average color for the interval between pos(i) and pos(i+1) + Sk4f c0 = Sk4f::Load(&colors[i]); + Sk4f c1 = Sk4f::Load(&colors[i + 1]); + // when pos == null, there are colorCount uniformly distributed stops, going from 0 to 1, + // so pos[i + 1] - pos[i] = 1/(colorCount-1) + SkScalar w = pos ? (pos[i + 1] - pos[i]) : SK_Scalar1; + blend += wScale * w * (c1 + c0); + } + + // Now account for any implicit intervals at the start or end of the stop definitions + if (pos) { + if (pos[0] > 0.0) { + // The first color is fixed between p = 0 to pos[0], so 0.5 * (ci + cj) * (pj - pi) + // becomes 0.5 * (c + c) * (pj - 0) = c * pj + Sk4f c = Sk4f::Load(&colors[0]); + blend += pos[0] * c; + } + if (pos[colorCount - 1] < SK_Scalar1) { + // The last color is fixed between pos[n-1] to p = 1, so 0.5 * (ci + cj) * (pj - pi) + // becomes 0.5 * (c + c) * (1 - pi) = c * (1 - pi) + Sk4f c = Sk4f::Load(&colors[colorCount - 1]); + blend += (1 - pos[colorCount - 1]) * c; + } + } + + SkColor4f avg; + blend.store(&avg); + return avg; +} + +// The default SkScalarNearlyZero threshold of .0024 is too big and causes regressions for svg +// gradients defined in the wild. +static constexpr SkScalar kDegenerateThreshold = SK_Scalar1 / (1 << 15); + +// Except for special circumstances of clamped gradients, every gradient shape--when degenerate-- +// can be mapped to the same fallbacks. The specific shape factories must account for special +// clamped conditions separately, this will always return the last color for clamped gradients. +static sk_sp make_degenerate_gradient(const SkColor4f colors[], const SkScalar pos[], + int colorCount, sk_sp colorSpace, + SkTileMode mode) { + switch(mode) { + case SkTileMode::kDecal: + // normally this would reject the area outside of the interpolation region, so since + // inside region is empty when the radii are equal, the entire draw region is empty + return SkShaders::Empty(); + case SkTileMode::kRepeat: + case SkTileMode::kMirror: + // repeat and mirror are treated the same: the border colors are never visible, + // but approximate the final color as infinite repetitions of the colors, so + // it can be represented as the average color of the gradient. + return SkShaders::Color( + average_gradient_color(colors, pos, colorCount), std::move(colorSpace)); + case SkTileMode::kClamp: + // Depending on how the gradient shape degenerates, there may be a more specialized + // fallback representation for the factories to use, but this is a reasonable default. + return SkShaders::Color(colors[colorCount - 1], std::move(colorSpace)); + } + SkDEBUGFAIL("Should not be reached"); + return nullptr; +} + +// assumes colors is SkColor4f* and pos is SkScalar* +#define EXPAND_1_COLOR(count) \ + SkColor4f tmp[2]; \ + do { \ + if (1 == count) { \ + tmp[0] = tmp[1] = colors[0]; \ + colors = tmp; \ + pos = nullptr; \ + count = 2; \ + } \ + } while (0) + +struct ColorStopOptimizer { + ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, int count, SkTileMode mode) + : fColors(colors) + , fPos(pos) + , fCount(count) { + + if (!pos || count != 3) { + return; + } + + if (SkScalarNearlyEqual(pos[0], 0.0f) && + SkScalarNearlyEqual(pos[1], 0.0f) && + SkScalarNearlyEqual(pos[2], 1.0f)) { + + if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode || + colors[0] == colors[1]) { + + // Ignore the leftmost color/pos. + fColors += 1; + fPos += 1; + fCount = 2; + } + } else if (SkScalarNearlyEqual(pos[0], 0.0f) && + SkScalarNearlyEqual(pos[1], 1.0f) && + SkScalarNearlyEqual(pos[2], 1.0f)) { + + if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode || + colors[1] == colors[2]) { + + // Ignore the rightmost color/pos. + fCount = 2; + } + } + } + + const SkColor4f* fColors; + const SkScalar* fPos; + int fCount; +}; + +struct ColorConverter { + ColorConverter(const SkColor* colors, int count) { + const float ONE_OVER_255 = 1.f / 255; + for (int i = 0; i < count; ++i) { + fColors4f.push_back({ + SkColorGetR(colors[i]) * ONE_OVER_255, + SkColorGetG(colors[i]) * ONE_OVER_255, + SkColorGetB(colors[i]) * ONE_OVER_255, + SkColorGetA(colors[i]) * ONE_OVER_255 }); + } + } + + SkSTArray<2, SkColor4f, true> fColors4f; +}; + +sk_sp SkGradientShader::MakeLinear(const SkPoint pts[2], + const SkColor colors[], + const SkScalar pos[], int colorCount, + SkTileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeLinear(pts, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, flags, + localMatrix); +} + +sk_sp SkGradientShader::MakeLinear(const SkPoint pts[2], + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], int colorCount, + SkTileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShaders::Color(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + if (SkScalarNearlyZero((pts[1] - pts[0]).length(), kDegenerateThreshold)) { + // Degenerate gradient, the only tricky complication is when in clamp mode, the limit of + // the gradient approaches two half planes of solid color (first and last). However, they + // are divided by the line perpendicular to the start and end point, which becomes undefined + // once start and end are exactly the same, so just use the end color for a stable solution. + return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode); + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp(pts, desc); +} + +sk_sp SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor colors[], + const SkScalar pos[], int colorCount, + SkTileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeRadial(center, radius, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, + flags, localMatrix); +} + +sk_sp SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], int colorCount, + SkTileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (radius < 0) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShaders::Color(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + if (SkScalarNearlyZero(radius, kDegenerateThreshold)) { + // Degenerate gradient optimization, and no special logic needed for clamped radial gradient + return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode); + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp(center, radius, desc); +} + +sk_sp SkGradientShader::MakeTwoPointConical(const SkPoint& start, + SkScalar startRadius, + const SkPoint& end, + SkScalar endRadius, + const SkColor colors[], + const SkScalar pos[], + int colorCount, + SkTileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(), + nullptr, pos, colorCount, mode, flags, localMatrix); +} + +sk_sp SkGradientShader::MakeTwoPointConical(const SkPoint& start, + SkScalar startRadius, + const SkPoint& end, + SkScalar endRadius, + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], + int colorCount, + SkTileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (startRadius < 0 || endRadius < 0) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (SkScalarNearlyZero((start - end).length(), kDegenerateThreshold)) { + // If the center positions are the same, then the gradient is the radial variant of a 2 pt + // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate + // (startRadius == endRadius). + if (SkScalarNearlyEqual(startRadius, endRadius, kDegenerateThreshold)) { + // Degenerate case, where the interpolation region area approaches zero. The proper + // behavior depends on the tile mode, which is consistent with the default degenerate + // gradient behavior, except when mode = clamp and the radii > 0. + if (mode == SkTileMode::kClamp && endRadius > kDegenerateThreshold) { + // The interpolation region becomes an infinitely thin ring at the radius, so the + // final gradient will be the first color repeated from p=0 to 1, and then a hard + // stop switching to the last color at p=1. + static constexpr SkScalar circlePos[3] = {0, 1, 1}; + SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]}; + return MakeRadial(start, endRadius, reColors, std::move(colorSpace), + circlePos, 3, mode, flags, localMatrix); + } else { + // Otherwise use the default degenerate case + return make_degenerate_gradient( + colors, pos, colorCount, std::move(colorSpace), mode); + } + } else if (SkScalarNearlyZero(startRadius, kDegenerateThreshold)) { + // We can treat this gradient as radial, which is faster. If we got here, we know + // that endRadius is not equal to 0, so this produces a meaningful gradient + return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount, + mode, flags, localMatrix); + } + // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the + // regular 2pt constructor. + } + + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + EXPAND_1_COLOR(colorCount); + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc); +} + +sk_sp SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], + const SkScalar pos[], + int colorCount, + SkTileMode mode, + SkScalar startAngle, + SkScalar endAngle, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount, + mode, startAngle, endAngle, flags, localMatrix); +} + +sk_sp SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], + int colorCount, + SkTileMode mode, + SkScalar startAngle, + SkScalar endAngle, + uint32_t flags, + const SkMatrix* localMatrix) { + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShaders::Color(colors[0], std::move(colorSpace)); + } + if (!SkScalarIsFinite(startAngle) || !SkScalarIsFinite(endAngle) || startAngle > endAngle) { + return nullptr; + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + if (SkScalarNearlyEqual(startAngle, endAngle, kDegenerateThreshold)) { + // Degenerate gradient, which should follow default degenerate behavior unless it is + // clamped and the angle is greater than 0. + if (mode == SkTileMode::kClamp && endAngle > kDegenerateThreshold) { + // In this case, the first color is repeated from 0 to the angle, then a hardstop + // switches to the last color (all other colors are compressed to the infinitely thin + // interpolation region). + static constexpr SkScalar clampPos[3] = {0, 1, 1}; + SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]}; + return MakeSweep(cx, cy, reColors, std::move(colorSpace), clampPos, 3, mode, 0, + endAngle, flags, localMatrix); + } else { + return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode); + } + } + + if (startAngle <= 0 && endAngle >= 360) { + // If the t-range includes [0,1], then we can always use clamping (presumably faster). + mode = SkTileMode::kClamp; + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + + const SkScalar t0 = startAngle / 360, + t1 = endAngle / 360; + + return sk_make_sp(SkPoint::Make(cx, cy), t0, t1, desc); +} + +void SkGradientShader::RegisterFlattenables() { + SK_REGISTER_FLATTENABLE(SkLinearGradient); + SK_REGISTER_FLATTENABLE(SkRadialGradient); + SK_REGISTER_FLATTENABLE(SkSweepGradient); + SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient); +} diff --git a/gfx/skia/skia/src/shaders/gradients/SkGradientShaderPriv.h b/gfx/skia/skia/src/shaders/gradients/SkGradientShaderPriv.h new file mode 100644 index 0000000000..b362e90c83 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkGradientShaderPriv.h @@ -0,0 +1,146 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGradientShaderPriv_DEFINED +#define SkGradientShaderPriv_DEFINED + +#include "include/effects/SkGradientShader.h" + +#include "include/core/SkMatrix.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkArenaAlloc.h" +#include "src/shaders/SkShaderBase.h" + +class SkColorSpace; +class SkRasterPipeline; +class SkReadBuffer; +class SkWriteBuffer; + +class SkGradientShaderBase : public SkShaderBase { +public: + struct Descriptor { + Descriptor() { + sk_bzero(this, sizeof(*this)); + fTileMode = SkTileMode::kClamp; + } + + const SkMatrix* fLocalMatrix; + const SkColor4f* fColors; + sk_sp fColorSpace; + const SkScalar* fPos; + int fCount; + SkTileMode fTileMode; + uint32_t fGradFlags; + + void flatten(SkWriteBuffer&) const; + }; + + class DescriptorScope : public Descriptor { + public: + DescriptorScope() {} + + bool unflatten(SkReadBuffer&); + + // fColors and fPos always point into local memory, so they can be safely mutated + // + SkColor4f* mutableColors() { return const_cast(fColors); } + SkScalar* mutablePos() { return const_cast(fPos); } + + private: + SkSTArray<16, SkColor4f, true> fColorStorage; + SkSTArray<16, SkScalar , true> fPosStorage; + SkMatrix fLocalMatrixStorage; + }; + + SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit); + ~SkGradientShaderBase() override; + + bool isOpaque() const override; + + uint32_t getGradFlags() const { return fGradFlags; } + + const SkMatrix& getGradientMatrix() const { return fPtsToUnit; } + +protected: + class GradientShaderBase4fContext; + + SkGradientShaderBase(SkReadBuffer& ); + void flatten(SkWriteBuffer&) const override; + + void commonAsAGradient(GradientInfo*) const; + + bool onAsLuminanceColor(SkColor*) const override; + + bool onAppendStages(const SkStageRec&) const override; + + virtual void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline, + SkRasterPipeline* postPipeline) const = 0; + + template + static Context* CheckedMakeContext(SkArenaAlloc* alloc, Args&&... args) { + auto* ctx = alloc->make(std::forward(args)...); + if (!ctx->isValid()) { + return nullptr; + } + return ctx; + } + + const SkMatrix fPtsToUnit; + SkTileMode fTileMode; + uint8_t fGradFlags; + +public: + SkScalar getPos(int i) const { + SkASSERT(i < fColorCount); + return fOrigPos ? fOrigPos[i] : SkIntToScalar(i) / (fColorCount - 1); + } + + SkColor getLegacyColor(int i) const { + SkASSERT(i < fColorCount); + return fOrigColors4f[i].toSkColor(); + } + + bool colorsCanConvertToSkColor() const { + bool canConvert = true; + for (int i = 0; i < fColorCount; ++i) { + canConvert &= fOrigColors4f[i].fitsInBytes(); + } + return canConvert; + } + + SkColor4f* fOrigColors4f; // original colors, as floats + SkScalar* fOrigPos; // original positions + int fColorCount; + sk_sp fColorSpace; // color space of gradient stops + + bool colorsAreOpaque() const { return fColorsAreOpaque; } + + SkTileMode getTileMode() const { return fTileMode; } + +private: + // Reserve inline space for up to 4 stops. + static constexpr size_t kInlineStopCount = 4; + static constexpr size_t kInlineStorageSize = (sizeof(SkColor4f) + sizeof(SkScalar)) + * kInlineStopCount; + SkAutoSTMalloc fStorage; + + bool fColorsAreOpaque; + + typedef SkShaderBase INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +struct SkColor4fXformer { + SkColor4fXformer(const SkColor4f* colors, int colorCount, SkColorSpace* src, SkColorSpace* dst); + + const SkColor4f* fColors; + SkSTArray<4, SkColor4f, true> fStorage; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkLinearGradient.cpp b/gfx/skia/skia/src/shaders/gradients/SkLinearGradient.cpp new file mode 100644 index 0000000000..98f15cf7f2 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkLinearGradient.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/shaders/gradients/SkLinearGradient.h" + +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/gradients/Sk4fLinearGradient.h" + +static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) { + SkVector vec = pts[1] - pts[0]; + SkScalar mag = vec.length(); + SkScalar inv = mag ? SkScalarInvert(mag) : 0; + + vec.scale(inv); + SkMatrix matrix; + matrix.setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); + matrix.postTranslate(-pts[0].fX, -pts[0].fY); + matrix.postScale(inv, inv); + return matrix; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc) + : SkGradientShaderBase(desc, pts_to_unit_matrix(pts)) + , fStart(pts[0]) + , fEnd(pts[1]) { +} + +sk_sp SkLinearGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + SkPoint pts[2]; + pts[0] = buffer.readPoint(); + pts[1] = buffer.readPoint(); + return SkGradientShader::MakeLinear(pts, desc.fColors, std::move(desc.fColorSpace), desc.fPos, + desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkLinearGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fStart); + buffer.writePoint(fEnd); +} + +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT +SkShaderBase::Context* SkLinearGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + // make sure our colorspaces are compatible with legacy blits + if (!rec.isLegacyCompatible(fColorSpace.get())) { + return nullptr; + } + // Can't use legacy blit if we can't represent our colors as SkColors + if (!this->colorsCanConvertToSkColor()) { + return nullptr; + } + + return fTileMode != SkTileMode::kDecal + ? CheckedMakeContext(alloc, *this, rec) + : nullptr; +} +#endif + +void SkLinearGradient::appendGradientStages(SkArenaAlloc*, SkRasterPipeline*, + SkRasterPipeline*) const { + // No extra stage needed for linear gradients. +} + +SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fStart; + info->fPoint[1] = fEnd; + } + return kLinear_GradientType; +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "src/gpu/gradients/GrGradientShader.h" + +std::unique_ptr SkLinearGradient::asFragmentProcessor( + const GrFPArgs& args) const { + return GrGradientShader::MakeLinear(*this, args); +} + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkLinearGradient.h b/gfx/skia/skia/src/shaders/gradients/SkLinearGradient.h new file mode 100644 index 0000000000..fff649380b --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkLinearGradient.h @@ -0,0 +1,44 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLinearGradient_DEFINED +#define SkLinearGradient_DEFINED + +#include "src/shaders/gradients/SkGradientShaderPriv.h" + +class SkLinearGradient : public SkGradientShaderBase { +public: + SkLinearGradient(const SkPoint pts[2], const Descriptor&); + + GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + SkLinearGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; +#endif + + void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline, + SkRasterPipeline* postPipeline) const final; + + +private: + SK_FLATTENABLE_HOOKS(SkLinearGradient) + + class LinearGradient4fContext; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; + const SkPoint fStart; + const SkPoint fEnd; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkRadialGradient.cpp b/gfx/skia/skia/src/shaders/gradients/SkRadialGradient.cpp new file mode 100644 index 0000000000..679aa8f557 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkRadialGradient.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/gradients/SkRadialGradient.h" + +namespace { + +SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) { + SkScalar inv = SkScalarInvert(radius); + + SkMatrix matrix; + matrix.setTranslate(-center.fX, -center.fY); + matrix.postScale(inv, inv); + return matrix; +} + +} // namespace + +///////////////////////////////////////////////////////////////////// + +SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc) + : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius)) + , fCenter(center) + , fRadius(radius) { +} + +SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter; + info->fRadius[0] = fRadius; + } + return kRadial_GradientType; +} + +sk_sp SkRadialGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + const SkPoint center = buffer.readPoint(); + const SkScalar radius = buffer.readScalar(); + return SkGradientShader::MakeRadial(center, radius, desc.fColors, std::move(desc.fColorSpace), + desc.fPos, desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkRadialGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter); + buffer.writeScalar(fRadius); +} + +void SkRadialGradient::appendGradientStages(SkArenaAlloc*, SkRasterPipeline* p, + SkRasterPipeline*) const { + p->append(SkRasterPipeline::xy_to_radius); +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "src/gpu/gradients/GrGradientShader.h" + +std::unique_ptr SkRadialGradient::asFragmentProcessor( + const GrFPArgs& args) const { + return GrGradientShader::MakeRadial(*this, args); +} + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkRadialGradient.h b/gfx/skia/skia/src/shaders/gradients/SkRadialGradient.h new file mode 100644 index 0000000000..0e615048fb --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkRadialGradient.h @@ -0,0 +1,39 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRadialGradient_DEFINED +#define SkRadialGradient_DEFINED + +#include "src/shaders/gradients/SkGradientShaderPriv.h" + +class SkRadialGradient final : public SkGradientShaderBase { +public: + SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&); + + GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + +protected: + SkRadialGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + + void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline, + SkRasterPipeline* postPipeline) const override; + +private: + SK_FLATTENABLE_HOOKS(SkRadialGradient) + + const SkPoint fCenter; + const SkScalar fRadius; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkSweepGradient.cpp b/gfx/skia/skia/src/shaders/gradients/SkSweepGradient.cpp new file mode 100644 index 0000000000..68f0cbac61 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkSweepGradient.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkFloatingPoint.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/gradients/SkSweepGradient.h" + +SkSweepGradient::SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1, + const Descriptor& desc) + : SkGradientShaderBase(desc, SkMatrix::MakeTrans(-center.x(), -center.y())) + , fCenter(center) + , fTBias(-t0) + , fTScale(1 / (t1 - t0)) +{ + SkASSERT(t0 < t1); +} + +SkShader::GradientType SkSweepGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter; + } + return kSweep_GradientType; +} + +static std::tuple angles_from_t_coeff(SkScalar tBias, SkScalar tScale) { + return std::make_tuple(-tBias * 360, (sk_ieee_float_divide(1, tScale) - tBias) * 360); +} + +sk_sp SkSweepGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + const SkPoint center = buffer.readPoint(); + + SkScalar startAngle = 0, + endAngle = 360; + if (!buffer.isVersionLT(SkPicturePriv::kTileInfoInSweepGradient_Version)) { + const auto tBias = buffer.readScalar(), + tScale = buffer.readScalar(); + std::tie(startAngle, endAngle) = angles_from_t_coeff(tBias, tScale); + } + + return SkGradientShader::MakeSweep(center.x(), center.y(), desc.fColors, + std::move(desc.fColorSpace), desc.fPos, desc.fCount, + desc.fTileMode, startAngle, endAngle, + desc.fGradFlags, desc.fLocalMatrix); +} + +void SkSweepGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter); + buffer.writeScalar(fTBias); + buffer.writeScalar(fTScale); +} + +void SkSweepGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p, + SkRasterPipeline*) const { + p->append(SkRasterPipeline::xy_to_unit_angle); + p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeScale(fTScale, 1), + SkMatrix::MakeTrans(fTBias , 0))); +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "src/gpu/gradients/GrGradientShader.h" + +std::unique_ptr SkSweepGradient::asFragmentProcessor( + const GrFPArgs& args) const { + return GrGradientShader::MakeSweep(*this, args); +} + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkSweepGradient.h b/gfx/skia/skia/src/shaders/gradients/SkSweepGradient.h new file mode 100644 index 0000000000..71d2221dc7 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkSweepGradient.h @@ -0,0 +1,44 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSweepGradient_DEFINED +#define SkSweepGradient_DEFINED + +#include "src/shaders/gradients/SkGradientShaderPriv.h" + +class SkSweepGradient final : public SkGradientShaderBase { +public: + SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1, const Descriptor&); + + GradientType asAGradient(GradientInfo* info) const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + + SkScalar getTBias() const { return fTBias; } + + SkScalar getTScale() const { return fTScale; } + +protected: + void flatten(SkWriteBuffer& buffer) const override; + + void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline, + SkRasterPipeline* postPipeline) const override; + +private: + SK_FLATTENABLE_HOOKS(SkSweepGradient) + + const SkPoint fCenter; + const SkScalar fTBias, + fTScale; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.cpp b/gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.cpp new file mode 100644 index 0000000000..f158bdd975 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkFloatingPoint.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/gradients/SkTwoPointConicalGradient.h" + +#include + +// Please see https://skia.org/dev/design/conical for how our shader works. + +bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) { + fIsSwapped = false; + fFocalX = sk_ieee_float_divide(r0, (r0 - r1)); + if (SkScalarNearlyZero(fFocalX - 1)) { + // swap r0, r1 + matrix->postTranslate(-1, 0); + matrix->postScale(-1, 1); + std::swap(r0, r1); + fFocalX = 0; // because r0 is now 0 + fIsSwapped = true; + } + + // Map {focal point, (1, 0)} to {(0, 0), (1, 0)} + const SkPoint from[2] = { {fFocalX, 0}, {1, 0} }; + const SkPoint to[2] = { {0, 0}, {1, 0} }; + SkMatrix focalMatrix; + if (!focalMatrix.setPolyToPoly(from, to, 2)) { + return false; + } + matrix->postConcat(focalMatrix); + fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f) + + // The following transformations are just to accelerate the shader computation by saving + // some arithmatic operations. + if (this->isFocalOnCircle()) { + matrix->postScale(0.5, 0.5); + } else { + matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1))); + } + matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f| + return true; +} + +sk_sp SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0, + const SkPoint& c1, SkScalar r1, + const Descriptor& desc) { + SkMatrix gradientMatrix; + Type gradientType; + + if (SkScalarNearlyZero((c0 - c1).length())) { + if (SkScalarNearlyZero(SkTMax(r0, r1)) || SkScalarNearlyEqual(r0, r1)) { + // Degenerate case; avoid dividing by zero. Should have been caught by caller but + // just in case, recheck here. + return nullptr; + } + // Concentric case: we can pretend we're radial (with a tiny twist). + const SkScalar scale = sk_ieee_float_divide(1, SkTMax(r0, r1)); + gradientMatrix = SkMatrix::MakeTrans(-c1.x(), -c1.y()); + gradientMatrix.postScale(scale, scale); + + gradientType = Type::kRadial; + } else { + const SkPoint centers[2] = { c0 , c1 }; + const SkPoint unitvec[2] = { {0, 0}, {1, 0} }; + + if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) { + // Degenerate case. + return nullptr; + } + + gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal; + } + + FocalData focalData; + if (gradientType == Type::kFocal) { + const auto dCenter = (c0 - c1).length(); + if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) { + return nullptr; + } + } + return sk_sp(new SkTwoPointConicalGradient(c0, r0, c1, r1, desc, + gradientType, gradientMatrix, focalData)); +} + +SkTwoPointConicalGradient::SkTwoPointConicalGradient( + const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data) + : SkGradientShaderBase(desc, gradientMatrix) + , fCenter1(start) + , fCenter2(end) + , fRadius1(startRadius) + , fRadius2(endRadius) + , fType(type) +{ + // this is degenerate, and should be caught by our caller + SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2); + if (type == Type::kFocal) { + fFocalData = data; + } +} + +bool SkTwoPointConicalGradient::isOpaque() const { + // Because areas outside the cone are left untouched, we cannot treat the + // shader as opaque even if the gradient itself is opaque. + // TODO(junov): Compute whether the cone fills the plane crbug.com/222380 + return false; +} + +// Returns the original non-sorted version of the gradient +SkShader::GradientType SkTwoPointConicalGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter1; + info->fPoint[1] = fCenter2; + info->fRadius[0] = fRadius1; + info->fRadius[1] = fRadius2; + } + return kConical_GradientType; +} + +sk_sp SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + SkPoint c1 = buffer.readPoint(); + SkPoint c2 = buffer.readPoint(); + SkScalar r1 = buffer.readScalar(); + SkScalar r2 = buffer.readScalar(); + + if (buffer.isVersionLT(SkPicturePriv::k2PtConicalNoFlip_Version) && buffer.readBool()) { + using std::swap; + // legacy flipped gradient + swap(c1, c2); + swap(r1, r2); + + SkColor4f* colors = desc.mutableColors(); + SkScalar* pos = desc.mutablePos(); + const int last = desc.fCount - 1; + const int half = desc.fCount >> 1; + for (int i = 0; i < half; ++i) { + swap(colors[i], colors[last - i]); + if (pos) { + SkScalar tmp = pos[i]; + pos[i] = SK_Scalar1 - pos[last - i]; + pos[last - i] = SK_Scalar1 - tmp; + } + } + if (pos) { + if (desc.fCount & 1) { + pos[half] = SK_Scalar1 - pos[half]; + } + } + } + if (!buffer.isValid()) { + return nullptr; + } + return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors, + std::move(desc.fColorSpace), desc.fPos, + desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter1); + buffer.writePoint(fCenter2); + buffer.writeScalar(fRadius1); + buffer.writeScalar(fRadius2); +} + +void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p, + SkRasterPipeline* postPipeline) const { + const auto dRadius = fRadius2 - fRadius1; + + if (fType == Type::kRadial) { + p->append(SkRasterPipeline::xy_to_radius); + + // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2]. + auto scale = SkTMax(fRadius1, fRadius2) / dRadius; + auto bias = -fRadius1 / dRadius; + + p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeTrans(bias, 0), + SkMatrix::MakeScale(scale, 1))); + return; + } + + if (fType == Type::kStrip) { + auto* ctx = alloc->make(); + SkScalar scaledR0 = fRadius1 / this->getCenterX1(); + ctx->fP0 = scaledR0 * scaledR0; + p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx); + p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx); + postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask); + return; + } + + auto* ctx = alloc->make(); + ctx->fP0 = 1/fFocalData.fR1; + ctx->fP1 = fFocalData.fFocalX; + + if (fFocalData.isFocalOnCircle()) { + p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle); + } else if (fFocalData.isWellBehaved()) { + p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx); + } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) { + p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx); + } else { + p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx); + } + + if (!fFocalData.isWellBehaved()) { + p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx); + } + if (1 - fFocalData.fFocalX < 0) { + p->append(SkRasterPipeline::negate_x); + } + if (!fFocalData.isNativelyFocal()) { + p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx); + } + if (fFocalData.isSwapped()) { + p->append(SkRasterPipeline::alter_2pt_conical_unswap); + } + if (!fFocalData.isWellBehaved()) { + postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask); + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "src/gpu/gradients/GrGradientShader.h" + +std::unique_ptr SkTwoPointConicalGradient::asFragmentProcessor( + const GrFPArgs& args) const { + return GrGradientShader::MakeConical(*this, args); +} + +#endif diff --git a/gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.h b/gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.h new file mode 100644 index 0000000000..252696f2d7 --- /dev/null +++ b/gfx/skia/skia/src/shaders/gradients/SkTwoPointConicalGradient.h @@ -0,0 +1,91 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTwoPointConicalGradient_DEFINED +#define SkTwoPointConicalGradient_DEFINED + +#include "src/shaders/gradients/SkGradientShaderPriv.h" + +class SkTwoPointConicalGradient final : public SkGradientShaderBase { +public: + // See https://skia.org/dev/design/conical for what focal data means and how our shader works. + // We make it public so the GPU shader can also use it. + struct FocalData { + SkScalar fR1; // r1 after mapping focal point to (0, 0) + SkScalar fFocalX; // f + bool fIsSwapped; // whether we swapped r0, r1 + + // The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}. + // We'll post concat matrix with our transformation matrix that maps focal point to (0, 0). + // Returns true if the set succeeded + bool set(SkScalar r0, SkScalar r1, SkMatrix* matrix); + + // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If + // this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves) + // will go through the focal point (aircraft). In our previous implementations, this was + // known as the edge case where the inside circle touches the outside circle (on the focal + // point). If we were to solve for t bruteforcely using a quadratic equation, this case + // implies that the quadratic equation degenerates to a linear equation. + bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); } + + bool isSwapped() const { return fIsSwapped; } + bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; } + bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); } + }; + + enum class Type { + kRadial, + kStrip, + kFocal + }; + + static sk_sp Create(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + const Descriptor&); + + SkShader::GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + bool isOpaque() const override; + + SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); } + SkScalar getStartRadius() const { return fRadius1; } + SkScalar getDiffRadius() const { return fRadius2 - fRadius1; } + const SkPoint& getStartCenter() const { return fCenter1; } + const SkPoint& getEndCenter() const { return fCenter2; } + SkScalar getEndRadius() const { return fRadius2; } + + Type getType() const { return fType; } + const FocalData& getFocalData() const { return fFocalData; } + +protected: + void flatten(SkWriteBuffer& buffer) const override; + + void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline, + SkRasterPipeline* postPipeline) const override; + +private: + SK_FLATTENABLE_HOOKS(SkTwoPointConicalGradient) + + SkTwoPointConicalGradient(const SkPoint& c0, SkScalar r0, + const SkPoint& c1, SkScalar r1, + const Descriptor&, Type, const SkMatrix&, const FocalData&); + + SkPoint fCenter1; + SkPoint fCenter2; + SkScalar fRadius1; + SkScalar fRadius2; + Type fType; + + FocalData fFocalData; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/GLSL.std.450.h b/gfx/skia/skia/src/sksl/GLSL.std.450.h new file mode 100644 index 0000000000..943fd8650f --- /dev/null +++ b/gfx/skia/skia/src/sksl/GLSL.std.450.h @@ -0,0 +1,131 @@ +/* +** Copyright (c) 2014-2016 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and/or associated documentation files (the "Materials"), +** to deal in the Materials without restriction, including without limitation +** the rights to use, copy, modify, merge, publish, distribute, sublicense, +** and/or sell copies of the Materials, and to permit persons to whom the +** Materials are furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Materials. +** +** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +** IN THE MATERIALS. +*/ + +#ifndef GLSLstd450_H +#define GLSLstd450_H + +static const int GLSLstd450Version = 100; +static const int GLSLstd450Revision = 3; + +enum GLSLstd450 { + GLSLstd450Bad = 0, // Don't use + + GLSLstd450Round = 1, + GLSLstd450RoundEven = 2, + GLSLstd450Trunc = 3, + GLSLstd450FAbs = 4, + GLSLstd450SAbs = 5, + GLSLstd450FSign = 6, + GLSLstd450SSign = 7, + GLSLstd450Floor = 8, + GLSLstd450Ceil = 9, + GLSLstd450Fract = 10, + + GLSLstd450Radians = 11, + GLSLstd450Degrees = 12, + GLSLstd450Sin = 13, + GLSLstd450Cos = 14, + GLSLstd450Tan = 15, + GLSLstd450Asin = 16, + GLSLstd450Acos = 17, + GLSLstd450Atan = 18, + GLSLstd450Sinh = 19, + GLSLstd450Cosh = 20, + GLSLstd450Tanh = 21, + GLSLstd450Asinh = 22, + GLSLstd450Acosh = 23, + GLSLstd450Atanh = 24, + GLSLstd450Atan2 = 25, + + GLSLstd450Pow = 26, + GLSLstd450Exp = 27, + GLSLstd450Log = 28, + GLSLstd450Exp2 = 29, + GLSLstd450Log2 = 30, + GLSLstd450Sqrt = 31, + GLSLstd450InverseSqrt = 32, + + GLSLstd450Determinant = 33, + GLSLstd450MatrixInverse = 34, + + GLSLstd450Modf = 35, // second operand needs an OpVariable to write to + GLSLstd450ModfStruct = 36, // no OpVariable operand + GLSLstd450FMin = 37, + GLSLstd450UMin = 38, + GLSLstd450SMin = 39, + GLSLstd450FMax = 40, + GLSLstd450UMax = 41, + GLSLstd450SMax = 42, + GLSLstd450FClamp = 43, + GLSLstd450UClamp = 44, + GLSLstd450SClamp = 45, + GLSLstd450FMix = 46, + GLSLstd450IMix = 47, // Reserved + GLSLstd450Step = 48, + GLSLstd450SmoothStep = 49, + + GLSLstd450Fma = 50, + GLSLstd450Frexp = 51, // second operand needs an OpVariable to write to + GLSLstd450FrexpStruct = 52, // no OpVariable operand + GLSLstd450Ldexp = 53, + + GLSLstd450PackSnorm4x8 = 54, + GLSLstd450PackUnorm4x8 = 55, + GLSLstd450PackSnorm2x16 = 56, + GLSLstd450PackUnorm2x16 = 57, + GLSLstd450PackHalf2x16 = 58, + GLSLstd450PackDouble2x32 = 59, + GLSLstd450UnpackSnorm2x16 = 60, + GLSLstd450UnpackUnorm2x16 = 61, + GLSLstd450UnpackHalf2x16 = 62, + GLSLstd450UnpackSnorm4x8 = 63, + GLSLstd450UnpackUnorm4x8 = 64, + GLSLstd450UnpackDouble2x32 = 65, + + GLSLstd450Length = 66, + GLSLstd450Distance = 67, + GLSLstd450Cross = 68, + GLSLstd450Normalize = 69, + GLSLstd450FaceForward = 70, + GLSLstd450Reflect = 71, + GLSLstd450Refract = 72, + + GLSLstd450FindILsb = 73, + GLSLstd450FindSMsb = 74, + GLSLstd450FindUMsb = 75, + + GLSLstd450InterpolateAtCentroid = 76, + GLSLstd450InterpolateAtSample = 77, + GLSLstd450InterpolateAtOffset = 78, + + GLSLstd450NMin = 79, + GLSLstd450NMax = 80, + GLSLstd450NClamp = 81, + + GLSLstd450Count +}; + +#endif // #ifndef GLSLstd450_H diff --git a/gfx/skia/skia/src/sksl/README b/gfx/skia/skia/src/sksl/README new file mode 100644 index 0000000000..c1d7ae6b33 --- /dev/null +++ b/gfx/skia/skia/src/sksl/README @@ -0,0 +1,173 @@ +Overview +======== + +SkSL ("Skia Shading Language") is a variant of GLSL which is used as Skia's +internal shading language. SkSL is, at its heart, a single standardized version +of GLSL which avoids all of the various version and dialect differences found +in GLSL "in the wild", but it does bring a few of its own changes to the table. + +Skia uses the SkSL compiler to convert SkSL code to GLSL, GLSL ES, or SPIR-V +before handing it over to the graphics driver. + + +Differences from GLSL +===================== + +* Precision modifiers are not used. 'float', 'int', and 'uint' are always high + precision. New types 'half', 'short', and 'ushort' are medium precision (we + do not use low precision). +* Vector types are named , so float2 instead of vec2 and + bool4 instead of bvec4 +* Matrix types are named x, so float2x3 instead of + mat2x3 and double4x4 instead of dmat4 +* "@if" and "@switch" are static versions of if and switch. They behave exactly + the same as if and switch in all respects other than it being a compile-time + error to use a non-constant expression as a test. +* GLSL caps can be referenced via the syntax 'sk_Caps.', e.g. + sk_Caps.sampleVariablesSupport. The value will be a constant boolean or int, + as appropriate. As SkSL supports constant folding and branch elimination, this + means that an 'if' statement which statically queries a cap will collapse down + to the chosen branch, meaning that: + + if (sk_Caps.externalTextureSupport) + do_something(); + else + do_something_else(); + + will compile as if you had written either 'do_something();' or + 'do_something_else();', depending on whether that cap is enabled or not. +* no #version statement is required, and it will be ignored if present +* the output color is sk_FragColor (do not declare it) +* use sk_Position instead of gl_Position. sk_Position is in device coordinates + rather than normalized coordinates. +* use sk_PointSize instead of gl_PointSize +* use sk_VertexID instead of gl_VertexID +* use sk_InstanceID instead of gl_InstanceID +* the fragment coordinate is sk_FragCoord, and is always relative to the upper + left. +* use sk_Clockwise instead of gl_FrontFacing. This is always relative to an + upper left origin. +* you do not need to include ".0" to make a number a float (meaning that + "float2(x, y) * 4" is perfectly legal in SkSL, unlike GLSL where it would + often have to be expressed "float2(x, y) * 4.0". There is no performance + penalty for this, as the number is converted to a float at compile time) +* type suffixes on numbers (1.0f, 0xFFu) are both unnecessary and unsupported +* creating a smaller vector from a larger vector (e.g. float2(float3(1))) is + intentionally disallowed, as it is just a wordier way of performing a swizzle. + Use swizzles instead. +* Swizzle components, in addition to the normal rgba / xyzw components, can also + be LTRB (meaning "left/top/right/bottom", for when we store rectangles in + vectors), and may also be the constants '0' or '1' to produce a constant 0 or + 1 in that channel instead of selecting anything from the source vector. + foo.rgb1 is equivalent to float4(foo.rgb, 1). +* All texture functions are named "sample", e.g. sample(sampler2D, float3) is + equivalent to GLSL's textureProj(sampler2D, float3). +* Render target width and height are available via sk_Width and sk_Height +* some built-in functions and one or two rarely-used language features are not + yet supported (sorry!) + +SkSL is still under development, and is expected to diverge further from GLSL +over time. + + +SkSL Fragment Processors +======================== + +******************************************************************************** +*** IMPORTANT: You must set gn arg "skia_compile_processors = true" to cause *** +*** .fp files to be recompiled! In order for compilation to succeed, you *** +*** must run bin/fetch-clang-format (once) to install our blessed version. *** +******************************************************************************** + +An extension of SkSL allows for the creation of fragment processors in pure +SkSL. The program defines its inputs similarly to a normal SkSL program (with +'in' and 'uniform' variables), but the 'main()' function represents only this +fragment processor's portion of the overall fragment shader. + +Within an '.fp' fragment processor file: + +* C++ code can be embedded in sections of the form: + + @section_name { } + + Supported section are: + @header (in the .h file, outside the class declaration) + @headerEnd (at the end of the .h file) + @class (in the .h file, inside the class declaration) + @cpp (in the .cpp file) + @cppEnd (at the end of the .cpp file) + @constructorParams (extra parameters to the constructor, comma-separated) + @constructor (replaces the default constructor) + @initializers (constructor initializer list, comma-separated) + @emitCode (extra code for the emitCode function) + @fields (extra private fields, each terminated with a semicolon) + @make (replaces the default Make function) + @clone (replaces the default clone() function) + @setData() (extra code for the setData function, where is + the name of the GrGLSLProgramDataManager) + @test() (the body of the TestCreate function, where is + the name of the GrProcessorTestData* parameter) + @coordTransform() + (the matrix to attach to the named sampler2D's + GrCoordTransform) + @samplerParams() + (the sampler params to attach to the named sampler2D) +* global 'in' variables represent data passed to the fragment processor at + construction time. These variables become constructor parameters and are + stored in fragment processor fields. By default float2/half2 maps to SkPoints, + and float4/half4 maps to SkRects (in x, y, width, height) order. Similarly, + int2/short2 maps to SkIPoint and int4/half4 maps to SkIRect. Use ctype + (below) to override this default mapping. +* global variables support an additional 'ctype' layout key, providing the type + they should be represented as from within the C++ code. For instance, you can + use 'layout(ctype=SkPMColor4f) in half4 color;' to create a variable that looks + like a half4 on the SkSL side of things, and a SkPMColor4f on the C++ side of + things. +* 'uniform' variables become, as one would expect, top-level uniforms. By + default they do not have any data provided to them; you will need to provide + them with data via the @setData section. +* 'in uniform' variables are uniforms that are automatically wired up to + fragment processor constructor parameters. The fragment processor will accept + a parameter representing the uniform's value, and automatically plumb it + through to the uniform's value in its generated setData() function. +* 'in uniform' variables support a 'tracked' flag in the layout that will + have the generated code automatically implement state tracking on the uniform + value to minimize GPU calls. +* the 'sk_TransformedCoords2D' array provides access to 2D transformed + coordinates. sk_TransformedCoords2D[0] is equivalent to calling + fragBuilder->ensureCoords2D(args.fTransformedCoords[0]) (and the result is + cached, so you need not worry about using the value repeatedly). +* Uniform variables support an additional 'when' layout key. + 'layout(when=foo) uniform int x;' means that this uniform will only be + emitted when the 'foo' expression is true. +* 'in' variables support an additional 'key' layout key. + 'layout(key) in uniform int x;' means that this uniform should be included in + the program's key. Matrix variables additionally support 'key=identity', + which causes the key to consider only whether or not the matrix is an + identity matrix. +* child processors can be declared with 'in fragmentProcessor ;', and can + be invoked by calling 'sample()' or 'sample(, )'. + The first variant emits the child with a solid white input color. The second + variant emits the child with the result of the 2nd argument's expression, + which must evaluate to a half4. The process function returns a half4. +* By default, fragment processors must be non-null. The type for a nullable + fragment processor is 'fragmentProcessor?', as in + 'in fragmentProcessor? '. You can check for the presence of such a + fragment processor by comparing it to 'null'. + + +Creating a new .fp file +======================= + +1. Ensure that you have set gn arg "skia_compile_processors = true" +2. Create your new .fp file, generally under src/gpu/effects. +3. Add the .fp file to sksl.gni. +4. Build Skia. This will cause the .fp file to be compiled, resulting in a new + .cpp and .h file for the fragment processor. +5. Add the .cpp and .h files to gpu.gni. +6. Add the new processor's ClassID (k_ClassID) to + GrProcessor::ClassID. +7. At this point you can reference the new fragment processor from within Skia. + +Once you have done this initial setup, simply re-build Skia to pick up any +changes to the .fp file. diff --git a/gfx/skia/skia/src/sksl/SkSLASTFile.h b/gfx/skia/skia/src/sksl/SkSLASTFile.h new file mode 100644 index 0000000000..71fb8eb2df --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLASTFile.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_ASTFILE +#define SKSL_ASTFILE + +#include "src/sksl/SkSLASTNode.h" + +namespace SkSL { + +struct ASTFile { + ASTFile() + : fRoot(ASTNode::ID::Invalid()) {} + + ASTNode& root() { + return fNodes[fRoot.fValue]; + } + +private: + std::vector fNodes; + + ASTNode::ID fRoot; + + friend class IRGenerator; + friend class Parser; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLASTNode.cpp b/gfx/skia/skia/src/sksl/SkSLASTNode.cpp new file mode 100644 index 0000000000..34e59ebb32 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLASTNode.cpp @@ -0,0 +1,238 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLASTNode.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLString.h" + +namespace SkSL { + +String ASTNode::description() const { + switch (fKind) { + case Kind::kNull: return ""; + case Kind::kBinary: + return "(" + this->begin()->description() + " " + + Compiler::OperatorName(getToken().fKind) + " " + + (this->begin() + 1)->description() + ")"; + case Kind::kBlock: { + String result = "{\n"; + for (const auto& c : *this) { + result += c.description(); + result += "\n"; + } + result += "}"; + return result; + } + case Kind::kBool: + return getBool() ? "true" : "false"; + case Kind::kBreak: + return "break"; + case Kind::kCall: { + auto iter = this->begin(); + String result = iter->description(); + result += "("; + const char* separator = ""; + while (iter != this->end()) { + result += separator; + result += (iter++)->description(); + separator = ","; + } + result += ")"; + return result; + } + case Kind::kContinue: + return "continue"; + case Kind::kDiscard: + return "discard"; + case Kind::kDo: + return "do " + this->begin()->description() + " while (" + + (this->begin() + 1)->description() + ")"; + case Kind::kEnum: { + String result = "enum "; + result += getString(); + result += " {\n"; + for (const auto& c : *this) { + result += c.description(); + result += "\n"; + } + result += "};"; + return result; + } + case Kind::kEnumCase: + if (this->begin() != this->end()) { + return String(getString()) + " = " + this->begin()->description(); + } + return getString(); + case Kind::kExtension: + return "#extension " + getString(); + case Kind::kField: + return this->begin()->description() + "." + getString(); + case Kind::kFile: { + String result; + for (const auto& c : *this) { + result += c.description(); + result += "\n"; + } + return result; + } + case Kind::kFloat: + return to_string(getFloat()); + case Kind::kFor: + return "for (" + this->begin()->description() + "; " + + (this->begin() + 1)->description() + "; " + (this->begin() + 2)->description() + + ") " + (this->begin() + 3)->description(); + case Kind::kFunction: { + FunctionData fd = getFunctionData(); + String result = fd.fModifiers.description(); + if (result.size()) { + result += " "; + } + auto iter = this->begin(); + result += (iter++)->description() + " " + fd.fName + "("; + const char* separator = ""; + for (size_t i = 0; i < fd.fParameterCount; ++i) { + result += separator; + result += (iter++)->description(); + separator = ", "; + } + result += ")"; + if (iter != this->end()) { + result += " " + (iter++)->description(); + SkASSERT(iter == this->end()); + } + else { + result += ";"; + } + return result; + } + case Kind::kIdentifier: + return getString(); + case Kind::kIndex: + return this->begin()->description() + "[" + (this->begin() + 1)->description() + "]"; + case Kind::kIf: { + String result; + if (getBool()) { + result = "@"; + } + auto iter = this->begin(); + result += "if (" + (iter++)->description() + ") "; + result += (iter++)->description(); + if (iter != this->end()) { + result += " else " + (iter++)->description(); + SkASSERT(iter == this->end()); + } + return result; + } + case Kind::kInt: + return to_string(getInt()); + case Kind::kInterfaceBlock: { + InterfaceBlockData id = getInterfaceBlockData(); + String result = id.fModifiers.description() + " " + id.fTypeName + " {\n"; + auto iter = this->begin(); + for (size_t i = 0; i < id.fDeclarationCount; ++i) { + result += (iter++)->description() + "\n"; + } + result += "} "; + result += id.fInstanceName; + for (size_t i = 0; i < id.fSizeCount; ++i) { + result += "[" + (iter++)->description() + "]"; + } + SkASSERT(iter == this->end()); + result += ";"; + return result; + } + case Kind::kModifiers: + return getModifiers().description(); + case Kind::kParameter: { + ParameterData pd = getParameterData(); + auto iter = this->begin(); + String result = (iter++)->description() + " " + pd.fName; + for (size_t i = 0; i < pd.fSizeCount; ++i) { + result += "[" + (iter++)->description() + "]"; + } + if (iter != this->end()) { + result += " = " + (iter++)->description(); + SkASSERT(iter == this->end()); + } + return result; + } + case Kind::kPostfix: + return this->begin()->description() + Compiler::OperatorName(getToken().fKind); + case Kind::kPrefix: + return Compiler::OperatorName(getToken().fKind) + this->begin()->description(); + case Kind::kReturn: + if (this->begin() != this->end()) { + return "return " + this->begin()->description() + ";"; + } + return "return;"; + case Kind::kSection: + return "@section { ... }"; + case Kind::kSwitchCase: { + auto iter = this->begin(); + String result; + if (*iter) { + result.appendf("case %s:\n", iter->description().c_str()); + } else { + result = "default:\n"; + } + for (++iter; iter != this->end(); ++iter) { + result += "\n" + iter->description(); + } + return result; + } + case Kind::kSwitch: { + auto iter = this->begin(); + String result; + if (getBool()) { + result = "@"; + } + result += "switch (" + (iter++)->description() + ") {"; + for (; iter != this->end(); ++iter) { + result += iter->description() + "\n"; + } + result += "}"; + return result; + } + case Kind::kTernary: + return "(" + this->begin()->description() + " ? " + (this->begin() + 1)->description() + + " : " + (this->begin() + 2)->description() + ")"; + case Kind::kType: + return String(getTypeData().fName); + case Kind::kVarDeclaration: { + VarData vd = getVarData(); + String result = vd.fName; + auto iter = this->begin(); + for (size_t i = 0; i < vd.fSizeCount; ++i) { + result += "[" + (iter++)->description() + "]"; + } + if (iter != this->end()) { + result += " = " + (iter++)->description(); + SkASSERT(iter == this->end()); + } + return result; + } + case Kind::kVarDeclarations: { + auto iter = this->begin(); + String result = (iter++)->description(); + if (result.size()) { + result += " "; + } + result += (iter++)->description(); + const char* separator = " "; + for (; iter != this->end(); ++iter) { + result += separator + iter->description(); + separator = ", "; + } + return result; + } + default: + SkASSERT(false); + return ""; + } +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLASTNode.h b/gfx/skia/skia/src/sksl/SkSLASTNode.h new file mode 100644 index 0000000000..cdb741b6f2 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLASTNode.h @@ -0,0 +1,635 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_ASTNODE +#define SKSL_ASTNODE + +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/SkSLString.h" +#include "src/sksl/ir/SkSLModifiers.h" + +#include + +namespace SkSL { + +// std::max isn't constexpr in some compilers +static constexpr size_t Max(size_t a, size_t b) { + return a > b ? a : b; +} + +/** + * Represents a node in the abstract syntax tree (AST). The AST is based directly on the parse tree; + * it is a parsed-but-not-yet-analyzed version of the program. + */ +struct ASTNode { + class ID { + public: + static ID Invalid() { + return ID(); + } + + bool operator==(const ID& other) { + return fValue == other.fValue; + } + + bool operator!=(const ID& other) { + return fValue != other.fValue; + } + + MOZ_IMPLICIT operator bool() const { return fValue >= 0; } + + private: + ID() + : fValue(-1) {} + + ID(int value) + : fValue(value) {} + + int fValue; + + friend struct ASTFile; + friend struct ASTNode; + friend class Parser; + }; + + enum class Kind { + // data: operator(Token), children: left, right + kBinary, + // children: statements + kBlock, + // data: value(bool) + kBool, + kBreak, + // children: target, arg1, arg2... + kCall, + kContinue, + kDiscard, + // children: statement, test + kDo, + // data: name(StringFragment), children: enumCases + kEnum, + // data: name(StringFragment), children: value? + kEnumCase, + // data: name(StringFragment) + kExtension, + // data: field(StringFragment), children: base + kField, + // children: declarations + kFile, + // data: value(float) + kFloat, + // children: init, test, next, statement + kFor, + // data: FunctionData, children: returnType, parameters, statement? + kFunction, + // data: name(StringFragment) + kIdentifier, + // children: base, index? + kIndex, + // data: isStatic(bool), children: test, ifTrue, ifFalse? + kIf, + // value(data): int + kInt, + // data: InterfaceBlockData, children: declaration1, declaration2, ..., size1, size2, ... + kInterfaceBlock, + // data: Modifiers + kModifiers, + kNull, + // data: ParameterData, children: type, arraySize1, arraySize2, ..., value? + kParameter, + // data: operator(Token), children: operand + kPostfix, + // data: operator(Token), children: operand + kPrefix, + // children: value + kReturn, + // ... + kSection, + // children: value, statement 1, statement 2... + kSwitchCase, + // children: value, case 1, case 2... + kSwitch, + // children: test, ifTrue, ifFalse + kTernary, + // data: TypeData, children: sizes + kType, + // data: VarData, children: arraySize1, arraySize2, ..., value? + kVarDeclaration, + // children: modifiers, type, varDeclaration1, varDeclaration2, ... + kVarDeclarations, + // children: test, statement + kWhile, + }; + + class iterator { + public: + iterator operator++() { + SkASSERT(fID); + fID = (**this).fNext; + return *this; + } + + iterator operator++(int) { + SkASSERT(fID); + iterator old = *this; + fID = (**this).fNext; + return old; + } + + iterator operator+=(int count) { + SkASSERT(count >= 0); + for (; count > 0; --count) { + ++(*this); + } + return *this; + } + + iterator operator+(int count) { + iterator result(*this); + return result += count; + } + + bool operator==(const iterator& other) const { + return fID == other.fID; + } + + bool operator!=(const iterator& other) const { + return fID != other.fID; + } + + ASTNode& operator*() { + SkASSERT(fID); + return (*fNodes)[fID.fValue]; + } + + ASTNode* operator->() { + SkASSERT(fID); + return &(*fNodes)[fID.fValue]; + } + + private: + iterator(std::vector* nodes, ID id) + : fNodes(nodes) + , fID(id) {} + + std::vector* fNodes; + + ID fID; + + friend struct ASTNode; + }; + + struct TypeData { + TypeData() {} + + TypeData(StringFragment name, bool isStructDeclaration, bool isNullable) + : fName(name) + , fIsStructDeclaration(isStructDeclaration) + , fIsNullable(isNullable) {} + + StringFragment fName; + bool fIsStructDeclaration; + bool fIsNullable; + }; + + struct ParameterData { + ParameterData() {} + + ParameterData(Modifiers modifiers, StringFragment name, size_t sizeCount) + : fModifiers(modifiers) + , fName(name) + , fSizeCount(sizeCount) {} + + Modifiers fModifiers; + StringFragment fName; + size_t fSizeCount; + }; + + struct VarData { + VarData() {} + + VarData(StringFragment name, size_t sizeCount) + : fName(name) + , fSizeCount(sizeCount) {} + + StringFragment fName; + size_t fSizeCount; + }; + + struct FunctionData { + FunctionData() {} + + FunctionData(Modifiers modifiers, StringFragment name, size_t parameterCount) + : fModifiers(modifiers) + , fName(name) + , fParameterCount(parameterCount) {} + + Modifiers fModifiers; + StringFragment fName; + size_t fParameterCount; + }; + + struct InterfaceBlockData { + InterfaceBlockData() {} + + InterfaceBlockData(Modifiers modifiers, StringFragment typeName, size_t declarationCount, + StringFragment instanceName, size_t sizeCount) + : fModifiers(modifiers) + , fTypeName(typeName) + , fDeclarationCount(declarationCount) + , fInstanceName(instanceName) + , fSizeCount(sizeCount) {} + + Modifiers fModifiers; + StringFragment fTypeName; + size_t fDeclarationCount; + StringFragment fInstanceName; + size_t fSizeCount; + }; + + struct SectionData { + SectionData() {} + + SectionData(StringFragment name, StringFragment argument, StringFragment text) + : fName(name) + , fArgument(argument) + , fText(text) {} + + StringFragment fName; + StringFragment fArgument; + StringFragment fText; + }; + + struct NodeData { + char fBytes[Max(sizeof(Token), + Max(sizeof(StringFragment), + Max(sizeof(bool), + Max(sizeof(SKSL_INT), + Max(sizeof(SKSL_FLOAT), + Max(sizeof(Modifiers), + Max(sizeof(TypeData), + Max(sizeof(FunctionData), + Max(sizeof(ParameterData), + Max(sizeof(VarData), + Max(sizeof(InterfaceBlockData), + sizeof(SectionData))))))))))))]; + + enum class Kind { + kToken, + kStringFragment, + kBool, + kInt, + kFloat, + kModifiers, + kTypeData, + kFunctionData, + kParameterData, + kVarData, + kInterfaceBlockData, + kSectionData + } fKind; + + NodeData() = default; + + NodeData(Token data) + : fKind(Kind::kToken) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(StringFragment data) + : fKind(Kind::kStringFragment) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(bool data) + : fKind(Kind::kBool) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(SKSL_INT data) + : fKind(Kind::kInt) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(SKSL_FLOAT data) + : fKind(Kind::kFloat) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(Modifiers data) + : fKind(Kind::kModifiers) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(TypeData data) + : fKind(Kind::kTypeData) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(FunctionData data) + : fKind(Kind::kFunctionData) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(VarData data) + : fKind(Kind::kVarData) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(ParameterData data) + : fKind(Kind::kParameterData) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(InterfaceBlockData data) + : fKind(Kind::kInterfaceBlockData) { + memcpy(fBytes, &data, sizeof(data)); + } + + NodeData(SectionData data) + : fKind(Kind::kSectionData) { + memcpy(fBytes, &data, sizeof(data)); + } + }; + + ASTNode() + : fOffset(-1) + , fKind(Kind::kNull) {} + + ASTNode(std::vector* nodes, int offset, Kind kind) + : fNodes(nodes) + , fOffset(offset) + , fKind(kind) { + switch (kind) { + case Kind::kBinary: + case Kind::kPostfix: + case Kind::kPrefix: + fData.fKind = NodeData::Kind::kToken; + break; + + case Kind::kBool: + case Kind::kIf: + case Kind::kSwitch: + fData.fKind = NodeData::Kind::kBool; + break; + + case Kind::kEnum: + case Kind::kEnumCase: + case Kind::kExtension: + case Kind::kField: + case Kind::kIdentifier: + fData.fKind = NodeData::Kind::kStringFragment; + break; + + case Kind::kFloat: + fData.fKind = NodeData::Kind::kFloat; + break; + + case Kind::kFunction: + fData.fKind = NodeData::Kind::kFunctionData; + break; + + case Kind::kInt: + fData.fKind = NodeData::Kind::kInt; + break; + + case Kind::kInterfaceBlock: + fData.fKind = NodeData::Kind::kInterfaceBlockData; + break; + + case Kind::kModifiers: + fData.fKind = NodeData::Kind::kModifiers; + break; + + case Kind::kParameter: + fData.fKind = NodeData::Kind::kParameterData; + break; + + case Kind::kVarDeclaration: + fData.fKind = NodeData::Kind::kVarData; + break; + + case Kind::kType: + fData.fKind = NodeData::Kind::kTypeData; + break; + + default: + break; + } + } + + ASTNode(std::vector* nodes, int offset, Kind kind, Token t) + : fNodes(nodes) + , fData(t) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, StringFragment s) + : fNodes(nodes) + , fData(s) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, const char* s) + : fNodes(nodes) + , fData(StringFragment(s)) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, bool b) + : fNodes(nodes) + , fData(b) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, SKSL_INT i) + : fNodes(nodes) + , fData(i) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, SKSL_FLOAT f) + : fNodes(nodes) + , fData(f) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, Modifiers m) + : fNodes(nodes) + , fData(m) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, TypeData td) + : fNodes(nodes) + , fData(td) + , fOffset(offset) + , fKind(kind) {} + + ASTNode(std::vector* nodes, int offset, Kind kind, SectionData s) + : fNodes(nodes) + , fData(s) + , fOffset(offset) + , fKind(kind) {} + + MOZ_IMPLICIT operator bool() const { + return fKind != Kind::kNull; + } + + Token getToken() const { + SkASSERT(fData.fKind == NodeData::Kind::kToken); + Token result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + bool getBool() const { + SkASSERT(fData.fKind == NodeData::Kind::kBool); + bool result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + SKSL_INT getInt() const { + SkASSERT(fData.fKind == NodeData::Kind::kInt); + SKSL_INT result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + SKSL_FLOAT getFloat() const { + SkASSERT(fData.fKind == NodeData::Kind::kFloat); + SKSL_FLOAT result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + StringFragment getString() const { + SkASSERT(fData.fKind == NodeData::Kind::kStringFragment); + StringFragment result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + Modifiers getModifiers() const { + SkASSERT(fData.fKind == NodeData::Kind::kModifiers); + Modifiers result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void setModifiers(const Modifiers& m) { + memcpy(fData.fBytes, &m, sizeof(m)); + } + + TypeData getTypeData() const { + SkASSERT(fData.fKind == NodeData::Kind::kTypeData); + TypeData result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void setTypeData(const ASTNode::TypeData& td) { + SkASSERT(fData.fKind == NodeData::Kind::kTypeData); + memcpy(fData.fBytes, &td, sizeof(td)); + } + + ParameterData getParameterData() const { + SkASSERT(fData.fKind == NodeData::Kind::kParameterData); + ParameterData result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void setParameterData(const ASTNode::ParameterData& pd) { + SkASSERT(fData.fKind == NodeData::Kind::kParameterData); + memcpy(fData.fBytes, &pd, sizeof(pd)); + } + + VarData getVarData() const { + SkASSERT(fData.fKind == NodeData::Kind::kVarData); + VarData result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void setVarData(const ASTNode::VarData& vd) { + SkASSERT(fData.fKind == NodeData::Kind::kVarData); + memcpy(fData.fBytes, &vd, sizeof(vd)); + } + + FunctionData getFunctionData() const { + SkASSERT(fData.fKind == NodeData::Kind::kFunctionData); + FunctionData result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void setFunctionData(const ASTNode::FunctionData& fd) { + SkASSERT(fData.fKind == NodeData::Kind::kFunctionData); + memcpy(fData.fBytes, &fd, sizeof(fd)); + } + + InterfaceBlockData getInterfaceBlockData() const { + SkASSERT(fData.fKind == NodeData::Kind::kInterfaceBlockData); + InterfaceBlockData result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void setInterfaceBlockData(const ASTNode::InterfaceBlockData& id) { + SkASSERT(fData.fKind == NodeData::Kind::kInterfaceBlockData); + memcpy(fData.fBytes, &id, sizeof(id)); + } + + SectionData getSectionData() const { + SkASSERT(fData.fKind == NodeData::Kind::kSectionData); + SectionData result; + memcpy(&result, fData.fBytes, sizeof(result)); + return result; + } + + void addChild(ID id) { + SkASSERT(!(*fNodes)[id.fValue].fNext); + if (fLastChild) { + SkASSERT(!(*fNodes)[fLastChild.fValue].fNext); + (*fNodes)[fLastChild.fValue].fNext = id; + } else { + fFirstChild = id; + } + fLastChild = id; + SkASSERT(!(*fNodes)[fLastChild.fValue].fNext); + } + + iterator begin() const { + return iterator(fNodes, fFirstChild); + } + + iterator end() const { + return iterator(fNodes, ID(-1)); + } + + String description() const; + + std::vector* fNodes; + + NodeData fData; + + int fOffset; + + Kind fKind; + + ID fFirstChild; + + ID fLastChild; + + ID fNext; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLByteCode.cpp b/gfx/skia/skia/src/sksl/SkSLByteCode.cpp new file mode 100644 index 0000000000..b03e2ee328 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLByteCode.cpp @@ -0,0 +1,1760 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_STANDALONE + +#include "include/core/SkPoint3.h" +#include "include/private/SkVx.h" +#include "src/core/SkUtils.h" // sk_unaligned_load +#include "src/sksl/SkSLByteCode.h" +#include "src/sksl/SkSLByteCodeGenerator.h" +#include "src/sksl/SkSLExternalValue.h" + +#include + +namespace SkSL { + +#if defined(SK_ENABLE_SKSL_INTERPRETER) + +constexpr int VecWidth = ByteCode::kVecWidth; + +struct Interpreter { + +using F32 = skvx::Vec; +using I32 = skvx::Vec; +using U32 = skvx::Vec; + +#define READ8() (*(ip++)) +#define READ16() (ip += 2, sk_unaligned_load(ip - 2)) +#define READ32() (ip += 4, sk_unaligned_load(ip - 4)) +#define READ_INST() (ip += sizeof(instruction), \ + sk_unaligned_load(ip - sizeof(instruction))) + +#define VECTOR_DISASSEMBLE(op, text) \ + case ByteCodeInstruction::op: printf(text); ++ip; break; \ + case ByteCodeInstruction::op##2: printf(text "2"); ++ip; break; \ + case ByteCodeInstruction::op##3: printf(text "3"); ++ip; break; \ + case ByteCodeInstruction::op##4: printf(text "4"); ++ip; break; + +#define VECTOR_DISASSEMBLE_NO_COUNT(op, text) \ + case ByteCodeInstruction::op: printf(text); break; \ + case ByteCodeInstruction::op##2: printf(text "2"); break; \ + case ByteCodeInstruction::op##3: printf(text "3"); break; \ + case ByteCodeInstruction::op##4: printf(text "4"); break; + +#define VECTOR_MATRIX_DISASSEMBLE(op, text) \ + VECTOR_DISASSEMBLE(op, text) \ + case ByteCodeInstruction::op##N: printf(text "N %d", READ8()); break; + +#define VECTOR_MATRIX_DISASSEMBLE_NO_COUNT(op, text) \ + VECTOR_DISASSEMBLE_NO_COUNT(op, text) \ + case ByteCodeInstruction::op##N: printf(text "N %d", READ8()); break; + +static const uint8_t* DisassembleInstruction(const uint8_t* ip) { + switch ((ByteCodeInstruction) (intptr_t) READ_INST()) { + VECTOR_MATRIX_DISASSEMBLE(kAddF, "addf") + VECTOR_DISASSEMBLE(kAddI, "addi") + case ByteCodeInstruction::kAndB: printf("andb"); break; + case ByteCodeInstruction::kBranch: printf("branch %d", READ16()); break; + case ByteCodeInstruction::kCall: printf("call %d", READ8()); break; + case ByteCodeInstruction::kCallExternal: { + int argumentCount = READ8(); + int returnCount = READ8(); + int externalValue = READ8(); + printf("callexternal %d, %d, %d", argumentCount, returnCount, externalValue); + break; + } + case ByteCodeInstruction::kClampIndex: printf("clampindex %d", READ8()); break; + VECTOR_DISASSEMBLE(kCompareIEQ, "compareieq") + VECTOR_DISASSEMBLE(kCompareINEQ, "compareineq") + VECTOR_MATRIX_DISASSEMBLE(kCompareFEQ, "comparefeq") + VECTOR_MATRIX_DISASSEMBLE(kCompareFNEQ, "comparefneq") + VECTOR_DISASSEMBLE(kCompareFGT, "comparefgt") + VECTOR_DISASSEMBLE(kCompareFGTEQ, "comparefgteq") + VECTOR_DISASSEMBLE(kCompareFLT, "compareflt") + VECTOR_DISASSEMBLE(kCompareFLTEQ, "compareflteq") + VECTOR_DISASSEMBLE(kCompareSGT, "comparesgt") + VECTOR_DISASSEMBLE(kCompareSGTEQ, "comparesgteq") + VECTOR_DISASSEMBLE(kCompareSLT, "compareslt") + VECTOR_DISASSEMBLE(kCompareSLTEQ, "compareslteq") + VECTOR_DISASSEMBLE(kCompareUGT, "compareugt") + VECTOR_DISASSEMBLE(kCompareUGTEQ, "compareugteq") + VECTOR_DISASSEMBLE(kCompareULT, "compareult") + VECTOR_DISASSEMBLE(kCompareULTEQ, "compareulteq") + VECTOR_DISASSEMBLE_NO_COUNT(kConvertFtoI, "convertftoi") + VECTOR_DISASSEMBLE_NO_COUNT(kConvertStoF, "convertstof") + VECTOR_DISASSEMBLE_NO_COUNT(kConvertUtoF, "convertutof") + VECTOR_DISASSEMBLE(kCos, "cos") + VECTOR_MATRIX_DISASSEMBLE(kDivideF, "dividef") + VECTOR_DISASSEMBLE(kDivideS, "divideS") + VECTOR_DISASSEMBLE(kDivideU, "divideu") + VECTOR_MATRIX_DISASSEMBLE(kDup, "dup") + case ByteCodeInstruction::kInverse2x2: printf("inverse2x2"); break; + case ByteCodeInstruction::kInverse3x3: printf("inverse3x3"); break; + case ByteCodeInstruction::kInverse4x4: printf("inverse4x4"); break; + case ByteCodeInstruction::kLoad: printf("load %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoad2: printf("load2 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoad3: printf("load3 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoad4: printf("load4 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadGlobal: printf("loadglobal %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadGlobal2: printf("loadglobal2 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadGlobal3: printf("loadglobal3 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadGlobal4: printf("loadglobal4 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadUniform: printf("loaduniform %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadUniform2: printf("loaduniform2 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadUniform3: printf("loaduniform3 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadUniform4: printf("loaduniform4 %d", READ16() >> 8); break; + case ByteCodeInstruction::kLoadSwizzle: { + int target = READ8(); + int count = READ8(); + printf("loadswizzle %d %d", target, count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kLoadSwizzleGlobal: { + int target = READ8(); + int count = READ8(); + printf("loadswizzleglobal %d %d", target, count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kLoadSwizzleUniform: { + int target = READ8(); + int count = READ8(); + printf("loadswizzleuniform %d %d", target, count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kLoadExtended: printf("loadextended %d", READ8()); break; + case ByteCodeInstruction::kLoadExtendedGlobal: printf("loadextendedglobal %d", READ8()); + break; + case ByteCodeInstruction::kLoadExtendedUniform: printf("loadextendeduniform %d", READ8()); + break; + case ByteCodeInstruction::kMatrixToMatrix: { + int srcCols = READ8(); + int srcRows = READ8(); + int dstCols = READ8(); + int dstRows = READ8(); + printf("matrixtomatrix %dx%d %dx%d", srcCols, srcRows, dstCols, dstRows); + break; + } + case ByteCodeInstruction::kMatrixMultiply: { + int lCols = READ8(); + int lRows = READ8(); + int rCols = READ8(); + printf("matrixmultiply %dx%d %dx%d", lCols, lRows, rCols, lCols); + break; + } + VECTOR_MATRIX_DISASSEMBLE(kMultiplyF, "multiplyf") + VECTOR_DISASSEMBLE(kMultiplyI, "multiplyi") + VECTOR_MATRIX_DISASSEMBLE_NO_COUNT(kNegateF, "negatef") + VECTOR_DISASSEMBLE_NO_COUNT(kNegateI, "negatei") + case ByteCodeInstruction::kNotB: printf("notb"); break; + case ByteCodeInstruction::kOrB: printf("orb"); break; + VECTOR_MATRIX_DISASSEMBLE_NO_COUNT(kPop, "pop") + case ByteCodeInstruction::kPushImmediate: { + uint32_t v = READ32(); + union { uint32_t u; float f; } pun = { v }; + printf("pushimmediate %s", (to_string(v) + "(" + to_string(pun.f) + ")").c_str()); + break; + } + case ByteCodeInstruction::kReadExternal: printf("readexternal %d", READ16() >> 8); break; + case ByteCodeInstruction::kReadExternal2: printf("readexternal2 %d", READ16() >> 8); break; + case ByteCodeInstruction::kReadExternal3: printf("readexternal3 %d", READ16() >> 8); break; + case ByteCodeInstruction::kReadExternal4: printf("readexternal4 %d", READ16() >> 8); break; + VECTOR_DISASSEMBLE(kRemainderF, "remainderf") + VECTOR_DISASSEMBLE(kRemainderS, "remainders") + VECTOR_DISASSEMBLE(kRemainderU, "remainderu") + case ByteCodeInstruction::kReserve: printf("reserve %d", READ8()); break; + case ByteCodeInstruction::kReturn: printf("return %d", READ8()); break; + case ByteCodeInstruction::kScalarToMatrix: { + int cols = READ8(); + int rows = READ8(); + printf("scalartomatrix %dx%d", cols, rows); + break; + } + case ByteCodeInstruction::kShiftLeft: printf("shl %d", READ8()); break; + case ByteCodeInstruction::kShiftRightS: printf("shrs %d", READ8()); break; + case ByteCodeInstruction::kShiftRightU: printf("shru %d", READ8()); break; + VECTOR_DISASSEMBLE(kSin, "sin") + VECTOR_DISASSEMBLE_NO_COUNT(kSqrt, "sqrt") + case ByteCodeInstruction::kStore: printf("store %d", READ8()); break; + case ByteCodeInstruction::kStore2: printf("store2 %d", READ8()); break; + case ByteCodeInstruction::kStore3: printf("store3 %d", READ8()); break; + case ByteCodeInstruction::kStore4: printf("store4 %d", READ8()); break; + case ByteCodeInstruction::kStoreGlobal: printf("storeglobal %d", READ8()); break; + case ByteCodeInstruction::kStoreGlobal2: printf("storeglobal2 %d", READ8()); break; + case ByteCodeInstruction::kStoreGlobal3: printf("storeglobal3 %d", READ8()); break; + case ByteCodeInstruction::kStoreGlobal4: printf("storeglobal4 %d", READ8()); break; + case ByteCodeInstruction::kStoreSwizzle: { + int target = READ8(); + int count = READ8(); + printf("storeswizzle %d %d", target, count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kStoreSwizzleGlobal: { + int target = READ8(); + int count = READ8(); + printf("storeswizzleglobal %d %d", target, count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kStoreSwizzleIndirect: { + int count = READ8(); + printf("storeswizzleindirect %d", count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: { + int count = READ8(); + printf("storeswizzleindirectglobal %d", count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + case ByteCodeInstruction::kStoreExtended: printf("storeextended %d", READ8()); break; + case ByteCodeInstruction::kStoreExtendedGlobal: printf("storeextendedglobal %d", READ8()); + break; + VECTOR_MATRIX_DISASSEMBLE(kSubtractF, "subtractf") + VECTOR_DISASSEMBLE(kSubtractI, "subtracti") + case ByteCodeInstruction::kSwizzle: { + printf("swizzle %d, ", READ8()); + int count = READ8(); + printf("%d", count); + for (int i = 0; i < count; ++i) { + printf(", %d", READ8()); + } + break; + } + VECTOR_DISASSEMBLE(kTan, "tan") + case ByteCodeInstruction::kWriteExternal: printf("writeexternal %d", READ16() >> 8); break; + case ByteCodeInstruction::kWriteExternal2: printf("writeexternal2 %d", READ16() >> 8); break; + case ByteCodeInstruction::kWriteExternal3: printf("writeexternal3 %d", READ16() >> 8); break; + case ByteCodeInstruction::kWriteExternal4: printf("writeexternal4 %d", READ16() >> 8); break; + case ByteCodeInstruction::kXorB: printf("xorb"); break; + case ByteCodeInstruction::kMaskPush: printf("maskpush"); break; + case ByteCodeInstruction::kMaskPop: printf("maskpop"); break; + case ByteCodeInstruction::kMaskNegate: printf("masknegate"); break; + case ByteCodeInstruction::kMaskBlend: printf("maskblend %d", READ8()); break; + case ByteCodeInstruction::kBranchIfAllFalse: + printf("branchifallfalse %d", READ16()); + break; + case ByteCodeInstruction::kLoopBegin: printf("loopbegin"); break; + case ByteCodeInstruction::kLoopNext: printf("loopnext"); break; + case ByteCodeInstruction::kLoopMask: printf("loopmask"); break; + case ByteCodeInstruction::kLoopEnd: printf("loopend"); break; + case ByteCodeInstruction::kLoopContinue: printf("loopcontinue"); break; + case ByteCodeInstruction::kLoopBreak: printf("loopbreak"); break; + default: + ip -= sizeof(instruction); + printf("unknown(%d)\n", (int) (intptr_t) READ_INST()); + SkASSERT(false); + } + return ip; +} + +#ifdef SKSLC_THREADED_CODE + #define LABEL(name) name: + #ifdef TRACE + #define NEXT() goto next + #else + #define NEXT() goto *READ_INST() + #endif +#else + #define LABEL(name) case ByteCodeInstruction::name: + #define NEXT() continue +#endif + +#define VECTOR_BINARY_OP(base, field, op) \ + LABEL(base ## 4) \ + sp[-4] = sp[-4].field op sp[0].field; \ + POP(); \ + /* fall through */ \ + LABEL(base ## 3) { \ + sp[-ip[0]] = sp[-ip[0]].field op sp[0].field; \ + POP(); \ + } /* fall through */ \ + LABEL(base ## 2) { \ + sp[-ip[0]] = sp[-ip[0]].field op sp[0].field; \ + POP(); \ + } /* fall through */ \ + LABEL(base) { \ + sp[-ip[0]] = sp[-ip[0]].field op sp[0].field; \ + POP(); \ + ++ip; \ + NEXT(); \ + } + +// A naive implementation of / or % using skvx operations will likely crash with a divide by zero +// in inactive vector lanesm, so we need to be sure to avoid masked-off lanes. +#define VECTOR_BINARY_MASKED_OP(base, field, op) \ + LABEL(base ## 4) \ + for (int i = 0; i < VecWidth; ++i) { \ + if (mask()[i]) { \ + sp[-4].field[i] op ## = sp[0].field[i]; \ + } \ + } \ + POP(); \ + /* fall through */ \ + LABEL(base ## 3) { \ + for (int i = 0; i < VecWidth; ++i) { \ + if (mask()[i]) { \ + sp[-ip[0]].field[i] op ## = sp[0].field[i]; \ + } \ + } \ + POP(); \ + } /* fall through */ \ + LABEL(base ## 2) { \ + for (int i = 0; i < VecWidth; ++i) { \ + if (mask()[i]) { \ + sp[-ip[0]].field[i] op ## = sp[0].field[i]; \ + } \ + } \ + POP(); \ + } /* fall through */ \ + LABEL(base) { \ + for (int i = 0; i < VecWidth; ++i) { \ + if (mask()[i]) { \ + sp[-ip[0]].field[i] op ## = sp[0].field[i]; \ + } \ + } \ + POP(); \ + ++ip; \ + NEXT(); \ + } + + +#define VECTOR_MATRIX_BINARY_OP(base, field, op) \ + VECTOR_BINARY_OP(base, field, op) \ + LABEL(base ## N) { \ + int count = READ8(); \ + for (int i = count; i > 0; --i) { \ + sp[-count] = sp[-count].field op sp[0].field; \ + POP(); \ + } \ + NEXT(); \ + } + +#define VECTOR_BINARY_FN(base, field, fn) \ + LABEL(base ## 4) \ + sp[-4] = fn(sp[-4].field, sp[0].field); \ + POP(); \ + /* fall through */ \ + LABEL(base ## 3) { \ + sp[-ip[0]] = fn(sp[-ip[0]].field, sp[0].field); \ + POP(); \ + } /* fall through */ \ + LABEL(base ## 2) { \ + sp[-ip[0]] = fn(sp[-ip[0]].field, sp[0].field); \ + POP(); \ + } /* fall through */ \ + LABEL(base) { \ + sp[-ip[0]] = fn(sp[-ip[0]].field, sp[0].field); \ + POP(); \ + ++ip; \ + NEXT(); \ + } + +#define VECTOR_UNARY_FN(base, fn, field) \ + LABEL(base ## 4) sp[-3] = fn(sp[-3].field); \ + LABEL(base ## 3) sp[-2] = fn(sp[-2].field); \ + LABEL(base ## 2) sp[-1] = fn(sp[-1].field); \ + LABEL(base) sp[ 0] = fn(sp[ 0].field); \ + NEXT(); + +#define VECTOR_UNARY_FN_VEC(base, fn) \ + LABEL(base ## 4) \ + LABEL(base ## 3) \ + LABEL(base ## 2) \ + LABEL(base) { \ + int count = READ8(); \ + float* v = (float*)sp - count + 1; \ + for (int i = VecWidth * count; i > 0; --i, ++v) { \ + *v = fn(*v); \ + } \ + NEXT(); \ + } + +#define VECTOR_LABELS(base) \ + &&base ## 4, \ + &&base ## 3, \ + &&base ## 2, \ + &&base + +#define VECTOR_MATRIX_LABELS(base) \ + VECTOR_LABELS(base), \ + &&base ## N + +// If you trip this assert, it means that the order of the opcodes listed in ByteCodeInstruction +// does not match the order of the opcodes listed in the 'labels' array in innerRun(). +#define CHECK_LABEL(name) \ + SkASSERT(labels[(int) ByteCodeInstruction::name] == &&name) + +#define CHECK_VECTOR_LABELS(name) \ + CHECK_LABEL(name ## 4); \ + CHECK_LABEL(name ## 3); \ + CHECK_LABEL(name ## 2); \ + CHECK_LABEL(name) + +#define CHECK_VECTOR_MATRIX_LABELS(name) \ + CHECK_VECTOR_LABELS(name); \ + CHECK_LABEL(name ## N) + +union VValue { + VValue() {} + VValue(F32 f) : fFloat(f) {} + VValue(I32 s) : fSigned(s) {} + VValue(U32 u) : fUnsigned(u) {} + + F32 fFloat; + I32 fSigned; + U32 fUnsigned; +}; + +struct StackFrame { + const uint8_t* fCode; + const uint8_t* fIP; + VValue* fStack; + int fParameterCount; +}; + +static F32 VecMod(F32 a, F32 b) { + return a - skvx::trunc(a / b) * b; +} + +#define spf(index) sp[index].fFloat + +static void CallExternal(const ByteCode* byteCode, const uint8_t*& ip, VValue*& sp, + int baseIndex, I32 mask) { + int argumentCount = READ8(); + int returnCount = READ8(); + int target = READ8(); + ExternalValue* v = byteCode->fExternalValues[target]; + sp -= argumentCount - 1; + + float tmpArgs[4]; + float tmpReturn[4]; + SkASSERT(argumentCount <= (int)SK_ARRAY_COUNT(tmpArgs)); + SkASSERT(returnCount <= (int)SK_ARRAY_COUNT(tmpReturn)); + + for (int i = 0; i < VecWidth; ++i) { + if (mask[i]) { + for (int j = 0; j < argumentCount; ++j) { + tmpArgs[j] = sp[j].fFloat[i]; + } + v->call(baseIndex + i, tmpArgs, tmpReturn); + for (int j = 0; j < returnCount; ++j) { + sp[j].fFloat[i] = tmpReturn[j]; + } + } + } + sp += returnCount - 1; +} + +static void Inverse2x2(VValue* sp) { + F32 a = sp[-3].fFloat, + b = sp[-2].fFloat, + c = sp[-1].fFloat, + d = sp[ 0].fFloat; + F32 idet = F32(1) / (a*d - b*c); + sp[-3].fFloat = d * idet; + sp[-2].fFloat = -b * idet; + sp[-1].fFloat = -c * idet; + sp[ 0].fFloat = a * idet; +} + +static void Inverse3x3(VValue* sp) { + F32 a11 = sp[-8].fFloat, a12 = sp[-5].fFloat, a13 = sp[-2].fFloat, + a21 = sp[-7].fFloat, a22 = sp[-4].fFloat, a23 = sp[-1].fFloat, + a31 = sp[-6].fFloat, a32 = sp[-3].fFloat, a33 = sp[ 0].fFloat; + F32 idet = F32(1) / (a11 * a22 * a33 + a12 * a23 * a31 + a13 * a21 * a32 - + a11 * a23 * a32 - a12 * a21 * a33 - a13 * a22 * a31); + sp[-8].fFloat = (a22 * a33 - a23 * a32) * idet; + sp[-7].fFloat = (a23 * a31 - a21 * a33) * idet; + sp[-6].fFloat = (a21 * a32 - a22 * a31) * idet; + sp[-5].fFloat = (a13 * a32 - a12 * a33) * idet; + sp[-4].fFloat = (a11 * a33 - a13 * a31) * idet; + sp[-3].fFloat = (a12 * a31 - a11 * a32) * idet; + sp[-2].fFloat = (a12 * a23 - a13 * a22) * idet; + sp[-1].fFloat = (a13 * a21 - a11 * a23) * idet; + sp[ 0].fFloat = (a11 * a22 - a12 * a21) * idet; +} + +static void Inverse4x4(VValue* sp) { + F32 a00 = spf(-15), a10 = spf(-11), a20 = spf( -7), a30 = spf( -3), + a01 = spf(-14), a11 = spf(-10), a21 = spf( -6), a31 = spf( -2), + a02 = spf(-13), a12 = spf( -9), a22 = spf( -5), a32 = spf( -1), + a03 = spf(-12), a13 = spf( -8), a23 = spf( -4), a33 = spf( 0); + + F32 b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32; + + F32 idet = F32(1) / + (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06); + + b00 *= idet; + b01 *= idet; + b02 *= idet; + b03 *= idet; + b04 *= idet; + b05 *= idet; + b06 *= idet; + b07 *= idet; + b08 *= idet; + b09 *= idet; + b10 *= idet; + b11 *= idet; + + spf(-15) = a11 * b11 - a12 * b10 + a13 * b09; + spf(-14) = a02 * b10 - a01 * b11 - a03 * b09; + spf(-13) = a31 * b05 - a32 * b04 + a33 * b03; + spf(-12) = a22 * b04 - a21 * b05 - a23 * b03; + spf(-11) = a12 * b08 - a10 * b11 - a13 * b07; + spf(-10) = a00 * b11 - a02 * b08 + a03 * b07; + spf( -9) = a32 * b02 - a30 * b05 - a33 * b01; + spf( -8) = a20 * b05 - a22 * b02 + a23 * b01; + spf( -7) = a10 * b10 - a11 * b08 + a13 * b06; + spf( -6) = a01 * b08 - a00 * b10 - a03 * b06; + spf( -5) = a30 * b04 - a31 * b02 + a33 * b00; + spf( -4) = a21 * b02 - a20 * b04 - a23 * b00; + spf( -3) = a11 * b07 - a10 * b09 - a12 * b06; + spf( -2) = a00 * b09 - a01 * b07 + a02 * b06; + spf( -1) = a31 * b01 - a30 * b03 - a32 * b00; + spf( 0) = a20 * b03 - a21 * b01 + a22 * b00; +} + +static bool InnerRun(const ByteCode* byteCode, const ByteCodeFunction* f, VValue* stack, + float* outReturn[], VValue globals[], const float uniforms[], + bool stripedOutput, int N, int baseIndex) { +#ifdef SKSLC_THREADED_CODE + static const void* labels[] = { + // If you aren't familiar with it, the &&label syntax is the GCC / Clang "labels as values" + // extension. If you add anything to this array, be sure to add the corresponding + // CHECK_LABEL() or CHECK_*_LABELS() assert below. + VECTOR_MATRIX_LABELS(kAddF), + VECTOR_LABELS(kAddI), + &&kAndB, + &&kBranch, + &&kCall, + &&kCallExternal, + &&kClampIndex, + VECTOR_LABELS(kCompareIEQ), + VECTOR_LABELS(kCompareINEQ), + VECTOR_MATRIX_LABELS(kCompareFEQ), + VECTOR_MATRIX_LABELS(kCompareFNEQ), + VECTOR_LABELS(kCompareFGT), + VECTOR_LABELS(kCompareFGTEQ), + VECTOR_LABELS(kCompareFLT), + VECTOR_LABELS(kCompareFLTEQ), + VECTOR_LABELS(kCompareSGT), + VECTOR_LABELS(kCompareSGTEQ), + VECTOR_LABELS(kCompareSLT), + VECTOR_LABELS(kCompareSLTEQ), + VECTOR_LABELS(kCompareUGT), + VECTOR_LABELS(kCompareUGTEQ), + VECTOR_LABELS(kCompareULT), + VECTOR_LABELS(kCompareULTEQ), + VECTOR_LABELS(kConvertFtoI), + VECTOR_LABELS(kConvertStoF), + VECTOR_LABELS(kConvertUtoF), + VECTOR_LABELS(kCos), + VECTOR_MATRIX_LABELS(kDivideF), + VECTOR_LABELS(kDivideS), + VECTOR_LABELS(kDivideU), + VECTOR_MATRIX_LABELS(kDup), + &&kInverse2x2, + &&kInverse3x3, + &&kInverse4x4, + VECTOR_LABELS(kLoad), + VECTOR_LABELS(kLoadGlobal), + VECTOR_LABELS(kLoadUniform), + &&kLoadSwizzle, + &&kLoadSwizzleGlobal, + &&kLoadSwizzleUniform, + &&kLoadExtended, + &&kLoadExtendedGlobal, + &&kLoadExtendedUniform, + &&kMatrixToMatrix, + &&kMatrixMultiply, + VECTOR_MATRIX_LABELS(kNegateF), + VECTOR_LABELS(kNegateI), + VECTOR_MATRIX_LABELS(kMultiplyF), + VECTOR_LABELS(kMultiplyI), + &&kNotB, + &&kOrB, + VECTOR_MATRIX_LABELS(kPop), + &&kPushImmediate, + VECTOR_LABELS(kReadExternal), + VECTOR_LABELS(kRemainderF), + VECTOR_LABELS(kRemainderS), + VECTOR_LABELS(kRemainderU), + &&kReserve, + &&kReturn, + &&kScalarToMatrix, + &&kShiftLeft, + &&kShiftRightS, + &&kShiftRightU, + VECTOR_LABELS(kSin), + VECTOR_LABELS(kSqrt), + VECTOR_LABELS(kStore), + VECTOR_LABELS(kStoreGlobal), + &&kStoreExtended, + &&kStoreExtendedGlobal, + &&kStoreSwizzle, + &&kStoreSwizzleGlobal, + &&kStoreSwizzleIndirect, + &&kStoreSwizzleIndirectGlobal, + &&kSwizzle, + VECTOR_MATRIX_LABELS(kSubtractF), + VECTOR_LABELS(kSubtractI), + VECTOR_LABELS(kTan), + VECTOR_LABELS(kWriteExternal), + &&kXorB, + + &&kMaskPush, + &&kMaskPop, + &&kMaskNegate, + &&kMaskBlend, + &&kBranchIfAllFalse, + + &&kLoopBegin, + &&kLoopNext, + &&kLoopMask, + &&kLoopEnd, + &&kLoopBreak, + &&kLoopContinue, + }; + // Verify that the order of the labels array matches the order of the ByteCodeInstruction enum. + CHECK_VECTOR_MATRIX_LABELS(kAddF); + CHECK_VECTOR_LABELS(kAddI); + CHECK_LABEL(kAndB); + CHECK_LABEL(kBranch); + CHECK_LABEL(kCall); + CHECK_LABEL(kCallExternal); + CHECK_LABEL(kClampIndex); + CHECK_VECTOR_LABELS(kCompareIEQ); + CHECK_VECTOR_LABELS(kCompareINEQ); + CHECK_VECTOR_MATRIX_LABELS(kCompareFEQ); + CHECK_VECTOR_MATRIX_LABELS(kCompareFNEQ); + CHECK_VECTOR_LABELS(kCompareFGT); + CHECK_VECTOR_LABELS(kCompareFGTEQ); + CHECK_VECTOR_LABELS(kCompareFLT); + CHECK_VECTOR_LABELS(kCompareFLTEQ); + CHECK_VECTOR_LABELS(kCompareSGT); + CHECK_VECTOR_LABELS(kCompareSGTEQ); + CHECK_VECTOR_LABELS(kCompareSLT); + CHECK_VECTOR_LABELS(kCompareSLTEQ); + CHECK_VECTOR_LABELS(kCompareUGT); + CHECK_VECTOR_LABELS(kCompareUGTEQ); + CHECK_VECTOR_LABELS(kCompareULT); + CHECK_VECTOR_LABELS(kCompareULTEQ); + CHECK_VECTOR_LABELS(kConvertFtoI); + CHECK_VECTOR_LABELS(kConvertStoF); + CHECK_VECTOR_LABELS(kConvertUtoF); + CHECK_VECTOR_LABELS(kCos); + CHECK_VECTOR_MATRIX_LABELS(kDivideF); + CHECK_VECTOR_LABELS(kDivideS); + CHECK_VECTOR_LABELS(kDivideU); + CHECK_VECTOR_MATRIX_LABELS(kDup); + CHECK_LABEL(kInverse2x2); + CHECK_LABEL(kInverse3x3); + CHECK_LABEL(kInverse4x4); + CHECK_VECTOR_LABELS(kLoad); + CHECK_VECTOR_LABELS(kLoadGlobal); + CHECK_VECTOR_LABELS(kLoadUniform); + CHECK_LABEL(kLoadSwizzle); + CHECK_LABEL(kLoadSwizzleGlobal); + CHECK_LABEL(kLoadSwizzleUniform); + CHECK_LABEL(kLoadExtended); + CHECK_LABEL(kLoadExtendedGlobal); + CHECK_LABEL(kLoadExtendedUniform); + CHECK_LABEL(kMatrixToMatrix); + CHECK_LABEL(kMatrixMultiply); + CHECK_VECTOR_MATRIX_LABELS(kNegateF); + CHECK_VECTOR_LABELS(kNegateI); + CHECK_VECTOR_MATRIX_LABELS(kMultiplyF); + CHECK_VECTOR_LABELS(kMultiplyI); + CHECK_LABEL(kNotB); + CHECK_LABEL(kOrB); + CHECK_VECTOR_MATRIX_LABELS(kPop); + CHECK_LABEL(kPushImmediate); + CHECK_VECTOR_LABELS(kReadExternal); + CHECK_VECTOR_LABELS(kRemainderF); + CHECK_VECTOR_LABELS(kRemainderS); + CHECK_VECTOR_LABELS(kRemainderU); + CHECK_LABEL(kReserve); + CHECK_LABEL(kReturn); + CHECK_LABEL(kScalarToMatrix); + CHECK_LABEL(kShiftLeft); + CHECK_LABEL(kShiftRightS); + CHECK_LABEL(kShiftRightU); + CHECK_VECTOR_LABELS(kSin); + CHECK_VECTOR_LABELS(kSqrt); + CHECK_VECTOR_LABELS(kStore); + CHECK_VECTOR_LABELS(kStoreGlobal); + CHECK_LABEL(kStoreExtended); + CHECK_LABEL(kStoreExtendedGlobal); + CHECK_LABEL(kStoreSwizzle); + CHECK_LABEL(kStoreSwizzleGlobal); + CHECK_LABEL(kStoreSwizzleIndirect); + CHECK_LABEL(kStoreSwizzleIndirectGlobal); + CHECK_LABEL(kSwizzle); + CHECK_VECTOR_MATRIX_LABELS(kSubtractF); + CHECK_VECTOR_LABELS(kSubtractI); + CHECK_VECTOR_LABELS(kTan); + CHECK_VECTOR_LABELS(kWriteExternal); + CHECK_LABEL(kXorB); + CHECK_LABEL(kMaskPush); + CHECK_LABEL(kMaskPop); + CHECK_LABEL(kMaskNegate); + CHECK_LABEL(kMaskBlend); + CHECK_LABEL(kBranchIfAllFalse); + CHECK_LABEL(kLoopBegin); + CHECK_LABEL(kLoopNext); + CHECK_LABEL(kLoopMask); + CHECK_LABEL(kLoopEnd); + CHECK_LABEL(kLoopBreak); + CHECK_LABEL(kLoopContinue); + f->fPreprocessOnce([f] { ((ByteCodeFunction*)f)->preprocess(labels); }); +#endif + + // Needs to be the first N non-negative integers, at least as large as VecWidth + static const Interpreter::I32 gLanes = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }; + + VValue* sp = stack + f->fParameterCount + f->fLocalCount - 1; + + #define POP() (*(sp--)) + #define PUSH(v) (sp[1] = v, ++sp) + + const uint8_t* code = f->fCode.data(); + const uint8_t* ip = code; + std::vector frames; + + I32 condStack[16]; // Independent condition masks + I32 maskStack[16]; // Combined masks (eg maskStack[0] & maskStack[1] & ...) + I32 contStack[16]; // Continue flags for loops + I32 loopStack[16]; // Loop execution masks + condStack[0] = maskStack[0] = (gLanes < N); + contStack[0] = I32( 0); + loopStack[0] = I32(~0); + I32* condPtr = condStack; + I32* maskPtr = maskStack; + I32* contPtr = contStack; + I32* loopPtr = loopStack; + + if (f->fConditionCount + 1 > (int)SK_ARRAY_COUNT(condStack) || + f->fLoopCount + 1 > (int)SK_ARRAY_COUNT(loopStack)) { + return false; + } + + auto mask = [&]() { return *maskPtr & *loopPtr; }; + +#ifdef SKSLC_THREADED_CODE + // If the "labels as values" extension is available, we implement this using threaded code. + // Instead of opcodes, the code directly contains the addresses of the labels to jump to. Then + // the code for each opcode simply grabs the address of the next opcode and uses a goto to jump + // there. + NEXT(); +#else + // Otherwise, we have to use a switch statement and a loop to execute the right label. + for (;;) { + #ifdef TRACE + printf("at %3d ", (int) (ip - code)); + disassemble_instruction(ip); + printf(" (stack: %d)\n", (int) (sp - stack) + 1); + #endif + switch ((ByteCodeInstruction) READ16()) { +#endif + + VECTOR_MATRIX_BINARY_OP(kAddF, fFloat, +) + VECTOR_BINARY_OP(kAddI, fSigned, +) + + // Booleans are integer masks: 0/~0 for false/true. So bitwise ops do what we want: + LABEL(kAndB) + sp[-1] = sp[-1].fSigned & sp[0].fSigned; + POP(); + NEXT(); + LABEL(kNotB) + sp[0] = ~sp[0].fSigned; + NEXT(); + LABEL(kOrB) + sp[-1] = sp[-1].fSigned | sp[0].fSigned; + POP(); + NEXT(); + LABEL(kXorB) + sp[-1] = sp[-1].fSigned ^ sp[0].fSigned; + POP(); + NEXT(); + + LABEL(kBranch) + ip = code + READ16(); + NEXT(); + + LABEL(kCall) { + // Precursor code reserved space for the return value, and pushed all parameters to + // the stack. Update our bottom of stack to point at the first parameter, and our + // sp to point past those parameters (plus space for locals). + int target = READ8(); + const ByteCodeFunction* fun = byteCode->fFunctions[target].get(); +#ifdef SKSLC_THREADED_CODE + fun->fPreprocessOnce([fun] { ((ByteCodeFunction*)fun)->preprocess(labels); }); +#endif + if (skvx::any(mask())) { + frames.push_back({ code, ip, stack, fun->fParameterCount }); + ip = code = fun->fCode.data(); + stack = sp - fun->fParameterCount + 1; + sp = stack + fun->fParameterCount + fun->fLocalCount - 1; + } + NEXT(); + } + + LABEL(kCallExternal) { + CallExternal(byteCode, ip, sp, baseIndex, mask()); + NEXT(); + } + + LABEL(kClampIndex) { + int length = READ8(); + if (skvx::any(mask() & ((sp[0].fSigned < 0) | (sp[0].fSigned >= length)))) { + return false; + } + NEXT(); + } + + VECTOR_BINARY_OP(kCompareIEQ, fSigned, ==) + VECTOR_MATRIX_BINARY_OP(kCompareFEQ, fFloat, ==) + VECTOR_BINARY_OP(kCompareINEQ, fSigned, !=) + VECTOR_MATRIX_BINARY_OP(kCompareFNEQ, fFloat, !=) + VECTOR_BINARY_OP(kCompareSGT, fSigned, >) + VECTOR_BINARY_OP(kCompareUGT, fUnsigned, >) + VECTOR_BINARY_OP(kCompareFGT, fFloat, >) + VECTOR_BINARY_OP(kCompareSGTEQ, fSigned, >=) + VECTOR_BINARY_OP(kCompareUGTEQ, fUnsigned, >=) + VECTOR_BINARY_OP(kCompareFGTEQ, fFloat, >=) + VECTOR_BINARY_OP(kCompareSLT, fSigned, <) + VECTOR_BINARY_OP(kCompareULT, fUnsigned, <) + VECTOR_BINARY_OP(kCompareFLT, fFloat, <) + VECTOR_BINARY_OP(kCompareSLTEQ, fSigned, <=) + VECTOR_BINARY_OP(kCompareULTEQ, fUnsigned, <=) + VECTOR_BINARY_OP(kCompareFLTEQ, fFloat, <=) + + LABEL(kConvertFtoI4) sp[-3] = skvx::cast(sp[-3].fFloat); + LABEL(kConvertFtoI3) sp[-2] = skvx::cast(sp[-2].fFloat); + LABEL(kConvertFtoI2) sp[-1] = skvx::cast(sp[-1].fFloat); + LABEL(kConvertFtoI) sp[ 0] = skvx::cast(sp[ 0].fFloat); + NEXT(); + + LABEL(kConvertStoF4) sp[-3] = skvx::cast(sp[-3].fSigned); + LABEL(kConvertStoF3) sp[-2] = skvx::cast(sp[-2].fSigned); + LABEL(kConvertStoF2) sp[-1] = skvx::cast(sp[-1].fSigned); + LABEL(kConvertStoF) sp[ 0] = skvx::cast(sp[ 0].fSigned); + NEXT(); + + LABEL(kConvertUtoF4) sp[-3] = skvx::cast(sp[-3].fUnsigned); + LABEL(kConvertUtoF3) sp[-2] = skvx::cast(sp[-2].fUnsigned); + LABEL(kConvertUtoF2) sp[-1] = skvx::cast(sp[-1].fUnsigned); + LABEL(kConvertUtoF) sp[ 0] = skvx::cast(sp[ 0].fUnsigned); + NEXT(); + + VECTOR_UNARY_FN_VEC(kCos, cosf) + + VECTOR_BINARY_MASKED_OP(kDivideS, fSigned, /) + VECTOR_BINARY_MASKED_OP(kDivideU, fUnsigned, /) + VECTOR_MATRIX_BINARY_OP(kDivideF, fFloat, /) + + LABEL(kDup4) PUSH(sp[1 - ip[0]]); + LABEL(kDup3) PUSH(sp[1 - ip[0]]); + LABEL(kDup2) PUSH(sp[1 - ip[0]]); + LABEL(kDup) PUSH(sp[1 - ip[0]]); + ++ip; + NEXT(); + + LABEL(kDupN) { + int count = READ8(); + memcpy(sp + 1, sp - count + 1, count * sizeof(VValue)); + sp += count; + NEXT(); + } + + LABEL(kInverse2x2) { + Inverse2x2(sp); + NEXT(); + } + LABEL(kInverse3x3) { + Inverse3x3(sp); + NEXT(); + } + LABEL(kInverse4x4) { + Inverse4x4(sp); + NEXT(); + } + + LABEL(kLoad4) sp[4] = stack[ip[1] + 3]; + LABEL(kLoad3) sp[3] = stack[ip[1] + 2]; + LABEL(kLoad2) sp[2] = stack[ip[1] + 1]; + LABEL(kLoad) sp[1] = stack[ip[1] + 0]; + sp += ip[0]; + ip += 2; + NEXT(); + + LABEL(kLoadGlobal4) sp[4] = globals[ip[1] + 3]; + LABEL(kLoadGlobal3) sp[3] = globals[ip[1] + 2]; + LABEL(kLoadGlobal2) sp[2] = globals[ip[1] + 1]; + LABEL(kLoadGlobal) sp[1] = globals[ip[1] + 0]; + sp += ip[0]; + ip += 2; + NEXT(); + + LABEL(kLoadUniform4) sp[4].fFloat = uniforms[ip[1] + 3]; + LABEL(kLoadUniform3) sp[3].fFloat = uniforms[ip[1] + 2]; + LABEL(kLoadUniform2) sp[2].fFloat = uniforms[ip[1] + 1]; + LABEL(kLoadUniform) sp[1].fFloat = uniforms[ip[1] + 0]; + sp += ip[0]; + ip += 2; + NEXT(); + + LABEL(kLoadExtended) { + int count = READ8(); + I32 src = POP().fSigned; + I32 m = mask(); + for (int i = 0; i < count; ++i) { + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + sp[i + 1].fSigned[j] = stack[src[j] + i].fSigned[j]; + } + } + } + sp += count; + NEXT(); + } + + LABEL(kLoadExtendedGlobal) { + int count = READ8(); + I32 src = POP().fSigned; + I32 m = mask(); + for (int i = 0; i < count; ++i) { + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + sp[i + 1].fSigned[j] = globals[src[j] + i].fSigned[j]; + } + } + } + sp += count; + NEXT(); + } + + LABEL(kLoadExtendedUniform) { + int count = READ8(); + I32 src = POP().fSigned; + I32 m = mask(); + for (int i = 0; i < count; ++i) { + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + sp[i + 1].fFloat[j] = uniforms[src[j] + i]; + } + } + } + sp += count; + NEXT(); + } + + LABEL(kLoadSwizzle) { + int src = READ8(); + int count = READ8(); + for (int i = 0; i < count; ++i) { + PUSH(stack[src + *(ip + i)]); + } + ip += count; + NEXT(); + } + + LABEL(kLoadSwizzleGlobal) { + int src = READ8(); + int count = READ8(); + for (int i = 0; i < count; ++i) { + PUSH(globals[src + *(ip + i)]); + } + ip += count; + NEXT(); + } + + LABEL(kLoadSwizzleUniform) { + int src = READ8(); + int count = READ8(); + for (int i = 0; i < count; ++i) { + PUSH(F32(uniforms[src + *(ip + i)])); + } + ip += count; + NEXT(); + } + + LABEL(kMatrixToMatrix) { + int srcCols = READ8(); + int srcRows = READ8(); + int dstCols = READ8(); + int dstRows = READ8(); + SkASSERT(srcCols >= 2 && srcCols <= 4); + SkASSERT(srcRows >= 2 && srcRows <= 4); + SkASSERT(dstCols >= 2 && dstCols <= 4); + SkASSERT(dstRows >= 2 && dstRows <= 4); + F32 tmp[16]; + memset(tmp, 0, sizeof(tmp)); + tmp[0] = tmp[5] = tmp[10] = tmp[15] = F32(1.0f); + for (int c = srcCols - 1; c >= 0; --c) { + for (int r = srcRows - 1; r >= 0; --r) { + tmp[c*4 + r] = POP().fFloat; + } + } + for (int c = 0; c < dstCols; ++c) { + for (int r = 0; r < dstRows; ++r) { + PUSH(tmp[c*4 + r]); + } + } + NEXT(); + } + + LABEL(kMatrixMultiply) { + int lCols = READ8(); + int lRows = READ8(); + int rCols = READ8(); + int rRows = lCols; + F32 tmp[16] = { 0.0f }; + F32* B = &(sp - (rCols * rRows) + 1)->fFloat; + F32* A = B - (lCols * lRows); + for (int c = 0; c < rCols; ++c) { + for (int r = 0; r < lRows; ++r) { + for (int j = 0; j < lCols; ++j) { + tmp[c*lRows + r] += A[j*lRows + r] * B[c*rRows + j]; + } + } + } + sp -= (lCols * lRows) + (rCols * rRows); + memcpy(sp + 1, tmp, rCols * lRows * sizeof(VValue)); + sp += (rCols * lRows); + NEXT(); + } + + VECTOR_BINARY_OP(kMultiplyI, fSigned, *) + VECTOR_MATRIX_BINARY_OP(kMultiplyF, fFloat, *) + + LABEL(kNegateF4) sp[-3] = -sp[-3].fFloat; + LABEL(kNegateF3) sp[-2] = -sp[-2].fFloat; + LABEL(kNegateF2) sp[-1] = -sp[-1].fFloat; + LABEL(kNegateF) sp[ 0] = -sp[ 0].fFloat; + NEXT(); + + LABEL(kNegateFN) { + int count = READ8(); + for (int i = count - 1; i >= 0; --i) { + sp[-i] = -sp[-i].fFloat; + } + NEXT(); + } + + LABEL(kNegateI4) sp[-3] = -sp[-3].fSigned; + LABEL(kNegateI3) sp[-2] = -sp[-2].fSigned; + LABEL(kNegateI2) sp[-1] = -sp[-1].fSigned; + LABEL(kNegateI) sp[ 0] = -sp[ 0].fSigned; + NEXT(); + + LABEL(kPop4) POP(); + LABEL(kPop3) POP(); + LABEL(kPop2) POP(); + LABEL(kPop) POP(); + NEXT(); + + LABEL(kPopN) + sp -= READ8(); + NEXT(); + + LABEL(kPushImmediate) + PUSH(U32(READ32())); + NEXT(); + + LABEL(kReadExternal) + LABEL(kReadExternal2) + LABEL(kReadExternal3) + LABEL(kReadExternal4) { + int count = READ8(); + int src = READ8(); + float tmp[4]; + I32 m = mask(); + for (int i = 0; i < VecWidth; ++i) { + if (m[i]) { + byteCode->fExternalValues[src]->read(baseIndex + i, tmp); + for (int j = 0; j < count; ++j) { + sp[j + 1].fFloat[i] = tmp[j]; + } + } + } + sp += count; + NEXT(); + } + + VECTOR_BINARY_FN(kRemainderF, fFloat, VecMod) + VECTOR_BINARY_MASKED_OP(kRemainderS, fSigned, %) + VECTOR_BINARY_MASKED_OP(kRemainderU, fUnsigned, %) + + LABEL(kReserve) + sp += READ8(); + NEXT(); + + LABEL(kReturn) { + int count = READ8(); + if (frames.empty()) { + if (outReturn) { + VValue* src = sp - count + 1; + if (stripedOutput) { + for (int i = 0; i < count; ++i) { + memcpy(outReturn[i], &src->fFloat, N * sizeof(float)); + ++src; + } + } else { + float* outPtr = outReturn[0]; + for (int i = 0; i < count; ++i) { + for (int j = 0; j < N; ++j) { + outPtr[count * j] = src->fFloat[j]; + } + ++outPtr; + ++src; + } + } + } + return true; + } else { + // When we were called, the caller reserved stack space for their copy of our + // return value, then 'stack' was positioned after that, where our parameters + // were placed. Copy our return values to their reserved area. + memcpy(stack - count, sp - count + 1, count * sizeof(VValue)); + + // Now move the stack pointer to the end of the passed-in parameters. This odd + // calling convention requires the caller to pop the arguments after calling, + // but allows them to store any out-parameters back during that unwinding. + // After that sequence finishes, the return value will be the top of the stack. + const StackFrame& frame(frames.back()); + sp = stack + frame.fParameterCount - 1; + stack = frame.fStack; + code = frame.fCode; + ip = frame.fIP; + frames.pop_back(); + NEXT(); + } + } + + LABEL(kScalarToMatrix) { + int cols = READ8(); + int rows = READ8(); + VValue v = POP(); + for (int c = 0; c < cols; ++c) { + for (int r = 0; r < rows; ++r) { + PUSH(c == r ? v : F32(0.0f)); + } + } + NEXT(); + } + + LABEL(kShiftLeft) + sp[0] = sp[0].fSigned << READ8(); + NEXT(); + LABEL(kShiftRightS) + sp[0] = sp[0].fSigned >> READ8(); + NEXT(); + LABEL(kShiftRightU) + sp[0] = sp[0].fUnsigned >> READ8(); + NEXT(); + + VECTOR_UNARY_FN_VEC(kSin, sinf) + VECTOR_UNARY_FN(kSqrt, skvx::sqrt, fFloat) + + LABEL(kStore4) + stack[*ip+3] = skvx::if_then_else(mask(), POP().fFloat, stack[*ip+3].fFloat); + LABEL(kStore3) + stack[*ip+2] = skvx::if_then_else(mask(), POP().fFloat, stack[*ip+2].fFloat); + LABEL(kStore2) + stack[*ip+1] = skvx::if_then_else(mask(), POP().fFloat, stack[*ip+1].fFloat); + LABEL(kStore) + stack[*ip+0] = skvx::if_then_else(mask(), POP().fFloat, stack[*ip+0].fFloat); + ++ip; + NEXT(); + + LABEL(kStoreGlobal4) + globals[*ip+3] = skvx::if_then_else(mask(), POP().fFloat, globals[*ip+3].fFloat); + LABEL(kStoreGlobal3) + globals[*ip+2] = skvx::if_then_else(mask(), POP().fFloat, globals[*ip+2].fFloat); + LABEL(kStoreGlobal2) + globals[*ip+1] = skvx::if_then_else(mask(), POP().fFloat, globals[*ip+1].fFloat); + LABEL(kStoreGlobal) + globals[*ip+0] = skvx::if_then_else(mask(), POP().fFloat, globals[*ip+0].fFloat); + ++ip; + NEXT(); + + LABEL(kStoreExtended) { + int count = READ8(); + I32 target = POP().fSigned; + VValue* src = sp - count + 1; + I32 m = mask(); + for (int i = 0; i < count; ++i) { + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + stack[target[j] + i].fSigned[j] = src[i].fSigned[j]; + } + } + } + sp -= count; + NEXT(); + } + LABEL(kStoreExtendedGlobal) { + int count = READ8(); + I32 target = POP().fSigned; + VValue* src = sp - count + 1; + I32 m = mask(); + for (int i = 0; i < count; ++i) { + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + globals[target[j] + i].fSigned[j] = src[i].fSigned[j]; + } + } + } + sp -= count; + NEXT(); + } + + LABEL(kStoreSwizzle) { + int target = READ8(); + int count = READ8(); + for (int i = count - 1; i >= 0; --i) { + stack[target + *(ip + i)] = skvx::if_then_else( + mask(), POP().fFloat, stack[target + *(ip + i)].fFloat); + } + ip += count; + NEXT(); + } + + LABEL(kStoreSwizzleGlobal) { + int target = READ8(); + int count = READ8(); + for (int i = count - 1; i >= 0; --i) { + globals[target + *(ip + i)] = skvx::if_then_else( + mask(), POP().fFloat, globals[target + *(ip + i)].fFloat); + } + ip += count; + NEXT(); + } + + LABEL(kStoreSwizzleIndirect) { + int count = READ8(); + I32 target = POP().fSigned; + I32 m = mask(); + for (int i = count - 1; i >= 0; --i) { + I32 v = POP().fSigned; + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + stack[target[j] + *(ip + i)].fSigned[j] = v[j]; + } + } + } + ip += count; + NEXT(); + } + + LABEL(kStoreSwizzleIndirectGlobal) { + int count = READ8(); + I32 target = POP().fSigned; + I32 m = mask(); + for (int i = count - 1; i >= 0; --i) { + I32 v = POP().fSigned; + for (int j = 0; j < VecWidth; ++j) { + if (m[j]) { + globals[target[j] + *(ip + i)].fSigned[j] = v[j]; + } + } + } + ip += count; + NEXT(); + } + + VECTOR_BINARY_OP(kSubtractI, fSigned, -) + VECTOR_MATRIX_BINARY_OP(kSubtractF, fFloat, -) + + LABEL(kSwizzle) { + VValue tmp[4]; + for (int i = READ8() - 1; i >= 0; --i) { + tmp[i] = POP(); + } + for (int i = READ8() - 1; i >= 0; --i) { + PUSH(tmp[READ8()]); + } + NEXT(); + } + + VECTOR_UNARY_FN_VEC(kTan, tanf) + + LABEL(kWriteExternal4) + LABEL(kWriteExternal3) + LABEL(kWriteExternal2) + LABEL(kWriteExternal) { + int count = READ8(); + int target = READ8(); + float tmp[4]; + I32 m = mask(); + sp -= count; + for (int i = 0; i < VecWidth; ++i) { + if (m[i]) { + for (int j = 0; j < count; ++j) { + tmp[j] = sp[j + 1].fFloat[i]; + } + byteCode->fExternalValues[target]->write(baseIndex + i, tmp); + } + } + NEXT(); + } + + LABEL(kMaskPush) + condPtr[1] = POP().fSigned; + maskPtr[1] = maskPtr[0] & condPtr[1]; + ++condPtr; ++maskPtr; + NEXT(); + LABEL(kMaskPop) + --condPtr; --maskPtr; + NEXT(); + LABEL(kMaskNegate) + maskPtr[0] = maskPtr[-1] & ~condPtr[0]; + NEXT(); + LABEL(kMaskBlend) { + int count = READ8(); + I32 m = condPtr[0]; + --condPtr; --maskPtr; + for (int i = 0; i < count; ++i) { + sp[-count] = skvx::if_then_else(m, sp[-count].fFloat, sp[0].fFloat); + --sp; + } + NEXT(); + } + LABEL(kBranchIfAllFalse) { + int target = READ16(); + if (!skvx::any(mask())) { + ip = code + target; + } + NEXT(); + } + + LABEL(kLoopBegin) + contPtr[1] = 0; + loopPtr[1] = loopPtr[0]; + ++contPtr; ++loopPtr; + NEXT(); + LABEL(kLoopNext) + *loopPtr |= *contPtr; + *contPtr = 0; + NEXT(); + LABEL(kLoopMask) + *loopPtr &= POP().fSigned; + NEXT(); + LABEL(kLoopEnd) + --contPtr; --loopPtr; + NEXT(); + LABEL(kLoopBreak) + *loopPtr &= ~mask(); + NEXT(); + LABEL(kLoopContinue) { + I32 m = mask(); + *contPtr |= m; + *loopPtr &= ~m; + NEXT(); + } +#ifdef SKSLC_THREADED_CODE + #ifdef TRACE + next: + printf("at %3d (stack: %d) (disable threaded code for disassembly)\n", + (int) (ip - code), (int) (sp - stack) + 1); + goto *READ_INST(); + #endif +#else + } + } +#endif +} + +}; // class Interpreter + +#endif // SK_ENABLE_SKSL_INTERPRETER + +#undef spf + +void ByteCodeFunction::disassemble() const { +#if defined(SK_ENABLE_SKSL_INTERPRETER) + const uint8_t* ip = fCode.data(); + while (ip < fCode.data() + fCode.size()) { + printf("%d: ", (int)(ip - fCode.data())); + ip = Interpreter::DisassembleInstruction(ip); + printf("\n"); + } +#endif +} + +#define VECTOR_PREPROCESS(base) \ + case ByteCodeInstruction::base ## 4: \ + case ByteCodeInstruction::base ## 3: \ + case ByteCodeInstruction::base ## 2: \ + case ByteCodeInstruction::base: READ8(); break; + +#define VECTOR_PREPROCESS_NO_COUNT(base) \ + case ByteCodeInstruction::base ## 4: \ + case ByteCodeInstruction::base ## 3: \ + case ByteCodeInstruction::base ## 2: \ + case ByteCodeInstruction::base: break; + +#define VECTOR_MATRIX_PREPROCESS(base) \ + VECTOR_PREPROCESS(base) \ + case ByteCodeInstruction::base ## N: READ8(); break; + +#define VECTOR_MATRIX_PREPROCESS_NO_COUNT(base) \ + VECTOR_PREPROCESS_NO_COUNT(base) \ + case ByteCodeInstruction::base ## N: READ8(); break; + +void ByteCodeFunction::preprocess(const void* labels[]) { +#if defined(SK_ENABLE_SKSL_INTERPRETER) +#ifdef TRACE + this->disassemble(); +#endif + uint8_t* ip = fCode.data(); + while (ip < fCode.data() + fCode.size()) { + ByteCodeInstruction inst = (ByteCodeInstruction) (intptr_t) READ_INST(); + const void* label = labels[(int) inst]; + memcpy(ip - sizeof(instruction), &label, sizeof(label)); + switch (inst) { + VECTOR_MATRIX_PREPROCESS(kAddF) + VECTOR_PREPROCESS(kAddI) + case ByteCodeInstruction::kAndB: break; + case ByteCodeInstruction::kBranch: READ16(); break; + case ByteCodeInstruction::kCall: READ8(); break; + case ByteCodeInstruction::kCallExternal: { + READ8(); + READ8(); + READ8(); + break; + } + case ByteCodeInstruction::kClampIndex: READ8(); break; + VECTOR_PREPROCESS(kCompareIEQ) + VECTOR_PREPROCESS(kCompareINEQ) + VECTOR_MATRIX_PREPROCESS(kCompareFEQ) + VECTOR_MATRIX_PREPROCESS(kCompareFNEQ) + VECTOR_PREPROCESS(kCompareFGT) + VECTOR_PREPROCESS(kCompareFGTEQ) + VECTOR_PREPROCESS(kCompareFLT) + VECTOR_PREPROCESS(kCompareFLTEQ) + VECTOR_PREPROCESS(kCompareSGT) + VECTOR_PREPROCESS(kCompareSGTEQ) + VECTOR_PREPROCESS(kCompareSLT) + VECTOR_PREPROCESS(kCompareSLTEQ) + VECTOR_PREPROCESS(kCompareUGT) + VECTOR_PREPROCESS(kCompareUGTEQ) + VECTOR_PREPROCESS(kCompareULT) + VECTOR_PREPROCESS(kCompareULTEQ) + VECTOR_PREPROCESS_NO_COUNT(kConvertFtoI) + VECTOR_PREPROCESS_NO_COUNT(kConvertStoF) + VECTOR_PREPROCESS_NO_COUNT(kConvertUtoF) + VECTOR_PREPROCESS(kCos) + VECTOR_MATRIX_PREPROCESS(kDivideF) + VECTOR_PREPROCESS(kDivideS) + VECTOR_PREPROCESS(kDivideU) + VECTOR_MATRIX_PREPROCESS(kDup) + + case ByteCodeInstruction::kInverse2x2: + case ByteCodeInstruction::kInverse3x3: + case ByteCodeInstruction::kInverse4x4: break; + + case ByteCodeInstruction::kLoad: + case ByteCodeInstruction::kLoad2: + case ByteCodeInstruction::kLoad3: + case ByteCodeInstruction::kLoad4: + case ByteCodeInstruction::kLoadGlobal: + case ByteCodeInstruction::kLoadGlobal2: + case ByteCodeInstruction::kLoadGlobal3: + case ByteCodeInstruction::kLoadGlobal4: + case ByteCodeInstruction::kLoadUniform: + case ByteCodeInstruction::kLoadUniform2: + case ByteCodeInstruction::kLoadUniform3: + case ByteCodeInstruction::kLoadUniform4: READ16(); break; + + case ByteCodeInstruction::kLoadSwizzle: + case ByteCodeInstruction::kLoadSwizzleGlobal: + case ByteCodeInstruction::kLoadSwizzleUniform: { + READ8(); + int count = READ8(); + ip += count; + break; + } + + case ByteCodeInstruction::kLoadExtended: + case ByteCodeInstruction::kLoadExtendedGlobal: + case ByteCodeInstruction::kLoadExtendedUniform: + READ8(); + break; + + case ByteCodeInstruction::kMatrixToMatrix: { + READ8(); + READ8(); + READ8(); + READ8(); + break; + } + case ByteCodeInstruction::kMatrixMultiply: { + READ8(); + READ8(); + READ8(); + break; + } + VECTOR_MATRIX_PREPROCESS(kMultiplyF) + VECTOR_PREPROCESS(kMultiplyI) + VECTOR_MATRIX_PREPROCESS_NO_COUNT(kNegateF) + VECTOR_PREPROCESS_NO_COUNT(kNegateI) + case ByteCodeInstruction::kNotB: break; + case ByteCodeInstruction::kOrB: break; + VECTOR_MATRIX_PREPROCESS_NO_COUNT(kPop) + case ByteCodeInstruction::kPushImmediate: READ32(); break; + + case ByteCodeInstruction::kReadExternal: + case ByteCodeInstruction::kReadExternal2: + case ByteCodeInstruction::kReadExternal3: + case ByteCodeInstruction::kReadExternal4: READ16(); break; + + VECTOR_PREPROCESS(kRemainderF) + VECTOR_PREPROCESS(kRemainderS) + VECTOR_PREPROCESS(kRemainderU) + case ByteCodeInstruction::kReserve: READ8(); break; + case ByteCodeInstruction::kReturn: READ8(); break; + case ByteCodeInstruction::kScalarToMatrix: READ8(); READ8(); break; + case ByteCodeInstruction::kShiftLeft: READ8(); break; + case ByteCodeInstruction::kShiftRightS: READ8(); break; + case ByteCodeInstruction::kShiftRightU: READ8(); break; + VECTOR_PREPROCESS(kSin) + VECTOR_PREPROCESS_NO_COUNT(kSqrt) + + case ByteCodeInstruction::kStore: + case ByteCodeInstruction::kStore2: + case ByteCodeInstruction::kStore3: + case ByteCodeInstruction::kStore4: + case ByteCodeInstruction::kStoreGlobal: + case ByteCodeInstruction::kStoreGlobal2: + case ByteCodeInstruction::kStoreGlobal3: + case ByteCodeInstruction::kStoreGlobal4: READ8(); break; + + case ByteCodeInstruction::kStoreSwizzle: + case ByteCodeInstruction::kStoreSwizzleGlobal: { + READ8(); + int count = READ8(); + ip += count; + break; + } + + case ByteCodeInstruction::kStoreSwizzleIndirect: + case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: { + int count = READ8(); + ip += count; + break; + } + + case ByteCodeInstruction::kStoreExtended: READ8(); break; + case ByteCodeInstruction::kStoreExtendedGlobal: READ8(); break; + + VECTOR_MATRIX_PREPROCESS(kSubtractF) + VECTOR_PREPROCESS(kSubtractI) + + case ByteCodeInstruction::kSwizzle: { + READ8(); + int count = READ8(); + ip += count; + break; + } + VECTOR_PREPROCESS(kTan) + case ByteCodeInstruction::kWriteExternal: + case ByteCodeInstruction::kWriteExternal2: + case ByteCodeInstruction::kWriteExternal3: + case ByteCodeInstruction::kWriteExternal4: READ16(); break; + + case ByteCodeInstruction::kXorB: break; + case ByteCodeInstruction::kMaskPush: break; + case ByteCodeInstruction::kMaskPop: break; + case ByteCodeInstruction::kMaskNegate: break; + case ByteCodeInstruction::kMaskBlend: READ8(); break; + case ByteCodeInstruction::kBranchIfAllFalse: READ16(); break; + case ByteCodeInstruction::kLoopBegin: break; + case ByteCodeInstruction::kLoopNext: break; + case ByteCodeInstruction::kLoopMask: break; + case ByteCodeInstruction::kLoopEnd: break; + case ByteCodeInstruction::kLoopContinue: break; + case ByteCodeInstruction::kLoopBreak: break; + default: + ip -= 2; + printf("unknown(%d)\n", READ16()); + SkASSERT(false); + } + } +#endif +} + +bool ByteCode::run(const ByteCodeFunction* f, + float* args, int argCount, + float* outReturn, int returnCount, + const float* uniforms, int uniformCount) const { +#if defined(SK_ENABLE_SKSL_INTERPRETER) + Interpreter::VValue stack[128]; + int stackNeeded = f->fParameterCount + f->fLocalCount + f->fStackCount; + if (stackNeeded > (int)SK_ARRAY_COUNT(stack)) { + return false; + } + + if (argCount != f->fParameterCount || + returnCount != f->fReturnCount || + uniformCount != fUniformSlotCount) { + return false; + } + + Interpreter::VValue globals[32]; + if (fGlobalSlotCount > (int)SK_ARRAY_COUNT(globals)) { + return false; + } + + // Transpose args into stack + { + float* src = args; + float* dst = (float*)stack; + for (int i = 0; i < argCount; ++i) { + *dst = *src++; + dst += VecWidth; + } + } + + bool stripedOutput = false; + float** outArray = outReturn ? &outReturn : nullptr; + if (!Interpreter::InnerRun(this, f, stack, outArray, globals, uniforms, stripedOutput, 1, 0)) { + return false; + } + + // Transpose out parameters back + { + float* dst = args; + float* src = (float*)stack; + for (const auto& p : f->fParameters) { + if (p.fIsOutParameter) { + for (int i = p.fSlotCount; i > 0; --i) { + *dst++ = *src; + src += VecWidth; + } + } else { + dst += p.fSlotCount; + src += p.fSlotCount * VecWidth; + } + } + } + + return true; +#else + SkDEBUGFAIL("ByteCode interpreter not enabled"); + return false; +#endif +} + +bool ByteCode::runStriped(const ByteCodeFunction* f, int N, + float* args[], int argCount, + float* outReturn[], int returnCount, + const float* uniforms, int uniformCount) const { +#if defined(SK_ENABLE_SKSL_INTERPRETER) + Interpreter::VValue stack[128]; + int stackNeeded = f->fParameterCount + f->fLocalCount + f->fStackCount; + if (stackNeeded > (int)SK_ARRAY_COUNT(stack)) { + return false; + } + + if (argCount != f->fParameterCount || + returnCount != f->fReturnCount || + uniformCount != fUniformSlotCount) { + return false; + } + + Interpreter::VValue globals[32]; + if (fGlobalSlotCount > (int)SK_ARRAY_COUNT(globals)) { + return false; + } + + // innerRun just takes outArgs, so clear it if the count is zero + if (returnCount == 0) { + outReturn = nullptr; + } + + int baseIndex = 0; + + while (N) { + int w = std::min(N, VecWidth); + + // Copy args into stack + for (int i = 0; i < argCount; ++i) { + memcpy(stack + i, args[i], w * sizeof(float)); + } + + bool stripedOutput = true; + if (!Interpreter::InnerRun(this, f, stack, outReturn, globals, uniforms, stripedOutput, w, + baseIndex)) { + return false; + } + + // Copy out parameters back + int slot = 0; + for (const auto& p : f->fParameters) { + if (p.fIsOutParameter) { + for (int i = slot; i < slot + p.fSlotCount; ++i) { + memcpy(args[i], stack + i, w * sizeof(float)); + } + } + slot += p.fSlotCount; + } + + // Step each argument pointer ahead + for (int i = 0; i < argCount; ++i) { + args[i] += w; + } + N -= w; + baseIndex += w; + } + + return true; +#else + SkDEBUGFAIL("ByteCode interpreter not enabled"); + return false; +#endif +} + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLByteCode.h b/gfx/skia/skia/src/sksl/SkSLByteCode.h new file mode 100644 index 0000000000..ce3a9188de --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLByteCode.h @@ -0,0 +1,304 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BYTECODE +#define SKSL_BYTECODE + +#include "include/private/SkOnce.h" +#include "src/sksl/SkSLString.h" + +#include +#include + +namespace SkSL { + +class ExternalValue; +struct FunctionDeclaration; + +// GCC and Clang support the "labels as values" extension which we need to implement the interpreter +// using threaded code. Otherwise, we fall back to using a switch statement in a for loop. +#if defined(__GNUC__) || defined(__clang__) + #define SKSLC_THREADED_CODE + using instruction = void*; +#else + using instruction = uint16_t; +#endif + +#define VECTOR(name) name ## 4, name ## 3, name ## 2, name +#define VECTOR_MATRIX(name) name ## 4, name ## 3, name ## 2, name, name ## N + +enum class ByteCodeInstruction : uint16_t { + // B = bool, F = float, I = int, S = signed, U = unsigned + // All binary VECTOR instructions (kAddF, KSubtractI, kCompareIEQ, etc.) are followed by a byte + // indicating the count, even though it is redundant due to the count appearing in the opcode. + // This is because the original opcodes are lost after we preprocess it into threaded code, and + // we need to still be able to access the count so as to permit the implementation to use opcode + // fallthrough. + VECTOR_MATRIX(kAddF), + VECTOR(kAddI), + kAndB, + kBranch, + // Followed by a byte indicating the index of the function to call + kCall, + // Followed by three bytes indicating: the number of argument slots, the number of return slots, + // and the index of the external value to call + kCallExternal, + // For dynamic array access: Followed by byte indicating length of array + kClampIndex, + VECTOR(kCompareIEQ), + VECTOR(kCompareINEQ), + VECTOR_MATRIX(kCompareFEQ), + VECTOR_MATRIX(kCompareFNEQ), + VECTOR(kCompareFGT), + VECTOR(kCompareFGTEQ), + VECTOR(kCompareFLT), + VECTOR(kCompareFLTEQ), + VECTOR(kCompareSGT), + VECTOR(kCompareSGTEQ), + VECTOR(kCompareSLT), + VECTOR(kCompareSLTEQ), + VECTOR(kCompareUGT), + VECTOR(kCompareUGTEQ), + VECTOR(kCompareULT), + VECTOR(kCompareULTEQ), + VECTOR(kConvertFtoI), + VECTOR(kConvertStoF), + VECTOR(kConvertUtoF), + // Followed by a (redundant) byte indicating the count + VECTOR(kCos), + VECTOR_MATRIX(kDivideF), + VECTOR(kDivideS), + VECTOR(kDivideU), + // Duplicates the top stack value. Followed by a (redundant) byte indicating the count. + VECTOR_MATRIX(kDup), + kInverse2x2, + kInverse3x3, + kInverse4x4, + // kLoad/kLoadGlobal are followed by a byte indicating the count, and a byte indicating the + // local/global slot to load + VECTOR(kLoad), + VECTOR(kLoadGlobal), + VECTOR(kLoadUniform), + // As kLoad/kLoadGlobal, then a count byte (1-4), and then one byte per swizzle component (0-3). + kLoadSwizzle, + kLoadSwizzleGlobal, + kLoadSwizzleUniform, + // kLoadExtended* are fallback load ops when we lack a specialization. They are followed by a + // count byte, and get the slot to load from the top of the stack. + kLoadExtended, + kLoadExtendedGlobal, + kLoadExtendedUniform, + // Followed by four bytes: srcCols, srcRows, dstCols, dstRows. Consumes the src matrix from the + // stack, and replaces it with the dst matrix. Per GLSL rules, there are no restrictions on + // dimensions. Any overlapping values are copied, and any other values are filled in with the + // identity matrix. + kMatrixToMatrix, + // Followed by three bytes: leftCols (== rightRows), leftRows, rightCols + kMatrixMultiply, + VECTOR_MATRIX(kNegateF), + VECTOR(kNegateI), + VECTOR_MATRIX(kMultiplyF), + VECTOR(kMultiplyI), + kNotB, + kOrB, + VECTOR_MATRIX(kPop), + // Followed by a 32 bit value containing the value to push + kPushImmediate, + // Followed by a byte indicating external value to read + VECTOR(kReadExternal), + VECTOR(kRemainderF), + VECTOR(kRemainderS), + VECTOR(kRemainderU), + // Followed by a byte indicating the number of slots to reserve on the stack (for later return) + kReserve, + // Followed by a byte indicating the number of slots being returned + kReturn, + // Followed by two bytes indicating columns and rows of matrix (2, 3, or 4 each). + // Takes a single value from the top of the stack, and converts to a CxR matrix with that value + // replicated along the diagonal (and zero elsewhere), per the GLSL matrix construction rules. + kScalarToMatrix, + // Followed by a byte indicating the number of bits to shift + kShiftLeft, + kShiftRightS, + kShiftRightU, + // Followed by a (redundant) byte indicating the count + VECTOR(kSin), + VECTOR(kSqrt), + // kStore/kStoreGlobal are followed by a byte indicating the local/global slot to store + VECTOR(kStore), + VECTOR(kStoreGlobal), + // Fallback stores. Followed by count byte, and get the slot to store from the top of the stack + kStoreExtended, + kStoreExtendedGlobal, + // As kStore/kStoreGlobal, then a count byte (1-4), then one byte per swizzle component (0-3). + // Expects the stack to look like: ... v1 v2 v3 v4, where the number of 'v's is equal to the + // number of swizzle components. After the store, all v's are popped from the stack. + kStoreSwizzle, + kStoreSwizzleGlobal, + // As above, but gets the store slot from the top of the stack (before values to be stored) + kStoreSwizzleIndirect, + kStoreSwizzleIndirectGlobal, + // Followed by two count bytes (1-4), and then one byte per swizzle component (0-3). The first + // count byte provides the current vector size (the vector is the top n stack elements), and the + // second count byte provides the swizzle component count. + kSwizzle, + VECTOR_MATRIX(kSubtractF), + VECTOR(kSubtractI), + // Followed by a (redundant) byte indicating the count + VECTOR(kTan), + // Followed by a byte indicating external value to write + VECTOR(kWriteExternal), + kXorB, + + kMaskPush, + kMaskPop, + kMaskNegate, + // Followed by count byte + kMaskBlend, + // Followed by address + kBranchIfAllFalse, + + kLoopBegin, + kLoopNext, + kLoopMask, + kLoopEnd, + kLoopBreak, + kLoopContinue, +}; +#undef VECTOR + +class ByteCodeFunction { +public: + int getParameterCount() const { return fParameterCount; } + int getReturnCount() const { return fReturnCount; } + + /** + * Print bytecode disassembly to stdout. + */ + void disassemble() const; + +private: + ByteCodeFunction(const FunctionDeclaration* declaration); + + friend class ByteCode; + friend class ByteCodeGenerator; + friend struct Interpreter; + + struct Parameter { + int fSlotCount; + bool fIsOutParameter; + }; + + SkSL::String fName; + std::vector fParameters; + int fParameterCount; + int fReturnCount = 0; + + int fLocalCount = 0; + int fStackCount = 0; + int fConditionCount = 0; + int fLoopCount = 0; + mutable SkOnce fPreprocessOnce; + std::vector fCode; + + /** + * Replace each opcode with the corresponding entry from the labels array. + */ + void preprocess(const void* labels[]); +}; + +enum class TypeCategory { + kBool, + kSigned, + kUnsigned, + kFloat, +}; + +class SK_API ByteCode { +public: + static constexpr int kVecWidth = 16; + + ByteCode() = default; + + const ByteCodeFunction* getFunction(const char* name) const { + for (const auto& f : fFunctions) { + if (f->fName == name) { + return f.get(); + } + } + return nullptr; + } + + /** + * Invokes the specified function once, with the given arguments. + * 'args', 'outReturn', and 'uniforms' are collections of 32-bit values (typically floats, + * but possibly int32_t or uint32_t, depending on the types used in the SkSL). + * Any 'out' or 'inout' parameters will result in the 'args' array being modified. + * The return value is stored in 'outReturn' (may be null, to discard the return value). + * 'uniforms' are mapped to 'uniform' globals, in order. + */ + bool SKSL_WARN_UNUSED_RESULT run(const ByteCodeFunction*, + float* args, int argCount, + float* outReturn, int returnCount, + const float* uniforms, int uniformCount) const; + + /** + * Invokes the specified function with the given arguments, 'N' times. 'args' and 'outReturn' + * are accepted and returned in structure-of-arrays form: + * args[0] points to an array of N values, the first argument for each invocation + * ... + * args[argCount - 1] points to an array of N values, the last argument for each invocation + * + * All values in 'args', 'outReturn', and 'uniforms' are 32-bit values (typically floats, + * but possibly int32_t or uint32_t, depending on the types used in the SkSL). + * Any 'out' or 'inout' parameters will result in the 'args' array being modified. + * The return value is stored in 'outReturn' (may be null, to discard the return value). + * 'uniforms' are mapped to 'uniform' globals, in order. + */ + bool SKSL_WARN_UNUSED_RESULT runStriped(const ByteCodeFunction*, int N, + float* args[], int argCount, + float* outReturn[], int returnCount, + const float* uniforms, int uniformCount) const; + + struct Uniform { + SkSL::String fName; + TypeCategory fType; + int fColumns; + int fRows; + int fSlot; + }; + + int getUniformSlotCount() const { return fUniformSlotCount; } + int getUniformCount() const { return fUniforms.size(); } + int getUniformLocation(const char* name) const { + for (int i = 0; i < (int)fUniforms.size(); ++i) { + if (fUniforms[i].fName == name) { + return fUniforms[i].fSlot; + } + } + return -1; + } + const Uniform& getUniform(int i) const { return fUniforms[i]; } + +private: + ByteCode(const ByteCode&) = delete; + ByteCode& operator=(const ByteCode&) = delete; + + friend class ByteCodeGenerator; + friend struct Interpreter; + + int fGlobalSlotCount = 0; + int fUniformSlotCount = 0; + std::vector fUniforms; + + std::vector> fFunctions; + std::vector fExternalValues; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.cpp new file mode 100644 index 0000000000..560a3a76b8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.cpp @@ -0,0 +1,1665 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLByteCodeGenerator.h" + +#include + +namespace SkSL { + +static TypeCategory type_category(const Type& type) { + switch (type.kind()) { + case Type::Kind::kVector_Kind: + case Type::Kind::kMatrix_Kind: + return type_category(type.componentType()); + default: + if (type.fName == "bool") { + return TypeCategory::kBool; + } else if (type.fName == "int" || type.fName == "short") { + return TypeCategory::kSigned; + } else if (type.fName == "uint" || type.fName == "ushort") { + return TypeCategory::kUnsigned; + } else { + SkASSERT(type.fName == "float" || type.fName == "half"); + return TypeCategory::kFloat; + } + ABORT("unsupported type: %s\n", type.description().c_str()); + } +} + + +ByteCodeGenerator::ByteCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + ByteCode* output) + : INHERITED(program, errors, nullptr) + , fContext(*context) + , fOutput(output) + , fIntrinsics { + { "cos", ByteCodeInstruction::kCos }, + { "dot", SpecialIntrinsic::kDot }, + { "inverse", ByteCodeInstruction::kInverse2x2 }, + { "sin", ByteCodeInstruction::kSin }, + { "sqrt", ByteCodeInstruction::kSqrt }, + { "tan", ByteCodeInstruction::kTan }, + } {} + + +int ByteCodeGenerator::SlotCount(const Type& type) { + if (type.kind() == Type::kOther_Kind) { + return 0; + } else if (type.kind() == Type::kStruct_Kind) { + int slots = 0; + for (const auto& f : type.fields()) { + slots += SlotCount(*f.fType); + } + SkASSERT(slots <= 255); + return slots; + } else if (type.kind() == Type::kArray_Kind) { + int columns = type.columns(); + SkASSERT(columns >= 0); + int slots = columns * SlotCount(type.componentType()); + SkASSERT(slots <= 255); + return slots; + } else { + return type.columns() * type.rows(); + } +} + +static inline bool is_uniform(const SkSL::Variable& var) { + return var.fModifiers.fFlags & Modifiers::kUniform_Flag; +} + +void ByteCodeGenerator::gatherUniforms(const Type& type, const String& name) { + if (type.kind() == Type::kOther_Kind) { + return; + } else if (type.kind() == Type::kStruct_Kind) { + for (const auto& f : type.fields()) { + this->gatherUniforms(*f.fType, name + "." + f.fName); + } + } else if (type.kind() == Type::kArray_Kind) { + for (int i = 0; i < type.columns(); ++i) { + this->gatherUniforms(type.componentType(), String::printf("%s[%d]", name.c_str(), i)); + } + } else { + fOutput->fUniforms.push_back({ name, type_category(type), type.rows(), type.columns(), + fOutput->fUniformSlotCount }); + fOutput->fUniformSlotCount += type.columns() * type.rows(); + } +} + +bool ByteCodeGenerator::generateCode() { + for (const auto& e : fProgram) { + switch (e.fKind) { + case ProgramElement::kFunction_Kind: { + std::unique_ptr f = this->writeFunction((FunctionDefinition&) e); + if (!f) { + return false; + } + fOutput->fFunctions.push_back(std::move(f)); + fFunctions.push_back(&(FunctionDefinition&)e); + break; + } + case ProgramElement::kVar_Kind: { + VarDeclarations& decl = (VarDeclarations&) e; + for (const auto& v : decl.fVars) { + const Variable* declVar = ((VarDeclaration&) *v).fVar; + if (declVar->fModifiers.fLayout.fBuiltin >= 0) { + continue; + } + // if you trip this assert, it means the program has raw 'in' variables. You + // should either specialize the program (Compiler::specialize) to bake in the + // final values of the 'in' variables, or not use 'in' variables (maybe you + // meant to use 'uniform' instead?). + SkASSERT(!(declVar->fModifiers.fFlags & Modifiers::kIn_Flag)); + if (is_uniform(*declVar)) { + this->gatherUniforms(declVar->fType, declVar->fName); + } else { + fOutput->fGlobalSlotCount += SlotCount(declVar->fType); + } + } + break; + } + default: + ; // ignore + } + } + return 0 == fErrors.errorCount(); +} + +std::unique_ptr ByteCodeGenerator::writeFunction(const FunctionDefinition& f) { + fFunction = &f; + std::unique_ptr result(new ByteCodeFunction(&f.fDeclaration)); + fParameterCount = result->fParameterCount; + fLoopCount = fMaxLoopCount = 0; + fConditionCount = fMaxConditionCount = 0; + fStackCount = fMaxStackCount = 0; + fCode = &result->fCode; + + this->writeStatement(*f.fBody); + if (0 == fErrors.errorCount()) { + SkASSERT(fLoopCount == 0); + SkASSERT(fConditionCount == 0); + SkASSERT(fStackCount == 0); + } + this->write(ByteCodeInstruction::kReturn, 0); + this->write8(0); + + result->fLocalCount = fLocals.size(); + result->fConditionCount = fMaxConditionCount; + result->fLoopCount = fMaxLoopCount; + result->fStackCount = fMaxStackCount; + + const Type& returnType = f.fDeclaration.fReturnType; + if (returnType != *fContext.fVoid_Type) { + result->fReturnCount = SlotCount(returnType); + } + fLocals.clear(); + fFunction = nullptr; + return result; +} + +// A "simple" Swizzle is based on a variable (or a compound variable like a struct or array), and +// that references consecutive values, such that it can be implemented using normal load/store ops +// with an offset. Note that all single-component swizzles (of suitable base types) are simple. +static bool swizzle_is_simple(const Swizzle& s) { + switch (s.fBase->fKind) { + case Expression::kFieldAccess_Kind: + case Expression::kIndex_Kind: + case Expression::kVariableReference_Kind: + break; + default: + return false; + } + + for (size_t i = 1; i < s.fComponents.size(); ++i) { + if (s.fComponents[i] != s.fComponents[i - 1] + 1) { + return false; + } + } + return true; +} + +int ByteCodeGenerator::StackUsage(ByteCodeInstruction inst, int count_) { + // Ensures that we use count iff we're passed a non-default value. Most instructions have an + // implicit count, so the caller shouldn't need to worry about it (or count makes no sense). + // The asserts avoids callers thinking they're supplying useful information in that scenario, + // or failing to supply necessary information for the ops that need a count. + struct CountValue { + operator int() { + SkASSERT(val != ByteCodeGenerator::kUnusedStackCount); + SkDEBUGCODE(used = true); + return val; + } + ~CountValue() { + SkASSERT(used || val == ByteCodeGenerator::kUnusedStackCount); + } + int val; + SkDEBUGCODE(bool used = false;) + } count = { count_ }; + + switch (inst) { + // Unary functions/operators that don't change stack depth at all: +#define VECTOR_UNARY_OP(base) \ + case ByteCodeInstruction::base: \ + case ByteCodeInstruction::base ## 2: \ + case ByteCodeInstruction::base ## 3: \ + case ByteCodeInstruction::base ## 4: \ + return 0; + + VECTOR_UNARY_OP(kConvertFtoI) + VECTOR_UNARY_OP(kConvertStoF) + VECTOR_UNARY_OP(kConvertUtoF) + + VECTOR_UNARY_OP(kCos) + VECTOR_UNARY_OP(kSin) + VECTOR_UNARY_OP(kSqrt) + VECTOR_UNARY_OP(kTan) + + VECTOR_UNARY_OP(kNegateF) + VECTOR_UNARY_OP(kNegateI) + + case ByteCodeInstruction::kInverse2x2: + case ByteCodeInstruction::kInverse3x3: + case ByteCodeInstruction::kInverse4x4: return 0; + + case ByteCodeInstruction::kClampIndex: return 0; + case ByteCodeInstruction::kNotB: return 0; + case ByteCodeInstruction::kNegateFN: return 0; + case ByteCodeInstruction::kShiftLeft: return 0; + case ByteCodeInstruction::kShiftRightS: return 0; + case ByteCodeInstruction::kShiftRightU: return 0; + +#undef VECTOR_UNARY_OP + + // Binary functions/operators that do a 2 -> 1 reduction (possibly N times) +#define VECTOR_BINARY_OP(base) \ + case ByteCodeInstruction::base: return -1; \ + case ByteCodeInstruction::base ## 2: return -2; \ + case ByteCodeInstruction::base ## 3: return -3; \ + case ByteCodeInstruction::base ## 4: return -4; + +#define VECTOR_MATRIX_BINARY_OP(base) \ + VECTOR_BINARY_OP(base) \ + case ByteCodeInstruction::base ## N: return -count; + + case ByteCodeInstruction::kAndB: return -1; + case ByteCodeInstruction::kOrB: return -1; + case ByteCodeInstruction::kXorB: return -1; + + VECTOR_BINARY_OP(kAddI) + VECTOR_MATRIX_BINARY_OP(kAddF) + + VECTOR_BINARY_OP(kCompareIEQ) + VECTOR_MATRIX_BINARY_OP(kCompareFEQ) + VECTOR_BINARY_OP(kCompareINEQ) + VECTOR_MATRIX_BINARY_OP(kCompareFNEQ) + VECTOR_BINARY_OP(kCompareSGT) + VECTOR_BINARY_OP(kCompareUGT) + VECTOR_BINARY_OP(kCompareFGT) + VECTOR_BINARY_OP(kCompareSGTEQ) + VECTOR_BINARY_OP(kCompareUGTEQ) + VECTOR_BINARY_OP(kCompareFGTEQ) + VECTOR_BINARY_OP(kCompareSLT) + VECTOR_BINARY_OP(kCompareULT) + VECTOR_BINARY_OP(kCompareFLT) + VECTOR_BINARY_OP(kCompareSLTEQ) + VECTOR_BINARY_OP(kCompareULTEQ) + VECTOR_BINARY_OP(kCompareFLTEQ) + + VECTOR_BINARY_OP(kDivideS) + VECTOR_BINARY_OP(kDivideU) + VECTOR_MATRIX_BINARY_OP(kDivideF) + VECTOR_BINARY_OP(kMultiplyI) + VECTOR_MATRIX_BINARY_OP(kMultiplyF) + VECTOR_BINARY_OP(kRemainderF) + VECTOR_BINARY_OP(kRemainderS) + VECTOR_BINARY_OP(kRemainderU) + VECTOR_BINARY_OP(kSubtractI) + VECTOR_MATRIX_BINARY_OP(kSubtractF) + +#undef VECTOR_BINARY_OP +#undef VECTOR_MATRIX_BINARY_OP + + // Ops that push or load data to grow the stack: + case ByteCodeInstruction::kDup: + case ByteCodeInstruction::kLoad: + case ByteCodeInstruction::kLoadGlobal: + case ByteCodeInstruction::kLoadUniform: + case ByteCodeInstruction::kReadExternal: + case ByteCodeInstruction::kPushImmediate: + return 1; + + case ByteCodeInstruction::kDup2: + case ByteCodeInstruction::kLoad2: + case ByteCodeInstruction::kLoadGlobal2: + case ByteCodeInstruction::kLoadUniform2: + case ByteCodeInstruction::kReadExternal2: + return 2; + + case ByteCodeInstruction::kDup3: + case ByteCodeInstruction::kLoad3: + case ByteCodeInstruction::kLoadGlobal3: + case ByteCodeInstruction::kLoadUniform3: + case ByteCodeInstruction::kReadExternal3: + return 3; + + case ByteCodeInstruction::kDup4: + case ByteCodeInstruction::kLoad4: + case ByteCodeInstruction::kLoadGlobal4: + case ByteCodeInstruction::kLoadUniform4: + case ByteCodeInstruction::kReadExternal4: + return 4; + + case ByteCodeInstruction::kDupN: + case ByteCodeInstruction::kLoadSwizzle: + case ByteCodeInstruction::kLoadSwizzleGlobal: + case ByteCodeInstruction::kLoadSwizzleUniform: + return count; + + // Pushes 'count' values, minus one for the 'address' that's consumed first + case ByteCodeInstruction::kLoadExtended: + case ByteCodeInstruction::kLoadExtendedGlobal: + case ByteCodeInstruction::kLoadExtendedUniform: + return count - 1; + + // Ops that pop or store data to shrink the stack: + case ByteCodeInstruction::kPop: + case ByteCodeInstruction::kStore: + case ByteCodeInstruction::kStoreGlobal: + case ByteCodeInstruction::kWriteExternal: + return -1; + + case ByteCodeInstruction::kPop2: + case ByteCodeInstruction::kStore2: + case ByteCodeInstruction::kStoreGlobal2: + case ByteCodeInstruction::kWriteExternal2: + return -2; + + case ByteCodeInstruction::kPop3: + case ByteCodeInstruction::kStore3: + case ByteCodeInstruction::kStoreGlobal3: + case ByteCodeInstruction::kWriteExternal3: + return -3; + + case ByteCodeInstruction::kPop4: + case ByteCodeInstruction::kStore4: + case ByteCodeInstruction::kStoreGlobal4: + case ByteCodeInstruction::kWriteExternal4: + return -4; + + case ByteCodeInstruction::kPopN: + case ByteCodeInstruction::kStoreSwizzle: + case ByteCodeInstruction::kStoreSwizzleGlobal: + return -count; + + // Consumes 'count' values, plus one for the 'address' + case ByteCodeInstruction::kStoreExtended: + case ByteCodeInstruction::kStoreExtendedGlobal: + case ByteCodeInstruction::kStoreSwizzleIndirect: + case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: + return -count - 1; + + // Strange ops where the caller computes the delta for us: + case ByteCodeInstruction::kCallExternal: + case ByteCodeInstruction::kMatrixToMatrix: + case ByteCodeInstruction::kMatrixMultiply: + case ByteCodeInstruction::kReserve: + case ByteCodeInstruction::kReturn: + case ByteCodeInstruction::kScalarToMatrix: + case ByteCodeInstruction::kSwizzle: + return count; + + // Miscellaneous + + // kCall is net-zero. Max stack depth is adjusted in writeFunctionCall. + case ByteCodeInstruction::kCall: return 0; + case ByteCodeInstruction::kBranch: return 0; + case ByteCodeInstruction::kBranchIfAllFalse: return 0; + + case ByteCodeInstruction::kMaskPush: return -1; + case ByteCodeInstruction::kMaskPop: return 0; + case ByteCodeInstruction::kMaskNegate: return 0; + case ByteCodeInstruction::kMaskBlend: return -count; + + case ByteCodeInstruction::kLoopBegin: return 0; + case ByteCodeInstruction::kLoopNext: return 0; + case ByteCodeInstruction::kLoopMask: return -1; + case ByteCodeInstruction::kLoopEnd: return 0; + case ByteCodeInstruction::kLoopBreak: return 0; + case ByteCodeInstruction::kLoopContinue: return 0; + + default: + ABORT("unsupported instruction %d\n", (int)inst); + return 0; + } +} + +ByteCodeGenerator::Location ByteCodeGenerator::getLocation(const Variable& var) { + // given that we seldom have more than a couple of variables, linear search is probably the most + // efficient way to handle lookups + switch (var.fStorage) { + case Variable::kLocal_Storage: { + for (int i = fLocals.size() - 1; i >= 0; --i) { + if (fLocals[i] == &var) { + SkASSERT(fParameterCount + i <= 255); + return { fParameterCount + i, Storage::kLocal }; + } + } + int result = fParameterCount + fLocals.size(); + fLocals.push_back(&var); + for (int i = 0; i < SlotCount(var.fType) - 1; ++i) { + fLocals.push_back(nullptr); + } + SkASSERT(result <= 255); + return { result, Storage::kLocal }; + } + case Variable::kParameter_Storage: { + int offset = 0; + for (const auto& p : fFunction->fDeclaration.fParameters) { + if (p == &var) { + SkASSERT(offset <= 255); + return { offset, Storage::kLocal }; + } + offset += SlotCount(p->fType); + } + SkASSERT(false); + return Location::MakeInvalid(); + } + case Variable::kGlobal_Storage: { + int offset = 0; + bool isUniform = is_uniform(var); + for (const auto& e : fProgram) { + if (e.fKind == ProgramElement::kVar_Kind) { + VarDeclarations& decl = (VarDeclarations&) e; + for (const auto& v : decl.fVars) { + const Variable* declVar = ((VarDeclaration&) *v).fVar; + if (declVar->fModifiers.fLayout.fBuiltin >= 0) { + continue; + } + if (isUniform != is_uniform(*declVar)) { + continue; + } + if (declVar == &var) { + SkASSERT(offset <= 255); + return { offset, isUniform ? Storage::kUniform : Storage::kGlobal }; + } + offset += SlotCount(declVar->fType); + } + } + } + SkASSERT(false); + return Location::MakeInvalid(); + } + default: + SkASSERT(false); + return Location::MakeInvalid(); + } +} + +ByteCodeGenerator::Location ByteCodeGenerator::getLocation(const Expression& expr) { + switch (expr.fKind) { + case Expression::kFieldAccess_Kind: { + const FieldAccess& f = (const FieldAccess&)expr; + Location baseLoc = this->getLocation(*f.fBase); + int offset = 0; + for (int i = 0; i < f.fFieldIndex; ++i) { + offset += SlotCount(*f.fBase->fType.fields()[i].fType); + } + if (baseLoc.isOnStack()) { + if (offset != 0) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(offset); + this->write(ByteCodeInstruction::kAddI); + this->write8(1); + } + return baseLoc; + } else { + return baseLoc + offset; + } + } + case Expression::kIndex_Kind: { + const IndexExpression& i = (const IndexExpression&)expr; + int stride = SlotCount(i.fType); + int length = i.fBase->fType.columns(); + SkASSERT(length <= 255); + int offset = -1; + if (i.fIndex->isConstant()) { + int64_t index = i.fIndex->getConstantInt(); + if (index < 0 || index >= length) { + fErrors.error(i.fIndex->fOffset, "Array index out of bounds."); + return Location::MakeInvalid(); + } + offset = index * stride; + } else { + if (i.fIndex->hasSideEffects()) { + // Having a side-effect in an indexer is technically safe for an rvalue, + // but with lvalues we have to evaluate the indexer twice, so make it an error. + fErrors.error(i.fIndex->fOffset, + "Index expressions with side-effects not supported in byte code."); + return Location::MakeInvalid(); + } + this->writeExpression(*i.fIndex); + this->write(ByteCodeInstruction::kClampIndex); + this->write8(length); + if (stride != 1) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(stride); + this->write(ByteCodeInstruction::kMultiplyI); + this->write8(1); + } + } + Location baseLoc = this->getLocation(*i.fBase); + + // Are both components known statically? + if (!baseLoc.isOnStack() && offset >= 0) { + return baseLoc + offset; + } + + // At least one component is dynamic (and on the stack). + + // If the other component is zero, we're done + if (baseLoc.fSlot == 0 || offset == 0) { + return baseLoc.makeOnStack(); + } + + // Push the non-dynamic component (if any) to the stack, then add the two + if (!baseLoc.isOnStack()) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(baseLoc.fSlot); + } + if (offset >= 0) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(offset); + } + this->write(ByteCodeInstruction::kAddI); + this->write8(1); + return baseLoc.makeOnStack(); + } + case Expression::kSwizzle_Kind: { + const Swizzle& s = (const Swizzle&)expr; + SkASSERT(swizzle_is_simple(s)); + Location baseLoc = this->getLocation(*s.fBase); + int offset = s.fComponents[0]; + if (baseLoc.isOnStack()) { + if (offset != 0) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(offset); + this->write(ByteCodeInstruction::kAddI); + this->write8(1); + } + return baseLoc; + } else { + return baseLoc + offset; + } + } + case Expression::kVariableReference_Kind: { + const Variable& var = ((const VariableReference&)expr).fVariable; + return this->getLocation(var); + } + default: + SkASSERT(false); + return Location::MakeInvalid(); + } +} + +void ByteCodeGenerator::write8(uint8_t b) { + fCode->push_back(b); +} + +void ByteCodeGenerator::write16(uint16_t i) { + size_t n = fCode->size(); + fCode->resize(n+2); + memcpy(fCode->data() + n, &i, 2); +} + +void ByteCodeGenerator::write32(uint32_t i) { + size_t n = fCode->size(); + fCode->resize(n+4); + memcpy(fCode->data() + n, &i, 4); +} + +void ByteCodeGenerator::write(ByteCodeInstruction i, int count) { + switch (i) { + case ByteCodeInstruction::kLoopBegin: this->enterLoop(); break; + case ByteCodeInstruction::kLoopEnd: this->exitLoop(); break; + + case ByteCodeInstruction::kMaskPush: this->enterCondition(); break; + case ByteCodeInstruction::kMaskPop: + case ByteCodeInstruction::kMaskBlend: this->exitCondition(); break; + default: /* Do nothing */ break; + } + instruction val = (instruction) i; + size_t n = fCode->size(); + fCode->resize(n + sizeof(val)); + memcpy(fCode->data() + n, &val, sizeof(val)); + fStackCount += StackUsage(i, count); + fMaxStackCount = std::max(fMaxStackCount, fStackCount); +} + +static ByteCodeInstruction vector_instruction(ByteCodeInstruction base, int count) { + SkASSERT(count >= 1 && count <= 4); + return ((ByteCodeInstruction) ((int) base + 1 - count)); +} + +void ByteCodeGenerator::writeTypedInstruction(const Type& type, ByteCodeInstruction s, + ByteCodeInstruction u, ByteCodeInstruction f, + int count, bool writeCount) { + switch (type_category(type)) { + case TypeCategory::kSigned: + this->write(vector_instruction(s, count)); + break; + case TypeCategory::kUnsigned: + this->write(vector_instruction(u, count)); + break; + case TypeCategory::kFloat: { + if (count > 4) { + this->write((ByteCodeInstruction)((int)f + 1), count); + } else { + this->write(vector_instruction(f, count)); + } + break; + } + default: + SkASSERT(false); + } + if (writeCount) { + this->write8(count); + } +} + +bool ByteCodeGenerator::writeBinaryExpression(const BinaryExpression& b, bool discard) { + if (b.fOperator == Token::Kind::EQ) { + std::unique_ptr lvalue = this->getLValue(*b.fLeft); + this->writeExpression(*b.fRight); + lvalue->store(discard); + discard = false; + return discard; + } + const Type& lType = b.fLeft->fType; + const Type& rType = b.fRight->fType; + bool lVecOrMtx = (lType.kind() == Type::kVector_Kind || lType.kind() == Type::kMatrix_Kind); + bool rVecOrMtx = (rType.kind() == Type::kVector_Kind || rType.kind() == Type::kMatrix_Kind); + Token::Kind op; + std::unique_ptr lvalue; + if (is_assignment(b.fOperator)) { + lvalue = this->getLValue(*b.fLeft); + lvalue->load(); + op = remove_assignment(b.fOperator); + } else { + this->writeExpression(*b.fLeft); + op = b.fOperator; + if (!lVecOrMtx && rVecOrMtx) { + for (int i = SlotCount(rType); i > 1; --i) { + this->write(ByteCodeInstruction::kDup); + this->write8(1); + } + } + } + int count = std::max(SlotCount(lType), SlotCount(rType)); + SkDEBUGCODE(TypeCategory tc = type_category(lType)); + switch (op) { + case Token::Kind::LOGICALAND: { + SkASSERT(tc == SkSL::TypeCategory::kBool && count == 1); + this->write(ByteCodeInstruction::kDup); + this->write8(1); + this->write(ByteCodeInstruction::kMaskPush); + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation falseLocation(this); + this->writeExpression(*b.fRight); + this->write(ByteCodeInstruction::kAndB); + falseLocation.set(); + this->write(ByteCodeInstruction::kMaskPop); + return false; + } + case Token::Kind::LOGICALOR: { + SkASSERT(tc == SkSL::TypeCategory::kBool && count == 1); + this->write(ByteCodeInstruction::kDup); + this->write8(1); + this->write(ByteCodeInstruction::kNotB); + this->write(ByteCodeInstruction::kMaskPush); + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation falseLocation(this); + this->writeExpression(*b.fRight); + this->write(ByteCodeInstruction::kOrB); + falseLocation.set(); + this->write(ByteCodeInstruction::kMaskPop); + return false; + } + case Token::Kind::SHL: + case Token::Kind::SHR: { + SkASSERT(count == 1 && (tc == SkSL::TypeCategory::kSigned || + tc == SkSL::TypeCategory::kUnsigned)); + if (!b.fRight->isConstant()) { + fErrors.error(b.fRight->fOffset, "Shift amounts must be constant"); + return false; + } + int64_t shift = b.fRight->getConstantInt(); + if (shift < 0 || shift > 31) { + fErrors.error(b.fRight->fOffset, "Shift amount out of range"); + return false; + } + + if (op == Token::Kind::SHL) { + this->write(ByteCodeInstruction::kShiftLeft); + } else { + this->write(type_category(lType) == TypeCategory::kSigned + ? ByteCodeInstruction::kShiftRightS + : ByteCodeInstruction::kShiftRightU); + } + this->write8(shift); + return false; + } + + default: + break; + } + this->writeExpression(*b.fRight); + if (lVecOrMtx && !rVecOrMtx) { + for (int i = SlotCount(lType); i > 1; --i) { + this->write(ByteCodeInstruction::kDup); + this->write8(1); + } + } + // Special case for M*V, V*M, M*M (but not V*V!) + if (op == Token::Kind::STAR && lVecOrMtx && rVecOrMtx && + !(lType.kind() == Type::kVector_Kind && rType.kind() == Type::kVector_Kind)) { + this->write(ByteCodeInstruction::kMatrixMultiply, + SlotCount(b.fType) - (SlotCount(lType) + SlotCount(rType))); + int rCols = rType.columns(), + rRows = rType.rows(), + lCols = lType.columns(), + lRows = lType.rows(); + // M*V treats the vector as a column + if (rType.kind() == Type::kVector_Kind) { + std::swap(rCols, rRows); + } + SkASSERT(lCols == rRows); + SkASSERT(SlotCount(b.fType) == lRows * rCols); + this->write8(lCols); + this->write8(lRows); + this->write8(rCols); + } else { + switch (op) { + case Token::Kind::EQEQ: + this->writeTypedInstruction(lType, ByteCodeInstruction::kCompareIEQ, + ByteCodeInstruction::kCompareIEQ, + ByteCodeInstruction::kCompareFEQ, + count); + // Collapse to a single bool + for (int i = count; i > 1; --i) { + this->write(ByteCodeInstruction::kAndB); + } + break; + case Token::Kind::GT: + this->writeTypedInstruction(lType, ByteCodeInstruction::kCompareSGT, + ByteCodeInstruction::kCompareUGT, + ByteCodeInstruction::kCompareFGT, + count); + break; + case Token::Kind::GTEQ: + this->writeTypedInstruction(lType, ByteCodeInstruction::kCompareSGTEQ, + ByteCodeInstruction::kCompareUGTEQ, + ByteCodeInstruction::kCompareFGTEQ, + count); + break; + case Token::Kind::LT: + this->writeTypedInstruction(lType, ByteCodeInstruction::kCompareSLT, + ByteCodeInstruction::kCompareULT, + ByteCodeInstruction::kCompareFLT, + count); + break; + case Token::Kind::LTEQ: + this->writeTypedInstruction(lType, ByteCodeInstruction::kCompareSLTEQ, + ByteCodeInstruction::kCompareULTEQ, + ByteCodeInstruction::kCompareFLTEQ, + count); + break; + case Token::Kind::MINUS: + this->writeTypedInstruction(lType, ByteCodeInstruction::kSubtractI, + ByteCodeInstruction::kSubtractI, + ByteCodeInstruction::kSubtractF, + count); + break; + case Token::Kind::NEQ: + this->writeTypedInstruction(lType, ByteCodeInstruction::kCompareINEQ, + ByteCodeInstruction::kCompareINEQ, + ByteCodeInstruction::kCompareFNEQ, + count); + // Collapse to a single bool + for (int i = count; i > 1; --i) { + this->write(ByteCodeInstruction::kOrB); + } + break; + case Token::Kind::PERCENT: + this->writeTypedInstruction(lType, ByteCodeInstruction::kRemainderS, + ByteCodeInstruction::kRemainderU, + ByteCodeInstruction::kRemainderF, + count); + break; + case Token::Kind::PLUS: + this->writeTypedInstruction(lType, ByteCodeInstruction::kAddI, + ByteCodeInstruction::kAddI, + ByteCodeInstruction::kAddF, + count); + break; + case Token::Kind::SLASH: + this->writeTypedInstruction(lType, ByteCodeInstruction::kDivideS, + ByteCodeInstruction::kDivideU, + ByteCodeInstruction::kDivideF, + count); + break; + case Token::Kind::STAR: + this->writeTypedInstruction(lType, ByteCodeInstruction::kMultiplyI, + ByteCodeInstruction::kMultiplyI, + ByteCodeInstruction::kMultiplyF, + count); + break; + + case Token::Kind::LOGICALXOR: + SkASSERT(tc == SkSL::TypeCategory::kBool && count == 1); + this->write(ByteCodeInstruction::kXorB); + break; + + case Token::Kind::BITWISEAND: + SkASSERT(count == 1 && (tc == SkSL::TypeCategory::kSigned || + tc == SkSL::TypeCategory::kUnsigned)); + this->write(ByteCodeInstruction::kAndB); + break; + case Token::Kind::BITWISEOR: + SkASSERT(count == 1 && (tc == SkSL::TypeCategory::kSigned || + tc == SkSL::TypeCategory::kUnsigned)); + this->write(ByteCodeInstruction::kOrB); + break; + case Token::Kind::BITWISEXOR: + SkASSERT(count == 1 && (tc == SkSL::TypeCategory::kSigned || + tc == SkSL::TypeCategory::kUnsigned)); + this->write(ByteCodeInstruction::kXorB); + break; + + default: + fErrors.error(b.fOffset, SkSL::String::printf("Unsupported binary operator '%s'", + Compiler::OperatorName(op))); + break; + } + } + if (lvalue) { + lvalue->store(discard); + discard = false; + } + return discard; +} + +void ByteCodeGenerator::writeBoolLiteral(const BoolLiteral& b) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(b.fValue ? ~0 : 0); +} + +void ByteCodeGenerator::writeConstructor(const Constructor& c) { + for (const auto& arg : c.fArguments) { + this->writeExpression(*arg); + } + if (c.fArguments.size() == 1) { + const Type& inType = c.fArguments[0]->fType; + const Type& outType = c.fType; + TypeCategory inCategory = type_category(inType); + TypeCategory outCategory = type_category(outType); + int inCount = SlotCount(inType); + int outCount = SlotCount(outType); + if (inCategory != outCategory) { + SkASSERT(inCount == outCount); + if (inCategory == TypeCategory::kFloat) { + SkASSERT(outCategory == TypeCategory::kSigned || + outCategory == TypeCategory::kUnsigned); + this->write(vector_instruction(ByteCodeInstruction::kConvertFtoI, outCount)); + } else if (outCategory == TypeCategory::kFloat) { + if (inCategory == TypeCategory::kSigned) { + this->write(vector_instruction(ByteCodeInstruction::kConvertStoF, outCount)); + } else { + SkASSERT(inCategory == TypeCategory::kUnsigned); + this->write(vector_instruction(ByteCodeInstruction::kConvertUtoF, outCount)); + } + } else { + SkASSERT(false); + } + } + if (inType.kind() == Type::kMatrix_Kind && outType.kind() == Type::kMatrix_Kind) { + this->write(ByteCodeInstruction::kMatrixToMatrix, + SlotCount(outType) - SlotCount(inType)); + this->write8(inType.columns()); + this->write8(inType.rows()); + this->write8(outType.columns()); + this->write8(outType.rows()); + } else if (inCount != outCount) { + SkASSERT(inCount == 1); + if (outType.kind() == Type::kMatrix_Kind) { + this->write(ByteCodeInstruction::kScalarToMatrix, SlotCount(outType) - 1); + this->write8(outType.columns()); + this->write8(outType.rows()); + } else { + SkASSERT(outType.kind() == Type::kVector_Kind); + for (; inCount != outCount; ++inCount) { + this->write(ByteCodeInstruction::kDup); + this->write8(1); + } + } + } + } +} + +void ByteCodeGenerator::writeExternalFunctionCall(const ExternalFunctionCall& f) { + int argumentCount = 0; + for (const auto& arg : f.fArguments) { + this->writeExpression(*arg); + argumentCount += SlotCount(arg->fType); + } + this->write(ByteCodeInstruction::kCallExternal, SlotCount(f.fType) - argumentCount); + SkASSERT(argumentCount <= 255); + this->write8(argumentCount); + this->write8(SlotCount(f.fType)); + int index = fOutput->fExternalValues.size(); + fOutput->fExternalValues.push_back(f.fFunction); + SkASSERT(index <= 255); + this->write8(index); +} + +void ByteCodeGenerator::writeExternalValue(const ExternalValueReference& e) { + int count = SlotCount(e.fValue->type()); + this->write(vector_instruction(ByteCodeInstruction::kReadExternal, count)); + this->write8(count); + int index = fOutput->fExternalValues.size(); + fOutput->fExternalValues.push_back(e.fValue); + SkASSERT(index <= 255); + this->write8(index); +} + +void ByteCodeGenerator::writeVariableExpression(const Expression& expr) { + Location location = this->getLocation(expr); + int count = SlotCount(expr.fType); + if (location.isOnStack() || count > 4) { + if (!location.isOnStack()) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(location.fSlot); + } + this->write(location.selectLoad(ByteCodeInstruction::kLoadExtended, + ByteCodeInstruction::kLoadExtendedGlobal, + ByteCodeInstruction::kLoadExtendedUniform), + count); + this->write8(count); + } else { + this->write(vector_instruction(location.selectLoad(ByteCodeInstruction::kLoad, + ByteCodeInstruction::kLoadGlobal, + ByteCodeInstruction::kLoadUniform), + count)); + this->write8(count); + this->write8(location.fSlot); + } +} + +static inline uint32_t float_to_bits(float x) { + uint32_t u; + memcpy(&u, &x, sizeof(uint32_t)); + return u; +} + +void ByteCodeGenerator::writeFloatLiteral(const FloatLiteral& f) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(float_to_bits(f.fValue)); +} + +void ByteCodeGenerator::writeIntrinsicCall(const FunctionCall& c) { + auto found = fIntrinsics.find(c.fFunction.fName); + if (found == fIntrinsics.end()) { + fErrors.error(c.fOffset, "unsupported intrinsic function"); + return; + } + int count = SlotCount(c.fArguments[0]->fType); + if (found->second.fIsSpecial) { + SpecialIntrinsic special = found->second.fValue.fSpecial; + switch (special) { + case SpecialIntrinsic::kDot: { + SkASSERT(c.fArguments.size() == 2); + SkASSERT(count == SlotCount(c.fArguments[1]->fType)); + this->write(vector_instruction(ByteCodeInstruction::kMultiplyF, count)); + this->write8(count); + for (int i = count; i > 1; --i) { + this->write(ByteCodeInstruction::kAddF); + this->write8(1); + } + break; + } + default: + SkASSERT(false); + } + } else { + switch (found->second.fValue.fInstruction) { + case ByteCodeInstruction::kCos: + case ByteCodeInstruction::kSin: + case ByteCodeInstruction::kTan: + SkASSERT(c.fArguments.size() > 0); + this->write(vector_instruction(found->second.fValue.fInstruction, count)); + this->write8(count); + break; + case ByteCodeInstruction::kSqrt: + SkASSERT(c.fArguments.size() > 0); + this->write(vector_instruction(found->second.fValue.fInstruction, count)); + break; + case ByteCodeInstruction::kInverse2x2: { + SkASSERT(c.fArguments.size() > 0); + auto op = ByteCodeInstruction::kInverse2x2; + switch (count) { + case 4: break; // float2x2 + case 9: op = ByteCodeInstruction::kInverse3x3; break; + case 16: op = ByteCodeInstruction::kInverse4x4; break; + default: SkASSERT(false); + } + this->write(op); + break; + } + default: + SkASSERT(false); + } + } +} + +void ByteCodeGenerator::writeFunctionCall(const FunctionCall& f) { + // Builtins have simple signatures... + if (f.fFunction.fBuiltin) { + for (const auto& arg : f.fArguments) { + this->writeExpression(*arg); + } + this->writeIntrinsicCall(f); + return; + } + + // Find the index of the function we're calling. We explicitly do not allow calls to functions + // before they're defined. This is an easy-to-understand rule that prevents recursion. + size_t idx; + for (idx = 0; idx < fFunctions.size(); ++idx) { + if (f.fFunction.matches(fFunctions[idx]->fDeclaration)) { + break; + } + } + if (idx > 255) { + fErrors.error(f.fOffset, "Function count limit exceeded"); + return; + } else if (idx >= fFunctions.size()) { + fErrors.error(f.fOffset, "Call to undefined function"); + return; + } + + // We may need to deal with out parameters, so the sequence is tricky + if (int returnCount = SlotCount(f.fType)) { + this->write(ByteCodeInstruction::kReserve, returnCount); + this->write8(returnCount); + } + + int argCount = f.fArguments.size(); + std::vector> lvalues; + for (int i = 0; i < argCount; ++i) { + const auto& param = f.fFunction.fParameters[i]; + const auto& arg = f.fArguments[i]; + if (param->fModifiers.fFlags & Modifiers::kOut_Flag) { + lvalues.emplace_back(this->getLValue(*arg)); + lvalues.back()->load(); + } else { + this->writeExpression(*arg); + } + } + + // The space used by the call is based on the callee, but it also unwinds all of that before + // we continue execution. We adjust our max stack depths below. + this->write(ByteCodeInstruction::kCall); + this->write8(idx); + + const ByteCodeFunction* callee = fOutput->fFunctions[idx].get(); + fMaxLoopCount = std::max(fMaxLoopCount, fLoopCount + callee->fLoopCount); + fMaxConditionCount = std::max(fMaxConditionCount, fConditionCount + callee->fConditionCount); + fMaxStackCount = std::max(fMaxStackCount, fStackCount + callee->fLocalCount + + callee->fStackCount); + + // After the called function returns, the stack will still contain our arguments. We have to + // pop them (storing any out parameters back to their lvalues as we go). We glob together slot + // counts for all parameters that aren't out-params, so we can pop them in one big chunk. + int popCount = 0; + auto pop = [&]() { + if (popCount > 4) { + this->write(ByteCodeInstruction::kPopN, popCount); + this->write8(popCount); + } else if (popCount > 0) { + this->write(vector_instruction(ByteCodeInstruction::kPop, popCount)); + } + popCount = 0; + }; + + for (int i = argCount - 1; i >= 0; --i) { + const auto& param = f.fFunction.fParameters[i]; + const auto& arg = f.fArguments[i]; + if (param->fModifiers.fFlags & Modifiers::kOut_Flag) { + pop(); + lvalues.back()->store(true); + lvalues.pop_back(); + } else { + popCount += SlotCount(arg->fType); + } + } + pop(); +} + +void ByteCodeGenerator::writeIntLiteral(const IntLiteral& i) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(i.fValue); +} + +void ByteCodeGenerator::writeNullLiteral(const NullLiteral& n) { + // not yet implemented + abort(); +} + +bool ByteCodeGenerator::writePrefixExpression(const PrefixExpression& p, bool discard) { + switch (p.fOperator) { + case Token::Kind::PLUSPLUS: // fall through + case Token::Kind::MINUSMINUS: { + SkASSERT(SlotCount(p.fOperand->fType) == 1); + std::unique_ptr lvalue = this->getLValue(*p.fOperand); + lvalue->load(); + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(type_category(p.fType) == TypeCategory::kFloat ? float_to_bits(1.0f) : 1); + if (p.fOperator == Token::Kind::PLUSPLUS) { + this->writeTypedInstruction(p.fType, + ByteCodeInstruction::kAddI, + ByteCodeInstruction::kAddI, + ByteCodeInstruction::kAddF, + 1); + } else { + this->writeTypedInstruction(p.fType, + ByteCodeInstruction::kSubtractI, + ByteCodeInstruction::kSubtractI, + ByteCodeInstruction::kSubtractF, + 1); + } + lvalue->store(discard); + discard = false; + break; + } + case Token::Kind::MINUS: { + this->writeExpression(*p.fOperand); + this->writeTypedInstruction(p.fType, + ByteCodeInstruction::kNegateI, + ByteCodeInstruction::kNegateI, + ByteCodeInstruction::kNegateF, + SlotCount(p.fOperand->fType), + false); + break; + } + case Token::Kind::LOGICALNOT: + case Token::Kind::BITWISENOT: { + SkASSERT(SlotCount(p.fOperand->fType) == 1); + SkDEBUGCODE(TypeCategory tc = type_category(p.fOperand->fType)); + SkASSERT((p.fOperator == Token::Kind::LOGICALNOT && tc == TypeCategory::kBool) || + (p.fOperator == Token::Kind::BITWISENOT && (tc == TypeCategory::kSigned || + tc == TypeCategory::kUnsigned))); + this->writeExpression(*p.fOperand); + this->write(ByteCodeInstruction::kNotB); + break; + } + default: + SkASSERT(false); + } + return discard; +} + +bool ByteCodeGenerator::writePostfixExpression(const PostfixExpression& p, bool discard) { + switch (p.fOperator) { + case Token::Kind::PLUSPLUS: // fall through + case Token::Kind::MINUSMINUS: { + SkASSERT(SlotCount(p.fOperand->fType) == 1); + std::unique_ptr lvalue = this->getLValue(*p.fOperand); + lvalue->load(); + // If we're not supposed to discard the result, then make a copy *before* the +/- + if (!discard) { + this->write(ByteCodeInstruction::kDup); + this->write8(1); + } + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(type_category(p.fType) == TypeCategory::kFloat ? float_to_bits(1.0f) : 1); + if (p.fOperator == Token::Kind::PLUSPLUS) { + this->writeTypedInstruction(p.fType, + ByteCodeInstruction::kAddI, + ByteCodeInstruction::kAddI, + ByteCodeInstruction::kAddF, + 1); + } else { + this->writeTypedInstruction(p.fType, + ByteCodeInstruction::kSubtractI, + ByteCodeInstruction::kSubtractI, + ByteCodeInstruction::kSubtractF, + 1); + } + // Always consume the result as part of the store + lvalue->store(true); + discard = false; + break; + } + default: + SkASSERT(false); + } + return discard; +} + +void ByteCodeGenerator::writeSwizzle(const Swizzle& s) { + if (swizzle_is_simple(s)) { + this->writeVariableExpression(s); + return; + } + + switch (s.fBase->fKind) { + case Expression::kVariableReference_Kind: { + Location location = this->getLocation(*s.fBase); + this->write(location.selectLoad(ByteCodeInstruction::kLoadSwizzle, + ByteCodeInstruction::kLoadSwizzleGlobal, + ByteCodeInstruction::kLoadSwizzleUniform), + s.fComponents.size()); + this->write8(location.fSlot); + this->write8(s.fComponents.size()); + for (int c : s.fComponents) { + this->write8(c); + } + break; + } + default: + this->writeExpression(*s.fBase); + this->write(ByteCodeInstruction::kSwizzle, + s.fComponents.size() - s.fBase->fType.columns()); + this->write8(s.fBase->fType.columns()); + this->write8(s.fComponents.size()); + for (int c : s.fComponents) { + this->write8(c); + } + } +} + +void ByteCodeGenerator::writeTernaryExpression(const TernaryExpression& t) { + int count = SlotCount(t.fType); + SkASSERT(count == SlotCount(t.fIfTrue->fType)); + SkASSERT(count == SlotCount(t.fIfFalse->fType)); + + this->writeExpression(*t.fTest); + this->write(ByteCodeInstruction::kMaskPush); + this->writeExpression(*t.fIfTrue); + this->write(ByteCodeInstruction::kMaskNegate); + this->writeExpression(*t.fIfFalse); + this->write(ByteCodeInstruction::kMaskBlend, count); + this->write8(count); +} + +void ByteCodeGenerator::writeExpression(const Expression& e, bool discard) { + switch (e.fKind) { + case Expression::kBinary_Kind: + discard = this->writeBinaryExpression((BinaryExpression&) e, discard); + break; + case Expression::kBoolLiteral_Kind: + this->writeBoolLiteral((BoolLiteral&) e); + break; + case Expression::kConstructor_Kind: + this->writeConstructor((Constructor&) e); + break; + case Expression::kExternalFunctionCall_Kind: + this->writeExternalFunctionCall((ExternalFunctionCall&) e); + break; + case Expression::kExternalValue_Kind: + this->writeExternalValue((ExternalValueReference&) e); + break; + case Expression::kFieldAccess_Kind: + case Expression::kIndex_Kind: + case Expression::kVariableReference_Kind: + this->writeVariableExpression(e); + break; + case Expression::kFloatLiteral_Kind: + this->writeFloatLiteral((FloatLiteral&) e); + break; + case Expression::kFunctionCall_Kind: + this->writeFunctionCall((FunctionCall&) e); + break; + case Expression::kIntLiteral_Kind: + this->writeIntLiteral((IntLiteral&) e); + break; + case Expression::kNullLiteral_Kind: + this->writeNullLiteral((NullLiteral&) e); + break; + case Expression::kPrefix_Kind: + discard = this->writePrefixExpression((PrefixExpression&) e, discard); + break; + case Expression::kPostfix_Kind: + discard = this->writePostfixExpression((PostfixExpression&) e, discard); + break; + case Expression::kSwizzle_Kind: + this->writeSwizzle((Swizzle&) e); + break; + case Expression::kTernary_Kind: + this->writeTernaryExpression((TernaryExpression&) e); + break; + default: + printf("unsupported expression %s\n", e.description().c_str()); + SkASSERT(false); + } + if (discard) { + int count = SlotCount(e.fType); + if (count > 4) { + this->write(ByteCodeInstruction::kPopN, count); + this->write8(count); + } else if (count != 0) { + this->write(vector_instruction(ByteCodeInstruction::kPop, count)); + } + discard = false; + } +} + +class ByteCodeExternalValueLValue : public ByteCodeGenerator::LValue { +public: + ByteCodeExternalValueLValue(ByteCodeGenerator* generator, ExternalValue& value, int index) + : INHERITED(*generator) + , fCount(ByteCodeGenerator::SlotCount(value.type())) + , fIndex(index) {} + + void load() override { + fGenerator.write(vector_instruction(ByteCodeInstruction::kReadExternal, fCount)); + fGenerator.write8(fCount); + fGenerator.write8(fIndex); + } + + void store(bool discard) override { + if (!discard) { + fGenerator.write(vector_instruction(ByteCodeInstruction::kDup, fCount)); + fGenerator.write8(fCount); + } + fGenerator.write(vector_instruction(ByteCodeInstruction::kWriteExternal, fCount)); + fGenerator.write8(fCount); + fGenerator.write8(fIndex); + } + +private: + typedef LValue INHERITED; + + int fCount; + + int fIndex; +}; + +class ByteCodeSwizzleLValue : public ByteCodeGenerator::LValue { +public: + ByteCodeSwizzleLValue(ByteCodeGenerator* generator, const Swizzle& swizzle) + : INHERITED(*generator) + , fSwizzle(swizzle) {} + + void load() override { + fGenerator.writeSwizzle(fSwizzle); + } + + void store(bool discard) override { + int count = fSwizzle.fComponents.size(); + if (!discard) { + fGenerator.write(vector_instruction(ByteCodeInstruction::kDup, count)); + fGenerator.write8(count); + } + ByteCodeGenerator::Location location = fGenerator.getLocation(*fSwizzle.fBase); + if (location.isOnStack()) { + fGenerator.write(location.selectStore(ByteCodeInstruction::kStoreSwizzleIndirect, + ByteCodeInstruction::kStoreSwizzleIndirectGlobal), + count); + } else { + fGenerator.write(location.selectStore(ByteCodeInstruction::kStoreSwizzle, + ByteCodeInstruction::kStoreSwizzleGlobal), + count); + fGenerator.write8(location.fSlot); + } + fGenerator.write8(count); + for (int c : fSwizzle.fComponents) { + fGenerator.write8(c); + } + } + +private: + const Swizzle& fSwizzle; + + typedef LValue INHERITED; +}; + +class ByteCodeExpressionLValue : public ByteCodeGenerator::LValue { +public: + ByteCodeExpressionLValue(ByteCodeGenerator* generator, const Expression& expr) + : INHERITED(*generator) + , fExpression(expr) {} + + void load() override { + fGenerator.writeVariableExpression(fExpression); + } + + void store(bool discard) override { + int count = ByteCodeGenerator::SlotCount(fExpression.fType); + if (!discard) { + if (count > 4) { + fGenerator.write(ByteCodeInstruction::kDupN, count); + fGenerator.write8(count); + } else { + fGenerator.write(vector_instruction(ByteCodeInstruction::kDup, count)); + fGenerator.write8(count); + } + } + ByteCodeGenerator::Location location = fGenerator.getLocation(fExpression); + if (location.isOnStack() || count > 4) { + if (!location.isOnStack()) { + fGenerator.write(ByteCodeInstruction::kPushImmediate); + fGenerator.write32(location.fSlot); + } + fGenerator.write(location.selectStore(ByteCodeInstruction::kStoreExtended, + ByteCodeInstruction::kStoreExtendedGlobal), + count); + fGenerator.write8(count); + } else { + fGenerator.write( + vector_instruction(location.selectStore(ByteCodeInstruction::kStore, + ByteCodeInstruction::kStoreGlobal), + count)); + fGenerator.write8(location.fSlot); + } + } + +private: + typedef LValue INHERITED; + + const Expression& fExpression; +}; + +std::unique_ptr ByteCodeGenerator::getLValue(const Expression& e) { + switch (e.fKind) { + case Expression::kExternalValue_Kind: { + ExternalValue* value = ((ExternalValueReference&) e).fValue; + int index = fOutput->fExternalValues.size(); + fOutput->fExternalValues.push_back(value); + SkASSERT(index <= 255); + return std::unique_ptr(new ByteCodeExternalValueLValue(this, *value, index)); + } + case Expression::kFieldAccess_Kind: + case Expression::kIndex_Kind: + case Expression::kVariableReference_Kind: + return std::unique_ptr(new ByteCodeExpressionLValue(this, e)); + case Expression::kSwizzle_Kind: { + const Swizzle& s = (const Swizzle&) e; + return swizzle_is_simple(s) + ? std::unique_ptr(new ByteCodeExpressionLValue(this, e)) + : std::unique_ptr(new ByteCodeSwizzleLValue(this, s)); + } + case Expression::kTernary_Kind: + default: + printf("unsupported lvalue %s\n", e.description().c_str()); + return nullptr; + } +} + +void ByteCodeGenerator::writeBlock(const Block& b) { + for (const auto& s : b.fStatements) { + this->writeStatement(*s); + } +} + +void ByteCodeGenerator::setBreakTargets() { + std::vector& breaks = fBreakTargets.top(); + for (DeferredLocation& b : breaks) { + b.set(); + } + fBreakTargets.pop(); +} + +void ByteCodeGenerator::setContinueTargets() { + std::vector& continues = fContinueTargets.top(); + for (DeferredLocation& c : continues) { + c.set(); + } + fContinueTargets.pop(); +} + +void ByteCodeGenerator::writeBreakStatement(const BreakStatement& b) { + // TODO: Include BranchIfAllFalse to top-most LoopNext + this->write(ByteCodeInstruction::kLoopBreak); +} + +void ByteCodeGenerator::writeContinueStatement(const ContinueStatement& c) { + // TODO: Include BranchIfAllFalse to top-most LoopNext + this->write(ByteCodeInstruction::kLoopContinue); +} + +void ByteCodeGenerator::writeDoStatement(const DoStatement& d) { + this->write(ByteCodeInstruction::kLoopBegin); + size_t start = fCode->size(); + this->writeStatement(*d.fStatement); + this->write(ByteCodeInstruction::kLoopNext); + this->writeExpression(*d.fTest); + this->write(ByteCodeInstruction::kLoopMask); + // TODO: Could shorten this with kBranchIfAnyTrue + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation endLocation(this); + this->write(ByteCodeInstruction::kBranch); + this->write16(start); + endLocation.set(); + this->write(ByteCodeInstruction::kLoopEnd); +} + +void ByteCodeGenerator::writeForStatement(const ForStatement& f) { + fContinueTargets.emplace(); + fBreakTargets.emplace(); + if (f.fInitializer) { + this->writeStatement(*f.fInitializer); + } + this->write(ByteCodeInstruction::kLoopBegin); + size_t start = fCode->size(); + if (f.fTest) { + this->writeExpression(*f.fTest); + this->write(ByteCodeInstruction::kLoopMask); + } + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation endLocation(this); + this->writeStatement(*f.fStatement); + this->write(ByteCodeInstruction::kLoopNext); + if (f.fNext) { + this->writeExpression(*f.fNext, true); + } + this->write(ByteCodeInstruction::kBranch); + this->write16(start); + endLocation.set(); + this->write(ByteCodeInstruction::kLoopEnd); +} + +void ByteCodeGenerator::writeIfStatement(const IfStatement& i) { + this->writeExpression(*i.fTest); + this->write(ByteCodeInstruction::kMaskPush); + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation falseLocation(this); + this->writeStatement(*i.fIfTrue); + falseLocation.set(); + if (i.fIfFalse) { + this->write(ByteCodeInstruction::kMaskNegate); + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation endLocation(this); + this->writeStatement(*i.fIfFalse); + endLocation.set(); + } + this->write(ByteCodeInstruction::kMaskPop); +} + +void ByteCodeGenerator::writeReturnStatement(const ReturnStatement& r) { + if (fLoopCount || fConditionCount) { + fErrors.error(r.fOffset, "return not allowed inside conditional or loop"); + return; + } + int count = SlotCount(r.fExpression->fType); + this->writeExpression(*r.fExpression); + + // Technically, the kReturn also pops fOutput->fLocalCount values from the stack, too, but we + // haven't counted pushing those (they're outside the scope of our stack tracking). Instead, + // we account for those in writeFunction(). + + // This is all fine because we don't allow conditional returns, so we only return once anyway. + this->write(ByteCodeInstruction::kReturn, -count); + this->write8(count); +} + +void ByteCodeGenerator::writeSwitchStatement(const SwitchStatement& r) { + // not yet implemented + abort(); +} + +void ByteCodeGenerator::writeVarDeclarations(const VarDeclarations& v) { + for (const auto& declStatement : v.fVars) { + const VarDeclaration& decl = (VarDeclaration&) *declStatement; + // we need to grab the location even if we don't use it, to ensure it has been allocated + Location location = this->getLocation(*decl.fVar); + if (decl.fValue) { + this->writeExpression(*decl.fValue); + int count = SlotCount(decl.fValue->fType); + if (count > 4) { + this->write(ByteCodeInstruction::kPushImmediate); + this->write32(location.fSlot); + this->write(ByteCodeInstruction::kStoreExtended, count); + this->write8(count); + } else { + this->write(vector_instruction(ByteCodeInstruction::kStore, count)); + this->write8(location.fSlot); + } + } + } +} + +void ByteCodeGenerator::writeWhileStatement(const WhileStatement& w) { + this->write(ByteCodeInstruction::kLoopBegin); + size_t cond = fCode->size(); + this->writeExpression(*w.fTest); + this->write(ByteCodeInstruction::kLoopMask); + this->write(ByteCodeInstruction::kBranchIfAllFalse); + DeferredLocation endLocation(this); + this->writeStatement(*w.fStatement); + this->write(ByteCodeInstruction::kLoopNext); + this->write(ByteCodeInstruction::kBranch); + this->write16(cond); + endLocation.set(); + this->write(ByteCodeInstruction::kLoopEnd); +} + +void ByteCodeGenerator::writeStatement(const Statement& s) { + switch (s.fKind) { + case Statement::kBlock_Kind: + this->writeBlock((Block&) s); + break; + case Statement::kBreak_Kind: + this->writeBreakStatement((BreakStatement&) s); + break; + case Statement::kContinue_Kind: + this->writeContinueStatement((ContinueStatement&) s); + break; + case Statement::kDiscard_Kind: + // not yet implemented + abort(); + case Statement::kDo_Kind: + this->writeDoStatement((DoStatement&) s); + break; + case Statement::kExpression_Kind: + this->writeExpression(*((ExpressionStatement&) s).fExpression, true); + break; + case Statement::kFor_Kind: + this->writeForStatement((ForStatement&) s); + break; + case Statement::kIf_Kind: + this->writeIfStatement((IfStatement&) s); + break; + case Statement::kNop_Kind: + break; + case Statement::kReturn_Kind: + this->writeReturnStatement((ReturnStatement&) s); + break; + case Statement::kSwitch_Kind: + this->writeSwitchStatement((SwitchStatement&) s); + break; + case Statement::kVarDeclarations_Kind: + this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration); + break; + case Statement::kWhile_Kind: + this->writeWhileStatement((WhileStatement&) s); + break; + default: + SkASSERT(false); + } +} + +ByteCodeFunction::ByteCodeFunction(const FunctionDeclaration* declaration) + : fName(declaration->fName) { + fParameterCount = 0; + for (const auto& p : declaration->fParameters) { + int slots = ByteCodeGenerator::SlotCount(p->fType); + fParameters.push_back({ slots, (bool)(p->fModifiers.fFlags & Modifiers::kOut_Flag) }); + fParameterCount += slots; + } +} + +} diff --git a/gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.h new file mode 100644 index 0000000000..4e3accd49d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.h @@ -0,0 +1,353 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BYTECODEGENERATOR +#define SKSL_BYTECODEGENERATOR + +#include +#include +#include + +#include "src/sksl/SkSLByteCode.h" +#include "src/sksl/SkSLCodeGenerator.h" +#include "src/sksl/SkSLMemoryLayout.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLBoolLiteral.h" +#include "src/sksl/ir/SkSLBreakStatement.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLContinueStatement.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExternalFunctionCall.h" +#include "src/sksl/ir/SkSLExternalValueReference.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLNullLiteral.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/ir/SkSLWhileStatement.h" +#include "src/sksl/spirv.h" + +namespace SkSL { + +class ByteCodeGenerator : public CodeGenerator { +public: + class LValue { + public: + LValue(ByteCodeGenerator& generator) + : fGenerator(generator) {} + + virtual ~LValue() {} + + /** + * Stack before call: ... lvalue + * Stack after call: ... lvalue load + */ + virtual void load() = 0; + + /** + * Stack before call: ... lvalue value + * Stack after call: ... + */ + virtual void store(bool discard) = 0; + + protected: + ByteCodeGenerator& fGenerator; + }; + + ByteCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + ByteCode* output); + + bool generateCode() override; + + void write8(uint8_t b); + + void write16(uint16_t b); + + void write32(uint32_t b); + + void write(ByteCodeInstruction inst, int count = kUnusedStackCount); + + /** + * Based on 'type', writes the s (signed), u (unsigned), or f (float) instruction. + */ + void writeTypedInstruction(const Type& type, ByteCodeInstruction s, ByteCodeInstruction u, + ByteCodeInstruction f, int count, bool writeCount = true); + + static int SlotCount(const Type& type); + +private: + static constexpr int kUnusedStackCount = INT32_MAX; + static int StackUsage(ByteCodeInstruction, int count); + + // reserves 16 bits in the output code, to be filled in later with an address once we determine + // it + class DeferredLocation { + public: + DeferredLocation(ByteCodeGenerator* generator) + : fGenerator(*generator) + , fOffset(generator->fCode->size()) { + generator->write16(0); + } + +#ifdef SK_DEBUG + ~DeferredLocation() { + SkASSERT(fSet); + } +#endif + + void set() { + int target = fGenerator.fCode->size(); + SkASSERT(target <= 65535); + (*fGenerator.fCode)[fOffset] = target; + (*fGenerator.fCode)[fOffset + 1] = target >> 8; +#ifdef SK_DEBUG + fSet = true; +#endif + } + + private: + ByteCodeGenerator& fGenerator; + size_t fOffset; +#ifdef SK_DEBUG + bool fSet = false; +#endif + }; + + // Intrinsics which do not simply map to a single opcode + enum class SpecialIntrinsic { + kDot, + }; + + struct Intrinsic { + Intrinsic(ByteCodeInstruction instruction) + : fIsSpecial(false) + , fValue(instruction) {} + + Intrinsic(SpecialIntrinsic special) + : fIsSpecial(true) + , fValue(special) {} + + bool fIsSpecial; + + union Value { + Value(ByteCodeInstruction instruction) + : fInstruction(instruction) {} + + Value(SpecialIntrinsic special) + : fSpecial(special) {} + + ByteCodeInstruction fInstruction; + SpecialIntrinsic fSpecial; + } fValue; + }; + + + // Similar to Variable::Storage, but locals and parameters are grouped together, and globals + // are further subidivided into uniforms and other (writable) globals. + enum class Storage { + kLocal, // include parameters + kGlobal, // non-uniform globals + kUniform, // uniform globals + }; + + struct Location { + int fSlot; + Storage fStorage; + + // Not really invalid, but a "safe" placeholder to be more explicit at call-sites + static Location MakeInvalid() { return { 0, Storage::kLocal }; } + + Location makeOnStack() { return { -1, fStorage }; } + bool isOnStack() const { return fSlot < 0; } + + Location operator+(int offset) { + SkASSERT(fSlot >= 0); + return { fSlot + offset, fStorage }; + } + + ByteCodeInstruction selectLoad(ByteCodeInstruction local, + ByteCodeInstruction global, + ByteCodeInstruction uniform) const { + switch (fStorage) { + case Storage::kLocal: return local; + case Storage::kGlobal: return global; + case Storage::kUniform: return uniform; + } + SkUNREACHABLE; + } + + ByteCodeInstruction selectStore(ByteCodeInstruction local, + ByteCodeInstruction global) const { + switch (fStorage) { + case Storage::kLocal: return local; + case Storage::kGlobal: return global; + case Storage::kUniform: ABORT("Trying to store to a uniform"); break; + } + return local; + } + }; + + /** + * Returns the local slot into which var should be stored, allocating a new slot if it has not + * already been assigned one. Compound variables (e.g. vectors) will consume more than one local + * slot, with the getLocation return value indicating where the first element should be stored. + */ + Location getLocation(const Variable& var); + + /** + * As above, but computes the (possibly dynamic) address of an expression involving indexing & + * field access. If the address is known, it's returned. If not, -1 is returned, and the + * location will be left on the top of the stack. + */ + Location getLocation(const Expression& expr); + + void gatherUniforms(const Type& type, const String& name); + + std::unique_ptr writeFunction(const FunctionDefinition& f); + + void writeVarDeclarations(const VarDeclarations& decl); + + void writeVariableExpression(const Expression& expr); + + void writeExpression(const Expression& expr, bool discard = false); + + /** + * Pushes whatever values are required by the lvalue onto the stack, and returns an LValue + * permitting loads and stores to it. + */ + std::unique_ptr getLValue(const Expression& expr); + + void writeIntrinsicCall(const FunctionCall& c); + + void writeFunctionCall(const FunctionCall& c); + + void writeConstructor(const Constructor& c); + + void writeExternalFunctionCall(const ExternalFunctionCall& c); + + void writeExternalValue(const ExternalValueReference& r); + + void writeSwizzle(const Swizzle& swizzle); + + bool writeBinaryExpression(const BinaryExpression& b, bool discard); + + void writeTernaryExpression(const TernaryExpression& t); + + void writeNullLiteral(const NullLiteral& n); + + bool writePrefixExpression(const PrefixExpression& p, bool discard); + + bool writePostfixExpression(const PostfixExpression& p, bool discard); + + void writeBoolLiteral(const BoolLiteral& b); + + void writeIntLiteral(const IntLiteral& i); + + void writeFloatLiteral(const FloatLiteral& f); + + void writeStatement(const Statement& s); + + void writeBlock(const Block& b); + + void writeBreakStatement(const BreakStatement& b); + + void writeContinueStatement(const ContinueStatement& c); + + void writeIfStatement(const IfStatement& stmt); + + void writeForStatement(const ForStatement& f); + + void writeWhileStatement(const WhileStatement& w); + + void writeDoStatement(const DoStatement& d); + + void writeSwitchStatement(const SwitchStatement& s); + + void writeReturnStatement(const ReturnStatement& r); + + // updates the current set of breaks to branch to the current location + void setBreakTargets(); + + // updates the current set of continues to branch to the current location + void setContinueTargets(); + + void enterLoop() { + fLoopCount++; + fMaxLoopCount = std::max(fMaxLoopCount, fLoopCount); + } + + void exitLoop() { + SkASSERT(fLoopCount > 0); + fLoopCount--; + } + + void enterCondition() { + fConditionCount++; + fMaxConditionCount = std::max(fMaxConditionCount, fConditionCount); + } + + void exitCondition() { + SkASSERT(fConditionCount > 0); + fConditionCount--; + } + + const Context& fContext; + + ByteCode* fOutput; + + const FunctionDefinition* fFunction; + + std::vector* fCode; + + std::vector fLocals; + + std::stack> fContinueTargets; + + std::stack> fBreakTargets; + + std::vector fFunctions; + + int fParameterCount; + int fStackCount; + int fMaxStackCount; + + int fLoopCount; + int fMaxLoopCount; + int fConditionCount; + int fMaxConditionCount; + + const std::unordered_map fIntrinsics; + + friend class DeferredLocation; + friend class ByteCodeExpressionLValue; + friend class ByteCodeSwizzleLValue; + + typedef CodeGenerator INHERITED; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLCFGGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLCFGGenerator.cpp new file mode 100644 index 0000000000..8ef2dd59e8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCFGGenerator.cpp @@ -0,0 +1,673 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLCFGGenerator.h" + +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExternalFunctionCall.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLWhileStatement.h" + +namespace SkSL { + +BlockId CFG::newBlock() { + BlockId result = fBlocks.size(); + fBlocks.emplace_back(); + if (fBlocks.size() > 1) { + this->addExit(fCurrent, result); + } + fCurrent = result; + return result; +} + +BlockId CFG::newIsolatedBlock() { + BlockId result = fBlocks.size(); + fBlocks.emplace_back(); + return result; +} + +void CFG::addExit(BlockId from, BlockId to) { + if (from == 0 || fBlocks[from].fEntrances.size()) { + fBlocks[from].fExits.insert(to); + fBlocks[to].fEntrances.insert(from); + } +} + +void CFG::dump() { + for (size_t i = 0; i < fBlocks.size(); i++) { + printf("Block %d\n-------\nBefore: ", (int) i); + const char* separator = ""; + for (auto iter = fBlocks[i].fBefore.begin(); iter != fBlocks[i].fBefore.end(); iter++) { + printf("%s%s = %s", separator, iter->first->description().c_str(), + iter->second ? (*iter->second)->description().c_str() : ""); + separator = ", "; + } + printf("\nEntrances: "); + separator = ""; + for (BlockId b : fBlocks[i].fEntrances) { + printf("%s%d", separator, (int) b); + separator = ", "; + } + printf("\n"); + for (size_t j = 0; j < fBlocks[i].fNodes.size(); j++) { + BasicBlock::Node& n = fBlocks[i].fNodes[j]; + printf("Node %d (%p): %s\n", (int) j, &n, n.fKind == BasicBlock::Node::kExpression_Kind + ? (*n.expression())->description().c_str() + : (*n.statement())->description().c_str()); + } + printf("Exits: "); + separator = ""; + for (BlockId b : fBlocks[i].fExits) { + printf("%s%d", separator, (int) b); + separator = ", "; + } + printf("\n\n"); + } +} + +bool BasicBlock::tryRemoveExpressionBefore(std::vector::iterator* iter, + Expression* e) { + if (e->fKind == Expression::kTernary_Kind) { + return false; + } + bool result; + if ((*iter)->fKind == BasicBlock::Node::kExpression_Kind) { + SkASSERT((*iter)->expression()->get() != e); + Expression* old = (*iter)->expression()->get(); + do { + if ((*iter) == fNodes.begin()) { + return false; + } + --(*iter); + } while ((*iter)->fKind != BasicBlock::Node::kExpression_Kind || + (*iter)->expression()->get() != e); + result = this->tryRemoveExpression(iter); + while ((*iter)->fKind != BasicBlock::Node::kExpression_Kind || + (*iter)->expression()->get() != old) { + SkASSERT(*iter != fNodes.end()); + ++(*iter); + } + } else { + Statement* old = (*iter)->statement()->get(); + do { + if ((*iter) == fNodes.begin()) { + return false; + } + --(*iter); + } while ((*iter)->fKind != BasicBlock::Node::kExpression_Kind || + (*iter)->expression()->get() != e); + result = this->tryRemoveExpression(iter); + while ((*iter)->fKind != BasicBlock::Node::kStatement_Kind || + (*iter)->statement()->get() != old) { + SkASSERT(*iter != fNodes.end()); + ++(*iter); + } + } + return result; +} + +bool BasicBlock::tryRemoveLValueBefore(std::vector::iterator* iter, + Expression* lvalue) { + switch (lvalue->fKind) { + case Expression::kExternalValue_Kind: // fall through + case Expression::kVariableReference_Kind: + return true; + case Expression::kSwizzle_Kind: + return this->tryRemoveLValueBefore(iter, ((Swizzle*) lvalue)->fBase.get()); + case Expression::kFieldAccess_Kind: + return this->tryRemoveLValueBefore(iter, ((FieldAccess*) lvalue)->fBase.get()); + case Expression::kIndex_Kind: + if (!this->tryRemoveLValueBefore(iter, ((IndexExpression*) lvalue)->fBase.get())) { + return false; + } + return this->tryRemoveExpressionBefore(iter, ((IndexExpression*) lvalue)->fIndex.get()); + case Expression::kTernary_Kind: + if (!this->tryRemoveExpressionBefore(iter, + ((TernaryExpression*) lvalue)->fTest.get())) { + return false; + } + if (!this->tryRemoveLValueBefore(iter, ((TernaryExpression*) lvalue)->fIfTrue.get())) { + return false; + } + return this->tryRemoveLValueBefore(iter, ((TernaryExpression*) lvalue)->fIfFalse.get()); + default: + ABORT("invalid lvalue: %s\n", lvalue->description().c_str()); + } +} + +bool BasicBlock::tryRemoveExpression(std::vector::iterator* iter) { + Expression* expr = (*iter)->expression()->get(); + switch (expr->fKind) { + case Expression::kBinary_Kind: { + BinaryExpression* b = (BinaryExpression*) expr; + if (b->fOperator == Token::EQ) { + if (!this->tryRemoveLValueBefore(iter, b->fLeft.get())) { + return false; + } + } else if (!this->tryRemoveExpressionBefore(iter, b->fLeft.get())) { + return false; + } + if (!this->tryRemoveExpressionBefore(iter, b->fRight.get())) { + return false; + } + SkASSERT((*iter)->expression()->get() == expr); + *iter = fNodes.erase(*iter); + return true; + } + case Expression::kTernary_Kind: { + // ternaries cross basic block boundaries, must regenerate the CFG to remove it + return false; + } + case Expression::kFieldAccess_Kind: { + FieldAccess* f = (FieldAccess*) expr; + if (!this->tryRemoveExpressionBefore(iter, f->fBase.get())) { + return false; + } + *iter = fNodes.erase(*iter); + return true; + } + case Expression::kSwizzle_Kind: { + Swizzle* s = (Swizzle*) expr; + if (s->fBase && !this->tryRemoveExpressionBefore(iter, s->fBase.get())) { + return false; + } + *iter = fNodes.erase(*iter); + return true; + } + case Expression::kIndex_Kind: { + IndexExpression* idx = (IndexExpression*) expr; + if (!this->tryRemoveExpressionBefore(iter, idx->fBase.get())) { + return false; + } + if (!this->tryRemoveExpressionBefore(iter, idx->fIndex.get())) { + return false; + } + *iter = fNodes.erase(*iter); + return true; + } + case Expression::kConstructor_Kind: { + Constructor* c = (Constructor*) expr; + for (auto& arg : c->fArguments) { + if (!this->tryRemoveExpressionBefore(iter, arg.get())) { + return false; + } + SkASSERT((*iter)->expression()->get() == expr); + } + *iter = fNodes.erase(*iter); + return true; + } + case Expression::kFunctionCall_Kind: { + FunctionCall* f = (FunctionCall*) expr; + for (auto& arg : f->fArguments) { + if (!this->tryRemoveExpressionBefore(iter, arg.get())) { + return false; + } + SkASSERT((*iter)->expression()->get() == expr); + } + *iter = fNodes.erase(*iter); + return true; + } + case Expression::kPrefix_Kind: + if (!this->tryRemoveExpressionBefore(iter, + ((PrefixExpression*) expr)->fOperand.get())) { + return false; + } + *iter = fNodes.erase(*iter); + return true; + case Expression::kPostfix_Kind: + if (!this->tryRemoveExpressionBefore(iter, + ((PrefixExpression*) expr)->fOperand.get())) { + return false; + } + *iter = fNodes.erase(*iter); + return true; + case Expression::kBoolLiteral_Kind: // fall through + case Expression::kFloatLiteral_Kind: // fall through + case Expression::kIntLiteral_Kind: // fall through + case Expression::kSetting_Kind: // fall through + case Expression::kVariableReference_Kind: + *iter = fNodes.erase(*iter); + return true; + default: + ABORT("unhandled expression: %s\n", expr->description().c_str()); + } +} + +bool BasicBlock::tryInsertExpression(std::vector::iterator* iter, + std::unique_ptr* expr) { + switch ((*expr)->fKind) { + case Expression::kBinary_Kind: { + BinaryExpression* b = (BinaryExpression*) expr->get(); + if (!this->tryInsertExpression(iter, &b->fRight)) { + return false; + } + ++(*iter); + if (!this->tryInsertExpression(iter, &b->fLeft)) { + return false; + } + ++(*iter); + BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr }; + *iter = fNodes.insert(*iter, node); + return true; + } + case Expression::kBoolLiteral_Kind: // fall through + case Expression::kFloatLiteral_Kind: // fall through + case Expression::kIntLiteral_Kind: // fall through + case Expression::kVariableReference_Kind: { + BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr }; + *iter = fNodes.insert(*iter, node); + return true; + } + case Expression::kConstructor_Kind: { + Constructor* c = (Constructor*) expr->get(); + for (auto& arg : c->fArguments) { + if (!this->tryInsertExpression(iter, &arg)) { + return false; + } + ++(*iter); + } + BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr }; + *iter = fNodes.insert(*iter, node); + return true; + } + case Expression::kSwizzle_Kind: { + Swizzle* s = (Swizzle*) expr->get(); + if (!this->tryInsertExpression(iter, &s->fBase)) { + return false; + } + ++(*iter); + BasicBlock::Node node = { BasicBlock::Node::kExpression_Kind, true, expr, nullptr }; + *iter = fNodes.insert(*iter, node); + return true; + } + default: + return false; + } +} + +void CFGGenerator::addExpression(CFG& cfg, std::unique_ptr* e, bool constantPropagate) { + SkASSERT(e); + switch ((*e)->fKind) { + case Expression::kBinary_Kind: { + BinaryExpression* b = (BinaryExpression*) e->get(); + switch (b->fOperator) { + case Token::LOGICALAND: // fall through + case Token::LOGICALOR: { + // this isn't as precise as it could be -- we don't bother to track that if we + // early exit from a logical and/or, we know which branch of an 'if' we're going + // to hit -- but it won't make much difference in practice. + this->addExpression(cfg, &b->fLeft, constantPropagate); + BlockId start = cfg.fCurrent; + cfg.newBlock(); + this->addExpression(cfg, &b->fRight, constantPropagate); + cfg.newBlock(); + cfg.addExit(start, cfg.fCurrent); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ + BasicBlock::Node::kExpression_Kind, + constantPropagate, + e, + nullptr + }); + break; + } + case Token::EQ: { + this->addExpression(cfg, &b->fRight, constantPropagate); + this->addLValue(cfg, &b->fLeft); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ + BasicBlock::Node::kExpression_Kind, + constantPropagate, + e, + nullptr + }); + break; + } + default: + this->addExpression(cfg, &b->fLeft, !Compiler::IsAssignment(b->fOperator)); + this->addExpression(cfg, &b->fRight, constantPropagate); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ + BasicBlock::Node::kExpression_Kind, + constantPropagate, + e, + nullptr + }); + } + break; + } + case Expression::kConstructor_Kind: { + Constructor* c = (Constructor*) e->get(); + for (auto& arg : c->fArguments) { + this->addExpression(cfg, &arg, constantPropagate); + } + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + } + case Expression::kExternalFunctionCall_Kind: { + ExternalFunctionCall* c = (ExternalFunctionCall*) e->get(); + for (auto& arg : c->fArguments) { + this->addExpression(cfg, &arg, constantPropagate); + } + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + } + case Expression::kFunctionCall_Kind: { + FunctionCall* c = (FunctionCall*) e->get(); + for (auto& arg : c->fArguments) { + this->addExpression(cfg, &arg, constantPropagate); + } + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + } + case Expression::kFieldAccess_Kind: + this->addExpression(cfg, &((FieldAccess*) e->get())->fBase, constantPropagate); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + case Expression::kIndex_Kind: + this->addExpression(cfg, &((IndexExpression*) e->get())->fBase, constantPropagate); + this->addExpression(cfg, &((IndexExpression*) e->get())->fIndex, constantPropagate); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + case Expression::kPrefix_Kind: { + PrefixExpression* p = (PrefixExpression*) e->get(); + this->addExpression(cfg, &p->fOperand, constantPropagate && + p->fOperator != Token::PLUSPLUS && + p->fOperator != Token::MINUSMINUS); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + } + case Expression::kPostfix_Kind: + this->addExpression(cfg, &((PostfixExpression*) e->get())->fOperand, false); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + case Expression::kSwizzle_Kind: + this->addExpression(cfg, &((Swizzle*) e->get())->fBase, constantPropagate); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + case Expression::kBoolLiteral_Kind: // fall through + case Expression::kExternalValue_Kind: // fall through + case Expression::kFloatLiteral_Kind: // fall through + case Expression::kIntLiteral_Kind: // fall through + case Expression::kNullLiteral_Kind: // fall through + case Expression::kSetting_Kind: // fall through + case Expression::kVariableReference_Kind: + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + break; + case Expression::kTernary_Kind: { + TernaryExpression* t = (TernaryExpression*) e->get(); + this->addExpression(cfg, &t->fTest, constantPropagate); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind, + constantPropagate, e, nullptr }); + BlockId start = cfg.fCurrent; + cfg.newBlock(); + this->addExpression(cfg, &t->fIfTrue, constantPropagate); + BlockId next = cfg.newBlock(); + cfg.fCurrent = start; + cfg.newBlock(); + this->addExpression(cfg, &t->fIfFalse, constantPropagate); + cfg.addExit(cfg.fCurrent, next); + cfg.fCurrent = next; + break; + } + case Expression::kFunctionReference_Kind: // fall through + case Expression::kTypeReference_Kind: // fall through + case Expression::kDefined_Kind: + SkASSERT(false); + break; + } +} + +// adds expressions that are evaluated as part of resolving an lvalue +void CFGGenerator::addLValue(CFG& cfg, std::unique_ptr* e) { + switch ((*e)->fKind) { + case Expression::kFieldAccess_Kind: + this->addLValue(cfg, &((FieldAccess&) **e).fBase); + break; + case Expression::kIndex_Kind: + this->addLValue(cfg, &((IndexExpression&) **e).fBase); + this->addExpression(cfg, &((IndexExpression&) **e).fIndex, true); + break; + case Expression::kSwizzle_Kind: + this->addLValue(cfg, &((Swizzle&) **e).fBase); + break; + case Expression::kExternalValue_Kind: // fall through + case Expression::kVariableReference_Kind: + break; + case Expression::kTernary_Kind: + this->addExpression(cfg, &((TernaryExpression&) **e).fTest, true); + // Technically we will of course only evaluate one or the other, but if the test turns + // out to be constant, the ternary will get collapsed down to just one branch anyway. So + // it should be ok to pretend that we always evaluate both branches here. + this->addLValue(cfg, &((TernaryExpression&) **e).fIfTrue); + this->addLValue(cfg, &((TernaryExpression&) **e).fIfFalse); + break; + default: + // not an lvalue, can't happen + SkASSERT(false); + break; + } +} + +static bool is_true(Expression& expr) { + return expr.fKind == Expression::kBoolLiteral_Kind && ((BoolLiteral&) expr).fValue; +} + +void CFGGenerator::addStatement(CFG& cfg, std::unique_ptr* s) { + switch ((*s)->fKind) { + case Statement::kBlock_Kind: + for (auto& child : ((Block&) **s).fStatements) { + addStatement(cfg, &child); + } + break; + case Statement::kIf_Kind: { + IfStatement& ifs = (IfStatement&) **s; + this->addExpression(cfg, &ifs.fTest, true); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + BlockId start = cfg.fCurrent; + cfg.newBlock(); + this->addStatement(cfg, &ifs.fIfTrue); + BlockId next = cfg.newBlock(); + if (ifs.fIfFalse) { + cfg.fCurrent = start; + cfg.newBlock(); + this->addStatement(cfg, &ifs.fIfFalse); + cfg.addExit(cfg.fCurrent, next); + cfg.fCurrent = next; + } else { + cfg.addExit(start, next); + } + break; + } + case Statement::kExpression_Kind: { + this->addExpression(cfg, &((ExpressionStatement&) **s).fExpression, true); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + break; + } + case Statement::kVarDeclarations_Kind: { + VarDeclarationsStatement& decls = ((VarDeclarationsStatement&) **s); + for (auto& stmt : decls.fDeclaration->fVars) { + if (stmt->fKind == Statement::kNop_Kind) { + continue; + } + VarDeclaration& vd = (VarDeclaration&) *stmt; + if (vd.fValue) { + this->addExpression(cfg, &vd.fValue, true); + } + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, + false, nullptr, &stmt }); + } + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + break; + } + case Statement::kDiscard_Kind: + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + cfg.fCurrent = cfg.newIsolatedBlock(); + break; + case Statement::kReturn_Kind: { + ReturnStatement& r = ((ReturnStatement&) **s); + if (r.fExpression) { + this->addExpression(cfg, &r.fExpression, true); + } + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + cfg.fCurrent = cfg.newIsolatedBlock(); + break; + } + case Statement::kBreak_Kind: + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + cfg.addExit(cfg.fCurrent, fLoopExits.top()); + cfg.fCurrent = cfg.newIsolatedBlock(); + break; + case Statement::kContinue_Kind: + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + cfg.addExit(cfg.fCurrent, fLoopContinues.top()); + cfg.fCurrent = cfg.newIsolatedBlock(); + break; + case Statement::kWhile_Kind: { + WhileStatement& w = (WhileStatement&) **s; + BlockId loopStart = cfg.newBlock(); + fLoopContinues.push(loopStart); + BlockId loopExit = cfg.newIsolatedBlock(); + fLoopExits.push(loopExit); + this->addExpression(cfg, &w.fTest, true); + BlockId test = cfg.fCurrent; + if (!is_true(*w.fTest)) { + cfg.addExit(test, loopExit); + } + cfg.newBlock(); + this->addStatement(cfg, &w.fStatement); + cfg.addExit(cfg.fCurrent, loopStart); + fLoopContinues.pop(); + fLoopExits.pop(); + cfg.fCurrent = loopExit; + break; + } + case Statement::kDo_Kind: { + DoStatement& d = (DoStatement&) **s; + BlockId loopStart = cfg.newBlock(); + fLoopContinues.push(loopStart); + BlockId loopExit = cfg.newIsolatedBlock(); + fLoopExits.push(loopExit); + this->addStatement(cfg, &d.fStatement); + this->addExpression(cfg, &d.fTest, true); + cfg.addExit(cfg.fCurrent, loopExit); + cfg.addExit(cfg.fCurrent, loopStart); + fLoopContinues.pop(); + fLoopExits.pop(); + cfg.fCurrent = loopExit; + break; + } + case Statement::kFor_Kind: { + ForStatement& f = (ForStatement&) **s; + if (f.fInitializer) { + this->addStatement(cfg, &f.fInitializer); + } + BlockId loopStart = cfg.newBlock(); + BlockId next = cfg.newIsolatedBlock(); + fLoopContinues.push(next); + BlockId loopExit = cfg.newIsolatedBlock(); + fLoopExits.push(loopExit); + if (f.fTest) { + this->addExpression(cfg, &f.fTest, true); + // this isn't quite right; we should have an exit from here to the loop exit, and + // remove the exit from the loop body to the loop exit. Structuring it like this + // forces the optimizer to believe that the loop body is always executed at least + // once. While not strictly correct, this avoids incorrect "variable not assigned" + // errors on variables which are assigned within the loop. The correct solution to + // this is to analyze the loop to see whether or not at least one iteration is + // guaranteed to happen, but for the time being we take the easy way out. + } + cfg.newBlock(); + this->addStatement(cfg, &f.fStatement); + cfg.addExit(cfg.fCurrent, next); + cfg.fCurrent = next; + if (f.fNext) { + this->addExpression(cfg, &f.fNext, true); + } + cfg.addExit(cfg.fCurrent, loopStart); + cfg.addExit(cfg.fCurrent, loopExit); + fLoopContinues.pop(); + fLoopExits.pop(); + cfg.fCurrent = loopExit; + break; + } + case Statement::kSwitch_Kind: { + SwitchStatement& ss = (SwitchStatement&) **s; + this->addExpression(cfg, &ss.fValue, true); + cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kStatement_Kind, false, + nullptr, s }); + BlockId start = cfg.fCurrent; + BlockId switchExit = cfg.newIsolatedBlock(); + fLoopExits.push(switchExit); + for (const auto& c : ss.fCases) { + cfg.newBlock(); + cfg.addExit(start, cfg.fCurrent); + if (c->fValue) { + // technically this should go in the start block, but it doesn't actually matter + // because it must be constant. Not worth running two loops for. + this->addExpression(cfg, &c->fValue, true); + } + for (auto& caseStatement : c->fStatements) { + this->addStatement(cfg, &caseStatement); + } + } + cfg.addExit(cfg.fCurrent, switchExit); + // note that unlike GLSL, our grammar requires the default case to be last + if (0 == ss.fCases.size() || ss.fCases[ss.fCases.size() - 1]->fValue) { + // switch does not have a default clause, mark that it can skip straight to the end + cfg.addExit(start, switchExit); + } + fLoopExits.pop(); + cfg.fCurrent = switchExit; + break; + } + case Statement::kNop_Kind: + break; + default: + printf("statement: %s\n", (*s)->description().c_str()); + ABORT("unsupported statement kind"); + } +} + +CFG CFGGenerator::getCFG(FunctionDefinition& f) { + CFG result; + result.fStart = result.newBlock(); + result.fCurrent = result.fStart; + this->addStatement(result, &f.fBody); + result.newBlock(); + result.fExit = result.fCurrent; + return result; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLCFGGenerator.h b/gfx/skia/skia/src/sksl/SkSLCFGGenerator.h new file mode 100644 index 0000000000..7bcb89e360 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCFGGenerator.h @@ -0,0 +1,170 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CFGGENERATOR +#define SKSL_CFGGENERATOR + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" + +#include +#include + +namespace SkSL { + +// index of a block within CFG.fBlocks +typedef size_t BlockId; + +struct BasicBlock { + struct Node { + enum Kind { + kStatement_Kind, + kExpression_Kind + }; + + Node(Kind kind, bool constantPropagation, std::unique_ptr* expression, + std::unique_ptr* statement) + : fKind(kind) + , fConstantPropagation(constantPropagation) + , fExpression(expression) + , fStatement(statement) {} + + std::unique_ptr* expression() const { + SkASSERT(fKind == kExpression_Kind); + return fExpression; + } + + void setExpression(std::unique_ptr expr) { + SkASSERT(fKind == kExpression_Kind); + *fExpression = std::move(expr); + } + + std::unique_ptr* statement() const { + SkASSERT(fKind == kStatement_Kind); + return fStatement; + } + + void setStatement(std::unique_ptr stmt) { + SkASSERT(fKind == kStatement_Kind); + *fStatement = std::move(stmt); + } + + String description() const { + if (fKind == kStatement_Kind) { + return (*fStatement)->description(); + } else { + SkASSERT(fKind == kExpression_Kind); + return (*fExpression)->description(); + } + } + + Kind fKind; + // if false, this node should not be subject to constant propagation. This happens with + // compound assignment (i.e. x *= 2), in which the value x is used as an rvalue for + // multiplication by 2 and then as an lvalue for assignment purposes. Since there is only + // one "x" node, replacing it with a constant would break the assignment and we suppress + // it. Down the road, we should handle this more elegantly by substituting a regular + // assignment if the target is constant (i.e. x = 1; x *= 2; should become x = 1; x = 1 * 2; + // and then collapse down to a simple x = 2;). + bool fConstantPropagation; + + private: + // we store pointers to the unique_ptrs so that we can replace expressions or statements + // during optimization without having to regenerate the entire tree + std::unique_ptr* fExpression; + std::unique_ptr* fStatement; + }; + + /** + * Attempts to remove the expression (and its subexpressions) pointed to by the iterator. If the + * expression can be cleanly removed, returns true and updates the iterator to point to the + * expression after the deleted expression. Otherwise returns false (and the CFG will need to be + * regenerated). + */ + bool tryRemoveExpression(std::vector::iterator* iter); + + /** + * Locates and attempts remove an expression occurring before the expression pointed to by iter. + * If the expression can be cleanly removed, returns true and resets iter to a valid iterator + * pointing to the same expression it did initially. Otherwise returns false (and the CFG will + * need to be regenerated). + */ + bool tryRemoveExpressionBefore(std::vector::iterator* iter, Expression* e); + + /** + * As tryRemoveExpressionBefore, but for lvalues. As lvalues are at most partially evaluated + * (for instance, x[i] = 0 evaluates i but not x) this will only look for the parts of the + * lvalue that are actually evaluated. + */ + bool tryRemoveLValueBefore(std::vector::iterator* iter, Expression* lvalue); + + /** + * Attempts to inserts a new expression before the node pointed to by iter. If the + * expression can be cleanly inserted, returns true and updates the iterator to point to the + * newly inserted expression. Otherwise returns false (and the CFG will need to be regenerated). + */ + bool tryInsertExpression(std::vector::iterator* iter, + std::unique_ptr* expr); + + std::vector fNodes; + std::set fEntrances; + std::set fExits; + // variable definitions upon entering this basic block (null expression = undefined) + DefinitionMap fBefore; +}; + +struct CFG { + BlockId fStart; + BlockId fExit; + std::vector fBlocks; + + void dump(); + +private: + BlockId fCurrent; + + // Adds a new block, adds an exit* from the current block to the new block, then marks the new + // block as the current block + // *see note in addExit() + BlockId newBlock(); + + // Adds a new block, but does not mark it current or add an exit from the current block + BlockId newIsolatedBlock(); + + // Adds an exit from the 'from' block to the 'to' block + // Note that we skip adding the exit if the 'from' block is itself unreachable; this means that + // we don't actually have to trace the tree to see if a particular block is unreachable, we can + // just check to see if it has any entrances. This does require a bit of care in the order in + // which we set the CFG up. + void addExit(BlockId from, BlockId to); + + friend class CFGGenerator; +}; + +/** + * Converts functions into control flow graphs. + */ +class CFGGenerator { +public: + CFGGenerator() {} + + CFG getCFG(FunctionDefinition& f); + +private: + void addStatement(CFG& cfg, std::unique_ptr* s); + + void addExpression(CFG& cfg, std::unique_ptr* e, bool constantPropagate); + + void addLValue(CFG& cfg, std::unique_ptr* e); + + std::stack fLoopContinues; + std::stack fLoopExits; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLCPP.h b/gfx/skia/skia/src/sksl/SkSLCPP.h new file mode 100644 index 0000000000..11b5b00407 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCPP.h @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CPP +#define SKSL_CPP + +// functions used by CPP programs created by skslc + +#include +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" + +using std::abs; + +struct Float4 { + Float4(float x, float y, float z, float w) + : fX(x) + , fY(y) + , fZ(z) + , fW(w) {} + + operator SkRect() const { + return SkRect::MakeLTRB(fX, fY, fZ, fW); + } + +private: + float fX; + float fY; + float fZ; + float fW; +}; + +// macros to make sk_Caps. work from C++ code +#define sk_Caps (*args.fShaderCaps) + +#define floatIs32Bits floatIs32Bits() + +// functions to make GLSL constructors work from C++ code +inline SkPoint float2(float xy) { return SkPoint::Make(xy, xy); } + +inline SkPoint float2(float x, float y) { return SkPoint::Make(x, y); } + +inline Float4 float4(float xyzw) { return Float4(xyzw, xyzw, xyzw, xyzw); } + +inline Float4 float4(float x, float y, float z, float w) { return Float4(x, y, z, w); } + +#define half2 float2 + +#define half3 float3 + +#define half4 float4 + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.cpp new file mode 100644 index 0000000000..a4d2e3b590 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.cpp @@ -0,0 +1,1343 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLCPPCodeGenerator.h" + +#include "src/sksl/SkSLCPPUniformCTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLHCodeGenerator.h" + +#include + +namespace SkSL { + +static bool needs_uniform_var(const Variable& var) { + return (var.fModifiers.fFlags & Modifiers::kUniform_Flag) && + var.fType.kind() != Type::kSampler_Kind; +} + +CPPCodeGenerator::CPPCodeGenerator(const Context* context, const Program* program, + ErrorReporter* errors, String name, OutputStream* out) +: INHERITED(context, program, errors, out) +, fName(std::move(name)) +, fFullName(String::printf("Gr%s", fName.c_str())) +, fSectionAndParameterHelper(program, *errors) { + fLineEnding = "\\n"; + fTextureFunctionOverride = "sample"; +} + +void CPPCodeGenerator::writef(const char* s, va_list va) { + static constexpr int BUFFER_SIZE = 1024; + va_list copy; + va_copy(copy, va); + char buffer[BUFFER_SIZE]; + int length = vsnprintf(buffer, BUFFER_SIZE, s, va); + if (length < BUFFER_SIZE) { + fOut->write(buffer, length); + } else { + std::unique_ptr heap(new char[length + 1]); + vsprintf(heap.get(), s, copy); + fOut->write(heap.get(), length); + } + va_end(copy); +} + +void CPPCodeGenerator::writef(const char* s, ...) { + va_list va; + va_start(va, s); + this->writef(s, va); + va_end(va); +} + +void CPPCodeGenerator::writeHeader() { +} + +bool CPPCodeGenerator::usesPrecisionModifiers() const { + return false; +} + +String CPPCodeGenerator::getTypeName(const Type& type) { + return type.name(); +} + +void CPPCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + if (b.fOperator == Token::PERCENT) { + // need to use "%%" instead of "%" b/c the code will be inside of a printf + Precedence precedence = GetBinaryPrecedence(b.fOperator); + if (precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*b.fLeft, precedence); + this->write(" %% "); + this->writeExpression(*b.fRight, precedence); + if (precedence >= parentPrecedence) { + this->write(")"); + } + } else if (b.fLeft->fKind == Expression::kNullLiteral_Kind || + b.fRight->fKind == Expression::kNullLiteral_Kind) { + const Variable* var; + if (b.fLeft->fKind != Expression::kNullLiteral_Kind) { + SkASSERT(b.fLeft->fKind == Expression::kVariableReference_Kind); + var = &((VariableReference&) *b.fLeft).fVariable; + } else { + SkASSERT(b.fRight->fKind == Expression::kVariableReference_Kind); + var = &((VariableReference&) *b.fRight).fVariable; + } + SkASSERT(var->fType.kind() == Type::kNullable_Kind && + var->fType.componentType() == *fContext.fFragmentProcessor_Type); + this->write("%s"); + const char* op; + switch (b.fOperator) { + case Token::EQEQ: + op = "<"; + break; + case Token::NEQ: + op = ">="; + break; + default: + SkASSERT(false); + } + fFormatArgs.push_back("_outer." + String(var->fName) + "_index " + op + " 0 ? \"true\" " + ": \"false\""); + } else { + INHERITED::writeBinaryExpression(b, parentPrecedence); + } +} + +void CPPCodeGenerator::writeIndexExpression(const IndexExpression& i) { + const Expression& base = *i.fBase; + if (base.fKind == Expression::kVariableReference_Kind) { + int builtin = ((VariableReference&) base).fVariable.fModifiers.fLayout.fBuiltin; + if (SK_TRANSFORMEDCOORDS2D_BUILTIN == builtin) { + this->write("%s"); + if (i.fIndex->fKind != Expression::kIntLiteral_Kind) { + fErrors.error(i.fIndex->fOffset, + "index into sk_TransformedCoords2D must be an integer literal"); + return; + } + int64_t index = ((IntLiteral&) *i.fIndex).fValue; + String name = "sk_TransformedCoords2D_" + to_string(index); + fFormatArgs.push_back("_outer.computeLocalCoordsInVertexShader() ? " + name + + ".c_str() : \"_coords\""); + if (fWrittenTransformedCoords.find(index) == fWrittenTransformedCoords.end()) { + addExtraEmitCodeLine("SkString " + name + + " = fragBuilder->ensureCoords2D(args.fTransformedCoords[" + + to_string(index) + "].fVaryingPoint);"); + fWrittenTransformedCoords.insert(index); + } + return; + } else if (SK_TEXTURESAMPLERS_BUILTIN == builtin) { + this->write("%s"); + if (i.fIndex->fKind != Expression::kIntLiteral_Kind) { + fErrors.error(i.fIndex->fOffset, + "index into sk_TextureSamplers must be an integer literal"); + return; + } + int64_t index = ((IntLiteral&) *i.fIndex).fValue; + fFormatArgs.push_back(" fragBuilder->getProgramBuilder()->samplerVariable(" + "args.fTexSamplers[" + to_string(index) + "])"); + return; + } + } + INHERITED::writeIndexExpression(i); +} + +static String default_value(const Type& type) { + if (type.fName == "bool") { + return "false"; + } + switch (type.kind()) { + case Type::kScalar_Kind: return "0"; + case Type::kVector_Kind: return type.name() + "(0)"; + case Type::kMatrix_Kind: return type.name() + "(1)"; + default: ABORT("unsupported default_value type\n"); + } +} + +static String default_value(const Variable& var) { + if (var.fModifiers.fLayout.fCType == SkSL::Layout::CType::kSkPMColor4f) { + return "{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}"; + } + return default_value(var.fType); +} + +static bool is_private(const Variable& var) { + return !(var.fModifiers.fFlags & Modifiers::kUniform_Flag) && + !(var.fModifiers.fFlags & Modifiers::kIn_Flag) && + var.fStorage == Variable::kGlobal_Storage && + var.fModifiers.fLayout.fBuiltin == -1; +} + +static bool is_uniform_in(const Variable& var) { + return (var.fModifiers.fFlags & Modifiers::kUniform_Flag) && + (var.fModifiers.fFlags & Modifiers::kIn_Flag) && + var.fType.kind() != Type::kSampler_Kind; +} + +void CPPCodeGenerator::writeRuntimeValue(const Type& type, const Layout& layout, + const String& cppCode) { + if (type.isFloat()) { + this->write("%f"); + fFormatArgs.push_back(cppCode); + } else if (type == *fContext.fInt_Type) { + this->write("%d"); + fFormatArgs.push_back(cppCode); + } else if (type == *fContext.fBool_Type) { + this->write("%s"); + fFormatArgs.push_back("(" + cppCode + " ? \"true\" : \"false\")"); + } else if (type == *fContext.fFloat2_Type || type == *fContext.fHalf2_Type) { + this->write(type.name() + "(%f, %f)"); + fFormatArgs.push_back(cppCode + ".fX"); + fFormatArgs.push_back(cppCode + ".fY"); + } else if (type == *fContext.fFloat4_Type || type == *fContext.fHalf4_Type) { + this->write(type.name() + "(%f, %f, %f, %f)"); + switch (layout.fCType) { + case Layout::CType::kSkPMColor: + fFormatArgs.push_back("SkGetPackedR32(" + cppCode + ") / 255.0"); + fFormatArgs.push_back("SkGetPackedG32(" + cppCode + ") / 255.0"); + fFormatArgs.push_back("SkGetPackedB32(" + cppCode + ") / 255.0"); + fFormatArgs.push_back("SkGetPackedA32(" + cppCode + ") / 255.0"); + break; + case Layout::CType::kSkPMColor4f: + fFormatArgs.push_back(cppCode + ".fR"); + fFormatArgs.push_back(cppCode + ".fG"); + fFormatArgs.push_back(cppCode + ".fB"); + fFormatArgs.push_back(cppCode + ".fA"); + break; + case Layout::CType::kSkVector4: + fFormatArgs.push_back(cppCode + ".fData[0]"); + fFormatArgs.push_back(cppCode + ".fData[1]"); + fFormatArgs.push_back(cppCode + ".fData[2]"); + fFormatArgs.push_back(cppCode + ".fData[3]"); + break; + case Layout::CType::kSkRect: // fall through + case Layout::CType::kDefault: + fFormatArgs.push_back(cppCode + ".left()"); + fFormatArgs.push_back(cppCode + ".top()"); + fFormatArgs.push_back(cppCode + ".right()"); + fFormatArgs.push_back(cppCode + ".bottom()"); + break; + default: + SkASSERT(false); + } + } else if (type.kind() == Type::kEnum_Kind) { + this->write("%d"); + fFormatArgs.push_back("(int) " + cppCode); + } else if (type == *fContext.fInt4_Type || + type == *fContext.fShort4_Type || + type == *fContext.fByte4_Type) { + this->write(type.name() + "(%d, %d, %d, %d)"); + fFormatArgs.push_back(cppCode + ".left()"); + fFormatArgs.push_back(cppCode + ".top()"); + fFormatArgs.push_back(cppCode + ".right()"); + fFormatArgs.push_back(cppCode + ".bottom()"); + } else { + printf("unsupported runtime value type '%s'\n", String(type.fName).c_str()); + SkASSERT(false); + } +} + +void CPPCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) { + if (is_private(var)) { + this->writeRuntimeValue(var.fType, var.fModifiers.fLayout, var.fName); + } else { + this->writeExpression(value, kTopLevel_Precedence); + } +} + +String CPPCodeGenerator::getSamplerHandle(const Variable& var) { + int samplerCount = 0; + for (const auto param : fSectionAndParameterHelper.getParameters()) { + if (&var == param) { + return "args.fTexSamplers[" + to_string(samplerCount) + "]"; + } + if (param->fType.kind() == Type::kSampler_Kind) { + ++samplerCount; + } + } + ABORT("should have found sampler in parameters\n"); +} + +void CPPCodeGenerator::writeIntLiteral(const IntLiteral& i) { + this->write(to_string((int32_t) i.fValue)); +} + +void CPPCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + if (fCPPMode) { + SkASSERT(swizzle.fComponents.size() == 1); // no support for multiple swizzle components yet + this->writeExpression(*swizzle.fBase, kPostfix_Precedence); + switch (swizzle.fComponents[0]) { + case 0: this->write(".left()"); break; + case 1: this->write(".top()"); break; + case 2: this->write(".right()"); break; + case 3: this->write(".bottom()"); break; + } + } else { + INHERITED::writeSwizzle(swizzle); + } +} + +void CPPCodeGenerator::writeVariableReference(const VariableReference& ref) { + if (fCPPMode) { + this->write(ref.fVariable.fName); + return; + } + switch (ref.fVariable.fModifiers.fLayout.fBuiltin) { + case SK_INCOLOR_BUILTIN: + this->write("%s"); + // EmitArgs.fInputColor is automatically set to half4(1) if + // no input was specified + fFormatArgs.push_back(String("args.fInputColor")); + break; + case SK_OUTCOLOR_BUILTIN: + this->write("%s"); + fFormatArgs.push_back(String("args.fOutputColor")); + break; + case SK_WIDTH_BUILTIN: + this->write("sk_Width"); + break; + case SK_HEIGHT_BUILTIN: + this->write("sk_Height"); + break; + default: + if (ref.fVariable.fType.kind() == Type::kSampler_Kind) { + this->write("%s"); + fFormatArgs.push_back("fragBuilder->getProgramBuilder()->samplerVariable(" + + this->getSamplerHandle(ref.fVariable) + ")"); + return; + } + if (ref.fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag) { + this->write("%s"); + String name = ref.fVariable.fName; + String var = String::printf("args.fUniformHandler->getUniformCStr(%sVar)", + HCodeGenerator::FieldName(name.c_str()).c_str()); + String code; + if (ref.fVariable.fModifiers.fLayout.fWhen.fLength) { + code = String::printf("%sVar.isValid() ? %s : \"%s\"", + HCodeGenerator::FieldName(name.c_str()).c_str(), + var.c_str(), + default_value(ref.fVariable.fType).c_str()); + } else { + code = var; + } + fFormatArgs.push_back(code); + } else if (SectionAndParameterHelper::IsParameter(ref.fVariable)) { + String name(ref.fVariable.fName); + this->writeRuntimeValue(ref.fVariable.fType, ref.fVariable.fModifiers.fLayout, + String::printf("_outer.%s", name.c_str()).c_str()); + } else { + this->write(ref.fVariable.fName); + } + } +} + +void CPPCodeGenerator::writeIfStatement(const IfStatement& s) { + if (s.fIsStatic) { + this->write("@"); + } + INHERITED::writeIfStatement(s); +} + +void CPPCodeGenerator::writeReturnStatement(const ReturnStatement& s) { + if (fInMain) { + fErrors.error(s.fOffset, "fragmentProcessor main() may not contain return statements"); + } + INHERITED::writeReturnStatement(s); +} + +void CPPCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + if (s.fIsStatic) { + this->write("@"); + } + INHERITED::writeSwitchStatement(s); +} + +void CPPCodeGenerator::writeFieldAccess(const FieldAccess& access) { + if (access.fBase->fType.name() == "fragmentProcessor") { + // Special field access on fragment processors are converted into function calls on + // GrFragmentProcessor's getters. + if (access.fBase->fKind != Expression::kVariableReference_Kind) { + fErrors.error(access.fBase->fOffset, "fragmentProcessor must be a reference\n"); + return; + } + + const Type::Field& field = fContext.fFragmentProcessor_Type->fields()[access.fFieldIndex]; + const Variable& var = ((const VariableReference&) *access.fBase).fVariable; + String cppAccess = String::printf("_outer.childProcessor(_outer.%s_index).%s()", + String(var.fName).c_str(), + String(field.fName).c_str()); + + if (fCPPMode) { + this->write(cppAccess.c_str()); + } else { + writeRuntimeValue(*field.fType, Layout(), cppAccess); + } + return; + } + INHERITED::writeFieldAccess(access); +} + +int CPPCodeGenerator::getChildFPIndex(const Variable& var) const { + int index = 0; + bool found = false; + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + const VarDeclaration& decl = (VarDeclaration&) *raw; + if (decl.fVar == &var) { + found = true; + } else if (decl.fVar->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + ++index; + } + } + } + if (found) { + break; + } + } + SkASSERT(found); + return index; +} + +void CPPCodeGenerator::writeFunctionCall(const FunctionCall& c) { + if (c.fFunction.fBuiltin && c.fFunction.fName == "sample" && + c.fArguments[0]->fType.kind() != Type::Kind::kSampler_Kind) { + // Sanity checks that are detected by function definition in sksl_fp.inc + SkASSERT(c.fArguments.size() >= 1 && c.fArguments.size() <= 3); + SkASSERT("fragmentProcessor" == c.fArguments[0]->fType.name() || + "fragmentProcessor?" == c.fArguments[0]->fType.name()); + + // Actually fail during compilation if arguments with valid types are + // provided that are not variable references, since sample() is a + // special function that impacts code emission. + if (c.fArguments[0]->fKind != Expression::kVariableReference_Kind) { + fErrors.error(c.fArguments[0]->fOffset, + "sample()'s fragmentProcessor argument must be a variable reference\n"); + return; + } + const Variable& child = ((const VariableReference&) *c.fArguments[0]).fVariable; + + // Start a new extra emit code section so that the emitted child processor can depend on + // sksl variables defined in earlier sksl code. + this->newExtraEmitCodeBlock(); + + // Set to the empty string when no input color parameter should be emitted, which means this + // must be properly formatted with a prefixed comma when the parameter should be inserted + // into the invokeChild() parameter list. + String inputArg; + if (c.fArguments.size() > 1 && c.fArguments[1]->fType.name() == "half4") { + // Use the invokeChild() variant that accepts an input color, so convert the 2nd + // argument's expression into C++ code that produces sksl stored in an SkString. + String inputName = "_input" + to_string(c.fOffset); + addExtraEmitCodeLine(convertSKSLExpressionToCPP(*c.fArguments[1], inputName)); + + // invokeChild() needs a char* + inputArg = ", " + inputName + ".c_str()"; + } + + bool hasCoords = c.fArguments.back()->fType.name() == "float2"; + + // Write the output handling after the possible input handling + String childName = "_sample" + to_string(c.fOffset); + addExtraEmitCodeLine("SkString " + childName + "(\"" + childName + "\");"); + String coordsName; + if (hasCoords) { + coordsName = "_coords" + to_string(c.fOffset); + addExtraEmitCodeLine(convertSKSLExpressionToCPP(*c.fArguments.back(), coordsName)); + } + if (c.fArguments[0]->fType.kind() == Type::kNullable_Kind) { + addExtraEmitCodeLine("if (_outer." + String(child.fName) + "_index >= 0) {\n "); + } + if (hasCoords) { + addExtraEmitCodeLine("this->invokeChild(_outer." + String(child.fName) + "_index" + + inputArg + ", &" + childName + ", args, " + coordsName + + ".c_str());"); + } else { + addExtraEmitCodeLine("this->invokeChild(_outer." + String(child.fName) + "_index" + + inputArg + ", &" + childName + ", args);"); + } + + if (c.fArguments[0]->fType.kind() == Type::kNullable_Kind) { + // Null FPs are not emitted, but their output can still be referenced in dependent + // expressions - thus we always declare the variable. + // Note: this is essentially dead code required to satisfy the compiler, because + // 'process' function calls should always be guarded at a higher level, in the .fp + // source. + addExtraEmitCodeLine( + "} else {" + " fragBuilder->codeAppendf(\"half4 %s;\", " + childName + ".c_str());" + "}"); + } + this->write("%s"); + fFormatArgs.push_back(childName + ".c_str()"); + return; + } + if (c.fFunction.fBuiltin) { + INHERITED::writeFunctionCall(c); + } else { + this->write("%s"); + fFormatArgs.push_back((String(c.fFunction.fName) + "_name.c_str()").c_str()); + this->write("("); + const char* separator = ""; + for (const auto& arg : c.fArguments) { + this->write(separator); + separator = ", "; + this->writeExpression(*arg, kSequence_Precedence); + } + this->write(")"); + } + if (c.fFunction.fBuiltin && c.fFunction.fName == "sample") { + this->write(".%s"); + SkASSERT(c.fArguments.size() >= 1); + SkASSERT(c.fArguments[0]->fKind == Expression::kVariableReference_Kind); + String sampler = this->getSamplerHandle(((VariableReference&) *c.fArguments[0]).fVariable); + fFormatArgs.push_back("fragBuilder->getProgramBuilder()->samplerSwizzle(" + sampler + + ").c_str()"); + } +} + +static const char* glsltype_string(const Context& context, const Type& type) { + if (type == *context.fFloat_Type) { + return "kFloat_GrSLType"; + } else if (type == *context.fHalf_Type) { + return "kHalf_GrSLType"; + } else if (type == *context.fFloat2_Type) { + return "kFloat2_GrSLType"; + } else if (type == *context.fHalf2_Type) { + return "kHalf2_GrSLType"; + } else if (type == *context.fFloat4_Type) { + return "kFloat4_GrSLType"; + } else if (type == *context.fHalf4_Type) { + return "kHalf4_GrSLType"; + } else if (type == *context.fFloat4x4_Type) { + return "kFloat4x4_GrSLType"; + } else if (type == *context.fHalf4x4_Type) { + return "kHalf4x4_GrSLType"; + } else if (type == *context.fVoid_Type) { + return "kVoid_GrSLType"; + } + SkASSERT(false); + return nullptr; +} + +void CPPCodeGenerator::writeFunction(const FunctionDefinition& f) { + const FunctionDeclaration& decl = f.fDeclaration; + fFunctionHeader = ""; + OutputStream* oldOut = fOut; + StringStream buffer; + fOut = &buffer; + if (decl.fName == "main") { + fInMain = true; + for (const auto& s : ((Block&) *f.fBody).fStatements) { + this->writeStatement(*s); + this->writeLine(); + } + fInMain = false; + + fOut = oldOut; + this->write(fFunctionHeader); + this->write(buffer.str()); + } else { + this->addExtraEmitCodeLine("SkString " + decl.fName + "_name;"); + String args = "const GrShaderVar " + decl.fName + "_args[] = { "; + const char* separator = ""; + for (const auto& param : decl.fParameters) { + args += String(separator) + "GrShaderVar(\"" + param->fName + "\", " + + glsltype_string(fContext, param->fType) + ")"; + separator = ", "; + } + args += "};"; + this->addExtraEmitCodeLine(args.c_str()); + for (const auto& s : ((Block&) *f.fBody).fStatements) { + this->writeStatement(*s); + this->writeLine(); + } + + fOut = oldOut; + String emit = "fragBuilder->emitFunction("; + emit += glsltype_string(fContext, decl.fReturnType); + emit += ", \"" + decl.fName + "\""; + emit += ", " + to_string((int64_t) decl.fParameters.size()); + emit += ", " + decl.fName + "_args"; + emit += ", \"" + buffer.str() + "\""; + emit += ", &" + decl.fName + "_name);"; + this->addExtraEmitCodeLine(emit.c_str()); + } +} + +void CPPCodeGenerator::writeSetting(const Setting& s) { + static constexpr const char* kPrefix = "sk_Args."; + if (!strncmp(s.fName.c_str(), kPrefix, strlen(kPrefix))) { + const char* name = s.fName.c_str() + strlen(kPrefix); + this->writeRuntimeValue(s.fType, Layout(), HCodeGenerator::FieldName(name).c_str()); + } else { + this->write(s.fName.c_str()); + } +} + +bool CPPCodeGenerator::writeSection(const char* name, const char* prefix) { + const Section* s = fSectionAndParameterHelper.getSection(name); + if (s) { + this->writef("%s%s", prefix, s->fText.c_str()); + return true; + } + return false; +} + +void CPPCodeGenerator::writeProgramElement(const ProgramElement& p) { + if (p.fKind == ProgramElement::kSection_Kind) { + return; + } + if (p.fKind == ProgramElement::kVar_Kind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + if (!decls.fVars.size()) { + return; + } + const Variable& var = *((VarDeclaration&) *decls.fVars[0]).fVar; + if (var.fModifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kUniform_Flag) || + -1 != var.fModifiers.fLayout.fBuiltin) { + return; + } + } + INHERITED::writeProgramElement(p); +} + +void CPPCodeGenerator::addUniform(const Variable& var) { + if (!needs_uniform_var(var)) { + return; + } + if (var.fModifiers.fLayout.fWhen.fLength) { + this->writef(" if (%s) {\n ", String(var.fModifiers.fLayout.fWhen).c_str()); + } + const char* type = glsltype_string(fContext, var.fType); + String name(var.fName); + this->writef(" %sVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, %s, " + "\"%s\");\n", HCodeGenerator::FieldName(name.c_str()).c_str(), type, + name.c_str()); + if (var.fModifiers.fLayout.fWhen.fLength) { + this->write(" }\n"); + } +} + +void CPPCodeGenerator::writeInputVars() { +} + +void CPPCodeGenerator::writePrivateVars() { + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + VarDeclaration& decl = (VarDeclaration&) *raw; + if (is_private(*decl.fVar)) { + if (decl.fVar->fType == *fContext.fFragmentProcessor_Type) { + fErrors.error(decl.fOffset, + "fragmentProcessor variables must be declared 'in'"); + return; + } + this->writef("%s %s = %s;\n", + HCodeGenerator::FieldType(fContext, decl.fVar->fType, + decl.fVar->fModifiers.fLayout).c_str(), + String(decl.fVar->fName).c_str(), + default_value(*decl.fVar).c_str()); + } else if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) { + // An auto-tracked uniform in variable, so add a field to hold onto the prior + // state. Note that tracked variables must be uniform in's and that is validated + // before writePrivateVars() is called. + const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, *decl.fVar); + SkASSERT(mapper && mapper->supportsTracking()); + + String name = HCodeGenerator::FieldName(String(decl.fVar->fName).c_str()); + // The member statement is different if the mapper reports a default value + if (mapper->defaultValue().size() > 0) { + this->writef("%s %sPrev = %s;\n", + Layout::CTypeToStr(mapper->ctype()), name.c_str(), + mapper->defaultValue().c_str()); + } else { + this->writef("%s %sPrev;\n", + Layout::CTypeToStr(mapper->ctype()), name.c_str()); + } + } + } + } + } +} + +void CPPCodeGenerator::writePrivateVarValues() { + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + VarDeclaration& decl = (VarDeclaration&) *raw; + if (is_private(*decl.fVar) && decl.fValue) { + this->writef("%s = ", String(decl.fVar->fName).c_str()); + fCPPMode = true; + this->writeExpression(*decl.fValue, kAssignment_Precedence); + fCPPMode = false; + this->write(";\n"); + } + } + } + } +} + +static bool is_accessible(const Variable& var) { + const Type& type = var.fType.nonnullable(); + return Type::kSampler_Kind != type.kind() && + Type::kOther_Kind != type.kind(); +} + +void CPPCodeGenerator::newExtraEmitCodeBlock() { + // This should only be called when emitting SKSL for emitCode(), which can be detected if the + // cpp buffer is not null, and the cpp buffer is not the current output. + SkASSERT(fCPPBuffer && fCPPBuffer != fOut); + + // Start a new block as an empty string + fExtraEmitCodeBlocks.push_back(""); + // Mark its location in the output buffer, uses ${\d} for the token since ${} will not occur in + // valid sksl and makes detection trivial. + this->writef("${%zu}", fExtraEmitCodeBlocks.size() - 1); +} + +void CPPCodeGenerator::addExtraEmitCodeLine(const String& toAppend) { + SkASSERT(fExtraEmitCodeBlocks.size() > 0); + String& currentBlock = fExtraEmitCodeBlocks[fExtraEmitCodeBlocks.size() - 1]; + // Automatically add indentation and newline + currentBlock += " " + toAppend + "\n"; +} + +void CPPCodeGenerator::flushEmittedCode() { + if (fCPPBuffer == nullptr) { + // Not actually within writeEmitCode() so nothing to flush + return; + } + + StringStream* skslBuffer = static_cast(fOut); + + String sksl = skslBuffer->str(); + // Empty the accumulation buffer since its current contents are consumed. + skslBuffer->reset(); + + // Switch to the cpp buffer + fOut = fCPPBuffer; + + // Iterate through the sksl, keeping track of where the last statement ended (e.g. the latest + // encountered ';', '{', or '}'). If an extra emit code block token is encountered then the + // code from 0 to last statement end is sent to writeCodeAppend, the extra code block is + // appended to the cpp buffer, and then the sksl string is trimmed to start where the last + // statement left off (minus the encountered token). + size_t i = 0; + int flushPoint = -1; + int tokenStart = -1; + while (i < sksl.size()) { + if (tokenStart >= 0) { + // Looking for the end of the token + if (sksl[i] == '}') { + // Must append the sksl from 0 to flushPoint (inclusive) then the extra code + // accumulated in the block with index parsed from chars [tokenStart+2, i-1] + String toFlush = String(sksl.c_str(), flushPoint + 1); + // writeCodeAppend automatically removes the format args that it consumed, so + // fFormatArgs will be in a valid state for any future sksl + this->writeCodeAppend(toFlush); + + int codeBlock = stoi(String(sksl.c_str() + tokenStart + 2, i - tokenStart - 2)); + SkASSERT(codeBlock < (int) fExtraEmitCodeBlocks.size()); + if (fExtraEmitCodeBlocks[codeBlock].size() > 0) { + this->write(fExtraEmitCodeBlocks[codeBlock].c_str()); + } + + // Now reset the sksl buffer to start after the flush point, but remove the token. + String compacted = String(sksl.c_str() + flushPoint + 1, + tokenStart - flushPoint - 1); + if (i < sksl.size() - 1) { + compacted += String(sksl.c_str() + i + 1, sksl.size() - i - 1); + } + sksl = compacted; + + // And reset iteration + i = -1; + flushPoint = -1; + tokenStart = -1; + } + } else { + // Looking for the start of extra emit block tokens, and tracking when statements end + if (sksl[i] == ';' || sksl[i] == '{' || sksl[i] == '}') { + flushPoint = i; + } else if (i < sksl.size() - 1 && sksl[i] == '$' && sksl[i + 1] == '{') { + // found an extra emit code block token + tokenStart = i++; + } + } + i++; + } + + // Once we've gone through the sksl string to this point, there are no remaining extra emit + // code blocks to interleave, so append the remainder as usual. + this->writeCodeAppend(sksl); + + // After appending, switch back to the emptied sksl buffer and reset the extra code blocks + fOut = skslBuffer; + fExtraEmitCodeBlocks.clear(); +} + +void CPPCodeGenerator::writeCodeAppend(const String& code) { + // codeAppendf can only handle appending 1024 bytes at a time, so we need to break the string + // into chunks. Unfortunately we can't tell exactly how long the string is going to end up, + // because printf escape sequences get replaced by strings of unknown length, but keeping the + // format string below 512 bytes is probably safe. + static constexpr size_t maxChunkSize = 512; + size_t start = 0; + size_t index = 0; + size_t argStart = 0; + size_t argCount; + while (index < code.size()) { + argCount = 0; + this->write(" fragBuilder->codeAppendf(\""); + while (index < code.size() && index < start + maxChunkSize) { + if ('%' == code[index]) { + if (index == start + maxChunkSize - 1 || index == code.size() - 1) { + break; + } + if (code[index + 1] != '%') { + ++argCount; + } + } else if ('\\' == code[index] && index == start + maxChunkSize - 1) { + // avoid splitting an escape sequence that happens to fall across a chunk boundary + break; + } + ++index; + } + fOut->write(code.c_str() + start, index - start); + this->write("\""); + for (size_t i = argStart; i < argStart + argCount; ++i) { + this->writef(", %s", fFormatArgs[i].c_str()); + } + this->write(");\n"); + argStart += argCount; + start = index; + } + + // argStart is equal to the number of fFormatArgs that were consumed + // so they should be removed from the list + if (argStart > 0) { + fFormatArgs.erase(fFormatArgs.begin(), fFormatArgs.begin() + argStart); + } +} + +String CPPCodeGenerator::convertSKSLExpressionToCPP(const Expression& e, + const String& cppVar) { + // To do this conversion, we temporarily switch the sksl output stream + // to an empty stringstream and reset the format args to empty. + OutputStream* oldSKSL = fOut; + StringStream exprBuffer; + fOut = &exprBuffer; + + std::vector oldArgs(fFormatArgs); + fFormatArgs.clear(); + + // Convert the argument expression into a format string and args + this->writeExpression(e, Precedence::kTopLevel_Precedence); + std::vector newArgs(fFormatArgs); + String expr = exprBuffer.str(); + + // After generating, restore the original output stream and format args + fFormatArgs = oldArgs; + fOut = oldSKSL; + + // The sksl written to exprBuffer is not processed by flushEmittedCode(), so any extra emit code + // block tokens won't get handled. So we need to strip them from the expression and stick them + // to the end of the original sksl stream. + String exprFormat = ""; + int tokenStart = -1; + for (size_t i = 0; i < expr.size(); i++) { + if (tokenStart >= 0) { + if (expr[i] == '}') { + // End of the token, so append the token to fOut + fOut->write(expr.c_str() + tokenStart, i - tokenStart + 1); + tokenStart = -1; + } + } else { + if (i < expr.size() - 1 && expr[i] == '$' && expr[i + 1] == '{') { + tokenStart = i++; + } else { + exprFormat += expr[i]; + } + } + } + + // Now build the final C++ code snippet from the format string and args + String cppExpr; + if (newArgs.size() == 0) { + // This was a static expression, so we can simplify the input + // color declaration in the emitted code to just a static string + cppExpr = "SkString " + cppVar + "(\"" + exprFormat + "\");"; + } else { + // String formatting must occur dynamically, so have the C++ declaration + // use SkStringPrintf with the format args that were accumulated + // when the expression was written. + cppExpr = "SkString " + cppVar + " = SkStringPrintf(\"" + exprFormat + "\""; + for (size_t i = 0; i < newArgs.size(); i++) { + cppExpr += ", " + newArgs[i]; + } + cppExpr += ");"; + } + return cppExpr; +} + +bool CPPCodeGenerator::writeEmitCode(std::vector& uniforms) { + this->write(" void emitCode(EmitArgs& args) override {\n" + " GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;\n"); + this->writef(" const %s& _outer = args.fFp.cast<%s>();\n" + " (void) _outer;\n", + fFullName.c_str(), fFullName.c_str()); + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + VarDeclaration& decl = (VarDeclaration&) *raw; + String nameString(decl.fVar->fName); + const char* name = nameString.c_str(); + if (SectionAndParameterHelper::IsParameter(*decl.fVar) && + is_accessible(*decl.fVar)) { + this->writef(" auto %s = _outer.%s;\n" + " (void) %s;\n", + name, name, name); + } + } + } + } + this->writePrivateVarValues(); + for (const auto u : uniforms) { + this->addUniform(*u); + } + this->writeSection(EMIT_CODE_SECTION); + + // Save original buffer as the CPP buffer for flushEmittedCode() + fCPPBuffer = fOut; + StringStream skslBuffer; + fOut = &skslBuffer; + + this->newExtraEmitCodeBlock(); + bool result = INHERITED::generateCode(); + this->flushEmittedCode(); + + // Then restore the original CPP buffer and close the function + fOut = fCPPBuffer; + fCPPBuffer = nullptr; + this->write(" }\n"); + return result; +} + +void CPPCodeGenerator::writeSetData(std::vector& uniforms) { + const char* fullName = fFullName.c_str(); + const Section* section = fSectionAndParameterHelper.getSection(SET_DATA_SECTION); + const char* pdman = section ? section->fArgument.c_str() : "pdman"; + this->writef(" void onSetData(const GrGLSLProgramDataManager& %s, " + "const GrFragmentProcessor& _proc) override {\n", + pdman); + bool wroteProcessor = false; + for (const auto u : uniforms) { + if (is_uniform_in(*u)) { + if (!wroteProcessor) { + this->writef(" const %s& _outer = _proc.cast<%s>();\n", fullName, fullName); + wroteProcessor = true; + this->writef(" {\n"); + } + + const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, *u); + SkASSERT(mapper); + + String nameString(u->fName); + const char* name = nameString.c_str(); + + // Switches for setData behavior in the generated code + bool conditionalUniform = u->fModifiers.fLayout.fWhen != ""; + bool isTracked = u->fModifiers.fLayout.fFlags & Layout::kTracked_Flag; + bool needsValueDeclaration = isTracked || !mapper->canInlineUniformValue(); + + String uniformName = HCodeGenerator::FieldName(name) + "Var"; + + String indent = " "; // 8 by default, 12 when nested for conditional uniforms + if (conditionalUniform) { + // Add a pre-check to make sure the uniform was emitted + // before trying to send any data to the GPU + this->writef(" if (%s.isValid()) {\n", uniformName.c_str()); + indent += " "; + } + + String valueVar = ""; + if (needsValueDeclaration) { + valueVar.appendf("%sValue", name); + // Use AccessType since that will match the return type of _outer's public API. + String valueType = HCodeGenerator::AccessType(fContext, u->fType, + u->fModifiers.fLayout); + this->writef("%s%s %s = _outer.%s;\n", + indent.c_str(), valueType.c_str(), valueVar.c_str(), name); + } else { + // Not tracked and the mapper only needs to use the value once + // so send it a safe expression instead of the variable name + valueVar.appendf("(_outer.%s)", name); + } + + if (isTracked) { + SkASSERT(mapper->supportsTracking()); + + String prevVar = HCodeGenerator::FieldName(name) + "Prev"; + this->writef("%sif (%s) {\n" + "%s %s;\n" + "%s %s;\n" + "%s}\n", indent.c_str(), + mapper->dirtyExpression(valueVar, prevVar).c_str(), indent.c_str(), + mapper->saveState(valueVar, prevVar).c_str(), indent.c_str(), + mapper->setUniform(pdman, uniformName, valueVar).c_str(), indent.c_str()); + } else { + this->writef("%s%s;\n", indent.c_str(), + mapper->setUniform(pdman, uniformName, valueVar).c_str()); + } + + if (conditionalUniform) { + // Close the earlier precheck block + this->writef(" }\n"); + } + } + } + if (wroteProcessor) { + this->writef(" }\n"); + } + if (section) { + int samplerIndex = 0; + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + VarDeclaration& decl = (VarDeclaration&) *raw; + String nameString(decl.fVar->fName); + const char* name = nameString.c_str(); + if (decl.fVar->fType.kind() == Type::kSampler_Kind) { + this->writef(" GrSurfaceProxy& %sProxy = " + "*_outer.textureSampler(%d).proxy();\n", + name, samplerIndex); + this->writef(" GrTexture& %s = *%sProxy.peekTexture();\n", + name, name); + this->writef(" (void) %s;\n", name); + ++samplerIndex; + } else if (needs_uniform_var(*decl.fVar)) { + this->writef(" UniformHandle& %s = %sVar;\n" + " (void) %s;\n", + name, HCodeGenerator::FieldName(name).c_str(), name); + } else if (SectionAndParameterHelper::IsParameter(*decl.fVar) && + decl.fVar->fType != *fContext.fFragmentProcessor_Type) { + if (!wroteProcessor) { + this->writef(" const %s& _outer = _proc.cast<%s>();\n", fullName, + fullName); + wroteProcessor = true; + } + this->writef(" auto %s = _outer.%s;\n" + " (void) %s;\n", + name, name, name); + } + } + } + } + this->writeSection(SET_DATA_SECTION); + } + this->write(" }\n"); +} + +void CPPCodeGenerator::writeOnTextureSampler() { + bool foundSampler = false; + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (param->fType.kind() == Type::kSampler_Kind) { + if (!foundSampler) { + this->writef( + "const GrFragmentProcessor::TextureSampler& %s::onTextureSampler(int " + "index) const {\n", + fFullName.c_str()); + this->writef(" return IthTextureSampler(index, %s", + HCodeGenerator::FieldName(String(param->fName).c_str()).c_str()); + foundSampler = true; + } else { + this->writef(", %s", + HCodeGenerator::FieldName(String(param->fName).c_str()).c_str()); + } + } + } + if (foundSampler) { + this->write(");\n}\n"); + } +} + +void CPPCodeGenerator::writeClone() { + if (!this->writeSection(CLONE_SECTION)) { + if (fSectionAndParameterHelper.getSection(FIELDS_SECTION)) { + fErrors.error(0, "fragment processors with custom @fields must also have a custom" + "@clone"); + } + this->writef("%s::%s(const %s& src)\n" + ": INHERITED(k%s_ClassID, src.optimizationFlags())", fFullName.c_str(), + fFullName.c_str(), fFullName.c_str(), fFullName.c_str()); + const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION); + for (size_t i = 0; i < transforms.size(); ++i) { + const Section& s = *transforms[i]; + String fieldName = HCodeGenerator::CoordTransformName(s.fArgument, i); + this->writef("\n, %s(src.%s)", fieldName.c_str(), fieldName.c_str()); + } + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + String fieldName = HCodeGenerator::FieldName(String(param->fName).c_str()); + if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + this->writef("\n, %s_index(src.%s_index)", + fieldName.c_str(), + fieldName.c_str()); + } else { + this->writef("\n, %s(src.%s)", + fieldName.c_str(), + fieldName.c_str()); + } + } + this->writef(" {\n"); + int samplerCount = 0; + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (param->fType.kind() == Type::kSampler_Kind) { + ++samplerCount; + } else if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + String fieldName = HCodeGenerator::FieldName(String(param->fName).c_str()); + if (param->fType.kind() == Type::kNullable_Kind) { + this->writef(" if (%s_index >= 0) {\n ", fieldName.c_str()); + } + this->writef(" this->registerChildProcessor(src.childProcessor(%s_index)." + "clone());\n", fieldName.c_str()); + if (param->fType.kind() == Type::kNullable_Kind) { + this->writef(" }\n"); + } + } + } + if (samplerCount) { + this->writef(" this->setTextureSamplerCnt(%d);", samplerCount); + } + for (size_t i = 0; i < transforms.size(); ++i) { + const Section& s = *transforms[i]; + String fieldName = HCodeGenerator::CoordTransformName(s.fArgument, i); + this->writef(" this->addCoordTransform(&%s);\n", fieldName.c_str()); + } + this->write("}\n"); + this->writef("std::unique_ptr %s::clone() const {\n", + fFullName.c_str()); + this->writef(" return std::unique_ptr(new %s(*this));\n", + fFullName.c_str()); + this->write("}\n"); + } +} + +void CPPCodeGenerator::writeTest() { + const Section* test = fSectionAndParameterHelper.getSection(TEST_CODE_SECTION); + if (test) { + this->writef( + "GR_DEFINE_FRAGMENT_PROCESSOR_TEST(%s);\n" + "#if GR_TEST_UTILS\n" + "std::unique_ptr %s::TestCreate(GrProcessorTestData* %s) {\n", + fFullName.c_str(), + fFullName.c_str(), + test->fArgument.c_str()); + this->writeSection(TEST_CODE_SECTION); + this->write("}\n" + "#endif\n"); + } +} + +void CPPCodeGenerator::writeGetKey() { + this->writef("void %s::onGetGLSLProcessorKey(const GrShaderCaps& caps, " + "GrProcessorKeyBuilder* b) const {\n", + fFullName.c_str()); + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + const VarDeclaration& decl = (VarDeclaration&) *raw; + const Variable& var = *decl.fVar; + String nameString(var.fName); + const char* name = nameString.c_str(); + if (var.fModifiers.fLayout.fKey != Layout::kNo_Key && + (var.fModifiers.fFlags & Modifiers::kUniform_Flag)) { + fErrors.error(var.fOffset, + "layout(key) may not be specified on uniforms"); + } + switch (var.fModifiers.fLayout.fKey) { + case Layout::kKey_Key: + if (is_private(var)) { + this->writef("%s %s =", + HCodeGenerator::FieldType(fContext, var.fType, + var.fModifiers.fLayout).c_str(), + String(var.fName).c_str()); + if (decl.fValue) { + fCPPMode = true; + this->writeExpression(*decl.fValue, kAssignment_Precedence); + fCPPMode = false; + } else { + this->writef("%s", default_value(var).c_str()); + } + this->write(";\n"); + } + if (var.fModifiers.fLayout.fWhen.fLength) { + this->writef("if (%s) {", String(var.fModifiers.fLayout.fWhen).c_str()); + } + if (var.fType == *fContext.fFloat4x4_Type) { + ABORT("no automatic key handling for float4x4\n"); + } else if (var.fType == *fContext.fFloat2_Type) { + this->writef(" b->add32(%s.fX);\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" b->add32(%s.fY);\n", + HCodeGenerator::FieldName(name).c_str()); + } else if (var.fType == *fContext.fFloat4_Type) { + this->writef(" b->add32(%s.x());\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" b->add32(%s.y());\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" b->add32(%s.width());\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" b->add32(%s.height());\n", + HCodeGenerator::FieldName(name).c_str()); + } else if (var.fType == *fContext.fHalf4_Type) { + this->writef(" uint16_t red = SkFloatToHalf(%s.fR);\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" uint16_t green = SkFloatToHalf(%s.fG);\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" uint16_t blue = SkFloatToHalf(%s.fB);\n", + HCodeGenerator::FieldName(name).c_str()); + this->writef(" uint16_t alpha = SkFloatToHalf(%s.fA);\n", + HCodeGenerator::FieldName(name).c_str()); + this->write(" b->add32(((uint32_t)red << 16) | green);\n"); + this->write(" b->add32(((uint32_t)blue << 16) | alpha);\n"); + } else { + this->writef(" b->add32((int32_t) %s);\n", + HCodeGenerator::FieldName(name).c_str()); + } + if (var.fModifiers.fLayout.fWhen.fLength) { + this->write("}"); + } + break; + case Layout::kIdentity_Key: + if (var.fType.kind() != Type::kMatrix_Kind) { + fErrors.error(var.fOffset, + "layout(key=identity) requires matrix type"); + } + this->writef(" b->add32(%s.isIdentity() ? 1 : 0);\n", + HCodeGenerator::FieldName(name).c_str()); + break; + case Layout::kNo_Key: + break; + } + } + } + } + this->write("}\n"); +} + +bool CPPCodeGenerator::generateCode() { + std::vector uniforms; + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + VarDeclaration& decl = (VarDeclaration&) *raw; + if ((decl.fVar->fModifiers.fFlags & Modifiers::kUniform_Flag) && + decl.fVar->fType.kind() != Type::kSampler_Kind) { + uniforms.push_back(decl.fVar); + } + + if (is_uniform_in(*decl.fVar)) { + // Validate the "uniform in" declarations to make sure they are fully supported, + // instead of generating surprising C++ + const UniformCTypeMapper* mapper = + UniformCTypeMapper::Get(fContext, *decl.fVar); + if (mapper == nullptr) { + fErrors.error(decl.fOffset, String(decl.fVar->fName) + + "'s type is not supported for use as a 'uniform in'"); + return false; + } + if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) { + if (!mapper->supportsTracking()) { + fErrors.error(decl.fOffset, String(decl.fVar->fName) + + "'s type does not support state tracking"); + return false; + } + } + + } else { + // If it's not a uniform_in, it's an error to be tracked + if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) { + fErrors.error(decl.fOffset, "Non-'in uniforms' cannot be tracked"); + return false; + } + } + } + } + } + const char* baseName = fName.c_str(); + const char* fullName = fFullName.c_str(); + this->writef("%s\n", HCodeGenerator::GetHeader(fProgram, fErrors).c_str()); + this->writef(kFragmentProcessorHeader, fullName); + this->writef("#include \"%s.h\"\n\n", fullName); + this->writeSection(CPP_SECTION); + this->writef("#include \"include/gpu/GrTexture.h\"\n" + "#include \"src/gpu/glsl/GrGLSLFragmentProcessor.h\"\n" + "#include \"src/gpu/glsl/GrGLSLFragmentShaderBuilder.h\"\n" + "#include \"src/gpu/glsl/GrGLSLProgramBuilder.h\"\n" + "#include \"src/sksl/SkSLCPP.h\"\n" + "#include \"src/sksl/SkSLUtil.h\"\n" + "class GrGLSL%s : public GrGLSLFragmentProcessor {\n" + "public:\n" + " GrGLSL%s() {}\n", + baseName, baseName); + bool result = this->writeEmitCode(uniforms); + this->write("private:\n"); + this->writeSetData(uniforms); + this->writePrivateVars(); + for (const auto& u : uniforms) { + if (needs_uniform_var(*u) && !(u->fModifiers.fFlags & Modifiers::kIn_Flag)) { + this->writef(" UniformHandle %sVar;\n", + HCodeGenerator::FieldName(String(u->fName).c_str()).c_str()); + } + } + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (needs_uniform_var(*param)) { + this->writef(" UniformHandle %sVar;\n", + HCodeGenerator::FieldName(String(param->fName).c_str()).c_str()); + } + } + this->writef("};\n" + "GrGLSLFragmentProcessor* %s::onCreateGLSLInstance() const {\n" + " return new GrGLSL%s();\n" + "}\n", + fullName, baseName); + this->writeGetKey(); + this->writef("bool %s::onIsEqual(const GrFragmentProcessor& other) const {\n" + " const %s& that = other.cast<%s>();\n" + " (void) that;\n", + fullName, fullName, fullName); + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + continue; + } + String nameString(param->fName); + const char* name = nameString.c_str(); + this->writef(" if (%s != that.%s) return false;\n", + HCodeGenerator::FieldName(name).c_str(), + HCodeGenerator::FieldName(name).c_str()); + } + this->write(" return true;\n" + "}\n"); + this->writeClone(); + this->writeOnTextureSampler(); + this->writeTest(); + this->writeSection(CPP_END_SECTION); + + result &= 0 == fErrors.errorCount(); + return result; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.h new file mode 100644 index 0000000000..803270cc57 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCPPCodeGenerator.h @@ -0,0 +1,141 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CPPCODEGENERATOR +#define SKSL_CPPCODEGENERATOR + +#include "src/sksl/SkSLGLSLCodeGenerator.h" +#include "src/sksl/SkSLSectionAndParameterHelper.h" + +#include + +namespace SkSL { + +class CPPCodeGenerator : public GLSLCodeGenerator { +public: + CPPCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + String name, OutputStream* out); + + bool generateCode() override; + +private: + void writef(const char* s, va_list va) SKSL_PRINTF_LIKE(2, 0); + + void writef(const char* s, ...) SKSL_PRINTF_LIKE(2, 3); + + bool writeSection(const char* name, const char* prefix = ""); + + void writeHeader() override; + + bool usesPrecisionModifiers() const override; + + String getTypeName(const Type& type) override; + + void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence) override; + + void writeIndexExpression(const IndexExpression& i) override; + + void writeIntLiteral(const IntLiteral& i) override; + + void writeSwizzle(const Swizzle& swizzle) override; + + void writeFieldAccess(const FieldAccess& access) override; + + void writeVariableReference(const VariableReference& ref) override; + + String getSamplerHandle(const Variable& var); + + void writeIfStatement(const IfStatement& s) override; + + void writeReturnStatement(const ReturnStatement& s) override; + + void writeSwitchStatement(const SwitchStatement& s) override; + + void writeFunctionCall(const FunctionCall& c) override; + + void writeFunction(const FunctionDefinition& f) override; + + void writeSetting(const Setting& s) override; + + void writeProgramElement(const ProgramElement& p) override; + + void addUniform(const Variable& var); + + // writes a printf escape that will be filled in at runtime by the given C++ expression string + void writeRuntimeValue(const Type& type, const Layout& layout, const String& cppCode); + + void writeVarInitializer(const Variable& var, const Expression& value) override; + + void writeInputVars() override; + + void writePrivateVars(); + + void writePrivateVarValues(); + + void writeCodeAppend(const String& code); + + bool writeEmitCode(std::vector& uniforms); + + void writeSetData(std::vector& uniforms); + + void writeGetKey(); + + void writeOnTextureSampler(); + + void writeClone(); + + void writeTest(); + + // If the returned C++ is included in the generated code, then the variable name stored in + // cppVar will refer to a valid SkString that matches the Expression. Successful returns leave + // the output buffer (and related state) unmodified. + // + // In the simplest cases, this will return "SkString {cppVar}(\"{e}\");", while more advanced + // cases will properly insert format arguments. + String convertSKSLExpressionToCPP(const Expression& e, const String& cppVar); + + // Process accumulated sksl to split it into appended code sections, properly interleaved with + // the extra emit code blocks, based on statement/block locations and the inserted tokens + // from newExtraEmitCodeBlock(). It is necessary to split the sksl after the program has been + // fully walked since many elements redirect fOut to simultaneously build header sections and + // bodies that are then concatenated; due to this it is not possible to split the sksl emission + // on the fly. + void flushEmittedCode(); + + // Start a new extra emit code block for accumulating C++ code. This will insert a token into + // the sksl stream to mark the fence between previous complete sksl statements and where the + // C++ code added to the new block will be added to emitCode(). These tokens are removed by + // flushEmittedCode() as it consumes them before passing pure sksl to writeCodeAppend(). + void newExtraEmitCodeBlock(); + + // Append CPP code to the current extra emit code block. + void addExtraEmitCodeLine(const String& toAppend); + + int getChildFPIndex(const Variable& var) const; + + String fName; + String fFullName; + SectionAndParameterHelper fSectionAndParameterHelper; + std::vector fExtraEmitCodeBlocks; + + std::vector fFormatArgs; + std::set fWrittenTransformedCoords; + // if true, we are writing a C++ expression instead of a GLSL expression + bool fCPPMode = false; + bool fInMain = false; + + // if not null, we are accumulating SkSL for emitCode into fOut, which + // replaced the original buffer with a StringStream. The original buffer is + // stored here for restoration. + OutputStream* fCPPBuffer = nullptr; + + typedef GLSLCodeGenerator INHERITED; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.cpp b/gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.cpp new file mode 100644 index 0000000000..2740762ac8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.cpp @@ -0,0 +1,234 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLCPPUniformCTypes.h" +#include "src/sksl/SkSLHCodeGenerator.h" +#include "src/sksl/SkSLStringStream.h" + +#include + +namespace SkSL { + +///////////////////////// +// Template evaluation // +///////////////////////// + +static String eval_template(const String& format, const std::vector& tokens, + const std::vector& values) { + StringStream stream; + + int tokenNameStart = -1; + for (size_t i = 0; i < format.size(); i++) { + if (tokenNameStart >= 0) { + // Within a token name so check if it is the end + if (format[i] == '}') { + // Skip 2 extra characters at the beginning for the $ and {, which must exist since + // otherwise tokenNameStart < 0 + String token(format.c_str() + tokenNameStart + 2, i - tokenNameStart - 2); + // Search for the token in supported list + bool found = false; + for (size_t j = 0; j < tokens.size(); j++) { + if (token == tokens[j]) { + // Found a match so append the value corresponding to j to the output + stream.writeText(values[j]->c_str()); + found = true; + break; + } + } + + if (!found) { + // Write out original characters as if we didn't consider it to be a token name + stream.writeText("${"); + stream.writeText(token.c_str()); + stream.writeText("}"); + } + + // And end the token name state + tokenNameStart = -1; + } + } else { + // Outside of a token name, so check if this character starts a name: + // i == $ and i+1 == { + if (i < format.size() - 1 && format[i] == '$' && format[i + 1] == '{') { + // Begin parsing the token + tokenNameStart = i; + } else { + // Just a character so append it + stream.write8(format[i]); + } + } + } + + return stream.str(); +} + +static bool determine_inline_from_template(const String& uniformTemplate) { + // True if there is at most one instance of the ${var} template matcher in fUniformTemplate. + int firstMatch = uniformTemplate.find("${var}"); + + if (firstMatch < 0) { + // Template doesn't use the value variable at all, so it can "inlined" + return true; + } + + // Check for another occurrence of ${var}, after firstMatch + 6 + int secondMatch = uniformTemplate.find("${var}", firstMatch + strlen("${var}")); + // If there's no second match, then the value can be inlined in the c++ code + return secondMatch < 0; +} + +/////////////////////////////////////// +// UniformCTypeMapper implementation // +/////////////////////////////////////// + +String UniformCTypeMapper::dirtyExpression(const String& newVar, const String& oldVar) const { + if (fSupportsTracking) { + std::vector tokens = { "newVar", "oldVar" }; + std::vector values = { &newVar, &oldVar }; + return eval_template(fDirtyExpressionTemplate, tokens, values); + } else { + return ""; + } +} + +String UniformCTypeMapper::saveState(const String& newVar, const String& oldVar) const { + if (fSupportsTracking) { + std::vector tokens = { "newVar", "oldVar" }; + std::vector values = { &newVar, &oldVar }; + return eval_template(fSaveStateTemplate, tokens, values); + } else { + return ""; + } +} + +String UniformCTypeMapper::setUniform(const String& pdman, const String& uniform, + const String& var) const { + std::vector tokens = { "pdman", "uniform", "var" }; + std::vector values = { &pdman, &uniform, &var }; + return eval_template(fUniformTemplate, tokens, values); +} + +UniformCTypeMapper::UniformCTypeMapper( + Layout::CType ctype, const std::vector& skslTypes, const String& setUniformFormat, + bool enableTracking, const String& defaultValue, const String& dirtyExpressionFormat, + const String& saveStateFormat) + : fCType(ctype) + , fSKSLTypes(skslTypes) + , fUniformTemplate(setUniformFormat) + , fInlineValue(determine_inline_from_template(setUniformFormat)) + , fSupportsTracking(enableTracking) + , fDefaultValue(defaultValue) + , fDirtyExpressionTemplate(dirtyExpressionFormat) + , fSaveStateTemplate(saveStateFormat) { } + +// NOTE: These would be macros, but C++ initialization lists for the sksl type names do not play +// well with macro parsing. + +static UniformCTypeMapper REGISTER(Layout::CType ctype, const std::vector& skslTypes, + const char* uniformFormat, const char* defaultValue, + const char* dirtyExpression) { + return UniformCTypeMapper(ctype, skslTypes, uniformFormat, defaultValue, dirtyExpression, + "${oldVar} = ${newVar}"); +} + +static UniformCTypeMapper REGISTER(Layout::CType ctype, const std::vector& skslTypes, + const char* uniformFormat, const char* defaultValue) { + return REGISTER(ctype, skslTypes, uniformFormat, defaultValue, + "${oldVar} != ${newVar}"); +} + +////////////////////////////// +// Currently defined ctypes // +////////////////////////////// + +static const std::vector& get_mappers() { + static const std::vector registeredMappers = { + REGISTER(Layout::CType::kSkRect, { "half4", "float4", "double4" }, + "${pdman}.set4fv(${uniform}, 1, reinterpret_cast(&${var}))", // to gpu + "SkRect::MakeEmpty()", // default value + "${oldVar}.isEmpty() || ${oldVar} != ${newVar}"), // dirty check + + REGISTER(Layout::CType::kSkIRect, { "int4", "short4", "byte4" }, + "${pdman}.set4iv(${uniform}, 1, reinterpret_cast(&${var}))", // to gpu + "SkIRect::MakeEmpty()", // default value + "${oldVar}.isEmpty() || ${oldVar} != ${newVar}"), // dirty check + + REGISTER(Layout::CType::kSkPMColor4f, { "half4", "float4", "double4" }, + "${pdman}.set4fv(${uniform}, 1, ${var}.vec())", // to gpu + "{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}"), // default value + + REGISTER(Layout::CType::kSkVector4, { "half4", "float4", "double4" }, + "${pdman}.set4fv(${uniform}, 1, ${var}.fData)", // to gpu + "SkVector4(SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN, SK_MScalarNaN)", // default value + "${oldVar} != (${newVar})"), // dirty check + + REGISTER(Layout::CType::kSkPoint, { "half2", "float2", "double2" } , + "${pdman}.set2f(${uniform}, ${var}.fX, ${var}.fY)", // to gpu + "SkPoint::Make(SK_FloatNaN, SK_FloatNaN)"), // default value + + REGISTER(Layout::CType::kSkIPoint, { "int2", "short2", "byte2" }, + "${pdman}.set2i(${uniform}, ${var}.fX, ${var}.fY)", // to gpu + "SkIPoint::Make(SK_NaN32, SK_NaN32)"), // default value + + REGISTER(Layout::CType::kSkMatrix, { "half3x3", "float3x3", "double3x3" }, + "${pdman}.setSkMatrix(${uniform}, ${var})", // to gpu + "SkMatrix::MakeScale(SK_FloatNaN)", // default value + "!${oldVar}.cheapEqualTo(${newVar})"), // dirty check + + REGISTER(Layout::CType::kSkMatrix44, { "half4x4", "float4x4", "double4x4" }, + "${pdman}.setSkMatrix44(${uniform}, ${var})", // to gpu + "SkMatrix44(SkMatrix44::kNaN_Constructor)", // default value + "${oldVar} != (${newVar})"), // dirty check + + REGISTER(Layout::CType::kFloat, { "half", "float", "double" }, + "${pdman}.set1f(${uniform}, ${var})", // to gpu + "SK_FloatNaN"), // default value + + REGISTER(Layout::CType::kInt32, { "int", "short", "byte" }, + "${pdman}.set1i(${uniform}, ${var})", // to gpu + "SK_NaN32"), // default value + }; + + return registeredMappers; +} + +///// + +// Greedy search through registered handlers for one that has a matching +// ctype and supports the sksl type of the variable. +const UniformCTypeMapper* UniformCTypeMapper::Get(const Context& context, const Type& type, + const Layout& layout) { + const std::vector& registeredMappers = get_mappers(); + + Layout::CType ctype = layout.fCType; + // If there's no custom ctype declared in the layout, use the default type mapping + if (ctype == Layout::CType::kDefault) { + ctype = HCodeGenerator::ParameterCType(context, type, layout); + } + + const String& skslType = type.name(); + + for (size_t i = 0; i < registeredMappers.size(); i++) { + if (registeredMappers[i].ctype() == ctype) { + // Check for sksl support, since some c types (e.g. SkMatrix) can be used in multiple + // uniform types and send data to the gpu differently in those conditions + const std::vector supportedSKSL = registeredMappers[i].supportedTypeNames(); + for (size_t j = 0; j < supportedSKSL.size(); j++) { + if (supportedSKSL[j] == skslType) { + // Found a match, so return it or an explicitly untracked version if tracking is + // disabled in the layout + return ®isteredMappers[i]; + } + } + } + } + + // Didn't find a match + return nullptr; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.h b/gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.h new file mode 100644 index 0000000000..c2c01f9984 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCPPUniformCTypes.h @@ -0,0 +1,133 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSLUniformCTypes_DEFINED +#define SkSLUniformCTypes_DEFINED + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLString.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +// This uses templates to define dirtyExpression(), saveState() and setUniform(). Each template can +// reference token names formatted ${name} that are replaced with the actual values passed into the +// functions. +// +// dirtyExpression() and saveState() support the following tokens: +// - ${newVar} replaced with value of newValueVarName (1st argument) +// - ${oldVar} replaced with value of oldValueVarName (2nd argument) +// +// setUniform() supports these tokens: +// - ${pdman} replaced with value of pdmanName (1st argument) +// - ${uniform} replaced with value of uniformHandleName (2nd argument) +// - ${var} replaced with value of valueVarName (3rd argument) +// +// All templates and C++ snippets should produce valid expressions, but do not need to include +// semicolons or newlines, which will be handled by the code generation itself. +class UniformCTypeMapper { +public: + // Create a templated mapper that does not support state tracking + UniformCTypeMapper(Layout::CType ctype, const std::vector& skslTypes, + const char* setUniformFormat) + : UniformCTypeMapper(ctype, skslTypes, setUniformFormat, false, "", "", "") { } + + // Create a templated mapper that provides extra patterns for the state + // tracking expressions. + UniformCTypeMapper(Layout::CType ctype, const std::vector& skslTypes, + const String& setUniformFormat, const String& defaultValue, + const String& dirtyExpressionFormat, const String& saveStateFormat) + : UniformCTypeMapper(ctype, skslTypes, setUniformFormat, + true, defaultValue, dirtyExpressionFormat, saveStateFormat) { } + + // Returns nullptr if the type and layout are not supported; the returned pointer's ownership + // is not transfered to the caller. + // + // The returned mapper can support tracking even if tracking is disabled based on the flags in + // the layout. + static const UniformCTypeMapper* Get(const Context& context, const Type& type, + const Layout& layout); + + static const UniformCTypeMapper* Get(const Context& context, const Variable& variable) { + return Get(context, variable.fType, variable.fModifiers.fLayout); + } + + // The C++ type name that this mapper applies to + Layout::CType ctype() const { + return fCType; + } + + // The sksl type names that the mapper's ctype can be mapped to + const std::vector& supportedTypeNames() const { + return fSKSLTypes; + } + + // Whether or not this handler knows how to write state tracking code + // for the uniform variables + bool supportsTracking() const { + return fSupportsTracking; + } + + // What the C++ class fields are initialized to in the GLSLFragmentProcessor The empty string + // implies the no-arg constructor is suitable. This is not used if supportsTracking() returns + // false. + // + // The returned snippet will be a valid as the lhs of an assignment. + const String& defaultValue() const { + return fDefaultValue; + } + + // Return a boolean expression that returns true if the variables specified by newValueVarName + // and oldValueVarName have different values. This is ignored if supportsTracking() returns + // false. + // + // The returned snippet will be a valid expression to be inserted into the condition of an 'if' + // statement. + String dirtyExpression(const String& newValueVarName, const String& oldValueVarName) const; + + // Return a statement that stores the value of newValueVarName into the variable specified by + // oldValueVarName. This is ignored if supportsTracking() returns false. + // + // The returned snippet will be a valid expression. + String saveState(const String& newValueVarName, const String& oldValueVarName) const; + + // Return a statement that invokes the appropriate setX method on the GrGLSLProgramDataManager + // specified by pdmanName, where the uniform is provided by the expression stored in + // uniformHandleName, and valueVarName is the variable name pointing to the ctype instance + // holding the new value. + // + // The returned snippet will be a valid expression. + String setUniform(const String& pdmanName, const String& uniformHandleName, + const String& valueVarName) const; + + // True if the setUniform() template only uses the value variable once in its expression. The + // variable does not necessarily get inlined if this returns true, since a local variable may be + // needed if state tracking is employed for a particular uniform. + bool canInlineUniformValue() const { + return fInlineValue; + } + +private: + UniformCTypeMapper(Layout::CType ctype, const std::vector& skslTypes, + const String& setUniformFormat, bool enableTracking, const String& defaultValue, + const String& dirtyExpressionFormat, const String& saveStateFormat); + + Layout::CType fCType; + std::vector fSKSLTypes; + String fUniformTemplate; + bool fInlineValue; // Cached value calculated from fUniformTemplate + + bool fSupportsTracking; + String fDefaultValue; + String fDirtyExpressionTemplate; + String fSaveStateTemplate; +}; + +} // namespace + +#endif // SkSLUniformCTypes_DEFINED diff --git a/gfx/skia/skia/src/sksl/SkSLCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLCodeGenerator.h new file mode 100644 index 0000000000..8541c29468 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCodeGenerator.h @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CODEGENERATOR +#define SKSL_CODEGENERATOR + +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/ir/SkSLProgram.h" + +namespace SkSL { + +/** + * Abstract superclass of all code generators, which take a Program as input and produce code as + * output. + */ +class CodeGenerator { +public: + CodeGenerator(const Program* program, ErrorReporter* errors, OutputStream* out) + : fProgram(*program) + , fErrors(*errors) + , fOut(out) { + SkASSERT(program->fIsOptimized); + } + + virtual ~CodeGenerator() {} + + virtual bool generateCode() = 0; + +protected: + + const Program& fProgram; + ErrorReporter& fErrors; + OutputStream* fOut; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLCompiler.cpp b/gfx/skia/skia/src/sksl/SkSLCompiler.cpp new file mode 100644 index 0000000000..7aa6b9f641 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCompiler.cpp @@ -0,0 +1,1676 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLCompiler.h" + +#include "src/sksl/SkSLByteCodeGenerator.h" +#include "src/sksl/SkSLCFGGenerator.h" +#include "src/sksl/SkSLCPPCodeGenerator.h" +#include "src/sksl/SkSLGLSLCodeGenerator.h" +#include "src/sksl/SkSLHCodeGenerator.h" +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/SkSLMetalCodeGenerator.h" +#include "src/sksl/SkSLPipelineStageCodeGenerator.h" +#include "src/sksl/SkSLSPIRVCodeGenerator.h" +#include "src/sksl/ir/SkSLEnum.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLUnresolvedFunction.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +#ifdef SK_ENABLE_SPIRV_VALIDATION +#include "spirv-tools/libspirv.hpp" +#endif + +// include the built-in shader symbols as static strings + +#define STRINGIFY(x) #x + +static const char* SKSL_GPU_INCLUDE = +#include "sksl_gpu.inc" +; + +static const char* SKSL_INTERP_INCLUDE = +#include "sksl_interp.inc" +; + +static const char* SKSL_VERT_INCLUDE = +#include "sksl_vert.inc" +; + +static const char* SKSL_FRAG_INCLUDE = +#include "sksl_frag.inc" +; + +static const char* SKSL_GEOM_INCLUDE = +#include "sksl_geom.inc" +; + +static const char* SKSL_FP_INCLUDE = +#include "sksl_enums.inc" +#include "sksl_fp.inc" +; + +static const char* SKSL_PIPELINE_INCLUDE = +#include "sksl_pipeline.inc" +; + +namespace SkSL { + +Compiler::Compiler(Flags flags) +: fFlags(flags) +, fContext(new Context()) +, fErrorCount(0) { + auto types = std::shared_ptr(new SymbolTable(this)); + auto symbols = std::shared_ptr(new SymbolTable(types, this)); + fIRGenerator = new IRGenerator(fContext.get(), symbols, *this); + fTypes = types; + #define ADD_TYPE(t) types->addWithoutOwnership(fContext->f ## t ## _Type->fName, \ + fContext->f ## t ## _Type.get()) + ADD_TYPE(Void); + ADD_TYPE(Float); + ADD_TYPE(Float2); + ADD_TYPE(Float3); + ADD_TYPE(Float4); + ADD_TYPE(Half); + ADD_TYPE(Half2); + ADD_TYPE(Half3); + ADD_TYPE(Half4); + ADD_TYPE(Double); + ADD_TYPE(Double2); + ADD_TYPE(Double3); + ADD_TYPE(Double4); + ADD_TYPE(Int); + ADD_TYPE(Int2); + ADD_TYPE(Int3); + ADD_TYPE(Int4); + ADD_TYPE(UInt); + ADD_TYPE(UInt2); + ADD_TYPE(UInt3); + ADD_TYPE(UInt4); + ADD_TYPE(Short); + ADD_TYPE(Short2); + ADD_TYPE(Short3); + ADD_TYPE(Short4); + ADD_TYPE(UShort); + ADD_TYPE(UShort2); + ADD_TYPE(UShort3); + ADD_TYPE(UShort4); + ADD_TYPE(Byte); + ADD_TYPE(Byte2); + ADD_TYPE(Byte3); + ADD_TYPE(Byte4); + ADD_TYPE(UByte); + ADD_TYPE(UByte2); + ADD_TYPE(UByte3); + ADD_TYPE(UByte4); + ADD_TYPE(Bool); + ADD_TYPE(Bool2); + ADD_TYPE(Bool3); + ADD_TYPE(Bool4); + ADD_TYPE(Float2x2); + ADD_TYPE(Float2x3); + ADD_TYPE(Float2x4); + ADD_TYPE(Float3x2); + ADD_TYPE(Float3x3); + ADD_TYPE(Float3x4); + ADD_TYPE(Float4x2); + ADD_TYPE(Float4x3); + ADD_TYPE(Float4x4); + ADD_TYPE(Half2x2); + ADD_TYPE(Half2x3); + ADD_TYPE(Half2x4); + ADD_TYPE(Half3x2); + ADD_TYPE(Half3x3); + ADD_TYPE(Half3x4); + ADD_TYPE(Half4x2); + ADD_TYPE(Half4x3); + ADD_TYPE(Half4x4); + ADD_TYPE(Double2x2); + ADD_TYPE(Double2x3); + ADD_TYPE(Double2x4); + ADD_TYPE(Double3x2); + ADD_TYPE(Double3x3); + ADD_TYPE(Double3x4); + ADD_TYPE(Double4x2); + ADD_TYPE(Double4x3); + ADD_TYPE(Double4x4); + ADD_TYPE(GenType); + ADD_TYPE(GenHType); + ADD_TYPE(GenDType); + ADD_TYPE(GenIType); + ADD_TYPE(GenUType); + ADD_TYPE(GenBType); + ADD_TYPE(Mat); + ADD_TYPE(Vec); + ADD_TYPE(GVec); + ADD_TYPE(GVec2); + ADD_TYPE(GVec3); + ADD_TYPE(GVec4); + ADD_TYPE(HVec); + ADD_TYPE(DVec); + ADD_TYPE(IVec); + ADD_TYPE(UVec); + ADD_TYPE(SVec); + ADD_TYPE(USVec); + ADD_TYPE(ByteVec); + ADD_TYPE(UByteVec); + ADD_TYPE(BVec); + + ADD_TYPE(Sampler1D); + ADD_TYPE(Sampler2D); + ADD_TYPE(Sampler3D); + ADD_TYPE(SamplerExternalOES); + ADD_TYPE(SamplerCube); + ADD_TYPE(Sampler2DRect); + ADD_TYPE(Sampler1DArray); + ADD_TYPE(Sampler2DArray); + ADD_TYPE(SamplerCubeArray); + ADD_TYPE(SamplerBuffer); + ADD_TYPE(Sampler2DMS); + ADD_TYPE(Sampler2DMSArray); + + ADD_TYPE(ISampler2D); + + ADD_TYPE(Image2D); + ADD_TYPE(IImage2D); + + ADD_TYPE(SubpassInput); + ADD_TYPE(SubpassInputMS); + + ADD_TYPE(GSampler1D); + ADD_TYPE(GSampler2D); + ADD_TYPE(GSampler3D); + ADD_TYPE(GSamplerCube); + ADD_TYPE(GSampler2DRect); + ADD_TYPE(GSampler1DArray); + ADD_TYPE(GSampler2DArray); + ADD_TYPE(GSamplerCubeArray); + ADD_TYPE(GSamplerBuffer); + ADD_TYPE(GSampler2DMS); + ADD_TYPE(GSampler2DMSArray); + + ADD_TYPE(Sampler1DShadow); + ADD_TYPE(Sampler2DShadow); + ADD_TYPE(SamplerCubeShadow); + ADD_TYPE(Sampler2DRectShadow); + ADD_TYPE(Sampler1DArrayShadow); + ADD_TYPE(Sampler2DArrayShadow); + ADD_TYPE(SamplerCubeArrayShadow); + ADD_TYPE(GSampler2DArrayShadow); + ADD_TYPE(GSamplerCubeArrayShadow); + ADD_TYPE(FragmentProcessor); + ADD_TYPE(Sampler); + ADD_TYPE(Texture2D); + + StringFragment skCapsName("sk_Caps"); + Variable* skCaps = new Variable(-1, Modifiers(), skCapsName, + *fContext->fSkCaps_Type, Variable::kGlobal_Storage); + fIRGenerator->fSymbolTable->add(skCapsName, std::unique_ptr(skCaps)); + + StringFragment skArgsName("sk_Args"); + Variable* skArgs = new Variable(-1, Modifiers(), skArgsName, + *fContext->fSkArgs_Type, Variable::kGlobal_Storage); + fIRGenerator->fSymbolTable->add(skArgsName, std::unique_ptr(skArgs)); + + std::vector> ignored; + this->processIncludeFile(Program::kFragment_Kind, SKSL_GPU_INCLUDE, strlen(SKSL_GPU_INCLUDE), + symbols, &ignored, &fGpuSymbolTable); + this->processIncludeFile(Program::kVertex_Kind, SKSL_VERT_INCLUDE, strlen(SKSL_VERT_INCLUDE), + fGpuSymbolTable, &fVertexInclude, &fVertexSymbolTable); + this->processIncludeFile(Program::kFragment_Kind, SKSL_FRAG_INCLUDE, strlen(SKSL_FRAG_INCLUDE), + fGpuSymbolTable, &fFragmentInclude, &fFragmentSymbolTable); + this->processIncludeFile(Program::kGeometry_Kind, SKSL_GEOM_INCLUDE, strlen(SKSL_GEOM_INCLUDE), + fGpuSymbolTable, &fGeometryInclude, &fGeometrySymbolTable); + this->processIncludeFile(Program::kPipelineStage_Kind, SKSL_PIPELINE_INCLUDE, + strlen(SKSL_PIPELINE_INCLUDE), fGpuSymbolTable, &fPipelineInclude, + &fPipelineSymbolTable); + this->processIncludeFile(Program::kGeneric_Kind, SKSL_INTERP_INCLUDE, + strlen(SKSL_INTERP_INCLUDE), symbols, &fInterpreterInclude, + &fInterpreterSymbolTable); +} + +Compiler::~Compiler() { + delete fIRGenerator; +} + +void Compiler::processIncludeFile(Program::Kind kind, const char* src, size_t length, + std::shared_ptr base, + std::vector>* outElements, + std::shared_ptr* outSymbolTable) { + fIRGenerator->fSymbolTable = std::move(base); + Program::Settings settings; + fIRGenerator->start(&settings, nullptr); + fIRGenerator->convertProgram(kind, src, length, *fTypes, outElements); + if (this->fErrorCount) { + printf("Unexpected errors: %s\n", this->fErrorText.c_str()); + } + SkASSERT(!fErrorCount); + fIRGenerator->fSymbolTable->markAllFunctionsBuiltin(); + *outSymbolTable = fIRGenerator->fSymbolTable; +} + +// add the definition created by assigning to the lvalue to the definition set +void Compiler::addDefinition(const Expression* lvalue, std::unique_ptr* expr, + DefinitionMap* definitions) { + switch (lvalue->fKind) { + case Expression::kVariableReference_Kind: { + const Variable& var = ((VariableReference*) lvalue)->fVariable; + if (var.fStorage == Variable::kLocal_Storage) { + (*definitions)[&var] = expr; + } + break; + } + case Expression::kSwizzle_Kind: + // We consider the variable written to as long as at least some of its components have + // been written to. This will lead to some false negatives (we won't catch it if you + // write to foo.x and then read foo.y), but being stricter could lead to false positives + // (we write to foo.x, and then pass foo to a function which happens to only read foo.x, + // but since we pass foo as a whole it is flagged as an error) unless we perform a much + // more complicated whole-program analysis. This is probably good enough. + this->addDefinition(((Swizzle*) lvalue)->fBase.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + break; + case Expression::kIndex_Kind: + // see comments in Swizzle + this->addDefinition(((IndexExpression*) lvalue)->fBase.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + break; + case Expression::kFieldAccess_Kind: + // see comments in Swizzle + this->addDefinition(((FieldAccess*) lvalue)->fBase.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + break; + case Expression::kTernary_Kind: + // To simplify analysis, we just pretend that we write to both sides of the ternary. + // This allows for false positives (meaning we fail to detect that a variable might not + // have been assigned), but is preferable to false negatives. + this->addDefinition(((TernaryExpression*) lvalue)->fIfTrue.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + this->addDefinition(((TernaryExpression*) lvalue)->fIfFalse.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + break; + case Expression::kExternalValue_Kind: + break; + default: + // not an lvalue, can't happen + SkASSERT(false); + } +} + +// add local variables defined by this node to the set +void Compiler::addDefinitions(const BasicBlock::Node& node, + DefinitionMap* definitions) { + switch (node.fKind) { + case BasicBlock::Node::kExpression_Kind: { + SkASSERT(node.expression()); + const Expression* expr = (Expression*) node.expression()->get(); + switch (expr->fKind) { + case Expression::kBinary_Kind: { + BinaryExpression* b = (BinaryExpression*) expr; + if (b->fOperator == Token::EQ) { + this->addDefinition(b->fLeft.get(), &b->fRight, definitions); + } else if (Compiler::IsAssignment(b->fOperator)) { + this->addDefinition( + b->fLeft.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + + } + break; + } + case Expression::kFunctionCall_Kind: { + const FunctionCall& c = (const FunctionCall&) *expr; + for (size_t i = 0; i < c.fFunction.fParameters.size(); ++i) { + if (c.fFunction.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) { + this->addDefinition( + c.fArguments[i].get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + } + } + break; + } + case Expression::kPrefix_Kind: { + const PrefixExpression* p = (PrefixExpression*) expr; + if (p->fOperator == Token::MINUSMINUS || p->fOperator == Token::PLUSPLUS) { + this->addDefinition( + p->fOperand.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + } + break; + } + case Expression::kPostfix_Kind: { + const PostfixExpression* p = (PostfixExpression*) expr; + if (p->fOperator == Token::MINUSMINUS || p->fOperator == Token::PLUSPLUS) { + this->addDefinition( + p->fOperand.get(), + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + } + break; + } + case Expression::kVariableReference_Kind: { + const VariableReference* v = (VariableReference*) expr; + if (v->fRefKind != VariableReference::kRead_RefKind) { + this->addDefinition( + v, + (std::unique_ptr*) &fContext->fDefined_Expression, + definitions); + } + } + default: + break; + } + break; + } + case BasicBlock::Node::kStatement_Kind: { + const Statement* stmt = (Statement*) node.statement()->get(); + if (stmt->fKind == Statement::kVarDeclaration_Kind) { + VarDeclaration& vd = (VarDeclaration&) *stmt; + if (vd.fValue) { + (*definitions)[vd.fVar] = &vd.fValue; + } + } + break; + } + } +} + +void Compiler::scanCFG(CFG* cfg, BlockId blockId, std::set* workList) { + BasicBlock& block = cfg->fBlocks[blockId]; + + // compute definitions after this block + DefinitionMap after = block.fBefore; + for (const BasicBlock::Node& n : block.fNodes) { + this->addDefinitions(n, &after); + } + + // propagate definitions to exits + for (BlockId exitId : block.fExits) { + if (exitId == blockId) { + continue; + } + BasicBlock& exit = cfg->fBlocks[exitId]; + for (const auto& pair : after) { + std::unique_ptr* e1 = pair.second; + auto found = exit.fBefore.find(pair.first); + if (found == exit.fBefore.end()) { + // exit has no definition for it, just copy it + workList->insert(exitId); + exit.fBefore[pair.first] = e1; + } else { + // exit has a (possibly different) value already defined + std::unique_ptr* e2 = exit.fBefore[pair.first]; + if (e1 != e2) { + // definition has changed, merge and add exit block to worklist + workList->insert(exitId); + if (e1 && e2) { + exit.fBefore[pair.first] = + (std::unique_ptr*) &fContext->fDefined_Expression; + } else { + exit.fBefore[pair.first] = nullptr; + } + } + } + } + } +} + +// returns a map which maps all local variables in the function to null, indicating that their value +// is initially unknown +static DefinitionMap compute_start_state(const CFG& cfg) { + DefinitionMap result; + for (const auto& block : cfg.fBlocks) { + for (const auto& node : block.fNodes) { + if (node.fKind == BasicBlock::Node::kStatement_Kind) { + SkASSERT(node.statement()); + const Statement* s = node.statement()->get(); + if (s->fKind == Statement::kVarDeclarations_Kind) { + const VarDeclarationsStatement* vd = (const VarDeclarationsStatement*) s; + for (const auto& decl : vd->fDeclaration->fVars) { + if (decl->fKind == Statement::kVarDeclaration_Kind) { + result[((VarDeclaration&) *decl).fVar] = nullptr; + } + } + } + } + } + } + return result; +} + +/** + * Returns true if assigning to this lvalue has no effect. + */ +static bool is_dead(const Expression& lvalue) { + switch (lvalue.fKind) { + case Expression::kVariableReference_Kind: + return ((VariableReference&) lvalue).fVariable.dead(); + case Expression::kSwizzle_Kind: + return is_dead(*((Swizzle&) lvalue).fBase); + case Expression::kFieldAccess_Kind: + return is_dead(*((FieldAccess&) lvalue).fBase); + case Expression::kIndex_Kind: { + const IndexExpression& idx = (IndexExpression&) lvalue; + return is_dead(*idx.fBase) && !idx.fIndex->hasSideEffects(); + } + case Expression::kTernary_Kind: { + const TernaryExpression& t = (TernaryExpression&) lvalue; + return !t.fTest->hasSideEffects() && is_dead(*t.fIfTrue) && is_dead(*t.fIfFalse); + } + case Expression::kExternalValue_Kind: + return false; + default: + ABORT("invalid lvalue: %s\n", lvalue.description().c_str()); + } +} + +/** + * Returns true if this is an assignment which can be collapsed down to just the right hand side due + * to a dead target and lack of side effects on the left hand side. + */ +static bool dead_assignment(const BinaryExpression& b) { + if (!Compiler::IsAssignment(b.fOperator)) { + return false; + } + return is_dead(*b.fLeft); +} + +void Compiler::computeDataFlow(CFG* cfg) { + cfg->fBlocks[cfg->fStart].fBefore = compute_start_state(*cfg); + std::set workList; + for (BlockId i = 0; i < cfg->fBlocks.size(); i++) { + workList.insert(i); + } + while (workList.size()) { + BlockId next = *workList.begin(); + workList.erase(workList.begin()); + this->scanCFG(cfg, next, &workList); + } +} + +/** + * Attempts to replace the expression pointed to by iter with a new one (in both the CFG and the + * IR). If the expression can be cleanly removed, returns true and updates the iterator to point to + * the newly-inserted element. Otherwise updates only the IR and returns false (and the CFG will + * need to be regenerated). + */ +bool try_replace_expression(BasicBlock* b, + std::vector::iterator* iter, + std::unique_ptr* newExpression) { + std::unique_ptr* target = (*iter)->expression(); + if (!b->tryRemoveExpression(iter)) { + *target = std::move(*newExpression); + return false; + } + *target = std::move(*newExpression); + return b->tryInsertExpression(iter, target); +} + +/** + * Returns true if the expression is a constant numeric literal with the specified value, or a + * constant vector with all elements equal to the specified value. + */ +bool is_constant(const Expression& expr, double value) { + switch (expr.fKind) { + case Expression::kIntLiteral_Kind: + return ((IntLiteral&) expr).fValue == value; + case Expression::kFloatLiteral_Kind: + return ((FloatLiteral&) expr).fValue == value; + case Expression::kConstructor_Kind: { + Constructor& c = (Constructor&) expr; + bool isFloat = c.fType.columns() > 1 ? c.fType.componentType().isFloat() + : c.fType.isFloat(); + if (c.fType.kind() == Type::kVector_Kind && c.isConstant()) { + for (int i = 0; i < c.fType.columns(); ++i) { + if (isFloat) { + if (c.getFVecComponent(i) != value) { + return false; + } + } else if (c.getIVecComponent(i) != value) { + return false; + } + } + return true; + } + return false; + } + default: + return false; + } +} + +/** + * Collapses the binary expression pointed to by iter down to just the right side (in both the IR + * and CFG structures). + */ +void delete_left(BasicBlock* b, + std::vector::iterator* iter, + bool* outUpdated, + bool* outNeedsRescan) { + *outUpdated = true; + std::unique_ptr* target = (*iter)->expression(); + SkASSERT((*target)->fKind == Expression::kBinary_Kind); + BinaryExpression& bin = (BinaryExpression&) **target; + SkASSERT(!bin.fLeft->hasSideEffects()); + bool result; + if (bin.fOperator == Token::EQ) { + result = b->tryRemoveLValueBefore(iter, bin.fLeft.get()); + } else { + result = b->tryRemoveExpressionBefore(iter, bin.fLeft.get()); + } + *target = std::move(bin.fRight); + if (!result) { + *outNeedsRescan = true; + return; + } + if (*iter == b->fNodes.begin()) { + *outNeedsRescan = true; + return; + } + --(*iter); + if ((*iter)->fKind != BasicBlock::Node::kExpression_Kind || + (*iter)->expression() != &bin.fRight) { + *outNeedsRescan = true; + return; + } + *iter = b->fNodes.erase(*iter); + SkASSERT((*iter)->expression() == target); +} + +/** + * Collapses the binary expression pointed to by iter down to just the left side (in both the IR and + * CFG structures). + */ +void delete_right(BasicBlock* b, + std::vector::iterator* iter, + bool* outUpdated, + bool* outNeedsRescan) { + *outUpdated = true; + std::unique_ptr* target = (*iter)->expression(); + SkASSERT((*target)->fKind == Expression::kBinary_Kind); + BinaryExpression& bin = (BinaryExpression&) **target; + SkASSERT(!bin.fRight->hasSideEffects()); + if (!b->tryRemoveExpressionBefore(iter, bin.fRight.get())) { + *target = std::move(bin.fLeft); + *outNeedsRescan = true; + return; + } + *target = std::move(bin.fLeft); + if (*iter == b->fNodes.begin()) { + *outNeedsRescan = true; + return; + } + --(*iter); + if (((*iter)->fKind != BasicBlock::Node::kExpression_Kind || + (*iter)->expression() != &bin.fLeft)) { + *outNeedsRescan = true; + return; + } + *iter = b->fNodes.erase(*iter); + SkASSERT((*iter)->expression() == target); +} + +/** + * Constructs the specified type using a single argument. + */ +static std::unique_ptr construct(const Type& type, std::unique_ptr v) { + std::vector> args; + args.push_back(std::move(v)); + auto result = std::unique_ptr(new Constructor(-1, type, std::move(args))); + return result; +} + +/** + * Used in the implementations of vectorize_left and vectorize_right. Given a vector type and an + * expression x, deletes the expression pointed to by iter and replaces it with (x). + */ +static void vectorize(BasicBlock* b, + std::vector::iterator* iter, + const Type& type, + std::unique_ptr* otherExpression, + bool* outUpdated, + bool* outNeedsRescan) { + SkASSERT((*(*iter)->expression())->fKind == Expression::kBinary_Kind); + SkASSERT(type.kind() == Type::kVector_Kind); + SkASSERT((*otherExpression)->fType.kind() == Type::kScalar_Kind); + *outUpdated = true; + std::unique_ptr* target = (*iter)->expression(); + if (!b->tryRemoveExpression(iter)) { + *target = construct(type, std::move(*otherExpression)); + *outNeedsRescan = true; + } else { + *target = construct(type, std::move(*otherExpression)); + if (!b->tryInsertExpression(iter, target)) { + *outNeedsRescan = true; + } + } +} + +/** + * Given a binary expression of the form x vec(y), deletes the right side and vectorizes the + * left to yield vec(x). + */ +static void vectorize_left(BasicBlock* b, + std::vector::iterator* iter, + bool* outUpdated, + bool* outNeedsRescan) { + BinaryExpression& bin = (BinaryExpression&) **(*iter)->expression(); + vectorize(b, iter, bin.fRight->fType, &bin.fLeft, outUpdated, outNeedsRescan); +} + +/** + * Given a binary expression of the form vec(x) y, deletes the left side and vectorizes the + * right to yield vec(y). + */ +static void vectorize_right(BasicBlock* b, + std::vector::iterator* iter, + bool* outUpdated, + bool* outNeedsRescan) { + BinaryExpression& bin = (BinaryExpression&) **(*iter)->expression(); + vectorize(b, iter, bin.fLeft->fType, &bin.fRight, outUpdated, outNeedsRescan); +} + +// Mark that an expression which we were writing to is no longer being written to +void clear_write(const Expression& expr) { + switch (expr.fKind) { + case Expression::kVariableReference_Kind: { + ((VariableReference&) expr).setRefKind(VariableReference::kRead_RefKind); + break; + } + case Expression::kFieldAccess_Kind: + clear_write(*((FieldAccess&) expr).fBase); + break; + case Expression::kSwizzle_Kind: + clear_write(*((Swizzle&) expr).fBase); + break; + case Expression::kIndex_Kind: + clear_write(*((IndexExpression&) expr).fBase); + break; + default: + ABORT("shouldn't be writing to this kind of expression\n"); + break; + } +} + +void Compiler::simplifyExpression(DefinitionMap& definitions, + BasicBlock& b, + std::vector::iterator* iter, + std::unordered_set* undefinedVariables, + bool* outUpdated, + bool* outNeedsRescan) { + Expression* expr = (*iter)->expression()->get(); + SkASSERT(expr); + if ((*iter)->fConstantPropagation) { + std::unique_ptr optimized = expr->constantPropagate(*fIRGenerator, definitions); + if (optimized) { + *outUpdated = true; + if (!try_replace_expression(&b, iter, &optimized)) { + *outNeedsRescan = true; + return; + } + SkASSERT((*iter)->fKind == BasicBlock::Node::kExpression_Kind); + expr = (*iter)->expression()->get(); + } + } + switch (expr->fKind) { + case Expression::kVariableReference_Kind: { + const VariableReference& ref = (VariableReference&) *expr; + const Variable& var = ref.fVariable; + if (ref.refKind() != VariableReference::kWrite_RefKind && + ref.refKind() != VariableReference::kPointer_RefKind && + var.fStorage == Variable::kLocal_Storage && !definitions[&var] && + (*undefinedVariables).find(&var) == (*undefinedVariables).end()) { + (*undefinedVariables).insert(&var); + this->error(expr->fOffset, + "'" + var.fName + "' has not been assigned"); + } + break; + } + case Expression::kTernary_Kind: { + TernaryExpression* t = (TernaryExpression*) expr; + if (t->fTest->fKind == Expression::kBoolLiteral_Kind) { + // ternary has a constant test, replace it with either the true or + // false branch + if (((BoolLiteral&) *t->fTest).fValue) { + (*iter)->setExpression(std::move(t->fIfTrue)); + } else { + (*iter)->setExpression(std::move(t->fIfFalse)); + } + *outUpdated = true; + *outNeedsRescan = true; + } + break; + } + case Expression::kBinary_Kind: { + BinaryExpression* bin = (BinaryExpression*) expr; + if (dead_assignment(*bin)) { + delete_left(&b, iter, outUpdated, outNeedsRescan); + break; + } + // collapse useless expressions like x * 1 or x + 0 + if (((bin->fLeft->fType.kind() != Type::kScalar_Kind) && + (bin->fLeft->fType.kind() != Type::kVector_Kind)) || + ((bin->fRight->fType.kind() != Type::kScalar_Kind) && + (bin->fRight->fType.kind() != Type::kVector_Kind))) { + break; + } + switch (bin->fOperator) { + case Token::STAR: + if (is_constant(*bin->fLeft, 1)) { + if (bin->fLeft->fType.kind() == Type::kVector_Kind && + bin->fRight->fType.kind() == Type::kScalar_Kind) { + // float4(1) * x -> float4(x) + vectorize_right(&b, iter, outUpdated, outNeedsRescan); + } else { + // 1 * x -> x + // 1 * float4(x) -> float4(x) + // float4(1) * float4(x) -> float4(x) + delete_left(&b, iter, outUpdated, outNeedsRescan); + } + } + else if (is_constant(*bin->fLeft, 0)) { + if (bin->fLeft->fType.kind() == Type::kScalar_Kind && + bin->fRight->fType.kind() == Type::kVector_Kind && + !bin->fRight->hasSideEffects()) { + // 0 * float4(x) -> float4(0) + vectorize_left(&b, iter, outUpdated, outNeedsRescan); + } else { + // 0 * x -> 0 + // float4(0) * x -> float4(0) + // float4(0) * float4(x) -> float4(0) + if (!bin->fRight->hasSideEffects()) { + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + } + } + else if (is_constant(*bin->fRight, 1)) { + if (bin->fLeft->fType.kind() == Type::kScalar_Kind && + bin->fRight->fType.kind() == Type::kVector_Kind) { + // x * float4(1) -> float4(x) + vectorize_left(&b, iter, outUpdated, outNeedsRescan); + } else { + // x * 1 -> x + // float4(x) * 1 -> float4(x) + // float4(x) * float4(1) -> float4(x) + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + } + else if (is_constant(*bin->fRight, 0)) { + if (bin->fLeft->fType.kind() == Type::kVector_Kind && + bin->fRight->fType.kind() == Type::kScalar_Kind && + !bin->fLeft->hasSideEffects()) { + // float4(x) * 0 -> float4(0) + vectorize_right(&b, iter, outUpdated, outNeedsRescan); + } else { + // x * 0 -> 0 + // x * float4(0) -> float4(0) + // float4(x) * float4(0) -> float4(0) + if (!bin->fLeft->hasSideEffects()) { + delete_left(&b, iter, outUpdated, outNeedsRescan); + } + } + } + break; + case Token::PLUS: + if (is_constant(*bin->fLeft, 0)) { + if (bin->fLeft->fType.kind() == Type::kVector_Kind && + bin->fRight->fType.kind() == Type::kScalar_Kind) { + // float4(0) + x -> float4(x) + vectorize_right(&b, iter, outUpdated, outNeedsRescan); + } else { + // 0 + x -> x + // 0 + float4(x) -> float4(x) + // float4(0) + float4(x) -> float4(x) + delete_left(&b, iter, outUpdated, outNeedsRescan); + } + } else if (is_constant(*bin->fRight, 0)) { + if (bin->fLeft->fType.kind() == Type::kScalar_Kind && + bin->fRight->fType.kind() == Type::kVector_Kind) { + // x + float4(0) -> float4(x) + vectorize_left(&b, iter, outUpdated, outNeedsRescan); + } else { + // x + 0 -> x + // float4(x) + 0 -> float4(x) + // float4(x) + float4(0) -> float4(x) + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + } + break; + case Token::MINUS: + if (is_constant(*bin->fRight, 0)) { + if (bin->fLeft->fType.kind() == Type::kScalar_Kind && + bin->fRight->fType.kind() == Type::kVector_Kind) { + // x - float4(0) -> float4(x) + vectorize_left(&b, iter, outUpdated, outNeedsRescan); + } else { + // x - 0 -> x + // float4(x) - 0 -> float4(x) + // float4(x) - float4(0) -> float4(x) + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + } + break; + case Token::SLASH: + if (is_constant(*bin->fRight, 1)) { + if (bin->fLeft->fType.kind() == Type::kScalar_Kind && + bin->fRight->fType.kind() == Type::kVector_Kind) { + // x / float4(1) -> float4(x) + vectorize_left(&b, iter, outUpdated, outNeedsRescan); + } else { + // x / 1 -> x + // float4(x) / 1 -> float4(x) + // float4(x) / float4(1) -> float4(x) + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + } else if (is_constant(*bin->fLeft, 0)) { + if (bin->fLeft->fType.kind() == Type::kScalar_Kind && + bin->fRight->fType.kind() == Type::kVector_Kind && + !bin->fRight->hasSideEffects()) { + // 0 / float4(x) -> float4(0) + vectorize_left(&b, iter, outUpdated, outNeedsRescan); + } else { + // 0 / x -> 0 + // float4(0) / x -> float4(0) + // float4(0) / float4(x) -> float4(0) + if (!bin->fRight->hasSideEffects()) { + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + } + } + break; + case Token::PLUSEQ: + if (is_constant(*bin->fRight, 0)) { + clear_write(*bin->fLeft); + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + break; + case Token::MINUSEQ: + if (is_constant(*bin->fRight, 0)) { + clear_write(*bin->fLeft); + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + break; + case Token::STAREQ: + if (is_constant(*bin->fRight, 1)) { + clear_write(*bin->fLeft); + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + break; + case Token::SLASHEQ: + if (is_constant(*bin->fRight, 1)) { + clear_write(*bin->fLeft); + delete_right(&b, iter, outUpdated, outNeedsRescan); + } + break; + default: + break; + } + break; + } + case Expression::kSwizzle_Kind: { + Swizzle& s = (Swizzle&) *expr; + // detect identity swizzles like foo.rgba + if ((int) s.fComponents.size() == s.fBase->fType.columns()) { + bool identity = true; + for (int i = 0; i < (int) s.fComponents.size(); ++i) { + if (s.fComponents[i] != i) { + identity = false; + break; + } + } + if (identity) { + *outUpdated = true; + if (!try_replace_expression(&b, iter, &s.fBase)) { + *outNeedsRescan = true; + return; + } + SkASSERT((*iter)->fKind == BasicBlock::Node::kExpression_Kind); + break; + } + } + // detect swizzles of swizzles, e.g. replace foo.argb.r000 with foo.a000 + if (s.fBase->fKind == Expression::kSwizzle_Kind) { + Swizzle& base = (Swizzle&) *s.fBase; + std::vector final; + for (int c : s.fComponents) { + if (c == SKSL_SWIZZLE_0 || c == SKSL_SWIZZLE_1) { + final.push_back(c); + } else { + final.push_back(base.fComponents[c]); + } + } + *outUpdated = true; + std::unique_ptr replacement(new Swizzle(*fContext, base.fBase->clone(), + std::move(final))); + if (!try_replace_expression(&b, iter, &replacement)) { + *outNeedsRescan = true; + return; + } + SkASSERT((*iter)->fKind == BasicBlock::Node::kExpression_Kind); + break; + } + } + default: + break; + } +} + +// returns true if this statement could potentially execute a break at the current level (we ignore +// nested loops and switches, since any breaks inside of them will merely break the loop / switch) +static bool contains_conditional_break(Statement& s, bool inConditional) { + switch (s.fKind) { + case Statement::kBlock_Kind: + for (const auto& sub : ((Block&) s).fStatements) { + if (contains_conditional_break(*sub, inConditional)) { + return true; + } + } + return false; + case Statement::kBreak_Kind: + return inConditional; + case Statement::kIf_Kind: { + const IfStatement& i = (IfStatement&) s; + return contains_conditional_break(*i.fIfTrue, true) || + (i.fIfFalse && contains_conditional_break(*i.fIfFalse, true)); + } + default: + return false; + } +} + +// returns true if this statement definitely executes a break at the current level (we ignore +// nested loops and switches, since any breaks inside of them will merely break the loop / switch) +static bool contains_unconditional_break(Statement& s) { + switch (s.fKind) { + case Statement::kBlock_Kind: + for (const auto& sub : ((Block&) s).fStatements) { + if (contains_unconditional_break(*sub)) { + return true; + } + } + return false; + case Statement::kBreak_Kind: + return true; + default: + return false; + } +} + +// Returns a block containing all of the statements that will be run if the given case matches +// (which, owing to the statements being owned by unique_ptrs, means the switch itself will be +// broken by this call and must then be discarded). +// Returns null (and leaves the switch unmodified) if no such simple reduction is possible, such as +// when break statements appear inside conditionals. +static std::unique_ptr block_for_case(SwitchStatement* s, SwitchCase* c) { + bool capturing = false; + std::vector*> statementPtrs; + for (const auto& current : s->fCases) { + if (current.get() == c) { + capturing = true; + } + if (capturing) { + for (auto& stmt : current->fStatements) { + if (contains_conditional_break(*stmt, s->fKind == Statement::kIf_Kind)) { + return nullptr; + } + if (contains_unconditional_break(*stmt)) { + capturing = false; + break; + } + statementPtrs.push_back(&stmt); + } + if (!capturing) { + break; + } + } + } + std::vector> statements; + for (const auto& s : statementPtrs) { + statements.push_back(std::move(*s)); + } + return std::unique_ptr(new Block(-1, std::move(statements), s->fSymbols)); +} + +void Compiler::simplifyStatement(DefinitionMap& definitions, + BasicBlock& b, + std::vector::iterator* iter, + std::unordered_set* undefinedVariables, + bool* outUpdated, + bool* outNeedsRescan) { + Statement* stmt = (*iter)->statement()->get(); + switch (stmt->fKind) { + case Statement::kVarDeclaration_Kind: { + const auto& varDecl = (VarDeclaration&) *stmt; + if (varDecl.fVar->dead() && + (!varDecl.fValue || + !varDecl.fValue->hasSideEffects())) { + if (varDecl.fValue) { + SkASSERT((*iter)->statement()->get() == stmt); + if (!b.tryRemoveExpressionBefore(iter, varDecl.fValue.get())) { + *outNeedsRescan = true; + } + } + (*iter)->setStatement(std::unique_ptr(new Nop())); + *outUpdated = true; + } + break; + } + case Statement::kIf_Kind: { + IfStatement& i = (IfStatement&) *stmt; + if (i.fTest->fKind == Expression::kBoolLiteral_Kind) { + // constant if, collapse down to a single branch + if (((BoolLiteral&) *i.fTest).fValue) { + SkASSERT(i.fIfTrue); + (*iter)->setStatement(std::move(i.fIfTrue)); + } else { + if (i.fIfFalse) { + (*iter)->setStatement(std::move(i.fIfFalse)); + } else { + (*iter)->setStatement(std::unique_ptr(new Nop())); + } + } + *outUpdated = true; + *outNeedsRescan = true; + break; + } + if (i.fIfFalse && i.fIfFalse->isEmpty()) { + // else block doesn't do anything, remove it + i.fIfFalse.reset(); + *outUpdated = true; + *outNeedsRescan = true; + } + if (!i.fIfFalse && i.fIfTrue->isEmpty()) { + // if block doesn't do anything, no else block + if (i.fTest->hasSideEffects()) { + // test has side effects, keep it + (*iter)->setStatement(std::unique_ptr( + new ExpressionStatement(std::move(i.fTest)))); + } else { + // no if, no else, no test side effects, kill the whole if + // statement + (*iter)->setStatement(std::unique_ptr(new Nop())); + } + *outUpdated = true; + *outNeedsRescan = true; + } + break; + } + case Statement::kSwitch_Kind: { + SwitchStatement& s = (SwitchStatement&) *stmt; + if (s.fValue->isConstant()) { + // switch is constant, replace it with the case that matches + bool found = false; + SwitchCase* defaultCase = nullptr; + for (const auto& c : s.fCases) { + if (!c->fValue) { + defaultCase = c.get(); + continue; + } + SkASSERT(c->fValue->fKind == s.fValue->fKind); + found = c->fValue->compareConstant(*fContext, *s.fValue); + if (found) { + std::unique_ptr newBlock = block_for_case(&s, c.get()); + if (newBlock) { + (*iter)->setStatement(std::move(newBlock)); + break; + } else { + if (s.fIsStatic && !(fFlags & kPermitInvalidStaticTests_Flag)) { + this->error(s.fOffset, + "static switch contains non-static conditional break"); + s.fIsStatic = false; + } + return; // can't simplify + } + } + } + if (!found) { + // no matching case. use default if it exists, or kill the whole thing + if (defaultCase) { + std::unique_ptr newBlock = block_for_case(&s, defaultCase); + if (newBlock) { + (*iter)->setStatement(std::move(newBlock)); + } else { + if (s.fIsStatic && !(fFlags & kPermitInvalidStaticTests_Flag)) { + this->error(s.fOffset, + "static switch contains non-static conditional break"); + s.fIsStatic = false; + } + return; // can't simplify + } + } else { + (*iter)->setStatement(std::unique_ptr(new Nop())); + } + } + *outUpdated = true; + *outNeedsRescan = true; + } + break; + } + case Statement::kExpression_Kind: { + ExpressionStatement& e = (ExpressionStatement&) *stmt; + SkASSERT((*iter)->statement()->get() == &e); + if (!e.fExpression->hasSideEffects()) { + // Expression statement with no side effects, kill it + if (!b.tryRemoveExpressionBefore(iter, e.fExpression.get())) { + *outNeedsRescan = true; + } + SkASSERT((*iter)->statement()->get() == stmt); + (*iter)->setStatement(std::unique_ptr(new Nop())); + *outUpdated = true; + } + break; + } + default: + break; + } +} + +void Compiler::scanCFG(FunctionDefinition& f) { + CFG cfg = CFGGenerator().getCFG(f); + this->computeDataFlow(&cfg); + + // check for unreachable code + for (size_t i = 0; i < cfg.fBlocks.size(); i++) { + if (i != cfg.fStart && !cfg.fBlocks[i].fEntrances.size() && + cfg.fBlocks[i].fNodes.size()) { + int offset; + switch (cfg.fBlocks[i].fNodes[0].fKind) { + case BasicBlock::Node::kStatement_Kind: + offset = (*cfg.fBlocks[i].fNodes[0].statement())->fOffset; + break; + case BasicBlock::Node::kExpression_Kind: + offset = (*cfg.fBlocks[i].fNodes[0].expression())->fOffset; + break; + } + this->error(offset, String("unreachable")); + } + } + if (fErrorCount) { + return; + } + + // check for dead code & undefined variables, perform constant propagation + std::unordered_set undefinedVariables; + bool updated; + bool needsRescan = false; + do { + if (needsRescan) { + cfg = CFGGenerator().getCFG(f); + this->computeDataFlow(&cfg); + needsRescan = false; + } + + updated = false; + for (BasicBlock& b : cfg.fBlocks) { + DefinitionMap definitions = b.fBefore; + + for (auto iter = b.fNodes.begin(); iter != b.fNodes.end() && !needsRescan; ++iter) { + if (iter->fKind == BasicBlock::Node::kExpression_Kind) { + this->simplifyExpression(definitions, b, &iter, &undefinedVariables, &updated, + &needsRescan); + } else { + this->simplifyStatement(definitions, b, &iter, &undefinedVariables, &updated, + &needsRescan); + } + if (needsRescan) { + break; + } + this->addDefinitions(*iter, &definitions); + } + } + } while (updated); + SkASSERT(!needsRescan); + + // verify static ifs & switches, clean up dead variable decls + for (BasicBlock& b : cfg.fBlocks) { + DefinitionMap definitions = b.fBefore; + + for (auto iter = b.fNodes.begin(); iter != b.fNodes.end() && !needsRescan;) { + if (iter->fKind == BasicBlock::Node::kStatement_Kind) { + const Statement& s = **iter->statement(); + switch (s.fKind) { + case Statement::kIf_Kind: + if (((const IfStatement&) s).fIsStatic && + !(fFlags & kPermitInvalidStaticTests_Flag)) { + this->error(s.fOffset, "static if has non-static test"); + } + ++iter; + break; + case Statement::kSwitch_Kind: + if (((const SwitchStatement&) s).fIsStatic && + !(fFlags & kPermitInvalidStaticTests_Flag)) { + this->error(s.fOffset, "static switch has non-static test"); + } + ++iter; + break; + case Statement::kVarDeclarations_Kind: { + VarDeclarations& decls = *((VarDeclarationsStatement&) s).fDeclaration; + for (auto varIter = decls.fVars.begin(); varIter != decls.fVars.end();) { + if ((*varIter)->fKind == Statement::kNop_Kind) { + varIter = decls.fVars.erase(varIter); + } else { + ++varIter; + } + } + if (!decls.fVars.size()) { + iter = b.fNodes.erase(iter); + } else { + ++iter; + } + break; + } + default: + ++iter; + break; + } + } else { + ++iter; + } + } + } + + // check for missing return + if (f.fDeclaration.fReturnType != *fContext->fVoid_Type) { + if (cfg.fBlocks[cfg.fExit].fEntrances.size()) { + this->error(f.fOffset, String("function can exit without returning a value")); + } + } +} + +void Compiler::registerExternalValue(ExternalValue* value) { + fIRGenerator->fRootSymbolTable->addWithoutOwnership(value->fName, value); +} + +Symbol* Compiler::takeOwnership(std::unique_ptr symbol) { + return fIRGenerator->fRootSymbolTable->takeOwnership(std::move(symbol)); +} + +std::unique_ptr Compiler::convertProgram(Program::Kind kind, String text, + const Program::Settings& settings) { + fErrorText = ""; + fErrorCount = 0; + std::vector>* inherited; + std::vector> elements; + switch (kind) { + case Program::kVertex_Kind: + inherited = &fVertexInclude; + fIRGenerator->fSymbolTable = fVertexSymbolTable; + fIRGenerator->start(&settings, inherited); + break; + case Program::kFragment_Kind: + inherited = &fFragmentInclude; + fIRGenerator->fSymbolTable = fFragmentSymbolTable; + fIRGenerator->start(&settings, inherited); + break; + case Program::kGeometry_Kind: + inherited = &fGeometryInclude; + fIRGenerator->fSymbolTable = fGeometrySymbolTable; + fIRGenerator->start(&settings, inherited); + break; + case Program::kFragmentProcessor_Kind: + inherited = nullptr; + fIRGenerator->fSymbolTable = fGpuSymbolTable; + fIRGenerator->start(&settings, nullptr); + fIRGenerator->convertProgram(kind, SKSL_FP_INCLUDE, strlen(SKSL_FP_INCLUDE), *fTypes, + &elements); + fIRGenerator->fSymbolTable->markAllFunctionsBuiltin(); + break; + case Program::kPipelineStage_Kind: + inherited = &fPipelineInclude; + fIRGenerator->fSymbolTable = fPipelineSymbolTable; + fIRGenerator->start(&settings, inherited); + break; + case Program::kGeneric_Kind: + inherited = &fInterpreterInclude; + fIRGenerator->fSymbolTable = fInterpreterSymbolTable; + fIRGenerator->start(&settings, inherited); + break; + } + for (auto& element : elements) { + if (element->fKind == ProgramElement::kEnum_Kind) { + ((Enum&) *element).fBuiltin = true; + } + } + std::unique_ptr textPtr(new String(std::move(text))); + fSource = textPtr.get(); + fIRGenerator->convertProgram(kind, textPtr->c_str(), textPtr->size(), *fTypes, &elements); + auto result = std::unique_ptr(new Program(kind, + std::move(textPtr), + settings, + fContext, + inherited, + std::move(elements), + fIRGenerator->fSymbolTable, + fIRGenerator->fInputs)); + if (fErrorCount) { + return nullptr; + } + return result; +} + +bool Compiler::optimize(Program& program) { + SkASSERT(!fErrorCount); + if (!program.fIsOptimized) { + program.fIsOptimized = true; + fIRGenerator->fKind = program.fKind; + fIRGenerator->fSettings = &program.fSettings; + for (auto& element : program) { + if (element.fKind == ProgramElement::kFunction_Kind) { + this->scanCFG((FunctionDefinition&) element); + } + } + if (program.fKind != Program::kFragmentProcessor_Kind) { + for (auto iter = program.fElements.begin(); iter != program.fElements.end();) { + if ((*iter)->fKind == ProgramElement::kVar_Kind) { + VarDeclarations& vars = (VarDeclarations&) **iter; + for (auto varIter = vars.fVars.begin(); varIter != vars.fVars.end();) { + const Variable& var = *((VarDeclaration&) **varIter).fVar; + if (var.dead()) { + varIter = vars.fVars.erase(varIter); + } else { + ++varIter; + } + } + if (vars.fVars.size() == 0) { + iter = program.fElements.erase(iter); + continue; + } + } + ++iter; + } + } + } + return fErrorCount == 0; +} + +std::unique_ptr Compiler::specialize( + Program& program, + const std::unordered_map& inputs) { + std::vector> elements; + for (const auto& e : program) { + elements.push_back(e.clone()); + } + Program::Settings settings; + settings.fCaps = program.fSettings.fCaps; + for (auto iter = inputs.begin(); iter != inputs.end(); ++iter) { + settings.fArgs.insert(*iter); + } + std::unique_ptr result(new Program(program.fKind, + nullptr, + settings, + program.fContext, + program.fInheritedElements, + std::move(elements), + program.fSymbols, + program.fInputs)); + return result; +} + +#if defined(SKSL_STANDALONE) || SK_SUPPORT_GPU + +bool Compiler::toSPIRV(Program& program, OutputStream& out) { + if (!this->optimize(program)) { + return false; + } +#ifdef SK_ENABLE_SPIRV_VALIDATION + StringStream buffer; + fSource = program.fSource.get(); + SPIRVCodeGenerator cg(fContext.get(), &program, this, &buffer); + bool result = cg.generateCode(); + fSource = nullptr; + if (result) { + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0); + const String& data = buffer.str(); + SkASSERT(0 == data.size() % 4); + auto dumpmsg = [](spv_message_level_t, const char*, const spv_position_t&, const char* m) { + SkDebugf("SPIR-V validation error: %s\n", m); + }; + tools.SetMessageConsumer(dumpmsg); + // Verify that the SPIR-V we produced is valid. If this SkASSERT fails, check the logs prior + // to the failure to see the validation errors. + SkAssertResult(tools.Validate((const uint32_t*) data.c_str(), data.size() / 4)); + out.write(data.c_str(), data.size()); + } +#else + fSource = program.fSource.get(); + SPIRVCodeGenerator cg(fContext.get(), &program, this, &out); + bool result = cg.generateCode(); + fSource = nullptr; +#endif + return result; +} + +bool Compiler::toSPIRV(Program& program, String* out) { + StringStream buffer; + bool result = this->toSPIRV(program, buffer); + if (result) { + *out = buffer.str(); + } + return result; +} + +bool Compiler::toGLSL(Program& program, OutputStream& out) { + if (!this->optimize(program)) { + return false; + } + fSource = program.fSource.get(); + GLSLCodeGenerator cg(fContext.get(), &program, this, &out); + bool result = cg.generateCode(); + fSource = nullptr; + return result; +} + +bool Compiler::toGLSL(Program& program, String* out) { + StringStream buffer; + bool result = this->toGLSL(program, buffer); + if (result) { + *out = buffer.str(); + } + return result; +} + +bool Compiler::toMetal(Program& program, OutputStream& out) { + if (!this->optimize(program)) { + return false; + } + MetalCodeGenerator cg(fContext.get(), &program, this, &out); + bool result = cg.generateCode(); + return result; +} + +bool Compiler::toMetal(Program& program, String* out) { + if (!this->optimize(program)) { + return false; + } + StringStream buffer; + bool result = this->toMetal(program, buffer); + if (result) { + *out = buffer.str(); + } + return result; +} + +bool Compiler::toCPP(Program& program, String name, OutputStream& out) { + if (!this->optimize(program)) { + return false; + } + fSource = program.fSource.get(); + CPPCodeGenerator cg(fContext.get(), &program, this, name, &out); + bool result = cg.generateCode(); + fSource = nullptr; + return result; +} + +bool Compiler::toH(Program& program, String name, OutputStream& out) { + if (!this->optimize(program)) { + return false; + } + fSource = program.fSource.get(); + HCodeGenerator cg(fContext.get(), &program, this, name, &out); + bool result = cg.generateCode(); + fSource = nullptr; + return result; +} + +#endif + +#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU +bool Compiler::toPipelineStage(const Program& program, String* out, + std::vector* outFormatArgs, + std::vector* outFunctions) { + SkASSERT(program.fIsOptimized); + fSource = program.fSource.get(); + StringStream buffer; + PipelineStageCodeGenerator cg(fContext.get(), &program, this, &buffer, outFormatArgs, + outFunctions); + bool result = cg.generateCode(); + fSource = nullptr; + if (result) { + *out = buffer.str(); + } + return result; +} +#endif + +std::unique_ptr Compiler::toByteCode(Program& program) { +#if defined(SK_ENABLE_SKSL_INTERPRETER) + if (!this->optimize(program)) { + return nullptr; + } + std::unique_ptr result(new ByteCode()); + ByteCodeGenerator cg(fContext.get(), &program, this, result.get()); + if (cg.generateCode()) { + return result; + } +#else + ABORT("ByteCode interpreter not enabled"); +#endif + return nullptr; +} + +const char* Compiler::OperatorName(Token::Kind kind) { + switch (kind) { + case Token::PLUS: return "+"; + case Token::MINUS: return "-"; + case Token::STAR: return "*"; + case Token::SLASH: return "/"; + case Token::PERCENT: return "%"; + case Token::SHL: return "<<"; + case Token::SHR: return ">>"; + case Token::LOGICALNOT: return "!"; + case Token::LOGICALAND: return "&&"; + case Token::LOGICALOR: return "||"; + case Token::LOGICALXOR: return "^^"; + case Token::BITWISENOT: return "~"; + case Token::BITWISEAND: return "&"; + case Token::BITWISEOR: return "|"; + case Token::BITWISEXOR: return "^"; + case Token::EQ: return "="; + case Token::EQEQ: return "=="; + case Token::NEQ: return "!="; + case Token::LT: return "<"; + case Token::GT: return ">"; + case Token::LTEQ: return "<="; + case Token::GTEQ: return ">="; + case Token::PLUSEQ: return "+="; + case Token::MINUSEQ: return "-="; + case Token::STAREQ: return "*="; + case Token::SLASHEQ: return "/="; + case Token::PERCENTEQ: return "%="; + case Token::SHLEQ: return "<<="; + case Token::SHREQ: return ">>="; + case Token::LOGICALANDEQ: return "&&="; + case Token::LOGICALOREQ: return "||="; + case Token::LOGICALXOREQ: return "^^="; + case Token::BITWISEANDEQ: return "&="; + case Token::BITWISEOREQ: return "|="; + case Token::BITWISEXOREQ: return "^="; + case Token::PLUSPLUS: return "++"; + case Token::MINUSMINUS: return "--"; + case Token::COMMA: return ","; + default: + ABORT("unsupported operator: %d\n", kind); + } +} + + +bool Compiler::IsAssignment(Token::Kind op) { + switch (op) { + case Token::EQ: // fall through + case Token::PLUSEQ: // fall through + case Token::MINUSEQ: // fall through + case Token::STAREQ: // fall through + case Token::SLASHEQ: // fall through + case Token::PERCENTEQ: // fall through + case Token::SHLEQ: // fall through + case Token::SHREQ: // fall through + case Token::BITWISEOREQ: // fall through + case Token::BITWISEXOREQ: // fall through + case Token::BITWISEANDEQ: // fall through + case Token::LOGICALOREQ: // fall through + case Token::LOGICALXOREQ: // fall through + case Token::LOGICALANDEQ: + return true; + default: + return false; + } +} + +Position Compiler::position(int offset) { + SkASSERT(fSource); + int line = 1; + int column = 1; + for (int i = 0; i < offset; i++) { + if ((*fSource)[i] == '\n') { + ++line; + column = 1; + } + else { + ++column; + } + } + return Position(line, column); +} + +void Compiler::error(int offset, String msg) { + fErrorCount++; + Position pos = this->position(offset); + fErrorText += "error: " + to_string(pos.fLine) + ": " + msg.c_str() + "\n"; +} + +String Compiler::errorText() { + this->writeErrorCount(); + fErrorCount = 0; + String result = fErrorText; + return result; +} + +void Compiler::writeErrorCount() { + if (fErrorCount) { + fErrorText += to_string(fErrorCount) + " error"; + if (fErrorCount > 1) { + fErrorText += "s"; + } + fErrorText += "\n"; + } +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLCompiler.h b/gfx/skia/skia/src/sksl/SkSLCompiler.h new file mode 100644 index 0000000000..7ef368d58b --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCompiler.h @@ -0,0 +1,239 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_COMPILER +#define SKSL_COMPILER + +#include +#include +#include +#include "src/sksl/SkSLCFGGenerator.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU +#include "src/gpu/GrShaderVar.h" +#endif + +#define SK_FRAGCOLOR_BUILTIN 10001 +#define SK_IN_BUILTIN 10002 +#define SK_INCOLOR_BUILTIN 10003 +#define SK_OUTCOLOR_BUILTIN 10004 +#define SK_TRANSFORMEDCOORDS2D_BUILTIN 10005 +#define SK_TEXTURESAMPLERS_BUILTIN 10006 +#define SK_OUT_BUILTIN 10007 +#define SK_LASTFRAGCOLOR_BUILTIN 10008 +#define SK_MAIN_X_BUILTIN 10009 +#define SK_MAIN_Y_BUILTIN 10010 +#define SK_WIDTH_BUILTIN 10011 +#define SK_HEIGHT_BUILTIN 10012 +#define SK_FRAGCOORD_BUILTIN 15 +#define SK_CLOCKWISE_BUILTIN 17 +#define SK_VERTEXID_BUILTIN 42 +#define SK_INSTANCEID_BUILTIN 43 +#define SK_CLIPDISTANCE_BUILTIN 3 +#define SK_INVOCATIONID_BUILTIN 8 +#define SK_POSITION_BUILTIN 0 + +namespace SkSL { + +class ByteCode; +class ExternalValue; +class IRGenerator; + +/** + * Main compiler entry point. This is a traditional compiler design which first parses the .sksl + * file into an abstract syntax tree (a tree of ASTNodes), then performs semantic analysis to + * produce a Program (a tree of IRNodes), then feeds the Program into a CodeGenerator to produce + * compiled output. + * + * See the README for information about SkSL. + */ +class SK_API Compiler : public ErrorReporter { +public: + static constexpr const char* RTADJUST_NAME = "sk_RTAdjust"; + static constexpr const char* PERVERTEX_NAME = "sk_PerVertex"; + + enum Flags { + kNone_Flags = 0, + // permits static if/switch statements to be used with non-constant tests. This is used when + // producing H and CPP code; the static tests don't have to have constant values *yet*, but + // the generated code will contain a static test which then does have to be a constant. + kPermitInvalidStaticTests_Flag = 1, + }; + + struct FormatArg { + enum class Kind { + kInput, + kOutput, + kCoordX, + kCoordY, + kUniform, + kChildProcessor, + kFunctionName + }; + + FormatArg(Kind kind) + : fKind(kind) {} + + FormatArg(Kind kind, int index) + : fKind(kind) + , fIndex(index) {} + + Kind fKind; + + int fIndex; + }; + +#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU + /** + * Represents the arguments to GrGLSLShaderBuilder::emitFunction. + */ + struct GLSLFunction { + GrSLType fReturnType; + SkString fName; + std::vector fParameters; + SkString fBody; + }; +#endif + + Compiler(Flags flags = kNone_Flags); + + ~Compiler() override; + + Compiler(const Compiler&) = delete; + Compiler& operator=(const Compiler&) = delete; + + /** + * Registers an ExternalValue as a top-level symbol which is visible in the global namespace. + */ + void registerExternalValue(ExternalValue* value); + + std::unique_ptr convertProgram(Program::Kind kind, String text, + const Program::Settings& settings); + + bool optimize(Program& program); + + std::unique_ptr specialize(Program& program, + const std::unordered_map& inputs); + + bool toSPIRV(Program& program, OutputStream& out); + + bool toSPIRV(Program& program, String* out); + + bool toGLSL(Program& program, OutputStream& out); + + bool toGLSL(Program& program, String* out); + + bool toMetal(Program& program, OutputStream& out); + + bool toMetal(Program& program, String* out); + + bool toCPP(Program& program, String name, OutputStream& out); + + bool toH(Program& program, String name, OutputStream& out); + + std::unique_ptr toByteCode(Program& program); + +#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU + bool toPipelineStage(const Program& program, String* out, + std::vector* outFormatArgs, + std::vector* outFunctions); +#endif + + /** + * Takes ownership of the given symbol. It will be destroyed when the compiler is destroyed. + */ + Symbol* takeOwnership(std::unique_ptr symbol); + + void error(int offset, String msg) override; + + String errorText(); + + void writeErrorCount(); + + int errorCount() override { + return fErrorCount; + } + + Context& context() { + return *fContext; + } + + static const char* OperatorName(Token::Kind token); + + static bool IsAssignment(Token::Kind token); + +private: + void processIncludeFile(Program::Kind kind, const char* src, size_t length, + std::shared_ptr base, + std::vector>* outElements, + std::shared_ptr* outSymbolTable); + + void addDefinition(const Expression* lvalue, std::unique_ptr* expr, + DefinitionMap* definitions); + + void addDefinitions(const BasicBlock::Node& node, DefinitionMap* definitions); + + void scanCFG(CFG* cfg, BlockId block, std::set* workList); + + void computeDataFlow(CFG* cfg); + + /** + * Simplifies the expression pointed to by iter (in both the IR and CFG structures), if + * possible. + */ + void simplifyExpression(DefinitionMap& definitions, + BasicBlock& b, + std::vector::iterator* iter, + std::unordered_set* undefinedVariables, + bool* outUpdated, + bool* outNeedsRescan); + + /** + * Simplifies the statement pointed to by iter (in both the IR and CFG structures), if + * possible. + */ + void simplifyStatement(DefinitionMap& definitions, + BasicBlock& b, + std::vector::iterator* iter, + std::unordered_set* undefinedVariables, + bool* outUpdated, + bool* outNeedsRescan); + + void scanCFG(FunctionDefinition& f); + + Position position(int offset); + + std::shared_ptr fGpuSymbolTable; + std::vector> fVertexInclude; + std::shared_ptr fVertexSymbolTable; + std::vector> fFragmentInclude; + std::shared_ptr fFragmentSymbolTable; + std::vector> fGeometryInclude; + std::shared_ptr fGeometrySymbolTable; + std::vector> fPipelineInclude; + std::shared_ptr fPipelineSymbolTable; + std::vector> fInterpreterInclude; + std::shared_ptr fInterpreterSymbolTable; + + std::shared_ptr fTypes; + IRGenerator* fIRGenerator; + int fFlags; + + const String* fSource; + std::shared_ptr fContext; + int fErrorCount; + String fErrorText; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLContext.h b/gfx/skia/skia/src/sksl/SkSLContext.h new file mode 100644 index 0000000000..acfafc587d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLContext.h @@ -0,0 +1,429 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONTEXT +#define SKSL_CONTEXT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +/** + * Contains compiler-wide objects, which currently means the core types. + */ +class Context { +public: + Context() + : fInvalid_Type(new Type("")) + , fVoid_Type(new Type("void")) + , fNull_Type(new Type("null")) + , fFloatLiteral_Type(new Type("$floatLiteral", Type::kFloat_NumberKind, 3)) + , fIntLiteral_Type(new Type("$intLiteral", Type::kSigned_NumberKind, 1)) + , fDouble_Type(new Type("double", Type::kFloat_NumberKind, 6, true)) + , fDouble2_Type(new Type("double2", *fDouble_Type, 2)) + , fDouble3_Type(new Type("double3", *fDouble_Type, 3)) + , fDouble4_Type(new Type("double4", *fDouble_Type, 4)) + , fFloat_Type(new Type("float", Type::kFloat_NumberKind, 5, true)) + , fFloat2_Type(new Type("float2", *fFloat_Type, 2)) + , fFloat3_Type(new Type("float3", *fFloat_Type, 3)) + , fFloat4_Type(new Type("float4", *fFloat_Type, 4)) + , fHalf_Type(new Type("half", Type::kFloat_NumberKind, 4)) + , fHalf2_Type(new Type("half2", *fHalf_Type, 2)) + , fHalf3_Type(new Type("half3", *fHalf_Type, 3)) + , fHalf4_Type(new Type("half4", *fHalf_Type, 4)) + , fUInt_Type(new Type("uint", Type::kUnsigned_NumberKind, 2, true)) + , fUInt2_Type(new Type("uint2", *fUInt_Type, 2)) + , fUInt3_Type(new Type("uint3", *fUInt_Type, 3)) + , fUInt4_Type(new Type("uint4", *fUInt_Type, 4)) + , fInt_Type(new Type("int", Type::kSigned_NumberKind, 2, true)) + , fInt2_Type(new Type("int2", *fInt_Type, 2)) + , fInt3_Type(new Type("int3", *fInt_Type, 3)) + , fInt4_Type(new Type("int4", *fInt_Type, 4)) + , fUShort_Type(new Type("ushort", Type::kUnsigned_NumberKind, 0)) + , fUShort2_Type(new Type("ushort2", *fUShort_Type, 2)) + , fUShort3_Type(new Type("ushort3", *fUShort_Type, 3)) + , fUShort4_Type(new Type("ushort4", *fUShort_Type, 4)) + , fShort_Type(new Type("short", Type::kSigned_NumberKind, 0)) + , fShort2_Type(new Type("short2", *fShort_Type, 2)) + , fShort3_Type(new Type("short3", *fShort_Type, 3)) + , fShort4_Type(new Type("short4", *fShort_Type, 4)) + , fUByte_Type(new Type("ubyte", Type::kUnsigned_NumberKind, 0)) + , fUByte2_Type(new Type("ubyte2", *fUByte_Type, 2)) + , fUByte3_Type(new Type("ubyte3", *fUByte_Type, 3)) + , fUByte4_Type(new Type("ubyte4", *fUByte_Type, 4)) + , fByte_Type(new Type("byte", Type::kSigned_NumberKind, 0)) + , fByte2_Type(new Type("byte2", *fByte_Type, 2)) + , fByte3_Type(new Type("byte3", *fByte_Type, 3)) + , fByte4_Type(new Type("byte4", *fByte_Type, 4)) + , fBool_Type(new Type("bool", Type::kNonnumeric_NumberKind, -1)) + , fBool2_Type(new Type("bool2", *fBool_Type, 2)) + , fBool3_Type(new Type("bool3", *fBool_Type, 3)) + , fBool4_Type(new Type("bool4", *fBool_Type, 4)) + , fFloat2x2_Type(new Type("float2x2", *fFloat_Type, 2, 2)) + , fFloat2x3_Type(new Type("float2x3", *fFloat_Type, 2, 3)) + , fFloat2x4_Type(new Type("float2x4", *fFloat_Type, 2, 4)) + , fFloat3x2_Type(new Type("float3x2", *fFloat_Type, 3, 2)) + , fFloat3x3_Type(new Type("float3x3", *fFloat_Type, 3, 3)) + , fFloat3x4_Type(new Type("float3x4", *fFloat_Type, 3, 4)) + , fFloat4x2_Type(new Type("float4x2", *fFloat_Type, 4, 2)) + , fFloat4x3_Type(new Type("float4x3", *fFloat_Type, 4, 3)) + , fFloat4x4_Type(new Type("float4x4", *fFloat_Type, 4, 4)) + , fHalf2x2_Type(new Type("half2x2", *fHalf_Type, 2, 2)) + , fHalf2x3_Type(new Type("half2x3", *fHalf_Type, 2, 3)) + , fHalf2x4_Type(new Type("half2x4", *fHalf_Type, 2, 4)) + , fHalf3x2_Type(new Type("half3x2", *fHalf_Type, 3, 2)) + , fHalf3x3_Type(new Type("half3x3", *fHalf_Type, 3, 3)) + , fHalf3x4_Type(new Type("half3x4", *fHalf_Type, 3, 4)) + , fHalf4x2_Type(new Type("half4x2", *fHalf_Type, 4, 2)) + , fHalf4x3_Type(new Type("half4x3", *fHalf_Type, 4, 3)) + , fHalf4x4_Type(new Type("half4x4", *fHalf_Type, 4, 4)) + , fDouble2x2_Type(new Type("double2x2", *fDouble_Type, 2, 2)) + , fDouble2x3_Type(new Type("double2x3", *fDouble_Type, 2, 3)) + , fDouble2x4_Type(new Type("double2x4", *fDouble_Type, 2, 4)) + , fDouble3x2_Type(new Type("double3x2", *fDouble_Type, 3, 2)) + , fDouble3x3_Type(new Type("double3x3", *fDouble_Type, 3, 3)) + , fDouble3x4_Type(new Type("double3x4", *fDouble_Type, 3, 4)) + , fDouble4x2_Type(new Type("double4x2", *fDouble_Type, 4, 2)) + , fDouble4x3_Type(new Type("double4x3", *fDouble_Type, 4, 3)) + , fDouble4x4_Type(new Type("double4x4", *fDouble_Type, 4, 4)) + , fTexture1D_Type(new Type("texture1D", SpvDim1D, false, false, false, true)) + , fTexture2D_Type(new Type("texture2D", SpvDim2D, false, false, false, true)) + , fTexture3D_Type(new Type("texture3D", SpvDim3D, false, false, false, true)) + , fTextureExternalOES_Type(new Type("textureExternalOES", SpvDim2D, false, false, false, true)) + , fTextureCube_Type(new Type("textureCube", SpvDimCube, false, false, false, true)) + , fTexture2DRect_Type(new Type("texture2DRect", SpvDimRect, false, false, false, true)) + , fTextureBuffer_Type(new Type("textureBuffer", SpvDimBuffer, false, false, false, true)) + , fITexture2D_Type(new Type("itexture2D", SpvDim2D, false, false, false, true)) + , fSampler1D_Type(new Type("sampler1D", *fTexture1D_Type)) + , fSampler2D_Type(new Type("sampler2D", *fTexture2D_Type)) + , fSampler3D_Type(new Type("sampler3D", *fTexture3D_Type)) + , fSamplerExternalOES_Type(new Type("samplerExternalOES", *fTextureExternalOES_Type)) + , fSamplerCube_Type(new Type("samplerCube", *fTextureCube_Type)) + , fSampler2DRect_Type(new Type("sampler2DRect", *fTexture2DRect_Type)) + , fSampler1DArray_Type(new Type("sampler1DArray")) + , fSampler2DArray_Type(new Type("sampler2DArray")) + , fSamplerCubeArray_Type(new Type("samplerCubeArray")) + , fSamplerBuffer_Type(new Type("samplerBuffer", *fTextureBuffer_Type)) + , fSampler2DMS_Type(new Type("sampler2DMS")) + , fSampler2DMSArray_Type(new Type("sampler2DMSArray")) + , fSampler1DShadow_Type(new Type("sampler1DShadow")) + , fSampler2DShadow_Type(new Type("sampler2DShadow")) + , fSamplerCubeShadow_Type(new Type("samplerCubeShadow")) + , fSampler2DRectShadow_Type(new Type("sampler2DRectShadow")) + , fSampler1DArrayShadow_Type(new Type("sampler1DArrayShadow")) + , fSampler2DArrayShadow_Type(new Type("sampler2DArrayShadow")) + , fSamplerCubeArrayShadow_Type(new Type("samplerCubeArrayShadow")) + + // Related to below FIXME, gsampler*s don't currently expand to cover integer case. + , fISampler2D_Type(new Type("isampler2D", *fITexture2D_Type)) + + , fSampler_Type(new Type("sampler", Type::kSeparateSampler_Kind)) + // FIXME express these as "gimage2D" that expand to image2D, iimage2D, and uimage2D. + , fImage2D_Type(new Type("image2D", SpvDim2D, false, false, false, true)) + , fIImage2D_Type(new Type("iimage2D", SpvDim2D, false, false, false, true)) + + // FIXME express these as "gsubpassInput" that expand to subpassInput, isubpassInput, + // and usubpassInput. + , fSubpassInput_Type(new Type("subpassInput", SpvDimSubpassData, false, false, + false, false)) + , fSubpassInputMS_Type(new Type("subpassInputMS", SpvDimSubpassData, false, false, + true, false)) + + // FIXME figure out what we're supposed to do with the gsampler et al. types) + , fGSampler1D_Type(new Type("$gsampler1D", static_type(*fSampler1D_Type))) + , fGSampler2D_Type(new Type("$gsampler2D", static_type(*fSampler2D_Type))) + , fGSampler3D_Type(new Type("$gsampler3D", static_type(*fSampler3D_Type))) + , fGSamplerCube_Type(new Type("$gsamplerCube", static_type(*fSamplerCube_Type))) + , fGSampler2DRect_Type(new Type("$gsampler2DRect", static_type(*fSampler2DRect_Type))) + , fGSampler1DArray_Type(new Type("$gsampler1DArray", + static_type(*fSampler1DArray_Type))) + , fGSampler2DArray_Type(new Type("$gsampler2DArray", + static_type(*fSampler2DArray_Type))) + , fGSamplerCubeArray_Type(new Type("$gsamplerCubeArray", + static_type(*fSamplerCubeArray_Type))) + , fGSamplerBuffer_Type(new Type("$gsamplerBuffer", static_type(*fSamplerBuffer_Type))) + , fGSampler2DMS_Type(new Type("$gsampler2DMS", static_type(*fSampler2DMS_Type))) + , fGSampler2DMSArray_Type(new Type("$gsampler2DMSArray", + static_type(*fSampler2DMSArray_Type))) + , fGSampler2DArrayShadow_Type(new Type("$gsampler2DArrayShadow", + static_type(*fSampler2DArrayShadow_Type))) + , fGSamplerCubeArrayShadow_Type(new Type("$gsamplerCubeArrayShadow", + static_type(*fSamplerCubeArrayShadow_Type))) + , fGenType_Type(new Type("$genType", { fFloat_Type.get(), fFloat2_Type.get(), + fFloat3_Type.get(), fFloat4_Type.get() })) + , fGenHType_Type(new Type("$genHType", { fHalf_Type.get(), fHalf2_Type.get(), + fHalf3_Type.get(), fHalf4_Type.get() })) + , fGenDType_Type(new Type("$genDType", { fDouble_Type.get(), fDouble2_Type.get(), + fDouble3_Type.get(), fDouble4_Type.get() })) + , fGenIType_Type(new Type("$genIType", { fInt_Type.get(), fInt2_Type.get(), + fInt3_Type.get(), fInt4_Type.get() })) + , fGenUType_Type(new Type("$genUType", { fUInt_Type.get(), fUInt2_Type.get(), + fUInt3_Type.get(), fUInt4_Type.get() })) + , fGenBType_Type(new Type("$genBType", { fBool_Type.get(), fBool2_Type.get(), + fBool3_Type.get(), fBool4_Type.get() })) + , fMat_Type(new Type("$mat", { fFloat2x2_Type.get(), fFloat2x3_Type.get(), + fFloat2x4_Type.get(), fFloat3x2_Type.get(), + fFloat3x3_Type.get(), fFloat3x4_Type.get(), + fFloat4x2_Type.get(), fFloat4x3_Type.get(), + fFloat4x4_Type.get(), fHalf2x2_Type.get(), + fHalf2x3_Type.get(), fHalf2x4_Type.get(), + fHalf3x2_Type.get(), fHalf3x3_Type.get(), + fHalf3x4_Type.get(), fHalf4x2_Type.get(), + fHalf4x3_Type.get(), fHalf4x4_Type.get(), + fDouble2x2_Type.get(), fDouble2x3_Type.get(), + fDouble2x4_Type.get(), fDouble3x2_Type.get(), + fDouble3x3_Type.get(), fDouble3x4_Type.get(), + fDouble4x2_Type.get(), fDouble4x3_Type.get(), + fDouble4x4_Type.get() })) + , fVec_Type(new Type("$vec", { fInvalid_Type.get(), fFloat2_Type.get(), + fFloat3_Type.get(), fFloat4_Type.get() })) + , fGVec_Type(new Type("$gvec")) + , fGVec2_Type(new Type("$gfloat2")) + , fGVec3_Type(new Type("$gfloat3")) + , fGVec4_Type(new Type("$gfloat4", static_type(*fFloat4_Type))) + , fHVec_Type(new Type("$hvec", { fInvalid_Type.get(), fHalf2_Type.get(), + fHalf3_Type.get(), fHalf4_Type.get() })) + , fDVec_Type(new Type("$dvec", { fInvalid_Type.get(), fDouble2_Type.get(), + fDouble3_Type.get(), fDouble4_Type.get() })) + , fIVec_Type(new Type("$ivec", { fInvalid_Type.get(), fInt2_Type.get(), + fInt3_Type.get(), fInt4_Type.get() })) + , fUVec_Type(new Type("$uvec", { fInvalid_Type.get(), fUInt2_Type.get(), + fUInt3_Type.get(), fUInt4_Type.get() })) + , fSVec_Type(new Type("$svec", { fInvalid_Type.get(), fShort2_Type.get(), + fShort3_Type.get(), fShort4_Type.get() })) + , fUSVec_Type(new Type("$usvec", { fInvalid_Type.get(), fUShort2_Type.get(), + fUShort3_Type.get(), fUShort4_Type.get() })) + , fByteVec_Type(new Type("$bytevec", { fInvalid_Type.get(), fByte2_Type.get(), + fByte3_Type.get(), fByte4_Type.get() })) + , fUByteVec_Type(new Type("$ubytevec", { fInvalid_Type.get(), fUByte2_Type.get(), + fUByte3_Type.get(), fUByte4_Type.get() })) + , fBVec_Type(new Type("$bvec", { fInvalid_Type.get(), fBool2_Type.get(), + fBool3_Type.get(), fBool4_Type.get() })) + , fSkCaps_Type(new Type("$sk_Caps")) + , fSkArgs_Type(new Type("$sk_Args")) + , fFragmentProcessor_Type(fp_type(fInt_Type.get(), fBool_Type.get())) + , fDefined_Expression(new Defined(*fInvalid_Type)) {} + + static std::vector static_type(const Type& t) { + return { &t, &t, &t, &t }; + } + + const std::unique_ptr fInvalid_Type; + const std::unique_ptr fVoid_Type; + const std::unique_ptr fNull_Type; + const std::unique_ptr fFloatLiteral_Type; + const std::unique_ptr fIntLiteral_Type; + + const std::unique_ptr fDouble_Type; + const std::unique_ptr fDouble2_Type; + const std::unique_ptr fDouble3_Type; + const std::unique_ptr fDouble4_Type; + + const std::unique_ptr fFloat_Type; + const std::unique_ptr fFloat2_Type; + const std::unique_ptr fFloat3_Type; + const std::unique_ptr fFloat4_Type; + + const std::unique_ptr fHalf_Type; + const std::unique_ptr fHalf2_Type; + const std::unique_ptr fHalf3_Type; + const std::unique_ptr fHalf4_Type; + + const std::unique_ptr fUInt_Type; + const std::unique_ptr fUInt2_Type; + const std::unique_ptr fUInt3_Type; + const std::unique_ptr fUInt4_Type; + + const std::unique_ptr fInt_Type; + const std::unique_ptr fInt2_Type; + const std::unique_ptr fInt3_Type; + const std::unique_ptr fInt4_Type; + + const std::unique_ptr fUShort_Type; + const std::unique_ptr fUShort2_Type; + const std::unique_ptr fUShort3_Type; + const std::unique_ptr fUShort4_Type; + + const std::unique_ptr fShort_Type; + const std::unique_ptr fShort2_Type; + const std::unique_ptr fShort3_Type; + const std::unique_ptr fShort4_Type; + + const std::unique_ptr fUByte_Type; + const std::unique_ptr fUByte2_Type; + const std::unique_ptr fUByte3_Type; + const std::unique_ptr fUByte4_Type; + + const std::unique_ptr fByte_Type; + const std::unique_ptr fByte2_Type; + const std::unique_ptr fByte3_Type; + const std::unique_ptr fByte4_Type; + + const std::unique_ptr fBool_Type; + const std::unique_ptr fBool2_Type; + const std::unique_ptr fBool3_Type; + const std::unique_ptr fBool4_Type; + + const std::unique_ptr fFloat2x2_Type; + const std::unique_ptr fFloat2x3_Type; + const std::unique_ptr fFloat2x4_Type; + const std::unique_ptr fFloat3x2_Type; + const std::unique_ptr fFloat3x3_Type; + const std::unique_ptr fFloat3x4_Type; + const std::unique_ptr fFloat4x2_Type; + const std::unique_ptr fFloat4x3_Type; + const std::unique_ptr fFloat4x4_Type; + + const std::unique_ptr fHalf2x2_Type; + const std::unique_ptr fHalf2x3_Type; + const std::unique_ptr fHalf2x4_Type; + const std::unique_ptr fHalf3x2_Type; + const std::unique_ptr fHalf3x3_Type; + const std::unique_ptr fHalf3x4_Type; + const std::unique_ptr fHalf4x2_Type; + const std::unique_ptr fHalf4x3_Type; + const std::unique_ptr fHalf4x4_Type; + + const std::unique_ptr fDouble2x2_Type; + const std::unique_ptr fDouble2x3_Type; + const std::unique_ptr fDouble2x4_Type; + const std::unique_ptr fDouble3x2_Type; + const std::unique_ptr fDouble3x3_Type; + const std::unique_ptr fDouble3x4_Type; + const std::unique_ptr fDouble4x2_Type; + const std::unique_ptr fDouble4x3_Type; + const std::unique_ptr fDouble4x4_Type; + + const std::unique_ptr fTexture1D_Type; + const std::unique_ptr fTexture2D_Type; + const std::unique_ptr fTexture3D_Type; + const std::unique_ptr fTextureExternalOES_Type; + const std::unique_ptr fTextureCube_Type; + const std::unique_ptr fTexture2DRect_Type; + const std::unique_ptr fTextureBuffer_Type; + const std::unique_ptr fITexture2D_Type; + + const std::unique_ptr fSampler1D_Type; + const std::unique_ptr fSampler2D_Type; + const std::unique_ptr fSampler3D_Type; + const std::unique_ptr fSamplerExternalOES_Type; + const std::unique_ptr fSamplerCube_Type; + const std::unique_ptr fSampler2DRect_Type; + const std::unique_ptr fSampler1DArray_Type; + const std::unique_ptr fSampler2DArray_Type; + const std::unique_ptr fSamplerCubeArray_Type; + const std::unique_ptr fSamplerBuffer_Type; + const std::unique_ptr fSampler2DMS_Type; + const std::unique_ptr fSampler2DMSArray_Type; + const std::unique_ptr fSampler1DShadow_Type; + const std::unique_ptr fSampler2DShadow_Type; + const std::unique_ptr fSamplerCubeShadow_Type; + const std::unique_ptr fSampler2DRectShadow_Type; + const std::unique_ptr fSampler1DArrayShadow_Type; + const std::unique_ptr fSampler2DArrayShadow_Type; + const std::unique_ptr fSamplerCubeArrayShadow_Type; + + const std::unique_ptr fISampler2D_Type; + const std::unique_ptr fSampler_Type; + + const std::unique_ptr fImage2D_Type; + const std::unique_ptr fIImage2D_Type; + + const std::unique_ptr fSubpassInput_Type; + const std::unique_ptr fSubpassInputMS_Type; + + const std::unique_ptr fGSampler1D_Type; + const std::unique_ptr fGSampler2D_Type; + const std::unique_ptr fGSampler3D_Type; + const std::unique_ptr fGSamplerCube_Type; + const std::unique_ptr fGSampler2DRect_Type; + const std::unique_ptr fGSampler1DArray_Type; + const std::unique_ptr fGSampler2DArray_Type; + const std::unique_ptr fGSamplerCubeArray_Type; + const std::unique_ptr fGSamplerBuffer_Type; + const std::unique_ptr fGSampler2DMS_Type; + const std::unique_ptr fGSampler2DMSArray_Type; + const std::unique_ptr fGSampler2DArrayShadow_Type; + const std::unique_ptr fGSamplerCubeArrayShadow_Type; + + const std::unique_ptr fGenType_Type; + const std::unique_ptr fGenHType_Type; + const std::unique_ptr fGenDType_Type; + const std::unique_ptr fGenIType_Type; + const std::unique_ptr fGenUType_Type; + const std::unique_ptr fGenBType_Type; + + const std::unique_ptr fMat_Type; + + const std::unique_ptr fVec_Type; + + const std::unique_ptr fGVec_Type; + const std::unique_ptr fGVec2_Type; + const std::unique_ptr fGVec3_Type; + const std::unique_ptr fGVec4_Type; + const std::unique_ptr fHVec_Type; + const std::unique_ptr fDVec_Type; + const std::unique_ptr fIVec_Type; + const std::unique_ptr fUVec_Type; + const std::unique_ptr fSVec_Type; + const std::unique_ptr fUSVec_Type; + const std::unique_ptr fByteVec_Type; + const std::unique_ptr fUByteVec_Type; + + const std::unique_ptr fBVec_Type; + + const std::unique_ptr fSkCaps_Type; + const std::unique_ptr fSkArgs_Type; + const std::unique_ptr fFragmentProcessor_Type; + + // dummy expression used to mark that a variable has a value during dataflow analysis (when it + // could have several different values, or the analyzer is otherwise unable to assign it a + // specific expression) + const std::unique_ptr fDefined_Expression; + +private: + class Defined : public Expression { + public: + Defined(const Type& type) + : INHERITED(-1, kDefined_Kind, type) {} + + bool hasSideEffects() const override { + return false; + } + + String description() const override { + return ""; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new Defined(fType)); + } + + typedef Expression INHERITED; + }; + + static std::unique_ptr fp_type(const Type* intType, const Type* boolType) { + // Build fields for FragmentProcessors, which should parallel the + // C++ API for GrFragmentProcessor. + Modifiers mods(Layout(), Modifiers::kConst_Flag); + std::vector fields = { + Type::Field(mods, "numTextureSamplers", intType), + Type::Field(mods, "numCoordTransforms", intType), + Type::Field(mods, "numChildProcessors", intType), + Type::Field(mods, "usesLocalCoords", boolType), + Type::Field(mods, "compatibleWithCoverageAsAlpha", boolType), + Type::Field(mods, "preservesOpaqueInput", boolType), + Type::Field(mods, "hasConstantOutputForConstantInput", boolType) + }; + return std::unique_ptr(new Type("fragmentProcessor", fields)); + } +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLDefines.h b/gfx/skia/skia/src/sksl/SkSLDefines.h new file mode 100644 index 0000000000..d0ba9d7572 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLDefines.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DEFINES +#define SKSL_DEFINES + +#include + +#ifdef SKSL_STANDALONE +#if defined(_WIN32) || defined(__SYMBIAN32__) +#define SKSL_BUILD_FOR_WIN +#endif +#else +#ifdef SK_BUILD_FOR_WIN +#define SKSL_BUILD_FOR_WIN +#endif // SK_BUILD_FOR_WIN +#endif // SKSL_STANDALONE + +#ifdef SKSL_STANDALONE +#define SkASSERT(x) do { if (!(x)) abort(); } while (false) +#define SkAssertResult(x) do { if (!(x)) abort(); } while (false) +#define SkDEBUGCODE(...) __VA_ARGS__ +#define SK_API +#if !defined(SkUNREACHABLE) +# if defined(_MSC_VER) && !defined(__clang__) +# define SkUNREACHABLE __assume(false) +# else +# define SkUNREACHABLE __builtin_unreachable() +# endif +#endif +#else +#include "include/core/SkTypes.h" +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define SKSL_PRINTF_LIKE(A, B) __attribute__((format(printf, (A), (B)))) +#define SKSL_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define SKSL_PRINTF_LIKE(A, B) +#define SKSL_WARN_UNUSED_RESULT +#endif + +#define ABORT(...) (printf(__VA_ARGS__), sksl_abort()) + +#if _MSC_VER +#define NORETURN __declspec(noreturn) +#else +#define NORETURN __attribute__((__noreturn__)) +#endif + +using SKSL_INT = int32_t; +using SKSL_FLOAT = float; + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLErrorReporter.h b/gfx/skia/skia/src/sksl/SkSLErrorReporter.h new file mode 100644 index 0000000000..a487e80e90 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLErrorReporter.h @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_ERRORREPORTER +#define SKSL_ERRORREPORTER + +#include "src/sksl/SkSLPosition.h" + +namespace SkSL { + +/** + * Interface for the compiler to report errors. + */ +class ErrorReporter { +public: + virtual ~ErrorReporter() {} + + void error(int offset, const char* msg) { + this->error(offset, String(msg)); + } + + virtual void error(int offset, String msg) = 0; + + virtual int errorCount() = 0; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLExternalValue.h b/gfx/skia/skia/src/sksl/SkSLExternalValue.h new file mode 100644 index 0000000000..d8aba5de49 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLExternalValue.h @@ -0,0 +1,116 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXTERNALVALUE +#define SKSL_EXTERNALVALUE + +#include "src/sksl/ir/SkSLSymbol.h" + +namespace SkSL { + +class String; +class Type; + +class ExternalValue : public Symbol { +public: + ExternalValue(const char* name, const Type& type) + : INHERITED(-1, kExternal_Kind, name) + , fType(type) {} + + virtual bool canRead() const { + return false; + } + + virtual bool canWrite() const { + return false; + } + + virtual bool canCall() const { + return false; + } + + /** + * Returns the type for purposes of read and write operations. + */ + virtual const Type& type() const { + return fType; + } + + virtual int callParameterCount() const { + return -1; + } + + /** + * Fills in the outTypes array with pointers to the parameter types. outTypes must be able to + * hold callParameterCount() pointers. + */ + virtual void getCallParameterTypes(const Type** outTypes) const { + SkASSERT(false); + } + + /** + * Returns the return type resulting from a call operation. + */ + virtual const Type& callReturnType() const { + return fType; + } + + /** + * Reads the external value and stores the resulting data in target. The caller must ensure + * that target is a valid pointer to a region of sufficient size to hold the data contained + * in this external value. + * 'index' is the element index ([0 .. N-1]) within a call to ByteCode::run() + */ + virtual void read(int index, float* target) { + SkASSERT(false); + } + + /** + * Copies the value in src into this external value. The caller must ensure that src is a + * pointer to the type of data expected by this external value. + * 'index' is the element index ([0 .. N-1]) within a call to ByteCode::run() + */ + virtual void write(int index, float* src) { + SkASSERT(false); + } + + /** + * Calls the value as a function with the specified parameters. arguments must be a pointer to + * a structure containing the arguments expected by the external value in source order, and + * outResult must be a pointer to a region of sufficient size to hold the function's return + * value. + * 'index' is the element index ([0 .. N-1]) within a call to ByteCode::run() + */ + virtual void call(int index, float* arguments, float* outResult) { + SkASSERT(false); + } + + /** + * Resolves 'name' within this context and returns an ExternalValue which represents it, or + * null if no such child exists. If the implementation of this method creates new + * ExternalValues and there isn't a more convenient place for ownership of the objects to + * reside, the compiler's takeOwnership method may be useful. + * + * The 'name' string may not persist after this call; do not store this pointer. + */ + virtual ExternalValue* getChild(const char* name) const { + return nullptr; + } + + String description() const override { + return String("external<") + fName + ">"; + } + +private: + typedef Symbol INHERITED; + + const Type& fType; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h b/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h new file mode 100644 index 0000000000..e7de13488b --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FILEOUTPUTSTREAM +#define SKSL_FILEOUTPUTSTREAM + +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLUtil.h" +#include + +namespace SkSL { + +class FileOutputStream : public OutputStream { +public: + FileOutputStream(const char* name) { + fFile = fopen(name, "wb"); + } + + ~FileOutputStream() override { + SkASSERT(!fOpen); + } + + bool isValid() const override { + return nullptr != fFile; + } + + void write8(uint8_t b) override { + SkASSERT(fOpen); + if (isValid()) { + if (EOF == fputc(b, fFile)) { + fFile = nullptr; + } + } + } + + void writeText(const char* s) override { + SkASSERT(fOpen); + if (isValid()) { + if (EOF == fputs(s, fFile)) { + fFile = nullptr; + } + } + } + + void write(const void* s, size_t size) override { + if (isValid()) { + size_t written = fwrite(s, 1, size, fFile); + if (written != size) { + fFile = nullptr; + } + } + } + + bool close() { + fOpen = false; + if (isValid() && fclose(fFile)) { + fFile = nullptr; + return false; + } + return true; + } + +private: + bool fOpen = true; + FILE *fFile; + + typedef OutputStream INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.cpp new file mode 100644 index 0000000000..df7c0b0729 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.cpp @@ -0,0 +1,1786 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLGLSLCodeGenerator.h" + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#ifndef SKSL_STANDALONE +#include "include/private/SkOnce.h" +#endif + +namespace SkSL { + +void GLSLCodeGenerator::write(const char* s) { + if (s[0] == 0) { + return; + } + if (fAtLineStart) { + for (int i = 0; i < fIndentation; i++) { + fOut->writeText(" "); + } + } + fOut->writeText(s); + fAtLineStart = false; +} + +void GLSLCodeGenerator::writeLine(const char* s) { + this->write(s); + fOut->writeText(fLineEnding); + fAtLineStart = true; +} + +void GLSLCodeGenerator::write(const String& s) { + this->write(s.c_str()); +} + +void GLSLCodeGenerator::write(StringFragment s) { + if (!s.fLength) { + return; + } + if (fAtLineStart) { + for (int i = 0; i < fIndentation; i++) { + fOut->writeText(" "); + } + } + fOut->write(s.fChars, s.fLength); + fAtLineStart = false; +} + +void GLSLCodeGenerator::writeLine(const String& s) { + this->writeLine(s.c_str()); +} + +void GLSLCodeGenerator::writeLine() { + this->writeLine(""); +} + +void GLSLCodeGenerator::writeExtension(const String& name) { + this->writeExtension(name, true); +} + +void GLSLCodeGenerator::writeExtension(const String& name, bool require) { + fExtensions.writeText("#extension "); + fExtensions.write(name.c_str(), name.length()); + fExtensions.writeText(require ? " : require\n" : " : enable\n"); +} + +bool GLSLCodeGenerator::usesPrecisionModifiers() const { + return fProgram.fSettings.fCaps->usesPrecisionModifiers(); +} + +String GLSLCodeGenerator::getTypeName(const Type& type) { + switch (type.kind()) { + case Type::kVector_Kind: { + Type component = type.componentType(); + String result; + if (component == *fContext.fFloat_Type || component == *fContext.fHalf_Type) { + result = "vec"; + } + else if (component == *fContext.fDouble_Type) { + result = "dvec"; + } + else if (component.isSigned()) { + result = "ivec"; + } + else if (component.isUnsigned()) { + result = "uvec"; + } + else if (component == *fContext.fBool_Type) { + result = "bvec"; + } + else { + ABORT("unsupported vector type"); + } + result += to_string(type.columns()); + return result; + } + case Type::kMatrix_Kind: { + String result; + Type component = type.componentType(); + if (component == *fContext.fFloat_Type || component == *fContext.fHalf_Type) { + result = "mat"; + } + else if (component == *fContext.fDouble_Type) { + result = "dmat"; + } + else { + ABORT("unsupported matrix type"); + } + result += to_string(type.columns()); + if (type.columns() != type.rows()) { + result += "x"; + result += to_string(type.rows()); + } + return result; + } + case Type::kArray_Kind: { + String result = this->getTypeName(type.componentType()) + "["; + if (type.columns() != -1) { + result += to_string(type.columns()); + } + result += "]"; + return result; + } + case Type::kScalar_Kind: { + if (type == *fContext.fHalf_Type) { + return "float"; + } + else if (type == *fContext.fShort_Type) { + return "int"; + } + else if (type == *fContext.fUShort_Type) { + return "uint"; + } + else if (type == *fContext.fByte_Type) { + return "int"; + } + else if (type == *fContext.fUByte_Type) { + return "uint"; + } + else { + return type.name(); + } + break; + } + default: + return type.name(); + } +} + +void GLSLCodeGenerator::writeType(const Type& type) { + if (type.kind() == Type::kStruct_Kind) { + for (const Type* search : fWrittenStructs) { + if (*search == type) { + // already written + this->write(type.fName); + return; + } + } + fWrittenStructs.push_back(&type); + this->write("struct "); + this->write(type.fName); + this->writeLine(" {"); + fIndentation++; + for (const auto& f : type.fields()) { + this->writeModifiers(f.fModifiers, false); + this->writeTypePrecision(*f.fType); + // sizes (which must be static in structs) are part of the type name here + this->writeType(*f.fType); + this->write(" "); + this->write(f.fName); + this->writeLine(";"); + } + fIndentation--; + this->write("}"); + } else { + this->write(this->getTypeName(type)); + } +} + +void GLSLCodeGenerator::writeExpression(const Expression& expr, Precedence parentPrecedence) { + switch (expr.fKind) { + case Expression::kBinary_Kind: + this->writeBinaryExpression((BinaryExpression&) expr, parentPrecedence); + break; + case Expression::kBoolLiteral_Kind: + this->writeBoolLiteral((BoolLiteral&) expr); + break; + case Expression::kConstructor_Kind: + this->writeConstructor((Constructor&) expr, parentPrecedence); + break; + case Expression::kIntLiteral_Kind: + this->writeIntLiteral((IntLiteral&) expr); + break; + case Expression::kFieldAccess_Kind: + this->writeFieldAccess(((FieldAccess&) expr)); + break; + case Expression::kFloatLiteral_Kind: + this->writeFloatLiteral(((FloatLiteral&) expr)); + break; + case Expression::kFunctionCall_Kind: + this->writeFunctionCall((FunctionCall&) expr); + break; + case Expression::kPrefix_Kind: + this->writePrefixExpression((PrefixExpression&) expr, parentPrecedence); + break; + case Expression::kPostfix_Kind: + this->writePostfixExpression((PostfixExpression&) expr, parentPrecedence); + break; + case Expression::kSetting_Kind: + this->writeSetting((Setting&) expr); + break; + case Expression::kSwizzle_Kind: + this->writeSwizzle((Swizzle&) expr); + break; + case Expression::kVariableReference_Kind: + this->writeVariableReference((VariableReference&) expr); + break; + case Expression::kTernary_Kind: + this->writeTernaryExpression((TernaryExpression&) expr, parentPrecedence); + break; + case Expression::kIndex_Kind: + this->writeIndexExpression((IndexExpression&) expr); + break; + default: + ABORT("unsupported expression: %s", expr.description().c_str()); + } +} + +static bool is_abs(Expression& expr) { + if (expr.fKind != Expression::kFunctionCall_Kind) { + return false; + } + return ((FunctionCall&) expr).fFunction.fName == "abs"; +} + +// turns min(abs(x), y) into ((tmpVar1 = abs(x)) < (tmpVar2 = y) ? tmpVar1 : tmpVar2) to avoid a +// Tegra3 compiler bug. +void GLSLCodeGenerator::writeMinAbsHack(Expression& absExpr, Expression& otherExpr) { + SkASSERT(!fProgram.fSettings.fCaps->canUseMinAndAbsTogether()); + String tmpVar1 = "minAbsHackVar" + to_string(fVarCount++); + String tmpVar2 = "minAbsHackVar" + to_string(fVarCount++); + this->fFunctionHeader += String(" ") + this->getTypePrecision(absExpr.fType) + + this->getTypeName(absExpr.fType) + " " + tmpVar1 + ";\n"; + this->fFunctionHeader += String(" ") + this->getTypePrecision(otherExpr.fType) + + this->getTypeName(otherExpr.fType) + " " + tmpVar2 + ";\n"; + this->write("((" + tmpVar1 + " = "); + this->writeExpression(absExpr, kTopLevel_Precedence); + this->write(") < (" + tmpVar2 + " = "); + this->writeExpression(otherExpr, kAssignment_Precedence); + this->write(") ? " + tmpVar1 + " : " + tmpVar2 + ")"); +} + +void GLSLCodeGenerator::writeInverseSqrtHack(const Expression& x) { + this->write("(1.0 / sqrt("); + this->writeExpression(x, kTopLevel_Precedence); + this->write("))"); +} + +void GLSLCodeGenerator::writeDeterminantHack(const Expression& mat) { + String name; + if (mat.fType == *fContext.fFloat2x2_Type || mat.fType == *fContext.fHalf2x2_Type) { + name = "_determinant2"; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "float " + name + "(mat2 m) {" + " return m[0][0] * m[1][1] - m[0][1] * m[1][0];" + "}" + ).c_str()); + } + } + else if (mat.fType == *fContext.fFloat3x3_Type || mat.fType == *fContext.fHalf3x3_Type) { + name = "_determinant3"; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "float " + name + "(mat3 m) {" + " float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];" + " float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];" + " float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];" + " float b01 = a22 * a11 - a12 * a21;" + " float b11 = -a22 * a10 + a12 * a20;" + " float b21 = a21 * a10 - a11 * a20;" + " return a00 * b01 + a01 * b11 + a02 * b21;" + "}" + ).c_str()); + } + } + else if (mat.fType == *fContext.fFloat4x4_Type || mat.fType == *fContext.fHalf4x4_Type) { + name = "_determinant3"; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "mat4 " + name + "(mat4 m) {" + " float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3];" + " float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3];" + " float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3];" + " float a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3];" + " float b00 = a00 * a11 - a01 * a10;" + " float b01 = a00 * a12 - a02 * a10;" + " float b02 = a00 * a13 - a03 * a10;" + " float b03 = a01 * a12 - a02 * a11;" + " float b04 = a01 * a13 - a03 * a11;" + " float b05 = a02 * a13 - a03 * a12;" + " float b06 = a20 * a31 - a21 * a30;" + " float b07 = a20 * a32 - a22 * a30;" + " float b08 = a20 * a33 - a23 * a30;" + " float b09 = a21 * a32 - a22 * a31;" + " float b10 = a21 * a33 - a23 * a31;" + " float b11 = a22 * a33 - a23 * a32;" + " return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;" + "}" + ).c_str()); + } + } + else { + SkASSERT(false); + } + this->write(name + "("); + this->writeExpression(mat, kTopLevel_Precedence); + this->write(")"); +} + +void GLSLCodeGenerator::writeInverseHack(const Expression& mat) { + String name; + if (mat.fType == *fContext.fFloat2x2_Type || mat.fType == *fContext.fHalf2x2_Type) { + name = "_inverse2"; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "mat2 " + name + "(mat2 m) {" + " return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / " + "(m[0][0] * m[1][1] - m[0][1] * m[1][0]);" + "}" + ).c_str()); + } + } + else if (mat.fType == *fContext.fFloat3x3_Type || mat.fType == *fContext.fHalf3x3_Type) { + name = "_inverse3"; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "mat3 " + name + "(mat3 m) {" + " float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];" + " float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];" + " float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];" + " float b01 = a22 * a11 - a12 * a21;" + " float b11 = -a22 * a10 + a12 * a20;" + " float b21 = a21 * a10 - a11 * a20;" + " float det = a00 * b01 + a01 * b11 + a02 * b21;" + " return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11)," + " b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10)," + " b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;" + "}" + ).c_str()); + } + } + else if (mat.fType == *fContext.fFloat4x4_Type || mat.fType == *fContext.fHalf4x4_Type) { + name = "_inverse4"; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "mat4 " + name + "(mat4 m) {" + " float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3];" + " float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3];" + " float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3];" + " float a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3];" + " float b00 = a00 * a11 - a01 * a10;" + " float b01 = a00 * a12 - a02 * a10;" + " float b02 = a00 * a13 - a03 * a10;" + " float b03 = a01 * a12 - a02 * a11;" + " float b04 = a01 * a13 - a03 * a11;" + " float b05 = a02 * a13 - a03 * a12;" + " float b06 = a20 * a31 - a21 * a30;" + " float b07 = a20 * a32 - a22 * a30;" + " float b08 = a20 * a33 - a23 * a30;" + " float b09 = a21 * a32 - a22 * a31;" + " float b10 = a21 * a33 - a23 * a31;" + " float b11 = a22 * a33 - a23 * a32;" + " float det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - " + " b04 * b07 + b05 * b06;" + " return mat4(" + " a11 * b11 - a12 * b10 + a13 * b09," + " a02 * b10 - a01 * b11 - a03 * b09," + " a31 * b05 - a32 * b04 + a33 * b03," + " a22 * b04 - a21 * b05 - a23 * b03," + " a12 * b08 - a10 * b11 - a13 * b07," + " a00 * b11 - a02 * b08 + a03 * b07," + " a32 * b02 - a30 * b05 - a33 * b01," + " a20 * b05 - a22 * b02 + a23 * b01," + " a10 * b10 - a11 * b08 + a13 * b06," + " a01 * b08 - a00 * b10 - a03 * b06," + " a30 * b04 - a31 * b02 + a33 * b00," + " a21 * b02 - a20 * b04 - a23 * b00," + " a11 * b07 - a10 * b09 - a12 * b06," + " a00 * b09 - a01 * b07 + a02 * b06," + " a31 * b01 - a30 * b03 - a32 * b00," + " a20 * b03 - a21 * b01 + a22 * b00) / det;" + "}" + ).c_str()); + } + } + else { + SkASSERT(false); + } + this->write(name + "("); + this->writeExpression(mat, kTopLevel_Precedence); + this->write(")"); +} + +void GLSLCodeGenerator::writeTransposeHack(const Expression& mat) { + String name = "transpose" + to_string(mat.fType.columns()) + to_string(mat.fType.rows()); + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + String type = this->getTypeName(mat.fType); + const Type& base = mat.fType.componentType(); + String transposed = this->getTypeName(base.toCompound(fContext, + mat.fType.rows(), + mat.fType.columns())); + fExtraFunctions.writeText((transposed + " " + name + "(" + type + " m) {\nreturn " + + transposed + "(").c_str()); + const char* separator = ""; + for (int row = 0; row < mat.fType.rows(); ++row) { + for (int column = 0; column < mat.fType.columns(); ++column) { + fExtraFunctions.writeText(separator); + fExtraFunctions.writeText(("m[" + to_string(column) + "][" + to_string(row) + + "]").c_str()); + separator = ", "; + } + } + fExtraFunctions.writeText("); }"); + } + this->write(name + "("); + this->writeExpression(mat, kTopLevel_Precedence); + this->write(")"); +} + +std::unordered_map* + GLSLCodeGenerator::fFunctionClasses = nullptr; + +void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) { +#ifdef SKSL_STANDALONE + if (!fFunctionClasses) { +#else + static SkOnce once; + once([] { +#endif + fFunctionClasses = new std::unordered_map(); + (*fFunctionClasses)["abs"] = FunctionClass::kAbs; + (*fFunctionClasses)["atan"] = FunctionClass::kAtan; + (*fFunctionClasses)["determinant"] = FunctionClass::kDeterminant; + (*fFunctionClasses)["dFdx"] = FunctionClass::kDFdx; + (*fFunctionClasses)["dFdy"] = FunctionClass::kDFdy; + (*fFunctionClasses)["fwidth"] = FunctionClass::kFwidth; + (*fFunctionClasses)["fma"] = FunctionClass::kFMA; + (*fFunctionClasses)["fract"] = FunctionClass::kFract; + (*fFunctionClasses)["inverse"] = FunctionClass::kInverse; + (*fFunctionClasses)["inverseSqrt"] = FunctionClass::kInverseSqrt; + (*fFunctionClasses)["min"] = FunctionClass::kMin; + (*fFunctionClasses)["pow"] = FunctionClass::kPow; + (*fFunctionClasses)["saturate"] = FunctionClass::kSaturate; + (*fFunctionClasses)["sample"] = FunctionClass::kTexture; + (*fFunctionClasses)["transpose"] = FunctionClass::kTranspose; + } +#ifndef SKSL_STANDALONE + ); +#endif + const auto found = c.fFunction.fBuiltin ? fFunctionClasses->find(c.fFunction.fName) : + fFunctionClasses->end(); + bool isTextureFunctionWithBias = false; + bool nameWritten = false; + if (found != fFunctionClasses->end()) { + switch (found->second) { + case FunctionClass::kAbs: { + if (!fProgram.fSettings.fCaps->emulateAbsIntFunction()) + break; + SkASSERT(c.fArguments.size() == 1); + if (c.fArguments[0]->fType != *fContext.fInt_Type) + break; + // abs(int) on Intel OSX is incorrect, so emulate it: + String name = "_absemulation"; + this->write(name); + nameWritten = true; + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + "int " + name + "(int x) {\n" + " return x * sign(x);\n" + "}\n" + ).c_str()); + } + break; + } + case FunctionClass::kAtan: + if (fProgram.fSettings.fCaps->mustForceNegatedAtanParamToFloat() && + c.fArguments.size() == 2 && + c.fArguments[1]->fKind == Expression::kPrefix_Kind) { + const PrefixExpression& p = (PrefixExpression&) *c.fArguments[1]; + if (p.fOperator == Token::MINUS) { + this->write("atan("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(", -1.0 * "); + this->writeExpression(*p.fOperand, kMultiplicative_Precedence); + this->write(")"); + return; + } + } + break; + case FunctionClass::kDFdy: + if (fProgram.fSettings.fFlipY) { + // Flipping Y also negates the Y derivatives. + this->write("-dFdy"); + nameWritten = true; + } + // fallthru + case FunctionClass::kDFdx: + case FunctionClass::kFwidth: + if (!fFoundDerivatives && + fProgram.fSettings.fCaps->shaderDerivativeExtensionString()) { + SkASSERT(fProgram.fSettings.fCaps->shaderDerivativeSupport()); + this->writeExtension(fProgram.fSettings.fCaps->shaderDerivativeExtensionString()); + fFoundDerivatives = true; + } + break; + case FunctionClass::kDeterminant: + if (fProgram.fSettings.fCaps->generation() < k150_GrGLSLGeneration) { + SkASSERT(c.fArguments.size() == 1); + this->writeDeterminantHack(*c.fArguments[0]); + return; + } + break; + case FunctionClass::kFMA: + if (!fProgram.fSettings.fCaps->builtinFMASupport()) { + SkASSERT(c.fArguments.size() == 3); + this->write("(("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(") * ("); + this->writeExpression(*c.fArguments[1], kSequence_Precedence); + this->write(") + ("); + this->writeExpression(*c.fArguments[2], kSequence_Precedence); + this->write("))"); + return; + } + break; + case FunctionClass::kFract: + if (!fProgram.fSettings.fCaps->canUseFractForNegativeValues()) { + SkASSERT(c.fArguments.size() == 1); + this->write("(0.5 - sign("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(") * (0.5 - fract(abs("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write("))))"); + return; + } + break; + case FunctionClass::kInverse: + if (fProgram.fSettings.fCaps->generation() < k140_GrGLSLGeneration) { + SkASSERT(c.fArguments.size() == 1); + this->writeInverseHack(*c.fArguments[0]); + return; + } + break; + case FunctionClass::kInverseSqrt: + if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) { + SkASSERT(c.fArguments.size() == 1); + this->writeInverseSqrtHack(*c.fArguments[0]); + return; + } + break; + case FunctionClass::kMin: + if (!fProgram.fSettings.fCaps->canUseMinAndAbsTogether()) { + SkASSERT(c.fArguments.size() == 2); + if (is_abs(*c.fArguments[0])) { + this->writeMinAbsHack(*c.fArguments[0], *c.fArguments[1]); + return; + } + if (is_abs(*c.fArguments[1])) { + // note that this violates the GLSL left-to-right evaluation semantics. + // I doubt it will ever end up mattering, but it's worth calling out. + this->writeMinAbsHack(*c.fArguments[1], *c.fArguments[0]); + return; + } + } + break; + case FunctionClass::kPow: + if (!fProgram.fSettings.fCaps->removePowWithConstantExponent()) { + break; + } + // pow(x, y) on some NVIDIA drivers causes crashes if y is a + // constant. It's hard to tell what constitutes "constant" here + // so just replace in all cases. + + // Change pow(x, y) into exp2(y * log2(x)) + this->write("exp2("); + this->writeExpression(*c.fArguments[1], kMultiplicative_Precedence); + this->write(" * log2("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write("))"); + return; + case FunctionClass::kSaturate: + SkASSERT(c.fArguments.size() == 1); + this->write("clamp("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(", 0.0, 1.0)"); + return; + case FunctionClass::kTexture: { + const char* dim = ""; + bool proj = false; + switch (c.fArguments[0]->fType.dimensions()) { + case SpvDim1D: + dim = "1D"; + isTextureFunctionWithBias = true; + if (c.fArguments[1]->fType == *fContext.fFloat_Type) { + proj = false; + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat2_Type); + proj = true; + } + break; + case SpvDim2D: + dim = "2D"; + if (c.fArguments[0]->fType != *fContext.fSamplerExternalOES_Type) { + isTextureFunctionWithBias = true; + } + if (c.fArguments[1]->fType == *fContext.fFloat2_Type) { + proj = false; + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat3_Type); + proj = true; + } + break; + case SpvDim3D: + dim = "3D"; + isTextureFunctionWithBias = true; + if (c.fArguments[1]->fType == *fContext.fFloat3_Type) { + proj = false; + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat4_Type); + proj = true; + } + break; + case SpvDimCube: + dim = "Cube"; + isTextureFunctionWithBias = true; + proj = false; + break; + case SpvDimRect: + dim = "2DRect"; + proj = false; + break; + case SpvDimBuffer: + SkASSERT(false); // doesn't exist + dim = "Buffer"; + proj = false; + break; + case SpvDimSubpassData: + SkASSERT(false); // doesn't exist + dim = "SubpassData"; + proj = false; + break; + } + if (fTextureFunctionOverride != "") { + this->write(fTextureFunctionOverride.c_str()); + } else { + this->write("texture"); + if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) { + this->write(dim); + } + if (proj) { + this->write("Proj"); + } + } + nameWritten = true; + break; + } + case FunctionClass::kTranspose: + if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) { + SkASSERT(c.fArguments.size() == 1); + this->writeTransposeHack(*c.fArguments[0]); + return; + } + break; + } + } + if (!nameWritten) { + this->write(c.fFunction.fName); + } + this->write("("); + const char* separator = ""; + for (const auto& arg : c.fArguments) { + this->write(separator); + separator = ", "; + this->writeExpression(*arg, kSequence_Precedence); + } + if (fProgram.fSettings.fSharpenTextures && isTextureFunctionWithBias) { + this->write(", -0.5"); + } + this->write(")"); +} + +void GLSLCodeGenerator::writeConstructor(const Constructor& c, Precedence parentPrecedence) { + if (c.fArguments.size() == 1 && + (this->getTypeName(c.fType) == this->getTypeName(c.fArguments[0]->fType) || + (c.fType.kind() == Type::kScalar_Kind && + c.fArguments[0]->fType == *fContext.fFloatLiteral_Type))) { + // in cases like half(float), they're different types as far as SkSL is concerned but the + // same type as far as GLSL is concerned. We avoid a redundant float(float) by just writing + // out the inner expression here. + this->writeExpression(*c.fArguments[0], parentPrecedence); + return; + } + this->writeType(c.fType); + this->write("("); + const char* separator = ""; + for (const auto& arg : c.fArguments) { + this->write(separator); + separator = ", "; + this->writeExpression(*arg, kSequence_Precedence); + } + this->write(")"); +} + +void GLSLCodeGenerator::writeFragCoord() { + if (!fProgram.fSettings.fCaps->canUseFragCoord()) { + if (!fSetupFragCoordWorkaround) { + const char* precision = usesPrecisionModifiers() ? "highp " : ""; + fFunctionHeader += precision; + fFunctionHeader += " float sk_FragCoord_InvW = 1. / sk_FragCoord_Workaround.w;\n"; + fFunctionHeader += precision; + fFunctionHeader += " vec4 sk_FragCoord_Resolved = " + "vec4(sk_FragCoord_Workaround.xyz * sk_FragCoord_InvW, sk_FragCoord_InvW);\n"; + // Ensure that we get exact .5 values for x and y. + fFunctionHeader += " sk_FragCoord_Resolved.xy = floor(sk_FragCoord_Resolved.xy) + " + "vec2(.5);\n"; + fSetupFragCoordWorkaround = true; + } + this->write("sk_FragCoord_Resolved"); + return; + } + + // We only declare "gl_FragCoord" when we're in the case where we want to use layout qualifiers + // to reverse y. Otherwise it isn't necessary and whether the "in" qualifier appears in the + // declaration varies in earlier GLSL specs. So it is simpler to omit it. + if (!fProgram.fSettings.fFlipY) { + this->write("gl_FragCoord"); + } else if (const char* extension = + fProgram.fSettings.fCaps->fragCoordConventionsExtensionString()) { + if (!fSetupFragPositionGlobal) { + if (fProgram.fSettings.fCaps->generation() < k150_GrGLSLGeneration) { + this->writeExtension(extension); + } + fGlobals.writeText("layout(origin_upper_left) in vec4 gl_FragCoord;\n"); + fSetupFragPositionGlobal = true; + } + this->write("gl_FragCoord"); + } else { + if (!fSetupFragPositionLocal) { + fFunctionHeader += usesPrecisionModifiers() ? "highp " : ""; + fFunctionHeader += " vec4 sk_FragCoord = vec4(gl_FragCoord.x, " SKSL_RTHEIGHT_NAME + " - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);\n"; + fSetupFragPositionLocal = true; + } + this->write("sk_FragCoord"); + } +} + +void GLSLCodeGenerator::writeVariableReference(const VariableReference& ref) { + switch (ref.fVariable.fModifiers.fLayout.fBuiltin) { + case SK_FRAGCOLOR_BUILTIN: + if (fProgram.fSettings.fCaps->mustDeclareFragmentShaderOutput()) { + this->write("sk_FragColor"); + } else { + this->write("gl_FragColor"); + } + break; + case SK_FRAGCOORD_BUILTIN: + this->writeFragCoord(); + break; + case SK_WIDTH_BUILTIN: + this->write("u_skRTWidth"); + break; + case SK_HEIGHT_BUILTIN: + this->write("u_skRTHeight"); + break; + case SK_CLOCKWISE_BUILTIN: + this->write(fProgram.fSettings.fFlipY ? "(!gl_FrontFacing)" : "gl_FrontFacing"); + break; + case SK_VERTEXID_BUILTIN: + this->write("gl_VertexID"); + break; + case SK_INSTANCEID_BUILTIN: + this->write("gl_InstanceID"); + break; + case SK_CLIPDISTANCE_BUILTIN: + this->write("gl_ClipDistance"); + break; + case SK_IN_BUILTIN: + this->write("gl_in"); + break; + case SK_INVOCATIONID_BUILTIN: + this->write("gl_InvocationID"); + break; + case SK_LASTFRAGCOLOR_BUILTIN: + this->write(fProgram.fSettings.fCaps->fbFetchColorName()); + break; + default: + this->write(ref.fVariable.fName); + } +} + +void GLSLCodeGenerator::writeIndexExpression(const IndexExpression& expr) { + this->writeExpression(*expr.fBase, kPostfix_Precedence); + this->write("["); + this->writeExpression(*expr.fIndex, kTopLevel_Precedence); + this->write("]"); +} + +bool is_sk_position(const FieldAccess& f) { + return "sk_Position" == f.fBase->fType.fields()[f.fFieldIndex].fName; +} + +void GLSLCodeGenerator::writeFieldAccess(const FieldAccess& f) { + if (f.fOwnerKind == FieldAccess::kDefault_OwnerKind) { + this->writeExpression(*f.fBase, kPostfix_Precedence); + this->write("."); + } + switch (f.fBase->fType.fields()[f.fFieldIndex].fModifiers.fLayout.fBuiltin) { + case SK_CLIPDISTANCE_BUILTIN: + this->write("gl_ClipDistance"); + break; + default: + StringFragment name = f.fBase->fType.fields()[f.fFieldIndex].fName; + if (name == "sk_Position") { + this->write("gl_Position"); + } else if (name == "sk_PointSize") { + this->write("gl_PointSize"); + } else { + this->write(f.fBase->fType.fields()[f.fFieldIndex].fName); + } + } +} + +void GLSLCodeGenerator::writeConstantSwizzle(const Swizzle& swizzle, const String& constants) { + this->writeType(swizzle.fType); + this->write("("); + this->write(constants); + this->write(")"); +} + +void GLSLCodeGenerator::writeSwizzleMask(const Swizzle& swizzle, const String& mask) { + this->writeExpression(*swizzle.fBase, kPostfix_Precedence); + this->write("."); + this->write(mask); +} + +void GLSLCodeGenerator::writeSwizzleConstructor(const Swizzle& swizzle, const String& constants, + const String& mask, + GLSLCodeGenerator::SwizzleOrder order) { + this->writeType(swizzle.fType); + this->write("("); + if (order == SwizzleOrder::CONSTANTS_FIRST) { + this->write(constants); + this->write(", "); + this->writeSwizzleMask(swizzle, mask); + } else { + this->writeSwizzleMask(swizzle, mask); + this->write(", "); + this->write(constants); + } + this->write(")"); +} + +void GLSLCodeGenerator::writeSwizzleConstructor(const Swizzle& swizzle, const String& constants, + const String& mask, const String& reswizzle) { + this->writeSwizzleConstructor(swizzle, constants, mask, SwizzleOrder::MASK_FIRST); + this->write("."); + this->write(reswizzle); +} + +// Writing a swizzle is complicated due to the handling of constant swizzle components. The most +// problematic case is a mask like '.r00a'. A naive approach might turn that into +// 'vec4(base.r, 0, 0, base.a)', but that would cause 'base' to be evaluated twice. We instead +// group the swizzle mask ('ra') and constants ('0, 0') together and use a secondary swizzle to put +// them back into the right order, so in this case we end up with something like +// 'vec4(base4.ra, 0, 0).rbag'. +void GLSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + // has a 1 bit in every position for which the swizzle mask is a constant, so 'r0b1' would + // yield binary 0101. + int constantBits = 0; + String mask; + String constants; + // compute mask ("ra") and constant ("0, 0") strings, and fill in constantBits + for (int c : swizzle.fComponents) { + constantBits <<= 1; + switch (c) { + case SKSL_SWIZZLE_0: + constantBits |= 1; + if (constants.length() > 0) { + constants += ", "; + } + constants += "0"; + break; + case SKSL_SWIZZLE_1: + constantBits |= 1; + if (constants.length() > 0) { + constants += ", "; + } + constants += "1"; + break; + case 0: + mask += "x"; + break; + case 1: + mask += "y"; + break; + case 2: + mask += "z"; + break; + case 3: + mask += "w"; + break; + default: + SkASSERT(false); + } + } + switch (swizzle.fComponents.size()) { + case 1: + if (constantBits == 1) { + this->write(constants); + } + else { + this->writeSwizzleMask(swizzle, mask); + } + break; + case 2: + switch (constantBits) { + case 0: // 00 + this->writeSwizzleMask(swizzle, mask); + break; + case 1: // 01 + this->writeSwizzleConstructor(swizzle, constants, mask, + SwizzleOrder::MASK_FIRST); + break; + case 2: // 10 + this->writeSwizzleConstructor(swizzle, constants, mask, + SwizzleOrder::CONSTANTS_FIRST); + break; + case 3: // 11 + this->writeConstantSwizzle(swizzle, constants); + break; + default: + SkASSERT(false); + } + break; + case 3: + switch (constantBits) { + case 0: // 000 + this->writeSwizzleMask(swizzle, mask); + break; + case 1: // 001 + case 3: // 011 + this->writeSwizzleConstructor(swizzle, constants, mask, + SwizzleOrder::MASK_FIRST); + break; + case 4: // 100 + case 6: // 110 + this->writeSwizzleConstructor(swizzle, constants, mask, + SwizzleOrder::CONSTANTS_FIRST); + break; + case 2: // 010 + this->writeSwizzleConstructor(swizzle, constants, mask, "xzy"); + break; + case 5: // 101 + this->writeSwizzleConstructor(swizzle, constants, mask, "yxz"); + break; + case 7: // 111 + this->writeConstantSwizzle(swizzle, constants); + break; + } + break; + case 4: + switch (constantBits) { + case 0: // 0000 + this->writeSwizzleMask(swizzle, mask); + break; + case 1: // 0001 + case 3: // 0011 + case 7: // 0111 + this->writeSwizzleConstructor(swizzle, constants, mask, + SwizzleOrder::MASK_FIRST); + break; + case 8: // 1000 + case 12: // 1100 + case 14: // 1110 + this->writeSwizzleConstructor(swizzle, constants, mask, + SwizzleOrder::CONSTANTS_FIRST); + break; + case 2: // 0010 + this->writeSwizzleConstructor(swizzle, constants, mask, "xywz"); + break; + case 4: // 0100 + this->writeSwizzleConstructor(swizzle, constants, mask, "xwyz"); + break; + case 5: // 0101 + this->writeSwizzleConstructor(swizzle, constants, mask, "xzyw"); + break; + case 6: // 0110 + this->writeSwizzleConstructor(swizzle, constants, mask, "xzwy"); + break; + case 9: // 1001 + this->writeSwizzleConstructor(swizzle, constants, mask, "zxyw"); + break; + case 10: // 1010 + this->writeSwizzleConstructor(swizzle, constants, mask, "zxwy"); + break; + case 11: // 1011 + this->writeSwizzleConstructor(swizzle, constants, mask, "yxzw"); + break; + case 13: // 1101 + this->writeSwizzleConstructor(swizzle, constants, mask, "yzxw"); + break; + case 15: // 1111 + this->writeConstantSwizzle(swizzle, constants); + break; + } + } +} + +GLSLCodeGenerator::Precedence GLSLCodeGenerator::GetBinaryPrecedence(Token::Kind op) { + switch (op) { + case Token::STAR: // fall through + case Token::SLASH: // fall through + case Token::PERCENT: return GLSLCodeGenerator::kMultiplicative_Precedence; + case Token::PLUS: // fall through + case Token::MINUS: return GLSLCodeGenerator::kAdditive_Precedence; + case Token::SHL: // fall through + case Token::SHR: return GLSLCodeGenerator::kShift_Precedence; + case Token::LT: // fall through + case Token::GT: // fall through + case Token::LTEQ: // fall through + case Token::GTEQ: return GLSLCodeGenerator::kRelational_Precedence; + case Token::EQEQ: // fall through + case Token::NEQ: return GLSLCodeGenerator::kEquality_Precedence; + case Token::BITWISEAND: return GLSLCodeGenerator::kBitwiseAnd_Precedence; + case Token::BITWISEXOR: return GLSLCodeGenerator::kBitwiseXor_Precedence; + case Token::BITWISEOR: return GLSLCodeGenerator::kBitwiseOr_Precedence; + case Token::LOGICALAND: return GLSLCodeGenerator::kLogicalAnd_Precedence; + case Token::LOGICALXOR: return GLSLCodeGenerator::kLogicalXor_Precedence; + case Token::LOGICALOR: return GLSLCodeGenerator::kLogicalOr_Precedence; + case Token::EQ: // fall through + case Token::PLUSEQ: // fall through + case Token::MINUSEQ: // fall through + case Token::STAREQ: // fall through + case Token::SLASHEQ: // fall through + case Token::PERCENTEQ: // fall through + case Token::SHLEQ: // fall through + case Token::SHREQ: // fall through + case Token::LOGICALANDEQ: // fall through + case Token::LOGICALXOREQ: // fall through + case Token::LOGICALOREQ: // fall through + case Token::BITWISEANDEQ: // fall through + case Token::BITWISEXOREQ: // fall through + case Token::BITWISEOREQ: return GLSLCodeGenerator::kAssignment_Precedence; + case Token::COMMA: return GLSLCodeGenerator::kSequence_Precedence; + default: ABORT("unsupported binary operator"); + } +} + +void GLSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + if (fProgram.fSettings.fCaps->unfoldShortCircuitAsTernary() && + (b.fOperator == Token::LOGICALAND || b.fOperator == Token::LOGICALOR)) { + this->writeShortCircuitWorkaroundExpression(b, parentPrecedence); + return; + } + + Precedence precedence = GetBinaryPrecedence(b.fOperator); + if (precedence >= parentPrecedence) { + this->write("("); + } + bool positionWorkaround = fProgramKind == Program::Kind::kVertex_Kind && + Compiler::IsAssignment(b.fOperator) && + Expression::kFieldAccess_Kind == b.fLeft->fKind && + is_sk_position((FieldAccess&) *b.fLeft) && + !strstr(b.fRight->description().c_str(), "sk_RTAdjust") && + !fProgram.fSettings.fCaps->canUseFragCoord(); + if (positionWorkaround) { + this->write("sk_FragCoord_Workaround = ("); + } + this->writeExpression(*b.fLeft, precedence); + this->write(" "); + this->write(Compiler::OperatorName(b.fOperator)); + this->write(" "); + this->writeExpression(*b.fRight, precedence); + if (positionWorkaround) { + this->write(")"); + } + if (precedence >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writeShortCircuitWorkaroundExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + if (kTernary_Precedence >= parentPrecedence) { + this->write("("); + } + + // Transform: + // a && b => a ? b : false + // a || b => a ? true : b + this->writeExpression(*b.fLeft, kTernary_Precedence); + this->write(" ? "); + if (b.fOperator == Token::LOGICALAND) { + this->writeExpression(*b.fRight, kTernary_Precedence); + } else { + BoolLiteral boolTrue(fContext, -1, true); + this->writeBoolLiteral(boolTrue); + } + this->write(" : "); + if (b.fOperator == Token::LOGICALAND) { + BoolLiteral boolFalse(fContext, -1, false); + this->writeBoolLiteral(boolFalse); + } else { + this->writeExpression(*b.fRight, kTernary_Precedence); + } + if (kTernary_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writeTernaryExpression(const TernaryExpression& t, + Precedence parentPrecedence) { + if (kTernary_Precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*t.fTest, kTernary_Precedence); + this->write(" ? "); + this->writeExpression(*t.fIfTrue, kTernary_Precedence); + this->write(" : "); + this->writeExpression(*t.fIfFalse, kTernary_Precedence); + if (kTernary_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writePrefixExpression(const PrefixExpression& p, + Precedence parentPrecedence) { + if (kPrefix_Precedence >= parentPrecedence) { + this->write("("); + } + this->write(Compiler::OperatorName(p.fOperator)); + this->writeExpression(*p.fOperand, kPrefix_Precedence); + if (kPrefix_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writePostfixExpression(const PostfixExpression& p, + Precedence parentPrecedence) { + if (kPostfix_Precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*p.fOperand, kPostfix_Precedence); + this->write(Compiler::OperatorName(p.fOperator)); + if (kPostfix_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writeBoolLiteral(const BoolLiteral& b) { + this->write(b.fValue ? "true" : "false"); +} + +void GLSLCodeGenerator::writeIntLiteral(const IntLiteral& i) { + if (i.fType == *fContext.fUInt_Type) { + this->write(to_string(i.fValue & 0xffffffff) + "u"); + } else if (i.fType == *fContext.fUShort_Type) { + this->write(to_string(i.fValue & 0xffff) + "u"); + } else if (i.fType == *fContext.fUByte_Type) { + this->write(to_string(i.fValue & 0xff) + "u"); + } else { + this->write(to_string((int32_t) i.fValue)); + } +} + +void GLSLCodeGenerator::writeFloatLiteral(const FloatLiteral& f) { + this->write(to_string(f.fValue)); +} + +void GLSLCodeGenerator::writeSetting(const Setting& s) { + ABORT("internal error; setting was not folded to a constant during compilation\n"); +} + +void GLSLCodeGenerator::writeFunction(const FunctionDefinition& f) { + fSetupFragPositionLocal = false; + fSetupFragCoordWorkaround = false; + if (fProgramKind != Program::kPipelineStage_Kind) { + this->writeTypePrecision(f.fDeclaration.fReturnType); + this->writeType(f.fDeclaration.fReturnType); + this->write(" " + f.fDeclaration.fName + "("); + const char* separator = ""; + for (const auto& param : f.fDeclaration.fParameters) { + this->write(separator); + separator = ", "; + this->writeModifiers(param->fModifiers, false); + std::vector sizes; + const Type* type = ¶m->fType; + while (type->kind() == Type::kArray_Kind) { + sizes.push_back(type->columns()); + type = &type->componentType(); + } + this->writeTypePrecision(*type); + this->writeType(*type); + this->write(" " + param->fName); + for (int s : sizes) { + if (s <= 0) { + this->write("[]"); + } else { + this->write("[" + to_string(s) + "]"); + } + } + } + this->writeLine(") {"); + fIndentation++; + } + fFunctionHeader = ""; + OutputStream* oldOut = fOut; + StringStream buffer; + fOut = &buffer; + this->writeStatements(((Block&) *f.fBody).fStatements); + if (fProgramKind != Program::kPipelineStage_Kind) { + fIndentation--; + this->writeLine("}"); + } + + fOut = oldOut; + this->write(fFunctionHeader); + this->write(buffer.str()); +} + +void GLSLCodeGenerator::writeModifiers(const Modifiers& modifiers, + bool globalContext) { + if (modifiers.fFlags & Modifiers::kFlat_Flag) { + this->write("flat "); + } + if (modifiers.fFlags & Modifiers::kNoPerspective_Flag) { + this->write("noperspective "); + } + String layout = modifiers.fLayout.description(); + if (layout.size()) { + this->write(layout + " "); + } + if (modifiers.fFlags & Modifiers::kReadOnly_Flag) { + this->write("readonly "); + } + if (modifiers.fFlags & Modifiers::kWriteOnly_Flag) { + this->write("writeonly "); + } + if (modifiers.fFlags & Modifiers::kCoherent_Flag) { + this->write("coherent "); + } + if (modifiers.fFlags & Modifiers::kVolatile_Flag) { + this->write("volatile "); + } + if (modifiers.fFlags & Modifiers::kRestrict_Flag) { + this->write("restrict "); + } + if ((modifiers.fFlags & Modifiers::kIn_Flag) && + (modifiers.fFlags & Modifiers::kOut_Flag)) { + this->write("inout "); + } else if (modifiers.fFlags & Modifiers::kIn_Flag) { + if (globalContext && + fProgram.fSettings.fCaps->generation() < GrGLSLGeneration::k130_GrGLSLGeneration) { + this->write(fProgramKind == Program::kVertex_Kind ? "attribute " + : "varying "); + } else { + this->write("in "); + } + } else if (modifiers.fFlags & Modifiers::kOut_Flag) { + if (globalContext && + fProgram.fSettings.fCaps->generation() < GrGLSLGeneration::k130_GrGLSLGeneration) { + this->write("varying "); + } else { + this->write("out "); + } + } + if (modifiers.fFlags & Modifiers::kUniform_Flag) { + this->write("uniform "); + } + if (modifiers.fFlags & Modifiers::kConst_Flag) { + this->write("const "); + } + if (modifiers.fFlags & Modifiers::kPLS_Flag) { + this->write("__pixel_localEXT "); + } + if (modifiers.fFlags & Modifiers::kPLSIn_Flag) { + this->write("__pixel_local_inEXT "); + } + if (modifiers.fFlags & Modifiers::kPLSOut_Flag) { + this->write("__pixel_local_outEXT "); + } + switch (modifiers.fLayout.fFormat) { + case Layout::Format::kUnspecified: + break; + case Layout::Format::kRGBA32F: // fall through + case Layout::Format::kR32F: + this->write("highp "); + break; + case Layout::Format::kRGBA16F: // fall through + case Layout::Format::kR16F: // fall through + case Layout::Format::kLUMINANCE16F: // fall through + case Layout::Format::kRG16F: + this->write("mediump "); + break; + case Layout::Format::kRGBA8: // fall through + case Layout::Format::kR8: // fall through + case Layout::Format::kRGBA8I: // fall through + case Layout::Format::kR8I: + this->write("lowp "); + break; + } +} + +void GLSLCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) { + if (intf.fTypeName == "sk_PerVertex") { + return; + } + this->writeModifiers(intf.fVariable.fModifiers, true); + this->writeLine(intf.fTypeName + " {"); + fIndentation++; + const Type* structType = &intf.fVariable.fType; + while (structType->kind() == Type::kArray_Kind) { + structType = &structType->componentType(); + } + for (const auto& f : structType->fields()) { + this->writeModifiers(f.fModifiers, false); + this->writeTypePrecision(*f.fType); + this->writeType(*f.fType); + this->writeLine(" " + f.fName + ";"); + } + fIndentation--; + this->write("}"); + if (intf.fInstanceName.size()) { + this->write(" "); + this->write(intf.fInstanceName); + for (const auto& size : intf.fSizes) { + this->write("["); + if (size) { + this->writeExpression(*size, kTopLevel_Precedence); + } + this->write("]"); + } + } + this->writeLine(";"); +} + +void GLSLCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) { + this->writeExpression(value, kTopLevel_Precedence); +} + +const char* GLSLCodeGenerator::getTypePrecision(const Type& type) { + if (usesPrecisionModifiers()) { + switch (type.kind()) { + case Type::kScalar_Kind: + if (type == *fContext.fShort_Type || type == *fContext.fUShort_Type || + type == *fContext.fByte_Type || type == *fContext.fUByte_Type) { + if (fProgram.fSettings.fForceHighPrecision || + fProgram.fSettings.fCaps->incompleteShortIntPrecision()) { + return "highp "; + } + return "mediump "; + } + if (type == *fContext.fHalf_Type) { + return fProgram.fSettings.fForceHighPrecision ? "highp " : "mediump "; + } + if (type == *fContext.fFloat_Type || type == *fContext.fInt_Type || + type == *fContext.fUInt_Type) { + return "highp "; + } + return ""; + case Type::kVector_Kind: // fall through + case Type::kMatrix_Kind: + return this->getTypePrecision(type.componentType()); + default: + break; + } + } + return ""; +} + +void GLSLCodeGenerator::writeTypePrecision(const Type& type) { + this->write(this->getTypePrecision(type)); +} + +void GLSLCodeGenerator::writeVarDeclarations(const VarDeclarations& decl, bool global) { + if (!decl.fVars.size()) { + return; + } + bool wroteType = false; + for (const auto& stmt : decl.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + if (wroteType) { + this->write(", "); + } else { + this->writeModifiers(var.fVar->fModifiers, global); + this->writeTypePrecision(decl.fBaseType); + this->writeType(decl.fBaseType); + this->write(" "); + wroteType = true; + } + this->write(var.fVar->fName); + for (const auto& size : var.fSizes) { + this->write("["); + if (size) { + this->writeExpression(*size, kTopLevel_Precedence); + } + this->write("]"); + } + if (var.fValue) { + this->write(" = "); + this->writeVarInitializer(*var.fVar, *var.fValue); + } + if (!fFoundExternalSamplerDecl && var.fVar->fType == *fContext.fSamplerExternalOES_Type) { + if (fProgram.fSettings.fCaps->externalTextureExtensionString()) { + this->writeExtension(fProgram.fSettings.fCaps->externalTextureExtensionString()); + } + if (fProgram.fSettings.fCaps->secondExternalTextureExtensionString()) { + this->writeExtension( + fProgram.fSettings.fCaps->secondExternalTextureExtensionString()); + } + fFoundExternalSamplerDecl = true; + } + if (!fFoundRectSamplerDecl && var.fVar->fType == *fContext.fSampler2DRect_Type) { + fFoundRectSamplerDecl = true; + } + } + if (wroteType) { + this->write(";"); + } +} + +void GLSLCodeGenerator::writeStatement(const Statement& s) { + switch (s.fKind) { + case Statement::kBlock_Kind: + this->writeBlock((Block&) s); + break; + case Statement::kExpression_Kind: + this->writeExpression(*((ExpressionStatement&) s).fExpression, kTopLevel_Precedence); + this->write(";"); + break; + case Statement::kReturn_Kind: + this->writeReturnStatement((ReturnStatement&) s); + break; + case Statement::kVarDeclarations_Kind: + this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration, false); + break; + case Statement::kIf_Kind: + this->writeIfStatement((IfStatement&) s); + break; + case Statement::kFor_Kind: + this->writeForStatement((ForStatement&) s); + break; + case Statement::kWhile_Kind: + this->writeWhileStatement((WhileStatement&) s); + break; + case Statement::kDo_Kind: + this->writeDoStatement((DoStatement&) s); + break; + case Statement::kSwitch_Kind: + this->writeSwitchStatement((SwitchStatement&) s); + break; + case Statement::kBreak_Kind: + this->write("break;"); + break; + case Statement::kContinue_Kind: + this->write("continue;"); + break; + case Statement::kDiscard_Kind: + this->write("discard;"); + break; + case Statement::kNop_Kind: + this->write(";"); + break; + default: + ABORT("unsupported statement: %s", s.description().c_str()); + } +} + +void GLSLCodeGenerator::writeStatements(const std::vector>& statements) { + for (const auto& s : statements) { + if (!s->isEmpty()) { + this->writeStatement(*s); + this->writeLine(); + } + } +} + +void GLSLCodeGenerator::writeBlock(const Block& b) { + this->writeLine("{"); + fIndentation++; + this->writeStatements(b.fStatements); + fIndentation--; + this->write("}"); +} + +void GLSLCodeGenerator::writeIfStatement(const IfStatement& stmt) { + this->write("if ("); + this->writeExpression(*stmt.fTest, kTopLevel_Precedence); + this->write(") "); + this->writeStatement(*stmt.fIfTrue); + if (stmt.fIfFalse) { + this->write(" else "); + this->writeStatement(*stmt.fIfFalse); + } +} + +void GLSLCodeGenerator::writeForStatement(const ForStatement& f) { + this->write("for ("); + if (f.fInitializer && !f.fInitializer->isEmpty()) { + this->writeStatement(*f.fInitializer); + } else { + this->write("; "); + } + if (f.fTest) { + if (fProgram.fSettings.fCaps->addAndTrueToLoopCondition()) { + std::unique_ptr and_true(new BinaryExpression( + -1, f.fTest->clone(), Token::LOGICALAND, + std::unique_ptr(new BoolLiteral(fContext, -1, + true)), + *fContext.fBool_Type)); + this->writeExpression(*and_true, kTopLevel_Precedence); + } else { + this->writeExpression(*f.fTest, kTopLevel_Precedence); + } + } + this->write("; "); + if (f.fNext) { + this->writeExpression(*f.fNext, kTopLevel_Precedence); + } + this->write(") "); + this->writeStatement(*f.fStatement); +} + +void GLSLCodeGenerator::writeWhileStatement(const WhileStatement& w) { + this->write("while ("); + this->writeExpression(*w.fTest, kTopLevel_Precedence); + this->write(") "); + this->writeStatement(*w.fStatement); +} + +void GLSLCodeGenerator::writeDoStatement(const DoStatement& d) { + if (!fProgram.fSettings.fCaps->rewriteDoWhileLoops()) { + this->write("do "); + this->writeStatement(*d.fStatement); + this->write(" while ("); + this->writeExpression(*d.fTest, kTopLevel_Precedence); + this->write(");"); + return; + } + + // Otherwise, do the do while loop workaround, to rewrite loops of the form: + // do { + // CODE; + // } while (CONDITION) + // + // to loops of the form + // bool temp = false; + // while (true) { + // if (temp) { + // if (!CONDITION) { + // break; + // } + // } + // temp = true; + // CODE; + // } + String tmpVar = "_tmpLoopSeenOnce" + to_string(fVarCount++); + this->write("bool "); + this->write(tmpVar); + this->writeLine(" = false;"); + this->writeLine("while (true) {"); + fIndentation++; + this->write("if ("); + this->write(tmpVar); + this->writeLine(") {"); + fIndentation++; + this->write("if (!"); + this->writeExpression(*d.fTest, kPrefix_Precedence); + this->writeLine(") {"); + fIndentation++; + this->writeLine("break;"); + fIndentation--; + this->writeLine("}"); + fIndentation--; + this->writeLine("}"); + this->write(tmpVar); + this->writeLine(" = true;"); + this->writeStatement(*d.fStatement); + this->writeLine(); + fIndentation--; + this->write("}"); +} + +void GLSLCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + this->write("switch ("); + this->writeExpression(*s.fValue, kTopLevel_Precedence); + this->writeLine(") {"); + fIndentation++; + for (const auto& c : s.fCases) { + if (c->fValue) { + this->write("case "); + this->writeExpression(*c->fValue, kTopLevel_Precedence); + this->writeLine(":"); + } else { + this->writeLine("default:"); + } + fIndentation++; + for (const auto& stmt : c->fStatements) { + this->writeStatement(*stmt); + this->writeLine(); + } + fIndentation--; + } + fIndentation--; + this->write("}"); +} + +void GLSLCodeGenerator::writeReturnStatement(const ReturnStatement& r) { + this->write("return"); + if (r.fExpression) { + this->write(" "); + this->writeExpression(*r.fExpression, kTopLevel_Precedence); + } + this->write(";"); +} + +void GLSLCodeGenerator::writeHeader() { + this->write(fProgram.fSettings.fCaps->versionDeclString()); + this->writeLine(); +} + +void GLSLCodeGenerator::writeProgramElement(const ProgramElement& e) { + switch (e.fKind) { + case ProgramElement::kExtension_Kind: + this->writeExtension(((Extension&) e).fName); + break; + case ProgramElement::kVar_Kind: { + VarDeclarations& decl = (VarDeclarations&) e; + if (decl.fVars.size() > 0) { + int builtin = ((VarDeclaration&) *decl.fVars[0]).fVar->fModifiers.fLayout.fBuiltin; + if (builtin == -1) { + // normal var + this->writeVarDeclarations(decl, true); + this->writeLine(); + } else if (builtin == SK_FRAGCOLOR_BUILTIN && + fProgram.fSettings.fCaps->mustDeclareFragmentShaderOutput() && + ((VarDeclaration&) *decl.fVars[0]).fVar->fWriteCount) { + if (fProgram.fSettings.fFragColorIsInOut) { + this->write("inout "); + } else { + this->write("out "); + } + if (usesPrecisionModifiers()) { + this->write("mediump "); + } + this->writeLine("vec4 sk_FragColor;"); + } + } + break; + } + case ProgramElement::kInterfaceBlock_Kind: + this->writeInterfaceBlock((InterfaceBlock&) e); + break; + case ProgramElement::kFunction_Kind: + this->writeFunction((FunctionDefinition&) e); + break; + case ProgramElement::kModifiers_Kind: { + const Modifiers& modifiers = ((ModifiersDeclaration&) e).fModifiers; + if (!fFoundGSInvocations && modifiers.fLayout.fInvocations >= 0) { + if (fProgram.fSettings.fCaps->gsInvocationsExtensionString()) { + this->writeExtension(fProgram.fSettings.fCaps->gsInvocationsExtensionString()); + } + fFoundGSInvocations = true; + } + this->writeModifiers(modifiers, true); + this->writeLine(";"); + break; + } + case ProgramElement::kEnum_Kind: + break; + default: + printf("%s\n", e.description().c_str()); + ABORT("unsupported program element"); + } +} + +void GLSLCodeGenerator::writeInputVars() { + if (fProgram.fInputs.fRTWidth) { + const char* precision = usesPrecisionModifiers() ? "highp " : ""; + fGlobals.writeText("uniform "); + fGlobals.writeText(precision); + fGlobals.writeText("float " SKSL_RTWIDTH_NAME ";\n"); + } + if (fProgram.fInputs.fRTHeight) { + const char* precision = usesPrecisionModifiers() ? "highp " : ""; + fGlobals.writeText("uniform "); + fGlobals.writeText(precision); + fGlobals.writeText("float " SKSL_RTHEIGHT_NAME ";\n"); + } +} + +bool GLSLCodeGenerator::generateCode() { + if (fProgramKind != Program::kPipelineStage_Kind) { + this->writeHeader(); + } + if (Program::kGeometry_Kind == fProgramKind && + fProgram.fSettings.fCaps->geometryShaderExtensionString()) { + this->writeExtension(fProgram.fSettings.fCaps->geometryShaderExtensionString()); + } + OutputStream* rawOut = fOut; + StringStream body; + fOut = &body; + for (const auto& e : fProgram) { + this->writeProgramElement(e); + } + fOut = rawOut; + + write_stringstream(fExtensions, *rawOut); + this->writeInputVars(); + write_stringstream(fGlobals, *rawOut); + + if (!fProgram.fSettings.fCaps->canUseFragCoord()) { + Layout layout; + switch (fProgram.fKind) { + case Program::kVertex_Kind: { + Modifiers modifiers(layout, Modifiers::kOut_Flag); + this->writeModifiers(modifiers, true); + if (this->usesPrecisionModifiers()) { + this->write("highp "); + } + this->write("vec4 sk_FragCoord_Workaround;\n"); + break; + } + case Program::kFragment_Kind: { + Modifiers modifiers(layout, Modifiers::kIn_Flag); + this->writeModifiers(modifiers, true); + if (this->usesPrecisionModifiers()) { + this->write("highp "); + } + this->write("vec4 sk_FragCoord_Workaround;\n"); + break; + } + default: + break; + } + } + + if (this->usesPrecisionModifiers()) { + this->writeLine("precision mediump float;"); + this->writeLine("precision mediump sampler2D;"); + if (fFoundExternalSamplerDecl && + !fProgram.fSettings.fCaps->noDefaultPrecisionForExternalSamplers()) { + this->writeLine("precision mediump samplerExternalOES;"); + } + if (fFoundRectSamplerDecl) { + this->writeLine("precision mediump sampler2DRect;"); + } + } + write_stringstream(fExtraFunctions, *rawOut); + write_stringstream(body, *rawOut); + return true; +} + +} diff --git a/gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.h new file mode 100644 index 0000000000..9977cf8dcf --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLGLSLCodeGenerator.h @@ -0,0 +1,266 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_GLSLCODEGENERATOR +#define SKSL_GLSLCODEGENERATOR + +#include +#include +#include + +#include "src/sksl/SkSLCodeGenerator.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBoolLiteral.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/ir/SkSLWhileStatement.h" + +namespace SkSL { + +#define kLast_Capability SpvCapabilityMultiViewport + +/** + * Converts a Program into GLSL code. + */ +class GLSLCodeGenerator : public CodeGenerator { +public: + enum Precedence { + kParentheses_Precedence = 1, + kPostfix_Precedence = 2, + kPrefix_Precedence = 3, + kMultiplicative_Precedence = 4, + kAdditive_Precedence = 5, + kShift_Precedence = 6, + kRelational_Precedence = 7, + kEquality_Precedence = 8, + kBitwiseAnd_Precedence = 9, + kBitwiseXor_Precedence = 10, + kBitwiseOr_Precedence = 11, + kLogicalAnd_Precedence = 12, + kLogicalXor_Precedence = 13, + kLogicalOr_Precedence = 14, + kTernary_Precedence = 15, + kAssignment_Precedence = 16, + kSequence_Precedence = 17, + kTopLevel_Precedence = kSequence_Precedence + }; + + GLSLCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + OutputStream* out) + : INHERITED(program, errors, out) + , fLineEnding("\n") + , fContext(*context) + , fProgramKind(program->fKind) {} + + bool generateCode() override; + +protected: + enum class SwizzleOrder { + MASK_FIRST, + CONSTANTS_FIRST + }; + + void write(const char* s); + + void writeLine(); + + void writeLine(const char* s); + + void write(const String& s); + + void write(StringFragment s); + + void writeLine(const String& s); + + virtual void writeHeader(); + + virtual bool usesPrecisionModifiers() const; + + virtual String getTypeName(const Type& type); + + void writeType(const Type& type); + + void writeExtension(const String& name); + + void writeExtension(const String& name, bool require); + + void writeInterfaceBlock(const InterfaceBlock& intf); + + void writeFunctionStart(const FunctionDeclaration& f); + + void writeFunctionDeclaration(const FunctionDeclaration& f); + + virtual void writeFunction(const FunctionDefinition& f); + + void writeLayout(const Layout& layout); + + void writeModifiers(const Modifiers& modifiers, bool globalContext); + + virtual void writeInputVars(); + + virtual void writeVarInitializer(const Variable& var, const Expression& value); + + const char* getTypePrecision(const Type& type); + + void writeTypePrecision(const Type& type); + + void writeVarDeclarations(const VarDeclarations& decl, bool global); + + void writeFragCoord(); + + virtual void writeVariableReference(const VariableReference& ref); + + void writeExpression(const Expression& expr, Precedence parentPrecedence); + + void writeIntrinsicCall(const FunctionCall& c); + + void writeMinAbsHack(Expression& absExpr, Expression& otherExpr); + + void writeDeterminantHack(const Expression& mat); + + void writeInverseHack(const Expression& mat); + + void writeTransposeHack(const Expression& mat); + + void writeInverseSqrtHack(const Expression& x); + + virtual void writeFunctionCall(const FunctionCall& c); + + void writeConstructor(const Constructor& c, Precedence parentPrecedence); + + virtual void writeFieldAccess(const FieldAccess& f); + + void writeConstantSwizzle(const Swizzle& swizzle, const String& constants); + + void writeSwizzleMask(const Swizzle& swizzle, const String& mask); + + void writeSwizzleConstructor(const Swizzle& swizzle, const String& constants, + const String& mask, SwizzleOrder order); + + void writeSwizzleConstructor(const Swizzle& swizzle, const String& constants, + const String& mask, const String& reswizzle); + virtual void writeSwizzle(const Swizzle& swizzle); + + static Precedence GetBinaryPrecedence(Token::Kind op); + + virtual void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); + void writeShortCircuitWorkaroundExpression(const BinaryExpression& b, + Precedence parentPrecedence); + + void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); + + virtual void writeIndexExpression(const IndexExpression& expr); + + void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence); + + void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence); + + void writeBoolLiteral(const BoolLiteral& b); + + virtual void writeIntLiteral(const IntLiteral& i); + + void writeFloatLiteral(const FloatLiteral& f); + + virtual void writeSetting(const Setting& s); + + void writeStatement(const Statement& s); + + void writeStatements(const std::vector>& statements); + + void writeBlock(const Block& b); + + virtual void writeIfStatement(const IfStatement& stmt); + + void writeForStatement(const ForStatement& f); + + void writeWhileStatement(const WhileStatement& w); + + void writeDoStatement(const DoStatement& d); + + virtual void writeSwitchStatement(const SwitchStatement& s); + + virtual void writeReturnStatement(const ReturnStatement& r); + + virtual void writeProgramElement(const ProgramElement& e); + + const char* fLineEnding; + const Context& fContext; + StringStream fExtensions; + StringStream fGlobals; + StringStream fExtraFunctions; + String fFunctionHeader; + Program::Kind fProgramKind; + int fVarCount = 0; + int fIndentation = 0; + bool fAtLineStart = false; + // Keeps track of which struct types we have written. Given that we are unlikely to ever write + // more than one or two structs per shader, a simple linear search will be faster than anything + // fancier. + std::vector fWrittenStructs; + std::set fWrittenIntrinsics; + // true if we have run into usages of dFdx / dFdy + bool fFoundDerivatives = false; + bool fFoundExternalSamplerDecl = false; + bool fFoundRectSamplerDecl = false; + bool fFoundGSInvocations = false; + bool fSetupFragPositionGlobal = false; + bool fSetupFragPositionLocal = false; + bool fSetupFragCoordWorkaround = false; + // if non-empty, replace all texture / texture2D / textureProj / etc. calls with this name + String fTextureFunctionOverride; + + // We map function names to function class so we can quickly deal with function calls that need + // extra processing + enum class FunctionClass { + kAbs, + kAtan, + kDeterminant, + kDFdx, + kDFdy, + kFwidth, + kFMA, + kFract, + kInverse, + kInverseSqrt, + kMin, + kPow, + kSaturate, + kTexture, + kTranspose + }; + static std::unordered_map* fFunctionClasses; + + typedef CodeGenerator INHERITED; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLHCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLHCodeGenerator.cpp new file mode 100644 index 0000000000..31cc890c41 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLHCodeGenerator.cpp @@ -0,0 +1,389 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLHCodeGenerator.h" + +#include "src/sksl/SkSLParser.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLEnum.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLSection.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +#include + +namespace SkSL { + +HCodeGenerator::HCodeGenerator(const Context* context, const Program* program, + ErrorReporter* errors, String name, OutputStream* out) +: INHERITED(program, errors, out) +, fContext(*context) +, fName(std::move(name)) +, fFullName(String::printf("Gr%s", fName.c_str())) +, fSectionAndParameterHelper(program, *errors) {} + +String HCodeGenerator::ParameterType(const Context& context, const Type& type, + const Layout& layout) { + Layout::CType ctype = ParameterCType(context, type, layout); + if (ctype != Layout::CType::kDefault) { + return Layout::CTypeToStr(ctype); + } + return type.name(); +} + +Layout::CType HCodeGenerator::ParameterCType(const Context& context, const Type& type, + const Layout& layout) { + if (layout.fCType != Layout::CType::kDefault) { + return layout.fCType; + } + if (type.kind() == Type::kNullable_Kind) { + return ParameterCType(context, type.componentType(), layout); + } else if (type == *context.fFloat_Type || type == *context.fHalf_Type) { + return Layout::CType::kFloat; + } else if (type == *context.fInt_Type || + type == *context.fShort_Type || + type == *context.fByte_Type) { + return Layout::CType::kInt32; + } else if (type == *context.fFloat2_Type || type == *context.fHalf2_Type) { + return Layout::CType::kSkPoint; + } else if (type == *context.fInt2_Type || + type == *context.fShort2_Type || + type == *context.fByte2_Type) { + return Layout::CType::kSkIPoint; + } else if (type == *context.fInt4_Type || + type == *context.fShort4_Type || + type == *context.fByte4_Type) { + return Layout::CType::kSkIRect; + } else if (type == *context.fFloat4_Type || type == *context.fHalf4_Type) { + return Layout::CType::kSkRect; + } else if (type == *context.fFloat3x3_Type || type == *context.fHalf3x3_Type) { + return Layout::CType::kSkMatrix; + } else if (type == *context.fFloat4x4_Type || type == *context.fHalf4x4_Type) { + return Layout::CType::kSkMatrix44; + } else if (type.kind() == Type::kSampler_Kind) { + return Layout::CType::kGrTextureProxy; + } else if (type == *context.fFragmentProcessor_Type) { + return Layout::CType::kGrFragmentProcessor; + } + return Layout::CType::kDefault; +} + +String HCodeGenerator::FieldType(const Context& context, const Type& type, + const Layout& layout) { + if (type.kind() == Type::kSampler_Kind) { + return "TextureSampler"; + } else if (type == *context.fFragmentProcessor_Type) { + // we don't store fragment processors in fields, they get registered via + // registerChildProcessor instead + SkASSERT(false); + return ""; + } + return ParameterType(context, type, layout); +} + +String HCodeGenerator::AccessType(const Context& context, const Type& type, + const Layout& layout) { + static const std::set primitiveTypes = { "int32_t", "float", "bool", "SkPMColor" }; + + String fieldType = FieldType(context, type, layout); + bool isPrimitive = primitiveTypes.find(fieldType) != primitiveTypes.end(); + if (isPrimitive) { + return fieldType; + } else { + return String::printf("const %s&", fieldType.c_str()); + } +} + +void HCodeGenerator::writef(const char* s, va_list va) { + static constexpr int BUFFER_SIZE = 1024; + va_list copy; + va_copy(copy, va); + char buffer[BUFFER_SIZE]; + int length = vsnprintf(buffer, BUFFER_SIZE, s, va); + if (length < BUFFER_SIZE) { + fOut->write(buffer, length); + } else { + std::unique_ptr heap(new char[length + 1]); + vsprintf(heap.get(), s, copy); + fOut->write(heap.get(), length); + } + va_end(copy); +} + +void HCodeGenerator::writef(const char* s, ...) { + va_list va; + va_start(va, s); + this->writef(s, va); + va_end(va); +} + +bool HCodeGenerator::writeSection(const char* name, const char* prefix) { + const Section* s = fSectionAndParameterHelper.getSection(name); + if (s) { + this->writef("%s%s", prefix, s->fText.c_str()); + return true; + } + return false; +} + +void HCodeGenerator::writeExtraConstructorParams(const char* separator) { + // super-simple parse, just assume the last token before a comma is the name of a parameter + // (which is true as long as there are no multi-parameter template types involved). Will replace + // this with something more robust if the need arises. + const Section* section = fSectionAndParameterHelper.getSection(CONSTRUCTOR_PARAMS_SECTION); + if (section) { + const char* s = section->fText.c_str(); + #define BUFFER_SIZE 64 + char lastIdentifier[BUFFER_SIZE]; + int lastIdentifierLength = 0; + bool foundBreak = false; + while (*s) { + char c = *s; + ++s; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '_') { + if (foundBreak) { + lastIdentifierLength = 0; + foundBreak = false; + } + SkASSERT(lastIdentifierLength < BUFFER_SIZE); + lastIdentifier[lastIdentifierLength] = c; + ++lastIdentifierLength; + } else { + foundBreak = true; + if (c == ',') { + SkASSERT(lastIdentifierLength < BUFFER_SIZE); + lastIdentifier[lastIdentifierLength] = 0; + this->writef("%s%s", separator, lastIdentifier); + separator = ", "; + } else if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { + lastIdentifierLength = 0; + } + } + } + if (lastIdentifierLength) { + SkASSERT(lastIdentifierLength < BUFFER_SIZE); + lastIdentifier[lastIdentifierLength] = 0; + this->writef("%s%s", separator, lastIdentifier); + } + } +} + +void HCodeGenerator::writeMake() { + const char* separator; + if (!this->writeSection(MAKE_SECTION)) { + this->writef(" static std::unique_ptr Make("); + separator = ""; + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + this->writef("%s%s %s", separator, ParameterType(fContext, param->fType, + param->fModifiers.fLayout).c_str(), + String(param->fName).c_str()); + separator = ", "; + } + this->writeSection(CONSTRUCTOR_PARAMS_SECTION, separator); + this->writef(") {\n" + " return std::unique_ptr(new %s(", + fFullName.c_str()); + separator = ""; + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + this->writef("%sstd::move(%s)", separator, String(param->fName).c_str()); + } else { + this->writef("%s%s", separator, String(param->fName).c_str()); + } + separator = ", "; + } + this->writeExtraConstructorParams(separator); + this->writef("));\n" + " }\n"); + } +} + +void HCodeGenerator::failOnSection(const char* section, const char* msg) { + std::vector s = fSectionAndParameterHelper.getSections(section); + if (s.size()) { + fErrors.error(s[0]->fOffset, String("@") + section + " " + msg); + } +} + +void HCodeGenerator::writeConstructor() { + if (this->writeSection(CONSTRUCTOR_SECTION)) { + const char* msg = "may not be present when constructor is overridden"; + this->failOnSection(CONSTRUCTOR_CODE_SECTION, msg); + this->failOnSection(CONSTRUCTOR_PARAMS_SECTION, msg); + this->failOnSection(INITIALIZERS_SECTION, msg); + this->failOnSection(OPTIMIZATION_FLAGS_SECTION, msg); + return; + } + this->writef(" %s(", fFullName.c_str()); + const char* separator = ""; + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + this->writef("%s%s %s", separator, ParameterType(fContext, param->fType, + param->fModifiers.fLayout).c_str(), + String(param->fName).c_str()); + separator = ", "; + } + this->writeSection(CONSTRUCTOR_PARAMS_SECTION, separator); + this->writef(")\n" + " : INHERITED(k%s_ClassID", fFullName.c_str()); + if (!this->writeSection(OPTIMIZATION_FLAGS_SECTION, ", (OptimizationFlags) ")) { + this->writef(", kNone_OptimizationFlags"); + } + this->writef(")"); + this->writeSection(INITIALIZERS_SECTION, "\n , "); + const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION); + for (size_t i = 0; i < transforms.size(); ++i) { + const Section& s = *transforms[i]; + String field = CoordTransformName(s.fArgument.c_str(), i); + if (s.fArgument.size()) { + this->writef("\n , %s(%s, %s.get())", field.c_str(), s.fText.c_str(), + FieldName(s.fArgument.c_str()).c_str()); + } + else { + this->writef("\n , %s(%s)", field.c_str(), s.fText.c_str()); + } + } + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + String nameString(param->fName); + const char* name = nameString.c_str(); + const Type& type = param->fType.nonnullable(); + if (type.kind() == Type::kSampler_Kind) { + this->writef("\n , %s(std::move(%s)", FieldName(name).c_str(), name); + for (const Section* s : fSectionAndParameterHelper.getSections( + SAMPLER_PARAMS_SECTION)) { + if (s->fArgument == name) { + this->writef(", %s", s->fText.c_str()); + } + } + this->writef(")"); + } else if (type == *fContext.fFragmentProcessor_Type) { + // do nothing + } else { + this->writef("\n , %s(%s)", FieldName(name).c_str(), name); + } + } + this->writef(" {\n"); + this->writeSection(CONSTRUCTOR_CODE_SECTION); + int samplerCount = 0; + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (param->fType.kind() == Type::kSampler_Kind) { + ++samplerCount; + } else if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + if (param->fType.kind() == Type::kNullable_Kind) { + this->writef(" if (%s) {\n", String(param->fName).c_str()); + } else { + this->writef(" SkASSERT(%s);", String(param->fName).c_str()); + } + this->writef(" %s_index = this->numChildProcessors();", + FieldName(String(param->fName).c_str()).c_str()); + if (fSectionAndParameterHelper.hasCoordOverrides(*param)) { + this->writef(" %s->setComputeLocalCoordsInVertexShader(false);", + String(param->fName).c_str()); + } + this->writef(" this->registerChildProcessor(std::move(%s));", + String(param->fName).c_str()); + if (param->fType.kind() == Type::kNullable_Kind) { + this->writef(" }"); + } + } + } + if (samplerCount) { + this->writef(" this->setTextureSamplerCnt(%d);", samplerCount); + } + for (size_t i = 0; i < transforms.size(); ++i) { + const Section& s = *transforms[i]; + String field = CoordTransformName(s.fArgument.c_str(), i); + this->writef(" this->addCoordTransform(&%s);\n", field.c_str()); + } + this->writef(" }\n"); +} + +void HCodeGenerator::writeFields() { + this->writeSection(FIELDS_SECTION); + const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION); + for (size_t i = 0; i < transforms.size(); ++i) { + const Section& s = *transforms[i]; + this->writef(" GrCoordTransform %s;\n", + CoordTransformName(s.fArgument.c_str(), i).c_str()); + } + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + String name = FieldName(String(param->fName).c_str()); + if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) { + this->writef(" int %s_index = -1;\n", name.c_str()); + } else { + this->writef(" %s %s;\n", FieldType(fContext, param->fType, + param->fModifiers.fLayout).c_str(), + name.c_str()); + } + } +} + +String HCodeGenerator::GetHeader(const Program& program, ErrorReporter& errors) { + SymbolTable types(&errors); + Parser parser(program.fSource->c_str(), program.fSource->length(), types, errors); + for (;;) { + Token header = parser.nextRawToken(); + switch (header.fKind) { + case Token::WHITESPACE: + break; + case Token::BLOCK_COMMENT: + return String(program.fSource->c_str() + header.fOffset, header.fLength); + default: + return ""; + } + } +} + +bool HCodeGenerator::generateCode() { + this->writef("%s\n", GetHeader(fProgram, fErrors).c_str()); + this->writef(kFragmentProcessorHeader, fFullName.c_str()); + this->writef("#ifndef %s_DEFINED\n" + "#define %s_DEFINED\n", + fFullName.c_str(), + fFullName.c_str()); + this->writef("#include \"include/core/SkTypes.h\"\n"); + this->writeSection(HEADER_SECTION); + this->writef("\n" + "#include \"src/gpu/GrCoordTransform.h\"\n" + "#include \"src/gpu/GrFragmentProcessor.h\"\n"); + this->writef("class %s : public GrFragmentProcessor {\n" + "public:\n", + fFullName.c_str()); + for (const auto& p : fProgram) { + if (ProgramElement::kEnum_Kind == p.fKind && !((Enum&) p).fBuiltin) { + this->writef("%s\n", p.description().c_str()); + } + } + this->writeSection(CLASS_SECTION); + this->writeMake(); + this->writef(" %s(const %s& src);\n" + " std::unique_ptr clone() const override;\n" + " const char* name() const override { return \"%s\"; }\n", + fFullName.c_str(), fFullName.c_str(), fName.c_str()); + this->writeFields(); + this->writef("private:\n"); + this->writeConstructor(); + this->writef(" GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;\n" + " void onGetGLSLProcessorKey(const GrShaderCaps&," + "GrProcessorKeyBuilder*) const override;\n" + " bool onIsEqual(const GrFragmentProcessor&) const override;\n"); + for (const auto& param : fSectionAndParameterHelper.getParameters()) { + if (param->fType.kind() == Type::kSampler_Kind) { + this->writef(" const TextureSampler& onTextureSampler(int) const override;"); + break; + } + } + this->writef(" GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n"); + this->writef(" typedef GrFragmentProcessor INHERITED;\n" + "};\n"); + this->writeSection(HEADER_END_SECTION); + this->writef("#endif\n"); + return 0 == fErrors.errorCount(); +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLHCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLHCodeGenerator.h new file mode 100644 index 0000000000..03ab038a6d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLHCodeGenerator.h @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_HCODEGENERATOR +#define SKSL_HCODEGENERATOR + +#include "src/sksl/SkSLCodeGenerator.h" +#include "src/sksl/SkSLSectionAndParameterHelper.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include + +constexpr const char* kFragmentProcessorHeader = +R"( +/************************************************************************************************** + *** This file was autogenerated from %s.fp; do not modify. + **************************************************************************************************/ +)"; + +namespace SkSL { + +class HCodeGenerator : public CodeGenerator { +public: + HCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + String name, OutputStream* out); + + bool generateCode() override; + + static String ParameterType(const Context& context, const Type& type, const Layout& layout); + + static Layout::CType ParameterCType(const Context& context, const Type& type, + const Layout& layout); + + static String FieldType(const Context& context, const Type& type, const Layout& layout); + + // Either the field type, or a const reference of the field type if the field type is complex. + static String AccessType(const Context& context, const Type& type, const Layout& layout); + + static String FieldName(const char* varName) { + return String(varName); + } + + static String CoordTransformName(const String& arg, int index) { + if (arg.size()) { + return HCodeGenerator::FieldName(arg.c_str()) + "CoordTransform"; + } + return "fCoordTransform" + to_string(index); + } + + static String GetHeader(const Program& program, ErrorReporter& errors); + +private: + void writef(const char* s, va_list va) SKSL_PRINTF_LIKE(2, 0); + + void writef(const char* s, ...) SKSL_PRINTF_LIKE(2, 3); + + bool writeSection(const char* name, const char* prefix = ""); + + // given a @constructorParams section of e.g. 'int x, float y', writes out "x, y". + // Writes nothing (not even the separator) if there is no @constructorParams section. + void writeExtraConstructorParams(const char* separator); + + void writeMake(); + + void writeConstructor(); + + void writeFields(); + + void failOnSection(const char* section, const char* msg); + + const Context& fContext; + String fName; + String fFullName; + SectionAndParameterHelper fSectionAndParameterHelper; + + typedef CodeGenerator INHERITED; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLIRGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLIRGenerator.cpp new file mode 100644 index 0000000000..9b71c17ffd --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLIRGenerator.cpp @@ -0,0 +1,2493 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLIRGenerator.h" + +#include "limits.h" +#include + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLParser.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBoolLiteral.h" +#include "src/sksl/ir/SkSLBreakStatement.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLContinueStatement.h" +#include "src/sksl/ir/SkSLDiscardStatement.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLEnum.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExternalFunctionCall.h" +#include "src/sksl/ir/SkSLExternalValueReference.h" +#include "src/sksl/ir/SkSLField.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLFunctionReference.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLLayout.h" +#include "src/sksl/ir/SkSLNullLiteral.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLUnresolvedFunction.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/ir/SkSLWhileStatement.h" + +namespace SkSL { + +class AutoSymbolTable { +public: + AutoSymbolTable(IRGenerator* ir) + : fIR(ir) + , fPrevious(fIR->fSymbolTable) { + fIR->pushSymbolTable(); + } + + ~AutoSymbolTable() { + fIR->popSymbolTable(); + SkASSERT(fPrevious == fIR->fSymbolTable); + } + + IRGenerator* fIR; + std::shared_ptr fPrevious; +}; + +class AutoLoopLevel { +public: + AutoLoopLevel(IRGenerator* ir) + : fIR(ir) { + fIR->fLoopLevel++; + } + + ~AutoLoopLevel() { + fIR->fLoopLevel--; + } + + IRGenerator* fIR; +}; + +class AutoSwitchLevel { +public: + AutoSwitchLevel(IRGenerator* ir) + : fIR(ir) { + fIR->fSwitchLevel++; + } + + ~AutoSwitchLevel() { + fIR->fSwitchLevel--; + } + + IRGenerator* fIR; +}; + +IRGenerator::IRGenerator(const Context* context, std::shared_ptr symbolTable, + ErrorReporter& errorReporter) +: fContext(*context) +, fCurrentFunction(nullptr) +, fRootSymbolTable(symbolTable) +, fSymbolTable(symbolTable) +, fLoopLevel(0) +, fSwitchLevel(0) +, fTmpCount(0) +, fErrors(errorReporter) {} + +void IRGenerator::pushSymbolTable() { + fSymbolTable.reset(new SymbolTable(std::move(fSymbolTable), &fErrors)); +} + +void IRGenerator::popSymbolTable() { + fSymbolTable = fSymbolTable->fParent; +} + +static void fill_caps(const SKSL_CAPS_CLASS& caps, + std::unordered_map* capsMap) { +#define CAP(name) \ + capsMap->insert(std::make_pair(String(#name), Program::Settings::Value(caps.name()))) + CAP(fbFetchSupport); + CAP(fbFetchNeedsCustomOutput); + CAP(flatInterpolationSupport); + CAP(noperspectiveInterpolationSupport); + CAP(sampleVariablesSupport); + CAP(externalTextureSupport); + CAP(mustEnableAdvBlendEqs); + CAP(mustEnableSpecificAdvBlendEqs); + CAP(mustDeclareFragmentShaderOutput); + CAP(mustDoOpBetweenFloorAndAbs); + CAP(atan2ImplementedAsAtanYOverX); + CAP(canUseAnyFunctionInShader); + CAP(floatIs32Bits); + CAP(integerSupport); +#undef CAP +} + +void IRGenerator::start(const Program::Settings* settings, + std::vector>* inherited) { + if (fStarted) { + this->popSymbolTable(); + } + fSettings = settings; + fCapsMap.clear(); + if (settings->fCaps) { + fill_caps(*settings->fCaps, &fCapsMap); + } else { + fCapsMap.insert(std::make_pair(String("integerSupport"), + Program::Settings::Value(true))); + } + this->pushSymbolTable(); + fInvocations = -1; + fInputs.reset(); + fSkPerVertex = nullptr; + fRTAdjust = nullptr; + fRTAdjustInterfaceBlock = nullptr; + if (inherited) { + for (const auto& e : *inherited) { + if (e->fKind == ProgramElement::kInterfaceBlock_Kind) { + InterfaceBlock& intf = (InterfaceBlock&) *e; + if (intf.fVariable.fName == Compiler::PERVERTEX_NAME) { + SkASSERT(!fSkPerVertex); + fSkPerVertex = &intf.fVariable; + } + } + } + } +} + +std::unique_ptr IRGenerator::convertExtension(int offset, StringFragment name) { + return std::unique_ptr(new Extension(offset, name)); +} + +void IRGenerator::finish() { + this->popSymbolTable(); + fSettings = nullptr; +} + +std::unique_ptr IRGenerator::convertStatement(const ASTNode& statement) { + switch (statement.fKind) { + case ASTNode::Kind::kBlock: + return this->convertBlock(statement); + case ASTNode::Kind::kVarDeclarations: + return this->convertVarDeclarationStatement(statement); + case ASTNode::Kind::kIf: + return this->convertIf(statement); + case ASTNode::Kind::kFor: + return this->convertFor(statement); + case ASTNode::Kind::kWhile: + return this->convertWhile(statement); + case ASTNode::Kind::kDo: + return this->convertDo(statement); + case ASTNode::Kind::kSwitch: + return this->convertSwitch(statement); + case ASTNode::Kind::kReturn: + return this->convertReturn(statement); + case ASTNode::Kind::kBreak: + return this->convertBreak(statement); + case ASTNode::Kind::kContinue: + return this->convertContinue(statement); + case ASTNode::Kind::kDiscard: + return this->convertDiscard(statement); + default: + // it's an expression + std::unique_ptr result = this->convertExpressionStatement(statement); + if (fRTAdjust && Program::kGeometry_Kind == fKind) { + SkASSERT(result->fKind == Statement::kExpression_Kind); + Expression& expr = *((ExpressionStatement&) *result).fExpression; + if (expr.fKind == Expression::kFunctionCall_Kind) { + FunctionCall& fc = (FunctionCall&) expr; + if (fc.fFunction.fBuiltin && fc.fFunction.fName == "EmitVertex") { + std::vector> statements; + statements.push_back(getNormalizeSkPositionCode()); + statements.push_back(std::move(result)); + return std::unique_ptr(new Block(statement.fOffset, + std::move(statements), + fSymbolTable)); + } + } + } + return result; + } +} + +std::unique_ptr IRGenerator::convertBlock(const ASTNode& block) { + SkASSERT(block.fKind == ASTNode::Kind::kBlock); + AutoSymbolTable table(this); + std::vector> statements; + for (const auto& child : block) { + std::unique_ptr statement = this->convertStatement(child); + if (!statement) { + return nullptr; + } + statements.push_back(std::move(statement)); + } + return std::unique_ptr(new Block(block.fOffset, std::move(statements), fSymbolTable)); +} + +std::unique_ptr IRGenerator::convertVarDeclarationStatement(const ASTNode& s) { + SkASSERT(s.fKind == ASTNode::Kind::kVarDeclarations); + auto decl = this->convertVarDeclarations(s, Variable::kLocal_Storage); + if (!decl) { + return nullptr; + } + return std::unique_ptr(new VarDeclarationsStatement(std::move(decl))); +} + +std::unique_ptr IRGenerator::convertVarDeclarations(const ASTNode& decls, + Variable::Storage storage) { + SkASSERT(decls.fKind == ASTNode::Kind::kVarDeclarations); + auto iter = decls.begin(); + const Modifiers& modifiers = iter++->getModifiers(); + const ASTNode& rawType = *(iter++); + std::vector> variables; + const Type* baseType = this->convertType(rawType); + if (!baseType) { + return nullptr; + } + if (fKind != Program::kFragmentProcessor_Kind && + (modifiers.fFlags & Modifiers::kIn_Flag) && + baseType->kind() == Type::Kind::kMatrix_Kind) { + fErrors.error(decls.fOffset, "'in' variables may not have matrix type"); + } + if (modifiers.fLayout.fWhen.fLength && fKind != Program::kFragmentProcessor_Kind && + fKind != Program::kPipelineStage_Kind) { + fErrors.error(decls.fOffset, "'when' is only permitted within fragment processors"); + } + if (modifiers.fLayout.fKey) { + if (fKind != Program::kFragmentProcessor_Kind && fKind != Program::kPipelineStage_Kind) { + fErrors.error(decls.fOffset, "'key' is only permitted within fragment processors"); + } + if ((modifiers.fFlags & Modifiers::kUniform_Flag) != 0) { + fErrors.error(decls.fOffset, "'key' is not permitted on 'uniform' variables"); + } + } + for (; iter != decls.end(); ++iter) { + const ASTNode& varDecl = *iter; + if (modifiers.fLayout.fLocation == 0 && modifiers.fLayout.fIndex == 0 && + (modifiers.fFlags & Modifiers::kOut_Flag) && fKind == Program::kFragment_Kind && + varDecl.getVarData().fName != "sk_FragColor") { + fErrors.error(varDecl.fOffset, + "out location=0, index=0 is reserved for sk_FragColor"); + } + const ASTNode::VarData& varData = varDecl.getVarData(); + const Type* type = baseType; + std::vector> sizes; + auto iter = varDecl.begin(); + for (size_t i = 0; i < varData.fSizeCount; ++i, ++iter) { + const ASTNode& rawSize = *iter; + if (rawSize) { + auto size = this->coerce(this->convertExpression(rawSize), *fContext.fInt_Type); + if (!size) { + return nullptr; + } + String name(type->fName); + int64_t count; + if (size->fKind == Expression::kIntLiteral_Kind) { + count = ((IntLiteral&) *size).fValue; + if (count <= 0) { + fErrors.error(size->fOffset, "array size must be positive"); + return nullptr; + } + name += "[" + to_string(count) + "]"; + } else { + fErrors.error(size->fOffset, "array size must be specified"); + return nullptr; + } + type = (Type*) fSymbolTable->takeOwnership( + std::unique_ptr(new Type(name, + Type::kArray_Kind, + *type, + (int) count))); + sizes.push_back(std::move(size)); + } else { + type = (Type*) fSymbolTable->takeOwnership( + std::unique_ptr(new Type(type->name() + "[]", + Type::kArray_Kind, + *type, + -1))); + sizes.push_back(nullptr); + } + } + auto var = std::unique_ptr(new Variable(varDecl.fOffset, modifiers, + varData.fName, *type, storage)); + if (var->fName == Compiler::RTADJUST_NAME) { + SkASSERT(!fRTAdjust); + SkASSERT(var->fType == *fContext.fFloat4_Type); + fRTAdjust = var.get(); + } + std::unique_ptr value; + if (iter != varDecl.end()) { + value = this->convertExpression(*iter); + if (!value) { + return nullptr; + } + value = this->coerce(std::move(value), *type); + if (!value) { + return nullptr; + } + var->fWriteCount = 1; + var->fInitialValue = value.get(); + } + if (storage == Variable::kGlobal_Storage && var->fName == "sk_FragColor" && + (*fSymbolTable)[var->fName]) { + // already defined, ignore + } else if (storage == Variable::kGlobal_Storage && (*fSymbolTable)[var->fName] && + (*fSymbolTable)[var->fName]->fKind == Symbol::kVariable_Kind && + ((Variable*) (*fSymbolTable)[var->fName])->fModifiers.fLayout.fBuiltin >= 0) { + // already defined, just update the modifiers + Variable* old = (Variable*) (*fSymbolTable)[var->fName]; + old->fModifiers = var->fModifiers; + } else { + variables.emplace_back(new VarDeclaration(var.get(), std::move(sizes), + std::move(value))); + StringFragment name = var->fName; + fSymbolTable->add(name, std::move(var)); + } + } + return std::unique_ptr(new VarDeclarations(decls.fOffset, + baseType, + std::move(variables))); +} + +std::unique_ptr IRGenerator::convertModifiersDeclaration(const ASTNode& m) { + SkASSERT(m.fKind == ASTNode::Kind::kModifiers); + Modifiers modifiers = m.getModifiers(); + if (modifiers.fLayout.fInvocations != -1) { + if (fKind != Program::kGeometry_Kind) { + fErrors.error(m.fOffset, "'invocations' is only legal in geometry shaders"); + return nullptr; + } + fInvocations = modifiers.fLayout.fInvocations; + if (fSettings->fCaps && !fSettings->fCaps->gsInvocationsSupport()) { + modifiers.fLayout.fInvocations = -1; + Variable* invocationId = (Variable*) (*fSymbolTable)["sk_InvocationID"]; + SkASSERT(invocationId); + invocationId->fModifiers.fFlags = 0; + invocationId->fModifiers.fLayout.fBuiltin = -1; + if (modifiers.fLayout.description() == "") { + return nullptr; + } + } + } + if (modifiers.fLayout.fMaxVertices != -1 && fInvocations > 0 && fSettings->fCaps && + !fSettings->fCaps->gsInvocationsSupport()) { + modifiers.fLayout.fMaxVertices *= fInvocations; + } + return std::unique_ptr(new ModifiersDeclaration(modifiers)); +} + +std::unique_ptr IRGenerator::convertIf(const ASTNode& n) { + SkASSERT(n.fKind == ASTNode::Kind::kIf); + auto iter = n.begin(); + std::unique_ptr test = this->coerce(this->convertExpression(*(iter++)), + *fContext.fBool_Type); + if (!test) { + return nullptr; + } + std::unique_ptr ifTrue = this->convertStatement(*(iter++)); + if (!ifTrue) { + return nullptr; + } + std::unique_ptr ifFalse; + if (iter != n.end()) { + ifFalse = this->convertStatement(*(iter++)); + if (!ifFalse) { + return nullptr; + } + } + if (test->fKind == Expression::kBoolLiteral_Kind) { + // static boolean value, fold down to a single branch + if (((BoolLiteral&) *test).fValue) { + return ifTrue; + } else if (ifFalse) { + return ifFalse; + } else { + // False & no else clause. Not an error, so don't return null! + std::vector> empty; + return std::unique_ptr(new Block(n.fOffset, std::move(empty), + fSymbolTable)); + } + } + return std::unique_ptr(new IfStatement(n.fOffset, n.getBool(), std::move(test), + std::move(ifTrue), std::move(ifFalse))); +} + +std::unique_ptr IRGenerator::convertFor(const ASTNode& f) { + SkASSERT(f.fKind == ASTNode::Kind::kFor); + AutoLoopLevel level(this); + AutoSymbolTable table(this); + std::unique_ptr initializer; + auto iter = f.begin(); + if (*iter) { + initializer = this->convertStatement(*iter); + if (!initializer) { + return nullptr; + } + } + ++iter; + std::unique_ptr test; + if (*iter) { + test = this->coerce(this->convertExpression(*iter), *fContext.fBool_Type); + if (!test) { + return nullptr; + } + } + ++iter; + std::unique_ptr next; + if (*iter) { + next = this->convertExpression(*iter); + if (!next) { + return nullptr; + } + this->checkValid(*next); + } + ++iter; + std::unique_ptr statement = this->convertStatement(*iter); + if (!statement) { + return nullptr; + } + return std::unique_ptr(new ForStatement(f.fOffset, std::move(initializer), + std::move(test), std::move(next), + std::move(statement), fSymbolTable)); +} + +std::unique_ptr IRGenerator::convertWhile(const ASTNode& w) { + SkASSERT(w.fKind == ASTNode::Kind::kWhile); + AutoLoopLevel level(this); + auto iter = w.begin(); + std::unique_ptr test = this->coerce(this->convertExpression(*(iter++)), + *fContext.fBool_Type); + if (!test) { + return nullptr; + } + std::unique_ptr statement = this->convertStatement(*(iter++)); + if (!statement) { + return nullptr; + } + return std::unique_ptr(new WhileStatement(w.fOffset, std::move(test), + std::move(statement))); +} + +std::unique_ptr IRGenerator::convertDo(const ASTNode& d) { + SkASSERT(d.fKind == ASTNode::Kind::kDo); + AutoLoopLevel level(this); + auto iter = d.begin(); + std::unique_ptr statement = this->convertStatement(*(iter++)); + if (!statement) { + return nullptr; + } + std::unique_ptr test = this->coerce(this->convertExpression(*(iter++)), + *fContext.fBool_Type); + if (!test) { + return nullptr; + } + return std::unique_ptr(new DoStatement(d.fOffset, std::move(statement), + std::move(test))); +} + +std::unique_ptr IRGenerator::convertSwitch(const ASTNode& s) { + SkASSERT(s.fKind == ASTNode::Kind::kSwitch); + AutoSwitchLevel level(this); + auto iter = s.begin(); + std::unique_ptr value = this->convertExpression(*(iter++)); + if (!value) { + return nullptr; + } + if (value->fType != *fContext.fUInt_Type && value->fType.kind() != Type::kEnum_Kind) { + value = this->coerce(std::move(value), *fContext.fInt_Type); + if (!value) { + return nullptr; + } + } + AutoSymbolTable table(this); + std::unordered_set caseValues; + std::vector> cases; + for (; iter != s.end(); ++iter) { + const ASTNode& c = *iter; + SkASSERT(c.fKind == ASTNode::Kind::kSwitchCase); + std::unique_ptr caseValue; + auto childIter = c.begin(); + if (*childIter) { + caseValue = this->convertExpression(*childIter); + if (!caseValue) { + return nullptr; + } + caseValue = this->coerce(std::move(caseValue), value->fType); + if (!caseValue) { + return nullptr; + } + if (!caseValue->isConstant()) { + fErrors.error(caseValue->fOffset, "case value must be a constant"); + return nullptr; + } + int64_t v; + this->getConstantInt(*caseValue, &v); + if (caseValues.find(v) != caseValues.end()) { + fErrors.error(caseValue->fOffset, "duplicate case value"); + } + caseValues.insert(v); + } + ++childIter; + std::vector> statements; + for (; childIter != c.end(); ++childIter) { + std::unique_ptr converted = this->convertStatement(*childIter); + if (!converted) { + return nullptr; + } + statements.push_back(std::move(converted)); + } + cases.emplace_back(new SwitchCase(c.fOffset, std::move(caseValue), + std::move(statements))); + } + return std::unique_ptr(new SwitchStatement(s.fOffset, s.getBool(), + std::move(value), std::move(cases), + fSymbolTable)); +} + +std::unique_ptr IRGenerator::convertExpressionStatement(const ASTNode& s) { + std::unique_ptr e = this->convertExpression(s); + if (!e) { + return nullptr; + } + this->checkValid(*e); + return std::unique_ptr(new ExpressionStatement(std::move(e))); +} + +std::unique_ptr IRGenerator::convertReturn(const ASTNode& r) { + SkASSERT(r.fKind == ASTNode::Kind::kReturn); + SkASSERT(fCurrentFunction); + // early returns from a vertex main function will bypass the sk_Position normalization, so + // SkASSERT that we aren't doing that. It is of course possible to fix this by adding a + // normalization before each return, but it will probably never actually be necessary. + SkASSERT(Program::kVertex_Kind != fKind || !fRTAdjust || "main" != fCurrentFunction->fName); + if (r.begin() != r.end()) { + std::unique_ptr result = this->convertExpression(*r.begin()); + if (!result) { + return nullptr; + } + if (fCurrentFunction->fReturnType == *fContext.fVoid_Type) { + fErrors.error(result->fOffset, "may not return a value from a void function"); + } else { + result = this->coerce(std::move(result), fCurrentFunction->fReturnType); + if (!result) { + return nullptr; + } + } + return std::unique_ptr(new ReturnStatement(std::move(result))); + } else { + if (fCurrentFunction->fReturnType != *fContext.fVoid_Type) { + fErrors.error(r.fOffset, "expected function to return '" + + fCurrentFunction->fReturnType.description() + "'"); + } + return std::unique_ptr(new ReturnStatement(r.fOffset)); + } +} + +std::unique_ptr IRGenerator::convertBreak(const ASTNode& b) { + SkASSERT(b.fKind == ASTNode::Kind::kBreak); + if (fLoopLevel > 0 || fSwitchLevel > 0) { + return std::unique_ptr(new BreakStatement(b.fOffset)); + } else { + fErrors.error(b.fOffset, "break statement must be inside a loop or switch"); + return nullptr; + } +} + +std::unique_ptr IRGenerator::convertContinue(const ASTNode& c) { + SkASSERT(c.fKind == ASTNode::Kind::kContinue); + if (fLoopLevel > 0) { + return std::unique_ptr(new ContinueStatement(c.fOffset)); + } else { + fErrors.error(c.fOffset, "continue statement must be inside a loop"); + return nullptr; + } +} + +std::unique_ptr IRGenerator::convertDiscard(const ASTNode& d) { + SkASSERT(d.fKind == ASTNode::Kind::kDiscard); + return std::unique_ptr(new DiscardStatement(d.fOffset)); +} + +std::unique_ptr IRGenerator::applyInvocationIDWorkaround(std::unique_ptr main) { + Layout invokeLayout; + Modifiers invokeModifiers(invokeLayout, Modifiers::kHasSideEffects_Flag); + FunctionDeclaration* invokeDecl = new FunctionDeclaration(-1, + invokeModifiers, + "_invoke", + std::vector(), + *fContext.fVoid_Type); + fProgramElements->push_back(std::unique_ptr( + new FunctionDefinition(-1, *invokeDecl, std::move(main)))); + fSymbolTable->add(invokeDecl->fName, std::unique_ptr(invokeDecl)); + + std::vector> variables; + Variable* loopIdx = (Variable*) (*fSymbolTable)["sk_InvocationID"]; + SkASSERT(loopIdx); + std::unique_ptr test(new BinaryExpression(-1, + std::unique_ptr(new VariableReference(-1, *loopIdx)), + Token::LT, + std::unique_ptr(new IntLiteral(fContext, -1, fInvocations)), + *fContext.fBool_Type)); + std::unique_ptr next(new PostfixExpression( + std::unique_ptr( + new VariableReference(-1, + *loopIdx, + VariableReference::kReadWrite_RefKind)), + Token::PLUSPLUS)); + ASTNode endPrimitiveID(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier, "EndPrimitive"); + std::unique_ptr endPrimitive = this->convertExpression(endPrimitiveID); + SkASSERT(endPrimitive); + + std::vector> loopBody; + std::vector> invokeArgs; + loopBody.push_back(std::unique_ptr(new ExpressionStatement( + this->call(-1, + *invokeDecl, + std::vector>())))); + loopBody.push_back(std::unique_ptr(new ExpressionStatement( + this->call(-1, + std::move(endPrimitive), + std::vector>())))); + std::unique_ptr assignment(new BinaryExpression(-1, + std::unique_ptr(new VariableReference(-1, *loopIdx)), + Token::EQ, + std::unique_ptr(new IntLiteral(fContext, -1, 0)), + *fContext.fInt_Type)); + std::unique_ptr initializer(new ExpressionStatement(std::move(assignment))); + std::unique_ptr loop = std::unique_ptr( + new ForStatement(-1, + std::move(initializer), + std::move(test), + std::move(next), + std::unique_ptr(new Block(-1, std::move(loopBody))), + fSymbolTable)); + std::vector> children; + children.push_back(std::move(loop)); + return std::unique_ptr(new Block(-1, std::move(children))); +} + +std::unique_ptr IRGenerator::getNormalizeSkPositionCode() { + // sk_Position = float4(sk_Position.xy * rtAdjust.xz + sk_Position.ww * rtAdjust.yw, + // 0, + // sk_Position.w); + SkASSERT(fSkPerVertex && fRTAdjust); + #define REF(var) std::unique_ptr(\ + new VariableReference(-1, *var, VariableReference::kRead_RefKind)) + #define FIELD(var, idx) std::unique_ptr(\ + new FieldAccess(REF(var), idx, FieldAccess::kAnonymousInterfaceBlock_OwnerKind)) + #define POS std::unique_ptr(new FieldAccess(REF(fSkPerVertex), 0, \ + FieldAccess::kAnonymousInterfaceBlock_OwnerKind)) + #define ADJUST (fRTAdjustInterfaceBlock ? \ + FIELD(fRTAdjustInterfaceBlock, fRTAdjustFieldIndex) : \ + REF(fRTAdjust)) + #define SWIZZLE(expr, ...) std::unique_ptr(new Swizzle(fContext, expr, \ + { __VA_ARGS__ })) + #define OP(left, op, right) std::unique_ptr( \ + new BinaryExpression(-1, left, op, right, \ + *fContext.fFloat2_Type)) + std::vector> children; + children.push_back(OP(OP(SWIZZLE(POS, 0, 1), Token::STAR, SWIZZLE(ADJUST, 0, 2)), + Token::PLUS, + OP(SWIZZLE(POS, 3, 3), Token::STAR, SWIZZLE(ADJUST, 1, 3)))); + children.push_back(std::unique_ptr(new FloatLiteral(fContext, -1, 0.0))); + children.push_back(SWIZZLE(POS, 3)); + std::unique_ptr result = OP(POS, Token::EQ, + std::unique_ptr(new Constructor(-1, + *fContext.fFloat4_Type, + std::move(children)))); + return std::unique_ptr(new ExpressionStatement(std::move(result))); +} + +void IRGenerator::convertFunction(const ASTNode& f) { + auto iter = f.begin(); + const Type* returnType = this->convertType(*(iter++)); + if (!returnType) { + return; + } + const ASTNode::FunctionData& fd = f.getFunctionData(); + std::vector parameters; + for (size_t i = 0; i < fd.fParameterCount; ++i) { + const ASTNode& param = *(iter++); + SkASSERT(param.fKind == ASTNode::Kind::kParameter); + ASTNode::ParameterData pd = param.getParameterData(); + auto paramIter = param.begin(); + const Type* type = this->convertType(*(paramIter++)); + if (!type) { + return; + } + for (int j = (int) pd.fSizeCount; j >= 1; j--) { + int size = (param.begin() + j)->getInt(); + String name = type->name() + "[" + to_string(size) + "]"; + type = (Type*) fSymbolTable->takeOwnership( + std::unique_ptr(new Type(std::move(name), + Type::kArray_Kind, + *type, + size))); + } + StringFragment name = pd.fName; + Variable* var = (Variable*) fSymbolTable->takeOwnership( + std::unique_ptr(new Variable(param.fOffset, + pd.fModifiers, + name, + *type, + Variable::kParameter_Storage))); + parameters.push_back(var); + } + + if (fd.fName == "main") { + switch (fKind) { + case Program::kPipelineStage_Kind: { + bool valid; + switch (parameters.size()) { + case 3: + valid = parameters[0]->fType == *fContext.fFloat_Type && + parameters[0]->fModifiers.fFlags == 0 && + parameters[1]->fType == *fContext.fFloat_Type && + parameters[1]->fModifiers.fFlags == 0 && + parameters[2]->fType == *fContext.fHalf4_Type && + parameters[2]->fModifiers.fFlags == (Modifiers::kIn_Flag | + Modifiers::kOut_Flag); + break; + case 1: + valid = parameters[0]->fType == *fContext.fHalf4_Type && + parameters[0]->fModifiers.fFlags == (Modifiers::kIn_Flag | + Modifiers::kOut_Flag); + break; + default: + valid = false; + } + if (!valid) { + fErrors.error(f.fOffset, "pipeline stage 'main' must be declared main(float, " + "float, inout half4) or main(inout half4)"); + return; + } + break; + } + case Program::kGeneric_Kind: + break; + default: + if (parameters.size()) { + fErrors.error(f.fOffset, "shader 'main' must have zero parameters"); + } + } + } + + // find existing declaration + const FunctionDeclaration* decl = nullptr; + auto entry = (*fSymbolTable)[fd.fName]; + if (entry) { + std::vector functions; + switch (entry->fKind) { + case Symbol::kUnresolvedFunction_Kind: + functions = ((UnresolvedFunction*) entry)->fFunctions; + break; + case Symbol::kFunctionDeclaration_Kind: + functions.push_back((FunctionDeclaration*) entry); + break; + default: + fErrors.error(f.fOffset, "symbol '" + fd.fName + "' was already defined"); + return; + } + for (const auto& other : functions) { + SkASSERT(other->fName == fd.fName); + if (parameters.size() == other->fParameters.size()) { + bool match = true; + for (size_t i = 0; i < parameters.size(); i++) { + if (parameters[i]->fType != other->fParameters[i]->fType) { + match = false; + break; + } + } + if (match) { + if (*returnType != other->fReturnType) { + FunctionDeclaration newDecl(f.fOffset, fd.fModifiers, fd.fName, parameters, + *returnType); + fErrors.error(f.fOffset, "functions '" + newDecl.description() + + "' and '" + other->description() + + "' differ only in return type"); + return; + } + decl = other; + for (size_t i = 0; i < parameters.size(); i++) { + if (parameters[i]->fModifiers != other->fParameters[i]->fModifiers) { + fErrors.error(f.fOffset, "modifiers on parameter " + + to_string((uint64_t) i + 1) + + " differ between declaration and " + "definition"); + return; + } + } + if (other->fDefined) { + fErrors.error(f.fOffset, "duplicate definition of " + + other->description()); + } + break; + } + } + } + } + if (!decl) { + // couldn't find an existing declaration + auto newDecl = std::unique_ptr(new FunctionDeclaration(f.fOffset, + fd.fModifiers, + fd.fName, + parameters, + *returnType)); + decl = newDecl.get(); + fSymbolTable->add(decl->fName, std::move(newDecl)); + } + if (iter != f.end()) { + // compile body + SkASSERT(!fCurrentFunction); + fCurrentFunction = decl; + decl->fDefined = true; + std::shared_ptr old = fSymbolTable; + AutoSymbolTable table(this); + if (fd.fName == "main" && fKind == Program::kPipelineStage_Kind) { + if (parameters.size() == 3) { + parameters[0]->fModifiers.fLayout.fBuiltin = SK_MAIN_X_BUILTIN; + parameters[1]->fModifiers.fLayout.fBuiltin = SK_MAIN_Y_BUILTIN; + parameters[2]->fModifiers.fLayout.fBuiltin = SK_OUTCOLOR_BUILTIN; + } else { + SkASSERT(parameters.size() == 1); + parameters[0]->fModifiers.fLayout.fBuiltin = SK_OUTCOLOR_BUILTIN; + } + } + for (size_t i = 0; i < parameters.size(); i++) { + fSymbolTable->addWithoutOwnership(parameters[i]->fName, decl->fParameters[i]); + } + bool needInvocationIDWorkaround = fInvocations != -1 && fd.fName == "main" && + fSettings->fCaps && + !fSettings->fCaps->gsInvocationsSupport(); + SkASSERT(!fExtraVars.size()); + std::unique_ptr body = this->convertBlock(*iter); + for (auto& v : fExtraVars) { + body->fStatements.insert(body->fStatements.begin(), std::move(v)); + } + fExtraVars.clear(); + fCurrentFunction = nullptr; + if (!body) { + return; + } + if (needInvocationIDWorkaround) { + body = this->applyInvocationIDWorkaround(std::move(body)); + } + // conservatively assume all user-defined functions have side effects + ((Modifiers&) decl->fModifiers).fFlags |= Modifiers::kHasSideEffects_Flag; + if (Program::kVertex_Kind == fKind && fd.fName == "main" && fRTAdjust) { + body->fStatements.insert(body->fStatements.end(), this->getNormalizeSkPositionCode()); + } + fProgramElements->push_back(std::unique_ptr( + new FunctionDefinition(f.fOffset, *decl, std::move(body)))); + } +} + +std::unique_ptr IRGenerator::convertInterfaceBlock(const ASTNode& intf) { + SkASSERT(intf.fKind == ASTNode::Kind::kInterfaceBlock); + ASTNode::InterfaceBlockData id = intf.getInterfaceBlockData(); + std::shared_ptr old = fSymbolTable; + this->pushSymbolTable(); + std::shared_ptr symbols = fSymbolTable; + std::vector fields; + bool haveRuntimeArray = false; + bool foundRTAdjust = false; + auto iter = intf.begin(); + for (size_t i = 0; i < id.fDeclarationCount; ++i) { + std::unique_ptr decl = this->convertVarDeclarations( + *(iter++), + Variable::kInterfaceBlock_Storage); + if (!decl) { + return nullptr; + } + for (const auto& stmt : decl->fVars) { + VarDeclaration& vd = (VarDeclaration&) *stmt; + if (haveRuntimeArray) { + fErrors.error(decl->fOffset, + "only the last entry in an interface block may be a runtime-sized " + "array"); + } + if (vd.fVar == fRTAdjust) { + foundRTAdjust = true; + SkASSERT(vd.fVar->fType == *fContext.fFloat4_Type); + fRTAdjustFieldIndex = fields.size(); + } + fields.push_back(Type::Field(vd.fVar->fModifiers, vd.fVar->fName, + &vd.fVar->fType)); + if (vd.fValue) { + fErrors.error(decl->fOffset, + "initializers are not permitted on interface block fields"); + } + if (vd.fVar->fModifiers.fFlags & (Modifiers::kIn_Flag | + Modifiers::kOut_Flag | + Modifiers::kUniform_Flag | + Modifiers::kBuffer_Flag | + Modifiers::kConst_Flag)) { + fErrors.error(decl->fOffset, + "interface block fields may not have storage qualifiers"); + } + if (vd.fVar->fType.kind() == Type::kArray_Kind && + vd.fVar->fType.columns() == -1) { + haveRuntimeArray = true; + } + } + } + this->popSymbolTable(); + Type* type = (Type*) old->takeOwnership(std::unique_ptr(new Type(intf.fOffset, + id.fTypeName, + fields))); + std::vector> sizes; + for (size_t i = 0; i < id.fSizeCount; ++i) { + const ASTNode& size = *(iter++); + if (size) { + std::unique_ptr converted = this->convertExpression(size); + if (!converted) { + return nullptr; + } + String name = type->fName; + int64_t count; + if (converted->fKind == Expression::kIntLiteral_Kind) { + count = ((IntLiteral&) *converted).fValue; + if (count <= 0) { + fErrors.error(converted->fOffset, "array size must be positive"); + return nullptr; + } + name += "[" + to_string(count) + "]"; + } else { + fErrors.error(intf.fOffset, "array size must be specified"); + return nullptr; + } + type = (Type*) symbols->takeOwnership(std::unique_ptr( + new Type(name, + Type::kArray_Kind, + *type, + (int) count))); + sizes.push_back(std::move(converted)); + } else { + fErrors.error(intf.fOffset, "array size must be specified"); + return nullptr; + } + } + Variable* var = (Variable*) old->takeOwnership(std::unique_ptr( + new Variable(intf.fOffset, + id.fModifiers, + id.fInstanceName.fLength ? id.fInstanceName : id.fTypeName, + *type, + Variable::kGlobal_Storage))); + if (foundRTAdjust) { + fRTAdjustInterfaceBlock = var; + } + if (id.fInstanceName.fLength) { + old->addWithoutOwnership(id.fInstanceName, var); + } else { + for (size_t i = 0; i < fields.size(); i++) { + old->add(fields[i].fName, std::unique_ptr(new Field(intf.fOffset, *var, + (int) i))); + } + } + return std::unique_ptr(new InterfaceBlock(intf.fOffset, + var, + id.fTypeName, + id.fInstanceName, + std::move(sizes), + symbols)); +} + +void IRGenerator::getConstantInt(const Expression& value, int64_t* out) { + switch (value.fKind) { + case Expression::kIntLiteral_Kind: + *out = ((const IntLiteral&) value).fValue; + break; + case Expression::kVariableReference_Kind: { + const Variable& var = ((VariableReference&) value).fVariable; + if ((var.fModifiers.fFlags & Modifiers::kConst_Flag) && + var.fInitialValue) { + this->getConstantInt(*var.fInitialValue, out); + } + break; + } + default: + fErrors.error(value.fOffset, "expected a constant int"); + } +} + +void IRGenerator::convertEnum(const ASTNode& e) { + SkASSERT(e.fKind == ASTNode::Kind::kEnum); + std::vector variables; + int64_t currentValue = 0; + Layout layout; + ASTNode enumType(e.fNodes, e.fOffset, ASTNode::Kind::kType, + ASTNode::TypeData(e.getString(), false, false)); + const Type* type = this->convertType(enumType); + Modifiers modifiers(layout, Modifiers::kConst_Flag); + std::shared_ptr symbols(new SymbolTable(fSymbolTable, &fErrors)); + fSymbolTable = symbols; + for (auto iter = e.begin(); iter != e.end(); ++iter) { + const ASTNode& child = *iter; + SkASSERT(child.fKind == ASTNode::Kind::kEnumCase); + std::unique_ptr value; + if (child.begin() != child.end()) { + value = this->convertExpression(*child.begin()); + if (!value) { + fSymbolTable = symbols->fParent; + return; + } + this->getConstantInt(*value, ¤tValue); + } + value = std::unique_ptr(new IntLiteral(fContext, e.fOffset, currentValue)); + ++currentValue; + auto var = std::unique_ptr(new Variable(e.fOffset, modifiers, child.getString(), + *type, Variable::kGlobal_Storage, + value.get())); + variables.push_back(var.get()); + symbols->add(child.getString(), std::move(var)); + symbols->takeOwnership(std::move(value)); + } + fProgramElements->push_back(std::unique_ptr(new Enum(e.fOffset, e.getString(), + symbols))); + fSymbolTable = symbols->fParent; +} + +const Type* IRGenerator::convertType(const ASTNode& type) { + ASTNode::TypeData td = type.getTypeData(); + const Symbol* result = (*fSymbolTable)[td.fName]; + if (result && result->fKind == Symbol::kType_Kind) { + if (td.fIsNullable) { + if (((Type&) *result) == *fContext.fFragmentProcessor_Type) { + if (type.begin() != type.end()) { + fErrors.error(type.fOffset, "type '" + td.fName + "' may not be used in " + "an array"); + } + result = fSymbolTable->takeOwnership(std::unique_ptr( + new Type(String(result->fName) + "?", + Type::kNullable_Kind, + (const Type&) *result))); + } else { + fErrors.error(type.fOffset, "type '" + td.fName + "' may not be nullable"); + } + } + for (const auto& size : type) { + String name(result->fName); + name += "["; + if (size) { + name += to_string(size.getInt()); + } + name += "]"; + result = (Type*) fSymbolTable->takeOwnership(std::unique_ptr( + new Type(name, + Type::kArray_Kind, + (const Type&) *result, + size ? size.getInt() + : 0))); + } + return (const Type*) result; + } + fErrors.error(type.fOffset, "unknown type '" + td.fName + "'"); + return nullptr; +} + +std::unique_ptr IRGenerator::convertExpression(const ASTNode& expr) { + switch (expr.fKind) { + case ASTNode::Kind::kBinary: + return this->convertBinaryExpression(expr); + case ASTNode::Kind::kBool: + return std::unique_ptr(new BoolLiteral(fContext, expr.fOffset, + expr.getBool())); + case ASTNode::Kind::kCall: + return this->convertCallExpression(expr); + case ASTNode::Kind::kField: + return this->convertFieldExpression(expr); + case ASTNode::Kind::kFloat: + return std::unique_ptr(new FloatLiteral(fContext, expr.fOffset, + expr.getFloat())); + case ASTNode::Kind::kIdentifier: + return this->convertIdentifier(expr); + case ASTNode::Kind::kIndex: + return this->convertIndexExpression(expr); + case ASTNode::Kind::kInt: + return std::unique_ptr(new IntLiteral(fContext, expr.fOffset, + expr.getInt())); + case ASTNode::Kind::kNull: + return std::unique_ptr(new NullLiteral(fContext, expr.fOffset)); + case ASTNode::Kind::kPostfix: + return this->convertPostfixExpression(expr); + case ASTNode::Kind::kPrefix: + return this->convertPrefixExpression(expr); + case ASTNode::Kind::kTernary: + return this->convertTernaryExpression(expr); + default: + ABORT("unsupported expression: %s\n", expr.description().c_str()); + } +} + +std::unique_ptr IRGenerator::convertIdentifier(const ASTNode& identifier) { + SkASSERT(identifier.fKind == ASTNode::Kind::kIdentifier); + const Symbol* result = (*fSymbolTable)[identifier.getString()]; + if (!result) { + fErrors.error(identifier.fOffset, "unknown identifier '" + identifier.getString() + "'"); + return nullptr; + } + switch (result->fKind) { + case Symbol::kFunctionDeclaration_Kind: { + std::vector f = { + (const FunctionDeclaration*) result + }; + return std::unique_ptr(new FunctionReference(fContext, + identifier.fOffset, + f)); + } + case Symbol::kUnresolvedFunction_Kind: { + const UnresolvedFunction* f = (const UnresolvedFunction*) result; + return std::unique_ptr(new FunctionReference(fContext, + identifier.fOffset, + f->fFunctions)); + } + case Symbol::kVariable_Kind: { + const Variable* var = (const Variable*) result; + switch (var->fModifiers.fLayout.fBuiltin) { + case SK_WIDTH_BUILTIN: + fInputs.fRTWidth = true; + break; + case SK_HEIGHT_BUILTIN: + fInputs.fRTHeight = true; + break; +#ifndef SKSL_STANDALONE + case SK_FRAGCOORD_BUILTIN: + fInputs.fFlipY = true; + if (fSettings->fFlipY && + (!fSettings->fCaps || + !fSettings->fCaps->fragCoordConventionsExtensionString())) { + fInputs.fRTHeight = true; + } +#endif + } + if (fKind == Program::kFragmentProcessor_Kind && + (var->fModifiers.fFlags & Modifiers::kIn_Flag) && + !(var->fModifiers.fFlags & Modifiers::kUniform_Flag) && + !var->fModifiers.fLayout.fKey && + var->fModifiers.fLayout.fBuiltin == -1 && + var->fType.nonnullable() != *fContext.fFragmentProcessor_Type && + var->fType.kind() != Type::kSampler_Kind) { + bool valid = false; + for (const auto& decl : fFile->root()) { + if (decl.fKind == ASTNode::Kind::kSection) { + ASTNode::SectionData section = decl.getSectionData(); + if (section.fName == "setData") { + valid = true; + break; + } + } + } + if (!valid) { + fErrors.error(identifier.fOffset, "'in' variable must be either 'uniform' or " + "'layout(key)', or there must be a custom " + "@setData function"); + } + } + // default to kRead_RefKind; this will be corrected later if the variable is written to + return std::unique_ptr(new VariableReference( + identifier.fOffset, + *var, + VariableReference::kRead_RefKind)); + } + case Symbol::kField_Kind: { + const Field* field = (const Field*) result; + VariableReference* base = new VariableReference(identifier.fOffset, field->fOwner, + VariableReference::kRead_RefKind); + return std::unique_ptr(new FieldAccess( + std::unique_ptr(base), + field->fFieldIndex, + FieldAccess::kAnonymousInterfaceBlock_OwnerKind)); + } + case Symbol::kType_Kind: { + const Type* t = (const Type*) result; + return std::unique_ptr(new TypeReference(fContext, identifier.fOffset, + *t)); + } + case Symbol::kExternal_Kind: { + ExternalValue* r = (ExternalValue*) result; + return std::unique_ptr( + new ExternalValueReference(identifier.fOffset, r)); + } + default: + ABORT("unsupported symbol type %d\n", result->fKind); + } +} + +std::unique_ptr
IRGenerator::convertSection(const ASTNode& s) { + ASTNode::SectionData section = s.getSectionData(); + return std::unique_ptr
(new Section(s.fOffset, section.fName, section.fArgument, + section.fText)); +} + + +std::unique_ptr IRGenerator::coerce(std::unique_ptr expr, + const Type& type) { + if (!expr) { + return nullptr; + } + if (expr->fType == type) { + return expr; + } + this->checkValid(*expr); + if (expr->fType == *fContext.fInvalid_Type) { + return nullptr; + } + if (expr->coercionCost(type) == INT_MAX) { + fErrors.error(expr->fOffset, "expected '" + type.description() + "', but found '" + + expr->fType.description() + "'"); + return nullptr; + } + if (type.kind() == Type::kScalar_Kind) { + std::vector> args; + args.push_back(std::move(expr)); + std::unique_ptr ctor; + if (type == *fContext.fFloatLiteral_Type) { + ctor = this->convertIdentifier(ASTNode(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier, + "float")); + } else if (type == *fContext.fIntLiteral_Type) { + ctor = this->convertIdentifier(ASTNode(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier, + "int")); + } else { + ctor = this->convertIdentifier(ASTNode(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier, + type.fName)); + } + if (!ctor) { + printf("error, null identifier: %s\n", String(type.fName).c_str()); + } + SkASSERT(ctor); + return this->call(-1, std::move(ctor), std::move(args)); + } + if (expr->fKind == Expression::kNullLiteral_Kind) { + SkASSERT(type.kind() == Type::kNullable_Kind); + return std::unique_ptr(new NullLiteral(expr->fOffset, type)); + } + std::vector> args; + args.push_back(std::move(expr)); + return std::unique_ptr(new Constructor(-1, type, std::move(args))); +} + +static bool is_matrix_multiply(const Type& left, const Type& right) { + if (left.kind() == Type::kMatrix_Kind) { + return right.kind() == Type::kMatrix_Kind || right.kind() == Type::kVector_Kind; + } + return left.kind() == Type::kVector_Kind && right.kind() == Type::kMatrix_Kind; +} + +/** + * Determines the operand and result types of a binary expression. Returns true if the expression is + * legal, false otherwise. If false, the values of the out parameters are undefined. + */ +static bool determine_binary_type(const Context& context, + Token::Kind op, + const Type& left, + const Type& right, + const Type** outLeftType, + const Type** outRightType, + const Type** outResultType, + bool tryFlipped) { + bool isLogical; + bool validMatrixOrVectorOp; + switch (op) { + case Token::EQ: + *outLeftType = &left; + *outRightType = &left; + *outResultType = &left; + return right.canCoerceTo(left); + case Token::EQEQ: // fall through + case Token::NEQ: + if (right.canCoerceTo(left)) { + *outLeftType = &left; + *outRightType = &left; + *outResultType = context.fBool_Type.get(); + return true; + } if (left.canCoerceTo(right)) { + *outLeftType = &right; + *outRightType = &right; + *outResultType = context.fBool_Type.get(); + return true; + } + return false; + case Token::LT: // fall through + case Token::GT: // fall through + case Token::LTEQ: // fall through + case Token::GTEQ: + isLogical = true; + validMatrixOrVectorOp = false; + break; + case Token::LOGICALOR: // fall through + case Token::LOGICALAND: // fall through + case Token::LOGICALXOR: // fall through + case Token::LOGICALOREQ: // fall through + case Token::LOGICALANDEQ: // fall through + case Token::LOGICALXOREQ: + *outLeftType = context.fBool_Type.get(); + *outRightType = context.fBool_Type.get(); + *outResultType = context.fBool_Type.get(); + return left.canCoerceTo(*context.fBool_Type) && + right.canCoerceTo(*context.fBool_Type); + case Token::STAREQ: + if (left.kind() == Type::kScalar_Kind) { + *outLeftType = &left; + *outRightType = &left; + *outResultType = &left; + return right.canCoerceTo(left); + } + // fall through + case Token::STAR: + if (is_matrix_multiply(left, right)) { + // determine final component type + if (determine_binary_type(context, Token::STAR, left.componentType(), + right.componentType(), outLeftType, outRightType, + outResultType, false)) { + *outLeftType = &(*outResultType)->toCompound(context, left.columns(), + left.rows()); + *outRightType = &(*outResultType)->toCompound(context, right.columns(), + right.rows()); + int leftColumns = left.columns(); + int leftRows = left.rows(); + int rightColumns; + int rightRows; + if (right.kind() == Type::kVector_Kind) { + // matrix * vector treats the vector as a column vector, so we need to + // transpose it + rightColumns = right.rows(); + rightRows = right.columns(); + SkASSERT(rightColumns == 1); + } else { + rightColumns = right.columns(); + rightRows = right.rows(); + } + if (rightColumns > 1) { + *outResultType = &(*outResultType)->toCompound(context, rightColumns, + leftRows); + } else { + // result was a column vector, transpose it back to a row + *outResultType = &(*outResultType)->toCompound(context, leftRows, + rightColumns); + } + return leftColumns == rightRows; + } else { + return false; + } + } + isLogical = false; + validMatrixOrVectorOp = true; + break; + case Token::PLUSEQ: + case Token::MINUSEQ: + case Token::SLASHEQ: + case Token::PERCENTEQ: + case Token::SHLEQ: + case Token::SHREQ: + if (left.kind() == Type::kScalar_Kind) { + *outLeftType = &left; + *outRightType = &left; + *outResultType = &left; + return right.canCoerceTo(left); + } + // fall through + case Token::PLUS: // fall through + case Token::MINUS: // fall through + case Token::SLASH: // fall through + isLogical = false; + validMatrixOrVectorOp = true; + break; + case Token::COMMA: + *outLeftType = &left; + *outRightType = &right; + *outResultType = &right; + return true; + default: + isLogical = false; + validMatrixOrVectorOp = false; + } + bool isVectorOrMatrix = left.kind() == Type::kVector_Kind || left.kind() == Type::kMatrix_Kind; + if (left.kind() == Type::kScalar_Kind && right.kind() == Type::kScalar_Kind && + right.canCoerceTo(left)) { + if (left.priority() > right.priority()) { + *outLeftType = &left; + *outRightType = &left; + } else { + *outLeftType = &right; + *outRightType = &right; + } + if (isLogical) { + *outResultType = context.fBool_Type.get(); + } else { + *outResultType = &left; + } + return true; + } + if (right.canCoerceTo(left) && isVectorOrMatrix && validMatrixOrVectorOp) { + *outLeftType = &left; + *outRightType = &left; + if (isLogical) { + *outResultType = context.fBool_Type.get(); + } else { + *outResultType = &left; + } + return true; + } + if ((left.kind() == Type::kVector_Kind || left.kind() == Type::kMatrix_Kind) && + (right.kind() == Type::kScalar_Kind)) { + if (determine_binary_type(context, op, left.componentType(), right, outLeftType, + outRightType, outResultType, false)) { + *outLeftType = &(*outLeftType)->toCompound(context, left.columns(), left.rows()); + if (!isLogical) { + *outResultType = &(*outResultType)->toCompound(context, left.columns(), + left.rows()); + } + return true; + } + return false; + } + if (tryFlipped) { + return determine_binary_type(context, op, right, left, outRightType, outLeftType, + outResultType, false); + } + return false; +} + +static std::unique_ptr short_circuit_boolean(const Context& context, + const Expression& left, + Token::Kind op, + const Expression& right) { + SkASSERT(left.fKind == Expression::kBoolLiteral_Kind); + bool leftVal = ((BoolLiteral&) left).fValue; + if (op == Token::LOGICALAND) { + // (true && expr) -> (expr) and (false && expr) -> (false) + return leftVal ? right.clone() + : std::unique_ptr(new BoolLiteral(context, left.fOffset, false)); + } else if (op == Token::LOGICALOR) { + // (true || expr) -> (true) and (false || expr) -> (expr) + return leftVal ? std::unique_ptr(new BoolLiteral(context, left.fOffset, true)) + : right.clone(); + } else { + // Can't short circuit XOR + return nullptr; + } +} + +std::unique_ptr IRGenerator::constantFold(const Expression& left, + Token::Kind op, + const Expression& right) const { + // If the left side is a constant boolean literal, the right side does not need to be constant + // for short circuit optimizations to allow the constant to be folded. + if (left.fKind == Expression::kBoolLiteral_Kind && !right.isConstant()) { + return short_circuit_boolean(fContext, left, op, right); + } else if (right.fKind == Expression::kBoolLiteral_Kind && !left.isConstant()) { + // There aren't side effects in SKSL within expressions, so (left OP right) is equivalent to + // (right OP left) for short-circuit optimizations + return short_circuit_boolean(fContext, right, op, left); + } + + // Other than the short-circuit cases above, constant folding requires both sides to be constant + if (!left.isConstant() || !right.isConstant()) { + return nullptr; + } + // Note that we expressly do not worry about precision and overflow here -- we use the maximum + // precision to calculate the results and hope the result makes sense. The plan is to move the + // Skia caps into SkSL, so we have access to all of them including the precisions of the various + // types, which will let us be more intelligent about this. + if (left.fKind == Expression::kBoolLiteral_Kind && + right.fKind == Expression::kBoolLiteral_Kind) { + bool leftVal = ((BoolLiteral&) left).fValue; + bool rightVal = ((BoolLiteral&) right).fValue; + bool result; + switch (op) { + case Token::LOGICALAND: result = leftVal && rightVal; break; + case Token::LOGICALOR: result = leftVal || rightVal; break; + case Token::LOGICALXOR: result = leftVal ^ rightVal; break; + default: return nullptr; + } + return std::unique_ptr(new BoolLiteral(fContext, left.fOffset, result)); + } + #define RESULT(t, op) std::unique_ptr(new t ## Literal(fContext, left.fOffset, \ + leftVal op rightVal)) + if (left.fKind == Expression::kIntLiteral_Kind && right.fKind == Expression::kIntLiteral_Kind) { + int64_t leftVal = ((IntLiteral&) left).fValue; + int64_t rightVal = ((IntLiteral&) right).fValue; + switch (op) { + case Token::PLUS: return RESULT(Int, +); + case Token::MINUS: return RESULT(Int, -); + case Token::STAR: return RESULT(Int, *); + case Token::SLASH: + if (rightVal) { + return RESULT(Int, /); + } + fErrors.error(right.fOffset, "division by zero"); + return nullptr; + case Token::PERCENT: + if (rightVal) { + return RESULT(Int, %); + } + fErrors.error(right.fOffset, "division by zero"); + return nullptr; + case Token::BITWISEAND: return RESULT(Int, &); + case Token::BITWISEOR: return RESULT(Int, |); + case Token::BITWISEXOR: return RESULT(Int, ^); + case Token::EQEQ: return RESULT(Bool, ==); + case Token::NEQ: return RESULT(Bool, !=); + case Token::GT: return RESULT(Bool, >); + case Token::GTEQ: return RESULT(Bool, >=); + case Token::LT: return RESULT(Bool, <); + case Token::LTEQ: return RESULT(Bool, <=); + case Token::SHL: + if (rightVal >= 0 && rightVal <= 31) { + return RESULT(Int, <<); + } + fErrors.error(right.fOffset, "shift value out of range"); + return nullptr; + case Token::SHR: + if (rightVal >= 0 && rightVal <= 31) { + return RESULT(Int, >>); + } + fErrors.error(right.fOffset, "shift value out of range"); + return nullptr; + + default: + return nullptr; + } + } + if (left.fKind == Expression::kFloatLiteral_Kind && + right.fKind == Expression::kFloatLiteral_Kind) { + double leftVal = ((FloatLiteral&) left).fValue; + double rightVal = ((FloatLiteral&) right).fValue; + switch (op) { + case Token::PLUS: return RESULT(Float, +); + case Token::MINUS: return RESULT(Float, -); + case Token::STAR: return RESULT(Float, *); + case Token::SLASH: + if (rightVal) { + return RESULT(Float, /); + } + fErrors.error(right.fOffset, "division by zero"); + return nullptr; + case Token::EQEQ: return RESULT(Bool, ==); + case Token::NEQ: return RESULT(Bool, !=); + case Token::GT: return RESULT(Bool, >); + case Token::GTEQ: return RESULT(Bool, >=); + case Token::LT: return RESULT(Bool, <); + case Token::LTEQ: return RESULT(Bool, <=); + default: return nullptr; + } + } + if (left.fType.kind() == Type::kVector_Kind && left.fType.componentType().isFloat() && + left.fType == right.fType) { + std::vector> args; + #define RETURN_VEC_COMPONENTWISE_RESULT(op) \ + for (int i = 0; i < left.fType.columns(); i++) { \ + float value = left.getFVecComponent(i) op \ + right.getFVecComponent(i); \ + args.emplace_back(new FloatLiteral(fContext, -1, value)); \ + } \ + return std::unique_ptr(new Constructor(-1, left.fType, \ + std::move(args))) + switch (op) { + case Token::EQEQ: + return std::unique_ptr(new BoolLiteral(fContext, -1, + left.compareConstant(fContext, right))); + case Token::NEQ: + return std::unique_ptr(new BoolLiteral(fContext, -1, + !left.compareConstant(fContext, right))); + case Token::PLUS: RETURN_VEC_COMPONENTWISE_RESULT(+); + case Token::MINUS: RETURN_VEC_COMPONENTWISE_RESULT(-); + case Token::STAR: RETURN_VEC_COMPONENTWISE_RESULT(*); + case Token::SLASH: + for (int i = 0; i < left.fType.columns(); i++) { + SKSL_FLOAT rvalue = right.getFVecComponent(i); + if (rvalue == 0.0) { + fErrors.error(right.fOffset, "division by zero"); + return nullptr; + } + float value = left.getFVecComponent(i) / rvalue; + args.emplace_back(new FloatLiteral(fContext, -1, value)); + } + return std::unique_ptr(new Constructor(-1, left.fType, + std::move(args))); + default: return nullptr; + } + } + if (left.fType.kind() == Type::kMatrix_Kind && + right.fType.kind() == Type::kMatrix_Kind && + left.fKind == right.fKind) { + switch (op) { + case Token::EQEQ: + return std::unique_ptr(new BoolLiteral(fContext, -1, + left.compareConstant(fContext, right))); + case Token::NEQ: + return std::unique_ptr(new BoolLiteral(fContext, -1, + !left.compareConstant(fContext, right))); + default: + return nullptr; + } + } + #undef RESULT + return nullptr; +} + +std::unique_ptr IRGenerator::convertBinaryExpression(const ASTNode& expression) { + SkASSERT(expression.fKind == ASTNode::Kind::kBinary); + auto iter = expression.begin(); + std::unique_ptr left = this->convertExpression(*(iter++)); + if (!left) { + return nullptr; + } + std::unique_ptr right = this->convertExpression(*(iter++)); + if (!right) { + return nullptr; + } + const Type* leftType; + const Type* rightType; + const Type* resultType; + const Type* rawLeftType; + if (left->fKind == Expression::kIntLiteral_Kind && right->fType.isInteger()) { + rawLeftType = &right->fType; + } else { + rawLeftType = &left->fType; + } + const Type* rawRightType; + if (right->fKind == Expression::kIntLiteral_Kind && left->fType.isInteger()) { + rawRightType = &left->fType; + } else { + rawRightType = &right->fType; + } + Token::Kind op = expression.getToken().fKind; + if (!determine_binary_type(fContext, op, *rawLeftType, *rawRightType, &leftType, &rightType, + &resultType, !Compiler::IsAssignment(op))) { + fErrors.error(expression.fOffset, String("type mismatch: '") + + Compiler::OperatorName(expression.getToken().fKind) + + "' cannot operate on '" + left->fType.description() + + "', '" + right->fType.description() + "'"); + return nullptr; + } + if (Compiler::IsAssignment(op)) { + this->setRefKind(*left, op != Token::EQ ? VariableReference::kReadWrite_RefKind : + VariableReference::kWrite_RefKind); + } + left = this->coerce(std::move(left), *leftType); + right = this->coerce(std::move(right), *rightType); + if (!left || !right) { + return nullptr; + } + std::unique_ptr result = this->constantFold(*left.get(), op, *right.get()); + if (!result) { + result = std::unique_ptr(new BinaryExpression(expression.fOffset, + std::move(left), + op, + std::move(right), + *resultType)); + } + return result; +} + +std::unique_ptr IRGenerator::convertTernaryExpression(const ASTNode& node) { + SkASSERT(node.fKind == ASTNode::Kind::kTernary); + auto iter = node.begin(); + std::unique_ptr test = this->coerce(this->convertExpression(*(iter++)), + *fContext.fBool_Type); + if (!test) { + return nullptr; + } + std::unique_ptr ifTrue = this->convertExpression(*(iter++)); + if (!ifTrue) { + return nullptr; + } + std::unique_ptr ifFalse = this->convertExpression(*(iter++)); + if (!ifFalse) { + return nullptr; + } + const Type* trueType; + const Type* falseType; + const Type* resultType; + if (!determine_binary_type(fContext, Token::EQEQ, ifTrue->fType, ifFalse->fType, &trueType, + &falseType, &resultType, true) || trueType != falseType) { + fErrors.error(node.fOffset, "ternary operator result mismatch: '" + + ifTrue->fType.description() + "', '" + + ifFalse->fType.description() + "'"); + return nullptr; + } + ifTrue = this->coerce(std::move(ifTrue), *trueType); + if (!ifTrue) { + return nullptr; + } + ifFalse = this->coerce(std::move(ifFalse), *falseType); + if (!ifFalse) { + return nullptr; + } + if (test->fKind == Expression::kBoolLiteral_Kind) { + // static boolean test, just return one of the branches + if (((BoolLiteral&) *test).fValue) { + return ifTrue; + } else { + return ifFalse; + } + } + return std::unique_ptr(new TernaryExpression(node.fOffset, + std::move(test), + std::move(ifTrue), + std::move(ifFalse))); +} + +std::unique_ptr IRGenerator::call(int offset, + const FunctionDeclaration& function, + std::vector> arguments) { + if (function.fParameters.size() != arguments.size()) { + String msg = "call to '" + function.fName + "' expected " + + to_string((uint64_t) function.fParameters.size()) + + " argument"; + if (function.fParameters.size() != 1) { + msg += "s"; + } + msg += ", but found " + to_string((uint64_t) arguments.size()); + fErrors.error(offset, msg); + return nullptr; + } + std::vector types; + const Type* returnType; + if (!function.determineFinalTypes(arguments, &types, &returnType)) { + String msg = "no match for " + function.fName + "("; + String separator; + for (size_t i = 0; i < arguments.size(); i++) { + msg += separator; + separator = ", "; + msg += arguments[i]->fType.description(); + } + msg += ")"; + fErrors.error(offset, msg); + return nullptr; + } + for (size_t i = 0; i < arguments.size(); i++) { + arguments[i] = this->coerce(std::move(arguments[i]), *types[i]); + if (!arguments[i]) { + return nullptr; + } + if (arguments[i] && (function.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag)) { + this->setRefKind(*arguments[i], + function.fParameters[i]->fModifiers.fFlags & Modifiers::kIn_Flag ? + VariableReference::kReadWrite_RefKind : + VariableReference::kPointer_RefKind); + } + } + return std::unique_ptr(new FunctionCall(offset, *returnType, function, + std::move(arguments))); +} + +/** + * Determines the cost of coercing the arguments of a function to the required types. Cost has no + * particular meaning other than "lower costs are preferred". Returns INT_MAX if the call is not + * valid. + */ +int IRGenerator::callCost(const FunctionDeclaration& function, + const std::vector>& arguments) { + if (function.fParameters.size() != arguments.size()) { + return INT_MAX; + } + int total = 0; + std::vector types; + const Type* ignored; + if (!function.determineFinalTypes(arguments, &types, &ignored)) { + return INT_MAX; + } + for (size_t i = 0; i < arguments.size(); i++) { + int cost = arguments[i]->coercionCost(*types[i]); + if (cost != INT_MAX) { + total += cost; + } else { + return INT_MAX; + } + } + return total; +} + +std::unique_ptr IRGenerator::call(int offset, + std::unique_ptr functionValue, + std::vector> arguments) { + switch (functionValue->fKind) { + case Expression::kTypeReference_Kind: + return this->convertConstructor(offset, + ((TypeReference&) *functionValue).fValue, + std::move(arguments)); + case Expression::kExternalValue_Kind: { + ExternalValue* v = ((ExternalValueReference&) *functionValue).fValue; + if (!v->canCall()) { + fErrors.error(offset, "this external value is not a function"); + return nullptr; + } + int count = v->callParameterCount(); + if (count != (int) arguments.size()) { + fErrors.error(offset, "external function expected " + to_string(count) + + " arguments, but found " + to_string((int) arguments.size())); + return nullptr; + } + static constexpr int PARAMETER_MAX = 16; + SkASSERT(count < PARAMETER_MAX); + const Type* types[PARAMETER_MAX]; + v->getCallParameterTypes(types); + for (int i = 0; i < count; ++i) { + arguments[i] = this->coerce(std::move(arguments[i]), *types[i]); + if (!arguments[i]) { + return nullptr; + } + } + return std::unique_ptr(new ExternalFunctionCall(offset, v->callReturnType(), + v, std::move(arguments))); + } + case Expression::kFunctionReference_Kind: { + FunctionReference* ref = (FunctionReference*) functionValue.get(); + int bestCost = INT_MAX; + const FunctionDeclaration* best = nullptr; + if (ref->fFunctions.size() > 1) { + for (const auto& f : ref->fFunctions) { + int cost = this->callCost(*f, arguments); + if (cost < bestCost) { + bestCost = cost; + best = f; + } + } + if (best) { + return this->call(offset, *best, std::move(arguments)); + } + String msg = "no match for " + ref->fFunctions[0]->fName + "("; + String separator; + for (size_t i = 0; i < arguments.size(); i++) { + msg += separator; + separator = ", "; + msg += arguments[i]->fType.description(); + } + msg += ")"; + fErrors.error(offset, msg); + return nullptr; + } + return this->call(offset, *ref->fFunctions[0], std::move(arguments)); + } + default: + fErrors.error(offset, "'" + functionValue->description() + "' is not a function"); + return nullptr; + } +} + +std::unique_ptr IRGenerator::convertNumberConstructor( + int offset, + const Type& type, + std::vector> args) { + SkASSERT(type.isNumber()); + if (args.size() != 1) { + fErrors.error(offset, "invalid arguments to '" + type.description() + + "' constructor, (expected exactly 1 argument, but found " + + to_string((uint64_t) args.size()) + ")"); + return nullptr; + } + if (type == args[0]->fType) { + return std::move(args[0]); + } + if (type.isFloat() && args.size() == 1 && args[0]->fKind == Expression::kFloatLiteral_Kind) { + double value = ((FloatLiteral&) *args[0]).fValue; + return std::unique_ptr(new FloatLiteral(offset, value, &type)); + } + if (type.isFloat() && args.size() == 1 && args[0]->fKind == Expression::kIntLiteral_Kind) { + int64_t value = ((IntLiteral&) *args[0]).fValue; + return std::unique_ptr(new FloatLiteral(offset, (double) value, &type)); + } + if (args[0]->fKind == Expression::kIntLiteral_Kind && (type == *fContext.fInt_Type || + type == *fContext.fUInt_Type)) { + return std::unique_ptr(new IntLiteral(offset, + ((IntLiteral&) *args[0]).fValue, + &type)); + } + if (args[0]->fType == *fContext.fBool_Type) { + std::unique_ptr zero(new IntLiteral(fContext, offset, 0)); + std::unique_ptr one(new IntLiteral(fContext, offset, 1)); + return std::unique_ptr( + new TernaryExpression(offset, std::move(args[0]), + this->coerce(std::move(one), type), + this->coerce(std::move(zero), + type))); + } + if (!args[0]->fType.isNumber()) { + fErrors.error(offset, "invalid argument to '" + type.description() + + "' constructor (expected a number or bool, but found '" + + args[0]->fType.description() + "')"); + return nullptr; + } + return std::unique_ptr(new Constructor(offset, type, std::move(args))); +} + +int component_count(const Type& type) { + switch (type.kind()) { + case Type::kVector_Kind: + return type.columns(); + case Type::kMatrix_Kind: + return type.columns() * type.rows(); + default: + return 1; + } +} + +std::unique_ptr IRGenerator::convertCompoundConstructor( + int offset, + const Type& type, + std::vector> args) { + SkASSERT(type.kind() == Type::kVector_Kind || type.kind() == Type::kMatrix_Kind); + if (type.kind() == Type::kMatrix_Kind && args.size() == 1 && + args[0]->fType.kind() == Type::kMatrix_Kind) { + // matrix from matrix is always legal + return std::unique_ptr(new Constructor(offset, type, std::move(args))); + } + int actual = 0; + int expected = type.rows() * type.columns(); + if (args.size() != 1 || expected != component_count(args[0]->fType) || + type.componentType().isNumber() != args[0]->fType.componentType().isNumber()) { + for (size_t i = 0; i < args.size(); i++) { + if (args[i]->fType.kind() == Type::kVector_Kind) { + if (type.componentType().isNumber() != + args[i]->fType.componentType().isNumber()) { + fErrors.error(offset, "'" + args[i]->fType.description() + "' is not a valid " + "parameter to '" + type.description() + + "' constructor"); + return nullptr; + } + actual += args[i]->fType.columns(); + } else if (args[i]->fType.kind() == Type::kScalar_Kind) { + actual += 1; + if (type.kind() != Type::kScalar_Kind) { + args[i] = this->coerce(std::move(args[i]), type.componentType()); + if (!args[i]) { + return nullptr; + } + } + } else { + fErrors.error(offset, "'" + args[i]->fType.description() + "' is not a valid " + "parameter to '" + type.description() + "' constructor"); + return nullptr; + } + } + if (actual != 1 && actual != expected) { + fErrors.error(offset, "invalid arguments to '" + type.description() + + "' constructor (expected " + to_string(expected) + + " scalars, but found " + to_string(actual) + ")"); + return nullptr; + } + } + return std::unique_ptr(new Constructor(offset, type, std::move(args))); +} + +std::unique_ptr IRGenerator::convertConstructor( + int offset, + const Type& type, + std::vector> args) { + // FIXME: add support for structs + Type::Kind kind = type.kind(); + if (args.size() == 1 && args[0]->fType == type) { + // argument is already the right type, just return it + return std::move(args[0]); + } + if (type.isNumber()) { + return this->convertNumberConstructor(offset, type, std::move(args)); + } else if (kind == Type::kArray_Kind) { + const Type& base = type.componentType(); + for (size_t i = 0; i < args.size(); i++) { + args[i] = this->coerce(std::move(args[i]), base); + if (!args[i]) { + return nullptr; + } + } + return std::unique_ptr(new Constructor(offset, type, std::move(args))); + } else if (kind == Type::kVector_Kind || kind == Type::kMatrix_Kind) { + return this->convertCompoundConstructor(offset, type, std::move(args)); + } else { + fErrors.error(offset, "cannot construct '" + type.description() + "'"); + return nullptr; + } +} + +std::unique_ptr IRGenerator::convertPrefixExpression(const ASTNode& expression) { + SkASSERT(expression.fKind == ASTNode::Kind::kPrefix); + std::unique_ptr base = this->convertExpression(*expression.begin()); + if (!base) { + return nullptr; + } + switch (expression.getToken().fKind) { + case Token::PLUS: + if (!base->fType.isNumber() && base->fType.kind() != Type::kVector_Kind && + base->fType != *fContext.fFloatLiteral_Type) { + fErrors.error(expression.fOffset, + "'+' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + return base; + case Token::MINUS: + if (base->fKind == Expression::kIntLiteral_Kind) { + return std::unique_ptr(new IntLiteral(fContext, base->fOffset, + -((IntLiteral&) *base).fValue)); + } + if (base->fKind == Expression::kFloatLiteral_Kind) { + double value = -((FloatLiteral&) *base).fValue; + return std::unique_ptr(new FloatLiteral(fContext, base->fOffset, + value)); + } + if (!base->fType.isNumber() && base->fType.kind() != Type::kVector_Kind) { + fErrors.error(expression.fOffset, + "'-' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + return std::unique_ptr(new PrefixExpression(Token::MINUS, std::move(base))); + case Token::PLUSPLUS: + if (!base->fType.isNumber()) { + fErrors.error(expression.fOffset, + String("'") + Compiler::OperatorName(expression.getToken().fKind) + + "' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + this->setRefKind(*base, VariableReference::kReadWrite_RefKind); + break; + case Token::MINUSMINUS: + if (!base->fType.isNumber()) { + fErrors.error(expression.fOffset, + String("'") + Compiler::OperatorName(expression.getToken().fKind) + + "' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + this->setRefKind(*base, VariableReference::kReadWrite_RefKind); + break; + case Token::LOGICALNOT: + if (base->fType != *fContext.fBool_Type) { + fErrors.error(expression.fOffset, + String("'") + Compiler::OperatorName(expression.getToken().fKind) + + "' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + if (base->fKind == Expression::kBoolLiteral_Kind) { + return std::unique_ptr(new BoolLiteral(fContext, base->fOffset, + !((BoolLiteral&) *base).fValue)); + } + break; + case Token::BITWISENOT: + if (base->fType != *fContext.fInt_Type && base->fType != *fContext.fUInt_Type) { + fErrors.error(expression.fOffset, + String("'") + Compiler::OperatorName(expression.getToken().fKind) + + "' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + break; + default: + ABORT("unsupported prefix operator\n"); + } + return std::unique_ptr(new PrefixExpression(expression.getToken().fKind, + std::move(base))); +} + +std::unique_ptr IRGenerator::convertIndex(std::unique_ptr base, + const ASTNode& index) { + if (base->fKind == Expression::kTypeReference_Kind) { + if (index.fKind == ASTNode::Kind::kInt) { + const Type& oldType = ((TypeReference&) *base).fValue; + SKSL_INT size = index.getInt(); + Type* newType = (Type*) fSymbolTable->takeOwnership(std::unique_ptr( + new Type(oldType.name() + "[" + to_string(size) + "]", + Type::kArray_Kind, oldType, size))); + return std::unique_ptr(new TypeReference(fContext, base->fOffset, + *newType)); + + } else { + fErrors.error(base->fOffset, "array size must be a constant"); + return nullptr; + } + } + if (base->fType.kind() != Type::kArray_Kind && base->fType.kind() != Type::kMatrix_Kind && + base->fType.kind() != Type::kVector_Kind) { + fErrors.error(base->fOffset, "expected array, but found '" + base->fType.description() + + "'"); + return nullptr; + } + std::unique_ptr converted = this->convertExpression(index); + if (!converted) { + return nullptr; + } + if (converted->fType != *fContext.fUInt_Type) { + converted = this->coerce(std::move(converted), *fContext.fInt_Type); + if (!converted) { + return nullptr; + } + } + return std::unique_ptr(new IndexExpression(fContext, std::move(base), + std::move(converted))); +} + +std::unique_ptr IRGenerator::convertField(std::unique_ptr base, + StringFragment field) { + if (base->fKind == Expression::kExternalValue_Kind) { + ExternalValue& ev = *((ExternalValueReference&) *base).fValue; + ExternalValue* result = ev.getChild(String(field).c_str()); + if (!result) { + fErrors.error(base->fOffset, "external value does not have a child named '" + field + + "'"); + return nullptr; + } + return std::unique_ptr(new ExternalValueReference(base->fOffset, result)); + } + auto fields = base->fType.fields(); + for (size_t i = 0; i < fields.size(); i++) { + if (fields[i].fName == field) { + return std::unique_ptr(new FieldAccess(std::move(base), (int) i)); + } + } + fErrors.error(base->fOffset, "type '" + base->fType.description() + "' does not have a " + "field named '" + field + ""); + return nullptr; +} + +std::unique_ptr IRGenerator::convertSwizzle(std::unique_ptr base, + StringFragment fields) { + if (base->fType.kind() != Type::kVector_Kind) { + fErrors.error(base->fOffset, "cannot swizzle type '" + base->fType.description() + "'"); + return nullptr; + } + std::vector swizzleComponents; + for (size_t i = 0; i < fields.fLength; i++) { + switch (fields[i]) { + case '0': + swizzleComponents.push_back(SKSL_SWIZZLE_0); + break; + case '1': + swizzleComponents.push_back(SKSL_SWIZZLE_1); + break; + case 'x': + case 'r': + case 's': + case 'L': + swizzleComponents.push_back(0); + break; + case 'y': + case 'g': + case 't': + case 'T': + if (base->fType.columns() >= 2) { + swizzleComponents.push_back(1); + break; + } + // fall through + case 'z': + case 'b': + case 'p': + case 'R': + if (base->fType.columns() >= 3) { + swizzleComponents.push_back(2); + break; + } + // fall through + case 'w': + case 'a': + case 'q': + case 'B': + if (base->fType.columns() >= 4) { + swizzleComponents.push_back(3); + break; + } + // fall through + default: + fErrors.error(base->fOffset, String::printf("invalid swizzle component '%c'", + fields[i])); + return nullptr; + } + } + SkASSERT(swizzleComponents.size() > 0); + if (swizzleComponents.size() > 4) { + fErrors.error(base->fOffset, "too many components in swizzle mask '" + fields + "'"); + return nullptr; + } + return std::unique_ptr(new Swizzle(fContext, std::move(base), swizzleComponents)); +} + +std::unique_ptr IRGenerator::getCap(int offset, String name) { + auto found = fCapsMap.find(name); + if (found == fCapsMap.end()) { + fErrors.error(offset, "unknown capability flag '" + name + "'"); + return nullptr; + } + String fullName = "sk_Caps." + name; + return std::unique_ptr(new Setting(offset, fullName, + found->second.literal(fContext, offset))); +} + +std::unique_ptr IRGenerator::getArg(int offset, String name) const { + auto found = fSettings->fArgs.find(name); + if (found == fSettings->fArgs.end()) { + return nullptr; + } + String fullName = "sk_Args." + name; + return std::unique_ptr(new Setting(offset, + fullName, + found->second.literal(fContext, offset))); +} + +std::unique_ptr IRGenerator::convertTypeField(int offset, const Type& type, + StringFragment field) { + std::unique_ptr result; + for (const auto& e : *fProgramElements) { + if (e->fKind == ProgramElement::kEnum_Kind && type.name() == ((Enum&) *e).fTypeName) { + std::shared_ptr old = fSymbolTable; + fSymbolTable = ((Enum&) *e).fSymbols; + result = convertIdentifier(ASTNode(&fFile->fNodes, offset, ASTNode::Kind::kIdentifier, + field)); + fSymbolTable = old; + } + } + if (!result) { + fErrors.error(offset, "type '" + type.fName + "' does not have a field named '" + field + + "'"); + } + return result; +} + +std::unique_ptr IRGenerator::convertIndexExpression(const ASTNode& index) { + SkASSERT(index.fKind == ASTNode::Kind::kIndex); + auto iter = index.begin(); + std::unique_ptr base = this->convertExpression(*(iter++)); + if (!base) { + return nullptr; + } + if (iter != index.end()) { + return this->convertIndex(std::move(base), *(iter++)); + } else if (base->fKind == Expression::kTypeReference_Kind) { + const Type& oldType = ((TypeReference&) *base).fValue; + Type* newType = (Type*) fSymbolTable->takeOwnership(std::unique_ptr( + new Type(oldType.name() + "[]", + Type::kArray_Kind, + oldType, + -1))); + return std::unique_ptr(new TypeReference(fContext, base->fOffset, + *newType)); + } + fErrors.error(index.fOffset, "'[]' must follow a type name"); + return nullptr; +} + +std::unique_ptr IRGenerator::convertCallExpression(const ASTNode& callNode) { + SkASSERT(callNode.fKind == ASTNode::Kind::kCall); + auto iter = callNode.begin(); + std::unique_ptr base = this->convertExpression(*(iter++)); + if (!base) { + return nullptr; + } + std::vector> arguments; + for (; iter != callNode.end(); ++iter) { + std::unique_ptr converted = this->convertExpression(*iter); + if (!converted) { + return nullptr; + } + arguments.push_back(std::move(converted)); + } + return this->call(callNode.fOffset, std::move(base), std::move(arguments)); +} + +std::unique_ptr IRGenerator::convertFieldExpression(const ASTNode& fieldNode) { + std::unique_ptr base = this->convertExpression(*fieldNode.begin()); + if (!base) { + return nullptr; + } + StringFragment field = fieldNode.getString(); + if (base->fType == *fContext.fSkCaps_Type) { + return this->getCap(fieldNode.fOffset, field); + } + if (base->fType == *fContext.fSkArgs_Type) { + return this->getArg(fieldNode.fOffset, field); + } + if (base->fKind == Expression::kTypeReference_Kind) { + return this->convertTypeField(base->fOffset, ((TypeReference&) *base).fValue, + field); + } + if (base->fKind == Expression::kExternalValue_Kind) { + return this->convertField(std::move(base), field); + } + switch (base->fType.kind()) { + case Type::kVector_Kind: + return this->convertSwizzle(std::move(base), field); + case Type::kOther_Kind: + case Type::kStruct_Kind: + return this->convertField(std::move(base), field); + default: + fErrors.error(base->fOffset, "cannot swizzle value of type '" + + base->fType.description() + "'"); + return nullptr; + } +} + +std::unique_ptr IRGenerator::convertPostfixExpression(const ASTNode& expression) { + std::unique_ptr base = this->convertExpression(*expression.begin()); + if (!base) { + return nullptr; + } + if (!base->fType.isNumber()) { + fErrors.error(expression.fOffset, + "'" + String(Compiler::OperatorName(expression.getToken().fKind)) + + "' cannot operate on '" + base->fType.description() + "'"); + return nullptr; + } + this->setRefKind(*base, VariableReference::kReadWrite_RefKind); + return std::unique_ptr(new PostfixExpression(std::move(base), + expression.getToken().fKind)); +} + +void IRGenerator::checkValid(const Expression& expr) { + switch (expr.fKind) { + case Expression::kFunctionReference_Kind: + fErrors.error(expr.fOffset, "expected '(' to begin function call"); + break; + case Expression::kTypeReference_Kind: + fErrors.error(expr.fOffset, "expected '(' to begin constructor invocation"); + break; + default: + if (expr.fType == *fContext.fInvalid_Type) { + fErrors.error(expr.fOffset, "invalid expression"); + } + } +} + +bool IRGenerator::checkSwizzleWrite(const Swizzle& swizzle) { + int bits = 0; + for (int idx : swizzle.fComponents) { + if (idx < 0) { + fErrors.error(swizzle.fOffset, "cannot write to a swizzle mask containing a constant"); + return false; + } + SkASSERT(idx <= 3); + int bit = 1 << idx; + if (bits & bit) { + fErrors.error(swizzle.fOffset, + "cannot write to the same swizzle field more than once"); + return false; + } + bits |= bit; + } + return true; +} + +void IRGenerator::setRefKind(const Expression& expr, VariableReference::RefKind kind) { + switch (expr.fKind) { + case Expression::kVariableReference_Kind: { + const Variable& var = ((VariableReference&) expr).fVariable; + if (var.fModifiers.fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag)) { + fErrors.error(expr.fOffset, + "cannot modify immutable variable '" + var.fName + "'"); + } + ((VariableReference&) expr).setRefKind(kind); + break; + } + case Expression::kFieldAccess_Kind: + this->setRefKind(*((FieldAccess&) expr).fBase, kind); + break; + case Expression::kSwizzle_Kind: { + const Swizzle& swizzle = (Swizzle&) expr; + this->checkSwizzleWrite(swizzle); + this->setRefKind(*swizzle.fBase, kind); + break; + } + case Expression::kIndex_Kind: + this->setRefKind(*((IndexExpression&) expr).fBase, kind); + break; + case Expression::kTernary_Kind: { + TernaryExpression& t = (TernaryExpression&) expr; + this->setRefKind(*t.fIfTrue, kind); + this->setRefKind(*t.fIfFalse, kind); + break; + } + case Expression::kExternalValue_Kind: { + const ExternalValue& v = *((ExternalValueReference&) expr).fValue; + if (!v.canWrite()) { + fErrors.error(expr.fOffset, + "cannot modify immutable external value '" + v.fName + "'"); + } + break; + } + default: + fErrors.error(expr.fOffset, "cannot assign to '" + expr.description() + "'"); + break; + } +} + +void IRGenerator::convertProgram(Program::Kind kind, + const char* text, + size_t length, + SymbolTable& types, + std::vector>* out) { + fKind = kind; + fProgramElements = out; + Parser parser(text, length, types, fErrors); + fFile = parser.file(); + if (fErrors.errorCount()) { + return; + } + SkASSERT(fFile); + for (const auto& decl : fFile->root()) { + switch (decl.fKind) { + case ASTNode::Kind::kVarDeclarations: { + std::unique_ptr s = this->convertVarDeclarations( + decl, + Variable::kGlobal_Storage); + if (s) { + fProgramElements->push_back(std::move(s)); + } + break; + } + case ASTNode::Kind::kEnum: { + this->convertEnum(decl); + break; + } + case ASTNode::Kind::kFunction: { + this->convertFunction(decl); + break; + } + case ASTNode::Kind::kModifiers: { + std::unique_ptr f = this->convertModifiersDeclaration(decl); + if (f) { + fProgramElements->push_back(std::move(f)); + } + break; + } + case ASTNode::Kind::kInterfaceBlock: { + std::unique_ptr i = this->convertInterfaceBlock(decl); + if (i) { + fProgramElements->push_back(std::move(i)); + } + break; + } + case ASTNode::Kind::kExtension: { + std::unique_ptr e = this->convertExtension(decl.fOffset, + decl.getString()); + if (e) { + fProgramElements->push_back(std::move(e)); + } + break; + } + case ASTNode::Kind::kSection: { + std::unique_ptr
s = this->convertSection(decl); + if (s) { + fProgramElements->push_back(std::move(s)); + } + break; + } + default: + ABORT("unsupported declaration: %s\n", decl.description().c_str()); + } + } +} + + +} diff --git a/gfx/skia/skia/src/sksl/SkSLIRGenerator.h b/gfx/skia/skia/src/sksl/SkSLIRGenerator.h new file mode 100644 index 0000000000..20a556f4e3 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLIRGenerator.h @@ -0,0 +1,180 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_IRGENERATOR +#define SKSL_IRGENERATOR + +#include "src/sksl/SkSLASTFile.h" +#include "src/sksl/SkSLASTNode.h" +#include "src/sksl/SkSLErrorReporter.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSection.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLTypeReference.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +struct Swizzle; + +/** + * Performs semantic analysis on an abstract syntax tree (AST) and produces the corresponding + * (unoptimized) intermediate representation (IR). + */ +class IRGenerator { +public: + IRGenerator(const Context* context, std::shared_ptr root, + ErrorReporter& errorReporter); + + void convertProgram(Program::Kind kind, + const char* text, + size_t length, + SymbolTable& types, + std::vector>* result); + + /** + * If both operands are compile-time constants and can be folded, returns an expression + * representing the folded value. Otherwise, returns null. Note that unlike most other functions + * here, null does not represent a compilation error. + */ + std::unique_ptr constantFold(const Expression& left, + Token::Kind op, + const Expression& right) const; + + std::unique_ptr getArg(int offset, String name) const; + + Program::Inputs fInputs; + const Program::Settings* fSettings; + const Context& fContext; + Program::Kind fKind; + +private: + /** + * Prepare to compile a program. Resets state, pushes a new symbol table, and installs the + * settings. + */ + void start(const Program::Settings* settings, + std::vector>* inherited); + + /** + * Performs cleanup after compilation is complete. + */ + void finish(); + + void pushSymbolTable(); + void popSymbolTable(); + + std::unique_ptr convertVarDeclarations(const ASTNode& decl, + Variable::Storage storage); + void convertFunction(const ASTNode& f); + std::unique_ptr convertStatement(const ASTNode& statement); + std::unique_ptr convertExpression(const ASTNode& expression); + std::unique_ptr convertModifiersDeclaration(const ASTNode& m); + + const Type* convertType(const ASTNode& type); + std::unique_ptr call(int offset, + const FunctionDeclaration& function, + std::vector> arguments); + int callCost(const FunctionDeclaration& function, + const std::vector>& arguments); + std::unique_ptr call(int offset, std::unique_ptr function, + std::vector> arguments); + int coercionCost(const Expression& expr, const Type& type); + std::unique_ptr coerce(std::unique_ptr expr, const Type& type); + std::unique_ptr convertBlock(const ASTNode& block); + std::unique_ptr convertBreak(const ASTNode& b); + std::unique_ptr convertNumberConstructor( + int offset, + const Type& type, + std::vector> params); + std::unique_ptr convertCompoundConstructor( + int offset, + const Type& type, + std::vector> params); + std::unique_ptr convertConstructor(int offset, + const Type& type, + std::vector> params); + std::unique_ptr convertContinue(const ASTNode& c); + std::unique_ptr convertDiscard(const ASTNode& d); + std::unique_ptr convertDo(const ASTNode& d); + std::unique_ptr convertSwitch(const ASTNode& s); + std::unique_ptr convertBinaryExpression(const ASTNode& expression); + std::unique_ptr convertExtension(int offset, StringFragment name); + std::unique_ptr convertExpressionStatement(const ASTNode& s); + std::unique_ptr convertFor(const ASTNode& f); + std::unique_ptr convertIdentifier(const ASTNode& identifier); + std::unique_ptr convertIf(const ASTNode& s); + std::unique_ptr convertIndex(std::unique_ptr base, + const ASTNode& index); + std::unique_ptr convertInterfaceBlock(const ASTNode& s); + Modifiers convertModifiers(const Modifiers& m); + std::unique_ptr convertPrefixExpression(const ASTNode& expression); + std::unique_ptr convertReturn(const ASTNode& r); + std::unique_ptr
convertSection(const ASTNode& e); + std::unique_ptr getCap(int offset, String name); + std::unique_ptr convertCallExpression(const ASTNode& expression); + std::unique_ptr convertFieldExpression(const ASTNode& expression); + std::unique_ptr convertIndexExpression(const ASTNode& expression); + std::unique_ptr convertPostfixExpression(const ASTNode& expression); + std::unique_ptr convertTypeField(int offset, const Type& type, + StringFragment field); + std::unique_ptr convertField(std::unique_ptr base, + StringFragment field); + std::unique_ptr convertSwizzle(std::unique_ptr base, + StringFragment fields); + std::unique_ptr convertTernaryExpression(const ASTNode& expression); + std::unique_ptr convertVarDeclarationStatement(const ASTNode& s); + std::unique_ptr convertWhile(const ASTNode& w); + void convertEnum(const ASTNode& e); + std::unique_ptr applyInvocationIDWorkaround(std::unique_ptr main); + // returns a statement which converts sk_Position from device to normalized coordinates + std::unique_ptr getNormalizeSkPositionCode(); + + void checkValid(const Expression& expr); + void setRefKind(const Expression& expr, VariableReference::RefKind kind); + void getConstantInt(const Expression& value, int64_t* out); + bool checkSwizzleWrite(const Swizzle& swizzle); + + std::unique_ptr fFile; + const FunctionDeclaration* fCurrentFunction; + std::unordered_map fCapsMap; + std::shared_ptr fRootSymbolTable; + std::shared_ptr fSymbolTable; + // holds extra temp variable declarations needed for the current function + std::vector> fExtraVars; + int fLoopLevel; + int fSwitchLevel; + // count of temporary variables we have created + int fTmpCount; + ErrorReporter& fErrors; + int fInvocations; + std::vector>* fProgramElements; + const Variable* fSkPerVertex = nullptr; + Variable* fRTAdjust; + Variable* fRTAdjustInterfaceBlock; + int fRTAdjustFieldIndex; + bool fStarted = false; + + friend class AutoSymbolTable; + friend class AutoLoopLevel; + friend class AutoSwitchLevel; + friend class Compiler; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLLexer.cpp b/gfx/skia/skia/src/sksl/SkSLLexer.cpp new file mode 100644 index 0000000000..2815408341 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLLexer.cpp @@ -0,0 +1,1037 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +/***************************************************************************************** + ******************** This file was generated by sksllex. Do not edit. ******************* + *****************************************************************************************/ +#include "src/sksl/SkSLLexer.h" + +namespace SkSL { + +static const uint8_t INVALID_CHAR = 18; +static int8_t mappings[127] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 4, 3, 5, 6, 7, 8, 3, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 26, 26, 26, 27, 26, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 28, 6, 6, 6, + 29, 6, 6, 30, 3, 31, 32, 33, 3, 34, 35, 36, 37, 38, 39, 6, 40, 41, 6, 42, 43, 44, + 45, 46, 47, 6, 48, 49, 50, 51, 52, 53, 54, 55, 6, 56, 57, 58, 59}; +static int16_t transitions[60][304] = { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, + 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 35, 36, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 44, 0, 0, 47, 0, + 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 26, 0, 0, 0, 0, 0, 32, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 44, 0, 0, 47, 0, + 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, + 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 38, 35, 37, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 40, 0, 0, 0, 0, 0, 0, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 30, 30, 33, 33, 33, 0, 35, 35, 0, 38, 0, 49, 42, + 42, 45, 45, 45, 48, 48, 48, 49, 52, 52, 52, 54, 54, 49, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, + 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 55, 0, 0, 0, 0, 0, 0, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 30, 30, 33, 33, 33, 0, 35, 35, 0, 38, 0, 49, 42, + 42, 45, 45, 45, 48, 48, 48, 49, 52, 52, 52, 54, 54, 49, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, + 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 63, 0, 0, 0, 6, 0, 0, 0, 0, 0, 12, 0, 16, 15, 0, 0, 0, 0, 20, 0, 23, 0, 0, + 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 39, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 61, 0, 0, 64, 0, 66, 0, 68, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 299, 0, 301, 0, 0, 0, + }, + { + 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 54, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, + 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 50, 46, 43, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 54, 54, 50, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 99, 10, 10, 10, 10, 10, 105, 10, 10, 10, 10, 10, 111, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 101, 10, 10, 10, 10, 10, 107, 10, 10, 10, 10, 10, 113, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 100, 10, 10, 10, 10, 10, 106, 10, 10, 10, 10, 10, 112, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 86, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 87, 10, 10, 10, 10, 10, 93, 10, 10, + 10, 10, 10, 102, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 238, 10, 10, 10, 242, 10, 10, 10, 10, 247, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 54, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 97, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 117, 10, 10, 10, 10, 10, 10, 10, 125, 10, 10, 10, 129, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 150, 10, 10, + 10, 10, 10, 10, 157, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 169, 10, 10, + 10, 10, 174, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 185, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 220, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 240, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 279, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 114, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 54, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, + 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 124, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 54, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 78, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 96, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 156, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 198, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 212, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 230, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 252, 10, 10, 10, 10, 10, 258, 10, 10, 10, 10, 263, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 147, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 54, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 159, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 221, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 245, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 161, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 35, + 35, 0, 38, 0, 50, 46, 43, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 54, + 54, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, + 91, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 116, 10, 10, 10, 10, 10, 122, 10, 10, 10, 10, + 127, 10, 10, 10, 10, 10, 10, 134, 10, 136, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 146, 10, 148, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 164, 10, 10, 10, 10, 10, 10, 10, 172, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 193, 10, 10, 10, 197, 10, + 10, 10, 10, 202, 10, 10, 10, 10, 10, 10, 10, 10, 211, 10, 10, 10, 10, 10, + 10, 10, 219, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 246, 10, 248, 10, 10, 251, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 268, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 283, 10, 10, 10, 10, 288, + 10, 10, 10, 292, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 168, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 54, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 73, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 120, 121, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 149, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 179, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 249, 250, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 272, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 79, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 133, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 239, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 264, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 285, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 178, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, + 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 71, + 71, 71, 71, 76, 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 89, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 103, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 143, 10, + 10, 10, 10, 154, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 200, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 213, 10, 215, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 229, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 244, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 261, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 271, 10, 10, 10, 10, 10, 10, 10, 10, 10, 281, 10, 10, 10, 10, 286, 10, 10, + 10, 290, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 118, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 237, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 184, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, + 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, + 10, 92, 10, 94, 10, 10, 10, 98, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 128, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 152, 10, 10, 10, 10, 10, 10, 10, 10, 10, 162, + 10, 10, 10, 10, 10, 10, 173, 170, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 204, 205, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 224, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 278, 10, 10, 10, 282, 10, 10, 10, 10, 287, 10, + 10, 10, 10, 10, 10, 10, 295, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 167, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 275, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 190, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 104, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 139, + 10, 10, 10, 137, 10, 10, 10, 10, 10, 10, 144, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 165, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 180, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 217, 10, 10, 10, 10, 10, 223, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 235, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 270, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 294, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 206, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, + 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, + 10, 10, 10, 10, 95, 10, 10, 10, 10, 10, 10, 10, 108, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 132, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 160, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 176, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 181, 10, 10, 10, 10, 10, 187, 10, 10, 10, 191, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 216, + 10, 10, 10, 10, 10, 222, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 273, 10, 10, 10, 277, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 293, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 209, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 88, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 192, 10, 10, 10, 196, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 218, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 115, 10, 10, 10, 10, 10, 10, 10, 123, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 135, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 158, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 177, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 194, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 210, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 228, + 10, 10, 10, 10, 10, 234, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 256, 10, 10, 10, 10, 10, 10, 10, 10, 10, 266, + 10, 10, 10, 10, 10, 10, 10, 274, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 289, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 236, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, + 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 126, + 10, 10, 10, 130, 131, 10, 10, 10, 10, 10, 10, 10, 10, 140, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 155, 10, 10, 10, 10, 10, 10, 10, + 163, 10, 10, 10, 10, 10, 10, 10, 171, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 195, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 214, 10, 10, + 10, 10, 10, 226, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 241, 10, 243, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 254, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 265, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, + 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, + 71, 71, 71, 71, 77, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 110, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 138, 10, 142, 141, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 153, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 175, 10, 10, 10, 10, 10, + 10, 10, 183, 10, 10, 10, 10, 10, 189, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 199, 10, 10, 10, 10, 10, 10, 10, 10, 208, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 232, 10, 10, 10, 10, 10, 10, 227, 10, 10, 10, 231, 10, 10, 10, + 10, 10, 255, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 253, 10, 10, 10, 10, 10, 259, 10, 10, 262, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 280, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 291, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 269, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 109, 10, 10, 10, 10, 10, + 119, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 145, 10, 10, 10, 10, 10, 151, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 166, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 182, 10, 10, 10, 10, 10, 188, 10, 10, + 203, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 207, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 233, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 257, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 267, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 276, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 201, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 284, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 75, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 260, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 53, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0, 0, 0, + 0, 0, 10, 10, 10, 90, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 9, 0, 0, 0, 0, 0, 8, 8, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, + 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 186, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 225, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 296, 10, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 297, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 298, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 300, 0, 0, 0, 0, 0, + }, + { + 0, 302, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 303, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, +}; + +static int8_t accepts[304] = { + -1, -1, 95, 95, 98, 69, 75, 98, 43, 42, 42, 59, 84, 64, 68, 92, 89, 45, 46, 57, 82, 55, + 53, 80, 52, 56, 54, 81, 94, 51, 1, -1, -1, 1, 58, -1, -1, 97, 96, 83, 2, 1, 1, -1, + -1, 1, -1, -1, 1, 2, -1, -1, 1, -1, 2, 2, 72, 71, 93, 77, 60, 85, 79, 73, 74, 76, + 78, 61, 86, 70, 98, 44, 44, 6, 44, 44, 44, 44, 44, 12, 49, 50, 63, 88, 67, 91, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 34, 42, 42, 42, 42, 42, 35, 42, 42, + 42, 42, 42, 36, 42, 42, 42, 42, 15, 42, 42, 42, 42, 32, 42, 42, 42, 13, 42, 42, 42, 41, + 42, 42, 42, 42, 42, 42, 29, 42, 42, 24, 42, 42, 42, 42, 16, 42, 42, 42, 42, 42, 42, 14, + 42, 42, 42, 42, 42, 17, 10, 42, 42, 42, 7, 42, 42, 40, 42, 42, 42, 42, 4, 42, 42, 25, + 42, 8, 42, 5, 20, 42, 42, 22, 42, 42, 42, 42, 42, 38, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 26, 42, 42, 19, 42, 42, 21, 42, 42, 42, 42, 42, 42, 42, 42, 39, 42, 42, + 42, 42, 42, 42, 42, 27, 42, 42, 42, 42, 42, 31, 42, 42, 42, 18, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 33, 42, 42, 42, 42, 37, 42, 42, 42, 42, + 11, 42, 42, 42, 3, 42, 42, 42, 42, 42, 42, 23, 42, 42, 42, 42, 42, 42, 42, 30, 42, 42, + 42, 42, 9, 42, 42, 42, 42, 42, 42, 42, 28, 47, 62, 87, 66, 90, 48, 65, +}; + +Token Lexer::next() { + // note that we cheat here: normally a lexer needs to worry about the case + // where a token has a prefix which is not itself a valid token - for instance, + // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid + // tokens. Our grammar doesn't have this property, so we can simplify the logic + // a bit. + int32_t startOffset = fOffset; + if (startOffset == fLength) { + return Token(Token::END_OF_FILE, startOffset, 0); + } + int16_t state = 1; + for (;;) { + if (fOffset >= fLength) { + if (accepts[state] == -1) { + return Token(Token::END_OF_FILE, startOffset, 0); + } + break; + } + uint8_t c = (uint8_t)fText[fOffset]; + if (c <= 8 || c >= 127) { + c = INVALID_CHAR; + } + int16_t newState = transitions[mappings[c]][state]; + if (!newState) { + break; + } + state = newState; + ++fOffset; + } + Token::Kind kind = (Token::Kind)accepts[state]; + return Token(kind, startOffset, fOffset - startOffset); +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLLexer.h b/gfx/skia/skia/src/sksl/SkSLLexer.h new file mode 100644 index 0000000000..233ccf9509 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLLexer.h @@ -0,0 +1,245 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +/***************************************************************************************** + ******************** This file was generated by sksllex. Do not edit. ******************* + *****************************************************************************************/ +#ifndef SKSL_Lexer +#define SKSL_Lexer +#include +#include +namespace SkSL { + +struct Token { + enum Kind { +#undef END_OF_FILE + END_OF_FILE, +#undef FLOAT_LITERAL + FLOAT_LITERAL, +#undef INT_LITERAL + INT_LITERAL, +#undef TRUE_LITERAL + TRUE_LITERAL, +#undef FALSE_LITERAL + FALSE_LITERAL, +#undef IF + IF, +#undef STATIC_IF + STATIC_IF, +#undef ELSE + ELSE, +#undef FOR + FOR, +#undef WHILE + WHILE, +#undef DO + DO, +#undef SWITCH + SWITCH, +#undef STATIC_SWITCH + STATIC_SWITCH, +#undef CASE + CASE, +#undef DEFAULT + DEFAULT, +#undef BREAK + BREAK, +#undef CONTINUE + CONTINUE, +#undef DISCARD + DISCARD, +#undef RETURN + RETURN, +#undef NULL_LITERAL + NULL_LITERAL, +#undef IN + IN, +#undef OUT + OUT, +#undef INOUT + INOUT, +#undef UNIFORM + UNIFORM, +#undef CONST + CONST, +#undef FLAT + FLAT, +#undef NOPERSPECTIVE + NOPERSPECTIVE, +#undef READONLY + READONLY, +#undef WRITEONLY + WRITEONLY, +#undef COHERENT + COHERENT, +#undef VOLATILE + VOLATILE, +#undef RESTRICT + RESTRICT, +#undef BUFFER + BUFFER, +#undef HASSIDEEFFECTS + HASSIDEEFFECTS, +#undef PLS + PLS, +#undef PLSIN + PLSIN, +#undef PLSOUT + PLSOUT, +#undef STRUCT + STRUCT, +#undef LAYOUT + LAYOUT, +#undef PRECISION + PRECISION, +#undef ENUM + ENUM, +#undef CLASS + CLASS, +#undef IDENTIFIER + IDENTIFIER, +#undef DIRECTIVE + DIRECTIVE, +#undef SECTION + SECTION, +#undef LPAREN + LPAREN, +#undef RPAREN + RPAREN, +#undef LBRACE + LBRACE, +#undef RBRACE + RBRACE, +#undef LBRACKET + LBRACKET, +#undef RBRACKET + RBRACKET, +#undef DOT + DOT, +#undef COMMA + COMMA, +#undef PLUSPLUS + PLUSPLUS, +#undef MINUSMINUS + MINUSMINUS, +#undef PLUS + PLUS, +#undef MINUS + MINUS, +#undef STAR + STAR, +#undef SLASH + SLASH, +#undef PERCENT + PERCENT, +#undef SHL + SHL, +#undef SHR + SHR, +#undef BITWISEOR + BITWISEOR, +#undef BITWISEXOR + BITWISEXOR, +#undef BITWISEAND + BITWISEAND, +#undef BITWISENOT + BITWISENOT, +#undef LOGICALOR + LOGICALOR, +#undef LOGICALXOR + LOGICALXOR, +#undef LOGICALAND + LOGICALAND, +#undef LOGICALNOT + LOGICALNOT, +#undef QUESTION + QUESTION, +#undef COLONCOLON + COLONCOLON, +#undef COLON + COLON, +#undef EQ + EQ, +#undef EQEQ + EQEQ, +#undef NEQ + NEQ, +#undef GT + GT, +#undef LT + LT, +#undef GTEQ + GTEQ, +#undef LTEQ + LTEQ, +#undef PLUSEQ + PLUSEQ, +#undef MINUSEQ + MINUSEQ, +#undef STAREQ + STAREQ, +#undef SLASHEQ + SLASHEQ, +#undef PERCENTEQ + PERCENTEQ, +#undef SHLEQ + SHLEQ, +#undef SHREQ + SHREQ, +#undef BITWISEOREQ + BITWISEOREQ, +#undef BITWISEXOREQ + BITWISEXOREQ, +#undef BITWISEANDEQ + BITWISEANDEQ, +#undef LOGICALOREQ + LOGICALOREQ, +#undef LOGICALXOREQ + LOGICALXOREQ, +#undef LOGICALANDEQ + LOGICALANDEQ, +#undef SEMICOLON + SEMICOLON, +#undef ARROW + ARROW, +#undef WHITESPACE + WHITESPACE, +#undef LINE_COMMENT + LINE_COMMENT, +#undef BLOCK_COMMENT + BLOCK_COMMENT, +#undef INVALID + INVALID, + }; + + Token() : fKind(Kind::INVALID), fOffset(-1), fLength(-1) {} + + Token(Kind kind, int32_t offset, int32_t length) + : fKind(kind), fOffset(offset), fLength(length) {} + + Kind fKind; + int fOffset; + int fLength; +}; + +class Lexer { +public: + void start(const char* text, int32_t length) { + fText = text; + fLength = length; + fOffset = 0; + } + + Token next(); + +private: + const char* fText; + int32_t fLength; + int32_t fOffset; +}; + +} // namespace +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLMain.cpp b/gfx/skia/skia/src/sksl/SkSLMain.cpp new file mode 100644 index 0000000000..b8009cddee --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMain.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLFileOutputStream.h" + +// Given the path to a file (e.g. src/gpu/effects/GrFooFragmentProcessor.fp) and the expected +// filename prefix and suffix (e.g. "Gr" and ".fp"), returns the "base name" of the +// file (in this case, 'FooFragmentProcessor'). If no match, returns the empty string. +static SkSL::String base_name(const char* fpPath, const char* prefix, const char* suffix) { + SkSL::String result; + const char* end = fpPath + strlen(fpPath); + const char* fileName = end; + // back up until we find a slash + while (fileName != fpPath && '/' != *(fileName - 1) && '\\' != *(fileName - 1)) { + --fileName; + } + if (!strncmp(fileName, prefix, strlen(prefix)) && + !strncmp(end - strlen(suffix), suffix, strlen(suffix))) { + result.append(fileName + strlen(prefix), end - fileName - strlen(prefix) - strlen(suffix)); + } + return result; +} + +/** + * Very simple standalone executable to facilitate testing. + */ +int main(int argc, const char** argv) { + if (argc != 3) { + printf("usage: skslc \n"); + exit(1); + } + SkSL::Program::Kind kind; + SkSL::String input(argv[1]); + if (input.endsWith(".vert")) { + kind = SkSL::Program::kVertex_Kind; + } else if (input.endsWith(".frag")) { + kind = SkSL::Program::kFragment_Kind; + } else if (input.endsWith(".geom")) { + kind = SkSL::Program::kGeometry_Kind; + } else if (input.endsWith(".fp")) { + kind = SkSL::Program::kFragmentProcessor_Kind; + } else if (input.endsWith(".stage")) { + kind = SkSL::Program::kPipelineStage_Kind; + } else { + printf("input filename must end in '.vert', '.frag', '.geom', '.fp', or '.stage'\n"); + exit(1); + } + + std::ifstream in(argv[1]); + std::string stdText((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + SkSL::String text(stdText.c_str()); + if (in.rdstate()) { + printf("error reading '%s'\n", argv[1]); + exit(2); + } + SkSL::Program::Settings settings; + settings.fArgs.insert(std::make_pair("gpImplementsDistanceVector", 1)); + SkSL::String name(argv[2]); + if (name.endsWith(".spirv")) { + SkSL::FileOutputStream out(argv[2]); + SkSL::Compiler compiler; + if (!out.isValid()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + std::unique_ptr program = compiler.convertProgram(kind, text, settings); + if (!program || !compiler.toSPIRV(*program, out)) { + printf("%s", compiler.errorText().c_str()); + exit(3); + } + if (!out.close()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + } else if (name.endsWith(".glsl")) { + SkSL::FileOutputStream out(argv[2]); + SkSL::Compiler compiler; + if (!out.isValid()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + std::unique_ptr program = compiler.convertProgram(kind, text, settings); + if (!program || !compiler.toGLSL(*program, out)) { + printf("%s", compiler.errorText().c_str()); + exit(3); + } + if (!out.close()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + } else if (name.endsWith(".metal")) { + SkSL::FileOutputStream out(argv[2]); + SkSL::Compiler compiler; + if (!out.isValid()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + std::unique_ptr program = compiler.convertProgram(kind, text, settings); + if (!program || !compiler.toMetal(*program, out)) { + printf("%s", compiler.errorText().c_str()); + exit(3); + } + if (!out.close()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + } else if (name.endsWith(".h")) { + SkSL::FileOutputStream out(argv[2]); + SkSL::Compiler compiler(SkSL::Compiler::kPermitInvalidStaticTests_Flag); + if (!out.isValid()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + settings.fReplaceSettings = false; + std::unique_ptr program = compiler.convertProgram(kind, text, settings); + if (!program || !compiler.toH(*program, base_name(argv[1], "Gr", ".fp"), out)) { + printf("%s", compiler.errorText().c_str()); + exit(3); + } + if (!out.close()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + } else if (name.endsWith(".cpp")) { + SkSL::FileOutputStream out(argv[2]); + SkSL::Compiler compiler(SkSL::Compiler::kPermitInvalidStaticTests_Flag); + if (!out.isValid()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + settings.fReplaceSettings = false; + std::unique_ptr program = compiler.convertProgram(kind, text, settings); + if (!program || !compiler.toCPP(*program, base_name(argv[1], "Gr", ".fp"), out)) { + printf("%s", compiler.errorText().c_str()); + exit(3); + } + if (!out.close()) { + printf("error writing '%s'\n", argv[2]); + exit(4); + } + } else { + printf("expected output filename to end with '.spirv', '.glsl', '.cpp', '.h', or '.metal'"); + exit(1); + } +} diff --git a/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h b/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h new file mode 100644 index 0000000000..4b3227601d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h @@ -0,0 +1,140 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKIASL_MEMORYLAYOUT +#define SKIASL_MEMORYLAYOUT + +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +class MemoryLayout { +public: + enum Standard { + k140_Standard, + k430_Standard, + kMetal_Standard + }; + + MemoryLayout(Standard std) + : fStd(std) {} + + static size_t vector_alignment(size_t componentSize, int columns) { + return componentSize * (columns + columns % 2); + } + + /** + * Rounds up to the nearest multiple of 16 if in std140, otherwise returns the parameter + * unchanged (std140 requires various things to be rounded up to the nearest multiple of 16, + * std430 does not). + */ + size_t roundUpIfNeeded(size_t raw) const { + switch (fStd) { + case k140_Standard: return (raw + 15) & ~15; + case k430_Standard: return raw; + case kMetal_Standard: return raw; + } + ABORT("unreachable"); + } + + /** + * Returns a type's required alignment when used as a standalone variable. + */ + size_t alignment(const Type& type) const { + // See OpenGL Spec 7.6.2.2 Standard Uniform Block Layout + switch (type.kind()) { + case Type::kScalar_Kind: + return this->size(type); + case Type::kVector_Kind: + return vector_alignment(this->size(type.componentType()), type.columns()); + case Type::kMatrix_Kind: + return this->roundUpIfNeeded(vector_alignment(this->size(type.componentType()), + type.rows())); + case Type::kArray_Kind: + return this->roundUpIfNeeded(this->alignment(type.componentType())); + case Type::kStruct_Kind: { + size_t result = 0; + for (const auto& f : type.fields()) { + size_t alignment = this->alignment(*f.fType); + if (alignment > result) { + result = alignment; + } + } + return this->roundUpIfNeeded(result); + } + default: + ABORT("cannot determine size of type %s", type.name().c_str()); + } + } + + /** + * For matrices and arrays, returns the number of bytes from the start of one entry (row, in + * the case of matrices) to the start of the next. + */ + size_t stride(const Type& type) const { + switch (type.kind()) { + case Type::kMatrix_Kind: { + size_t base = vector_alignment(this->size(type.componentType()), type.rows()); + return this->roundUpIfNeeded(base); + } + case Type::kArray_Kind: { + int align = this->alignment(type.componentType()); + int stride = this->size(type.componentType()) + align - 1; + stride -= stride % align; + return this->roundUpIfNeeded(stride); + } + default: + ABORT("type does not have a stride"); + } + } + + /** + * Returns the size of a type in bytes. + */ + size_t size(const Type& type) const { + switch (type.kind()) { + case Type::kScalar_Kind: + if (type.name() == "bool") { + return 1; + } + // FIXME need to take precision into account, once we figure out how we want to + // handle it... + return 4; + case Type::kVector_Kind: + if (fStd == kMetal_Standard && type.columns() == 3) { + return 4 * this->size(type.componentType()); + } + return type.columns() * this->size(type.componentType()); + case Type::kMatrix_Kind: // fall through + case Type::kArray_Kind: + return type.columns() * this->stride(type); + case Type::kStruct_Kind: { + size_t total = 0; + for (const auto& f : type.fields()) { + size_t alignment = this->alignment(*f.fType); + if (total % alignment != 0) { + total += alignment - total % alignment; + } + SkASSERT(total % alignment == 0); + total += this->size(*f.fType); + } + size_t alignment = this->alignment(type); + SkASSERT(!type.fields().size() || + (0 == alignment % this->alignment(*type.fields()[0].fType))); + return (total + alignment - 1) & ~(alignment - 1); + } + default: + ABORT("cannot determine size of type %s", type.name().c_str()); + } + } + + const Standard fStd; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.cpp new file mode 100644 index 0000000000..641e4c0435 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.cpp @@ -0,0 +1,1702 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLMetalCodeGenerator.h" + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#ifdef SK_MOLTENVK + static const uint32_t MVKMagicNum = 0x19960412; +#endif + +namespace SkSL { + +void MetalCodeGenerator::setupIntrinsics() { +#define METAL(x) std::make_pair(kMetal_IntrinsicKind, k ## x ## _MetalIntrinsic) +#define SPECIAL(x) std::make_pair(kSpecial_IntrinsicKind, k ## x ## _SpecialIntrinsic) + fIntrinsicMap[String("sample")] = SPECIAL(Texture); + fIntrinsicMap[String("mod")] = SPECIAL(Mod); + fIntrinsicMap[String("equal")] = METAL(Equal); + fIntrinsicMap[String("notEqual")] = METAL(NotEqual); + fIntrinsicMap[String("lessThan")] = METAL(LessThan); + fIntrinsicMap[String("lessThanEqual")] = METAL(LessThanEqual); + fIntrinsicMap[String("greaterThan")] = METAL(GreaterThan); + fIntrinsicMap[String("greaterThanEqual")] = METAL(GreaterThanEqual); +} + +void MetalCodeGenerator::write(const char* s) { + if (!s[0]) { + return; + } + if (fAtLineStart) { + for (int i = 0; i < fIndentation; i++) { + fOut->writeText(" "); + } + } + fOut->writeText(s); + fAtLineStart = false; +} + +void MetalCodeGenerator::writeLine(const char* s) { + this->write(s); + fOut->writeText(fLineEnding); + fAtLineStart = true; +} + +void MetalCodeGenerator::write(const String& s) { + this->write(s.c_str()); +} + +void MetalCodeGenerator::writeLine(const String& s) { + this->writeLine(s.c_str()); +} + +void MetalCodeGenerator::writeLine() { + this->writeLine(""); +} + +void MetalCodeGenerator::writeExtension(const Extension& ext) { + this->writeLine("#extension " + ext.fName + " : enable"); +} + +void MetalCodeGenerator::writeType(const Type& type) { + switch (type.kind()) { + case Type::kStruct_Kind: + for (const Type* search : fWrittenStructs) { + if (*search == type) { + // already written + this->write(type.name()); + return; + } + } + fWrittenStructs.push_back(&type); + this->writeLine("struct " + type.name() + " {"); + fIndentation++; + this->writeFields(type.fields(), type.fOffset); + fIndentation--; + this->write("}"); + break; + case Type::kVector_Kind: + this->writeType(type.componentType()); + this->write(to_string(type.columns())); + break; + case Type::kMatrix_Kind: + this->writeType(type.componentType()); + this->write(to_string(type.columns())); + this->write("x"); + this->write(to_string(type.rows())); + break; + case Type::kSampler_Kind: + this->write("texture2d "); // FIXME - support other texture types; + break; + default: + if (type == *fContext.fHalf_Type) { + // FIXME - Currently only supporting floats in MSL to avoid type coercion issues. + this->write(fContext.fFloat_Type->name()); + } else if (type == *fContext.fByte_Type) { + this->write("char"); + } else if (type == *fContext.fUByte_Type) { + this->write("uchar"); + } else { + this->write(type.name()); + } + } +} + +void MetalCodeGenerator::writeExpression(const Expression& expr, Precedence parentPrecedence) { + switch (expr.fKind) { + case Expression::kBinary_Kind: + this->writeBinaryExpression((BinaryExpression&) expr, parentPrecedence); + break; + case Expression::kBoolLiteral_Kind: + this->writeBoolLiteral((BoolLiteral&) expr); + break; + case Expression::kConstructor_Kind: + this->writeConstructor((Constructor&) expr, parentPrecedence); + break; + case Expression::kIntLiteral_Kind: + this->writeIntLiteral((IntLiteral&) expr); + break; + case Expression::kFieldAccess_Kind: + this->writeFieldAccess(((FieldAccess&) expr)); + break; + case Expression::kFloatLiteral_Kind: + this->writeFloatLiteral(((FloatLiteral&) expr)); + break; + case Expression::kFunctionCall_Kind: + this->writeFunctionCall((FunctionCall&) expr); + break; + case Expression::kPrefix_Kind: + this->writePrefixExpression((PrefixExpression&) expr, parentPrecedence); + break; + case Expression::kPostfix_Kind: + this->writePostfixExpression((PostfixExpression&) expr, parentPrecedence); + break; + case Expression::kSetting_Kind: + this->writeSetting((Setting&) expr); + break; + case Expression::kSwizzle_Kind: + this->writeSwizzle((Swizzle&) expr); + break; + case Expression::kVariableReference_Kind: + this->writeVariableReference((VariableReference&) expr); + break; + case Expression::kTernary_Kind: + this->writeTernaryExpression((TernaryExpression&) expr, parentPrecedence); + break; + case Expression::kIndex_Kind: + this->writeIndexExpression((IndexExpression&) expr); + break; + default: + ABORT("unsupported expression: %s", expr.description().c_str()); + } +} + +void MetalCodeGenerator::writeIntrinsicCall(const FunctionCall& c) { + auto i = fIntrinsicMap.find(c.fFunction.fName); + SkASSERT(i != fIntrinsicMap.end()); + Intrinsic intrinsic = i->second; + int32_t intrinsicId = intrinsic.second; + switch (intrinsic.first) { + case kSpecial_IntrinsicKind: + return this->writeSpecialIntrinsic(c, (SpecialIntrinsic) intrinsicId); + break; + case kMetal_IntrinsicKind: + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + switch ((MetalIntrinsic) intrinsicId) { + case kEqual_MetalIntrinsic: + this->write(" == "); + break; + case kNotEqual_MetalIntrinsic: + this->write(" != "); + break; + case kLessThan_MetalIntrinsic: + this->write(" < "); + break; + case kLessThanEqual_MetalIntrinsic: + this->write(" <= "); + break; + case kGreaterThan_MetalIntrinsic: + this->write(" > "); + break; + case kGreaterThanEqual_MetalIntrinsic: + this->write(" >= "); + break; + default: + ABORT("unsupported metal intrinsic kind"); + } + this->writeExpression(*c.fArguments[1], kSequence_Precedence); + break; + default: + ABORT("unsupported intrinsic kind"); + } +} + +void MetalCodeGenerator::writeFunctionCall(const FunctionCall& c) { + const auto& entry = fIntrinsicMap.find(c.fFunction.fName); + if (entry != fIntrinsicMap.end()) { + this->writeIntrinsicCall(c); + return; + } + if (c.fFunction.fBuiltin && "atan" == c.fFunction.fName && 2 == c.fArguments.size()) { + this->write("atan2"); + } else if (c.fFunction.fBuiltin && "inversesqrt" == c.fFunction.fName) { + this->write("rsqrt"); + } else if (c.fFunction.fBuiltin && "inverse" == c.fFunction.fName) { + SkASSERT(c.fArguments.size() == 1); + this->writeInverseHack(*c.fArguments[0]); + } else if (c.fFunction.fBuiltin && "dFdx" == c.fFunction.fName) { + this->write("dfdx"); + } else if (c.fFunction.fBuiltin && "dFdy" == c.fFunction.fName) { + // Flipping Y also negates the Y derivatives. + this->write((fProgram.fSettings.fFlipY) ? "-dfdy" : "dfdy"); + } else { + this->writeName(c.fFunction.fName); + } + this->write("("); + const char* separator = ""; + if (this->requirements(c.fFunction) & kInputs_Requirement) { + this->write("_in"); + separator = ", "; + } + if (this->requirements(c.fFunction) & kOutputs_Requirement) { + this->write(separator); + this->write("_out"); + separator = ", "; + } + if (this->requirements(c.fFunction) & kUniforms_Requirement) { + this->write(separator); + this->write("_uniforms"); + separator = ", "; + } + if (this->requirements(c.fFunction) & kGlobals_Requirement) { + this->write(separator); + this->write("_globals"); + separator = ", "; + } + if (this->requirements(c.fFunction) & kFragCoord_Requirement) { + this->write(separator); + this->write("_fragCoord"); + separator = ", "; + } + for (size_t i = 0; i < c.fArguments.size(); ++i) { + const Expression& arg = *c.fArguments[i]; + this->write(separator); + separator = ", "; + if (c.fFunction.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) { + this->write("&"); + } + this->writeExpression(arg, kSequence_Precedence); + } + this->write(")"); +} + +void MetalCodeGenerator::writeInverseHack(const Expression& mat) { + String typeName = mat.fType.name(); + String name = typeName + "_inverse"; + if (mat.fType == *fContext.fFloat2x2_Type || mat.fType == *fContext.fHalf2x2_Type) { + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + typeName + " " + name + "(" + typeName + " m) {" + " return float2x2(m[1][1], -m[0][1], -m[1][0], m[0][0]) * (1/determinant(m));" + "}" + ).c_str()); + } + } + else if (mat.fType == *fContext.fFloat3x3_Type || mat.fType == *fContext.fHalf3x3_Type) { + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + typeName + " " + name + "(" + typeName + " m) {" + " float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];" + " float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];" + " float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];" + " float b01 = a22 * a11 - a12 * a21;" + " float b11 = -a22 * a10 + a12 * a20;" + " float b21 = a21 * a10 - a11 * a20;" + " float det = a00 * b01 + a01 * b11 + a02 * b21;" + " return " + typeName + + " (b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11)," + " b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10)," + " b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) * " + " (1/det);" + "}" + ).c_str()); + } + } + else if (mat.fType == *fContext.fFloat4x4_Type || mat.fType == *fContext.fHalf4x4_Type) { + if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) { + fWrittenIntrinsics.insert(name); + fExtraFunctions.writeText(( + typeName + " " + name + "(" + typeName + " m) {" + " float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3];" + " float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3];" + " float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3];" + " float a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3];" + " float b00 = a00 * a11 - a01 * a10;" + " float b01 = a00 * a12 - a02 * a10;" + " float b02 = a00 * a13 - a03 * a10;" + " float b03 = a01 * a12 - a02 * a11;" + " float b04 = a01 * a13 - a03 * a11;" + " float b05 = a02 * a13 - a03 * a12;" + " float b06 = a20 * a31 - a21 * a30;" + " float b07 = a20 * a32 - a22 * a30;" + " float b08 = a20 * a33 - a23 * a30;" + " float b09 = a21 * a32 - a22 * a31;" + " float b10 = a21 * a33 - a23 * a31;" + " float b11 = a22 * a33 - a23 * a32;" + " float det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - " + " b04 * b07 + b05 * b06;" + " return " + typeName + "(a11 * b11 - a12 * b10 + a13 * b09," + " a02 * b10 - a01 * b11 - a03 * b09," + " a31 * b05 - a32 * b04 + a33 * b03," + " a22 * b04 - a21 * b05 - a23 * b03," + " a12 * b08 - a10 * b11 - a13 * b07," + " a00 * b11 - a02 * b08 + a03 * b07," + " a32 * b02 - a30 * b05 - a33 * b01," + " a20 * b05 - a22 * b02 + a23 * b01," + " a10 * b10 - a11 * b08 + a13 * b06," + " a01 * b08 - a00 * b10 - a03 * b06," + " a30 * b04 - a31 * b02 + a33 * b00," + " a21 * b02 - a20 * b04 - a23 * b00," + " a11 * b07 - a10 * b09 - a12 * b06," + " a00 * b09 - a01 * b07 + a02 * b06," + " a31 * b01 - a30 * b03 - a32 * b00," + " a20 * b03 - a21 * b01 + a22 * b00) / det;" + "}" + ).c_str()); + } + } + this->write(name); +} + +void MetalCodeGenerator::writeSpecialIntrinsic(const FunctionCall & c, SpecialIntrinsic kind) { + switch (kind) { + case kTexture_SpecialIntrinsic: + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(".sample("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(SAMPLER_SUFFIX); + this->write(", "); + this->writeExpression(*c.fArguments[1], kSequence_Precedence); + if (c.fArguments[1]->fType == *fContext.fFloat3_Type) { + this->write(".xy)"); // FIXME - add projection functionality + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat2_Type); + this->write(")"); + } + break; + case kMod_SpecialIntrinsic: + // fmod(x, y) in metal calculates x - y * trunc(x / y) instead of x - y * floor(x / y) + this->write("(("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(") - ("); + this->writeExpression(*c.fArguments[1], kSequence_Precedence); + this->write(") * floor(("); + this->writeExpression(*c.fArguments[0], kSequence_Precedence); + this->write(") / ("); + this->writeExpression(*c.fArguments[1], kSequence_Precedence); + this->write(")))"); + break; + default: + ABORT("unsupported special intrinsic kind"); + } +} + +// If it hasn't already been written, writes a constructor for 'matrix' which takes a single value +// of type 'arg'. +String MetalCodeGenerator::getMatrixConstructHelper(const Type& matrix, const Type& arg) { + String key = matrix.name() + arg.name(); + auto found = fHelpers.find(key); + if (found != fHelpers.end()) { + return found->second; + } + String name; + int columns = matrix.columns(); + int rows = matrix.rows(); + if (arg.isNumber()) { + // creating a matrix from a single scalar value + name = "float" + to_string(columns) + "x" + to_string(rows) + "_from_float"; + fExtraFunctions.printf("float%dx%d %s(float x) {\n", + columns, rows, name.c_str()); + fExtraFunctions.printf(" return float%dx%d(", columns, rows); + for (int i = 0; i < columns; ++i) { + if (i > 0) { + fExtraFunctions.writeText(", "); + } + fExtraFunctions.printf("float%d(", rows); + for (int j = 0; j < rows; ++j) { + if (j > 0) { + fExtraFunctions.writeText(", "); + } + if (i == j) { + fExtraFunctions.writeText("x"); + } else { + fExtraFunctions.writeText("0"); + } + } + fExtraFunctions.writeText(")"); + } + fExtraFunctions.writeText(");\n}\n"); + } else if (arg.kind() == Type::kMatrix_Kind) { + // creating a matrix from another matrix + int argColumns = arg.columns(); + int argRows = arg.rows(); + name = "float" + to_string(columns) + "x" + to_string(rows) + "_from_float" + + to_string(argColumns) + "x" + to_string(argRows); + fExtraFunctions.printf("float%dx%d %s(float%dx%d m) {\n", + columns, rows, name.c_str(), argColumns, argRows); + fExtraFunctions.printf(" return float%dx%d(", columns, rows); + for (int i = 0; i < columns; ++i) { + if (i > 0) { + fExtraFunctions.writeText(", "); + } + fExtraFunctions.printf("float%d(", rows); + for (int j = 0; j < rows; ++j) { + if (j > 0) { + fExtraFunctions.writeText(", "); + } + if (i < argColumns && j < argRows) { + fExtraFunctions.printf("m[%d][%d]", i, j); + } else { + fExtraFunctions.writeText("0"); + } + } + fExtraFunctions.writeText(")"); + } + fExtraFunctions.writeText(");\n}\n"); + } else if (matrix.rows() == 2 && matrix.columns() == 2 && arg == *fContext.fFloat4_Type) { + // float2x2(float4) doesn't work, need to split it into float2x2(float2, float2) + name = "float2x2_from_float4"; + fExtraFunctions.printf( + "float2x2 %s(float4 v) {\n" + " return float2x2(float2(v[0], v[1]), float2(v[2], v[3]));\n" + "}\n", + name.c_str() + ); + } else { + SkASSERT(false); + name = ""; + } + fHelpers[key] = name; + return name; +} + +bool MetalCodeGenerator::canCoerce(const Type& t1, const Type& t2) { + if (t1.columns() != t2.columns() || t1.rows() != t2.rows()) { + return false; + } + if (t1.columns() > 1) { + return this->canCoerce(t1.componentType(), t2.componentType()); + } + return t1.isFloat() && t2.isFloat(); +} + +void MetalCodeGenerator::writeConstructor(const Constructor& c, Precedence parentPrecedence) { + if (c.fArguments.size() == 1 && this->canCoerce(c.fType, c.fArguments[0]->fType)) { + this->writeExpression(*c.fArguments[0], parentPrecedence); + return; + } + if (c.fType.kind() == Type::kMatrix_Kind && c.fArguments.size() == 1) { + const Expression& arg = *c.fArguments[0]; + String name = this->getMatrixConstructHelper(c.fType, arg.fType); + this->write(name); + this->write("("); + this->writeExpression(arg, kSequence_Precedence); + this->write(")"); + } else { + this->writeType(c.fType); + this->write("("); + const char* separator = ""; + int scalarCount = 0; + for (const auto& arg : c.fArguments) { + this->write(separator); + separator = ", "; + if (Type::kMatrix_Kind == c.fType.kind() && arg->fType.columns() != c.fType.rows()) { + // merge scalars and smaller vectors together + if (!scalarCount) { + this->writeType(c.fType.componentType()); + this->write(to_string(c.fType.rows())); + this->write("("); + } + scalarCount += arg->fType.columns(); + } + this->writeExpression(*arg, kSequence_Precedence); + if (scalarCount && scalarCount == c.fType.rows()) { + this->write(")"); + scalarCount = 0; + } + } + this->write(")"); + } +} + +void MetalCodeGenerator::writeFragCoord() { + if (fRTHeightName.length()) { + this->write("float4(_fragCoord.x, "); + this->write(fRTHeightName.c_str()); + this->write(" - _fragCoord.y, 0.0, _fragCoord.w)"); + } else { + this->write("float4(_fragCoord.x, _fragCoord.y, 0.0, _fragCoord.w)"); + } +} + +void MetalCodeGenerator::writeVariableReference(const VariableReference& ref) { + switch (ref.fVariable.fModifiers.fLayout.fBuiltin) { + case SK_FRAGCOLOR_BUILTIN: + this->write("_out->sk_FragColor"); + break; + case SK_FRAGCOORD_BUILTIN: + this->writeFragCoord(); + break; + case SK_VERTEXID_BUILTIN: + this->write("sk_VertexID"); + break; + case SK_INSTANCEID_BUILTIN: + this->write("sk_InstanceID"); + break; + case SK_CLOCKWISE_BUILTIN: + // We'd set the front facing winding in the MTLRenderCommandEncoder to be counter + // clockwise to match Skia convention. This is also the default in MoltenVK. + this->write(fProgram.fSettings.fFlipY ? "_frontFacing" : "(!_frontFacing)"); + break; + default: + if (Variable::kGlobal_Storage == ref.fVariable.fStorage) { + if (ref.fVariable.fModifiers.fFlags & Modifiers::kIn_Flag) { + this->write("_in."); + } else if (ref.fVariable.fModifiers.fFlags & Modifiers::kOut_Flag) { + this->write("_out->"); + } else if (ref.fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag && + ref.fVariable.fType.kind() != Type::kSampler_Kind) { + this->write("_uniforms."); + } else { + this->write("_globals->"); + } + } + this->writeName(ref.fVariable.fName); + } +} + +void MetalCodeGenerator::writeIndexExpression(const IndexExpression& expr) { + this->writeExpression(*expr.fBase, kPostfix_Precedence); + this->write("["); + this->writeExpression(*expr.fIndex, kTopLevel_Precedence); + this->write("]"); +} + +void MetalCodeGenerator::writeFieldAccess(const FieldAccess& f) { + const Type::Field* field = &f.fBase->fType.fields()[f.fFieldIndex]; + if (FieldAccess::kDefault_OwnerKind == f.fOwnerKind) { + this->writeExpression(*f.fBase, kPostfix_Precedence); + this->write("."); + } + switch (field->fModifiers.fLayout.fBuiltin) { + case SK_CLIPDISTANCE_BUILTIN: + this->write("gl_ClipDistance"); + break; + case SK_POSITION_BUILTIN: + this->write("_out->sk_Position"); + break; + default: + if (field->fName == "sk_PointSize") { + this->write("_out->sk_PointSize"); + } else { + if (FieldAccess::kAnonymousInterfaceBlock_OwnerKind == f.fOwnerKind) { + this->write("_globals->"); + this->write(fInterfaceBlockNameMap[fInterfaceBlockMap[field]]); + this->write("->"); + } + this->writeName(field->fName); + } + } +} + +void MetalCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + int last = swizzle.fComponents.back(); + if (last == SKSL_SWIZZLE_0 || last == SKSL_SWIZZLE_1) { + this->writeType(swizzle.fType); + this->write("("); + } + this->writeExpression(*swizzle.fBase, kPostfix_Precedence); + this->write("."); + for (int c : swizzle.fComponents) { + if (c >= 0) { + this->write(&("x\0y\0z\0w\0"[c * 2])); + } + } + if (last == SKSL_SWIZZLE_0) { + this->write(", 0)"); + } + else if (last == SKSL_SWIZZLE_1) { + this->write(", 1)"); + } +} + +MetalCodeGenerator::Precedence MetalCodeGenerator::GetBinaryPrecedence(Token::Kind op) { + switch (op) { + case Token::STAR: // fall through + case Token::SLASH: // fall through + case Token::PERCENT: return MetalCodeGenerator::kMultiplicative_Precedence; + case Token::PLUS: // fall through + case Token::MINUS: return MetalCodeGenerator::kAdditive_Precedence; + case Token::SHL: // fall through + case Token::SHR: return MetalCodeGenerator::kShift_Precedence; + case Token::LT: // fall through + case Token::GT: // fall through + case Token::LTEQ: // fall through + case Token::GTEQ: return MetalCodeGenerator::kRelational_Precedence; + case Token::EQEQ: // fall through + case Token::NEQ: return MetalCodeGenerator::kEquality_Precedence; + case Token::BITWISEAND: return MetalCodeGenerator::kBitwiseAnd_Precedence; + case Token::BITWISEXOR: return MetalCodeGenerator::kBitwiseXor_Precedence; + case Token::BITWISEOR: return MetalCodeGenerator::kBitwiseOr_Precedence; + case Token::LOGICALAND: return MetalCodeGenerator::kLogicalAnd_Precedence; + case Token::LOGICALXOR: return MetalCodeGenerator::kLogicalXor_Precedence; + case Token::LOGICALOR: return MetalCodeGenerator::kLogicalOr_Precedence; + case Token::EQ: // fall through + case Token::PLUSEQ: // fall through + case Token::MINUSEQ: // fall through + case Token::STAREQ: // fall through + case Token::SLASHEQ: // fall through + case Token::PERCENTEQ: // fall through + case Token::SHLEQ: // fall through + case Token::SHREQ: // fall through + case Token::LOGICALANDEQ: // fall through + case Token::LOGICALXOREQ: // fall through + case Token::LOGICALOREQ: // fall through + case Token::BITWISEANDEQ: // fall through + case Token::BITWISEXOREQ: // fall through + case Token::BITWISEOREQ: return MetalCodeGenerator::kAssignment_Precedence; + case Token::COMMA: return MetalCodeGenerator::kSequence_Precedence; + default: ABORT("unsupported binary operator"); + } +} + +void MetalCodeGenerator::writeMatrixTimesEqualHelper(const Type& left, const Type& right, + const Type& result) { + String key = "TimesEqual" + left.name() + right.name(); + if (fHelpers.find(key) == fHelpers.end()) { + fExtraFunctions.printf("%s operator*=(thread %s& left, thread const %s& right) {\n" + " left = left * right;\n" + " return left;\n" + "}", result.name().c_str(), left.name().c_str(), + right.name().c_str()); + } +} + +void MetalCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + Precedence precedence = GetBinaryPrecedence(b.fOperator); + bool needParens = precedence >= parentPrecedence; + switch (b.fOperator) { + case Token::EQEQ: + if (b.fLeft->fType.kind() == Type::kVector_Kind) { + this->write("all"); + needParens = true; + } + break; + case Token::NEQ: + if (b.fLeft->fType.kind() == Type::kVector_Kind) { + this->write("any"); + needParens = true; + } + break; + default: + break; + } + if (needParens) { + this->write("("); + } + if (Compiler::IsAssignment(b.fOperator) && + Expression::kVariableReference_Kind == b.fLeft->fKind && + Variable::kParameter_Storage == ((VariableReference&) *b.fLeft).fVariable.fStorage && + (((VariableReference&) *b.fLeft).fVariable.fModifiers.fFlags & Modifiers::kOut_Flag)) { + // writing to an out parameter. Since we have to turn those into pointers, we have to + // dereference it here. + this->write("*"); + } + if (b.fOperator == Token::STAREQ && b.fLeft->fType.kind() == Type::kMatrix_Kind && + b.fRight->fType.kind() == Type::kMatrix_Kind) { + this->writeMatrixTimesEqualHelper(b.fLeft->fType, b.fRight->fType, b.fType); + } + this->writeExpression(*b.fLeft, precedence); + if (b.fOperator != Token::EQ && Compiler::IsAssignment(b.fOperator) && + Expression::kSwizzle_Kind == b.fLeft->fKind && !b.fLeft->hasSideEffects()) { + // This doesn't compile in Metal: + // float4 x = float4(1); + // x.xy *= float2x2(...); + // with the error message "non-const reference cannot bind to vector element", + // but switching it to x.xy = x.xy * float2x2(...) fixes it. We perform this tranformation + // as long as the LHS has no side effects, and hope for the best otherwise. + this->write(" = "); + this->writeExpression(*b.fLeft, kAssignment_Precedence); + this->write(" "); + String op = Compiler::OperatorName(b.fOperator); + SkASSERT(op.endsWith("=")); + this->write(op.substr(0, op.size() - 1).c_str()); + this->write(" "); + } else { + this->write(String(" ") + Compiler::OperatorName(b.fOperator) + " "); + } + this->writeExpression(*b.fRight, precedence); + if (needParens) { + this->write(")"); + } +} + +void MetalCodeGenerator::writeTernaryExpression(const TernaryExpression& t, + Precedence parentPrecedence) { + if (kTernary_Precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*t.fTest, kTernary_Precedence); + this->write(" ? "); + this->writeExpression(*t.fIfTrue, kTernary_Precedence); + this->write(" : "); + this->writeExpression(*t.fIfFalse, kTernary_Precedence); + if (kTernary_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void MetalCodeGenerator::writePrefixExpression(const PrefixExpression& p, + Precedence parentPrecedence) { + if (kPrefix_Precedence >= parentPrecedence) { + this->write("("); + } + this->write(Compiler::OperatorName(p.fOperator)); + this->writeExpression(*p.fOperand, kPrefix_Precedence); + if (kPrefix_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void MetalCodeGenerator::writePostfixExpression(const PostfixExpression& p, + Precedence parentPrecedence) { + if (kPostfix_Precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*p.fOperand, kPostfix_Precedence); + this->write(Compiler::OperatorName(p.fOperator)); + if (kPostfix_Precedence >= parentPrecedence) { + this->write(")"); + } +} + +void MetalCodeGenerator::writeBoolLiteral(const BoolLiteral& b) { + this->write(b.fValue ? "true" : "false"); +} + +void MetalCodeGenerator::writeIntLiteral(const IntLiteral& i) { + if (i.fType == *fContext.fUInt_Type) { + this->write(to_string(i.fValue & 0xffffffff) + "u"); + } else { + this->write(to_string((int32_t) i.fValue)); + } +} + +void MetalCodeGenerator::writeFloatLiteral(const FloatLiteral& f) { + this->write(to_string(f.fValue)); +} + +void MetalCodeGenerator::writeSetting(const Setting& s) { + ABORT("internal error; setting was not folded to a constant during compilation\n"); +} + +void MetalCodeGenerator::writeFunction(const FunctionDefinition& f) { + fRTHeightName = fProgram.fInputs.fRTHeight ? "_globals->_anonInterface0->u_skRTHeight" : ""; + const char* separator = ""; + if ("main" == f.fDeclaration.fName) { + switch (fProgram.fKind) { + case Program::kFragment_Kind: +#ifdef SK_MOLTENVK + this->write("fragment Outputs main0"); +#else + this->write("fragment Outputs fragmentMain"); +#endif + break; + case Program::kVertex_Kind: +#ifdef SK_MOLTENVK + this->write("vertex Outputs main0"); +#else + this->write("vertex Outputs vertexMain"); +#endif + break; + default: + SkASSERT(false); + } + this->write("(Inputs _in [[stage_in]]"); + if (-1 != fUniformBuffer) { + this->write(", constant Uniforms& _uniforms [[buffer(" + + to_string(fUniformBuffer) + ")]]"); + } + for (const auto& e : fProgram) { + if (ProgramElement::kVar_Kind == e.fKind) { + VarDeclarations& decls = (VarDeclarations&) e; + if (!decls.fVars.size()) { + continue; + } + for (const auto& stmt: decls.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + if (var.fVar->fType.kind() == Type::kSampler_Kind) { + this->write(", texture2d "); // FIXME - support other texture types + this->writeName(var.fVar->fName); + this->write("[[texture("); + this->write(to_string(var.fVar->fModifiers.fLayout.fBinding)); + this->write(")]]"); + this->write(", sampler "); + this->writeName(var.fVar->fName); + this->write(SAMPLER_SUFFIX); + this->write("[[sampler("); + this->write(to_string(var.fVar->fModifiers.fLayout.fBinding)); + this->write(")]]"); + } + } + } else if (ProgramElement::kInterfaceBlock_Kind == e.fKind) { + InterfaceBlock& intf = (InterfaceBlock&) e; + if ("sk_PerVertex" == intf.fTypeName) { + continue; + } + this->write(", constant "); + this->writeType(intf.fVariable.fType); + this->write("& " ); + this->write(fInterfaceBlockNameMap[&intf]); + this->write(" [[buffer("); +#ifdef SK_MOLTENVK + this->write(to_string(intf.fVariable.fModifiers.fLayout.fSet)); +#else + this->write(to_string(intf.fVariable.fModifiers.fLayout.fBinding)); +#endif + this->write(")]]"); + } + } + if (fProgram.fKind == Program::kFragment_Kind) { + if (fProgram.fInputs.fRTHeight && fInterfaceBlockNameMap.empty()) { +#ifdef SK_MOLTENVK + this->write(", constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(0)]]"); +#else + this->write(", constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(1)]]"); +#endif + fRTHeightName = "_anonInterface0.u_skRTHeight"; + } + this->write(", bool _frontFacing [[front_facing]]"); + this->write(", float4 _fragCoord [[position]]"); + } else if (fProgram.fKind == Program::kVertex_Kind) { + this->write(", uint sk_VertexID [[vertex_id]], uint sk_InstanceID [[instance_id]]"); + } + separator = ", "; + } else { + this->writeType(f.fDeclaration.fReturnType); + this->write(" "); + this->writeName(f.fDeclaration.fName); + this->write("("); + Requirements requirements = this->requirements(f.fDeclaration); + if (requirements & kInputs_Requirement) { + this->write("Inputs _in"); + separator = ", "; + } + if (requirements & kOutputs_Requirement) { + this->write(separator); + this->write("thread Outputs* _out"); + separator = ", "; + } + if (requirements & kUniforms_Requirement) { + this->write(separator); + this->write("Uniforms _uniforms"); + separator = ", "; + } + if (requirements & kGlobals_Requirement) { + this->write(separator); + this->write("thread Globals* _globals"); + separator = ", "; + } + if (requirements & kFragCoord_Requirement) { + this->write(separator); + this->write("float4 _fragCoord"); + separator = ", "; + } + } + for (const auto& param : f.fDeclaration.fParameters) { + this->write(separator); + separator = ", "; + this->writeModifiers(param->fModifiers, false); + std::vector sizes; + const Type* type = ¶m->fType; + while (Type::kArray_Kind == type->kind()) { + sizes.push_back(type->columns()); + type = &type->componentType(); + } + this->writeType(*type); + if (param->fModifiers.fFlags & Modifiers::kOut_Flag) { + this->write("*"); + } + this->write(" "); + this->writeName(param->fName); + for (int s : sizes) { + if (s <= 0) { + this->write("[]"); + } else { + this->write("[" + to_string(s) + "]"); + } + } + } + this->writeLine(") {"); + + SkASSERT(!fProgram.fSettings.fFragColorIsInOut); + + if ("main" == f.fDeclaration.fName) { + if (fNeedsGlobalStructInit) { + this->writeLine(" Globals globalStruct;"); + this->writeLine(" thread Globals* _globals = &globalStruct;"); + for (const auto& intf: fInterfaceBlockNameMap) { + const auto& intfName = intf.second; + this->write(" _globals->"); + this->writeName(intfName); + this->write(" = &"); + this->writeName(intfName); + this->write(";\n"); + } + for (const auto& var: fInitNonConstGlobalVars) { + this->write(" _globals->"); + this->writeName(var->fVar->fName); + this->write(" = "); + this->writeVarInitializer(*var->fVar, *var->fValue); + this->writeLine(";"); + } + for (const auto& texture: fTextures) { + this->write(" _globals->"); + this->writeName(texture->fName); + this->write(" = "); + this->writeName(texture->fName); + this->write(";\n"); + this->write(" _globals->"); + this->writeName(texture->fName); + this->write(SAMPLER_SUFFIX); + this->write(" = "); + this->writeName(texture->fName); + this->write(SAMPLER_SUFFIX); + this->write(";\n"); + } + } + this->writeLine(" Outputs _outputStruct;"); + this->writeLine(" thread Outputs* _out = &_outputStruct;"); + } + fFunctionHeader = ""; + OutputStream* oldOut = fOut; + StringStream buffer; + fOut = &buffer; + fIndentation++; + this->writeStatements(((Block&) *f.fBody).fStatements); + if ("main" == f.fDeclaration.fName) { + switch (fProgram.fKind) { + case Program::kFragment_Kind: + this->writeLine("return *_out;"); + break; + case Program::kVertex_Kind: + this->writeLine("_out->sk_Position.y = -_out->sk_Position.y;"); + this->writeLine("return *_out;"); // FIXME - detect if function already has return + break; + default: + SkASSERT(false); + } + } + fIndentation--; + this->writeLine("}"); + + fOut = oldOut; + this->write(fFunctionHeader); + this->write(buffer.str()); +} + +void MetalCodeGenerator::writeModifiers(const Modifiers& modifiers, + bool globalContext) { + if (modifiers.fFlags & Modifiers::kOut_Flag) { + this->write("thread "); + } + if (modifiers.fFlags & Modifiers::kConst_Flag) { + this->write("constant "); + } +} + +void MetalCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) { + if ("sk_PerVertex" == intf.fTypeName) { + return; + } + this->writeModifiers(intf.fVariable.fModifiers, true); + this->write("struct "); + this->writeLine(intf.fTypeName + " {"); + const Type* structType = &intf.fVariable.fType; + fWrittenStructs.push_back(structType); + while (Type::kArray_Kind == structType->kind()) { + structType = &structType->componentType(); + } + fIndentation++; + writeFields(structType->fields(), structType->fOffset, &intf); + if (fProgram.fInputs.fRTHeight) { + this->writeLine("float u_skRTHeight;"); + } + fIndentation--; + this->write("}"); + if (intf.fInstanceName.size()) { + this->write(" "); + this->write(intf.fInstanceName); + for (const auto& size : intf.fSizes) { + this->write("["); + if (size) { + this->writeExpression(*size, kTopLevel_Precedence); + } + this->write("]"); + } + fInterfaceBlockNameMap[&intf] = intf.fInstanceName; + } else { + fInterfaceBlockNameMap[&intf] = "_anonInterface" + to_string(fAnonInterfaceCount++); + } + this->writeLine(";"); +} + +void MetalCodeGenerator::writeFields(const std::vector& fields, int parentOffset, + const InterfaceBlock* parentIntf) { +#ifdef SK_MOLTENVK + MemoryLayout memoryLayout(MemoryLayout::k140_Standard); +#else + MemoryLayout memoryLayout(MemoryLayout::kMetal_Standard); +#endif + int currentOffset = 0; + for (const auto& field: fields) { + int fieldOffset = field.fModifiers.fLayout.fOffset; + const Type* fieldType = field.fType; + if (fieldOffset != -1) { + if (currentOffset > fieldOffset) { + fErrors.error(parentOffset, + "offset of field '" + field.fName + "' must be at least " + + to_string((int) currentOffset)); + } else if (currentOffset < fieldOffset) { + this->write("char pad"); + this->write(to_string(fPaddingCount++)); + this->write("["); + this->write(to_string(fieldOffset - currentOffset)); + this->writeLine("];"); + currentOffset = fieldOffset; + } + int alignment = memoryLayout.alignment(*fieldType); + if (fieldOffset % alignment) { + fErrors.error(parentOffset, + "offset of field '" + field.fName + "' must be a multiple of " + + to_string((int) alignment)); + } + } +#ifdef SK_MOLTENVK + if (fieldType->kind() == Type::kVector_Kind && + fieldType->columns() == 3) { + SkASSERT(memoryLayout.size(*fieldType) == 3); + // Pack all vec3 types so that their size in bytes will match what was expected in the + // original SkSL code since MSL has vec3 sizes equal to 4 * component type, while SkSL + // has vec3 equal to 3 * component type. + + // FIXME - Packed vectors can't be accessed by swizzles, but can be indexed into. A + // combination of this being a problem which only occurs when using MoltenVK and the + // fact that we haven't swizzled a vec3 yet means that this problem hasn't been + // addressed. + this->write(PACKED_PREFIX); + } +#endif + currentOffset += memoryLayout.size(*fieldType); + std::vector sizes; + while (fieldType->kind() == Type::kArray_Kind) { + sizes.push_back(fieldType->columns()); + fieldType = &fieldType->componentType(); + } + this->writeModifiers(field.fModifiers, false); + this->writeType(*fieldType); + this->write(" "); + this->writeName(field.fName); + for (int s : sizes) { + if (s <= 0) { + this->write("[]"); + } else { + this->write("[" + to_string(s) + "]"); + } + } + this->writeLine(";"); + if (parentIntf) { + fInterfaceBlockMap[&field] = parentIntf; + } + } +} + +void MetalCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) { + this->writeExpression(value, kTopLevel_Precedence); +} + +void MetalCodeGenerator::writeName(const String& name) { + if (fReservedWords.find(name) != fReservedWords.end()) { + this->write("_"); // adding underscore before name to avoid conflict with reserved words + } + this->write(name); +} + +void MetalCodeGenerator::writeVarDeclarations(const VarDeclarations& decl, bool global) { + SkASSERT(decl.fVars.size() > 0); + bool wroteType = false; + for (const auto& stmt : decl.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + if (global && !(var.fVar->fModifiers.fFlags & Modifiers::kConst_Flag)) { + continue; + } + if (wroteType) { + this->write(", "); + } else { + this->writeModifiers(var.fVar->fModifiers, global); + this->writeType(decl.fBaseType); + this->write(" "); + wroteType = true; + } + this->writeName(var.fVar->fName); + for (const auto& size : var.fSizes) { + this->write("["); + if (size) { + this->writeExpression(*size, kTopLevel_Precedence); + } + this->write("]"); + } + if (var.fValue) { + this->write(" = "); + this->writeVarInitializer(*var.fVar, *var.fValue); + } + } + if (wroteType) { + this->write(";"); + } +} + +void MetalCodeGenerator::writeStatement(const Statement& s) { + switch (s.fKind) { + case Statement::kBlock_Kind: + this->writeBlock((Block&) s); + break; + case Statement::kExpression_Kind: + this->writeExpression(*((ExpressionStatement&) s).fExpression, kTopLevel_Precedence); + this->write(";"); + break; + case Statement::kReturn_Kind: + this->writeReturnStatement((ReturnStatement&) s); + break; + case Statement::kVarDeclarations_Kind: + this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration, false); + break; + case Statement::kIf_Kind: + this->writeIfStatement((IfStatement&) s); + break; + case Statement::kFor_Kind: + this->writeForStatement((ForStatement&) s); + break; + case Statement::kWhile_Kind: + this->writeWhileStatement((WhileStatement&) s); + break; + case Statement::kDo_Kind: + this->writeDoStatement((DoStatement&) s); + break; + case Statement::kSwitch_Kind: + this->writeSwitchStatement((SwitchStatement&) s); + break; + case Statement::kBreak_Kind: + this->write("break;"); + break; + case Statement::kContinue_Kind: + this->write("continue;"); + break; + case Statement::kDiscard_Kind: + this->write("discard_fragment();"); + break; + case Statement::kNop_Kind: + this->write(";"); + break; + default: + ABORT("unsupported statement: %s", s.description().c_str()); + } +} + +void MetalCodeGenerator::writeStatements(const std::vector>& statements) { + for (const auto& s : statements) { + if (!s->isEmpty()) { + this->writeStatement(*s); + this->writeLine(); + } + } +} + +void MetalCodeGenerator::writeBlock(const Block& b) { + this->writeLine("{"); + fIndentation++; + this->writeStatements(b.fStatements); + fIndentation--; + this->write("}"); +} + +void MetalCodeGenerator::writeIfStatement(const IfStatement& stmt) { + this->write("if ("); + this->writeExpression(*stmt.fTest, kTopLevel_Precedence); + this->write(") "); + this->writeStatement(*stmt.fIfTrue); + if (stmt.fIfFalse) { + this->write(" else "); + this->writeStatement(*stmt.fIfFalse); + } +} + +void MetalCodeGenerator::writeForStatement(const ForStatement& f) { + this->write("for ("); + if (f.fInitializer && !f.fInitializer->isEmpty()) { + this->writeStatement(*f.fInitializer); + } else { + this->write("; "); + } + if (f.fTest) { + this->writeExpression(*f.fTest, kTopLevel_Precedence); + } + this->write("; "); + if (f.fNext) { + this->writeExpression(*f.fNext, kTopLevel_Precedence); + } + this->write(") "); + this->writeStatement(*f.fStatement); +} + +void MetalCodeGenerator::writeWhileStatement(const WhileStatement& w) { + this->write("while ("); + this->writeExpression(*w.fTest, kTopLevel_Precedence); + this->write(") "); + this->writeStatement(*w.fStatement); +} + +void MetalCodeGenerator::writeDoStatement(const DoStatement& d) { + this->write("do "); + this->writeStatement(*d.fStatement); + this->write(" while ("); + this->writeExpression(*d.fTest, kTopLevel_Precedence); + this->write(");"); +} + +void MetalCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + this->write("switch ("); + this->writeExpression(*s.fValue, kTopLevel_Precedence); + this->writeLine(") {"); + fIndentation++; + for (const auto& c : s.fCases) { + if (c->fValue) { + this->write("case "); + this->writeExpression(*c->fValue, kTopLevel_Precedence); + this->writeLine(":"); + } else { + this->writeLine("default:"); + } + fIndentation++; + for (const auto& stmt : c->fStatements) { + this->writeStatement(*stmt); + this->writeLine(); + } + fIndentation--; + } + fIndentation--; + this->write("}"); +} + +void MetalCodeGenerator::writeReturnStatement(const ReturnStatement& r) { + this->write("return"); + if (r.fExpression) { + this->write(" "); + this->writeExpression(*r.fExpression, kTopLevel_Precedence); + } + this->write(";"); +} + +void MetalCodeGenerator::writeHeader() { + this->write("#include \n"); + this->write("#include \n"); + this->write("using namespace metal;\n"); +} + +void MetalCodeGenerator::writeUniformStruct() { + for (const auto& e : fProgram) { + if (ProgramElement::kVar_Kind == e.fKind) { + VarDeclarations& decls = (VarDeclarations&) e; + if (!decls.fVars.size()) { + continue; + } + const Variable& first = *((VarDeclaration&) *decls.fVars[0]).fVar; + if (first.fModifiers.fFlags & Modifiers::kUniform_Flag && + first.fType.kind() != Type::kSampler_Kind) { + if (-1 == fUniformBuffer) { + this->write("struct Uniforms {\n"); + fUniformBuffer = first.fModifiers.fLayout.fSet; + if (-1 == fUniformBuffer) { + fErrors.error(decls.fOffset, "Metal uniforms must have 'layout(set=...)'"); + } + } else if (first.fModifiers.fLayout.fSet != fUniformBuffer) { + if (-1 == fUniformBuffer) { + fErrors.error(decls.fOffset, "Metal backend requires all uniforms to have " + "the same 'layout(set=...)'"); + } + } + this->write(" "); + this->writeType(first.fType); + this->write(" "); + for (const auto& stmt : decls.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + this->writeName(var.fVar->fName); + } + this->write(";\n"); + } + } + } + if (-1 != fUniformBuffer) { + this->write("};\n"); + } +} + +void MetalCodeGenerator::writeInputStruct() { + this->write("struct Inputs {\n"); + for (const auto& e : fProgram) { + if (ProgramElement::kVar_Kind == e.fKind) { + VarDeclarations& decls = (VarDeclarations&) e; + if (!decls.fVars.size()) { + continue; + } + const Variable& first = *((VarDeclaration&) *decls.fVars[0]).fVar; + if (first.fModifiers.fFlags & Modifiers::kIn_Flag && + -1 == first.fModifiers.fLayout.fBuiltin) { + this->write(" "); + this->writeType(first.fType); + this->write(" "); + for (const auto& stmt : decls.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + this->writeName(var.fVar->fName); + if (-1 != var.fVar->fModifiers.fLayout.fLocation) { + if (fProgram.fKind == Program::kVertex_Kind) { + this->write(" [[attribute(" + + to_string(var.fVar->fModifiers.fLayout.fLocation) + ")]]"); + } else if (fProgram.fKind == Program::kFragment_Kind) { + this->write(" [[user(locn" + + to_string(var.fVar->fModifiers.fLayout.fLocation) + ")]]"); + } + } + } + this->write(";\n"); + } + } + } + this->write("};\n"); +} + +void MetalCodeGenerator::writeOutputStruct() { + this->write("struct Outputs {\n"); + if (fProgram.fKind == Program::kVertex_Kind) { + this->write(" float4 sk_Position [[position]];\n"); + } else if (fProgram.fKind == Program::kFragment_Kind) { + this->write(" float4 sk_FragColor [[color(0)]];\n"); + } + for (const auto& e : fProgram) { + if (ProgramElement::kVar_Kind == e.fKind) { + VarDeclarations& decls = (VarDeclarations&) e; + if (!decls.fVars.size()) { + continue; + } + const Variable& first = *((VarDeclaration&) *decls.fVars[0]).fVar; + if (first.fModifiers.fFlags & Modifiers::kOut_Flag && + -1 == first.fModifiers.fLayout.fBuiltin) { + this->write(" "); + this->writeType(first.fType); + this->write(" "); + for (const auto& stmt : decls.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + this->writeName(var.fVar->fName); + if (fProgram.fKind == Program::kVertex_Kind) { + this->write(" [[user(locn" + + to_string(var.fVar->fModifiers.fLayout.fLocation) + ")]]"); + } else if (fProgram.fKind == Program::kFragment_Kind) { + this->write(" [[color(" + + to_string(var.fVar->fModifiers.fLayout.fLocation) +")"); + int colorIndex = var.fVar->fModifiers.fLayout.fIndex; + if (colorIndex) { + this->write(", index(" + to_string(colorIndex) + ")"); + } + this->write("]]"); + } + } + this->write(";\n"); + } + } + } + if (fProgram.fKind == Program::kVertex_Kind) { + this->write(" float sk_PointSize;\n"); + } + this->write("};\n"); +} + +void MetalCodeGenerator::writeInterfaceBlocks() { + bool wroteInterfaceBlock = false; + for (const auto& e : fProgram) { + if (ProgramElement::kInterfaceBlock_Kind == e.fKind) { + this->writeInterfaceBlock((InterfaceBlock&) e); + wroteInterfaceBlock = true; + } + } + if (!wroteInterfaceBlock && fProgram.fInputs.fRTHeight) { + this->writeLine("struct sksl_synthetic_uniforms {"); + this->writeLine(" float u_skRTHeight;"); + this->writeLine("};"); + } +} + +void MetalCodeGenerator::writeGlobalStruct() { + bool wroteStructDecl = false; + for (const auto& intf : fInterfaceBlockNameMap) { + if (!wroteStructDecl) { + this->write("struct Globals {\n"); + wroteStructDecl = true; + } + fNeedsGlobalStructInit = true; + const auto& intfType = intf.first; + const auto& intfName = intf.second; + this->write(" constant "); + this->write(intfType->fTypeName); + this->write("* "); + this->writeName(intfName); + this->write(";\n"); + } + for (const auto& e : fProgram) { + if (ProgramElement::kVar_Kind == e.fKind) { + VarDeclarations& decls = (VarDeclarations&) e; + if (!decls.fVars.size()) { + continue; + } + const Variable& first = *((VarDeclaration&) *decls.fVars[0]).fVar; + if ((!first.fModifiers.fFlags && -1 == first.fModifiers.fLayout.fBuiltin) || + first.fType.kind() == Type::kSampler_Kind) { + if (!wroteStructDecl) { + this->write("struct Globals {\n"); + wroteStructDecl = true; + } + fNeedsGlobalStructInit = true; + this->write(" "); + this->writeType(first.fType); + this->write(" "); + for (const auto& stmt : decls.fVars) { + VarDeclaration& var = (VarDeclaration&) *stmt; + this->writeName(var.fVar->fName); + if (var.fVar->fType.kind() == Type::kSampler_Kind) { + fTextures.push_back(var.fVar); + this->write(";\n"); + this->write(" sampler "); + this->writeName(var.fVar->fName); + this->write(SAMPLER_SUFFIX); + } + if (var.fValue) { + fInitNonConstGlobalVars.push_back(&var); + } + } + this->write(";\n"); + } + } + } + if (wroteStructDecl) { + this->write("};\n"); + } +} + +void MetalCodeGenerator::writeProgramElement(const ProgramElement& e) { + switch (e.fKind) { + case ProgramElement::kExtension_Kind: + break; + case ProgramElement::kVar_Kind: { + VarDeclarations& decl = (VarDeclarations&) e; + if (decl.fVars.size() > 0) { + int builtin = ((VarDeclaration&) *decl.fVars[0]).fVar->fModifiers.fLayout.fBuiltin; + if (-1 == builtin) { + // normal var + this->writeVarDeclarations(decl, true); + this->writeLine(); + } else if (SK_FRAGCOLOR_BUILTIN == builtin) { + // ignore + } + } + break; + } + case ProgramElement::kInterfaceBlock_Kind: + // handled in writeInterfaceBlocks, do nothing + break; + case ProgramElement::kFunction_Kind: + this->writeFunction((FunctionDefinition&) e); + break; + case ProgramElement::kModifiers_Kind: + this->writeModifiers(((ModifiersDeclaration&) e).fModifiers, true); + this->writeLine(";"); + break; + default: + printf("%s\n", e.description().c_str()); + ABORT("unsupported program element"); + } +} + +MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const Expression& e) { + switch (e.fKind) { + case Expression::kFunctionCall_Kind: { + const FunctionCall& f = (const FunctionCall&) e; + Requirements result = this->requirements(f.fFunction); + for (const auto& e : f.fArguments) { + result |= this->requirements(*e); + } + return result; + } + case Expression::kConstructor_Kind: { + const Constructor& c = (const Constructor&) e; + Requirements result = kNo_Requirements; + for (const auto& e : c.fArguments) { + result |= this->requirements(*e); + } + return result; + } + case Expression::kFieldAccess_Kind: { + const FieldAccess& f = (const FieldAccess&) e; + if (FieldAccess::kAnonymousInterfaceBlock_OwnerKind == f.fOwnerKind) { + return kGlobals_Requirement; + } + return this->requirements(*((const FieldAccess&) e).fBase); + } + case Expression::kSwizzle_Kind: + return this->requirements(*((const Swizzle&) e).fBase); + case Expression::kBinary_Kind: { + const BinaryExpression& b = (const BinaryExpression&) e; + return this->requirements(*b.fLeft) | this->requirements(*b.fRight); + } + case Expression::kIndex_Kind: { + const IndexExpression& idx = (const IndexExpression&) e; + return this->requirements(*idx.fBase) | this->requirements(*idx.fIndex); + } + case Expression::kPrefix_Kind: + return this->requirements(*((const PrefixExpression&) e).fOperand); + case Expression::kPostfix_Kind: + return this->requirements(*((const PostfixExpression&) e).fOperand); + case Expression::kTernary_Kind: { + const TernaryExpression& t = (const TernaryExpression&) e; + return this->requirements(*t.fTest) | this->requirements(*t.fIfTrue) | + this->requirements(*t.fIfFalse); + } + case Expression::kVariableReference_Kind: { + const VariableReference& v = (const VariableReference&) e; + Requirements result = kNo_Requirements; + if (v.fVariable.fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) { + result = kGlobals_Requirement | kFragCoord_Requirement; + } else if (Variable::kGlobal_Storage == v.fVariable.fStorage) { + if (v.fVariable.fModifiers.fFlags & Modifiers::kIn_Flag) { + result = kInputs_Requirement; + } else if (v.fVariable.fModifiers.fFlags & Modifiers::kOut_Flag) { + result = kOutputs_Requirement; + } else if (v.fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag && + v.fVariable.fType.kind() != Type::kSampler_Kind) { + result = kUniforms_Requirement; + } else { + result = kGlobals_Requirement; + } + } + return result; + } + default: + return kNo_Requirements; + } +} + +MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const Statement& s) { + switch (s.fKind) { + case Statement::kBlock_Kind: { + Requirements result = kNo_Requirements; + for (const auto& child : ((const Block&) s).fStatements) { + result |= this->requirements(*child); + } + return result; + } + case Statement::kVarDeclaration_Kind: { + Requirements result = kNo_Requirements; + const VarDeclaration& var = (const VarDeclaration&) s; + if (var.fValue) { + result = this->requirements(*var.fValue); + } + return result; + } + case Statement::kVarDeclarations_Kind: { + Requirements result = kNo_Requirements; + const VarDeclarations& decls = *((const VarDeclarationsStatement&) s).fDeclaration; + for (const auto& stmt : decls.fVars) { + result |= this->requirements(*stmt); + } + return result; + } + case Statement::kExpression_Kind: + return this->requirements(*((const ExpressionStatement&) s).fExpression); + case Statement::kReturn_Kind: { + const ReturnStatement& r = (const ReturnStatement&) s; + if (r.fExpression) { + return this->requirements(*r.fExpression); + } + return kNo_Requirements; + } + case Statement::kIf_Kind: { + const IfStatement& i = (const IfStatement&) s; + return this->requirements(*i.fTest) | + this->requirements(*i.fIfTrue) | + (i.fIfFalse ? this->requirements(*i.fIfFalse) : 0); + } + case Statement::kFor_Kind: { + const ForStatement& f = (const ForStatement&) s; + return this->requirements(*f.fInitializer) | + this->requirements(*f.fTest) | + this->requirements(*f.fNext) | + this->requirements(*f.fStatement); + } + case Statement::kWhile_Kind: { + const WhileStatement& w = (const WhileStatement&) s; + return this->requirements(*w.fTest) | + this->requirements(*w.fStatement); + } + case Statement::kDo_Kind: { + const DoStatement& d = (const DoStatement&) s; + return this->requirements(*d.fTest) | + this->requirements(*d.fStatement); + } + case Statement::kSwitch_Kind: { + const SwitchStatement& sw = (const SwitchStatement&) s; + Requirements result = this->requirements(*sw.fValue); + for (const auto& c : sw.fCases) { + for (const auto& st : c->fStatements) { + result |= this->requirements(*st); + } + } + return result; + } + default: + return kNo_Requirements; + } +} + +MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const FunctionDeclaration& f) { + if (f.fBuiltin) { + return kNo_Requirements; + } + auto found = fRequirements.find(&f); + if (found == fRequirements.end()) { + fRequirements[&f] = kNo_Requirements; + for (const auto& e : fProgram) { + if (ProgramElement::kFunction_Kind == e.fKind) { + const FunctionDefinition& def = (const FunctionDefinition&) e; + if (&def.fDeclaration == &f) { + Requirements reqs = this->requirements(*def.fBody); + fRequirements[&f] = reqs; + return reqs; + } + } + } + } + return found->second; +} + +bool MetalCodeGenerator::generateCode() { + OutputStream* rawOut = fOut; + fOut = &fHeader; +#ifdef SK_MOLTENVK + fOut->write((const char*) &MVKMagicNum, sizeof(MVKMagicNum)); +#endif + fProgramKind = fProgram.fKind; + this->writeHeader(); + this->writeUniformStruct(); + this->writeInputStruct(); + this->writeOutputStruct(); + this->writeInterfaceBlocks(); + this->writeGlobalStruct(); + StringStream body; + fOut = &body; + for (const auto& e : fProgram) { + this->writeProgramElement(e); + } + fOut = rawOut; + + write_stringstream(fHeader, *rawOut); + write_stringstream(fExtraFunctions, *rawOut); + write_stringstream(body, *rawOut); +#ifdef SK_MOLTENVK + this->write("\0"); +#endif + return true; +} + +} diff --git a/gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.h new file mode 100644 index 0000000000..ad7ea1e949 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMetalCodeGenerator.h @@ -0,0 +1,288 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_METALCODEGENERATOR +#define SKSL_METALCODEGENERATOR + +#include +#include +#include + +#include "src/sksl/SkSLCodeGenerator.h" +#include "src/sksl/SkSLMemoryLayout.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBoolLiteral.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/ir/SkSLWhileStatement.h" + +namespace SkSL { + +#define kLast_Capability SpvCapabilityMultiViewport + +/** + * Converts a Program into Metal code. + */ +class MetalCodeGenerator : public CodeGenerator { +public: + static constexpr const char* SAMPLER_SUFFIX = "Smplr"; + static constexpr const char* PACKED_PREFIX = "packed_"; + + enum Precedence { + kParentheses_Precedence = 1, + kPostfix_Precedence = 2, + kPrefix_Precedence = 3, + kMultiplicative_Precedence = 4, + kAdditive_Precedence = 5, + kShift_Precedence = 6, + kRelational_Precedence = 7, + kEquality_Precedence = 8, + kBitwiseAnd_Precedence = 9, + kBitwiseXor_Precedence = 10, + kBitwiseOr_Precedence = 11, + kLogicalAnd_Precedence = 12, + kLogicalXor_Precedence = 13, + kLogicalOr_Precedence = 14, + kTernary_Precedence = 15, + kAssignment_Precedence = 16, + kSequence_Precedence = 17, + kTopLevel_Precedence = kSequence_Precedence + }; + + MetalCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + OutputStream* out) + : INHERITED(program, errors, out) + , fReservedWords({"atan2", "rsqrt", "dfdx", "dfdy", "vertex", "fragment"}) + , fLineEnding("\n") + , fContext(*context) { + this->setupIntrinsics(); + } + + bool generateCode() override; + +protected: + typedef int Requirements; + static constexpr Requirements kNo_Requirements = 0; + static constexpr Requirements kInputs_Requirement = 1 << 0; + static constexpr Requirements kOutputs_Requirement = 1 << 1; + static constexpr Requirements kUniforms_Requirement = 1 << 2; + static constexpr Requirements kGlobals_Requirement = 1 << 3; + static constexpr Requirements kFragCoord_Requirement = 1 << 4; + + enum IntrinsicKind { + kSpecial_IntrinsicKind, + kMetal_IntrinsicKind, + }; + + enum SpecialIntrinsic { + kTexture_SpecialIntrinsic, + kMod_SpecialIntrinsic, + }; + + enum MetalIntrinsic { + kEqual_MetalIntrinsic, + kNotEqual_MetalIntrinsic, + kLessThan_MetalIntrinsic, + kLessThanEqual_MetalIntrinsic, + kGreaterThan_MetalIntrinsic, + kGreaterThanEqual_MetalIntrinsic, + }; + + void setupIntrinsics(); + + void write(const char* s); + + void writeLine(); + + void writeLine(const char* s); + + void write(const String& s); + + void writeLine(const String& s); + + void writeHeader(); + + void writeUniformStruct(); + + void writeInputStruct(); + + void writeOutputStruct(); + + void writeInterfaceBlocks(); + + void writeFields(const std::vector& fields, int parentOffset, + const InterfaceBlock* parentIntf = nullptr); + + int size(const Type* type, bool isPacked) const; + + int alignment(const Type* type, bool isPacked) const; + + void writeGlobalStruct(); + + void writePrecisionModifier(); + + void writeType(const Type& type); + + void writeExtension(const Extension& ext); + + void writeInterfaceBlock(const InterfaceBlock& intf); + + void writeFunctionStart(const FunctionDeclaration& f); + + void writeFunctionDeclaration(const FunctionDeclaration& f); + + void writeFunction(const FunctionDefinition& f); + + void writeLayout(const Layout& layout); + + void writeModifiers(const Modifiers& modifiers, bool globalContext); + + void writeGlobalVars(const VarDeclaration& vs); + + void writeVarInitializer(const Variable& var, const Expression& value); + + void writeName(const String& name); + + void writeVarDeclarations(const VarDeclarations& decl, bool global); + + void writeFragCoord(); + + void writeVariableReference(const VariableReference& ref); + + void writeExpression(const Expression& expr, Precedence parentPrecedence); + + void writeIntrinsicCall(const FunctionCall& c); + + void writeMinAbsHack(Expression& absExpr, Expression& otherExpr); + + void writeFunctionCall(const FunctionCall& c); + + void writeInverseHack(const Expression& mat); + + String getMatrixConstructHelper(const Type& matrix, const Type& arg); + + void writeMatrixTimesEqualHelper(const Type& left, const Type& right, const Type& result); + + void writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind); + + bool canCoerce(const Type& t1, const Type& t2); + + void writeConstructor(const Constructor& c, Precedence parentPrecedence); + + void writeFieldAccess(const FieldAccess& f); + + void writeSwizzle(const Swizzle& swizzle); + + static Precedence GetBinaryPrecedence(Token::Kind op); + + void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); + + void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); + + void writeIndexExpression(const IndexExpression& expr); + + void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence); + + void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence); + + void writeBoolLiteral(const BoolLiteral& b); + + void writeIntLiteral(const IntLiteral& i); + + void writeFloatLiteral(const FloatLiteral& f); + + void writeSetting(const Setting& s); + + void writeStatement(const Statement& s); + + void writeStatements(const std::vector>& statements); + + void writeBlock(const Block& b); + + void writeIfStatement(const IfStatement& stmt); + + void writeForStatement(const ForStatement& f); + + void writeWhileStatement(const WhileStatement& w); + + void writeDoStatement(const DoStatement& d); + + void writeSwitchStatement(const SwitchStatement& s); + + void writeReturnStatement(const ReturnStatement& r); + + void writeProgramElement(const ProgramElement& e); + + Requirements requirements(const FunctionDeclaration& f); + + Requirements requirements(const Expression& e); + + Requirements requirements(const Statement& e); + + typedef std::pair Intrinsic; + std::unordered_map fIntrinsicMap; + std::unordered_set fReservedWords; + std::vector fInitNonConstGlobalVars; + std::vector fTextures; + std::unordered_map fInterfaceBlockMap; + std::unordered_map fInterfaceBlockNameMap; + int fAnonInterfaceCount = 0; + int fPaddingCount = 0; + bool fNeedsGlobalStructInit = false; + const char* fLineEnding; + const Context& fContext; + StringStream fHeader; + String fFunctionHeader; + StringStream fExtraFunctions; + Program::Kind fProgramKind; + int fVarCount = 0; + int fIndentation = 0; + bool fAtLineStart = false; + // Keeps track of which struct types we have written. Given that we are unlikely to ever write + // more than one or two structs per shader, a simple linear search will be faster than anything + // fancier. + std::vector fWrittenStructs; + std::set fWrittenIntrinsics; + // true if we have run into usages of dFdx / dFdy + bool fFoundDerivatives = false; + std::unordered_map fRequirements; + bool fSetupFragPositionGlobal = false; + bool fSetupFragPositionLocal = false; + std::unordered_map fHelpers; + int fUniformBuffer = -1; + String fRTHeightName; + + typedef CodeGenerator INHERITED; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp b/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp new file mode 100644 index 0000000000..f72637d4cb --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLOutputStream.h" + +namespace SkSL { + +void OutputStream::writeString(String s) { + this->write(s.c_str(), s.size()); +} + +void OutputStream::printf(const char format[], ...) { + va_list args; + va_start(args, format); + this->appendVAList(format, args); + va_end(args); +} + +void OutputStream::appendVAList(const char format[], va_list args) { + char buffer[kBufferSize]; + int length = vsnprintf(buffer, kBufferSize, format, args); + SkASSERT(length >= 0 && length < (int) kBufferSize); + this->write(buffer, length); +} + +} diff --git a/gfx/skia/skia/src/sksl/SkSLOutputStream.h b/gfx/skia/skia/src/sksl/SkSLOutputStream.h new file mode 100644 index 0000000000..c7e979b273 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLOutputStream.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_OUTPUTSTREAM +#define SKSL_OUTPUTSTREAM + +#include "src/sksl/SkSLDefines.h" +#include "src/sksl/SkSLString.h" + +namespace SkSL { + +class OutputStream { +public: + virtual bool isValid() const { + return true; + } + + virtual void write8(uint8_t b) = 0; + + virtual void writeText(const char* s) = 0; + + virtual void write(const void* s, size_t size) = 0; + + void writeString(String s); + + void printf(const char format[], ...) SKSL_PRINTF_LIKE(2, 3); + + void appendVAList(const char format[], va_list args); + + virtual ~OutputStream() {} + +private: + static const int kBufferSize = 1024; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLParser.cpp b/gfx/skia/skia/src/sksl/SkSLParser.cpp new file mode 100644 index 0000000000..ddca218c38 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLParser.cpp @@ -0,0 +1,2150 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "stdio.h" +#include "src/sksl/SkSLASTNode.h" +#include "src/sksl/SkSLParser.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" + +#ifndef SKSL_STANDALONE +#include "include/private/SkOnce.h" +#endif + +namespace SkSL { + +#define MAX_PARSE_DEPTH 50 + +class AutoDepth { +public: + AutoDepth(Parser* p) + : fParser(p) + , fDepth(0) {} + + ~AutoDepth() { + fParser->fDepth -= fDepth; + } + + bool increase() { + ++fDepth; + ++fParser->fDepth; + if (fParser->fDepth > MAX_PARSE_DEPTH) { + fParser->error(fParser->peek(), String("exceeded max parse depth")); + return false; + } + return true; + } + +private: + Parser* fParser; + int fDepth; +}; + +std::unordered_map* Parser::layoutTokens; + +void Parser::InitLayoutMap() { + layoutTokens = new std::unordered_map; + #define TOKEN(name, text) (*layoutTokens)[text] = LayoutToken::name + TOKEN(LOCATION, "location"); + TOKEN(OFFSET, "offset"); + TOKEN(BINDING, "binding"); + TOKEN(INDEX, "index"); + TOKEN(SET, "set"); + TOKEN(BUILTIN, "builtin"); + TOKEN(INPUT_ATTACHMENT_INDEX, "input_attachment_index"); + TOKEN(ORIGIN_UPPER_LEFT, "origin_upper_left"); + TOKEN(OVERRIDE_COVERAGE, "override_coverage"); + TOKEN(BLEND_SUPPORT_ALL_EQUATIONS, "blend_support_all_equations"); + TOKEN(BLEND_SUPPORT_MULTIPLY, "blend_support_multiply"); + TOKEN(BLEND_SUPPORT_SCREEN, "blend_support_screen"); + TOKEN(BLEND_SUPPORT_OVERLAY, "blend_support_overlay"); + TOKEN(BLEND_SUPPORT_DARKEN, "blend_support_darken"); + TOKEN(BLEND_SUPPORT_LIGHTEN, "blend_support_lighten"); + TOKEN(BLEND_SUPPORT_COLORDODGE, "blend_support_colordodge"); + TOKEN(BLEND_SUPPORT_COLORBURN, "blend_support_colorburn"); + TOKEN(BLEND_SUPPORT_HARDLIGHT, "blend_support_hardlight"); + TOKEN(BLEND_SUPPORT_SOFTLIGHT, "blend_support_softlight"); + TOKEN(BLEND_SUPPORT_DIFFERENCE, "blend_support_difference"); + TOKEN(BLEND_SUPPORT_EXCLUSION, "blend_support_exclusion"); + TOKEN(BLEND_SUPPORT_HSL_HUE, "blend_support_hsl_hue"); + TOKEN(BLEND_SUPPORT_HSL_SATURATION, "blend_support_hsl_saturation"); + TOKEN(BLEND_SUPPORT_HSL_COLOR, "blend_support_hsl_color"); + TOKEN(BLEND_SUPPORT_HSL_LUMINOSITY, "blend_support_hsl_luminosity"); + TOKEN(PUSH_CONSTANT, "push_constant"); + TOKEN(POINTS, "points"); + TOKEN(LINES, "lines"); + TOKEN(LINE_STRIP, "line_strip"); + TOKEN(LINES_ADJACENCY, "lines_adjacency"); + TOKEN(TRIANGLES, "triangles"); + TOKEN(TRIANGLE_STRIP, "triangle_strip"); + TOKEN(TRIANGLES_ADJACENCY, "triangles_adjacency"); + TOKEN(MAX_VERTICES, "max_vertices"); + TOKEN(INVOCATIONS, "invocations"); + TOKEN(WHEN, "when"); + TOKEN(KEY, "key"); + TOKEN(TRACKED, "tracked"); + TOKEN(CTYPE, "ctype"); + TOKEN(SKPMCOLOR4F, "SkPMColor4f"); + TOKEN(SKVECTOR4, "SkVector4"); + TOKEN(SKRECT, "SkRect"); + TOKEN(SKIRECT, "SkIRect"); + TOKEN(SKPMCOLOR, "SkPMColor"); + TOKEN(SKMATRIX44, "SkMatrix44"); + TOKEN(BOOL, "bool"); + TOKEN(INT, "int"); + TOKEN(FLOAT, "float"); + #undef TOKEN +} + +Parser::Parser(const char* text, size_t length, SymbolTable& types, ErrorReporter& errors) +: fText(text) +, fPushback(Token::INVALID, -1, -1) +, fTypes(types) +, fErrors(errors) { + fLexer.start(text, length); + static const bool layoutMapInitialized = []{ return (void)InitLayoutMap(), true; }(); + (void) layoutMapInitialized; +} + +#define CREATE_NODE(result, ...) \ + ASTNode::ID result(fFile->fNodes.size()); \ + fFile->fNodes.emplace_back(&fFile->fNodes, __VA_ARGS__) + +#define RETURN_NODE(...) \ + do { \ + CREATE_NODE(result, __VA_ARGS__); \ + return result; \ + } while (false) + +#define CREATE_CHILD(child, target, ...) \ + CREATE_NODE(child, __VA_ARGS__); \ + fFile->fNodes[target.fValue].addChild(child) + +#define CREATE_EMPTY_CHILD(target) \ + do { \ + ASTNode::ID child(fFile->fNodes.size()); \ + fFile->fNodes.emplace_back(); \ + fFile->fNodes[target.fValue].addChild(child); \ + } while (false) + +/* (directive | section | declaration)* END_OF_FILE */ +std::unique_ptr Parser::file() { + fFile.reset(new ASTFile()); + CREATE_NODE(result, 0, ASTNode::Kind::kFile); + fFile->fRoot = result; + for (;;) { + switch (this->peek().fKind) { + case Token::END_OF_FILE: + return std::move(fFile); + case Token::DIRECTIVE: { + ASTNode::ID dir = this->directive(); + if (fErrors.errorCount()) { + return nullptr; + } + if (dir) { + getNode(result).addChild(dir); + } + break; + } + case Token::SECTION: { + ASTNode::ID section = this->section(); + if (fErrors.errorCount()) { + return nullptr; + } + if (section) { + getNode(result).addChild(section); + } + break; + } + default: { + ASTNode::ID decl = this->declaration(); + if (fErrors.errorCount()) { + return nullptr; + } + if (decl) { + getNode(result).addChild(decl); + } + } + } + } + return std::move(fFile); +} + +Token Parser::nextRawToken() { + if (fPushback.fKind != Token::INVALID) { + Token result = fPushback; + fPushback.fKind = Token::INVALID; + return result; + } + Token result = fLexer.next(); + return result; +} + +Token Parser::nextToken() { + Token token = this->nextRawToken(); + while (token.fKind == Token::WHITESPACE || token.fKind == Token::LINE_COMMENT || + token.fKind == Token::BLOCK_COMMENT) { + token = this->nextRawToken(); + } + return token; +} + +void Parser::pushback(Token t) { + SkASSERT(fPushback.fKind == Token::INVALID); + fPushback = std::move(t); +} + +Token Parser::peek() { + if (fPushback.fKind == Token::INVALID) { + fPushback = this->nextToken(); + } + return fPushback; +} + +bool Parser::checkNext(Token::Kind kind, Token* result) { + if (fPushback.fKind != Token::INVALID && fPushback.fKind != kind) { + return false; + } + Token next = this->nextToken(); + if (next.fKind == kind) { + if (result) { + *result = next; + } + return true; + } + this->pushback(std::move(next)); + return false; +} + +bool Parser::expect(Token::Kind kind, const char* expected, Token* result) { + Token next = this->nextToken(); + if (next.fKind == kind) { + if (result) { + *result = std::move(next); + } + return true; + } else { + this->error(next, "expected " + String(expected) + ", but found '" + + this->text(next) + "'"); + return false; + } +} + +StringFragment Parser::text(Token token) { + return StringFragment(fText + token.fOffset, token.fLength); +} + +void Parser::error(Token token, String msg) { + this->error(token.fOffset, msg); +} + +void Parser::error(int offset, String msg) { + fErrors.error(offset, msg); +} + +bool Parser::isType(StringFragment name) { + return nullptr != fTypes[name]; +} + +/* DIRECTIVE(#version) INT_LITERAL ("es" | "compatibility")? | + DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER */ +ASTNode::ID Parser::directive() { + Token start; + if (!this->expect(Token::DIRECTIVE, "a directive", &start)) { + return ASTNode::ID::Invalid(); + } + StringFragment text = this->text(start); + if (text == "#extension") { + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::COLON, "':'")) { + return ASTNode::ID::Invalid(); + } + // FIXME: need to start paying attention to this token + if (!this->expect(Token::IDENTIFIER, "an identifier")) { + return ASTNode::ID::Invalid(); + } + RETURN_NODE(start.fOffset, ASTNode::Kind::kExtension, this->text(name)); + } else { + this->error(start, "unsupported directive '" + this->text(start) + "'"); + return ASTNode::ID::Invalid(); + } +} + +/* SECTION LBRACE (LPAREN IDENTIFIER RPAREN)? + RBRACE */ +ASTNode::ID Parser::section() { + Token start; + if (!this->expect(Token::SECTION, "a section token", &start)) { + return ASTNode::ID::Invalid(); + } + StringFragment argument; + if (this->peek().fKind == Token::LPAREN) { + this->nextToken(); + Token argToken; + if (!this->expect(Token::IDENTIFIER, "an identifier", &argToken)) { + return ASTNode::ID::Invalid(); + } + argument = this->text(argToken); + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + } + if (!this->expect(Token::LBRACE, "'{'")) { + return ASTNode::ID::Invalid(); + } + StringFragment text; + Token codeStart = this->nextRawToken(); + size_t startOffset = codeStart.fOffset; + this->pushback(codeStart); + text.fChars = fText + startOffset; + int level = 1; + for (;;) { + Token next = this->nextRawToken(); + switch (next.fKind) { + case Token::LBRACE: + ++level; + break; + case Token::RBRACE: + --level; + break; + case Token::END_OF_FILE: + this->error(start, "reached end of file while parsing section"); + return ASTNode::ID::Invalid(); + default: + break; + } + if (!level) { + text.fLength = next.fOffset - startOffset; + break; + } + } + StringFragment name = this->text(start); + ++name.fChars; + --name.fLength; + RETURN_NODE(start.fOffset, ASTNode::Kind::kSection, + ASTNode::SectionData(name, argument, text)); +} + +/* ENUM CLASS IDENTIFIER LBRACE (IDENTIFIER (EQ expression)? (COMMA IDENTIFIER (EQ expression))*)? + RBRACE */ +ASTNode::ID Parser::enumDeclaration() { + Token start; + if (!this->expect(Token::ENUM, "'enum'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::CLASS, "'class'")) { + return ASTNode::ID::Invalid(); + } + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LBRACE, "'{'")) { + return ASTNode::ID::Invalid(); + } + fTypes.add(this->text(name), std::unique_ptr(new Type(this->text(name), + Type::kEnum_Kind))); + CREATE_NODE(result, name.fOffset, ASTNode::Kind::kEnum, this->text(name)); + if (!this->checkNext(Token::RBRACE)) { + Token id; + if (!this->expect(Token::IDENTIFIER, "an identifier", &id)) { + return ASTNode::ID::Invalid(); + } + if (this->checkNext(Token::EQ)) { + ASTNode::ID value = this->assignmentExpression(); + if (!value) { + return ASTNode::ID::Invalid(); + } + CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id)); + getNode(child).addChild(value); + } else { + CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id)); + } + while (!this->checkNext(Token::RBRACE)) { + if (!this->expect(Token::COMMA, "','")) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::IDENTIFIER, "an identifier", &id)) { + return ASTNode::ID::Invalid(); + } + if (this->checkNext(Token::EQ)) { + ASTNode::ID value = this->assignmentExpression(); + if (!value) { + return ASTNode::ID::Invalid(); + } + CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id)); + getNode(child).addChild(value); + } else { + CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id)); + } + } + } + this->expect(Token::SEMICOLON, "';'"); + return result; +} + +/* enumDeclaration | modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter + (COMMA parameter)* RPAREN (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */ +ASTNode::ID Parser::declaration() { + Token lookahead = this->peek(); + if (lookahead.fKind == Token::ENUM) { + return this->enumDeclaration(); + } + Modifiers modifiers = this->modifiers(); + lookahead = this->peek(); + if (lookahead.fKind == Token::IDENTIFIER && !this->isType(this->text(lookahead))) { + // we have an identifier that's not a type, could be the start of an interface block + return this->interfaceBlock(modifiers); + } + if (lookahead.fKind == Token::STRUCT) { + return this->structVarDeclaration(modifiers); + } + if (lookahead.fKind == Token::SEMICOLON) { + this->nextToken(); + RETURN_NODE(lookahead.fOffset, ASTNode::Kind::kModifiers, modifiers); + } + ASTNode::ID type = this->type(); + if (!type) { + return ASTNode::ID::Invalid(); + } + if (getNode(type).getTypeData().fIsStructDeclaration && this->checkNext(Token::SEMICOLON)) { + return ASTNode::ID::Invalid(); + } + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + if (this->checkNext(Token::LPAREN)) { + CREATE_NODE(result, name.fOffset, ASTNode::Kind::kFunction); + ASTNode::FunctionData fd(modifiers, this->text(name), 0); + getNode(result).addChild(type); + if (this->peek().fKind != Token::RPAREN) { + for (;;) { + ASTNode::ID parameter = this->parameter(); + if (!parameter) { + return ASTNode::ID::Invalid(); + } + ++fd.fParameterCount; + getNode(result).addChild(parameter); + if (!this->checkNext(Token::COMMA)) { + break; + } + } + } + getNode(result).setFunctionData(fd); + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID body; + if (!this->checkNext(Token::SEMICOLON)) { + body = this->block(); + if (!body) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(body); + } + return result; + } else { + return this->varDeclarationEnd(modifiers, type, this->text(name)); + } +} + +/* modifiers type IDENTIFIER varDeclarationEnd */ +ASTNode::ID Parser::varDeclarations() { + Modifiers modifiers = this->modifiers(); + ASTNode::ID type = this->type(); + if (!type) { + return ASTNode::ID::Invalid(); + } + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + return this->varDeclarationEnd(modifiers, type, this->text(name)); +} + +/* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */ +ASTNode::ID Parser::structDeclaration() { + if (!this->expect(Token::STRUCT, "'struct'")) { + return ASTNode::ID::Invalid(); + } + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LBRACE, "'{'")) { + return ASTNode::ID::Invalid(); + } + std::vector fields; + while (this->peek().fKind != Token::RBRACE) { + ASTNode::ID decls = this->varDeclarations(); + if (!decls) { + return ASTNode::ID::Invalid(); + } + ASTNode& declsNode = getNode(decls); + auto type = (const Type*) fTypes[(declsNode.begin() + 1)->getTypeData().fName]; + for (auto iter = declsNode.begin() + 2; iter != declsNode.end(); ++iter) { + ASTNode& var = *iter; + ASTNode::VarData vd = var.getVarData(); + for (int j = vd.fSizeCount - 1; j >= 0; j--) { + const ASTNode& size = *(var.begin() + j); + if (!size || size.fKind != ASTNode::Kind::kInt) { + this->error(declsNode.fOffset, "array size in struct field must be a constant"); + return ASTNode::ID::Invalid(); + } + uint64_t columns = size.getInt(); + String name = type->name() + "[" + to_string(columns) + "]"; + type = (Type*) fTypes.takeOwnership(std::unique_ptr( + new Type(name, + Type::kArray_Kind, + *type, + (int) columns))); + } + fields.push_back(Type::Field(declsNode.begin()->getModifiers(), vd.fName, type)); + if (vd.fSizeCount ? (var.begin() + (vd.fSizeCount - 1))->fNext : var.fFirstChild) { + this->error(declsNode.fOffset, "initializers are not permitted on struct fields"); + } + } + } + if (!this->expect(Token::RBRACE, "'}'")) { + return ASTNode::ID::Invalid(); + } + fTypes.add(this->text(name), std::unique_ptr(new Type(name.fOffset, this->text(name), + fields))); + RETURN_NODE(name.fOffset, ASTNode::Kind::kType, + ASTNode::TypeData(this->text(name), true, false)); +} + +/* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */ +ASTNode::ID Parser::structVarDeclaration(Modifiers modifiers) { + ASTNode::ID type = this->structDeclaration(); + if (!type) { + return ASTNode::ID::Invalid(); + } + Token name; + if (this->checkNext(Token::IDENTIFIER, &name)) { + return this->varDeclarationEnd(modifiers, std::move(type), this->text(name)); + } + this->expect(Token::SEMICOLON, "';'"); + return ASTNode::ID::Invalid(); +} + +/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER + (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */ +ASTNode::ID Parser::varDeclarationEnd(Modifiers mods, ASTNode::ID type, StringFragment name) { + CREATE_NODE(result, -1, ASTNode::Kind::kVarDeclarations); + CREATE_CHILD(modifiers, result, -1, ASTNode::Kind::kModifiers, mods); + getNode(result).addChild(type); + CREATE_NODE(currentVar, -1, ASTNode::Kind::kVarDeclaration); + ASTNode::VarData vd(name, 0); + getNode(result).addChild(currentVar); + while (this->checkNext(Token::LBRACKET)) { + if (this->checkNext(Token::RBRACKET)) { + CREATE_EMPTY_CHILD(currentVar); + } else { + ASTNode::ID size = this->expression(); + if (!size) { + return ASTNode::ID::Invalid(); + } + getNode(currentVar).addChild(size); + if (!this->expect(Token::RBRACKET, "']'")) { + return ASTNode::ID::Invalid(); + } + } + ++vd.fSizeCount; + } + getNode(currentVar).setVarData(vd); + if (this->checkNext(Token::EQ)) { + ASTNode::ID value = this->assignmentExpression(); + if (!value) { + return ASTNode::ID::Invalid(); + } + getNode(currentVar).addChild(value); + } + while (this->checkNext(Token::COMMA)) { + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + currentVar = ASTNode::ID(fFile->fNodes.size()); + vd = ASTNode::VarData(this->text(name), 0); + fFile->fNodes.emplace_back(&fFile->fNodes, -1, ASTNode::Kind::kVarDeclaration); + getNode(result).addChild(currentVar); + while (this->checkNext(Token::LBRACKET)) { + if (this->checkNext(Token::RBRACKET)) { + CREATE_EMPTY_CHILD(currentVar); + } else { + ASTNode::ID size = this->expression(); + if (!size) { + return ASTNode::ID::Invalid(); + } + getNode(currentVar).addChild(size); + if (!this->expect(Token::RBRACKET, "']'")) { + return ASTNode::ID::Invalid(); + } + } + ++vd.fSizeCount; + } + getNode(currentVar).setVarData(vd); + if (this->checkNext(Token::EQ)) { + ASTNode::ID value = this->assignmentExpression(); + if (!value) { + return ASTNode::ID::Invalid(); + } + getNode(currentVar).addChild(value); + } + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + return result; +} + +/* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */ +ASTNode::ID Parser::parameter() { + Modifiers modifiers = this->modifiersWithDefaults(0); + ASTNode::ID type = this->type(); + if (!type) { + return ASTNode::ID::Invalid(); + } + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, name.fOffset, ASTNode::Kind::kParameter); + ASTNode::ParameterData pd(modifiers, this->text(name), 0); + getNode(result).addChild(type); + while (this->checkNext(Token::LBRACKET)) { + Token sizeToken; + if (!this->expect(Token::INT_LITERAL, "a positive integer", &sizeToken)) { + return ASTNode::ID::Invalid(); + } + CREATE_CHILD(child, result, sizeToken.fOffset, ASTNode::Kind::kInt, + SkSL::stoi(this->text(sizeToken))); + if (!this->expect(Token::RBRACKET, "']'")) { + return ASTNode::ID::Invalid(); + } + ++pd.fSizeCount; + } + getNode(result).setParameterData(pd); + return result; +} + +/** EQ INT_LITERAL */ +int Parser::layoutInt() { + if (!this->expect(Token::EQ, "'='")) { + return -1; + } + Token resultToken; + if (this->expect(Token::INT_LITERAL, "a non-negative integer", &resultToken)) { + return SkSL::stoi(this->text(resultToken)); + } + return -1; +} + +/** EQ IDENTIFIER */ +StringFragment Parser::layoutIdentifier() { + if (!this->expect(Token::EQ, "'='")) { + return StringFragment(); + } + Token resultToken; + if (!this->expect(Token::IDENTIFIER, "an identifier", &resultToken)) { + return StringFragment(); + } + return this->text(resultToken); +} + + +/** EQ */ +StringFragment Parser::layoutCode() { + if (!this->expect(Token::EQ, "'='")) { + return ""; + } + Token start = this->nextRawToken(); + this->pushback(start); + StringFragment code; + code.fChars = fText + start.fOffset; + int level = 1; + bool done = false; + while (!done) { + Token next = this->nextRawToken(); + switch (next.fKind) { + case Token::LPAREN: + ++level; + break; + case Token::RPAREN: + --level; + break; + case Token::COMMA: + if (level == 1) { + done = true; + } + break; + case Token::END_OF_FILE: + this->error(start, "reached end of file while parsing layout"); + return ""; + default: + break; + } + if (!level) { + done = true; + } + if (done) { + code.fLength = next.fOffset - start.fOffset; + this->pushback(std::move(next)); + } + } + return code; +} + +/** (EQ IDENTIFIER('identity'))? */ +Layout::Key Parser::layoutKey() { + if (this->peek().fKind == Token::EQ) { + this->expect(Token::EQ, "'='"); + Token key; + if (this->expect(Token::IDENTIFIER, "an identifer", &key)) { + if (this->text(key) == "identity") { + return Layout::kIdentity_Key; + } else { + this->error(key, "unsupported layout key"); + } + } + } + return Layout::kKey_Key; +} + +Layout::CType Parser::layoutCType() { + if (this->expect(Token::EQ, "'='")) { + Token t = this->nextToken(); + String text = this->text(t); + auto found = layoutTokens->find(text); + if (found != layoutTokens->end()) { + switch (found->second) { + case LayoutToken::SKPMCOLOR4F: + return Layout::CType::kSkPMColor4f; + case LayoutToken::SKVECTOR4: + return Layout::CType::kSkVector4; + case LayoutToken::SKRECT: + return Layout::CType::kSkRect; + case LayoutToken::SKIRECT: + return Layout::CType::kSkIRect; + case LayoutToken::SKPMCOLOR: + return Layout::CType::kSkPMColor; + case LayoutToken::BOOL: + return Layout::CType::kBool; + case LayoutToken::INT: + return Layout::CType::kInt32; + case LayoutToken::FLOAT: + return Layout::CType::kFloat; + case LayoutToken::SKMATRIX44: + return Layout::CType::kSkMatrix44; + default: + break; + } + } + this->error(t, "unsupported ctype"); + } + return Layout::CType::kDefault; +} + +/* LAYOUT LPAREN IDENTIFIER (EQ INT_LITERAL)? (COMMA IDENTIFIER (EQ INT_LITERAL)?)* RPAREN */ +Layout Parser::layout() { + int flags = 0; + int location = -1; + int offset = -1; + int binding = -1; + int index = -1; + int set = -1; + int builtin = -1; + int inputAttachmentIndex = -1; + Layout::Format format = Layout::Format::kUnspecified; + Layout::Primitive primitive = Layout::kUnspecified_Primitive; + int maxVertices = -1; + int invocations = -1; + StringFragment when; + Layout::Key key = Layout::kNo_Key; + Layout::CType ctype = Layout::CType::kDefault; + if (this->checkNext(Token::LAYOUT)) { + if (!this->expect(Token::LPAREN, "'('")) { + return Layout(flags, location, offset, binding, index, set, builtin, + inputAttachmentIndex, format, primitive, maxVertices, invocations, when, + key, ctype); + } + for (;;) { + Token t = this->nextToken(); + String text = this->text(t); + auto found = layoutTokens->find(text); + if (found != layoutTokens->end()) { + switch (found->second) { + case LayoutToken::LOCATION: + location = this->layoutInt(); + break; + case LayoutToken::OFFSET: + offset = this->layoutInt(); + break; + case LayoutToken::BINDING: + binding = this->layoutInt(); + break; + case LayoutToken::INDEX: + index = this->layoutInt(); + break; + case LayoutToken::SET: + set = this->layoutInt(); + break; + case LayoutToken::BUILTIN: + builtin = this->layoutInt(); + break; + case LayoutToken::INPUT_ATTACHMENT_INDEX: + inputAttachmentIndex = this->layoutInt(); + break; + case LayoutToken::ORIGIN_UPPER_LEFT: + flags |= Layout::kOriginUpperLeft_Flag; + break; + case LayoutToken::OVERRIDE_COVERAGE: + flags |= Layout::kOverrideCoverage_Flag; + break; + case LayoutToken::BLEND_SUPPORT_ALL_EQUATIONS: + flags |= Layout::kBlendSupportAllEquations_Flag; + break; + case LayoutToken::BLEND_SUPPORT_MULTIPLY: + flags |= Layout::kBlendSupportMultiply_Flag; + break; + case LayoutToken::BLEND_SUPPORT_SCREEN: + flags |= Layout::kBlendSupportScreen_Flag; + break; + case LayoutToken::BLEND_SUPPORT_OVERLAY: + flags |= Layout::kBlendSupportOverlay_Flag; + break; + case LayoutToken::BLEND_SUPPORT_DARKEN: + flags |= Layout::kBlendSupportDarken_Flag; + break; + case LayoutToken::BLEND_SUPPORT_LIGHTEN: + flags |= Layout::kBlendSupportLighten_Flag; + break; + case LayoutToken::BLEND_SUPPORT_COLORDODGE: + flags |= Layout::kBlendSupportColorDodge_Flag; + break; + case LayoutToken::BLEND_SUPPORT_COLORBURN: + flags |= Layout::kBlendSupportColorBurn_Flag; + break; + case LayoutToken::BLEND_SUPPORT_HARDLIGHT: + flags |= Layout::kBlendSupportHardLight_Flag; + break; + case LayoutToken::BLEND_SUPPORT_SOFTLIGHT: + flags |= Layout::kBlendSupportSoftLight_Flag; + break; + case LayoutToken::BLEND_SUPPORT_DIFFERENCE: + flags |= Layout::kBlendSupportDifference_Flag; + break; + case LayoutToken::BLEND_SUPPORT_EXCLUSION: + flags |= Layout::kBlendSupportExclusion_Flag; + break; + case LayoutToken::BLEND_SUPPORT_HSL_HUE: + flags |= Layout::kBlendSupportHSLHue_Flag; + break; + case LayoutToken::BLEND_SUPPORT_HSL_SATURATION: + flags |= Layout::kBlendSupportHSLSaturation_Flag; + break; + case LayoutToken::BLEND_SUPPORT_HSL_COLOR: + flags |= Layout::kBlendSupportHSLColor_Flag; + break; + case LayoutToken::BLEND_SUPPORT_HSL_LUMINOSITY: + flags |= Layout::kBlendSupportHSLLuminosity_Flag; + break; + case LayoutToken::PUSH_CONSTANT: + flags |= Layout::kPushConstant_Flag; + break; + case LayoutToken::TRACKED: + flags |= Layout::kTracked_Flag; + break; + case LayoutToken::POINTS: + primitive = Layout::kPoints_Primitive; + break; + case LayoutToken::LINES: + primitive = Layout::kLines_Primitive; + break; + case LayoutToken::LINE_STRIP: + primitive = Layout::kLineStrip_Primitive; + break; + case LayoutToken::LINES_ADJACENCY: + primitive = Layout::kLinesAdjacency_Primitive; + break; + case LayoutToken::TRIANGLES: + primitive = Layout::kTriangles_Primitive; + break; + case LayoutToken::TRIANGLE_STRIP: + primitive = Layout::kTriangleStrip_Primitive; + break; + case LayoutToken::TRIANGLES_ADJACENCY: + primitive = Layout::kTrianglesAdjacency_Primitive; + break; + case LayoutToken::MAX_VERTICES: + maxVertices = this->layoutInt(); + break; + case LayoutToken::INVOCATIONS: + invocations = this->layoutInt(); + break; + case LayoutToken::WHEN: + when = this->layoutCode(); + break; + case LayoutToken::KEY: + key = this->layoutKey(); + break; + case LayoutToken::CTYPE: + ctype = this->layoutCType(); + break; + default: + this->error(t, ("'" + text + "' is not a valid layout qualifier").c_str()); + break; + } + } else if (Layout::ReadFormat(text, &format)) { + // AST::ReadFormat stored the result in 'format'. + } else { + this->error(t, ("'" + text + "' is not a valid layout qualifier").c_str()); + } + if (this->checkNext(Token::RPAREN)) { + break; + } + if (!this->expect(Token::COMMA, "','")) { + break; + } + } + } + return Layout(flags, location, offset, binding, index, set, builtin, inputAttachmentIndex, + format, primitive, maxVertices, invocations, when, key, ctype); +} + +/* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE | + READONLY | WRITEONLY | COHERENT | VOLATILE | RESTRICT | BUFFER | PLS | PLSIN | + PLSOUT)* */ +Modifiers Parser::modifiers() { + Layout layout = this->layout(); + int flags = 0; + for (;;) { + // TODO: handle duplicate / incompatible flags + switch (peek().fKind) { + case Token::UNIFORM: + this->nextToken(); + flags |= Modifiers::kUniform_Flag; + break; + case Token::CONST: + this->nextToken(); + flags |= Modifiers::kConst_Flag; + break; + case Token::IN: + this->nextToken(); + flags |= Modifiers::kIn_Flag; + break; + case Token::OUT: + this->nextToken(); + flags |= Modifiers::kOut_Flag; + break; + case Token::INOUT: + this->nextToken(); + flags |= Modifiers::kIn_Flag; + flags |= Modifiers::kOut_Flag; + break; + case Token::FLAT: + this->nextToken(); + flags |= Modifiers::kFlat_Flag; + break; + case Token::NOPERSPECTIVE: + this->nextToken(); + flags |= Modifiers::kNoPerspective_Flag; + break; + case Token::READONLY: + this->nextToken(); + flags |= Modifiers::kReadOnly_Flag; + break; + case Token::WRITEONLY: + this->nextToken(); + flags |= Modifiers::kWriteOnly_Flag; + break; + case Token::COHERENT: + this->nextToken(); + flags |= Modifiers::kCoherent_Flag; + break; + case Token::VOLATILE: + this->nextToken(); + flags |= Modifiers::kVolatile_Flag; + break; + case Token::RESTRICT: + this->nextToken(); + flags |= Modifiers::kRestrict_Flag; + break; + case Token::BUFFER: + this->nextToken(); + flags |= Modifiers::kBuffer_Flag; + break; + case Token::HASSIDEEFFECTS: + this->nextToken(); + flags |= Modifiers::kHasSideEffects_Flag; + break; + case Token::PLS: + this->nextToken(); + flags |= Modifiers::kPLS_Flag; + break; + case Token::PLSIN: + this->nextToken(); + flags |= Modifiers::kPLSIn_Flag; + break; + case Token::PLSOUT: + this->nextToken(); + flags |= Modifiers::kPLSOut_Flag; + break; + default: + return Modifiers(layout, flags); + } + } +} + +Modifiers Parser::modifiersWithDefaults(int defaultFlags) { + Modifiers result = this->modifiers(); + if (!result.fFlags) { + return Modifiers(result.fLayout, defaultFlags); + } + return result; +} + +/* ifStatement | forStatement | doStatement | whileStatement | block | expression */ +ASTNode::ID Parser::statement() { + Token start = this->nextToken(); + AutoDepth depth(this); + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + this->pushback(start); + switch (start.fKind) { + case Token::IF: // fall through + case Token::STATIC_IF: + return this->ifStatement(); + case Token::FOR: + return this->forStatement(); + case Token::DO: + return this->doStatement(); + case Token::WHILE: + return this->whileStatement(); + case Token::SWITCH: // fall through + case Token::STATIC_SWITCH: + return this->switchStatement(); + case Token::RETURN: + return this->returnStatement(); + case Token::BREAK: + return this->breakStatement(); + case Token::CONTINUE: + return this->continueStatement(); + case Token::DISCARD: + return this->discardStatement(); + case Token::LBRACE: + return this->block(); + case Token::SEMICOLON: + this->nextToken(); + RETURN_NODE(start.fOffset, ASTNode::Kind::kBlock); + case Token::CONST: + return this->varDeclarations(); + case Token::IDENTIFIER: + if (this->isType(this->text(start))) { + return this->varDeclarations(); + } + // fall through + default: + return this->expressionStatement(); + } +} + +/* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */ +ASTNode::ID Parser::type() { + Token type; + if (!this->expect(Token::IDENTIFIER, "a type", &type)) { + return ASTNode::ID::Invalid(); + } + if (!this->isType(this->text(type))) { + this->error(type, ("no type named '" + this->text(type) + "'").c_str()); + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, type.fOffset, ASTNode::Kind::kType); + ASTNode::TypeData td(this->text(type), false, false); + while (this->checkNext(Token::LBRACKET)) { + if (this->peek().fKind != Token::RBRACKET) { + SKSL_INT i; + if (this->intLiteral(&i)) { + CREATE_CHILD(child, result, -1, ASTNode::Kind::kInt, i); + } else { + return ASTNode::ID::Invalid(); + } + } else { + CREATE_EMPTY_CHILD(result); + } + this->expect(Token::RBRACKET, "']'"); + } + td.fIsNullable = this->checkNext(Token::QUESTION); + getNode(result).setTypeData(td); + return result; +} + +/* IDENTIFIER LBRACE varDeclaration* RBRACE (IDENTIFIER (LBRACKET expression? RBRACKET)*)? */ +ASTNode::ID Parser::interfaceBlock(Modifiers mods) { + Token name; + if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) { + return ASTNode::ID::Invalid(); + } + if (peek().fKind != Token::LBRACE) { + // we only get into interfaceBlock if we found a top-level identifier which was not a type. + // 99% of the time, the user was not actually intending to create an interface block, so + // it's better to report it as an unknown type + this->error(name, "no type named '" + this->text(name) + "'"); + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, name.fOffset, ASTNode::Kind::kInterfaceBlock); + ASTNode::InterfaceBlockData id(mods, this->text(name), 0, "", 0); + this->nextToken(); + while (this->peek().fKind != Token::RBRACE) { + ASTNode::ID decl = this->varDeclarations(); + if (!decl) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(decl); + ++id.fDeclarationCount; + } + this->nextToken(); + std::vector sizes; + StringFragment instanceName; + Token instanceNameToken; + if (this->checkNext(Token::IDENTIFIER, &instanceNameToken)) { + id.fInstanceName = this->text(instanceNameToken); + while (this->checkNext(Token::LBRACKET)) { + if (this->peek().fKind != Token::RBRACKET) { + ASTNode::ID size = this->expression(); + if (!size) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(size); + } else { + CREATE_EMPTY_CHILD(result); + } + ++id.fSizeCount; + this->expect(Token::RBRACKET, "']'"); + } + instanceName = this->text(instanceNameToken); + } + getNode(result).setInterfaceBlockData(id); + this->expect(Token::SEMICOLON, "';'"); + return result; +} + +/* IF LPAREN expression RPAREN statement (ELSE statement)? */ +ASTNode::ID Parser::ifStatement() { + Token start; + bool isStatic = this->checkNext(Token::STATIC_IF, &start); + if (!isStatic && !this->expect(Token::IF, "'if'", &start)) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kIf, isStatic); + if (!this->expect(Token::LPAREN, "'('")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID test = this->expression(); + if (!test) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(test); + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID ifTrue = this->statement(); + if (!ifTrue) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(ifTrue); + ASTNode::ID ifFalse; + if (this->checkNext(Token::ELSE)) { + ifFalse = this->statement(); + if (!ifFalse) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(ifFalse); + } + return result; +} + +/* DO statement WHILE LPAREN expression RPAREN SEMICOLON */ +ASTNode::ID Parser::doStatement() { + Token start; + if (!this->expect(Token::DO, "'do'", &start)) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kDo); + ASTNode::ID statement = this->statement(); + if (!statement) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(statement); + if (!this->expect(Token::WHILE, "'while'")) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LPAREN, "'('")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID test = this->expression(); + if (!test) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(test); + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + return result; +} + +/* WHILE LPAREN expression RPAREN STATEMENT */ +ASTNode::ID Parser::whileStatement() { + Token start; + if (!this->expect(Token::WHILE, "'while'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LPAREN, "'('")) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kWhile); + ASTNode::ID test = this->expression(); + if (!test) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(test); + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID statement = this->statement(); + if (!statement) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(statement); + return result; +} + +/* CASE expression COLON statement* */ +ASTNode::ID Parser::switchCase() { + Token start; + if (!this->expect(Token::CASE, "'case'", &start)) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kSwitchCase); + ASTNode::ID value = this->expression(); + if (!value) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::COLON, "':'")) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(value); + while (this->peek().fKind != Token::RBRACE && this->peek().fKind != Token::CASE && + this->peek().fKind != Token::DEFAULT) { + ASTNode::ID s = this->statement(); + if (!s) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(s); + } + return result; +} + +/* SWITCH LPAREN expression RPAREN LBRACE switchCase* (DEFAULT COLON statement*)? RBRACE */ +ASTNode::ID Parser::switchStatement() { + Token start; + bool isStatic = this->checkNext(Token::STATIC_SWITCH, &start); + if (!isStatic && !this->expect(Token::SWITCH, "'switch'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LPAREN, "'('")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID value = this->expression(); + if (!value) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LBRACE, "'{'")) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kSwitch, isStatic); + getNode(result).addChild(value); + while (this->peek().fKind == Token::CASE) { + ASTNode::ID c = this->switchCase(); + if (!c) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(c); + } + // Requiring default: to be last (in defiance of C and GLSL) was a deliberate decision. Other + // parts of the compiler may rely upon this assumption. + if (this->peek().fKind == Token::DEFAULT) { + Token defaultStart; + SkAssertResult(this->expect(Token::DEFAULT, "'default'", &defaultStart)); + if (!this->expect(Token::COLON, "':'")) { + return ASTNode::ID::Invalid(); + } + CREATE_CHILD(defaultCase, result, defaultStart.fOffset, ASTNode::Kind::kSwitchCase); + CREATE_EMPTY_CHILD(defaultCase); // empty test to signify default case + while (this->peek().fKind != Token::RBRACE) { + ASTNode::ID s = this->statement(); + if (!s) { + return ASTNode::ID::Invalid(); + } + getNode(defaultCase).addChild(s); + } + } + if (!this->expect(Token::RBRACE, "'}'")) { + return ASTNode::ID::Invalid(); + } + return result; +} + +/* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN + STATEMENT */ +ASTNode::ID Parser::forStatement() { + Token start; + if (!this->expect(Token::FOR, "'for'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::LPAREN, "'('")) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kFor); + ASTNode::ID initializer; + Token nextToken = this->peek(); + switch (nextToken.fKind) { + case Token::SEMICOLON: + this->nextToken(); + CREATE_EMPTY_CHILD(result); + break; + case Token::CONST: { + initializer = this->varDeclarations(); + if (!initializer) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(initializer); + break; + } + case Token::IDENTIFIER: { + if (this->isType(this->text(nextToken))) { + initializer = this->varDeclarations(); + if (!initializer) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(initializer); + break; + } + } // fall through + default: + initializer = this->expressionStatement(); + if (!initializer) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(initializer); + } + ASTNode::ID test; + if (this->peek().fKind != Token::SEMICOLON) { + test = this->expression(); + if (!test) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(test); + } else { + CREATE_EMPTY_CHILD(result); + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID next; + if (this->peek().fKind != Token::RPAREN) { + next = this->expression(); + if (!next) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(next); + } else { + CREATE_EMPTY_CHILD(result); + } + if (!this->expect(Token::RPAREN, "')'")) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID statement = this->statement(); + if (!statement) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(statement); + return result; +} + +/* RETURN expression? SEMICOLON */ +ASTNode::ID Parser::returnStatement() { + Token start; + if (!this->expect(Token::RETURN, "'return'", &start)) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kReturn); + if (this->peek().fKind != Token::SEMICOLON) { + ASTNode::ID expression = this->expression(); + if (!expression) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(expression); + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + return result; +} + +/* BREAK SEMICOLON */ +ASTNode::ID Parser::breakStatement() { + Token start; + if (!this->expect(Token::BREAK, "'break'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + RETURN_NODE(start.fOffset, ASTNode::Kind::kBreak); +} + +/* CONTINUE SEMICOLON */ +ASTNode::ID Parser::continueStatement() { + Token start; + if (!this->expect(Token::CONTINUE, "'continue'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + RETURN_NODE(start.fOffset, ASTNode::Kind::kContinue); +} + +/* DISCARD SEMICOLON */ +ASTNode::ID Parser::discardStatement() { + Token start; + if (!this->expect(Token::DISCARD, "'continue'", &start)) { + return ASTNode::ID::Invalid(); + } + if (!this->expect(Token::SEMICOLON, "';'")) { + return ASTNode::ID::Invalid(); + } + RETURN_NODE(start.fOffset, ASTNode::Kind::kDiscard); +} + +/* LBRACE statement* RBRACE */ +ASTNode::ID Parser::block() { + Token start; + if (!this->expect(Token::LBRACE, "'{'", &start)) { + return ASTNode::ID::Invalid(); + } + AutoDepth depth(this); + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, start.fOffset, ASTNode::Kind::kBlock); + for (;;) { + switch (this->peek().fKind) { + case Token::RBRACE: + this->nextToken(); + return result; + case Token::END_OF_FILE: + this->error(this->peek(), "expected '}', but found end of file"); + return ASTNode::ID::Invalid(); + default: { + ASTNode::ID statement = this->statement(); + if (!statement) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(statement); + } + } + } + return result; +} + +/* expression SEMICOLON */ +ASTNode::ID Parser::expressionStatement() { + ASTNode::ID expr = this->expression(); + if (expr) { + if (this->expect(Token::SEMICOLON, "';'")) { + return expr; + } + } + return ASTNode::ID::Invalid(); +} + +/* assignmentExpression (COMMA assignmentExpression)* */ +ASTNode::ID Parser::expression() { + ASTNode::ID result = this->assignmentExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::COMMA, &t)) { + ASTNode::ID right = this->assignmentExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, t.fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* ternaryExpression ((EQEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSEQ | MINUSEQ | SHLEQ | SHREQ | + BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ) + assignmentExpression)* + */ +ASTNode::ID Parser::assignmentExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->ternaryExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + switch (this->peek().fKind) { + case Token::EQ: // fall through + case Token::STAREQ: // fall through + case Token::SLASHEQ: // fall through + case Token::PERCENTEQ: // fall through + case Token::PLUSEQ: // fall through + case Token::MINUSEQ: // fall through + case Token::SHLEQ: // fall through + case Token::SHREQ: // fall through + case Token::BITWISEANDEQ: // fall through + case Token::BITWISEXOREQ: // fall through + case Token::BITWISEOREQ: // fall through + case Token::LOGICALANDEQ: // fall through + case Token::LOGICALXOREQ: // fall through + case Token::LOGICALOREQ: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID right = this->assignmentExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, + std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + break; + } + default: + return result; + } + } +} + +/* logicalOrExpression ('?' expression ':' assignmentExpression)? */ +ASTNode::ID Parser::ternaryExpression() { + AutoDepth depth(this); + ASTNode::ID base = this->logicalOrExpression(); + if (!base) { + return ASTNode::ID::Invalid(); + } + if (this->checkNext(Token::QUESTION)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID trueExpr = this->expression(); + if (!trueExpr) { + return ASTNode::ID::Invalid(); + } + if (this->expect(Token::COLON, "':'")) { + ASTNode::ID falseExpr = this->assignmentExpression(); + if (!falseExpr) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(ternary, getNode(base).fOffset, ASTNode::Kind::kTernary); + getNode(ternary).addChild(base); + getNode(ternary).addChild(trueExpr); + getNode(ternary).addChild(falseExpr); + return ternary; + } + return ASTNode::ID::Invalid(); + } + return base; +} + +/* logicalXorExpression (LOGICALOR logicalXorExpression)* */ +ASTNode::ID Parser::logicalOrExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->logicalXorExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::LOGICALOR, &t)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID right = this->logicalXorExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* logicalAndExpression (LOGICALXOR logicalAndExpression)* */ +ASTNode::ID Parser::logicalXorExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->logicalAndExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::LOGICALXOR, &t)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID right = this->logicalAndExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */ +ASTNode::ID Parser::logicalAndExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->bitwiseOrExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::LOGICALAND, &t)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID right = this->bitwiseOrExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */ +ASTNode::ID Parser::bitwiseOrExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->bitwiseXorExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::BITWISEOR, &t)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID right = this->bitwiseXorExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */ +ASTNode::ID Parser::bitwiseXorExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->bitwiseAndExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::BITWISEXOR, &t)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID right = this->bitwiseAndExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* equalityExpression (BITWISEAND equalityExpression)* */ +ASTNode::ID Parser::bitwiseAndExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->equalityExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + Token t; + while (this->checkNext(Token::BITWISEAND, &t)) { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID right = this->equalityExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + } + return result; +} + +/* relationalExpression ((EQEQ | NEQ) relationalExpression)* */ +ASTNode::ID Parser::equalityExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->relationalExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + switch (this->peek().fKind) { + case Token::EQEQ: // fall through + case Token::NEQ: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID right = this->relationalExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, + std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + break; + } + default: + return result; + } + } +} + +/* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */ +ASTNode::ID Parser::relationalExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->shiftExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + switch (this->peek().fKind) { + case Token::LT: // fall through + case Token::GT: // fall through + case Token::LTEQ: // fall through + case Token::GTEQ: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID right = this->shiftExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, + std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + break; + } + default: + return result; + } + } +} + +/* additiveExpression ((SHL | SHR) additiveExpression)* */ +ASTNode::ID Parser::shiftExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->additiveExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + switch (this->peek().fKind) { + case Token::SHL: // fall through + case Token::SHR: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID right = this->additiveExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, + std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + break; + } + default: + return result; + } + } +} + +/* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */ +ASTNode::ID Parser::additiveExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->multiplicativeExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + switch (this->peek().fKind) { + case Token::PLUS: // fall through + case Token::MINUS: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID right = this->multiplicativeExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, + std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + break; + } + default: + return result; + } + } +} + +/* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */ +ASTNode::ID Parser::multiplicativeExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->unaryExpression(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + switch (this->peek().fKind) { + case Token::STAR: // fall through + case Token::SLASH: // fall through + case Token::PERCENT: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID right = this->unaryExpression(); + if (!right) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, + std::move(t)); + getNode(newResult).addChild(result); + getNode(newResult).addChild(right); + result = newResult; + break; + } + default: + return result; + } + } +} + +/* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */ +ASTNode::ID Parser::unaryExpression() { + AutoDepth depth(this); + switch (this->peek().fKind) { + case Token::PLUS: // fall through + case Token::MINUS: // fall through + case Token::LOGICALNOT: // fall through + case Token::BITWISENOT: // fall through + case Token::PLUSPLUS: // fall through + case Token::MINUSMINUS: { + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + Token t = this->nextToken(); + ASTNode::ID expr = this->unaryExpression(); + if (!expr) { + return ASTNode::ID::Invalid(); + } + CREATE_NODE(result, t.fOffset, ASTNode::Kind::kPrefix, std::move(t)); + getNode(result).addChild(expr); + return result; + } + default: + return this->postfixExpression(); + } +} + +/* term suffix* */ +ASTNode::ID Parser::postfixExpression() { + AutoDepth depth(this); + ASTNode::ID result = this->term(); + if (!result) { + return ASTNode::ID::Invalid(); + } + for (;;) { + Token t = this->peek(); + switch (t.fKind) { + case Token::FLOAT_LITERAL: + if (this->text(t)[0] != '.') { + return result; + } + // fall through + case Token::LBRACKET: + case Token::DOT: + case Token::LPAREN: + case Token::PLUSPLUS: + case Token::MINUSMINUS: + case Token::COLONCOLON: + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + result = this->suffix(result); + if (!result) { + return ASTNode::ID::Invalid(); + } + break; + default: + return result; + } + } +} + +/* LBRACKET expression? RBRACKET | DOT IDENTIFIER | LPAREN parameters RPAREN | + PLUSPLUS | MINUSMINUS | COLONCOLON IDENTIFIER | FLOAT_LITERAL [IDENTIFIER] */ +ASTNode::ID Parser::suffix(ASTNode::ID base) { + SkASSERT(base); + Token next = this->nextToken(); + AutoDepth depth(this); + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + switch (next.fKind) { + case Token::LBRACKET: { + if (this->checkNext(Token::RBRACKET)) { + CREATE_NODE(result, next.fOffset, ASTNode::Kind::kIndex); + getNode(result).addChild(base); + return result; + } + ASTNode::ID e = this->expression(); + if (!e) { + return ASTNode::ID::Invalid(); + } + this->expect(Token::RBRACKET, "']' to complete array access expression"); + CREATE_NODE(result, next.fOffset, ASTNode::Kind::kIndex); + getNode(result).addChild(base); + getNode(result).addChild(e); + return result; + } + case Token::DOT: // fall through + case Token::COLONCOLON: { + int offset = this->peek().fOffset; + StringFragment text; + if (this->identifier(&text)) { + CREATE_NODE(result, offset, ASTNode::Kind::kField, std::move(text)); + getNode(result).addChild(base); + return result; + } + } + case Token::FLOAT_LITERAL: { + // Swizzles that start with a constant number, e.g. '.000r', will be tokenized as + // floating point literals, possibly followed by an identifier. Handle that here. + StringFragment field = this->text(next); + SkASSERT(field.fChars[0] == '.'); + ++field.fChars; + --field.fLength; + for (size_t i = 0; i < field.fLength; ++i) { + if (field.fChars[i] != '0' && field.fChars[i] != '1') { + this->error(next, "invalid swizzle"); + return ASTNode::ID::Invalid(); + } + } + // use the next *raw* token so we don't ignore whitespace - we only care about + // identifiers that directly follow the float + Token id = this->nextRawToken(); + if (id.fKind == Token::IDENTIFIER) { + field.fLength += id.fLength; + } else { + this->pushback(id); + } + CREATE_NODE(result, next.fOffset, ASTNode::Kind::kField, field); + getNode(result).addChild(base); + return result; + } + case Token::LPAREN: { + CREATE_NODE(result, next.fOffset, ASTNode::Kind::kCall); + getNode(result).addChild(base); + if (this->peek().fKind != Token::RPAREN) { + for (;;) { + ASTNode::ID expr = this->assignmentExpression(); + if (!expr) { + return ASTNode::ID::Invalid(); + } + getNode(result).addChild(expr); + if (!this->checkNext(Token::COMMA)) { + break; + } + } + } + this->expect(Token::RPAREN, "')' to complete function parameters"); + return result; + } + case Token::PLUSPLUS: // fall through + case Token::MINUSMINUS: { + CREATE_NODE(result, next.fOffset, ASTNode::Kind::kPostfix, next); + getNode(result).addChild(base); + return result; + } + default: { + this->error(next, "expected expression suffix, but found '" + this->text(next) + "'"); + return ASTNode::ID::Invalid(); + } + } +} + +/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | NULL_LITERAL | '(' expression ')' */ +ASTNode::ID Parser::term() { + Token t = this->peek(); + switch (t.fKind) { + case Token::IDENTIFIER: { + StringFragment text; + if (this->identifier(&text)) { + RETURN_NODE(t.fOffset, ASTNode::Kind::kIdentifier, std::move(text)); + } + } + case Token::INT_LITERAL: { + SKSL_INT i; + if (this->intLiteral(&i)) { + RETURN_NODE(t.fOffset, ASTNode::Kind::kInt, i); + } + break; + } + case Token::FLOAT_LITERAL: { + SKSL_FLOAT f; + if (this->floatLiteral(&f)) { + RETURN_NODE(t.fOffset, ASTNode::Kind::kFloat, f); + } + break; + } + case Token::TRUE_LITERAL: // fall through + case Token::FALSE_LITERAL: { + bool b; + if (this->boolLiteral(&b)) { + RETURN_NODE(t.fOffset, ASTNode::Kind::kBool, b); + } + break; + } + case Token::NULL_LITERAL: + this->nextToken(); + RETURN_NODE(t.fOffset, ASTNode::Kind::kNull); + case Token::LPAREN: { + this->nextToken(); + AutoDepth depth(this); + if (!depth.increase()) { + return ASTNode::ID::Invalid(); + } + ASTNode::ID result = this->expression(); + if (result) { + this->expect(Token::RPAREN, "')' to complete expression"); + return result; + } + break; + } + default: + this->nextToken(); + this->error(t.fOffset, "expected expression, but found '" + this->text(t) + "'"); + } + return ASTNode::ID::Invalid(); +} + +/* INT_LITERAL */ +bool Parser::intLiteral(SKSL_INT* dest) { + Token t; + if (this->expect(Token::INT_LITERAL, "integer literal", &t)) { + *dest = SkSL::stol(this->text(t)); + return true; + } + return false; +} + +/* FLOAT_LITERAL */ +bool Parser::floatLiteral(SKSL_FLOAT* dest) { + Token t; + if (this->expect(Token::FLOAT_LITERAL, "float literal", &t)) { + *dest = SkSL::stod(this->text(t)); + return true; + } + return false; +} + +/* TRUE_LITERAL | FALSE_LITERAL */ +bool Parser::boolLiteral(bool* dest) { + Token t = this->nextToken(); + switch (t.fKind) { + case Token::TRUE_LITERAL: + *dest = true; + return true; + case Token::FALSE_LITERAL: + *dest = false; + return true; + default: + this->error(t, "expected 'true' or 'false', but found '" + this->text(t) + "'"); + return false; + } +} + +/* IDENTIFIER */ +bool Parser::identifier(StringFragment* dest) { + Token t; + if (this->expect(Token::IDENTIFIER, "identifier", &t)) { + *dest = this->text(t); + return true; + } + return false; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLParser.h b/gfx/skia/skia/src/sksl/SkSLParser.h new file mode 100644 index 0000000000..33beb7fdef --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLParser.h @@ -0,0 +1,289 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PARSER +#define SKSL_PARSER + +#include +#include +#include +#include +#include "src/sksl/SkSLASTFile.h" +#include "src/sksl/SkSLASTNode.h" +#include "src/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/ir/SkSLLayout.h" + +struct yy_buffer_state; +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; + +namespace SkSL { + +struct Modifiers; +class SymbolTable; + +/** + * Consumes .sksl text and produces an abstract syntax tree describing the contents. + */ +class Parser { +public: + enum class LayoutToken { + LOCATION, + OFFSET, + BINDING, + INDEX, + SET, + BUILTIN, + INPUT_ATTACHMENT_INDEX, + ORIGIN_UPPER_LEFT, + OVERRIDE_COVERAGE, + BLEND_SUPPORT_ALL_EQUATIONS, + BLEND_SUPPORT_MULTIPLY, + BLEND_SUPPORT_SCREEN, + BLEND_SUPPORT_OVERLAY, + BLEND_SUPPORT_DARKEN, + BLEND_SUPPORT_LIGHTEN, + BLEND_SUPPORT_COLORDODGE, + BLEND_SUPPORT_COLORBURN, + BLEND_SUPPORT_HARDLIGHT, + BLEND_SUPPORT_SOFTLIGHT, + BLEND_SUPPORT_DIFFERENCE, + BLEND_SUPPORT_EXCLUSION, + BLEND_SUPPORT_HSL_HUE, + BLEND_SUPPORT_HSL_SATURATION, + BLEND_SUPPORT_HSL_COLOR, + BLEND_SUPPORT_HSL_LUMINOSITY, + PUSH_CONSTANT, + POINTS, + LINES, + LINE_STRIP, + LINES_ADJACENCY, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLES_ADJACENCY, + MAX_VERTICES, + INVOCATIONS, + WHEN, + KEY, + TRACKED, + CTYPE, + SKPMCOLOR4F, + SKVECTOR4, + SKRECT, + SKIRECT, + SKPMCOLOR, + SKMATRIX44, + BOOL, + INT, + FLOAT, + }; + + Parser(const char* text, size_t length, SymbolTable& types, ErrorReporter& errors); + + /** + * Consumes a complete .sksl file and returns the parse tree. Errors are reported via the + * ErrorReporter; the return value may contain some declarations even when errors have occurred. + */ + std::unique_ptr file(); + + StringFragment text(Token token); + + Position position(Token token); + +private: + static void InitLayoutMap(); + + /** + * Return the next token, including whitespace tokens, from the parse stream. + */ + Token nextRawToken(); + + /** + * Return the next non-whitespace token from the parse stream. + */ + Token nextToken(); + + /** + * Push a token back onto the parse stream, so that it is the next one read. Only a single level + * of pushback is supported (that is, it is an error to call pushback() twice in a row without + * an intervening nextToken()). + */ + void pushback(Token t); + + /** + * Returns the next non-whitespace token without consuming it from the stream. + */ + Token peek(); + + /** + * Checks to see if the next token is of the specified type. If so, stores it in result (if + * result is non-null) and returns true. Otherwise, pushes it back and returns false. + */ + bool checkNext(Token::Kind kind, Token* result = nullptr); + + /** + * Reads the next non-whitespace token and generates an error if it is not the expected type. + * The 'expected' string is part of the error message, which reads: + * + * "expected , but found ''" + * + * If 'result' is non-null, it is set to point to the token that was read. + * Returns true if the read token was as expected, false otherwise. + */ + bool expect(Token::Kind kind, const char* expected, Token* result = nullptr); + bool expect(Token::Kind kind, String expected, Token* result = nullptr); + + void error(Token token, String msg); + void error(int offset, String msg); + /** + * Returns true if the 'name' identifier refers to a type name. For instance, isType("int") will + * always return true. + */ + bool isType(StringFragment name); + + // The pointer to the node may be invalidated by modifying the fNodes vector + ASTNode& getNode(ASTNode::ID id) { + SkASSERT(id.fValue >= 0 && id.fValue < (int) fFile->fNodes.size()); + return fFile->fNodes[id.fValue]; + } + + // these functions parse individual grammar rules from the current parse position; you probably + // don't need to call any of these outside of the parser. The function declarations in the .cpp + // file have comments describing the grammar rules. + + ASTNode::ID precision(); + + ASTNode::ID directive(); + + ASTNode::ID section(); + + ASTNode::ID enumDeclaration(); + + ASTNode::ID declaration(); + + ASTNode::ID varDeclarations(); + + ASTNode::ID structDeclaration(); + + ASTNode::ID structVarDeclaration(Modifiers modifiers); + + ASTNode::ID varDeclarationEnd(Modifiers modifiers, ASTNode::ID type, StringFragment name); + + ASTNode::ID parameter(); + + int layoutInt(); + + StringFragment layoutIdentifier(); + + StringFragment layoutCode(); + + Layout::Key layoutKey(); + + Layout::CType layoutCType(); + + Layout layout(); + + Modifiers modifiers(); + + Modifiers modifiersWithDefaults(int defaultFlags); + + ASTNode::ID statement(); + + ASTNode::ID type(); + + ASTNode::ID interfaceBlock(Modifiers mods); + + ASTNode::ID ifStatement(); + + ASTNode::ID doStatement(); + + ASTNode::ID whileStatement(); + + ASTNode::ID forStatement(); + + ASTNode::ID switchCase(); + + ASTNode::ID switchStatement(); + + ASTNode::ID returnStatement(); + + ASTNode::ID breakStatement(); + + ASTNode::ID continueStatement(); + + ASTNode::ID discardStatement(); + + ASTNode::ID block(); + + ASTNode::ID expressionStatement(); + + ASTNode::ID expression(); + + ASTNode::ID assignmentExpression(); + + ASTNode::ID ternaryExpression(); + + ASTNode::ID logicalOrExpression(); + + ASTNode::ID logicalXorExpression(); + + ASTNode::ID logicalAndExpression(); + + ASTNode::ID bitwiseOrExpression(); + + ASTNode::ID bitwiseXorExpression(); + + ASTNode::ID bitwiseAndExpression(); + + ASTNode::ID equalityExpression(); + + ASTNode::ID relationalExpression(); + + ASTNode::ID shiftExpression(); + + ASTNode::ID additiveExpression(); + + ASTNode::ID multiplicativeExpression(); + + ASTNode::ID unaryExpression(); + + ASTNode::ID postfixExpression(); + + ASTNode::ID suffix(ASTNode::ID base); + + ASTNode::ID term(); + + bool intLiteral(SKSL_INT* dest); + + bool floatLiteral(SKSL_FLOAT* dest); + + bool boolLiteral(bool* dest); + + bool identifier(StringFragment* dest); + + static std::unordered_map* layoutTokens; + + const char* fText; + Lexer fLexer; + YY_BUFFER_STATE fBuffer; + // current parse depth, used to enforce a recursion limit to try to keep us from overflowing the + // stack on pathological inputs + int fDepth = 0; + Token fPushback; + SymbolTable& fTypes; + ErrorReporter& fErrors; + + std::unique_ptr fFile; + + friend class AutoDepth; + friend class HCodeGenerator; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp new file mode 100644 index 0000000000..dde55f64d1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp @@ -0,0 +1,297 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLPipelineStageCodeGenerator.h" + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLHCodeGenerator.h" + +#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU + +namespace SkSL { + +PipelineStageCodeGenerator::PipelineStageCodeGenerator( + const Context* context, + const Program* program, + ErrorReporter* errors, + OutputStream* out, + std::vector* outFormatArgs, + std::vector* outFunctions) +: INHERITED(context, program, errors, out) +, fName("Temp") +, fFullName(String::printf("Gr%s", fName.c_str())) +, fSectionAndParameterHelper(program, *errors) +, fFormatArgs(outFormatArgs) +, fFunctions(outFunctions) {} + +void PipelineStageCodeGenerator::writef(const char* s, va_list va) { + static constexpr int BUFFER_SIZE = 1024; + va_list copy; + va_copy(copy, va); + char buffer[BUFFER_SIZE]; + int length = vsnprintf(buffer, BUFFER_SIZE, s, va); + if (length < BUFFER_SIZE) { + fOut->write(buffer, length); + } else { + std::unique_ptr heap(new char[length + 1]); + vsprintf(heap.get(), s, copy); + fOut->write(heap.get(), length); + } + va_end(copy); +} + +void PipelineStageCodeGenerator::writef(const char* s, ...) { + va_list va; + va_start(va, s); + this->writef(s, va); + va_end(va); +} + +void PipelineStageCodeGenerator::writeHeader() { +} + +bool PipelineStageCodeGenerator::usesPrecisionModifiers() const { + return false; +} + +String PipelineStageCodeGenerator::getTypeName(const Type& type) { + return type.name(); +} + +void PipelineStageCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + if (b.fOperator == Token::PERCENT) { + // need to use "%%" instead of "%" b/c the code will be inside of a printf + Precedence precedence = GetBinaryPrecedence(b.fOperator); + if (precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*b.fLeft, precedence); + this->write(" %% "); + this->writeExpression(*b.fRight, precedence); + if (precedence >= parentPrecedence) { + this->write(")"); + } + } else { + INHERITED::writeBinaryExpression(b, parentPrecedence); + } +} + +void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) { + if (c.fFunction.fBuiltin && c.fFunction.fName == "sample" && + c.fArguments[0]->fType.kind() != Type::Kind::kSampler_Kind) { + SkASSERT(c.fArguments.size() == 1); + SkASSERT("fragmentProcessor" == c.fArguments[0]->fType.name() || + "fragmentProcessor?" == c.fArguments[0]->fType.name()); + SkASSERT(Expression::kVariableReference_Kind == c.fArguments[0]->fKind); + int index = 0; + bool found = false; + for (const auto& p : fProgram) { + if (ProgramElement::kVar_Kind == p.fKind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + VarDeclaration& decl = (VarDeclaration&) *raw; + if (decl.fVar == &((VariableReference&) *c.fArguments[0]).fVariable) { + found = true; + } else if (decl.fVar->fType == *fContext.fFragmentProcessor_Type) { + ++index; + } + } + } + if (found) { + break; + } + } + SkASSERT(found); + this->write("%s"); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kChildProcessor, + index)); + return; + } + if (c.fFunction.fBuiltin) { + INHERITED::writeFunctionCall(c); + } else { + this->write("%s"); + int index = 0; + for (const auto& e : fProgram) { + if (e.fKind == ProgramElement::kFunction_Kind) { + if (&((FunctionDefinition&) e).fDeclaration == &c.fFunction) { + break; + } + ++index; + } + } + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kFunctionName, + index)); + this->write("("); + const char* separator = ""; + for (const auto& arg : c.fArguments) { + this->write(separator); + separator = ", "; + this->writeExpression(*arg, kSequence_Precedence); + } + this->write(")"); + } +} + +void PipelineStageCodeGenerator::writeIntLiteral(const IntLiteral& i) { + this->write(to_string((int32_t) i.fValue)); +} + +void PipelineStageCodeGenerator::writeVariableReference(const VariableReference& ref) { + switch (ref.fVariable.fModifiers.fLayout.fBuiltin) { + case SK_INCOLOR_BUILTIN: + this->write("%s"); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kInput)); + break; + case SK_OUTCOLOR_BUILTIN: + this->write("%s"); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kOutput)); + break; + case SK_MAIN_X_BUILTIN: + this->write("%s"); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kCoordX)); + break; + case SK_MAIN_Y_BUILTIN: + this->write("%s"); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kCoordY)); + break; + default: + if (ref.fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag) { + this->write("%s"); + int index = 0; + bool found = false; + for (const auto& e : fProgram) { + if (found) { + break; + } + if (e.fKind == ProgramElement::Kind::kVar_Kind) { + const VarDeclarations& decls = (const VarDeclarations&) e; + for (const auto& decl : decls.fVars) { + const Variable& var = *((VarDeclaration&) *decl).fVar; + if (&var == &ref.fVariable) { + found = true; + break; + } + if (var.fModifiers.fFlags & Modifiers::kUniform_Flag) { + ++index; + } + } + } + } + SkASSERT(found); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kUniform, + index)); + } else { + this->write(ref.fVariable.fName); + } + } +} + +void PipelineStageCodeGenerator::writeIfStatement(const IfStatement& s) { + if (s.fIsStatic) { + this->write("@"); + } + INHERITED::writeIfStatement(s); +} + +void PipelineStageCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + if (s.fIsStatic) { + this->write("@"); + } + INHERITED::writeSwitchStatement(s); +} + +static GrSLType glsltype(const Context& context, const Type& type) { + if (type == *context.fFloat_Type) { + return GrSLType::kFloat_GrSLType; + } else if (type == *context.fHalf_Type) { + return GrSLType::kHalf_GrSLType; + } else if (type == *context.fFloat2_Type) { + return GrSLType::kFloat2_GrSLType; + } else if (type == *context.fHalf2_Type) { + return GrSLType::kHalf2_GrSLType; + } else if (type == *context.fFloat4_Type) { + return GrSLType::kFloat4_GrSLType; + } else if (type == *context.fHalf4_Type) { + return GrSLType::kHalf4_GrSLType; + } else if (type == *context.fFloat4x4_Type) { + return GrSLType::kFloat4x4_GrSLType; + } else if (type == *context.fHalf4x4_Type) { + return GrSLType::kHalf4x4_GrSLType; + } else if (type == *context.fVoid_Type) { + return GrSLType::kVoid_GrSLType; + } + SkASSERT(false); + return GrSLType::kVoid_GrSLType; +} + + +void PipelineStageCodeGenerator::writeFunction(const FunctionDefinition& f) { + fCurrentFunction = &f.fDeclaration; + fFunctionHeader = ""; + OutputStream* oldOut = fOut; + StringStream buffer; + fOut = &buffer; + if (f.fDeclaration.fName == "main") { + this->write("%s = %s;\n"); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kOutput)); + fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kInput)); + for (const auto& s : ((Block&) *f.fBody).fStatements) { + this->writeStatement(*s); + this->writeLine(); + } + fOut = oldOut; + this->write(fFunctionHeader); + this->writef("%s", buffer.str().c_str()); + } else { + const FunctionDeclaration& decl = f.fDeclaration; + Compiler::GLSLFunction result; + result.fReturnType = glsltype(fContext, decl.fReturnType); + result.fName = decl.fName; + for (const Variable* v : decl.fParameters) { + result.fParameters.emplace_back(v->fName, glsltype(fContext, v->fType)); + } + for (const auto& s : ((Block&) *f.fBody).fStatements) { + this->writeStatement(*s); + this->writeLine(); + } + fOut = oldOut; + result.fBody = buffer.str(); + fFunctions->push_back(result); + } +} + +bool PipelineStageCodeGenerator::writeSection(const char* name, const char* prefix) { + const Section* s = fSectionAndParameterHelper.getSection(name); + if (s) { + this->writef("%s%s", prefix, s->fText.c_str()); + return true; + } + return false; +} + +void PipelineStageCodeGenerator::writeProgramElement(const ProgramElement& p) { + if (p.fKind == ProgramElement::kSection_Kind) { + return; + } + if (p.fKind == ProgramElement::kVar_Kind) { + const VarDeclarations& decls = (const VarDeclarations&) p; + if (!decls.fVars.size()) { + return; + } + const Variable& var = *((VarDeclaration&) *decls.fVars[0]).fVar; + if (var.fModifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kUniform_Flag) || + -1 != var.fModifiers.fLayout.fBuiltin) { + return; + } + } + INHERITED::writeProgramElement(p); +} + +} // namespace +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.h new file mode 100644 index 0000000000..183b66b752 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLPipelineStageCodeGenerator.h @@ -0,0 +1,73 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PIPELINESTAGECODEGENERATOR +#define SKSL_PIPELINESTAGECODEGENERATOR + +#include "src/sksl/SkSLGLSLCodeGenerator.h" +#include "src/sksl/SkSLSectionAndParameterHelper.h" + +#include + +#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU + +namespace SkSL { + +class PipelineStageCodeGenerator : public GLSLCodeGenerator { +public: + PipelineStageCodeGenerator(const Context* context, const Program* program, + ErrorReporter* errors, OutputStream* out, + std::vector* outFormatArgs, + std::vector* outFunctions); + +private: + void writef(const char* s, va_list va) SKSL_PRINTF_LIKE(2, 0); + + void writef(const char* s, ...) SKSL_PRINTF_LIKE(2, 3); + + bool writeSection(const char* name, const char* prefix = ""); + + void writeHeader() override; + + bool usesPrecisionModifiers() const override; + + String getTypeName(const Type& type) override; + + void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence) override; + + void writeFunctionCall(const FunctionCall& c) override; + + void writeIntLiteral(const IntLiteral& i) override; + + void writeVariableReference(const VariableReference& ref) override; + + void writeIfStatement(const IfStatement& s) override; + + void writeSwitchStatement(const SwitchStatement& s) override; + + void writeFunction(const FunctionDefinition& f) override; + + void writeProgramElement(const ProgramElement& p) override; + + bool writeEmitCode(std::vector& uniforms); + + String fName; + String fFullName; + SectionAndParameterHelper fSectionAndParameterHelper; + std::set fWrittenTransformedCoords; + std::vector* fFormatArgs; + std::vector* fFunctions; + const FunctionDeclaration* fCurrentFunction; + + typedef GLSLCodeGenerator INHERITED; +}; + +} + +#endif + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLPosition.h b/gfx/skia/skia/src/sksl/SkSLPosition.h new file mode 100644 index 0000000000..96a2f4b919 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLPosition.h @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_POSITION +#define SKSL_POSITION + +#include "src/sksl/SkSLString.h" +#include "src/sksl/SkSLUtil.h" + +namespace SkSL { + +/** + * Represents a position in the source code. Both line and column are one-based. Column is currently + * ignored. + */ +struct Position { + Position() + : fLine(-1) + , fColumn(-1) {} + + Position(int line, int column) + : fLine(line) + , fColumn(column) {} + + String description() const { + return to_string(fLine); + } + + int fLine; + int fColumn; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.cpp b/gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.cpp new file mode 100644 index 0000000000..b7ffd1c7de --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.cpp @@ -0,0 +1,3257 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLSPIRVCodeGenerator.h" + +#include "src/sksl/GLSL.std.450.h" + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#ifdef SK_VULKAN +#include "src/gpu/vk/GrVkCaps.h" +#endif + +namespace SkSL { + +static const int32_t SKSL_MAGIC = 0x0; // FIXME: we should probably register a magic number + +void SPIRVCodeGenerator::setupIntrinsics() { +#define ALL_GLSL(x) std::make_tuple(kGLSL_STD_450_IntrinsicKind, GLSLstd450 ## x, GLSLstd450 ## x, \ + GLSLstd450 ## x, GLSLstd450 ## x) +#define BY_TYPE_GLSL(ifFloat, ifInt, ifUInt) std::make_tuple(kGLSL_STD_450_IntrinsicKind, \ + GLSLstd450 ## ifFloat, \ + GLSLstd450 ## ifInt, \ + GLSLstd450 ## ifUInt, \ + SpvOpUndef) +#define ALL_SPIRV(x) std::make_tuple(kSPIRV_IntrinsicKind, SpvOp ## x, SpvOp ## x, SpvOp ## x, \ + SpvOp ## x) +#define SPECIAL(x) std::make_tuple(kSpecial_IntrinsicKind, k ## x ## _SpecialIntrinsic, \ + k ## x ## _SpecialIntrinsic, k ## x ## _SpecialIntrinsic, \ + k ## x ## _SpecialIntrinsic) + fIntrinsicMap[String("round")] = ALL_GLSL(Round); + fIntrinsicMap[String("roundEven")] = ALL_GLSL(RoundEven); + fIntrinsicMap[String("trunc")] = ALL_GLSL(Trunc); + fIntrinsicMap[String("abs")] = BY_TYPE_GLSL(FAbs, SAbs, SAbs); + fIntrinsicMap[String("sign")] = BY_TYPE_GLSL(FSign, SSign, SSign); + fIntrinsicMap[String("floor")] = ALL_GLSL(Floor); + fIntrinsicMap[String("ceil")] = ALL_GLSL(Ceil); + fIntrinsicMap[String("fract")] = ALL_GLSL(Fract); + fIntrinsicMap[String("radians")] = ALL_GLSL(Radians); + fIntrinsicMap[String("degrees")] = ALL_GLSL(Degrees); + fIntrinsicMap[String("sin")] = ALL_GLSL(Sin); + fIntrinsicMap[String("cos")] = ALL_GLSL(Cos); + fIntrinsicMap[String("tan")] = ALL_GLSL(Tan); + fIntrinsicMap[String("asin")] = ALL_GLSL(Asin); + fIntrinsicMap[String("acos")] = ALL_GLSL(Acos); + fIntrinsicMap[String("atan")] = SPECIAL(Atan); + fIntrinsicMap[String("sinh")] = ALL_GLSL(Sinh); + fIntrinsicMap[String("cosh")] = ALL_GLSL(Cosh); + fIntrinsicMap[String("tanh")] = ALL_GLSL(Tanh); + fIntrinsicMap[String("asinh")] = ALL_GLSL(Asinh); + fIntrinsicMap[String("acosh")] = ALL_GLSL(Acosh); + fIntrinsicMap[String("atanh")] = ALL_GLSL(Atanh); + fIntrinsicMap[String("pow")] = ALL_GLSL(Pow); + fIntrinsicMap[String("exp")] = ALL_GLSL(Exp); + fIntrinsicMap[String("log")] = ALL_GLSL(Log); + fIntrinsicMap[String("exp2")] = ALL_GLSL(Exp2); + fIntrinsicMap[String("log2")] = ALL_GLSL(Log2); + fIntrinsicMap[String("sqrt")] = ALL_GLSL(Sqrt); + fIntrinsicMap[String("inverse")] = ALL_GLSL(MatrixInverse); + fIntrinsicMap[String("transpose")] = ALL_SPIRV(Transpose); + fIntrinsicMap[String("inversesqrt")] = ALL_GLSL(InverseSqrt); + fIntrinsicMap[String("determinant")] = ALL_GLSL(Determinant); + fIntrinsicMap[String("matrixInverse")] = ALL_GLSL(MatrixInverse); + fIntrinsicMap[String("mod")] = SPECIAL(Mod); + fIntrinsicMap[String("min")] = SPECIAL(Min); + fIntrinsicMap[String("max")] = SPECIAL(Max); + fIntrinsicMap[String("clamp")] = SPECIAL(Clamp); + fIntrinsicMap[String("saturate")] = SPECIAL(Saturate); + fIntrinsicMap[String("dot")] = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDot, + SpvOpUndef, SpvOpUndef, SpvOpUndef); + fIntrinsicMap[String("mix")] = SPECIAL(Mix); + fIntrinsicMap[String("step")] = ALL_GLSL(Step); + fIntrinsicMap[String("smoothstep")] = ALL_GLSL(SmoothStep); + fIntrinsicMap[String("fma")] = ALL_GLSL(Fma); + fIntrinsicMap[String("frexp")] = ALL_GLSL(Frexp); + fIntrinsicMap[String("ldexp")] = ALL_GLSL(Ldexp); + +#define PACK(type) fIntrinsicMap[String("pack" #type)] = ALL_GLSL(Pack ## type); \ + fIntrinsicMap[String("unpack" #type)] = ALL_GLSL(Unpack ## type) + PACK(Snorm4x8); + PACK(Unorm4x8); + PACK(Snorm2x16); + PACK(Unorm2x16); + PACK(Half2x16); + PACK(Double2x32); + fIntrinsicMap[String("length")] = ALL_GLSL(Length); + fIntrinsicMap[String("distance")] = ALL_GLSL(Distance); + fIntrinsicMap[String("cross")] = ALL_GLSL(Cross); + fIntrinsicMap[String("normalize")] = ALL_GLSL(Normalize); + fIntrinsicMap[String("faceForward")] = ALL_GLSL(FaceForward); + fIntrinsicMap[String("reflect")] = ALL_GLSL(Reflect); + fIntrinsicMap[String("refract")] = ALL_GLSL(Refract); + fIntrinsicMap[String("findLSB")] = ALL_GLSL(FindILsb); + fIntrinsicMap[String("findMSB")] = BY_TYPE_GLSL(FindSMsb, FindSMsb, FindUMsb); + fIntrinsicMap[String("dFdx")] = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDPdx, + SpvOpUndef, SpvOpUndef, SpvOpUndef); + fIntrinsicMap[String("dFdy")] = SPECIAL(DFdy); + fIntrinsicMap[String("fwidth")] = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpFwidth, + SpvOpUndef, SpvOpUndef, SpvOpUndef); + fIntrinsicMap[String("makeSampler2D")] = SPECIAL(SampledImage); + + fIntrinsicMap[String("sample")] = SPECIAL(Texture); + fIntrinsicMap[String("subpassLoad")] = SPECIAL(SubpassLoad); + + fIntrinsicMap[String("any")] = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpUndef, + SpvOpUndef, SpvOpUndef, SpvOpAny); + fIntrinsicMap[String("all")] = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpUndef, + SpvOpUndef, SpvOpUndef, SpvOpAll); + fIntrinsicMap[String("equal")] = std::make_tuple(kSPIRV_IntrinsicKind, + SpvOpFOrdEqual, SpvOpIEqual, + SpvOpIEqual, SpvOpLogicalEqual); + fIntrinsicMap[String("notEqual")] = std::make_tuple(kSPIRV_IntrinsicKind, + SpvOpFOrdNotEqual, SpvOpINotEqual, + SpvOpINotEqual, + SpvOpLogicalNotEqual); + fIntrinsicMap[String("lessThan")] = std::make_tuple(kSPIRV_IntrinsicKind, + SpvOpFOrdLessThan, SpvOpSLessThan, + SpvOpULessThan, SpvOpUndef); + fIntrinsicMap[String("lessThanEqual")] = std::make_tuple(kSPIRV_IntrinsicKind, + SpvOpFOrdLessThanEqual, + SpvOpSLessThanEqual, + SpvOpULessThanEqual, + SpvOpUndef); + fIntrinsicMap[String("greaterThan")] = std::make_tuple(kSPIRV_IntrinsicKind, + SpvOpFOrdGreaterThan, + SpvOpSGreaterThan, + SpvOpUGreaterThan, + SpvOpUndef); + fIntrinsicMap[String("greaterThanEqual")] = std::make_tuple(kSPIRV_IntrinsicKind, + SpvOpFOrdGreaterThanEqual, + SpvOpSGreaterThanEqual, + SpvOpUGreaterThanEqual, + SpvOpUndef); + fIntrinsicMap[String("EmitVertex")] = ALL_SPIRV(EmitVertex); + fIntrinsicMap[String("EndPrimitive")] = ALL_SPIRV(EndPrimitive); +// interpolateAt* not yet supported... +} + +void SPIRVCodeGenerator::writeWord(int32_t word, OutputStream& out) { + out.write((const char*) &word, sizeof(word)); +} + +static bool is_float(const Context& context, const Type& type) { + if (type.columns() > 1) { + return is_float(context, type.componentType()); + } + return type == *context.fFloat_Type || type == *context.fHalf_Type || + type == *context.fDouble_Type; +} + +static bool is_signed(const Context& context, const Type& type) { + if (type.kind() == Type::kVector_Kind) { + return is_signed(context, type.componentType()); + } + return type == *context.fInt_Type || type == *context.fShort_Type || + type == *context.fByte_Type; +} + +static bool is_unsigned(const Context& context, const Type& type) { + if (type.kind() == Type::kVector_Kind) { + return is_unsigned(context, type.componentType()); + } + return type == *context.fUInt_Type || type == *context.fUShort_Type || + type == *context.fUByte_Type; +} + +static bool is_bool(const Context& context, const Type& type) { + if (type.kind() == Type::kVector_Kind) { + return is_bool(context, type.componentType()); + } + return type == *context.fBool_Type; +} + +static bool is_out(const Variable& var) { + return (var.fModifiers.fFlags & Modifiers::kOut_Flag) != 0; +} + +void SPIRVCodeGenerator::writeOpCode(SpvOp_ opCode, int length, OutputStream& out) { + SkASSERT(opCode != SpvOpLoad || &out != &fConstantBuffer); + SkASSERT(opCode != SpvOpUndef); + switch (opCode) { + case SpvOpReturn: // fall through + case SpvOpReturnValue: // fall through + case SpvOpKill: // fall through + case SpvOpBranch: // fall through + case SpvOpBranchConditional: + SkASSERT(fCurrentBlock); + fCurrentBlock = 0; + break; + case SpvOpConstant: // fall through + case SpvOpConstantTrue: // fall through + case SpvOpConstantFalse: // fall through + case SpvOpConstantComposite: // fall through + case SpvOpTypeVoid: // fall through + case SpvOpTypeInt: // fall through + case SpvOpTypeFloat: // fall through + case SpvOpTypeBool: // fall through + case SpvOpTypeVector: // fall through + case SpvOpTypeMatrix: // fall through + case SpvOpTypeArray: // fall through + case SpvOpTypePointer: // fall through + case SpvOpTypeFunction: // fall through + case SpvOpTypeRuntimeArray: // fall through + case SpvOpTypeStruct: // fall through + case SpvOpTypeImage: // fall through + case SpvOpTypeSampledImage: // fall through + case SpvOpTypeSampler: // fall through + case SpvOpVariable: // fall through + case SpvOpFunction: // fall through + case SpvOpFunctionParameter: // fall through + case SpvOpFunctionEnd: // fall through + case SpvOpExecutionMode: // fall through + case SpvOpMemoryModel: // fall through + case SpvOpCapability: // fall through + case SpvOpExtInstImport: // fall through + case SpvOpEntryPoint: // fall through + case SpvOpSource: // fall through + case SpvOpSourceExtension: // fall through + case SpvOpName: // fall through + case SpvOpMemberName: // fall through + case SpvOpDecorate: // fall through + case SpvOpMemberDecorate: + break; + default: + SkASSERT(fCurrentBlock); + } + this->writeWord((length << 16) | opCode, out); +} + +void SPIRVCodeGenerator::writeLabel(SpvId label, OutputStream& out) { + fCurrentBlock = label; + this->writeInstruction(SpvOpLabel, label, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, OutputStream& out) { + this->writeOpCode(opCode, 1, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out) { + this->writeOpCode(opCode, 2, out); + this->writeWord(word1, out); +} + +void SPIRVCodeGenerator::writeString(const char* string, size_t length, OutputStream& out) { + out.write(string, length); + switch (length % 4) { + case 1: + out.write8(0); + // fall through + case 2: + out.write8(0); + // fall through + case 3: + out.write8(0); + break; + default: + this->writeWord(0, out); + } +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, StringFragment string, OutputStream& out) { + this->writeOpCode(opCode, 1 + (string.fLength + 4) / 4, out); + this->writeString(string.fChars, string.fLength, out); +} + + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, StringFragment string, + OutputStream& out) { + this->writeOpCode(opCode, 2 + (string.fLength + 4) / 4, out); + this->writeWord(word1, out); + this->writeString(string.fChars, string.fLength, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + StringFragment string, OutputStream& out) { + this->writeOpCode(opCode, 3 + (string.fLength + 4) / 4, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeString(string.fChars, string.fLength, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + OutputStream& out) { + this->writeOpCode(opCode, 3, out); + this->writeWord(word1, out); + this->writeWord(word2, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, OutputStream& out) { + this->writeOpCode(opCode, 4, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, OutputStream& out) { + this->writeOpCode(opCode, 5, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + OutputStream& out) { + this->writeOpCode(opCode, 6, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + int32_t word6, OutputStream& out) { + this->writeOpCode(opCode, 7, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); + this->writeWord(word6, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + int32_t word6, int32_t word7, OutputStream& out) { + this->writeOpCode(opCode, 8, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); + this->writeWord(word6, out); + this->writeWord(word7, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + int32_t word6, int32_t word7, int32_t word8, + OutputStream& out) { + this->writeOpCode(opCode, 9, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); + this->writeWord(word6, out); + this->writeWord(word7, out); + this->writeWord(word8, out); +} + +void SPIRVCodeGenerator::writeCapabilities(OutputStream& out) { + for (uint64_t i = 0, bit = 1; i <= kLast_Capability; i++, bit <<= 1) { + if (fCapabilities & bit) { + this->writeInstruction(SpvOpCapability, (SpvId) i, out); + } + } + if (fProgram.fKind == Program::kGeometry_Kind) { + this->writeInstruction(SpvOpCapability, SpvCapabilityGeometry, out); + } + else { + this->writeInstruction(SpvOpCapability, SpvCapabilityShader, out); + } +} + +SpvId SPIRVCodeGenerator::nextId() { + return fIdCount++; +} + +void SPIRVCodeGenerator::writeStruct(const Type& type, const MemoryLayout& memoryLayout, + SpvId resultId) { + this->writeInstruction(SpvOpName, resultId, type.name().c_str(), fNameBuffer); + // go ahead and write all of the field types, so we don't inadvertently write them while we're + // in the middle of writing the struct instruction + std::vector types; + for (const auto& f : type.fields()) { + types.push_back(this->getType(*f.fType, memoryLayout)); + } + this->writeOpCode(SpvOpTypeStruct, 2 + (int32_t) types.size(), fConstantBuffer); + this->writeWord(resultId, fConstantBuffer); + for (SpvId id : types) { + this->writeWord(id, fConstantBuffer); + } + size_t offset = 0; + for (int32_t i = 0; i < (int32_t) type.fields().size(); i++) { + const Type::Field& field = type.fields()[i]; + size_t size = memoryLayout.size(*field.fType); + size_t alignment = memoryLayout.alignment(*field.fType); + const Layout& fieldLayout = field.fModifiers.fLayout; + if (fieldLayout.fOffset >= 0) { + if (fieldLayout.fOffset < (int) offset) { + fErrors.error(type.fOffset, + "offset of field '" + field.fName + "' must be at " + "least " + to_string((int) offset)); + } + if (fieldLayout.fOffset % alignment) { + fErrors.error(type.fOffset, + "offset of field '" + field.fName + "' must be a multiple" + " of " + to_string((int) alignment)); + } + offset = fieldLayout.fOffset; + } else { + size_t mod = offset % alignment; + if (mod) { + offset += alignment - mod; + } + } + this->writeInstruction(SpvOpMemberName, resultId, i, field.fName, fNameBuffer); + this->writeLayout(fieldLayout, resultId, i); + if (field.fModifiers.fLayout.fBuiltin < 0) { + this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, SpvDecorationOffset, + (SpvId) offset, fDecorationBuffer); + } + if (field.fType->kind() == Type::kMatrix_Kind) { + this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationColMajor, + fDecorationBuffer); + this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationMatrixStride, + (SpvId) memoryLayout.stride(*field.fType), + fDecorationBuffer); + } + if (!field.fType->highPrecision()) { + this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, + SpvDecorationRelaxedPrecision, fDecorationBuffer); + } + offset += size; + Type::Kind kind = field.fType->kind(); + if ((kind == Type::kArray_Kind || kind == Type::kStruct_Kind) && offset % alignment != 0) { + offset += alignment - offset % alignment; + } + } +} + +Type SPIRVCodeGenerator::getActualType(const Type& type) { + if (type.isFloat()) { + return *fContext.fFloat_Type; + } + if (type.isSigned()) { + return *fContext.fInt_Type; + } + if (type.isUnsigned()) { + return *fContext.fUInt_Type; + } + if (type.kind() == Type::kMatrix_Kind || type.kind() == Type::kVector_Kind) { + if (type.componentType() == *fContext.fHalf_Type) { + return fContext.fFloat_Type->toCompound(fContext, type.columns(), type.rows()); + } + if (type.componentType() == *fContext.fShort_Type || + type.componentType() == *fContext.fByte_Type) { + return fContext.fInt_Type->toCompound(fContext, type.columns(), type.rows()); + } + if (type.componentType() == *fContext.fUShort_Type || + type.componentType() == *fContext.fUByte_Type) { + return fContext.fUInt_Type->toCompound(fContext, type.columns(), type.rows()); + } + } + return type; +} + +SpvId SPIRVCodeGenerator::getType(const Type& type) { + return this->getType(type, fDefaultLayout); +} + +SpvId SPIRVCodeGenerator::getType(const Type& rawType, const MemoryLayout& layout) { + Type type = this->getActualType(rawType); + String key = type.name() + to_string((int) layout.fStd); + auto entry = fTypeMap.find(key); + if (entry == fTypeMap.end()) { + SpvId result = this->nextId(); + switch (type.kind()) { + case Type::kScalar_Kind: + if (type == *fContext.fBool_Type) { + this->writeInstruction(SpvOpTypeBool, result, fConstantBuffer); + } else if (type == *fContext.fInt_Type || type == *fContext.fShort_Type || + type == *fContext.fIntLiteral_Type) { + this->writeInstruction(SpvOpTypeInt, result, 32, 1, fConstantBuffer); + } else if (type == *fContext.fUInt_Type || type == *fContext.fUShort_Type) { + this->writeInstruction(SpvOpTypeInt, result, 32, 0, fConstantBuffer); + } else if (type == *fContext.fFloat_Type || type == *fContext.fHalf_Type || + type == *fContext.fFloatLiteral_Type) { + this->writeInstruction(SpvOpTypeFloat, result, 32, fConstantBuffer); + } else if (type == *fContext.fDouble_Type) { + this->writeInstruction(SpvOpTypeFloat, result, 64, fConstantBuffer); + } else { + SkASSERT(false); + } + break; + case Type::kVector_Kind: + this->writeInstruction(SpvOpTypeVector, result, + this->getType(type.componentType(), layout), + type.columns(), fConstantBuffer); + break; + case Type::kMatrix_Kind: + this->writeInstruction(SpvOpTypeMatrix, result, + this->getType(index_type(fContext, type), layout), + type.columns(), fConstantBuffer); + break; + case Type::kStruct_Kind: + this->writeStruct(type, layout, result); + break; + case Type::kArray_Kind: { + if (type.columns() > 0) { + IntLiteral count(fContext, -1, type.columns()); + this->writeInstruction(SpvOpTypeArray, result, + this->getType(type.componentType(), layout), + this->writeIntLiteral(count), fConstantBuffer); + this->writeInstruction(SpvOpDecorate, result, SpvDecorationArrayStride, + (int32_t) layout.stride(type), + fDecorationBuffer); + } else { + SkASSERT(false); // we shouldn't have any runtime-sized arrays right now + this->writeInstruction(SpvOpTypeRuntimeArray, result, + this->getType(type.componentType(), layout), + fConstantBuffer); + this->writeInstruction(SpvOpDecorate, result, SpvDecorationArrayStride, + (int32_t) layout.stride(type), + fDecorationBuffer); + } + break; + } + case Type::kSampler_Kind: { + SpvId image = result; + if (SpvDimSubpassData != type.dimensions()) { + image = this->getType(type.textureType(), layout); + } + if (SpvDimBuffer == type.dimensions()) { + fCapabilities |= (((uint64_t) 1) << SpvCapabilitySampledBuffer); + } + if (SpvDimSubpassData != type.dimensions()) { + this->writeInstruction(SpvOpTypeSampledImage, result, image, fConstantBuffer); + } + break; + } + case Type::kSeparateSampler_Kind: { + this->writeInstruction(SpvOpTypeSampler, result, fConstantBuffer); + break; + } + case Type::kTexture_Kind: { + this->writeInstruction(SpvOpTypeImage, result, + this->getType(*fContext.fFloat_Type, layout), + type.dimensions(), type.isDepth(), type.isArrayed(), + type.isMultisampled(), type.isSampled() ? 1 : 2, + SpvImageFormatUnknown, fConstantBuffer); + fImageTypeMap[key] = result; + break; + } + default: + if (type == *fContext.fVoid_Type) { + this->writeInstruction(SpvOpTypeVoid, result, fConstantBuffer); + } else { + ABORT("invalid type: %s", type.description().c_str()); + } + } + fTypeMap[key] = result; + return result; + } + return entry->second; +} + +SpvId SPIRVCodeGenerator::getImageType(const Type& type) { + SkASSERT(type.kind() == Type::kSampler_Kind); + this->getType(type); + String key = type.name() + to_string((int) fDefaultLayout.fStd); + SkASSERT(fImageTypeMap.find(key) != fImageTypeMap.end()); + return fImageTypeMap[key]; +} + +SpvId SPIRVCodeGenerator::getFunctionType(const FunctionDeclaration& function) { + String key = function.fReturnType.description() + "("; + String separator; + for (size_t i = 0; i < function.fParameters.size(); i++) { + key += separator; + separator = ", "; + key += function.fParameters[i]->fType.description(); + } + key += ")"; + auto entry = fTypeMap.find(key); + if (entry == fTypeMap.end()) { + SpvId result = this->nextId(); + int32_t length = 3 + (int32_t) function.fParameters.size(); + SpvId returnType = this->getType(function.fReturnType); + std::vector parameterTypes; + for (size_t i = 0; i < function.fParameters.size(); i++) { + // glslang seems to treat all function arguments as pointers whether they need to be or + // not. I was initially puzzled by this until I ran bizarre failures with certain + // patterns of function calls and control constructs, as exemplified by this minimal + // failure case: + // + // void sphere(float x) { + // } + // + // void map() { + // sphere(1.0); + // } + // + // void main() { + // for (int i = 0; i < 1; i++) { + // map(); + // } + // } + // + // As of this writing, compiling this in the "obvious" way (with sphere taking a float) + // crashes. Making it take a float* and storing the argument in a temporary variable, + // as glslang does, fixes it. It's entirely possible I simply missed whichever part of + // the spec makes this make sense. +// if (is_out(function->fParameters[i])) { + parameterTypes.push_back(this->getPointerType(function.fParameters[i]->fType, + SpvStorageClassFunction)); +// } else { +// parameterTypes.push_back(this->getType(function.fParameters[i]->fType)); +// } + } + this->writeOpCode(SpvOpTypeFunction, length, fConstantBuffer); + this->writeWord(result, fConstantBuffer); + this->writeWord(returnType, fConstantBuffer); + for (SpvId id : parameterTypes) { + this->writeWord(id, fConstantBuffer); + } + fTypeMap[key] = result; + return result; + } + return entry->second; +} + +SpvId SPIRVCodeGenerator::getPointerType(const Type& type, SpvStorageClass_ storageClass) { + return this->getPointerType(type, fDefaultLayout, storageClass); +} + +SpvId SPIRVCodeGenerator::getPointerType(const Type& rawType, const MemoryLayout& layout, + SpvStorageClass_ storageClass) { + Type type = this->getActualType(rawType); + String key = type.description() + "*" + to_string(layout.fStd) + to_string(storageClass); + auto entry = fTypeMap.find(key); + if (entry == fTypeMap.end()) { + SpvId result = this->nextId(); + this->writeInstruction(SpvOpTypePointer, result, storageClass, + this->getType(type), fConstantBuffer); + fTypeMap[key] = result; + return result; + } + return entry->second; +} + +SpvId SPIRVCodeGenerator::writeExpression(const Expression& expr, OutputStream& out) { + switch (expr.fKind) { + case Expression::kBinary_Kind: + return this->writeBinaryExpression((BinaryExpression&) expr, out); + case Expression::kBoolLiteral_Kind: + return this->writeBoolLiteral((BoolLiteral&) expr); + case Expression::kConstructor_Kind: + return this->writeConstructor((Constructor&) expr, out); + case Expression::kIntLiteral_Kind: + return this->writeIntLiteral((IntLiteral&) expr); + case Expression::kFieldAccess_Kind: + return this->writeFieldAccess(((FieldAccess&) expr), out); + case Expression::kFloatLiteral_Kind: + return this->writeFloatLiteral(((FloatLiteral&) expr)); + case Expression::kFunctionCall_Kind: + return this->writeFunctionCall((FunctionCall&) expr, out); + case Expression::kPrefix_Kind: + return this->writePrefixExpression((PrefixExpression&) expr, out); + case Expression::kPostfix_Kind: + return this->writePostfixExpression((PostfixExpression&) expr, out); + case Expression::kSwizzle_Kind: + return this->writeSwizzle((Swizzle&) expr, out); + case Expression::kVariableReference_Kind: + return this->writeVariableReference((VariableReference&) expr, out); + case Expression::kTernary_Kind: + return this->writeTernaryExpression((TernaryExpression&) expr, out); + case Expression::kIndex_Kind: + return this->writeIndexExpression((IndexExpression&) expr, out); + default: + ABORT("unsupported expression: %s", expr.description().c_str()); + } + return -1; +} + +SpvId SPIRVCodeGenerator::writeIntrinsicCall(const FunctionCall& c, OutputStream& out) { + auto intrinsic = fIntrinsicMap.find(c.fFunction.fName); + SkASSERT(intrinsic != fIntrinsicMap.end()); + int32_t intrinsicId; + if (c.fArguments.size() > 0) { + const Type& type = c.fArguments[0]->fType; + if (std::get<0>(intrinsic->second) == kSpecial_IntrinsicKind || is_float(fContext, type)) { + intrinsicId = std::get<1>(intrinsic->second); + } else if (is_signed(fContext, type)) { + intrinsicId = std::get<2>(intrinsic->second); + } else if (is_unsigned(fContext, type)) { + intrinsicId = std::get<3>(intrinsic->second); + } else if (is_bool(fContext, type)) { + intrinsicId = std::get<4>(intrinsic->second); + } else { + intrinsicId = std::get<1>(intrinsic->second); + } + } else { + intrinsicId = std::get<1>(intrinsic->second); + } + switch (std::get<0>(intrinsic->second)) { + case kGLSL_STD_450_IntrinsicKind: { + SpvId result = this->nextId(); + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + if (c.fFunction.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) { + arguments.push_back(this->getLValue(*c.fArguments[i], out)->getPointer()); + } else { + arguments.push_back(this->writeExpression(*c.fArguments[i], out)); + } + } + this->writeOpCode(SpvOpExtInst, 5 + (int32_t) arguments.size(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + this->writeWord(fGLSLExtendedInstructions, out); + this->writeWord(intrinsicId, out); + for (SpvId id : arguments) { + this->writeWord(id, out); + } + return result; + } + case kSPIRV_IntrinsicKind: { + SpvId result = this->nextId(); + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + if (c.fFunction.fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag) { + arguments.push_back(this->getLValue(*c.fArguments[i], out)->getPointer()); + } else { + arguments.push_back(this->writeExpression(*c.fArguments[i], out)); + } + } + if (c.fType != *fContext.fVoid_Type) { + this->writeOpCode((SpvOp_) intrinsicId, 3 + (int32_t) arguments.size(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + } else { + this->writeOpCode((SpvOp_) intrinsicId, 1 + (int32_t) arguments.size(), out); + } + for (SpvId id : arguments) { + this->writeWord(id, out); + } + return result; + } + case kSpecial_IntrinsicKind: + return this->writeSpecialIntrinsic(c, (SpecialIntrinsic) intrinsicId, out); + default: + ABORT("unsupported intrinsic kind"); + } +} + +std::vector SPIRVCodeGenerator::vectorize( + const std::vector>& args, + OutputStream& out) { + int vectorSize = 0; + for (const auto& a : args) { + if (a->fType.kind() == Type::kVector_Kind) { + if (vectorSize) { + SkASSERT(a->fType.columns() == vectorSize); + } + else { + vectorSize = a->fType.columns(); + } + } + } + std::vector result; + for (const auto& a : args) { + SpvId raw = this->writeExpression(*a, out); + if (vectorSize && a->fType.kind() == Type::kScalar_Kind) { + SpvId vector = this->nextId(); + this->writeOpCode(SpvOpCompositeConstruct, 3 + vectorSize, out); + this->writeWord(this->getType(a->fType.toCompound(fContext, vectorSize, 1)), out); + this->writeWord(vector, out); + for (int i = 0; i < vectorSize; i++) { + this->writeWord(raw, out); + } + this->writePrecisionModifier(a->fType, vector); + result.push_back(vector); + } else { + result.push_back(raw); + } + } + return result; +} + +void SPIRVCodeGenerator::writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst, + SpvId signedInst, SpvId unsignedInst, + const std::vector& args, + OutputStream& out) { + this->writeOpCode(SpvOpExtInst, 5 + args.size(), out); + this->writeWord(this->getType(type), out); + this->writeWord(id, out); + this->writeWord(fGLSLExtendedInstructions, out); + + if (is_float(fContext, type)) { + this->writeWord(floatInst, out); + } else if (is_signed(fContext, type)) { + this->writeWord(signedInst, out); + } else if (is_unsigned(fContext, type)) { + this->writeWord(unsignedInst, out); + } else { + SkASSERT(false); + } + for (SpvId a : args) { + this->writeWord(a, out); + } +} + +SpvId SPIRVCodeGenerator::writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, + OutputStream& out) { + SpvId result = this->nextId(); + switch (kind) { + case kAtan_SpecialIntrinsic: { + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + arguments.push_back(this->writeExpression(*c.fArguments[i], out)); + } + this->writeOpCode(SpvOpExtInst, 5 + (int32_t) arguments.size(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + this->writeWord(fGLSLExtendedInstructions, out); + this->writeWord(arguments.size() == 2 ? GLSLstd450Atan2 : GLSLstd450Atan, out); + for (SpvId id : arguments) { + this->writeWord(id, out); + } + break; + } + case kSampledImage_SpecialIntrinsic: { + SkASSERT(2 == c.fArguments.size()); + SpvId img = this->writeExpression(*c.fArguments[0], out); + SpvId sampler = this->writeExpression(*c.fArguments[1], out); + this->writeInstruction(SpvOpSampledImage, + this->getType(c.fType), + result, + img, + sampler, + out); + break; + } + case kSubpassLoad_SpecialIntrinsic: { + SpvId img = this->writeExpression(*c.fArguments[0], out); + std::vector> args; + args.emplace_back(new FloatLiteral(fContext, -1, 0.0)); + args.emplace_back(new FloatLiteral(fContext, -1, 0.0)); + Constructor ctor(-1, *fContext.fFloat2_Type, std::move(args)); + SpvId coords = this->writeConstantVector(ctor); + if (1 == c.fArguments.size()) { + this->writeInstruction(SpvOpImageRead, + this->getType(c.fType), + result, + img, + coords, + out); + } else { + SkASSERT(2 == c.fArguments.size()); + SpvId sample = this->writeExpression(*c.fArguments[1], out); + this->writeInstruction(SpvOpImageRead, + this->getType(c.fType), + result, + img, + coords, + SpvImageOperandsSampleMask, + sample, + out); + } + break; + } + case kTexture_SpecialIntrinsic: { + SpvOp_ op = SpvOpImageSampleImplicitLod; + switch (c.fArguments[0]->fType.dimensions()) { + case SpvDim1D: + if (c.fArguments[1]->fType == *fContext.fFloat2_Type) { + op = SpvOpImageSampleProjImplicitLod; + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat_Type); + } + break; + case SpvDim2D: + if (c.fArguments[1]->fType == *fContext.fFloat3_Type) { + op = SpvOpImageSampleProjImplicitLod; + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat2_Type); + } + break; + case SpvDim3D: + if (c.fArguments[1]->fType == *fContext.fFloat4_Type) { + op = SpvOpImageSampleProjImplicitLod; + } else { + SkASSERT(c.fArguments[1]->fType == *fContext.fFloat3_Type); + } + break; + case SpvDimCube: // fall through + case SpvDimRect: // fall through + case SpvDimBuffer: // fall through + case SpvDimSubpassData: + break; + } + SpvId type = this->getType(c.fType); + SpvId sampler = this->writeExpression(*c.fArguments[0], out); + SpvId uv = this->writeExpression(*c.fArguments[1], out); + if (c.fArguments.size() == 3) { + this->writeInstruction(op, type, result, sampler, uv, + SpvImageOperandsBiasMask, + this->writeExpression(*c.fArguments[2], out), + out); + } else { + SkASSERT(c.fArguments.size() == 2); + if (fProgram.fSettings.fSharpenTextures) { + FloatLiteral lodBias(fContext, -1, -0.5); + this->writeInstruction(op, type, result, sampler, uv, + SpvImageOperandsBiasMask, + this->writeFloatLiteral(lodBias), + out); + } else { + this->writeInstruction(op, type, result, sampler, uv, + out); + } + } + break; + } + case kMod_SpecialIntrinsic: { + std::vector args = this->vectorize(c.fArguments, out); + SkASSERT(args.size() == 2); + const Type& operandType = c.fArguments[0]->fType; + SpvOp_ op; + if (is_float(fContext, operandType)) { + op = SpvOpFMod; + } else if (is_signed(fContext, operandType)) { + op = SpvOpSMod; + } else if (is_unsigned(fContext, operandType)) { + op = SpvOpUMod; + } else { + SkASSERT(false); + return 0; + } + this->writeOpCode(op, 5, out); + this->writeWord(this->getType(operandType), out); + this->writeWord(result, out); + this->writeWord(args[0], out); + this->writeWord(args[1], out); + break; + } + case kDFdy_SpecialIntrinsic: { + SpvId fn = this->writeExpression(*c.fArguments[0], out); + this->writeOpCode(SpvOpDPdy, 4, out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + this->writeWord(fn, out); + if (fProgram.fSettings.fFlipY) { + // Flipping Y also negates the Y derivatives. + SpvId flipped = this->nextId(); + this->writeInstruction(SpvOpFNegate, this->getType(c.fType), flipped, result, out); + this->writePrecisionModifier(c.fType, flipped); + return flipped; + } + break; + } + case kClamp_SpecialIntrinsic: { + std::vector args = this->vectorize(c.fArguments, out); + SkASSERT(args.size() == 3); + this->writeGLSLExtendedInstruction(c.fType, result, GLSLstd450FClamp, GLSLstd450SClamp, + GLSLstd450UClamp, args, out); + break; + } + case kMax_SpecialIntrinsic: { + std::vector args = this->vectorize(c.fArguments, out); + SkASSERT(args.size() == 2); + this->writeGLSLExtendedInstruction(c.fType, result, GLSLstd450FMax, GLSLstd450SMax, + GLSLstd450UMax, args, out); + break; + } + case kMin_SpecialIntrinsic: { + std::vector args = this->vectorize(c.fArguments, out); + SkASSERT(args.size() == 2); + this->writeGLSLExtendedInstruction(c.fType, result, GLSLstd450FMin, GLSLstd450SMin, + GLSLstd450UMin, args, out); + break; + } + case kMix_SpecialIntrinsic: { + std::vector args = this->vectorize(c.fArguments, out); + SkASSERT(args.size() == 3); + this->writeGLSLExtendedInstruction(c.fType, result, GLSLstd450FMix, SpvOpUndef, + SpvOpUndef, args, out); + break; + } + case kSaturate_SpecialIntrinsic: { + SkASSERT(c.fArguments.size() == 1); + std::vector> finalArgs; + finalArgs.push_back(c.fArguments[0]->clone()); + finalArgs.emplace_back(new FloatLiteral(fContext, -1, 0)); + finalArgs.emplace_back(new FloatLiteral(fContext, -1, 1)); + std::vector spvArgs = this->vectorize(finalArgs, out); + this->writeGLSLExtendedInstruction(c.fType, result, GLSLstd450FClamp, GLSLstd450SClamp, + GLSLstd450UClamp, spvArgs, out); + break; + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeFunctionCall(const FunctionCall& c, OutputStream& out) { + const auto& entry = fFunctionMap.find(&c.fFunction); + if (entry == fFunctionMap.end()) { + return this->writeIntrinsicCall(c, out); + } + // stores (variable, type, lvalue) pairs to extract and save after the function call is complete + std::vector>> lvalues; + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + // id of temporary variable that we will use to hold this argument, or 0 if it is being + // passed directly + SpvId tmpVar; + // if we need a temporary var to store this argument, this is the value to store in the var + SpvId tmpValueId; + if (is_out(*c.fFunction.fParameters[i])) { + std::unique_ptr lv = this->getLValue(*c.fArguments[i], out); + SpvId ptr = lv->getPointer(); + if (ptr) { + arguments.push_back(ptr); + continue; + } else { + // lvalue cannot simply be read and written via a pointer (e.g. a swizzle). Need to + // copy it into a temp, call the function, read the value out of the temp, and then + // update the lvalue. + tmpValueId = lv->load(out); + tmpVar = this->nextId(); + lvalues.push_back(std::make_tuple(tmpVar, &c.fArguments[i]->fType, std::move(lv))); + } + } else { + // see getFunctionType for an explanation of why we're always using pointer parameters + tmpValueId = this->writeExpression(*c.fArguments[i], out); + tmpVar = this->nextId(); + } + this->writeInstruction(SpvOpVariable, + this->getPointerType(c.fArguments[i]->fType, + SpvStorageClassFunction), + tmpVar, + SpvStorageClassFunction, + fVariableBuffer); + this->writeInstruction(SpvOpStore, tmpVar, tmpValueId, out); + arguments.push_back(tmpVar); + } + SpvId result = this->nextId(); + this->writeOpCode(SpvOpFunctionCall, 4 + (int32_t) c.fArguments.size(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + this->writeWord(entry->second, out); + for (SpvId id : arguments) { + this->writeWord(id, out); + } + // now that the call is complete, we may need to update some lvalues with the new values of out + // arguments + for (const auto& tuple : lvalues) { + SpvId load = this->nextId(); + this->writeInstruction(SpvOpLoad, getType(*std::get<1>(tuple)), load, std::get<0>(tuple), + out); + this->writePrecisionModifier(*std::get<1>(tuple), load); + std::get<2>(tuple)->store(load, out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeConstantVector(const Constructor& c) { + SkASSERT(c.fType.kind() == Type::kVector_Kind && c.isConstant()); + SpvId result = this->nextId(); + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + arguments.push_back(this->writeExpression(*c.fArguments[i], fConstantBuffer)); + } + SpvId type = this->getType(c.fType); + if (c.fArguments.size() == 1) { + // with a single argument, a vector will have all of its entries equal to the argument + this->writeOpCode(SpvOpConstantComposite, 3 + c.fType.columns(), fConstantBuffer); + this->writeWord(type, fConstantBuffer); + this->writeWord(result, fConstantBuffer); + for (int i = 0; i < c.fType.columns(); i++) { + this->writeWord(arguments[0], fConstantBuffer); + } + } else { + this->writeOpCode(SpvOpConstantComposite, 3 + (int32_t) c.fArguments.size(), + fConstantBuffer); + this->writeWord(type, fConstantBuffer); + this->writeWord(result, fConstantBuffer); + for (SpvId id : arguments) { + this->writeWord(id, fConstantBuffer); + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeFloatConstructor(const Constructor& c, OutputStream& out) { + SkASSERT(c.fType.isFloat()); + SkASSERT(c.fArguments.size() == 1); + SkASSERT(c.fArguments[0]->fType.isNumber()); + SpvId result = this->nextId(); + SpvId parameter = this->writeExpression(*c.fArguments[0], out); + if (c.fArguments[0]->fType.isSigned()) { + this->writeInstruction(SpvOpConvertSToF, this->getType(c.fType), result, parameter, + out); + } else { + SkASSERT(c.fArguments[0]->fType.isUnsigned()); + this->writeInstruction(SpvOpConvertUToF, this->getType(c.fType), result, parameter, + out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeIntConstructor(const Constructor& c, OutputStream& out) { + SkASSERT(c.fType.isSigned()); + SkASSERT(c.fArguments.size() == 1); + SkASSERT(c.fArguments[0]->fType.isNumber()); + SpvId result = this->nextId(); + SpvId parameter = this->writeExpression(*c.fArguments[0], out); + if (c.fArguments[0]->fType.isFloat()) { + this->writeInstruction(SpvOpConvertFToS, this->getType(c.fType), result, parameter, + out); + } + else { + SkASSERT(c.fArguments[0]->fType.isUnsigned()); + this->writeInstruction(SpvOpBitcast, this->getType(c.fType), result, parameter, + out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeUIntConstructor(const Constructor& c, OutputStream& out) { + SkASSERT(c.fType.isUnsigned()); + SkASSERT(c.fArguments.size() == 1); + SkASSERT(c.fArguments[0]->fType.isNumber()); + SpvId result = this->nextId(); + SpvId parameter = this->writeExpression(*c.fArguments[0], out); + if (c.fArguments[0]->fType.isFloat()) { + this->writeInstruction(SpvOpConvertFToU, this->getType(c.fType), result, parameter, + out); + } else { + SkASSERT(c.fArguments[0]->fType.isSigned()); + this->writeInstruction(SpvOpBitcast, this->getType(c.fType), result, parameter, + out); + } + return result; +} + +void SPIRVCodeGenerator::writeUniformScaleMatrix(SpvId id, SpvId diagonal, const Type& type, + OutputStream& out) { + FloatLiteral zero(fContext, -1, 0); + SpvId zeroId = this->writeFloatLiteral(zero); + std::vector columnIds; + for (int column = 0; column < type.columns(); column++) { + this->writeOpCode(SpvOpCompositeConstruct, 3 + type.rows(), + out); + this->writeWord(this->getType(type.componentType().toCompound(fContext, type.rows(), 1)), + out); + SpvId columnId = this->nextId(); + this->writeWord(columnId, out); + columnIds.push_back(columnId); + for (int row = 0; row < type.columns(); row++) { + this->writeWord(row == column ? diagonal : zeroId, out); + } + this->writePrecisionModifier(type, columnId); + } + this->writeOpCode(SpvOpCompositeConstruct, 3 + type.columns(), + out); + this->writeWord(this->getType(type), out); + this->writeWord(id, out); + for (SpvId id : columnIds) { + this->writeWord(id, out); + } + this->writePrecisionModifier(type, id); +} + +void SPIRVCodeGenerator::writeMatrixCopy(SpvId id, SpvId src, const Type& srcType, + const Type& dstType, OutputStream& out) { + SkASSERT(srcType.kind() == Type::kMatrix_Kind); + SkASSERT(dstType.kind() == Type::kMatrix_Kind); + SkASSERT(srcType.componentType() == dstType.componentType()); + SpvId srcColumnType = this->getType(srcType.componentType().toCompound(fContext, + srcType.rows(), + 1)); + SpvId dstColumnType = this->getType(dstType.componentType().toCompound(fContext, + dstType.rows(), + 1)); + SpvId zeroId; + if (dstType.componentType() == *fContext.fFloat_Type) { + FloatLiteral zero(fContext, -1, 0.0); + zeroId = this->writeFloatLiteral(zero); + } else if (dstType.componentType() == *fContext.fInt_Type) { + IntLiteral zero(fContext, -1, 0); + zeroId = this->writeIntLiteral(zero); + } else { + ABORT("unsupported matrix component type"); + } + SpvId zeroColumn = 0; + SpvId columns[4]; + for (int i = 0; i < dstType.columns(); i++) { + if (i < srcType.columns()) { + // we're still inside the src matrix, copy the column + SpvId srcColumn = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, srcColumnType, srcColumn, src, i, out); + this->writePrecisionModifier(dstType, srcColumn); + SpvId dstColumn; + if (srcType.rows() == dstType.rows()) { + // columns are equal size, don't need to do anything + dstColumn = srcColumn; + } + else if (dstType.rows() > srcType.rows()) { + // dst column is bigger, need to zero-pad it + dstColumn = this->nextId(); + int delta = dstType.rows() - srcType.rows(); + this->writeOpCode(SpvOpCompositeConstruct, 4 + delta, out); + this->writeWord(dstColumnType, out); + this->writeWord(dstColumn, out); + this->writeWord(srcColumn, out); + for (int i = 0; i < delta; ++i) { + this->writeWord(zeroId, out); + } + this->writePrecisionModifier(dstType, dstColumn); + } + else { + // dst column is smaller, need to swizzle the src column + dstColumn = this->nextId(); + int count = dstType.rows(); + this->writeOpCode(SpvOpVectorShuffle, 5 + count, out); + this->writeWord(dstColumnType, out); + this->writeWord(dstColumn, out); + this->writeWord(srcColumn, out); + this->writeWord(srcColumn, out); + for (int i = 0; i < count; i++) { + this->writeWord(i, out); + } + this->writePrecisionModifier(dstType, dstColumn); + } + columns[i] = dstColumn; + } else { + // we're past the end of the src matrix, need a vector of zeroes + if (!zeroColumn) { + zeroColumn = this->nextId(); + this->writeOpCode(SpvOpCompositeConstruct, 3 + dstType.rows(), out); + this->writeWord(dstColumnType, out); + this->writeWord(zeroColumn, out); + for (int i = 0; i < dstType.rows(); ++i) { + this->writeWord(zeroId, out); + } + this->writePrecisionModifier(dstType, zeroColumn); + } + columns[i] = zeroColumn; + } + } + this->writeOpCode(SpvOpCompositeConstruct, 3 + dstType.columns(), out); + this->writeWord(this->getType(dstType), out); + this->writeWord(id, out); + for (int i = 0; i < dstType.columns(); i++) { + this->writeWord(columns[i], out); + } + this->writePrecisionModifier(dstType, id); +} + +void SPIRVCodeGenerator::addColumnEntry(SpvId columnType, Precision precision, + std::vector* currentColumn, + std::vector* columnIds, + int* currentCount, int rows, SpvId entry, + OutputStream& out) { + SkASSERT(*currentCount < rows); + ++(*currentCount); + currentColumn->push_back(entry); + if (*currentCount == rows) { + *currentCount = 0; + this->writeOpCode(SpvOpCompositeConstruct, 3 + currentColumn->size(), out); + this->writeWord(columnType, out); + SpvId columnId = this->nextId(); + this->writeWord(columnId, out); + columnIds->push_back(columnId); + for (SpvId id : *currentColumn) { + this->writeWord(id, out); + } + currentColumn->clear(); + this->writePrecisionModifier(precision, columnId); + } +} + +SpvId SPIRVCodeGenerator::writeMatrixConstructor(const Constructor& c, OutputStream& out) { + SkASSERT(c.fType.kind() == Type::kMatrix_Kind); + // go ahead and write the arguments so we don't try to write new instructions in the middle of + // an instruction + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + arguments.push_back(this->writeExpression(*c.fArguments[i], out)); + } + SpvId result = this->nextId(); + int rows = c.fType.rows(); + int columns = c.fType.columns(); + if (arguments.size() == 1 && c.fArguments[0]->fType.kind() == Type::kScalar_Kind) { + this->writeUniformScaleMatrix(result, arguments[0], c.fType, out); + } else if (arguments.size() == 1 && c.fArguments[0]->fType.kind() == Type::kMatrix_Kind) { + this->writeMatrixCopy(result, arguments[0], c.fArguments[0]->fType, c.fType, out); + } else if (arguments.size() == 1 && c.fArguments[0]->fType.kind() == Type::kVector_Kind) { + SkASSERT(c.fType.rows() == 2 && c.fType.columns() == 2); + SkASSERT(c.fArguments[0]->fType.columns() == 4); + SpvId componentType = this->getType(c.fType.componentType()); + SpvId v[4]; + for (int i = 0; i < 4; ++i) { + v[i] = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, componentType, v[i], arguments[0], i, out); + } + SpvId columnType = this->getType(c.fType.componentType().toCompound(fContext, 2, 1)); + SpvId column1 = this->nextId(); + this->writeInstruction(SpvOpCompositeConstruct, columnType, column1, v[0], v[1], out); + SpvId column2 = this->nextId(); + this->writeInstruction(SpvOpCompositeConstruct, columnType, column2, v[2], v[3], out); + this->writeInstruction(SpvOpCompositeConstruct, this->getType(c.fType), result, column1, + column2, out); + } else { + SpvId columnType = this->getType(c.fType.componentType().toCompound(fContext, rows, 1)); + std::vector columnIds; + // ids of vectors and scalars we have written to the current column so far + std::vector currentColumn; + // the total number of scalars represented by currentColumn's entries + int currentCount = 0; + Precision precision = c.fType.highPrecision() ? Precision::kHigh : Precision::kLow; + for (size_t i = 0; i < arguments.size(); i++) { + if (currentCount == 0 && c.fArguments[i]->fType.kind() == Type::kVector_Kind && + c.fArguments[i]->fType.columns() == c.fType.rows()) { + // this is a complete column by itself + columnIds.push_back(arguments[i]); + } else { + if (c.fArguments[i]->fType.columns() == 1) { + this->addColumnEntry(columnType, precision, ¤tColumn, &columnIds, + ¤tCount, rows, arguments[i], out); + } else { + SpvId componentType = this->getType(c.fArguments[i]->fType.componentType()); + for (int j = 0; j < c.fArguments[i]->fType.columns(); ++j) { + SpvId swizzle = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, componentType, swizzle, + arguments[i], j, out); + this->addColumnEntry(columnType, precision, ¤tColumn, &columnIds, + ¤tCount, rows, swizzle, out); + } + } + } + } + SkASSERT(columnIds.size() == (size_t) columns); + this->writeOpCode(SpvOpCompositeConstruct, 3 + columns, out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + for (SpvId id : columnIds) { + this->writeWord(id, out); + } + } + this->writePrecisionModifier(c.fType, result); + return result; +} + +SpvId SPIRVCodeGenerator::writeVectorConstructor(const Constructor& c, OutputStream& out) { + SkASSERT(c.fType.kind() == Type::kVector_Kind); + if (c.isConstant()) { + return this->writeConstantVector(c); + } + // go ahead and write the arguments so we don't try to write new instructions in the middle of + // an instruction + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + if (c.fArguments[i]->fType.kind() == Type::kVector_Kind) { + // SPIR-V doesn't support vector(vector-of-different-type) directly, so we need to + // extract the components and convert them in that case manually. On top of that, + // as of this writing there's a bug in the Intel Vulkan driver where OpCreateComposite + // doesn't handle vector arguments at all, so we always extract vector components and + // pass them into OpCreateComposite individually. + SpvId vec = this->writeExpression(*c.fArguments[i], out); + SpvOp_ op = SpvOpUndef; + const Type& src = c.fArguments[i]->fType.componentType(); + const Type& dst = c.fType.componentType(); + if (dst == *fContext.fFloat_Type || dst == *fContext.fHalf_Type) { + if (src == *fContext.fFloat_Type || src == *fContext.fHalf_Type) { + if (c.fArguments.size() == 1) { + return vec; + } + } else if (src == *fContext.fInt_Type || + src == *fContext.fShort_Type || + src == *fContext.fByte_Type) { + op = SpvOpConvertSToF; + } else if (src == *fContext.fUInt_Type || + src == *fContext.fUShort_Type || + src == *fContext.fUByte_Type) { + op = SpvOpConvertUToF; + } else { + SkASSERT(false); + } + } else if (dst == *fContext.fInt_Type || + dst == *fContext.fShort_Type || + dst == *fContext.fByte_Type) { + if (src == *fContext.fFloat_Type || src == *fContext.fHalf_Type) { + op = SpvOpConvertFToS; + } else if (src == *fContext.fInt_Type || + src == *fContext.fShort_Type || + src == *fContext.fByte_Type) { + if (c.fArguments.size() == 1) { + return vec; + } + } else if (src == *fContext.fUInt_Type || + src == *fContext.fUShort_Type || + src == *fContext.fUByte_Type) { + op = SpvOpBitcast; + } else { + SkASSERT(false); + } + } else if (dst == *fContext.fUInt_Type || + dst == *fContext.fUShort_Type || + dst == *fContext.fUByte_Type) { + if (src == *fContext.fFloat_Type || src == *fContext.fHalf_Type) { + op = SpvOpConvertFToS; + } else if (src == *fContext.fInt_Type || + src == *fContext.fShort_Type || + src == *fContext.fByte_Type) { + op = SpvOpBitcast; + } else if (src == *fContext.fUInt_Type || + src == *fContext.fUShort_Type || + src == *fContext.fUByte_Type) { + if (c.fArguments.size() == 1) { + return vec; + } + } else { + SkASSERT(false); + } + } + for (int j = 0; j < c.fArguments[i]->fType.columns(); j++) { + SpvId swizzle = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, this->getType(src), swizzle, vec, j, + out); + if (op != SpvOpUndef) { + SpvId cast = this->nextId(); + this->writeInstruction(op, this->getType(dst), cast, swizzle, out); + arguments.push_back(cast); + } else { + arguments.push_back(swizzle); + } + } + } else { + arguments.push_back(this->writeExpression(*c.fArguments[i], out)); + } + } + SpvId result = this->nextId(); + if (arguments.size() == 1 && c.fArguments[0]->fType.kind() == Type::kScalar_Kind) { + this->writeOpCode(SpvOpCompositeConstruct, 3 + c.fType.columns(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + for (int i = 0; i < c.fType.columns(); i++) { + this->writeWord(arguments[0], out); + } + } else { + SkASSERT(arguments.size() > 1); + this->writeOpCode(SpvOpCompositeConstruct, 3 + (int32_t) arguments.size(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + for (SpvId id : arguments) { + this->writeWord(id, out); + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeArrayConstructor(const Constructor& c, OutputStream& out) { + SkASSERT(c.fType.kind() == Type::kArray_Kind); + // go ahead and write the arguments so we don't try to write new instructions in the middle of + // an instruction + std::vector arguments; + for (size_t i = 0; i < c.fArguments.size(); i++) { + arguments.push_back(this->writeExpression(*c.fArguments[i], out)); + } + SpvId result = this->nextId(); + this->writeOpCode(SpvOpCompositeConstruct, 3 + (int32_t) c.fArguments.size(), out); + this->writeWord(this->getType(c.fType), out); + this->writeWord(result, out); + for (SpvId id : arguments) { + this->writeWord(id, out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeConstructor(const Constructor& c, OutputStream& out) { + if (c.fArguments.size() == 1 && + this->getActualType(c.fType) == this->getActualType(c.fArguments[0]->fType)) { + return this->writeExpression(*c.fArguments[0], out); + } + if (c.fType == *fContext.fFloat_Type || c.fType == *fContext.fHalf_Type) { + return this->writeFloatConstructor(c, out); + } else if (c.fType == *fContext.fInt_Type || + c.fType == *fContext.fShort_Type || + c.fType == *fContext.fByte_Type) { + return this->writeIntConstructor(c, out); + } else if (c.fType == *fContext.fUInt_Type || + c.fType == *fContext.fUShort_Type || + c.fType == *fContext.fUByte_Type) { + return this->writeUIntConstructor(c, out); + } + switch (c.fType.kind()) { + case Type::kVector_Kind: + return this->writeVectorConstructor(c, out); + case Type::kMatrix_Kind: + return this->writeMatrixConstructor(c, out); + case Type::kArray_Kind: + return this->writeArrayConstructor(c, out); + default: + ABORT("unsupported constructor: %s", c.description().c_str()); + } +} + +SpvStorageClass_ get_storage_class(const Modifiers& modifiers) { + if (modifiers.fFlags & Modifiers::kIn_Flag) { + SkASSERT(!(modifiers.fLayout.fFlags & Layout::kPushConstant_Flag)); + return SpvStorageClassInput; + } else if (modifiers.fFlags & Modifiers::kOut_Flag) { + SkASSERT(!(modifiers.fLayout.fFlags & Layout::kPushConstant_Flag)); + return SpvStorageClassOutput; + } else if (modifiers.fFlags & Modifiers::kUniform_Flag) { + if (modifiers.fLayout.fFlags & Layout::kPushConstant_Flag) { + return SpvStorageClassPushConstant; + } + return SpvStorageClassUniform; + } else { + return SpvStorageClassFunction; + } +} + +SpvStorageClass_ get_storage_class(const Expression& expr) { + switch (expr.fKind) { + case Expression::kVariableReference_Kind: { + const Variable& var = ((VariableReference&) expr).fVariable; + if (var.fStorage != Variable::kGlobal_Storage) { + return SpvStorageClassFunction; + } + SpvStorageClass_ result = get_storage_class(var.fModifiers); + if (result == SpvStorageClassFunction) { + result = SpvStorageClassPrivate; + } + return result; + } + case Expression::kFieldAccess_Kind: + return get_storage_class(*((FieldAccess&) expr).fBase); + case Expression::kIndex_Kind: + return get_storage_class(*((IndexExpression&) expr).fBase); + default: + return SpvStorageClassFunction; + } +} + +std::vector SPIRVCodeGenerator::getAccessChain(const Expression& expr, OutputStream& out) { + std::vector chain; + switch (expr.fKind) { + case Expression::kIndex_Kind: { + IndexExpression& indexExpr = (IndexExpression&) expr; + chain = this->getAccessChain(*indexExpr.fBase, out); + chain.push_back(this->writeExpression(*indexExpr.fIndex, out)); + break; + } + case Expression::kFieldAccess_Kind: { + FieldAccess& fieldExpr = (FieldAccess&) expr; + chain = this->getAccessChain(*fieldExpr.fBase, out); + IntLiteral index(fContext, -1, fieldExpr.fFieldIndex); + chain.push_back(this->writeIntLiteral(index)); + break; + } + default: { + SpvId id = this->getLValue(expr, out)->getPointer(); + SkASSERT(id != 0); + chain.push_back(id); + } + } + return chain; +} + +class PointerLValue : public SPIRVCodeGenerator::LValue { +public: + PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, SpvId type, + SPIRVCodeGenerator::Precision precision) + : fGen(gen) + , fPointer(pointer) + , fType(type) + , fPrecision(precision) {} + + virtual SpvId getPointer() override { + return fPointer; + } + + virtual SpvId load(OutputStream& out) override { + SpvId result = fGen.nextId(); + fGen.writeInstruction(SpvOpLoad, fType, result, fPointer, out); + fGen.writePrecisionModifier(fPrecision, result); + return result; + } + + virtual void store(SpvId value, OutputStream& out) override { + fGen.writeInstruction(SpvOpStore, fPointer, value, out); + } + +private: + SPIRVCodeGenerator& fGen; + const SpvId fPointer; + const SpvId fType; + const SPIRVCodeGenerator::Precision fPrecision; +}; + +class SwizzleLValue : public SPIRVCodeGenerator::LValue { +public: + SwizzleLValue(SPIRVCodeGenerator& gen, SpvId vecPointer, const std::vector& components, + const Type& baseType, const Type& swizzleType, + SPIRVCodeGenerator::Precision precision) + : fGen(gen) + , fVecPointer(vecPointer) + , fComponents(components) + , fBaseType(baseType) + , fSwizzleType(swizzleType) + , fPrecision(precision) {} + + virtual SpvId getPointer() override { + return 0; + } + + virtual SpvId load(OutputStream& out) override { + SpvId base = fGen.nextId(); + fGen.writeInstruction(SpvOpLoad, fGen.getType(fBaseType), base, fVecPointer, out); + fGen.writePrecisionModifier(fPrecision, base); + SpvId result = fGen.nextId(); + fGen.writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) fComponents.size(), out); + fGen.writeWord(fGen.getType(fSwizzleType), out); + fGen.writeWord(result, out); + fGen.writeWord(base, out); + fGen.writeWord(base, out); + for (int component : fComponents) { + fGen.writeWord(component, out); + } + fGen.writePrecisionModifier(fPrecision, result); + return result; + } + + virtual void store(SpvId value, OutputStream& out) override { + // use OpVectorShuffle to mix and match the vector components. We effectively create + // a virtual vector out of the concatenation of the left and right vectors, and then + // select components from this virtual vector to make the result vector. For + // instance, given: + // float3L = ...; + // float3R = ...; + // L.xz = R.xy; + // we end up with the virtual vector (L.x, L.y, L.z, R.x, R.y, R.z). Then we want + // our result vector to look like (R.x, L.y, R.y), so we need to select indices + // (3, 1, 4). + SpvId base = fGen.nextId(); + fGen.writeInstruction(SpvOpLoad, fGen.getType(fBaseType), base, fVecPointer, out); + SpvId shuffle = fGen.nextId(); + fGen.writeOpCode(SpvOpVectorShuffle, 5 + fBaseType.columns(), out); + fGen.writeWord(fGen.getType(fBaseType), out); + fGen.writeWord(shuffle, out); + fGen.writeWord(base, out); + fGen.writeWord(value, out); + for (int i = 0; i < fBaseType.columns(); i++) { + // current offset into the virtual vector, defaults to pulling the unmodified + // value from the left side + int offset = i; + // check to see if we are writing this component + for (size_t j = 0; j < fComponents.size(); j++) { + if (fComponents[j] == i) { + // we're writing to this component, so adjust the offset to pull from + // the correct component of the right side instead of preserving the + // value from the left + offset = (int) (j + fBaseType.columns()); + break; + } + } + fGen.writeWord(offset, out); + } + fGen.writePrecisionModifier(fPrecision, shuffle); + fGen.writeInstruction(SpvOpStore, fVecPointer, shuffle, out); + } + +private: + SPIRVCodeGenerator& fGen; + const SpvId fVecPointer; + const std::vector& fComponents; + const Type& fBaseType; + const Type& fSwizzleType; + const SPIRVCodeGenerator::Precision fPrecision; +}; + +std::unique_ptr SPIRVCodeGenerator::getLValue(const Expression& expr, + OutputStream& out) { + Precision precision = expr.fType.highPrecision() ? Precision::kHigh : Precision::kLow; + switch (expr.fKind) { + case Expression::kVariableReference_Kind: { + SpvId type; + const Variable& var = ((VariableReference&) expr).fVariable; + if (var.fModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) { + type = this->getType(Type("sk_in", Type::kArray_Kind, var.fType.componentType(), + fSkInCount)); + } else { + type = this->getType(expr.fType); + } + auto entry = fVariableMap.find(&var); + SkASSERT(entry != fVariableMap.end()); + return std::unique_ptr(new PointerLValue(*this, + entry->second, + type, + precision)); + } + case Expression::kIndex_Kind: // fall through + case Expression::kFieldAccess_Kind: { + std::vector chain = this->getAccessChain(expr, out); + SpvId member = this->nextId(); + this->writeOpCode(SpvOpAccessChain, (SpvId) (3 + chain.size()), out); + this->writeWord(this->getPointerType(expr.fType, get_storage_class(expr)), out); + this->writeWord(member, out); + for (SpvId idx : chain) { + this->writeWord(idx, out); + } + return std::unique_ptr(new PointerLValue( + *this, + member, + this->getType(expr.fType), + precision)); + } + case Expression::kSwizzle_Kind: { + Swizzle& swizzle = (Swizzle&) expr; + size_t count = swizzle.fComponents.size(); + SpvId base = this->getLValue(*swizzle.fBase, out)->getPointer(); + SkASSERT(base); + if (count == 1) { + IntLiteral index(fContext, -1, swizzle.fComponents[0]); + SpvId member = this->nextId(); + this->writeInstruction(SpvOpAccessChain, + this->getPointerType(swizzle.fType, + get_storage_class(*swizzle.fBase)), + member, + base, + this->writeIntLiteral(index), + out); + return std::unique_ptr(new PointerLValue( + *this, + member, + this->getType(expr.fType), + precision)); + } else { + return std::unique_ptr(new SwizzleLValue( + *this, + base, + swizzle.fComponents, + swizzle.fBase->fType, + expr.fType, + precision)); + } + } + case Expression::kTernary_Kind: { + TernaryExpression& t = (TernaryExpression&) expr; + SpvId test = this->writeExpression(*t.fTest, out); + SpvId end = this->nextId(); + SpvId ifTrueLabel = this->nextId(); + SpvId ifFalseLabel = this->nextId(); + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, ifTrueLabel, ifFalseLabel, out); + this->writeLabel(ifTrueLabel, out); + SpvId ifTrue = this->getLValue(*t.fIfTrue, out)->getPointer(); + SkASSERT(ifTrue); + this->writeInstruction(SpvOpBranch, end, out); + ifTrueLabel = fCurrentBlock; + SpvId ifFalse = this->getLValue(*t.fIfFalse, out)->getPointer(); + SkASSERT(ifFalse); + ifFalseLabel = fCurrentBlock; + this->writeInstruction(SpvOpBranch, end, out); + SpvId result = this->nextId(); + this->writeInstruction(SpvOpPhi, this->getType(*fContext.fBool_Type), result, ifTrue, + ifTrueLabel, ifFalse, ifFalseLabel, out); + return std::unique_ptr(new PointerLValue( + *this, + result, + this->getType(expr.fType), + precision)); + } + default: + // expr isn't actually an lvalue, create a dummy variable for it. This case happens due + // to the need to store values in temporary variables during function calls (see + // comments in getFunctionType); erroneous uses of rvalues as lvalues should have been + // caught by IRGenerator + SpvId result = this->nextId(); + SpvId type = this->getPointerType(expr.fType, SpvStorageClassFunction); + this->writeInstruction(SpvOpVariable, type, result, SpvStorageClassFunction, + fVariableBuffer); + this->writeInstruction(SpvOpStore, result, this->writeExpression(expr, out), out); + return std::unique_ptr(new PointerLValue( + *this, + result, + this->getType(expr.fType), + precision)); + } +} + +SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, OutputStream& out) { + SpvId result = this->nextId(); + auto entry = fVariableMap.find(&ref.fVariable); + SkASSERT(entry != fVariableMap.end()); + SpvId var = entry->second; + this->writeInstruction(SpvOpLoad, this->getType(ref.fVariable.fType), result, var, out); + this->writePrecisionModifier(ref.fVariable.fType, result); + if (ref.fVariable.fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN && + fProgram.fSettings.fFlipY) { + // need to remap to a top-left coordinate system + if (fRTHeightStructId == (SpvId) -1) { + // height variable hasn't been written yet + std::shared_ptr st(new SymbolTable(&fErrors)); + SkASSERT(fRTHeightFieldIndex == (SpvId) -1); + std::vector fields; + SkASSERT(fProgram.fSettings.fRTHeightOffset >= 0); + fields.emplace_back(Modifiers(Layout(0, -1, fProgram.fSettings.fRTHeightOffset, -1, + -1, -1, -1, -1, Layout::Format::kUnspecified, + Layout::kUnspecified_Primitive, -1, -1, "", + Layout::kNo_Key, Layout::CType::kDefault), 0), + SKSL_RTHEIGHT_NAME, fContext.fFloat_Type.get()); + StringFragment name("sksl_synthetic_uniforms"); + Type intfStruct(-1, name, fields); + int binding; + int set; +#ifdef SK_VULKAN + const GrVkCaps* vkCaps = fProgram.fSettings.fVkCaps; + SkASSERT(vkCaps); + binding = vkCaps->getFragmentUniformBinding(); + set = vkCaps->getFragmentUniformSet(); +#else + binding = 0; + set = 0; +#endif + Layout layout(0, -1, -1, binding, -1, set, -1, -1, Layout::Format::kUnspecified, + Layout::kUnspecified_Primitive, -1, -1, "", Layout::kNo_Key, + Layout::CType::kDefault); + Variable* intfVar = (Variable*) fSynthetics.takeOwnership(std::unique_ptr( + new Variable(-1, + Modifiers(layout, Modifiers::kUniform_Flag), + name, + intfStruct, + Variable::kGlobal_Storage))); + InterfaceBlock intf(-1, intfVar, name, String(""), + std::vector>(), st); + fRTHeightStructId = this->writeInterfaceBlock(intf); + fRTHeightFieldIndex = 0; + } + SkASSERT(fRTHeightFieldIndex != (SpvId) -1); + // write float4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, 0.0, gl_FragCoord.w) + SpvId xId = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fFloat_Type), xId, + result, 0, out); + IntLiteral fieldIndex(fContext, -1, fRTHeightFieldIndex); + SpvId fieldIndexId = this->writeIntLiteral(fieldIndex); + SpvId heightPtr = this->nextId(); + this->writeOpCode(SpvOpAccessChain, 5, out); + this->writeWord(this->getPointerType(*fContext.fFloat_Type, SpvStorageClassUniform), out); + this->writeWord(heightPtr, out); + this->writeWord(fRTHeightStructId, out); + this->writeWord(fieldIndexId, out); + SpvId heightRead = this->nextId(); + this->writeInstruction(SpvOpLoad, this->getType(*fContext.fFloat_Type), heightRead, + heightPtr, out); + SpvId rawYId = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fFloat_Type), rawYId, + result, 1, out); + SpvId flippedYId = this->nextId(); + this->writeInstruction(SpvOpFSub, this->getType(*fContext.fFloat_Type), flippedYId, + heightRead, rawYId, out); + FloatLiteral zero(fContext, -1, 0.0); + SpvId zeroId = writeFloatLiteral(zero); + FloatLiteral one(fContext, -1, 1.0); + SpvId wId = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fFloat_Type), wId, + result, 3, out); + SpvId flipped = this->nextId(); + this->writeOpCode(SpvOpCompositeConstruct, 7, out); + this->writeWord(this->getType(*fContext.fFloat4_Type), out); + this->writeWord(flipped, out); + this->writeWord(xId, out); + this->writeWord(flippedYId, out); + this->writeWord(zeroId, out); + this->writeWord(wId, out); + return flipped; + } + if (ref.fVariable.fModifiers.fLayout.fBuiltin == SK_CLOCKWISE_BUILTIN && + !fProgram.fSettings.fFlipY) { + // FrontFacing in Vulkan is defined in terms of a top-down render target. In skia, we use + // the default convention of "counter-clockwise face is front". + SpvId inverse = this->nextId(); + this->writeInstruction(SpvOpLogicalNot, this->getType(*fContext.fBool_Type), inverse, + result, out); + return inverse; + } + return result; +} + +SpvId SPIRVCodeGenerator::writeIndexExpression(const IndexExpression& expr, OutputStream& out) { + if (expr.fBase->fType.kind() == Type::Kind::kVector_Kind) { + SpvId base = this->writeExpression(*expr.fBase, out); + SpvId index = this->writeExpression(*expr.fIndex, out); + SpvId result = this->nextId(); + this->writeInstruction(SpvOpVectorExtractDynamic, this->getType(expr.fType), result, base, + index, out); + return result; + } + return getLValue(expr, out)->load(out); +} + +SpvId SPIRVCodeGenerator::writeFieldAccess(const FieldAccess& f, OutputStream& out) { + return getLValue(f, out)->load(out); +} + +SpvId SPIRVCodeGenerator::writeSwizzle(const Swizzle& swizzle, OutputStream& out) { + SpvId base = this->writeExpression(*swizzle.fBase, out); + SpvId result = this->nextId(); + size_t count = swizzle.fComponents.size(); + if (count == 1) { + this->writeInstruction(SpvOpCompositeExtract, this->getType(swizzle.fType), result, base, + swizzle.fComponents[0], out); + } else { + this->writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) count, out); + this->writeWord(this->getType(swizzle.fType), out); + this->writeWord(result, out); + this->writeWord(base, out); + SpvId other = base; + for (int c : swizzle.fComponents) { + if (c < 0) { + if (!fConstantZeroOneVector) { + FloatLiteral zero(fContext, -1, 0); + SpvId zeroId = this->writeFloatLiteral(zero); + FloatLiteral one(fContext, -1, 1); + SpvId oneId = this->writeFloatLiteral(one); + SpvId type = this->getType(*fContext.fFloat2_Type); + fConstantZeroOneVector = this->nextId(); + this->writeOpCode(SpvOpConstantComposite, 5, fConstantBuffer); + this->writeWord(type, fConstantBuffer); + this->writeWord(fConstantZeroOneVector, fConstantBuffer); + this->writeWord(zeroId, fConstantBuffer); + this->writeWord(oneId, fConstantBuffer); + } + other = fConstantZeroOneVector; + break; + } + } + this->writeWord(other, out); + for (int component : swizzle.fComponents) { + if (component == SKSL_SWIZZLE_0) { + this->writeWord(swizzle.fBase->fType.columns(), out); + } else if (component == SKSL_SWIZZLE_1) { + this->writeWord(swizzle.fBase->fType.columns() + 1, out); + } else { + this->writeWord(component, out); + } + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeBinaryOperation(const Type& resultType, + const Type& operandType, SpvId lhs, + SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, + SpvOp_ ifUInt, SpvOp_ ifBool, OutputStream& out) { + SpvId result = this->nextId(); + if (is_float(fContext, operandType)) { + this->writeInstruction(ifFloat, this->getType(resultType), result, lhs, rhs, out); + } else if (is_signed(fContext, operandType)) { + this->writeInstruction(ifInt, this->getType(resultType), result, lhs, rhs, out); + } else if (is_unsigned(fContext, operandType)) { + this->writeInstruction(ifUInt, this->getType(resultType), result, lhs, rhs, out); + } else if (operandType == *fContext.fBool_Type) { + this->writeInstruction(ifBool, this->getType(resultType), result, lhs, rhs, out); + return result; // skip RelaxedPrecision check + } else { + ABORT("invalid operandType: %s", operandType.description().c_str()); + } + if (getActualType(resultType) == operandType && !resultType.highPrecision()) { + this->writeInstruction(SpvOpDecorate, result, SpvDecorationRelaxedPrecision, + fDecorationBuffer); + } + return result; +} + +SpvId SPIRVCodeGenerator::foldToBool(SpvId id, const Type& operandType, SpvOp op, + OutputStream& out) { + if (operandType.kind() == Type::kVector_Kind) { + SpvId result = this->nextId(); + this->writeInstruction(op, this->getType(*fContext.fBool_Type), result, id, out); + return result; + } + return id; +} + +SpvId SPIRVCodeGenerator::writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, + SpvOp_ floatOperator, SpvOp_ intOperator, + SpvOp_ vectorMergeOperator, SpvOp_ mergeOperator, + OutputStream& out) { + SpvOp_ compareOp = is_float(fContext, operandType) ? floatOperator : intOperator; + SkASSERT(operandType.kind() == Type::kMatrix_Kind); + SpvId columnType = this->getType(operandType.componentType().toCompound(fContext, + operandType.rows(), + 1)); + SpvId bvecType = this->getType(fContext.fBool_Type->toCompound(fContext, + operandType.rows(), + 1)); + SpvId boolType = this->getType(*fContext.fBool_Type); + SpvId result = 0; + for (int i = 0; i < operandType.columns(); i++) { + SpvId columnL = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, columnType, columnL, lhs, i, out); + SpvId columnR = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, columnType, columnR, rhs, i, out); + SpvId compare = this->nextId(); + this->writeInstruction(compareOp, bvecType, compare, columnL, columnR, out); + SpvId merge = this->nextId(); + this->writeInstruction(vectorMergeOperator, boolType, merge, compare, out); + if (result != 0) { + SpvId next = this->nextId(); + this->writeInstruction(mergeOperator, boolType, next, result, merge, out); + result = next; + } + else { + result = merge; + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, + SpvId rhs, SpvOp_ floatOperator, + SpvOp_ intOperator, + OutputStream& out) { + SpvOp_ op = is_float(fContext, operandType) ? floatOperator : intOperator; + SkASSERT(operandType.kind() == Type::kMatrix_Kind); + SpvId columnType = this->getType(operandType.componentType().toCompound(fContext, + operandType.rows(), + 1)); + SpvId columns[4]; + for (int i = 0; i < operandType.columns(); i++) { + SpvId columnL = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, columnType, columnL, lhs, i, out); + SpvId columnR = this->nextId(); + this->writeInstruction(SpvOpCompositeExtract, columnType, columnR, rhs, i, out); + columns[i] = this->nextId(); + this->writeInstruction(op, columnType, columns[i], columnL, columnR, out); + } + SpvId result = this->nextId(); + this->writeOpCode(SpvOpCompositeConstruct, 3 + operandType.columns(), out); + this->writeWord(this->getType(operandType), out); + this->writeWord(result, out); + for (int i = 0; i < operandType.columns(); i++) { + this->writeWord(columns[i], out); + } + return result; +} + +std::unique_ptr create_literal_1(const Context& context, const Type& type) { + if (type.isInteger()) { + return std::unique_ptr(new IntLiteral(-1, 1, &type)); + } + else if (type.isFloat()) { + return std::unique_ptr(new FloatLiteral(-1, 1.0, &type)); + } else { + ABORT("math is unsupported on type '%s'", type.name().c_str()); + } +} + +SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs, Token::Kind op, + const Type& rightType, SpvId rhs, + const Type& resultType, OutputStream& out) { + Type tmp(""); + // overall type we are operating on: float2, int, uint4... + const Type* operandType; + // IR allows mismatched types in expressions (e.g. float2 * float), but they need special + // handling in SPIR-V + if (this->getActualType(leftType) != this->getActualType(rightType)) { + if (leftType.kind() == Type::kVector_Kind && rightType.isNumber()) { + if (op == Token::SLASH) { + SpvId one = this->writeExpression(*create_literal_1(fContext, rightType), out); + SpvId inverse = this->nextId(); + this->writeInstruction(SpvOpFDiv, this->getType(rightType), inverse, one, rhs, out); + rhs = inverse; + op = Token::STAR; + } + if (op == Token::STAR) { + SpvId result = this->nextId(); + this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType), + result, lhs, rhs, out); + return result; + } + // promote number to vector + SpvId vec = this->nextId(); + const Type& vecType = leftType; + this->writeOpCode(SpvOpCompositeConstruct, 3 + vecType.columns(), out); + this->writeWord(this->getType(vecType), out); + this->writeWord(vec, out); + for (int i = 0; i < vecType.columns(); i++) { + this->writeWord(rhs, out); + } + rhs = vec; + operandType = &leftType; + } else if (rightType.kind() == Type::kVector_Kind && leftType.isNumber()) { + if (op == Token::STAR) { + SpvId result = this->nextId(); + this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType), + result, rhs, lhs, out); + return result; + } + // promote number to vector + SpvId vec = this->nextId(); + const Type& vecType = rightType; + this->writeOpCode(SpvOpCompositeConstruct, 3 + vecType.columns(), out); + this->writeWord(this->getType(vecType), out); + this->writeWord(vec, out); + for (int i = 0; i < vecType.columns(); i++) { + this->writeWord(lhs, out); + } + lhs = vec; + operandType = &rightType; + } else if (leftType.kind() == Type::kMatrix_Kind) { + SpvOp_ spvop; + if (rightType.kind() == Type::kMatrix_Kind) { + spvop = SpvOpMatrixTimesMatrix; + } else if (rightType.kind() == Type::kVector_Kind) { + spvop = SpvOpMatrixTimesVector; + } else { + SkASSERT(rightType.kind() == Type::kScalar_Kind); + spvop = SpvOpMatrixTimesScalar; + } + SpvId result = this->nextId(); + this->writeInstruction(spvop, this->getType(resultType), result, lhs, rhs, out); + return result; + } else if (rightType.kind() == Type::kMatrix_Kind) { + SpvId result = this->nextId(); + if (leftType.kind() == Type::kVector_Kind) { + this->writeInstruction(SpvOpVectorTimesMatrix, this->getType(resultType), result, + lhs, rhs, out); + } else { + SkASSERT(leftType.kind() == Type::kScalar_Kind); + this->writeInstruction(SpvOpMatrixTimesScalar, this->getType(resultType), result, + rhs, lhs, out); + } + return result; + } else { + SkASSERT(false); + return -1; + } + } else { + tmp = this->getActualType(leftType); + operandType = &tmp; + SkASSERT(*operandType == this->getActualType(rightType)); + } + switch (op) { + case Token::EQEQ: { + if (operandType->kind() == Type::kMatrix_Kind) { + return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdEqual, + SpvOpIEqual, SpvOpAll, SpvOpLogicalAnd, out); + } + SkASSERT(resultType == *fContext.fBool_Type); + const Type* tmpType; + if (operandType->kind() == Type::kVector_Kind) { + tmpType = &fContext.fBool_Type->toCompound(fContext, + operandType->columns(), + operandType->rows()); + } else { + tmpType = &resultType; + } + return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs, + SpvOpFOrdEqual, SpvOpIEqual, + SpvOpIEqual, SpvOpLogicalEqual, out), + *operandType, SpvOpAll, out); + } + case Token::NEQ: + if (operandType->kind() == Type::kMatrix_Kind) { + return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdNotEqual, + SpvOpINotEqual, SpvOpAny, SpvOpLogicalOr, out); + } + SkASSERT(resultType == *fContext.fBool_Type); + const Type* tmpType; + if (operandType->kind() == Type::kVector_Kind) { + tmpType = &fContext.fBool_Type->toCompound(fContext, + operandType->columns(), + operandType->rows()); + } else { + tmpType = &resultType; + } + return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs, + SpvOpFOrdNotEqual, SpvOpINotEqual, + SpvOpINotEqual, SpvOpLogicalNotEqual, + out), + *operandType, SpvOpAny, out); + case Token::GT: + SkASSERT(resultType == *fContext.fBool_Type); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, + SpvOpFOrdGreaterThan, SpvOpSGreaterThan, + SpvOpUGreaterThan, SpvOpUndef, out); + case Token::LT: + SkASSERT(resultType == *fContext.fBool_Type); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdLessThan, + SpvOpSLessThan, SpvOpULessThan, SpvOpUndef, out); + case Token::GTEQ: + SkASSERT(resultType == *fContext.fBool_Type); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, + SpvOpFOrdGreaterThanEqual, SpvOpSGreaterThanEqual, + SpvOpUGreaterThanEqual, SpvOpUndef, out); + case Token::LTEQ: + SkASSERT(resultType == *fContext.fBool_Type); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, + SpvOpFOrdLessThanEqual, SpvOpSLessThanEqual, + SpvOpULessThanEqual, SpvOpUndef, out); + case Token::PLUS: + if (leftType.kind() == Type::kMatrix_Kind && + rightType.kind() == Type::kMatrix_Kind) { + SkASSERT(leftType == rightType); + return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, + SpvOpFAdd, SpvOpIAdd, out); + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFAdd, + SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out); + case Token::MINUS: + if (leftType.kind() == Type::kMatrix_Kind && + rightType.kind() == Type::kMatrix_Kind) { + SkASSERT(leftType == rightType); + return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, + SpvOpFSub, SpvOpISub, out); + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFSub, + SpvOpISub, SpvOpISub, SpvOpUndef, out); + case Token::STAR: + if (leftType.kind() == Type::kMatrix_Kind && + rightType.kind() == Type::kMatrix_Kind) { + // matrix multiply + SpvId result = this->nextId(); + this->writeInstruction(SpvOpMatrixTimesMatrix, this->getType(resultType), result, + lhs, rhs, out); + return result; + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMul, + SpvOpIMul, SpvOpIMul, SpvOpUndef, out); + case Token::SLASH: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFDiv, + SpvOpSDiv, SpvOpUDiv, SpvOpUndef, out); + case Token::PERCENT: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMod, + SpvOpSMod, SpvOpUMod, SpvOpUndef, out); + case Token::SHL: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpShiftLeftLogical, SpvOpShiftLeftLogical, + SpvOpUndef, out); + case Token::SHR: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpShiftRightArithmetic, SpvOpShiftRightLogical, + SpvOpUndef, out); + case Token::BITWISEAND: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpBitwiseAnd, SpvOpBitwiseAnd, SpvOpUndef, out); + case Token::BITWISEOR: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpBitwiseOr, SpvOpBitwiseOr, SpvOpUndef, out); + case Token::BITWISEXOR: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpBitwiseXor, SpvOpBitwiseXor, SpvOpUndef, out); + case Token::COMMA: + return rhs; + default: + SkASSERT(false); + return -1; + } +} + +SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, OutputStream& out) { + // handle cases where we don't necessarily evaluate both LHS and RHS + switch (b.fOperator) { + case Token::EQ: { + SpvId rhs = this->writeExpression(*b.fRight, out); + this->getLValue(*b.fLeft, out)->store(rhs, out); + return rhs; + } + case Token::LOGICALAND: + return this->writeLogicalAnd(b, out); + case Token::LOGICALOR: + return this->writeLogicalOr(b, out); + default: + break; + } + + std::unique_ptr lvalue; + SpvId lhs; + if (is_assignment(b.fOperator)) { + lvalue = this->getLValue(*b.fLeft, out); + lhs = lvalue->load(out); + } else { + lvalue = nullptr; + lhs = this->writeExpression(*b.fLeft, out); + } + SpvId rhs = this->writeExpression(*b.fRight, out); + SpvId result = this->writeBinaryExpression(b.fLeft->fType, lhs, remove_assignment(b.fOperator), + b.fRight->fType, rhs, b.fType, out); + if (lvalue) { + lvalue->store(result, out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeLogicalAnd(const BinaryExpression& a, OutputStream& out) { + SkASSERT(a.fOperator == Token::LOGICALAND); + BoolLiteral falseLiteral(fContext, -1, false); + SpvId falseConstant = this->writeBoolLiteral(falseLiteral); + SpvId lhs = this->writeExpression(*a.fLeft, out); + SpvId rhsLabel = this->nextId(); + SpvId end = this->nextId(); + SpvId lhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, lhs, rhsLabel, end, out); + this->writeLabel(rhsLabel, out); + SpvId rhs = this->writeExpression(*a.fRight, out); + SpvId rhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(end, out); + SpvId result = this->nextId(); + this->writeInstruction(SpvOpPhi, this->getType(*fContext.fBool_Type), result, falseConstant, + lhsBlock, rhs, rhsBlock, out); + return result; +} + +SpvId SPIRVCodeGenerator::writeLogicalOr(const BinaryExpression& o, OutputStream& out) { + SkASSERT(o.fOperator == Token::LOGICALOR); + BoolLiteral trueLiteral(fContext, -1, true); + SpvId trueConstant = this->writeBoolLiteral(trueLiteral); + SpvId lhs = this->writeExpression(*o.fLeft, out); + SpvId rhsLabel = this->nextId(); + SpvId end = this->nextId(); + SpvId lhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, lhs, end, rhsLabel, out); + this->writeLabel(rhsLabel, out); + SpvId rhs = this->writeExpression(*o.fRight, out); + SpvId rhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(end, out); + SpvId result = this->nextId(); + this->writeInstruction(SpvOpPhi, this->getType(*fContext.fBool_Type), result, trueConstant, + lhsBlock, rhs, rhsBlock, out); + return result; +} + +SpvId SPIRVCodeGenerator::writeTernaryExpression(const TernaryExpression& t, OutputStream& out) { + SpvId test = this->writeExpression(*t.fTest, out); + if (t.fIfTrue->fType.columns() == 1 && t.fIfTrue->isConstant() && t.fIfFalse->isConstant()) { + // both true and false are constants, can just use OpSelect + SpvId result = this->nextId(); + SpvId trueId = this->writeExpression(*t.fIfTrue, out); + SpvId falseId = this->writeExpression(*t.fIfFalse, out); + this->writeInstruction(SpvOpSelect, this->getType(t.fType), result, test, trueId, falseId, + out); + return result; + } + // was originally using OpPhi to choose the result, but for some reason that is crashing on + // Adreno. Switched to storing the result in a temp variable as glslang does. + SpvId var = this->nextId(); + this->writeInstruction(SpvOpVariable, this->getPointerType(t.fType, SpvStorageClassFunction), + var, SpvStorageClassFunction, fVariableBuffer); + SpvId trueLabel = this->nextId(); + SpvId falseLabel = this->nextId(); + SpvId end = this->nextId(); + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, trueLabel, falseLabel, out); + this->writeLabel(trueLabel, out); + this->writeInstruction(SpvOpStore, var, this->writeExpression(*t.fIfTrue, out), out); + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(falseLabel, out); + this->writeInstruction(SpvOpStore, var, this->writeExpression(*t.fIfFalse, out), out); + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(end, out); + SpvId result = this->nextId(); + this->writeInstruction(SpvOpLoad, this->getType(t.fType), result, var, out); + this->writePrecisionModifier(t.fType, result); + return result; +} + +SpvId SPIRVCodeGenerator::writePrefixExpression(const PrefixExpression& p, OutputStream& out) { + if (p.fOperator == Token::MINUS) { + SpvId result = this->nextId(); + SpvId typeId = this->getType(p.fType); + SpvId expr = this->writeExpression(*p.fOperand, out); + if (is_float(fContext, p.fType)) { + this->writeInstruction(SpvOpFNegate, typeId, result, expr, out); + } else if (is_signed(fContext, p.fType)) { + this->writeInstruction(SpvOpSNegate, typeId, result, expr, out); + } else { + ABORT("unsupported prefix expression %s", p.description().c_str()); + } + this->writePrecisionModifier(p.fType, result); + return result; + } + switch (p.fOperator) { + case Token::PLUS: + return this->writeExpression(*p.fOperand, out); + case Token::PLUSPLUS: { + std::unique_ptr lv = this->getLValue(*p.fOperand, out); + SpvId one = this->writeExpression(*create_literal_1(fContext, p.fType), out); + SpvId result = this->writeBinaryOperation(p.fType, p.fType, lv->load(out), one, + SpvOpFAdd, SpvOpIAdd, SpvOpIAdd, SpvOpUndef, + out); + lv->store(result, out); + return result; + } + case Token::MINUSMINUS: { + std::unique_ptr lv = this->getLValue(*p.fOperand, out); + SpvId one = this->writeExpression(*create_literal_1(fContext, p.fType), out); + SpvId result = this->writeBinaryOperation(p.fType, p.fType, lv->load(out), one, + SpvOpFSub, SpvOpISub, SpvOpISub, SpvOpUndef, + out); + lv->store(result, out); + return result; + } + case Token::LOGICALNOT: { + SkASSERT(p.fOperand->fType == *fContext.fBool_Type); + SpvId result = this->nextId(); + this->writeInstruction(SpvOpLogicalNot, this->getType(p.fOperand->fType), result, + this->writeExpression(*p.fOperand, out), out); + return result; + } + case Token::BITWISENOT: { + SpvId result = this->nextId(); + this->writeInstruction(SpvOpNot, this->getType(p.fOperand->fType), result, + this->writeExpression(*p.fOperand, out), out); + return result; + } + default: + ABORT("unsupported prefix expression: %s", p.description().c_str()); + } +} + +SpvId SPIRVCodeGenerator::writePostfixExpression(const PostfixExpression& p, OutputStream& out) { + std::unique_ptr lv = this->getLValue(*p.fOperand, out); + SpvId result = lv->load(out); + SpvId one = this->writeExpression(*create_literal_1(fContext, p.fType), out); + switch (p.fOperator) { + case Token::PLUSPLUS: { + SpvId temp = this->writeBinaryOperation(p.fType, p.fType, result, one, SpvOpFAdd, + SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out); + lv->store(temp, out); + return result; + } + case Token::MINUSMINUS: { + SpvId temp = this->writeBinaryOperation(p.fType, p.fType, result, one, SpvOpFSub, + SpvOpISub, SpvOpISub, SpvOpUndef, out); + lv->store(temp, out); + return result; + } + default: + ABORT("unsupported postfix expression %s", p.description().c_str()); + } +} + +SpvId SPIRVCodeGenerator::writeBoolLiteral(const BoolLiteral& b) { + if (b.fValue) { + if (fBoolTrue == 0) { + fBoolTrue = this->nextId(); + this->writeInstruction(SpvOpConstantTrue, this->getType(b.fType), fBoolTrue, + fConstantBuffer); + } + return fBoolTrue; + } else { + if (fBoolFalse == 0) { + fBoolFalse = this->nextId(); + this->writeInstruction(SpvOpConstantFalse, this->getType(b.fType), fBoolFalse, + fConstantBuffer); + } + return fBoolFalse; + } +} + +SpvId SPIRVCodeGenerator::writeIntLiteral(const IntLiteral& i) { + ConstantType type; + if (i.fType == *fContext.fInt_Type) { + type = ConstantType::kInt; + } else if (i.fType == *fContext.fUInt_Type) { + type = ConstantType::kUInt; + } else if (i.fType == *fContext.fShort_Type || i.fType == *fContext.fByte_Type) { + type = ConstantType::kShort; + } else if (i.fType == *fContext.fUShort_Type || i.fType == *fContext.fUByte_Type) { + type = ConstantType::kUShort; + } else { + SkASSERT(false); + } + std::pair key(i.fValue, type); + auto entry = fNumberConstants.find(key); + if (entry == fNumberConstants.end()) { + SpvId result = this->nextId(); + this->writeInstruction(SpvOpConstant, this->getType(i.fType), result, (SpvId) i.fValue, + fConstantBuffer); + fNumberConstants[key] = result; + return result; + } + return entry->second; +} + +SpvId SPIRVCodeGenerator::writeFloatLiteral(const FloatLiteral& f) { + if (f.fType != *fContext.fDouble_Type) { + ConstantType type; + if (f.fType == *fContext.fHalf_Type) { + type = ConstantType::kHalf; + } else { + type = ConstantType::kFloat; + } + float value = (float) f.fValue; + std::pair key(f.fValue, type); + auto entry = fNumberConstants.find(key); + if (entry == fNumberConstants.end()) { + SpvId result = this->nextId(); + uint32_t bits; + SkASSERT(sizeof(bits) == sizeof(value)); + memcpy(&bits, &value, sizeof(bits)); + this->writeInstruction(SpvOpConstant, this->getType(f.fType), result, bits, + fConstantBuffer); + fNumberConstants[key] = result; + return result; + } + return entry->second; + } else { + std::pair key(f.fValue, ConstantType::kDouble); + auto entry = fNumberConstants.find(key); + if (entry == fNumberConstants.end()) { + SpvId result = this->nextId(); + uint64_t bits; + SkASSERT(sizeof(bits) == sizeof(f.fValue)); + memcpy(&bits, &f.fValue, sizeof(bits)); + this->writeInstruction(SpvOpConstant, this->getType(f.fType), result, + bits & 0xffffffff, bits >> 32, fConstantBuffer); + fNumberConstants[key] = result; + return result; + } + return entry->second; + } +} + +SpvId SPIRVCodeGenerator::writeFunctionStart(const FunctionDeclaration& f, OutputStream& out) { + SpvId result = fFunctionMap[&f]; + this->writeInstruction(SpvOpFunction, this->getType(f.fReturnType), result, + SpvFunctionControlMaskNone, this->getFunctionType(f), out); + this->writeInstruction(SpvOpName, result, f.fName, fNameBuffer); + for (size_t i = 0; i < f.fParameters.size(); i++) { + SpvId id = this->nextId(); + fVariableMap[f.fParameters[i]] = id; + SpvId type; + type = this->getPointerType(f.fParameters[i]->fType, SpvStorageClassFunction); + this->writeInstruction(SpvOpFunctionParameter, type, id, out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeFunction(const FunctionDefinition& f, OutputStream& out) { + fVariableBuffer.reset(); + SpvId result = this->writeFunctionStart(f.fDeclaration, out); + this->writeLabel(this->nextId(), out); + StringStream bodyBuffer; + this->writeBlock((Block&) *f.fBody, bodyBuffer); + write_stringstream(fVariableBuffer, out); + if (f.fDeclaration.fName == "main") { + write_stringstream(fGlobalInitializersBuffer, out); + } + write_stringstream(bodyBuffer, out); + if (fCurrentBlock) { + if (f.fDeclaration.fReturnType == *fContext.fVoid_Type) { + this->writeInstruction(SpvOpReturn, out); + } else { + this->writeInstruction(SpvOpUnreachable, out); + } + } + this->writeInstruction(SpvOpFunctionEnd, out); + return result; +} + +void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target) { + if (layout.fLocation >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationLocation, layout.fLocation, + fDecorationBuffer); + } + if (layout.fBinding >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationBinding, layout.fBinding, + fDecorationBuffer); + } + if (layout.fIndex >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationIndex, layout.fIndex, + fDecorationBuffer); + } + if (layout.fSet >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationDescriptorSet, layout.fSet, + fDecorationBuffer); + } + if (layout.fInputAttachmentIndex >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationInputAttachmentIndex, + layout.fInputAttachmentIndex, fDecorationBuffer); + fCapabilities |= (((uint64_t) 1) << SpvCapabilityInputAttachment); + } + if (layout.fBuiltin >= 0 && layout.fBuiltin != SK_FRAGCOLOR_BUILTIN && + layout.fBuiltin != SK_IN_BUILTIN && layout.fBuiltin != SK_OUT_BUILTIN) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationBuiltIn, layout.fBuiltin, + fDecorationBuffer); + } +} + +void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target, int member) { + if (layout.fLocation >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationLocation, + layout.fLocation, fDecorationBuffer); + } + if (layout.fBinding >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBinding, + layout.fBinding, fDecorationBuffer); + } + if (layout.fIndex >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationIndex, + layout.fIndex, fDecorationBuffer); + } + if (layout.fSet >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationDescriptorSet, + layout.fSet, fDecorationBuffer); + } + if (layout.fInputAttachmentIndex >= 0) { + this->writeInstruction(SpvOpDecorate, target, member, SpvDecorationInputAttachmentIndex, + layout.fInputAttachmentIndex, fDecorationBuffer); + } + if (layout.fBuiltin >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBuiltIn, + layout.fBuiltin, fDecorationBuffer); + } +} + +static void update_sk_in_count(const Modifiers& m, int* outSkInCount) { + switch (m.fLayout.fPrimitive) { + case Layout::kPoints_Primitive: + *outSkInCount = 1; + break; + case Layout::kLines_Primitive: + *outSkInCount = 2; + break; + case Layout::kLinesAdjacency_Primitive: + *outSkInCount = 4; + break; + case Layout::kTriangles_Primitive: + *outSkInCount = 3; + break; + case Layout::kTrianglesAdjacency_Primitive: + *outSkInCount = 6; + break; + default: + return; + } +} + +SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) { + bool isBuffer = (0 != (intf.fVariable.fModifiers.fFlags & Modifiers::kBuffer_Flag)); + bool pushConstant = (0 != (intf.fVariable.fModifiers.fLayout.fFlags & + Layout::kPushConstant_Flag)); + MemoryLayout memoryLayout = (pushConstant || isBuffer) ? + MemoryLayout(MemoryLayout::k430_Standard) : + fDefaultLayout; + SpvId result = this->nextId(); + const Type* type = &intf.fVariable.fType; + if (fProgram.fInputs.fRTHeight) { + SkASSERT(fRTHeightStructId == (SpvId) -1); + SkASSERT(fRTHeightFieldIndex == (SpvId) -1); + std::vector fields = type->fields(); + fRTHeightStructId = result; + fRTHeightFieldIndex = fields.size(); + fields.emplace_back(Modifiers(), StringFragment(SKSL_RTHEIGHT_NAME), fContext.fFloat_Type.get()); + type = new Type(type->fOffset, type->name(), fields); + } + SpvId typeId; + if (intf.fVariable.fModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) { + for (const auto& e : fProgram) { + if (e.fKind == ProgramElement::kModifiers_Kind) { + const Modifiers& m = ((ModifiersDeclaration&) e).fModifiers; + update_sk_in_count(m, &fSkInCount); + } + } + typeId = this->getType(Type("sk_in", Type::kArray_Kind, intf.fVariable.fType.componentType(), + fSkInCount), memoryLayout); + } else { + typeId = this->getType(*type, memoryLayout); + } + if (intf.fVariable.fModifiers.fFlags & Modifiers::kBuffer_Flag) { + this->writeInstruction(SpvOpDecorate, typeId, SpvDecorationBufferBlock, fDecorationBuffer); + } else if (intf.fVariable.fModifiers.fLayout.fBuiltin == -1) { + this->writeInstruction(SpvOpDecorate, typeId, SpvDecorationBlock, fDecorationBuffer); + } + SpvStorageClass_ storageClass = get_storage_class(intf.fVariable.fModifiers); + SpvId ptrType = this->nextId(); + this->writeInstruction(SpvOpTypePointer, ptrType, storageClass, typeId, fConstantBuffer); + this->writeInstruction(SpvOpVariable, ptrType, result, storageClass, fConstantBuffer); + Layout layout = intf.fVariable.fModifiers.fLayout; + if (intf.fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag && layout.fSet == -1) { + layout.fSet = 0; + } + this->writeLayout(layout, result); + fVariableMap[&intf.fVariable] = result; + if (fProgram.fInputs.fRTHeight) { + delete type; + } + return result; +} + +void SPIRVCodeGenerator::writePrecisionModifier(const Type& type, SpvId id) { + this->writePrecisionModifier(type.highPrecision() ? Precision::kHigh : Precision::kLow, id); +} + +void SPIRVCodeGenerator::writePrecisionModifier(Precision precision, SpvId id) { + if (precision == Precision::kLow) { + this->writeInstruction(SpvOpDecorate, id, SpvDecorationRelaxedPrecision, fDecorationBuffer); + } +} + +#define BUILTIN_IGNORE 9999 +void SPIRVCodeGenerator::writeGlobalVars(Program::Kind kind, const VarDeclarations& decl, + OutputStream& out) { + for (size_t i = 0; i < decl.fVars.size(); i++) { + if (decl.fVars[i]->fKind == Statement::kNop_Kind) { + continue; + } + const VarDeclaration& varDecl = (VarDeclaration&) *decl.fVars[i]; + const Variable* var = varDecl.fVar; + // These haven't been implemented in our SPIR-V generator yet and we only currently use them + // in the OpenGL backend. + SkASSERT(!(var->fModifiers.fFlags & (Modifiers::kReadOnly_Flag | + Modifiers::kWriteOnly_Flag | + Modifiers::kCoherent_Flag | + Modifiers::kVolatile_Flag | + Modifiers::kRestrict_Flag))); + if (var->fModifiers.fLayout.fBuiltin == BUILTIN_IGNORE) { + continue; + } + if (var->fModifiers.fLayout.fBuiltin == SK_FRAGCOLOR_BUILTIN && + kind != Program::kFragment_Kind) { + SkASSERT(!fProgram.fSettings.fFragColorIsInOut); + continue; + } + if (!var->fReadCount && !var->fWriteCount && + !(var->fModifiers.fFlags & (Modifiers::kIn_Flag | + Modifiers::kOut_Flag | + Modifiers::kUniform_Flag | + Modifiers::kBuffer_Flag))) { + // variable is dead and not an input / output var (the Vulkan debug layers complain if + // we elide an interface var, even if it's dead) + continue; + } + SpvStorageClass_ storageClass; + if (var->fModifiers.fFlags & Modifiers::kIn_Flag) { + storageClass = SpvStorageClassInput; + } else if (var->fModifiers.fFlags & Modifiers::kOut_Flag) { + storageClass = SpvStorageClassOutput; + } else if (var->fModifiers.fFlags & Modifiers::kUniform_Flag) { + if (var->fType.kind() == Type::kSampler_Kind || + var->fType.kind() == Type::kSeparateSampler_Kind || + var->fType.kind() == Type::kTexture_Kind) { + storageClass = SpvStorageClassUniformConstant; + } else { + storageClass = SpvStorageClassUniform; + } + } else { + storageClass = SpvStorageClassPrivate; + } + SpvId id = this->nextId(); + fVariableMap[var] = id; + SpvId type; + if (var->fModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) { + type = this->getPointerType(Type("sk_in", Type::kArray_Kind, + var->fType.componentType(), fSkInCount), + storageClass); + } else { + type = this->getPointerType(var->fType, storageClass); + } + this->writeInstruction(SpvOpVariable, type, id, storageClass, fConstantBuffer); + this->writeInstruction(SpvOpName, id, var->fName, fNameBuffer); + this->writePrecisionModifier(var->fType, id); + if (varDecl.fValue) { + SkASSERT(!fCurrentBlock); + fCurrentBlock = -1; + SpvId value = this->writeExpression(*varDecl.fValue, fGlobalInitializersBuffer); + this->writeInstruction(SpvOpStore, id, value, fGlobalInitializersBuffer); + fCurrentBlock = 0; + } + this->writeLayout(var->fModifiers.fLayout, id); + if (var->fModifiers.fFlags & Modifiers::kFlat_Flag) { + this->writeInstruction(SpvOpDecorate, id, SpvDecorationFlat, fDecorationBuffer); + } + if (var->fModifiers.fFlags & Modifiers::kNoPerspective_Flag) { + this->writeInstruction(SpvOpDecorate, id, SpvDecorationNoPerspective, + fDecorationBuffer); + } + } +} + +void SPIRVCodeGenerator::writeVarDeclarations(const VarDeclarations& decl, OutputStream& out) { + for (const auto& stmt : decl.fVars) { + SkASSERT(stmt->fKind == Statement::kVarDeclaration_Kind); + VarDeclaration& varDecl = (VarDeclaration&) *stmt; + const Variable* var = varDecl.fVar; + // These haven't been implemented in our SPIR-V generator yet and we only currently use them + // in the OpenGL backend. + SkASSERT(!(var->fModifiers.fFlags & (Modifiers::kReadOnly_Flag | + Modifiers::kWriteOnly_Flag | + Modifiers::kCoherent_Flag | + Modifiers::kVolatile_Flag | + Modifiers::kRestrict_Flag))); + SpvId id = this->nextId(); + fVariableMap[var] = id; + SpvId type = this->getPointerType(var->fType, SpvStorageClassFunction); + this->writeInstruction(SpvOpVariable, type, id, SpvStorageClassFunction, fVariableBuffer); + this->writeInstruction(SpvOpName, id, var->fName, fNameBuffer); + if (varDecl.fValue) { + SpvId value = this->writeExpression(*varDecl.fValue, out); + this->writeInstruction(SpvOpStore, id, value, out); + } + } +} + +void SPIRVCodeGenerator::writeStatement(const Statement& s, OutputStream& out) { + switch (s.fKind) { + case Statement::kNop_Kind: + break; + case Statement::kBlock_Kind: + this->writeBlock((Block&) s, out); + break; + case Statement::kExpression_Kind: + this->writeExpression(*((ExpressionStatement&) s).fExpression, out); + break; + case Statement::kReturn_Kind: + this->writeReturnStatement((ReturnStatement&) s, out); + break; + case Statement::kVarDeclarations_Kind: + this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration, out); + break; + case Statement::kIf_Kind: + this->writeIfStatement((IfStatement&) s, out); + break; + case Statement::kFor_Kind: + this->writeForStatement((ForStatement&) s, out); + break; + case Statement::kWhile_Kind: + this->writeWhileStatement((WhileStatement&) s, out); + break; + case Statement::kDo_Kind: + this->writeDoStatement((DoStatement&) s, out); + break; + case Statement::kSwitch_Kind: + this->writeSwitchStatement((SwitchStatement&) s, out); + break; + case Statement::kBreak_Kind: + this->writeInstruction(SpvOpBranch, fBreakTarget.top(), out); + break; + case Statement::kContinue_Kind: + this->writeInstruction(SpvOpBranch, fContinueTarget.top(), out); + break; + case Statement::kDiscard_Kind: + this->writeInstruction(SpvOpKill, out); + break; + default: + ABORT("unsupported statement: %s", s.description().c_str()); + } +} + +void SPIRVCodeGenerator::writeBlock(const Block& b, OutputStream& out) { + for (size_t i = 0; i < b.fStatements.size(); i++) { + this->writeStatement(*b.fStatements[i], out); + } +} + +void SPIRVCodeGenerator::writeIfStatement(const IfStatement& stmt, OutputStream& out) { + SpvId test = this->writeExpression(*stmt.fTest, out); + SpvId ifTrue = this->nextId(); + SpvId ifFalse = this->nextId(); + if (stmt.fIfFalse) { + SpvId end = this->nextId(); + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out); + this->writeLabel(ifTrue, out); + this->writeStatement(*stmt.fIfTrue, out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, end, out); + } + this->writeLabel(ifFalse, out); + this->writeStatement(*stmt.fIfFalse, out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, end, out); + } + this->writeLabel(end, out); + } else { + this->writeInstruction(SpvOpSelectionMerge, ifFalse, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out); + this->writeLabel(ifTrue, out); + this->writeStatement(*stmt.fIfTrue, out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, ifFalse, out); + } + this->writeLabel(ifFalse, out); + } +} + +void SPIRVCodeGenerator::writeForStatement(const ForStatement& f, OutputStream& out) { + if (f.fInitializer) { + this->writeStatement(*f.fInitializer, out); + } + SpvId header = this->nextId(); + SpvId start = this->nextId(); + SpvId body = this->nextId(); + SpvId next = this->nextId(); + fContinueTarget.push(next); + SpvId end = this->nextId(); + fBreakTarget.push(end); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(header, out); + this->writeInstruction(SpvOpLoopMerge, end, next, SpvLoopControlMaskNone, out); + this->writeInstruction(SpvOpBranch, start, out); + this->writeLabel(start, out); + if (f.fTest) { + SpvId test = this->writeExpression(*f.fTest, out); + this->writeInstruction(SpvOpBranchConditional, test, body, end, out); + } + this->writeLabel(body, out); + this->writeStatement(*f.fStatement, out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, next, out); + } + this->writeLabel(next, out); + if (f.fNext) { + this->writeExpression(*f.fNext, out); + } + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(end, out); + fBreakTarget.pop(); + fContinueTarget.pop(); +} + +void SPIRVCodeGenerator::writeWhileStatement(const WhileStatement& w, OutputStream& out) { + SpvId header = this->nextId(); + SpvId start = this->nextId(); + SpvId body = this->nextId(); + SpvId continueTarget = this->nextId(); + fContinueTarget.push(continueTarget); + SpvId end = this->nextId(); + fBreakTarget.push(end); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(header, out); + this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out); + this->writeInstruction(SpvOpBranch, start, out); + this->writeLabel(start, out); + SpvId test = this->writeExpression(*w.fTest, out); + this->writeInstruction(SpvOpBranchConditional, test, body, end, out); + this->writeLabel(body, out); + this->writeStatement(*w.fStatement, out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, continueTarget, out); + } + this->writeLabel(continueTarget, out); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(end, out); + fBreakTarget.pop(); + fContinueTarget.pop(); +} + +void SPIRVCodeGenerator::writeDoStatement(const DoStatement& d, OutputStream& out) { + // We believe the do loop code below will work, but Skia doesn't actually use them and + // adequately testing this code in the absence of Skia exercising it isn't straightforward. For + // the time being, we just fail with an error due to the lack of testing. If you encounter this + // message, simply remove the error call below to see whether our do loop support actually + // works. + fErrors.error(d.fOffset, "internal error: do loop support has been disabled in SPIR-V, see " + "SkSLSPIRVCodeGenerator.cpp for details"); + + SpvId header = this->nextId(); + SpvId start = this->nextId(); + SpvId next = this->nextId(); + SpvId continueTarget = this->nextId(); + fContinueTarget.push(continueTarget); + SpvId end = this->nextId(); + fBreakTarget.push(end); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(header, out); + this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out); + this->writeInstruction(SpvOpBranch, start, out); + this->writeLabel(start, out); + this->writeStatement(*d.fStatement, out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, next, out); + } + this->writeLabel(next, out); + SpvId test = this->writeExpression(*d.fTest, out); + this->writeInstruction(SpvOpBranchConditional, test, continueTarget, end, out); + this->writeLabel(continueTarget, out); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(end, out); + fBreakTarget.pop(); + fContinueTarget.pop(); +} + +void SPIRVCodeGenerator::writeSwitchStatement(const SwitchStatement& s, OutputStream& out) { + SpvId value = this->writeExpression(*s.fValue, out); + std::vector labels; + SpvId end = this->nextId(); + SpvId defaultLabel = end; + fBreakTarget.push(end); + int size = 3; + for (const auto& c : s.fCases) { + SpvId label = this->nextId(); + labels.push_back(label); + if (c->fValue) { + size += 2; + } else { + defaultLabel = label; + } + } + labels.push_back(end); + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeOpCode(SpvOpSwitch, size, out); + this->writeWord(value, out); + this->writeWord(defaultLabel, out); + for (size_t i = 0; i < s.fCases.size(); ++i) { + if (!s.fCases[i]->fValue) { + continue; + } + SkASSERT(s.fCases[i]->fValue->fKind == Expression::kIntLiteral_Kind); + this->writeWord(((IntLiteral&) *s.fCases[i]->fValue).fValue, out); + this->writeWord(labels[i], out); + } + for (size_t i = 0; i < s.fCases.size(); ++i) { + this->writeLabel(labels[i], out); + for (const auto& stmt : s.fCases[i]->fStatements) { + this->writeStatement(*stmt, out); + } + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, labels[i + 1], out); + } + } + this->writeLabel(end, out); + fBreakTarget.pop(); +} + +void SPIRVCodeGenerator::writeReturnStatement(const ReturnStatement& r, OutputStream& out) { + if (r.fExpression) { + this->writeInstruction(SpvOpReturnValue, this->writeExpression(*r.fExpression, out), + out); + } else { + this->writeInstruction(SpvOpReturn, out); + } +} + +void SPIRVCodeGenerator::writeGeometryShaderExecutionMode(SpvId entryPoint, OutputStream& out) { + SkASSERT(fProgram.fKind == Program::kGeometry_Kind); + int invocations = 1; + for (const auto& e : fProgram) { + if (e.fKind == ProgramElement::kModifiers_Kind) { + const Modifiers& m = ((ModifiersDeclaration&) e).fModifiers; + if (m.fFlags & Modifiers::kIn_Flag) { + if (m.fLayout.fInvocations != -1) { + invocations = m.fLayout.fInvocations; + } + SpvId input; + switch (m.fLayout.fPrimitive) { + case Layout::kPoints_Primitive: + input = SpvExecutionModeInputPoints; + break; + case Layout::kLines_Primitive: + input = SpvExecutionModeInputLines; + break; + case Layout::kLinesAdjacency_Primitive: + input = SpvExecutionModeInputLinesAdjacency; + break; + case Layout::kTriangles_Primitive: + input = SpvExecutionModeTriangles; + break; + case Layout::kTrianglesAdjacency_Primitive: + input = SpvExecutionModeInputTrianglesAdjacency; + break; + default: + input = 0; + break; + } + update_sk_in_count(m, &fSkInCount); + if (input) { + this->writeInstruction(SpvOpExecutionMode, entryPoint, input, out); + } + } else if (m.fFlags & Modifiers::kOut_Flag) { + SpvId output; + switch (m.fLayout.fPrimitive) { + case Layout::kPoints_Primitive: + output = SpvExecutionModeOutputPoints; + break; + case Layout::kLineStrip_Primitive: + output = SpvExecutionModeOutputLineStrip; + break; + case Layout::kTriangleStrip_Primitive: + output = SpvExecutionModeOutputTriangleStrip; + break; + default: + output = 0; + break; + } + if (output) { + this->writeInstruction(SpvOpExecutionMode, entryPoint, output, out); + } + if (m.fLayout.fMaxVertices != -1) { + this->writeInstruction(SpvOpExecutionMode, entryPoint, + SpvExecutionModeOutputVertices, m.fLayout.fMaxVertices, + out); + } + } + } + } + this->writeInstruction(SpvOpExecutionMode, entryPoint, SpvExecutionModeInvocations, + invocations, out); +} + +void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream& out) { + fGLSLExtendedInstructions = this->nextId(); + StringStream body; + std::set interfaceVars; + // assign IDs to functions, determine sk_in size + int skInSize = -1; + for (const auto& e : program) { + switch (e.fKind) { + case ProgramElement::kFunction_Kind: { + FunctionDefinition& f = (FunctionDefinition&) e; + fFunctionMap[&f.fDeclaration] = this->nextId(); + break; + } + case ProgramElement::kModifiers_Kind: { + Modifiers& m = ((ModifiersDeclaration&) e).fModifiers; + if (m.fFlags & Modifiers::kIn_Flag) { + switch (m.fLayout.fPrimitive) { + case Layout::kPoints_Primitive: // break + case Layout::kLines_Primitive: + skInSize = 1; + break; + case Layout::kLinesAdjacency_Primitive: // break + skInSize = 2; + break; + case Layout::kTriangles_Primitive: // break + case Layout::kTrianglesAdjacency_Primitive: + skInSize = 3; + break; + default: + break; + } + } + break; + } + default: + break; + } + } + for (const auto& e : program) { + if (e.fKind == ProgramElement::kInterfaceBlock_Kind) { + InterfaceBlock& intf = (InterfaceBlock&) e; + if (SK_IN_BUILTIN == intf.fVariable.fModifiers.fLayout.fBuiltin) { + SkASSERT(skInSize != -1); + intf.fSizes.emplace_back(new IntLiteral(fContext, -1, skInSize)); + } + SpvId id = this->writeInterfaceBlock(intf); + if (((intf.fVariable.fModifiers.fFlags & Modifiers::kIn_Flag) || + (intf.fVariable.fModifiers.fFlags & Modifiers::kOut_Flag)) && + intf.fVariable.fModifiers.fLayout.fBuiltin == -1) { + interfaceVars.insert(id); + } + } + } + for (const auto& e : program) { + if (e.fKind == ProgramElement::kVar_Kind) { + this->writeGlobalVars(program.fKind, ((VarDeclarations&) e), body); + } + } + for (const auto& e : program) { + if (e.fKind == ProgramElement::kFunction_Kind) { + this->writeFunction(((FunctionDefinition&) e), body); + } + } + const FunctionDeclaration* main = nullptr; + for (auto entry : fFunctionMap) { + if (entry.first->fName == "main") { + main = entry.first; + } + } + if (!main) { + fErrors.error(0, "program does not contain a main() function"); + return; + } + for (auto entry : fVariableMap) { + const Variable* var = entry.first; + if (var->fStorage == Variable::kGlobal_Storage && + ((var->fModifiers.fFlags & Modifiers::kIn_Flag) || + (var->fModifiers.fFlags & Modifiers::kOut_Flag))) { + interfaceVars.insert(entry.second); + } + } + this->writeCapabilities(out); + this->writeInstruction(SpvOpExtInstImport, fGLSLExtendedInstructions, "GLSL.std.450", out); + this->writeInstruction(SpvOpMemoryModel, SpvAddressingModelLogical, SpvMemoryModelGLSL450, out); + this->writeOpCode(SpvOpEntryPoint, (SpvId) (3 + (main->fName.fLength + 4) / 4) + + (int32_t) interfaceVars.size(), out); + switch (program.fKind) { + case Program::kVertex_Kind: + this->writeWord(SpvExecutionModelVertex, out); + break; + case Program::kFragment_Kind: + this->writeWord(SpvExecutionModelFragment, out); + break; + case Program::kGeometry_Kind: + this->writeWord(SpvExecutionModelGeometry, out); + break; + default: + ABORT("cannot write this kind of program to SPIR-V\n"); + } + SpvId entryPoint = fFunctionMap[main]; + this->writeWord(entryPoint, out); + this->writeString(main->fName.fChars, main->fName.fLength, out); + for (int var : interfaceVars) { + this->writeWord(var, out); + } + if (program.fKind == Program::kGeometry_Kind) { + this->writeGeometryShaderExecutionMode(entryPoint, out); + } + if (program.fKind == Program::kFragment_Kind) { + this->writeInstruction(SpvOpExecutionMode, + fFunctionMap[main], + SpvExecutionModeOriginUpperLeft, + out); + } + for (const auto& e : program) { + if (e.fKind == ProgramElement::kExtension_Kind) { + this->writeInstruction(SpvOpSourceExtension, ((Extension&) e).fName.c_str(), out); + } + } + + write_stringstream(fExtraGlobalsBuffer, out); + write_stringstream(fNameBuffer, out); + write_stringstream(fDecorationBuffer, out); + write_stringstream(fConstantBuffer, out); + write_stringstream(fExternalFunctionsBuffer, out); + write_stringstream(body, out); +} + +bool SPIRVCodeGenerator::generateCode() { + SkASSERT(!fErrors.errorCount()); + this->writeWord(SpvMagicNumber, *fOut); + this->writeWord(SpvVersion, *fOut); + this->writeWord(SKSL_MAGIC, *fOut); + StringStream buffer; + this->writeInstructions(fProgram, buffer); + this->writeWord(fIdCount, *fOut); + this->writeWord(0, *fOut); // reserved, always zero + write_stringstream(buffer, *fOut); + return 0 == fErrors.errorCount(); +} + +} diff --git a/gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.h b/gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.h new file mode 100644 index 0000000000..8f14b46fb5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLSPIRVCodeGenerator.h @@ -0,0 +1,411 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SPIRVCODEGENERATOR +#define SKSL_SPIRVCODEGENERATOR + +#include +#include +#include + +#include "src/sksl/SkSLCodeGenerator.h" +#include "src/sksl/SkSLMemoryLayout.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBoolLiteral.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/ir/SkSLWhileStatement.h" +#include "src/sksl/spirv.h" + +union ConstantValue { + ConstantValue(int64_t i) + : fInt(i) {} + + ConstantValue(double d) + : fDouble(d) {} + + bool operator==(const ConstantValue& other) const { + return fInt == other.fInt; + } + + int64_t fInt; + double fDouble; +}; + +enum class ConstantType { + kInt, + kUInt, + kShort, + kUShort, + kFloat, + kDouble, + kHalf, +}; + +namespace std { + +template <> +struct hash> { + size_t operator()(const std::pair& key) const { + return key.first.fInt ^ (int) key.second; + } +}; + +} + +namespace SkSL { + +#define kLast_Capability SpvCapabilityMultiViewport + +/** + * Converts a Program into a SPIR-V binary. + */ +class SPIRVCodeGenerator : public CodeGenerator { +public: + class LValue { + public: + virtual ~LValue() {} + + // returns a pointer to the lvalue, if possible. If the lvalue cannot be directly referenced + // by a pointer (e.g. vector swizzles), returns 0. + virtual SpvId getPointer() = 0; + + virtual SpvId load(OutputStream& out) = 0; + + virtual void store(SpvId value, OutputStream& out) = 0; + }; + + SPIRVCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors, + OutputStream* out) + : INHERITED(program, errors, out) + , fContext(*context) + , fDefaultLayout(MemoryLayout::k140_Standard) + , fCapabilities(0) + , fIdCount(1) + , fBoolTrue(0) + , fBoolFalse(0) + , fSetupFragPosition(false) + , fCurrentBlock(0) + , fSynthetics(nullptr, errors) { + this->setupIntrinsics(); + } + + bool generateCode() override; + +private: + enum IntrinsicKind { + kGLSL_STD_450_IntrinsicKind, + kSPIRV_IntrinsicKind, + kSpecial_IntrinsicKind + }; + + enum SpecialIntrinsic { + kAtan_SpecialIntrinsic, + kClamp_SpecialIntrinsic, + kMax_SpecialIntrinsic, + kMin_SpecialIntrinsic, + kMix_SpecialIntrinsic, + kMod_SpecialIntrinsic, + kDFdy_SpecialIntrinsic, + kSaturate_SpecialIntrinsic, + kSampledImage_SpecialIntrinsic, + kSubpassLoad_SpecialIntrinsic, + kTexture_SpecialIntrinsic, + }; + + enum class Precision { + kLow, + kHigh, + }; + + void setupIntrinsics(); + + SpvId nextId(); + + Type getActualType(const Type& type); + + SpvId getType(const Type& type); + + SpvId getType(const Type& type, const MemoryLayout& layout); + + SpvId getImageType(const Type& type); + + SpvId getFunctionType(const FunctionDeclaration& function); + + SpvId getPointerType(const Type& type, SpvStorageClass_ storageClass); + + SpvId getPointerType(const Type& type, const MemoryLayout& layout, + SpvStorageClass_ storageClass); + + void writePrecisionModifier(Precision precision, SpvId id); + + void writePrecisionModifier(const Type& type, SpvId id); + + std::vector getAccessChain(const Expression& expr, OutputStream& out); + + void writeLayout(const Layout& layout, SpvId target); + + void writeLayout(const Layout& layout, SpvId target, int member); + + void writeStruct(const Type& type, const MemoryLayout& layout, SpvId resultId); + + void writeProgramElement(const ProgramElement& pe, OutputStream& out); + + SpvId writeInterfaceBlock(const InterfaceBlock& intf); + + SpvId writeFunctionStart(const FunctionDeclaration& f, OutputStream& out); + + SpvId writeFunctionDeclaration(const FunctionDeclaration& f, OutputStream& out); + + SpvId writeFunction(const FunctionDefinition& f, OutputStream& out); + + void writeGlobalVars(Program::Kind kind, const VarDeclarations& v, OutputStream& out); + + void writeVarDeclarations(const VarDeclarations& decl, OutputStream& out); + + SpvId writeVariableReference(const VariableReference& ref, OutputStream& out); + + std::unique_ptr getLValue(const Expression& value, OutputStream& out); + + SpvId writeExpression(const Expression& expr, OutputStream& out); + + SpvId writeIntrinsicCall(const FunctionCall& c, OutputStream& out); + + SpvId writeFunctionCall(const FunctionCall& c, OutputStream& out); + + + void writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst, + SpvId signedInst, SpvId unsignedInst, + const std::vector& args, OutputStream& out); + + /** + * Given a list of potentially mixed scalars and vectors, promotes the scalars to match the + * size of the vectors and returns the ids of the written expressions. e.g. given (float, vec2), + * returns (vec2(float), vec2). It is an error to use mismatched vector sizes, e.g. (float, + * vec2, vec3). + */ + std::vector vectorize(const std::vector>& args, + OutputStream& out); + + SpvId writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, OutputStream& out); + + SpvId writeConstantVector(const Constructor& c); + + SpvId writeFloatConstructor(const Constructor& c, OutputStream& out); + + SpvId writeIntConstructor(const Constructor& c, OutputStream& out); + + SpvId writeUIntConstructor(const Constructor& c, OutputStream& out); + + /** + * Writes a matrix with the diagonal entries all equal to the provided expression, and all other + * entries equal to zero. + */ + void writeUniformScaleMatrix(SpvId id, SpvId diagonal, const Type& type, OutputStream& out); + + /** + * Writes a potentially-different-sized copy of a matrix. Entries which do not exist in the + * source matrix are filled with zero; entries which do not exist in the destination matrix are + * ignored. + */ + void writeMatrixCopy(SpvId id, SpvId src, const Type& srcType, const Type& dstType, + OutputStream& out); + + void addColumnEntry(SpvId columnType, Precision precision, std::vector* currentColumn, + std::vector* columnIds, int* currentCount, int rows, SpvId entry, + OutputStream& out); + + SpvId writeMatrixConstructor(const Constructor& c, OutputStream& out); + + SpvId writeVectorConstructor(const Constructor& c, OutputStream& out); + + SpvId writeArrayConstructor(const Constructor& c, OutputStream& out); + + SpvId writeConstructor(const Constructor& c, OutputStream& out); + + SpvId writeFieldAccess(const FieldAccess& f, OutputStream& out); + + SpvId writeSwizzle(const Swizzle& swizzle, OutputStream& out); + + /** + * Folds the potentially-vector result of a logical operation down to a single bool. If + * operandType is a vector type, assumes that the intermediate result in id is a bvec of the + * same dimensions, and applys all() to it to fold it down to a single bool value. Otherwise, + * returns the original id value. + */ + SpvId foldToBool(SpvId id, const Type& operandType, SpvOp op, OutputStream& out); + + SpvId writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, SpvOp_ floatOperator, + SpvOp_ intOperator, SpvOp_ vectorMergeOperator, + SpvOp_ mergeOperator, OutputStream& out); + + SpvId writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, SpvId rhs, + SpvOp_ floatOperator, SpvOp_ intOperator, + OutputStream& out); + + SpvId writeBinaryOperation(const Type& resultType, const Type& operandType, SpvId lhs, + SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt, + SpvOp_ ifBool, OutputStream& out); + + SpvId writeBinaryOperation(const BinaryExpression& expr, SpvOp_ ifFloat, SpvOp_ ifInt, + SpvOp_ ifUInt, OutputStream& out); + + SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Token::Kind op, + const Type& rightType, SpvId rhs, const Type& resultType, + OutputStream& out); + + SpvId writeBinaryExpression(const BinaryExpression& b, OutputStream& out); + + SpvId writeTernaryExpression(const TernaryExpression& t, OutputStream& out); + + SpvId writeIndexExpression(const IndexExpression& expr, OutputStream& out); + + SpvId writeLogicalAnd(const BinaryExpression& b, OutputStream& out); + + SpvId writeLogicalOr(const BinaryExpression& o, OutputStream& out); + + SpvId writePrefixExpression(const PrefixExpression& p, OutputStream& out); + + SpvId writePostfixExpression(const PostfixExpression& p, OutputStream& out); + + SpvId writeBoolLiteral(const BoolLiteral& b); + + SpvId writeIntLiteral(const IntLiteral& i); + + SpvId writeFloatLiteral(const FloatLiteral& f); + + void writeStatement(const Statement& s, OutputStream& out); + + void writeBlock(const Block& b, OutputStream& out); + + void writeIfStatement(const IfStatement& stmt, OutputStream& out); + + void writeForStatement(const ForStatement& f, OutputStream& out); + + void writeWhileStatement(const WhileStatement& w, OutputStream& out); + + void writeDoStatement(const DoStatement& d, OutputStream& out); + + void writeSwitchStatement(const SwitchStatement& s, OutputStream& out); + + void writeReturnStatement(const ReturnStatement& r, OutputStream& out); + + void writeCapabilities(OutputStream& out); + + void writeInstructions(const Program& program, OutputStream& out); + + void writeOpCode(SpvOp_ opCode, int length, OutputStream& out); + + void writeWord(int32_t word, OutputStream& out); + + void writeString(const char* string, size_t length, OutputStream& out); + + void writeLabel(SpvId id, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, StringFragment string, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, StringFragment string, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, StringFragment string, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, int32_t word6, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, int32_t word6, int32_t word7, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, int32_t word6, int32_t word7, int32_t word8, + OutputStream& out); + + void writeGeometryShaderExecutionMode(SpvId entryPoint, OutputStream& out); + + const Context& fContext; + const MemoryLayout fDefaultLayout; + + uint64_t fCapabilities; + SpvId fIdCount; + SpvId fGLSLExtendedInstructions; + typedef std::tuple Intrinsic; + std::unordered_map fIntrinsicMap; + std::unordered_map fFunctionMap; + std::unordered_map fVariableMap; + std::unordered_map fInterfaceBlockMap; + std::unordered_map fImageTypeMap; + std::unordered_map fTypeMap; + StringStream fCapabilitiesBuffer; + StringStream fGlobalInitializersBuffer; + StringStream fConstantBuffer; + StringStream fExtraGlobalsBuffer; + StringStream fExternalFunctionsBuffer; + StringStream fVariableBuffer; + StringStream fNameBuffer; + StringStream fDecorationBuffer; + + SpvId fBoolTrue; + SpvId fBoolFalse; + std::unordered_map, SpvId> fNumberConstants; + // The constant float2(0, 1), used in swizzling + SpvId fConstantZeroOneVector = 0; + bool fSetupFragPosition; + // label of the current block, or 0 if we are not in a block + SpvId fCurrentBlock; + std::stack fBreakTarget; + std::stack fContinueTarget; + SpvId fRTHeightStructId = (SpvId) -1; + SpvId fRTHeightFieldIndex = (SpvId) -1; + // holds variables synthesized during output, for lifetime purposes + SymbolTable fSynthetics; + int fSkInCount = 1; + + friend class PointerLValue; + friend class SwizzleLValue; + + typedef CodeGenerator INHERITED; +}; + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.cpp b/gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.cpp new file mode 100644 index 0000000000..edd9fbe42b --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.cpp @@ -0,0 +1,239 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLSectionAndParameterHelper.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarationsStatement.h" +#include "src/sksl/ir/SkSLWhileStatement.h" + +namespace SkSL { + +SectionAndParameterHelper::SectionAndParameterHelper(const Program* program, ErrorReporter& errors) + : fProgram(*program) { + for (const auto& p : fProgram) { + switch (p.fKind) { + case ProgramElement::kVar_Kind: { + const VarDeclarations& decls = (const VarDeclarations&) p; + for (const auto& raw : decls.fVars) { + const VarDeclaration& decl = (VarDeclaration&) *raw; + if (IsParameter(*decl.fVar)) { + fParameters.push_back(decl.fVar); + } + } + break; + } + case ProgramElement::kSection_Kind: { + const Section& s = (const Section&) p; + if (IsSupportedSection(s.fName.c_str())) { + if (SectionRequiresArgument(s.fName.c_str()) && !s.fArgument.size()) { + errors.error(s.fOffset, + ("section '@" + s.fName + + "' requires one parameter").c_str()); + } + if (!SectionAcceptsArgument(s.fName.c_str()) && s.fArgument.size()) { + errors.error(s.fOffset, + ("section '@" + s.fName + "' has no parameters").c_str()); + } + } else { + errors.error(s.fOffset, + ("unsupported section '@" + s.fName + "'").c_str()); + } + if (!SectionPermitsDuplicates(s.fName.c_str()) && + fSections.find(s.fName) != fSections.end()) { + errors.error(s.fOffset, + ("duplicate section '@" + s.fName + "'").c_str()); + } + fSections[s.fName].push_back(&s); + break; + } + default: + break; + } + } +} + +bool SectionAndParameterHelper::hasCoordOverrides(const Variable& fp) { + for (const auto& pe : fProgram) { + if (this->hasCoordOverrides(pe, fp)) { + return true; + } + } + return false; +} + +bool SectionAndParameterHelper::hasCoordOverrides(const ProgramElement& pe, const Variable& fp) { + if (pe.fKind == ProgramElement::kFunction_Kind) { + return this->hasCoordOverrides(*((const FunctionDefinition&) pe).fBody, fp); + } + return false; +} + +bool SectionAndParameterHelper::hasCoordOverrides(const Expression& e, const Variable& fp) { + switch (e.fKind) { + case Expression::kFunctionCall_Kind: { + const FunctionCall& fc = (const FunctionCall&) e; + const FunctionDeclaration& f = fc.fFunction; + if (f.fBuiltin && f.fName == "sample" && fc.fArguments.size() >= 2 && + fc.fArguments.back()->fType == *fProgram.fContext->fFloat2_Type && + fc.fArguments[0]->fKind == Expression::kVariableReference_Kind && + &((VariableReference&) *fc.fArguments[0]).fVariable == &fp) { + return true; + } + for (const auto& e : fc.fArguments) { + if (this->hasCoordOverrides(*e, fp)) { + return true; + } + } + return false; + } + case Expression::kConstructor_Kind: { + const Constructor& c = (const Constructor&) e; + for (const auto& e : c.fArguments) { + if (this->hasCoordOverrides(*e, fp)) { + return true; + } + } + return false; + } + case Expression::kFieldAccess_Kind: { + return this->hasCoordOverrides(*((const FieldAccess&) e).fBase, fp); + } + case Expression::kSwizzle_Kind: + return this->hasCoordOverrides(*((const Swizzle&) e).fBase, fp); + case Expression::kBinary_Kind: { + const BinaryExpression& b = (const BinaryExpression&) e; + return this->hasCoordOverrides(*b.fLeft, fp) || + this->hasCoordOverrides(*b.fRight, fp); + } + case Expression::kIndex_Kind: { + const IndexExpression& idx = (const IndexExpression&) e; + return this->hasCoordOverrides(*idx.fBase, fp) || + this->hasCoordOverrides(*idx.fIndex, fp); + } + case Expression::kPrefix_Kind: + return this->hasCoordOverrides(*((const PrefixExpression&) e).fOperand, fp); + case Expression::kPostfix_Kind: + return this->hasCoordOverrides(*((const PostfixExpression&) e).fOperand, fp); + case Expression::kTernary_Kind: { + const TernaryExpression& t = (const TernaryExpression&) e; + return this->hasCoordOverrides(*t.fTest, fp) || + this->hasCoordOverrides(*t.fIfTrue, fp) || + this->hasCoordOverrides(*t.fIfFalse, fp); + } + case Expression::kVariableReference_Kind: + return false; + case Expression::kBoolLiteral_Kind: + case Expression::kDefined_Kind: + case Expression::kExternalFunctionCall_Kind: + case Expression::kExternalValue_Kind: + case Expression::kFloatLiteral_Kind: + case Expression::kFunctionReference_Kind: + case Expression::kIntLiteral_Kind: + case Expression::kNullLiteral_Kind: + case Expression::kSetting_Kind: + case Expression::kTypeReference_Kind: + return false; + } + SkASSERT(false); + return false; +} + +bool SectionAndParameterHelper::hasCoordOverrides(const Statement& s, const Variable& fp) { + switch (s.fKind) { + case Statement::kBlock_Kind: { + for (const auto& child : ((const Block&) s).fStatements) { + if (this->hasCoordOverrides(*child, fp)) { + return true; + } + } + return false; + } + case Statement::kVarDeclaration_Kind: { + const VarDeclaration& var = (const VarDeclaration&) s; + if (var.fValue) { + return hasCoordOverrides(*var.fValue, fp); + } + return false; + } + case Statement::kVarDeclarations_Kind: { + const VarDeclarations& decls = *((const VarDeclarationsStatement&) s).fDeclaration; + for (const auto& stmt : decls.fVars) { + if (this->hasCoordOverrides(*stmt, fp)) { + return true; + } + } + return false; + } + case Statement::kExpression_Kind: + return this->hasCoordOverrides(*((const ExpressionStatement&) s).fExpression, fp); + case Statement::kReturn_Kind: { + const ReturnStatement& r = (const ReturnStatement&) s; + if (r.fExpression) { + return this->hasCoordOverrides(*r.fExpression, fp); + } + return false; + } + case Statement::kIf_Kind: { + const IfStatement& i = (const IfStatement&) s; + return this->hasCoordOverrides(*i.fTest, fp) || + this->hasCoordOverrides(*i.fIfTrue, fp) || + (i.fIfFalse && this->hasCoordOverrides(*i.fIfFalse, fp)); + } + case Statement::kFor_Kind: { + const ForStatement& f = (const ForStatement&) s; + return this->hasCoordOverrides(*f.fInitializer, fp) || + this->hasCoordOverrides(*f.fTest, fp) || + this->hasCoordOverrides(*f.fNext, fp) || + this->hasCoordOverrides(*f.fStatement, fp); + } + case Statement::kWhile_Kind: { + const WhileStatement& w = (const WhileStatement&) s; + return this->hasCoordOverrides(*w.fTest, fp) || + this->hasCoordOverrides(*w.fStatement, fp); + } + case Statement::kDo_Kind: { + const DoStatement& d = (const DoStatement&) s; + return this->hasCoordOverrides(*d.fTest, fp) || + this->hasCoordOverrides(*d.fStatement, fp); + } + case Statement::kSwitch_Kind: { + const SwitchStatement& sw = (const SwitchStatement&) s; + for (const auto& c : sw.fCases) { + for (const auto& st : c->fStatements) { + if (this->hasCoordOverrides(*st, fp)) { + return true; + } + } + } + return this->hasCoordOverrides(*sw.fValue, fp); + } + case Statement::kBreak_Kind: + case Statement::kContinue_Kind: + case Statement::kDiscard_Kind: + case Statement::kGroup_Kind: + case Statement::kNop_Kind: + return false; + } + SkASSERT(false); + return false; +} + +} diff --git a/gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.h b/gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.h new file mode 100644 index 0000000000..3251024f2e --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLSectionAndParameterHelper.h @@ -0,0 +1,125 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SECTIONANDPARAMETERHELPER +#define SKSL_SECTIONANDPARAMETERHELPER + +#include "src/sksl/SkSLErrorReporter.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSection.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include +#include + +namespace SkSL { + +#define CLASS_SECTION "class" +#define CLONE_SECTION "clone" +#define CONSTRUCTOR_SECTION "constructor" +#define CONSTRUCTOR_CODE_SECTION "constructorCode" +#define CONSTRUCTOR_PARAMS_SECTION "constructorParams" +#define COORD_TRANSFORM_SECTION "coordTransform" +#define CPP_SECTION "cpp" +#define CPP_END_SECTION "cppEnd" +#define HEADER_SECTION "header" +#define HEADER_END_SECTION "headerEnd" +#define EMIT_CODE_SECTION "emitCode" +#define FIELDS_SECTION "fields" +#define INITIALIZERS_SECTION "initializers" +#define MAKE_SECTION "make" +#define OPTIMIZATION_FLAGS_SECTION "optimizationFlags" +#define SAMPLER_PARAMS_SECTION "samplerParams" +#define SET_DATA_SECTION "setData" +#define TEST_CODE_SECTION "test" + +class SectionAndParameterHelper { +public: + SectionAndParameterHelper(const Program* program, ErrorReporter& errors); + + const Section* getSection(const char* name) { + SkASSERT(!SectionPermitsDuplicates(name)); + auto found = fSections.find(name); + if (found == fSections.end()) { + return nullptr; + } + SkASSERT(found->second.size() == 1); + return found->second[0]; + } + + std::vector getSections(const char* name) { + auto found = fSections.find(name); + if (found == fSections.end()) { + return std::vector(); + } + return found->second; + } + + const std::vector& getParameters() { + return fParameters; + } + + bool hasCoordOverrides(const Variable& fp); + + static bool IsParameter(const Variable& var) { + return (var.fModifiers.fFlags & Modifiers::kIn_Flag) && + -1 == var.fModifiers.fLayout.fBuiltin; + } + + static bool IsSupportedSection(const char* name) { + return !strcmp(name, CLASS_SECTION) || + !strcmp(name, CLONE_SECTION) || + !strcmp(name, CONSTRUCTOR_SECTION) || + !strcmp(name, CONSTRUCTOR_CODE_SECTION) || + !strcmp(name, CONSTRUCTOR_PARAMS_SECTION) || + !strcmp(name, COORD_TRANSFORM_SECTION) || + !strcmp(name, CPP_SECTION) || + !strcmp(name, CPP_END_SECTION) || + !strcmp(name, EMIT_CODE_SECTION) || + !strcmp(name, FIELDS_SECTION) || + !strcmp(name, HEADER_SECTION) || + !strcmp(name, HEADER_END_SECTION) || + !strcmp(name, INITIALIZERS_SECTION) || + !strcmp(name, MAKE_SECTION) || + !strcmp(name, OPTIMIZATION_FLAGS_SECTION) || + !strcmp(name, SAMPLER_PARAMS_SECTION) || + !strcmp(name, SET_DATA_SECTION) || + !strcmp(name, TEST_CODE_SECTION); + } + + static bool SectionAcceptsArgument(const char* name) { + return !strcmp(name, COORD_TRANSFORM_SECTION) || + !strcmp(name, SAMPLER_PARAMS_SECTION) || + !strcmp(name, SET_DATA_SECTION) || + !strcmp(name, TEST_CODE_SECTION); + } + + static bool SectionRequiresArgument(const char* name) { + return !strcmp(name, SAMPLER_PARAMS_SECTION) || + !strcmp(name, SET_DATA_SECTION) || + !strcmp(name, TEST_CODE_SECTION); + } + + static bool SectionPermitsDuplicates(const char* name) { + return !strcmp(name, COORD_TRANSFORM_SECTION) || + !strcmp(name, SAMPLER_PARAMS_SECTION); + } + +private: + bool hasCoordOverrides(const Statement& s, const Variable& fp); + + bool hasCoordOverrides(const Expression& e, const Variable& fp); + + bool hasCoordOverrides(const ProgramElement& p, const Variable& fp); + + const Program& fProgram; + std::vector fParameters; + std::unordered_map> fSections; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLString.cpp b/gfx/skia/skia/src/sksl/SkSLString.cpp new file mode 100644 index 0000000000..88eb1c7d3b --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLString.cpp @@ -0,0 +1,274 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLString.h" + +#include "src/sksl/SkSLUtil.h" +#include +#include +#include +#include +#include +#include + +namespace SkSL { + +String String::printf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + String result; + result.vappendf(fmt, args); + va_end(args); + return result; +} + +void String::appendf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + this->vappendf(fmt, args); + va_end(args); +} + +void String::reset() { + this->clear(); +} + +int String::findLastOf(const char c) const { + // Rely on find_last_of and remap the output + size_t index = this->find_last_of(c); + return (index == std::string::npos ? -1 : index); +} + +void String::vappendf(const char* fmt, va_list args) { +#ifdef SKSL_BUILD_FOR_WIN + #define VSNPRINTF _vsnprintf +#else + #define VSNPRINTF vsnprintf +#endif + #define BUFFER_SIZE 256 + char buffer[BUFFER_SIZE]; + va_list reuse; + va_copy(reuse, args); + size_t size = VSNPRINTF(buffer, BUFFER_SIZE, fmt, args); + if (BUFFER_SIZE >= size) { + this->append(buffer, size); + } else { + auto newBuffer = std::unique_ptr(new char[size + 1]); + VSNPRINTF(newBuffer.get(), size + 1, fmt, reuse); + this->append(newBuffer.get(), size); + } + va_end(reuse); +} + + +bool String::startsWith(const char* s) const { + return !strncmp(c_str(), s, strlen(s)); +} + +bool String::endsWith(const char* s) const { + size_t len = strlen(s); + if (size() < len) { + return false; + } + return !strncmp(c_str() + size() - len, s, len); +} + +int String::find(const String& substring, int fromPos) const { + return find(substring.c_str(), fromPos); +} + +int String::find(const char* substring, int fromPos) const { + SkASSERT(fromPos >= 0); + size_t found = INHERITED::find(substring, (size_t) fromPos); + return found == std::string::npos ? -1 : found; +} + +String String::operator+(const char* s) const { + String result(*this); + result.append(s); + return result; +} + +String String::operator+(const String& s) const { + String result(*this); + result.append(s); + return result; +} + +String String::operator+(StringFragment s) const { + String result(*this); + result.append(s.fChars, s.fLength); + return result; +} + +String& String::operator+=(char c) { + INHERITED::operator+=(c); + return *this; +} + +String& String::operator+=(const char* s) { + INHERITED::operator+=(s); + return *this; +} + +String& String::operator+=(const String& s) { + INHERITED::operator+=(s); + return *this; +} + +String& String::operator+=(StringFragment s) { + this->append(s.fChars, s.fLength); + return *this; +} + +bool String::operator==(const String& s) const { + return this->size() == s.size() && !memcmp(c_str(), s.c_str(), this->size()); +} + +bool String::operator!=(const String& s) const { + return !(*this == s); +} + +bool String::operator==(const char* s) const { + return this->size() == strlen(s) && !memcmp(c_str(), s, this->size()); +} + +bool String::operator!=(const char* s) const { + return !(*this == s); +} + +String operator+(const char* s1, const String& s2) { + String result(s1); + result.append(s2); + return result; +} + +bool operator==(const char* s1, const String& s2) { + return s2 == s1; +} + +bool operator!=(const char* s1, const String& s2) { + return s2 != s1; +} + +bool StringFragment::operator==(StringFragment s) const { + if (fLength != s.fLength) { + return false; + } + return !memcmp(fChars, s.fChars, fLength); +} + +bool StringFragment::operator!=(StringFragment s) const { + if (fLength != s.fLength) { + return true; + } + return memcmp(fChars, s.fChars, fLength); +} + +bool StringFragment::operator==(const char* s) const { + for (size_t i = 0; i < fLength; ++i) { + if (fChars[i] != s[i]) { + return false; + } + } + return 0 == s[fLength]; +} + +bool StringFragment::operator!=(const char* s) const { + for (size_t i = 0; i < fLength; ++i) { + if (fChars[i] != s[i]) { + return true; + } + } + return 0 != s[fLength]; +} + +bool StringFragment::operator<(StringFragment other) const { + int comparison = strncmp(fChars, other.fChars, std::min(fLength, other.fLength)); + if (comparison) { + return comparison < 0; + } + return fLength < other.fLength; +} + +bool operator==(const char* s1, StringFragment s2) { + return s2 == s1; +} + +bool operator!=(const char* s1, StringFragment s2) { + return s2 != s1; +} + +String to_string(int32_t value) { + return SkSL::String::printf("%d", value); +} + +String to_string(uint32_t value) { + return SkSL::String::printf("%u", value); +} + +String to_string(int64_t value) { + std::stringstream buffer; + buffer << value; + return String(buffer.str().c_str()); +} + +String to_string(uint64_t value) { + std::stringstream buffer; + buffer << value; + return String(buffer.str().c_str()); +} + +String to_string(double value) { + std::stringstream buffer; + buffer.imbue(std::locale::classic()); + buffer.precision(17); + buffer << value; + bool needsDotZero = true; + const std::string str = buffer.str(); + for (int i = str.size() - 1; i >= 0; --i) { + char c = str[i]; + if (c == '.' || c == 'e') { + needsDotZero = false; + break; + } + } + if (needsDotZero) { + buffer << ".0"; + } + return String(buffer.str().c_str()); +} + +SKSL_INT stoi(const String& s) { + char* p; + SkDEBUGCODE(errno = 0;) + long result = strtoul(s.c_str(), &p, 0); + SkASSERT(*p == 0); + SkASSERT(!errno); + return result; +} + +SKSL_FLOAT stod(const String& s) { + double result; + std::string str(s.c_str(), s.size()); + std::stringstream buffer(str); + buffer.imbue(std::locale::classic()); + buffer >> result; + SkASSERT(!buffer.fail()); + return result; +} + +long stol(const String& s) { + char* p; + SkDEBUGCODE(errno = 0;) + long result = strtoul(s.c_str(), &p, 0); + SkASSERT(*p == 0); + SkASSERT(!errno); + return result; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLString.h b/gfx/skia/skia/src/sksl/SkSLString.h new file mode 100644 index 0000000000..c60a74877d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLString.h @@ -0,0 +1,153 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_STRING +#define SKSL_STRING + +#include "src/sksl/SkSLDefines.h" +#include +#include +#include + +#ifndef SKSL_STANDALONE +#include "include/core/SkString.h" +#endif + +namespace SkSL { + +// Represents a (not necessarily null-terminated) slice of a string. +struct StringFragment { + StringFragment() + : fChars("") + , fLength(0) {} + + StringFragment(const char* chars) + : fChars(chars) + , fLength(strlen(chars)) {} + + StringFragment(const char* chars, size_t length) + : fChars(chars) + , fLength(length) {} + + char operator[](size_t idx) const { + return fChars[idx]; + } + + bool operator==(const char* s) const; + bool operator!=(const char* s) const; + bool operator==(StringFragment s) const; + bool operator!=(StringFragment s) const; + bool operator<(StringFragment s) const; + +#ifndef SKSL_STANDALONE + operator SkString() const { return SkString(fChars, fLength); } +#endif + + const char* fChars; + size_t fLength; +}; + +bool operator==(const char* s1, StringFragment s2); + +bool operator!=(const char* s1, StringFragment s2); + +class SK_API String : public std::string { +public: + String() = default; + String(const String&) = default; + String(String&&) = default; + String& operator=(const String&) = default; + String& operator=(String&&) = default; + + String(const char* s) + : INHERITED(s) {} + + String(const char* s, size_t size) + : INHERITED(s, size) {} + + String(StringFragment s) + : INHERITED(s.fChars, s.fLength) {} + + static String printf(const char* fmt, ...); + + void appendf(const char* fmt, ...); + // For API compatibility with SkString's reset (vs. std:string's clear) + void reset(); + // For API compatibility with SkString's findLastOf(vs. find_last_of -> size_t) + int findLastOf(const char c) const; + + void vappendf(const char* fmt, va_list va); + + bool startsWith(const char* s) const; + bool endsWith(const char* s) const; + + int find(const char* substring, int fromPos = 0) const; + int find(const String& substring, int fromPos = 0) const; + + String operator+(const char* s) const; + String operator+(const String& s) const; + String operator+(StringFragment s) const; + String& operator+=(char c); + String& operator+=(const char* s); + String& operator+=(const String& s); + String& operator+=(StringFragment s); + bool operator==(const char* s) const; + bool operator!=(const char* s) const; + bool operator==(const String& s) const; + bool operator!=(const String& s) const; + friend String operator+(const char* s1, const String& s2); + friend bool operator==(const char* s1, const String& s2); + friend bool operator!=(const char* s1, const String& s2); + +#ifndef SKSL_STANDALONE + operator SkString() const { return SkString(c_str()); } +#endif + +private: + typedef std::string INHERITED; +}; + +String operator+(const char* s1, const String& s2); +bool operator!=(const char* s1, const String& s2); + +String to_string(double value); + +String to_string(int32_t value); + +String to_string(uint32_t value); + +String to_string(int64_t value); + +String to_string(uint64_t value); + +SKSL_INT stoi(const String& s); + +SKSL_FLOAT stod(const String& s); + +long stol(const String& s); + +} // namespace SkSL + +namespace std { + template<> struct hash { + size_t operator()(const SkSL::StringFragment& s) const { + size_t result = 0; + for (size_t i = 0; i < s.fLength; ++i) { + result = result * 101 + s.fChars[i]; + } + return result; + } + }; + + template<> struct hash { + size_t operator()(const SkSL::String& s) const { + return hash{}(s); + } + }; +} // namespace std + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLStringStream.h b/gfx/skia/skia/src/sksl/SkSLStringStream.h new file mode 100644 index 0000000000..d34d08dd3d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLStringStream.h @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_STRINGSTREAM +#define SKSL_STRINGSTREAM + +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLString.h" + +#ifdef SKSL_STANDALONE + +namespace SkSL { + +class StringStream : public OutputStream { +public: + void write8(uint8_t b) override { + fBuffer += (char) b; + } + + void writeText(const char* s) override { + fBuffer += s; + } + + void write(const void* s, size_t size) override { + fBuffer.append((const char*) s, size); + } + + const String& str() const { + return fBuffer; + } + + void reset() { + fBuffer = ""; + } + +private: + String fBuffer; +}; + +#else + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" + +namespace SkSL { + +class StringStream : public OutputStream { +public: + void write8(uint8_t b) override { + fStream.write8(b); + } + + void writeText(const char* s) override { + fStream.writeText(s); + } + + void write(const void* s, size_t size) override { + fStream.write(s, size); + } + + const String& str() const { + if (!fString.size()) { + sk_sp data = fStream.detachAsData(); + fString = String((const char*) data->data(), data->size()); + } + return fString; + } + + void reset() { + fStream.reset(); + fString = ""; + } + +private: + mutable SkDynamicMemoryWStream fStream; + mutable String fString; +}; + +#endif // SKSL_STANDALONE + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLUtil.cpp b/gfx/skia/skia/src/sksl/SkSLUtil.cpp new file mode 100644 index 0000000000..b2c516245d --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLUtil.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLUtil.h" + +#include "src/sksl/SkSLStringStream.h" + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +namespace SkSL { + +#if defined(SKSL_STANDALONE) || !SK_SUPPORT_GPU +StandaloneShaderCaps standaloneCaps; +#endif + +void sksl_abort() { +#ifdef SKSL_STANDALONE + abort(); +#else + sk_abort_no_print(); + exit(1); +#endif +} + +void write_stringstream(const StringStream& s, OutputStream& out) { + out.write(s.str().c_str(), s.str().size()); +} + +bool is_assignment(Token::Kind op) { + switch (op) { + case Token::EQ: // fall through + case Token::PLUSEQ: // fall through + case Token::MINUSEQ: // fall through + case Token::STAREQ: // fall through + case Token::SLASHEQ: // fall through + case Token::PERCENTEQ: // fall through + case Token::SHLEQ: // fall through + case Token::SHREQ: // fall through + case Token::BITWISEOREQ: // fall through + case Token::BITWISEXOREQ: // fall through + case Token::BITWISEANDEQ: // fall through + case Token::LOGICALOREQ: // fall through + case Token::LOGICALXOREQ: // fall through + case Token::LOGICALANDEQ: + return true; + default: + return false; + } +} + +Token::Kind remove_assignment(Token::Kind op) { + switch (op) { + case Token::PLUSEQ: return Token::PLUS; + case Token::MINUSEQ: return Token::MINUS; + case Token::STAREQ: return Token::STAR; + case Token::SLASHEQ: return Token::SLASH; + case Token::PERCENTEQ: return Token::PERCENT; + case Token::SHLEQ: return Token::SHL; + case Token::SHREQ: return Token::SHR; + case Token::BITWISEOREQ: return Token::BITWISEOR; + case Token::BITWISEXOREQ: return Token::BITWISEXOR; + case Token::BITWISEANDEQ: return Token::BITWISEAND; + case Token::LOGICALOREQ: return Token::LOGICALOR; + case Token::LOGICALXOREQ: return Token::LOGICALXOR; + case Token::LOGICALANDEQ: return Token::LOGICALAND; + default: return op; + } +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/SkSLUtil.h b/gfx/skia/skia/src/sksl/SkSLUtil.h new file mode 100644 index 0000000000..60057b6c09 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLUtil.h @@ -0,0 +1,396 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_UTIL +#define SKSL_UTIL + +#include +#include +#include "stdlib.h" +#include "string.h" +#include "src/sksl/SkSLDefines.h" +#include "src/sksl/SkSLLexer.h" + +#ifndef SKSL_STANDALONE +#include "include/core/SkTypes.h" +#if SK_SUPPORT_GPU +#include "include/gpu/GrContextOptions.h" +#include "src/gpu/GrShaderCaps.h" +#endif // SK_SUPPORT_GPU +#endif // SKSL_STANDALONE + +class GrShaderCaps; + +namespace SkSL { + +class OutputStream; +class StringStream; + +#if defined(SKSL_STANDALONE) || !SK_SUPPORT_GPU + +// we're being compiled standalone, so we don't have access to caps... +enum GrGLSLGeneration { + k110_GrGLSLGeneration, + k130_GrGLSLGeneration, + k140_GrGLSLGeneration, + k150_GrGLSLGeneration, + k330_GrGLSLGeneration, + k400_GrGLSLGeneration, + k420_GrGLSLGeneration, + k310es_GrGLSLGeneration, + k320es_GrGLSLGeneration, +}; + +#define SKSL_CAPS_CLASS StandaloneShaderCaps +class StandaloneShaderCaps { +public: + GrGLSLGeneration generation() const { + return k400_GrGLSLGeneration; + } + + bool atan2ImplementedAsAtanYOverX() const { + return false; + } + + bool canUseMinAndAbsTogether() const { + return true; + } + + bool mustForceNegatedAtanParamToFloat() const { + return false; + } + + bool shaderDerivativeSupport() const { + return true; + } + + bool usesPrecisionModifiers() const { + return true; + } + + bool mustDeclareFragmentShaderOutput() const { + return true; + } + + bool fbFetchSupport() const { + return true; + } + + bool fbFetchNeedsCustomOutput() const { + return false; + } + + bool flatInterpolationSupport() const { + return true; + } + + bool noperspectiveInterpolationSupport() const { + return true; + } + + bool multisampleInterpolationSupport() const { + return true; + } + + bool sampleVariablesSupport() const { + return true; + } + + bool externalTextureSupport() const { + return true; + } + + bool mustDoOpBetweenFloorAndAbs() const { + return false; + } + + bool mustEnableAdvBlendEqs() const { + return false; + } + + bool mustEnableSpecificAdvBlendEqs() const { + return false; + } + + bool canUseAnyFunctionInShader() const { + return false; + } + + bool noDefaultPrecisionForExternalSamplers() const { + return false; + } + + bool floatIs32Bits() const { + return true; + } + + bool integerSupport() const { + return false; + } + + bool builtinFMASupport() const { + return true; + } + + const char* shaderDerivativeExtensionString() const { + return nullptr; + } + + const char* fragCoordConventionsExtensionString() const { + return nullptr; + } + + const char* geometryShaderExtensionString() const { + return nullptr; + } + + const char* gsInvocationsExtensionString() const { + return nullptr; + } + + const char* externalTextureExtensionString() const { + return nullptr; + } + + const char* secondExternalTextureExtensionString() const { + return nullptr; + } + + const char* versionDeclString() const { + return ""; + } + + bool gsInvocationsSupport() const { + return true; + } + + bool canUseFractForNegativeValues() const { + return true; + } + + bool canUseFragCoord() const { + return true; + } + + bool incompleteShortIntPrecision() const { + return false; + } + + bool addAndTrueToLoopCondition() const { + return false; + } + + bool unfoldShortCircuitAsTernary() const { + return false; + } + + bool emulateAbsIntFunction() const { + return false; + } + + bool rewriteDoWhileLoops() const { + return false; + } + + bool removePowWithConstantExponent() const { + return false; + } + + const char* fbFetchColorName() const { + return nullptr; + } +}; + +extern StandaloneShaderCaps standaloneCaps; + +#else + +#define SKSL_CAPS_CLASS GrShaderCaps +// Various sets of caps for use in tests +class ShaderCapsFactory { +public: + static sk_sp Default() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fShaderDerivativeSupport = true; + return result; + } + + static sk_sp Version450Core() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 450 core"; + return result; + } + + static sk_sp Version110() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 110"; + result->fGLSLGeneration = GrGLSLGeneration::k110_GrGLSLGeneration; + return result; + } + + static sk_sp UsesPrecisionModifiers() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fUsesPrecisionModifiers = true; + return result; + } + + static sk_sp CannotUseMinAndAbsTogether() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fCanUseMinAndAbsTogether = false; + return result; + } + + static sk_sp CannotUseFractForNegativeValues() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fCanUseFractForNegativeValues = false; + return result; + } + + static sk_sp MustForceNegatedAtanParamToFloat() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fMustForceNegatedAtanParamToFloat = true; + return result; + } + + static sk_sp ShaderDerivativeExtensionString() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fShaderDerivativeSupport = true; + result->fShaderDerivativeExtensionString = "GL_OES_standard_derivatives"; + result->fUsesPrecisionModifiers = true; + return result; + } + + static sk_sp FragCoordsOld() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 110"; + result->fGLSLGeneration = GrGLSLGeneration::k110_GrGLSLGeneration; + result->fFragCoordConventionsExtensionString = "GL_ARB_fragment_coord_conventions"; + return result; + } + + static sk_sp FragCoordsNew() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fFragCoordConventionsExtensionString = "GL_ARB_fragment_coord_conventions"; + return result; + } + + static sk_sp GeometryShaderSupport() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fGeometryShaderSupport = true; + result->fGSInvocationsSupport = true; + return result; + } + + static sk_sp NoGSInvocationsSupport() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fGeometryShaderSupport = true; + result->fGSInvocationsSupport = false; + return result; + } + + static sk_sp GeometryShaderExtensionString() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 310es"; + result->fGeometryShaderSupport = true; + result->fGeometryShaderExtensionString = "GL_EXT_geometry_shader"; + result->fGSInvocationsSupport = true; + return result; + } + + static sk_sp GSInvocationsExtensionString() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fGeometryShaderSupport = true; + result->fGSInvocationsSupport = true; + result->fGSInvocationsExtensionString = "GL_ARB_gpu_shader5"; + return result; + } + + static sk_sp VariousCaps() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fExternalTextureSupport = true; + result->fFBFetchSupport = false; + result->fCanUseAnyFunctionInShader = false; + return result; + } + + static sk_sp CannotUseFragCoord() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fCanUseFragCoord = false; + return result; + } + + static sk_sp IncompleteShortIntPrecision() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 310es"; + result->fUsesPrecisionModifiers = true; + result->fIncompleteShortIntPrecision = true; + return result; + } + + static sk_sp AddAndTrueToLoopCondition() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fAddAndTrueToLoopCondition = true; + return result; + } + + static sk_sp UnfoldShortCircuitAsTernary() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fUnfoldShortCircuitAsTernary = true; + return result; + } + + static sk_sp EmulateAbsIntFunction() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fEmulateAbsIntFunction = true; + return result; + } + + static sk_sp RewriteDoWhileLoops() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fRewriteDoWhileLoops = true; + return result; + } + + static sk_sp RemovePowWithConstantExponent() { + sk_sp result = sk_make_sp(GrContextOptions()); + result->fVersionDeclString = "#version 400"; + result->fRemovePowWithConstantExponent = true; + return result; + } +}; +#endif + +void write_stringstream(const StringStream& d, OutputStream& out); + +// Returns true if op is '=' or any compound assignment operator ('+=', '-=', etc.) +bool is_assignment(Token::Kind op); + +// Given a compound assignment operator, returns the non-assignment version of the operator (e.g. +// '+=' becomes '+') +Token::Kind remove_assignment(Token::Kind op); + +NORETURN void sksl_abort(); + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h new file mode 100644 index 0000000000..4ab25d2287 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BINARYEXPRESSION +#define SKSL_BINARYEXPRESSION + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A binary operation. + */ +struct BinaryExpression : public Expression { + BinaryExpression(int offset, std::unique_ptr left, Token::Kind op, + std::unique_ptr right, const Type& type) + : INHERITED(offset, kBinary_Kind, type) + , fLeft(std::move(left)) + , fOperator(op) + , fRight(std::move(right)) {} + + std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) override { + return irGenerator.constantFold(*fLeft, + fOperator, + *fRight); + } + + bool hasSideEffects() const override { + return Compiler::IsAssignment(fOperator) || fLeft->hasSideEffects() || + fRight->hasSideEffects(); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new BinaryExpression(fOffset, fLeft->clone(), fOperator, + fRight->clone(), fType)); + } + + String description() const override { + return "(" + fLeft->description() + " " + Compiler::OperatorName(fOperator) + " " + + fRight->description() + ")"; + } + + std::unique_ptr fLeft; + const Token::Kind fOperator; + std::unique_ptr fRight; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBlock.h b/gfx/skia/skia/src/sksl/ir/SkSLBlock.h new file mode 100644 index 0000000000..8a4449a01c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBlock.h @@ -0,0 +1,63 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BLOCK +#define SKSL_BLOCK + +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +namespace SkSL { + +/** + * A block of multiple statements functioning as a single statement. + */ +struct Block : public Statement { + Block(int offset, std::vector> statements, + const std::shared_ptr symbols = nullptr) + : INHERITED(offset, kBlock_Kind) + , fSymbols(std::move(symbols)) + , fStatements(std::move(statements)) {} + + bool isEmpty() const override { + for (const auto& s : fStatements) { + if (!s->isEmpty()) { + return false; + } + } + return true; + } + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& s : fStatements) { + cloned.push_back(s->clone()); + } + return std::unique_ptr(new Block(fOffset, std::move(cloned), fSymbols)); + } + + String description() const override { + String result("{"); + for (size_t i = 0; i < fStatements.size(); i++) { + result += "\n"; + result += fStatements[i]->description(); + } + result += "\n}\n"; + return result; + } + + // it's important to keep fStatements defined after (and thus destroyed before) fSymbols, + // because destroying statements can modify reference counts in symbols + const std::shared_ptr fSymbols; + std::vector> fStatements; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBoolLiteral.h b/gfx/skia/skia/src/sksl/ir/SkSLBoolLiteral.h new file mode 100644 index 0000000000..b99aec89c4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBoolLiteral.h @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BOOLLITERAL +#define SKSL_BOOLLITERAL + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * Represents 'true' or 'false'. + */ +struct BoolLiteral : public Expression { + BoolLiteral(const Context& context, int offset, bool value) + : INHERITED(offset, kBoolLiteral_Kind, *context.fBool_Type) + , fValue(value) {} + + String description() const override { + return String(fValue ? "true" : "false"); + } + + bool hasSideEffects() const override { + return false; + } + + bool isConstant() const override { + return true; + } + + bool compareConstant(const Context& context, const Expression& other) const override { + BoolLiteral& b = (BoolLiteral&) other; + return fValue == b.fValue; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new BoolLiteral(fOffset, fValue, &fType)); + } + + const bool fValue; + + typedef Expression INHERITED; + +private: + BoolLiteral(int offset, bool value, const Type* type) + : INHERITED(offset, kBoolLiteral_Kind, *type) + , fValue(value) {} +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h new file mode 100644 index 0000000000..ae0c1987e9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BREAKSTATEMENT +#define SKSL_BREAKSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A 'break' statement. + */ +struct BreakStatement : public Statement { + BreakStatement(int offset) + : INHERITED(offset, kBreak_Kind) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new BreakStatement(fOffset)); + } + + String description() const override { + return String("break;"); + } + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h new file mode 100644 index 0000000000..8d7a4989da --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h @@ -0,0 +1,223 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR +#define SKSL_CONSTRUCTOR + +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" + +namespace SkSL { + +/** + * Represents the construction of a compound type, such as "float2(x, y)". + * + * Vector constructors will always consist of either exactly 1 scalar, or a collection of vectors + * and scalars totalling exactly the right number of scalar components. + * + * Matrix constructors will always consist of either exactly 1 scalar, exactly 1 matrix, or a + * collection of vectors and scalars totalling exactly the right number of scalar components. + */ +struct Constructor : public Expression { + Constructor(int offset, const Type& type, std::vector> arguments) + : INHERITED(offset, kConstructor_Kind, type) + , fArguments(std::move(arguments)) {} + + std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) override { + if (fArguments.size() == 1 && fArguments[0]->fKind == Expression::kIntLiteral_Kind) { + if (fType.isFloat()) { + // promote float(1) to 1.0 + int64_t intValue = ((IntLiteral&) *fArguments[0]).fValue; + return std::unique_ptr(new FloatLiteral(irGenerator.fContext, + fOffset, + intValue)); + } else if (fType.isInteger()) { + // promote uint(1) to 1u + int64_t intValue = ((IntLiteral&) *fArguments[0]).fValue; + return std::unique_ptr(new IntLiteral(fOffset, + intValue, + &fType)); + } + } + return nullptr; + } + + bool hasSideEffects() const override { + for (const auto& arg : fArguments) { + if (arg->hasSideEffects()) { + return true; + } + } + return false; + } + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& arg : fArguments) { + cloned.push_back(arg->clone()); + } + return std::unique_ptr(new Constructor(fOffset, fType, std::move(cloned))); + } + + String description() const override { + String result = fType.description() + "("; + String separator; + for (size_t i = 0; i < fArguments.size(); i++) { + result += separator; + result += fArguments[i]->description(); + separator = ", "; + } + result += ")"; + return result; + } + + bool isConstant() const override { + for (size_t i = 0; i < fArguments.size(); i++) { + if (!fArguments[i]->isConstant()) { + return false; + } + } + return true; + } + + bool compareConstant(const Context& context, const Expression& other) const override { + SkASSERT(other.fKind == Expression::kConstructor_Kind && other.fType == fType); + Constructor& c = (Constructor&) other; + if (c.fType.kind() == Type::kVector_Kind) { + bool isFloat = c.fType.columns() > 1 ? c.fType.componentType().isFloat() + : c.fType.isFloat(); + for (int i = 0; i < fType.columns(); i++) { + if (isFloat) { + if (this->getFVecComponent(i) != c.getFVecComponent(i)) { + return false; + } + } else if (this->getIVecComponent(i) != c.getIVecComponent(i)) { + return false; + } + } + return true; + } + // shouldn't be possible to have a constant constructor that isn't a vector or matrix; + // a constant scalar constructor should have been collapsed down to the appropriate + // literal + SkASSERT(fType.kind() == Type::kMatrix_Kind); + for (int col = 0; col < fType.columns(); col++) { + for (int row = 0; row < fType.rows(); row++) { + if (getMatComponent(col, row) != c.getMatComponent(col, row)) { + return false; + } + } + } + return true; + } + + template + type getVecComponent(int index) const { + SkASSERT(fType.kind() == Type::kVector_Kind); + if (fArguments.size() == 1 && fArguments[0]->fType.kind() == Type::kScalar_Kind) { + if (std::is_floating_point::value) { + return fArguments[0]->getConstantFloat(); + } else { + return fArguments[0]->getConstantInt(); + } + } + int current = 0; + for (const auto& arg : fArguments) { + SkASSERT(current <= index); + if (arg->fType.kind() == Type::kScalar_Kind) { + if (index == current) { + if (std::is_floating_point::value) { + return arg.get()->getConstantFloat(); + } else { + return arg.get()->getConstantInt(); + } + } + current++; + } else if (arg->fKind == kConstructor_Kind) { + if (current + arg->fType.columns() > index) { + return ((const Constructor&) *arg).getVecComponent(index - current); + } + current += arg->fType.columns(); + } else { + if (current + arg->fType.columns() > index) { + SkASSERT(arg->fKind == kPrefix_Kind); + const PrefixExpression& p = (PrefixExpression&) *arg; + const Constructor& c = (const Constructor&) *p.fOperand; + return -c.getVecComponent(index - current); + } + current += arg->fType.columns(); + } + } + ABORT("failed to find vector component %d in %s\n", index, description().c_str()); + } + + SKSL_FLOAT getFVecComponent(int n) const override { + return this->getVecComponent(n); + } + + /** + * For a literal vector expression, return the integer value of the n'th vector component. It is + * an error to call this method on an expression which is not a literal vector. + */ + SKSL_INT getIVecComponent(int n) const override { + return this->getVecComponent(n); + } + + SKSL_FLOAT getMatComponent(int col, int row) const override { + SkASSERT(this->isConstant()); + SkASSERT(fType.kind() == Type::kMatrix_Kind); + SkASSERT(col < fType.columns() && row < fType.rows()); + if (fArguments.size() == 1) { + if (fArguments[0]->fType.kind() == Type::kScalar_Kind) { + // single scalar argument, so matrix is of the form: + // x 0 0 + // 0 x 0 + // 0 0 x + // return x if col == row + return col == row ? fArguments[0]->getConstantFloat() : 0.0; + } + if (fArguments[0]->fType.kind() == Type::kMatrix_Kind) { + SkASSERT(fArguments[0]->fKind == Expression::kConstructor_Kind); + // single matrix argument. make sure we're within the argument's bounds. + const Type& argType = ((Constructor&) *fArguments[0]).fType; + if (col < argType.columns() && row < argType.rows()) { + // within bounds, defer to argument + return ((Constructor&) *fArguments[0]).getMatComponent(col, row); + } + // out of bounds + return 0.0; + } + } + int currentIndex = 0; + int targetIndex = col * fType.rows() + row; + for (const auto& arg : fArguments) { + SkASSERT(targetIndex >= currentIndex); + SkASSERT(arg->fType.rows() == 1); + if (currentIndex + arg->fType.columns() > targetIndex) { + if (arg->fType.columns() == 1) { + return arg->getConstantFloat(); + } else { + return arg->getFVecComponent(targetIndex - currentIndex); + } + } + currentIndex += arg->fType.columns(); + } + ABORT("can't happen, matrix component out of bounds"); + } + + std::vector> fArguments; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h new file mode 100644 index 0000000000..ecd9c3f497 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONTINUESTATEMENT +#define SKSL_CONTINUESTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A 'continue' statement. + */ +struct ContinueStatement : public Statement { + ContinueStatement(int offset) + : INHERITED(offset, kContinue_Kind) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new ContinueStatement(fOffset)); + } + + String description() const override { + return String("continue;"); + } + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h new file mode 100644 index 0000000000..40f625c178 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DISCARDSTATEMENT +#define SKSL_DISCARDSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A 'discard' statement. + */ +struct DiscardStatement : public Statement { + DiscardStatement(int offset) + : INHERITED(offset, kDiscard_Kind) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new DiscardStatement(fOffset)); + } + + String description() const override { + return String("discard;"); + } + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h new file mode 100644 index 0000000000..5cab5c8bcd --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DOSTATEMENT +#define SKSL_DOSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A 'do' statement. + */ +struct DoStatement : public Statement { + DoStatement(int offset, std::unique_ptr statement, + std::unique_ptr test) + : INHERITED(offset, kDo_Kind) + , fStatement(std::move(statement)) + , fTest(std::move(test)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new DoStatement(fOffset, fStatement->clone(), + fTest->clone())); + } + + String description() const override { + return "do " + fStatement->description() + " while (" + fTest->description() + ");"; + } + + std::unique_ptr fStatement; + std::unique_ptr fTest; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLEnum.h b/gfx/skia/skia/src/sksl/ir/SkSLEnum.h new file mode 100644 index 0000000000..a22db36577 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLEnum.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_ENUM +#define SKSL_ENUM + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include +#include + +namespace SkSL { + +struct Symbol; + +struct Enum : public ProgramElement { + Enum(int offset, StringFragment typeName, std::shared_ptr symbols) + : INHERITED(offset, kEnum_Kind) + , fTypeName(typeName) + , fSymbols(std::move(symbols)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new Enum(fOffset, fTypeName, fSymbols)); + } + + String description() const override { + String result = "enum class " + fTypeName + " {\n"; + String separator; + std::vector sortedSymbols; + for (const auto& pair : *fSymbols) { + sortedSymbols.push_back(pair.second); + } + std::sort(sortedSymbols.begin(), sortedSymbols.end(), + [](const Symbol* a, const Symbol* b) { return a->fName < b->fName; }); + for (const auto& s : sortedSymbols) { + result += separator + " " + s->fName + " = " + + ((Variable*) s)->fInitialValue->description(); + separator = ",\n"; + } + result += "\n};"; + return result; + } + + bool fBuiltin = false; + const StringFragment fTypeName; + const std::shared_ptr fSymbols; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLExpression.h new file mode 100644 index 0000000000..928295dc72 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExpression.h @@ -0,0 +1,149 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXPRESSION +#define SKSL_EXPRESSION + +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include + +namespace SkSL { + +struct Expression; +class IRGenerator; + +typedef std::unordered_map*> DefinitionMap; + +/** + * Abstract supertype of all expressions. + */ +struct Expression : public IRNode { + enum Kind { + kBinary_Kind, + kBoolLiteral_Kind, + kConstructor_Kind, + kExternalFunctionCall_Kind, + kExternalValue_Kind, + kIntLiteral_Kind, + kFieldAccess_Kind, + kFloatLiteral_Kind, + kFunctionReference_Kind, + kFunctionCall_Kind, + kIndex_Kind, + kNullLiteral_Kind, + kPrefix_Kind, + kPostfix_Kind, + kSetting_Kind, + kSwizzle_Kind, + kVariableReference_Kind, + kTernary_Kind, + kTypeReference_Kind, + kDefined_Kind + }; + + Expression(int offset, Kind kind, const Type& type) + : INHERITED(offset) + , fKind(kind) + , fType(std::move(type)) {} + + /** + * Returns true if this expression is constant. compareConstant must be implemented for all + * constants! + */ + virtual bool isConstant() const { + return false; + } + + /** + * Compares this constant expression against another constant expression of the same type. It is + * an error to call this on non-constant expressions, or if the types of the expressions do not + * match. + */ + virtual bool compareConstant(const Context& context, const Expression& other) const { + ABORT("cannot call compareConstant on this type"); + } + + /** + * For an expression which evaluates to a constant int, returns the value. Otherwise calls + * ABORT. + */ + virtual int64_t getConstantInt() const { + ABORT("not a constant int"); + } + + /** + * For an expression which evaluates to a constant float, returns the value. Otherwise calls + * ABORT. + */ + virtual double getConstantFloat() const { + ABORT("not a constant float"); + } + + /** + * Returns true if evaluating the expression potentially has side effects. Expressions may never + * return false if they actually have side effects, but it is legal (though suboptimal) to + * return true if there are not actually any side effects. + */ + virtual bool hasSideEffects() const = 0; + + /** + * Given a map of known constant variable values, substitute them in for references to those + * variables occurring in this expression and its subexpressions. Similar simplifications, such + * as folding a constant binary expression down to a single value, may also be performed. + * Returns a new expression which replaces this expression, or null if no replacements were + * made. If a new expression is returned, this expression is no longer valid. + */ + virtual std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) { + return nullptr; + } + + virtual int coercionCost(const Type& target) const { + return fType.coercionCost(target); + } + + /** + * For a literal vector expression, return the floating point value of the n'th vector + * component. It is an error to call this method on an expression which is not a literal vector. + */ + virtual SKSL_FLOAT getFVecComponent(int n) const { + SkASSERT(false); + return 0; + } + + /** + * For a literal vector expression, return the integer value of the n'th vector component. It is + * an error to call this method on an expression which is not a literal vector. + */ + virtual SKSL_INT getIVecComponent(int n) const { + SkASSERT(false); + return 0; + } + + /** + * For a literal matrix expression, return the floating point value of the component at + * [col][row]. It is an error to call this method on an expression which is not a literal + * matrix. + */ + virtual SKSL_FLOAT getMatComponent(int col, int row) const { + SkASSERT(false); + return 0; + } + + virtual std::unique_ptr clone() const = 0; + + const Kind fKind; + const Type& fType; + + typedef IRNode INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h new file mode 100644 index 0000000000..80a8d316c7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXPRESSIONSTATEMENT +#define SKSL_EXPRESSIONSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A lone expression being used as a statement. + */ +struct ExpressionStatement : public Statement { + ExpressionStatement(std::unique_ptr expression) + : INHERITED(expression->fOffset, kExpression_Kind) + , fExpression(std::move(expression)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new ExpressionStatement(fExpression->clone())); + } + + String description() const override { + return fExpression->description() + ";"; + } + + std::unique_ptr fExpression; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExtension.h b/gfx/skia/skia/src/sksl/ir/SkSLExtension.h new file mode 100644 index 0000000000..5eed97508f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExtension.h @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXTENSION +#define SKSL_EXTENSION + +#include "src/sksl/ir/SkSLProgramElement.h" + +namespace SkSL { + +/** + * An extension declaration. + */ +struct Extension : public ProgramElement { + Extension(int offset, String name) + : INHERITED(offset, kExtension_Kind) + , fName(std::move(name)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new Extension(fOffset, fName)); + } + + String description() const override { + return "#extension " + fName + " : enable"; + } + + const String fName; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExternalFunctionCall.h b/gfx/skia/skia/src/sksl/ir/SkSLExternalFunctionCall.h new file mode 100644 index 0000000000..3c52a10de6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExternalFunctionCall.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXTERNALFUNCTIONCALL +#define SKSL_EXTERNALFUNCTIONCALL + +#include "src/sksl/SkSLExternalValue.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +namespace SkSL { + +/** + * An external function invocation. + */ +struct ExternalFunctionCall : public Expression { + ExternalFunctionCall(int offset, const Type& type, ExternalValue* function, + std::vector> arguments) + : INHERITED(offset, kExternalFunctionCall_Kind, type) + , fFunction(function) + , fArguments(std::move(arguments)) {} + + bool hasSideEffects() const override { + return true; + } + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& arg : fArguments) { + cloned.push_back(arg->clone()); + } + return std::unique_ptr(new ExternalFunctionCall(fOffset, + fType, + fFunction, + std::move(cloned))); + } + + String description() const override { + String result = String(fFunction->fName) + "("; + String separator; + for (size_t i = 0; i < fArguments.size(); i++) { + result += separator; + result += fArguments[i]->description(); + separator = ", "; + } + result += ")"; + return result; + } + + ExternalValue* fFunction; + std::vector> fArguments; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExternalValueReference.h b/gfx/skia/skia/src/sksl/ir/SkSLExternalValueReference.h new file mode 100644 index 0000000000..ee8e81e12a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExternalValueReference.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXTERNALVALUEREFERENCE +#define SKSL_EXTERNALVALUEREFERENCE + +#include "src/sksl/SkSLExternalValue.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * Represents an identifier referring to an ExternalValue. + */ +struct ExternalValueReference : public Expression { + ExternalValueReference(int offset, ExternalValue* ev) + : INHERITED(offset, kExternalValue_Kind, ev->type()) + , fValue(ev) {} + + bool hasSideEffects() const override { + return true; + } + + String description() const override { + return String(fValue->fName); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new ExternalValueReference(fOffset, fValue)); + } + + ExternalValue* fValue; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLField.h b/gfx/skia/skia/src/sksl/ir/SkSLField.h new file mode 100644 index 0000000000..d187a8032b --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLField.h @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FIELD +#define SKSL_FIELD + +#include "src/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLSymbol.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +/** + * A symbol which should be interpreted as a field access. Fields are added to the symboltable + * whenever a bare reference to an identifier should refer to a struct field; in GLSL, this is the + * result of declaring anonymous interface blocks. + */ +struct Field : public Symbol { + Field(int offset, const Variable& owner, int fieldIndex) + : INHERITED(offset, kField_Kind, owner.fType.fields()[fieldIndex].fName) + , fOwner(owner) + , fFieldIndex(fieldIndex) {} + + virtual String description() const override { + return fOwner.description() + "." + fOwner.fType.fields()[fFieldIndex].fName; + } + + const Variable& fOwner; + const int fFieldIndex; + + typedef Symbol INHERITED; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h new file mode 100644 index 0000000000..7576c0a31e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FIELDACCESS +#define SKSL_FIELDACCESS + +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * An expression which extracts a field from a struct, as in 'foo.bar'. + */ +struct FieldAccess : public Expression { + enum OwnerKind { + kDefault_OwnerKind, + // this field access is to a field of an anonymous interface block (and thus, the field name + // is actually in global scope, so only the field name needs to be written in GLSL) + kAnonymousInterfaceBlock_OwnerKind + }; + + FieldAccess(std::unique_ptr base, int fieldIndex, + OwnerKind ownerKind = kDefault_OwnerKind) + : INHERITED(base->fOffset, kFieldAccess_Kind, *base->fType.fields()[fieldIndex].fType) + , fBase(std::move(base)) + , fFieldIndex(fieldIndex) + , fOwnerKind(ownerKind) {} + + bool hasSideEffects() const override { + return fBase->hasSideEffects(); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new FieldAccess(fBase->clone(), fFieldIndex, + fOwnerKind)); + } + + String description() const override { + return fBase->description() + "." + fBase->fType.fields()[fFieldIndex].fName; + } + + std::unique_ptr fBase; + const int fFieldIndex; + const OwnerKind fOwnerKind; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFloatLiteral.h b/gfx/skia/skia/src/sksl/ir/SkSLFloatLiteral.h new file mode 100644 index 0000000000..2ed2b796b1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFloatLiteral.h @@ -0,0 +1,67 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FLOATLITERAL +#define SKSL_FLOATLITERAL + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A literal floating point number. + */ +struct FloatLiteral : public Expression { + FloatLiteral(const Context& context, int offset, double value) + : INHERITED(offset, kFloatLiteral_Kind, *context.fFloatLiteral_Type) + , fValue(value) {} + + FloatLiteral(int offset, double value, const Type* type) + : INHERITED(offset, kFloatLiteral_Kind, *type) + , fValue(value) {} + + String description() const override { + return to_string(fValue); + } + + bool hasSideEffects() const override { + return false; + } + + bool isConstant() const override { + return true; + } + + int coercionCost(const Type& target) const override { + if (target.isFloat()) { + return 0; + } + return INHERITED::coercionCost(target); + } + + bool compareConstant(const Context& context, const Expression& other) const override { + FloatLiteral& f = (FloatLiteral&) other; + return fValue == f.fValue; + } + + double getConstantFloat() const override { + return fValue; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new FloatLiteral(fOffset, fValue, &fType)); + } + + const double fValue; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h new file mode 100644 index 0000000000..4906e192a6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h @@ -0,0 +1,67 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FORSTATEMENT +#define SKSL_FORSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +namespace SkSL { + +/** + * A 'for' statement. + */ +struct ForStatement : public Statement { + ForStatement(int offset, std::unique_ptr initializer, + std::unique_ptr test, std::unique_ptr next, + std::unique_ptr statement, std::shared_ptr symbols) + : INHERITED(offset, kFor_Kind) + , fSymbols(symbols) + , fInitializer(std::move(initializer)) + , fTest(std::move(test)) + , fNext(std::move(next)) + , fStatement(std::move(statement)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new ForStatement(fOffset, fInitializer->clone(), + fTest->clone(), fNext->clone(), + fStatement->clone(), fSymbols)); + } + + String description() const override { + String result("for ("); + if (fInitializer) { + result += fInitializer->description(); + } + result += " "; + if (fTest) { + result += fTest->description(); + } + result += "; "; + if (fNext) { + result += fNext->description(); + } + result += ") " + fStatement->description(); + return result; + } + + // it's important to keep fSymbols defined first (and thus destroyed last) because destroying + // the other fields can update symbol reference counts + const std::shared_ptr fSymbols; + std::unique_ptr fInitializer; + std::unique_ptr fTest; + std::unique_ptr fNext; + std::unique_ptr fStatement; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h new file mode 100644 index 0000000000..90c173f66a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONCALL +#define SKSL_FUNCTIONCALL + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +namespace SkSL { + +/** + * A function invocation. + */ +struct FunctionCall : public Expression { + FunctionCall(int offset, const Type& type, const FunctionDeclaration& function, + std::vector> arguments) + : INHERITED(offset, kFunctionCall_Kind, type) + , fFunction(std::move(function)) + , fArguments(std::move(arguments)) {} + + bool hasSideEffects() const override { + for (const auto& arg : fArguments) { + if (arg->hasSideEffects()) { + return true; + } + } + return fFunction.fModifiers.fFlags & Modifiers::kHasSideEffects_Flag; + } + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& arg : fArguments) { + cloned.push_back(arg->clone()); + } + return std::unique_ptr(new FunctionCall(fOffset, fType, fFunction, + std::move(cloned))); + } + + String description() const override { + String result = String(fFunction.fName) + "("; + String separator; + for (size_t i = 0; i < fArguments.size(); i++) { + result += separator; + result += fArguments[i]->description(); + separator = ", "; + } + result += ")"; + return result; + } + + const FunctionDeclaration& fFunction; + std::vector> fArguments; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h new file mode 100644 index 0000000000..9b6d25e483 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h @@ -0,0 +1,117 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONDECLARATION +#define SKSL_FUNCTIONDECLARATION + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLSymbol.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +/** + * A function declaration (not a definition -- does not contain a body). + */ +struct FunctionDeclaration : public Symbol { + FunctionDeclaration(int offset, Modifiers modifiers, StringFragment name, + std::vector parameters, const Type& returnType) + : INHERITED(offset, kFunctionDeclaration_Kind, std::move(name)) + , fDefined(false) + , fBuiltin(false) + , fModifiers(modifiers) + , fParameters(std::move(parameters)) + , fReturnType(returnType) {} + + String description() const override { + String result = fReturnType.description() + " " + fName + "("; + String separator; + for (auto p : fParameters) { + result += separator; + separator = ", "; + result += p->description(); + } + result += ")"; + return result; + } + + bool matches(const FunctionDeclaration& f) const { + if (fName != f.fName) { + return false; + } + if (fParameters.size() != f.fParameters.size()) { + return false; + } + for (size_t i = 0; i < fParameters.size(); i++) { + if (fParameters[i]->fType != f.fParameters[i]->fType) { + return false; + } + } + return true; + } + + /** + * Determine the effective types of this function's parameters and return value when called with + * the given arguments. This is relevant for functions with generic parameter types, where this + * will collapse the generic types down into specific concrete types. + * + * Returns true if it was able to select a concrete set of types for the generic function, false + * if there is no possible way this can match the argument types. Note that even a true return + * does not guarantee that the function can be successfully called with those arguments, merely + * indicates that an attempt should be made. If false is returned, the state of + * outParameterTypes and outReturnType are undefined. + */ + bool determineFinalTypes(const std::vector>& arguments, + std::vector* outParameterTypes, + const Type** outReturnType) const { + SkASSERT(arguments.size() == fParameters.size()); + int genericIndex = -1; + for (size_t i = 0; i < arguments.size(); i++) { + if (fParameters[i]->fType.kind() == Type::kGeneric_Kind) { + std::vector types = fParameters[i]->fType.coercibleTypes(); + if (genericIndex == -1) { + for (size_t j = 0; j < types.size(); j++) { + if (arguments[i]->fType.canCoerceTo(*types[j])) { + genericIndex = j; + break; + } + } + if (genericIndex == -1) { + return false; + } + } + outParameterTypes->push_back(types[genericIndex]); + } else { + outParameterTypes->push_back(&fParameters[i]->fType); + } + } + if (fReturnType.kind() == Type::kGeneric_Kind) { + if (genericIndex == -1) { + return false; + } + *outReturnType = fReturnType.coercibleTypes()[genericIndex]; + } else { + *outReturnType = &fReturnType; + } + return true; + } + + mutable bool fDefined; + bool fBuiltin; + Modifiers fModifiers; + const std::vector fParameters; + const Type& fReturnType; + + typedef Symbol INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h new file mode 100644 index 0000000000..7344373bdf --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONDEFINITION +#define SKSL_FUNCTIONDEFINITION + +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLProgramElement.h" + +namespace SkSL { + +/** + * A function definition (a declaration plus an associated block of code). + */ +struct FunctionDefinition : public ProgramElement { + FunctionDefinition(int offset, const FunctionDeclaration& declaration, + std::unique_ptr body) + : INHERITED(offset, kFunction_Kind) + , fDeclaration(declaration) + , fBody(std::move(body)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new FunctionDefinition(fOffset, fDeclaration, + fBody->clone())); + } + + String description() const override { + return fDeclaration.description() + " " + fBody->description(); + } + + const FunctionDeclaration& fDeclaration; + std::unique_ptr fBody; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h new file mode 100644 index 0000000000..e78f1cd641 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONREFERENCE +#define SKSL_FUNCTIONREFERENCE + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +namespace SkSL { + +/** + * An identifier referring to a function name. This is an intermediate value: FunctionReferences are + * always eventually replaced by FunctionCalls in valid programs. + */ +struct FunctionReference : public Expression { + FunctionReference(const Context& context, int offset, + std::vector function) + : INHERITED(offset, kFunctionReference_Kind, *context.fInvalid_Type) + , fFunctions(function) {} + + bool hasSideEffects() const override { + return false; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new FunctionReference(fOffset, fFunctions, &fType)); + } + + String description() const override { + return String(""); + } + + const std::vector fFunctions; + + typedef Expression INHERITED; + +private: + FunctionReference(int offset, std::vector function, + const Type* type) + : INHERITED(offset, kFunctionReference_Kind, *type) + , fFunctions(function) {}}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIRNode.h b/gfx/skia/skia/src/sksl/ir/SkSLIRNode.h new file mode 100644 index 0000000000..ca9ea99998 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIRNode.h @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_IRNODE +#define SKSL_IRNODE + +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/SkSLString.h" + +namespace SkSL { + +/** + * Represents a node in the intermediate representation (IR) tree. The IR is a fully-resolved + * version of the program (all types determined, everything validated), ready for code generation. + */ +struct IRNode { + IRNode(int offset) + : fOffset(offset) {} + + virtual ~IRNode() {} + + virtual String description() const = 0; + + // character offset of this element within the program being compiled, for error reporting + // purposes + int fOffset; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h new file mode 100644 index 0000000000..5d0a22b647 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_IFSTATEMENT +#define SKSL_IFSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * An 'if' statement. + */ +struct IfStatement : public Statement { + IfStatement(int offset, bool isStatic, std::unique_ptr test, + std::unique_ptr ifTrue, std::unique_ptr ifFalse) + : INHERITED(offset, kIf_Kind) + , fIsStatic(isStatic) + , fTest(std::move(test)) + , fIfTrue(std::move(ifTrue)) + , fIfFalse(std::move(ifFalse)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new IfStatement(fOffset, fIsStatic, fTest->clone(), + fIfTrue->clone(), fIfFalse ? fIfFalse->clone() : nullptr)); + } + + String description() const override { + String result; + if (fIsStatic) { + result += "@"; + } + result += "if (" + fTest->description() + ") " + fIfTrue->description(); + if (fIfFalse) { + result += " else " + fIfFalse->description(); + } + return result; + } + + bool fIsStatic; + std::unique_ptr fTest; + std::unique_ptr fIfTrue; + // may be null + std::unique_ptr fIfFalse; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h new file mode 100644 index 0000000000..7c5c1290b8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INDEX +#define SKSL_INDEX + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * Given a type, returns the type that will result from extracting an array value from it. + */ +static const Type& index_type(const Context& context, const Type& type) { + if (type.kind() == Type::kMatrix_Kind) { + if (type.componentType() == *context.fFloat_Type) { + switch (type.rows()) { + case 2: return *context.fFloat2_Type; + case 3: return *context.fFloat3_Type; + case 4: return *context.fFloat4_Type; + default: SkASSERT(false); + } + } else if (type.componentType() == *context.fHalf_Type) { + switch (type.rows()) { + case 2: return *context.fHalf2_Type; + case 3: return *context.fHalf3_Type; + case 4: return *context.fHalf4_Type; + default: SkASSERT(false); + } + } else { + SkASSERT(type.componentType() == *context.fDouble_Type); + switch (type.rows()) { + case 2: return *context.fDouble2_Type; + case 3: return *context.fDouble3_Type; + case 4: return *context.fDouble4_Type; + default: SkASSERT(false); + } + } + } + return type.componentType(); +} + +/** + * An expression which extracts a value from an array or matrix, as in 'm[2]'. + */ +struct IndexExpression : public Expression { + IndexExpression(const Context& context, std::unique_ptr base, + std::unique_ptr index) + : INHERITED(base->fOffset, kIndex_Kind, index_type(context, base->fType)) + , fBase(std::move(base)) + , fIndex(std::move(index)) { + SkASSERT(fIndex->fType == *context.fInt_Type || fIndex->fType == *context.fUInt_Type); + } + + bool hasSideEffects() const override { + return fBase->hasSideEffects() || fIndex->hasSideEffects(); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new IndexExpression(fBase->clone(), fIndex->clone(), + &fType)); + } + + String description() const override { + return fBase->description() + "[" + fIndex->description() + "]"; + } + + std::unique_ptr fBase; + std::unique_ptr fIndex; + + typedef Expression INHERITED; + +private: + IndexExpression(std::unique_ptr base, std::unique_ptr index, + const Type* type) + : INHERITED(base->fOffset, kIndex_Kind, *type) + , fBase(std::move(base)) + , fIndex(std::move(index)) {} +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIntLiteral.h b/gfx/skia/skia/src/sksl/ir/SkSLIntLiteral.h new file mode 100644 index 0000000000..a95875c3ea --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIntLiteral.h @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INTLITERAL +#define SKSL_INTLITERAL + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A literal integer. + */ +struct IntLiteral : public Expression { + // FIXME: we will need to revisit this if/when we add full support for both signed and unsigned + // 64-bit integers, but for right now an int64_t will hold every value we care about + IntLiteral(const Context& context, int offset, int64_t value) + : INHERITED(offset, kIntLiteral_Kind, *context.fInt_Type) + , fValue(value) {} + + IntLiteral(int offset, int64_t value, const Type* type = nullptr) + : INHERITED(offset, kIntLiteral_Kind, *type) + , fValue(value) {} + + String description() const override { + return to_string(fValue); + } + + bool hasSideEffects() const override { + return false; + } + + bool isConstant() const override { + return true; + } + + bool compareConstant(const Context& context, const Expression& other) const override { + IntLiteral& i = (IntLiteral&) other; + return fValue == i.fValue; + } + + int coercionCost(const Type& target) const override { + if (target.isSigned() || target.isUnsigned() || target.isFloat()) { + return 0; + } + return INHERITED::coercionCost(target); + } + + int64_t getConstantInt() const override { + return fValue; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new IntLiteral(fOffset, fValue, &fType)); + } + + const int64_t fValue; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h new file mode 100644 index 0000000000..3dfd9fdf29 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h @@ -0,0 +1,83 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INTERFACEBLOCK +#define SKSL_INTERFACEBLOCK + +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +namespace SkSL { + +/** + * An interface block, as in: + * + * out sk_PerVertex { + * layout(builtin=0) float4 sk_Position; + * layout(builtin=1) float sk_PointSize; + * }; + * + * At the IR level, this is represented by a single variable of struct type. + */ +struct InterfaceBlock : public ProgramElement { + InterfaceBlock(int offset, const Variable* var, String typeName, String instanceName, + std::vector> sizes, + std::shared_ptr typeOwner) + : INHERITED(offset, kInterfaceBlock_Kind) + , fVariable(*var) + , fTypeName(std::move(typeName)) + , fInstanceName(std::move(instanceName)) + , fSizes(std::move(sizes)) + , fTypeOwner(typeOwner) {} + + std::unique_ptr clone() const override { + std::vector> sizesClone; + for (const auto& s : fSizes) { + sizesClone.push_back(s->clone()); + } + return std::unique_ptr(new InterfaceBlock(fOffset, &fVariable, fTypeName, + fInstanceName, + std::move(sizesClone), + fTypeOwner)); + } + + String description() const override { + String result = fVariable.fModifiers.description() + fTypeName + " {\n"; + const Type* structType = &fVariable.fType; + while (structType->kind() == Type::kArray_Kind) { + structType = &structType->componentType(); + } + for (const auto& f : structType->fields()) { + result += f.description() + "\n"; + } + result += "}"; + if (fInstanceName.size()) { + result += " " + fInstanceName; + for (const auto& size : fSizes) { + result += "["; + if (size) { + result += size->description(); + } + result += "]"; + } + } + return result + ";"; + } + + const Variable& fVariable; + const String fTypeName; + const String fInstanceName; + std::vector> fSizes; + const std::shared_ptr fTypeOwner; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLayout.h b/gfx/skia/skia/src/sksl/ir/SkSLLayout.h new file mode 100644 index 0000000000..143aff3d8f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLLayout.h @@ -0,0 +1,432 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_LAYOUT +#define SKSL_LAYOUT + +#include "src/sksl/SkSLString.h" +#include "src/sksl/SkSLUtil.h" + +namespace SkSL { + +/** + * Represents a layout block appearing before a variable declaration, as in: + * + * layout (location = 0) int x; + */ +struct Layout { + enum Flag { + kOriginUpperLeft_Flag = 1 << 0, + kOverrideCoverage_Flag = 1 << 1, + kPushConstant_Flag = 1 << 2, + kBlendSupportAllEquations_Flag = 1 << 3, + kBlendSupportMultiply_Flag = 1 << 4, + kBlendSupportScreen_Flag = 1 << 5, + kBlendSupportOverlay_Flag = 1 << 6, + kBlendSupportDarken_Flag = 1 << 7, + kBlendSupportLighten_Flag = 1 << 8, + kBlendSupportColorDodge_Flag = 1 << 9, + kBlendSupportColorBurn_Flag = 1 << 10, + kBlendSupportHardLight_Flag = 1 << 11, + kBlendSupportSoftLight_Flag = 1 << 12, + kBlendSupportDifference_Flag = 1 << 13, + kBlendSupportExclusion_Flag = 1 << 14, + kBlendSupportHSLHue_Flag = 1 << 15, + kBlendSupportHSLSaturation_Flag = 1 << 16, + kBlendSupportHSLColor_Flag = 1 << 17, + kBlendSupportHSLLuminosity_Flag = 1 << 18, + kTracked_Flag = 1 << 19 + }; + + enum Primitive { + kUnspecified_Primitive = -1, + kPoints_Primitive, + kLines_Primitive, + kLineStrip_Primitive, + kLinesAdjacency_Primitive, + kTriangles_Primitive, + kTriangleStrip_Primitive, + kTrianglesAdjacency_Primitive + }; + + // These are used by images in GLSL. We only support a subset of what GL supports. + enum class Format { + kUnspecified = -1, + kRGBA32F, + kR32F, + kRGBA16F, + kR16F, + kLUMINANCE16F, + kRGBA8, + kR8, + kRGBA8I, + kR8I, + kRG16F, + }; + + // used by SkSL processors + enum Key { + // field is not a key + kNo_Key, + // field is a key + kKey_Key, + // key is 0 or 1 depending on whether the matrix is an identity matrix + kIdentity_Key, + }; + + enum class CType { + kDefault, + kBool, + kFloat, + kInt32, + kSkRect, + kSkIRect, + kSkPMColor4f, + kSkPMColor, + kSkVector4, + kSkPoint, + kSkIPoint, + kSkMatrix, + kSkMatrix44, + kGrTextureProxy, + kGrFragmentProcessor, + }; + + static const char* FormatToStr(Format format) { + switch (format) { + case Format::kUnspecified: return ""; + case Format::kRGBA32F: return "rgba32f"; + case Format::kR32F: return "r32f"; + case Format::kRGBA16F: return "rgba16f"; + case Format::kR16F: return "r16f"; + case Format::kLUMINANCE16F: return "lum16f"; + case Format::kRGBA8: return "rgba8"; + case Format::kR8: return "r8"; + case Format::kRGBA8I: return "rgba8i"; + case Format::kR8I: return "r8i"; + case Format::kRG16F: return "rg16f"; + } + ABORT("Unexpected format"); + } + + static bool ReadFormat(String str, Format* format) { + if (str == "rgba32f") { + *format = Format::kRGBA32F; + return true; + } else if (str == "r32f") { + *format = Format::kR32F; + return true; + } else if (str == "rgba16f") { + *format = Format::kRGBA16F; + return true; + } else if (str == "r16f") { + *format = Format::kR16F; + return true; + } else if (str == "lum16f") { + *format = Format::kLUMINANCE16F; + return true; + } else if (str == "rgba8") { + *format = Format::kRGBA8; + return true; + } else if (str == "r8") { + *format = Format::kR8; + return true; + } else if (str == "rgba8i") { + *format = Format::kRGBA8I; + return true; + } else if (str == "r8i") { + *format = Format::kR8I; + return true; + } else if (str == "rg16f") { + *format = Format::kRG16F; + return true; + } + return false; + } + + static const char* CTypeToStr(CType ctype) { + switch (ctype) { + case CType::kDefault: + return nullptr; + case CType::kFloat: + return "float"; + case CType::kInt32: + return "int32_t"; + case CType::kSkRect: + return "SkRect"; + case CType::kSkIRect: + return "SkIRect"; + case CType::kSkPMColor4f: + return "SkPMColor4f"; + case CType::kSkPMColor: + return "SkPMColor"; + case CType::kSkVector4: + return "SkVector4"; + case CType::kSkPoint: + return "SkPoint"; + case CType::kSkIPoint: + return "SkIPoint"; + case CType::kSkMatrix: + return "SkMatrix"; + case CType::kSkMatrix44: + return "SkMatrix44"; + case CType::kGrTextureProxy: + return "sk_sp"; + case CType::kGrFragmentProcessor: + return "std::unique_ptr"; + default: + SkASSERT(false); + return nullptr; + } + } + + Layout(int flags, int location, int offset, int binding, int index, int set, int builtin, + int inputAttachmentIndex, Format format, Primitive primitive, int maxVertices, + int invocations, StringFragment when, Key key, CType ctype) + : fFlags(flags) + , fLocation(location) + , fOffset(offset) + , fBinding(binding) + , fIndex(index) + , fSet(set) + , fBuiltin(builtin) + , fInputAttachmentIndex(inputAttachmentIndex) + , fFormat(format) + , fPrimitive(primitive) + , fMaxVertices(maxVertices) + , fInvocations(invocations) + , fWhen(when) + , fKey(key) + , fCType(ctype) {} + + Layout() + : fFlags(0) + , fLocation(-1) + , fOffset(-1) + , fBinding(-1) + , fIndex(-1) + , fSet(-1) + , fBuiltin(-1) + , fInputAttachmentIndex(-1) + , fFormat(Format::kUnspecified) + , fPrimitive(kUnspecified_Primitive) + , fMaxVertices(-1) + , fInvocations(-1) + , fKey(kNo_Key) + , fCType(CType::kDefault) {} + + String description() const { + String result; + String separator; + if (fLocation >= 0) { + result += separator + "location = " + to_string(fLocation); + separator = ", "; + } + if (fOffset >= 0) { + result += separator + "offset = " + to_string(fOffset); + separator = ", "; + } + if (fBinding >= 0) { + result += separator + "binding = " + to_string(fBinding); + separator = ", "; + } + if (fIndex >= 0) { + result += separator + "index = " + to_string(fIndex); + separator = ", "; + } + if (fSet >= 0) { + result += separator + "set = " + to_string(fSet); + separator = ", "; + } + if (fBuiltin >= 0) { + result += separator + "builtin = " + to_string(fBuiltin); + separator = ", "; + } + if (fInputAttachmentIndex >= 0) { + result += separator + "input_attachment_index = " + to_string(fInputAttachmentIndex); + separator = ", "; + } + if (Format::kUnspecified != fFormat) { + result += separator + FormatToStr(fFormat); + separator = ", "; + } + if (fFlags & kOriginUpperLeft_Flag) { + result += separator + "origin_upper_left"; + separator = ", "; + } + if (fFlags & kOverrideCoverage_Flag) { + result += separator + "override_coverage"; + separator = ", "; + } + if (fFlags & kBlendSupportAllEquations_Flag) { + result += separator + "blend_support_all_equations"; + separator = ", "; + } + if (fFlags & kBlendSupportMultiply_Flag) { + result += separator + "blend_support_multiply"; + separator = ", "; + } + if (fFlags & kBlendSupportScreen_Flag) { + result += separator + "blend_support_screen"; + separator = ", "; + } + if (fFlags & kBlendSupportOverlay_Flag) { + result += separator + "blend_support_overlay"; + separator = ", "; + } + if (fFlags & kBlendSupportDarken_Flag) { + result += separator + "blend_support_darken"; + separator = ", "; + } + if (fFlags & kBlendSupportLighten_Flag) { + result += separator + "blend_support_lighten"; + separator = ", "; + } + if (fFlags & kBlendSupportColorDodge_Flag) { + result += separator + "blend_support_colordodge"; + separator = ", "; + } + if (fFlags & kBlendSupportColorBurn_Flag) { + result += separator + "blend_support_colorburn"; + separator = ", "; + } + if (fFlags & kBlendSupportHardLight_Flag) { + result += separator + "blend_support_hardlight"; + separator = ", "; + } + if (fFlags & kBlendSupportSoftLight_Flag) { + result += separator + "blend_support_softlight"; + separator = ", "; + } + if (fFlags & kBlendSupportDifference_Flag) { + result += separator + "blend_support_difference"; + separator = ", "; + } + if (fFlags & kBlendSupportExclusion_Flag) { + result += separator + "blend_support_exclusion"; + separator = ", "; + } + if (fFlags & kBlendSupportHSLHue_Flag) { + result += separator + "blend_support_hsl_hue"; + separator = ", "; + } + if (fFlags & kBlendSupportHSLSaturation_Flag) { + result += separator + "blend_support_hsl_saturation"; + separator = ", "; + } + if (fFlags & kBlendSupportHSLColor_Flag) { + result += separator + "blend_support_hsl_color"; + separator = ", "; + } + if (fFlags & kBlendSupportHSLLuminosity_Flag) { + result += separator + "blend_support_hsl_luminosity"; + separator = ", "; + } + if (fFlags & kPushConstant_Flag) { + result += separator + "push_constant"; + separator = ", "; + } + if (fFlags & kTracked_Flag) { + result += separator + "tracked"; + separator = ", "; + } + switch (fPrimitive) { + case kPoints_Primitive: + result += separator + "points"; + separator = ", "; + break; + case kLines_Primitive: + result += separator + "lines"; + separator = ", "; + break; + case kLineStrip_Primitive: + result += separator + "line_strip"; + separator = ", "; + break; + case kLinesAdjacency_Primitive: + result += separator + "lines_adjacency"; + separator = ", "; + break; + case kTriangles_Primitive: + result += separator + "triangles"; + separator = ", "; + break; + case kTriangleStrip_Primitive: + result += separator + "triangle_strip"; + separator = ", "; + break; + case kTrianglesAdjacency_Primitive: + result += separator + "triangles_adjacency"; + separator = ", "; + break; + case kUnspecified_Primitive: + break; + } + if (fMaxVertices >= 0) { + result += separator + "max_vertices = " + to_string(fMaxVertices); + separator = ", "; + } + if (fInvocations >= 0) { + result += separator + "invocations = " + to_string(fInvocations); + separator = ", "; + } + if (fWhen.fLength) { + result += separator + "when = " + fWhen; + separator = ", "; + } + if (result.size() > 0) { + result = "layout (" + result + ")"; + } + if (fKey) { + result += "/* key */"; + } + return result; + } + + bool operator==(const Layout& other) const { + return fFlags == other.fFlags && + fLocation == other.fLocation && + fOffset == other.fOffset && + fBinding == other.fBinding && + fIndex == other.fIndex && + fSet == other.fSet && + fBuiltin == other.fBuiltin && + fInputAttachmentIndex == other.fInputAttachmentIndex && + fFormat == other.fFormat && + fPrimitive == other.fPrimitive && + fMaxVertices == other.fMaxVertices && + fInvocations == other.fInvocations; + } + + bool operator!=(const Layout& other) const { + return !(*this == other); + } + + int fFlags; + int fLocation; + int fOffset; + int fBinding; + int fIndex; + int fSet; + // builtin comes from SPIR-V and identifies which particular builtin value this object + // represents. + int fBuiltin; + // input_attachment_index comes from Vulkan/SPIR-V to connect a shader variable to the a + // corresponding attachment on the subpass in which the shader is being used. + int fInputAttachmentIndex; + Format fFormat; + Primitive fPrimitive; + int fMaxVertices; + int fInvocations; + StringFragment fWhen; + Key fKey; + CType fCType; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLModifiers.h b/gfx/skia/skia/src/sksl/ir/SkSLModifiers.h new file mode 100644 index 0000000000..171ea82387 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLModifiers.h @@ -0,0 +1,116 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MODIFIERS +#define SKSL_MODIFIERS + +#include "src/sksl/ir/SkSLLayout.h" + +namespace SkSL { + +/** + * A set of modifier keywords (in, out, uniform, etc.) appearing before a declaration. + */ +struct Modifiers { + enum Flag { + kNo_Flag = 0, + kConst_Flag = 1 << 0, + kIn_Flag = 1 << 1, + kOut_Flag = 1 << 2, + kUniform_Flag = 1 << 3, + kFlat_Flag = 1 << 4, + kNoPerspective_Flag = 1 << 5, + kReadOnly_Flag = 1 << 6, + kWriteOnly_Flag = 1 << 7, + kCoherent_Flag = 1 << 8, + kVolatile_Flag = 1 << 9, + kRestrict_Flag = 1 << 10, + kBuffer_Flag = 1 << 11, + kHasSideEffects_Flag = 1 << 12, + kPLS_Flag = 1 << 13, + kPLSIn_Flag = 1 << 14, + kPLSOut_Flag = 1 << 15, + }; + + Modifiers() + : fLayout(Layout()) + , fFlags(0) {} + + Modifiers(const Layout& layout, int flags) + : fLayout(layout) + , fFlags(flags) {} + + String description() const { + String result = fLayout.description(); + if (fFlags & kUniform_Flag) { + result += "uniform "; + } + if (fFlags & kConst_Flag) { + result += "const "; + } + if (fFlags & kFlat_Flag) { + result += "flat "; + } + if (fFlags & kNoPerspective_Flag) { + result += "noperspective "; + } + if (fFlags & kReadOnly_Flag) { + result += "readonly "; + } + if (fFlags & kWriteOnly_Flag) { + result += "writeonly "; + } + if (fFlags & kCoherent_Flag) { + result += "coherent "; + } + if (fFlags & kVolatile_Flag) { + result += "volatile "; + } + if (fFlags & kRestrict_Flag) { + result += "restrict "; + } + if (fFlags & kBuffer_Flag) { + result += "buffer "; + } + if (fFlags & kHasSideEffects_Flag) { + result += "sk_has_side_effects "; + } + if (fFlags & kPLS_Flag) { + result += "__pixel_localEXT "; + } + if (fFlags & kPLSIn_Flag) { + result += "__pixel_local_inEXT "; + } + if (fFlags & kPLSOut_Flag) { + result += "__pixel_local_outEXT "; + } + if ((fFlags & kIn_Flag) && (fFlags & kOut_Flag)) { + result += "inout "; + } else if (fFlags & kIn_Flag) { + result += "in "; + } else if (fFlags & kOut_Flag) { + result += "out "; + } + + return result; + } + + bool operator==(const Modifiers& other) const { + return fLayout == other.fLayout && fFlags == other.fFlags; + } + + bool operator!=(const Modifiers& other) const { + return !(*this == other); + } + + Layout fLayout; + int fFlags; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h b/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h new file mode 100644 index 0000000000..1dc74149e6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MODIFIERDECLARATION +#define SKSL_MODIFIERDECLARATION + +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLProgramElement.h" + +namespace SkSL { + +/** + * A declaration that consists only of modifiers, e.g.: + * + * layout(blend_support_all_equations) out; + */ +struct ModifiersDeclaration : public ProgramElement { + ModifiersDeclaration(Modifiers modifiers) + : INHERITED(-1, kModifiers_Kind) + , fModifiers(modifiers) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new ModifiersDeclaration(fModifiers)); + } + + String description() const override { + return fModifiers.description() + ";"; + } + + Modifiers fModifiers; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLNop.h b/gfx/skia/skia/src/sksl/ir/SkSLNop.h new file mode 100644 index 0000000000..2ead371f87 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLNop.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NOP +#define SKSL_NOP + +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +namespace SkSL { + +/** + * A no-op statement that does nothing. + */ +struct Nop : public Statement { + Nop() + : INHERITED(-1, kNop_Kind) {} + + virtual bool isEmpty() const override { + return true; + } + + String description() const override { + return String(";"); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new Nop()); + } + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLNullLiteral.h b/gfx/skia/skia/src/sksl/ir/SkSLNullLiteral.h new file mode 100644 index 0000000000..2d8816d6a2 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLNullLiteral.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NULLLITERAL +#define SKSL_NULLLITERAL + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * Represents 'null'. + */ +struct NullLiteral : public Expression { + NullLiteral(const Context& context, int offset) + : INHERITED(offset, kNullLiteral_Kind, *context.fNull_Type) {} + + NullLiteral(int offset, const Type& type) + : INHERITED(offset, kNullLiteral_Kind, type) {} + + String description() const override { + return "null"; + } + + bool hasSideEffects() const override { + return false; + } + + bool isConstant() const override { + return true; + } + + bool compareConstant(const Context& context, const Expression& other) const override { + return true; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new NullLiteral(fOffset, fType)); + } + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h new file mode 100644 index 0000000000..c11e2085e4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_POSTFIXEXPRESSION +#define SKSL_POSTFIXEXPRESSION + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * An expression modified by a unary operator appearing after it, such as 'i++'. + */ +struct PostfixExpression : public Expression { + PostfixExpression(std::unique_ptr operand, Token::Kind op) + : INHERITED(operand->fOffset, kPostfix_Kind, operand->fType) + , fOperand(std::move(operand)) + , fOperator(op) {} + + bool hasSideEffects() const override { + return true; + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new PostfixExpression(fOperand->clone(), fOperator)); + } + + String description() const override { + return fOperand->description() + Compiler::OperatorName(fOperator); + } + + std::unique_ptr fOperand; + const Token::Kind fOperator; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h new file mode 100644 index 0000000000..408f1d0864 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PREFIXEXPRESSION +#define SKSL_PREFIXEXPRESSION + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" + +namespace SkSL { + +/** + * An expression modified by a unary operator appearing before it, such as '!flag'. + */ +struct PrefixExpression : public Expression { + PrefixExpression(Token::Kind op, std::unique_ptr operand) + : INHERITED(operand->fOffset, kPrefix_Kind, operand->fType) + , fOperand(std::move(operand)) + , fOperator(op) {} + + bool isConstant() const override { + return fOperator == Token::MINUS && fOperand->isConstant(); + } + + bool hasSideEffects() const override { + return fOperator == Token::PLUSPLUS || fOperator == Token::MINUSMINUS || + fOperand->hasSideEffects(); + } + + std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) override { + if (fOperand->fKind == Expression::kFloatLiteral_Kind) { + return std::unique_ptr(new FloatLiteral( + irGenerator.fContext, + fOffset, + -((FloatLiteral&) *fOperand).fValue)); + + } + return nullptr; + } + + SKSL_FLOAT getFVecComponent(int index) const override { + SkASSERT(fOperator == Token::Kind::MINUS); + return -fOperand->getFVecComponent(index); + } + + SKSL_INT getIVecComponent(int index) const override { + SkASSERT(fOperator == Token::Kind::MINUS); + return -fOperand->getIVecComponent(index); + } + + SKSL_FLOAT getMatComponent(int col, int row) const override { + SkASSERT(fOperator == Token::Kind::MINUS); + return -fOperand->getMatComponent(col, row); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new PrefixExpression(fOperator, fOperand->clone())); + } + + String description() const override { + return Compiler::OperatorName(fOperator) + fOperand->description(); + } + + std::unique_ptr fOperand; + const Token::Kind fOperator; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLProgram.h b/gfx/skia/skia/src/sksl/ir/SkSLProgram.h new file mode 100644 index 0000000000..c62bd0b11d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLProgram.h @@ -0,0 +1,298 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PROGRAM +#define SKSL_PROGRAM + +#include +#include + +#include "src/sksl/ir/SkSLBoolLiteral.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLIntLiteral.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#ifdef SK_VULKAN +#include "src/gpu/vk/GrVkCaps.h" +#endif + +// name of the render target width uniform +#define SKSL_RTWIDTH_NAME "u_skRTWidth" + +// name of the render target height uniform +#define SKSL_RTHEIGHT_NAME "u_skRTHeight" + +namespace SkSL { + +class Context; + +/** + * Represents a fully-digested program, ready for code generation. + */ +struct Program { + struct Settings { + struct Value { + Value(bool b) + : fKind(kBool_Kind) + , fValue(b) {} + + Value(int i) + : fKind(kInt_Kind) + , fValue(i) {} + + Value(unsigned int i) + : fKind(kInt_Kind) + , fValue(i) {} + + Value(float f) + : fKind(kFloat_Kind) + , fValue(f) {} + + std::unique_ptr literal(const Context& context, int offset) const { + switch (fKind) { + case Program::Settings::Value::kBool_Kind: + return std::unique_ptr(new BoolLiteral(context, + offset, + fValue)); + case Program::Settings::Value::kInt_Kind: + return std::unique_ptr(new IntLiteral(context, + offset, + fValue)); + case Program::Settings::Value::kFloat_Kind: + return std::unique_ptr(new FloatLiteral(context, + offset, + fValue)); + default: + SkASSERT(false); + return nullptr; + } + } + + enum { + kBool_Kind, + kInt_Kind, + kFloat_Kind, + } fKind; + + int fValue; + }; + +#if defined(SKSL_STANDALONE) || !SK_SUPPORT_GPU + const StandaloneShaderCaps* fCaps = &standaloneCaps; +#else + const GrShaderCaps* fCaps = nullptr; +#ifdef SK_VULKAN + const GrVkCaps* fVkCaps = nullptr; +#endif +#endif + // if false, sk_FragCoord is exactly the same as gl_FragCoord. If true, the y coordinate + // must be flipped. + bool fFlipY = false; + // If true the destination fragment color is read sk_FragColor. It must be declared inout. + bool fFragColorIsInOut = false; + // if true, Setting objects (e.g. sk_Caps.fbFetchSupport) should be replaced with their + // constant equivalents during compilation + bool fReplaceSettings = true; + // if true, all halfs are forced to be floats + bool fForceHighPrecision = false; + // if true, add -0.5 bias to LOD of all texture lookups + bool fSharpenTextures = false; + // if the program needs to create an RTHeight uniform, this is its offset in the uniform + // buffer + int fRTHeightOffset = -1; + std::unordered_map fArgs; + }; + + struct Inputs { + // if true, this program requires the render target width uniform to be defined + bool fRTWidth; + + // if true, this program requires the render target height uniform to be defined + bool fRTHeight; + + // if true, this program must be recompiled if the flipY setting changes. If false, the + // program will compile to the same code regardless of the flipY setting. + bool fFlipY; + + void reset() { + fRTWidth = false; + fRTHeight = false; + fFlipY = false; + } + + bool isEmpty() { + return !fRTWidth && !fRTHeight && !fFlipY; + } + }; + + class iterator { + public: + ProgramElement& operator*() { + if (fIter1 != fEnd1) { + return **fIter1; + } + return **fIter2; + } + + iterator& operator++() { + if (fIter1 != fEnd1) { + ++fIter1; + return *this; + } + ++fIter2; + return *this; + } + + bool operator==(const iterator& other) const { + return fIter1 == other.fIter1 && fIter2 == other.fIter2; + } + + bool operator!=(const iterator& other) const { + return !(*this == other); + } + + private: + using inner = std::vector>::iterator; + + iterator(inner begin1, inner end1, inner begin2, inner end2) + : fIter1(begin1) + , fEnd1(end1) + , fIter2(begin2) + , fEnd2(end2) {} + + inner fIter1; + inner fEnd1; + inner fIter2; + inner fEnd2; + + friend struct Program; + }; + + class const_iterator { + public: + const ProgramElement& operator*() { + if (fIter1 != fEnd1) { + return **fIter1; + } + return **fIter2; + } + + const_iterator& operator++() { + if (fIter1 != fEnd1) { + ++fIter1; + return *this; + } + ++fIter2; + return *this; + } + + bool operator==(const const_iterator& other) const { + return fIter1 == other.fIter1 && fIter2 == other.fIter2; + } + + bool operator!=(const const_iterator& other) const { + return !(*this == other); + } + + private: + using inner = std::vector>::const_iterator; + + const_iterator(inner begin1, inner end1, inner begin2, inner end2) + : fIter1(begin1) + , fEnd1(end1) + , fIter2(begin2) + , fEnd2(end2) {} + + inner fIter1; + inner fEnd1; + inner fIter2; + inner fEnd2; + + friend struct Program; + }; + + enum Kind { + kFragment_Kind, + kVertex_Kind, + kGeometry_Kind, + kFragmentProcessor_Kind, + kPipelineStage_Kind, + kGeneric_Kind, + }; + + Program(Kind kind, + std::unique_ptr source, + Settings settings, + std::shared_ptr context, + std::vector>* inheritedElements, + std::vector> elements, + std::shared_ptr symbols, + Inputs inputs) + : fKind(kind) + , fSource(std::move(source)) + , fSettings(settings) + , fContext(context) + , fSymbols(symbols) + , fInputs(inputs) + , fInheritedElements(inheritedElements) + , fElements(std::move(elements)) {} + + iterator begin() { + if (fInheritedElements) { + return iterator(fInheritedElements->begin(), fInheritedElements->end(), + fElements.begin(), fElements.end()); + } + return iterator(fElements.begin(), fElements.end(), fElements.end(), fElements.end()); + } + + iterator end() { + if (fInheritedElements) { + return iterator(fInheritedElements->end(), fInheritedElements->end(), + fElements.end(), fElements.end()); + } + return iterator(fElements.end(), fElements.end(), fElements.end(), fElements.end()); + } + + const_iterator begin() const { + if (fInheritedElements) { + return const_iterator(fInheritedElements->begin(), fInheritedElements->end(), + fElements.begin(), fElements.end()); + } + return const_iterator(fElements.begin(), fElements.end(), fElements.end(), fElements.end()); + } + + const_iterator end() const { + if (fInheritedElements) { + return const_iterator(fInheritedElements->end(), fInheritedElements->end(), + fElements.end(), fElements.end()); + } + return const_iterator(fElements.end(), fElements.end(), fElements.end(), fElements.end()); + } + + Kind fKind; + std::unique_ptr fSource; + Settings fSettings; + std::shared_ptr fContext; + // it's important to keep fElements defined after (and thus destroyed before) fSymbols, + // because destroying elements can modify reference counts in symbols + std::shared_ptr fSymbols; + Inputs fInputs; + bool fIsOptimized = false; + +private: + std::vector>* fInheritedElements; + std::vector> fElements; + + friend class Compiler; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLProgramElement.h b/gfx/skia/skia/src/sksl/ir/SkSLProgramElement.h new file mode 100644 index 0000000000..4caaebe97a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLProgramElement.h @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PROGRAMELEMENT +#define SKSL_PROGRAMELEMENT + +#include "src/sksl/ir/SkSLIRNode.h" + +#include + +namespace SkSL { + +/** + * Represents a top-level element (e.g. function or global variable) in a program. + */ +struct ProgramElement : public IRNode { + enum Kind { + kEnum_Kind, + kExtension_Kind, + kFunction_Kind, + kInterfaceBlock_Kind, + kModifiers_Kind, + kSection_Kind, + kVar_Kind + }; + + ProgramElement(int offset, Kind kind) + : INHERITED(offset) + , fKind(kind) {} + + Kind fKind; + + virtual std::unique_ptr clone() const = 0; + + typedef IRNode INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h new file mode 100644 index 0000000000..e61fa36c74 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_RETURNSTATEMENT +#define SKSL_RETURNSTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A 'return' statement. + */ +struct ReturnStatement : public Statement { + ReturnStatement(int offset) + : INHERITED(offset, kReturn_Kind) {} + + ReturnStatement(std::unique_ptr expression) + : INHERITED(expression->fOffset, kReturn_Kind) + , fExpression(std::move(expression)) {} + + std::unique_ptr clone() const override { + if (fExpression) { + return std::unique_ptr(new ReturnStatement(fExpression->clone())); + } + return std::unique_ptr(new ReturnStatement(fOffset)); + } + + String description() const override { + if (fExpression) { + return "return " + fExpression->description() + ";"; + } else { + return String("return;"); + } + } + + std::unique_ptr fExpression; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSection.h b/gfx/skia/skia/src/sksl/ir/SkSLSection.h new file mode 100644 index 0000000000..20856127ca --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSection.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SECTION +#define SKSL_SECTION + +#include "src/sksl/ir/SkSLProgramElement.h" + +namespace SkSL { + +/** + * A section declaration (e.g. @body { body code here }).. + */ +struct Section : public ProgramElement { + Section(int offset, String name, String arg, String text) + : INHERITED(offset, kSection_Kind) + , fName(std::move(name)) + , fArgument(std::move(arg)) + , fText(std::move(text)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new Section(fOffset, fName, fArgument, fText)); + } + + String description() const override { + String result = "@" + fName; + if (fArgument.size()) { + result += "(" + fArgument + ")"; + } + result += " { " + fText + " }"; + return result; + } + + const String fName; + const String fArgument; + const String fText; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp new file mode 100644 index 0000000000..1cb0d4afd3 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +std::unique_ptr Setting::constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) { + if (irGenerator.fSettings->fReplaceSettings) { + return VariableReference::copy_constant(irGenerator, fValue.get()); + } + return nullptr; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSetting.h b/gfx/skia/skia/src/sksl/ir/SkSLSetting.h new file mode 100644 index 0000000000..33c8a31acc --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSetting.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SETTING +#define SKSL_SETTING + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * Represents a compile-time constant setting, such as sk_Caps.fbFetchSupport. These are generally + * collapsed down to their constant representations during the compilation process. + */ +struct Setting : public Expression { + Setting(int offset, String name, std::unique_ptr value) + : INHERITED(offset, kSetting_Kind, value->fType) + , fName(std::move(name)) + , fValue(std::move(value)) { + SkASSERT(fValue->isConstant()); + } + + std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) override; + + std::unique_ptr clone() const override { + return std::unique_ptr(new Setting(fOffset, fName, fValue->clone())); + } + + String description() const override { + return fName; + } + + bool hasSideEffects() const override { + return false; + } + + bool isConstant() const override { + return true; + } + + const String fName; + std::unique_ptr fValue; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLStatement.h new file mode 100644 index 0000000000..ed8d33b142 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLStatement.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_STATEMENT +#define SKSL_STATEMENT + +#include "src/sksl/ir/SkSLIRNode.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +/** + * Abstract supertype of all statements. + */ +struct Statement : public IRNode { + enum Kind { + kBlock_Kind, + kBreak_Kind, + kContinue_Kind, + kDiscard_Kind, + kDo_Kind, + kExpression_Kind, + kFor_Kind, + kGroup_Kind, + kIf_Kind, + kNop_Kind, + kReturn_Kind, + kSwitch_Kind, + kVarDeclaration_Kind, + kVarDeclarations_Kind, + kWhile_Kind + }; + + Statement(int offset, Kind kind) + : INHERITED(offset) + , fKind(kind) {} + + virtual bool isEmpty() const { + return false; + } + + virtual std::unique_ptr clone() const = 0; + + const Kind fKind; + + typedef IRNode INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h b/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h new file mode 100644 index 0000000000..b1ddb012ec --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SWITCHCASE +#define SKSL_SWITCHCASE + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A single case of a 'switch' statement. + */ +struct SwitchCase : public Statement { + SwitchCase(int offset, std::unique_ptr value, + std::vector> statements) + : INHERITED(offset, kSwitch_Kind) + , fValue(std::move(value)) + , fStatements(std::move(statements)) {} + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& s : fStatements) { + cloned.push_back(s->clone()); + } + return std::unique_ptr(new SwitchCase(fOffset, + fValue ? fValue->clone() : nullptr, + std::move(cloned))); + } + + String description() const override { + String result; + if (fValue) { + result.appendf("case %s:\n", fValue->description().c_str()); + } else { + result += "default:\n"; + } + for (const auto& s : fStatements) { + result += s->description() + "\n"; + } + return result; + } + + // null value implies "default" case + std::unique_ptr fValue; + std::vector> fStatements; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h new file mode 100644 index 0000000000..0777c5c5c6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SWITCHSTATEMENT +#define SKSL_SWITCHSTATEMENT + +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLSwitchCase.h" + +namespace SkSL { + +class SymbolTable; + +/** + * A 'switch' statement. + */ +struct SwitchStatement : public Statement { + SwitchStatement(int offset, bool isStatic, std::unique_ptr value, + std::vector> cases, + const std::shared_ptr symbols) + : INHERITED(offset, kSwitch_Kind) + , fIsStatic(isStatic) + , fValue(std::move(value)) + , fSymbols(std::move(symbols)) + , fCases(std::move(cases)) {} + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& s : fCases) { + cloned.push_back(std::unique_ptr((SwitchCase*) s->clone().release())); + } + return std::unique_ptr(new SwitchStatement(fOffset, fIsStatic, fValue->clone(), + std::move(cloned), fSymbols)); + } + + String description() const override { + String result; + if (fIsStatic) { + result += "@"; + } + result += String::printf("switch (%s) {\n", fValue->description().c_str()); + for (const auto& c : fCases) { + result += c->description(); + } + result += "}"; + return result; + } + + bool fIsStatic; + std::unique_ptr fValue; + // it's important to keep fCases defined after (and thus destroyed before) fSymbols, because + // destroying statements can modify reference counts in symbols + const std::shared_ptr fSymbols; + std::vector> fCases; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h new file mode 100644 index 0000000000..5067b844a4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SWIZZLE +#define SKSL_SWIZZLE + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +// represents a swizzle component of constant 0, as in x.rgb0 +const int SKSL_SWIZZLE_0 = -2; + +// represents a swizzle component of constant 1, as in x.rgb1 +const int SKSL_SWIZZLE_1 = -1; + +/** + * Given a type and a swizzle component count, returns the type that will result from swizzling. For + * instance, swizzling a float3 with two components will result in a float2. It is possible to + * swizzle with more components than the source vector, as in 'float2(1).xxxx'. + */ +static const Type& get_type(const Context& context, Expression& value, size_t count) { + const Type& base = value.fType.componentType(); + if (count == 1) { + return base; + } + if (base == *context.fFloat_Type) { + switch (count) { + case 2: return *context.fFloat2_Type; + case 3: return *context.fFloat3_Type; + case 4: return *context.fFloat4_Type; + } + } else if (base == *context.fHalf_Type) { + switch (count) { + case 2: return *context.fHalf2_Type; + case 3: return *context.fHalf3_Type; + case 4: return *context.fHalf4_Type; + } + } else if (base == *context.fDouble_Type) { + switch (count) { + case 2: return *context.fDouble2_Type; + case 3: return *context.fDouble3_Type; + case 4: return *context.fDouble4_Type; + } + } else if (base == *context.fInt_Type) { + switch (count) { + case 2: return *context.fInt2_Type; + case 3: return *context.fInt3_Type; + case 4: return *context.fInt4_Type; + } + } else if (base == *context.fShort_Type) { + switch (count) { + case 2: return *context.fShort2_Type; + case 3: return *context.fShort3_Type; + case 4: return *context.fShort4_Type; + } + } else if (base == *context.fByte_Type) { + switch (count) { + case 2: return *context.fByte2_Type; + case 3: return *context.fByte3_Type; + case 4: return *context.fByte4_Type; + } + } else if (base == *context.fUInt_Type) { + switch (count) { + case 2: return *context.fUInt2_Type; + case 3: return *context.fUInt3_Type; + case 4: return *context.fUInt4_Type; + } + } else if (base == *context.fUShort_Type) { + switch (count) { + case 2: return *context.fUShort2_Type; + case 3: return *context.fUShort3_Type; + case 4: return *context.fUShort4_Type; + } + } else if (base == *context.fUByte_Type) { + switch (count) { + case 2: return *context.fUByte2_Type; + case 3: return *context.fUByte3_Type; + case 4: return *context.fUByte4_Type; + } + } else if (base == *context.fBool_Type) { + switch (count) { + case 2: return *context.fBool2_Type; + case 3: return *context.fBool3_Type; + case 4: return *context.fBool4_Type; + } + } + ABORT("cannot swizzle %s\n", value.description().c_str()); +} + +/** + * Represents a vector swizzle operation such as 'float2(1, 2, 3).zyx'. + */ +struct Swizzle : public Expression { + Swizzle(const Context& context, std::unique_ptr base, std::vector components) + : INHERITED(base->fOffset, kSwizzle_Kind, get_type(context, *base, components.size())) + , fBase(std::move(base)) + , fComponents(std::move(components)) { + SkASSERT(fComponents.size() >= 1 && fComponents.size() <= 4); + } + + std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) override { + if (fBase->fKind == Expression::kConstructor_Kind && fBase->isConstant()) { + // we're swizzling a constant vector, e.g. float4(1).x. Simplify it. + SkASSERT(fBase->fKind == Expression::kConstructor_Kind); + if (fType.isInteger()) { + SkASSERT(fComponents.size() == 1); + int64_t value = ((Constructor&) *fBase).getIVecComponent(fComponents[0]); + return std::unique_ptr(new IntLiteral(irGenerator.fContext, + -1, + value)); + } else if (fType.isFloat()) { + SkASSERT(fComponents.size() == 1); + double value = ((Constructor&) *fBase).getFVecComponent(fComponents[0]); + return std::unique_ptr(new FloatLiteral(irGenerator.fContext, + -1, + value)); + } + } + return nullptr; + } + + bool hasSideEffects() const override { + return fBase->hasSideEffects(); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new Swizzle(fType, fBase->clone(), fComponents)); + } + + String description() const override { + String result = fBase->description() + "."; + for (int x : fComponents) { + result += "01xyzw"[x + 2]; + } + return result; + } + + std::unique_ptr fBase; + std::vector fComponents; + + typedef Expression INHERITED; + +private: + Swizzle(const Type& type, std::unique_ptr base, std::vector components) + : INHERITED(base->fOffset, kSwizzle_Kind, type) + , fBase(std::move(base)) + , fComponents(std::move(components)) { + SkASSERT(fComponents.size() >= 1 && fComponents.size() <= 4); + } + + +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbol.h b/gfx/skia/skia/src/sksl/ir/SkSLSymbol.h new file mode 100644 index 0000000000..4ee4d3d90d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbol.h @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SYMBOL +#define SKSL_SYMBOL + +#include "src/sksl/ir/SkSLIRNode.h" + +namespace SkSL { + +/** + * Represents a symboltable entry. + */ +struct Symbol : public IRNode { + enum Kind { + kFunctionDeclaration_Kind, + kUnresolvedFunction_Kind, + kType_Kind, + kVariable_Kind, + kField_Kind, + kExternal_Kind + }; + + Symbol(int offset, Kind kind, StringFragment name) + : INHERITED(offset) + , fKind(kind) + , fName(name) {} + + virtual ~Symbol() {} + + Kind fKind; + StringFragment fName; + + typedef IRNode INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp new file mode 100644 index 0000000000..08bd6c2a65 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLUnresolvedFunction.h" + +namespace SkSL { + +std::vector SymbolTable::GetFunctions(const Symbol& s) { + switch (s.fKind) { + case Symbol::kFunctionDeclaration_Kind: + return { &((FunctionDeclaration&) s) }; + case Symbol::kUnresolvedFunction_Kind: + return ((UnresolvedFunction&) s).fFunctions; + default: + return std::vector(); + } +} + +const Symbol* SymbolTable::operator[](StringFragment name) { + const auto& entry = fSymbols.find(name); + if (entry == fSymbols.end()) { + if (fParent) { + return (*fParent)[name]; + } + return nullptr; + } + if (fParent) { + auto functions = GetFunctions(*entry->second); + if (functions.size() > 0) { + bool modified = false; + const Symbol* previous = (*fParent)[name]; + if (previous) { + auto previousFunctions = GetFunctions(*previous); + for (const FunctionDeclaration* prev : previousFunctions) { + bool found = false; + for (const FunctionDeclaration* current : functions) { + if (current->matches(*prev)) { + found = true; + break; + } + } + if (!found) { + functions.push_back(prev); + modified = true; + } + } + if (modified) { + SkASSERT(functions.size() > 1); + return this->takeOwnership(std::unique_ptr( + new UnresolvedFunction(functions))); + } + } + } + } + return entry->second; +} + +Symbol* SymbolTable::takeOwnership(std::unique_ptr s) { + Symbol* result = s.get(); + fOwnedSymbols.push_back(std::move(s)); + return result; +} + +IRNode* SymbolTable::takeOwnership(std::unique_ptr n) { + IRNode* result = n.get(); + fOwnedNodes.push_back(std::move(n)); + return result; +} + +void SymbolTable::add(StringFragment name, std::unique_ptr symbol) { + this->addWithoutOwnership(name, symbol.get()); + this->takeOwnership(std::move(symbol)); +} + +void SymbolTable::addWithoutOwnership(StringFragment name, const Symbol* symbol) { + const auto& existing = fSymbols.find(name); + if (existing == fSymbols.end()) { + fSymbols[name] = symbol; + } else if (symbol->fKind == Symbol::kFunctionDeclaration_Kind) { + const Symbol* oldSymbol = existing->second; + if (oldSymbol->fKind == Symbol::kFunctionDeclaration_Kind) { + std::vector functions; + functions.push_back((const FunctionDeclaration*) oldSymbol); + functions.push_back((const FunctionDeclaration*) symbol); + std::unique_ptr u = std::unique_ptr(new UnresolvedFunction(std::move( + functions))); + fSymbols[name] = this->takeOwnership(std::move(u)); + } else if (oldSymbol->fKind == Symbol::kUnresolvedFunction_Kind) { + std::vector functions; + for (const auto* f : ((UnresolvedFunction&) *oldSymbol).fFunctions) { + functions.push_back(f); + } + functions.push_back((const FunctionDeclaration*) symbol); + std::unique_ptr u = std::unique_ptr(new UnresolvedFunction(std::move( + functions))); + fSymbols[name] = this->takeOwnership(std::move(u)); + } + } else { + fErrorReporter.error(symbol->fOffset, "symbol '" + name + "' was already defined"); + } +} + + +void SymbolTable::markAllFunctionsBuiltin() { + for (const auto& pair : fSymbols) { + switch (pair.second->fKind) { + case Symbol::kFunctionDeclaration_Kind: + if (!((FunctionDeclaration&)*pair.second).fDefined) { + ((FunctionDeclaration&)*pair.second).fBuiltin = true; + } + break; + case Symbol::kUnresolvedFunction_Kind: + for (auto& f : ((UnresolvedFunction&) *pair.second).fFunctions) { + if (!((FunctionDeclaration*)f)->fDefined) { + ((FunctionDeclaration*)f)->fBuiltin = true; + } + } + break; + default: + break; + } + } +} + +std::unordered_map::iterator SymbolTable::begin() { + return fSymbols.begin(); +} + +std::unordered_map::iterator SymbolTable::end() { + return fSymbols.end(); +} + + +} // namespace diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h new file mode 100644 index 0000000000..6969ba5e08 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SYMBOLTABLE +#define SKSL_SYMBOLTABLE + +#include +#include +#include +#include "src/sksl/SkSLErrorReporter.h" +#include "src/sksl/ir/SkSLSymbol.h" + +namespace SkSL { + +struct FunctionDeclaration; + +/** + * Maps identifiers to symbols. Functions, in particular, are mapped to either FunctionDeclaration + * or UnresolvedFunction depending on whether they are overloaded or not. + */ +class SymbolTable { +public: + SymbolTable(ErrorReporter* errorReporter) + : fErrorReporter(*errorReporter) {} + + SymbolTable(std::shared_ptr parent, ErrorReporter* errorReporter) + : fParent(parent) + , fErrorReporter(*errorReporter) {} + + const Symbol* operator[](StringFragment name); + + void add(StringFragment name, std::unique_ptr symbol); + + void addWithoutOwnership(StringFragment name, const Symbol* symbol); + + Symbol* takeOwnership(std::unique_ptr s); + + IRNode* takeOwnership(std::unique_ptr n); + + void markAllFunctionsBuiltin(); + + std::unordered_map::iterator begin(); + + std::unordered_map::iterator end(); + + const std::shared_ptr fParent; + +private: + static std::vector GetFunctions(const Symbol& s); + + std::vector> fOwnedSymbols; + + std::vector> fOwnedNodes; + + std::unordered_map fSymbols; + + ErrorReporter& fErrorReporter; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h new file mode 100644 index 0000000000..e2bf9ed28b --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TERNARYEXPRESSION +#define SKSL_TERNARYEXPRESSION + +#include "src/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A ternary expression (test ? ifTrue : ifFalse). + */ +struct TernaryExpression : public Expression { + TernaryExpression(int offset, std::unique_ptr test, + std::unique_ptr ifTrue, std::unique_ptr ifFalse) + : INHERITED(offset, kTernary_Kind, ifTrue->fType) + , fTest(std::move(test)) + , fIfTrue(std::move(ifTrue)) + , fIfFalse(std::move(ifFalse)) { + SkASSERT(fIfTrue->fType == fIfFalse->fType); + } + + bool hasSideEffects() const override { + return fTest->hasSideEffects() || fIfTrue->hasSideEffects() || fIfFalse->hasSideEffects(); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new TernaryExpression(fOffset, fTest->clone(), + fIfTrue->clone(), + fIfFalse->clone())); + } + + String description() const override { + return "(" + fTest->description() + " ? " + fIfTrue->description() + " : " + + fIfFalse->description() + ")"; + } + + std::unique_ptr fTest; + std::unique_ptr fIfTrue; + std::unique_ptr fIfFalse; + + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLType.cpp b/gfx/skia/skia/src/sksl/ir/SkSLType.cpp new file mode 100644 index 0000000000..5d735fc11f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLType.cpp @@ -0,0 +1,232 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +int Type::coercionCost(const Type& other) const { + if (*this == other) { + return 0; + } + if (this->kind() == kNullable_Kind && other.kind() != kNullable_Kind) { + int result = this->componentType().coercionCost(other); + if (result != INT_MAX) { + ++result; + } + return result; + } + if (this->fName == "null" && other.kind() == kNullable_Kind) { + return 0; + } + if (this->kind() == kVector_Kind && other.kind() == kVector_Kind) { + if (this->columns() == other.columns()) { + return this->componentType().coercionCost(other.componentType()); + } + return INT_MAX; + } + if (this->kind() == kMatrix_Kind) { + if (this->columns() == other.columns() && this->rows() == other.rows()) { + return this->componentType().coercionCost(other.componentType()); + } + return INT_MAX; + } + if (this->isNumber() && other.isNumber() && other.priority() > this->priority()) { + return other.priority() - this->priority(); + } + for (size_t i = 0; i < fCoercibleTypes.size(); i++) { + if (*fCoercibleTypes[i] == other) { + return (int) i + 1; + } + } + return INT_MAX; +} + +const Type& Type::toCompound(const Context& context, int columns, int rows) const { + SkASSERT(this->kind() == Type::kScalar_Kind); + if (columns == 1 && rows == 1) { + return *this; + } + if (*this == *context.fFloat_Type || *this == *context.fFloatLiteral_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fFloat2_Type; + case 3: return *context.fFloat3_Type; + case 4: return *context.fFloat4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + case 2: + switch (columns) { + case 2: return *context.fFloat2x2_Type; + case 3: return *context.fFloat3x2_Type; + case 4: return *context.fFloat4x2_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + case 3: + switch (columns) { + case 2: return *context.fFloat2x3_Type; + case 3: return *context.fFloat3x3_Type; + case 4: return *context.fFloat4x3_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + case 4: + switch (columns) { + case 2: return *context.fFloat2x4_Type; + case 3: return *context.fFloat3x4_Type; + case 4: return *context.fFloat4x4_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fHalf_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fHalf2_Type; + case 3: return *context.fHalf3_Type; + case 4: return *context.fHalf4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + case 2: + switch (columns) { + case 2: return *context.fHalf2x2_Type; + case 3: return *context.fHalf3x2_Type; + case 4: return *context.fHalf4x2_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + case 3: + switch (columns) { + case 2: return *context.fHalf2x3_Type; + case 3: return *context.fHalf3x3_Type; + case 4: return *context.fHalf4x3_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + case 4: + switch (columns) { + case 2: return *context.fHalf2x4_Type; + case 3: return *context.fHalf3x4_Type; + case 4: return *context.fHalf4x4_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fDouble_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fDouble2_Type; + case 3: return *context.fDouble3_Type; + case 4: return *context.fDouble4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + case 2: + switch (columns) { + case 2: return *context.fDouble2x2_Type; + case 3: return *context.fDouble3x2_Type; + case 4: return *context.fDouble4x2_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + case 3: + switch (columns) { + case 2: return *context.fDouble2x3_Type; + case 3: return *context.fDouble3x3_Type; + case 4: return *context.fDouble4x3_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + case 4: + switch (columns) { + case 2: return *context.fDouble2x4_Type; + case 3: return *context.fDouble3x4_Type; + case 4: return *context.fDouble4x4_Type; + default: ABORT("unsupported matrix column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fInt_Type || *this == *context.fIntLiteral_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fInt2_Type; + case 3: return *context.fInt3_Type; + case 4: return *context.fInt4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fShort_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fShort2_Type; + case 3: return *context.fShort3_Type; + case 4: return *context.fShort4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fByte_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fByte2_Type; + case 3: return *context.fByte3_Type; + case 4: return *context.fByte4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fUInt_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fUInt2_Type; + case 3: return *context.fUInt3_Type; + case 4: return *context.fUInt4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fUShort_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fUShort2_Type; + case 3: return *context.fUShort3_Type; + case 4: return *context.fUShort4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fUByte_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fUByte2_Type; + case 3: return *context.fUByte3_Type; + case 4: return *context.fUByte4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } else if (*this == *context.fBool_Type) { + switch (rows) { + case 1: + switch (columns) { + case 2: return *context.fBool2_Type; + case 3: return *context.fBool3_Type; + case 4: return *context.fBool4_Type; + default: ABORT("unsupported vector column count (%d)", columns); + } + default: ABORT("unsupported row count (%d)", rows); + } + } + ABORT("unsupported scalar_to_compound type %s", this->description().c_str()); +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/ir/SkSLType.h b/gfx/skia/skia/src/sksl/ir/SkSLType.h new file mode 100644 index 0000000000..49ea8a5531 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLType.h @@ -0,0 +1,442 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKIASL_TYPE +#define SKIASL_TYPE + +#include "src/sksl/SkSLPosition.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLSymbol.h" +#include "src/sksl/spirv.h" +#include +#include +#include + +namespace SkSL { + +class Context; + +/** + * Represents a type, such as int or float4. + */ +class Type : public Symbol { +public: + struct Field { + Field(Modifiers modifiers, StringFragment name, const Type* type) + : fModifiers(modifiers) + , fName(name) + , fType(std::move(type)) {} + + const String description() const { + return fType->description() + " " + fName + ";"; + } + + Modifiers fModifiers; + StringFragment fName; + const Type* fType; + }; + + enum Kind { + kArray_Kind, + kEnum_Kind, + kGeneric_Kind, + kNullable_Kind, + kMatrix_Kind, + kOther_Kind, + kSampler_Kind, + kSeparateSampler_Kind, + kScalar_Kind, + kStruct_Kind, + kTexture_Kind, + kVector_Kind + }; + + enum NumberKind { + kFloat_NumberKind, + kSigned_NumberKind, + kUnsigned_NumberKind, + kNonnumeric_NumberKind + }; + + // Create an "other" (special) type with the given name. These types cannot be directly + // referenced from user code. + Type(const char* name) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kOther_Kind) + , fNumberKind(kNonnumeric_NumberKind) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create an "other" (special) type that supports field access. + Type(const char* name, std::vector fields) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kOther_Kind) + , fNumberKind(kNonnumeric_NumberKind) + , fFields(std::move(fields)) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a simple type. + Type(String name, Kind kind) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(std::move(name)) + , fTypeKind(kind) + , fNumberKind(kNonnumeric_NumberKind) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a generic type which maps to the listed types. + Type(const char* name, std::vector types) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kGeneric_Kind) + , fNumberKind(kNonnumeric_NumberKind) + , fCoercibleTypes(std::move(types)) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a struct type with the given fields. + Type(int offset, String name, std::vector fields) + : INHERITED(offset, kType_Kind, StringFragment()) + , fNameString(std::move(name)) + , fTypeKind(kStruct_Kind) + , fNumberKind(kNonnumeric_NumberKind) + , fFields(std::move(fields)) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a scalar type. + Type(const char* name, NumberKind numberKind, int priority, bool highPrecision = false) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kScalar_Kind) + , fNumberKind(numberKind) + , fPriority(priority) + , fColumns(1) + , fRows(1) + , fHighPrecision(highPrecision) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a scalar type which can be coerced to the listed types. + Type(const char* name, + NumberKind numberKind, + int priority, + std::vector coercibleTypes) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kScalar_Kind) + , fNumberKind(numberKind) + , fPriority(priority) + , fCoercibleTypes(std::move(coercibleTypes)) + , fColumns(1) + , fRows(1) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a nullable type. + Type(String name, Kind kind, const Type& componentType) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(std::move(name)) + , fTypeKind(kind) + , fNumberKind(kNonnumeric_NumberKind) + , fComponentType(&componentType) + , fColumns(1) + , fRows(1) + , fDimensions(SpvDim1D) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a vector type. + Type(const char* name, const Type& componentType, int columns) + : Type(name, kVector_Kind, componentType, columns) {} + + // Create a vector or array type. + Type(String name, Kind kind, const Type& componentType, int columns) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(std::move(name)) + , fTypeKind(kind) + , fNumberKind(kNonnumeric_NumberKind) + , fComponentType(&componentType) + , fColumns(columns) + , fRows(1) + , fDimensions(SpvDim1D) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a matrix type. + Type(const char* name, const Type& componentType, int columns, int rows) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kMatrix_Kind) + , fNumberKind(kNonnumeric_NumberKind) + , fComponentType(&componentType) + , fColumns(columns) + , fRows(rows) + , fDimensions(SpvDim1D) { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a texture type. + Type(const char* name, SpvDim_ dimensions, bool isDepth, bool isArrayed, bool isMultisampled, + bool isSampled) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kTexture_Kind) + , fNumberKind(kNonnumeric_NumberKind) + , fDimensions(dimensions) + , fIsDepth(isDepth) + , fIsArrayed(isArrayed) + , fIsMultisampled(isMultisampled) + , fIsSampled(isSampled) + { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + // Create a sampler type. + Type(const char* name, const Type& textureType) + : INHERITED(-1, kType_Kind, StringFragment()) + , fNameString(name) + , fTypeKind(kSampler_Kind) + , fNumberKind(kNonnumeric_NumberKind) + , fDimensions(textureType.dimensions()) + , fIsDepth(textureType.isDepth()) + , fIsArrayed(textureType.isArrayed()) + , fIsMultisampled(textureType.isMultisampled()) + , fIsSampled(textureType.isSampled()) + , fTextureType(&textureType) + { + fName.fChars = fNameString.c_str(); + fName.fLength = fNameString.size(); + } + + const String& name() const { + return fNameString; + } + + String description() const override { + if (fNameString == "$floatLiteral") { + return "float"; + } + if (fNameString == "$intLiteral") { + return "int"; + } + return fNameString; + } + + bool operator==(const Type& other) const { + return fName == other.fName; + } + + bool operator!=(const Type& other) const { + return fName != other.fName; + } + + /** + * Returns the category (scalar, vector, matrix, etc.) of this type. + */ + Kind kind() const { + return fTypeKind; + } + + /** + * Returns true if this is a numeric scalar type. + */ + bool isNumber() const { + return fNumberKind != kNonnumeric_NumberKind; + } + + /** + * Returns true if this is a floating-point scalar type (float, half, or double). + */ + bool isFloat() const { + return fNumberKind == kFloat_NumberKind; + } + + /** + * Returns true if this is a signed scalar type (int or short). + */ + bool isSigned() const { + return fNumberKind == kSigned_NumberKind; + } + + /** + * Returns true if this is an unsigned scalar type (uint or ushort). + */ + bool isUnsigned() const { + return fNumberKind == kUnsigned_NumberKind; + } + + /** + * Returns true if this is a signed or unsigned integer. + */ + bool isInteger() const { + return isSigned() || isUnsigned(); + } + + /** + * Returns the "priority" of a number type, in order of double > float > half > int > short. + * When operating on two number types, the result is the higher-priority type. + */ + int priority() const { + return fPriority; + } + + /** + * Returns true if an instance of this type can be freely coerced (implicitly converted) to + * another type. + */ + bool canCoerceTo(const Type& other) const { + return coercionCost(other) != INT_MAX; + } + + /** + * Determines the "cost" of coercing (implicitly converting) this type to another type. The cost + * is a number with no particular meaning other than that lower costs are preferable to higher + * costs. Returns INT_MAX if the coercion is not possible. + */ + int coercionCost(const Type& other) const; + + /** + * For matrices and vectors, returns the type of individual cells (e.g. mat2 has a component + * type of kFloat_Type). For all other types, causes an SkASSERTion failure. + */ + const Type& componentType() const { + SkASSERT(fComponentType); + return *fComponentType; + } + + /** + * For texturesamplers, returns the type of texture it samples (e.g., sampler2D has + * a texture type of texture2D). + */ + const Type& textureType() const { + SkASSERT(fTextureType); + return *fTextureType; + } + + /** + * For nullable types, returns the base type, otherwise returns the type itself. + */ + const Type& nonnullable() const { + if (fTypeKind == kNullable_Kind) { + return this->componentType(); + } + return *this; + } + + /** + * For matrices and vectors, returns the number of columns (e.g. both mat3 and float3return 3). + * For scalars, returns 1. For arrays, returns either the size of the array (if known) or -1. + * For all other types, causes an SkASSERTion failure. + */ + int columns() const { + SkASSERT(fTypeKind == kScalar_Kind || fTypeKind == kVector_Kind || + fTypeKind == kMatrix_Kind || fTypeKind == kArray_Kind); + return fColumns; + } + + /** + * For matrices, returns the number of rows (e.g. mat2x4 returns 4). For vectors and scalars, + * returns 1. For all other types, causes an SkASSERTion failure. + */ + int rows() const { + SkASSERT(fRows > 0); + return fRows; + } + + const std::vector& fields() const { + SkASSERT(fTypeKind == kStruct_Kind || fTypeKind == kOther_Kind); + return fFields; + } + + /** + * For generic types, returns the types that this generic type can substitute for. For other + * types, returns a list of other types that this type can be coerced into. + */ + const std::vector& coercibleTypes() const { + SkASSERT(fCoercibleTypes.size() > 0); + return fCoercibleTypes; + } + + SpvDim_ dimensions() const { + SkASSERT(kSampler_Kind == fTypeKind || kTexture_Kind == fTypeKind); + return fDimensions; + } + + bool isDepth() const { + SkASSERT(kSampler_Kind == fTypeKind || kTexture_Kind == fTypeKind); + return fIsDepth; + } + + bool isArrayed() const { + SkASSERT(kSampler_Kind == fTypeKind || kTexture_Kind == fTypeKind); + return fIsArrayed; + } + + bool isMultisampled() const { + SkASSERT(kSampler_Kind == fTypeKind || kTexture_Kind == fTypeKind); + return fIsMultisampled; + } + + bool isSampled() const { + SkASSERT(kSampler_Kind == fTypeKind || kTexture_Kind == fTypeKind); + return fIsSampled; + } + + bool highPrecision() const { + if (fComponentType) { + return fComponentType->highPrecision(); + } + return fHighPrecision; + } + + /** + * Returns the corresponding vector or matrix type with the specified number of columns and + * rows. + */ + const Type& toCompound(const Context& context, int columns, int rows) const; + +private: + typedef Symbol INHERITED; + + String fNameString; + Kind fTypeKind; + // always kNonnumeric_NumberKind for non-scalar values + NumberKind fNumberKind; + int fPriority = -1; + const Type* fComponentType = nullptr; + std::vector fCoercibleTypes; + int fColumns = -1; + int fRows = -1; + std::vector fFields; + SpvDim_ fDimensions = SpvDim1D; + bool fIsDepth = false; + bool fIsArrayed = false; + bool fIsMultisampled = false; + bool fIsSampled = false; + bool fHighPrecision = false; + const Type* fTextureType = nullptr; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h new file mode 100644 index 0000000000..20a533aeb0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TYPEREFERENCE +#define SKSL_TYPEREFERENCE + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * Represents an identifier referring to a type. This is an intermediate value: TypeReferences are + * always eventually replaced by Constructors in valid programs. + */ +struct TypeReference : public Expression { + TypeReference(const Context& context, int offset, const Type& value) + : INHERITED(offset, kTypeReference_Kind, *context.fInvalid_Type) + , fValue(value) {} + + bool hasSideEffects() const override { + return false; + } + + String description() const override { + return String(fValue.fName); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new TypeReference(fOffset, fValue, &fType)); + } + + const Type& fValue; + + typedef Expression INHERITED; + +private: + TypeReference(int offset, const Type& value, const Type* type) + : INHERITED(offset, kTypeReference_Kind, *type) + , fValue(value) {} +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLUnresolvedFunction.h b/gfx/skia/skia/src/sksl/ir/SkSLUnresolvedFunction.h new file mode 100644 index 0000000000..d32cd42856 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLUnresolvedFunction.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_UNRESOLVEDFUNCTION +#define SKSL_UNRESOLVEDFUNCTION + +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +namespace SkSL { + +/** + * A symbol representing multiple functions with the same name. + */ +struct UnresolvedFunction : public Symbol { + UnresolvedFunction(std::vector funcs) + : INHERITED(-1, kUnresolvedFunction_Kind, funcs[0]->fName) + , fFunctions(std::move(funcs)) { +#ifdef DEBUG + for (auto func : funcs) { + SkASSERT(func->fName == fName); + } +#endif + } + + String description() const override { + return fName; + } + + const std::vector fFunctions; + + typedef Symbol INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h new file mode 100644 index 0000000000..82dbd8616d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARDECLARATIONS +#define SKSL_VARDECLARATIONS + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +/** + * A single variable declaration within a var declaration statement. For instance, the statement + * 'int x = 2, y[3];' is a VarDeclarations statement containing two individual VarDeclaration + * instances. + */ +struct VarDeclaration : public Statement { + VarDeclaration(const Variable* var, + std::vector> sizes, + std::unique_ptr value) + : INHERITED(var->fOffset, Statement::kVarDeclaration_Kind) + , fVar(var) + , fSizes(std::move(sizes)) + , fValue(std::move(value)) {} + + std::unique_ptr clone() const override { + std::vector> sizesClone; + for (const auto& s : fSizes) { + if (s) { + sizesClone.push_back(s->clone()); + } else { + sizesClone.push_back(nullptr); + } + } + return std::unique_ptr(new VarDeclaration(fVar, std::move(sizesClone), + fValue ? fValue->clone() : nullptr)); + } + + String description() const override { + String result = fVar->fName; + for (const auto& size : fSizes) { + if (size) { + result += "[" + size->description() + "]"; + } else { + result += "[]"; + } + } + if (fValue) { + result += " = " + fValue->description(); + } + return result; + } + + const Variable* fVar; + std::vector> fSizes; + std::unique_ptr fValue; + + typedef Statement INHERITED; +}; + +/** + * A variable declaration statement, which may consist of one or more individual variables. + */ +struct VarDeclarations : public ProgramElement { + VarDeclarations(int offset, const Type* baseType, + std::vector> vars) + : INHERITED(offset, kVar_Kind) + , fBaseType(*baseType) { + for (auto& var : vars) { + fVars.push_back(std::unique_ptr(var.release())); + } + } + + std::unique_ptr clone() const override { + std::vector> cloned; + for (const auto& v : fVars) { + cloned.push_back(std::unique_ptr( + (VarDeclaration*) v->clone().release())); + } + return std::unique_ptr(new VarDeclarations(fOffset, &fBaseType, + std::move(cloned))); + } + + String description() const override { + if (!fVars.size()) { + return String(); + } + String result = ((VarDeclaration&) *fVars[0]).fVar->fModifiers.description() + + fBaseType.description() + " "; + String separator; + for (const auto& var : fVars) { + result += separator; + separator = ", "; + result += var->description(); + } + return result; + } + + const Type& fBaseType; + // this *should* be a vector of unique_ptr, but it significantly simplifies the + // CFG to only have to worry about unique_ptr + std::vector> fVars; + + typedef ProgramElement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarationsStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarationsStatement.h new file mode 100644 index 0000000000..465b691936 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarationsStatement.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARDECLARATIONSSTATEMENT +#define SKSL_VARDECLARATIONSSTATEMENT + +#include "src/sksl/ir/SkSLStatement.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +namespace SkSL { + +/** + * One or more variable declarations appearing as a statement within a function. + */ +struct VarDeclarationsStatement : public Statement { + VarDeclarationsStatement(std::unique_ptr decl) + : INHERITED(decl->fOffset, kVarDeclarations_Kind) + , fDeclaration(std::move(decl)) {} + + bool isEmpty() const override { + for (const auto& s : fDeclaration->fVars) { + if (!s->isEmpty()) { + return false; + } + } + return true; + } + + std::unique_ptr clone() const override { + std::unique_ptr cloned((VarDeclarations*) fDeclaration->clone().release()); + return std::unique_ptr(new VarDeclarationsStatement(std::move(cloned))); + } + + String description() const override { + return fDeclaration->description() + ";"; + } + + std::unique_ptr fDeclaration; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariable.h b/gfx/skia/skia/src/sksl/ir/SkSLVariable.h new file mode 100644 index 0000000000..bdf5cd8f79 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariable.h @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARIABLE +#define SKSL_VARIABLE + +#include "src/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLModifiers.h" +#include "src/sksl/ir/SkSLSymbol.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +struct Expression; + +/** + * Represents a variable, whether local, global, or a function parameter. This represents the + * variable itself (the storage location), which is shared between all VariableReferences which + * read or write that storage location. + */ +struct Variable : public Symbol { + enum Storage { + kGlobal_Storage, + kInterfaceBlock_Storage, + kLocal_Storage, + kParameter_Storage + }; + + Variable(int offset, Modifiers modifiers, StringFragment name, const Type& type, + Storage storage, Expression* initialValue = nullptr) + : INHERITED(offset, kVariable_Kind, name) + , fModifiers(modifiers) + , fType(type) + , fStorage(storage) + , fInitialValue(initialValue) + , fReadCount(0) + , fWriteCount(initialValue ? 1 : 0) {} + + ~Variable() override { + // can't destroy a variable while there are remaining references to it + if (fInitialValue) { + --fWriteCount; + } + SkASSERT(!fReadCount && !fWriteCount); + } + + virtual String description() const override { + return fModifiers.description() + fType.fName + " " + fName; + } + + bool dead() const { + if ((fStorage != kLocal_Storage && fReadCount) || + (fModifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag | + Modifiers::kUniform_Flag))) { + return false; + } + return !fWriteCount || + (!fReadCount && !(fModifiers.fFlags & (Modifiers::kPLS_Flag | + Modifiers::kPLSOut_Flag))); + } + + mutable Modifiers fModifiers; + const Type& fType; + const Storage fStorage; + + Expression* fInitialValue = nullptr; + + // Tracks how many sites read from the variable. If this is zero for a non-out variable (or + // becomes zero during optimization), the variable is dead and may be eliminated. + mutable int fReadCount; + // Tracks how many sites write to the variable. If this is zero, the variable is dead and may be + // eliminated. + mutable int fWriteCount; + + typedef Symbol INHERITED; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp new file mode 100644 index 0000000000..2de8410ee5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLVariableReference.h" + +#include "src/sksl/SkSLIRGenerator.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLFloatLiteral.h" +#include "src/sksl/ir/SkSLSetting.h" + +namespace SkSL { + +VariableReference::VariableReference(int offset, const Variable& variable, RefKind refKind) +: INHERITED(offset, kVariableReference_Kind, variable.fType) +, fVariable(variable) +, fRefKind(refKind) { + if (refKind != kRead_RefKind) { + fVariable.fWriteCount++; + } + if (refKind != kWrite_RefKind) { + fVariable.fReadCount++; + } +} + +VariableReference::~VariableReference() { + if (fRefKind != kRead_RefKind) { + fVariable.fWriteCount--; + } + if (fRefKind != kWrite_RefKind) { + fVariable.fReadCount--; + } +} + +void VariableReference::setRefKind(RefKind refKind) { + if (fRefKind != kRead_RefKind) { + fVariable.fWriteCount--; + } + if (fRefKind != kWrite_RefKind) { + fVariable.fReadCount--; + } + if (refKind != kRead_RefKind) { + fVariable.fWriteCount++; + } + if (refKind != kWrite_RefKind) { + fVariable.fReadCount++; + } + fRefKind = refKind; +} + +std::unique_ptr VariableReference::copy_constant(const IRGenerator& irGenerator, + const Expression* expr) { + SkASSERT(expr->isConstant()); + switch (expr->fKind) { + case Expression::kIntLiteral_Kind: + return std::unique_ptr(new IntLiteral(irGenerator.fContext, + -1, + ((IntLiteral*) expr)->fValue)); + case Expression::kFloatLiteral_Kind: + return std::unique_ptr(new FloatLiteral( + irGenerator.fContext, + -1, + ((FloatLiteral*) expr)->fValue)); + case Expression::kBoolLiteral_Kind: + return std::unique_ptr(new BoolLiteral(irGenerator.fContext, + -1, + ((BoolLiteral*) expr)->fValue)); + case Expression::kConstructor_Kind: { + const Constructor* c = (const Constructor*) expr; + std::vector> args; + for (const auto& arg : c->fArguments) { + args.push_back(copy_constant(irGenerator, arg.get())); + } + return std::unique_ptr(new Constructor(-1, c->fType, + std::move(args))); + } + case Expression::kSetting_Kind: { + const Setting* s = (const Setting*) expr; + return std::unique_ptr(new Setting(-1, s->fName, + copy_constant(irGenerator, + s->fValue.get()))); + } + default: + ABORT("unsupported constant\n"); + } +} + +std::unique_ptr VariableReference::constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) { + if (fRefKind != kRead_RefKind) { + return nullptr; + } + if (irGenerator.fKind == Program::kPipelineStage_Kind && + fVariable.fStorage == Variable::kGlobal_Storage && + (fVariable.fModifiers.fFlags & Modifiers::kIn_Flag) && + !(fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag)) { + return irGenerator.getArg(fOffset, fVariable.fName); + } + if ((fVariable.fModifiers.fFlags & Modifiers::kConst_Flag) && fVariable.fInitialValue && + fVariable.fInitialValue->isConstant() && fType.kind() != Type::kArray_Kind) { + return copy_constant(irGenerator, fVariable.fInitialValue); + } + auto exprIter = definitions.find(&fVariable); + if (exprIter != definitions.end() && exprIter->second && + (*exprIter->second)->isConstant()) { + return copy_constant(irGenerator, exprIter->second->get()); + } + return nullptr; +} + +} // namespace diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h new file mode 100644 index 0000000000..6c8a92305d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARIABLEREFERENCE +#define SKSL_VARIABLEREFERENCE + +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +class IRGenerator; + +/** + * A reference to a variable, through which it can be read or written. In the statement: + * + * x = x + 1; + * + * there is only one Variable 'x', but two VariableReferences to it. + */ +struct VariableReference : public Expression { + enum RefKind { + kRead_RefKind, + kWrite_RefKind, + kReadWrite_RefKind, + // taking the address of a variable - we consider this a read & write but don't complain if + // the variable was not previously assigned + kPointer_RefKind + }; + + VariableReference(int offset, const Variable& variable, RefKind refKind = kRead_RefKind); + + ~VariableReference() override; + + RefKind refKind() const { + return fRefKind; + } + + void setRefKind(RefKind refKind); + + bool hasSideEffects() const override { + return false; + } + + bool isConstant() const override { + return 0 != (fVariable.fModifiers.fFlags & Modifiers::kConst_Flag); + } + + std::unique_ptr clone() const override { + return std::unique_ptr(new VariableReference(fOffset, fVariable, fRefKind)); + } + + String description() const override { + return fVariable.fName; + } + + static std::unique_ptr copy_constant(const IRGenerator& irGenerator, + const Expression* expr); + + std::unique_ptr constantPropagate(const IRGenerator& irGenerator, + const DefinitionMap& definitions) override; + + const Variable& fVariable; + RefKind fRefKind; + +private: + typedef Expression INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLWhileStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLWhileStatement.h new file mode 100644 index 0000000000..8e311a090a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLWhileStatement.h @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_WHILESTATEMENT +#define SKSL_WHILESTATEMENT + +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLStatement.h" + +namespace SkSL { + +/** + * A 'while' loop. + */ +struct WhileStatement : public Statement { + WhileStatement(int offset, std::unique_ptr test, + std::unique_ptr statement) + : INHERITED(offset, kWhile_Kind) + , fTest(std::move(test)) + , fStatement(std::move(statement)) {} + + std::unique_ptr clone() const override { + return std::unique_ptr(new WhileStatement(fOffset, fTest->clone(), + fStatement->clone())); + } + + String description() const override { + return "while (" + fTest->description() + ") " + fStatement->description(); + } + + std::unique_ptr fTest; + std::unique_ptr fStatement; + + typedef Statement INHERITED; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/DFA.h b/gfx/skia/skia/src/sksl/lex/DFA.h new file mode 100644 index 0000000000..1fab51f921 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/DFA.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DFA +#define SKSL_DFA + +#include +#include + +/** + * Tables representing a deterministic finite automaton for matching regular expressions. + */ +struct DFA { + DFA(std::vector charMappings, std::vector> transitions, + std::vector accepts) + : fCharMappings(charMappings) + , fTransitions(transitions) + , fAccepts(accepts) {} + + // maps chars to the row index of fTransitions, as multiple characters may map to the same row. + // starting from state s and looking at char c, the new state is + // fTransitions[fCharMappings[c]][s]. + std::vector fCharMappings; + + // one row per character mapping, one column per state + std::vector> fTransitions; + + // contains, for each state, the token id we should report when matching ends in that state (-1 + // for no match) + std::vector fAccepts; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/DFAState.h b/gfx/skia/skia/src/sksl/lex/DFAState.h new file mode 100644 index 0000000000..141078a769 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/DFAState.h @@ -0,0 +1,73 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DFASTATE +#define SKSL_DFASTATE + +#include "src/sksl/lex/LexUtil.h" + +#include +#include + +struct DFAState { + struct Label { + std::vector fStates; + + Label(std::vector states) + : fStates(std::move(states)) {} + + bool operator==(const Label& other) const { + return fStates == other.fStates; + } + + bool operator!=(const Label& other) const { + return !(*this == other); + } + + std::string description() const { + std::string result = "<"; + const char* separator = ""; + for (int s : fStates) { + result += separator; + result += std::to_string(s); + separator = ", "; + } + result += ">"; + return result; + } + }; + + DFAState() + : fId(INVALID) + , fLabel({}) {} + + DFAState(int id, Label label) + : fId(id) + , fLabel(std::move(label)) {} + + DFAState(const DFAState& other) = delete; + + int fId; + + Label fLabel; + + bool fIsScanned = false; +}; + +namespace std { + template<> struct hash { + size_t operator()(const DFAState::Label& s) const { + size_t result = 0; + for (int i : s.fStates) { + result = result * 101 + i; + } + return result; + } + }; +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/LexUtil.h b/gfx/skia/skia/src/sksl/lex/LexUtil.h new file mode 100644 index 0000000000..338b864646 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/LexUtil.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_LEXUTIL +#define SKSL_LEXUTIL + +#include + +#define INVALID -1 + +#define ABORT(...) (fprintf(stderr, __VA_ARGS__), abort()) +#define SkASSERT(x) (void)((x) || (ABORT("failed SkASSERT(%s): %s:%d\n", #x, __FILE__, __LINE__), 0)) + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/Main.cpp b/gfx/skia/skia/src/sksl/lex/Main.cpp new file mode 100644 index 0000000000..903e464e7c --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/Main.cpp @@ -0,0 +1,216 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/NFAtoDFA.h" +#include "src/sksl/lex/RegexParser.h" + +#include +#include +#include + +/** + * Processes a .lex file and produces .h and .cpp files which implement a lexical analyzer. The .lex + * file is a text file with one token definition per line. Each line is of the form: + * = + * where is either a regular expression (e.g [0-9]) or a double-quoted literal string. + */ + +static constexpr const char* HEADER = + "/*\n" + " * Copyright 2017 Google Inc.\n" + " *\n" + " * Use of this source code is governed by a BSD-style license that can be\n" + " * found in the LICENSE file.\n" + " */\n" + "/*****************************************************************************************\n" + " ******************** This file was generated by sksllex. Do not edit. *******************\n" + " *****************************************************************************************/\n"; + +void writeH(const DFA& dfa, const char* lexer, const char* token, + const std::vector& tokens, const char* hPath) { + std::ofstream out(hPath); + SkASSERT(out.good()); + out << HEADER; + out << "#ifndef SKSL_" << lexer << "\n"; + out << "#define SKSL_" << lexer << "\n"; + out << "#include \n"; + out << "#include \n"; + out << "namespace SkSL {\n"; + out << "\n"; + out << "struct " << token << " {\n"; + out << " enum Kind {\n"; + for (const std::string& t : tokens) { + out << " #undef " << t << "\n"; + out << " " << t << ",\n"; + } + out << " };\n"; + out << "\n"; + out << " " << token << "()\n"; + out << " : fKind(Kind::INVALID)\n"; + out << " , fOffset(-1)\n"; + out << " , fLength(-1) {}\n"; + out << "\n"; + out << " " << token << "(Kind kind, int32_t offset, int32_t length)\n"; + out << " : fKind(kind)\n"; + out << " , fOffset(offset)\n"; + out << " , fLength(length) {}\n"; + out << "\n"; + out << " Kind fKind;\n"; + out << " int fOffset;\n"; + out << " int fLength;\n"; + out << "};\n"; + out << "\n"; + out << "class " << lexer << " {\n"; + out << "public:\n"; + out << " void start(const char* text, int32_t length) {\n"; + out << " fText = text;\n"; + out << " fLength = length;\n"; + out << " fOffset = 0;\n"; + out << " }\n"; + out << "\n"; + out << " " << token << " next();\n"; + out << "\n"; + out << "private:\n"; + out << " const char* fText;\n"; + out << " int32_t fLength;\n"; + out << " int32_t fOffset;\n"; + out << "};\n"; + out << "\n"; + out << "} // namespace\n"; + out << "#endif\n"; +} + +void writeCPP(const DFA& dfa, const char* lexer, const char* token, const char* include, + const char* cppPath) { + std::ofstream out(cppPath); + SkASSERT(out.good()); + out << HEADER; + out << "#include \"" << include << "\"\n"; + out << "\n"; + out << "namespace SkSL {\n"; + out << "\n"; + + size_t states = 0; + for (const auto& row : dfa.fTransitions) { + states = std::max(states, row.size()); + } + // arbitrarily-chosen character which is greater than START_CHAR and should not appear in actual + // input + out << "static const uint8_t INVALID_CHAR = 18;"; + out << "static int8_t mappings[" << dfa.fCharMappings.size() << "] = {\n "; + const char* separator = ""; + for (int m : dfa.fCharMappings) { + out << separator << std::to_string(m); + separator = ", "; + } + out << "\n};\n"; + out << "static int16_t transitions[" << dfa.fTransitions.size() << "][" << states << "] = {\n"; + for (size_t c = 0; c < dfa.fTransitions.size(); ++c) { + out << " {"; + for (size_t j = 0; j < states; ++j) { + if ((size_t) c < dfa.fTransitions.size() && j < dfa.fTransitions[c].size()) { + out << " " << dfa.fTransitions[c][j] << ","; + } else { + out << " 0,"; + } + } + out << " },\n"; + } + out << "};\n"; + out << "\n"; + + out << "static int8_t accepts[" << states << "] = {"; + for (size_t i = 0; i < states; ++i) { + if (i < dfa.fAccepts.size()) { + out << " " << dfa.fAccepts[i] << ","; + } else { + out << " " << INVALID << ","; + } + } + out << " };\n"; + out << "\n"; + + out << token << " " << lexer << "::next() {\n"; + out << " // note that we cheat here: normally a lexer needs to worry about the case\n"; + out << " // where a token has a prefix which is not itself a valid token - for instance, \n"; + out << " // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid\n"; + out << " // tokens. Our grammar doesn't have this property, so we can simplify the logic\n"; + out << " // a bit.\n"; + out << " int32_t startOffset = fOffset;\n"; + out << " if (startOffset == fLength) {\n"; + out << " return " << token << "(" << token << "::END_OF_FILE, startOffset, 0);\n"; + out << " }\n"; + out << " int16_t state = 1;\n"; + out << " for (;;) {\n"; + out << " if (fOffset >= fLength) {\n"; + out << " if (accepts[state] == -1) {\n"; + out << " return Token(Token::END_OF_FILE, startOffset, 0);\n"; + out << " }\n"; + out << " break;\n"; + out << " }\n"; + out << " uint8_t c = (uint8_t) fText[fOffset];"; + out << " if (c <= 8 || c >= " << dfa.fCharMappings.size() << ") {"; + out << " c = INVALID_CHAR;"; + out << " }"; + out << " int16_t newState = transitions[mappings[c]][state];\n"; + out << " if (!newState) {\n"; + out << " break;\n"; + out << " }\n"; + out << " state = newState;"; + out << " ++fOffset;\n"; + out << " }\n"; + out << " Token::Kind kind = (" << token << "::Kind) accepts[state];\n"; + out << " return " << token << "(kind, startOffset, fOffset - startOffset);\n"; + out << "}\n"; + out << "\n"; + out << "} // namespace\n"; +} + +void process(const char* inPath, const char* lexer, const char* token, const char* hPath, + const char* cppPath) { + NFA nfa; + std::vector tokens; + tokens.push_back("END_OF_FILE"); + std::string line; + std::ifstream in(inPath); + while (std::getline(in, line)) { + std::istringstream split(line); + std::string name, delimiter, pattern; + if (split >> name >> delimiter >> pattern) { + SkASSERT(split.eof()); + SkASSERT(name != ""); + SkASSERT(delimiter == "="); + SkASSERT(pattern != ""); + tokens.push_back(name); + if (pattern[0] == '"') { + SkASSERT(pattern.size() > 2 && pattern[pattern.size() - 1] == '"'); + RegexNode node = RegexNode(RegexNode::kChar_Kind, pattern[1]); + for (size_t i = 2; i < pattern.size() - 1; ++i) { + node = RegexNode(RegexNode::kConcat_Kind, node, + RegexNode(RegexNode::kChar_Kind, pattern[i])); + } + nfa.addRegex(node); + } + else { + nfa.addRegex(RegexParser().parse(pattern)); + } + } + } + NFAtoDFA converter(&nfa); + DFA dfa = converter.convert(); + writeH(dfa, lexer, token, tokens, hPath); + writeCPP(dfa, lexer, token, (std::string("src/sksl/SkSL") + lexer + ".h").c_str(), cppPath); +} + +int main(int argc, const char** argv) { + if (argc != 6) { + printf("usage: sksllex \n"); + exit(1); + } + process(argv[1], argv[2], argv[3], argv[4], argv[5]); + return 0; +} diff --git a/gfx/skia/skia/src/sksl/lex/NFA.cpp b/gfx/skia/skia/src/sksl/lex/NFA.cpp new file mode 100644 index 0000000000..0c94bf7234 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFA.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/NFA.h" + +int NFA::match(std::string s) const { + std::vector states = fStartStates; + for (size_t i = 0; i < s.size(); ++i) { + std::vector next; + for (int id : states) { + if (fStates[id].accept(s[i])) { + for (int nextId : fStates[id].fNext) { + if (fStates[nextId].fKind != NFAState::kRemapped_Kind) { + next.push_back(nextId); + } else { + next.insert(next.end(), fStates[nextId].fData.begin(), + fStates[nextId].fData.end()); + } + } + } + } + if (!next.size()) { + return INVALID; + } + states = next; + } + int accept = INVALID; + for (int id : states) { + if (fStates[id].fKind == NFAState::kAccept_Kind) { + int result = fStates[id].fData[0]; + if (accept == INVALID || result < accept) { + accept = result; + } + } + } + return accept; +} diff --git a/gfx/skia/skia/src/sksl/lex/NFA.h b/gfx/skia/skia/src/sksl/lex/NFA.h new file mode 100644 index 0000000000..49d18fcf83 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFA.h @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NFA +#define SKSL_NFA + +#include "src/sksl/lex/NFAState.h" +#include "src/sksl/lex/RegexNode.h" + +/** + * A nondeterministic finite automaton for matching regular expressions. The NFA is initialized with + * a number of regular expressions, and then matches a string against all of them simultaneously. + */ +struct NFA { + /** + * Adds a new regular expression to the set of expressions matched by this automaton, returning + * its index. + */ + int addRegex(const RegexNode& regex) { + std::vector accept; + // we reserve token 0 for END_OF_FILE, so this starts at 1 + accept.push_back(this->addState(NFAState(++fRegexCount))); + std::vector startStates = regex.createStates(this, accept); + fStartStates.insert(fStartStates.end(), startStates.begin(), startStates.end()); + return fStartStates.size() - 1; + } + + /** + * Adds a new state to the NFA, returning its index. + */ + int addState(NFAState s) { + fStates.push_back(std::move(s)); + return fStates.size() - 1; + } + + /** + * Matches a string against all of the regexes added to this NFA. Returns the index of the first + * (in addRegex order) matching expression, or -1 if no match. This is relatively slow and used + * only for debugging purposes; the NFA should be converted to a DFA before actual use. + */ + int match(std::string s) const; + + int fRegexCount = 0; + + std::vector fStates; + + std::vector fStartStates; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/NFAState.h b/gfx/skia/skia/src/sksl/lex/NFAState.h new file mode 100644 index 0000000000..6f6e289b7f --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFAState.h @@ -0,0 +1,150 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NFASTATE +#define SKSL_NFASTATE + +#include +#include + +#include "src/sksl/lex/LexUtil.h" + +struct NFAState { + enum Kind { + // represents an accept state - if the NFA ends up in this state, we have successfully + // matched the token indicated by fData[0] + kAccept_Kind, + // matches the single character fChar + kChar_Kind, + // the regex '.'; matches any char but '\n' + kDot_Kind, + // a state which serves as a placeholder for the states indicated in fData. When we + // transition to this state, we instead transition to all of the fData states. + kRemapped_Kind, + // contains a list of true/false values in fData. fData[c] tells us whether we accept the + // character c. + kTable_Kind + }; + + NFAState(Kind kind, std::vector next) + : fKind(kind) + , fNext(std::move(next)) {} + + NFAState(char c, std::vector next) + : fKind(kChar_Kind) + , fChar(c) + , fNext(std::move(next)) {} + + NFAState(std::vector states) + : fKind(kRemapped_Kind) + , fData(std::move(states)) {} + + NFAState(bool inverse, std::vector accepts, std::vector next) + : fKind(kTable_Kind) + , fInverse(inverse) + , fNext(std::move(next)) { + for (bool b : accepts) { + fData.push_back(b); + } + } + + NFAState(int token) + : fKind(kAccept_Kind) { + fData.push_back(token); + } + + bool accept(char c) const { + switch (fKind) { + case kAccept_Kind: + return false; + case kChar_Kind: + return c == fChar; + case kDot_Kind: + return c != '\n'; + case kTable_Kind: { + bool value; + if ((size_t) c < fData.size()) { + value = fData[c]; + } else { + value = false; + } + return value != fInverse; + } + default: + ABORT("unreachable"); + } + } + + std::string description() const { + switch (fKind) { + case kAccept_Kind: + return "Accept(" + std::to_string(fData[0]) + ")"; + case kChar_Kind: { + std::string result = "Char('" + std::string(1, fChar) + "'"; + for (int v : fNext) { + result += ", "; + result += std::to_string(v); + } + result += ")"; + return result; + } + case kDot_Kind: { + std::string result = "Dot("; + const char* separator = ""; + for (int v : fNext) { + result += separator; + result += std::to_string(v); + separator = ", "; + } + result += ")"; + return result; + } + case kRemapped_Kind: { + std::string result = "Remapped("; + const char* separator = ""; + for (int v : fData) { + result += separator; + result += std::to_string(v); + separator = ", "; + } + result += ")"; + return result; + } + case kTable_Kind: { + std::string result = std::string("Table(") + (fInverse ? "true" : "false") + ", ["; + const char* separator = ""; + for (int v : fData) { + result += separator; + result += v ? "true" : "false"; + separator = ", "; + } + result += "]"; + for (int n : fNext) { + result += ", "; + result += std::to_string(n); + } + result += ")"; + return result; + } + default: + ABORT("unreachable"); + } + } + + Kind fKind; + + char fChar = 0; + + bool fInverse = false; + + std::vector fData; + + // states we transition to upon a succesful match from this state + std::vector fNext; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h b/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h new file mode 100644 index 0000000000..3c9038006d --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h @@ -0,0 +1,168 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef NFAtoDFA_DEFINED +#define NFAtoDFA_DEFINED + +#include "src/sksl/lex/DFA.h" +#include "src/sksl/lex/DFAState.h" +#include "src/sksl/lex/NFA.h" +#include "src/sksl/lex/NFAState.h" + +#include +#include +#include +#include +#include +#include + +/** + * Converts a nondeterministic finite automaton to a deterministic finite automaton. Since NFAs and + * DFAs differ only in that an NFA allows multiple states at the same time, we can find each + * possible combination of simultaneous NFA states and give this combination a label. These labelled + * nodes are our DFA nodes, since we can only be in one such unique set of NFA states at a time. + * + * As an NFA can end up in multiple accept states at the same time (for instance, the token "while" + * is valid for both WHILE and IDENTIFIER), we disambiguate by preferring the first matching regex + * (in terms of the order in which they were added to the NFA). + */ +class NFAtoDFA { +public: + static constexpr char START_CHAR = 9; + static constexpr char END_CHAR = 126; + + NFAtoDFA(NFA* nfa) + : fNFA(*nfa) {} + + /** + * Returns a DFA created from the NFA. + */ + DFA convert() { + // create state 0, the "reject" state + getState(DFAState::Label({})); + // create a state representing being in all of the NFA's start states at once + std::vector startStates = fNFA.fStartStates; + std::sort(startStates.begin(), startStates.end()); + // this becomes state 1, our start state + DFAState* start = getState(DFAState::Label(startStates)); + this->scanState(start); + + this->computeMappings(); + + int stateCount = 0; + for (const auto& row : fTransitions) { + stateCount = std::max(stateCount, (int) row.size()); + } + return DFA(fCharMappings, fTransitions, fAccepts); + } + +private: + /** + * Returns an existing state with the given label, or creates a new one and returns it. + */ + DFAState* getState(DFAState::Label label) { + auto found = fStates.find(label); + if (found == fStates.end()) { + int id = fStates.size(); + fStates[label] = std::unique_ptr(new DFAState(id, label)); + return fStates[label].get(); + } + return found->second.get(); + } + + void add(int nfaState, std::vector* states) { + NFAState state = fNFA.fStates[nfaState]; + if (state.fKind == NFAState::kRemapped_Kind) { + for (int next : state.fData) { + this->add(next, states); + } + } else { + for (int state : *states) { + if (nfaState == state) { + return; + } + } + states->push_back(nfaState); + } + } + + void addTransition(char c, int start, int next) { + while (fTransitions.size() <= (size_t) c) { + fTransitions.push_back(std::vector()); + } + std::vector& row = fTransitions[c]; + while (row.size() <= (size_t) start) { + row.push_back(INVALID); + } + row[start] = next; + } + + void scanState(DFAState* state) { + state->fIsScanned = true; + for (char c = START_CHAR; c <= END_CHAR; ++c) { + std::vector next; + int bestAccept = INT_MAX; + for (int idx : state->fLabel.fStates) { + const NFAState& nfaState = fNFA.fStates[idx]; + if (nfaState.accept(c)) { + for (int nextState : nfaState.fNext) { + if (fNFA.fStates[nextState].fKind == NFAState::kAccept_Kind) { + bestAccept = std::min(bestAccept, fNFA.fStates[nextState].fData[0]); + } + this->add(nextState, &next); + } + } + } + std::sort(next.begin(), next.end()); + DFAState* nextState = this->getState(DFAState::Label(next)); + this->addTransition(c, state->fId, nextState->fId); + if (bestAccept != INT_MAX) { + while (fAccepts.size() <= (size_t) nextState->fId) { + fAccepts.push_back(INVALID); + } + fAccepts[nextState->fId] = bestAccept; + } + if (!nextState->fIsScanned) { + this->scanState(nextState); + } + } + } + + // collapse rows with the same transitions to a single row. This is common, as each row + // represents a character and often there are many characters for which all transitions are + // identical (e.g. [0-9] are treated the same way by all lexer rules) + void computeMappings() { + // mappings[] = + std::vector*> uniques; + // this could be done more efficiently, but O(n^2) is plenty fast for our purposes + for (size_t i = 0; i < fTransitions.size(); ++i) { + int found = -1; + for (size_t j = 0; j < uniques.size(); ++j) { + if (*uniques[j] == fTransitions[i]) { + found = j; + break; + } + } + if (found == -1) { + found = (int) uniques.size(); + uniques.push_back(&fTransitions[i]); + } + fCharMappings.push_back(found); + } + std::vector> newTransitions; + for (std::vector* row : uniques) { + newTransitions.push_back(*row); + } + fTransitions = newTransitions; + } + + const NFA& fNFA; + std::unordered_map> fStates; + std::vector> fTransitions; + std::vector fCharMappings; + std::vector fAccepts; +}; +#endif // NFAtoDFA_DEFINED diff --git a/gfx/skia/skia/src/sksl/lex/RegexNode.cpp b/gfx/skia/skia/src/sksl/lex/RegexNode.cpp new file mode 100644 index 0000000000..90eeda2bff --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexNode.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/RegexNode.h" + +#include "src/sksl/lex/NFA.h" + +std::vector RegexNode::createStates(NFA* nfa, const std::vector& accept) const { + std::vector result; + switch (fKind) { + case kChar_Kind: + result.push_back(nfa->addState(NFAState(fPayload.fChar, accept))); + break; + case kCharset_Kind: { + std::vector chars; + for (const RegexNode& child : fChildren) { + if (child.fKind == kChar_Kind) { + while (chars.size() <= (size_t) child.fPayload.fChar) { + chars.push_back(false); + } + chars[child.fPayload.fChar] = true; + } else { + SkASSERT(child.fKind == kRange_Kind); + while (chars.size() <= (size_t) child.fChildren[1].fPayload.fChar) { + chars.push_back(false); + } + for (char c = child.fChildren[0].fPayload.fChar; + c <= child.fChildren[1].fPayload.fChar; + ++c) { + chars[c] = true; + } + } + } + result.push_back(nfa->addState(NFAState(fPayload.fBool, chars, accept))); + break; + } + case kConcat_Kind: { + std::vector right = fChildren[1].createStates(nfa, accept); + result = fChildren[0].createStates(nfa, right); + break; + } + case kDot_Kind: + result.push_back(nfa->addState(NFAState(NFAState::kDot_Kind, accept))); + break; + case kOr_Kind: { + std::vector states = fChildren[0].createStates(nfa, accept); + result.insert(result.end(), states.begin(), states.end()); + states = fChildren[1].createStates(nfa, accept); + result.insert(result.end(), states.begin(), states.end()); + break; + } + case kPlus_Kind: { + std::vector next = accept; + std::vector placeholder; + int id = nfa->addState(NFAState(placeholder)); + next.push_back(id); + result = fChildren[0].createStates(nfa, next); + nfa->fStates[id] = NFAState(result); + break; + } + case kQuestion_Kind: + result = fChildren[0].createStates(nfa, accept); + result.insert(result.end(), accept.begin(), accept.end()); + break; + case kRange_Kind: + ABORT("unreachable"); + case kStar_Kind: { + std::vector next = accept; + std::vector placeholder; + int id = nfa->addState(NFAState(placeholder)); + next.push_back(id); + result = fChildren[0].createStates(nfa, next); + result.insert(result.end(), accept.begin(), accept.end()); + nfa->fStates[id] = NFAState(result); + break; + } + } + return result; +} + +std::string RegexNode::description() const { + switch (fKind) { + case kChar_Kind: + return std::string(1, fPayload.fChar); + case kCharset_Kind: { + std::string result("["); + if (fPayload.fBool) { + result += "^"; + } + for (const RegexNode& c : fChildren) { + result += c.description(); + } + result += "]"; + return result; + } + case kConcat_Kind: + return fChildren[0].description() + fChildren[1].description(); + case kDot_Kind: + return "."; + case kOr_Kind: + return "(" + fChildren[0].description() + "|" + fChildren[1].description() + ")"; + case kPlus_Kind: + return fChildren[0].description() + "+"; + case kQuestion_Kind: + return fChildren[0].description() + "?"; + case kRange_Kind: + return fChildren[0].description() + "-" + fChildren[1].description(); + case kStar_Kind: + return fChildren[0].description() + "*"; + default: + return "<" + std::to_string(fKind) + ">"; + } +} diff --git a/gfx/skia/skia/src/sksl/lex/RegexNode.h b/gfx/skia/skia/src/sksl/lex/RegexNode.h new file mode 100644 index 0000000000..e3aa65b3d1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexNode.h @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_REGEXNODE +#define SKSL_REGEXNODE + +#include +#include + +struct NFA; + +/** + * Represents a node in the parse tree of a regular expression. + */ +struct RegexNode { + enum Kind { + kChar_Kind, + kCharset_Kind, + kConcat_Kind, + kDot_Kind, + kOr_Kind, + kPlus_Kind, + kRange_Kind, + kQuestion_Kind, + kStar_Kind + }; + + RegexNode(Kind kind) + : fKind(kind) {} + + RegexNode(Kind kind, char payload) + : fKind(kind) { + fPayload.fChar = payload; + } + + RegexNode(Kind kind, const char* children) + : fKind(kind) { + fPayload.fBool = false; + while (*children != '\0') { + fChildren.emplace_back(kChar_Kind, *children); + ++children; + } + } + + RegexNode(Kind kind, RegexNode child) + : fKind(kind) { + fChildren.push_back(std::move(child)); + } + + RegexNode(Kind kind, RegexNode child1, RegexNode child2) + : fKind(kind) { + fChildren.push_back(std::move(child1)); + fChildren.push_back(std::move(child2)); + } + + /** + * Creates NFA states for this node, with a successful match against this node resulting in a + * transition to all of the states in the accept vector. + */ + std::vector createStates(NFA* nfa, const std::vector& accept) const; + + std::string description() const; + + Kind fKind; + + union Payload { + char fChar; + bool fBool; + } fPayload; + + std::vector fChildren; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/RegexParser.cpp b/gfx/skia/skia/src/sksl/lex/RegexParser.cpp new file mode 100644 index 0000000000..9b5445e1a3 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexParser.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/RegexParser.h" + +#include "src/sksl/lex/LexUtil.h" + +RegexNode RegexParser::parse(std::string source) { + fSource = source; + fIndex = 0; + SkASSERT(fStack.size() == 0); + this->regex(); + SkASSERT(fStack.size() == 1); + SkASSERT(fIndex == source.size()); + return this->pop(); +} + +char RegexParser::peek() { + if (fIndex >= fSource.size()) { + return END; + } + return fSource[fIndex]; +} + +void RegexParser::expect(char c) { + if (this->peek() != c) { + printf("expected '%c' at index %d, but found '%c'", c, (int) fIndex, this->peek()); + exit(1); + } + ++fIndex; +} + +RegexNode RegexParser::pop() { + RegexNode result = fStack.top(); + fStack.pop(); + return result; +} + +void RegexParser::term() { + switch (this->peek()) { + case '(': this->group(); break; + case '[': this->set(); break; + case '.': this->dot(); break; + default: this->literal(); + } +} + +void RegexParser::quantifiedTerm() { + this->term(); + switch (this->peek()) { + case '*': fStack.push(RegexNode(RegexNode::kStar_Kind, this->pop())); ++fIndex; break; + case '+': fStack.push(RegexNode(RegexNode::kPlus_Kind, this->pop())); ++fIndex; break; + case '?': fStack.push(RegexNode(RegexNode::kQuestion_Kind, this->pop())); ++fIndex; break; + default: break; + } +} + +void RegexParser::sequence() { + this->quantifiedTerm(); + for (;;) { + switch (this->peek()) { + case END: // fall through + case '|': // fall through + case ')': return; + default: + this->sequence(); + RegexNode right = this->pop(); + RegexNode left = this->pop(); + fStack.emplace(RegexNode::kConcat_Kind, std::move(left), std::move(right)); + } + } +} + +RegexNode RegexParser::escapeSequence(char c) { + switch (c) { + case 'n': return RegexNode(RegexNode::kChar_Kind, '\n'); + case 'r': return RegexNode(RegexNode::kChar_Kind, '\r'); + case 't': return RegexNode(RegexNode::kChar_Kind, '\t'); + case 's': return RegexNode(RegexNode::kCharset_Kind, " \t\n\r"); + default: return RegexNode(RegexNode::kChar_Kind, c); + } +} + +void RegexParser::literal() { + char c = this->peek(); + if (c == '\\') { + ++fIndex; + fStack.push(this->escapeSequence(peek())); + ++fIndex; + } + else { + fStack.push(RegexNode(RegexNode::kChar_Kind, c)); + ++fIndex; + } +} + +void RegexParser::dot() { + this->expect('.'); + fStack.push(RegexNode(RegexNode::kDot_Kind)); +} + +void RegexParser::group() { + this->expect('('); + this->regex(); + this->expect(')'); +} + +void RegexParser::setItem() { + this->literal(); + if (this->peek() == '-') { + ++fIndex; + if (peek() == ']') { + fStack.push(RegexNode(RegexNode::kChar_Kind, '-')); + } + else { + literal(); + RegexNode end = this->pop(); + SkASSERT(end.fKind == RegexNode::kChar_Kind); + RegexNode start = this->pop(); + SkASSERT(start.fKind == RegexNode::kChar_Kind); + fStack.push(RegexNode(RegexNode::kRange_Kind, std::move(start), std::move(end))); + } + } +} + +void RegexParser::set() { + expect('['); + size_t depth = fStack.size(); + RegexNode set(RegexNode::kCharset_Kind); + if (this->peek() == '^') { + ++fIndex; + set.fPayload.fBool = true; + } + else { + set.fPayload.fBool = false; + } + for (;;) { + switch (this->peek()) { + case ']': + ++fIndex; + while (fStack.size() > depth) { + set.fChildren.push_back(this->pop()); + } + fStack.push(std::move(set)); + return; + case END: + printf("unterminated character set\n"); + exit(1); + default: + this->setItem(); + } + } +} + +void RegexParser::regex() { + this->sequence(); + switch (this->peek()) { + case '|': { + ++fIndex; + this->regex(); + RegexNode right = this->pop(); + RegexNode left = this->pop(); + fStack.push(RegexNode(RegexNode::kOr_Kind, left, right)); + break; + } + case END: // fall through + case ')': + return; + default: + SkASSERT(false); + } +} diff --git a/gfx/skia/skia/src/sksl/lex/RegexParser.h b/gfx/skia/skia/src/sksl/lex/RegexParser.h new file mode 100644 index 0000000000..c1753de368 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexParser.h @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_REGEXPARSER +#define SKSL_REGEXPARSER + +#include "src/sksl/lex/RegexNode.h" + +#include +#include + +/** + * Turns a simple regular expression into a parse tree. The regular expression syntax supports only + * the basic quantifiers ('*', '+', and '?'), alternation ('|'), character sets ('[a-z]'), and + * groups ('()'). + */ +class RegexParser { +public: + RegexNode parse(std::string source); + +private: + static constexpr char END = '\0'; + + char peek(); + + void expect(char c); + + RegexNode pop(); + + /** + * Matches a char literal, parenthesized group, character set, or dot ('.'). + */ + void term(); + + /** + * Matches a term followed by an optional quantifier ('*', '+', or '?'). + */ + void quantifiedTerm(); + + /** + * Matches a sequence of quantifiedTerms. + */ + void sequence(); + + /** + * Returns a node representing the given escape character (e.g. escapeSequence('n') returns a + * node which matches a newline character). + */ + RegexNode escapeSequence(char c); + + /** + * Matches a literal character or escape sequence. + */ + void literal(); + + /** + * Matches a dot ('.'). + */ + void dot(); + + /** + * Matches a parenthesized group. + */ + void group(); + + /** + * Matches a literal character, escape sequence, or character range from a character set. + */ + void setItem(); + + /** + * Matches a character set. + */ + void set(); + + void regex(); + + std::string fSource; + + size_t fIndex; + + std::stack fStack; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/sksl.lex b/gfx/skia/skia/src/sksl/lex/sksl.lex new file mode 100644 index 0000000000..3ed96fc1b1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/sksl.lex @@ -0,0 +1,98 @@ +FLOAT_LITERAL = [0-9]*\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?|[0-9]+([eE][+-]?[0-9]+) +INT_LITERAL = [0-9]+|0x[0-9a-fA-F]+ +TRUE_LITERAL = "true" +FALSE_LITERAL = "false" +IF = "if" +STATIC_IF = "@if" +ELSE = "else" +FOR = "for" +WHILE = "while" +DO = "do" +SWITCH = "switch" +STATIC_SWITCH = "@switch" +CASE = "case" +DEFAULT = "default" +BREAK = "break" +CONTINUE = "continue" +DISCARD = "discard" +RETURN = "return" +NULL_LITERAL = "null" +IN = "in" +OUT = "out" +INOUT = "inout" +UNIFORM = "uniform" +CONST = "const" +FLAT = "flat" +NOPERSPECTIVE = "noperspective" +READONLY = "readonly" +WRITEONLY = "writeonly" +COHERENT = "coherent" +VOLATILE = "volatile" +RESTRICT = "restrict" +BUFFER = "buffer" +HASSIDEEFFECTS = "sk_has_side_effects" +PLS = "__pixel_localEXT" +PLSIN = "__pixel_local_inEXT" +PLSOUT = "__pixel_local_outEXT" +STRUCT = "struct" +LAYOUT = "layout" +PRECISION = "precision" +ENUM = "enum" +CLASS = "class" +IDENTIFIER = [a-zA-Z_$]([0-9]|[a-zA-Z_$])* +DIRECTIVE = #[a-zA-Z_$]([0-9]|[a-zA-Z_$])* +SECTION = @[a-zA-Z_$]([0-9]|[a-zA-Z_$])* +LPAREN = "(" +RPAREN = ")" +LBRACE = "{" +RBRACE = "}" +LBRACKET = "[" +RBRACKET = "]" +DOT = "." +COMMA = "," +PLUSPLUS = "++" +MINUSMINUS = "--" +PLUS = "+" +MINUS = "-" +STAR = "*" +SLASH = "/" +PERCENT = "%" +SHL = "<<" +SHR = ">>" +BITWISEOR = "|" +BITWISEXOR = "^" +BITWISEAND = "&" +BITWISENOT = "~" +LOGICALOR = "||" +LOGICALXOR = "^^" +LOGICALAND = "&&" +LOGICALNOT = "!" +QUESTION = "?" +COLONCOLON = "::" +COLON = ":" +EQ = "=" +EQEQ = "==" +NEQ = "!=" +GT = ">" +LT = "<" +GTEQ = ">=" +LTEQ = "<=" +PLUSEQ = "+=" +MINUSEQ = "-=" +STAREQ = "*=" +SLASHEQ = "/=" +PERCENTEQ = "%=" +SHLEQ = "<<=" +SHREQ = ">>=" +BITWISEOREQ = "|=" +BITWISEXOREQ = "^=" +BITWISEANDEQ = "&=" +LOGICALOREQ = "||=" +LOGICALXOREQ = "^^=" +LOGICALANDEQ = "&&=" +SEMICOLON = ";" +ARROW = "->" +WHITESPACE = \s+ +LINE_COMMENT = //.* +BLOCK_COMMENT = /\*([^*]|\*[^/])*\*/ +INVALID = . \ No newline at end of file diff --git a/gfx/skia/skia/src/sksl/sksl_enums.inc b/gfx/skia/skia/src/sksl/sksl_enums.inc new file mode 100644 index 0000000000..eb60b3b0c5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_enums.inc @@ -0,0 +1,35 @@ +R"(/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +/*************************************************************************************************/ +/* This file is used from both C++ and SkSL, so we need to stick to syntax compatible with both. */ +/*************************************************************************************************/ + +/** + * We have coverage effects that clip rendering to the edge of some geometric primitive. + * This enum specifies how that clipping is performed. Not all factories that take a + * GrProcessorEdgeType will succeed with all values and it is up to the caller to check for + * a NULL return. + */ +enum class GrClipEdgeType { + kFillBW, + kFillAA, + kInverseFillBW, + kInverseFillAA, + kHairlineAA, + + kLast = kHairlineAA +}; + +enum class PMConversion { + kToPremul = 0, + kToUnpremul = 1, + kPMConversionCnt = 2 +}; + +)" diff --git a/gfx/skia/skia/src/sksl/sksl_fp.inc b/gfx/skia/skia/src/sksl/sksl_fp.inc new file mode 100644 index 0000000000..0608230302 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_fp.inc @@ -0,0 +1,34 @@ +STRINGIFY( + +// defines built-in interfaces supported by SkiaSL fragment shaders + +layout(builtin=15) in float4 sk_FragCoord; +layout(builtin=3) float sk_ClipDistance[1]; + +// 9999 is a temporary value that causes us to ignore these declarations beyond +// adding them to the symbol table. This works fine in GLSL (where they do not +// require any further handling) but will fail in SPIR-V. We'll have a better +// solution for this soon. +layout(builtin=9999) float4 gl_LastFragData[1]; +layout(builtin=9999) half4 gl_LastFragColor; +layout(builtin=9999) half4 gl_LastFragColorARM; +layout(builtin=9999) int gl_SampleMaskIn[1]; +layout(builtin=9999) out int gl_SampleMask[1]; +layout(builtin=9999) half4 gl_SecondaryFragColorEXT; + +layout(builtin=10003) half4 sk_InColor; +layout(builtin=10004) out half4 sk_OutColor; +layout(builtin=10005) float2[] sk_TransformedCoords2D; +layout(builtin=10006) sampler2D[] sk_TextureSamplers; +layout(builtin=10011) half sk_Width; +layout(builtin=10012) half sk_Height; + +half4 sample(fragmentProcessor fp); +half4 sample(fragmentProcessor fp, float2 coords); +half4 sample(fragmentProcessor fp, half4 input); +half4 sample(fragmentProcessor fp, half4 input, float2 coords); +half4 sample(fragmentProcessor? fp); +half4 sample(fragmentProcessor? fp, float2 coords); +half4 sample(fragmentProcessor? fp, half4 input); +half4 sample(fragmentProcessor? fp, half4 input, float2 coords); +) diff --git a/gfx/skia/skia/src/sksl/sksl_frag.inc b/gfx/skia/skia/src/sksl/sksl_frag.inc new file mode 100644 index 0000000000..5bc5f55dce --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_frag.inc @@ -0,0 +1,23 @@ +STRINGIFY( + +// defines built-in interfaces supported by SkiaSL fragment shaders + +// See "enum SpvBuiltIn_" in ./spirv.h +layout(builtin=15) in float4 sk_FragCoord; +layout(builtin=17) in bool sk_Clockwise; // Similar to gl_FrontFacing, but defined in device space. +layout(builtin=3) float sk_ClipDistance[1]; + +// 9999 is a temporary value that causes us to ignore these declarations beyond +// adding them to the symbol table. This works fine in GLSL (where they do not +// require any further handling) but will fail in SPIR-V. We'll have a better +// solution for this soon. +layout(builtin=9999) int gl_SampleMaskIn[1]; +layout(builtin=9999) out int gl_SampleMask[1]; +layout(builtin=9999) out half4 gl_SecondaryFragColorEXT; + +layout(location=0,index=0,builtin=10001) out half4 sk_FragColor; +layout(builtin=10008) half4 sk_LastFragColor; +layout(builtin=10011) half sk_Width; +layout(builtin=10012) half sk_Height; + +) diff --git a/gfx/skia/skia/src/sksl/sksl_geom.inc b/gfx/skia/skia/src/sksl/sksl_geom.inc new file mode 100644 index 0000000000..6d535d26a3 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_geom.inc @@ -0,0 +1,24 @@ +STRINGIFY( + +// defines built-in interfaces supported by SkiaSL geometry shaders + +layout(builtin=10002) in sk_PerVertex { + layout(builtin=0) float4 sk_Position; + layout(builtin=1) float sk_PointSize; + layout(builtin=3) float sk_ClipDistance[1]; +} sk_in[1]; + +layout(builtin=10007) out sk_PerVertex { + layout(builtin=0) float4 sk_Position; + layout(builtin=1) float sk_PointSize; + layout(builtin=3) float sk_ClipDistance[1]; +}; + +layout(builtin=8) in int sk_InvocationID; + +sk_has_side_effects void EmitStreamVertex(int stream); +sk_has_side_effects void EndStreamPrimitive(int stream); +sk_has_side_effects void EmitVertex(); +sk_has_side_effects void EndPrimitive(); + +) diff --git a/gfx/skia/skia/src/sksl/sksl_gpu.inc b/gfx/skia/skia/src/sksl/sksl_gpu.inc new file mode 100644 index 0000000000..c027c2cc33 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_gpu.inc @@ -0,0 +1,299 @@ +STRINGIFY( + +// defines built-in functions supported by SkSL when running on a GPU + +$genType radians($genType degrees); +$genType sin($genType angle); +$genType cos($genType angle); +$genType tan($genType angle); +$genType asin($genType x); +$genType acos($genType x); +$genType atan($genType y, $genType x); +$genType atan($genType y_over_x); +$genType sinh($genType x); +$genType cosh($genType x); +$genType tanh($genType x); +$genType asinh($genType x); +$genType acosh($genType x); +$genType atanh($genType x); +$genType pow($genType x, $genType y); +$genType exp($genType x); +$genType log($genType x); +$genType exp2($genType x); +$genType log2($genType x); +$genType sqrt($genType x); +$genHType radians($genHType degrees); +$genHType sin($genHType angle); +$genHType cos($genHType angle); +$genHType tan($genHType angle); +$genHType asin($genHType x); +$genHType acos($genHType x); +$genHType atan($genHType y, $genHType x); +$genHType atan($genHType y_over_x); +$genHType sinh($genHType x); +$genHType cosh($genHType x); +$genHType tanh($genHType x); +$genHType asinh($genHType x); +$genHType acosh($genHType x); +$genHType atanh($genHType x); +$genHType pow($genHType x, $genHType y); +$genHType exp($genHType x); +$genHType log($genHType x); +$genHType exp2($genHType x); +$genHType log2($genHType x); +$genHType sqrt($genHType x); +$genType inversesqrt($genType x); +$genType abs($genType x); +$genHType abs($genHType x); +$genIType abs($genIType x); +$genType sign($genType x); +$genHType sign($genHType x); +$genIType sign($genIType x); +$genType floor($genType x); +$genHType floor($genHType x); +$genType trunc($genType x); +$genHType trunc($genHType x); +$genType round($genType x); +$genHType round($genHType x); +$genType roundEven($genType x); +$genHType roundEven($genHType x); +$genType ceil($genType x); +$genHType ceil($genHType x); +$genType fract($genType x); +$genHType fract($genHType x); +$genType mod($genType x, float y); +$genType mod($genType x, $genType y); +$genHType mod($genHType x, half y); +$genHType mod($genHType x, $genType y); +$genType modf($genType x, out $genType i); +$genHType modf($genHType x, out $genHType i); +$genType min($genType x, $genType y); +$genType min($genType x, float y); +$genHType min($genHType x, $genHType y); +$genHType min($genHType x, half y); +$genIType min($genIType x, $genIType y); +$genIType min($genIType x, int y); +$genType max($genType x, $genType y); +$genType max($genType x, float y); +$genHType max($genHType x, $genHType y); +$genHType max($genHType x, half y); +$genIType max($genIType x, $genIType y); +$genIType max($genIType x, int y); +$genType clamp($genType x, $genType minVal, $genType maxVal); +$genType clamp($genType x, float minVal, float maxVal); +$genHType clamp($genHType x, $genHType minVal, $genHType maxVal); +$genHType clamp($genHType x, half minVal, half maxVal); +$genIType clamp($genIType x, $genIType minVal, $genIType maxVal); +$genIType clamp($genIType x, int minVal, int maxVal); +$genType saturate($genType x); +$genHType saturate($genHType x); +$genType mix($genType x, $genType y, $genType a); +$genType mix($genType x, $genType y, float a); +$genHType mix($genHType x, $genHType y, $genHType a); +$genHType mix($genHType x, $genHType y, half a); +$genType mix($genType x, $genType y, $genBType a); +$genIType mix($genIType x, $genIType y, $genBType a); +$genBType mix($genBType x, $genBType y, $genBType a); +$genType step($genType edge, $genType x); +$genType step(float edge, $genType x); +$genHType step($genHType edge, $genHType x); +$genHType step(half edge, $genHType x); +$genType smoothstep($genType edge0, $genType edge1, $genType x); +$genType smoothstep(float edge0, float edge1, $genType x); +$genHType smoothstep($genHType edge0, $genHType edge1, $genHType x); +$genHType smoothstep(half edge0, half edge1, $genHType x); +$genBType isnan($genType x); +$genBType isnan($genDType x); +$genBType isinf($genType x); +$genBType isinf($genDType x); +$genIType floatBitsToInt($genType value); +$genType intBitsTofloat($genIType value); +$genType uintBitsTofloat($genUType value); +$genType fma($genType a, $genType b, $genType c); +$genHType fma($genHType a, $genHType b, $genHType c); +$genDType fma($genDType a, $genDType b, $genDType c); +sk_has_side_effects $genType frexp($genType x, out $genIType exp); +$genType ldexp($genType x, in $genIType exp); +uint packUnorm2x16(float2 v); +uint packSnorm2x16(float2 v); +uint packUnorm4x8(float4 v); +uint packSnorm4x8(float4 v); +float2 unpackUnorm2x16(uint p); +float2 unpackSnorm2x16(uint p); +float4 unpackUnorm4x8(uint p); +float4 unpackSnorm4x8(uint p); +uint2 unpackDouble2x32(double v); +uint packHalf2x16(float2 v); +float2 unpackHalf2x16(uint v); +float length($genType x); +half length($genHType x); +double length($genDType x); +float distance($genType p0, $genType p1); +half distance($genHType p0, $genHType p1); +double distance($genDType p0, $genDType p1); +float dot($genType x, $genType y); +half dot($genHType x, $genHType y); +double dot($genDType x, $genDType y); +float3 cross(float3 x, float3 y); +half3 cross(half3 x, half3 y); +double3 cross(double3 x, double3 y); +$genType normalize($genType x); +$genHType normalize($genHType x); +$genDType normalize($genDType x); +float4 ftransform(); +$genType faceforward($genType N, $genType I, $genType Nref); +$genHType faceforward($genHType N, $genHType I, $genHType Nref); +$genDType faceforward($genDType N, $genDType I, $genDType Nref); +$genType reflect($genType I, $genType N); +$genHType reflect($genHType I, $genHType N); +$genDType reflect($genDType I, $genDType N); +$genType refract($genType I, $genType N, float eta); +$genHType refract($genHType I, $genHType N, float eta); +$genDType refract($genDType I, $genDType N, float eta); +$mat matrixCompMult($mat x, $mat y); +float2x2 outerProduct(float2 c, float2 r); +float3x3 outerProduct(float3 c, float3 r); +float4x3 outerProduct(float4 c, float4 r); +float2x3 outerProduct(float3 c, float2 r); +float3x2 outerProduct(float2 c, float3 r); +float2x4 outerProduct(float4 c, float2 r); +float4x2 outerProduct(float2 c, float4 r); +float3x4 outerProduct(float4 c, float3 r); +float4x3 outerProduct(float3 c, float4 r); +half2x2 outerProduct(half2 c, half2 r); +half3x3 outerProduct(half3 c, half3 r); +half4x3 outerProduct(half4 c, half4 r); +half2x3 outerProduct(half3 c, half2 r); +half3x2 outerProduct(half2 c, half3 r); +half2x4 outerProduct(half4 c, half2 r); +half4x2 outerProduct(half2 c, half4 r); +half3x4 outerProduct(half4 c, half3 r); +half4x3 outerProduct(half3 c, half4 r); +float2x2 transpose(float2x2 m); +float3x3 transpose(float3x3 m); +float4x4 transpose(float4x4 m); +float2x3 transpose(float3x2 m); +float3x2 transpose(float2x3 m); +float2x4 transpose(float4x2 m); +float4x2 transpose(float2x4 m); +float3x4 transpose(float4x3 m); +float4x3 transpose(float3x4 m); +half2x2 transpose(half2x2 m); +half3x3 transpose(half3x3 m); +half4x4 transpose(half4x4 m); +half2x3 transpose(half3x2 m); +half3x2 transpose(half2x3 m); +half2x4 transpose(half4x2 m); +half4x2 transpose(half2x4 m); +half3x4 transpose(half4x3 m); +half4x3 transpose(half3x4 m); +float determinant(float2x2 m); +float determinant(float3x3 m); +float determinant(float4x4 m); +half determinant(half2x2 m); +half determinant(half3x3 m); +half determinant(half4x4 m); +float2x2 inverse(float2x2 m); +float3x3 inverse(float3x3 m); +float4x4 inverse(float4x4 m); +half2x2 inverse(half2x2 m); +half3x3 inverse(half3x3 m); +half4x4 inverse(half4x4 m); +$bvec lessThan($vec x, $vec y); +$bvec lessThan($hvec x, $hvec y); +$bvec lessThan($dvec x, $dvec y); +$bvec lessThan($ivec x, $ivec y); +$bvec lessThan($svec x, $svec y); +$bvec lessThan($usvec x, $usvec y); +$bvec lessThan($uvec x, $uvec y); +$bvec lessThanEqual($vec x, $vec y); +$bvec lessThanEqual($hvec x, $hvec y); +$bvec lessThanEqual($dvec x, $dvec y); +$bvec lessThanEqual($ivec x, $ivec y); +$bvec lessThanEqual($uvec x, $uvec y); +$bvec lessThanEqual($svec x, $svec y); +$bvec lessThanEqual($usvec x, $usvec y); +$bvec greaterThan($vec x, $vec y); +$bvec greaterThan($hvec x, $hvec y); +$bvec greaterThan($dvec x, $dvec y); +$bvec greaterThan($ivec x, $ivec y); +$bvec greaterThan($uvec x, $uvec y); +$bvec greaterThan($svec x, $svec y); +$bvec greaterThan($usvec x, $usvec y); +$bvec greaterThanEqual($vec x, $vec y); +$bvec greaterThanEqual($hvec x, $hvec y); +$bvec greaterThanEqual($dvec x, $dvec y); +$bvec greaterThanEqual($ivec x, $ivec y); +$bvec greaterThanEqual($uvec x, $uvec y); +$bvec greaterThanEqual($svec x, $svec y); +$bvec greaterThanEqual($usvec x, $usvec y); +$bvec equal($vec x, $vec y); +$bvec equal($hvec x, $hvec y); +$bvec equal($dvec x, $dvec y); +$bvec equal($ivec x, $ivec y); +$bvec equal($uvec x, $uvec y); +$bvec equal($svec x, $svec y); +$bvec equal($usvec x, $usvec y); +$bvec equal($bvec x, $bvec y); +$bvec notEqual($vec x, $vec y); +$bvec notEqual($hvec x, $hvec y); +$bvec notEqual($dvec x, $dvec y); +$bvec notEqual($ivec x, $ivec y); +$bvec notEqual($uvec x, $uvec y); +$bvec notEqual($svec x, $svec y); +$bvec notEqual($usvec x, $usvec y); +$bvec notEqual($bvec x, $bvec y); +bool any($bvec x); +bool all($bvec x); +$bvec not($bvec x); + +$genIType bitCount($genIType value); +$genIType bitCount($genUType value); +$genIType findLSB($genIType value); +$genIType findLSB($genUType value); +$genIType findMSB($genIType value); +$genIType findMSB($genUType value); + +sampler2D makeSampler2D(texture2D texture, sampler sampler); +int2 textureSize($gsampler2DRect sampler); + +half4 sample($gsampler1D sampler, float P); +half4 sample($gsampler1D sampler, float P, float bias); +half4 sample($gsampler2D sampler, float2 P); +// The above currently only expand to handle the float/fixed case. So we also declare this integer +// version of sample(). +int4 sample(isampler2D sampler, float2 P); +half4 sample(samplerExternalOES sampler, float2 P, float bias); +half4 sample(samplerExternalOES sampler, float2 P); + +half4 sample($gsampler2DRect sampler, float2 P); +half4 sample($gsampler2DRect sampler, float3 P); + +// Currently we do not support the generic types of loading subpassInput so we have some explicit +// versions that we currently use +float4 subpassLoad(subpassInput subpass); +float4 subpassLoad(subpassInputMS subpass, int sample); + +half4 sample($gsampler1D sampler, float2 P); +half4 sample($gsampler1D sampler, float2 P, float bias); +half4 sample($gsampler2D sampler, float3 P); +half4 sample($gsampler2D sampler, float3 P, float bias); + +float4 imageLoad(image2D image, int2 P); +int4 imageLoad(iimage2D image, int2 P); +$genType dFdx($genType p); +$genType dFdy($genType p); +$genHType dFdx($genHType p); +$genHType dFdy($genHType p); +$genType fwidth($genType p); +$genHType fwidth($genHType p); +float interpolateAtSample(float interpolant, int sample); +float2 interpolateAtSample(float2 interpolant, int sample); +float3 interpolateAtSample(float3 interpolant, int sample); +float4 interpolateAtSample(float4 interpolant, int sample); +float interpolateAtOffset(float interpolant, float2 offset); +float2 interpolateAtOffset(float2 interpolant, float2 offset); +float3 interpolateAtOffset(float3 interpolant, float2 offset); +float4 interpolateAtOffset(float4 interpolant, float2 offset); + +) diff --git a/gfx/skia/skia/src/sksl/sksl_interp.inc b/gfx/skia/skia/src/sksl/sksl_interp.inc new file mode 100644 index 0000000000..f43f05f8bf --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_interp.inc @@ -0,0 +1,53 @@ +STRINGIFY( + +$genType cos($genType y); +$genHType cos($genHType y); +float dot($genType x, $genType y); +float2x2 inverse(float2x2 m); +float3x3 inverse(float3x3 m); +float4x4 inverse(float4x4 m); +$genType sin($genType x); +$genHType sin($genHType x); +$genType sqrt($genType x); +$genHType sqrt($genHType x); +$genType tan($genType x); +$genHType tan($genHType x); + +float degrees(float rad) { return rad * 57.2957795; } +float2 degrees(float2 rad) { return rad * 57.2957795; } +float3 degrees(float3 rad) { return rad * 57.2957795; } +float4 degrees(float4 rad) { return rad * 57.2957795; } + +float radians(float deg) { return deg * 0.0174532925; } +float2 radians(float2 deg) { return deg * 0.0174532925; } +float3 radians(float3 deg) { return deg * 0.0174532925; } +float4 radians(float4 deg) { return deg * 0.0174532925; } + +float length(float2 v) { return sqrt(dot(v, v)); } +float length(float3 v) { return sqrt(dot(v, v)); } +float length(float4 v) { return sqrt(dot(v, v)); } + +float distance(float2 a, float2 b) { return length(a - b); } +float distance(float3 a, float3 b) { return length(a - b); } +float distance(float4 a, float4 b) { return length(a - b); } + +float2 normalize(float2 v) { return v / length(v); } +float3 normalize(float3 v) { return v / length(v); } +float4 normalize(float4 v) { return v / length(v); } + +float mix(float x, float y, float t) { return x * (1 - t) + y * t; } +float2 mix(float2 x, float2 y, float t) { return x * (1 - t) + y * t; } +float3 mix(float3 x, float3 y, float t) { return x * (1 - t) + y * t; } +float4 mix(float4 x, float4 y, float t) { return x * (1 - t) + y * t; } + +float2 mix(float2 x, float2 y, float2 t) { return x * (1 - t) + y * t; } +float3 mix(float3 x, float3 y, float3 t) { return x * (1 - t) + y * t; } +float4 mix(float4 x, float4 y, float4 t) { return x * (1 - t) + y * t; } + +float3 cross(float3 a, float3 b) { + return float3(a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x); +} + +) diff --git a/gfx/skia/skia/src/sksl/sksl_pipeline.inc b/gfx/skia/skia/src/sksl/sksl_pipeline.inc new file mode 100644 index 0000000000..56f189e317 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_pipeline.inc @@ -0,0 +1,3 @@ +STRINGIFY( + half4 sample(fragmentProcessor fp); +) diff --git a/gfx/skia/skia/src/sksl/sksl_vert.inc b/gfx/skia/skia/src/sksl/sksl_vert.inc new file mode 100644 index 0000000000..86db5d2c24 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_vert.inc @@ -0,0 +1,14 @@ +STRINGIFY( + +// defines built-in interfaces supported by SkiaSL vertex shaders + +out sk_PerVertex { + layout(builtin=0) float4 sk_Position; + layout(builtin=1) float sk_PointSize; + layout(builtin=3) float sk_ClipDistance[1]; +}; + +layout(builtin=42) in int sk_VertexID; +layout(builtin=43) in int sk_InstanceID; + +) diff --git a/gfx/skia/skia/src/sksl/spirv.h b/gfx/skia/skia/src/sksl/spirv.h new file mode 100644 index 0000000000..22821ed862 --- /dev/null +++ b/gfx/skia/skia/src/sksl/spirv.h @@ -0,0 +1,870 @@ +/* +** Copyright (c) 2014-2016 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and/or associated documentation files (the "Materials"), +** to deal in the Materials without restriction, including without limitation +** the rights to use, copy, modify, merge, publish, distribute, sublicense, +** and/or sell copies of the Materials, and to permit persons to whom the +** Materials are furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Materials. +** +** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +** IN THE MATERIALS. +*/ + +/* +** This header is automatically generated by the same tool that creates +** the Binary Section of the SPIR-V specification. +*/ + +/* +** Enumeration tokens for SPIR-V, in various styles: +** C, C++, C++11, JSON, Lua, Python +** +** - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +** - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +** - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +** - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +** - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +** +** Some tokens act like mask values, which can be OR'd together, +** while others are mutually exclusive. The mask-like ones have +** "Mask" in their name, and a parallel enum that has the shift +** amount (1 << x) for each corresponding enumerant. +*/ + +#ifndef spirv_H +#define spirv_H + +typedef unsigned int SpvId; + +#define SPV_VERSION 0x10000 +#define SPV_REVISION 4 + +static const unsigned int SpvMagicNumber = 0x07230203; +static const unsigned int SpvVersion = 0x00010000; +static const unsigned int SpvRevision = 4; +static const unsigned int SpvOpCodeMask = 0xffff; +static const unsigned int SpvWordCountShift = 16; + +typedef enum SpvSourceLanguage_ { + SpvSourceLanguageUnknown = 0, + SpvSourceLanguageESSL = 1, + SpvSourceLanguageGLSL = 2, + SpvSourceLanguageOpenCL_C = 3, + SpvSourceLanguageOpenCL_CPP = 4, +} SpvSourceLanguage; + +typedef enum SpvExecutionModel_ { + SpvExecutionModelVertex = 0, + SpvExecutionModelTessellationControl = 1, + SpvExecutionModelTessellationEvaluation = 2, + SpvExecutionModelGeometry = 3, + SpvExecutionModelFragment = 4, + SpvExecutionModelGLCompute = 5, + SpvExecutionModelKernel = 6, +} SpvExecutionModel; + +typedef enum SpvAddressingModel_ { + SpvAddressingModelLogical = 0, + SpvAddressingModelPhysical32 = 1, + SpvAddressingModelPhysical64 = 2, +} SpvAddressingModel; + +typedef enum SpvMemoryModel_ { + SpvMemoryModelSimple = 0, + SpvMemoryModelGLSL450 = 1, + SpvMemoryModelOpenCL = 2, +} SpvMemoryModel; + +typedef enum SpvExecutionMode_ { + SpvExecutionModeInvocations = 0, + SpvExecutionModeSpacingEqual = 1, + SpvExecutionModeSpacingFractionalEven = 2, + SpvExecutionModeSpacingFractionalOdd = 3, + SpvExecutionModeVertexOrderCw = 4, + SpvExecutionModeVertexOrderCcw = 5, + SpvExecutionModePixelCenterInteger = 6, + SpvExecutionModeOriginUpperLeft = 7, + SpvExecutionModeOriginLowerLeft = 8, + SpvExecutionModeEarlyFragmentTests = 9, + SpvExecutionModePointMode = 10, + SpvExecutionModeXfb = 11, + SpvExecutionModeDepthReplacing = 12, + SpvExecutionModeDepthGreater = 14, + SpvExecutionModeDepthLess = 15, + SpvExecutionModeDepthUnchanged = 16, + SpvExecutionModeLocalSize = 17, + SpvExecutionModeLocalSizeHint = 18, + SpvExecutionModeInputPoints = 19, + SpvExecutionModeInputLines = 20, + SpvExecutionModeInputLinesAdjacency = 21, + SpvExecutionModeTriangles = 22, + SpvExecutionModeInputTrianglesAdjacency = 23, + SpvExecutionModeQuads = 24, + SpvExecutionModeIsolines = 25, + SpvExecutionModeOutputVertices = 26, + SpvExecutionModeOutputPoints = 27, + SpvExecutionModeOutputLineStrip = 28, + SpvExecutionModeOutputTriangleStrip = 29, + SpvExecutionModeVecTypeHint = 30, + SpvExecutionModeContractionOff = 31, +} SpvExecutionMode; + +typedef enum SpvStorageClass_ { + SpvStorageClassUniformConstant = 0, + SpvStorageClassInput = 1, + SpvStorageClassUniform = 2, + SpvStorageClassOutput = 3, + SpvStorageClassWorkgroup = 4, + SpvStorageClassCrossWorkgroup = 5, + SpvStorageClassPrivate = 6, + SpvStorageClassFunction = 7, + SpvStorageClassGeneric = 8, + SpvStorageClassPushConstant = 9, + SpvStorageClassAtomicCounter = 10, + SpvStorageClassImage = 11, +} SpvStorageClass; + +typedef enum SpvDim_ { + SpvDim1D = 0, + SpvDim2D = 1, + SpvDim3D = 2, + SpvDimCube = 3, + SpvDimRect = 4, + SpvDimBuffer = 5, + SpvDimSubpassData = 6, +} SpvDim; + +typedef enum SpvSamplerAddressingMode_ { + SpvSamplerAddressingModeNone = 0, + SpvSamplerAddressingModeClampToEdge = 1, + SpvSamplerAddressingModeClamp = 2, + SpvSamplerAddressingModeRepeat = 3, + SpvSamplerAddressingModeRepeatMirrored = 4, +} SpvSamplerAddressingMode; + +typedef enum SpvSamplerFilterMode_ { + SpvSamplerFilterModeNearest = 0, + SpvSamplerFilterModeLinear = 1, +} SpvSamplerFilterMode; + +typedef enum SpvImageFormat_ { + SpvImageFormatUnknown = 0, + SpvImageFormatRgba32f = 1, + SpvImageFormatRgba16f = 2, + SpvImageFormatR32f = 3, + SpvImageFormatRgba8 = 4, + SpvImageFormatRgba8Snorm = 5, + SpvImageFormatRg32f = 6, + SpvImageFormatRg16f = 7, + SpvImageFormatR11fG11fB10f = 8, + SpvImageFormatR16f = 9, + SpvImageFormatRgba16 = 10, + SpvImageFormatRgb10A2 = 11, + SpvImageFormatRg16 = 12, + SpvImageFormatRg8 = 13, + SpvImageFormatR16 = 14, + SpvImageFormatR8 = 15, + SpvImageFormatRgba16Snorm = 16, + SpvImageFormatRg16Snorm = 17, + SpvImageFormatRg8Snorm = 18, + SpvImageFormatR16Snorm = 19, + SpvImageFormatR8Snorm = 20, + SpvImageFormatRgba32i = 21, + SpvImageFormatRgba16i = 22, + SpvImageFormatRgba8i = 23, + SpvImageFormatR32i = 24, + SpvImageFormatRg32i = 25, + SpvImageFormatRg16i = 26, + SpvImageFormatRg8i = 27, + SpvImageFormatR16i = 28, + SpvImageFormatR8i = 29, + SpvImageFormatRgba32ui = 30, + SpvImageFormatRgba16ui = 31, + SpvImageFormatRgba8ui = 32, + SpvImageFormatR32ui = 33, + SpvImageFormatRgb10a2ui = 34, + SpvImageFormatRg32ui = 35, + SpvImageFormatRg16ui = 36, + SpvImageFormatRg8ui = 37, + SpvImageFormatR16ui = 38, + SpvImageFormatR8ui = 39, +} SpvImageFormat; + +typedef enum SpvImageChannelOrder_ { + SpvImageChannelOrderR = 0, + SpvImageChannelOrderA = 1, + SpvImageChannelOrderRG = 2, + SpvImageChannelOrderRA = 3, + SpvImageChannelOrderRGB = 4, + SpvImageChannelOrderRGBA = 5, + SpvImageChannelOrderBGRA = 6, + SpvImageChannelOrderARGB = 7, + SpvImageChannelOrderIntensity = 8, + SpvImageChannelOrderLuminance = 9, + SpvImageChannelOrderRx = 10, + SpvImageChannelOrderRGx = 11, + SpvImageChannelOrderRGBx = 12, + SpvImageChannelOrderDepth = 13, + SpvImageChannelOrderDepthStencil = 14, + SpvImageChannelOrdersRGB = 15, + SpvImageChannelOrdersRGBx = 16, + SpvImageChannelOrdersRGBA = 17, + SpvImageChannelOrdersBGRA = 18, +} SpvImageChannelOrder; + +typedef enum SpvImageChannelDataType_ { + SpvImageChannelDataTypeSnormInt8 = 0, + SpvImageChannelDataTypeSnormInt16 = 1, + SpvImageChannelDataTypeUnormInt8 = 2, + SpvImageChannelDataTypeUnormInt16 = 3, + SpvImageChannelDataTypeUnormShort565 = 4, + SpvImageChannelDataTypeUnormShort555 = 5, + SpvImageChannelDataTypeUnormInt101010 = 6, + SpvImageChannelDataTypeSignedInt8 = 7, + SpvImageChannelDataTypeSignedInt16 = 8, + SpvImageChannelDataTypeSignedInt32 = 9, + SpvImageChannelDataTypeUnsignedInt8 = 10, + SpvImageChannelDataTypeUnsignedInt16 = 11, + SpvImageChannelDataTypeUnsignedInt32 = 12, + SpvImageChannelDataTypeHalfFloat = 13, + SpvImageChannelDataTypeFloat = 14, + SpvImageChannelDataTypeUnormInt24 = 15, + SpvImageChannelDataTypeUnormInt101010_2 = 16, +} SpvImageChannelDataType; + +typedef enum SpvImageOperandsShift_ { + SpvImageOperandsBiasShift = 0, + SpvImageOperandsLodShift = 1, + SpvImageOperandsGradShift = 2, + SpvImageOperandsConstOffsetShift = 3, + SpvImageOperandsOffsetShift = 4, + SpvImageOperandsConstOffsetsShift = 5, + SpvImageOperandsSampleShift = 6, + SpvImageOperandsMinLodShift = 7, +} SpvImageOperandsShift; + +typedef enum SpvImageOperandsMask_ { + SpvImageOperandsMaskNone = 0, + SpvImageOperandsBiasMask = 0x00000001, + SpvImageOperandsLodMask = 0x00000002, + SpvImageOperandsGradMask = 0x00000004, + SpvImageOperandsConstOffsetMask = 0x00000008, + SpvImageOperandsOffsetMask = 0x00000010, + SpvImageOperandsConstOffsetsMask = 0x00000020, + SpvImageOperandsSampleMask = 0x00000040, + SpvImageOperandsMinLodMask = 0x00000080, +} SpvImageOperandsMask; + +typedef enum SpvFPFastMathModeShift_ { + SpvFPFastMathModeNotNaNShift = 0, + SpvFPFastMathModeNotInfShift = 1, + SpvFPFastMathModeNSZShift = 2, + SpvFPFastMathModeAllowRecipShift = 3, + SpvFPFastMathModeFastShift = 4, +} SpvFPFastMathModeShift; + +typedef enum SpvFPFastMathModeMask_ { + SpvFPFastMathModeMaskNone = 0, + SpvFPFastMathModeNotNaNMask = 0x00000001, + SpvFPFastMathModeNotInfMask = 0x00000002, + SpvFPFastMathModeNSZMask = 0x00000004, + SpvFPFastMathModeAllowRecipMask = 0x00000008, + SpvFPFastMathModeFastMask = 0x00000010, +} SpvFPFastMathModeMask; + +typedef enum SpvFPRoundingMode_ { + SpvFPRoundingModeRTE = 0, + SpvFPRoundingModeRTZ = 1, + SpvFPRoundingModeRTP = 2, + SpvFPRoundingModeRTN = 3, +} SpvFPRoundingMode; + +typedef enum SpvLinkageType_ { + SpvLinkageTypeExport = 0, + SpvLinkageTypeImport = 1, +} SpvLinkageType; + +typedef enum SpvAccessQualifier_ { + SpvAccessQualifierReadOnly = 0, + SpvAccessQualifierWriteOnly = 1, + SpvAccessQualifierReadWrite = 2, +} SpvAccessQualifier; + +typedef enum SpvFunctionParameterAttribute_ { + SpvFunctionParameterAttributeZext = 0, + SpvFunctionParameterAttributeSext = 1, + SpvFunctionParameterAttributeByVal = 2, + SpvFunctionParameterAttributeSret = 3, + SpvFunctionParameterAttributeNoAlias = 4, + SpvFunctionParameterAttributeNoCapture = 5, + SpvFunctionParameterAttributeNoWrite = 6, + SpvFunctionParameterAttributeNoReadWrite = 7, +} SpvFunctionParameterAttribute; + +typedef enum SpvDecoration_ { + SpvDecorationRelaxedPrecision = 0, + SpvDecorationSpecId = 1, + SpvDecorationBlock = 2, + SpvDecorationBufferBlock = 3, + SpvDecorationRowMajor = 4, + SpvDecorationColMajor = 5, + SpvDecorationArrayStride = 6, + SpvDecorationMatrixStride = 7, + SpvDecorationGLSLShared = 8, + SpvDecorationGLSLPacked = 9, + SpvDecorationCPacked = 10, + SpvDecorationBuiltIn = 11, + SpvDecorationNoPerspective = 13, + SpvDecorationFlat = 14, + SpvDecorationPatch = 15, + SpvDecorationCentroid = 16, + SpvDecorationSample = 17, + SpvDecorationInvariant = 18, + SpvDecorationRestrict = 19, + SpvDecorationAliased = 20, + SpvDecorationVolatile = 21, + SpvDecorationConstant = 22, + SpvDecorationCoherent = 23, + SpvDecorationNonWritable = 24, + SpvDecorationNonReadable = 25, + SpvDecorationUniform = 26, + SpvDecorationSaturatedConversion = 28, + SpvDecorationStream = 29, + SpvDecorationLocation = 30, + SpvDecorationComponent = 31, + SpvDecorationIndex = 32, + SpvDecorationBinding = 33, + SpvDecorationDescriptorSet = 34, + SpvDecorationOffset = 35, + SpvDecorationXfbBuffer = 36, + SpvDecorationXfbStride = 37, + SpvDecorationFuncParamAttr = 38, + SpvDecorationFPRoundingMode = 39, + SpvDecorationFPFastMathMode = 40, + SpvDecorationLinkageAttributes = 41, + SpvDecorationNoContraction = 42, + SpvDecorationInputAttachmentIndex = 43, + SpvDecorationAlignment = 44, +} SpvDecoration; + +typedef enum SpvBuiltIn_ { + SpvBuiltInPosition = 0, + SpvBuiltInPointSize = 1, + SpvBuiltInClipDistance = 3, + SpvBuiltInCullDistance = 4, + SpvBuiltInVertexId = 5, + SpvBuiltInInstanceId = 6, + SpvBuiltInPrimitiveId = 7, + SpvBuiltInInvocationId = 8, + SpvBuiltInLayer = 9, + SpvBuiltInViewportIndex = 10, + SpvBuiltInTessLevelOuter = 11, + SpvBuiltInTessLevelInner = 12, + SpvBuiltInTessCoord = 13, + SpvBuiltInPatchVertices = 14, + SpvBuiltInFragCoord = 15, + SpvBuiltInPointCoord = 16, + SpvBuiltInFrontFacing = 17, + SpvBuiltInSampleId = 18, + SpvBuiltInSamplePosition = 19, + SpvBuiltInSampleMask = 20, + SpvBuiltInFragDepth = 22, + SpvBuiltInHelperInvocation = 23, + SpvBuiltInNumWorkgroups = 24, + SpvBuiltInWorkgroupSize = 25, + SpvBuiltInWorkgroupId = 26, + SpvBuiltInLocalInvocationId = 27, + SpvBuiltInGlobalInvocationId = 28, + SpvBuiltInLocalInvocationIndex = 29, + SpvBuiltInWorkDim = 30, + SpvBuiltInGlobalSize = 31, + SpvBuiltInEnqueuedWorkgroupSize = 32, + SpvBuiltInGlobalOffset = 33, + SpvBuiltInGlobalLinearId = 34, + SpvBuiltInSubgroupSize = 36, + SpvBuiltInSubgroupMaxSize = 37, + SpvBuiltInNumSubgroups = 38, + SpvBuiltInNumEnqueuedSubgroups = 39, + SpvBuiltInSubgroupId = 40, + SpvBuiltInSubgroupLocalInvocationId = 41, + SpvBuiltInVertexIndex = 42, + SpvBuiltInInstanceIndex = 43, +} SpvBuiltIn; + +typedef enum SpvSelectionControlShift_ { + SpvSelectionControlFlattenShift = 0, + SpvSelectionControlDontFlattenShift = 1, +} SpvSelectionControlShift; + +typedef enum SpvSelectionControlMask_ { + SpvSelectionControlMaskNone = 0, + SpvSelectionControlFlattenMask = 0x00000001, + SpvSelectionControlDontFlattenMask = 0x00000002, +} SpvSelectionControlMask; + +typedef enum SpvLoopControlShift_ { + SpvLoopControlUnrollShift = 0, + SpvLoopControlDontUnrollShift = 1, +} SpvLoopControlShift; + +typedef enum SpvLoopControlMask_ { + SpvLoopControlMaskNone = 0, + SpvLoopControlUnrollMask = 0x00000001, + SpvLoopControlDontUnrollMask = 0x00000002, +} SpvLoopControlMask; + +typedef enum SpvFunctionControlShift_ { + SpvFunctionControlInlineShift = 0, + SpvFunctionControlDontInlineShift = 1, + SpvFunctionControlPureShift = 2, + SpvFunctionControlConstShift = 3, +} SpvFunctionControlShift; + +typedef enum SpvFunctionControlMask_ { + SpvFunctionControlMaskNone = 0, + SpvFunctionControlInlineMask = 0x00000001, + SpvFunctionControlDontInlineMask = 0x00000002, + SpvFunctionControlPureMask = 0x00000004, + SpvFunctionControlConstMask = 0x00000008, +} SpvFunctionControlMask; + +typedef enum SpvMemorySemanticsShift_ { + SpvMemorySemanticsAcquireShift = 1, + SpvMemorySemanticsReleaseShift = 2, + SpvMemorySemanticsAcquireReleaseShift = 3, + SpvMemorySemanticsSequentiallyConsistentShift = 4, + SpvMemorySemanticsUniformMemoryShift = 6, + SpvMemorySemanticsSubgroupMemoryShift = 7, + SpvMemorySemanticsWorkgroupMemoryShift = 8, + SpvMemorySemanticsCrossWorkgroupMemoryShift = 9, + SpvMemorySemanticsAtomicCounterMemoryShift = 10, + SpvMemorySemanticsImageMemoryShift = 11, +} SpvMemorySemanticsShift; + +typedef enum SpvMemorySemanticsMask_ { + SpvMemorySemanticsMaskNone = 0, + SpvMemorySemanticsAcquireMask = 0x00000002, + SpvMemorySemanticsReleaseMask = 0x00000004, + SpvMemorySemanticsAcquireReleaseMask = 0x00000008, + SpvMemorySemanticsSequentiallyConsistentMask = 0x00000010, + SpvMemorySemanticsUniformMemoryMask = 0x00000040, + SpvMemorySemanticsSubgroupMemoryMask = 0x00000080, + SpvMemorySemanticsWorkgroupMemoryMask = 0x00000100, + SpvMemorySemanticsCrossWorkgroupMemoryMask = 0x00000200, + SpvMemorySemanticsAtomicCounterMemoryMask = 0x00000400, + SpvMemorySemanticsImageMemoryMask = 0x00000800, +} SpvMemorySemanticsMask; + +typedef enum SpvMemoryAccessShift_ { + SpvMemoryAccessVolatileShift = 0, + SpvMemoryAccessAlignedShift = 1, + SpvMemoryAccessNontemporalShift = 2, +} SpvMemoryAccessShift; + +typedef enum SpvMemoryAccessMask_ { + SpvMemoryAccessMaskNone = 0, + SpvMemoryAccessVolatileMask = 0x00000001, + SpvMemoryAccessAlignedMask = 0x00000002, + SpvMemoryAccessNontemporalMask = 0x00000004, +} SpvMemoryAccessMask; + +typedef enum SpvScope_ { + SpvScopeCrossDevice = 0, + SpvScopeDevice = 1, + SpvScopeWorkgroup = 2, + SpvScopeSubgroup = 3, + SpvScopeInvocation = 4, +} SpvScope; + +typedef enum SpvGroupOperation_ { + SpvGroupOperationReduce = 0, + SpvGroupOperationInclusiveScan = 1, + SpvGroupOperationExclusiveScan = 2, +} SpvGroupOperation; + +typedef enum SpvKernelEnqueueFlags_ { + SpvKernelEnqueueFlagsNoWait = 0, + SpvKernelEnqueueFlagsWaitKernel = 1, + SpvKernelEnqueueFlagsWaitWorkGroup = 2, +} SpvKernelEnqueueFlags; + +typedef enum SpvKernelProfilingInfoShift_ { + SpvKernelProfilingInfoCmdExecTimeShift = 0, +} SpvKernelProfilingInfoShift; + +typedef enum SpvKernelProfilingInfoMask_ { + SpvKernelProfilingInfoMaskNone = 0, + SpvKernelProfilingInfoCmdExecTimeMask = 0x00000001, +} SpvKernelProfilingInfoMask; + +typedef enum SpvCapability_ { + SpvCapabilityMatrix = 0, + SpvCapabilityShader = 1, + SpvCapabilityGeometry = 2, + SpvCapabilityTessellation = 3, + SpvCapabilityAddresses = 4, + SpvCapabilityLinkage = 5, + SpvCapabilityKernel = 6, + SpvCapabilityVector16 = 7, + SpvCapabilityFloat16Buffer = 8, + SpvCapabilityFloat16 = 9, + SpvCapabilityFloat64 = 10, + SpvCapabilityInt64 = 11, + SpvCapabilityInt64Atomics = 12, + SpvCapabilityImageBasic = 13, + SpvCapabilityImageReadWrite = 14, + SpvCapabilityImageMipmap = 15, + SpvCapabilityPipes = 17, + SpvCapabilityGroups = 18, + SpvCapabilityDeviceEnqueue = 19, + SpvCapabilityLiteralSampler = 20, + SpvCapabilityAtomicStorage = 21, + SpvCapabilityInt16 = 22, + SpvCapabilityTessellationPointSize = 23, + SpvCapabilityGeometryPointSize = 24, + SpvCapabilityImageGatherExtended = 25, + SpvCapabilityStorageImageMultisample = 27, + SpvCapabilityUniformBufferArrayDynamicIndexing = 28, + SpvCapabilitySampledImageArrayDynamicIndexing = 29, + SpvCapabilityStorageBufferArrayDynamicIndexing = 30, + SpvCapabilityStorageImageArrayDynamicIndexing = 31, + SpvCapabilityClipDistance = 32, + SpvCapabilityCullDistance = 33, + SpvCapabilityImageCubeArray = 34, + SpvCapabilitySampleRateShading = 35, + SpvCapabilityImageRect = 36, + SpvCapabilitySampledRect = 37, + SpvCapabilityGenericPointer = 38, + SpvCapabilityInt8 = 39, + SpvCapabilityInputAttachment = 40, + SpvCapabilitySparseResidency = 41, + SpvCapabilityMinLod = 42, + SpvCapabilitySampled1D = 43, + SpvCapabilityImage1D = 44, + SpvCapabilitySampledCubeArray = 45, + SpvCapabilitySampledBuffer = 46, + SpvCapabilityImageBuffer = 47, + SpvCapabilityImageMSArray = 48, + SpvCapabilityStorageImageExtendedFormats = 49, + SpvCapabilityImageQuery = 50, + SpvCapabilityDerivativeControl = 51, + SpvCapabilityInterpolationFunction = 52, + SpvCapabilityTransformFeedback = 53, + SpvCapabilityGeometryStreams = 54, + SpvCapabilityStorageImageReadWithoutFormat = 55, + SpvCapabilityStorageImageWriteWithoutFormat = 56, + SpvCapabilityMultiViewport = 57, +} SpvCapability; + +typedef enum SpvOp_ { + SpvOpNop = 0, + SpvOpUndef = 1, + SpvOpSourceContinued = 2, + SpvOpSource = 3, + SpvOpSourceExtension = 4, + SpvOpName = 5, + SpvOpMemberName = 6, + SpvOpString = 7, + SpvOpLine = 8, + SpvOpExtension = 10, + SpvOpExtInstImport = 11, + SpvOpExtInst = 12, + SpvOpMemoryModel = 14, + SpvOpEntryPoint = 15, + SpvOpExecutionMode = 16, + SpvOpCapability = 17, + SpvOpTypeVoid = 19, + SpvOpTypeBool = 20, + SpvOpTypeInt = 21, + SpvOpTypeFloat = 22, + SpvOpTypeVector = 23, + SpvOpTypeMatrix = 24, + SpvOpTypeImage = 25, + SpvOpTypeSampler = 26, + SpvOpTypeSampledImage = 27, + SpvOpTypeArray = 28, + SpvOpTypeRuntimeArray = 29, + SpvOpTypeStruct = 30, + SpvOpTypeOpaque = 31, + SpvOpTypePointer = 32, + SpvOpTypeFunction = 33, + SpvOpTypeEvent = 34, + SpvOpTypeDeviceEvent = 35, + SpvOpTypeReserveId = 36, + SpvOpTypeQueue = 37, + SpvOpTypePipe = 38, + SpvOpTypeForwardPointer = 39, + SpvOpConstantTrue = 41, + SpvOpConstantFalse = 42, + SpvOpConstant = 43, + SpvOpConstantComposite = 44, + SpvOpConstantSampler = 45, + SpvOpConstantNull = 46, + SpvOpSpecConstantTrue = 48, + SpvOpSpecConstantFalse = 49, + SpvOpSpecConstant = 50, + SpvOpSpecConstantComposite = 51, + SpvOpSpecConstantOp = 52, + SpvOpFunction = 54, + SpvOpFunctionParameter = 55, + SpvOpFunctionEnd = 56, + SpvOpFunctionCall = 57, + SpvOpVariable = 59, + SpvOpImageTexelPointer = 60, + SpvOpLoad = 61, + SpvOpStore = 62, + SpvOpCopyMemory = 63, + SpvOpCopyMemorySized = 64, + SpvOpAccessChain = 65, + SpvOpInBoundsAccessChain = 66, + SpvOpPtrAccessChain = 67, + SpvOpArrayLength = 68, + SpvOpGenericPtrMemSemantics = 69, + SpvOpInBoundsPtrAccessChain = 70, + SpvOpDecorate = 71, + SpvOpMemberDecorate = 72, + SpvOpDecorationGroup = 73, + SpvOpGroupDecorate = 74, + SpvOpGroupMemberDecorate = 75, + SpvOpVectorExtractDynamic = 77, + SpvOpVectorInsertDynamic = 78, + SpvOpVectorShuffle = 79, + SpvOpCompositeConstruct = 80, + SpvOpCompositeExtract = 81, + SpvOpCompositeInsert = 82, + SpvOpCopyObject = 83, + SpvOpTranspose = 84, + SpvOpSampledImage = 86, + SpvOpImageSampleImplicitLod = 87, + SpvOpImageSampleExplicitLod = 88, + SpvOpImageSampleDrefImplicitLod = 89, + SpvOpImageSampleDrefExplicitLod = 90, + SpvOpImageSampleProjImplicitLod = 91, + SpvOpImageSampleProjExplicitLod = 92, + SpvOpImageSampleProjDrefImplicitLod = 93, + SpvOpImageSampleProjDrefExplicitLod = 94, + SpvOpImageFetch = 95, + SpvOpImageGather = 96, + SpvOpImageDrefGather = 97, + SpvOpImageRead = 98, + SpvOpImageWrite = 99, + SpvOpImage = 100, + SpvOpImageQueryFormat = 101, + SpvOpImageQueryOrder = 102, + SpvOpImageQuerySizeLod = 103, + SpvOpImageQuerySize = 104, + SpvOpImageQueryLod = 105, + SpvOpImageQueryLevels = 106, + SpvOpImageQuerySamples = 107, + SpvOpConvertFToU = 109, + SpvOpConvertFToS = 110, + SpvOpConvertSToF = 111, + SpvOpConvertUToF = 112, + SpvOpUConvert = 113, + SpvOpSConvert = 114, + SpvOpFConvert = 115, + SpvOpQuantizeToF16 = 116, + SpvOpConvertPtrToU = 117, + SpvOpSatConvertSToU = 118, + SpvOpSatConvertUToS = 119, + SpvOpConvertUToPtr = 120, + SpvOpPtrCastToGeneric = 121, + SpvOpGenericCastToPtr = 122, + SpvOpGenericCastToPtrExplicit = 123, + SpvOpBitcast = 124, + SpvOpSNegate = 126, + SpvOpFNegate = 127, + SpvOpIAdd = 128, + SpvOpFAdd = 129, + SpvOpISub = 130, + SpvOpFSub = 131, + SpvOpIMul = 132, + SpvOpFMul = 133, + SpvOpUDiv = 134, + SpvOpSDiv = 135, + SpvOpFDiv = 136, + SpvOpUMod = 137, + SpvOpSRem = 138, + SpvOpSMod = 139, + SpvOpFRem = 140, + SpvOpFMod = 141, + SpvOpVectorTimesScalar = 142, + SpvOpMatrixTimesScalar = 143, + SpvOpVectorTimesMatrix = 144, + SpvOpMatrixTimesVector = 145, + SpvOpMatrixTimesMatrix = 146, + SpvOpOuterProduct = 147, + SpvOpDot = 148, + SpvOpIAddCarry = 149, + SpvOpISubBorrow = 150, + SpvOpUMulExtended = 151, + SpvOpSMulExtended = 152, + SpvOpAny = 154, + SpvOpAll = 155, + SpvOpIsNan = 156, + SpvOpIsInf = 157, + SpvOpIsFinite = 158, + SpvOpIsNormal = 159, + SpvOpSignBitSet = 160, + SpvOpLessOrGreater = 161, + SpvOpOrdered = 162, + SpvOpUnordered = 163, + SpvOpLogicalEqual = 164, + SpvOpLogicalNotEqual = 165, + SpvOpLogicalOr = 166, + SpvOpLogicalAnd = 167, + SpvOpLogicalNot = 168, + SpvOpSelect = 169, + SpvOpIEqual = 170, + SpvOpINotEqual = 171, + SpvOpUGreaterThan = 172, + SpvOpSGreaterThan = 173, + SpvOpUGreaterThanEqual = 174, + SpvOpSGreaterThanEqual = 175, + SpvOpULessThan = 176, + SpvOpSLessThan = 177, + SpvOpULessThanEqual = 178, + SpvOpSLessThanEqual = 179, + SpvOpFOrdEqual = 180, + SpvOpFUnordEqual = 181, + SpvOpFOrdNotEqual = 182, + SpvOpFUnordNotEqual = 183, + SpvOpFOrdLessThan = 184, + SpvOpFUnordLessThan = 185, + SpvOpFOrdGreaterThan = 186, + SpvOpFUnordGreaterThan = 187, + SpvOpFOrdLessThanEqual = 188, + SpvOpFUnordLessThanEqual = 189, + SpvOpFOrdGreaterThanEqual = 190, + SpvOpFUnordGreaterThanEqual = 191, + SpvOpShiftRightLogical = 194, + SpvOpShiftRightArithmetic = 195, + SpvOpShiftLeftLogical = 196, + SpvOpBitwiseOr = 197, + SpvOpBitwiseXor = 198, + SpvOpBitwiseAnd = 199, + SpvOpNot = 200, + SpvOpBitFieldInsert = 201, + SpvOpBitFieldSExtract = 202, + SpvOpBitFieldUExtract = 203, + SpvOpBitReverse = 204, + SpvOpBitCount = 205, + SpvOpDPdx = 207, + SpvOpDPdy = 208, + SpvOpFwidth = 209, + SpvOpDPdxFine = 210, + SpvOpDPdyFine = 211, + SpvOpFwidthFine = 212, + SpvOpDPdxCoarse = 213, + SpvOpDPdyCoarse = 214, + SpvOpFwidthCoarse = 215, + SpvOpEmitVertex = 218, + SpvOpEndPrimitive = 219, + SpvOpEmitStreamVertex = 220, + SpvOpEndStreamPrimitive = 221, + SpvOpControlBarrier = 224, + SpvOpMemoryBarrier = 225, + SpvOpAtomicLoad = 227, + SpvOpAtomicStore = 228, + SpvOpAtomicExchange = 229, + SpvOpAtomicCompareExchange = 230, + SpvOpAtomicCompareExchangeWeak = 231, + SpvOpAtomicIIncrement = 232, + SpvOpAtomicIDecrement = 233, + SpvOpAtomicIAdd = 234, + SpvOpAtomicISub = 235, + SpvOpAtomicSMin = 236, + SpvOpAtomicUMin = 237, + SpvOpAtomicSMax = 238, + SpvOpAtomicUMax = 239, + SpvOpAtomicAnd = 240, + SpvOpAtomicOr = 241, + SpvOpAtomicXor = 242, + SpvOpPhi = 245, + SpvOpLoopMerge = 246, + SpvOpSelectionMerge = 247, + SpvOpLabel = 248, + SpvOpBranch = 249, + SpvOpBranchConditional = 250, + SpvOpSwitch = 251, + SpvOpKill = 252, + SpvOpReturn = 253, + SpvOpReturnValue = 254, + SpvOpUnreachable = 255, + SpvOpLifetimeStart = 256, + SpvOpLifetimeStop = 257, + SpvOpGroupAsyncCopy = 259, + SpvOpGroupWaitEvents = 260, + SpvOpGroupAll = 261, + SpvOpGroupAny = 262, + SpvOpGroupBroadcast = 263, + SpvOpGroupIAdd = 264, + SpvOpGroupFAdd = 265, + SpvOpGroupFMin = 266, + SpvOpGroupUMin = 267, + SpvOpGroupSMin = 268, + SpvOpGroupFMax = 269, + SpvOpGroupUMax = 270, + SpvOpGroupSMax = 271, + SpvOpReadPipe = 274, + SpvOpWritePipe = 275, + SpvOpReservedReadPipe = 276, + SpvOpReservedWritePipe = 277, + SpvOpReserveReadPipePackets = 278, + SpvOpReserveWritePipePackets = 279, + SpvOpCommitReadPipe = 280, + SpvOpCommitWritePipe = 281, + SpvOpIsValidReserveId = 282, + SpvOpGetNumPipePackets = 283, + SpvOpGetMaxPipePackets = 284, + SpvOpGroupReserveReadPipePackets = 285, + SpvOpGroupReserveWritePipePackets = 286, + SpvOpGroupCommitReadPipe = 287, + SpvOpGroupCommitWritePipe = 288, + SpvOpEnqueueMarker = 291, + SpvOpEnqueueKernel = 292, + SpvOpGetKernelNDrangeSubGroupCount = 293, + SpvOpGetKernelNDrangeMaxSubGroupSize = 294, + SpvOpGetKernelWorkGroupSize = 295, + SpvOpGetKernelPreferredWorkGroupSizeMultiple = 296, + SpvOpRetainEvent = 297, + SpvOpReleaseEvent = 298, + SpvOpCreateUserEvent = 299, + SpvOpIsValidEvent = 300, + SpvOpSetUserEventStatus = 301, + SpvOpCaptureEventProfilingInfo = 302, + SpvOpGetDefaultQueue = 303, + SpvOpBuildNDRange = 304, + SpvOpImageSparseSampleImplicitLod = 305, + SpvOpImageSparseSampleExplicitLod = 306, + SpvOpImageSparseSampleDrefImplicitLod = 307, + SpvOpImageSparseSampleDrefExplicitLod = 308, + SpvOpImageSparseSampleProjImplicitLod = 309, + SpvOpImageSparseSampleProjExplicitLod = 310, + SpvOpImageSparseSampleProjDrefImplicitLod = 311, + SpvOpImageSparseSampleProjDrefExplicitLod = 312, + SpvOpImageSparseFetch = 313, + SpvOpImageSparseGather = 314, + SpvOpImageSparseDrefGather = 315, + SpvOpImageSparseTexelsResident = 316, + SpvOpNoLine = 317, + SpvOpAtomicFlagTestAndSet = 318, + SpvOpAtomicFlagClear = 319, + SpvOpImageSparseRead = 320, +} SpvOp; + +#endif // #ifndef spirv_H diff --git a/gfx/skia/skia/src/svg/SkSVGCanvas.cpp b/gfx/skia/skia/src/svg/SkSVGCanvas.cpp new file mode 100644 index 0000000000..30c9198c5b --- /dev/null +++ b/gfx/skia/skia/src/svg/SkSVGCanvas.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/svg/SkSVGCanvas.h" +#include "src/core/SkMakeUnique.h" +#include "src/svg/SkSVGDevice.h" +#include "src/xml/SkXMLWriter.h" + +std::unique_ptr SkSVGCanvas::Make(const SkRect& bounds, SkWStream* writer, + uint32_t flags) { + // TODO: pass full bounds to the device + const auto size = bounds.roundOut().size(); + const auto xml_flags = (flags & kNoPrettyXML_Flag) ? SkToU32(SkXMLStreamWriter::kNoPretty_Flag) + : 0; + + auto svgDevice = SkSVGDevice::Make(size, + skstd::make_unique(writer, xml_flags), + flags); + + return svgDevice ? skstd::make_unique(std::move(svgDevice)) + : nullptr; +} diff --git a/gfx/skia/skia/src/svg/SkSVGDevice.cpp b/gfx/skia/skia/src/svg/SkSVGDevice.cpp new file mode 100644 index 0000000000..85e0b1bbd3 --- /dev/null +++ b/gfx/skia/skia/src/svg/SkSVGDevice.cpp @@ -0,0 +1,1048 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/svg/SkSVGDevice.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkBlendMode.h" +#include "include/core/SkColorFilter.h" +#include "include/core/SkData.h" +#include "include/core/SkImage.h" +#include "include/core/SkImageEncoder.h" +#include "include/core/SkPaint.h" +#include "include/core/SkShader.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypeface.h" +#include "include/private/SkChecksum.h" +#include "include/private/SkTHash.h" +#include "include/private/SkTo.h" +#include "include/svg/SkSVGCanvas.h" +#include "include/utils/SkBase64.h" +#include "include/utils/SkParsePath.h" +#include "src/codec/SkJpegCodec.h" +#include "src/codec/SkPngCodec.h" +#include "src/core/SkAnnotationKeys.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkClipStack.h" +#include "src/core/SkDraw.h" +#include "src/core/SkFontPriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkUtils.h" +#include "src/shaders/SkShaderBase.h" +#include "src/xml/SkXMLWriter.h" + +namespace { + +static SkString svg_color(SkColor color) { + // https://www.w3.org/TR/css-color-3/#html4 + auto named_color = [](SkColor c) -> const char* { + switch (c & 0xffffff) { + case 0x000000: return "black"; + case 0x000080: return "navy"; + case 0x0000ff: return "blue"; + case 0x008000: return "green"; + case 0x008080: return "teal"; + case 0x00ff00: return "lime"; + case 0x00ffff: return "aqua"; + case 0x800000: return "maroon"; + case 0x800080: return "purple"; + case 0x808000: return "olive"; + case 0x808080: return "gray"; + case 0xc0c0c0: return "silver"; + case 0xff0000: return "red"; + case 0xff00ff: return "fuchsia"; + case 0xffff00: return "yellow"; + case 0xffffff: return "white"; + default: break; + } + + return nullptr; + }; + + if (const auto* nc = named_color(color)) { + return SkString(nc); + } + + return SkStringPrintf("rgb(%u,%u,%u)", + SkColorGetR(color), + SkColorGetG(color), + SkColorGetB(color)); +} + +static SkScalar svg_opacity(SkColor color) { + return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE; +} + +// Keep in sync with SkPaint::Cap +static const char* cap_map[] = { + nullptr, // kButt_Cap (default) + "round", // kRound_Cap + "square" // kSquare_Cap +}; +static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry"); + +static const char* svg_cap(SkPaint::Cap cap) { + SkASSERT(cap < SK_ARRAY_COUNT(cap_map)); + return cap_map[cap]; +} + +// Keep in sync with SkPaint::Join +static const char* join_map[] = { + nullptr, // kMiter_Join (default) + "round", // kRound_Join + "bevel" // kBevel_Join +}; +static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry"); + +static const char* svg_join(SkPaint::Join join) { + SkASSERT(join < SK_ARRAY_COUNT(join_map)); + return join_map[join]; +} + +static SkString svg_transform(const SkMatrix& t) { + SkASSERT(!t.isIdentity()); + + SkString tstr; + switch (t.getType()) { + case SkMatrix::kPerspective_Mask: + // TODO: handle perspective matrices? + break; + case SkMatrix::kTranslate_Mask: + tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY()); + break; + case SkMatrix::kScale_Mask: + tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY()); + break; + default: + // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined + // | a c e | + // | b d f | + // | 0 0 1 | + tstr.printf("matrix(%g %g %g %g %g %g)", + t.getScaleX(), t.getSkewY(), + t.getSkewX(), t.getScaleY(), + t.getTranslateX(), t.getTranslateY()); + break; + } + + return tstr; +} + +struct Resources { + Resources(const SkPaint& paint) + : fPaintServer(svg_color(paint.getColor())) {} + + SkString fPaintServer; + SkString fColorFilter; +}; + +// Determine if the paint requires us to reset the viewport. +// Currently, we do this whenever the paint shader calls +// for a repeating image. +bool RequiresViewportReset(const SkPaint& paint) { + SkShader* shader = paint.getShader(); + if (!shader) + return false; + + SkTileMode xy[2]; + SkImage* image = shader->isAImage(nullptr, xy); + + if (!image) + return false; + + for (int i = 0; i < 2; i++) { + if (xy[i] == SkTileMode::kRepeat) + return true; + } + return false; +} + +SkPath GetPath(const SkGlyphRun& glyphRun, const SkPoint& offset) { + SkPath path; + + struct Rec { + SkPath* fPath; + const SkPoint fOffset; + const SkPoint* fPos; + } rec = { &path, offset, glyphRun.positions().data() }; + + glyphRun.font().getPaths(glyphRun.glyphsIDs().data(), SkToInt(glyphRun.glyphsIDs().size()), + [](const SkPath* path, const SkMatrix& mx, void* ctx) { + Rec* rec = reinterpret_cast(ctx); + if (path) { + SkMatrix total = mx; + total.postTranslate(rec->fPos->fX + rec->fOffset.fX, + rec->fPos->fY + rec->fOffset.fY); + rec->fPath->addPath(*path, total); + } else { + // TODO: this is going to drop color emojis. + } + rec->fPos += 1; // move to the next glyph's position + }, &rec); + + return path; +} + +} // namespace + +// For now all this does is serve unique serial IDs, but it will eventually evolve to track +// and deduplicate resources. +class SkSVGDevice::ResourceBucket : ::SkNoncopyable { +public: + ResourceBucket() + : fGradientCount(0) + , fPathCount(0) + , fImageCount(0) + , fPatternCount(0) + , fColorFilterCount(0) {} + + SkString addLinearGradient() { + return SkStringPrintf("gradient_%d", fGradientCount++); + } + + SkString addPath() { + return SkStringPrintf("path_%d", fPathCount++); + } + + SkString addImage() { + return SkStringPrintf("img_%d", fImageCount++); + } + + SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); } + + SkString addPattern() { + return SkStringPrintf("pattern_%d", fPatternCount++); + } + +private: + uint32_t fGradientCount; + uint32_t fPathCount; + uint32_t fImageCount; + uint32_t fPatternCount; + uint32_t fColorFilterCount; +}; + +struct SkSVGDevice::MxCp { + const SkMatrix* fMatrix; + const SkClipStack* fClipStack; + + MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {} + MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {} +}; + +class SkSVGDevice::AutoElement : ::SkNoncopyable { +public: + AutoElement(const char name[], SkXMLWriter* writer) + : fWriter(writer) + , fResourceBucket(nullptr) { + fWriter->startElement(name); + } + + AutoElement(const char name[], const std::unique_ptr& writer) + : AutoElement(name, writer.get()) {} + + AutoElement(const char name[], SkSVGDevice* svgdev, + ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint) + : fWriter(svgdev->fWriter.get()) + , fResourceBucket(bucket) { + + svgdev->syncClipStack(*mc.fClipStack); + Resources res = this->addResources(mc, paint); + + fWriter->startElement(name); + + this->addPaint(paint, res); + + if (!mc.fMatrix->isIdentity()) { + this->addAttribute("transform", svg_transform(*mc.fMatrix)); + } + } + + ~AutoElement() { + fWriter->endElement(); + } + + void addAttribute(const char name[], const char val[]) { + fWriter->addAttribute(name, val); + } + + void addAttribute(const char name[], const SkString& val) { + fWriter->addAttribute(name, val.c_str()); + } + + void addAttribute(const char name[], int32_t val) { + fWriter->addS32Attribute(name, val); + } + + void addAttribute(const char name[], SkScalar val) { + fWriter->addScalarAttribute(name, val); + } + + void addText(const SkString& text) { + fWriter->addText(text.c_str(), text.size()); + } + + void addRectAttributes(const SkRect&); + void addPathAttributes(const SkPath&); + void addTextAttributes(const SkFont&); + +private: + Resources addResources(const MxCp&, const SkPaint& paint); + void addShaderResources(const SkPaint& paint, Resources* resources); + void addGradientShaderResources(const SkShader* shader, const SkPaint& paint, + Resources* resources); + void addColorFilterResources(const SkColorFilter& cf, Resources* resources); + void addImageShaderResources(const SkShader* shader, const SkPaint& paint, + Resources* resources); + + void addPatternDef(const SkBitmap& bm); + + void addPaint(const SkPaint& paint, const Resources& resources); + + + SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader); + + SkXMLWriter* fWriter; + ResourceBucket* fResourceBucket; +}; + +void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) { + SkPaint::Style style = paint.getStyle(); + if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) { + static constexpr char kDefaultFill[] = "black"; + if (!resources.fPaintServer.equals(kDefaultFill)) { + this->addAttribute("fill", resources.fPaintServer); + + if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { + this->addAttribute("fill-opacity", svg_opacity(paint.getColor())); + } + } + } else { + SkASSERT(style == SkPaint::kStroke_Style); + this->addAttribute("fill", "none"); + } + + if (!resources.fColorFilter.isEmpty()) { + this->addAttribute("filter", resources.fColorFilter.c_str()); + } + + if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) { + this->addAttribute("stroke", resources.fPaintServer); + + SkScalar strokeWidth = paint.getStrokeWidth(); + if (strokeWidth == 0) { + // Hairline stroke + strokeWidth = 1; + this->addAttribute("vector-effect", "non-scaling-stroke"); + } + this->addAttribute("stroke-width", strokeWidth); + + if (const char* cap = svg_cap(paint.getStrokeCap())) { + this->addAttribute("stroke-linecap", cap); + } + + if (const char* join = svg_join(paint.getStrokeJoin())) { + this->addAttribute("stroke-linejoin", join); + } + + if (paint.getStrokeJoin() == SkPaint::kMiter_Join) { + this->addAttribute("stroke-miterlimit", paint.getStrokeMiter()); + } + + if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { + this->addAttribute("stroke-opacity", svg_opacity(paint.getColor())); + } + } else { + SkASSERT(style == SkPaint::kFill_Style); + // SVG default stroke value is "none". + } +} + +Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) { + Resources resources(paint); + + if (paint.getShader()) { + AutoElement defs("defs", fWriter); + + this->addShaderResources(paint, &resources); + } + + if (const SkColorFilter* cf = paint.getColorFilter()) { + // TODO: Implement skia color filters for blend modes other than SrcIn + SkBlendMode mode; + if (cf->asAColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) { + this->addColorFilterResources(*cf, &resources); + } + } + + return resources; +} + +void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader, + const SkPaint& paint, + Resources* resources) { + SkShader::GradientInfo grInfo; + grInfo.fColorCount = 0; + if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) { + // TODO: non-linear gradient support + return; + } + + SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount); + SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount); + grInfo.fColors = grColors.get(); + grInfo.fColorOffsets = grOffsets.get(); + + // One more call to get the actual colors/offsets. + shader->asAGradient(&grInfo); + SkASSERT(grInfo.fColorCount <= grColors.count()); + SkASSERT(grInfo.fColorCount <= grOffsets.count()); + + resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str()); +} + +void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf, + Resources* resources) { + SkString colorfilterID = fResourceBucket->addColorFilter(); + { + AutoElement filterElement("filter", fWriter); + filterElement.addAttribute("id", colorfilterID); + filterElement.addAttribute("x", "0%"); + filterElement.addAttribute("y", "0%"); + filterElement.addAttribute("width", "100%"); + filterElement.addAttribute("height", "100%"); + + SkColor filterColor; + SkBlendMode mode; + bool asAColorMode = cf.asAColorMode(&filterColor, &mode); + SkAssertResult(asAColorMode); + SkASSERT(mode == SkBlendMode::kSrcIn); + + { + // first flood with filter color + AutoElement floodElement("feFlood", fWriter); + floodElement.addAttribute("flood-color", svg_color(filterColor)); + floodElement.addAttribute("flood-opacity", svg_opacity(filterColor)); + floodElement.addAttribute("result", "flood"); + } + + { + // apply the transform to filter color + AutoElement compositeElement("feComposite", fWriter); + compositeElement.addAttribute("in", "flood"); + compositeElement.addAttribute("operator", "in"); + } + } + resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str()); +} + +// Returns data uri from bytes. +// it will use any cached data if available, otherwise will +// encode as png. +sk_sp AsDataUri(SkImage* image) { + sk_sp imageData = image->encodeToData(); + if (!imageData) { + return nullptr; + } + + const char* src = (char*)imageData->data(); + const char* selectedPrefix = nullptr; + size_t selectedPrefixLength = 0; + + const static char pngDataPrefix[] = "data:image/png;base64,"; + const static char jpgDataPrefix[] = "data:image/jpeg;base64,"; + + if (SkJpegCodec::IsJpeg(src, imageData->size())) { + selectedPrefix = jpgDataPrefix; + selectedPrefixLength = sizeof(jpgDataPrefix); + } else { + if (!SkPngCodec::IsPng(src, imageData->size())) { + imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100); + } + selectedPrefix = pngDataPrefix; + selectedPrefixLength = sizeof(pngDataPrefix); + } + + size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr); + sk_sp dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size); + char* dest = (char*)dataUri->writable_data(); + memcpy(dest, selectedPrefix, selectedPrefixLength); + SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1); + dest[dataUri->size() - 1] = 0; + return dataUri; +} + +void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint, + Resources* resources) { + SkMatrix outMatrix; + + SkTileMode xy[2]; + SkImage* image = shader->isAImage(&outMatrix, xy); + SkASSERT(image); + + SkString patternDims[2]; // width, height + + sk_sp dataUri = AsDataUri(image); + if (!dataUri) { + return; + } + SkIRect imageSize = image->bounds(); + for (int i = 0; i < 2; i++) { + int imageDimension = i == 0 ? imageSize.width() : imageSize.height(); + switch (xy[i]) { + case SkTileMode::kRepeat: + patternDims[i].appendScalar(imageDimension); + break; + default: + // TODO: other tile modes? + patternDims[i] = "100%"; + } + } + + SkString patternID = fResourceBucket->addPattern(); + { + AutoElement pattern("pattern", fWriter); + pattern.addAttribute("id", patternID); + pattern.addAttribute("patternUnits", "userSpaceOnUse"); + pattern.addAttribute("patternContentUnits", "userSpaceOnUse"); + pattern.addAttribute("width", patternDims[0]); + pattern.addAttribute("height", patternDims[1]); + pattern.addAttribute("x", 0); + pattern.addAttribute("y", 0); + + { + SkString imageID = fResourceBucket->addImage(); + AutoElement imageTag("image", fWriter); + imageTag.addAttribute("id", imageID); + imageTag.addAttribute("x", 0); + imageTag.addAttribute("y", 0); + imageTag.addAttribute("width", image->width()); + imageTag.addAttribute("height", image->height()); + imageTag.addAttribute("xlink:href", static_cast(dataUri->data())); + } + } + resources->fPaintServer.printf("url(#%s)", patternID.c_str()); +} + +void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) { + const SkShader* shader = paint.getShader(); + SkASSERT(shader); + + if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) { + this->addGradientShaderResources(shader, paint, resources); + } else if (shader->isAImage()) { + this->addImageShaderResources(shader, paint, resources); + } + // TODO: other shader types? +} + +SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info, + const SkShader* shader) { + SkASSERT(fResourceBucket); + SkString id = fResourceBucket->addLinearGradient(); + + { + AutoElement gradient("linearGradient", fWriter); + + gradient.addAttribute("id", id); + gradient.addAttribute("gradientUnits", "userSpaceOnUse"); + gradient.addAttribute("x1", info.fPoint[0].x()); + gradient.addAttribute("y1", info.fPoint[0].y()); + gradient.addAttribute("x2", info.fPoint[1].x()); + gradient.addAttribute("y2", info.fPoint[1].y()); + + if (!as_SB(shader)->getLocalMatrix().isIdentity()) { + this->addAttribute("gradientTransform", svg_transform(as_SB(shader)->getLocalMatrix())); + } + + SkASSERT(info.fColorCount >= 2); + for (int i = 0; i < info.fColorCount; ++i) { + SkColor color = info.fColors[i]; + SkString colorStr(svg_color(color)); + + { + AutoElement stop("stop", fWriter); + stop.addAttribute("offset", info.fColorOffsets[i]); + stop.addAttribute("stop-color", colorStr.c_str()); + + if (SK_AlphaOPAQUE != SkColorGetA(color)) { + stop.addAttribute("stop-opacity", svg_opacity(color)); + } + } + } + } + + return id; +} + +void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) { + // x, y default to 0 + if (rect.x() != 0) { + this->addAttribute("x", rect.x()); + } + if (rect.y() != 0) { + this->addAttribute("y", rect.y()); + } + + this->addAttribute("width", rect.width()); + this->addAttribute("height", rect.height()); +} + +void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) { + SkString pathData; + SkParsePath::ToSVGString(path, &pathData); + this->addAttribute("d", pathData); +} + +void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) { + this->addAttribute("font-size", font.getSize()); + + SkString familyName; + SkTHashSet familySet; + sk_sp tface = font.refTypefaceOrDefault(); + + SkASSERT(tface); + SkFontStyle style = tface->fontStyle(); + if (style.slant() == SkFontStyle::kItalic_Slant) { + this->addAttribute("font-style", "italic"); + } else if (style.slant() == SkFontStyle::kOblique_Slant) { + this->addAttribute("font-style", "oblique"); + } + int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100; + if (weightIndex != 3) { + static constexpr const char* weights[] = { + "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900" + }; + this->addAttribute("font-weight", weights[weightIndex]); + } + int stretchIndex = style.width() - 1; + if (stretchIndex != 4) { + static constexpr const char* stretches[] = { + "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", + "normal", + "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" + }; + this->addAttribute("font-stretch", stretches[stretchIndex]); + } + + sk_sp familyNameIter(tface->createFamilyNameIterator()); + SkTypeface::LocalizedString familyString; + if (familyNameIter) { + while (familyNameIter->next(&familyString)) { + if (familySet.contains(familyString.fString)) { + continue; + } + familySet.add(familyString.fString); + familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str()); + } + } + if (!familyName.isEmpty()) { + this->addAttribute("font-family", familyName); + } +} + +sk_sp SkSVGDevice::Make(const SkISize& size, std::unique_ptr writer, + uint32_t flags) { + return writer ? sk_sp(new SkSVGDevice(size, std::move(writer), flags)) + : nullptr; +} + +SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr writer, uint32_t flags) + : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight), + SkSurfaceProps(0, kUnknown_SkPixelGeometry)) + , fWriter(std::move(writer)) + , fResourceBucket(new ResourceBucket) + , fFlags(flags) +{ + SkASSERT(fWriter); + + fWriter->writeHeader(); + + // The root tag gets closed by the destructor. + fRootElement.reset(new AutoElement("svg", fWriter)); + + fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg"); + fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); + fRootElement->addAttribute("width", size.width()); + fRootElement->addAttribute("height", size.height()); +} + +SkSVGDevice::~SkSVGDevice() { + // Pop order is important. + while (!fClipStack.empty()) { + fClipStack.pop_back(); + } +} + +void SkSVGDevice::syncClipStack(const SkClipStack& cs) { + SkClipStack::B2TIter iter(cs); + + const SkClipStack::Element* elem; + size_t rec_idx = 0; + + // First, find/preserve the common bottom. + while ((elem = iter.next()) && (rec_idx < fClipStack.size())) { + if (fClipStack[SkToInt(rec_idx)].fGenID != elem->getGenID()) { + break; + } + rec_idx++; + } + + // Discard out-of-date stack top. + while (fClipStack.size() > rec_idx) { + fClipStack.pop_back(); + } + + auto define_clip = [this](const SkClipStack::Element* e) { + const auto cid = SkStringPrintf("cl_%x", e->getGenID()); + + AutoElement clip_path("clipPath", fWriter); + clip_path.addAttribute("id", cid); + + // TODO: handle non-intersect clips. + + switch (e->getDeviceSpaceType()) { + case SkClipStack::Element::DeviceSpaceType::kEmpty: { + // TODO: can we skip this? + AutoElement rect("rect", fWriter); + } break; + case SkClipStack::Element::DeviceSpaceType::kRect: { + AutoElement rect("rect", fWriter); + rect.addRectAttributes(e->getDeviceSpaceRect()); + } break; + case SkClipStack::Element::DeviceSpaceType::kRRect: { + // TODO: complex rrect handling? + const auto& rr = e->getDeviceSpaceRRect(); + const auto radii = rr.getSimpleRadii(); + + AutoElement rrect("rect", fWriter); + rrect.addRectAttributes(rr.rect()); + rrect.addAttribute("rx", radii.x()); + rrect.addAttribute("ry", radii.y()); + } break; + case SkClipStack::Element::DeviceSpaceType::kPath: { + const auto& p = e->getDeviceSpacePath(); + AutoElement path("path", fWriter); + path.addPathAttributes(p); + if (p.getFillType() == SkPath::kEvenOdd_FillType) { + path.addAttribute("clip-rule", "evenodd"); + } + } break; + } + + return cid; + }; + + // Rebuild the top. + while (elem) { + const auto cid = define_clip(elem); + + auto clip_grp = skstd::make_unique("g", fWriter); + clip_grp->addAttribute("clip-path", SkStringPrintf("url(#%s)", cid.c_str())); + + fClipStack.push_back({ std::move(clip_grp), elem->getGenID() }); + + elem = iter.next(); + } +} + +void SkSVGDevice::drawPaint(const SkPaint& paint) { + AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint); + rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()), + SkIntToScalar(this->height()))); +} + +void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) { + if (!value) { + return; + } + + if (!strcmp(SkAnnotationKeys::URL_Key(), key) || + !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) { + this->cs().save(); + this->cs().clipRect(rect, this->ctm(), kIntersect_SkClipOp, true); + SkRect transformedRect = this->cs().bounds(this->getGlobalBounds()); + this->cs().restore(); + if (transformedRect.isEmpty()) { + return; + } + + SkString url(static_cast(value->data()), value->size() - 1); + AutoElement a("a", fWriter); + a.addAttribute("xlink:href", url.c_str()); + { + AutoElement r("rect", fWriter); + r.addAttribute("fill-opacity", "0.0"); + r.addRectAttributes(transformedRect); + } + } +} + +void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count, + const SkPoint pts[], const SkPaint& paint) { + SkPath path; + + switch (mode) { + // todo + case SkCanvas::kPoints_PointMode: + // TODO? + break; + case SkCanvas::kLines_PointMode: + count -= 1; + for (size_t i = 0; i < count; i += 2) { + path.rewind(); + path.moveTo(pts[i]); + path.lineTo(pts[i+1]); + AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint); + elem.addPathAttributes(path); + } + break; + case SkCanvas::kPolygon_PointMode: + if (count > 1) { + path.addPoly(pts, SkToInt(count), false); + path.moveTo(pts[0]); + AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint); + elem.addPathAttributes(path); + } + break; + } +} + +void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) { + std::unique_ptr svg; + if (RequiresViewportReset(paint)) { + svg.reset(new AutoElement("svg", this, fResourceBucket.get(), MxCp(this), paint)); + svg->addRectAttributes(r); + } + + AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint); + + if (svg) { + rect.addAttribute("x", 0); + rect.addAttribute("y", 0); + rect.addAttribute("width", "100%"); + rect.addAttribute("height", "100%"); + } else { + rect.addRectAttributes(r); + } +} + +void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) { + AutoElement ellipse("ellipse", this, fResourceBucket.get(), MxCp(this), paint); + ellipse.addAttribute("cx", oval.centerX()); + ellipse.addAttribute("cy", oval.centerY()); + ellipse.addAttribute("rx", oval.width() / 2); + ellipse.addAttribute("ry", oval.height() / 2); +} + +void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) { + SkPath path; + path.addRRect(rr); + + AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint); + elem.addPathAttributes(path); +} + +void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) { + AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint); + elem.addPathAttributes(path); + + // TODO: inverse fill types? + if (path.getFillType() == SkPath::kEvenOdd_FillType) { + elem.addAttribute("fill-rule", "evenodd"); + } +} + +static sk_sp encode(const SkBitmap& src) { + SkDynamicMemoryWStream buf; + return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr; +} + +void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) { + sk_sp pngData = encode(bm); + if (!pngData) { + return; + } + + size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr); + SkAutoTMalloc b64Data(b64Size); + SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get()); + + SkString svgImageData("data:image/png;base64,"); + svgImageData.append(b64Data.get(), b64Size); + + SkString imageID = fResourceBucket->addImage(); + { + AutoElement defs("defs", fWriter); + { + AutoElement image("image", fWriter); + image.addAttribute("id", imageID); + image.addAttribute("width", bm.width()); + image.addAttribute("height", bm.height()); + image.addAttribute("xlink:href", svgImageData); + } + } + + { + AutoElement imageUse("use", this, fResourceBucket.get(), mc, paint); + imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str())); + } +} + +void SkSVGDevice::drawSprite(const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) { + MxCp mc(this); + SkMatrix adjustedMatrix = *mc.fMatrix; + adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y)); + mc.fMatrix = &adjustedMatrix; + + drawBitmapCommon(mc, bitmap, paint); +} + +void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull, + const SkRect& dst, const SkPaint& paint, + SkCanvas::SrcRectConstraint) { + SkClipStack* cs = &this->cs(); + SkClipStack::AutoRestore ar(cs, false); + if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) { + cs->save(); + cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias()); + } + + SkMatrix adjustedMatrix; + adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()), + dst, + SkMatrix::kFill_ScaleToFit); + adjustedMatrix.postConcat(this->ctm()); + + drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint); +} + +class SVGTextBuilder : SkNoncopyable { +public: + SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun) + : fOrigin(origin) { + auto runSize = glyphRun.runSize(); + SkAutoSTArray<64, SkUnichar> unichars(runSize); + SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(), + runSize, unichars.get()); + auto positions = glyphRun.positions(); + for (size_t i = 0; i < runSize; ++i) { + this->appendUnichar(unichars[i], positions[i]); + } + } + + const SkString& text() const { return fText; } + const SkString& posX() const { return fPosXStr; } + const SkString& posY() const { return fHasConstY ? fConstYStr : fPosYStr; } + +private: + void appendUnichar(SkUnichar c, SkPoint position) { + bool discardPos = false; + bool isWhitespace = false; + + switch(c) { + case ' ': + case '\t': + // consolidate whitespace to match SVG's xml:space=default munging + // (http://www.w3.org/TR/SVG/text.html#WhiteSpace) + if (fLastCharWasWhitespace) { + discardPos = true; + } else { + fText.appendUnichar(c); + } + isWhitespace = true; + break; + case '\0': + // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these + // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets) + discardPos = true; + isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation + break; + case '&': + fText.append("&"); + break; + case '"': + fText.append("""); + break; + case '\'': + fText.append("'"); + break; + case '<': + fText.append("<"); + break; + case '>': + fText.append(">"); + break; + default: + fText.appendUnichar(c); + break; + } + + fLastCharWasWhitespace = isWhitespace; + + if (discardPos) { + return; + } + + position += fOrigin; + fPosXStr.appendf("%.8g, ", position.fX); + fPosYStr.appendf("%.8g, ", position.fY); + + if (fConstYStr.isEmpty()) { + fConstYStr = fPosYStr; + fConstY = position.fY; + } else { + fHasConstY &= SkScalarNearlyEqual(fConstY, position.fY); + } + } + + const SkPoint fOrigin; + + SkString fText, + fPosXStr, fPosYStr, + fConstYStr; + SkScalar fConstY; + bool fLastCharWasWhitespace = true, // start off in whitespace mode to strip leading space + fHasConstY = true; +}; + +void SkSVGDevice::drawGlyphRunAsPath(const SkGlyphRun& glyphRun, const SkPoint& origin, + const SkPaint& runPaint) { + this->drawPath(GetPath(glyphRun, origin), runPaint); +} + +void SkSVGDevice::drawGlyphRunAsText(const SkGlyphRun& glyphRun, const SkPoint& origin, + const SkPaint& runPaint) { + AutoElement elem("text", this, fResourceBucket.get(), MxCp(this), runPaint); + elem.addTextAttributes(glyphRun.font()); + + SVGTextBuilder builder(origin, glyphRun); + elem.addAttribute("x", builder.posX()); + elem.addAttribute("y", builder.posY()); + elem.addText(builder.text()); +} + +void SkSVGDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) { + const auto processGlyphRun = (fFlags & SkSVGCanvas::kConvertTextToPaths_Flag) + ? &SkSVGDevice::drawGlyphRunAsPath + : &SkSVGDevice::drawGlyphRunAsText; + + for (auto& glyphRun : glyphRunList) { + (this->*processGlyphRun)(glyphRun, glyphRunList.origin(), glyphRunList.paint()); + } +} + +void SkSVGDevice::drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode, + const SkPaint&) { + // todo +} + +void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y, + const SkPaint&) { + // todo +} diff --git a/gfx/skia/skia/src/svg/SkSVGDevice.h b/gfx/skia/skia/src/svg/SkSVGDevice.h new file mode 100644 index 0000000000..fb850caaef --- /dev/null +++ b/gfx/skia/skia/src/svg/SkSVGDevice.h @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSVGDevice_DEFINED +#define SkSVGDevice_DEFINED + +#include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" +#include "src/core/SkClipStackDevice.h" + +class SkXMLWriter; + +class SkSVGDevice final : public SkClipStackDevice { +public: + static sk_sp Make(const SkISize& size, std::unique_ptr, + uint32_t flags); + +protected: + void drawPaint(const SkPaint& paint) override; + void drawAnnotation(const SkRect& rect, const char key[], SkData* value) override; + void drawPoints(SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint) override; + void drawRect(const SkRect& r, const SkPaint& paint) override; + void drawOval(const SkRect& oval, const SkPaint& paint) override; + void drawRRect(const SkRRect& rr, const SkPaint& paint) override; + void drawPath(const SkPath& path, + const SkPaint& paint, + bool pathIsMutable = false) override; + + void drawSprite(const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) override; + void drawBitmapRect(const SkBitmap&, + const SkRect* srcOrNull, const SkRect& dst, + const SkPaint& paint, SkCanvas::SrcRectConstraint) override; + void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override; + void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, SkBlendMode, + const SkPaint& paint) override; + + void drawDevice(SkBaseDevice*, int x, int y, + const SkPaint&) override; + +private: + SkSVGDevice(const SkISize& size, std::unique_ptr, uint32_t); + ~SkSVGDevice() override; + + void drawGlyphRunAsText(const SkGlyphRun&, const SkPoint&, const SkPaint&); + void drawGlyphRunAsPath(const SkGlyphRun&, const SkPoint&, const SkPaint&); + + struct MxCp; + void drawBitmapCommon(const MxCp&, const SkBitmap& bm, const SkPaint& paint); + + void syncClipStack(const SkClipStack&); + + class AutoElement; + class ResourceBucket; + + const std::unique_ptr fWriter; + const std::unique_ptr fResourceBucket; + const uint32_t fFlags; + + struct ClipRec { + std::unique_ptr fClipPathElem; + uint32_t fGenID; + }; + + std::unique_ptr fRootElement; + SkTArray fClipStack; + + typedef SkClipStackDevice INHERITED; +}; + +#endif // SkSVGDevice_DEFINED diff --git a/gfx/skia/skia/src/utils/Sk3D.cpp b/gfx/skia/skia/src/utils/Sk3D.cpp new file mode 100644 index 0000000000..0df42b5fa9 --- /dev/null +++ b/gfx/skia/skia/src/utils/Sk3D.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/utils/Sk3D.h" + +static void set_col(SkMatrix44* m, int col, const SkPoint3& v) { + m->set(0, col, v.fX); + m->set(1, col, v.fY); + m->set(2, col, v.fZ); +} + +static SkPoint3 cross(const SkPoint3& a, const SkPoint3& b) { + return { + a.fY * b.fZ - a.fZ * b.fY, + a.fZ * b.fX - a.fX * b.fZ, + a.fX * b.fY - a.fY * b.fX, + }; +} + +void Sk3LookAt(SkMatrix44* dst, const SkPoint3& eye, const SkPoint3& center, const SkPoint3& up) { + SkPoint3 f = center - eye; + f.normalize(); + SkPoint3 u = up; + u.normalize(); + SkPoint3 s = cross(f, u); + s.normalize(); + u = cross(s, f); + + dst->setIdentity(); + set_col(dst, 0, s); + set_col(dst, 1, u); + set_col(dst, 2, -f); + set_col(dst, 3, eye); + dst->invert(dst); +} + +bool Sk3Perspective(SkMatrix44* dst, float near, float far, float angle) { + SkASSERT(far > near); + + float denomInv = sk_ieee_float_divide(1, far - near); + float halfAngle = angle * 0.5f; + float cot = sk_float_cos(halfAngle) / sk_float_sin(halfAngle); + + dst->setIdentity(); + dst->set(0, 0, cot); + dst->set(1, 1, cot); + dst->set(2, 2, (far + near) * denomInv); + dst->set(2, 3, 2 * far * near * denomInv); + dst->set(3, 2, -1); + return true; +} + +void Sk3MapPts(SkPoint dst[], const SkMatrix44& m4, const SkPoint3 src[], int count) { + for (int i = 0; i < count; ++i) { + SkVector4 v = m4 * SkVector4{ src[i].fX, src[i].fY, src[i].fZ, 1 }; + // clip v; + dst[i] = { v.fData[0] / v.fData[3], v.fData[1] / v.fData[3] }; + } +} + diff --git a/gfx/skia/skia/src/utils/SkAnimCodecPlayer.cpp b/gfx/skia/skia/src/utils/SkAnimCodecPlayer.cpp new file mode 100644 index 0000000000..dc827e5f6f --- /dev/null +++ b/gfx/skia/skia/src/utils/SkAnimCodecPlayer.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkImage.h" +#include "include/utils/SkAnimCodecPlayer.h" +#include "src/codec/SkCodecImageGenerator.h" +#include + +SkAnimCodecPlayer::SkAnimCodecPlayer(std::unique_ptr codec) : fCodec(std::move(codec)) { + fImageInfo = fCodec->getInfo(); + fFrameInfos = fCodec->getFrameInfo(); + fImages.resize(fFrameInfos.size()); + + // change the interpretation of fDuration to a end-time for that frame + size_t dur = 0; + for (auto& f : fFrameInfos) { + dur += f.fDuration; + f.fDuration = dur; + } + fTotalDuration = dur; + + if (!fTotalDuration) { + // Static image -- may or may not have returned a single frame info. + fFrameInfos.clear(); + fImages.clear(); + fImages.push_back(SkImage::MakeFromGenerator( + SkCodecImageGenerator::MakeFromCodec(std::move(fCodec)))); + } +} + +SkAnimCodecPlayer::~SkAnimCodecPlayer() {} + +SkISize SkAnimCodecPlayer::dimensions() { + return { fImageInfo.width(), fImageInfo.height() }; +} + +sk_sp SkAnimCodecPlayer::getFrameAt(int index) { + SkASSERT((unsigned)index < fFrameInfos.size()); + + if (fImages[index]) { + return fImages[index]; + } + + size_t rb = fImageInfo.minRowBytes(); + size_t size = fImageInfo.computeByteSize(rb); + auto data = SkData::MakeUninitialized(size); + + SkCodec::Options opts; + opts.fFrameIndex = index; + + const int requiredFrame = fFrameInfos[index].fRequiredFrame; + if (requiredFrame != SkCodec::kNoFrame) { + auto requiredImage = fImages[requiredFrame]; + SkPixmap requiredPM; + if (requiredImage && requiredImage->peekPixels(&requiredPM)) { + sk_careful_memcpy(data->writable_data(), requiredPM.addr(), size); + opts.fPriorFrame = requiredFrame; + } + } + if (SkCodec::kSuccess == fCodec->getPixels(fImageInfo, data->writable_data(), rb, &opts)) { + return fImages[index] = SkImage::MakeRasterData(fImageInfo, std::move(data), rb); + } + return nullptr; +} + +sk_sp SkAnimCodecPlayer::getFrame() { + SkASSERT(fTotalDuration > 0 || fImages.size() == 1); + + return fTotalDuration > 0 + ? this->getFrameAt(fCurrIndex) + : fImages.front(); +} + +bool SkAnimCodecPlayer::seek(uint32_t msec) { + if (!fTotalDuration) { + return false; + } + + msec %= fTotalDuration; + + auto lower = std::lower_bound(fFrameInfos.begin(), fFrameInfos.end(), msec, + [](const SkCodec::FrameInfo& info, uint32_t msec) { + return (uint32_t)info.fDuration < msec; + }); + int prevIndex = fCurrIndex; + fCurrIndex = lower - fFrameInfos.begin(); + return fCurrIndex != prevIndex; +} + + diff --git a/gfx/skia/skia/src/utils/SkBase64.cpp b/gfx/skia/skia/src/utils/SkBase64.cpp new file mode 100644 index 0000000000..225cb56c14 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkBase64.cpp @@ -0,0 +1,163 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "include/utils/SkBase64.h" + +#define DecodePad -2 +#define EncodePad 64 + +static const char default_encode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/="; + +static const signed char decodeData[] = { + 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, DecodePad, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +SkBase64::SkBase64() : fLength((size_t) -1), fData(nullptr) { +} + +#if defined _WIN32 // disable 'two', etc. may be used without having been initialized +#pragma warning ( push ) +#pragma warning ( disable : 4701 ) +#endif + +SkBase64::Error SkBase64::decode(const void* srcPtr, size_t size, bool writeDestination) { + unsigned char* dst = (unsigned char*) fData; + const unsigned char* dstStart = (const unsigned char*) fData; + const unsigned char* src = (const unsigned char*) srcPtr; + bool padTwo = false; + bool padThree = false; + const unsigned char* end = src + size; + while (src < end) { + unsigned char bytes[4]; + int byte = 0; + do { + unsigned char srcByte = *src++; + if (srcByte == 0) + goto goHome; + if (srcByte <= ' ') + continue; // treat as white space + if (srcByte < '+' || srcByte > 'z') + return kBadCharError; + signed char decoded = decodeData[srcByte - '+']; + bytes[byte] = decoded; + if (decoded < 0) { + if (decoded == DecodePad) + goto handlePad; + return kBadCharError; + } else + byte++; + if (*src) + continue; + if (byte == 0) + goto goHome; + if (byte == 4) + break; +handlePad: + if (byte < 2) + return kPadError; + padThree = true; + if (byte == 2) + padTwo = true; + break; + } while (byte < 4); + int two = 0; + int three = 0; + if (writeDestination) { + int one = (uint8_t) (bytes[0] << 2); + two = bytes[1]; + one |= two >> 4; + two = (uint8_t) ((two << 4) & 0xFF); + three = bytes[2]; + two |= three >> 2; + three = (uint8_t) ((three << 6) & 0xFF); + three |= bytes[3]; + SkASSERT(one < 256 && two < 256 && three < 256); + *dst = (unsigned char) one; + } + dst++; + if (padTwo) + break; + if (writeDestination) + *dst = (unsigned char) two; + dst++; + if (padThree) + break; + if (writeDestination) + *dst = (unsigned char) three; + dst++; + } +goHome: + fLength = dst - dstStart; + return kNoError; +} + +#if defined _WIN32 +#pragma warning ( pop ) +#endif + +size_t SkBase64::Encode(const void* srcPtr, size_t length, void* dstPtr, const char* encodeMap) { + const char* encode; + if (nullptr == encodeMap) { + encode = default_encode; + } else { + encode = encodeMap; + } + const unsigned char* src = (const unsigned char*) srcPtr; + unsigned char* dst = (unsigned char*) dstPtr; + if (dst) { + size_t remainder = length % 3; + const unsigned char* end = &src[length - remainder]; + while (src < end) { + unsigned a = *src++; + unsigned b = *src++; + unsigned c = *src++; + int d = c & 0x3F; + c = (c >> 6 | b << 2) & 0x3F; + b = (b >> 4 | a << 4) & 0x3F; + a = a >> 2; + *dst++ = encode[a]; + *dst++ = encode[b]; + *dst++ = encode[c]; + *dst++ = encode[d]; + } + if (remainder > 0) { + int k1 = 0; + int k2 = EncodePad; + int a = (uint8_t) *src++; + if (remainder == 2) + { + int b = *src++; + k1 = b >> 4; + k2 = (b << 2) & 0x3F; + } + *dst++ = encode[a >> 2]; + *dst++ = encode[(k1 | a << 4) & 0x3F]; + *dst++ = encode[k2]; + *dst++ = encode[EncodePad]; + } + } + return (length + 2) / 3 * 4; +} + +SkBase64::Error SkBase64::decode(const char* src, size_t len) { + Error err = decode(src, len, false); + SkASSERT(err == kNoError); + if (err != kNoError) + return err; + fData = new char[fLength]; // should use sk_malloc/sk_free + decode(src, len, true); + return kNoError; +} diff --git a/gfx/skia/skia/src/utils/SkBitSet.h b/gfx/skia/skia/src/utils/SkBitSet.h new file mode 100644 index 0000000000..770b0d8bce --- /dev/null +++ b/gfx/skia/skia/src/utils/SkBitSet.h @@ -0,0 +1,67 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBitSet_DEFINED +#define SkBitSet_DEFINED + +#include "include/private/SkTemplates.h" + +class SkBitSet { +public: + explicit SkBitSet(int numberOfBits) { + SkASSERT(numberOfBits >= 0); + fDwordCount = (numberOfBits + 31) / 32; // Round up size to 32-bit boundary. + if (fDwordCount > 0) { + fBitData.reset((uint32_t*)sk_calloc_throw(fDwordCount * sizeof(uint32_t))); + } + } + + /** Set the value of the index-th bit to true. */ + void set(int index) { + uint32_t mask = 1 << (index & 31); + uint32_t* chunk = this->internalGet(index); + SkASSERT(chunk); + *chunk |= mask; + } + + bool has(int index) const { + const uint32_t* chunk = this->internalGet(index); + uint32_t mask = 1 << (index & 31); + return chunk && SkToBool(*chunk & mask); + } + + // Calls f(unsigned) for each set value. + template + void getSetValues(FN f) const { + const uint32_t* data = fBitData.get(); + for (unsigned i = 0; i < fDwordCount; ++i) { + if (uint32_t value = data[i]) { // There are set bits + unsigned index = i * 32; + for (unsigned j = 0; j < 32; ++j) { + if (0x1 & (value >> j)) { + f(index | j); + } + } + } + } + } + +private: + std::unique_ptr> fBitData; + size_t fDwordCount; // Dword (32-bit) count of the bitset. + + uint32_t* internalGet(int index) const { + size_t internalIndex = index / 32; + if (internalIndex >= fDwordCount) { + return nullptr; + } + return fBitData.get() + internalIndex; + } +}; + + +#endif diff --git a/gfx/skia/skia/src/utils/SkCallableTraits.h b/gfx/skia/skia/src/utils/SkCallableTraits.h new file mode 100644 index 0000000000..003db21280 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCallableTraits.h @@ -0,0 +1,86 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCallableTraits_DEFINED +#define SkCallableTraits_DEFINED + +#include +#include + +template struct sk_base_callable_traits { + using return_type = R; + static constexpr std::size_t arity = sizeof...(Args); + template struct argument { + static_assert(N < arity, ""); + using type = typename std::tuple_element>::type; + }; +}; + +#define SK_CALLABLE_TRAITS__COMMA , + +#define SK_CALLABLE_TRAITS__VARARGS(quals, _) \ +SK_CALLABLE_TRAITS__INSTANCE(quals,) \ +SK_CALLABLE_TRAITS__INSTANCE(quals, SK_CALLABLE_TRAITS__COMMA ...) + +#ifdef __cpp_noexcept_function_type +#define SK_CALLABLE_TRAITS__NE_VARARGS(quals, _) \ +SK_CALLABLE_TRAITS__VARARGS(quals,) \ +SK_CALLABLE_TRAITS__VARARGS(quals noexcept,) +#else +#define SK_CALLABLE_TRAITS__NE_VARARGS(quals, _) \ +SK_CALLABLE_TRAITS__VARARGS(quals,) +#endif + +#define SK_CALLABLE_TRAITS__REF_NE_VARARGS(quals, _) \ +SK_CALLABLE_TRAITS__NE_VARARGS(quals,) \ +SK_CALLABLE_TRAITS__NE_VARARGS(quals &,) \ +SK_CALLABLE_TRAITS__NE_VARARGS(quals &&,) + +#define SK_CALLABLE_TRAITS__CV_REF_NE_VARARGS() \ +SK_CALLABLE_TRAITS__REF_NE_VARARGS(,) \ +SK_CALLABLE_TRAITS__REF_NE_VARARGS(const,) \ +SK_CALLABLE_TRAITS__REF_NE_VARARGS(volatile,) \ +SK_CALLABLE_TRAITS__REF_NE_VARARGS(const volatile,) + +/** Infer the return_type and argument of a callable type T. */ +template struct SkCallableTraits : SkCallableTraits {}; + +// function (..., (const, volatile), (&, &&), noexcept) +#define SK_CALLABLE_TRAITS__INSTANCE(quals, varargs) \ +template \ +struct SkCallableTraits : sk_base_callable_traits {}; + +SK_CALLABLE_TRAITS__CV_REF_NE_VARARGS() +#undef SK_CALLABLE_TRAITS__INSTANCE + +// pointer to function (..., noexcept) +#define SK_CALLABLE_TRAITS__INSTANCE(quals, varargs) \ +template \ +struct SkCallableTraits : sk_base_callable_traits {}; + +SK_CALLABLE_TRAITS__NE_VARARGS(,) +#undef SK_CALLABLE_TRAITS__INSTANCE + +// pointer to method (..., (const, volatile), (&, &&), noexcept) +#define SK_CALLABLE_TRAITS__INSTANCE(quals, varargs) \ +template \ +struct SkCallableTraits : sk_base_callable_traits {}; + +SK_CALLABLE_TRAITS__CV_REF_NE_VARARGS() +#undef SK_CALLABLE_TRAITS__INSTANCE + +// pointer to field +template +struct SkCallableTraits : sk_base_callable_traits::type> {}; + +#undef SK_CALLABLE_TRAITS__CV_REF_NE_VARARGS +#undef SK_CALLABLE_TRAITS__REF_NE_VARARGS +#undef SK_CALLABLE_TRAITS__NE_VARARGS +#undef SK_CALLABLE_TRAITS__VARARGS +#undef SK_CALLABLE_TRAITS__COMMA + +#endif diff --git a/gfx/skia/skia/src/utils/SkCamera.cpp b/gfx/skia/skia/src/utils/SkCamera.cpp new file mode 100644 index 0000000000..63d1c804fa --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCamera.cpp @@ -0,0 +1,397 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/utils/SkCamera.h" + +static SkScalar SkScalarDotDiv(int count, const SkScalar a[], int step_a, + const SkScalar b[], int step_b, + SkScalar denom) { + SkScalar prod = 0; + for (int i = 0; i < count; i++) { + prod += a[0] * b[0]; + a += step_a; + b += step_b; + } + return prod / denom; +} + +static SkScalar SkScalarDot(int count, const SkScalar a[], int step_a, + const SkScalar b[], int step_b) { + SkScalar prod = 0; + for (int i = 0; i < count; i++) { + prod += a[0] * b[0]; + a += step_a; + b += step_b; + } + return prod; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkScalar SkPoint3D::normalize(SkUnit3D* unit) const { + SkScalar mag = SkScalarSqrt(fX*fX + fY*fY + fZ*fZ); + if (mag) { + SkScalar scale = SkScalarInvert(mag); + unit->fX = fX * scale; + unit->fY = fY * scale; + unit->fZ = fZ * scale; + } else { + unit->fX = unit->fY = unit->fZ = 0; + } + return mag; +} + +SkScalar SkUnit3D::Dot(const SkUnit3D& a, const SkUnit3D& b) { + return a.fX * b.fX + a.fY * b.fY + a.fZ * b.fZ; +} + +void SkUnit3D::Cross(const SkUnit3D& a, const SkUnit3D& b, SkUnit3D* cross) { + SkASSERT(cross); + + // use x,y,z, in case &a == cross or &b == cross + + SkScalar x = a.fY * b.fZ - a.fZ * b.fY; + SkScalar y = a.fZ * b.fX - a.fX * b.fY; + SkScalar z = a.fX * b.fY - a.fY * b.fX; + + cross->set(x, y, z); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPatch3D::SkPatch3D() { + this->reset(); +} + +void SkPatch3D::reset() { + fOrigin.set(0, 0, 0); + fU.set(SK_Scalar1, 0, 0); + fV.set(0, -SK_Scalar1, 0); +} + +void SkPatch3D::transform(const SkMatrix3D& m, SkPatch3D* dst) const { + if (dst == nullptr) { + dst = (SkPatch3D*)this; + } + m.mapVector(fU, &dst->fU); + m.mapVector(fV, &dst->fV); + m.mapPoint(fOrigin, &dst->fOrigin); +} + +SkScalar SkPatch3D::dotWith(SkScalar dx, SkScalar dy, SkScalar dz) const { + SkScalar cx = fU.fY * fV.fZ - fU.fZ * fV.fY; + SkScalar cy = fU.fZ * fV.fX - fU.fX * fV.fY; + SkScalar cz = fU.fX * fV.fY - fU.fY * fV.fX; + + return cx * dx + cy * dy + cz * dz; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkMatrix3D::reset() { + memset(fMat, 0, sizeof(fMat)); + fMat[0][0] = fMat[1][1] = fMat[2][2] = SK_Scalar1; +} + +void SkMatrix3D::setTranslate(SkScalar x, SkScalar y, SkScalar z) { + memset(fMat, 0, sizeof(fMat)); + fMat[0][0] = x; + fMat[1][1] = y; + fMat[2][2] = z; +} + +void SkMatrix3D::setRotateX(SkScalar degX) { + SkScalar r = SkDegreesToRadians(degX), + s = SkScalarSin(r), + c = SkScalarCos(r); + this->setRow(0, SK_Scalar1, 0, 0); + this->setRow(1, 0, c, -s); + this->setRow(2, 0, s, c); +} + +void SkMatrix3D::setRotateY(SkScalar degY) { + SkScalar r = SkDegreesToRadians(degY), + s = SkScalarSin(r), + c = SkScalarCos(r); + this->setRow(0, c, 0, -s); + this->setRow(1, 0, SK_Scalar1, 0); + this->setRow(2, s, 0, c); +} + +void SkMatrix3D::setRotateZ(SkScalar degZ) { + SkScalar r = SkDegreesToRadians(degZ), + s = SkScalarSin(r), + c = SkScalarCos(r); + this->setRow(0, c, -s, 0); + this->setRow(1, s, c, 0); + this->setRow(2, 0, 0, SK_Scalar1); +} + +void SkMatrix3D::preTranslate(SkScalar x, SkScalar y, SkScalar z) { + SkScalar col[3] = { x, y, z}; + + for (int i = 0; i < 3; i++) { + fMat[i][3] += SkScalarDot(3, &fMat[i][0], 1, col, 1); + } +} + +void SkMatrix3D::preRotateX(SkScalar degX) { + SkMatrix3D m; + m.setRotateX(degX); + this->setConcat(*this, m); +} + +void SkMatrix3D::preRotateY(SkScalar degY) { + SkMatrix3D m; + m.setRotateY(degY); + this->setConcat(*this, m); +} + +void SkMatrix3D::preRotateZ(SkScalar degZ) { + SkMatrix3D m; + m.setRotateZ(degZ); + this->setConcat(*this, m); +} + +void SkMatrix3D::setConcat(const SkMatrix3D& a, const SkMatrix3D& b) { + SkMatrix3D tmp; + SkMatrix3D* c = this; + + if (this == &a || this == &b) { + c = &tmp; + } + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + c->fMat[i][j] = SkScalarDot(3, &a.fMat[i][0], 1, &b.fMat[0][j], 4); + } + c->fMat[i][3] = SkScalarDot(3, &a.fMat[i][0], 1, + &b.fMat[0][3], 4) + a.fMat[i][3]; + } + + if (c == &tmp) { + *this = tmp; + } +} + +void SkMatrix3D::mapPoint(const SkPoint3D& src, SkPoint3D* dst) const { + SkScalar x = SkScalarDot(3, &fMat[0][0], 1, &src.fX, 1) + fMat[0][3]; + SkScalar y = SkScalarDot(3, &fMat[1][0], 1, &src.fX, 1) + fMat[1][3]; + SkScalar z = SkScalarDot(3, &fMat[2][0], 1, &src.fX, 1) + fMat[2][3]; + dst->set(x, y, z); +} + +void SkMatrix3D::mapVector(const SkVector3D& src, SkVector3D* dst) const { + SkScalar x = SkScalarDot(3, &fMat[0][0], 1, &src.fX, 1); + SkScalar y = SkScalarDot(3, &fMat[1][0], 1, &src.fX, 1); + SkScalar z = SkScalarDot(3, &fMat[2][0], 1, &src.fX, 1); + dst->set(x, y, z); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkCamera3D::SkCamera3D() { + this->reset(); +} + +void SkCamera3D::reset() { + fLocation.set(0, 0, -SkIntToScalar(576)); // 8 inches backward + fAxis.set(0, 0, SK_Scalar1); // forward + fZenith.set(0, -SK_Scalar1, 0); // up + + fObserver.set(0, 0, fLocation.fZ); + + fNeedToUpdate = true; +} + +void SkCamera3D::update() { + fNeedToUpdate = true; +} + +void SkCamera3D::doUpdate() const { + SkUnit3D axis, zenith, cross; + + // construct a orthonormal basis of cross (x), zenith (y), and axis (z) + fAxis.normalize(&axis); + + { + SkScalar dot = SkUnit3D::Dot(SkUnit3D{fZenith.fX, fZenith.fY, fZenith.fZ}, axis); + + zenith.fX = fZenith.fX - dot * axis.fX; + zenith.fY = fZenith.fY - dot * axis.fY; + zenith.fZ = fZenith.fZ - dot * axis.fZ; + + SkPoint3D{zenith.fX, zenith.fY, zenith.fZ}.normalize(&zenith); + } + + SkUnit3D::Cross(axis, zenith, &cross); + + { + SkMatrix* orien = &fOrientation; + SkScalar x = fObserver.fX; + SkScalar y = fObserver.fY; + SkScalar z = fObserver.fZ; + + // Looking along the view axis we have: + // + // /|\ zenith + // | + // | + // | * observer (projected on XY plane) + // | + // |____________\ cross + // / + // + // So this does a z-shear along the view axis based on the observer's x and y values, + // and scales in x and y relative to the negative of the observer's z value + // (the observer is in the negative z direction). + + orien->set(SkMatrix::kMScaleX, x * axis.fX - z * cross.fX); + orien->set(SkMatrix::kMSkewX, x * axis.fY - z * cross.fY); + orien->set(SkMatrix::kMTransX, x * axis.fZ - z * cross.fZ); + orien->set(SkMatrix::kMSkewY, y * axis.fX - z * zenith.fX); + orien->set(SkMatrix::kMScaleY, y * axis.fY - z * zenith.fY); + orien->set(SkMatrix::kMTransY, y * axis.fZ - z * zenith.fZ); + orien->set(SkMatrix::kMPersp0, axis.fX); + orien->set(SkMatrix::kMPersp1, axis.fY); + orien->set(SkMatrix::kMPersp2, axis.fZ); + } +} + +void SkCamera3D::patchToMatrix(const SkPatch3D& quilt, SkMatrix* matrix) const { + if (fNeedToUpdate) { + this->doUpdate(); + fNeedToUpdate = false; + } + + const SkScalar* mapPtr = (const SkScalar*)(const void*)&fOrientation; + const SkScalar* patchPtr; + SkPoint3D diff; + SkScalar dot; + + diff.fX = quilt.fOrigin.fX - fLocation.fX; + diff.fY = quilt.fOrigin.fY - fLocation.fY; + diff.fZ = quilt.fOrigin.fZ - fLocation.fZ; + + dot = SkUnit3D::Dot(SkUnit3D{diff.fX, diff.fY, diff.fZ}, + SkUnit3D{mapPtr[6], mapPtr[7], mapPtr[8]}); + + // This multiplies fOrientation by the matrix [quilt.fU quilt.fV diff] -- U, V, and diff are + // column vectors in the matrix -- then divides by the length of the projection of diff onto + // the view axis (which is 'dot'). This transforms the patch (which transforms from local path + // space to world space) into view space (since fOrientation transforms from world space to + // view space). + // + // The divide by 'dot' isn't strictly necessary as the homogeneous divide would do much the + // same thing (it's just scaling the entire matrix by 1/dot). It looks like it's normalizing + // the matrix into some canonical space. + patchPtr = (const SkScalar*)&quilt; + matrix->set(SkMatrix::kMScaleX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot)); + matrix->set(SkMatrix::kMSkewY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot)); + matrix->set(SkMatrix::kMPersp0, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot)); + + patchPtr += 3; + matrix->set(SkMatrix::kMSkewX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot)); + matrix->set(SkMatrix::kMScaleY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot)); + matrix->set(SkMatrix::kMPersp1, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot)); + + patchPtr = (const SkScalar*)(const void*)&diff; + matrix->set(SkMatrix::kMTransX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot)); + matrix->set(SkMatrix::kMTransY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot)); + matrix->set(SkMatrix::kMPersp2, SK_Scalar1); +} + +/////////////////////////////////////////////////////////////////////////////// + +Sk3DView::Sk3DView() { + fInitialRec.fMatrix.reset(); + fRec = &fInitialRec; +} + +Sk3DView::~Sk3DView() { + Rec* rec = fRec; + while (rec != &fInitialRec) { + Rec* next = rec->fNext; + delete rec; + rec = next; + } +} + +void Sk3DView::save() { + Rec* rec = new Rec; + rec->fNext = fRec; + rec->fMatrix = fRec->fMatrix; + fRec = rec; +} + +void Sk3DView::restore() { + SkASSERT(fRec != &fInitialRec); + Rec* next = fRec->fNext; + delete fRec; + fRec = next; +} + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK +void Sk3DView::setCameraLocation(SkScalar x, SkScalar y, SkScalar z) { + // the camera location is passed in inches, set in pt + SkScalar lz = z * 72.0f; + fCamera.fLocation.set(x * 72.0f, y * 72.0f, lz); + fCamera.fObserver.set(0, 0, lz); + fCamera.update(); + +} + +SkScalar Sk3DView::getCameraLocationX() const { + return fCamera.fLocation.fX / 72.0f; +} + +SkScalar Sk3DView::getCameraLocationY() const { + return fCamera.fLocation.fY / 72.0f; +} + +SkScalar Sk3DView::getCameraLocationZ() const { + return fCamera.fLocation.fZ / 72.0f; +} +#endif + +void Sk3DView::translate(SkScalar x, SkScalar y, SkScalar z) { + fRec->fMatrix.preTranslate(x, y, z); +} + +void Sk3DView::rotateX(SkScalar deg) { + fRec->fMatrix.preRotateX(deg); +} + +void Sk3DView::rotateY(SkScalar deg) { + fRec->fMatrix.preRotateY(deg); +} + +void Sk3DView::rotateZ(SkScalar deg) { + fRec->fMatrix.preRotateZ(deg); +} + +SkScalar Sk3DView::dotWithNormal(SkScalar x, SkScalar y, SkScalar z) const { + SkPatch3D patch; + patch.transform(fRec->fMatrix); + return patch.dotWith(x, y, z); +} + +void Sk3DView::getMatrix(SkMatrix* matrix) const { + if (matrix != nullptr) { + SkPatch3D patch; + patch.transform(fRec->fMatrix); + fCamera.patchToMatrix(patch, matrix); + } +} + +#include "include/core/SkCanvas.h" + +void Sk3DView::applyToCanvas(SkCanvas* canvas) const { + SkMatrix matrix; + + this->getMatrix(&matrix); + canvas->concat(matrix); +} diff --git a/gfx/skia/skia/src/utils/SkCanvasStack.cpp b/gfx/skia/skia/src/utils/SkCanvasStack.cpp new file mode 100644 index 0000000000..93612e2724 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCanvasStack.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/utils/SkCanvasStack.h" + +SkCanvasStack::SkCanvasStack(int width, int height) + : INHERITED(width, height) {} + +SkCanvasStack::~SkCanvasStack() { + this->removeAll(); +} + +void SkCanvasStack::pushCanvas(std::unique_ptr canvas, const SkIPoint& origin) { + if (canvas) { + // compute the bounds of this canvas + const SkIRect canvasBounds = SkIRect::MakeSize(canvas->getBaseLayerSize()); + + // push the canvas onto the stack + this->INHERITED::addCanvas(canvas.get()); + + // push the canvas data onto the stack + CanvasData* data = &fCanvasData.push_back(); + data->origin = origin; + data->requiredClip.setRect(canvasBounds); + data->ownedCanvas = std::move(canvas); + + // subtract this region from the canvas objects already on the stack. + // This ensures they do not draw into the space occupied by the layers + // above them. + for (int i = fList.count() - 1; i > 0; --i) { + SkIRect localBounds = canvasBounds; + localBounds.offset(origin - fCanvasData[i-1].origin); + + fCanvasData[i-1].requiredClip.op(localBounds, SkRegion::kDifference_Op); + fList[i-1]->clipRegion(fCanvasData[i-1].requiredClip); + } + } + SkASSERT(fList.count() == fCanvasData.count()); +} + +void SkCanvasStack::removeAll() { + this->INHERITED::removeAll(); // call the baseclass *before* we actually delete the canvases + fCanvasData.reset(); +} + +/** + * Traverse all canvases (e.g. layers) the stack and ensure that they are clipped + * to their bounds and that the area covered by any canvas higher in the stack is + * also clipped out. + */ +void SkCanvasStack::clipToZOrderedBounds() { + SkASSERT(fList.count() == fCanvasData.count()); + for (int i = 0; i < fList.count(); ++i) { + fList[i]->clipRegion(fCanvasData[i].requiredClip); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/** + * We need to handle setMatrix specially as it overwrites the matrix in each + * canvas unlike all other matrix operations (i.e. translate, scale, etc) which + * just pre-concatenate with the existing matrix. + */ +void SkCanvasStack::didSetMatrix(const SkMatrix& matrix) { + SkASSERT(fList.count() == fCanvasData.count()); + for (int i = 0; i < fList.count(); ++i) { + + SkMatrix tempMatrix = matrix; + tempMatrix.postTranslate(SkIntToScalar(-fCanvasData[i].origin.x()), + SkIntToScalar(-fCanvasData[i].origin.y())); + fList[i]->setMatrix(tempMatrix); + } + this->SkCanvas::didSetMatrix(matrix); +} + +void SkCanvasStack::onClipRect(const SkRect& r, SkClipOp op, ClipEdgeStyle edgeStyle) { + this->INHERITED::onClipRect(r, op, edgeStyle); + this->clipToZOrderedBounds(); +} + +void SkCanvasStack::onClipRRect(const SkRRect& rr, SkClipOp op, ClipEdgeStyle edgeStyle) { + this->INHERITED::onClipRRect(rr, op, edgeStyle); + this->clipToZOrderedBounds(); +} + +void SkCanvasStack::onClipPath(const SkPath& p, SkClipOp op, ClipEdgeStyle edgeStyle) { + this->INHERITED::onClipPath(p, op, edgeStyle); + this->clipToZOrderedBounds(); +} + +void SkCanvasStack::onClipRegion(const SkRegion& deviceRgn, SkClipOp op) { + SkASSERT(fList.count() == fCanvasData.count()); + for (int i = 0; i < fList.count(); ++i) { + SkRegion tempRegion; + deviceRgn.translate(-fCanvasData[i].origin.x(), + -fCanvasData[i].origin.y(), &tempRegion); + tempRegion.op(fCanvasData[i].requiredClip, SkRegion::kIntersect_Op); + fList[i]->clipRegion(tempRegion, op); + } + this->SkCanvas::onClipRegion(deviceRgn, op); +} diff --git a/gfx/skia/skia/src/utils/SkCanvasStack.h b/gfx/skia/skia/src/utils/SkCanvasStack.h new file mode 100644 index 0000000000..ae293c7d77 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCanvasStack.h @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCanvasStack_DEFINED +#define SkCanvasStack_DEFINED + +#include "include/core/SkRegion.h" +#include "include/private/SkTArray.h" +#include "include/utils/SkNWayCanvas.h" + +/** + * Like NWayCanvas, in that it forwards all canvas methods to each sub-canvas that is "pushed". + * + * Unlike NWayCanvas, this takes ownership of each subcanvas, and deletes them when this canvas + * is deleted. + */ +class SkCanvasStack : public SkNWayCanvas { +public: + SkCanvasStack(int width, int height); + ~SkCanvasStack() override; + + void pushCanvas(std::unique_ptr, const SkIPoint& origin); + void removeAll() override; + + /* + * The following add/remove canvas methods are overrides from SkNWayCanvas + * that do not make sense in the context of our CanvasStack, but since we + * can share most of the other implementation of NWay we override those + * methods to be no-ops. + */ + void addCanvas(SkCanvas*) override { SkDEBUGFAIL("Invalid Op"); } + void removeCanvas(SkCanvas*) override { SkDEBUGFAIL("Invalid Op"); } + +protected: + void didSetMatrix(const SkMatrix&) override; + + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; + +private: + void clipToZOrderedBounds(); + + struct CanvasData { + SkIPoint origin; + SkRegion requiredClip; + std::unique_ptr ownedCanvas; + }; + + SkTArray fCanvasData; + + typedef SkNWayCanvas INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/utils/SkCanvasStateUtils.cpp b/gfx/skia/skia/src/utils/SkCanvasStateUtils.cpp new file mode 100644 index 0000000000..73fb5caa96 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCanvasStateUtils.cpp @@ -0,0 +1,318 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/utils/SkCanvasStateUtils.h" + +#include "include/core/SkCanvas.h" +#include "src/core/SkClipOpPriv.h" +#include "src/core/SkDevice.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkWriter32.h" +#include "src/utils/SkCanvasStack.h" + +/* + * WARNING: The structs below are part of a stable ABI and as such we explicitly + * use unambigious primitives (e.g. int32_t instead of an enum). + * + * ANY CHANGES TO THE STRUCTS BELOW THAT IMPACT THE ABI SHOULD RESULT IN A NEW + * NEW SUBCLASS OF SkCanvasState. SUCH CHANGES SHOULD ONLY BE MADE IF ABSOLUTELY + * NECESSARY! + * + * In order to test changes, run the CanvasState tests. gyp/canvas_state_lib.gyp + * describes how to create a library to pass to the CanvasState tests. The tests + * should succeed when building the library with your changes and passing that to + * the tests running in the unchanged Skia. + */ +enum RasterConfigs { + kUnknown_RasterConfig = 0, + kRGB_565_RasterConfig = 1, + kARGB_8888_RasterConfig = 2 +}; +typedef int32_t RasterConfig; + +enum CanvasBackends { + kUnknown_CanvasBackend = 0, + kRaster_CanvasBackend = 1, + kGPU_CanvasBackend = 2, + kPDF_CanvasBackend = 3 +}; +typedef int32_t CanvasBackend; + +struct ClipRect { + int32_t left, top, right, bottom; +}; + +struct SkMCState { + float matrix[9]; + // NOTE: this only works for non-antialiased clips + int32_t clipRectCount; + ClipRect* clipRects; +}; + +// NOTE: If you add more members, create a new subclass of SkCanvasState with a +// new CanvasState::version. +struct SkCanvasLayerState { + CanvasBackend type; + int32_t x, y; + int32_t width; + int32_t height; + + SkMCState mcState; + + union { + struct { + RasterConfig config; // pixel format: a value from RasterConfigs. + uint64_t rowBytes; // Number of bytes from start of one line to next. + void* pixels; // The pixels, all (height * rowBytes) of them. + } raster; + struct { + int32_t textureID; + } gpu; + }; +}; + +class SkCanvasState { +public: + SkCanvasState(int32_t version, SkCanvas* canvas) { + SkASSERT(canvas); + this->version = version; + width = canvas->getBaseLayerSize().width(); + height = canvas->getBaseLayerSize().height(); + + } + + /** + * The version this struct was built with. This field must always appear + * first in the struct so that when the versions don't match (and the + * remaining contents and size are potentially different) we can still + * compare the version numbers. + */ + int32_t version; + int32_t width; + int32_t height; + int32_t alignmentPadding; +}; + +class SkCanvasState_v1 : public SkCanvasState { +public: + static const int32_t kVersion = 1; + + SkCanvasState_v1(SkCanvas* canvas) : INHERITED(kVersion, canvas) { + layerCount = 0; + layers = nullptr; + mcState.clipRectCount = 0; + mcState.clipRects = nullptr; + originalCanvas = canvas; + } + + ~SkCanvasState_v1() { + // loop through the layers and free the data allocated to the clipRects + for (int i = 0; i < layerCount; ++i) { + sk_free(layers[i].mcState.clipRects); + } + + sk_free(mcState.clipRects); + sk_free(layers); + } + + SkMCState mcState; + + int32_t layerCount; + SkCanvasLayerState* layers; +private: + SkCanvas* originalCanvas; + typedef SkCanvasState INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////////// + +static void setup_MC_state(SkMCState* state, const SkMatrix& matrix, const SkIRect& clip) { + // initialize the struct + state->clipRectCount = 0; + + // capture the matrix + for (int i = 0; i < 9; i++) { + state->matrix[i] = matrix.get(i); + } + + /* + * We only support a single clipRect, so we take the clip's bounds. Clients have long made + * this assumption anyway, so this restriction is fine. + */ + SkSWriter32 clipWriter; + + if (!clip.isEmpty()) { + state->clipRectCount = 1; + state->clipRects = (ClipRect*)sk_malloc_throw(sizeof(ClipRect)); + state->clipRects->left = clip.fLeft; + state->clipRects->top = clip.fTop; + state->clipRects->right = clip.fRight; + state->clipRects->bottom = clip.fBottom; + } +} + + + +SkCanvasState* SkCanvasStateUtils::CaptureCanvasState(SkCanvas* canvas) { + SkASSERT(canvas); + + // Check the clip can be decomposed into rectangles (i.e. no soft clips). + if (canvas->androidFramework_isClipAA()) { + return nullptr; + } + + std::unique_ptr canvasState(new SkCanvasState_v1(canvas)); + + setup_MC_state(&canvasState->mcState, canvas->getTotalMatrix(), canvas->getDeviceClipBounds()); + + /* + * decompose the layers + * + * storage is allocated on the stack for the first 3 layers. It is common in + * some view systems (e.g. Android) that a few non-clipped layers are present + * and we will not need to malloc any additional memory in those cases. + */ + SkSWriter32<3*sizeof(SkCanvasLayerState)> layerWriter; + int layerCount = 0; + for (SkCanvas::LayerIter layer(canvas); !layer.done(); layer.next()) { + + // we currently only work for bitmap backed devices + SkPixmap pmap; + if (!layer.device()->accessPixels(&pmap) || 0 == pmap.width() || 0 == pmap.height()) { + return nullptr; + } + + SkCanvasLayerState* layerState = + (SkCanvasLayerState*) layerWriter.reserve(sizeof(SkCanvasLayerState)); + layerState->type = kRaster_CanvasBackend; + layerState->x = layer.x(); + layerState->y = layer.y(); + layerState->width = pmap.width(); + layerState->height = pmap.height(); + + switch (pmap.colorType()) { + case kN32_SkColorType: + layerState->raster.config = kARGB_8888_RasterConfig; + break; + case kRGB_565_SkColorType: + layerState->raster.config = kRGB_565_RasterConfig; + break; + default: + return nullptr; + } + layerState->raster.rowBytes = pmap.rowBytes(); + layerState->raster.pixels = pmap.writable_addr(); + + setup_MC_state(&layerState->mcState, layer.matrix(), layer.clipBounds()); + layerCount++; + } + + // allocate memory for the layers and then and copy them to the struct + SkASSERT(layerWriter.bytesWritten() == layerCount * sizeof(SkCanvasLayerState)); + canvasState->layerCount = layerCount; + canvasState->layers = (SkCanvasLayerState*) sk_malloc_throw(layerWriter.bytesWritten()); + layerWriter.flatten(canvasState->layers); + + return canvasState.release(); +} + +//////////////////////////////////////////////////////////////////////////////// + +static void setup_canvas_from_MC_state(const SkMCState& state, SkCanvas* canvas) { + // reconstruct the matrix + SkMatrix matrix; + for (int i = 0; i < 9; i++) { + matrix.set(i, state.matrix[i]); + } + + // only realy support 1 rect, so if the caller (legacy?) sent us more, we just take the bounds + // of what they sent. + SkIRect bounds = SkIRect::MakeEmpty(); + if (state.clipRectCount > 0) { + bounds.setLTRB(state.clipRects[0].left, + state.clipRects[0].top, + state.clipRects[0].right, + state.clipRects[0].bottom); + for (int i = 1; i < state.clipRectCount; ++i) { + bounds.join({state.clipRects[i].left, + state.clipRects[i].top, + state.clipRects[i].right, + state.clipRects[i].bottom}); + } + } + + canvas->clipRect(SkRect::Make(bounds)); + canvas->concat(matrix); +} + +static std::unique_ptr +make_canvas_from_canvas_layer(const SkCanvasLayerState& layerState) { + SkASSERT(kRaster_CanvasBackend == layerState.type); + + SkBitmap bitmap; + SkColorType colorType = + layerState.raster.config == kARGB_8888_RasterConfig ? kN32_SkColorType : + layerState.raster.config == kRGB_565_RasterConfig ? kRGB_565_SkColorType : + kUnknown_SkColorType; + + if (colorType == kUnknown_SkColorType) { + return nullptr; + } + + bitmap.installPixels(SkImageInfo::Make(layerState.width, layerState.height, + colorType, kPremul_SkAlphaType), + layerState.raster.pixels, (size_t) layerState.raster.rowBytes); + + SkASSERT(!bitmap.empty()); + SkASSERT(!bitmap.isNull()); + + std::unique_ptr canvas(new SkCanvas(bitmap)); + + // setup the matrix and clip + setup_canvas_from_MC_state(layerState.mcState, canvas.get()); + + return canvas; +} + +std::unique_ptr SkCanvasStateUtils::MakeFromCanvasState(const SkCanvasState* state) { + SkASSERT(state); + // Currently there is only one possible version. + SkASSERT(SkCanvasState_v1::kVersion == state->version); + + const SkCanvasState_v1* state_v1 = static_cast(state); + + if (state_v1->layerCount < 1) { + return nullptr; + } + + std::unique_ptr canvas(new SkCanvasStack(state->width, state->height)); + + // setup the matrix and clip on the n-way canvas + setup_canvas_from_MC_state(state_v1->mcState, canvas.get()); + + // Iterate over the layers and add them to the n-way canvas + for (int i = state_v1->layerCount - 1; i >= 0; --i) { + std::unique_ptr canvasLayer = make_canvas_from_canvas_layer(state_v1->layers[i]); + if (!canvasLayer.get()) { + return nullptr; + } + canvas->pushCanvas(std::move(canvasLayer), SkIPoint::Make(state_v1->layers[i].x, + state_v1->layers[i].y)); + } + + return canvas; +} + +//////////////////////////////////////////////////////////////////////////////// + +void SkCanvasStateUtils::ReleaseCanvasState(SkCanvasState* state) { + SkASSERT(!state || SkCanvasState_v1::kVersion == state->version); + // Upcast to the correct version of SkCanvasState. This avoids having a virtual destructor on + // SkCanvasState. That would be strange since SkCanvasState has no other virtual functions, and + // instead uses the field "version" to determine how to behave. + delete static_cast(state); +} diff --git a/gfx/skia/skia/src/utils/SkCharToGlyphCache.cpp b/gfx/skia/skia/src/utils/SkCharToGlyphCache.cpp new file mode 100644 index 0000000000..a4f459cd44 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCharToGlyphCache.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkTFitsIn.h" +#include "src/utils/SkCharToGlyphCache.h" + +SkCharToGlyphCache::SkCharToGlyphCache() { + this->reset(); +} + +SkCharToGlyphCache::~SkCharToGlyphCache() {} + +void SkCharToGlyphCache::reset() { + fK32.reset(); + fV16.reset(); + + // Add sentinels so we can always rely on these to stop linear searches (in either direction) + // Neither is a legal unichar, so we don't care what glyphID we use. + // + *fK32.append() = 0x80000000; *fV16.append() = 0; + *fK32.append() = 0x7FFFFFFF; *fV16.append() = 0; + + fDenom = 0; +} + +// Determined experimentally. For N much larger, the slope technique is faster. +// For N much smaller, a simple search is faster. +// +constexpr int kSmallCountLimit = 16; + +// To use slope technique we need at least 2 real entries (+2 sentinels) hence the min of 4 +// +constexpr int kMinCountForSlope = 4; + +static int find_simple(const SkUnichar base[], int count, SkUnichar value) { + int index; + for (index = 0;; ++index) { + if (value <= base[index]) { + if (value < base[index]) { + index = ~index; // not found + } + break; + } + } + return index; +} + +static int find_with_slope(const SkUnichar base[], int count, SkUnichar value, double denom) { + SkASSERT(count >= kMinCountForSlope); + + int index; + if (value <= base[1]) { + index = 1; + if (value < base[index]) { + index = ~index; + } + } else if (value >= base[count - 2]) { + index = count - 2; + if (value > base[index]) { + index = ~(index + 1); + } + } else { + // make our guess based on the "slope" of the current values +// index = 1 + (int64_t)(count - 2) * (value - base[1]) / (base[count - 2] - base[1]); + index = 1 + (int)(denom * (count - 2) * (value - base[1])); + SkASSERT(index >= 1 && index <= count - 2); + + if (value >= base[index]) { + for (;; ++index) { + if (value <= base[index]) { + if (value < base[index]) { + index = ~index; // not found + } + break; + } + } + } else { + for (--index;; --index) { + SkASSERT(index >= 0); + if (value >= base[index]) { + if (value > base[index]) { + index = ~(index + 1); + } + break; + } + } + } + } + return index; +} + +int SkCharToGlyphCache::findGlyphIndex(SkUnichar unichar) const { + const int count = fK32.count(); + int index; + if (count <= kSmallCountLimit) { + index = find_simple(fK32.begin(), count, unichar); + } else { + index = find_with_slope(fK32.begin(), count, unichar, fDenom); + } + if (index >= 0) { + return fV16[index]; + } + return index; +} + +void SkCharToGlyphCache::insertCharAndGlyph(int index, SkUnichar unichar, SkGlyphID glyph) { + SkASSERT(fK32.size() == fV16.size()); + SkASSERT((unsigned)index < fK32.size()); + SkASSERT(unichar < fK32[index]); + + *fK32.insert(index) = unichar; + *fV16.insert(index) = glyph; + + // if we've changed the first [1] or last [count-2] entry, recompute our slope + const int count = fK32.count(); + if (count >= kMinCountForSlope && (index == 1 || index == count - 2)) { + SkASSERT(index >= 1 && index <= count - 2); + fDenom = 1.0 / ((double)fK32[count - 2] - fK32[1]); + } + +#ifdef SK_DEBUG + for (int i = 1; i < fK32.count(); ++i) { + SkASSERT(fK32[i-1] < fK32[i]); + } +#endif +} diff --git a/gfx/skia/skia/src/utils/SkCharToGlyphCache.h b/gfx/skia/skia/src/utils/SkCharToGlyphCache.h new file mode 100644 index 0000000000..6e0a47a930 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkCharToGlyphCache.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCharToGlyphCache_DEFINED +#define SkCharToGlyphCache_DEFINED + +#include "include/core/SkTypes.h" +#include "include/private/SkTDArray.h" + +class SkCharToGlyphCache { +public: + SkCharToGlyphCache(); + ~SkCharToGlyphCache(); + + // return number of unichars cached + int count() const { + return fK32.count(); + } + + void reset(); // forget all cache entries (to save memory) + + /** + * Given a unichar, return its glyphID (if the return value is positive), else return + * ~index of where to insert the computed glyphID. + * + * int result = cache.charToGlyph(unichar); + * if (result >= 0) { + * glyphID = result; + * } else { + * glyphID = compute_glyph_using_typeface(unichar); + * cache.insertCharAndGlyph(~result, unichar, glyphID); + * } + */ + int findGlyphIndex(SkUnichar c) const; + + /** + * Insert a new char/glyph pair into the cache at the specified index. + * See charToGlyph() for how to compute the bit-not of the index. + */ + void insertCharAndGlyph(int index, SkUnichar, SkGlyphID); + + // helper to pre-seed an entry in the cache + void addCharAndGlyph(SkUnichar unichar, SkGlyphID glyph) { + int index = this->findGlyphIndex(unichar); + if (index >= 0) { + SkASSERT(SkToU16(index) == glyph); + } else { + this->insertCharAndGlyph(~index, unichar, glyph); + } + } + +private: + SkTDArray fK32; + SkTDArray fV16; + double fDenom; +}; + +#endif diff --git a/gfx/skia/skia/src/utils/SkDashPath.cpp b/gfx/skia/skia/src/utils/SkDashPath.cpp new file mode 100644 index 0000000000..92e4bdbda8 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkDashPath.cpp @@ -0,0 +1,461 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkPathMeasure.h" +#include "include/core/SkStrokeRec.h" +#include "src/core/SkPointPriv.h" +#include "src/utils/SkDashPathPriv.h" + +#include + +static inline int is_even(int x) { + return !(x & 1); +} + +static SkScalar find_first_interval(const SkScalar intervals[], SkScalar phase, + int32_t* index, int count) { + for (int i = 0; i < count; ++i) { + SkScalar gap = intervals[i]; + if (phase > gap || (phase == gap && gap)) { + phase -= gap; + } else { + *index = i; + return gap - phase; + } + } + // If we get here, phase "appears" to be larger than our length. This + // shouldn't happen with perfect precision, but we can accumulate errors + // during the initial length computation (rounding can make our sum be too + // big or too small. In that event, we just have to eat the error here. + *index = 0; + return intervals[0]; +} + +void SkDashPath::CalcDashParameters(SkScalar phase, const SkScalar intervals[], int32_t count, + SkScalar* initialDashLength, int32_t* initialDashIndex, + SkScalar* intervalLength, SkScalar* adjustedPhase) { + SkScalar len = 0; + for (int i = 0; i < count; i++) { + len += intervals[i]; + } + *intervalLength = len; + // Adjust phase to be between 0 and len, "flipping" phase if negative. + // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80 + if (adjustedPhase) { + if (phase < 0) { + phase = -phase; + if (phase > len) { + phase = SkScalarMod(phase, len); + } + phase = len - phase; + + // Due to finite precision, it's possible that phase == len, + // even after the subtract (if len >>> phase), so fix that here. + // This fixes http://crbug.com/124652 . + SkASSERT(phase <= len); + if (phase == len) { + phase = 0; + } + } else if (phase >= len) { + phase = SkScalarMod(phase, len); + } + *adjustedPhase = phase; + } + SkASSERT(phase >= 0 && phase < len); + + *initialDashLength = find_first_interval(intervals, phase, + initialDashIndex, count); + + SkASSERT(*initialDashLength >= 0); + SkASSERT(*initialDashIndex >= 0 && *initialDashIndex < count); +} + +static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) { + SkScalar radius = SkScalarHalf(rec.getWidth()); + if (0 == radius) { + radius = SK_Scalar1; // hairlines + } + if (SkPaint::kMiter_Join == rec.getJoin()) { + radius *= rec.getMiter(); + } + rect->outset(radius, radius); +} + +// If line is zero-length, bump out the end by a tiny amount +// to draw endcaps. The bump factor is sized so that +// SkPoint::Distance() computes a non-zero length. +// Offsets SK_ScalarNearlyZero or smaller create empty paths when Iter measures length. +// Large values are scaled by SK_ScalarNearlyZero so significant bits change. +static void adjust_zero_length_line(SkPoint pts[2]) { + SkASSERT(pts[0] == pts[1]); + pts[1].fX += SkTMax(1.001f, pts[1].fX) * SK_ScalarNearlyZero; +} + +static bool clip_line(SkPoint pts[2], const SkRect& bounds, SkScalar intervalLength, + SkScalar priorPhase) { + SkVector dxy = pts[1] - pts[0]; + + // only horizontal or vertical lines + if (dxy.fX && dxy.fY) { + return false; + } + int xyOffset = SkToBool(dxy.fY); // 0 to adjust horizontal, 1 to adjust vertical + + SkScalar minXY = (&pts[0].fX)[xyOffset]; + SkScalar maxXY = (&pts[1].fX)[xyOffset]; + bool swapped = maxXY < minXY; + if (swapped) { + using std::swap; + swap(minXY, maxXY); + } + + SkASSERT(minXY <= maxXY); + SkScalar leftTop = (&bounds.fLeft)[xyOffset]; + SkScalar rightBottom = (&bounds.fRight)[xyOffset]; + if (maxXY < leftTop || minXY > rightBottom) { + return false; + } + + // Now we actually perform the chop, removing the excess to the left/top and + // right/bottom of the bounds (keeping our new line "in phase" with the dash, + // hence the (mod intervalLength). + + if (minXY < leftTop) { + minXY = leftTop - SkScalarMod(leftTop - minXY, intervalLength); + if (!swapped) { + minXY -= priorPhase; // for rectangles, adjust by prior phase + } + } + if (maxXY > rightBottom) { + maxXY = rightBottom + SkScalarMod(maxXY - rightBottom, intervalLength); + if (swapped) { + maxXY += priorPhase; // for rectangles, adjust by prior phase + } + } + + SkASSERT(maxXY >= minXY); + if (swapped) { + using std::swap; + swap(minXY, maxXY); + } + (&pts[0].fX)[xyOffset] = minXY; + (&pts[1].fX)[xyOffset] = maxXY; + + if (minXY == maxXY) { + adjust_zero_length_line(pts); + } + return true; +} + +// Handles only lines and rects. +// If cull_path() returns true, dstPath is the new smaller path, +// otherwise dstPath may have been changed but you should ignore it. +static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec, + const SkRect* cullRect, SkScalar intervalLength, SkPath* dstPath) { + if (!cullRect) { + SkPoint pts[2]; + if (srcPath.isLine(pts) && pts[0] == pts[1]) { + adjust_zero_length_line(pts); + dstPath->moveTo(pts[0]); + dstPath->lineTo(pts[1]); + return true; + } + return false; + } + + SkRect bounds; + bounds = *cullRect; + outset_for_stroke(&bounds, rec); + + { + SkPoint pts[2]; + if (srcPath.isLine(pts)) { + if (clip_line(pts, bounds, intervalLength, 0)) { + dstPath->moveTo(pts[0]); + dstPath->lineTo(pts[1]); + return true; + } + return false; + } + } + + if (srcPath.isRect(nullptr)) { + // We'll break the rect into four lines, culling each separately. + SkPath::Iter iter(srcPath, false); + + SkPoint pts[4]; // Rects are all moveTo and lineTo, so we'll only use pts[0] and pts[1]. + SkAssertResult(SkPath::kMove_Verb == iter.next(pts)); + + SkScalar accum = 0; // Sum of unculled edge lengths to keep the phase correct. + while (iter.next(pts) == SkPath::kLine_Verb) { + // Notice this vector v and accum work with the original unclipped length. + SkVector v = pts[1] - pts[0]; + + if (clip_line(pts, bounds, intervalLength, SkScalarMod(accum, intervalLength))) { + // pts[0] may have just been changed by clip_line(). + // If that's not where we ended the previous lineTo(), we need to moveTo() there. + SkPoint last; + if (!dstPath->getLastPt(&last) || last != pts[0]) { + dstPath->moveTo(pts[0]); + } + dstPath->lineTo(pts[1]); + } + + // We either just traveled v.fX horizontally or v.fY vertically. + SkASSERT(v.fX == 0 || v.fY == 0); + accum += SkScalarAbs(v.fX + v.fY); + } + return !dstPath->isEmpty(); + } + + return false; +} + +class SpecialLineRec { +public: + bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec, + int intervalCount, SkScalar intervalLength) { + if (rec->isHairlineStyle() || !src.isLine(fPts)) { + return false; + } + + // can relax this in the future, if we handle square and round caps + if (SkPaint::kButt_Cap != rec->getCap()) { + return false; + } + + SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]); + + fTangent = fPts[1] - fPts[0]; + if (fTangent.isZero()) { + return false; + } + + fPathLength = pathLength; + fTangent.scale(SkScalarInvert(pathLength)); + SkPointPriv::RotateCCW(fTangent, &fNormal); + fNormal.scale(SkScalarHalf(rec->getWidth())); + + // now estimate how many quads will be added to the path + // resulting segments = pathLen * intervalCount / intervalLen + // resulting points = 4 * segments + + SkScalar ptCount = pathLength * intervalCount / (float)intervalLength; + ptCount = SkTMin(ptCount, SkDashPath::kMaxDashCount); + int n = SkScalarCeilToInt(ptCount) << 2; + dst->incReserve(n); + + // we will take care of the stroking + rec->setFillStyle(); + return true; + } + + void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const { + SkASSERT(d0 <= fPathLength); + // clamp the segment to our length + if (d1 > fPathLength) { + d1 = fPathLength; + } + + SkScalar x0 = fPts[0].fX + fTangent.fX * d0; + SkScalar x1 = fPts[0].fX + fTangent.fX * d1; + SkScalar y0 = fPts[0].fY + fTangent.fY * d0; + SkScalar y1 = fPts[0].fY + fTangent.fY * d1; + + SkPoint pts[4]; + pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo + pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo + pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo + pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo + + path->addPoly(pts, SK_ARRAY_COUNT(pts), false); + } + +private: + SkPoint fPts[2]; + SkVector fTangent; + SkVector fNormal; + SkScalar fPathLength; +}; + + +bool SkDashPath::InternalFilter(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect, const SkScalar aIntervals[], + int32_t count, SkScalar initialDashLength, int32_t initialDashIndex, + SkScalar intervalLength, + StrokeRecApplication strokeRecApplication) { + // we must always have an even number of intervals + SkASSERT(is_even(count)); + + // we do nothing if the src wants to be filled + SkStrokeRec::Style style = rec->getStyle(); + if (SkStrokeRec::kFill_Style == style || SkStrokeRec::kStrokeAndFill_Style == style) { + return false; + } + + const SkScalar* intervals = aIntervals; + SkScalar dashCount = 0; + int segCount = 0; + + SkPath cullPathStorage; + const SkPath* srcPtr = &src; + if (cull_path(src, *rec, cullRect, intervalLength, &cullPathStorage)) { + // if rect is closed, starts in a dash, and ends in a dash, add the initial join + // potentially a better fix is described here: bug.skia.org/7445 + if (src.isRect(nullptr) && src.isLastContourClosed() && is_even(initialDashIndex)) { + SkScalar pathLength = SkPathMeasure(src, false, rec->getResScale()).getLength(); + SkScalar endPhase = SkScalarMod(pathLength + initialDashLength, intervalLength); + int index = 0; + while (endPhase > intervals[index]) { + endPhase -= intervals[index++]; + SkASSERT(index <= count); + if (index == count) { + // We have run out of intervals. endPhase "should" never get to this point, + // but it could if the subtracts underflowed. Hence we will pin it as if it + // perfectly ran through the intervals. + // See crbug.com/875494 (and skbug.com/8274) + endPhase = 0; + break; + } + } + // if dash ends inside "on", or ends at beginning of "off" + if (is_even(index) == (endPhase > 0)) { + SkPoint midPoint = src.getPoint(0); + // get vector at end of rect + int last = src.countPoints() - 1; + while (midPoint == src.getPoint(last)) { + --last; + SkASSERT(last >= 0); + } + // get vector at start of rect + int next = 1; + while (midPoint == src.getPoint(next)) { + ++next; + SkASSERT(next < last); + } + SkVector v = midPoint - src.getPoint(last); + const SkScalar kTinyOffset = SK_ScalarNearlyZero; + // scale vector to make start of tiny right angle + v *= kTinyOffset; + cullPathStorage.moveTo(midPoint - v); + cullPathStorage.lineTo(midPoint); + v = midPoint - src.getPoint(next); + // scale vector to make end of tiny right angle + v *= kTinyOffset; + cullPathStorage.lineTo(midPoint - v); + } + } + srcPtr = &cullPathStorage; + } + + SpecialLineRec lineRec; + bool specialLine = (StrokeRecApplication::kAllow == strokeRecApplication) && + lineRec.init(*srcPtr, dst, rec, count >> 1, intervalLength); + + SkPathMeasure meas(*srcPtr, false, rec->getResScale()); + + do { + bool skipFirstSegment = meas.isClosed(); + bool addedSegment = false; + SkScalar length = meas.getLength(); + int index = initialDashIndex; + + // Since the path length / dash length ratio may be arbitrarily large, we can exert + // significant memory pressure while attempting to build the filtered path. To avoid this, + // we simply give up dashing beyond a certain threshold. + // + // The original bug report (http://crbug.com/165432) is based on a path yielding more than + // 90 million dash segments and crashing the memory allocator. A limit of 1 million + // segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the + // maximum dash memory overhead at roughly 17MB per path. + dashCount += length * (count >> 1) / intervalLength; + if (dashCount > kMaxDashCount) { + dst->reset(); + return false; + } + + // Using double precision to avoid looping indefinitely due to single precision rounding + // (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest. + double distance = 0; + double dlen = initialDashLength; + + while (distance < length) { + SkASSERT(dlen >= 0); + addedSegment = false; + if (is_even(index) && !skipFirstSegment) { + addedSegment = true; + ++segCount; + + if (specialLine) { + lineRec.addSegment(SkDoubleToScalar(distance), + SkDoubleToScalar(distance + dlen), + dst); + } else { + meas.getSegment(SkDoubleToScalar(distance), + SkDoubleToScalar(distance + dlen), + dst, true); + } + } + distance += dlen; + + // clear this so we only respect it the first time around + skipFirstSegment = false; + + // wrap around our intervals array if necessary + index += 1; + SkASSERT(index <= count); + if (index == count) { + index = 0; + } + + // fetch our next dlen + dlen = intervals[index]; + } + + // extend if we ended on a segment and we need to join up with the (skipped) initial segment + if (meas.isClosed() && is_even(initialDashIndex) && + initialDashLength >= 0) { + meas.getSegment(0, initialDashLength, dst, !addedSegment); + ++segCount; + } + } while (meas.nextContour()); + + if (segCount > 1) { + dst->setConvexity(SkPath::kConcave_Convexity); + } + + return true; +} + +bool SkDashPath::FilterDashPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect, const SkPathEffect::DashInfo& info) { + if (!ValidDashPath(info.fPhase, info.fIntervals, info.fCount)) { + return false; + } + SkScalar initialDashLength = 0; + int32_t initialDashIndex = 0; + SkScalar intervalLength = 0; + CalcDashParameters(info.fPhase, info.fIntervals, info.fCount, + &initialDashLength, &initialDashIndex, &intervalLength); + return InternalFilter(dst, src, rec, cullRect, info.fIntervals, info.fCount, initialDashLength, + initialDashIndex, intervalLength); +} + +bool SkDashPath::ValidDashPath(SkScalar phase, const SkScalar intervals[], int32_t count) { + if (count < 2 || !SkIsAlign2(count)) { + return false; + } + SkScalar length = 0; + for (int i = 0; i < count; i++) { + if (intervals[i] < 0) { + return false; + } + length += intervals[i]; + } + // watch out for values that might make us go out of bounds + return length > 0 && SkScalarIsFinite(phase) && SkScalarIsFinite(length); +} diff --git a/gfx/skia/skia/src/utils/SkDashPathPriv.h b/gfx/skia/skia/src/utils/SkDashPathPriv.h new file mode 100644 index 0000000000..5c6a5db23e --- /dev/null +++ b/gfx/skia/skia/src/utils/SkDashPathPriv.h @@ -0,0 +1,52 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDashPathPriv_DEFINED +#define SkDashPathPriv_DEFINED + +#include "include/core/SkPathEffect.h" + +namespace SkDashPath { + /** + * Calculates the initialDashLength, initialDashIndex, and intervalLength based on the + * inputed phase and intervals. If adjustedPhase is passed in, then the phase will be + * adjusted to be between 0 and intervalLength. The result will be stored in adjustedPhase. + * If adjustedPhase is nullptr then it is assumed phase is already between 0 and intervalLength + * + * Caller should have already used ValidDashPath to exclude invalid data. + */ + void CalcDashParameters(SkScalar phase, const SkScalar intervals[], int32_t count, + SkScalar* initialDashLength, int32_t* initialDashIndex, + SkScalar* intervalLength, SkScalar* adjustedPhase = nullptr); + + bool FilterDashPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*, + const SkPathEffect::DashInfo& info); + + const SkScalar kMaxDashCount = 1000000; + + /** See comments for InternalFilter */ + enum class StrokeRecApplication { + kDisallow, + kAllow, + }; + + /** + * Caller should have already used ValidDashPath to exclude invalid data. Typically, this leaves + * the strokeRec unmodified. However, for some simple shapes (e.g. a line) it may directly + * evaluate the dash and stroke to produce a stroked output path with a fill strokeRec. Passing + * true for disallowStrokeRecApplication turns this behavior off. + */ + bool InternalFilter(SkPath* dst, const SkPath& src, SkStrokeRec* rec, + const SkRect* cullRect, const SkScalar aIntervals[], + int32_t count, SkScalar initialDashLength, int32_t initialDashIndex, + SkScalar intervalLength, + StrokeRecApplication = StrokeRecApplication::kAllow); + + bool ValidDashPath(SkScalar phase, const SkScalar intervals[], int32_t count); +} + +#endif diff --git a/gfx/skia/skia/src/utils/SkEventTracer.cpp b/gfx/skia/skia/src/utils/SkEventTracer.cpp new file mode 100644 index 0000000000..2574306f6c --- /dev/null +++ b/gfx/skia/skia/src/utils/SkEventTracer.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkOnce.h" +#include "include/utils/SkEventTracer.h" +#include + +#include + +class SkDefaultEventTracer : public SkEventTracer { + SkEventTracer::Handle + addTraceEvent(char phase, + const uint8_t* categoryEnabledFlag, + const char* name, + uint64_t id, + int numArgs, + const char** argNames, + const uint8_t* argTypes, + const uint64_t* argValues, + uint8_t flags) override { return 0; } + + void + updateTraceEventDuration(const uint8_t* categoryEnabledFlag, + const char* name, + SkEventTracer::Handle handle) override {} + + const uint8_t* getCategoryGroupEnabled(const char* name) override { + static uint8_t no = 0; + return &no; + } + const char* getCategoryGroupName( + const uint8_t* categoryEnabledFlag) override { + static const char* dummy = "dummy"; + return dummy; + } +}; + +// We prefer gUserTracer if it's been set, otherwise we fall back on a default tracer; +static std::atomic gUserTracer{nullptr}; + +bool SkEventTracer::SetInstance(SkEventTracer* tracer) { + SkEventTracer* expected = nullptr; + if (!gUserTracer.compare_exchange_strong(expected, tracer)) { + delete tracer; + return false; + } + atexit([]() { delete gUserTracer.load(); }); + return true; +} + +SkEventTracer* SkEventTracer::GetInstance() { + if (auto tracer = gUserTracer.load(std::memory_order_acquire)) { + return tracer; + } + static SkOnce once; + static SkDefaultEventTracer* defaultTracer; + once([] { defaultTracer = new SkDefaultEventTracer; }); + return defaultTracer; +} diff --git a/gfx/skia/skia/src/utils/SkFloatToDecimal.cpp b/gfx/skia/skia/src/utils/SkFloatToDecimal.cpp new file mode 100644 index 0000000000..4bfcdadda2 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkFloatToDecimal.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/utils/SkFloatToDecimal.h" + +#include +#include +#include + +#include "include/core/SkTypes.h" + +// returns `value * pow(base, e)`, assuming `e` is positive. +static double pow_by_squaring(double value, double base, int e) { + // https://en.wikipedia.org/wiki/Exponentiation_by_squaring + SkASSERT(e > 0); + while (true) { + if (e & 1) { + value *= base; + } + e >>= 1; + if (0 == e) { + return value; + } + base *= base; + } +} + +// Return pow(10.0, e), optimized for common cases. +static double pow10(int e) { + switch (e) { + case 0: return 1.0; // common cases + case 1: return 10.0; + case 2: return 100.0; + case 3: return 1e+03; + case 4: return 1e+04; + case 5: return 1e+05; + case 6: return 1e+06; + case 7: return 1e+07; + case 8: return 1e+08; + case 9: return 1e+09; + case 10: return 1e+10; + case 11: return 1e+11; + case 12: return 1e+12; + case 13: return 1e+13; + case 14: return 1e+14; + case 15: return 1e+15; + default: + if (e > 15) { + return pow_by_squaring(1e+15, 10.0, e - 15); + } else { + SkASSERT(e < 0); + return pow_by_squaring(1.0, 0.1, -e); + } + } +} + +/** Write a string into output, including a terminating '\0' (for + unit testing). Return strlen(output) (for SkWStream::write) The + resulting string will be in the form /[-]?([0-9]*.)?[0-9]+/ and + sscanf(output, "%f", &x) will return the original value iff the + value is finite. This function accepts all possible input values. + + Motivation: "PDF does not support [numbers] in exponential format + (such as 6.02e23)." Otherwise, this function would rely on a + sprintf-type function from the standard library. */ +unsigned SkFloatToDecimal(float value, char output[kMaximumSkFloatToDecimalLength]) { + /* The longest result is -FLT_MIN. + We serialize it as "-.0000000000000000000000000000000000000117549435" + which has 48 characters plus a terminating '\0'. */ + + static_assert(kMaximumSkFloatToDecimalLength == 49, ""); + // 3 = '-', '.', and '\0' characters. + // 9 = number of significant digits + // abs(FLT_MIN_10_EXP) = number of zeros in FLT_MIN + static_assert(kMaximumSkFloatToDecimalLength == 3 + 9 - FLT_MIN_10_EXP, ""); + + /* section C.1 of the PDF1.4 spec (http://goo.gl/0SCswJ) says that + most PDF rasterizers will use fixed-point scalars that lack the + dynamic range of floats. Even if this is the case, I want to + serialize these (uncommon) very small and very large scalar + values with enough precision to allow a floating-point + rasterizer to read them in with perfect accuracy. + Experimentally, rasterizers such as pdfium do seem to benefit + from this. Rasterizers that rely on fixed-point scalars should + gracefully ignore these values that they can not parse. */ + char* output_ptr = &output[0]; + const char* const end = &output[kMaximumSkFloatToDecimalLength - 1]; + // subtract one to leave space for '\0'. + + /* This function is written to accept any possible input value, + including non-finite values such as INF and NAN. In that case, + we ignore value-correctness and output a syntacticly-valid + number. */ + if (value == INFINITY) { + value = FLT_MAX; // nearest finite float. + } + if (value == -INFINITY) { + value = -FLT_MAX; // nearest finite float. + } + if (!std::isfinite(value) || value == 0.0f) { + // NAN is unsupported in PDF. Always output a valid number. + // Also catch zero here, as a special case. + *output_ptr++ = '0'; + *output_ptr = '\0'; + return static_cast(output_ptr - output); + } + if (value < 0.0) { + *output_ptr++ = '-'; + value = -value; + } + SkASSERT(value >= 0.0f); + + int binaryExponent; + (void)std::frexp(value, &binaryExponent); + static const double kLog2 = 0.3010299956639812; // log10(2.0); + int decimalExponent = static_cast(std::floor(kLog2 * binaryExponent)); + int decimalShift = decimalExponent - 8; + double power = pow10(-decimalShift); + SkASSERT(value * power <= (double)INT_MAX); + int d = static_cast(value * power + 0.5); + // SkASSERT(value == (float)(d * pow(10.0, decimalShift))); + SkASSERT(d <= 999999999); + if (d > 167772159) { // floor(pow(10,1+log10(1<<24))) + // need one fewer decimal digits for 24-bit precision. + decimalShift = decimalExponent - 7; + // SkASSERT(power * 0.1 = pow10(-decimalShift)); + // recalculate to get rounding right. + d = static_cast(value * (power * 0.1) + 0.5); + SkASSERT(d <= 99999999); + } + while (d % 10 == 0) { + d /= 10; + ++decimalShift; + } + SkASSERT(d > 0); + // SkASSERT(value == (float)(d * pow(10.0, decimalShift))); + unsigned char buffer[9]; // decimal value buffer. + int bufferIndex = 0; + do { + buffer[bufferIndex++] = d % 10; + d /= 10; + } while (d != 0); + SkASSERT(bufferIndex <= (int)sizeof(buffer) && bufferIndex > 0); + if (decimalShift >= 0) { + do { + --bufferIndex; + *output_ptr++ = '0' + buffer[bufferIndex]; + } while (bufferIndex); + for (int i = 0; i < decimalShift; ++i) { + *output_ptr++ = '0'; + } + } else { + int placesBeforeDecimal = bufferIndex + decimalShift; + if (placesBeforeDecimal > 0) { + while (placesBeforeDecimal-- > 0) { + --bufferIndex; + *output_ptr++ = '0' + buffer[bufferIndex]; + } + *output_ptr++ = '.'; + } else { + *output_ptr++ = '.'; + int placesAfterDecimal = -placesBeforeDecimal; + while (placesAfterDecimal-- > 0) { + *output_ptr++ = '0'; + } + } + while (bufferIndex > 0) { + --bufferIndex; + *output_ptr++ = '0' + buffer[bufferIndex]; + if (output_ptr == end) { + break; // denormalized: don't need extra precision. + // Note: denormalized numbers will not have the same number of + // significantDigits, but do not need them to round-trip. + } + } + } + SkASSERT(output_ptr <= end); + *output_ptr = '\0'; + return static_cast(output_ptr - output); +} diff --git a/gfx/skia/skia/src/utils/SkFloatToDecimal.h b/gfx/skia/skia/src/utils/SkFloatToDecimal.h new file mode 100644 index 0000000000..ac1042dbfb --- /dev/null +++ b/gfx/skia/skia/src/utils/SkFloatToDecimal.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFloatToDecimal_DEFINED +#define SkFloatToDecimal_DEFINED + +constexpr unsigned kMaximumSkFloatToDecimalLength = 49; + +/** \fn SkFloatToDecimal + Convert a float into a decimal string. + + The resulting string will be in the form `[-]?([0-9]*\.)?[0-9]+` (It does + not use scientific notation.) and `sscanf(output, "%f", &x)` will return + the original value if the value is finite. This function accepts all + possible input values. + + INFINITY and -INFINITY are rounded to FLT_MAX and -FLT_MAX. + + NAN values are converted to 0. + + This function will always add a terminating '\0' to the output. + + @param value Any floating-point number + @param output The buffer to write the string into. Must be non-null. + + @return strlen(output) +*/ +unsigned SkFloatToDecimal(float value, char output[kMaximumSkFloatToDecimalLength]); + +#endif // SkFloatToDecimal_DEFINED diff --git a/gfx/skia/skia/src/utils/SkFloatUtils.h b/gfx/skia/skia/src/utils/SkFloatUtils.h new file mode 100644 index 0000000000..f89a77254e --- /dev/null +++ b/gfx/skia/skia/src/utils/SkFloatUtils.h @@ -0,0 +1,173 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFloatUtils_DEFINED +#define SkFloatUtils_DEFINED + +#include "include/core/SkTypes.h" +#include +#include + +template +class SkTypeWithSize { +public: + // Prevents using SkTypeWithSize with non-specialized N. + typedef void UInt; +}; + +template <> +class SkTypeWithSize<32> { +public: + typedef uint32_t UInt; +}; + +template <> +class SkTypeWithSize<64> { +public: + typedef uint64_t UInt; +}; + +template +struct SkNumericLimits { + static const int digits = 0; +}; + +template <> +struct SkNumericLimits { + static const int digits = DBL_MANT_DIG; +}; + +template <> +struct SkNumericLimits { + static const int digits = FLT_MANT_DIG; +}; + +//See +//http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison/3423299#3423299 +//http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h +//http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm + +template +class SkFloatingPoint { +public: + /** Bits is a unsigned integer the same size as the floating point number. */ + typedef typename SkTypeWithSize::UInt Bits; + + /** # of bits in a number. */ + static const size_t kBitCount = CHAR_BIT * sizeof(RawType); + + /** # of fraction bits in a number. */ + static const size_t kFractionBitCount = SkNumericLimits::digits - 1; + + /** # of exponent bits in a number. */ + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + /** The mask for the sign bit. */ + static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); + + /** The mask for the fraction bits. */ + static const Bits kFractionBitMask = + ~static_cast(0) >> (kExponentBitCount + 1); + + /** The mask for the exponent bits. */ + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + /** How many ULP's (Units in the Last Place) to tolerate when comparing. */ + static const size_t kMaxUlps = ULPs; + + /** + * Constructs a FloatingPoint from a raw floating-point number. + * + * On an Intel CPU, passing a non-normalized NAN (Not a Number) + * around may change its bits, although the new value is guaranteed + * to be also a NAN. Therefore, don't expect this constructor to + * preserve the bits in x when x is a NAN. + */ + explicit SkFloatingPoint(const RawType& x) { fU.value = x; } + + /** Returns the exponent bits of this number. */ + Bits exponent_bits() const { return kExponentBitMask & fU.bits; } + + /** Returns the fraction bits of this number. */ + Bits fraction_bits() const { return kFractionBitMask & fU.bits; } + + /** Returns true iff this is NAN (not a number). */ + bool is_nan() const { + // It's a NAN if both of the folloowing are true: + // * the exponent bits are all ones + // * the fraction bits are not all zero. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + /** + * Returns true iff this number is at most kMaxUlps ULP's away from ths. + * In particular, this function: + * - returns false if either number is (or both are) NAN. + * - treats really large numbers as almost equal to infinity. + * - thinks +0.0 and -0.0 are 0 DLP's apart. + */ + bool AlmostEquals(const SkFloatingPoint& rhs) const { + // Any comparison operation involving a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + const Bits dist = DistanceBetweenSignAndMagnitudeNumbers(fU.bits, + rhs.fU.bits); + //SkDEBUGF("(%f, %f, %d) ", u_.value_, rhs.u_.value_, dist); + return dist <= kMaxUlps; + } + +private: + /** The data type used to store the actual floating-point number. */ + union FloatingPointUnion { + /** The raw floating-point number. */ + RawType value; + /** The bits that represent the number. */ + Bits bits; + }; + + /** + * Converts an integer from the sign-and-magnitude representation to + * the biased representation. More precisely, let N be 2 to the + * power of (kBitCount - 1), an integer x is represented by the + * unsigned number x + N. + * + * For instance, + * + * -N + 1 (the most negative number representable using + * sign-and-magnitude) is represented by 1; + * 0 is represented by N; and + * N - 1 (the biggest number representable using + * sign-and-magnitude) is represented by 2N - 1. + * + * Read http://en.wikipedia.org/wiki/Signed_number_representations + * for more details on signed number representations. + */ + static Bits SignAndMagnitudeToBiased(const Bits &sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + /** + * Given two numbers in the sign-and-magnitude representation, + * returns the distance between them as an unsigned number. + */ + static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, + const Bits &sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + FloatingPointUnion fU; +}; + +#endif diff --git a/gfx/skia/skia/src/utils/SkFrontBufferedStream.cpp b/gfx/skia/skia/src/utils/SkFrontBufferedStream.cpp new file mode 100644 index 0000000000..6c2d310677 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkFrontBufferedStream.cpp @@ -0,0 +1,214 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkTemplates.h" +#include "include/utils/SkFrontBufferedStream.h" + +class FrontBufferedStream : public SkStreamRewindable { +public: + // Called by Make. + FrontBufferedStream(std::unique_ptr, size_t bufferSize); + + size_t read(void* buffer, size_t size) override; + + size_t peek(void* buffer, size_t size) const override; + + bool isAtEnd() const override; + + bool rewind() override; + + bool hasLength() const override { return fHasLength; } + + size_t getLength() const override { return fLength; } + +private: + SkStreamRewindable* onDuplicate() const override { return nullptr; } + + std::unique_ptr fStream; + const bool fHasLength; + const size_t fLength; + // Current offset into the stream. Always >= 0. + size_t fOffset; + // Amount that has been buffered by calls to read. Will always be less than + // fBufferSize. + size_t fBufferedSoFar; + // Total size of the buffer. + const size_t fBufferSize; + // FIXME: SkAutoTMalloc throws on failure. Instead, Create should return a + // nullptr stream. + SkAutoTMalloc fBuffer; + + // Read up to size bytes from already buffered data, and copy to + // dst, if non-nullptr. Updates fOffset. Assumes that fOffset is less + // than fBufferedSoFar. + size_t readFromBuffer(char* dst, size_t size); + + // Buffer up to size bytes from the stream, and copy to dst if non- + // nullptr. Updates fOffset and fBufferedSoFar. Assumes that fOffset is + // less than fBufferedSoFar, and size is greater than 0. + size_t bufferAndWriteTo(char* dst, size_t size); + + // Read up to size bytes directly from the stream and into dst if non- + // nullptr. Updates fOffset. Assumes fOffset is at or beyond the buffered + // data, and size is greater than 0. + size_t readDirectlyFromStream(char* dst, size_t size); + + typedef SkStream INHERITED; +}; + +std::unique_ptr SkFrontBufferedStream::Make(std::unique_ptr stream, + size_t bufferSize) { + if (!stream) { + return nullptr; + } + return std::unique_ptr(new FrontBufferedStream(std::move(stream), + bufferSize)); +} + +FrontBufferedStream::FrontBufferedStream(std::unique_ptr stream, size_t bufferSize) + : fStream(std::move(stream)) + , fHasLength(fStream->hasPosition() && fStream->hasLength()) + , fLength(fStream->getLength() - fStream->getPosition()) + , fOffset(0) + , fBufferedSoFar(0) + , fBufferSize(bufferSize) + , fBuffer(bufferSize) {} + +bool FrontBufferedStream::isAtEnd() const { + if (fOffset < fBufferedSoFar) { + // Even if the underlying stream is at the end, this stream has been + // rewound after buffering, so it is not at the end. + return false; + } + + return fStream->isAtEnd(); +} + +bool FrontBufferedStream::rewind() { + // Only allow a rewind if we have not exceeded the buffer. + if (fOffset <= fBufferSize) { + fOffset = 0; + return true; + } + return false; +} + +size_t FrontBufferedStream::readFromBuffer(char* dst, size_t size) { + SkASSERT(fOffset < fBufferedSoFar); + // Some data has already been copied to fBuffer. Read up to the + // lesser of the size requested and the remainder of the buffered + // data. + const size_t bytesToCopy = SkTMin(size, fBufferedSoFar - fOffset); + if (dst != nullptr) { + memcpy(dst, fBuffer + fOffset, bytesToCopy); + } + + // Update fOffset to the new position. It is guaranteed to be + // within the buffered data. + fOffset += bytesToCopy; + SkASSERT(fOffset <= fBufferedSoFar); + + return bytesToCopy; +} + +size_t FrontBufferedStream::bufferAndWriteTo(char* dst, size_t size) { + SkASSERT(size > 0); + SkASSERT(fOffset >= fBufferedSoFar); + SkASSERT(fBuffer); + // Data needs to be buffered. Buffer up to the lesser of the size requested + // and the remainder of the max buffer size. + const size_t bytesToBuffer = SkTMin(size, fBufferSize - fBufferedSoFar); + char* buffer = fBuffer + fOffset; + const size_t buffered = fStream->read(buffer, bytesToBuffer); + + fBufferedSoFar += buffered; + fOffset = fBufferedSoFar; + SkASSERT(fBufferedSoFar <= fBufferSize); + + // Copy the buffer to the destination buffer and update the amount read. + if (dst != nullptr) { + memcpy(dst, buffer, buffered); + } + + return buffered; +} + +size_t FrontBufferedStream::readDirectlyFromStream(char* dst, size_t size) { + SkASSERT(size > 0); + // If we get here, we have buffered all that can be buffered. + SkASSERT(fBufferSize == fBufferedSoFar && fOffset >= fBufferSize); + + const size_t bytesReadDirectly = fStream->read(dst, size); + fOffset += bytesReadDirectly; + + // If we have read past the end of the buffer, rewinding is no longer + // supported, so we can go ahead and free the memory. + if (bytesReadDirectly > 0) { + sk_free(fBuffer.release()); + } + + return bytesReadDirectly; +} + +size_t FrontBufferedStream::peek(void* dst, size_t size) const { + // Keep track of the offset so we can return to it. + const size_t start = fOffset; + + if (start >= fBufferSize) { + // This stream is not able to buffer. + return 0; + } + + size = SkTMin(size, fBufferSize - start); + FrontBufferedStream* nonConstThis = const_cast(this); + const size_t bytesRead = nonConstThis->read(dst, size); + nonConstThis->fOffset = start; + return bytesRead; +} + +size_t FrontBufferedStream::read(void* voidDst, size_t size) { + // Cast voidDst to a char* for easy addition. + char* dst = reinterpret_cast(voidDst); + SkDEBUGCODE(const size_t totalSize = size;) + const size_t start = fOffset; + + // First, read any data that was previously buffered. + if (fOffset < fBufferedSoFar) { + const size_t bytesCopied = this->readFromBuffer(dst, size); + + // Update the remaining number of bytes needed to read + // and the destination buffer. + size -= bytesCopied; + SkASSERT(size + (fOffset - start) == totalSize); + if (dst != nullptr) { + dst += bytesCopied; + } + } + + // Buffer any more data that should be buffered, and copy it to the + // destination. + if (size > 0 && fBufferedSoFar < fBufferSize && !fStream->isAtEnd()) { + const size_t buffered = this->bufferAndWriteTo(dst, size); + + // Update the remaining number of bytes needed to read + // and the destination buffer. + size -= buffered; + SkASSERT(size + (fOffset - start) == totalSize); + if (dst != nullptr) { + dst += buffered; + } + } + + if (size > 0 && !fStream->isAtEnd()) { + SkDEBUGCODE(const size_t bytesReadDirectly =) this->readDirectlyFromStream(dst, size); + SkDEBUGCODE(size -= bytesReadDirectly;) + SkASSERT(size + (fOffset - start) == totalSize); + } + + return fOffset - start; +} diff --git a/gfx/skia/skia/src/utils/SkInterpolator.cpp b/gfx/skia/skia/src/utils/SkInterpolator.cpp new file mode 100644 index 0000000000..6bf2335f03 --- /dev/null +++ b/gfx/skia/skia/src/utils/SkInterpolator.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2008 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/utils/SkInterpolator.h" + +#include "include/core/SkMath.h" +#include "include/private/SkFixed.h" +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" +#include "src/core/SkTSearch.h" + +SkInterpolatorBase::SkInterpolatorBase() { + fStorage = nullptr; + fTimes = nullptr; + SkDEBUGCODE(fTimesArray = nullptr;) +} + +SkInterpolatorBase::~SkInterpolatorBase() { + if (fStorage) { + sk_free(fStorage); + } +} + +void SkInterpolatorBase::reset(int elemCount, int frameCount) { + fFlags = 0; + fElemCount = SkToU8(elemCount); + fFrameCount = SkToS16(frameCount); + fRepeat = SK_Scalar1; + if (fStorage) { + sk_free(fStorage); + fStorage = nullptr; + fTimes = nullptr; + SkDEBUGCODE(fTimesArray = nullptr); + } +} + +/* Each value[] run is formated as: +